当前位置: 首页 > backend >正文

HashMap相关学习

1. 手写HashMap的put方法和get方法

1.1 基础部分搭建

package map;public class MyHashMap<K,V> {/*** 哈希表*/private Node<K,V>[] table;/*** 键值对的个数*/private int size;@SuppressWarnings("unchecked")public MyHashMap() {this.table = new Node[16];}static class Node<K,V>{/*** 哈希值*/int hash;/*** 键*/K key;/*** 值*/V value;/*** 下一个结点的内存地址*/Node<K,V> next;/*** 构造节点对象* @param hash 哈希值* @param key 键* @param value 值* @param next 下一个结点地址*/public Node(int hash, K key, V value, Node<K,V> next) {this.hash = hash;this.key = key;this.value = value;this.next = next;}}/*** 获取集合中键值对的个数* @return*/public int size() {return size;}
}

1.2 put方法

1.2.1 基础部分

/*** 向Map中添加一个键值对* @param key 键* @param value 值* @return value,如果key重复,则返回oldValue,如果key不重复,则返回newValue*/
public V put(K key, V value) {return null;
}/*** 通过key获取value* @param key 键* @return 值*/
public V get(K key) {return null;
}

1.2.2 核心实现

  1. 当key为null时,则将该键值对存储到table数组中索引为0的位置
private V putForNullKey(V value) {Node<K,V> node = table[0];if(node == null) {table[0] = new Node<>(0,null,value, null);size++;return value;}Node<K,V> prev = null;while(node != null) {if(node.key == null) {V oldValue = node.value;node.value = value;return oldValue;}prev.next = node;node = node.next;}prev.next = new Node<>(0,null,value, null);size++;return value;
}
  1. 当key不为null时,则就需要调用key的hashcode()方法,获取到key的哈希值
int hash = key.hashCode();
int index = hash % table.length;
Node<K,V> node = table[index];
if(node == null) {table[index] = new Node<>(hash, key, value, null);size++;return value;
}
Node<K,V> prev = null;
while(null != node) {if(key.equals(node.key)) {V oldValue = node.value;node.value = value;return oldValue;}prev = node;node = node.next;
}
prev.next = new Node<>(hash, key, value, null);
size++;
return value;

1.3 重写toString方法

1.3.1 Node<K,V>类中的重写

@Override
public String toString() {return "Node{key=" + key + ", value=" + value + '}';
}

1.3.2 MyHashMap类中的重写

/**
* 重写toSting方法,输出集合时调用
* @return
*/
@Override
public String toString() {StringBuilder sb = new StringBuilder();for(int i = 0; i < table.length; i++) {Node<K,V> node = table[i];while(null != node) {sb.append(node);sb.append("\n");node = node.next;}}return sb.toString();
}

1.4 get方法

1.4.1 key为null

if(key == null) {Node<K,V> node = table[0];if(node == null) {return null;}while(null != node) {if(null == node.key) {return node.value;}node = node.next;}
}

1.4.1 key不为null

int hash = key.hashCode();
int index = hash % table.length;
Node<K,V> node = table[index];
if(node == null) {return null;
}
while(null != node) {if(key.equals(node.key)) {return node.value;}
}
return null;

1.5 测试

1.5.1 测试put方法

public class MyHashMapTest {public static void main(String[] args) {MyHashMap<String, String> myHashMap = new MyHashMap<>();myHashMap.put("key1", "value1");myHashMap.put("key2", "value2");myHashMap.put("key3", "value3");myHashMap.put(null, "value4");System.out.println(myHashMap);}
}

输出结果:

Node{key=null, value=value4}
Node{key=key1, value=value1}
Node{key=key2, value=value2}
Node{key=key3, value=value3}

1.5.1 测试get方法

public class MyHashMapTest {public static void main(String[] args) {MyHashMap<String, String> myHashMap = new MyHashMap<>();myHashMap.put("key1", "value1");myHashMap.put("key2", "value2");myHashMap.put("key3", "value3");myHashMap.put(null, "value4");System.out.println(myHashMap.get(null));}
}

输出结果:

value4

2. 不同jdk版本的HashMap

