从Guava-Cache到HashMap

本文详细探讨了GuavaCache的数据淘汰策略,包括FIFO、LRU和LFU,并介绍了GuavaCache如何利用ConcurrentHashMap实现线程安全的缓存。同时,对比分析了JDK1.7和1.8中HashMap与ConcurrentHashMap的实现细节,如初始化、扩容和put方法。文章还讨论了DougLea的经典写法,解析了提高散列度的技巧,并解释了为何单线程下会抛出并发修改异常。最后,对比了JDK1.7和1.8中ConcurrentHashMap的线程安全性差异。

1、简介

Guava是google的一款开源java工具集库,其中许多功能甚是好用。其中的cache缓存功能,如果抛开业界的分布式缓存软件不提,guava cache是非常优秀的缓存工具。下面来看看美团技术团队对它的评价。

总体来看,Guava Cache在高并发场景支持和线程安全上都有相应的改进策略,使用Reference引用命令,提升高并发下的数据……访问速度并保持了GC的可回收,有效节省空间;同时,write链和access链的设计,能更灵活、高效的实现多种类型的缓存清理策略,包括基于容量的清理、基于时间的清理、基于引用的清理等;编程式的build生成器管理,让使用者有更多的自由度,能够根据不同场景设置合适的模式。

2、Guava Cache

guava cache实际是针对单机内存的缓存工具,其实正如HashMap、ConcurrentHashMap一样,他们都是将缓存加载到内存中。但是HashMap和ConCurrentHashMap只能通过主动删除来进行缓存回收。而guava Cache则可以支持通过容量、时间和引用的缓存回收机制。

2.1 数据淘汰策略

常用的缓存淘汰策略有以下几种:

2.2.1 FIFO:First In First Out

1、核心思想:简单的将数据塞入队列中,push --> pop 。
2、优点:当队列中的容量超过阈值时,先进的先出,直接将最先进入队列的元素挤出队列。
3、缺点:没有考虑缓存命中率的问题,使用较少。

2.2.2 LRU:Least Recently Used

1、核心思想:按照缓存命中的时间排序,不考虑单个数据命中的次数。超过阈值时,淘汰最久没用的数据。
2、优点:适用于热点数据的存放,高频访问的保留。
3、缺点:即使某个数据命中率很高,但最近该数据没有使用,依然会被淘汰。周期性高频访问数据会被删掉。

2.2.3 LFU:Least Frequently Used

1、核心思想:淘汰最不常用的数据,对数据的命中次数加权重。
2、优点:LFU要优于LRU,避免周期性高频访问数据被删除。
3、缺点:LFU存在历史数据影响将来数据的“缓存污染”效用。
guava cache采用的是LRU策略。

3、ConcurrentHashMap 与 HashMap

guava cache的底层其实是对ConcurrentHashMap的优化重写,这边文章从guava localCache到ConcurrentHashMap再到HashMap的源码都好好梳理一遍。

3.0.1 ArrayList

  • 数组的扩容为何是通过拷贝来完成的?
  • 1、数组的内存地址需要是连续的,如果一味的在原有基础上扩容,则需要更大且连续的内存空间来完成,显然无法满足。
  • 2、如果强行延展连续的内容空间来满足扩容的需要,将耗费更多的资源来完成其他内存空间的挪动。

在这里插入图片描述

3.0.2 LinkedList

在这里插入图片描述

3.1 JDK1.7 HashMap

在这里插入图片描述

3.1.1 初始化HashMap

new HashMap(),调用构造方法,将初始化HashMap数组长度(JDK1.7的HashMap由数组加链表组成,默认值1 << 4,二进制左移4位=16)和负载因子(影响扩容、get读取数据效率的一个参数,默认0.75即元素超过75%时就要考虑扩容啦)
在这里插入图片描述

3.1.2 put方法

