JAVA常用資料結構及原理分析

發表於2020-11-09

集合父介面Collection,Map和集合工具類Collections

集合框架Collection的三種主要實現如下:List(列表),Set(雜湊集,有一個key-value的Map進行維護,其中key值保證Set集合裡元素的唯一性),Queue(佇列,先進先出,底層實現可以用List列表或者LinkedList連結串列)

集合框架的另外一種資料型別的總介面是Map,基於Key-Value進行儲存資料的,其中Key鍵值是不可重複的,主要是通過類的hashCode()和equal()進行保證的

Collections,它封裝了所有集合的關於演算法操作的具體實現靜態方法:二分查詢,找出集合最大值,集合最小值,打亂集合,以及生成不可修改集合,同步集合(多執行緒環境下使用),其主要方法API如下:
在這裡插入圖片描述
由於大部分的集合介面實現類都是不同步的,可以使用Collections.synchronized*方法建立同步的集合類物件。
如建立一個同步的List:
List synList = Collections.synchronizedList(new ArrayList());
其實現原理就是重新封裝new出來的物件,操作物件時用關鍵字synchronized同步。看原始碼很容易理解。
Collections部分原始碼:

//Collections.synchronizedList返回的是靜態類SynchronizedCollection的例項,最終將new出來的ArrayList物件賦值給了Collection<e> c。
static class SynchronizedCollection<e> implements Collection<e>, Serializable {
 
         final Collection<e> c;  // Backing Collection
         final Object mutex;     // Object on which to synchronize
 
         SynchronizedCollection(Collection<e> c) {
             if (c== null )
                 throw new NullPointerException();
             this .c = c;
             mutex = this ;
         }
         //...
         public boolean add(E e) {
             //鎖定訊號量才可以對集合進行操作,在多執行緒環境下來進行同步,呼叫的還是原來的方法
             //操作集合時簡單呼叫原本的ArrayList物件,只是做了同步
             synchronized (mutex) { return c.add(e);}
         }
         //...
}

List(列表)

List列表允許儲存相同元素,插入元素和按照下標獲取元素方便,具體實現有ArrayList,LinkedList和Vector

  1. ArrayList底層基於陣列實現的,按順序儲存元素以及快速按照元素下標進行獲取元素,不可同步的;

  2. Vector底層也是陣列,可進行同步操作,在多執行緒環境下可以使用;

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //可以看出新增的物件放到elementData陣列中去了
    elementData[size++] = e;
    return  true;
}
//ArrayList.remove
public E remove(int index) {
    rangeCheck(index);
 
    modCount++;
    E oldValue = elementData(index);
 
    int  numMoved = size - index - 1;
    if  (numMoved > 0)
        //移除元素時陣列產生的空位由System.arraycopy方法將其後的所有元素往前移一位,System.arraycopy呼叫虛擬機器提供的本地方法來提升效率
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // Let gc do its work
 
    return  oldValue;
}
 
//Vector add方法上多了synchronized關鍵字
public  synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return  true;
}
  1. LinkedList是連結串列可以在具體下標位置刪除和新增元素,在許多需要根據具體下標新增和刪除元素的應用場景下比ArrayList有更好的效能,遍歷比線性錶慢。
