Java,这是一个模仿HashMap的put,get功能的自定义MyHashMap

news2024/12/28 19:00:51

Java 手写HashMap源码

一,手写源码

这是一个模仿HashMap的put,get功能的自定义的MyHashMap

package cn.wxs.demo;

import java.io.Serializable;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;

class MyHashMap<K, V> extends AbstractMap<K, V>
        implements Map<K, V>, Cloneable, Serializable {
    //数组默认长度
    private static final int DEFAULT_CAPACITY = 16;
    //负载因子
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;

    //在jdk8中 链表转换红黑树阈值8 这里参考jdk11,提前进入红黑树
    private static final int TREEIFY_THRESHOLD = 6;

    // 在jdk8中 红黑树退回链表是6 这里参考jdk11,提高转换链表的阈值
    private static final int UNTREEIFY_THRESHOLD = 8;

    //数组长度低于64,出现转红黑树的情况,进行扩容,原先的二倍
    private static final int MIN_TREEIFY_CAPACITY = 64;


    private static Node[] table; // 存储桶数组
    private static transient int size; // 键值对数量

    //用于记录集合被修改的次数。在Java集合类中,如果在迭代器遍历集合时,
    // 集合被修改了,就会抛出ConcurrentModificationException异常,
    // 这是因为在遍历时,迭代器会检查modCount的值是否与集合的修改次数相等
    //,如果不相等就会抛出异常,以此保证遍历的安全性。
    private static transient int modCount;

    private transient int threshold; // 扩容阈值
    private final float loadFactor; // 负载因子



    private static class Node<K, V> {
        final int hash; // 哈希值
        final K key; // 键
        V value; // 值
        Node<K, V> next; // 下一个节点

        Node(int hash, K key, V value, Node<K, V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                return Objects.equals(key, e.getKey()) &&
                        Objects.equals(value, e.getValue());
            }
            return false;
        }
    }


    public MyHashMap() {
        this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

    public MyHashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity <= 0)
            throw new IllegalArgumentException("Invalid initial capacity");
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Invalid load factor");

        table = new Node[initialCapacity];
        size = 0;
        this.loadFactor = loadFactor;
        this.threshold = (int) (initialCapacity * loadFactor);
    }

    public V get(Object key) {
        Node<K, V> node = getNode((K) key);
        return (node != null) ? node.value : null;
    }

    public V put(K key, V value) {
        if (key == null)
            putForNullKey(value);
        else
            putNonNullKey(key, value);
        return value;
    }

    private void putNonNullKey(K key, V value) {
        int hash = hash(key);
        int index = indexFor(hash, table.length);

        // 遍历链表,查找是否已存在相同的键
        for (Node node = table[index]; node != null; node = node.next) {
            if (node.hash == hash && Objects.equals(node.key, key)) {
                // 如果找到相同的键,更新对应的值
                node.value = value;
                return;
            }
        }

        // 如果没有找到相同的键,将新的键值对添加到链表头部
        addNode(hash, key, value, index);
    }

    private void putForNullKey(V value) {
        // 对于 null 键,存储在数组的第一个位置
        for (Node node = table[0]; node != null; node = node.next) {
            if (node.key == null) {
                // 如果找到 null 键,更新对应的值
                node.value = value;
                return;
            }
        }

        // 如果没有找到 null 键,将新的键值对添加到链表头部
        addNode(0, null, value, 0);
    }

    private void addNode(int hash, K key, V value, int bucketIndex) {
        // 检查是否需要扩容
        if (size >= threshold)
            resize(2 * table.length);

        // 将新的节点添加到链表头部
        Node newNode = new Node<>(hash, key, value, table[bucketIndex]);
        table[bucketIndex] = newNode;
        size++;
        modCount++;
    }

    private Node<K, V> getNode(K key) {
        if (key == null)
            return getNodeForNullKey();

        int hash = hash(key);
        int index = indexFor(hash, table.length);

        // 遍历链表,查找指定的键
        for (Node node = table[index]; node != null; node = node.next) {
            if (node.hash == hash && Objects.equals(node.key, key))
                return node;
        }

        return null;
    }

    private Node<K, V> getNodeForNullKey() {
        // 对于 null 键,存储在数组的第一个位置
        for (Node<K, V> node = table[0]; node != null; node = node.next) {
            if (node.key == null)
                return node;
        }
        return null;
    }

    private int hash(K key) {
        // 计算键的哈希值
        return (key == null) ? 0 : key.hashCode();
    }

    private int indexFor(int hash, int length) {
        // 根据哈希值和数组长度计算索引位置
        return hash & (length - 1);
    }

    private void resize(int newCapacity) {
        Node<K, V>[] oldTable = table;
        int oldCapacity = oldTable.length;

        // 检查是否达到容量上限
        if (oldCapacity >= 1 << 30)
            threshold = Integer.MAX_VALUE;
        else
            threshold = (int) (newCapacity * loadFactor);

        // 创建新的数组,将旧的键值对重新分配到新的数组中
        Node<K,V>[] newTable = new Node[newCapacity];
        transfer(oldTable, newTable);
        table = newTable;
    }

    private void transfer(Node<K, V>[] src, Node<K, V>[] dest) {
        // 遍历旧的数组,将键值对重新分配到新的数组中
        for (Node<K, V> node : src) {
            while (node != null) {
                Node<K, V> next = node.next;
                int index = indexFor(node.hash, dest.length);
                node.next = dest[index];
                dest[index] = node;
                node = next;
            }
        }
    }


    @Override
    public void forEach(BiConsumer<? super K, ? super V> action) {
        Node[] tab;
        if (action == null)
            throw new NullPointerException();
        if (size > 0 && (tab = table) != null) {
            int mc = modCount;
            for (Node kvNode : tab) {
                for (Node<K, V> e = kvNode; e != null; e = e.next)
                    action.accept(e.key, e.value);
            }
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }


    @Override
    public MyHashMap<K, V> clone() {
        try {
            MyHashMap clone = (MyHashMap) super.clone();
            // TODO: copy mutable state here, so the clone can't change the internals of the original
            return clone;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }

    /**
     * 获取hashmap的存储值数量
     *
     * @return 存储数量
     */
    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size() == 0;
    }


    @Override
    public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
        super.replaceAll(function);
    }

    @Override
    public V putIfAbsent(K key, V value) {
        return super.putIfAbsent(key, value);
    }

    @Override
    public boolean remove(Object key, Object value) {
        return super.remove(key, value);
    }

    @Override
    public boolean replace(K key, V oldValue, V newValue) {
        return super.replace(key, oldValue, newValue);
    }

    @Override
    public V replace(K key, V value) {
        return super.replace(key, value);
    }

    @Override
    public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
        return super.computeIfAbsent(key, mappingFunction);
    }

    @Override
    public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        return super.computeIfPresent(key, remappingFunction);
    }

    @Override
    public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        return super.compute(key, remappingFunction);
    }

    @Override
    public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
        return super.merge(key, value, remappingFunction);
    }


    @Override
    public Set<Entry<K, V>> entrySet() {
        return null;
    }

    @Override
    public V getOrDefault(Object key, V defaultValue) {
        return super.getOrDefault(key, defaultValue);
    }



    public static void main(String[] args) {
        Map<String,String> para = new HashMap<>();
        para.remove("");
        MyHashMap<String, String> param = new MyHashMap<>();
        param.put("你好", "你好");
        param.put("你好1", "你好1");
        param.put("你好2", "你好2");
        param.put("你好3", "你好3");
        param.put("你好4", "你好4");
        param.put("你好5", "你好4");
        param.put("你好6", "你好4");
        param.put("你好7", "你好4");
        param.put("你好8", "你好4");
        param.put("你好9", "你好4");
        param.put("你好10", "你好4");
        param.put("你好11", "你好4");
        param.put("你好12", "你好4");
        param.put("你好13", "你好4");
        param.put("你好14", "你好4");
        param.put("你好15", "你好4");
        param.put("你好16", "你好4");
        param.put("你好17", "你好4");
        param.put("你好18", "你好4");
        param.put("你好20", "你好4");
        param.put("你好21", "你好4");
        param.put("你好22", "你好4");
        param.put("你好23", "你好4");
        param.put("你好24", "你好4");
        param.put("你好25", "你好4");
        param.put("你好26", "你好4");
        param.put("你好27", "你好4");
        param.put("你好28", "你好4");
        param.put("你好29", "你好4");
        param.put("你好30", "你好4");
        param.put("你好31", "你好4");
        param.put("你好32", "你好4");

        param.forEach((k, v) -> System.out.println(k));
        System.out.println(param.get("你好4"));
        System.out.println(param.size());
    }
}

