Java中的集合(五)繼承Collection的List接口


Java中的集合(五)繼承Collection的List接口

 一、List接口簡介

List是有序的Collection的,此接口能夠精確的控制每個元素插入的位置。用戶能夠根據索引(元素在List接口的中位置)訪問List中的元素,類似於Java中的數組。

List接口有如下特點:

    • 有序的集合。存儲順序和獲取元素的順序都是一致的;
    • 可重復。允許存儲重復的元素;
    • 提供索引。提供一些索引的方法,供用戶操作。

二、List類圖結構

 通過上面類圖可用看出,List接口下有4個實現類,分別為:LinkedList、ArrayList、Vector和Stack


三、List接口中帶索引的方法(特有):

 

 需要注意的是:操作索引時,一定要注意防止索引越界異常提示。

  • IndexOutOfBoundsException:索引越界異常,集合會報;
  • ArrayIndexOutOfBoundsException:數組索引越界異常;
  • StringIndexOutOfBoundsException:字符串索引越界異常。

四、ArrayList

(一)、簡介、繼承結構

ArrayList是List接口的實現類,也是最常用的集合類。底層數據結構是一個可變的動態數組,它允許任何符合規則的元素插入(包括null)。每一個ArrayList都有一個初始容量(10),該容量代表了數組的大小。隨着容器中的元素不斷增加,容器的大小也會隨着增加。在每次向容器中增加元素的同時都會進行容量檢查,當快溢出時,就會進行擴容操作。

如果我們明確所插入元素的多少,最好指定一個初始容量值,避免過多的進行擴容操作而浪費時間、效率。

1、ArrayList繼承結構

 

通過結構圖可以看出,ArrayList繼承自AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable這些接口。

    • 繼承AbstractList,實現了List接口:它是一個動態數組,提供了相關添加,刪除,修改和遍歷等功能;
    • 實現RandomAccess接口:提供隨機訪問的能力;RandmoAccess是Java中用來被List接口實現,為List提供快速訪問功能的。在ArrayList中,可以通過元素的序號快速獲取元素對象,這就是快速隨機訪問。
    • 實現Cloneable接口:覆蓋函數clone(),可以被克隆;
    • 實現Serializable接口:支持序列化和反序列化,可以通過序列化傳輸數據。

2、ArrayList的特性:

  • 增刪慢,查詢快(底層數據結構是數組);
  • 效率高,線程不安全的(非同步);
  • 擅長隨機訪問(實現了RandomAccess接口);

(二)、ArrayList構造方法

(三)、ArrayList的兩個重要的屬性:elementData 和 size

1、elementData:Object[]的數組,保存了添加到ArrayList中的元素。

  • 通過ArrayList()初始化ArrayList時,elementData的大小為默認值10,即Object[10];
  • 通過ArrayList(int initialCapacity)初始化ArrayList時,elementData的大小為指定值initialCapacity,即Object[initialCapacity];
  • ArrayList(Collection<? extends E> c)初始化ArrayList時,elementData的大小為參數集合的大小,即Object[c.length];

2、size:動態數組的實際大小

(四)、ArrayList的遍歷方式

1、Iterator迭代器遍歷方式

ArrayList類中封裝了Iterator接口,調用Iterator()方法獲得Iterator對象,Iterator對象調用hashNext()、next()方法進行迭代遍歷。

1 Integer value = null;
2 Iterator iter = list.iterator();
3 while (iter.hasNext()) {
4     value = (Integer)iter.next();
5 }
View Code

 

2、隨機訪問。通過索引值遍歷

由於ArrayList實現了RandomAccess接口,它支持通過索引值去隨機訪問元素,通過調用public E get(int index)方法遍歷。

1 Integer value = null;
2 for (int i=0; i<list.size(); i++) {
3     value = (Integer)list.get(i);        
4 }
View Code

 

3、for-each循環遍歷

1 Integer value = null;
2 for (Integer integ : list) {
3     value = integ;
4 }
View Code

 

4、三種遍歷方式的性能

遍歷ArrayList時,在性能方面:隨機訪問 , 通過索引值遍歷> for-each遍歷 > Iterator迭代器遍歷。

(五)、使用toArray()異常

ArrayList提供了兩個“toArray“方法,分別是: Object[] toArray() 和 <T> T[] toArray(T[] contents) 。

