手写简单实例MyHashMap

MyHashMap

通过一个简单的例子来理解HashMap源码实现。

package com.cskaoyan.hashmap;

数组 + 链表
import java.util.LinkedHashSet;
import java.util.Set;

/*
API:
    void put(K key, V value)//添加键值对
    V get(K key)//获取键所对应的值
    void delete(K key)//删除key
    boolean contains(K key) //判断是否包含key
    void clear() //清空map
    boolean isEmpty() //判断是否为空
    int size() //map大小
    Set<K> keys() //返回所有key的集合
 */
public class MyHashMap<K, V> {
    
    
    private static final int DEFAULT_CAPACITY = 16;//默认容量  0 100000000000000  2的30吃饭
    private static final int MAX_CAPACITY = 1 << 30;//最大容量,取2的次幂效率高(复习位运算)
    private static final double DEFAULT_LOAD_FACTOR = 0.75;//经验指数,没有为什么。大数据试验
    
    // 字段
    private Entry<K, V>[] table;
    //创建一个节点数组,含义是 table数组里每个位置的对象是entry节点,每个entry节点里有键值对和hash值,还有一个后驱节点。
    private int size; //大小
    private double loadFactor; //装填因子,默认为0.75
    private int threshold; // 阈值,达到阈值就要开始扩容
    /**
    *如何理解这个Entry? 
    *	1.了解HashMap构造,HashMap底层是数组加向链表。数组支持随机访问,故HashMap查找数组位置为	*	0(1),然后再到链表上去差找为0(1)   常量级。  
    *	2.hash值是通过hash算法计算出的随机数,具有高度保密性。(撞库攻击)
    *	3.构造方法里的this是调用其他构造方法的意思。
    */
    private static class Entry<K, V> {
    
    
        K key;
        V val;
        int hash;
        Entry<K, V> next;

        Entry(K key, V val, int hash) {
    
    
            this.key = key;
            this.val = val;
            this.hash = hash;
        }

        @Override
        public String toString() {
    
    
            return key + "=" + val;
        }
    }

