前言:

平时工作的时候,用的最多的就是ArrayList和HashMap了,今天看了遍HashMap的源码,决定自己手写一遍HashMap。

一、创建MyHashMap接口

      我们首先创建一个MyHashMap的入口,暴露一个外部调用的接口,里面简单的定义一下putget。

public interface MyHashMap<K,V> {

    public V put(K k,V v);
public V get(K k);
interface Entry<K,V>{
public K getKey();
public V getValue();
} }

二、建一个实现类MyHashMapImpl

      接口定义完成之后,那就要开始实现了,我们首先创建一个类MyHashMapImpl来实现MyHashMap。然后我们定义一些变量。以及构造函数,比如我们定义的数组初始长度为16,加载因子为0.75。这两个参数会涉及到自动扩容,我们后面再说。

public class MyHashMapImpl<K, V> implements MyHashMap<K, V> {
//数组的初始长度
private static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

//阀值比例(加载因子)
private static final float DEFAULT_LOAD_FACTOR = 0.75f;

private int defaultInitSize;

private final float defaultLoadFactor;

//Map当中entry的数量
private int entryUseSize;

//数组
private Entry<K, V>[] table;

//构造函数
public MyHashMapImpl() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}

public MyHashMapImpl(int defaultInitialCapacity, float defaultLoadFactor) {

if (defaultInitialCapacity < 0)
//容量不合规
throw new IllegalArgumentException("Illegal initial capacity" + defaultInitialCapacity);
if (defaultLoadFactor <= 0 || Float.isNaN(defaultLoadFactor))
//不合规的加载因子
throw new IllegalArgumentException("Illegal load factor" + defaultLoadFactor);
this.defaultInitSize = defaultInitialCapacity;
this.defaultLoadFactor = defaultLoadFactor;
table = new Entry[this.defaultInitSize];
}

}

三、重写put方法

我们首先重写下put方法,可以看到,当Map中存储的数据大于加载因子*初始化数据长度的时候,会第一时间触发扩容机制,扩容的过程也就是重新设置一个更大的数组,并把原本的数组地址指过去,并且把原本的值重新put进去。这个过程如果频繁发生还是很消耗机器性能的,所以我们在写代码的时候最好是预估好初始大小,尽量不触发扩容机制。

 @Override
public V put(K k, V v) {
V oldValue;
//是否需要扩容
//扩容完毕,肯定需要重新散列
if (entryUseSize >= defaultInitSize * defaultLoadFactor) {
resize(2 * defaultInitSize);
}
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 null;
} 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());
} } class Entry<K, V> implements MyHashMap.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;
} }

四、重写get方法

      如果要拿到数组中的值,我们首先要获取对应的位置。其中有一个基本概念要说一下,每一个数据通过hash函数都会得到一个值,并且这个值是固定的,所以我们可以通过k.hashCode()

来获取对应的hash值,然后按照散列算法均匀分散hash值,然后通过hashcode获取对应的值,得到基本数组的下标。这时候就能拿到我们存在map中的值了,但是hash值并不是一定是唯一的,也就是说可以能a.hash和b.hash值是一样的,但是a不等于b,所以如果两个数据hash值相同,会触发hash冲突。严重降低hashmap的性能,本次hash方法的作用也就是尽量减少hash冲突。使数据排列的更加均匀一些。当我们遇到hash冲突的时候可以再次hash解决冲突。

  @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;
}

