深入理解-HashMap
一、HashMap概述
HashMap 在家族中位置:实现了Map接口,继承AbstractMap类。HashMap 允许key/value 都为null.
二、HashMap存储结构
HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。在其内部维护一个Entry类型数组,初始大小为16。
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry[] table; static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
……
}
三、HashMap 的存取遍历
1、存储
public V put(K key, V value) {
// HashMap允许存放null键和null值。
// 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。
if (key == null)
return putForNullKey(value);
// 根据key的keyCode重新计算hash值。
int hash = hash(key.hashCode());
// 搜索指定hash值在对应table中的索引。
int i = indexFor(hash, table.length);
// 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 如果i索引处的Entry为null,表明此处还没有Entry。
modCount++;
// 将key、value添加到i索引处。
addEntry(hash, key, value, i);
return null;
}
当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
} h ^= k.hashCode(); // This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}
获取的hash码之后对数组大小取模。
addEntry(hash, key, value, i)方法根据计算出的hash值,将key-value对放在数组table的i索引处。addEntry 是 HashMap 提供的一个包访问权限的方法,代码如下:
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);
}
如果经过hash之后,数组的下标不在数组大小范围之内而且当前数组大小超过指定阈值,那么将会把hashmap 2倍扩容。
2、读取数据
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key); return null == entry ? null : entry.getValue();
}
private V getForNullKey() {
if (size == 0) {
return null;
}
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
} int hash = (key == null) ? 0 : hash(key);
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 != null && key.equals(k))))
return e;
}
return null;
}
首先,如果要获取的key==null,那么直接调用getForNullKey()从table[0]中,获取相应的value;如果key != null,那么将会获得该key的hashcode,之后获取table的下标,从中遍历出来相应的value。
3、遍历
Set<String> keys = map.keySet();
Iterator<String> iterator = keys.iterator();
while (iterator.hasNext()) {
String key = iterator.next();
System.out.println("[" + key + "," + map.get(key) + "]");
}
4、resize() 再哈希
当哈希表的容量超过默认容量时(在hashmap中,如果第一次hash之后,找到的数组下标不在合理的范围之内而且当前的大小超过规定阈值),必须调整table的大小。当容量已经达到最大可能值时,那么该方法就将容量调整到Integer.MAX_VALUE返回,这时,需要创建一张新表,将原表的映射到新表中。
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
} Entry[] newTable = new Entry[newCapacity];
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
四、性能
Fail-Fast机制
我们知道java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。
这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。其中modCount是声明为volatile,保证线程可见。
感谢:http://zhangshixi.iteye.com/blog/672697
深入理解-HashMap的更多相关文章
- 深入理解HashMap+ConcurrentHashMap的扩容策略
前言 理解HashMap和ConcurrentHashMap的重点在于: (1)理解HashMap的数据结构的设计和实现思路 (2)在(1)的基础上,理解ConcurrentHashMap的并发安全的 ...
- Map 综述(一):彻头彻尾理解 HashMap
转载自:https://blog.csdn.net/justloveyou_/article/details/62893086 摘要: HashMap是Map族中最为常用的一种,也是 Java Col ...
- 深入理解HashMap
转自:http://annegu.iteye.com/blog/539465 Hashmap是一种非常常用的.应用广泛的数据类型,最近研究到相关的内容,就正好复习一下.网上关于hashmap的文章很多 ...
- 深入理解HashMap的扩容机制
什么时候扩容: 网上总结的会有很多,但大多都总结的不够完整或者不够准确.大多数可能值说了满足我下面条件一的情况. 扩容必须满足两个条件: 1. 存放新值的时候当前已有元素的个数必须大于等于阈值 2. ...
- 集合之深入理解HashMap
Hashmap是一种非常常用的.应用广泛的数据类型 1.hashmap的数据结构 要知道hashmap是什么,首先要搞清楚它的数据结构,在java编程语言中,最基本的结构就是两种,一个是数组,另外一个 ...
- 深入理解HashMap上篇
前言: HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型.随着JDK(Java Developmet Kit)版本的更新,JDK1.8对HashMap底层的实现进行了优化, ...
- 通过源码理解HashMap的并发问题
最近在学习有关于Java的基础知识,在学习到HashMap的相关知识的时候,了解了HashMap的并发中会出现的问题,在此记录,加深理解(这篇文章是基于Java1.7的,主要是为了更加直观,更新版本的 ...
- 深入理解HashMap和CurrentHashMap
原文链接:https://segmentfault.com/a/1190000015726870 前言 Map 这样的 Key Value 在软件开发中是非常经典的结构,常用于在内存中存放数据. 本篇 ...
- 理解HashMap的原理
HashMap内部数据结构 HashMap内部采用数组和链表结合的方式来存取数据(见下图).这种方式有什么好处呢? 我们知道,数组操作对于检索是O(1)的,能够很快的根据数组的下标定位对 ...
- 深入理解HashMap(原理,查找,扩容)
面试的时候闻到了Hashmap的扩容机制,之前只看到了Hasmap的实现机制,补一下基础知识,讲的非常好 原文链接: http://www.iteye.com/topic/539465 Hashmap ...
随机推荐
- Android 开发笔记 “Sqlite Cursor 使用”
使用过 SQLite 数据库的童鞋对 Cursor 应该不陌生,如果你是搞.net 开发你大可以把Cursor理解成 Ado.net 中的数据集合相当于dataReader.今天特地将它单独拿出来谈, ...
- PHP-变量(www.w3school.com.cn/php)
写在前面: 变量可以形象的解释为信息的容器(存信息的东西).比如说$x=8,相当于把8给放到x里面,以后操作$x的时候就相当于操作8 >5+$x结果为13 ------------------- ...
- widget intent重复问题
今天在做android widget时发现点击任意widget时只会更新最后一个widget 原来是requestCode的问题 Intent intent = new Intent(WidgetPr ...
- 求1+2+...+n
题目:求1+2+…+n,要求不能使用乘除法.for.while.if.else.switch.case等关键字以及条件判断语句(A?B:C). 程序很简单,就看想到想不到了.悲剧,我属于后者... 算 ...
- Python CSV文件处理/读写及With as 用法
可以不使用CSV模块 逐行处理: for line in open("samples/sample.csv"): title, year, director = line.spli ...
- Libev学习笔记3
设置完需要监听的事件之后,就开始event loop了.在Libev中,该工作由ev_run函数完成.它的大致流程如下: int ev_run (EV_P_ int flags) { do { /* ...
- poj 2533 Longest Ordered Subsequence(线性dp)
题目链接:http://poj.org/problem?id=2533 思路分析:该问题为经典的最长递增子序列问题,使用动态规划就可以解决: 1)状态定义:假设序列为A[0, 1, .., n],则定 ...
- hdu4725 The Shortest Path in Nya Graph【最短路+建图】
转载请注明出处,谢谢:http://www.cnblogs.com/KirisameMarisa/p/4297574.html ---by 墨染之樱花 题目链接:http://acm.hdu ...
- 关于UITableview刷新笔记
今天在做项目的时候 发现调用tableview 的 reloaddata 方法的时候出现崩溃. - 具体操作是执行某个方法后将数据数组中的某条数据删除(数组中存数组)发现调用刷新方法后 程序崩溃,查看 ...
- Extjs 3.0 htmleditor实现插入图片功能
首先感谢前辈们的无私奉献.贴出参考地址 http://zhidao.baidu.com/link?url=Q0ZM405OFNy_xAHSut9TepRJxgXCxFayQttrQz1N82dlA1_ ...