java HashMap HashSet的存储方式
今天遇到一个bug,简单的说就是把自定义对象作为key 存到HashMap中之后,经过一系列操作(没有remove操作)之后 用该对象到map中取,返回null。
然后查看了HashMap的源代码,get方法的核心代码如下:
     final Entry<K,V> getEntry(Object key) {
         int hash = (key == null) ? 0 : hash(key);
         for (Entry<K,V> e = table[indexFor(hash, table.length)];
              e != null;
              e = e.next) {
             Object k;
             if (e.hash == hash &&
                 ((k = e.key) == key || (key != null && key.equals(k))))
                 return e;
         }
         return null;
     }
可以看出 hashmap在比较Key的时候,是先比较该key的hashcode. 返回entry条件是hashcode相同并且对象的地址或者equals方法比较相同。然后检查了出现bug的代码,因为重写了hashcode方法,然后修改了对象的属性(与hashcode计算相关)导致不太容易注意到的bug
验证HashMap的存储与hashCode,equals相关:
public class MapTest {
    public static void main(String[] args){
        People people = new People();
        people.age = 1;
        people.name = "test";
        Map<People,Integer> map = new HashMap<People, Integer>();
        map.put(people, 1);
        people.age = 2;
        System.out.println("同一对象修改hashcode:" + map.get(people));
        People otherPeople = new People();
        otherPeople.age = 1;
        otherPeople.name = "test";
        System.out.println("不同对象有相同的hashcode与equals:" + map.get(otherPeople));
    }
    static class People{
        public int age;
        public String name;
        @Override
        public int hashCode() {
            return age;
        }
        @Override
        public boolean equals(Object obj) {
            if(obj == null || !(obj instanceof People)){
                return false;
            }
            if(((People)obj).name != name){
                return false;
            }
            return true;
        }
    }
}
结果:
同一对象修改hashcode:null
不同对象有相同的hashcode与equals:1
然后考虑到HashSet是不是也有这样的特性: 因为set是一个不包含相同元素的collections,所以在判断set中是否含有同一个元素的时候,是不是也是根据hashcode跟equals来判断的,Set源码的add方法如下:
     private transient HashMap<E,Object> map;
     public HashSet() {
         map = new HashMap<>();
     }
     public boolean add(E e) {
         return map.put(e, PRESENT)==null;
     }
可以看出HashSet的add实际上使用HashMap来实现的,所以用的是同一个机制:判断是否包含某个对象,1.首先hashcode相同,2.然后地址或者equals方法比较相同,条件1跟2是&&关系,而不是||关系
这是hashmap/hashset的判断是否包含某个key或者元素的机制,然后看看其他map接口的实现类,比如ConcurrentSkipListMap:
concurrentSkipListMap#put方法核心代码:
Node<K,V> b = findPredecessor(key);
Node<K,V> n = b.next;
for (;;) {
if (n == null)
return null;
Node<K,V> f = n.next;
if (n != b.next) // inconsistent read
break;
Object v = n.value;
if (v == null) { // n is deleted
n.helpDelete(b, f);
break;
}
if (v == n || b.value == null) // b is deleted
break;
int c = key.compareTo(n.key);
if (c == 0)
return n;
if (c < 0)
return null;
b = n;
n = f;
}
}
以上代码第16行可看出:比较的时候是根据对象重写的compareTo方法来比较的,我们做个测试:
 public class MapTest {
     public static void main(String[] args){
         People people = new People();
         people.age = 1;
         people.name = "test";
         Map<People,Integer> map = new ConcurrentSkipListMap<People, Integer>();
         map.put(people, 1);
         people.age = 2;
         System.out.println(map.get(people));
         People otherPeople = new People();
         otherPeople.age = 1;
         otherPeople.name = "test1";
         System.out.println(people.compareTo(people));
         System.out.println(people.compareTo(otherPeople));
         System.out.println(map.get(otherPeople));
     }
     static class People implements Comparable{
         public int age;
         public String name;
         @Override
         public int compareTo(Object o) {
             if(o == null || !(o instanceof People)){
                 return -1;
             }
             return ((People)o).age == age?0:-1;
         }
     }
 }
结果:
1
0
-1
null
分析:hashmap比较hash值,而在map put进对象的时候 该hash值就已经固定了(详情参考HashMap内部类Entry<K,V>),所以put进之后如果改变与hashcode方法的计算有关系的属性时,hashcode()返回的变了,map里也就找不到了
而concurrentSkipListMap比较的是对象的compartTo()方法(concurrentSkipListMap的key必须实现Comparable接口),同一个对象,不管改变什么属性,改变之后自己跟自己compareTo返回的肯定是相等的,因为比较的都是改变之后的同一个对象,而不像hashmap一样,是执行put方法的时候获取的hashcode值与改变之后的值相比较。所以concurrentSkipListMap的key不管属性怎么变 get(对象本身)都能获取到。上段代码,最后一个返回null的原因是:因为people的age已经变成了2,也就是说concurrentSkipListMap中的该对象(key)的age也是2,而otherpeople的age是1, compareto()方法返回不是零。所以get的时候返回null。
java HashMap HashSet的存储方式的更多相关文章
- 原码,补码,反码的概念及Java中使用那种存储方式
		
原码,补码,反码的概念及Java中使用那种存储方式: 原码:原码表示法是机器数的一种简单的表示法.其符号位用0表示正号,用:表示负号,数值一般用二进制形式表示 补码:机器数的补码可由原码得到.如果机器 ...
 - Java HashMap两种遍历方式
		
第一种: Map map = new HashMap(); Iterator iter = map.entrySet().iterator(); while (iter.hasNext()) { Ma ...
 - Java HashMap 四种遍历方式
		
HashMap遍历方式包含以下4种: 1.遍历KeySet,再通过Key来getValue. 2.使用entrySet的迭代器. 3.foreach entrySet的方式. 3.foreache v ...
 - Java集合 -- HashSet 和 HashMap
		
HashSet 集合 HashMap 集合 HashSet集合 1.1 Set 接口的特点 Set体系的集合: A:存入集合的顺序和取出集合的顺序不一致 B:没有索引 C:存入集合的元素没有重复 1. ...
 - Java中HashSet和HashMap
		
Set中存储元素为什么不重复(即使hashCode相同)? HashSet中存放自定义类型元素时候,需要重写对象中的hashCode方法和equals方法, HashSet中存放自定义类型元素时候,需 ...
 - Java 集合 HashMap & HashSet 拾遗
		
Java 集合 HashMap & HashSet 拾遗 @author ixenos 摘要:HashMap内部结构分析 Java HashMap采用的是冲突链表方式 从上图容易看出,如果选择 ...
 - Java List/HashSet/HashMap的排序
		
在对Java无序类集合,如List(ArrayList/LinkedList).HashSet(TreeSet有序).HashMap等排序时,Java中一个公共的类Collections,提供了对Ja ...
 - Redis入门 – Jedis存储Java对象 - (Java序列化为byte数组方式)
		
Redis入门 – Jedis存储Java对象 - (Java序列化为byte数组方式) 原文地址:http://alanland.iteye.com/admin/blogs/1600685(欢迎转载 ...
 - StoreType.java 存储方式
		
StoreType.java 存储方式 http://injavawetrust.iteye.com package com.iteye.injavawetrust.miner; /** * 存储方式 ...
 
随机推荐
- jquery实现简单的验证码倒计时的效果
			
HTML: <div class="container"> <form> <div class="form-group"> ...
 - IOS UIActionSheet(底部 弹出框的使用)
			
UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"确定要注销?" delegate:self cancel ...
 - 【BZOJ4033】[HAOI2015] 树上染色(树形DP)
			
点此看题面 大致题意: 给你一棵点数为N的带权树,要你在这棵树中选择K个点染成黑色,并将其他的N-K个点染成白色.要求你求出黑点两两之间的距离加上白点两两之间距离的和的最大值. 树形\(DP\) 这道 ...
 - python_65_生成器1
			
# map()函数 # map()是 Python 内置的高阶函数,它接收一个函数 f 和一个 list,并通过把函数 f 依次作用在 list 的每个元素上,得到一个新的 list 并返回. # 例 ...
 - 使用pip 提示UnicodeDecodeError: 'ascii' codec can't decode解决方法
			
python目录 Python27\Lib\site-packages 建一个文件sitecustomize.py 内容写: import sys sys.setdefaultencoding('gb ...
 - 如何让图片相对于上层DIV始终保持水平、垂直都居中
			
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
 - WP Mail SMTP插件解决Contact Form 7表单提交失败问题
			
WP Mail SMTP插件解决Contact Form 7表单提交失败问题 WP Mail SMTP是一款非常优秀的解决WordPress主机因为不支持或者是禁用了mail()函数,导致无法实现在线 ...
 - vue学习之路 - 3.基本操作(中)
			
基本操作(中) 本章节主要介绍:vue的条件渲染.列表渲染,计算属性和侦听器 条件渲染和列表渲染 条件渲染主要使用到了 v-if 指令,列表渲染主要使用了 v-for 指令. 下面介绍 v-if . ...
 - 32-3题:LeetCode103. Binary Tree Zigzag Level Order Traversal锯齿形层次遍历/之字形打印二叉树
			
题目 给定一个二叉树,返回其节点值的锯齿形层次遍历.(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行). 例如: 给定二叉树 [3,9,20,null,null,15,7], 3 ...
 - JAVA JDBC  连接 Oracle
			
使用 Junit 测试类编写 public class JdbcTest { private Connection con = null;// 创建一个数据库连接 private PreparedSt ...