java-容器 原

java 容器

1. 常用容器分類

①在這裏,集合類分爲了Map和Collection兩個大的類別。

        圖片左上角的那一塊灰色裏面的四個類(Dictionary、HashTable、Vector、Stack)都是線程安全的,但是它們都是JDK的老的遺留類,現在基本都不怎麼使用了,都有了對應的取代類。其中Map是用來代替圖片中左上角的那個Dictionary抽象類(Map的官方文檔裏面有說明)。官方推薦使用Map接口來代替它。同樣對於HashTable,官方推薦ConcurrentHashMap來代替。接着下面的Vector是List下面的一個實現類。
        圖片最上面的粉紅色部分是集合類所有接口關係圖。其中Map的結構比較簡單,而Collection的結構就相對複雜一些。Collection有三個繼承接口:List、Queue和Set。
        接下來綠色部分則是集合類的主要實現類了。這也是我們最經常使用的集合類了。
        最下方的一個整塊都是java.util.concurrent包裏面的類,按照包名我們就可以知道這個包裏面的類都是用來處理Java編程中各種併發場景的

②按照實現接口分類

實現Map接口的有:EnumMap、IdentityHashMap、HashMap、LinkedHashMap、WeakHashMap、TreeMap
實現List接口的有:ArrayList、LinkedList
實現Set接口的有:HashSet、LinkedHashSet、TreeSet
實現Queue接口的有:PriorityQueue、LinkedList、ArrayQueue

③根據底層實現的數據結構分類

底層以數組的形式實現:EnumMap、ArrayList、ArrayQueue
底層以鏈表的形式實現:LinkedHashSet、LinkedList、LinkedHashMap
底層以hash table的形式實現:HashMap、HashSet、LinkedHashMap、LinkedHashSet、WeakHashMap、IdentityHashMap
底層以紅黑樹的形式實現:TreeMap、TreeSet
底層以二叉堆的形式實現:PriorityQueue

④併發容器

ConcurrentHashMap、ConcurrentSkipListHashMap
LinkedTransferQueue、CopyOnWriteArrayList、ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、 LinkedBlockDeque、DelayQueue、SynchronusQueue
ConcurrentSkipListSet、CopyOnWriteArraySet

2.collection

void clear()  
boolean isEmpty()  
int size()  
Object[] toArray() 返回一個數組,該數組包含容器中的所有元素  
<T> T[] toArray(T[] a) 返回一個數組,該數組包含容器中的所有元素。返回結果的運行時類型與參數數組a的類型相同,而不是單純的Object。  
Iterator<T> iterator()  
boolean contains(T)  
boolean containsAll(Collection<?>)  
boolean add(T)  
boolean addAll(Collection<? extends T>)  
boolean remove(Object)  
boolean removeAll(Collection<?>)  
boolean retainAll(Collection<?>) 與參數中的元素取交集,即在兩個集合中都有的元素  

3.Iterator

boolean hasNext():如果被迭代的集合,元素還有沒被遍歷完,則返回true。  
Object next():返回集合裏下一個元素。  
void remove():刪除集合裏上一次next方法返回的元素。  
void forEachRemaining(Consumer action),這是Java 8爲Iterator新增的默認方法,該方法可使用Lambda表達式來遍歷集合元素。  

4.未獲支持的操作 UnsupportedOperationException

常見特例:

①Arrays.asList()會生成一個List,它基於一個固定大小的數組,僅支持那些不會改變數組大小的操作,

任何對引起底層數據結構的尺寸進行修改的方法都會產生一個UnsupportedOperationException異常,以表示對未獲支持操作的調用。
通常的做法是,把Arrays.asList()的結果作爲構造方法的參數傳遞給任何Collection(或者使用addAll()方法或Collections.addAll()靜態方法),這樣就可以生成允許使用所有的方法的普通容器。

②Collections.unmodifiableList(*)引起的錯誤

Collections.unmodifiableList 起到了對modifyList設置權限的目的**。
但是如果你如果不改引用,是可以通過引用normalList來更改其所指向的對象的。

private static void testReadOnly(){
        //1.創建一個list。並且這個list的訪問權限未進行設置。
        List<String> normalList = new ArrayList<>(); 
        //2.向其中插入相關的數據。【可行】
        normalList.add("you");
        normalList.add("are");
        normalList.add("boy");
        //3.對list進行設置。使之可讀。
        List<String> modifyList = Collections.unmodifiableList(normalList);
        //4.在次插入,出現UnsupportedOperationException錯誤。
        modifyList.add("hello");
    }

5.散列

①散列原理

