Collection原始碼之路(1)——ArrayList
(以下原始碼建立在JDK 10版本基礎上)
ArrayList這個類用的實在是太頻繁了,除基本型別之外應該算是最常用了吧,但是一直用過卻一直不曾研究過裡面的原始碼,這是程式設計師的大忌,用什麼就要研究什麼,否則只是程式碼工匠談不上程式碼師。
在開始ArrayList學習之前,我一直有個疑問,就是陣列可以動態擴容嗎?我們知道java宣告陣列時,必須指明陣列的個數或者列舉出所有的元素(等於告訴編譯器自己長度為多少)
String a[] = new String[]{"1","2","3"};
String b[] = new String[10];
String c[] = new String[];//這一行編譯不過去
可是如果我們使用的時候發現數據太多了,陣列太小了,需要擴容怎麼辦呢?
沒辦法,新建一個數組,將舊的陣列資料拷貝到新數組裡面
String a[] = new String[]{"1","2","3"};
String b[] = new String[a.length+10];
例子比較簡單,這樣做尚可但是如果比較複雜的陣列怎麼處理長度呢?而且即使你處理完了發現數組又小了,該如何是好?繼續建立新的陣列嗎?這樣做太麻煩了!
建立一個可改變長度的陣列就好了——ArrayList
正因為有了這樣的需求,ArrayList橫空出世,ArrayList就是可改變長度的陣列
Resizable-array implementation of the {@code List} interface.
與此同時,其元素還包括null
Implements all optional list operations, and permits all elements, including {@code null}
Vector執行緒安全,ArrayList執行緒不安全
(This class is roughly equivalent to {@code Vector}, except that it is unsynchronized.)
其成員變數有下面幾個
/**
* Default initial capacity. 預設集合長度為10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
* 當你用new ArrayList(0)初始化的時候指定長度為0就會用到這個陣列
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
* 當你使用new ArrayList()生成集合的時候預設使用這個陣列,解釋具體看後面的程式碼
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
前面transient 表示是暫時的變數,序列化和反序列化都不會操作這個變數
這個變數什麼呢?就是在不斷變化的那個陣列,就好像我們使用p=head,在不破壞原有指標的情況下,構造了一個區域性變數來代替head指標,這個也是如此用於代替上面兩個陣列
transient Object[] elementData;
集合的長度
需要注意的是size是表示集合的長度,而後續原始碼中有elementData.length表示的是內部陣列的長度,這兩個不是一個概念!size是暴露給呼叫者的,elementData這個陣列是底層陣列,對於使用者是不可知的,他的長度不用和size相同
private int size;
集合最大長度,最大值-8,為啥-8我也不知道????
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
1 先看看我們常用的初始化操作 new ArrayList<>()
List<String> a=new ArrayList<>();
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
也就是說如果我們使用無參構造方式建立的集合,使用的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA這個陣列
2 再看看add()方法
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
如果發現數組已經滿了,程式會走進grow()方法,用於擴容這個陣列,操作成功之後集合長度+1
private Object[] grow(int minCapacity) {
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
private Object[] grow() {
return grow(size + 1);
}
最少也要在原來的陣列上增加1個,所以size+1,接下來我們看下Arrays.copyOf裡面的newCapacity方法,這個方法是獲取要擴容的長度,我們增加一個元素只擴容1的話豈不是每次擴容全部方法都要走一遍?不如多擴容點,這樣的話省的以後麻煩
private int newCapacity(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity <= 0) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return minCapacity;
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
int newCapacity = oldCapacity + (oldCapacity >> 1);
這句話的意思是新的陣列長度要是舊的1.5倍,也就是說擴容要擴充1.5倍,>>1表示除以2的意思
當集合為空的時候,newCapacity - minCapacity <= 0,又是DEFAULTCAPACITY_EMPTY_ELEMENTDATA陣列所以返回的擴容長度為DEFAULT_CAPACITY,所以預設集合長度為10
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
new Object[newLength]是新建了一個數組
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
這段程式碼很重要,陣列擴容就是用的這個方法
這句話的意思是:
將original陣列從0位置至original.length位置,複製到copy陣列0位置到Math.min位置。
為什麼做了一個取最小值操作呢?
因為有可能是陣列增加也有可能是陣列刪除,防止造成陣列下標越界。
經過了一系列操作,這個陣列變成了一個新陣列copy,陣列長度也變化了
3 再看看get()方法
public E get(int index) {
Objects.checkIndex(index, size);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
Objects.checkIndex(index, size);這句話是檢驗是否下標越界,如果越界的話會丟擲異常
本身就是陣列,所以取相應索引的資料也很容易看懂
4 remove方法
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found: {
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
fastRemove(es, i);
return true;
}
這也很容易只是找到對應object的索引,具體刪除的工作交給了fastRemove方法
found:{}這個是語句塊命名的意思,現在基本上不這麼寫了,估計是作者寫1.2java的時候還是忘不掉c語言的goto語句就這麼寫了,就是返回個索引而已,很簡單
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
如果要刪除的元素在最後一個,也就是說(newSize = size - 1) == i的話,沒有必要運算元組,直接陣列的最後一個數據為空就好了,但是如果是前面的某個資料要被刪除呢?
System.arraycopy(es, i + 1, es, i, newSize - i);
又是這句話,意思是把i之後的元素複製到i個位置開始到newSize - i長度,這個有點繞,舉個例子
陣列 1 2 3 4
我想刪除第二個元素,下標是1
變成新陣列 1 3 4
把2刪除掉,34前移
5 addAll()方法
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
modCount++;
int numNew = a.length;
if (numNew == 0)
return false;
Object[] elementData;
final int s;
if (numNew > (elementData = this.elementData).length - (s = size))
elementData = grow(s + numNew);
System.arraycopy(a, 0, elementData, s, numNew);
size = s + numNew;
return true;
}
增加的操作之前已經說過啦,但是有一點需要說下,專案中是不允許addAll空的集合的,因為作者在第一句Object[] a = c.toArray();使用的時候就沒有做判空操作,所以我專案中就遇到過原始碼的空指標異常,把我嚇壞了~~~
6 clear方法
public void clear() {
modCount++;
final Object[] es = elementData;
for (int to = size, i = size = 0; i < to; i++)
es[i] = null;
}
陣列還在,只是裡面每個資料變成空了而已,但是size已經被賦值成了0,也就是說對於外部來說這個集合為空但是實際上裡面還是有陣列的。
好啦,以上就是ArrayList的大致原始碼總結,歡迎讀者朋友下方留言。