1. 程式人生 > >Java核心技術卷一基礎技術-第13章-集合-讀書筆記

Java核心技術卷一基礎技術-第13章-集合-讀書筆記

第13章 集合

本章內容:

* 集合介面

* 具體的集合

* 集合框架

* 演算法

* 遺留的集合

13.1 集合介面

  1. Enumeration介面提供了一種用於訪問任意容器中各個元素的抽象機制。

13.1.1 將集合的介面與實現分離

  1. Java集合類庫將介面(interface)與實現(implementation)分離。

  2. 佇列介面指出可以在佇列的尾部新增元素,在佇列的頭部刪除元素,並且可以查詢佇列中元素的個數。當需要收集物件,並按照“先進先出”的規則檢索物件時就應該使用佇列。

  3. 佇列通常有兩種實現方式:一種是使用迴圈陣列;另一種是使用連結串列。
    每一個實現都可以通過一個實現了Queue介面的類表示。

  4. 如果需要一個迴圈陣列佇列,就可以使用ArrayDeque類。如果需要一個連結串列佇列,就直接使用LinkedList類,這個類實現了Queue介面。
  5. 可以使用介面型別存放集合的引用。
  6. 迴圈陣列要比連結串列更高效,因此多數人優先選擇迴圈陣列。然而,通常這樣做也需要付出一定的代價。
  7. 迴圈陣列是一個有界集合,即容量有限。如果程式中要收集的物件數量沒有上限,就最好使用連結串列來實現。
  8. 在研究API文件時,會發現另外一組名字以Abstract開頭的類,例如,AbstractQueue。這些類是為類庫實現者設計的。如果想要實現自己的佇列類,會發現擴充套件AbstractQueue類要比實現Queue介面中的所有方法輕鬆得多。

13.1.2 Java類庫中的集合介面和迭代器介面

  1. 在Java類庫中,集合類的基本介面是Collection介面,這個介面有兩個基本方法:
    public interface Collection<E>
    {
     boolean add(E element);
     Iterator<E> iterator();
     ...
    }
    
    add方法用於向集合中新增元素。如果新增元素確實改變了集合就返回true,如果集合沒有發生變化就返回false。集合中不允許有重複的物件。
    iterator方法用於返回一個實現了Iterator介面的物件,可以使用這個迭代器物件依次訪問集合中的元素。
  2. Iterator介面包含3個方法:
    public interface Iterator<E>
    {
     E next();
     boolean hasNext();
     void remove();
    }
    
    通過反覆呼叫next方法,可以逐個訪問集合中的每個元素。但是,如果到達了集合的末尾,next方法將丟擲一個NoSuchElementException。因此,需要在呼叫next之前呼叫hasNext方法。如果迭代器物件還有多個供訪問的元素,這個方法就返回true。如果想要檢視集合中的所有元素,就請求一個迭代器,並在hasNext返回true時反覆地呼叫next方法。
  3. 編譯器簡單地將“for each”迴圈翻譯為帶有迭代器的迴圈。
  4. “for each”迴圈可以與任何實現了Iterable介面的物件一起工作,這個介面只包含一個方法:
    public interface Iterable<E>
    {
     Iterator<E> iterator();
    }
    
  5. Collection介面擴充套件了Iterable介面。因此,對於標準類庫中的任何集合都可以使用“for each”迴圈。
  6. 元素被訪問的順序取決於集合型別,如果對ArrayList進行迭代,迭代器將從索引0開始,沒迭代一次,索引值加1。然而,如果訪問HashSet中的元素,每個元素將會按照某種隨機的次序出現。雖然可以確定在迭代過程中能夠遍歷到集合中的所有元素,但卻無法預知元素被訪問的次序。
  7. Iterator介面的remove方法將會刪除上次呼叫next方法時返回的元素。
  8. 對next方法和remove方法的呼叫具有互相依賴性。如果呼叫remove之前沒有呼叫next將是不合法的。如果這樣做,將會丟擲一個IllegalStateException異常。
  9. 實現Collection介面的集合類需要提供很多的例行方法,為了能夠讓實現者更容易地實現這個介面,Java類庫提供了一個類AbstractCollection,它將基礎方法size和iterator抽象化了。
  10. java.util.Collection< E > 1.2
    • Iterator< E > iterator()
      返回一個用於訪問集合中每個元素的迭代器。
    • int size()
      返回當前儲存在幾個中的元素個數。
    • boolean isEmpty()
      如果集合中沒有元素,返回true。
    • boolean contains(Object obj)
      如果集合中包含了一個與obj相等的物件,返回true。
    • boolean containsAll(Collection<?> other)
      如果這個集合包含other集合中的所有元素,返回true。
    • boolean add(Object element)
      將一個元素新增到集合中,如果由於這個呼叫改變了集合,返回true。
    • boolean addAll(Collection<? extends E> other)
      將other集合中的所有元素新增到這個集合。如果由於這個呼叫改變了集合,返回true。
    • boolean remove(Object obj)
      從這個集合中刪除等於obj的物件。如果有匹配的物件被刪除,返回true。
    • boolean removeAll(Collection<?> other)
      從這個集合中刪除other集合中存在的所有元素。如果由於這個呼叫改變了集合,返回true。
    • void clear()
      從這個集合中刪除所有的元素。
    • boolean retainAll(Collection<?> other)
      從這個集合中刪除所有與other集合中的元素不同的元素。如果由於這個呼叫改變了集合,返回true。
    • Object[] toArray()
      返回這個集合的物件陣列。
    • < T > T[] toArray(T[] arrayToFill)
      返回這個集合的物件陣列。如果arrayToFill足夠大,就將集合中的元素填入這個陣列中。剩餘空間填充null;否則,分配一個新陣列,其成員型別與arrayToFill的成員型別相同,其長度等於集合的大小,並添入集合元素。
  11. java.util.Iterator< E > 1.2
    • boolean hasNext()
      如果存在可訪問的元素,返回true。
    • E next()
      返回將要訪問的下一個物件。如果已經到達了集合的尾部,將丟擲一個NoSuchElementException。
    • void remove()
      刪除上次訪問的物件。這個方法必須緊跟在訪問一個元素之後執行。如果上次訪問之後,集合已經發生了變化,這個方法將丟擲一個IllegalStateException。

