重复造轮子(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里面的源码差距还是有些大的,但是自己亲自手写过一遍集合框架的内容之后,确实能提升自己一些能力。

目录
相关文章
|
索引
源码分析系列教程(11) - 手写Map框架(基于LinkedList)
源码分析系列教程(11) - 手写Map框架(基于LinkedList)
44 0
|
6月前
|
搜索推荐 Java 索引
|
6月前
|
搜索推荐 Java 索引
|
6月前
|
搜索推荐 Java
|
8月前
|
Java
JavaSE——集合框架二(6/6)-(案例)补充知识:集合的嵌套(需求与分析、问题解决、运行测试)
JavaSE——集合框架二(6/6)-(案例)补充知识:集合的嵌套(需求与分析、问题解决、运行测试)
84 0
|
开发工具
代码重构之重复代码处理
介绍使用IDEA去重构重复的代码块
代码重构之重复代码处理
|
SQL 安全 Java
28个案例问题分析---007---在线人员逻辑反例--ThreadLocal、继承、索引失效、
28个案例问题分析---007---在线人员逻辑反例--ThreadLocal、继承、索引失效、
82 0
HashMap,你是怎么做到的Key重复?
HashMap,你是怎么做到的Key重复?
|
JSON 缓存 算法
工具分享--避免重复造轮子(一)
开源工具类库,建议收藏,小标题都带有官网链接
757 2
工具分享--避免重复造轮子(一)
|
开发框架 数据库
GoFrame代码优化:使用gconv类型转换 避免重复定义map
最近一直在研究 GoFrame 框架,经过一段时间的使用、总结、思考,发现确实不失为一款非常值得使用的企业级开发框架。
468 0