当前位置: 首页 > news >正文

Android学习总结之Glide自定义三级缓存(面试篇)

一、三级缓存核心原理与设计
问题 1:为什么需要三级缓存?各层缓存的核心作用是什么?

回答核心

  • 内存缓存:毫秒级快速响应,存储近期浏览的图片(如滑动列表来回切换的图片),通过 LRU 算法自动清理冷数据,通常占内存 15%。
  • 磁盘缓存:持久化存储常用图片,解决内存容量限制,支持离线访问(如商品详情页图片),按 URL 哈希值命名避免重复,容量 100MB 左右。
  • 网络缓存:结合 HTTP 缓存头(如 Cache-Control),避免重复下载,降低流量和服务器压力,容量 50MB,优先存高频访问图片。
  • 分层优势:形成 “速度优先→持久化存储→网络优化” 的三级防护,覆盖 90% 以上的图片加载场景,提升用户体验和系统稳定性。

话术示例
三级缓存的设计是为了解决图片加载中的三个核心问题:

  • 内存缓存(快):就像手机的 “最近使用列表”,存用户刚看过的图片(比如滑动列表时来回切换的图片)。用 LRU 算法自动清理太久没看的图片,比如 8GB 内存的手机分 1.2GB 给它,还会把大图片压缩到屏幕大小,确保滑动时瞬间加载,不卡顿。
  • 磁盘缓存(稳):相当于 “本地相册”,存常用但暂时不在内存里的图片(比如用户常看的商品详情页图)。存 100MB 左右,用 URL 的哈希值命名避免重复,即使手机重启或没网,也能从这里找到图片。
  • 网络缓存(省):类似 “路由器记忆”,避免重复下载。比如用 OkHttp 缓存 50MB,配合服务器的缓存策略(比如设置 1 天有效期),下次打开同一张图直接从路由器拿,省流量还减轻服务器压力。
    三层配合,比如用户滑动列表时,先从内存秒开,滑走后存磁盘,下次没网也能看,联网时优先用网络缓存避免重复下载,覆盖 90% 的使用场景。
问题 2:LruCache 算法的核心数据结构和工作机制是什么?

回答核心

  • 数据结构:双向链表(维护访问顺序)+ 哈希表(快速查找)。
  • 工作机制
    1. 新元素加入链表头部,存在元素访问后移到头部;
    2. 缓存满时删除链表尾部元素(最久未使用);
    3. 通过sizeOf()方法计算元素大小,支持自定义内存占用(如 Bitmap 的字节数)。
  • 大厂优化点:Glide 的 LruResourceCache 结合弱引用队列,感知 Bitmap 回收,避免内存泄漏,同时复用资源池对象减少创建开销。

话术示例
LruCache 就像一个 “智能书架”,核心是按 “最近最少用” 原则管理图片:

  • 每次取图片(访问),就把它放到书架最前面(双向链表头部),很久没取的书(最久未用)放在最后面(链表尾部);
  • 书架满了(内存不够),直接扔掉最后一本书(删除尾部元素),保证书架始终不超载。
    Glide 在这个基础上做了两个关键优化:
  1. 防内存泄漏:给图片加 “弱引用”,就像给书贴一个标签,书被卖掉(系统回收)时,标签自动从书架移除,避免书架上留空标签(无效引用);
  2. 资源复用:把用过的图片暂时存到 “回收箱”(资源池),下次需要相同尺寸的图片,直接从回收箱拿,不用重新买(创建 Bitmap),比如滑动列表时,同尺寸的图片反复用,内存占用能降 30% 以上。
二、常见问题解决方案(缓存穿透 / 雪崩 / OOM)
问题 1:缓存穿透的本质是什么?大厂如何解决高并发下的穿透问题?

回答核心

  • 本质:请求不存在的数据,导致每次都查数据库 / 网络,形成流量黑洞。
  • 解决方案
    1. 布隆过滤器:提前将所有合法 URL 存入布隆过滤器,请求时先过滤无效 URL,拒绝率达 99% 以上;
    2. 缓存空值:对不存在的 URL 缓存一个特殊值(如 NULL),设置短过期时间(5 分钟),防止恶意攻击;
    3. 占位图策略:Glide 中设置error()/fallback()占位图,避免界面闪烁,同时记录无效请求日志。
问题 2:缓存雪崩的危害及多级预防策略?

