Java常見的本地儲存方式

zL66發表於2024-06-23

1、HashMap

1.1 特點

  • K-V 形式
  • 執行緒不安全
  • 查詢效率快

透過執行緒不安全的特點,表現了 HashMap 的應用場景侷限於單執行緒(沒有執行緒併發問題的場景)

1.2 基礎操作

Map<Object,Object> map=new HashMap<>();
// 新增元素
map.put("key","value");
// 獲取元素
map.get("key");
// 刪除元素
map.remove("key");

2、ConcurrentHashMap

為了針對無法應用 HashMap 的多執行緒環境,可以採用併發安全的集合類 ConcurrentHashMap。

對於 HashMap 的併發安全類有許多

  • Hashtable
  • Collections.synchronizedMap()
  • ConcurrentHashMap

concurrentHashMap 在 JDK 採用的 CAS+synchronized,效能最好

Map<Object,Object> map=new ConcurrentHashMap<>();
// 新增元素
map.put("key","value");
// 獲取元素
map.get("key");
// 刪除元素
map.remove("key");

3、Guava 快取

Guava是Google提供的一套JAVA的工具包,而Guava Cache則是該工具包中提供的一套完善的JVM級別的高併發快取框架。其實現機制類似ConcurrentHashMap,但是進行了眾多的封裝與能力擴充套件。作為JVM級別的本地快取框架,Guava Cache具備快取框架該有的眾多基礎特性。

3.1特性

  • 執行緒安全(和 ConcurrentHashMap 一樣)
  • 支援快取記錄的過期設定( 和 redis 快取一樣)
  • 支援快取容量限制與不同淘汰策略( FIFO 演算法、LRU 演算法)

3.2應用場景

初次接觸到 Guava 感覺 redis 分散式快取很像,具有同樣的功能。但是區別在於,redis 分散式快取是部署在應用之外的,儲存大小受限於機器記憶體大小,在於應用通訊存在網路消耗。Guava 是 JVM 本地快取,與應用互動不存在網路消耗,訪問效率快、但是儲存大小受限於 JVM 堆記憶體大小。

應用場景:

  • 儲存內容小
  • 命中率高
  • 訪問速度快
  • 能夠容忍資料不一致。(嘗試使用定時任務進行同步或利用 cancel 訂閱 binLog 進行資料同步)

注意:上面的場景並不是僅僅侷限於 Guava 本地快取,而是整個的本地快取體系

3.3 整合 Guava

3.3.1 匯入 guava 依賴

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>

3.3.2 guava cache 建立

Cache<String, String> localCache = CacheBuilder.newBuilder()
               // 內部雜湊表的最小容量,也就是 cache 的初始容量
                .initialCapacity(5)
               // cache 的最大快取數
                .maximumSize(10)
               // 併發等級,也可以定義為同時操作快取的執行緒數,預設 4 
                .concurrencyLevel(3)
               // 設定過期時間 
                .expireAfterWrite(10, TimeUnit.SECONDS)
                .build();

3.3.3 獲取物件

從快取中獲取資料呼叫的方法為 get (K key, Callable<? extends V> loader) 方法,此方法的含義是根據鍵 key 獲取資料,若 key 不存在,則透過執行指定的 Callable 方法來構造快取,示例程式碼如下所示:

 localCache.get("key", new Callable<ThreadPoolConfigEntity>() {
                @Override
                public Object call() throws Exception {
                    System.out.println("未獲取到 key 資料,可以進行回撥");
                    // 進行資料寫入,如查詢 MySQL、 Redis
                    return null;
                }
            })

3.3.4 刪除資料

3.3.4.1 被動刪除
  • 基於資料大小刪除:LRU+FIFO
  • 基於過期時間刪除:在指定時間內沒有被訪問
  • 基於引用刪除:透過 weakKeys 和 weakValues 方法指定 Cache 只儲存對快取記錄 key 和 value 的弱引用。這樣當沒有其他強引用指向 key 和 value 時,key 和 value 物件就會被垃圾回收器回收
3.3.4.2 主動刪除
//刪除指定的key對應資料
cache.invalidate("s");
//將一批對應的資料刪除
cache.invalidateAll(Arrays.asList("st","r","ing"));
//全部刪除
cache.invalidateAll();

3.3.5 原始碼分析 guava

淺析本地快取技術 - Guava Cache | 京東物流技術團隊 - 知乎 (zhihu.com)

3.3.6 Guava 其他應用

3.3.6.1 布隆過濾器

布隆過濾器 ; 一段 bitMap + 多個 Hash 函式,在新增元素的時候,會對元素進行相應的 hash 運算,將相應的 hash 槽,設定為 1,訪問的時候也是進行 hash 運算,計算出相應的 hash 槽,當所有的 hash 槽都為 1 時,表示該資料才存在。

特點:

  • 存在一定誤差
  • 查詢結果不存在的情況,一定不存在
  • 不可以進行元素的刪除,只能整體刪除
3.3.6.2 guava 實現布隆過濾器
public class BloomFilterTest {

    public static void main(String[] args) {
        long star = System.currentTimeMillis();
        BloomFilter<Integer> filter = BloomFilter.create(
                Funnels.integerFunnel(),
                //預計存放多少資料
                10000000,
                //可以接受的誤報率
                0.01);

        for (int i = 0; i < 10000000; i++) {
            filter.put(i);
        }

        Assert.isTrue(filter.mightContain(1),"不存在");
        Assert.isTrue(filter.mightContain(2),"不存在");
        Assert.isTrue(filter.mightContain(3),"不存在");
        Assert.isTrue(filter.mightContain(10000000),"不存在");
        long end = System.currentTimeMillis();
        System.out.println("執行時間:" + (end - star));
    }
}

這裡Guava引入了一個叫做Funnel的類,Funnel類定義瞭如何把一個具體的物件型別分解為原生欄位值,從而將值分解為Byte以供後面BloomFilter進行hash運算。透過使用這個類,我們可以自己定義一個屬於自己類的Funnel。如下程式碼

enum PersonFunnel implements Funnel<Person> {
    INSTANCE;

    @Override
    public void funnel(Object person, PrimitiveSink into) {
        into.putString(person.getFirstName(), Charset.defaultCharset())
                .putString(person.getLastName(), Charset.defaultCharset());
    }
}

此外,Guava預定義了一些原生型別的Funnel,如String、Long、Integer

3.3.6.3 Guava 時間視窗限流

阿里面試:說說自適應限流?_王磊_InfoQ寫作社群

RateLimiter limiter = RateLimiter.create(5.0); // 建立一個每秒放入5個令牌的RateLimiter
for (int i = 0; i < 10; i++) {
    // 請求一個令牌
    limiter.acquire();
    System.out.println("處理請求: " + i);
}

4、Caffeine ——本地快取之王

推薦地址: java - 效能利器Caffeine快取全面指南 - 宋小黑 - SegmentFault 思否我的技術與生活——本地快取之王-Caffeine | Hexo (iyaovo.github.io)

相關文章