Java 集合工具包

Java 集合工具包

Java集合是java提供的工具包,包含了常用的數據結構:集合、鏈表、隊列、棧、數組、映射等。

Java集合工具包位置是java.util.*
Java集合主要可以劃分爲4個部分:

  • List列表
  • Set集合
  • Map映射
  • 工具類(Iterator迭代器、Enumeration枚舉類、Arrays和Collections)。

image

集合的作用

  • 在類的內部,對數據進行組織;
  • 簡單而快速的搜索大數量的條目;
  • 有的集合接口,提供了一系列排列有序的元素,並且可以在序列中間快速的插入或者刪除有關元素;
  • 有的集合接口,提供了映射關係,可以通過關鍵字(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是專門的迭代輸出接口,迭代輸出就是將元素一個個進行判斷,判斷其是否有內容,如果有內容則把內容取出。

image

二:arrayList

ArrayList繼承了AbstractList,實現了List。
構造圖如下:
藍色線條:繼承
綠色線條:接口實現

image

 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遜色些。
構造圖如下:
藍色線條:繼承
綠色線條:接口實現

image

總結:

  • (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的形式存在。
構造圖如下:
藍色線條:繼承
綠色線條:接口實現

image

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的實現首先就要對紅黑樹有所瞭解。
構造圖如下:
藍色線條:繼承
綠色線條:接口實現

image

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實現的)。
構造圖如下:
藍色線條:繼承
綠色線條:接口實現

image

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 來說比較慢
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章