「 深刻淺出 」集合List

第一篇文章 「 深刻淺出 」java集合Collection和Map 主要講了對集合的總體介紹,本篇文章主要講List相對於Collection新增的一些重要功能以及其重要子類ArrayList、LinkedList、Vectorjava

1、List集合

關於List集合的介紹與方法,可參考第一篇文章 「 深刻淺出 」java集合Collection和Mapnode

迭代方法ListIterator

相對於其它集合,List集合添加了一種新的迭代方法ListIterator
ListIterator的方法以下:面試

image

ListIterator接口在Iterator接口基礎上增長了以下方法:
boolean hasPrevious(): 若是以逆向遍歷列表。若是迭代器有上一個元素,則返回 true。
E previous():返回迭代器的前一個元素。
void add(Object o):將指定的元素插入列表。
int nextIndex():下一個索引號
int previousIndex():上一個索引號
void set(E e):修改迭代器當前元素的值
void add(E e):在迭代器當前位置插入一個元素數組

ListIterator接口比Iterator接口多了兩個功能:
1.ListIterator可在遍歷過程當中新增和修改
2.ListIterator可逆向遍歷安全

使用示例以下:數據結構

public class ListIteratorDemo {
    public static void main(String[] args) {
        // 建立列表
        List<Integer> list = new ArrayList<Integer>();
        // 向列表中增長10個元素
        for (int i = 0; i < 10; i++) {
            list.add(i);
        }
        // 得到ListIterator對象
        ListIterator<Integer> it = list.listIterator();
        // 正序遍歷修改與新增
        while (it.hasNext()) {
            Integer i = it.next();
            //修改元素值
            it.set(i+1);

            if(i == 5 ){
                //新增元素值
                it.add(55);
            }

            //! it.set(i+1);
            // 注意:若是修改的代碼在這個位置會報錯
            //set操做不能放在add操做以後
            // 這裏不作解析,欲知詳情,請看源碼
        }

        System.out.println("正向遍歷");
        //正向遍歷
        for(Integer i:list){
            System.out.println(i+" ");
        }

        System.out.println("逆向遍歷");
        //逆向遍歷
        //通過上面迭代器it遍歷後,迭代器it已到達最後一個節點
        while (it.hasPrevious()) {
            System.out.println(it.previous() + " ");
        }
    }
}

2、ArrayList和Vector

ArrayList和Vector很類似,因此就一塊兒介紹了框架

ArrayList和Vector類都是基於數組實現的List類,因此ArrayList和Vector類封裝了一個動態的、容許再分配的Object[]數組。ArrayList和Vector對象使用initalCapacity參數來設置該數組的長度,當向ArrayList和Vector中添加元素超過了該數組的長度時,它們的initalCapacity會自動增長。函數

下面咱們經過閱讀JDK 1.8 ArrayList源碼來了解ArrayList性能

無參構造函數

