手写HashMap JDK1.7(无红黑树)

发布时间 2023-05-02 23:11:42作者: 蔡徐坤1987
public interface MyMap <K,V>{

    V get(K k);
    V put(K k, V v);
    int size();
    V remove(K k);
    boolean isEmpty();
}
package main.java.com.hashmap;

public class MyHashMap <K, V> implements MyMap<K, V>{

    final static int DEFAULT_CAPACITY = 16;
    final static float DEFAULT_LOAD_FACTORY = 0.75f;

    private  int capacity;
    private float loadFactor;
    private int size = 0;

    Entry<K,V>[] table;

    public MyHashMap() {
        this(DEFAULT_CAPACITY,DEFAULT_LOAD_FACTORY);
    }

    public MyHashMap(int capacity, float loadFactor){
        this.capacity = upperMinPowerOf2(capacity);     //获取为2的幂次方的容量大小
        this.loadFactor = loadFactor;                  //加载因子,用于扩容,本次实现中尚未用到该字段
        this.table = new Entry[capacity];
    }

    private int upperMinPowerOf2(int capacity) {
        int power = 1;
        while (power <= capacity){
            power *= 2;
        }
        return power;
    }

    @Override
    public V get(K k) {
        int index = k.hashCode() % table.length;
        Entry<K, V> current = table[index];
        //遍历链表直至找到相同的hashcode
        while(current != null){
            if(current.k == k){
                return current.v;
            }
            current = current.next;
        }
        return null;
    }

    @Override
    public V put(K k, V v) {
        // 通过k换算hashcode取模绑定数组位置
        int index = k.hashCode() % table.length;
        Entry<K,V> current = table[index];
        //如果index下没有绑定元素,则直接头插法
        if(current == null){
            table[index] = new Entry<K,V>(k,v,null);
            size ++;
            return null;
        }
        //如果index下有元素
        if(current != null){
            while(current != null){
                if(current.k == k){
                    V oldValue = current.v;
                    current.v = v;
                    return oldValue;
                }
                current = current.next;
                table[index] = new Entry<K,V>(k,v,table[index]);
                size ++;
                return null;
            }
        }
        return null;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public V remove(K k) {
        int index = k.hashCode() % table.length;
        Entry<K, V> current = table[index];
        V result = null;
        Entry<K,V> pre = null;

        while(current != null){
            if(current.k == k){
                result = current.v;
                size --;
                if(pre!=null){
                    pre.next = current.next;
                }else{
                    table[index] = current.next;
                }
                return result;
            }
            //向下遍历
            pre = current;
            current = current.next;
        }
        return null;
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    class Entry<K, V>{
        K k;
        V v;
        Entry<K,V> next;

        public Entry(K k, V v, Entry<K, V> next) {
            this.k = k;
            this.v = v;
            this.next = next;
        }
    }
}
package main.java.com.hashmap;

public class HashMapTest {

    public static void main(String[] args) {
        MyHashMap<Integer, Integer> hashMap = new MyHashMap<>();
        hashMap.put(1,101);
        hashMap.put(2,202);
        hashMap.put(3,303);
        hashMap.put(1,111);

        int[] keys = new int[]{1,2,3};
        for (int i = 0; i < keys.length; i++) {
            System.out.println(keys[i]+"---"+hashMap.get(keys[i]));
        }

        hashMap.remove(1);
        hashMap.remove(3);
        System.out.println("hashMap size:"+hashMap.size());
        System.out.println(1+": "+hashMap.get(1));
        System.out.println(3+": "+hashMap.get(3));
        System.out.println(2+": "+hashMap.get(2));
    }
}

理解:

HashMap在JDK1.8之前是数组+链表的数据结构,比如数组长度是10 (默认长度是1<<4 16), 在put存储的时候会将key换算成hashcode,并取模length的余数 0~9之间,以便于分散在数组的各个下标中;

而1%10和 11%10 的余数都是1,这种情况下会产生哈希碰撞也叫哈希冲突,1.7中才用单向链表存储这类数据,并使用头插法,查到链表的头部,而其余的元素向下移动一位;

当map中元素过多,且大于数组的75%时,数组会扩容 2的N次幂,此时,比如数组长度由10变成了20,那么取模的范围变成0~19,所有10以上的元素位置发生了变化重新计算位置也就是再哈希,其map在重新计算位置时,查询或者插入的效率会有抖动。

JDK1.8以后,hashmap是数组+链表+红黑树,对查询效率,时间复杂度进行了优化,也就是说 在 n>8 时 查询效率由n/2 优化为了 olog(n),之所以还有8位的链表,是因为n<8时,n/2 的效率是大于 olog(n)的