集合讲解

200 阅读16分钟

ArrayList

ArrayList概览

数组大小默认为10

private static final int DEFAULT_CAPACITY = 10;
ArrayList序列化

之前序列化章节专门介绍过

ArrayList扩容机制
// JDK 1.8
// ArrayList的添加操作里首先会检查是否越界,如果越界则扩容成原来数组大小的1.5倍
// ArrayList的add传入的是地址值副本,也就是说我在外面改变e的属性,ArrayList里的e也会改变!!!
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
public boolean addAll(Collection<? extends E> c) {
    // 但是ArrayList的addAll方法不会,因为源码里面是对传入参数复制了一份并没有使用传入参数的地址
        Object[] a = c.toArray();
    //........
}

// 判断数组是否越界
private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//这里是懒加载机制,添加元素时才初始化数组
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    //这里ArrayList由于添加操作改变了原有结构,modCount++
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

// 扩容
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容大小是原来的1.5倍
    //如果扩容后还是比minCapacity小,说明这是第一次初始化数组,则直接赋值为minCapacity
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //如果扩容的大小大于允许的数组最大值,那就直接赋值为Integer.MAX_VALUE
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    //Arrays.copy()方法封装了system.arrayCopy()方法
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
ArrayList删除机制
//ArrayList删除机制首先检查index是否合法,然后将删除元素后面的元素移动,最后将新数组的最后一位置为null,引发GC回收,然后返回被删除的元素。
public E remove(int index) {
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    elementData[--size] = null; // clear to let GC do its work
    return oldValue;
}
ArrayList的Fail-Fast机制

fail-fast机制中的核心字段就是之前扩容操作中的modcount,在使用iterator遍历集合或者进行序列化的时候如果集合的结构发生了变化,会抛出Concurrent Modification Exception异常。

在使用iterator遍历集合或者序列化之前,会将之前保存过的modcount与当前modcount作比较,如果不同就说明数组结构已经发生了改变,如果继续下去遍历和序列化都将无意义。

public E next() {
                    checkForComodification();
                    int i = cursor;
                    if (i >= SubList.this.size)
                        throw new NoSuchElementException();
                    Object[] elementData = root.elementData;
                    if (offset + i >= elementData.length)
                        throw new ConcurrentModificationException();
                    cursor = i + 1;
                    return (E) elementData[offset + (lastRet = i)];
                }
final void checkForComodification() {
                    if (root.modCount != expectedModCount)
                        throw new ConcurrentModificationException();
                }

LinkedList

LinkedList的底层数据结构是由双向链表实现的,它并没有实现RandomAccess接口,但在add和remove方法中同样可以指定index,并且为了提高效率方法中还会先检查index是离对首比较近还是离队尾比较近

public void add(int index, E element) {
        checkPositionIndex(index);
        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
Node<E> node(int index) {
        // assert isElementIndex(index);
	//检查index是离对首比较近还是离队尾比较近,但都需要遍历!!!
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

LinkedList的插入和删除的效率都很高,都是常数操作,但是不支持RandomAccess,查找操作都需要进行遍历,效率很低。

Vector

//vector有两种创建方式,其中一种可以指定每次扩容增加的空间
public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }
 public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
//vector的扩容机制与ArrayList扩容机制不太一样,ArrayList扩容倍数是1.5,vevtor是2倍
private int newCapacity(int minCapacity) {
        ...
        int oldCapacity = elementData.length;
    //这儿的capacityIncrement就是之前提到的扩容空间,如果我们没有特别指定的话就是0,如果是0要扩容的时候扩容值就是以前old数组的两倍
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        ...
    }

vector的方法都被synchronized关键字修饰,用于多线程同步,但一般情况不建议使用vector,因为效率较低。

效率较低的原因有

  1. 方法都是线程安全的
  2. 每次扩容都是扩两倍
  3. 插入删除操作都只能在尾部操作

几种线程安全的List

  1. Vector
  2. 使用工具类collections中的synchronizedList方法返回一个线程安全的List,方法如下
public static <T> List<T> synchronizedList(List<T> list) {
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list) :
                new SynchronizedList<>(list));
    }
