手写HashMap

目录

一、基于 hash 算法、单向链表、数组实现 HashMap

1.1 MyHashMap类

1.2 Entry类

1.3 Student类

1.4 启动类/测试类

二、基于ArrayList实现HashMap

2.1 MyHashMap类

2.2 Entry类

2.3 启动类/测试类


一、基于 hash 算法、单向链表、数组实现 HashMap

说明:使用hash算法实现hashMap要注意发生hash冲突,为了解决这个问题,因此使用了单向链表对集合中的键值对进行存储。

对上述图片进行讲解:entyr 即一个黄色小框框,为数组中一个对象,代表hashMap集合中的键值对元素;0,1,2...1000则为该数据的链表;“key=2”和“key=4”为两个键值对对象,这里假设“key=2”以及“key=4”的两个对象的hashCode值相等,由于“key=2”的键值对先存进数组中,因此“key=4”的键值对通过单向链表的方式追加到“key=2”的键值对后面。那么为什么会出现链表的方式?原因是通过hash算法会存在hash冲突的问题,本文的hash算法为:存放地址 = key的hash值 取余 数组的长度。代码意思就是:index = key.hashCode() % entries.length,因此若两个键值对对象的key的hash值相同,则存放的位置肯定是相同,因此通过链表的方式解决这一hash冲突。

本文实现了HashMap的重要的方法以及特点,如:只能存放唯一个key值,并且key值以及Value值均可以存放自定义对象,并且解决了hash冲突。但当存放自定义对象作为key值的时候,需要重写该对象的equals()方法以及hashCode()方法。

hash冲突:根据key(键)即经过一个函数f(key)得到的结果的作为地址去存放当前的key value键值对(这个是hashmap的存值方式),但是却发现算出来的地址上已经被占用了。这就是所谓的hash冲突。而本文的函数 f 的实现大概就是:存放地址 = key的hash值 取余 数组的长度。代码意思就是:index = key.hashCode() % entries.length

注:本文还存在一个问题,本文存储集合中的键值对的主要方式是通过数组的方式,而为了提高查询效率,有时候增加数组的长度是非常必要的,需要用到数组内容的扩容机制,后续会慢慢更新,读者也可以自行的去解决

1.1 MyHashMap类

package com.mingfa.work01;

import java.util.ArrayList;
import java.util.Map;

/**
 * 基于 hash 算法、单向链表、数组实现 HashMap
 * @param <K>
 * @param <V>
 */
public class MyHashMap<K,V> {
    //Entry为存放集合的键值对,即 <key,value>
    private Entry[] entries = new Entry[10];//假设数组容量为10,但是实际上是可以动态修改,因为存在扩容机制

    /**
     * 为集合添加数据
     * @param k key 值
     * @param v value 值
     */
    public void put(K k,V v){
        /**
         * 1、根据 key 的 hashcode 取余 entrys.length , 余数为该 key 存放在我们数组中对应的 index 位置
         * 2、判断该位置是否存在键值对
         * 3、如果是,则查看该结点以及相应的链表是否存在以 k 为 key 的值
         * 4、如果没有,则在最后面插入
         */
        int hash = Math.abs(k.hashCode());//去 key 值的hash值,注意这里需要确保为整数,因为 hash 值是用来觉得该键值对存放在数据的位置,而数组的索引必须大于等于0
        int index = hash % entries.length;//这是本文的 hash 算法,根据key的哈希值决定键值对的存放索引
        Entry oldEntry = entries[index];//取该索引的键值对
        //看当前数组节点是否为空
        if (oldEntry == null) {//若当前索引不存在键值对
            entries[index] = new Entry<>(k,v,hash);//则将该键值对插入
            return;
        }
        //若不为空,则遍历链表
        for(;;){
            //查看链表(包括首结点)是否存在当前key值,若是,则更换 value 值
            if (oldEntry.getKey().equals(k)){
                oldEntry.setValue(v);
                return;
            }
            //若不存在,则查找该结点的下一个结点
            if (oldEntry.getNext() == null){//若下一个结点为空,则追加到链结点尾部
                oldEntry.setNext(new Entry(k,v,hash));
                return;
            }else{//否则取下一个结点,继续循环
                oldEntry = oldEntry.getNext();
            }
        }
    }

    /**
     * 根据 key 取 value
     * @param k key 值
     * @return value 值
     */
    public V get(K k){
        /**
         * 1、通过 hash 算法找到数组中的链表首结点
         * 2、通过for循环查找 key 为 k 的 value
         * 注:如果是存放自定义对象作为 key 值,这里需要注意 hashCode() 以及 equals() 方法的重写
         */
        //根据 hash 算法先获取 entries 数组的某个位置
        int hash = Math.abs(k.hashCode());
        int index = hash % entries.length;
        //判断该位置上是否存在键值对,如果是,则遍历以该结点为头的链表,从而寻找 key 为 k 的 value
        for (Entry<K,V> entry = entries[index];entry != null;entry = entry.getNext()){
            if (entry.getHash() == hash && (entry.getKey() == k || entry.getKey().equals(k)))
                return entry.getValue();
        }
        return null;
    }

