Java核心技術梳理-集合

Java核心技術梳理-集合
目錄

一、前言
二、Collection 和 Iterator
2.1 Collection
2.2 Iterator
2.3 foreach
2.4 Predicate
2.4 Stream
三、Set
3.1 HashSet
3.2 LinkedHashSet
3.2 TreeSet
3.4 EnumSet
3.5 性能選擇
四、List
4.1 ArrayList 、Vector、LinkedList
五、Queue
5.1 PriorityQueue
5.2 ArrayDeque
六、Map
6.1 HashMap與Hashtable
6.2 LinkedHashMap
6.3 TreeMap
6.4 WeakHashMap
6.5 EnumMap
七、Collections工具類
7.1 排序
7.2 查找、替換操作
7.3 同步控制
7.4 不可變集合

回到頂部
一、前言
在日常開發中,我們經常會碰到需要在運行時才知道對象個數的情況,這種情況不能使用數組,因爲數組是固定數量的,這個時候我們就會使用集合,因爲集合可以存儲數量不確定的對象。

集合類是特別有用的工具類,不僅可以存儲數量不等的對象,還可以實現常用的數據結構,並且能夠存儲有映射關聯的關聯數組。

集合類和數組不一樣,數據既可以存儲基本類型,也可以存儲對象,而集合只能存儲對象(對象的引用變量)。

Java集合大致分爲:

Set :無序,不可重複集合

List:有序,可重複集合

Map:具有映射關係集合

Queue:隊列集合

Java的集合類主要是由兩個接口派生出來:Collection 和Map 。

集合的框架可看此圖:http://img.blog.csdn.net/20160124221843905

回到頂部
二、Collection 和 Iterator
2.1 Collection
Collection 接口是List、Set、Queue的父接口,其中定義了一些集合操作的通用方法,集合類似一個容器,而容器無非是添加對象、刪除對象、清空容器、判斷容器是否爲空。

複製代碼
Collection collection = new ArrayList();
//添加
collection.add("晚安");
collection.add(9);
//返回長度
System.out.println(collection.size());
//移除
collection.remove(9);
//是否包含
System.out.println(collection.contains("晚安"));
//是否爲空
System.out.println(collection.isEmpty());
Collection books = new HashSet();
books.add("晚安");
books.add("願長夜無夢");
books.add("在所有夜晚安眠");
//去掉collection 包含的元素
books.removeAll(collection);
System.out.println(books);
books.add("晚安");
//保留兩者都有的數據
books.retainAll(collection);
System.out.println(books);
複製代碼
Collection 繼承了Iterable接口,Java 8爲Iterable提供了forEach方法,且這個方法的參數是一個函數式接口,我們可以通過這個方法進行集合遍歷,並且可以使用Lambda表達式。

books.forEach(p -> System.out.println(p));
2.2 Iterator
複製代碼
//獲取迭代器
Iterator iterator = books.iterator();
//判斷是否遍歷完成
while (iterator.hasNext()){

//獲取集合中的下一個元素,返回的Object對象,需要強制轉換
String text = (String)iterator.next();
System.out.println(text);
//這裏的刪除對象是迭代器中刪除,刪除的是上一個next返回的方法,而且不會真正刪除books中的內容
iterator.remove();
//會報錯
books.remove(text);

}
複製代碼
我們看到這裏有一個刪除方法,但是刪除的並不是books的內容,而且如果修改了其中的內容,實際的內容也不會改變,這裏我們就可以得出結論:集合並不是把本身傳給了迭代器,而是將集合中的元素傳遞給了迭代器

迭代器採用的是快速失敗機制,一旦在迭代過程中發現集合被改變,立即拋出錯誤,這樣可以避免共享了資源而導致數據不一致問題。

我們也可以直接通過forEachRemaining 來遍歷,這也是一個函數式接口

iterator.forEachRemaining(p-> System.out.println(p));
2.3 foreach
除了迭代器之外,我們也可以直接通過 foreach遍歷集合,且這種寫法更便捷

for (Object s : books) {

System.out.println(s);

}
與迭代器相同,這裏循環的也不是集合本身,而是元素,並且也不能修改。

