官方文档对 HashMap 的定义:

public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable

UML Class Diagram:

HashMap 实现了 Map interface。

HashMap 是一个数据结构,如同一个 DBMS 一样,基本功能其实就是增删改查。

Operations Time Complexity Notes
get, put O(1) assuming the hash function has dispersed the elements properly among the buckets
iteration over collection views proportional to the capacity plus the size do not set the initial capacity too high or the load factor too low
  • 不保证迭代顺序,也不保证顺序一直不变。
  • 非同步,允许null值(key&value)
  • 迭代的时间复杂度:O(capacity+#mappings),与容量和元素数量有关(若对迭代性能要求高,不要将capacity设置过高,load factor设置过小,避免空闲空间过多)。
  • 内部数组长度为2的幂次方(为了高效实现取模运算),元素下标通过hash&(length-1)计算得到;hash&(length-1) == hash%length,这两个操作等价但不等效,前者更高效(使用了位与运算)。

1、 根据 key 查询 map

调用 get(Object key) 方法:

     public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}

hash(key):

     /**
* Computes key.hashCode() and spreads (XORs) higher bits of hash
* to lower. Because the table uses power-of-two masking, sets of
* hashes that vary only in bits above the current mask will
* always collide. (Among known examples are sets of Float keys
* holding consecutive whole numbers in small tables.) So we
* apply a transform that spreads the impact of higher bits
* downward. There is a tradeoff between speed, utility, and
* quality of bit-spreading. Because many common sets of hashes
* are already reasonably distributed (so don't benefit from
* spreading), and because we use trees to handle large sets of
* collisions in bins, we just XOR some shifted bits in the
* cheapest possible way to reduce systematic lossage, as well as
* to incorporate impact of the highest bits that would otherwise
* never be used in index calculations because of table bounds.
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

这个方法计算 key 的哈希值,可以看到如果 key == null,哈希值为 0;

否则,调用 Object.hashCode() 计算 key 的哈希值,再将这个哈希值的低16位与自己的高16位按位异或,最后返回这个异或值。

从注释看,设计者为了降低 key collision,综合考虑了 速度、实用、质量三个指标,再加上一般情况下key 的分布已经很均匀了,所以仅仅利用了key哈希值的高16位进行异或,最后得到了比较理想的结果。

然后调用了 getNode(int hash, Object key),这个方法为 final,子类无法覆盖:

 final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
// 如果table != null && not empty && 根据 hash 找到对应位置的元素也不为null
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
// 如果哈希值相等 &&(key相同或相等),则找到了元素,直接返回
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
// 不是要找的元素,查找这个元素的下一个元素
if ((e = first.next) != null) {
// 是一棵树,调用 getTreeNode 查找
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
// 是一个链表,顺序查找
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}

2、往 map 里插入数据

直接调用的是 put(K key, V value):

     public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

又调用了putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict),这个方法为 final,子类无法覆盖:

     final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 如果 table is null or is empty,调用resize()初始化table,默认大小16,负载因子0.75
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 根据hash找到对应位置的元素,如果这个元素为null则直接新增node;其中 (n - 1) & hash 使用了位与运算(&),比取余运算(%)更高效
if ((p = tab[i = (n - 1) & hash]) == null)
// 直接新建一个node并放到这个位置
tab[i] = newNode(hash, key, value, null);
// 不为null,则进一步检查
else {
Node<K,V> e; K k;
// 第一个元素就是我们要找的元素,最后e要么为空,要么保存了我们要找的元素
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 是一棵树,调用putTreeVal()方法
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 是一个链表,遍历这个链表
else {
for (int binCount = 0; ; ++binCount) {
// 没有找到,则在最后新增一个node,只有在这种情况下e==null
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 如果binCount>=TREEIFY_THRESHOLD - 1,则将链表转换为树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 找到元素,跳出循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
// 没有找到元素,且没有到最后一个元素,则继续遍历下一个元素p
p = e;
}
}
// e != null 说明我们要找的元素存在于map中,就是e这个元素
if (e != null) { // existing mapping for key
V oldValue = e.value;
// 更新对应的 value
if (!onlyIfAbsent || oldValue == null)
e.value = value;
// 空方法,由LinkedHashMap覆盖
afterNodeAccess(e);
// 返回旧值,其他情况说明key原来不存在,返回null
return oldValue;
}
}
// 改变了map的结构,自增modCount
++modCount;
// 自增size,如果size超过了阈值,则调用resize()对容量增加一倍
if (++size > threshold)
resize();
// 空方法,由LinkedHashMap覆盖
afterNodeInsertion(evict);
return null;
}

3、HashMap使用的树是红黑树

HashMap的节点分为普通的节点 Node, 树节点 TreeNode。

Node:

     static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
// 省略部分代码
}

TreeNode 相比于 Node,增加了 before, after, parent, left, right, prev 等“指针”。

     static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
TreeNode(int hash, K key, V val, Node<K,V> next) {
super(hash, key, val, next);
} /**
* Returns root of tree containing this node.
*/
final TreeNode<K,V> root() {
for (TreeNode<K,V> r = this, p;;) {
if ((p = r.parent) == null)
return r;
r = p;
}
}
// 省略部分代码
}

