简体   繁体   English

为什么 HashSet 中的项总是以相同的顺序显示?

[英]Why are the items in the HashSet always displayed in the same order?

I've created two sets: HashSet and unmodifiable set.我创建了两个集合:HashSet 和不可修改的集合。 Both types of sets do not guarantee the order of the elements.两种类型的集合都不能保证元素的顺序。 But I have noticed that in case of hashset the result is always the same:但我注意到,在 hashset 的情况下,结果总是相同的:

 @Test
void displaySets() {
    Set<String> hashSet = new HashSet<>();
    hashSet.add("J1");
    hashSet.add("J2");
    hashSet.add("J3");
    hashSet.add("J4");
    for(String el : hashSet) {
        System.out.println(el); // always the same order - J1, J2, J3, J4
    }

    System.out.println("----------------");

    Set<String> set = Set.of("J1", "J2", "J3", "J4");
    for(String el : set) {
        System.out.println(el); // random order
    }
}

Is there any meaningful explanation ?有什么有意义的解释吗?

Set.of iteration purposely shuffled Set.of迭代故意洗牌

Actually, the iteration behavior of Set.of was altered in more recent versions of the OpenJDK implementation to change the order arbitrarily on each usage.实际上,在较新版本的 OpenJDK 实现中, Set.of的迭代行为已更改,以在每次使用时任意更改顺序。 Earlier versions did maintain a fixed iteration order across successive usages.早期版本确实在连续使用中保持固定的迭代顺序。

Set < String > set = Set.of( "J1" , "J2" , "J3" , "J4" );
System.out.println( set );

Example running that code multiple times:多次运行该代码的示例:

[J2, J1, J4, J3] [J2、J1、J4、J3]

[J1, J2, J3, J4] [J1、J2、J3、J4]

[J2, J1, J4, J3] [J2、J1、J4、J3]

This new arbitrarily-changing-order behavior is intended to train programmers not to rely on any particular order.这种新的任意改变顺序的行为旨在训练程序员不要依赖任何特定的顺序。 This new behavior reinforces what the Javadoc states: Expect no particular order.这种新行为强化了 Javadoc 所说的内容:没有特定的顺序。

So why wasn't the behavior of the HashSet class' iteration order also changed to a shuffling behavior?那么为什么HashSet类的迭代顺序的行为没有也改变为改组行为呢? I can imagine two reasons:我可以想象两个原因:

  • HashSet is much older, arriving in Java 2. Decades of software has been written using that class. HashSet更老,出现在 Java 2 中。使用该类编写了数十年的软件。 Presumably some of that code incorrectly expects a certain ordering.据推测,其中一些代码错误地期望某种顺序。 Changing that behavior unnecessarily now would be obnoxious.现在不必要地改变这种行为会令人讨厌。 In contrast, Set.of is relatively quite new and unused at the time of its change in behavior.相比之下, Set.of在其行为发生变化时相对较新且未使用。
  • Set.of may well be changed as Java evolves, to choose among several implementations. Set.of可能会随着 Java 的发展而改变,以在多种实现中进行选择。 The choice of implementation can depend on the kinds of objects being collected, and can depend on compile-time or runtime conditions.实现的选择可以取决于被收集的对象的种类,并且可以取决于编译时或运行时条件。 For example, if collecting enum objects with Set.of , the EnumSet class could be chosen as the underlying implementation returned.例如,如果使用Set.of收集枚举对象,则可以选择EnumSet类作为返回的底层实现。 These various underlying implementations might vary in their iteration-order behavior.这些各种底层实现的迭代顺序行为可能会有所不同。 So it makes sense to emphasize to programmers now not to rely on the behavior of today's implementations when tomorrow may well bring other implementations.因此,当明天很可能会带来其他实现时,现在向程序员强调不要依赖今天实现的行为是有道理的。

Notice that I carefully avoided using the word “randomized”, instead choosing to use “shuffled”.请注意,我小心地避免使用“随机化”一词,而是选择使用“洗牌”。 This is important, because you should not even depend on the iteration order of your Set being truly randomized.这很重要,因为您甚至不应该依赖真正随机化的Set的迭代顺序。 Always consider any Set object's iteration to be arbitrary (and subject to change).始终认为任何Set对象的迭代都是任意的(并且可能会发生变化)。

Predictable iteration order with NavigableSet / SortedSet NavigableSet / SortedSet可预测迭代顺序

If you want a certain iteration order, use a NavigableSet / SortedSet implementation such as TreeSet or ConcurrentSkipListSet .如果您想要特定的迭代顺序,请使用NavigableSet / SortedSet实现,例如TreeSetConcurrentSkipListSet

NavigableSet < String > navSet = new TreeSet <>();
navSet.add( "J3" );
navSet.add( "J1" );
navSet.add( "J4" );
navSet.add( "J2" );

System.out.println( "navSet = " + navSet.toString() );

When run, we see those String object sorted alphabetically.运行时,我们看到这些String对象按字母顺序排列。 As we added each of our String objects to the set, the TreeSet class used their natural ordering , that is, used their implementation of compareTo defined in the Comparable interface.当我们将每个String对象添加到集合中时, TreeSet类使用它们的自然顺序,即使用它们在Comparable接口中定义的compareTo的实现。

