HashMap的存储原理
HashMap是java中相当重要的数据结构,使用HashMap的场景非常之多,因此,了解HashMap实现的过程和原理,是非常有必要的,在一些面试中也会经常被问到。好了,我们赶紧来研究java内部是怎么实现HashMap的吧!
首先,我们都知道,数组的元素查找的效率是不错的,但是涉及到插入操作和删除操作,效率低下,因为可能会涉及到后续元素位置的迁移。而另外一个数据结构链表则很好的解决了这个问题,插入和删除操作都只需要改变节点的指针就行,但是链表的检索的效率就很低了,试想一下,要检索的元素在链表的末尾,而我们只能从链表头开始走完整个链表,才能检索到这个元素。而Hash表能给我们带来高效查询,插入和删除。它是怎么做到的呢?
Hash表的实质是构造记录的存储位置和其对应的关键字之间的映射函数f,关于Hash函数的构造方法,主要有如下几种:
(1)直接定址法,取关键字的某个线性函数作为Hash函数即Hash(key) = a*key+b。这种方法很少使用,虽然不会发生冲突,但是当key非常多的时候,整张Hash表也会非常大,毕竟是一一映射的。
(2)平方取中法,将key的平方的中间几位数作为得到的Hash地址。
(3)除留余数法,将key除以某个数,得到的余数作为Hash地址。
还有一些方法我们在此就不说了。当多个关键字经过这个Hash函数的映射而得到同一个值的时候,就发生了Hash冲突。解决Hash冲突主要有两种方法:
(1)开放定址法:

其中i=1,2,3。。。。,k(k<=m-1),H(key)为哈希函数,m为哈希表表长,di为增量序列,可能有下列2种情况:
当 di=1,2,3....,m-1时,称线性探测在散列;
当
时,称为二次探测再散列。
(2)链地址法:
即将所有关键字为同义词的记录存储在同一线性表中。假设某哈希函数产生的哈希地址在区间[0,m-1]上,则设立一个指针型向量 ChainHash[m];
其每个分量的初始状态都是空指针。凡是哈希地址为i的记录都插入到头指针为ChainHash[i]的链表中。在列表中的插入位置可以在表头或表尾;也可以在中间,以保持同义词在同一线性表中按关键字有序。
例如:已知一组关键字为(19,14,23,01,68,20,84,27,55,11,10,79),则按哈希函数H(key)=key MOD 13 和链地址法处理冲突构造所得的哈希表,如下图所示:

