【java集合】自己实现简易的HashMap

面试中经常会被问到HashMap的原理,所以自己尝试实现了一个极简版的,应该可以应付一般的考官了微笑,但是功能、性能方面肯定远不及官方jdk的了。

先来看看定义的IMap接口:

public interface IMap<K, V> {

    V get(K key);

    V put(K key, V value);

    int size();

    interface Entry<K, V>{

        K getKey();

        V getValue();
    }
}

下面是HashMap的实现:

import java.util.ArrayList;
import java.util.List;

public class HashMap<K, V> implements IMap<K, V> {

    private static int default_array_len = 16;    //hashmap里数组默认长度

    private static double default_load = 0.75;    //负载因子

    private Entry<K, V>[] array = null;           //hashmap的数组

    private int size = 0;                         //(链头)元素的个数

    public HashMap() {
        this(default_array_len, default_load);
    }

    public HashMap(int array_len, double load) {
        this.default_array_len = array_len;
        this.default_load = load;
        array = new Entry[default_array_len];
    }

    @Override
    public V get(K key) {
        int index = getIndex(key);
        Entry<K, V> entry = this.array[index];
        return getEntryValue(entry, key);
    }

    //递归得到与参数key一致的entry的value
    private V getEntryValue(Entry<K, V> entry, K key){
        if (null == entry)
            return null;
        if (key == entry.getKey() || key.equals(entry.key))
            return entry.value;
        return getEntryValue(entry.next, key);
    }

    @Override
    public V put(K key, V value) {

        //如果array元素个数大于array长度 * load(负载因子),则需要(成倍的)扩容:
        if (size > default_array_len * default_load)
            doubleSize();

        //1.根据key用定义的算法算出key对应的数组下标
        int index = getIndex(key);
        //2.判断数组对应index位置是否已有元素
        Entry<K, V> entry = array[index];
        if (null != entry){
            //一开始我想到的是逐层寻找链尾节点,再往尾节点后补上新节点,但这样效率会比较低
            //把新元素的指针指向旧元素,然后再把旧元素所在的数组区域给占了,这样做就旧节点既不会被GC,复杂度也低了很多
            array[index] = new Entry<>(key, value, entry, index);
        } else {
            array[index] = new Entry<>(key, value, null, index);
            size++;
        }
        return array[index].getValue();
    }

    /**
     * 哈希经典算法之一:取模得到数组下标(会造成大量元素的冲突,有空看看jdk怎么实现的,
     * 它几乎不会产生冲突,也就是说数组中每个链表的长度为1)
     * hashCode的值可能为负数,为避免下标越界,要取绝对值
     * @param key
     * @return
     */
    private int getIndex(K key){
        int index = key.hashCode() % default_array_len;
        return index > 0 ? index : -index;
    }

    //返回数组的(链头)元素的个数
    @Override
    public int size() {
        return this.size;
    }

    /**
     * 数组扩容算法
     * 需要把原来的数组里面的元素再散列
     * 因为我们使用的哈希算法是取模,扩容后用相同的key得出的数组下标就未必相同了
     */
    private void doubleSize(){
        Entry<K, V> newArray[] = new Entry[default_array_len * 2];

        List<Entry<K, V>> entryList = new ArrayList<>();

        //遍历全部元素,并将它们加入到entryList
        for (int i=0; i<default_array_len; i++){
            if (null == array[i])
                continue;
            findEntry(array[i], entryList);
        }
        //再散列
        hashAgain(newArray, entryList);
    }

    //递归查找出entry并加入到list
    private void findEntry(Entry<K, V> entry, List<Entry<K, V>> entryList){
        if (null == entry.next)
            entryList.add(entry);
        else {
            entryList.add(entry);
            findEntry(entry.next, entryList);
        }
    }

    //再散列
    private void hashAgain(Entry<K, V>[] newArray, List<Entry<K, V>> entryList){
        this.size = 0;
        default_array_len *= 2;

        this.array = newArray;

        for (Entry<K, V> entry : entryList){
            put(entry.key, entry.value);
        }
    }

    class Entry<K, V> implements IMap.Entry<K, V>{

        K key;
        V value;
        Entry<K, V> next;   //指向下一个Entry的指针
        int index;          //该Entry在数组中的下标

        public Entry(K key, V value, Entry<K, V> next, int index) {
            this.key = key;
            this.value = value;
            this.next = next;
            this.index = index;
        }

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

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

测试代码:

public class Main {

    public static void main(String[] args) {
        IMap<String, Object> map = new HashMap<>();
        for (int i=0; i<10; i++){
            map.put("key"+i, "lon"+i);
        }

        for (int i=0; i<10; i++){
            System.out.println(map.get("key"+i));
        }

    }
}

运行结果:

lon0
lon1
lon2
lon3
lon4
lon5
lon6
lon7
lon8
lon9

顺便说一下,判断HashMap性能的指标有2个:

1.散列越均匀,性能越好

2.冲突越少越好,也就是通过哈希算法算出的索引被占用的概率越少越好

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值