手写简单HashMap

本文深入解析HashMap的内部工作原理,包括其构造函数、容量计算、哈希函数以及扩容机制。通过实例展示了无参和有参构造函数的实现,并详细解释了哈希值的计算方法。此外,还介绍了自定义HashMap的基本实现,强调了头插法和扩容策略。
摘要由CSDN通过智能技术生成

学习目标:

HashMap浅析

学习内容:

1、 我们从使用的角度来展开
首先hashmap的结构如下图所示(数组加链表的数据结构)。
在这里插入图片描述
一般我们都是如下面使用hashmap

HashMap map1 = new HashMap();

上面是无参构造函数,看一下调用了啥?

/**
     * Constructs an empty <tt>HashMap</tt> with the default initial capacity
     * (16) and the default load factor (0.75).
     */
     //这是给一个默认的扩展因子
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

还有一个这样的使用方式

 HashMap map2 = new HashMap(15, 0.75f);

上面是有参构造函数,看一下调用了啥?

/**
     * Constructs an empty <tt>HashMap</tt> with the specified initial
     * capacity and load factor.
     *
     * @param  initialCapacity the initial capacity
     * @param  loadFactor      the load factor
     * @throws IllegalArgumentException if the initial capacity is negative
     *         or the load factor is nonpositive
     */
    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
        
                                             loadFactor);
        //扩展因子
        this.loadFactor = loadFactor;
        //数组的总长度
        this.threshold = tableSizeFor(initialCapacity);
    }

/**
     * Returns a power of two size for the given target capacity.
     */
     //确定了我们hashmap的长度 
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

通过这个构造函数我们初始化了hashmap最大长度以及扩展因子,接下来就聊聊hash函数的实现方式,还是看一下源码。
可以从get的时候入手

public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
}

 final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            //n-1 & hash 这样就只保留了hashCode,以这个值作为数组的下标
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
 }
 static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
 }

从上面的代码我们可以发现hash(Object key)这个就是hashmap的hash算法,就两行,
比如 : key.hashCode : 0000000000001100 0000000000001011 (32位)
>> 16 : 0000000000000000 0000000000001100 (32位)
return : 0000000000001100 0000000000000111 (32位)
而计算我们这个node存放下标的时候 会把这个hash值在进行处理n -1 & hash
这样就会取到 0111,算出来位置就是7,node就会在table[7]的地方插入。插入的时候也会遇到当前位置已经被别人占用的时候,就会采用头插法(插入的时候处理比较复杂),当链表长度大于8时,就会把当前链表变成红黑树。

看了一会儿代码就来实现一下自己的hashMap(无红黑树,非线程安全)

package com.suyong.lib;

import java.util.HashMap;

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

    //最大长度
    static final int MAXIMUM_CAPACITY = 1 << 30;

    //扩展因子
    private float loadFactor = 0.75f;

    //默认长度(16)
    private int threshold;

    //HashMap table
    private Node[] table = null;

    //记录实际的节点个数
    private int size = 0;

    public MyHashMap() {
        threshold = 1 << 4;
    }

    public MyHashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                    initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                    loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

    //列如:13 ->0000 1101
    //计算后长度 -> 0001 0000(16)
    //列如:25 ->0001 1001
    //计算后长度 -> 0010 0000(32)
    //计算HashMap长度
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

    //hash算法
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

    @Override
    public V put(K key, V value) {
        synchronized (HashMap.class) {
            if (table == null) {
                table = new Node[threshold];
            }
        }
        //达到扩容因子的限制个数,开始扩容,防止查找性能降低。
        if (size >= (threshold * loadFactor)) {
            //扩容重新分配大小
            resize();
        }
        int index = hash(key) & (threshold - 1);
        Node<K, V> node = table[index];
        if (node == null) {
            node = new Node<K, V>(key, value, null);
            size++;
        } else {
            Node<K, V> newNode = node;
            while (newNode != null) {
                if (newNode.getKey().equals(key)) {
                    //找到既存的key,就个更新value
                    return newNode.setValue(value);
                } else {
                    if (newNode.next == null) {
                        //头插法
                        node = new Node<K, V>(key, value, node);
                        size++;
                    }
                }
                newNode = newNode.next;
            }
        }
        table[index] = node;
        return null;
    }

    //扩容
    private void resize() {
        threshold = threshold << 1;
        Node<K, V>[] newTable = new Node[threshold];
        for (int i = 0; i < table.length; i++) {
            Node<K,V> oldNode = table[i];
            while(oldNode!=null) {
                Node oldNodeNext = oldNode.next;
                int index = hash(oldNode.getKey()) & (threshold - 1);
                newTable[index] = oldNode;
                oldNode.next = newTable[index];
                newTable[index] = oldNode;
                oldNode = oldNodeNext;
            }
            table[i] = null;
        }
        table = newTable;
        threshold = newTable.length;
        newTable = null;
    }

    @Override
    public V get(K key) {
        Node<K, V> node = getNode(table[hash(key.hashCode()) & (threshold - 1)], key);
        return node.value;
    }

    private Node<K, V> getNode(Node<K, V> node, K k) {
        while (node != null) {
            if (node.getKey().equals(k) || node.getKey() == k) {
                return node;
            }
            node = node.next;
        }
        return null;
    }

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

    private final class Node<K, V> implements Entry<K, V> {

        private K key;

        private V value;

        private Node<K, V> next;

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

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

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

        @Override
        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }
    }
}


public interface AbstractHashMap<K,V>{

    V put(K key,V value);

    V get(K key);

    int size();

    interface Entry<K,V>{
        K getKey();
        V getValue();
        V setValue(V value);
    }
}

附上插入时的流程图
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值