💪手写mini-HashMap

引言

先来放一张图, 表示HashMap的内部的一些原理

概述

在本节中,我们将深入研究手写的HashMap实现,名为MyHashMap 。我们将逐步解释其结构和工作原理,并对关键方法进行讲解。 ( 此图来自:pdai.tech/md/java/col...

MyHashMap的基础知识

创建HashMap的基本结构

MyHashMap的核心结构是一个数组,数组中的每个元素是一个链表的头节点,用于存储哈希冲突的键值对。

css 复制代码
javaCopy code
private Node<K, V>[] table;

哈希节点的定义

Node类用于表示链表节点,存储键、值、哈希码,以及指向下一个节点的引用。

csharp 复制代码
static class Node<K, V> {
    int hash;
    K key;
    V value;
    Node next;
​
    // 构造方法和toString方法
}

put方法实现

( 此图来自:pdai.tech/md/java/col...

ini 复制代码
public V put(K key, V value) {
    // 处理key为null的情况
    if (key == null) {
        return putForNullKey(value);
    }
​
    // 计算哈希值并获取索引位置
    int index = Math.abs(key.hashCode()) % table.length;
    Node<K, V> node = table[index];
​
    // 如果对应位置上没有值
    if (null == table[index]) {
        table[index] = new Node<K, V>(key.hashCode(), key, value, null);
        size++;
        return value;
    }
​
    // 如果有链表
    Node<K, V> pre = node;
    while (node != null) {
        if (key.equals(node.key)) {
            // 如果键已存在,更新值并返回旧值
            V oldValue = node.value;
            node.value = value;
            return oldValue;
        }
        pre = node;
        node = node.next;
    }
​
    // 如果键不存在,将新节点添加到链表末尾
    pre.next = new Node<>(key.hashCode(), key, value, null);
    size++;
    return value;
}

put方法中,我们首先处理了键为null的特殊情况,然后计算了键的哈希值,并根据取余操作找到对应的索引位置。接着,我们检查该位置是否已经有节点,如果没有,则直接创建新节点。如果已经有链表,我们遍历链表,查找是否存在相同的键,如果找到则更新其值,否则将新节点追加到链表末尾。

putForNullKey方法实现

ini 复制代码
private V putForNullKey(V value) {
Node<K, V> node = table[0];
if (node == null) {
    // 如果对应位置上没有值,创建新节点
    table[0] = new Node<>(0, null, value, null);
    size++;
    return value; // 返回新值
}
​
// 说明有单向链表
Node<K, V> pre = node;
while (node != null) {
    if (node.key == null) {
        // 如果键已存在,更新值并返回旧值
        V oldValue = node.value;
        node.value = value;
        return oldValue;
    }
    pre = node;
    node = node.next;
}
​
// 如果键不存在,将新节点添加到链表末尾
pre.next = new Node<K, V>(0, null, value, null);
return value;  // 返回新值
}

putForNullKey 方法用于处理键为null的情况。与put方法类似,我们首先判断是否已经有节点,如果没有,则创建新节点。如果有链表,我们遍历链表,查找是否存在null键,如果找到则更新其值,否则将新节点追加到链表末尾。

get方法实现

ini 复制代码
public V get(K key) {
// key为null
if (null == key) {
    Node<K, V> node = table[0];
    if (node == null) {
        return null;
    }
​
    // 存在链表
    while (node != null) {
        if (null == node.key) {
            // 如果找到null键,返回对应的值
            return node.value;
        }
        node = node.next;
    }
}
​
// key不是null
int hash = key.hashCode();
int index = Math.abs(hash % 16);
Node<K, V> node = table[index];
​
// 遍历链表查找键值对
while (node != null) {
    if (node.key.equals(key)) {
        // 如果找到键,返回对应的值
        return node.value;
    }
    node = node.next;
}
​
// 不存在key
return null;
}

get方法中,我们首先处理键为null的情况,遍历存储在索引0处的链表。如果键不为null,我们计算哈希值并获取索引位置,然后遍历对应的链表查找键值对。如果找到了相应的键,返回其对应的值,否则返回null。

重写toString方法

ini 复制代码
@Override
public String toString() {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < table.length; i++) {
        Node<K, V> node = table[i];
        while (node != null) {
            sb.append(node);
            node = node.next;
        }
    }
    return sb.toString();
}

toString 方法用于输出MyHashMap 的字符串表示形式。我们遍历数组中的每个链表,将节点的字符串表示逐个添加到StringBuilder中。

完整代码

ini 复制代码
​
public class MyHashMap<K, V> {
​
    private Node<K, V>[] table;
​
    private int size;
​
    public MyHashMap() {
        this.table = new Node[16];
    }
​
    static class Node<K, V> {
        int hash;
​
        K key;
​
        V value;
​
        Node next;
​
        @Override
        public String toString() {
            return new StringBuilder()
                    .append("[")
                    .append(key)
                    .append(",")
                    .append(value)
                    .append("]")
                    .toString();
        }
​
​
        public Node(int hash, K key, V value, Node next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
​
​
        }
    }
​
    public V put(K key, V value) {
        // 处理key为null的情况
        if (key == null) {
            return putForNullKey(value);
        }
        // 不为null,取对应位置
        int index = Math.abs(key.hashCode()) % table.length;
        Node<K, V> node = table[index];
​
        // 如果对应位置上没有值
        if (null == table[index]) {
            table[index] = new Node<K, V>(key.hashCode(), key, value, null);
            size++;
            return value;
        }
        // 如果有链表
        Node<K, V> pre = node;
        while (node != null) {
            if (key.equals(node.key)) {
                V oldValue = node.value;
                node.value = value;
                return oldValue;
            }
            pre = node;
            node = node.next;
        }
        pre.next = new Node<>(key.hashCode(), key, value, null);
        size++;
        return value;
    }
​
    private V putForNullKey(V value) {
        Node<K, V> node = table[0];
        if (node == null) {
            table[0] = new Node<>(0, null, value, null);
            size++;
            return value; // 返回新值
        }
        // 说明有单向链表
        Node<K, V> pre = node;
        while (node != null) {
            if (node.key == null) {
                V oldValue = node.value;
                node.value = value; // 覆盖原有的值
                return oldValue;
            }
            pre = node;
            node = node.next;
        }
        // node为null后
        pre.next = new Node<K, V>(0, null, value, null);
        return value;  // 返回新值
    }
​
    /**
     * get方法
     */
    public V get(K key) {
        // key为null
        if (null == key) {
            Node<K, V> node = table[0];
            if (node == null) {
                return null;
            }
            // 存在链表
            while (node != null) {
                if (null == node.key) {
                    return node.value;
                }
                node = node.next;
            }
        }
        // key不是null
        int hash = key.hashCode();
        int index = Math.abs(hash % 16);
        Node<K, V> node = table[index];
        while (node != null) {
            if (node.key.equals(key)) {
                return node.value;
            }
            node = node.next;
        }
        // 不存在key
        return null;
    }
​
    /**
     * 重写toString方法,输出map集合时会调用
     *
     * @return
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < table.length; i++) {
            Node<K, V> node = table[i];
            while (node != null) {
                sb.append(node);
                node = node.next;
            }
        }
        return sb.toString();
    }
}

测试

总结

在本节中,我们深入了解了MyHashMap 的实现。我们介绍了其基本结构、putget方法的实现,以及特殊情况下的处理。

Java8 对 HashMap 进行了一些修改,最大的不同就是利用了红黑树,其由 数组+链表+红黑树组成,我们目前只实现了数组+链表的结构。

相关推荐
Hello-Brand1 分钟前
Java核心知识体系10-线程管理
java·高并发·多线程·并发·多线程模型·线程管理
乐悠小码7 分钟前
数据结构------队列(Java语言描述)
java·开发语言·数据结构·链表·队列
史努比.9 分钟前
Pod控制器
java·开发语言
2的n次方_12 分钟前
二维费用背包问题
java·算法·动态规划
皮皮林55112 分钟前
警惕!List.of() vs Arrays.asList():这些隐藏差异可能让你的代码崩溃!
java
莳光.12 分钟前
122、java的LambdaQueryWapper的条件拼接实现数据sql中and (column1 =1 or column1 is null)
java·mybatis
程序猿麦小七17 分钟前
基于springboot的景区网页设计与实现
java·spring boot·后端·旅游·景区
weisian15124 分钟前
认证鉴权框架SpringSecurity-2--重点组件和过滤器链篇
java·安全
蓝田~25 分钟前
SpringBoot-自定义注解,拦截器
java·spring boot·后端
.生产的驴28 分钟前
SpringCloud Gateway网关路由配置 接口统一 登录验证 权限校验 路由属性
java·spring boot·后端·spring·spring cloud·gateway·rabbitmq