Java編程思想——第17章 容器深刻研究(two)

6、隊列

排隊,先進先出。html

除併發應用外Queue只有兩個實現:LinkedList,PriorityQueue。他們的差別在於排序而非性能。算法

一些經常使用方法:編程

 繼承自Collection的方法:數組

  add 在尾部增長一個元索 若是隊列已滿,則拋出一個IIIegaISlabEepeplian異常安全

  remove 移除並返回隊列頭部的元素 若是隊列爲空,則拋出一個NoSuchElementException異常數據結構

  element 返回隊列頭部的元素 若是隊列爲空,則拋出一個NoSuchElementException異常併發

 自帶的方法:這些更適用於緩衝和併發訪問,最主要是不報異常啊dom

  offer 在尾部添加一個元素並返回true 若是隊列已滿,則返回falseide

  poll 移除並返問隊列頭部的元素 若是隊列爲空,則返回null函數

  peek 返回隊列頭部的元素 若是隊列爲空,則返回null

  put 添加一個元素 若是隊列滿,則阻塞

  take 移除並返回隊列頭部的元素 若是隊列爲空,則阻塞

7、理解Map

標準的Java類庫包含以幾種基本實現:

HashMap,TreeMap,LinkedHashMap,WeakHashMap,ConcurrentHashMap,IdentityHahMap.

1.性能

普通的Map中get()方法呈線性搜索,執行速度至關慢,而hashMap使用了特殊的:散列碼來取代對鍵緩慢的搜索。

散列碼:「相對惟一」的,用以表明對象的int值。hashCode()是根類Object中的方法,因此全部Java對象都有散列碼,HashMap就是使用對象的hashCode()進行快速查詢的。

HashMap *:Map基於散列表的實現。插入和查詢「鍵值對」的開銷是固定的。能夠經過構造器設置容量和負載因子以調節容器的性能。最經常使用的Map。

LinkedHashMap:使用鏈表維護內部順序,因此迭代訪問快。get訪問要慢一點點。

TreeMap:基於紅黑樹實現的。鍵會由Comparable或Comparator進行排序,是惟一帶有subMap()方法的Map;

      TreeMap<Integer, String> treeMap = new TreeMap<>();
        treeMap.put(2, "two"); treeMap.put(1, "one"); treeMap.put(3, "three"); treeMap.put(4, "fore"); //fromKey-- 返回映射中鍵的低端點。 //fromInclusive-- true若是低端點要包含在返回的視圖。 //toKey-- 返回映射中鍵的高端點。 //toInclusive-- 這爲true若是高端點要包含在返回的視圖。 NavigableMap<Integer, String> navigableMap = treeMap.subMap(1, true, 3, true); System.out.println("values: " + navigableMap);
結果: values: {1=one, 2=two, 3=three}

WeakHashMap:弱鍵(weak key)映射,容許釋放映射所指向的對象;若是映射以外沒有引用指向某個"鍵",則此」鍵「能夠被垃圾回收

ConcurrentHashMap:一種線程安全的Map.詳見 Java編程思想——第21章 併發 讀書筆記系列

IdentityHashMap:使用== 代替 equals()對鍵進行比較的散列映射。

對Map的鍵要求於Set中的元素要求同樣,任何鍵都要由一個equals()方法;若是是散列Map,鍵要實現hashCode()方法;若是是TreeMap,必須實現Comparable。

2.SortedMap

 TreeMap是如今的惟一實現,確保鍵處於排序狀態,如下是由SortedMap提供的方法:

    //返回當前Map使用的Comparator
    public Comparator<? super K> comparator() {
        return comparator; } //返回Map的第一個Key public K firstKey() { return key(getFirstEntry()); } //返回Map的最後一個Key public K lastKey() { return key(getLastEntry()); } //生成Map子集 由fromKey(包含) 到 toKey(不包含)的鍵值組成 public SortedMap<K,V> subMap(K fromKey, K toKey) { return subMap(fromKey, true, toKey, false); } //生成Map子集 由鍵小於toKey的鍵值組成 public SortedMap<K,V> headMap(K toKey) { return headMap(toKey, false); } //生成Map子集 由鍵大於或等於fromKey的鍵值組成 public SortedMap<K,V> tailMap(K fromKey) { return tailMap(fromKey, true); }

3.LinkedHashMap

  爲了提升速度LindedHashMap散列化全部的元素,可是遍歷鍵值對時又以元素的插入順序返回鍵值對。

  *能夠在構造函數中設定LinkedHashMap,使之採用基於訪問的最近最少使用(LRU)算法。

    
// 初始化大小,加權因子,true開啓LRU算法 false插入順序
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }


8、散列與散列碼

HashMap使用equals()判斷當前的鍵是否與表中存在的鍵相同。

正確的equals()方法需知足一下條件:

1)自反性。x.equals(x) 是true;

2)對稱性。x.equalse(y) 返回true y.equals(x)也得是true;

3)傳遞性。x.equals(y) 返回true ,y.equals(z) 返回true , x.equals(z)返回true;