假如鍵沒有按照一定的順序進行保存,那麼查詢的時候就只能按照順序進行線性查詢,然而,線性查詢是最慢的查詢方式。 所以,將鍵值按照一定的順序排序,並且使用二分查找能購有效的提升速度。
散列在此之上,更近一步,他將鍵保存在數組中(數組的查詢速度最快),用數組來表示鍵的信息,但是由於Map的容量是可變的,而數組的容量是不變的。
要解決這個問題,數組中存的並不是鍵本身,而是鍵對象生成散列碼,並根據散列碼計算出數組的下標。而這種辦法所產生的問題就是下標重複。而我們的解決辦法就是配合equals來確定鍵值。
查詢的過程首先就是計算散列碼,然後用散列碼來查詢函數(下標),通常,我們的數組中保存的是值的list,因此,我們計算出散列碼之後,通過下表取到的對應部分的list,然後通過equals就可以快速找到鍵值。

②如果要使用我們自己的類作爲鍵,我們必須同時重寫hashCode() 和 equals()兩個方法。

HashMap使用equals方法來判斷當前的鍵是否與表中的鍵相同。
equals()方法需要滿足以下5個條件:
自反性 x.equals(x) 一定返回true
對稱性 x.equals(y)返回true,則y.equals(x) 也返回true
傳遞性 x.equals(y)返回true,y.equals(z)返回true,則x.equals(y)返回true
一致性 如果對象中的信息沒有改變,x.equals(y)要麼一直返回true,要麼一直返回false
對任何不是null的x,想x.equals(null)一定返回false

6.強引用 軟引用 弱引用 虛引用

①以Reference對象作爲弱對象與普通引用間的媒介(代理)可以實現弱引用。

但如果普通引用直接指向弱對象,則弱對象不會被釋放。

②SoftReference(軟引用) WeakReference(弱引用) PhantomReference(虛引用)由弱到強

③soft reference和weak reference一樣, 但被GC回收的時候需要多一個條件: 當系統內存不足時, soft reference指向的object纔會被回收.

正因爲有這個特性, soft reference比weak reference更加適合做cache objects的reference. 因爲它可以儘可能的retain cached objects, 減少重建他們所需的時間和消耗.

④PhantomReference的進入隊列之後jvm不會自動回收,需要人爲調用clear方法後才最終釋放,這是使用PhantomReference的關鍵。

虛引用常用來做對象清理工作,類似finalize方法。

⑤WeakReference的一個特點是它何時被回收是不可確定的, 因爲這是由GC運行的不確定性所確定的.

所以, 一般用weak reference引用的對象是有價值被cache, 而且很容易被重新被構建, 且很消耗內存的對象.
在weak reference指向的對象被回收後, weak reference本身其實也就沒有用了. java提供了一個ReferenceQueue來保存這些所指向的對象已經被回收的reference.
用法是在定義WeakReference的時候將一個ReferenceQueue的對象作爲參數傳入構造函數.
WeakHashMap是一種特殊的Map,用來保存WeakReference。

7.併發容器

①CAS 算法

CAS(Compare-And-Swap) 算法是硬件對於併發的支持,針對多處理器操作而設計的處理器中的一種特殊指令,用於管理對共享數據的併發訪問;
CAS 是一種無鎖的非阻塞算法的實現;

CAS 包含了三個操作數:  
需要讀寫的內存值: V  
進行比較的預估值: A  
擬寫入的更新值: B  
當且僅當 V == A 時, V = B, 否則,將不做任何操作;  

②傳統同步容器缺陷

1)synchronized關鍵字併發低。
2)在一些併發安全的複合操作中會出錯。
雖然Vector的方法採用了synchronized進行了同步,但是由於Vector是繼承的AbstarctList,因此通過Iterator來訪問容器的話,事實上是不需要獲取鎖就可以訪問。
那麼顯然,由於使用iterator對容器進行訪問不需要獲取鎖,在多線程中就會造成當一個線程刪除了元素,由於modCount是AbstarctList的成員變量,因此可能會導致在其他線程中modCount和expectedModCount值不等。
初始時,線程1和線程2中的modCount、expectedModCount都爲0,
當線程2通過iterator.remove()刪除元素時,會修改modCount值爲1,並且會修改線程2中的expectedModCount的值爲1, 而此時線程1中的expectedModCount值爲0,雖然modCount不是volatile變量,但是照樣導致線程1中比expectedModCount和modCount不等,而拋出異常。
因此一般有2種解決辦法:
1)在使用iterator迭代的時候使用synchronized或者Lock進行同步;
2)使用併發容器CopyOnWriteArrayList代替ArrayList和Vector。

③JUC併發

java.util.concurrent(簡稱JUC)包
1)根據具體場景進行設計,儘量避免synchronized,提供併發性。
2)定義了一些併發安全的複合操作,並且保證併發環境下的迭代操作不會出錯。

