Java中Set排序问题:为何Set.of顺序不确定但Collectors.toSet顺序固定?
关于两类Set遍历顺序差异的核心原因
两类Set的实现本质区别
- 代码中最开始通过
Set.of(...)创建的不可变Set,其非确定的遍历顺序是刻意设计的特性:
这类实现为了防御哈希碰撞引发的拒绝服务攻击,会在JVM启动时随机生成散列扰码种子,相同元素在不同JVM运行实例下的落桶位置完全随机,遍历顺序没有任何可预测性,这也是示例中每次运行看到初始Set打印顺序都不一样的原因。 - 通过
Collectors.toSet()收集得到的Set,其行为完全取决于当前JDK的默认实现:
从规范层面来说,Collectors.toSet()从来没有承诺返回的Set具备可变性、固定遍历顺序、可序列化特性,只是在OpenJDK的默认实现下,这个收集器最终会返回一个普通的HashSet实例。
为什么示例中收集到的HashSet顺序始终固定
- HashSet的底层基于HashMap实现,遍历顺序由内部哈希表数组的存储位置决定,计算落桶位置时完全依托元素自身固定的
hashCode值,不会像Set.of(...)那样引入跨JVM实例的随机化扰码。 - 示例中经过map操作收集到的元素全是单字符String,这类元素的
hashCode是完全固定的(比如"C"的hash值为67、"T"为84、"I"为73、"N"为78),加上HashMap默认初始容量固定为16,几个元素计算出的落桶位置永远是固定的。遍历哈希表时从低索引到高索引依次扫描非空桶位,就会永远输出C-T-I-N的顺序,给人一种“顺序始终固定”的直观感受。 - 需要明确的是:这种固定顺序只是实现细节带来的巧合,不是任何规范要求的行为。如果收集到的元素hash值发生变化、换用其他JDK发行版、JDK后续修改了
Collectors.toSet()的默认返回实现(比如替换为LinkedHashSet),这个顺序随时可能变化,绝对不能在业务代码中依赖Set的遍历顺序。
内容的提问来源于stack exchange,提问作者mrok
相关产品推荐
相关产品推荐

