HashMap大厂面试频率高,源码太长了不想看?看完这一篇就完事了!
HashMap源码分析系列 – HashMap的继承体系,内部类,成员变量
从今天起,争取一周之内整理完HashMap有关的各种问题。网上有很多HashMap的源码分析的帖子,我看了一些。当然也有写的非常好的,不过大多的源码分析帖企图一篇文章就把问题讲完,有时候看起来就很混乱。而且贴了较多的源码。这里我准备分为6个部分,分别上传五篇篇博文,希望用更清晰的方式,来总结HashMap的相关问题。
六个部分
- HashMap的继承体系,HashMap的内部类,成员变量
- HashMap的常见方法的实现流程
- HashMap的一些特定算法,常量的分析
- HashMap的线程安全问题
- HashMap的线程安全问题解决方案
- HashMap1.8和1.7的区别
HashMap的继承体系
HashMap是AbstractMap的子类,是Map的实现类,同时实现了Cloneable接口和Serializable接口。
一般我们给HashMap一个专业的描述:HashMap是Map接口的非同步实现类。
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
我们看一下Map接口的方法(常见方法)
增删改相关
返回值 | 方法名 | 描述 |
---|---|---|
V |
put(K key, V value) |
将指定的值与该映射中的指定键相关联(可选操作)。 |
void |
putAll(Map<? extends K,? extends V> m) |
将指定地图的所有映射复制到此映射(可选操作)。 |
V |
remove(Object key) |
如果存在(从可选的操作),从该地图中删除一个键的映射。 |
default boolean |
remove(Object key, Object value) |
仅当指定的密钥当前映射到指定的值时删除该条目。 |
default V |
replace(K key, V value) |
只有当目标映射到某个值时,才能替换指定键的条目。 |
default boolean |
replace(K key, V oldValue, V newValue) |
仅当当前映射到指定的值时,才能替换指定键的条目。 |
default void |
replaceAll(BiFunction<? super K,? super V,? extends V> function) |
将每个条目的值替换为对该条目调用给定函数的结果,直到所有条目都被处理或该函数抛出异常。 |
查询相关
返回值 | 方法名 | 描述 |
---|---|---|
int |
hashCode() |
返回此地图的哈希码值。 |
int |
size() |
返回此地图中键值映射的数量。 |
Collection<V> |
values() |
返回此地图中包含的值的Collection视图。 |
boolean |
isEmpty() |
如果此地图不包含键值映射,则返回 true。 |
V |
get(Object key) |
返回到指定键所映射的值,或 null如果此映射包含该键的映射。 |
boolean |
containsKey(Object key) |
如果此映射包含指定键的映射,则返回 true。 |
boolean |
containsValue(Object value) |
如果此地图将一个或多个键映射到指定的值,则返回 true。 |
比较相关
返回值 | 方法名 | 描述 |
---|---|---|
boolean |
equals(Object o) |
将指定的对象与此映射进行比较以获得相等性。 |
转换相关
返回值 | 方法名 | 描述 |
---|---|---|
Set<K> |
keySet() |
返回此地图中包含的键的Set视图。 |
Set<Map.Entry<K,V>> |
entrySet() |
返回此地图中包含的映射的Set视图。 |
HashMap的成员属性
常量
//默认初始化Node数组容量16 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //最大的数组容量 static final int MAXIMUM_CAPACITY = 1 << 30; //默认负载因子0.75 static final float DEFAULT_LOAD_FACTOR = 0.75f; //由链表转红黑树的临界值 static final int TREEIFY_THRESHOLD = 8; //由红黑树转链表的临界值 static final int UNTREEIFY_THRESHOLD = 6; //桶转化为树形结构的最小容量 static final int MIN_TREEIFY_CAPACITY = 64;
普通变量
trainsient关键字修饰的属性,当Map序列化时,是不参与的。
//HashMap结构修改的次数,结构修改是指更改HashMap中的映射数或以其他方式修改其内部结构(例如,rehash的修改)。该字段用于在Collection-views上快速生成迭代器。 transient int modCount; //Node数组下一次扩容的临界值,第一次为16*0.75=12(容量*负载因子) int threshold; //负载因子 final float loadFactor; //map中包含的键值对的数量 transient int size;
复杂变量
Node<K,V>是HashMap的内部类,实现Map.Entry<K,V>接口,HashMap的哈希桶数组中存放的键值对对象就是Node<K,V>。类中维护了一个next指针指向链表中的下一个元素。值得注意的是,当链表中的元素数量超过TREEIFY_THRESHOLD后会HashMap会将链表转换为红黑树,此时该下标的元素将成为TreeNode<K,V>,继承于LinkedHashMap.Entry<K,V>,而LinkedHashMap.Entry<K,V>是Node<K,V>的子类,因此HashMap的底层数组数据类型即为Node<K,V>。
//表数据,即Node键值对数组,Node是单向链表,它实现了Map.Entry接口,长度是2的幂次倍 transient Node<K,V>[] table; //存放具体元素的集,可用于遍历map集合 transient Set<Map.Entry<K,V>> entrySet;
HashMap的构造方法
HashMap提供了四种构造方式
- 默认构造方式
- 自定义容量构造方式
- 自定义容量,加载因子构造方式
- 传入Map构造
默认构造方法,由于HashMap是懒加载,真正的数组初始化是在第一次添加元素的时候。
//默认构造方法 public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted }
给定加载因子和初始容量
//初始化容量以及负载因子 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); //初始化负载因子 this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); }
自定义初始容量
//自定义初始化容量 public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); }
给定一个Map容器。
//把另一个Map的值映射到当前新的Map中 public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); }
HashMap静态内部类
Node
static class Node<K,V> implements Map.Entry<K,V> { // 哈希值,HashMap根据该值确定记录的位置 final int hash; // node的key final K key; // node的value 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; } // 返回 node 对应的键 public final K getKey() { return key; } // 返回 node 对应的值 public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } //作用:判断2个Entry是否相等,必须key和value都相等,才返回true public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }
TreeNode
/** * 红黑树节点 实现类:继承自LinkedHashMap.Entry<K,V>类 */ static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { // 属性 = 父节点、左子树、右子树、删除辅助节点 + 颜色 TreeNode<K,V> parent; TreeNode<K,V> left; TreeNode<K,V> right; TreeNode<K,V> prev; boolean red; // 构造函数 TreeNode(int hash, K key, V val, Node<K,V> next) { super(hash, key, val, next); } // 返回当前节点的根节点 final TreeNode<K,V> root() { for (TreeNode<K,V> r = this, p;;) { if ((p = r.parent) == null) return r; r = p; } } }
HashMap的底层实现
Jdk1.8 数组+链表+红黑树(也就是Node[],Node,TreeNode)
Jdk1.7 数组+链表 (Entry[],entry)
总结
本篇主要是将HashMap的继承体系,以及结构,进行讲解。
- HashMap源码分析 —— 一篇文章搞定HashMap面试
- 一篇文章搞定HashMap面试——HashMap源码分析
- HashMap源码分析(二):看完彻底了解HashMap
- BAT面试必问HashMap源码分析
- Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结
- 面试大厂被问MyBatis哑口无言?MyBatis源码笔记助你吊打面试官
- 面试专辑——HashMap源码剖析
- 关于HashMap面试详解,看完再也不怕面试问HashMap了
- 面试必考之HashMap源码分析与实现
- HashMap 底层实现原理,看完面试不再懵逼。
- BAT面试必问HashMap源码分析
- 面试必会之HashMap源码分析
- 一篇文章带你吃透 hashmap(面试指南升级版)
- BAT面试必问HashMap源码分析
- 害怕面试被问HashMap?这一篇就搞定了!
- Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结
- HashMap源码分析(学HashMap源码看这一篇就够了)
- 2020年去一线大厂面试先过SSM框架源码这一关!
- 面试必备:HashMap源码解析(JDK8)
- 听说你看过HashMap源码,来面试下这几个问题