目录
HashMap类的源码:
hashCode方法
Integer类重写了hashCode方法
HashMap类的源码:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {......}
其中有:
transient Node<K,V>[] table;
table就是HashMap的核心数组结构,我们也称之为“位桶数组”(往里面装东西的)。
table是一个数组,它就是核心内容,他的类似是内部定义的一个类,本质上是链表的一个节点:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
......
}
其中,hash是哈希值,还有key对象、value对象,以及下一个节点next
这是一个单向链表,因为它没有上一个。
hashCode方法
每一个对象的hashCode是用它的地址生成的:
public class Test {
public static void main(String[] args) {
A a = new A();
System.out.println(a.hashCode());
}
}
class A{}
输出结果:
1449621165
该输出结果取决于它的地址。
这来自Object类的hashCode方法,Object类的hashCode方法源码:
public native int hashCode();
可以看到,这是调用了本地方法(native)。
Integer类重写了hashCode方法
Integer类重写了hashCode方法,把它的值直接返回了。
Integer类的hashCode方法:
@Override
public int hashCode() {
return Integer.hashCode(value);
}
其中,Integer.hashCode(value):
public static int hashCode(int value) {
return value;
}
举例:
public class Test {
public static void main(String[] args) {
Integer a = Integer.valueOf(123);
System.out.println(a.hashCode());
}
}
输出结果:
123
每个对象调用hashCode方法后都会返回一串数字。
HashMap存储键值对的过程
key对象调用hashCode方法得到一个哈希码。
前面说的table数组的默认长度是16,后面也可以更大:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
也就是要把key对象生成的哈希码与0-15对应起来。
一个最简单的方法,就是对16取余数它的余数就是0-15。
尽量要散,这样弄利用数组的特点,查询速度快。
注意不要把调用hashcode方法生成的哈希吗和HashMap类里面的hash方法算出的哈希值(hash值)混淆,这是2个东西。
hashcode是一个整数,我们需要将它转化成[0, 数组长度-1]的范围。我们要求转化后的hash值尽量均匀地分布在[0,数组长度-1]这个区间,减少“hash冲突”
i. 一种极端简单和低下的算法是:
hash值 = hashcode/hashcode;
也就是说,hash值总是1。意味着,键值对对象都会存储到数组索引1位置,这样就形成一个非常长的链表。相当于每存储一个对象都会发生“hash冲突”,HashMap也退化成了一个“链表”。
ii. 一种简单和常用的算法是(相除取余算法):
hash值 = hashcode%数组长度
这种算法可以让hash值均匀的分布在[0,数组长度-1]的区间。 早期的HashTable就是采用这种算法。但是,这种算法由于使用了“除法”,效率低下。JDK后来改进了算法。首先约定数组长度必须为2的整数幂,这样采用位运算即可实现取余的效果:hash值 = hashcode&(数组长度-1)。
JDK8中,HashMap在存储一个元素时,当对应链表长度大于8时,链表就转换为红黑树,这样又大大提高了查找的效率。