手撸HashMap实现
前言
HashMap是Java中常用的集合,而且HashMap的一些思想,对于我们平时解决业务上的一些问题,在思路上有帮助,基于此,本篇博客将分析HashMap底层设计思想,并手写一个迷你版的HashMap!
对HashMap的思考

第一,如图所示,HashMap有3个要素:hash函数+数组+单链表
第二,对于hash函数而言,需要考虑些什么?
要快,对于给定的Key,要能够快速计算出在数组中的index。那么什么运算够快呢?显然是位运算!
要均匀分布,要较少碰撞。说白了,我们希望通过hash函数,让数据均匀分布在数组中,不希望大量数据发生碰撞,导致链表过长。那么怎么办到呢?也是利用位运算,通过对数据的二进制的位进行移动,让hash函数得到的数据散列开来,从而减低了碰撞的概率。
如果发生了碰撞怎么办?上面的图其实已经说明了JDK的HashMap是如何处理hash冲突的,就是通过单链表解决的。那么除了这个方法,还有其他思路么?比如说,如果发生冲突,那么记下这个冲突的位置为index,然后在加上固定步长,即index+step,找到这个位置,看一下是否仍然冲突,如果继续冲突,那么按照这个思路,继续加上固定步长。其实这就是所谓的线性探测来解决Hash冲突的方法!
通过写一个迷你版的HashMap来深刻理解
1.定义接口
package hashMapTest; /**
* @ClassName: MyMap
* @Description: 自定义Map接口,对外暴露快速存取的方法
* @author Kingram
* @param <V>
* @param <K>
* @date 2018年8月3日
*
*/
public interface MyMap<K,V> { public V put(K k,V v);
public V get(K k); interface Entry<K,V> {
public K getKey();
public V getValue();
}
}
2.接口的实现
package hashMapTest; import java.util.ArrayList;
import java.util.List; /**
* @ClassName: MyHashMap
* @Description: 自定义HashMap
* @author Kingram
* @date 2018年8月3日
*
*/
public class MyHashMap<K, V> implements MyMap<K, V> { // 数组的默认初始化长度
private static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 设置默认加载因子
private static final float DEFAULT_LOAD_FACTOR = 0.75f; private int defaultInitSize;
private float deaultLoadFactor; // Map当中entry的数量
private int entryUseSize; // 数组
private Entry<K, V>[] table = null; // 构造方法,这里使用到了"门面模式".
// 这里的2个构造方法其实指向的是同一个,但是对外却暴露了两个"门面".
public MyHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
} public MyHashMap(int defaultInitialCapacity, float deaultLoadFactor) {
if (defaultInitialCapacity < 0) {
throw new IllegalArgumentException("参数有误" + defaultInitialCapacity);
} if (deaultLoadFactor <= 0 || Float.isNaN(deaultLoadFactor)) {
throw new IllegalArgumentException("参数有误" + deaultLoadFactor);
} this.defaultInitSize = defaultInitialCapacity;
this.deaultLoadFactor = deaultLoadFactor; table = new Entry[this.defaultInitSize];
} /**
*
* @ClassName: Entry
* @Description: HashMap的内部类,HashMap的要素之一,单链表的体现就在这里!
* @author Kingram
* @date 2018年8月3日
*
* @param <K>
* @param <V>
*/
class Entry<K, V> implements MyMap.Entry<K, V> { private K key;
private V value;
private Entry<K, V> next; public Entry() {
} public Entry(K key, V value, Entry<K, V> next) {
this.key = key;
this.value = value;
this.next = next;
} @Override
public K getKey() {
return key;
} @Override
public V getValue() {
return value;
}
} /**
* 第一,要考虑是否扩容?
*
* HashMap中的Entry的数量(数组以及单链表中的所有Entry)是否达到加载因子限制的最大值?
*
* 第二,如果扩容,意味着新生成一个Entry[],不仅如此还得重新散列。
*
* 第三,要根据Key计算出在Entry[]中的位置,定位后,如果Entry[]中的元素为null,那么可以放入其中,如果不为空,那么得遍历单链表,
* 要么更新value,要么形成一个新的Entry“挤压”单链表!
*/
@Override
public V put(K k, V v) {
V oldValue = null;
// 判断是否需要扩容?
// 扩容完毕 re肯定需要重新散列
if (entryUseSize >= defaultInitSize * deaultLoadFactor) {
resize(2 * defaultInitSize);
}
// 得到HASH值,计算出数组的索引
int index = hash(k) & (defaultInitSize - 1);
if (table[index] == null) {// 判断当前索引位置有没有元素,如果没有直接插入
table[index] = new Entry<K, V>(k, v, null);
++entryUseSize;
} else {// 如果有元素就需要遍历链表
// 遍历链表
Entry<K, V> entry = table[index];
Entry<K, V> e = entry;
while (e != null) {
if (k == e.getKey() || k.equals(e.getKey())) {
oldValue = e.value;
e.value = v;
return oldValue;
}
e = e.next;
}
table[index] = new Entry<K, V>(k, v, entry);
++entryUseSize;
}
return oldValue;
} /**
*
* @Title: hash
* @Description: hash函数,跟据Key计算出索引
* @param @param k
* @param @return 参数
* @return int 返回类型
* @throws
*/
private int hash(K k) {
int hashCode = k.hashCode();
hashCode ^= (hashCode >>> 20) ^ (hashCode >>> 12);
return hashCode ^ (hashCode >>> 7) ^ (hashCode >>>4);
} private void resize(int i) {
Entry[] newTable = new Entry[i];
// 改变了数组的大小
defaultInitSize = i;
entryUseSize = 0;
rehash(newTable);
} private void rehash(Entry<K,V>[] newTable) {
// 得到原来老的Entry集合 注意遍历单链表
List<Entry<K,V>> entryList = new ArrayList<Entry<K,V>>();
for(Entry<K,V> entry : table) {
if(entry != null) {
do{
entryList.add(entry);
entry = entry.next;
}while(entry != null);
}
} // 覆盖旧的引用
if(newTable.length > 0) {
table = newTable;
} // 所谓的重新HASH就是重新PUT ENTRY到HASHMAP
for(Entry<K,V> entry : entryList) {
put(entry.getKey(),entry.getValue());
}
} @Override
public V get(K k) {
int index = hash(k) & (defaultInitSize -1);
if(table[index] == null) {
return null;
} else {
Entry<K,V> entry = table[index];
do{
if(k == entry.getKey() || k.equals(entry.getKey())) {
return entry.value;
}
entry = entry.next;
}while(entry != null);
}
return null;
} }
3.测试
package hashMapTest; /**
* @ClassName: HashMapTest
* @Description: 测试模拟HashMap的实现用例
* @author Kingram
* @date 2018年8月3日
*
*/
public class HashMapTest {
public static void main(String[] args) {
MyMap<String,String> myMap = new MyHashMap<String,String>();
for(int i =0;i < 500;i++) {
myMap.put("key"+i, "value"+i);
} for(int i = 0;i < 500;i++) {
System.out.println("key"+i+",value is:"+myMap.get("key"+i));
}
}
}
4.输出...嗯...亲测可用...

