Java集合之双列集合

news2024/10/6 20:37:56

双列集合特点

  • 双列集合一次需要添加一对数据,分别是键和值
  • 键不能重复,值可以重复
  • 键和值是一一对应的,每一个键只能找到自己对应的值
  • 键 + 值这个整体称为“键值对”或者“键值对对象”,Java中叫“Entry对象”

双列集合的体系结构

 Map的常见API

  • V put(K key,V Value):添加元素【如果键存在,会把原有的键值对覆盖,并返回被覆盖的键值对的值,如果不存在,返回null】
  • V remove(Object key):根据键删除键值对,并返回被删除的键值对的值
  • void clear():清空所有的键值对
  • boolean containsKey(Object key):判断集合是否包含指定的键【键存在:true 不存在:false】
  • boolean containsValue(Object key):判断集合是否包含指定的值【键存在:true 不存在:false】
  • boolean isEmpty():判断集合是否为空
  • int size():集合的长度,集合中键值对的个数

Map集合的遍历方式

通过键找值

        Set<String> keys = map.keySet();
        for (String key : keys) {
            System.out.println(map.get( key ));
        }

键值对遍历

        Set<Map.Entry<String, String>> set = map.entrySet();
        for (Map.Entry<String, String> entry : set) {
            System.out.println(entry.getKey()+":"+entry.getValue());
        }

Lambda表达式遍历

        map.forEach( (key,value)-> System.out.println(key+":"+value) );

HashMap

  • HashMap是Map下面的实现类
  • 直接使用Map里面的方法即可
  • 键无序【存和取得顺序】、不重复、无索引
  • HashMap根HashSet底层原理一样,都是哈希表结构,唯一不同的是,会创建Entry对象,通过键计算哈希值,如果计算出来的哈希值是一样的,新的Entry对象会覆盖原有的Entry对象,其他的根HashSet一样的
  • 依赖hashCode方法和equals方法保证键的唯一
  • 如果键存储的是自定义对象,需重写hashCode方法和equals方法,如果值存储的自定义对象,就不需要重写hashCode方法和equals方法

LinkedHashMap

  • 由键决定:有序【存和取的顺序】、不重复、无索引
  • 原理:底层数据结构是哈希表,只是每个键值对额外多一个双向链表机制记录存储的顺序

TreeMap

  • TreeMap和TreeSet底层原理一样,都是红黑树结构
  • 由键决定:不重复、无索引、可排序【对键进行排序】
  • 默认按照键的从小到大进行排序,也可以自定义规则排序

