手写HashMap

 

 

 

 jdk1.8以前为 HashMap由hash函数+数组+单链表实现(本次实现)。

jdk 1.8版本之后的java中HashMap是数组+链表+红黑树实现的。在链表长度<8的时候都是一样的,当链表长度到达8时,该链表会转换成红黑树来存储节点。

结构图:

 

 

                                                                  (图片来自网络)

 

如图:0-6表示数组长度,纵向为单链表,每一列的 hash(key) 得到的下标相同。

实现代码:

  1. 定义接口

 


public interface BaseMap<K,V> {

    public V put(K k, V v);

    public V get(K k);
}

 

 

/**
 * 节点的基类接口
 */
public interface BaseEntry<K,V> {
    public K getKey();//获取键

    public V  getValue(); //获取值
}

 

2.单链表实现

/**
 * 单链表的体现!
 * 
 */
public class Entry<K,V> implements  BaseEntry<K,V> {
    private K k;
     V v;
    Entry<K,V> next;//存在next指向下一个节点,形成链表

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

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

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

3.实现Map

public class MyHashMap<K,V> implements BaseMap<K,V> {
    //默认初始化长度
    private static final int DEFAULT_INITIAL_CAPACITY= 1<<4;
    //阈值比例(超过这个阈值长度*2)
    private static final float  DEFAULT_LOAD_FACTOR= 0.75F;
    //属性
    private int defaultLength ;//长度
    private double defaultAddFactor;//负载因子
    private int useSize;//使用数组位置的数量
    private int entryUseSize;//map中 entry的数量
    private Entry<K, V>[] table;//数组


       //构造方法 门面模式”。这里的2个构造方法其实指向的是同一个,但是对外却暴露了2个“门面”

        // 默认使用基本长度和负载因子
        public MyHashMap() {
            this(DEFAULT_INITIAL_CAPACITY,DEFAULT_LOAD_FACTOR);
        }

        // 自定义长度和负载因子。
        public MyHashMap(int defaultLength, double defaultAddFactor) {
            if (defaultLength < 0) {
                throw new IllegalArgumentException("数组异常");
            }
            if (defaultAddFactor <= 0 || Double.isNaN(defaultAddFactor)) {
                throw new IllegalArgumentException("因子异常");
            }
            this.defaultLength = defaultLength;
            this.defaultAddFactor = defaultAddFactor;
            table = new Entry[defaultLength];
     }


    /**
     * 使用每个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;
    }

    /**
     * 扩容i
     * 对于HashMap而言,如果频繁进行resize/rehash操作,是会影响性能的。
     *resize/rehash的过程,就是数组变大,原来数组中的entry元素一个个的put到新数组的过程
     * @param i
     */
    private void resize(int i) {
        Entry<K, V>[] newTable = new Entry[i];
        //改变数组大小
        defaultLength=i;
        entryUseSize=0;
        useSize=0;
        rehash(newTable);


    }

    //对新数组设置值
    private void rehash(Entry<K, V>[] newTable){
        //得到原来老map中的entry集合
        List<Entry<K, V>> entryList = new ArrayList<>();
        for (int i = 0; i < table.length; i++) {//遍历数组
            if (table[i] == null)
                continue;
            //遍历链表 添加到list
            Entry<K, V> entry = table[i];
            while (entry != null) {//遍历单链表
                entryList.add(entry);
                entry = entry.next;
            }
        }

        //添加到新的map中
        if (entryList.size() > 0) {
            table = newTable;//覆盖旧的引用
            for (Entry<K, V> entry : entryList) {
                //分离所有的entry
                if (entry.next != null) {
                    entry.next = null;
                }
                put(entry.getKey(), entry.getValue());//entryUseSize/useSize 重新计算
            }
        }

    }


    /**
     * 第一,要考虑是否扩容?
     *HashMap中的Entry的数量(数组以及单链表中的所有Entry)是否达到阀值?
     *第二,如果扩容,意味着新生成一个Entry[],不仅如此还得重新散列。
     *第三,要根据Key计算出在Entry[]中的位置,定位后,如果Entry[]中的元素为null,那么可以放入其中,如果不为空,那么得遍历单链表,要么更新value,要么形成一个新的Entry“挤压”单链表!
     * @param k
     * @param v
     * @return
     */
    @Override
    public V put(K k, V v) {
        V oldValue=null;
        if(useSize > defaultAddFactor * defaultLength){
            //扩容
            resize(2*defaultLength);
        }
        //计算出下标(数组中的位置)
        int index =getIndex(k,table.length);
        Entry<K, V> entry = table[index];
        Entry<K, V> newEntry = new Entry<>(k, v, null);
        if (entry == null) {
            table[index] = newEntry;//新值(新的位置)
            useSize++;//占用table中的一个位置
            ++entryUseSize;
        }else{//该位置已经被占用
            Entry<K, V> t = entry;
            //改值
            while (t != null) {//遍历单链表(查看是否有相同的key)
                if (t.getKey() == k || (t.getKey() != null && t.getKey().equals(k))) {//相同key 对应修改当前value 直接退出
                    oldValue=t.v;
                    t.v = v;//赋值
                    return oldValue;
                }
                t = t.next;
            }
            //添加值   不同key 添加到当前数组位置的链表
            table[index]= new Entry<>(k,v,entry);//同一位置添加值在尾端
            ++entryUseSize;
        }
        return oldValue;
    }

    @Override
    public V get(K k ) {
        //获取数组下标
        int index = getIndex(k, table.length);
        //获取当前链表
        Entry<K, V> entry = table[index];
        if (entry == null) {
            return null;
        }
        //循环单链表取值
        do{
            if (k == entry.getKey() || k.equals(entry.getKey())) {
                return entry.v;
            } else {
                entry = entry.next;
            }
        }while (entry != null);

        return null;
    }
}

4.测试

 public static void main(String[] args) {
        BaseMap<String,String>  map =  new MyHashMap<>();
        map.put("小明","10");
        System.out.println("old="+map.put("小明","20"));
        System.out.println("new="+map.get("小明"));
        /*for (int i = 0; i < 500; i++) {
            map.put("key"+i,"value"+i);
        }

        for (int i = 0; i < 500; i++ ) {
            System.out.println("key"+i+",value is :" +map.get("key"+i));
        }*/

    }

 

5.扩容影响性能,如果已知最大长度可以设置长度创建

     数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度为O(n),首先遍历链表,存在即覆盖,否则新增;对于查找操作来讲,仍需遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值