手写HashMap部分核心功能

手写java HashMap部分核心功能

嗯,先介绍一下HashMap的运用场景和其内部使用的结构 废话不多说,我们开始

HashMap数据结构是 数组、链表、红黑树(此章代码并没涉及)

大学中科班的同学应该知道上述这三类,是属于一门叫做数据结构和算法中涉及到的内容

数组:一段地址连续的存储单元在这里插入图片描述

上述这这一张图片也是比较经典的数组存储的状态,从上上面的图片可以看出

数组的优点:查询速度很快(下标知道,使用数学表达式即可找到目标)
缺点:添加/删除数据的速度很慢(涉及到数据位移的情况,若是中间某一个数据进行插入,后面所有数据下标都需要+1,以此来满足
对于元素的添加操作)

链表:存储一个数据和下一个数据的存储地址

在这里插入图片描述

从上述图片的特点我们可以看出部分链表的特点和优点
链表的优点随机存储和删除数据效率很高(不存在考虑数组当中的下标和移动的问题)
缺点查询效率较低(链表当中不存在下标的缘故,最优的情况是在第一个节点就找了我们需要的节点O1,最差的情况就是最后一个数据中
找到我们需要的数据On)

注:此处的O1、On指的是数据结构当中的时间复杂度

下面我们进行今天的重要内容HashMap的部分功能实现

1、HashMap的使用

HashMap<Integer,String> hashmap = new HashMap<>();
hashmap.put(1,"zhangsan");
//put方法代表向HashMap中添加数据
//添加我们指定的数据Integer、String类型
//这也可以说是向HashMap集合当中添加key,value值
hashmap.get(1);
//而get功能呢 顾名思义就是根据key值来获取与其对应的value值

2、关于添加的数据是添加在那段内存当中?(添加)

首先我们需要知道在jdk包中的Object类中有一个叫做hashCode的这么一个类(返回一个hashCode值)

int hash = "name".hashCode();//第一步得到其hash值
int index = hash%7;//第二步做取余的运算 而7相当于是存储的长度
//假设最后的index的值为2,那么就要把数据存储到这个索引为2的位置上

3、查找数据

int hashCode = "name".hashCode();//获取name的hashCode值
int index = hash%7;//取余数
String value = data[2];//取出第二个位置上的value值取出来

可能发生的问题:
1、哈希冲突:这个名字具体指的是,最后得到的两个数据的index的值相同的,映射到了同一个位置上
答:数组中存放的是一个节点的数据结构,节点拥有next属性,若hash冲突了,单链表进行存放(挂载到同一个位置上,利用链表的特点),取的方式也是同理,遍历链表查询
2、若数组存储满了应该如何操作:
答:和ArrayList(动态数组:可以根据存储的大小进行自动扩容)进行扩容,重新映射
3、由于直接使用hashCode值的话,hash冲突的概率会很大
答:在JDK包中HashMap上有一个hash的方法,再在上面对其hash值进行修改运算方式(使用一个不容易重复的运算方式即可)

在这里插入图片描述
后面有一个或者两个目标的为挂载的对象(在hash冲突之后,key值相同,value值不同)