ConcurrentHashMap代替同步的Map(Collections.synchronized(new HashMap())),衆所周知,HashMap是根據散列值分段存儲的,同步Map在同步的時候鎖住了所有的段,而ConcurrentHashMap加鎖的時候根據散列值鎖住了散列值鎖對應的那段,因此提高了併發性能。

ConcurrentHashMap爲了提高本身的併發能力,在內部採用了一個叫做Segment的結構,一個Segment其實就是一個類Hash Table的結構,Segment內部維護了一個鏈表數組,ConcurrentHashMap定位一個元素的過程需要進行兩次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的鏈表的頭部,因此,這一種結構的帶來的副作用是Hash的過程要比普通的HashMap要長,但是帶來的好處是寫操作的時候可以只對元素所在的Segment進行加鎖即可,不會影響到其他的Segment, ConcurrentHashMap的key 和 value都不允許null出現。原因在於ConcurrentHashMap不能區分出value是null還是沒有map上,相對的HashMap卻可以允許null值,在於其使用在單線程環境下,可以使用containKey(key)方法提前判定是否能map上,從而區分這兩種情況,但是ConcurrentHashMap在多線程使用上下文中則不能這麼判定。

ConcurrentHashMap也增加了對常用複合操作的支持,比如"若沒有則添加":putIfAbsent(),替換:replace()。這2個操作都是原子操作。
CopyOnWriteArrayList和CopyOnWriteArraySet分別代替List和Set,主要是在遍歷操作爲主的情況下來代替同步的List和同步的Set,這也就是上面所述的思路:迭代過程要保證不出錯,除了加鎖,另外一種方法就是"克隆"容器對象。
ConcurrentLinkedQuerue是一個先進先出的隊列。它是非阻塞隊列。
ConcurrentSkipListMap可以在高效併發中替代SoredMap(例如用Collections.synchronzedMap包裝的TreeMap)。
ConcurrentSkipListSet可以在高效併發中替代SoredSet(例如用Collections.synchronzedSet包裝的TreeMap)。

④COW

Copy-On-Write簡稱COW,是一種用於程序設計中的優化策略。其基本思路是,從一開始大家都在共享同一個內容,當某個人想要修改這個內容的時候,纔會真正把內容Copy出去形成一個新的內容然後再改。

通俗的理解是當我們往一個容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行Copy,複製出一個新的容器,然後新的容器裏添加元素,添加完元素之後,再將原容器的引用指向新的容器。
這樣做的好處是我們可以對CopyOnWrite容器進行併發的讀,而不需要加鎖,因爲當前容器不會添加任何元素。
所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器。

JDK提供了CopyOnWriteArrayList和CopyOnWriteArraySet類

CopyOnWrite併發容器用於讀多寫少的併發場景。
使用CopyOnWriteMap需要注意三件事情:

  1. 減少擴容開銷。根據實際需要,初始化CopyOnWriteMap的大小,避免寫時CopyOnWriteMap擴容的開銷。
  2. 使用批量添加。因爲每次添加,容器每次都會進行復制,所以減少添加次數,可以減少容器的複製次數。如使用上面代碼裏的addBlackList方法。
  3. 數據一致性問題。CopyOnWrite容器只能保證數據的最終一致性,不能保證數據的實時一致性。所以如果你希望寫入的的數據,馬上能讀到,請不要使用CopyOnWrite容器。

8.常用方法

①排序操作(主要針對List接口相關)

reverse(List list):反轉指定List集合中元素的順序
shuffle(List list):對List中的元素進行隨機排序(洗牌)
sort(List list):對List裏的元素根據自然升序排序
sort(List list, Comparator c):自定義比較器進行排序
swap(List list, int i, int j):將指定List集合中i處元素和j出元素進行交換
rotate(List list, int distance):將所有元素向右移位指定長度,如果distance等於size那麼結果不變

 public void testSort() {
        System.out.println("原始順序:" + list);
        
        Collections.reverse(list);
        System.out.println("reverse後順序:" + list);

        Collections.shuffle(list);
        System.out.println("shuffle後順序:" + list);
        
        Collections.swap(list, 1, 3);
        System.out.println("swap後順序:" + list);

        Collections.sort(list);
        System.out.println("sort後順序:" + list);

        Collections.rotate(list, 1);
        System.out.println("rotate後順序:" + list);
    }

輸出:
原始順序:[b張三, d孫六, a李四, e錢七, c趙五]
reverse後順序:[c趙五, e錢七, a李四, d孫六, b張三]
shuffle後順序:[b張三, c趙五, d孫六, e錢七, a李四]
swap後順序:[b張三, e錢七, d孫六, c趙五, a李四]
sort後順序:[a李四, b張三, c趙五, d孫六, e錢七]
rotate後順序:[e錢七, a李四, b張三, c趙五, d孫六]

