Java 8 中 ArrayList 的變化原始碼分析

魔王不造反發表於2018-04-08

1.ArrayList上級介面的變化

先回顧一下ArrayList的類定義

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
複製程式碼
Java 8 中 ArrayList 的變化原始碼分析


介面/類新增方法

public interface Collection<E> extends Iterable<E> {
    .....
    //1.8新增方法:提供了介面預設實現,返回分片迭代器
    @Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, 0);
    }
    //1.8新增方法:提供了介面預設實現,返回序列流物件
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
    //1.8新增方法:提供了介面預設實現,返回並行流物件
    default Stream<E> parallelStream() {
        return StreamSupport.stream(spliterator(), true);
    }
    /**
     * Removes all of the elements of this collection that satisfy the given
     * predicate.  Errors or runtime exceptions thrown during iteration or by
     * the predicate are relayed to the caller.
     * 1.8新增方法:提供了介面預設實現,移除集合內所有匹配規則的元素,支援Lambda表示式
     */
    default boolean removeIf(Predicate<? super E> filter) {
        //空指標校驗
        Objects.requireNonNull(filter);
        //注意:JDK官方推薦的遍歷方式還是Iterator,雖然forEach是直接用for迴圈
        boolean removed = false;
        final Iterator<E> each = iterator();
        while (each.hasNext()) {
            if (filter.test(each.next())) {
                each.remove();//移除元素必須選用Iterator.remove()方法
                removed = true;//一旦有一個移除成功,就返回true
            }
        }
        //這裡補充一下:由於一旦出現移除失敗將丟擲異常,因此返回false指的僅僅是沒有匹配到任何元素而不是執行異常
        return removed;
    }
}
public interface Iterable<T>{
    .....
    //1.8新增方法:提供了介面預設實現,用於遍歷集合
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
    //1.8新增方法:提供了介面預設實現,返回分片迭代器
    default Spliterator<T> spliterator() {
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    }
}
public interface List<E> extends Collection<E> {
    //1.8新增方法:提供了介面預設實現,用於對集合進行排序,主要是方便Lambda表示式
    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }
    //1.8新增方法:提供了介面預設實現,支援批量刪除,主要是方便Lambda表示式
    default void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final ListIterator<E> li = this.listIterator();
        while (li.hasNext()) {
            li.set(operator.apply(li.next()));
        }
    }
    /**
      * 1.8新增方法:返回ListIterator例項物件 
      * 1.8專門為List提供了專門的ListIterator,相比於Iterator主要有以下增強:
      *     1.ListIterator新增hasPrevious()和previous()方法,從而可以實現逆向遍歷
      *     2.ListIterator新增nextIndex()和previousIndex()方法,增強了其索引定位的能力
      *     3.ListIterator新增set()方法,可以實現物件的修改
      *     4.ListIterator新增add()方法,可以向List中新增物件
      */
    ListIterator<E> listIterator();
}
複製程式碼

2.ArrayList的變化

1.8的ArrayList只是在1.7的基礎上做了很少的改動,主要集中於初始化以及實現介面新增方法方面
1.7版本請參見筆者的 集合番@ArrayList一文通(1.7版)

3.ArrayList的屬性變化

全域性變數的變更

/**
  * The array buffer into which the elements of the ArrayList are stored.
  * The capacity of the ArrayList is the length of this array buffer. 
  * Any empty ArrayList with elementData == EMPTY_ELEMENTDATA will be expanded to
  * DEFAULT_CAPACITY when the first element is added.
  * 陣列快取,跟1.7版本相比,主要有兩個變化:
  *     1.去掉private屬性,使用預設的friendly作用域,開放給同包類使用
  *     2.一個空陣列的elementData將設定為EMPTY_ELEMENTDATA直到第一個元素新增時
  *       使用DEFAULT_CAPACITY(10)完成有容量的初始化 -- 優化:這裡選擇將記憶體分配後置,而從儘可能節省空間
  */
transient Object[] elementData; // non-private to simplify nested class access
/**
  * Shared empty array instance used for empty instances.
  * 當時用空構造時,給予陣列(elementData)預設值
  */
private static final Object[] EMPTY_ELEMENTDATA = {};
複製程式碼


構造器的變更

/**
  * Constructs an empty list with an initial capacity of ten.
  * 1.8版的預設構造器,只會初始化一個空陣列
  */
public ArrayList() {
    super();
    this.elementData = EMPTY_ELEMENTDATA;//初始化一個空陣列
}
/**
  * Constructs an empty list with an initial capacity of ten.
  * 1.7版的預設構造器,會直接初始化一個10容量的陣列
  */
