ArrayList | Vector | CopyOnWriteArrayList | LinkedList | HashMap | ConcurrentHashMap | LinkedHashMap | LinkedBlockingQueue | PriorityQueue | |
使用場景 | 隨機訪問 | ArrayList的線程安全版 | 讀多寫少,寫加鎖,寫操作在復制的數組上進行,會導致數據不一致,存在 | 添加刪除更快 | 映射 | 線程安全的HashMap | 保證插入次序或者LRU次序的HashMap | 鏈表實現的阻塞隊列 | 優先隊列,排序 |
底層實現 | 數組 | 數組 | 數組 | 雙向鏈表 | 數組+鏈表/紅黑樹(鏈表長度大於8時) | 數組+鏈表/紅黑樹 | 數組+鏈表/紅黑樹,另外維護了一個雙向鏈表 | 雙向鏈表 | 堆 |
擴容 | 1.5倍 | 2倍 | 每次add操作都會將數組拷貝到len+1長的新數組中 | / | 2倍(rehash) | 2倍 | |||
線程安全 | 否,可以使用Collections.synchronizedList()得到線程安全的List | 是(synchronized) | 是(ReentrantLock) | 否 | 否 | 是(分段表/CAS+synchronized(jdk1.8之后)) | 否 | 是(ReentrantLock) | 否 |
Fail-Fast | 是 | 是 | 否 | 是 | 是 | 否 | 是 |
1.Fail-Fast使用 protected transient int modCount = 0; 支持,modCount記錄容器結構化修改次數。在操作的前后判斷modCount是否改變,若改變則認為序列化或者使用迭代其期間容器被結構化修改,則拋出異常。
2.HashMap中使用的技巧:
- 求hash值:將key的hash值的高16位和低16位進行與運算,降低哈希碰撞
- 取模:y%x => y&(x-1)
- 擴容rehash計算桶下標:原容量的二進制位為0則位置不變,為1則位置+oldCap
- 當用戶傳入制定容量時,找到大於等於容量的最近的二進制數
一、ArrayList
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList主要實現了List、RandomAccess接口。
ArrayList是List接口的可變大小數組的實現。實現所有可選鏈表操作,允許所有元素,包括null。除了實現了List接口之外,這個類還提供了操作用來內部存儲鏈表的數組大小的方法(這個類大致等同於Vector,除了他是異步的以外)。
size()、isEmpty()、get()、set()、iterator()、listIterator()操作以恆定的時間運行。add()操作以O(n)的時間復雜度運行,其他的所有操作則以線性的時間運行(粗略的說)。常量因子相比與LinkedList較低。
每個ArrayList實例有一個capacity(容量)。capacity是用來存儲鏈表元素的數組的大小。它總是至少和list大小一樣大。當元素被加入到ArrayList后,它的capacity會自動擴容。除了增加一個元素有恆定的攤銷時間成本之外,增長策略的細節未被指定。
在添加大量的元素之前,一個應用可以使用ensureCapacity()操作提高一個實例的capacity。這可以減少增量重分配的數量(This may reduce the amount of incremental reallocation)。
要注意的是ArrayList不是同步的。如果多線程並發的訪問一個ArrayList實例,至少有一個線程會修改鏈表結構,必須在它的外部進行異步控制。(一個結構化的修改是指增加或刪除一個或多個元素的操作,或者顯式改變底層數組的大小;僅僅設置一個元素的值不是一個結構化的修改。)一個典型的實現是通過對某個對象的同步來自然封裝這個鏈表。(This is typically accomplished by synchronizing on some object that naturally encapsulates the list.)
如果沒有這種對象,這個鏈表則需要用Collections.synchronizedList()方法來封裝。這最好是在創建的時候完成,以防止偶然的對鏈表的異步訪問。
List list = Collections.synchronizedList(new ArrayList(...));
類的iterator()和listIterator(int)方法返回的迭代器都是fail-fast的:如果迭代器創建之后,鏈表在任意時刻被結構化的修改,除了迭代器自己的remove()、add()方法(迭代器自己的add()、remove()方法會將expectedModCount=modCount,因此不會觸發異常,具體見方法分析),迭代器將拋出ConcurrentModificationException異常。因此,面對並發修改,迭代器將快速失敗並清空,而不是在未來的不確定的時刻冒着任意的風險以及不確定的行為。
注意,迭代器的fail-fast行為不能被保證一定發生,通常來說,當異步並發修改發生時,不可能做出任何硬性保證。Fail-fast迭代器基於最大努力拋出ConcurrentModificationException異常。因此,這樣的做法的是錯的,寫一個程序,依賴於這個異常來保證程序的正確性:迭代器的fail-fast行為應該只被用來檢查bug(個人理解是如果發生並發修改,並不一定保證拋出ConcurrentModificationException異常,因此程序的正確性不應該依賴此異常)。
1.默認容量capacity
/** * Default initial capacity.
*/ private static final int DEFAULT_CAPACITY = 10;
2.存儲ArrayList元素的數組elementData
/** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */ transient Object[] elementData; // non-private to simplify nested class access
ArrayList的容量就是數組的長度。
任何elementData等於DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList,當其第一個元素被添加進來時,elementData的長度將被擴展為DEFAULT_CAPACITY。
3.ArrayList包含元素的數量size
/** * The size of the ArrayList (the number of elements it contains). * * @serial
*/ private int size;
4.構造器
ArrayList包含三種構造器:
1.ArrayList():將elementData初始化為空數組DEFAULTCAPACITY_EMPTY_ELEMENTDATA。
2.ArrayList(int initialCapacity):將element初始化為容量為initialCapacity的數組,當initialCapacity為0是,初始化為EMPTY_ELEMENTDATA
3.ArrayList(Collection<? extends E> c):使用集合來初始化elementData,如果集合的length為0,則初始化為EMPTY_ELEMENTDATA。
/** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } /** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // defend against c.toArray (incorrectly) not returning Object[] // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
5.Fail-fast與結構化修改
ArrayList繼承於AbstractList,AbstractList中的域modCount用來統計結構化修改的次數。
結構化修改是指改變鏈表的大小,或者其他擾亂它的方式,可能導致正在進行的迭代可能產生不正確的結果。

/** * The number of times this list has been <i>structurally modified</i>. * Structural modifications are those that change the size of the * list, or otherwise perturb it in such a fashion that iterations in * progress may yield incorrect results. * * <p>This field is used by the iterator and list iterator implementation * returned by the {@code iterator} and {@code listIterator} methods. * If the value of this field changes unexpectedly, the iterator (or list * iterator) will throw a {@code ConcurrentModificationException} in * response to the {@code next}, {@code remove}, {@code previous}, * {@code set} or {@code add} operations. This provides * <i>fail-fast</i> behavior, rather than non-deterministic behavior in * the face of concurrent modification during iteration. * * <p><b>Use of this field by subclasses is optional.</b> If a subclass * wishes to provide fail-fast iterators (and list iterators), then it * merely has to increment this field in its {@code add(int, E)} and * {@code remove(int)} methods (and any other methods that it overrides * that result in structural modifications to the list). A single call to * {@code add(int, E)} or {@code remove(int)} must add no more than * one to this field, or the iterators (and list iterators) will throw * bogus {@code ConcurrentModificationExceptions}. If an implementation * does not wish to provide fail-fast iterators, this field may be * ignored. */ protected transient int modCount = 0;
在序列化以及迭代(forEach)等操作的前后,需要記錄modCount的值進行對比,如果不相等,則拋出ConcurrentModificationException異常。
/** * Saves the state of the {@code ArrayList} instance to a stream * (that is, serializes it). * * @param s the stream * @throws java.io.IOException if an I/O error occurs * @serialData The length of the array backing the {@code ArrayList} * instance is emitted (int), followed by all of its elements * (each an {@code Object}) in the proper order. */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // Write out size as capacity for behavioral compatibility with clone() s.writeInt(size); // Write out all elements in the proper order. for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } /** * @throws NullPointerException {@inheritDoc} */ @Override public void forEach(Consumer<? super E> action) { Objects.requireNonNull(action); final int expectedModCount = modCount; final Object[] es = elementData; final int size = this.size; for (int i = 0; modCount == expectedModCount && i < size; i++) action.accept(elementAt(es, i)); if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
6.重要的方法
- public void ensureCapacity(int minCapacity)
參數minCapacity是要擴容的最小大小,要進行擴容必須滿足:
1.minCapacity大於現在elementData的長度。
2.不滿足(elementData為默認空數組&&要擴容的最小大小小於DEFAULT_CAPACITY
/** * Increases the capacity of this {@code ArrayList} instance, if * necessary, to ensure that it can hold at least the number of elements * specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ public void ensureCapacity(int minCapacity) { if (minCapacity > elementData.length && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA && minCapacity <= DEFAULT_CAPACITY)) { modCount++; grow(minCapacity); } }
- private int newCapacity(int minCapacity)
返回至少和給定minCapacity一樣大小的容量。
如果可以的話將返回elementData大小的1.5倍。
除非給定minCapacity大於MAX_ARRAY_SIZE,否則容量不應超過MAX_ARRAY_SIZE。
/** * Returns a capacity at least as large as the given minimum capacity. * Returns the current capacity increased by 50% if that suffices. * Will not return a capacity greater than MAX_ARRAY_SIZE unless * the given minimum capacity is greater than MAX_ARRAY_SIZE. * * @param minCapacity the desired minimum capacity * @throws OutOfMemoryError if minCapacity is less than zero */ private int newCapacity(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity <= 0) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
//之前介紹elementData的時候說到,elementData為DEFAULTCAPACITY_EMPTY_ELEMENTDATA
//的空ArrayList,當第一次添加元素時,容量被擴充為DEFAULT_CAPACITY return Math.max(DEFAULT_CAPACITY, minCapacity); if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return minCapacity; } return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity : hugeCapacity(minCapacity); }
- private Object[] grow(int minCapacity)
- private Object[] grow()
/** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity * @throws OutOfMemoryError if minCapacity is less than zero */ private Object[] grow(int minCapacity) { return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity)); } private Object[] grow() { return grow(size + 1); }
- private void add(E e, Object[] elementData, int s)
- public boolean add(E e)
- public add(int index, E element)
add(E e)執行時,根據調用關系,當要進行擴容時,最終會調用newCapacity(),容量為擴充為element.length*1.5和minCapacity中的較大者(通常情況是這樣,具體參見代碼newCapacity())。
add(int index, E element)執行時,如需進行擴容會先進行擴容,然后通過System.arraycopy進行移位,再將元素插入指定位置。
/** * This helper method split out from add(E) to keep method * bytecode size under 35 (the -XX:MaxInlineSize default value), * which helps when add(E) is called in a C1-compiled loop. */ private void add(E e, Object[] elementData, int s) { if (s == elementData.length) elementData = grow(); elementData[s] = e; size = s + 1; } /** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return {@code true} (as specified by {@link Collection#add}) */ public boolean add(E e) { modCount++; add(e, elementData, size); return true; } /** * Inserts the specified element at the specified position in this * list. Shifts the element currently at that position (if any) and * any subsequent elements to the right (adds one to their indices). * * @param index index at which the specified element is to be inserted * @param element element to be inserted * @throws IndexOutOfBoundsException {@inheritDoc} */ public void add(int index, E element) { rangeCheckForAdd(index); modCount++; final int s; Object[] elementData; if ((s = size) == (elementData = this.elementData).length) elementData = grow(); System.arraycopy(elementData, index, elementData, index + 1, s - index); elementData[index] = element; size = s + 1; }
- public E remove(int index)
- public boolean remove(Object o)
- private void fastRemove(Object[] es, int i)
remove(int index)主要調用了fastRemove()來進行刪除,fastRemove()用的也是System.arraycopy()。
remove(Object o)先找到該對象的索引,然后調用fastRemove()。
/** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their * indices). * * @param index the index of the element to be removed * @return the element that was removed from the list * @throws IndexOutOfBoundsException {@inheritDoc} */ public E remove(int index) { Objects.checkIndex(index, size); final Object[] es = elementData; @SuppressWarnings("unchecked") E oldValue = (E) es[index]; fastRemove(es, index); return oldValue; } /** * Removes the first occurrence of the specified element from this list, * if it is present. If the list does not contain the element, it is * unchanged. More formally, removes the element with the lowest index * {@code i} such that * {@code Objects.equals(o, get(i))} * (if such an element exists). Returns {@code true} if this list * contained the specified element (or equivalently, if this list * changed as a result of the call). * * @param o element to be removed from this list, if present * @return {@code true} if this list contained the specified element */ public boolean remove(Object o) { final Object[] es = elementData; final int size = this.size; int i = 0; found: { if (o == null) { for (; i < size; i++) if (es[i] == null) break found; } else { for (; i < size; i++) if (o.equals(es[i])) break found; } return false; } fastRemove(es, i); return true; } /** * Private remove method that skips bounds checking and does not * return the value removed. */ private void fastRemove(Object[] es, int i) { modCount++; final int newSize; if ((newSize = size - 1) > i) System.arraycopy(es, i + 1, es, i, newSize - i); es[size = newSize] = null; }
- 迭代器的add()、remove(),通過對expectedModCount的同步,使得迭代器本身對鏈表的修改不會觸發異常。
private class Itr implements Iterator<E> { ... public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } ... } private class ListItr extends Itr implements ListIterator<E> { ... public void add(E e) { checkForComodification(); try { int i = cursor; ArrayList.this.add(i, e); cursor = i + 1; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } ... }
7.序列化
ArrayList 基於數組實現,並且具有動態擴容特性,因此保存元素的數組不一定都會被使用,那么就沒必要全部進行序列化。
可以發現,elementData被transient修飾,不會被序列化。
/** * Saves the state of the {@code ArrayList} instance to a stream * (that is, serializes it). * * @param s the stream * @throws java.io.IOException if an I/O error occurs * @serialData The length of the array backing the {@code ArrayList} * instance is emitted (int), followed by all of its elements * (each an {@code Object}) in the proper order. */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { // Write out element count, and any hidden stuff int expectedModCount = modCount; s.defaultWriteObject(); // Write out size as capacity for behavioral compatibility with clone() s.writeInt(size); // Write out all elements in the proper order. for (int i=0; i<size; i++) { s.writeObject(elementData[i]); } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } } /** * Reconstitutes the {@code ArrayList} instance from a stream (that is, * deserializes it). * @param s the stream * @throws ClassNotFoundException if the class of a serialized object * could not be found * @throws java.io.IOException if an I/O error occurs */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in size, and any hidden stuff s.defaultReadObject(); // Read in capacity s.readInt(); // ignored if (size > 0) { // like clone(), allocate array based upon size not capacity SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size); Object[] elements = new Object[size]; // Read in all elements in the proper order. for (int i = 0; i < size; i++) { elements[i] = s.readObject(); } elementData = elements; } else if (size == 0) { elementData = EMPTY_ELEMENTDATA; } else { throw new java.io.InvalidObjectException("Invalid size: " + size); } }
二、Vector
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
Vector主要實現了List、RandomAccess接口。
Vector類實現了一個對象的可增長數組。如同一個數組,Vector包含可以使用整數索引訪問的component。然而,Vector被創建出來之后,其大小可以根據增加或者移除item來增長或者縮減。
每個Vector通過維持capacity和capacityIncrement來試圖優化存儲管理。capacity總是至少和Vector的size一樣大;通常,capacity會更大,因為當元素添加進Vector的時候,Vector的存儲空間會增長capacityIncrement的大小。在插入大量元素以前,應用會增長Vector的capacity;Vector減少了增長重分配的數量。
1.重要的域
- elementData:存儲Vector組件(以下翻譯成元素)的數組elementData,最后一個元素之后的數組元素為null
/** * The array buffer into which the components of the vector are * stored. The capacity of the vector is the length of this array buffer, * and is at least large enough to contain all the vector's elements. * * <p>Any array elements following the last element in the Vector are null. * * @serial */ protected Object[] elementData;
- elementCount:Vector對象合法元素的個數
/** * The number of valid components in this {@code Vector} object. * Components {@code elementData[0]} through * {@code elementData[elementCount-1]} are the actual items. * * @serial */ protected int elementCount;
- capacityIncrement:當元素數量超過capacity時,capacity會自動增長capacityIncrement的大小。如果capacityIncrement小於等於0,capacit則y在每次需要增長時會擴大為兩倍。
/** * The amount by which the capacity of the vector is automatically * incremented when its size becomes greater than its capacity. If * the capacity increment is less than or equal to zero, the capacity * of the vector is doubled each time it needs to grow. * * @serial */ protected int capacityIncrement;
3.重要的方法
/** * Returns a capacity at least as large as the given minimum capacity. * Will not return a capacity greater than MAX_ARRAY_SIZE unless * the given minimum capacity is greater than MAX_ARRAY_SIZE. * * @param minCapacity the desired minimum capacity * @throws OutOfMemoryError if minCapacity is less than zero */ private int newCapacity(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity <= 0) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return minCapacity; } return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity : hugeCapacity(minCapacity); }
4.Vector與ArrayList比較
- Vector與ArrayList類似,但是Vector使用了synchronized進行同步。
- 由於Vector時同步的,因此其開銷要比ArrayList更大,訪問速度更慢。最好使用ArrayList而不是Vector,因為同步操作完全可以由程序員自己來控制。
- Vector每次擴容的空間是由capacityIncrement決定的,要么為oldCapacity+capacityIncrement,要么為原先的兩倍,ArrayList為原先的1.5倍。
5.替代方案
- 在ArrayList部分中提到,可以使用Collections.synchronizedList()得到一個線程安全的ArrayList。
- 可以使用concurrent並發包下的CopyOnWriteArrayList類。
三、CopyOnWriteArrayList
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
CopyOnWriteArrayList主要實現了List、RandomAccess接口。
ArrayList線程安全的版本,所有的修改操作通過底層數組的一個拷貝來實現。
這通常會造成很大的開銷,但是當便利操作遠超於修改操作時,通常會更有效。並且當你不能或者不想同步遍歷這將會很有用,你需要排除並發帶來的干擾。“快照”風格的迭代器方法在迭代器創建時,使用數組狀態的一個引用。在迭代器的生命周期中,數組不會被改變,因此不會存在干擾且迭代器保證不會拋出ConcurrentModificationException。迭代器被創建之后,不會反映對鏈表的增、刪、改。迭代器自身的元素改變操作remove()、set()不被支持。這些方法會拋出UnsupportOperationException。
所有的元素都被允許,包括null。
存儲一致性效果:和其他並發集合一樣,向CopyOnWriteArrayList放置一個元素的線程發生在訪問或移除這個元素的其他線程之前。
1.重要的域
- 鎖lock(對一些修改操作上鎖)
/** * The lock protecting all mutators. (We have a mild preference * for builtin monitors over ReentrantLock when either will do.) */ final transient Object lock = new Object();
- array
/** The array, accessed only via getArray/setArray. */ private transient volatile Object[] array;
2.重要的方法
修改的操作都進行了鎖,防止數據丟失等。
add()方法先getArray()取得數組,然后拷貝一份新數組進行修改,最后將底層數組設置為新數組。
同理,remove(int index)也是不是對原數組進行操作,而是使用一個新的數組。
寫操作在新的拷貝上進行,讀操作仍然讀取原來的數組,互不影響。
public E set(int index, E element) { synchronized (lock) { Object[] es = getArray(); E oldValue = elementAt(es, index); if (oldValue != element) { es = es.clone(); es[index] = element; setArray(es); } return oldValue; } } 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; } } public void add(int index, E element) { synchronized (lock) { Object[] es = getArray(); int len = es.length; if (index > len || index < 0) throw new IndexOutOfBoundsException(outOfBounds(index, len)); Object[] newElements; int numMoved = len - index; if (numMoved == 0) newElements = Arrays.copyOf(es, len + 1); else { newElements = new Object[len + 1]; System.arraycopy(es, 0, newElements, 0, index); System.arraycopy(es, index, newElements, index + 1, numMoved); } newElements[index] = element; setArray(newElements); } } public E remove(int index) { synchronized (lock) { Object[] es = getArray(); int len = es.length; E oldValue = elementAt(es, index); int numMoved = len - index - 1; Object[] newElements; if (numMoved == 0) newElements = Arrays.copyOf(es, len - 1); else { newElements = new Object[len - 1]; System.arraycopy(es, 0, newElements, 0, index); System.arraycopy(es, index + 1, newElements, index, numMoved); } setArray(newElements); return oldValue; } }
3.適用場景
CopyOnWriteArrayList在寫操作的同時允許讀操作,大大提交了讀操作的性能,因此很適合讀多寫少的應用場景。
但是CopyOnWriteArrayList有其缺陷:
- 內存占用:在寫操作時需要復制一個新的數組,使得內存占用為原來的兩倍左右。
- 數據不一致:讀操作不能讀取實時性的數據,因為部分寫操作的數據還未同步到數組中。
所以CopyOnWriteArrayList不適合內存敏感以及對實時性要求很高的場景。
四、LinkedList
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
LinkedList主要實現了List、Deque(雙向鏈表)接口。
List和Deque接口的雙向鏈表實現。實現了所有鏈表的操作,允許所有類型包括null。
所有的操作表現為一個雙向鏈表所預期的那樣。索引鏈表的操作將從開始或結尾開始遍歷鏈表,這取決於開始和結尾哪個更靠近要索引的節點。
注意LinkedList不是同步的。如果在並發訪問下,並且至少有一個會結構化的修改鏈表,則必須在外部進行同步。關於結構化修改和之前所述一致。
LinkedList也可以使用Collections.synchronizedList(new LinkedList(...));來進行封裝,使得LinkedList支持並發訪問。
LinkedList的迭代器也遵循Fail-fast機制。
1.重要的域
- transient Node<E> first;
- transient Node<E> last;
/** * Pointer to first node. */ transient Node<E> first; /** * Pointer to last node. */ transient Node<E> last;
2.重要的方法
LinkedList方法較為簡單,主要是對雙向鏈表進行操作。

