多用多學之Java中的Set,List,Map

来源:http://www.cnblogs.com/5207/archive/2016/06/27/5620604.html
-Advertisement-
Play Games

很長時間以來一直代碼中用的比較多的數據列表主要是List,而且都是ArrayList,感覺有這個玩意就夠了。ArrayList是用於實現動態數組的包裝工具類,這樣寫代碼的時候就可以拉進拉出,迭代遍歷,蠻方便的。 也不知道從什麼時候開始慢慢的代碼中就經常會出現HashMap和HashSet之類的工具類 ...


        很長時間以來一直代碼中用的比較多的數據列表主要是List,而且都是ArrayList,感覺有這個玩意就夠了。ArrayList是用於實現動態數組的包裝工具類,這樣寫代碼的時候就可以拉進拉出,迭代遍歷,蠻方便的。           也不知道從什麼時候開始慢慢的代碼中就經常會出現HashMap和HashSet之類的工具類。應該說HashMap比較多一些,而且還是面試經典題,平時也會多看看。開始用的時候簡單理解就是個鍵值對應表,使用鍵來找數據比較方便。隨後深入瞭解後發現這玩意還有點小奧秘,特別是新版本的JDK對HashMap的改成樹後,代碼都有點小複雜咯。           Set開始用的較少,只是無意中在一個代碼中發現一個TreeSet,發現這個類可以自帶順利,感覺蠻有點意思,才慢慢的發現這也是個好工具啊。           代碼寫的多了就感覺到基礎的重要性,所以在此寫一篇小文簡單的整理一下對集合的一些知識。   好了,簡單的整理一下:
  • List:即是列表,支持數組、鏈表的功能,一般都是線性的
  • Map:即是映射表,存儲的是鍵與值的對應關係
  • Set:即是集合的意思,主要是用於排重數據及排序
 

先來看看List

List是用於存放線性數據的一種視窗,比如:用於數組的ArrayList和用於鏈表的LinkedList。  

ArrayList

