主頁 > 後端開發 > 手寫本地快取實戰2—— 打造正規軍,構建通用本地快取框架

手寫本地快取實戰2—— 打造正規軍,構建通用本地快取框架

2022-11-10 06:59:23 後端開發

大家好,又見面了,


本文是筆者作為掘金技術社區簽約作者的身份輸出的快取專欄系列內容,將會通過系列專題,講清楚快取的方方面面,如果感興趣,歡迎關注以獲取后續更新,


村上春樹有本著名的小說名叫《當我談跑步時我談些什么》,講述了一個人怎么樣通過跑步去悟道出人生的很多哲理與感悟,而讀書的價值,就是讓我們可以將別人參悟出的道理化為己用,將別人走過的路化為充實自己的養料,

在上一篇文章《手寫本地快取實戰1——各個擊破,按需應對實際使用場景》中,我們領略了實際專案中一些零散的快取場景的實作方式,并對快取實作中的LRU淘汰策略TTL過期清理機制實作方案進行了探討,作為《深入理解快取原理與實戰設計》系列專欄的第四篇文章,我們將在上一篇的基礎之上進行升華,一起思考如何構建一個完整且通用的本地快取框架,并在程序中體會快取實作的關鍵點與架構設計的思路,

有的小伙伴可能會有疑問,現在有很多成熟的開源庫,比如JAVA專案的Guava cacheCaffeine CacheSpring Cache等(這些在我們的系列文章中,后面都會逐個介紹),它們都提供了相對完善、開箱即用的本地快取能力,為什么這里還要去自己手寫本地快取呢?這不是重復造輪子嗎?

是也?非也!在編碼的進階之路上,“會用”永遠都只是讓自己停留在入門級別,正所謂知其然更要知其所以然,通過一起探討手寫快取的實作與設計關鍵點,來切身的體會蘊藏在快取架構中的設計哲學,只有真正的掌握其原理,才能在使用中更好的去發揮其最大價值,

快取框架定調

在一個專案系統中需要快取資料的場景會非常多,而且需要快取的資料型別也不盡相同,如果每個使用到快取的地方,我們都單獨的去實作一套快取,那開發小伙伴們的作業量又要上升了,且后續各業務邏輯獨立的快取部分代碼的維護也是一個可預見的頭疼問題,

作為應對之法,我們的本地快取必須往一個更高層級進行演進,使得專案中不同的快取場景都可以通用 —— 也即將其抽象封裝為一個通用的本地快取框架,既然定位為業務通用的本地快取框架,那至少從規范或者能力層面,具備一些框架該有的樣子:

  • 泛型化設計,不同業務維度可以通用

  • 標準化介面,滿足大部分場景的使用訴求

  • 輕量級集成,對業務邏輯不要有太強侵入性

  • 多策略可選,允許選擇不同實作策略甚至是快取存盤機制,打破眾口難調的困局

下面,我們以上述幾個點要求作為出發點,一起來勾勒一個符合上述訴求的本地快取框架的模樣,

快取框架實作

快取容器介面設計

在前一篇文章中,我們有介紹過專案中常見的快取使用場景,基于提及的幾種具體應用場景,我們可以歸納出業務對本地快取的API介面層的一些共性訴求,如下表所示:

介面名稱 含義說明
get 根據key查詢對應的值
put 將對應的記錄添加到快取中
remove 將指定的快取記錄洗掉
containsKey 判斷快取中是否有指定的值
clear 清空快取
getAll 傳入多個key,然后批量查詢各個key對應的值,批量回傳,提升呼叫方的使用效率
putAll 一次性批量將多個鍵值對添加到快取中,提升呼叫方的使用效率
putIfAbsent 如果不存在的情況下則添加到快取中,如果存在則不做操作
putIfPresent 如果key已存在的情況下則去更新key對應的值,如果不存在則不做操作

為了滿足一些場景對資料過期的支持,還需要提供或者多載一些介面用于設定過期時間

介面名稱 含義說明
expireAfter 用于指定某個記錄的過期時間長度
put 多載方法,增加過期時間的引數設定
putAll 多載方法,增加過期時間的引數設定

基于上述提供的各個API方法,我們可以確定快取的具體介面類定義:

/**
 * 快取容器介面
 *
 * @author 架構悟道
 * @since 2022/10/15
 */
