【转】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. */ ...
随机推荐
- LINUX下实现按秒执行计划任务
由于linux最小单位为分,但是很多需求上需要按秒执行,如30秒请求一个URL地址之类的,思路很简单就是修改计划任务脚本用循环控制,代码如下: #!/bin/bash PATH=/bin:/sbin: ...
- Apache Common-IO 使用
Apache Common-IO 是什么? Apache File 工具类,能够方便的操作 File 运行环境 jdk 1.7 commons-io 2.6 测试代码 package com.m.ba ...
- Asp.net自定义控件开发任我行(7)-注册自定义事件
摘要 前面我们已经把嵌入资源讲完了,不知道大家有没有得到收益,本章主要讲自定义事件,也就是给TextBox注册一个点击事件. 引言 不知道道上的朋友有没有注意到TextBox控件没有点击事件,就连网上 ...
- 谋哥:App推广最有效的是自推广
[谋哥每天一原创,第一百五十二篇] 目前市场上,各类App已经覆盖到所有你能想到的领域,并且各个山头也被占得差不多了,网上 的说法就是布局已经完成.如果你想现在再插那么一杠子进去,就得看你的真本事了, ...
- 安卓手机关闭底部键盘灯的方法(htc G11亲测有效)
还在因为看电子书和看电影时键盘灯刺眼而苦恼吗?下面提供一个方法关闭键盘灯,让你轻松DIY! 1、手机必须先Root。使用RE管理器,按照这个路径,找到文件:brightness sys/devices ...
- 【Copy List with Random Pointer】cpp
题目: A linked list is given such that each node contains an additional random pointer which could poi ...
- java中常用的几种缓存类型介绍
在平时的开发中会经常用到缓存,比如locache.redis等,但一直没有对缓存有过比较全面的总结.下面从什么是缓存.为什么使用缓存.缓存的分类以及对每种缓存的使用分别进行分析,从而对缓存有更深入的了 ...
- 16进制转10进制 HDU-1720
A+B Coming Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total ...
- [NOI2010][bzoj2005] 能量采集 [欧拉函数+分块前缀和优化]
题面: 传送门 思路: 稍微转化一下,可以发现,每个植物到原点连线上植物的数量,等于gcd(x,y)-1,其中xy是植物的横纵坐标 那么我们实际上就是要求2*sigma(gcd(x,y))-n*m了 ...
- [NOI2010][bzoj2006] 超级钢琴 [主席树/ST表+堆]
题面: 传送门 思路: 首先容易想到用堆维护的O(n2logn)暴力 那么肯定就是在这个基础上套数据结构了[愉快] 然而我因为过于蒟蒻......只想得到主席树暴力***过去的方法 大概就是把前缀和算 ...