首页 > 其他分享 >映射

映射

时间:2024-07-06 20:10:16浏览次数:7  
标签:map hash 映射 value key put null

Map

1 Map的基本使用

Map(映射)是由Key(键)和Value(值)组成的。每一个键都是不重复的。一个键对应的一个值。键和值在一起称之为键值对。

一个映射是由多个键值对组成的,

将每一个键值对看作一个对象,抽取出一个代表键值对的类-->Map.Entry

  • 常用方法
public class TestDemo {
    public static void main(String[] args) {
        // 创建Map
        Map<String,String> map = new HashMap<>();
        // 添加元素   
        map.put("汪峰","章子怡");
        map.put("王宝强","马蓉");
        map.put("陈羽凡","白百合");
        map.put("文章","马伊利");
        map.put("华晨宇","张碧晨");
        map.put("汪小菲","大S");
        // 如果key重复,对应的value会被覆盖
        map.put("李小璐","贾乃亮");
        map.put("李小璐","pgone");
        map.put("周淑怡","pgone");
        // 清空集合
//        map.clear();

        // 根据key获取value
        String s = map.get("华晨宇");
        System.out.println(s);
        // 如果key不存在,会返回null
        String s1 = map.get("柯震东");
        System.out.println(s1);

        // 根据key移除元素,会返回移除掉的value
        String s2 = map.remove("王宝强");
        System.out.println(s2);

        // 判断key是否存在
        System.out.println(map.containsKey("周淑怡"));
        // 判断value是否存在
        System.out.println(map.containsValue("pgone"));
        // 返回键值对的集合
        Set<Map.Entry<String, String>> entries = map.entrySet();
        System.out.println(entries);
//        entries.forEach(System.out::println);
//        map.forEach(new BiConsumer<String, String>() {
//            // s : key
//            // s2 : value
//            @Override
//            public void accept(String s, String s2) {
//                System.out.println(s + s2);
//            }
//        });
        // 快速遍历map
        map.forEach((key,value) -> System.out.println(key + " : " + value));
        // 判断map是否为空
        System.out.println(map.isEmpty());
        // 返回所有的key的集合
        Set<String> strings = map.keySet();
        for (String key : strings) {
            String value = map.get(key);
            System.out.println(value);
        }
        // 键值对的个数
        int size = map.size();
        System.out.println(size);

        Collection<String> values = map.values();
        System.out.println(values);
        System.out.println(map);
        practice();
    }

    // 课堂练习: 输入一个字符串,统计每一个字符出现的次数  aaabbccdd
    public static void practice(){
        String str = new Scanner(System.in).next();
        // 创建Map  map的key是字符 map的value是字符出现的次数
        Map<Character,Integer> map = new HashMap<>();
        // 遍历字符串,获取每一个字符
        for (int i = 0; i < str.length(); i++) {
            // 获取字符
            char c = str.charAt(i);
            // 判断字符是否出现
            if (map.containsKey(c)){
                // 如果存在
                Integer integer = map.get(c);
                integer += 1;
                map.put(c,integer);
            }else {
                // 首次出现
                map.put(c,1);
            }
        }

        map.forEach((key,value) -> System.out.println(key + "出现的次数是" + value));
    }

}

2 补充:断点调试

F8 : 执行下一行代码

F9: 跳到下一个断点处

image-20240625111148952

3 HashMap

HashMap是Map接口的实现类,是无序的,key是唯一的。并且key和value都可以为null。是线程不安全的。而且其中的数据位置可能会发生改变。

底层的数据结构是数组 + 链表 + 红黑树

3.1 常用构造方法

// 无参构造,默认底层的数组的初始容量是16,加载因子是0.75F
// 实际上,调用这行无参构造,只会把0.75赋值给加载因子,并没有立即创建长度为16的数组
HashMap() 使用默认初始容量(16)和默认加载因子(0.75)构造一个空 HashMap 。  
    
// 按照指定的初始化容量来创建数组,加载因子是0.75
// 实际上初始化容量在底层经过运算,会变成2的n次方的形式
HashMap​(int initialCapacity) 使用指定的初始容量和默认加载因子(0.75)构造一个空 HashMap 。  

// 创建HashMap,可以指定初始化容量和加载因子
// 实际上初始化容量在底层经过运算,会变成2的n次方的形式
HashMap​(int initialCapacity, float loadFactor) 使用指定的初始容量和加载因子构造一个空 HashMap 。  

