ava-jdk拾遺-Map

[b]Map:[/b]
Map 接口提供三種collection 視圖,允許以鍵集、值集或鍵-值映射關係集的形式查看某個映射的內容。映射順序 定義爲迭代器在映射的 collection 視圖上返回其元素的順序。某些映射實現可明確保證其順序,如 TreeMap 類;另一些映射實現則不保證順序,如 HashMap 類。
注:將可變對象用作映射鍵時必須格外小心。當對象是映射中某個鍵時,如果以影響 equals 比較的方式更改了對象的值,則映射的行爲將是不確定的。此項禁止的一種特殊情況是不允許某個映射將自身作爲一個鍵包含。雖然允許某個映射將自身作爲值包含,但請格外小心:在這樣的映射上 equals 和 hashCode 方法的定義將不再是明確的。
所有通用的映射實現類應該提供兩個“標準的”構造方法:一個 void(無參數)構造方法,用於創建空映射;一個是帶有單個 Map 類型參數的構造方法,用於創建一個與其參數具有相同鍵-值映射關係的新映射。實際上,後一個構造方法允許用戶複製任意映射,生成所需類的一個等價映射。儘管無法強制執行此建議(因爲接口不能包含構造方法),但是 JDK 中所有通用的映射實現都遵從它。
此接口中包含的“破壞”方法可修改其操作的映射,如果此映射不支持該操作,這些方法將拋出 UnsupportedOperationException。如果是這樣,那麼在調用對映射無效時,這些方法可以(但不要求)拋出 UnsupportedOperationException。例如,如果某個不可修改的映射(其映射關係是“重疊”的)爲空,則對該映射調用 putAll(Map) 方法時,可以(但不要求)拋出異常。
某些映射實現對可能包含的鍵和值有所限制。例如,某些實現禁止 null 鍵和值,另一些則對其鍵的類型有限制。嘗試插入不合格的鍵或值將拋出一個未經檢查的異常,通常是 NullPointerException 或 ClassCastException。試圖查詢是否存在不合格的鍵或值可能拋出異常,或者返回 false;某些實現將表現出前一種行爲,而另一些則表現後一種。一般來說,試圖對不合格的鍵或值執行操作且該操作的完成不會導致不合格的元素被插入映射中時,將可能拋出一個異常,也可能操作成功,這取決於實現本身。這樣的異常在此接口的規範中標記爲“可選”。
此接口是 Java Collections Framework 的成員。
Collections Framework 接口中的很多方法是根據 equals 方法定義的。例如,containsKey(Object key) 方法的規範中寫道:“當且僅當此映射包含針對滿足 (key==null ? k==null : key.equals(k)) 的鍵 k 的映射關係時,返回 true”。不 應將此規範解釋爲:調用具有非空參數 key 的 Map.containsKey 將導致對任意的鍵 k 調用 key.equals(k)。實現可隨意進行優化,以避免調用 equals,例如,可首先比較兩個鍵的哈希碼(Object.hashCode() 規範保證哈希碼不相等的兩個對象不會相等)。一般來說,只要實現者認爲合適,各種 Collections Framework 接口的實現可隨意利用底層 Object 方法的指定行爲。
[b]HashMap:[/b]
基於哈希表的 Map 接口的實現。此實現提供所有可選的映射操作,並允許使用 null 值和 null 鍵。(除了非同步和允許使用 null 之外,HashMap 類與 Hashtable 大致相同。)此類不保證映射的順序,特別是它不保證該順序恆久不變。

此實現假定哈希函數將元素適當地分佈在各桶之間,可爲基本操作(get 和 put)提供穩定的性能。迭代 collection 視圖所需的時間與 HashMap 實例的“容量”(桶的數量)及其大小(鍵-值映射關係數)成比例。所以,如果迭代性能很重要,則不要將初始容量設置得太高(或將加載因子設置得太低)。

HashMap 的實例有兩個參數影響其性能:初始容量 和加載因子。容量 是哈希表中桶的數量,初始容量只是哈希表在創建時的容量。加載因子 是哈希表在其容量自動增加之前可以達到多滿的一種尺度。當哈希表中的條目數超出了加載因子與當前容量的乘積時,則要對該哈希表進行 rehash 操作(即重建內部數據結構),從而哈希表將具有大約兩倍的桶數。

通常,默認加載因子 (.75) 在時間和空間成本上尋求一種折衷。加載因子過高雖然減少了空間開銷,但同時也增加了查詢成本(在大多數 HashMap 類的操作中,包括 get 和 put 操作,都反映了這一點)。在設置初始容量時應該考慮到映射中所需的條目數及其加載因子,以便最大限度地減少 rehash 操作次數。如果初始容量大於最大條目數除以加載因子,則不會發生 rehash 操作。

