我的Set
有时会被排序,有时则不排序。
以下是示例:
public class SetOfInteger {
public static void main(String[] args) {
Random rand = new Random(47);
Set<Integer> intset = new HashSet<>();
for (int i = 0; i < 10; i++) {
int j = rand.nextInt(30);
System.out.print(j + " ");
intset.add(j);
}
System.out.println();
System.out.println(intset);
}
}
结果显示set
未排序。
8 5 13 11 1 29 28 20 12 7
[1, 20, 5, 7, 8, 11, 12, 29, 28, 13]
当我在for语句中将终止表达式更改为i < 20
时,结果显示set
已排序。
8 5 13 11 1 29 28 20 12 7 18 18 21 19 29 28 28 1 20 28
[1, 5, 7, 8, 11, 12, 13, 19, 18, 21, 20, 29, 28]
太奇怪了,是吗?我只是不知道如何解释它,我需要一些帮助,非常感谢你。
答案 0 :(得分:13)
HashSet不保证排序迭代,但在非常特定的情况下,其内部数据结构可能像bucket sort一样。
具体地说,对于[0,65535]范围内的整数键和大于最大键的表大小,存储密钥的桶的索引等于密钥本身,并且因为迭代器迭代按桶顺序,它按排序顺序发出元素。
答案 1 :(得分:6)
周围有一些很好的答案,但没有人试图解释在这种特殊情况下究竟发生了什么,所以我将限制我的答案,而不是添加另一个关于HashSet如何工作的解释。我认为这种理解是理所当然的。
default constructor of HashSet创建一个容量为16且加载因子为0.75的集合。这意味着有16个箱,当你插入16 * 0.75 = 12个独特元素时,这个容量会增加。
这就是为什么在第一种情况下,数字按其除以16的余数排序:集合以表格大小16开始,&#34;哈希&#34;通过x % 16
将每个元素添加到bin中。然后当有12个元素的时候,它会在桌面上进行重新演示(如果不清楚的话,请参阅Javier Martin的答案),可能会将表格增加到32个。(我只能找到有关它的信息)在the java 6 doc中增长,它表示桶的数量是&#34;大约&#34;加倍,无论这意味着什么。)这给了每个30以下的整数自己的bin,所以当set在每个bin上迭代时顺序,它按顺序迭代数字。如果您在64以下插入数字,您可能会发现在迭代出现排序之前需要插入32 * 0.75 = 24个元素。
另请注意,这种分配箱的方式是不保证行为。其他Java版本/实现中的HashSets可能会对对象进行更复杂的操作。 hashCode()
值不仅仅是取余数。 (正如评论中的ruakh和蓬松所指出的那样 - 谢谢!)
答案 2 :(得分:5)
您的问题指出,随着集合变大,项目顺序会更改。但是,您不能指望保留的订单。 Set
有一个保证:每种元素只有一种。还有其他Set
个对象可以提供进一步的保证,但简单的HashSet
不能保证顺序。
由于HashSet在内部的存储方式,您看到的重新排序只是内部重组。在一种非常简化的思维方式中,HashSet有一定数量的&#34; slot&#34;存储值通常是奇数,如果不是素数。来自getHashCode()
的哈希码用于将对象分配给插槽。当您有哈希代码冲突时,HashSet使用相等运算符equals()
来确定对象是否实际上是唯一的。
当您向HashSet
添加项目时,会发生以下几件事:
HashSet
需要调整自身大小
最重要的是,如果对象神奇地对自己进行排序,那么除非您使用TreeSet
对设置项执行排序顺序,否则这不是您可以依赖的实现。
答案 3 :(得分:3)
有趣的问题。 Set使用array of linked list
来存储其元素。 hashCode()
用于查找要存储在Set
中的对象的位置(间接)。
如果有两个对象需要存储在同一位置,则该对象存储在该位置的链表的下一个槽中。
数组的大小是动态的,并根据其中的对象数计算运行时间。它不确定,但我假设你看到你的数字排序,因为Set可能会增加大小。 hashCode()
取决于数值,因此将按顺序计算。随着底层数组的大小随着循环大小的增加而增加。不会发生冲突,输出也会排序。
但我仍然想强调,以便我的答案不会导致任何误解。 HashSet
不保证元素的任何排序
答案 4 :(得分:3)
没有定义HashSet的迭代顺序,唯一的保证是它是一致的:迭代未修改的HashSet会产生相同的序列。
在内部,正如评论者所说,该类使用每个元素的hashCode方法将存储在一定数量的bin中。因此,例如,如果它使用20个bin,则可以将o.hashCode() % 20
作为bin索引。每个bin可以在列表中包含多个项目,然后通过equals方法进行区分。因此,即使Integer的散列是其int值,顺序也不必是自然整数排序。
此外,该组在插入和移除元素时监视其负载因子;考虑到免费垃圾箱的比例,最大垃圾箱列表大小,每个垃圾箱的平均物品数量等等。当它认为合适时,它执行rehash,这意味着更改用于存储元素的bin的数量,因此它们的bin索引会发生变化,因为o.hashCode() % n
中的n会发生变化。 每个元素都被“重新洗牌”到新的位置(这是一项代价高昂的操作),因此解释了添加更多元素后您看到的不同顺序。
答案 5 :(得分:1)
您必须手动对其进行排序,因为无法保证将对哈希集进行排序。如果你想要你也可以使用TreeSet来提供你想要的功能,但是如果你想使用HashSet,请试试这个:
Set intset = new HashSet();
List sortedIntList = new ArrayList(intset);
Collections.sort(sortedIntList);