入坑java很多年了,现在总结一下自己学到的东西。

1、首先我们先来聊聊什么是HashMap?
  什么是hash?hash用中文的说法就叫做“散列”,通俗的讲就是把任意长度的字符串输入,经过hash计算出固定长度的字符串。而这个字符串就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,而不可能从散列值来唯一的确定输入值。
2、hash碰撞?
  上面已经说到了,不同的输入值可能会计算出相同的输出,这就是碰撞。
3、java中的hashMap是怎么实现的?
  java的hashMap底层是用数组加链表实现的。存储对象的时候,我们是将K/V传给put方法,然后这个put方法会调用hashCode方法计算出hash值(也就是数组的下标)从而得到存储的位置,进一步存储。那么问题来了,我们都知道,创建数组的时候是要指定数组的长度的,万一它的初始化的数组不够用怎么办?其实hashMap早都给你想好了,hashMap会根据当前你存储的数据的占用情况自动调整数组的长度。获取对象的时候,我们是把K传给get()方法,它就会调用hashCode计算hash的值从而得到存储位置,并且进一步调用equals方法确定键值对。发生碰撞的时候:HashMap通过链表将产生碰撞冲突的元素组织起来,在Java8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。关于解决hash冲突的方法在这里暂且不说,我直接上代码了。
4、代码
  
首先我们先定义一个Map接口:

package com.jian.utils;
/**
* Map接口
* @author weijianyi
*
* @param <K>
* @param <V>
*/
public interface Map<K,V> { //向Map中插入值
public V put(K k,V v); //根据key获取hashMap中的值
public V get(K k); //获得Map中元素的个数
public int size(); //获取Map中,键值对的对象
interface Entry<K,V>{
K getKey(); V getValue(); V setValue(V v); }
}

第二步定义我们的MyhashMap类:

package com.jian.utils;
/**
* hashMap的实现 数组+链表
*
* @author weijianyi
*
* @param <K>
* @param <V>
*/
public class HashMap<K, V> implements Map<K, V> {
// 数据存储的结构==>数组+链表
Node<K, V>[] array = null; // 数组/哈希桶的长度,就是数组初始化的长度
private static int defaultLength = 16; /**加载因子/扩容因子,就是给数组界定一个存储阈值,
当数组沾满整个数组的75%的时候,触发扩容操作)**/
private static double factor = 0.75D; // hashMap中的元素个数
private int size; // put元素方法
@SuppressWarnings("unchecked")
@Override
public V put(K k, V v) { // 1.载机制,使用的时候进行分配
if (array == null) {
//初始化一个数组,给的长度是defaultLength
array = new Node[defaultLength];
} // 2.通过hash算法,计算出具体插入的位置,也就是数组的下标
int index = position(k, defaultLength); // 扩容,判断是否需要扩容
// 扩容规则,元素的个数size 大于 桶的尺寸*加载因子
if (size > defaultLength * factor) {
resize();
} // 3.放入要插入的元素(添加到链表)
Node<K, V> node = array[index];
//先判断一下这个链表的index位置是否为空
if (node == null) {
//链表的index这个位置是空的,直接新建该链表并将值放入该位置
array[index] = new Node<K, V>(k, v, null);
//元素的个数加自增1
size++;
} else {
if (k.equals(node.getKey()) || k == node.getKey()) {
return node.setValue(v);
} else {
array[index] = new Node<K, V>(k, v, node);
size++;
}
} return null;
} // 扩容,并且重新排列元素
private void resize() {
// 翻倍扩容
// 1.创建新的array临时变量,相当于defaultlength*2
@SuppressWarnings("unchecked")
Node<K, V>[] temp = new Node[defaultLength << 1]; // 2.重新计算散列值,插入到新的array中去。 code=key % defaultLength ==> code=key %
// defaultLength*2
Node<K, V> node = null;
for (int i = 0; i < array.length; i++) {
node = array[i];
while (node != null) {
// 重新散列
int index = position(node.getKey(), temp.length);
// 插入链表的头部
Node<K, V> next = node.next;
//
node.next = temp[index];
//
temp[index] = node;
//
node = next; }
} // 3.替换掉旧的array
array = temp;
//更新默认的扩容因子的值
defaultLength = temp.length;
temp = null; } // 计算位置
private int position(K k, int length) {
int code = k.hashCode(); // 取模算法
return code % (length - 1); // 求与算法
// return code & (defaultLength-1);
}
/**
* 用K获取hashMap 的K对应的值
*/
@Override
public V get(K k) {
if (array != null) {
int index = position(k, defaultLength);
Node<K, V> node = array[index];
// 遍历链表
while (node != null) {
// 如果key值相同返回value
if (node.getKey() == k) {
return node.getValue();
} else {
// 如果key值不同则调到下一个元素
node = node.next;
}
}
} return null;
}
/**
* 获取hashMap元素个数
*/
@Override
public int size() { return size;
} // 链表节点(链表类)
static class Node<K, V> implements Entry<K, V> {
K key;
V value;
//表示下一个节点
Node<K, V> next; // 构造一个包含当前节点和下一个节点的链表
public Node(K key, V value, Node<K, V> next) {
super();
this.key = key;
this.value = value;
this.next = next;
}
/**
* 链表的get和set方法
*/
@Override
public K getKey() {
return this.key;
} @Override
public V getValue() {
return this.value;
} @Override
public V setValue(V v) {
V oldValue = this.value;
this.value = v;
return oldValue;
} } // 测试方法
public void print() {
System.out.println("===============================");
if (array != null) {
Node<K, V> node = null;
for (int i = 0; i < array.length; i++) {
node = array[i];
System.out.print("下标[" + i + "]");
// 遍历链表
while (node != null) {
System.out.print("[" + node.getKey() + ":" + node.getValue() + "]");
if (node.next != null) {
node = node.next;
} else {
// 到尾部元素
node = null;
}
}
System.out.println();
} }
}
}

到此,自己动手写一个hashMap结束,其实并不难,只要大家认真的去看他每一个步骤和方法流程,我相信大家都能自己写出一个。我这里只是写了一个简易的,如果有什么地方不对或者有什么修改意见,欢迎大家评论探讨,我们一起进步。

自己动手用java写一个hashMap的更多相关文章

  1. 用JAVA写一个函数,功能例如以下: 随意给定一组数, 找出随意数相加之后的结果为35(随意设定)的情况

    用JAVA写一个函数.功能例如以下:随意给定一组数,比如{12,60,-8,99,15,35,17,18},找出随意数相加之后的结果为35(随意设定)的情况. 能够递归算法来解: package te ...

  2. 五:用JAVA写一个阿里云VPC Open API调用程序

    用JAVA写一个阿里云VPC Open API调用程序 摘要:用JAVA拼出来Open API的URL 引言 VPC提供了丰富的API接口,让网络工程是可以通过API调用的方式管理网络资源.用程序和软 ...

  3. 用java写一个servlet,可以将放在tomcat项目根目录下的文件进行下载

    用java写一个servlet,可以将放在tomcat项目根目录下的文件进行下载,将一个完整的项目进行展示,主要有以下几个部分: 1.servlet部分   Export 2.工具类:TxtFileU ...

  4. 用JAVA写一个多线程程序,写四个线程,其中二个对一个变量加1,另外二个对一个变量减1

    package com.ljn.base; /** * @author lijinnan * @date:2013-9-12 上午9:55:32 */ public class IncDecThrea ...

  5. 使用JAVA写一个简单的日历

    JAVA写一个简单的日历import java.text.DateFormat;import java.text.ParseException;import java.text.SimpleDateF ...

  6. 学了编译原理能否用 Java 写一个编译器或解释器?

    16 个回答 默认排序​ RednaxelaFX JavaScript.编译原理.编程 等 7 个话题的优秀回答者 282 人赞同了该回答 能.我一开始学编译原理的时候就是用Java写了好多小编译器和 ...

  7. java 写一个"HelloJavaWorld你好世界"输出到操作系统文件Hello.txt文件中

    package com.beiwo.homework; import java.io.File; import java.io.FileOutputStream; import java.io.IOE ...

  8. Java写一个简单学生管理系统

    其实作为一名Java的程序猿,无论你是初学也好,大神也罢,学生管理系统一直都是一个非常好的例子,初学者主要是用数组.List等等来写出一个简易的学生管理系统,二.牛逼一点的大神则用数据库+swing来 ...

  9. 用java写一个用户登陆界面

    一.课堂测试源代码及其结果截图 用java的swing写一个用户登录界面,采用网格布局.源代码如下: /** * */package LiuLijia; import java.awt.CardLay ...

随机推荐

  1. winform把所有dll打包成一个exe

    大家都知道做winform开发,是可以利用visual studio进行打包的,但是这种打包的方式需要双击安装,那么有没有什么方法,可以把winform程序打包成绿色版呢?当然,这里的“绿色版”也是相 ...

  2. spi设备描述过程

    一.spi通信 中控制器驱动及spi设备.spi设备驱动的关系入下图: 控制器驱动以及设备全志已经完成,在/driver/spi/spi--sunxi.c  中,打开源码文件可以看到spi控制器属于平 ...

  3. Java复习(二)类与对象的基本概念

    2.1面向对象的程序设计方法概述 对象 程序中: 一切皆是对象 都具有标识,属性和行为 通过一个或多个变量来保存其状态 通过方法实现他的行为 类 将属性及行为相同或相似的对象归为一类 类可以看成是对象 ...

  4. element ui 自定义异步验证

    之前提到过,axios是一个异步请求,但是很多时候我们都需要同步请求,比如在element的表单验证中需要验证一个用户名是否存在的时候,异步请求好像就不太好用了.前边博客中提到过,这种情况可以用es6 ...

  5. 在select2插件中append下拉选,点击没反应的解决

    今天前端大佬帮我解决了一个棘手的问题:克隆了已有系统的网页,尝试把复制下来的html改造成jsp.基本功能正常,然而点击新增按钮,出来的行无法点击下拉选,控制台也没报错. 项目用的是jeesite2. ...

  6. 一个搜索框的小demo

    一.实时按照输入的搜索值显示与其匹配的内容,隐藏其它内容 <%@ Page Language="C#" AutoEventWireup="true" Co ...

  7. linux进程(一)

    回顾:CentOS6的启动过程开机自检->找硬盘->操作系统->内核->进程->登录 Systemd借鉴了很多launchd的思想,他的重要特性如下:1.同SysVini ...

  8. java连接access的用户名、密码异常Decoding not supported解决

    Java通过ucanaccess对Access数据库.accdb文件连接: public static Connection getConn() { try { String dbURL = &quo ...

  9. wareshark判断一个http请求链接是否断开

    使用curl -v www.baidu.com发送一个请求 使用wareshark的过滤器表达式显示这个完整请求 TCP HTTP协议 , 其中192.168.1.4是本地ip 可以看到84 85两个 ...

  10. Qt Sleep、QCoreApplication::processEvents()(最佳的平衡:一边发送消息,一边睡眠)

    sleep()//秒 msleep()//毫秒 usleep()//微秒 以前为了模拟鼠标点击用过这些函数,可以让进程中断,今天发现我原来的做法其实不对.这组函数会将你当前的线程/进程变为“睡眠”状态 ...