2020-01-10
Java鸠合概述(上)

Java鸠合概述(上)

媒介

先说说,为何要写这么一篇博客(我老是喜好写缘由)。因为近来到岁尾了,恰好又要预备口试,所以在做各方面的手艺总结。而Java鸠合是Java非常重要的一部份,本身前前后后也花了不少时刻进修,然则一向比较零星。所以,盘算趁着这个时机,来写一个总结。

因为才能有限,这方面没有充足积聚,假如有什么问题,还请指出。感谢。

鸠合分类,重要分为:

  • Collection(继承Iterable接口):根据单个元素存储的鸠合
    • List:一种线性数据构造的重要表现。有序,可反复
    • Set:一种不允许涌现反复元素的鸠合。无序(插进去递次与输出递次不一致),不可反复
    • Queue:一种先进先出(FIFO)的数据构造。有序,可反复,先进先出
  • Map(无继承接口):根据K-V存储的Map
    • keySet:能够检察一切的Key。底层完成各不雷同。ConcurrentHashMap则是采纳的自定义完成的KeySetView内部静态类(完成了Set接口),而HashMap如许的AbstractMap子类,则是是Set接口
    • values:同上,ConcurrentHashMap采纳ValueSetView,HashMap采纳Set接口
    • entrySet:同上,ConcurrentHashMap采纳EntrySetView,HashMap采纳Set接口

底本Map是盘算根据 AbstractMap;SortedMap;ConcurrentMap;来分类,然则发明这个分类属于理论代价,大于使用代价,也多是我如今条理不够吧。末了照样学着孤尽大佬在《码处高效》中那样,经过历程三个视图,来视察Map。详细背面叙述,我也只是叙述个中部份的Map。

叙述方面,我重要会从数据组织体式格局(底层数据存储体式格局),数据处置惩罚体式格局(如HashMap的put操纵等),特征小结结三个方面举行叙述。然则因为内容量的问题,这里并不会非常仔细地叙述代码完成。

末了,因为内容量的原因,这部份内容,我将分为两个部份。这篇博客重要叙述List与Map,而Set与Queue放在别的一篇博客。

一,List

ArrayList

数据组织体式格局


    transient Object[] elementData; // non-private to simplify nested class access

ArrayList的底层是一个Object范例的数组。那末ArrayList就有着和数组一样的特征:随机查询快,但数据的插进去,删除慢(因为极大概须要挪动其他元素)。

数据处置惩罚体式格局

add

    public void add(int index, E element) {
        // 校验index是不是在0-size局限内,假如不是,抛出非常IndexOutOfBoundsException
        rangeCheckForAdd(index);

        // 这个操纵背面有多个操纵,总结一下,就是校验,推断是不是须要扩容,扩容。
        ensureCapacityInternal(size   1);  // Increments modCount!!
        // 经过历程System.arraycopy操纵,为新增加的元素element,在elementData数组的对应index位置,腾出空间
        System.arraycopy(elementData, index, elementData, index   1,
                         size - index);
        // 紧跟着上面的操纵elementData数组的index位置,赋值为element
        elementData[index] = element;
        // 数组元素数目 1
        size  ;
    }
