Open bigwolftime opened 3 years ago
https://index1024.gitee.io/xblog/hashMapOfJDK8/
一. 概要
JDK8 版本的 HashMap 相对于 JDK7 有了一些优化, 比较明显的就是引入了红黑树的数据结构, 提升查询效率.
HashMap:
日常使用频次较高的结构. 非线程安全, 如果有线程安全的需求, 可以考虑使用 Collections.synchronizedMap() 或者 ConcurrentHashMap. 允许 key 为 null(最多只能有一个), 允许 value 为 null. 键值对的插入顺序与遍历顺序不固定.
HashTable:
遗留类,很多映射的常用功能与 HashMap 类似,不同的是它承自 Dictionary 类,并且是线程安全的. 任一时间只有一个线程能写 Hashtable,并发性不如 ConcurrentHashMap,因为 ConcurrentHashMap 引入了分段锁.
LinkedHashMap:
属于 HashMap 的子类, 底层使用链表实现, 可保存键值对的插入顺序.
TreeMap:
实现 SortedMap 接口, 默认根据”键”进行排序(也可自定义比较器).
二. HashMap 的实现
图中有一个数组 table, 当键值对插入时, 会根据 key 进行 hashCode() 计算, 然后一系列操作后确定数组的索引位置(下面介绍), 然后键值对会封装成 Node 对象存储到 table 数组中. Node 的代码如下:
// Node 是 HashMap 内部类, 实现 Entry 接口 static class Node<K, V> implements Entry<K, V> { final int hash; final K key; V value;
/** * 下一个元素的地址. 用于解决 hash 冲突 * hash 冲突的解决办法一般有: 链地址法和开放地址法(我所见过的一些组件, hash 冲突使用的基本都是链地址, 开放地址法暂未遇到过) * * hash 冲突一般与这些因素有关: * 1. hash 函数: 一个好的 hash 函数, 基本可以保证数据均匀分布, 冲突最少, 以达到最好的性能; * 2. table 数组容量: 若容量较小, 即使使用优秀的 Hash 函数, 也会有大概率的冲突. */ HashMap.Node<K, V> next; Node(int hash, K key, V value, HashMap.Node<K, V> next) {} public final K getKey() {} public final V getValue() {} public final String toString() {} public final int hashCode() { return Objects.hashCode(this.key) ^ Objects.hashCode(this.value); } public final V setValue(V newValue) {} public final boolean equals(Object o) { if (o == this) { return true; } else { if (o instanceof Entry) { Entry<?, ?> e = (Entry)o; if (Objects.equals(this.key, e.getKey()) && Objects.equals(this.value, e.getValue())) { return true; } } return false; } } }
// 默认的 table 数组容量 static final int DEFAULT_INITIAL_CAPACITY = 16; // 默认的负载因子值 static final float DEFAULT_LOAD_FACTOR = 0.75F;
/**
// 实际的 Node 个数(注意和 table.length, threshold 区别) transient int size; // 记录 HashMap 内部结构发生变化的次数, 用于迭代的快速失败. transient int modCount;
// 当前可容纳键值对的最大值 int threshold;
此外, 数组的长度也有要求, 需要保证为 table.length = 2 的 n 次幂, 采用这种设计主要是优化: 取模和扩容的过程.
对 map 中的 key 进行曾删改查, 第一步就是要根据 key 确定数组的哪个索引位置.
static final int hash(Object key) { int h; return key == null ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
// jdk1.7的源码,jdk1.8没有这个方法,但是实现原理一样的 static int indexFor(int h, int length) { /**
上面的 hash() 方法: (h = key.hashCode()) ^ (h >>> 16). 首先计算 key 的 hashCode(int_32), 然后和 hashCode 的高 16bit 异或计算, 当数组的长度较小, 也可保证高低位都参与到计算中, 同时不会有大的开销. 如下图:
参考
Java 8系列之重新认识HashMap 2016 为什么HashTable的桶会取一个素数 2013 教你初步了解红黑树 2010 JDK 笔记
https://index1024.gitee.io/xblog/hashMapOfJDK8/
一. 概要
JDK8 版本的 HashMap 相对于 JDK7 有了一些优化, 比较明显的就是引入了红黑树的数据结构, 提升查询效率.
HashMap:
日常使用频次较高的结构. 非线程安全, 如果有线程安全的需求, 可以考虑使用 Collections.synchronizedMap() 或者 ConcurrentHashMap. 允许 key 为 null(最多只能有一个), 允许 value 为 null. 键值对的插入顺序与遍历顺序不固定.
遗留类,很多映射的常用功能与 HashMap 类似,不同的是它承自 Dictionary 类,并且是线程安全的. 任一时间只有一个线程能写 Hashtable,并发性不如 ConcurrentHashMap,因为 ConcurrentHashMap 引入了分段锁.
属于 HashMap 的子类, 底层使用链表实现, 可保存键值对的插入顺序.
实现 SortedMap 接口, 默认根据”键”进行排序(也可自定义比较器).
二. HashMap 的实现
图中有一个数组 table, 当键值对插入时, 会根据 key 进行 hashCode() 计算, 然后一系列操作后确定数组的索引位置(下面介绍), 然后键值对会封装成 Node 对象存储到 table 数组中. Node 的代码如下:
// Node 是 HashMap 内部类, 实现 Entry 接口 static class Node<K, V> implements Entry<K, V> { final int hash; final K key; V value;
// 默认的 table 数组容量 static final int DEFAULT_INITIAL_CAPACITY = 16; // 默认的负载因子值 static final float DEFAULT_LOAD_FACTOR = 0.75F;
/**
// 实际的 Node 个数(注意和 table.length, threshold 区别) transient int size; // 记录 HashMap 内部结构发生变化的次数, 用于迭代的快速失败. transient int modCount;
// 当前可容纳键值对的最大值 int threshold;
/**
此外, 数组的长度也有要求, 需要保证为 table.length = 2 的 n 次幂, 采用这种设计主要是优化: 取模和扩容的过程.
对 map 中的 key 进行曾删改查, 第一步就是要根据 key 确定数组的哪个索引位置.
static final int hash(Object key) { int h; return key == null ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
// jdk1.7的源码,jdk1.8没有这个方法,但是实现原理一样的 static int indexFor(int h, int length) { /**
上面的 hash() 方法: (h = key.hashCode()) ^ (h >>> 16). 首先计算 key 的 hashCode(int_32), 然后和 hashCode 的高 16bit 异或计算, 当数组的长度较小, 也可保证高低位都参与到计算中, 同时不会有大的开销. 如下图:
参考
Java 8系列之重新认识HashMap 2016 为什么HashTable的桶会取一个素数 2013 教你初步了解红黑树 2010 JDK 笔记