Redis 和 HashMap 的区别

Redis 是 Remote Dictionary Service 三个单词中加粗字母的组合,是一种基于键值对(key-value)的 NoSQL 数据库。

但比一般的键值对,比如 HashMap 强大的多,Redis 中的 value 支持 string(字符串)、hash(哈希)、 list(列表)、set(集合)、zset(有序集合)、Bitmaps(位图)、 HyperLogLog(基数估算)、GEO(地理信息定位)等多种数据结构。

而且因为 Redis 的所有数据都存放在内存当中,所以它的读写性能非常出色。

不仅如此,Redis 还可以将内存数据持久化到硬盘上,这样在发生类似断电或者机器故障的时候,内存中的数据并不会“丢失”。

除此之外,Redis 还提供了键过期、发布订阅、事务、流水线、Lua 脚本等附加功能,是互联网技术领域中使用最广泛的缓存中间件。

HashMap 是否安全

HashMap 不是线程安全的,如果需要线程安全,需要使用ConcurrentHashMap,HashMap 之所以不是线程安全的,主要有以下几个问题:

  1. 多线程下扩容会死循环。JDK1.7 中的 HashMap 使用的是头插法插入元素,在多线程的环境下,扩容的时候就有可能导致出现环形链表,造成死循环。

    不过,JDK 8 时已经修复了这个问题,扩容时会保持链表原来的顺序。

  2. 多线程的 put 可能会导致元素的丢失。因为计算出来的位置可能会被其他线程的 put 覆盖,很好理解。本来哈希冲突是应该用链表的,但多线程时由于没有加锁,相同位置的元素可能就被干掉了。

  3. put 和 get 并发时,可能导致 get 为 null。线程 1 执行 put 时,因为元素个数超出阈值而导致出现扩容,线程 2 此时执行 get,就有可能出现这个问题,因为线程 1 执行完 table = newTab 之后,线程 2 中的 table 此时也发生了变化,此时去 get 的时候当然会 get 到 null 了,因为元素还没有转移。

Java Hashmap

JDK 8 中 HashMap 的数据结构是 数组 + 链表 + 红黑树。

也就是说,HashMap 的底层数据结构最主要的还是数组,当发生哈希冲突的时候就用链表来解决;不过,如果链表过长时,查询效率会比较低,于是当链表的长度超过 8 时,链表就会转换为红黑树。

HashMap 之所以叫“哈希表”,是因为它利用了哈希函数来计算键的哈希值,然后根据哈希值来决定元素在数组中的位置,这样就可以实现高效的增删改查效率。

数组的查询效率是 O(1),如果哈希冲突,就会用链表来解决,链表的查询效率是 O(n),于是当链表的长度超过 8 时,链表就会转换为红黑树,红黑树的查询效率是 O(logn)。

当向 HashMap 中添加一个键值对时,会使用哈希函数计算键的哈希码,确定其在数组中的位置。如果该位置已有元素(发生哈希冲突),则新元素将被添加到链表的末尾或红黑树中。如果键已经存在,其对应的值将被新值覆盖。

当从 HashMap 中获取元素时,也会使用哈希函数计算键的位置,然后根据位置在数组、链表或者红黑树中查找元素。

随着元素的不断添加,HashMap 的容量(也就是数组大小)可能不足,于是就需要进行扩容,阈值是capacity * loadFactor,capacity 为容量,loadFactor 为负载因子,默认为 0.75。扩容后的数组大小是原来的 2 倍,然后把原来的元素重新计算哈希值,放到新的数组中。

总的来说,HashMap 是一种通过哈希表实现的键值对集合,它通过将键哈希化成数组索引,并在冲突时使用链表或红黑树来存储元素,从而实现快速的查找、插入和删除操作。

详解HashMap底层原理+扩容机制

参考