想必很多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的更多相关文章

  1. 手写一个简单的HashMap

    HashMap简介 HashMap是Java中一中非常常用的数据结构,也基本是面试中的"必考题".它实现了基于"K-V"形式的键值对的高效存取.JDK1.7之前 ...

  2. 自己动手用java写一个hashMap

    入坑java很多年了,现在总结一下自己学到的东西. 1.首先我们先来聊聊什么是HashMap? 什么是hash?hash用中文的说法就叫做“散列”,通俗的讲就是把任意长度的字符串输入,经过hash计算 ...

  3. 学记:为spring boot写一个自动配置

    spring boot遵循"约定优于配置"的原则,使用annotation对一些常规的配置项做默认配置,减少或不使用xml配置,让你的项目快速运行起来.spring boot的神奇 ...

  4. 一起写一个JSON解析器

    [本篇博文会介绍JSON解析的原理与实现,并一步一步写出来一个简单但实用的JSON解析器,项目地址:SimpleJSON.希望通过这篇博文,能让我们以后与JSON打交道时更加得心应手.由于个人水平有限 ...

  5. Java Web 开发利用Struts2+Spring+mybatis写一个用户登录界面以及简单的数据交互

    框架的东西太复杂也难以讲通,直接上代码: 一.首先得配置环境 和导入必要的jar包 有一些重要的如下: Filter文件夹下的SafetyFilter.java   model文件夹下的 Global ...

  6. 手动的写一个structs

    为了更好的学习框架的运行机制,这里开始学习框架之前,介绍一个简单的自定义的框架. 需求: 登录:id:aaa,pwd:888登录成功之后,跳转到,index.jsp页面并显示,欢迎你,aaa 注册,页 ...

  7. 自己写一个java的mvc框架吧(四)

    自己写一个mvc框架吧(四) 写一个请求的入口,以及初始化框架 上一章写了获取方法的入参,并根据入参的参数类型进行数据转换.这时候,我们已经具备了通过反射调用方法的一切必要条件.现在我们缺少一个htt ...

  8. 自己写一个java的mvc框架吧(三)

    自己写一个mvc框架吧(三) 根据Method获取参数并转换参数类型 上一篇我们将url与Method的映射创建完毕,并成功的将映射关系创建起来了.这一篇我们将根据Method的入参参数名称.参数类型 ...

  9. 手把手教你写一个RPC

    1.1 RPC 是什么 定义:RPC(Remote Procedure Call Protocol)--远程过程调用协议 ,RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数 ...

随机推荐

  1. Python按顺序读取文件夹中文件

    参考资料: https://blog.csdn.net/qq_22227123/article/details/79903116 https://blog.csdn.net/merdy_xi/arti ...

  2. 三星note8港版如何显示电量百分比呢?

    设置-通知-状态栏,显示电量百分比打钩即可.

  3. Python学习日志-02

    (2)Python如何运行程序 Python解释器简介: Python不仅仅是一门编程语言,它也是一个名为解释器的软件包.解释器是一种让其他程序运行起来的程序.当你编写了一段Python程序,Pyth ...

  4. 信息收集-DNS

    首先更正一个小白很普遍的错误观点,www.baidu.com(严格上是www.baidu.com. 这个点是根的意思,所有的记录从这里开始)并不是一个真正意义上的域名,而是百度服务器的A记录,baid ...

  5. C++核心内容和机制

    备注:不局限与C++版本   一. 基础知识 数据类型和POD/Trivial 数据类型: 类型转换: NULL和nullptr: 操作符重载: 全局静态变量和成员静态变量的申明和初始化: 左值和右值 ...

  6. ABP(ASP.NET Boilerplate Project)快速入门

    前言 这两天看了一下ABP,做个简单的学习记录.记录主要有以下内容: 从官网创建并下载项目(.net core 3.x + vue) 项目在本地成功运行 新增实体并映射到数据库 完成对新增实体的基本增 ...

  7. 全网最深分析SpringBoot MVC自动配置失效的原因

    前言 本来没有计划这一篇文章的,只是在看完SpringBoot核心原理后,突然想到之前开发中遇到的MVC自动失效的问题,虽然网上有很多文章以及官方文档都说明了原因,但还是想亲自看一看,本以为很简单的事 ...

  8. 命令 关闭SElinux

    # sed -i 's/^SELINUX=.*$/SELINUX=disabled/g' /etc/selinux/config

  9. 缓存数据库之redis

    NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,泛指非关系型的数据库,NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题 N ...

  10. Mybatis一对一映射resultMap子标签中顺序问题

    直接上图 鼠标点上红线出现如下提示 The content of element type "resultMap" must match  "(constructor?, ...