grow

    // 简朴来讲, 就是根据所给的minCapacity,盘算对应容量(2的幂次方),然后校验容量,末了扩容
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity   (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

小结

根据其数据组织体式格局,与数据处置惩罚体式格局,能够明白:

  • ArrayList随机查询快(直接经过历程index定位数据中详细元素)
  • ArrayList插进去与删除操纵慢(触及数组元素挪动操纵System.arraycopy,还大概触及扩容操纵)
  • ArrayList是容量可变的(自带扩容操纵,初始化,默以为DEFAULT_CAPACITY=10)
  • ArrayList黑白线程平安的(没有线程平安措施)

补充:

  • ArrayList的默许容量为10(即无参组织时)
  • 出于机能斟酌,防止屡次扩容,最幸亏初始化时设置对应size(纵然背面不够了,它也能够自动扩容)

LinkedList

数据组织体式格局


    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

LinkedList的底层是自定义的Node双向链表。那末LinkedList就有着和链表一样的特征:数据的插进去与删除快,然则随机接见慢。

数据处置惩罚体式格局

add

    public void add(int index, E element) {
        // 数据校验,index是不是超越0-size局限
        checkPositionIndex(index);

        if (index == size)
            // 假如插进去的元素是放在末了一个,那就实行尾插进去操纵(因为LinkedList是有保存first与last两个Node的,所以能够直接操纵)
            linkLast(element);
        else
            // 起首经过历程node(index)要领,猎取到当前index位置的Node元素(内部完成,照旧是遍历。不过会根据index与列表中值的比较效果,推断是从first入手下手遍历,照样从last入手下手遍历),再经过历程linkBefore要领,举行插进去操纵
            linkBefore(element, node(index));
    }
peek
    
    // LinkedList完成了Deque接口,所以须要完成个中的peek要领。猎取当前数组的第一个元素,但不举行删除操纵
    public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

小结

根据其数据组织体式格局,与数据处置惩罚体式格局,能够明白:

  • LinkedList随机查询慢(须要举行遍历查询,虽然经过历程列表中值,下降了一半的遍历局限,但其数据组织体式格局决议了它的速率慢):

    测试表明,10W条数据,LinkedList的随即提取速率与ArrayList比拟,存在数百倍的差异(引自《码出高效》)

  • LinkedList插进去与删除操纵快(照旧须要靠遍向来定位目的元素,但只须要修正链表节点的前后节点援用)
  • LinkedList是容量可变的(链表能够随便链接)
  • LinkedList黑白线程平安的(没有线程平安措施)

补充:

  • 经过历程链表,能够有用地将零星的内存单位经过历程援用的体式格局串连起来,构成按链路递次查找的线性构造,内存应用率较高(援用自《码出高效》)

Vector

Vector实质与ArrayList没太大区分,底层同样是Object数组,默许大小照旧为10(不过Vector采纳的是不引荐的魔法数字)。

唯一的区分,就是Vector在症结要领上增加了Sychronized症结字,来确保线程平安。

然则,因为处置惩罚得较为粗拙,以及其特征,所以机能很差,基础已被扬弃。

这里就不再赘述了。

CopyOnWriteArrayList

CopyOnWriteArrayList,作为COW容器的一员,其头脑就是空间换时刻,重要针对读多写少的场景。当有元素写入时,会新建一个数组,将原有数组的元素复制过来,然后举行写操纵(此时数组的读操纵,照样针对原数组)。在写操纵完成后,会将读操纵针对的数组援用,从原数组指向新数组。如许就能够在写操纵举行时,不影响读操纵的举行。

数据组织体式格局


    /** The array, accessed only via getArray/setArray. */
    // 一方面经过历程transient防止序列化,另一方面经过历程volatile确保可见性,从而确保单个属性(这里是援用变量)的线程平安
    private transient volatile Object[] array;

数据处置惩罚体式格局

add

    public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        // 举行加锁,同时只能有一个写操纵
        // 别的,加锁操纵放在try块外,一方面是try范例(lock操纵并不会发作非常,而且能够削减try块大小),另一方面是防止加锁失利,finally的开释锁涌现IllegalMonitorStateException非常
        lock.lock();
        try {
            // 猎取原有数组,并赋值给elements(援用变量)
            Object[] elements = getArray();
            int len = elements.length;
            // 数据校验
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: " index 
                                                    ", Size: " len);
            // 下面的操纵,就是对原有数组举行复制,并赋值给newElements(而且留出index位置)
            Object[] newElements;
            int numMoved = len - index;
            if (numMoved == 0)
                newElements = Arrays.copyOf(elements, len   1);
            else {
                newElements = new Object[len   1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index   1,
                                 numMoved);
            }
            // 设置新数组index位置的值为element,完成赋值操纵
            newElements[index] = element;
            // 将数组援用(读操纵正在读的数组援用)改成newElements
            setArray(newElements);
        } finally {
            // 不管是不黑白常,都须要开释锁,
            lock.unlock();
        }
    }

最大的特征,就是这部份了。至于remove操纵,都是相似的。故不再赘述。

小结

因为CopyOnWriteArrayList的数据组织体式格局与ArrayList一致,也是采纳的数组,故:

  • CopyOnWriteArrayList随机查询快
  • CopyOnWriteArrayList插进去与读写慢
  • CopyOnWriteArrayList是容量可变的(每次举行增删的写操纵,都邑新建一个数组,进而举行替代)

补充:

  • CopyOnWriteArrayList是线程平安的(读写操纵断绝,写操纵经过历程ReentrantLock确保线程平安)
  • CopyOnWriteArrayList的写操纵不直接影响读操纵(二者在内存上针对的不是统一个数组)
  • CopyOnWriteArrayList只适用于读多写少场景(毕竟写操纵是须要复制数组)
  • CopyOnWriteArrayList占有双倍内存(因为写操纵的时刻须要复制数组)
  • CopyOnWriteArrayList的机能会跟着写入频次与数组大小上升,而疾速下落(写入频次m x 数组大小n)

