HashMap系列(二):自己写一个建议HashMap

MyHashMap

此章节是仿照已有的集合类自己实现一个,在保证原有类的精髓之下,尽可能的简化,只抽取主干方法进行实现

1 概述

对于HashMap的精髓我简单概述一下:

  • 数组长度设置为2的次幂,这个有两个好处:
    • 一是hash%length计算具体位置时可以用hash&(length-1)代替
    • 二是数组进行扩容时,一个链表上面的数据要么在原位置,要么在原位置+oldlength
  • 使用tableSizeFor这个函数即得到一个二的次幂长度,具体做法是使用位运算
  • 使用扰动函数:hash = hash ^ hash>>16来进行hash值计算,使得碰撞更加均匀
  • 使用一个阈值,让使用者自定义空间换事件效率
  • 当链表长度大于8时,链表转换成红黑树(这个我没有实现)

1 源代码

具体代码仓库:https://gitee.com/Aer2/tool-class/blob/master/src/main/java/com/aer/util/MyHashMap.java

ppackage com.aer.util;

import java.util.HashMap;

/**
 * @author:"aer"
 * @date:2021/8/25 21:26
 * @description: 自己实现一个HashMap,只抽取主干方法进行实现
 */
public class MyHashMap<K,V> {

    /**
     * 用于存储元素的节点
     * @param <K>
     * @param <V>
     */
    static class Node<K,V> {
        final int hash;
        final K key;
        V value;
        Node next;


        public Node(int hash, K key, V value, Node next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }
        
    }

    /**
     * 默认数组长度
     */
    static public int DEFAUL_LENGTH = 16;

    /**
     * 默认负载因子
     */
    static public float DEFAULT_LOADFACTOR = 0.75f;


    /**
     * table的长度
     */
    private int length;

    /**
     * table中节点的数量
     */
    private int size;

    /**
     * 负载因子
     */
    private float loadFactor;

    /**
     * 存储节点的数组
     */
    private Node[] tables;

    public MyHashMap(){
        this.length = DEFAUL_LENGTH;
        this.loadFactor = DEFAULT_LOADFACTOR;
    }

    public MyHashMap(int length){
        this(length,DEFAULT_LOADFACTOR);
    }

    public MyHashMap(int length, float loadFactor) {
        this.length = tableSizeFor(length);
        this.loadFactor = loadFactor;
    }


    /**
     *
     * @param n 输入参数
     * @return 返回大于n的最小二次幂整数
     */
    static public int tableSizeFor(int n) {
        n = n - 1;
        n = n | n>>>1;
        n = n | n>>>2;
        n = n | n>>>4;
        n = n | n>>>8;
        n = n | n>>>16;
        return  n + 1;

    }

    /**
     * hash函数
     * @param key
     * @return
     */
    public int hash(Object key){
        if(key == null) return 0;
        int code = key.hashCode();
        // 扰动函数 前16位与后16位异或
        return code ^ code>>>16;
    }

    /**
     *  放置元素
     * @param key
     * @param value
     */
    public void put(K key,V value){
        //1. 如果table为空就初始化
        if (tables == null){
            tables = new Node[this.length];
        }

        //2. 获取key的hash值并获取对应的位置
        int hash = hash(key);
        int index = hash&(this.length-1);
        Node p = tables[index];


        //3.1 如果为空就直接放入
        if(p == null){  // 如果对应为空就直接插入
            Node<K, V> node = new Node<>(hash, key, value, null);
            tables[index] = node;
        }
        else if(p.hash == hash && (p.key == key || (p.key != null && p.key.equals(key)))){
            //更新旧值
            p.value = value;
        }
        //3.2 出现冲突就拉链法
        else{

            Node q = p.next;
            while (q != null){
                if(q.hash == hash && (q.key == key ||(p.key != null && p.key.equals(key)))){
                    q.value = value;
                    break;
                }
                p = q;
                q = q.next;
            }
            // 在链表尾部插入
            Node<K, V> node = new Node<>(hash, key, value, null);
            p.next = node;
        }

        //3.3 数目超过一定限度就进行扩容
        size++;
        if(size > length*loadFactor){
            resize();
        }
    }

    /**
     * 扩容
     */
    public void resize(){
        //1.将新表扩大一倍
        Node[] tab = this.tables;
        int n = tab.length;
        this.length = this.length * 2;
        Node[] newTab = new Node[this.length];

        //2.将数据进行搬迁
        for (int i = 0; i < tab.length; i++) {
            Node node = tab[i];
            if(node == null) continue;
            if(node.next == null) newTab[node.hash & (this.length - 1)] = node;

            Node p = node;
            Node<K,V> loHead = null, loTail = null;
            Node<K,V> hiHead = null, hiTail = null;
            while (p != null){
                if((p.hash & n) == 0){  //原位置
                    if(loTail != null){
                        loTail.next = p;
                    }else{
                        loHead = p;
                    }
                    loTail = p;
                }else{  //原位置 + 二次幂
                    if(hiTail != null){
                        hiTail.next = p;
                    }else{
                        hiHead = p;
                    }
                    hiTail = p;
                }
                p = p.next;
            }
            newTab[i] = loHead;
            newTab[i+n] = hiHead;
        }

        tables = newTab;
    }

    /**
     * 获取容量里面的元素
     * @param key
     * @return
     */
    public V get(Object key){
        //1.获取key对应的位置
        int hash = hash(key);
        int index = hash&(this.length-1);
        Node p,q;
        // 2. 获取对应位置的value
        if(tables != null && (p = tables[index]) != null){
            //2.1 链表头部
            if(hash == p.hash && (p.key == key || (p.key != null && p.key.equals(key)))){  //判断key是否相等
                return (V)p.value;
            }
            //2.2 去链表中找
            if(p.next != null){
                for(q = p.next; q != null; q = q.next){
                    if(hash == q.hash && (q.key == key || (q.key != null && q.key.equals(key)))){
                        return (V)q.value;
                    }
                }
            }

        }
        //找不到数据就返回null
        return null;
    }
}
posted @ 2021-09-14 13:27  A,er  阅读(42)  评论(0)    收藏  举报