public interface ICache<K, V> {
    V get(K key);
    void put(K key, V value);
    void put(K key, V value, int timeIntvl, TimeUnit timeUnit);
    V remove(K key);
    boolean containsKey(K key);
    void clear();
    boolean containsValue(V value);
    Map<K, V> getAll(Set<K> keys);
    void putAll(Map<K, V> map);
    void putAll(Map<K, V> map, int timeIntvl, TimeUnit timeUnit);
    boolean putIfAbsent(K key, V value);
    boolean putIfPresent(K key, V value);
    void expireAfter(K key, int timeIntvl, TimeUnit timeUnit);
}

此外,為了方便框架層面對快取資料的管理與維護,我們也可以定義一套統一的管理API介面:

介面名稱 含義說明
removeIfExpired 如果給定的key過期則直接洗掉
clearAllExpiredCaches 清除當前容器中已經過期的所有快取記錄

同樣地,我們可以基于上述介面說明,敲定介面定義如下:

public interface ICacheClear<K> {
    void removeIfExpired(K key);
    void clearAllExpiredCaches();
}

至此,我們已完成了快取的操作與管理維護介面的定義,下面我們看下如何對快取進行維護管理,

快取管理能力構建

在一個專案中,我們會涉及到多種不同業務維度的資料快取,而不同業務快取對應的資料存管要求也各不相同,

比如對于一個公司行政管理系統而言,其涉及到如下資料的快取:

  • 部門資訊

部門資訊量比較少,且部門組織架構相對固定,所以需要全量存盤,資料不允許過期

  • 員工資訊

員工資訊總體體量也不大,但是員工資訊可能會變更,如員工可能會修改簽名、頭像或者更換部門等,這些操作對實時性的要求并不高,所以需要設定每條記錄快取30分鐘,超時則從快取中洗掉,后續使用到之后重新查詢DB并寫入快取中,

從上面的示例場景中,可以提煉出快取框架需要關注到的兩個管理能力訴求:

  1. 需要支持托管多個快取容器,分別存盤不同的資料,比如部門資訊和員工資訊,需要存盤在兩個獨立的快取容器中,需要支持獲取各自獨立的快取容器進行操作,

  2. 需要支持選擇多種不同能力的快取容器,比如常規的容器、支持資料過期的快取容器等,

  3. 需要能夠支持對快取容器的管理,以及快取基礎維護能力的支持,比如銷毀快取容器、比如清理容器內的過期資料,

基于上述訴求,我們敲定管理介面類如下:

介面名稱 含義說明
createCache 創建一個新的快取容器
getCache 獲取指定的快取容器
destoryCache 銷毀指定的快取容器
destoryAllCache 銷毀所有的快取容器
getAllCacheNames 獲取所有的快取容器名稱

對應地,可以完成介面類的定義:

public interface ICacheManager {
    <K, V> ICache<K, V> getCache(String key, Class<K> keyType, Class<V> valueType);
    void createCache(String key, CacheType cacheType);
    void destoryCache(String key);
    void destoryAllCache();
    Set<String> getAllCacheNames();
}

在上一節關于快取容器的介面劃定描述中,我們敲定了兩大類的介面,一類是提供給業務呼叫的,另一類是給框架管理使用的,為了簡化實作,我們的快取容器可以同時實作這兩類介面,對應UML圖如下:

為了能讓業務自行選擇使用的容器型別,可以通過專門的容器工廠來創建,根據傳入的快取容器型別,創建對應的快取容器實體:

這樣,在CacheManager管理層面,我們可以很輕松的完成創建快取容器或者獲取快取容器的介面實作:

@Override
public void createCache(String key, CacheType cacheType) {
    ICache cache = CacheFactory.createCache(cacheType);
    caches.put(key, cache);
}
@Override
public <K, V> ICache<K, V> getCache(String cacheCollectionKey, Class<K> keyType, Class<V>valueType) {
    try {
        return (ICache<K, V>) caches.get(cacheCollectionKey);
    } catch (Exception e) {
        throw new RuntimeException("failed to get cache", e);
    }
}

過期清理

作為快取,經常會需要設定一個快取有效期,這個有效期可以基于Entry維度進行實作,并且需要支持到期后自動洗掉此條資料,在前一篇文章《本地快取實作的時候需要考慮什么——按需應對實際使用場景》中我們有詳細探討過幾種不同的過期資料清理機制,這里我們直接套用結論,采用惰性洗掉與定期清理結合的策略來實作,

