Java常見面試題(二)JAVA集合類

JAVA集合類圖:



1. hashmap原理,與hashtable區別

Java中的HashMap是以鍵值對(key-value)的形式存儲元素的。HashMap需要一個hash函數,它使用hashCode()和equals()方法來向集合/從集合添加和檢索元素。當調用put()方法的時候,HashMap會計算key的hash值,然後把鍵值對存儲在集合中合適的索引上。如果key已經存在了,value會被更新成新值。HashMap的一些重要的特性是它的容量(capacity),負載因子(load factor)和擴容極限(threshold resizing)。

附上put的源碼:
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; }

addEntry:

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原理

基於HashMap實現的,HashSet底層使用HashMap來保存所有元素(看了源碼之後我發現就是用hashmap的keyset來保存的),因此HashSet 的實現比較簡單,相關HashSet的操作,基本上都是直接調用底層HashMap的相關方法來完成, 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。

Java代碼  收藏代碼
  1. HashIterator() {  
  2.     expectedModCount = modCount;  
  3.     if (size > 0) { // advance to first entry  
  4.     Entry[] t = table;  
  5.     while (index < t.length && (next = t[index++]) == null)  
  6.         ;  
  7.     }  
  8. }  

 

   在迭代過程中,判斷modCount跟expectedModCount是否相等,如果不相等就表示已經有其他線程修改了Map:

   注意到modCount聲明爲volatile,保證線程之間修改的可見性。

Java代碼  收藏代碼
  1. final Entry<K,V> nextEntry() {     
  2.     if (modCount != expectedModCount)     
  3.         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的作用就是在讓經常訪問的元素更快的被訪問到。用雙向鏈表可以方便地執行鏈表中元素的插入刪除操作。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章