1. 程式人生 > >Java常用資料結構之List

Java常用資料結構之List

JDK 11正式釋出了,Oracle終於出了一個長期維護版本,應該將是繼JDK 8之後的一個常規使用版本。

前言

作為Java系開發者對Java集合類的使用應該是較為頻繁的,也是面試中經常會被問的問題。一直想整理一下Java集合和Android中的優化集合類,借這次機會把Java中的常用集合都整理一遍。由於JDK 11已出,本系列文章中的原始碼都來自JDK 11(集合這部分應該沒什麼變化)。

List類關係

List集合中常用的就是ArrayListLinkedList,(曾經被問到這兩個類的祖先基類是啥?)直接上類圖:

List類圖

可以看到關鍵介面類:List、Collection和Iterable屬於繼承關係,相當於做了一個職責區分(設計模式的職責單一原則)。而AbstractCollection、AbstractList和AbstractSequentialList是抽象類,對外提供了可擴充套件的模板(設計模式中的模板模式)。如果你想自己設計一個List,可以直接繼承AbstractList或者AbstractSequentialList類,實現其中的特定方法即可。

AbstractList和AbstractSequentialList

AbstractList實現了部分List介面中的方法,為想實現List介面的開發者提供了便利。

AbatractList類結構

  • 如果開發人員想要實現一個不可修改的List,那麼只需要繼承該類,然後重寫兩個方法:get(int)和size()方法。
  • 如果開發人員想要實現一個可修改的List,那麼需要重寫set()方法,如果不重寫會丟擲UnsupportedOperationException異常,如果size()的大小是可變的,那麼開發人員還需要重寫add(int, E)和 remove(int)方法。

AbstractList實現了兩種預設迭代器:

  • private class Itr implements Iterator
  • private class ListItr extends Itr implements ListIterator

也就是說,子類可以不用實現iterator()listIterator()方法。

該類只有一個成員變數:該List在結構上已經被修改的次數。

protected transient int modCount = 0;

Iterator迭代器和ListIterator迭代器會使用到該變數,如果迭代器的expectModCount與該變數的值不同,那就會丟擲ConcurrentModificationException異常,在官方文件中稱其為fail-fast

AbstractList實現了兩種子列表:

  • private static class SubList extends AbstractList
  • private static class RandomAccessSubList
    extends SubList implements RandomAccess

SubList中的方法呼叫其實還是呼叫的父類方法,即AbstractList。這裡主要說一下RandomAccess類,它是一個空介面,用來標記是否可以隨機訪問。能隨機訪問代表在查詢的時候可以用效率更高的演算法(相較於順序訪問,典型例子Arraylist)。

AbstractSequentialList繼承自AbstractList,是對順序訪問列表的一種擴充套件。它的iterator()方法直接返回的是listIterator()

    /**
     * Returns an iterator over the elements in this list (in proper
     * sequence).<p>
     *
     * This implementation merely returns a list iterator over the list.
     *
     * @return an iterator over the elements in this list (in proper sequence)
     */
    public Iterator<E> iterator() {
        return listIterator();
    }

ArrayList分析

ArrayList

ArrayList實現了Serializable介面,因此它支援序列化,能夠通過序列化傳輸;實現了RandomAccess介面,支援快速隨機訪問,實際上就是通過下標序號進行快速訪問;實現了Cloneable介面,能被克隆(淺拷貝)。

關鍵點

1、ArrayList是基於陣列實現的,是動態陣列,動態增長內容容量,每次擴充套件為原來的1.5倍,預設容量大小10。

    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;
    
    /**
     * 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 == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access
    
    /**
     * Returns a capacity at least as large as the given minimum capacity.
     * Returns the current capacity increased by 50% if that suffices.
     * Will not return a capacity greater than MAX_ARRAY_SIZE unless
     * the given minimum capacity is greater than MAX_ARRAY_SIZE.
     *
     * @param minCapacity the desired minimum capacity
     * @throws OutOfMemoryError if minCapacity is less than zero
     */
    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1); // 這裡就是擴充套件1.5倍
        if (newCapacity - minCapacity <= 0) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return minCapacity;
        }
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }

2、ArrayList不是執行緒安全的,多執行緒環境下可以考慮用Collections.synchronizedList(List l)函式返回一個執行緒安全的ArrayList,也可以用CopyOnWriteArrayList類。

3、ArrayList的實現中大量地呼叫了Arrays.copyof()和System.arraycopy()方法,當容量不夠時,每次增加元素,都要將原來的元素拷貝到一個新的陣列中,非常之耗時,也因此建議在事先能確定元素數量的情況下,才使用ArrayList,否則建議使用LinkedList。

grow方法

add方法

特點

  • ArrayList中允許元素為null。
  • ArrayList查詢效率高,但每次插入或刪除元素,就要大量地移動元素,插入刪除元素的效率低。

LinkedList分析

LinkedList比ArrayList多實現了一個Deque介面,即:雙端佇列。LinkedList實現了Serializable介面,因此它支援序列化,能夠通過序列化傳輸,實現了Cloneable介面,能被克隆(淺拷貝);

關鍵點

1、LinkedList是基於雙向連結串列(已經不再是雙向迴圈列表)實現的;除了可以當做連結串列來操作外,它還可以當做棧、佇列和雙端佇列來使用。

    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
    
    /**
     * Pointer to first node.
     */
    transient Node<E> first;

    /**
     * Pointer to last node.
     */
    transient Node<E> last;

2、LinkedList同樣是非執行緒安全的。

特點

  • LinkedList中允許元素為null。
  • 不存在容量不足的問題,所以這裡沒有擴容的方法。
  • 插入刪除效率高,查詢效率低。

總結

  1. 多執行緒環境下使用ArrayList和LinkedList時注意執行緒同步。
  2. 根據資料特點和使用環境進行選擇。

參考資料

  1. jdk8文件
  2. Java集合(5)——List介面與AbstractList抽象類原始碼解析
  3. Java 集合深入理解(6):AbstractList

關注微信公眾號,最新技術乾貨實時推送