1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        終于有人把HashTable這種數(shù)據(jù)結(jié)構(gòu)講清楚了!

        共 9028字,需瀏覽 19分鐘

         ·

        2020-12-03 13:49

        概論

        HashTable是遺留類,很多映射的常用功能與HashMap類似,不同的是它承自Dictionary類,并且是線程安全的,并發(fā)性不如ConcurrentHashMap,因?yàn)镃oncurrentHashMap引入了分段鎖。

        Hashtable不建議在新代碼中使用,不需要線程安全的場(chǎng)合可以用HashMap替換,需要線程安全的場(chǎng)合可以用ConcurrentHashMap替換。

        對(duì)比HashMap 的初始容量

        默認(rèn)11 的初始容量

        需要注意的是Hashtable的默認(rèn)初始容量大小是11,而HashMap 是16,但是他們的加載因子都是0.75f

        ????/**
        ?????*?Constructs?a?new,?empty?hashtable?with?a?default?initial?capacity?(11)
        ?????*?and?load?factor?(0.75).
        ?????*/

        ????public?Hashtable()?{
        ????????this(11,?0.75f);
        ????}
        /**
        ?*?Constructs?an?empty?HashMap?with?the?default?initial?capacity
        ?*?(16)?and?the?default?load?factor?(0.75).
        ?*/

        public?HashMap()?{
        ????this.loadFactor?=?DEFAULT_LOAD_FACTOR;?//?all?other?fields?defaulted
        }

        任意指定非負(fù)的容量

        還有一點(diǎn)就是Hashtable的initialCapacity 也就是初始容量是是可以是你指定的任何非負(fù)整數(shù),也就是你給它設(shè)置個(gè)0 也可以的

        public?Hashtable(int?initialCapacity)?{
        ????this(initialCapacity,?0.75f);
        }

        public?Hashtable(int?initialCapacity,?float?loadFactor)?{
        ????if?(initialCapacity?0)
        ????????throw?new?IllegalArgumentException("Illegal?Capacity:?"+
        ???????????????????????????????????????????initialCapacity);
        ????if?(loadFactor?<=?0?||?Float.isNaN(loadFactor))
        ????????throw?new?IllegalArgumentException("Illegal?Load:?"+loadFactor);

        ????if?(initialCapacity==0)
        ????????initialCapacity?=?1;
        ????this.loadFactor?=?loadFactor;
        ????table?=?new?Entry[initialCapacity];
        ????threshold?=?(int)Math.min(initialCapacity?*?loadFactor,?MAX_ARRAY_SIZE?+?1);
        }

        但是你看一下HashMap 的初始容量就不那么聽(tīng)話了,默認(rèn)情況下,當(dāng)我們?cè)O(shè)置HashMap的初始化容量時(shí),實(shí)際上HashMap會(huì)采用第一個(gè)大于該數(shù)值的2的冪作為初始化容量(0 1 除外)

        public?HashMap(int?initialCapacity,?float?loadFactor)?{
        ????if?(initialCapacity?0)
        ????????throw?new?IllegalArgumentException("Illegal?initial?capacity:?"?+?initialCapacity);
        ????if?(initialCapacity?>?MAXIMUM_CAPACITY)
        ????????initialCapacity?=?MAXIMUM_CAPACITY;
        ????if?(loadFactor?<=?0?||?Float.isNaN(loadFactor))
        ????????throw?new?IllegalArgumentException("Illegal?load?factor:?"?+?loadFactor);
        ????this.loadFactor?=?loadFactor;
        ????this.threshold?=?tableSizeFor(initialCapacity);
        }

        對(duì)比HashMap 的 對(duì)null 值的支持

        HashTable key value 都不支持null

        首先HashMap 是支持null 值做key和value 的,但是HashTable 是不支持的,key 也不支持 value 也不支持

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

        ????//?Makes?sure?the?key?is?not?already?in?the?hashtable.
        ????Entry?tab[]?=?table;
        ????int?hash?=?key.hashCode();
        ????int?index?=?(hash?&?0x7FFFFFFF)?%?tab.length;
        ????@SuppressWarnings("unchecked")
        ????Entry?entry?=?(Entry)tab[index];
        ????for(;?entry?!=?null?;?entry?=?entry.next)?{
        ????????if?((entry.hash?==?hash)?&&?entry.key.equals(key))?{
        ????????????V?old?=?entry.value;
        ????????????entry.value?=?value;
        ????????????return?old;
        ????????}
        ????}
        ????addEntry(hash,?key,?value,?index);
        ????return?null;
        }

        聰明的你們發(fā)現(xiàn)了嗎,上面值檢測(cè)了value ==null 則拋出NPE 但是沒(méi)有說(shuō)key 啊,因?yàn)槿绻鹝ey 是null 的話,key.hashCode()則會(huì)拋出異常,根本不需要判斷,但是value 就不會(huì)拋出來(lái)

        但是需要注意的實(shí)HashMap 對(duì)null 值雖然支持,但是可以從hash值的計(jì)算方法中看出,的鍵值對(duì),value 會(huì)覆蓋的。

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

        升級(jí)HashTable 使其支持null 做value

        大部分代碼都是直接copy 的HashTable,只去掉了value 的空值檢測(cè)

        public?class?BuerHashTable<K,?V>?extends?Hashtable<K,?V>?{
        ????????//?.....?省略了部分代碼,直接copy?HashTable?的即可,主要是BuerHashTable.Entry?的定義和構(gòu)造方法
        ????public?synchronized?V?put(K?key,?V?value)?{

        ????????//?Makes?sure?the?key?is?not?already?in?the?hashtable.
        ????????Entry?tab[]?=?table;
        ????????int?hash?=?key.hashCode();
        ????????int?index?=?(hash?&?0x7FFFFFFF)?%?tab.length;
        ????????@SuppressWarnings("unchecked")
        ????????Entry?entry?=?(Entry)tab[index];
        ????????for(;?entry?!=?null?;?entry?=?entry.next)?{
        ????????????if?((entry.hash?==?hash)?&&?entry.key.equals(key))?{
        ????????????????V?old?=?entry.value;
        ????????????????entry.value?=?value;
        ????????????????return?old;
        ????????????}
        ????????}

        ????????addEntry(hash,?key,?value,?index);
        ????????return?null;
        ????}
        ????private?void?addEntry(int?hash,?K?key,?V?value,?int?index)?{
        ????????modCount++;

        ????????BuerHashTable.Entry?tab[]?=?table;
        ????????if?(count?>=?threshold)?{
        ????????????//?Rehash?the?table?if?the?threshold?is?exceeded
        ????????????rehash();

        ????????????tab?=?table;
        ????????????hash?=?key.hashCode();
        ????????????index?=?(hash?&?0x7FFFFFFF)?%?tab.length;
        ????????}

        ????????//?Creates?the?new?entry.
        ????????@SuppressWarnings("unchecked")
        ????????BuerHashTable.Entry?e?=?(BuerHashTable.Entry)?tab[index];
        ????????tab[index]?=?new?BuerHashTable.Entry<>(hash,?key,?value,?e);
        ????????count++;
        ????}
        }

        接下來(lái),就可以將null 值作為value 存入BuerHashTable 了

        BuerHashTable?buerHashTable?=?new?BuerHashTable<>();
        buerHashTable.put("a",?null);

        對(duì)比 HashTable 的繼承關(guān)系

        image-20201129184257104

        Dictionary

        這個(gè)類是HashTable特有繼承的,HashMap 是沒(méi)有繼承的,但是這個(gè)抽象類其實(shí)是沒(méi)有多大意義的,因?yàn)樗姆椒ǘ荚贛ap接口中有,其實(shí)這個(gè)就是個(gè)歷史問(wèn)題了,因?yàn)镸ap接口是在Java1.2 中才加進(jìn)去的,而Dictionary抽象類在Java1.0中就存在了

        public?abstract
        class?Dictionary<K,V>?{
        ????public?Dictionary()?{
        ????}
        ????abstract?public?int?size();
        ????abstract?public?boolean?isEmpty();
        ????abstract?public?Enumeration?keys();
        ????abstract?public?Enumeration?elements();
        ????abstract?public?V?get(Object?key);
        ????/**
        ?????*?@exception??NullPointerException??if?the?key?or
        ?????*/

        ????abstract?public?V?put(K?key,?V?value);
        ????abstract?public?V?remove(Object?key);
        }

        這個(gè)地方的NullPointerException 對(duì)應(yīng)的就是HashTable 中put 方法中的null 值檢測(cè)

        最后一點(diǎn)就是Dictionary 抽象類上的注釋,新的實(shí)現(xiàn)應(yīng)該實(shí)現(xiàn)Map 接口而不是該抽象類

        NOTE:?This?class?is?obsolete.??New?implementations?should?implement?the?Map?interface,?rather?than?extending?this?class

        其實(shí)HashMap更準(zhǔn)確地說(shuō)是繼承自AbstractMap類,而不是直接實(shí)現(xiàn)了Map 接口,所以要是Dictionary這個(gè)抽象類要是實(shí)現(xiàn)的實(shí)Map 接口,那HashMap和Hashtable 就在繼承關(guān)系上保持一致了

        Hashtable

        線程安全

        其實(shí)HashTable 沒(méi)有那么多要說(shuō)的,比較重要的一點(diǎn)就是線程安全,但是這個(gè)線程安全的實(shí)現(xiàn)方式就是所有的操作都加了synchronized關(guān)鍵字,哈哈!關(guān)于synchronized 我們后面會(huì)說(shuō)

        public?synchronized?int?size()?{}
        public?synchronized?boolean?isEmpty()?{}
        public?synchronized?boolean?contains(Object?value)?{}
        public?synchronized?boolean?containsKey(Object?key)?{}
        public?synchronized?V?get(Object?key)?{}
        public?synchronized?V?put(K?key,?V?value)?{}
        public?synchronized?V?remove(Object?key)?{}

        而HashMap 是線程不安全的

        contains方法

        HashMap中沒(méi)有Hashtable中的contains方法,只有containsValue和containsKey,因?yàn)閏ontains方法容易讓人引起誤解。

        Hashtable則保留了contains,containsValue和containsKey三個(gè)方法,其中contains和containsValue功能相同。

        debug 源碼 put 方法

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

        ????//?Makes?sure?the?key?is?not?already?in?the?hashtable.
        ????//?這里的英文注釋很有意思啊,就是告訴你確保key?不存在,存在咋地,覆蓋又咋地
        ????Entry?tab[]?=?table;
        ????//?哈希值的計(jì)算不同,HashTable直接使用對(duì)象的hashCode。而HashMap重新計(jì)算hash值(高16位異或低16位)
        ????int?hash?=?key.hashCode();
        ????//?計(jì)算下標(biāo) HashMap 是計(jì)算key的hash再與tab.length-1進(jìn)行與運(yùn)算;
        ????//?HashTable則是key的hash值與0x7FFFFFFF進(jìn)行與運(yùn)算,然后再對(duì)tab.length取模
        ????//?先hash&0x7FFFFFFF后,再對(duì)length取模,與0x7FFFFFFF的目的是為了將負(fù)的hash值轉(zhuǎn)化為正值,因?yàn)閔ash值有可能為負(fù)數(shù),而&0x7FFFFFFF后,只有符號(hào)外改變,而后面的位都不變
        ????int?index?=?(hash?&?0x7FFFFFFF)?%?tab.length;
        ????@SuppressWarnings("unchecked")
        ????//?確定?index?位置上的鏈表頭,這里主要是遍歷鏈表找到key?值相等的節(jié)點(diǎn),然后返回old?value,這樣的話就不用添加新值
        ????//?也就是不用調(diào)用addEntry?方法
        ????Entry?entry?=?(Entry)tab[index];
        ????//?存在key?
        ????for(;?entry?!=?null?;?entry?=?entry.next)?{
        ????????if?((entry.hash?==?hash)?&&?entry.key.equals(key))?{
        ????????????V?old?=?entry.value;
        ????????????entry.value?=?value;
        ????????????return?old;
        ????????}
        ????}
        ????//?鏈表中不存在,則添加新值
        ????addEntry(hash,?key,?value,?index);
        ????//?返回null?
        ????return?null;
        }
        private?void?addEntry(int?hash,?K?key,?V?value,?int?index)?{
        ????modCount++;
        ????Entry?tab[]?=?table;
        ????//?判斷是否要擴(kuò)容
        ????if?(count?>=?threshold)?{
        ????????//?Rehash?the?table?if?the?threshold?is?exceeded
        ????????rehash();
        ????????tab?=?table;
        ????????hash?=?key.hashCode();
        ????????index?=?(hash?&?0x7FFFFFFF)?%?tab.length;
        ????}
        ????//?Creates?the?new?entry.
        ????@SuppressWarnings("unchecked")
        ????Entry?e?=?(Entry)?tab[index];
        ????//?e?也就是??tab[index]?是這個(gè)鏈表的頭結(jié)點(diǎn),?tab[index]?=?new?Entry<>(hash,?key,?value,?e);?也就是將元素添加到鏈表的頭部,e?做為new?Entry<>(hash,?key,?value,?e)的next?節(jié)點(diǎn)
        ????tab[index]?=?new?Entry<>(hash,?key,?value,?e);
        ????count++;
        }

        這里我們對(duì)比一下HashMap 的添加方法,很明顯別人都是添加的鏈表尾部的,因?yàn)镠ashTable 是線程安全的,在這個(gè)前提下,使用頭查法性能更好,否則還有遍歷到鏈表的尾部插入

        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;
        }

        最后我們?cè)倏匆幌聰U(kuò)容的方法

        @SuppressWarnings("unchecked")
        protected?void?rehash()?{
        ????int?oldCapacity?=?table.length;
        ????Entry[]?oldMap?=?table;

        ????//?overflow-conscious?code?
        ????//?擴(kuò)容成2倍+1
        ????int?newCapacity?=?(oldCapacity?<1)?+?1;
        ????//?這里判斷是否超出了容量限制
        ????if?(newCapacity?-?MAX_ARRAY_SIZE?>?0)?{
        ????????if?(oldCapacity?==?MAX_ARRAY_SIZE)
        ????????????//?Keep?running?with?MAX_ARRAY_SIZE?buckets
        ????????????return;
        ????????//?最大容量?MAX_ARRAY_SIZE????
        ????????newCapacity?=?MAX_ARRAY_SIZE;
        ????}
        ????//?創(chuàng)建新的數(shù)組
        ????Entry[]?newMap?=?new?Entry[newCapacity];
        ????modCount++;
        ????//?更新?threshold
        ????threshold?=?(int)Math.min(newCapacity?*?loadFactor,?MAX_ARRAY_SIZE?+?1);
        ????table?=?newMap;
        ????//?數(shù)據(jù)遷移,遍歷數(shù)組
        ????for?(int?i?=?oldCapacity?;?i--?>?0?;)?{
        ????????????//?for?循環(huán)的方式遍歷鏈表
        ????????for?(Entry?old?=?(Entry)oldMap[i]?;?old?!=?null?;?)?{
        ????????????Entry?e?=?old;
        ????????????old?=?old.next;
        ????????????int?index?=?(e.hash?&?0x7FFFFFFF)?%?newCapacity;
        ????????????e.next?=?(Entry)newMap[index];
        ????????????newMap[index]?=?e;
        ????????}
        ????}
        }

        總結(jié)

        1. 需要注意的是Hashtable的默認(rèn)初始容量大小是11,而HashMap 是16,但是他們的加載因子都是0.75f

        2. HashTable的初始容量可以使任何非負(fù)整數(shù),但是HashMap會(huì)采用第一個(gè)大于該數(shù)值的2的冪作為初始化容量(0 1 除外,都是 1)

        3. HashTable的線程安全是完全借助synchronized 的加持

        4. HashTable 的元素是頭插法,也就是插入到鏈表的頭部,因?yàn)镠ashTable 是線程安全的,在這個(gè)前提下,使用頭查法性能更好,否則還有遍歷到鏈表的尾部插入

        5. HashTable 是沒(méi)有紅黑樹(shù)支持的,就是不論鏈表的長(zhǎng)度有多長(zhǎng),都不會(huì)轉(zhuǎn)化成紅黑樹(shù)

        6. 哈希值的計(jì)算不同,HashTable直接使用對(duì)象的hashCode。而HashMap重新計(jì)算hash值(高16位異或低16位),并且HashMap 支持key 為null 就是在這里的

        7. Hashtable擴(kuò)容時(shí),將容量變?yōu)樵瓉?lái)的2倍加1,而HashMap擴(kuò)容時(shí),將容量變?yōu)樵瓉?lái)的2倍。

        你覺(jué)得HashTable 還有什么可以改進(jìn)的地方嗎,歡迎討論

        和上一節(jié)一樣這里我依然給出這個(gè)思考題,雖然我們的說(shuō)法可能不對(duì),可能我們永遠(yuǎn)也站不到源代碼作者當(dāng)年的高度,但是我們依然積極思考,大膽討論

        雖然java 源代碼的山很高,如果你想跨越,至少你得有登山的勇氣,這里我給出自己的一點(diǎn)點(diǎn)愚見(jiàn),希望各位不吝指教

        int?hash?=?key.hashCode();
        addEntry(hash,?key,?value,?index);
        private?void?addEntry(int?hash,?K?key,?V?value,?int?index)?{
        ????????//?記錄修改,快速失敗
        ????modCount++;
        ????Entry?tab[]?=?table;
        ????//?count?實(shí)際存儲(chǔ)的key-value?數(shù)目,在HashMap?中用size?表示
        ????if?(count?>=?threshold)?{
        ????????//?Rehash?the?table?if?the?threshold?is?exceeded
        ????????rehash();
        ????????tab?=?table;
        ????????//?咋地,數(shù)組擴(kuò)容之后key?的hash值會(huì)變嗎,你還有重新計(jì)算一下
        ????????hash?=?key.hashCode();
        ????????index?=?(hash?&?0x7FFFFFFF)?%?tab.length;
        ????}
        ????//?Creates?the?new?entry.
        ????@SuppressWarnings("unchecked")
        ????Entry?e?=?(Entry)?tab[index];
        ????tab[index]?=?new?Entry<>(hash,?key,?value,?e);
        ????count++;
        }

        當(dāng)然這只是小問(wèn)題,但是也有很多其他小問(wèn)題,例如求index 時(shí)候的計(jì)算方式是直接取模,而不是用與運(yùn)算,它最大的問(wèn)題在設(shè)計(jì)上,例如hash值的計(jì)算方式就沒(méi)有HashMap 設(shè)計(jì)的好,還有就是沒(méi)有紅黑樹(shù)的支持,還有就是線程安全的實(shí)現(xiàn)方式也不高效,所以我們說(shuō)它好像是遺留類,HashTable 在Java1.0 時(shí)代就存在了,而HashMap才是Java1.2才有的

        瀏覽 33
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            4438国产精品一区二区 | 韩日大奶子姑娘性交 | 国产婷婷色一区二区三 | 在线观看小黄片 | 五月天开心激情网_亚欧乱色国产精品 | 啊~嗯短裙直接进去秘书 | 夜夜躁爽日日躁狠 | 操老妈小说 | 久久99热狠狠色一区二区 | 亚洲aaaaaaaaa |