java list addall源码_Java集合:ArrayList源码分析
其實我看到已有很多大佬寫過此類文章,并且寫的也比較清晰明了,那我為何要再寫一遍呢?其實也是為了加深本身的印象,鞏固本身的基礎html
(主要是不少文章沒有寫出來我想知道的東西!!!?!!!!)java
前言
我說一下我認為怎么樣才能去看懂,看透徹一個源碼。
在你去分析源碼的時候,首先要會用,要明白這個工具類的做用,若是連一個工具類都包含哪些功能,這些功能的做用都不清楚,我以為看源碼就是一種煎熬。(固然,大佬除外)api
?自我洗腦中~我是大佬!我是大佬!我是大佬!(he~tui!我不配!!!)數組
正文
本次是基于JDK1.8來具體分析ArrayList源碼dom
ArrayList的概念:
動態數組,它提供了動態的增長和減小元素,實現了Collection和List接口,靈活的設置數組的大小等好處。
每一個 ArrayList 實例都有一個容量。該容量是指用來存儲列表元素的數組的大小。它老是至少等于列表的大小。隨著向 ArrayList 中不斷添加元素,其容量也自動增加。函數
一、繼承結構分析
咱們先來看一下ArrayList類的繼承結構:工具
?
Java支持單繼承,多實現源碼分析
AbstractList:性能
抽象接口類,目的是使用抽象類中已經實現的方法。
咱們點開AbstractList源碼,會看到其實AbstractList已經也實現了List接口,為何要先繼承AbstractList,而讓AbstractList先實現List?而不是讓ArrayList直接實現List?優化
這里是有一個思想,接口中全都是抽象的方法,而抽象類中能夠有抽象方法,還能夠有具體的實現方法,正是利用了這一點,讓AbstractList實現接口中一些通用的方法,而如ArrayList就繼承這個AbstractList類,拿到一些通用的方法,而后本身在實現一些本身特有的方法,這樣一來,讓代碼更簡潔,就繼承結構最底層的類中通用的方法都抽取出來,先一塊兒實現了,減小重復代碼。因此通常看到一個類上面還有一個抽象類,應該就是這個做用。
List:
使用List的接口規范
RandomAccess:
這個是一個標記性接口,經過查看api文檔,它的做用就是用來快速隨機存取,有關效率的問題,在實現了該接口的話,那么使用普通的for循環來遍歷,性能更高,例如arrayList。而沒有實現該接口的話,使用Iterator來迭代,這樣性能更高,例如linkedList。因此這個標記性只是為了讓咱們知道咱們用什么樣的方式去獲取數據性能更好。
Cloneable:
Serializable:
實現該序列化接口,代表該類能夠被序列化,什么是序列化?簡單的說,就是可以從類變成字節流傳輸,而后還能從字節流變成原來的類。
🤔🤔🤔🤔為何AbstractList已經實現了List,ArrayList還要再實現一次呢?
其實ArrayList再去實現一次List在這里并無什么實際意義,這實際上是一個錯誤,由于做者寫這代碼的時候以為這個會有用處,可是其實并沒什么用,但由于沒什么影響,就一直留到了如今。有興趣的同窗能夠去研究一下。
二、類分析
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* 缺省容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 有參構造缺省空數組
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 無參構造缺省空數組
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 數組元素(實際操做的數組,新增,刪除等方法都是在此數組發生操做)
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* 實際數組的大小
*/
private int size;
/**
* 數組的最大容量
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
這里分析幾個地方:
(1)為何數組最大容量是Integer.MAX_VALUE - 8,而不是Integer.MAX_VALUE?
其實源碼中給了備注:意思應該是有些虛擬機在數組中保留了一些頭信息。避免內存溢出!
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
(2)為何定義了兩個空數組?
首先定義空數組的根本緣由是
優化處理,若是一個應用中有不少這樣ArrayList空實例的話,就會有不少的空數組,無疑是為了優化性能,全部ArrayList空實例都指向同一個空數組。二者都是用來減小空數組的建立,全部空ArrayList都共享空數組。二者的區別主要是用來起區分做用,針對有參無參的構造在擴容時作區分走不一樣的擴容邏輯,優化性能。
(3)elementData為何定義成transient?
三、構造方法
?
Array List總共有三個構造方法,下面咱們一一分析
1)無參構造方法 ArrayList()
/**
* 將空數組初始化大小為10(將空數組初始化大小為10,具體在何時初始化大小為10,待會兒會說到)
*/
public ArrayList() {
// 將elementData元素數組初始化為空數組
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
無參構造方法中,將元素數組elementData初始化為空數組。(注意:這里就體現了我上文說到的,為何定義兩個空數組)
2)有參構造方法 ArrayList(int)
/**
* 構造一個具備指定初始容量的列表
*
* @param initialCapacity: 初始化數組的值
*/
public ArrayList(int initialCapacity) {
//若是初始化的值大于0,則給定elementData一個長度為initialCapacity的數組
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) { // 若是初始化的值等于0,則初始化為空數組
this.elementData = EMPTY_ELEMENTDATA;
} else { //不然(小于0的狀況)拋出異常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
3)有參構造方法 ArrayList(Collection extends E> c)
/**
* 構造一個指定元素的集合(此方法不太經常使用)
* @param c
*/
public ArrayList(Collection extends E> c) {
// 將集合轉換為數組并賦值給elementData
elementData = c.toArray();
// 若是集合的大小不為0
if ((size = elementData.length) != 0) {
// 若是轉換后的數組不是泛型(object),則須要用Arrays的工具轉換一下為object數組(這里再也不對Arrays.copyOf展開論述)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else { // 不然初始化elementData為一個空數組
this.elementData = EMPTY_ELEMENTDATA;
}
}
對于當前構造方法,我舉個例子,更清晰明了
?
四、經常使用方法源碼分析
boolean add(E e)
重中之重,ArrayList的核心奧秘!!!!
/**
* 在數組中增長一個元素
* @param e 元素對象
*/
public boolean add(E e) {
// 肯定內部容量是否夠用,size是元素數組中數據的個數,由于要添加一個元素,因此size+1,先判斷size+1的這個個數數組可否放得下,就在這個方法中去判斷是否數組.length是否夠用了。
ensureCapacityInternal(size + 1);
// 將元素e賦值到elementData末尾
elementData[size++] = e;
return true;
}
// 此方法能夠理解為中轉計算
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 判斷數組是否是空數組, 若是是空數組(此時minCapacity = 0 + 1 = 1),就將minCapacity初始化為10,但此時僅僅是返回要初始化數組的大小,并無真正初始化數組為10
// private static final int DEFAULT_CAPACITY = 10;
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// Math.max(參數1,參數2)方法的意思是返回參數中最大的數,若是是空數組是,此時返回的是10
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 若是初始化的集合不是空,則返回元素數組的size + 1
return minCapacity;
}
// 別擔憂 我有奧妙全自動(奧妙洗衣粉~全國人民都知道~~)
private void ensureExplicitCapacity(int minCapacity) {
// 結構變化記錄+1 在父類AbstractList中定義了一個int型的屬性:modCount,記錄了ArrayList結構性變化的次數
modCount++;
// 判斷數組是否夠用,若是不夠用,則自動擴容
// 一、當初始化的集合為空數組時,此時minCapacity是10,而elementData的長度為0,因此須要擴容
// 二、當初始化的集合不為空是,也就是給定了大小,或已經初始化了元素,此時的minCapacity = 實際數組個數+1,此時判斷集合不夠用,也須要進行擴容,不然元素會溢出
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
// 自動擴容
private void grow(int minCapacity) {
// oldCapacity:元素數組的實際長度(即擴充前的數組大小)
int oldCapacity = elementData.length;
// oldCapacity 擴容1.5倍賦值給newCapacity( >>為右移運算符,至關于除以2 即oldCapacity/2 )
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 若是初始化為空的狀況,則將數組擴容為10,此時才是真正初始化元素數組elementData大小為10
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 若是1.5倍的數組大小超過了集合的最大長度,則調用hugeCapacity方法,從新計算,也就是給定最大的集合長度
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 已經肯定了大小,就將元素copy到elementData元素數組中~~
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
// 內存溢出判斷
if (minCapacity < 0)
throw new OutOfMemoryError();
// 這里的邏輯為:若是須要擴容的大小比數組的最大值都大,就返回Integer,MAX_VALUE(int最大值),不然返回集合的最大值(int最大值-8)
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
void add(int index, E element)
增長元素到指定下標
/**
* 增長元素到指定下標
*
* @param index 下標
* @param element 元素
*/
public void add(int index, E element) {
// 參數校驗
rangeCheckForAdd(index);
// 此方法再也不贅述,參考上文Add方法重的論述
ensureCapacityInternal(size + 1);
// 請看下面代碼塊的注釋
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 將指定元素覆蓋到指定下標
elementData[index] = element;
// 長度size + 1
size++;
}
/**
* 適用于add 和 addAll的校驗方法
*/
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
System.arraycopy 方法解析
/**
* System提供了一個靜態方法arraycopy(),咱們可使用它來實現數組之間的復制
* 函數為:public static native void arraycopy(Object src,int srcPos,Object dest, int destPos,int length);
* @param src the source array. 源數組
* @param srcPos starting position in the source array. 源數組的起始位置
* @param dest the destination array. 目標數組
* @param destPos starting position in the destination data. 目標數組的起始位置
* @param length the number of array elements to be copied. 復制的長度
//舉個例子
public static void main(String[] args){
int[] arr = {1,2,3,4,5};
int[] copied = new int[10];
System.out.println(Arrays.toString(copied));
System.arraycopy(arr, 0, copied, 1, 5);//5是復制的長度
System.out.println(Arrays.toString(copied));
}
輸出結果為:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 1, 2, 3, 4, 5, 0, 0, 0, 0]
boolean remove(Object o)
根據元素進行刪除
public E remove(int index) {
// 參數校驗
rangeCheck(index);
// 結構變化記錄+1
modCount++;
// 獲取舊數據,返回給開發人員,目的是讓開發人員知道刪除了哪一個數據
E oldValue = elementData(index);
// 計算須要元素須要移動的次數
int numMoved = size - index - 1;
if (numMoved > 0)
// 同上文敘述
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 將最后一個元素置為空(元素前移,最后一位置為空),讓GC回收
elementData[--size] = null;
return oldValue;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
void clear()
清空集合
/**
* 清空集合
*/
public void clear() {
modCount++;
// 將數組置為空,促使GC回收
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
E get(int index)
返回此列表中指定位置上的元素
/**
* 檢查給定的索引是否在范圍內。 若是沒有,則拋出一個適當的運行時異常。
* @param index : 下標
*/
public E get(int index) {
// 校驗下標有效性
rangeCheck(index);
// 返回元素數組中指定index位置的數據
return elementData(index);
}
private void rangeCheck(int index) {
// 若是下標大于實際數組長度(元素數組最后一個數據下標為size-1)
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
E set(int index, E element)
/**
* 覆蓋相應下標的數據
* @param index 下標
* @param element 元素數據
*/
public E set(int index, E element) {
// 校驗方法(再也不解釋,與get方法中同樣)
rangeCheck(index);
// 獲取到舊數據,這里將舊數據返回出去,為了讓開發者知道替換的是哪一個值
E oldValue = elementData(index);
// 將指定下標覆蓋為新元素
elementData[index] = element;
return oldValue;
}
結尾
其實ArrayList中還有不少不少方法,這里就不在一一敘述了,由于你理解了本文中所說的源碼,其實其它再去理解,再去查看,是比較容易簡單的。
?
本篇文章就講到這里,若是有寫的很差的地方,請多多指教,我是善良的小黑哥,但愿與你們共同進步!!
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的java list addall源码_Java集合:ArrayList源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ssd3 employee.java_S
- 下一篇: java事务过大影响系统性能吗_Java