手撸HashMap实现的更多相关文章
- 使用Java Socket手撸一个http服务器
原文连接:使用Java Socket手撸一个http服务器 作为一个java后端,提供http服务可以说是基本技能之一了,但是你真的了解http协议么?你知道知道如何手撸一个http服务器么?tomc ...
- 通过 Netty、ZooKeeper 手撸一个 RPC 服务
说明 项目链接 微服务框架都包括什么? 如何实现 RPC 远程调用? 开源 RPC 框架 限定语言 跨语言 RPC 框架 本地 Docker 搭建 ZooKeeper 下载镜像 启动容器 查看容器日志 ...
- 《Spring 手撸专栏》第 3 章:初显身手,运用设计模式,实现 Bean 的定义、注册、获取
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你是否能预见复杂内容的设计问题? 讲道理,无论产品功能是否复杂,都有很大一部分程序员 ...
- 手撸一个springsecurity,了解一下security原理
手撸一个springsecurity,了解一下security原理 转载自:www.javaman.cn 手撸一个springsecurity,了解一下security原理 今天手撸一个简易版本的sp ...
- 五分钟,手撸一个Spring容器!
大家好,我是老三,Spring是我们最常用的开源框架,经过多年发展,Spring已经发展成枝繁叶茂的大树,让我们难以窥其全貌. 这节,我们回归Spring的本质,五分钟手撸一个Spring容器,揭开S ...
- php手撸轻量级开发(一)
聊聊本文内容 之前讲过php简单的内容,但是原生永远是不够看的,这次用框架做一些功能性的事情. 但是公司用自己的框架不能拿出来,用了用一些流行的框架比如tp,larveral之类的感觉太重,CI也不顺 ...
- 手写HASHMAP
手写HASHMAP const int MAXN=10010; const int HASH=10100; //需要hash的数的总个数最大值 struct HASHMAP { ...
- 【手撸一个ORM】MyOrm的使用说明
[手撸一个ORM]第一步.约定和实体描述 [手撸一个ORM]第二步.封装实体描述和实体属性描述 [手撸一个ORM]第三步.SQL语句构造器和SqlParameter封装 [手撸一个ORM]第四步.Ex ...
- 康少带你手撸orm
orm 什么是orm? 对象关系映射: 一个类映射成一张数据库的表 类的对象映射成数据库中的一条条数据 对象点数据映射成数据库某条记录的某个值 优点:不会写sql语句的程序员也可以很6的操作sql语句 ...
随机推荐
- Redis缓存数据库安全加固指导(一)
背景 在众多开源缓存技术中,Redis无疑是目前功能最为强大,应用最多的缓存技术之一,参考2018年国外数据库技术权威网站DB-Engines关于key-value数据库流行度排名,Redis暂列第一 ...
- mysql安装后改动port号password默认字符编码
1.改动password grant all privileges on *.* to 'root'@'localhost' identified by 'new password'; 2.改动por ...
- Linux ALSA声卡驱动之四:Control设备的创建
声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢! Control接口 Control接口主要让用户空间的应用程序(alsa-lib)可以访问 ...
- UVA 1640(DFS)
题意:给你a,b两个数 问你a b区间中0 9出现的次数 其实就是求1-n中0-9出现的次数 ans[n] 答案就是ans[b]-ans[a-1] 怎么求的话看代码吧 #include<io ...
- (函数即服务)Faas的现状与未来
刚看到jolestar一位从法律转行程序员的前辈写了一篇Faas现状与未来的文章,里面很多观点都很有启发,或许正如他说的那样,由于Faas能较好的解决资源利用率和开发效率问题,2018年Faas将变得 ...
- C#窗体间传值的简便方法/工具
一.问题:窗体间传值必须需要窗体之间有联系,具体有如下方式 窗体间传值涉及到窗体A必须拥有窗体B,这样才可以实现A-B之间传值 窗体A与窗体B在窗体/实例C中,A-B可互相通讯 其他方式,不细讨论,复 ...
- 开启和安装Kubernetes 基于Docker For Windows
0.最近发现,Docker For Windows Stable在Enable Kubernetes这个问题上是有Bug的,建议切换到Edge版本,并且采用下文AliyunContainerServi ...
- python自动化测试学习笔记-2-列表
上次我们学习了python的基本概念,了解了python的变量及数据类型,并实战了条件判断,for/while循环,字符串输出,格式化输出的一些基本用法,接下来我们继续学习其他的一些数据类型. pyt ...
- 为什么选择Android Studio 而是 Eclipse
Android Studio 现在的版本已经比较稳定了,刚出来时也是各种BUG,自己用了下,摸索了一天,感觉挺好的. 优点之一:代码提示和搜索功能非常强大,非常智能. 1).自定义theme有个名字叫 ...
- Spring Boot (25) RabbitMQ消息队列
MQ全程(Message Queue)又名消息队列,是一种异步通讯的中间件.可以理解为邮局,发送者将消息投递到邮局,然后邮局帮我们发送给具体的接收者,具体发送过程和时间与我们无关,常见的MQ又kafk ...