JAVA集合類圖:
1. hashmap原理,與hashtable區別
public V put(K key, V value) { // HashMap允許存放null鍵和null值。 // 當key爲null時,調用putForNullKey方法,將value放置在數組第一個位置。 if (key == null) return putForNullKey(value); // 根據key的keyCode重新計算hash值。 int hash = hash(key.hashCode()); // 搜索指定hash值在對應table中的索引。 int i = indexFor(hash, table.length); // 如果 i 索引處的 Entry 不爲 null,通過循環不斷遍歷 e 元素的下一個元素。 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; } } // 如果i索引處的Entry爲null,表明此處還沒有Entry。 modCount++; //這個mod是用於線程安全的,下文有講述 // 將key、value添加到i索引處。 addEntry(hash, key, value, i); return null; }
void addEntry(int hash, K key, V value, int bucketIndex) { // 獲取指定 bucketIndex 索引處的 Entry Entry<K,V> e = table[bucketIndex]; // <strong><span style="color:#ff0000;">將新創建的 Entry 放入 bucketIndex 索引處,並讓新的 Entry 指向原來的 Entry </span></strong> table[bucketIndex] = new Entry<K,V>(hash, key, value, e); // 如果 Map 中的 key-value 對的數量超過了極限 if (size++ >= threshold) // 把 table 對象的長度擴充到原來的2倍。 resize(2 * table.length); }
更詳細的原理請看: http://zhangshixi.iteye.com/blog/672697
區別:
http://blog.csdn.net/shohokuf/article/details/3932967
- HashMap允許鍵和值是null,而Hashtable不允許鍵或者值是null。
- Hashtable是同步的,而HashMap不是。因此,HashMap更適合於單線程環境,而Hashtable適合於多線程環境。
- HashMap提供了可供應用迭代的鍵的集合,因此,HashMap是快速失敗(具體看下文)的。另一方面,Hashtable提供了對鍵的列舉(Enumeration)。
- 一般認爲Hashtable是一個遺留的類。
2.讓hashmap變成線程安全的兩種方法
方法一:通過Collections.synchronizedMap()返回一個新的Map,這個新的map就是線程安全的. 這個要求大家習慣基於接口編程,因爲返回的並不是HashMap,而是一個Map的實現.
Map map = Collections.synchronizedMap(new HashMap());
方法二:使用ConcurrentHashMap
Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<String, Integer>();
3.ArrayList也是非線程安全的
一個 ArrayList 類,在添加一個元素的時候,它可能會有兩步來完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。在單線程運行的情況下,如果 Size = 0,添加一個元素後,此元素在位置 0,而且 Size=1;
而如果是在多線程情況下,比如有兩個線程,線程 A 先將元素存放在位置 0。但是此時 CPU 調度線程A暫停,線程 B 得到運行的機會。線程B也將元素放在位置0,(因爲size還未增長),完了之後,兩個線程都是size++,結果size變成2,而只有 items[0]有元素。
util.concurrent包也提供了一個線程安全的ArrayList替代者CopyOnWriteArrayList。
4. hashset原理
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L; // 底層使用HashMap來保存HashSet中所有元素。 private transient HashMap<E,Object> map; // 定義一個虛擬的Object對象作爲HashMap的value,將此對象定義爲static final。 private static final Object PRESENT = new Object(); /** * 默認的無參構造器,構造一個空的HashSet。 * * 實際底層會初始化一個空的HashMap,並使用默認初始容量爲16和加載因子0.75。 */ public HashSet() { map = new HashMap<E,Object>(); } /** * 構造一個包含指定collection中的元素的新set。 * * 實際底層使用默認的加載因子0.75和足以包含指定 * collection中所有元素的初始容量來創建一個HashMap。 * @param c 其中的元素將存放在此set中的collection。 */ public HashSet(Collection<? extends E> c) { map = new HashMap<E,Object>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } /** * 以指定的initialCapacity和loadFactor構造一個空的HashSet。 * * 實際底層以相應的參數構造一個空的HashMap。 * @param initialCapacity 初始容量。 * @param loadFactor 加載因子。 */ public HashSet(int initialCapacity, float loadFactor) { map = new HashMap<E,Object>(initialCapacity, loadFactor); } /** * 以指定的initialCapacity構造一個空的HashSet。 * * 實際底層以相應的參數及加載因子loadFactor爲0.75構造一個空的HashMap。 * @param initialCapacity 初始容量。 */ public HashSet(int initialCapacity) { map = new HashMap<E,Object>(initialCapacity); } /** * 以指定的initialCapacity和loadFactor構造一個新的空鏈接哈希集合。 * 此構造函數爲包訪問權限,不對外公開,實際只是是對LinkedHashSet的支持。 * * 實際底層會以指定的參數構造一個空LinkedHashMap實例來實現。 * @param initialCapacity 初始容量。 * @param loadFactor 加載因子。 * @param dummy 標記。 */ HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor); } /** * 返回對此set中元素進行迭代的迭代器。返回元素的順序並不是特定的。 * * 底層實際調用底層HashMap的keySet來返回所有的key。 * 可見HashSet中的元素,只是存放在了底層HashMap的key上, * value使用一個static final的Object對象標識。 * @return 對此set中元素進行迭代的Iterator。 */ public Iterator<E> iterator() { return map.keySet().iterator(); } /** * 返回此set中的元素的數量(set的容量)。 * * 底層實際調用HashMap的size()方法返回Entry的數量,就得到該Set中元素的個數。 * @return 此set中的元素的數量(set的容量)。 */ public int size() { return map.size(); } /** * 如果此set不包含任何元素,則返回true。 * * 底層實際調用HashMap的isEmpty()判斷該HashSet是否爲空。 * @return 如果此set不包含任何元素,則返回true。 */ public boolean isEmpty() { return map.isEmpty(); } /** * 如果此set包含指定元素,則返回true。 * 更確切地講,當且僅當此set包含一個滿足(o==null ? e==null : o.equals(e)) * 的e元素時,返回true。 * * 底層實際調用HashMap的containsKey判斷是否包含指定key。 * @param o 在此set中的存在已得到測試的元素。 * @return 如果此set包含指定元素,則返回true。 */ public boolean contains(Object o) { return map.containsKey(o); } /** * 如果此set中尚未包含指定元素,則添加指定元素。 * 更確切地講,如果此 set 沒有包含滿足(e==null ? e2==null : e.equals(e2)) * 的元素e2,則向此set 添加指定的元素e。 * 如果此set已包含該元素,則該調用不更改set並返回false。 * * 底層實際將將該元素作爲key放入HashMap。 * 由於HashMap的put()方法添加key-value對時,當新放入HashMap的Entry中key * 與集合中原有Entry的key相同(hashCode()返回值相等,通過equals比較也返回true), * 新添加的Entry的value會將覆蓋原來Entry的value,但key不會有任何改變, * 因此如果向HashSet中添加一個已經存在的元素時,新添加的集合元素將不會被放入HashMap中, * 原來的元素也不會有任何改變,這也就滿足了Set中元素不重複的特性。 * @param e 將添加到此set中的元素。 * @return 如果此set尚未包含指定元素,則返回true。 */ public boolean add(E e) { return map.put(e, PRESENT)==null; } /** * 如果指定元素存在於此set中,則將其移除。 * 更確切地講,如果此set包含一個滿足(o==null ? e==null : o.equals(e))的元素e, * 則將其移除。如果此set已包含該元素,則返回true * (或者:如果此set因調用而發生更改,則返回true)。(一旦調用返回,則此set不再包含該元素)。 * * 底層實際調用HashMap的remove方法刪除指定Entry。 * @param o 如果存在於此set中則需要將其移除的對象。 * @return 如果set包含指定元素,則返回true。 */ public boolean remove(Object o) { return map.remove(o)==PRESENT; } /** * 從此set中移除所有元素。此調用返回後,該set將爲空。 * * 底層實際調用HashMap的clear方法清空Entry中所有元素。 */ public void clear() { map.clear(); } /** * 返回此HashSet實例的淺表副本:並沒有複製這些元素本身。 * * 底層實際調用HashMap的clone()方法,獲取HashMap的淺表副本,並設置到HashSet中。 */ public Object clone() { try { HashSet<E> newSet = (HashSet<E>) super.clone(); newSet.map = (HashMap<E, Object>) map.clone(); return newSet; } catch (CloneNotSupportedException e) { throw new InternalError(); } } }
5. ArrayList,Vector, LinkedList的存儲性能和特性
ArrayList 和Vector都是使用數組方式存儲數據,此數組元素數大於實際存儲的數據以便增加和插入元素,它們都允許直接按序號索引元素,但是插入元素要涉及數組元素移動等內存操作,所以索引數據快而插入數據慢,Vector由於使用了synchronized方法(線程安全),通常性能上較ArrayList差,而LinkedList使用雙向鏈表實現存儲,按序號索引數據需要進行前向或後向遍歷,但是插入數據時只需要記錄本項的前後項即可,所以插入速度較快。
6.快速失敗(fail-fast)和安全失敗(fail-safe)
Fail-Fast機制:
我們知道java.util.HashMap不是線程安全的,因此如果在使用迭代器的過程中有其他線程修改了map,那麼將拋ConcurrentModificationException,這就是所謂fail-fast策略。
這一策略在源碼中的實現是通過modCount域,modCount顧名思義就是修改次數,對HashMap內容的修改都將增加這個值,那麼在迭代器初始化過程中會將這個值賦給迭代器的expectedModCount。
- HashIterator() {
- expectedModCount = modCount;
- if (size > 0) { // advance to first entry
- Entry[] t = table;
- while (index < t.length && (next = t[index++]) == null)
- ;
- }
- }
在迭代過程中,判斷modCount跟expectedModCount是否相等,如果不相等就表示已經有其他線程修改了Map:
注意到modCount聲明爲volatile,保證線程之間修改的可見性。
- final Entry<K,V> nextEntry() {
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException();
在HashMap的API中指出:
由所有HashMap類的“collection 視圖方法”所返回的迭代器都是快速失敗的:在迭代器創建之後,如果從結構上對映射進行修改,除非通過迭代器本身的 remove 方法,其他任何時間任何方式的修改,迭代器都將拋出ConcurrentModificationException。因此,面對併發的修改,迭代器很快就會完全失敗,而不冒在將來不確定的時間發生任意不確定行爲的風險。
注意,迭代器的快速失敗行爲不能得到保證,一般來說,存在非同步的併發修改時,不可能作出任何堅決的保證。快速失敗迭代器盡最大努力拋出 ConcurrentModificationException。因此,編寫依賴於此異常的程序的做法是錯誤的,正確做法是:迭代器的快速失敗行爲應該僅用於檢測程序錯誤。
Fail-Safe機制:Iterator的安全失敗是基於對底層集合做拷貝,因此,它不受源集合上修改的影響。java.util包下面的所有的集合類都是快速失敗(一般的集合類)的,而java.util.concurrent包下面的所有的類(比如CopyOnWriteArrayList,ConcurrentHashMap )都是安全失敗的。快速失敗的迭代器會拋出ConcurrentModificationException異常,而安全失敗的迭代器永遠不會拋出這樣的異常。
7.傳遞一個集合作爲參數給函數時,我們如何能確保函數將無法對其進行修改
我們可以創建一個只讀集合,使用Collections.unmodifiableCollection作爲參數傳遞給使用它的方法,這將確保任何改變集合的操作將拋出UnsupportedOperationException。
8.Collections類的方法們
上面說到了很多了collections的方法,我們來深究一下這個類
Collections則是集合類的一個工具類/幫助類,其中提供了一系列靜態方法,用於對集合中元素進行排序、搜索以及線程安全等各種操作。
1) 排序(Sort)
使用sort方法可以根據元素的自然順序 對指定列表按升序進行排序。列表中的所有元素都必須實現 Comparable接口。此列表內的所有元素都必須是使用指定比較器可相互比較的
可以直接Collections.sort(...)
或者可以指定一個比較器,讓這個列表遵照在比較器當中所設定的排序方式進行排序,這就提供了更大的靈活性
public static void sort(List l, Comparatorc)
這個Comparator同樣是一個在java.util包中的接口。這個接口中有兩個方法:int compare(T o1, T o2 )和boolean equals(Object obj)
2)很多常用的,沒必要多講的方法
shuffle(Collection) :對集合進行隨機排序
binarySearch(Collection,Object)方法的使用(含義:查找指定集合中的元素,返回所查找元素的索引)
max(Collection),max(Collection,Comparator)方法的使用(前者採用Collection內含自然比較法,後者採用Comparator進行比較)
min(Collection),min(Collection,Comparator)方法的使用(前者採用Collection內含自然比較法,後者採用Comparator進行比較)。
indexOfSubList(List list,List subList)方法的使用(含義:查找subList在list中首次出現位置的索引)。
lastIndexOfSubList(List source,List target)方法的使用與上例方法的使用相同,在此就不做介紹了。
replaceAll(List list,Object old,Object new)方法的使用(含義:替換批定元素爲某元素,若要替換的值存在剛返回true,反之返回false)。
以及等等等等。
3)我自己看看有哪些方法。 (這一段可以直接參考JAVA API說明 http://www.apihome.cn/api/java/Collections.html )
除了2)中講到的一些零碎的,可以看到還分成了checked , empty , singleton, synchronized unmodifiable這幾類。
checked:2個用途:
返回指定 collection 的一個動態類型安全視圖。試圖插入一個錯誤類型的元素將導致立即拋出 ClassCastException。假設在生成動態類型安全視圖之前,collection 不包含任何類型不正確的元素,並且所有對該 collection 的後續訪問都通過該視圖進行,則可以保證 該 collection 不包含類型不正確的元素。
一般的編程語言機制中都提供了編譯時(靜態)類型檢查,但是一些未經檢查的強制轉換可能會使此機制無效。通常這不是一個問題,因爲編譯器會在所有這類未經檢查的操作上發出警告。但有的時候,只進行單獨的靜態類型檢查並不夠。例如,假設將 collection 傳遞給一個第三方庫,則庫代碼不能通過插入一個錯誤類型的元素來毀壞 collection。
動態類型安全視圖的另一個用途是調試。假設某個程序運行失敗並拋出 ClassCastException,這指示一個類型不正確的元素被放入已參數化 collection 中。不幸的是,該異常可以發生在插入錯誤元素之後的任何時間,因此,這通常只能提供很少或無法提供任何關於問題真正來源的信息。如果問題是可再現的,那麼可以暫時修改程序,使用一個動態類型安全視圖來包裝該 collection,通過這種方式可快速確定問題的來源。
unmodifiable:
返回指定 集合的不可修改視圖。此方法允許模塊爲用戶提供對內部 集合的“只讀”訪問。在返回的 集合 上執行的查詢操作將“讀完”指定的集合。試圖修改返回的集合(不管是直接修改還是通過其迭代器進行修改)將導致拋出 UnsupportedOperationException。
synchronized:
public static <T> Collection<T> synchronizedCollection(Collection<T> c)
- 返回指定 collection 支持的同步(線程安全的)collection。爲了保證按順序訪問,必須通過返回的 collection 完成所有對底層實現 collection 的訪問。
在返回的 collection 上進行迭代時,用戶必須手工在返回的 collection 上進行同步:
Collection c = Collections.synchronizedCollection(myCollection); ... synchronized(c) { Iterator i = c.iterator(); // Must be in the synchronized block while (i.hasNext()) foo(i.next()); }
empty: (以set 爲例,我沒看懂到底是幹嘛的。。)
public static final <T> Set<T> emptySet()
- 返回空的 set(不可變的)。此 set 是可序列化的。與 like-named(找不到關於這個東西的資料。。) 字段不同,此方法是參數化的。
以下示例演示了獲得空 set 的類型安全 (type-safe) 方式:
Set<String> s = Collections.emptySet();
9.Tree, Hash ,Linked
再看看這個圖。
發現set和map的實現分成了 Tree,Hash,和Linked。
以map爲例,來看看這三者的區別.
TreeMap用紅黑樹實現,能夠把它保存的記錄根據鍵排序,默認是按升序排序,也可以指定排序的比較器。當用Iteraor遍歷TreeMap時,得到的記錄是排過序的。TreeMap的鍵和值都不能爲空。
HashMap上文有說。
LinkedHashmap:它繼承與HashMap、底層使用哈希表與雙向鏈表來保存所有元素。其基本操作與父類HashMap相似,它通過重寫父類相關的方法,來實現自己的鏈接列表特性。put方法沒有重寫,重寫了addEntry()。(因爲加入的時候要維護好一個雙向鏈表的結構)LinkedHashMap重寫了父類HashMap的get方法,實際在調用父類getEntry()方法取得查找的元素後,再判斷當排序模式accessOrder爲true時,記錄訪問順序,將最新訪問的元素添加到雙向鏈表的表頭,並從原來的位置刪除。由於的鏈表的增加、刪除操作是常量級的,故並不會帶來性能的損失(accessOrder是LinkedHashmap中的一個屬性,用來判斷是否要根據讀取順序來重寫調整結構。如果爲false,就按照插入的順序排序,否則按照最新訪問的放在鏈表前面的順序,以提高性能)。
我個人的理解是:LinedHashMap的作用就是在讓經常訪問的元素更快的被訪問到。用雙向鏈表可以方便地執行鏈表中元素的插入刪除操作。