TreeSet 与 TreeMap And HashSet 与 HashMap

news2024/9/24 19:43:54

目录

Map

TreeMap

put()方法  :  

 get()方法 : 

Set> entrySet() (重) : 

 foreach遍历 : 

Set

哈希表

哈希冲突 : 

冲突避免 : 

冲突解决 ---- > 比散列(开放地址法) :

开散列 (链地址法 . 开链法)

简介 : 

在Java中 , TreeSet 与 TreeMap 利用搜索树实现 Map 与 Set , 其实它们的底层就是一个红黑树(仅作了解).

HashSet与HashMap底层是Hash表.

关于搜索 : 

Map和set是一种专门用来搜索的容器.其搜索的效率与其具体的实例化子类有关.

像我们之前常见的搜索方式有 : 

1.直接遍历 , 时间复杂度为O(n) , 元素如果比较多那么效率就会很慢很慢.

2.二分查找, 时间复杂度为O(logn),但搜索前必须要求序列是有序的.

在现实中会有一些查找 : 例如

根据一个人的姓名去查找它的成绩,或者根据姓名去查找电话号码.

如果是这样的一些查找那么上述的两种方式就不太适合了,而Map和Set就是一种适合动态查找的集合容器.根据Map和Set我们就可去实现这种要求去查询.

Map

对于Map来说它是一个Key - Value 模型   Key 就是一个关键字 而Value就是Key对应的一种结果

例如 : 

统计文件中每个单词出现的次数 , 那么Map就可以实现, <单词 , 单词出现的次数>

Map 与 SortedMap 是两个接口 ,  HashMap 与 TreeMap 就是两个普通类.

TreeMap

以下就是Map中的一些方法 : 

put()方法  :  

put方法的一些注意点 : 

public class Test {
    public static void main(String[] args) {
        Map<String,Integer> treeMap = new TreeMap<>();
        treeMap.put("he",3);
        treeMap.put("she",6);
        treeMap.put("it",8);
        System.out.println(treeMap);
    }
}

上面我们提到了TreeMap与TreeSet它的底层就是一个搜索树.在上一篇文章中我们讲到搜索树的原理以及是如何进行增,删,查的操作.

 get()方法 : 

根据get方法 : 当我们传进去一个Key关键字那么它就会输出相对应的value

public static void main(String[] args) {
        Map<String,Integer> treeMap = new TreeMap<>();
        treeMap.put("he",3);
        treeMap.put("she",6);
        treeMap.put("it",8);
        Integer val = treeMap.get("he");
        System.out.println(val);
    }

 如果treeMap对象中不存在所要查找的关键字Key时 , 则会返回一个null.

 或者我们可以使用另一个方法 : 

如果treeMap对象中不存在abc这个字符串时 , 则输出默认值10.

public static void main(String[] args) {
        Map<String,Integer> treeMap = new TreeMap<>();
        treeMap.put("he",3);
        Integer val = treeMap.getOrDefault("abc",10);
        System.out.println(val);
    }

KeySet()方法 : 

将treeMap对象中全部key放入keySet中.

public static void main(String[] args) {
        Map<String,Integer> treeMap = new TreeMap<>();
        treeMap.put("he",3);
        treeMap.put("she",6);
        treeMap.put("it",8);
        Set<String> keySet = treeMap.keySet();
        System.out.println(keySet);
    }

Set<Map.Entry<K, V>> entrySet() () : 

 这个方法它会将所有存在treeMao对象中的Key - Value 模型返回.

    public static void main(String[] args) {
        Map<String,Integer> treeMap = new TreeMap<>();
        treeMap.put("he",3);
        treeMap.put("she",6);
        treeMap.put("it",8);
        Set<Map.Entry<String, Integer>> entry = treeMap.entrySet();
        System.out.println(entry);
    }

 foreach遍历 : 

我们就可以使用Entry里面的getKey()方法来得到每一个模型的关键字key : 

for (Map.Entry<String, Integer> entry: set) {
     System.out.print(entry.getKey() + " ");
}

注意 : 

1.Map是一个接口,不能直接实例化对象, 如果要实例化对象只能实例化其实现类TreeMap或者HashMap.

