HashMap的相关原理介绍
-
在JDK1.7中HashMap是由数组加链表实现的,此时的链表不同于LinkedList,LinkedLists是双向链表,HashMap中用到的是单向链表。而在1.7之后,则由数组加红黑树实现
-
官方测试JDK1.8的HashMap的性能比JDK1.7的HashMap性能提高了15%
-
根据上图可以看到:
(1)紫色表示的是数组,hash数组(桶),数组元素是每个链表的头结点。
(2)绿色的代表的是链表,用来解决hash冲突,不同的key映射到了数组的同一个索引处,则形成链表,也就是说key做hash取值,值为数组的下标,将key放到数组中,如果key的hash冲突,就存到数组为头结点的列表中。 -
map中的Entry是用来存储key-value键值对的
-
put和get方法
put方法的过程大概如下:
- 如果添加的key是null,则将该键值添加到数组索引是0的链表中,不一定是链表的首节点,因为有可能其他的key的hash值也是0。
- 如果添加的key不为null,则根据key计算数组索引的位置,数组索引处存在链表,则遍历该链表,如果发现key已经存在,那么将新的value值替换旧的value值,key不存在就插入到列表中。如果数组索引处不存在链表,就将该key-value添加到这里,成为链表的第一个元素
get方法的大概过程
- 如果key是null,那么在数组索引table[0]处的链表中遍历查找key是null的value
- 如果key不是null,根据key找到数组索引位置处的链表,遍历查找key的value,找到返回的value,若没有找到则返回null
扩容机制
- 先看一个例子,创建一个HashMap,初始容量默认是16,负载银子是0.75,那么他什么时候会进行扩容操作呢?
- 来看下下面的公式:
- 实际容量 = 初始容量 * 负载因子
- 经过计算可以知道16*0.75 = 12, 也就是说当实际容量超过12的时候,这个HashMap就会进行扩容
- 初始容量:当我们构造一个hashMap的时候,初始容量设为不小于指定容量的2的次方的一个数,并且最大值不能超过2的30次方。(比如说,new HashMap(5),指定容量是5,那么实际上他的初始化容量是8,因为2^3=8 > 5)
- 负载因子:负载因子是哈希数组在其容量自动增加之前可以达到多满的一种尺度,当哈希数组中的条目数超出了加载因子与初始容量的乘积的时候,则要对该哈希数组进行扩容操作(即resize)。
- 负载因子的特点:
(1)负载因子越小,容易扩扩容,浪费空间,但是查找效率高
(2)负载因子越大,不容易扩容,对空间的利用更加充分,查找效率低(链表过长,不扩容会使其hashCode重复概率增大,导致链表增长)- 扩容的过程: HashMap在进行扩容的时候,新数组的容量是原来的二倍,由于容量发生了变化,原有的每个元素会进行重新计算数组索引的index值,再存放到扩容后的新的数组中去,这就是所谓的rehash
hashCode与equals
- hashCode相同,equals不一定相同
- hashCode不同,rquals一定不同
- equals相同,hashCode一定相同
- 可以这么理解,hashCode比较的是哈希数组的下标,equals比较的是该hash下的链表。如果hashCode相等,代表了链表的头结点相同,equals相等,代表头结点下链表的某一个具体的节点相等。
- 相当于是紫色比较hashCode,绿色比较的是equals
基于ArrayList手写HashMap
- 不推荐使用ArrayList实现HashMap,导致HashMap效率低,查询的时候会从头到尾遍历ArrayList,并且ArrayList的删除,新增较慢,所以用ArrayList实现的HashMap无论是查询还是增加删除,都慢。(HashMap查询慢,是因为ArrayList需要从头遍历,没有下标,所以慢,不代表ArrayList查询慢,ArrayList查询比较快(可以直接使用下标进行查询)。)
package com.xiyou.arrayListHashMap;
import java.util.ArrayList;
import java.util.List;
/**
* 利用ArrayList实现HashMap
*/
public class ArrayListHashMap<Key, Value> {
/**
* 定义一个ArrayList用来存储Map
*/
List<Entry<Key, Value>> tables = new ArrayList<>();
/**
* 定义put方法
* @param key
* @param value
*/
public void put(Key key, Value value){
// 根据key得到entry对象
Entry<Key, Value> entry = getEntry(key);
if (entry != null) {
// 表示已经存在进行覆盖操作
entry.value = value;
} else {
// entry不存在第一次添加
Entry<Key, Value> keyValueEntry = new Entry<Key, Value>(key, value);
tables.add(keyValueEntry);
}
}
/**
* 定义get方法
* @param key
* @return
*/
public Value get(Key key){
// 根据key得到key,value对象
Entry<Key, Value> entry = getEntry(key);
if (entry != null) {
return entry.value;
}
return null;
}
/**
* 定义getEntry方法
*/
public Entry<Key, Value> getEntry(Key key){
// 需要从头遍历
for (Entry<Key, Value> entry : tables) {
if (entry.key.equals(key)) {
return entry;
}
}
// 找不到就返回null
return null;
}
}
/**
* 定义一个Entry类,用于存储Map的key和value
*/
class Entry<Key, Value> {
/**
* 保存key
*/
public Key key;
/**
* 用来保存value
*/
public Value value;
/**
* 定义构造函数
* @param key
* @param value
*/
public Entry(Key key, Value value){
super();
this.key = key;
this.value = value;
}
}
基于LinkedList手写HashMap
- table[]用来存放数据元素,存放Entry对象,默认长度是10
- 用key的hash值取模table的长度确定下标
package com.xiyou.linkedListHashMap;
import java.util.LinkedList;
/**
* 利用LinkedList实现HashMap,会判断Hash冲突问题, 暂时不解决扩容问题
*/
public class LinkedListHashMap {
/**
* 定义一个数组,里面存放LinkedList
* 直接将数组定义为998是为了不解决扩容,让其hash很少冲突
*/
LinkedList<Entry>[] tables = new LinkedList[998];
/**
* 定义get方法
* @param key
* @return
*/
public Object get(Object key){
// 取key的hash值
int hashCode = key.hashCode();
// 判断该hash值的数组下标
int hash = hashCode % tables.length;
// 得到数组中对应下标的LinkedList
LinkedList<Entry> linkedList = tables[hash];
if (linkedList == null) {
System.out.println("没有找到该key, 报错");
}
for (Entry entry : linkedList) {
// 进入循环表示hash有冲突,证明数组下面挂了链表
if (entry.key.equals(key)) {
return entry.value;
}
}
// 若没有找到则返回null
return tables[hash];
}
/**
* 定义put方法
* @param key
* @param value
*/
public void put(Object key, Object value){
Entry<Object, Object> newEntry = new Entry<>(key, value);
// 得到数组下标
int hash = key.hashCode() % tables.length;
// 得到下标对应的list
LinkedList<Entry> entryLinkedList = tables[hash];
if (entryLinkedList == null) {
// 表示没有hash冲突,第一次添加
entryLinkedList = new LinkedList<>();
entryLinkedList.add(newEntry);
tables[hash] = entryLinkedList;
} else {
// 用于保存是否进行覆盖操作
boolean flag = false;
// 存在hash冲突
// 存在hash冲突,需要判断是进行覆盖,还是新元素添加
for (Entry entry : entryLinkedList) {
if (entry.key.equals(key)) {
// 进行覆盖操作
entry.value = value;
flag = true;
break;
}
}
if (!flag) {
// 表示没有进行覆盖操作,需要新添加元素
entryLinkedList.add(newEntry);
}
}
}
public static void main(String[] args) {
LinkedListHashMap linkedListHashMap = new LinkedListHashMap();
linkedListHashMap.put("a", "aaaa");
linkedListHashMap.put("a", "ccccc");// 覆盖
linkedListHashMap.put("a", "bbbbb");// 覆盖
linkedListHashMap.put("b", "bbbbe");// 覆盖
System.out.println(linkedListHashMap.get("b"));
System.out.println(linkedListHashMap.get("a"));
System.out.println(linkedListHashMap.get("e"));
}
}
/**
* 定义一个Entry类,用于存储Map的key和value
*/
class Entry<Key, Value> {
/**
* 保存key
*/
public Key key;
/**
* 用来保存value
*/
public Value value;
/**
* 定义构造函数
* @param key
* @param value
*/
public Entry(Key key, Value value){
super();
this.key = key;
this.value = value;
}
}