jdk1.7与jdk1.8的HashMap区别2-底层原理区别

jdk1.7与jdk1.8的HashMap区别1-基本结构与属性对比_ycsdn10的博客-CSDN博客

一、代码区别

1.代码数:JDK1.7与JDK1.8相差了一倍的代码

2.方法数:JDK1.7是40个,JDK1.8是51个(只算基本方法)

二、Hash差别

1.JDK1.7

    static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

 进行了4次位运算,5次亦或运算

2.JDK1.8

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

 进行了1次位运算,1次亦或运算

三、数据存储机制差别

由上一章知道,JDK1.7是数组+链表,JDK1.8是数组+链表+红黑树

1.JDK1.7存储机制

(1)数组特点

根据下标定位,一般情况下查询、修改快于链表。

(2)链表特点

连接元素通过指针,新增和删除只需变更指针连接即可,所以一般情况下增删快,查询因需要遍历链表,所以查询效率一般慢于数组。

(3)解决Hash冲突

采用链地址法来存储发生Hash冲突的元素。

(4)节点

i.节点属性

    static class Entry<K,V> implements Map.Entry<K,V> {
        // 对应的key值
        final K key;
        // 对应value值
        V value;
        // 指针,指向下一个entry
        Entry<K,V> next;
        // hash值
        final int hash;
    }

 ii.具体代码结构

 iii.完整数据结构

1)假设以下链表当中的hash通过计算是同一个hash,那么为以下结构

 2)数组和链表两部分

(5) 初始化

public HashMap() {}

public HashMap(int initialCapacity) {}

public HashMap(int initialCapacity, float loadFactor) {}

public HashMap(Map<? extends K, ? extends V> m) {}

 以上为4个初始化的几个方式。

i.HashMap()

    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
    }

负载因子为:默认DEFAULT_LOAD_FACTOR,为0.75f

扩容阈值为:默认初始化容量*负载因子=16*0.75 = 12

数组table:初始化为Entry[16]的数组

ii.HashMap(int initialCapacity) 

    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

从上面看出,容量参数进来之后,调用的是this,也就是iii的方法 

iii.HashMap(int initialCapacity, float loadFactor) 

    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        // Find a power of 2 >= initialCapacity
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;

        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
        table = new Entry[capacity];
        init();
    }

1)当容量小于0时,直接抛出异常

2)当容量大于最大容量时候,容量赋值为最大容量2的30次

3)如果负载因子是小于等于0或者不是Float,那么就抛出异常

4)设置容量变化初始为1,如果初始值大于实际容量,那么变化值左移一位,即实际容量值=实际容量值*2,之后在比较,循环 4)步骤,直到初始化容量小于或者等于实际容量(这边可以看到,实际容量只能是2的倍数)

5)扩容阈值 = 实际容量*负载因子(因为实际容量是2的倍数,如果负载因子是默认值,那么扩容阈值=2的倍数*0.75,最小值为1.5)

6)数组容量为刚才计算得到的实际容量

iv.HashMap(Map<? extends K, ? extends V> m) 

    public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        putAllForCreate(m);
    }

    private void putAllForCreate(Map<? extends K, ? extends V> m) {
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            putForCreate(e.getKey(), e.getValue());
    }

    private void putForCreate(K key, V value) {
        int hash = (key == null) ? 0 : hash(key.hashCode());
        int i = indexFor(hash, table.length);

        /**
         * Look for preexisting entry for key.  This will never happen for
         * clone or deserialize.  It will only happen for construction if the
         * input Map is a sorted map whose ordering is inconsistent w/ equals.
         */
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                e.value = value;
                return;
            }
        }

        createEntry(hash, key, value, i);
    }

    static int indexFor(int h, int length) {
        return h & (length-1);
    }

    void createEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

1)调用iii的方法进行初始化,容量=(int)(传进来的map的大小/默认负载因子(0.75)+1)和默认初始容量(16)取大的值;负载因子为默认负载因子

2)循环处理所有键值对

2.1)如果key为null,取0,否则进行取hash后(进行了4运算)的值,次位运算,5次亦或

2.2)进行hash值与长度-1进行与运算,得到Entry数组的下标,因为实际容量只能是2的倍数,所以在这个条件下,满足以下

h % length == h&(length-1)

也就是取模运算, 但是因为与运算效率高,而%length是要转换成10进制,所以用h&(length-1)

