iOS IdiotAVplayer实现视频分片缓存
文章目录
- IdiotAVplayer 实现视频切片缓存
- 一 iOS视频边下边播原理
- 一 分片下载的实现
- 1 分片下载的思路
- 2 IdiotAVplayer 实现架构
- 三 IdiotAVplayer 代码解析
- IdiotPlayer
- IdiotResourceLoader
- IdiotDownLoader
IdiotAVplayer 实现视频切片缓存
一 iOS视频边下边播原理
初始化AVURLAsset 的时候,将资源链接中的http替换成其他字符串,并且将AVURLAsset的resourceLoader 设置代理对象,然后该代理对象实现AVAssetResourceLoaderDelegate 的代理方法
#pragma mark - AVAssetResourceLoaderDelegate
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {return YES;
}- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {
}
在代理方法中实现资源的下载,保存, 并将下载好的资源塞给 loadingRequest, 实现视频的播放
一 分片下载的实现
简单的实现方案,就是将一个视频从头开始下载,或者从当前下载到的位置开始下载,然后下载到结束 这种方案对于短视频是可以的,因为短视频总共也没有多大,即使我们快进,从头下载开始到快进的地方也没有多少流量,用户体验影响不大,但是仍然浪费了中间的流量。
如果一个视频比较大,用户进行快进操作的话,从开头下载到用户快进的地方需要的时间很长,这时候,如果能根据用户快进的进度,根据用户的需要进行资源下载,那就是一个好的方案了。
1 分片下载的思路
步骤
1 首先根据链接获取本地资源
2 根据获取到的本地资源和视频请求request对比,计算需要新下载的资源 片段。
3 将本地的资源或者下载好的资源分片塞给请求对象request
2 IdiotAVplayer 实现架构
IdiotAVPlayer 负责实现视频播放功能
IdiotResourceLoader
负责实现
AVAssetResourceLoaderDelegate代理 方法,
负责将数据塞给AVAssetResourceLoadingRequest 请求,并管理AVAssetResourceLoadingRequest 请求,添加,移除,塞数据,快进的处理
IdiotDownLoader 负责 资源片段的获取,需要下载的片段的计算
NSURLSessionDelegate 代理方法的实现,并将下载好的数据传给IdiotResourceLoader, 还负责在读取本地数据的时候,将占用内存较大的视频资源分片读取到内存中传给 IdiotResourceLoader,避免造成因为资源较大而产生的内存撑爆问题
IdiotFileManager 负责管理下载的资源
三 IdiotAVplayer 代码解析
创建播放器,并设置resouceLoader代理
IdiotPlayer
_resourceLoader = [[IdiotResourceLoader alloc] init];_resourceLoader.delegate = self;AVURLAsset * playerAsset = [AVURLAsset URLAssetWithURL:[_currentUrl idiotSchemeURL] options:nil];[playerAsset.resourceLoader setDelegate:_resourceLoader queue:_queue];_playerItem = [AVPlayerItem playerItemWithAsset:playerAsset];
IdiotResourceLoader
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {[self addLoadingRequest:loadingRequest];DLogDebug(@"loadingRequest == %@",loadingRequest)return YES;
}- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {[self removeLoadingRequest:loadingRequest];
}- (void)removeLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);NSArray * temptaskList = [NSArray arrayWithArray:self.taskList];dispatch_semaphore_signal(semaphore);IdiotResourceTask * deleteTask = nil;for (IdiotResourceTask * task in temptaskList) {if ([task.loadingRequest isEqual:loadingRequest]) {deleteTask = task;break;}}if (deleteTask) {dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList removeObject:deleteTask];dispatch_semaphore_signal(semaphore);}}- (void)addLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {if (self.currentResource) {if (loadingRequest.dataRequest.requestedOffset >= self.currentResource.requestOffset &&loadingRequest.dataRequest.requestedOffset <= self.currentResource.requestOffset + self.currentResource.cacheLength) {IdiotResourceTask * task = [[IdiotResourceTask alloc] init];task.loadingRequest = loadingRequest;task.resource = self.currentResource;dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList addObject:task];dispatch_semaphore_signal(semaphore);[self processRequestList];}else{if (self.seek) {[self newTaskWithLoadingRequest:loadingRequest];}else{IdiotResourceTask * task = [[IdiotResourceTask alloc] init];task.loadingRequest = loadingRequest;task.resource = self.currentResource;NSLog(@"哈哈哈哈哈啊哈哈这里这里这里添加22222 %lld %lld %p\n", loadingRequest.dataRequest.requestedOffset, loadingRequest.dataRequest.currentOffset, task);dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList addObject:task];dispatch_semaphore_signal(semaphore);}}}else {[self newTaskWithLoadingRequest:loadingRequest];}
}- (void)newTaskWithLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {long long fileLength = 0;if (self.currentResource) {fileLength = self.currentResource.fileLength;self.currentResource.cancel = YES;}IdiotResource * resource = [[IdiotResource alloc] init];resource.requestURL = loadingRequest.request.URL;resource.requestOffset = loadingRequest.dataRequest.requestedOffset;resource.resourceType = IdiotResourceTypeTask;if (fileLength > 0) {resource.fileLength = fileLength;}IdiotResourceTask * task = [[IdiotResourceTask alloc] init];task.loadingRequest = loadingRequest;task.resource = resource;self.currentResource = resource;dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList addObject:task];dispatch_semaphore_signal(semaphore);printf("哈哈哈这里事创建的这里事创建的%lld %lld %lld %p %p\n", resource.requestOffset, loadingRequest.dataRequest.requestedOffset, loadingRequest.dataRequest.currentOffset, loadingRequest, task);[IdiotDownLoader share].delegate = self;[[IdiotDownLoader share] start:self.currentResource];self.seek = NO;
}- (void)stopResourceLoader{[[IdiotDownLoader share] cancel];
}- (void)processRequestList {@synchronized (self) {dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);NSArray * temptaskList = [NSArray arrayWithArray:self.taskList];dispatch_semaphore_signal(semaphore);for (IdiotResourceTask * task in temptaskList) {NSInvocationOperation * invoke = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(finishLoadingWithLoadingRequest:) object:task];[_playQueue addOperation:invoke];}}
}- (void)finishLoadingWithLoadingRequest:(IdiotResourceTask *)task {//填充信息task.loadingRequest.contentInformationRequest.contentType = @"video/mp4";task.loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;task.loadingRequest.contentInformationRequest.contentLength = task.resource.fileLength;if (task.resource.fileLength <= 0) {DLogDebug(@"requestTask.fileLength <= 0");}//读文件,填充数据long long cacheLength = task.resource.cacheLength;long long requestedOffset = task.loadingRequest.dataRequest.requestedOffset;if (task.loadingRequest.dataRequest.currentOffset != 0) {requestedOffset = task.loadingRequest.dataRequest.currentOffset;}printf("哈哈哈1111执行执行执行%lld点 %lld 一 %lld %p %p\n", task.loadingRequest.dataRequest.requestedOffset,task.loadingRequest.dataRequest.currentOffset, task.resource.requestOffset, task.loadingRequest, task);printf("哈哈哈数量数量%ld\n", self.taskList.count);for (IdiotResourceTask *task1 in self.taskList) {printf("哈哈哈啦啊啦这里这里数组里的%p %lld\n",task1, task.resource.requestOffset);}if (requestedOffset < task.resource.requestOffset) {printf("哈哈哈1111返回%lld点 %lld 一 %lld %p %p\n", task.loadingRequest.dataRequest.requestedOffset,task.loadingRequest.dataRequest.currentOffset, task.resource.requestOffset, task.loadingRequest, task);return;}long long paddingOffset = requestedOffset - task.resource.requestOffset;long long canReadLength = cacheLength - paddingOffset;printf("哈哈哈能获取到的能获取到的%lld \n", canReadLength);if (canReadLength <= 0) {printf("哈哈哈返回222222 %lld\n", canReadLength);return;}long long respondLength = MIN(canReadLength, task.loadingRequest.dataRequest.requestedLength);NSFileHandle * handle = [IdiotFileManager fileHandleForReadingAtPath:task.resource.cachePath];[handle seekToFileOffset:paddingOffset];[task.loadingRequest.dataRequest respondWithData:[handle readDataOfLength:[[NSNumber numberWithLongLong:respondLength] unsignedIntegerValue]]];printf("哈哈哈匹配到匹配到%lld \n",respondLength);[handle closeFile];//如果完全响应了所需要的数据,则完成long long nowendOffset = requestedOffset + canReadLength;long long reqEndOffset = task.loadingRequest.dataRequest.requestedOffset + task.loadingRequest.dataRequest.requestedLength;printf("哈哈哈差别差别%lld\n",reqEndOffset - nowendOffset);if (nowendOffset >= reqEndOffset) {[task.loadingRequest finishLoading];printf("哈哈哈移除移除移除%lld %lld\n", nowendOffset, reqEndOffset);[self removeLoadingRequest:task.loadingRequest];return;}}#pragma mark - DownLoaderDataDelegate
- (void)didReceiveData:(IdiotDownLoader *__weak)downLoader{[self processRequestList];if (self.delegate&&[self.delegate respondsToSelector:@selector(didCacheProgressChange:)]) {__weak typeof(self) weakself = self;dispatch_async(dispatch_get_main_queue(), ^{__strong typeof(weakself) strongself = weakself;NSMutableArray * caches = [downLoader.resources mutableCopy];[caches addObject:self.currentResource];[strongself.delegate didCacheProgressChange:caches];});}}
下面单独介绍各个方法的实现
if (self.currentResource) {if (loadingRequest.dataRequest.requestedOffset >= self.currentResource.requestOffset &&loadingRequest.dataRequest.requestedOffset <= self.currentResource.requestOffset + self.currentResource.cacheLength) {IdiotResourceTask * task = [[IdiotResourceTask alloc] init];task.loadingRequest = loadingRequest;task.resource = self.currentResource;dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList addObject:task];dispatch_semaphore_signal(semaphore);[self processRequestList];}else{if (self.seek) {[self newTaskWithLoadingRequest:loadingRequest];}else{IdiotResourceTask * task = [[IdiotResourceTask alloc] init];task.loadingRequest = loadingRequest;task.resource = self.currentResource;dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);[self.taskList addObject:task];dispatch_semaphore_signal(semaphore);}}}else {[self newTaskWithLoadingRequest:loadingRequest];}
}
上面方法中,的判断条件 self.currentResource 说明执行过newTaskWithLoadingRequest 方法了,因为在该方法中设置了self.currentResource,说明就不是第一次执行addLoadingRequest 添加request了,loadingRequest.dataRequest.requestedOffset >= self.currentResource.requestOffset &&
loadingRequest.dataRequest.requestedOffset <= self.currentResource.requestOffset + self.currentResource.cacheLength 该判断条件说明
新请求的offset 是大于当前的offset, 但是小于当前的offset + cachelength ,说明
当前的的本地资源是有一部分是可以塞给当前的 request的 ,所以在创建了新的任务task的同时,还执行了 [self processRequestList];
方法。下面的 else中 if (self.seek) 说明当前的request是因为用户拖拽进度条触发的,所以要重新创建一个source ,因为一个拖拽就会引起一个不连续的下载片段,而在IdiotAvplayer的设计中,每一个资源片段都要有一个resouce,
所以要执行newTaskWithLoadingRequest 方法
else说明不是拖拽的,则直接添加新的任务即可,等到新的下载好的资源到来,就会去塞给新添加的请求,而新的下载是不会停止的,直到到达资源的最后。
- (void)finishLoadingWithLoadingRequest:(IdiotResourceTask *)task {//填充信息task.loadingRequest.contentInformationRequest.contentType = @"video/mp4";task.loadingRequest.contentInformationRequest.byteRangeAccessSupported = YES;task.loadingRequest.contentInformationRequest.contentLength = task.resource.fileLength;if (task.resource.fileLength <= 0) {DLogDebug(@"requestTask.fileLength <= 0");}//读文件,填充数据long long cacheLength = task.resource.cacheLength;long long requestedOffset = task.loadingRequest.dataRequest.requestedOffset;if (task.loadingRequest.dataRequest.currentOffset != 0) {requestedOffset = task.loadingRequest.dataRequest.currentOffset;}if (requestedOffset < task.resource.requestOffset) {/*task.resource 是第一次播放或者拖拽才会创建的对象,其 requestOffset就是对应的那次请求的offset,这里的判断条件 requestedOffset < task.resource.requestOffset 说明 该request是 创建 resouce 之前的request,那么该resouce 对应的资源中满足该request,所以就返回*/ return;}long long paddingOffset = requestedOffset - task.resource.requestOffset;long long canReadLength = cacheLength - paddingOffset;if (canReadLength <= 0) {如果该resouce offset+ resouce的资源长度,仍然小与request 的offset,说明该资源完全在request的前面,无法满足该request,返回return;}long long respondLength = MIN(canReadLength, task.loadingRequest.dataRequest.requestedLength);NSFileHandle * handle = [IdiotFileManager fileHandleForReadingAtPath:task.resource.cachePath];[handle seekToFileOffset:paddingOffset];[task.loadingRequest.dataRequest respondWithData:[handle readDataOfLength:[[NSNumber numberWithLongLong:respondLength] unsignedIntegerValue]]];[handle closeFile];//如果完全响应了所需要的数据,则完成long long nowendOffset = requestedOffset + canReadLength;long long reqEndOffset = task.loadingRequest.dataRequest.requestedOffset + task.loadingRequest.dataRequest.requestedLength;if (nowendOffset >= reqEndOffset) {[task.loadingRequest finishLoading];[self removeLoadingRequest:task.loadingRequest];return;}}
如下图,分片缓存的资源在沙盒中的保存形式,是根据offset 分别保存的
IdiotDownLoader
- (void)start:(IdiotResource *)task {if (self.currentDataTask) {[self.currentDataTask cancel];}[self.taskDic setObject:task forKey:[NSString stringWithFormat:@"%zd",task.requestOffset]];//获取本地资源BOOL refresh = NO;while (!self.writing&&!refresh) {self.resources = [IdiotFileManager getResourceWithUrl:task.requestURL];refresh = YES;}IdiotResource * resource = nil;//找出对应的资源if (!self.resources.count) {//本地无资源resource = [[IdiotResource alloc] init];resource.requestURL = task.requestURL;resource.requestOffset = task.requestOffset;resource.fileLength = task.fileLength;resource.cachePath = task.cachePath;resource.cacheLength = 0;resource.resourceType = IdiotResourceTypeNet;//网络资源[self.resources addObject:resource];}else{//本地有资源for (IdiotResource * obj in self.resources) {if (task.requestOffset >= obj.requestOffset&&task.requestOffset < obj.requestOffset+obj.cacheLength) {/*该判断条件说明当前任务offset比获取的本地分片资源offset大,比本地分片资源offset+cachelength小,在本地资源中间,有重合的地方*/resource = obj;break;}}if (task.requestOffset > resource.requestOffset&&resource.resourceType == IdiotResourceTypeNet) {/*该resouce 是从上面的判断条件中获取的该判断说明当前任务比获取到的本地resouce offset大,并且是网路请求资源,说明本地没有资源,需要重新下载,这里新建一个IdiotResource,并且设置offset=task.offset就是为了从当前任务的offset开始下载,否则会中本得resouce 的offset开始下载,这样就会导致下载的比我们需要的多,并且用户会有一个卡住的体验,因为下载的不是用户需要的offset,这里这样写,保证下载的offset就是用户需要的,并且避免流量浪费 */long long adjustCacheLength = task.requestOffset - resource.requestOffset;IdiotResource * net = [[IdiotResource alloc] init];net.requestURL = task.requestURL;net.requestOffset = task.requestOffset;net.fileLength = task.fileLength;net.cachePath = task.cachePath;net.cacheLength = resource.cacheLength - adjustCacheLength;net.resourceType = IdiotResourceTypeNet;//网络资源resource.cacheLength = adjustCacheLength;NSInteger index = [self.resources indexOfObject:resource]+1;[self.resources insertObject:net atIndex:index];resource = net;}}self.currentResource = resource;[self fetchDataWith:task Resource:self.currentResource];}
- (void)fetchFromLocal:(IdiotResource *)sliceRequest withResource:(IdiotResource *)resource{if (sliceRequest.requestOffset == resource.requestOffset) {sliceRequest.cachePath = resource.cachePath;sliceRequest.fileLength = resource.fileLength;sliceRequest.cacheLength = resource.cacheLength;//直接开始下一个资源获取if (self.delegate && [self.delegate respondsToSelector:@selector(didReceiveData:)]) {[self.delegate didReceiveData:self];}[self willNextResource:sliceRequest];return;}NSFileHandle * readHandle = [IdiotFileManager fileHandleForReadingAtPath:resource.cachePath];unsigned long long seekOffset = sliceRequest.requestOffset < resource.requestOffset?0:sliceRequest.requestOffset-resource.requestOffset;[readHandle seekToFileOffset:seekOffset];//文件过大可分次读取long long canReadLength = resource.cacheLength-seekOffset;NSUInteger bufferLength = 5242880; //长度大于5M分次返回数据/*如果本地资源比较大,就分片塞数据,如果一下将整个资源读取到内存中,就会造成内存撑爆,导致严重的卡顿*/while (canReadLength >= bufferLength) {//长度大于1M分次返回数据canReadLength -= bufferLength;NSData * responseData = [readHandle readDataOfLength:bufferLength];[self didReceiveLocalData:responseData requestTask:sliceRequest complete:canReadLength==0?YES:NO];}if (canReadLength != 0) {NSData * responseData = [readHandle readDataOfLength:[[NSNumber numberWithLongLong:canReadLength] unsignedIntegerValue]];[readHandle closeFile];[self didReceiveLocalData:responseData requestTask:sliceRequest complete:YES];}else{[readHandle closeFile];}}
相关文章:

iOS IdiotAVplayer实现视频分片缓存
文章目录 IdiotAVplayer 实现视频切片缓存一 iOS视频边下边播原理一 分片下载的实现1 分片下载的思路2 IdiotAVplayer 实现架构 三 IdiotAVplayer 代码解析IdiotPlayerIdiotResourceLoaderIdiotDownLoader IdiotAVplayer 实现视频切片缓存 一 iOS视频边下边播原理 初始化AVUR…...

SpringBootWeb请求-响应
HTTP请求 前后端分离 在这种模式下,前端技术人员基于"接口文档",开发前端程序;后端技术人员也基于"接口文档",开发后端程序。 由于前后端分离,对我们后端技术人员来讲,在开发过程中&a…...

List集合详解
目录 1、集合是什么? 1.1、集合与集合之间的关系 2、List集合的特点 3、遍历集合的三种方式 3.1、foreach(增强佛如循环遍历) 3.2、for循环遍历 3.3、迭代器遍历 4、LinkedList和ArrayList的区别 4.1、为什么ArrayList查询会快一些? 4.2、为什么LinkedLi…...

投稿指南【NO.12_8】【极易投中】核心期刊投稿(组合机床与自动化加工技术)
近期有不少同学咨询投稿期刊的问题,大部分院校的研究生都有发学术论文的要求,少部分要求高的甚至需要SCI或者多篇核心期刊论文才可以毕业,但是核心期刊要求论文质量高且审稿周期长,所以本博客梳理一些计算机特别是人工智能相关的期…...

解决git无法上传大文件(50MB)
解决方法 使用LFS解决GitHub无法上传大于50MB的文件 LFS简介 Git LFS(Large File Storage)是 Git 的一个扩展,用于管理大型文件,如二进制文件、图像、音频和视频文件等。它的主要目的是解决 Git 对大型二进制文件的版本控制和存…...

用递归实现字符串逆序(不使用库函数)
文章目录 前言一、题目要求二、解题步骤1.大概框架2.如何反向排列?3.模拟实现strlen4.实现反向排列5.递归实现反向排列 总结 前言 嗨,亲爱的读者们!我是艾老虎尤,今天,我们将探索一个题目,这个题目对新手非…...

初学python(一)
一、python的背景和前景 二、 python的一些小事项 1、在Java、C中,2 / 3 0,也就是整数 / 整数 整数,会把小数部分舍掉。而在python中2 / 3 0.66666.... 不会舍掉小数部分。 在编程语言中,浮点数遵循IEEE754标准,不…...

Excel VSTO开发8 -相关控件
版权声明:本文为博主原创文章,转载请在显著位置标明本文出处以及作者网名,未经作者允许不得用于商业目的。 8 相关控件 在VSTO开发中,Ribbon(或称为Ribbon UI)是指Office应用程序中的那个位于顶部的带有选…...

华为数据管理——《华为数据之道》
数据分析与开发 元数据是描述数据的数据,用于打破业务和IT之间的语言障碍,帮助业务更好地理解数据。 元数据是数据中台的重要的基础设施,元数据治理贯彻数据产生、加工、消费的全过程,沉淀了数据资产,搭建了技术和业务…...

Flink CDC 菜鸟教程 -环境篇
本教程将介绍如何使用 Flink CDC 来实现这个需求, 在 Flink SQL CLI 中进行,只涉及 SQL,无需一行 Java/Scala 代码,也无需安装 IDE。 系统的整体架构如下图所示: 环境篇 1、 准备一台Linux 2、准备教程所需要的组件 下载 flink-1.13.2 并将其解压至目录 flink-1.13.2 …...
【线上问题】linux部署docker应用docker-compose启动报端口占用问题(感觉上没有被占用)
目录 一、问题说明二、排查过程 一、问题说明 1.linux服务器使用的不是root用户权限 2.docker应用服务没有关闭的情况下,做了些重装docker,重启docker等操作 3.docker-compose up -d然后docker logs查看日志报端口被占用 4.netstat -ntpl | grep 端口 也…...

解决虚拟机克隆后IP和命名冲突问题
目录 解决IP冲突问题 解决命名冲突 解决IP冲突问题 克隆后的虚拟机和硬件地址和ip和我们原虚拟机的相同,我们需要重新生成硬件地址和定义ip,步骤如下: (1)进入 /etc/sysconfig/network-scripts/ifcfg-ens33 配置文件…...

分享一个python基于数据可视化的智慧社区服务平台源码
💕💕作者:计算机源码社 💕💕个人简介:本人七年开发经验,擅长Java、Python、PHP、.NET、Node.js、微信小程序、爬虫、大数据等,大家有这一块的问题可以一起交流! …...
[密码学入门]凯撒密码
单表代换 单表:英文26字母的顺序 代换:替换为别的字母并保证解密的唯一性 假如我们让加密方式为所有字母顺序移动3位 import stringstring.ascii_lowercase abcdefghijklmnopqrstuvwxyz b3 加密算法y(xb)mod26 解密算法为x(y-b)mod26 密钥空间26 …...

博客之QQ登录功能(一)
流程图 上图spring social 封装了1-8步需要的工作 1、新建包和书写配置文件 public class QQProperties {//App唯一标 识private String appId "100550231";private String appSecret "69b6ab57b22f3c2fe6a6149274e3295e";//QQ供应商private String…...

Redis多机数据库实现
Redis多机数据库实现 为《Redis设计与实现》笔记 复制 客户端可以使用SLAVEOF命令将指定服务器设置为该服务器的主服务器 127.0.0.1:12345> SLAVEOF 127.0.0.1 6379127.0.0.1:6379将被设置为127.0.0.1:123456的主服务器 旧版复制功能的实现 Redis的复制功能分为同步&a…...

Leangoo领歌 -敏捷任务管理软件,任务管理更轻松更透明
任务管理,简单易懂,就是对任务进行管理。那怎么可以更好进行任务管理呢?怎么样样可以让任务进度可视化,一目了然呢?有效的管理可以让我们事半功倍。 接下来我们看一下如何借助任务管理软件高效的做任务管理。 首先…...

go的iris框架进行本地资源映射到服务端
我这里使用的是HandleDirapi,有其他的请补充 package mainimport ("github.com/kataras/iris/v12" )type Hello struct{Status int json:"status"Message string json:"message" }func main(){app : iris.New()//第一个api:相当于首页app.Get(&q…...
代码随想录day46|139. 单词拆分
139. 单词拆分 class Solution:def wordBreak(self, s: str, wordDict: List[str]) -> bool:dp [False]*(len(s)1)dp[0]Truefor i in range(len(s)1):for j in wordDict:if i>len(j) and (s[i-len(j):i] in wordDict) and dp[i-len(j)]:dp[i] Truereturn dp[len(s)]多…...

MATLAB实现函数拟合
目录 一.理论知识 1.拟合与插值的区别 2.几何意义 3.误差分析 二.操作实现 1.数据准备 2.使用cftool——拟合工具箱 三.函数拟合典例 四.代码扩展 一.理论知识 1.拟合与插值的区别 通俗的说,插值的本质是根据现有离散点的信息创建出更多的离散点…...

C++初阶-list的底层
目录 1.std::list实现的所有代码 2.list的简单介绍 2.1实现list的类 2.2_list_iterator的实现 2.2.1_list_iterator实现的原因和好处 2.2.2_list_iterator实现 2.3_list_node的实现 2.3.1. 避免递归的模板依赖 2.3.2. 内存布局一致性 2.3.3. 类型安全的替代方案 2.3.…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...

STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...

RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
探索Selenium:自动化测试的神奇钥匙
目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

uniapp 小程序 学习(一)
利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 :开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置,将微信开发者工具放入到Hbuilder中, 打开后出现 如下 bug 解…...

CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!
本文介绍了一种名为AnomalyAny的创新框架,该方法利用Stable Diffusion的强大生成能力,仅需单个正常样本和文本描述,即可生成逼真且多样化的异常样本,有效解决了视觉异常检测中异常样本稀缺的难题,为工业质检、医疗影像…...