【转】java8中谨慎使用实数作为HashMap的key!
java8中谨慎使用实数作为HashMap的key!
java8中一个hashCode()函数引发的血案
java8中一个hashCode()函数引发的血案
1.起因
2.实数的hashCode()
3.总结
1.起因
让我关注到这一点的起因是一道题:牛客网上的max-points-on-a-line (如果链接打不开可以直接搜索题目哦)
题目是这么描述的:
Given n points on a 2D plane, find the maximum number of points that lie on the same straight line.
大意就是给我一些点的X,Y坐标,找到过这些点最多的直线,输出这条线上的点数量
于是我就敲出了以下的代码:
import java.util.HashMap;
import java.util.Map;
//class Point {
// int x;
// int y;
// Point(int a, int b) { x = a; y = b; }
//}
public class Solution {
public int maxPoints(Point[] points) {
if (points.length <= 2) {
return points.length;
}
int max = 2;
for (int i = 0; i < points.length - 1; i++) {
Map<Float, Integer> map = new HashMap<>(16);
// 记录垂直点数; 当前和Points[i]在一条线上的最大点数; 和Points[i]垂直的点数
int ver = 0, cur, dup = 0;
for (int j = i + 1; j < points.length; j++) {
if (points[j].x == points[i].x) {
if (points[j].y != points[i].y) {
ver++;
} else {
dup++;
}
} else {
float d = (float)((points[j].y - points[i].y) / (double) (points[j].x - points[i].x));
map.put(d, map.get(d) == null ? 1 : map.get(d) + 1);
}
}
cur = ver;
for (int v : map.values()) {
cur = Math.max(v, cur);
}
max = Math.max(max, cur + dup + 1);
}
return max;
}
}
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
这段代码在天真的我看来是没啥问题的,可就是没办法过,经过长久的排查错误,我写了以下代码加在上面的代码里运行
public static void main(String[] args) {
int[][] vals = {{2,3},{3,3},{-5,3}};
Point[] points = new Point[3];
for (int i=0; i<vals.length; i++){
points[i] = new Point(vals[i][0], vals[i][1]);
}
Solution solution = new Solution();
System.out.println(solution.maxPoints(points));
}
1
2
3
4
5
6
7
8
9
10
11
12
它输出的,竟然是2
也就是说,它认为(3-3) / (3-2) 和 (3-3) / (-5-2) 不同? 什么鬼…
经过debug,发现上述结果分别是0.0和-0.0
0.0 难道不等于 -0.0 ?
这时我心里已经一阵卧槽了,不过我还是写了验证代码:
System.out.println(0.0 == -0.0);
1
结果是True,没问题啊,我凌乱了……
一定是java底层代码错了! 我没错……
又是一阵debug,我找到了这条语句:
map.put(d, map.get(d) == null ? 1 : map.get(d) + 1);
1
我觉得map.get()很有问题, 它的源代码是这样的:
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
1
2
3
4
唔,先获得hash()是吧,那我找到了它的hash函数:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
1
2
3
4
再来,这里是要比较h 和key的hashCode是吧,那我们去看hashCode()函数
public native int hashCode();
1
这是一个本地方法,看不到源码了,唔,,那我就使用它看看吧,测试一下不就好了吗,我写了以下的测试代码:
public static void main(String[] args) {
System.out.println(0.0 == -0.0);
System.out.println(new Float(0.0).hashCode() ==
new Float(-0.0).hashCode());
}
1
2
3
4
5
结果竟然是True和False !!!
这个源头终于找到了, 0.0 和 -0.0 的hashCode值是不同的 !
经过一番修改,我通过了这道题(其实精度也会有问题,应该使用BigDecimal的,不过牛客网要求没那么高。后来我想了想只有把直线方程写成Ax+By+C=0的形式才能完全避免精度问题)
接下来,探讨下实数的hashCode()函数是个啥情况:
2.实数的hashCode()
在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。
如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。
如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数。
——《effective java》
那么我们来看看,0.0和-0.0调用equals方法是否相等:
System.out.println(new Float(0.0).equals(0.0f));
System.out.println(new Float(0.0).equals((float) -0.0));
1
2
输出是True 和 False
好吧,二者调用equals() 方法不相等,看来是满足了书里说的逻辑的
那我们看看Float底层equals函数咋写的:
public boolean equals(Object obj) {
return (obj instanceof Float)
&& (floatToIntBits(((Float)obj).value) ==
floatToIntBits(value));
}
1
2
3
4
5
哦,原来是把Float转换成Bits的时候发生了点奇妙的事,于是我找到了一切的源头:
/**
* Returns a representation of the specified floating-point value
* according to the IEEE 754 floating-point "single format" bit
* layout.
*
* <p>Bit 31 (the bit that is selected by the mask
* {@code 0x80000000}) represents the sign of the floating-point
* number.
* Bits 30-23 (the bits that are selected by the mask
* {@code 0x7f800000}) represent the exponent.
* Bits 22-0 (the bits that are selected by the mask
* {@code 0x007fffff}) represent the significand (sometimes called
* the mantissa) of the floating-point number.
*
* <p>If the argument is positive infinity, the result is
* {@code 0x7f800000}.
*
* <p>If the argument is negative infinity, the result is
* {@code 0xff800000}.
*
* <p>If the argument is NaN, the result is {@code 0x7fc00000}.
*
* <p>In all cases, the result is an integer that, when given to the
* {@link #intBitsToFloat(int)} method, will produce a floating-point
* value the same as the argument to {@code floatToIntBits}
* (except all NaN values are collapsed to a single
* "canonical" NaN value).
*
* @param value a floating-point number.
* @return the bits that represent the floating-point number.
*/
public static int floatToIntBits(float value) {
int result = floatToRawIntBits(value);
// Check for NaN based on values of bit fields, maximum
// exponent and nonzero significand.
if (((result & FloatConsts.EXP_BIT_MASK) ==
FloatConsts.EXP_BIT_MASK) &&
(result & FloatConsts.SIGNIF_BIT_MASK) != 0)
result = 0x7fc00000;
return result;
}
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
30
31
32
33
34
35
36
37
38
39
40
41
这文档挺长的,也查了其它资料,看了半天终于搞懂了
就是说Java浮点数的语义一般遵循IEEE 754二进制浮点算术标准。IEEE 754标准提供了浮点无穷,负无穷,负零和NaN(非数字)的定义。在使用Java过程中,一些特殊的浮点数通常会让大家很迷惑
详情请查看这篇文章: java中特殊且复杂的浮点数
里面提到,当浮点运算产生一个非常接近0的负浮点数时,会产生“-0.0”,而这个浮点数不能正常表示
我们可以输出一波0.0和-0.0的数据:
System.out.println(Float.floatToIntBits((float) 0.0));
System.out.println(Float.floatToIntBits((float) -0.0));
System.out.println(Float.floatToRawIntBits(0.0f));
System.out.println(Float.floatToRawIntBits((float)-0.0));
1
2
3
4
结果:
0
-2147483648
0
-2147483648
就是说,存储-0.0, 竟然用的是0x80000000
这也是我们熟悉的Integer.MIN_VALUE
3.总结
java中浮点数的表示比较复杂,特别是牵涉到-0.0, NaN, 正负无穷这种,所以不适宜用来作为Map的key, 因为可能跟我们预想的不一致
---------------------
作者:0x落尘
来源:CSDN
原文:https://blog.csdn.net/qq_30219017/article/details/79689492
版权声明:本文为博主原创文章,转载请附上博文链接!
【转】java8中谨慎使用实数作为HashMap的key!的更多相关文章
- 初探Java8中的HashMap(转)
HashMap是我们最常用的集合之一,同时Java8也提升了HashMap的性能.本着学习的原则,在这探讨一下HashMap. 原理 简单讲解下HashMap的原理:HashMap基于Hash算法,我 ...
- Java8中的HashMap分析
本篇文章是网上多篇文章的精华的总结,结合自己看源代码的一些感悟,其中线程安全性和性能测试部分并未做实践测试,直接是“拿来”网上的博客的. 哈希表概述 哈希表本质上一个数组,数组中每一个元素称为一个箱子 ...
- java7,java8 中HashMap和ConcurrentHashMap简介
一:Java7 中的HashMap 结构: HashMap 里面是一个数组,然后数组中每个元素是一个单向链表.链表中每个元素称为一个Entry 实例,Entry 包含四个属性:key, value, ...
- Java7与Java8中的HashMap和ConcurrentHashMap知识点总结
JAVA7 Java7的ConcurrentHashMap里有多把锁,每一把锁用于其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率呢.这 ...
- java8中map的meger方法的使用
java8中map有一个merge方法使用示例: /** * 打印出包含号码集的label的集合 * * @param args */ public static void main(String[] ...
- Java8 中 ConcurrentHashMap工作原理的要点分析
简介: 本文主要介绍Java8中的并发容器ConcurrentHashMap的工作原理,和其它文章不同的是,本文重点分析了不同线程的各类并发操作如get,put,remove之间是如何同步的,以及这些 ...
- Java7 和 Java8 中的 ConcurrentHashMap 原理解析
Java7 中 ConcurrentHashMap ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并发操作,所以要复杂一些. 整个 ConcurrentHash ...
- Java8中聚合操作collect、reduce方法详解
Stream的基本概念 Stream和集合的区别: Stream不会自己存储元素.元素储存在底层集合或者根据需要产生.Stream操作符不会改变源对象.相反,它会返回一个持有结果的新的Stream.3 ...
- 关于java8中的String
String 对象的不可变性 java8中的String只有2个属性value和hash,相关代码如下: /** The value is used for character storage. */ ...
随机推荐
- 2 Model层 - 模型查询
1.简介 查询集表示从数据库中获取的对象集合 查询集可以含有零个.一个或多个过滤器 过滤器基于所给的参数限制查询的结果 从Sql的角度,查询集和select语句等价,过滤器像where和limit子句 ...
- cf965c Greedy Arkady
呸,大傻逼题,我更傻逼ref #include <iostream> using namespace std; typedef long long ll; ll n, k, m, d, a ...
- App 设计技巧
http://www.360doc.com/content/14/1120/18/21412_426730809.shtml http://veryui.diandian.com/post/2013- ...
- 【Maximum Subarray 】cpp
题目: Find the contiguous subarray within an array (containing at least one number) which has the larg ...
- Selenium中自动输入10位随机数字的方法
有时候项目中需要输入快递号,对于已输入过的快递单号则不能再次输入,这种问题怎么解决呢,可以看下这个方法 public static final String ALLCHAR = "01234 ...
- Halcon18 Mac os 下载
Halcon18 Mac os 下载地址:http://www.211xun.com/download_page_15.html HALCON 18 是一套机器视觉图像处理库,由一千多个算子以及底层的 ...
- android studio首个项目碰到的一些问题
一开始,我用的是android studio3.1,碰到Get “.gradle”的问题.解决方法是https://www.zhihu.com/question/37810416.在project中的 ...
- [oldboy-django][1初识django]阻止默认事件发生 + ajax + 模态编辑对话框
阻止默认事件发生 a 阻止a标签默认事件发生方法 <a href="http://www.baidu.com" onclick="modalEdit();" ...
- 【转】UGUI文本字体颜色和大小等代码修改
http://blog.csdn.net/alayeshi/article/details/51842195 由于项目需求用UGUI在同个文本中显示出颜色和大小都不同的文字,我上网找个很久并没有见到相 ...
- MySql数据库 - 1.安装
下载: 官网:www.mysql.com 打开官网之后依次点击:DOWNLOADS - Windows - MySql Installer MySql Installer 包含的功能,使用C#连接数据 ...