<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 9028字,需瀏覽 19分鐘

           ·

          2020-12-03 13:49

          概論

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

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

          對比HashMap 的初始容量

          默認11 的初始容量

          需要注意的是Hashtable的默認初始容量大小是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
          }

          任意指定非負的容量

          還有一點就是Hashtable的initialCapacity 也就是初始容量是是可以是你指定的任何非負整數(shù),也就是你給它設置個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 的初始容量就不那么聽話了,默認情況下,當我們設置HashMap的初始化容量時,實際上HashMap會采用第一個大于該數(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);
          }

          對比HashMap 的 對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)了嗎,上面值檢測了value ==null 則拋出NPE 但是沒有說key 啊,因為如果key 是null 的話,key.hashCode()則會拋出異常,根本不需要判斷,但是value 就不會拋出來

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

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

          升級HashTable 使其支持null 做value

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

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

          接下來,就可以將null 值作為value 存入BuerHashTable 了

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

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

          image-20201129184257104

          Dictionary

          這個類是HashTable特有繼承的,HashMap 是沒有繼承的,但是這個抽象類其實是沒有多大意義的,因為它的方法都在Map接口中有,其實這個就是個歷史問題了,因為Map接口是在Java1.2 中才加進去的,而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);
          }

          這個地方的NullPointerException 對應的就是HashTable 中put 方法中的null 值檢測

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

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

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

          Hashtable

          線程安全

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

          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中沒有Hashtable中的contains方法,只有containsValue和containsKey,因為contains方法容易讓人引起誤解。

          Hashtable則保留了contains,containsValue和containsKey三個方法,其中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;
          ????//?哈希值的計算不同,HashTable直接使用對象的hashCode。而HashMap重新計算hash值(高16位異或低16位)
          ????int?hash?=?key.hashCode();
          ????//?計算下標 HashMap 是計算key的hash再與tab.length-1進行與運算;
          ????//?HashTable則是key的hash值與0x7FFFFFFF進行與運算,然后再對tab.length取模
          ????//?先hash&0x7FFFFFFF后,再對length取模,與0x7FFFFFFF的目的是為了將負的hash值轉(zhuǎn)化為正值,因為hash值有可能為負數(shù),而&0x7FFFFFFF后,只有符號外改變,而后面的位都不變
          ????int?index?=?(hash?&?0x7FFFFFFF)?%?tab.length;
          ????@SuppressWarnings("unchecked")
          ????//?確定?index?位置上的鏈表頭,這里主要是遍歷鏈表找到key?值相等的節(jié)點,然后返回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;
          ????//?判斷是否要擴容
          ????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]?是這個鏈表的頭結(jié)點,?tab[index]?=?new?Entry<>(hash,?key,?value,?e);?也就是將元素添加到鏈表的頭部,e?做為new?Entry<>(hash,?key,?value,?e)的next?節(jié)點
          ????tab[index]?=?new?Entry<>(hash,?key,?value,?e);
          ????count++;
          }

          這里我們對比一下HashMap 的添加方法,很明顯別人都是添加的鏈表尾部的,因為HashTable 是線程安全的,在這個前提下,使用頭查法性能更好,否則還有遍歷到鏈表的尾部插入

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

          最后我們再看一下擴容的方法

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

          ????//?overflow-conscious?code?
          ????//?擴容成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的默認初始容量大小是11,而HashMap 是16,但是他們的加載因子都是0.75f

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

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

          4. HashTable 的元素是頭插法,也就是插入到鏈表的頭部,因為HashTable 是線程安全的,在這個前提下,使用頭查法性能更好,否則還有遍歷到鏈表的尾部插入

          5. HashTable 是沒有紅黑樹支持的,就是不論鏈表的長度有多長,都不會轉(zhuǎn)化成紅黑樹

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

          7. Hashtable擴容時,將容量變?yōu)樵瓉淼?倍加1,而HashMap擴容時,將容量變?yōu)樵瓉淼?倍。

          你覺得HashTable 還有什么可以改進的地方嗎,歡迎討論

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

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

          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?實際存儲的key-value?數(shù)目,在HashMap?中用size?表示
          ????if?(count?>=?threshold)?{
          ????????//?Rehash?the?table?if?the?threshold?is?exceeded
          ????????rehash();
          ????????tab?=?table;
          ????????//?咋地,數(shù)組擴容之后key?的hash值會變嗎,你還有重新計算一下
          ????????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++;
          }

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

          瀏覽 33
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产乱婬AⅤ片免费 | 日日操日日| 日韩成人午夜 | 人人舔人人操人人干 | 伊人大香蕉视频在线 |