前言 在 "上一篇" 中回顧了Java的三大特性:封裝、繼承和多態。本篇則來介紹下集合。 集合介紹 我們在進行Java程式開發的時候,除了最常用的基礎數據類型和String對象外,也經常會用到集合相關類。 集合類存放的都是對象的引用,而非對象本身,出於表達上的便利,我們稱集合中的對象就是指集合中對象 ...
前言
在上一篇中回顧了Java的三大特性:封裝、繼承和多態。本篇則來介紹下集合。
集合介紹
我們在進行Java程式開發的時候,除了最常用的基礎數據類型和String對象外,也經常會用到集合相關類。
集合類存放的都是對象的引用,而非對象本身,出於表達上的便利,我們稱集合中的對象就是指集合中對象的引用。
集合類型主要有3種:List、Set、和Map。
它們之間的關係可用下圖來表示:
註:Map不是collections的子類,但是它們完全整合在集合中了!
List
List 介面是繼承於 Collection介面並定義 一個允許重覆項的有序集合。該介面不但能夠對列表的一部分進行處理,還添加了面向位置的操作。
一般來說,我們在單線程中主要使用的List是ArrayList和LinkedList來實現,多線程則是使用Vector或者使用Collections.sychronizedList來裝飾一個集合。
這三個的解釋如下:
- ArrayList:內部是通過數組實現的,它允許對元素進行快隨機訪問。當從ArrayList的中間位置插入或者刪除元素時,需要對數組進行複製、移動、代價比較高。因此,它適合隨機查找和遍歷,不適合插入和刪除。
- LinkedList: 則是鏈表結構存儲數據的,很適合數據的動態插入和刪除,隨機訪問和遍歷速度比較慢。另外,他還提供了List介面中沒有定義的方法,專門用於操作表頭和表尾元素,可以當作堆棧、隊列和雙向隊列使用。
- Vector: 通過數組實現的,不同的是它支持線程的同步。訪問速度ArrayList慢。
它們的用法如下:
List list1 = new ArrayList();
List list2 = new LinkedList();
List list3 = new Vector();
List list4=Collections.synchronizedList(new ArrayList())
在瞭解了它們的用法之後,我們來看看為什麼說使用ArrayList比LinkedList查詢快,使用LinkedList比ArrayList新增和刪除快!
這裡也用以下代碼來進行說明,順便也將Vector進行比較。
代碼示例:
private final static int count=50000;
private static ArrayList arrayList = new ArrayList<>();
private static LinkedList linkedList = new LinkedList<>();
private static Vector vector = new Vector<>();
public static void main(String[] args) {
insertList(arrayList);
insertList(linkedList);
insertList(vector);
System.out.println("--------------------");
readList(arrayList);
readList(linkedList);
readList(vector);
System.out.println("--------------------");
delList(arrayList);
delList(linkedList);
delList(vector);
}
private static void insertList(List list){
long start=System.currentTimeMillis();
Object o = new Object();
for(int i=0;i<count;i++){
list.add(0, o);
}
System.out.println(getName(list)+"插入"+count+"條數據,耗時:"+(System.currentTimeMillis()-start)+"ms");
}
private static void readList(List list){
long start=System.currentTimeMillis();
Object o = new Object();
for(int i = 0 ; i < count ; i++){
list.get(i);
}
System.out.println(getName(list)+"查詢"+count+"條數據,耗時:"+(System.currentTimeMillis()-start)+"ms");
}
private static void delList(List list){
long start=System.currentTimeMillis();
Object o = new Object();
for(int i = 0 ; i < count ; i++){
list.remove(0);
}
System.out.println(getName(list)+"刪除"+count+"條數據,耗時:"+(System.currentTimeMillis()-start)+"ms");
}
private static String getName(List list) {
String name = "";
if(list instanceof ArrayList){
name = "ArrayList";
}
else if(list instanceof LinkedList){
name = "LinkedList";
}
else if(list instanceof Vector){
name = "Vector";
}
return name;
}
輸出結果:
ArrayList插入50000條數據,耗時:281ms
LinkedList插入50000條數據,耗時:2ms
Vector插入50000條數據,耗時:274ms
--------------------
ArrayList查詢50000條數據,耗時:1ms
LinkedList查詢50000條數據,耗時:1060ms
Vector查詢50000條數據,耗時:2ms
--------------------
ArrayList刪除50000條數據,耗時:143ms
LinkedList刪除50000條數據,耗時:1ms
Vector刪除50000條數據,耗時:137ms
從上述結果中,可以明顯看出ArrayList和LinkedList在新增、刪除和查詢性能上的區別。
在集合中,我們一般用於存儲數據。不過有時在有多個集合的時候,我們想將這幾個集合做合集、交集、差集和並集的操作。在List中,這些方法已經封裝好了,我們無需在進行編寫相應的代碼,直接拿來使用就行。
代碼示例如下:
/**
* 合集
* @param ls1
* @param ls2
* @return
*/
private static List<String> addAll(List<String> ls1,List<String>ls2){
ls1.addAll(ls2);
return ls1;
}
/**
* 交集 (retainAll 會刪除 ls1在ls2中沒有的元素)
* @param ls1
* @param ls2
* @return
*/
private static List<String> retainAll(List<String> ls1,List<String>ls2){
ls1.retainAll(ls2);
return ls1;
}
/**
* 差集 (刪除ls2中沒有ls1中的元素)
* @param ls1
* @param ls2
* @return
*/
private static List<String> removeAll(List<String> ls1,List<String>ls2){
ls1.removeAll(ls2);
return ls1;
}
/**
* 無重覆的並集 (ls1和ls2中並集,並無重覆)
* @param ls1
* @param ls2
* @return
*/
private static List<String> andAll(List<String> ls1,List<String>ls2){
//刪除在ls1中出現的元素
ls2.removeAll(ls1);
//將剩餘的ls2中的元素添加到ls1中
ls1.addAll(ls2);
return ls1;
}
當然,經常用到的還有對List進行遍歷。
List數組遍歷主要有這三種方法,普通的for迴圈,增強for迴圈(jdk1.5之後出現),和Iterator(迭代器)。
代碼示例:
List<String> list=new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
for (String str : list) {
System.out.println(str);
}
Iterator<String> iterator=list.iterator();
while(iterator.hasNext())
{
System.out.println(iterator.next());
}
說明:普通的for迴圈和增強for迴圈區別不大,主要區別在於普通的for迴圈可以獲取集合的下標,而增強for迴圈則不可以。但增強for迴圈寫起來方法,如果不需要獲取具體集合的下標,推薦使用增強for迴圈。至於Iterator(迭代器)這種也是無法獲取數據下標,但是該方法可以不用擔心在遍歷的過程中會集合的長度發生改變。也就是在遍歷的時候對集合進行增加和刪除。
在<阿裡巴巴Java開發手冊>中,對於集合操作也有這種說明。
不要在 foreach 迴圈里進行元素的 remove / add 操作。 remove 元素請使用Iterator方式,如果併發操作,需要對 Iterator 對象加鎖。
那麼為什麼不要使用 foreach 迴圈進行元素的 remove / add 操作呢?
我們這裡可以簡單的做下驗證。
代碼示例:
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
System.out.println("list遍歷之前:"+list);
for (String item : list) {
if ("2".equals(item)) {
list.remove(item);
//如果這裡不適用break的話,會直接報錯的
break;
}
}
System.out.println("list遍歷之後:"+list);
List<String> list1 = new ArrayList<String>();
list1.add("1");
list1.add("2");
System.out.println("list1遍歷之前:"+list1);
Iterator<String> iterator = list1.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if ("2".equals(item)) {
iterator.remove();
}
}
System.out.println("list1遍歷之後:"+list1);
輸出結果:
list遍歷之前:[1, 2]
list遍歷之後:[1]
list1遍歷之前:[1, 2]
list1遍歷之後:[1]
註意:上述代碼中,在對list進行for迴圈遍歷的時候,加了break,
上述示例中,都正確的列印我們想要的數據,不過在foreach迴圈中,我在其中是加上了break。如果不加break,就會直接拋出ConcurrentModificationException異常!
Map
Map 介面並不是 Collection 介面的繼承。Map提供key到value的映射。一個Map中不能包含相同的key,每個key只能映射一個value。Map介面提供3種集合的視圖,Map的內容可以被當作一組key集合,一組value集合,或者一組key-value映射。
Map介面主要由HashMap、TreeMap、LinkedHashMap、Hashtable和ConcurrentHashMap這幾個類實現。
它們的解釋如下:
- HashMap: HashMap的鍵是根據HashCode來獲取,所以根據鍵可以很快的獲取相應的值。不過它的鍵對象是不可以重覆的,它允許鍵為Null,但是最多只能有一條記錄,不過卻是可以允許多條記錄的值為Null。因為HashMap是非線程安全的,所以它的效率很高。
- TreeMap:可以將保存的記錄根據鍵進行排序,預設是按鍵值的升序排序(自然順序)。也可以指定排序的比較器,當用Iterator遍歷TreeMap時,得到的記錄是排過序的。它也是不允許key值為空,並且不是線程安全的。
- LinkedHashMap:LinkedHashMap基本和HashMap一致。不過區別在與LinkedHashMap是維護一個雙鏈表,可以將裡面的數據按寫入 的順序讀出。可以認為LinkedHashMap是HashMap+LinkedList。即它既使用HashMap操作數據結構,又使用LinkedList維護插入元素的先後順序。它也不是線程安全的。
- Hashtable:Hashtable與HashMap類似,可以說是HashMap的線程安全版。不過它是不允許記錄的鍵或者值為null。因為它支持線程的同步,是線程安全的,所以也導致了Hashtale在效率較低。
- ConcurrentHashMap: ConcurrentHashMap在Java 1.5作為Hashtable的替代選擇新引入的。使用鎖分段技術技術來保證線程安全的,可以看作是Hashtable的升級版。
在工作中,我們使用得最多的Map應該是HashMap。不過有時在使用Map的時候,需要進行自然順序排序。這裡我們就可以使用TreeMap,而不必自己實現這個功能。TreeMap的使用和HashMap差不多。不過需要註意的是TreeMap是不允許key為null。 這裡簡單的介紹下TreeMap的使用。
代碼示例:
Map<String,Object> hashMap=new HashMap<String,Object>();
hashMap.put("a", 1);
hashMap.put("c", 3);
hashMap.put("b", 2);
System.out.println("HashMap:"+hashMap);
Map<String,Object> treeMap=new TreeMap<String,Object>();
treeMap.put("a", 1);
treeMap.put("c", 3);
treeMap.put("b", 2);
System.out.println("TreeMap:"+treeMap);
輸出結果:
HashMap:{b=2, c=3, a=1}
TreeMap:{a=1, b=2, c=3}
上述中可以看出HashMap是無序的,TreeMap是有序的。
在使用Map的時候,也會對Map進行遍歷。一般遍歷Map的key和value有三種方式:
第一種通過Map.keySet遍歷;
第二種通過Map.entrySet使用iterator遍歷;
第三種是通過Map.entrySet進行遍歷。
使用如下:
Map<String, String> map = new HashMap<String, String>();
for (String key : map.keySet()) {
System.out.println("key= "+ key + " and value= " + map.get(key));
}
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
}
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
}
如果只想獲取Map中value的話,可以使用foreach對Map.values()進行遍歷。
for (String v : map.values()) {
System.out.println("value= " + v);
}
在上述遍歷中,我們最多使用的是第一種Map.keySet,因為寫起來比較簡單。不過在容量大的時候,推薦使用第三種,效率會更高!
Set
Set是一種不包含重覆的元素的Collection,即任意的兩個元素e1和e2都有e1.equals(e2)=false,Set最多有一個null元素。因為Set是一個抽象的介面,所以是不能直接實例化一個set對象。
Set s = new Set()
這種寫法是錯誤的。
Set介面主要是由HashSet、TreeSet和LinkedHashSet來實現。
它們簡單的使用如下:
Set hashSet = new HashSet();
Set treeSet = new TreeSet();
Set linkedSet = new LinkedHashSet();
因為Set是無法擁有重覆元素的,所以也經常用它來去重。例如在一個list集合中有兩條相同的數據,想去掉一條,這時便可以使用Set的機制來去重。
代碼示例:
public static void set(){
List<String> list = new ArrayList<String>();
list.add("Java");
list.add("C");
list.add("C++");
list.add("JavaScript");
list.add("Java");
Set<String> set = new HashSet<String>();
for (int i = 0; i < list.size(); i++) {
String items = list.get(i);
System.out.println("items:"+items);
if (!set.add(items)) {
System.out.println("重覆的數據: " + items);
}
}
System.out.println("list:"+list);
}
輸出結果:
items:Java
items:C
items:C++
items:JavaScript
items:Java
重覆的數據: Java
list:[Java, C, C++, JavaScript, Java]
註意:如果是將對象進行去重的話,是需要重寫set中的equals和hashcode方法的。
總結
關於集合中List、Map、Set這三個的總結如下:
- List:List和數組類似,可以動態增長,根據實際存儲的數據的長度自動增長List的長度。查找元素效率高,插入刪除效率低,因為會引起其他元素位置改變 <實現類有ArrayList,LinkedList,Vector>
- ArrayList:非線程安全,適合隨機查找和遍歷,不適合插入和刪除。
- LinkedList : 非線程安全,適合插入和刪除,不適合查找。
Vector : 線程安全。不過不推薦。
- Map:一個key到value的映射的類 。
- HashMap:非線程安全,鍵和值都允許有null值存在。
- TreeMap:非線程安全,按自然順序或自定義順序遍歷鍵(key)。
- LinkedHashMap:非線程安全,維護一個雙鏈表,可以將裡面的數據按寫入的順序讀出。寫入比HashMap強,新增和刪除比HashMap差。
- Hashtable:線程安全,鍵和值不允許有null值存在。不推薦使用。
ConcurrentHashMap:線程安全,Hashtable的升級版。推薦多線程使用。
- Set:不允許重覆的數據 。檢索效率低下,刪除和插入效率高。
- HashSet: 非線程安全、無序、數據可為空。
- TreeSet: 非線程安全、有序、數據不可為空。
LinkedHashSet:非線程安全、無序、數據可為空。寫入比HashSet強,新增和刪除比HashSet差。
到此,本文結束,謝謝閱讀。
版權聲明:
作者:虛無境
博客園出處:http://www.cnblogs.com/xuwujing
CSDN出處:http://blog.csdn.net/qazwsxpcm
個人博客出處:http://www.panchengming.com
原創不易,轉載請標明出處,謝謝!