2.Map中存放的键值对的Key是唯一的,value是可以重复的

3 . 在TreeMap中插入键值对时,key不能为空,否则就会抛NullPointerException异常value可以为空。但是HashMap的key和value都可以为空。

4. Map 中的 Key 可以全部分离出来,存储到 Set 来进行访问 ( 因为 Key 不能重复 )
使用 Set<K> KeySet() 方法
5. Map 中的 value 可以全部分离出来,存储在 Collection 的任何一个子集合中 (value 可能有重复 )

使用Collection<V> values()方法

6. Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行重新插入.

Set

Set是一个纯Key模型. 因此它只存关键词key.

 

 Set 与 SortedSet 就是两个接口 , 而 TreeSet 和 HashSet是两个普通类.

TreeSet使用add的方法来添加Key.但是如果重复它就不放入.

    public static void main(String[] args) {
        Set<String> set = new TreeSet<>();
        set.add("she");
        set.add("he");
        set.add("he");
        System.out.println(set);
    }

 TreeSet底层也是去调用TreeMap.

 注意 : 

1. Set是继承自Collection的一个接口类
2. Set中只存储了key,并且要求key一定要唯一
3. TreeSet的底层是使用Map来实现的,其使用keyObject的一个默认对象作为键值对插入到Map中的
4. Set最大的功能就是对集合中的元素进行去重
5. 实现Set接口的常用类有TreeSetHashSet,还有一个LinkedHashSetLinkedHashSet是在HashSet的基础
上维护了一个双向链表来记录元素的插入次序。
6. Set中的Key不能修改,如果要修改,先将原来的删除掉,然后再重新插入
7. TreeSet中不能插入null的key,HashSet可以。

哈希表

在顺序结构以及平衡树中, 查找一个元素时,必须要经过关键码的多次比较.顺序查找时间复杂度O(n),平衡树中为树的高度,即O(logn).

而哈希表它就可以直接以O(1)的速度去查找!!!!

实现方法 : 构造一种存储结构 , 通过某种函数使元素的存储位置与它的关键码之间能够建立---映射关系,那么在查找时通过该函数就可以很快找到该元素.

哈希函数 (散列函数) : 哈希方法中使用的转换函数称为哈希(散列)函数 , 构造出来的结构称为哈希表(HashTable)(或者称散列表)

例如 : 有一组数 {1,4,3,2,75,23}

哈希函数设置为 : hash(key) = key % capacity ; capacity为元素底层空间总的大小

哈希冲突 : 

冲突避免 : 

首先我们要明白一点 : 在哈希表底层数组的容量往往是小于实际要存储的关键字的数量的, 这就导致了 :  冲突的发生是必然的 ,  因此我们要做的就是尽量的降低冲突率.

冲突避免 -----> 负载因子调节 (重点)

散列表的载荷因子定义为 : α = 填入表中的元素个数 / 散列表的长度 

冲突解决的两种方法 : 

冲突解决 ---- > 比散列(开放地址法) :

 

上面这种方法叫做线性探测 : 缺点是如果我们要删除4下标这个元素,如果直接删掉之后,那14就找不到了,因此我们在删4的时候只能是通过标记法来标记他已经被删除.

相比起比散列, 开散列会好很多

开散列 (链地址法 . 开链法):

它是由数组 + 链表 + 红黑树  组织起来的

 

红黑树的体现 : 

 

在java底层代码中 , 每一个下标下是一个链表, 采用的是尾插.

接下来我们来实现一下哈希表下面的put方法 : 

我采用了头插的方式.

public class HashBuck {
    static class Node {
        public int key;
        public int val;
        public Node next;

        public Node(int key, int value) {
            this.key = key;
            this.val = value;
        }
    }

    public Node[] array;
    public int usedSize;

    //负载因子 设置为0.7
    public static final double loadFactor = 0.7;

    public HashBuck() {
        this.array = new Node[10];
    }

