Java核心技術梳理-集合

1、前言

在平常開發中,咱們常常會碰到須要在運行時才知道對象個數的狀況,這種狀況不能使用數組,由於數組是固定數量的,這個時候咱們就會使用集合,由於集合能夠存儲數量不肯定的對象。算法

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

集合類和數組不同,數據既能夠存儲基本類型,也能夠存儲對象,而集合只能存儲對象(對象的引用變量)。安全

Java集合大體分爲:數據結構

  • Set :無序,不可重複集合多線程

  • List:有序,可重複集合框架

  • Map:具備映射關係集合函數

  • Queue:隊列集合工具

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

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

2、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());

在平時的開發咱們能夠慢慢熟悉這些寫法。

3、Set

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

3.1 HashSet

顧名思義,HashSet是按照Hash算法來存儲集合中的元素,所以具備很好的存儲和查找性能,Hashset不是線程安全的,在多線程狀況下,咱們須要經過代碼來保證其同步,HashSet元素值能夠是null。

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

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

  1. 若是兩個對象的equals()爲true,可是hashCode()的返回值不相等,那這個時候HashSet認爲這兩個對象不等,都會保存,可是其實與咱們的指望就不同了。

  2. 若是兩個對象的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。

4、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());

5、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);

6、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。

7、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<Object> objects = Collections.synchronizedCollection(new ArrayList<>());

7.4 不可變集合

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

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

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

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

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

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

相關文章
相關標籤/搜索