引荐:高并发请求下,能够攒一下要举行的写操纵(如增加,或删除,能够离开保存),然后举行addAll或removeAll操纵。如许能够有用减低资本斲丧。然则这个攒的度须要好好把握,就和请求兼并一样,须要好好衡量。

二,Map

TreeMap

数据组织体式格局

数据处置惩罚体式格局

小结

HashMap

HashMap一方面是事情顶用的非常多的鸠合,另一方面是口试的高频(我每次口试险些都邑被人问这个)。

而HashMap,与ConcurrentHashMap一样,都存在Jdk8之前与Jdk8以后的区分。不过,我应该会以Jdk8以后为重点,毕竟如今SpringBoot2.x都请求Jdk8了。

数据组织体式格局

Jdk8之前

    // jdk8之前,其底层是数组 链表
    // 链表底层Entry是Map的内部接口
    transient Entry<K, V>[] table;
Jdk8以后

    transient Node<K, V>[] table;


    static class Node<K, V> implements Map.Entry<K, V> {
        final int hash;
        final K key;
        V value;
        Node<K, V> next;
    }

数据处置惩罚体式格局

Jdk8之前的put要领(诠释并不多,因为我没有源码,我是根据笔记图片,手撸的这段)

    public V put (K key, V value) {
        // HashMap采纳耽误建立。推断当前table是不是为空。假如为空,就根据默许值15,建立一个数组,并赋值给table
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        // 数据校验
        if ( key == null) 
            return putForNullKey(value);
        // 根据key,盘算哈希值
        int hash = hash(key);
        // 经过历程indexFor(内部貌似采纳位运算),根据key的哈希值与数组长度,盘算该K-V键值对在数组中的下标i
        int i = indexFor(hash, table.length);
        for (Entry<K, V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash = hash && ((k = e.key) || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

            // 纪录修正次数 1,相似版本号
        modCount  ;
        addEntry(hash, key, value, i);
        return null;
    }
Jdk8以后的put要领

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


    // 盘算key的哈希值(数据校验,key的哈希值,即其hashCode)
    static final int hash(Object key) {
        int h;
        // 经过历程其hashCode的高16位与其低16位的异或运算,既下降体系机能开支,又防止高位不列入下标运算形成的碰撞
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }


    // 实行重要put操纵
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        // 从下面这个代码块,能够看出Java8后的HashMap等,代码艰涩不少
        if ((tab = table) == null || (n = tab.length) == 0)
            // 假如table为null,或table.length为0(个中混淆了赋值语句),就举行举行初始化操纵(经过历程resize()操纵,这点与Spring的refresh()应用是一致的),并将其长度赋值给n(注重这里,都赋值给了部分变量,而非全局变量)
            n = (tab = resize()).length;
        // 根据key的hash值,盘算其下标,并推断数组中对应下标位置是不是为null
        if ((p = tab[i = (n - 1) & hash]) == null)
            // 假如对应位置为null,直接经过历程newNode要领(生成Node),设置数组对应i位置为对应新Node
            tab[i] = newNode(hash, key, value, null);
        else {
            // 假如对应位置不为null,那就须要举行链表操纵,进而推断是不是树化(红黑树),是不是扩容等
            Node<K,V> e; K k;
            // 经过历程hash与equals等,推断新增加值的key与已存在值的key是不是真正相称
            // 这里扩大两点:第一,推断对象是不是相称,必需hashcode与equals都推断相称。前者防止两个对象只是值,但不是统一个对象(两位都是p9大佬,不代表两位就是统一个人)。后者防止哈希碰撞问题(纵然是两个差别的对象的内存地址,也大概哈希值相称)
            // 第二,我看到这里的时刻,比较忧郁,会不会涌现value相称,然则hashCode差别,致使这里推断为false。然后我发明包装范例,早就重写了hashCode要领,如Integer的hashCode就直接返回value
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                // 假如相称,就直接更新对应Node即可
                e = p;
            // 假如上面推断失利,则推断原有的数组元素,是不是是已树化(不再是Node范例,而是TreeNode,固然TreeNode照旧是由Node构成的)
            else if (p instanceof TreeNode)
                // 假如原有数组元素已树化,那末就举行挪用putTreeVal要领,将当前元素,置入目的红黑树中(个中触及红黑树的扭转等操纵)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            // 假如不是空,也不是雷同元素,更不是红黑树,那申明那已是一个链表(已由多个元素),或行将成为链表(已有一个元素,并行将增加一个新的元素)
            else {
                // 遍历对应链表元素,并经过历程binCount纪录链表已存在的元素数
                for (int binCount = 0; ;   binCount) {
                    // 假如e=p.next()为null,申明到达了链表的末了(e的前一个值为当前链表的末了一个元素)
                    if ((e = p.next) == null) {
                        // 经过历程newNode取得对应p的Node,并将其设置为链表的末了一个元素
                        p.next = newNode(hash, key, value, null);
                        // 经过历程binCount,推断链表的长度是不是到达了树化的阈值
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            // 到达阈值,则经过历程当前table数组与hash值,以及treefyBin要领,将当前数组位置的链表树化
                            treeifyBin(tab, hash);
                        break;
                    }
                    // 在遍历历程当中,找到了雷同的元素,即跳过(因为内容雷同)
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    // 该赋值操纵,属于链表的操纵,从而继承链表遍历
                    p = e;
                }
            }
            // 下面这段代码,就触及到HashMap的putIfAbsent(也是挪用putVal,只是第四个参数onlyIfAbsent差别)
            // 简朴来讲,就是碰到key雷同的元素,怎样处置惩罚。put操纵是直接赋值,而putIfAbsent则是推断对应key的value是不是为null,假如是null,才会赋值。不然就稳定(相似Redis)
            // 只不过,这个历程经过历程新增的第四个参数掌握,从而确保统一套代码(putVal要领),完成两种差别功用(put与putIfAbsent)
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        // 版本号
          modCount;
        // 一方面size前缀自增,另一方面,推断自增后的size是不是凌驾阈值(默许16*0.75=12,数组容量*负载因子)
        if (  size > threshold)
            // 扩容(扩容2倍后,重排)
            resize();
        // 空要领,为子类保存的,如LinkedHashMap
        afterNodeInsertion(evict);
        return null;
    }

这个要领能够算是HashMap的中心,毕竟经过历程这个要领,也算是摸到了HashMap的运行机制了。

流程简述:

  1. 假如HashMap的底层数组没有初始化,则经过历程resize()要领举行构建
  2. 对key盘算hash值,然后再盘算下标
  3. 假如数组对应下标位置为null(这里我以为不该用哈希碰撞),则直接放入对应位置
  4. 假如数组对应下标位置为TreeNode(即对应位置已树化),则经过历程putTreeVal要领,将对应Node置入树中
  5. 不然遍历数组对应下标位置的链表,将对应Node置入
  6. 假如链表的长度凌驾阈值,则举行树化操纵
  7. 假如节点存在旧值,直接替代
  8. 假如数组的元素数目凌驾阈值(数组容量*负载因子),则举行扩容(扩容2倍,重排)
Jdk8以后的get要领

    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }


    // 这里我以为没什么说的。根据差别状况,分别从数组,红黑树,数组来猎取目的元素
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        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<K,V>)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;
    }

