Java知识:HashMap类详解

来源:互联网 发布:火影手游网络连接不上 编辑:程序博客网 时间:2024/06/06 03:37

HashMap是什么?

散列图;

图是什么?

图(Map)是一种依照键值对的形式进行存储的数据结构。

HashMap是如何实现的?

HashMap基于哈希表实现也可以说HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,JDK1.8之前就是数组+链表。如下如所示:
这里写图片描述

HashMap为什么以数组+链表+红黑树的存储方式实现呢,这样的存储方式有什么优点呢?

哈希图是基于哈希表实现的,那么问题就可以换个说法:
哈希表为什么以数组+链表+红黑树的存储方式实现呢,这样的存储方式有什么优点呢?
咱们先不考虑红黑树,因为它是JDK1.8的新特性。咱们先分析为啥要数组+链表!

先普及一下哈希相关知识:
哈希法又称散列法、杂凑法以及关键字地址计算法等,相应的表称为哈希表。这种方法的基本思想是:首先在元素的关键字k和元素的存储位置p之间建立一个对应关系f,使得p=f(k),f称为哈希函数。创建哈希表时,把关键字为k的元素直接存入地址为f(k)的单元;以后当查找关键字为k的元素时,再利用哈希函数计算出该元素的存储位置p=f(k),从而达到按关键字直接存取元素的目的。
当关键字集合很大时,关键字值不同的元素可能会映象到哈希表的同一地址上,即 k1≠k2 ,但 H(k1)=H(k2),这种现象称为冲突,此时称k1和k2为同义词。实际中,冲突是不可避免的,只能通过改进哈希函数的性能来减少冲突。

处理冲突的方法有哪些呢?
主要解决冲突方法有两种:
1、开放定址法
2、链地址法(数组+链表)
Java采用的就是第二种解决冲突的方法,所以哈希表是数组+链表实现的。也就是说哈希表不一定要用数组+链表的形式,只不过Java中的哈希表是通过数组+链表实现的。

这种存储方式的优点其实就是哈希技术的优点:查询速度快。

HashMap构造函数

