文章目錄
前言:
如果作爲java開發工程師,大家都使用過List的主要實現類ArrayList,那麼你真的懂他嗎?
-
1.ArrayList是單列的、存儲有序的、可重複的、線程不安全的,但是你知道爲什麼嗎?
-
2.是不是感覺ArrayList與數組非常相似? 那麼爲什麼ArrayList不會下標越界呢?那ArrayList底層做了什麼呢?
-
3.如題:從java7到java8進行了哪些迭代?爲什麼要這樣做?
- java8的ArrayList是什麼樣子的?
- java7的ArrayList是什麼樣子的?
-
4.如何高效的使用ArrayList?
環境介紹
因爲本身詳細介紹與對比了java7和java8的ArrayList的源碼,在此聲明本文的環境:
- JDK7(以jdk-7u7-windows-x64爲例):後續文章簡稱爲java8
- JDK8(以jdk-8u131-windows-x64爲例):後續文章中簡稱java7
問題一:ArrayList是單列的、存儲有序的、可重複的、線程不安全的,但是你知道爲什麼嗎?
1.爲什麼是單列的?
下面一段代碼是小編從java中ArrayList的源碼中截取的(這裏java7和java8不存在差異),elementData這個成員變量的作用是對ArrayList的添加的元素進行存儲,可以看出這是一個Object類型的數組,因爲數組是線程存儲的,所以ArrayList也是線性存儲的。
/**
* 存儲ArrayList元素的數組緩衝區。ArrayList的容量是這個數組緩衝區的長度。任何帶有
* elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList將在添加第一
* 個元素時擴展爲DEFAULT_CAPACITY。
*/
transient Object[] elementData; // non-private to simplify nested class access
2.爲什麼有序的?
從上面可以看出,ArrayList底層是Object類型的數組進行存儲,數組是有序的,所以其存儲也是有序的,那麼接下來,咱們進行驗證一下:
@Test
public void test()
{
ArrayList list=new ArrayList();
list.add(1);
list.add(2);
list.add(3);
System.out.println("list的輸出結果:"+list);
}
根據結論可以看出,ArrayList的確是順序存儲的。
3.爲什麼是可重複的?
閒話少說,來個例子看看:
@Test
public void test()
{
ArrayList list=new ArrayList();
list.add(1);
list.add(2);
list.add(3);
list.add(3);
System.out.println("list的輸出結果:"+list);
}
正如輸出結果:兩個完全相同的,所以ArrayList是可以存儲可重複的數據的,接下來就看看源碼:
源碼分析:以java8爲例
- 1.首先數組是可以存儲重複的數據的,
- 2.如果ArrayList在添加的時候,沒有進行過濾,那麼他就是可以存儲重複的數據。
public boolean add(E e) {
//size爲添加前數據元素的個數,加一以後,代表加上新添加的元素的長度
//該方法主要作用是:判斷是否開闢了空間,判斷是否擴容,此處省略,問題2會詳細講述
ensureCapacityInternal(size + 1);
//將elementData的下一份元素賦值爲e
elementData[size++] = e;
//返回true
return true;
}
//minCapacity長度:爲ArrayList添加新元素後的長度。
private void ensureCapacityInternal(int minCapacity) {
//這裏暫時略過,後續在java7和java8的時候會講
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//帶上minCapacity形參,去下面的方法
ensureExplicitCapacity(minCapacity);
}
//minCapacity長度:爲ArrayList添加新元素後的長度。
private void ensureExplicitCapacity(int minCapacity) {
//這是一個快速失敗機制,此處略過
modCount++;
//判斷數組長度,如果不夠就擴容,此處省略後續會講
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
4.爲什麼是線程不安全的?
有理有據,看源碼
正如下面的圖是筆者隨意截取的一張圖,發現方法中並沒有synchronized關鍵字,所以是線程不安全的。
問題二:是不是感覺ArrayList與數組非常相似?那麼爲什麼ArrayList不會下標越界呢?那ArrayList底層做了什麼呢?
1.是不是感覺ArrayList與數組非常相似?
這個問題:相信大家都瞭解了,因爲在前面已經看到了ArrayList底層就是採用elementData這個成員變量進行存儲的。
2.那麼爲什麼ArrayList不會下標越界呢?那ArrayList底層做了什麼呢?
因爲在ArrayList底層封裝了自動擴容的規則.
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
上面的代碼塊,就是對底層elementData進行擴容,大概邏輯如下:
- 將數組的長度擴容oldCapacity + (oldCapacity >> 1);擴容1.5倍,因爲是有符號的右移,縮小一倍。>>是位運算符,效率賊高。
- 如果擴容1.5倍以後,還是不夠用,那麼就把形參的值 (因爲在add()方法中已經把要添加的元素的長度加入了進入,所以該值是擴容後應該的長度) 直接賦值給數組長度
- 判斷是否大於MAX_ARRAY_SIZE (這是int的最大值-8),如果比這個大,就把int的最大值給他,如果不夠,就拋異常。
繼續看,加油,你看得累,我寫也很累,後面有更精彩的乾貨。、
瞭解了擴容以後,記住擴容的方法名字**grow(int minCapacity)**繼續往下看添加的流程
public boolean add(E e) {
//size爲添加前數據元素的個數,加一以後,代表加上新添加的元素的長度
//該方法主要作用是:判斷是否開闢了空間,判斷是否擴容,此處省略,問題2會詳細講述
ensureCapacityInternal(size + 1);
//將elementData的下一份元素賦值爲e
elementData[size++] = e;
//返回true
return true;
}
//minCapacity長度:爲ArrayList添加新元素後的長度。
private void ensureCapacityInternal(int minCapacity) {
//這裏暫時略過,後續在java7和java8的時候會講
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//帶上minCapacity形參,去下面的方法
ensureExplicitCapacity(minCapacity);
}
//minCapacity長度:爲ArrayList添加新元素後的長度。
private void ensureExplicitCapacity(int minCapacity) {
//這是一個快速失敗機制,此處略過
modCount++;
//判斷數組長度,如果不夠就擴容,此處省略後續會講
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
在添加的方法的最底部,發現每次添加添加操作的時候,都會調用grow(int minCapacity)方法,所以每次向ArrayList添加元素的時候,都會判斷,如果長度不夠,就調用擴容的這個方法進行擴容。
問題三:如題:從java7到java8進行了哪些迭代?爲什麼要這樣做?
針對於java7和java8的對比,主要是針對於構造器和add方法進行的提升。
可能你累了,但是此時我也類,繼續看下去,你就可以自己手寫一個簡單的ArrayList
1. java8的ArrayList是什麼樣子的?
構造器
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
經過查看源碼的無參構造器,創建ArrayList對象時,僅僅給成員變量elementData賦值了一個{},記住這裏:java8的構造器並沒有對成員變量elementData進行開闢默認長度是10的數組
add方法
接下來,詳細解析add方法,你準備好了嗎?
public boolean add(E e) {
//size爲添加前數據元素的個數,加一以後,代表加上新添加的元素的長度
//該方法主要作用是:判斷是否開闢了空間,判斷是否擴容,此處省略,問題2會詳細講述
ensureCapacityInternal(size + 1);
//將elementData的下一份元素賦值爲e
elementData[size++] = e;
//返回true
return true;
}
//minCapacity長度:爲ArrayList添加新元素後的長度。
private void ensureCapacityInternal(int minCapacity) {
//這裏暫時略過,後續在java7和java8的時候會講
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//帶上minCapacity形參,去下面的方法
ensureExplicitCapacity(minCapacity);
}
//minCapacity長度:爲ArrayList添加新元素後的長度。
private void ensureExplicitCapacity(int minCapacity) {
//這是一個快速失敗機制,此處略過
modCount++;
//判斷數組長度,如果不夠就擴容,此處省略後續會講
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
源碼大意:
- 因爲默認實例化的時候,並沒有創建對象,所以在ensureCapacityInternal(int minCapacity) 方法 中,進行了判斷,如果第一次的話,就創建對象。
2.java7的ArrayList是什麼樣子的?
構造器
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
public ArrayList() {
this(10);
}
經過查看源碼的無參構造器,發現無參的構造器調用了有參的構造器,傳入了一個10作爲參數,然後有參的構造器創建一個長度爲10的Object類型的數組,賦值給成員變elementData
add方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
源碼大意:
- 因爲默認實例化的時候,爲成員變量elementData創建了長度爲10的Object數組,所以在ensureCapacityInternal(int minCapacity) 方法 僅僅判斷是否需要擴容,沒有判斷其其否爲{}
3.Java7->8的變化(總結)
JDK7 | JDK8 | |
---|---|---|
構造器 | 實例化即開闢數組長度爲10的內存空間 | 實例化時不開闢內存空間 |
add()方法 | 直接添加 | 第一次調用add方法時,開闢數組長度爲10的內存空間 |
問題四:如何高效的使用ArrayList?
ArrayList主要實現類,大部分都使用它。但是如果明確知道長度,則建議使用有參構造器,這樣可以降低底層數組的擴容,提高效率。
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);
}
}
源碼大意 如果你給了一個長度,則會直接創建一個你給的長度的Object類型數組,賦值給成員變量elementData,這樣就減少了擴容的次數,從而提高效率。