我們對實際快取資料值套個外殼,用于存盤一些管理類的屬性,比如過期時間等,然后我們的容器類實作ICacheClear介面,并在對外提供的業務操作介面中進行惰性洗掉的實作邏輯,

比如對于默認的快取容器而言,其ICacheClear的實作邏輯可能如下:

@Override
public synchronized void removeIfExpired(K key) {
    Optional.ofNullable(data.get(key)).map(CacheItem::hasExpired).ifPresent(expired -> {
        if (expired) {
            data.remove(key);
        }
    });
}
@Override
public synchronized void clearAllExpiredCaches() {
    List<K> expiredKeys = data.entrySet().stream()
            .filter(cacheItemEntry -> cacheItemEntry.getValue().hasExpired())
            .map(Map.Entry::getKey)
            .collect(Collectors.toList());
    for (K key : expiredKeys) {
        data.remove(key);
    }
}

這樣呢,按照惰性洗掉的策略,在各個業務介面中,需要先呼叫removeIfExpired方法移除已過期的資料:

@Override
public Optional<V> get(K key) {
    removeIfExpired(key);
    return Optional.ofNullable(data.get(key)).map(CacheItem::getValue);
}

而在框架管理層面,作為兜底,需要提供定時機制,來清理各個容器中的過期資料:

public class CacheManager implements ICacheManager {
    private Map<String, ICache> caches = new ConcurrentHashMap<>();
    private List<ICacheHandler> handlers = Collections.synchronizedList(new ArrayList<>());

    public CacheManager() {
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("start clean expired data timely");
                handlers.forEach(ICacheHandler::clearAllExpiredCaches);
            }
        }, 60000L, 1000L * 60 * 60 * 24);
    }

    // 省略其它方法
}

這樣呢,對快取的資料過期能力的支撐便完成了,

構建不同能力的快取容器

作為快取框架,勢必需要面臨不同的業務各不相同的訴求,在框架搭建層面,我們整體框架的設計實作遵循著里式替換的原則,且借助泛型進行構建,這樣,我們就可以實作給定的介面類,提供不同的快取容器來滿足業務的場景需要,

比如我們需要提供兩種型別的容器:

  • 普通的鍵值對容器

  • 支持設定最大容量且使用LRU策略淘汰的鍵值對容器

可以直接創建兩個不同的容器類,然后分別實作介面方法即可,對應UML示意如下:

最后,需要將我們創建的不同的容器型別在CacheType中注冊下,這樣呼叫方便可以通過指定不同的CacheType來選擇使用不同的快取容器,

@AllArgsConstructor
@Getter
public enum CacheType {
    DEFAULT(DefaultCache.class),
    LRU(LruCache.class);
    private Class<? extends ICache> classType;
}

快取框架使用初體驗

至此呢,我們的本地快取框架就算是搭建完成了,在業務中有需要使用快取的場景直接使用CacheManager中的createCache方法創建出對應快取容器,然后呼叫快取容器的介面進行快取的操作即可,

我們來呼叫一下,看看使用體驗與功能如何,比如我們現在需要為用戶資訊創建一個獨立的快取,然后往里面寫入一個用戶記錄并設定1s后過期:

public static void main(String[] args) {
    manager.createCache("userData", CacheType.LRU);
    ICache<String, User> userDataCache = manager.getCache("userData", String.class, User.class);
    userDataCache.put("user1", new User("user1"));
    userDataCache.expireAfter("user1", 1, TimeUnit.SECONDS);
    userDataCache.get("user1").ifPresent(value -> System.out.println("找到用戶:" + value));
    try {
        Thread.sleep(2000L);
    } catch (Exception e) {
    }
    boolean present = userDataCache.get("user1").isPresent();
    if (!present) {
        System.out.println("用戶不存在");
    }
}

執行之后,輸出結果為:

找到用戶:User(userName=user1)
用戶不存在

可以發現,完全符合我們的預期,且過期資料清理機也已生效,同樣地,如果需要為其它資料創建獨立的快取存盤,也參考上面的邏輯,創建自己獨立的快取容器即可,

擴展探討

分布式場景下本地快取漂移現象應對策略