Java中的HashMap的基本结构就如上图所示,竖着看是一个数组,横着看就是多个链表。当新建一个HashMap的时候,就初始化了一个数组:
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/ transient Entry[] table;
关于transient关键字,是为了使其修饰的对象不参与序列化,也就是说,这个对象无法被持久化,这里用这个关键字是有原因的,由于HashCode()方法是一个本地方法(由java调用本地的外部函数执行),所以不同的虚拟机,对于相同的hashCode 产生的 Code 值可能是不一样的,如果你使用默认的序列化,那么反序列化后,元素的位置和之前的是保持一致的,可是由于 hashCode 的值不一样了,那么定位函数 indexOf()返回的元素下标就会不同,这样不是我们所想要的结果.举个网上大神的例子:
向HashMap存一个entry, key为 字符串"STRING", 在第一个java程序里, "STRING"的hashcode()为1, 存入第1号bucket; 在第二个java程序里, "STRING"的hashcode()有可能就是2, 存入第2号bucket. 如果用默认的串行化(Entry[] table不用transient), 那么这个HashMap从第一个java程序里通过串行化导入第二个java程序后, 其内存分布是一样的,那么我取1号bucket能拿到“STRING”这个key,取2号bucket也能拿到相同的key,这是不合理的。
因此,HashMap这个entry数组是不可以串行化的。因此,HashMap自己实现了readObject和writeObject,在其中它只保存了bucket size,entry count(这两个其实不是必需的,但有助于提高效率)和所有的key/value(这个才是必须的)。
这就是数组内的链表:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; //持有的一个指向下一个元素的引用,构成链表。
....
}
下面让我们来看看Hash在put新元素时所做的操作:
public V put(K key, V value) { // HashMap允许存放null键和null值, 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。
if (key == null)
return putForNullKey(value); //null key 存放的总是数组的第一个元素中
int hash = hash(key.hashCode()); // 根据key的HashCode重新计算hash值
int i = indexFor(hash, table.length); //通过hash值算出在对应table中的索引(下标)。
for (Entry<K,V> e = table[i]; e != null; e = e.next) { // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //如果存在key相等的情形时,则用新的value值覆盖老的value的值
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++; // 如果i索引处的Entry为null,表明此处还没有Entry。
addEntry(hash, key, value, i); // 将key、value添加到i索引处。
return null;
}
怎么新加传进来的entry呢:
void addEntry(int hash, K key, V value, int bucketIndex) { // 获取指定 bucketIndex 索引处的 Entry,bucketIndex可以理解为存放的table中的index,当这个bucketIndex相同时,就是发生了Hash冲突,
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e); // 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry
4 if (size++ >= threshold) // 如果新加入后的大小超过了当前最大容量,则把 table 对象的长度扩充到原来的2倍。
resize(2 * table.length);
}
原来新加的entry都是加在了链表的头端。
在取Entry的时候就非常简单了,如果key等于null,直接取数组的第一个元素,如果不是,先计算出key的hashcode找到下标,再用key的equals方法判断是否相等,如果相等,则返回对应的entry,如果不相等,则返回null:
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
关于HashMap的存储原理就说到这里啦,有什么错误,请欢迎指正。
参考资料:http://zhangshixi.iteye.com/blog/672697
HashMap的存储原理的更多相关文章
- 牛客网Java刷题知识点之HashMap的实现原理、HashMap的存储结构、HashMap在JDK1.6、JDK1.7、JDK1.8之间的差异以及带来的性能影响
不多说,直接上干货! 福利 => 每天都推送 欢迎大家,关注微信扫码并加入我的4个微信公众号: 大数据躺过的坑 Java从入门到架构师 人工智能躺过的坑 ...
- List、Set集合系列之剖析HashSet存储原理(HashMap底层)
目录 List接口 1.1 List接口介绍 1.2 List接口中常用方法 List的子类 2.1 ArrayList集合 2.2 LinkedList集合 Set接口 3.1 Set接口介绍 Se ...
- HashMap的工作原理
HashMap的工作原理 HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道HashTable和HashMap之间 ...
- HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别
①HashMap的工作原理 HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象.当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算h ...
- HashMap的工作原理深入再深入
前言 首先再次强调hashcode (==)和equals的真正含义(我记得以前有人会说,equals是判断对象内容,hashcode是判断是否相等之类): equals:是否同一个对象实例.注意,是 ...
- [转] HashMap的工作原理
HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之间的区别,那么为何这道面试题如此 ...
- 【转】HashMap的工作原理
很好的文章,推荐Java的一个好网站:ImportNew HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hasht ...
- 转:HashMap的工作原理,及笔记
HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之间的区别,那么为何这道面试题如此 ...
- HashMap的工作原理(转)
HashMap的工作原理是近年来常见的Java面试题.几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道Hashtable和HashMap之间的区别,那么为何这道面试题如此 ...
随机推荐
- TextView 实现跑马灯效果
在String.xml中添加: <string name="txt">跑马灯效果,我跑啊跑</string>在layout/mian.xml中添加TextV ...
- 兼容ie浏览器的placeholder的几种方法
项目中遇到的问题,试了几种方法,今天整理出来,如果有不合适的地方,希望大家多多提意见. 第一种方法是:使用html新增的属性 “data-”来实现的,实现的时候,input框没有使用placehole ...
- 每天一个JS 小demo之商品下架特效制作,主要知识点:定时器,倒计时,抖动特效。PS:由于不方便上传文件夹,只能上传效果图,图片等素材需自寻哟。
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- jQuery插件 -- 图片随页面滚动fixed
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 网络安全——一图看懂HTTPS建立过程
关于网络安全加密的介绍可以看之前文章: 1. 网络安全--数据的加密与签名,RSA介绍 2. Base64编码.MD5.SHA1-SHA512.HMAC(SHA1-SHA512) 3. When I ...
- springmvc 添加@ResponseBody
1.添加ResponseBody之后的话 返回字符串的时候 就是一个字符串. @RequestMapping(value = "/{bookId}/detail.do",metho ...
- 咦,好像可以自己做个webapi框架了-IRouteHandler的使用
当我们学习到一定程度的时候,我们会想要去深入了解代码底层的东西,也更想拥有一个属于自己的框架,当然,博主也正是如此.本文可能成为编写一个webapi框架的开端.有研究MVC框架的朋友会发现,mvc框架 ...
- java基础(二章)
java基础(二章) 一,变量 1.变量是内存中的一个标识符号,用于存储数据 2.变量命名规则 l 必须以字母.下划线 _ .美元符号 $ 开头 l 变量中,可以包括数字 l 变量中,不能出现特 ...
- 软件测试基础(软件测试分类和工具组)firebug、firepath的安装
白盒测试:需要了解内部结构和代码 黑盒测试:不关心内部结构和代码 灰盒测试:介于白盒黑盒之间 静态测试:测试时不执行被测试软件 动态测试:测试时执行被测试软件 单元测试:测试软件的单元模块 集成测试: ...
- 聊聊vue组件开发的“边界把握”和“状态驱动”
vue有着完整的组件化开发机制,但是官网只给了开发的方式,对于开发规范以及组件化开发的最佳实践,还需要我们来摸索.本文就平时开发中的经验来谈谈“把握边界”和“状态驱动”这两个话题. 边界把握 边界把握 ...