學習java的同學注意了!!! 學習過程中遇到什么問題或者想獲取學習資源的話,歡迎加入Java學習交流群,群號碼:183993990 我們一起學Java!
Java實用類庫提供了一套相當完整的容器來幫助我們解決很多具體問題。因為我本身是一名Android開發者,包括我在內很多安卓開發,最拿手的就是ListView(RecycleView)+BaseAdapter+ArrayList三劍客, 平時接觸使用的容器也只有ArrayList和HashMap。導致對于整個Java容器體系的掌握和使用還停留在很淺的層面。省不足而思改進,那么跟著我來總結一下Java容器的相關知識吧。
結構
java容器類的繼承結構具體介紹迭代器CollectionListSetQueueMap一些建議進階·并發容器CopyOnWriteArrayList與Copy-On-Write策略ConcurrentLinkedQueueConcurrentHashMap與鎖分段技術阻塞隊列java容器類的繼承結構
Java容器類庫定義了兩個不同概念的容器,Collection和Map
Collection 一個獨立元素的序列,這些元素都服從一條或多條規則。List必須按照插入的順序保存元素。Set不能有重復元素。Queue按照排隊規則來確定對象產生的順序。
(文中Jdk源碼版本無特殊說明均為jdk1.8.0_101)
public interface Collection<E> extends Iterable<E> { int size(); boolean isEmpty(); boolean contains(Object o); Iterator<E> iterator(); Object[] toArray(); <T> T[] toArray(T[] a); boolean add(E e); boolean remove(Object o); boolean containsAll(java.util.Collection<?> c); boolean addAll(java.util.Collection<? extends E> c); boolean removeAll(java.util.Collection<?> c); ... //省略了其他方法 }可以看到,java定義了Collection接口和內部集合的基本操作方法,Collection默認可以進行對集合末端添加元素,刪除指定元素等操作。List、Set、Queue接口都繼承自Collection并定義了各自不同的方法。
Map 一組成對的”鍵值對”對象,允許我們使用鍵來查找值。
public interface Map<K,V> { int size(); boolean containsKey(Object key); boolean containsValue(Object value); V get(Object key); V put(K key, V value); V remove(Object key); void putAll(java.util.Map<? extends K, ? extends V> m); void clear(); Set<K> keySet(); Collection<V> values(); Set<java.util.Map.Entry<K, V>> entrySet(); interface Entry<K,V> { K getKey(); V getValue(); V setValue(V value); boolean equals(Object o); int hashCode(); ... } boolean equals(Object o); int hashCode(); }Map內部接口Entry<K,V>對應著Map的鍵值對。
具體介紹
迭代器
先介紹一下迭代器。迭代器本身也是一種設計模式,設計的初衷在于:容器的實現由很多種,而我們想對容器進行遍歷操作的話,首先不應該關心容器實現的細節,其次遍歷操作應該是輕量級的。迭代器統一了對容器的訪問方式,同時創建它的代價很小。值得注意的是,Iterator只能單向移動。
public interface Iterator<E> { boolean hasNext(); E next(); default void remove() { throw new UnsupportedOperationException("remove"); } default void forEachRemaining(Consumer<? super E> action) { Objects.requireNonNull(action); while (hasNext()) action.accept(next()); } }通過容器的iterator()方法拿到容器的迭代器迭代器的next()獲取下一個元素hasNext()判斷是否還有元素remove()刪除指定元素
ListIterator
ListIterator是Iterator的擴展之內,用于各種List類訪問,支持雙向移動。
Collection
List
List 承諾可以將元素維護在特定的序列中.List接口在Collection的基礎上添加了大量的方法,使得可以再List中間插入和移除元素。
public interface List<E> extends Collection<E> { ... boolean add(E e); boolean remove(Object o); boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); boolean addAll(int index, Collection<? extends E> c); boolean removeAll(Collection<?> c); boolean retainAll(Collection<?> c); E get(int index); E set(int index, E element); void add(int index, E element); E remove(int index); int indexOf(Object o); int lastIndexOf(Object o); java.util.List<E> subList(int fromIndex, int toIndex); ... }
有兩種類型的List,ArrayList和LinkedList
List類型 優點 缺點 底層實現 ArrayList 隨機訪問元素較快 中間元素的插入和刪除較慢 數組 LinkedList 中間元素的插入和刪除,順序訪問的優化 隨機訪問元素較慢 雙向鏈表 Set
Set不保存重復的元素,通常用于快速查找元素。值得一提的是,Set具有與Collection完全一樣的接口,沒有任何額外的功能。 存入的元素必須定義equals()方法
Set類型 使用場景 底層實現 HashSet 快速查找,元素必須定義hashCode() 鏈表 TreeSet 保持次序,元素必須實現Comparable接口 紅-黑樹結構 LinkedHashSet 維護次序的HashSet, 元素必須定義hashCode() 鏈表
Queue
除了并發應用,Queue僅有的兩個實現是LinkedList和PRiorityQueue, 其中LinkedList同時實現了List, Deque接口。它們的差異在于排序行為而不是性能。
public interface Queue<E> extends Collection<E> { boolean add(E e); boolean offer(E e); E remove(); E poll(); E element(); E peek(); }
Map
Map類型 使用場景 底層實現 HashMap 快速查詢 散列表 LinkedHashMap 迭代遍歷具有順序(插入順序 or 最近最少使用) 鏈表 TreeMap 具有排序,唯一可以返回子樹的Map(subMap()) 紅-黑樹結構 WeakHashMap 弱鍵映射,映射之外無引用的鍵,可以被垃圾回收 散列表 ConcurrentHashMap 線程安全的Map 鏈表 IdentityHashMap 使用==代替equals()對鍵進行排序,專位解決特殊問題 鏈表
我們可以手工調整HashMap來調整性能,涉及到如容量、初始容量、尺寸、負載因子等概念。感興趣的話可以看一些相關資料。
一些建議
不要使用過時的容器 如Vector Enumeration Hashtable Stack(沒錯,這就是java最初的糟糕設計,實際中使用棧的話推薦LinkedList)進階·并發容器
這里不會討論的太細致的實現,僅僅簡單介紹一下基礎知識,感興趣的可以閱讀《Java 并發編程的藝術》這本書。
CopyOnWriteArrayList與Copy-On-Write策略
Copy-On-Write簡稱COW,是一種用于程序設計中的優化策略。其基本思路是,從一開始大家都在共享同一個內容,當某個人想要修改這個內容的時候,才會真正把內容Copy出去形成一個新的內容然后再改,這是一種延時懶惰策略。從JDK1.5開始Java并發包里提供了兩個使用CopyOnWrite機制實現的并發容器,它們是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用,可以在非常多的并發場景中使用到。
CopyOnWrite容器即寫時復制的容器。通俗的理解是當我們往一個容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行Copy,復制出一個新的容器,然后新的容器里添加元素,添加完元素之后,再將原容器的引用指向新的容器。這樣做的好處是我們可以對CopyOnWrite容器進行并發的讀,而不需要加鎖,因為當前容器不會添加任何元素。所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器。
CopyOnWrite容器只能保證數據的最終一致性,不能保證數據的實時一致性。所以如果你希望寫入的的數據,馬上能讀到,請不要使用CopyOnWrite容器。
ConcurrentLinkedQueue
在并發編程中,有時候需要使用線程安全的隊列或列表。通常實現線程安全有兩種方式,一種是使用阻塞算法,一種是使用非阻塞算法。非阻塞算法實現基礎為循環CAS(Compare and Swipe 比較和交換)。
ConcurrentLinkedQueue技術上的實現與CopyOnWriteArrayList與Copy類似,但是容器只有部分內容而不是整個容器可以被復制和修改。ConcurrentLinkedQueue有head節點和tail節點組成,每個節點由節點元素(item)和指向下一個結點(next)的引用組成。節點之間通過next關聯起來,形成一張鏈表結構的隊列。
ConcurrentHashMap與鎖分段技術
ConcurrentHashMap是線程安全且高效的HashMap。多線程環境下,使用非線程安全的HashMap會導致死循環,而如文章中建議的那樣,HashTable這種過時容器效率低下(使用synchronized來保證線程安全)。ConcurrentHashMap使用鎖分段技術,大大提高了并發使用的效率。
鎖分段技術: 假設容器有多把鎖,每一把鎖用于鎖容器其中一部分數據,當多線程訪問容器不同數據段數據時,線程間就不存在鎖競爭,從而提高并發訪問效率。
阻塞隊列
JDK7 提供了7個阻塞隊列,實現原理都是基于生產-消費模式的等待通知機制。
阻塞隊列類型 特點 ArrayBlockingQueue 由數組結構組成的有界阻塞隊列 LinkedBlockingQueue 由鏈表結構組成的有界阻塞隊列 PriorityBlockingQueue 支持優先級排序的無界阻塞隊列 DelayQueue 使用優先級隊列實現的無界阻塞隊列 SynchronousQueue 不儲存元素的阻塞隊列 LinkedTransferQueue 由鏈表結構組成的無界阻塞隊列 LinkedBlockingQueue 由鏈表結構組成的雙向阻塞隊列 學習Java的同學注意了!!! 學習過程中遇到什么問題或者想獲取學習資源的話,歡迎加入Java學習交流群,群號碼:183993990 我們一起學Java!
新聞熱點
疑難解答