手写HashMap原码
上述内容简单的描述了HashMap的一些情况,接下来就用最少的代码对其进行实现
下面先创建一个关于Entry的类

    public static class Entry<K,V>{


        int hash;//key对应哈希值
        K key;//存放的key
        V value;//存放的value
        Entry<K,V> next;
        /*
        在hash值相同的时候,会出现同一个位置上面已近存在元素了
        故,后进来的新元素作为指向之前存放的元素,作为表头
        总结:添加新元素时,添加在表头
       */



        public Entry(int hash, K key, V value, Entry<K, V> next) {//构造方法
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
    }

上面定义了Entry类的基本定义有了,下面看下 HashMap 类中需要哪些属性? HashMap 类的定义如下图

 private static final int DEFAULT_INITIAL_CAPACITY = 16;
    //默认容量

    private static final float DEFAULT_FACTOR = 0.75f;
    //默认加载因子

    private Entry[] table;
    //存放元素的数组

    private int size;
    //数组中元素个数

下面是关于hashmap的无参构造

 public hashmap_1222() {
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        //默认创建数组大小为16
        size = 0;
        //元素个数为0
    }

put方法(添加) ——详细请看注释

public V put(K key,V value)
    {
        if(key ==null)
        {
            throw new RuntimeException("key is null");
        }

        int hash = hash(key.hashCode());

        int index = indexFor(hash,table.length);
        //用key和数组大小做一次映射,得到其位置


        Entry<K,V> entry = table[index];

        //不为空 相同key的情况
        while(entry !=null)
        {
            /**
             * 先比较hash值是否相同,再比较对象是否相同,或者比较equals方法
             * 如果相同,说明存在一个同样的key,这个时候需要把旧的值转换为新的值,同时返回(旧)value
             */
            if((entry.hash ==hash) &&(key==entry.key ||key.equals(key)))
            {
                    V oldValue = entry.value;
                    entry.value = value;
                    return oldValue;
            }
            entry = entry.next;
        }

        //没有value,有key,同时和进来的key相同(保存新的value)

        Entry<K,V> next = table[index];
        /**
         * next可能为空,也可能不为空,不管最后是否为空,
         * next都需要作为新元素的下一个节点
         * 然后新元素保存在index上
         */
        table[index] = new Entry<>(hash,key,value,next);

        //当数组容量达到总容量75%,需要对其进行扩容处理(前面提到的负载因子)
        if(size++ >= (table.length*DEFAULT_FACTOR))
        {
            resize();
        }
        return null;
    }

下面的hash方法是照搬HashMap原码的(由于我是1.8的版本的,不同版本上的hash方法会有一些出入)

  static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    static int indexFor(int h ,int length)
    {
        /**
         * 逻辑与比取余的方式运算更好
         */
        h = h > 0 ? h:-h;//避免出现负数
        return h % length;
    }

扩容(容量到达75%进行扩容)


    private  void resize()
    {
        //新建一个数组(这里设定为原来数组的两倍)
        int newCapacity = table.length * 2;

        Entry [] newTable = new Entry[newCapacity];

        Entry[]src =  table;
        
        /**
         * 遍历旧数组,重新映射到新数组上面
         */
        for (int i = 0; i < src.length; i++) {
            Entry<K,V> e = src[i];
            //获取旧数组中的元素

            //释放旧的元素
            src[i]=null;

            //由于e是一个节点,属于链表,可能有多个节点,循环遍历
            while(e!=null)//没有数据,无法进行遍历
            {
                //把e的下一个节点数据进行保存
                Entry<K,V>  next = e.next;

                //e在当前节点进行新的数组的映射
                int i1 = indexFor(e.hash,newCapacity);

                //newTable的位置上可能为null,可能不为null
                //不管是否为null 都需要作为e的下一个节点
                e.next = newTable[i];

                //e的下一个节点同理
                e = next;
            }
            //所有节点映射到新的数组上,新数组赋值给table
        table = newTable;
        }
        
    }

get方法——详细请看注释

 public V get(K key)
    {
        if(key ==null)
        {
            throw new RuntimeException("key is null");
        }

        //对于key求其hash值
        int hash = hash(key.hashCode());

        //用hash值进行映射,得到应该去那个位置上去取数据
        int index = indexFor(hash, table.length);

        /**
         * 把index位置元素保存下来进行遍历
         * 因为e是一个链表,我们需要进行遍历
         * 找到和key相等的那个Entry,并返回value
         */

        Entry<K,V> e = table[index];
        while(e!=null)
        {

            //比较hash值是否相同
            if(hash == e.hash &&(key == e.key||key.equals(e.key)))
            {
                return e.value;
            }

            //若不相同,则找一下个
            e = e.next;
        }
        return null;
    }

下面对其进行测试:
在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值