这是 java.util.stream.Collectors
类的 toSet()
方法的实现:
public static <T>
Collector<T, ?, Set<T>> toSet() {
return new CollectorImpl<>((Supplier<Set<T>>) HashSet::new, Set::add,
(left, right) -> { left.addAll(right); return left; },
CH_UNORDERED_ID);
}
正如我们所见,它使用了一个HashSet
并调用了add
。来自 HashSet
documentation , “它不保证集合的迭代顺序;特别是,它不保证顺序将随着时间的推移保持不变。”
在下面的代码中,String
的 List
被流式传输、排序并收集到 Set
中:
public static void main(String[] args) {
Set<String> strings = Arrays.asList("c", "a", "b")
.stream()
.sorted()
.collect(Collectors.toSet());
System.out.println(strings.getClass());
System.out.println(strings);
}
这提供了输出:
类 java.util.HashSet
[a, b, c]
输出已排序。我认为这里发生的事情是,虽然 HashSet
文档提供的契约(Contract)指定排序不是它提供的东西,但实现恰好按顺序添加。我想这可能会在未来的版本中发生变化/在 JVM 之间有所不同,并且更明智的方法是执行类似 Collectors.toCollection(TreeSet::new)
的操作。
调用Collectors.toSet()
时可以依赖sorted()
吗?
此外,“它不保证订单会随着时间的推移保持不变”到底是什么意思? (我想 add
,remove
,底层数组的大小调整?)
最佳答案
要回答这个问题,您必须对 HashSet
的实现方式有所了解。顾名思义,HashSet
是使用哈希表 实现的。基本上,哈希表是一个由元素哈希索引的数组。哈希函数(在 Java 中,对象的哈希值由 object.hashCode()
计算)基本上是一个满足几个条件的函数:
- (相对)快速计算给定元素
.equals()
彼此具有相同哈希值的两个对象- 不同项目具有相同哈希值的可能性很低
因此,当您遇到一个“已排序”(理解为“迭代器保留元素的自然顺序”)的 HashSet
时,这是由于几个巧合:
- 元素的自然顺序遵循其
hashCode
的自然顺序 - 哈希表足够小,不会发生冲突(具有相同哈希码的两个元素)
如果查看 String
类的 hashCode()
方法,您会发现对于单字母字符串,哈希码对应于 Unicode 索引(代码点)字母的 - 所以在这种特定情况下,只要哈希表足够小,元素就会被排序。然而,这是一个巨大的巧合和
- 不适用于任何其他排序顺序
- 对于 hashCode 不遵循其自然顺序的类不成立
- 不会持有冲突的哈希表
此外,这与 sorted()
在流上被调用这一事实无关 - 这仅仅是由于 hashCode()
的实现方式和因此哈希表的顺序。因此,问题的简单答案是“否”。
关于java - 使用 Java 8 Streams API,在调用 Collectors.toSet() 时是否可以依赖 sorted()?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46857619/