從源碼看Android常用的數據結構 ( SDK23版本 ) ( 二, List篇 )

此係列文章放在了我的專欄裏, 歡迎查看
https://blog.csdn.net/column/details/24187.html

相關銜接
從源碼看Android常用的數據結構 ( SDK23版本 ) ( 一 , 總述 )
從源碼看Android常用的數據結構 ( SDK23版本 ) ( 二, List篇 )
從源碼看Android常用的數據結構 ( SDK23版本 ) ( 三 , Queue篇)
從源碼看Android常用的數據結構 ( SDK23版本 ) ( 四, Set篇 )
從源碼看Android常用的數據結構 ( SDK23版本 ) ( 五, Map篇 )
從源碼看Android常用的數據結構 ( SDK23版本 ) ( 六, ConcurrentHashMap )
從源碼看Android常用的數據結構 ( 七, SDK28下的HashMap )

Github裏有一份Android使用到的小技術, 歡迎查看:
https://github.com/YouCii/LearnApp


總覽

List 接口的官方註釋

A {@code List} is a collection which maintains an ordering for its elements. Every
element in the {@code List} has an index. Each element can thus be accessed by its
index, with the first index being zero. Normally, {@code List}s allow duplicate
elements, as compared to Sets, where elements have to be unique.

List是一個保持其元素順序的集合. 每個List中的元素具有index索引, 每個元素都可以通過從0開始的index訪問. 
通常, List允許元素重複, 而Sets則要求元素必須唯一.

實現 List 接口中日常使用比較多的類有以下幾個:
ArrayList, LinkedList(也同時實現了Queue), Vector, CopyOnWriteArrayList, Stack.

大體比較下他們的不同, 具體的請看後面的單獨分析

CopyOnWriteArrayList: 讀操作不加鎖, 寫操作加鎖, 內部首先new Object[], 然後執行System.arraycopy操作, 複製完成後將原數據數組引用賦值爲此新的object[], 實現讀寫分離的, 大幅提高讀效率, 因爲寫每次都要執行復制操作, 所以寫效率甚至比ArrayList更差.

Vector和Colleactions.synchronizedList: 所有方法均加鎖, 效率差, 但頻繁寫的場景要比CopyOnWriteArrayList更適用.


ArrayList

“啥, ArrayList還用得着分析?”
本來我也是這麼想的, 後來看了下源碼, 發現還有幾處奇妙的地方, 摘錄部分源碼

public class ArrayList<E> extends AbstractList<E> implements Cloneable, Serializable, RandomAccess {
    private static final int MIN_CAPACITY_INCREMENT = 12;
    transient Object[] array;
    
    @Override public boolean add(E object) {
        Object[] a = array;
        int s = size;
        if (s == a.length) {
            Object[] newArray = new Object[s +
                    (s < (MIN_CAPACITY_INCREMENT / 2) ?
                     MIN_CAPACITY_INCREMENT : s >> 1)];
            System.arraycopy(a, 0, newArray, 0, s);
            array = a = newArray;
        }
        a[s] = object;
        size = s + 1;
        modCount++;
        return true;
    }
    
	@Override public E remove(int index) {
        Object[] a = array;
        int s = size;
        if (index >= s) {
            throwIndexOutOfBoundsException(index, s);
        }
        @SuppressWarnings("unchecked") E result = (E) a[index];
        System.arraycopy(a, index + 1, a, index, --s - index);
        a[s] = null;  // Prevent memory leak
        size = s;
        modCount++;
        return result;
    }
    