2.4 Predicate
Java 8爲Collection 提供了一個removeIf(Predicate<? super E> filter) 方法,這個方法是批量刪除符合條件的元素,這也是一個函數式接口,我們可以使用Lambda表達式。

books.removeIf(p -> ((String) p).length() > 5);
這個Predicate 我們可以充分的利用,它可以充分簡化集合運算,如:

複製代碼
public static int count(Predicate predicate, Collection collection) {

int total = 0;
for (Object object : collection) {
    //判斷是否滿足條件
    if (predicate.test(object)) {
        total++;
    }
}
return total;

}
複製代碼
System.out.println(count(p -> ((String) p).length() > 5, books));
2.4 Stream
Collection 還有一個Stream()流式API,流式API在JQuery中常常會用到,主要分爲中間方法和末端方法,顧名思義,中間方法就是允許繼續調用後續方法,而末端方法是最終的操作。Stream的引入極大的豐富了集合的操作。

常用的中間方法有

filter(Predicate<? super T> predicate) :過濾不符合條件的集合

sorted:排序

limit(long maxSize) :對數量進行控制,一般是排序之後的操作

distinct():去重

常用的末端方法有

forEach(Consumer<? super T> action):遍歷

toArray():轉換成數據

min(Comparator<? super T> comparator):獲取最小值

max(Comparator<? super T> comparator) :獲取最大值

count() :總數

我們可以很方便的組合這些API,而對集合進行操作,簡單的例子如下:

System.out.println(books.stream().filter(p->((String) p).contains("夜")).count());
在平時的開發我們可以慢慢熟悉這些寫法。

回到頂部
三、Set
Set不記住添加順序,也就是並不會按照添加順序進行排序,並且不允許包含重複元素,當添加了重複元素時,add方法會返回false,下面分別介紹其實現類HashSet,TreeSet,LinkedHashSet,EnumSet。

3.1 HashSet
顧名思義,HashSet是按照Hash算法來存儲集合中的元素,因此具有很好的存儲和查找性能,Hashset不是線程安全的,在多線程情況下,我們需要通過代碼來保證其同步,HashSet元素值可以是null。

HashSet是通過判斷兩個對象equals()相等並且hashCode()的返回值也相等來決定這兩個對象是否爲同一對象的。

那這個時候就有些問題了,

如果兩個對象的equals()爲true,但是hashCode()的返回值不相等,那這個時候HashSet認爲這兩個對象不等,都會保存,但是其實與我們的期望就不一樣了。

如果兩個對象的hashCode()返回值相等,但是equals()爲false,這個時候也會保存,但是會保存在同一個位置,並通過鏈式結構來保存,這樣會對性能產生影響。

所以我們要將對象保存到HashSet中,我們就要儘量保證兩個對象在equals()爲true時,其返回的hashCode()的值也要相等。

3.2 LinkedHashSet
LinkedHashSet是HashSet的子類,LinkedHashSet也是通過hashCode來確定位置的,但是從名字中可以看出,它還通過鏈表進行了插入次序的維護,也就說是遍歷的時候可以是有順序的,但是加入了排序意味着性能的降低。

3.2 TreeSet
TreeSet是SortedSet的實現類,這就意味着TreeSet可以確保集合元素處於排序狀態,既然需要排序,那就有排序規則,TreeSet有兩個排序方法:自然排序和定製排序。

自然排序:TreeSet是調用compareTo方法來比較元素之間的大小。

定製排序:定製排序就是我們按照我們制定的規則來進行排序。

複製代碼
TreeSet treeSet=new TreeSet((o1,o2)->
{
String m1 = (String)o1;
String m2=(String)o2;
return m1.length()>m2.length()?-1:0;
});
複製代碼
由於要進行排序,所以TreeSet添加的必須是同一個類元素,否則會報錯。

因爲增加了排序,所以相應的也增加了一些方法:

複製代碼
TreeSet treeSet1 = new TreeSet<>();
treeSet1.add(1);
treeSet1.add(2);
treeSet1.add(3);
//之前的一個元素
System.out.println(treeSet1.lower(2));
//後一個元素
System.out.println(treeSet1.higher(2));
//第一個元素
System.out.println(treeSet1.first());
//最後一個元素
System.out.println(treeSet1.last());
複製代碼
3.4 EnumSet
EnumSet是專門存儲枚舉的集合,所有的元素都必須是指定枚舉類型的枚舉值,EnumSet也是有序的,排序規則與枚舉定義的順序相同。

