手写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];
    }

接下来便是核心的getput方法了:

get方法的实现如下所示:

扫描二维码关注公众号,回复: 1872429 查看本文章
    @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));
            }
        }

    }
}

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

 

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

猜你喜欢

转载自blog.csdn.net/Danny_idea/article/details/80908867