以前大概分爲三種,Set
,List
,Map
三種,JDK5以後,增長Queue
.主要由Collection
和Map
兩個接口衍生出來,同時Collection
接口繼承Iterable
接口,因此咱們也能夠說java裏面的集合類主要是由Iterable
和Map
兩個接口以及他們的子接口或者其實現類組成。咱們能夠認爲Collection
接口定義了單列集合的規範,每次只能存儲一個元素,而Map
接口定義了雙列集合的規範,每次能存儲一對元素。java
Collection
接口關係不大,只是個別函數使用到。java集合最源頭的接口,實現這個接口的做用主要是集合對象能夠經過迭代器去遍歷每個元素。git
源碼以下:github
// 返回一個內部元素爲T類型的迭代器(JDK1.5只有這個接口) Iterator<T> iterator(); // 遍歷內部元素,action意思爲動做,指能夠對每一個元素進行操做(JDK1.8添加) default void forEach(Consumer<? super T> action) {} // 建立並返回一個可分割迭代器(JDK1.8添加),分割的迭代器主要是提供能夠並行遍歷元素的迭代器,能夠適應如今cpu多核的能力,加快速度。 default Spliterator<T> spliterator() { return Spliterators.spliteratorUnknownSize(iterator(), 0); }
從上面能夠看出,foreach
迭代以及可分割迭代,都加了default
關鍵字,這個是Java 8 新的關鍵字,之前接口的全部接口,具體子類都必須實現,而對於deafult
關鍵字標識的方法,其子類能夠不用實現,這也是接口規範發生變化的一點。
下面咱們分別展現三個接口的調用: redis
public static void iteratorHasNext(){ List<String> list=new ArrayList<String>(); list.add("Jam"); list.add("Jane"); list.add("Sam"); // 返回迭代器 Iterator<String> iterator=list.iterator(); // hashNext能夠判斷是否還有元素 while(iterator.hasNext()){ //next()做用是返回當前指針指向的元素,以後將指針移向下個元素 System.out.println(iterator.next()); } }
固然也可使用for-each loop
方式遍歷編程
for (String item : list) { System.out.println(item); }
可是實際上,這種寫法在class文件中也是會轉成迭代器形式,這只是一個語法糖。class文件以下:數組
public class IterableTest { public IterableTest() { } public static void main(String[] args) { iteratorHasNext(); } public static void iteratorHasNext() { List<String> list = new ArrayList(); list.add("Jam"); list.add("Jane"); list.add("Sam"); Iterator<String> iterator = list.iterator(); Iterator var2 = list.iterator(); while(var2.hasNext()) { String item = (String)var2.next(); System.out.println(item); } } }
須要注意的一點是,迭代遍歷的時候,若是刪除或者添加元素,都會拋出修改異常,這是因爲快速失敗【fast-fail】機制。安全
public static void iteratorHasNext(){ List<String> list=new ArrayList<String>(); list.add("Jam"); list.add("Jane"); list.add("Sam"); for (String item : list) { if(item.equals("Jam")){ list.remove(item); } System.out.println(item); } }
從下面的錯誤咱們能夠看出,第一個元素是有被打印出來的,也就是remove操做是成功的,只是遍歷到第二個元素的時候,迭代器檢查,發現被改變了,因此拋出了異常。多線程
Jam Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList$Itr.next(ArrayList.java:859) at IterableTest.iteratorHasNext(IterableTest.java:15) at IterableTest.main(IterableTest.java:7)
其實就是把對每個元素的操做當成了一個對象傳遞進來,對每個元素進行處理。app
default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } ```java 固然像ArrayList天然也是有本身的實現的,那咱們就可使用這樣的寫法,簡潔優雅。forEach方法在java8中參數是`java.util.function.Consumer`,能夠稱爲**消費行爲**或者說**動做**類型。 ```java list.forEach(x -> System.out.print(x));
同時,咱們只要實現Consumer
接口,就能夠自定義動做,若是不自定義,默認迭代順序是按照元素的順序。框架
public class ConsumerTest { public static void main(String[] args) { List<String> list=new ArrayList<String>(); list.add("Jam"); list.add("Jane"); list.add("Sam"); MyConsumer myConsumer = new MyConsumer(); Iterator<String> it = list.iterator(); list.forEach(myConsumer); } static class MyConsumer implements Consumer<Object> { @Override public void accept(Object t) { System.out.println("自定義打印:" + t); } } }
輸出的結果:
自定義打印:Jam 自定義打印:Jane 自定義打印:Sam
這是一個爲了並行遍歷數據元素而設計的迭代方法,返回的是Spliterator
,是專門並行遍歷的迭代器。以發揮多核時代的處理器性能,java默認在集合框架中提供了一個默認的Spliterator
實現,底層也就是Stream.isParallel()實現的,咱們能夠看一下源碼:
// stream使用的就是spliterator default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); } default Spliterator<E> spliterator() { return Spliterators.spliterator(this, 0); } public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) { Objects.requireNonNull(spliterator); return new ReferencePipeline.Head<>(spliterator, StreamOpFlag.fromCharacteristics(spliterator), parallel); }
使用的方法以下:
public static void spliterator(){ List<String> list = Arrays.asList("1", "2", "3","4","5","6","7","8","9","10"); // 獲取可迭代器 Spliterator<String> spliterator = list.spliterator(); // 一個一個遍歷 System.out.println("tryAdvance: "); spliterator.tryAdvance(item->System.out.print(item+" ")); spliterator.tryAdvance(item->System.out.print(item+" ")); System.out.println("\n-------------------------------------------"); // 依次遍歷剩下的 System.out.println("forEachRemaining: "); spliterator.forEachRemaining(item->System.out.print(item+" ")); System.out.println("\n------------------------------------------"); // spliterator1:0~10 Spliterator<String> spliterator1 = list.spliterator(); // spliterator1:6~10 spliterator2:0~5 Spliterator<String> spliterator2 = spliterator1.trySplit(); // spliterator1:8~10 spliterator3:6~7 Spliterator<String> spliterator3 = spliterator1.trySplit(); System.out.println("spliterator1: "); spliterator1.forEachRemaining(item->System.out.print(item+" ")); System.out.println("\n------------------------------------------"); System.out.println("spliterator2: "); spliterator2.forEachRemaining(item->System.out.print(item+" ")); System.out.println("\n------------------------------------------"); System.out.println("spliterator3: "); spliterator3.forEachRemaining(item->System.out.print(item+" ")); }
結果以下:
tryAdvance: 1 2 ------------------------------------------- forEachRemaining: 3 4 5 6 7 8 9 10 ------------------------------------------ spliterator1: 8 9 10 ------------------------------------------ spliterator2: 1 2 3 4 5 ------------------------------------------ spliterator3: 6 7
還有一些其餘的用法在這裏就不列舉了,主要是trySplit()以後,能夠用於多線程遍歷。理想的時候,能夠平均分紅兩半,有利於並行計算,可是不是必定平分的。
Collection
接口能夠算是集合類的一個根接口之一,通常不可以直接使用,只是定義了一個規範,定義了添加,刪除等管理數據的方法。繼承Collection
接口的有List
,Set
,Queue
,不過Queue
定義了本身的一些接口,相對來講和其餘的差別比較大。
源碼以下:
boolean add(Object o) //添加元素 boolean remove(Object o) //移除元素 boolean addAll(Collection c) //批量添加 boolean removeAll(Collection c) //批量移除 void retainAll(Collection c) // 移除在c中不存在的元素 void clear() //清空集合 int size() //集合大小 boolean isEmpty() //是否爲空 boolean contains(Object o) //是否包含在集合中 boolean containsAll(Collection c) //是否包含全部的元素 Iterator<E> iterator() // 獲取迭代器 Object[] toArray() // 轉成數組 default boolean removeIf(Predicate<? super E> filter) {} // 刪除集合中複合條件的元素,刪除成功返回true boolean equals(Object o) int hashCode() default Spliterator<E> spliterator() {} //獲取可分割迭代器 default Stream<E> stream() {} //獲取流 default Stream<E> parallelStream() {} //獲取並行流
裏面獲取並行流的方法parallelStream()
,其實就是經過默認的ForkJoinPool(主要用來使用分治法(Divide-and-Conquer Algorithm)來解決問題),提升多線程任務的速度。咱們可使用ArrayList來演示一下平行處理能力。例以下面的例子,輸出的順序就不必定是1,2,3...,多是亂序的,這是由於任務會被分紅多個小任務,任務執行是沒有特定的順序的。
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); list.parallelStream() .forEach(out::println);
graph LR; Collection -->List-有順序,可重複 List-有順序,可重複 -->LinkedList-使用鏈表實現,線程不安全 List-有順序,可重複 -->ArrayList-數組實現,線程不安全 List-有順序,可重複 -->Vector-數組實現,線程安全 Vector-數組實現,線程安全 -->Stack-堆棧,先進後出 Collection-->Set-不可重複,內部排序 Set-不可重複,內部排序-->HashSet-hash表存儲 HashSet-hash表存儲-->LinkHashSet-鏈表維護插入順序 Set-不可重複,內部排序-->TreeSet-二叉樹實現,排序 Collection-->Queue-隊列,先進先出
繼承於Collection
接口,有順序,取出的順序與存入的順序一致,有索引,能夠根據索引獲取數據,容許存儲重複的元素,能夠放入爲null的元素。
最多見的三個實現類就是ArrayList
,Vector
,LinkedList
,ArrayList
和Vector
都是內部封裝了對數組的操做,惟一不一樣的是,Vector
是線程安全的,而ArrayList
不是,理論上ArrayList
操做的效率會比Vector
好一些。
裏面是接口定義的方法:
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(Collection<?> c); // 是否包含全部的元素 boolean addAll(Collection<? extends E> c); //批量添加 boolean addAll(int index, Collection<? extends E> c); //批量添加,指定開始的索引 boolean removeAll(Collection<?> c); //批量移除 boolean retainAll(Collection<?> c); //將c中不包含的元素移除 default void replaceAll(UnaryOperator<E> operator) {}//替換 default void sort(Comparator<? super E> c) {}// 排序 void clear();//清除全部的元素 boolean equals(Object o);//是否相等 int hashCode(); //計算獲取hash值 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); //獲取對象元素的最後一個元素 ListIterator<E> listIterator(); // 獲取List迭代器 ListIterator<E> listIterator(int index); // 根據索引獲取當前的位置的迭代器 List<E> subList(int fromIndex, int toIndex); //截取某一段數據 default Spliterator<E> spliterator(){} //獲取可切分迭代器
上面的方法都比較簡單,值得一提的是裏面出現了ListIterator
,這是一個功能更增強大的迭代器,繼承於Iterator
,只能用於List
類型的訪問,拓展功能例如:經過調用listIterator()
方法得到一個指向List開頭的ListIterator
,也能夠調用listIterator(n)
獲取一個指定索引爲n的元素的ListIterator
,這是一個能夠雙向移動的迭代器。
操做數組索引的時候須要注意,因爲List的實現類底層不少都是數組,因此索引越界會報錯IndexOutOfBoundsException
。
提及List的實現子類:
Set
接口,不容許放入重複的元素,也就是若是相同,則只存儲其中一個。
下面是源碼方法:
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(Collection<?> c); //是否包含全部的元素 boolean addAll(Collection<? extends E> c); //批量添加 boolean retainAll(Collection<?> c); //移除全部不存在於c集合中的元素 boolean removeAll(Collection<?> c); //移除全部在c集合中存在的元素 void clear(); //清空集合 boolean equals(Object o); //是否相等 int hashCode(); //計算hashcode default Spliterator<E> spliterator() {} //獲取可分割迭代器
主要的子類:
隊列接口,在Collection接口的接觸上添加了增刪改查接口定義,通常默認是先進先出,即FIFO,除了優先隊列和棧,優先隊列是本身定義了排序的優先順序,隊列中不容許放入null元素。
下面是源碼:
boolean add(E e); //插入一個元素到隊列,失敗時返回IllegalStateException (若是隊列容量不夠) boolean offer(E e); //插入一個元素到隊列,失敗時返回false E remove(); //移除隊列頭的元素並移除 E poll(); //返回並移除隊列的頭部元素,隊列爲空時返回null E element(); //返回隊列頭元素 E peek(); //返回隊列頭部的元素,隊列爲空時返回null
主要的子接口以及實現類有:
下面的源碼的方法:
V put(K key, V value); // 添加元素 V remove(Object key); // 刪除元素 void putAll(Map<? extends K, ? extends V> m); // 批量添加 void clear() // 移除全部元素 V get(Object key); // 經過key查詢元素 int size(); // 查詢集合大小 boolean isEmpty(); // 集合是否爲空 boolean containsKey(Object key); // 是否包含某個key boolean containsValue(Object value); // 是否包含某個value Set<K> keySet(); // 獲取全部key的set集合 Collection<V> values(); // 獲取全部的value的set集合 Set<Map.Entry<K, V>> entrySet(); // 返回鍵值對的set,每個鍵值對是一個entry對象 boolean equals(Object o); // 用於比較的函數 int hashCode(); // 計算hashcode default V getOrDefault(Object key, V defaultValue) // 獲取key對應的Value,沒有則返回默認值() default void forEach(BiConsumer<? super K, ? super V> action) {} // 遍歷 default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {} // 批量替換 // 缺乏這個key的時候纔會添加進去 // 返回值是是key對應的value值,若是不存在,則返回的是剛剛放進去的value default V putIfAbsent(K key, V value) {} default boolean remove(Object key, Object value) {} // 移除元素 default boolean replace(K key, V oldValue, V newValue) {} // 替換 default V replace(K key, V value) {} //替換 // 和putIfAbsent有點像,只不過傳進去的mappingFunction是映射函數,也就是若是不存在key對應的value,將會執行函數,函數返回值會被當成value添加進去,同時返回新的value值 default V computeIfAbsent(K key,Function<? super K, ? extends V> mappingFunction) {} // 和computeIfAbsent方法相反,只有key存在的時候,纔會執行函數,而且返回 default V computeIfPresent(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction) {} // 無論如何都會執行映射函數,返回value default V compute(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction) {} default V merge(K key, V value,BiFunction<? super V, ? super V, ? extends V> remappingFunction) {}
值得注意的是,Map裏面定義了一個Entry類,其實就是定義了一個存儲數據的類型,一個entry就是一個<key,value>.
Map的經常使用的實現子類:
這些集合原始接口究竟是什麼?爲何須要?
我想,這些接口其實都是一種規則/規範的定義,若是不這麼作也能夠,全部的子類本身實現,可是從迭代以及維護的角度來講,這就是一種抽象或者分類,好比定義了Iterator接口,某一些類就能夠去繼承或者實現,那就得遵照這個規範/契約。能夠有所拓展,每一個子類的拓展不同,因此每一個類就各有所長,可是都有一箇中心,就是原始的集合接口。好比實現Map接口的全部類的中心思想都不變,只是各有所長,各分千秋,造成了大千集合世界。
【做者簡介】:
秦懷,公衆號【秦懷雜貨店】做者,技術之路不在一時,山高水長,縱使緩慢,馳而不息。我的寫做方向:Java源碼解析,JDBC,Mybatis,Spring,redis,分佈式,劍指Offer,LeetCode等,認真寫好每一篇文章,不喜歡標題黨,不喜歡花裏胡哨,大多寫系列文章,不能保證我寫的都徹底正確,可是我保證所寫的均通過實踐或者查找資料。遺漏或者錯誤之處,還望指正。
平日時間寶貴,只能使用晚上以及週末時間學習寫做,關注我,咱們一塊兒成長吧~