深入Java源碼分析Vector、ArrayList、LinkedList的特點和區別

深入Java源碼分析Vector、ArrayList、LinkedList的特點和區別?

* 三者區別:
  • ArrayList和Vector底層的數據結構基於【動態數組】實現的; LinkedList底層的數據結構是基於 【雙鏈表】實現的.
  • ArrayList和Vector實現了RandomAccess接口【支持隨機訪問】; LinkedList沒有實現RandomAccess接口【不支持隨機訪問】.
  • ArrayList和Vector隨機訪問數據效率高, 插入和刪除數據的效率低 (需要涉及元素移動) ; LinkedList插入和刪除操作效率高, 讀取數據效率很低 (需要從第一個元素開始遍歷).
  • Vector線程安全; ArrayList和LinkedList線程不安全.
* 一致特點:
  • ArrayList、Vector和LinkedList都實現了Serializable接口【都可以被序列化】.
  • ArrayList、Vector和LinkedList都實現了Cloneable接口【此接口只有聲明、沒有方法體、表示都支持克隆複製】.
  • ArrayList、Vector和LinkedList都繼承AbstractList抽象類 【都支持 “增刪改查” 操作】.
  • ArrayList、Vector和LinkedList都允許爲空、都允許有重複數據、都有序.
    ······

一. Vector源碼分析

向量類(Vector)實現了一個動態數組。和 ArrayList 很相似,但是兩者是不同的:

  1. Vector 是同步訪問的。
  2. Vector 包含了許多傳統的方法,這些方法不屬於集合框架。
  3. Vector線程安全; ArrayList線程不安全。
1. Vector的成員變量分析

Vector集合的底層爲數組,對Vector集合的操作,其實就是對底層elementCount數組的操作。JDK1.7源碼如下:

	//與ArrayList一樣,Vector的底層也是使用數組elementData進行存儲數據
    protected Object[] elementData;
	//當前elementData數組中元素的個數
    protected int elementCount;
	//當前elementData數組進行擴容的增量
    protected int capacityIncrement;
	//序列化版本號
    private static final long serialVersionUID = -2767605614048989439L;
2. Vector的構造方法分析

Vector構造方法有4個,分別是:

  1. 無參構造:Vector()【底層elementData數組的長度默認容量爲10,擴容增量爲0】
  2. 帶參構造:Vector(int initialCapacity),指定initialCapacity容量大小,創建Vector集合;
  3. 帶參構造:Vector(int initialCapacity, int capacityIncrement),指定initialCapacity容量大小和擴容增量大小,創建Vector集合;
  4. 帶參構造:Vector(Collection<? extends E> c),通過一個集合,創建Vector集合;

構造方法JDK1.7源碼如下:

    public Vector() {
    	//初始化elementData數組的容量長度爲initialCapacity==10,capacityIncrement ==0
        this(10);
    }
    public Vector(int initialCapacity) {
    	//初始化elementData數組的容量長度爲initialCapacity,capacityIncrement ==0
        this(initialCapacity, 0);
    }
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        //初始化elementData數組的容量長度爲initialCapacity
        this.elementData = new Object[initialCapacity];
        //設置elementData數組的擴容增量爲capacityIncrement
        this.capacityIncrement = capacityIncrement;
    }
    public Vector(Collection<? extends E> c) {
        elementData = c.toArray();
        elementCount = elementData.length;
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
    }
3. Vector的擴容源碼分析

例:add添加元素方法源碼的具體實現:JDK1.7源碼如下:

    public synchronized boolean add(E e) {
        modCount++;
        //元素e在存儲之前會調用ensureCapacityHelper(int minCapacity)方法
        ensureCapacityHelper(elementCount + 1);
        //把元素e存放在數組elementData[elementCount++]下標位置
        elementData[elementCount++] = e;
        return true;
    }

ensureCapacityHelper(int minCapacity)判斷是否需要擴容方法的實現:JDK1.7源碼如下:

    private void ensureCapacityHelper(int minCapacity) {
    	//元素個數size+1大於當前elementData.length時,Vector集合將進行擴容
        if (minCapacity - elementData.length > 0)
        	//true,擴容
            grow(minCapacity);
    }

grow(int minCapacity)擴容方法的實現:JDK1.7源碼如下:

   private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
        	//true,則newCapacity取兩者最大值
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
        	//newCapacity 最大值限定爲2147483647
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) 
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

小結:

  • Vector大量的方法基本上均加synchronized修飾,因此Vector是線程安全的
  • Vector集合創建時,會初試化成員elementData數組的容量長度和擴容增量,既elementData.length和capacityIncrement;
  • 在add元素時,元素個數size+1大於當前elementData.length時,Vector集合將進行擴容擴容標準根據capacityIncrement和elementData.length,以及當前elementCount元素個數來確定;
  • 擴容過程實際就是elementData數組按照newCapacity,進行數組的複製,生成1個新elementData數組,替換掉舊的elementData數組。

二. ArrayList源碼分析

