java 集合詳解及如何應用
1、結構圖
2、集合對比說明
有序 |
允許元素重複 |
同步 |
描述 | 備註 |
實現類 |
|
Iterable(A) |
|
|
|
|
|
|
Collection |
|
|
|
|
||
List |
是 | 是 |
|
和陣列類似,List可以動態增長,查詢元素效率高,插入刪除元素效率低,因為會引起其他元素位置改變 |
|
|
ArrayList |
是 |
|
否 |
基於Array的List,動態陣列,根據索引排序 |
|
|
LinkedList |
是 |
|
否 |
插入的順序;可被用作堆疊(stack),佇列(queue)或雙向佇列(deque)。 |
構造一個同步的List:List list=Collections.synchronizedList(new LinkedList(...)); |
|
Vector |
是 |
|
是 | 基於Array的List,一種老的動態陣列,是執行緒同步的,效率很低,一般不贊成使用 |
|
|
Stack |
是 |
|
是 |
陣列結構,繼承vector,後進先出的堆疊 |
|
|
Set |
否 | 否 |
|
檢索元素效率低下,刪除和插入效率高,插入和刪除不會引起元素位置改變。 |
|
|
HashSet |
否 |
|
否 |
以雜湊表的形式存放元素,插入刪除速度很快 |
|
|
TreeSet |
是 |
|
否 |
二叉樹排序,升序排列 |
|
|
LinkedHashSet |
是 |
|
否 |
具有HashSet的查詢速度,且內部使用連結串列維護元素的順序(插入的次序) |
|
|
Map |
是 |
|
|
使用key-value來對映和儲存資料,Key必須惟一,value可以重複; |
|
|
HashMap |
否 |
|
否 |
它根據鍵的HashCode 值儲存資料,根據鍵可以直接獲取它的值,具有很快的訪問速度。HashMap最多隻允許一條記錄的鍵為Null;允許多條記錄的值為 Null |
構造一個同步的Map:Map map=Collections.synchronizedMap(new HashMap(...)); |
|
Hashtable |
否 |
|
是 |
不允許記錄的鍵或者值為空;它支援執行緒的同步,即任一時刻只有一個執行緒能寫Hashtable,因此也導致了Hashtale在寫入時會比較慢。 |
|
|
LinkedHashMap |
是 |
|
否 |
儲存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先得到的記錄肯定是先插入的.在遍歷 的時候會比HashMap慢。 |
|
|
TreeMap |
是 |
|
否 |
儲存的記錄根據鍵排序,預設是按升序排序,也可以指定排序的比較器,當用Iterator 遍歷TreeMap時,得到的記錄是排過序的。 |
|
|
*ps:雜湊表是基於陣列、連結串列的 雜湊表(Hash table,也叫散列表)
2、對LinkedList進行插入和刪除操作具有較快的速度
1、對Java陣列進行隨機訪問和迭代操作具有最快的速度
3、對ArrayList進行隨機訪問具有較快的速度
4、Vector沒有突出的效能,已不提倡使用
3、集合類詳細說明
參考 Java集合詳解: http://www.cnblogs.com/eflylab/archive/2007/01/19/625086.html
1.理解集合類
集合類存放於java.util包中。
集合類存放的都是物件的引用,而非物件本身,出於表達上的便利,我們稱集合中的物件就是指集合中物件的引用(reference)。
集合型別主要有3種:set(集)、list(列表)和map(對映)。
Collection<--List<--Vector
Collection<--List<--ArrayList
Collection<--List<--LinkedList
Collection<--Set<--HashSet
Collection<--Set<--HashSet<--LinkedHashSet
Collection<--Set<--SortedSet<--TreeSet
Map<--HashMap
Map<--HashMap<--LinkedHashMap
Map<--SortedMap
Map<--SortedMap<--TreeMap
(1)集
集(set)是最簡單的一種集合,它的物件不按特定方式排序,只是簡單的把物件加入集合中,就像往口袋裡放東西。
對集中成員的訪問和操作是通過集中物件的引用進行的,所以集中不能有重複物件。
集也有多種變體,可以實現排序等功能,如TreeSet,它把物件新增到集中的操作將變為按照某種比較規則將其插入到有序的物件序列中。它實現的是SortedSet介面,也就是加入了物件比較的方法。通過對集中的物件迭代,我們可以得到一個升序的物件集合。
(2)列表
列表的主要特徵是其物件以線性方式儲存,沒有特定順序,只有一個開頭和一個結尾,當然,它與根本沒有順序的集是不同的。
列表在資料結構中分別表現為:陣列和向量、連結串列、堆疊、佇列。
關於實現列表的集合類,是我們日常工作中經常用到的,將在後邊的筆記詳細介紹。
(3)對映
對映與集或列表有明顯區別,對映中每個項都是成對的。對映中儲存的每個物件都有一個相關的關鍵字(Key)物件,關鍵字決定了物件在對映中的儲存位置,檢索物件時必須提供相應的關鍵字,就像在字典中查單詞一樣。關鍵字應該是唯一的。
關鍵字本身並不能決定物件的儲存位置,它需要對過一種雜湊(hashing)技術來處理,產生一個被稱作雜湊碼(hash code)的整數值,
雜湊碼通常用作一個偏置量,該偏置量是相對於分配給對映的記憶體區域起始位置的,由此確定關鍵字/物件對的儲存位置。理想情況下,雜湊處理應該產生給定範圍內均勻分佈的值,而且每個關鍵字應得到不同的雜湊碼。
2.List介面
List是有序的Collection,使用此介面能夠精確的控制每個元素插入的位置。使用者能夠使用索引(元素在List中的位置,類似於陣列下標)來訪問List中的元素,這類似於Java的陣列。
和下面要提到的Set不同,List允許有相同的元素。
除了具有Collection介面必備的iterator()方法外,List還提供一個listIterator()方法,返回一個 ListIterator介面,和標準的Iterator介面相比,ListIterator多了一些add()之類的方法,允許新增,刪除,設定元素,還能向前或向後遍歷。
List總結:
1. 所有的List中只能容納單個不同型別的物件組成的表,而不是Key-Value鍵值對。例如:[ tom,1,c ];
2. 所有的List中可以有相同的元素,例如Vector中可以有 [ tom,koo,too,koo ];
3. 所有的List中可以有null元素,例如[ tom,null,1 ];
4. 基於Array的List(Vector,ArrayList)適合查詢,而LinkedList(連結串列)適合新增,刪除操作。
2.1.Vector類
基於Array的List,其實就是封裝了Array所不具備的一些功能方便我們使用,它不可能不受Array的限制。效能也就不可能超越Array。所以,在可能的情況下,我們要多運用Array。另外很重要的一點就是Vector:sychronized”的,這個也是Vector和ArrayList的唯一的區別。
實現一個類似陣列一樣的表,自動增加容量來容納你所需的元素。使用下標儲存和檢索物件就象在一個標準的陣列中一樣。你也可以用一個迭代器從一個Vector中檢索物件。Vector是唯一的同步容器類!!當兩個或多個執行緒同時訪問時也是效能良好的。
2.2.ArrayList類
同Vector一樣是一個基於Array上的連結串列,但是不同的是ArrayList不是同步的。所以在效能上要比Vector優越一些,但是當執行到多執行緒環境中時,可需要自己在管理執行緒的同步問題。
擴充套件閱讀:在java.util.concurrent包中定義的CopyOnWriteArrayList提供了執行緒安全的Arraylist,但是當進行add和set等變化操作時它是通過為底層陣列建立新的副本實現的,所以比較耗費資源
(原始碼在此:
publicboolean add(E e) {
final ReentrantLock lock =this.lock;lock.lock();try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements,len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}}),
但是如果存在頻繁遍歷,遍歷操作比變化(寫入和修改)操作多的時候這種遍歷就相對於自己進行的同步遍歷效果要好,而且它也允許存在null元素)
2.1.LinkedList類
LinkedList不同於前面兩種List,它不是基於Array的,所以不受Array效能的限制。它每一個節點(Node)都包含兩方面的內容:1.節點本身的資料(data);2.下一個節點的資訊(nextNode)。所以當對LinkedList做新增,刪除動作的時候就不用像基於Array的List一樣,必須進行大量的資料移動。只要更改nextNode的相關資訊就可以實現了。這就是LinkedList的優勢。LinkedList提供額外的get,remove,insert方法在 LinkedList的首部或尾部。這些操作使LinkedList可被用作堆疊(stack),佇列(queue)或雙向佇列(deque)。注意LinkedList沒有同步方法。
如果多個執行緒同時訪問一個List,則必須自己實現訪問同步。一種解決方法是在建立List時構造一個同步的List:
List list=Collections.synchronizedList(new LinkedList(...));
2.4.Stack類
Stack繼承自Vector,實現一個後進先出的堆疊。Stack提供5個額外的方法使得Vector得以被當作堆疊使用。基本的push和pop方法,還有peek方法得到棧頂的元素,empty方法測試堆疊是否為空,search方法檢測一個元素在堆疊中的位置。Stack剛建立後是空棧。
3. set介面
雖然Set同List都實現了Collection介面,但是他們的實現方式卻大不一樣。List基本上都是以Array為基礎。但是Set則是在HashMap的基礎上來實現的,這個就是Set和List的根本區別。
1. Set實現的基礎是Map(HashMap);
2. Set中的元素是不能重複的,如果使用add(Object obj)方法新增已經存在的物件,則會覆蓋前面的物件
3.1HashSet :
HashSet的儲存方式是把HashMap中的Key作為Set的對應儲存項。看看HashSet的add(Object obj)方法的實現就可以一目瞭然了。
public boolean add(Object obj)
{
return map.put(obj, PRESENT) == null;
}這個也是為什麼在Set中不能像在List中一樣有重複的項的根本原因,因為HashMap的key是不能有重複的。
3.2LinkedHashSet: 具有HashSet的查詢速度,且內部使用連結串列維護元素的順序(插入的次序,不含有重複元素)。於是在使用迭代器遍歷Set時,結果會按元素插入的次序顯示。HashSet的一個子類,一個連結串列。 3.3TreeSet:SortedSet的子類,它不同於HashSet的根本就是TreeSet是有序的(升序)。它是通過TreeMap來實現的。
4.Map的功能方法
java為資料結構中的對映定義了一個介面java.util.Map;它有四個實現類,分別是HashMap Hashtable LinkedHashMap 和TreeMap
HashTable: 實現一個映象,所有的鍵必須非空。為了能高效的工作,定義鍵的類必須實現hashcode()方法和equal()方法。這個類是前面java實現的一個繼承,並且通常能在實現映象的其他類中更好的使用。
HashMap: 實現一個映象,允許儲存空物件,而且允許鍵是空(由於鍵必須是唯一的,當然只能有一個)。
LinkedHashMap:儲存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先得到的記錄肯定是先插入的.在遍歷 的時候會比HashMap慢。
TreeMap: 實現這樣一個映象,物件是按鍵升序排列的。TreeMap能夠把它儲存的記錄根據鍵排序,預設是按升序排序,也可以指定排序的比較器,當用Iterator 遍歷TreeMap時,得到的記錄是排過序的。
在JDK1.5中引入 ConcurrentHashMap
ConcurrentHashMap具體是怎麼實現執行緒安全的呢,肯定不可能是每個方法加synchronized,那樣就變成了HashTable。
從ConcurrentHashMap程式碼中可以看出,它引入了一個“分段鎖”的概念,具體可以理解為把一個大的Map拆分成N個小的HashTable(就是段Segment),根據key.hashCode()來決定把key放到哪個HashTable中。
4、工具類
Collections是針對集合類的一個幫助類。提供了一系列靜態方法實現對各種集合的搜尋、排序、執行緒完全化等操作。
相當於對Array進行類似操作的類——Arrays。
如:
Collections.max(Collection coll); 取coll中最大的元素。
Collections.sort(List list); 對list中元素排序
5、如何選擇集合?
* 在各種Lists中,最好的做法是以ArrayList作為預設選擇。當插入、刪除頻繁時,使用LinkedList();Vector總是比ArrayList慢,所以要儘量避免使用。
* 在各種Sets中,HashSet通常優於TreeSet(插入、查詢)。只有當需要產生一個經過排序的序列,才用TreeSet。TreeSet存在的唯一理由:能夠維護其內元素的排序狀態。
* 在各種Maps中HashMap用於快速查詢。
* 當元素個數固定,用Array,因為Array效率是最高的。
結論:最常用的是ArrayList,HashSet,HashMap,Array。而且,我們也會發現一個規律,用TreeXXX都是排序的。
注意:
1、Collection沒有get()方法來取得某個元素。只能通過iterator()遍歷元素。
5、Map用 put(k,v) / get(k),還可以使用containsKey()/containsValue()來檢查其中是否含有某個key/value。HashMap會利用物件的hashCode來快速找到key。
* hashing
雜湊碼就是將物件的資訊經過一些轉變形成一個獨一無二的int值,???,這個值儲存在一個array中。
我們都知道所有儲存結構中,array查詢速度是最快的。所以,可以加速查詢。
發生碰撞時,讓array指向多個values。即,陣列每個位置上又生成一個槤表。
6、Map中元素,可以將key序列、value序列單獨抽取出來。
使用keySet()抽取key序列,將map中的所有keys生成一個Set。
使用values()抽取value序列,將map中的所有values生成一個Collection。
為什麼一個生成Set,一個生成Collection?那是因為,key總是獨一無二的,value允許重複。
7、Vector 還是ArrayList,哪一個更好,為什麼?
要回答這個問題不能一概而論,有時候使用Vector比較好;有時是ArrayList,有時候這兩個都不是最好的選擇。你別指望能夠獲得一個簡單肯定答案,因為這要看你用它們幹什麼。下面有4個要考慮的因素:
(1)API
(2)同步處理
(3)資料增長性
(4)使用模式
下面針對這4個方面進行一一探討
API
在由Ken Arnold等編著的《Java Programming Language》(Addison-Wesley, June 2000)一書中有這樣的描述,Vector類似於ArrayList.。所有從API的角度來看這兩個類非常相似。但他們之間也還是有一些主要的區別的。同步性
Vector是同步的。這個類中的一些方法保證了Vector中的物件是執行緒安全的。而ArrayList則是非同步的,因此ArrayList中的物件並不是執行緒安全的。因為同步的要求會影響執行的效率,所以如果你不需要執行緒安全的集合那麼使用ArrayList是一個很好的選擇,這樣可以避免由於同步帶來的不必要的效能開銷。
資料增長
從內部實現機制來講ArrayList和Vector都是使用陣列(Array)來控制集合中的物件。當你向這兩種型別中增加元素的時候,如果元素的數目超出了內部陣列目前的長度它們都需要擴充套件內部陣列的長度,Vector預設情況下自動增長原來一倍的陣列長度,ArrayList是原來的50%,所以最後你獲得的這個集合所佔的空間總是比你實際需要的要大。所以如果你要在集合中儲存大量的資料那麼使用Vector有一些優勢,因為你可以通過設定集合的初始化大小來避免不必要的資源開銷。
使用模式
在ArrayList和Vector中,從一個指定的位置(通過索引)查詢資料或是在集合的末尾增加、移除一個元素所花費的時間是一樣的,這個時間我們用O(1)表示。但是,如果在集合的其他位置增加或移除元素那麼花費的時間會呈線形增長:O(n-i),其中n代表集合中元素的個數,i代表元素增加或移除元素的索引位置。為什麼會這樣呢?以為在進行上述操作的時候集合中第i和第i個元素之後的所有元素都要執行位移的操作。這一切意味著什麼呢?
這意味著,你只是查詢特定位置的元素或只在集合的末端增加、移除元素,那麼使用Vector或ArrayList都可以。如果是其他操作,你最好選擇其他的集合操作類。
比如,LinkList集合類在增加或移除集合中任何位置的元素所花費的時間都是一樣的—O(1),但它在索引一個元素的使用卻比較慢-O(i),其中i是索引的位置.使用ArrayList也很容易,因為你可以簡單的使用索引來代替建立iterator物件的操作。LinkList也會為每個插入的元素建立物件,所以你要明白它也會帶來額外的開銷。Q:Comparable和Comparator區別
A:呼叫java.util.Collections.sort(List list)方法來進行排序的時候,List內的Object都必須實現了Comparable介面。
java.util.Collections.sort(List list,Comparator c),可以臨時宣告一個Comparator 來實現排序。
Collections.sort(imageList, new Comparator() {
public int compare(Object a, Object b) {
int orderA = Integer.parseInt( ( (Image) a).getSequence());
int orderB = Integer.parseInt( ( (Image) b).getSequence());
return orderA - orderB;
}
});
如果需要改變排列順序
改成return orderb - orderA 即可。
4.1 equals()相等的兩個物件,hashcode()一定相等,equals()不相等的兩個物件,卻並不能證明他們的hashcode()不相等。換句話說,equals()方法不相等的兩個物件,hashCode()有可能相等。
4.2 Java中的hashCode方法就是根據一定的規則將與物件相關的資訊(比如物件的儲存地址,物件的欄位等)對映成一個數值,這個數值稱作為雜湊值
ps1:HashSet底層是通過HashMap實現的,看如下的建構函式,構造HashSet的時候底層就構造了一個HashMap;hashSet 和 hashMap 是不重複的,對比物件是否相等時,先對比hashcode值(hashcode()效率高),如果雜湊值相等,再呼叫equals方法。object 的equals方法預設呼叫“==”對比的是兩個物件的記憶體地址
ps2:(有問題)hashCode是所有java物件的固有方法,如果不過載的話,返回的實際上是該物件在jvm的堆上的記憶體地址,而不同物件的記憶體地址肯定不同,所以這個hashCode也就肯定不同了。如果過載了的話,由於採用的演算法的問題,有可能導致兩個不同物件的hashCode相同。
6、示例(map 遍歷)
附:map 遍歷的四種方法:
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("1", "value1");
map.put("2", "value2");
System.out.println("通過Map.keySet遍歷key和value:");
map.put("3", "value3"); //第一種:普遍使用,二次取值
System.out.println("key= "+ key + " and value= " + map.get(key));
for (String key : map.keySet()) { } //第二種 效率也可以
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
System.out.println("通過Map.entrySet使用iterator遍歷key和value:"); while (it.hasNext()) {
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
Map.Entry<String, String> entry = it.next(); } //第三種:推薦,尤其是容量大時 System.out.println("通過Map.entrySet遍歷key和value");
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
for (Map.Entry<String, String> entry : map.entrySet()) { } //第四種 System.out.println("通過Map.values()遍歷所有的value,但不能遍歷key"); for (String v : map.values()) {
}
System.out.println("value= " + v);
}
示例:MapTest
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
public static void init(Map map) {
public class MapTester {
String key = null;
if (map != null) {
key = new Integer(i).toString() + ".0";
for (int i = 5; i > 0; i--) { map.put(key, key.toString()); // Map中的鍵是不重複的,如果插入兩個鍵值一樣的記錄,
public static void output(Map map) {
// 那麼後插入的記錄會覆蓋先插入的記錄 map.put(key, key.toString() + "0"); } } } if (map != null) { Object key = null;
key = it.next();
Object value = null; // 使用迭代器遍歷Map的鍵,根據鍵取值 Iterator it = map.keySet().iterator(); while (it.hasNext()) { value = map.get(key);
it = map.entrySet().iterator();
System.out.println("key: " + key + "; value: " + value); } // 或者使用迭代器遍歷Map的記錄Map.Entry Map.Entry entry = null; while (it.hasNext()) { // 一個Map.Entry代表一條記錄
public static boolean containsKey(Map map, Object key) {
entry = (Map.Entry) it.next(); // 通過entry可以獲得記錄的鍵和值 // System.out.println("key: " + entry.getKey() + "; value: " + // entry.getValue()); } } } if (map != null) {
public static void testHashMap() {
return map.containsKey(key); } return false; } public static boolean containsValue(Map map, Object value) { if (map != null) { return map.containsValue(value); } return false; }
Map myMap = new Hashtable();
Map myMap = new HashMap(); init(myMap); // HashMap的鍵可以為null myMap.put(null, "ddd"); // HashMap的值可以為null myMap.put("aaa", null); output(myMap); } public static void testHashtable() { init(myMap);
// LinkedHashMap的鍵可以為null
// Hashtable的鍵不能為null // myMap.put(null,"ddd"); // Hashtable的值不能為null // myMap.put("aaa", null); output(myMap); } public static void testLinkedHashMap() { Map myMap = new LinkedHashMap(); init(myMap);
// TreeMap的值不能為null
myMap.put(null, "ddd"); // LinkedHashMap的值可以為null myMap.put("aaa", null); output(myMap); } public static void testTreeMap() { Map myMap = new TreeMap(); init(myMap); // TreeMap的鍵不能為null // myMap.put(null,"ddd");
System.out.println("採用LinkedHashMap");
// myMap.put("aaa", null); output(myMap); } public static void main(String[] args) { System.out.println("採用HashMap"); MapTester.testHashMap(); System.out.println("採用Hashtable"); MapTester.testHashtable(); MapTester.testLinkedHashMap();
System.out.println("將myMap clear後,myMap空了麼? " + myMap.isEmpty());
System.out.println("採用TreeMap"); MapTester.testTreeMap(); Map myMap = new HashMap(); MapTester.init(myMap); System.out.println("新初始化一個Map: myMap"); MapTester.output(myMap); // 清空Map myMap.clear(); MapTester.output(myMap); myMap.put("aaa", "aaaa");
System.out.println("刪除鍵aaa後,myMap包含鍵aaa? "
myMap.put("bbb", "bbbb"); // 判斷Map是否包含某鍵或者某值 System.out.println("myMap包含鍵aaa? " + MapTester.containsKey(myMap, "aaa")); System.out.println("myMap包含值aaaa? " + MapTester.containsValue(myMap, "aaaa")); // 根據鍵刪除Map中的記錄 myMap.remove("aaa");
}
+ MapTester.containsKey(myMap, "aaa")); // 獲取Map的記錄數 System.out.println("myMap包含的記錄數: " + myMap.size());