libilibi项目优化(1)使用Redis实现缓存
第一版
获取视频信息使用旁路缓存
- 当视频信息存在缓存中时(命中),直接从缓存中获取。
- 不存在缓存中时,先从数据库中查出对应的信息,写入缓存后再放回数据。
//获取视频详细信息@RequestMapping("/getVideoInfo")public ResponseVO getVideoInfo(@NotEmpty String videoId) {//旁路缓存模式,先从缓存中拿VideoInfo videoInfo = redisComponent.getVideoInfoDetail(videoId);if(videoInfo == null){//缓存中不存在就从数据库中取videoInfo = videoInfoService.getVideoInfoByVideoId(videoId);}if(videoInfo==null){throw new BusinessException(ResponseCodeEnum.CODE_404);}//将视频信息保存到缓存中redisComponent.saveVideoInfoDeTail(videoInfo);//获取当前用户对应的点赞和投币信息TokenUserInfoDto userInfoDto = getTokenUserInfoDto();List<UserAction> userActionList = new ArrayList<>();if(userInfoDto!=null){UserActionQuery actionQuery = new UserActionQuery();actionQuery.setVideoId(videoId);actionQuery.setUserId(userInfoDto.getUserId());//查询视频对应用户的点赞投币收藏信息actionQuery.setActionTypeArray(new Integer[]{UserActionTypeEnum.VIDEO_LIKE.getType(),UserActionTypeEnum.VIDEO_COLLECT.getType(),UserActionTypeEnum.VIDEO_COIN.getType(),});userActionList = userActionService.findListByParam(actionQuery);}VideoInfoResultVo resultVo = new VideoInfoResultVo();//设置用户的点赞投币收藏信息resultVo.setUserActionList(userActionList);resultVo.setVideoInfo(CopyTools.copy(videoInfo, VideoInfoVo.class));return getSuccessResponseVO(resultVo);}
用户点赞、收藏使用异步缓存写入
在进行更新视频点赞、收藏数量等信息时,并非直接修改数据库,而是先修改缓存中的数据,再利用消息队列,或定时任务等方式,将缓存中的数据更新到数据库
@Override@Transactional(rollbackFor = Exception.class)public void saveAction(UserAction bean) {//旁路缓存模式,想从缓存中拿VideoInfo videoInfo = redisComponent.getVideoInfoDetail(bean.getVideoId());if(videoInfo == null){//缓存中不存在就从数据库中取videoInfo = videoInfoMapper.selectByVideoId(bean.getVideoId());}if(videoInfo==null){throw new BusinessException(ResponseCodeEnum.CODE_404);}//设置视频对应的用户idbean.setVideoUserId(videoInfo.getUserId());//获得对应的用户行为(点赞,收藏,投币,评论点赞)UserActionTypeEnum actionTypeEnum = UserActionTypeEnum.getByType(bean.getActionType());if(actionTypeEnum==null){throw new BusinessException(ResponseCodeEnum.CODE_600);}//从数据库中根据视频id,评论id(若为评论点赞的话),行为类型,和用户行为来查询对应的行为记录UserAction dbAction = userActionMapper.selectByVideoIdAndCommentIdAndActionTypeAndUserId(bean.getVideoId(),bean.getCommentId(), bean.getActionType(), bean.getUserId());bean.setActionTime(new Date());switch (actionTypeEnum){//点赞和收藏case VIDEO_LIKE:case VIDEO_COLLECT://若存在点赞和收藏的记录,则取消点赞或收藏if(dbAction!=null){userActionMapper.deleteByActionId(dbAction.getActionId());}else{//添加对应的行为记录userActionMapper.insert(bean);}//若之前点过赞或收藏过则改变数量为-1,否则为1Integer changeCount = dbAction == null? Constants.ONE:-Constants.ONE;//更新视频对应的点赞或收藏信息if (actionTypeEnum.getType() == 2){Integer likeCount = videoInfo.getLikeCount();likeCount += changeCount;videoInfo.setLikeCount(likeCount);}else{Integer collectCount = videoInfo.getCollectCount();collectCount += changeCount;videoInfo.setCollectCount(collectCount);}//videoInfoMapper.updateCountInfo(bean.getVideoId(),actionTypeEnum.getField(),changeCount);if(actionTypeEnum == UserActionTypeEnum.VIDEO_COLLECT){//更新es的收藏数量esSearchComponent.updateDocCount(videoInfo.getVideoId(), SearchOrderTypeEnum.VIDEO_COLLECT.getField(), changeCount);}break;//投币case VIDEO_COIN:if (videoInfo.getUserId().equals(bean.getUserId())) {throw new BusinessException("UP主不能给自己投币");}if (dbAction != null) {throw new BusinessException("对本稿件的投币枚数已用完");}//更新当前用户的硬币数量, 返回的整数即为成功更新的行数Integer updateCoinCountInfo = userInfoMapper.updateCoinCountInfo(bean.getUserId(), -bean.getActionCount());//未成功更新说明硬币数量不够if(updateCoinCountInfo == 0){throw new BusinessException("硬币数量不足");}updateCoinCountInfo = userInfoMapper.updateCoinCountInfo(bean.getVideoUserId(), bean.getActionCount());//并发操作,数据库行上锁if(updateCoinCountInfo == 0){throw new BusinessException("投币失败");}//添加对应的行为记录userActionMapper.insert(bean);//更新视频对应的点赞或收藏信息//videoInfoMapper.updateCountInfo(bean.getVideoId(),actionTypeEnum.getField(), bean.getActionCount());break;}//将视频信息保存到缓存中redisComponent.saveVideoInfoDeTail(videoInfo);}
使用定时任务同步缓存与数据库
@Scheduled(cron = "0 0/5 * * * ?") // 每5分钟执行一次public void updateCachePeriodically() {try {// 从Redis中批量获取视频信息Map<String, VideoInfo> videoInfoBatch = redisComponent.getVideoInfoBatch();if (videoInfoBatch == null || videoInfoBatch.isEmpty()) {log.info("No video info found in Redis cache.");return;}// 提取视频信息列表List<VideoInfo> videoInfoList = new ArrayList<>(videoInfoBatch.values());// 批量更新数据库videoInfoService.addOrUpdateBatch(videoInfoList);log.info("Successfully updated {} video infos in the database.", videoInfoList.size());} catch (Exception e) {log.error("Error occurred during periodic cache update", e);}}
在你的第一版实现中,已经很好地结合了旁路缓存(视频基本信息)和异步写入缓存(点赞、收藏、播放量等高频修改信息)。以下是对你现有实现的分析和进一步优化的建议,以确保数据一致性和系统的高效性。
现有实现的优点
-
旁路缓存模式:
- 视频基本信息通过旁路缓存模式实现,减少了数据库的读取压力。
- 缓存未命中时,从数据库加载数据并更新缓存,保证了数据的最终一致性。
-
异步写入缓存:
- 点赞、收藏等高频修改信息直接更新缓存,然后通过定时任务批量同步到数据库,减少了数据库的写入压力。
- 使用消息队列或定时任务确保数据最终一致性。
-
定时任务同步:
- 定时任务定期从缓存中获取数据并同步到数据库,避免了实时同步带来的性能开销。
潜在问题和优化建议
1. 缓存穿透问题
在旁路缓存模式下,如果视频信息不存在,每次请求都会直接查询数据库,可能导致数据库压力过大。
优化建议:
- 在缓存中存储一个空对象或特殊标记(如
null或empty),表示该videoId对应的视频信息不存在。 - 示例:
if (videoInfo == null) {videoInfo = videoInfoService.getVideoInfoByVideoId(videoId);if (videoInfo == null) {redisComponent.saveVideoInfoDetail(new VideoInfo(videoId, "empty"), 5, TimeUnit.MINUTES); // 存储空对象,设置较短的过期时间throw new BusinessException(ResponseCodeEnum.CODE_404);} }
2. 缓存更新冲突
在多线程环境下,多个请求可能同时从数据库加载数据并更新缓存,导致缓存写入冲突。
优化建议:
- 使用分布式锁(如 Redisson)控制缓存的写入操作。
- 示例:
if (videoInfo == null) {redisComponent.lockVideoInfo(videoId); // 加锁videoInfo = videoInfoService.getVideoInfoByVideoId(videoId);if (videoInfo == null) {throw new BusinessException(ResponseCodeEnum.CODE_404);}redisComponent.saveVideoInfoDetail(videoInfo);redisComponent.unlockVideoInfo(videoId); // 解锁 }
3. 数据一致性问题
在异步写入缓存模式下,缓存数据可能因为服务故障而丢失,导致数据不一致。
优化建议:
- 消息队列:将每次用户行为(如点赞、收藏)记录到消息队列中,后台消费者定期从队列中读取行为记录并更新缓存和数据库。
- 持久化更新日志:在更新缓存时,将更新操作记录到持久化存储(如日志文件或数据库),以便在缓存服务故障时恢复数据。
- 示例:
// 发送消息到队列 messageQueue.send(new VideoActionMessage(bean.getVideoId(), actionTypeEnum, changeCount));
4. 定时任务的优化
定时任务每次同步所有缓存数据,可能导致性能问题,尤其是在缓存数据量较大时。
优化建议:
- 增量更新:只同步缓存中发生变化的数据,而不是全量同步。
- 设置缓存过期时间:为缓存中的高频修改信息设置较短的过期时间(如 5 分钟),确保数据不会长时间不一致。
- 示例:
@Scheduled(cron = "0 0/5 * * * ?") public void updateCachePeriodically() {List<VideoMetrics> changedMetrics = redisComponent.getChangedMetrics();if (changedMetrics == null || changedMetrics.isEmpty()) {log.info("No changed video metrics found in Redis cache.");return;}videoInfoService.updateMetricsBatch(changedMetrics);log.info("Successfully updated {} video metrics in the database.", changedMetrics.size()); }
5. 数据库更新的原子性
在更新数据库时,可能存在并发问题,导致数据不一致。
优化建议:
- 使用数据库事务(
@Transactional)确保更新操作的原子性。 - 在更新数据库时,使用乐观锁或悲观锁,避免并发冲突。
优化后的代码示例
获取视频信息
@RequestMapping("/getVideoInfo")
public ResponseVO getVideoInfo(@NotEmpty String videoId) {VideoInfo videoInfo = redisComponent.getVideoInfoDetail(videoId);if (videoInfo == null) {redisComponent.lockVideoInfo(videoId); // 加锁videoInfo = videoInfoService.getVideoInfoByVideoId(videoId);if (videoInfo == null) {redisComponent.saveVideoInfoDetail(new VideoInfo(videoId, "empty"), 5, TimeUnit.MINUTES); // 存储空对象redisComponent.unlockVideoInfo(videoId); // 解锁throw new BusinessException(ResponseCodeEnum.CODE_404);}redisComponent.saveVideoInfoDetail(videoInfo);redisComponent.unlockVideoInfo(videoId); // 解锁}// 获取用户行为信息TokenUserInfoDto userInfoDto = getTokenUserInfoDto();List<UserAction> userActionList = new ArrayList<>();if (userInfoDto != null) {UserActionQuery actionQuery = new UserActionQuery();actionQuery.setVideoId(videoId);actionQuery.setUserId(userInfoDto.getUserId());actionQuery.setActionTypeArray(new Integer[]{UserActionTypeEnum.VIDEO_LIKE.getType(),UserActionTypeEnum.VIDEO_COLLECT.getType(),UserActionTypeEnum.VIDEO_COIN.getType()});userActionList = userActionService.findListByParam(actionQuery);}VideoInfoResultVo resultVo = new VideoInfoResultVo();resultVo.setUserActionList(userActionList);resultVo.setVideoInfo(CopyTools.copy(videoInfo, VideoInfoVo.class));return getSuccessResponseVO(resultVo);
}
用户行为处理
@Override
@Transactional(rollbackFor = Exception.class)
public void saveAction(UserAction bean) {VideoInfo videoInfo = redisComponent.getVideoInfoDetail(bean.getVideoId());if (videoInfo == null) {videoInfo = videoInfoMapper.selectByVideoId(bean.getVideoId());if (videoInfo == null) {throw new BusinessException(ResponseCodeEnum.CODE_404);}}bean.setVideoUserId(videoInfo.getUserId());UserActionTypeEnum actionTypeEnum = UserActionTypeEnum.getByType(bean.getActionType());if (actionTypeEnum == null) {throw new BusinessException(ResponseCodeEnum.CODE_600);}UserAction dbAction = userActionMapper.selectByVideoIdAndCommentIdAndActionTypeAndUserId(bean.getVideoId(), bean.getCommentId(), bean.getActionType(), bean.getUserId());bean.setActionTime(new Date());switch (actionTypeEnum) {case VIDEO_LIKE:case VIDEO_COLLECT:if (dbAction != null) {userActionMapper.deleteByActionId(dbAction.getActionId());} else {userActionMapper.insert(bean);}Integer changeCount = dbAction == null ? Constants.ONE : -Constants.ONE;updateVideoMetrics(bean.getVideoId(), actionTypeEnum, changeCount);break;case VIDEO_COIN:// 投币逻辑break;}redisComponent.saveVideoInfoDetail(videoInfo); // 更新缓存messageQueue.send(new VideoActionMessage(bean.getVideoId(), actionTypeEnum, changeCount)); // 发送消息到队列
}
更新视频指标
private void updateVideoMetrics(String videoId, UserActionTypeEnum actionType, Integer changeCount) {VideoMetrics metrics = redisComponent.getVideoMetrics(videoId);if (metrics == null) {metrics = new VideoMetrics(videoId);}switch (actionType) {case VIDEO_LIKE:metrics.setLikeCount(metrics.getLikeCount() + changeCount);break;case VIDEO_COLLECT:metrics.setCollectCount(metrics.getCollectCount() + changeCount);break;}redisComponent.saveVideoMetrics(metrics);
}
定时任务同步
@Scheduled(cron = "0 0/5 * * * ?")
public void updateCachePeriodically() {List<VideoMetrics> changedMetrics = redisComponent.getChangedMetrics();if (changedMetrics == null || changedMetrics.isEmpty()) {log.info("No changed video metrics found in Redis cache.");return;}videoInfoService.updateMetricsBatch(changedMetrics);log.info("Successfully updated {} video metrics in the database.", changedMetrics.size());
}
总结
通过上述优化,你的实现能够更好地解决以下问题:
- 缓存穿透:通过存储空对象避免无效的数据库查询。
- 缓存更新冲突:使用分布式锁确保缓存更新的线程安全。
- 数据一致性:结合消息队列和定时任务确保缓存与数据库的最终一致性。
- 性能优化:通过增量更新和缓存过期时间减少定时任务的压力。
这种设计既保证了系统的高性能,又确保了数据的一致性。
第二版(规划中)
- 将视频的播放量,点赞,收藏信息独立出来,使用Hash进行缓存。
- 定时任务只更新有变动的视频数据
- 实现用户点赞高亮信息从SET中获取
相关文章:
libilibi项目优化(1)使用Redis实现缓存
第一版 获取视频信息使用旁路缓存 当视频信息存在缓存中时(命中),直接从缓存中获取。不存在缓存中时,先从数据库中查出对应的信息,写入缓存后再放回数据。 //获取视频详细信息RequestMapping("/getVideoInfo&q…...
The Rust Programming Language 学习 (二)
通用编程概念 变量和可变性 默认情况下变量是不可变的(immutable),不过你也可以选择让变量是可变的(mutable). 变量的遮蔽 你可以声明和前面变量具有相同名称的新变量,说这个是第一个变量被第二个变量遮蔽(shadow&…...
http链接转成https的链接的几种方法
以下是一个将HTTP链接转换为HTTPS的JavaScript函数,处理了多种常见输入情况: function convertToHttps(url) {if (typeof url ! string) return url;// 移除首尾空格并处理空字符串const trimmedUrl url.trim();if (!trimmedUrl) return https://;// 替…...
STM32——串口通信 UART
一、基础配置 Universal Asynchronous Receiver Transmitter 异步,串行,全双工 TTL电平 :高电平1 低电平0 帧格式: 起始位1bit 数据位8bit 校验位1bit 终止位1bit NVIC Settings一栏使能接受中断。 之前有设置LCD,…...
mybatis日期格式与字符串不匹配bug
异常特征:java.lang.IllegalArgumentException: invalid comparison: java.time.LocalDateTime and java.lang.String ### Error updating database. Cause: java.lang.IllegalArgumentException: invalid comparison: java.time.LocalDateTime and java.lang.Str…...
文献分享: ConstBERT固定数目向量编码文档
😂图放这了,大道至简的 idea \text{idea} idea不愧是 ECIR \text{ECIR} ECIR 👉原论文 1. ConstBERT \textbf{1. ConstBERT} 1. ConstBERT的原理 1️⃣模型的改进点:相较于 ColBERT \text{ColBERT} ColBERT为每个 Token \text{Tok…...
学习记录-用例设计编写
黑马测试视频记录 目录 一、 软件测试流程 二、测试用例编写格式 1、等价类法 2、边界值分析法 3、 判定表法 4、场景法编辑 5、错误推荐法 一、 软件测试流程 二、测试用例编写格式 1、等价类法 2、边界值分析法 3、 判定表法 4、场景法 5、错误推荐法 时间紧任务重…...
学习工具的一天之(burp)
第一呢一定是先下载 【Java环境】:Java Downloads | Oracle 下来是burp的下载 Download Burp Suite Community Edition - PortSwigger 【下载方法二】关注的一个博主 【BurpSuite 安装激活使用详细上手教程 web安全测试工具】https://www.bilibili.com/video/BV…...
el-tree右键节点动态位置展示菜单;el-tree的节点图片动态根据节点属性color改变背景色;加遮罩层(opacity)
一、el-tree右键节点动态位置展示菜单 关键:@node-contextmenu="handleRightClick"与@node-click=“handleNodeClick” <div class="content"><el-tabs class="tabs" @tab-click="handleClick" v-model="Modal"…...
K8s 1.27.1 实战系列(一)准备工作
一、主机规划与硬件要求 1、节点数量 至少需要 3 台服务器(1 台 Master 节点,2 台 Worker 节点)。本地测试可缩容:若仅用于测试,可缩减为 1 个 Master 和 1 个 Worker,但需注意稳定性风险。2、硬件配置 Master 节点:建议 2 核 CPU、8GB 内存、80GB 硬盘。Worker 节…...
说一下SpringBoot3新特新和JDK17新特性
JDK1.8(Java8)新特性 stream流式编程 流处理 Stream API 提供了对集合数据进行操作的一种高效、简洁的方式。它支持顺序和并行的聚合操作 如:过滤(filter)、排序(sort)、映射(map&…...
Linux系统服务安全检测手记
一:服务器ip暴露ip和端口的安全问题 服务器IP和端口暴露在外网中确实存在一定的安全风险,以下是几个主要的安全问题及相应的缓解措施: ### 主要安全问题 1. **直接攻击**: - 暴露的IP地址和开放的端口可能成为黑客直接攻击的…...
鸿蒙与DeepSeek深度整合:构建下一代智能操作系统生态
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 https://www.captainbed.cn/north 目录 技术融合背景与价值鸿蒙分布式架构解析DeepSeek技术体系剖析核心整合架构设计智能调度系统实现…...
[创业之路-329]:华为铁三角实施的步骤
一、通用过程 华为铁三角实施的步骤主要包括以下几个关键阶段: 1、明确角色与职责 确定铁三角成员:组建由客户经理(AR)、解决方案经理(SR)和交付经理(FR)组成的铁三角团队。制定岗…...
1.15-16-17-18迭代器与生成器,函数,数据结构,模块
目录 15,Python3 迭代器与生成器15-1 迭代器15-1-1 基础知识15-1-2 迭代器与for循环工作原理 15-2 生成器(本质就是迭代器)15-2-1 yield 表达式15-2-2 三元表达式15-2-3 列表生成式15-2-4 其他生成器(——没有元祖生成式——&…...
java面向对象(详细讲解)
第一章 类和对象 1.面向对象的介绍 1.面向过程:自己的事情自己做,代表语言c语言 2.面向对象:自己的事情别人做,代表语言java 3.为啥要使用面向对象思想编程:很多功能别人给我们实现好了,我们只需要拿过…...
代码随想录二刷|图论2
图论 基础知识 1 无向图 (1)度:一个顶点连n条边就度为n (2)权 加权无向图:有边长的无向图 (3)通道:两个顶点之间有一些边和点,并且没有重复的边 路&am…...
毕业项目推荐:基于yolov8/yolov5/yolo11的暴力行为检测识别系统(python+卷积神经网络)
文章目录 概要一、整体资源介绍技术要点功能展示:功能1 支持单张图片识别功能2 支持遍历文件夹识别功能3 支持识别视频文件功能4 支持摄像头识别功能5 支持结果文件导出(xls格式)功能6 支持切换检测到的目标查看 二、数据集三、算法介绍1. YO…...
服务器CPU微架构
1、微架构图 前端:预解码、解码、分支预测、L1指令缓存、指令TLB缓存 后端:顺序重排缓存器ROB处理依赖,调度器送到执行引擎 执行引擎:8路超标量,每一路可以进行独立的微操作处理 Port0、1、5、6支持整数、浮点数的加…...
用本地浏览器打开服务器上使用的Tensorboard
文章目录 前言一、Tensorboard的安装二、使用步骤1.服务器上的设置2.在本地打开 总结 前言 最近有使用服务器上的Tensorboard的需求,踩了几个雷,现已在搜索和帮助下解决,总结于此。 一、Tensorboard的安装 pip install tensorboard2.12.0注…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...
comfyui 工作流中 图生视频 如何增加视频的长度到5秒
comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗? 在ComfyUI中实现图生视频并延长到5秒,需要结合多个扩展和技巧。以下是完整解决方案: 核心工作流配置(24fps下5秒120帧) #mermaid-svg-yP…...
规则与人性的天平——由高考迟到事件引发的思考
当那位身着校服的考生在考场关闭1分钟后狂奔而至,他涨红的脸上写满绝望。铁门内秒针划过的弧度,成为改变人生的残酷抛物线。家长声嘶力竭的哀求与考务人员机械的"这是规定",构成当代中国教育最尖锐的隐喻。 一、刚性规则的必要性 …...
二维FDTD算法仿真
二维FDTD算法仿真,并带完全匹配层,输入波形为高斯波、平面波 FDTD_二维/FDTD.zip , 6075 FDTD_二维/FDTD_31.m , 1029 FDTD_二维/FDTD_32.m , 2806 FDTD_二维/FDTD_33.m , 3782 FDTD_二维/FDTD_34.m , 4182 FDTD_二维/FDTD_35.m , 4793...
