ArrayList有自動擴容機制,數組沒有
ArrayList是如何擴容的呢?
jdk1.2
到jdk1.6
中的ArrayList
的源碼中,在構造方法上的確是創建了一個初始容量為10的容器。
在jdk_1.7
中的源碼是這樣寫的
調用構造方法時,如下
說明從jdk_1.7
開始,當你進行new ArrayList();創建的是一個空數組
初始容量就不是10了,而是一個空數組
在jdk_1.7
中的ArrayList
定義了一個常量這個值就是10
這個常量在進行擴容的時候,會和當前容器的最小容量進行比較,取最大的作為新容器的容量
例如當你第一次調用add進行添加元素的時候,會觸發擴容
進入ensureCapacityInternal(size + 1);
,一開始是一個空容器所以size=0
傳入的minCapacity=1
DEFAULT_CAPACITY=10,傳入的最小容量minCapacity=1,二者取大,故minCapacity變成10。
會發現minCapacity
被重新賦值為10 (DEFAULT_CAPACITY=10
)
傳入ensureExplicitCapacity(minCapacity);
這時minCapacity=10
下面是方法體:
10-0>0
modCount++
是在父類AbstarctList
中,
后又調用了擴容grow(10)
第一次擴容size,即elementData.length=0,故newCapacity=0+0/2=0,故進入第4行的if中,使得newCapacity=minCapacity=10。
java中有三種移位運算符
<< : 左移運算符,num << 1,相當於num乘以2
>> : 右移運算符,num >> 1,相當於num除以2
>>> : 無符號右移,忽略符號位,空位都以0補齊
這是jdk1.7中的擴容機制代碼
其中int newCapacity = oldCapacity + (oldCapacity >> 1);
就是面試過程中經常提問到的,1.5倍
原來數組的長度
當第二次擴容時,size,即elementData.length=10,則minCapacity=size+1=11
minCapacity=minCapacity=11。
進入grow(11)
這時oldCapacity=10,則newCapacity=10+10/2=15,即擴容1.5倍
下一次oldCapacity=15,則newCapacity=15+15/2=22.5,即擴容1.5倍
引自 https://blog.csdn.net/qq_41813208/article/details/107777539
Arrays.copyOf函數
copyOf函數:把原數組中的數據復制到新數組中,屬於深層拷貝。
所以也是創建一個新數組,逐個拷貝過去。故擴容是費時的。
add(E e)如果不需要擴容,只是把數據加到隊尾,不需要挪位置,那么是很快的。
add(int index, E element)若要插到隊中的話,就要用到arraycopy函數,挪空塞數據,那會很慢。
每次用帶索引的add和remove函數時都需要調用arraycopy函數,是費時的。
了解一下copyOf函數
在 Java 編程中經常會遇到數組拷貝操作,一般會有如下四種方式對數組進行拷貝。
* for遍歷,遍歷源數組並將每個元素賦給目標數組。
* clone方法,原數組調用clone方法克隆新對象賦給目標數組,更深入的克隆可以看之前的文章《從JDK角度看對象克隆》。
* System.arraycopy,JVM 提供的數組拷貝實現。
* Arrays.copyof,實際也是調用System.arraycopy。
關於@HotSpotIntrinsicCandidate
這個注解是 HotSpot VM 標准的注解,被它標記的方法表明它為 HotSpot VM 的固有方法, HotSpot VM 會對其做一些增強處理以提高它的執行性能,比如可能手工編寫匯編或手工編寫編譯器中間語言來替換該方法的實現。雖然這里被聲明為 native 方法,但是它跟 JDK 中其他的本地方法實現地方不同,固有方法會在 JVM 內部實現,而其他的會在 JDK 庫中實現。在調用方面,由於直接調用 JVM 內部實現,不走常規 JNI lookup,所以也省了開銷。
System.arraycopy為 JVM 內部固有方法,它通過手工編寫匯編或其他優化方法來進行 Java 數組拷貝,這種方式比起直接在 Java 上進行 for 循環或 clone 是更加高效的。數組越大體現地越明顯。
引自 https://blog.csdn.net/weixin_34308389/article/details/92348857
System.arraycopy是一個native函數,需要看native層的代碼
找到對應的openjdk6-src/hotspot/src/share/vm/prims/jvm.cpp,這里有JVM_ArrayCopy的入口
引自博客園某BLOG,兩方證明是hotspot虛擬機JVM底層的東西。挖個坑以后看。
為什么用右移運算符呢?運算規則是怎樣的呢?
移位運算是二進制的運算,計算機運算速度快。
右移
運算方式:數值的補碼向右移X位,符號位不變(左邊補上符號位)
10換算成二進制等於1010
2^3 2^2 2^1 2^0
8 4 2 1 8+2=10
1 0 1 0
正數的補碼與原碼相同,即原碼=補碼=1010
32位計算機x86,即 0000 0000 0000 0000 0000 0000 0000 1010
右移一位指整體往右挪,不是小數點右移,即變成0000 0000 0000 0000 0000 0000 0000 0101
64位計算機x64同理。
101轉換為十進制為5
2^2 2^1 2^0
4 2 1 4+1=5
1 0 1
引自 https://blog.csdn.net/onezg/article/details/53103559
不需要擴容的時候
第二次add
此時還沒完全add完,故當前的size還是1
minCapacity是假使add完成,add一個后的size,即當前minCapacity=2
EMPTY_ELEMENTDATA={}
這時ArrayList中已經有了一個數據,故elementData不等於{},故直接進入ensureExplictCapacity(2)
此時minCapacity=2,elementData.length是當前最大可存放容量,即上一次擴容的10,故elementData.length=10
故2-10不大於0,則不會進入grow函數,也就不會執行擴容了。
sqList