ArrayList
1.動態數組
2.線程不安全
3.儲存空間連續
4.查詢快,添加刪除慢
/** + Shared empty array instance used for default sized empty instances. We + distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when + first element is added. */private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};/** + Constructs an empty list with an initial capacity of ten. */public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}
這個構造方法很簡單,初始化了一個空的elementData,並沒有賦予數組長度
/** + Default initial capacity. */private static final int DEFAULT_CAPACITY = 10;/** + 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/** + The size of the ArrayList (the number of elements it contains). * + @serial */private int size;/** + Appends the specified element to the end of this list. * + @param e element to be appended to this list + @return <tt>true</tt> (as specified by {@link Collection#add}) */public boolean add(E e) { // 首先進行擴充 ensureCapacityInternal(size + 1); // Increments modCount!! // 將元素追加到最後 elementData[size++] = e; return true;}// 擴充private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}// 計算數組大小 第一次調用此處的elementData={},所以傳回值為DEFAULT_CAPACITY=10,也就是預設的數組長度是10private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity;}private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) // 當加上當前元素後的集合長度(size)大於現在數組長度(elementData.length)在進行擴充 grow(minCapacity);}// 真正的擴充操作private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; // 此處oldCapacity=0 int newCapacity = oldCapacity + (oldCapacity >> 1); // 此處newCapacity=0 if (newCapacity - minCapacity < 0) // 此處minCapacity=10 newCapacity = minCapacity; // 此處newCapacity=10 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); //數組拷貝}
真正的數組長度是在第一次添加的時候進行初始化的,預設為10
最主要的消耗是在擴容(數組拷貝)
當集合長度大於數組長度的時候進行擴充,擴充的標準是1.5倍[oldCapacity + (oldCapacity >> 1)]
public E get(int index) { rangeCheck(index);// 校正 return elementData(index);}E elementData(int index) { return (E) elementData[index];}
Vector
1.動態數組,類似於ArrayList
2.安全執行緒
3.消耗大
public Vector() { this(10); // initialCapacity初始容量}
/** * Appends the specified element to the end of this Vector. * * @param e element to be appended to this Vector * @return {@code true} (as specified by {@link Collection#add}) * @since 1.2 */public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true;}
被synchronized修飾,安全執行緒,但是效率較低
public void add(int index, E element) { insertElementAt(element, index);}public synchronized void insertElementAt(E obj, int index) { modCount++; if (index > elementCount) { throw new ArrayIndexOutOfBoundsException(index - " > " + elementCount); } ensureCapacityHelper(elementCount + 1); System.arraycopy(elementData, index, elementData, index + 1, elementCount - index); elementData[index] = obj; elementCount++;}
LinkedList
1.雙向鏈表:jdk1.7/8以後
2.插入快,查詢慢
/** * Constructs an empty list. */public LinkedList() {}
空的構造方法
public boolean add(E e) { linkLast(e); return true;}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++; }
預設添加到鏈表結尾,prev指向原結尾元素,原結尾元素next指標指向新添加元素,並記錄結尾元素為新添加元素。只有指標移動,並沒有數組拷貝,所以插入效率較快
public E get(int index) { checkElementIndex(index); return node(index).item;}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; }}
查詢採用二分法尋找,先將數組拆分成一半,然後進行遍曆。所以查詢較慢。當index值接近二分之一size時,更慢。
HashMap
1.儲存結構:數組+鏈表/數組+紅/黑樹狀結構
2.線程不安全
static final float DEFAULT_LOAD_FACTOR = 0.75f;public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted}
沒有初始化數組,負載因子為0.75
public V put(K key, V value) { return putVal(hash(key), key, value, false, true);}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) //[1] n = (tab = resize()).length; // [2] if ((p = tab[i = (n - 1) & hash]) == null) [// [3] tab[i] = newNode(hash, key, value, null); // [4] else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))// [5] e = p; // [6] else if (p instanceof TreeNode) // [7] e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); //[8] else { //[9] for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { // [10] p.next = newNode(hash, key, value, null); // [11] if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st [12] treeifyBin(tab, hash); // [13] break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) // [14] break; p = e; } } if (e != null) { // existing mapping for key [15] V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) // [16] e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) // [17] resize(); afterNodeInsertion(evict); return null;}
[1]判斷table是否為null,長度是否為0,table用於擴充時記錄擴充後的新數組
[2]進行數組擴充,將新數組賦值給tab,n為新數組的長度
[3]判斷新key需要儲存的數組節點是否有值
[4]如果沒有值,直接儲存於該節點,如果當前數組節點有值
[5]判斷新key與當前儲存的key是否相同
[6]記錄當前儲存元素到e
[7]判斷當前節點是否為數節點
[8]進行樹節點操作
[9]當前節點儲存的key與新key不同,並且不是樹形結構(鏈表結構)
[10]迴圈遍曆,找到鏈表的尾節點
[11]將新元素追加到鏈表的末尾,即原尾節點的next指標指向新元素
[12]當鏈表的長度達到8時,轉為樹形結構[13]
[14]迴圈過程中如果發現儲存的key與新key相同,則中斷迴圈
[15]如果存在匹配的key,則替換value
[16]返回舊值
[17]能走到這裡說明是新增元素,並不是更新元素,判斷當前集合長度是否大於threshold(threshold=當前集合長度*0.75),如果大於需要進行擴充
// 擴容final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; // [1] int oldCap = (oldTab == null) ? 0 : oldTab.length; //[2] int oldThr = threshold; // [3] int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { // [4] threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) // [5] newThr = oldThr << 1; } else if (oldThr > 0) // [6] newCap = oldThr; else { // [7] newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { // [8] float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; // [9] @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; // [10] if (oldTab != null) { for (int j = 0; j < oldCap; ++j) {// [11] Node<K,V> e; if ((e = oldTab[j]) != null) {// [12] oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) // [13] ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order [14] Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } 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;}
[1]oldTab用來記錄上次擴充的table
[2]oldCap用來記錄上次擴充table的長度
[3]oldThr用來記錄上次擴充的閾值
[4]如果oldCap大於等於最大值(2^30),threshold等於2^30-1,直接返回,不在進行擴充
[5]newCap等於(oldCap*2),如果newCap小於最大值(2^30)並且oldCap大於初始值(2^4),則newThr=oldThr*2
[6]如果oldThr大於0,則newCap等於oldThr,上次擴充的閾值
[7]如果oldCap和oldThr都不大於0,則newCap等於2^4,newThr等於2^4*0.75(首次擴充)
[8]當oldCap小於2^4的時,newThr等於0,newThr=2*oldCap*0.75
[9]threshold等於newThr,記錄下次需要擴充的閾值
[10]建立新的Node數字,長度為newCap
[11]如果oldTab不為空白,則遍曆這個數組
[12]將原數組的元素散列到新數組中
[13]以紅/黑樹狀結構的結構重新散列元素
[14]以鏈表的結構重新散列元素
- get方法,先根據key計算出對應的數組指標位置,然後遍曆鏈表或者紅/黑樹狀結構擷取相同key的元素
Iterator<Map.Entry<String, Integer>> entryIterator = map.entrySet().iterator(); while (entryIterator.hasNext()) { Map.Entry<String, Integer> next = entryIterator.next(); System.out.println("key=" + next.getKey() + " value=" + next.getValue()); }
Iterator<String> iterator = map.keySet().iterator(); while (iterator.hasNext()){ String key = iterator.next(); System.out.println("key=" + key + " value=" + map.get(key)); }
map.forEach((key,value)->{ System.out.println("key=" + key + " value=" + value);});
hashmap只能在單線程中使用,並盡量減少擴容,迴圈鏈表的時間複雜度是O(n),O(logn)
多線程情境下推薦使用ConcurrentHashMap
ConcurrentHashMap
Object put(Object key, int hash, Object value, boolean onlyIfAbsent) { lock(); try { int c = count; if (c++ > threshold) // ensure capacity rehash(); HashEntry[] tab = table; int index = hash & (tab.length - 1); HashEntry first = tab[index]; HashEntry e = first; while (e != null && (e.hash != hash || !key.equals(e.key))) e = e.next; Object oldValue; if (e != null) { oldValue = e.value; if (!onlyIfAbsent) e.value = value; } else { oldValue = null; ++modCount; tab[index] = new HashEntry(key, hash, first, value); count = c; // write-volatile } return oldValue; } finally { unlock(); }}
ConcurrentHashMap之所以是安全執行緒的是因為在添加元素的時候先上了一個鎖,操作完成在解鎖。
HashSet
1.hashmap儲存資料
2.不允許儲存重複元素的集合
public HashSet() { map = new HashMap<>();}
private static final Object PRESENT = new Object();public boolean add(E e) { return map.put(e, PRESENT)==null;}
此方法將添加的元素e作為hashmap的key,value都是相同的PRESENT,因為hashmap的key是不允許重複的,所以相同的元素添加進來,後添加的會覆蓋先添加的,這就是不允許重複的原因