08、java集合類----List集合

目錄

集合與數組

hashCode方法的作用?

層次關係

集合類遍歷

List集合層次圖

對比 Vector、ArrayList、LinkedList 有何區別?

讀寫效率:

擴容:

一般來說,也可以補充一下不同容器類型適合的場景:

Set 集合的幾種實例

線程安全

在 Java 9 中,Java 標準類庫提供了一系列的靜態工廠方法


集合與數組

數組(可以存儲基本數據類型)是用來存現對象的一種容器,但是數組的長度固定,不適合在對象數量未知的情況下使用。

集合(只能存儲對象,對象類型可以不一樣)的長度可變,可在多數情況下使用。

 

hashCode方法的作用?

  Set集合元素無序,但元素不可重複。那麼這裏就有一個比較嚴重的問題了:要想保證元素不重複,可兩個元素是否重複應該依據什麼來判斷呢?如果集合中現在已經有1000個元素,那麼第1001個元素加入集合時,它就要調用1000次equals方法。這顯然會大大降低效率。於是,Java採用了哈希表的原理,hashCode方法實際上返回的就是對象存儲的物理地址(實際可能並不是)。這樣一來,當集合要添加新的元素時,先調用這個元素的hashCode方法,就一下子能定位到它應該放置的物理位置上。如果這個位置上沒有元素,它就可以直接存儲在這個位置上,不用再進行任何比較了;如果這個位置上已經有元素了,就調用它的equals方法與新元素進行比較,相同的話就不存了,不相同就散列其它的地址。所以這裏存在一個衝突解決的問題。這樣一來實際調用equals方法的次數就大大降低了,幾乎只需要一兩次。

 

層次關係

圖中,實線邊框的是實現類,折線邊框的是抽象類,而點線邊框的是接口

Collection接口是集合類的根接口。擴展開提供了三大類集合,分別是:

  • List,有序集合,它提供了方便的訪問、插入、刪除等操作。
  • Set,Set 是不允許重複元素的,這是和 List 最明顯的區別,也就是不存在兩個對象 equals 返回 true。我們在日常開發中有很多需要保證元素唯一性的場合。
  • Queue/Deque,則是 Java 提供的標準隊列結構的實現,除了集合的基本功能,它還支持類似先入先出(FIFO, First-in-First-Out)或者後入先出(LIFO,Last-In-First-Out)等特定行爲。這裏不包括 BlockingQueue,因爲BlockingQueue通常是用在併發編程場合,所以被放置在併發包裏。

Map是Java.util包中的另一個接口,它和Collection接口沒有關係,是相互獨立的,但是都屬於集合類的一部分。Map包含了key-value鍵值對。Map不能包含重複的key,但是可以包含相同的value。

Iterator,所有的集合類,都實現了Iterator接口,這是一個用於遍歷集合中元素的接口,主要包含以下三種方法:

  • hasNext()是否還有下一個元素。
  • next()返回下一個元素。
  • remove()刪除當前元素。

 

集合類遍歷

在類集中提供了以下四種的常見輸出方式:

1、Iterator:迭代輸出,是使用最多的輸出方式。

Iterator it = arr.iterator();
while (it.hasNext()) {
    Object o = it.next(); 
}

2、ListIterator:是Iterator的子接口,專門用於輸出List中的內容

Iterator it = arr.listIterator();
while (it.hasNext()) {
    Object o = it.next();
}

3、foreach輸出:JDK1.5之後提供的新功能,可以輸出數組或集合。

for (Object i:arr) {
    ...            
}

4、for循環

for (int i = 0; i <arr.size() ; i++) {
    ... 
}

 

對比 Vector、ArrayList、LinkedList 有何區別?

這三者都是實現集合框架中的 List,也就是所謂的有序集合,因此具體功能也比較近似,比如都提供按照位置進行定位、添加或者刪除的操作,都提供迭代器以遍歷其內容等。但因爲具體的設計區別,在行爲、性能、線程安全等方面,表現又有很大不同。

Vector 是 Java 早期提供的線程安全的動態數組,如果不需要線程安全,並不建議選擇,畢竟同步是有額外開銷的。Vector 內部是使用對象數組來保存數據,可以根據需要自動的增加容量,當數組已滿時,會創建新的數組,並拷貝原有數組數據。

ArrayList 非線程安全動態數組實現,它本身不是線程安全的,所以性能要好很多。與 Vector 近似,ArrayList 也是可以根據需要調整容量,不過兩者的調整邏輯有所區別,vector增長率爲目前數組長度的100%,而Arraylist增長率爲目前數組長度的50%。

LinkedList 顧名思義是 Java 提供的雙向鏈表,所以它不需要像上面兩種那樣調整容量,它也不是線程安全的。它本身有自己特定的方法,如: addFirst(),addLast(),getFirst(),removeFirst(),removeLast()等.。

 

讀寫效率:

在刪除可插入對象的動作時,爲什麼ArrayList的效率會比較低呢?

  • Vector ArrayList 作爲動態數組,除了尾部,從數組的其它位置插入和刪除元素,需要移動後段的數組元素,從而會重新調整索引順序,調整索引順序會消耗一定的時間,所以速度上就會比LinkedList要慢許多,比如我們在中間位置插入一個元素,需要移動後續所有元素。
  • 相反,LinkedList是使用鏈表實現的,若要從鏈表中刪除或插入某一個對象,只需要改變前後對象的引用即可。

 