回答核心

  • 危害:大量缓存同时失效,瞬间流量冲击数据库,可能导致服务雪崩。
  • 预防策略
    1. 错峰过期:给缓存时间添加随机偏移(如 24 小时 ±1 小时),避免集中失效;
    2. 多级缓存:内存 + 磁盘 + CDN 三层缓存,CDN 层抗住 80% 静态资源请求;
    3. 熔断降级:流量突增时,返回低清图或占位图,保证核心流程可用;
    4. 互斥锁:缓存失效时,仅允许一个线程重建缓存,其他线程阻塞等待,避免并发查库。

话术示例
缓存穿透是 “无效请求攻击”,比如恶意用户大量请求不存在的图片 URL,导致每次都要查数据库,就像有人一直按门铃问 “10086 号房在吗”,但小区根本没这个房号。
大厂用三层防线解决:

  1. 门口装 “门禁”(布隆过滤器):提前把所有存在的房号(URL)录入门禁系统,访客(请求)先刷门禁,不存在的直接拦在门外,准确率 99% 以上,比如电商 APP 用布隆过滤器,每天能拦截 10 万 + 无效请求;
  2. 留 “空房记录”(空值缓存):对查过不存在的房号,记下来 “10086 号房不存在”,有效期 5 分钟,期间再有人问直接说 “不用查了”,防止短时间内重复攻击;
  3. 门口贴 “提示牌”(占位图):在 Glide 里设置默认图,比如请求失败时显示 “图片加载中” 的占位图,用户看不到空白,体验更好,同时后台记录这些无效请求,方便定位攻击源。
问题 3:如何从源头预防 OOM?Glide 中的关键配置有哪些?

回答核心

  • 内存优化三原则
    1. 尺寸压缩:按 ImageView 尺寸加载(override(width, height)),避免加载超分辨率图片;
    2. 格式转换:使用 RGB_565(比 ARGB_8888 节省 50% 内存)或 WebP 格式,Glide 中通过format(DecodeFormat.PREFER_RGB_565)配置;
    3. 生命周期绑定:Glide 自动与 Activity/Fragment 绑定,界面不可见时清理资源,避免内存泄漏,同时可手动调用clear(imageView)释放。
  • 进阶策略:动态调整内存缓存大小(如低端机设为 10%,高端机 20%),结合skipMemoryCache(true)跳过非当前屏幕图片的内存存储。

话术示例
OOM 就像 “书包塞满了大书”,图片太大或存太多就会撑爆内存。Glide 通过三个 “瘦身” 技巧预防:

  1. 按尺寸买书:比如手机屏幕是 1000px,就只加载 1000px 的图,不加载 2000px 的原图,用override(1000, 1000)强制压缩,内存占用直接减半;
  2. 选轻便包装:用 RGB_565 格式代替默认的 ARGB_8888,前者每个像素占 2 字节,后者占 4 字节,同样一张图,内存占用少一半,配置代码:
    Glide.with(context).load(url).format(DecodeFormat.PREFER_RGB_565);  
    
  3. 定期断舍离:Glide 会自动跟着 Activity/Fragment 的生命周期清理内存,比如页面关掉时,把相关图片从内存删掉,也可以手动调用clear(imageView),避免 “过期图片” 占空间。
    比如一个短视频 APP,通过这三个技巧,内存峰值能从 500MB 降到 300MB 以下,OOM 崩溃率下降 70%。

话术示例
缓存雪崩就像 “电梯超载”,比如双十一零点,大量商品图片的缓存同时过期,几十万人同时请求,数据库像电梯一样可能被挤瘫。
大厂在大促时会做三件事:

  1. 错峰下班:给每个缓存设置不同的过期时间,比如原定 24 点过期,让有的 23 点 50 分过期,有的 0 点 10 分过期,像员工分批次下班,避免电梯拥挤。代码上可以加随机值:
    int expireTime = 24*60*60 + new Random().nextInt(3600); // 过期时间波动±1小时  
    
  2. 多级防护:最外层用 CDN 缓存(比如阿里云 OSS),扛住 80% 的图片请求,中间层用本地磁盘缓存,最后才到数据库,就像 “保安 + 前台 + 门禁” 三层过滤;
  3. 降级处理:如果流量实在太大,暂时给用户看低清图或模糊图,比如把 10MB 的高清图换成 1MB 的低清图,保证页面能加载,同时对数据库访问加锁,同一时间只允许一个线程更新缓存,其他线程等待,避免所有人同时挤向数据库。
三、HTTP 缓存与网络优化
问题 1:Cache-Control 头中 max-age、no-cache、no-store 的区别和使用场景?

