隨筆-13  評(píng)論-22  文章-0  trackbacks-0


          HashSet and HashMap

          本文github地址

          總體介紹

          之所以把HashSetHashMap放在一起講解,是因?yàn)槎咴贘ava里有著相同的實(shí)現(xiàn),前者僅僅是對(duì)后者做了一層包裝,也就是說HashSet里面有一個(gè)HashMap(適配器模式)。因此本文將重點(diǎn)分析HashMap

          HashMap實(shí)現(xiàn)了Map接口,允許放入null元素,除該類未實(shí)現(xiàn)同步外,其余跟Hashtable大致相同,跟TreeMap不同,該容器不保證元素順序,根據(jù)需要該容器可能會(huì)對(duì)元素重新哈希,元素的順序也會(huì)被重新打散,因此不同時(shí)間迭代同一個(gè)HashMap的順序可能會(huì)不同。
          根據(jù)對(duì)沖突的處理方式不同,哈希表有兩種實(shí)現(xiàn)方式,一種開放地址方式(Open addressing),另一種是沖突鏈表方式(Separate chaining with linked lists)。Java HashMap采用的是沖突鏈表方式
          HashMap_base

          從上圖容易看出,如果選擇合適的哈希函數(shù),put()get()方法可以在常數(shù)時(shí)間內(nèi)完成。但在對(duì)HashMap進(jìn)行迭代時(shí),需要遍歷整個(gè)table以及后面跟的沖突鏈表。因此對(duì)于迭代比較頻繁的場(chǎng)景,不宜將HashMap的初始大小設(shè)的過大。

          有兩個(gè)參數(shù)可以影響HashMap的性能:初始容量(inital capacity)和負(fù)載系數(shù)(load factor)。初始容量指定了初始table的大小,負(fù)載系數(shù)用來指定自動(dòng)擴(kuò)容的臨界值。當(dāng)entry的數(shù)量超過capacity*load_factor時(shí),容器將自動(dòng)擴(kuò)容并重新哈希。對(duì)于插入元素較多的場(chǎng)景,將初始容量設(shè)大可以減少重新哈希的次數(shù)。

          方法剖析

          get()

          get(Object key)方法根據(jù)指定的key值返回對(duì)應(yīng)的value,該方法調(diào)用了getEntry(Object key)得到相應(yīng)的entry,然后返回entry.getValue()。因此getEntry()是算法的核心。
          算法思想是首先通過hash()函數(shù)得到對(duì)應(yīng)bucket的下標(biāo),然后依次遍歷沖突鏈表,通過key.equals(k)方法來判斷是否是要找的那個(gè)entry
          HashMap_getEntry
          上圖中hash(k)&(table.length-1)等價(jià)于hash(k)%table.length,原因是HashMap要求table.length必須是2的指數(shù),因此table.length-1就是二進(jìn)制低位全是1,跟hash(k)相與會(huì)將哈希值的高位全抹掉,剩下的就是余數(shù)了。

          //getEntry()方法
          final Entry<K,V> getEntry(Object key) {
              
              int hash = (key == null) ? 0 : hash(key);
              for (Entry<K,V> e = table[hash&(table.length-1)];//得到?jīng)_突鏈表
                   e != null; e = e.next) {//依次遍歷沖突鏈表中的每個(gè)entry
                  Object k;
                  //依據(jù)equals()方法判斷是否相等
                  if (e.hash == hash &&
                      ((k = e.key) == key || (key != null && key.equals(k))))
                      return e;
              }
              return null;
          }

          put()

          put(K key, V value)方法是將指定的key, value對(duì)添加到map里。該方法首先會(huì)對(duì)map做一次查找,看是否包含該元組,如果已經(jīng)包含則直接返回,查找過程類似于getEntry()方法;如果沒有找到,則會(huì)通過addEntry(int hash, K key, V value, int bucketIndex)方法插入新的entry,插入方式為頭插法
          HashMap_addEntry

          //addEntry()
          void addEntry(int hash, K key, V value, int bucketIndex) {
              if ((size >= threshold) && (null != table[bucketIndex])) {
                  resize(2 * table.length);//自動(dòng)擴(kuò)容,并重新哈希
                  hash = (null != key) ? hash(key) : 0;
                  bucketIndex = hash & (table.length-1);//hash%table.length
              }
              //在沖突鏈表頭部插入新的entry
              Entry<K,V> e = table[bucketIndex];
              table[bucketIndex] = new Entry<>(hash, key, value, e);
              size++;
          }

          remove()

          remove(Object key)的作用是刪除key值對(duì)應(yīng)的entry,該方法的具體邏輯是在removeEntryForKey(Object key)里實(shí)現(xiàn)的。removeEntryForKey()方法會(huì)首先找到key值對(duì)應(yīng)的entry,然后刪除該entry(修改鏈表的相應(yīng)指針)。查找過程跟getEntry()過程類似。
          HashMap_removeEntryForKey

          //removeEntryForKey()
          final Entry<K,V> removeEntryForKey(Object key) {
              
              int hash = (key == null) ? 0 : hash(key);
              int i = indexFor(hash, table.length);//hash&(table.length-1)
              Entry<K,V> prev = table[i];//得到?jīng)_突鏈表
              Entry<K,V> e = prev;
              while (e != null) {//遍歷沖突鏈表
                  Entry<K,V> next = e.next;
                  Object k;
                  if (e.hash == hash &&
                      ((k = e.key) == key || (key != null && key.equals(k)))) {//找到要?jiǎng)h除的entry
                      modCount++; size--;
                      if (prev == e) table[i] = next;//刪除的是沖突鏈表的第一個(gè)entry
                      else prev.next = next;
                      return e;
                  }
                  prev = e; e = next;
              }
              return e;
          }

          HashSet

          前面已經(jīng)說過HashSet是對(duì)HashMap的簡(jiǎn)單包裝,對(duì)HashSet的函數(shù)調(diào)用都會(huì)轉(zhuǎn)換成合適的HashMap方法,因此HashSet的實(shí)現(xiàn)非常簡(jiǎn)單,只有不到300行代碼。這里不再贅述。

          //HashSet是對(duì)HashMap的簡(jiǎn)單包裝
          public class HashSet<E>
          {
              
              private transient HashMap<E,Object> map;//HashSet里面有一個(gè)HashMap
              
          // Dummy value to associate with an Object in the backing Map
              private static final Object PRESENT = new Object();
              public HashSet() {
                  map = new HashMap<>();
              }
              
              public boolean add(E e) {//簡(jiǎn)單的方法轉(zhuǎn)換
                  return map.put(e, PRESENT)==null;
              }
              
          }


          posted on 2016-04-27 21:27 CarpenterLee 閱讀(1860) 評(píng)論(0)  編輯  收藏

          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
          博客園   IT新聞   Chat2DB   C++博客   博問  
           
          主站蜘蛛池模板: 太仓市| 桑植县| 基隆市| 上思县| 太谷县| 会同县| 余干县| 宣武区| 山东省| 郴州市| 遂昌县| 石林| 吕梁市| 柳州市| 城固县| 延寿县| 白朗县| 青龙| 广南县| 西充县| 太湖县| 建平县| 遵化市| 康乐县| 广州市| 松原市| 长海县| 库尔勒市| 临高县| 义乌市| 三原县| 五常市| 汉寿县| 乌鲁木齐县| 香港| 永顺县| 班玛县| 宁安市| 松溪县| 老河口市| 通海县|