HashMap的数据结构

HashMap是Java中基于哈希表的数据结构,用于快速的插入和查找操作。其核心是数组和链表/红黑树组合,通过哈希函数确定元素位置。性能优化包括设置初始容量和负载因子,自定义哈希函数,以及在多线程环境下使用ConcurrentHashMap确保线程安全。

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

HashMap是Java中最常用的数据结构之一,它允许将键值对存储在一个哈希表中,从而实现快速的查找和插入操作。在本篇博客中,我们将深入探讨HashMap的实现原理和性能优化方法。

目录

HashMap的实现原理

HashMap的性能优化方法

总结


HashMap的实现原理

HashMap是一种基于哈希表实现的数据结构。它可以将键值对映射到一个数组中的某个位置,从而实现快速的插入和查找操作。HashMap的核心数据结构是一个数组,每个数组元素称为桶(bucket),每个桶中存储一个链表或红黑树。

数组的特点是:寻址容易,插入和删除困难;
而链表的特点是:寻址困难,插入和删除容易。
HashMap其实也是用一个线性的数组实现的,所以可以理解为其存储数据的容器就是一个线性数组。但一个线性的数组怎么实现按键值对来存取数据呢?这里HashMap有做一些处理。

当我们向HashMap中插入一个键值对时,首先计算出该键值对的哈希值,然后将其对数组长度取模,得到该键值对应的桶的位置。如果该桶中已经存在元素,则将该元素添加到链表或红黑树的末尾;否则,将该元素作为链表或红黑树的头节点。

当我们需要查找一个键值对时,首先计算出该键的哈希值,然后将其对数组长度取模,得到该键值对应的桶的位置。然后遍历该桶对应的链表或红黑树,查找是否存在该键值对。如果找到了,则返回对应的值;否则,返回null。

HashMap的数据结构可以用以下伪代码表示:

class Entry {
    Object key;
    Object value;
    Entry next;
}

class HashMap {
    int capacity; // 数组长度
    float loadFactor; // 负载因子
    int size; // 元素数量
    Entry[] table; // 数组

    // ...
}

其中,Entry表示HashMap中的一个键值对,capacity表示数组长度,loadFactor表示负载因子,size表示元素数量,table表示数组。每个Entry对象包含了一个键、一个值和一个指向下一个Entry对象的指针。当多个键映射到同一个桶时,它们会组成一个链表或红黑树。

当我们需要查找一个键值对时,首先计算出该键的哈希值,然后将其对数组长度取模,得到该键值对应的桶的位置。然后遍历该桶对应的链表,查找是否存在该键值对。如果找到了,则返回对应的值;否则,返回null。

HashMap的性能优化方法

     1.初始容量和负载因子

在创建HashMap时,我们可以指定其初始容量和负载因子。初始容量指的是HashMap中桶的数量,负载因子指的是当HashMap中元素数量达到总容量的百分之多少时,需要对HashMap进行扩容操作。默认的初始容量为16,负载因子为0.75。如果我们预先知道HashMap中元素的数量,可以通过设置初始容量来减少扩容操作的次数,从而提高性能。

      2.哈希函数

HashMap中使用的哈希函数是Java中的Object类中的hashCode()方法。然而,该方法并不是一个好的哈希函数,因为它很容易产生哈希冲突。为了提高HashMap的性能,我们可以自定义哈希函数,使其更加均匀地分布元素。

      3.链表长度

当HashMap中某个桶中的链表长度过长时,查找元素的效率会大大降低。为了避免这种情况,我们可以通过调整负载因子来控制HashMap的容量和元素数量之间的比例,从而减少链表长度。当链表中Node节点的数量大于8并且数组的长度大于64时,链表会转换成一个红黑树,有利于查找搜索

public class Node {
   public Node  next;
   private Object data;
   public Node(Object data) {
      this.data = data;
   }
   //链表:链表是一种物理存储单元上非连续、非顺序的存储结构
   //特点:插入、删除时间复杂度O(1) 查找遍历时间复杂度0(N) 插入快 查找慢
   public static void main(String[] args) {
         Node node=new Node(15);
         node.next=new Node(1);
         node.next.next=new Node(9);
   }

      4.线程安全

HashMap在多线程环境下可能会出现线程安全问题。为了解决这个问题,我们可以使用ConcurrentHashMap类,它提供了一些线程安全的方法,如put()和rehash()。

ConcurrentHashMap的put方法源码如下:

public V put(K key, V value) {
    Segment<K,V> s;
    if (value == null)
        throw new NullPointerException();
    int hash = hash(key);
    int j = (hash >>> segmentShift) & segmentMask;

    // 根据key的hash定位出一个segment,如果指定index的segment还没初始化,则调用ensureSegment方法初始化
    if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
         (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
        s = ensureSegment(j);
    // 调用segment的put方法
    return s.put(key, hash, value, false);
}

ConcurrentHashMap在rehash的时候是有锁的,所以在rehash过程中,其他线程无法对segment的hash表做操作,这就保证了线程安全。

总结

HashMap 容量为2次幂的原因,就是为了数据的的均匀分布,减少hash冲突,毕竟hash冲突越大,代表数组中一个链的长度越大,这样的话会降低HashMap的性能。

HashMap是Java中最常用的数据结构之一,它的实现原理是通过哈希表实现的。为了提高HashMap的性能,我们可以通过调整初始容量和负载因子、自定义哈希函数、控制链表长度和使用线程安全的方法来优化HashMap的性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值