JDK 1.6 HashMap 源码分析
前言
前段时间研究了一下JDK 1.6 的 HashMap
源码,把部份重要的方法分析一下,当然HashMap
中还有一些值得研究得就交给读者了,如有不正确之处还望留言指正。
准备
需要熟悉数组和链表这两个基本数据结构。如果对链表不太熟悉的话,可以来几道leetcode上的相关的链表算法题。熟悉后看 HashMap
就会快很多了。
基本原理:HashMap
中的基本数据结构是数组加链表。table
是一个固定的数组。 数组里面的每个坑里面填的是一个叫Entry
类。 其实就是一个固定的Entry
数组。如果同一个坑里面存在两个不同的数据,那么两个数据就以链表的形式连接起来。最新的在最前面,原因是认为最新的容易经常被访问。
构造函数
基本原理知道了。现在直接研究带参数的构造函数就可以了,其他的构造函数就是调用该方法。
public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " +loadFactor);// Find a power of 2 >= initialCapacityint capacity = 1;while (capacity < initialCapacity)capacity <<= 1;this.loadFactor = loadFactor;threshold = (int)(capacity * loadFactor);table = new Entry[capacity];init();}
MAXIMUM_CAPACITY = 1 << 30
2的30次方1073741824,也就是HashMap
中table
数组的大小不能超过该数字。 从上面代码可以看出来table
的坑位只能是2的幂次方。如果你传入的initialCapacity
为7 那么其实table
的大小为8; 也就是table
的大小为传入进来的initialCapacity
的数值大于该大小的2的幂次方。threshold
为他的阈值也就是 HashMap 的真正大小不能超过该值,超过了就进行扩容操作。 如果table
数组的大小为16时。用它默认的扩容因子0.75f。那么他的阈值就是12。 也就是 table
数据,数组中的加上链表的不能超过12。
我们看看第二个构造函数。参数为一个Map
我这里顺便把HashMap
中的嵌套类Entry
类说一下。可以自己再源码上观看。
public HashMap(Map<? extends K, ? extends V> m) {// 对比该map的size大小,新的map最新的容量为16this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);// 创建所有mapputAllForCreate(m);}private void putAllForCreate(Map<? extends K, ? extends V> m) {// 对每一个Entry进行迭代for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {Map.Entry<? extends K, ? extends V> e = i.next();//创建数据赋值putForCreate(e.getKey(), e.getValue());}}private void putForCreate(K key, V value) {int hash = (key == null) ? 0 : hash(key.hashCode());// 计算table中的位置int i = indexFor(hash, table.length);/*** Look for preexisting entry for key. This will never happen for* clone or deserialize. It will only happen for construction if the* input Map is a sorted map whose ordering is inconsistent w/ equals.*/for (Entry<K,V> e = table[i]; e != null; e = e.next) {Object k;// 相同的值覆盖。if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k)))) {e.value = value;return;}}// 创建EntrycreateEntry(hash, key, value, i);}void createEntry(int hash, K key, V value, int bucketIndex) {Entry<K,V> e = table[bucketIndex];// 头节点插入table[bucketIndex] = new Entry<K,V>(hash, key, value, e);size++;}// 嵌套类 和HashMap类没关系 独立存在 默认权限 只能本包访问 也就是Java.util下的包访问 HashHap中并没有提供 Map.Entry<K,V>这样的返回对象出去。有的只是一个 Set<Map.Entry<K,V>>
//一个代理类。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;}public final K getKey() {return key;}public final V getValue() {return value;}public final V setValue(V newValue) {V oldValue = value;value = newValue;return oldValue;}public final boolean equals(Object o) {if (!(o instanceof Map.Entry))return false;Map.Entry e = (Map.Entry)o;Object k1 = getKey();Object k2 = e.getKey();if (k1 == k2 || (k1 != null && k1.equals(k2))) {Object v1 = getValue();Object v2 = e.getValue();if (v1 == v2 || (v1 != null && v1.equals(v2)))return true;}return false;}public final int hashCode() {return (key==null ? 0 : key.hashCode()) ^(value==null ? 0 : value.hashCode());}public final String toString() {return getKey() + "=" + getValue();}/*** This method is invoked whenever the value in an entry is* overwritten by an invocation of put(k,v) for a key k that's already* in the HashMap.*/void recordAccess(HashMap<K,V> m) {}/*** This method is invoked whenever the entry is* removed from the table.*/void recordRemoval(HashMap<K,V> m) {}}
put方法
为什么要从put
方法研究起呢。因为HashMap
中最常用得就是put
方法。而且里面还涉及到扩容操作。如果把这些看懂了还是会很舒服得。
public V put(K key, V value) {if (key == null)// 如果key为null的话 直接添加到table[0]的位置 for 循环 table[0]上的元素。如果有元素的话 查看该元素的key是不是null 如果是的话 就更新value值,直到table[0]这个链表结束。 如果结束后还是没有的话,就把为null的key 对应的value 头插法 插入头部。 可以查看 putForNullKey(value) 方法。return putForNullKey(value);// 计算Hash值 int hash = hash(key.hashCode());// 取key的Hash值得 二进制数得后几位。 如果key得hash为1101011 。而table这个数组得大小一直都是2的幂次方。 indexFor()方法做的事 key的hash与table.length-1做&运算。假如table数组的大小为16,也就是 11011011 & 1111 会等于 1011 。这个方法的意义也就是只要你得Hash值是随机的,碰撞性低,那么你在table中位置也就是 碰撞低的。int i = indexFor(hash, table.length);// 查询该table[i] 位置上的链表。for (Entry<K,V> e = table[i]; e != null; e = e.next) {Object k;// 如果 key相等 那么就更新 否则 下一位。。。。 直至结束。if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}// 修改次数加一modCount++;// 头插法 并看size是都大于阈值了,如果大于就要扩容了。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);if (size++ >= threshold)//扩容操作 2倍扩容resize(2 * table.length);}// 扩容方法 参数为扩容大小void resize(int newCapacity) {Entry[] oldTable = table;int oldCapacity = oldTable.length;if (oldCapacity == MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return;}// 创建一个新得数组 名字叫做newTable length为 newCapacityEntry[] newTable = new Entry[newCapacity];// 扩容操作transfer(newTable);// 重新赋值 table = newTable;// 阈值threshold = (int)(newCapacity * loadFactor);}// 扩容操作void transfer(Entry[] newTable) {// 将原先的table数组 赋值给 srcEntry[] src = table;int newCapacity = newTable.length;// 逐个操作 从 src[0] 位置上的Entry 开始for (int j = 0; j < src.length; j++) {// 将src[j]的值给 e变量。Entry<K,V> e = src[j];// 对这个e 链表进行往下操作if (e != null) {// 清空src[j] = null;do {//e 的下面一位 其实就是 next 后移 (这里如果两个线程同时在这里操作的话,A线程在这里执行这条语句后挂起的话,B线程完成扩容操作后,A线程再唤醒时,有可能发生循环链表。然后使用get方法的时候,导致死循环,cpu利用100%)Entry<K,V> next = e.next;// 对e 重新定位。int i = indexFor(e.hash, newCapacity);// 将e.next 从e 断开 并把e.next的值 指到 newTable[i]的值e.next = newTable[i];// 将 e 赋值给 newTable[i] newTable[i] = e;// e 往后移e = next;} while (e != null);}}}
舒服了舒服了。 如果想看怎么发生死循环的可以看小灰的文章 高并发下的HashMap 。
get方法
get方法相对而言就比较简单了。
public V get(Object key) {if (key == null)// 直接查询table[0] 上链表key为 null的值return getForNullKey();// 定位table上的位置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;}
remove方法
remove方法相对而言,只要你会链表的删除操作,就很好理解了。如果有不明白的可以。将链表这个数据结构好好学习一下。
public V remove(Object key) {// 移除元素方法Entry<K,V> e = removeEntryForKey(key);return (e == null ? null : e.value);}// 这里其实就是链表的删除操作 。final Entry<K,V> removeEntryForKey(Object key) {int hash = (key == null) ? 0 : hash(key.hashCode());// 定位位置int i = indexFor(hash, table.length);// 将table[i] 这个链表赋值给prev Entry<K,V> prev = table[i];// prev 赋值给 eEntry<K,V> e = prev;while (e != null) {// 下面一位Entry<K,V> next = e.next;Object k;// key是否相等if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k)))) {modCount++;size--;// 如果要删除的时table[i]的头部数据 if (prev == e)// table[i] 等于next 删除头部table[i] = next;else// 否则 删除这个 prev.next = next;e.recordRemoval(this);return e;}prev = e;e = next;}return e;}
总结
HashMap
中的学问,远不止这些。 其中还涉及到设计模式,迭代器等等。上面这些只是常用的。个人非常推荐把数组和链表这个两个非常基础的数据结构好好练习一下。虽然说早就把JDK 1.6的HashMap
源码看了一下,顺便把 ConcurrentHashMap
中的一些源码也看了。但是写下来的时候,再看一遍,印象果然深刻多了。先把1.6的看了,在看1.8的吧。
转载于:https://www.cnblogs.com/Krloypower/p/10675686.html
JDK 1.6 HashMap 源码分析相关推荐
- 在参考了众多博客之后,我写出了多达三万字的HashMap源码分析,比我本科毕业论文都要精彩
HashMap源码分析 以下代码都是基于java8的版本 HashMap简介 源码: public class HashMap<K,V> extends AbstractMap<K, ...
- hashmap源码分析及常用方法测试_一点课堂(多岸学院)
HashMap 简介 底层数据结构分析 JDK1.8之前 JDK1.8之后 HashMap源码分析 构造方法 put方法 get方法 resize方法 HashMap常用方法测试 感谢 changfu ...
- HashMap 源码分析与常见面试题
文章目录 HashMap 源码分析 jdk 1.7 内部常量 静态内部类 Holder 类 构造方法 put 过程 put 整体流程图 jdk 1.8 增加的常量 Node 类 Hash 值计算的变化 ...
- 【Java源码分析】Java8的HashMap源码分析
Java8中的HashMap源码分析 源码分析 HashMap的定义 字段属性 构造函数 hash函数 comparableClassFor,compareComparables函数 tableSiz ...
- Java类集框架 —— HashMap源码分析
HashMap是基于Map的键值对映射表,底层是通过数组.链表.红黑树(JDK1.8加入)来实现的. HashMap结构 HashMap中存储元素,是将key和value封装成了一个Node,先以一个 ...
- 查询已有链表的hashmap_源码分析系列1:HashMap源码分析(基于JDK1.8)
1.HashMap的底层实现图示 如上图所示: HashMap底层是由 数组+(链表)=(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...
- 源码分析系列1:HashMap源码分析(基于JDK1.8)
1.HashMap的底层实现图示 如上图所示: HashMap底层是由 数组+(链表)+(红黑树) 组成,每个存储在HashMap中的键值对都存放在一个Node节点之中,其中包含了Key-Value ...
- HashMap源码分析(转载)
一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了不同步和允许使用 null 之外,HashMap ...
- Map接口总结与HashMap源码分析
Map接口 1.Map,用于保存K-V(双列元素) 2.Map中的Key Value可以是任意引用分类型的数据,会封装到HashMap的Node对象中 3.Map的key不允许重复.原因和HashSe ...
最新文章
- 未来网络经济的99个趋势报告
- Spring Boot 项目设置网站图标
- 2012届华为校园招聘机试题
- 给我的宏基上网本用U盘装XP系统
- 微星z370黑苹果_记录一下装了第二台黑苹果(Z370 + High Sierra)
- React引入,运行
- Java进阶:java字符串定位语句
- C/C++ _wcslwr_s 函数 – unicode 字符串大写转小写 - C语言零基础入门教程
- 新独立版抖音口红机全修复版本附视频教程
- 卷积神经网络流程图_基于卷积神经网络的叶片气孔自动计数方法
- PHP7.0,PHP7.1.x新特性
- win8的cmd运行命令大全
- Elasticsearch——》测试:es近义词(同义词)配置
- Xcode隐藏SDK C、C++、Objective-C符号
- 数据中心机房有哪些等级,国内外的评级标准?
- 自动铅笔的简笔画怎么画,自动化简笔画图片大全
- python的日志模块:logging;django的日志系统;django日志输出时间修改
- 马云退休启示录:第一代互联网创始人老去,谁来接棒?
- 进化算法的比较(GA,PSO,DE)及其优化算法包的使用
- 如何将AE动画导出为json文件