【集合系列】HashMap 集合

HashMap 集合

    • 1. 概述
    • 2. 方法
    • 3. 遍历方式
    • 4. 代码示例1
    • 5. 代码示例2
    • 6. 注意事项
    • 7. 源码分析

其他集合类

父类 Map

实现类 LinkedHashMap

集合类的遍历方式

具体信息请查看 API 帮助文档

1. 概述

HashMap 是 Java 中的一种集合类,它实现了 Map 接口。HashMap 使用键值对存储数据,每个键值对被称为一个 Entry(条目)。HashMap 使用哈希表来存储数据,因此能够在 O(1) 时间复杂度下进行插入、删除和查找操作。

HashMap 的特点包括:

  1. 键不重复:HashMap 中的键是唯一的,如果插入时有重复键,则后面的值会覆盖之前的值。

  2. 无序性:HashMap 中的元素没有固定的顺序,即不保证元素的顺序与插入的顺序一致。

  3. 允许空键和空值:HashMap 允许键和值都为 null。

  4. 非线程安全:HashMap 不是线程安全的,如果有多个线程同时访问一个 HashMap 对象并对其进行修改,可能会导致数据不一致或抛出异常。如果需要在多线程环境中使用,可以考虑使用 ConcurrentHashMap。

2. 方法

HashMap集合是Map集合的子类,因此Map集合的方法HashMap集合都能使用。

Map集合

方法名说明
V put(K key,V value)添加元素
V remove(Object key)根据键删除键值对元素
void clear()移除所有的键值对元素
boolean containsKey(Object key)判断集合是否包含指定的键
boolean containsValue(Object value)判断集合是否包含指定的值
boolean isEmpty()判断集合是否为空
int size()集合的长度,也就是集合中键值对的个数

3. 遍历方式

与共有的 集合遍历方式 一样

4. 代码示例1

  • 代码示例
    存储学生对象并遍历
    需求:创建一个HashMap集合,键是学生对象(Student),值是籍贯(String)。存储三个键值对元素,并遍历集合
    要求:同姓名,同年龄认为是同一个学生
package text.text02;

import java.util.*;
import java.util.function.BiConsumer;

/*
存储学生对象并遍历
需求:创建一个HashMap集合,键是学生对象(Student),值是籍贯(String)。存储三个键值对元素,并遍历集合
要求:同姓名,同年龄认为是同一个学生
 */
public class text49 {
    public static void main(String[] args) {
        //创建学生对象
        Student7 student1 = new Student7("张三", 23);
        Student7 student2 = new Student7("李四", 24);
        Student7 student3 = new Student7("王五", 25);
        Student7 student4 = new Student7("王五", 25);

        //创建集合对象
        HashMap<Student7, String> hm = new HashMap<>();  //键是自定义对象,必须要重写equals和hashCode方法

        //添加集合元素
        hm.put(student1, "山西");
        hm.put(student2, "河南");
        hm.put(student3, "湖北");
        hm.put(student4, "湖北");

        //遍历集合            、

        //1.通过键找值的方式遍历
        System.out.println("1.通过键找值的方式遍历:");
        Set<Student7> set = hm.keySet();
        Iterator<Student7> it = set.iterator();
        while (it.hasNext()) {
            Student7 key = it.next();
            String value = hm.get(key);
            System.out.println(key + " = " + value);
        }


        //2.通过键值对的方式遍历
        System.out.println("2.通过键值对的方式遍历:");
        Set<Map.Entry<Student7, String>> entries = hm.entrySet();
        for (Map.Entry<Student7, String> map : entries) {
            Student7 key = map.getKey();
            String value = map.getValue();
            System.out.println(key + " = " + value);
        }

        //3.通过结合Lambda表达式的方式遍历
        System.out.println("3.通过结合Lambda表达式的方式遍历:");
        hm.forEach(new BiConsumer<Student7, String>() {
            @Override
            public void accept(Student7 key, String value) {
                System.out.println(key + " = " + value);
            }
        });
    }
}

class Student7 {
    private String name;
    private int age;

    public Student7() {
    }

