日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

深入分析HashMap与Hashtable的区别

發(fā)布時間:2024/9/18 43 生活家
生活随笔 收集整理的這篇文章主要介紹了 深入分析HashMap与Hashtable的区别 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

一、前言

??上個月花了點時間研究了一下HashMap的源碼,對HashMap的實現(xiàn)原理有了一個較為深入的了解,今天突然想到有一個常考的面試題——HashMapHashtable的區(qū)別,于是又花了點時間研究了一下Hashtable的源碼。今天這篇博客就來簡單介紹一下兩者的區(qū)別,以下內(nèi)容將主要從Hashtable的角度,描述它和HashMap有什么不同,而且將分兩個方面來敘述——使用方面實現(xiàn)細節(jié)方面。如果對HashMap不是很了解的,可以閱讀一下這篇博客:HashMap源碼解讀——深入理解HashMap高效的原因。

二、正文

?2.1 使用上的不同

?(1)Hashtable線程安全,HashMap線程不安全

??這是這兩個容器最根本的區(qū)別。HashMap是一個線程不安全的容器,它并沒有實現(xiàn)線程同步,所以不應(yīng)該在多線程的環(huán)境下使用。而Hashtable實現(xiàn)了線程同步,是一個線程安全的容器,但是它實現(xiàn)同步的方式比較拙劣——直接在大部分的public方法上添加synchronized關(guān)鍵字進行同步,這樣對于同一個Hashtable對象,每次只能有一個線程調(diào)用它的實例方法。這種拙劣的同步方式使得Hashtable的效率要遠遠低于HashMap,而且在很多情況下,這種同步是沒有意義的,比如在沒有寫操作發(fā)生的情況下,完全可以多個線程同時對Hashtable進行讀操作,并不會造成危害。

?(2)Hashtable不允許key或value為null,HashMap可以

??在Hashtable的很多方法中(比如put方法),特判了valuenull的情況,此時將直接拋出一個NullPointerException;而對于keynull的情況卻沒有特判,但是若在其中加入一個keynull的元素,依舊會拋出NullPointerException,因為Hashtable中獲取keyhash值是直接調(diào)用key.hashCode方法,所有key不能為null。我們看Hashtable中的put方法即可知曉(多余的代碼被刪除):

public synchronized V put(K key, V value) {
    // Make sure the value is not null
    if (value == null) {
        throw new NullPointerException();
    }

    // ......
    int hash = key.hashCode();

    // ......
}

??但是HashMap中,無論是key還是value,都可以為null,因為它對null做了特殊處理。HashMap實現(xiàn)中,判斷keynull,則不會調(diào)用key.hashCode獲取它的hash值,而是將0作為它的hash值;而在get方法獲取value時,如果找到的節(jié)點為null,則直接返回null,否則返回node.value。同樣看HashMap中的兩段代碼來驗證這一點:

// 此方法為HashMap中獲取key的hash值的方法,特判了key == null的情況
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

// HashMap中的get方法,特判了value為null的情況
public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

?2.2 實現(xiàn)細節(jié)上的不同

?(1)Hashtable使用頭插法,JDK1.8之后的HashMap使用尾插法

??先簡單說一說什么是頭插和尾插:

頭插法:在鏈表中插入一個新的節(jié)點時,將新節(jié)點作為鏈表的頭節(jié)點,然后將新節(jié)點的next指向舊頭節(jié)點;
尾插法:在鏈表中插入一個新的節(jié)點時,讓鏈表的尾節(jié)點的next指向新節(jié)點;

??Hashtable使用的是頭插法,而HashMapJDK1.8之前,使用的也是頭插法。但是,有人發(fā)現(xiàn)在并發(fā)情況下,若沒有進行線程同步,頭插法有很小的概率會導(dǎo)致死循環(huán)(這個問題我就不展開描述了,可以自己去查一查),于是從JDK1.8開始,為HashMap添加節(jié)點改為了尾插法。

?(2)HashMap的容量必須是2^n,而Hashtable的容量沒有這個限制

??這是兩者之間非常大的一個區(qū)別,就這一點,讓HashMapHashtble在實現(xiàn)上有很大的不同。HashMap的容量一定是2^n,其原因我在前言中推薦的博客里面做了詳細的描述,這里就簡單說一下。容量定為2^n是為了對取余運算做優(yōu)化,因為2^n滿足一個性質(zhì):