擴容:

ArrayList 在執行插入元素是超過當前數組預定義的最大值時,數組需要擴容,擴容過程需要調用底層System.arraycopy()方法進行大量的數組複製操作;在刪除元素時並不會減少數組的容量(如果需要縮小數組容量,可以調用trimToSize()方法);在查找元素時要遍歷數組,對於非null的元素採取equals的方式尋找。

Vector ArrayList 僅在插入元素時容量擴充機制不一致。Vector ArrayList 默認創建一個大小爲10的Object數組,如 Vector的容量爲10,一次擴容後是容量爲20,如 ArrayList的容量爲10,一次擴容後是容量爲16。

對於Vector,將capacityIncrement設置爲0;當插入元素數組大小不夠時,如果capacityIncrement大於0,則將Object數組的大小擴大爲現有size+capacityIncrement;如果capacityIncrement<=0,則將Object數組的大小擴大爲現有大小的2倍

 

一般來說,也可以補充一下不同容器類型適合的場景:

  • Vector ArrayList 作爲動態數組,其內部元素以數組形式順序存儲的,所以非常適合隨機訪問的場合。
  • LinkedList 經常用在增刪操作較多而查詢操作很少的情況下,ArrayList則相反。
  • LinkedList 實現Stack(堆棧)使用removeFirst()方法,實現Queue(隊列)使用removeLast()方法。前者先進後出,後者是先進先出。LinkedList還是實現了Queue接口,因此可以直接作爲隊列使用。

 

Set 集合的幾種實例

TreeSet  

  • TreeSet 實際是利用 TreeMap 實現的,TreeSet 支持自然順序訪問,但是添加、刪除、包含等操作要相對低效(log(n) 時間)
  • TreeSet支持兩種排序方式,自然排序和定製排序,其中自然排序爲默認的排序方式。
  • TreeSet判斷兩個對象不相等的方式是兩個對象通過equals方法返回false,或者通過CompareTo方法比較沒有返回0

HashSet 

  • 其實是以 HashMap 爲基礎實現的。
  • HashSet 不能保證元素的排列順序,順序有可能發生變化,不是同步的,集合元素可以是null,但只能放入一個null。
  • 當向HashSet結合中存入一個元素時,HashSet會調用該對象的hashCode()方法來得到該對象的hashCode值,然後根據 hashCode值來決定該對象在HashSet中存儲位置。

LinkedHashSet

  • 是根據元素的hashCode值來決定元素的存儲位置。
  • 內部構建了一個記錄插入順序的雙向鏈表,當遍歷該集合時候,LinkedHashSet將會以元素的添加順序訪問集合的元素。
  • LinkedHashSet在迭代訪問Set中的全部元素時,性能比HashSet好,但是插入時性能稍微遜色於HashSet。

在遍歷元素時,HashSet 性能受自身容量影響,所以初始化時,除非有必要,不然不要將其背後的 HashMap 容量設置過大。而對於 LinkedHashSet,由於其內部鏈表提供的方便,遍歷性能只和元素多少有關係。

 

線程安全

不是線程安全的集合,並不代表這些集合完全不能支持併發編程的場景,在 Collections 工具類中,提供了一系列的 synchronized 方法,比如:

static <T> List<T> synchronizedList(List<T> list)

我們完全可以利用類似方法來實現基本的線程安全集合:

List list = Collections.synchronizedList(new ArrayList());

它的實現,基本就是將每個基本方法,比如 get、set、add 之類,都通過 synchronizd 添加基本的同步支持,非常簡單粗暴,但也非常實用。注意這些方法創建的線程安全集合,都符合迭代時 fail-fast 行爲,當發生意外的併發修改時,儘早拋出 ConcurrentModificationException 異常,以避免不可預計的行爲。

 

在 Java 9 中,Java 標準類庫提供了一系列的靜態工廠方法

比如,List.of()、Set.of(),大大簡化了構建小的容器實例的代碼量。根據業界實踐經驗,我們發現相當一部分集合實例都是容量非常有限的,而且在生命週期中並不會進行修改。但是,在原有的 Java 類庫中,我們可能不得不寫成:

ArrayList<String>  list = new ArrayList<>();
list.add("Hello");
list.add("World");

 而利用新的容器靜態工廠方法,一句代碼就夠了,並且保證了不可變性。

List<String> simpleList = List.of("Hello","world");

更進一步,通過各種 of 靜態工廠方法創建的實例,還應用了一些我們所謂的最佳實踐,比如,它是不可變的,符合我們對線程安全的需求;它因爲不需要考慮擴容,所以空間上更加緊湊等。

如果我們去看 of 方法的源碼,你還會發現一個特別有意思的地方:我們知道 Java 已經支持所謂的可變參數(varargs),但是官方類庫還是提供了一系列特定參數長度的方法,看起來似乎非常不優雅,爲什麼呢?這其實是爲了最優的性能,JVM 在處理變長參數的時候會有明顯的額外開銷,如果你需要實現性能敏感的 API,也可以進行參考。

 

 

 

發佈了96 篇原創文章 · 獲贊 19 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章