【Java集合类篇】HashMap的数据结构是怎样的?

在这里插入图片描述

HashMap的数据结构是怎样的?

  • ✔️HashMap的数据结构
    • ✔️ 数组
    • ✔️ 链表


✔️HashMap的数据结构


在Java中,保存数据有两种比较简单的数据结构: 数组和链表(或红黑树)。


HashMapJava 中常用的数据结构,它实现了 Map 接口。HashMap通过键值对的形式存储数据,其中键是唯一的,而值可以是任何对象。HashMap底层使用数组和链表(或红黑树)来实现。


常用的哈希函数的冲突解决办法中有一种方法叫做链地址法,其实就是将数组和链表组合在一起,发挥了两者的优势,我们可以将其理解为链表的数组。在JDK 1.8之前,HashMap就是通过这种结构来存储数据的。


在这里插入图片描述


我们可以从上图看到,左边很明显是个数组,数组的每个成员是一个链表。该数据结构所容纳的所有元素均包含一个指针,用于元素间的链接。我们根据元素的自身特征把元素分配到不同的链表中去,反过来我们也正是通过这些特征找到正确的链表,再从链表中找出正确的元素。其中,根据元素特征计算元素数组下标的方法就是哈希算法,即本文的主角 hash() 函数 (当然,还包括indexOf()函数)。


✔️ 数组


数组:HashMap使用一个数组来存储键值对。数组的每个元素都是一个桶(bucket),桶中存储着一个链表(LinkedList)或红黑树(TreeMap)。桶的数量可以根据需要动态调整。数组的索引方式采用哈希算法,通过将键的哈希值对数组长度取模来得到对应的桶。


数组的特点是:寻址容易,插入和删除困难。


看一个如何使用数组实现HashMap的代码片段:


public class MyHashMap<K, V> { 
	// 默认初始容量  
    private static final int DEFAULT_INITIAL_CAPACITY = 16;  
     // 默认加载因子  
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;  
     // 存储键值对的数组 
    private Entry<K, V>[] table;  
    // 当前容量 
    private int capacity; 
    // 实际存储的键值对数量   
    private int size;  
    
    public MyHashMap() {  
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);  
    }  
  
    public MyHashMap(int capacity) {  
        this(capacity, DEFAULT_LOAD_FACTOR);  
    }  
  
    public MyHashMap(int capacity, float loadFactor) {  
        this.capacity = capacity;  
        table = new Entry[capacity];  
        size = 0;  
    }  
  
    public V put(K key, V value) {  
        int hash = hash(key);  
        int index = indexFor(hash, table.length);  
        Entry<K, V> oldValue = table[index];  
        if (oldValue == null) {  
            table[index] = new Entry<>(key, value, null);  
            size++;  
            if (size > capacity * loadFactor) {  
                rehash();  
            }  
        } else {  
            Entry<K, V> newEntry = new Entry<>(key, value, oldValue);  
            table[index] = newEntry;  
        }  
        return oldValue == null ? null : oldValue.value;  
    }  
  
    public V get(K key) {  
        int hash = hash(key);  
        int index = indexFor(hash, table.length);  
        Entry<K, V> entry = table[index];  
        if (entry != null && Objects.equals(entry.key, key)) {  
            return entry.value;  
        } else {  
            return null;  
        }  
    }  
  
    public int size() {  
        return size;  
    }  
  
    private int hash(Object key) {  
        return Objects.hashCode(key);  
    }  
  
    private int indexFor(int hash, int length) {  
        return hash % length;  
    }  
  
    private void rehash() {  
        Entry<K, V>[] oldTable = table;  
        int oldCapacity = oldTable.length;  
        int newCapacity = oldCapacity * 2;  
        Entry<K, V>[] newTable = new Entry[newCapacity];  
        for (Entry<K, V> oldEntry : oldTable) {  
            while (oldEntry != null) {  
                Entry<K, V> next = oldEntry.next;  
                int hash = hash(oldEntry.key);  
                int index = indexFor(hash, newCapacity);  
                oldEntry.next = newTable[index];  
                newTable[index] = oldEntry;  
                oldEntry = next;  
            }  
        }  
        table = newTable;  
        capacity = newCapacity;  
    }  
}

