我是写Linux后端的(golang、c++、py),后端缓存算法通常是指的是内存里面的lru、或diskqueue,都是独立使用。 很少有用内存lru与disklru结合的场景需求。近段时间研究android开发,里面有一些设计思想值得后端学习。
写这篇文章的原因:
看到了android开发里面的一个片段
于是在画板里面手绘下图:
为了简化测试,用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()
测试效果:
经过缓存的图片从内存或文件加载,速度快了很多。 用空间换时间_