HashMap 中的双倍

问题描述 投票:0回答:8

我正在考虑使用 Double 作为 HashMap 的键,但我知道浮点比较是不安全的,这让我开始思考。 Double 类上的 equals 方法也不安全吗?如果是,则意味着 hashCode 方法也可能不正确。这意味着使用 Double 作为 HashMap 的键将导致不可预测的行为。

有人可以证实我的猜测吗?

java hashcode
8个回答
18
投票

简短回答:不要这样做

长答案: 以下是计算密钥的方式:

实际的键将是一个

java.lang.Double
对象,因为键必须是对象。这是它的
hashCode()
方法:

public int hashCode() {
  long bits = doubleToLongBits(value);
  return (int)(bits ^ (bits >>> 32));
}

doubleToLongBits()
方法基本上采用8个字节并表示它们一样长。因此,这意味着 double 计算中的微小变化可能意味着很大,并且您将有关键的失误。

如果您可以满足点后给定数量的点数 - 乘以 10^(点后的位数)并转换为 int (例如 - 对于 2 位数字乘以 100)。

会安全很多。


8
投票

我认为你是对的。尽管双精度数的哈希值是整数,但双精度数可能会弄乱哈希值。这就是为什么,正如 Josh Bloch 在《Effective Java》中提到的,当您使用 double 作为哈希函数的输入时,您应该使用 doubleToLongBits()。类似地,对浮点数使用 floatToIntBits。

特别是,要使用双精度作为散列,按照 Josh Bloch 的食谱,您可以这样做:

public int hashCode() {
  int result = 17;
  long temp = Double.doubleToLongBits(the_double_field);
  result = 37 * result + ((int) (temp ^ (temp >>> 32)));
  return result;
}

这来自《Effective Java》的第 8 条,“当你重写 equals 时,总是重写 hashCode”。它可以在本书章节的 pdf 中找到

希望这有帮助。


7
投票

这取决于您如何使用它。

如果您只满足于能够根据“完全相同的位模式”(或“潜在的”等效位模式,例如 +/- 0 和各种 NaN)找到值,那么可能没问题。 特别是,所有 NaN 最终都会被视为相等,但 +0 和 -0 将被视为不同。来自 Double.equals 的文档:

请注意,在大多数情况下,对于两个
  Double、d1 和 d2 类的实例,
  d1.equals(d2) 的值为 true,如果
  并且只有当

d1.doubleValue() == d2.doubleValue() 也有值 真的。然而,有两个 例外:

如果d1和d2都代表 Double.NaN,然后是equals方法 返回 true,即使 Double.NaN==Double.NaN 有值 假的。

如果d1代表+0.0,而d2 代表-0.0,反之亦然, equal test 的值为 false,即使 尽管 +0.0==-0.0 的值为 true。
  • 这个定义允许哈希表 正常操作。
您很可能对“非常接近关键的数字”感兴趣,这使得它的可行性大大降低。特别是,如果您打算执行一组计算来获取密钥一次,然后执行一组不同的计算来第二次获取密钥,那么您就会遇到问题。

问题不在于哈希码,而在于双精度数的精度。这会导致一些奇怪的结果。示例:


5
投票

计算出的值(键)是“433.29999999999995”,它不等于433.3,因此您在Map中找不到该条目(哈希码可能也不同,但这不是主要问题)。

使用 
map.get(key)

而不是

map.get(433.3)

将找到该条目。

    
简短回答:可能行不通。


3
投票

更长的答案:哈希码不是问题,而是浮点上相等比较的本质。正如 Nalandial 和他的帖子中的评论者所指出的那样,最终任何与哈希表的匹配仍然会使用 equals 来选择正确的值。

所以问题是,你的双打生成方式是否让你知道 equals 真的意味着 equals?如果您读取或计算一个值,将其存储在哈希表中,然后使用完全相同的计算读取或计算该值,则 Double.equals 将起作用。但除此之外它是不可靠的:1.2 + 2.3 不一定等于 3.5,它可能等于 3.4999995 或其他。 (不是一个真实的例子,我只是编造的,但这就是发生的事情。)您可以合理可靠地比较浮点数和双精度数的小于或大于,但不能比较等于。

也许

BigDecimal

2
投票

使用双精度数的哈希值,而不是双精度数本身。


0
投票
编辑:

谢谢,乔恩,我实际上不知道。

我对此不确定(您应该只查看 Double 对象的源代码),但我认为浮点比较的任何问题都会为您解决。

这取决于您如何存储和访问地图,是的,相似的值最终可能会略有不同,因此不会散列到相同的值。


0
投票

但是一切都很好

map.put(1.1+2.3, value);
...
map.get(5.0 - 1.6);

会有危险

	

© www.soinside.com 2019 - 2024. All rights reserved.