JAVA1.7 1.8 HashMap concurrentHashMap源码阅读

hashMap中 table是一个含有 Entry 的数组;初始化大小为initialCapacity,其中Entry是一个含有hash值。key,value,next的entry的 自定义结构。其中桶的概念就是一个table数组中的一个元素,而形成的一个链表;

  • 1.7 hashMap put(K key, V value)方法
public V put(K key, V value) {
        //初始化一个比阈值大,且最接近的threshold大小两倍的空的数组
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        //根据key的hash值,和table长度,返回一个table数组的下标
        int i = indexFor(hash, table.length);
        //得到这个table[i](也就是所谓的一个桶)的第一个元素e,然后遍历这个桶,
        for (Entry e = table[i]; e != null; e = e.next) {
            Object k;
            //为了返回相同key的原来的value值;并把新value放进去,返回原来的value
            //如果存在相同相同的hash值 和key;则返回原来存储的key值对应的value值
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        
        modCount++;
        //如果第一个的元素e 为空,
        addEntry(hash, key, value, i);
        return null;
    }
    
    private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        int capacity = roundUpToPowerOf2(toSize);

        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }
    
    void addEntry(int hash, K key, V value, int bucketIndex) {
        //当前数组大小大于阈值;并且这个table数组中的一个元素table[bucketIndex](桶)不空null,
        进行数组2倍大小扩容;重新计算要添加元素的的数组下标(桶)
        if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }
        //桶这个链表的插入顺序为从;从头开始插入
        //添加一个新的的entry 分两种情况;1.桶的第一个entry为空;创建entry,放入桶的第一个位置
        //2.桶的第一个元素不为空;拿到桶的第一个元素;新建一个元素(entry),放入桶的第一个位置;并把桶原来的第一个元素e利用链表的形式,链接到新放入元素的后面
        createEntry(hash, key, value, bucketIndex);
    }
    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

  • 1.7 hashMap get(Object key) 方法
public V get(Object key) {
    if (key == null)
        return getForNullKey();
    Entry entry = getEntry(key);

    return null == entry ? null : entry.getValue();
}

final Entry getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        //根据hash 定位到桶;遍历桶;相同返回
        for (Entry e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

  • 1.8 hashMap put(K key, V value)
public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    //hashMap中,将原来的链表修改为了红黑树
    //TREEIFY_THRESHOLD 当这个阈值超过8之后,就变为了红黑树
    //HashEntry 修改为 Node
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node[] tab; Node p; int n, i;
    //当table 为空时,初始化table
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //当地一个桶的节点为空时,初始化第一个桶的节点
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node e; K k;
        //此处为桶的第一个节点,hash key  和新插入节点的 hash key 相等的时,将原来的节点值 赋值给 e;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        //当节点是一个TreeNode的时候,使用红黑树的方式,存储该节点
        //如果key值相同,返回原来的节点
        else if (p instanceof TreeNode)
            e = ((TreeNode)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 为null
        //如果原节点不为null,返回原节点的value,并将新value赋值给原节点的value
        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.8 hashMap get(Object key)方法
public V get(Object key) {
        Node e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
final Node getNode(int hash, Object key) {
    Node[] tab; Node first, e; int n; K k;
    //为null 直接返回 null
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        //第一个节点的判断;第二个节点存在红黑树的情况,区别对单
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        //第二个节点
        if ((e = first.next) != null) {
            //为红黑树
            if (first instanceof TreeNode)
                return ((TreeNode)first).getTreeNode(hash, key);
            do {
                //链表 循环
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}
  • 1.7 concurrentHashMap put(K key, V value)方法
//ConcurrentHashMap初始化时,计算出Segment数组的大小ssize和每个Segment中HashEntry数组的大小cap
public V put(K key, V value) {
    Segment s;
    if (value == null)
        throw new NullPointerException();
    int hash = hash(key);
    int j = (hash >>> segmentShift) & segmentMask;
    //根据key值定位到segment
    if ((s = (Segment)UNSAFE.getObject          // nonvolatile; recheck
         (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
        s = ensureSegment(j);
        //调用segment的put()方法
    return s.put(key, hash, value, false);
}

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            //尝试获取锁
            HashEntry node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                HashEntry[] tab = table;
                int index = (tab.length - 1) & hash;
                //定位到元素所在链表的第一个元素
                HashEntry first = entryAt(tab, index);
                for (HashEntry e = first;;) {
                    if (e != null) {
                        K k;
                         //只是针对存在相同key的情况
                        if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            //是否用新值,替换相同key的原来的value
                            if (!onlyIfAbsent) {
                                //修改相同key值节点的的value 值
                                e.value = value;
                                //修改次数 加 1
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                        if (node != null)
                            //从scanAndLockForPut()中得到的节点非空;桶的头结点插入第一个元素
                            node.setNext(first);
                        else
                            //(11 本方法节点) node为空创建新节点,并设置他的下一个节点 first节点  
                            node = new HashEntry(hash, key, value, first);
                        int c = count + 1;
                        //扩容
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            //根据本方法内新建的节点(11 处新建节点)加入到table中,形成链表
                            //setEntryAt()操作以实现对链头的延时写,以提升性能,因为此时并不需要将该更新写入到内存
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                unlock();
            }
            return oldValue;
        }
        //这个方法的主要作用是在 未 获取锁的情况下,一直缓存缓存当前链表的节点到cpu缓存,提高缓存命中率;
        //这个方法的重点不是在找到节点或者新建节点,而是为了缓存;尽可能多的缓存节点;顺便找到了相同的节点
        //或者为空,创建了新节点,这是很好的。如果没有也没事,而是在本方法scanAndLockForPut的调用的方法put内重新创建节点。
        //在未获取锁的情况下,
private HashEntry scanAndLockForPut(K key, int hash, V value) {
            //定位到是哪个桶的第一个节点
            HashEntry first = entryForHash(this, hash);
            HashEntry e = first;
            HashEntry node = null;
            //遍历链表标志
            int retries = -1; // negative while locating node
            //重点是这个while循环; 条件是 未 获取到锁;进行循环
            //不管有没有找到相同key的节点或者新建节点,只要被其他线程唤醒,立刻返回当前节点
            while (!tryLock()) {
                HashEntry f; // to recheck first below
                if (retries < 0) {
                    //如果第一个节点为null
                    if (e == null) {
                        //新节点也为null
                        //一直遍历,到为空的尾节点,新建节点
                        if (node == null) // speculatively create node
                            node = new HashEntry(hash, key, value, null);
                        retries = 0;
                    }
                    //找到节点也不在遍历
                    else if (key.equals(e.key))
                        retries = 0;
                    else
                        //节点循环
                        e = e.next;
                }
                
                else if (++retries > MAX_SCAN_RETRIES) {
                    lock(); //进入加锁方法,失败则会进入排队,阻塞当前线程,直到被唤醒;不在去循环
                    break;
                }
                //若遍历过程中,由于其他线程的操作导致链表头结点变化,则需要重新遍历。
                else if ((retries & 1) == 0 &&
                         (f = entryForHash(this, hash)) != first) {
                    e = first = f; // re-traverse if entry changed
                    retries = -1;
                }
            }
            return node;
        }
  • 1.8 concurrentHashMap put(K key, V value)方法
  • 1.8 concurrentHashMap get(K key)方法
public V get(Object key) {
    Segment s; // manually integrate access methods to reduce overhead
    HashEntry[] tab;
    int h = hash(key);
    long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
    //根据hash定位到segment
    if ((s = (Segment)UNSAFE.getObjectVolatile(segments, u)) != null &&
        (tab = s.table) != null) {
        //在根据hash 再定位到链表头,进行遍历
        for (HashEntry e = (HashEntry) UNSAFE.getObjectVolatile
                 (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
             e != null; e = e.next) {
            K k;
            if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                return e.value;
        }
    }
    return null;
}

参考
https://my.oschina.net/7001/blog/896587
https://zhuanlan.zhihu.com/p/40327960
https://blog.csdn.net/fw0124/article/details/43308193
https://juejin.im/entry/59bfa3106fb9a00a652afd2f
https://juejin.im/entry/59fc786d518825297f3fa968

你可能感兴趣的:(JAVA1.7 1.8 HashMap concurrentHashMap源码阅读)