13.2 具體的集合

  1. Java庫中的具體集合
集合型別 描述
ArrayList 一種可以動態增長和縮減的索引序列
LinkedList 一種可以在任何位置進行高效地插入和刪除操作的有序序列
ArrayDeque 一種用迴圈陣列實現的雙端佇列
HashSet 一種沒有重複元素的無序集合
TreeSet 一種有序集
EnumSet 一種包含列舉型別的集
LinkedHashSet 一種可以記住元素插入次序的集
PriorityQueue 一種允許高效刪除最小元素的集合
HashMap 一種儲存鍵/值關聯的資料結構
TreeMap 一種鍵值有序排列的對映表
EnumMap 一種鍵值屬於列舉型別的對映表
LinkedHashMap 一種可以記住鍵/值項新增次序的對映表
WeakHashMap 一種其值無用武之地後可以被垃圾回收器回收的對映表
IdentityHashMap 一種用==而不是用equals比較鍵值的對映表

除了以Map結尾的類之外,其他類都實現了Collection介面。而以Map結尾的類實現了Map介面。

13.2.1 連結串列

  1. 陣列和陣列列表都有一個重大的缺陷,就是從陣列的中間位置刪除一個元素要付出很大的程式碼,其原因是陣列中處於被刪除元素之後的所有元素都要向陣列的前端移動。在陣列中間的位置上插入一個元素也是如此。

  2. 在Java程式設計語言中,所有連結串列實際上都是雙向連結的(doubly linked)。

  3. 連結串列與泛型集合之間有一個重要的區別。連結串列是一個有序集合(ordered collection),每個物件的位置十分重要。LinkedList.add方法將物件新增到連結串列的尾部。但是,嚐嚐需要將元素新增到連結串列的中間。由於迭代器是描述集合中位置的,所以這種依賴於位置的add方法將由迭代器負責。只有對自然有序的集合使用迭代器新增元素才有實際意義。因此,在Iterator介面中就沒有add方法。相反地,集合類庫提供了子介面ListIterator,其中包含了add方法。

  4. LinkedList的listIterator方法返回一個實現了ListIterator介面的迭代器物件。
  5. Add方法在迭代器位置之前新增一個新物件。
  6. set方法用一個新元素取代呼叫next或previus方法返回的上一個元素。
  7. 連結串列只負責耿總對列表的結構性修改,例如,新增元素、刪除元素。set操作不被視為結構性修改。
  8. 連結串列不支援快速地隨機訪問。儘管如此,LinkedList類還是提供了一個用來訪問某個特定元素的get方法:
    LinkedList<String> list = ...;
    String obj = list.get(n);
    
    這個方法的效率不高。如果發現自己正在使用這個方法,說明有可能對於所要解決的問題使用了錯誤的資料結構。
    絕對不應該使用這種讓人誤解的隨機訪問方法來遍歷連結串列。下面這段程式碼的效率極低:
    for (int i = 0; i<list.size();i++)
     do something with list.get(i);
    
    每次查詢一個元素都要從列表的頭部重新開始搜尋。LinkedList物件根本不做任何快取位置資訊的操作。
  9. get方法做了微小的優化;如果索引大於size()/2就從列表尾端開始搜尋元素。
  10. 列表迭代器介面還有一個方法,可以告之當前位置的索引。實際上,從概念上講,由於Java迭代器指向兩個元素之間的位置,所以可以同時產生兩個索引:nextIndex方法返回下一次呼叫next方法時返回元素的整數索引;previousIndex方法返回下一次呼叫previous方法時返回元素的整數索引。當然,這個索引只比nextIndex返回的索引值小1。這兩個方法的效率非常高,這是因為迭代器保持著當前位置的計數值。最後需要說一下,如果一個整數索引n,list.listIterator(n)將返回一個迭代器,這個迭代器指向索引為n的元素前面的位置。也就是說,呼叫next與呼叫list.get(n)會產生同一個元素,知識獲得這個迭代器的效率比較低。
  11. 為什麼要優先使用連結串列呢?使用連結串列的唯一理由是儘可能地減少在列表中間插入或刪除元素所付出的代價。如果列表只有少數幾個元素,就完全可以使用ArrayList。
  12. 避免使用以整數索引表示連結串列中位置的所有方法。如果需要對幾個進行隨機訪問,就使用陣列或ArrayList,而不要使用連結串列。
  13. java.util.List< E > 1.2
    • ListIterator< E > listIterator()
      返回一個列表迭代器,以便用來訪問列表中的元素。
    • ListIterator< E > listIterator(int index)
      返回一個列表迭代器,以便用來訪問列表中的元素,這個元素是第一次呼叫next返回的給定索引的元素。
    • void add(int i,E element)
      在給定位置新增一個元素。
    • void addAll(int i,Collection<? extends E> elements)
      將某個集合中的所有元素新增到給定位置。
    • E remove(int i)
      刪除給定位置的元素並返回這個元素。
    • E get(int i)
      獲取給定位置的元素。
    • E set(int i,E element)
      用新元素取代給定位置的元素,並返回原來那個元素。
    • int indexOf(Object element)
      返回與給定元素相等的元素在列表中第一次出現的位置,如果沒有這樣的元素將返回-1。
    • int lastIndexOf(Object element)
      返回與給定元素相等的元素在列表中最後一次出現的位置,如果沒有這樣的元素將返回-1。
  14. java.util.ListIterator 1.2
    • void add(E newElement)
      在當前位置前新增一個元素。
    • void set(E newElement)
      用新元素取代next或previous上次訪問的元素。如果在next或previous上次呼叫之後列表結構被修改了,將丟擲一個IllegalStateException異常。
    • boolean hasPrevious()
      當反向迭代列表是,還有可供訪問的元素,返回true。
    • E previous()
      返回前一個物件。如果已經到達了列表的頭部,就丟擲一個NoSuchElementException異常。
    • int nextIndex()
      返回下一次呼叫next方法時將返回的元素索引。
    • int previousIndex()
      返回下一次呼叫previous方法時返回的元素索引。
  15. java.util.LinkedList< E > 1.2
    • LinkedList()
      構造一個空連結串列。
    • LinkedList<collection<? extends="" e=""> elements>
      構造一個連結串列,並將集合中所有的元素新增到這個連結串列中。
    • void addFirst(E element)
    • void addLast(E element)
      將某個元素新增到列表的頭部或尾部。
    • E getFirst()
    • E getLast()
      返回列表頭部或尾部的元素。
    • E removeFirst()
    • E removeLast()
      刪除並返回列表頭部或尾部的元素。