两种排序方式

  • 实现Comparable接口,指定比较规则
  • 创建集合时传递Comparator比较器对象,指定比较规则

 HashMap源码分析

        Map<String,String> map = new HashMap<>();
        map.put( "ab","ba" );
        map.put( "ac","ca" );
        map.put( "ad","da" );
        map.put( "ae","ea" );
    public V put(K key, V value) {//key是键,value是值
        //如果覆盖了原来的键值对,就返回被覆盖的键值对的值,没有覆盖就返回null
        return putVal(hash(key), key, value, false, true);
    }
    //利用键计算对应的哈希值,在把计算出的哈希值做一些额外的处理
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
//hash:键的哈希值 key:键 value:值 onlyIfAbsent:如果键重复了是否保留【true:保留 false:不保留】
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:数组的长度 i:索引
        //tab = table将哈希表中数组的地址值赋值给局部变量tab
        if ((tab = table) == null || (n = tab.length) == 0)
            //1.如果是第一次添加元素,底层会创建一个默认长度16,加载因子0.75的数组
            //2.如果不是第一次添加,会看数组中的元素是否到达需要扩容的条件,
            //如果没有达到,会做任何操作,
            //如果达到了,底层会将数组扩容到原来的2倍,并把数据全部转移到新的哈希表中
            //并把当前数组的长度赋值给n
            n = (tab = resize()).length;
        //用数组的长度和键的哈希值进行计算,计算出当前键值对对象,在数组中应存入的位置
        i = (n - 1) & hash]
        //获取数组中对应元素的数据
        p = tab[i]
        if (p == null)
            //底层会创建一个键值对对象,直接放到数组中
            tab[i] = newNode(hash, key, value, null);
        //不是第一次添加元素是就走else
        else {
            Node<K,V> e; K k;
            //数组中键值对的哈希值与当前需要添加键值对的哈希值
            boolean f = p.hash == hash;
            if (f && ((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) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //如果哈希值相同,就会调用equals方法比较内部的属性值是否相同
                    if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
             //覆盖值需要走此处的if语句
            //如果e为null,表示不需要覆盖任何元素
            //如果e不为null,表示当前的键之一样的,值就会被覆盖
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    //左边:当前要添加的值
                    //右边:e的value值
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        //threshold:记录的是数组的长度 * 0.75,就是哈希表的扩容机制
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        //表示当前没有覆盖任何元素
        return null;
    }

TreeMap源码分析

//TreeMap集合的get方法源码分析
static final class Entry<K,V> implements Map.Entry<K,V> {
    K key;//表示键
    V value;//表示值
    Entry<K,V> left;//表示左节点的地址
    Entry<K,V> right;//表示右节点的地址
    Entry<K,V> parent;//表示父节点的地址
    boolean color = BLACK;//表示当前节点的颜色
}
public V get(Object key) {
	//调用getEntry方法获取Entry对象
	Entry<K,V> p = getEntry(key);
	//判断对象是否为null,为null返回null,不是则返回对象中的值
	return (p==null ? null : p.value);
}
final Entry<K,V> getEntry(Object key) {
	//判断有没有传入comparator
	if (comparator != null)
		//调用getEntryUsingComparator方法,使用比较器做查询
		return getEntryUsingComparator(key);
	//判断传入的键是否为null
	if (key == null)
		//如果查询的键为null,抛出空指针异常
		throw new NullPointerException();
	@SuppressWarnings("unchecked")
	//把Object类型的键向下转型为Comparable
	Comparable<? super K> k = (Comparable<? super K>) key;
	//先把二叉树的根节点赋值给p
	Entry<K,V> p = root;
	//如果p不为空,一直循环作比较
	while (p != null) {
		//调用compareTo方法进行比较
		int cmp = k.compareTo(p.key);
		//如果局部变量cmp小于0,表示要查询的键小于节点的数值
		if (cmp < 0)
			//把p的左节点赋值给p对象
			p = p.left;
		//如果局部变量cmp大于0,表示要查询的键大于节点的数值
		else if (cmp > 0)
			//把p的右节点赋值给p对象
			p = p.right;
		else
			//要查找的键等于节点的值,就把当前Entry对象直接返回
			return p;
	}
	//没有找到要查找的结点的值,就会返回null
	return null;
}
//传入比较器的情况下
final Entry<K,V> getEntryUsingComparator(Object key) {
	@SuppressWarnings("unchecked")
	//把Object类型的key向下转型为对应的键的类型
	K k = (K) key;
	//初始化比较器对象【起名字】
	Comparator<? super K> cpr = comparator;
	if (cpr != null) {
		//把二叉树的根节点赋值给p对象
		Entry<K,V> p = root;
		//循环用要查找的键的值和节点中键的值作比较
		while (p != null) {
			//调用比较器的compare方法
			int cmp = cpr.compare(k, p.key);
			if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }
    }
    return null;
}


//put()添加方法源码分析
public V put(K key, V value) {
	//获取根节点赋值给t
    Entry<K,V> t = root;
	//判断根节点是否为空
    if (t == null) {
		//为空就对key进行非空和类型检验
        compare(key, key);
		//新建节点
        root = new Entry<>(key, value, null);
		//把集合的长度设置为1
        size = 1;
		//记录集合被修改的次数
        modCount++;
		//添加成功就返回null
        return null;
    }
	//如果根节点不为空则执行以下代码
    int cmp;
    Entry<K,V> parent;
	//把比较器对象赋值给cpr
    Comparator<? super K> cpr = comparator;
	//判断比较器对象为空则执行一下代码
    if (cpr != null) {
        do {
			//把当前节点赋值给变量parent
            parent = t;
			//比较当前节点的键和要存储的键的大小
            cmp = cpr.compare(key, t.key);
			//要存储的键小于当前节点的键的值,则继续和当前节点的左节点进行比较
            if (cmp < 0)
                t = t.left;
			//要存储的键大于当前节点的键的值,则继续和当前节点的右节点进行比较
            else if (cmp > 0)
                t = t.right;
			//要存储的键等于当前节点的键的值,则调用setValue方法设置新的值,并结束循环
            else
                return t.setValue(value);
        } while (t != null);//循环直到遍历到叶子节点结束循环
    }
	//如果比较器对象不为空,执行以下代码
    else {
		//如果要保存的键为空,判处空指针异常
        if (key == null)
            throw new NullPointerException();
        @SuppressWarnings("unchecked")
			//把键转型为Comparable类型
            Comparable<? super K> k = (Comparable<? super K>) key;
        do {
			//把当前节点赋值给变量parent
            parent = t;
			//比较要存储的键和当前节点的键的值
            cmp = k.compareTo(t.key);
			//要存储的键小于当前节点的键的值,则继续和当前节点的左节点进行比较
            if (cmp < 0)
                t = t.left;
			//要存储的键大于当前节点的键的值,则继续和当前节点的右节点进行比较
            else if (cmp > 0)
                t = t.right;
			//要存储的键等于当前节点的键的值,则调用setValue方法设置新的值,并结束循环
            else
                return t.setValue(value);
        } while (t != null);//循环直到遍历到叶子节点结束循环
    }
	//创建新的节点对象,保存键值对,设置父节点
    Entry<K,V> e = new Entry<>(key, value, parent);
	//新的键的值小于父节点键的值
    if (cmp < 0)
		//就把要保存的键值对设置为当前节点的左节点
        parent.left = e;
    else
		//如果要保存的键的值大于父节点键的值,则把要保存的节点设置为当前节点的右节点
        parent.right = e;
	//保持红黑树的平衡
    fixAfterInsertion(e);
	//集合长度加一
    size++;
	//集合修改次数加一
    modCount++;
	//返回被覆盖的值
    return null;
}

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

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

相关文章

linux系统systemd初始化进程

前言&#xff1a;目前绝大多数服务器系统以及从RHEL6换成RHEL7了&#xff0c;以前习惯使用service来管理系统服务的&#xff0c;那么现在就比较郁闷了&#xff0c;RHEL7系统中使用systemctl命令来管理服务。 systemctl启动、重启、停止、查看状态命令&#xff1a; systemctl …

算法竞赛字符串篇之C++中string的成员函数

2023年5月7日&#xff0c;周日中午&#xff1a; 今天决定从字符串这个知识点开始学起&#xff0c;记录一下我今天的字符串学习。 不定期更新。 相关的英文文档&#xff1a; https://cplusplus.com/reference/string/string/ 容量方面的成员函数&#xff1a; empty&#xff…

基于AT89C51单片机的电子闹钟设计与仿真

点击链接获取Keil源码与Project Backups仿真图: https://download.csdn.net/download/qq_64505944/87761718?spm=1001.2014.3001.5503 源码获取 主要内容: 基于51单片机设计一个电子闹钟,至少具有以下功能:时间的设定、时间的调整、闹钟的设定、温度的设定。 基本要求:…

排队论_M/M/1/inf/inf 问题

例:某修理店只有一一个修理工人&#xff0c;来修理的顾客到达数服从泊松分布&#xff0c;平均每小时4人;修理时间服从负指数分布&#xff0c;平均需6分钟。求: (1)修理店空闲的概率; (2)店内有3个顾客的概率; (3)店内至少有1个顾客的概率; (4)店内顾客的平均数; (5)顾客在店内的…

显著性检测:从传统方法到深度学习网络的演进与挑战

显著性检测技术在计算机视觉领域中扮演着至关重要的角色&#xff0c;它是一项对图像中最显著或最有区别的视觉特征进行分析和提取的技术。显著性检测技术可以为计算机视觉任务提供帮助&#xff0c;例如图像分割、目标检测、场景理解、图像检索和人机交互等方面。 本文将从传统方…

【MySQL】基于规则的优化(内含子查询优化;派生表;物化表;半连接;标量子查询;行子查询)

概念 常量表&#xff1a;下述两种查询方式查询的表&#xff1a; 类型1&#xff1a;查询的表中一条记录都没有&#xff0c;或者只有一条记录。 类型2&#xff1a;使用主键等值匹配或者唯一二级索引列等值匹配作为搜索条件来查询某个表 派生表&#xff1a;放在FROM子句后面的子…

UDP报头、TCP报头、IP报头、MAC头部、ARP头部

前言&#xff1a;DUP报头、TCP报头、IP报头、MAC头部、ARP头部。 UDP报头&#xff1a; UDP报头由八个字节组成&#xff0c;每个字段都是两个字节 &#xff1a; 1.源端口号&#xff1a;发送方端口号&#xff0c;需要对方回信的时候选用&#xff0c;不需要对方回信的时候置0 …

[LeetCode复盘] LCCUP‘23春季赛组队赛 20230507

[LeetCode复盘] LCCUP23春季赛组队赛 20230507 一、本周周赛总结1. 符文储备1. 题目描述2. 思路分析3. 代码实现 2. 城墙防线1. 题目描述2. 思路分析3. 代码实现 3. 提取咒文1. 题目描述2. 思路分析3. 代码实现 4. 生物进化录1. 题目描述2. 思路分析3. 代码实现 5. 与非的谜题…

HNU-操作系统OS-实验Lab3

OS_Lab3_Experimental report 湖南大学信息科学与工程学院 计科 210X wolf &#xff08;学号 202108010XXX&#xff09; 实验目的 了解虚拟内存的Page Fault异常处理实现了解页替换算法在操作系统中的实现 实验内容 本次实验是在lab2的基础上&#xff0c;借助于页表机制…

【python数据分析】运算符与表达式

&#x1f64b;‍ 哈喽大家好&#xff0c;本次是python数据分析、挖掘与可视化专栏第三期 ⭐本期内容&#xff1a;运算符与表达式 &#x1f3c6;系列专栏&#xff1a;Python数据分析、挖掘与可视化 &#x1f44d;保持开心&#xff0c;拒绝拖延&#xff0c;你想要的都会有&#x…

车载软件架构——闲聊几句AUTOSAR BSW(四)

我是穿拖鞋的汉子,魔都中坚持长期主义的工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 我们并不必要为了和谐,而时刻保持通情达理;我们需要具备的是,偶尔有肚量欣然承认在某些方面我们可能会有些不可理喻。该有主见的时候能掷地有声地镇得住场…

iOS 对https App内部的http请求进行白名单设置

苹果从iOS9开始要求应用使用Https链接来对请求进行加密,来保证数据的安全.如果使用http请求将会报错,当然,如果你想继续使用http请求,有两种方式: 1.使用ASIHttpRequest来请求,ASI是使用CFNetwork来处理请求的,更底层些,避开了苹果的限制 2.在Info.plist文件设置如下 <key…

Docker安装常用软件-Apollo(有问题)

零&#xff1a;apollo概念介绍 官网网站&#xff1a;GitHub - apolloconfig/apollo: Apollo is a reliable configuration management system suitable for microservice configuration management scenarios. gitee网址&#xff1a;mirrors / ctripcorp / apollo GitCode …

自学软件测试简历没项目写怎么办?

目录 一、引言 二、测试任务 三、测试进度 四、测试资源 五、测试策略 六、测试完成标准 七、风险和约束 八、问题严重程度描述和响应时间规范 九、测试的主要角色和职责 软件测试是使用人工或者自动的手段来运行或者测定某个软件系统的过程&#xff0c;其目的在于检验…

Python:Python进阶:Python字符串驻留技术

Python字符串驻留技术 1.什么是字符串驻留2. 为什么要驻留字符串3. Python的字符串驻留4. Python 字符驻留原理4.1 如何驻留字符串4.2 如何清理驻留的字符串 5. 字符串驻留的实现5.1. 变量、常量与函数名5.2 字典的键5.3 任何对象的属性5.4 显式地驻留 6 字符串驻留的其他发现 …

MySQL --- DML

接下来学习第二个部分&#xff1a;根据页面原型以及需求进行相关功能的开发&#xff0c;进而完成数据库的操作。 学习数据库的DML操作 3. 数据库操作-DML-insert&#xff0c;update,delete DML DML英文全称是Data Manipulation Language(数据操作语言)&#xff0c;用来对数据…

176_工具_Power BI 实用工具 pbi-utils 更新至 v1.0.3.1

176_工具_Power BI 实用工具 pbi-utils 更新至 v1.0.3.1 pbi-utils 更新至&#xff1a;v1.0.3.1, 从 v1.0.0.0 到 v1.0.3.1 更新了 8 次。 文档地址&#xff1a;https://jiaopengzi.com/2880.html 主要功能&#xff1a; 快速设置 Power BI 模板&#xff0c;实现高复用。设计…

【大数据基础】Spark+Kafka构建实时分析Dashboard

https://dblab.xmu.edu.cn/post/spark-kafka-dashboard/ https://dblab.xmu.edu.cn/post/8116/ 实验环境准备 Kafka安装 访问Kafka官方下载页面,下载稳定版本0.10.1.0的kafka.此安装包内已经附带zookeeper,不需要额外安装zookeeper.按顺序执行如下步骤: cd ~/下载 sudo tar …

《Markdown编辑器》的使用

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

Simulink 自动代码生成电机控制:方波高频注入仿真到代码生成开发板演示

目录 前言 方波高频注入仿真 生成代码开发板运行 总结 前言 最近换了一个小电机&#xff0c;于是尝试了一下方波高频注入的仿真到代码生成的实验&#xff0c;正弦波注入的方式已经实现 STM32 Simulink 自动代码生成电机控制——脉振高频注入_高频注入代码_卡洛斯伊的博客-…