title: HashMap
date: 2018-09-24 11:33:31
tags:HashMap
前一部分是拿的大佬的在这声明,后部分是自己看课程总结的
在这里贴上大佬的地址:https://www.jianshu.com/p/52066d6b7717
1.HashMap的实现原理
1,HashMap概述
HashMap 是基于哈希表的 Map 接口的非同步实现。此实现提供所有可选的映射操作, 并允许使用 null 值和 null 键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变
2,HashMap的数据结构
-
类图结构
1.Map 接口: 定义将键值映射到值的对象,Map规定不能包含重复的键值,每个键最多可以映射一个值,这个接口是用来替换Dictionary类。
2.AbstractMap 类: 提供了一个Map骨架的实现,尽量减少了实现Map接口所需要的工作量
3.Cloneable 接口: 实现了该接口的类可以显示的调用Object.clone()方法,合法的对该类实例进行字段复制,如果没有实现Cloneable接口的实例上调用Obejct.clone()方法,会抛出CloneNotSupportException异常。正常情况下,实现了Cloneable接口的类会以公共方法重写Object.clone()
4.Serializable 接口: 实现了该接口标示了类可以被序列化和反序列化
-
构造方法:
1.public HashMap(int initialCapacity, float loadFactor):需要传入自定义的initialCapacity(初始化容量),loadFactor(加载因子)
2.public HashMap(int initialCapacity):需要传入自定义的initialCapacity(初始化容量),实际在平时的使用过程中如果可以大概知道数据量,建议使用这种构造方法,原因是指定了HashMap的容量之后,可以避免没必要的扩容操作,从而减少了浪费。
3.public HashMap():默认的构造方法,按照初始值创建HashMap
4.public HashMap(Map<? extends K, ? extends V> m):需要传入一个Map集合
-
在 java 编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引 用),所有的数据结构都可以用这两个基本结构来构造的,HashMap 也不例外。HashMap 实际上是一个“链表散列”的数据结构,即数组和链表的结合体
3.原理
-
内部结构
上图所示,是HashMap内部实现的结构图,正如介绍的其内部是个Node<K,V>类型的数组。数组中保存的有两种数据结构,第一种是链表,第二种是树形结构(使用的是红黑树)
从上图中可以看出,HashMap 底层就是一个数组结构,数组中的每一项又是一个链表,当新建一个 HashMap 的时候,就会初始化一个数组。
-
ArrayList(查询快)
-
数组实现
-
-
LinkedList(插入和删除快)
-
链表
-
-
Hash函数计算得到一个理想的——插入位置
-
源码猜测
-
HashMap中的key value存储单元是什么
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next;
-
数组该如何表示
Node<K,v>[]
-
数组的大小
Defaultsize=初始大小
Maximumsiez=上限大小
-
数组大小的解决方案
final Node<K,V>[] resize() 扩容
扩容依据 负载因子
数组目前使用的大小 size
-
链表的长度限制
在jdk1.8 如果链表的长度大于某个值,就将其结构改为红黑树
-
新来的Node节点放在哪里
存在哪里需要有一个约束,就需要计算得来
Key value
利用这个key进行计算,计算这个Node到底放在哪里
Key.hashcode ——>int 类型的值,得到了位置
49 这就是个合适的位置(49越界了)
Hash函数 hash(key) ——> 得到理想的值
-
-
源码分析
-
put——putVal()
-
putVal()
hash(Key)
-
因为要知道这个Key value 到底在HashMap结构的哪里
Hash函数
(h = key.hashCode()) ^ (h >>> 16);
-
-
充分的将int类型的32为数全部应用起来
高16位6^ 低16位
DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
1<<4 位与运算 计算机识别更快
Node<K,V>[] tab; Node<K,V> p; int n, i;
resize() 一个是初始化数组大小 扩容
n 是记录数组大小的一个变量
int threshold 容量X负载因子 16*0.75 12
tab[i = (n - 1) & hash] //tab[数值]确保数组不会越界
数组的散列性就大,碰撞概率就低
数组的大小一定要是2的n次幂
if ((p = tab[i = (n - 1) & hash]) == null)
如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到 此数组中的该位置上。
put方法:put 方法比较经常使用的方法,主要功能是为HashMap对象添加一个Node 节点,如果Node存在则更新Node里面的内容
-
1)根据key计算当前Node的hash值,用于定位对象在HashMap数组的哪个节点。
2)判断table有没有初始化,如果没有初始化,则调用resize()方法为table初始化容量,以及threshold的值。
3)根据hash值定位该key 对应的数组索引,如果对应的数组索引位置无值,则调用newNode()方法,为该索引创建Node节点
4)如果根据hash值定位的数组索引有Node,并且Node中的key和需要新增的key相等,则将对应的value值更新。
5)如果在已有的table中根据hash找到Node,其中Node中的hash值和新增的hash相等,但是key值不相等的,那么创建新的Node,放到当前已存在的Node的链表尾部。
如果当前Node的长度大于8,则调用treeifyBin()方法扩大table数组的容量,或者将当前索引的所有Node节点变成TreeNode节点,变成TreeNode节点的原因是由于TreeNode节点组成的链表索引元素会快很多。
5)将当前的key-value 数量标识size自增,然后和threshold对比,如果大于threshold的值,则调用resize()方法,扩大当前HashMap对象的存储容量。
6)返回oldValue或者null。
4.HashMap的存取实现
-
存储:当程序试图将一个 key-value 对放入 HashMap 中时,程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储位置:如果两 个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。
-
读取:从 HashMap 中 get 元素时,首先计算 key 的 hashCode,找到数组中对应 位置的某一元素,然后通过 key 的 equals 方法在对应位置的链表中找到需要的元素
-
小结:
-
HashMap 在底层将 key-value 当成一个整体进行处理,这个整体 就是一个 Entry 对象
-
HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当 需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals 方法决定其在该数组位置上的链表中的存储位置;当需要取出一个 Entry 时,也会根据 hash 算法找到其在数组中的存储位置,再根据 equals 方法从该位置上的链表中取出该 Entry
-
2.HashMap的一些说法
-
HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。HashMap的底层结构是一个数组,数组中的每一项是一条链表。
-
HashMap的实例有俩个参数影响其性能: “初始容量” 和 装载因子。
-
HashMap中的key-value都是存储在Entry中的
-
HashMap实现不同步,线程不安全
-
HashMap可以存null键和null值,不保证元素的顺序恒久不变,它的底层使用的是数组和链表,通过hashCode()方法和equals方法保证键的唯一性
-
解决冲突主要有三种方法:定址法,拉链法,再散列法。HashMap是采用拉链法解决哈希冲突的。 注: 链表法是将相同hash值的对象组成一个链表放在hash值对应的槽位; 用开放定址法解决冲突的做法是:当冲突发生时,使用某种探查(亦称探测)技术在散列表中形成一个探查(测)序列。 沿此序列逐个单元地查找,直到找到给定 的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。 拉链法解决冲突的做法是: 将所有关键字为同义词的结点链接在同一个单链表中 。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数 组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于1,但一般均取α≤1。拉链法适合未规定元素的大小