    private void writeObject(ObjectOutputStream stream) throws IOException {
        stream.defaultWriteObject();
        stream.writeInt(array.length);
        for (int i = 0; i < size; i++) {
            stream.writeObject(array[i]);
        }
    }

    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
        stream.defaultReadObject();
        int cap = stream.readInt();
        if (cap < size) {
            throw new InvalidObjectException(
                    "Capacity: " + cap + " < size: " + size);
        }
        array = (cap == 0 ? EmptyArray.OBJECT : new Object[cap]);
        for (int i = 0; i < size; i++) {
            array[i] = stream.readObject();
        }
    }   

	private class ArrayListIterator implements Iterator<E> {
        private int remaining = size;
        private int expectedModCount = modCount;

        public boolean hasNext() {
            return remaining != 0;
        }

        @SuppressWarnings("unchecked") public E next() {
            ArrayList<E> ourList = ArrayList.this;
            int rem = remaining;
            if (ourList.modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            if (rem == 0) {
                throw new NoSuchElementException();
            }
            remaining = rem - 1;
            return (E) ourList.array[removalIndex = ourList.size - rem];
        }

        public void remove() {
            Object[] a = array;
            int removalIdx = removalIndex;
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            if (removalIdx < 0) {
                throw new IllegalStateException();
            }
            System.arraycopy(a, removalIdx + 1, a, removalIdx, remaining);
            a[--size] = null;  // Prevent memory leak
            removalIndex = -1;
            expectedModCount = ++modCount;
        }
    }

從源碼中可以看到幾個值得注意的地方

  • ArrayList 使用 Object[] 存儲數據, get 讀取速度非常快;
  • add 方法中會有條件的進行擴容, 如果當前size<6, 就增加12個, 如果多於6個, 就增加原size的一半, 而每次擴容都要執行一次System.arraycopy, 這步操作耗時較多, 所以要求我們在使用ArrayList的時候儘量聲明初始大小, 減少擴容次數;
  • 每次 remove 必定執行 System.arraycopy, 所以如果需要頻繁刪減元素的話還是另請高明吧;
  • 注意到 Object[] 前面增加了 transient 不允許 Serializable 默認序列化, 奇怪爲什麼最關鍵的數據集合不允許序列化. 原來後面使用了writeObject/readObject方法, 這兩個方法會替代Serializable的默認序列化而進行自己的序列化操作. 那這樣複雜的操作有什麼意義呢? 這和ArrayList的擴容有關係, Object[] 擴容後會有很多空位置, 如果直接序列化可能會浪費大量的內存空間, 所以使用這種方式僅序列化實際存儲的數據. 另外, writeObject/readObject是private的, 之所以能生效是因爲反射機制(其實Serializable本身就是反射機制實現的, 所以自己序列化的話最好還是使用parcelable);
  • ArrayList迭代器使用一個 remaining 剩餘size來實現 next 邏輯, 效率比較高, 但是遍歷時還是不如只有一句 array[index] ( 還有比這更快的嗎? ), 所以遍歷ArrayList 時使用 for(int i=0; i<list.size(); i++)for(Object o : list) 要快一些.

LinkedList

LinkedList is an implementation of {@link List}, backed by a doubly-linked list.
All optional operations including adding, removing, and replacing elements are supported.
<p>All elements are permitted, including null.
<p>This class is primarily useful if you need queue-like behavior. It may also be useful
as a list if you expect your lists to contain zero or one element, but still require the
ability to scale to slightly larger numbers of elements. In general, though, you should
probably use {@link ArrayList} if you don't need the queue-like behavior.

LinkedList 是由 Link 實現的一種雙向鏈表, 支持包括增刪改查等所有操作, 而且支持包括null在內的所有數據類型.
如果您需要類似於隊列的行爲, 這個類一般比較有用. 如果需要列表從一兩個原色擴容到稍大的容量時也可以使用它.
但一般來說, 如果您不需要類似隊列的行爲, 那麼您應該使用ArrayList.

LinkedList 作爲一種鏈表, 核心就在於 Link 這個內部類

    private static final class Link<ET> {
        ET data;

        Link<ET> previous, next;

        Link(ET o, Link<ET> p, Link<ET> n) {
            data = o;
            previous = p;
            next = n;
        }
    }

每個 Link 在鏈表稱爲一個結點, 內部存有元素的數據, 以及該元素的前後兩個元素( 可以前後雙向查詢, 所以叫雙向表 ).
鏈式存儲結構的每個結點在內存中自行分配, 無需像線性表一樣按順序排放, 這樣就避免了導致線性表插入/刪除效率低下的數組拷貝操作.
那麼鏈表具體是怎麼實現的呢, 看一下源碼( 只簡單摘錄了實例化和基本的增刪改查 )

	/**
	* list的大小
	*/
    transient int size = 0;
    /**
    * 根節點, 各種操作均從根節點開始
    */
    transient Link<E> voidLink;

	/**
	* 構造方法, 實例化voidLink, 並把 previous/next 均指向其本身, 構成雙向表
	*/
    public LinkedList() {
        voidLink = new Link<E>(null, null, null);
        voidLink.previous = voidLink;
        voidLink.next = voidLink;
    }
   
   /**
 * 增加操作, 無需擴容, 只需要修改兩個previous/next即可, 效率高
   */
    @Override
    public boolean add(E object) {
        Link<E> oldLast = voidLink.previous;
        Link<E> newLink = new Link<E>(object, oldLast, voidLink);
        voidLink.previous = newLink;
        oldLast.next = newLink;
        size++;
        modCount++;
        return true;
    }
    
    @Override
    public E remove(int location) {
        if (location >= 0 && location < size) {
            Link<E> link = voidLink;
            // 二分法, previous/next 雙向查詢
            if (location < (size / 2)) {
                for (int i = 0; i <= location; i++) {
                    link = link.next;
                }
            } else {
                for (int i = size; i > location; i--) {
                    link = link.previous;
                }
            }
            Link<E> previous = link.previous;
            Link<E> next = link.next;
            previous.next = next;
            next.previous = previous;
            size--;
            modCount++;
            return link.data;
        }
        throw new IndexOutOfBoundsException();
    }  
    
    @Override
    public E get(int location) {
        if (location >= 0 && location < size) {
            Link<E> link = voidLink;
            // 二分法, previous/next 雙向查詢
            if (location < (size / 2)) {
                for (int i = 0; i <= location; i++) {
                    link = link.next;
                }
            } else {
                for (int i = size; i > location; i--) {
                    link = link.previous;
                }
            }
            return link.data;
        }
        throw new IndexOutOfBoundsException();
    }
    
    @Override
    public E set(int location, E object) {
        if (location >= 0 && location < size) {
            Link<E> link = voidLink;
            // 二分法, previous/next 雙向查詢
            if (location < (size / 2)) {
                for (int i = 0; i <= location; i++) {
                    link = link.next;
                }
            } else {
                for (int i = size; i > location; i--) {
                    link = link.previous;
                }
            }
            E result = link.data;
            link.data = object;
            return result;
        }
        throw new IndexOutOfBoundsException();
    }  
    
	 private static final class LinkIterator<ET> implements ListIterator<ET> {
		...

	    public boolean hasNext() {
            return link.next != list.voidLink;
        }

        public ET next() {
            if (expectedModCount == list.modCount) {
                LinkedList.Link<ET> next = link.next;
                if (next != list.voidLink) {
                    lastLink = link = next;
                    pos++;
                    return link.data;
                }
                throw new NoSuchElementException();
            }
            throw new ConcurrentModificationException();
        }
    }
  • 可以看到查找時首先需要通過二分法循環定位到position位置, 與ArrayList相比, 查詢效率較慢;
  • LinkedList 的增刪操作避免了數組拷貝, 在元素數量較大的情況下增刪效率大大提升.
  • 因爲其鏈表的特性, 使用迭代器 for(Object o : list) 的方式比 for(int i=0; i<list.size(); i++) 的效率要高很多.

Vector

Vector is an implementation of {@link List}, backed by an array and synchronized.
All optional operations including adding, removing, and replacing elements are supported.
<p>All elements are permitted, including null.
<p>This class is equivalent to {@link ArrayList} with synchronized operations. This has a 
performance cost, and the synchronization is not necessarily meaningful to your application:
synchronizing each call to {@code get}, for example, is not equivalent to synchronizing on the
list and iterating over it (which is probably what you intended). If you do need very highly 
concurrent access, you should also consider {@link java.util.concurrent.CopyOnWriteArrayList}.

vector是List接口的實現類, 基於數組和同步特性, 支持包括添加、移除和替換元素在內的所有操作,允許包括NULL在
內所有元素. 
此類相當於具有同步操作的AlayList, 同步特性會造成更高的性能成本,  而且並不一定有意義:例如,  同步每次get操作
(for{synchronized{get}})並不等同於在列表上同步並迭代它(synchronized{for{get}}, 這可能是您想要的). 
如果確實需要做非常高的併發訪問,  你也應該考慮CopyOnWriteArrayList. 

Vector與ArrayList有以下幾點不同

  1. Vector的構造方法除了capacity, 還多出了capacityIncrement參數 public Vector(int capacity, int capacityIncrement), capacityIncrement用於控制每次擴容時增加的元素數, 如果capacityIncrement<=0, Vector的大小會翻倍, 而ArrayList大多數情況都是擴容一半;
  2. Vector的元素數組沒有transient修飾 protected Object[] elementData, 所以會執行默認的序列化;
  3. Vector的包括增刪改查在內的所有方法均添加了同步鎖, 效率較差的同步方式;
  4. Vetor使用了 AbstractList 內的 SimpleListIterator, 並沒有自己重寫.

由以上2/3可知, Vector提供了同步操作, 如果無須同步特性時, 效率要比ArrayList要差.
其實同步場景下使用 Collections.synchronizedList() 和 CopyOnWriteArrayList 也要比 Vector 要更合適, 後續說明.


CopyOnWriteArrayList

 A thread-safe random-access list.
 一種線程安全的隨機訪問列表.

 <p>Read operations (including {@link #get}) do not block and may overlap with
 update operations. Reads reflect the results of the most recently completed
 operations. Aggregate operations like {@link #addAll} and {@link #clear} are
 atomic; they never expose an intermediate state.
 包括get在內的讀操作不會阻塞, 可能會和更新操作同時進行, 讀到的值是最近完成的操作後的.
 像addAll/clear等聚合操作都是原子性的, 不會返回中間態(注, 這是因爲外部加鎖的原因).

 <p>Iterators of this list never throw {@link ConcurrentModificationException}. 
 When an iterator is created, it keeps a copy of the list's contents. It is 
 always safe to iterate this list, but iterations may not reflect the latest 
 state of the list.
 這種List不會拋出ConcurrentModificationException異常. 當其迭代器創建時, 會拷貝其內容. 
 迭代此列表永遠都是安全的, 只不過結果有可能不是最新的.

 <p>Iterators returned by this list and its sub lists cannot modify the
 underlying list. In particular, {@link Iterator#remove}, {@link
 ListIterator#add} and {@link ListIterator#set} all throw {@link
 UnsupportedOperationException}.
 這個List及其子類List的迭代器都不允許修改List內的原內容, 特別是remove/add/set操作均直接拋出
 UnsupportedOperationException異常.

 <p>This class offers extended API beyond the {@link List} interface. It
 includes additional overloads for indexed search ({@link #indexOf} and {@link
 #lastIndexOf}) and methods for conditional adds ({@link #addIfAbsent} and
 {@link #addAllAbsent}).
 這個類在List接口基礎上又擴展了很多API, 包括索引查找方法indexOf/lastIndesOf的額外重載方法, 
 以及按條件查找方法addIfAbsent/addAllAbsent.

下面看源碼分析其特性

  1. 與ArrayList一致的 transient 修飾的元素數組 private transient volatile Object[] elements, 降低其序列化時的內存開銷;

  2. 無鎖讀/有鎖寫的設計使其在保證同步特性的同時降低了性能損耗public synchronized E remove(int index) public synchronized boolean add(E e) public E get(int index) public boolean contains(Object o);

  3. 迭代器只允許讀, 不允許寫

    static class CowIterator<E> implements ListIterator<E> {
        public void add(E object) {
            throw new UnsupportedOperationException();
        }
        @SuppressWarnings("unchecked")
        public E next() {
            if (index < to) {
                return (E) snapshot[index++];
            } else {
                throw new NoSuchElementException();
            }
        }
        public void set(E object) {
            throw new UnsupportedOperationException();
        }
    }
    
  4. 讀性能很好, 而寫操作執行了clone以及大量的arraycopy, 性能較差(類似於ArrayList);

    public E get(int index) {
        return (E) elements[index];
    }
    public synchronized E set(int index, E e) {
        Object[] newElements = elements.clone();
        @SuppressWarnings("unchecked")
        E result = (E) newElements[index];
        newElements[index] = e;
        elements = newElements;
        return result;
    }
    /**
    * 利用COW機制實時更改容器大小
    */
    public synchronized boolean add(E e) {
        Object[] newElements = new Object[elements.length + 1];
        System.arraycopy(elements, 0, newElements, 0, elements.length);
        newElements[elements.length] = e;
        elements = newElements;
        return true;
    }
    public synchronized void add(int index, E e) {
        Object[] newElements = new Object[elements.length + 1];
        System.arraycopy(elements, 0, newElements, 0, index);
        newElements[index] = e;
        System.arraycopy(elements, index, newElements, index + 1, elements.length - index);
        elements = newElements;
    }
    private void removeRange(int from, int to) {
        Object[] newElements = new Object[elements.length - (to - from)];
        System.arraycopy(elements, 0, newElements, 0, from);
        System.arraycopy(elements, to, newElements, from, elements.length - to);
        elements = newElements;
    }
    
  5. CopyOnWriteArrayList 是 COW( 寫時拷貝 )機制的典範, COW機制是一種延時懶惰策略, Object[] newElements = new Object[elements.length + 1]; elements = newElements 首先創建一個新的數組, 複製操作完成之後, 再將原容器的引用指向新的容器(引用賦值是原子性的). 這樣做的好處是我們可以對CopyOnWrite容器進行併發的讀寫, 而不需要給讀加鎖, 提高了效率, 因爲讀操作時容器不會添加任何元素. 所以CopyOnWrite容器也是一種讀寫分離的思想, 讀和寫不同的容器. 這也是JMM(java內存模型)的要求, JMM相關知識請看我的另一篇博客java 關於volatile, 指令重排, synchronized的心得.

由上可見, CopyOnWriteArrayList 以較小的性能損耗實現了同步操作, 是多線程下要求頻繁讀少量寫的場景的最佳選擇.

另外再說一個上面提到過的 Collections.synchronizedList() , 這個類可以給任意List的基本共有方法賦予同步操作, 看一下源碼

     SynchronizedList(List<E> l) {
         super(l);
         list = l;
     }
     SynchronizedList(List<E> l, Object mutex) {
         super(l, mutex);
         list = l;
     }
	 @Override public void add(int location, E object) {
		  synchronized (mutex) {
	          list.add(location, object);
	      }
	  }
	  ...
	  @Override public E get(int location) {
	      synchronized (mutex) {
	          return list.get(location);
	      }
	  }

與 CopyOnWriteArrayList 相比 , Collections.synchronizedList(ArrayList) 的寫操作性能較好, 但讀操作 CopyOnWriteArrayList 更勝一籌, 而Vector並沒有什麼優勢, 參考: https://blog.csdn.net/zljjava/article/details/48139465


Stack

堆棧, 使用LIFO(後進先出)規則提供彈出/壓入操作的集合.
源碼裏的Stack有兩個, 一個是public class Stack<E> extends Vector<E>, 另一個是public class Stack<T> extends ArrayList<T>, 大同小異, 繼承 Vector 的 Stack 的方法加了 Synchronized 鎖實現同步, 下面摘錄三個核心方法

    /**
     * 獲取棧頂的元素, 但不移除
     */
    @SuppressWarnings("unchecked")
    public synchronized E peek() {
        try {
            return (E) elementData[elementCount - 1];
        } catch (IndexOutOfBoundsException e) {
            throw new EmptyStackException();
        }
    }

    /**
     * 彈出棧頂元素, 集合中不再保留
     */
    @SuppressWarnings("unchecked")
    public synchronized E pop() {
        if (elementCount == 0) {
            throw new EmptyStackException();
        }
        final int index = --elementCount;
        final E obj = (E) elementData[index];
        elementData[index] = null;
        modCount++;
        return obj;
    }

    /**
     * 把一個元素壓入棧頂
     */
    public E push(E object) {
        addElement(object);
        return object;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章