navSet = [J1, J2, J3, J4] navSet = [J1, J2, J3, J4]

By the way, if you want the best of both, the sorting of TreeSet but also the convenient brief syntax of Set.of , you can combine them.顺便说一句,如果你想要两者的最好, TreeSet的排序以及Set.of方便的简短语法,你可以将它们结合起来。 The constructor of Set implementations such as TreeSet allow you to pass an existing collection. Set实现(例如TreeSet的构造函数允许您传递现有集合。

Set < String > set = new TreeSet <>( Set.of( "J3" , "J1" , "J4" , "J2" ) );

If you want to specify the sorting order rather than natural order, pass a Comparator to the NavigableSet constructor.如果要指定排序顺序而不是自然顺序,请将Comparator传递给NavigableSet构造函数。 See the following example, where we use the Java 16 feature of records for brevity.请参见以下示例,为了简洁起见,我们在其中使用了 Java 16记录功能。 Our Comparator implementation is based on the getter method for the date hired, so we get a list of people by seniority.我们的Comparator实现基于雇用日期的 getter 方法,因此我们按资历获得人员列表。 This works because the LocalDate class implements Comparable , and so has a compareTo method.这是有效的,因为LocalDate类实现了Comparable ,因此有一个compareTo方法。

record Person(String name , LocalDate whenHired) {}
Set < Person > navSet = new TreeSet <>(
        Comparator.comparing( Person :: whenHired )
);
navSet.addAll(
        Set.of(
                new Person( "Alice" , LocalDate.of( 2019 , Month.JANUARY , 23 ) ) ,
                new Person( "Bob" , LocalDate.of( 2021 , Month.JUNE , 27 ) ) ,
                new Person( "Carol" , LocalDate.of( 2014 , Month.NOVEMBER , 11 ) )
        )
);

When run:运行时:

navSet.toString() ➠ [Person[name=Carol, whenHired=2014-11-11], Person[name=Alice, whenHired=2019-01-23], Person[name=Bob, whenHired=2021-06-27]] navSet.toString() ➠ [Person[name=Carol,whenHired=2014-11-11],Person[name=Alice,whenHired=2019-01-23],Person[name=Bob,whenHired=2021-06-27 ]]

"Do not guarantee the order of the elements" (the actual wording in the documentation is "It makes no guarantees as to the iteration order of the set; in particular, it does not guarantee that the order will remain constant over time.") doesn't mean "the order is random". “不保证元素的顺序”( 文档中的实际措辞是“它不保证集合的迭代顺序;特别是,它不保证顺序会随着时间的推移保持不变。”)并不意味着“顺序是随机的”。 It means "don't rely on the ordering".这意味着“不要依赖顺序”。

And as a corollary "don't assume the elements won't be in some order".并且作为推论“不要假设元素不会某种顺序排列”。

If you need a Set with a predictable iteration order use a LinkedHashSet .如果您需要具有可预测迭代顺序的Set ,请使用LinkedHashSet

If you want it in (pseudo)random order, convert it to a List and shuffle it, like this:如果您想要(伪)随机顺序,请将其转换为List并对其进行洗牌,如下所示:

Set<String> hashSet = new HashSet<>();
hashSet.add("J1");
hashSet.add("J2");
hashSet.add("J3");
hashSet.add("J4");
List<String> toList = new ArrayList<>(hashSet);
Collections.shuffle(toList);

They might be displayed in a consistent order on your system under basic circumstances, but not another system, or not under complex circumstances.在基本情况下,它们可能会以一致的顺序在您的系统上显示,但在其他系统上或在复杂情况下不会显示。

So it is best to respect the warnings that some behaviour is not guaranteed.因此,最好尊重某些行为无法保证的警告。

Why are the items in the HashSet always displayed in the same order?为什么HashSet中的项总是以相同的顺序显示?

If you mean the same hashSet over and over again it is because the hashCode is used to build the set the same way each time for the same set of values.如果您一遍hashSet遍地使用相同的hashSet ,那是因为hashCode每次都用于为相同的一组值以相同的方式构建该集合。 But no particular order is guaranteed (which is one of the reasons no get() method with an index is provided - since the location is unpredictable it would be of questionable use);但是没有保证特定的顺序(这是没有提供带索引的get()方法的原因之一——因为位置是不可预测的,所以使用会有问题);

Internally, there are default capacity and loadfactor values (which are explained in the JavaDoc for HashSet ) which can affect the ultimate order of a given HashSet .在内部,有默认的capacityloadfactor值(在HashSet的 JavaDoc 中进行了解释),它们会影响给定HashSet的最终顺序。 But these can be passed as arguments to the HashSet constructor.但是这些可以作为参数传递给HashSet构造函数。 An example follows:一个例子如下:

Set<Integer> set = new HashSet<>();
set.addAll(Set.of(1,3,4,2,10,9,28,5,6));
System.out.println(set);
System.out.println(set);
System.out.println(set);

Set<Integer> set2 = new HashSet<>(2, 3f);
set2.addAll(set);
System.out.println(set2);

Prints印刷

[1, 2, 3, 4, 5, 6, 9, 10, 28]
[1, 2, 3, 4, 5, 6, 9, 10, 28]
[1, 2, 3, 4, 5, 6, 9, 10, 28]
[4, 28, 1, 5, 9, 2, 6, 10, 3]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM