1. HashMap的框架图
文章参考:https://www.cnblogs.com/2511zzZ/p/12770864.html
HashMap是Java中一中非常常用的数据结构。它实现了基于“K-V”形式的键值对的高效存取。JDK1.7之前,HashMap是基于数组+链表实现的,1.8以后,HashMap的底层实现中加入了红黑树用于提升查找效率。
HashMap的优缺点
- 遍历速度:慢,Key是无序不重复的且从框架图看完整遍历还需要包含链表
- 缺陷:高度依赖hash散列算法,想要提高HashMap的效率,最重要的就是尽量避免生成链表,或者说尽量减少链表的长度,避免哈希冲突,降低key的比较次数
- 插入删除速度:快,散列算法快速定位下标+头插入|指针移动
- 随机访问速度:快,最显著的优势=散列算法快速定位下标+遍历链表equals准确匹配
- 适用场景:适合海量数据,o(1)的随机访问速度,但不建议全部遍历
2. 自定义 MyHashMap
需求:功能主要实现put()、get()、remove()方法
原理:MyHashMap = 散列 + 数组 + 列表
技术点:散列、头插入、链表指针移动
- put()函数:
1. 下标=散列+取余=key.hashCode() % arrays.length
2. 首次插入数据::下标对应的值为null表示当前坑位为空,直接赋值
3. 非首次插入数据:需要在该坑位上建立一个链表或内容覆盖
3.1. 若key相同,则覆盖内容
3.2. 若key不同,则链表新增节点-例如头插法:只需将新节点的next指向原来链表的节点即可
- get()函数:遍历链表,若存在key值相等,则返回结果
- remove函数:链表指针操作:将目标节点的前驱节点的next引用指向目标节点的后继节点
1. 接口 MyMap
定义put()、get()、remove()接口方法
package com.sufadi.study.hashmap;
public interface MyMap<K, V> {
void put(K k, V v);
V get(K k);
boolean remove(K k);
int size();
}
2. MyEntry
HashMap数组和链表的基本单位,留意next的指向关系就行
package com.sufadi.study.hashmap;
public class MyEntry<K, V> {
K k;
V v;
MyEntry<K, V> next;
public MyEntry(K k, V v, MyEntry<K, V> next) {
this.k = k;
this.v = v;
this.next = next;
}
}
3. MyHashMap
- put()函数:
1. 下标=散列+取余=key.hashCode() % arrays.length
2. 首次插入数据::下标对应的值为null表示当前坑位为空,直接赋值
3. 非首次插入数据:需要在该坑位上建立一个链表或内容覆盖
3.1. 若key相同,则覆盖内容
3.2. 若key不同,则链表新增节点-例如头插法:只需将新节点的next指向原来链表的节点即可
- get()函数:遍历链表,若存在key值相等,则返回结果
- remove函数:链表指针操作:将目标节点的前驱节点的next引用指向目标节点的后继节点
package com.sufadi.study.hashmap;
public class MyHashMap implements MyMap {
int size = 0;
MyEntry<Object, Object>[] arrays;
public MyHashMap() {
arrays = new MyEntry[16];
}
@Override
public void put(Object key, Object newV) {
// 下标=散列+取余
int index = key.hashCode() % arrays.length;
System.out.println(" 下标=散列+取余 = " + index + ", key:" + key + ",value:" + newV);
MyEntry<Object, Object> current = arrays[index];
// 非首次插入数据:需要在该坑位上建立一个链表或内容覆盖
if (current != null) {
while (current != null) {
/**
* 链表中,进行比较key,
* 若key相同,则覆盖内容
* 若key不同,则链表新增节点-例如头插法
*/
if (current.k == key) {
// 若key相同,则覆盖内容
current.v = newV;
return;
}
current = current.next;
}
// 若key不同,则链表新增节点-头插法
/**
* 尾插法:需遍历链表,将新节点插入末尾
* 头插法:只需将新节点的next指向原来链表的节点即可
*/
arrays[index] = new MyEntry<>(key, newV, arrays[index]);
size ++;
} else {
size ++;
// null表示当前坑位为空,直接赋值
arrays[index] = new MyEntry<>(key, newV, null);
}
}
@Override
public Object get(Object key) {
// 下标=散列+取余
int index = key.hashCode() % arrays.length;
MyEntry<Object, Object> current = arrays[index];
/**
* 遍历链表,若存在key值相等,则返回结果
*/
while (current != null) {
if (current.k == key) {
return current.v;
}
current = current.next;
}
return null;
}
@Override
public boolean remove(Object key) {
// 下标=散列+取余
int index = key.hashCode() % arrays.length;
MyEntry<Object, Object> current = arrays[index];
// 无链表,则直接置空
if (current.k == key) {
arrays[index] = null;
size --;
return true;
}
while (current.next != null) {
if (current.next.k == key) {
// 将目标节点的前驱节点的next引用指向目标节点的后继节点
current.next = current.next.next;
size --;
return true;
}
current = current.next;
}
return false;
}
@Override
public int size() {
return size;
}
public void printMap() {
System.out.println("========打印MyHashMap的列表========");
for (int i = 0; i < arrays.length; i++) {
if (arrays[i] == null) {
continue;
}
if (arrays[i].next == null) {
System.out.println("数组下标为" + i + "无链表,key:" + arrays[i].k + ", value:"+ arrays[i].v);
} else {
System.out.println("数组下标为" + i + "的位置有链表且表头为 key:" + arrays[i].k + ", value:"+ arrays[i].v);
MyEntry<Object, Object> current = arrays[i];
while (current != null) {
System.out.println(" 链表节点 key:" + current.k + ", value:"+ current.v);
current = current.next;
}
}
}
System.out.println("========打印MyHashMap的列表========");
}
}
4. 测试类 MyHashMapTest
package com.sufadi.study.hashmap;
public class MyHashMapTest {
public static void main(String[] args) {
MyHashMap map = new MyHashMap();
System.out.println("*******测试HashMap的put函数*******");
map.put("Jim", 18);
map.put("Tom", 19);
map.put("LouLou", 1);
map.put("AaAa", 66);
map.put("BBBB", 88);
map.printMap();
System.out.println("\n*******测试HashMap的remove函数*******");
map.remove("LouLou");
map.printMap();
}
}
5. 运行结果
注意LouLou、AaAa、BBBB 转化的hashCode是一致的,下述运行接口可以清晰看到链表生成
*******测试HashMap的put函数*******
下标=散列+取余 = 14, key:Jim,value:18
下标=散列+取余 = 2, key:Tom,value:19
下标=散列+取余 = 0, key:LouLou,value:1
下标=散列+取余 = 0, key:AaAa,value:66
下标=散列+取余 = 0, key:BBBB,value:88
========打印MyHashMap的列表========
数组下标为0的位置有链表且表头为 key:BBBB, value:88
链表节点 key:BBBB, value:88
链表节点 key:AaAa, value:66
链表节点 key:LouLou, value:1
数组下标为2无链表,key:Tom, value:19
数组下标为14无链表,key:Jim, value:18
========打印MyHashMap的列表========
*******测试HashMap的remove函数*******
========打印MyHashMap的列表========
数组下标为0的位置有链表且表头为 key:BBBB, value:88
链表节点 key:BBBB, value:88
链表节点 key:AaAa, value:66
数组下标为2无链表,key:Tom, value:19
数组下标为14无链表,key:Jim, value:18
========打印MyHashMap的列表========
Process finished with exit code 0