JAVA集合類中的哈希總結
目 錄
1、哈希表
2、Hashtable、HashMap、ConcurrentHashMap、LinkedHashMap、TreeMap區別
3、Hashtable、HashMap、ConcurrentHashMap、LinkedHashMap、TreeMap源碼分析
4、一致性哈希算法
5、transient使用方法
6、迭代器的強一致和弱一致
7、總結
一、哈希表
哈希表,是一種數據結構。它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。
常用的散列函數方法有:取余數法、平方取中法、線性函數法、隨機數法等。常見的解決沖突的方法有:鏈地址法、開發定址法、建立公共溢出區、多哈希函數法。
Java中的哈希表,即類Hashtable。它的散列方法采用了除留取余數法;解決沖突的方法采用了鏈地址法。鏈地址法使用於頻繁的插入和刪除的操作類型。
二、Hashtable、HashMap、ConcurrentHashMap、LinkedHashMap、TreeMap區別
Hashtable是一個包含單向鏈表的二維數組,其數據結構的數組中是Entry<K,V>存儲,entry對象。Hashtable有潔癖,不允許存入其中的key或者value為null。Hashtable是線程安全的,所有的方法均用synchronized修飾,這樣在任一時刻,只有一個線程可以寫Hashtable,因此,對於頻繁寫操作的業務邏輯,諸如寫excel表等時候,速度會非常慢。
HashMap是最常用的Map型數據結構,它根據鍵的hashCode()值存儲數據。HashMap允許一個key為null,允許多個value為空,HashMap不支持線程的同步,即可能會出現在同一時刻有多個線程同時寫HashMap,會產生數據的不一致。如果在修改代碼的過程中,需要給HashMap限制為線程同步的,可以采用Collections.synchronizedMap(map);方法使得HashMap可以同步。
ConcurrentHashMap是基於這樣的考慮:降低鎖的粒度。在Hashtable中的關鍵字是使用synchronized基於整張表結構的,鎖的粒度太大,它每次通過鎖住整張表讓線程獨占,來保證安全性。
LinkedHashMap保存了記錄的插入順序,在使用Iterator遍歷LinkedHashMap的時候,先得到的記錄肯定是先插入的。在遍歷的時候會比HashMap慢,因為HashMap是以O(1)來設計存取的。並且LinkedHashMap繼承自HashMap,擁有它的全部特性。
TreeMap是基於紅黑樹實現的,它是一種有序的存儲結構,並且程序員可以自己定義排序器。TreeMap默認會按存入的鍵值key來排序,默認是按升序排序,當然也可以指定排序的比較器。TreeMap同樣有潔癖,不允許存入null值。使用Iterator遍歷出來的TreeMap往往是有序的。
總結:常用HashMap,允許null插入;有兩個子類:ConcurrentHashMap和LinkedHashMap。前者用來彌補線程安全,后者用來彌補有序。此外還有Hashtable和TreeMap。雖然CouncurrentHashMap性能明顯優於Hashtable,但是並不能完全取代Hashtable,因為遍歷ConcurrentHashMap的迭代器是弱一致的。TreeMap數據結構則可以幫助我們得到一個有序的結果,適用於需要輸出排序結果的場景。
三、Hashtable、HashMap、ConcurrentHashMap、LinkedHashMap、TreeMap源碼分析
Hashtable源碼如下:

public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable { /** * The hash table data. */ private transient Entry<K,V>[] table; /** * The total number of entries in the hash table. */ private transient int count; public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); } /** * Constructs a new, empty hashtable with the specified initial * capacity and the specified load factor. * * @param initialCapacity the initial capacity of the hashtable. * @param loadFactor the load factor of the hashtable. * @exception IllegalArgumentException if the initial capacity is less * than zero, or if the load factor is nonpositive. */ 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); initHashSeedAsNeeded(initialCapacity); } /** * Constructs a new, empty hashtable with the specified initial capacity * and default load factor (0.75). * * @param initialCapacity the initial capacity of the hashtable. * @exception IllegalArgumentException if the initial capacity is less * than zero. */ public Hashtable(int initialCapacity) { this(initialCapacity, 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 a new hashtable with the same mappings as the given * Map. The hashtable is created with an initial capacity sufficient to * hold the mappings in the given Map and a default load factor (0.75). * * @param t the map whose mappings are to be placed in this map. * @throws NullPointerException if the specified map is null. * @since 1.2 */ public Hashtable(Map<? extends K, ? extends V> t) { this(Math.max(2*t.size(), 11), 0.75f); putAll(t); } /** * Returns the number of keys in this hashtable. * * @return the number of keys in this hashtable. */ public synchronized int size() { return count; } /** * Tests if this hashtable maps no keys to values. * * @return <code>true</code> if this hashtable maps no keys to values; * <code>false</code> otherwise. */ public synchronized boolean isEmpty() { return count == 0; }
HashMap等源碼不一一列舉。
四、一致性哈希算法
一致性hash算法提出了在動態變化的Cache環境中,判定哈希算法好壞的四個定義:
1、平衡性(Balance):平衡性是指哈希的結果能夠盡可能分布到所有的緩沖中去,這樣可以使得所有的緩沖空間都得到利用。很多哈希算法都能夠滿足這一條件。
2、單調性(Monotonicity):單調性是指如果已經有一些內容通過哈希分派到了相應的緩沖中,又有新的緩沖加入到系統中。哈希的結果應能夠保證原有已分配的內容可以被映射到原有的或者新的緩沖中去,而不會被映射到舊的緩沖集合中的其他緩沖區。
3、分散性(Spread):在分布式環境中,終端有可能看不到所有的緩沖,而是只能看到其中的一部分。當終端希望通過哈希過程將內容映射到緩沖上時,由於不同終端所見的緩沖范圍有可能不同,從而導致哈希的結果不一致,最終的結果是相同的內容被不同的終端映射到不同的緩沖區中。這種情況顯然是應該避免的,因為它導致相同內容被存儲到不同緩沖中去,降低了系統存儲的效率。分散性的定義就是上述情況發生的嚴重程度。好的哈希算法應能夠盡量避免不一致的情況發生,也就是盡量降低分散性。
4、負載(Load):負載問題實際上是從另一個角度看待分散性問題。既然不同的終端可能將相同的內容映射到不同的緩沖區中,那么對於一個特定的緩沖區而言,也可能被不同的用戶映射為不同 的內容。與分散性一樣,這種情況也是應當避免的,因此好的哈希算法應能夠盡量降低緩沖的負荷。
五、transient使用方法
在Java中一個對象只要實現了Serilizable接口,這個對象就可以被序列化。java的這種序列化模式為開發者提供了很多便利,我們可以不必關系具體序列化的過程,只要這個類實現了Serilizable接口,這個類的所有屬性和方法都會自動序列化。在開發過程中,我們常常會遇到這樣的問題,這個類的有些屬性需要序列化,而其他屬性不需要被序列化,如果一個用戶有一些敏感信息(如密碼,銀行卡號等),為了安全起見,不希望在網絡操作(主要涉及到序列化操作,本地序列化緩存也適用)中被傳輸,這些信息對應的變量就可以加上transient關鍵字。換句話說,這個字段的生命周期僅存於調用者的內存中而不會寫到磁盤里持久化。示例代碼如下:

import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class TransientTest { public static void main(String[] args) { User user = new User(); user.setUsername("Alexia"); user.setPasswd("123456"); System.out.println("read before Serializable: "); System.out.println("username: " + user.getUsername()); System.err.println("password: " + user.getPasswd()); try { ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("C:/user.txt")); os.writeObject(user); // 將User對象寫進文件 os.flush(); os.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } try { ObjectInputStream is = new ObjectInputStream(new FileInputStream("C:/user.txt")); user = (User) is.readObject(); // 從流中讀取User的數據 is.close(); System.out.println("\nread after Serializable: "); System.out.println("username: " + user.getUsername()); System.err.println("password: " + user.getPasswd()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } class User implements Serializable { private static final long serialVersionUID = 8294180014912103005L; private String username; private transient String passwd; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPasswd() { return passwd; } public void setPasswd(String passwd) { this.passwd = passwd; } }
java的transient關鍵字為我們提供了便利,你只需要實現Serilizable接口,將不需要序列化的屬性前添加關鍵字transient,序列化對象的時候,這個屬性就不會序列化到指定的目的地中。
六、迭代器的強一致和弱一致
fail-fast機制,是Java集合中的一種錯誤機制。當多個線程對同一個集合中進行操作時,就可能會產生fail-fast事件。當某一個線程A通過iterator去遍歷某集合的過程中,若該集合的內容被其他線程所改變了;那么線程A訪問集合時,就會拋出ConcurrentModificationException異常,產生fail-fast事件。fail-fast機制,是一種錯誤檢測機制。它只能被用來檢測錯誤,因為JDK並不保證fail-fast機制一定會發生。若在多線程環境下使用fail-fast機制的集合,建議使用“java.util.concurrent包下的類”去取代“java.util包下的類”。
java.util 包中的集合類都返回 fail-fast 迭代器,這意味着它們假設線程在集合內容中進行迭代時,集合不會更改它的內容。如果 fail-fast 迭代器檢測到在迭代過程中進行了更改操作,那么它會拋出 ConcurrentModificationException,這是不可控異常。
七、總結
ConcurrentHashMap是一個線程安全的Map結合,它采用鎖分離技術,通過多個鎖代替Hashtable中的單個鎖(這么說,JDK中先有的hashtable,然后又的ConcurrentHashMap)。ConcurrentHashMap使用了ReentrantLock鎖,而不是Sychronized鎖。ConcurrentHashMap中的get、put、remove三個方法保證了數據同步,但是沒有使用鎖。
詳細請參考:http://ifeve.com/java-concurrent-hashmap-2/
具體使用細節,示例代碼如下:

import java.util.HashMap; public class HashTableTest { public static void main(String args[]) { HashMap<String, String> map = new HashMap<String, String>(); map.put("weight", "85.4KG"); map.put("height", "180cm"); boolean isexists = map.containsKey("weight"); for (String str : map.keySet()) { if (isexists) { System.err.println("name:" + str + ", value:" + map.get(str)); } } } }