//首先对输入参数list进行检查,看是否支持随机访问,比如linkedList就不支持,因为他是链表
//然后返回一个线程安全的list。
  1. CopyOnWriteArrayList

CopyOnWriteArrayList 类在concurrent并发包下, CopyOnWriteArrayList基本思想是读写分离,当对CopyOnWriteArrayList容器进行写操作时,会在自己的线程下复制一份完全相同的容器,然后对复制的容器进行操作,完成写操作后再将原来的引用指向这个复制的容器。

//写操作加锁,读操作get()不加锁
public boolean add(E e) {
    //加锁
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            //在这里容器进行了复制,生成了一个新的数组
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            setArray(es);
            return true;
        }
    }

这样的好处就是在多线程情况下可以并发的读取。但是读的数据会是旧容器里的内容。这样的坏处就是比如当一个线程在执行读操作,另一个线程在执行remove操作时,remove操作完毕size-1,有可能会出现查询index数组越界的情况。总的来说就是不能保持数据的实时一致性。还有一个缺点就是内存消耗过大,在进行写操作时内存区中会同时存在新旧两个对象。会导致频繁的Young GC和Full GC

HashMap

transient int size;//HashMap中实际键值对数量
transient Node<K,V>[] table;//HashMap的数组
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//懒加载,默认数组初始容量16
transient int modCount;//与之前的arratlist一样
final float loadFactor;//负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认负载因子0.75
int threshold;//承载量,当大于承载量会促发扩容操作。threshold = capacity * loadFactor
static final int TREEIFY_THRESHOLD = 8;//红黑树默认阈值8,当链表长度为8时会转为红黑树

HashMap中的hash()方法
//HashMap中是可以存储null值的,如果是null直接放在数组0处
//key值不为null会首先获取key类型的hashcode方法的hash值,然后对这个值无符号右移16位,再与原值异或
//使用^而不使用&或|的原因是因为异或操作更能保留各部分的特征。关于为何要右移16位后面有讲
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;
    //1.第一步先判断数组尺寸是否为空,为空的进行resize()
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
    //2.获取key值得hash值然后确定哈希桶数组的索引,若那个index的‘桶’里连头节点都还没有,
    //将这个值设为头节点。
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            //如果数组index那已经有节点了,进行比较如果与头节点key值相同,替换,并记录被替换的值
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //如果数组index那的节点已经是红黑树的节点了,进入红黑树模式
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //如果key值与头节点不一样,那么就顺着节点一个一个比较直到被比较的节点next为null
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        //如果此时链表长度等于单个链表承载阈值-1的话,需要转化为红黑树结构
                        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有值,说明这次put的值与链表或者红黑树中的某个节点key值一样,e保存了被替换了的			   //节点的值,因为是被替换数组结构并被被改变所以modcount不需要改,e为null才需要改
            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;
    }