如果很多映射關係要存儲在 HashMap 實例中,則相對於按需執行自動的 rehash 操作以增大表的容量來說,使用足夠大的初始容量創建它將使得映射關係能更有效地存儲。

注意,此實現不是同步的。如果多個線程同時訪問一個哈希映射,而其中至少一個線程從結構上修改了該映射,則它必須 保持外部同步。(結構上的修改是指添加或刪除一個或多個映射關係的任何操作;僅改變與實例已經包含的鍵關聯的值不是結構上的修改。)這一般通過對自然封裝該映射的對象進行同步操作來完成。如果不存在這樣的對象,則應該使用 Collections.synchronizedMap 方法來“包裝”該映射。最好在創建時完成這一操作,以防止對映射進行意外的非同步訪問,如下所示:

Map m = Collections.synchronizedMap(new HashMap(...));由所有此類的“collection 視圖方法”所返回的迭代器都是快速失敗 的:在迭代器創建之後,如果從結構上對映射進行修改,除非通過迭代器本身的 remove 方法,其他任何時間任何方式的修改,迭代器都將拋出 ConcurrentModificationException。因此,面對併發的修改,迭代器很快就會完全失敗,而不冒在將來不確定的時間發生任意不確定行爲的風險。

注意,迭代器的快速失敗行爲不能得到保證,一般來說,存在非同步的併發修改時,不可能作出任何堅決的保證。快速失敗迭代器盡最大努力拋出 ConcurrentModificationException。因此,編寫依賴於此異常的程序的做法是錯誤的,正確做法是:迭代器的快速失敗行爲應該僅用於檢測程序錯誤。

此類是 Java Collections Framework 的成員。
[b]public static interface Map.Entry<K,V>[/b]
映射項(鍵-值對)。Map.entrySet 方法返回映射的 collection 視圖,其中的元素屬於此類。獲得映射項引用的唯一 方法是通過此 collection 視圖的迭代器來實現。這些 Map.Entry 對象僅 在迭代期間有效;更確切地講,如果在迭代器返回項之後修改了底層映射,則某些映射項的行爲是不確定的,除了通過 setValue 在映射項上執行操作之外。
[b]ConcurrentHashMap:[/b]
public class ConcurrentHashMap<K,V>extends AbstractMap<K,V>implements ConcurrentMap<K,V>, Serializable
支持獲取的完全併發和更新的所期望可調整併發的哈希表。此類遵守與 Hashtable 相同的功能規範,並且包括對應於 Hashtable 的每個方法的方法版本。不過,儘管所有操作都是線程安全的,但獲取操作不 必鎖定,並且不 支持以某種防止所有訪問的方式鎖定整個表。此類可以通過程序完全與 Hashtable 進行互操作,這取決於其線程安全,而與其同步細節無關。

獲取操作(包括 get)通常不會受阻塞,因此,可能與更新操作交迭(包括 put 和 remove)。獲取會影響最近完成的 更新操作的結果。對於一些聚合操作,比如 putAll 和 clear,併發獲取可能隻影響某些條目的插入和移除。類似地,在創建迭代器/枚舉時或自此之後,Iterators 和 Enumerations 返回在某一時間點上影響哈希表狀態的元素。它們不會 拋出 ConcurrentModificationException。不過,迭代器被設計成每次僅由一個線程使用。

這允許通過可選的 concurrencyLevel 構造方法參數(默認值爲 16)來引導更新操作之間的併發,該參數用作內部調整大小的一個提示。表是在內部進行分區的,試圖允許指示無爭用併發更新的數量。因爲哈希表中的位置基本上是隨意的,所以實際的併發將各不相同。理想情況下,應該選擇一個儘可能多地容納併發修改該表的線程的值。使用一個比所需要的值高很多的值可能會浪費空間和時間,而使用一個顯然低很多的值可能導致線程爭用。對數量級估計過高或估計過低通常都會帶來非常顯著的影響。當僅有一個線程將執行修改操作,而其他所有線程都只是執行讀取操作時,才認爲某個值是合適的。此外,重新調整此類或其他任何種類哈希表的大小都是一個相對較慢的操作,因此,在可能的時候,提供構造方法中期望表大小的估計值是一個好主意。

此類及其視圖和迭代器實現了 Map 和 Iterator 接口的所有可選 方法。

此類與 Hashtable 相似,但與 HashMap 不同,它不 允許將 null 用作鍵或值。

[b]ConcurrentMap[/b]
public interface ConcurrentMap<K,V>extends Map<K,V>
提供其他原子 putIfAbsent、remove、replace 方法的 Map。

