手写一个HashMap

1. HashMap的框架图

文章参考:https://www.cnblogs.com/2511zzZ/p/12770864.html
在这里插入图片描述
HashMap是Java中一中非常常用的数据结构。它实现了基于“K-V”形式的键值对的高效存取。JDK1.7之前,HashMap是基于数组+链表实现的,1.8以后,HashMap的底层实现中加入了红黑树用于提升查找效率。

HashMap的优缺点

  • 遍历速度:慢,Key是无序不重复的且从框架图看完整遍历还需要包含链表
  • 缺陷:高度依赖hash散列算法,想要提高HashMap的效率,最重要的就是尽量避免生成链表,或者说尽量减少链表的长度,避免哈希冲突,降低key的比较次数
  • 插入删除速度:快,散列算法快速定位下标+头插入|指针移动
  • 随机访问速度:快,最显著的优势=散列算法快速定位下标+遍历链表equals准确匹配
  • 适用场景:适合海量数据,o(1)的随机访问速度,但不建议全部遍历

2. 自定义 MyHashMap

需求:功能主要实现put()、get()、remove()方法
原理:MyHashMap = 散列 + 数组 + 列表
技术点:散列、头插入、链表指针移动

  • put()函数:
1. 下标=散列+取余=key.hashCode() % arrays.length
2. 首次插入数据::下标对应的值为null表示当前坑位为空,直接赋值
3. 非首次插入数据:需要在该坑位上建立一个链表或内容覆盖
3.1. 若key相同,则覆盖内容
3.2. 若key不同,则链表新增节点-例如头插法:只需将新节点的next指向原来链表的节点即可
  • get()函数:遍历链表,若存在key值相等,则返回结果
  • remove函数:链表指针操作:将目标节点的前驱节点的next引用指向目标节点的后继节点

1. 接口 MyMap

定义put()、get()、remove()接口方法

package com.sufadi.study.hashmap;

public interface MyMap<K, V> {

    void put(K k, V v);

    V get(K k);

    boolean remove(K k);

    int size();

}

2. MyEntry

HashMap数组和链表的基本单位,留意next的指向关系就行

package com.sufadi.study.hashmap;

public class MyEntry<K, V> {
    K k;
    V v;
    MyEntry<K, V> next;

    public MyEntry(K k, V v, MyEntry<K, V> next) {
        this.k = k;
        this.v = v;
        this.next = next;
    }
}

3. MyHashMap

  • put()函数:
1. 下标=散列+取余=key.hashCode() % arrays.length
2. 首次插入数据::下标对应的值为null表示当前坑位为空,直接赋值
3. 非首次插入数据:需要在该坑位上建立一个链表或内容覆盖
3.1. 若key相同,则覆盖内容
3.2. 若key不同,则链表新增节点-例如头插法:只需将新节点的next指向原来链表的节点即可
  • get()函数:遍历链表,若存在key值相等,则返回结果
  • remove函数:链表指针操作:将目标节点的前驱节点的next引用指向目标节点的后继节点
package com.sufadi.study.hashmap;

public class MyHashMap implements MyMap {

    int size = 0;
    MyEntry<Object, Object>[] arrays;

    public MyHashMap() {
        arrays = new MyEntry[16];
    }

    @Override
    public void put(Object key, Object newV) {
        // 下标=散列+取余
        int index = key.hashCode() % arrays.length;
        System.out.println("    下标=散列+取余 = " + index + ", key:" + key + ",value:" + newV);
        MyEntry<Object, Object> current = arrays[index];

        // 非首次插入数据:需要在该坑位上建立一个链表或内容覆盖
        if (current != null) {
            while (current != null) {
                /**
                 * 链表中,进行比较key,
                 * 若key相同,则覆盖内容
                 * 若key不同,则链表新增节点-例如头插法
                 */
                if (current.k == key) {
                    // 若key相同,则覆盖内容
                    current.v = newV;
                    return;
                }

                current = current.next;
            }

            // 若key不同,则链表新增节点-头插法
            /**
             * 尾插法:需遍历链表,将新节点插入末尾
             * 头插法:只需将新节点的next指向原来链表的节点即可
             */
            arrays[index] = new MyEntry<>(key, newV, arrays[index]);
            size ++;
        } else {
            size ++;
            // null表示当前坑位为空,直接赋值
            arrays[index] = new MyEntry<>(key, newV, null);
        }
    }

