Java集合擴容機制
-
-
-
- 1、ArrayList
- 2、Vector
- 3、Stack
- 4、HashMap
-
-
為什麼需要擴容?即目前集合能容納的資料量達到一個飽和狀态(飽和狀态和加載因子有關)之後,集合需要申請新的存儲空間,即擴容。
常見的需要擴容的集合一般是底層基于數組實作的,連結清單不涉及擴容問題,是以LinkedList等無擴容,常見的有ArrayList、Vector、Stack、HashMap。
加載因子:集合中元素填滿的程度,例如ArrayList加載因子為1,初始容量為10,則當目前元素>=10*1需要進行擴容;HashMap加載因子為0.75,初始容量為16,當目前元素>=16 * 0.75=12時進行擴容。
1、ArrayList
檢視ArrayList源碼可以看到,在ArrayList初始化時,内部表示為一個object類型的數組,預設初始容量為10,加載因子為1;
private static final int DEFAULT_CAPACITY = 10; //預設大小為10
private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
transient Object[] elementData; //ArrayList 在内部的表示即為一個Object類型的數組
初始化ArrayList時,會涉及到兩個構造器:
- 無參構造器:構造一個初始容量為 10 的空清單。
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
- 有參構造器:構造一個具有指定初始容量的空清單。
//當傳入的值大于0時,數組的大小即為傳入的值大小,為0時,則為EMPTY_ELEMENTDATA(即0),小于0則抛出異常。 //initialCapacity不是ArrayList的size(),initialCapacity表示初始容量,size則是實際存放對象的數量。 public ArrayList(int initialCapacity) if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else { if (initialCapacity != 0) { throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); } this.elementData = EMPTY_ELEMENTDATA; } }
- 此外在源碼中還有另一個構造器,該構造器一般用于構造一個包含指定 collection 的元素的清單,元素按照該 collection 的疊代器傳回它們的順序排列。
public ArrayList(Collection<? extends E> c) { this.elementData = c.toArray(); if ((this.size = this.elementData.length) != 0) { if (this.elementData.getClass() != Object[].class) { this.elementData = Arrays.copyOf(this.elementData, this.size, Object[].class); } } else { this.elementData = EMPTY_ELEMENTDATA; } }
擴容時,先進行目前容量長度和元素數量比較,需要擴容即調用grow()方法進行擴容。
public void ensureCapacity(int minCapacity) {
if (minCapacity > this.elementData.length && (this.elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA || minCapacity > 10)) { //判斷目前的長度大于了elementData長度,即需要進行擴容。
++this.modCount;
this.grow(minCapacity); //調用grow方法進行擴容,傳入參數為目前需要長度
}
}
private Object[] grow(int minCapacity) {
return this.elementData = Arrays.copyOf(this.elementData, this.newCapacity(minCapacity));//調用newCapacity(), 将目前數組中的内容傳給新的數組
}
private Object[] grow() {
return this.grow(this.size + 1);
}
//擴容核心方法
private int newCapacity(int minCapacity) {
int oldCapacity = this.elementData.length;
//對目前數組長度進行擴容:擴容後長度 = 目前長度 + 目前長度二進制進位1(即目前長的1/2);
//是以初始化預設長度為10時,後面的每一次擴容後容量為 10、15、22、33、49...
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);
}
}
2、Vector
Vector與ArrayList一樣,是存儲在數組結構中,加載因子為1,初始容量為10。
public Vector(int initialCapacity, int capacityIncrement) {
if (initialCapacity < 0) {
throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
} else {
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
//初始容量10
public Vector() {
this(10);
}
擴容時,有兩種不同的方式
//判斷進行擴容
public synchronized void ensureCapacity(int minCapacity) {
if (minCapacity > 0) {
++this.modCount;
if (minCapacity > this.elementData.length) {
this.grow(minCapacity);
}
}
}
private Object[] grow(int minCapacity) {
return this.elementData = Arrays.copyOf(this.elementData, this.newCapacity(minCapacity));//将目前的數組複制給擴容後數組
}
private Object[] grow() {
return this.grow(this.elementCount + 1);
}
擴容時會先對capacityIncrement參數進行判斷
private int newCapacity(int minCapacity) {
int oldCapacity = this.elementData.length;
//判斷capacityIncrement是否大于0:大于0則擴容為目前長度加capacityIncrement的大小,否則擴容為目前長度的2倍
int newCapacity = oldCapacity + (this.capacityIncrement > 0 ? this.capacityIncrement : oldCapacity);
if (newCapacity - minCapacity <= 0) {
if (minCapacity < 0) {
throw new OutOfMemoryError();
} else {
return minCapacity;
}
} else {
return newCapacity - 2147483639 <= 0 ? newCapacity : hugeCapacity(minCapacity);
}
}
//例如 Vector<String> v = new Vector<String>(); 擴容大小則為10、20、40、80....
//例如 Vector<String> v = new Vector<String>(8,4); 擴容大小則為8、12、16、20、24....
3、Stack
stack繼承自Vector,是以Stack的擴容機制等同于Vector的預設情況,也就是2倍速度擴容,加載因子也為1。
public class Stack<E> extends Vector<E> {
private static final long serialVersionUID = 1224463164541339165L;
public Stack() {
}
//......
}
4、HashMap
HashMap的底層實作根據jdk版本的不同有兩種不同的實作方式,但是共同點是都有數組+連結清單的形式,HashMap預設初始容量為16,加載因子為0.75.
static final int DEFAULT_INITIAL_CAPACITY = 16; //初始容量
static final float DEFAULT_LOAD_FACTOR = 0.75F; //加載因子
哈希表的擴容方法主要在put新值的時候進行判斷并執行,是以在他的put方法中,由于本地JDK版本為1.8,在1.8中擴容中,初始化後首次插入,先擴容再插入,後面容量達到容量*加載因子時,先插入,再擴容;但是在1.7之前,擴容方式是先判斷進行擴容,再插入資料。
public V put(K key, V value) {
return this.putVal(hash(key), key, value, false, true);//運用到putVal方法
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
HashMap.Node[] tab;
int n;
if ((tab = this.table) == null || (n = tab.length) == 0) {
n = (tab = this.resize()).length;
}
Object p;
int i;
//插入資料
if ((p = tab[i = n - 1 & hash]) == null) {
tab[i] = this.newNode(hash, key, value, (HashMap.Node)null);
} else {
//.....
}
//判斷擴容
++this.modCount;
if (++this.size > this.threshold) {
//size空間不足時,進行resize擴容
this.resize();
}
this.afterNodeInsertion(evict);
return null;
}
擴容核心在于resize()方法
//resize()是進行擴容的核心函數
final HashMap.Node<K, V>[] resize() {
HashMap.Node<K, V>[] oldTab = this.table;
int oldCap = oldTab == null ? 0 : oldTab.length;
int oldThr = this.threshold;
int newThr = 0;
int newCap;
if (oldCap > 0) {
if (oldCap >= 1073741824) {
this.threshold = 2147483647;
return oldTab;
}
//擴容大小為原來的兩倍,二進制左移一位,原容量的2倍
if ((newCap = oldCap << 1) < 1073741824 && oldCap >= 16) {
newThr = oldThr << 1;
}
} else if (oldThr > 0) {
newCap = oldThr;
} else {
newCap = 16;
newThr = 12;
}
//擴容時機
if (newThr == 0) {
float ft = (float)newCap * this.loadFactor;
newThr = newCap < 1073741824 && ft < 1.07374182E9F ? (int)ft : 2147483647;
}
this.threshold = newThr;
HashMap.Node<K, V>[] newTab = new HashMap.Node[newCap];
this.table = newTab;
if (oldTab != null) {
//這部分代碼目前還沒搞懂,個人覺得是擴容後兩個容器之間資料的拷貝,1.8這部分涉及到紅黑樹,目前紅黑樹還是懵逼狀态
}
return newTab;
}