    public Student7(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 获取
     *
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     *
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     *
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     *
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student7 student7 = (Student7) o;
        return age == student7.age && Objects.equals(name, student7.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    public String toString() {
        return "Student7{name = " + name + ", age = " + age + "}";
    }
}

  • 输出结果
    • 1.通过键找值的方式遍历
      在这里插入图片描述

    • 2.通过键值对的方式遍历
      在这里插入图片描述

    • 3.通过结合Lambda表达式的方式遍历
      在这里插入图片描述

5. 代码示例2

  • 代码示例
    Map集合案例–统计投票人数
    需求:某个班级80名学生,现在需要组成秋游活动,班长提供了四个景点,依次是(A,B,C,D),每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多。
package text.text02;

import java.util.*;

/*
Map集合案例--统计投票人数
需求:某个班级80名学生,现在需要组成秋游活动,班长提供了四个景点,依次是(A,B,C,D),每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多。
 */
public class text50 {
    public static void main(String[] args) {
        //计数器方法
        System.out.println("====================计数器方法==================");
        method1();
        //通用方法
        System.out.println("====================通用方法====================");
        method2();
    }

    //计数器方法
    public static void method1() {
        //定义变量记录A,B,C,D四个景点的投票人数
        int A = 0;
        int B = 0;
        int C = 0;
        int D = 0;

        //创建集合
        HashMap<String, Integer> hm = new HashMap<>();

        //利用随机数模拟随机选择的景点,并利用循环添加进集合
        Random r = new Random();
        for (int i = 0; i < 80; i++) {
            int num = r.nextInt(4);
            if (num == 0) A++;
            if (num == 1) B++;
            if (num == 2) C++;
            if (num == 3) D++;
        }

        //将景点以及每个景点的投票人数添加进去
        hm.put("A", A);
        hm.put("B", B);
        hm.put("C", C);
        hm.put("D", D);

        System.out.println("四个景点的投票情况:" + hm);

        //判断哪个景点投票人数最多
        //求出投票最多的票数
        int sum = 0;//定义一个变量记录最多的景点的票数
        Set<String> set = hm.keySet();
        for (String key : set) {
            Integer value = hm.get(key);
            if (value > sum) {
                sum = value;
            }
        }
        System.out.println("景点投票最多的票数:" + sum);
        //遍历map集合,根据投票最多的票数确定景点(可能存在多个景点投票一样的情况)
        for (String key : set) {
            Integer value = hm.get(key);
            if (value == sum) {
                System.out.println("投票最多的景点:" + key);
            }
        }

    }

    //通用方法
    public static void method2() {
        //定义个数组,存储四个景点
        String[] arr = {"A", "B", "C", "D"};

        //定义个集合用来存储每个学生投票的景点
        ArrayList<String> list = new ArrayList<>();
        Random r = new Random();
        //利用循环将学生的投票景点添加进集合
        for (int i = 0; i < 80; i++) {
            int index = r.nextInt(arr.length);
            list.add(arr[index]);
        }
        System.out.println("ArrayList集合里的数据:" + list);

        //创建双列集合用于存储景点名称和票数
        HashMap<String, Integer> hm = new HashMap<>();

        //遍历单列集合,并将其中的景点和对应的票数添加进双列集合
        for (String name : list) {
            //单列集合中存储的景点名称在双列集合中存在
            if (hm.containsKey(name)) {
                //获取该景点的投票数
                Integer value = hm.get(name);
                //将投票数+1
                value++;
                //再将新的投票数覆盖原来的投票数
                hm.put(name, value);
            }
            //单列集合中存储的景点名称在双列集合中不存在
            else {
                hm.put(name, 1);
            }
        }
        System.out.println("HashMap集合里面的数据:" + hm);

        //判断哪个景点投票人数最多
        //求出投票最多的票数
        int sum = 0;//定义一个变量记录最多的景点的票数
        Set<String> set = hm.keySet();
        for (String key : set) {
            Integer value = hm.get(key);
            if (value > sum) {
                sum = value;
            }
        }
        System.out.println("景点投票最多的票数:" + sum);
        //遍历map集合,根据投票最多的票数确定景点(可能存在多个景点投票一样的情况)
        for (String key : set) {
            Integer value = hm.get(key);
            if (value == sum) {
                System.out.println("投票最多的景点:" + key);
            }
        }
    }
}

  • 输出结果
    • 计数器方法
      在这里插入图片描述

    • 通用方法
      在这里插入图片描述

6. 注意事项

  1. 键的唯一性:HashMap 中的键是唯一的,如果插入时有重复键,则后面的值会覆盖之前的值。因此,在使用 HashMap 时要确保键的唯一性,避免出现重复键的情况。

  2. 键的稳定性:HashMap 的元素没有固定的顺序,也就是说,存入元素的顺序与取出元素时的顺序可能不一致。如果需要按照特定的顺序遍历或操作元素,可以考虑使用 LinkedHashMap。它是 HashMap 的一个子类,保留了元素插入的顺序。

  3. hashCode()和equals()方法:HashMap 使用键的 hashCode() 和 equals() 方法来确定键的位置。因此,键的类型必须正确实现这两个方法,以确保正确的插入、查找和删除操作。如果自定义的类作为 HashMap 的键,必须重写 hashCode() 和 equals() 方法,以保证键的唯一性和正确性。

  4. 并发安全性:HashMap 是非线程安全的,如果有多个线程同时访问一个 HashMap 并对其进行修改,可能会导致数据不一致或抛出异常。如果需要在多线程环境中使用 HashMap,可以考虑使用 ConcurrentHashMap,它是线程安全的。

  5. 初始容量和负载因子:HashMap 默认的初始容量为 16,并且每次扩容是当前容量的两倍。若事先无法估计容量大小,则可以通过构造函数指定初始容量和负载因子。负载因子表示 HashMap 在容量自动增加之前可以达到的平均负载程度,默认为 0.75。较低的负载因子会减少碰撞,但会增加空间消耗;较高的负载因子会增加碰撞,但会减少空间消耗。根据实际情况选择合适的初始容量和负载因子来平衡空间和时间的开销。

7. 源码分析

  1. 看源码之前需要了解的一些内容

    Node<K,V>[] table   哈希表结构中数组的名字
    
    DEFAULT_INITIAL_CAPACITY:   数组默认长度16
    
    DEFAULT_LOAD_FACTOR:        默认加载因子0.75
    
    

    HashMap里面每一个对象包含以下内容:

    1. 链表中的键值对对象
    • 包含:
    int hash;         //键的哈希值
    final K key;      //键
    V value;          //值
    Node<K,V> next;   //下一个节点的地址值
    
    1. 红黑树中的键值对对象
    • 包含:
    int hash;         		//键的哈希值
    final K key;      		//键
    V value;         	 	//值
    TreeNode<K,V> parent;  	//父节点的地址值
    TreeNode<K,V> left;		//左子节点的地址值
    TreeNode<K,V> right;	//右子节点的地址值
    boolean red;			//节点的颜色
    
  2. 添加元素

    HashMap<String,Integer> hm = new HashMap<>();
    hm.put("aaa" , 111);
    hm.put("bbb" , 222);
    hm.put("ccc" , 333);
    hm.put("ddd" , 444);
    hm.put("eee" , 555);
    

    添加元素的时候至少考虑三种情况:

    • 数组位置为null

    • 数组位置不为null,键不重复,挂在下面形成链表或者红黑树

    • 数组位置不为null,键重复,元素覆盖

  • put()方法
//参数一:键
//参数二:值
//返回值:被覆盖元素的值,如果没有覆盖,返回null
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

  • hash()方法
//利用键计算出对应的哈希值,再把哈希值进行一些额外的处理
//简单理解:返回值就是返回键的哈希值
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • putVal()方法
//参数一:键的哈希值
//参数二:键
//参数三:值
//参数四:如果键重复了是否保留(默认是false)
//		   true,表示老元素的值保留,不会覆盖
//		   false,表示老元素的值不保留,会进行覆盖
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
	    //定义一个局部变量,用来记录哈希表中数组的地址值。
		//虽然开头定义了一个全局变量table,但是全局变量存储在堆内存中,而局部变量和方法都存储在栈内存中,省去了再在堆内存调用全局变量的时间,提高了效率
        Node<K,V>[] tab;
		
		//临时的第三方变量,用来记录键值对对象的地址值
        Node<K,V> p;
        
		//表示当前数组的长度
		int n;
		
		//表示索引
        int i;
		
		//把哈希表中数组的地址值,赋值给局部变量tab
		tab = table;

        if (tab == null || (n = tab.length) == 0){
			//1.如果当前是第一次添加数据,底层会创建一个默认长度为16,加载因子为0.75的数组
			//2.如果不是第一次添加数据,会看数组中的元素是否达到了扩容的条件
			//如果没有达到扩容条件,底层不会做任何操作
			//如果达到了扩容条件,底层会把数组扩容为原先的两倍,并把数据全部转移到新的哈希表中
			tab = resize();
			//表示把当前数组的长度赋值给n
            n = tab.length;
        }

		//拿着数组的长度跟键的哈希值进行计算,计算出当前键值对对象在数组中应存入的位置
		i = (n - 1) & hash;//index
		//获取数组中对应元素的数据
		p = tab[i];
		
		
        if (p == null){
			//底层会创建一个键值对对象,直接放到数组当中
            tab[i] = newNode(hash, key, value, null);
        }else {
            Node<K,V> e;
            K k;
			
			//等号的左边:数组中键值对的哈希值
			//等号的右边:当前要添加键值对的哈希值
			//如果键不一样,此时返回false
			//如果键一样,返回true
			boolean b1 = p.hash == hash;
			
            if (b1 && ((k = p.key) == key || (key != null && key.equals(k)))){
                e = p;
            } 
			//判断数组中获取出来的键值对是不是红黑树中的节点
			else if (p instanceof TreeNode){
				//如果是,则调用方法putTreeVal,把当前的节点按照红黑树的规则添加到树当中。
                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);
						//判断当前链表长度是否超过8,如果超过8,就会调用方法treeifyBin
						//treeifyBin方法的底层还会继续判断
						//判断数组的长度是否大于等于64
						//如果同时满足这两个条件,就会把这个链表转成红黑树
                        if (binCount >= TREEIFY_THRESHOLD - 1)
                            treeifyBin(tab, hash);
                        break;
                    }
					//e:			  0x0044  ddd  444
					//要添加的元素: 0x0055   ddd   555
					//如果哈希值一样,就会调用equals方法比较内部的属性值是否相同
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
						 break;
					}