當我們調用“toArray()”方法時會拋““java.lang.ClassCastException”異常,但是調用"toArray(T[] t)"可以返回正常的T[]。

調用“toArray()”方法時會拋“java.lang.ClassCastException”異常是因為返回值類型是“Object[]”,Object是Java中最頂層的對象,且Java不支持對象向下轉型,比如當Object[]轉成Integer[]時,就會拋“java.lang.ClassCastException”異常。

使用另一個<T> T[] toArray(T[] contents)可以解決該異常問題。

調用 toArray(T[] contents) 返回T[]的可以通過以下幾種方式實現。

 1 // toArray(T[] contents)調用方式一
 2 public static Integer[] vectorToArray1(ArrayList<Integer> v) {
 3     Integer[] newText = new Integer[v.size()];
 4     v.toArray(newText);
 5     return newText;
 6 }
 7 
 8 // toArray(T[] contents)調用方式二。最常用!
 9 public static Integer[] vectorToArray2(ArrayList<Integer> v) {
10     Integer[] newText = (Integer[])v.toArray(new Integer[0]);
11     return newText;
12 }
13 
14 // toArray(T[] contents)調用方式三
15 public static Integer[] vectorToArray3(ArrayList<Integer> v) {
16     Integer[] newText = new Integer[v.size()];
17     Integer[] newStrings = (Integer[])v.toArray(newText);
18     return newStrings;
19 }
View Code

 (六)、總結

1、ArrayList是基於動態數組的集合,當使用默認構造方法ArrayList()時,則ArrayList的默認容量大小是10

2、當ArrayList容量不足時,ArrayList會動態擴容大小:JDK 1.7前 : 新的容量 = (原始容量 * 3 / 2) + 1 , JDK 1.7后 : 新的容量 = 原始容量 + 原始容量 >> 1

3、ArrayList的克隆復制是將全部元素克隆至新的數組中;

4、ArrayList實現了Serializable接口,當寫入輸出流時,先寫入“容量”,再依次寫入“每一個節點的值”,當讀取輸入流時,先讀取“容量”,再依次讀取“每個節點的元素”;

五、Vector

(一)、簡介

Vector同ArrayList類似,底層數據結構是數組,特性即功能與ArrayList類似,也具有增刪慢,查詢快的特點,不同的是,Vector是同步的,線程安全的,所以在多線程環境下,使用Vector比ArrayList更合適。

Vector的特性:

    • 增刪慢,查詢快;
    • 效率低,線程安全(與ArrayList相反);
    • 擅長隨機訪問,通過索引值遍歷;

(二)、Vector構造方法

(三)、Vector的三個重要的屬性:elementData , elementCount, capacityIncrement

elementData的特性與ArrayList中的elementData一致,這里不再贅述,這里主要講一下elementCount和capacityIncrement。

1、elementCount:動態數組的大小,同ArrayList的size

2、capacityIncrement:擴容數組大小的增長系數。

在創建Vector時如指定了capacityIncrement的系數,則在Vector需要擴容時,擴容的大小總是capacityIncrement的系數大小。

(四)、Vector的遍歷方式

Vector一共有4種遍歷方式:Iterator迭代器遍歷、通過索引值的隨機方法、for-each遍歷和Enumeration遍歷。

Iterator迭代器遍歷、通過索引值的隨機方法、for-each遍歷和ArrayList類似不再贅述,這里講一下Enumeration遍歷。

1、Enumeration遍歷

1 Integer value = null;
2 Enumeration enu = vector.elements();
3 while (enu.hasMoreElements()) {
4     value = (Integer)enu.nextElement();
5 }
View Code

2、4種遍歷方式的性能

遍歷Vector時,在性能方面:隨機訪問,通過索引值遍歷 > for-each遍歷 > Enumeration遍歷 > Iterator迭代器遍歷

所以,無論是ArrayList還是Vector遍歷,推薦使用隨機訪問,通過索引值遍歷 。

五)、ArrayList與Vector的區別

1、線程安全。ArrayList是線程不安全的,Vector是線程安全的;

2、擴容不同。ArrayList默認擴容是(原始容量 * 3  / 2) + 1,Vector默認擴容是原始容量 * 2;

3、遍歷方式不同,ArrayList有3種,Vector有4種,多的一種是Enumeration遍歷;

(六)、總結

