今天要分享的Java集合是Map,主要是针对它的常见实现类HashMap进行讲解(jdk1.8)java
什么是Map核心方法源码剖析1.文档注释2.成员变量3.构造方法4.put()5.get()node
Map是非线性数据结构的主要实现,用来存放一组键-值型数据,咱们称之为散列表。在其余语言中,也被称为字典。
HashMap是一个很是重要的数据结构,在咱们的项目中有大量的运用,或充当参数,或用于缓存。而在面试中,HashMap也几乎成为了一个“必考项”,因此今天咱们就从源码的角度,对这种数据结构进行剖析。
首先咱们先就使用上,给出几个先入为主的概念。有了概念,再去看源码就会容易不少。
HashMap的查询速度是O(1),它为何会这么快呢?由于散列表利用的是数组支持按下标随机访问的特性,因此散列表实际上是对数组的一种扩充,是由数组演化而来的。
咱们来举一个例子,A班一共有64个学生,学号是惟一的9位数。在一场期末考试中,若是咱们想知道一个学生的成绩,那么就能够经过学号来定位学生,而后获取全部的考试信息。为了便于存储和查询,咱们将学生的学号,经过编码映射到下标从1-63的数组中。web
这一部分,选取了HashMap的一些核心内容进行讲解。分别是:文档注释,成员变量,构造方法,put()、hash()、get()、remove()。面试
permits null values and the null key
容许存储null值和null键数组
The HashMap class is roughly equivalent to Hashtable, except that it is unsynchronized and permits nulls.
HashMap近似于Hashtable,除了它不一样步而且容许null值缓存
This class makes no guarantees as to the order of the map
这个类存储数据是无序的数据结构
An instance of HashMap has two parameters that affect its performance: initial capacity and load factor
一个散列表有两个影响其性能的参数:初始值和负载因子app
so that the hash table has approximately twice the number of buckets
每次扩容2倍函数
1// 默认容量
2static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
3
4// 最大容量
5static final int MAXIMUM_CAPACITY = 1 << 30;
6
7// 负载因子:已用空间 / 总空间,负载因子平衡了时间和空间成本,若是负载因子太高,致使空间成本减小,查找成本增长,反之亦然。
8static final float DEFAULT_LOAD_FACTOR = 0.75f;
9
10// 一个哈系桶存储的元素超过8,就会将链表转换成红黑二叉树
11static final int TREEIFY_THRESHOLD = 8;
12
13// 一个哈希桶存储的元素小于6,而且是红黑二叉树存储,那么此时就会将红黑二叉树从新转换成链表
14static final int UNTREEIFY_THRESHOLD = 6;
15
16// 数据量阈值,数据量只有大于这一个值,才会发生树化
17static final int MIN_TREEIFY_CAPACITY = 64;
HashMap的构造方法有4种,咱们通常不会修改它的负载因子,经常使用的构造方法只有无参构造和传入初始值的构造方法。性能
1HashMap()
2HashMap(int initialCapacity)
3HashMap(int initialCapacity, float loadFactor)
4HashMap(Map<? extends K,? extends V> m)
put中有一个核心的方法,hash(),即散列方法
1public V put(K key, V value) {
2 return putVal(hash(key), key, value, false, true);
3}
4
5static final int hash(Object key) {
6 int h;
7 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
8}
从hash方法中能够看出,若是传入的key是null,就会固定的返回0位置。若是传入的key不是null,那么就会取key的hashCode方法的返回值,记作h,返回h与h高16位的异或值。
hash()方法的这段代码,又被称为扰动函数,咱们能够思考一下,h的值是一个int,它的范围是[-2147483648, 2147483647],大概包含了40亿的映射空间。若是直接存到内存中,确定是存不下的,并且HashMap的初始值只有16,那么咱们就须要将h映射到这16个哈希桶中,能够直接取模,可是这样的效率不是很高,因此这里jdk使用了位与的方法,代码抽象以下:
1private int getIndex(int length, int h){
2 return h & (length - 1);
3}
这里能够解释一下,为何HashMap要求初始值是2的整次幂?,这样length - 1正好至关于一个低位掩码,只截取了低位的值.
1final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
2 Node<K,V>[] tab;
3 Node<K,V> p;
4 int n, i;
5 // 当散列表为null的时候,调用resize()进行初始化
6 if ((tab = table) == null || (n = tab.length) == 0)
7 n = (tab = resize()).length;
8 // 若是没有发生哈希碰撞,直接将元素存进哈希桶
9 if ((p = tab[i = (n - 1) & hash]) == null)
10 tab[i] = newNode(hash, key, value, null);
11 else {
12 // 若是发生了哈希碰撞
13 Node<K,V> e; K k;
14 if (p.hash == hash &&
15 ((k = p.key) == key || (key != null && key.equals(k))))
16 // 记录要插入的元素
17 e = p;
18 else if (p instanceof TreeNode)
19 // 若是是树结构,就调用树的插入方法
20 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
21 else {
22 // 若是是链表结构,就调用链表的插入方法
23 for (int binCount = 0; ; ++binCount) {
24 if ((e = p.next) == null) {
25 p.next = newNode(hash, key, value, null);
26 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
27 treeifyBin(tab, hash);
28 break;
29 }
30 if (e.hash == hash &&
31 ((k = e.key) == key || (key != null && key.equals(k))))
32 break;
33 p = e;
34 }
35 }
36 if (e != null) { // 覆盖元素
37 V oldValue = e.value;
38 if (!onlyIfAbsent || oldValue == null)
39 e.value = value;
40 afterNodeAccess(e);
41 return oldValue;
42 }
43 }
44 ++modCount;
45 if (++size > threshold)
46 resize();
47 afterNodeInsertion(evict);
48 return null;
49}
这里的扩容方法是resize(),每次扩容2倍,采用的也是数据搬运的方式,因此咱们要尽量的去避免HashMap的扩容。
1public V get(Object key) {
2 Node<K,V> e;
3 return (e = getNode(hash(key), key)) == null ? null : e.value;
4}
5
6final Node<K,V> getNode(int hash, Object key) {
7 Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
8 if ((tab = table) != null && (n = tab.length) > 0 &&
9 (first = tab[(n - 1) & hash]) != null) {
10 // 若是在桶的首位就能够找到,那么就直接返回(提高效率,哈希冲突并不那么容易出现)
11 if (first.hash == hash && // always check first node
12 ((k = first.key) == key || (key != null && key.equals(k))))
13 return first;
14 if ((e = first.next) != null) {
15 // 根据节点类型,在红黑二叉树或者链表中查询数据
16 if (first instanceof TreeNode)
17 return ((TreeNode<K,V>)first).getTreeNode(hash, key);
18 do {
19 if (e.hash == hash &&
20 ((k = e.key) == key || (key != null && key.equals(k))))
21 return e;
22 } while ((e = e.next) != null);
23 }
24 }
25 return null;
26}
get()的思想和put()相似,根据不一样的Node类型,进行查找
最后,期待您的订阅和点赞,专栏每周都会更新,但愿能够和您一块儿进步,同时也期待您的批评与指正!