3.2 底层原码剖析

  • 成员变量
// 默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 加载因子
final float loadFactor;
// 扩容的阈值
int threshold;
// HashMap底层的数据
transient Node<K,V>[] table;
// 默认初始化容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
// 红黑树形成的结点数量
static final int TREEIFY_THRESHOLD = 8;
// 扭转红黑树的数组长度
static final int MIN_TREEIFY_CAPACITY = 64;
  • put方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    	// 结点的数组
        Node<K,V>[] tab; 
        // 临时的结点
        Node<K,V> p;
        // n 数组的长度
        // i 数组元素放入的索引
        int n, i;
        // 当第一次put时,table还是null,会创建长度默认是16的数组
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        // 判断数组对应索引处是否有结点
        if ((p = tab[i = (n - 1) & hash]) == null)
            // 如果没有结点,创建新结点,赋值给i处
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            // 比较hash和key是否相同
            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 {
                // 如果第一个结点不同,那么继续向下判断剩下的每一个结点,如果有一个相同,就替换value
                // 如果都不同,把结点放在链表最后一位
                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
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                // 如果key相同,将新的value覆盖旧的value,并且返回旧的value
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        // 实际元素个数 + 1 并且判断是否需要扩容
        if (++size > threshold)
            resize();// 扩容 会重新分布元素的位置
        afterNodeInsertion(evict);
        return null;
    }
final Node<K,V>[] resize() {
    // 老数组
        Node<K,V>[] oldTab = table;
    // 老容量
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
    // 老阈值
        int oldThr = threshold;
    // 新容量和新阈值
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            } // 把老容量 翻倍来进行扩容
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                // 老阈值翻倍
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            // 如果还没有数组,指定初始化容量16和阈值 12
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
    // 创建新的数组
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    // 把新的数组赋值给底层数组
        table = newTab;
    
    // 如果老数组有值,那么遍历老数组的内容,把元素重新分布在新数组中.要么在原来位置,要么是原来位置+原来的数组长度
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

总结:

  • 当创建HashMap时,会把加载因子赋值为0.75(默认无参构造中)
  • 当第一次put时,会创建一个长度为16的Node类型的数组,默认阈值是12.然后根据计算把数组中的某一个索引添加一个Node对象
  • 如果key相同,hash值也会相同。会把旧值替换成新值,并且返回旧值
  • 如果key不相同,但是hash值相同,会到对应桶的位置进行一个个的逐位比较,直到链表结点的next是null、然后把结点连接在这个链表的最后一位。如果链表长度大于8,并且数组长度 >= 64 会扭转成红黑树。红黑树可以提高查询效率
  • 如果链表长度大于8,数组长度小于64,会进行一次扩容。数组长度翻倍,元素位置重新分配、
  • HashMap的初始化容量永远是2的n次方的形式
  • 扩容是容量翻倍,重新分配的索引要么是原来的索引处,要么是原来索引 + 原来的数组长度 处
  • 当扩容时,如果桶上是一个红黑树,经过重新分配后长度 <= 6,会重新扭转成链表
  • 注意: HashMap的key一般不要使用自定义的类型。如果要使用,建议重写hashCode方法
    public static void main(String[] args) {
        // 字符串有可能内容不同,但是hashCode相同
//        System.out.println("三个".hashCode());
//        System.out.println("上下".hashCode());

        HashMap<String,Integer> hashMap = new HashMap<>();
        // 当首次添加元素时,会创建一个长度为16的数组,并且把数据放置在某一个桶上面
        hashMap.put("1",1000);
        // 哈希冲突/碰撞  根据 hash & (n - 1) 的结果重复,就是哈希冲突
        // HashMap如何处理hash碰撞?
        // 情况一: key的值一样  key的hash值肯定也一样  就会出现hash碰撞
        // 结果:新的value替换旧的value,并且返回旧的value
//        Integer integer = hashMap.put("1", 2000);
//        System.out.println(integer);
        // 情况二: key的值不一样 key的hash一样,会出现哈希碰撞
        hashMap.put("Ma",3000);
        hashMap.put("NB",4000);

        // 情况三:key的值不一样, key的hash不一样,但是经过计算的位置重复
        hashMap.put("a",5000);
        hashMap.put("b",6000);
        hashMap.put("c",7000);
        hashMap.put("d",5000);
        hashMap.put("e",5000);

        System.out.println(hashMap);
    }

