【技術積累】Java中的集合框架【一】

2023-06-18 09:00:31

什麼是Java集合框架?

Java集合框架是Java程式語言中提供的一組介面、實現和演演算法,用於儲存和運算元據集合。集合框架可以讓程式設計師更加高效地組織和運算元據,而無需手動實現底層資料結構。

Java集合框架的優點是:

  1. 提供了豐富、靈活的資料結構和演演算法,讓程式設計師可以更加高效地完成各種資料操作;
  2. 提供了一組統一的介面,讓程式設計師可以隨時替換底層資料結構,以達到更好的效能和效率;
  3. 提供了執行緒安全的集合實現,可以在多執行緒環境下進行安全的資料操作。

綜上所述,Java集合框架是Java程式語言中十分實用、基礎而重要的工具,其靈活、高效和易用的特點使得它一直是Java程式設計師使用的必備工具之一。

Java集合框架的三大分類是什麼

  1. List(列表):List集合儲存有序的元素,允許元素重複。List集合提供了根據索引存取列表元素的能力,因此可以通過整數索引來隨機存取列表中指定位置的元素。

  2. Set(集合):Set集合儲存無序的元素,不允許元素重複。Set集合不提供存取元素的索引,因此只能通過迭代器來遍歷Set集合中的元素。

  3. Map(對映):Map集合儲存鍵值對,每個鍵對映到一個特定的值。Map集合中的鍵是唯一的,每個鍵對應一個特定的值。因此,可以通過鍵來存取相應的值,而不是通過位置或索引來存取。

 

什麼是執行緒安全的集合?什麼是非執行緒安全的集合?

執行緒安全的集合指多個執行緒同時操作該集合時,不會出現資料錯亂、資料丟失、邏輯錯誤等問題。Java集合框架中已經提供了許多執行緒安全的集合,如ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentSkipListSet等。

舉例說明:

ConcurrentHashMap:可以在多執行緒環境下安全地進行讀取和寫入操作,內部採用分段鎖機制來保證執行緒安全。例如,多個執行緒同時想向一個ConcurrentHashMap中新增元素:

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 1; i <= 5; i++) {
    executorService.execute(() -> {
        for (int j = 1; j <= 10000; j++) {
            map.put(Thread.currentThread().getName() + "_" + j, j);
        }
    });
}
executorService.shutdown();

CopyOnWriteArrayList:可以在多執行緒環境下安全地進行讀取操作,寫入操作則會進行復制操作來保證執行緒安全。例如,多個執行緒同時想向一個CopyOnWriteArrayList中新增元素:

List<String> list = new CopyOnWriteArrayList<>();
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 1; i <= 5; i++) {
    executorService.execute(() -> {
        for (int j = 1; j <= 10000; j++) {
            list.add(Thread.currentThread().getName() + "_" + j);
        }
    });
}
executorService.shutdown();

非執行緒安全的集合指多個執行緒同時操作該集合時,會出現資料錯亂、資料丟失、邏輯錯誤等問題。如ArrayList、HashSet等。如果多個執行緒同時向一個ArrayList中新增元素,就會出現資料錯亂的問題,如:

List<String> list = new ArrayList<>();
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 1; i <= 5; i++) {
    executorService.execute(() -> {
        for (int j = 1; j <= 10000; j++) {
            list.add(Thread.currentThread().getName() + "_" + j);
        }
    });
}
executorService.shutdown();

在多執行緒環境下,為了保證執行緒安全,應該使用執行緒安全的集合。

Java集合框架中什麼是迭代器?

Java集合框架中的迭代器(Iterator)是一種用於遍歷集合中元素的物件。

通過迭代器,可以逐個存取集合中的元素,而不需要了解集合的內部實現方式。

簡單來說,Java集合框架就是一組用來管理和操作物件集合的類和介面。

這些類和介面提供了許多在開發過程中常用的方法和功能,如查詢、排序、遍歷、新增、刪除、替換、合併等操作。

迭代器是Java集合框架中的一個重要介面,用來遍歷集合中的元素。迭代器允許我們在不瞭解集合內部結構的情況下,存取集合中的所有元素,逐一進行處理。

迭代器基本操作

  1. hasNext():判斷是否還有下一個元素;
  2. next():返回當前元素,並移動指標到下一個元素;
  3. remove():從集合中移除當前元素。

在遍歷集合時,建議使用 foreach 迴圈,可以更加方便地遍歷集合,但是並沒有 remove() 操作,所以需使用迭代器進行刪除。

Java集合框架是Java提供的一組資料結構和演演算法,用於處理儲存資料的集合。其中,迭代器是集合框架的一個重要概念,指的是一種遍歷集合元素的機制。

迭代器的作用

允許我們以一種統一的方式存取不同型別的集合物件中的元素,無需關心具體實現細節。

它提供了統一的方法來存取集合物件的元素,包括遍歷元素、檢查元素是否存在、刪除元素等操作。