    /**
     * 获取集合大小
     * @return 集合大小
     */
    public int size(){
        int count = 0;//记录集合大小
        Entry entry = null;//用来遍历键值对
        //以数组的长度为循环结束条件
        for (int i = 0;i<entries.length;i++){
            if (entries[i] != null){
                entry = entries[i];
                count++;//如果当前有对象,则集合大小+1
                //遍历以该结点为首结点的链表
                while (entry.getNext() != null){
                    count++;//当下一个结点不为空时,则+1
                    entry = entry.getNext();
                }
            }
        }
        return count;
    }

    /**
     * 通过 ArrayList 取集合中全部的 value 值
     * @return 集合中全部的 value 值
     */
    public ArrayList arrayListGetValues(){
        ArrayList arrayList = new ArrayList();//存放结果
        Entry entry = null;//用来遍历键值对
        //以数组的长度为循环结束条件
        for (int i = 0;i < entries.length;i++){
            if (entries[i] != null){
                arrayList.add(entries[i].getValue());//如果结点不为空,则将该结点的 value 值加入到列表中
                entry = entries[i];//取该结点
                //死循环,遍历以该结点为首结点的链表
                for (;;){
                    if (entry.getNext() != null){//如果该结点存在下一个结点,则将下一个结点存放在列表
                        arrayList.add(entry.getNext().getValue());
                        entry = entry.getNext();
                    }else break;//遍历完整条链表,退出死循环
                }
            }
        }
        return arrayList;
    }

    /**
     * 通过 ArrayList 取集合中全部的 key 值
     * @return 集合中全部的 key 值
     */
    public ArrayList arrayListGetKeys(){
        //该方法实现逻辑同上
        ArrayList arrayList = new ArrayList();
        Entry entry = null;
        for (int i = 0;i < entries.length;i++){
            if (entries[i] != null){
                arrayList.add(entries[i].getKey());
                entry = entries[i];
                for (;;){
                    if (entry.getNext() != null){
                        arrayList.add(entry.getNext().getKey());
                        entry = entry.getNext();
                    }else break;
                }
            }
        }
        return arrayList;
    }

    /**
     * 通过 ArrayList 取集合中的全部键值对
     * @return 集合中的全部键值对
     */
    public ArrayList<Entry> arrayListGetKeyAndValue(){
        //该方法实现逻辑同上
        ArrayList<Entry> arrayList = new ArrayList();
        Entry entry = null;
        for (int i = 0;i < entries.length;i++){
            if (entries[i] != null){
                entry = entries[i];
                arrayList.add(new Entry(entry.getKey(),entry.getValue()));
                for (;;){
                    if (entry.getNext() != null){
                        entry = entry.getNext();
                        arrayList.add(new Entry(entry.getKey(),entry.getValue()));
                    }else break;
                }
            }
        }
        return arrayList;
    }
}

1.2 Entry类

存放集合中的键值对,一个Entry对象为一个键值对 <key,value>

package com.mingfa.work01;

/**
 * 存放集合中的 键值对
 * @param <K>
 * @param <V>
 */
public class Entry<K,V> {
    private K key;//存放 key 值
    private V value;//存放 value 值
    private int hash;// 存放 hashCode 值
    public Entry<K,V> next;//存放集合元素,解决hash冲突

    public Entry(K k, V v, int hash) {
        this.key = k;
        this.value = v;
        this.hash = hash;
    }

    public Entry(K k,V v){
        this.key = k;
        this.value = v;
    }

    public K getKey() {
        return key;
    }

    public void setKey(K key) {
        this.key = key;
    }

    public V getValue() {
        return value;
    }

    public void setValue(V value) {
        this.value = value;
    }

    public int getHash() {
        return hash;
    }

    public void setHash(int hash) {
        this.hash = hash;
    }

    public Entry<K, V> getNext() {
        return next;
    }

    public void setNext(Entry<K, V> next) {
        this.next = next;
    }
}

1.3 Student类

package com.mingfa.work01;


public class Student {
    private Integer id;
    private String name;