✔️ 链表


链表:当多个键的哈希值映射到同一个桶时,它们会形成一个链表。链表中的每个节点包含一个键值对和指向下一个节点的指针。链表的作用是在插入、删除和查找操作时解决哈希冲突。


链表的特点是: 寻址困难,插入和删除容易


看一个如何使用链表实现HashMap的代码片段,是一个简单的HashMap实现,使用链表来处理哈希冲突:


public class MyHashMap<K, V> {  
    private static class Entry<K, V> {  
        K key;  
        V value;  
        Entry<K, V> next;  
  
        Entry(K key, V value, Entry<K, V> next) {  
            this.key = key;  
            this.value = value;  
            this.next = next;  
        }  
    }  
  
    private Entry<K, V>[] table;  
    private int capacity;  
    private int size;  
    private float loadFactor;  
  
    public MyHashMap(int capacity, float loadFactor) {  
        if (capacity < 0) throw new IllegalArgumentException("Capacity must be non-negative");  
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Load factor must be positive");  
        this.capacity = capacity;  
        this.loadFactor = loadFactor;  
        table = new Entry[capacity];  
        size = 0;  
    }  
  
    public V put(K key, V value) {  
        if (key == null) return null; // HashMaps don't allow null keys  
  
        // If size exceeds load factor * capacity, rehash  
        if (size >= capacity * loadFactor) {  
            rehash();  
        }  
  
        int hash = hash(key);  
        int index = indexFor(hash, capacity);  
  
        Entry<K, V> entry = table[index];  
        if (entry == null) {  
            // No collision, create new entry  
            table[index] = new Entry<>(key, value, null);  
            size++;  
        } else {  
            // Collision occurred, handle it using chaining  
            while (entry != null && !entry.key.equals(key)) {  
                if (entry.next == null) {  
                    // End of chain, insert new entry  
                    entry.next = new Entry<>(key, value, null);  
                    size++;  
                    break;  
                }  
                entry = entry.next;  
            }  
  
            // If key already exists, update value  
            if (entry != null && entry.key.equals(key)) {  
                V oldValue = entry.value;  
                entry.value = value;  
                return oldValue;  
            }  
        }  
  
        return null; // If key was new or not found  
    }  
  
    public V get(K key) {  
        if (key == null) return null; // HashMaps don't allow null keys  
  
        int hash = hash(key);  
        int index = indexFor(hash, capacity);  
  
        Entry<K, V> entry = table[index];  
        while (entry != null && !entry.key.equals(key)) {  
            entry = entry.next;  
        }  
  
        return entry == null ? null : entry.value;  
    }  
  
    private void rehash() {  
        capacity *= 2;  
        Entry<K, V>[] oldTable = table;  
        table = new Entry[capacity];  
        size = 0;  
  
        for (Entry<K, V> entry : oldTable) {  
            while (entry != null) {  
                Entry<K, V> next = entry.next;  
                int hash = hash(entry.key);  
                int index = indexFor(hash, capacity);  
                entry.next = table[index];  
                table[index] = entry;  
                size++;  
                entry = next;  
            }  
        }  
    }  
  
    private int hash(K key) {  
        return Math.abs(key.hashCode()) % capacity;  
    }  
  
    private int indexFor(int hash, int length) {  
        return hash % length;  
    }  
  
