Java 遍历 HashSet 为什么输出是有序的

1.问题

今天学习数据结构哈希表,知道了哈希表的牺牲了顺序性,从而保证了效率。然后我想到了java中的 hashset和Treeset,我记得以前学习set的时候,老师说set元素是有序的,有就是说,hashset 和 treeset都是有序的。可是现在在学习数据结构的时候发现hashset 是无序的, treeset 是有序的,那这就和前面所说的set是有序的互相矛盾了,所以让来验证一下,如下:

使用的是 java 8

HashSet<Integer> set = new HashSet<>();
set.add(3);
set.add(1);
set.add(2);
System.out.println("HashSet : " + set);

TreeSet<Integer> treeSet = new TreeSet<>();
treeSet.add(3);
treeSet.add(1);
treeSet.add(2);
System.out.println("TreeSet : " + treeSet);
HashSet : [1, 2, 3]
TreeSet : [1, 2, 3]

从结果得知,hashset和 treeset都是有序的输出元素了。

这样的结果其实和我前面所讲的哈希表牺牲了有序这一命题所矛盾了,可是哈希表确实是牺牲了有序性,那么问题在哪里呢?

2.hashset实现原理

仔细回想,不难发现hashset底层使用hashmap来实现的,set的元素存放在map的key上面。

对于hashmap来讲,它的主体是一个Entry数组,Entry又是一个链表,因此hashmap的存储过程如下:

  1. 计算key的hash值;
  2. 把得到的hash值作为数据下标去存储到Entry数组。在这里可能出现不一样的key的得到的hash值相等,如果相等,就把新的value存到当前下标下保存的Entry里面(Entry是链表)。

由此可见,hashset实际上存储元素的时候进行了获取hash的操作,而对于测试用例中的 3,2,1来说,他们的hash值就是自身,所以根据这个hash值得到数组的下标存储元素后,表现出来就是有序的。

3.treeset实现原理

treeset的底层使用treemap实现的,set的元素存放在map的key上面,

treemap又是用红黑树进行实现,实际上红黑树是一种自平衡二叉查找树,它满足元素的有序性,因此treeset的元素是具有有序性的。

4.结论
针对以上分析,再写一次测试代码:

HashSet<Integer> set = new HashSet<>();
set.add(3);
set.add(1);
set.add(2);
System.out.println("HashSet : " + set);

TreeSet<Integer> treeSet = new TreeSet<>((m, n) -> n - m);
treeSet.add(3);
treeSet.add(1);
treeSet.add(2);
System.out.println("TreeSet : " + treeSet);
HashSet : [1, 2, 3]
TreeSet : [3, 2, 1]

这里可以看出,Treeset按照我们实现的排序方法按照顺序的打印出了结果。

这里有人可能会问,你的hashset没有添加和treeset相同的排序代码,肯定结果不一样啊;这是因为hashset不支持添加比较器的构造方法,这也说明hashset所存储的元素不需要具备可比较性!

posted @ 2022-12-30 16:06  Fogram  阅读(305)  评论(0编辑  收藏  举报