hashmap扩容机制
final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
    //newCap,newThr表示接下来一样是创建一个数组然后把原数组内容复制进去
        int newCap, newThr = 0;
        if (oldCap > 0) {
            //如果原数组长度已经达到int最大值了说明不能进行长度扩容了
            //此时只能将负载量扩大
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //如果原数组没有达到最大,将原数组长度扩大两倍然后复制给newCap
            //负载因子不变,所以newThr=oldThr
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
    //这是当oldThr小于等于0的情况,这种情况的发生是初次创建hashMap没有设置threshold值
    //相当于懒加载机制
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
    //新建了初始容量为newCap的Node数组
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            //对原哈希桶数组进行遍历
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    //如果e节点的next节点为空
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    //如果节点是红黑树类型,则把书上的节点放导newTab中
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    //如果next节点不为空,则分为两组,low组和high组
                    else { // preserve order
                        //这里定义的loHead,loTail,hiHead,hiTail本人猜测分别是low和high
                        //因为数组扩容了一倍,并不需要把原数组的索引和链表完完全全照搬到新数组
                        //所以jdk1.8中对其进行了改进
                        //例如有一个hash=18 二进制为0001 0010,还有个hash=2 二进制为0000 0010
                        //原数组长度为16,二进制为0001 0000,那么这两个节点索引都为2
                        //新数组长度为32,二进制为0010 0000,这两个节点一个会放到高位(high)索引
                        //一个会被放到低位(low)索引去。
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            //将节点与原数组长度相与,如果等于0,就跟以前的索引位一样
                            //这里的数据结构就是一个链表,其中头部包含一头一尾两节点
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            //如果不等于0,就放到高位索引上去
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            //!!!!这里就是高位与低位的不同处,上面我们其实可以通过例子运算
                            //得到结果,就是高位会比低位多一个原数组长度
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

这里还有一点值得说的是,jdk1.7之前的resize()方法使用的是头插法,上面jdk1.8我们可以很明显看出是尾插法,头插法的缺点就是当在多线程的使用情况下,出现扩容操作时会因为头插法可能产生环形链表的问题。

hashmap的put方法中确定索引的方式分析

p = tab[i = (n - 1) & hash]

确定索引的方式是将数组长度大小减一然后&上hash值,因为规定了hashmap数组的大小只能为2的n次方大小,所以n-1相当于时低位掩码,实际上就相当于hash值对数组长度取模,不用'%'的原因是为运算的效率要远远大于它,如果hashmap数组长度大小不是2的n次方,那么会造成hash冲突

hashmap的hash方法中为什么右移16位 ?

(h = key.hashCode()) ^ (h >>> 16)

首先对key使用key类型的hashcode方法获得一个int类型的散列值,再跟它无符号右移16位的数进行异或,这样做的好处是因为我们的数组容量大小通常设置的不会很大,初始容量n只有16,在进行对hash值分析找数组的index的时候会进行 (n - 1) & hash ,如果不右移16位异或的话hash的高位值几乎用不上,这样会导致hash冲突。

hashmap中为何链表节点数到8才会触发红黑树机制?

实际上并不只是到8才会变成红黑树,此时数组的长度还得必须大于64,满足这两个条件才会出发红黑树机制,具体查看treeifyBin()方法,而为何要选用8这个数字是因为链表长度符合泊松分布,链表长度为8概率已经非常低了。

static final int MIN_TREEIFY_CAPACITY = 64;
final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
           ...
    }

红黑树查找的时间复杂度 红黑树和二叉树的区别 ?

ConcurrentHashMap(JDK1.8)

折磨王来了

ConcurrentHashMap几个重要成员属性(后面还有)
//ConcurrentHashMap的用volatile修饰的数组
transient volatile Node<K,V>[] table;
//扩容时准备的数组
private transient volatile Node<K,V>[] nextTable;

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
    //被volatile修饰,保持内存可见性
        volatile V val;
        volatile Node<K,V> next;
    //......
}

