集合知識點整理

1.前言:數據結構——隊列

隊列接口先說明有哪些功能,但不說是如何實現的,隊列有兩種實現方式:

  • 循環數組
  • 鏈表

循環數組更加高效,但循環數組是個有界集合,容量有限,如果對象的數量沒有上限,最好用鏈表來實現。

一旦構建了集合就不需要知道到底使用了哪種實現。所以可以使用接口類型存放集合的引用

List<String> list = new ArrayList<>();
list.add("abc");

如果我們想修改成另一種實現,只需要把ArrayList改成LinkedList


2.Collection 接口

collection接口有兩個基本方法:

// 如果添加元素確實改變了集合就返回 true;如果沒有改變就返回 false,比如添加的元素已經存在就不會改變原集合
boolean add(E element);

// 迭代器,見下一點
Iterator<E> iterator();

3.迭代器

Iterator接口有4個方法:

public interface Iterator<E>{
    E next();
    boolean hasNext();
    void remove();
    default void forEachRemaining(Student<? super E> stu);
}

反復調用 next() 方法,能逐個訪問集合的每個元素,但如果訪問到了集合的末尾,會拋出一個 NoSuchElementException,所以我們在調用next()前最好使用hasNext()檢查下。

List<String> list = new ArrayList<>();
list.add("abc");
list.add("123");
list.add("qwe");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next()); // abc 123 qwe
}

當然,如果要循環一個集合,更簡單的方法是使用forEach:

for (String s : list) {
    System.out.println(s);
}

也可以使用forEachRemaining

iterator.forEachRemaining(element -> System.out.println(iterator.next()));

順序問題:元素被訪問的順序取決于集合類型,如果對 ArrayList 進行迭代,迭代器將從索引0開始,每迭代一次,索引值加1。如果是 HashSet ,每個元素會按照某種特定的次序出現,雖然也能訪問到所有元素,但順序是隨機的。

remove()刪除的是上次調用 next 方法時返回的元素。所以調用 remove 之前必須要有 next 方法被調用。


4.集合中的接口

集合框架的接口

需要注意的是:數組支持的有序集合因為可以快速隨機訪問,所有最好提供一個整數索引來訪問,鏈表最好使用迭代器來遍歷。


5.Java 庫中的具體集合

image-20180909112447085
集合類型 描述
ArrayList 一種可以動態增長和縮減的索引序列
LinkedList 一種可以在任何位置進行高效地插入和刪除操作的有序序列
ArrayDeque 一種用循環數組實現的雙端隊列
HashSet 一種沒有重復元素的無序集合
TreeSet 一種有序集
EnumSet 一種包含枚舉類型值的集
LinkedHashSet 一種可以記住元素插入次序的集
PriorityQueue 一種允許高效刪除最小元素的集合
HashMap 一種存儲鍵 / 值關聯的數據結構
TreeMap 一種鍵值有序排列的映射表
EnumMap 一種鍵值屬于枚舉類型的映射表
LinkedHashMap 一種可以記住鍵 / 值添加次序的映射表
WeakHashMap 一種其值無用武之地后可以被垃圾回收器回收的映射表
IdentityHashMap 一種用 == 而不是 equals 比較鍵值的映射表

6.鏈表

數組列表的問題:從中間刪除一個元素之后,該元素后面的元素都需要向前移動,在中間插入也是。

鏈表就沒有這個問題。

鏈表讓我想到了比特幣的區塊結構,看圖吧

image-20180909122239957

每個 Link 可以看成一個節點,每個節點存放著前一個節點的引用和后一個節點的引用,所以鏈表實際上都是雙向鏈接的。回到數組列表的問題,當我們需要插入一個元素的時候,只需要修改該節點以及其前后節點的引用就可以了。

但鏈表不支持快速的隨機訪問,如果要查看第100個元素,就必須從頭開始,越過99個元素。所以如果程序需要采用整數索引訪問元素時,通常不選用鏈表。


7.數組列表

List 接口用于描述一個有序集合,并且集合中每個元素的位置十分重要。有兩種訪問元素的協議:

  1. 用迭代器
  2. 用 get 和 set 方法隨機訪問每個元素

8.散列集

如果我們想要訪問某個元素,但又忘了它的位置,這時候就需要散列集。該集運用的數據結構為散列表(hash table)。散列表會為每個對象計算一個整數,成為散列碼(hash code)。散列碼是由對象的實例域產生的。比如下表。

字符串 散列碼
"Lee" 76268
"lee" 107020
"eel" 100300

