集合框架----源码解读HashMap篇(一)

news2025/1/19 23:04:07

1.HashMap官方介绍

基于哈希表的Map接口实现。该实现提供了所有可选的映射操作,并允许空值和空键。(HashMap类大致相当于Hashtable,除了它是非同步的,并且允许为空值。)这个类不保证映射的顺序;特别是,它不能保证顺序随时间的推移保持不变。
这个实现为基本操作(get和put)提供了恒定时间的性能,假设哈希函数将元素适当地分散到桶中。在集合视图上迭代所需的时间与HashMap实例的“容量”(桶的数量)加上它的大小(键-值映射的数量)成正比。因此,如果迭代性能很重要,就不要将初始容量设置得太高(或负载因子设置得太低)。
HashMap实例有两个影响其性能的参数:初始容量和负载因子。容量是哈希表中桶的数量,初始容量是创建哈希表时的容量。负载系数衡量的是在哈希表的容量自动增加之前允许达到的满度。当哈希表中的条目数超过负载因子和当前容量的乘积时,哈希表将被重新哈希(即重新构建内部数据结构),以便哈希表的桶数约为原来的两倍。
作为一般规则,默认负载系数(.75)在时间和空间成本之间提供了很好的权衡。较高的值会减少空间开销,但会增加查找成本(反映在HashMap类的大多数操作中,包括get和put)。在设置映射的初始容量时,应考虑映射中期望的条目数及其负载因子,以减少重哈希操作的次数。如果初始容量大于最大条目数除以负载因数,则不会发生重哈希操作。
如果要将许多映射存储在一个HashMap实例中,那么用足够大的容量创建它将允许更有效地存储映射,而不是让它根据需要执行自动重哈希以增长表。注意,在同一个hashCode()中使用多个键肯定会降低任何哈希表的性能。为了改善影响,当键具有可比性时,该类可以使用键之间的比较顺序来帮助打破束缚。
注意,这个实现不是同步的。如果多个线程并发访问一个哈希映射,并且至少有一个线程在结构上修改了映射,那么它必须从外部同步。(结构修改是添加或删除一个或多个映射的任何操作;仅仅改变与实例中已经包含的键相关联的值并不是结构修改。)这通常是通过在自然封装映射的某些对象上同步来实现的。如果不存在这样的对象,则应该使用集合“包装”映射。synchronizedMap方法。这最好在创建时完成,以防止意外的不同步访问映射:
映射m =集合。synchronizedMap(新HashMap(…));
这个类的所有“集合视图方法”返回的迭代器都是快速失败的:如果映射在迭代器创建后的任何时间被结构修改,除了通过迭代器自己的remove方法以外,迭代器将抛出ConcurrentModificationException。因此,在面对并发修改时,迭代器会快速而干净地失败,而不是在未来不确定的时间发生任意的、不确定的行为。
注意,迭代器的快速失败行为不能得到保证,因为一般来说,在存在不同步的并发修改时,不可能做出任何硬保证。快速失败迭代器尽可能地抛出ConcurrentModificationException。因此,编写一个依赖于此异常的程序是错误的:迭代器的快速失败行为应该只用于检测错误。
这个类是Java集合框架的成员。

2.equals方法

 在学习hashMap之前我需要学一下equals的底层原理,创建一个对象,两个完全相等的对象比较结果输出的确是false? 进入equals方法

 进入这个方法我们可以得知 equal方法默认是比较两个对象的内存地址是否相等

 当我们两个字符串相比较输出结果是true,这里之所以是true,是因为string方法重写了equal方法

我们在string方法也发现了equal方法

 这个方法表达的意思很简单,先判断两个对象的内存地址是否一样,再判断两个字符串的长度,最后判断两个字符串的值是否相等,看到这里 你应该猜到 字符串的底层就是一个字节数组

 3.HashCode方法

HashCode方法就是把堆内存地址转换成整数类型,在重写equal方法的同时也要重写hashCode方法

1.如果equal方法比较两个对象相等,则HashCode值也相等

2.如果两个对象的HashCode值相等,但是equal值不一定相等(hash冲突)

 a底层就是97,但是我们人知道97和a是两个东西,但是机器不知道,字符一共有65535个,a就放在97这个位置。机器会任务这个两个就是同一个东西,这就是常说的哈希冲突

 4.HashMap底层原理

主要讲的1.7的HashMap,hashMap 底层就是靠Entry对象进行存储对象的,下图我们可以得知,这里用到了单向列表

 我们之前学过ArrayList,假如我们把Entry放进ArrayList集合厉害呢,来实现HashMap

package com.example.list.list;


import java.util.ArrayList;

public class AHashMap<K, V> {
    /**
     * 基于ArrayList的HashMap 的查询效率极低
     * 可以保证存放键值对是有序的,不是散列的
     */
    //创建一个容器存放
    private ArrayList<Entry<K, V>> arrayListEntry = new ArrayList<Entry<K, V>>();

    /**
     * Entry存放键值对
     */
    class Entry<K, V> {
        K k;
        V v;

        public Entry(K k, V v) {
            this.k = k;
            this.v = v;
        }
    }

    public void put(K k, V v) {
        arrayListEntry.add(new Entry<>(k, v));

    }

    public V get(K k) {
        for (Entry<K, V> entry : arrayListEntry) {
            if(entry.k.equals(k)){
                return entry.v;
            }
            
        }
        return null;
    }


}

 为了解决查询效率低的问题,这里就不得不说一下Key的Hash算法 我用java1.7实现hashmap数组+链表的结构,讲解一下Key的Hash算法 

1.根据key的hashCode取entrys.length 余数就是该key的存放位置
int index = k.hashCode() % entrys.length;
这一行就是key的hash ,Map之所以高效就是利用直观哈希值取余的算法实现高效的查询,用了哈希算法会出现一个致命的问题就是哈希冲突,我们下面的HashMap只是基于数组实现的,发生哈希冲突会把会把之前的一样的key的值覆盖。我学些过Map知道K是唯一的,HashMap又是这么解决的呢?

package com.example.list.list;


public class HHashMap<K, V> {

    private Entry[] entrys = new Entry[10000]; //这里我写死,不进行扩容


    /**
     * Entry存放键值对
     */
    class Entry<K, V> {
        K k;
        V v;

        public Entry(K k, V v) {
            this.k = k;
            this.v = v;
        }
    }

    /**
     * 1.7 数组+链表实现HashMap
     * 1.8 数组+链表+红黑树来实现HashMap
     *
     * @param k
     * @param v
     */

    public void put(K k, V v) {
        //1.根据key的hashCode取entrys.length 余数就是该key的存放位置
        int index = k.hashCode() % entrys.length;
        entrys[index] = new Entry<>(k, v);
    }

    public V get(K k) {
        int index = k.hashCode() % entrys.length;
        return (V)entrys[index].v;


    }


}

在1.7为了解决哈希冲突问题,引入了链表,假设第一次存入的a ,a的HashCode值是97,我们现在把他放在97这个位置。第二次我们存入数字的97,,在1.7版本中第一步拿着这97对应的 HashCode值去找,发现上面有一个a,他就放在a,的下方(不是在98),形成链表。 假如没有发生哈希冲突,HashMap还是数组,有了冲突就会变成 数组+链表(冲突的放在链表)的组合。

 

如果debug运行现在存放的put的哈希冲突问题已经解决了,下面我需要对get方法进行升级,确保我们查的元素就是我们 要的值

package com.example.list.list;



public class HHashMap<K, V> {

    private Entry[] entrys = new Entry[10000]; //这里我写死,不进行扩容


    /**
     * Entry存放键值对
     */
    class Entry<K, V> {
        K k;
        V v;
        int hash; //hash值
        Entry<K, V> next;  //下一个节点

        public Entry(K k, V v) {
            this.k = k;
            this.v = v;
        }
    }

    /**
     * 1.7 数组+链表实现HashMap
     * 1.8 数组+链表+红黑树来实现HashMap
     *
     * @param k
     * @param v
     */

    public void put(K k, V v) {
        /**
         * 1.根据key的hashCode取entrys.length 余数就是该key的存放位置
         * 2.先判断该index对应的index位置
         * 3.如果能够取出Entry对象,则发生哈希冲突,存放在他的下方
         */

        int index = k.hashCode() % entrys.length;
        Entry oldEntry = entrys[index];
        if (oldEntry == null) {
            entrys[index] = new Entry<>(k, v);

        } else {
            oldEntry.next = new Entry<>(k, v);
        }
    }

    public V get(K k) {
        int index = k.hashCode() % entrys.length;
        return (V) entrys[index].v;


    }

    public static void main(String[] args) {
        HHashMap<Object, Object> map = new HHashMap<Object, Object>();
        map.put("a", "a");
        map.put(97, 97);
        System.out.println(map.get(97));
        

    }


}

我第一步去算去对应的hash值,第二步再去遍历这个链表,把值和哈希值相等的数就是我们要查找的数

package com.example.list.list;


public class HHashMap<K, V> {

    private Entry[] entrys = new Entry[10000]; //这里我写死,不进行扩容


    /**
     * Entry存放键值对
     */
    class Entry<K, V> {
        K k;
        V v;
        int hash; //hash值
        Entry<K, V> next;  //下一个节点

        public Entry(K k, V v, int hash) {
            this.k = k;
            this.v = v;
            this.hash = hash;
        }
    }

    /**
     * 1.7 数组+链表实现HashMap
     * 1.8 数组+链表+红黑树来实现HashMap
     *
     * @param k
     * @param v
     */

    public void put(K k, V v) {
        /**
         * 1.根据key的hashCode取entrys.length 余数就是该key的存放位置
         * 2.先判断该index对应的index位置
         * 3.如果能够取出Entry对象,则发生哈希冲突,存放在他的下方
         */
        int hash=k.hashCode();
        int index = hash % entrys.length;
        Entry oldEntry = entrys[index];
        if (oldEntry == null) {
            entrys[index] = new Entry<>(k, v,hash);

        } else {
            oldEntry.next = new Entry<>(k, v,hash);
        }
    }

    public V get(K k) {

        int hash=k.hashCode();
        int index = hash % entrys.length;
        //遍历链表,把hash值和值相等的
        for (Entry<K, V> entry = entrys[index]; entry != null; entry = entry.next) {
        if(entry.hash==hash&&entry.k.equals(k)){
            return entry.v;
        }
        }
        return null;


    }

    public static void main(String[] args) {
        HHashMap<Object, Object> map = new HHashMap<Object, Object>();
        map.put("a", "a");
        map.put(97, 97);
        System.out.println(map.get(97));


    }


}

 其实这里还存在一个潜在问题,假如冲突的元素非常多呢,会导致链表特别长,如果有学习过链表我们就会会知道,这样我们的查询效率就会很慢,为了解决这个问题就引入红黑树。红黑树的英文是“Red-Black Tree”,简称 R-B Tree。它是一种不严格的平衡二叉查找树。

5.HashMap 散列表为什么是无序的

回到这个问题,其实根本原因就是hash冲突造成的,数组index我们将当做根,当HashMap进行遍历会把在index等于0的下面的链表也遍历出来,后面的也一样我们存放链表的数据可能比index等于2后,但是依然会先遍历出来

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

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

相关文章

Nodejs -- Express托管静态资源

文章目录托管静态资源1 expess.static()2 托管多个静态资源目录3 挂载路径前缀托管静态资源 1 expess.static() express提供了一个非常好用的函数&#xff0c;叫做express.static()&#xff0c;通过它&#xff0c;我们可以非常方便地创建一个静态资源服务器&#xff0c;例如&…

PG::FunboxEasyEnum

nmap -Pn -p- -T4 --min-rate1000 192.168.81.132 nmap -Pn -p 22,80 -sCV 192.168.81.132 80端口是Apache2 Ubuntu的默认页面 尝试路径爆破 /mini.php可以进行文件上传 直接上传reverse-php-shell 上传linpeas脚本进行枚举&#xff0c;得到oracle用户的密码hash oracle…

2022-11-28-大数据可视化“可视化国产/进口电影票房榜单”分析,特征维度大于50

可视化国产/进口电影票房榜单前言数据分析数据可视化过程分析总结前言 党的十八大以来&#xff0c;国产电影产业与事业快速发展&#xff0c;创作水平不断提高&#xff0c;题材类型丰富多元&#xff0c;受众口碑不断提升&#xff0c;在市场竞争中表现愈发突出&#xff0c;已成为…

《论文阅读》BA-NET: DENSE BUNDLE ADJUSTMENT NETWORKS

留个笔记自用 BA-NET: DENSE BUNDLE ADJUSTMENT NETWORKS 做什么 首先是最基础的&#xff0c;Structure-from-Motion&#xff08;SFM&#xff09;&#xff0c;SFM可以简单翻译成运动估计&#xff0c;是一种基于dui8序列图片进行三维重建的算法。简单来说就是是从运动中不同角…

【Python】记录从3.9升级到3.11踩的坑

写在前面的话&#xff1a;如果想体验python3.11&#xff0c;不推荐生产环境升级&#xff0c;可以现在测试环境试试看 环境变化 原始环境 Python3.9&#xff0c;有挺多安装的第三方库&#xff0c;有自己写的类和方法&#xff0c;程序一切运行正常 升级环境 Python3.11&#…

如何获取Adreno GPU数据

什么是GPU GPU&#xff08;Graphic Processing Unit&#xff09;是图形处理器&#xff0c;相当于在计算机和移动终端上做图形图像运算工作的微处理器&#xff0c;显示芯片。通过向量计算和并行计算等方式加速了原有的计算工作&#xff0c;能够更好地处理几何转换和光照计算等&a…

如何与意法半导体STMicro建立EDI连接?

项目背景 意法半导体STMicro是全球最大的半导体公司之一&#xff0c;2010 年净收入 103.5 亿美元&#xff0c;2011 年第二季度净收入 25.7亿美元。 以业内最广泛的产品组合著称&#xff0c;凭借多元化的技术、尖端的设计能力、知识产权组合、合作伙伴战略和高效的制造能力&…

pdf怎么编辑?分享两款pdf编辑软件,编辑pdf也很简单!

pdf怎么编辑&#xff1f;其实也很简单&#xff0c;现在跟大家分享两款pdf编辑软件&#xff0c;可以让我们对pdf实现自由编辑修改&#xff0c;有了这两款pdf编辑软件&#xff0c;编辑pdf将不再困难。 pdf编辑软件一&#xff1a;万兴pdf编辑软件 万兴pdf是一款受众广泛&#xff0…

【设计】OOA、OOD、OOP

这三者都是 OO&#xff08;Object-Oriented&#xff09;领域的思想。 一般我们我们接到产品经理的需求后&#xff0c;开发阶段分这样几个步骤&#xff1a; 可行性预研阶段&#xff0c;此阶段评估需求是否合理&#xff0c;能否实现&#xff1b;OOA阶段&#xff0c;此阶段分析用…

【Lilishop商城】No2-5.确定软件架构搭建四(本篇包括消息中间件RocketMQ)

仅涉及后端&#xff0c;全部目录看顶部专栏&#xff0c;代码、文档、接口路径在&#xff1a; 【Lilishop商城】记录一下B2B2C商城系统学习笔记~_清晨敲代码的博客-CSDN博客 全篇只介绍重点架构逻辑&#xff0c;具体编写看源代码就行&#xff0c;读起来也不复杂~ 谨慎&#xff…

Python:如何在 CentOS 8 服务器上运行 Selenium 代码?

前言 因项目需求&#xff0c;需要在 CentOS 8 服务器上运行 Python-Selenium 代码&#xff0c;那么该如何操作呢&#xff1f; 运行环境 CentOS Stream 8Python 3.9.13selenium4.6.0Google Chrome 107.0.5304.121 操作步骤 安装 Google Chrome 下载 Linux 版本的 Chrome 将下…

怎么合并视频?快把这些方法收好

小伙伴们平时会在通过网课来提高自己的技能吗&#xff1f;我经常会在网上保存一系列的视频进行学习&#xff0c;可是当保存的网课视频数量多起来后&#xff0c;每次想要找对应的视频&#xff0c;都得花上不少的时间。其实我们可以通过将相同系列的视频合并起来的方法&#xff0…

java word,excel,ppt转pdf

准备工作 1.下载 jacob.jar 链接&#xff1a;https://pan.baidu.com/s/1TWIGyX9A3xQ6AG9Y3mVlVg 提取码&#xff1a;abcd 2.下载安装wpsWPS Office-支持多人在线编辑多种文档格式_WPS官方网站 3.添加 jar到项目和ddl文件放在jdk的jre/bin目录下&#xff0c;记得自己系统是…

13_cgi

知识点1【cgi实现计算器案例】 2、GET的同步方式&#xff1a; index.html <html><head><title>table</title><meta charset"UTF-8"><!--这是描述 js中的函数来之哪个js文件--><script type"text/javascript" sr…

Kafka基础与核心概念

本文&#xff0c;我们将试图回答什么是apache kafka。 kafka是一个分布式流平台或者分布式消息提交日志 分布式 Kafka 由一个或多个节点组成的工作集群&#xff0c;这些节点可以位于不同的数据中心&#xff0c;我们可以在 Kafka 集群的不同节点之间分布数据/负载&#xff0c;并…

【学习笔记47】开关变量和拖拽效果

一、开关案例 <button>点击获取验证码</button>&#xff08;一&#xff09;基本功能的实现 // 获取标签对象const oBtn document.querySelector(button);// 给按钮添加点击事件oBtn.addEventListener(click, function () {// 定义变量 用于获取验证码let count 5…

javaSE- 方法的使用

一、方法的基本用法 方法就是一个代码片段. 类似于 C 语言中的 “函数”. 方法存在的意义(不要背, 重在体会): 是能够模块化的组织代码(当代码规模比较复杂的时候).做到代码被重复使用, 一份代码可以在多个位置使用.让代码更好理解更简单.直接调用现有方法开发, 不必重复造轮…

04_SpringBoot整合Mybatis

文章目录SpringBoot整合Mybatis0x01_创建项目导入依赖0x02_编写配置文件0x03_编写功能代码SpringBoot整合Mybatis 欢迎关注公众号“小东方不败” 0x01_创建项目导入依赖 创建项目&#xff1a; 目前稳定的最新版本是2.7.5,勾选两个依赖&#xff1a;Lombok和Spring Web 然后需…

Nodejs -- Express 路由原理及设置模块化路由

文章目录1. 路由的概念1.1 什么是路由1.2 显示生活中的路由1.3 Express中的路由1.4 Express中路由的例子1.5 路由的匹配过程2 路由的使用2.1 最简单的用法2.2 模块化路由2.3 为路由模块添加统一前缀1. 路由的概念 1.1 什么是路由 广义上来讲&#xff0c;路由就是映射关系 1.…

yarn : 无法加载文件 C:\Users\sunlight\AppData\Roaming\npm\yarn.ps1,因为在此系统上禁止运行脚本。

问题&#xff1a; yarn安装好了&#xff0c;vscode报错 报错问题&#xff1a;yarn 无法加载文件 C:\Users\sunlight\AppData\Roaming\npm\yarn.ps1&#xff0c;因为在此系统上禁止运行脚本。 具体问题&#xff1a;cmd&#xff08;管理员运行&#xff09;中安装好了yarn &…