HashMap的工作原理是什么?
HashMap内部是通过一个数组实现的,只是这个数组比较特殊,数组里存储的元素是一个Entry实体(在Java8中为Node),这个Entry实体主要包含Key、value以及一个指向自身的next指针。
static class Node<K,V> implements Map.Entry<K,V> {final int hash;final K key;V value;Node<K,V> next;}//也就是说这个数组每个元素都是单向链表
HashMap是基于hashing实现的,当进行put操作时,根据传递的key值得到它的hashcode,然后再用这个hashcode与数组的长度进行模运算,得到一个int值,就是Entry要存储在数组的位置(下标);
当通过get方法获取指定的key的值时,会根据这个key算出它的hash值(数组下标),根据这个hash值获取数组下标对应的Entry,然后判断Entry里的key,hash值或者通过equals()比较是否与要找的相同,如果相同,返回value,否则遍历该链表(有可能只有一个Entry,此时直接返回null),直到找到为止,否则返回null。
HashMap之所以在每个数组元素存储的是一个链表,是为了解决hash冲突问题,当两个对象的hash值相等时,那么一个位置肯定是放不下两个值的,于是hashmap采用链表解决这冲突,hash值相等的两个元素会形成一个链表。在Java8中,如果一个位置中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。
HashMap的长度为什么是2的幂次方?
HashMap为了存取的高效,要尽量减小碰撞冲突,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现就是把数据存到哪个链表中的算法;这个算法实际就是取模,hash%length,计算机中直接求余效率不如位移运算,源码中做了优化hash%(length-1),而hash%length==hash&(length-1)的前提是:length是2的幂次方。
HashMap与Hashtable的区别是什么?
Hashtable继承Dictionary类,而HashMap继承AbstratMap。Dictionary是任何可将键映射到相应值的类的抽象父类,而AbstratMap是基于Map接口的实现,它以最大限度地减少实现此接口所需的工作。HashMap与Hashtable都实现了Map接口。HashMap允许键和值是null,Hashtable不允许键或者值是null。线程安全不同,Hashtable的几乎所有函数都是同步的,即它是线程安全的,支持多线程。而HashMap的函数则是非同步的,它不是线程安全的。若要在多线程中使用HashMap,需要我们额外的进行同步处理。迭代器(Iterator)。HashMap的迭代器是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其他线程(非迭代器方法)改变了HashMap的结构(增加或移除元素),将会抛出java.util.ConcurrentModificationException异常。容量的初始值和增加方式不一样;HashMap的默认容量大小是16,增加容量时,每次将容量变为“原始容量*2”,当Map中元素总数超过Entry数组的75%,触发扩容操作,减少链表长度,元素分配更均匀。Hashtable默认容量大小是11,增加容量时,每次将容量变为“原始容量*2+1”。添加key-value时的hash估值算法不同:HashMap添加元素时,使用自定义的哈希算法;而Hashtable没有自定义哈希算法,直接采用key的hashCode()。速度,由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,自需要单一线程,那么使用HashMap性能要好过Hashtable。
能否让HashMap同步?
可以通过语句;
Map m = Collections.synchronizeMap(hashMap);
HashMap、Hashtable、ConccurentHashMap三者的区别:
HashMap线程不安全,数组+链表+红黑树
Hashtable线程安全,锁住整个对象,数组+链表
ConccurentHashMap线程安全,CAS+同步锁,数组+链表+红黑树
HashMap的key,value均可为null,其他两个不行。
ConcurrentHashMap在JDK1.7和JDK1.8中的区别:
在JDK1.8主要设计上的改进有以下几点:
1、不采用segment而采用node,锁住node来实现减小锁粒度。
2、设计了MOVED状态 当resize的中过程中 线程2还在put数据,线程2会帮助resize。
3、使用3个CAS操作来确保node的一些操作的原子性,这种方式代替了锁。
4、sizeCtl的不同值来代表不同含义,起到了控制的作用。
5、采用synchronized而不是ReentrantLock。
ConcurrentHashMap具体知识,可以看:
/p/5dbaa6707017
为什么 HashMap 中 String、Integer 这样的包装类适合作为 key 键?
Hashmap的结构,1.7和1.8有哪些区别?
JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法,那么他们为什么要这样做呢?因为JDK1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。
死循环形成示意图:
扩容后数据存储位置的计算方式也不一样: 在JDK1.7的时候是直接用hash值和需要扩容的二进制数进行&(这里就是为什么扩容的时候为啥一定必须是2的多少次幂的原因所在,因为如果只有2的n次幂的情况时最后一位二进制数才一定是1,这样能最大程度减少hash碰撞)(hash值 & length-1)而在JDK1.8的时候直接用了JDK1.7的时候计算的规律,也就是:扩容前的原始位置+扩容的大小值=JDK1.8的计算方式,而不再是JDK1.7的那种异或的方法。但是这种方式就相当于只需要判断Hash值的新增参与运算的位是0还是1就直接迅速计算出了扩容后的储存方式。
在计算hash值的时候,JDK1.7用了9次扰动处理=4次位运算+5次异或,而JDK1.8只用了2次扰动处理=1次位运算+1次异或。
扩容流程:
添加数据流程:
具体区别:
本文参考文章:/p/8324a34577a0?utm_source=oschina-app
我是pavel,一位憨憨傻傻的程序员,平时幽默又有才,专注于Java,go,微服务,云开发。不定时发送些腾讯程序员的工作/生活日常,请大家多多关注我的公众号!