在本系列的開篇文章《聊一聊作為高并發系統基石之一的快取,會用很簡單,用好才是技識訓》中,我們有提到過一個本地快取在分布式場景下存在的一個快取漂移問題:

解決快取漂移問題,一個簡單的方案就是借助集中式快取來解決(比如Redis),但是在一些簡單的小型分布式節點中,不太值得引入太多額外公共組件服務的時候,也可以考慮對本地快取進行增強,提供一些同步更新各節點快取的機制,

下面介紹兩個兩個實作思路,

  • 組網廣播

在一些小型組網中,當某一個節點執行快取更新操作的時候,都同時廣播一個事件通知給其余節點,各個節點都進行節點自身快取資料的更新,

  • 定時輪詢式

一般的系統中,都會有個資料庫節點(比如MySQL),我們可以借助資料庫作為一個中間輔助,每次更新之后,都將快取的更新資訊寫入一個獨立的表中,然后各個快取節點都定時從DB中拉取增量更新的記錄,然后更新到本地快取中,

值得注意的是,上面這些思路僅適用于寫操作不是很頻繁、并且對實時一致性要求不是特別嚴苛的場景 —— 當然,在實際專案中,真正這么搞的情況比較少,因為本地快取設計存在的初衷就是用來應對單行程內的快取獨立快取使用,而這種涉及到多節點之間快取資料一致保證的場景,本就不是本地快取的擅長領域,所以在分布式場景下,往往都會直接選擇使用集中式快取,

當然啦,上面我們提到的兩種本地快取同步的機制,都是相對簡單的一種實作,一些比較主流的本地快取框架,也有提供一些集群化資料同步的機制,比如Ehcache就提供了高達5種不同的集群化策略,以達到各個本地快取節點資料保持一致的效果:

  • RMI組播方式

  • JMS訊息方式

  • Cache Server模式

  • JGroup方式

  • Terracotta方式

后續文章中我們會一起探討下Ehcache的相關內容,這里先賣個關子,到時候我們細聊,

小結回顧

好啦,關于手寫本地通用快取框架的內容,我們就聊這么多,通過本篇內容,我們完成了對前面文章中提過的一些快取設計理論原則的實踐,并一步步的闡述了快取的設計與實作關鍵點,更展示了如何讓一個快取模塊從簡單的能用變為好用、通用,

當然,本篇內容主要是為了通過手寫快取的模式,來讓大家更切身的體會快取實作中的關鍵點與架構設計思路,并能在后續的使用中更正確的去使用,在實際專案中,除非一些特殊定制訴求需要手動實作快取機制外,我們倒也不必自己費時勞神地去手寫快取框架,直接采用現有的開源方案即可,比如JAVA類的專案,目前有很多開源庫(比如Guava cacheCaffeine CacheSpring Cache等)都提供了相對完善、開箱即用的本地快取能力,可以直接使用,在后面的系列文章中,我們將逐個剖析,

那么,關于快取模塊的設計與實作,你是否也曾手動撰寫過呢?你是如何解決這些問題的呢?你關于這些問題你是否有更好的理解與應對策略呢?歡迎評論區一起交流下,期待和各位小伙伴們一起切磋、共同成長,

?? 補充說明1

本文屬于《深入理解快取原理與實戰設計》系列專欄的內容之一,該專欄圍繞快取這個宏大命題進行展開闡述,全方位、系統性地深度剖析各種快取實作策略與原理、以及快取的各種用法、各種問題應對策略,并一起探討下快取設計的哲學,

如果有興趣,也歡迎關注此專欄,

?? 補充說明2

  • 關于本文中涉及的演示代碼的完整示例,我已經整理并提交到github中,如果您有需要,可以自取:https://github.com/veezean/JavaBasicSkills

我是悟道,聊技術、又不僅僅聊技術~

如果覺得有用,請點贊 + 關注讓我感受到您的支持,也可以關注下我的公眾號【架構悟道】,獲取更及時的更新,

期待與你一起探討,一起成長為更好的自己,

本文來自博客園,作者:架構悟道,歡迎關注公眾號[架構悟道]持續獲取更多干貨,轉載請注明原文鏈接:https://www.cnblogs.com/softwarearch/p/16870925.html

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/530532.html

標籤:其他

上一篇:jps命令的簡介及使用方法說明

下一篇:【深入淺出 Yarn 架構與實作】2-2 Yarn 基礎庫 - 底層通信庫 RPC

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more