    // 构造方法
    public MyHashMap() {
    
    
        this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

    public MyHashMap(int initialCapacity) {
    
    
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    @SuppressWarnings("unchecked")
    public MyHashMap(int initialCapacity, double loadFactor) {
    
    
        // initialCapacity: 大概存储键值对的个数
        if (initialCapacity <= 0) {
    
    
            throw new IllegalArgumentException("initialCapacity=" + initialCapacity);
        }
        if (loadFactor <= 0) {
    
    
            throw new IllegalArgumentException("loadFactor=" + loadFactor);
        }
        this.loadFactor = loadFactor;
        int cap = (int) (initialCapacity / loadFactor);
        // 计算大于等于n的最小2的次幂
        int n = tableLength(cap);
        table = new Entry[n];
        threshold = (int) (table.length * loadFactor);//阈值等于容量乘以装载因子
    }

    // 计算大于cap的最小的2^n (见文件:计算大于cap的最小的2的次幂)
    private int tableLength(int cap) {
    
    
        if (cap >= MAX_CAPACITY) return MAX_CAPACITY;
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return n + 1;
    }

    /**
     * 添加键值对,如果key存在,就更新它对应的值
     *
     * @param key   键
     * @param value 值
     * @return 如果key不存在, 返回null, 如果key存在,返回原来的值
     */
    public V put(K key, V value) {
    
    
        if (key == null || value == null) {
    
    
            throw new IllegalArgumentException("Key or value can not be null");
        }
        int hash = hash(key);
        int idx = indexFor(hash, table.length);
        // 遍历链表
        for (Entry<K, V> e = table[idx]; e != null; e = e.next) {
    
    
            if (hash == e.hash && ((key == e.key) || key.equals(e.key))) {
    
    
                // key存在
                V oldValue = e.val;
                e.val = value;
                return oldValue;
            }
        }
        
        // key不存在, 在头结点添加键值对。
        addEntry(key, value, hash, idx);
        return null;
    }

    private void addEntry(K key, V value, int hash, int idx) {
    
    
        // 判断是否需要进行扩容
        if (size == threshold) {
    
    
            if (table.length == MAX_CAPACITY) {
    
    
                // 也可以抛出异常
                threshold = Integer.MAX_VALUE;
            } else {
    
    
                grow(table.length << 1); //两倍扩容
                idx = indexFor(hash, table.length);
            }
        }
        // 添加键值对
        Entry<K, V> entryToAdd = new Entry<>(key, value, hash);
        entryToAdd.next = table[idx];//头插   
        table[idx] = entryToAdd;
        size++;
    }

    @SuppressWarnings("unchecked")
    private void grow(int newCapacity) {
    
    
        Entry<K, V>[] newTable = new Entry[newCapacity];
        //遍历table头插到新数组
        for (Entry<K, V> e : table) {
    
    
            while (e != null) {
    
    
                Entry<K, V> next = e.next;
                int idx = indexFor(e.hash, newCapacity);
                e.next = newTable[idx];
                newTable[idx] = e;
                e = next;
            }
        }
        table = newTable;//吧新数组的地址赋值给table
        threshold = (int) (table.length * loadFactor);
    }

    private int hash(K key) {
    
    
        int h = key.hashCode();
        return (h >> 16) ^ (h << 16);
    }

    private int indexFor(int hash, int length) {
    
    
        return hash & (length - 1);(见附件2)
    }

    /**
     * 根据key获取值
     *
     * @param key 指定的key
     * @return 对应的值, 如果key不存在返回null
     */
    public V get(K key) {
    
    
        if (key == null) {
    
    
            throw new IllegalArgumentException("Key cannot be null.");
        }   jdk可以存null   null建或者值  ?  可以同时吗?
        int hash = hash(key);
        int idx = indexFor(hash, table.length);
        for (Entry<K, V> e = table[idx]; e != null; e = e.next) {
    
    
            if (hash == e.hash && ((key == e.key) || (key.equals(e.key)))) {
    
    
                return e.val;
            }
        }
        return null;???我就说JDK
    }

    /**
     * 根据指定的key, 删除键值对
     *
     * @param key 指定的key
     * @return key对应的value, 如果key不存在返回null
     */
    public V delete(K key) {
    
    
        if (key == null) {
    
    
            throw new IllegalArgumentException("Key cannot be null.");
        }
        int hash = hash(key);
        int idx = indexFor(hash, table.length);
        Entry prev = null;//定义一个前驱节点
        for (Entry<K, V> e = table[idx]; e != null; e = e.next) {
    
    
            if (hash == e.hash && ((key == e.key) || (key.equals(e.key)))) {
    
    
                V deleteValue = e.val;
                if (prev == null) table[idx] = e.next; // // 删除头结点
                else prev.next = e.next;
                size--;
                return deleteValue;
            }
            prev = e;
        }
        return null;
    }

    /**
     * 判断哈希表中是否包含指定的键
     *
     * @param key 指定的键
     * @return 如果包含返回true, 否则返回false
     */
    public boolean contains(K key) {
    
    
        if (key == null) {
    
    
            throw new IllegalArgumentException("Key cannot be null.");
        }
        int hash = hash(key);
        int idx = indexFor(hash, table.length);
        for (Entry<K, V> e = table[idx]; e != null; e = e.next) {
    
    
            if (hash == e.hash && ((key == e.key) || (key.equals(e.key)))) {
    
    
                return true;
            }
        }
        return false;
    }

    public int size() {
    
    
        return size;
    }

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

    public void clear() {
    
    
        ///foreach??? 我记得老师好像是这么说的,大问题
        for(int i = 0; i < table.length; i++) {
    
    
            table[i] = null;
        }
        size = 0;
    }

    /**
     * 获取哈希表中键的集合
     *
     * @return 哈希表中键的集合
     */
    public Set<K> keys() {
    
    
        Set<K> set = new LinkedHashSet<>();
        for (Entry<K, V> e : table) {
    
    
            while (e != null) {
    
    
                set.add(e.key);
                e = e.next;
            }
        }
        return set;
    }

    @Override
    public String toString() {
    
    
        StringBuilder sb = new StringBuilder("{");
        for (Entry<K, V> e : table) {
    
    
            while (e != null) {
    
    
                sb.append(e).append(", ");
                e = e.next;
            }
        }
        if (!isEmpty()) sb.delete(sb.length() - 2, sb.length());
        return sb.append("}").toString();
    }

    public static void main(String[] args) {
    
    
        MyHashMap<String, String> map = new MyHashMap<>();
        /*System.out.println(map);
        System.out.println(map.size());
        System.out.println(map.isEmpty());*/

        map.put("王宝强", "马蓉");
        map.put("刘强东", "章泽天");
        map.put("文章", "马伊利");
        map.put("贾乃亮", "李小璐");

       /* System.out.println(map);
        System.out.println(map.size());
        System.out.println(map.isEmpty());*/

        /*System.out.println(map.put("谢霆锋", "王菲"));
        System.out.println(map);
        System.out.println(map.put("谢霆锋", "张柏芝"));
        System.out.println(map);*/
        // System.out.println(map.put(null, "A"));
        // System.out.println(map.put("A", null));

        // V get(K key)
        // System.out.println(map.get(null));
        /*System.out.println(map.get("刘强东"));
        System.out.println(map.get("邓超"));*/

        // V delete(K key)
        // System.out.println(map.delete(null));
        /*System.out.println(map.delete("刘强东"));
        System.out.println(map);
        System.out.println(map.size());*/

        /*System.out.println(map.delete("邓超"));
        System.out.println(map);
        System.out.println(map.size());*/

        // contains(K key)
        // System.out.println(map.contains(null));
        /*System.out.println(map.contains("文章"));
        System.out.println(map.contains("邓超"));*/

        System.out.println(map.keys());
        map.clear();
        System.out.println(map.keys());
        System.out.println(map.size());
    }
}

附件1:计算大于cap的最小2的次幂

| (或运算):只要有1 结果就是1,0 | 0 = 0

&( 与运算):同时为1结果才为1,否则为0.

^(异或运算符) :0^0 =0, 01=1,10=1,1^1=0 (相同为0 不同为1)

例如:cap = 100 ,那么 n = 99;转换为二进制为:0000 0000 0110 0011,执行第一步:n |= n>>>1,

先把n右移1位:0000 0000 0011 0001,然后进行 | 运算。

0000 0000 0110 0011

0000 0000 0011 0001

0000 0000 0111 0011

然后执行第二步:

0000 0000 0111 0011

0000 0000 0001 1100

0000 0000 0111 1111

然后第三步:

0000 0000 0111 1111

0000 0000 0000 0111

0000 0000 0111 1111

循环下去:得到0000 0000 0111 1111 = 127

127+1 = 128;

所以 大于100的最小的2的次幂是 128

附件2:hash & (length -1)

比如hash = 100;length -1 = 63;(只写低八位)

hash = 0110 0100

length -1 = 0011 1111

​ = 0010 0100 (36)

所以 hash 为 100的值 应该存在索引36处。

附件3:HashMap在并发场景中可能出现死循环

1

0000 0000 0000 0111

0000 0000 0111 1111

循环下去:得到0000 0000 0111 1111 = 127

127+1 = 128;

所以 大于100的最小的2的次幂是 128

附件2:hash & (length -1)

比如hash = 100;length -1 = 63;(只写低八位)

hash = 0110 0100

length -1 = 0011 1111

​ = 0010 0100 (36)

所以 hash 为 100的值 应该存在索引36处。

附件3:HashMap在并发场景中可能出现死循环

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ji8JKith-1585706032777)(C:\Users\86133\Desktop\markdown\王道笔记\HashMap在并发场景中可能会出现死循环.png)]

猜你喜欢

转载自blog.csdn.net/qq_31702655/article/details/105239069