小结

就使用场景而言,《码出高效》给出如许一句话:

除部分要领或相对线程平安的情况外,优先引荐ConcurrentHashMap。二者虽然机能相差无几,但后者处理了高并发下的线程平安问题。HashMap的死链问题及扩容数据丧失问题是慎用HashMap的两个重要缘由。

这里,我不由得站在Java工程师的角度,引荐《码出高效》以及配套的《阿里Java开发手册》。作为一位也算看过不少手艺书本的开发者,这两本书在我这儿,也算得上是优异书本了。

不过,文中也提到,这类情况,在Jdk8以后有所修复,改良。详细的,能够看看书本(重要内容有点多)。

ConcurrentHashMap

ConcurrentHashMap部份,我将只形貌Jdk8以后的版本。

而Jdk8之前的版本,实在底层就是相似HashTable的Segament构成的数组。经过历程分段锁,杀青线程平安。算是HashTable与HashMap的折衷计划。庞杂度并非很高,不过Jdk8以后的版本,就较为庞杂。起首,引入红黑树,优化存储构造。其次,作废原有的分段锁设想,采纳了更高效的线程平安设想计划(应用了无锁操纵CAS与头节点同步锁等)。末了,使用了更优化的体式格局统计鸠合内的元素数目(援用自《码出高效》,我还真没注重到这点)。