                    p = e;
                }
            }
			
			//如果e为null,表示当前不需要覆盖任何元素
			//如果e不为null,表示当前的键是一样的,值会被覆盖
			//e:0x0044  ddd  444
			//要添加的元素: 0x0055   ddd   555
            if (e != null) {
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null){
					
					//等号的右边:当前要添加的值
					//等号的左边:0x0044的值
					e.value = value;
				}
                afterNodeAccess(e);
                return oldValue;
            }
        }
		
        //threshold:记录的就是数组的长度 * 0.75,哈希表的扩容时机  16 * 0.75 = 12
        if (++size > threshold){
			 resize();
		}
        
		//表示当前没有覆盖任何元素,返回null
        return null;
    }
    

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Rust 格式化输出

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、format! 宏二、fmt::Debug三、fmt::Display四、? 操作符 循环打印 前言 Rust学习系列-本文根据教程学习Rust的格式化输出&#xff0c;包括fmt::Debug&…

百卓Smart管理平台 uploadfile.php 文件上传漏洞(CVE-2024-0939)

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

(1)短距离(<10KM)

文章目录 1.1 Bluetooth 1.2 CUAV PW-Link 1.3 ESP8266 wifi telemetry 1.4 ESP32 wifi telemetry 1.5 FrSky telemetry 1.6 Yaapu双向遥测地面站 1.7 HOTT telemetry 1.8 MSP(MultiWii 串行协议)(4.1 版) 1.9 MSP (version 4.2) 1.10 SiK Radio v1 1.11 SiK Radio …

