Java ArrayList
之前曾經參考 數據結構與算法這本書寫過ArrayList
的demo,本來以為實現起來都差不多,今天抽空看了下jdk
中的ArrayList
的實現,差距還是很大啊
首先看一下ArrayList
的類圖
ArrayList
實現了Serializable Cloneable RandomAccess List
這幾個接口,可序列化,可克隆,可以隨機訪問
構造方法:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
之前手寫ArrayList
的時候,都會用一個默認容量來 new 一個數組,在jdk
中實現是默認一個空數組,因為有的時候ArrayList
創建后並不會添加元素
當然,這兩個都是靜態私有域
值得注意的是 this.elementData
是一個Object的數組 transient
表示這個屬性不用被序列化,通過注釋可以得知,element在第一次添加的時候會被擴容到默認容量(默認為10)
add 方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
add 方法中調用了 ensureCapacityInternal
相當於確保容量最少是size+1,size
就是當前ArrayList
元素個數,然后在elementData
末尾加入元素
接下來看一下是如何確保容量的
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
ensureCapacityInternal
首先會調用calculateCapacity
,這里主要是為了計算第一次初始化的時候,因為我們在默認初始化的時候,默認容量是10,但是為什么確保闊容是Math.max(DEFAULT_CAPACITY, minCapacity);
,這里主要是因為如果我們添加一個集合的話,要確保至少大小是集合中元素的大小,否則可能會多一次擴容
然后調用ensureExplicitCapacity
ensureExplicitCapacity
:先設置一下當前容器已經被更改,然后判斷當前最少需要容量是不是大於數組長度,如果大於,那就擴容
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 int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
首先獲取舊數組的長度然后用舊數組長度進行擴容為1.5倍,然后判斷和最小需求容量對比,如果小於最小容量,那么就擴容到最小容量那么長,然后判斷是不是大於一個閾值,如果大於這個最大閾值,那么就擴容到Integer.MAX_VALUE(正整數最大值,2^31-1)
至於為什么要判斷minCapacity<0
,那是因為假設當前已經擴容到最大值,要是還不夠,那么再擴容就是int溢出
最后把源數組copy到新的容量大小賦值給elementData
,Array.copyOf
底層是native方法(System.arraycopy)
之前自己寫的
ArrayList
都是通過oldcaptain = oldcaptain<<1+1;
來進行擴容的(+1是避免舊數組長度為0的情況),jdk
對於不同的情況有不同的擴容標准,而且以前自己的Copy都是用數組遍歷Copy的很笨重,這里學到了
再來看一下 add(int index,T ele)
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
這個也很好理解,就是先檢查index是否在范圍內(0~size)如果不在就拋出一個越界異常
然后准備擴容,接下來就是數組拷貝
System.arraycopy
也是一個native方法
看一下注釋就是把src
從srcPos
開始拷貝到dest
從destPos
開始的位置一共copy
length
這么長
如果src==dst
那么這個函數表現就像先拷貝到一個臨時數組,再覆蓋dst
對應位置
不會像
*dst++=*src++
把后面的元素覆蓋然后后面元素都是一個值
這樣就是把elementData
從index開始到最后一個元素,拷貝到src
+1的位置
最后執行elementData[index] = element;
把元素覆蓋
然后我們看remove :
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
remove方法跟add基本同理,但是不需要擴容而且最后覆蓋元素的時候是使用null填充最后一個元素
之前實現的時候沒考慮到用null覆蓋,這樣會導致在
GC
的時候,本來需要刪除的元素還可以通過ArrayList
找到,然后就無法GC
,這里學到了
remove一個對象
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
找對應元素的話基本都是大同小異,主要是fastRemove
跟自己實現的不太一樣
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
fastRemove
里面跟remove基本相同,少了一個index判斷也沒有返回值
clear:
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
clear方法之前一直以為是直接把size設為0,但是jdk
里面實現是遍歷一下設null,但是這里我總覺得應該再多提供一個fastclear
什么的比較好吧
設為null會讓對象索引不到,可以被垃圾回收,但是如果頻繁add clear的話總覺得不值得啊
再看一下一些跟集合的操作
通過一個集合初始化:
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
這里首先調用集合的toArray()
方法,不過要確保elementData
真的是一個Object[]數組
Java 中對象數組子類數組引用也可以轉換為超類的引用
比方說 Manager 繼承了 Employee
Manager[]managers = new Manager[10];
那么我們可以
Employee[]employees = managers;//完全沒問題
但是如果我們在使用employees的時候在里面存放了一個new Employees,那么就會發生一個異常
這個
jdk
的bug可以查一下
Java集合中toArray
一般情況下都是Object[]
數組,不過手動實現一個集合,有可能出問題,所以jdk
采用這種方式避免了不必要的麻煩
就是避免這種情況:
ArrayList<Integer> integers = new ArrayList<>(0);
integers.add(1);
System.out.println(integers.toArray().getClass());
Integer[]integers_array = new Integer[2];
integers_array[0]=1;
integers_array[1]=2;
Class c = Arrays.asList(integers_array).toArray().getClass();
System.out.println(c);
Array.asList
就是包裝一個視圖,里面使用add remove什么的都會拋一個異常