4 Hashtable

特点:

不允许key和value是null,如果是null,抛出空指针异常

默认初始容量是11

默认的扩容是扩大一倍并且 + 1

指定容量给多少就是多少

同步式线程安全的映射

标签:map,hash,映射,value,key,put,null
From: https://www.cnblogs.com/460759461-zeze/p/18287664

相关文章

  • [图解]企业应用架构模式2024新译本讲解19-数据映射器1
    100:00:01,720-->00:00:03,950下一个我们要讲的就是200:00:04,660-->00:00:07,420数据映射器这个模式300:00:09,760-->00:00:13,420这个也是在数据源模式里面400:00:13,430-->00:00:14,820用得最广泛的500:00:16,250-->00:00:19,170大多数都是用600:......
  • 内存映射
    mmap内存映射函数(显示图片的新方法)----也是Linux系统IO中的函数之一基本概念系统IO函数的共同点:就是他们的形参中一定有一个是文件描述符(除open)。内存映射的意思:拆内存:普通运存,显存(集显和独显),是一块内存空间,存放显示画面的像素点。映射:两个集合中的元素,都具有......
  • Nginx设置二级域名映射到不同的Tomcat
    一、前言在之前的博客中,已经安装好了多个tomcat和nginx,本篇博客将介绍如何设置不同的二级域名转发到不同的tomcat上二、配置服务器端我使用的是腾讯云服务器,只需要在云解析中配置相关域名信息即可三、配置nginx进入nginx的配置文件中cd/usr/local/nginx/confvimnginx.c......
  • AI算法04-自组织映射神经网络Self-Organizing Map | SOM
    自组织映射神经网络自组织映射(SOM)或自组织特征映射(SOFM)是一种类型的人工神经网络(ANN),其使用已训练的无监督学习以产生低维(通常为二维),离散的表示训练样本的输入空间,称为地图,因此是一种减少维数的方法。自组织映射与其他人工神经网络不同,因为它们应用竞争学习而不是纠错学习(例如......
  • 【Elasticsearch】Elasticsearch动态映射与静态映射详解
    文章目录......
  • Docker 目录挂载和卷映射
    docker卷和目录的区别_docker挂载和映射的区别-CSDN博客linuxdocker目录挂载映射_linux创建网关docker映射目录-CSDN博客因为容器是无状态,rm掉就不报错数据,所以需要-v挂载到宿主机上路径:使用绝对路径的是目录挂载-v/usr/local/www:/opt/html使用相对路径的是卷映射-v......
  • 掌握Eloquent ORM:Laravel中的对象关系映射艺术
    掌握EloquentORM:Laravel中的对象关系映射艺术在现代Web应用开发中,数据库的操作是核心功能之一。Laravel框架提供了一个强大而优雅的ORM(对象关系映射)工具——Eloquent。Eloquent让数据库操作变得简单直观,同时保留了SQL的强大灵活性。本文将详细介绍如何在Laravel中使用Eloq......
  • 修改主机名和IP的映射关系
    1.vim/etc/hosts,输入:127.0.0.1localhostlocalhost.localdomainlocalhost4localhost4.localdomain4::1localhostlocalhost.localdomainlocalhost6localhost6.localdomain6192.168.137.131node1192.168.137.132node2192.168.137.133node3然后cat/etc/hos......
  • MyBatis2(MyBatis基础配置 动态代理 映射器 select 元素 insert 元素 update 元素和del
    目录一、MyBatis基础配置1.MyBatis配置文件2.<configuration>元素3.<enviroments>元素4.<properties>元素5.<typeAliases>元素6.<mappers>元素二、动态代理三、映射器1.映射器与接口2. 映射器的引入 3.映射器的组成 四、select元素参数传递多......
  • Unity 导航路线生成,小地图同步映射, 经过以后地图与小地图删除点位(点击小地图控制导航
    效果:(如下图所示)操作方法:搭建小地图UI截取图片创建地面挂载如下代码:usingSystem.Collections.Generic;usingUnityEngine;usingUnityEngine.UI;[RequireComponent(typeof(MeshFilter),typeof(MeshCollider),typeof(MeshRenderer))]publicclassMap:Mo......