当前位置: 首页 > news >正文

HashMap详解+简单手写实现(哈希表)

1. 什么是 HashMap?

HashMap是Java集合框架中的一种数据结构,它实现了Map接口,用于存储键值对(Key-Value Pair)。HashMap允许null键和null值,并且不保证元素的顺序。

---

2. HashMap 的工作原理

2.1 内部结构
  • 数组 + 链表/红黑树:HashMap内部使用一个数组(称为table)来存储数据,每个数组元素是一个链表或红黑树的头节点。
  • 哈希函数:通过哈希函数将键(Key)映射到数组的索引位置。
2.2 插入数据
  • 计算哈希值:使用键的hashCode()方法计算哈希值。
  • 计算索引:通过哈希值和数组长度计算索引位置。
  • 处理冲突:如果索引位置已经有元素,则通过链表或红黑树处理冲突。
  • 插入数据:将键值对插入到链表或红黑树中。
2.3 查找数据
  • 计算哈希值:使用键的hashCode()方法计算哈希值。
  • 计算索引:通过哈希值和数组长度计算索引位置。
  • 查找数据:在链表或红黑树中查找键值对。
2.4 扩容机制
  • 负载因子:HashMap有一个负载因子(默认0.75),当元素数量超过容量 * 负载因子时,HashMap会进行扩容。
  • 扩容过程:创建一个新的数组,将旧数组中的元素重新哈希到新数组中。

---

3. HashMap 的特点

3.1 优点
  • 快速查找:通过哈希函数,HashMap可以在平均O(1)的时间复杂度内查找元素。
  • 灵活:允许null键和null值。
3.2 缺点
  • 无序:HashMap不保证元素的顺序。
  • 线程不安全:HashMap不是线程安全的,多线程环境下需要使用ConcurrentHashMap。

---

4. 常见问题

4.1 HashMap 和 Hashtable 的区别?
  • HashMap:允许null键和null值,线程不安全。
  • Hashtable:不允许null键和null值,线程安全。
4.2 HashMap 的负载因子为什么是0.75?
  • 负载因子:0.75是时间和空间的一个平衡点,既不会浪费太多空间,也不会导致频繁扩容。
4.3 HashMap 如何处理哈希冲突?
  • 链表:Java 8之前,HashMap使用链表处理冲突。
  • 红黑树:Java 8之后,当链表长度超过8时,HashMap会将链表转换为红黑树,提高查找效率。

---

5. 进一步优化与迭代方向

  • 使用合适的初始容量:根据预估的元素数量设置初始容量,减少扩容次数。
  • 选择合适的负载因子:根据实际需求调整负载因子,平衡时间和空间。
  • 线程安全:在多线程环境下使用ConcurrentHashMap,避免线程安全问题。

6.手写一个哈希表


class HashNode<K,V> {
    //定义哈希表的节点类
    K key;
    V value;
    HashNode<K,V> next;//用于处理哈希冲突的链表
    public HashNode(K key, V value){
        this.key = key;
        this.value = value;
        this.next = null;
    }
    public static void main(String[] args) {
        MyHashMap<String, Integer> map = new MyHashMap<>();
        map.put("Apple", 1);
        map.put("Banana", 2);
        map.put("Orange", 3);

        System.out.println(map.get("Banana")); // 输出: 2
        map.remove("Banana");
        System.out.println(map.get("Banana")); // 输出: null
        System.out.println(map.size()); // 输出: 2
    }
}
//计算键的哈希值
class MyHashMap<K,V> {
    private static final int DEFAULT_CAPACITY = 16;//默认容量
    private static final float LOAD_FACTOR = 0.75f; // 负载因子

    private HashNode<K, V>[] table;//哈希表数组
    private int size;//当前元素数量
    public MyHashMap(){
        table = new HashNode[DEFAULT_CAPACITY];
        size = 0;
    }
    //计算键的哈希值
    private int hash(K key){
        return key == null ? 0 :Math.abs(key.hashCode() % table.length);
    }

    //插入键值对
    public void put(K key,V value){
        int index = hash(key);
        HashNode<K,V> node = table[index];

        //遍历链表,检查是否存在相同的键
        while (node != null){
            if(node.key.equals(key)){
                node.value = value;//更新值
                return;
            }
            node = node.next;
        }
        //插入到新节点到链表头部
        HashNode<K,V> newNode = new HashNode<>(key,value);
        newNode.next = table[index];
        table[index] = newNode;
        size++;
        //检查是否需要扩容
        if ((float) size/table.length > LOAD_FACTOR){
            resize();
        }
    }
    //查找键相对应的值
    public V get(K key){
        int index = hash(key);
        HashNode<K,V> node = table[index];

        //遍历链表,查找键
        while (node != null){
            if (node.key.equals(key)){
                return node.value;
            }
            node = node.next;
        }
        return null;//未找到
    }
    //删除键值对
    public void remove(K key){
        int index = hash(key);
        HashNode<K,V> node = table[index];
        HashNode<K,V> prev = null;


        //遍历链表,查找键
        while (node != null){
            if(node.key.equals(key)){
                if(prev == null){
                    table[index] = node.next;//删除链表头部
                }else {
                    prev.next = node.next;//删除链表的中间或尾部
                }
                size--;
                return;
            }
            prev = node;
            node = node.next;
        }
    }

    //扩容哈希表
    private void resize(){
        int newCapacity = table.length*2;
        HashNode<K,V>[] newTable = new HashNode[newCapacity];

        //重新载入哈希所有元素
        for (HashNode<K,V> node : table){
            while(node != null){
                int newIndex = hash(node.key);
                HashNode<K,V> next = node.next;
                node.next = newTable[newIndex];
                newTable[newIndex] = node;
                node = next;

            }
        }
        table = newTable;
    }

    //获取当前元素数量
    public  int size(){
        return size;
    }


}

相关文章:

  • 深度学习机器学习:常用激活函数(activation function)详解
  • Qt Creator 5.0.2 (Community)用久了突然变得很卡
  • Kafka分区管理大师指南:扩容、均衡、迁移与限流全解析
  • Flutter 实现 iOS 小组件与主 App 的通信
  • make命令学习
  • 知识拓展:设计模式之装饰器模式
  • 传输层协议TCP ( 下 )
  • springboot集成zookeeper的增删改查、节点监听、分布式读写锁、分布式计数器
  • UEFI PI PEI(3. PEI Foundation/PEI Dispatcher)
  • 蓝桥与力扣刷题(108 将有序数组转换成二叉搜索树)
  • 解锁豆瓣高清海报(三)从深度爬虫到URL构造,实现极速下载
  • 网站地址栏怎么变成HTTPS开头?
  • windows平台上 oracle简单操作手册
  • 【SQL server】存储过程模板
  • List对象进行排序
  • 如何提升谷歌SEO排名?
  • Springboot RabbitMQ 消费失败消息清洗与重试机制
  • 超越DeepSeek R1的Moe开源大模型 Qwen2.5-max 和 Qwen Chat Web UI 的发布,阿里搅动AI生态
  • langchain实现的内部问答系统及本地化替代方案
  • Linux相关概念和易错知识点(27)(认识线程、页表与进程地址空间、线程资源划分)
  • 深圳网站建设公司 概况/现在网络推广哪家好
  • 游戏发号网站源码/单页面seo搜索引擎优化
  • 自己做网站卖视频/怎么建立一个属于自己的网站
  • 淘宝刷单网站建设/爱站网站seo查询工具
  • 承德 网站维护/百度云电脑网页版入口
  • 小地方网站建设公司/专业代写文案的公司