num % 2^n == num & (2^n - 1)

??不論是HashMap還是Hashtable,要獲取節(jié)點在數(shù)組中的下標,都需要用keyhash值對數(shù)組長度取余,而HashMap將數(shù)組長度限制為2^n,就是為了用上面公式中的&替代%進行取余操作,因為&的效率要比%更高,而獲取下標又是一個頻繁的操作,所以這一步優(yōu)化讓HashMap的查找速度有了很大的提升。而在Hashtable中,就是直接用%取余。我們看兩者取余的源碼:

// HashMap中的取余:tab為存放節(jié)點的數(shù)組,n為數(shù)組長度,hash變量存的是key的哈希值
// 這一步就是獲取哈希值為hash的key在tab中的位置
// HashMap中的hash值不會是負數(shù),因為HashMap中自己實現(xiàn)了hash方法求hash值
tab[(n - 1) & hash]

// Hashtable中的取余:hash & 0x7FFFFFFF其實就是取hash的絕對值,然后直接對tab.length取余
// 0x7FFFFFFF轉(zhuǎn)換成二進制就是第一位為0,后31位為1,這個操作也就是將符號位轉(zhuǎn)為0(第一位為符號位),
// 而二進制中,符號位為0表示表示正數(shù),這一步是防止計算出負數(shù)的下標,因為hash值可能為負
// 不用Math.abs是因為Math.abs(Integer.MAX_VALUE)越界,結(jié)果還是負數(shù)
int index = (hash & 0x7FFFFFFF) % tab.length;

??而正是這個優(yōu)化,導(dǎo)致了HashMapHashtable的許多其他區(qū)別:

HashMap的默認初始容量為16,也就是2^4,如果指定了一個容量,則HashMap不會直接用它,而是先找出第一個大于這個指定容量的2^n;而Hashtable的默認初始容量是1111是一個質(zhì)數(shù),根據(jù)hash求下標時,質(zhì)數(shù)能夠讓下標分配更加均勻,Hashtable會直接接收指定的初始容量;
HashMap擴容時直接將數(shù)組大小乘2,因為(2^n)×2 == 2^(n+1),仍然滿足2^n;而Hashtable擴容是乘2加1,因為這樣可以讓一個質(zhì)數(shù)很大概率還是一個質(zhì)數(shù);

?(3)JDK1.8開始,HashMap結(jié)構(gòu)變?yōu)閿?shù)組+鏈表+紅黑樹

??在JDK1.7以前,HashMapHashtable的結(jié)構(gòu)都是數(shù)組+鏈表的實現(xiàn),但是從JDK1.8開始,HashMap變成了數(shù)組+鏈表+紅黑樹。這是因為,在hash取余的結(jié)果不均勻的情況下,節(jié)點可能集中在數(shù)組中的某幾條鏈表上,導(dǎo)致鏈表過長。比如舉一個極端的例子,所有的節(jié)點通過hash值計算出的下標都相等,此時所有的節(jié)點都在一條鏈表上,這個時候HashMap或者Hashtable就退化成了一個鏈表,查詢的時間復(fù)雜度退化為了O(n)。為了避免這種情況的發(fā)生,從JDK1.8開始,HashMap中若某條鏈表的節(jié)點數(shù)達到了8個,就會將這條鏈表轉(zhuǎn)換為紅黑樹,此時,對這條鏈表的查詢效率就從O(n)提升為O(logn)。而Hashtable作為一個已經(jīng)過時的容器,自然不會花時間對它進行復(fù)雜的優(yōu)化。

三、總結(jié)

??Hashtable是一個過時的容器,在開發(fā)當中,我們應(yīng)該少去使用它,甚至根本不要使用它。在單線程的環(huán)境下,我們最好使用HashMap,而在多線程的環(huán)境下,應(yīng)該使用ConcurrentHashMap,它的效率要遠遠高于Hashtable。上面最后提到的幾點都和HashMap的實現(xiàn)有關(guān),建議閱讀我在前言中推薦的博客,了解HashMap的具體實現(xiàn)。

四、參考

https://blog.csdn.net/weixin_43871333/article/details/100888713

總結(jié)

以上是生活随笔為你收集整理的深入分析HashMap与Hashtable的区别的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。