13.2.2 陣列列表

  1. List介面用於描述一個有序集合,並且集合中每個元素的位置十分重要。有兩種訪問元素的協議:一種是用迭代器。另一種是用get和set方法隨機地訪問每個元素。後者不適用於連結串列,但對陣列卻很有用。集合類庫提供了一種大家熟悉的ArrayList類,這個類也實現了List介面。ArrayList封裝了一個動態再分配的物件陣列。

  2. 在需要動態陣列時,可能會使用Vector類。為什麼要用ArrayList取代Vector呢?原因很簡單:Vector類的所有方法都是同步的。可以由兩個執行緒安全地訪問一個Vector物件。但是,如果由一個執行緒訪問Vector,程式碼要在同步操作上耗費大量的時間。這種情況還是很常見的。而ArrayList方法不是同步的,因此,建議在不需要同步時使用ArrayLits,而不要使用Vector。

13.2.3 雜湊集

  1. 可以快速地查詢所需要的物件的常見的資料結構就是散列表(hash table)。散列表為每個物件計算一個整數,稱為雜湊碼(hash code)。雜湊碼是由物件的例項域產生的一個整數。更準確的說,具有不用資料域的物件將產生不同的雜湊碼。

  2. 如果自定義類,就要負責實現這個類的hashCode方法。注意,自己實現的hashCode方法應該與equals方法相容,即如果a.equals(b)俄日true,a與b必須具有相同的雜湊碼。

  3. 在Java中,散列表用連結串列陣列實現。每個列表被稱為桶(bucket)。要想查詢表中物件的位置,就要先計算它的雜湊碼,然後與桶的總數取餘,所得到的結果就是儲存這個元素的桶的索引。如果在這個桶中沒有其他元素,此時將元素直接插入到桶中就可以了。當然,有時候會遇到桶被佔滿的情況,這也是不可避免的。這種現象被稱為雜湊衝突(hash colision)。這時,需要用新物件與桶中的所有物件進行比較,檢視這個物件是否已經存在。如果雜湊碼是合理且隨機分佈的,桶的數目也足夠大,需要比較的次數就會很少。

  4. 如果想更多地控制散列表的執行效能,就要指定一個初始的桶數。桶數是指用於收集具有相同雜湊值的桶的數目。如果要插入到散列表中的元素太多,就會增加衝突的可能性,降低執行效能。
  5. 如果大致知道最終會有多少個元素要插入到散列表中,就可以設定桶數。通常,將桶數設定為預計元素個數的75%~150%。有些研究人員認為:儘管還沒有確鑿的證據,但最好將桶數設定為一個素數,以防鍵的集聚。標準類庫使用的桶數是2的冪,預設值為16(為表大小提供的任何值都將被自動地轉換為2的下一個冪)。
  6. 如果散列表太滿,就需要再雜湊(rehashed)。如果要對散列表再雜湊,就需要建立一個桶數更多的表,並將所有元素插入到這個新表中,然後丟棄原來的表。裝填因子(load factor)決定何時對散列表進行再雜湊。例如,如果裝填因子為0.75(預設值),而表中超過75%的位置已經填入元素,這個表就會用雙倍的桶數自動地進行再雜湊。對於大多數應用程式來說,填裝因子為0.75是比較合理的。
  7. 散列表可以用於實現幾個重要的資料結構。其中最簡單的是set型別。set是沒有重複元素的元素集合。set的add方法首先在集中查詢要新增的物件,如果不存在,就將這個物件新增進去。
  8. Java集合類庫提供了一個HashSet類,它實現了基於散列表的集。可以用add方法新增元素。contains方法已經被重新定義,用來快速地檢視是否某個元素已經出現在集中。它只在某個桶中查詢元素,而不必檢視集合中的所有元素。
  9. 雜湊集迭代器將以此訪問所有的桶。由於雜湊將元素分散在表的各個位置上,所以訪問它們的順序幾乎是隨機的。只有不關心集合中元素的順序時才應該使用HashSet。
  10. java.util.HashSet< E > 1.2
    • HashSet()
      構造一個空散列表。
    • HashSet(Collection<? extends E> elements)
      構造一個散列表,並將集合中的所有元素新增到這個雜湊集中。
    • HashSet(int initialCapacity)
      構造一個空的具有指定容量(桶數)的雜湊集。
    • HashSet(int initialCapacity,float loadFactor)
      構造一個具有指定容量和裝填因子(一個0.0~1.0之間的數值,確定散列表填充的百分比,當大於這個百分比時,散列表進行再雜湊)的空雜湊集。
  11. java.lang.Object 1.0
    • int hashCode()
      返回這個物件的雜湊碼。雜湊碼可以是任何整數,包括正數或負數。equals和hashCode的定義必須相容,即如果x.equals(y)為true,x.hashCode()必須等於y.hashCode()。

