一文认识Java常见集合

引言

在 Java 的世界里,集合框架犹如一位无声的搭档,它伴随着我们编写的每一行代码,支撑着数据处理的核心逻辑。无论是电商系统中的购物车管理、社交网络的好友关系存储,还是大数据平台的数据聚合处理,Java 集合都扮演着不可或缺的角色。

然而,面对 ArrayList、LinkedList、HashMap、ConcurrentHashMap等众多选择,你是否曾感到困惑:

为什么有时候使用 ArrayList很快,有时候却很慢?

HashMap和 ConcurrentHashMap在并发场景下该如何选择?

LinkedList真的比 ArrayList更适合插入操作吗?

为什么遍历 CopyOnWriteArrayList时修改数据不会报错?

这些问题背后,隐藏着 Java 集合框架的深刻设计哲学。Java 集合不仅是数据存储的工具,更是算法思想、数据结构、并发控制和设计模式的完美结合。从 JDK 1.2 的初代集合框架,到如今 Java 21 中不断优化的并发容器,集合框架见证了 Java 语言的进化历程。

本文将带你深入 Java 集合的核心世界,我们将:

📚 系统梳理​ - 从 List、Set、Map、Queue 四大体系出发,构建完整的知识脉络

🔍 深入源码​ - 揭示 HashMap 的扩容机制、ArrayList 的动态增长原理

⚡ 性能对比​ - 在不同场景下,如何选择最合适的集合类

🛡️ 并发安全​ - 探索多线程环境下的最佳实践

💡 实战应用​ - 结合真实业务场景,讲解集合的最佳使用姿势

无论你是刚接触 Java 的初学者,还是有一定经验的开发者,相信这篇文章都能帮助你:

彻底理解 Java 集合的内部工作原理

避免常见的性能陷阱和并发问题

在面试和实际工作中游刃有余地选择集合类型

编写出更高效、更健壮的 Java 代码

让我们从最基础的 "为什么需要集合" 开始,逐步深入这个既熟悉又神秘的 Java 核心领域。准备好你的思考,我们即将启程探索 Java 集合的精彩世界!

2.集合

2.1 Java 集合框架体系

bash 复制代码
Collection (接口)
├── List (有序、可重复)
│   ├── ArrayList
│   ├── LinkedList
│   ├── Vector
│   └── Stack
├── Set (无序、唯一)
│   ├── HashSet
│   ├── LinkedHashSet
│   └── TreeSet
└── Queue (队列)
    ├── PriorityQueue
    ├── ArrayDeque
    └── LinkedList

Map (接口)
├── HashMap
├── LinkedHashMap
├── TreeMap
├── Hashtable
└── ConcurrentHashMap

2.2 List 接口实现

2.2.1 ArrayList

特点:基于动态数组,随机访问快,增删慢

线程安全:非线程安全

扩容:默认初始容量10,按1.5倍扩容

java 复制代码
List<String> list = new ArrayList<>();
list.add("Java");
list.get(0);  // O(1)

2.2.2 LinkedList

特点:基于双向链表,增删快,随机访问慢

用途:频繁插入删除的场景

java 复制代码
List<String> list = new LinkedList<>();
list.add("First");
list.addLast("Last");  // 特有方法

2.2.3 Vector

特点:线程安全的ArrayList(使用synchronized)

性能:较慢,已逐渐被淘汰

扩容:默认2倍扩容

java 复制代码
import java.util.Vector;

public class VectorExample {
    public static void main(String[] args) {
        // 创建 Vector
        Vector<String> vector = new Vector<>();
        
        // 添加元素
        vector.add("Java");
        vector.add("Python");
        vector.add("C++");
        vector.add("JavaScript");
        
        System.out.println("Vector元素: " + vector); // [Java, Python, C++, JavaScript]
        
        // 获取元素
        String first = vector.get(0);
        System.out.println("第一个元素: " + first); // Java
        
        // 删除元素
        vector.remove("Python");
        System.out.println("删除后: " + vector); // [Java, C++, JavaScript]
        
        // 遍历元素
        System.out.println("遍历Vector:");
        for (String language : vector) {
            System.out.println("- " + language);
        }
    }
}

