java collection 集合之list

collection中幾種常用的集合類型特點

| 集合類型 | 是否允許空 | 是否允許重複數據 | 是否有序 | 是否線程安全
| ------------- |-------------| -----| -----|
| ArrayList | 是| 是 | 是 | 否
| Vector | 是| 是 | 是 | 是(相對線程安全)
| LinkedList | 是| 是 | 是 | 否
|CopyOnWriteArrayList | 是| 是 | 是 | 是(絕對的線程安全)

所有代碼均是jdk1.8版本下
底層實現

ArrayList

	private transient Object[] elementData;
	是由一個transient修飾的object數組實現
	ps:transient是不希望elementData被序列化,因爲arrayList初始化之後是有固定的長度的,如默認的10,但elementData裏面可能只有3個元素,沒必要序列化整個elementData

Vector

   與ArrayList類似,vector底層也是由Object[] 數組實現
   protected Object[] elementData;
   不同的是 vector是沒有transient來修飾的。意味了在序列化的時候會將數組中爲空的元素也序列化

LinkedList

	LinkedList的各項特質與ArrayList一致,但是底層實現上是完全不一樣的。
	LinkedList是基於鏈表實現,是一種雙向鏈表,鏈表是一種線性的存儲結構,鏈表中的每個元素都由三部分組成。 
	Node<E> prev, E element, Node<E> next
	prev和next分別指向前一個存儲單元和後一個存儲單元的引用地址
	

CopyOnWriteArrayList

	顧名思義,這個類就是arraylist的併發版本,位於juc包下。根據CopyOnWrite可以看出這個類是讀寫分離的。任何可變的操作都是伴隨着複製。
	底層也是由
	private transient volatile Object[] array;
	輕量級volatile修飾的一個Object數組
	

初始化

ArrayList

	 /**如不指定初始化集合大小,默認是10,這個10不是new ArrayList<>()初始化的時候賦值的,是在第一次調用的時候賦值*/
	List<Object> list = new ArrayList<>(); 
	/**初始化一個大小爲16的數組*/
	List<Object> list1 = new ArrayList<>(16);

Vector

	 /**不指定容量的大小, 會調new Vector<>(10) 方法創建一個大小爲10的數組*/
      Vector<Object> vector = new Vector<>();
      /**指定容量大小*/
      Vector<Object> vector1 = new Vector<>(16);
      /**指定容量大小和增長因子*/
      Vector<Object> vector2 = new Vector<>(16, 2);

      List<Object> list = new ArrayList<>();
      /**將Collection的集合初始化到Vector中,大小是list的長度*/
      Vector<Object> vector3 = new Vector<>(list);

LinkedList

	/**linkedlist的初始化沒有任何容量跟增長因子*/
	List<Object> list = new LinkedList<>();

    List<Object> tempList= new ArrayList<>();
    /**將Collection的集合初始化到LinkedList中,大小是list的長度*/
    List<Object> list1 = new LinkedList<>(tempList);
	/**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;
        }
    }

CopyOnWriteArrayList

	private transient volatile Object[] array;
    /**getter/setter方法*/
    final Object[] getArray() {
        return array;
    }
    final void setArray(Object[] a) {
        array = a;
    }
    /**創建一個空數組*/
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }
    與array list的區別是並沒有初始大小的數組,而是空數組

添加