4)一致性。若是對象中用於等價比較的信息沒有變,那麼不管多少次 x.equals(y)返回值不會變

5)x.equals(null) 返回 false ;注意:(null).equals(x)報空指針。

強調:默認的Object.equals()只比較對象的地址,所以若是使用本身的類做爲HashMap的Key,必須同時重載hashCode()和equals()。

hashCode()並不須要老是可以返回惟一的標識碼,可是equals()方法必須嚴格的判斷兩個對象是否相等,做爲鍵必須惟一不然系統報錯。

    @Override
    public boolean equals(Object o) { return o instanceof T && (i.equals(((T) o).i))); }

instanceof檢查了此對象是否爲null ,是null則返回false。

爲速度而散列

  以線性查詢的是最慢的查詢方式,存儲一組元素最快的數據結構是數組,因此使用它來標識鍵的信息(注意,這裏說的是鍵信息不是鍵自己)。因爲數組不能調整容量,因此數組不保存鍵自己,而是經過鍵對象生成一個散列碼,將其做爲數組的下標,這個散列碼就是由Object中的、或本身的類覆蓋的hashCode()生成的。

  數組固定的問題解決了,可是鍵能夠產生相同的下標,也就是說可能會有衝突。數組多大不重要,任何鍵總能在數組中找到它的位置。因而,查詢一個值的過程首先就是計算散列碼,而後使用散列碼查找數組。若是可以保證沒有衝突(若是被查詢的值的數量是固定的,就有可能)。

  一般,衝突由外部連接處理;數組並不直接保存值,而是保存值的list。而後對list中的值使用equals()方法進行線性查詢。這部分查找會比較慢,可是若是散列函數好的話,數組每一個位置就有較少的值。

所以,不是查詢整個List而是快速的跳轉到數組的某個位置,只對不多的元素進行比較。這就是HashMap會如此快的緣由。 

  咱們把散列表的數組稱爲bucket(桶),爲了散佈均勻且速度快,桶的容積一般使用質數或者2的整數次方,用LinkedList填充桶。

put()操做,計算key的hashCode(),找到桶中的位置,看LinkedList內容,有值用equals()與值的key相比,相等就替換,不等或者沒有值就在尾部加上新值。

覆蓋hashCode()

桶下標值是沒法控制的,這個值依賴於具體的HashMap對象的容量,而容量的改變與容器的充滿程度和負載因子有關。hashCode()生成的結果,通過畜欄裏後成爲桶位的下標。

Joshua Blochw指出爲寫出一份像樣的hashCode給出了知道:

1)給 int 變量 result 賦予某個非0常量,

2)爲對象內每一個有意義的域f(既每一個能夠作equals()操做的域)計算一個int 散列碼 c:

域類型:                                                                 計算:

boolean                                                                   c=(f?0:1)

byte、char、short、int                                           c=(int)f

float                                                                         c=(int)(f^(f>>>32))

double                                                                     long I =Double.doubleToLongBits(f); c=(int)(I^(I>>>32))

Object,其equals()調用這個域的equals()       c=f.hashCode()

數組                     對每一個元素應用上述規則   

3)合併計算獲得散列碼

result = 37*result+c   

9、選擇接口的不一樣實現

容器之間的區別一般歸結於由什麼數據結構實現的。

  好比:ArrayList和LinkedList都實現List接口,因此基本操做都是同樣的。然而ArrayList底層是數組實現的,而LinkedList是雙向鏈表實現的,其中每一個對象包含數據的同時還包含直想鏈表中前一個和後一個元素的引用。所以更適合用於插入、刪除多的操做。而隨機訪問就應該選擇ArrayLIst,根據不一樣操做的性能選擇實現

  再好比:TreeSet、HashSet、LinkedHashSet都實現Set接口。每種都有不一樣行爲:HashSet查詢速度最快;LinkedHashSet保持元素插入的次序;TreeSet基於TreeMap,生成一個處於排序狀態的Set。因此根據不一樣行爲選擇的實現

對List的選擇

  對於數組支撐的List和ArrayList,不管列表的大小如何,訪問速度都是同樣的快。而對於LinkedList,訪問時間對於較大的列表將明顯增長。因此操做隨機訪問類型的操做,數組結構要比鏈表結構更合適。

  當使用迭代器插入新元素時,對於ArrayList當列表變大時,開銷變大。但對於LinkedList,並不會隨着列表尺寸變化而明顯變化。由於,ArrayList插入時,必須爲數組擴展空間,並將引用向前移動。而LinkedList則只須要連接新的元素,而沒必要修改列表中剩餘的元素。

  LinkedList對List的端點會進行特殊處理——這使得LinkedList在做用於Queue時,效率提升。LInkedList中的插入和移除代價至關低廉,而且不會隨着列表尺寸發生變化,可是對於ArrayList插入操做的代價高昂,而且代價將隨列表尺寸增長而增長。

  對於隨機訪問get() 和 set() 操做,ArrayList明顯速度快於LinkedList,由於LInkedLIst不是針對隨機訪問設計的。

  最佳的作法時選擇ArrayList,只有常常插入和刪除而影響性能時纔會選擇LinkedList.