    public void put(int key, int val) {
        int index = key % array.length;
        Node cur = array[index];
        while (cur != null) {
            if (cur.key == key) {
                cur.val = val;
            }
            cur = cur.next;
        }
        Node newNode = new Node(key,val);
        newNode.next = array[index];
        array[index] = newNode;
        usedSize++;
        //如果负载因子 大于0.7那就扩容数组
        if (calculateLoadFactor() >= loadFactor) {
            //扩容
        }
    }
    //计算负载因子
    public double calculateLoadFactor() {
        return (usedSize*1.0) / array.length;
    }


}

重点在于 :  如果负载因子超过了0.7,那么我们就要去扩容,

扩容要注意

一定要重新哈希计算,假设我们最开始的14存在了4下标,扩容后它应该在14下标.

//扩容
    private void resize() {
        Node[] newArray = new Node[2*array.length];
        for (int i = 0; i < array.length; i++) {
            Node cur = array[i];
            Node curNext = null;
            while (cur != null) {
                curNext = cur.next;
                int index = cur.key % newArray.length; //找到了在新数组当中的位置
                cur.next = newArray[index];
                newArray[index] = cur;
                cur = curNext;
            }
        }
        this.array = newArray;
    }

get方法 : 

public int get(int key) {
        int index = key % array.length;
        Node cur = array[index];
        while (cur != null) {
            if (cur.key == key) {
                return cur.val;
            }
            cur = cur.next;
        }
        return -1;
    }

我们在去求哈希参数的位置时 , 用key%数组的长度. 但是如果key是引用类型呢?

那我们就去调用hashcode方法 , 如果引用数据类型没有实现hashcode方法,那么就会调用Object里面的hashcode方法.

class Student {
    public int age;
    public String name;

    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }
}
public class Test {
    public static void main(String[] args) {
        Student stu = new Student(12,"zs");
        System.out.println(stu.hashCode());
    }
}

 但是问题又来了 : 如果stu 与 stu2里面的两个参数值一样 , 按照我们的理解它们应该是一样的 : 

但结果确实不一样的两个结果.

public static void main(String[] args) {
        Student stu = new Student(12,"zs");
        System.out.println(stu.hashCode());
        Student stu2 = new Student(12,"zs");
        System.out.println(stu2.hashCode());
    }

要想解决这个问题 , 我们就去Student里面去重写hashCode方法. 

class Student {
    public int age;
    public String name;

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

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

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

 可以看到重写后的哈希方法里 , return语句 括号里面有两个参数 , 也就意味着根据age与name去形成结果. 因此在一样的age 与 name  的情况下 , 它们调用hashCode返回的值都是相同的.

将上面的代码转变为引用类型

package demo2;

public class HashBuck<K,V> {
    static class Node<K,V> {
        public K key;
        public V value;
        public Node<K,V> next;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }

    public Node<K,V>[] array;
    public int usedSize;
    public static final double loadFactor = 0.7;

    public HashBuck() {
        this.array = (Node<K,V>[])new Node[10];
    }
    public void put(K key, V value) {
        int index = key.hashCode() % array.length;
        Node<K,V> cur = array[index];
        while (cur != null) {
            if (cur.key.equals(key)) {
                cur.value = value;
                return;
            }
            cur = cur.next;
        }
        Node<K,V> node = new Node<>(key,value);
        node.next = array[index];
        array[index] = node;
        usedSize++;
        if (calculateLoadFactor() >= loadFactor) {
            resize();
        }
    }

    private void resize() {
        Node<K,V>[] newArray = (Node<K,V>[])new Node[2*array.length];
        for (int i = 0; i < array.length; i++) {
            Node cur = array[i];
            Node curNext = null;
            while (cur != null) {
                curNext = cur.next;
                int index = cur.key.hashCode() % newArray.length; //找到了在新数组当中的位置
                cur.next = newArray[index];
                newArray[index] = cur;
                cur = curNext;
            }
        }
        this.array = newArray;
    }

    public double calculateLoadFactor() {
        return (usedSize*1.0) / array.length;
    }
    
