JavaHashMap的简单实现

HashMap作为java中经常使用到的一个数据结构,同时也是面试中常考的数据结构,掌握其结构与实现原理是必须的。

一、什么是HashMap

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。

HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。

HashMap 是无序的,即不会记录插入的顺序。

二、简单实现

首先要建立一个HNode类

class HNode<K, V> {
    K key;
    V value;
    HNode<K, V> next;
    public HNode(K key, V value) {
        this.key = key;
        this.value = value;
    }
    public int hashCode() {
        return key.hashCode();
    }
}

K用于存储键值,V用于存储数据

接下来是简单的实现

public class MyHashMap<K, V> {
    // 存储节点的数组
    HNode<K, V>[] table;
    // - 已存储节点的个数
    int elmSize;
    // - 数组的长度
    int length;
    // - 数组中被占用的下标个数
    int usedArrSize;
    // - 扩容的阈值 0.75
    double loadFactor = 0.75;
    // 扩容倍数
    int factor = 2;
    // 数组的初始长度
    static final int initLength = 16;

    public MyHashMap() {
        table = new HNode[initLength];
        length = initLength;
    }

    public void put(K key, V value) {
        HNode<K, V> node = new HNode<>(key, value);
        // 根据key的hash值 计算一个需要存储的位置下标
        int hash = node.hashCode();
        int index = hash & (length - 1);//
        // 0000 1000 1001 1101 & 0000 0000 0000 1111 = 1101
        // 0000 1010 1100 1101 & 0000 0000 0000 1111 = 1101
        // 不同的hash值运算下标时也会出现下标冲突: 解决方式 拉链
        // & : 按位与运算符,两边的数进行按位与运算,相同保留源码,不同为0 (都为1才为1)
        HNode<K, V> first = table[index];
        if (first == null) {
            table[index] = node;
            elmSize++;
            usedArrSize++;
        } else {
            // 验证first的key是否与node的key是否相同
            // 两个对象之间使用==判断 判断的是地址
            // hash值不一样的情况 就肯定不一样了
            // hash值一样,会存在两个不同的key算出来相同的hash值
            // 接着判断是不是同一个对象,从地址 和 值比较
            if (first.hashCode() == hash && first.key == key || first.key.equals(key)) {
                // 相等就要替换
                first.value = value;
            } else {
                HNode<K, V> kvNode = null;
                HNode<K, V> temp = first;
                while (temp.next != null) {
                    temp = temp.next;
                    if (temp.hashCode() == hash && temp.key == key ||
                            temp.key.equals(key)) {
                        kvNode = temp;
                        break;
                    }
                }
                if (kvNode == null) {
                    temp.next = node;
                    elmSize++;
                } else {
                    kvNode.value = value;
                }
            }
        }
        // 计算是否需要扩容
        if (length * loadFactor <= usedArrSize) {
            resize();
        }
    }

    private void resize() {
        length = length * factor;
        HNode<K, V>[] newTable = new HNode[length];
        usedArrSize = 0;
        for (HNode<K, V> node : table) {
            while (node != null) {
                HNode<K, V> next = node.next;
                int index = node.hashCode() & (length - 1);
                node.next = newTable[index];
                newTable[index] = node;
                node = next;
                usedArrSize++;
            }
        }
        table = newTable;
    }

    public V get(K key) {
        int hash = key.hashCode();
        int index = hash & (length - 1);
        HNode<K, V> first = table[index];
        return null;
    }

    public static void main(String[] args) {
        MyHashMap<String, Integer> map = new MyHashMap<>();
        for (int i = 0; i < 100; i++) {
            map.put("hello" + i, 1);
        }
    }
}

这段代码是一个简化版的哈希表 (MyHashMap) 实现,哈希表是一种用于存储键值对的数据结构。以下是对每个函数的作用以及变量的用途的解释:

变量解释

  • HNode<K, V>[] table: 存储哈希表节点 (HNode) 的数组,其中每个元素都是一个链表的头节点。每个链表节点存储一个键值对。

  • int elmSize: 哈希表中已存储的节点总数。

  • int length: 哈希表数组的当前长度,即 table 的大小。

  • int usedArrSize: 哈希表数组中已被占用的下标数量,即有多少个数组位置存储了链表的头节点。

  • double loadFactor = 0.75: 负载因子,表示当哈希表的填充程度达到 length * loadFactor 时,需要进行扩容。

  • int factor = 2: 扩容倍数,每次扩容时,数组的大小会乘以这个倍数。

  • static final int initLength = 16: 数组的初始长度,默认为16。

构造函数

public MyHashMap() {
    table = new HNode[initLength];
    length = initLength;
}

这个构造函数初始化了哈希表的数组 table,并将数组的初始长度设置为16。

put 方法

public void put(K key, V value) {
    // ...代码略
}

put 方法用于在哈希表中插入键值对,如果键已经存在则更新对应的值。

  • 计算哈希值: 首先通过 key.hashCode() 计算键的哈希值。

  • 计算数组下标: 使用哈希值与数组长度减1进行按位与运算 (hash & (length - 1)),得到在 table 中的存储位置。

  • 链表冲突处理: 如果该下标对应的链表为空,则直接插入节点。如果不为空,判断该位置的链表中是否已经存在相同的键,如果存在则更新其值,否则将新节点添加到链表的末尾。

  • 扩容检查: 在每次插入后,检查当前填充的数量是否超过了扩容阈值 (length * loadFactor),如果超过则调用 resize() 方法进行扩容。

resize 方法

private void resize() {
    // ...代码略
}

resize 方法用于哈希表的扩容。

  • 扩容: 将哈希表的数组长度乘以扩容倍数 (factor)。

  • 重新分布: 扩容后,重新计算所有已有键值对在新数组中的位置,并将它们放入新数组中。

get 方法

public V get(K key) {
    int hash = key.hashCode();
    int index = hash & (length - 1);
    HNode<K, V> first = table[index];
    return null;
}

get 方法用于根据键来获取对应的值。

  • 计算哈希值和下标: 同 put 方法,先计算哈希值并确定数组中的下标。

  • 遍历链表: 在计算出的下标位置查找链表中的节点,如果找到匹配的键则返回对应的值,否则返回 null

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值