ArrayList是最常見以及每個Java開發者最熟悉的集合類了。

  1. ArrayList底層的數據結構基於動態數組實現的;
  2. ArrayList實現了Cloneable和Serialiable接口,所以可以被克隆和序列化;
  3. ArrayList和Vector實現了RandomAccess接口,所以可以支持隨機訪問;
  4. ArrayList隨機訪問數據效率高, 插入和刪除數據的效率低(需要涉及元素移動);
  5. ArrayList允許爲空、允許有重複數據、有序;
  6. ArrayList線程不安全。
1. ArrayList的成員變量分析
  • :elementData是使用transient修飾的呢?
    我來說說我的看法:因爲ArrayList實現了Serializable接口,這意味着ArrayList是可以被序列化的。用transient修飾elementData意味着我不希望elementData數組被序列化。
    這是爲什麼?因爲序列化ArrayList的時候,ArrayList裏面的elementData未必是滿的,比方說elementData有10的大小,但是我只用了其中的3個,那麼是否有必要序列化整個elementData呢?顯然沒有這個必要,因此ArrayList中重寫了writeObject方法,每次序列化的時候調用這個方法,先調用defaultWriteObject()方法序列化ArrayList中的非transient元素,elementData不去序列化它,然後遍歷elementData,只序列化那些有的元素。
  • 好處【 加快了序列化的速度】、【減小了序列化之後的文件大小】

JDK1.7源碼如下:

	//序列化版本號
	private static final long serialVersionUID = 8683452581122892189L;
 	//存放元素的數組,被transient修飾
    transient Object[] elementData; 
 	//ArrayList當前元素的個數
    private int size;
    //數組最大長度
    private static final int MAX_ARRAY_SIZE = 2147483639;
2. ArrayList的構造方法分析

ArrayList構造方法有3個,分別是:

  1. 無參構造:ArrayList()【底層elementData數組的長度默認容量爲10】
  2. 帶參構造ArrayList(int i),指定i容量大小;
  3. 帶參構造:ArrayList(Collection collection),入參爲集合;

