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

news2025/4/7 16:00:33

在这里插入图片描述

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 的效率,引入了红黑树。


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

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

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

相关文章

Jupyter Notbook+cpolar内网穿透实现公共互联网访问使用数据分析工作

​​ 文章目录 1.前言2.Jupyter Notebook的安装2.1 Jupyter Notebook下载安装2.2 Jupyter Notebook的配置2.3 Cpolar下载安装 3.Cpolar端口设置3.1 Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 ​​​​ 1.前言 在数据分析工作中&#xff0c;使用最多的无疑就是各…

水稻潜在产量估算解决方案

1.背景与技术路线 统计资料表明&#xff0c;尽管我国粮食单产已由 50 年代初期的 1.2t/ha 增加到如今的 5.2t/h&#xff0c;粮食产量增加了 4 倍&#xff0c;但我国人口的增长速度与气候变化导致的农业生产的不确定性&#xff0c; 在水稻收获指数保持稳定的情况下&#xff0c;…

软件测试方法分类-按测试对象划分

接上一篇,下来我们再细讲,第四个维度的分类, 软件测试方法分类-按测试对象划分 本章节重点介绍非功能测试的相关知识,因为功能测试的基本在之前的分类都是有涉及的。 一、非功能测试 1,性能测试(Performance Testing) 检查系统是否满足需求规格说明书中规定的性能。 …

Mysql事务transaction简介

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

从0到1入门C++编程——03 内存分区、引用、函数高级应用

文章目录 一、内存分区二、引用三、函数的高级应用1.默认参数2.占位参数3.函数重载 一、内存分区 C程序在执行时&#xff0c;会将内存大致分为4个区&#xff0c;分别是代码区、全局区、栈区和堆区。 代码区用来存放函数体和二进制代码&#xff0c;由操作系统进行管理。 全局区…

力扣刷题-二叉树-二叉搜索树中的搜索

700 二叉搜索树中的搜索 给定二叉搜索树&#xff08;BST&#xff09;的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在&#xff0c;则返回 NULL。 例如&#xff0c; 在上述示例中&#xff0c;如果要找的值是 5&#x…

迟来的扫雷游戏

今天我们讲如何用C语言编写出一个简单扫雷&#xff0c;扫雷也算是一个比较原始的游戏了吧&#xff0c;那么我们今天就来实现他&#xff01; 首先我们要来缕一缕游戏框架 我们在代码中我们肯定会写许多函数来实现扫雷&#xff0c;那么我们为了简便看出游戏的运行逻辑&#xff0…

字节跳动 Spark 支持万卡模型推理实践

摘要&#xff1a;本文整理自字节跳动基础架构工程师刘畅和字节跳动机器学习系统工程师张永强在本次 CommunityOverCode Asia 2023 中的《字节跳动 Spark 支持万卡模型推理实践》主题演讲。 背景介绍 在云原生化的发展过程中 Kubernetes 由于其强大的生态构建能力和影响力&…

Consule安装与SpringBoot集成

Consule Consul 是由 HashiCorp 开发的一款软件工具&#xff0c;提供了一组功能&#xff0c;用于服务发现、配置管理和网络基础设施自动化。它旨在帮助组织管理现代分布式和微服务架构系统的复杂性。以下是Consul的一些关键方面和功能&#xff1a; 服务发现&#xff1a;Consul…

大数据 - Doris系列《二》- Doris安装(亲测成功版)

目录 &#x1f436;2.1 安装前准备 &#x1f959;1.设置系统最大文件打开句柄数 >启动一个程序的时候&#xff0c;打开文件的数量就是句柄数 &#x1f959;3.时钟同步 &#x1f959;4.关闭交换分区&#xff08;swap&#xff09; &#x1f436;2.2 安装FE &#x1f436…

seo分享:慎重使用蜘蛛池

其实要提高搜索引擎蜘蛛的来访次数&#xff0c;唯一的方法还是要通过网站本身的内容更新。频繁更新有质量的内容&#xff0c;才能够提高蜘蛛的来访次数。如果本身内容更新不多&#xff0c;外部引流的蜘蛛过多&#xff0c;最终发现没什么内容索引&#xff0c;蜘蛛来访的次数也会…

express+mongoDB开发入门教程之mongoose使用讲解

系列文章 node.js express框架开发入门教程 expressmongoDB开发入门教程之mongoDB安装expressmongoDB开发入门教程之mongoose使用讲解 文章目录 系列文章前言一、Mongoose是什么&#xff1f;二、Mongoose安装三、Mongoose在express项目中使用步骤一、连接mongoDB数据库步骤二、…

网络路由跟踪工具

随着企业网络需求的增长&#xff0c;组织发现监控和管理其网络基础设施变得越来越困难。网络管理员正在转向其他工具和资源&#xff0c;这些工具和资源可以使他们的工作更轻松一些&#xff0c;尤其是在故障排除方面。 目前&#xff0c;网络管理员主要使用简单、免费提供的实用…

C#中使用 async await TaskCompletionSource<T>实现异步逻辑同步写

Task、async 和 await 是 C# 中用于处理异步编程的关键概念。它们一起构成了异步编程的基础。 Task Task 是表示异步操作的抽象&#xff0c;它属于 System.Threading.Tasks 命名空间。Task 可以表示已经完成的任务、正在运行的任务或者尚未开始的任务。通过 Task&#xff0c;…

MySQL自定义时间间隔抽稀

MySQL自定义时间间隔抽稀 表设计数据如下按分钟抽稀按小时抽稀按天抽稀 表设计 create table monitor (tid varchar(255) not null,save_date datetime not null,tlevel decimal(10, 2) null,primary key (tid, save_date) );数据如下 按分钟抽稀 SELECT t2…

FLatten Transformer:聚焦式线性注意力模块

线性注意力将Softmax解耦为两个独立的函数&#xff0c;从而能够将注意力的计算顺序从(querykey)value调整为query(keyvalue)&#xff0c;使得总体的计算复杂度降低为线性。然而&#xff0c;目前的线性注意力方法要么性能明显不如Softmax注意力&#xff0c;并且可能涉及映射函数…

webapp下没有蓝点解决

解决方法&#xff1a; File->Project Structure 现在就是一个JavaWeb项目了。

华为云CES监控与飞书通知

华为云负载均衡连接数监控与飞书通知 在云服务的日常运维中&#xff0c;持续监控资源状态是保障系统稳定性的关键步骤之一。本文通过一个实际案例展示了如何使用华为云的Go SDK获取负载均衡器的连接数&#xff0c;并通过飞书Webhook发送通知到团队群组&#xff0c;以便运维人员…

2024 Nomachine 最简安装与使用指南

一、前言 二、Nomachine安装包的下载 1、Windows系统下Nomachine包的下载 2、Linux系统Nomachine的下载 &#xff08;1&#xff09;下载Nomachine安装包 &#xff08;2&#xff09;解压安装包 &#xff08;3&#xff09;添加权限 &#xff08;4&#xff09;下载安装包 三、在Wi…

基于GEC6818的点餐系统

本次项目开发环境&#xff1a;gec6818&#xff0c;QT5.14.2&#xff0c;SecureCRT。 所使用的相关技术&#xff1a;c/s架构&#xff0c;STL库&#xff0c;C封装&#xff0c;标准化代码编写 实现的功能&#xff1a;用户登录页面&#xff0c;食品分区在不同页面&#xff0c;用户…