2.3)for循环处理 在entry数组中查找hash码相同并且key相同的位置,放入当前的传入的value,并且结束此次设置值处理

2.4)2.3没有找对相同的hash与key的位置,会进行一个插入,会新建一个Entry对象,next指向原来的头节点,也就是头插法;头插法的好处就是效率更快(createEntry官方的注释是:与addEntry类似,只是在创建条目时使用此版本,作为Map构造或“伪构造”(克隆,反序列化)。这个版本不必担心调整表的大小。子类覆盖它以改变HashMap(Map)的clone和readObject行为。)

关于头插法是这样的,新的节点放置在hash相同的链表的头部

(6)添加元素

public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }

 6.1)key为null的时候,数组下标为0的Entry,对应的值为传进来的Value,因为是null,所以hash为0,直接添加到数组下标为0的时候的链表头结点当中。并结束

6.2)进行取hash后的值,4次位运算,5次亦或进行hash值与长度-1进行与运算,得到Entry数组的下标,因为实际容量只能是2的倍数,所以在这个条件下,满足以下

h % length == h&(length-1)

也就是取模运算, 但是因为与运算效率高,而%length是要转换成10进制,所以用h&(length-1).(跟初始化的时候类似)

6.3)for循环处理 在entry数组中查找hash码相同并且key相同的位置,放入当前的传入的value,并且结束此次设置值处理

6.4)6.3没有找对相同的hash与key的位置,会进行一个插入,会新建一个Entry对象,next指向原来的头节点,也就是头插法;头插法的好处就是效率更快(跟初始化类似)

(7)移除元素

    public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }

    final Entry<K,V> removeEntryForKey(Object key) {
        int hash = (key == null) ? 0 : hash(key.hashCode());
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;

        while (e != null) {
            Entry<K,V> next = e.next;
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++;
                size--;
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }

        return e;
    }

 7.1)根据传入的Key,进行Hash处理,null则为0,不为null则计算hash

7.2)通过hash与数组长度,计算数组下标

7.3)获取对应下标的数组的Entry

7.4)如果Entry当前为Null,代表这个key在对应的数组并没有对应的value,所以直接返回

7.5)如果取得的Entry不为null,则要取到当前数组位置下的链表,并且循环判断如果key相等,hash相等,modCount(更改的次数)会增加,size减去1,然后对应的链表节点移除,当前位置指向下一个节点,前一个节点的下一个位置属性从原有的当前Key位置指向下一个节点

7.6)所有都没有找到,原样返回。

(8)扩容

public void putAll(Map<? extends K, ? extends V> m) {
        int numKeysToBeAdded = m.size();
        if (numKeysToBeAdded == 0)
            return;

        /*
         * Expand the map if the map if the number of mappings to be added
         * is greater than or equal to threshold.  This is conservative; the
         * obvious condition is (m.size() + size) >= threshold, but this
         * condition could result in a map with twice the appropriate capacity,
         * if the keys to be added overlap with the keys already in this map.
         * By using the conservative calculation, we subject ourself
         * to at most one extra resize.
         */
        if (numKeysToBeAdded > threshold) {
            int targetCapacity = (int)(numKeysToBeAdded / loadFactor + 1);
            if (targetCapacity > MAXIMUM_CAPACITY)
                targetCapacity = MAXIMUM_CAPACITY;
            int newCapacity = table.length;
            while (newCapacity < targetCapacity)
                newCapacity <<= 1;
            if (newCapacity > table.length)
                resize(newCapacity);
        }

        for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
            put(e.getKey(), e.getValue());
    }
    void addEntry(int hash, K key, V value, int bucketIndex) {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }

8.1)不管数组容量左移一位,还是直接2*数组容量,都是扩大为原来的两倍长度

    void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }

 

8.2)扩容时,先拿到原来的数组长度,如果老的数组长度已经跟设定的最大长度相等了,那么,扩容阈值就设置为最大值,并且结束扩容

8.3)容量正常情况下,建立新数组,然后进入实际的数据复制阶段

void transfer(Entry[] newTable) {
        Entry[] src = table;
        int newCapacity = newTable.length;
        for (int j = 0; j < src.length; j++) {
            Entry<K,V> e = src[j];
            if (e != null) {
                src[j] = null;
                do {
                    Entry<K,V> next = e.next;
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }

8.3.1)先获取原来的数组,并且得到新数组的大小(设置hash对应的位置不变)

 

8.3.2)循环下标处理数组,不为null的时候

8.3.2.1)原数组下标位置为null