EnumSet在內部以位向量方式存儲,存儲非常緊湊、高效,運行效率也很好,EnumSet不允許加null。

3.5 性能選擇
如何選擇HashSet和TreeSet呢?從性能方面來講,HashSet要好,因爲TreeSet需要額外的紅黑樹算法來排序,所以如果在不需要排序的情況下,我們都是選擇HashSet。

回到頂部
四、List
List是有序的,可重複的集合,每個元素都可以通過對應的索引來進行訪問,List繼承了Collection,Collection中的方法List都能使用,而List作爲有序集合,也就有一些與索引相關的方法。

複製代碼
List list = new ArrayList();
list.add("晚安");
list.add("願路途遙遠");
list.add("都有人陪在身邊");
list.forEach(p-> System.out.println(p));
list.remove(1);
//在索引處添加數據
list.add(1, "願路途遙遠");
//獲取指定索引位置元素
System.out.println(list.get(2));
System.out.println(list.size());
//設置索引位置的數據,index必須在現有的長度之內
list.set(2, "想要說的話還沒說完");
//返回fromIndex(包含),到toIndex(不包含)集合至新集合
List list1 = list.subList(0, 2);
//排序,比較函數
list.sort((o1, o2) -> ((String) o1).length() - ((String) o2).length());
//將字符串長度作爲新的集合元素替換原來的集合
list.replaceAll(p -> ((String) p).length());
list.forEach(p-> System.out.println(p));
複製代碼
4.1 ArrayList 、Vector、LinkedList
ArrayList 、Vector、LinkedList 是list的三個實現類,完全支持前面list接口實現的全部功能。

ArrayList 、Vector 是基於數組實現的,內部封裝了一個動態的、允許再分配的Object[] 數組,初始化是通過initialCapacity參數確定初始長度,如果不指定的話默認是10,當我們能確定數組的長度時,我們可以給出,這樣可以減少重新分配的次數,而提高性能。

ArrayList 、Vector在使用上完全相同,而Vector出現的較早,所有其中的一些方法名較長,而後改成List接口的方法後增加了一些方法,但是與其之前的方法有一些重複,我們一般都喜歡使用新東西的嘛,雖然Vector 線程安全,但如果我們使用Collections工具類同樣可以使ArrayList 線程安全,所以總結就是使用ArrayList 就完事了。

LinkedList的內部實現與ArrayList 、Vector完全不同,它的內部實現是通過鏈表來存儲的,並且它還繼承了Deque接口,也即是可以當做雙端隊列來使用,由此可見它功能的強大。

複製代碼
LinkedList linkedList = new LinkedList();
//將字符串放入隊列尾部
linkedList.offer("隊列尾部字符串");
//將字符放入棧頂部
linkedList.push("棧頂部字符串");
//將字符串放入到隊列的頭部
linkedList.offerFirst("隊列頭部字符串");
linkedList.forEach(p-> System.out.println(p));
//訪問不刪除棧頂元素
System.out.println(linkedList.peekFirst());
//訪問不刪除隊列的最後一個元素
System.out.println(linkedList.peekLast());
//彈出棧頂元素
System.out.println(linkedList.pop());
//訪問並刪除隊列的最後一個元素
System.out.println(linkedList.pollLast());
複製代碼
回到頂部
五、Queue
Queue 用於模擬隊列這種數據結構,也就是先進先出的容器,隊列簡單理解就是排隊打飯,先排隊的人先吃飯,後來的就到隊列尾部,隊列通常不允許隨機訪問數據(這樣就相當於插隊了)。有以下方法:add(E e)

add(E e) :添加元素到尾部。

offer(E e):也是添加元素到尾部,不過在使用容量有限制的隊列時,效率比add要高。

remove():獲取頭部元素並刪除。

poll():獲取尾部元素並刪除。

element():獲取頭部元素,但不刪除。

peek():獲取頭部元素,但不刪除,隊列爲空返回null

