HashMap 也是我们使用非常多的 Collection,它是基于哈希表的 Map 接口的实现,以key-value 的形式存在。在 HashMap 中,key-value 总是会当做一个整体来处理,系统会根据 hash 算法来来计算 key-value 的存储位置,我们总是可以通过 key 快速地存、取 value。下面就来分析 HashMap 的存取。
一、定义
HashMap 实现了 Map 接口,继承 AbstractMap。其中 Map 接口定义了键映射到值的规则,而 AbstractMap类提供 Map 接口的骨干实现,以最大限度地减少实现此接口所需的工作,其实 AbstractMap 类已经实现了Map,这里标注 Map LZ 觉得应该是更加清晰吧!
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
二、构造函数
HashMap 提供了三个构造函数:
HashMap():构造一个具有默认初始容量 (16) 和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity):构造一个带指定初始容量和默认加载因子 (0.75) 的空 HashMap。
HashMap(int initialCapacity, float loadFactor):构造一个带指定初始容量和加载因子的空 HashMap。
在这里提到了两个参数:初始容量,加载因子。这两个参数是影响 HashMap 性能的重要参数,其中容量表示哈希表中桶的数量,初始容量是创建哈希表时的容 量,加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之 愈小。对于使用链表法的散列表来说,查找一个元素的平均时间是 O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果 负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。系统默认负载因子为 0.75,一般情况下我们是无需修改的。HashMap 是一种支持快速存取的数据结构,要了解它的性能必须要了解它的数据结构。
三、数据结构
我们知道在 Java 中最常用的两种结构是数组和模拟指针(引用),几乎所有的数据结构都可以利用这两种来组合实现,HashMap 也是如此。实际上 HashMap 是一个“链表散列”,如下是它数据结构:
从上图我们可以看出 HashMap 底层实现还是数组,只是数组的每一项都是一条链。其中参数initialCapacity 就代表了该数组的长度。下面为 HashMap 构造函数的源码:
public HashMap(int initialCapacity, float loadFactor) { //初始容量不能<0 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); //初始容量不能 > 最大容量值,HashMap 的最大容量值为 2^30 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //负载因子不能 < 0 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: "+ loadFactor); // 计算出大于 initialCapacity 的最小的2的n 次方值。 int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor;//设置 HashMap 的容量极限,当 HashMap 的容量达到该极限时就会进行扩容操作 threshold = (int) (capacity * loadFactor); //初始化 table 数组 table = new Entry[capacity]; init(); }
从源码中可以看出,每次新建一个 HashMap 时,都会初始化一个 table 数组。table 数组的元素为 Entry节点。
static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; Entry<K,V> next; final int hash; /** * Creates new entry. */ Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } ....... }
其中 Entry 为 HashMap 的内部类,它包含了键 key、值 value、下一个节点 next,以及 hash 值,这是
非常重要的,正是由于 Entry 才构成了 table 数组的项为链表。
上面简单分析了 HashMap 的数据结构,下面将探讨 HashMap 是如何实现快速存取的。
四、存储实现:put(key,vlaue)
首先我们先看源码
public V put(K key, V value) {
//当 key 为 null,调用 putForNullKey 方法,保存 null 与 table 第一个位置中,这是 HashMap允许为 null 的原因
if (key == null) return putForNullKey(value); //计算 key 的 hash 值 int hash = hash(key.hashCode()); ------(1) //计算 key hash 值在 table 数组中的位置 int i = indexFor(hash, table.length); ------(2) //从 i 出开始迭代 e,找到 key 保存的位置 for (Entry<K, V> e = table[i]; e != null; e = e.next) { Object k; //判断该条链上是否有 hash 值相同的(key 相同)//若存在相同,则直接覆盖 value,返回旧 value if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; //旧值 = 新值 e.value = value; e.recordAccess(this); return oldValue; //返回旧值 } } //修改次数增加 1 modCount++; //将 key、value 添加至 i 位置处 addEntry(hash, key, value, i); return null; }
通过源码我们可以清晰看到 HashMap 保存数据的过程为:首先判断 key 是否为 null,若为 null,则直接调用 putForNullKey 方法。若 不为空则先计算 key 的 hash 值,然后根据 hash 值搜索在 table 数组中的索引位置,如果 table 数组在该位置处有元素,则通过比较是否存在相同 的 key,若存在则覆盖原来 key的 value,否则将该元素保存在链头(最先保存的元素放在链尾)。若 table 在该处没有元素,则直接保存。这个过程 看似比较简单,其实深有内幕。有如下几点:
1、 先看迭代处。此处迭代原因就是为了防止存在相同的 key 值,若发现两个 hash 值(key)相同时,HashMap 的处理方式是用新 value 替换旧 value,这里并没有处理 key,这就解释了 HashMap 中没有两个相同的 key。
2、 在看(1)、(2)处。这里是 HashMap 的精华所在。首先是 hash 方法,该方法为一个纯粹的数学计算,就是计算 h 的 hash 值。
static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
我们知道对于 HashMap 的 table 而言,数据分布需要均匀(最好每项都只有一个元素,这样就可以直接找到),不能太紧也不能太松,太紧会导致查询速 度慢,太松则浪费空间。计算 hash 值后,怎么才能保证 table 元素分布均与呢?我们会想到取模,但是由于取模的消耗较大,HashMap 是这样处理 的:调用indexFor 方法。
static int indexFor(int h, int length) { return h & (length-1); }
HashMap 的底层数组长度总是 2 的 n 次方,在构造函数中存在:capacity <<= 1;这样做总是能够保证 HashMap 的底层数组长度为 2 的 n 次方。当 length 为 2 的 n 次方时,h&(length - 1)就相当于对 length取模,而且速度比直接取模快得多,这是 HashMap 在速度上的一个优化。至于为什么是 2 的 n 次方下面解释。
我们回到 indexFor 方法,该方法仅有一条语句:h&(length - 1),这句话除了上面的取模运算外还有一个非常重要的责任:均匀分布 table 数据和充分利用空间。这里我们假设 length 为 16(2^n)和 15,h 为 5、6、7。
【下载地址】
百度网盘链接:https://pan.baidu.com/s/1oY020ak67X1HnKf7R4j6Hw
提取码:c8m2
相关文章
使用-JFreeChart来创建基于web的图表
XStream使用文档
WebService发布过程及常见问题
webpack实战入门进阶调优分享
weblogic调优参数及监控指标
weblogic节点管理
weblogic管理控制台概述
weblogic-部署和启动
WebLogic-Server-性能及调优-调优-Java-虚拟机
Java 虚拟机(Java virtual machine,简称 JVM)是一种虚拟“执行引擎”实例,可在微处理器上执行 Java 类文件中的字节码。调整 JVM 的方式会影响 Weblogic Server 和应用程序的性能。
Velocity是一个基于java的模板引擎(template engine)。它允许任何人仅仅简单的使用模板语言(template language)来引用由java代码定义的对象。
Velocity 用户手册是帮助页面设计者和内容提供者认识 Velocity 和其简单而功能强大的脚本语言――Velocity 模板语言(VTL)。在手册上的许多例子,都是用 Velocity 插入动态的内容到网页上,但是所有的 VLT 例子都能应用到其他的页面和模板中。
FlashFXP绿色版网盘下载,附激活教程 1774
FlashFxp百度网盘下载链接:https://pan.baidu.com/s/1MBQ5gkZY1TCFY8A7fnZCfQ。FlashFxp是功能强大的FTP工具
Adobe Fireworks CS6 Ansifa绿色精简版网盘下载 1559
firework可以制作精美或是可以闪瞎眼的gif,这在广告领域是需要常用的,还有firework制作下logo,一些原创的图片还是很便捷的,而且fireworks用法简单,配合dw在做网站这一块往往会发挥出很强大的效果。百度网盘下载链接:https://pan.baidu.com/s/1fzIZszfy8VX6VzQBM_bdZQ
navicat for mysql中文绿色版网盘下载 1620
Navicat for Mysql是用于Mysql数据库管理的一款图形化管理软件,非常的便捷和好用,可以方便的增删改查数据库、数据表、字段、支持mysql命令,视图等等。百度网盘下载链接:https://pan.baidu.com/s/1T_tlgxzdQLtDr9TzptoWQw 提取码:y2yq
火车头采集器(旗舰版)绿色版网盘下载 1703
火车头采集器是站长常用的工具,相比于八爪鱼,简洁好用,易于配置。火车头能够轻松的抓取网页内容,并通过自带的工具对内容进行处理。站长圈想要做网站,火车头采集器是必不可少的。百度网盘链接:https://pan.baidu.com/s/1u8wUqS901HgOmucMBBOvEA
Photoshop(CS-2015-2023)绿色中文版软件下载 1819
安装文件清单(共46G)包含Window和Mac OS各个版本的安装包,从cs到cc,从绿色版到破解版,从安装文件激活工具,应有尽有,一次性打包。 Photoshop CC绿色精简版 Photoshop CS6 Mac版 Photoshop CC 2015 32位 Photoshop CC 2015 64位 Photoshop CC 2015 MAC版 Photoshop CC 2017 64位 Adobe Photoshop CC 2018 Adobe_Photoshop_CC_2018 Photoshop CC 2018 Win32 Photoshop CC 2018 Win64