以前在研究ArrayList源碼的時候看到過一篇文章Java 8 容器源碼-ArrayList裏面說當ArrayList在進行插入的時候,若是容量不夠那麼就會進行自動擴容,擴容大小是現有容量的1.5倍,具體代碼能夠參考下面。html
此處的默認容量是指當構建空的ArrayList構造函數時給分配的默認數組容量大小,爲10。java
/**
* 擴容,保證ArrayList至少能存儲minCapacity個元素
* 第一次擴容,邏輯爲newCapacity = oldCapacity + (oldCapacity >> 1);即在原有的容量基礎上增長一半。第一次擴容後,若是容量仍是小於minCapacity,就將容量擴充爲minCapacity。
*
* @param minCapacity 想要的最小容量
*/
private void grow(int minCapacity) {
// 獲取當前數組的容量
int oldCapacity = elementData.length;
// 擴容。新的容量=當前容量+當前容量/2.即將當前容量增長一半。
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);
}
/**
複製代碼
因此在網上就會有許多的人說當知道了要用ArrayList存儲多少數據的前提下,咱們要指定ArrayList的容量大小,這樣會減小ArrayList自動擴容的次數而增長效率。程序員
此時相信你們內心就會和我有同樣的疑問,在不一樣數據量的面前使用默認容量和指定容量大小效率到底會有多少的差異。相信有些人(好比我)一開始上來就會寫以下的代碼進行驗證。編程
public class TestListAdd {
public static void main(String[] args){
System.out.println("Test 10000000 List add start");
System.out.println("Default Capacity: "+ listAdd(10000000));//默認容量
System.out.println("10000000 Capacity: "+ listAdd(10000000,10000000));//指定容量
}
public static Long listAdd(int num){
Long starTime = System.currentTimeMillis();
List<Object> list = new ArrayList<>();
for (Integer i = 0; i < num; i++) {
list.add(i);
}
Long endTime = System.currentTimeMillis();
return endTime - starTime;
}
public static Long listAdd(int capatity,int num){
Long starTime = System.currentTimeMillis();
List<Object> list = new ArrayList<>(capatity);
for (Integer i = 0; i < 10000000; i++) {
list.add(i);
}
Long endTime = System.currentTimeMillis();
return endTime - starTime;
}
}
複製代碼
結果固然也是符合預期的,下面的輸出比上面的要少量多數組
Test 10000000 List add start
Default Capacity: 2416
10000000 Capacity: 397
複製代碼
若是試驗到此爲止我也就不會寫這篇博客了,由於沒什麼可寫的了,可是當我將上面默認容量大小和指定容量大小兩行代碼換個位置,那麼結果就會與咱們所想的不相同。bash
Test 10000000 List add start
10000000 Capacity: 2130
Default Capacity: 933
複製代碼
通過查詢各類資料,中間走了許多的坑,例如一開始我想着是第一次的調用會將int從0-10000000的數第一次建立到常量池裏面,而第二次就不用建立直接去常量池裏面去取第一次建立好的int類型就行了,因此每次的第一次調用會比第二次調用速度要慢許多。固然隨後通過學習查資料,否認了我第一次的蠢想法。兩個不一樣的方法內的變量根本不會有交互。隨後看到了一篇文章和個人問題差很少java循環長度的相同、循環體代碼相同的兩次for循環的執行時間相差了100倍?裏面的回答提到了一句話。併發
在HotSpot VM上跑microbenchmark切記不要在main()裏跑循環計時就完事。這是典型錯誤。重要的事情重複三遍:請用JMH,請用JMH,請用JMH。除非很是瞭解HotSpot的實現細節,在main裏這樣跑循環計時獲得的結果其實對通常程序員來講根本沒有任何意義,由於沒法解釋。框架
固然裏面的其餘術語我是看不懂的,只是簡單的明白了兩點函數
JMH 是 Java Microbenchmark Harness(微基準測試)框架的縮寫(2013年首次發佈)。與其餘衆多測試框架相比,其特點優點在於它是由 Oracle 實現 JIT 的相同人員開發的。性能
我會在後面貼出一些學習JMH的經典文章。下面就開始咱們的測試吧。
使用JMH進行測試十分的簡單,只須要加入一些註解便可。他就能屏蔽一些JVM對於測試的影響。讓咱們只關注於測試結果。
@Benchmark
public static void listAdd(Blackhole blackhole){
List<Object> list = new ArrayList<>(10000);
for (Integer i = 0; i < 10000; i++) {
list.add(i);
}
blackhole.consume(list);
}
複製代碼
個人JMH的配置以下
具體的測試代碼我就不展現了,其中無非就是循環次數和初始化的容量。我就直接將數據展現出來吧。
循環次數 | 初始化容量 | 平均時間(微秒) |
---|---|---|
10 | 10 | 55.38 |
100 | 79.06 | |
1000 | 361.85 | |
10000 | 2355.82 |
循環次數 | 初始化容量 | 平均時間(微秒) |
---|---|---|
100 | 10 | 524.50 |
100 | 506.01 | |
1000 | 851.23 | |
10000 | 2804.22 |
循環次數 | 初始化容量 | 平均時間(微秒) |
---|---|---|
1000 | 10 | 7233.56 |
100 | 6923.32 | |
1000 | 5284.91 | |
10000 | 7723.32 |
循環次數 | 初始化容量 | 平均時間(微秒) |
---|---|---|
10000 | 10 | 81188.92 |
100 | 64393.20 | |
1000 | 59316.76 | |
10000 | 51382.20 |
下面是根據上面數據畫的折線圖,可以更加直觀的感覺到變化。
大概由上面的試驗,咱們可以得出如下結論
尤爲這種效率的提高在數據量大的時候更爲明顯,由於數據量大而致使初始化容量不夠,擴容次數不斷的增長。致使效率下降。