/** * Links e as first element. */ private void linkFirst(E e) { final Node<E> f = first; final Node<E> newNode = new Node<>(null, e, f); first = newNode; if (f == null) last = newNode; else f.prev = newNode; size++; modCount++; } /** * Links e as last element. */ void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; } /** * Inserts element e before non-null Node succ. */ void linkBefore(E e, Node<E> succ) { // assert succ != null; final Node<E> pred = succ.prev; final Node<E> newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++; } /** * Unlinks non-null first node f. */ private E unlinkFirst(Node<E> f) { // assert f == first && f != null; final E element = f.item; final Node<E> next = f.next; f.item = null; f.next = null; // help GC first = next; if (next == null) last = null; else next.prev = null; size--; modCount++; return element; } /** * Unlinks non-null last node l. */ private E unlinkLast(Node<E> l) { // assert l == last && l != null; final E element = l.item; final Node<E> prev = l.prev; l.item = null; l.prev = null; // help GC last = prev; if (prev == null) first = null; else prev.next = null; size--; modCount++; return element; } /** * Unlinks non-null node x. */ E unlink(Node<E> x) { // assert x != null; final E element = x.item; final Node<E> next = x.next; final Node<E> prev = x.prev; if (prev == null) { first = next; } else { prev.next = next; x.prev = null; } if (next == null) { last = prev; } else { next.prev = prev; x.next = null; } x.item = null; size--; modCount++; return element; }

/** * Returns the (non-null) Node at the specified element index. */ Node<E> node(int index) { // assert isElementIndex(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; } }
3.LinkedList和ArrayList的比較
- ArrayList底層基於動態擴容的數組實現,LinkedList基於雙向鏈表實現。
- ArrayList支持隨機訪問,LinkedList不支持。
- LinkedList在任意位置添加刪除元素更快。
這些區別主要是基於數組和鏈表的特點。
五、HashMap
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
HashMap主要實現了Map接口。
Map接口基於HashTable的實現。HashMap提供了所有的map操作,允許null value和null key。HashMap類大致等同於HashTable類,除了HashMap是異步的且允許null。這個類不保證map的順序;特別的,它不保證順序隨着時間的推移會保持不變。
在哈希函數使元素正確分散的前提下,HashMap對於get()、put()操作提供恆定時間的表現(constant-time performance)。集合視圖的迭代需要與HashMap的capacity(bucket的數量)及size(鍵值對的數量)成正比的時間。因此,如果迭代性能很重要的話,不要把初始容量設的太高(或者加載因子設的太低)使很重要的。
HashMap實例有兩個參數影響其性能:
- 初始容量
- 加載因子
容量(capacity)是HashTable的桶(bucket)的數量,初始容量在創建時HashTable的容量。
加載因子(load factor)是衡量在capacity擴容之前,HashTable允許被填裝的多滿。
當HashTable中鍵值對個數超過加載因子和當前容量的乘積時,HashTable將會被rehash(即內部數據結構將重建),HashTable的桶將會擴大為近似兩倍的桶。
一般來說,默認加載因子(0.75)提供了對時間和空間較好的平衡。加載因子更高會減少空間開銷但會提升查找開銷(對照HashMap類大多數操作,包括get()、put())。在初始化容量的時候,預期的鍵值對數量和加載因子需要被考慮,來最小化rehash的次數。如果初始化容量大於鍵值對最大值除以加載因子,就不會發生rehash操作。
如果HashMap實例要存儲較多的鍵值對,用一個足夠大的容量來創建它將使得鍵值對更有效率的被存儲,相比於讓它自動rehash來增長。注意使用具有相同hashCode()的鍵會降低一些hash table的性能。為了改善影響,當鍵是可比較的,這個類將使用比較順序來打破這種關系。
注意,HashMap也不是同步的。可以使用Collections.synchronizedMap(new HashMap(...))來進行異步訪問。
1.構造函數
/** * Constructs an empty {@code HashMap} with the specified initial * capacity and load factor. * * @param initialCapacity the initial capacity * @param loadFactor the load factor * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */ 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); } /** * Constructs an empty {@code HashMap} with the specified initial * capacity and the default load factor (0.75). * * @param initialCapacity the initial capacity. * @throws IllegalArgumentException if the initial capacity is negative. */ public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * Constructs an empty {@code HashMap} with the default initial capacity * (16) and the default load factor (0.75). */ public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } /** * Constructs a new {@code HashMap} with the same mappings as the * specified {@code Map}. The {@code HashMap} is created with * default load factor (0.75) and an initial capacity sufficient to * hold the mappings in the specified {@code Map}. * * @param m the map whose mappings are to be placed in this map * @throws NullPointerException if the specified map is null */ public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); }
2.Node節點
/** * Basic hash bin node, used for most entries. (See below for * TreeNode subclass, and in LinkedHashMap for its Entry subclass.) */ 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; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }
3.put操作
需要注意的是,從jdk1.8開始,新節點是插入在鏈表的尾部,此前是插在頭部。
- putVal(...)
/** * Implements Map.put and related methods. * * @param hash hash for key * @param key the key * @param value the value to put * @param onlyIfAbsent if true, don't change existing value * @param evict if false, the table is in creation mode. * @return previous value, or null if none */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i; //若table為null或者大小為0,則初始化表 if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; //如果對應的桶為null if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); //若不為null else { HashMap.Node<K,V> e; K k; //如果為同一個key,則取得節點 if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; //若為TreeNode else if (p instanceof HashMap.TreeNode) e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //否則遍歷鏈表 else { for (int binCount = 0; ; ++binCount) { //如果遍歷到結尾還沒找到相同的key,則將新node插入到鏈表尾部 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可能要被覆蓋的節點的位置,如果插入到鏈表尾,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; }
4.確定桶下標
HashMap中有許多操作要先確定一個key對應的桶的下表
- 計算key的hash值
這里對h和h>>>16進行異或,而不用key的hashCode(),是因為由於HashMap的key的hash值最終是要和capacity進行與運算,當capacity值不大時,與運算實際有效的位數較少,這樣key.hashCode()得到的hash值有很大一部分沒有用上,散列到table上沖突的概率增大,(h = key.hashCode()) ^ (h >>> 16)將hash值的低16位變為低16和高16的異或,原來的高16位不變,這樣可以減少散列沖突。
至於 >> 是指右移,>>>是指無符號右移。
具體解釋看這里,膜拜大佬。
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
- 取模
如果x為2的次方,則 (y%x) 等於 (y & x-1)。
令x = 1 << 4,即x為2的4次方:
x : 00010000
x - 1 : 00001111
令一個數y與x-1做與運算,可以去除y的第4位以上的數:
y : 10110010 x - 1 : 00001111 y&(x-1) : 00000010
得到的結果與y對x取模的結果是一樣的。
由於位運算的代價比求模運算小的多,因此在進行求模運算的時候用位運算替代能帶來更大的性能。
確定桶下標的最后一步是將key的hash值對桶個數取模:hash%capacity,如果能保證capacity為2的n次方,那么就可以將取模的操作轉換為位運算。
在上一節中,putVal()方法也使用了位運算來計算桶下標。
5.動態擴容
設 HashMap 的 table 長度為 M,需要存儲的鍵值對數量為 N,如果哈希函數滿足均勻性的要求,那么每條鏈表的長度大約為 N/M,因此平均查找次數的復雜度為 O(N/M)。
為了讓查找的成本降低,應該盡可能使得 N/M 盡可能小,因此需要保證 M 盡可能大,也就是說 table 要盡可能大。但如果M太大的話,會造成空間上的浪費,因此需要在時間和空間的開銷上找到平衡。HashMap 采用動態擴容來根據當前的 N 值來調整 M 值,使得空間效率和時間效率都能得到保證。
和擴容相關的參數主要有:capacity、size、threshold 和 loadFactor。
參數 | 含義 |
---|---|
capacity | table 的容量大小,默認為 16。需要注意的是 capacity 必須保證為 2 的 n 次方。 |
size | 鍵值對數量。 |
threshold | size 的臨界值,當 size 大於等於 threshold 就必須進行擴容操作。 |
loadFactor | 裝載因子,table 能夠使用的比例,threshold = capacity * loadFactor。 |
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; static final int MAXIMUM_CAPACITY = 1 << 30; static final float DEFAULT_LOAD_FACTOR = 0.75f; transient Node<K,V>[] table; transient int size; int threshold; final float loadFactor;
擴容使用resize()方法實現:
/** * Initializes or doubles table size. If null, allocates in * accord with initial capacity target held in field threshold. * Otherwise, because we are using power-of-two expansion, the * elements from each bin must either stay at same index, or move * with a power of two offset in the new table. * 初始化或者將table大小變為兩倍。 * 如果table為null,則根據threshold字段保存的初始容量來分配。 * 如果table不為null,我們將用二次冪來擴展,每個桶的元素必須在相同索引的位置,或者在新表中 * 移動二次冪的偏移。 * * @return the table */ final HashMap.Node<K,V>[] resize() { HashMap.Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; //如果原來容量大於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; // double threshold } //如果原來容量為0,原來的閾值不為0,則為第一次初始化table,新的容量為原來的閾值 //使用帶參的構造函數走的是這個分支 else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; //如果原來的容量為0,原來閾值也為0,則用默認值來初始化新的容量和閾值 //使用HashMap不帶參的構造函數走的就是這個分支 else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } //第二個分支沒有設置到newThr,在這里重新計算 if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; //使用新的容量定義新的table @SuppressWarnings({"rawtypes","unchecked"}) HashMap.Node<K,V>[] newTab = (HashMap.Node<K,V>[])new HashMap.Node[newCap]; table = newTab; //遍歷舊表,將舊表中元素移到新表中 if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { HashMap.Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; //若當前桶只有一個元素,則直接進行rehash if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof HashMap.TreeNode) ((HashMap.TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order HashMap.Node<K,V> loHead = null, loTail = null; HashMap.Node<K,V> hiHead = null, hiTail = null; HashMap.Node<K,V> next; do { next = e.next; //如果哈希值和oldCap做與運算為0,則在新表中還在原位 if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } //若與運算不為0,則在新表中的位置為原索引+oldCap,可以舉例驗證 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; }
5.1擴容-重新計算桶下標
重新計算桶下標的邏輯也在resize()方法中
//如果哈希值和oldCap做與運算為0,則在新表中還在原位 if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } //若與運算不為0,則在新表中的位置為原索引+oldCap else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; }
i,f (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
在進行擴容時,需要把鍵值對重新放到對應的桶上。HashMap 使用了一個特殊的機制,可以降低重新計算桶下標的操作。
假設原數組長度 capacity 為 16,擴容之后 new capacity 為 32:
capacity : 00010000
new capacity : 00100000
對於一個 Key,
- 它的哈希值如果在第 5 位上為 0,那么取模得到的結果和之前一樣;
- 如果為 1,那么得到的結果為原來的結果 +16。
6.計算數組容量
HashMap要求容量capacity為2的n次方,構造函數允許用戶傳入的容量不是2的n次方,因為它可以自動地將傳入的容量轉換為2的n次方。
/** * Returns a power of two size for the given target capacity. */ 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; }
具體請看這里。
7.鏈表轉紅黑樹
從 JDK 1.8 開始,一個桶存儲的鏈表長度大於 8 時會將鏈表轉換為紅黑樹。
8.HashMap與HashTable比較
- HashTable使用synchronized來進行同步。
- HashMap可以插入鍵為null的Entry。
- HashMap的迭代器是Fail-fast迭代器。
- HashMap不能保證隨着時間的推移Map中的元素次序是不變的。
六、ConcurrentHashMap
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V> implements ConcurrentMap<K,V>, Serializable
支持檢索的全並發和更新的高預期並發的hash table。ConcurrentHashMap遵循與Hashtable相同的功能,包括Hashtable的每個方法的版本。然而,即使所有的操作是線程安全的,檢索操作也不意味着上鎖,ConcurrentHashMap對於對整個表上鎖來保護所有的訪問沒有任何的支持。在依賴線程安全但不依賴於它的同步細節的程序中,ConcurrentHashMap完全與Hashtable互操作。
檢索操作(包括get)通常不加鎖,所以可能會和更新操作(包括put、remove)重疊。檢索反應大多數近期完成的更新操作的結果(更加正式的說,檢索更新的值只與在這之前發生的更新操作有關)。對於聚合操作(putAll、clear),並發檢索可能只反映一部分插入和移除。相似的,迭代器、分割器、枚舉只反映它們創建時hashtable的狀態。它們不會拋出ConcurrentModificationException。然而迭代器被設計只用來一個時間只在一個線程中使用。記住聚合狀態方法(size、isEmpty、containsValue)的結果只有當map不會在其他線程中並發更新才有用。否則這些方法的結果反應的短暫狀態只適用於監視或者估計,而不適用於程序控制。
當有許多沖突時,ConcurrentHashMap會自動擴張(有不同hashcode的key卻掉進相同的插槽)。
1.put操作
/** * Maps the specified key to the specified value in this table. * Neither the key nor the value can be null. * * <p>The value can be retrieved by calling the {@code get} method * with a key that is equal to the original key. * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return the previous value associated with {@code key}, or * {@code null} if there was no mapping for {@code key} * @throws NullPointerException if the specified key or value is null */ 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) { if (key == null || value == null) throw new NullPointerException(); //獲取key的hash int hash = spread(key.hashCode()); //記錄鏈表長度 int binCount = 0; for (ConcurrentHashMap.Node<K,V>[] tab = table;;) { ConcurrentHashMap.Node<K,V> f; int n, i, fh; //如果還未初始化table if (tab == null || (n = tab.length) == 0) tab = initTable(); //如果對應的桶為空 else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { //用CAS操作插入節點,如果失敗則重試 if (casTabAt(tab, i, null, new ConcurrentHashMap.Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin } //如果在擴容 else if ((fh = f.hash) == MOVED) //幫助數據遷移 tab = helpTransfer(tab, f); //如果table已經初始化完畢,且當前桶已經有數據了,且不在擴容。f為桶中的頭節點 else { V oldVal = null; //對頭節點加鎖 synchronized (f) { if (tabAt(tab, i) == f) { //頭節點hash值大於等於0,說明是鏈表 if (fh >= 0) { binCount = 1; for (ConcurrentHashMap.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; } ConcurrentHashMap.Node<K,V> pred = e; //如果遍歷到結尾,則將新值插入到鏈表尾 if ((e = e.next) == null) { pred.next = new ConcurrentHashMap.Node<K,V>(hash, key, value, null); break; } } } //紅黑樹 else if (f instanceof ConcurrentHashMap.TreeBin) { ConcurrentHashMap.Node<K,V> p; binCount = 2; if ((p = ((ConcurrentHashMap.TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; if (!onlyIfAbsent) p.val = value; } } } }
if (binCount != 0) { //如果binCount大於閾值 if (binCount >= TREEIFY_THRESHOLD) //這個方法和HashMap有點不同,它不是一定會進行紅黑樹轉化 //如果當前數組的長度小於64,則會選擇進行擴容 treeifyBin(tab, i); if (oldVal != null) return oldVal; break; } } } addCount(1L, binCount); return null; }
2.initTable
/** * Initializes table, using the size recorded in sizeCtl. */ private final Node<K,V>[] initTable() { Node<K,V>[] tab; int sc; while ((tab = table) == null || tab.length == 0) { //已有其他的線程在初始化或擴容 if ((sc = sizeCtl) < 0) Thread.yield(); // lost initialization race; just spin //CAS一下,將sizeCtl設置為-1,代表搶到了鎖 else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { if ((tab = table) == null || tab.length == 0) { //初始化數組,長度為16或為構造函數傳入的長度 int n = (sc > 0) ? sc : DEFAULT_CAPACITY; @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; //初始化table table = tab = nt; //0.75 * n sc = n - (n >>> 2); } } finally { //設置sizeCtl為sc,默認情況下是12 sizeCtl = sc; } break; } } return tab; }
3.treeifyBin
當鏈表長度大於給定值時,將鏈表轉化為紅黑樹,但如果table的長度未達到MIN_TREEIFY_CAPACITY時,嘗試擴容而不是樹化。
/** * Replaces all linked nodes in bin at given index unless table is * too small, in which case resizes instead. */ private final void treeifyBin(Node<K,V>[] tab, int index) { Node<K,V> b; int n, sc; if (tab != null) { //當table長度小於MIN_TREEIFY_CAPACITY時,嘗試擴容 if ((n = tab.length) < MIN_TREEIFY_CAPACITY) tryPresize(n << 1); //否則對鏈表進行紅黑樹轉化 else if ((b = tabAt(tab, index)) != null && b.hash >= 0) { synchronized (b) { if (tabAt(tab, index) == b) { TreeNode<K,V> hd = null, tl = null; for (Node<K,V> e = b; e != null; e = e.next) { TreeNode<K,V> p = new TreeNode<K,V>(e.hash, e.key, e.val, null, null); if ((p.prev = tl) == null) hd = p; else tl.next = p; tl = p; } setTabAt(tab, index, new TreeBin<K,V>(hd)); } } } } }
4.tryPresize
嘗試擴容table來容納給定的元素數量
/** * Tries to presize table to accommodate the given number of elements. * * @param size number of elements (doesn't need to be perfectly accurate) */ private final void tryPresize(int size) { int c = (size >= (MAXIMUM_CAPACITY >>> 1)) ? MAXIMUM_CAPACITY : tableSizeFor(size + (size >>> 1) + 1); int sc; //當sizeCtl大於0,代表下次擴容的大小 while ((sc = sizeCtl) >= 0) { Node<K,V>[] tab = table; int n; //初始化代碼? if (tab == null || (n = tab.length) == 0) { n = (sc > c) ? sc : c; if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { try { if (table == tab) { @SuppressWarnings("unchecked") Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n]; table = nt; sc = n - (n >>> 2); } } finally { sizeCtl = sc; } } } //若擴容的大小還沒有達到下次擴容的閾值或者已經大於MAXIMUM_CAPACITY else if (c <= sc || n >= MAXIMUM_CAPACITY) break; //進行擴容和數據遷移 else if (tab == table) { int rs = resizeStamp(n); if (sc < 0) { Node<K,V>[] nt; if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 || sc == rs + MAX_RESIZERS || (nt = nextTable) == null || transferIndex <= 0) break; if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) transfer(tab, nt); } else if (U.compareAndSwapInt(this, SIZECTL, sc, (rs << RESIZE_STAMP_SHIFT) + 2)) //傳入空值,在transfer中進行初始化,將table擴容為兩倍大 transfer(tab, null); } } }
5.transfer
/** * Moves and/or copies the nodes in each bin to new table. See * above for explanation. */ private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) { int n = tab.length, stride; if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) stride = MIN_TRANSFER_STRIDE; // subdivide range 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; } int nextn = nextTab.length; ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); boolean advance = true; boolean finishing = false; // to ensure sweep before committing nextTab for (int i = 0, bound = 0;;) { Node<K,V> f; int fh; while (advance) { int nextIndex, nextBound; if (--i >= bound || finishing) advance = false; else if ((nextIndex = transferIndex) <= 0) { i = -1; advance = false; } else if (U.compareAndSwapInt (this, TRANSFERINDEX, nextIndex, nextBound = (nextIndex > stride ? nextIndex - stride : 0))) { bound = nextBound; i = nextIndex - 1; advance = false; } } if (i < 0 || i >= n || i + n >= nextn) { int sc; if (finishing) { nextTable = null; table = nextTab; sizeCtl = (n << 1) - (n >>> 1); return; } if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) { if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT) return; finishing = advance = true; i = n; // recheck before commit } } else if ((f = tabAt(tab, i)) == null) advance = casTabAt(tab, i, null, fwd); else if ((fh = f.hash) == MOVED) advance = true; // already processed else { synchronized (f) { if (tabAt(tab, i) == f) { 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); 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; } } } } } }
七、LinkedHashMap
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
LinkedHashMap繼承於HashMap,內部維護了一個雙向鏈表,用來維護插入順序或者LRU順序。
/** * The head (eldest) of the doubly linked list. */ transient LinkedHashMap.Entry<K,V> head; /** * The tail (youngest) of the doubly linked list. */ transient LinkedHashMap.Entry<K,V> tail;
1.向鏈表中追加新節點
當向LinkedHashMap中插入新節點時,linkNodeLast()實現了向雙向鏈表中增加新節點,它在newNode()中被調用。
// link at the end of list private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { LinkedHashMap.Entry<K,V> last = tail; tail = p; if (last == null) head = p; else { p.before = last; last.after = p; } }
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) { LinkedHashMap.Entry<K,V> p = new LinkedHashMap.Entry<K,V>(hash, key, value, e); linkNodeLast(p); return p; }
2.從鏈表中刪除節點
當從LinkedListMap移除節點時,afterNodeRemoval()實現從雙向鏈表中移除節點。
void afterNodeRemoval(Node<K,V> e) { // unlink LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; p.before = p.after = null; if (b == null) head = a; else b.after = a; if (a == null) tail = b; else a.before = b; }
3.accessOrder
accessOrder決定了LinkedHashMap雙向鏈表所維護的順序,默認為false,此時維護的是插入順序,當為true時,維護的是訪問順序(當訪問了LinkedHashMap的某個節點,則節點被刷新為youngest)。
4.訪問順序
afterNodeAccess()
當一個節點被訪問時,如果 accessOrder 為 true,則會將該節點移到鏈表尾部。也就是說指定為 LRU 順序之后,在每次訪問一個節點時,會將這個節點移到鏈表尾部,保證鏈表尾部是最近訪問的節點,那么鏈表首部就是最近最久未使用的節點。
void afterNodeAccess(Node<K,V> e) { // move node to last LinkedHashMap.Entry<K,V> last; if (accessOrder && (last = tail) != e) { LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; p.after = null; if (b == null) head = a; else b.after = a; if (a != null) a.before = b; else last = b; if (last == null) head = p; else { p.before = last; last.after = p; } tail = p; ++modCount; } }
afterNodeInsertion()
在 put 等操作之后執行,當 removeEldestEntry() 方法返回 true 時會移除最晚的節點,也就是鏈表首部節點 first。
evict 只有在構建 Map 的時候才為 false,在這里為 true。
void afterNodeInsertion(boolean evict) { // possibly remove eldest LinkedHashMap.Entry<K,V> first; if (evict && (first = head) != null && removeEldestEntry(first)) { K key = first.key; removeNode(hash(key), key, null, false, true); } }
removeEldestEntry() 默認為 false,如果需要讓它為 true,需要繼承 LinkedHashMap 並且覆蓋這個方法的實現,這在實現 LRU 的緩存中特別有用,通過移除最近最久未使用的節點,從而保證緩存空間足夠,並且緩存的數據都是熱點數據。
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; }
4.LRU 緩存
以下是使用 LinkedHashMap 實現的一個 LRU 緩存:
- 設定最大緩存空間 MAX_ENTRIES 為 3;
- 使用 LinkedHashMap 的構造函數將 accessOrder 設置為 true,開啟 LRU 順序;
- 覆蓋 removeEldestEntry() 方法實現,在節點多於 MAX_ENTRIES 就會將最近最久未使用的數據移除。
class LRUCache<K, V> extends LinkedHashMap<K, V> { private static final int MAX_ENTRIES = 3; protected boolean removeEldestEntry(Map.Entry eldest) { return size() > MAX_ENTRIES; } LRUCache() { super(MAX_ENTRIES, 0.75f, true); } } public static void main(String[] args) { LRUCache<Integer, String> cache = new LRUCache<>(); cache.put(1, "a"); cache.put(2, "b"); cache.put(3, "c"); cache.get(1); cache.put(4, "d"); System.out.println(cache.keySet()); }
[3, 1, 4]
八、Hashtable
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable
Hashtable是線程安全的,使用synchronized進行控制。只能使用非空的key或者value。
為了成功的存儲和索引hashtable,作為key的對象必須實現hashcode()和equals()。
底層使用Entry數組來存儲
/** * The hash table data. */ private transient Entry<?,?>[] table;
兩個重要參數:threshold和loadFactor
1.put操作
從代碼中看出hashtable的key的hash值使用的是key.hashCode(),如果key只存在則替換,若不存在則新增。
/** * Maps the specified <code>key</code> to the specified * <code>value</code> in this hashtable. Neither the key nor the * value can be <code>null</code>. <p> * * The value can be retrieved by calling the <code>get</code> method * with a key that is equal to the original key. * * @param key the hashtable key * @param value the value * @return the previous value of the specified key in this hashtable, * or <code>null</code> if it did not have one * @exception NullPointerException if the key or value is * <code>null</code> * @see Object#equals(Object) * @see #get(Object) */ public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } // Makes sure the key is not already in the hashtable. Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> entry = (Entry<K,V>)tab[index]; for(; entry != null ; entry = entry.next) { if ((entry.hash == hash) && entry.key.equals(key)) { V old = entry.value; entry.value = value; return old; } } addEntry(hash, key, value, index); return null; }
private void addEntry(int hash, K key, V value, int index) { modCount++; Entry<?,?> tab[] = table; if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); tab = table; hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++; }
容量擴大為原來的兩倍+1
protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; // overflow-conscious code int newCapacity = (oldCapacity << 1) + 1; if (newCapacity - MAX_ARRAY_SIZE > 0) { if (oldCapacity == MAX_ARRAY_SIZE) // Keep running with MAX_ARRAY_SIZE buckets return; newCapacity = MAX_ARRAY_SIZE; } Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; modCount++; threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); table = newMap; for (int i = oldCapacity ; i-- > 0 ;) { for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) { Entry<K,V> e = old; old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = (Entry<K,V>)newMap[index]; newMap[index] = e; } } }
2.remove操作
public synchronized V remove(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>)tab[index]; for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { modCount++; if (prev != null) { prev.next = e.next; } else { tab[index] = e.next; } count--; V oldValue = e.value; e.value = null; return oldValue; } } return null; }
3.get操作
public synchronized V get(Object key) { Entry<?,?> tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return (V)e.value; } } return null; }
九、TreeMap
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable
基於紅黑樹的NavigableMap的實現,map根據key的Comparale自然順序排序,或者根據創建時候提供的Comparator,這取決於用什么構造函數創建。
注意TreeMap使用一個樹映射來維持排序,類似於其他的排序映射,不管是否有一個顯式comparator被提供,只要是要正確的實現Map接口,必須與equals()返回結果一致。
1.put操作
與root值進行比較,大於root則將值插在右子樹,小於則插在左子樹
public V put(K key, V value) { Entry<K,V> t = root; if (t == null) { compare(key, key); // type (and possibly null) check root = new Entry<>(key, value, null); size = 1; modCount++; return null; } int cmp; Entry<K,V> parent; // split comparator and comparable paths Comparator<? super K> cpr = comparator; if (cpr != null) { do { parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } else { if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable<? super K> k = (Comparable<? super K>) key; do { parent = t; cmp = k.compareTo(t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } Entry<K,V> e = new Entry<>(key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; fixAfterInsertion(e); size++; modCount++; return null; }
2.get操作
和put是同樣的原理
final Entry<K,V> getEntry(Object key) { // Offload comparator-based version for sake of performance if (comparator != null) return getEntryUsingComparator(key); if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable<? super K> k = (Comparable<? super K>) key; Entry<K,V> p = root; while (p != null) { int cmp = k.compareTo(p.key); if (cmp < 0) p = p.left; else if (cmp > 0) p = p.right; else return p; } return null; }
3.getFloorEntry
返回小於key的最大節點
final Entry<K,V> getFloorEntry(K key) { Entry<K,V> p = root; while (p != null) { int cmp = compare(key, p.key); if (cmp > 0) { if (p.right != null) p = p.right; else return p; } else if (cmp < 0) { if (p.left != null) { p = p.left; } else { Entry<K,V> parent = p.parent; Entry<K,V> ch = p; while (parent != null && ch == parent.left) { ch = parent; parent = parent.parent; } return parent; } } else return p; } return null; }
Collections.synchronizedList()
實現原理:不是線程安全的集合,通過Collections.synchronizedList()函數封裝一層,內部通過synchronized對一個Object對象進行同步,來實現線程安全。
參考文獻:
1.GitHub-