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

          聽(tīng)說(shuō)同學(xué)你搞不懂Java的LinkedHashMap,可笑

          共 7562字,需瀏覽 16分鐘

           ·

          2020-08-12 04:30

          同學(xué)們好啊,還記得 HashMap 那篇嗎?我自己感覺(jué)寫(xiě)得非常棒啊,既通俗易懂,又深入源碼,真的是分析得透透徹徹、清清楚楚、明明白白的。(一不小心又上仨成語(yǔ)?)HashMap 哪哪都好,真的,只要你想用鍵值對(duì),第一時(shí)間就應(yīng)該想到它。

          但俗話(huà)說(shuō)了,“金無(wú)足赤人無(wú)完人”,HashMap 也不例外。有一種需求它就滿(mǎn)足不了,假如我們需要一個(gè)按照插入順序來(lái)排列的鍵值對(duì)集合,那 HashMap 就無(wú)能為力了。因?yàn)闉榱颂岣卟檎倚?,HashMap 在插入的時(shí)候?qū)︽I做了一次哈希算法,這就導(dǎo)致插入的元素是無(wú)序的。

          對(duì)這一點(diǎn)還不太明白的同學(xué),可以再回到 HashMap 那一篇,看看我對(duì) put() 方法的講解。

          final?V?putVal(int?hash,?K?key,?V?value,?boolean?onlyIfAbsent,
          ???????????????boolean?evict)
          ?
          {
          ????HashMap.Node[]?tab;?HashMap.Node?p;?int?n,?i;
          ????//?①、數(shù)組?table?為?null?時(shí),調(diào)用?resize?方法創(chuàng)建默認(rèn)大小的數(shù)組
          ????if?((tab?=?table)?==?null?||?(n?=?tab.length)?==?0)
          ????????n?=?(tab?=?resize()).length;
          ????//?②、計(jì)算下標(biāo),如果該位置上沒(méi)有值,則填充
          ????if?((p?=?tab[i?=?(n?-?1)?&?hash])?==?null)
          ????????tab[i]?=?newNode(hash,?key,?value,?null);
          }

          這個(gè)公式 i = (n - 1) & hash 計(jì)算后的值并不是按照 0、1、2、3、4、5 這樣有序的下標(biāo)將鍵值對(duì)插入到數(shù)組當(dāng)中的,而是有一定的隨機(jī)性。

          那 LinkedHashMap 就是為這個(gè)需求應(yīng)運(yùn)而生的。LinkedHashMap 繼承了 HashMap,所以 HashMap 有的關(guān)于鍵值對(duì)的功能,它也有了。

          public?class?LinkedHashMap<K,V>
          ????extends?HashMap<K,V>
          ????implements?Map<K,V>
          {}

          此外,LinkedHashMap 內(nèi)部又追加了雙向鏈表,來(lái)維護(hù)元素的插入順序。注意下面代碼中的 before 和 after,它倆就是用來(lái)維護(hù)當(dāng)前元素的前一個(gè)元素和后一個(gè)元素的順序的。

          static?class?Entry<K,V>?extends?HashMap.Node<K,V>?{
          ????LinkedHashMap.Entry?before,?after;
          ????Entry(int?hash,?K?key,?V?value,?HashMap.Node?next)?{
          ????????super(hash,?key,?value,?next);
          ????}
          }

          關(guān)于雙向鏈表,同學(xué)們可以回頭看一遍我寫(xiě)的 LinkedList 那篇文章,會(huì)對(duì)理解本篇的 LinkedHashMap 有很大的幫助。

          在繼續(xù)下面的內(nèi)容之前,我先貼一張圖片,給大家增添一點(diǎn)樂(lè)趣——看我這心操的。UUID 那篇文章的標(biāo)題里用了“可笑”和“你”,結(jié)果就看到了下面這么樂(lè)呵的留言。

          (到底是知道還是不知道,我搞不清楚了。。。)那 LinkedHashMap 這篇也用了“你”和“可笑”,不知道到時(shí)候會(huì)不會(huì)有人繼續(xù)對(duì)號(hào)入座啊,想想就覺(jué)得特別歡樂(lè)。

          01、插入順序

          HashMap 那篇文章里,我有講解到一點(diǎn),不知道同學(xué)們記不記得,就是 null 會(huì)插入到 HashMap 的第一位。

          Map?hashMap?=?new?HashMap<>();
          hashMap.put("沉",?"沉默王二");
          hashMap.put("默",?"沉默王二");
          hashMap.put("王",?"沉默王二");
          hashMap.put("二",?"沉默王二");
          hashMap.put(null,?null);

          for?(String?key?:?hashMap.keySet())?{
          ????System.out.println(key?+?"?:?"?+?hashMap.get(key));
          }

          輸出的結(jié)果是:

          null?:?null
          默?:?沉默王二
          沉?:?沉默王二
          王?:?沉默王二
          二?:?沉默王二

          雖然 null 最后一位 put 進(jìn)去的,但在遍歷輸出的時(shí)候,跑到了第一位。

          那再來(lái)對(duì)比看一下 LinkedHashMap。

          Map?linkedHashMap?=?new?LinkedHashMap<>();
          linkedHashMap.put("沉",?"沉默王二");
          linkedHashMap.put("默",?"沉默王二");
          linkedHashMap.put("王",?"沉默王二");
          linkedHashMap.put("二",?"沉默王二");
          linkedHashMap.put(null,?null);

          for?(String?key?:?linkedHashMap.keySet())?{
          ????System.out.println(key?+?"?:?"?+?linkedHashMap.get(key));
          }

          輸出結(jié)果是:

          沉?:?沉默王二
          默?:?沉默王二
          王?:?沉默王二
          二?:?沉默王二
          null?:?null

          null 在最后一位插入,在最后一位輸出。

          輸出結(jié)果可以再次證明,HashMap 是無(wú)序的,LinkedHashMap 是可以維持插入順序的。

          那 LinkedHashMap 是如何做到這一點(diǎn)呢?我相信同學(xué)們和我一樣,非常希望知道原因。

          要想搞清楚,就需要深入研究一下 LinkedHashMap 的源碼。LinkedHashMap 并未重寫(xiě) HashMap 的 put() 方法,而是重寫(xiě)了 put() 方法需要調(diào)用的內(nèi)部方法 newNode()。

          HashMap.Node?newNode(int?hash,?K?key,?V?value,?HashMap.Node?e)?{
          ????LinkedHashMap.Entry?p?=
          ????????????new?LinkedHashMap.Entry<>(hash,?key,?value,?e);
          ????linkNodeLast(p);
          ????return?p;
          }

          前面說(shuō)了,LinkedHashMap.Entry 繼承了 HashMap.Node,并且追加了兩個(gè)字段 before 和 after。

          那,緊接著來(lái)看看 linkNodeLast() 方法:

          private?void?linkNodeLast(LinkedHashMap.Entry?p)?{
          ????LinkedHashMap.Entry?last?=?tail;
          ????tail?=?p;
          ????if?(last?==?null)
          ????????head?=?p;
          ????else?{
          ????????p.before?=?last;
          ????????last.after?=?p;
          ????}
          }

          看到了吧,LinkedHashMap 在添加第一個(gè)元素的時(shí)候,會(huì)把 head 賦值為第一個(gè)元素,等到第二個(gè)元素添加進(jìn)來(lái)的時(shí)候,會(huì)把第二個(gè)元素的 before 賦值為第一個(gè)元素,第一個(gè)元素的 afer 賦值為第二個(gè)元素。

          這就保證了鍵值對(duì)是按照插入順序排列的,明白了吧?

          注:我用到的 JDK 版本為 14

          02、訪(fǎng)問(wèn)順序

          LinkedHashMap 不僅能夠維持插入順序,還能夠維持訪(fǎng)問(wèn)順序。訪(fǎng)問(wèn)包括調(diào)用 get() 方法、remove() 方法和 put() 方法。

          要維護(hù)訪(fǎng)問(wèn)順序,需要我們?cè)诼暶?LinkedHashMap 的時(shí)候指定三個(gè)參數(shù)。

          LinkedHashMap?map?=?new?LinkedHashMap<>(16,?.75f,?true);

          第一個(gè)參數(shù)和第二個(gè)參數(shù),看過(guò) HashMap 的同學(xué)們應(yīng)該很熟悉了,指的是初始容量和負(fù)載因子。

          第三個(gè)參數(shù)如果為 true 的話(huà),就表示 LinkedHashMap 要維護(hù)訪(fǎng)問(wèn)順序;否則,維護(hù)插入順序。默認(rèn)是 false。

          Map?linkedHashMap?=?new?LinkedHashMap<>(16,?.75f,?true);
          linkedHashMap.put("沉",?"沉默王二");
          linkedHashMap.put("默",?"沉默王二");
          linkedHashMap.put("王",?"沉默王二");
          linkedHashMap.put("二",?"沉默王二");

          System.out.println(linkedHashMap);

          linkedHashMap.get("默");
          System.out.println(linkedHashMap);

          linkedHashMap.get("王");
          System.out.println(linkedHashMap);

          輸出的結(jié)果如下所示:

          {沉=沉默王二,?默=沉默王二,?王=沉默王二,?二=沉默王二}
          {沉=沉默王二,?王=沉默王二,?二=沉默王二,?默=沉默王二}
          {沉=沉默王二,?二=沉默王二,?默=沉默王二,?王=沉默王二}

          當(dāng)我們使用 get() 方法訪(fǎng)問(wèn)鍵位“默”的元素后,輸出結(jié)果中,默=沉默王二 在最后;當(dāng)我們?cè)L問(wèn)鍵位“王”的元素后,輸出結(jié)果中,王=沉默王二 在最后,默=沉默王二 在倒數(shù)第二位。

          也就是說(shuō),最不經(jīng)常訪(fǎng)問(wèn)的放在頭部,這就有意思了。有意思在哪呢?

          我們可以使用 LinkedHashMap 來(lái)實(shí)現(xiàn) LRU 緩存,LRU 是 Least Recently Used 的縮寫(xiě),即最近最少使用,是一種常用的頁(yè)面置換算法,選擇最近最久未使用的頁(yè)面予以淘汰。

          public?class?MyLinkedHashMap<K,?V>?extends?LinkedHashMap<K,?V>?{

          ????private?static?final?int?MAX_ENTRIES?=?5;

          ????public?MyLinkedHashMap(
          ????????????int?initialCapacity,?float?loadFactor,?boolean?accessOrder)
          ?
          {
          ????????super(initialCapacity,?loadFactor,?accessOrder);
          ????}

          ????@Override
          ????protected?boolean?removeEldestEntry(Map.Entry?eldest)?{
          ????????return?size()?>?MAX_ENTRIES;
          ????}

          }

          MyLinkedHashMap 是一個(gè)自定義類(lèi),它繼承了 LinkedHashMap,并且重寫(xiě)了 removeEldestEntry() 方法——使 Map 最多可容納 5 個(gè)元素,超出后就淘汰。

          我們來(lái)測(cè)試一下。

          MyLinkedHashMap?map?=?new?MyLinkedHashMap<>(16,0.75f,true);
          map.put("沉",?"沉默王二");
          map.put("默",?"沉默王二");
          map.put("王",?"沉默王二");
          map.put("二",?"沉默王二");
          map.put("一枚有趣的程序員",?"一枚有趣的程序員");

          System.out.println(map);

          map.put("一枚有顏值的程序員",?"一枚有顏值的程序員");
          System.out.println(map);

          map.put("一枚有才華的程序員","一枚有才華的程序員");
          System.out.println(map);

          輸出結(jié)果如下所示:

          {沉=沉默王二,?默=沉默王二,?王=沉默王二,?二=沉默王二,?一枚有趣的程序員=一枚有趣的程序員}
          {默=沉默王二,?王=沉默王二,?二=沉默王二,?一枚有趣的程序員=一枚有趣的程序員,?一枚有顏值的程序員=一枚有顏值的程序員}
          {王=沉默王二,?二=沉默王二,?一枚有趣的程序員=一枚有趣的程序員,?一枚有顏值的程序員=一枚有顏值的程序員,?一枚有才華的程序員=一枚有才華的程序員}

          沉=沉默王二默=沉默王二 依次被淘汰出局。

          假如在 put “一枚有才華的程序員”之前 get 了鍵位為“默”的元素:

          MyLinkedHashMap?map?=?new?MyLinkedHashMap<>(16,0.75f,true);
          map.put("沉",?"沉默王二");
          map.put("默",?"沉默王二");
          map.put("王",?"沉默王二");
          map.put("二",?"沉默王二");
          map.put("一枚有趣的程序員",?"一枚有趣的程序員");

          System.out.println(map);

          map.put("一枚有顏值的程序員",?"一枚有顏值的程序員");
          System.out.println(map);

          map.get("默");
          map.put("一枚有才華的程序員","一枚有才華的程序員");
          System.out.println(map);

          那輸出結(jié)果就變了,對(duì)吧?

          {沉=沉默王二,?默=沉默王二,?王=沉默王二,?二=沉默王二,?一枚有趣的程序員=一枚有趣的程序員}
          {默=沉默王二,?王=沉默王二,?二=沉默王二,?一枚有趣的程序員=一枚有趣的程序員,?一枚有顏值的程序員=一枚有顏值的程序員}
          {二=沉默王二,?一枚有趣的程序員=一枚有趣的程序員,?一枚有顏值的程序員=一枚有顏值的程序員,?默=沉默王二,?一枚有才華的程序員=一枚有才華的程序員}

          沉=沉默王二王=沉默王二 被淘汰出局了。

          那 LinkedHashMap 是如何來(lái)維持訪(fǎng)問(wèn)順序呢?同學(xué)們感興趣的話(huà),可以研究一下下面這三個(gè)方法。

          void?afterNodeAccess(Node?p)?{?}
          void?afterNodeInsertion(boolean?evict)?{?}
          void?afterNodeRemoval(Node?p)?{?}

          afterNodeAccess() 會(huì)在調(diào)用 get() 方法的時(shí)候被調(diào)用,afterNodeInsertion() 會(huì)在調(diào)用 put() 方法的時(shí)候被調(diào)用,afterNodeRemoval() 會(huì)在調(diào)用 remove() 方法的時(shí)候被調(diào)用。

          我來(lái)以 afterNodeAccess() 為例來(lái)講解一下。

          void?afterNodeAccess(HashMap.Node?e)?{?//?move?node?to?last
          ????LinkedHashMap.Entry?last;
          ????if?(accessOrder?&&?(last?=?tail)?!=?e)?{
          ????????LinkedHashMap.Entry?p?=
          ????????????????(LinkedHashMap.Entry)e,?b?=?p.before,?a?=?p.after;
          ????????p.after?=?null;
          ????????if?(b?==?null)
          ????????????head?=?a;
          ????????else
          ????????????b.after?=?a;
          ????????if?(a?!=?null)
          ????????????a.before?=?b;
          ????????else
          ????????????last?=?b;
          ????????if?(last?==?null)
          ????????????head?=?p;
          ????????else?{
          ????????????p.before?=?last;
          ????????????last.after?=?p;
          ????????}
          ????????tail?=?p;
          ????????++modCount;
          ????}
          }

          哪個(gè)元素被 get 就把哪個(gè)元素放在最后。了解了吧?

          那同學(xué)們可能還想知道,為什么 LinkedHashMap 能實(shí)現(xiàn) ?LRU 緩存,把最不經(jīng)常訪(fǎng)問(wèn)的那個(gè)元素淘汰?

          在插入元素的時(shí)候,需要調(diào)用 put() 方法,該方法最后會(huì)調(diào)用 afterNodeInsertion() 方法,這個(gè)方法被 LinkedHashMap 重寫(xiě)了。

          void?afterNodeInsertion(boolean?evict)?{?//?possibly?remove?eldest
          ????LinkedHashMap.Entry?first;
          ????if?(evict?&&?(first?=?head)?!=?null?&&?removeEldestEntry(first))?{
          ????????K?key?=?first.key;
          ????????removeNode(hash(key),?key,?null,?false,?true);
          ????}
          }

          removeEldestEntry() 方法會(huì)判斷第一個(gè)元素是否超出了可容納的最大范圍,如果超出,那就會(huì)調(diào)用 removeNode() 方法對(duì)最不經(jīng)常訪(fǎng)問(wèn)的那個(gè)元素進(jìn)行刪除。

          03、最后

          由于 LinkedHashMap 要維護(hù)雙向鏈表,所以 LinkedHashMap 在插入、刪除操作的時(shí)候,花費(fèi)的時(shí)間要比 HashMap 多一些。

          這也是沒(méi)辦法的事,對(duì)吧,欲戴皇冠必承其重嘛。既然想要維護(hù)元素的順序,總要付出點(diǎn)代價(jià)才行。

          那這篇文章就到此戛然而止了,同學(xué)們要覺(jué)得意猶未盡,請(qǐng)肆無(wú)忌憚地留言告訴我哦。(一不小心又在文末甩仨成語(yǔ),有點(diǎn)文化底蘊(yùn),對(duì)吧?)


          ------------------

          公眾號(hào):沉默王二(ID:cmower)
          CSDN:沉默王二
          這是一個(gè)有顏值卻靠才華吃飯的程序員,你知道,他的文章風(fēng)趣幽默,讀起來(lái)就好像花錢(qián)一樣爽快。

          長(zhǎng)按下圖二維碼關(guān)注,你將感受到一個(gè)有趣的靈魂,且每篇文章都有干貨。


          ------------------



          原創(chuàng)不易,莫要白票,如果覺(jué)得有點(diǎn)用的話(huà),請(qǐng)毫不留情地素質(zhì)三連吧,分享、點(diǎn)贊、在看,我不挑,因?yàn)檫@將是我寫(xiě)作更多優(yōu)質(zhì)文章的最強(qiáng)動(dòng)力。

          PS:公布一下周日的中獎(jiǎng)名單,有小伙伴表示,人生第一次中獎(jiǎng)啊,激動(dòng)啊,二哥,你咋這么棒!哎呀,那股激動(dòng)的勁啊,放心,還會(huì)有的,只要你們想白嫖,二哥就給機(jī)會(huì)。

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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  中文字幕日亚州 | 在线高清无码观看 | 操骚逼视频免费试看 | 青娱乐91视频 | 亚洲欧美性交网站在线观看 |