13.2.4 樹集

  1. 樹集是一個有序集合(sorted collection)。可以以任意順序將元素插入到集合中。在對集合進行遍歷時,每個值將自動地按照排序後的順序呈現。

  2. 排序是用樹結構完成的(當前實現使用的是紅黑樹(red-black tree))每次將一個元素新增到樹中時,都被放置在正確的排序位置上。因此,迭代器總是以排好序的順序訪問每個元素。

  3. 將一個元素新增到樹中要比新增到散列表中慢,但是,與將元素新增到陣列或連結串列的正確位置上相比還是快很多的。如果樹中包含n個元素,查詢新元素的正確位置平均需要log2n次比較。

  4. 將一個元素新增到TreeSet中要比新增到HashSet中慢。
  5. 將元素新增到雜湊集和樹集
    文件 單詞總數 不同的單詞個數 HashSet TreeSet
    Alice in Wonderland 28195 5909 5秒 7秒
    The Count of Monte Cirsto 466300 37545 75秒 98秒
  6. java.util.TreeSet 1.2
    • TreeSet()
      構造一個空樹集。
    • TreeSet(Collection<? exxtends E> elements)
      構造一個樹集,並將集合中的所有元素新增到樹集中。

13.2.5 物件的比較

  1. TreeSet如何知道希望元素怎樣排列呢?在預設情況下,樹集假定插入的元素實現了Comparable介面。這個介面定義了一個方法:
    public interface Comparable<T>
    {
     int compreTo(T other);
    }
    
    如果a與b相等,呼叫a.compareTo(b)一定返回0;如果排序後a位於b之前,則返回負值;如果a位於b之後,則返回正值。具體返回什麼值並不重要,關鍵是符號(>0,0或<0)。有些標準的Java平臺類實現了Comparable介面,例如,String類。這個類的compareTo方法依據字典序(有時稱為字典序)對於字串進行比較。如果要插入自定義的物件,就必須通過實現Comparable介面自定義排序順序。在Object類中,沒有提供任何compareTo介面的預設實現。
  2. 使用Comparable介面定義排列排序顯然有其侷限性。對於一個給定的類,只能夠實現這個介面一次。可以通過將Comparator物件傳遞給TreeSet構造器來告訴樹集使用不用的比較方法。Comparator介面聲明瞭一個帶有兩個顯式引數的compare方法:

    public interface Comparator<T>
    {
     int compare(T a,T b);
    }
    
  3. 從Java SE 6起,TreeSet類實現了NavigableSet介面。這個介面增加了幾個便於定位元素以及反向遍歷的方法。

  4. java.lang.Comparale 1.2
    • int compareTo(T other)
      將這個物件(this)與另一個物件(other)進行比較,如果this位於other之前則返回負值;如果兩個物件在排列順序中處於相同的位置則返回0;如果this位於other之後則返回正值。
  5. java.util.Comparator 1.2
    • int compare(T a,T b)
      將兩個物件進行比較,如果a位於b之前則返回負值;如果兩個物件在排列順序中處於相同的位置則返回0;如果a位於b之後則返回正值。
  6. java.util.SortedSet 1.2
    • Comparator<? super E> comparator()
      返回用於對元素進行排序的比較器。如果元素用Comparable介面的compareTo方法進行比較則返回null。
    • E first()
    • E last()
      返回有序集中的最小元素或最大元素。
  7. java.util.NavigableSet 6
    • E higher(E value)
    • E lower(E value)
      返回大於value的最小元素或小於value的最大元素,如果沒有這樣的元素則返回null。
    • E ceiling(E value)
    • E floor(E value)
      返回大於等於value的最小元素或小於等於value的最大元素,如果沒有這樣的元素則返回null。
    • E pollLast
      刪除並返回這個集中的最大元素或做小元素,這個集為空時返回null。
    • Iterator descendingIterator()
      返回一個按照遞減順序遍歷集中元素的迭代器。
  8. java.util.TreeSet 1.2
    • TreeSet()
      構造一個用於排列Comparable物件的樹集。
    • TreeSet(Comparator<? super E> c)
      構造一個樹集,並使用指定的比較器對其中的元素進行排序。
    • TreeSet(SortedSet<? extends E> elements)
      構造一個樹集,將有序集中的所有元素新增到這個樹集中,並使用與給定集相同的元素比較器。

