1. 程式人生 > >原始碼分析HashMap、Hashtable、HashSet的區別

原始碼分析HashMap、Hashtable、HashSet的區別

HashMap原始碼分析-基於JDK1.8

基本結構

1)、初始變數

public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {
    private static final long serialVersionUID = 362498820763181265L;
    //預設初始容量
    static final int DEFAULT_INITIAL_CAPACITY = 16;
    //最大容量
    static final int MAXIMUM_CAPACITY = 1073741824;
    //預設擴充套件因子,達到容量的一定係數就開始擴容
    static final float DEFAULT_LOAD_FACTOR = 0.75F;
    //轉為紅黑樹結構的 
    static final int TREEIFY_THRESHOLD = 8;
    //當桶(連結串列)節點數大於這個值時會轉為紅黑樹
    static final int UNTREEIFY_THRESHOLD = 6;
    //桶結構轉化為紅黑樹對應的table的最小大小
    static final int MIN_TREEIFY_CAPACITY = 64;
    //儲存元素的陣列
    transient HashMap.Node<K, V>[] table;
    //存放具體元素的值
    transient Set<Entry<K, V>> entrySet;
    //存放元素的個數
    transient int size;
    //每次擴容修改結構map結構的計數器
    transient int modCount;
    //臨界值,當實際大小超過臨界值會擴容
    int threshold;
    //載入因子
    final float loadFactor;
}

2)、連結串列結構

存放的是鍵值對。

static class Node<K, V> implements Entry<K, V> {
        final int hash; //hash值
        final K key; 
        V value;
        HashMap.Node<K, V> next;  //下一個節點
//.......省略......
	
        public final String toString() {
            return this.key + "=" + this.value;
        }
		//key的hash值與value的hash值的異或結果
        public final int hashCode() {
            return Objects.hashCode(this.key) ^ Objects.hashCode(this.value);
        }
		.......省略........
		//判斷連結串列值是否相等,key相等且value相等
        public final boolean equals(Object var1) {
            if (var1 == this) {
                return true;
            } else {
                if (var1 instanceof Entry) {
                    Entry var2 = (Entry)var1;
                    if (Objects.equals(this.key, var2.getKey()) && Objects.equals(this.value, var2.getValue())) {
                        return true;
                    }
                }
                return false;
            }
        }
    }

3)、紅黑樹結構

繼承於LinkedHashMap,提高查詢效率。

    static final class TreeNode<K, V> extends java.util.LinkedHashMap.Entry<K, V> {
        HashMap.TreeNode<K, V> parent; //父節點
        HashMap.TreeNode<K, V> left; //左節點
        HashMap.TreeNode<K, V> right; 
        HashMap.TreeNode<K, V> prev;
        boolean red; //顏色標誌

        TreeNode(int var1, K var2, V var3, HashMap.Node<K, V> var4) {
            super(var1, var2, var3, var4);
        }
		//返回根節點
        final HashMap.TreeNode<K, V> root() {
            HashMap.TreeNode var1 = this;
            while(true) {
                HashMap.TreeNode var2 = var1.parent;
                if (var1.parent == null) {
                    return var1;
                }
                var1 = var2;
            }
        }
        ......省略.......
 }
hash演算法
  1. 計算key的hashcode —h
  2. 對h無符號向右位移 16位 i
  3. h和i做異或運算。使得高位也可以參與hash,更大程度上減少了碰撞率。
static final int hash(Object var0) {
        int var1;
        return var0 == null ? 0 : (var1 = var0.hashCode()) ^ var1 >>> 16;
    }
重要方法分析
1)、存放資料putVal方法

對外方法

 //計算key的hash值,呼叫putval方法
 public V put(K var1, V var2) {
        return this.putVal(hash(var1), var1, var2, false, true);
    }
final V putVal(int var1, K var2, V var3, boolean var4, boolean var5) {
        HashMap.Node[] var6 = this.table;
        int var8;
        //陣列是否存在,或長度是否為0 
        if (this.table == null || (var8 = var6.length) == 0) {
        	//是->進行擴容
            var8 = (var6 = this.resize()).length;
        }
        Object var7;
        int var9;
        //計算當前索引,判斷table[i]是否存在
        if ((var7 = var6[var9 = var8 - 1 & var1]) == null) {
        	//不存在--->新建節點
            var6[var9] = this.newNode(var1, var2, var3, (HashMap.Node)null);
        } else {
            Object var10;
            label79: {
                Object var11;
                //存在---判斷key值是否在陣列中存在
                if (((HashMap.Node)var7).hash == var1) {
                    var11 = ((HashMap.Node)var7).key;
                    //判斷key值是否相等
                    if (((HashMap.Node)var7).key == var2 || var2 != null && var2.equals(var11)) { //是 --->替換舊值
                        var10 = var7;
                        break label79;
                    }
                }
				//table[i] 是否是紅黑樹結構
                if (var7 instanceof HashMap.TreeNode) {
                //是--加入紅黑樹結構
                    var10 = ((HashMap.TreeNode)var7).putTreeVal(this, var6, var1, var2, var3);
                } else {
                 //否--->遍歷連結串列
                    int var12 = 0;

                    while(true) {
                    	//是否有下一個節點
                        var10 = ((HashMap.Node)var7).next;
                        if (((HashMap.Node)var7).next == null) {
                        	//無,建立節點
                            ((HashMap.Node)var7).next = this.newNode(var1, var2, var3, (HashMap.Node)null);
                            //節點數是否 >= 7
                            if (var12 >= 7) {
                            //節點大於8 ,轉為紅黑樹結構
                                this.treeifyBin(var6, var1);
                            }
                            break;
                        }
						//hash判斷key是否存在
                        if (((HashMap.Node)var10).hash == var1) {
                            var11 = ((HashMap.Node)var10).key;
                            if (((HashMap.Node)var10).key == var2 || var2 != null && var2.equals(var11)) {
                                break;
                            }
                        }
						//存在--->替換舊值
                        var7 = var10;
                        //節點數的計數器
                        ++var12;
                    }
                }
            }
            if (var10 != null) {
                Object var13 = ((HashMap.Node)var10).value;
                if (!var4 || var13 == null) {
                    ((HashMap.Node)var10).value = var3;
                }
                this.afterNodeAccess((HashMap.Node)var10);
                //返回舊值
                return var13;
            }
        }
        ++this.modCount;
        //陣列長度是否大於臨界值
        if (++this.size > this.threshold) {
        	//擴容
            this.resize();
        }
        this.afterNodeInsertion(var5);
        return null;
    }