ConcurrentHashMap的putVal()方法
final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
    //计算hash值,与hashMap一样
        int hash = spread(key.hashCode());
        int binCount = 0;
    //进入死循环
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh; K fk; V fv;
            //1.如果tab还没初始化,进行初始化,否则进入步骤2,(注意,这是懒加载机制)
            //*懒加载机制好处:为了防止初始化后的首次操作就需要扩容(比如putAll),从而影响效率
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            //2.如果tab中的i索引处还没有节点,使用CAS原语将该处设置为输入节点,否则进入3
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value)))
                    break;                   // no lock when adding to empty bin
            }
            //3.若桶中该节点的hash值为MOVED,则对该桶中的节点进行转移,否则进入4
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            //4.如果输入节点的key值与桶的头节点一样,则替换桶中的节点,否则进入5
            else if (onlyIfAbsent // check first node without acquiring lock
                     && fh == hash
                     && ((fk = f.key) == key || (fk != null && key.equals(fk)))
                     && (fv = f.val) != null)
                return fv;
            else {
                //5.对该处桶节点进行加锁操作,之后的操作与hashmap一样,相同则替换,直到末尾还没有				//相同的就放到队尾
                V oldVal = null;
                //这里是对桶的单个对象进行加锁,并不是整个数组加锁也不是1.7分段加锁,粒度更细
                synchronized (f) {
                 	// 再次检查,即double check
                	// 即避免进入同步块之前,链表被修改了
                    if (tabAt(tab, i) == f) {
                        //如果fh大于0那么就是链表形式
                        if (fh >= 0) {
                            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);
                                    break;
                                }
                            }
                        }
                        //之前判断过了fh既不是moved状态也不是链表,那么得判断是不是红黑树结构
                        else if (f instanceof TreeBin) {
                            Node<K,V> p;
                            binCount = 2;
                            if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                           value)) != null) {
                                oldVal = p.val;
                                if (!onlyIfAbsent)
                                    p.val = value;
                            }
                        }
                        else if (f instanceof ReservationNode)
                            throw new IllegalStateException("Recursive update");
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

ConcurrentHashMap的initTable()方法
//sizeCtl默认为0,用来控制数组初始化和扩容
//sizeCtl=-1,代表此时数组正在初始化
//sizeCtl=-N,代表此时数组正在被N-1个线程扩容
//当数组还没被初始化,表示数组需要初始化的大小
//当数组已经被初始化,表示数组的扩容阈值,为数组长度的0.75倍
private transient volatile int sizeCtl;

private final Node<K,V>[] initTable() {
        Node<K,V>[] tab; int sc;
        while ((tab = table) == null || tab.length == 0) {
            //如果sizeCtl小于0说明是其他线程已经在进行初始化工作了,此时只需要让出时间片
            if ((sc = sizeCtl) < 0)
                Thread.yield(); // lost initialization race; just spin
            //如果不小于0则将其设置为-1进行初始化操作,防止其他线程进入
            else if (U.compareAndSetInt(this, SIZECTL, sc, -1)) {
                try {
                    //二次检查
                    if ((tab = table) == null || tab.length == 0) {
                        //ConcurrentHashMap构造函数里sizeCtl被赋值为DEFAULT_CAPACITY,16
                        int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                        @SuppressWarnings("unchecked")
                        Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                        table = tab = nt;
                        //这里相当于0.75倍,位运算效率高
                        sc = n - (n >>> 2);
                    }
                } finally {
                    //此时sizeCtl变为扩容阈值12而不是数组容量16
                    sizeCtl = sc;
                }
                break;
            }
        }
        return tab;
    }

ConcurrentHashMap的addCount()方法(putVal代码有使用)
//该方法可能会触发扩容机制
private final void addCount(long x, int check) {
        CounterCell[] cs; long b, s;
        if ((cs = counterCells) != null ||
            !U.compareAndSetLong(this, BASECOUNT, b = baseCount, s = b + x)) {
            //省略.......首先会对basecount进行一个争抢
            s = sumCount();
        }
        if (check >= 0) {
            Node<K,V>[] tab, nt; int n, sc;
            //这里的s是sumCount()方法获得的,通过反射可以知道这个方法是获取当前KV键值对的个数
            //如果s大于了阈值sizeCtl则说明此时数组要么是需要扩容要么是数组还没初始化
            while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
                   (n = tab.length) < MAXIMUM_CAPACITY) {
                int rs = resizeStamp(n) << RESIZE_STAMP_SHIFT;
                if (sc < 0) {
                    //如果nextTable为null,transferIndex小于0,
                    //且是在sc小于0的情况下,说明数组并不是在扩容而是在初始化
                    if (sc == rs + MAX_RESIZERS || sc == rs + 1 ||
                        (nt = nextTable) == null || transferIndex <= 0)
                        break;
                    //其他线程在扩容,协助其他线程
                    if (U.compareAndSetInt(this, SIZECTL, sc, sc + 1))
                        transfer(tab, nt);
                }
                //仅当前线程扩容
                else if (U.compareAndSetInt(this, SIZECTL, sc, rs + 2))
                    transfer(tab, null);
                s = sumCount();
            }
        }
    }

