数据结构实现—— 哈希表(HashMap)

94 阅读3分钟

哈希表的实现

使用拉链法来实现哈希表,也就是说我这里哈希表的底层数据结构使用的是 数组和链表来实现

为了简化操作,这里我们链表就直接使用List接口的实现类LinkedList

实现MyHashMap的代码

实际上在实现的时候有一个较大的感受就是,Java很多代码已经整合的非常好了

  • 比如说在通过key来计算所谓的哈希值的时候,实际上我们只需要调用hashCode()这个方法即可,因为Java中任何一个类(包括我们自定义的类)都是Object类的子类,Object类中就有hashCode()方法

    • 那么不管是后续的子类,是否重写了hashCode()方法。 反正调用就完事儿了
package com.nylonmin.mapDemo;
​
import java.util.LinkedList;
import java.util.List;
​
@SuppressWarnings("all")
public class MyHashMap<K,V> {
    private int size;  // 记录当前存储的键值对的个数
    private LinkedList<Node<K,V>>[] table;
    private static final int DEFAULT_CAPACITY = 16;
    //阈值比率
    private static final double THRESHOLD_RATE = 0.75;
​
    /**
     * 静态内部类, 定义节点
     * @param <K>
     * @param <V>
     */
    private static class Node<K,V>{
        K key;
        V value;
        public Node(K key,V value){
            this.key= key;
            this.value=value;
        }
    }
​
    /**
     * 无参构造函数,默认创建长度为16大小的数组
     */
    public MyHashMap(){
        this(DEFAULT_CAPACITY);
    }
​
    /**
     * 有参构造函数,创建容量为指定的capacity大小的数组
     * @param capacity
     */
    public MyHashMap(int capacity){
        if(capacity<0 || capacity >Integer.MAX_VALUE){
            throw new IllegalArgumentException("初始化哈希表有误,容量输入有误");
        }
        this.size = 0;
        table = (LinkedList<Node<K,V>>[])new LinkedList[capacity];
        for(int i=0;i<table.length;i++){
            table[i] = new LinkedList<Node<K,V>>();
        }
    }
​
    /**
     * 计算当前key对应的哈希值
     * 参照hashMap底层来做
     * @return
     */
    private int hash(K key){
        int h;
        return (h=key.hashCode()) ^ h>>>16;
    }
​
    /**
     * 根据键key来查询对应的值value
     * @param key
     * @return
     */
    public V get(K key){
        Node<K, V> node = this.getNode(key);
        if(node == null){
            return null;
        }
        return node.value;
​
    }
​
    /**
     * 通过key类获取对应的节点
     * 用private修饰,达到后续增加 等功能能够复用 ,减少代码的重复率
     * @param key
     * @return
     */
    private Node<K,V> getNode(K key){
        if (key == null) {
            throw new IllegalArgumentException("key is null");
        }
        LinkedList<Node<K,V>> list = new LinkedList<>();
        int n = table.length;
        list = this.table[hash(key) & (n-1)];
        for(Node<K,V> node : list){
            if (node.key.equals(key)){
                return node;
            }
        }
        return null;
    }
    /**
     * 往map中增加元素
     * @param key 键
     * @param value 值
     * @return 返回是否增加成功
     */
    public boolean put(K key,V value){
        Node<K, V> node = getNode(key);
​
        //如果已经查到了有对应的key 那么直接修改node对应的value即可
        if(node != null){
            node.value=value;
            return true;
        }
        //如果没有查到 那么就要检查是否需要扩容
        // TODO 检查是否需要扩容
        if(size+1> THRESHOLD_RATE*table.length){
            resize(table.length*2);
        }
        LinkedList<Node<K,V>> list = new LinkedList<>();
        list = table[hash(key) & (table.length-1)];
        //如果当前哈希表中没有对应的key对应的node,那么直接插入
        Node node1 = (Node<K,V>) new Node(key, value);
        list.add(node1);
        size++;
        return true;
​
​
    }
​
    /**
     * 根据key来移除node
     * @param key
     * @return
     */
    public Node<K,V> remove(K key){
        Node<K, V> node = getNode(key);
        //如果哈希表中不存在这个key对应的node
        if(node == null){
            return null;
        }
        LinkedList<Node<K,V>> list = new LinkedList<>();
        list = table[hash(key) & (table.length-1)];
        list.remove(node);
        size--;
        return node;
​
    }
​
    /**
     * 将table的容量改变为指定的capacity大小
     * HashMap的底层代码是扩容到大于等于当前容量两倍的 最小2的幂次方
     * 这里我们就简单化处理扩容到原容量两倍就行
     * @param capacity
     */
    private void resize(int newCapacity){
        //不能让newCapacity超出Integer的最大长度
        newCapacity = Math.min(newCapacity,Integer.MAX_VALUE);
        MyHashMap<K,V> newMap = new MyHashMap<>(newCapacity);
        //把原来的map中的Node放入到新的map中
        for(int i=0;i<table.length;i++){
            LinkedList<Node<K,V>> list = table[i];
            for(Node<K,V> node :list){
                newMap.put(node.key,node.value);
            }
        }
        //把HashMap的底层table数组给替换掉
        this.table = newMap.table;
    }
    /**
     * 简单工具类
     */
    public int size(){
        return size;
    }
    public List<K> keys(){
        List<K> result = new LinkedList<>();
        for(LinkedList<Node<K,V>> list :table){
            for(Node<K,V> node :list){
                result.add(node.key);
            }
        }
        return result;
    }
​
}
​

测试代码

package com.nylonmin.mapDemo;
​
@SuppressWarnings("all")
public class MapDemoTest {
    public static void main(String[] args) {
​
        MyHashMap<Integer, Integer> map = new MyHashMap<>();
        map.put(1, 1);
        map.put(2, 2);
        map.put(3, 3);
        System.out.println(map.get(1)); // 1
        System.out.println(map.get(2)); // 2
​
        map.put(1, 100);
        System.out.println(map.get(1)); // 100
​
        map.remove(2);
        System.out.println(map.get(2)); // null
        // [1, 3](顺序可能不同)
        System.out.println(map.keys());
​
        map.remove(1);
        map.remove(2);
        map.remove(3);
        System.out.println(map.get(1)); // null
        //下面是用来测试是否会扩容
        for(int i=0;i<20;i++){
            map.put(i,1);
        }
​
    }
}