Java基礎知識回顧之四 ----- 集合List、Map和Set


前言

上一篇中回顧了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
原創不易,轉載請標明出處,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM