HashMap概述
HashMap 是基于哈希表的 Map 接口的非同步实现,非线程安全。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变
特点:基于Map接口实现、允许null键/值、非同步、不保证有序(比如插入的顺序)、也不保证序不随时间变化。
HashMap的数据结构
HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
从上图中看出HashMap底层就是一个数组结构,数组中的每一项又是一个链表
HashMap的put操作
对key的hashCode()做hash,然后再计算index;
如果没碰撞直接放到bucket里;
如果碰撞了,以链表的形式存在buckets后;(hash相同<<==只要两个key和hashcode相同,hash()计算出来的值就相同,必要充分条件,可能会出现key不同,计算的hash相同,hash碰撞)
如果碰撞导致链表过长(大于等于TREEIFY_THRESHOLD),就把链表转换成红黑树;
-
如果节点已经存在就替换old value(保证key的唯一性)(存在条件:
(e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))
如果bucket满了(超过load factor*current capacity),就要resize。
HashMap的get操作
bucket(桶/数组元素和下面的链表)里的第一个节点,直接命中;
如果有冲突,则通过key.equals(k)去查找对应的entry
归纳
简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据 hash 算法来决定其在数组中的存储位置,在根据 equals 方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry 时,也会根据 hash 算法找到其在数组中的存储位置,再根据 equals 方法从该位置上的链表中取出该Entry。
http://wiki.jikexueyuan.com/project/java-collection/hashmap.html
为什么重写equals要重写hashcode方法
equals如果一般不重写的话,它是通过’==’来比较两个对象的引用地址:
public boolean equals(Object obj) {
return (this == obj);
}
而String则是自己实现了equals方法。
Object版本的equal只是简单地判断是不是同一个实例。但是有的时候,我们想要的的是逻辑上的相等,这个时候就需要重写equals方法了。而涉及到HashMap的时候,重写了equals(),就需要重写hashCode()
比如:
可以看出,字符串s与t拥有相同的散列码,这是因为字符串的散列码(在使用hashmap时的key和hashcode决定)是由内容导出的。而字符串缓冲sb与tb却有着不同的散列码,这是因为StringBuilder没有重写hashCode方法,它的散列码是由Object类默认的hashCode方法计算出来的对象存储地址,所以散列码自然也就不同了。
因为String类重写了equals方法和hashCode方法,使其比较的是内容和获取的是内容的哈希码。
但是对于k1和k2的结果就不太尽人意了,k1获取到的值是2,k2获取到的是null,Key只重写了equals方法并没有重写hashCode方法,这样的话,equals比较的确实是内容,而hashCode方法呢?没重写,那就肯定调用超类Object的hashCode方法,返回的不就是地址了吗?k1与k2属于两个不同的对象,返回的地址肯定不一样,所以现在我们知道调用map2.get(k2)为什么返回null了吧?
很简单,我们要做也重写一下hashCode方法即可(如果参与equals方法比较的成员变量是引用类型的,则可以递归调用hashCode方法来实现)