對Set的選擇

  HashSet的性能總比TreeSet好,特別是在添加和刪除元素時,而這兩個操做更爲重要。TreeSet惟一好吃就是能夠維持元素的排序;由於排序 因此TreeSet的迭代一般比HashSet快。

  對於插入LinkedHashSet要比HashSet代價高;由於要額外維護鏈表所帶來的額外開銷。

對Map的選擇

  除了IdentityHashMap外,全部的Map實現的插入操做都會隨着Map尺寸的變大而明顯變慢,可是查找操做代價要小得多。

  TreeMap一般比HashMap慢,TreeMap是一種建立有序列表的方式。樹的行爲是:保證有序,而且沒必要進行特殊排序。一旦填充TreeMap,就能夠經過keySet()方法獲取鍵的Set試圖,而後調用toArray()造成鍵的數組。

  當使用Map時HahsMap應該是首選,除非須要Map始終保持有序時使用TreeMap。

  LinkedHashMap在插入時比HashMap慢一點,由於在維護散列數據結構得同時還要維護鏈表,也所以迭代速度更快。

  IdentityHashMap具備徹底不一樣的性能由於使用== 而不是 equals()來比較元素。

HashMap的性能因子

  能夠經過手動調整HashMap提升性能,這裏有些術語必須瞭解:

  容量:表中的桶位。

  初始容量:表在建立時所擁有的桶位數。HashMap和HashSet均可以經過構造函數指定初始化容量。

  尺寸:表中當前存儲的項數。

  負載因子:尺寸/容量。空表的負載因子是0,半滿表的負載因子是0.5,負載輕的表衝突可能性小,所以插入和查找更快,迭代則慢一些。HashMap和HashSet都具備指定負載因子的構造器,當負載達到該負載因子水平時,容器會自動增長容量,實現方式是使容量大體加倍,並從新將現有對象分佈到新的桶位集中(再散列)。

  HashMap使用默認的負載因子是0.75,更高的負載因子會增長查找代價。

10、實用方法

  Collections類(注意不是Collection)內部有不少卓越的的靜態方法:

public static <T extends Object & Comparable<? super T>> T max/min(Collection<? extends T> coll) 返回Collection中最大或最小的元素-採用Collection內置的天然比較法,
                                              (Collection<? extends T> coll, Comparator<? super T> comp) - 採用Comparator進行比較。 public static int indexOfSubList/lastIndexOfSubList(List<?> source, List<?> target) 返回target在source中第一次/最後一次出現的位置,找不到返回-1 public static <T> boolean replaceAll(List<T> list, T oldVal, T newVal) 使用newVal替換oldVal public static void reverse(List<?> list) 逆轉全部元素次序 public static <T> Comparator<T> reverseOrder() 返回一個排序規則 逆轉天然順序 例:TreeSet tr=new TreeSet(Collections.reverseOrder()); public static <T> Comparator<T> reverseOrder(Comparator<T> cmp) 逆轉參數的順序 例:TreeSet tr=new TreeSet(Collections.reverseOrder(new StrLenComparator())); public static void rotate(List<?> list, int distance) 全部元素向後移動distance個位置,將末尾元素移到前面。 public static void shuffle(List<?> list) 隨機改變指定列表順序 參數列表:(List<?> list, Random rnd)時可以使用本身的隨機機制 public static <T> void sort(List<T> list) 使用List中的天然排序 參數列表:(List<T> list, Comparator<? super T> c) 時,利用參數中排序規則排序 public static <T> void copy(List<? super T> dest, List<? extends T> src) 將src中的元素複製到dest public static void swap(List<?> list, int i, int j) 替換list中位置i和位置j的元素 public static <T> void fill(List<? super T> list, T obj) 用元素x替換list中的元素 public static boolean disjoint(Collection<?> c1, Collection<?> c2) 兩個集合沒有任何相同元素時 返回true public static int frequency(Collection<?> c, Object o) 返回集合中等於o的元素個數 public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) 在有排序的list中查找key元素的位置 public static <T> List<T> nCopies(int n, T o) 返回大小爲n的List,且List不可改變,o爲List中元素 emptyList()/emptyMap()/emptySet()返回不可變的空集合 singleton(T t)/singleList(T t)/singleMap(K key,V value) 產生不可變的集合,只包含參數中的元素

設定Collection或Map爲不可修改

  對「不可修改的」方法調用並不會產生編譯時檢查,可是完成轉換後,任何會改變容器內容的操做都會引發UnsupportedOperationException異常。

  不可修改:Collections.unmodifiableXXX(XXX); 好比:Collections.unmodifiableList(new ArrayList(data));

同步

  Collections.synchronizedXXX(XXX) 好比:Collections.synchronizedList(new ArrayList(data)) ; 一旦發現兩個線程同時操做就會拋出ConcurrentMdificationException      

相關文章
相關標籤/搜索