2.1 初始化

  1. Java8之前,构造方法执行时初始化table数组
  2. Java8之后,第一次调用put方法的时候初始化table数组

2.2 链表插入

  1. Java8之前,头插法
  2. Java8之后,尾插法

2.3 数据结构

  1. Java8之前,数组 + 单向链表
  2. Java8之后,数组 + 单向链表 + 红黑树

3. 容量和扩充

3.1 初始化容量永远是2的次幂

首先初始化时,先设一个不是2的次幂的容量,然后查看源码进行分析

Map<String, String> map = new HashMap<>(31);

首先进入有参数的构造方法

在这里插入图片描述

然后进入到这个方法,发现在最后一行代码有一个对集合大小设置的一个方法,在这个方法结束之后,这里的容量就变成32了,即2的次幂。

在这里插入图片描述
在这里插入图片描述
将容量设置为2的次幂的原因是加快哈希运算,减少哈希冲突。

  1. 加快哈希运算:对于计算机而言,使用二进制进行运算的速度是最快的。当一个数字是2的次幂时,hash & (length - 1) 的结果和 hash % length的结果是相同的。由于使用了&运算符,因此会提升效率。
  2. 减少哈希冲突:由于底层运算是hash & (length - 1),如果length是偶数,length - 1后一定是奇数,奇数二进制位最后一位一定是1,1和其他二进制位进行与运算,结果可能是1,也可能是0,这样可以减少哈希冲突,让散列分布更加均匀;如果length是奇数,length - 1后一定是偶数,偶数二进制位最后一位一定是0,0和任何数进行与运算,结果一定是0,这样就会导致发生大量的哈希冲突,白白浪费了一半的空间。

3.2 扩容机制

  1. 初始化容量为16
  2. 扩容时机:如果HashMap中存储元素的个数超过“数组长度 * loadFactor”的结果(loadFactor指的是负载因子,loadFactor的默认值一般为0.75),那么就需要执行数组扩容操作,就比如原先数量为16,负载因子为0.75,那当存储的数量超过12时,就会进行扩容
  3. 扩容为原来的2倍,原先为16,扩容后则为32
  4. 由于HashMap扩容后,需要重新再去计算每个元素的位置,会十分的消耗性能,因此建议在创建HashMap之前预测存入的元素个数,从而给一个合理的初始化容量,避免进行频繁地扩容操作
http://www.xdnf.cn/news/14537.html

相关文章:

  • 嵌入式学习笔记C语言阶段--16函数指针
  • UI前端大数据可视化:从设计到实现的完整流程
  • SQL基础语法+运行原理+云端数据库搭建
  • Qwen2.5-VL 是什么?
  • 大模型笔记4:RAG检索增强生成
  • LangGraph--框架核心思想
  • 数字系统设计与verilog hdl第8版王金明
  • HPC软件架构---Vector solution方案简介
  • 订单状态定时处理-01.需求分析
  • 免费插件集-illustrator插件-Ai插件-移除非纯黑叠印
  • NodeJS怎么开启多核执行任务,加快执行速度
  • 基于51单片机的流量检测及时间显示系统
  • PaddleOCR项目实战(2):SpringBoot服务开发之接口设计
  • 基于CL_PSO与BP神经网络分类模型的特征选择方法研究(Python实现)
  • 基于CATIA轴系的最小边界曲面自动化生成技术深度解析
  • linux多线程之POSIX信号量
  • PHP Swoft2 框架精华系列:Config 配置解析,使用说明
  • 如何在 Elementary OS 上安装 Google Chrome 浏览器
  • 智慧流水线在ESOP数字工厂中的作用
  • 迈向通用具身智能:具身智能的综述与发展路线
  • 前端如何调用外部api获取省市区数据
  • leetcode138-随机链表的复制
  • 技术突破与落地应用:端到端 2.0 时代辅助驾驶TOP10 论文深度拆解系列【第四篇(排名不分先后)】
  • 【C++】模板入门
  • LeetCode HOT 100
  • C语言空指针异常在Java中的解决方案
  • 智慧流水线在esop数字工厂中的作用?
  • GO语言---短变量声明
  • 手写简版React-router
  • DeepSeek提示词指南:从基础到高阶的全面解析