關於Java中的雜湊表

來源:互聯網
上載者:User

首先來瞭解一下基本概念 

所謂雜湊表(Hash Table,又叫散列表),是儲存索引值對(Key-value)的表,之所以不叫它Map(索引值對一起儲存一般叫做Map),是因為它下面的特性:它能把關鍵碼(key)映射到表中的一個位置來直接存取,這樣訪問速度就非常快。其中的映射函數稱為散列函數(Hash function)。 

1) 對於關鍵字key, f(key)是其儲存位置,f則是散列函數 

2) 如果key1 != key2 但是 f(key1) == f(key2),這種現象稱為衝突(collison)。衝突不可避免,這是因為key值無限而表容量總是有限(*見篇末思考題*)。我們追求的是對任意關鍵字,散列到表中的地址機率是相等的,這樣的散列函數為均勻散列函數。 

散列函數有多種 
× 直接定址法:取關鍵字或關鍵字的某個線性函數值為散列地址。即H(key)=key或H(key) = a·key + b,其中a和b為常數(這種散列函數叫做自身函數) 
× 數字分析法 
× 平方取中法 
× 摺疊法 
× 隨機數法 
× 除留餘數法:取關鍵字被某個不大於散列表表長m的數p除後所得的餘數為散列地址。即 H(key) = key MOD p, p<=m。不僅可以對關鍵字直接模數,也可在摺疊、平方取中等運算之後模數。對p的選擇很重要,一般取素數或m,若p選的不好,容易產生同義字。 

可以想像,當表中的資料個數接近表的容量大小時,發生衝突的機率會明顯增大,因此,在“資料個數/表容量”到達某個比例的時侯,需要擴大表的容量,這個比例稱為“裝填因子”(load factor). 

解決衝突主要有下面兩類方法: 
× 分離連結法,就是對hash到同一地址的不同元素,用鏈表連起來,也叫拉鏈法 
× 開放定址法,如果地址有衝突,就在此地址附近找。包括線性探測法,平方探測法,雙散列等 

然後來看一下Java的Hashtable實現 

java.util.Hashtable的本質是個數組,數組的元素是linked的索引值對(單向鏈表)。 

Java代碼  private transient Entry[] table; // Entry數組  

private static class Entry<K,V> implements Map.Entry<K,V> {      int hash;      K key;      V value;      Entry<K,V> next; // Entry此處表明是個單鏈表      ...  }

我們可以使用指定數組大小、裝填因子的建構函式,也可以使用預設建構函式,預設數組的大小是11,裝填因子是0.75. 

public Hashtable(int initialCapacity, float loadFactor) {  ...  }  public Hashtable() {      this(11, 0.75f);  }  

當要擴大數組時,大小變為oldCapacity * 2 + 1,當然這無法保證數組的大小總是素數。 
來看下其中的元素插入的方法,put方法: 

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;      for (Entry<K, V> e = tab[index]; e != null; e = e.next) {          if ((e.hash == hash) && e.key.equals(key)) {              V old = e.value;              e.value = value;              return old;          }      }  }

Java中Object類有幾個方法,其中一個是hashCode(), 這說明Java中所有對象都具有這一方法,調用可以得到對象自身的hash碼。對錶的長度取餘得址,並在衝突位置使用鏈表。 
HashMap與Hashtable的功能幾乎一樣。但HashMap的的初始數組大小是16而不是11,當要擴大數組時,大小變為原來的2倍,預設的裝填因子也是0.75. 其put方法如下,對hash值和index都有更改: 

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;  }      /**  * Applies a supplemental hash function to a given hashCode, which  * defends against poor quality hash functions.  This is critical  * because HashMap uses power-of-two length hash tables, that  * otherwise encounter collisions for hashCodes that do not differ  * in lower bits. Note: Null keys always map to hash 0, thus index 0.  */  static int hash(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);  }    /**  * Returns index for hash code h.  */  static int indexFor(int h, int length) {      return h & (length-1);  }

再看看其它開源的Java庫中的Hashtable 
目前存在多個開源的Java Collection實現,各個目的不同,側重點也不同。以下對開源架構中雜湊表的分析主要從幾個方面入手:預設裝填因子和capacity擴充方式,散列函數以及解決衝突的方法。 
1. Trove - Trove庫提供一套高效的基礎集合類。 
gnu.trove.set.hash.THashMap的繼承關係:THashMap -> TObjectHash -> THash,其內部的鍵和值使分別用2個數組表示。其解決衝突的方式採用開放定址法,開放定址法對空間要求較高,因此其預設裝填因子load factor是0.5,而不是0.75. 下面看代碼一步步解釋: 
預設初始化,裝填因子0.5,數組大小始從素數中取,也就是始終是素數。

/** the load above which rehashing occurs. */  public static final float DEFAULT_LOAD_FACTOR = 0.5f;    protected int setUp( int initialCapacity ) {      int capacity;      capacity = PrimeFinder.nextPrime( initialCapacity );      computeMaxSize( capacity );      computeNextAutoCompactionAmount( initialCapacity );      return capacity;  }

然後看其put方法,insertKey(T key)是其散列演算法,hash碼對數組長度取餘後,得到index,首先檢查該位置是否被佔用,如果被佔用,使用雙散列演算法解決衝突,也就是代碼中的insertKeyRehash()方法。 

public V put(K key, V value) {      // insertKey() inserts the key if a slot if found and returns the index      int index = insertKey(key);      return doPut(value, index);  }      protected int insertKey(T key) {      consumeFreeSlot = false;        if (key == null)          return insertKeyForNull();        final int hash = hash(key) & 0x7fffffff;      int index = hash % _set.length;      Object cur = _set[index];        if (cur == FREE) {          consumeFreeSlot = true;          _set[index] = key;  // insert value          return index;       // empty, all done      }        if (cur == key || equals(key, cur)) {          return -index - 1;   // already stored      }        return insertKeyRehash(key, index, hash, cur);  }

2. Javolution - 對即時、內建、高效能系統提供Java解決方案 
Javolution中的雜湊表是jvolution.util.FastMap, 其內部是雙向鏈表,預設初始大小是16,擴充時變為2倍。並沒有顯式定義load factor, 從下面語句可以知道,其值為0.5 

if (map._entryCount + map._nullCount > (entries.length >> 1)) { // Table more than half empty.      map.resizeTable(_isShared);  }  

再看下put函數,比較驚人的是其index和slot的取得,完全是用hashkey移位的方式取得的,這樣同時計算了index和避免了碰撞。 

private final Object put(Object key, Object value, int keyHash,          boolean concurrent, boolean noReplace, boolean returnEntry) {      final FastMap map = getSubMap(keyHash);      final Entry[] entries = map._entries; // Atomic.      final int mask = entries.length - 1;      int slot = -1;      for (int i = keyHash >> map._keyShift;; i++) {          Entry entry = entries[i & mask];          if (entry == null) {              slot = slot < 0 ? i & mask : slot;              break;          } else if (entry == Entry.NULL) {              slot = slot < 0 ? i & mask : slot;          } else if ((key == entry._key) || ((keyHash == entry._keyHash) && (_isDirectKeyComparator ? key.equals(entry._key)                  : _keyComparator.areEqual(key, entry._key)))) {              if (noReplace) {                  return returnEntry ? entry : entry._value;              }              Object prevValue = entry._value;              entry._value = value;              return returnEntry ? entry : prevValue;          }      }      ...  }

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.