手写HashMap(数组+链表形式)详解

目录

1.定义MyMap接口(可以理解为HashMap实现的Map接口)

2 实现接口及参数解释

3,自定义HashCode

4,重写put方法

5,重写get方法   

6,重写remove方法


1.定义MyMap接口(可以理解为HashMap实现的Map接口)

public interface MyMap<K, V> {
    /**
     * 定义put方法
     *
     * @param k
     * @param v
     * @return
     */
    V put(K k, V v);

    /**
     * 定义get 方法
     *
     * @param k
     * @return
     */
    V get(K k);

    /**
     * 定义长度
     *
     * @return
     */
    int size();

    /**
     * 删除一个元素
     * @param k
     * @return
     */
    boolean remove(K k);

    /**
     * 定义规范
     *
     * @param <K>
     * @param <V>
     */
    interface Entry<K, V> {
        K getKey();

        V getValue();
    }
}

2 实现接口及参数解释

public class MyHashMap<K, V> implements MyMap<K, V> {
    //容器
    private Entry<K, V>[] table = null;

    private int mapLength = 16;
    //计算临界值负载因子
    private static final double CRITICAL_VALUE = 0.75;

    //临界值 扩容用的
    private int threshold = (int) Math.ceil(mapLength * CRITICAL_VALUE);

    private int size = 0;

    public MyHashMap() {
        table = new Entry[mapLength];
    }


    public MyHashMap(int mapLength) {
        this.mapLength = mapLength;
    }
    class Entry<K, V> implements MyMap.Entry<K, V> {
        K key;
        V value;
        int index;
        Entry<K, V> next;

        public Entry(K key, V value, int index, Entry<K, V> next) {
            this.key = key;
            this.value = value;
            this.index = index;
            this.next = next;
        }

        @Override
        public K getKey() {
            return key;
        }

        @Override
        public V getValue() {
            return value;
        }
    }
    @Override
    public V put(K k, V v) {
        return null;
    }

    @Override
    public V get(K k) {
        return null;
    }
    //简单,直接完成
    @Override
    public int size() {
        return this.size;
    }

    @Override
    public boolean remove(K k) {
        return false;
    }
}

值得注意的就是前面的几个参数和一个内部类Entry:此类主要的参数有四个key,value,index,next,分别来做解释

key 和 value 对应 HashMap中put方法传进来的key 和value;

index对应的它在数组中的下表;next:当hash冲突的时候,就是指向下一个结点的元素;

其实可以简单理解为HashMap具体存的只是Entry,这个元素里存的具体参数,因为传进来的反省不能保证有next,而我们又需要next。

3,自定义HashCode

private int hashCode(Object key) {
        if (key == null) {
            return 0;
        }
        int h = key.hashCode();
        return (h ^ (h >>> 16)) % this.mapLength;
}

哈希算法如果不懂的可以百度一下,百度更加清晰些,简单解释就是通过hashcode计算他在数组中的具体下标。

4,重写put方法

 public V put(K k, V v) {
        //这里是扩容逻辑,可以看完扩容再来看下面的if
        Entry<K, V>[] tab;
       
        if ((tab = resize()) != null) {
            this.table = tab;
        }
        //通过hashCode方法计算他的下标
        int index = hashCode(k);
        //通过下标拿元素
        Entry<K, V> kvEntry = table[index];
        //如果当前元素为空 直接把传进来的key和value放在当前位置,因为他没有next元素
        //所以传null即可,然后数组长度+1;
        if (kvEntry == null) {
            table[index] = new Entry<>(k, v, index, null);
            size++;
        } else {//如果当前位置有元素:这里的主要的逻辑有两个,判重和添加
            //保存旧的值
            V oldValue;
            //如果当前元素有下一个就继续便利
            while (kvEntry.next != null) {
                //如果当前key已经存在了,返回旧的值,对原来的值进行替换
                if (kvEntry.getKey().equals(k)) {
                    oldValue = kvEntry.getValue();
                    kvEntry.value = v;
                    return oldValue;
                } else {//否则继续查找下一个
                    kvEntry = kvEntry.next;
                }
            }
            //便利到最后一个元素了,还没找到重复值,直接添加到链表尾
            kvEntry.next = new Entry<>(k, v, index, null);
        }
        //返回value
        return table[index].getValue();
    }

5,重写get方法   

    public V get(K k) {
        int index = hashCode(k);
        //通过哈希获取的index传入到下面的方法中
        Entry<K, V> kvEntry = table[index];
        return findValueByKey(k, kvEntry);
}
private V findValueByKey(K k, Entry<K, V> kvEntry) {
        //如果当前下标的元素就是我们需要的值直接返回value
        if (kvEntry != null && (k == kvEntry.getKey() || kvEntry.getKey().equals(k))) {
            return kvEntry.getValue();
        } else {//遍历链表,找到返回value
            while (kvEntry != null) {
                if (k == kvEntry.getKey() || kvEntry.getKey().equals(k)) {
                    return kvEntry.getValue();
                }
                kvEntry = kvEntry.next;
            }
        }
        //找不到直接返回null
        return null;
}

6,重写remove方法

    public boolean remove(K k) {
        //老规矩,还是计算下标
        int hashCode = hashCode(k);
        //下标没有元素,说明这个key没插入过,返回false
        if (table[hashCode] == null) {
            return false;
        
        }
        //如果下标就是要删除的元素,让当前链表等于链表的下一个
        else if (table[hashCode].getKey() == k || table[hashCode].getKey().equals(k)) {
            table[hashCode] = table[hashCode].next;
            size--;
            return true;
        } else {
            //遍历链表删除元素,如果不懂可以做做链表的力扣,如果对链表中的元素进行删除,我没有
            //就算size
            Entry temp = table[hashCode];
            while (temp.next != null) {
                if (temp.next.getKey() == k || temp.next.getKey().equals(k)) {
                    temp.next = temp.next.next;
                    return true;
                } else {
                    temp = temp.next;
                }
            }
        }
        //把所有情况做完了,说明这个key没插入过,返回false
        return false;
    }

7,扩容方法

private Entry<K, V>[] resize() {
        Entry<K, V>[] kvEntry = null;
        //如果当前的容器为空,返回一个新的链表
         if (this.table == null) {
            Entry<K, V>[] entries = new Entry[mapLength];
            return entries;
        //如果当前容器不为空,需要创建新的容器,并把旧容器的每一个元素重新计算索引后放入到新的
        //容器中,如果不这样做,会出现get不到的情况,因为我们的索引值也是和容器的长度有关的
        // else if 里面的意思是如果当前容器长度大于了临界值,我们进行扩容
        } else if (size >= (this.threshold)) {
            //容器长度扩充2倍,临界值也扩充两倍
            this.mapLength = this.mapLength * 2;
            this.threshold *= 2;
            kvEntry = new Entry[this.mapLength];
            int hashCode;
            //遍历添加             
            for (Entry entry : table) {
                if (entry == null) {
                    continue;
                }
                //如果当前元素是链表,计算每一个元素的哈希值
                if (entry.next != null) {
                    while (entry != null) {
                        kvEntry[hashCode(entry)] = entry;
                        entry = entry.next;
                    }
                }
                //直接添加
                else {
                    hashCode = hashCode(entry.getKey());
                    kvEntry[hashCode] = entry;
                }
            }
        }
        //返回新节点,然后可以去看看put方法的第一个if
        return kvEntry;
}

具体代码都在gitee上,可以直接下载,写的不好,写的不对的地方还请指正。

MyDemo


版权声明:本文为weixin_57929476原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。