J2SE I一一Java集合類(詳解)

Java集合類的詳解

一.集合的簡述

集合類是用來存放某類對象的。集合類有一個共同特色,就是它們只容納對象(其實是對象名,即指向地址的指針)。這一點和數組不一樣,數組能夠容納對象和簡單數據。若是在集合類中既想使用簡單數據類型,又想利用集合類的靈活性,就能夠把簡單數據類型數據變成該數據類型類的對象,而後放入集合中處理,但這樣執行效率會下降。java

集合類容納的對象都是Object類的實例,一旦把一個對象置入集合類中,它的類信息將丟失,也就是說,集合類中容納的都是指向Object類對象的指針。這樣的設計是爲了使集合類具備通用性,由於Object類是全部類的祖先,因此能夠在這些集合中存聽任何類而不受限制。固然這也帶來了不便,這令使用集合成員以前必須對它從新造型。node

集合類是Java數據結構的實現。在編寫程序時,常常須要和各類數據打交道,爲了處理這些數據而選用數據結構對於程序的運行效率是很是重要的。程序員

集合類是Java數據結構的實現。Java的集合類是java.util包中的重要內容,它容許以各類方式將元素分組,並定義了各類使這些元素更容易操做的方法。Java集合類是Java將一些基本的和使用頻率極高的基礎類進行封裝和加強後再以一個類的形式提供。集合類是能夠往裏面保存多個對象的類,存放的是對象,不一樣的集合類有不一樣的功能和特色,適合不一樣的場合,用以解決一些實際問題。數組

二.集合的分類

Java中的集合類能夠分爲兩大類:安全

一類是實現Collection接口;另外一類是實現Map接口數據結構

Collection是一個基本的集合接口,Collection中能夠容納一組集合元素(Element)。併發

Map沒有繼承Collection接口,與Collection是並列關係。Map提供鍵(key)到值(value)的映射。一個Map中不能包含相同的鍵,每一個鍵只能映射一個值。app

Collection有兩個重要的子接口List和Set。List表達一個有序的集合,List中的每一個元素都有索引,使用此接口可以準確的控制每一個元素插入的位置。用戶也可以使用索引來訪問List中的元素,List相似於Java的數組。Set接口的特色是不能包含重複的元素。對Set中任意的兩個元素element1和element2都有elementl.equals(element2)= false。另外,Set最多有一個null元素。此接口模仿了數學上的集合概念。框架

Collection接口、List接口、Set接口以及相關類的關係如圖1所示。函數

1.List

List接口擴展自Collection,它能夠定義一個容許重複的有序集合,從List接口中的方法來看,List接口主要是增長了面向位置的操做,容許在指定位置上操做元素,同時增長了一個可以雙向遍歷線性表的新列表迭代器ListIterator。AbstractList類提供了List接口的部分實現,AbstractSequentialList擴展自AbstractList,主要是提供對鏈表的支持。下面介紹List接口的兩個重要的具體實現類,也是咱們可能最經常使用的類,ArrayList和LinkedList。

1.1ArrayList

經過閱讀ArrayList的源碼,咱們能夠很清楚地看到裏面的邏輯,它是用數組存儲元素的,這個數組能夠動態建立,若是元素個數超過了數組的容量,那麼就建立一個更大的新數組,並將當前數組中的全部元素都複製到新數組中。假設第一次是集合沒有任何元素,下面以插入一個元素爲例看看源碼的實現。

一、找到add()實現方法。
 public boolean add(E e) {
 ensureCapacityInternal(size + 1);  // Increments modCount!!
 elementData[size++] = e;
 return true;
 }

二、此方法主要是肯定將要建立的數組大小。
 private void ensureCapacityInternal(int minCapacity) {
 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
 }
 ensureExplicitCapacity(minCapacity);
 }
 private void ensureExplicitCapacity(int minCapacity) {
 modCount++;
 if (minCapacity - elementData.length > 0)
 grow(minCapacity);
 }
三、最後是建立數組,能夠明顯的看到先是肯定了添加元素後的大小以後將元素複製到新數組中。
 private void grow(int minCapacity) {
 // overflow-conscious code
 int oldCapacity = elementData.length;
 int newCapacity = oldCapacity + (oldCapacity >> 1);
 if (newCapacity - minCapacity < 0)
 newCapacity = minCapacity;
 if (newCapacity - MAX_ARRAY_SIZE > 0)
 newCapacity = hugeCapacity(minCapacity);
 // minCapacity is usually close to size, so this is a win:
 elementData = Arrays.copyOf(elementData, newCapacity);
 }

1.2LinkedList

一樣,咱們打開LinkedList的源文件,不難看到LinkedList是在一個鏈表中存儲元素。

在學習數據結構的時候,咱們知道鏈表和數組的最大區別在於它們對元素的存儲方式的不一樣致使它們在對數據進行不一樣操做時的效率不一樣,一樣,ArrayList與LinkedList也是如此,實際使用中咱們須要根據特定的需求選用合適的類,若是除了在末尾外不能在其餘位置插入或者刪除元素,那麼ArrayList效率更高,若是須要常常插入或者刪除元素,就選擇LinkedList。

