读集合源码 | Word count: 4.1k | Reading time: 19min | Post View:
Arraylist
1 transient Object[] elementData;
1 2 3 4 5 public MyArrayList () { elementData = new Object [10 ]; size = 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public void add (T t) { elementData[size++] = t; } public Object get (int index) { if (index > size){ throw new RuntimeException ("数组没有" +index+"这个元素" ); } return elementData[index]; } public void remove (int moveNum) { int lastes = size - moveNum - 1 ; if (moveNum > 0 ){ System.arraycopy(elementData,moveNum+1 ,elementData,moveNum,lastes); } } public String set (int index,String newValue) { if (index >= size){ throw new RuntimeException ("索引越界" ); } String oldValue = (String)elementData[index]; elementData[index] = newValue; return oldValue; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private int newCapacity (int minCapacity) { int oldCapacity = this .elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1 ); if (newCapacity - minCapacity <= 0 ) { if (this .elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(10 , minCapacity); } else if (minCapacity < 0 ) { throw new OutOfMemoryError (); } else { return minCapacity; } } else { return newCapacity - 2147483639 <= 0 ? newCapacity : hugeCapacity(minCapacity); } }
elementData 用transient 修饰,序列化后数据会丢失吗
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 private void writeObject (java.io.ObjectOutputStream s) throws java.io.IOException { int expectedModCount = modCount; s.defaultWriteObject(); s.writeInt(size); for (int i=0 ; i<size; i++) { s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException (); } }
不对elementData序列化,对elementData中的元素进行循环,取出来单独进行序列化。
为什么不直接对数组序列化
一般情况下数组是没有存满的,直接序列化会造成空间浪费
与Vector的比较
与Arraylist相似,除了添加了synchronized保证线成安全,扩容自定义1 2 3 4 5 6 7 8 9 10 public synchronized boolean add (E e) { ++this .modCount; this .add(e, this .elementData, this .elementCount); return true ; } int newCapacity = oldCapacity + (this .capacityIncrement > 0 ? this .capacityIncrement : oldCapacity);
LinkedList
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 transient int size;transient LinkedList.Node<E> first; transient LinkedList.Node<E> last; private static class Node <E> { E item; LinkedList.Node<E> next; LinkedList.Node<E> prev; Node(LinkedList.Node<E> prev, E element, LinkedList.Node<E> next) { this .item = element; this .next = next; this .prev = prev; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 public void add (E e) { Node l = last; MyLinkedList.Node<E> newNode = new MyLinkedList .Node(l, e, null ); this .last = newNode; if (l == null ) first = newNode; else l.next = newNode; size++; } public E set (int index, E element) { if (0 >= index || index >= size){ throw new RuntimeException ("不存元素" ); } MyLinkedList.Node<E> x = this .get(index); E oldVal = x.item; x.item = element; return oldVal; } public MyLinkedList.Node<E> get (int index) { if (0 >= index || index >= size){ throw new RuntimeException ("不存元素" ); } MyLinkedList.Node x; int i; if (index < size >> 1 ){ x = this .first; for (i = 0 ; i < index; i++){ x = x.next; } }else { x = this .last; for (i = size; i > index; i--){ x = x.prev; } } return x; } public E remove (int index) { if (0 >= index || index >= size){ throw new RuntimeException ("不存元素" ); } Node<E> x = get(index); E element = x.item; MyLinkedList.Node<E> next = x.next; MyLinkedList.Node<E> prev = x.prev; if (prev == null ){ this .first = next; }else { prev.next = next; x.prev = null ; } if (next == null ){ this .last = prev; }else { next.prev = prev; x.next = null ; } x.item = null ; --this .size; return element; }
HashMap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static class Node <K,V> implements Map .Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this .hash = hash; this .key = key; this .value = value; this .next = next; }
1 2 3 4 5 6 7 8 9 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); } n = (tab = resize()).length; if ((p = tab[i = (n - 1 ) & hash]) == null )
这里可以体现为什么HashMap的长度建议为2的幂次
当数组长度为 2 的幂次是 length - 1就刚好所有的二进制全为 1
不是2的幂次,则算出的length - 1就会存在一些位置为 0,而0进行&操作,就一定为0,增加了hash冲突概率
jdk如何保证刚好是2的幂次
1 2 3 4 5 6 7 8 9 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 ; }
n 如果是正整数,那么最高位肯定是 1
n或上无符号右移1位,则不管n的第二位是0,1都会变成1,现在就最高位和次位都为1
n或上无符号右移2位,就是将第一步前两个1与第3,4个或变成1,现在就是前4位为1
。。。。。。。
为什么要cap - 1;
因为如果n 刚好是2的整数幂,则会将n变成2^(n + 1);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4 ; static final int MAXIMUM_CAPACITY = 1 << 30 ; static final float DEFAULT_LOAD_FACTOR = 0.75f ; static final int TREEIFY_THRESHOLD = 8 ; static final int UNTREEIFY_THRESHOLD = 6 ; static final int MIN_TREEIFY_CAPACITY = 64 ; public HashMap (int initialCapacity, float loadFactor) { if (initialCapacity < 0 ) throw new IllegalArgumentException ("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException ("Illegal load factor: " + loadFactor); this .loadFactor = loadFactor; this .threshold = tableSizeFor(initialCapacity); }
负载因子越大则散列表的装填程度越高也就是能容纳更多的元素,元素多了,链表大了,所以此时索引效率就会降低 。
负载因子越小则链表中的数据量就越稀疏,此时会对空间造成浪费 ,但是此时索引效率高。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 final V putVal (int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; 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<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this , tab, hash, key, value); else { } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null ; } final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null ) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0 ; if (oldCap > 0 ) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; }else if ((newCap = oldCap << 1 ) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1 ; } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) 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 ; if (e.next == null ) newTab[e.hash & (newCap - 1 )] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this , newTab, j, oldCap); else { } } } } return newTab; } final void split (HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) { TreeNode<K,V> b = this ; TreeNode<K,V> loHead = null , loTail = null ; TreeNode<K,V> hiHead = null , hiTail = null ; int lc = 0 , hc = 0 ; for (TreeNode<K,V> e = b, next; e != null ; e = next) { if (hiHead != null ) { if (hc <= UNTREEIFY_THRESHOLD) tab[index + bit] = hiHead.untreeify(map); else { tab[index + bit] = hiHead; if (loHead != null ) hiHead.treeify(tab); } } } }
HashTable和HashMap区别
继承的父类不同 Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。
线程安全性不同
Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省情况下是非Synchronize的。
key和value是否允许null值
1 2 3 4 5 6 7 8 9 10 11 #################### hashMap ##################### static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); } #################### hashtable ##################### int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF ) % tab.length; if (value == null ) { throw new NullPointerException (); }
内部实现使用的数组初始化和扩容方式不同
Hashtable扩容时,将容量变为原来的2倍加1 ,而HashMap扩容时,将容量变为原来的2倍 。 Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable中hash数组默认大小是11,增加的方式是 old*2+1。
HashSet
1 2 3 4 5 6 7 8 9 private transient HashMap<E, Object> map;private static final Object PRESENT = new Object ();public boolean add (E e) { return map.put(e, PRESENT)==null ; }
ConcurrentHashMap
1 2 3 4 5 6 7 8 9 10 11 12 public ConcurrentHashMap (int initialCapacity, float loadFactor, int concurrencyLevel) { if (!(loadFactor > 0.0f ) || initialCapacity < 0 || concurrencyLevel <= 0 ) throw new IllegalArgumentException (); if (initialCapacity < concurrencyLevel) initialCapacity = concurrencyLevel; long size = (long )(1.0 + (long )initialCapacity / loadFactor); int cap = (size >= (long )MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : tableSizeFor((int )size); this .sizeCtl = cap; }
concurrencyLevel :并行级别、并发数、Segment 数,怎么翻译不重要,理解它。默认是 16,也就是说 ConcurrentHashMap 有 16 个 Segments,所以理论上,这个时候,最多可以同时支持 16 个线程并发写,只要它们的操作分别分布在不同的 Segment 上。这个值可以在初始化的时候设置为其他值,但是一旦初始化以后,它是不可以扩容的。
JDK8中彻底放弃了Segment转而采用的是Node,其设计思想也不再是JDK1.7中的分段锁思想。
Node:保存key,value及key的hash值的数据结构。其中value和next都用volatile修饰,保证并发的可见性。
Java8 ConcurrentHashMap结构基本上和Java8的HashMap一样,不过保证线程安全性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 final V putVal (K key, V value, boolean onlyIfAbsent) { if (key == null || value == null ) throw new NullPointerException (); 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; if (tab == null || (n = tab.length) == 0 ) tab = initTable(); else if ((f = tabAt(tab, i = (n - 1 ) & hash)) == null ) { if (casTabAt(tab, i, null , new Node <K,V>(hash, key, value))) break ; } else if ((fh = f.hash) == MOVED) tab = helpTransfer(tab, f); else if (onlyIfAbsent && fh == hash && ((fk = f.key) == key || (fk != null && key.equals(fk))) && (fv = f.val) != null ) return fv; else { V oldVal = null ; synchronized (f) { } if (binCount != 0 ) { if (binCount >= TREEIFY_THRESHOLD) treeifyBin(tab, i); if (oldVal != null ) return oldVal; break ; } } } addCount(1L , binCount); return null ; }
区别总结
特性/集合类
HashMap
Hashtable
ConcurrentHashMap
线程安全
否
是,基于方法锁
是,基于分段锁
继承关系
AbstractMap
Dictionary
AbstractMap,ConcurrentMap
允许null值
K-V都允许
K-V都不允许
K-V都不允许
默认初始容量
16
11
16
默认加载因子
0.75
0.75
0.75
扩容后容量
原来的两倍
原来的两倍加1
原来的两倍
是否支持fail-fast
支持
不支持
fail-safe
ConcurrentSkipListMap ConcurrentSkipListMap
是线程安全的有序的哈希表,适用于高并发的场景。ConcurrentSkipListMap
和TreeMap
,它们虽然都是有序的哈希表。也有不同点
第一,它们的线程安全机制不同,TreeMap
是非线程安全的,而ConcurrentSkipListMap
是线程安全的。
第二,ConcurrentSkipListMap
是通过跳表实现的,而TreeMap
是通过红黑树实现的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 * Here's the sequence of events for a deletion of node n with * predecessor b and successor f, initially: * * +------+ +------+ +------+ * ... | b |------>| n |----->| f | ... * +------+ +------+ +------+ * * 1. CAS n' s value field from non-null to null . * Traversals encountering a node with null value ignore it. * However, ongoing insertions and deletions might still modify * n's next pointer. * * 2. CAS n' s next pointer to point to a new marker node. * From this point on, no other nodes can be appended to n. * which avoids deletion errors in CAS-based linked lists. * * +------+ +------+ +------+ +------+ * ... | b |------>| n |----->|marker|------>| f | ... * +------+ +------+ +------+ +------+ * * 3. CAS b's next pointer over both n and its marker. * From this point on, no new traversals will encounter n, * and it can eventually be GCed. * +------+ +------+ * ... | b |----------------------------------->| f | ... * +------+ +------+
插入和删除操作都是利用CAS进行,如果过程中发生竞争,当前线程将重试操作,直到成功为止。
ConcurrentSkipListMap内部采用了SkipList数据结构实现,使用三个内部类来构建这样的结构:
Node
、Index
、HeadIndex
1 2 3 4 5 6 7 8 9 10 11 12 13 14 * Head nodes Index nodes * +-+ right +-+ +-+ * |2 |---------------->| |--------------------->| |->null * +-+ +-+ +-+ * | down | | * v v v * +-+ +-+ +-+ +-+ +-+ +-+ * |1 |----------->| |->| |------>| |----------->| |------>| |->null * +-+ +-+ +-+ +-+ +-+ +-+ * v | | | | | * Nodes next v v v v v * +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ * | |->|A|->|B|->|C|->|D|->|E|->|F|->|G|->|H|->|I|->|J|->|K|->null * +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+ +-+
index nodes 和 head nodes 基于Node节点的索引,提供向下和向右的索引
head nodes 比 Index nodes 多了一个level来表示层级
LinkedHashMap实现LRU 介绍LinkedHashMap维护的两种顺序,插入顺序和访问顺序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap <>(16 ,0.75f ,true );; linkedHashMap.put("f" , "1" ); linkedHashMap.put("s" , "2" ); linkedHashMap.put("t" , "3" ); System.out.println("插入顺序:" ); for (Map.Entry<String, String> item : linkedHashMap.entrySet()) { System.out.println(item.getKey() + ": =>" + item.getValue()); } System.out.println("访问顺序:" ); for (Map.Entry<String, String> item : linkedHashMap.entrySet()) { System.out.println(item.getKey() + ": =>" + item.getValue()); } }
插入顺序: f: =>1 s: =>2 t: =>3 访问顺序: f: =>1 s: =>2 t: =>3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap <>(16 ,0.75f ,true ); linkedHashMap.put("f" , "1" ); linkedHashMap.put("s" , "2" ); linkedHashMap.put("t" , "3" ); System.out.println("插入顺序:" ); for (Map.Entry<String, String> item : linkedHashMap.entrySet()) { System.out.println(item.getKey() + ": =>" + item.getValue()); } linkedHashMap.get("s" ); System.out.println("访问顺序:" ); for (Map.Entry<String, String> item : linkedHashMap.entrySet()) { System.out.println(item.getKey() + ": =>" + item.getValue()); }
插入顺序: f: =>1 s: =>2 t: =>3 访问顺序: f: =>1 t: =>3 s: =>2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class LRULinkedHashMap <K,V> extends LinkedHashMap <K,V> { private int cap; LRULinkedHashMap(int cap){ super (16 ,0.75f ,true ); this .cap = cap; } @Override protected boolean removeEldestEntry (Map.Entry<K, V> eldest) { return size() > cap; } }