深入理解HashMap原理(二)——手写HashMap

前言

前面一篇文章(深入理解HashMap原理(一)——HashMap源码解析(JDK 1.8))我们通过JDK 1.8的HashMap的源码了解了HashMap的原理。俗话说:光说不练假把式。下面我们自己动手撸一个HashMap。本文基于JDK1.7 只写数组+链表结构。

思考

我们前面知道,HashMap的存储结构为散列表,解决哈希冲突是通过链表法解决的,结构如下:

那么我们也要来实现,这个结构。

1、定义接口

我们知道HashMap是实现了Map接口的,我们也需要定义一个类似的接口,主要包括存取数据,及存储数据的结构接口(包括取键和取值)。我们定义如下:

/**
 * Copyright: Copyright (c) 2018
 * Project:
 * Author: bthvi
 * Date: 2019/4/10 09:27
 */
public interface MYMap<K,V> {
    //取数据
     V get(K k);
     //存数据
     V put(K k,V v);
     //内部接口
     interface Entry<K,V>{
         //取值
         V getValue();
         //取键
         K getKey();
    }
}

2、自定义Map实现自定义的接口

下面我们需要实现自定义的接口,并且我们要定义默认的容量,默认加载因子(0.75),以及数组使用的长度,还有存储的数组。

import java.util.ArrayList;
import java.util.List;

/**
 * Copyright: Copyright (c) 2018
 * Project:
 * Author: bthvi
 * Date: 2019/4/10 09:32
 */
public class MYHashMap<K,V> implements MYMap<K,V> {

    //默认容量
    private  int DEFAULT_LENGTH = 16;
    //加载因子
    private  float LOADER = 0.75f;
    //Map使用数组长度
    private int useSize = 0;
    //数组
    private Entry<K,V>[] table;

    public MYHashMap() {
        this(16,0.75f);
    }

    public MYHashMap(int defaultLength, float loader) {
        if (defaultLength < 0){
            throw new IllegalArgumentException("数组异常");
        }
        if (loader <= 0 || Float.isNaN(loader)){
            throw  new IllegalArgumentException("负载因子异常");
        }
        this.DEFAULT_LENGTH = defaultLength;
        this.LOADER = loader;
        table = new Entry[DEFAULT_LENGTH];
    }

    /**
     * 取
     * @param k 键值
     * @return
     */
    @Override
    public V get(K k) {
        return null;
    }

    /**
     * 存
     * @param k 键值
     * @param v 值
     * @return
     */
    @Override
    public V put(K k, V v) {
        return null;
    }

}

HashMap的存储结构