数据组织体式格局


    transient volatile Node<K,V>[] table;


    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;

        Node(int hash, K key, V val, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.val = val;
            this.next = next;
        }

        // 此处省略其内部要领,感兴趣的,能够自行检察
    }

从上述来看,ConcurrentHashMap的底层数据组织为数组 链表。根据Jdk8后的HashMap,能够推想,在对应条件下,链表会转为红黑树构造。现实也是云云,请看下代码。


    static final class TreeNode<K,V> extends Node<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;

        TreeNode(int hash, K key, V val, Node<K,V> next,
                 TreeNode<K,V> parent) {
            super(hash, key, val, next);
            this.parent = parent;
        }

        // 此处省略其内部要领,感兴趣的,能够自行检察
    }

ConcurrentHashMap,与HashMap一样,其内部也有特地为红黑树效劳的TreeNode。

所以,从数据组织方面来看,实在ConcurrentHashMap与同版本的HashMap,能够说就是一个模型刻出来的(毕竟都是Doug Lea带着撸的)。

二者的区分,或许说ConcurrentHashMap的精巧的地方,就在于ConcurrentHashMap对多线程的斟酌与处置惩罚。

个中的细节挺多的,我只叙述我对个中一些大头的明白(因为许多细节,我也不知道,也是看了大佬的总结,才发明)。

数据处置惩罚体式格局

put

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

    /** Implementation for put and putIfAbsent */
    final V putVal(K key, V value, boolean onlyIfAbsent) {
        // 数据校验,假如key或value为Null,直接NPE
        if (key == null || value == null) throw new NullPointerException();
        // 经过历程spread要领,盘算hash值(实质照样与HashMap一样,针对hashCode举行上下16位异或盘算等)
        int hash = spread(key.hashCode());
        // 纪录链表长度
        int binCount = 0;
        // 这里的轮回操纵是为了以后的CAS操纵(就是CAS的自旋操纵)
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                // 同HashMap一样,假如数组为空或长度为0,则举行数组初始化操纵(轮回头中已完成赋值操纵)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                // 假如数组对应位置为null,则经过历程CAS操纵,举行值的插进去操纵
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            // 假如对应节点的Node.hash值为MOVED=-1
            else if ((fh = f.hash) == MOVED)
                // 举行resize辅佐操纵(详细辅佐体式格局,还没研讨)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        // 假如数组对应位置(即首节点)的哈希值大于等于零(树化后等状况下,对应位置哈希值小于零)
                        //  static final int MOVED     = -1; // hash for forwarding nodes
                        //  static final int TREEBIN   = -2; // hash for roots of trees
                        //  static final int RESERVED  = -3; // hash for transient reservations
                        if (fh >= 0) {
                            // 申明此状况下,数组对应位置,存储的是链表。举行链表插进去,遍历操纵(详细参照HashMap的put操纵)
                            binCount = 1;
                            for (Node<K,V> e = f;;   binCount) {
                                K ek;
                                if (e.hash == hash &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    oldVal = e.val;
                                    if (!onlyIfAbsent)
                                        e.val = value;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    pred.next = new Node<K,V>(hash, key,
                                                              value, null);
                                    break;
                                }
                            }
                        }
                        // 假如数组对应位置的元素,是树化节点(即为TreeBin实例)
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            // 挪用putTreeVal要领,举行红黑树的值插进去操纵
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                // 推断onlylfAbsent参数,举行val设置。详细参照HashMap的put要领的对应位置诠释
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                    }
                }
                // 前面的各种操纵,都邑盘算binCount(数组当前位置存储的节点数)
                if (binCount != 0) {
                    // 假如对应节点数凌驾了树化阈值TREEIFY_THRESHOLD=8
                    if (binCount >= TREEIFY_THRESHOLD)
                        // 对数组当前位置,举行树化操纵
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
            // 计数
        addCount(1L, binCount);
        return null;
    }

小结

ConcurrentHashMap的魅力在于其线程平安的完成,有时机好好研讨研讨,特地写一个相干的博客。

三,总结

实在,Java鸠合重要从两个维度剖析。一个是底层数据组织体式格局,如链表与数组(基础就这两种,或许如HashMap那样组合两种)。另一个是线程平安体式格局,就是线程平安与非线程平安。

末了就是因为一些底层数据组织体式格局的调解,带来的轮回,有序等特征。


更多内容请关注十大外围足彩网站