158基于matlab的用于分析弧齿锥齿轮啮合轨迹的程序

基于matlab的用于分析弧齿锥齿轮啮合轨迹的程序&#xff0c;输出齿轮啮合轨迹及传递误差。程序已调通&#xff0c;可直接运行。 158 matlab 弧齿锥齿轮啮合轨迹 传递误差 (xiaohongshu.com)

雨云宿迁云服务器测评

我本打算趁着暑假买台云服务器开mc服务器&#xff0c;但由于没有试用且直接完结导致白废20块钱。 在此提醒大家&#xff0c;买用于开mc服务器的云服务器前能试用一定要试用&#xff01;不然鬼知道它性能够不够用&#xff01; 服务器配置如下: cpu:2v gold61332.5Ghz ram:2GiB…

【C++】初识模板:函数模板和类模板

目录 一、模板函数 1、函数模板的概念 2、函数模板的格式 3、函数模板的原理 4、函数模板实例化 5、 模板参数的匹配原则 二、类模板 1 、类模板的定义格式 2 、类模板的实例化 3、模板类示例 一、模板函数 1、函数模板的概念 函数模板代表了一个函数家族&#xff0c…

【粉丝福利社】Flutter小白开发——跨平台客户端应用开发学习路线(文末送书-完结)

&#x1f3c6; 作者简介&#xff0c;愚公搬代码 &#x1f3c6;《头衔》&#xff1a;华为云特约编辑&#xff0c;华为云云享专家&#xff0c;华为开发者专家&#xff0c;华为产品云测专家&#xff0c;CSDN博客专家&#xff0c;CSDN商业化专家&#xff0c;阿里云专家博主&#xf…