迭代器的使用場景

  1. 遍歷List、Set、Map集合中的所有元素,執行某些操作;
  2. 查詢符合條件的元素;
  3. 刪除或替換集合中的某個元素等。

迭代器遍歷集合的步驟

  1. 使用集合物件的 iterator() 方法獲取迭代器物件;
  2. 使用 hasNext() 方法判斷是否還有下一個元素,有則呼叫 next() 方法獲取;
  3. 使用 remove() 方法可以在迭代過程中刪除元素。

迭代器工作的基本原理

  1. 通過集合物件的iterator()方法獲取迭代器物件;
  2. 判斷是否還有下一個元素,如果有,則返回true,否則返回false;
  3. 如果有下一個元素,則使用next()方法獲取下一個元素。

以下是一個簡單的Java案例來說明迭代器的用法:

import java.util.ArrayList;
import java.util.Iterator;

public class IteratorDemo {
    public static void main(String[] args) {

        ArrayList<Integer> list = new ArrayList<Integer>();

        // 新增元素到列表
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);

        // 獲取迭代器
        Iterator<Integer> it = list.iterator();

        // 遍歷集合中的元素
        while (it.hasNext()) {
            System.out.println(it.next());
        }
    }
}

該程式碼通過建立一個 ArrayList,新增元素,然後獲取列表的迭代器並遍歷所有元素,最後列印每個元素的值。這是一個非常基本的使用迭代器的範例。

什麼是fail-fast迭代器和fail-safe迭代器?

Java集合框架中的迭代器是用來遍歷集合中元素的工具。在遍歷過程中,如果集合被修改,可能會導致迭代器的行為產生不可預測的結果。因此,在Java集合框架中,有兩種迭代器型別:fail-fast迭代器和fail-safe迭代器。

fail-fast迭代器的行為是在遍歷過程中發現集合被修改,立即丟擲ConcurrentModificationException異常,停止遍歷過程。fail-fast迭代器採用快速失敗機制,可以保證多執行緒存取集合時的安全性。這種迭代器是預設的迭代器型別,在集合框架中,大多數集合實現類都是採用fail-fast迭代器。

fail-safe迭代器的行為是在遍歷過程中發現集合被修改,不會丟擲異常,而是建立一個集合的副本,遍歷副本中的元素。因為遍歷的是副本,即使原集合中的元素被修改,也不會對遍歷過程造成影響。這種迭代器適合於在存在並行修改的情況下對集合進行遍歷。fail-safe迭代器在Java集合框架中的實現較少,主要應用於Java5之前的Map介面中。

下面是fail-fast迭代器和fail-safe迭代器的使用案例:

1. fail-fast迭代器使用:

List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    System.out.println(s);
    list.remove(s);
}

執行上述程式碼會丟擲ConcurrentModificationException異常,因為在遍歷列表過程中修改了列表。

2. fail-safe迭代器使用:

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(Arrays.asList("a", "b", "c"));
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    System.out.println(s);
    list.remove(s);
}

執行上述程式碼可以看到,程式正常執行完成。這是因為CopyOnWriteArrayList類使用的是fail-safe迭代器,在遍歷過程中刪除元素不會對遍歷過程造成影響。

ArrayList與LinkedList的區別是什麼?

ArrayList和LinkedList都是Java中的集合類,但它們的實現方式略有不同,具體區別如下:

  1. 資料結構
    • ArrayList底層實現是一個可變大小的陣列,當新增或刪除元素時,需要對陣列進行擴容或縮容,因此在頻繁新增或刪除元素的情況下,效能可能會較差。
    • LinkedList底層實現是一個雙向連結串列,新增或刪除元素時只需要改變前後節點的參照,效能較好。
  2. 隨機存取
    • 由於ArrayList底層是陣列,因此可以通過索引隨機存取元素,其時間複雜度為O(1)。
    • LinkedList需要從頭節點或尾節點開始順序遍歷,查詢某個元素時時間複雜度是O(n)。
  3. 遍歷操作
    • 由於ArrayList底層是陣列,因此遍歷元素時效能較好,時間複雜度為O(n)。
    • LinkedList遍歷時需要每次遍歷一個節點,時間複雜度也為O(n),但是在插入或刪除元素時由於只需要改變前後節點的參照,效能可能會比ArrayList略好一些。
  4. 記憶體開銷
    • ArrayList底層是一個陣列,因此在使用時需要預先指定容量,且如果陣列中有很多空元素時會浪費一定的記憶體空間。
    • LinkedList是一個連結串列,節點之間的關係是通過參照來維護的,記憶體空間利用率相對較高。
  5. 同步
    • ArrayList沒有實現同步機制,因此多執行緒操作時需要手動進行同步處理。
    • LinkedList實現了List介面的同步方法,可以在多執行緒環境下安全地使用。

 

