第二章 ArrayList原始碼解析
一、對於ArrayList需要掌握的七點內容
- ArrayList的建立:即構造器
- 往ArrayList中新增物件:即add(E)方法
- 獲取ArrayList中的單個物件:即get(int index)方法
- 刪除ArrayList中的物件:即remove(E)方法
- 遍歷ArrayList中的物件:即iterator,在實際中更常用的是增強型的for迴圈去做遍歷
- 判斷物件是否存在於ArrayList中:contain(E)
- ArrayList中物件的排序:主要取決於所採取的排序演算法(以後講)
二、原始碼分析
2.1、ArrayList的建立(常見的兩種方式)
List<String> strList = newArrayList<String>(); List<String> strList2 = new ArrayList<String>(2);
ArrayList原始碼:
基本屬性:
//物件陣列:ArrayList的底層資料結構 private transient Object[] elementData; //elementData中已存放的元素的個數,注意:不是elementData的容量 private int size;
注意:
- transient關鍵字的作用:在採用Java預設的
- ArrayList類實現了java.io.Serializable介面,即採用了Java預設的序列化機制
- 上面的elementData屬性採用了transient來修飾,表明其不使用Java預設的序列化機制來例項化,但是該屬性是ArrayList的底層資料結構,在網路傳輸中一定需要將其序列化,之後使用的時候還需要反序列化,那不採用Java預設的序列化機制,那採用什麼呢?直接翻到原始碼的最下邊有兩個方法,發現ArrayList自己實現了序列化和反序列化的方法
/** * Save the state of the <tt>ArrayList</tt> instance to a stream (that is, * serialize it). * *
View Code
構造器:
/** * 建立一個容量為initialCapacity的空的(size==0)物件陣列 */ public ArrayList(int initialCapacity) { super();//即父類protected AbstractList() {} if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity:" + initialCapacity); this.elementData = new Object[initialCapacity]; } /** * 預設初始化一個容量為10的物件陣列 */ public ArrayList() { this(10);//即上邊的public ArrayList(int initialCapacity){}構造器 }
在我們執行new ArrayList<String>()時,會呼叫上邊的無參構造器,創造一個容量為10的物件陣列。
在我們執行new ArrayList<String>(2)時,會呼叫上邊的public ArrayList(int initialCapacity),創造一個容量為2的物件陣列。
注意:
- 上邊有參構造器的super()方法是ArrayList父類AbstractList的構造方法,這個構造方法如下,是一個空構造方法:
protected AbstractList() { }
- 在實際使用中,如果我們能對所需的ArrayList的大小進行判斷,有兩個好處:
- 節省記憶體空間(eg.我們只需要放置兩個元素到陣列,new ArrayList<String>(2))
- 避免陣列擴容(下邊會講)引起的效率下降(eg.我們只需要放置大約37個元素到陣列,new ArrayList<String>(40))
2.2、往ArrayList中新增物件(常見的兩個方法add(E)和addAll(Collection<? extends E> c))
2.2.1、add(E)
strList2.add("hello");
ArrayList原始碼:
/** * 向elementData中新增元素 */ public boolean add(E e) { ensureCapacity(size + 1);//確保物件陣列elementData有足夠的容量,可以將新加入的元素e加進去 elementData[size++] = e;//加入新元素e,size加1 return true; }
/** * 確保陣列的容量足夠存放新加入的元素,若不夠,要擴容 */ public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length;//獲取陣列大小(即陣列的容量) //當陣列滿了,又有新元素加入的時候,執行擴容邏輯 if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3) / 2 + 1;//新容量為舊容量的1.5倍+1 if (newCapacity < minCapacity)//如果擴容後的新容量還是沒有傳入的所需的最小容量大或等於(主要發生在addAll(Collection<? extends E> c)中) newCapacity = minCapacity;//新容量設為最小容量 elementData = Arrays.copyOf(elementData, newCapacity);//複製新容量 } }
在上述程式碼的擴容結束後,呼叫了Arrays.copyOf(elementData, newCapacity)方法,這個方法中:對於我們這裡而言,先建立了一個新的容量為newCapacity的物件陣列,然後使用System.arraycopy()方法將舊的物件陣列複製到新的物件陣列中去了。
注意:
- modCount變數用於在遍歷集合(iterator())時,檢測是否發生了add、remove操作。
2.2.2、addAll(Collection<? extends E> c)
使用方式:
List<String> strList = new ArrayList<String>(); strList.add("jigang"); strList.add("nana"); strList.add("nana2"); List<String> strList2 = new ArrayList<String>(2); strList2.addAll(strList);
原始碼:
/** * 將c全部加入elementData */ public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray();//將c集合轉化為物件陣列a int numNew = a.length;//獲取a物件陣列的容量 ensureCapacity(size + numNew);//確保物件陣列elementData有足夠的容量,可以將新加入的a物件陣列加進去 System.arraycopy(a, 0, elementData, size, numNew);//將物件陣列a拷貝到elementData中去 size += numNew;//重新設定elementData中已加入的元素的個數 return numNew != 0;//若加入的是空集合則返回false }
注意:
- 從上述程式碼可以看出,若加入的c是空集合,則返回false
- ensureCapacity(size + numNew);這個方法在上邊講
- System.arraycopy()方法定義如下:
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
將陣列src從下標為srcPos開始拷貝,一直拷貝length個元素到dest陣列中,在dest陣列中從destPos開始加入先的srcPos陣列元素。
除了以上兩種常用的add方法外,還有如下兩種:
2.2.3、add(int index, E element)
/** * 在特定位置(只能是已有元素的陣列的特定位置)index插入元素E */ public void add(int index, E element) { //檢查index是否在已有的陣列中 if (index > size || index < 0) throw new IndexOutOfBoundsException("Index:"+index+",Size:"+size); ensureCapacity(size + 1);//確保物件陣列elementData有足夠的容量,可以將新加入的元素e加進去 System.arraycopy(elementData, index, elementData, index+1, size-index);//將index及其後邊的所有的元素整塊後移,空出index位置 elementData[index] = element;//插入元素 size++;//已有陣列元素個數+1 }
注意:
- index<=size才行,並不是index<elementData.length
2.2.4、set(int index, E element)
/** * 更換特定位置index上的元素為element,返回該位置上的舊值 */ public E set(int index, E element) { RangeCheck(index);//檢查索引範圍 E oldValue = (E) elementData[index];//舊值 elementData[index] = element;//該位置替換為新值 return oldValue;//返回舊值 }
2.3、獲取ArrayList中的單個物件(get(int index))
實現方式:
ArrayList<String> strList2 = new ArrayList<String>(2); strList2.add("hello"); strList2.add("nana"); strList2.add("nana2"); System.out.println(strList2.get(0));
原始碼:
/** * 按照索引查詢物件E */ public E get(int index) { RangeCheck(index);//檢查索引範圍 return (E) elementData[index];//返回元素,並將Object轉型為E }
/** * 檢查索引index是否超出size-1 */ private void RangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException("Index:"+index+",Size:"+size); }
注:這裡對index進行了索引檢查,是為了將異常內容寫的詳細一些並且將檢查的內容縮小(index<0||index>=size,注意這裡的size是已儲存元素的個數);
事實上不檢查也可以,因為對於陣列而言,如果index不滿足要求(index<0||index>=length,注意這裡的length是陣列的容量),都會直接丟擲陣列越界異常,而假設陣列的length為10,當前的size是2,你去計算array[9],這時候得出是null,這也是上邊get為什麼減小檢查範圍的原因。
2.4、刪除ArrayList中的物件
2.4.1、remove(Object o)
使用方式:
strList2.remove("hello");
原始碼:
/** * 從前向後移除第一個出現的元素o */ public boolean remove(Object o) { if (o == null) {//移除物件陣列elementData中的第一個null for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else {//移除物件陣列elementData中的第一個o for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } /* * 刪除單個位置的元素,是ArrayList的私有方法 */ 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; //將最後一個元素設為null,在下次gc的時候就會回收掉了 }
2.4.2、remove(int index)
使用方式:
strList2.remove(0);
原始碼:
/** * 刪除指定索引index下的元素,返回被刪除的元素 */ public E remove(int index) { RangeCheck(index);//檢查索引範圍 E oldValue = (E) elementData[index];//被刪除的元素 fastRemove(index); return oldValue; }
注意:
- remove(Object o)需要遍歷陣列,remove(int index)不需要,只需要判斷索引符合範圍即可,所以,通常:後者效率更高。
2.5、判斷物件是否存在於ArrayList中(contains(E))
原始碼:
/** * 判斷動態陣列是否包含元素o */ public boolean contains(Object o) { return indexOf(o) >= 0; } /** * 返回第一個出現的元素o的索引位置 */ public int indexOf(Object o) { if (o == null) {//返回第一個null的索引 for (int i = 0; i < size; i++) if (elementData[i] == null) return i; } else {//返回第一個o的索引 for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1;//若不包含,返回-1 } /** * 返回最後一個出現的元素o的索引位置 */ 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; }
注意:
- indexOf(Object o)返回第一個出現的元素o的索引;lastIndexOf(Object o)返回最後一個o的索引
2.6、遍歷ArrayList中的物件(iterator())
使用方式:
List<String> strList = new ArrayList<String>(); strList.add("jigang"); strList.add("nana"); strList.add("nana2"); Iterator<String> it = strList.iterator(); while (it.hasNext()) { System.out.println(it.next()); }
原始碼:iterator()方法是在AbstractList中實現的,該方法返回AbstractList的一個內部類Itr物件
public Iterator<E> iterator() { return new Itr();//返回一個內部類物件 }
Itr:
private class Itr implements Iterator<E> { int cursor = 0;//標記位:標記遍歷到哪一個元素 int expectedModCount = modCount;//標記位:用於判斷是否在遍歷的過程中,是否發生了add、remove操作 //檢測物件陣列是否還有元素 public boolean hasNext() { return cursor != size();//如果cursor==size,說明已經遍歷完了,上一次遍歷的是最後一個元素 } //獲取元素 public E next() { checkForComodification();//檢測在遍歷的過程中,是否發生了add、remove操作 try { E next = get(cursor++); return next; } catch (IndexOutOfBoundsException e) {//捕獲get(cursor++)方法的IndexOutOfBoundsException checkForComodification(); throw new NoSuchElementException(); } } //檢測在遍歷的過程中,是否發生了add、remove等操作 final void checkForComodification() { if (modCount != expectedModCount)//發生了add、remove操作,這個我們可以檢視add等的原始碼,發現會出現modCount++ throw new ConcurrentModificationException(); } }
遍歷的整個流程結合"使用方式"與"Itr的註釋"來看。注:上述的Itr我去掉了一個此時用不到的方法和屬性。
三、總結
- ArrayList基於陣列方式實現,無容量的限制(會擴容)
- 新增元素時可能要擴容(所以最好預判一下),刪除元素時不會減少容量(若希望減少容量,trimToSize()),刪除元素時,將刪除掉的位置元素置為null,下次gc就會回收這些元素所佔的記憶體空間。
- 執行緒不安全
- add(int index, E element):新增元素到陣列中指定位置的時候,需要將該位置及其後邊所有的元素都整塊向後複製一位
- get(int index):獲取指定位置上的元素時,可以通過索引直接獲取(O(1))
- remove(Object o)需要遍歷陣列
- remove(int index)不需要遍歷陣列,只需判斷index是否符合條件即可,效率比remove(Object o)高
- contains(E)需要遍歷陣列
做以上總結,主要是為了與後邊的LinkedList作比較。
elementData
相關推薦
第二章 ArrayList原始碼解析
一、對於ArrayList需要掌握的七點內容 ArrayList的建立:即構造器 往ArrayList中新增物件:即add(E)方法 獲取ArrayList中的單個物件:即get(int index)方法 刪除ArrayList中的物件:即remove(E)方法 遍歷ArrayList中的物件:
DelayQueue阻塞佇列第二章:原始碼解析
DelayQueue阻塞佇列系列文章 介紹 DelayQueue是java併發包中提供的延遲阻塞佇列,業務場景一般是下單後多長時間過期,定時執行程式等 1-DelayQueue的組成結構 /** * DelayQueue佇列繼承了AbstractQueue,
第二章 ConcurrentHashMap原始碼解析
注:在看這篇文章之前,如果對HashMap的層不清楚的話,建議先去看看HashMap原始碼解析。 1、對於ConcurrentHashMap需要掌握以下幾點 Map的建立:ConcurrentHashMap() 往Map中新增鍵值對:即put(Object key, Object value)方
Java——ArrayList原始碼解析
以下針對JDK 1.8版本中的ArrayList進行分析。 概述 ArrayList基於List介面實現的大小可變的陣列。其實現了所有可選的List操作,並且元素允許為任意型別,包括null元素。除了實現List介面,此類還提供了操作內部用於儲存列表陣列大小
Java集合框架——jdk 1.8 ArrayList 原始碼解析 System.arraycopy 怎麼使用的?
前言:作為菜鳥,需要經常回頭鞏固一下基礎知識,今天看看 jdk 1.8 的原始碼,這裡記錄 ArrayList 的實現。 一、簡介 ArrayList 是有序的集合; 底層採用陣列實現對資料的增刪查改; 不是執行緒安全的; 有自動擴容的功能。 二、類圖
Java原始碼解析系列(二)ArrayList原始碼解析
備註:以下都是基於JDK8 原始碼分析 ArrayList簡介 ArrayList 是一個數組佇列,相當於 動態陣列。與Java中的陣列相比,它的容量能動態增長。它繼承於AbstractList,實現了List, RandomAccess, Clonea
Java集合(一)-ArrayList原始碼解析
ArrayList是什麼? ArrayList是Java集合中的一份子,它的內部結構實為陣列並封裝了一些方法和特性方便使用者,為什麼不用陣列呢?因為ArrayList更加方便:如果你再不確定元素個數的情況下建立一個數組,那麼在陣列容量不夠的情況下需要手動擴容(也就是重新初始
ArrayList原始碼解析
概要 ArrayList 是一個動態陣列,它是執行緒不安全的,允許元素為null。 其底層資料結構依然是陣列,它實現了List, RandomAccess, Cloneable, java.io.Serializable介面,其中RandomAccess代表了其
資料結構-ArrayList原始碼解析
一、ArrayList簡介 1.1、ArrayList概述 1)ArrayList是個動態陣列,它是基於陣列實現的List類。 2)該類封裝了一個動態再分配的Object[]陣列,每一個類物件都有一個capacity屬性,表示它們所封裝的Object[]陣列的長度,當向ArrayList中新增元
第十一章 AtomicInteger原始碼解析
1、原子類 可以實現一些原子操作 基於CAS 下面就以AtomicInteger為例。 2、AtomicInteger 在沒有AtomicInteger之前,對於一個Integer的執行緒安全操作,是需要使用同步鎖來實現的,當然現在也可以通過ReentrantLock來實現,但是最好最方
第三章 CopyOnWriteArrayList原始碼解析
注:在看這篇文章之前,如果對ArrayList底層不清楚的話,建議先去看看ArrayList原始碼解析。 1、對於CopyOnWriteArrayList需要掌握以下幾點 建立:CopyOnWriteArrayList() 新增元素:即add(E)方法 獲取單個物件:即get(int)方法
第五章 HashMap原始碼解析
5.1、對於HashMap需要掌握以下幾點 Map的建立:HashMap() 往Map中新增鍵值對:即put(Object key, Object value)方法 獲取Map中的單個物件:即get(Object key)方法 刪除Map中的物件:即remove(Object key)方法 判斷
Java集合(一):ArrayList原始碼解析
前言今天來介紹下ArrayList,在集合框架整體框架一章中,我們介紹了List介面,ArrayList繼承了AbstractList,實現了List。ArrayList在工作中經常用到,所以要弄懂這個類是極其重要的。構造圖如下:藍色線條:繼承綠色線條:介面實現正文Array
【JDk原始碼解析之一】ArrayList原始碼解析
1.ArrayList的繼承關係如圖所示: 2.巨集觀上說,ArrayList是基於動態陣列實現的,陣列具有按索引查詢的特性,所以訪問很快,適合經常查詢的資料。 3.具體原始碼解析。 為什麼說ArrayList是動態陣列,這個可以看它的建構函式。如下圖所示,有兩個構造方法
ArrayList原始碼解析[一]
歡迎轉載,轉載煩請註明出處,謝謝。 https://www.cnblogs.com/sx-wuyj/p/11177257.html 自己學習ArrayList原始碼的一些心得記錄.. 1.1 ArrayList的體系 Iterable : iterable接口裡定義了返回iterator的方法,相當於對
ArrayList原始碼解析(二)
歡迎轉載,轉載煩請註明出處,謝謝。 https://www.cnblogs.com/sx-wuyj/p/11177257.html 自己學習ArrayList原始碼的一些心得記錄. 繼續上一篇,ArrayList原始碼解析(一) addll(Collection<? extends E> c)
集合-ArrayList 原始碼解析
ArrayList是一種以陣列實現的List,與陣列相比,它具有動態擴充套件的能力,因此也可稱之為動態陣列。 類圖 ArrayList實現了List, RandomAccess, Cloneable, java.io.Serializable等介面。 ArrayList實現了List,提供了基礎的新增、刪除
《spring原始碼深度解析》第二章
1:容器基本用法 2:功能分析 3:工程搭建 4:spring的結構組成(beans包的層級結構、核心類介紹) 5:容器的基礎XMLBeanFactory(配置檔案封裝、載入
ArrayList實現原理以及原始碼解析(補充JDK1.7,1.8)
ArrayList實現原理以及原始碼解析(補充JDK1.7,1.8) ArrayList的基本知識在上一節已經討論過,這節主要看ArrayList在JDK1.6到1.8的一些實現變化。 JDK版本不一樣,ArrayList類的原始碼也不一樣。 1、ArrayList類結構:
ArrayList實現原理以及原始碼解析(JDK1.6)
ArrayList實現原理以及原始碼解析(JDK1.6) 1、ArrayList ArrayList是基於陣列實現的,是一個動態陣列,其容量能自動增長,類似於C語言中的動態申請記憶體,動態增長記憶體。 ArrayList不是執行緒安全的,只能用在單執行緒環境下。