Redis實戰(黑馬點評)——關于緩存(緩存更新策略、緩存穿透、緩存雪崩、緩存擊穿、Redis工具)

redis實現查詢緩存的業務邏輯?

?service層實現

@Overridepublic Result queryById(Long id) {String key = CACHE_SHOP_KEY + id;// 現查詢redis內有沒有數據String shopJson = (String) redisTemplate.opsForValue().get(key);if(StrUtil.isNotBlank(shopJson)){ // 如果redis的數據為存在,那么解析為對象// 將json轉為對象Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}// 如果不存在,就先查數據庫,再存入redisShop shop = getById(id);if(shop == null){return Result.fail("店鋪不存在");}// 存在就寫入redis,包括將對象轉為jsonredisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));return Result.ok(shop);}

緩存更新策略?

緩存更新策略的最佳實踐方案:

  1. 低一致性需求:使用Redis自帶的內存淘汰機制
  2. 高一致性需求:主動更新,并以超時刪除作為處理方案
讀操作:(查詢)
  • 緩存命中則直接返回
  • 緩存未命中則查詢數據庫,并寫入緩存,設定超時時間
寫操作:(增刪改)
  • 先寫數據庫,然后再刪除緩存
  • 要確保數據庫與緩存操作的原子性

緩存穿透

緩存穿透產生的原因是什么?

  • 用戶請求的數據在緩存中和數據庫中都不存在,不斷發起這樣的請求,給數據庫帶來巨大壓力。

緩存穿透的解決方案有哪些?

  • 緩存null值
  • 布隆過濾器
  • 增強id的復雜度,避免被猜測id規律
  • 做好數據的基本格式校驗
  • 加強用戶權限校驗
  • 做好熱點參數的限流

?緩存空對象的方法解決緩存穿透

@Override  
public Result queryById(Long id) {  // 使用店鋪ID構建緩存鍵  String key = CACHE_SHOP_KEY + id;  // 檢查店鋪信息是否已經緩存到Redis中  String shopJson = (String) redisTemplate.opsForValue().get(key);  // 如果緩存中存在數據,則將JSON字符串解析為Shop對象  if (StrUtil.isNotBlank(shopJson)) {   // 將JSON轉換為Shop對象  Shop shop = JSONUtil.toBean(shopJson, Shop.class);  return Result.ok(shop); // 返回店鋪對象作為成功結果  }  // 如果緩存中包含表示無數據的占位符,則返回錯誤信息  if ("#".equals(shopJson)) {  return Result.fail("沒有店鋪相關信息"); // 沒有店鋪信息可用  }  // 如果緩存中未找到店鋪數據,則查詢數據庫  Shop shop = getById(id);  // 如果數據庫中不存在該店鋪  if (shop == null) {  // 在緩存中存儲一個占位符,以表示該店鋪不存在  // 這可以防止對同一ID的進一步查詢再次訪問數據庫  redisTemplate.opsForValue().set(key, "#", CACHE_NULL_TTL, TimeUnit.MINUTES);  return Result.fail("店鋪不存在"); // 返回錯誤,指示店鋪不存在  }  // 如果找到店鋪,則將店鋪對象以JSON字符串的形式存儲到緩存中  redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);  return Result.ok(shop); // 返回店鋪對象作為成功結果  
}

緩存雪崩

緩存擊穿?

緩存擊穿是指在高并發環境下,某個熱點數據的緩存同時失效,導致大量請求直接訪問數據庫,從而造成數據庫壓力驟增的現象。

緩存擊穿解決方案

解決方案優點缺點
互斥鎖

- 沒有額外的內存消耗

- 保證一致性

- 實現簡單

- 線程需要等待,性能受到影響

- 可能有死鎖風險

邏輯鎖- 線程無需等待,性能較好

- 不保證一致性

- 有額外的內存消耗

- 實現復雜

基于互斥鎖解決緩存擊穿?

?基于互斥鎖解決緩存擊穿 + 緩存空對象的方法解決緩存穿透

// 查詢店鋪信息,使用互斥鎖解決緩存擊穿問題
public Shop queryWithMutex(Long id) {// 構造緩存的keyString key = CACHE_SHOP_KEY + id;// 1. 先從Redis中查詢店鋪信息String shopJson = (String) redisTemplate.opsForValue().get(key);// 如果Redis中存在緩存數據,直接解析JSON并返回對象if (StrUtil.isNotBlank(shopJson)) {// 將JSON字符串轉換為Shop對象Shop shop = JSONUtil.toBean(shopJson, Shop.class);return shop;}// 如果Redis中緩存的值為"#", 表示數據庫中沒有該店鋪信息if ("#".equals(shopJson)) {return null;}// 2. 構造互斥鎖的keyString lockKey = "lock:shop:" + id;// 定義店鋪對象Shop shop = null;try {// 嘗試獲取互斥鎖boolean isLock = tryLock(lockKey);// 如果獲取鎖失敗,線程休眠50ms后重試if (!isLock) {Thread.sleep(50); // 等待50msreturn queryWithMutex(id); // 遞歸調用,再次嘗試獲取鎖}// 3. 如果沒有獲取到緩存數據,查詢數據庫shop = getById(id);// 模擬數據庫查詢的延時,生產環境應該去掉這段代碼// Thread.sleep(200);// 4. 如果數據庫中沒有該店鋪信息if (shop == null) {// 在Redis中存儲一個特殊的標記值"#", 表示該店鋪不存在redisTemplate.opsForValue().set(key, "#", CACHE_NULL_TTL, TimeUnit.MINUTES);return null;}// 5. 如果數據庫中有數據,將店鋪信息存入Redis// 將Shop對象轉換為JSON字符串redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (InterruptedException e) {// 捕獲線程中斷異常throw new RuntimeException(e);} finally {// 釋放互斥鎖unlock(lockKey);}// 返回查詢到的店鋪信息return shop;
}// 封裝獲取鎖,釋放鎖
// 嘗試獲取互斥鎖
private boolean tryLock(String key) {// 使用Redis的setIfAbsent方法嘗試設置鎖// 如果key不存在,則設置成功并返回true;否則返回falseBoolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);// 并設置一個過期時間(這里是 10 秒)return BooleanUtil.isTrue(flag);
}// 釋放互斥鎖
private void unlock(String key) {// 刪除鎖對應的keyredisTemplate.delete(key);
}

setIfAbsent?方法是 Redis 中的一種操作,用于設置一個鍵的值,僅在該鍵不存在的情況下進行設置。具體來說,它的功能如下:

  • 鍵不存在時:如果指定的鍵(key)在 Redis 中不存在,則將其設置為指定的值(在這個例子中是?"1"),并可以指定該鍵的過期時間(這里是 10 秒)。此時,方法返回?true

  • 鍵已存在時:如果指定的鍵已經存在于 Redis 中,則不會進行任何操作,保持原有的值不變,方法返回?false

基于邏輯鎖解決緩存擊穿?

 // 創建一個固定大小的線程池,用于緩存重建任務,避免頻繁創建線程帶來的開銷private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);/*** 查詢商鋪信息,考慮邏輯過期* @param id 商鋪ID* @return 商鋪信息,如果不存在或已過期則返回null*/public Shop queryWithLogicalExpire(Long id){// 構建緩存的key,用于從Redis中查詢對應的商鋪信息String key = CACHE_SHOP_KEY + id;// 1. 從Redis查詢商鋪緩存,獲取商鋪信息的JSON字符串String shopJson = (String) redisTemplate.opsForValue().get(key);// 2. 判斷緩存是否存在if (StrUtil.isBlank(shopJson)) {// 3. 緩存不存在,直接返回nullreturn null;}// 4. 緩存命中,需要先將JSON字符串反序列化為RedisData對象RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);// 從RedisData對象中提取商鋪信息,并將其反序列化為Shop對象Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);// 獲取緩存的過期時間LocalDateTime expireTime = redisData.getExpireTime();// 5. 判斷緩存是否過期if (expireTime.isAfter(LocalDateTime.now())) {// 5.1. 緩存未過期,直接返回商鋪信息return shop;}// 構建鎖的key,用于控制緩存重建的并發訪問String lockKey = LOCK_SHOP_KEY + id;// 嘗試獲取鎖,確保緩存重建操作的線程安全boolean isLock = tryLock(lockKey);// 6.2. 判斷是否成功獲取鎖if (isLock) {// 6.3. 成功獲取鎖,開啟獨立線程進行緩存重建,避免阻塞主線程CACHE_REBUILD_EXECUTOR.submit(() -> {try {// 重建緩存,將最新的商鋪信息保存到Redis中,并設置過期時間this.saveShop2Redis(id, 20L); // 假設20L是過期時間,單位為秒} catch (Exception e) {// 如果在緩存重建過程中發生異常,拋出運行時異常,并記錄日志throw new RuntimeException(e);} finally {// 無論緩存重建成功與否,都需要釋放鎖,避免死鎖unlock(lockKey);}});}// 返回當前查詢到的商鋪信息(可能已過期)return shop;}public void saveShop2Redis(Long id, Long expireSeconds) {// 1. 查詢店鋪數據Shop shop = getById(id);// 2. 封裝邏輯過期時間RedisData redisData = new RedisData();redisData.setData(shop);redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));// 3. 寫入RedisredisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));}

RedisData類

@Data
public class RedisData {private LocalDateTime expireTime;private Object data;
}

?封裝redis工具類

    @Resourceprivate CacheClient cacheClient;public static final String CACHE_SHOP_KEY = "cache:shop:";public static final Long CACHE_SHOP_TTL = 30L;@Overridepublic Result queryById(Long id)  {// 解決緩存穿透
//        Shop shop = cacheClient // 傳入一個從數據庫內獲取Shop對象的函數:this::getById
//                .queryWithPassThrough(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);// 互斥鎖解決緩存擊穿Shop shop = cacheClient.queryWithMutex(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);// 邏輯過期解決緩存擊穿
//        Shop shop = cacheClient
//                .queryWithLogicalExpire(CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);if(shop == null){return Result.fail("店鋪不存在");}return Result.ok(shop);}

1.?queryWithPassThrough

這個方法用于處理緩存穿透問題。緩存穿透是指查詢一個數據庫中不存在的數據,由于緩存中也沒有這個數據,所以每次查詢都會直接打到數據庫上,增加數據庫的壓力。

  • 參數

    • keyPrefix:緩存的前綴。

    • id:緩存的ID。

    • type:返回對象的類型。

    • dbFallback:數據庫查詢的回調函數。

    • time:緩存時間。

    • unit:時間單位。

2.?queryWithLogicalExpire

這個方法用于處理緩存擊穿問題。緩存擊穿是指一個緩存中非常熱門的數據突然過期,導致大量請求同時打到數據庫上,增加數據庫的壓力。

  • 參數:與 queryWithPassThrough 相同。

最大的缺點是運行前要把所有緩存加到redis內,不然怎么查都是null

3.?queryWithMutex(互斥鎖)

這個方法結合了 queryWithPassThroughqueryWithLogicalExpire 的功能,用于處理緩存穿透和緩存擊穿問題。

  • 參數:與 queryWithPassThrough 相同。

@Slf4j
@Component
public class CacheClient {public static final Long CACHE_NULL_TTL = 2L;public static final String LOCK_SHOP_KEY = "lock:shop:";private final RedisTemplate redisTemplate;private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);public CacheClient(RedisTemplate redisTemplate) {this.redisTemplate = redisTemplate;}public void set(String key, Object value, Long time, TimeUnit unit) {redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);}public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {// 設置邏輯過期RedisData redisData = new RedisData();redisData.setData(value);redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));// 寫入RedisredisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));}public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){String key = keyPrefix + id;// 1.從redis查詢商鋪緩存String json = (String) redisTemplate.opsForValue().get(key);// 2.判斷是否存在if (StrUtil.isNotBlank(json)) {// 3.存在,直接返回return JSONUtil.toBean(json, type);}// 判斷命中的是否是空值if (json != null) {// 返回一個錯誤信息return null;}// 4.不存在,根據id查詢數據庫R r = dbFallback.apply(id);// 5.不存在,返回錯誤if (r == null) {// 將空值寫入redisredisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回錯誤信息return null;}// 6.存在,寫入redisthis.set(key, r, time, unit);return r;}public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;// 1.從redis查詢商鋪緩存String json = (String) redisTemplate.opsForValue().get(key);// 2.判斷是否存在if (StrUtil.isBlank(json)) {// 3.存在,直接返回return null;}// 4.命中,需要先把json反序列化為對象RedisData redisData = JSONUtil.toBean(json, RedisData.class);R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);LocalDateTime expireTime = redisData.getExpireTime();// 5.判斷是否過期if(expireTime.isAfter(LocalDateTime.now())) {// 5.1.未過期,直接返回店鋪信息return r;}// 5.2.已過期,需要緩存重建// 6.緩存重建// 6.1.獲取互斥鎖String lockKey = LOCK_SHOP_KEY + id;boolean isLock = tryLock(lockKey);// 6.2.判斷是否獲取鎖成功if (isLock){// 6.3.成功,開啟獨立線程,實現緩存重建CACHE_REBUILD_EXECUTOR.submit(() -> {try {// 查詢數據庫R newR = dbFallback.apply(id);// 重建緩存this.setWithLogicalExpire(key, newR, time, unit);} catch (Exception e) {throw new RuntimeException(e);}finally {// 釋放鎖unlock(lockKey);}});}// 6.4.返回過期的商鋪信息return r;}public <R, ID> R queryWithMutex(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;// 1.從redis查詢商鋪緩存String shopJson = (String) redisTemplate.opsForValue().get(key);// 2.判斷是否存在if (StrUtil.isNotBlank(shopJson)) {// 3.存在,直接返回return JSONUtil.toBean(shopJson, type);}// 判斷命中的是否是空值if (shopJson != null) {// 返回一個錯誤信息return null;}// 4.實現緩存重建// 4.1.獲取互斥鎖String lockKey = LOCK_SHOP_KEY + id;R r = null;try {boolean isLock = tryLock(lockKey);// 4.2.判斷是否獲取成功if (!isLock) {// 4.3.獲取鎖失敗,休眠并重試Thread.sleep(50);return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit);}// 4.4.獲取鎖成功,根據id查詢數據庫r = dbFallback.apply(id);// 5.不存在,返回錯誤if (r == null) {// 將空值寫入redisredisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回錯誤信息return null;}// 6.存在,寫入redisthis.set(key, r, time, unit);} catch (InterruptedException e) {throw new RuntimeException(e);}finally {// 7.釋放鎖unlock(lockKey);}// 8.返回return r;}private boolean tryLock(String key) {Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}private void unlock(String key) {redisTemplate.delete(key);}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/67189.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/67189.shtml
英文地址,請注明出處:http://en.pswp.cn/web/67189.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

算法-加油站問題

hello 大家好&#xff01;今天開寫一個新章節&#xff0c;每一天一道算法題。讓我們一起來學習算法思維吧&#xff01; function canCompleteCircuit(gas, cost) {// 加油站的總數const n gas.length;// 記錄總剩余油量&#xff0c;若總剩余油量小于 0&#xff0c;說明無法繞環…

訊飛繪鏡(ai生成視頻)技術淺析(二):大模型

1.訊飛星火大模型的基礎架構 2.自然語言處理(NLP)技術的具體實現 3.腳本生成的具體過程與模型公式 4.分鏡生成的具體過程與模型公式 5.視頻生成與編輯的技術細節 6.關鍵技術公式的詳細推導與解釋 一、訊飛星火大模型的基礎架構 訊飛星火大模型是基于Transformer架構的深…

【機器學習】深入探索SVM:支持向量機的原理與應用

目錄 &#x1f354; SVM引入 1.1什么是SVM? 1.2支持向量機分類 1.3 線性可分、線性和非線性的區分 &#x1f354; 小結 學習目標 知道SVM的概念 &#x1f354; SVM引入 1.1什么是SVM? 看一個故事&#xff0c;故事是這樣子的&#xff1a; 在很久以前的情人節&#xf…

pycharm 運行遠程環境問題 Error:Failed to prepare environment.

問題排查 拿到更詳細的報錯信息&#xff1a; Help > Diagnostic Tools > Debug Log Settings section: 添加下面的配置 com.intellij.execution.configurations.GeneralCommandLine 重顯報錯&#xff0c;我這里是再次運行代碼打開 Help | Collect Logs and Diagnosti…

一組開源、免費、Metro風格的 WPF UI 控件庫

前言 今天大姚給大家分享一個開源、免費、Metro風格的 WPF UI 控件庫&#xff1a;MahApps.Metro。 項目介紹 MahApps.Metro 是一個開源、免費、Metro風格的 WPF UI 控件庫&#xff0c;提供了現代化、平滑和美觀的控件和樣式&#xff0c;幫助開發人員輕松創建具有現代感的 Win…

讀寫和解析簡單的 nc 文件

NetCDF 文件格式在氣象數據工程領域占據著舉足輕重的地位&#xff0c;其結構靈活、強兼容性等優勢使其成為該領域的一個標準。無論是從事學術研究還是工程實踐&#xff0c;掌握這種數據格式變得越發重要。其次&#xff0c;我注意到目前社區中氣象編程大多數課程都聚焦于某個特定…

Mac m1,m2,m3芯片使用nvm安裝node14報錯

使用nvm安裝了node 12/16/18都沒有問題&#xff0c;到14就報錯了。第一次看到這個報錯有點懵&#xff0c;查詢資料發現是Mac芯片的問題。 Issue上提供了兩個方案&#xff1a; 1、為了在arm64的Mac上安裝node 14&#xff0c;需要使用Rosseta&#xff0c;可以通過以下命令安裝 …

【計算機網絡】host文件

host文件的主要功能&#xff1a; 域名解析 本地映射&#xff1a;host文件的主要功能是將**域名映射到相應的 IP 地址**。當計算機需要訪問一個網站或服務時&#xff0c;它會首先在 host文件中查找該域名對應的 IP 地址。如果在 host文件中找到了匹配的域名和 IP 地址映射&…

vue3中customRef的用法以及使用場景

1. 基本概念 customRef 是 Vue3 提供的用于創建自定義響應式引用的 API&#xff0c;允許顯式地控制依賴追蹤和觸發響應。它返回一個帶有 get 和 set 函數的工廠函數來自定義 ref 的行為。 1.1 基本語法 import { customRef } from vuefunction createCustomRef(value) {retu…

周末總結(2024/01/25)

工作 人際關系核心實踐&#xff1a; 要學會隨時回應別人的善意&#xff0c;執行時間控制在5分鐘以內 堅持每天早會打招呼 遇到接不住的話題時拉低自己&#xff0c;抬高別人(無陰陽氣息) 朋友圈點贊控制在5min以內&#xff0c;職場社交不要放在5min以外 職場的人際關系在面對利…

C++和Python實現SQL Server數據庫導出數據到S3并導入Redshift數據倉庫

用C實現高性能數據處理&#xff0c;Python實現操作Redshift導入數據文件。 在Visual Studio 2022中用C和ODBC API導出SQL Server數據庫中張表中的所有表的數據為CSV文件格式的數據流&#xff0c;用逗號作為分隔符&#xff0c;用雙引號包裹每個數據&#xff0c;字符串類型的數據…

基于OpenCV實現的答題卡自動判卷系統

一、圖像預處理 ?? 二、查找答題卡輪廓 ?? 三、透視變換 ?? 四、判卷與評分 ?? 五、主函數 六、完整代碼+測試圖像集 總結 ?? 在這篇博客中,我將分享如何使用Python結合OpenCV庫開發一個答題卡自動判卷系統。這個系統能夠自動從掃描的答題卡中提取信…

Android AOP:aspectjx

加入引用 在整個項目的 build.gradle 中&#xff0c;添加 classpath "com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10" 可以看到測試demo的 gradle 版本是很低的。 基于 github 上的文檔&#xff0c;可以看到原版只支持到 gradle 4.4 。后續需要使…

第84期 | GPTSecurity周報

GPTSecurity是一個涵蓋了前沿學術研究和實踐經驗分享的社區&#xff0c;集成了生成預訓練Transformer&#xff08;GPT&#xff09;、人工智能生成內容&#xff08;AIGC&#xff09;以及大語言模型&#xff08;LLM&#xff09;等安全領域應用的知識。在這里&#xff0c;您可以找…

TCP/IP 協議:互聯網通信的基石

TCP/IP 協議:互聯網通信的基石 引言 TCP/IP協議,全稱為傳輸控制協議/互聯網協議,是互聯網上應用最為廣泛的通信協議。它定義了數據如何在網絡上傳輸,是構建現代互聯網的基礎。本文將深入探討TCP/IP協議的原理、結構、應用以及其在互聯網通信中的重要性。 TCP/IP 協議概述…

蛇年特別版貪吃蛇H5小游戲

該作者的原創文章目錄: 生產制造執行MES系統的需求設計和實現 企業后勤管理系統的需求設計和實現 行政辦公管理系統的需求設計和實現 人力資源管理HR系統的需求設計和實現 企業財務管理系統的需求設計和實現 董事會辦公管理系統的需求設計和實現 公司組織架構圖設計工具 庫存管…

MapReduce,Yarn,Spark理解與執行流程

MapReduce的API理解 Mapper 如果是單詞計數&#xff1a;hello&#xff1a;1&#xff0c; hello&#xff1a;1&#xff0c; world&#xff1a;1 public void map(Object key, // 首字符偏移量Text value, // 文件的一行內容Context context) // Mapper端的上下文&#xff0c;…

如何將xps文件轉換為txt文件?xps轉為pdf,pdf轉為txt,提取pdf表格并轉為txt

文章目錄 xps轉txt方法一方法二 pdf轉txt整頁轉txt提取pdf表格&#xff0c;并轉為txt 總結另外參考XPS文件轉換為TXT文件XPS文件轉換為PDF文件PDF文件轉換為TXT文件提取PDF表格并轉為TXT示例代碼&#xff08;部分&#xff09; 本文測試代碼已上傳&#xff0c;路徑如下&#xff…

Day26-【13003】短文,什么是順序表?順序表和數組、內存地址的關系?順序表的插入、刪除操作如何實現?操作的時間復雜度是多少?

文章目錄 第二節&#xff0c;線性表的順序存儲及實現概覽什么是順序表和鏈表&#xff1f;順序存儲的叫順序表順序表和數組還有內存地址的關系&#xff1f;順序表的基本操作如何實現&#xff1f;1、插入操作如何實現&#xff1f;2、刪除操作如何實現&#xff1f;3、賦值和查找操…

【含開題報告+文檔+PPT+源碼】基于SpringBoot的校園跑腿管理系統

開題報告 本文旨在探討校園跑腿系統的設計與實現&#xff0c;通過深入研究與分析&#xff0c;實現了一套包含用戶管理、發布跑腿單、跑腿搶單、跑腿單評論、在線留言以及用戶在線充值等功能的綜合性系統。該系統以提高校園內物品跑腿與配送效率為核心目標&#xff0c;為廣大學…