13.2.6 佇列與雙端佇列

  1. 佇列可以讓人們有效地在尾部新增一個元素,在頭部刪除一個元素。有兩個端頭的佇列,即雙端佇列,可以讓人們有效地再頭部和尾部同事新增或刪除元素。不支援在佇列中間新增元素。在Java SE 6中引入了Deque介面,並由ArrayDeque和LinkedList類實現。這兩個類都提供了雙端佇列,並且在必要時可以增加佇列的長度。

  2. java.util.Queue 5.0

    • boolean add(E element)
    • boolean offer(E element)
      如果佇列沒有滿,將給定的元素新增到這個雙端佇列的尾部並返回true。如果佇列滿了,第一個方法將丟擲一個IllegalStatelException,而第二個方法返回false。
    • E remove()
    • E poll()
      加入佇列不空,刪除並返回這個佇列頭部的元素。如果佇列是空的,第一個方法丟擲NoSuchElmentException,而第二個方法返回null。
    • E element()
    • E peek()
      如果佇列不空,返回這個佇列頭部的元素,但不刪除。如果佇列空,第一個方法將丟擲一個NoSuchElementException,而第二個方法返回null。
  3. java.util.Deque< E > 6

    • void addFirst(E element)
    • void addLast(E element)
    • boolean offerFirst(E element)
    • boolean offerLast(E element)
      將給定的物件新增到雙端佇列的頭部或尾部。如果佇列滿了,前面兩個方法將丟擲一個IllegalStateException,而後面兩個方法返回false。
    • E removeFirst()
    • E removeLast()
    • E pollFirst()
    • E pollLast()
      如果佇列不空,刪除並返回佇列頭部的元素。如果佇列為空,前面兩個方法將丟擲一個NoSuchElementException,而後面兩個方法返回null。
    • E getFirst()
    • E getLast()
    • E peekFirst()
    • E peekLast()
      如果佇列非空,返回佇列頭部的元素,但不刪除。如果佇列空,前面兩個方法將丟擲一個NoSuchElementException,而後面兩個方法返回null。
  4. java.util.ArrayDeque< E > 6

    • ArrayDeque()
    • ArrayDeque(int initialCapacity)
      用初始容量16或給定的初始容量構造一個無限雙端佇列。