如果是我們自己定義的類,就要實現這個類的hashCode()方法,同時還有equals()方法。

set 類型:set 是沒有重復元素的元素集合。set 的 add 方法首先會集中查找要添加的對象,如果不存在,就將這個元素添加進去。


9.數集

TreeSet可以看成一個有序的散列集。當添加的時候會比較慢,但查詢會比散列集快很多。

使用數集必須能比較元素,所有必須實現 comparable 接口。


10.隊列與雙端隊列

隊列可以讓人們有效的在尾部添加一個元素,在頭部刪除一個元素。有兩個端頭的隊列,即為雙端隊列,可以讓人們有效的在頭部和尾部同時添加或刪除元素。不支持在隊列中添加元素。Deque 接口由 ArrayDeque 和 LinkedList 實現。


11.優先級隊列

優先級隊列中的元素可以按照任意的順序插入,卻總是按照排序的順序進行索引。也就是說,無論何時調用 remove 方法,總是獲得當前優先級隊列中最小的元素。


12.映射的兩個實現

  • HashMap:散列映射對鍵進行散列。
  • TreeMap:樹映射用鍵的整體順序對元素進行排序,并將其組織成搜索樹。

如果不需要按照一定順序訪問鍵,就選擇散列,因為散列更快一些。

一些基本的操作:

Map<String, String> maps = new HashMap<>();
maps.put("first", "ethan");
maps.put("second", "zyc");
maps.put("third", "zyy");
String myName = maps.get("second");
System.out.println(myName); // zyc

鍵必須是唯一的,如果 put 的鍵是重復的,則會覆蓋前一個。而且 put 會返回被覆蓋的那個值。


13.迭代

  • 利用 forEach 和 lambda 表達式:
Map<String, String> maps = new HashMap<>();
maps.put("first", "ethan");
maps.put("second", "zyc");
maps.put("third", "zyy");
maps.forEach((k,v) ->
             System.out.println("key:" + k +",val:" + v));
  • 利用迭代器:
Map<String, String> maps = new HashMap<>();
maps.put("first", "ethan");
maps.put("second", "zyc");
maps.put("third", "zyy");
Iterator<Map.Entry<String,String>> entryIterator = maps.entrySet().iterator();
entryIterator.forEachRemaining(System.out::println);
// entryIterator.forEachRemaining(element -> System.out.println(element));

14.Merge

當有這樣一個需求:需要更新/新增的映射項在映射存在或者不存在的時候會進行不同的操作,這時候就需要用上merge 了。具體看代碼吧

Map<String, String> maps = new HashMap<>();
maps.put("first", "ethan");
maps.put("second", "zyc");
maps.put("third", "zyy");
maps.merge("first","i am new",(oldVal, newVal) -> {
    System.out.println("1執行了:" + oldVal + "," + newVal);
    return oldVal;
});
maps.merge("first1","i am new",(oldVal, newVal) -> {
    System.out.println("1執行了:" + oldVal + "," + newVal);
    return oldVal;
});

maps.forEach((k,v) ->
             System.out.println("key:" + k +",val:" + v));

/*
    1執行了:ethan,i am new
    key:third,val:zyy
    key:first1,val:i am new
    key:first,val:ethan
    key:second,val:zyc
 */

因為first已經存在了,所以會執行后面的函數,而first1不存在,所以根本就沒有執行后面的值,而是僅僅把i am new設為first1對應的值。


15.映射視圖

雖然很多數據結構認為映射屬于集合,但在 java 中,映射并不在集合框架中。不過映射視圖是實現了 Collection 的對象。視圖包括鍵集合、值集合以及鍵值對集合。并且對三者進行remove操作會影響原視圖。鍵是不能重復的,所以直接刪除,但值時可以重復的,這里只刪除了其中一個,不建議以刪除值來刪除鍵值對。

Map<String, String> maps = new HashMap<>();
maps.put("first", "ethan");
maps.put("second", "zyc");
maps.put("third", "zyy");
maps.put("forth","zyc");
Set<String> keys = maps.keySet();
Collection<String> vals = maps.values();
Set<Map.Entry<String,String>> keyVals = maps.entrySet();
System.out.println(keys);
System.out.println(vals);
System.out.println(keyVals);
keys.remove("first");
vals.remove("zyc");
System.out.println(keyVals);

/*
    [third, forth, first, second]
    [zyy, zyc, ethan, zyc]
    [third=zyy, forth=zyc, first=ethan, second=zyc]
    [third=zyy, second=zyc]
*/
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容