ArrayList原始碼閱讀,拒絕人云亦云~
ArrayList我們稱之為動態陣列,其在保留了傳統陣列快速查詢優點的同時,也增加了可變長這一特性。
在Java的世界中,陣列可以說是我們用的最多的資料結構之一。陣列是什麼呢?個人理解就是記憶體中一塊連續的儲存相同型別元素的地方(這個“地方”用的比較low。。。多看書多看書)
陣列的優點是什麼呢?查詢快呀!!!
大家都知道查詢快~
為什麼查詢快呀?有索引呀!!!
哦?那索引又是什麼?陣列下標啊,0,1,2,3…
原來索引就是0,1,2,3呀~
那有索引為啥就快呀?
額。。。年輕人不講武德,耗子尾汁!!!
我大意了,沒有說清楚
為什麼有索引就會查詢快呢?對於陣列我們實際上在記憶體中存的是陣列元素的首地址,且陣列儲存的元素是地址連續的,因此當我們想要查詢某個索引對應的值時,只需要用首地址+index*元素大小(如byte,short,int,long,分別為1,2,4,8),就是對應索引的地址啦,時間複雜度為O(1)。
是不是很快?什麼?你沒感覺到快?
那給你舉個查詢慢的資料結構,連結串列。連結串列連結串列,顧名思義,它是鏈起來的,每個連結串列的節點都會儲存與之相鏈的下一個節點的地址(單向連結串列),因此他們的地址幾乎是不連續的,因此當我們想要查詢某個節點時,不管怎樣,我們每次都得從頭節點開始遍歷,直到找到我們想要的那個元素(如果按照一般回答,有索引就快,那連結串列也是有索引的。。。),因此連結串列查詢的時間複雜度就是O(n)。
那陣列的缺點是啥嘞?普通陣列的缺點就是不可變長,這個問題就會造成記憶體浪費,或者記憶體不夠,這是一個尷尬的問題。。。
因此動態陣列來了,動態陣列繼承了傳統陣列查詢快這一優點同時又可變長,那麼動態陣列的缺點是啥呢?
增刪慢呀。
為啥增刪慢呀?
因為動態陣列在每一次增刪,都會在整個陣列上操作,有點牽一髮而動全身的趕腳,與之相反連結串列在增刪比較快,因為連結串列只需要斷鏈再連線鏈即可,只是區域性地方在變化。
什麼?你不信?來我們看原始碼!
直接奔向add方法
//增加一個元素,只要不報錯,返回為恆為true
public boolean add(E e) {
//擴容
ensureCapacityInternal(size + 1); // Increments modCount!!
//末尾插入元素
elementData[size++] = e;
return true;
}
//在指定index處插入一個值
public void add(int index, E element) {
//檢查index合法性
rangeCheckForAdd(index);
//擴容,注意並不是容量擴充為size+1,請細看grow函式
ensureCapacityInternal(size + 1); // Increments modCount!!
//將原陣列的index位置及以後的元素複製給從index+1開始及以後的位置
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//將指定元素放到index位置
elementData[index] = element;
//容量+1
size++;
}
我們看到ArrayList裡面有兩個add方法,順便複習一下這叫做方法過載(與之相對就是重寫)
這兩個add方法,分別是在末尾插入一個元素以及在指定位置插入一個元素,先看第一個
,首先映入眼簾的是ensureCapacityInternal()選手
//若傳入陣列為空則返回初始容量與傳入引數之間的最大值
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++,代表這個ArrayList被修改一次(這個與迭代器有關)
modCount++;
// overflow-conscious code
//若傳入引數比陣列本身的容量大,則進行grow
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
走我們去看看grow()
//擴容
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) {
//若傳入引數小於0,丟擲OutOfMemoryError
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//若傳入引數大於最大容量則返回Integer的最大值,否則返回最大容量
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
我們繼續看一下Arrays.copyOf()
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
繼續往裡面走copyOf()
//引數分別是原陣列,新的陣列長度,以及陣列型別
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
//先判斷陣列型別是否是Object型,反正最後都是new了一個新的陣列
T[] copy = ((Object)newType == (Object)Object[].class)
//若是則直接new一個Object型陣列
? (T[]) new Object[newLength]
//若不是則通過反射建立一個對應型別陣列
//getComponentType:返回Class物件,如果不是Class物件那麼返回null
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
//然後將原陣列複製到新的copy陣列上,複製的起始是從0開始
//到original.length與newLength之間最小長度的值
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
//然後返回新的陣列
return copy;
}
什麼?還想看System.arraycopy()?走起
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
一看是native修飾。。。惹不起惹不起,溜了溜了
我們接著看另外一個add方法,道理是一樣的啦,註釋寫的比較清楚~
走,我們去看remove方法,同樣寫在了註釋了
//移除指定索引位置元素
public E remove(int index) {
//檢查index合法性
rangeCheck(index);
//記錄ArrayList被修改過一次
modCount++;
//要被移除的值
E oldValue = elementData(index);
//要複製的元素個數
int numMoved = size - index - 1;
if (numMoved > 0)
//將原陣列index+1及以後的元素複製到原陣列index及以後的位置上,複製長度為numMoved
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//將最後一個位置設定為空,意思是GC會最後處理這個空閒地址?
elementData[--size] = null; // clear to let GC do its work
//返回被移除的值
return oldValue;
}
//移除指定元素
public boolean remove(Object o) {
//若指定元素為空
if (o == null) {
//找到第一個為指定元素的就移除並返回true
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;
}
}
//若沒找到則返回false
return false;
}
//同理
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
}
ArrayList裡面還有許多其他的方法。。。你們繼續看~
相關文章
- ArrayList原始碼閱讀(增)原始碼
- ArrayList原始碼閱讀筆記原始碼筆記
- 閱讀原始碼,從ArrayList開始原始碼
- 走進原始碼——ArrayList閱讀筆記原始碼筆記
- 原始碼閱讀之ArrayList實現細節原始碼
- 【原始碼閱讀】AndPermission原始碼閱讀原始碼
- 【原始碼解析】- ArrayList原始碼解析,絕對詳細原始碼
- 知其然知其所以然之ArrayList常用原始碼閱讀原始碼
- 【原始碼閱讀】Glide原始碼閱讀之with方法(一)原始碼IDE
- 【原始碼閱讀】Glide原始碼閱讀之into方法(三)原始碼IDE
- ReactorKit原始碼閱讀React原始碼
- AQS原始碼閱讀AQS原始碼
- CountDownLatch原始碼閱讀CountDownLatch原始碼
- HashMap 原始碼閱讀HashMap原始碼
- delta原始碼閱讀原始碼
- 原始碼閱讀-HashMap原始碼HashMap
- NGINX原始碼閱讀Nginx原始碼
- Mux 原始碼閱讀UX原始碼
- HashMap原始碼閱讀HashMap原始碼
- fuzz原始碼閱讀原始碼
- RunLoop 原始碼閱讀OOP原始碼
- express 原始碼閱讀Express原始碼
- muduo原始碼閱讀原始碼
- stack原始碼閱讀原始碼
- 【原始碼閱讀】Glide原始碼閱讀之load方法(二)原始碼IDE
- PostgreSQL 原始碼解讀(3)- 如何閱讀原始碼SQL原始碼
- JDK原始碼閱讀:Object類閱讀筆記JDK原始碼Object筆記
- Laravel 原始碼閱讀 - QueueLaravel原始碼
- Vollery原始碼閱讀(—)原始碼
- 使用OpenGrok閱讀原始碼原始碼
- 如何閱讀Java原始碼?Java原始碼
- buffer 原始碼包閱讀原始碼
- 原始碼閱讀技巧篇原始碼
- 如何閱讀框架原始碼框架原始碼
- 再談原始碼閱讀原始碼
- Laravel 原始碼閱讀 - EloquentLaravel原始碼
- 如何閱讀jdk原始碼?JDK原始碼
- express 原始碼閱讀(全)Express原始碼