当前位置: 首页 > news >正文

【数据结构与算法】手写HashMap的模拟实现

✨哈喽,进来的小伙伴们,你们好耶!✨

🛰️🛰️系列专栏:【数据结构与算法】

✈️✈️本篇内容:手写HashMap的模拟实现!

🚀🚀代码存放仓库gitee:Java数据结构代码存放!

⛵⛵作者简介:一名双非本科大三在读的科班Java编程小白,道阻且长,你我同行

🚋🚋给大家推荐一个超级好用的刷题网站—牛客网点击链接注册,开启刷题之路!

85cb934670274cef935e78f6f6bb4904.png

 一、手写HashMap

🎁1、Map的常用方法

67be11e8c0624fe4b6707e2071f60acc.png

🍊方法一(基本类型)

因为map的底层结构是数组,每个元素是一个链表,所以我们这里定义的数组是Node类型,定义一个usedSize代表数组中有多少个数据。

 🍅1、内部类准备

  static class Node {
        public int key;
        public int val;
        public Node next;

        public Node(int key, int val) {
            this.key = key;
            this.val = val;
        }
    }

    public Node[] array;
    public int usedSize;

    public HashBuck() {
        array = new Node[8];
    }

🍎2、实现put以及头插法

首先假如我们要插入一个节点,那么我们首先需要找到这个节点,找到下标index,那么这里就涉及到头插和尾插法,JDK1.7默认是头插法,JDK1.8默认是尾插法,那么我们这里使用头插法来演示。

  public void put(int key,int val) {
        int index = key % array.length;
        //遍历Index下标的数组,如果有相同的key那么替换
        Node cur = array[index];
        while (cur != null) {
            if(cur.key == key) {
                cur.val = val;
                return;
            }
            cur = cur.next;
        }
        //进行头插法
        Node node = new Node(key, val);
        node.next = array[index];
        array[index] = node;
        usedSize++;
        if( loadFactor() >= 0.75f) {
            resize();
        }
    }

 🍏3、扩容函数resize

这里扩容不能仅仅将数组增大到二倍,因为扩容之后对应的节点下标会发生变化,所以这里需要重新哈希!需要注意的是我们将节点插入到新的下标之后,那么我们需要定义一个curNext来提前存储cur.next,不然当我们插入之后,链表就会断掉,找不到cur.next了。

   private void resize() {
        Node[] newArray = new Node[2*array.length];
        for (int i = 0; i < array.length; i++) {
            Node cur = array[i];
            while (cur != null) {
                Node curNext = cur.next;
                int newIndex = cur.key % newArray.length;
                //拿着cur节点 进行插入到新的位置
                cur.next = newArray[newIndex];
                newArray[newIndex] = cur;
                cur =  curNext;
            }
        }
        array = newArray;
    }

🍋实验: 这里我们给出main函数代码,来测试一下我们的resize()函数是否有问题。

   public static void main(String[] args) {
        HashBuck hashBuck = new HashBuck();
        hashBuck.put(1,6);
        hashBuck.put(2,7);
        hashBuck.put(3,8);
        hashBuck.put(4,54);
        hashBuck.put(5,66);
        hashBuck.put(6,234);
        hashBuck.put(15,888);
        System.out.println(hashBuck.get(6));
    }

🍯这里我们给出的数组空间大小是8,那么已知负载因子是0.75,所以我们在第六个元素的位置打个断点, 来debug一下。

ce60453aeecd4452b192c068e655cc1c.png

🍻 我们发现,元素的存储没有问题,那么点击上面那个蓝色往右下角偏移的箭头,代码会向下执行。

f500e568a77840ba8a82026b0d5dc449.png

🍺OK,因为这里的15 % 16 = 15;所以15应该插在index为15的位置,usedsize = 7,没有问题。

🥞4、实现get方法

    public int get(int key) {
        int index = key % array.length;
        Node head = array[index];
        while (head != null) {
            if(head.key == key) {
                return head.val;
            }
            head = head.next;
        }
        return -1;
    }

🥘测试:

ea3ca91b762246d8a1d70a9e6599b2d6.png

🍏方法二(引用类型)

那么我们知道Map的结构是<K,V>键值对模型,那么我们在实现的时候可以给出泛型<K,V>。

🍱1、内部类实现

    static class Node<K,V> {
        public K key;
        public V val;
        public Node<K,V> next;

        public Node(K key, V val) {
            this.key = key;
            this.val = val;
        }
    }
    public Node<K,V>[] array = (Node<K,V>[])new Node[10];
    public int usedSize;

🍊2、实现put方法

因为这里的key是引用类型不可以去%length,必须想办法转换为整型,那么这里就用到了我们的hashCode()方法,将引用类型转换为哈希码,再用hash%length。

40f8c1da0c714f7ebce519cf96afee4c.png🍋代码实现:

    public void put(K key,V val) {
        int hash = key.hashCode();
        int index = hash % array.length;
        Node<K,V> cur = array[index];
        while (cur != null) {
            if(cur.key.equals(key)) {
                cur.val = val;
                return;
            }
            cur = cur.next;
        }
        Node<K,V> node = new Node<>(key, val);
        node.next = array[index];
        array[index] = node;
        usedSize++;
    }

🥙3、测试类

那么注意,因为我们这里是使用的引用类型,那么必须重写hashCode()方法和equals()方法,hashCode()方法用来定位我们的数组下标,刚才有介绍过hashCode()方法是native int 类型的,equals()方法用来引用类型的比较,那么假如我们这里给一个成员变量id,那么这里我们来测试一下,重写hashCode()方法和equals()方法后:

   @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(id, person.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Person p1 = new Person("12345");
        Person p2 = new Person("12345");
        System.out.println(p1.hashCode());
        System.out.println(p2.hashCode());
    }
}

🌽运行结果:

9c0685df0c8e47e4a428dcb6eca39176.png

我们发现结果是正确的!

🍻4、实现get方法

 public V get(K key) {
        int hash = key.hashCode();
        int index = hash % array.length;
        Node<K,V> cur = array[index];
        while (cur != null) {
            if(cur.key.equals(key)) {
                return cur.val;
            }
            cur = cur.next;
        }
        return null;
    }

面试问题:

🚦1、两个对象的hashCode一样,equals一定一样吗? 不一定

🚦2、两个对象的equals一样,hashCode一定一样吗?一定

 原因:

🍬🍬如果两个对象hashCode()相等,它们并不一定相等。因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等,此时就出现所谓的哈希冲突场景。

🍼🍼如果两个对象相等,那么它们的hashCode()值一定相同。这里的相等是指,通过equals()比较两个对象时返回true。

总结:

🛥️🛥️1、hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。

⛵⛵2、equals它的作用也是判断两个对象是否相等,如果对象重写了equals()方法,比较两个对象的内容是否相等;如果没有重写,比较两个对象的地址是否相同,价于“==”。同样的,equals()定义在JDK的Object.java中,这就意味着Java中的任何类都包含有equals()函数。
 

相关文章:

  • 电加热油锅炉工作原理_电加热导油
  • 大型电蒸汽锅炉_工业电阻炉
  • 燃气蒸汽锅炉的分类_大连生物质蒸汽锅炉
  • 天津市维修锅炉_锅炉汽化处理方法
  • 蒸汽汽锅炉厂家_延安锅炉厂家
  • 山西热水锅炉厂家_酒店热水 锅炉
  • 蒸汽锅炉生产厂家_燃油蒸汽发生器
  • 燃煤锅炉烧热水_张家口 淘汰取缔燃煤锅炉
  • 生物质锅炉_炉
  • 锅炉天然气_天燃气热风炉