关于老师视频中Set集合输出无序,而自己重复码了老师的代码却输出有序的问题

来源:4-11 学生选课---通过 Set 集合管理课程

未来的先驱者

2016-03-23 18:27

转自知乎

看到《Thinking in Java》中有这么一段代码,书中给出的Output是无序的,可是我实际运行出来是有序的,就是从0递增到29,这是为什么呢?
public class SetOfInteger {
   public static void main(String[] args){
       Random rand=new Random(47);
       Set<Integer> intset=new HashSet<Integer>();
       for (int i=0;i<10000;i++){
           intset.add(rand.nextInt(30));
       }
       Iterator<Integer> iterator=intset.iterator();
       while (iterator.hasNext()){
           System.out.print(iterator.next()+" ");
       }
   }
}


首先 @赵劼 大大的答案就是正解了。“不保证有序”和“保证无序”不等价,HashSet的iterator是前者而不是后者,所以在一次运行中看到有序的结果也是正常的,但不能依赖这个有序行为。
况且HashSet并不关心key的“排序”,就算其iterator“有序”通常也是说“按元素插入顺序”(LinkedHashSet就支持插入顺序遍历)。题主在此看到的所谓“有序”纯粹是个巧合。

然后我复制粘贴了题主的代码运行了一次:

$ java SetOfInteger
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 16 19 18 21 20 23 22 25 24 27 26 29 28
$ java -version
java version "1.7.0-internal-zing_99.99.99.99.dev"
Zing Runtime Environment for Java Applications (build 1.7.0-internal-zing_99.99.99.99.dev-b65)
Zing 64-Bit Tiered VM (build 1.7.0-zing_99.99.99.99.dev-b870-product-azlinuxM-X86_64, mixed mode)

(Zing JDK7的开发版)
就不是有序的嘛。同样在Oracle JDK7u51上也是如此:

$ java SetOfInteger
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 16 19 18 21 20 23 22 25 24 27 26 29 28
$ java -version
java version "1.7.0_51"
Java(TM) SE Runtime Environment (build 1.7.0_51-b13)
Java HotSpot(TM) 64-Bit Server VM (build 24.51-b03, mixed mode)


换到Zing JDK8:

$ java SetOfInteger
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
$ java -version
java version "1.8.0-internal-zing_99.99.99.99.dev"
Zing Runtime Environment for Java Applications (build 1.8.0-internal-zing_99.99.99.99.dev-b65)
Zing 64-Bit Tiered VM (build 1.8.0-zing_99.99.99.99.dev-b870-product-azlinuxM-X86_64, mixed mode)

再换到Oracle JDK8u25:

$ java SetOfInteger
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
$ java -version
java version "1.8.0_25"
Java(TM) SE Runtime Environment (build 1.8.0_25-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)

就看到了题主说的有序行为。

JDK8的HashSet实现变了,导致元素插入的位置发生了变化;iterator自身实现的顺序倒没变,还是按照内部插入的位置顺序来遍历,于是题主就看到了JDK7和JDK8的结果不一样。具体来说,是JDK7与JDK8的java.util.HashMap的hash算法以及HashMap的数据布局发生了变化。

题主插入HashSet的是Integer,其hashCode()实现就返回int值本身。所以在对象hashCode这一步引入了巧合的“按大小排序”。
然后HashMap.hash(Object)获取了对象的hashCode()之后会尝试进一步混淆。
JDK8版java.util.HashMap内的hash算法比JDK7版的混淆程度低;在[0, 2^32-1]范围内经过HashMap.hash()之后还是得到自己。题主的例子正好落入这个范围内。外加load factor正好在此例中让这个HashMap没有hash冲突,这就导致例中元素正好按大小顺序插入在HashMap的开放式哈希表里。
根据它的实现特征,把题主的例子稍微修改一下的话:

$ cat SetOfInteger.java
import java.util.*;

public class SetOfInteger {
   public static void main(String[] args){
       Random rand=new Random(47);
       Set<Integer> intset=new HashSet<Integer>();
       for (int i=0;i<10000;i++){
           intset.add(rand.nextInt(30) + (1 << 16));
       }
       Iterator<Integer> iterator=intset.iterator();
       while (iterator.hasNext()){
           System.out.print((iterator.next() - (1 << 16)) +" ");
       }
   }
}
$ java SetOfInteger
1 0 3 2 5 4 7 6 9 8 11 10 13 12 15 14 17 16 19 18 21 20 23 22 25 24 27 26 29 28
$ java -version
java version "1.8.0_25"
Java(TM) SE Runtime Environment (build 1.8.0_25-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)

就可以看到顺序不一样了。修改的内容就是把插入的数字先加上2的16次方,然后拿出来之后再减去2的16次方,而已 ^_^



作者:RednaxelaFX
链接:https://www.zhihu.com/question/28414001/answer/40733996
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


这里我看的不是很明白,他说的意思是JDK1.8版之后HashSet实现类的方法改变了,导致能够产生无序的最小范围扩大了吗?还是其他什么?

至于原HashSet实现类具体是怎么样我还没学不知道是什么样子,看样子好像是原来是没有这个范围的?不管是输入多少输出之后都是无序的,像视频里一样?恳请各位大大帮忙解答。


写回答 关注

1回答

  • qq_琉璃苣_1
    2016-03-23 20:51:10

    他的意思应该是在[0, 2^32-1]范围内经过HashMap.hash()之后还是得到自己,就是说在这个范围内默认是按哈希表里的顺序也就是从小到大排列        在讲解中不是说先加2的16次方然后再减去2的16次方    也就是只要跳出那个范围就是正常无序的了    (我的一点见解,不保证一定喔)

    未来的先驱者

    额,我想知道的是我们输出了有序的Set集合是不是也是因为他说的这个原因呢?

    2016-03-23 21:25:26

    共 1 条回复 >

Java入门第三季

Java中你必须懂得常用技能,不容错过的精彩,快来加入吧

409792 学习 · 4340 问题

查看课程

相似问题