我在Java中实现了HashSet,这对我来说是一种莫名其妙的行为。我像这样实现了HashSet,并用列表的值填充了它。
HashSet<Integer> set = new HashSet<Integer>(list);
我首先使用一个列表,该列表包含从0到9的数字,以填充HashSet:
示例:{1,0,5,9,6,7,3,1,3,6,1,5,1,3,4,9,9,7}
输出:[0, 1, 3, 4, 5, 6, 7, 9]
由于HashSet通常以升序排序的方式返回值,直到现在一切正常。但是,一旦我开始使用包含较大值的列表,它便开始以一种怪异的方式返回值:
示例:{67,1,122,19,456,42,144,42,3,34,5,5,42}
输出:[1, 34, 67, 3, 5, 456, 42, 144, 19, 122]
我读到一些有关此内容取决于此处的内部哈希算法的信息:Java HashSet shows list in weird order, always starting with 3,但由于我使用完全相同的HashSet只是具有不同的值,因此更加令人困惑。
能否请我解释为什么会这样?
答案 0 :(得分:6)
HashSet
显然没有提供可预测的顺序。
很容易发生,在第一种情况下,哈希码(对于Integer
来说,它只是整数值)都小于存储桶数,这意味着如果所有值都小于默认的存储桶数(16),它们将同时处于顺序状态。
答案 1 :(得分:4)
由于HashSet通常按升序返回值
简短的答案,整数0到15,一个HashSet恰好是自然顺序。但是,由于这不是文档功能,也不是您应依赖的功能,因此将来可能会改变。
长答案:
这仅由于键的哈希方式而发生。 Integer.hashCode()实现为
public int hashCode() {
return Integer.hashCode(value);
}
调用
public static int hashCode(int value) {
return value;
}
例如,值0到15的哈希值仅为0到15。
HashSet依次获取哈希并对其进行搅动,以使高位保持有效。
// from HashMap
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
但是,正如您所看到的,在OpenJDK Java 11中,值0到65535保持不变。
最后,保留低位以确定它们在HashSet数组中的存储位置。
// from HashMap.putVal
i = (n - 1) & hash
n
是始终为2的幂的容量。由于默认容量为16,因此0到15的值保持不变。
此索引i
用于确定条目在基础数组中的什么位置。
当您遍历HashSet或HashMap时,它仅从数组的第一个索引开始,然后以索引顺序(也恰好是键的自然顺序)进行迭代。
答案 2 :(得分:3)
HashSet
是无序的Collection
。它不保持元素插入的顺序。因此它不会总是以升序排序的值