如何写一个自己的HashMap
想必很多Java工程师出去面试的时候都会被问到HashMap的底层实现原理,很多人觉得没什么必要,反正我会用就行,就我的感觉而言,在初期确实没什么必要,但是站在公司角度想,如果面试者连底层实现都搞定了,还怕一点表层应用的东西吗?又或者说,我看到了另一个答案最能打动我:沉下去有多深,浮上来就有多高。
开始说一下必要的准备阶段:
1.在写自己的HashMap之初,我们需要对一定HashMap有一个初步的认识,比如HashMap底层是由数组和链表实现的,具体的结构图如图所示:

还有比如说最大容量,初始容量,加载因子,红黑树等等概念
推荐几个具体点的地址:
A.微信公众号 --- 码农翻身,搜索HashMap就有
B.https://mubu.com/edit/mY8GnK94oi 幕布思维导图
C.网易云课堂搜索HashMap,有一门只要一分钱的课程,40多分钟,看完也可以有一个比较明确的了解
2.开始撸代码了
A.首先定义一个自己的Map接口,和链表接口:
public interface MyMapInterface<k,v> {
// 大小
int size();
// 是否为空
boolean isEmpty();
// 根据key获取元素
Object get(Object key);
// 添加元素
Object put(Object key,Object value);
// 内部接口
interface Entry<k,v>{
k getKey();
v getValue();
}
}
B.编写实现类:
public class MyHashMap<k,v> implements MyMapInterface {
// 初始容量大小 --- 源码写法 1 << 4
private final int DEFAULT_INITIAL_CAPACITY = 16;
// 加载因子
private final float DEFAULT_LOAD_FACTOR = 0.75f;
// 根据定义的静态内部类,初始化链表,长度为默认长度
Node[] table = new Node[DEFAULT_INITIAL_CAPACITY];
// 长度
private int size = 0;
@Override
public int size() {
return size;
}
@Override
public boolean isEmpty() {
return size == 0;
}
@Override
public Object put(Object key, Object value) {
// 计算key的hash值
int hashValue = hash(key);
// 计算出应该存放的位置
int i = indexFor(hashValue,table.length);
// 如果i处有数据且key一样,进行覆盖
for(Node node = table[i];node != null; node = node.next){
Object k;
if(node.hash == hashValue && ((k = node.key)==key||key.equals(k))){
Object oldValue = node.value;
node.value = value;
return oldValue;
}
}
// 如果i位置没有数据,或i位置有数据,但key是新的key,新增节点
addEntry(key,value,hashValue,i);
return null;
}
@Override
public Object get(Object key) {
// 根据对象的hashcode计算hash值
int hashValue = hash(key);
// 根据hash值和链表长度,获取插入位置的索引
int i = indexFor(hashValue,table.length);
for(Node node = table[i];node != null;node = node.next){
if(node.key.equals(key) && hashValue == node.hash){
return node.value;
}
}
return null;
}
// 向Entry添加元素
// hashvalue --- hash值
// i --- 索引位置
public void addEntry(Object key,Object value,int hashValue,int i){
// 如果超过了数组约定长度,就扩容
if(++size >= table.length * DEFAULT_LOAD_FACTOR){
Node[] newTable = new Node[table.length << 1];
// 复制数组
//System.arraycopy(table,0,newTable,0,table.length);
transfer(table,newTable);
table = newTable;
}
// 得到i处的数据
Node eNode = table[i];
// 新增节点,将该节点的next指向前一个节点
table[i] = new Node(hashValue,key,value,eNode);
}
// 引用JDK1.7的复制代码
public void transfer(Node[] src,Node[] newTable) { //src引用了旧的Entry数组
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) { //遍历旧的Entry数组
Node e = src[j]; //取得旧Entry数组的每个元素
if (e != null) {
src[j] = null;//释放旧Entry数组的对象引用(for循环后,旧的Entry数组不再引用任何对象)
do {
Node next = e.next;
int i = indexFor(e.hash, newCapacity); //!!重新计算每个元素在数组中的位置
e.next = newTable[i]; //标记[1]
newTable[i] = e; //将元素放在数组上
e = next; //访问下一个Entry链上的元素
} while (e != null);
}
}
}
// 获取插入的位置(取模运算 有瑕疵)
public int indexFor(int hashValue,int length){
return hashValue % length;
}
// 获取插入的位置,根据Obeject对象的hashcode 获取hash值
public int hash(Object key){
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
static class Node implements MyMapInterface.Entry{
// hash值
int hash;
Object key;
Object value;
//指向下个节点(单链表)
Node next;
Node(int hash,Object key,Object value,Node next){
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
@Override
public Object getKey() {
return key;
}
@Override
public Object getValue() {
return value;
}
}
}
C.注释还是很清楚的,在了解了一部分基础知识的情况下,可以完全看动,至少我是这样过来的,需要说明遇到的一个坑,在写代码的初期,进行扩容操作的时候,我用的是System.arraycopy方法进行复制链表,但始终会出现莫名其妙的问题,因此我引入了JDK1.7的源码方法,transfer方法,进行一定的修改后,替换掉上面的复制方法,结果就正常了
D.测试类截图:
如果有错误的地方,希望各位指出,需要互相交流的可以联系我,Q806857264
如何写一个自己的HashMap的更多相关文章
- 手写一个简单的HashMap
HashMap简介 HashMap是Java中一中非常常用的数据结构,也基本是面试中的"必考题".它实现了基于"K-V"形式的键值对的高效存取.JDK1.7之前 ...
- 自己动手用java写一个hashMap
入坑java很多年了,现在总结一下自己学到的东西. 1.首先我们先来聊聊什么是HashMap? 什么是hash?hash用中文的说法就叫做“散列”,通俗的讲就是把任意长度的字符串输入,经过hash计算 ...
- 学记:为spring boot写一个自动配置
spring boot遵循"约定优于配置"的原则,使用annotation对一些常规的配置项做默认配置,减少或不使用xml配置,让你的项目快速运行起来.spring boot的神奇 ...
- 一起写一个JSON解析器
[本篇博文会介绍JSON解析的原理与实现,并一步一步写出来一个简单但实用的JSON解析器,项目地址:SimpleJSON.希望通过这篇博文,能让我们以后与JSON打交道时更加得心应手.由于个人水平有限 ...
- Java Web 开发利用Struts2+Spring+mybatis写一个用户登录界面以及简单的数据交互
框架的东西太复杂也难以讲通,直接上代码: 一.首先得配置环境 和导入必要的jar包 有一些重要的如下: Filter文件夹下的SafetyFilter.java model文件夹下的 Global ...
- 手动的写一个structs
为了更好的学习框架的运行机制,这里开始学习框架之前,介绍一个简单的自定义的框架. 需求: 登录:id:aaa,pwd:888登录成功之后,跳转到,index.jsp页面并显示,欢迎你,aaa 注册,页 ...
- 自己写一个java的mvc框架吧(四)
自己写一个mvc框架吧(四) 写一个请求的入口,以及初始化框架 上一章写了获取方法的入参,并根据入参的参数类型进行数据转换.这时候,我们已经具备了通过反射调用方法的一切必要条件.现在我们缺少一个htt ...
- 自己写一个java的mvc框架吧(三)
自己写一个mvc框架吧(三) 根据Method获取参数并转换参数类型 上一篇我们将url与Method的映射创建完毕,并成功的将映射关系创建起来了.这一篇我们将根据Method的入参参数名称.参数类型 ...
- 手把手教你写一个RPC
1.1 RPC 是什么 定义:RPC(Remote Procedure Call Protocol)--远程过程调用协议 ,RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数 ...
随机推荐
- MySQL 性能优化之慢查询
性能优化的思路 首先需要使用慢查询功能,去获取所有查询时间比较长的SQL语句 其次使用explain命令去查询由问题的SQL的执行计划(脑补链接:点我直达1,点我直达2) 最后可以使用show pro ...
- Jmeter基础002----Jmeter简单使用
一.Jmeter概述 1.概述 JMeter是Apache公司使用JAVA开发的一款开源测试工具,它的功能强大.高效,可以模拟一些高并发或多次循环等测试场景,使用方便灵活. 2.使用 安装配置java ...
- Activity学习笔记1
Activity概述 简单的理解Activity就是指Android手机或平板的一个屏,类似Window的一个窗口,浏览器的一个页面. Activity的4种状态 Activity的生命周期 创建Ac ...
- JavaWeb网上图书商城完整项目--day02-10.提交注册表单功能之页面实现
1.当从服务器返回的注册错误信息的时候,我们在注册界面需要将错误信息显示出来 我们需要修改regist.jsp页面的代码:其中error是一个haspmap,c标签对map的属性可以直接使用 ${er ...
- android屏幕适配的全攻略--支持不同的屏幕尺寸适配平板和手机
一. 核心概念与单位详解 1. 什么是屏幕尺寸.屏幕分辨率.屏幕像素密度? 屏幕分辨率越大,手机越清晰 dpi就是dot per inch dot意思是点,就是每英寸上面的像素点数 android原始 ...
- centos下面基本的linux命令操作
参考视频: 1.如何打开命令窗口 鼠标点击右键,选择open Terminal就可以打开命令行窗口了. 现在我们可以调整这个窗口的大小,增大窗口大小使用ctrl+shif+加号,缩小窗口ctrl+减号 ...
- C# 9.0 新特性之 Lambda 弃元参数
阅读本文大概需要不到 1 分钟. 弃元(Discards) 是在 C# 7.0 的时候开始支持的,它是一种人为丢弃不使用的临时虚拟变量.语法上它是用来赋值的,但它却不被分配存储空间,即没有值,所以不能 ...
- Python之浅谈多态和封装
目录 组合 什么是组合 为什么使用组合 多态和多态性 多态 什么是多态? 多态性 好处 多态性 什么是多态性 封装 封装是什么意思? 隐藏 如何用代码实现隐藏 python 实际上是可以访问隐藏属性的 ...
- 10w行级别数据的Excel导入优化记录
需求说明 项目中有一个 Excel 导入的需求:缴费记录导入 由实施 / 用户 将别的系统的数据填入我们系统中的 Excel 模板,应用将文件内容读取.校对.转换之后产生欠费数据.票据.票据详情并存储 ...
- 《算法笔记》6.7小节 问题 A: 简单计算器
又是大模拟淦淦淦淦淦淦淦 思路: 这道题我居然用的队列orz. 言归正传,这道题就是模拟计算器.从读题目样例可以发现,数字的个数只比符号的个数多一个,那么这就给我们了思路:用队列,先提前放出一个数,每 ...