public ArrayList() {
    this(10);
}
複製程式碼

4.ArrayList的方法變化

trimToSize方法變更

/**
  * 1.8版的trimToSize,跟1.7版相比:
  *     可以明顯的看到去掉了oldCapacity這一臨時變數
  *     筆者認為這進一步強調了HashMap是非執行緒安全的,因此直接用length即可
  */
public void trimToSize() {
    modCount++;
    if (size < elementData.length) {
        elementData = Arrays.copyOf(elementData, size);
    }
}
/**
  * 1.7版的trimToSize
  */
public void trimToSize() {
    modCount++;
    int oldCapacity = elementData.length;
    if (size < oldCapacity) {
        elementData = Arrays.copyOf(elementData, size);
    }
}
複製程式碼

5.ArrayList的新增方法

5.1 forEach方法

/**
  * Performs the given action for each element of the {@code Iterable}
  * until all elements have been processed or the action throws an
  * exception.  Unless otherwise specified by the implementing class,
  * actions are performed in the order of iteration (if an iteration order
  * is specified).  Exceptions thrown by the action are relayed to the
  * caller.
  * 1.8新增方法,重寫Iterable介面的forEach方法
  * 提供對陣列的遍歷操作,由於支援Consumer因此在遍歷時將執行傳入的方法
  */
@Override
public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    @SuppressWarnings("unchecked")
    final E[] elementData = (E[]) this.elementData;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        action.accept(elementData[i]);//執行傳入的自定義方法
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}
--------------
List<String> list = new ArrayList<>();
list.add("有村架純");
list.add("橋本環奈");
list.add("齋藤飛鳥");
list.forEach(s -> System.out.print(s + "!!")); //輸出:有村架純!!橋本環奈!!齋藤飛鳥!!
複製程式碼

5.2 removeIf方法

/**
  * Removes all of the elements of this collection that satisfy the given
  * predicate.  Errors or runtime exceptions thrown during iteration or by
  * the predicate are relayed to the caller.
  * 1.8新增方法,重寫Collection介面的removeIf方法
  * 移除集合內所有複合匹配條件的元素,迭代時報錯會丟擲異常 或 把斷言傳遞給呼叫者(即斷言中斷)
  * 該方法主要乾了兩件事情:
  *     1.根據匹配規則找到所有符合要求的元素
  *     2.移除元素並轉移剩餘元素位置
  * 補充:為了安全和快速,removeIf分成兩步走,而不是直接找到就執行刪除和轉移操作,寫法值得借鑑
  */
@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;
    //BitSet用於按位儲存,這裡用作儲存待移除元素(即符合匹配規則的元素)
    //BitSet能夠通過點陣圖演算法大幅減少資料佔用儲存空間和記憶體,尤其適合在海量資料方面,這裡是個很明顯的優化
    //有機會會在基礎番中解析一下BitSet的奇妙之處
    final BitSet removeSet = new BitSet(size);
    final int expectedModCount = modCount;
    final int size = this.size;
    //每次迴圈都要判斷modCount == expectedModCount!
    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];
        }
        for (int k=newSize; k < size; k++) {
            elementData[k] = null;  // Let gc do its work
        }
        this.size = newSize;
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }
    //正常情況下,一旦匹配到元素,應該刪除成功,否則將丟擲異常,當沒有匹配到任何元素時,返回false
    return anyToRemove;
}
--------------
List<String> list = new ArrayList<>();
list.add("有村架純");
list.add("橋本環奈");
list.add("齋藤飛鳥");
list.forEach(s -> System.out.print(s + "!!")); //輸出:有村架純!!橋本環奈!!齋藤飛鳥!!
System.out.println(list.removeIf(s -> s.startsWith("齋藤")));//輸出:true
list.forEach(s -> System.out.print(s + ",")); //輸出:有村架純!!橋本環奈!!
--------------
//這裡補充一點,使用Arrays.asList()生成的ArrayList是Arrays自己的私有靜態內部類
//強行使用removeIf的話會丟擲java.lang.UnsupportedOperationException的異常(因為它沒實現這個方法)
複製程式碼

5.3 replaceAll方法

/**
  * Replaces each element of this list with the result of applying the operator to that element.
  * Errors or runtime exceptions thrown by the operator are relayed to the caller.
  * 1.8新增方法,重寫List介面的replaceAll方法
  * 提供支援一元操作的批量替換功能
  */