這是一個數組列表,不過提供了自動擴容的功能,實現List介面,外部操作都是通過介面申明的方法訪問,這樣即安全又方便。   ArrayList的關鍵就是自動擴容,在對象初始化時可以設定初始容量,也可以按預設的容量。如果對數組大小沒有特別明確可以不指定初始大小,如果明確的話可以指定一個大小,這樣減少動態擴容時產生的卡頓。說到這就要說一下擴容是怎麼實現的了,看下麵的代碼:
1 2 3 4 5 6 7 8 9 10 11     private void grow(int minCapacity) {         // overflow-conscious code         int oldCapacity = elementData.length;         int newCapacity = oldCapacity + (oldCapacity >> 1);         if (newCapacity - minCapacity < 0)             newCapacity = minCapacity;         if (newCapacity - MAX_ARRAY_SIZE > 0)             newCapacity = hugeCapacity(minCapacity);         // minCapacity is usually close to size, so this is a win:         elementData = Arrays.copyOf(elementData, newCapacity);     }
grow是在ArrayList在添加元素或者一些容易檢查時會觸發的一個方法。主要過程: 1、得到數組的長度,並對其進行右移,這樣就相當於oldCapacity/2,得到新的長度 2、如果這個長度小於最小容量那麼直接就用最小容易 3、如果大於了最大容易則取一個最大值,這裡會調用一個hugeCapacity方法,主要是比較minCapacity與MAX_ARRAY_SIZE的,如果minCapacity大於MAX_ARRAY_SIZE則取Integer.MAX_VALUE,否則就取MAX_ARRAY_SIZE,有意思的是MAX_ARRAY_SIZE取的是Integer.MAX_VALUE - 8;並不知道這樣做的意義是什麼 4、最後就是調用一個複製方法將現有數複製到一個新的數組中。   因為有這個複製過程,如果數組比較大,那麼老是觸發擴容當然就會出現卡頓的情況。所以如果一開始就知道最大值而且很容易增長到這個值,那麼開始初始化時就指定大小會有一定的作用。  

LinkedList

這是針對鏈表的工具類,鏈表的優秀是添加刪除啥的比較快,但是查找會慢一些。   至於代碼好像也沒什麼特別的,就是一串指針鏈接起來,當然Java中就使用對象來代替,建立一個Node的對象,Node本身指向了前一個Node和後一個Node,這就是鏈表的結構:
1 2 3 4 5 6 7 8 9 10 11     private static class Node<E> {         E item;         Node<E> next;         Node<E> prev;           Node(Node<E> prev, E element, Node<E> next) {             this.item = element;             this.next = next;             this.prev = prev;         }     }
  然後用兩個Node指向頭和尾就完成了,下麵的代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13     /**      * Pointer to first node.      * Invariant: (first == null && last == null) ||      *            (first.prev == null && first.item != null)      */     transient Node<E> first;       /**      * Pointer to last node.      * Invariant: (first == null && last == null) ||      *            (last.next == null && last.item != null)      */     transient Node<E> last;
看一個add操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14     /**      * Links e as last element.      */     void linkLast(E e) {         final Node<E> l = last;         final Node<E> newNode = new Node<>(l, e, null);         last = newNode;         if (l == null)             first = newNode;         else             l.next = newNode;         size++;         modCount++;     }
過往就是:   1、獲取到最後的Node並放在l中 2、創建一個新的Node,將數據取到這個Node中,創建過程會將新Node的prev指向l,這樣就接上了鏈 3、然後將last指向這個新Node 4、然判斷l是否null,如果是null說明是空鏈表,新node就是第一個元素,這樣first也要指向newNode 5、如果不為空則將l的next指向newNode 6、累加計數器   刪除操作也是這種Node的前後Node指向移動操作。    

再來看看Map

Map是鍵與值做一個映射表的應用,主要的實現類:HashMap,HashTable,TreeMap  

HashMap和HashTable

使用hash演算法進行鍵值映射的就是HashMap啦,HashTable是帶有同步的線程安全的類,它們兩主要的區別就是這個。原理也類似,都是通過桶+鏈來組合實現。桶是用來存Key的,而由於Hash碰撞的原因值需要用一個鏈表來存儲。
  • 的意義在於高效,通過Hash計算可以一步定位
  • 鏈表的意義在於存取重覆hash的數據
  具體的原理以前寫過一篇《學習筆記:Hashtable和HashMap》 只不過看JDK1.8的HashMap換了存儲結構,採用紅黑樹的結構,這樣可能是解決鏈表查找效率問題吧?具體沒有細研究。  

TreeMap

看過TreeMap的代碼後發現還是使用的樹結構,紅黑樹。由於紅黑樹是有序的,所以自然帶排序功能。當然也可通過comparator來指定比較方法來實現特定的排序。   因為採用了樹結構存儲那麼添加和刪除數據時會麻煩一些,看一下put的代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public V put(K key, V value) {         Entry<K,V> t = root;         if (t == null) {             compare(key, key); // type (and possibly null) check               root = new Entry<>(key, value, null);             size = 1;             modCount++;             return null;         }         int cmp;         Entry<K,V> parent;         // split comparator and comparable paths         Comparator<? super K> cpr = comparator;         if (cpr != null) {             do {                 parent = t;                 cmp = cpr.compare(key, t.key);                 if (cmp < 0)                     t = t.left;                 else if (cmp > 0)                     t = t.right;                 else                     return t.setValue(value);             while (t != null);         }         else {             if (key == null)                 throw new NullPointerException();             @SuppressWarnings("unchecked")                 Comparable<? super K> k = (Comparable<? super K>) key;             do {                 parent = t;                 cmp = k.compareTo(t.key);                 if (cmp < 0)                     t = t.left;                 else if (cmp > 0)                     t = t.right;                 else                     return t.setValue(value);             while (t != null);         }         Entry<K,V> e = new Entry<>(key, value, parent);         if (cmp < 0)             parent.left = e;         else             parent.right = e;         fixAfterInsertion(e);         size++;         modCount++;         return null;     }
1、先是檢查根節點是否存在,不存在說明是第一條數據,直接作為樹的根 2、判斷是否存在比較器,如果存在則使用比較器進行查找數據的存放位置,如果比較器返回結果小於0取左,大於0取右,否則直接替換當前節點的值 3、如果不存在比較器則key直接與節點的key比較,比較和前面方法一樣 4、接下來就是在找到的parent上創建一個子節點,並放入左或者右子節點中 5、fixAfterInsertion是對節點進行著色 6、累加器處理   在remove操作時也會有點麻煩,除了刪除數據外,還要重新平衡一下紅黑樹。   另外,TreeMap實現了NavigableMap<K,V>介面,所以也提供了對數據集合的一些返回操作。  

最後看看Set

Set主要是兩類應用:HashSet和TreeSet。  

HashSet

字面意思很明確,使用了Hash的集合。這種集合的特點就是使用Hash演算法存數據,所以數據不重覆,存取都相對較快。怎麼做到的呢?  
1 2 3 4     public boolean add(E e) {         return map.put(e, PRESENT)==null;     }     
原來是存在一個map對象中,再看map是個啥?
1 private transient HashMap<E,Object> map;
是個HashMap,瞭解HashMap的就明白,這樣的數據是不會重覆的。因為存入時是鞀對象本身作為Key來存的,所以在HashMap中只會存在一份。   瞭解了這點其他的東西就非常明白了。  

TreeSet

這個集合是用於對集合進行排序的,也就是除了帶有排重的能力外,還可以自帶排序功能。只不過看了TreeSet的代碼發現,其就是在TreeMap的基礎實現的。更準確的說應該是NavigableMap的派生類。預設不指定map情況下TreeSet是以TreeMap為基礎的。
1 2 3     public TreeSet() {         this(new TreeMap<E,Object>());     }
所以,這裡可能更關註的是TreeSet是如何排重呢?看一下add的方法吧:
1 2 3     public boolean add(E e) {         return m.put(e, PRESENT)==null;     }
和HashSet有點類似,都是基於Map的特性來實現排重。確實簡單而且有效。    
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 閱讀目錄 1. Process 2. Lock 3. Semaphore 4. Event 5. Queue 6. Pipe 7. Pool 閱讀目錄 1. Process 2. Lock 3. Semaphore 4. Event 5. Queue 6. Pipe 7. Pool 序. multi ...
  • 本文講述的是log4cplus日誌輸出到qt widget,封裝了serverSocket。 log4cplus支持用戶自定義輸出設備,只需要繼承自Appender,或者Appender子類,並實現append成員方法,然後在 log4cplus初始化成功之後,把自定義輸出設備添加到logger中, ...
  • jdk自帶註解 ①@Override覆蓋父類方法時顯示 ②@Deprecated定義過時的方法 @SuppressWarnings忽略過時函數的警告 廢話少說,上個例子 /////////////////////////////////////////Person.java://////////// ...
  • 因為項目用到DataTable表格載入後臺數據,要連表查詢虛擬機選中的策略狀態,所以想到先把策略表內容取出來,組成一個'<select><option value="1"></option>[n個option]</select>'字元串,在遍歷虛擬機列表時把他的策略值拼成 'value="1"' 這 ...
  • 關於偽共用的文章已經很多了,對於多線程編程來說,特別是多線程處理列表和數組的時候,要非常註意偽共用的問題。否則不僅無法發揮多線程的優勢,還可能比單線程性能還差。隨著JAVA版本的更新,再各個版本上減少偽共用的做法都有區別,一不小心代碼可能就失效了,要註意進行測試。這篇文章總結一下。 什麼是偽共用 關... ...
  • 前端JS代碼: var conditons = []; var test1 = new Object(); test1.name="1"; test1.id="2"; var test2 = new Object(); test2.name="1"; test2.id="2"; conditons. ...
  • 題目:輸入一個整數數組,判斷該數組是不是某二叉搜索樹的後序遍歷的結果。如果是則輸出Yes,否則輸出No。假設輸入的數組的任意兩個數字都互不相同。 思路:需要遍歷樹,二叉排序樹的特點是 lchild.key < root.key < rchild.key 那麼我們使用分治思想,先利用上面特點將左右子樹 ...
  • 作者:楓雪庭 出處:http://www.cnblogs.com/FengXueTing-px/ 歡迎轉載 Java學習心得之 HttpClient的GET和POST請求 1. 前言2. GET請求3. POST請求 一、前言 本篇博文記錄了HttpClient的GET和POST請求 本文內容基於以 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...