由淺入深理解java集合(三)——集合 List

第一篇文章中介紹了List集合的一些通用知識。本篇文章將集中介紹了List集合相比Collection接口增加的一些重要功能以及List集合的兩個重要子類ArrayList及LinkedList。

一、List集合

關于List集合的介紹及方法,可以參考第一篇文章

List集合判斷元素相等的標準

List判斷兩個對象相等只要通過equals()方法比較返回true即可(關于equals()方法的詳解可以參考第二篇文章中的內容)。
下面以用代碼具體展示。
創建一個Book類,并重寫equals()方法,如果兩個Book對象的name屬性相同,則認為兩個對象相等。

public class Book {
    public String name;

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Book other = (Book) obj;
        if (this.name == other.name) {
            return true;
        } 
        return false;
    }
}

向List集合中加入book1對象,然后調用remove(Object o)方法,從集合中刪除指定對象,這個時候指定的對象是book2。

public static void main(String[] args){
        Book book1 = new Book();
        book1.name = "Effective Java";
        Book book2 = new Book();
        book2.name = "Effective Java";
        List<Book> list = new ArrayList<Book>();
        list.add(book1);
        list.remove(book2);
        System.out.println(list.size());
    }

輸出結果:

0

可見把book1對象從集合中刪除了,這表明List集合判斷兩個對象相等只要通過equals()方法比較返回true即可。
與Set不同,List還額外提供了一個listIterator()方法,該方法返回一個ListIterator對象。下面具體介紹下ListIterator。

ListIterator

ListIterator接口在Iterator接口基礎上增加了如下方法:

**boolean hasPrevious(): **如果以逆向遍歷列表。如果迭代器有上一個元素,則返回 true。
Object previous():返回迭代器的前一個元素。
void add(Object o):將指定的元素插入列表(可選操作)。

與Iterator相比,ListIterator增加了前向迭代的功能,還可以通過add()方法向List集合中添加元素。

二、ArrayList

既然要介紹ArrayList,那么就順帶一起介紹Vector。因為二者的用法功能非常相似,可以一起了解比對。

ArrayList簡介

ArrayList和Vector作為List類的兩個典型實現,完全支持之前介紹的List接口的全部功能。
ArrayList和Vector類都是基于數組實現的List類,所以ArrayList和Vector類封裝了一個動態的、允許再分配的Object[]數組。ArrayList或Vector對象使用initalCapacity參數來設置該數組的長度,當向ArrayList或Vector中添加元素超過了該數組的長度時,它們的initalCapacity會自動增加。下面我們通過閱讀JDK 1.8 ArrayList源碼來了解這些內容。

ArrayList的本質

當以List<Book> list = new ArrayList<Book>(3);方式創建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);
        }
    }

當以List<Book> list = new ArrayList<Book>();方式創建ArrayList集合時,不指定集合的大小

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

在這里可以看出private static final int DEFAULT_CAPACITY = 10;默認容量確實為10。
當向數組中添加元素list.add(book1);時:
先調用add(E e)方法

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 數組的大小增加1
        elementData[size++] = e;
        return true;
    }

