java基础学习——5、HashMap实现原理

一、HashMap的数据结构

  数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表,哈希表有多种不同的实现方法,我接下来解释的是最常用的一种方法—— 拉链法,我们可以理解为“链表的数组” ,如图:

从上图我们可以发现哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。

  HashMap其实也是一个线性的数组实现的,所以可以理解为其存储数据的容器就是一个线性数组。这可能让我们很不解,一个线性的数组怎么实现按键值对来存取数据呢?这里HashMap有做一些处理。

  1.首先HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。

二、HashMap的存取实现

     既然是线性数组,为什么能随机存取?这里HashMap用了一个小算法,大致是这样实现:

//存储时:
int hash = key.hashCode();// 这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值
int index = hash % Entry[].length;
Entry[index] = value;//取值时:
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];

到这里我们轻松的理解了HashMap通过键值对实现存取的基本原理

    3.疑问:如果两个key通过hash%Entry[].length得到的index相同,会不会有覆盖的危险?

  这里HashMap里面用到链式数据结构的一个概念。上面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。打个比方, 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。所以疑问不用担心。也就是说数组中存储的是最后插入的元素。到这里为止,HashMap的大致实现,我们应该已经清楚了。

  当然HashMap里面也包含一些优化方面的实现,这里也说一下。比如:Entry[]的长度一定后,随着map里面数据的越来越长,这样同一个index的链就会很长,会不会影响性能?HashMap里面设置一个因素(也称为因子),随着map的size越来越大,Entry[]会以一定的规则加长长度。

三、解决hash冲突的办法

  1. 开放定址法(线性探测再散列,二次探测再散列,伪随机探测再散列)
  2. 再哈希法
  3. 链地址法
  4. 建立一个公共溢出区

Java中hashmap的解决办法就是采用的链地址法。

四、实现自己的HashMap

Entry.java

package edu.sjtu.erplab.hash;public class Entry<K,V>{final K key;V value;Entry<K,V> next;//下一个结点//构造函数public Entry(K k, V v, Entry<K,V> n) {key = k;value = v;next = n;}public final K getKey() {return key;}public final V getValue() {return value;}public final V setValue(V newValue) {V oldValue = value;value = newValue;return oldValue;}public final boolean equals(Object o) {if (!(o instanceof Entry))return false;Entry e = (Entry)o;Object k1 = getKey();Object k2 = e.getKey();if (k1 == k2 || (k1 != null && k1.equals(k2))) {Object v1 = getValue();Object v2 = e.getValue();if (v1 == v2 || (v1 != null && v1.equals(v2)))return true;}return false;}public final int hashCode() {return (key==null   ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode());}public final String toString() {return getKey() + "=" + getValue();}}
View Code

MyHashMap.java

package edu.sjtu.erplab.hash;//保证key与value不为空
public class MyHashMap<K, V> {private Entry[] table;//Entry数组表static final int DEFAULT_INITIAL_CAPACITY = 16;//默认数组长度private int size;// 构造函数public MyHashMap() {table = new Entry[DEFAULT_INITIAL_CAPACITY];size = DEFAULT_INITIAL_CAPACITY;}//获取数组长度public int getSize() {return size;}// 求indexstatic int indexFor(int h, int length) {return h % (length - 1);}//获取元素public V get(Object key) {if (key == null)return null;int hash = key.hashCode();// key的哈希值int index = indexFor(hash, table.length);// 求key在数组中的下标for (Entry<K, V> e = table[index]; e != null; e = e.next) {Object k = e.key;if (e.key.hashCode() == hash && (k == key || key.equals(k)))return e.value;}return null;}// 添加元素public V put(K key, V value) {if (key == null)return null;int hash = key.hashCode();int index = indexFor(hash, table.length);// 如果添加的key已经存在,那么只需要修改value值即可for (Entry<K, V> e = table[index]; e != null; e = e.next) {Object k = e.key;if (e.key.hashCode() == hash && (k == key || key.equals(k))) {V oldValue = e.value;e.value = value;return oldValue;// 原来的value值
            }}// 如果key值不存在,那么需要添加Entry<K, V> e = table[index];// 获取当前数组中的etable[index] = new Entry<K, V>(key, value, e);// 新建一个Entry,并将其指向原先的ereturn null;}}
View Code

MyHashMapTest.java

package edu.sjtu.erplab.hash;public class MyHashMapTest {public static void main(String[] args) {MyHashMap<Integer, Integer> map = new MyHashMap<Integer, Integer>();map.put(1, 90);map.put(2, 95);map.put(17, 85);System.out.println(map.get(1));System.out.println(map.get(2));System.out.println(map.get(17));System.out.println(map.get(null));}
}
View Code

转载于:https://www.cnblogs.com/doubiwan/p/7246137.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/391877.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

看懂nfl定理需要什么知识_NFL球队为什么不经常通过?

看懂nfl定理需要什么知识Debunking common NFL myths in an analytical study on the true value of passing the ball在关于传球真实价值的分析研究中揭穿NFL常见神话 Background背景 Analytics are not used enough in the NFL. In a league with an abundance of money, i…

Docker初学者指南-如何创建您的第一个Docker应用程序

您是一名开发人员&#xff0c;并且想要开始使用Docker&#xff1f; 本文是为您准备的。 (You are a developer and you want to start with Docker? This article is made for you.) After a short introduction on what Docker is and why to use it, you will be able to cr…

mybatis if-else(写法)

mybaits 中没有else要用chose when otherwise 代替 范例一 <!--批量插入用户--> <insert id"insertBusinessUserList" parameterType"java.util.List">insert into business_user (id , user_type , user_login )values<foreach collection…

spring—拦截器和异常

SpringMVC的拦截器 SpringMVC拦截器-拦截器的作用 Spring MVC 的拦截器类似于 Servlet 开发中的过滤器 Filter&#xff0c;用于对处理器进行预处理和后处理。 将拦截器按一定的顺序联结成一条链&#xff0c;这条链称为拦截器链&#xff08;InterceptorChain&#xff09;。在…

29/07/2010 sunrise

** .. We can only appreciate the miracle of a sunrise if we have waited in the darkness .. 人们在黑暗中等待着&#xff0c;那是期盼着如同日出般的神迹出现 .. 附&#xff1a;27/07/2010 sunrise ** --- 31 July 改动转载于:https://www.cnblogs.com/orderedchaos/archi…

密度聚类dbscan_DBSCAN —基于密度的聚类方法的演练

密度聚类dbscanThe idea of having newer algorithms come into the picture doesn’t make the older ones ‘completely redundant’. British statistician, George E. P. Box had once quoted that, “All models are wrong, but some are useful”, meaning that no model…

node aws 内存溢出_在AWS Elastic Beanstalk上运行生产Node应用程序的现实

node aws 内存溢出by Jared Nutt贾里德努特(Jared Nutt) 在AWS Elastic Beanstalk上运行生产Node应用程序的现实 (The reality of running a production Node app on AWS Elastic Beanstalk) 从在AWS的ELB平台上运行生产Node应用程序两年的经验教训 (Lessons learned from 2 y…

Day2-数据类型

数据类型与内置方法 数据类型 数字字符串列表字典元组集合字符串 1.用途 用来描述某个物体的特征&#xff1a;姓名&#xff0c;性别&#xff0c;爱好等 2.定义方式 变量名 字符串 如&#xff1a;name huazai 3.常用操作和内置方法 1.按索引取值&#xff1a;&#xff08;只能取…

嵌套路由

父组件不能用精准匹配&#xff0c;否则只组件路由无法展示 转载于:https://www.cnblogs.com/dianzan/p/11308146.html

leetcode 992. K 个不同整数的子数组(滑动窗口)

给定一个正整数数组 A&#xff0c;如果 A 的某个子数组中不同整数的个数恰好为 K&#xff0c;则称 A 的这个连续、不一定独立的子数组为好子数组。 &#xff08;例如&#xff0c;[1,2,3,1,2] 中有 3 个不同的整数&#xff1a;1&#xff0c;2&#xff0c;以及 3。&#xff09; …

从完整的新手到通过TensorFlow开发人员证书考试

I recently graduated with a bachelor’s degree in Civil Engineering and was all set to start with a Master’s degree in Transportation Engineering this fall. Unfortunately, my plans got pushed to the winter term because of COVID-19. So as of January this y…

微信开发者平台如何编写代码_编写超级清晰易读的代码的初级开发者指南

微信开发者平台如何编写代码Writing code is one thing, but writing clean, readable code is another thing. But what is “clean code?” I’ve created this short clean code for beginners guide to help you on your way to mastering and understanding the art of c…

【转】PHP面试题总结

PHP面试总结 PHP基础1&#xff1a;变量的传值与引用。 2&#xff1a;变量的类型转换和判断类型方法。 3&#xff1a;php运算符优先级&#xff0c;一般是写出运算符的运算结果。 4&#xff1a;PHP中函数传参&#xff0c;闭包&#xff0c;判断输出的echo&#xff0c;print是不是函…

Winform控件WebBrowser与JS脚本交互

1&#xff09;在c#中调用js函数 如果要传值&#xff0c;则可以定义object[]数组。 具体方法如下例子&#xff1a; 首先在js中定义被c#调用的方法: function Messageaa(message) { alert(message); } 在c#调用js方法Messageaa private void button1_Click(object …

从零开始撸一个Kotlin Demo

####前言 自从google将kotlin作为亲儿子后就想用它撸一管app玩玩&#xff0c;由于工作原因一直没时间下手&#xff0c;直到项目上线后才有了空余时间&#xff0c;期间又由于各种各样烦人的事断了一个月&#xff0c;现在终于开发完成项目分为服务器和客户端&#xff1b;服务器用…

移动平均线ma分析_使用动态移动平均线构建交互式库存量和价格分析图

移动平均线ma分析I decided to code out my own stock tracking chart despite a wide array of freely available tools that serve the same purpose. Why? Knowledge gain, it’s fun, and because I recognize that a simple project can generate many new ideas. Even t…

敏捷开发创始人_开发人员和技术创始人如何将他们的想法转化为UI设计

敏捷开发创始人by Simon McCade西蒙麦卡德(Simon McCade) 开发人员和技术创始人如何将他们的想法转化为UI设计 (How developers and tech founders can turn their ideas into UI design) Discover how to turn a great idea for a product or service into a beautiful UI de…

在ubuntu怎样修改默认的编码格式

ubuntu修改系统默认编码的方法是&#xff1a;1. 参考 /usr/share/i18n/SUPPORTED 编辑/var/lib/locales/supported.d/* gedit /var/lib/locales/supported.d/localgedit /var/lib/locales/supported.d/zh-hans如&#xff1a;more /var/lib/locales/supported.d/localzh_CN GB18…

JAVA中PO,BO,VO,DTO,POJO,Entity

https://my.oschina.net/liaodo/blog/2988512转载于:https://www.cnblogs.com/dianzan/p/11311217.html

【Lolttery】项目开发日志 (三)维护好一个项目好难

项目的各种配置开始出现混乱的现象了 在只有一个人开发的情况下也开始感受到维护一个项目的难度。 之前明明还好用的东西&#xff0c;转眼就各种莫名其妙的报错&#xff0c;完全不知道为什么。 今天一天的工作基本上就是整理各种配置。 再加上之前数据库设计出现了问题&#xf…