LRU 和 DiskLRU实现相册缓存器

我是写Linux后端的(golang、c++、py),后端缓存算法通常是指的是内存里面的lru、或diskqueue,都是独立使用。 很少有用内存lru与disklru结合的场景需求。近段时间研究android开发,里面有一些设计思想值得后端学习。

写这篇文章的原因:

看到了android开发里面的一个片段

于是在画板里面手绘下图:

为了简化测试,用Python编程语言实现

python 复制代码
import tkinter as tk
from tkinter import ttk, messagebox
from PIL import Image, ImageTk, ImageOps
import requests
from io import BytesIO
import threading
import queue
from functools import lru_cache
from diskcache import Cache
import os

# 配置缓存
CACHE_DIR = "image_cache"
os.makedirs(CACHE_DIR, exist_ok=True)
disk_cache = Cache(CACHE_DIR)  # 磁盘缓存(自动管理容量)

@lru_cache(maxsize=5)  # LRU缓存(仅记录URL)
def get_from_lru(url):
    pass

class ImageLoader:
    def __init__(self):
        self.queue = queue.Queue()
        self.thread = threading.Thread(target=self._worker, daemon=True)
        self.thread.start()
    
    def load(self, url, callback):
        self.queue.put((url, callback))
    
    def _worker(self):
        while True:
            url, callback = self.queue.get()
            data = None
            cache_type = "error"
            
            # 检查LRU
            if get_from_lru.cache_info().currsize > 0:
                data = disk_cache.get(url)
                if data:
                    cache_type = "lru"
            
            # 检查磁盘
            if not data:
                data = disk_cache.get(url)
                if data:
                    cache_type = "disk"
                    get_from_lru(url)  # 更新LRU标记
            
            # 网络加载
            if not data:
                try:
                    res = requests.get(url, timeout=10)
                    res.raise_for_status()
                    data = res.content
                    cache_type = "network"
                    disk_cache.set(url, data)  # 自动处理容量限制
                    get_from_lru(url)
                except Exception as e:
                    callback(None, cache_type)
                    continue
            
            # 返回结果
            try:
                img = Image.open(BytesIO(data))
                callback(img, cache_type)
            except:
                callback(None, "error")

class ImageViewerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("图片查看器")
        self.urls = [f"https://picsum.photos/seed/img{i}/800/600" for i in range(1, 11)]
        self.current = 0
        self.loader = ImageLoader()
        self._create_widgets()
    
    def _create_widgets(self):
        frame = ttk.Frame(self.root, padding=10)
        frame.pack(fill=tk.BOTH, expand=True)
        
        # 图片显示区域
        self.img_label = ttk.Label(frame)
        self.img_label.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 导航按钮
        btn_frame = ttk.Frame(frame)
        btn_frame.pack(fill=tk.X, pady=5)
        
        self.prev_btn = ttk.Button(btn_frame, text="◀ 上一张", command=self.prev_image)
        self.prev_btn.pack(side=tk.LEFT, padx=5)
        
        self.next_btn = ttk.Button(btn_frame, text="下一张 ▶", command=self.next_image)
        self.next_btn.pack(side=tk.RIGHT, padx=5)
        
        # 缓存状态
        self.status_label = ttk.Label(frame, text="缓存状态: LRU(0/5), 磁盘(0/8)")
        self.status_label.pack(fill=tk.X, pady=2)
        
        # 加载指示器
        self.loading = ttk.Label(self.img_label, text="加载中...", font=("SimHei", 12))
    
    def _load_image(self, index):
        self.current = index
        url = self.urls[index]
        self.status_label.config(text="加载中...")
        self.loading.place(relx=0.5, rely=0.5, anchor="center")
        self.prev_btn.config(state=tk.DISABLED)
        self.next_btn.config(state=tk.DISABLED)
        
        self.loader.load(url, self._on_loaded)
    
    def _on_loaded(self, img, cache_type):
        self.root.after(0, lambda: self._update_display(img, cache_type))
    
    def _update_display(self, img, cache_type):
        self.loading.place_forget()
        self.prev_btn.config(state=tk.NORMAL)
        self.next_btn.config(state=tk.NORMAL)
        
        if img:
            # 调整图片大小
            max_w = self.img_label.winfo_width() - 20
            max_h = self.img_label.winfo_height() - 20
            img = ImageOps.contain(img, (max_w or 500, max_h or 400))
            self.photo = ImageTk.PhotoImage(img)
            self.img_label.config(image=self.photo)
            
            # 更新缓存状态
            lru = get_from_lru.cache_info().currsize
            disk = len(disk_cache)
            self.status_label.config(
                text=f"缓存状态: LRU({lru}/5) [{cache_type.upper()}], 磁盘({disk}/8)"
            )
        else:
            messagebox.showerror("错误", "无法加载图片")
    
    def prev_image(self):
        self._load_image((self.current - 1) % 10)
    
    def next_image(self):
        self._load_image((self.current + 1) % 10)

if __name__ == "__main__":
    root = tk.Tk()
    root.geometry("800x600")
    app = ImageViewerApp(root)
    app._load_image(0)
    root.mainloop()

测试效果:

经过缓存的图片从内存或文件加载,速度快了很多。 用空间换时间_

相关推荐
小旭95271 小时前
商品详情实现与缓存问题(穿透、击穿、雪崩)解决方案
java·数据库·spring boot·后端·缓存
Mr. zhihao2 小时前
Redis 内存管理深度解析:过期删除与内存淘汰策略
数据库·redis·缓存
Solis程序员2 小时前
分层缓存调度:削峰控压下的 Feed 流高性能设计
缓存
難釋懷2 小时前
Redis内存回收-过期key处理
数据库·redis·缓存
Nayxxu5 小时前
Gemini 长上下文成本估算表:输入、输出、缓存怎么拆
java·缓存
爱莉希雅&&&6 小时前
Redis哨兵模式和主从复制和集群模式搭建与扩容缩容
linux·redis·缓存·集群·哨兵·数据库同步
JohnnyDeng947 小时前
OkHttp 拦截器链与缓存策略:深度解析网络层的核心机制
okhttp·缓存
MRSM_017 小时前
Redis 缓存、队列、排行榜的核心用法
数据库·redis·缓存
Trouvaille ~8 小时前
【Redis篇】Redis 安装与启动:快速搭建一个 Redis 环境
数据库·redis·后端·ubuntu·缓存·环境搭建·安装教程
fengxin_rou8 小时前
【Feed 高并发架构实战】:雪花 ID + 三级缓存 + 计数旁路设计详解
数据库·redis·缓存·架构·事务·并发