歷史文章:
Collection 源碼分析
AbstractList 源碼分析html
ArrayList是一個數組隊列,至關於動態數組,與Java的數組對比,他的容量能夠動態改變。
List
,RandomAccess
,Cloneable
,Serializable
接口RandomAccess
接口,提供了隨機訪問功能Cloneable
接口,能被克隆Serializable
接口,支持序列化傳輸public ArrayList();//無元素默認爲0,有元素初始化默認容量爲10 public ArrayList(Collection<? extends E> c);//默認爲c這個集合的list(淺拷貝) public ArrayList(int initialCapacity);//設置一個初始化爲initialCapacity集合
注意點java
public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652) //解決bug問題,因爲其c.toArray()可能出現返回值不爲Object[]的錯誤,因此採用以下方法 if (elementData.getClass() != Object[].class) //使用數組拷貝來進行 elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
在這個方法中出現if (elementData.getClass() != Object[].class)
這樣一組判斷,查閱資料發現,這是一個bug才這麼判斷的地址,改問題已經在JDK9已經進行了修復了。設計模式
private static final long serialVersionUID = 8683452581122892189L; /** * 默認初始化容量10 */ 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 /** * 容器數據集大小 * * @serial */ private int size; /** * 該容器可以承受的最大容量 * 爲何是Integer.MAX_VALUE - 8; * 由於有些VM虛擬機會在一個數組中存儲一些頭部信息,因此採用這個值 * 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;
值得注意的點:數組
MAX_ARRAY_SIZE
設置爲Integer.MAX_VALUE - 8
是由於有些VM虛擬機會在數組中存儲一些頭部信息,從而佔用一些空間,因此-8縮小容器大小讓容器釋放多餘的空間,會觸發一次數組的變化緩存
/** * 縮小容器大小 * 例如當你一開始建立了一個100個的List可是你只使用了10個,想將這個容器縮減爲10個 */ public void trimToSize() { //這個參數和咱們併發控制的時候version一個味道,用於判斷是否併發修改的標誌1 modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } }
返回數據的淺克隆的實例安全
/** * 返回數據的淺克隆的實例 * * @return a clone of this <tt>ArrayList</tt> instance */ public Object clone() { try { //調用父類的克隆方法->Object的克隆方法 ArrayList<?> v = (ArrayList<?>) super.clone(); //拷貝數組,注意是直接經過Arrays的copyOf因此爲淺克隆 v.elementData = Arrays.copyOf(elementData, size); //設置併發version v.modCount = 0; return v; } catch (CloneNotSupportedException e) { //克隆異常則拋出error // this shouldn't happen, since we are Cloneable throw new InternalError(e); } }
一樣爲淺拷貝,拷貝出來的數組仍是會被改變的,該方法返回的爲Object[]數組併發
/** * 一樣爲淺拷貝,拷貝出來的數組仍是會被改變的 * 將List轉換成數組 * Demo[] cloneArr = (Demo[]) demos.toArray(); //ERROR * * @return an array containing all of the elements in this list in * proper sequence */ public Object[] toArray() { return Arrays.copyOf(elementData, size); }
/** * 追加一個元素在列表的最後面 * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { //這裏會致使其併發版本+1, // 由於須要先確認容器大小操做,並肯定是否須要擴容。 //對數據有修改,於是其併發版本也就會+1 ensureCapacityInternal(size + 1); // Increments modCount!! //設置值 elementData[size++] = e; return true; } /** * 在index位置後插入元素,並移動後面元素的位置 * 1. 須要對index後面的全部的元素index+1,須要拷貝工做產生 * 2. 若是你的List中有大量的這樣的插入工做建議採用 * @see LinkedList * * @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) { //校驗index rangeCheckForAdd(index); //肯定擴容權限 ensureCapacityInternal(size + 1); // Increments modCount!! //數組拷貝,耗時工做 System.arraycopy(elementData, index, elementData, index + 1, size - index); //賦值 elementData[index] = element; //長度 size++; }
public E remove(int index) { //校驗 rangeCheck(index); //併發參數+1 modCount++; //獲取舊值 E oldValue = elementData(index); //移動的長度爲=數組長度-須要刪除元素下標-1 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; }
該方法主要用於將數組進行分割,對於數組分割後,其實該數組爲淺拷貝操做,若是在該SubList中操做相關數據,將會致使ArrayList中的數據改變!!app
public List<E> subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList(this, 0, fromIndex, toIndex); }
Integer.MAX_VALUE - 8
,則會嘗試擴容爲Integer.MAX_VALUE
大小肯定minCapacity值是否比容器中的數據容量大less
若是大則擴容,不然什麼也不作dom
擴容
若是minCapacity比newCapacity小則直接使用minCapacity做爲擴容容量
若是其數組個數大於最大的數組的長度,則嘗試使用Integer.MAX_VALUE做爲數組的容器大小
/** * 1.肯定minCapacity值是否比容器中的數據容量大 * 2.若是大則擴容,不然什麼也不作 * @param minCapacity */ private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } /** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * 擴容 * 1. 若是minCapacity比newCapacity小則直接使用minCapacity做爲擴容容量 * 2. 若是其數組個數大於最大的數組的長度,則嘗試使用Integer.MAX_VALUE做爲數組的容器大小 * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; //新容器容量擴容爲如今容器的容量的1.5倍 //eg 舊10個->新15個 int newCapacity = oldCapacity + (oldCapacity >> 1); //看看誰大 if (newCapacity - minCapacity < 0) //若是min比new小則直接複製min newCapacity = minCapacity; //若是新的容器比最大的數組大小還要打 if (newCapacity - MAX_ARRAY_SIZE > 0) //只能複製爲最大容器大小,可是可能會拋出oom newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } /** * 大數組容器擴容 * @param minCapacity * @return */ private static int hugeCapacity(int minCapacity) { //校驗參數合法性 if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
因爲Java默認序列化以及反序列化的時候回分別調用對應的writeObject方法以及readObject()方法,因此如下將對這兩個方法進行分析。
因爲在ArrayList中的elementData數組中可能存在一些空的元素(因爲ArrayList擴容機制)
transient
不會被序列化/** * 緩存數據集合 */ transient Object[] elementData; // non-private to simplify nested class access
如何支持序列化操做
1. 序列化數量 2. 獲取數據數組,而後使用for循環一個一個序列化該對象到數據中。
/** * Save the state of the <tt>ArrayList</tt> instance to a stream (that * is, serialize it). * 保證其能夠被序列化到對象中 * * @serialData The length of the array backing the <tt>ArrayList</tt> * instance is emitted (int), followed by all of its elements * (each an <tt>Object</tt>) in the proper order. */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // Write out element count, and any hidden stuff //數據被序列化的數量 int expectedModCount = modCount; //使用默認的模式進行序列化,只序列化非靜態化變量以及非transient修飾的數據 s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone() //寫入長度 s.writeInt(size); // Write out all elements in the proper order. for (int i=0; i<size; i++) { //寫入每個object數據 s.writeObject(elementData[i]); } //若是發現序列化的modCount與expectedModCount多是併發致使 if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }
如何進行的反序列化
/** * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is, * deserialize it). * 反序列化數據 */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { //設置默認的數組長度爲空數組長度 elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff //使用默認的模式進行序列化,只序列化非靜態化變量以及非transient修飾的數據 s.defaultReadObject(); // Read in capacity //讀取list長度 s.readInt(); // ignored if (size > 0) { // be like clone(), allocate array based upon size not capacity //開始計算並克隆 int capacity = calculateCapacity(elementData, size); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity); //肯定數組長度是否夠 ensureCapacityInternal(size); //進行數據讀取 Object[] a = elementData; // Read in all elements in the proper order. for (int i=0; i<size; i++) { a[i] = s.readObject(); } } }
lambda循環方法
/** * JDK新增方法 ForEach方法 * @param action */ @Override public void forEach(Consumer<? super E> action) { //校驗lambda不爲空 Objects.requireNonNull(action); //併發version版本統計 final int expectedModCount = modCount; @SuppressWarnings("unchecked") final E[] elementData = (E[]) this.elementData; final int size = this.size; //循環每次判斷一下併發參數是否進行了修改,若是進行了修改則直接退出for循環 for (int i=0; modCount == expectedModCount && i < size; i++) { action.accept(elementData[i]); } //並拋出併發異常 if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }
該方法用於返回進行併發計算時候的分割器
/** * Creates a <em><a href="Spliterator.html#binding">late-binding</a></em> * and <em>fail-fast</em> {@link Spliterator} over the elements in this * list. * * <p>The {@code Spliterator} reports {@link Spliterator#SIZED}, * {@link Spliterator#SUBSIZED}, and {@link Spliterator#ORDERED}. * Overriding implementations should document the reporting of additional * characteristic values. * 併發分割方法 * 懶加載加入,只有當數據 * @return a {@code Spliterator} over the elements in this list * @since 1.8 */ @Override public Spliterator<E> spliterator() { /** * 1. 參數1 this * 2. origin * 3. fence 當使用的時候才進行初始化 * 4. 併發參數 */ return new ArrayListSpliterator<>(this, 0, -1, 0); }
lambda 移除符合某個規則的方法
/** * liambad方法移除元素 * @param filter * @return */ @Override public boolean removeIf(Predicate<? super E> filter) { Objects.requireNonNull(filter); // figure out which elements are to be removed // any exception thrown from the filter predicate at this stage // will leave the collection unmodified int removeCount = 0; //使用這玩意來統計存在的位置 final BitSet removeSet = new BitSet(size); final int expectedModCount = modCount; final int size = this.size; for (int i=0; modCount == expectedModCount && i < size; i++) { @SuppressWarnings("unchecked") final E element = (E) elementData[i]; if (filter.test(element)) { removeSet.set(i); removeCount++; } } if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } // shift surviving elements left over the spaces left by removed elements final boolean anyToRemove = removeCount > 0; if (anyToRemove) { //新數組的大小爲以前的數組長度-須要移除元素的個數 final int newSize = size - removeCount; //執行清除工做 for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) { i = removeSet.nextClearBit(i); elementData[j] = elementData[i]; } //釋放gc for (int k=newSize; k < size; k++) { elementData[k] = null; // Let gc do its work } this.size = newSize; //併發version檢查 if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } //併發值+1 modCount++; } return anyToRemove; }
其爲併發分割器,用於咱們使用併發調用parallelStream方法時候調用該方法
static final class ArrayListSpliterator<E> implements Spliterator<E> { private final ArrayList<E> list; private int index; // current index, modified on advance/split private int fence; // -1 until used; then one past last index private int expectedModCount; // initialized when fence set /** Create new spliterator covering the given range */ ArrayListSpliterator(ArrayList<E> list, int origin, int fence, int expectedModCount) { this.list = list; // OK if null unless traversed this.index = origin; this.fence = fence; this.expectedModCount = expectedModCount; } /** * 當獲取的時候才進行fence的初始化操做 * @return */ private int getFence() { // initialize fence to size on first use int hi; // (a specialized variant appears in method forEach) ArrayList<E> lst; if ((hi = fence) < 0) { //以前的數組爲空則說明沒有進行分割,則從0開始 if ((lst = list) == null) hi = fence = 0; else { //不然則 expectedModCount = lst.modCount; //則將該值賦值爲lst的長度 hi = fence = lst.size; } } return hi; } /** * 嘗試分割 * 一、總長度爲數組長度 * 二、分割成兩份 * 三、二分法分割 * 三、中間值爲數組長度+分割後的數組長度,就是二分法啦 * @return */ public ArrayListSpliterator<E> trySplit() { int hi = getFence(), lo = index, mid = (lo + hi) >>> 1; return (lo >= mid) ? null : // divide range in half unless too small new ArrayListSpliterator<E>(list, lo, index = mid, expectedModCount); } /** * 併發執行操做 * @param action * @return */ public boolean tryAdvance(Consumer<? super E> action) { if (action == null) throw new NullPointerException(); int hi = getFence(), i = index; //若是沒有超過這個分割的長度則繼續操做不然返回false if (i < hi) { index = i + 1; @SuppressWarnings("unchecked") E e = (E)list.elementData[i]; action.accept(e); //若是出現了併發改變,則拋出異常 if (list.modCount != expectedModCount) throw new ConcurrentModificationException(); return true; } return false; } /** * 併發ForEach輸出 * @param action */ public void forEachRemaining(Consumer<? super E> action) { int i, hi, mc; // hoist accesses and checks from loop ArrayList<E> lst; Object[] a; if (action == null) throw new NullPointerException(); if ((lst = list) != null && (a = lst.elementData) != null) { if ((hi = fence) < 0) { mc = lst.modCount; hi = lst.size; } else mc = expectedModCount; if ((i = index) >= 0 && (index = hi) <= a.length) { for (; i < hi; ++i) { @SuppressWarnings("unchecked") E e = (E) a[i]; action.accept(e); } if (lst.modCount == mc) return; } } throw new ConcurrentModificationException(); } public long estimateSize() { return (long) (getFence() - index); } public int characteristics() { return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED; } }
Iterator<Double> iterator = list.iterator(); while (iterator.hasNext()){ Double next = iterator.next(); System.out.println(next); }
for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); }
for (Double next : list) { System.out.println(next); }
list.forEach(System.out::println);
測試代碼以下
package cn.lonecloud; import java.time.Duration; import java.time.Instant; import java.util.*; /** * @author lonecloud * @version v1.0 * @date 2019/4/3 19:56 */ public class ListTest { public static void main(String[] args) { int len=20000000; List<Integer> list=new ArrayList<>(len); for (int i = 0; i < len; i++) { list.add(i); } test(list); } public static void test(List<Integer> list){ long itrBegin = System.currentTimeMillis(); //1. 使用迭代器 Iterator<Integer> iterator = list.iterator(); Integer a=0; while (iterator.hasNext()){ Integer next = iterator.next(); a=next; } long itrEnd=System.currentTimeMillis(); long foriStart=System.currentTimeMillis(); //2. fori for (int i = 0; i < list.size(); i++) { a=list.get(i); } long foriEnd=System.currentTimeMillis(); long forStart=System.currentTimeMillis(); //3. for for (Integer next : list) { a=next; } long forEnd=System.currentTimeMillis(); long streamstart=System.currentTimeMillis(); //4. stream list.forEach((value)->{ Integer b=value; }); long streamEnd=System.currentTimeMillis(); System.out.println("迭代器時間:"+(itrEnd-itrBegin)); System.out.println("fori時間:"+(foriEnd-foriStart)); System.out.println("for時間:"+(forEnd-forStart)); System.out.println("stream時間:"+(streamEnd-streamstart)); } }
結果:
迭代器時間:31 fori時間:32 for時間:32 stream時間:74
總結:
效率:
fori和for和迭代器大體相同,因爲事先了RandomAccess
stream時間高一些,緣由是須要進行更多的方法調用產生的時間
ArrayList是基於數組的集合,適合循環迭代多的場景,不適合修改多的場景
在使用ArrayList時候須要注意在建立的時候(預估一下你須要的容器大小)
因爲若是你在使用的時候超過了初始化容量(10),這將會致使容器進行一次(容器擴容),而數組複製是一件很是耗時的操做
ArrayList中的clone()方法以及copy方法,都是淺克隆的。
在通常狀況下,若是集合容器出現容量不足須要擴容的時候,其集合會擴容爲原集合的1.5倍大小
若是須要將List轉換成數組,推薦使用泛型方法T[] toArray(T[] a)
而不是Object[] toArray()
方法
若是涉及在指定位置上插入指定元素的操做,若是這種操做比較多,推薦使用LinkedList而不是使用ArrayList,由於你每次在指定的位置上插入元素會致使數組拷貝操做。
若是List涉及到頻繁修改的時候,建議使用LinkedList,而不是使用ArrayList。
ArrayList是一個非線程安全類,若是須要設計到線程安全,請使用併發包相關的類
subList(int fromIndex, int toIndex)
方法返回的SubList
類,其中若是你對該List操做時候,原集合也會改變