Java集合實戰

xfcoding發表於2024-05-20

ArrayList

排序

Collections.sort()預設將元素升序排序,前提是集合的元素支援自熱順序,即實現了 Comparable 介面的,如 String, Integer。Collections.sort()也可以傳一個 Comparable 引數實現自定義排序。

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    list.add(20);
    list.add(10);
    list.add(16);
    list.add(14);
    Collections.sort(list);
    System.out.println("ASC: " + list);
    list.sort(Comparator.comparing(Integer::intValue).reversed());
    System.out.println("DESC: " + list);
}

// 輸出
ASC: [10, 14, 16, 20]
DESC: [20, 16, 14, 10]

如果集合元素是其他物件,就用物件的屬性做排序依據。那麼 sort 方法就要傳排序引數了。

public static void main(String[] args) {
    List<Person> list = new ArrayList<>();
    list.add(new Person("張三", 20));
    list.add(new Person("李四", 14));
    list.add(new Person("王五", 16));
    list.add(new Person("趙六", 10));

    list.sort(Comparator.comparing(Person::getAge));
    System.out.println("ASC: " + list);
    list.sort(Comparator.comparing(Person::getAge).reversed());
    System.out.println("DESC: " + list);
}

TreeSet

TreeSet 是支援排序的 Set。它實際就是 TreeMap。如果 TreeSet 的元素支援自熱排序,它預設排序是自然排序,也就是升序;否則就必須給 TreeSet的構造器傳一個 Comparator 引數指定排序依據,否則新增元素會報錯。

private static void order() {
    Set<Integer> treeSet = new TreeSet<>(Comparator.comparing(Integer::intValue).reversed());
    treeSet.add(20);
    treeSet.add(10);
    treeSet.add(16);
    treeSet.add(14);
    System.out.println("DESC: " + treeSet);
}
// 輸出
DSC: [20, 16, 14, 10]

同樣地,TreeMap 是一個支援排序的 Map。

LinkedHashSet

LinkedHashSet 是元素保持插入順序的、不重複的集合,LinkedHashMap 是元素保持插入順序的、不重複的鍵值對集合。

Queue

佇列(Queue)是一種經常使用的集合。Queue 實際上是實現了一個先進先出(FIFO:First In First Out)的有序表。它和 List 的區別在於,List 可以在任意位置新增和刪除元素,而 Queue 只有兩個操作:

  1. 把元素新增到佇列末尾;
  2. 從佇列頭部取出元素。

超市的收銀臺就是一個佇列:

在Java的標準庫中,佇列介面Queue定義了以下幾個方法:

  • int size():獲取佇列長度;
  • boolean add(E)/boolean offer(E):新增元素到隊尾;
  • E remove()/E poll():獲取隊首元素並從佇列中刪除;
  • E element()/E peek():獲取隊首元素但並不從佇列中刪除。

對於具體的實現類,有的Queue有最大佇列長度限制,有的Queue沒有。注意到新增、刪除和獲取佇列元素總是有兩個方法,這是因為在新增或獲取元素失敗時,這兩個方法的行為是不同的。我們用一個表格總結如下:

throw Exception 返回 false 或 null
新增元素到隊尾 add(E e) boolean offer(E e)
取隊首元素並刪除 E remove() E poll()
取隊首元素但不刪除 E element() E peek()

Deque

Deque 是雙端佇列(Double Ended Queue),讀作 deck,它允許兩頭都進,兩頭都出

The name deque is short for "double ended queue" and is usually pronounced "deck".

由於 Deque 繼承了 Queue 介面,因此它繼承了 Queue 介面的所有方法。

除了 Queue 介面中可用的方法之外,Deque 介面還包括以下方法:

  • addFirst()/addLast() - 在雙端佇列的開頭/末尾新增指定的元素。如果雙端佇列已滿,則引發異常。

  • offerFirst()/offerLast() - 在雙端佇列的開頭/末尾新增指定的元素。如果雙端佇列已滿,則返回 false。

  • getFirst()/getLast() - 返回雙端佇列的第一個/最後一個元素。如果雙端佇列為空,則引發異常。

  • peekFirst()/peekLast() - 返回雙端佇列的第一個/最後一個元素。如果雙端佇列為空,則返回 null。

  • removeFirst()/removeLast() - 返回並刪除雙端佇列的第一個/最後一個元素。如果雙端佇列為空,則引發異常。

  • pollFirst()/pollLast() - 返回並刪除雙端佇列的第一個/最後一個元素。如果雙端佇列為空,則返回 null。

使用 Deque 時,最好使用 Deque 自己提供的方法,因為語義清晰。如果直接寫deque.offer(),我們就需要思考,offer()實際上是offerLast(),我們明確地寫上offerLast(),不需要思考就能一眼看出這是新增到隊尾。

因此,使用 Deque,推薦總是明確呼叫offerLast()/offerFirst()或者pollFirst()/pollLast()方法。

ArrayDeque 是 Deque 的經典實現。

public static void main(String[] args) {
    // 使用ArrayDeque類建立Deque 
    Deque<Integer> numbers = new ArrayDeque<>();

    //新增元素到Deque
    numbers.offer(1);
    numbers.offerLast(2);
    numbers.offerFirst(3);
    System.out.println("Deque: " + numbers);

    //訪問Deque的元素
    int firstElement = numbers.peekFirst();
    System.out.println("第一個元素: " + firstElement);

    int lastElement = numbers.peekLast();
    System.out.println("最後一個元素: " + lastElement);

    //從Deque 移除元素
    int removedNumber1 = numbers.pollFirst();
    System.out.println("移除第一個元素: " + removedNumber1);

    int removedNumber2 = numbers.pollLast();
    System.out.println("移除最後一個元素: " + removedNumber2);

    System.out.println("更新後的Deque: " + numbers);
    }

ArrayDeque 和 LinkedList

ArrayDeque 和 LinkedList 都實現了 Deque 介面。但是,它們之間存在一些差異。

  • LinkedList 支援空元素,而 ArrayDeque 不支援
  • 連結串列中的每個節點都包含到其他節點的連結。這就是 LinkedList 比 ArrayDeque 需要更多儲存空間的原因。
  • 如果要實現佇列或雙端佇列資料結構,則 ArrayDeque 可能比 LinkedList 快。

雙端佇列作為堆疊資料結構

Java Collections 框架的 Stack 類提供了堆疊的實現。

但是,建議 Deque 用作堆疊而不是 Stack 類。Stack 繼承 Vector 集合,Stack 的方法都是引用 Vector 的方法, 而 Vector 的方法都加了同步鎖,所以效率低。

Deque 介面提供的用於實現堆疊的方法:

  • push() - 在雙端佇列的開頭新增元素
  • pop() - 從雙端佇列的開頭刪除元素
  • peek() - 從雙端佇列的開頭返回一個元素
public static void main(String[] args) {
    ArrayDeque<String> stack = new ArrayDeque<>();

    //將元素新增到stack
    stack.push("Dog");
    stack.push("Cat");
    stack.push("Horse");
    System.out.println("Stack: " + stack);

    //從堆疊頂部訪問元素
    String element = stack.peek();
    System.out.println("訪問元素: " + element);

    //從堆疊頂部刪除元素
    String remElement = stack.pop();
    System.out.println("刪除element: " + remElement);
}

PriorityQueue

EnumSet

EnumSet、EnumMap 的 key 要求為 Enum 型別,在操作列舉類的時候可以考慮是否能用得上這種集合,目前我還沒在業務中使用過。

相關文章