1.3CopyOnWriteArrayList

CopyOnWriteArrayList,是一個線程安全的List接口的實現,它使用了ReentrantLock鎖來保證在併發狀況下提供高性能的併發讀取。

2.Set

Set接口擴展自Collection,它與List的不一樣之處在於,規定Set的實例不包含重複的元素。在一個規則集內,必定不存在兩個相等的元素。AbstractSet是一個實現Set接口的抽象類,Set接口有三個具體實現類,分別是散列集HashSet、鏈式散列集LinkedHashSet和樹形集TreeSet。

2.1HashSet

散列集HashSet是一個用於實現Set接口的具體類,可使用它的無參構造方法來建立空的散列集,也能夠由一個現有的集合建立散列集。在散列集中,有兩個名詞須要關注,初始容量和客座率。客座率是肯定在增長規則集以前,該規則集的飽滿程度,當元素個數超過了容量與客座率的乘積時,容量就會自動翻倍。

從輸出結果咱們能夠看到,規則集裏最後有4個元素,並且在輸出時元素仍是無序的。

2.2LinkedHashSet

LinkedHashSet是用一個鏈表實現來擴展HashSet類,它支持對規則集內的元素排序。HashSet中的元素是沒有被排序的,而LinkedHashSet中的元素能夠按照它們插入規則集的順序提取。

2.3TreeSet

TreeSet擴展自AbstractSet,並實現了NavigableSet,AbstractSet擴展自AbstractCollection,樹形集是一個有序的Set,其底層是一顆樹,這樣就能從Set裏面提取一個有序序列了。在實例化TreeSet時,咱們能夠給TreeSet指定一個比較器Comparator來指定樹形集中的元素順序。樹形集中提供了不少便捷的方法。

3.Queue

隊列是一種先進先出的數據結構,元素在隊列末尾添加,在隊列頭部刪除。Queue接口擴展自Collection,並提供插入、提取、檢驗等操做。

上圖中,方法offer表示向隊列添加一個元素,poll()與remove()方法都是移除隊列頭部的元素,二者的區別在於若是隊列爲空,那麼poll()返回的是null,而remove()會拋出一個異常。方法element()與peek()主要是獲取頭部元素,不刪除。

接口Deque,是一個擴展自Queue的雙端隊列,它支持在兩端插入和刪除元素,由於LinkedList類實現了Deque接口,因此一般咱們可使用LinkedList來建立一個隊列。PriorityQueue類實現了一個優先隊列,優先隊列中元素被賦予優先級,擁有高優先級的先被刪除。

如前面提到的,Map接口與Collection接口不一樣,Map提供鍵到值的映射。Map接口提供三種Collection視圖,容許以鍵集、值集或鍵一值映射關係集的形式查看某個映射的內容。Map接口及其相關類的關係如圖2所示。
image

1.HashMap

HashMap是基於哈希表的Map接口的非同步實現,繼承自AbstractMap,AbstractMap是部分實現Map接口的抽象類。在平時的開發中,HashMap的使用仍是比較多的。咱們知道ArrayList主要是用數組來存儲元素的,LinkedList是用鏈表來存儲的,那麼HashMap的實現原理是什麼呢?先看下面這張圖:

在以前的版本中,HashMap採用數組+鏈表實現,即便用鏈表處理衝突,同一hash值的鏈表都存儲在一個鏈表裏。可是當鏈表中的元素較多,即hash值相等的元素較多時,經過key值依次查找的效率較低。而JDK1.8中,HashMap採用數組+鏈表+紅黑樹實現,當鏈表長度超過閾值(8)時,將鏈表轉換爲紅黑樹,這樣大大減小了查找時間。

下面主要經過源碼介紹一下它的實現原理。

HashMap存儲元素的數組
transient Node<K,V>[] table;</pre>

數組的元素類型是Node<K,V>,Node<K,V>繼承自Map.Entry<K,V>,表示鍵值對映射。
static class Node<K,V> implements Map.Entry<K,V> {
 final int hash;
 final K key;
 V value;
 Node<K,V> next;

 //構造函數 ( Hash值鍵值下一個節點 )
 Node(int hash, K key, V value, Node<K,V> next) {
 this.hash = hash;
 this.key = key;
 this.value = value;
 this.next = next;
 }
 public final K getKey()        { return key; }
 public final V getValue()      { return value; }
 public final String toString() { return key + "=" + value; }
 public final int hashCode() {
 return Objects.hashCode(key) ^ Objects.hashCode(value);
 }
​
 public final V setValue(V newValue) {
 V oldValue = value;
 value = newValue;
 return oldValue;
 }
​
 public final boolean equals(Object o) {
 if (o == this)
 return true;
 if (o instanceof Map.Entry) {
 Map.Entry<?,?> e = (Map.Entry<?,?>)o;
 if (Objects.equals(key, e.getKey()) &&
 Objects.equals(value, e.getValue()))
 return true;
 }
 return false;
 }
 }