2.2.4 CopyOnWriteArrayList

CopyOnWriteArrayList​ 是 Java 并发包 (java.util.concurrent) 中的一个线程安全的 List 实现。它采用 "写时复制" (Copy-On-Write)​ 策略来保证线程安全。

  1. 线程安全

    读操作完全无锁

    写操作使用锁保证线程安全

  2. 弱一致性迭代器

    迭代器基于创建时的数组快照

    迭代过程中不会反映后续的修改

  3. 写时复制

    每次修改都创建新数组

    原数组在修改期间保持不变

java 复制代码
// 读操作时间复杂度:O(1)
public E get(int index) {
    return get(getArray(), index);  // 直接访问数组,无锁
}

// 写操作时间复杂度:O(n) - 需要复制整个数组
public boolean add(E e) {
    synchronized (lock) {
        Object[] elements = getArray();
        int len = elements.length;
        // 复制整个数组!这是性能瓶颈
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    }
}

适合使用场景:

✅ 读多写少

java 复制代码
// 场景:配置信息缓存
CopyOnWriteArrayList<Config> configCache = new CopyOnWriteArrayList<>();
// 大量读取,极少修改

✅遍历操作频繁

java 复制代码
// 场景:监听器列表
CopyOnWriteArrayList<EventListener> listeners = new CopyOnWriteArrayList<>();
// 经常遍历通知所有监听器

✅需要遍历时修改

java 复制代码
// 不会抛出 ConcurrentModificationException
for (String item : list) {
    if (condition) {
        list.remove(item);  // 安全操作
    }
}

❌ 不适合场景

写操作频繁

大数据量集合(复制成本高)

实时性要求高(数据可能不是最新的)

List常见问题

从网上以及之前面试经历中收集了一些问题,例如 Java集合常见面试题总结(上)|Java Guid

ArrayList 和 Array (数组) 的区别?

  • ArrayList 内部基于动态数组实现,比 Array (静态数组) 使用起来更加灵活:
  • ArrayList 会根据实际存储的元素动态地扩容或缩容,而 Array 被创建之后就不能改变它的长度了。
  • ArrayList 允许你使用泛型来确保类型安全,Array 则不可以。
  • ArrayList 中只能存储对象。对于基本类型数据,需要使用其对应的包装类(如 Integer、Double 等)。Array 可以直接存储基本类型数据,也可以存储对象。
  • ArrayList 支持插入、删除、遍历等常见操作,并且提供了丰富的 API 操作方法,比如 add() 、 remove() 等。Array 只是一个固定长度的数组,只能按照下标访问其中的元素,不具备动态添加、删除元素的能力。
  • ArrayList 创建时不需要指定大小,而 Array 创建时必须指定大小。

2.3 Set 接口实现

2.3.1 HashSet

特点:基于HashMap实现,无序

性能:添加、删除、查询 O(1)

线程安全:非线程安全

java 复制代码
Set<String> set = new HashSet<>();
set.add("apple");
set.add("banana");

2.3.2 LinkedHashSet

特点:保持插入顺序

底层:LinkedHashMap实现

java 复制代码
Set<String> set = new LinkedHashSet<>();

2.3.3 TreeSet

特点:基于红黑树,元素有序

排序:自然排序或Comparator

java 复制代码
Set<String> set = new TreeSet<>();
set.add("c");
set.add("a");  // 自动排序:["a", "c"]

2.4 Map 接口实现

2.4.1 HashMap

特点:数组+链表/红黑树,键值对存储

线程安全:非线程安全

重要参数:

初始容量:16

加载因子:0.75

链表转红黑树阈值:8

红黑树转链表阈值:6

java 复制代码
Map<String, Integer> map = new HashMap<>();
map.put("age", 25);
map.get("age");

2.4.2 LinkedHashMap

特点:保持插入顺序或访问顺序

用途:LRU缓存实现

java 复制代码
Map<String, Integer> map = new LinkedHashMap<>();

2.4.3 TreeMap

特点:基于红黑树,键有序

java 复制代码
Map<String, Integer> map = new TreeMap<>();

2.4.4 ConcurrentHashMap

特点:线程安全的HashMap(分段锁/CAS)

推荐:多线程环境首选