手撕HashMap的更多相关文章

  1. 手写HashMap,快手面试官直呼内行!

    手写HashMap?这么狠,面试都卷到这种程度了? 第一次见到这个面试题,是在某个不方便透露姓名的Offer收割机大佬的文章: 这--我当时就麻了,我们都知道HashMap的数据结构是数组+链表+红黑 ...

  2. Netty实现高性能IOT服务器(Groza)之手撕MQTT协议篇上

    前言 诞生及优势 MQTT由Andy Stanford-Clark(IBM)和Arlen Nipper(Eurotech,现为Cirrus Link)于1999年开发,用于监测穿越沙漠的石油管道.目标 ...

  3. 手撕RPC框架

    手撕RPC 使用Netty+Zookeeper+Spring实现简易的RPC框架.阅读本文需要有一些Netty使用基础. 服务信息在网络传输,需要讲服务类进行序列化,服务端使用Spring作为容器.服 ...

  4. 手写HASHMAP

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

  5. NN入门,手把手教你用Numpy手撕NN(一)

    前言 这是一篇包含极少数学推导的NN入门文章 大概从今年4月份起就想着学一学NN,但是无奈平时时间不多,而且空闲时间都拿去做比赛或是看动漫去了,所以一拖再拖,直到这8月份才正式开始NN的学习. 这篇文 ...

  6. NN入门,手把手教你用Numpy手撕NN(2)

    这是一篇包含较少数学推导的NN入门文章 上篇文章中简单介绍了如何手撕一个NN,但其中仍有可以改进的地方,将在这篇文章中进行完善. 误差反向传播 之前的NN计算梯度是利用数值微分法,虽容易实现,但是计算 ...

  7. 手撕公司SSO登陆原理

    Single Sign-on SSO是老生常谈的话题了,但部分同学对SSO可能掌握的也是云里雾里,一知半解.本次手撕公司的SSO登陆原理,试图以一种简单,流畅的形式为你提供 有用的SSO登陆原理. 按 ...

  8. NN入门,手把手教你用Numpy手撕NN(三)

    NN入门,手把手教你用Numpy手撕NN(3) 这是一篇包含极少数学的CNN入门文章 上篇文章中简单介绍了NN的反向传播,并利用反向传播实现了一个简单的NN,在这篇文章中将介绍一下CNN. CNN C ...

  9. 手撕代码:统计1到n二进制数中1出现的总次数

    题目描述: 互娱手撕代码题. 统计从1到n这n个数的二进制表示中1出现的次数. 思路分析: 思路一:直接的做法是从1遍历到n,对于每个数和1做与操作,之后,对于这个数不断做右移操作,不断和1做与操作, ...

随机推荐

  1. python实现类的多态

    多态 关注公众号"轻松学编程"了解更多. 1.多态使用 一种事物的多种体现形式,举例:动物有很多种 注意: 继承是多态的前提 函数重写就是多态的体现形式 演示:重写Animal类 ...

  2. mkdir()和mkdirs()区别

    mkdir()和mkdirs()区别如下: mkdirs()可以建立多级文件夹, mkdir()只会建立一级的文件夹, 如下: new File("/tmp/one/two/three&qu ...

  3. 你说一下对Java中的volatile的理解吧

    前言 volatile相关的知识其实自己一直都是有掌握的,能大概讲出一些知识,例如:它可以保证可见性:禁止指令重排.这两个特性张口就来,但要再往深了问,具体是如何实现这两个特性的,以及在什么场景下使用 ...

  4. 「SHOI2014」三叉神经树

    「SHOI2014」三叉神经树 给你一颗由\(n\)个非叶子结点和\(2n+1\)个叶子结点构成的完全三叉树,每个叶子结点有一个输出:\(0\)或\(1\),每个非叶子结点的输出为自己的叶子结点中较多 ...

  5. 2020提高组模拟赛7 StormWind

    StormWind 中文 切换语言(Change Language) 时间:4s   空间:512M 题目描述: 风暴城建造的防线错综复杂,可以抽象成一个有$n$个点$m$条边的有向拓扑图,暴风城的最 ...

  6. 【QT】子类化QObject+moveToThread实现多线程

    往期链接: <QThread源码浅析> <子类化QThread实现多线程> 从往期<QThread源码浅析>可知,在Qt4.4之前,run 是纯虚函数,必须子类化Q ...

  7. 【SpringCloud】04.SpringCloud Eureka Server与Client的创建

    Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的.SpringCloud将它集成在其子项 ...

  8. 4G模块与WIFI模块的工作及应用区别

    在物联网行业中,4G模块和wifi模块经经常会被使用,但是由于网络不同,二者的工作原理和场景还是有很大的不同,本篇主要讲讲4G模块和WIFI模块的功能和应用场景 什么是4G模块? 4G模块是基于4G网 ...

  9. 9.集合set和frozenset冻结集合函数

    集合set set和dict类似,也是一组key的集合,但不存储value.由于key不能重复,所以在set中没有重复的key. 集合中的元素要求是不可变的并且还是唯一的,我们就利用它是唯一来做去重. ...

  10. ubuntu下minicom安装和简单设置使用

    minicom是一个串口通信工具. Ubuntu下的安装: 打开终端 sudo apt-get install minicom即可完成安装. 简单设置使用: 安装完成后第一次启动时运行 sudo mi ...