Java基础知识强化之集合框架笔记79:HashMap的实现原理
1. HashMap的实现原理之 HashMap数据结构:
HashMap是对数据结构中哈希表(Hash Table)的实现, Hash表又叫散列表。Hash表是根据关键码Key来访问其对应的值Value的数据结构。
它通过一个映射函数把关键码Key映射到Hash表中一个位置来访问该位置的值Value,从而加快查找的速度。这个映射函数叫做Hash函数,存放记录的数组叫做Hash表。
在Java中,HashMap的内部实现结合了链表和数组的优势,链接节点的数据结构是Entry<k,v>,每个Entry对象的内部又含有指向下一个Entry类型对象的引用,如以下代码所示:
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next; //Entry类型内部有一个自己类型的引用,指向下一个Entry
final int hash;
...
}
哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法--- 拉链法,我们可以理解为"链表的数组" ,如图:
2. HashMap的实现原理之 HashMap的存取实现:
既然是线性数组,为什么能随机存取?这里HashMap用了一个小算法,大致是这样实现:
// 存储时:
int hash = key.hashCode(); // 这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值
int index = hash % Entry[].length;
Entry[index] = value; // 取值时:
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];
(1)put
这里HashMap里面用到链式数据结构的一个概念。上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。所以疑问不用担心。也就是说数组中存储的是最后插入的元素,HashMap同一index下使用头插法(每次插入数据,从链头部插入)。
到这里为止,HashMap的大致实现,我们应该已经清楚了。
public V put(K key, V value) {
if (key == null)
return putForNullKey(value); //null总是放在数组的第一个链表中
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
//遍历链表
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//如果key在链表中已存在,则替换为新value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
} void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e); //参数e, 是Entry.next
//如果size超过threshold,则扩充table大小。再散列
if (size++ >= threshold)
resize(2 * table.length);
}
当然HashMap里面也包含一些优化方面的实现,这里也说一下。比如:Entry[]的长度一定后,随着map里面数据的越来越长,这样同一个index的链就会很长,会不会影响性能?
回答:会影响性能,HashMap里面设置一个因子,随着map的size越来越大,Entry[](对应index的链表,每个元素都是Entry)会以一定的规则加长长度。
(2)get
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;
}
(3)null key 的存取
null key总是存放在Entry[]数组的第一个元素。
private V putForNullKey(V value) {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
} private V getForNullKey() {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
(4)确定数组的index:hashcode % table.length取模
HashMap存取时,都需要计算当前key应该对应Entry[]数组哪个元素,即计算数组下标;算法如下:
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
return h & (length-1);
}
public HashMap(int initialCapacity, float loadFactor) {
.....
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
注意table初始大小并不是构造函数中的initialCapacity!!
而是 >= initialCapacity的2的n次幂!!!!
3. HashMap的实现原理之 解决hash冲突的办法:
- 开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
- 再哈希法
- 链地址法
- 建立一个公共溢出区
Java中hashmap的解决办法就是采用的链地址法。
4. HashMap的实现原理之 哈希表rehash过程(扩容机制):
当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,这是一个常用的操作,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。
当哈希表的容量超过默认容量时,必须调整table的大小。当容量已经达到最大可能值时,那么该方法就将容量调整到Integer.MAX_VALUE返回,这时,需要创建一张新表,将原表的映射到新表中。
HashMap 类中包含3个和扩容相关的常量:
DEFAULT_INITIAL_CAPACITY 是初始容量,默认是 2^4 = 16;
MAXIMUM_CAPACITY是最大容量,默认是 2^30;
DEFAULT_LOAD_FACTOR是增长因子,当占用率超过这个值时,就会触发扩容操作。
DEFAULT_INITIAL_CAPACITY是table数组的容量,DEFAULT_LOAD_FACTOR则是为了最大程度避免哈希冲突,提高HashMap效率而设置的一个影响因子,将DEFAULT_LOAD_FACTOR乘以DEFAULT_INITIAL_CAPACITY就得到了一个阈值threshold,当HashMap的容量达到threshold时就需要进行扩容,这个时候就要进行ReHash操作了,可以看到下面addEntry函数的实现,当size达到threshold时会调用resize()函数进行扩容。
HashMap的默认扩容机制,是存储的key超过容量的75%时,容量翻番。其实,这些和有序无序没关系。
比如:当前大小是,当占用超过16*0.75=12时,就把容量扩充到16*2=。
resize()方法的源码如下:
/**
* Rehashes the contents of this map into a new array with a
* larger capacity. This method is called automatically when the
* number of keys in this map reaches its threshold.
*
* If current capacity is MAXIMUM_CAPACITY, this method does not
* resize the map, but sets threshold to Integer.MAX_VALUE.
* This has the effect of preventing future calls.
*
* @param newCapacity the new capacity, MUST be a power of two;
* must be greater than current capacity unless current
* capacity is MAXIMUM_CAPACITY (in which case value
* is irrelevant).
*/
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);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
} /**
* Transfers all entries from current table to newTable.
*/
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
//重新计算index
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
在扩容的过程中需要进行ReHash操作,而这是非常耗时的,在实际中应该尽量避免。
Java基础知识强化之集合框架笔记79:HashMap的实现原理的更多相关文章
- Java基础知识强化之集合框架笔记76:ConcurrentHashMap之 ConcurrentHashMap简介
1. ConcurrentHashMap简介: ConcurrentHashMap是一个线程安全的Hash Table,它的主要功能是提供了一组和Hashtable功能相同但是线程安全的方法.Conc ...
- Java基础知识强化之集合框架笔记67:Hashtable的实现原理
至于Hashtable的实现原理,直接参考网友的博客,总结很全面: 深入Java集合学习系列:Hashtable的实现原理
- Java基础知识强化之集合框架笔记64:Map集合之ArrayList嵌套HashMap
1. ArrayList集合嵌套HashMap集合并遍历. 需求: 假设ArrayList集合的元素是HashMap.有3个. 每一个HashMap集合的键和值都是字 ...
- Java基础知识强化之集合框架笔记63:Map集合之HashMap嵌套ArrayList
1. ArrayList集合嵌套HashMap集合并遍历. 需求:假设ArrayList集合的元素是HashMap.有3个.每一个HashMap集合的键和值都是字符串.元素我已经完成,请遍历. 结果: ...
- Java基础知识强化之集合框架笔记47:Set集合之TreeSet保证元素唯一性和比较器排序的原理及代码实现(比较器排序:Comparator)
1. 比较器排序(定制排序) 前面我们说到的TreeSet的自然排序是根据集合元素的大小,TreeSet将它们以升序排列. 但是如果需要实现定制排序,比如实现降序排序,则要通过比较器排序(定制排序)实 ...
- Java基础知识强化之集合框架笔记39:Set集合之HashSet存储字符串并遍历
1. HashSet类的概述: (1)不保证set的迭代顺序 (2)特别是它不保证该顺序恒久不变 HashSet底层数据结构是哈希表,哈希表依赖于哈希值存储,通过哈希值来确定元素的位置, 而保证元素 ...
- Java基础知识强化之集合框架笔记27:ArrayList集合练习之去除ArrayList集合中的重复字符串元素
1. 去除ArrayList集合中的重复字符串元素(字符串内容相同) 分析: (1)创建集合对象 (2)添加多个字符串元素(包含重复的) (3)创建新的集合 (4)遍历旧集合,获取得到每一个元素 (5 ...
- Java基础知识强化之集合框架笔记07:Collection集合的遍历之迭代器遍历
1. Collection的迭代器: Iterator iterator():迭代器,集合的专用遍历方式 2. 代码示例: package cn.itcast_03; import java.util ...
- Java基础知识强化之集合框架笔记05:Collection集合的遍历
1.Collection集合的遍历 Collection集合直接是不能遍历的,所以我们要间接方式才能遍历,我们知道数组Array方便实现变量,我们可以这样: 使用Object[] toArray() ...
随机推荐
- .net WINFORM的GDI双缓冲的实现
有时候在窗体中执行不断的GDI+操作的时候会出现闪速的状况,除了修改窗体的参数,更应该解决刷新本身的问题,双缓冲可能就是这样来的. 方法1: 用GDI绘制在位图上,然后再重新生成位图 Bitmap b ...
- 安装完xampp启用时,计算机中丢失api-ms-win-crt-conio-l1-1-0.dll怎么办?
安装完xampp启用时,计算机中丢失api-ms-win-crt-conio-l1-1-0.dll怎么办 api-ms-win-crt-conio-l1-1-0.dll 第一步:我们需要下载缺失的这个 ...
- 线程10--NSOperation的基本操作
一.并发数 (1)并发数:同时执⾏行的任务数.比如,同时开3个线程执行3个任务,并发数就是3 (2)最大并发数:同一时间最多只能执行的任务的个数. (3)最⼤大并发数的相关⽅方法 - (NSInteg ...
- 深入理解JVM之JVM内存区域与内存分配
深入理解JVM之JVM内存区域与内存分配 在学习jvm的内存分配的时候,看到的这篇博客,该博客对jvm的内存分配总结的很好,同时也利用jvm的内存模型解释了java程序中有关参数传递的问题. 博客出处 ...
- pipenv虚拟环境和依赖管理工具
一.pipenv用来干嘛 每门编程语言发展到现在,都需要一个工具,能够管理代码版本和控制生产环境和测试环境依赖一致的,这样减少不可代码上线之后不可控的问题出现.Php有Composer.Nodejs有 ...
- html5中高德、腾讯、百度 地图api调起手机app
html 部分 <div id="mapBg"> <div class="mapTab"> <a href="" ...
- setTimeout OR setInterval?
setTimeout 和setInterval从字面上应该是可以知道其大意的.timeout:延时:interval:间隔: 两者的区别就像是它们自己的英文解释一样:setTimeout是延时执行,并 ...
- 清除浮动以及:after元素
http://www.iyunlu.com/demo/enclosing-float-and-clearing-float/index.html 以上这篇示意图把清除浮动的几种方法讲的非常清楚了,其中 ...
- Oracle如何把数据库表迁移到指定表空间
问题描述: 将测试数据库中的表结果导入到正式数据库.需要在正式库中建立独立的表空间存放新导入的表,以避免和正式库中原来的表混淆. 处理步骤: 1. 在命令行中用exp指令导出测试库中指定表到指定 ...
- Vue 框架-04-计算属性
Vue 框架-04-计算属性 计算属性是什么? 大家可以去看官网解释:计算属性和侦听器 今天的第一个小实例: 为啥先放折磨一个实例,之前数据绑定的就已经可以实现了,看起来那么简单,就是为了告诉大家,当 ...