HashMap原理详解

1、HashMap是存储键值对的数据结构;

2、几个重要参数:

 

  • 容量,默认为16
  • 负载因子,默认为0.75
  • 扩容极限(暂不十分了解)

说明:当我们不指定任何参数创建HashMap时,就会创建一个容量为16,负载因子为0.75的HashMap,当HashMap中实际的元素个数大于等于16*0.75=12时,会触发HashMap的resize操作,HashMap的容量会自动扩展一倍。负载因子0.75被证明是性能比较好的取值,通常不会修改,那么只有初始同理会导致频繁扩容的行为,这是非常耗费资源的操作,所以如果能够事先估计出容器所需要存储的容量,就在初始化时修改默认值,即new HashMap(int initialCapacity)。

几个重要函数:hashCode() 和 equal() 

  • hashCode(),计算key值对应的哈希值,然后确定存储的数组索引;
  • equal(),确定存储索引后,在索引处单向链表上遍历比较key值,看是否已经存储。

说明:这两个函数对实现HashMap的精确性和正确性至关重要。

3、HashMap的底层实现:Entry数组 + 链表 + 红黑树。

首先,在HashMap类中有一个Entry的内部类,这个Entry类包含了key-value作为实例变量。没当向HashMap中存放k-v对时,都会为其实例化一个Entry对象,则个Entry对象就会存储Entry数组中。而Entry在数组中的具体位置,会根据key的HashCode()方法计算出的哈希值来决定。如果哈希算法设计的足够好,是不会发生碰撞冲突的,但实际中肯定没有这么理想,所以在每个索引处,会有一个单向链表,来存储相同索引的Entry对象。

然后,当调用put方法向哈希表中存储键值对时,首先计算key的hashcode,定位到合适的数组索引,然后在该索引上的单向链表进行遍历,用equals函数比较key是否存在。如果存在,则新的value值覆盖原来的value值;如果不存在,则将新的Entry对象插入到链表头部。且当链表长度大于某个值(可能是8)时,链表会变为红黑树,链表的查询效率为O(n),红黑树的查询效率为O(lgn),可见后者的效率更高。

最后,当需要取出一个Entry对象时,也是根据key的hashCode值来找到其存储位置,直接取出该Entry。

4、resize在多线程环境下,可能会产生条件竞争。

如果两个线程都发现HashMap需要重新调整大小,那么它们会同时试着去调整大小。在调整大小时,存储在链表中的元素的次序会反过来,因为在放入新的位置时,HashMap会将Entry对象不断的插入链表的头部。插入头部也主要是为了防止尾部遍历,否则这对key的HashCode相同的Entry每次添加还要定位到尾节点。如果条件竞争发送了,可能会出现环形链表,之后当我们get(key)操作时,就有可能发生死循环。形成循环的原因可参考连接http://www.cnblogs.com/andy-zhou/p/5402984.html。

此外,虽然HashTable使用synchronized来保证线程安全,但是它会锁住整个哈希表,在线程竞争激烈的情况下,效率非常低,所以并不在多线程中经常使用HashTable。

5、多线程中的哈希表ConcurrentHashMap

采用segment分段锁来保护不同段的数据,这样在多线程中就既安全又高效。当多线程访问不同段的数据时,线程间并不存在锁的竞争,从而可以有效提高并发访问效率。

6、HashMap和HashTable有什么区别和联系?

  • 都实现了Map接口;
  • HashMap允许键和值时null,而HashTable不允许键或者值是null;
  • HashMap不是同步的,多线程不安全;而HashTable是多线程安全的,同步的;
  • HashTable是遗留类,内部实现很多没优化和冗余。其次在多线环境下,现在也有ConcurrentHashMap代替。

 7、一个解释很详细的链接:http://www.cnblogs.com/xwdreamer/archive/2012/06/03/2532832.html

posted on 2017-02-24 20:07  阳小徐  阅读(2257)  评论(1编辑  收藏  举报