ArrayList

    transient Object[] elementData;
    //默認容量大小
    private static final int DEFAULT_CAPACITY = 10;
    //默認空數組
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //數組的長度
    private int size;
	//最大的數組長度
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * 在數組隊尾添加一個元素
     */
    public boolean add(E e) {
        //確保內部容量
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    /**
     * 在指定下標處添加一個元素
     */
    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++;
    }

	/**確保當前數組的長度可用*/
    private void ensureCapacityInternal(int minCapacity) {
        //如果當前數組爲空,則獲取 默認容量 跟 參數容量中大的一個
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        //當前容器修改的次數
        modCount++;
        /**如果不可用了,增加容量*/
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        /**  
         * 看到好多文檔,博客上直接寫明擴容的長度就是原來的1.5倍,
         * 個人理解,oldCapacity的值可能是 0,10,15,22, 33, 48, 62 並不是嚴格意義上的1.5倍
         * 初始後的第一次擴容,oldCapacity = 10 ,二進制是 1010 右移 0101是5,新容量是15
         * 第二次擴容 oldCapacity 15 二進制是1111 右移 1 得 0111 是 7 新容量是22 
         */
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

	新增的最後都調用了Arrays.copyOf System.arrayCopy 等函數,創建一個新的數組,將原來的數組賦值到新數組上

Vector

	與ArrayList實現方式類似,不同的方法簽名上是有Synchronized來修飾的。
	//擴容的方式
	private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        /**如果指定了增長因子,則每次擴容加上增長因子,如果沒有,則翻倍*/
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

LinkedList

public boolean add(E e) {
        linkLast(e);
        return true;
    }

    void linkLast(E e) {
	    //獲取當前鏈表中最後一個元素
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

CopyOnWriteArrayList

	final transient ReentrantLock lock = new ReentrantLock();
	public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            /**核心動作,複製一個新數組*/
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
    
    public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            Object[] newElements;
            int numMoved = len - index;
            if (numMoved == 0)
                newElements = Arrays.copyOf(elements, len + 1);
            else {
                newElements = new Object[len + 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            newElements[index] = element;
            setArray(newElements);
        } finally {
            lock.unlock();
        }
    }
    對當前操作的方法加上重入鎖,每次有更新動作都會複製一個新數組,並將原數組的引用轉移到新數組上。

刪除

ArrayList

	/**
		移除指定下標的元素
	*/
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        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

        return oldValue;
    }
	在指定位置插入元素的時間複雜度是o(size - index)

    /**
     * 移除第一個等於o的元素
     */
    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;
    }

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    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
    }

Vector

	與ArrayList實現方式類似,不同的方法簽名上是有Synchronized來修飾的。

LinkedList

public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

    public boolean remove(Object o) {
        if (o == null) {
            for (Node<E> x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node<E> x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }
    Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
    移除操作跟新增操作都是操作node的prev跟next將它們的引用指向新的元素
    爲了找到插入,刪除的元素,linkedlist的操作方式是分別從首尾兩端向index處進行遍歷,時間複雜度是o(index)

CopyOnWriteArrayList

public E remove(int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }

總結

ArrayList
從ArrayList的源碼不難看出它的核心方法是擴容和數組複製
優點如下:

  1. 底層是數組實現,是一種隨機訪問模式,實現了RandomAccess接口,查找速度快
  2. 順序添加元素的時候快,
    缺點:
    插入和刪除時涉及到數組的複製拷貝,如果數組的元素較多,會影響性能。

LinkedList
1.插入效率
順序插入時ArrayList會比較快,因爲linkedlist每次新增一個對象出來,如果對象比較大,那麼new的時間勢必會長一點,再加上一些引用賦值的操作,所以順序插入LinkedList必然慢於ArrayList。
ArrayLIst隨機插入的時間複雜度是o(size - index)
LinkedList隨機插入的時間複雜度是o(index) index 最大不會超過size的一半
如果待插入、刪除的元素是在數據結構的前半段尤其是非常靠前的位置的時候,LinkedList的效率將大大快過ArrayList,因爲ArrayList將批量copy大量的元素;越往後,對於LinkedList來說,因爲它是雙向鏈表,所以在第2個元素後面插入一個數據和在倒數第2個元素後面插入一個元素在效率上基本沒有差別,但是ArrayList由於要批量copy的元素越來越少,操作速度必然追上乃至超過LinkedList
2.內存會比ArrayList耗費更多一點
3.使用各自遍歷效率最高的方式,ArrayList的遍歷效率會比LinkedList的遍歷效率高一些
ps:如果使用普通for循環遍歷LinkedList,其遍歷速度將慢得令人髮指。

CopyOnWriteArrayList
優點:
絕對的線程安全
缺點:
每次寫操作都會伴隨數組的複製,性能開銷大
帶來的兩個很重要的分佈式理念:
讀寫分離和最終一致

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章