在这里插入图片描述

二,代码介绍

1.这是一个简单的自定义哈希映射(HashMap)的实现。功能不太完善,代码不太优雅,细节不够好,没有源码的那么好。后面我会慢慢进行优化
2.这段代码实现了一个简化版本的 HashMap,包含了 put 和 get 方法,并使用了数组+链表的存储结构。注意,这里的红黑树部分并未实现,只使用了链表来处理冲突。由于短时间没有办法能按自己的方式写出来全部的功能和底层设计,难度还是比较大的后期我会慢慢的把存储结构改成数组+链表+红黑树的数据结构

请注意,这只是一个简化版本的实现,并没有处理扩容、红黑树转换等复杂的细节。实际的 HashMap 实现要复杂得多。如果你对完整的实现感兴趣,建议你查阅 Java 的 HashMap 源代码,以便更好地理解和学习。

三,下期完善功能

存储结构 数组+链表+红黑树

三,HashMap

HashMap 是 Java 中的一个常用的数据结构,它实现了 Map 接口,并且基于哈希表实现。HashMap 允许存储键值对,并且提供了快速的插入、查找和删除操作。

下面是 HashMap 的一些重要特点和概念:

  • 哈希表HashMap 内部使用了一个数组来存储数据,这个数组被称为哈希表。哈希表的每个元素称为一个桶(bucket),每个桶可以存储一个或多个键值对。通过计算键的哈希值,可以确定键值对在哈希表中的位置。

  • 哈希函数:哈希函数用于将键映射到哈希表中的索引位置。好的哈希函数能够将键均匀地分布在哈希表的不同位置上,以减少冲突的概率。在 HashMap 中,键的 hashCode() 方法被用作哈希函数。

  • 冲突:当两个不同的键通过哈希函数计算得到的索引位置相同时,就发生了冲突。HashMap 使用链表或红黑树来解决冲突。当冲突较少时,使用链表;当链表长度超过一定阈值时,转换为红黑树,以提高查找的效率。

  • 键的唯一性:在 HashMap 中,键是唯一的。如果尝试将一个已经存在的键插入到 HashMap 中,它的值将被更新为新的值。

  • null 键HashMap 允许存储一个键为 null 的键值对。这个键将被存储在哈希表的第一个位置上。

  • 迭代顺序HashMap 不保证键值对的迭代顺序,它通常是不确定的。如果需要有序的键值对集合,可以使用 LinkedHashMap

