前言

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实现的更多相关文章

  1. 使用Java Socket手撸一个http服务器

    原文连接:使用Java Socket手撸一个http服务器 作为一个java后端,提供http服务可以说是基本技能之一了,但是你真的了解http协议么?你知道知道如何手撸一个http服务器么?tomc ...

  2. 通过 Netty、ZooKeeper 手撸一个 RPC 服务

    说明 项目链接 微服务框架都包括什么? 如何实现 RPC 远程调用? 开源 RPC 框架 限定语言 跨语言 RPC 框架 本地 Docker 搭建 ZooKeeper 下载镜像 启动容器 查看容器日志 ...

  3. 《Spring 手撸专栏》第 3 章:初显身手,运用设计模式,实现 Bean 的定义、注册、获取

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你是否能预见复杂内容的设计问题? 讲道理,无论产品功能是否复杂,都有很大一部分程序员 ...

  4. 手撸一个springsecurity,了解一下security原理

    手撸一个springsecurity,了解一下security原理 转载自:www.javaman.cn 手撸一个springsecurity,了解一下security原理 今天手撸一个简易版本的sp ...

  5. 五分钟,手撸一个Spring容器!

    大家好,我是老三,Spring是我们最常用的开源框架,经过多年发展,Spring已经发展成枝繁叶茂的大树,让我们难以窥其全貌. 这节,我们回归Spring的本质,五分钟手撸一个Spring容器,揭开S ...

  6. php手撸轻量级开发(一)

    聊聊本文内容 之前讲过php简单的内容,但是原生永远是不够看的,这次用框架做一些功能性的事情. 但是公司用自己的框架不能拿出来,用了用一些流行的框架比如tp,larveral之类的感觉太重,CI也不顺 ...

  7. 手写HASHMAP

    手写HASHMAP const int MAXN=10010; const int HASH=10100;            //需要hash的数的总个数最大值 struct HASHMAP { ...

  8. 【手撸一个ORM】MyOrm的使用说明

    [手撸一个ORM]第一步.约定和实体描述 [手撸一个ORM]第二步.封装实体描述和实体属性描述 [手撸一个ORM]第三步.SQL语句构造器和SqlParameter封装 [手撸一个ORM]第四步.Ex ...

  9. 康少带你手撸orm

    orm 什么是orm? 对象关系映射: 一个类映射成一张数据库的表 类的对象映射成数据库中的一条条数据 对象点数据映射成数据库某条记录的某个值 优点:不会写sql语句的程序员也可以很6的操作sql语句 ...

随机推荐

  1. Redis缓存数据库安全加固指导(一)

    背景 在众多开源缓存技术中,Redis无疑是目前功能最为强大,应用最多的缓存技术之一,参考2018年国外数据库技术权威网站DB-Engines关于key-value数据库流行度排名,Redis暂列第一 ...

  2. mysql安装后改动port号password默认字符编码

    1.改动password grant all privileges on *.* to 'root'@'localhost' identified by 'new password'; 2.改动por ...

  3. Linux ALSA声卡驱动之四:Control设备的创建

    声明:本博内容均由http://blog.csdn.net/droidphone原创,转载请注明出处,谢谢! Control接口 Control接口主要让用户空间的应用程序(alsa-lib)可以访问 ...

  4. UVA 1640(DFS)

    题意:给你a,b两个数 问你a b区间中0 9出现的次数 其实就是求1-n中0-9出现的次数 ans[n]   答案就是ans[b]-ans[a-1] 怎么求的话看代码吧 #include<io ...

  5. (函数即服务)Faas的现状与未来

    刚看到jolestar一位从法律转行程序员的前辈写了一篇Faas现状与未来的文章,里面很多观点都很有启发,或许正如他说的那样,由于Faas能较好的解决资源利用率和开发效率问题,2018年Faas将变得 ...

  6. C#窗体间传值的简便方法/工具

    一.问题:窗体间传值必须需要窗体之间有联系,具体有如下方式 窗体间传值涉及到窗体A必须拥有窗体B,这样才可以实现A-B之间传值 窗体A与窗体B在窗体/实例C中,A-B可互相通讯 其他方式,不细讨论,复 ...

  7. 开启和安装Kubernetes 基于Docker For Windows

    0.最近发现,Docker For Windows Stable在Enable Kubernetes这个问题上是有Bug的,建议切换到Edge版本,并且采用下文AliyunContainerServi ...

  8. python自动化测试学习笔记-2-列表

    上次我们学习了python的基本概念,了解了python的变量及数据类型,并实战了条件判断,for/while循环,字符串输出,格式化输出的一些基本用法,接下来我们继续学习其他的一些数据类型. pyt ...

  9. 为什么选择Android Studio 而是 Eclipse

    Android Studio 现在的版本已经比较稳定了,刚出来时也是各种BUG,自己用了下,摸索了一天,感觉挺好的. 优点之一:代码提示和搜索功能非常强大,非常智能. 1).自定义theme有个名字叫 ...

  10. Spring Boot (25) RabbitMQ消息队列

    MQ全程(Message Queue)又名消息队列,是一种异步通讯的中间件.可以理解为邮局,发送者将消息投递到邮局,然后邮局帮我们发送给具体的接收者,具体发送过程和时间与我们无关,常见的MQ又kafk ...