Bee+SpringBoot稳定的Sharding、Mongodb ORM功能(同步 Maven)

Hibernate/MyBatis plus Sharding JDBC Jpa Spring data GraphQL App ORM (Android, 鸿蒙) Bee 小巧玲珑&#xff01;仅 860K, 还不到 1M, 但却是功能强大&#xff01; V2.2 (2024春节・LTS 版) 1.Javabean 实体支持继承 (配置 bee.osql.openEntityCanExtendtrue) 2. 增强批…

项目学习记录

项目开发 创建项目环境配置关联git新增模块项目启动打印地址日志使用httpclient进行idea内部控制台测试使用AOP拦截器打印日志 创建项目 创建一个空项目&#xff0c;并勾选下面选项 然后进入pom.xml中修改项目配置 根据这个链接选则&#xff0c;修改项目的支持版本 链接&#…

猫头虎分享已解决Bug || CPU过载(CPU Overload):HighCpuUsageWarning, CpuOverloadException

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

vue3 之 商城项目—一级分类

整体认识和路由配置 场景&#xff1a;点击哪个分类跳转到对应的路由页面&#xff0c;路由传对应的参数 router/index.js import { createRouter, createWebHashHistory } from vue-router import Layout from /views/Layout/index.vue import Home from /views/Home/index.vu…

linux 06 磁盘管理

01.先管理vm中的磁盘&#xff0c;添加一个磁盘 只有这种方式才可以增加/dev/sd* 中的目录 例如会增加一个sdc 第一步.vm软件&#xff0c;打开虚拟机设置&#xff0c;添加硬盘 第二步.选择推荐scsi 第三步.创建一个新的虚拟磁盘 第四步. 第五步. 02.在创建好的vm虚拟机中查…

automative

car sevice car-lib

html5+css3胶囊按钮代码

效果 代码 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <title></title> <style> /* 胶囊开关的样式 */ .switch { position: relative; display: inline-block; width: 6…

【leetcode热题100】最大矩形

给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵&#xff0c;找出只包含 1 的最大矩形&#xff0c;并返回其面积。 示例 1&#xff1a; 输入&#xff1a;matrix [["1","0","1","0","0"],["1",&quo…

文件绕过-Unsafe Fileuoload

文件上传基础 什么是文件上传 将客户端数据以文件形式封装通过网络协议发送到服务器端&#xff0c;在服务器端解析数据&#xff0c;最终在服务端硬盘上作为真实的文件保存。 通常一个文件以HTTP协议进行上传时&#xff0c;将以POST请求发送至Web服务器&#xff0c;Web服务器…

1987-2022年各省进出口总额数据整理(含进口和出口)(无缺失)

1987-2022年各省进出口总额数据整理&#xff08;含进口和出口&#xff09;&#xff08;无缺失&#xff09; 1、时间&#xff1a;1987-2022年 2、来源&#xff1a;各省年鉴、统计公报 3、指标&#xff1a;进出口总额&#xff08;万美元&#xff09;、进口总额&#xff08;万美…

2009-2019年地级市分类转移支付数据

2009-2019年地级市分类转移支付数据 1、时间&#xff1a;2009-2019年 2、来源&#xff1a;整理自wind 3、指标&#xff1a;公共财政收入:返还性收入、公共财政收入:一般性转移支付收入、公共财政收入:专项转移支付收入 4、范围&#xff1a;280个地级市 5、指标解释&#x…

Windows - URL Scheme - 在Windows上无管理员权限为你的程序添加URL Scheme

Windows - URL Scheme - 在Windows上无管理员权限为你的程序添加URL Scheme What 想不想在浏览器打开/控制你的电脑应用&#xff1f; 比如我在浏览器地址栏输入wegame://后回车会提示是否打开URL:wegame Portocol。 若出现了始终允许选项&#xff0c;你甚至可以写一个Web界面…

高效测试利器:Jmeter+Ant+Jenkins定时监控接口揭秘!

基于JmeterantJenkins的接口性能监控框架 Jenkins的安装和配置 简介 部署到持续集成平台可以实现脚本的定时运行&#xff0c;这是接口测试的核心。 这里我们选用了jenkins,jenkins是一个强大的持续集成系统&#xff0c;使用起来也很简单。 使用步骤如下&#xff1a; 1、 安装…