13.2.7 優先順序佇列

  1. 優先順序佇列(priority queue)中的元素可以按照任意的順序插入,卻總是按照排序的順序進行檢索。也就是說,無論何時呼叫remove方法,總會獲得當前優先順序佇列中最小的元素。然而,優先順序佇列並沒有對所有的元素進行排序。如果用迭代的方式處理這些元素,並不需要對它們進行排序。優先順序佇列使用了一個優雅且高效地資料結構,稱為堆(heap)。堆是一個可以自我調整的二叉樹,對樹執行新增(add)和刪除(remore)操作,可以讓最小的元素移動到根,而不必花費時間對元素進行排序。

  2. 與TreeSet一樣,一個優先順序佇列既可以儲存實現了Comparable介面的類物件,也可以儲存在構造器中提供比較器的物件。

  3. java.util.PriorityQueue 5.0

    • PriorityQueue()
    • PriorityQueue(int initialCapacity)
      構造一個用於存放Comparable物件的優先順序佇列。
    • PriorityQueue(int initialCapacity,Comparator<? super E> c)
      構造一個優先順序佇列,並用指定的比較器對元素進行排序。

13.2.8 對映表

  1. 對映表用來存放鍵/值對。如果提供了鍵,就能夠查詢到值。

  2. Java類庫為對映表提供了兩個通用的實現:HashMap和TreeMap。這兩個類都實現了Map介面。

  3. 雜湊對映表對鍵進行雜湊,樹對映表用鍵的整體順序對元素進行排序,並將其組織成搜尋樹。雜湊或比較函式只能作用於鍵。與鍵關聯的值不能進行雜湊或比較。

  4. 應該選擇雜湊對映表還是樹對映表呢?與集一樣,雜湊稍微快一些,如果不需要按照排列順序訪問鍵,就最好選擇雜湊。
  5. 鍵必須是唯一的。不能對同一個鍵存放兩個值。如果對同一個鍵兩次呼叫put方法,第二個值就會取代第一個值。實際上,put將返回用這個鍵引數儲存的上一個值。
  6. remove方法用於從對映表中刪除給定鍵對應的元素。size方法用於返回對映表中的元素數。
  7. 集合框架並沒有將對映表本身視為一個集合(其他的資料結果框架則將對映表視為對(pairs)的集合,或者視為用鍵作為索引的值的集合)。然而,可以獲得對映表的檢視,這是一組實現了Collection介面物件,或者它的子介面的檢視。
  8. keySet既不是HashSet,也不是TreeSet,而是實現了Set介面的某個其他類的物件。Set介面擴充套件了Collection介面。因此,可以與使用任何集合一樣使用keySey。
  9. 如果呼叫迭代器的remove方法,實際上就從對映表中刪除了鍵以及對應的值。但是,不能將元素新增到鍵集的檢視中。如果只新增鍵而不新增值是毫無意義的。如果檢視呼叫add方法,將會丟擲一個UnsupportedOperationException異常。條目集檢視也有同樣的限制,不過,從概念上講,新增新的鍵/值對是有意義的。
  10. java.util.Map< K,V > 1.2
    • V get(Object key)
      獲取與鍵對應的值;返回與鍵對應的物件,如果在對映表中沒有這個物件則返回null。鍵可以為null。
    • V put(K key,V value)
      將鍵與對應的值關係插入到對映表中。如果這個鍵已經存在,新的物件將取代與這個鍵對應的就物件。這個方法將返回鍵對應的舊值。如果這個鍵以前沒有出現過則返回null。鍵可以為null,但值不能為null。
    • void putAll(Map<? extends K,?extends V> entries)
      將給定對映表中的所有條目新增到這個對映表中。
    • boolean containsKey(Object key)
      如果在對映表中已經有這個鍵,返回true。
    • boolean containsValue(Objecy value)
      如果應設變中已經有這個值,返回true。
    • Set< Map.Entry< K,V >> entrySet()
      返回Map.Entry物件的集檢視,即對映表中的鍵/值對。可以從這個集中刪除元素,同時也從對映表中刪除了它們。但是,不能新增任何元素。
    • Set< K > keySet()
      返回對映表中所有鍵的集檢視。可以從這個集中刪除元素,同時也從對映表中刪除了它們。但是,不能新增任何元素。
    • Collection< V > values()
      返回對映表中所有值的集合檢視。可以從這個集中刪除元素,同時也從對映表中刪除了它們。但是,不能新增任何元素。
  11. java.util.Map.Entry< K,V > 1.2
    • K getKey()
    • V getValue()
      返回這個條目的鍵或值。
    • V getValue(V newValue)
      設定在對映表中與新值對應的值,並返回舊值。
  12. java.util.HashMap< K,V > 1.2
    • HashMap()
    • HashMap(int initialCapacity)
    • HashMap(int initialCapacity,float loadFactor)
      用給定的容量和裝填因子構造一個空雜湊對映表(裝填因子是一個0.0~1.0之間的數值。這個數值決定散列表填充的百分比。一旦到了這個比例,就要將其再雜湊到更大的表中)。預設的裝填因子是0.75。
  13. java.util.TreeMap< K,V > 1.2
    • TreeMap(Comparator<? super K> c)
      構造一個樹對映表,並使用一個指定的比較器對鍵進行排序。
    • TreeMap<map<? extends="" k,?="" v=""> entries>
      構造一個樹對映表,並將某個對映表中的所有條目新增到樹對映表中。
    • TreeMap(SortedMap<? extends K,? extends V> entries)
      構造一個樹對映表,將某個有序對映表中的所有條目新增到樹對映表中,並使用與給定的有序對映表相同的比較器。
  14. java.util.SortedMap< K,V > 1.2
    • Comparator<? super K> comparator()
      返回對鍵進行排序的比較器。如果鍵是用Comparable介面的compareTo方法進行比較的,返回null。
    • K firstKey()
    • K lastKey()
      返回對映表中最小元素和最大元素。