在該方法中,先調用了一個ensureCapacityInternal()方法,顧名思義:該方法用來確保數組中是否還有足夠容量。
經過一系列方法(不必關心),最后有個判斷:如果剩余容量足夠存放這個數據,則進行下一步,如果不夠,則需要執行一個重要的方法:

 private void grow(int minCapacity) {
        //......省略部分內容  主要是為了生成大小合適的newCapacity
       //下面這行就是進行了數組擴容
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

由此,我們就清楚地明白了,ArrayList是一個動態擴展的數組,Vector也同樣如此。
如果開始就知道ArrayList或Vector集合需要保存多少個元素,則可以在創建它們時就指定initalCapacity的大小,這樣可以提高性能。
此外,ArrayList還提供了兩個額外的方法來調整其容量大小:

void ensureCapacity(int minCapacity): 如有必要,增加此 ArrayList 實例的容量,以確保它至少能夠容納最小容量參數所指定的元素數。
void trimToSize():將此 ArrayList 實例的容量調整為列表的當前大小。

ArrayList和Vector的區別

1.ArrayList是線程不安全的,Vector是線程安全的。
2.Vector的性能比ArrayList差。

Stack

Stack是Vector的子類,用戶模擬“棧”這種數據結構,“棧”通常是指“后進先出”(LIFO)的容器。最后“push”進棧的元素,將被最先“pop”出棧。如下圖所示:



Stack類里提供了如下幾個方法:



Stack與Vector一樣,是線程安全的,但是性能較差,盡量少用Stack類。如果要實現棧”這種數據結構,可以考慮使用LinkedList(下面就會介紹)。

ArrayList的遍歷方式

ArrayList支持3種遍歷方式
(01) 第一種,通過迭代器遍歷

Integer value = null;
Iterator iter = list.iterator();
while (iter.hasNext()) {
    value = (Integer)iter.next();
}

(02) 第二種,隨機訪問,通過索引值去遍歷
由于ArrayList實現了RandomAccess接口,它支持通過索引值去隨機訪問元素。

Integer value = null;
int size = list.size();
for (int i=0; i<size; i++) {
    value = (Integer)list.get(i);        
}

(03) 第三種,for循環遍歷

Integer value = null;
for (Integer integ:list) {
    value = integ;
}

遍歷ArrayList時,使用隨機訪問(即,通過索引序號訪問)效率最高,而使用迭代器的效率最低。具體可以測試下。

三、LinkedList

LinkedList簡介

LinkedList類是List接口的實現類——這意味著它是一個List集合,可以根據索引來隨機訪問集合中的元素。除此之外,LinkedList還實現了Deque接口,可以被當作成雙端隊列來使用,因此既可以被當成“棧"來使用,也可以當成隊列來使用。
LinkedList的實現機制與ArrayList完全不同。ArrayList內部是以數組的形式來保存集合中的元素的,因此隨機訪問集合元素時有較好的性能;而LinkedList內部以鏈表的形式來保存集合中的元素,因此隨機訪問集合元素時性能較差,但在插入、刪除元素時性能比較出色。
由于LinkedList雙端隊列的特性,所以新增了一些方法。

LinkedList方法

void addFirst(E e):將指定元素插入此列表的開頭。
void addLast(E e): 將指定元素添加到此列表的結尾。
E getFirst(E e): 返回此列表的第一個元素。
E getLast(E e): 返回此列表的最后一個元素。
boolean offerFirst(E e): 在此列表的開頭插入指定的元素。
boolean offerLast(E e): 在此列表末尾插入指定的元素。
E peekFirst(E e): 獲取但不移除此列表的第一個元素;如果此列表為空,則返回 null。
E peekLast(E e): 獲取但不移除此列表的最后一個元素;如果此列表為空,則返回 null。
E pollFirst(E e): 獲取并移除此列表的第一個元素;如果此列表為空,則返回 null。
E pollLast(E e): 獲取并移除此列表的最后一個元素;如果此列表為空,則返回 null。
E removeFirst(E e): 移除并返回此列表的第一個元素。
boolean removeFirstOccurrence(Objcet o): 從此列表中移除第一次出現的指定元素(從頭部到尾部遍歷列表時)。
E removeLast(E e): 移除并返回此列表的最后一個元素。
boolean removeLastOccurrence(Objcet o): 從此列表中移除最后一次出現的指定元素(從頭部到尾部遍歷列表時)。

下面我們就以閱讀源碼的方式來了解LinkedList內部是怎樣維護鏈表的。

LinkedList本質

LinkedList調用默認構造函數,創建一個鏈表。由于維護了一個表頭,表尾的Node對象的變量。可以進行后續的添加元素到鏈表中的操作,以及其他刪除,插入等操作。也因此實現了雙向隊列的功能,即可向表頭加入元素,也可以向表尾加入元素

//成員變量:表頭,表尾
 transient Node<E> first;
transient Node<E> last;
//默認構造函數,表示創建一個空鏈表
public LinkedList() {
    }

下面來了解Node類的具體情況

private static class Node<E> {
        //表示集合元素的值
        E item;
       //指向下個元素
        Node<E> next;
     //指向上個元素
        Node<E> prev;
...................................省略
    }

由此可以具體了解鏈表是如何串聯起來并且每個節點包含了傳入集合的元素。
下面以增加操作,具體了解LinkedList的工作原理。

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

調用linkLast(e);方法,默認向表尾節點加入新的元素

 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++;
    }

