Java 集合框架详解:从原理到实战,一篇吃透所有常用集合

Java 集合框架详解:从原理到实战,一篇吃透所有常用集合

Java 集合框架是开发中最常用的工具类集合,它统一管理了各类数据存储结构(数组、链表、红黑树等),提供了便捷的增删改查方法,解决了数组固定长度、操作繁琐的痛点。本文从集合框架整体结构出发,详解核心集合类的原理、用法和适用场景,搭配实战代码,让你既能理解底层逻辑,又能在开发中灵活选型。

一、集合框架整体结构:两大核心阵营

Java 集合框架主要分为 Collection(单列集合)Map(双列集合) 两大阵营,所有集合类都围绕这两个核心接口展开:

1. 核心结构概览

注:图片来自面试鸭

2. 核心接口区别

  • Collection:存储单个元素的集合,提供统一的元素操作方法(add、remove、iterator 等);
  • Map:存储键值对(key-value),key 唯一,value 可重复,提供根据 key 操作 value 的方法(put、get、remove 等)。

二、Collection 接口详解:单列集合的核心

1. List 接口:有序、可重复的 “动态数组”

List 接口的核心特点是 有序(元素插入顺序 = 遍历顺序)、可重复,支持通过索引操作元素,适合需要按位置访问、频繁查询的场景。

(1)ArrayList:数组实现的 “动态数组”(最常用)
  • 底层原理:基于数组实现,默认初始容量为 10,当元素个数超过容量时,会扩容为原来的 1.5 倍(oldCapacity + (oldCapacity >> 1)),扩容时会复制原数组元素到新数组;
  • 核心特点:查询快(数组支持索引随机访问,时间复杂度 O (1))、增删慢(需移动数组元素,时间复杂度 O (n));
  • 适用场景:频繁查询、少量增删的场景(如商品列表展示、数据查询结果存储)。