1、Vector實際上是通過一個數組去保存數據的。當我們構造Vecotr時;若使用默認構造函數,則Vector的默認容量大小是10
2、 當Vector容量不足以容納全部元素時,Vector的容量會增加。若容量增加系數 >0,則將容量的值增加“容量增加系數”否則,將容量大小增加一倍
3、 Vector的克隆函數,即是將全部元素克隆到一個數組中;

4、Vector默認擴容是原始容量 * 2

六、LinkedList

(一)、簡介及繼承結構

1、簡介

LinkedList是List接口的另一種實現方式,底層數據結構是鏈表,具有增刪快,查詢慢的特性。自定義了操作頭部和尾部元素的方法,可以當做“棧”使用,同時又是Deque的實現類,所以有具有雙端隊列的特性。所以LinkedList既可以當做“棧”,也可以當做雙端隊列

LinkedList的特性:

  • 元素增刪快,查詢慢(基於鏈表的數據結構);
  • 效率高,線程不安全(非同步);
  • 既可以當作“棧”使用,又可以當作隊列使用。

2、繼承結構

 

 

通過結構圖可以看出,ArrayList繼承自AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable、Deque這些接口。

      • 繼承AbstractList,實現了List接口:它是一個動態鏈表,提供了相關添加,刪除,修改和遍歷等功能;
      • 實現RandomAccess接口:提供隨機訪問的能力;
      • 實現Cloneable接口:覆蓋函數clone(),可以被克隆;
      • 實現Serializable接口:支持序列化和反序列化,可以通過序列化傳輸數據。

 

(二)、LinkedList的構造方法

 

 

 

(三)、LinkedList三個重要屬性:size,first、last

1、size:雙向鏈表節點的個數;

2、first:雙向鏈表的頭部節點;

3、last:雙向鏈表的尾部節點。

 1 transient Node<E> first;
 2 
 3 transient Node<E> last;
 4 
 5 private static class Node<E> {
 6      E item;
 7      Node<E> next;
 8      Node<E> prev;
 9  
10      Node(Node<E> prev, E element, Node<E> next) {
11          this.item = element;
12          this.next = next;
13          this.prev = prev;
14      }
15 }

 

從部分源碼中可以看出,first和last是Node的實體實例,Node是LinkedList內部的私有靜態類,Node定義了三個變量:item、next和prev。

  • item:保存當前節點的值;
  • next:指向下一個節點的值;
  • prev:指向上一個節點的值;

(四)、LinkedList的遍歷方式

LinkedList支持多種遍歷方式。建議不要采用隨機訪問的方式去遍歷LinkedList,而采用逐個遍歷的方式。

1、Iterator迭代器遍歷

Integer integer = null;
Iterator iterator = list.iterator();

while(iterator.hasNext()){
    integer = (Integer) iterator.next(); 
}
View Code

 

2、快速隨機訪問遍歷

 

1 Integer integer = null;
2         
3 for (int i=0; i<list.size(); i++) {
4     integer = (Integer) list.get(i);        
5 }
View Code

 

 

 

3、for-each方式遍歷

 

1 Integer integer = null;
2         
3 for (Object object : list) {
4     integer = (Integer) object;
5 }
View Code

 

 

 

4、pollFirst()遍歷

 while(list.pollFirst() != null) {} 

5、pollLast()遍歷

 while(list.pollLast() != null) {} 

6、removeFirst()遍歷

 while(list.removeFirst() != null) {} 

7、removeLast()遍歷

 while(list.removeLast() != null) {} 

8、遍歷方式的性能

遍歷LinkedList時,removeFirst()和removeLast()效率最高,但是使用這兩種方式會在遍歷時刪除數據。如果只想單獨讀取數據,不刪除數據,推薦使用for-each方式遍歷。

(五)、LinkedList常用方法

1、add(E e)和 add(int index, E element)

 add(E e)的添加方式:

 1 // 添加元素,以添加最后一個元素為例
 2 public boolean add(E e) {
 3     linkLast(e);
 4     return true;
 5 }
 6 
 7 void linkLast(E e) {
 8     final Node<E> l = last;
 9     final Node<E> newNode = new Node<>(l, e, null);
10     last = newNode;
11     if (l == null)
12         first = newNode;
13     else
14         l.next = newNode;
15     size++;
16     modCount++;
17 }
View Code

 

情況1:假設LinkedList集合為空的情況,看如下圖解:

 

情況2:假設LinkedList集合不為空,看如下圖解:

如果LinkedList不為空,那么添加進來的元素2就是last,元素2的prev指向以前的最后一個元素,即元素1,元素2的next為null;

注意:如果只在頭部或尾部添加元素,可以直接調用addFirst(E e)或addLast(E e);

add(int index,E element)添加方式:

 1 //在指定位置添加一個元素
 2 public void add(int index, E element) {
 3     checkPositionIndex(index);
 4     if (index == size)
 5         linkLast(element);
 6     else
 7         linkBefore(element, node(index));
 8 }
 9 
10 private void checkPositionIndex(int index) {
11     if (!isPositionIndex(index))
12         throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
13 }
14 
15 private boolean isPositionIndex(int index) {
16     return index >= 0 && index <= size;
17 }
18 
19 void linkBefore(E e, Node<E> succ) {
20     // assert succ != null;
21     final Node<E> pred = succ.prev;
22     final Node<E> newNode = new Node<>(pred, e, succ);
23     succ.prev = newNode;
24     if (pred == null)
25         first = newNode;
26     else
27         pred.next = newNode;
28     size++;
29     modCount++;
30 }
View Code

 

從代碼中看到和add(E e)的代碼實現沒有本質區別,都是通過新建一個Node實體,同時指定其prev和next來實現,不同點在於需要調用node(int index)通過傳入的index來定位到要插入的位置,這個也是比較耗時的。

如下圖解:

 

LinkedList插入效率高是相對的,因為它省去了ArrayList插入數據可能的數組擴容和數據元素移動時所造成的開銷,但數據擴容和數據元素移動卻並不是時時刻刻都在發生的。

2、remove(Object o) 和 remove(int 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;
}
//刪除某個位置的元素
public E remove(int index) {
    checkElementIndex(index);
    return unlink(node(index));
}
//刪除某節點,並將該節點的上一個節點(如果有)和下一個節點(如果有)關聯起來
E unlink(Node<E> x) {
    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;
}
View Code

 

 

先找到要刪除的節點,remove(Object o)方法遍歷整個集合,通過 == 或 equals方法進行判斷;remove(int index)通過node(index)方法。

 

(六)、LinkedList與ArrayList的區別

1、底層數據結構不同。ArrayList底層數據結構是數組。LinkedList底層數據結構是鏈表;

2、性能不同。ArrayList增刪慢,查詢快。LinkedLIst增刪快,查詢慢(底層數據結構不同);

3、內存不同。LinkedList比ArrayList更占內存(因為LinkedList每個節點存儲兩個引用,一個指向前元素,一個指向后元素)。

(七)、總結

1、LinkedList是基於鏈表結構的集合。內部定義了一個私有的Node實體實例,Node是雙向鏈表節點對應數據的數據結構,其定義了三個屬性:item(保存該節點的值)、prev(指向前一個元素的節點)、next(指向后一個元素的節點);

2、LinkedList不存在容量不足的問題;

3、LinkedList克隆時,是將全部元素克隆到新的LinkedLIst對象中;

4、LinkedList實現了Serializable接口,當寫入輸出流時,先寫入“容量”,再依次寫入“每一個節點的值”,當讀取輸入流時,先讀取“容量”,再依次讀取“每個節點的元素”;

5、LinkedList實現了Deque,而Deque接口定義了在雙端隊列兩端訪問元素的方法,提供插入、移除和檢查元素的方法。每種方法都存在兩種形式:一種形式在操作失敗時拋出異常,另一種形式返回一個特殊值(null 或 false)。

七、Stack

(一)、簡介及繼承結構

1、Stack是繼承自Vector,所以它的底層數據結構也是動態數組。實現了一個“先進后出(FILO, First In Last Out)”的棧結構

2、Stack的繼承結構

 

由於繼承了Vector,所以具有了Vector的相關特性,這里不再贅述。

(二)、Stack的構造方法

 

(三)、常用的API方法

(四)、總結

1、Stack實際上也是通過數組去實現的。
       執行push時(即,將元素推入棧中),是通過將元素追加的數組的末尾中。
       執行peek時(即,取出棧頂元素,不執行刪除),是返回數組末尾的元素。
       執行pop時(即,取出棧頂元素,並將該元素從棧中刪除),是取出數組末尾的元素,然后將該元素從數組中刪除。
2、Stack繼承於Vector,意味着Vector擁有的屬性和功能,Stack都擁有。

八、List與實現類的擴展

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM