再探Java集合系列—HashMap

news2024/11/16 21:38:58

前面我们已经针对LinkedList和ArrayList的底层原理进行了具体研究讨论,大家可以跳链接阅读哦~

再探Java集合系列—ArrayList-CSDN博客

再探Java集合系列—LinkedList-CSDN博客

HashMap有哪些特征呢?

  • value可以重复,key不能重复,允许null键和null值(如果新添加key-value的Map中已经存在重复的key,那么新添加的value就会覆盖该key原来对应的value)
  • 不能保证key-value对的顺序
  • 如果添加相同的key,则会覆盖原来的key-val,等同于修改
  • 没有实现同步,因此线程不安全,方法没有做同步互斥操作,

如何使用HashMap呢?

Map<k,v> map =new HashMap<k,v>();

Map是一种键-值对(key-value)集合,Map中每一个元素都包含一个键对象和一个值对象。

元素:键-值对整体

因为Map中的key和value是不允许使用基本类型的,那为什么呢?

Java中的基本类型和包装类型之间存在自动装箱(autoboxing)和拆箱(unboxing)的过程。当我们将基本类型的值放入Map中时,Java会自动将其转换为对应的包装类型;当我们从Map中取出包装类型的值时,Java会自动将其转换为对应的基本类型。这个过程会带来一些性能上的损耗,特别是在大量数据操作时

Map有哪些方法?

  • put(key,value):添加数据
  • get(key,value):根据key取值
  • containsKey(key):判断当前的map集合是否包含指定的key
  • containsValue(value):判断当前的map集合是否包含指定的value
  • clear:清空集合
  • keySet():获取map集合的key的集合
  • values():获取集合的所有value的值
  • for(String key:keys):遍历map集合

实战演练

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class HashMapTest {
    public static void main(String[] args) {
        Map<String,Integer> map = new HashMap<String,Integer>();
        //添加数据
        map.put("b",1);
        map.put("c",2);
        map.put("e",2);
        System.out.println(map);   //输出结果:{b=1, c=2, e=2}

        //根据key取值
        System.out.println(map.get("b")); //输出结果:1

        //根据key移除键值对
        map.remove("c");
        System.out.println(map);  //输出结果:{b=1, e=2}

        //map集合的长度
        System.out.println(map.size());  //输出结果:2

        //判断当前的map集合是否包含指定的key
        System.out.println(map.containsKey("b"));  //输出结果:true
        //判断当前的map集合是否包含指定的Value(书写敏感,加双引号和不加双引号的区别)
        System.out.println(map.containsValue(1));  //输出结果:true
        System.out.println(map.containsValue("1"));  //输出结果:false

        //清空集合
        //map.clear();

        //获取map集合的key的集合
        map.keySet();
        //获取集合的所有value值
        map.values();


        //获取map集合的key的集合
        Set<String> keys = map.keySet();
        //遍历map集合
        // 第一种方式:for循环
        for(String key:keys){
            System.out.println("key:"+key+",value:"+map.get(key));
        }

       //第二种方式:通过map.entrySet();遍历map集合
        Set<Map.Entry<String,Integer>> entrys = map.entrySet();
        for (Map.Entry<String,Integer> en:entrys){
            System.out.println("key:"+en.getKey()+",value:"+en.getValue());
        }
    }
}

数据结构

jdk1.8前:数组+链表 无序,头插

jdk1.8 :数组+链表+红黑树 无序,尾插


底层原理

实例化HashMap

在创建HashMap对象的时候知识初始化一些参数

为什么默认初始容量为16?

匹配硬件设计

大量数据测试,确定基本参数配置


增加元素—put

(图片来源于网上)

当我们在put添加元素的时候,当多个元素映射到了同一个位置,hashmap中采用单链表的形式链接每一个元素,在jdk7使用头插法,jdk8开始使用尾插法,同一个位置会将要插入的新元素放在链表的头部。并且总是会判断当前table的容量是否满足可使用范围

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;  //n为table容量,i为下标           
    
        if ((tab = table) == null || (n = tab.length) == 0)   //如果table为null  或者  table长度==0
            n = (tab = resize()).length;  //进行第一次扩容,n为第一次扩容后的table长度
    
        if ((p = tab[i = (n - 1) & hash]) == null)   //计算节点要插入的位置,如果p为null表示这个位置为空,则创建一个新节点插入
            tab[i] = newNode(hash, key, value, null);   //在table表的置顶索引位置处插入新节点
        else {  //否则,说明要映射的索引位置已经存在了元素
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))  
                //判断要插入的节点是否已存在,如果存在则直接覆盖原来的key-value
                e = p;
                
            else if (p instanceof TreeNode)    //判断该链是不是红黑树
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);   
                
            else {        //否则,不是红黑树
                for (int binCount = 0; ; ++binCount) {  //遍历该链表表
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st   //判断该链表长度是大于8则转换为红黑树进行处理
                            treeifyBin(tab, hash);
                        break;
                    }

                    //如果key已经存在则直接覆盖value
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }

            //直接覆盖
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;

    //超过最大容量则扩容
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

如何确定key的位置的?(哈希函数)

先计算出key对应的hashcode(32位的int类型数),将hashcode的高16位和低16位进行异或运算(相同为0,不同为1)

Key值相同会覆盖

对于HashMap来说key必须是唯一的,插入的时候如果已经存在相同的key则会把原来的值覆盖。如下面的例子

无序插入

前面说到了HashMap可以插入key为null的键值对,我们模拟一下看看:我在put的时候最后一个元素的时候key和value都为null,但输出结果来看key为null的键值对是在集合的第一个,因此我们可以总结出HashMap插入顺序是无序的

HashMap如何解决哈希冲突的?

将相同哈希值的键值对通过链表(拉链法)+红黑树的形式存放起来


扩容机制——resize()

思想:扩容数组容量+重新计算hash值

什么时候需要扩容?

table容量>=容量*加载因子(0.75)

第一次扩容:16*0.75=12,当临界值为12的时候

,第一次扩容之后的table容量为 32,临界值为24

第二次开始及之后:容量为原来容量的2倍,临界值为原来的2倍,第二次扩容后table容量为64,临界值为48,依次类推

为什么加载因子是0.75?

必须是2的几次幂,当加载因子选择了0.75就可以保证它与容量的乘积为证书

什么时候会由链表变成红黑树?(树化思想)

容量先扩容到64,并且链表长度超过 8 的时候,会将链表转化为红黑树来提高查询效率

扩容的流程?

通过 resize 方法来实现的

  1. 将临界值修改为原来临界值的2倍
  2. 将table容量变为原来的table容量的2倍
  3. 创建新数组
  4. 遍历旧数组,将旧数组中的元素复制到新数组中
    1. 确定在新数组中的位置:hashcode / table容量
    2. 如果新位置已经有元素:当前元素添加到链表的末尾
    3. 如果链表长度>8,转成红黑树

注意:在jdk8中扩容的时候会保持链表原来的顺序


存在什么问题?

①、线程安全问题

  • 多线程下扩容会死循环
  • 多线程下 put 会导致元素丢失
  • put 和 get 并发时会导致 get 到 null

解决方案:使用ConcurrentHashMap

整个集合是这一个(table数组)

对node节点的共享变量使用volatile修饰保证可见性

ConcurrentHashMmap在第一次进行put操作的时候才会对进行初始化,在初始化node节点的过程中会调用initTable 方法,判断到如果有其他的线程正在进行初始化则调用yield()让出cpu,并且修改状态为正在初始化状态

CAS(自旋)

②、无序插入

hashmap插入的元素是无序的,如果想要顺序可以使用LinkedhashMap,并且LinkedHashMap继承了HashMap

如果有想要交流的内容欢迎在评论区进行留言,如果这篇文档受到了您的喜欢那就留下你点赞+收藏+评论脚印支持一下博主~

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

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

相关文章

一个数据中心的PUE修养,必将迎来液冷存储的曙光

实现小于1.3的PUE硬指标&#xff0c;数据中心液冷存储将功不可没。 【全球存储观察 &#xff5c; 科技热点关注】 4000亿千瓦时&#xff0c;能耗如此惊人&#xff0c;这是预计到2030年全国数据中心的年耗电总量。 小于1.3&#xff0c;看似微不足道的数字&#xff0c;这是新建…

鸿蒙HarmonyOS应用开发-ColumnRow组件

1 概述 一个丰富的页面需要很多组件组成&#xff0c;那么&#xff0c;我们如何才能让这些组件有条不紊地在页面上布局呢&#xff1f;这就需要借助容器组件来实现。 容器组件是一种比较特殊的组件&#xff0c;它可以包含其他的组件&#xff0c;而且按照一定的规律布局&#xf…

原生GPT本地及云端部署方式保姆级教程

前提条件 部署必须要有一个超过1年的Github账号 本地服务部署 运行效果 部署方法 下载安装包 暂时无法在飞书文档外展示此内容 GitHub授权登录&#xff1a; https://dash.pandoranext.com/ 登录后是这个样子&#xff1a; 复制下面红框里面这个License Id 编辑Config.js…

基于SpringBoot母婴商城

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本母婴商城系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&am…

Postman如何使用(四):接口测试

一.接口 1.程序内部接口&#xff1a;方法与方法之间&#xff0c;模块与模块之间的交互&#xff0c;程序内部抛出的接口&#xff0c;比如bbs系统&#xff0c;有登录模块&#xff0c;发帖模块等等&#xff0c;那你要发帖就必须先登录&#xff0c;那么这两个模块就得有交互&#…

如何选择共模噪声滤波器

在当前电子产品中&#xff0c;绝大多数的高速信号都使用地差分对结构。 差分结构有一个好处就是可以降低外界对信号的干扰&#xff0c;但是由于设计的原因&#xff0c;在传输结构上还会受到共模噪声的影响。 共模噪声滤波器就可以用于抑制不必要的共模噪声&#xff0c;而不会对…

Flutter使用flutter_gen管理资源文件

pub地址&#xff1a; https://pub.dev/packages/flutter_gen 1.添加依赖 在你的pubspec.yaml文件中添加flutter_gen作为开发依赖 dependencies:build_runner:flutter_gen_runner: 2.配置pubspec.yaml 在pubspec.yaml文件中&#xff0c;配置flutter_gen的参数。指定输出路…

Windows Terminal CMD 终端配置方案: 不只是酷炫外观

大一的时候小学期我们还是用 Windows cmd 终端写的订餐系统&#xff0c;尽管进我们所能地改了改配色&#xff0c;成品还是让人不忍直视。 当时学习遇到的大多数运行需求可以通过 IDE 解决&#xff0c;再加上 CMD 丑成这样&#xff0c;挺让人抵触的。 后来对命令行操作的学习需…

Linux下删除当前目录下的所有目录

Linux下删除当前目录下的所有目录 Linux下删除当前目录下的所有目录&#xff0c;可以使用命令&#xff1a;rm -rf ./* rm -rf ./*可以得知rm -rf ./命令是删除当前目录下的所有文件和文件夹&#xff0c;但不会删除根目录下的文件。其中&#xff0c;".“代表当前目录&…

ERP软件对Oracle安全产品的支持

这里的ERP软件仅指SAP ECC和Oracle EBS。 先来看Oracle EBS&#xff1a; EBS的认证查询方式&#xff0c;和数据库认证是一样的。这个体验到时不错。 结果中和安全相关的有&#xff1a; Oracle Database VaultTransparent Data Encryption TDE被支持很容易理解&#xff0c;…

个人成长|普通人要想摆脱贫穷,一定要注意这3点

哈喽呀&#xff0c;你好&#xff0c;我是雷工。 身为普通人&#xff0c;没有背景&#xff0c;没有资源&#xff0c;也没有人脉&#xff0c;在什么都没有的情况下如何才能摆脱贫穷&#xff0c;让生活过得更好。 要想自我蜕变&#xff0c;摆脱贫穷&#xff0c;就必须注意以下3点。…

C++ Primer学习笔记 第2章 变量和基本类型

2.1 基本内置类型 2.1.2 类型转换 首先了解下取模和取余的区别&#xff01;&#xff01;&#xff01;[取模与取余的区别] 当我们赋给无符号类型一个超出它表示范围的值时&#xff0c;结果是初始值对无符号类型表示数值总数取模后的余数。如8bit大小的unsigned char 可以表示…

【C++ Primer Plus学习记录】循环和文本输入

目录 1.使用原始的cin进行输入 2.使用cin.get(char)进行补救 3.使用哪一个cin.get() 4.文件尾条件 循环完后的一项最常见、最重要的任务&#xff1a;逐字符地读取来自文件或键盘的文本。 cin对象支持3种不同模式的单字符输入&#xff0c;其用户接口各不相同。下面介绍如何…

低代码平台在数字化转型过程中的定位

内容来自演讲&#xff1a;郭昊东 | 上海外服 | 流程分析工程师 摘要 本文介绍了外服集团的 IT 共享中心在低代码平台应用开发方面的实践经验。他们选择低代码平台的原因包括开发成本低、快速看到实际产品以及能够解决数据孤岛和影子 IT 等问题。他们在应用开发中面临的挑战包括…

软考:2024年软考高级:软件工程

软考&#xff1a;2024年软考高级: 提示&#xff1a;系列被面试官问的问题&#xff0c;我自己当时不会&#xff0c;所以下来自己复盘一下&#xff0c;认真学习和总结&#xff0c;以应对未来更多的可能性 关于互联网大厂的笔试面试&#xff0c;都是需要细心准备的 &#xff08;1…

LeetCode Hot100 3.无重复字符的最长子串

题目&#xff1a; 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 代码&#xff1a; class Solution {public int lengthOfLongestSubstring(String s) {char[] arr s.toCharArray(); // 转换成 char[] 加快效率&#xff08;忽略带来的空间…

Ubuntu Server 20.04.6下Anaconda3安装Pytorch

环境 Ubuntu 20.04.6 LTS Anaconda3-2023.09-0-Linux-x86_64.sh conda 23.7.4 Pytorch 1.11.0 安装 先创建一个工作环境&#xff0c;环境名叫lia&#xff1a; conda create -n lia python3.8环境的使用方法如下&#xff1a; conda activate lia # 激活环境 conda deactiv…

centos8 下载

下载网址 Download 直接下载地址 https://mirrors.cqu.edu.cn/CentOS/8-stream/isos/x86_64/CentOS-Stream-8-20231127.0-x86_64-dvd1.iso 这个版本安装的时候方便

经典策略梯度算法

经典策略梯度算法 DDPG算法 DDPG 算法被提出的初衷其实是 DQN 算法的一个连续动作空间版本扩展。深度确定性策略梯度算法&#xff08; deep deterministic policy gradient&#xff0c;DDPG&#xff09;&#xff0c;是一种确定性的策略梯度算法。 由于DQN算法中动作是通过贪…

【MATLAB】EWT分解+FFT+HHT组合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 EWTFFTHHT组合算法是一种广泛应用于信号处理领域的算法&#xff0c;它结合了经验小波变换&#xff08;Empirical Wavelet Transform&#xff0c;EWT&#xff09;、快速傅里叶变换&#x…