    public Student(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    //注:无论是手写的hashMap集合,还是java提供的hashMap集合,当以自定义对象为 key 值的时候,需要重写 equals 以及 hashMap 方法

    /**
     * 重写 equals 方法
     * @param o
     * @return
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return this.id.equals(student.id) && this.name.equals(student.name);
        //return Objects.equals(this.id,student.id) && Objects.equals(this.name,student.name);
    }

    /**
     * 重写 hashCode 方法
     * @return
     */
    @Override
    public int hashCode() {
        return this.id.hashCode() + this.name.hashCode();
        //return Objects.hash(id, name);
    }

    /**
     * 重写 toString 方法,方便打印
     * @return
     */
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

1.4 启动类/测试类

package com.mingfa.work01;

import java.util.*;

public class test {
    public static void main(String[] args) {
        MyHashMap map = new MyHashMap<>();
        /**
         * 存放 a ,97 或者 b ,98,是为了测试 hash 冲突对该集合的影响
         * 原因是 a 的哈希值与97的哈希值均是97。其他雷同
         */
        map.put("a","123");
        map.put(97,"789");
        map.put("a","aaa");
        map.put(97,"977");
        map.put("b","bbb");
        map.put(98,"988");
        map.put("c","ccc");
        map.put(99,"999");
        /**
         * 测试存放两个内容相同的自定义对象,集合是否能够有效处理
         */
        map.put(new Student(1,"mingfa"),"mingfafa");
        map.put(new Student(1,"mingfa"),"mingfafa1");
        map.put(new Student(2,"mingfa"),"mingfafa2");
        map.put("mingfa",new Student(1,"jinjin"));
        map.put("mingfa",new Student(2,"jinjin"));
        map.put("haha",new Student(3,"jinjin"));

        System.out.println("集合长度为:"+map.size());//输出:10
        System.out.println("key为a的value为:"+ map.get("a"));//输出:aaa
        System.out.println("key为97的value为:"+ map.get(97));//输出:977
        System.out.println("集合中全部的key:"+map.arrayListGetKeys());
        System.out.println("集合中全部的value:"+map.arrayListGetValues());
        System.out.print("集合中全部的键值对:");
        ArrayList<Entry> arrayList = map.arrayListGetKeyAndValue();
        for (Entry entry : arrayList){
            System.out.print(entry.getKey()+"-"+entry.getValue()+"   ");
        }
        System.out.println();
        System.out.println(map.get(new Student(1, "mingfa")));//输出:mingfafa1

    }
}

二、基于ArrayList实现HashMap

2.1 MyHashMap类

package com.mingfa.work01;

import java.util.ArrayList;

/**
 * 使用 ArrayList 实现 HashMap 
 * 缺点:
 * 1、效率低,原因是 ArrayList 的底层实现是数据的形式实现
 * 优点:
 * 1、保证存放的键值对是有序存放,而不是无序的
 * @param <K>
 * @param <V>
 */
public class MyHashMap<K,V> {
    private ArrayList<Entry<K,V>> arrayList = new ArrayList<>();

    /**
     * 插入数据
     * @param k key 值
     * @param v value 值
     */
    public void put(K k,V v){
        //先查看集合中是否存在该 key 值
        for (Entry entry : arrayList){
            if (entry.getK().equals(k)) {//当 map 集合存放自定义对象,需要重写 equals() 以及 hashCode() 方法
                entry.setV(v);
                return;
            }
        }
        //若不存在,则添加
        arrayList.add(new Entry<>(k,v));
    }

    /**
     * 根据 key 得到 value
     * @param k key 值
     * @return value 值
     */
    public V get(K k){
        for (Entry entry: arrayList) {
            if (entry.getK().equals(k)) return (V)entry.getV();
        }
        return null;
    }

    /**
     * 返回集合大小
     * @return 集合大小
     */
    public int size(){
        return arrayList.size();
    }
}

2.2 Entry类

package com.mingfa.work01;

/**
 * 存放集合中的 键值对
 * @param <K>
 * @param <V>
 */
public class Entry<K,V> {
    private K k;//存放 key 值
    private V v;//存放 value 值

    public Entry(K k, V v) {
        this.k = k;
        this.v = v;
    }

    public K getK() {
        return k;
    }

    public V getV() {
        return v;
    }

    public void setK(K k) {
        this.k = k;
    }

    public void setV(V v) {
        this.v = v;
    }
}

2.3 启动类/测试类

package com.mingfa.work01;

import java.util.ArrayList;

/**
 * 使用 ArrayList 实现 HashMap 
 * 缺点:
 * 1、效率低,原因是 ArrayList 的底层实现是数据的形式实现
 * 优点:
 * 1、保证存放的键值对是有序存放,而不是无序的
 * @param <K>
 * @param <V>
 */
public class MyHashMap<K,V> {
    private ArrayList<Entry<K,V>> arrayList = new ArrayList<>();

    /**
     * 插入数据
     * @param k key 值
     * @param v value 值
     */
    public void put(K k,V v){
        //先查看集合中是否存在该 key 值
        for (Entry entry : arrayList){
            if (entry.getK().equals(k)) {//当 map 集合存放自定义对象,需要重写 equals() 以及 hashCode() 方法
                entry.setV(v);
                return;
            }
        }
        //若不存在,则添加
        arrayList.add(new Entry<>(k,v));
    }

    /**
     * 根据 key 得到 value
     * @param k key 值
     * @return value 值
     */
    public V get(K k){
        for (Entry entry: arrayList) {
            if (entry.getK().equals(k)) return (V)entry.getV();
        }
        return null;
    }

    /**
     * 返回集合大小
     * @return 集合大小
     */
    public int size(){
        return arrayList.size();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值