下面是一些常用的操作方法:

  • put(key, value):向 HashMap 中插入一个键值对。
  • get(key):根据键获取对应的值。
  • remove(key):根据键删除对应的键值对。
  • containsKey(key):检查是否包含指定的键。
  • containsValue(value):检查是否包含指定的值。
  • size():返回 HashMap 中键值对的数量。

需要注意的是,HashMap 是非线程安全的,如果在多线程环境中使用,需要进行适当的同步处理,或者使用线程安全的 ConcurrentHashMap

HashMap 的时间复杂度通常是常数级别的,即 O(1),但在最坏的情况下,可能会达到 O(n),其中 n 是 HashMap 中存储的键值对数量。

希望这个简介对你理解 HashMap 有所帮助!如果你有任何其他问题,请随时提问。

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

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

相关文章

Linux-----10、查找命令

# 查找命令 # 1、 命令查找 Linux下一切皆文件&#xff01; which 命令 &#xff1a;找出命令的绝对路径 whereis 命令 &#xff1a;找出命令的路径以及文档手册信息 [rootheima ~]# which mkdir /usr/bin/mkdir[rootheima ~]# whereis mkdir mkdir: /usr/bin/mkdir /usr/…

+0和不+0的性能差异

前几日&#xff0c;有群友转发了某位技术大佬的weibo。并在群里询问如下两个函数哪个执行的速度比较快&#xff08;weibo内容&#xff09;。 func g(n int, ch chan<- int) {r : 0for i : 0; i < n; i {r i}ch <- r 0 }func f(n int, ch chan<- int) {r : 0for …