JDK1.7源碼如下:

	//沒有入參的構造方法,默認初始長度爲10,隨着存入的數據增多時,會調用擴容方法進行擴容
    public ArrayList()
    {
        this(10);
    }
    //制定初始化容量的構造方法
    public ArrayList(int i)
    {
        if(i < 0)
        {
            throw new IllegalArgumentException((new StringBuilder()).append("Illegal Capacity: ").append(i).toString());
        } else
        {
            elementData = new Object[i];
            return;
        }
    }
	//入參爲集合的構造方法
    public ArrayList(Collection collection)
    {
        elementData = collection.toArray();
        size = elementData.length;
        if(((Object) (elementData)).getClass() != [Ljava/lang/Object;)
            //使用 Arrays.copyOf方法拷創建一個 Object 數組
            elementData = Arrays.copyOf(elementData, size, [Ljava/lang/Object;);
    }
3.ArrayList的增刪改查分析

ArrayList隨機訪問數據效率高, 插入和刪除數據的效率低(需要涉及元素移動),這與LinkedList相反。JDK1.7源碼如下:

    /**添加數據,在i下標下添加obj元素*/
    public void add(int i, Object obj)
    {
        rangeCheckForAdd(i);
        //判斷是否需要擴容
        ensureCapacityInternal(size + 1);
        //System,arraycopy方法做一個整體的複製,向後移動位置
        System.arraycopy(((Object) (elementData)), i, ((Object) (elementData)), i + 1, size - i);
        elementData[i] = obj;
        size++;
    }
    /**刪除數據*/
    public Object remove(int i)
    {
        rangeCheck(i);
        modCount++;
        Object obj = elementData(i);
        int j = size - i - 1;
        if(j > 0)
        	////System,arraycopy方法做一個整體的複製,向前移動位置
            System.arraycopy(((Object) (elementData)), i + 1, ((Object) (elementData)), i, j);
        elementData[--size] = null;
        return obj;
    }
    /**獲取數據*/
    public Object get(int i)
    {
    	//校驗下標是否合法
        rangeCheck(i);
        //返回數據
        return elementData(i);
    }
    private void rangeCheck(int i)
    {
        if(i >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(i));
        else
            return;
    }
    Object elementData(int i)
    {
        return elementData[i];
    }
4. ArrayList的擴容源碼分析

調用ensureCapacityInternal(int minCapacity)方法對數組的容量進行判斷,如果不夠就調用grow(int minCapacity)方法對數組進行擴容,新容量默認爲老容量的1.5倍,如果老容量的1.5倍依然不夠,則將最小容量作爲新容量。JDK1.7源碼如下:

	//容量進行判斷,是都需要擴容
    private void ensureCapacityInternal(int i)
    {
        modCount++;
        if(i - elementData.length > 0)
            grow(i);
    }
	//擴容
    private void grow(int i)
    {
        int j = elementData.length;
        ////新容量爲老容量的1.5倍
        int k = j + (j >> 1);
        //如果新容量小於最小容量,則將最小容量賦值給新容量
        if(k - i < 0)
            k = i;
        //如果新容量大於閾值
        if(k - 2147483639 > 0)
            k = hugeCapacity(i);
        //新建的數組容量爲 k
        elementData = Arrays.copyOf(elementData, k);
    }

    private static int hugeCapacity(int i)
    {
        if(i < 0)
            throw new OutOfMemoryError();
        else
            return i <= 2147483639 ? 2147483639 : 2147483647;
    }

三. LinkedList源碼分析

LinkedList底層的數據結構是基於雙向鏈表的,每個節點分爲頭部、尾部以及業務數據,前一個節點尾部指向後一個節點的頭部,後一節點的頭部指向前一個節點的尾部。但是頭結點和尾節點比較特殊,頭結點的頭部沒有上一個結點,尾節點的尾部也沒有指向下一個結點。對應的就是下面圖示:
在這裏插入圖片描述

  1. LinkedList底層的數據結構是基於雙鏈表實現的;
  2. LinkedList實現了Cloneable和Serialiable接口,所以可以被克隆和序列化;
  3. LinkedList插入和刪除操作效率高, 讀取數據效率低(需要從第一個元素開始遍歷);
  4. LinkedList允許爲空、允許有重複數據、有序;
  5. LinkedList線程不安全。
1. LinkedList元素的存儲結構分析

在LinkedList中,每一個元素都是Node存儲,Node擁有一個存儲值的item與一個前驅prev和一個後繼next。JDK1.7源碼如下:

	// 典型的鏈表結構
    private static class Node
    {
        Object item;// 存儲元素
        Node next;// 指向上一個元素
        Node prev;// 指向下一個元素
        Node(Node node1, Object obj, Node node2)
        {
            item = obj;
            next = node2;
            prev = node1;
        }
    }
2. LinkedList構造函數與成員變量分析
  • 變量主要有4個:JDK1.7源碼如下:
	// 當前列表的元素個數
    transient int size;
    // 第一個元素
    transient Node first;
    // 最後一個元素
    transient Node last;
    // 序列化版本號
    private static final long serialVersionUID = 876323262645176354L;
  • LinkedList中的構造函數有兩個:JDK1.7源碼如下:
	//無參構造函數
    public LinkedList()
    {
        size = 0;
    }
	//入參爲集合的構造方法
    public LinkedList(Collection collection)
    {
        this();
        //將c中的元素都添加到此列表中
        addAll(collection);
    }
3.ArrayList的增刪改查分析
  • 添加數據add一:尾部添加 JDK1.7源碼如下:
    void linkLast(Object obj)
    {
        Node node1 = last;
        //創建一個尾節點
        Node node2 = new Node(node1, obj, null);
        last = node2;
        if(node1 == null)
            first = node2;
        else
            node1.next = node2;
        size++;
        modCount++;
    }

實現原理如下圖:
在這裏插入圖片描述

  • 添加數據add二:指定位置添加 JDK1.7源碼如下:
    void linkBefore(Object obj, Node node1)
    {
        Node node2 = node1.prev;
        Node node3 = new Node(node2, obj, node1);
        node1.prev = node3;
        if(node2 == null)
            first = node3;
        else
            node2.next = node3;
        size++;
        modCount++;
    }

實現原理如下圖:
在這裏插入圖片描述

  • 刪除數據remove JDK1.7源碼如下:
	//刪除數據
    public Object remove(int i)
    {
        checkElementIndex(i);
        return unlink(node(i));
    }
    //修改指向
    Object unlink(Node node1)
    {
        Object obj = node1.item;
        Node node2 = node1.next;
        Node node3 = node1.prev;
        if(node3 == null)
        {
            first = node2;
        } else
        {
            node3.next = node2;
            node1.prev = null;
        }
        if(node2 == null)
        {
            last = node3;
        } else
        {
            node2.prev = node3;
            node1.next = null;
        }
        node1.item = null;
        size--;
        modCount++;
        return obj;
    }

實現原理如下圖:
在這裏插入圖片描述

  • 查詢數據get:【從第一個元素開始遍歷鏈表查找數據,效率低】 JDK1.7源碼如下:
	//查詢數據
    public Object get(int i)
    {
        checkElementIndex(i);
        return node(i).item;
    }
    //從第一個元素開始遍歷鏈表查找數據
    Node node(int i)
    {
        if(i < size >> 1)
        {
            Node node1 = first;
            for(int j = 0; j < i; j++)
                node1 = node1.next;

            return node1;
        }
        Node node2 = last;
        for(int k = size - 1; k > i; k--)
            node2 = node2.prev;

        return node2;
    }

實現原理如下圖:
在這裏插入圖片描述

……
幫助他人,快樂自己,最後,感謝您的閱讀!
所以如有紕漏或者建議,還請讀者朋友們在評論區不吝指出!
……
個人網站…知識是一種寶貴的資源和財富,益發掘,更益分享…

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