返回顶部
首页 > 资讯 > 后端开发 > JAVA >Java 中HashMap 详解
  • 498
分享到

Java 中HashMap 详解

哈希算法散列表java 2023-09-03 07:09:35 498人浏览 薄情痞子
摘要

本篇重点: HashMap的存储结构 HashMap的put和get操作过程 HashMap的扩容 关于transient关键字 HashMap的存储结构 HashMap 总体是数组+链表的存储结构, 从jdk1.8开始,当数组的长度大

本篇重点:

HashMap的存储结构

HashMap的put和get操作过程

HashMap的扩容

关于transient关键字

HashMap的存储结构

HashMap 总体是数组+链表的存储结构, 从jdk1.8开始,当数组的长度大于64,且链表的长度大于8的时候,会把链表转为红黑树。

数组的默认长度是16。数组中的每一个元素为一个node,也就是链表的一个节点,node的数据包含: key的hashcode, key, value,指向下一个node节点的指针。

部分源码如下:

static class Node implements Map.Entry {        final int hash;         final K key;        V value;        Node next;        Node(int hash, K key, V value, Node next) {            this.hash = hash;            this.key = key;            this.value = value;            this.next = next;        }...}

随着put操作的进行,如果数组的长度超过64,且链表的长度大于8的时候, 则将链表转为红黑树,红黑树节点的结构如下,TreeNode继承的LinkedHashMap.Entry是继承HashMap.Node的,所以TreeNode是上面Node的子类。

static final class TreeNode extends LinkedHashMap.Entry {        TreeNode parent;  // red-black tree links        TreeNode left;        TreeNode right;        TreeNode prev;    // needed to unlink next upon deletion        boolean red;        TreeNode(int hash, K key, V val, Node next) {            super(hash, key, val, next);        }//...}

HashMap类的主要成员变量:

        transient Node[] table;        transient Set> entrySet;        transient int size;        transient int modCount;        // (The javadoc description is true upon serialization.    // Additionally, if the table array has not been allocated, this    // field holds the initial array capacity, or zero signifying    // DEFAULT_INITIAL_CAPACITY.)    int threshold;        final float loadFactor;View Code

HashMap的put操作过程

本小节讲述put操作中的主要步骤,细小环节会忽略。

map.put(key, value),首先计算key的hash,得到一个int值。

如果Node数组为空则初始化Node数组。这里注意,Node数组的长度length始终应该是2的n次方,比如默认的16, 还有32,64等

用 hash&(length-1) 运算得到数组下标,这里要提一句,其实正常我们最容易想到的,而且也是我之前很长一段时间以为的,这一步应该进行的是求模运算: hash % length ,这样得到的正好是0~length-1之间的值,可以作为数组的下标, 那么为何此处是位与运算呢?

先说结论: 上面提到数组的长度length始终是2^n,在这个前提下,hash & (length-1) 与hash % length是等价的。 而位与运算更快。这里后面会另开一遍进行详解。

  如果Node[hash&(length-1)]处为空,用传入的的key, value创建Node对象,直接放入该下标;如果该下标处不为空,且对象为TreeNode类型,证明此下标处的元素们是按照红黑树的结构存储的,将传入的key,value作为新的红黑树的节点插入到红黑树;否则,此处为链表,用next找到链表的末尾,将新的元素插入。如果在遍历链表的过程中发现链表的长度超过了8,此时如果数组长度<64则进行扩容,否则转红黑树。

如果key的hash和key本身都相等则将该key对应的value更新为新的value

需要扩容的话则进行扩容。

注意:

如果key是null则返回的hash为0,也就是key为null的元素一直被放在数组下标为0的位置。

 在JDK 1.8以前,链表是采用的头部插入的方式,从1.8改成了在链表尾部插入新元素的方式。 这么做是为了防止在扩容的时候,多线程时出现循环链表死循环。具体会新开一遍进行详细演绎。

HashMap的get操作过程

get的过程比较简单。

map.get(key). 首先计算key的hash。

根据hash&(length-1)定位到Node数组中的一个下标。如果该下标的元素(也就是链表/红黑树的第一个元素)中 key的hash的key本身 都和传入的key相同,则证明找到了元素,直接返回即可。

如果第一个元素不是要找的,如果第一个元素的类型是TreeNode,则按照红黑树的查找方法查找元素,如果不是则证明是链表,按照next指针找下去,直到找到或者到达队尾。

HashMap的扩容

先说这里的两个概念: size, length.

size:是map.size() 方法返回的值,表示的是map中有多少个key-value键值对儿

length: 这里是指Node数组的长度,比如默认长度是16.

如下面的代码:

        Map map = new HashMap<>();        map.put(1,"a");        map.put(2,"b");        map.put(3,"c");    

没有在构造函数中指定HashMap的大小,则数组的长度length取默认的16,put了3个元素,则size为3.

Q: 何时需要扩容呢?

A: 在put方法中,每次完成了put操作,都判断一下++size是否大于threshold,如果大于则进行扩容: 调用resize()方法。

Q: 那么threshold又是如何得到的呢?

A: 简单来讲threshold = length * loadfactor(默认为0.75)。 也就是说默认情况下,map中的键值对的个数(size)大于Node数组长度(length)的75%时,就需要扩容了。

Q: 扩容时具体做什么呢?

A: 首先计算出新的数组长度和新的threshold(阈值). 简单来讲,新的length/capacity 是原来的2倍(位运算左移一位),新的threshold为原来的2倍。 还有一些细节此处不再赘述。创建新的Node数组,将原来数组中的元素重新映射到新的数组中。

关于transient关键字

transient关键字的作用:用transient关键字修饰的字段不会被序列化

查看下面的例子:

public class TransientExample implements Serializable{    private String firstName;    private transient String middleName;    private String lastName;    public TransientExample(String firstName,String middleName,String lastName) {        this.firstName = firstName;        this.middleName = middleName;        this.lastName = lastName;    }    @Override    public String toString() {        StringBuilder sb = new StringBuilder();        sb.append("firstName:").append(firstName).append("\n")                .append("middleName:").append(middleName).append("\n")                .append("lastName:").append(lastName);        return sb.toString();    }    public static void main(String[] args) throws Exception {        TransientExample e = new TransientExample("Adeline","test","Pan");        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("/path/testObj"));        oos.writeObject(e);        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/path/testObj"));        TransientExample e1 = (TransientExample) ois.readObject();        System.out.println("e:"+e.toString());        System.out.println("e1:"+e1.toString());    }}View Code

输出结果:

e:firstName:AdelinemiddleName:testlastName:Pane1:firstName:AdelinemiddleName:nulllastName:Pan

被transient关键字修饰的middleName字段没有被序列化,反序列化回来的值是null

Q:HashMap类是实现了Serializable接口的,那么为何其中的table, entrySet变量都标为transient呢?

A:我们知道,table数组中元素分布的下标位置是根据元素中key的hash进行散列运算得到的,而hash运算是native的,不同平台得到的结果可能是不相同的。举一个简单的例子,假设我们在目前的平台有键值对 key1-value1,计算出key1的hash为1, 计算后存在table数组中下标为1的地方,假设table被序列化了,并传输到了另外的平台,并反序列化为了原来的HashMap,key1-value1仍然存在下标1的位置,当在这个平台运行get("key1")的时候,可能计算出key1的hash为2,就有可能到下标为2的地方去找该元素,这样就出错了。

Q:那么HashMap是如何实现的序列化呢?

A:HashMap是通过实现如下方法直接将元素数量(size), key, value等写入到了ObjectOutputStream中,实现的定制化的序列化和反序列化。在Serializable接口中有关于这种做法的说明。

private void writeObject(java.io.ObjectOutputStream out)

throws IOException

private void readObject(java.io.ObjectInputStream in)

throws IOException, ClassNotFoundException;

 

来源地址:https://blog.csdn.net/java1527/article/details/126850576

--结束END--

本文标题: Java 中HashMap 详解

本文链接: https://lsjlt.com/news/391813.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Java 中HashMap 详解
    本篇重点: HashMap的存储结构 HashMap的put和get操作过程 HashMap的扩容 关于transient关键字 HashMap的存储结构 HashMap 总体是数组+链表的存储结构, 从JDK1.8开始,当数组的长度大...
    99+
    2023-09-03
    哈希算法 散列表 java
  • Java之HashMap案例详解
    概述 这篇文章,我们打算探索一下Java集合(Collections)框架中Map接口中HashMap的实现。Map虽然是Collctions框架的一部分,但是Map并没有实现Col...
    99+
    2024-04-02
  • Java详细分析讲解HashMap
    目录1.HashMap数据结构2.HashMap特点3.HashMap中put方法流程java集合容器类分为Collection和Map两大类,Collection类的子接口有Set...
    99+
    2024-04-02
  • HashMap详解
    一、HashMap集合简介 HashMap 基于哈希表的 Map 接口实现,是以 key-value 存储形式存在,即主要用来存放键值对。HashMap 的实现不是同步的,这意味着它不是线程安全的。...
    99+
    2023-09-22
    数据结构 java 链表
  • Java中HashMap集合的常用方法详解
    目录public Object clone()总结public Object clone() 返回hashMap集合的副本   其余的方法都是实现Map...
    99+
    2024-04-02
  • Java 详解Map集合之HashMap和TreeMap
    目录HashMap创建HashMap添加元素访问元素删除元素TreeMap创建TreeMap添加元素访问元素删除元素HashMap、TreeMap区别 Map接口储存一组成对的键-值...
    99+
    2024-04-02
  • Java集合HashMap的知识点详解
    这篇文章主要讲解了“Java集合HashMap的知识点详解”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Java集合HashMap的知识点详解”吧!一、什么是哈希表在讨论哈希表之前,我们先大...
    99+
    2023-06-02
  • 深入理解Java中的HashMap
    目录一、HashMap的结构图示二、HashMap的成员变量以及含义2.1、hash方法说明2.2、tableSizeFor方法说明三、HashMap的构造方法四、HashMap元素...
    99+
    2024-04-02
  • Java模拟实现HashMap算法流程详解
    目录1、前言2、成员变量的设定3、构造方法4、hash方法以及阈值判断方法5、put方法6、resize 方法7、get 方法1、前言 上期讲解了 HashMap 和 HashSet...
    99+
    2023-02-08
    Java HashMap Java模拟实现HashMap
  • Java源码解析之HashMap的put、resize方法详解
    目录一、HashMap 简介二、源码分析2.1 继承和实现2.2 属性2.3 节点类型Node内部类2.4 红黑树的节点三、构造方法3.1 构造器13.2 构造器23.3 构造器33...
    99+
    2024-04-02
  • java中对HashMap的put过程解读
    目录HashMap解析put的过程默认值为啥是16自动扩容put的过程为啥要转化成红黑树?总结HashMap解析put的过程 首先,用代码运行下,来体会下: 代码实现: @Test ...
    99+
    2023-03-22
    java HashMap HashMap的put过程 java HashMap put
  • Java中HashMap是什么
    这篇文章主要介绍Java中HashMap是什么,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、HashMap的结构图示本文主要说的是jdk1.8版本中的实现。而1.8中HashMap是数组+链表+红黑树实现的,大概...
    99+
    2023-06-15
  • rust的vector和hashmap详解
    目录动态数组Vector创建动态数组向数组末尾追加元素重提内存安全所有权系统引用规则hashmap创建hashmap新增键值对根据键查询值根据键删除hashmap的键值对动态数组Ve...
    99+
    2023-03-19
    rust的vector和hashmap rust的vector rust hashmap
  • Java详解HashMap实现原理和源码分析
    目录学习要点:1、什么是HashMap?2、HashMap的特性3、HashMap的数据结构4、HashMap初始化操作4.1、成员变量4.2、 构造方法5、Jdk8中HashMap...
    99+
    2024-04-02
  • Java实现HashMap排序方法的示例详解
    目录简介排序已有数据按key排序按value排序按插入顺序存放HashMap不按插入顺序存放LinkedHashMap会按照插入顺序存放简介 本文用示例介绍HashMap排序的方法。...
    99+
    2024-04-02
  • java Map接口子类HashMap遍历与LinkedHashMap详解
    目录一、概述二、Map常用子类三、Map接口中的常用方法四、Map集合遍历键找值方式五、Entry键值对对象六、Map集合遍历键值对方式七、HashMap存储自定义类型键值八、Lin...
    99+
    2024-04-02
  • Java 中的HashMap详解和使用示例_动力节点Java学院整理
    第1部分 HashMap介绍HashMap简介HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializab...
    99+
    2023-05-31
    java hashmap ava
  • Java中HashMap如何解决哈希冲突
    目录1. Hash算法和Hash表2. Hash冲突3. 解决Hash冲突的方法有四种4.HashMap在JDK1.8版本的优化1. Hash算法和Hash表 了解Hash冲突首先了...
    99+
    2024-04-02
  • Java中HashMap怎么解决哈希冲突
    这篇文章主要介绍“Java中HashMap怎么解决哈希冲突”,在日常操作中,相信很多人在Java中HashMap怎么解决哈希冲突问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java中HashMap怎么解决哈...
    99+
    2023-06-30
  • Java中HashMap中的一个坑
    目录前言问题展示原因分析解决方案LinkedHashMap 的魔力总结前言 最近公司的系统要增加一个新的列表展示功能,功能本身难度并不大,但遇到了一个很“奇怪&rdquo...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作