- HashMap实现原理
HashMap维护了静态类entry<k,v>,和一个entry数组,每次put和set的时候根据key来通过自己的hash()方法获取hashcode,通过hashcode与数组大小运算获取存放地址,从地址中获取需要的值。Hashcode可能会出现hash碰撞,hashMap的解决方法为:1.通过使用hashcode与数组大小length-1进行位与&运算减少碰撞2.在静态类entry中维护一个自己entry,通过链表的方式将hashcode相同的值放在同一个bucket(桶)中,set和get的时候,根据key的hashcode获取到数组索引,循环遍历这个链表(链表中也是使用的k,v存取),如果两个key的值相同就取这个值
- HashMap与Hashtable
HashMap与HashTable实现原理差不多,但是HashTable的方法上都加了sychronize关键字,线程安全.HashMap允许添加key和value为null,Hashtable因为加锁同步,因此效率比HashMap低。可以使用concurrentHashMap代替
- HashMap与HashSet
HashSet就是在类中维护了一个HashMap,用set的值作为HashMap的key,初始化一个静态Object当做value;区别:1.HashMap实现Map接口,HashSet实现set接口2.HashMap存放键值对,HashSet存放对象3.HashMap使用put方法,HashSet使用add方法
- HashMap indexfor方法
static int indexFor(int h, int length) {
return h & (length-1);
}
位与运算,HashMap中数组长度初始化默认为16,每次如果长度不够会将数组长度变为之前长度的两倍,使用length-1,在二进制运算中保证所有值都为1,&运算,相同则为1,得到数据在数组中索引位置,并且减少hash碰撞
- HashMap扩容
初始化和扩容时会根据loadfactory负载因子来计算极限容量,loadfactory默认为0.75,选择0.75,JDK1.7说明
作为一般规则,默认负载因子(0.75)在时间和空间成本上提供了很好的折衷。较高的值会降低空间开销,但提高查找成本(体现在大多数的HashMap类的操作,包括get和put)。设置初始大小时,应该考虑预计的entry数在map及其负载系数,并且尽量减少rehash操作的次数。如果初始容量大于最大条目数除以负载因子,rehash操作将不会发生。
每次向HashMap中添加数据,先判断根据key获取到的桶位置是否有值,如果有值遍历key是否有相同的,有相同的则替换老值,没有相同的则在该索引位置添加entry,添加entry之前判断size是否大于极限容量,如果大于极限容量并且在该索引位置已经存在值了才会将数据长度加倍,将之前的数据放到新数组中,从数组中获取该索引位置已经存在的entry,在该索引处重新赋值entry<hashcode,k,v,entry>,将原来的entry放到新的entry的字段属性中,由于扩容机制,在多线程情况下HashMap会出现锁死情况。JDK1.8之前使用的是Entry,根据源码得到链表插入数据是插入链表的头部
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}