1. hashMap内部实现
HashMap是基于哈希表的 Map 接口的实现
HashMap就是以散列表的形式将数据存储到hashTable里面去。
hashTable的数据结构:数组+链表的形式 ,同一下标如果有多个值,则用链表连起来。
假设只放2个值(数据量),则需要定义hashMap容量(N)为:N>=2*1.34 &&N==2的K次方(K=1,2,3...)
这里N=4 (N一定要设为2的k次方,一是确保容量足够,2是为了让hash值均匀分布在表(数组)中,否则有可能会导致不同key的值进入到相同的插槽,形成链表,性能急剧下降,所以一定要保证下面(a%b == (b-1)&a (与运算))&前面部分中的二进制位全为1,才能最大限度的利用hashCode,以生产更多的散列结果)
Map<string,string> map=new HashMap<>(4);
map.put("name","zhangsan");
map.put("sex","male");
2.生成hashCode
@Override
public int hashCode(){
return Objects.hash("name")//or "sex"
}
4.为什么使用hashCode?
hashMap速度快,是因为他使用散列表
散列表是根据key的hashcode值生成数组下标(通过内存地址直接查找,无任何判断,速度快)
生成hashcode的过程就是将key转为一个唯一的整型的数的过程
将string类型的key转为hashCode
转化方法:
public int hashCode(){
int hash=0;
int h=hash;
if(h==0&& value.length>0){
char val[] =value;
for(int i=0;i<value.length;i++){
h=31*h +val[i];
}
hash=h;
}
return h;
}
例如:value(key)是字符串 "abc" ('a'的ascii码是97)
则 在for循环内:第一次结果 h=97;第二次 h=31*97+98;第3次 h=31*(31*97+98)+99
最终返回第3次h的值 为hashCode
取31的目的:尽可能使各个key的hashCode不同,不同key的hashcode是有可能相同的。
打个比方:先按照hashcode分配不同的猪圈,将hashcode相同的猪放到一个猪圈里。
查找的时候,先找到hashcode对应的猪圈,然后在逐个比较里面的小猪。
所以问题的关键就是建造多少个猪圈比较合适。
如果每个小猪的体重全部不同(考虑到毫克级别),每个都建一个猪圈,那么我们可以最快速度的找到这头猪。缺点就是,建造那么多猪圈的费用有点太高了。
3. 通过生成的hashCode 按一定规则将key对应的值放入hashTable中
hashTable 是以数组的形式存储值的
val result=hash(key)
static final int hash(Object Key){
int h;
return (key==null)?0:(h=key.hashCode())^(h>>>16);}
右移16位的目的增大空间,以避免在使用get方法时候 不同hashcode跟数组长度-1进行与运算时 取到同一个位置的值,造成错误。也就是说虽然两个key的hashcode不同,但经过与运算也可能有相同的hash值。
实际存储值的地方=arr[result],由Put方法将值存到下标对应的内存区域中。
4.调用get方法时候:比较表中已有的hash与传入参数key生成的hash是否相等时:
为什么不用a%b 而用 (b-1)&a ? :对于现代处理器,除法和求余数是最慢的动作。
当 b是2的指数时,a%b == (b-1)&a (与运算)
5. 不同的key可能有相同的hashcode(少量,可以调整),但不同的hashcode其对应的hash()返回值一定不同。
6.如何线程安全的使用HashMap
http://www.importnew.com/21396.html
对于线程不安全的类,是不能采用单例对象以减少对象的生成个数的,需要把线程不安全的类放到方法体中,生成相关的对象。
对于线程不安全的类的对象,同一个对象只能被1个线程使用1次,不能被复用,否则会出一些莫名其妙的未知错误。