Queue接口有PriorityQueue 實現類,除此之外,Queue 還有一個Deque 子接口,是一個雙端隊列,可以從兩端來添加和刪除元素,這樣Deque實現類既可以當隊列使用,也可以當棧使用,上面的LinkedList就是其實現子類,另外還有一個ArrayDeque。

5.1 PriorityQueue
PriorityQueue並不是一個標準的隊列,因爲它保存隊列的順序不是按照添加的順序,而是按照大小去進行排序的,這樣其實違反了隊列的基本原則:先進先出,而排序的規則與之前說的TreeSet相同,這裏就不贅述了。

5.2 ArrayDeque
ArrayDeque實現的是Deque,也就是說它是雙端隊列,簡單理解就是既可以當隊列使用,又可以當棧使用,當我們需要棧這種數據結構時,推薦使用ArrayDeque,Stack是古老的集合,不推薦使用。

我們分別將ArrayDeque 當做棧和隊列來使用下:

棧:

複製代碼
ArrayDeque stack = new ArrayDeque();
stack.push("晚安");
stack.push("願路途遙遠");
stack.push("都有人陪在身邊");
System.out.println(stack);
//訪問第一個元素,但不彈出
System.out.println(stack.peek());
//訪問第一個元素,並且彈出
System.out.println(stack.pop());
System.out.println(stack);
複製代碼
隊列:

複製代碼
ArrayDeque queue=new ArrayDeque<>();
queue.offer("晚安");
queue.offer("願長夜無夢");
queue.offer("在每個夜晚安眠");
System.out.println(queue);
//訪問隊列頭部元素,但不刪除
System.out.println(queue.peek());
//訪問隊列頭部元素,並且刪除
System.out.println(queue.poll());
System.out.println(queue);
複製代碼
回到頂部
六、Map
Map用於存儲具有映射關係的數據,也就是鍵值對,Map集合保存着兩組值,一組存key,另一組存value,這兩組數據可以是任何應用類型的數據,key不允許重複,key和value存在單向的一對一關係。

Map中key 組合起來是一個Set集合,key沒有順序,也不能重複,Map中有個keySet()方法就是獲取key集合。

Map的一些常用方法如下:

複製代碼
HashMap map = new HashMap<>();
//放入數據
map.put(1,"宋江");
map.put(2,"盧俊義");
map.put(3,"吳用");
//如果原先位置存在數據時會返回原先的數據
System.out.println(map.put(3,"武松"));
//是否存在某key
System.out.println(map.containsKey(2));
//是否存在某value
System.out.println(map.containsValue("武松"));
//是否爲空
System.out.println(map.isEmpty());
//獲取長度
System.out.println(map.size());
//循環key值
for (Object key: map.keySet()) {

//通過key值直接獲取value
System.out.println(map.get(key));

}
//根據key移除元素
System.out.println(map.remove(3));
//新的循環方式
map.forEach((key,value)-> System.out.println(key+":"+value));
//獲取value,不存在則返回默認值
map.getOrDefault(8,"查無此人");
//只是替換,不會新增
map.replace(2,"林沖");
//清空數據
map.clear();
複製代碼
6.1 HashMap與Hashtable
HashMap與Hashtable都是Map接口的典型實現類,他們關係類似ArrayList與Vector,Hashtable早出現且線程安全,但是實現並不好,HashMap性能更好但線程不安全,Hashtable的key和value不允許爲空,但是HashMap可以,我們一般也是推薦使用HashMap,即使需要線程安全也可以使用Collections工具類。

我們要正確的存儲key,就要讓作爲key的對象必須實現hashCode()和equals()方法,那我們判斷兩個key值是否相等,也是和HashSet相同,必須hashCode()相等,equals()返回爲true。

除了key值之外,我們有時候也要比較value值是否相等containsValue(),這裏判斷的話只需要equals()返回爲true即可。

6.2 LinkedHashMap
HashMap也有一個子類 LinkedHashMap,使用的雙向鏈表來維護key-value的次序,鏈表維護了迭代順序,迭代順序與插入順序相同。LinkedHashMap需要維護元素的插入順序,那性能比HashMap要低,但因爲其維護了順序,迭代的時候就更快。