    public static void main(String[] args) {  
        MyHashMap<String, Integer> map = new MyHashMap<>(16, 0.75f);  
        map.put("one", 1);  
        map.put("two", 2);  
        map.put("three", 3);  
  
        System.out.println(map.get("one"));    // Should print 1  
        System.out.println(map.get("two"));    // Should print 2  
        System.out.println(map.get("three"));  //Should print 3

在JDK 1.8中为了解决因hash冲突导致某个链表长度过长,影响 put get 的效率,引入了红黑树。


关于红黑树,下一篇会作为单独的博文进行更新。

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

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

相关文章

ASP.NET Core高级之认证与授权(一)--JWT入门-颁发、验证令牌

阅读本文你的收获 了解认证和授权的作用了解在ASP.NET Core中实现身份认证的技术都有哪些学习基于JWT认证并学会颁发和验证JWT令牌 一、重要的前置概念 在一个系统中&#xff0c;不是所有的功能和资源都能够被自由地访问&#xff0c;比如你存在银行系统里面的资金&#xff0c…

椭球面系列---大地坐标和笛卡尔坐标的相互转换

目录 大地坐标笛卡尔坐标大地坐标 ( λ , φ , h ) (\lambda,\varphi,h) (λ,φ,h)转换为笛卡尔坐标 ( x , y , z ) (x,y,z) (x,y,z)笛卡尔坐标 ( x , y , z ) (x,y,z) (x,y,z)转换为大地坐标 ( λ , φ , h ) (\lambda,\varphi,h) (λ,φ,h) 椭球体下&#xff0c;尤其是地球的…

深度学习课程实验二深层神经网络搭建及优化

一、 实验目的 1、学会训练和搭建深层神经网络&#xff1b; 2、掌握超参数调试正则化及优化。 二、 实验步骤 初始化 1、导入所需要的库 2、搭建神经网络模型 3、零初始化 4、随机初始化 5、He初始化 6、总结三种不同类型的初始化 正则化 1、导入所需要的库 2、使用非正则化…

android 通过反射获取U盘路径地址

2015-01-20 21:37:05.420 26674-26674/ E/MainActivity: ---getUsbPath() length2 2015-01-20 21:37:05.420 26674-26674/E/MainActivity: ---getUsbPath()[/storage/emulated/0, /storage/D65A-07AE]

SpringBoot全局Controller返回值格式统一处理

一、Controller返回值格式统一 1、WebResult类 在 Controller对外提供服务的时候&#xff0c;我们都需要统一返回值格式。一般定义一个 WebResult类。 统一返回值&#xff08;WebResult类&#xff09;格式如下&#xff1a; {"success": true,"code": 2…

Mysql事务transaction简介

文章目录 什么是事务针对Mysql隔离级别读未提交读提交可重复读串行化 mysql中的数据结构索引数据结构mysql中的锁种类**共享锁和独占锁**表锁、行锁(记录锁、间隙锁、临键锁) spring中的事务事务特性 什么是事务 事务是一个不可分割的数据库操作序列&#xff0c;也是数据库并发…

element-ui Tree 树形控件 过滤保留子级并获取过滤后的数据 多选改单选

本示例基于vue2 element-ui element-ui 的官网demo是只保留到过滤值一级的&#xff0c;并不会保留其子级 目标 1、Tree 树形控件 保留过滤值的子级 2、在第一次过滤数据的基础上进行第二次过滤 3、Tree 树形控件 多选改为单选&#xff0c;且只有最末端子级可以选择 不足…

【Spring】AOP的AspectJ开发

AOP基础不了解可以阅读&#xff1a;【Spring】AOP原来如此-CSDN博客 AspectJ是一个居于JAVA开发的AOP框架 基于XML的声明式AspectJ 基于XML的声明式AspectJ是通过XML文件来定义切面&#xff0c;切入点及通知&#xff0c;所有的切面、切入点和通知必须定义在内&#xff0c; 元…

Android Jetpack学习系列——Navigation

写在前面 Google在2018年就推出了Jetpack组件库&#xff0c;但是直到今天我才给重视起来&#xff0c;这真的不得不说是一件让人遗憾的事。过去几年的空闲时间里&#xff0c;我一直在尝试做一套自己的组件库&#xff0c;帮助自己快速开发&#xff0c;虽然也听说过Jetpack&#…

第一课:Transformer

第一课&#xff1a;Transformer 文章目录 第一课&#xff1a;Transformer1、学习总结&#xff1a;什么是语言模型&#xff1f;大语言模型&#xff08;LLM&#xff09;技术演变史注意力机制Transformer结构课程ppt及代码地址 2、学习心得&#xff1a;3、经验分享&#xff1a;4、…

【DevOps-02】Code编码阶段工具

一、简要说明 在code阶段,我们需要将不同版本的代码存储到一个仓库中,常见的版本控制工具就是SVN或者Git,这里我们采用Git作为版本控制工具,GitLab作为远程仓库。 Git安装安装GitLab配置GitLab登录账户二、Git安装 Git官网 Githttps://git-scm.com/

移动通信原理与关键技术学习(2)

1.多径信道滤波器表示&#xff0c;多径信道可以认为是线性时变滤波器&#xff0c;接收信号为发送信号与信道冲激响应的卷积。 2.调制就是对信号源的信息进行处理加到载波上&#xff0c;使其变为适合于信道传输的形式的过程&#xff0c;就是使载波随信号而改变的技术。 3.进行调…

VUE 若依框架,当页面设置了keepAlive=true,v-if和v-hasPermi作用在统一个按钮上时v-hasPermi失效,出现按钮显示异常问题

当前列表页设置了缓存keepAlivetrue&#xff0c;同时&#xff0c;在同一个按钮上使用v-if判断数据状态、用v-hasPermi判断按钮权限 当v-if的数据状态改变&#xff0c;由 1 变成 2 的时候&#xff0c;后面的v-hasPermi判断失效 原因&#xff1a; 是因为一开始页面初始化时&#…

HTML5+CSS3⑥——CSS三大特性、表格、列表

CSS特性 继承性 层叠性 优先级 叠加计算规则 表格 表格结构标签 合并单元格 列表 无序列表 有序列表 定义列表

显著提升VMware虚拟机运行速度的技巧

最主要是要把CPU核心减少到2&#xff0c;以前设置为4非常卡。因为我的电脑一个就4个CPU。

听GPT 讲Rust源代码--compiler(11)

File: rust/compiler/rustc_mir_transform/src/simplify.rs 在Rust源代码中&#xff0c;rust/compiler/rustc_mir_transform/src/simplify.rs文件是Rust编译器中一系列进行MIR&#xff08;中间表示&#xff09;简化的转换的实现。MIR是Rust编译器中用于进行优化和代码生成的中间…

Python遍历读取 A 文件夹中的 A1、A2、A3、A4、A5 中的各子文件夹中的图片,并对每张图片处理后保存到指定路径

目录 一、具体步骤二、文件夹目录结构样例三、代码四、实例遍历处理后结果五、总结 一、具体步骤 首先&#xff0c;指定 A 文件夹的路径和重命名后的文件夹路径。 然后&#xff0c;遍历 A 文件夹中的各子文件夹。 在每个子文件夹中&#xff0c;遍历所有文件。 读取每个文件&am…

电路分析竟然这么简单?还可以用软件仿真~

同学们大家好&#xff0c;今天我们继续学习杨欣的《电子设计从零开始》&#xff0c;这本书从基本原理出发&#xff0c;知识点遍及无线电通讯、仪器设计、三极管电路、集成电路、传感器、数字电路基础、单片机及应用实例&#xff0c;可以说是全面系统地介绍了电子设计所需的知识…

【MongoDB】关于MongoDB更新文档update的操作,十分详细,建议收藏!!!

&#x1f601; 作者简介&#xff1a;一名大四的学生&#xff0c;致力学习前端开发技术 ⭐️个人主页&#xff1a;夜宵饽饽的主页 ❔ 系列专栏&#xff1a;MongoDB数据库学习 &#x1f450;学习格言&#xff1a;成功不是终点&#xff0c;失败也并非末日&#xff0c;最重要的是继…

UDP单播

CMakeLists.txt文件中添加如下行&#xff1a; link_libraries(ws2_32) 1.发送端 #include <iostream> #include <winsock2.h> #include <cstdio>#pragma comment(lib, "Ws2_32.lib") // Link with ws2_32.libint main() {1.Initialize winsock…