中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

HashMap,難的不在Map,而在Hash

發布時間:2020-07-06 10:33:11 來源:網絡 閱讀:116 作者:沉默王二 欄目:編程語言

在平常的開發當中,HashMap是我最常用的Map類(沒有之一),它支持null鍵和null值,是絕大部分利用鍵值對存取場景的首選。需要切記的一點是——HashMap不是線程安全的數據結構,所以不要在多線程場景中應用它。

通常情況下,我們使用Map的主要目的是用來放入(put)、訪問(get)或者刪除(remove),而對順序沒有特別的要求——HashMap在這種情況下就是最好的選擇。

01、Hash

對于HashMap來說,難理解的不在于Map,而在于Hash。

Hash,一般譯作“散列”,也有直接音譯為“哈希”的,這玩意什么意思呢?就是把任意長度的數據通過一種算法映射到固定長度的域上(散列值)。

再直觀一點,就是對一串數據wang進行雜糅,輸出另外一段固定長度的數據er——作為數據wang的特征。我們通常用一串指紋來映射某一個人,別小瞧手指頭那么大點的指紋,在你所處的范圍內很難找出第二個和你相同的(人的散列算法也好厲害,有沒有?)。

對于任意兩個不同的數據塊,其散列值相同的可能性極小,也就是說,對于一個給定的數據塊,找到和它散列值相同的數據塊極為困難。再者,對于一個數據塊,哪怕只改動它的一個比特位,其散列值的改動也會非常的大——這正是Hash存在的價值!

在Java中,String字符串的散列值計算方法如下:

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

看得懂看不懂都沒關系,我們就當是一個“乘加迭代運算”的算法。借此機會,我們來看一下“沉”、“默”、“王”、“二”四個字符串的散列值是多少。

String[] cmower = { "沉", "默", "王", "二" };
for (String s : cmower) {
    System.out.println(s.hashCode());
}

輸出的結果如下(5位數字):

沉的散列值:27785
默的散列值:40664
王的散列值:29579
二的散列值:20108

對于HashMap來說,Hash(key,鍵位)存在的目的是為了加速鍵值對的查找(你想,如果電話薄不是按照人名的首字母排列的話,找一個人該多困難「我的微信好友有不少在昵稱前加了A,好狠」)。通常情況下,我們習慣使用String字符串來作為Map的鍵,請看以下代碼:

Map<String, String> map = new HashMap<>();
String[] cmower = { "沉", "默", "王", "二" };
for (String s : cmower) {
    map.put(s, s + "月入25萬");
}

那HashMap會真的會將String字符串作為實際的鍵嗎?我們來看HashMap的put方法源碼:

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

雖然只有一個putVal()方法的調用,但是你應該已經發現,HashMap內部會把key進行一個hash運算,具體代碼如下:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

假如key是String字符串的話,hash()會先獲取字符串的hashCode(散列值),再對散列值進行位于運算,最終的值為HashMap實際的鍵(int值)。

既然HashMap在put的時候使用鍵的散列值作為實際的鍵,那么在根據鍵獲取值的時候,自然也要先對get(key)方法的key進行hash運算,請看以下代碼:

public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

02、散列值沖突怎么解決

盡管散列值很難重復,我們還是要明白,這種轉換是一種壓縮映射,也就是,散列值的空間通常遠小于輸入的空間,不同的輸入可能會散列成相同的輸出。

也就是說,key1 ≠ key2,但function(key1)有可能等于function(key2)——散列值沖突了。怎么辦?

最容易想到的解決辦法就是:當關鍵字key2的散列值value與key1的散列值value出現沖突時,以value為基礎,產生另一個散列值value1,如果value1與value不再沖突,則將value1作為key2的散列值。

依照這個辦法,總會找到不沖突的那個。

03、初始容量和負載因子

HashMap的構造方法主要有三種:

public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
}

其中initialCapacity為初始容量(默認為1 << 4 = 16),loadFactor為負載因子(默認為0.75)。初始容量是HashMap在創建時的容量(HashMap中桶的數量);負載因子是HashMap在其容量自動增加之前可以達到多滿的一種尺度。

當HashMap中的條目數超出了負載因子與當前容量的乘積時,則要對HashMap擴容,增加大約兩倍的桶數。

通常,默認的負載因子 (0.75) 是時間和空間成本上的一種折衷。負載因子過高雖然減少了空間開銷,但同時增加了查詢成本。如果負載因子過小,則初始容量要增大,否則會導致頻繁的擴容。

在設置初始容量時應該考慮到映射中所需的條目數及其加載因子,以便最大限度地減少擴容的操作次數。

如果能夠提前預知要存取的鍵值對數量的話,可以考慮設置合適的初始容量(大于“預估元素數量 / 負載因子”,并且是2的冪數)。

04、小結

在之前很長的一段時間內,我對HashMap的認知僅限于會用它的put(key, value)value = get(key)

但,當我強迫自己每周要輸出一篇Java方面的技術文章后,我對HashMap真的“深入淺出”了——散列值(哈希值)、散列沖突(哈希沖突)、初始容量和負載因子,竟然能站在我面前一直笑——而原先,我見到這些關鍵字就逃之夭夭了,我怕見到它們。


上一篇:Java 集合類入門篇

下一篇:Java泛型的重要目的:別讓貓別站在狗隊里

微信搜索「沉默王二」公眾號,關注后回復「免費視頻」獲取 500G 高質量教學視頻(已分門別類)。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

和平县| 宜城市| 隆化县| 定西市| 巴林左旗| 怀安县| 报价| 稻城县| 西宁市| 商水县| 和顺县| 弥渡县| 渑池县| 澄城县| 二连浩特市| 勐海县| 松原市| 茌平县| 德令哈市| 儋州市| 泾源县| 桦南县| 肥西县| 广东省| 宁远县| 四会市| 衡东县| 阳新县| 遂溪县| 南宁市| 新干县| 界首市| 北辰区| 确山县| 四平市| 沙湾县| 乐业县| 太谷县| 澎湖县| 夏津县| 镇雄县|