在这里插入图片描述
HashMap.get()方法

    /**
     * Returns the entry associated with the specified key in the
     * HashMap.  Returns null if the HashMap contains no mapping
     * for the key.
     */
    final Entry<K,V> getEntry(Object key) {
   
   
        if (size == 0) {
   
   
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
   
   
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }
hashMap.get(key)方法,先计算key的hash值,然后计算对应下标,在数组中找到对应位置,遍历整个链表找到所在key的value
我们都知道
1、数组查找快,插值慢(因为数组在物理内存中地址连续,对应下标容易找到具体位置,但是插值慢是因为需要将插值后续的元素挨个后移,所以插值慢)
2、而链表插值快,查找慢(插值只需要将next引用的地址修改即可,查找则需要遍历整个表数据)
JDK1.7 hashMap为了解决链表过长所做的努力有:
2.1 在扩容时,同一数组下的链表,重新计算下标,打乱原先过长的链表
2.2 看经典案例2所述,多次二进制右移,保证数据散列度

3.1.3 扩容

1.7HashMap扩容的源码比较简单,源码如下面代码,示意见下图。

    /**
     * Transfers all entries from current table to newTable.
     */
    void transfer(Entry[] newTable, boolean rehash) {
   
   
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {
   
   
            while(null != e) {
   
   
                Entry<K,V> next = e.next;
                if (rehash) {
   
   
                    e.hash = null == e.key ? 0 : hash(e
### Guava Cache 与本地 Map 的区别 #### 基本概念对比 Guava Cache 和 `HashMap` 或其他形式的本地 `Map` 都是用来存储键值对的数据结构,但是两者的设计目标不同。Guava Cache 主要是为了提供更高级别的缓存功能而设计,具有自动清理机制和支持多种回收策略等功能[^2]。 #### 功能特性差异 - **线程安全性** - Guava Cache 默认是线程安全的,并且提供了比 `ConcurrentHashMap` 更丰富的元素失效策略。 - 而普通的 `HashMap` 不具备内置的并发控制能力,在多线程环境下需要额外同步措施才能保证线程安全[^4]。 - Guava Cache 支持基于时间、引用强度以及最大容量等多种维度上的对象回收逻辑,这使得它非常适合用来做临时数据缓冲区。 - 可配置为使用弱引用(`weakKeys`, `weakValues`)或软引用来管理缓存项的生命期,从而让不再活跃的对象更容易被垃圾收集器处理掉。 - 还可以根据设定的最大条目数来限制缓存规模,超出限额时会触发淘汰算法清除旧记录。 - 标准 Java 中的地图类则不具备这样的动态调整能力和自动化维护手段[^5]。 - **统计信息支持** - Guava Cache 内置了详细的性能监控工具,可以方便地追踪命中率等指标以便优化应用表现。 - 传统 Maps 并无此类辅助设施可供利用。 #### 性能考量 对于大多数应用场景而言,由于其内部实现了高效的哈希表查找加上合理的锁分段技术,即使是在高负载情况下也能保持较好的响应速度;而且得益于灵活可定制化的驱逐规则,可以在一定程度上缓解内存压力并减少不必要的磁盘 I/O 操作次数。相比之下,除非特别针对某些极端条件做了特殊优化,否则常规 HashMap 在这方面可能稍显逊色一些[^1]。 #### 使用场景推荐 - 当业务需求涉及到频繁读取相对稳定的小型数据集,并希望获得更好的资源利用率及时效性保障时,选用 Guava Cache 将是一个明智的选择。 - 如果只是简单地保存少量静态映射关系而不涉及复杂的生命周期管理,则可以直接采用标准库里的 map 类型即可满足要求[^3]。 ```java // 创建一个带有LRU(Least Recently Used)置换政策且最多容纳10万条目的guava cache实例 Cache<String, String> myCache = CacheBuilder.newBuilder() .maximumSize(100_000) .expireAfterWrite(10, TimeUnit.MINUTES)// 设置写入后过期时间为十分钟 .build(); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旧梦昂志

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值