HashMap的底层原理

发布时间 2023-09-10 15:53:35作者: deigang

HashMap

哈希表(Hash Table)是一种用于存储键值对的数据结构,他的底层实现在jdk1.8后是数组+链表+红黑树,在jdk1.8前是数组+链表,他通过哈希函数将键映射到储存桶中,从而实现快速的插入,查找和删除操作。哈希表的实现通常包括一个数组和一个哈希函数,其中数组用于储存键值对,哈希函数将建映射到数组的索引上,当两个不同的键被哈希函数映射到同一索引上时,就会发生哈希冲突,此时需要使用一些技术来解决冲突,例如链式法或开放寻址法。

哈希函数呢就是会把键转化为一个索引值,通常回事一个整数,好一点的哈希函数应该尽可能均匀地将键和值映射到不同的索引上,以减少哈希冲突的概率。

数组的话,就是用来存储键值对,数组的大小通常是固定的,但是也可以动态调整。

如果两个键产生了相同的索引位置就是发生了冲突,此时可以采用链式法在冲突的索引位置上维护一个链表或则其他数据结构,将冲突的键值对依次添加到链表中。也可以用开放寻址法根据一定的规则在其他空闲位置进行探测知道找到一个可用的位置。

Hash Map通过哈希值的计算和直接索引访问,可以快速查找键得对应值,这样既实现了高效的查询,也实现了快速的插入和删除。Hash Map的键值对是无序储存的不会保持插入顺序或者其他特定的排序顺序,并且是不可重复的,因为不一定挂到哪一个单向链表上的,因此加入顺序和取出也不一样。怎么保持不可重复?使用equals方法来保证HashMap集合key不可重复,如key重复来,value就会覆盖。存放在HashMap集合key部分的元素,其实就是存放在HashSet集合中,则HashSet集合也需要重写equals和Hash Code方法。

Hash Map具有动态扩容的能力,Hash Map集合的默认初始化容量为16,默认加载因子为0.75,也就是说这个默认加载因子是当Hash Map集合底层数组的容量达到75%时,数组就开始扩容。Hash Map集合初始化容量是2的陪数,为了达到散列均匀,提高Hash Map集合的存取效率,JDK8之后,如果哈希表单向链表中元素超过8个,那么单向链表这种数据结构会变成红黑树数据结构。当红黑树上的节点数量小于6个,会重新把红黑树变成单向链表数据结构。

HashMap是允许存储null值和null键。当插入null值时,会将其哈希到数组的某个特定索引位置;当插入null键时,会将其存储在数组的第一个位置。

然而,Hashmap 函数也有一些缺点。首先,它的空间复杂度比较高。由于哈希表需要预留一定的空间来存储键值对,所以当哈希表中的键值对比较少时,它的空间利用率会比较低

另一个缺点是 Hashmap函数的性能可能会受到哈希冲突的影响当多个键映射到同一个索引位置上时,它们会被存储在同一个链表中。这意味着在查找、插入和删除操作时,我们需要遍历这个链表如果这个链表比较长,那么这些操作的时间复杂度就会变得比较高。

并且hash Map 的线程不安全,主要原因是它的内部结构和操作不是线程安全的,HashMap 的操作不是线程同步的,也就是说,在多线程环境下同时对 HashMap 进行读写操作可能会导致数据不一致的问题。

HashMap 的操作不是原子性的,例如 put() 方法涉及到了多个步骤,包括计算哈希值、查找或插入元素等。如果多个线程同时执行这些操作,就有可能导致数据不一致的情况。

HashMap 在扩容时,需要重新计算元素的哈希值并重新分配存储位置,这个过程涉及到对原数组进行复制和重新插入元素的操作。如果在扩容期间有其他线程对 HashMap 进行并发修改,就可能导致数据丢失或出现异常。

为了在多线程环境下安全地使用 HashMap,

可以使用同步机制:通过在访问 HashMap 时使用 synchronized 或其他锁机制来确保同一时间只有一个线程能够修改 HashMap。

也可以使用线程封闭:可以将 HashMap 封闭在单个线程中,通过使用 ThreadLocal 或将 HashMap 作为局部变量在每个线程中进行操作,从而避免多线程访问导致的线程安全问题。