綜上所述

  • ArrayList適用於讀取多、插入、刪除少的場景
  • LinkedList適用於插入、刪除多、讀取少的場景。

因此,在使用時應根據具體的業務場景選擇合適的列表實現。

下面是一個使用ArrayList和LinkedList儲存一百萬個整數,並計算它們的總和的例子。可以看到,ArrayList的操作速度較快,特別是在存取元素時,而LinkedList的操作速度相對較慢,特別是在插入和刪除元素時。

import java.util.*;

public class ListExample {
    public static void main(String[] args) {
        long startTime;
        long endTime;

        // 使用ArrayList儲存一百萬個整數,並計算它們的總和
        List<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < 1000000; i++) {
            arrayList.add(i);
        }
        startTime = System.currentTimeMillis();
        long sum = 0;
        for (int i = 0; i < arrayList.size(); i++) {
            sum += arrayList.get(i);
        }
        endTime = System.currentTimeMillis();
        System.out.println("ArrayList: " + sum + ", time: " + (endTime - startTime) + "ms");

        // 使用LinkedList儲存一百萬個整數,並計算它們的總和
        List<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < 1000000; i++) {
            linkedList.add(i);
        }
        startTime = System.currentTimeMillis();
        sum = 0;
        for (int i : linkedList) {
            sum += i;
        }
        endTime = System.currentTimeMillis();
        System.out.println("LinkedList: " + sum + ", time: " + (endTime - startTime) + "ms");
    }
}

Vector與ArrayList的區別是什麼?

Vector和ArrayList都是Java中實現動態陣列的類,兩者都是基於陣列實現,但也存在一些區別:

  1. 執行緒安全:Vector是執行緒安全的類,即在並行場景下可以保證資料的一致性,而ArrayList則不是。因為Vector在操作時會加鎖,可能會影響效能。

  2. 初始容量:Vector預設容量為10,每次擴容後容量變為原來的2倍,而ArrayList預設容量為10,每次擴容後容量變為原來的1.5倍。

  3. 擴容方式:Vector在擴容時會增加一倍的容量,而ArrayList會增加50%的容量。

  4. 存取速度:在存取元素的時候,Vector採用了同步處理,所以兩者的速度上略微有所差別,在並行存取時,ArrayList的速度會更快。

  5. 迭代器:在迭代元素時,Vector使用Enumeration,ArrayList使用Iterator,Iterator的功能更強,可以在迭代時進行刪除操作,Enumeration則不支援。

總之,如果沒有執行緒安全方面的考慮,建議使用ArrayList,因為它執行速度更快,操作靈活,功能更豐富。而如果需要執行緒安全的特性,則應使用Vector。

Stack與LinkedList的區別是什麼?

Stack和LinkedList都是Java集合框架中常用的資料結構,但兩者有一些明顯的區別,主要如下:

1. 底層實現:Stack是用陣列實現的,而LinkedList則是使用連結串列實現的。

2. 資料操作:Stack只能在棧頂做入和出操作。當需要在中間位置插入、刪除元素時,必須整體移動陣列來完成,效率較低。而LinkedList則可以在任意位置插入、刪除元素。

3. 儲存方式:Stack是在固定空間記憶體儲元素,而LinkedList則是在節點中儲存元素,每個節點都包含指向前後節點的指標,因此佔用空間相比Stack更大,但當需要經常執行插入、刪除元素操作時,LinkedList的效能要優於Stack。

4. 執行緒安全:Stack是執行緒安全的,因為在壓入和彈出元素時使用了同步鎖來保證執行緒安全性,而LinkedList則不是執行緒安全的。

5. 操作效率:由於Stack是基於陣列實現的,因此其在存取和遍歷元素時的操作效率較高,而LinkedList則因為使用了指標,每次遍歷元素時需要存取指標,因此效率較低。

綜上所述,當需要快速存取元素時可以使用Stack,當需要經常插入、刪除元素時可以使用LinkedList。但LinkedList不是執行緒安全的,因此在多執行緒環境下需要考慮使用Stack。

List的isEmpty()和.size()方法的區別是什麼?

在Java集合框架中,isEmpty()和size()方法用於判斷集合是否為空和獲取集合的大小。它們的區別如下:

1. isEmpty()方法

isEmpty()方法返回一個布林值,用於判斷當前集合是否為空。如果集合中沒有任何元素,isEmpty()方法將返回true,否則將返回false。

範例程式碼:

List<String> list = new ArrayList<>();
System.out.println(list.isEmpty()); // true
list.add("Java");
System.out.println(list.isEmpty()); // false

2. size()方法

size()方法返回一個整數,表示當前集合中的元素數量。

範例程式碼:

List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("C++");
System.out.println(list.size()); // 3

因此,isEmpty()和size()方法的區別是:

- isEmpty()方法用於判斷集合是否為空,返回一個布林值;
- size()方法用於獲取集合中元素的數量,返回一個整數。