读书笔记-《数据结构与算法》-摘要5[归并排序]

归并排序 核心&#xff1a;将两个有序对数组归并成一个更大的有序数组。通常做法为递归排序&#xff0c;并将两个不同的有序数组归并到第三个数组中。 先来看看动图&#xff0c;归并排序是一种典型的分治应用。 public class MergeSort {public static void main(String[] ar…

vscode 文件目录栏缩进

一个好的开发IDE&#xff0c;一定是让人赏心悦目的&#xff0c;这个赏心悦目也一定是包含层级目录的清晰明了&#xff01;不能像感冒的鼻涕一样一擤一摊子&#xff01;就像。。。。嗯&#xff0c;算了&#xff0c;断子还是不讲了&#xff0c;怕有些妹子投诉 或发消息批评我。。…

Java反射类、构造方法、类变量、类方法

被反射的Student类 package reflect;public class Student {public String name;private String age;public int num;public Student(){}public Student(String age) {this.age age;}private Student(String name, String age){this.age age;this.name name;}public String…

Windows下使用CMake编译lua

Lua 是一个功能强大、高效、轻量级、可嵌入的脚本语言。它支持程序编程、面向对象程序设计、函数式编程、数据驱动编程和数据描述。 Lua的官方网站上只提供了源码&#xff0c;需要使用Make进行编译&#xff0c;具体的编译方法为 curl -R -O http://www.lua.org/ftp/lua-5.4.6.…

从零构建属于自己的GPT系列6:模型部署2(文本生成函数解读、模型本地化部署、文本生成文本网页展示、代码逐行解读)

&#x1f6a9;&#x1f6a9;&#x1f6a9;Hugging Face 实战系列 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在PyCharm中进行 本篇文章配套的代码资源已经上传 从零构建属于自己的GPT系列1&#xff1a;数据预处理 从零构建属于自己的GPT系列2&#xff1a;模型训…

IP属地变化背后的原因

随着互联网的普及和技术的不断发展&#xff0c;IP属地变化的现象越来越受到人们的关注。近日&#xff0c;有网友发现自己的IP属地发生了变化&#xff0c;引发了广泛讨论。那么&#xff0c;IP属地为什么会发生变化呢&#xff1f; 首先&#xff0c;网络环境的变化是导致IP属地变化…

过拟合与欠拟合

一、模型选择 1、问题导入 2、训练误差与泛化误差 3、验证数据集和测试数据集 4、K-折交叉验证 一般在没有足够多数据时使用。 二、过拟合与欠拟合 1、过拟合 过拟合的定义&#xff1a; 当学习器把训练样本学的“太好”了的时候&#xff0c;很可能已经把训练样本自身的一些特…

JAVA:乘除窗体的实现

目录 题目要求&#xff1a; 窗口的实现&#xff1a; try 和 catch 的用法&#xff1a; 思路大意&#xff1a; 关键代码的实现&#xff1a; 题目要求&#xff1a; 使用 try 和catch 方法完成乘法除法的异常处理和窗体的实现&#xff0c;如下图所示&#xff1a; 窗口的实…

PDF控件Spire.PDF for .NET【转换】演示:将 PDF 转换为线性化

PDF 线性化&#xff0c;也称为“快速 Web 查看”&#xff0c;是一种优化 PDF 文件的方法。通常&#xff0c;只有当用户的网络浏览器从服务器下载了所有页面后&#xff0c;用户才能在线查看多页 PDF 文件。然而&#xff0c;如果 PDF 文件是线性化的&#xff0c;即使完整下载尚未…

Java-----链表

本篇碎碎念&#xff1a;唐朝诡事录中的西安与洛阳让我想到了&#xff0c;远赴人间惊鸿宴会&#xff0c;一睹人间盛世颜&#xff0c;描绘的就是这两个古都吧&#xff0c;有机会一定要去游览一番 今日份励志文案: 最好的状态就是向自己喜欢的东西一点点靠近 …

国产数据库适配-南大通用(Gbase)问题整理

Gbase 函数 [GBase 8s 教程]GBase 8s 常用函数、表达式_gbase函数-CSDN博客 Gbase 8s hibernate方言包下载&#xff1a; Index of /dl/hibernate select * from sysmaster:sysdbslocale 导出数据 su - gbasedbt export DB_LOCALEzh_CN.57372 export CLIENT_LOCALEzh_cn…

ESXI 6.7升级update3

一、适用场景 1、企业已有专业服务器&#xff0c;通过虚拟化环境搭建了vm server&#xff1b; 2、备份整个vm server时&#xff0c;需要使用ovftool工具完成&#xff0c;直接导出ovf模板时报错&#xff1b; 3、升级EXSI6.7的build 8169922版本为update 3版本后&#xff0c;已保…

Vue3-08-条件渲染-v-if 的基本使用

v-if 是什么 v-if 一个指令&#xff0c; 它是用来根据条件表达式&#xff0c;进行选择性地【展示】/【不展示】html元素的。比如 &#xff1a; 有一个按钮A&#xff0c;当条件为真时&#xff0c;展示该按钮&#xff1b;条件为假时&#xff0c;不展示该按钮。与 js 中的 条件判…

轮播图封装,包含进度圆点,左右切换

轮播图封装&#xff0c;包含进度圆点&#xff0c;左右切换 封装一个函数&#xff0c;方便多次调用 html: <div class"home-Carousel" ><ul class"list1"><li class"item1 active"><a href"javascript:;" class&…

redis-学习笔记(Jedis 通用命令)

flushAll 清空全部的数据库数据 jedis.flushAll();set & get set 命令 get 命令 运行结果展示 exists 判断该 key 值是否存在 当 redis 中存在该键值对时, 返回 true 如果键值对不存在, 返回 false keys 获取所有的 key 值 参数是模式匹配 *代表匹配任意个字符 _代表匹配一…

SD-MTSP:开普勒优化算法KOA求解单仓库多旅行商问题MATLAB(可更改数据集,旅行商的数量和起点)

一、开普勒优化算法KOA 开普勒优化算法&#xff08;Kepler optimization algorithm&#xff0c;KOA&#xff09;由Mohamed Abdel-Basset等人于2023年提出。 参考文献&#xff1a; [1]Mohamed Abdel-Basset, Reda Mohamed, Shaimaa A. Abdel Azeem, Mohammed Jameel, Mohamed …

提升数据分析效率:Amazon S3 Express One Zone数据湖实战教程

前言 什么是 Amazon S3&#xff1f;什么是 S3 Express One Zone&#xff1f;实现概述 技术架构组件实现步骤概览 第一步&#xff1a;构建数据湖的基础第二步&#xff1a;选择并查看数据集第三步&#xff1a;在 Athena 中搭建架构第四步&#xff1a;数据转换与优化第五步&#x…

three.js(二)

three.js&#xff08;二&#xff09; 参考前言正文简单开始(不使用任何框架)补充 粗略带过(使用Vue框架)细致讲解(比如我使用react框架)App.jsx 的进阶版 项目打包补充打包遇到的问题:原因:解决办法: 参考 https://threejs.org/docs/ 前言 上一集中,我们用到了three.js的一个…