內存一致性效果:當存在其他併發 collection 時,將對象放入 ConcurrentMap 之前的線程中的操作 happen-before 隨後通過另一線程從 ConcurrentMap 中訪問或移除該元素的操作。
ConcurrentSkipListMap[b]
[/b]
public class ConcurrentSkipListMap<K,V>extends AbstractMap<K,V>implements ConcurrentNavigableMap<K,V>, Cloneable, Serializable
可縮放的併發 ConcurrentNavigableMap 實現。映射可以根據鍵的自然順序進行排序,也可以根據創建映射時所提供的 Comparator 進行排序,具體取決於使用的構造方法。

此類實現 SkipLists 的併發變體,爲 containsKey、get、put、remove 操作及其變體提供預期平均 log(n) 時間開銷。多個線程可以安全地併發執行插入、移除、更新和訪問操作。迭代器是弱一致 的,返回的元素將反映迭代器創建時或創建後某一時刻的映射狀態。它們不 拋出 ConcurrentModificationException,可以併發處理其他操作。升序鍵排序視圖及其迭代器比降序鍵排序視圖及其迭代器更快。

此類及此類視圖中的方法返回的所有 Map.Entry 對,表示他們產生時的映射關係快照。它們不 支持 Entry.setValue 方法。(注意,根據所需效果,可以使用 put、putIfAbsent 或 replace 更改關聯映射中的映射關係。)

請注意,與在大多數 collection 中不同,這裏的 size 方法不是 一個固定時間 (constant-time) 操作。因爲這些映射的異步特性,確定元素的當前數目需要遍歷元素。此外,批量操作 putAll、equals 和 clear 並不 保證能以原子方式 (atomically) 執行。例如,與 putAll 操作一起併發操作的迭代器只能查看某些附加元素。

此類及其視圖和迭代器實現 Map 和 Iterator 接口的所有可選 方法。與大多數其他併發 collection 一樣,此類不 允許使用 null 鍵或值,因爲無法可靠地區分 null 返回值與不存在的元素值。

[b]Hashtable[/b]
public class Hashtable<K,V>extends Dictionary<K,V>implements Map<K,V>, Cloneable, Serializable

此類實現一個哈希表,該哈希表將鍵映射到相應的值。任何非 null 對象都可以用作鍵或值。

爲了成功地在哈希表中存儲和獲取對象,用作鍵的對象必須實現 hashCode 方法和 equals 方法。

Hashtable 的實例有兩個參數影響其性能:初始容量 和加載因子。容量 是哈希表中桶 的數量,初始容量 就是哈希表創建時的容量。注意,哈希表的狀態爲 open:在發生“哈希衝突”的情況下,單個桶會存儲多個條目,這些條目必須按順序搜索。加載因子 是對哈希表在其容量自動增加之前可以達到多滿的一個尺度。初始容量和加載因子這兩個參數只是對該實現的提示。關於何時以及是否調用 rehash 方法的具體細節則依賴於該實現。

通常,默認加載因子(.75)在時間和空間成本上尋求一種折衷。加載因子過高雖然減少了空間開銷,但同時也增加了查找某個條目的時間(在大多數 Hashtable 操作中,包括 get 和 put 操作,都反映了這一點)。

初始容量主要控制空間消耗與執行 rehash 操作所需要的時間損耗之間的平衡。如果初始容量大於 Hashtable 所包含的最大條目數除以加載因子,則永遠 不會發生 rehash 操作。但是,將初始容量設置太高可能會浪費空間。

如果很多條目要存儲在一個 Hashtable 中,那麼與根據需要執行自動 rehashing 操作來增大表的容量的做法相比,使用足夠大的初始容量創建哈希表或許可以更有效地插入條目。

下面這個示例創建了一個數字的哈希表。它將數字的名稱用作鍵:

Hashtable<String, Integer> numbers
= new Hashtable<String, Integer>();
numbers.put("one", 1);
numbers.put("two", 2);
numbers.put("three", 3);
[b]synchronizedMap[/b]
collections.synchronizeMap()
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)返回由指定映射支持的同步(線程安全的)映射。爲了保證按順序訪問,必須通過返回的映射完成所有對底層實現映射的訪問。
在返回映射的任意 collection 視圖上進行迭代時,用戶必須手工在返回的映射上進行同步:

Map m = Collections.synchronizedMap(new HashMap());
...
Set s = m.keySet(); // Needn't be in synchronized block
...
synchronized(m) { // Synchronizing on m, not s!
Iterator i = s.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
}
不遵從此建議將導致無法確定的行爲。
如果指定映射是可序列化的,則返回的映射也將是可序列化的
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章