8.3.2.2)获取对应位置链表的首个Entry,然后计算新数组下标位置

8.3.2.3)把原链表反序,加入到目前的数组位置

8.4)处理扩容阈值和新数组地址

2.JDK1.8存储机制

待补充

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
            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;
    }

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

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

相关文章

深入学习 Redis - 事务、实现原理、指令使用及场景

目录 一、Redis 事务 vs MySQL事务 二、Redis 事务的执行原理 2.1、执行原理 2.2、Redis 事务设计这么简单&#xff0c;为什么不涉及成 MySQL 那样强大呢&#xff1f; 三、Redis 事务的使用 3.1、使用场景 3.2、具体演示 开启/执行/放弃事务 watch 监控 watch 实现原理…

SQL Server数据库如何添加Oracle链接服务器(Windows系统)

SQL Server数据库如何添加Oracle链接服务器 一、在添加访问Oracle的组件1.1 下载Oracle的组件 Oracle Provider for OLE DB1.2 注册该组件1.2.1 下载的压缩包解压位置1.2.2 接着用管理员运行Cmd 此处一定要用管理员运行&#xff0c;否则会报错 二、配置环境变量三、 重启SQL Se…

Android Studio System.out.println()中文乱码

第一步&#xff1a; 打开studio64.exe.vmoptions加入-Dfile.encodingUTF-8 第二步&#xff1a; File-Settings-Editor-File Encodings 把所有的编码格式改为UTF-8 尝试跑一下代码&#xff0c;如果还不行&#xff0c;重启IDE 再试试。

【链表OJ 1】移除链表元素val

大家好&#xff0c;欢迎来到我的博客&#xff0c;此题是关于链表oj的第一题&#xff0c;此后还会陆续更新博客&#xff0c;如有错误&#xff0c;欢迎大家指正。 来源:https://leetcode.cn/problems/remove-linked-list-elements/description/ 题目: 方法一:定义prev和cur指针…

大模型“瘦身”进手机 下一个iPhone时刻将至?

一股“端侧大模型”浪潮正在涌来。华为、高通等芯片巨头正探索将AI大模型植入端侧&#xff0c;让手机实现新一代物种进化。 相比ChatGPT、Midjourney等AI应用依赖云端服务器提供服务&#xff0c;端侧大模型主打在本地实现智能化。它的优势在于能够更好地保护隐私&#xff0c;同…

我在VScode学Java多态(Java多态、instanceof)

Java的多态&#xff08;Polymorphism&#xff09;是面向对象编程中的一种特性&#xff0c;它允许不同的对象能够以统一的方式进行访问和操作。它允许一个类的实例在运行时表现出多种形态。 Java多态的实现主要依赖于两个基本概念&#xff1a;继承和方法重写。在Java中&#xff…

DC-7靶机

DC-7靶机地址 同样的&#xff0c;把靶机跟kali放在同一网段&#xff0c;&#xff08;NAT模式&#xff09; 主机发现 arp-scan -l端口扫描 nmap -A -T4 -p- 192.168.80.13922端口开始&#xff0c;80端口开启 浏览器先访问一下靶机的80端口 熟悉的Drupal站点 先爆破一下目录…

http相关知识点

文章目录 长链接http周边会话保持方案1方案2 基本工具postmanFiddlerFiddler的原理 长链接 一张网页实际上可能会有多种元素组成&#xff0c;这也就说明了网页需要多次的http请求。可由于http是基于TCP的&#xff0c;而TCP创建链接是有代价的&#xff0c;因此频繁的创建链接会…

【Spring】Spring AOP 初识及实现原理解析

博主简介&#xff1a;想进大厂的打工人博主主页&#xff1a;xyk:所属专栏: JavaEE进阶 目录 文章目录 一、初识AOP 1.1 什么是AOP&#xff1f; 1.2 AOP的组成 1.2.1 切面&#xff08;Aspect&#xff09; 1.2.2 切点&#xff08;Pointcut&#xff09; 1.2.3 连接点&…

菲律宾的区块链和NFT市场调研

