实现一个简单的哈希表(数组 + 链表)

1、什么是哈希表
2、主要方法的逻辑和代码实现
1)put()方法
2)get()方法
3) remove()方法
1、哈希表

hash,哈希表这个词,以前接触过好多次了,对哈希表了解最多的就是,这玩意儿也是一种数据结构,而且哈希表查找、添加和删除元素都很快。

哈希表,跟链表一样,大都使用结点类型来存储键跟值。哈希表的存储类型是种叫 键值对 <K, V> 的东西。

在本文中,我用的是一个数组 + 链表实现一个简单的哈希表。
我们得到键与值之后,用这个键得到这个值在数组中存储的下标(至于这个下标怎么得到,需要用到一种 hash 算法去实现这个值),就可以将值存储起来。
当想查找一个值时,我们只需通过 hash 算出下标,就可以在数组该下标下得到值。

在这里插入图片描述

hash()算法:
在JDK源码中,使用的是hash = (h = key.hashCode) ^ (h >>> 16),
index = hash & table.length - 1;

为什么需要链表捏?我们通过对 key 进行 hash() 得到的下标值 index,可能不同的 key 得到同样的下标,我们就不能轻易的在此处放置要添加的新值。这个时候,我们可以像链表一样把新值链在旧值后面(但是要进行判断,判断条件与结果在 put() 种)。
这也就是所谓的哈希冲突,我们通过对 key 进行 hash() 得到了下标值,这个下标却被别人占了(暂时简单的这样理解),我们要做其它处理呢对不对。
如此,就建立了一个简单的哈希表。

哈希表综合了数组和链表的优点:key ➡ index ➡ 操作值
查找、添加、删除都几乎可以一次找到位置,
只是发生哈希冲突的情况下要在链表中遍历。

一个好的哈希算法的冲突贼低,所以查找、添加和删除元素的时间复杂度都是O(1)。

2、主要方法的逻辑实现
1)put()方法

逻辑:
首先,明白这个结构。
是在一个数组里放值,那么通过 hash() 算法 对 key 得到下标 index ,如果得到的下标所在的结点不为空,说明有相同的 key 或者不同的 key ,通过 hash 得到了相同的 index ,此时要对链表进行操作。如果为空,直接放。
如果不为空,在 HashMap 中不允许存在相同的 key 值,我们从到到尾遍历链表,如果欲添加的 key 等于某个结点的 key 值,用新值覆盖旧值(这里使用),否则链链表。
代码:

public void put(K key, V value){
        int hash = hash(key);
        int index = hash & table.length - 1;

        Node<K, V> firstNode = table[index];
        //当前 index 下没有节点
        if (firstNode == null){
            table[index] = new Node<>(hash, key, value);
            size++;
        } else {
            //index 下已被某个结点占据
            Node<K, V> curNode = firstNode;
            //从该已被占据的节点处开始遍历
            //while 循环
            //如果当前结点有下一个结点并且当前结点的 key 不等于欲添加的 key,向后遍历
            while (curNode.next != null && !curNode.key.equals(key)){
                curNode = curNode.next;
            }
            //退出 while 循环条件
            //是最后一个结点或者 key 相等

            //条件1:不是最后一个结点,欲添加的 key 等于当前结点的 key 值
            if (curNode.next != null){
                curNode.value = value;
            } else if(curNode.next == null && curNode.key.equals(key)){
                //条件2:欲添加的 key 等于最后一个结点的 key 值
                curNode.value = value;
            } else {
                //条件3:欲添加的 key 不等于最后一个结点的 key 值
                curNode = new Node<>(hash, key, value);
                size++;
            }
        }
    }

对于put()方法里面某个 index 处的链表来说,如果链表太长,比如在JDK源码中,链表中结点超过8的话,就把当前链表转为红黑树。
由于不会,暂且不做讨论。

2)get()方法

逻辑:
首先得到 index ,看当前结点为空吗,不为空就从当前结点开始尝试往后找,
匹配 hash 值和 key 值,如果匹配上就返回该结点的值,不匹配就代表没有的;为空,该节点就没有值。

为什么要同时匹配 hash 值和 key 值呢?匹配 key 值是其一,继续匹配 hash 值是为了保险,对于两个不同的对象来说,通过哈希算法有可能得到相同的 hash 值。

代码:

 public V get(K key){
        int hash = hash(key);
        int index = hash & table.length - 1;
        
        Node<K, V> firstNode = table[index];
        //当前结点没东西
        if (firstNode == null){
            return null;
        } else {
            //有的话,遍历,找到相符结点
            Node<K, V> curNode = firstNode;
            while (curNode != null){
                if (curNode.key.equals(key) && curNode.hash == hash){
                    return curNode.value;
                }
                curNode = curNode.next;
            }
        }
        return null;
    }
3) remove()方法

逻辑:
先搞到下标不用说。
得到下标的结点没有就返回也不用说。
如果有结点,开删。
看看是头结点嘛?是的话删,
不是的话,找到要删的继续删。
否则仍然没找到

代码:

public boolean remove(K key) {
        int hash = hash(key);
        int index = hash & table.length - 1;

        Node<K, V> firstNode = table[index];
        //第一个结点为空
        if (firstNode == null) {
            return false;
        } else {
            //删除第一个结点
            if (firstNode.key.equals(key) && firstNode.hash == hash){
                table[index] = firstNode.next;
                return true;
            } else {
                //不是头节点
                Node<K, V> curNode = firstNode;
                while (curNode.next != null){
                    if (curNode.next.key.equals(key) && curNode.next.hash == hash){
                        curNode.next = curNode.next.next;
                        return true;
                    } else {
                        curNode = curNode.next;
                    }
                }
                //没返回 true ,指定没找到
                return false;
            }
        }
    }
4)构造器、内部结点类和哈希算法
class HashMap<K, V> {
    static class Node<K, V> {
        private int hash;
        private K key;
        private V value;
        private Node<K, V> next;

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

    private Node<K, V>[] table;
    private int size;

    public HashMap(int capacity) {
        table = new Node[capacity];
    }

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

注:
完整的哈希表代码

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值