下面我们需要实现内部接口,来自定义它每个点的存储结构,需要有键,值,及指向下一节点的对象

   //存储结构
    class Entry<K,V> implements MYMap.Entry<K,V>{

        //键
        private K key;
        //值
        private V value;

        //链表指向下一个
        private Entry<K,V> next;

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

        @Override
        public V getValue() {
            return value;
        }

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

实现HashMap的HashCode来计算下标

我们需要自己实现HashCode来自定义确定key所对应的下标。


    /**
     * 使用每个object的hashCode计算hashCode
     * @param hashCode
     * @return  hashCode
     */
    private int hash(int hashCode) {
        hashCode = hashCode ^ ((hashCode >>> 20) ^ (hashCode >>> 12));
        return hashCode ^ ((hashCode >>> 7) ^ hashCode >>> 4);
    }

    /**
     * 获取下标
     * @param k 键值
     * @param length  数组长度
     * @return 下标
     */
    private int getIndex(K k,int length){
        int m = length - 1;
        int index = hash(k.hashCode()) & m;
        return index >= 0 ? index : -index;
    }

实现HashMap的put方法

HashMap 的put方法就是通过key确定对于数组的下标,再确定有没有哈希冲突,有则往后追加,没有则直接存放。

/**
     * 存
     * @param k 键值
     * @param v 值
     * @return
     */
    @Override
    public V put(K k, V v) {
        //扩容
        if (useSize >= DEFAULT_LENGTH * LOADER){
            resize();
        }
        //计算下标
        int index = getIndex(k,table.length);
        Entry<K,V> entry = table[index];
        Entry<K,V> newEntry = new Entry<K,V>(k,v,null);
        if (null == entry){//直接插入第一个
            table[index] = newEntry;
            useSize ++;
        }else {
            Entry<K,V> t= entry;
            if (t.getKey() == k || (t.getKey() != null && t.getKey().equals(k))){//已经存在,替换
                t.value = v;
            }else {
                while (t.next != null){
                    if (t.getKey() == k || (t.getKey() != null && t.getKey().equals(k))) {//已经存在替换
                        t.value = v;
                        break;
                    }else {
                        t = t.next;
                    }
                }
                if (t.next == null){//不存在,追加到链表后面
                    t.next = newEntry;
                }
            }
        }
        return newEntry.getValue();
    }

当然我们存储的时候还要考虑到HashMap的扩容,下面我们实现HashMap的扩容方法

实现HashMap的扩容resize

首先,我们将数组的长度乘以2,再将原来的数据重新塞到新数组中,注意这个时候存储的会变掉(因为计算下标和数组长度有关)。

    /**
     * 扩容
     */
   private void resize() {
      Entry<K,V>[]  newTable = new Entry[DEFAULT_LENGTH * 2];
       List<Entry<K,V>> list = new ArrayList<>();
       //循环遍历取出旧数组的数据
       for (int i = 0; i < table.length; i++){
           if (null == table[i]){
               continue;
           }
           Entry<K,V> entry = table[i];
           while (null != entry){
               list.add(entry);
               entry = entry.next;
           }
       }

       //重新塞进数组
       if (list.size() > 0){
           useSize = 0;
           DEFAULT_LENGTH = DEFAULT_LENGTH * 2;
           table = newTable;
           for (Entry<K,V> entry : list){
               if (entry.next != null){
                   entry.next = null;
               }
               put(entry.getKey(),entry.getValue());
           }
       }

   }

实现HashMap的get方法

上面我们实现了HashMap的put方法,下面我们来实现HashMap的get方法。

    /**
     * 取
     * @param k 键值
     * @return
     */
    @Override
    public V get(K k) {
        int index = getIndex(k,table.length);
        Entry<K,V> entry = table[index];
        if (null != entry){
            while (entry.next != null){
                if (entry.getKey() == k || (entry.getKey() != null && entry.getKey().equals(k))){
                    return entry.getValue();
                }else {
                    entry = entry.next;
                }
            }
            if (entry.next == null ){
                if (entry.getKey() == k || (entry.getKey() != null && entry.getKey().equals(k))){
                    return entry.getValue();
                }
            }
        }else {
            throw new NullPointerException();
        }
        return null;
    }

总结

至此,缩减版的HashMap就实现了。最后附上github源码地址:https://github.com/Terrybthvi/MyHashMap

其他文章
1、Gradle
[一分钟帮你提升Android studio 编译速度](https://bthvi-leiqi.blog.csdn...
2、Flutter
[Flutter从入门到实战](https://bthvi-leiqi.blog.csdn...
3、源码
[深入理解HashMap原理(一)——HashMap源码解析(JDK 1.8)](https://bthvi-leiqi.blog.csdn...
[深入理解HashMap原理(二)——手写HashMap](https://bthvi-leiqi.blog.csdn...
4、热修复
 [Android学习——手把手教你实现Android热修复](https://bthvi-leiqi.blog.csdn...
 [Android热修复——深入剖析AndFix热修复及自己动手实现](https://bthvi-leiqi.blog.csdn...
 [手撸一款Android屏幕适配SDK](https://bthvi-leiqi.blog.csdn...


欢迎在评论区留下你的观点大家一起交流,一起成长。如果今天的这篇文章对你在工作和生活有所帮助,欢迎转发分享给更多人。

同时欢迎大家加入我组建的大前端学习交流群,从这里出发我们一起讨论,一起交流,一起提升。

群号:872749114

我的公众号

阅读 72

推荐阅读
目录