ConcurrentHashMap的transfer()方法(多线程扩容核心)
//transferIndex表示为'数组迁移索引',ConcurrentHashMap的扩容任务会被分成多个任务来让多个线程
//一起完成或者是一个线程一点一点完成,代码中与之配和的一个属性stride,'步长',一个线程会从
//transferIndex开始从后往前开始迁移,迁移stride个桶。一开始transferIndex在数组最右边
private transient volatile int transferIndex;
//ForwardingNode,字面意思即正在被迁移的节点,它的hash值被设置为moved,-1。相当于一个标志
static final class ForwardingNode<K,V> extends Node<K,V> {
        final Node<K,V>[] nextTable;
        ForwardingNode(Node<K,V>[] tab) {
            super(MOVED, null, null);
            this.nextTable = tab;
        }

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
    //1.设置stride步长
    // stride 在单核下直接等于 n,多核模式下为 (n>>>3)/NCPU,最小值是 16
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range
    //2.nextTab为空,对nextTab初始化,容量为原数组的两倍
        if (nextTab == null) {            // initiating
            try {
                @SuppressWarnings("unchecked")
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
            nextTable = nextTab;
            //注意这里的transferIndex=n的情况是nextTab为空的条件赋予的,其他线程来帮忙转移的时候			//transferIndex不等于n
            transferIndex = n;
        }
        int nextn = nextTab.length;
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
    //*这里的advance可以认为是一个迁移任务的完成标志,当他为true即可领取下一个迁移任务
    //*finishiing可以认为是整个数组是否迁移完毕
        boolean advance = true;
        boolean finishing = false; // to ensure sweep before committing nextTab
    //进入死循环
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            //3.设置nextBound,这个相当于每次迁移任务只迁移到某个索引位前
            //这个while循环大致意思就是“i指向transferIndex,bound指向transferIndex-stride”。
            while (advance) {
                int nextIndex, nextBound;
                //如果i--小于bound说明该线程的迁移任务全部完全或者第一次进入循环
                if (--i >= bound || finishing)
                    advance = false;
                //这个是当其他线程来领取迁移下标的时候transferIndex==0的情况
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                else if (U.compareAndSetInt
                         (this, TRANSFERINDEX, nextIndex,
                          //这里nextBound选取就是当前准备开始的index减去步长stride,如果大于0,
                          //就把这个大于0的结果赋值给bound,如果小于0就说明此次任务长度不够一个						//步长,bound直接赋值为0.
                          nextBound = (nextIndex > stride ?
                                       nextIndex - stride : 0))) {
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            //4.检查是否完成了整个数组的迁移任务
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                //如果完成了将nextTable设为null,sizeCtl设为原数组容量的1.5倍,即现数组的0.75倍
                if (finishing) {
                    nextTable = null;
                    table = nextTab;
                    sizeCtl = (n << 1) - (n >>> 1);
                    return;
                }
                //如果没完成
                //在进行迁移之前,sizeCtl被设置为rs+2,每有一个线程参与扩容操作,sizectl就会+1
                //*加1操作具体参考helpTransfer()方法,完成迁移任务后使用CAS将sizeCtl减1
                if (U.compareAndSetInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    //(sc - 2)等于resizeStamp(n) << RESIZE_STAMP_SHIFT,说明所有迁移任务完成
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            //5.如果没有完成,检查桶索引处节点是否为null,为null的话在此处放置fwd节点
            else if ((f = tabAt(tab, i)) == null)
                advance = casTabAt(tab, i, null, fwd);
            //6.如果此处节点为fwd,说明该位置已经被迁移过了
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
            //7.如果节点不为null也不为fwd,那就需要对此处节点加锁进行迁移
            else {
                synchronized (f) {
                    //二次检查
                    if (tabAt(tab, i) == f) {
                    //这里的迁移方法跟hashmap扩容有点类似,都是为了使散列值更加均匀
                    //一个链表会被分成低位,高位两条链表
                    //1.首先在链表中找到一个lastRun节点,该节点之后的runbit都跟他一样
                    //2.lastRun节点的runbit如果假设0,那他就属于低位链表ln,否则属于高位链表hn
                    //3.从桶索引头节点开始遍历,直到lastRun节点因为lastRun后面节点跟他一样
                    //4.如果节点runbit为0,那它的next节点就接上上一个ln节点,高位同理
                        Node<K,V> ln, hn;
                        if (fh >= 0) {
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            for (Node<K,V> p = f.next; p != null; p = p.next) {
                                int b = p.hash & n;
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            else {
                                hn = lastRun;
                                ln = null;
                            }
                            for (Node<K,V> p = f; p != lastRun; p = p.next) {
                                int ph = p.hash; K pk = p.key; V pv = p.val;
                                if ((ph & n) == 0)
                                    ln = new Node<K,V>(ph, pk, pv, ln);
                                else
                                    hn = new Node<K,V>(ph, pk, pv, hn);
                            }
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            //迁移完将此处设置为fwd节点
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                        else if (f instanceof TreeBin) {
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> lo = null, loTail = null;
                            TreeNode<K,V> hi = null, hiTail = null;
                            int lc = 0, hc = 0;
                            for (Node<K,V> e = t.first; e != null; e = e.next) {
                                int h = e.hash;
                                TreeNode<K,V> p = new TreeNode<K,V>
                                    (h, e.key, e.val, null, null);
                                if ((h & n) == 0) {
                                    if ((p.prev = loTail) == null)
                                        lo = p;
                                    else
                                        loTail.next = p;
                                    loTail = p;
                                    ++lc;
                                }
                                else {
                                    if ((p.prev = hiTail) == null)
                                        hi = p;
                                    else
                                        hiTail.next = p;
                                    hiTail = p;
                                    ++hc;
                                }
                            }
                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                (hc != 0) ? new TreeBin<K,V>(lo) : t;
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                (lc != 0) ? new TreeBin<K,V>(hi) : t;
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                        else if (f instanceof ReservationNode)
                            throw new IllegalStateException("Recursive update");
                    }
                }
            }
        }
    }


ConcurrentHashMap的jdk1.7与1.8的区别`

  • 锁的对象不同。1.7中ConcurrentHashMap是使用的 segment锁来保持线程安全的,1.8直接舍弃了segmen概念,将table数组中每个节点Node都使用volatile关键定义。
  • 数据结构不同。1.7中ConcurrentHashMap数据结构是数组+链表,1.8是数组+链表+红黑树
  • 锁的粒度不同。1.7是对需要操作的segment加锁,而1.8是对需要操作的数组元素加锁,这样锁的粒度更细
  • 锁的方法不同。segment是使用ReentraintLock,而1.8是使用synchronized+CAS。
  • 遍历时间复杂度不同。链表是O(n),红黑树是O(logn).

Hashtable

为什么Hashtable ,ConcurrentHashmap不支持key或者value为null?

Hashtable 与ConcurrentHashmap都是支持并发的,当get(key)获得的value为null时无法判断是这个key没做过映射,还是本来值设置的就是null,所以它得用contains(key)来去检查这个key是否做过映射,但在高并发的环境下让他这样去检查很可能情况已经改变了。