在 Java 開發中,集合類是處理數據的核心工具。合理選擇集合,不僅可以提高代碼效率,還能讓代碼更簡潔。本篇文章將重點探討 List、Set 和 Map?的適用場景及優缺點,幫助你在實際開發中找到最佳解決方案。
一、List:有序存儲的/最佳選擇
1. ArrayList:快速查詢與動態數組
應用場景:當你需要頻繁查詢元素,或者存儲的元素數目動態變化時,ArrayList
是首選。例如:分頁展示用戶數據。
代碼示例:
List<String> users = new ArrayList<>();
users.add("Alice");
users.add("Bob");
System.out.println(users.get(1)); // 輸出 Bob
底層結構:ArrayList
使用一個 動態數組 來存儲元素。初始時,數組的大小是固定的,當元素超過數組的容量時,會自動擴展數組的大小。
優點:
-
查詢效率高:數組支持按索引快速訪問元素,時間復雜度為
O(1)
,因此get()
操作非常高效。 -
內存局部性:數組存儲在連續的內存空間中,CPU 緩存友好,可以利用 CPU 的緩存機制提高訪問效率。
缺點:
-
插入和刪除效率低:當插入或刪除元素時,尤其是在中間位置時,必須移動數組中的大量元素,時間復雜度為
O(n)
。 -
擴容操作代價高:數組擴容時需要分配新的數組并將舊數組元素復制到新數組,操作的時間復雜度為
O(n)
。
2. LinkedList:高效增刪的雙向鏈表
應用場景:需要頻繁在列表中間或首尾插入、刪除數據時,例如實現任務隊列。
代碼示例:
LinkedList<String> tasks = new LinkedList<>();
tasks.addFirst("Task1");
tasks.addLast("Task2");
tasks.removeFirst();
底層結構:LinkedList
使用 雙向鏈表,每個元素都有兩個指針:一個指向前一個元素,一個指向下一個元素。這樣可以在常數時間內插入或刪除元素。
優點:
-
插入和刪除高效:無論是在鏈表的頭部、中部還是尾部,插入和刪除元素的時間復雜度都為
O(1)
,因為只需要改變相關節點的指針。 -
內存使用靈活:每個元素的內存可以分散存儲,不需要連續的內存塊。
【比如在中間插入】
假設需要在鏈表中的某個位置
node
前插入新節點newNode
:
將
newNode
的next
指向node
,將newNode
的prev
指向node.prev
。更新
node.prev.next
為newNode
,更新node.prev
為newNode
。這只涉及 4 次指針操作,與鏈表的長度無關,因此在已定位到目標節點后,插入操作的時間復雜度為
O(1)
。
缺點:
-
查詢效率低:為了查找元素,必須從頭節點或尾節點開始遍歷鏈表,時間復雜度為
O(n)
。 -
內存開銷大:每個元素都需要額外存儲指向前后元素的指針,相較于數組,占用更多的內存。
二、Set:無重復集合的首選
1. HashSet:高效去重
應用場景:當需要存儲一組不允許重復的元素,且對順序沒有要求時,例如用戶注冊時驗證用戶名的唯一性。
代碼示例
Set<String> usernames = new HashSet<>();
usernames.add("Alice");
usernames.add("Bob");
usernames.add("Alice"); // 重復的元素會被忽略
System.out.println(usernames.size()); // 輸出 2
底層結構:HashSet
使用 哈希表(HashMap
)【哈希表在文末有補充講解】來存儲元素。哈希表通過將元素的哈希碼映射到表中的桶來進行存儲,確保元素是唯一的。
優點:
-
去重高效:哈希表能夠快速判斷元素是否已存在,因為它通過哈希值進行查找,時間復雜度為
O(1)
。 -
查詢效率高:哈希表的查找時間復雜度為
O(1)
,因此contains()
和add()
操作非常高效。
缺點:
-
無序存儲:哈希表并不維護元素的順序,因此
HashSet
中的元素是無序的。 -
哈希沖突:不同的元素可能具有相同的哈希值,哈希沖突會影響性能,但通常情況下,哈希表的設計會盡量減少沖突的概率。
2. LinkedHashSet:有序去重
應用場景:當需要去重的同時保留插入順序,例如記錄用戶最近瀏覽的商品。
代碼示例:
Set<String> products = new LinkedHashSet<>();
products.add("Laptop");
products.add("Phone");
products.add("Laptop"); // 再次添加無效
System.out.println(products); // 輸出 [Laptop, Phone]
底層結構:LinkedHashSet
使用一個 哈希表 來存儲元素,并通過一個 雙向鏈表 來維護元素的插入順序。
優點:
-
有序存儲:由于鏈表的存在,
LinkedHashSet
能夠保持元素的插入順序,訪問時能夠按照插入的順序遍歷元素。 -
去重高效:與
HashSet
一樣,哈希表提供了快速的查找和去重機制。
缺點:性能略低于 HashSet,由于還需要維護鏈表,LinkedHashSet
的操作稍微比 HashSet
慢,但差距通常不大。
3. TreeSet:排序與去重兼備
應用場景:當需要去重的同時對元素進行排序,例如實現排行榜或數據字典。
代碼示例:
TreeSet<Integer> scores = new TreeSet<>();
scores.add(50);
scores.add(80);
scores.add(70);
System.out.println(scores); // 輸出 [50, 70, 80]
底層結構:TreeSet
使用 紅黑樹 來存儲元素。紅黑樹是一種自平衡的二叉搜索樹,能夠確保樹的深度保持在對數級別。
優點:
-
有序存儲:
TreeSet
會自動對元素進行排序,默認按自然順序排序(compareTo(Object obj)
)或者通過傳入Comparator
自定義排序)。 -
查找、插入和刪除的時間復雜度為
O(log n)
:由于紅黑樹的結構特性,所有操作的時間復雜度為對數級別。
缺點:性能較低,相比哈希表,紅黑樹的插入、刪除和查找操作的時間復雜度為 O(log n)
,因此在大量數據操作時,性能略遜色于 HashSet
和 LinkedHashSet
。
三、Map:鍵值對存儲的首選
Map
是存儲鍵值對的集合類,每個鍵唯一對應一個值。常用于快速查找和關聯關系的存儲。
1. HashMap:高效的鍵值映射
應用場景:需要高效查找時,例如存儲用戶 ID 和用戶信息的映射。
代碼示例:
Map<Integer, String> userMap = new HashMap<>();
userMap.put(1, "Alice");
userMap.put(2, "Bob");
System.out.println(userMap.get(1)); // 輸出 Alice
底層結構:HashMap
使用 哈希表 來存儲鍵值對,通過鍵的哈希碼來確定存儲位置。
優點:
-
查找和插入高效:查找、插入和刪除操作的時間復雜度為
O(1)
,通過哈希值直接定位位置。 -
支持鍵值對的存儲:每個鍵對應唯一的值,適合各種映射操作。
缺點:
-
無序存儲:哈希表中的元素是無序的,因此遍歷時無法保證順序。
2. LinkedHashMap:有序的鍵值映射
應用場景:需要既保持插入順序,又能高效查找,例如實現最近訪問頁面的緩存。
代碼示例:
Map<Integer, String> accessLog = new LinkedHashMap<>();
accessLog.put(1, "HomePage");
accessLog.put(2, "ProfilePage");
accessLog.put(3, "SettingsPage");
System.out.println(accessLog); // 輸出 {1=HomePage, 2=ProfilePage, 3=SettingsPage}
底層結構:LinkedHashMap
使用 哈希表 存儲元素,并通過 雙向鏈表 維護元素的插入順序。
優點:
-
有序存儲:保持了元素的插入順序,遍歷時能夠按照插入順序輸出。
-
高效查找:與
HashMap
一樣,查詢和插入操作的時間復雜度為O(1)
。
缺點:內存開銷較大,需要額外的內存來存儲鏈表指針。
3. TreeMap:有序的鍵值存儲
應用場景:需要按鍵排序存儲鍵值對,例如實現字典或排行榜。
代碼示例:
Map<Integer, String> sortedMap = new TreeMap<>();
sortedMap.put(3, "C");
sortedMap.put(1, "A");
sortedMap.put(2, "B");
System.out.println(sortedMap); // 輸出 {1=A, 2=B, 3=C}
-
底層結構:
TreeMap
使用 紅黑樹 來存儲鍵值對,按照鍵的自然順序(或通過指定的Comparator
)進行排序。 -
優點:
-
有序存儲:自動對鍵進行排序,適用于需要順序訪問鍵值對的場景。
-
高效的查找、插入和刪除:操作時間復雜度為
O(log n)
。
-
-
缺點:性能略低于
HashMap
和LinkedHashMap,
由于紅黑樹需要維護平衡,操作的時間復雜度為對數級別,性能不如哈希表
4. Properties
應用場景
-
Properties
常用于管理應用程序的配置信息,如數據庫連接信息、語言國際化資源等。 -
它可以方便地加載和存儲鍵值對到
.properties
文件中,支持流式操作。
代碼示例:
import java.io.*;
import java.util.Properties;
?
public class PropertiesExample {public static void main(String[] args) throws IOException {Properties properties = new Properties();// 設置鍵值對properties.setProperty("database.url", "jdbc:mysql://localhost:3306/mydb");properties.setProperty("database.user", "root");properties.setProperty("database.password", "password");
?// 保存到文件try (FileOutputStream output = new FileOutputStream("config.properties")) {properties.store(output, "Database Configuration");}
?// 從文件加載try (FileInputStream input = new FileInputStream("config.properties")) {properties.load(input);}
?// 打印所有屬性properties.forEach((key, value) -> System.out.println(key + ": " + value));}
}
底層結構
-
Properties
的基礎是Hashtable
:-
底層采用線程安全的哈希表結構。
-
鍵和值均為字符串類型(
String
),以適應配置文件的存儲和解析需求。 -
提供了
load()
和store()
方法,用于流式操作,方便配置文件的讀寫。
-
優點
-
簡單直觀:內置方法支持直接操作配置文件,減少手動解析的復雜性;適合存儲和管理小規模配置。
-
線程安全:繼承自
Hashtable
,所有操作均是同步的,適合簡單的多線程環境。 -
與文件系統集成良好:提供了流式操作接口,方便將鍵值對直接保存為
.properties
文件或從文件中加載。
缺點
-
性能較低:由于繼承自同步的
Hashtable
,在現代高并發場景下不推薦使用,性能落后于HashMap
。 -
局限性:僅支持
String
類型的鍵值對,若需要存儲復雜對象,需額外序列化。 -
不適合大規模配置:適合小型項目或簡單模塊的配置管理,大型系統建議采用更復雜的配置管理工具(如 Apache Commons Configuration 或 Spring)。
總結
集合類 | 底層數據結構 | 主要優點 | 主要缺點 |
---|---|---|---|
ArrayList | 動態數組 | 查詢效率高,支持隨機訪問 | 插入刪除效率低,擴容代價大 |
LinkedList | 雙向鏈表 | 插入刪除效率高,內存靈活 | 查詢效率低,占用內存大 |
HashSet | 哈希表 | 查詢和去重效率高 | 無序存儲,受哈希沖突影響 |
LinkedHashSet | 哈希表 + 雙向鏈表 | 有序存儲,去重效率高 | 內存占用較高 |
TreeSet | 紅黑樹 | 有序存儲,按自然順序或自定義順序排序 | 性能略低于哈希表,操作復雜度為 O(log n) |
HashMap | 哈希表 | 查找和插入效率高,支持鍵值對映射 | 無序存儲,受哈希沖突影響 |
LinkedHashMap | 哈希表 + 雙向鏈表 | 有序存儲,按插入順序遍歷鍵值對 | 內存開銷較高 |
TreeMap | 紅黑樹 | 有序存儲,按鍵自然順序或自定義順序排序 | 性能略低于哈希表,操作復雜度為 O(log n) |
Properties | 哈希表(繼承自 Hashtable ) | 專為存儲鍵值對配置設計,支持讀寫 .properties 文件 | 性能較低(繼承自同步的 Hashtable ),不適合高并發 |
知識點補充
1. 什么是哈希表?
哈希表是一種用來存儲 鍵值對 的工具,它能非常快速地找到數據。你可以把它想象成一個 帶編號的儲物柜,每個柜子都有一個編號(索引),你把東西存進去時,會根據物品的特點計算出一個編號,然后直接放進對應的柜子里。取東西時也用相同的方法計算編號,直接找到對應的柜子打開拿走。
例子:
-
假如你有一本字典,查找某個單詞(鍵)對應的解釋(值)。
-
傳統查找方法:逐頁翻閱,耗時長。
-
使用哈希表:計算單詞的編號,直接跳到對應的位置查看解釋,速度非常快。
總結類比:
-
鍵是 “單詞”,值是 “解釋”。
-
哈希表通過哈希函數快速找到這個單詞在哪頁。
2. 哈希表是如何存數據的?
哈希表的核心在于 哈希函數。這個函數就像一個計算器,可以把一個鍵(比如一個字符串)變成一個數字(哈希值)。哈希值用來確定數據存儲的位置。
步驟:
-
計算位置:用哈希函數把鍵變成一個數字,然后對儲物柜的總數取模(%),確定放在哪個柜子里。假如儲物柜有 10 個,"Alice" 的哈希值是 42,42 % 10 = 2,所以數據放到第 2 個柜子。
-
存儲值:把數據放到計算出的柜子里。