回答核心

  • max-age=3600:缓存有效期 1 小时,期间直接读本地缓存,适合不常更新的图片(如商品主图);
  • no-cache:每次请求需服务器验证缓存有效性(发 304 请求),适合频繁更新但需浏览器缓存的图片(如活动海报);
  • no-store:禁止任何形式的缓存,响应内容不落地,适用于敏感图片(如用户证件照)。
  • 最佳实践:同时配置 ETag 和 Last-Modified,ETag 做精准校验(解决时间戳精度问题),Last-Modified 做快速判断,提升 304 命中率。
问题 2:客户端如何强制获取最新图片?服务器端如何配合?

回答核心

  • 客户端方案
    1. URL 添加版本号或时间戳(如image.jpg?v=2),破坏缓存键一致性;
    2. 设置请求头Cache-Control: no-cache,强制服务器验证;
    3. 清除本地网络缓存(OkHttp 中通过cache.remove(request))。
  • 服务器端配合
    1. 返回正确的 Cache-Control 头(如更新时设置max-age=0);
    2. 图片变更时更新 ETag 值,确保客户端能检测到变化。
四、性能监控与架构设计
问题 1:如何计算缓存命中率?大厂关注哪些核心指标?

回答核心

  • 计算公式:缓存命中率 =(内存命中数 + 磁盘命中数)÷ 总请求数 × 100%。
  • 监控手段
    1. Glide 开启 DEBUG 日志,筛选Fetched from memory cacheFetched from disk cache条目;
    2. 自定义 ModelLoader 统计各层命中次数;
    3. 关注衍生指标:内存峰值(Android Profiler 监控 Heap Size)、加载耗时(System.currentTimeMillis () 打点)、FPS(确保滑动≥55fps)。
  • 优化目标:内存命中率≥30%,磁盘命中率≥50%,整体命中率≥80%。
问题 2:设计一个高并发图片缓存系统,需要规避哪些坑?

回答核心

  • 核心坑点与对策
    1. 热点问题:高频图片集中失效,通过 “热点缓存 + 本地副本” 解决(如 Redis 热 key + 本地 Ehcache);
    2. 存储碎片化:URL 哈希冲突(概率极低),通过加盐哈希或 SHA-256 提升唯一性;
    3. 跨平台一致性:多端(Android/iOS/Web)缓存策略统一,如使用相同的 URL 参数规则和 Cache-Control 配置;
    4. 容量失控:磁盘缓存设置严格的 LRU 淘汰策略 + 过期时间(如 7 天未访问则删除),定期清理僵尸文件。

相关文章:

  • 关于 Golang GC 机制的一些细节:什么是根对象?GC 机制的触发时机?
  • “天神之眼”计算平台的算力设计(预计500-1000 TOPS)
  • 基于EFISH-SCB-RK3576/SAIL-RK3576的无人快递柜控制器技术方案
  • 【sql】按照数据的日期/天 ,对入库数据做数量分类
  • 驾驭数据洪流:大数据治理的全面解析与实战方案
  • ⭐️⭐️⭐️【课时6:如何创建工作流应用】学习总结 ⭐️⭐️⭐️ for《大模型Clouder认证:基于百炼平台构建智能体应用》认证
  • Git的安装和配置(idea中配置Git)
  • 当数控编程“联姻”AI:制造工厂的“智能大脑”如何炼成?
  • 全局优化搜索高次方程的解
  • ssh connect to remote gitlab without authority
  • 完整的 CentOS 6.10 虚拟机安装启动脚本
  • 【python爬虫】python+selenium实现Google Play Store应用信息爬虫+apk下载
  • 生命之舞:创建,终止与等待,Linux进程控制的交响乐章
  • C++矩阵操作:正交矩阵(旋转矩阵)
  • RPA vs. 传统浏览器自动化:效率与灵活性的终极较量
  • 电商平台自动化
  • list 容器常见用法及实现
  • Java知识框架
  • 【JVS更新日志】企业文档AI助手上线、低代码、智能BI、智能APS、AI助手5.14更新说明!
  • 机器学习 Day17 朴素贝叶斯算法-----概率论知识
  • 欠债七十万后,一个乡镇驿站站长的中年心事
  • 佩斯科夫:俄方代表团15日将在伊斯坦布尔等候乌克兰代表团
  • 美国明尼苏达州发生山火,过火面积超80平方公里
  • 彭丽媛同巴西总统夫人罗桑热拉参观中国国家大剧院
  • 秦洪看盘|预期改善,或迎来新的增量资金
  • 上海建筑领域绿色发展2025年工作要点发布