手写hash表

本文深入探讨了HashMap的工作原理,通过手写实现的方式解释了其内部结构及如何处理哈希冲突,包括链地址法的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以前一直用hash表,但是并不清楚它的原理。所以,我决定手写一个hash表,彻底搞清它的工作方式。这里有一个误区是:通常我们会认为对象数组是存储对象本身,也就是对象数组是开辟一个空间,然后将对象放进去。这不仅从jvm的角度来解释(java对象创建在堆上),而且自己认真想想就不是这样。对象数组其实保存的是对象的引用而已。为什么要说这么简单的事,因为当初确实理解这个hashmap的时候搞糊涂了。有一段代码可以证明保存的是引用:

/**
 *最后输出的是“改变”,说明存储的是内存地址(不然没有修改数组中的对象数组中对象为什么会发生改变)
 */
public class TestArray {
    public static void main(String[] args) {
        Item[] items = new Item[3];
        Item item = new Item();
        item.setName("123");
        items[0] = item;
        item.setName("改变");
        System.out.println(items[0].getName());
    }
}

class Item {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Item{" +
                "name='" + name + '\'' +
                '}';
    }
}

说到hash表,其实他是通过一个函数输入key算value的地址在哪。业界采用的比较多的方式是链地址法。

img_d93f52e0285132c487d5bfd0481427d4.png
链地址法.png

这里只是说了个大概的结构,如果想要看详细的过程,可以看 这里
这个方法的好处是,结合了数组和链表的优势,抵消hash冲突带来的劣势。每个链表节点都有key,value,next节点。工作流程:先通过key算地址得到f(key),找到链表的头,然后将key的值与每个节点key的值比较,如果key相同,将value值返回。
代码如下:

class entry<k, v>{
    int capacity;
    node[] no;

    public entry(int n){
        capacity = n;
        no = new node[n];
    }

    //链表类
    class node<k, v>{
        k key;
        v value;
        node<k, v> next;

        public node(){}

        node(k key, v value, node<k, v> next){
            this.key = key;
            this.value = value;
            this.next = next;
        }
    }

    public int hashcode(k key){
        return (key.hashCode() & 0x7ffffff) % capacity;
    }

    public void put(k key, v value){
        int h = hashcode(key);
        for(node<k, v> n = no[h]; n != null; n = n.next){
            if(key.equals(n.key)){
                n.value = value;
                return;
            }
        }

        node<k, v> old = no[h];//插入的时候应该是这么插,将新节点的地址放到数组中,然后让新节点指向原来第一个节点
        no[h] = new node(key, value, old);
    }

    public v get(k key){
        int h = hashcode(key);
        if(no[h] == null){
            return null;
        }

        for(node<k, v> n = no[h]; n != null; n = n.next){
            if(key.equals(n.key))
                return n.value;
        }
        return null;
    }

    public static void main(String[] args) {
        entry<Integer, Integer> map = new entry<Integer, Integer>(100);

        map.put(1, 90);
        map.put(2, 95);

        System.out.println(map.get(1));
        System.out.println(map.get(2));
    }

}
### 如何在 C++ 中实现自定义哈希 #### 定义哈希的基本结构 为了创建一个简单的哈希,首先需要定义存储键值对的数据结构。通常情况下,哈希由桶(bucket)组成,每个桶是一个链或其他形式的集合,用于处理冲突。 ```cpp #include <list> #include <vector> template<typename KeyType, typename ValueType> struct HashTable { using Pair = std::pair<KeyType, ValueType>; private: size_t capacity; std::vector<std::list<Pair>> buckets; public: explicit HashTable(size_t cap) : capacity(cap), buckets(capacity) {} void insert(const KeyType& key, const ValueType& value); bool find(const KeyType& key, ValueType& outValue) const; }; ``` 这段代码展示了哈希的基础框架[^2]。 #### 自定义哈希函数 对于不同的键类型,可能需要编写专门的哈希函数。这里展示了一个通用的方式: ```cpp // 默认哈希函数模板 template<typename T> struct DefaultHashFunction { size_t operator()(const T& t) const noexcept; }; // 特定类型的特化版本 template<> struct DefaultHashFunction<int> { size_t operator()(int i) const noexcept { return static_cast<size_t>(i); } }; // 字符串类型的特化版本 template<> struct DefaultHashFunction<std::string> { size_t operator()(const std::string& str) const noexcept { // 使用 djb2 算法作为例子 unsigned long hash = 5381; for (char c : str) hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ return hash; } }; ``` 上述实现了针对 `int` 和 `std::string` 类型的默认哈希函数[^5]。 #### 插入操作 当向哈希中插入新元素时,先计算给定键对应的索引位置,再将该键值对加入到对应的位置上。 ```cpp void HashTable<KeyType, ValueType>::insert(const KeyType& key, const ValueType& value) { auto index = DefaultHashFunction<KeyType>()(key) % this->capacity; for (auto it = buckets[index].begin(); it != buckets[index].end(); ++it) { if (it->first == key) { // 更新已有记录 it->second = value; return; } } // 添加新的键值对 buckets[index].push_back(Pair(key, value)); } ``` 此部分逻辑负责处理碰撞情况下的更新或新增行为[^1]。 #### 查找功能 查找某个特定键是否存在及其关联的值的过程如下所示: ```cpp bool HashTable<KeyType, ValueType>::find(const KeyType& key, ValueType& outValue) const { auto index = DefaultHashFunction<KeyType>()(key) % this->capacity; for (const auto& pair : buckets[index]) { if (pair.first == key) { outValue = pair.second; return true; } } return false; } ``` 这段代码遍历指定索引处的所有条目直到找到匹配项为止[^3]。 #### 扩展思考 实际开发过程中还需要考虑负载因子、动态调整容量等因素以优化性能现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值