@Override
@SuppressWarnings("unchecked")
public void replaceAll(UnaryOperator<E> operator) {
    Objects.requireNonNull(operator);
    final int expectedModCount = modCount;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        elementData[i] = operator.apply((E) elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}
--------------
List<String> list = new ArrayList<>();
list.add("有村架純");
list.add("橋本環奈");
list.add("齋藤飛鳥");
list.forEach(s -> System.out.print(s + "!!")); //輸出:有村架純!!橋本環奈!!齋藤飛鳥!!
list.replaceAll(t -> {
    if(t.equals("橋本環奈")) t = "逢澤莉娜";//這裡我們將"橋本環奈"替換成"逢澤莉娜"
    return t;//注意如果是語句塊的話一定要返回
});
list.forEach(s -> System.out.print(s + "!!")); //輸出:有村架純!!逢澤莉娜!!齋藤飛鳥!!
複製程式碼

5.4 sort方法

/**
  * Sorts this list according to the order induced by the specified
  * 1.8新增方法,重寫List介面的sort方法
  * 支援對陣列進行排序,主要方便於Lambda表示式
  */
@Override
@SuppressWarnings("unchecked")
public void sort(Comparator<? super E> c) {
    final int expectedModCount = modCount;
    //Arrays.sort底層是結合歸併排序和插入排序的混合排序演算法,有不錯的效能
    //有機會在基礎番對Timsort(1.8版)和ComparableTimSort(1.7版)進行解析
    Arrays.sort((E[]) elementData, 0, size, c);
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
    modCount++;
}
--------------
List<String> list = new ArrayList<>();
list.add("有村架純");
list.add("橋本環奈");
list.add("齋藤飛鳥");
list.forEach(s -> System.out.print(s + "!!")); //輸出:有村架純!!橋本環奈!!齋藤飛鳥!!
list.sort((prev, next) -> prev.compareTo(next));//這裡我們選用自然排序
list.forEach(s -> System.out.print(s + "!!"));//輸出:齋藤飛鳥!!有村架純!!橋本環奈!!
複製程式碼

6.ArrayList的新增並行分片迭代器

什麼是並行分片迭代器?

  • 並行分片迭代器是Java為了並行遍歷資料來源中的元素而專門設計的迭代器
  • 並行分片迭代器借鑑了Fork/Join框架的核心思想:用遞迴的方式把並行的任務拆分成更小的子任務,然後把每個子任務的結果合併起來生成整體結果
  • 並行分片迭代器主要是提供給Stream,準確說是提供給並行流使用,使用時推薦直接用Stream即可

ArrayListSpliterator類解析

default Stream<E> parallelStream() {//並行流
    return StreamSupport.stream(spliterator(), true);//true表示使用並行處理
}
static final class ArrayListSpliterator<E> implements Spliterator<E> {
    private final ArrayList<E> list;
    //起始位置(包含),advance/split操作時會修改
    private int index; // current index, modified on advance/split
    //結束位置(不包含),-1 表示到最後一個元素
    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的初始化賦值
      */
    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) {
            //當list為空,fence=0
            if ((lst = list) == null)
                hi = fence = 0;
            else {
            //否則,fence = list的長度
                expectedModCount = lst.modCount;
                hi = fence = lst.size;
            }
        }
        return hi;
    }
    /**
      * 對任務(list)分割,返回一個新的Spliterator迭代器
      */
    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);
    }
    /**
      * 對單個元素執行給定的執行方法
      * 若沒有元素需要執行,返回false;若可能還有元素尚未執行,返回true
      */
    public boolean tryAdvance(Consumer<? super E> action) {
        if (action == null)
            throw new NullPointerException();
        int hi = getFence(), i = index;
        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;
    }
    /**
      * 對每個元素執行給定的方法,依次處理,直到所有元素已被處理或被異常終止
      * 預設方法呼叫tryAdvance方法
      */
    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;
    }
}
--------------
List<String> list = new ArrayList<>();
list.add("有村架純");
list.add("橋本環奈");
list.add("齋藤飛鳥");
Spliterator<String> spliterator =  list.spliterator();
spliterator.forEachRemaining(s -> System.out.print(s += "妹子!!"));
//輸出:有村架純妹子!!橋本環奈妹子!!齋藤飛鳥妹子!!
//因為這個類是提供給Stream使用的,因此可以直接用Stream,下面的程式碼作用等同上面,但進行了併發優化
Stream<String> parallelStream = list.parallelStream();
parallelStream.forEach(s -> System.out.print(s += "妹子!!"));
//輸出:橋本環奈妹子!!有村架純妹子!!齋藤飛鳥妹子!!  --> 因為引入併發,所有執行順序會有些不同
複製程式碼


相關文章