更新表尾節點,建立連接。其他操作類似,維護了整個鏈表。
下面具體來看,如何將“雙向鏈表和索引值聯系起來的”?

 public E get(int index) {
        checkElementIndex(index);//檢查索引是否有效
        return node(index).item;
    }

調用了node(index)方法返回了一個Node對象,其中node(index)方法具體如下

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;
        }
    }

首先會比較“index”和“雙向鏈表長度的1/2”;若前者小,則從鏈表頭開始往后查找,直到index位置;否則,從鏈表末尾開始先前查找,直到index位置。這就是“雙線鏈表和索引值聯系起來”的方法。
到此我們便會明白,LinkedList在插入、刪除元素時性能比較出色,隨機訪問集合元素時性能較差。

LinkedList遍歷方式

LinkedList支持多種遍歷方式。
1.通過迭代器遍歷LinkedList
2通過快速隨機訪問遍歷LinkedList
3.通過for循環遍歷LinkedList
4.通過pollFirst()遍歷LinkedList
5.通過pollLast()遍歷LinkedList
6通過removeFirst()遍歷LinkedList
7.通過removeLast()遍歷LinkedList
實現都比較簡單,就不貼代碼了。
其中采用逐個遍歷的方式,效率比較高。采用隨機訪問的方式去遍歷LinkedList的方式效率最低。

LinkedList也是非線程安全的。

四、ArrayList與LinkedList性能對比

ArrayList 是一個數組隊列,相當于動態數組。它由數組實現,隨機訪問效率高,隨機插入、隨機刪除效率低。ArrayList應使用隨機訪問(即,通過索引序號訪問)遍歷集合元素。
LinkedList 是一個雙向鏈表。它也可以被當作堆棧、隊列或雙端隊列進行操作。LinkedList隨機訪問效率低,但隨機插入、隨機刪除效率高。LinkedList應使用采用逐個遍歷的方式遍歷集合元素。
如果涉及到“動態數組”、“棧”、“隊列”、“鏈表”等結構,應該考慮用List,具體的選擇哪個List,根據下面的標準來取舍。
(01) 對于需要快速插入,刪除元素,應該使用LinkedList。
(02) 對于需要快速隨機訪問元素,應該使用ArrayList。
(03) 對于“單線程環境” 或者 “多線程環境,但List僅僅只會被單個線程操作”,此時應該使用非同步的類(如ArrayList)。對于“多線程環境,且List可能同時被多個線程操作”,此時,應該使用同步的類(如Vector)。

由淺入深理解java集合(一)——集合框架 Collction、Map
由淺入深理解java集合(二)——集合 Set
由淺入深理解java集合(四)——集合 Queue
由淺入深理解java集合(五)——集合 Map
由淺入深理解java集合(六)——集合增刪改查的細節、性能及選擇推薦(待更新)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容

  • Collection接口 Collection接口是所有集合的祖先類。他有兩個構造方法,一個無參構造,一個是帶Co...
    夜幕繁華閱讀 603評論 0 0
  • Java集合 作為一個Developer,Java集合類是我們在工作中運用最多的、最頻繁的類。相比于數組(Arra...
    賈博巖閱讀 65,807評論 14 103
  • Collection ├List │├LinkedList │├ArrayList │└Vector │└Stac...
    AndyZX閱讀 887評論 0 1
  • 集合類簡介 為什么出現集合類?面向對象語言對事物的體現都是以對象的形式,所以為了方便對多個對象的操作,就要對對象進...
    阿敏其人閱讀 1,435評論 0 7
  • 親愛的小嘉咪寶貝: 今天的飛盤課累了吧?也爽了吧?看著你們在藍天綠地間奔跑跳躍,聽著你們的大呼小叫,我覺得生命好滋...
    陳穎_樂嘉媽媽閱讀 271評論 7 8