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

来源:https://www.cnblogs.com/xuwujing/archive/2018/04/19/8886821.html
-Advertisement-
Play Games

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ########################################IO流: IO:用於處理設備上的數據的技術。設備:記憶體,硬碟,光碟 流:系統資源,Windows系統本身就可以操作的設備。各種語言只是使用系統平臺的這個資源。並對外提供了各種語言自己的操作功能,這些功能最終調用的是系統資 ...
  • vue
    1.Vue.js 是什麼? 網址:https://vuejs.org/ Vue.js(讀音 /vjuː/,類似於 view 的讀音)是一套構建用戶界面(user interface)的漸進式框架。與其他重量級框架不同的是,Vue 從根本上採用最小成本、漸進增量(incrementally adopt ...
  • 方法的重載(overload) 1 //定義兩個int型變數的和 2 public int getSum(int i, int j) { 3 return j + i; 4 } 5 6 //定義三個int變數的和 7 public int getSum(int i, int j, int k) { ...
  • 有時候想調試線上的程式 可以啟用遠程調試功能 在本地調試遠程代碼。 遠程JVM啟用調試模式 -XDebug 表示虛擬機啟用調試功能-Xrunjdwp 載入JDWPtransport 調試程式JVM使用的進程之間通訊方式dt_socket socket通訊server=y/n JVM是否需要作為調試服 ...
  • 關於java傳值的一點小細節:java語言中對象傳遞地址,而不是引用。 例如:假設對象Test有name的屬性。 public void call(Test){ Test t2 = new Test(); t2.setName("cba"); t.setName("abc"); t=12; } pu ...
  • Pycharm強大的功能總是讓我很是著迷,比如它的makemigrations 和 migrate。 然而某一次,當我再次敲下這熟悉的命令時,它報錯了。。。。 不想看上邊的朋友我還截了個大圖。 錯誤是:django.db.migrations.exceptions.InconsistentMigra ...
  • 在spring中採用tx方式配置 <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="synch*" propagation="REQUIRED" re ...
  • 簡單地總結就一句話: Python2的mysql庫為:mysqldb,而Python3的為:pymysql。 當我們使用Pycharm開發項目時,首先需要下載安裝相對應的資料庫,以及在項目根目錄下的setting.py文件中連接資料庫,代碼如下: (我用的框架是Django) 值得註意的是:如果你是 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...