欢迎光临
我们一直在努力

为什么 Java 集合要细分这么多类型?

一、为什么 Java 集合要细分这么多类型?

1. 核心底层数据结构不同,性能差异巨大

不同集合底层基于数组、链表、红黑树、哈希表、跳表实现,增删改查时间复杂度天差地别:

  • 数组:随机查询快,中间插入删除慢;
  • 链表:头尾增删快,随机查询慢;
  • 哈希表:读写接近 O (1),无序;
  • 红黑树:有序,查找 O (logn),插入删除平衡;
  • 跳表:有序并发安全,性能均衡。

2. 特性需求不同(五大核心维度区分)

  • 是否允许重复元素:List 允许重复,Set 不允许;
  • 是否有序:插入有序、自然排序、访问有序、无序;
  • 是否允许 null:有的支持多个 null,有的只能 1 个,有的完全禁止;
  • 线程安全:单线程高性能 vs 多线程并发安全;
  • 键值对 / 单元素:Collection 单值集合,Map 键值对映射集合。
  • 3. 业务场景多样化

    业务需求千差万别:

    • 单纯存有序可重复数据 → List
    • 去重存储数据 → Set
    • 键值映射、缓存、查找 → Map
    • 队列、栈、任务排队 → Queue/Deque

    如果只提供一种集合,会出现性能浪费、功能缺失、并发异常等问题,细分是性能、功能、安全三者权衡的结果。

    二、Java 集合整体架构

    集合分为两大根接口:

  • Collection:单列集合,只存单个对象
    • List:有序、可重复
    • Set:不可重复
    • Queue/Deque:队列、双端队列
  • Map:双列集合,键 (Key)- 值 (Value) 映射,Key 不可重复
  • 第一部分:List 系列(有序、可重复、支持索引)

    通用特点

  • 存入顺序 = 取出顺序,有数字下标索引;
  • 允许元素重复,允许存多个 null;
  • 支持根据索引 get(i) 随机访问。
  • 1. ArrayList

    底层

    动态数组(Object []),初始容量 10,扩容 1.5 倍。

    时间复杂度

    • 查询 get(i):O (1)(数组随机访问极强)
    • 尾部新增:O (1)(无扩容);扩容 O (n)
    • 中间 / 头部插入、删除:O (n)(需要数组拷贝移位)

    线程安全

    非线程安全,多线程读写会并发异常。

    适用场景(最常用)

  • 绝大多数单线程存储场景:遍历多、查询多,极少中间插入删除;
  • 分页数据、列表展示、循环遍历;
  • 临时缓存有序数据,数据量中等;
  • 不适合

    频繁在集合头部、中间插入 / 删除;超大集合频繁扩容。

    2. LinkedList

    底层

    双向链表,不支持数组随机访问,无扩容概念。

    时间复杂度

    • 查询 get(i):O (n)(需要从头 / 尾遍历找节点)
    • 头尾增删:O (1)
    • 中间增删:O (n)(需要先遍历定位节点)

    额外功能

    实现 Deque 接口,可当队列、栈、双端队列使用。

    线程安全

    非线程安全。

    适用场景

  • 频繁在头部 / 尾部新增、删除元素;
  • 需要同时充当队列、栈结构;
  • 数据量大,很少根据索引查询;
  • 不适合

    大量随机索引查询(循环 get 会极度缓慢)。

    3. Vector

    底层

    动态数组,初始容量 10,扩容 2 倍。

    核心特点

    所有方法加synchronized同步锁,线程安全;性能极差。

    适用场景

    老旧遗留项目兼容,新项目完全不推荐,并发场景用 CopyOnWriteArrayList 替代。

    4. Stack(Vector 子类,废弃)

    基于 Vector 实现栈,push/pop/peek,同步锁性能差。

    替代方案

    Deque 接口实现类(ArrayDeque)官方推荐做栈。

    5. CopyOnWriteArrayList

    底层

    数组,写时复制机制:修改元素时复制一份新数组操作,完成替换原数组;读不加锁。

    特点

    • 读极快,写极慢;
    • 线程安全;
    • 遍历不会并发修改异常;

    适用场景

    读多写极少的并发场景:配置缓存、白名单、常量列表;

    不适合

    高频增删改场景,每次写复制数组内存开销巨大。

    第二部分:Set 系列(不可重复,无索引)

    通用特点

    元素唯一,自动去重;没有索引,不能通过下标取值;判断重复依靠 equals() + hashCode()。

    1. HashSet

    底层

    哈希表(数组 + 链表 + 红黑树),基于 HashMap 实现,元素存到 Map 的 key。

    特性

  • 无序:存取顺序不一致;
  • 允许存1 个 null;
  • 去重依赖 hashCode+equals;
  • 查询、新增、删除接近 O (1);
  • 线程安全

    非线程安全。

    适用场景

  • 快速去重:批量数据过滤重复;
  • 只需要判断元素是否存在(存在性校验);
  • 不需要保证顺序,追求最高读写性能; 例:用户 ID 去重、黑名单校验。
  • 2. LinkedHashSet

    底层

    哈希表 + 双向链表(继承 HashSet,基于 LinkedHashMap)。

    特性

  • 插入有序:存取顺序完全一致;
  • 仅允许 1 个 null;
  • 性能略低于 HashSet,链表维护顺序有少量开销;
  • 适用场景

  • 需要去重 + 保留插入顺序;
  • 缓存 LRU 简易实现、有序不重复列表; 例:操作日志去重,保留操作先后顺序。
  • 3. TreeSet

    底层

    红黑树(基于 TreeMap)。

    特性

  • 自然排序:元素实现 Comparable 接口,或创建时传入 Comparator 比较器;
  • 不允许 null 元素;
  • 元素自动升序 / 自定义排序;
  • 增删查 O (logn);
  • 适用场景

  • 自动排序且去重:排行榜、有序唯一编号;
  • 需要区间查询:获取大于 / 小于某个值的元素; 例:分数排名、有序唯一设备 ID。
  • 4. CopyOnWriteArraySet

    底层

    封装 CopyOnWriteArrayList 实现。

    特性

    线程安全,读快写慢,有序,元素可重复?底层自动去重。

    适用场景

    并发环境下读多写少、需要去重的有序集合;极少使用。

    5. ConcurrentSkipListSet

    底层

    跳表,并发安全有序 Set。

    特性

    多线程并发读写性能远优于 TreeSet,自动排序;

    适用场景

    高并发下需要有序、去重的数据存储;分布式本地有序计数器。

    第三部分:Queue / Deque 队列、双端队列(存取有规则)

    Queue:单向队列,先进先出 FIFO; Deque:双端队列,可头尾操作,可做栈 LIFO。

    1. ArrayDeque(最推荐队列 / 栈)

    底层

    循环数组,无容量限制自动扩容。

    特性

  • 非线程安全;
  • 不允许 null;
  • 头尾增删 O (1),性能远超 LinkedList;
  • 可当队列、栈、双端队列;
  • 适用场景

  • 普通任务队列、消息临时排队;
  • 栈结构替代 Stack;
  • 滑动窗口算法、BFS 广度优先遍历;
  • 2. LinkedList(充当 Queue/Deque)

    双向链表实现队列,头尾操作 O (1),随机查询慢。

    适用场景

    队列数据频繁扩容、长度波动极大,不适合 ArrayDeque;极少使用。

    3. PriorityQueue 优先队列

    底层

    最小堆(数组实现二叉堆)。

    特性

  • 出队顺序按元素优先级排序,不是 FIFO;
  • 不允许 null,自定义比较器控制大小堆;
  • 非线程安全;
  • 适用场景

  • 任务优先级调度(VIP 任务优先执行);
  • TopK 问题:取最大 / 最小前 N 个数据; 例:定时任务优先级、排行榜 Top10。
  • 4. 并发阻塞队列(java.util.concurrent,多线程生产者消费者)

    (1) ArrayBlockingQueue

    底层固定长度数组,有界阻塞队列;必须指定容量。 适用:生产消费速度可控,防止无限堆积,线程池任务队列。

    (2) LinkedBlockingQueue

    单向链表,无界 / 有界可选,读写两把锁,并发性能高于 ArrayBlockingQueue; 适用:线程池默认队列、通用生产者消费者。

    (3) SynchronousQueue

    不存储元素,插入操作必须等待对应删除操作,直接交付; 适用:任务必须立刻处理,无缓冲,Executors.newCachedThreadPool 使用。

    (4) DelayQueue

    延迟阻塞队列,元素实现 Delayed 接口,到期才能取出; 适用:定时任务、超时订单、缓存过期清理。

    (5) ConcurrentLinkedQueue / ConcurrentLinkedDeque

    无锁并发队列,基于 CAS,非阻塞;高并发读写性能极高; 适用:高并发消息排队,不需要阻塞等待。

    第四部分:Map 键值对集合(Key 唯一,存储映射关系)

    通用特点

    存储 key-value 键值对;key 不可重复,value 可重复;key 重复会覆盖旧 value。

    1. HashMap(日常最常用)

    底层

    哈希表:数组 + 链表 + 红黑树,JDK8 优化,链表长度≥8 转红黑树。

    特性

  • 无序,存取顺序不一致;
  • key 允许 1 个 null,value 允许多个 null;
  • 读写接近 O (1),单线程性能最强;
  • 非线程安全;
  • 适用场景

  • 绝大多数缓存、映射查询;
  • 根据唯一 key 快速获取对应值; 例:用户 ID – 用户信息、编码 – 名称映射。
  • 2. LinkedHashMap

    底层

    哈希表 + 双向链表。

    特性

  • 插入有序,遍历顺序和 put 顺序一致;
  • 支持开启 accessOrder=true,实现访问有序(LRU);
  • 适用场景

  • 需要有序的键值映射;
  • 简易本地 LRU 缓存(超过容量删除最久未访问数据); 例:接口参数有序存储、本地有限缓存。
  • 3. TreeMap

    底层

    红黑树。

    特性

  • key 自动排序(自然排序 / 自定义比较器);
  • key 不允许 null;
  • 支持区间查询:subMap、higherKey、lowerKey;
  • 适用场景

  • 按键自动排序的映射;
  • 范围检索业务; 例:时间戳 – 日志映射、有序区间统计。
  • 4. Hashtable(淘汰)

    特性

    全方法 synchronized 同步,线程安全;key 和 value 都不允许 null;性能极差。 新项目禁用,并发用 ConcurrentHashMap。

    5. ConcurrentHashMap(高并发首选 Map)

    底层

    JDK7 分段锁、JDK8 CAS + 同步锁 + 红黑树;

    特性

  • 线程安全,并发读写性能远超 Hashtable;
  • key/value 都不允许 null;
  • 分段减少锁竞争,支持高并发插入查询;
  • 适用场景

    多线程环境下的键值缓存:全局配置、多线程共享映射、本地并发计数器。

    6. Properties

    继承 Hashtable,专门存储字符串键值对,支持读写 properties 配置文件。 适用:读取项目配置文件(数据库配置、常量配置)。

    7. ConcurrentSkipListMap

    底层跳表,并发安全、key 自动排序; 适用:高并发下需要有序键值映射,替代 TreeMap 并发场景。

    三、各集合选型速查表(业务直接对照)

    单列集合 Collection

    表格

    需求场景推荐集合不推荐
    单线程,查询遍历多,有序可重复 ArrayList LinkedList
    频繁头尾增删,很少索引查询 LinkedList / ArrayDeque ArrayList
    多线程读多写少,有序列表 CopyOnWriteArrayList Vector
    数据去重,不要求顺序 HashSet TreeSet
    数据去重,保留插入顺序 LinkedHashSet TreeSet
    数据去重,自动排序、区间查询 TreeSet HashSet
    栈、普通 FIFO 队列 ArrayDeque Stack、LinkedList
    任务按优先级执行 PriorityQueue ArrayDeque
    多线程生产者消费者阻塞队列 ArrayBlockingQueue / LinkedBlockingQueue LinkedList
    高并发无锁消息队列 ConcurrentLinkedQueue ArrayBlockingQueue

    双列集合 Map

    表格

    需求场景推荐集合
    单线程键值缓存,追求性能 HashMap
    键值需要插入顺序 / LRU 缓存 LinkedHashMap
    key 自动排序、区间查询 TreeMap
    多线程并发读写映射缓存 ConcurrentHashMap
    读取 properties 配置文件 Properties
    高并发 + key 有序 ConcurrentSkipListMap

    四、补充高频易错点

  • 不要用 Vector/Stack/Hashtable:同步锁太重,有现代替代方案;
  • ArrayList 不要频繁中间插入,数据量大优先预估初始容量减少扩容;
  • CopyOnWrite 系列只适合读多写极少,大量写入会 OOM;
  • TreeSet/TreeMap 存储对象必须实现 Comparable 或传入比较器;
  • 并发场景禁止多线程操作 HashMap,会出现死循环、数据丢失;
  • PriorityQueue 只是堆,遍历无序,只有出队时才按优先级取出。
  • 赞(0)
    未经允许不得转载:171主机测评 » 为什么 Java 集合要细分这么多类型?
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址