重复造轮子(1) --- 手写HashMap

简介: 重复造轮子(1) --- 手写HashMap

手写Hashmap代码:


首先我们需要了解jdk里面hashmap的源码部分,如果对于hashmap的源码有了解的朋友,就应该会知道hashmap里面的这段代码:


网络异常,图片无法展示
|


是的,这是一段经典的node节点代码,我们可以吧hashmap看成一个存储单向链表的数组,然后每个链表里面的元素都是包含有相应的k v值。


参考源码的内容,我写了一小段自己关于hashmap的设计代码:


首先是一个父类接口的设计:


/**
 * 作者:idea
 * 日期:2018/6/25
 * 描述:
 */
public interface MyMap<K,V> {
    public V get(K key);
    public V put(K key,V value);
    interface IEntry<K,V>{
        public K getKey();
        public V getValue();
    }
}
复制代码


\

设计好了父类接口之后,编写一个MyHashMap类实现这个接口即可了。


在MyHashMap类里面需要编写相应的初始化参数:


\

/**
 * 作者:idea
 * 日期:2018/6/25
 * 描述:
 */
public class MyHashMap<K,V> implements MyMap<K,V> {
    private static final int DEAFULT_SIZE=1<<4;  //16
    private static final float DEFAULT_LOAD_SIZE=0.76f; //负载因子
    private int deaultInitSize;  //默认大小
    private float deaultLoadSize; //默认负载因子
    private int entrySize; //默认的entry数组数量
    private Entry<K,V>[] table=null;
    public MyHashMap(){
        this(DEAFULT_SIZE,DEFAULT_LOAD_SIZE);
    }
    //采用了门面模式
    //仔细观察下,你会发现,其实这里使用到了“门面模式”。这里的2个构造方法其实指向的是同一个,但是对外却暴露了2个“门面”!
    public MyHashMap(int deaultInitSize,float deaultLoadSize){
        this.deaultInitSize=deaultInitSize;
        this.deaultLoadSize=deaultLoadSize;
        table=new Entry[this.deaultInitSize];
    }
复制代码


接下来便是核心的get和put方法了:


get方法的实现如下所示:


@Override
    public V get(K key) {
        int index=hash(key)&(deaultInitSize-1);
        if(table[index]==null){
            return null;
        }else{
            Entry<K,V> e=table[index];
            do{
                if(e.getKey().equals(key)){
                    return e.getValue();
                }
                e=e.next;
            }while(e!=null);
        }
        return null;
    }
复制代码


\

关于put部分方法的实现如下所示:从函数的实现可以看出resize是一个比较消耗性能的部分。


@Override
    public V put(K key, V value) {
        if(entrySize>=deaultInitSize*deaultLoadSize){
            //扩容操作
            resize(deaultInitSize*2);
        }
        int index=hash(key)&(deaultInitSize-1);
        if(table[index]==null){
            table[index]=new Entry<K,V>(key,value,null);
            entrySize++;
        }else{
            Entry<K,V> tempEntry=table[index];
            Entry<K,V> e=tempEntry;
            while(e!=null){
                if(key ==e.getKey()||e.getKey().equals(key)){
                    e.setValue(value);
                    break;
                }
                e=e.next;
            }
            //这里面需要联想一下链表那边的引用,next指示了原有的链表地址,所以第三个参数是tempentry
            table[index]=new Entry<K,V>(key,value,tempEntry);
            entrySize++;
        }
        return value;
    }
复制代码


\

在上述的截图里面,读者会看到一个叫做hash的函数,这是我自己封装的一个关于hash值计算的函数,可以通过输入的key自动算出相应的hash值。以及一个rehash函数和resize函数都是用于提供扩容数组的功能。


关于hash函数里面的>>>和^操作符,可能大多数人在平时编程里面接触的不多,在这里我简单提及一下:


<<          左移运算符,num << 1,相当于num乘以2

>>          右移运算符,num >> 1,相当于num除以2

>>>         无符号右移,忽略符号位,空位都以0补齐


如:


a=00110111,则a>>2=00001101,a>>>2=00001101,

b=11010011,则b>>2=11110100,b>>>2=00110100

 

//自定义的hash算法
    private int hash(Object key){
        int hashcode= Objects.hashCode(key);
        hashcode^=(hashcode>>>20)^(hashcode>>>12);
        return hashcode^(hashcode>>>7)^(hashcode>>>4);
    }
    //重新扩容,核心在rehash里面
    private void resize(int i){
        Entry[] newTable=new Entry[i];
        this.deaultInitSize=i;
        this.entrySize=0;
        System.out.println("newTable size:--------->"+newTable.length);
        rehash(newTable);
    }
    //扩容算法的核心部分
    private void rehash(Entry<K,V>[] newTable){
        List<Entry<K,V>> entryList=new ArrayList<Entry<K,V>>();
        for(Entry<K,V> e:table){
            while(e!=null) {
                entryList.add(e);
                e=e.next;
            }
        }
        if(newTable.length>0){
            table=newTable;
        }
        for (Entry<K,V>  entry : entryList) {
            put(entry.getKey(),entry.getValue());
        }
    }
复制代码


\

如果读者有兴趣的话,可以去看看jdk源码里面关于hashcode的生成函数:


网络异常,图片无法展示
|


在Objects类里面,点开源码追溯hashCode函数,你会看到以下内容:(jdk1.8环境里面)


网络异常,图片无法展示
|


这个时候会看到一个叫做native的声明修饰符。


关于native这个关键字而言,说明修饰的方法是一个原生态的函数,方法的对应实现并不是在当前文件中,而是在别的C/C++文件里面。


由于在java语言里面,不允许直接对操作系统的底层进行直接的访问和操作,于是在jdk里面提供了一个JNI接口用于供java代码调用其他语言编写的代码内容。


最后便是相应的测试代码了:


/**
 * 作者:idea
 * 日期:2018/6/25
 * 描述:
 */
public class Test {
    public static void main(String[] args) {
        MyHashMap mh=new MyHashMap();
        for(int i=0;i<10000;i++){
            mh.put(i,i);
        }
        for(int i=0;i<10000;i++) {
            if (mh.get(i) != null) {
                System.out.println(mh.get(i));
            }
        }
    }
}
复制代码


\

测试成功之后,相应的输出如下所示:


网络异常,图片无法展示
|


当然,自己写的hashmap和jdk里面的源码差距还是有些大的,但是自己亲自手写过一遍集合框架的内容之后,确实能提升自己一些能力。

目录
相关文章
|
2月前
|
Java
【Java集合类面试二十三】、List和Set有什么区别?
List和Set的主要区别在于List是一个有序且允许元素重复的集合,而Set是一个无序且元素不重复的集合。
|
2月前
|
Java
【Java集合类面试十四】、HashMap是如何解决哈希冲突的?
HashMap解决哈希冲突的方法是通过链表和红黑树:当链表长度超过一定阈值时,转换为红黑树以提高性能;当链表长度缩小到另一个阈值时,再转换回链表。
|
2月前
|
Java
【Java集合类面试二十二】、Map和Set有什么区别?
该CSDN博客文章讨论了Map和Set的区别,但提供的内容摘要并未直接解释这两种集合类型的差异。通常,Map是一种键值对集合,提供通过键快速检索值的能力,而Set是一个不允许重复元素的集合。
|
2月前
|
存储 缓存 Java
如何删除 HashMap 中的重复元素?—— 99% 的人不知道的第 3 种实现思路
如何删除 HashMap 中的重复元素?—— 99% 的人不知道的第 3 种实现思路
36 0
|
4月前
|
存储 Java 数据处理
Java Set:无序之美,不重复之魅!
【6月更文挑战第17天】Java的Set接口是集合框架的一部分,确保元素不重复且无序。它基于hashCode和equals检查重复,添加元素时无视索引。例如,HashSet不会保存重复项,即使尝试添加,如示例所示,输出仍显示3个唯一元素。无序性利于高效查找,适合需要唯一约束但不关注顺序的情景。Set简化了数据处理,提升了性能。
26 0
|
存储 人工智能 Java
如何用Java找出两个List中的重复元素,读这一篇就够了
在Java编程中,我们经常需要找出两个列表(List)中的重复元素。在本文中,我们将探讨三种方法来实现这一目标。
|
11月前
|
人工智能 算法 机器人
List 函数排序操作,用对方法事半功倍!
作为一名程序员,以下这些场景你肯定不陌生, 1.数据分析和处理:在处理大量数据时,需要对数据进行排序以进行进一步的分析和处理。例如,在市场调研中,可能需要按照客户的购买频率对客户列表进行排序,以确定哪些客户最有可能购买产品或服务。 2.报表生成:在生成报表时,往往需要按照特定的顺序对数据进行排列,以便清晰地展示数据。例如,在制定销售报告时,可能需要按照销售额对产品进行排序,以了解哪些产品的销售额最高。 ......
|
存储 Java 程序员
Java集合List介绍和去重方案
Java集合List介绍和去重方案
98 0
List 集合遍历过程中删除元素。这个坑踩一遍就不要再踩了!
List 集合遍历过程中删除元素。这个坑踩一遍就不要再踩了!
HashMap,你是怎么做到的Key重复?
HashMap,你是怎么做到的Key重复?