默認初始化爲容量爲10學習

   /**
     * Constructs an empty list with an initial capacity of ten。
       意思是:構造一個空數組,默認的容量爲10
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

有參構造函數

建立指定容量的ArrayList

//動態Object數組,用來保存加入到ArrayList的元素
Object[] elementData;

//ArrayList的構造函數,傳入參數爲數組大小
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
             //建立一個對應大小的數組對象
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //傳入數字爲0,將elementData 指定爲一個靜態類型的空數組
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

add方法

執行add方法時,先確保容量足夠大,若容量不夠,則會進行擴容;
擴容大小爲原來的1.5倍(這個須要注意一下,面試常常考)

//添加元素e
public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    //將對應索引下的元素賦值爲e:
    elementData[size++] = e;
    return true;
}
//獲得最小擴容量
private void ensureCapacityInternal(int minCapacity) {
    //若是此時ArrayList是空數組,則將最小擴容大小設置爲10:
    if (elementData == EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    //判斷是否須要擴容:
    ensureExplicitCapacity(minCapacity);
}
//判斷是否須要擴容
private void ensureExplicitCapacity(int minCapacity) {
    //操做數+1
    modCount++;
    //判斷最小擴容容量-數組大小是否大於0:
    if (minCapacity - elementData.length > 0)
        //擴容:
        grow(minCapacity);
}
//ArrayList動態擴容的核心方法:
private void grow(int minCapacity) {
    //獲取現有數組大小:
    int oldCapacity = elementData.length;
    //位運算,獲得新的數組容量大小,爲原有的1.5倍:
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //若是新擴容的大小依舊小於傳入的容量值,那麼將傳入的值設爲新容器大小:
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;

    //若是新容器大小,大於ArrayList最大長度:
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        //計算出最大容量值:
        newCapacity = hugeCapacity(minCapacity);
    //數組複製:
    elementData = Arrays.copyOf(elementData, newCapacity);
}
//計算ArrayList最大容量:
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0)
        throw new OutOfMemoryError();
    //若是新的容量大於MAX_ARRAY_SIZE
    //將會調用hugeCapacity將int的最大值賦給newCapacity
    return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
}

remove方法

有如下兩種刪除方法:

  • remove(int index)是針對於索引來進行刪除,不須要去遍歷整個集合,效率更高;
  • remove(Object o)是針對於對象來進行刪除,須要遍歷整個集合進行equals()方法比對,因此效率較低;

不過,不管是哪一種形式的刪除,最終都會調用System.arraycopy()方法進行數組複製操做,等同於移動數組位置,因此效率都會受到影響

//在ArrayList的移除index位置的元素
public E remove(int index) {
    //檢查索引是否合法:不合法拋異常
    rangeCheck(index);
    //操做數+1:
    modCount++;
    //獲取當前索引的value:
    E oldValue = elementData(index);
    //獲取須要刪除元素 到最後一個元素的長度,也就是刪除元素後,後續元素移動的個數;
    int numMoved = size - index - 1;
    //若是移動元素個數大於0 ,也就是說刪除的不是最後一個元素:
    if (numMoved > 0)
        // 將elementData數組index+1位置開始拷貝到elementData從index開始的空間
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    //size減1,並將最後一個元素置爲null
    elementData[--size] = null;
    //返回被刪除的元素:
    return oldValue;
}

//在ArrayList的移除對象爲O的元素,不返回被刪除的元素:
public boolean remove(Object o) {
    //若是o==null,則遍歷集合,判斷哪一個元素爲null:
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                //快速刪除,和前面的remove(index)同樣的邏輯
                fastRemove(index);
                return true;
            }
    } else {
        //同理:
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

//快速刪除:
private void fastRemove(int index) {
    //操做數+1
    modCount++;
    //獲取須要刪除元素 到最後一個元素的長度,也就是刪除元素後,後續元素移動的個數;
    int numMoved = size - index - 1;
    //若是移動元素個數大於0 ,也就是說刪除的不是最後一個元素:
    if (numMoved > 0)
        // 將elementData數組index+1位置開始拷貝到elementData從index開始的空間
        System.arraycopy(elementData, index+1, elementData, index, numMoved);
    //size減1,並將最後一個元素置爲null
    elementData[--size] = null;
}

get方法

經過elementData()方法獲取對應索引元素,在返回時候進行類型轉換

//獲取index位置的元素
public E get(int index) {
    //檢查index是否合法:
    rangeCheck(index);
    //獲取元素:
    return elementData(index);
}
//獲取數組index位置的元素:返回時類型轉換
E elementData(int index) {
    return (E) elementData[index];
}

set方法

經過elementData獲取舊元素,再設置新元素值相應index位置,最後返回舊元素

//設置index位置的元素值了element,返回該位置的以前的值
public E set(int index, E element) {
    //檢查index是否合法:判斷index是否大於size
    rangeCheck(index);
    //獲取該index原來的元素:
    E oldValue = elementData(index);
    //替換成新的元素:
    elementData[index] = element;
    //返回舊的元素:
    return oldValue;
}

調整容量大小

ArrayList還提供了兩個額外的方法來調整其容量大小

  • void ensureCapacity(int minCapacity): 增長容量,以確保它至少可以容納最小容量參數所指定的元素數。
  • void trimToSize():將容量調整爲列表的當前大小。

Vector實現原理與ArrayList基本相同,可參考上述內容

ArrayList和Vector的主要區別

  • ArrayList是線程不安全的,Vector是線程安全的。
  • Vector的性能比ArrayList差。

LinkedList

LinkedList是基於雙向鏈表實現的,內部存儲主要是Node對象,該對象存儲着元素值外,還指向上一節點和下一節點。
注意,由於LinkedList是基於鏈表實現的,沒有容量的說法,因此更沒有擴容之說

集合基礎框架

public class LinkedList<E>
        extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable {

    //LinkedList的元素個數:
    transient int size = 0;

    //LinkedList的頭結點:Node內部類
    transient java.util.LinkedList.Node<E> first;

    //LinkedList尾結點:Node內部類
    transient java.util.LinkedList.Node<E> last;

    //空實現:頭尾結點均爲null,鏈表不存在
    public LinkedList() {
    }

    //調用添加方法:
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

    //節點的數據結構,包含先後節點的引用和當前節點
    private static class Node<E> {
        //結點元素:
        E item;
        //結點後指針
        java.util.LinkedList.Node<E> next;
        //結點前指針
        java.util.LinkedList.Node<E> prev;

        Node(java.util.LinkedList.Node<E> prev, E element, java.util.LinkedList.Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
}
LinkedList的經常使用方法只作簡單介紹不貼源碼,具體可自行看源碼

add方法

LinkedList有兩種添加方法

  • add(E e)鏈表最後添加一個元素
  • add(int index, E element)指定位置下添加一個元素;

LinedList添加元素主要分爲如下步驟:
1.將添加的元素轉換爲LinkedList的Node對象節點;
2.增長該Node節點的先後引用,即該Node節點的prev、next屬性,讓其分別指上、下節點;
3.修改該Node節點的先後Node節點中pre/next屬性,使其指向該節點。

image

remove方法

LinkedList的刪除也提供了2種形式

  • remove(int index)直接經過索引刪除元素
  • remove(Object o)經過對象刪除元素,須要逐個遍歷LinkedList的元素,重複元素只刪除第一個:

刪除後,須要修改上節點的next指向當前下一節點,下節點的prev指向當前上一節點

set方法

set(int index, E element)方法經過node(index)獲取到相應的Node,再修改元素的值

get方法

這是咱們最經常使用的方法,其中核心方法node(int index),須要從頭遍歷或從後遍歷找到相應Node節點
在經過node(int index)獲取到對應節點後,返回節點中的item屬性,該屬性就是咱們所保存的元素。

//獲取相應角標的元素:
public E get(int index) {
    //檢查索引是否正確:
    checkElementIndex(index);
    //獲取索引所屬結點的 元素值:
    return node(index).item;
}
//獲取對應角標所屬於的結點:
java.util.LinkedList.Node<E> node(int index) {
    //位運算:若是位置索引小於列表長度的一半,則從頭開始遍歷;不然,從後開始遍歷;
    if (index < (size >> 1)) {
        java.util.LinkedList.Node<E> x = first;
        //從頭結點開始遍歷:遍歷的長度就是index的長度,獲取對應的index的元素
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        //從集合尾結點遍歷:
        java.util.LinkedList.Node<E> x = last;
        //一樣道理:
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}

ArrayList和LinkedList的主要區別

  • ArrayList基於數組實現的,LinkedList是基於雙向鏈表實現的
  • ArrayList隨機訪問效率高,隨機插入、隨機刪除效率低,須要移動元素位置
    LinkedList隨機插入、隨機刪除效率高,隨機訪問效率低,因須要遍歷鏈表

好叻,搞完,溜了溜了
下一期爲<集合Set>,敬請期待

近期推薦:

好人?壞人?作真實的人
「 優質資源 」收藏!最新精選優質資源!
java當心機(5)| 淺談類成員初始化順序

更多精彩內容,可閱讀原文

您的點贊、轉發是對我最大的支持!

image

image

 THANDKS

  • End -

一個立志成大腿而天天努力奮鬥的年輕人

伴學習伴成長,成長之路你並不孤單!

掃描二維碼,關注公衆號

相關文章
相關標籤/搜索