6.3 TreeMap
TreeMap是一個紅黑樹數據結構,每一個key-value即爲紅黑樹的一個節點,存儲時根據key進行節點排序,TreeMap保證key-value處於有序狀態,也是兩個排序機制,自然排序和定製排序,跟之前講的類似。

因爲TreeMap是有序的,那麼就會提供一些訪問前一個,後一個,第一個,最後一個這種方法,具體方法參考API文檔。

6.4 WeakHashMap
從名字上就可以看出來WeakHashMap 是一個弱引用對象,HashMap的key保留了對對象的強引用,意味着只要HashMap對象不被銷燬,那麼HashMap所引用的對象就不會被銷燬,HashMap也不會自動的刪除這些key對應的key-value,而WeakHashMap則不行。

6.5 EnumMap
EnumMap 是與枚舉類一起使用的,也就是說每個EnumMap 的key必須是一個枚舉值,創建EnumMap時必須顯示或隱式的指定對應的枚舉類,EnumMap在內部已數組形式存儲,緊湊而高效,並且按照枚舉類定義的順序進行排序,不允許key爲null,但運行value爲null。

回到頂部
七、Collections工具類
Collections工具類在上面已經提到過,就是用於操作集合的工具類,對集合的操作有排序、查找、同步控制、設置不可變。

7.1 排序
Collections提供瞭如下方法對list進行排序

複製代碼
ArrayList list = new ArrayList<>();
list.add(2);
list.add(8);
list.add(5);
list.add(10);
list.add(7);
System.out.println("----自然排序----");
//自然排序
Collections.sort(list);
list.forEach(p-> System.out.println(p));
System.out.println("----反轉----");
//反轉
Collections.reverse(list);
list.forEach(p-> System.out.println(p));
System.out.println("----隨機排序----");
//隨機排序,相當於洗牌
Collections.shuffle(list);
list.forEach(p-> System.out.println(p));
System.out.println("----定製排序規則----");
//定製排序規則
Collections.sort(list,(o1,o2)->(o1-o2));
list.forEach(p-> System.out.println(p));
System.out.println("----定製排序規則----");
//調換list中指定位置的順序
Collections.swap(list,2,4);
list.forEach(p-> System.out.println(p));
System.out.println("----將list最後的兩個元素移到前面----");
//將list最後的兩個元素移到前面
Collections.rotate(list,2);
list.forEach(p-> System.out.println(p));
System.out.println("----將list最後的兩個元素移到前面----");
//將list中前面的兩個元素移到後面
Collections.rotate(list,-2);
list.forEach(p-> System.out.println(p));
複製代碼
7.2 查找、替換操作
Collections提供瞭如下方法對list進行查找、替換操作

複製代碼
ArrayList list = new ArrayList<>();
list.add(2);
list.add(8);
list.add(5);
list.add(10);
list.add(7);
list.add(7);
//自然排序
Collections.sort(list);
//二分法查找list,帶入的參數爲value,返回的爲索引值(必須是排序之後)
System.out.println(Collections.binarySearch(list,10));
//最大值
System.out.println(Collections.max(list));
//最小值
System.out.println(Collections.min(list));
//出現的次數
System.out.println(Collections.frequency(list,8));
//新值替換所有的舊值
Collections.replaceAll(list,8,6);
list.forEach(p-> System.out.println(p));
//全部替換
Collections.fill(list,8);
複製代碼
7.3 同步控制
上面提過很多次可以使用Collections可以是集合變成線程安全,只要調用synchronizedXXX()便可以創建線程按照的集合

如:

Collection

emptyXXX():返回一個不可變的、空的集合對象

singletonXXX():返回一個只包含一個對象的,不可變的集合

unmodifiableXXX():返回指定集合的不可變視圖

Collections.emptyList();
Collections.singletonList("原來是這樣");
ArrayList list = new ArrayList<>();
Collections.unmodifiableCollection(list);
集合的介紹和基本用法就是這樣,當然這只是使用,後面還會進行源碼的分析

作者:茶底世界
出處:https://www.cnblogs.com/yuanqinnan/
源碼地址:https://github.com/yuanqinnan/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章