如何写一个自己的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,为通信程序之间携带信息数 ...
随机推荐
- fiddler修改请求参数
1.打开fiddler ,点击界面左侧左侧底部 2.此图标为before request请求(修改请求参数时,设置这个,可以修改请求参数) 3..再次点击该按钮,将图标切换到下图after respo ...
- matlab中imwrite函数详解(imwrite的输出格式)
参考资料: https://www.mathworks.com/help/matlab/ref/imwrite.html?s_tid=srchtitle 你可能觉得imread函数很简单,但是还是有一 ...
- Ios App破解之路二 JJ斗地主
前提条件: 越狱手机里, 安装了 <JJ斗地主> 使用砸壳工具clutch 下载地址: https://github.com/KJCracks/Clutch/releases dzq:~/ ...
- Redis的常用配置
1. 配置守护线程方式运行,修改damonize,使用yes启用守护线程,这样就可以后台运行了 damonize no 修改为 damonize yes 2. 手动指定redis的pid,可以通过pi ...
- Centos中使用virtualenvwrapper
Centos中使用virtualenvwrapper python特有的一种软件环境,创建多个python环境,各个环境之间完全隔离,互不影响.它可以用来解决Python项目开发和运行过程中的依赖项和 ...
- 【K8S学习笔记】初识K8S 及架构组件
K8S是什么?发展历史 Kubernetes (简称 k8s)是 Google 在2014年开源的,对容器生命周期管理的开源平台,致力于对容器集群提供易于管理.高可用.弹性负载与故障转移的能力,提高服 ...
- maven依赖冲突以及解决方法
什么是依赖冲突 依赖冲突是指项目依赖的某一个jar包,有多个不同的版本,因而造成类包版本冲突 依赖冲突的原因 依赖冲突很经常是类包之间的间接依赖引起的.每个显式声明的类包都会依赖于一些其它的隐式类包, ...
- android 中IntentService的使用场景
IntentService是继承并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统的Service一样,同时,当任务执行 ...
- vue指令,实例成员,父子组件传参
v-once指令 """ v-once:单独使用,限制的标签内容一旦赋值,便不可被动更改(如果是输入框,可以主动修改) """ <di ...
- 云计算课程实验之安装Hadoop及配置伪分布式模式的Hadoop
一.实验目的 1. 掌握Linux虚拟机的安装方法. 2. 掌握Hadoop的伪分布式安装方法. 二.实验内容 (一)Linux基本操作命令 Linux常用基本命令包括: ls,cd,mkdir,rm ...