
为什么 LinkedHashSet 能去重又保序,而 HashSet 不行
因为 LinkedHashSet 内部用哈希表 + 双向链表实现,插入顺序由链表维护;HashSet 只有哈希表,不记录插入顺序。Java 8 之后 HashSet 的迭代顺序虽有一定规律(取决于桶分布和扩容),但**不保证稳定**,不能当保序用。
常见错误现象:new HashSet(list) 转成集合后转回 ArrayList,发现元素顺序乱了,尤其在不同 JVM 版本或数据量变化时更明显。
- 使用场景:清洗用户上传的 ID 列表、日志中按时间采集的事件流去重、前端传来的待处理任务队列
- 参数差异:构造
LinkedHashSet时传入initialCapacity和loadFactor对性能影响不大,除非你明确知道数据量极大且对初始化开销敏感 - 性能影响:比
HashSet略高一点内存占用(每个节点多两个指针),但时间复杂度仍是 O(1) 平均查找/插入
一行代码去重并保持顺序的写法及注意事项
最简写法是 new ArrayList(new LinkedHashSet(list)),但它有个隐藏坑:如果 list 是 null,会直接抛 NullPointerException。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 始终先判空:
Objects.requireNonNull(list, "list must not be null")或手动检查 - 如果原
list是Arrays.asList()返回的不可变列表,new LinkedHashSet仍能正常工作,但后续修改原列表不会影响新集合 - 注意元素必须正确重写
equals()和hashCode(),否则重复判断失效 —— 这是 90% 的“去重失败”根源 - 示例:
List<string> deduped = new ArrayList(new LinkedHashSet(originalList));</string>
想保留首次出现位置,但原始 list 含 null 怎么办
LinkedHashSet 允许存一个 null,但如果原始 list 中有多个 null,它只会留第一个 —— 这符合“去重”预期,但容易被忽略。
问题在于:有些业务逻辑把 null 当作合法值(比如数据库字段允许为空),这时你得确认“多个 null 是否真该视为重复”。
- 如果需要严格按对象语义去重(比如自定义类),
null值本身不会干扰,只要你的equals()实现合理(通常推荐用Objects.equals(a, b)) - 如果原始
list是List<integer></integer>,且含多个null,LinkedHashSet会只留一个,这是正确行为 - 兼容性提醒:Java 7+ 都支持,无需额外依赖
替代方案对比:Stream.distinct() vs LinkedHashSet
list.stream().distinct().collect(Collectors.toList()) 看起来更函数式,但底层其实也是靠 LinkedHashSet 实现的(JDK 源码里明确用了它)。所以两者效果一致,但有细微差别:
-
Stream.distinct()在小数据量下略慢(创建 Stream 对象、包装等开销),大数据量无明显差距 -
Stream方式天然支持链式调用(比如接着filter()或map()),适合组合操作 - 容易踩的坑:
distinct()依赖元素的equals()/hashCode(),跟LinkedHashSet完全一样,别以为换了写法就绕过这个问题 - 不要为了“看起来现代”硬用 Stream —— 简单去重,
LinkedHashSet更直白、调试更方便
equals() 和 hashCode() 是否真正覆盖到位,尤其是继承自父类、用了 Lombok 的 @Data 却忘了 @EqualsAndHashCode 的情况。