public class LinkedList<e>
extends AbstractSequentialList<e>
implements List<e>, Deque<e>, Cloneable, java.io.Serializable
{
    //頭尾節點
    transient  Node<e> first;
    transient  Node<e> last;
}
//節點類
 private  static class  Node<e> {
    //節點儲存的資料
    E item;
    Node<e> next;
    Node<e> prev;
    Node(Node<e> prev, E element, Node<e> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

Map(儲存鍵值對,key唯一)

HashMap結構的實現原理是將put進來的key-value封裝成一個Entry物件儲存到一個Entry陣列中,位置(陣列下標)由key的雜湊值與陣列長度計算而來。如果陣列當前下標已有值,則將陣列當前下標的值指向新新增的Entry物件。

public  class HashMap<k,v>
extends AbstractMap<k,v>
implements Map<k,v>, Cloneable, Serializable
{
    transient  Entry<k,v>[] table;
    public  V put(K key, V value) {
        if  (key == null)
            return  putForNullKey(value);
        int  hash = hash(key);
        int  i = indexFor(hash, table.length);
        //遍歷當前下標的Entry物件鏈,如果key已存在則替換
        for  (Entry<k,v> e = table[i]; e != null; e = e.next) {
        Object k;
        if  (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return  oldValue;
        }
    }
       addEntry(hash, key, value, i);
        return  null;
    }
}
static  class Entry<k,v> implements  Map.Entry<k,v> {
    final  K key;
    V value;
    Entry<k,v> next;
    int  hash;
}

TreeMap是由Entry物件為節點組成的一顆紅黑樹,put到TreeMap的資料預設按key的自然順序排序,new TreeMap時傳入Comparator自定義排序。

HashMap和TreeMap的區別

(1)HashMap:適用於在Map中插入、刪除和定位元素。
(2)Treemap:適用於按自然順序或自定義順序遍歷鍵(key)。
(3)HashMap通常比TreeMap快一點(樹和雜湊表的資料結構使然),建議多使用HashMap,在需要排序的Map時候才用TreeMap.
(4)HashMap 非執行緒安全 TreeMap 非執行緒安全
(5)HashMap的結果是沒有排序的,而TreeMap輸出的結果是排好序的

Set(保證容器內元素唯一性,插入刪除效率高)

Set結構其實就是維護一個Map來儲存資料的,利用Map結構key值唯一性
HashSet的底層通過HashMap實現的,而HashMap在1.7之前使用的是陣列+連結串列實現,在1.8+使用的陣列+連結串列+紅黑樹實現。其實也可以這樣理解,HashSet的底層實現和HashMap使用的是相同的方式,因為Map是無序的,因此HashSet也無法保證順序。HashSet的方法也是藉助HashMap的方法來實現的。

public  class HashSet<e>
extends AbstractSet<e>
implements Set<e>, Cloneable, java.io.Serializable
{    
 
    //無意義物件來作為Map的value 
    private  static final  Object PRESENT = new  Object();
    public  boolean add(E e) {
        return  map.put(e, PRESENT)==null;
    }
}

TreeSet實現了SortedSet介面,它是一個有序的集合類,TreeSet的底層是通過TreeMap實現的。TreeSet並不是根據插入的順序來排序,而是根據實際的值的大小來排序。。

陣列和連結串列的區別

不同:

  1. 連結串列是鏈式的儲存結構;陣列是順序的儲存結構 連結串列通過指標來連線元素與元素,陣列則是把所有元素按次序依次儲存。
  2. 連結串列的插入刪除元素相對陣列較為簡單,不需要移動元素,且較為容易實現長度擴充,但是尋找某個元素較為困難;
陣列尋找某個元素較為簡單,但插入與刪除比較複雜,由於最大長度需要再程式設計一開始時指定,故當達到最大長度時,擴充長度不如連結串列方便。

相同
兩種結構均可實現資料的順序儲存,構造出來的模型呈線性結構。

連結串列是什麼

連結串列是一種上一個元素的引用指向下一個元素的儲存結構,連結串列通過指標來連線元素與元素;連結串列就是將一系列不連續的記憶體聯絡起來,將那種碎片記憶體進行合理的利用,解決空間的問題。

什麼是hash碰撞?

HashMap中用的最多的方法就屬put() 和 get() 方法;HashMap的Key值是唯一的,那如何保證唯一性呢?我們首先想到的是用equals比較,沒錯,這樣可以實現,但隨著內部元素的增多,put和get的效率將越來越低,這裡的時間複雜度是O(n),假如有1000個元素,put時最差情況需要比較1000次。實際上,HashMap很少會用到equals方法,因為其內通過一個雜湊表管理所有元素,雜湊是通過hash單詞音譯過來的,也可以稱為雜湊表,雜湊演算法可以快速的存取元素,當我們呼叫put存值時,HashMap首先會呼叫Key的hash方法,計算出雜湊碼,通過雜湊碼快速找到某個存放位置(桶),這個位置可以被稱之為bucketIndex,但可能會存在多個元素找到了相同的bucketIndex,有個專業名詞叫碰撞,當碰撞發生時,這時會取到bucketIndex位置已儲存的元素,最終通過equals來比較,equals方法就是碰撞時才會執行的方法,所以前面說HashMap很少會用到equals。HashMap通過hashCode和equals最終判斷出Key是否已存在,如果已存在,則使用新Value值替換舊Value值,並返回舊Value值,如果不存在 ,則存放新的鍵值對<K, V>到bucketIndex位置。
  碰撞:多個元素計算得出相同的hashCode,在put時出現衝突。

HashMap 碰撞問題處理:

Java中HashMap是利用“拉鍊法”處理HashCode的碰撞問題。在呼叫HashMap的put方法或get方法時,都會首先呼叫hashcode方法,去查詢相關的key,當有衝突時,再呼叫equals方法。hashMap基於hasing原理,我們通過put和get方法存取物件。當我們將鍵值對傳遞給put方法時,他呼叫鍵物件的hashCode()方法來計算hashCode,然後找到bucket(雜湊桶)位置來儲存物件。當獲取物件時,通過鍵物件的equals()方法找到正確的鍵值對,然後返回值物件。HashMap使用連結串列來解決碰撞問題,當碰撞發生了,物件將會儲存在連結串列的下一個節點中。hashMap在每個連結串列節點儲存鍵值對物件。當兩個不同的鍵卻有相同的hashCode時,他們會儲存在同一個bucket位置的連結串列中。鍵物件的equals()來找到鍵值對。

hashcode是什麼?

  1. hashcode就是通過hash函式得來的,通俗的說,就是通過某一種演算法得到的,hashcode就是在hash表中有對應的位置。
  2. hash是一個函式,該函式中的實現就是一種演算法,就是通過一系列的演算法來得到一個hash值,這個時候,我們就需要知道另一個東西,hash表,通過hash演算法得到的hash值就在這張hash表中,也就是說,hash表就是所有的hash值組成的,有很多種hash函式,也就代表著有很多種演算法得到hash值,如上面截圖的三種,等會我們就拿第一種來說。

相關文章