iOS IdiotAVplayer实现视频分片缓存

news2025/3/13 11:48:31

文章目录

  • 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];
    }
    
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/996571.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Kafka3.0.0版本——消费者(offset的默认维护位置)

目录 一、offset的默认维护位置1.1、offset的默认维护位置概述1.2、offset的默认维护位置图解 二、消费者offset的案例 一、offset的默认维护位置 1.1、offset的默认维护位置概述 Kafka0.9版本之前&#xff0c;consumer默认将offset保存在Zookeeper中。从Kafka0.9版本开始&am…

强大的JTAG边界扫描(2):BSDL文件介绍

文章目录 1. 什么是BSDL文件&#xff1f;2. BSDL文件的获取方式1&#xff1a;BSDL Library方式2&#xff1a;各芯片的官方网站Xilinx BSDL文件获取Altera BSDL文件获取Microsemi FPGA BSDL文件获取ST BSDL文件获取 3. BSDL文件示例4. BSDL文件的应用 1. 什么是BSDL文件&#xf…

C语言_指针(1)

文章目录 前言一、指针数组1.1利用指针数组模拟出二维数组 二、数组指针2.1数组名是数组首元素的地址2.2 二维数组传参2.3 一级指针传参2.4 二级指针传参 三. 函数指针四 . typedef 重命名 前言 指针数组是由指针组成的数组。它的每个元素都是一个指针&#xff0c;可以指向任何…

【算法与数据结构】530、LeetCode二叉搜索树的最小绝对差

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;二叉搜索树的性质是左子树的所有节点键值小于中间节点键值&#xff0c;右子树的所有节点键值大于中间节…

【Spring面试】三、Bean的配置、线程安全、自动装配

文章目录 Q1、什么是Spring Bean&#xff1f;和对象有什么区别Q2、配置Bean有哪几种方式&#xff1f;Q3、Spring支持的Bean有哪几种作用域&#xff1f;Q4、单例Bean的优势是什么&#xff1f;Q5、Spring的Bean是线程安全的吗&#xff1f;Q6、Spring如何处理线程并发问题&#xf…

uview 组件 u-form-item 点击事件

问题 click"showCalendar(false)"点击没反应 原因&#xff1a; 组件未定义此事件&#xff0c;可使用原生点击事件.native click.native"showCalendar()" <u-form-item label"开始时间" label-width"150" right-icon"arrow…

戳气球00

题目链接 戳气球 题目描述 注意点 求戳破所有的气球所能获得硬币的最大数量0 < nums[i] < 1001 < n < 300 解答思路 初始只想到深度优先遍历暴力搜索所有情况找到获得硬币的最大数量&#xff0c;但是时间复杂度很高一定会超时参照题解使用动态规划解决本题&am…

机器人中的数值优化(十五)——PHR增广拉格朗日乘子法

本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考&#xff0c;主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等&#xff0c;本系列文章篇数较多&#xff0c;不定期更新&#xff0c;上半部分介绍无约束优化&#xff0c;…

Python中的进度条显示方案

迷途小书童 读完需要 3分钟 速读仅需 1 分钟 大家好&#xff0c;我是迷途小书童! tqdm 是一个非常常用的 Python 进度条库&#xff0c;它可以在循环迭代和 IO 操作期间添加一个进度条&#xff0c;直观地显示循环迭代的进程。 tqdm 是在 2013 年发布的&#xff0c;目的是为 Pyth…

netrw模拟nerdtree的go命令连续打开多个文件

vim9自带的文件浏览器netrw功能很强大。过去用惯了nerdtree的我&#xff0c;对netrw的文件操作还要适应一些时间。 使用netrw一段时间后发现它没有nerdtree的go命令的替代操作&#xff0c;今天就自制一个。 一、制作go命令&#xff1a; nerdtree的go命令功能&#xff1a;就是…

NetSuite 客户销售团队更新方法辨析

在NetSuite中如果想将销售团队与客户数据进行隔离&#xff0c;采用的方法是“在客户上定义销售团队&#xff0c;同时将销售团队成员的访问角色进行Employee Restrictions”。 其中&#xff0c;Employee Restrictions的主题我们过去发过几篇&#xff0c;大家可以参考。 NetSui…

【Blender】Blender入门学习

目录 0 参考视频教程0.1 Blender理论知识0.2 Blender上手实践0.3 FBX模型导入Unity 1 Blender的窗口介绍1.1 主界面1.2 模型编辑窗口 2 Blender的基本操作2.1 3D视图的平移2.2 3D视图的旋转2.3 3D视图的缩放2.4 修改快捷键2.5 使物体围绕选择的物体旋转2.6 四视图的查看2.7 局部…

Vivado 2017.04版本安装教程

文章目录 前言一、vivado 简介二、vivado 下载三、vivado 安装四、vivado 申请证书五、关闭升级提醒六、资源自取 前言 本文记录了在 windows 11 下安装 vivado 2017 的详细步骤。 一、vivado 简介 Vivado 是 Xilinx 公司于 2012 推出的新一代集成设计环境&#xff0c;虽然目…

【初阶C语言】操作符2---表达式求值

前言&#xff1a;本节重点介绍操作符的使用&#xff0c;如&#xff0c;优先级高低、类型转换等 一、逻辑操作符 前言&#xff1a;逻辑操作符包括逻辑与&#xff08;&&&#xff09;和逻辑或&#xff08;||&#xff09;&#xff0c;操作对象&#xff1a;两个 1.逻辑与&…

LeetCode 1359. Count All Valid Pickup and Delivery Options【动态规划,组合数学】1722

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

10.Xaml ListBox控件

1.运行界面 2.运行源码 a.Xaml 源码 <Grid Name="Grid1"><!--IsSelected="True" 表示选中--><ListBox x:Name="listBo

腾讯云免费SSL证书申请流程(图文教程)

2023腾讯云免费SSL证书申请流程&#xff0c;一个腾讯云账号可以申请50张免费SSL证书&#xff0c;免费SSL证书为DV证书&#xff0c;仅支持单一域名&#xff0c;申请腾讯云免费SSL证书3分钟即可申请成功&#xff0c;免费SSL证书品牌为TrustAsia亚洲诚信&#xff0c;腾讯云百科分享…

coderforces round 894(div.3)

Problem - A - Codeforces AC代码: #include<iostream> #include<algorithm> #include<cstring> #include<cmath> #include<queue> #include<set> #include<vector> #define endl \n //#define int long long using namespace std…

LeetCode——贪心篇(三)

目录 452. 用最少数量的箭引爆气球 435. 无重叠区间 763. 划分字母区间 56. 合并区间 738. 单调递增的数字 968. 监控二叉树 刷题顺序及思路来源于代码随想录&#xff0c;网站地址&#xff1a;https://programmercarl.com 452. 用最少数量的箭引爆气球 有一些球形气球贴…

系统架构设计专业技能 ·结构化需求分析 - 数据流图

现在的一切都是为将来的梦想编织翅膀&#xff0c;让梦想在现实中展翅高飞。 Now everything is for the future of dream weaving wings, let the dream fly in reality. 点击进入系列文章目录 系统架构设计高级技能 结构化需求分析 - 数据流图 一、数据流图的基本概念二、需…