②查找和替換(主要針對Collection接口相關)

binarySearch(List list, Object key):使用二分搜索法,以獲得指定對象在List中的索引,前提是集合已經排序
max(Collection coll):返回最大元素
max(Collection coll, Comparator comp):根據自定義比較器,返回最大元素
min(Collection coll):返回最小元素
min(Collection coll, Comparator comp):根據自定義比較器,返回最小元素
fill(List list, Object obj):使用指定對象填充
frequency(Collection Object o):返回指定集合中指定對象出現的次數
replaceAll(List list, Object old, Object new):替換

public void testSearch() {
        System.out.println("給定的list:" + list);
        System.out.println("max:" + Collections.max(list));
        System.out.println("min:" + Collections.min(list));
        System.out.println("frequency:" + Collections.frequency(list, "a李四"));
        Collections.replaceAll(list, "a李四", "aa李四");
        System.out.println("replaceAll之後:" + list);
        
        // 如果binarySearch的對象沒有排序的話,搜索結果是不確定的
        System.out.println("binarySearch在sort之前:" + Collections.binarySearch(list, "c趙五"));
        Collections.sort(list);
        // sort之後,結果出來了
        System.out.println("binarySearch在sort之後:" + Collections.binarySearch(list, "c趙五"));

        Collections.fill(list, "A");
        System.out.println("fill:" + list);
    }

輸出:
給定的list:[b張三, d孫六, a李四, e錢七, c趙五]
max:e錢七
min:a李四
frequency:1
replaceAll之後:[b張三, d孫六, aa李四, e錢七, c趙五]
binarySearch在sort之前:-4
binarySearch在sort之後:2
fill:[A, A, A, A, A]

③同步控制

Collections工具類中提供了多個synchronizedXxx方法,該方法返回指定集合對象對應的同步對象,從而解決多線程併發訪問集合時線程的安全問題。
HashSet、ArrayList、HashMap都是線程不安全的,如果需要考慮同步,則使用這些方法。
這些方法主要有:synchronizedSet、synchronizedSortedSet、synchronizedList、synchronizedMap、synchronizedSortedMap。
特別需要指出的是,在使用迭代方法遍歷集合時需要手工同步返回的集合。

Map m = Collections.synchronizedMap(new HashMap());
      ...
  Set s = m.keySet();  // Needn't be in synchronized block
      ...
  synchronized (m) {  // Synchronizing on m, not s!
      Iterator i = s.iterator(); // Must be in synchronized block
      while (i.hasNext())
          foo(i.next());
  }

④設置不可變集合

Collections有三類方法可返回一個不可變集合:
emptyXxx():返回一個空的不可變的集合對象
singletonXxx():返回一個只包含指定對象的,不可變的集合對象。
unmodifiableXxx():返回指定集合對象的不可變視圖

public void testUnmodifiable() {
        System.out.println("給定的list:" + list);
        List<String> unmodList = Collections.unmodifiableList(list);
        
        unmodList.add("再加個試試!"); // 拋出:java.lang.UnsupportedOperationException
        
        // 這一行不會執行了
        System.out.println("新的unmodList:" + unmodList);
    }

⑤其它

disjoint(Collection<?> c1, Collection<?> c2) - 如果兩個指定 collection 中沒有相同的元素,則返回 true。
addAll(Collection<? super T> c, T... a) - 一種方便的方式,將所有指定元素添加到指定 collection 中。示範:
Collections.addAll(flavors, "Peaches 'n Plutonium", "Rocky Racoon");
Comparator<T> reverseOrder(Comparator<T> cmp) - 返回一個比較器,它強行反轉指定比較器的順序。
如果指定比較器爲 null,則此方法等同於 reverseOrder()(換句話說,它返回一個比較器,該比較器將強行反轉實現 Comparable 接口那些對象 collection 上的自然順序)。

public void testOther() {
        List<String> list1 = new ArrayList<String>();
        List<String> list2 = new ArrayList<String>();
        
        // addAll增加變長參數
        Collections.addAll(list1, "大家好", "你好","我也好");
        Collections.addAll(list2, "大家好", "a李四","我也好");
        
        // disjoint檢查兩個Collection是否的交集
        boolean b1 = Collections.disjoint(list, list1);
        boolean b2 = Collections.disjoint(list, list2);
        System.out.println(b1 + "\t" + b2);
        
        // 利用reverseOrder倒序
        Collections.sort(list1, Collections.reverseOrder());
        System.out.println(list1);
    }

輸出:
true false
[我也好, 大家好, 你好]
```
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章