乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      HashMap深入分析

       燮羽 2010-12-14

      java.util.HashMap是很常見(jiàn)的類,前段時(shí)間公司系統(tǒng)由于對(duì)HashMap使用不當(dāng),導(dǎo)致cpu百分之百,在并發(fā)環(huán)境下使用HashMap而沒(méi)有做同步,可能會(huì)引起死循環(huán),關(guān)于這一點(diǎn),sun的官方網(wǎng)站上已有闡述,這并非是bug。


      HashMap的數(shù)據(jù)結(jié)構(gòu)

               HashMap主要是用數(shù)組來(lái)存儲(chǔ)數(shù)據(jù)的,我們都知道它會(huì)對(duì)key進(jìn)行哈希運(yùn)算,哈系運(yùn)算會(huì)有重復(fù)的哈希值,對(duì)于哈希值的沖突,HashMap采用鏈表來(lái)解決的。在HashMap里有這樣的一句屬性聲明:

      transient Entry[] table;

      Entry就是HashMap存儲(chǔ)的數(shù)據(jù),它擁有的屬性如下

      final K key;
       V value;
       final int hash;
       Entry<K,V> next;

      看到next了嗎?next就是為了哈希沖突而存在的。比如通過(guò)哈希運(yùn)算,一個(gè)新元素應(yīng)該在數(shù)組的第10個(gè)位置,但是第10個(gè)位置已經(jīng)有Entry,那么好吧,將新加的元素也放到第10個(gè)位置,將第10個(gè)位置的原有Entry賦值給當(dāng)前新加的Entry的next屬性。數(shù)組存儲(chǔ)的鏈表,鏈表是為了解決哈希沖突的,這一點(diǎn)要注意。


      幾個(gè)關(guān)鍵的屬性

      存儲(chǔ)數(shù)據(jù)的數(shù)組

      transient Entry[] table; 這個(gè)上面已經(jīng)講到了
      默認(rèn)容量

      static final int DEFAULT_INITIAL_CAPACITY = 16;

      最大容量

       static final int MAXIMUM_CAPACITY = 1 << 30;

      默認(rèn)加載因子,加載因子是一個(gè)比例,當(dāng)HashMap的數(shù)據(jù)大小>=容量*加載因子時(shí),HashMap會(huì)將容量擴(kuò)容

      static final float DEFAULT_LOAD_FACTOR = 0.75f;

      當(dāng)實(shí)際數(shù)據(jù)大小超過(guò)threshold時(shí),HashMap會(huì)將容量擴(kuò)容,threshold=容量*加載因子

      int threshold;

      加載因子

      final float loadFactor;


      HashMap的初始過(guò)程

      構(gòu)造函數(shù)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);

              // Find a power of 2 >= initialCapacity
              int capacity = 1;
              while (capacity < initialCapacity) 
                  capacity <<= 1;
          
              this.loadFactor = loadFactor;
              threshold = (int)(capacity * loadFactor);
              table = new Entry[capacity];
              init();
          }

      重點(diǎn)注意這里 

      while (capacity < initialCapacity) 
                  capacity <<= 1;

      capacity才是初始容量,而不是initialCapacity,這個(gè)要特別注意,如果執(zhí)行new HashMap(9,0.75);那么HashMap的初始容量是16,而不是9,想想為什么吧。


      構(gòu)造函數(shù)2

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


      構(gòu)造函數(shù)3,全部都是默認(rèn)值

         public HashMap() {
              this.loadFactor = DEFAULT_LOAD_FACTOR;
              threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
              table = new Entry[DEFAULT_INITIAL_CAPACITY];
              init();
          }


      構(gòu)造函數(shù)4

        public HashMap(Map<? extends K, ? extends V> m) {
              this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                            DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
              putAllForCreate(m);
          }



      如何哈希

              HashMap并不是直接將對(duì)象的hashcode作為哈希值的,而是要把key的hashcode作一些運(yùn)算以得到最終的哈希值,并且得到的哈希值也不是在數(shù)組中的位置哦,無(wú)論是get還是put還是別的方法,計(jì)算哈希值都是這一句:

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

      hash函數(shù)如下:

        static int hash(int h) {
          return useNewHash ? newHash(h) : oldHash(h);
          }

      useNewHash聲明如下:

         private static final boolean useNewHash;
          static { useNewHash = false; }

      這說(shuō)明useNewHash其實(shí)一直為false且不可改變的,hash函數(shù)里對(duì)useNewHash的判斷真是多余的。

          private static int oldHash(int h) {
              h += ~(h << 9);
              h ^=  (h >>> 14);
              h +=  (h << 4);
              h ^=  (h >>> 10);
              return h;
          }

          private static int newHash(int h) {
              // This function ensures that hashCodes that differ only by
              // constant multiples at each bit position have a bounded
              // number of collisions (approximately 8 at default load factor).
              h ^= (h >>> 20) ^ (h >>> 12);
              return h ^ (h >>> 7) ^ (h >>> 4);
          }

      其實(shí)HashMap的哈希函數(shù)會(huì)一直都是oldHash。

       

       

      如果確定數(shù)據(jù)的位置

      看下面兩行

            int hash = hash(k.hashCode());
            int i = indexFor(hash, table.length);

      第一行,上面講過(guò)了,是得到哈希值,第二行,則是根據(jù)哈希指計(jì)算元素在數(shù)組中的位置了,位置的計(jì)算是將哈希值和數(shù)組長(zhǎng)度按位與運(yùn)算。

         static int indexFor(int h, int length) {
              return h & (length-1);
          }

       

       

      put方法到底作了什么?

         public V put(K key, V value) {
          if (key == null)
              return putForNullKey(value);
              int hash = hash(key.hashCode());
              int i = indexFor(hash, table.length);
              for (Entry<K,V> e = table[i]; e != null; e = e.next) {
                  Object k;
                  if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                      V oldValue = e.value;
                      e.value = value;
                      e.recordAccess(this);
                      return oldValue;
                  }
              }

              modCount++;
              addEntry(hash, key, value, i);
              return null;
          }

      如果key為NULL,則是單獨(dú)處理的,看看putForNullKey方法:

          private V putForNullKey(V value) {
              int hash = hash(NULL_KEY.hashCode());
              int i = indexFor(hash, table.length);

              for (Entry<K,V> e = table[i]; e != null; e = e.next) {
                  if (e.key == NULL_KEY) {
                      V oldValue = e.value;
                      e.value = value;
                      e.recordAccess(this);
                      return oldValue;
                  }
              }

              modCount++;
              addEntry(hash, (K) NULL_KEY, value, i);
              return null;
          }

      NULL_KEY的聲明:static final Object NULL_KEY = new Object();

      這一段代碼是處理哈希沖突的,就是說(shuō),在數(shù)組某個(gè)位置的對(duì)象可能并不是唯一的,它是一個(gè)鏈表結(jié)構(gòu),根據(jù)哈希值找到鏈表后,還要對(duì)鏈表遍歷,找出key相等的對(duì)象,替換它,并且返回舊的值。

            for (Entry<K,V> e = table[i]; e != null; e = e.next) {
                  if (e.key == NULL_KEY) {
                      V oldValue = e.value;
                      e.value = value;
                      e.recordAccess(this);
                      return oldValue;
                  }
              }


      如果遍歷完了該位置的鏈表都沒(méi)有找到有key相等的,那么將當(dāng)前對(duì)象增加到鏈表里面去

        modCount++;
        addEntry(hash, (K) NULL_KEY, value, i);
        return null;


      且看看addEntry方法

          void addEntry(int hash, K key, V value, int bucketIndex) {
          Entry<K,V> e = table[bucketIndex];
              table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
              if (size++ >= threshold)
                  resize(2 * table.length);
          }

      table[bucketIndex] = new Entry<K,V>(hash, key, value, e);新建一個(gè)Entry對(duì)象,并放在當(dāng)前位置的Entry鏈表的頭部,看看下面的 Entry構(gòu)造函數(shù)就知道了,注意紅色部分。

           Entry(int h, K k, V v, Entry<K,V> n) {
                  value = v;
                  next = n; 
                  key = k;
                  hash = h;
              }

       

       

      如何擴(kuò)容? 

              當(dāng)put一個(gè)元素時(shí),如果達(dá)到了容量限制,HashMap就會(huì)擴(kuò)容,新的容量永遠(yuǎn)是原來(lái)的2倍。

      上面的put方法里有這樣的一段:

      if (size++ >= threshold)
                  resize(2 * table.length);

      這是擴(kuò)容判斷,要注意,并不是數(shù)據(jù)尺寸達(dá)到HashMap的最大容量時(shí)才擴(kuò)容,而是達(dá)到 threshold指定的值時(shí)就開始擴(kuò)容,threshold=最大容量*加載因子。 看看resize方法

          void resize(int newCapacity) {
              Entry[] oldTable = table;
              int oldCapacity = oldTable.length;
              if (oldCapacity == MAXIMUM_CAPACITY) {
                  threshold = Integer.MAX_VALUE;
                  return;
              }

              Entry[] newTable = new Entry[newCapacity];
              transfer(newTable); 
              table = newTable;
              threshold = (int)(newCapacity * loadFactor);
          }

      重點(diǎn)看看紅色部分的 transfer方法

          void transfer(Entry[] newTable) {
              Entry[] src = table;
              int newCapacity = newTable.length;
              for (int j = 0; j < src.length; j++) {
                  Entry<K,V> e = src[j];
                  if (e != null) {
                      src[j] = null;
                      do {
                          Entry<K,V> next = e.next;
                          int i = indexFor(e.hash, newCapacity);  
                          e.next = newTable[i];
                          newTable[i] = e;
                          e = next;
                      } while (e != null);
                  }
              }
          }

      tranfer方法將所有的元素重新哈希,因?yàn)樾碌娜萘孔兇螅悦總€(gè)元素的哈希值和位置都是不一樣的。



      正確的使用HashMap

      1:不要在并發(fā)場(chǎng)景中使用HashMap

                 HashMap是線程不安全的,如果被多個(gè)線程共享的操作,將會(huì)引發(fā)不可預(yù)知的問(wèn)題,據(jù)sun的說(shuō)法,在擴(kuò)容時(shí),會(huì)引起鏈表的閉環(huán),在get元素時(shí),就會(huì)無(wú)限循環(huán),后果是cpu100%。

      看看get方法的紅色部分

       public V get(Object key) {
          if (key == null)
              return getForNullKey();
              int hash = hash(key.hashCode());
              for (Entry<K,V> e = table[indexFor(hash, table.length)]; 
                   e != null;
                   e = e.next) {
                  Object k;
                  if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                      return e.value;
              }
              return null;
          }
       


      2:如果數(shù)據(jù)大小是固定的,那么最好給HashMap設(shè)定一個(gè)合理的容量值

              根據(jù)上面的分析,HashMap的初始默認(rèn)容量是16,默認(rèn)加載因子是0.75,也就是說(shuō),如果采用HashMap的默認(rèn)構(gòu)造函數(shù),當(dāng)增加數(shù)據(jù)時(shí),數(shù)據(jù)實(shí)際容量超過(guò)10*0.75=12時(shí),HashMap就擴(kuò)容,擴(kuò)容帶來(lái)一系列的運(yùn)算,新建一個(gè)是原來(lái)容量2倍的數(shù)組,對(duì)原有元素全部重新哈希,如果你的數(shù)據(jù)有幾千幾萬(wàn)個(gè),而用默認(rèn)的HashMap構(gòu)造函數(shù),那結(jié)果是非常悲劇的,因?yàn)镠ashMap不斷擴(kuò)容,不斷哈希,在使用HashMap的場(chǎng)景里,不會(huì)是多個(gè)線程共享一個(gè)HashMap,除非對(duì)HashMap包裝并同步,由此產(chǎn)生的內(nèi)存開銷和cpu開銷在某些情況下可能是致命的。

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多