putval呼叫流程圖如下:
在這裡插入圖片描述

HashMap儲存原理:
  1. 根據key計算key.hashcode = (h = hash(key) ^ h>>>16).
  2. 根據key.hash 計算得到桶的索引 i。
    1. 如果該索引位置無資料 table[i],則用該資料生成一個新的節點
    2. 如果該索引有資料且是一個紅黑樹,在紅黑樹中執行更新或新增操作
    3. 如果該索引有資料且是一個連結串列,遍歷連結串列,在連結串列結尾建立節點,如果節點數超過7 ,轉為紅黑樹,判斷key的hash值是否相等,key及value相等跳出迴圈。
2)、get方法
public V get(Object var1) {
    HashMap.Node var2;
    return (var2 = this.getNode(hash(var1), var1)) == null ? null : var2.value;
}
	//計算key的hash值,根據key.hash取值
    final HashMap.Node<K, V> getNode(int var1, Object var2) {
        HashMap.Node[] var3 = this.table;
        HashMap.Node var4;
        int var6;
        //陣列是否存在,長度 ,根據keyhash計算的索引對應的資料 table[i]
        if (this.table != null && (var6 = var3.length) > 0 && (var4 = var3[var6 - 1 & var1]) != null) {
            Object var7;
            //根據keyhash相同找位置  ----其實查詢的是第一項資料
            if (var4.hash == var1) {
                var7 = var4.key;
                //equals方法判斷key是否一致
                if (var4.key == var2 || var2 != null && var2.equals(var7)) {
                    return var4;
                }
            }
            HashMap.Node var5 = var4.next;
            //存在連結串列
            if (var4.next != null) {
            //連結串列是紅黑樹--從紅黑樹中取
                if (var4 instanceof HashMap.TreeNode) {
                    return ((HashMap.TreeNode)var4).getTreeNode(var1, var2);
                }
                //遍歷連結串列--獲取value
                do {
                    if (var5.hash == var1) {
                        var7 = var5.key;
                        if (var5.key == var2 || var2 != null && var2.equals(var7)) {
                            return var5;
                        }
                    }
                } while((var5 = var5.next) != null);
            }
        }
        return null;
    }

在這裡插入圖片描述

HashMap取資料原理
  1. 計算key的hash值
  2. 判斷table及根據key.hash計算的索引的table[i]值是否!=null.
  3. 開始查詢資料
    1. 查詢第一個元素,是-則返回
    2. 是否是紅黑樹,是—則在紅黑樹中查詢
    3. 遍歷連結串列查詢keyhash相等且使用equals方法相等的。
3)、resize()方法
  1. 當陣列大小達到臨界值或者在初始化的時候,則開始進行擴容
  2. 每次擴容都是原來容量的2倍
  3. 擴充套件後Node物件的位置要麼不變,要麼是原來位置的兩倍。
  4. 擴容的原理:重新建立一個new HashMap.Node[var4],將老的資料複製到新的Node,並將老的置空。

區別

原理:

HashMap實現原理:JDK1.8以前是陣列+連結串列結構,jdk1.8 改為陣列+連結串列+紅黑樹結構。當連結串列的節點大於等於8的時候就會使用紅黑樹結構。

Hashtable實現原理:陣列+連結串列結構。

**HashSet實現原理:**是簡單型別的HashMap,值是固定的,只儲存key值。

主幹table是元素為連結串列的陣列,連結串列是為了解決hash衝突的,當使用key值計算的hash地址一致時,在該下標下增加連結串列,存放及查詢時需要遍歷陣列先查下標,如果該下標有連結串列還需要遍歷連結串列,通過key值的equals方法判斷取值或存值。

1、初始化
  • HashMap:初始化容量為16(必須是2的倍數)
  • Hashtable:初始化容量為11

兩者的載入因子都是0.75.

2、執行緒是否安全
  • HashMap:執行緒不安全,如果是單執行緒操作,效率較高。如果要使HashMap執行緒安全,可以使用Collection.synchronizedMap(hashmap)進行同步。
  • Hashtable:執行緒安全,內部方法都使用了synchronized保證執行緒安全,因此單執行緒環境下他比HashMap慢。
3、空鍵值
  • HashMap:鍵值都可以為null ,key為null 時放在下標為1的位置。
  • Hashtable:鍵值不可以為null
4、擴容
  • HashMap:擴容大小是原大小的2倍
  • Hashtable:擴容大小是*2+1。

HashSet是儲存了一個固定值的的HashMap,結構及初始化基本相同,具體實現上有區別,set實現的是Set介面,map實現的是map介面。兩者hashcode的演算法不一樣,HashMap是使用key計算hash值,hashset使用成員物件計算hash值,並使用物件的equals方法判斷物件的想等性。

參考文案連結

  1. https://www.cnblogs.com/xiaoxi/p/7233201.html
  2. https://mp.weixin.qq.com/s/WSHJW8nnNqC28IaZ3bKzbg