实战代码:ArrayList 常用操作
import java.util.ArrayList; import java.util.List; public class ArrayListDemo { public static void main(String[] args) { // 1. 创建ArrayList(存储字符串) List<String> list = new ArrayList<>(); // 2. 添加元素(有序、可重复) list.add("Java"); list.add("Python"); list.add("Java"); // 允许重复元素 System.out.println("添加后:" + list); // 输出:[Java, Python, Java] // 3. 按索引访问元素 String element = list.get(1); System.out.println("索引1的元素:" + element); // 输出:Python // 4. 按索引修改元素 list.set(2, "C++"); System.out.println("修改后:" + list); // 输出:[Java, Python, C++] // 5. 按索引删除元素 list.remove(1); System.out.println("删除后:" + list); // 输出:[Java, C++] // 6. 遍历元素(三种方式) // 方式1:普通for循环(适合需要索引的场景) for (int i = 0; i < list.size(); i++) { System.out.print(list.get(i) + " "); } System.out.println(); // 方式2:增强for循环(简洁) for (String s : list) { System.out.print(s + " "); } System.out.println(); // 方式3:迭代器(支持遍历中删除元素) java.util.Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String s = iterator.next(); if (s.equals("Java")) { iterator.remove(); // 安全删除,不会触发ConcurrentModificationException } } System.out.println("迭代器删除后:" + list); // 输出:[C++] } } 
(2)LinkedList:双向链表实现的 “链表集合”
  • 底层原理:基于双向链表实现,每个元素(节点)包含前驱节点、后继节点的引用,无需连续内存空间;
  • 核心特点:查询慢(需遍历链表,时间复杂度 O (n))、增删快(只需修改节点引用,时间复杂度 O (1));
  • 适用场景:频繁增删、少量查询的场景(如队列、栈、消息队列)。
实战代码:LinkedList 常用操作
import java.util.LinkedList; import java.util.List; public class LinkedListDemo { public static void main(String[] args) { List<Integer> list = new LinkedList<>(); // 添加元素 list.add(10); list.add(20); list.add(30); System.out.println("添加后:" + list); // 输出:[10, 20, 30] // 在指定位置插入元素(增删效率高) list.add(1, 15); System.out.println("插入后:" + list); // 输出:[10, 15, 20, 30] // 删除指定元素 list.remove(Integer.valueOf(20)); System.out.println("删除后:" + list); // 输出:[10, 15, 30] // 链表特有的操作(作为队列/栈) LinkedList<Integer> linkedList = (LinkedList<Integer>) list; linkedList.addFirst(5); // 头部添加 linkedList.addLast(35); // 尾部添加 System.out.println("头部+尾部添加后:" + linkedList); // 输出:[5, 10, 15, 30, 35] int first = linkedList.getFirst(); // 获取头部元素 int last = linkedList.getLast(); // 获取尾部元素 System.out.println("头部:" + first + ",尾部:" + last); // 输出:头部:5,尾部:35 } } 
(3)ArrayList vs LinkedList 核心区别
特性ArrayListLinkedList
底层结构数组双向链表
查询效率高(O (1))低(O (n))
增删效率低(O (n),需移动元素)高(O (1),仅改引用)
内存占用连续内存,可能有冗余非连续内存,每个节点有额外引用开销
适用场景频繁查询、少量增删频繁增删、少量查询

2. Set 接口:无序、不可重复的 “集合”

Set 接口的核心特点是 无序(元素插入顺序≠遍历顺序)、不可重复(基于equals()hashCode()判断),适合需要去重的场景。

(1)HashSet:哈希表实现的去重集合(最常用)
  • 底层原理:基于 HashMap 实现(HashSet 的元素作为 HashMap 的 key,value 为固定常量),利用哈希表保证元素唯一;
  • 核心特点:无序、不可重复、查询和增删效率高(时间复杂度 O (1));
  • 去重规则:先通过hashCode()计算哈希值,若哈希值不同则元素不同;若哈希值相同,再通过equals()判断是否相同,两者都相同则视为重复元素。
实战代码:HashSet 常用操作
import java.util.HashSet; import java.util.Set; public class HashSetDemo { public static void main(String[] args) { Set<String> set = new HashSet<>(); // 添加元素(不可重复) set.add("Apple"); set.add("Banana"); set.add("Apple"); // 重复元素,添加失败 System.out.println("添加后:" + set); // 输出:[Apple, Banana](无序) // 删除元素 set.remove("Banana"); System.out.println("删除后:" + set); // 输出:[Apple] // 判断元素是否存在 boolean contains = set.contains("Apple"); System.out.println("是否包含Apple:" + contains); // 输出:true // 遍历元素(增强for/迭代器,无普通for循环,因为无序无索引) for (String s : set) { System.out.print(s + " "); // 输出:Apple } // 去重场景示例:数组去重 String[] arr = {"Java", "Python", "Java", "C++"}; Set<String> uniqueSet = new HashSet<>(); for (String s : arr) { uniqueSet.add(s); } System.out.println("\n数组去重后:" + uniqueSet); // 输出:[Java, Python, C++] } } 
(2)LinkedHashSet:有序的去重集合
  • 底层原理:基于 HashMap + 双向链表实现,链表记录元素插入顺序,哈希表保证元素唯一;
  • 核心特点:有序(插入顺序 = 遍历顺序)、不可重复、效率略低于 HashSet;
  • 适用场景:需要去重且保持插入顺序的场景(如用户浏览记录去重)。
(3)TreeSet:排序的去重集合
  • 底层原理:基于红黑树实现,元素会按自然顺序(或自定义比较器)排序;
  • 核心特点:有序(排序后顺序)、不可重复、查询和增删效率 O (logn);
  • 适用场景:需要去重且排序的场景(如成绩排名、数据按关键字排序)。
实战代码:TreeSet 排序示例
import java.util.TreeSet; public class TreeSetDemo { public static void main(String[] args) { // 1. 自然排序(Integer按数值升序) TreeSet<Integer> numSet = new TreeSet<>(); numSet.add(30); numSet.add(10); numSet.add(20); System.out.println("自然排序:" + numSet); // 输出:[10, 20, 30] // 2. 自定义排序(字符串按长度降序) TreeSet<String> strSet = new TreeSet<>((s1, s2) -> s2.length() - s1.length()); strSet.add("Apple"); strSet.add("Banana"); strSet.add("Pear"); System.out.println("自定义排序(长度降序):" + strSet); // 输出:[Banana, Apple, Pear] } } 

3. Queue 接口:先进先出的 “队列”

Queue 接口遵循 先进先出(FIFO) 原则,元素从队列尾部添加,从头部删除,适合任务排队、消息传递等场景。

核心方法
  • offer(E e):添加元素到队列尾部,失败返回 false(推荐使用,不抛出异常);
  • poll():删除并返回队列头部元素,队列为空返回 null;
  • peek():返回队列头部元素,不删除,队列为空返回 null;
  • remove():删除并返回队列头部元素,队列为空抛出异常。
实战代码:LinkedList 实现队列
import java.util.LinkedList; import java.util.Queue; public class QueueDemo { public static void main(String[] args) { Queue<String> queue = new LinkedList<>(); // 添加元素到队列 queue.offer("任务1"); queue.offer("任务2"); queue.offer("任务3"); System.out.println("队列初始化:" + queue); // 输出:[任务1, 任务2, 任务3] // 获取头部元素(不删除) String head = queue.peek(); System.out.println("队列头部:" + head); // 输出:任务1 // 删除并返回头部元素 String task = queue.poll(); System.out.println("执行任务:" + task); // 输出:执行任务:任务1 System.out.println("执行后队列:" + queue); // 输出:[任务2, 任务3] } } 

三、Map 接口详解:双列集合的核心

Map 接口存储 键值对(key-value),key 唯一(不可重复),value 可重复,每个 key 对应一个 value,适合通过 key 快速查找 value 的场景(如用户信息存储、配置项管理)。

1. HashMap:哈希表实现的键值对集合(最常用)

  • 底层原理:JDK8 之前是 “数组 + 链表”,JDK8 之后是 “数组 + 链表 + 红黑树”;当链表长度超过阈值(8)且数组容量≥64 时,链表转为红黑树(提高查询效率);当链表长度≤6 时,红黑树转回链表(节省内存);
  • 核心特点:无序、key 唯一、查询和增删效率高(O (1))、线程不安全;
  • key 去重规则:同 HashSet,基于hashCode()equals()判断。
实战代码:HashMap 常用操作
import java.util.HashMap; import java.util.Map; import java.util.Set; public class HashMapDemo { public static void main(String[] args) { // 1. 创建HashMap(存储用户ID-用户名) Map<Integer, String> userMap = new HashMap<>(); // 2. 添加键值对(key唯一,重复key会覆盖value) userMap.put(1001, "张三"); userMap.put(1002, "李四"); userMap.put(1001, "张三三"); // 重复key,覆盖value System.out.println("添加后:" + userMap); // 输出:{1001=张三三, 1002=李四} // 3. 根据key获取value String username = userMap.get(1001); System.out.println("用户1001的名称:" + username); // 输出:张三三 // 4. 判断key是否存在 boolean hasKey = userMap.containsKey(1002); System.out.println("是否存在key=1002:" + hasKey); // 输出:true // 5. 删除键值对 userMap.remove(1002); System.out.println("删除后:" + userMap); // 输出:{1001=张三三} // 6. 遍历HashMap(三种方式) // 方式1:遍历key集合 Set<Integer> keySet = userMap.keySet(); for (Integer key : keySet) { System.out.println("key:" + key + ",value:" + userMap.get(key)); } // 方式2:遍历entrySet(推荐,效率高,避免多次get) Set<Map.Entry<Integer, String>> entrySet = userMap.entrySet(); for (Map.Entry<Integer, String> entry : entrySet) { System.out.println("key:" + entry.getKey() + ",value:" + entry.getValue()); } // 方式3:遍历value集合(无法获取key) for (String value : userMap.values()) { System.out.println("value:" + value); } } } 

2. LinkedHashMap:有序的键值对集合

  • 底层原理:基于 HashMap + 双向链表实现,链表记录键值对的插入顺序或访问顺序;
  • 核心特点:有序(插入顺序或访问顺序)、key 唯一、效率略低于 HashMap;
  • 适用场景:需要保持键值对顺序的场景(如缓存 LRU 策略、历史操作记录)。
实战代码:LinkedHashMap 有序性
import java.util.LinkedHashMap; import java.util.Map; public class LinkedHashMapDemo { public static void main(String[] args) { // 1. 按插入顺序排序(默认) Map<String, Integer> insertOrderMap = new LinkedHashMap<>(); insertOrderMap.put("Apple", 10); insertOrderMap.put("Banana", 20); insertOrderMap.put("Pear", 15); System.out.println("插入顺序:" + insertOrderMap); // 输出:{Apple=10, Banana=20, Pear=15} // 2. 按访问顺序排序(LRU策略) Map<String, Integer> accessOrderMap = new LinkedHashMap<>(16, 0.75f, true); accessOrderMap.put("Apple", 10); accessOrderMap.put("Banana", 20); accessOrderMap.put("Pear", 15); // 访问Banana和Apple accessOrderMap.get("Banana"); accessOrderMap.get("Apple"); System.out.println("访问顺序:" + accessOrderMap); // 输出:{Pear=15, Banana=20, Apple=10} } } 

3. TreeMap:排序的键值对集合

  • 底层原理:基于红黑树实现,key 按自然顺序或自定义比较器排序;
  • 核心特点:有序(key 排序后顺序)、key 唯一、查询和增删效率 O (logn);
  • 适用场景:需要按 key 排序的场景(如成绩排名表、字典序查询)。
实战代码:TreeMap 排序
import java.util.TreeMap; public class TreeMapDemo { public static void main(String[] args) { // 1. 自然排序(Integer按数值升序) TreeMap<Integer, String> numMap = new TreeMap<>(); numMap.put(3, "C"); numMap.put(1, "A"); numMap.put(2, "B"); System.out.println("自然排序:" + numMap); // 输出:{1=A, 2=B, 3=C} // 2. 自定义排序(key按字符串长度降序) TreeMap<String, Integer> strMap = new TreeMap<>((s1, s2) -> s2.length() - s1.length()); strMap.put("Apple", 10); strMap.put("Banana", 20); strMap.put("Pear", 15); System.out.println("自定义排序:" + strMap); // 输出:{Banana=20, Apple=10, Pear=15} } } 

4. ConcurrentHashMap:线程安全的高效并发集合

  • 底层原理:JDK8 之前是 “分段锁”,JDK8 之后是 “CAS+ synchronized”,只锁定链表 / 红黑树的节点,不锁定整个数组,并发效率高;
  • 核心特点:线程安全、高效并发、key 唯一、无序;
  • 适用场景:多线程环境下的键值对存储(如分布式系统中的共享配置、缓存)。
实战代码:ConcurrentHashMap 并发操作
import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class ConcurrentHashMapDemo { public static void main(String[] args) throws InterruptedException { Map<Integer, Integer> map = new ConcurrentHashMap<>(); // 多线程并发添加元素 Runnable task = () -> { for (int i = 0; i < 1000; i++) { map.put(i, i); } }; Thread t1 = new Thread(task); Thread t2 = new Thread(task); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("最终元素个数:" + map.size()); // 输出:1000(无并发问题) } } 

四、集合框架选型指南(开发必备)

场景需求推荐集合类核心原因
频繁查询、少量增删ArrayList数组实现,查询效率 O (1)
频繁增删、少量查询LinkedList链表实现,增删效率 O (1)
元素去重(无序)HashSet哈希表实现,去重效率高
元素去重(有序)LinkedHashSet保持插入顺序,兼顾去重和有序
元素去重 + 排序TreeSet红黑树排序,自动去重
任务排队、先进先出LinkedList(Queue)双端队列实现,增删效率高
键值对存储(无序、高效)HashMap哈希表实现,查询 O (1)
键值对存储(有序)LinkedHashMap保持插入 / 访问顺序
键值对存储(排序)TreeMap按 key 排序,支持自定义比较器
多线程并发键值对存储ConcurrentHashMap线程安全,并发效率高
数组去重HashSet一行代码实现,简洁高效
用户信息、配置项存储HashMap/LinkedHashMap按 key 快速查询 value

五、常见坑点与避坑指南

  1. ArrayList 遍历中删除元素:使用普通 for 循环删除会导致索引错位,推荐使用迭代器(Iterator.remove())或增强 for 循环配合标记;
  2. HashMap 的 key 必须重写 equals () 和 hashCode ():自定义对象作为 key 时,需重写这两个方法,否则无法保证去重正确性;
  3. HashSet/HashMap 无序性:不要依赖遍历顺序,若需有序请使用 LinkedHashSet/LinkedHashMap;
  4. 线程安全问题:ArrayList、HashMap、HashSet 等都是线程不安全的,多线程环境下需使用 ConcurrentHashMap、CopyOnWriteArrayList 等线程安全集合,或手动加锁;
  5. TreeSet/TreeMap 的 key 必须可比较:自定义对象作为 key 时,需实现 Comparable 接口或传入 Comparator,否则抛出 ClassCastException。

六、总结

Java 集合框架是开发中的 “工具箱”,核心是根据业务场景选择合适的存储结构:

  • 需按位置访问→List(ArrayList/LinkedList);
  • 需去重→Set(HashSet/LinkedHashSet/TreeSet);
  • 需排队→Queue(LinkedList);
  • 需键值对查询→Map(HashMap/LinkedHashMap/TreeMap);
  • 多线程环境→ConcurrentHashMap/CopyOnWriteArrayList。

掌握集合的底层原理(数组、链表、红黑树)能帮助你更精准地选型,而熟练使用常用操作(增删改查、遍历)能提高开发效率。建议多结合实际场景练习,理解不同集合的适用边界,避免 “一刀切” 使用 ArrayList 或 HashMap。

Could not load content