java 复制代码
Map<String, String> map = new ConcurrentHashMap<>();

2.5 Queue 接口实现

2.5.1 PriorityQueue

特点:优先级队列(最小堆)

java 复制代码
Queue<Integer> queue = new PriorityQueue<>();
queue.offer(5);
queue.poll();  // 最小元素

2.5.2 ArrayDeque

特点:双端队列,数组实现

java 复制代码
Deque<String> deque = new ArrayDeque<>();
deque.addFirst("first");
deque.addLast("last");

2.6 Java 集合类线程安全性对照表

非线程安全 线程安全替代
ArrayList CopyOnWriteArrayList
HashSet CopyOnWriteArraySet
HashMap ConcurrentHashMap
TreeMap ConcurrentSkipListMap

表格说明:

  • CopyOnWriteArrayList: 写时复制,适合读多写少的场景
  • CopyOnWriteArraySet: 基于 CopyOnWriteArrayList 实现
  • ConcurrentHashMap: 分段锁/CAS 实现,性能较好
  • ConcurrentSkipListMap: 跳表实现,键有序的并发 Map

使用示例:

java 复制代码
// 线程安全的 List
List<String> list = new CopyOnWriteArrayList<>();

// 线程安全的 Map
Map<String, Object> map = new ConcurrentHashMap<>();

Java 集合类性能对比表

集合类 获取 添加 删除 内存
ArrayList O(1) O(1)~O(n) O(n)
LinkedList O(n) O(1) O(1)
HashSet O(1) O(1) O(1)
TreeSet O(log n) O(log n) O(log n)
HashMap O(1) O(1) O(1)

时间复杂度符号说明

  • O(1): 常数时间复杂度,性能最好
  • O(log n): 对数时间复杂度,性能较好
  • O(n): 线性时间复杂度,性能随数据量增长
  • O(1)~O(n): 最好情况下为O(1),最坏情况下为O(n)

内存占用说明

  • : 内存占用较少
  • : 内存占用适中
  • : 内存占用较高

各集合类特性总结

  1. ArrayList: 随机访问快,尾部添加快,但中间插入/删除慢
  2. LinkedList: 插入删除快,但随机访问慢
  3. HashSet: 基于哈希表,各项操作平均为O(1)
  4. TreeSet: 基于红黑树,保持元素有序,但操作需要O(log n)
  5. HashMap: 基于哈希表,键值对操作平均为O(1)

根据具体需求选择合适的集合类:

  • 需要快速随机访问 → ArrayList
  • 需要频繁插入删除 → LinkedList
  • 需要去重且快速查找 → HashSet
  • 需要有序集合 → TreeSet
  • 需要键值对存储 → HashMap

选择建议

需要快速随机访问​ → ArrayList

频繁插入删除​ → LinkedList

去重且不关心顺序​ → HashSet

去重且保持插入顺序​ → LinkedHashSet

需要排序​ → TreeSet/TreeMap

键值对存储​ → HashMap

多线程环境​ → ConcurrentHashMap, CopyOnWriteArrayList

先进先出队列​ → ArrayDeque

优先级处理​ → PriorityQueue

Java 集合框架非常丰富,根据不同的业务场景选择合适的集合类,可以显著提升程序性能和可维护性。

相关推荐
kyle~3 小时前
Python---Flask 轻量级Web框架
开发语言·python·flask
云深处@3 小时前
【C++11】特殊类&&单例模式
开发语言·c++
烟花落o3 小时前
算法的时间复杂度和空间复杂度
开发语言·数据结构·笔记·算法
玹外之音3 小时前
Spring AI 实战:手把手教你构建支持多会话管理的智能聊天服务
java·spring
西门吹-禅3 小时前
node js 性能处理
开发语言·javascript·ecmascript
我不是8神3 小时前
go-zero微服务框架总结
开发语言·微服务·golang
Ronaldinho Gaúch3 小时前
算法题中的日期问题
开发语言·c++·算法
callJJ4 小时前
Spring Bean 生命周期详解——从出生到销毁,结合源码全程追踪
java·后端·spring·bean·八股文
怒放吧德德4 小时前
AsyncTool + SpringBoot:轻量级异步编排最佳实践
java·后端