arraylist 初始化_ArrayList(JDK1.8)源码解析
既然是看源碼,那我們要怎么看一個類的源碼呢?這里我推薦的方法是:
1)看繼承結構
看這個類的層次結構,處于一個什么位置,可以在自己心里有個大概的了解。
2)看構造方法
在構造方法中,看做了哪些事情,跟蹤方法中里面的方法。
3)看常用的方法
跟構造方法一樣,這個方法實現功能是如何實現的
注:既然是源碼,為什么要這樣設計類,有這樣的繼承關系。這就要說到設計模式的問題了。所以我們要了解常用的設計模式,才能更深刻的去理解這個類。
簡介
ArrayList 是 Java 集合框架中 List 接口的一個實現類。底層是數組,相當于動態數組。與 Java 中的數組相比,它的容量能動態增長。
ArrayList是Vector的翻版,區別在于ArrayList是線程不安全的,而Vector則是線程安全的。但是Vector是一個較老的集合,具有很多缺點,不建議使用,這里我們就不對其進行分析了。
ArrayList 可以說是我們使用最多的 List 集合,它有以下特點:
它是基于數組實現的List類
可以動態地調整容量
有序的(元素輸出順序與輸入順序一致)
元素可以為 null
不同步,非線程安全,效率高
查詢快,增刪慢
占用空間更小,對比 LinkedList,不用占用額外空間維護鏈表結構
ArrayList 為什么有這些優點呢?我們通過源碼來分析分析。在閱讀源碼前先來看看ArrayList繼承關系。
繼承關系圖
可以看到,ArrayList是AbstractList的子類,同時實現了List接口。除此之外,它還實現了三個標識型接口,這幾個接口都沒有任何方法,僅作為標識表示實現類具備某項功能。RandomAccess表示實現類支持快速隨機訪問,Cloneable表示實現類支持克隆,具體表現為重寫了clone方法,java.io.Serializable則表示支持序列化,如果需要對此過程自定義,可以重寫writeObject與readObject方法。
成員變量
// 序列號private static final long serialVersionUID = 8683452581122892189L;// 數組初始容量為 10private static final int DEFAULT_CAPACITY = 10;// 空對象數組private static final Object[] EMPTY_ELEMENTDATA = {};// 缺省空對象數組private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};// 底層數據結構,數組transient Object[] elementData;// 數組元素個數,默認為0private int size;// 最大數組容量private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;構造方法
//默認構造方法,初始為空數組。//只有插入一條數據后才會擴展為10,而實際上默認是空的 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}//根據指定容量創建對象數組public ArrayList(int initialCapacity) { if (initialCapacity > 0) { //創建initialCapacity大小的數組 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { //創建空數組 this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); }}/** * 構造一個包含指定集合的元素的列表,按照它們由集合的迭代器返回的順序。 */public ArrayList(Collection extends E> c) { //轉換最主要的是toArray(),這在Collection中就定義了 elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray 有可能不返回一個 Object 數組 if (elementData.getClass() != Object[].class) //使用 Arrays.copy 方法拷創建一個 Object 數組 elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // 替換為空數組 this.elementData = EMPTY_ELEMENTDATA; }}以無參數構造方法創建 ArrayList 時,實際上初始化賦值的是一個空數組。當真正對數組進行添加元素操作時,才真正分配容量。即向數組中添加第一個元素時,數組容量擴為10。
內部類
(1)private class Itr implements Iterator(2)private class ListItr extends Itr implements ListIterator(3)private class SubList extends AbstractListimplements RandomAccess (4)static final class ArrayListSpliteratorimplements SpliteratorArrayList有四個內部類,其中的Itr是實現了Iterator接口,同時重寫了里面的hasNext(), next(), remove() 等方法;其中的ListItr 繼承 Itr,實現了ListIterator接口,同時重寫了hasPrevious(), nextIndex(), previousIndex(), previous(), set(E e), add(E e) 等方法,所以這也可以看出了 Iterator和ListIterator的區別:ListIterator在Iterator的基礎上增加了添加對象,修改對象,逆向遍歷等方法,這些是Iterator不能實現的。
核心方法
add()方法(有四個)
增和刪是ArrayList最重要的部分,這部分代碼需要我們細細研究
//添加一個特定的元素到list的末尾public boolean add(E e) { //先確保elementData數組的長度足夠,size是數組中數據的個數,因為要添加一個元素,所以size+1,先判斷size+1的這個個數數組能否放得下,在這個方法中去判斷數組長度是否夠用 ensureCapacityInternal(size + 1); // Increments modCount!! //在數據中正確的位置上放上元素e,并且size++ elementData[size++] = e; return true;}//在指定位置添加一個元素public void add(int index, E element) { rangeCheckForAdd(index); //先確保elementData數組的長度足夠 ensureCapacityInternal(size + 1); // Increments modCount!! //將數據整體向后移動一位,空出位置之后再插入,效率不太好 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++;}// 校驗插入位置是否合理private void rangeCheckForAdd(int index) { //插入的位置肯定不能大于size 和小于0 if (index > size || index < 0) //如果是,就報越界異常 throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}//添加一個集合public boolean addAll(Collection extends E> c) { //把該集合轉為對象數組 Object[] a = c.toArray(); int numNew = a.length; //增加容量 ensureCapacityInternal(size + numNew); // Increments modCount //挨個向后遷移 System.arraycopy(a, 0, elementData, size, numNew); size += numNew; //新數組有元素,就返回 true return numNew != 0;}//在指定位置,添加一個集合public boolean addAll(int index, Collection extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index; //原來的數組挨個向后遷移 if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); //把新的集合數組 添加到指定位置 System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0;}雖說 System.arraycopy 是底層方法,但每次添加都后移一位還是不太好。
對數組的容量進行調整
以上兩種添加數據的方式都調用到了ensureCapacityInternal這個方法,我們看看它是如何完成工作的
//確保內部容量夠用private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}//計算容量。判斷初始化的elementData是不是空的數組,如果是空的話,返回默認容量10與minCapacity=size+1的較大值者。private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity;}//確認實際的容量,這個方法就是真正的判斷elementData是否夠用private void ensureExplicitCapacity(int minCapacity) { modCount++; //minCapacity如果大于了實際elementData的長度,那么就說明elementData數組的長度不夠用,不夠用那么就要增加elementData的length。這里有的小伙伴就會模糊minCapacity到底是什么呢,這里解釋一下/** * 當我們要 add 進第1個元素到 ArrayList 時,elementData.length 為0 (因為還是一個空的 list),因為執行了 `ensureCapacityInternal()` 方法 ,所以 minCapacity 此時為10。此時,`minCapacity - elementData.length > 0 `成立,所以會進入 `grow(minCapacity)` 方法。 * 當add第2個元素時,minCapacity 為2,此時e lementData.length(容量)在添加第一個元素后擴容成 10 了。此時,`minCapacity - elementData.length > 0 ` 不成立,所以不會進入 (執行)`grow(minCapacity)` 方法。 * 添加第3、4···到第10個元素時,依然不會執行grow方法,數組容量都為10。 * 直到添加第11個元素,minCapacity(為11)比elementData.length(為10)要大。進入grow方法進行擴容。 */ // overflow-conscious code if (minCapacity - elementData.length > 0) //ArrayList能自動擴展大小的關鍵方法就在這里了 grow(minCapacity);}//擴容核心方法private void grow(int minCapacity) { //將擴充前的elementData大小給oldCapacity // overflow-conscious code int oldCapacity = elementData.length; //新容量newCapacity是1.5倍的舊容量oldCapacity int newCapacity = oldCapacity + (oldCapacity >> 1); //這句話就是適應于elementData就空數組的時候,length=0,那么oldCapacity=0,newCapacity=0,所以這個判斷成立,在這里就是真正的初始化elementData的大小了,就是為10。 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //如果newCapacity超過了最大的容量限制,就調用hugeCapacity,也就是將能給的最大值給newCapacity if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); //新的容量大小已經確定好了,就copy數組,改變容量大小。 // 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(); //如果minCapacity都大于MAX_ARRAY_SIZE,那么就Integer.MAX_VALUE返回,反之將MAX_ARRAY_SIZE返回。因為maxCapacity是三倍的minCapacity,可能擴充的太大了,就用minCapacity來判斷了。 //Integer.MAX_VALUE:2147483647 MAX_ARRAY_SIZE:2147483639 也就是說最大也就能給到第一個數值。還是超過了這個限制,就要溢出了。相當于arraylist給了兩層防護。 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;}至此,我們徹底明白了ArrayList的擴容機制了。首先創建一個空數組elementData,第一次插入數據時直接擴充至10,然后如果elementData的長度不足,就擴充至1.5倍,如果擴充完還不夠,就使用需要的長度作為elementData的長度。
大數據插入問題
這樣的方式顯然比我們例子中好一些,但是在遇到大量數據時還是會頻繁的拷貝數據。那么如何緩解這種問題呢,ArrayList為我們提供了兩種可行的方案:
使用ArrayList(int initialCapacity)這個有參構造,在創建時就聲明一個較大的大小,這樣解決了頻繁拷貝問題,但是需要我們提前預知數據的數量級,也會一直占有較大的內存。
除了添加數據時可以自動擴容外,我們還可以在插入前先進行一次擴容。只要提前預知數據的數量級,就可以在需要時直接一次擴充到位,與ArrayList(int initialCapacity)相比的好處在于不必一直占有較大內存,同時數據拷貝的次數也大大減少了。這個方法就是ensureCapacity(int minCapacity),其內部就是調用了ensureCapacityInternal(int minCapacity)。
我們這里使用ensureCapacity()方法測試
public class EnsureCapacityTest { public static void main(String[] args) { ArrayList list = new ArrayList(); final int N = 10000000; long startTime = System.currentTimeMillis(); for (int i = 0; i < N; i++) { list.add(i); } long endTime = System.currentTimeMillis(); System.out.println("使用ensureCapacity方法前:" + (endTime - startTime)); list = new ArrayList(); long startTime1 = System.currentTimeMillis(); list.ensureCapacity(N); for (int i = 0; i < N; i++) { list.add(i); } long endTime1 = System.currentTimeMillis(); System.out.println("使用ensureCapacity方法后:" + (endTime1 - startTime1)); }}運行結果
使用ensureCapacity方法前:2700使用ensureCapacity方法后:1054通過運行結果,我們可以很明顯的看出向 ArrayList 添加大量元素之前最好先使用ensureCapacity?方法,以減少增量重新分配的次數
remove()方法
其實這幾個刪除方法都是類似的。
//根據索引刪除指定位置的元素public E remove(int index) { //檢查index的合理性 rangeCheck(index); //這個作用很多,比如用來檢測快速失敗的一種標志。 modCount++; //通過索引直接找到該元素 E oldValue = elementData(index); //計算要移動的位數。 int numMoved = size - index - 1; if (numMoved > 0) //移動元素,挨個往前移一位。 System.arraycopy(elementData, index+1, elementData, index, numMoved); //將--size上的位置賦值為null,讓gc(垃圾回收機制)更快的回收它。 elementData[--size] = null; // clear to let GC do its work //返回刪除的元素。 return oldValue;}//從此列表中刪除指定元素的第一個匹配項,如果存在,則刪除。通過元素來刪除該元素,就依次遍歷,如果有這個元素,就將該元素的索引傳給fastRemobe(index),使用這個方法來刪除該元素,fastRemove(index)方法的內部跟remove(index)的實現幾乎一樣,這里最主要是知道arrayList可以存儲null值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;}//內部方法,“快速刪除”,就是把重復的代碼移到一個方法里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}//刪除或者保留指定集合中的元素//用于兩個方法,一個removeAll():它只清除指定集合中的元素,retainAll()用來測試兩個集合是否有交集。 private boolean batchRemove(Collection> c, boolean complement) { //將原集合,記名為A final Object[] elementData = this.elementData; //r用來控制循環,w是記錄有多少個交集 int r = 0, w = 0; boolean modified = false; try { //遍歷 ArrayList 集合 for (; r < size; r++) //參數中的集合c一次檢測集合A中的元素是否有 if (c.contains(elementData[r]) == complement) //有的話,就給集合A elementData[w++] = elementData[r]; } finally { //發生了異常,直接把 r 后面的復制到 w 后面 if (r != size) { //將剩下的元素都賦值給集合A System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { //這里有兩個用途,在removeAll()時,w一直為0,就直接跟clear一樣,全是為null。 //retainAll():沒有一個交集返回true,有交集但不全交也返回true,而兩個集合相等的時候,返回false,所以不能根據返回值來確認兩個集合是否有交集,而是通過原集合的大小是否發生改變來判斷,如果原集合中還有元素,則代表有交集,而元集合沒有元素了,說明兩個集合沒有交集。 // 清除多余的元素,clear to let GC do its work for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified;}//保留公共的public boolean retainAll(Collection> c) { Objects.requireNonNull(c); return batchRemove(c, true);}//將elementData中每個元素都賦值為null,等待垃圾回收將這個給回收掉public void clear() { modCount++; //并沒有直接使數組指向 null,而是逐個把元素置為空,下次使用時就不用重新 new 了 for (int i = 0; i < size; i++) elementData[i] = null; size = 0;}總結:根據索引刪除指定位置的元素,此時會把指定下標到數組末尾的元素挨個向前移動一個單位,并且會把數組最后一個元素設置為null,這樣是為了方便之后將整個數組不被使用時,會被GC,可以作為小的技巧使用。
get()方法
public E get(int index) { // 檢驗索引是否合法 rangeCheck(index); return elementData(index);}private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}說明:get函數會檢查索引值是否合法(只檢查是否大于size,而沒有檢查是否小于0),值得注意的是,在get函數中存在element函數,element函數用于返回具體的元素,具體函數如下:
E elementData(int index) { return (E) elementData[index];}說明:返回的值都經過了向下轉型(Object -> E),這些是對我們應用程序屏蔽的小細節。
set()方法
//設定指定下標索引的元素值public E set(int index, E element) { // 檢驗索引是否合法 rangeCheck(index); // 舊值 E oldValue = elementData(index); // 賦新值 elementData[index] = element; // 返回舊值 return oldValue;}indexOf()方法
// 從首開始查找數組里面是否存在指定元素public int indexOf(Object o) { // 查找的元素為空 if (o == null) { // 遍歷數組,找到第一個為空的元素,返回下標 for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { // 查找的元素不為空 // 遍歷數組,找到第一個和指定元素相等的元素,返回下標 for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } // 沒有找到,返回空 return -1;}//返回列表中指定元素最后一次出現的索引,倒著遍歷public int lastIndexOf(Object o) { if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1;}說明:從頭開始查找與指定元素相等的元素,需要注意的是可以查找null元素,意味著ArrayList中可以存放null元素的。與此函數對應的lastIndexOf,表示從尾部開始查找。
contains()方法
//判斷是否含有某個元素public boolean contains(Object o) { return indexOf(o) >= 0;}toArray()方法
/** 以正確的順序返回一個包含此列表中所有元素的數組(從第一個到最后一個元素); 返回的數組的運行時類型是指定數組的運行時類型。 */public Object[] toArray() { //elementData:要復制的數組;size:要復制的長度 return Arrays.copyOf(elementData, size);}public T[] toArray(T[] a) { //如果只是要把一部分轉換成數組 if (a.length < size) // Make a new array of a's runtime type, but my contents: return (T[]) Arrays.copyOf(elementData, size, a.getClass()); //全部元素拷貝到 數組 a System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a;}System.arraycopy()和 Arrays.copyOf()方法
閱讀源碼的話,我們就會發現 ArrayList 中大量調用了這兩個方法。比如:我們上面講的擴容操作以及add(int index, E element)、E remove(int index)、toArray() 等方法中都用到了該方法!
System.arraycopy()方法
System.arraycopy(…):將指定源數組中的數組從指定位置開始復制到目標數組的指定位置。
// src:源對象// srcPos:源對象對象的起始位置// dest:目標對象// destPost:目標對象的起始位置// length:從起始位置往后復制的長度。// 這段的大概意思就是解釋這個方法的用法,復制src到dest,復制的位置是從src的srcPost開始,到srcPost+length-1的位置結束,復制到destPost上,從destPost開始到destPost+length-1的位置上public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)簡單的方法測試以下:
public class ArraycopyTest { public static void main(String[] args) { int[] a = new int[10]; a[0] = 0; a[1] = 1; a[2] = 2; a[3] = 3; System.arraycopy(a, 2, a, 3, 3); a[2] = 99; for (int i = 0; i < a.length; i++) { System.out.print(a[i] + " "); } }}結果:
0 1 99 2 3 0 0 0 0 0Arrays.copyOf()方法
Array.copyOf() 選擇指定的數組,截斷或填充空值(如果需要),使副本具有指定的長度。以達到擴容的目的
//Arrays的copyOf()方法傳回的數組是新的數組對象,改變傳回數組中的元素值,不會影響原來的數組。//copyOf()的第二個自變量指定要建立的新數組長度,如果新數組的長度超過原數組的長度,則保留數組默認值public static T[] copyOf(T[] original, int newLength) { return (T[]) copyOf(original, newLength, original.getClass());}/** * @Description 復制指定的數組, 如有必要用 null 截取或填充,以使副本具有指定的長度 * 對于所有在原數組和副本中都有效的索引,這兩個數組相同索引處將包含相同的值 * 對于在副本中有效而在原數組無效的所有索引,副本將填充 null,當且僅當指定長度大于原數組的長度時,這些索引存在 * 返回的數組屬于 newType 類 * * @param original 要復制的數組 * @param newLength 副本的長度 * @param newType 副本的類 * * @return T 原數組的副本,截取或用 null 填充以獲得指定的長度 * @throws NegativeArraySizeException 如果 newLength 為負 * @throws NullPointerException 如果 original 為 null * @throws ArrayStoreException 如果從 original 中復制的元素不屬于存儲在 newType 類數組中的運行時類型 */public static 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;}測試代碼如下:
public static void main(String[] args) { int[] a = new int[3]; a[0] = 0; a[1] = 1; a[2] = 2; int[] b = Arrays.copyOf(a, 10); System.out.println("b.length " + b.length); for (int i : b) { System.out.print(i + " "); } }結果:
b.length100 1 2 0 0 0 0 0 0 0兩者聯系與區別
聯系:
看兩者源代碼可以發現copyOf()內部調用了System.arraycopy()方法區別:
arraycopy()需要目標數組,將原數組拷貝到你自己定義的數組里,而且可以選擇拷貝的起點和長度以及放入新數組中的位置
copyOf()是系統自動在內部新建一個數組,并返回該數組。
原文鏈接:
https://blog.csdn.net/ThinkWon/article/details/98845119
總結
以上是生活随笔為你收集整理的arraylist 初始化_ArrayList(JDK1.8)源码解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 爬虫入门知识+简单案例《python网络
- 下一篇: c#用canny算子做边缘提取_机器视觉