深度解析HashSet.add执行过程

深度解析HashSet.add执行过程

一、创建新的HashSet对象。

我们先看一段代码,这里调用了HashSet的无参构造方法,创建了一个新的对象,将对象的引用赋值给了它实现的接口Set。

Set<String> set=new HashSet<String>();

调用HashSet的无参构造,实际上是调用了HashMap的无参构造初始化了成员变量map。

public HashSet() {
    map = new HashMap<>();
}

二、HashSet.add过程

这里我们看一下JDK1.8 里面add的方法源码:

set.add("Jim");

调用HashSet里的add方法添加元素,这里的e为我们要传入的对象“Jim”,它 依然是调用的是hashMap里的put方法,如果该方法结果return map.put(e, PRESENT)==null返回值为true,map.put(e,PERSENT)返回的结果为空。

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

进入put方法后,这里又调用了HashMap里的putkey,之前e里的“Jim”传入到put里的key中,putVal()传入的第一个参数调用了hash()方法。

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

三、hash()的实现原理;

hash接收到的参数类型为object,这里传入的key被上转型为object类型。

hash的返回值,如果传入的key值为空则返回0,key值不为空则调用object里的HashCode方法,这里由于string重写了object里的hashCode()方法,所以调用的是string里的hashCode()方法,然后进行右移位运算,在于之前得到的hashCode()进行按位异或运算得到hash值。

static final int hash(Object key) {
    int h;    
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); 
}

四、HashMap中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为新对象数组tab赋值,并判断其是否为空。
    //数组tab的长度赋值给n,并判断其是否为空。
    //如果有一个条件成立,tab指向了一个长度为16的数组将其长度赋值给n。
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //根据hash与(n-1)的按位与计算出Jim在tab数组中的下标。赋值给p,判断其是否为空
    //因为String中重写了hashCode的方法,如果String类型中两个值相同则hash值相同。
    if ((p = tab[i = (n - 1) & hash]) == null)
         /*
            数组内没有将要添加的元素
         */
    
        //如果传入hash后tab[i]的元素为空,则为该数组添加该元素。
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        /*
            数组中含有将要添加的元素
         */  
        //如果传入hash后tab[i]的元素不为空,调取这个数组元素的hash值与传入的hash作比较
        if (p.hash == hash &&
        //同时与(这个元素的key地址值赋值给k,再与传入的key地址值)作比较
        //或者传入的key地址值不为空同时传入的key和本数组key元素赋值的k作比较
        //因为String中同样重写了equals方法,实质是比较字符转内容后是否相同。
            ((k = p.key) == key || (key != null && key.equals(k))))
            //那么就将已存在的p值赋值给e
            e = p;
            
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            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;
            }
        }
        //然后然后就到了这里,通过上面的逻辑,如果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;
}

1.如果传入的key值与数组内的元素相同,则return返回的值最后传入hashSet的add方法中,map.put(e, PRESENT)==null,因为返回值不为空,所以结果为false。

2.如果传入的key值与数组内的元素不相同,则上述结果返回return null;,返回的值最后传入hashSet的add方法中,map.put(e, PRESENT)==null,因为返回值为空,所以结果为true

//HashMap里的resize方法,第一次添加元素时,会创建一个长度为16的数组。
final Node<K,V>[] resize() {       
    newCap = DEFAULT_INITIAL_CAPACITY; //静态常量,默认容量16。
    newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}

 

 

上一篇:JavaSE基础八----<集合(3)>【Set接口及其实现类,Set接口的迭代方式】


下一篇:CopyOnWriteArraySet