Java 集合工具包
Java集合是java提供的工具包,包含了常用的數據結構:集合、鏈表、隊列、棧、數組、映射等。
Java集合工具包位置是java.util.*
Java集合主要可以劃分爲4個部分:
- List列表
- Set集合
- Map映射
- 工具類(Iterator迭代器、Enumeration枚舉類、Arrays和Collections)。
集合的作用
- 在類的內部,對數據進行組織;
- 簡單而快速的搜索大數量的條目;
- 有的集合接口,提供了一系列排列有序的元素,並且可以在序列中間快速的插入或者刪除有關元素;
- 有的集合接口,提供了映射關係,可以通過關鍵字(key)去快速查找對應的唯一對象,而這個關鍵字額可以是任意類型。
與數組的對比—————爲何選擇集合而不是數組
- 數組的長度固定,集合長度可變
- 數組只能通過下標訪問元素,類型固定,而有的集合可以通過任意類型查找所映射的具體對象。
一:集合框架
看上面的框架圖,先抓住它的主幹,即Collection和Map。
Collection接口、子接口以及實現類
Collection接口
- 是List、Set和Queue接口的父接口
- 定義了可用於操作List、Set和Queue的方法-增刪改查
List接口
- List是元素有序並且可以重複的集合,被稱爲序列
- List可以精確的控制每個元素的插入位置,或刪除某個位置元素
- List接口的常用子類:
ArrayList
LinkedList
Vector
Stack
Set接口
- Set接口中不能加入重複元素,無序
- Set接口常用子類:
散列存放:HashSet
有序存放:TreeSet
Map和HashMap
Map接口
- Map提供了一種映射關係,其中的元素是以鍵值對(key-value)的形式存儲的,能夠實現根據key快速查找value
- Map中的鍵值對以Entry類型的對象實例形式存在
- 鍵(key值)不可重複,value值可以
- 每個建最多隻能映射到一個值
- Map接口提供了分別返回key值集合、value值集合以及Entry(鍵值對)集合的方法
- Map支持泛型,形式如:Map<K,V>
HashMap類
- HashMap是Map的一個重要實現類,也是最常用,基於哈希表實現
- HashMap中的Entry對象是無序排列的
- Key值和Value值都可以爲null,但是一個HashMap只能有一個key值爲null的映射(key值不可重複)
Comparable和Comparator
Comparable接口——可比較的
- 實現該接口表示:這個類的實例可以比較大小,可以進行自然排序
- 定義了默認的比較規則
- 其實現類需要實現compareTo()方法
- compareTo()方法返回正數表示大,負數表示小0表示相等
Comparator接口——比較工具接口
- 用於定義臨時比較規則,而不是默認比較規則
- 其實現類需要實現compare()方法
- Comparable和Comparator都是Java集合框架的成員
Iterator接口
- 集合輸出的標準操作
- 標準做法,使用Iterator接口
- 操作原理:
- Iterator是專門的迭代輸出接口,迭代輸出就是將元素一個個進行判斷,判斷其是否有內容,如果有內容則把內容取出。
二:arrayList
ArrayList繼承了AbstractList,實現了List。
構造圖如下:
藍色線條:繼承
綠色線條:接口實現
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList 是一個數組隊列,相當於 動態數組。與Java中的數組相比,它的容量能動態增長。它繼承於AbstractList,實現了List, RandomAccess, Cloneable, java.io.Serializable這些接口。
ArrayList就是用數組實現的List容器,底層用數組實現,ArrayList包含了兩個重要的對象:elementData 和 size。
// 保存ArrayList中數據的數組
private transient Object[] elementData;
// ArrayList中實際數據的數量
private int size;
ArrayList 實現了RandmoAccess接口,即提供了隨機訪問功能。
ArrayList 實現了Cloneable接口,即覆蓋了函數clone(),能被克隆。
ArrayList 實現java.io.Serializable接口,這意味着ArrayList支持序列化,能通過序列化去傳輸。
ArrayList支持3種遍歷方式
- (01) 第一種,通過迭代器遍歷。即通過Iterator去遍歷。
- (02) 第二種,隨機訪問,通過索引值去遍歷。
由於ArrayList實現了RandomAccess接口,它支持通過索引值去隨機訪問元素。 - (03) 第三種,for循環遍歷。
遍歷ArrayList時,使用隨機訪問(即,通過索引序號訪問)效率最高,而使用迭代器的效率最低!
ArrayList中的操作不是線程安全的!
所以,建議在單線程中才使用ArrayList,而在多線程中可以選擇Vector或者CopyOnWriteArrayList
小結:
- (01) ArrayList 實際上是通過一個數組去保存數據的。當我們構造ArrayList時;若使用默認構造函數,則ArrayList的默認容量大小是10。
- (02) 當ArrayList容量不足以容納全部元素時,ArrayList會重新設置容量:新的容量=“(原始容量x3)/2 + 1”。
- (03) ArrayList的克隆函數,即是將全部元素克隆到一個數組中。
- (04) ArrayList實現java.io.Serializable的方式。當寫入到輸出流時,先寫入“容量”,再依次寫入“每一個元素”;當讀出輸入流時,先讀取“容量”,再依次讀取“每一個元素”。
ArrayList和LinkedList的區別
- ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構。
- 對於隨機訪問get和set,ArrayList覺得優於LinkedList,因爲LinkedList要移動指針。
- 對於新增和刪除操作add和remove,LinkedList比較佔優勢,因爲ArrayList要移動數據。
ArrayList和Vector的區別
- Vector和ArrayList幾乎是完全相同的,唯一的區別在於Vector是同步類(synchronized),屬於強同步類。因此開銷就比ArrayList要大,訪問要慢。正常情況下,大多數的Java程序員使用ArrayList而不是Vector,因爲同步完全可以由程序員自己來控制。
- Vector每次擴容請求其大小的2倍空間,而ArrayList是1.5倍。
- Vector還有一個子類Stack.
三:LinkedList
與ArrayList一樣,實現List接口,只是ArrayList是List接口的大小可變數組的實現,LinkedList是List接口鏈表的實現。基於鏈表實現的方式使得LinkedList在插入和刪除時更優於ArrayList,而隨機訪問則比ArrayList遜色些。
構造圖如下:
藍色線條:繼承
綠色線條:接口實現
總結:
- (01) LinkedList 實際上是通過雙向鏈表去實現的。
- 它包含一個非常重要的內部類:Entry。Entry是雙向鏈表節點所對應的數據結構,它包括的屬性有:當前節點所包含的值,上一個節點,下一個節點。
- (02) 從LinkedList的實現方式中可以發現,它不存在LinkedList容量不足的問題。
- (03) LinkedList的克隆函數,即是將全部元素克隆到一個新的LinkedList對象中。
- (04) LinkedList實現java.io.Serializable。當寫入到輸出流時,先寫入“容量”,再依次寫入“每一個節點保護的值”;當讀出輸入流時,先讀取“容量”,再依次讀取“每一個元素”。
- (05) 由於LinkedList實現了Deque,而Deque接口定義了在雙端隊列兩端訪問元素的方法。提供插入、移除和檢查元素的方法。每種方法都存在兩種形式:一種形式在操作失敗時拋出異常,另一種形式返回一個特殊值(null 或 false,具體取決於操作)。
小結
ArrayList和LinkedList的比較
1、順序插入速度ArrayList會比較快,因爲ArrayList是基於數組實現的,數組是事先new好的,只要往指定位置塞一個數據就好了;LinkedList則不同,每次順序插入的時候LinkedList將new一個對象出來,如果對象比較大,那麼new的時間勢必會長一點,再加上一些引用賦值的操作,所以順序插入LinkedList必然慢於ArrayList
2、基於上一點,因爲LinkedList裏面不僅維護了待插入的元素,還維護了Entry的前置Entry和後繼Entry,如果一個LinkedList中的Entry非常多,那麼LinkedList將比ArrayList更耗費一些內存
3、數據遍歷的速度,ArrayList使用最普通的for循環遍歷比較快,LinkedList使用foreach循環比較快。如果各自比較,結論是:使用各自遍歷效率最高的方式,ArrayList的遍歷效率會比LinkedList的遍歷效率高一些
4、有些說法認爲LinkedList做插入和刪除更快,這種說法其實是不準確的:
(1)LinkedList做插入、刪除的時候,慢在尋址,快在只需要改變前後Entry的引用地址
(2)ArrayList做插入、刪除的時候,慢在數組元素的批量copy,快在尋址
所以,如果待插入、刪除的元素是在數據結構的前半段尤其是非常靠前的位置的時候,LinkedList的效率將大大快過ArrayList,因爲ArrayList將批量copy大量的元素;越往後,對於LinkedList來說,因爲它是雙向鏈表,所以在第2個元素後面插入一個數據和在倒數第2個元素後面插入一個元素在效率上基本沒有差別,但是ArrayList由於要批量copy的元素越來越少,操作速度必然追上乃至超過LinkedList。
從這個分析看出,如果你十分確定你插入、刪除的元素是在前半段,那麼就使用LinkedList;如果你十分確定你刪除、刪除的元素在比較靠後的位置,那麼可以考慮使用ArrayList。如果你不能確定你要做的插入、刪除是在哪兒呢?那還是建議你使用LinkedList吧,因爲一來LinkedList整體插入、刪除的執行效率比較穩定,沒有ArrayList這種越往後越快的情況;二來插入元素的時候,弄得不好ArrayList就要進行一次擴容。
記住,ArrayList底層數組擴容是一個既消耗時間又消耗空間的操作。
四:HashMap
ArrayList、LinkedList,反映的是兩種思想:
ArrayList以數組形式實現,順序插入、查找快,插入、刪除較慢
LinkedList以鏈表形式實現,順序插入、查找較慢,插入、刪除方便
那麼是否有一種數據結構能夠結合上面兩種的優點呢?有,答案就是HashMap。
HashMap是基於哈希表的 Map 接口的實現,以key-value的形式存在。
構造圖如下:
藍色線條:繼承
綠色線條:接口實現
HashMap 是一個散列表,它存儲的內容是鍵值對(key-value)映射。
HashMap繼承於AbstractMap,實現了Map、Cloneable、java.io.Serializable接口。
HashMap 的實現不是同步的,這意味着它不是線程安全的。它的key、value都可以爲null。此外,HashMap中的映射不是有序的。
HashMap的底層結構是一個數組,而數組的元素是一個單向鏈表
HashMap和Hashtable的區別
- 兩者最主要的區別在於Hashtable是線程安全,而HashMap則非線程安全
Hashtable的實現方法裏面都添加了synchronized關鍵字來確保線程同步,因此相對而言HashMap性能會高一些,我們平時使用時若無特殊需求建議使用HashMap,在多線程環境下若使用HashMap需要使用Collections.synchronizedMap()方法來獲取一個線程安全的集合(Collections.synchronizedMap()實現原理是Collections定義了一個SynchronizedMap的內部類,這個類實現了Map接口,在調用方法時使用synchronized來保證線程同步,當然了實際上操作的還是我們傳入的HashMap實例,簡單的說就是Collections.synchronizedMap()方法幫我們在操作HashMap時自動添加了synchronized來實現線程同步,類似的其它Collections.synchronizedXX方法也是類似原理)
- HashMap可以使用null作爲key,而Hashtable則不允許null作爲key
雖說HashMap支持null值作爲key,不過建議還是儘量避免這樣使用,因爲一旦不小心使用了,若因此引發一些問題,排查起來很是費事
HashMap以null作爲key時,總是存儲在table數組的第一個節點上
- HashMap是對Map接口的實現,HashTable實現了Map接口和Dictionary抽象類
- HashMap的初始容量爲16,Hashtable初始容量爲11,兩者的填充因子默認都是0.75
HashMap擴容時是當前容量翻倍即:capacity2,Hashtable擴容時是容量翻倍+1即:capacity2+1
- HashMap和Hashtable的底層實現都是數組+鏈表結構實現
- 兩者計算hash的方法不同
Hashtable計算hash是直接使用key的hashcode對table數組的長度直接進行取模
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
HashMap計算hash對key的hashcode進行了二次hash,以獲得更好的散列值,然後對table數組長度取摸
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
五:TreeMap
TreeMap是基於紅黑樹結構實現的一種Map,要分析TreeMap的實現首先就要對紅黑樹有所瞭解。
構造圖如下:
藍色線條:繼承
綠色線條:接口實現
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
TreeMap 是一個有序的key-value集合,它是通過紅黑樹實現的。
TreeMap 繼承於AbstractMap,所以它是一個Map,即一個key-value集合。
TreeMap 實現了NavigableMap接口,意味着它支持一系列的導航方法。比如返回有序的key集合。
TreeMap 實現了Cloneable接口,意味着它能被克隆。
TreeMap 實現了java.io.Serializable接口,意味着它支持序列化。
TreeMap基於紅黑樹(Red-Black tree)實現。該映射根據其鍵的自然順序進行排序,或者根據創建映射時提供的 Comparator 進行排序,具體取決於使用的構造方法。
TreeMap的基本操作 containsKey、get、put 和 remove 的時間複雜度是 log(n) 。
另外,TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fast的。
六:HashSet
Set的實現類都是基於Map來實現的(HashSet是通過HashMap實現的)。
構造圖如下:
藍色線條:繼承
綠色線條:接口實現
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
HashSet 是一個沒有重複元素的集合。
它是由HashMap實現的,不保證元素的順序,而且HashSet允許使用 null 元素。
HashSet是非同步的。如果多個線程同時訪問一個哈希 set,而其中至少一個線程修改了該 set,那麼它必須 保持外部同步。這通常是通過對自然封裝該 set 的對象執行同步操作來完成的。如果不存在這樣的對象,則應該使用 Collections.synchronizedSet 方法來“包裝” set。最好在創建時完成這一操作,以防止對該 set 進行意外的不同步訪問:
Set s = Collections.synchronizedSet(new HashSet(...));
HashSet通過iterator()返回的迭代器是fail-fast的。
// 底層使用HashMap來保存HashSet的元素
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
// 由於Set只使用到了HashMap的key,所以此處定義一個靜態的常量Object類,來充當HashMap的value
private static final Object PRESENT = new Object();
HashSet是用HashMap來保存數據,而主要使用到的就是HashMap的key。
小結
HashSet和HashMap、Hashtable的區別
HashMap | HashSet |
---|---|
HashMap 實現了Map 接口 | HashSet實現了set 接口 |
HashMap 儲存鍵值對 | HashSet僅僅存儲對象 |
使用put()方法將元素放入map 中 | 使用add()方法將元素放入set 中 |
HashMap中使用鍵對象來計算 hashCode值 | HashSet使用成員對象來計算hashcode 值,對於兩個對象來說hashcode 可能相同,所以equals()方法用來判斷對象的相等性,如果兩個對象不同的話,那麼返回false |
HashMap 比較快,因爲是使用唯一的鍵來獲取對象 | HashSet較HashMap 來說比較慢 |