1. 程式人生 > >Java核心技術梳理-集合

Java核心技術梳理-集合

關聯數組 odi jquery 由於 基本用法 頭部 query 全部 批量刪除

目錄

一、前言
二、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<Integer> 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<String> 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<String> 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<String> 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<Integer, String> 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<Integer> 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<Integer> 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&lt;Object&gt; objects = Collections.synchronizedCollection(new ArrayList&lt;&gt;());
7.4 不可變集合

Collections提供了三類方法來獲取不可變集合

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

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

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

Collections.emptyList();
Collections.singletonList("原來是這樣");
ArrayList<Integer> list = new ArrayList<>();
Collections.unmodifiableCollection(list);

集合的介紹和基本用法就是這樣,當然這只是使用,後面還會進行源碼的分析

Java核心技術梳理-集合