    public V get(K key) {
        int index = key.hashCode() % array.length;
        Node<K,V> cur = array[index];
        while (cur != null) {
            if (cur.key.equals(key)) {
                return cur.value;
            }
            cur = cur.next;
        }
        throw new RuntimeException("找不到Key所对应的Value");
    }
}

重点要注意一下 : 在求哈希函数时 , 要用key.hashCode() % 数组的长度. 再然后引用数据类型之间比较相不相同要用equals()方法.

问题 : hashCode()一样 , equals一定一样吗  ? 

          equals()一样 , hashCode()一定一样吗 ? 

答案分别为 : 不一定 , 一定 .

以上就是我对 HashMap 与 HashSet 的一些见解 . 希望可以帮到大家~~~

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

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

相关文章

【项目精选】javaEE健康管理系统(论文+开题报告+答辩PPT+源代码+数据库+讲解视频)

点击下载源码 javaEE健康管理系统主要功能包括&#xff1a;教师登录退出、教师饮食管理、教师健康日志、体检管理等等。本系统结构如下&#xff1a; &#xff08;1&#xff09;用户模块&#xff1a; 实现登录功能 实现用户登录的退出 实现用户注册 &#xff08;2&#xff09;教…

运筹系列78:cbc使用介绍

1. 上手 1.1 快速使用 首先是简单的调用测试&#xff0c;在mac上首先安装clp的库&#xff1a;brew install coin-or-tools/coinor/cbc&#xff0c;然后新建项目进行调用&#xff0c;各项配置如下&#xff0c;注意要添加的library和directory比较多&#xff1a; 1.2 命令行方…

锁机制面试题

你是怎么理解乐观锁和悲观锁的&#xff0c;具体怎么实现呢&#xff1f; 悲观锁认为多个线程访问同一个共享变量冲突的概率较大 , 在每次访问共享变量之前都去真正加锁. 乐观锁认为多个线程访问同一个共享变量冲突的概率不大.,并不会真的加锁, 而是直接尝试访问数据 . 在访问的…

AcWing 165. 小猫爬山(DFS + 剪枝优化)

AcWing 165. 小猫爬山&#xff08;DFS 剪枝优化&#xff09;一、问题二、分析1、贪心想法的误区2、正解三、代码一、问题 二、分析 这道题其实总结下来&#xff0c;就是一句话&#xff0c;让更多的小猫坐在一辆车上&#xff0c;从而减少车的数量。 1、贪心想法的误区 这道题…

Spring Security in Action 第四章 SpringSecurity处理密码的相关讨论

本专栏将从基础开始&#xff0c;循序渐进&#xff0c;以实战为线索&#xff0c;逐步深入SpringSecurity相关知识相关知识&#xff0c;打造完整的SpringSecurity学习步骤&#xff0c;提升工程化编码能力和思维能力&#xff0c;写出高质量代码。希望大家都能够从中有所收获&#…

Java面试——maven篇

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

以太坊数据开发-Web3.py-安装连接以太坊数据

Web3.py是连接以太坊的python库&#xff0c;它的API从web3.js中派生而来。如果你用过web3.js&#xff0c;你会对它的API很熟悉。但惭愧的是&#xff0c;作为一个以太坊上Dapp的开发者&#xff0c;我几乎没有直接使用过web3.js&#xff0c;也没有看过它的API。 官网&#xff1a…

STL——queue

一、queue介绍和使用 1.queue文档介绍 &#xff08;1&#xff09;队列是一种容器适配器&#xff0c;专门用于在FIFO上下文&#xff08;先进先出&#xff09;中操作&#xff0c;其中从容器的一端插入元素&#xff0c;另一端提取元素。 &#xff08;2&#xff09;队列是作为容…

ImageMagick任意文件读取漏洞(CVE-2022-44268)

0x00 前提 前几天爆出一个 ImageMagick 漏洞 &#xff0c;可以造成一个任意文件读取的危害比较可观&#xff0c;最近有时间来复现学习一下 主要是影响的范围很大&#xff0c;很多地方都有这个问题&#xff0c;需要来学习一下 0x01 介绍 ImageMagick 是一个免费的开源软件套…

【计组】设计大型DMP系统--《深入浅出计算机组成原理》(十四)

目录 一、DMP&#xff1a;数据管理平台 二、MongoDB 真的万能吗 三、关系型数据库&#xff1a;不得不做的随机读写 &#xff08;一&#xff09;Cassandra&#xff1a;顺序写和随机读 1、Cassandra 的数据模型 2、Cassandra 的写操作 3、Cassandra 的读操作 &#xff08…

Mysql 增删改查(一) —— 查询(条件查询where、分页limits、排序order by、分组 group by)

查询 select 可以认为是四个基本操作中使用最为频繁的操作&#xff0c;然而数据量比较大的时候&#xff0c;我们不可能查询所有内容&#xff0c;我们一般会搭配其他语句进行查询&#xff1a; 假如要查询某一个字段的内容&#xff0c;可以使用 where假如要查询前几条记录&#…

线上服务质量的问题该如何去处理?你有什么思路?

线上服务质量的问题该如何去处理&#xff1f;你有什么思路&#xff1f; 目录&#xff1a;导读 发现线上故障 处理线上故障 修复线上故障 运营线上质量 就是前几天有个同学问了我一个问题&#xff1a;目前业内高可用部署主要采用方案&#xff1f; 看到这个问题&#xff0c…

5G网络架构与组网部署

文章目录5G网络架构与组网部署一、5G网络架构的演进趋势5G移动通信系统4G移动通信系统二、核心网架构的演进三、无线接入网的演进趋势5G网络架构与组网部署 一、5G网络架构的演进趋势 5G移动通信系统 5G移动通信系统包括5GC(5G Corn Network,5G核心网)和NG-RAN(Next Generat…

Zookeeper下载安装与集群搭建

Zookeeper下载安装与集群搭建1.下载安装1.1 下载安装1.2 配置启动2.集群搭建2.1 搭建要求2.2 准备工作2.3 配置集群2.4 启动集群2.5 模拟集群异常1.下载安装 1.1 下载安装 1、环境准备 ZooKeeper服务器是用Java创建的&#xff0c;它运行在JVM之上。需要安装JDK 7或更高版本。…

跟我一起写Makefile--个人总结

此篇笔记是根据陈皓大佬《跟我一起写Makefile》学习所得 文章目录换行符clean变量make的自动推导另类风格的Makefile清空目标文件的规则cleanMakefile总述显示规则隐晦规则变量的定义注释引用其它的Makefile环境变量MAKEFILESmake的工作方式书写规则规则举例规则的语法在规则中…

Flutter Windows端打包并生成可安装文件流程

Windows打包 1.首先安装visual Studio 下载地址&#xff1a;https://visualstudio.microsoft.com/zh-hans/ 下载成功后按照下图勾选桌面应用和移动应用下的使用C的桌面开发&#xff0c;勾选右侧安装详细信息中的windows 11/10 sdk 中的任意一个完成安装即可 2.打包Windows …

IC封装常见形式

参考&#xff1a;https://blog.csdn.net/dhs888888/article/details/127673300?utm_mediumdistribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-127673300-blog-115610343.pc_relevant_multi_platform_whitelistv4&spm1001.2101.3001.4242…

truffle 创建测试合约并部署到测试网络

1、npm 安装truffle npm install -g truffle2、创建truffle项目 mkdir imooc-on-blockchain-truffle && cd imooc-on-blockchain-truffle3、初始化truffle目录&#xff0c;会生成如下几个目录 contracts 存放.sol合约文件migrations 部署脚本目录test 测试文件目录t…

20230210组会论文总结

目录 【Ultra-High-Definition Low-Light Image Enhancement: A Benchmark and Transformer-Based Method】 【ShuffleMixer: An Efficient ConvNet for Image Super-Resolution】 【A Close Look at Spatial Modeling: From Attention to Convolution 】 【DEA-Net: Single i…

自动机,即有限状态机

文章目录一、问题来源二、题目描述三、题解中的自动机四、自动机学习五、有限状态机的使用场景一、问题来源 今天做力克题目的时候看到了字符串转换整数的一道算法题&#xff0c;其中又看到了题解中有自动机的概念&#xff0c;所以在这里对自动机做个笔记。题目链接 二、题目描…