接下來咱們看下HashMap的put操做。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
 boolean evict) {
 Node<K,V>[] tab; Node<K,V> p; int n, i;
 if ((tab = table) == null || (n = tab.length) == 0)
 n = (tab = resize()).length;  //若是沒有初始化則初始化table
 if ((p = tab[i = (n - 1) & hash]) == null)
 //這裏 (n-1)&hash 是根據hash值獲得這個元素在數組中的位置(即下標)
 tab[i] = newNode(hash, key, value, null);
 //若是數組該位置上沒有元素,就直接將該元素放到此數組中的該位置上
 else {
 Node<K,V> e; K k;
 //第一節節點hash值同,且key值與插入key相同
 if (p.hash == hash &&
 ((k = p.key) == key || (key != null && key.equals(k))))
 e = p;
 else if (p instanceof TreeNode)
 //屬於紅黑樹處理衝突
 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
 else {
 /鏈表處理衝突
 for (int binCount = 0; ; ++binCount) {
 if ((e = p.next) == null) {
 p.next = newNode(hash, key, value, null);
 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
 //新增節點後若是節點個數到達閾值,則將鏈表轉換爲紅黑樹
 treeifyBin(tab, hash);
 break;
 }
 if (e.hash == hash &&
 ((k = e.key) == key || (key != null && key.equals(k))))
 break;
 p = e;
 }
 }
 //更新hash值和key值均相同的節點Value值
 if (e != null) { // existing mapping for key
 V oldValue = e.value;
 if (!onlyIfAbsent || oldValue == null)
 e.value = value;
 afterNodeAccess(e);
 return oldValue;
 }
 }
 ++modCount;
 if (++size > threshold)
 resize();
 afterNodeInsertion(evict);
 return null;
 }
接下來咱們看下HashMap的get操做。
final Node<K,V> getNode(int hash, Object key) {
 Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
 if ((tab = table) != null && (n = tab.length) > 0 &&
 (first = tab[(n - 1) & hash]) != null) {
 if (first.hash == hash && // always check first node
 ((k = first.key) == key || (key != null && key.equals(k))))
 return first;
 if ((e = first.next) != null) {
 //若是第一個節點是TreeNode,說明採用的是數組+紅黑樹結構處理衝突
 //遍歷紅黑樹,獲得節點值
 if (first instanceof TreeNode)
 return ((TreeNode<K,V>)first).getTreeNode(hash, key);
 do {
 if (e.hash == hash &&
 ((k = e.key) == key || (key != null && 
 key.equals(k))))
 return e;
 } while ((e = e.next) != null);
 }
 }
 return null;
 }

到這裏HashMap的大體實現原理應該很清楚了,有幾個須要關注的重點是:HashMap存儲元素的方式以及根據Hash值肯定映射在數組中的位置還有JDK 1.8以後加入的紅黑樹的。

在HashMap中要找到某個元素,須要根據key的hash值來求得對應數組中的位置。對於任意給定的對象,只要它的hashCode()返回值相同,那麼程序調用hash(int h)方法所計算獲得的hash碼值老是相同的。咱們首先想到的就是把hash值對數組長度取模運算,這樣一來,元素的分佈相對來講是比較均勻的。可是,「模」運算的消耗仍是比較大的,在HashMap中, (n - 1) & hash用於計算對象應該保存在table數組的哪一個索引處。HashMap底層數組的長度老是2的n次方,當數組長度爲2的n次冪的時候, (n - 1) & hash 算得的index相同的概率較小,數據在數組上分佈就比較均勻,也就是說碰撞的概率小,相對的,查詢的時候就不用遍歷某個位置上的鏈表,這樣查詢效率也就較高了。

2.LinkedHashMap

LinkedHashMap繼承自HashMap,它主要是用鏈表實現來擴展HashMap類,HashMap中條目是沒有順序的,可是在LinkedHashMap中元素既能夠按照它們插入圖的順序排序,也能夠按它們最後一次被訪問的順序排序。

3.TreeMap

TreeMap基於紅黑樹數據結構的實現,鍵值可使用Comparable或Comparator接口來排序。TreeMap繼承自AbstractMap,同時實現了接口NavigableMap,而接口NavigableMap則繼承自SortedMap。SortedMap是Map的子接口,使用它能夠確保圖中的條目是排好序的。

在實際使用中,若是更新圖時不須要保持圖中元素的順序,就使用HashMap,若是須要保持圖中元素的插入順序或者訪問順序,就使用LinkedHashMap,若是須要使圖按照鍵值排序,就使用TreeMap。

4.ConcurrentHashMap

Concurrent,併發,從名字就能夠看出來ConcurrentHashMap是HashMap的線程安全版。同HashMap相比,ConcurrentHashMap不只保證了訪問的線程安全性,並且在效率上與HashTable相比,也有較大的提升。

三.集合的特色

集合類的特色有三個:

第一點,集合類這種框架是高性能的。對基本類集(動態數組,連接表,樹和散列表)的實現是高效率的。通常人不多去改動這些已經很成熟而且高效的APl;

第二點,集合類容許不一樣類型的集合以相同的方式和高度互操做方式工做;

第三點,集合類容易擴展和修改,程序員能夠很容易地稍加改造就能知足本身的數據結構需求。

相關文章
相關標籤/搜索