菲律宾的区块链和NFT市场调研 基本介绍 参考&#xff1a; https://zh.wikipedia.org/wiki/%E8%8F%B2%E5%BE%8B%E5%AE%BE zheng治制度&#xff1a;Zongtong议会制 现任Zongtong&#xff1a; 小费迪南德马科斯, 是独裁者费迪南德马科斯之子&#xff0c;人称“小马科斯” 官方语言…

使用ResponseBodyAdvice做分页处理

目录 父pom文件 pom文件 配置文件 MyResponseBodyAdvice ResponseDto MyBatisConfig UsersController UsersMapper UserMapper.xml 结果 父pom文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/PO…

【计算机视觉 | Kaggle】保姆级教程:入门 Kaggle 的步骤详细介绍

文章目录 一、Overview二、Evaluation三、Timeline四、Code Requirements五、Data5.1 数据的可视化5.2 文件 六、Discussion七、Code 一、Overview 当进入到一场比赛的 Overview 页面后&#xff0c;先读完 Description&#xff0c;了解比赛讲了一件什么事情。 我们以一场比赛…

鸿鹄工程项目管理系统em Spring Cloud+Spring Boot+前后端分离构建工程项目管理系统em

​ Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目…

成功解决Linux下中文乱码问题,CentOS7设置系统字符编码

在linux中&#xff0c;可以使用以下命令查看当前系统的字符编码&#xff1a; echo $LANG 如果不是UTF-8&#xff0c;就会出现中文乱码现象! 解决办法&#xff1a;设置字符编码环境变量为utf-8 1. 打开 ~/.bashrc 或 ~/.bash_profile 文件 vi ~/.bashrc 或 vi ~/.bash_prof…

力扣 474. 一和零

题目来源&#xff1a;https://leetcode.cn/problems/ones-and-zeroes/description/ C题解&#xff1a;本题其实是01背包问题&#xff01;只不过这个背包有两个维度&#xff0c;一个是m 一个是n&#xff0c;而不同长度的字符串就是不同大小的待装物品。动规五部曲&#xff1a; …

《高性能MySQL》——查询性能优化(笔记)

文章目录 六、查询性能优化6.1 查询为什么会慢6.2 慢查询基础&#xff1a;优化数据访问6.2.1 是否向数据库请求了不需要的数据查询不需要的记录多表关联时返回全部列总是取出全部列重复查询相同的数据 6.2.2 MySQL 是否在扫描额外的记录响应时间扫描的行数与返回的行数扫描的行…

云计算技术——多GPU渲染的云渲染服务

多GPU渲染的云渲染服务&#xff0c;是一种利用云计算技术&#xff0c;将多个图形处理器&#xff08;GPU&#xff09;集成在一起&#xff0c;为用户提供高效、便捷、低成本的渲染解决方案的服务。本文将从多GPU渲染的概念、优势、应用场景&#xff0c;云渲染服务的特点、优势&am…

使用 PowerShell 将 Excel 中的每个工作表单独另存为独立的文件

导语&#xff1a;在日常工作中&#xff0c;我们经常需要处理 Excel 文件。本文介绍了如何使用 PowerShell 脚本将一个 Excel 文件中的每个工作表单独另存为独立的 Excel 文件&#xff0c;以提高工作效率。 1. 准备工作 在开始之前&#xff0c;请确保已经安装了 Microsoft Exc…

领航优配:沪指震荡涨0.47%,保险、券商板块强势,互联金融概念活跃

4日早盘&#xff0c;两市股指高开高走&#xff0c;沪指一度涨逾1%打破3300点&#xff0c;随后涨幅有所收窄&#xff1b;两市半日成交超6000亿元&#xff0c;北向资金小幅净流入。 截至午间收盘&#xff0c;沪指涨0.47%报3295.91点&#xff0c;深成指涨0.67%&#xff0c;创业板指…

【ARM Cache 系列文章 9 番外篇 -- ARMv9 系列 Core 介绍】

文章目录 ARMv9 系列CoreARM Cortex-A510 介绍ARM Cortex-A715ARM Cortex-A720 ARMv9 系列Core 2021年5月Arm公布了其最新3款CPU和3款GPU核心设计&#xff0c;三款新CPU分别是旗舰核心Cortex-X2、高性能核心Cortex-A710、高能效核心Cortex-A510 CPU&#xff0c;三款新GPU核心则…