程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 多用多學之Java中的Set,List,Map,setmap

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

編輯:JAVA綜合教程

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


        很長時間以來一直代碼中用的比較多的數據列表主要是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的特性來實現排重。確實簡單而且有效。    

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved