0
点赞
收藏
分享

微信扫一扫

【JAVA集合V-Set接口实现类HashSet】

code_balance 2022-04-30 阅读 59

JAVA集合V-Set接口实现类HashSet

1.HashSet简单说明

(1)说明

  • HashSet实现了Set接口
  • HashSet实际上是HashMap,看下图源码
  • 可以存放null值,但是只能有一个
  • HashSet不保证元素是有序的,取决于Hash后,再确定索引的结果(不保证存放元素的顺序和取出的顺序一致)。
  • 不能有重复元素/对象,前面Seth中已说明。

在这里插入图片描述
在这里插入图片描述

2.案例说明

package Collection.hashSet;

import java.util.HashSet;


public class hashSetTest2 {

    public static void main(String[] args) {

        HashSet hashSet = new HashSet();

        //1. 在执行 add 方法后,会返回一个 boolean 值
        // 2. 如果添加成功,返回 true, 否则返回 false
        // 3. 可以通过 remove 指定删除哪个对象
        hashSet.add("lucy");//T
        hashSet.add("lucy");//F


        //创建两个Dog对象,名字和age均相同,可以添加成功
        HashSet hashSet1=new HashSet();
        hashSet1.add(new Dog("嘿嘿",3));//T
        hashSet1.add(new Dog("嘿嘿",3));//T
        for (Object o : hashSet1) {
            System.out.println(o);
        }


        //面试题:利用String创建两个对象,内容相同,
        //添加不了的原因:
        //因为String类中重写了hashcode和equals方法
        //而hashset在添加时,会利用hashcode和equals方法比较当前添加对象或元素是否相等

        //当将Dog类中重写hashcode和equals方法后,发现,上面添加时new Dog("嘿嘿",3)
        //也只能添加第一个。

        HashSet hashSet2=new HashSet();
        hashSet2.add(new String("小明"));//可以
        hashSet2.add(new String("小明"));//添加不了
        for (Object o : hashSet2) {
            System.out.println(o);
        }


    }
}
class Dog{
    private String name;
    private int age;

    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Dog dog = (Dog) o;

        if (age != dog.age) return false;
        return name != null ? name.equals(dog.name) : dog.name == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
}

3.hashSet底层源码

HashSet底层是HashMap,HashMap底层是(数组+链表+红黑树)

(1)HashSet添加元素的方式

  1. 先获取元素的哈希值(hashcode方法以及一个右移操作">>>"),源码计算公式 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  2. 对哈希值进行运算,得出一个索引值即为要存放哈希表中的位置号
  3. 如果该位置上没有其他元素,则直接存放;如果该位置上已经有元素,则需要进行equals判断,如果相等,则不再添加;如果不相等,则以链表的方式添加。

如下图解:

在这里插入图片描述

(2)HashSet底层解析

  1. HashSet底层是HashMap
  2. 添加一个元素时,先得到hash值-会转成索引值
  3. 找到存储数据表table,看这个索引位置是否已经存放有元素
  4. 如果没有,直接加入
  5. 如果有,调用equals比较,如果相同,就放弃添加;否则就添加到最后
  6. 在java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)。

在这里插入图片描述
涉及源码有些复杂,简单介绍:

package Collection.hashSet;

import java.util.HashSet;

public class hashSetTest {
    public static void main(String[] args) {


        HashSet set = new HashSet();
        set.add("java");
        set.add("php");
        set.add("java");
        System.out.println("set="+set);
    }

    /*
    * HashSet 底层源码剖析
    * 1,执行构造器hashset()
    *   public HashSet() {
           map = new HashMap<>();
         }
      2,执行add方法
        public boolean add(E e) {
            return map.put(e, PRESENT)==null;//PRESENT是一个object类,仅仅
            用于占位,为了使用hashMap
        }
      3,执行put()方法,会得到一个key的hash值, 算法:return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        public V put(K key, V value) {
          return putVal(hash(key), key, value, false, true);
        }

      4,执行putVal()方法

               final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                           boolean evict) {
                Node<K,V>[] tab; Node<K,V> p; int n, i;
                //table就是hashMap的一个数组,类型为node[]
                //if语句的作用是为table赋值,如果table为空,如进入resize(),为
                //table赋值16个大小的空间
                if ((tab = table) == null || (n = tab.length) == 0)
                    n = (tab = resize()).length;
                //根据key的hash值定位table表的位置,判断改位置是否为空,
                //如果为空执行if语句内容,则创建一个node<key="java",value=PRESENT>;
                //否则,执行else语句,判断hash值和equals.
                if ((p = tab[i = (n - 1) & hash]) == null)
                    tab[i] = newNode(hash, key, value, null);
                else {
                    Node<K,V> e; K k;
                    //当前索引对应的table中的位置的第一个元素的hash值与当前key的hash值比较p.hash == hash
                    //同时比较对应的key的是否相等,(key != null && key.equals(k))
                    //或者key的地址是否相等(可能传入的是对象)(k = p.key) == key。
                    if (p.hash == hash &&
                        ((k = p.key) == key || (key != null && key.equals(k))))
                        e = p;
                    //在判断p是不是一颗红黑树
                    //如果是一颗红黑树,就调用putTreeVal来进行添加
                    else if (p instanceof TreeNode)
                        e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                    else {
                    //(1)依次和该链表的每一个元素比较后,都不相同,则加入该链表的最后
                        注意:在把元素添加到链表后,立即判断该链表是否已经到该链表的最后
                        ,就调用treeifyBin()对当前这个链表进行树化(转成红黑树)
                        注意,在转成红黑树时,要进行判断数组长度是否大于64,如果没有
                        继续扩容,直到数组长度为64,且链表长度等于8时,转成红黑树。
                    //(2)依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break.
                        for (int binCount = 0; ; ++binCount) {
                            if ((e = p.next) == null) {
                                p.next = newNode(hash, key, value, null);
                                if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                                    treeifyBin(tab, hash);
                                break;
                            }
                            if (e.hash == hash &&
                                ((k = e.key) == key || (key != null && key.equals(k))))
                                break;
                            p = e;
                        }
                    }
                    if (e != null) { // existing mapping for key
                        V oldValue = e.value;
                        if (!onlyIfAbsent || oldValue == null)
                            e.value = value;
                        afterNodeAccess(e);
                        return oldValue;
                    }
                }
                ++modCount;
                if (++size > threshold)
                    resize();
                //留给子类去实现的方法
                afterNodeInsertion(evict);
                return null;
            }

    *
    *
    *
    * */
}
举报

相关推荐

0 条评论