java集合总结
1 ArrayList
Java ArrayList 的核心特点
- 动态数组结构:自动扩容(默认1.5倍),初始容量10,适合数据量不固定的场景。
- 高效随机访问:通过索引(
get/set
)直接操作元素,时间复杂度 O(1)。 - 低效增删中间元素:插入或删除元素需移动后续元素,时间复杂度 O(n)。
- 允许重复和null:可存储重复值和
null
,灵活但需注意业务逻辑。 - 非线程安全:多线程操作需手动同步(如加锁或用
Collections.synchronizedList
)。
开发中的典型使用场景
-
动态数据缓存
- 场景:缓存从数据库或API查询的结果集(如用户列表、商品信息)。
- 优势:自动扩容,支持快速遍历和按索引查询。
-
批量数据处理
- 场景:读取文件、解析日志后存储数据,或批量上传前的临时存储。
- 优势:顺序添加高效,适合流式处理(如过滤、转换后存入列表)。
-
UI层数据展示
- 场景:前端页面渲染表格、下拉列表时,后端返回
ArrayList
格式的数据集合。 - 优势:直接序列化为JSON,方便前端遍历。
- 场景:前端页面渲染表格、下拉列表时,后端返回
-
算法中的临时存储
- 场景:实现广度优先搜索(BFS)时用
ArrayList
模拟队列(需配合索引控制);动态规划中暂存中间结果。 - 优势:快速随机访问,避免链表的结构开销。
- 场景:实现广度优先搜索(BFS)时用
-
数据转换与过滤
- 场景:结合Stream API对集合进行过滤、映射、排序(如筛选出符合条件的订单)。
- 优势:链式操作后直接生成新列表,代码简洁。
-
配置项管理
- 场景:存储系统配置参数(如白名单IP、允许的文件类型)。
- 优势:初始化后较少修改,适合高频读取。
避坑指南
- 避免频繁中间插入/删除:大量增删时改用
LinkedList
。 - 预分配容量:已知数据量大时,初始化指定容量(如
new ArrayList<>(1000)
)减少扩容开销。 - 多线程同步:并发环境用
CopyOnWriteArrayList
或手动加锁。 - 慎用
contains
方法:判断元素是否存在时,ArrayList
的contains
是 O(n),高频查询改用HashSet
。
总结
- 选 ArrayList 的场景:数据量动态变化、高频查询、少量增删、需要快速原型开发。
- 不选的场景:高频在头部/中间增删、严格的内存限制(数组结构有预留空间)、线程安全要求高。
2 LinkedList
Java LinkedList 的核心特点
- 双向链表结构:每个节点包含前后节点的引用,内存空间非连续,增删节点只需修改指针,无需移动数据。
- 高效增删头尾元素:
addFirst()
、addLast()
、removeFirst()
、removeLast()
等操作时间复杂度为 O(1)。 - 低效随机访问:按索引访问元素需从头或尾遍历链表,时间复杂度 O(n)。
- 实现双端队列(Deque):天然支持队列、栈、双端队列的操作(如
offer()
、poll()
、push()
、pop()
)。 - 内存占用较高:每个节点需额外存储前后指针,适合元素数量较少或对增删性能要求高的场景。
- 非线程安全:多线程操作需手动同步。
开发中的典型使用场景
-
高频头尾增删操作
- 场景:
- 实现任务队列(如线程池任务调度)。
- 消息中间件的临时消息缓存。
- 操作历史记录(如撤销/重做功能,通过头插法记录操作步骤)。
- 优势:增删头尾元素的性能远高于
ArrayList
。
- 场景:
-
实现栈(Stack)或队列(Queue)
- 场景:
- 栈:方法调用栈模拟、表达式解析(如括号匹配)。
- 队列:广度优先搜索(BFS)中的待处理节点队列。
- 优势:直接调用
push()
、pop()
(栈)或offer()
、poll()
(队列),无需额外封装。
- 场景:
-
需要双向遍历的场景
- 场景:
- 音乐播放器的“上一曲/下一曲”列表。
- 分步表单中“上一步/下一步”导航。
- 优势:通过
ListIterator
支持双向遍历和修改。
- 场景:
-
动态调整元素顺序
- 场景:
- LRU缓存淘汰算法(快速将最近使用的元素移动到头部)。
- 实时排行榜中频繁调整元素位置。
- 优势:插入/删除中间元素时仅需修改指针,无数据搬移开销。
- 场景:
-
不确定数据量的流式处理
- 场景:
- 逐行读取大文件并动态处理(如日志分析)。
- 网络流数据的临时缓冲(如分片上传)。
- 优势:链表动态扩展无扩容成本,避免
ArrayList
的扩容数组拷贝。
- 场景:
-
内存敏感但增删频繁的小数据集
- 场景:
- 高频增删的小型配置项集合(如动态路由规则)。
- 临时存储中间计算结果(如递归算法的路径记录)。
- 优势:小数据量下链表的内存劣势不明显,且增删性能更优。
- 场景:
避坑指南
- 避免按索引遍历:优先用
Iterator
或foreach
,避免低效的get(index)
。 - 慎用中间插入/删除:虽然链表增删节点快,但定位到中间位置仍需 O(n) 时间。
- 多线程同步:使用
Collections.synchronizedList()
或改用ConcurrentLinkedQueue
。 - 注意内存开销:存储海量数据时,链表的指针内存可能成为瓶颈,优先考虑
ArrayList
。
总结
- 选 LinkedList 的场景:高频头尾增删、需实现队列/栈、双向遍历、动态调整顺序、小规模数据流处理。
- 不选的场景:高频随机访问、海量数据存储、内存严格受限、需要连续内存的场景(如缓存友好型算法)。
对比 ArrayList:
- 增删性能:
LinkedList
在头尾增删占优,ArrayList
在尾部增删占优。 - 访问性能:
ArrayList
的随机访问效率碾压LinkedList
。 - 内存占用:
ArrayList
内存紧凑,LinkedList
因指针占用额外空间。
3 Vector
Java Vector 的核心特点
- 线程安全:所有方法均用
synchronized
修饰,支持多线程并发读写(但性能较低)。 - 动态数组结构:底层基于数组实现,自动扩容(默认容量翻倍,如 10 → 20 → 40)。
- 遗留类:早期 Java 集合的实现(JDK 1.0),现多被
ArrayList
或并发集合(如CopyOnWriteArrayList
)取代。 - 枚举遍历:提供
elements()
方法返回Enumeration
对象(类似Iterator
的前身)。 - 同步开销大:所有操作加锁,单线程环境下性能显著低于
ArrayList
。
开发中的典型使用场景
-
多线程环境下的共享数据存储
- 场景:多个线程需要同时读写同一列表(如全局配置参数表)。
- 注意:即使
Vector
线程安全,复合操作(如“检查再更新”)仍需额外同步。
-
历史遗留系统维护
- 场景:维护早期 Java 版本(如 JDK 1.2 之前)的代码库时,需兼容
Vector
的 API。
- 场景:维护早期 Java 版本(如 JDK 1.2 之前)的代码库时,需兼容
-
需要同步的动态数组
- 场景:简单的多线程任务队列(如生产者-消费者模型),但性能要求不高。
-
替代 Stack 类
- 场景:
Stack
类继承自Vector
,但已不推荐使用;若需线程安全的栈,可用Vector
的addElement()
和removeElement()
模拟栈操作。
- 场景:
-
同步枚举遍历
- 场景:通过
elements()
方法获取Enumeration
遍历集合(需在遍历过程中同步)。
- 场景:通过
避坑指南
- 避免在单线程中使用:优先选择
ArrayList
(性能更高)。 - 慎用复合操作:即使单个方法线程安全,组合操作(如
if (!vec.isEmpty()) vec.remove(0)
)仍需外部同步。 - 替代方案:
- 高并发读:用
CopyOnWriteArrayList
。 - 高并发写:用
ConcurrentLinkedQueue
或ConcurrentHashMap
。 - 简单同步需求:用
Collections.synchronizedList(new ArrayList<>())
。
- 高并发读:用
- 内存浪费风险:容量翻倍扩容可能导致内存冗余,可初始化时指定容量(如
new Vector<>(100)
)。
总结
- 选 Vector 的场景:兼容旧代码、简单的多线程共享数据、需同步且对性能不敏感的场景。
- 不选的场景:高并发写、单线程环境、内存敏感、需要高性能随机访问。
对比 ArrayList/LinkedList:
- 线程安全:
Vector
自带同步,ArrayList/LinkedList
需外部控制。 - 性能:
Vector
的同步机制导致性能远低于ArrayList
。 - 扩容策略:
Vector
容量翻倍,ArrayList
扩容 1.5 倍,内存利用更高效。
现代替代方案:
- 需线程安全的动态数组:
Collections.synchronizedList()
+ArrayList
。 - 高并发场景:
CopyOnWriteArrayList
(读多写少)或ConcurrentLinkedQueue
(写多读少)。
4 HashSet
Java HashSet 的核心特点
- 基于哈希表实现:底层依赖
HashMap
,利用哈希值快速定位元素,查找/插入/删除的平均时间复杂度为 O(1)。 - 元素唯一性:通过
equals()
和hashCode()
方法保证元素不重复。 - 无序性:不保证元素的存储顺序(与插入顺序无关),迭代顺序可能随机变化。
- 允许
null
值:可存储一个null
元素。 - 非线程安全:多线程操作需手动同步(如
Collections.synchronizedSet()
)。 - 性能依赖哈希函数:哈希冲突过多时,性能退化为 O(n)。
开发中的典型使用场景
-
快速去重
- 场景:
- 从数据库或文件中提取数据后,过滤重复记录(如用户ID、订单号)。
- 统计日志中独立IP地址或唯一用户访问量。
- 优势:去重操作时间复杂度低,代码简洁。
- 场景:
-
集合运算
- 场景:
- 判断两个数据集是否有交集(如用户权限校验)。
- 合并多个来源的数据并去重(如多平台订单汇总)。
- 优势:直接调用
addAll()
(并集)、retainAll()
(交集)、removeAll()
(差集)等方法。
- 场景:
-
快速存在性检查
- 场景:
- 缓存校验黑名单(如敏感词、禁止访问的IP)。
- 用户注册时检查用户名是否已被占用。
- 优势:
contains()
方法性能远高于List
的遍历查找。
- 场景:
-
临时唯一性存储
- 场景:
- 遍历树或图时记录已访问的节点(避免重复处理)。
- 分布式任务调度中标记已处理的任务ID。
- 优势:轻量且无需维护顺序。
- 场景:
-
标签或属性管理
- 场景:
- 商品的多标签分类(如“电子产品”“促销品”),确保标签唯一。
- 用户兴趣标签的存储与快速匹配。
- 优势:天然支持唯一性,避免重复标签污染数据。
- 场景:
-
数据清洗与预处理
- 场景:
- 清洗爬虫抓取的网页URL,避免重复爬取。
- 预处理数据集中的异常值或重复条目。
- 优势:去重效率高,适合大规模数据处理。
- 场景:
避坑指南
- 重写
hashCode()
和equals()
:存储自定义对象时,必须正确重写这两个方法,否则无法保证唯一性。 - 避免存储可变对象:若对象哈希值随内容变化,可能导致
HashSet
中元素“丢失”。 - 初始化容量与负载因子:数据量较大时,指定初始容量和负载因子(如
new HashSet<>(1000, 0.75f)
),减少扩容次数。 - 线程安全替代方案:多线程环境用
ConcurrentHashMap.newKeySet()
或CopyOnWriteArraySet
。 - 慎用大规模数据:哈希表内存占用较高,超大规模数据去重可改用布隆过滤器(Bloom Filter)。
总结
- 选 HashSet 的场景:高频去重、快速存在性检查、无需顺序的集合运算、临时唯一性存储。
- 不选的场景:需要元素有序(用
LinkedHashSet
或TreeSet
)、需要按范围查询(用TreeSet
)、内存极度敏感(用位图或数组)。
对比其他 Set 实现:
TreeSet
:基于红黑树,元素按自然顺序或自定义规则排序,查找/插入/删除时间复杂度 O(log n)。LinkedHashSet
:维护插入顺序的链表,适合需要按顺序迭代且去重的场景。
性能关键点:
- 哈希函数的质量直接影响冲突概率,设计差的对象哈希方法会显著降低性能。
- 负载因子默认 0.75,表示哈希表填充 75% 后触发扩容,可根据场景调整(值越小,冲突越少,内存占用越高)。
5 TreeSet
Java TreeSet 的核心特点
- 基于红黑树实现:元素按自然顺序或自定义规则排序,插入时自动维护有序性。
- 元素唯一且有序:通过
compareTo()
或Comparator
保证唯一性,迭代时按升序或自定义顺序输出。 - 操作效率稳定:查找、插入、删除操作的时间复杂度为 O(log n),适合需要动态维护有序集合的场景。
- 不允许
null
值(自然排序模式下):若未指定自定义Comparator
,插入null
会抛出NullPointerException
。 - 非线程安全:多线程操作需手动同步(如
Collections.synchronizedSortedSet()
)。
开发中的典型使用场景
-
动态排序数据集
- 场景:
- 实时排行榜(如游戏玩家积分榜、电商商品销量榜),插入数据时自动排序。
- 股票价格实时更新后按价格排序展示。
- 优势:无需手动调用排序方法,维护成本低。
- 场景:
-
范围查询与过滤
- 场景:
- 查询某区间内的数据(如筛选考试分数在 80~100 分的学生)。
- 统计日志中某时间段内的请求记录(通过
subSet()
、headSet()
、tailSet()
方法)。
- 优势:基于红黑树结构,范围查询效率远高于遍历无序集合。
- 场景:
-
去重且有序存储
- 场景:
- 从异构数据源合并数据后,按时间戳或优先级去重并排序。
- 用户操作日志按时间顺序存储,避免重复记录。
- 优势:同时满足唯一性和有序性需求。
- 场景:
-
任务调度与优先级管理
- 场景:
- 定时任务队列按执行时间排序(如延迟任务调度)。
- 线程池中任务按优先级(高/中/低)动态调整执行顺序。
- 优势:自动按规则调整顺序,确保高优先级任务优先处理。
- 场景:
-
字典序或分类管理
- 场景:
- 实现自动补全功能(如搜索框提示词按字母顺序排序)。
- 文件系统中目录项按名称排序展示。
- 优势:天然支持字典序,简化业务逻辑。
- 场景:
-
复杂规则去重
- 场景:
- 根据自定义业务规则(如“用户ID+日期”组合)去重。
- 订单去重时需忽略大小写或特定字段差异。
- 优势:通过自定义
Comparator
灵活定义唯一性规则。
- 场景:
避坑指南
-
正确实现比较逻辑:
- 存储自定义对象时,需实现
Comparable
接口或提供Comparator
,否则抛出ClassCastException
。 - 比较逻辑需与
equals()
一致,避免破坏唯一性规则(如两个对象compareTo()
返回 0,但equals()
为false
)。
- 存储自定义对象时,需实现
-
慎用可变对象:
- 若对象的排序字段被修改,可能导致
TreeSet
内部顺序混乱,需先删除对象再重新插入。
- 若对象的排序字段被修改,可能导致
-
性能敏感场景慎用:
- 高频插入/删除时,红黑树的旋转操作可能成为性能瓶颈,可改用
HashSet
+ 手动排序。
- 高频插入/删除时,红黑树的旋转操作可能成为性能瓶颈,可改用
-
替代方案:
- 需要线程安全:使用
ConcurrentSkipListSet
。 - 仅需插入顺序:使用
LinkedHashSet
。 - 内存敏感:权衡红黑树的结构开销与查询效率。
- 需要线程安全:使用
总结
- 选 TreeSet 的场景:需动态维护有序集合、高频范围查询、自定义排序规则、去重且排序。
- 不选的场景:仅需去重无需排序(用
HashSet
)、高频随机访问(用ArrayList
)、内存极度受限。
对比其他 Set 实现:
HashSet
:无序,去重效率 O(1),适合纯去重场景。LinkedHashSet
:维护插入顺序,适合需要按插入顺序迭代的去重场景。ConcurrentSkipListSet
:线程安全且有序,适合高并发排序需求。
性能关键点:
- 数据量较小时,
TreeSet
的有序性优势明显;数据量极大时需权衡红黑树的维护成本。 - 自定义
Comparator
的复杂度直接影响操作效率,需避免复杂计算逻辑。
6 HashMap
Java HashMap 的核心特点
- 基于哈希表实现:底层是“数组+链表/红黑树”(JDK8+),通过哈希值快速定位桶位置,理想情况下操作时间复杂度为 O(1)。
- 键值对存储:唯一键(通过
equals()
和hashCode()
去重),允许一个null
键和多个null
值。 - 非线程安全:多线程并发修改可能触发无限循环或数据丢失(JDK7)或脏读(JDK8+)。
- 动态扩容:默认容量 16,负载因子 0.75,容量超过阈值时扩容 2 倍并重新哈希。
- 哈希冲突管理:链表长度超过 8 时转红黑树(提升查询效率),长度小于 6 时退化为链表。
开发中的典型使用场景
-
缓存系统
- 场景:
- 临时存储高频访问数据(如用户会话信息、热点商品详情)。
- 本地缓存数据库查询结果(键为查询条件,值为结果集)。
- 优势:快速通过键查找值,避免重复计算或数据库访问。
- 场景:
-
数据索引与映射
- 场景:
- 建立对象关联(如用户ID映射用户详情、订单号映射物流状态)。
- 文件路径与文件内容的快速映射(如配置文件解析)。
- 优势:直接通过键定位数据,无需遍历。
- 场景:
-
分组与统计
- 场景:
- 统计词频(如文本中每个单词的出现次数)。
- 按属性分组数据(如按部门分类员工、按城市归类订单)。
- 优势:键作为分类标识,值存储统计结果或子集合。
- 场景:
-
配置参数管理
- 场景:
- 存储系统动态配置(如开关参数、API地址映射)。
- 国际化资源文件加载(键为文案ID,值为多语言文本)。
- 优势:灵活增删配置项,支持快速读取。
- 场景:
-
分布式分片
- 场景:
- 按哈希值分配任务到不同节点(如用户ID分片路由)。
- 数据库分库分表时,用键计算数据所属分片。
- 优势:哈希算法均匀分布数据,减少热点问题。
- 场景:
-
对象属性动态扩展
- 场景:
- 为实体附加临时元数据(如请求上下文的附加参数)。
- 动态表单字段存储(键为字段名,值为用户输入)。
- 优势:无需预定义结构,灵活扩展键值对。
- 场景:
避坑指南
-
线程安全替代方案:
- 多线程读写用
ConcurrentHashMap
。 - 多线程读多写少用
Collections.synchronizedMap()
包装。
- 多线程读写用
-
优化哈希函数:
- 自定义对象作为键时,需正确重写
hashCode()
和equals()
,避免哈希冲突。
- 自定义对象作为键时,需正确重写
-
合理初始化容量:
- 预估数据量时指定初始容量(如
new HashMap<>(1000)
),减少扩容开销。
- 预估数据量时指定初始容量(如
-
避免遍历时修改:
- 使用
Iterator
遍历时删除元素,防止ConcurrentModificationException
。
- 使用
-
慎用可变对象作为键:
- 若键对象哈希值变化,会导致数据“丢失”(无法通过
get()
找到原有条目)。
- 若键对象哈希值变化,会导致数据“丢失”(无法通过
-
处理哈希碰撞:
- 高频冲突时,考虑调整哈希算法或改用
LinkedHashMap
(维护插入顺序)。
- 高频冲突时,考虑调整哈希算法或改用
总结
- 选 HashMap 的场景:高频键值查询、去重映射、动态配置、缓存、分组统计、哈希分片。
- 不选的场景:需要有序遍历(用
LinkedHashMap
或TreeMap
)、高并发写(用ConcurrentHashMap
)、范围查询(用TreeMap
)。
对比其他 Map 实现:
Hashtable
:遗留类,全方法同步,性能低,不推荐使用。TreeMap
:基于红黑树,按键自然顺序或自定义顺序排序,操作效率 O(log n)。LinkedHashMap
:维护插入顺序或访问顺序,适合缓存淘汰策略(如LRU)。
性能关键点:
- 哈希函数的质量直接影响冲突率,设计差的哈希函数会导致性能退化为 O(n)。
- 负载因子默认 0.75,表示哈希表填充 75% 后扩容,可根据场景调整(值越小,冲突概率越低,但内存占用更高)。
7 TreeMap
Java TreeMap 的核心特点
- 基于红黑树实现:键值对按键的自然顺序(
Comparable
)或自定义Comparator
排序,保证有序性。 - 键唯一且有序:迭代时按键的升序或自定义顺序输出,支持范围查询(如子映射、头部/尾部映射)。
- 操作效率稳定:查找、插入、删除操作的时间复杂度为 O(log n),适合需要动态维护有序键值对的场景。
- 不允许
null
键(自然排序模式下):若未指定自定义Comparator
,插入null
键会抛出NullPointerException
。 - 非线程安全:多线程操作需手动同步(如
Collections.synchronizedSortedMap()
)。
开发中的典型使用场景
-
动态排序键值对
- 场景:
- 实时排行榜(如玩家积分按分数排序、商品按价格排序)。
- 日志按时间戳排序存储(键为时间戳,值为日志内容)。
- 优势:自动维护键的有序性,无需手动排序。
- 场景:
-
范围查询与区间操作
- 场景:
- 查询某时间段内的数据(如订单在 2023-01-01 至 2023-06-30 的记录)。
- 筛选价格区间内的商品(如 100~500 元)。
- 优势:通过
subMap()
、headMap()
、tailMap()
快速获取子映射。
- 场景:
-
字典序或分类管理
- 场景:
- 实现搜索提示词(如按字母顺序存储关键词)。
- 多语言资源文件按键的字母顺序加载(如国际化文案)。
- 优势:天然支持字典序,简化排序逻辑。
- 场景:
-
带权重的优先级调度
- 场景:
- 任务队列按优先级排序(如高、中、低优先级的后台任务)。
- 定时任务按触发时间排序(键为执行时间,值为任务对象)。
- 优势:按键顺序处理任务,确保优先级规则。
- 场景:
-
复杂键的排序与去重
- 场景:
- 按组合键排序(如“用户ID+操作时间”作为复合键)。
- 去重并排序异构数据(如合并多个数据源的记录)。
- 优势:通过自定义
Comparator
灵活定义排序规则。
- 场景:
-
统计与聚合分析
- 场景:
- 统计区间数据分布(如按分数段统计学生人数)。
- 实时聚合时间窗口内的指标(如每分钟请求量)。
- 优势:基于有序键快速定位区间并聚合结果。
- 场景:
避坑指南
-
正确实现比较逻辑:
- 自定义键对象需实现
Comparable
或提供Comparator
,否则抛出ClassCastException
。 - 比较逻辑需与
equals()
一致,避免键唯一性冲突。
- 自定义键对象需实现
-
慎用可变键对象:
- 若键的排序字段被修改,会导致
TreeMap
内部顺序混乱,需先删除再重新插入。
- 若键的排序字段被修改,会导致
-
替代方案:
- 需要线程安全:使用
ConcurrentSkipListMap
。 - 仅需插入顺序:使用
LinkedHashMap
。 - 无需排序且高频查询:使用
HashMap
。
- 需要线程安全:使用
-
性能敏感场景优化:
- 高频插入/删除时,红黑树的平衡操作可能成为瓶颈,可改用
HashMap
+ 定期排序。
- 高频插入/删除时,红黑树的平衡操作可能成为瓶颈,可改用
总结
- 选 TreeMap 的场景:需要键有序、高频范围查询、动态排序、复杂键规则、优先级调度。
- 不选的场景:仅需快速存取无序数据(用
HashMap
)、内存极度受限、高并发写(用ConcurrentSkipListMap
)。
对比其他 Map 实现:
HashMap
:无序,操作效率 O(1),适合纯键值存取。LinkedHashMap
:维护插入或访问顺序,适合缓存淘汰策略(如LRU)。ConcurrentSkipListMap
:线程安全且有序,适合高并发排序需求。
性能关键点:
- 数据量较小时,
TreeMap
的有序性优势明显;数据量极大时需权衡红黑树的维护成本。 - 自定义
Comparator
的逻辑复杂度直接影响操作效率,需避免复杂计算。