官方文档对 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. 大话设计模式之PHP篇 - 单例模式

    在编写PHP代码的时候,经常使用new关键字实例化一个对象,比如 <?php Class Database { } $db = new Database; 这是最常规的实例化操作方法,像数据库操 ...

  2. oracle创建表空间 扩展表空间文件 修改表空间自动增长

    1. 创建表空间 create tablespace SIRM2  datafile 'D:\oracle\product\10.2.0\oradata\orcl\SIRM2.dbf' size 10 ...

  3. Efficient Vector Representation for Documents through Corruption-by Minmin Chen阅读

    关键词: 词向量.文档向量.文档表示 地址:https://openreview.net/forum?id=B1Igu2ogg&noteId=B1Igu2ogg 首先,论文解决的是Word2V ...

  4. Java线程池ExecutorService和CountDownLatch的小例子

    import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java ...

  5. Shell脚本报错--syntax error near unexpected token for((i=0;i<$length;i++))

    现象: shell脚本使用Nodepad++进行本地编辑,在编辑后上传到linux机器进行执行时提示“syntax error near unexpected token for((i=0;i< ...

  6. Valid Parentheses有效括号匹配。利用栈。

    问题描述:给定一个字符串,其中只包含字符‘{’,    '}',    '[',    ']',   '(',    ')'确定如果输入字符串是有效的.括号必须以正确的顺序排列,“()”和“()[]{ ...

  7. Eureka、Ribbon、Feign常见问题及解决

    1.Eureka常见问 1.1.Eureka Enviroment 的配置 eureka.enviroment=product 参考 https://github.com/Netflix/eureka ...

  8. QT5 QtreeWidget 实现点击item事件以及右键菜单删除item 和 重命名item

    1.new 一个QTreeWidget 对象,并设置头标签,和根节点(个人程序需要) QTreeWidget* treeWidget = ui.treeWidget;//我已经在ui设计师中拖了一个Q ...

  9. C#中List<object>.Clear()方法和实例化new List<object>()操作的结果分析

    本文主要的目的是想简单的探讨一下C#中List针对内存的操作过程,以便以后遇到该种情况可以避免走进误区,内容非常简单,只是在此作为记录.能帮到人最好,帮不到就当给自己提个醒.C#将复杂的指针操作全都隐 ...

  10. 时间服务器: NTP 服务器及客户端搭建

    时间服务器: NTP 服务器及客户端搭建 一. NTP 服务器的安装与设定 1. NTP 服务器的安装与设定前言 2. 所需软件与软件结构 3. 主要配置文件 ntp.conf 的处理 4. NTP ...