今天復習了一下自認為java 中很重要的一部分集合,這篇文章主要從底層源碼進行分析這幾種集合的區別與聯系,他們的用法不多講,用法不難;大多數東西我也是從各位大神的博客上或者書上扒下來的,小菜鳥在復習,寫下來主要是一:是為了想留下點東西 二:我發現在寫的過程中我可以學到很多東西;
關於java 集合的內容之前在阿里電面的時候問到的我第二個問題就是關於HashTable 線程安全的問題,問題不難但是深度問起來還是挺難回答的。接下來我根據我自己復習的情況簡單寫一篇文章供大家學習,希望多多指教;
這是一張Java集合的總框架圖很清晰明了 圖片來源(http://blog.sina.com.cn/s/blog_51721d970100a37a.html)
迭代器:
在講集合之前我又不得不先看一眼迭代器 collection 和map 都屬於迭代器模式的的一種;
迭代器(iterator)模式 :迭代器(Iterator)模式,又叫做游標(Cursor)模式。GOF給出的定義為:提供一種方法訪問一個容器(container)對象中各個元素,而 又不需暴露該對象的內部細節
在講集合之前我又不得不先看一眼迭代器 collection 和map 都屬於迭代器的一種;
我先根據我自己的理解給大家簡單的講解一下 下邊橙色的是實現類 也是我們最常使用的一種;草綠色的是抽象類上邊藍色的是接口;
一:先講map, Map沒有繼承Collection接口,Map提供key到value的映射。一個Map中不能包含相同的key,每個key只能映射一個value。Map的內容可以被當作一組key集合,一組value集合,或者一組key-value映射。
面試中常問的問題是HashMap 和HashTable 的區別與聯系 在講他們之前 HashCode 需要簡單的了解一下 hashCode方法,這個方法我們平時通常是用不到的,它是為哈希家族的集合類框架(HashMap、HashSet、HashTable)提供服務,hashCode 的常規協定是:
關於HashMap和HashTable 是面試中常問的java 經典問題;
HashMap 幾乎等同於HashTable 但是因為HashTable 使用同步鎖 所以線程安全; 判斷值得存在性如圖1.1
HashMap: 1.線程不安全(因為沒有使用同步鎖(synchronized)),
2.允許value 為null ,
3.HashMap 底層是通過Entry[] 每次新建一個HashMap 就相當於新建一個Entry[];圖1.2實現原理圖
4.調用put方法是首先通過獲取 key的Hashcode 來保證key 的唯一性
5.get,put 方法都通過數組鏈表指針移動等方式 利用hashcode 實現;不詳細講
HashTable:線程安全(使用synchronized),不允許value 為 null; 其他的和HashMap 類似不多講;
圖1.1 圖1.2
二 :關於colletion Collection是最基本的集合接口,一個Collection代表一組Object,即Collection的元素(Elements)。一些Collection允許相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接繼承自Collection的類,Java SDK提供的類都是繼承自Collection的“子接口”如List和Set。
List and set :
1、List,Set都是繼承自Collection接口
2、List特點:元素有放入順序,元素可重復 ,
Set特點:元素無放入順序,元素不可重復(注意:元素雖然無放入順序,但是元素在set中的位置是有該元素的HashCode決 定的,其位置其實是固定的)
3、List接口有三個實現類:LinkedList,ArrayList,Vector ,Set接口有兩個實現類:HashSet(底層由HashMap實現),LinkedHashSet
List:
1. vector 是同步的所以線程安全 提供的方法和arraylist 類似不多講
2.關於ArrayList 和 Linkist 類(自認為是重點 ) 多講一點 線程不安全
ArrayList 優點: 提供了可增長數組的實現;對查詢的調用花費常數的時間;
缺點:新項的插入和現有項的刪除耗費時間較長 線程不安全
而Linklist 與Array List 正好相反 它不容易做索引所以對於查詢 耗費相對多一些但是對於add and remove 方法開銷缺很小;總而言之各有優缺點;(面試常 問)
其導致的原因就是 arraylist 底層用數組的形式實現的list 而 LinkList 底層用底層用雙向循環鏈表 實現的List
接下來就是set 方法了;
set:
HashSet => 1.hashset 底層是用hashmap 實現的(圖 1—1 )
2.hashset 的add方法實際上是將需要添加的對象看做底層維護的map的key值儲存的,而map 的value值都是同一個值;(圖 1—2)
(講到這大家應該明白為什么hashset中不能儲存相同的值了,因為map中鍵值對的形式,鍵值不能相同,所以。。。。)
3.hashset的remove和clear實際上都是調用了map 的方法;更加充分說明了hashset底層以hashmap 為基礎;(圖 1—3)
圖 1—1 圖1—2 圖1-3
下面是關於集合的幾種常見面試題 題目來自於百度經驗
-
集合類特性(幾個常用類的區別)
ArrayList: 元素單個,效率高,多用於查詢 Vector: 元素單個,線程安全,多用於查詢 LinkedList: 元素單個,多用於插入和刪除 HashMap: 元素成對,元素可為空 HashTable: 元素成對,線程安全,元素不可為空
WeakHashMap: 是一種改進的HashMap,它對key實行“弱引用”,如果一個key不再被外部所引用,那么該key可以被GC回收
-
Iterator 是什么
一些集合類提供了內容遍歷的功能,通過java.util.Iterator接口。這些接口允許遍歷對象的集合。依次操作每個元素對象。當使用Iterators時,在獲得Iterator的時候包含一個集合快照。通常在遍歷一個Iterator的時候不建議修改集合本省。
Iterator與ListIterator有什么區別
1. Iterator可用來遍歷Set和List集合,但是ListIterator只能用來遍歷List。
2. Iterator只能正向遍歷集合,適用於獲取移除元素。ListIerator繼承Iterator,可以雙向列表的遍歷,同樣支持元素的修改。比如:增加元素,替換元素,獲取前一個和后一個元素的索引,等等。
-
Collection 和 Collections的區別
Collection是集合類的上級接口,繼承與他的接口主要有Set 和List.
Collections是針對集合類的一個幫助類,他提供一系列靜態方法實現對各種集合的搜索、排序、線程安全化等操作
List、Map、Set三個接口,存取元素時,各有什么特點
List 以特定次序來持有元素,可有重復元素;
Set 無法擁有重復元素,內部排序(無序);
Map 保存key-value值,value可多值。
-
HashMap和Hashtable的區別
都屬於Map接口的類,實現了將惟一鍵映射到特定的值上。
一.歷史原因:
Hashtable是基於陳舊的Dictionary類的,HashMap是Java 1.2引進的Map接口的一個實現
二.同步性:
Hashtable是線程安全的,也就是說是同步的,而HashMap是線程序不安全的,不是同步的
三.值:
HashMap 類沒有分類或者排序。它允許一個 null 鍵和多個 null 值。
Hashtable 類似於 HashMap,但是不允許 null 鍵和 null 值。
四.效率:
Hashtable 比 HashMap 慢,因為它是同步的。
怎樣使HashMap同步
HashMap可以通過Map m = Collections.synchronizedMap(hashMap)來達到同步的效果。
-
ArrayList和Vector的區別
ArrayList與Vector主要從二方面來說.
一.同步性:
Vector是線程安全的,也就是說是同步的,而ArrayList是線程序不安全的,不是同步的。
二.操作:
由於Vector支持多線程操作,所以在性能上就比不上ArrayList了。
三.數據增長:
ArrayList和Vector都有一個初始的容量大小,當存儲進去它們里面的元素個數超出容量的時候,就需要增加ArrayList和Vector的存儲空間,每次增加存儲空間的時候不是只增加一個存儲單元,是增加多個存儲單元。
Vector默認增加原來的一倍,ArrayList默認增加原來的0.5倍。
Vector可以由我們自己來設置增長的大小,ArrayList沒有提供相關的方法。
LinkedList與ArrayList有什么區別
兩者都實現的是List接口,不同之處在於:
(1)、ArrayList是基於動態數組實現的,LinkedList是基於鏈表的數據結構。
(2)、get訪問List內部任意元素時,ArrayList的性能要比LinkedList性能好。LinkedList中的get方法是要按照順序從列表的一端開始檢查,直到另一端
(3)、對於新增和刪除操作LinkedList要強於ArrayList,因為ArrayList要移動數據
附加:
LinkedList實現了List接口,允許null元素。此外LinkedList提供額外的get,remove,insert方法在LinkedList的首部或尾部。這些操作使LinkedList可被用作堆棧(stack),隊列(queue)或雙向隊列(deque)。
注意LinkedList沒有同步方法。如果多個線程同時訪問一個List,則必須自己實現訪問同步。一種解決方法是在創建List時構造一個同步的List:
List list = Collections.synchronizedList(new LinkedList(…));
-
數組(Array)和列表集合(ArrayList)有什么區別
下面列出了Array和ArrayList的不同點:
Array可以包含基本類型和對象類型,ArrayList只能包含對象類型。
Array大小是固定的,ArrayList的大小是動態變化的。
ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator() 等等。
對於基本類型數據,集合使用自動裝箱來減少編碼工作量。但是,當處理固定大小的基本數據類型的時候,這種方式相對比較慢。
Enumeration接口和Iterator接口的區別有哪些
Enumeration速度是Iterator的2倍,同時占用更少的內存。但是,Iterator遠遠比Enumeration安全,因為其他線程不能夠修改正在被iterator遍歷的集合里面的對象。同時,Iterator允許調用者刪除底層集合里面的元素,這對Enumeration來說是不可能的。
-
HashSet和TreeSet有什么區別
HashSet有以下特點:
A. 無序(不能保證元素的排列順序,順序有可能發生變化)B. 不同步C. 允許空值(集合元素可以是null,可以放入多個null,但會自動覆蓋)
當向HashSet結合中存入一個元素時,HashSet會調用該對象的hashCode()方法來得到該對象的hashCode值,然后根據 hashCode值來決定該對象在HashSet中存儲位置。簡單的說,HashSet集合判斷兩個元素相等的標准是兩個對象通過equals方法比較相等,並且兩個對象的hashCode()方法返回值相 等注意,如果要把一個對象放入HashSet中,重寫該對象對應類的equals方法,也應該重寫其hashCode()方法。其規則是如果兩個對象通過equals方法比較返回true時,其hashCode也應該相同。另外,對象中用作equals比較標准的屬性,都應該用來計算 hashCode的值。
TreeSet有以下特點:
A. 有序
1. TreeSet是由一個樹形的結構來實現的(數據結構是二叉樹),它里面元 素是有序的
2.TreeSet是SortedSet接口的唯一實現類,TreeSet可以確保集合元素處於 排序狀 態,支持兩種排序方式,自然排序和定制排序。其中自然排序為 默認的排序方式;定制排序,TreeSet中的對象元素需要實Comparable 接口
TreeSet類中跟HashSet類一樣也沒有get()方法來獲取列表中的元素,所以 也只能通過迭代器方法來獲取
二叉樹:
B. 不允許空值
1. HashSet是通過HashMap實現的,TreeSet是通過TreeMap實現的,只不過 Set用的只是Map的key
2. Map的key和Set都有一個共同的特性就是集合的唯一性.TreeMap更是 多 了一個有序性.
3. TreeSet類中跟HashSet類一樣也沒有get()方法來獲取列表中的元素,所 以也只能通過迭代器方法來獲取
4. HashSet是基於hash算法實現的,性能優於TreeSet,通常使用 HashSet。在我們需要對其中元素排序的時候才使用TreeSet。
-
HashMap,LinkedMap,TreeMap的區別
HashMap,LinkedHashMap,TreeMap都屬於Map。
LinkedHashMap是HashMap的子類。
Map 主要用於存儲鍵(key)值(value)對,根據鍵得到值,因此鍵不允許鍵重復,但允許值重復
1.HashMap的內部結構是一個數組,線性順序存儲,二次結構使用線性的單鏈表。使用key的hashCode做二次hash之后,再截取小於數組長度的值為索引值。key可以為null,存在索引為0的位置上。由於使用了數組,所以有一個負載因子loadFactor的概念(臨界閾值threshold)和resize。resize比較耗時,沖突時鏈式遍歷查找也比較耗時,所以選定一個合適的初始容易比較重要。存取性能都較高。迭代遍歷時一維使用數組,二維使用鏈表。
HashMap 是一個最常用的Map,它根據鍵的HashCode 值存儲數據,根據鍵可以直 接獲取它的值,具有很快的訪問速度。HashMap最多只允許一條記錄的 鍵為Null;允許多條記錄的值為 Null;HashMap不支持線程的同步,即任 一 時刻可以有多個線程同時寫HashMap;可能會導致數據的不一致。如 果需要同步,可以用 Collections的synchronizedMap方法使HashMap 具有同步的能力。
2.LinkedHashMap是HashMap的子類。內部結構是一個數組,線性順序存儲,二次結構使用線性的單鏈表,但同時內部維護了一個雙向循環鏈表,可以保持順序。access-order=false默認為使用新增存儲順序,access-order=true則指定使用讀取和訪問順序。removeEldestEntry=false(當指定為true時,就是實現LRU算法的緩存容器,當然要指定淘汰時的使用頻率和容量上限,其實是一個最近最少使用-->最近使用access-order=true/最新存儲access-order=false)。存取性能較HashMap差-些,但相差不大。header.after為尾方向,header.before為首方向。迭代遍歷時entrySet().iterator()跟HashMap一樣(有點困惑,為什么不按線性順序進行迭代,只能重寫entrySet(),keySet()和values()方法)。適用於有緩存設計需求的情況(需繼承)。 3.TreeMap的內部結構是一棵紅黑樹(又叫排序數,是二叉樹的一種),使用鏈式存儲,可以指定比較器Comparator,key需實現Comparable接口。key不能為null。存結點性能稍差,因為需要調整樹結構;取結點用的是鏈表遍歷,但是屬於有序比較,性能中等。迭代遍歷時用的樹的中序遍歷,是一個有序序列。適用於有排序需求的情況。
下面是我測試用的簡單的代碼 發給大家吧:
/*ArrayList */ public static void Array (){ ArrayList<String> arrayList = new ArrayList(); /*arrlist 添加的方法 */ arrayList.add("你好"); arrayList.add("我好"); arrayList.add("他好"); arrayList.add("大家好"); arrayList.add("真的好"); //查詢size int size = arrayList.size(); //查詢特定的元素是否存在 boolean has = arrayList.contains("a"); //是否為空 boolean isEmpty = arrayList.isEmpty(); //根據下標刪除特定的元素 arrayList.remove(1); //根據內容刪除特定元素 arrayList.remove("a"); //刪除全部元素 arrayList.removeAll(arrayList); } public static void Link (){ LinkedList<String> linkedList = new LinkedList(); /*linklist 添加的方法 */ linkedList.add("你好"); linkedList.add("我好"); linkedList.add("他好"); linkedList.add("大家好"); linkedList.add("真的好"); //查詢size int size = linkedList.size(); //查詢特定的元素是否存在 boolean has = linkedList.contains("a"); //是否為空 boolean isEmpty = linkedList.isEmpty(); //根據下標刪除特定的元素 linkedList.remove(1); //根據內容刪除特定元素 linkedList.remove("a"); //刪除全部元素 linkedList.removeAll(linkedList); /* 這是比arraylist 多提供的一些方法 不多解釋一看就懂*/ linkedList.getFirst(); linkedList.getLast(); linkedList.removeFirst(); linkedList.removeLast(); } /*Treeset 以及利用構造器進行數據排序*/ public static void tree(){ TreeSet treeSet = new TreeSet(); treeSet.add("A"); treeSet.add("B"); treeSet.add("D"); treeSet.add("C"); for(Iterator iterator= treeSet.iterator();iterator.hasNext();){ System.out.println((String)iterator.next()); } } /*HashSet */ public static void hash(){ HashSet hashSet = new HashSet(); hashSet.add("A"); hashSet.add("B"); hashSet.add("D"); hashSet.add("C"); /*利用迭代器的方法 排序輸出*/ for(Iterator iterator= hashSet.iterator();iterator.hasNext();){ System.out.println((String)iterator.next()); } } /*Hash Map */ public static void hashmap(){ HashMap hashMap = new HashMap(); hashMap.put("a","A"); hashMap.put("b","A"); hashMap.put("c","A"); hashMap.put("d","A"); hashMap.put("e",null); /*判斷某個值得存在性*/ hashMap.containsKey("a"); hashMap.containsValue("A"); System.out.println(hashMap.get("e")); } /*HashTable*/ public static void hashtable(){ Hashtable hashtable = new Hashtable(); hashtable.put("a","A"); hashtable.put("b","A"); hashtable.put("c","A"); hashtable.put("d","A"); /*判斷某個值得存在性 */ hashtable.containsKey("a"); hashtable.containsValue("A"); hashtable.contains("b"); System.out.println(hashtable.get("e")); } public static void main(String[] atgs){ tree(); hashmap(); hashtable(); }