HashMap提供了四个构造函数:
HashMap():构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap
HashMap(int initialCapacity):构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity, float loadFactor):构造一个带指定初始容量和加载因子的空 HashMap。
HashMap(Map

HashMap中capacity、loadFactor、threshold、size等概念的解释

约定前面的数组结构的每一个格格称为桶
约定桶后面存放的每一个数据称为bin
bin这个术语来自于JDK 1.8的HashMap注释。
size表示HashMap中存放KV的数量(为链表和树中的KV的总和)。
capacity译为容量。capacity就是指HashMap中桶的数量。默认值为16。
loadFactor译为装载因子。装载因子用来衡量HashMap满的程度。loadFactor的默认值为0.75f。计算HashMap的实时装载因子的方法为:size/capacity,而不是占用桶的数量去除以capacity。
threshold表示当HashMap的size大于threshold时会执行resize操作。
threshold=capacity*loadFactor
我们以默认HashMap为例:
capacity(桶):16
loadFactor(装载因子):0.75
threshold=16*0.75=12
当size大于12的时候就会引发扩容。
我们创建一个HashMap他的capacity和loadFactor就确定了,然后两者的乘积就是极限值,当hashmap里面的键值对数量大于极限值就扩容,这么一分析这几个参数就好理解了。

HashMap中hash方法

HashMap为什么要重写equals方法就必须要重写hashcode方法

背景知识:
HashMap的equals方法是继承自Object的equals方法,Object的equals方法是比较内存地址的是否相等。
HashMap的hashcode方法是继承自Object的hashcode方法,Object的hashcode方法是根据内存地址换算出来的一个值。

题目有两个信息:
1、equals方法不是必须重写的!
2、在重写equals方法的前提下必须重写hashcode方法

第一点大家可能不理解,为什么我见到的equals方法都重写,感觉equals方法就是必须要重写的啊?!
在我们实际应用比如公安系统,判断两个人是否想得是看他们身份证号码,也就是说equals方法比较的是身份证号码是否相同,而不是比较内存地址。也就是说我们看到的equals方法都重写的原因是业务需求,当Object的equals方法不能满足业务需求的时候自然需要重写。当Object的equals方法能满足业务需求的时候可以不重写。

对于第二点,假设在公安系统中有两个人:A(123,张三),B(123,李四)key是身份证号码,value是名字,现实生活中身份证号不能重复,两个人在系统中共存。如果我们重写过equals方法(比较身份证号是否相同)后并没有重写hashcode方法有可能出现什么情况呢?根据hashmap的存储数据流程,它先计算hashcode,然后根据hashcode值来把他分配到桶数组中(如果不重写hashcode代码,问题就出现在这一步),然后再调用equals方法判断这个桶数组中是否有相同的key,有则覆盖没有则直接添加。
但是你能保证这两个人的身份证号经过hashcode之后存在同一个桶数组中吗?如果不存在同一个数组中会出现什么问题呢?存在两个“同样”的人,没错,本应该是重复的人(本应该进行覆盖操作的人)由于你没有修改hashcode进入不同的桶数组中,此时他会在这个桶数组中查找有没有其他身份证.equals相同的数据,没有就添加。

我配个图大家就明白了:
这里写图片描述
这里写图片描述
第一幅图是AB地址hashcode相同的情况下,会发生覆盖,也就是说没有重复的元素。
第二幅图AB地址hashcode不同的情况下,不会发生覆盖,也就是说存在了重复的元素。这与实际不符!
其实大家只要记住一个宗旨:keyA.equals(keyB)相同,那么keyA.hascode(keyB)也必须相同。重写hashcode就是为了保证这一点!

HashMap的put方法

HashMap的put方法执行过程可以通过下图来理解,自己有兴趣可以去对比源码更清楚地研究学习。
这里写图片描述
①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;

②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;

③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;

④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;

⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;

⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。

JDK1.8HashMap的put方法源码如下:

1 public V put(K key, V value) { 2     // 对key的hashCode()做hash 3     return putVal(hash(key), key, value, false, true); 4 } 5  6 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, 7                boolean evict) { 8     Node<K,V>[] tab; Node<K,V> p; int n, i; 9     // 步骤①:tab为空则创建10     if ((tab = table) == null || (n = tab.length) == 0)11         n = (tab = resize()).length;12     // 步骤②:计算index,并对null做处理 13     if ((p = tab[i = (n - 1) & hash]) == null) 14         tab[i] = newNode(hash, key, value, null);15     else {16         Node<K,V> e; K k;17         // 步骤③:节点key存在,直接覆盖value18         if (p.hash == hash &&19             ((k = p.key) == key || (key != null && key.equals(k))))20             e = p;21         // 步骤④:判断该链为红黑树22         else if (p instanceof TreeNode)23             e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);24         // 步骤⑤:该链为链表25         else {26             for (int binCount = 0; ; ++binCount) {27                 if ((e = p.next) == null) {28                     p.next = newNode(hash, key,value,null);                        //链表长度大于8转换为红黑树进行处理29                     if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st  30                         treeifyBin(tab, hash);31                     break;32                 }                    // key已经存在直接覆盖value33                 if (e.hash == hash &&34                     ((k = e.key) == key || (key != null && key.equals(k)))) 35                            break;36                 p = e;37             }38         }39         40         if (e != null) { // existing mapping for key41             V oldValue = e.value;42             if (!onlyIfAbsent || oldValue == null)43                 e.value = value;44             afterNodeAccess(e);45             return oldValue;46         }47     }48     ++modCount;49     // 步骤⑥:超过最大容量 就扩容50     if (++size > threshold)51         resize();52     afterNodeInsertion(evict);53     return null;54 }

HashMap的扩容()

扩充为原来的2倍

HashMap的get方法

在存储的过程中,系统根据key的hashcode来决定Entry在table数组中的存储位置,在取的过程中同样根据key的hashcode取出相对应的Entry对象。

HashMap的线程安全性

HashMap是线程不安全的,不要在并发的环境中同时操作HashMap,建议使用ConcurrentHashMap,ConcurrentHashMap引入了分段锁。
除了ConcurrentHashMap以外Collections.synchronizedMap(Map)也可以在多线程中使用,那两者的差别呢?

ConcurrentHashMap

  • 当你程序需要高度的并行化的时候,你应该使用ConcurrentHashMap
  • 尽管没有同步整个Map,但是它仍然是线程安全的
  • 读操作非常快,而写操作则是通过加锁完成的
  • 在对象层次上不存在锁(即不会阻塞线程)
  • 锁的粒度设置的非常好,只对哈希表的某一个key加锁
  • ConcurrentHashMap不会抛出ConcurrentModificationException,即使一个线程在遍历的同时,另一个线程尝试进行修改。
  • ConcurrentHashMap会使用多个锁

SynchronizedHashMap

  • 会同步整个对象
  • 每一次的读写操作都需要加锁
  • 对整个对象加锁会极大降低性能
  • 这相当于只允许同一时间内至多一个线程操作整个Map,而其他线程必须等待
  • 它有可能造成资源冲突(某些线程等待较长时间)
  • SynchronizedHashMap会返回Iterator,当遍历时进行修改会抛出异常

参考资料:
http://www.cnblogs.com/xingzc/p/5765572.html
http://panlianghui-126-com.iteye.com/blog/968057#comments
http://blog.csdn.net/chenssy/article/details/18323767
http://blog.csdn.net/cike110120/article/details/8675366
http://blog.csdn.net/not_in_mountain/article/details/77887491
http://blog.csdn.net/hwz2311245/article/details/51454686

原创粉丝点击