List
ArrayList | LinkedList … | Vector … | Stack … | |
---|---|---|---|---|
初始大小 | 10 | size = 0 | 10 | |
底层结构 | Object[] elementData | Node 双向链表 private static class Node { E item; Node next; Node prev; Node(Node prev, E element, Node next) { this.item = element; this.next = next; this.prev = prev; } } | object[] element | Stack就是继承自Vector Object[] element |
modCount //继承于AbstractList,用以表示list结构被修改的次数。 | ||||
其他参数 | Object[] EMPTY_ELEMENTDATA = {} //空实例的共享空数组实例。有参构造。 Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}//默认大小的空实例的共享空数组实例。无参构造分配。 | |||
构造函数 | ArrayList(int initialCapacity) //有参空示例构造函数(懒加载,在扩容的时候加载)和有参构造函数(不是懒加载) ArrayList() //无参构造函数 | public LinkedList() { } | Vector(){ this(10)} Vector(int initialCapacity){this(initialCapactiy,0) Vector(int initialCapacity,int capacityIncrement) | publicStack(){} 使用父类数据结构 Object[] element 基于数组实现的栈 |
其他方法 | trimToSize() //实例的容量(length)调整为列表的当前大小(size) | private void linkFirst(E e) void linkLast(E e) void linkBefore private E unlinkFirst private E unlinkLast(Node l) E unlink(Node x) | ||
扩容 | //最小容量 //旧容量是数组长度 //新容量=旧容量*1.5 //如果扩容后新容量<最小容量 -------- 新容量等于最小容量 //如果扩容后大于ArrayList定义的最大size(MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;) //hugeCapacity(int minCapacity) //返回一个扩容后的数组 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); } 扩容1.5倍,新容量>MAX_ARRAY_SIZE 使用最大容量 //如果最小容量超过了Integer的范围 ,OOM //否则在MAX_ARRAY_SIZE-Integer内最小容量Integer.MAX_VALUE //否则=MAX_ARRAY_SIZE private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } | modcount。判读新容量=原容量+扩容增长步长。(未指定扩容增长步长就扩容两倍)扩容后不够用能够完成增加操作最小长度(length+1),扩容后大于最大容量用最大容量。 newCapacity=oldCapacity+((capacityIncrement>0) ? capacityIncrement : oldCapacity); | ||
add | //先看要不要扩容(不够做扩容操作) //添加数据 public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } //相当于在指定索引处插入,必须在数组的add部分中加入,index必须在size以内,不能加在还为空的地方 public void add(int index, E element) 按索引添加 add->ensureCapacityInternal->grow | //加首部 public boolean offerFirst(E e) public boolean offer(E e) //加尾部 public boolean offerLast(E e) | public synchronized void addElement(E obj) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = obj; } 第一步:它在添加元素的时候首先将modCount加1,保证线程安全。第二步:ensureCapacityHelper()主要用于保障Stack的容量,在合理范围第三步:真正实现元素的添加,将该元素添加到栈顶,数目加1; | public synchronized void addElement(E obj) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = obj; } |
remove | //检查下标 //改变结构+1 //返回移除元素 //移除System.arraycopy //移除对象引用,便于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; } //移除掉第一个匹配的object public boolean remove(Object o) | //取last的节点,从链表中删除 public E pollLast() //取first节点,并从链表中删除 public E pollFirst() //取尾节点并移除 public E poll() //取last节点,不删除 public E peekLast() //取first节点,不删除 public E peekFirst() //移除第一个 public E remove() //取首个值,不从链表中移除 (队列操作) public E peek() //取首个值 public E element() | modcount。删除元素(指定位置),删除元素(指定元素)。数组复制。 | pop peek 对于元素的改变用的都是system.arraycopy |
查找 | 按照索引查找 | //折半查找 Node node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) { Node x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node x = last; for (int i = size - 1; i > index; i–) x = x.prev; return x; } } | 查询Vector容器中是否包含某个元素. 查询第一次出现的指定元素的索引 indexof(Object o,int index) | |
总结 | 在队尾添加删除还行,要是在中间,比如那些索引啊,就要使用arraycopy | |||
线程安全 | 不安全 add的时候因为是element[size++]一个是数组越界异常,一个是这不是一个原子操作。这样线程A、B执行完成后,elementData的期待结果为:size为2,elementData[0]=“A”,elementData[1]=“B”。而实际情况是:size为2,elementData[0]=“B”,elementData[1]=null。 | 不安全,两个线程同时操作一个linkedlist就会不安全 | ||
和jdk1.7相比较 | 相较于jdk7 EMPTY_ELEMENTDATA作用改变了。从构造函数源码中 可以看到,如果给定初始容量为0,或者传入集合为空集合(不是null),那么,将空数组EMPTY_ELEMENTDATA赋给elementData。而在Java7中如果容量是0的话,会创建一个空数组,赋值给elementData。这里可以知道jdk8的EMPTY_ELEMENTDATA在一定程度上减少了空数组的存在,降低内存的消耗,一定程度上是对性能的优化。 | |||
ArrayList和LinkedList的比较 | 1.对ArrayList和LinkedList而言,在列表末尾增加一个元素所花的开销都是固定的O(1)。对ArrayList而言,主要是在内部数组中增加一项,指向所添加的元素,偶尔可能会导致对数组重新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。 2.在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。 3.ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间 | 从上面的构造方法还有增删改查的操作其实我们都发现了,都有这么一个synchronized关键字,就是这个关键字为Vector容器提供了一个安全机制,保证了线程安全。 |
Map(Set和map类似)
HashMap
… | HashMap |
---|---|
默认长度和默认加载因子 | 16 0.75 |
参数 | static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 初始容量 16 //最大容量,如果某个具有参数的构造函数隐式指定了更高的值,则使用该值。 static final int MAXIMUM_CAPACITY = 1 << 30; //构造函数中未指定时使用的负载因子(填充比)。 static final float DEFAULT_LOAD_FACTOR = 0.75f; //使用树(而不是列表)的存储箱计数阈值。 static final int TREEIFY_THRESHOLD = 8; //应该小于这个值,同时至少是6,一边再去除时检测到收缩 static final int UNTREEIFY_THRESHOLD = 6; //数组长度超过不够了扩大数组(节点)长度 static final int MIN_TREEIFY_CAPACITY = 64; |
成员变量 | transient int size;//实际放的 transient int modCount;//被修改的次数fast-fail机制 int threshold;//临界值 当实际大小(容量*填充比)超过临界值时,会进行扩容 final float loadFactor;//负载因子。填充比(…后面略) |
构造函数 | //自己指定的初始容量 负载因子 public HashMap(int initialCapacity, float loadFactor) //指定初始容量(同时会确定临界值)+默认负载因子 public HashMap(int initialCapacity) //默认长度16 负载因子0.75 public HashMap() //构造函数4用m的元素初始化散列映射 public HashMap(Map<? extends K, ? extends V> m) 并没有实例hashmap对象,懒加载只是设置了参数 |
原理 | |
线程安全 | 否 |
初始容量 | 16 |
存储结构 | public HashMap(int initialCapacity, float loadFactor) {数组+链表/红黑树 transient Node<K,V>[] table; static class Node<K,V> implements Map.Entry<K,V> 【单链表】 |
顺序规则 | 取值无顺序 |
存储特点 | 最多一条记录的key为null,可以多条记录value为null |
插入效率 | 高 |
遍历效率 | 高 |
使用场景 | 使用最多,绝大多数无需排序的情况都可使用 |
重要方法 | //取key的hashCode,hashCode是32位int。 //然后h=hashcode右移16位在和原来的hash做异或得到的值 //1111 1111 1111 1111 1111 0000 1100 0001 //0000 0000 0000 0000 1111 1111 1111 1111 —>右移16位后的值(h >>> 16) //1111 1111 1111 1111 0000 1111 0011 1110 ---->这就是hash方法返回的值(高十六位和原哈希异或) static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } |
//返回大于输入参数且最近的2的整数次幂的数。比如10,则返回16 static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } | |
get | 总结:现根据hash找到数组里有没有哈希值相同(头)node,有的话先看数组里,不一样就遍历(链表,红黑树)找value也相同的node 注意:tab[(n - 1) & hash] // hash方法返回的值,然后和(n-1)做按位与操作,n默认16,找到存放和参数hash相同哈希值的key的数组索引。 //比如length=16 n=15 15(二进制码是1111) //当往数组里放的时候,node的下标是这个算法=hash值&(n-1)。就是在hash低位取n-1位 |
put | 总结: 先根据hash找到在数组中对应的下标,看数组里这个下标里有没有值,没有就放进去 有就数组冲突,遍历链表。 看头头key值一不一样,一样记录这个node 不一样就遍历小弟(小弟多久用红黑树的方法,小弟不多就走链表) 用链表的话找到了就记录这个node,没找到遍历到最后都没找到与key的哈希还有equals一摸一样的,说明没有冲突,放在链表最后,如果冲突的节点数已经达到8个,看是否需要改变冲突节点的存储结构,【//treeifyBin首先判断当前hashMap的长度,如果不足64,只进行 。。。 //resize,扩容table,如果达到64,那么将冲突的存储结构为红黑树】 //找到冲突的key了,替换值 最后判断需不需要扩容 |
扩容 | 确定新表长度和临界值: 最大长度 | 旧长度*2 第一次初始化:把新长度设置为旧临界值 //比如hashMap(5),临界值是8,把新长度设置为8 为空参构造函数所有值设置为默认 临界值是:长度*加载因子 构造新表: //node没有下一个节点,就是这个节点对应的hash只有他一个 //把他放到 根据key取的hash和newCap-1取与的结果作为下标 //如果e是树节点,走树节点方法 //e有下一个值,就是链表了。使用分流。 |
LinkedHashMap
… | LinkedHashMap |
---|---|
默认长度和默认加载因子 | 16 0.75 |
参数 | static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 初始容量 16 //最大容量,如果某个具有参数的构造函数隐式指定了更高的值,则使用该值。 static final int MAXIMUM_CAPACITY = 1 << 30; //构造函数中未指定时使用的负载因子(填充比)。 static final float DEFAULT_LOAD_FACTOR = 0.75f; //使用树(而不是列表)的存储箱计数阈值。 static final int TREEIFY_THRESHOLD = 8; //应该小于这个值,同时至少是6,一边再去除时检测到收缩 static final int UNTREEIFY_THRESHOLD = 6; //数组长度超过不够了扩大数组(节点)长度 static final int MIN_TREEIFY_CAPACITY = 64; |
成员变量 | transient int size;//实际放的 transient int modCount;//被修改的次数fast-fail机制 int threshold;//临界值 当实际大小(容量*填充比)超过临界值时,会进行扩容 final float loadFactor;//负载因子。填充比(…后面略) |
构造函数 | 需要注意的就是accessOrder // 构造方法1,构造一个指定初始容量和负载因子的、按照插入顺序的LinkedList public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; } // 构造方法2,构造一个指定初始容量的LinkedHashMap,取得键值对的顺序是插入顺序 public LinkedHashMap(int initialCapacity) { super(initialCapacity); accessOrder = false; } … // 构造方法5,根据指定容量、装载因子和键值对保持顺序创建一个LinkedHashMap public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; } 并没有实例map对象,懒加载只是设置了参数 |
原理 | |
线程安全 | 否 |
初始容量 | 16 |
存储结构 | 在HashMap的Node的基础上,添加了两个指向它前的和它后的Entry static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } } |
顺序规则 | 有序 |
存储特点 | 最多一条记录的key为null,可以多条记录value为null |
插入效率 | 高 |
遍历效率 | 低 |
使用场景 | |
重要方法 | //取key的hashCode,hashCode是32位int。 //然后h=hashcode右移16位在和原来的hash做异或得到的值 //1111 1111 1111 1111 1111 0000 1100 0001 //0000 0000 0000 0000 1111 1111 1111 1111 —>右移16位后的值(h >>> 16) //1111 1111 1111 1111 0000 1111 0011 1110 ---->这就是hash方法返回的值(高十六位和原哈希异或) static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } |
remove | LinkedHashMap重写了其中的afterNodeRemoval(Node e),该方法在HashMap中没有具体实现,通过此方法在删除节点的时候调整了双链表的结构。 就还是像以前一样删除,但是同时要在双向链表中删除这个节点 |
get | LinkedHashMap的get方法与HashMap中get方法的不同点也在于多了afterNodeAccess()方法 public V get(Object key) { Node<K,V> e; if ((e = getNode(hash(key), key)) == null) return null; if (accessOrder) afterNodeAccess(e); return e.value; } |
put | 总结: 先根据hash找到在数组中对应的下标,看数组里这个下标里有没有值,没有就放进去 有就数组冲突,遍历链表。 看头头key值一不一样,一样记录这个node 不一样就遍历小弟(小弟多久用红黑树的方法,小弟不多就走链表) 用链表的话找到了就记录这个node,没找到遍历到最后都没找到与key的哈希还有equals一摸一样的,说明没有冲突,放在链表最后,如果冲突的节点数已经达到8个,看是否需要改变冲突节点的存储结构,【//treeifyBin首先判断当前hashMap的长度,如果不足64,只进行 。。。 //resize,扩容table,如果达到64,那么将冲突的存储结构为红黑树】 //找到冲突的key了,替换值 最后判断需不需要扩容 重点来了 put方法会把map变得有序,主要是因为这两个方法 newNode:把加入的节点放置到双向链表的尾部(针对新加的) afterNodeAccess:把加入把加入的节点放置到双向链表的尾部(针对原来的节点也就是覆盖) 有一个很重要的参数:accessOrder,如果它为true就只要有改动比如put get的就要放到尾部,false原来有的覆盖的时候位置不变 |
扩容 | 确定新表长度和临界值: 最大长度 | 旧长度*2 第一次初始化:把新长度设置为旧临界值 //比如hashMap(5),临界值是8,把新长度设置为8 为空参构造函数所有值设置为默认 临界值是:长度*加载因子 构造新表: //node没有下一个节点,就是这个节点对应的hash只有他一个 //把他放到 根据key取的hash和newCap-1取与的结果作为下标 //如果e是树节点,走树节点方法 //e有下一个值,就是链表了。使用分流。 |
next就是每个链表中的next
before是当前entry的下一个
TreeMap
… | TreeMap(排序) |
---|---|
默认长度和加载因子 | 无 |
成员变量 | //底层结构---------红黑树 private transient Entry<K,V> root; //entry数量 private transient int size = 0; //红黑树结构的调整次数 private transient int modCount; //默认是key的自然排序规则,也可以定义自己的排序规则 private final Comparator<? super K> comparator; |
Entry<K,V> | //key,val是存储的原始数据 K key; V value; //定义了节点的左孩子 Entry<K,V> left; //定义了节点的右孩子 Entry<K,V> right; //通过该节点可以反过来往上找到自己的父亲 Entry<K,V> parent; //默认情况下为黑色节点,可调整 boolean color = BLACK; |
构造函数 | 重点在于comparator的选择 //默认构造函数,按照key的自然顺序排列 public TreeMap() {comparator = null;} //传递Comparator具体实现,按照该实现规则进行排序 public TreeMap(Comparator<? super K> comparator) {this.comparator = comparator;} //传递一个map实体构建TreeMap,按照默认规则排序 public TreeMap(Map<? extends K, ? extends V> m) { comparator = null; putAll(m); } //传递一个map实体构建TreeMap,按照传递的map的排序规则进行排序 public TreeMap(SortedMap<K, ? extends V> m) { comparator = m.comparator(); try { buildFromSorted(m.size(), m.entrySet().iterator(), null, null); } catch (java.io.IOException cannotHappen) { } catch (ClassNotFoundException cannotHappen) { } } |
原理 | |
线程安全 | 不是 |
初始容量 | 无 |
存储结构 | 红黑树节点结构 |
顺序规则 | 有序 |
存储特点 | 不可以为null |
插入效率 | 高 |
遍历效率 | 高 |
使用场景 | |
重要方法 | |
remove | 要根据红黑树的删除规则来删除 |
get | 总结:就根据二叉排序树来查找 |
put | 总结: 如果root为空直接root=put了 需要注意一点的是:compare默认为空,如果根据key排序,如果compare不为空根据compare排序 需要put的时候二分查找,就是二叉排序树的添加,判断依据shicompare 所有都添加结束之后,要确认父亲 最后调整红黑树结构(左旋,右旋,变色) |
扩容 | 没有扩容 |
红黑树插入总结
无需调整 | 【变色】即可实现平衡 | 【旋转+变色】才可实现平衡 | |
---|---|---|---|
情况1: | 当父节点为黑色时插入子节点 | 空树插入根节点,将根节点红色变为黑色 | 父节点为红色左节点,叔父节点为黑色,插入左子节点,那么通过【左左节点旋转】 |
情况2: | - | 父节点和叔父节点都为红色 | 父节点为红色左节点,叔父节点为黑色,插入右子节点,那么通过【左右节点旋转】 |
情况3: | - | - | 父节点为红色右节点,叔父节点为黑色,插入左子节点,那么通过【右左节点旋转】 |
情况4: | - | - | 父节点为红色右节点,叔父节点为黑色,插入右子节点,那么通过【右右节点旋转】 |
Hashtable
… | Hashtable |
---|---|
默认长度和加载因子 | 11 0.75 |
成员变量 | //底层结构 private transient Entry<?,?>[] table; //实际数量 private transient int count; //临界值 private int threshold; //加载因子 private float loadFactor; private transient int modCount = 0; private static final long serialVersionUID = 1421746759512286392L; |
构造函数 | //在构造函数里就初始化,临界值=加载因子*长度 public Hashtable(int initialCapacity, float loadFactor) //指定长度的 public Hashtable(int initialCapacity) //默认长度11,默认加载因子0.75f public Hashtable() //加入table里,使用此构造函数长度选map和11里最大那个 public Hashtable(Map<? extends K, ? extends V> t)并没有实例hashtable对象,懒加载只是设置了参数 |
原理 | |
线程安全 | 是。操作方法上都加了synchronized |
初始容量 | 16 |
存储结构 | 数组+链表 |
顺序规则 | 无序 |
存储特点 | Hashtable线程安全、元素无序(因为以hashCode为基准进行散列存储),不允许[key,value]为null。 |
插入效率 | 高 |
遍历效率 | 高 |
使用场景 | |
重要方法 | |
remove | 找到就删除找法和put一样,链表的删除。 |
get | 总结: 根据hash找到下标,然后从老大开始找,在自己链表里找,找到就替换旧值。 |
put | 总结: 不允许有空值 从老大开始找,在自己链表里找,找到就替换旧值。 没找到直接加【addentry(扩容+放入hashtable)】 |
扩容 | rehash() 扩容:2倍+1 临界值:长度*加载因子 移到新表【扩容转移元素时采用的是头插法(jdk1.7hashmap用的)。】 |
1.Hashtable线程安全、元素无序(因为以hashCode为基准进行散列存储),不允许[key,value]为null。
#2.Hashtable默认容量为11,与HashMap不同(默认容量16),扩容时容量增长为2*n+1(HashMap直接增长为2倍)。
#3.扩容转移元素时采用的是头插法(jdk1.7hashmap用的)。
底层是数组+链表 entry<k,v>
超级超级重要的concurrentHashMap
这里我就放源码了
// forwarding nodes的hash值
static final int MOVED = -1; // hash for forwarding nodes //forwarding nodes:正在扩容
// 树根节点的hash值
static final int TREEBIN = -2; // hash for roots of trees //树根节点
// ReservationNode的hash值
static final int RESERVED = -3; // hash for transient reservations
static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
sizeCtl
特别重要的属性:
private transient volatile int sizeCtl;
这是一个重要的属性,无论是初始化哈希表,还是扩容 rehash 的过程,都是需要依赖这个关键属性的。该属性有以下几种取值:
- 0:默认值
- -1:代表哈希表正在进行初始化
- 大于0:相当于 HashMap 中的 threshold,表示阈值
- 小于-1:代表有多个线程正在进行扩容
节点数据结构
单链表——node
volatile V val;
volatile Node<K,V> next;
static class Node<K,V> implements Map.Entry<K,V> {
//链表的数据结构
final int hash;
final K key;
//val和next都会在扩容时发生变化,所以加上volatile来保持可见性和禁止重排序
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;
}
public final K getKey() { return key; }
public final V getValue() { return val; }
public final int hashCode() { return key.hashCode() ^ val.hashCode(); }
public final String toString(){ return key + "=" + val; }
//不允许更新value
public final V setValue(V value) {
throw new UnsupportedOperationException();
}
}
红黑树结构——TreeBin
对于一个红黑树要有:root,first,waiter,lockState,WRITER,WAITER,READER
static final class TreeBin<K,V> extends Node<K,V> {
//指向TreeNode列表和根节点
TreeNode<K,V> root;
volatile TreeNode<K,V> first;
volatile Thread waiter;
volatile int lockState;
// 读写锁状态
static final int WRITER = 1; // 获取写锁的状态
static final int WAITER = 2; // 等待写锁的状态
static final int READER = 4; // 增加数据时读锁的状态
/**
* 初始化红黑树
*/
TreeBin(TreeNode<K,V> b) {
......
}
......
}
红黑树存储的结构——TreeNode
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;
}
Node<K,V> find(int h, Object k) {
return findTreeNode(h, k, null);
}
//根据key查找 从根节点开始找出相应的TreeNode,
final TreeNode<K,V> findTreeNode(int h, Object k, Class<?> kc) {
......
}
}
构造函数
public ConcurrentHashMap() {
}
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
//确定临界值
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
this.sizeCtl = DEFAULT_CAPACITY;
putAll(m);
}
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, 1);
}
//第三个参数并发度
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
//如果初始容量<并发度,那么初始容量=并发度,能有多少线程就要保证有多少的容量,万一一开始大家都操作就得保证每个线程都有用的
if (initialCapacity < concurrencyLevel) // Use at least as many bins
initialCapacity = concurrencyLevel; // as estimated threads
//确定容量
long size = (long)(1.0 + (long)initialCapacity / loadFactor);
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int)size);//保证最终计算的大小是2^n
this.sizeCtl = cap;
}
put
//onlyIfAbsent true:第一次put键值才会放入map,以后有相同key,不会覆盖
final V putVal(K key, V value, boolean onlyIfAbsent) {
//K V 不允许为空
if (key == null || value == null) throw new NullPointerException();
//保证key的hash是个正整数,spread综合高低位
//key的hash值:两次hash,减少hash冲突,可以均匀分布
int hash = spread(key.hashCode());
// 用来计算在这个节点总共有多少个元素,用来控制扩容或者转移为树,链表的长度
int binCount = 0;
//对table迭代,死循环
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; //存放在tab里找到的和key哈希一样的值
int n, i, fh;//i是索引,n是长度
//table使用无参构造方法(里面没有任何操作)在这里进行初始化
if (tab == null || (n = tab.length) == 0)
//初始化table,使用cas,无需synchronized 只有一个线程创建成功,失败的线程进行下一轮循环
tab = initTable();
//找头头,看头头里有没有哈希值一样的
//头头里没有的话在,i的位置无锁插入
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
//帮忙扩容
//每扩容一链就会把老大设置成forwardnode
// 如果检测到当前某个节点的hash值为MOVED,则表示(其他线程)正在进行数组扩张的数据复制阶段
20 // 则当前线程与会参与复制,通过允许多线程复制的功能,减少数组的复制来带来的性能损失
else if ((fh = f.hash) == MOVED)
//帮忙扩容
tab = helpTransfer(tab, f);
//既不是扩容中,也不是初始化中
//桶下标冲突才加锁,只对桶链表加锁(锁住链表头节点)
else {
V oldVal = null;
synchronized (f) {
//再次确认头节点没有被移动
if (tabAt(tab, i) == f) {
//链表,普通节点
if (fh >= 0) {
binCount = 1;
//遍历链表,bincount会++
for (Node<K,V> e = f;; ++binCount) {
K ek;
//找到相同的key,替换
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;
//已经是最后的节点了,新增node,追加至链表尾
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
//红黑树节点
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;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
//如果链表长度》=树话阈值,进行链表转红黑树
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
//增加size计数,设置多个累加单元,维护整个元素计数,还有扩容的逻辑
addCount(1L, binCount);
return null;
}
}