    @Override
    public Object get(Object key) {
        // 下标=散列+取余
        int index = key.hashCode() % arrays.length;
        MyEntry<Object, Object> current = arrays[index];
        /**
         * 遍历链表,若存在key值相等,则返回结果
         */
        while (current != null) {
            if (current.k == key) {
                return current.v;
            }
            current = current.next;
        }
        return null;
    }

    @Override
    public boolean remove(Object key) {
        // 下标=散列+取余
        int index = key.hashCode() % arrays.length;
        MyEntry<Object, Object> current = arrays[index];

        // 无链表,则直接置空
        if (current.k == key) {
            arrays[index] = null;
            size --;
            return true;
        }

        while (current.next != null) {
            if (current.next.k == key) {
                // 将目标节点的前驱节点的next引用指向目标节点的后继节点
                current.next = current.next.next;
                size --;
                return true;
            }
            current = current.next;
        }

        return false;
    }

    @Override
    public int size() {
        return size;
    }

    public void printMap() {
        System.out.println("========打印MyHashMap的列表========");
        for (int i = 0; i < arrays.length; i++) {
            if (arrays[i] == null) {
                continue;
            }
            if (arrays[i].next == null) {
                System.out.println("数组下标为" + i + "无链表,key:" + arrays[i].k + ", value:"+ arrays[i].v);
            } else  {
                System.out.println("数组下标为" + i + "的位置有链表且表头为 key:" + arrays[i].k + ", value:"+ arrays[i].v);
                MyEntry<Object, Object> current = arrays[i];
                while (current != null) {
                    System.out.println("    链表节点 key:" + current.k + ", value:"+ current.v);
                    current = current.next;
                }
            }
        }
        System.out.println("========打印MyHashMap的列表========");
    }
}

4. 测试类 MyHashMapTest

package com.sufadi.study.hashmap;

public class MyHashMapTest {
    public static void main(String[] args) {
        MyHashMap map = new MyHashMap();
        System.out.println("*******测试HashMap的put函数*******");
        map.put("Jim", 18);
        map.put("Tom", 19);
        map.put("LouLou", 1);
        map.put("AaAa", 66);
        map.put("BBBB", 88);
        map.printMap();

        System.out.println("\n*******测试HashMap的remove函数*******");
        map.remove("LouLou");
        map.printMap();
    }
}

5. 运行结果

注意LouLou、AaAa、BBBB 转化的hashCode是一致的,下述运行接口可以清晰看到链表生成

*******测试HashMap的put函数*******
    下标=散列+取余 = 14, key:Jim,value:18
    下标=散列+取余 = 2, key:Tom,value:19
    下标=散列+取余 = 0, key:LouLou,value:1
    下标=散列+取余 = 0, key:AaAa,value:66
    下标=散列+取余 = 0, key:BBBB,value:88
========打印MyHashMap的列表========
数组下标为0的位置有链表且表头为 key:BBBB, value:88
    链表节点 key:BBBB, value:88
    链表节点 key:AaAa, value:66
    链表节点 key:LouLou, value:1
数组下标为2无链表,key:Tom, value:19
数组下标为14无链表,key:Jim, value:18
========打印MyHashMap的列表========

*******测试HashMap的remove函数*******
========打印MyHashMap的列表========
数组下标为0的位置有链表且表头为 key:BBBB, value:88
    链表节点 key:BBBB, value:88
    链表节点 key:AaAa, value:66
数组下标为2无链表,key:Tom, value:19
数组下标为14无链表,key:Jim, value:18
========打印MyHashMap的列表========

Process finished with exit code 0
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

法迪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值