JDK 8 - java.util.HashMap 实现机制分析的更多相关文章

  1. JDK 8 - java.util.HashSet 实现机制分析

    JDK 8 Class HashSet<E> Doc: public class HashSet<E> extends AbstractSet<E> impleme ...

  2. java.util.HashMap源码分析

    在java jdk8中对HashMap的源码进行了优化,在jdk7中,HashMap处理“碰撞”的时候,都是采用链表来存储,当碰撞的结点很多时,查询时间是O(n). 在jdk8中,HashMap处理“ ...

  3. JDK1.8源码(七)——java.util.HashMap 类

    本篇博客我们来介绍在 JDK1.8 中 HashMap 的源码实现,这也是最常用的一个集合.但是在介绍 HashMap 之前,我们先介绍什么是 Hash表. 1.哈希表 Hash表也称为散列表,也有直 ...

  4. java:警告:[unchecked] 对作为普通类型 java.util.HashMap 的成员的put(K,V) 的调用未经检查

    java:警告:[unchecked] 对作为普通类型 java.util.HashMap 的成员的put(K,V) 的调用未经检查 一.问题:学习HashMap时候,我做了这样一个程序: impor ...

  5. LinkedHashMap和HashMap的比较使用 由于现在项目中用到了LinkedHashMap,并不是太熟悉就到网上搜了一下。 ? import java.util.HashMap; impo

    LinkedHashMap和HashMap的比较使用 由于现在项目中用到了LinkedHashMap,并不是太熟悉就到网上搜了一下. import java.util.HashMap; import ...

  6. java.util.HashMap和java.util.HashTable (JDK1.8)

    一.java.util.HashMap 1.1 java.util.HashMap 综述 java.util.HashMap继承结构如下图 HashMap是非线程安全的,key和value都支持nul ...

  7. java.lang.ClassCastException: java.util.HashMap cannot be cast to java.lang.String

    问题背景:从前端传来的json字符串中取某些值,拼接成json格式入参调外部接口. 报如下错: java.lang.ClassCastException: java.util.HashMap cann ...

  8. EL1008E: Property or field 'timestamp' cannot be found on object of type 'java.util.HashMap

    2018-06-22 09:50:19.488  INFO 20096 --- [nio-8081-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/]       : ...

  9. 原子类java.util.concurrent.atomic.*原理分析

    原子类java.util.concurrent.atomic.*原理分析 在并发编程下,原子操作类的应用可以说是无处不在的.为解决线程安全的读写提供了很大的便利. 原子类保证原子的两个关键的点就是:可 ...

随机推荐

  1. Kubernetes Horizontal Pod Autoscaler

    非常牛逼的技术,目前最新的版本支持众多的Feature HPA功能需要Heapster收集的CPU.内存等数据作为支撑 配置示例: apiVersion: autoscaling/v2beta1 ki ...

  2. Jquery 获取地址位置

    直接在浏览器地址 输入: http://pv.sohu.com/cityjson?ie=utf-8 可以查看数据格式 引入一个搜狐的js库: <script src="http://p ...

  3. 2015年蓝桥杯C/C++ B组题目题解

    1. 输入一个字符串,求它包含多少个单词.单词间以一个或者多个空格分开. 第一个单词前,最后一个单词后也可能有0到多个空格.比如:" abc xyz" 包含两个单词,"a ...

  4. java格式化输出 printf 例子

    import java.util.Date; public class Printf { public static void main(String[] args) { // %s表示输出字符串,也 ...

  5. 第二节课-Data-driven approach:KNN和线性分类器分类图片

    2017-08-12 1.图片分类是很多CV任务的基础: 2.图片分类要面临很多的问题,比如图片被遮挡,同一种动物有很多种颜色,形状等等,算法需要足够强壮: 3.所以很难直接写出程序来进行图片分类,常 ...

  6. Linux上SFTP用法

    SFTP简介 sftp是一个交互式的文件传输协议,类似于ftp,但它进行加密传输,比ftp更安全. SFTP用法 localhost 从远程主机获取文件或目录到本地目录下 sftp>get /h ...

  7. jQuery ajax 标准写法及进度条绘制

    jQuery ajax 标准写法及进度条绘制 $.ajax({ url: "http://www.microsoft.com", //请求的url地址 dataType: &quo ...

  8. Windows 10 SDK 10.0.10158

    昨天微软发布了Windows 10 SDK 10158版本: http://blogs.windows.com/buildingapps/2015/06/30/windows-10-sdk-previ ...

  9. Vue中mixin的用法

    在项目中我们经常会遇到多个组件调用同一个方法的问题,为了避免每次都在.vue文件中定义并调用,我们可采用vue的mixin的用法: 具体使用如下: 我们需要在main.js中引入mixins文件夹下的 ...

  10. 20165210 Java第三次实验报告

    20165210 实验二 敏捷开发与XP实践 一.敏捷开发与XP实践-1 实验要求: http://www.cnblogs.com/rocedu/p/4795776.html, Eclipse的内容替 ...