hash冲突
源码:1.8
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
//首次进入直接进行扩容
n = (tab = resize()).length; //resize 扩容 扩容的时候创建数组
if ((p = tab[i = (n - 1) & hash]) == null)
当前位置为空
//1.7中计算存放位置
// static int indexFor(int h, int length) {
// return h & (length-1);
// }
//经过这种算法 i = (n - 1) & hash 得到当前位置
tab[i] = newNode(hash, key, value, null);
else {
当前位置为不为空 此时开始形成链表
Node<K,V> e; K k;
if (p.hash == hash &&
哈希值相等
((k = p.key) == key || (key != null && key.equals(k))))
内容相等
e = p;
第二次: 将 p 给 e
p是需要插入的 e是内部定义的
else if (p instanceof TreeNode) //是否为红黑树
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
第二次:如果哈希值不等 内容不相等
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
当下一个值为空 死循环一直找到为空的
p.next = newNode(hash, key, value, null); 将最后一个赋为null;
将你需要给的作为p的下一个 就是所谓的七上八下
//什么情况下为红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st 一直找到最后一个
//TREEIFY_THRESHOLD=8
8-1=7; 判段条件 8 当链表长度为8 的时候
//p.next = newNode(hash, key, value, null); 又增加了一个新的节点
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
当下一个值不为空 并且哈希值相同内容相同 直接跳出
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e; 如果两个都不满足 将e 给 p 继续执行 e = p.next
p是需要插入的 e是内部定义的
}
}
if (e != null) { // existing mapping for key
进行替换
V oldValue = e.value; 1.8 将需要插入的值给oldValue返回出去 1.7 将本身已有的返回出去
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize(); //扩容
afterNodeInsertion(evict);
return null;
}
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
//MIN_TREEIFY_CAPACITY==64;
原来的长度不够长,元素都堆积在一起,扩容一下就可以;
resize();//扩容
链表树化(treeifyBin)发生在,链表长度大于8 并且数组长度大于64 转为红黑树
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
哈希冲突的主要原因是因为在数组长度在一定条件下没有进行扩容,使用(h = key.hashCode()) ^ (h >>> 16)进行计算哈希值,因为key的类型是Object,当key相同时进行计算后的哈希值有可能相同,此时就会产生哈希冲突。
在源码中解决哈希冲突的方法是进行数组扩容,提高他的散列性。
内存溢出
内存溢出就是内存超出了JVM虚拟机的内存容量,导致出现了java.lang.OutOfMemoryError异常, 主要原因是自定义类没有同时重写hashCode方法和equals方法导致的。 当自定义类重写了hashCode方法,但并没有重写equals方法,而HashMap中判断key是否相同是根据e.hash == hash && ((k = e.key) == key || key.equals(k))判断的,首先hash相等,这个我们的自定义类满足,然后判断key是否是同一个对象或者key相等。我们每次都是new的新对象,所以和HashMap中存在key肯定不是同一个对象,而equals方法我们并没有实现,那么默认使用的父类也就是Object的equals方法:
public boolean equals(Object obj) {
return (this == obj);//还是判断是否同一个对象 ,进行地址上的判断
}
尔真实情况下并非进行这样的判断 ,导致没有按照hashmap的存储方式进行存储。所以需要同时重写hashCode方法和equals方法。
内存泄漏
HashMap put同一个对象,理论上是会覆盖的,不会导致内存泄露,这里之所以出现这种情况,主要是因为我们put的并不是同一个对象。 一个没有实现hasCode和equals方法的Key类在HashMap中保存的情况。最后会生成很多重复的对象。所有的内存泄露最后都会抛出OutOfMemoryError异常。
同样的解决办法还是同时重写hashCode方法和equals方法。
public class Test {
public static void main(String[] args) {
Map<Person, Integer> map = new HashMap<>();
Person p = new Person("0", 10);
for (int i = 0; i < 50000; i++) {
p.setName(String.valueOf(i));
map.put(p, 1);
System.out.println(map.size());
}
System.out.println("end.");
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj instanceof Person) {
Person personValue = (Person) obj;
if (personValue.getName() == null && name == null) {
return true;
}
return personValue.getName() != null && personValue.getName().equals(name);
}
return false;
}
@Override
public int hashCode() {
return name.hashCode();
}
}
|