摘要:概述為了彌補(bǔ)普通數(shù)組無法自動(dòng)擴(kuò)容的不足提供了集合類其中就對數(shù)組進(jìn)行了封裝使其可以自動(dòng)的擴(kuò)容或縮小長度因?yàn)槭菍?shù)據(jù)進(jìn)行了封裝所以底層存儲(chǔ)結(jié)構(gòu)是數(shù)組結(jié)構(gòu)可以想象的到數(shù)組長度的自動(dòng)變化必須需要開辟新內(nèi)存然后進(jìn)行數(shù)組元素的拷貝因?yàn)閿?shù)組所以也就具有數(shù)
[TOC]
1. 概述為了彌補(bǔ)普通數(shù)組無法自動(dòng)擴(kuò)容的不足, Java提供了集合類, 其中ArrayList就對數(shù)組進(jìn)行了封裝, 使其可以自動(dòng)的擴(kuò)容或縮小長度.
因?yàn)槭菍?shù)據(jù)進(jìn)行了封裝, 所以底層存儲(chǔ)結(jié)構(gòu)是數(shù)組結(jié)構(gòu). 可以想象的到, 數(shù)組長度的自動(dòng)變化必須需要開辟新內(nèi)存, 然后進(jìn)行數(shù)組元素的拷貝.
因?yàn)閿?shù)組, 所以ArrayList也就具有數(shù)組的一些性, 如支持隨機(jī)訪問.
2. 存儲(chǔ)結(jié)構(gòu)第一節(jié)已經(jīng)說了, 它是一種自動(dòng)數(shù)組, 內(nèi)部存儲(chǔ)結(jié)構(gòu)就是數(shù)組了.
Object[] elementData;3. 構(gòu)造方法
先從構(gòu)造方法開始分析.
3-1. 無參構(gòu)造方法/** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
注釋說, 構(gòu)造一個(gè)容量為10的空的list.
但是這里我們并沒有看到數(shù)組的容量變?yōu)?0, 而是一個(gè)空的數(shù)組 DEFAULTCAPACITY_EMPTY_ELEMENTDATA.
那么什么時(shí)候會(huì)被初始化為10的數(shù)組呢?答案是有元素被加入時(shí)(add方法).
3-2. 帶有初始化容量的構(gòu)造方法/** * Constructs an empty list with the specified initial capacity. * * @param initialCapacity the initial capacity of the list * @throws IllegalArgumentException if the specified initial capacity * is negative */ public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } }
這個(gè)就比無參構(gòu)造方法清晰多了, 直接建立一個(gè)initialCapacity大小的數(shù)組.
3-3. 帶有初始化元素的構(gòu)造方法/** * Constructs a list containing the elements of the specified * collection, in the order they are returned by the collection"s * iterator. * * @param c the collection whose elements are to be placed into this list * @throws NullPointerException if the specified collection is null */ public ArrayList(Collection extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
這里的意思傳入一個(gè)集合類(Collection的子類即可), 轉(zhuǎn)為list.
Collection有一個(gè)子抽象類, 默認(rèn)實(shí)現(xiàn)了toArray();, 如果子類比較特殊需要, 進(jìn)行重寫即可.
4. 集合的操作集合類的重要功能就是進(jìn)行交互(元素的存儲(chǔ)、修改、刪除、遍歷等).
4-1. 添加內(nèi)部有四種方式的添加:
直接添加
指定位置添加
添加全部
在指定位置添加全部
4-1-1. 普通添加(在尾端添加元素)因?yàn)槭莿?dòng)態(tài)數(shù)組, 所以元素添加時(shí)需要校驗(yàn)數(shù)組空間是否足夠, 如果不足, 需要進(jìn)行數(shù)組的擴(kuò)容(關(guān)于擴(kuò)容看第5節(jié)).
源碼如下:
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return true (as specified by {@link Collection#add}) */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }4-1-2. 指定位置添加
在指定位置添加, 必然會(huì)影響到該位置的元素以及后續(xù)元素, 對于數(shù)組這種數(shù)據(jù)結(jié)構(gòu), 只能進(jìn)行元素的后移了.
這就體現(xiàn)出數(shù)組這種數(shù)據(jù)結(jié)構(gòu)的不足了: 對元素的修改不太擅長.
同普通添加元素一樣, 需要校驗(yàn)數(shù)組容量數(shù)組足夠, 不過在校驗(yàn)之前還要檢查一下入?yún)⒌脑匚恢?index)是否在范圍內(nèi).
然后進(jìn)行數(shù)組元素的移動(dòng).
源碼如下:
/** * Inserts the specified element at the specified position in this * list. Shifts the element currently at that position (if any) and * any subsequent elements to the right (adds one to their indices). * * @param index index at which the specified element is to be inserted * @param element element to be inserted * @throws IndexOutOfBoundsException {@inheritDoc} */ public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }4-1-3. 添加一個(gè)集合中的全部元素
與構(gòu)造方法類似, 這里也使用了toArray();
只不過需要進(jìn)行數(shù)組大小的校驗(yàn)擴(kuò)容, 然后進(jìn)行元素拷貝.
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; return numNew != 0; }4-1-4. 在指定位置添加一個(gè)集合中的全部元素
通過上面的說明, 這個(gè)方法就很容易懂了.
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; }4-2. 修改
修改操作比較簡單, 就是給數(shù)組的某個(gè)下標(biāo)重新賦值.
只有一個(gè)方法: E set(int index, E element)
將指定位置上的元素進(jìn)行更新, 會(huì)對index進(jìn)行越界檢查.
4-3. 刪除刪除操作也意味著數(shù)組中會(huì)有元素移動(dòng), 除非刪除的是最后一個(gè)元素.
刪除方法有一下幾個(gè):
E remove(int index)
boolean remove(Object o)
boolean removeAll(Collection> c)
boolean retainAll(Collection> c)
4-3-1. 通過下標(biāo)進(jìn)行刪除刪除指定位置上的元素, 如果刪除的不是最后一個(gè)元素, 則要進(jìn)行元素的移動(dòng).
public E remove(int index) { rangeCheck(index); // 檢查下標(biāo)是否越界 modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; // 最后 -1 是為了數(shù)組下標(biāo)不越界 if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; }4-3-2. 通過對象進(jìn)行刪除
刪除數(shù)組中的某個(gè)元素, 會(huì)分為兩種情況: null OR !null.
找到元素之后會(huì)使用fastRemove(index)進(jìn)行刪除.
源碼如下:
// 刪除成功返回true, 失敗false 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; } // 快速刪除指定下標(biāo)的元素, 無越界檢查 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 }4-3-3. 刪除集合中的所有元素
傳入一個(gè)集合c, 如果集合c中包含本集合中的元素, 則對改元素進(jìn)行處理, 這里利用了一個(gè)complement屬性, 來決定含有的元素是刪除還是保留.
簡單說一下批量刪除/保留操作, 把匹配的元素(c.contains(elementData[r]) == complement)進(jìn)行保留.
然后對不需要保留的下標(biāo)賦予null值.
public boolean removeAll(Collection> c) { Objects.requireNonNull(c); return batchRemove(c, false); } private boolean batchRemove(Collection> c, boolean complement) { final Object[] elementData = this.elementData; int r = 0, w = 0; boolean modified = false; try { for (; r < size; r++) // 如果complement為false, 則c集合包含本集合中的元素, 則不進(jìn)行操作, 這就是保留不屬于c集合中的所有元素. // 這就是 4-3-4. boolean retainAll(Collection> c) if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { // 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; }4-4. 查找
這部分就不貼源碼了, 很容易理解.
查找可以分為兩種情況:
通過下標(biāo)直接定位元素
通過元素進(jìn)行定位下標(biāo)
包含的方法:
boolean contains(Object o)
int indexOf(Object o)
int lastIndexOf(Object o)
E elementData(int index)
E get(int index)
contains(Object o)方法使用了indexOf(Object o)作為底層實(shí)現(xiàn), 我們需要了解indexOf(Object o)的底層實(shí)現(xiàn).
indexOf(Object o)是從數(shù)組的頭開始查找, 查找到相同元素時(shí), 進(jìn)行返回, 而lastIndexOf(Object o)正好相反, 從數(shù)組的尾開始查找, 查找到相同元素時(shí)進(jìn)行返回.
可以使用indexOf和lastIndexOf進(jìn)行返回值的比較, 如果相等, 說明該元素在數(shù)組中唯一(前提是返回非-1).
elementData(int index)則是內(nèi)部方法, 獲取指定下標(biāo)處的元素.
而get(int index)內(nèi)部調(diào)用了element(int index), 調(diào)用之前會(huì)進(jìn)行下標(biāo)越界的校驗(yàn).
4-5. 遍歷遍歷分為三種方式:
普通for
foreach
iterator
至于三種方式的性能, 自己測試吧, 哈哈哈
重點(diǎn): 只有某種情況下的普通for和iterator可以在循環(huán)的同事進(jìn)行元素的刪除.
例如:
普通for// 該情況下不會(huì)發(fā)生異常 for (int i = 0; i < list.size(); i++) { String s = list.get(i); // do something... list.remove(i); } // 這種情況會(huì)出現(xiàn)越界問題 int size = list.size(); for (int i = 0; i < size; i++) { String s = list.get(i); // do something... list.remove(i); }
第一種情況下, for循環(huán)的終止條件會(huì)隨著數(shù)組元素的移除不斷的變化.
第二種情況下, for循環(huán)的種植條件不會(huì)變化.
for (String s : list) { // do something... list.remove(s); }
這種方式會(huì)發(fā)生ConcurrentModificationException, 因?yàn)锳rrayList內(nèi)部有一個(gè)屬性為modCount, 每當(dāng)數(shù)組中的元素發(fā)生變化是, 該數(shù)值會(huì)增1, 而foreach形式的循環(huán)編譯后會(huì)變?yōu)?/p>
Iterator var2 = list.iterator(); while(var2.hasNext()) { String s = (String)var2.next(); list.remove(s); }
這種形式. 因?yàn)锳rrayList內(nèi)部的Iterator也維護(hù)了一個(gè)屬性expectedModCount來標(biāo)識(shí)數(shù)組元素的變化, 初始化為modCount, 如果modCount與expectedModCount不一致的話, 就會(huì)拋出這個(gè)異常.
iteratorIteratoriterator = list.iterator(); while (iterator.hasNext()) { // do something... iterator.remove(); }
foreach時(shí)我們直接調(diào)用了list.remove(s);方法, 該方法只會(huì)改變modCount的值, 并不會(huì)改變expectedModCount的值, 相反, 使用Iterator提供的remove方法則會(huì)對兩個(gè)值一起修改.
5. 擴(kuò)容方式首先記住一點(diǎn): 每次會(huì)擴(kuò)容原數(shù)組的 1.5倍(正常情況下).
非正常情況當(dāng)前是兩端的情況:
初始化時(shí)第一次擴(kuò)容會(huì)擴(kuò)容到初始化容量(DEFAULT_CAPACITY)
當(dāng)容量達(dá)到最大值時(shí)不會(huì)遵循這個(gè)規(guī)律
擴(kuò)容方式: 在每次添加新元素時(shí), 會(huì)調(diào)用擴(kuò)容代碼, 擴(kuò)容方式還是比較簡單的. 只是進(jìn)行了一些防止溢出的判斷.
// 進(jìn)行擴(kuò)容調(diào)用 private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } // 計(jì)算是使用 DEFAULT_CAPACITY 還是傳入的 minCapacity private static int calculateCapacity(Object[] elementData, int minCapacity) { // 當(dāng)使用 new ArrayList() 創(chuàng)建實(shí)例時(shí), 數(shù)組的默認(rèn)容量為10就是在這里產(chǎn)生的. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } // 進(jìn)行一次modCount的修改, 表示數(shù)組元素發(fā)生了變動(dòng) private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code // 防止元素溢出 if (minCapacity - elementData.length > 0) grow(minCapacity); } /** * 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; /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; // 獲取舊數(shù)組長度 int newCapacity = oldCapacity + (oldCapacity >> 1); // 計(jì)算出新數(shù)組長度 oldCapacity + oldCapacity / 2. if (newCapacity - minCapacity < 0) // 如果計(jì)算出的長度比傳入的長度小, 則使用傳入的長度作為新數(shù)組長度. newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) // 如果新數(shù)組長度比MAX_ARRAY_SIZE, 進(jìn)行溢出的校驗(yàn) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: // 進(jìn)行數(shù)組的拷貝 elementData = Arrays.copyOf(elementData, newCapacity); } // 如果minCapacity比MAX_ARRAY_SIZE大, 就取Integer.MAX_VALUE的值作為新數(shù)組長度, 否則使用MAX_ARRAY_SIZE // 也就是傳入的長度, 而非通過原數(shù)組計(jì)算出來的長度. private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }6. 總結(jié)
ArrayList就是一個(gè)動(dòng)態(tài)數(shù)組.
ArrayList的擴(kuò)容操作必然會(huì)進(jìn)行內(nèi)存的申請以及數(shù)組元素的拷貝.
實(shí)例化時(shí)盡可能的確定好數(shù)組中元素的數(shù)量, 避免發(fā)生擴(kuò)容.
使用List
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/72676.html
摘要:需要注意的是,通過構(gòu)造函數(shù)定義初始量是動(dòng)態(tài)數(shù)組的實(shí)際大小。帶容量的構(gòu)造函數(shù)新建一個(gè)容量為的數(shù)組默認(rèn)構(gòu)造函數(shù),默認(rèn)為空構(gòu)造一個(gè)包含指定元素的第一個(gè)構(gòu)造方法使用提供的來初始化數(shù)組的大小。 前言 今天介紹經(jīng)常使用的一個(gè)Java集合類——ArrayList(基于JDK1.8.0_121)。ArrayList在工作和日常面試中經(jīng)常被使用或者提到。總的來說,工作中使用ArrayList主要是因?yàn)閯?dòng)...
摘要:源碼和多線程安全問題分析在分析線程安全問題之前,我們線對此類的源碼進(jìn)行分析,找出可能出現(xiàn)線程安全問題的地方,然后代碼進(jìn)行驗(yàn)證和分析。即當(dāng)多線程調(diào)用方法的時(shí)候會(huì)出現(xiàn)元素覆蓋的問題。 1.ArrayList源碼和多線程安全問題分析 在分析ArrayList線程安全問題之前,我們線對此類的源碼進(jìn)行分析,找出可能出現(xiàn)線程安全問題的地方,然后代碼進(jìn)行驗(yàn)證和分析。 1.1 數(shù)據(jù)結(jié)構(gòu) ArrayLi...
摘要:部分源碼分析小記底層數(shù)據(jù)結(jié)構(gòu)底層的數(shù)據(jù)結(jié)構(gòu)就是數(shù)組,數(shù)組元素類型為類型,即可以存放所有類型數(shù)據(jù)。初始容量大于初始化元素?cái)?shù)組新建一個(gè)數(shù)組初始容量為為空對象數(shù)組初始容量小于,拋出異常無參構(gòu)造函數(shù)。 JDK1.8 ArrayList部分源碼分析小記 底層數(shù)據(jù)結(jié)構(gòu) 底層的數(shù)據(jù)結(jié)構(gòu)就是數(shù)組,數(shù)組元素類型為Object類型,即可以存放所有類型數(shù)據(jù)。我們對ArrayList類的實(shí)例的所有的操作底層都...
摘要:表明該類具有序列化功能。關(guān)鍵屬性默認(rèn)初始容量大小指定該容量為時(shí),返回該空數(shù)組。構(gòu)造一個(gè)包含指定的元素的列表,這些元素是按照該的迭代器返回它們的順序排列的。對擴(kuò)容后的容量進(jìn)行判斷,如果大于允許的最大容量,則將容量再次調(diào)整為。 總覽 showImg(https://segmentfault.com/img/bVbsm9v?w=1232&h=643); 底層:ArrayList底層是一個(gè)數(shù)...
摘要:源碼分析類的實(shí)現(xiàn)接口及繼承父類和和都實(shí)現(xiàn)了接口。這個(gè)接口的作用是實(shí)現(xiàn)它能夠支持快速隨機(jī)訪問。在取出值的時(shí)候利用范型轉(zhuǎn)為聲明的類型。如果等于則初始化為空如果小于則拋出異常。并且設(shè)置為傳入的大小。常用方法解析的元素?cái)?shù)方法很簡單直接返回值的大小。 ArrayList源碼分析 類的實(shí)現(xiàn)接口及繼承父類 public class ArrayList extends AbstractList. i...
摘要:同步眾所周知,是同步的而不是,在一些必要的方法上都加了關(guān)鍵字,但是這也會(huì)加大系統(tǒng)開銷。中有一個(gè)方法用來返回一個(gè),以匿名內(nèi)部類的方式實(shí)現(xiàn)的接口和類似,都用作于對集合進(jìn)行迭代,不過沒有刪除功能,已經(jīng)被取代。還有是的,但不是,這一點(diǎn)很重要。 在上篇文章ArrayList源碼淺析中分析了一下 ArrayList的源碼和一些重要方法,現(xiàn)在對比 ArrayList,總結(jié)一下 Vector和 Arr...
閱讀 3062·2021-10-12 10:12
閱讀 1569·2021-09-09 11:39
閱讀 1845·2019-08-30 15:44
閱讀 2339·2019-08-29 15:23
閱讀 2898·2019-08-29 15:18
閱讀 2960·2019-08-29 13:02
閱讀 2688·2019-08-26 18:36
閱讀 733·2019-08-26 12:08