13.2.9 專用集合對映表類

  1. 弱雜湊對映表
    設計WeakHashMap類是為了解決一個有趣的問題。如果有一個值,對應的鍵已經不在使用了,將會出現什麼情況呢?假定對某個鍵的最後一次引用已經消亡,不會有任何途徑引用這個值的物件了。但是,由於在程式中的任何部分沒有再出現這個鍵,所以,這個鍵/值對無法從對映表中刪除。為什麼垃圾回收期不能夠刪除它呢?難道刪除無用的物件不是垃圾回收器的工作嗎?
    垃圾回收器耿總活動的物件。只要對映表物件是活動的,其中的所有桶也是活動的,它們不能被回收。因此,需要由程式負責從長期存活的對映表中刪除那些無用的值。或者使用WeakHashMap完成這件事情。當對鍵的唯一引用來自散列表條目時,這一資料結構將與垃圾回收器協同工作一起刪除鍵/值對。
    WeakHashMap使用弱引用(weak references)儲存鍵。WeakReference物件將引用儲存到另外一個物件中,在這裡,就是散列表鍵。對於這種型別的物件,垃圾回收器用一種特有的方式進行處理。通常,如果垃圾回收器發現某個特定的物件已經沒有他人引用了,就將其回收。然而,如果某個物件只能有WeakReference引用,垃圾回收器仍然回收它,但要將引用這個物件的弱引用放入佇列中。WeakHashMap將週期性地檢查佇列,以便找出新新增的弱引用。一個弱引用進入佇列意味著這個鍵不再被他人使用,並且已經被收集起來。於是,WeakHashMap將刪除對應的條目。

  2. 連結雜湊集和連結對映集
    Java SE 1.4增加了兩個類:LInkedHashSet和LinkedHashMap,用來記住插入元素想的順序。這樣就可以避免在散列表中的項從表面上看是隨機排列的。當條目插入到表中時,就會併入到雙向連結串列中。
    連結雜湊對映表將用訪問順序,而不是插入順序,對對映表條目進行迭代。每次呼叫get或put,受到影響的條目將從當前的位置刪除,並放到條目連結串列的尾部(只有條目在連結串列中的位置會受影響,而散列表中的桶不會受影響。一個條目總位於與鍵雜湊碼對應的桶中)。
    訪問順序對於實現快取記憶體的“最近最少使用”原色十分重要。

  3. 列舉集與對映集
    EnumSet是一個列舉型別元素集的高效實現。由於列舉型別只有有限個例項,所以EnumSet內部用位序列實現。如果對應的值在集中,則對應的位被置為1。
    EnumSet類沒有公共的構造器。可以使用靜態工廠方構造這個集。
    可以使用Set介面的常用方法來修改EnumSet。
    EnumMap是一個鍵型別為列舉型別的對映表。它可以直接且高效地用一個值陣列實現。在使用時,需要在構造器中指定鍵型別。

  4. 標識雜湊對映表
    Java SE 1.4還為另外一個特殊目的增加了另一個類IdentityHashMap。在這個類中,鍵的雜湊值不是用hashCode函式計算的,而是用System,identityHashCode方法計算的。這是Object.hashCode方法根據物件的記憶體地址來計算雜湊碼時所使用的方法。而且,在對兩個物件進行比較時,IdentityHashMap類使用==,而不使用equals。
    也就是說,不同的鍵物件,即使內容相同,也被視為是不同的物件。在實現物件遍歷演算法(如物件序列化)時,這個類非常有用,可以用來跟蹤每個物件的遍歷狀況。
  5. java.util.WeakHashMap< K,V > 1.2

    • WeakHashMap()
    • WeakHashMao(int initialCapacity)
    • WeakHashMap(int initialCapacity,float loadFactor)
      用給定的容量和填充因子構造一個空的雜湊對映表。
  6. java.util.LinkedHashSet 1.4

    • LinkedHashSet()
    • LinkedHashSet(int initialCapacity)
    • LinkedHashSet(int initialCapacity,float loadFactor)
      用給定的容量和填充因子構造一個空連結雜湊集。
  7. java.util.LinkedHashMap< K,V > 1.4

    • LinkedHashMap()
    • LinkedHashMap(int initialCapacity)
    • LinkedHashMap(int initialCapacity,float loadFactor)
    • LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder)
      用給定的容量、填充因子和順組構造一個空的連結雜湊對映表。
    • protected boolean removeEldestEntry(Map.Entry< K,V > eldest)
      如果想刪除eldest元素,並同時返回true,就應該覆蓋這個方法。eldest引數是預期要刪除的條目。這個方法將在條目新增到對映表中之後呼叫。其預設的實現將返回false。即在預設情況下,舊元素沒有被刪除。然而,可以重新定義這個方法,以便有選擇地返回true。
  8. java.util.EnumSet< E extends Enum< E >> 5.0

    • static < E extends Enum< E >> EnumSet< E > allOf(Class< E > enumType)
      返回一個包含給定列舉型別的所有值的集。
    • static < E extends Enum< E >> EnumSet< E > noneOf(Class< E > enumType)
      返回一個空集,並有足夠的儲存給定的列舉型別所有的值。
    • static < E extends Enum< E > > EnumSet< E > range(E from,E to)
      返回一個包含from~to之間的所有值(包括兩個邊界的元素)的集。
    • static < E extends Enum< E > > EnumSet< E > of(E value)
    • static < E extends Enum< E > > EnumSet< E > off(E value,E… value)
      返回包括給定值的集。
  9. java.util.EnumMap< K extends Enum< K >,V > 5.0

    • EnumMap(Class< K > keyType)
      構造一個鍵為給定型別的空對映集。
  10. java.util.IdentityHashMap< K,V > 1.4

    • IdentityHashMap()
    • IdentityHashMap(int expectedMaxSize)
      構造一個空的標識雜湊對映集,其容量是大於1.5*expectedMaxSize的2的最小次冪(expectedMaxSize的預設值是21)。
  11. java.lang.System 1.0

    • static int identityHashCode(Object obj) 1.1
      返回Object.hashCode計算出來的相同雜湊碼(根據物件的記