摘要:源碼分析默認容量默認容量為,也就是通過創建時的默認容量。集合中元素的個數真正存儲元素的個數,而不是數組的長度。方法刪除指定元素值的元素,時間復雜度為。方法求兩個集合的交集。
簡介
ArrayList是一種以數組實現的List,與數組相比,它具有動態擴展的能力,因此也可稱之為動態數組。
繼承體系ArrayList實現了List, RandomAccess, Cloneable, java.io.Serializable等接口。
ArrayList實現了List,提供了基礎的添加、刪除、遍歷等操作。
ArrayList實現了RandomAccess,提供了隨機訪問的能力。
ArrayList實現了Cloneable,可以被克隆。
ArrayList實現了Serializable,可以被序列化。
源碼分析/** * 默認容量, 默認容量為10,也就是通過new ArrayList()創建時的默認容量。 */ private static final int DEFAULT_CAPACITY = 10; /** * 空數組,如果傳入的容量為0時使用, 通過new ArrayList(0)創建時用的是這個空數組。 */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * 空數組,傳傳入容量時使用,添加第一個元素的時候會重新初始為默認容量大小 * 這種是通過new ArrayList()創建時用的是這個空數組, * 與EMPTY_ELEMENTDATA的區別是在添加第一個元素時使用這個空數組的會初始化為DEFAULT_CAPACITY(10)個元素。 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * 存儲元素的數組 * 真正存放元素的地方,使用transient是為了不序列化這個字段。 */ transient Object[] elementData; // non-private to simplify nested class access /** * 集合中元素的個數 * 真正存儲元素的個數,而不是elementData數組的長度。 */ private int size;ArrayList(int initialCapacity)構造方法
public ArrayList(int initialCapacity) { if (initialCapacity > 0) { // 如果傳入的初始容量大于0,就新建一個數組存儲元素 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { // 如果傳入的初始容量等于0,使用空數組EMPTY_ELEMENTDATA this.elementData = EMPTY_ELEMENTDATA; } else { // 如果傳入的初始容量小于0,拋出異常 throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); } }ArrayList()構造方法
public ArrayList() { // 如果沒有傳入初始容量,則使用空數組DEFAULTCAPACITY_EMPTY_ELEMENTDATA // 使用這個數組是在添加第一個元素的時候會擴容到默認大小10 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }ArrayList 構造方法
/** * 把傳入集合的元素初始化到ArrayList中 */ public ArrayList(Collection extends E> c) { // 集合轉數組 elementData = c.toArray(); if ((size = elementData.length) != 0) { // 檢查c.toArray()返回的是不是Object[]類型,如果不是,重新拷貝成Object[].class類型 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // 如果c的空集合,則初始化為空數組EMPTY_ELEMENTDATA this.elementData = EMPTY_ELEMENTDATA; } }
這里 c.toArray(); 因為返回的有可能不是Object[]類型,請看下面的代碼:
class MyList extends ArrayListadd(E e)方法{ /** * 子類重寫父類的方法,返回值可以不一樣 * 但這里只能用數組類型,換成Object就不行 * 應該算是java本身的bug */ @Override public String[] toArray() { // 為了方便舉例直接寫死 return new String[]{"1", "2", "3"}; } }
添加元素到末尾,平均時間復雜度為O(1)。
public boolean add(E e) { // 檢查是否需要擴容 ensureCapacityInternal(size + 1); // 把元素插入到最后一位 elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private static int calculateCapacity(Object[] elementData, int minCapacity) { // 如果是空數組DEFAULTCAPACITY_EMPTY_ELEMENTDATA,就初始化為默認大小10 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0) // 擴容 grow(minCapacity); } private void grow(int minCapacity) { int oldCapacity = elementData.length; // 新容量為舊容量的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); // 如果新容量發現比需要的容量還小,則以需要的容量為準 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 如果新容量已經超過最大容量了,則使用最大容量 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 以新容量拷貝出來一個新數組 elementData = Arrays.copyOf(elementData, newCapacity); }
檢查是否需要擴容;
如果elementData等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA則初始化容量大小為DEFAULT_CAPACITY;
新容量是老容量的1.5倍(oldCapacity + (oldCapacity >> 1)),如果加了這么多容量發現比需要的容量還小,則以需要的容量為準;
創建新容量的數組并把老數組拷貝到新數組;
add(int index, E element)方法添加元素到指定位置,平均時間復雜度為O(n)。
public void add(int index, E element) { // 檢查是否越界 rangeCheckForAdd(index); // 檢查是否需要擴容 ensureCapacityInternal(size + 1); // 將inex及其之后的元素往后挪一位,則index位置處就空出來了 System.arraycopy(elementData, index, elementData, index + 1, size - index); // 將元素插入到index的位置 elementData[index] = element; // 大小增1 size++; } private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
檢查索引是否越界;
檢查是否需要擴容;
把插入索引位置后的元素都往后挪一位;
在插入索引位置放置插入的元素;
大小加1;
addAll 方法求兩個集合的并集。
/** * 將集合c中所有元素添加到當前ArrayList中 */ public boolean addAll(Collection extends E> c) { // 將集合c轉為數組 Object[] a = c.toArray(); int numNew = a.length; // 檢查是否需要擴容 ensureCapacityInternal(size + numNew); // 將c中元素全部拷貝到數組的最后 System.arraycopy(a, 0, elementData, size, numNew); // 大小增加c的大小 size += numNew; // 如果c不為空就返回true,否則返回false return numNew != 0; }get(int index)方法
獲取指定索引位置的元素,時間復雜度為O(1)。
public E get(int index) { // 檢查是否越界 rangeCheck(index); // 返回數組index位置的元素 return elementData(index); } private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } E elementData(int index) { return (E) elementData[index]; }
檢查索引是否越界,這里只檢查是否越上界,如果越上界拋出IndexOutOfBoundsException異常,如果越下界拋出的是ArrayIndexOutOfBoundsException異常。
返回索引位置處的元素;
remove(int index)方法刪除指定索引位置的元素,時間復雜度為O(n)。
public E remove(int index) { // 檢查是否越界 rangeCheck(index); modCount++; // 獲取index位置的元素 E oldValue = elementData(index); // 如果index不是最后一位,則將index之后的元素往前挪一位 int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // 將最后一個元素刪除,幫助GC elementData[--size] = null; // clear to let GC do its work // 返回舊值 return oldValue; }
檢查索引是否越界;
獲取指定索引位置的元素;
如果刪除的不是最后一位,則其它元素往前移一位;
將最后一位置為null,方便GC回收;
返回刪除的元素。
可以看到,ArrayList刪除元素的時候并沒有縮容。
remove(Object o)方法刪除指定元素值的元素,時間復雜度為O(n)。
public boolean remove(Object o) { if (o == null) { // 遍歷整個數組,找到元素第一次出現的位置,并將其快速刪除 for (int index = 0; index < size; index++) // 如果要刪除的元素為null,則以null進行比較,使用== if (elementData[index] == null) { fastRemove(index); return true; } } else { // 遍歷整個數組,找到元素第一次出現的位置,并將其快速刪除 for (int index = 0; index < size; index++) // 如果要刪除的元素不為null,則進行比較,使用equals()方法 if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } private void fastRemove(int index) { // 少了一個越界的檢查 modCount++; // 如果index不是最后一位,則將index之后的元素往前挪一位 int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // 將最后一個元素刪除,幫助GC elementData[--size] = null; // clear to let GC do its work }
找到第一個等于指定元素值的元素;
快速刪除;
fastRemove(int index)相對于remove(int index)少了檢查索引越界的操作,可見jdk將性能優化到極致。
retainAll方法求兩個集合的交集。
public boolean retainAll(Collection> c) { // 集合c不能為null Objects.requireNonNull(c); // 調用批量刪除方法,這時complement傳入true,表示刪除不包含在c中的元素 return batchRemove(c, true); } /** * 批量刪除元素 * complement為true表示刪除c中不包含的元素 * complement為false表示刪除c中包含的元素 */ private boolean batchRemove(Collection> c, boolean complement) { final Object[] elementData = this.elementData; // 使用讀寫兩個指針同時遍歷數組 // 讀指針每次自增1,寫指針放入元素的時候才加1 // 這樣不需要額外的空間,只需要在原有的數組上操作就可以了 int r = 0, w = 0; boolean modified = false; try { // 遍歷整個數組,如果c中包含該元素,則把該元素放到寫指針的位置(以complement為準) for (; r < size; r++) if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { // 正常來說r最后是等于size的,除非c.contains()拋出了異常 if (r != size) { // 如果c.contains()拋出了異常,則把未讀的元素都拷貝到寫指針之后 System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { // 將寫指針之后的元素置為空,幫助GC for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; // 新大小等于寫指針的位置(因為每寫一次寫指針就加1,所以新大小正好等于寫指針的位置) size = w; modified = true; } } // 有修改返回true return modified; }
遍歷elementData數組;
如果元素在c中,則把這個元素添加到elementData數組的w位置并將w位置往后移一位;
遍歷完之后,w之前的元素都是兩者共有的,w之后(包含)的元素不是兩者共有的;
將w之后(包含)的元素置為null,方便GC回收;
removeAll求兩個集合的單方向差集,只保留當前集合中不在c中的元素,不保留在c中不在當前集體中的元素。
public boolean removeAll(Collection> c) { // 集合c不能為空 Objects.requireNonNull(c); // 同樣調用批量刪除方法,這時complement傳入false,表示刪除包含在c中的元素 return batchRemove(c, false); }總結
ArrayList內部使用數組存儲元素,當數組長度不夠時進行擴容,每次加一半的空間,ArrayList不會進行縮容;
ArrayList支持隨機訪問,通過索引訪問元素極快,時間復雜度為O(1);
ArrayList添加元素到尾部極快,平均時間復雜度為O(1);
ArrayList添加元素到中間比較慢,因為要搬移元素,平均時間復雜度為O(n);
ArrayList從尾部刪除元素極快,時間復雜度為O(1);
ArrayList從中間刪除元素比較慢,因為要搬移元素,平均時間復雜度為O(n);
ArrayList支持求并集,調用addAll(Collection extends E> c)方法即可;
ArrayList支持求交集,調用retainAll(Collection extends E> c)方法即可;
ArrayList支持求單向差集,調用removeAll(Collection extends E> c)方法即可;
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/75971.html
摘要:加載因子是哈希表在其容量自動增加之前可以達到多滿的一種尺度。當哈希表中的條目數超出了加載因子與當前容量的乘積時,則要對該哈希表進行操作即重建內部數據結構,從而哈希表將具有大約兩倍的桶數。成員變量每個對由封裝,存在了對象數組中。 雖是讀書筆記,但是如轉載請注明出處 http://segmentfault.com/blog/exploring/ .. 拒絕伸手復制黨 LinkedLis...
摘要:簡介是的線程安全版本,內部也是通過數組實現,每次對數組的修改都完全拷貝一份新的數組來修改,修改完了再替換掉老數組,這樣保證了只阻塞寫操作,不阻塞讀操作,實現讀寫分離。 簡介 CopyOnWriteArrayList是ArrayList的線程安全版本,內部也是通過數組實現,每次對數組的修改都完全拷貝一份新的數組來修改,修改完了再替換掉老數組,這樣保證了只阻塞寫操作,不阻塞讀操作,實現讀寫...
摘要:是棧,它繼承于。滿二叉樹除了葉結點外每一個結點都有左右子葉且葉子結點都處在最底層的二叉樹。沒有鍵值相等的節點。這是數據庫選用樹的最主要原因。 在我們學習Java的時候,很多人會面臨我不知道繼續學什么或者面試會問什么的尷尬情況(我本人之前就很迷茫)。所以,我決定通過這個開源平臺來幫助一些有需要的人,通過下面的內容,你會掌握系統的Java學習以及面試的相關知識。本來是想通過Gitbook的...
摘要:常用集合使用場景分析過年前的最后一篇,本章通過介紹,,,底層實現原理和四個集合的區別。和都是線程安全的,不同的是前者使用類,后者使用關鍵字。面試官會認為你是一個基礎扎實,內功深厚的人才到這里常用集合使用場景分析就結束了。 Java 常用List集合使用場景分析 過年前的最后一篇,本章通過介紹ArrayList,LinkedList,Vector,CopyOnWriteArrayList...
閱讀 3949·2021-11-22 13:53
閱讀 1676·2021-08-25 09:39
閱讀 2410·2019-08-29 18:36
閱讀 1469·2019-08-26 13:35
閱讀 1215·2019-08-26 11:57
閱讀 1678·2019-08-23 15:57
閱讀 803·2019-08-23 14:55
閱讀 1163·2019-08-23 14:51