NSThead的进阶使用和简单探讨

news2025/1/19 1:57:05

概述

NSThread类是一个继承于NSObjct类的轻量级类。一个NSThread对象就代表一个线程。它需要管理线程的生命周期、同步、加锁等问题,因此会产生一定的性能开销。 使用NSThread类可以在特定的线程中被调用某个OC方法。当需要执行一个冗长的任务,并且不想让这个任务阻塞应用中的其他部分,尤其为了避免阻塞app的主线程(因为主线程用于处理用户界面展示交互和事件相关的操作),这个时候非常适合使用多线程。线程也可以将一个庞大的任务分为几个较小的任务,从而提高多核计算机的性能。

NSThread类在运行期监听一个线程的语义和NSOperation类是相似的。比如取消一个线程或者决定一个任务执行完后这个线程是否存在。

本文将会从这几个方面开始探讨NSThread

方法属性的介绍

初始化(创建)一个NSThread对象

// 返回一个初始化的NSThread对象
- (instancetype)init
// 返回一个带有多个参数的初始化的NSThread对象
// selector :线程执行的方法,最多只能接收一个参数
// target :selector消息发送的对象
// argument : 传给selector的唯一参数,也可以是nil
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument );
// iOS 10
- (instancetype)initWithBlock:(void (^)(void))block; 

启动一个线程。

// 开辟一个新的线程,并且使用特殊的选择器Selector作为线程入口,调用完毕后,会马上创建并开启新线程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
 // iOS 10
+ (void)detachNewThreadWithBlock:(void (^)(void))block;
// 启动接受者
- (void)start;
// 线程体方法,线程主要入口,start 后执行
// 该方法默认实现了目标(target)和选择器(selector),用于初始化接受者和调用指定目标(target)的方法。如果子类化NSThread,需要重写这个方法并且用它来实现这个线程主体。在这种情况下,是不需要调用super方法的。
// 不应该直接调用这个方法。你应该通过调用启动方法开启一个线程。
- (void)main; 

使用initWithTarget:selector:initWithBlock:detachNewThreadSelector:detachNewThreadWithBlock:创建线程都是异步线程。

停止一个线程

// 阻塞当前线程,直到特定的时间。
+ (void)sleepUntilDate:(NSDate *)date;
// 让线程处于休眠状态,直到经过给定的时间间隔
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 终止当前线程
+ (void)exit;
// 改变接收者的取消状态,来表示它应该终止
- (void)cancel; 

决定线程状态

// 接收者是否存在
@property (readonly, getter=isExecuting) BOOL executing;
// 接收者是否结束执行
@property (readonly, getter=isFinished) BOOL finished;
// 接收者是否取消
@property (readonly, getter=isCancelled) BOOL cancelled; 

主线程相关

// 当前线程是否是主线程
@property (class, readonly) BOOL isMainThread;
// 接受者是否是主线程
@property (readonly) BOOL isMainThread;
// 获取主线程的对象
@property (class, readonly, strong) NSThread *mainThread; 

执行环境

// 这个app是否是多线程
+ (BOOL)isMultiThreaded;
// 返回当前执行线程的线程对象。
@property (class, readonly, strong) NSThread *currentThread;
// 返回一个数组,包括回调堆栈返回的地址
@property (class, readonly, copy) NSArray<NSNumber *> *callStackReturnAddresses ;
// 返回一个数组,包括回调堆栈信号
@property (class, readonly, copy) NSArray<NSString *> *callStackSymbols; 

线程属性相关

// 线程对象的字典
@property (readonly, retain) NSMutableDictionary *threadDictionary;

NSAssertionHandlerKey
// 接收者的名字
@property (nullable, copy) NSString *name;
// 接收者的对象大小,以byte为单位
@property NSUInteger stackSize; 

线程优先级

// 线程开启后是个只读属性
@property NSQualityOfService qualityOfService;
// 返回当前线程的优先级
+ (double)threadPriority;
// 接受者的优先级,已经废弃,使用qualityOfService代替
@property double threadPriority;
// 设置当前线程的优先级。设置线程的优先级(0.0 - 1.0,1.0最高级)
+ (BOOL)setThreadPriority:(double)p; 

通知

// 未被实现,没有实际意义,保留项
NSDidBecomeSingleThreadedNotification
// 在线程退出前,一个NSThread对象收到到退出消息时会发送这个通知。
NSThreadWillExitNotification
// 当第一个线程启动时会发送这个通知。这个通知最多发送一次。当NSThread第一次发送用`detachNewThreadSelector:toTarget:withObject:`,`detachNewThreadWithBlock:`,`start`消息时,发送通知。后续调用这些方法是不会发送通知。
NSWillBecomeMultiThreadedNotification 

线程间通信, 在NSObject的分类NSThreadPerformAdditions中的方法(NSThread.h文件中)具有这些特性:

1.无论是在主线程还是在子线程中都可执行,并且均会调用主线程的aSelector方法;
2.方法是异步的

@interface NSObject (NSThreadPerformAdditions)
// 如果设置wait为YES: 等待当前线程执行完以后,主线程才会执行aSelector方法;
// 如果设置wait为NO:不等待当前线程执行完,就在主线程上执行aSelector方法。
// 如果,当前线程就是主线程,那么aSelector方法会马上执行,wait是YES参数无效。
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;

// 等于第一个方法中modes是kCFRunLoopCommonModes的情况。指定了线程中 Runloop 的 Modes =kCFRunLoopCommonModes。
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

// 在指定线程上操作,因为子线程默认未添加NSRunloop,在线程未添加runloop时,是不会调用选择器中的方法的。
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:( NSArray<NSString *> *)array ;
// 等于第一个方法中modes是kCFRunLoopCommonModes的情况。
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait ;

// 隐式创建子线程,在后台创建。并且是个同步线程。
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg ;
@end 

直接给接受者发消息的其他方法。

1.协议NSObject中的方法,可在主线程或者子线程执行。因为是在当前线程执行的同步任务,因此会阻塞当前线程。这几个方法等同于直接调用方法。

// 当前线程操作。
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2; 

2.延迟操作&按照顺序操作

NSRunLoop.h文件中

// 延迟操作
/**************** 	Delayed perform	 ******************/

@interface NSObject (NSDelayedPerforming)
// 异步方法,不会阻塞当前线程,只能在主线程中执行。是把`Selector`加到主队列里,当 `delay`之后执行`Selector`。如果主线程在执行业务,那只能等到执行完所有业务之后才会去执行`Selector`,就算`delay`等于 0。
// 那`delay `从什么时候开始计算呢?从发送`performSelector`消息的时候。就算这时主线程在阻塞也会计算时间,当阻塞结束之后,如果到了`delay`那就执行`Selector`,如果没到就继续 `delay`。
// 只能在主线程中执行,在子线程中不会调到aSelector方法
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
// 等于第一个方法中modes是kCFRunLoopCommonModes的情况。指定了线程中 Runloop 的 Modes =kCFRunLoopCommonModes。
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
// 在方法未到执行时间之前,取消方法。调用这2个方法当前target执行dealloc之前,以确保不会Crash。
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;

@end
// 按照排序顺序执行
@interface NSRunLoop (NSOrderedPerform)
// 按某种顺序order执行方法。参数order越小,优先级越高,执行越早
// selector都是target的方法,argument都是target的参数
// 这2个方法会设置一个定时器去在下个runloop循环的开始时让target执行aSelector消息。 定时器根据modes确认模式。当定时器触发,定时器尝试队列从runloop中拿出消息并执行。
如果run loop 正在运行,并且是指定modes的一种,则是成功的,否则定时器一直等待直到runloop是modes 中的一种。
- (void)performSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg order:(NSUInteger)order modes:(NSArray<NSRunLoopMode> *)modes;
- (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg;
- (void)cancelPerformSelectorsWithTarget:(id)target;

@end 

本文介绍大部分的知识点如思维导图:

使用

1.创建线程 用initXXX初始化的需要调用start方法来启动线程。而detachXXX初始化方法,直接启动线程。这个2中方式创建的线程都是显式创建线程。

//1. 手动开启,action-target 方式
NSThread * actionTargetThread = [[NSThread alloc] initWithTarget:self selector:@selector(add:) object:nil];
[actionTargetThread start];
//2. 手动开启, block方式
NSThread *blockThread = [[NSThread alloc] initWithBlock:^{NSLog(@"%s",__func__);
}];
[blockThread start];
//3. 创建就启动, action-target方式
[NSThread detachNewThreadSelector:@selector(add2:) toTarget:self withObject:@"detachNewThreadSelector"];
//4. 创建就启动, block 方式
[NSThread detachNewThreadWithBlock:^{NSLog(@"%s",__func__);
}]; 

2.线程中通信

2.1 NSThreadPerformAdditions分类方法,异步调用方法 // 无论在子线程还是主线程,都会调用主线程方法。

a. 主线程

 [self performSelectorOnMainThread:@selector(add:) withObject:nil waitUntilDone:YES];//[self performSelectorOnMainThread:@selector(add:) withObject:@"arg" waitUntilDone:YES modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]]; 

子线程默认没有开启runloop。需要手动添加,不然选择器方法无法调用。

b. 子线程

使用initWithBlock:方式创建。

//1. 开辟一个子线程
NSThread *subThread1 = [[NSThread alloc] initWithBlock:^{// 2.子线程方法中添加runloop// 3.实现线程方法[[NSRunLoop currentRunLoop] run];
}];
//1.2. 启动一个子线程
[subThread1 start];
// 2. 在子线程中调用方法
// [self performSelector:@selector(add:) onThread:subThread1 withObject:@"22" waitUntilDone:YES];
[self performSelector:@selector(add:) onThread:subThread1 withObject:@"arg" waitUntilDone:YES modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]]; 

使用initWithTarget:selector:object:创建。

// 1. 开辟一个子线程
NSThread *subThread2 = [[NSThread alloc] initWithTarget:self selector:@selector(startThread) object:nil];
// 1.2 启动一个子线程
[subThread2 start];
// 3. 在子线程中调用方法
// [self performSelector:@selector(add:) onThread:subThread2 withObject:@"22" waitUntilDone:YES];
[self performSelector:@selector(add:) onThread:subThread1 withObject:@"arg" waitUntilDone:YES modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]];
// 2.子线程方法中添加runloop
- (void)startThread{[[NSRunLoop currentRunLoop] run];
} 

c. 后台线程(隐式创建一个线程)

[self performSelectorInBackground:@selector(add:) withObject:@"arg"]; 

2.2 协议NSObject方法 创建是的同步任务。

[NSThread detachNewThreadWithBlock:^{// 直接调用[self performSelector:@selector(add:) withObject:@"xxx"];
}]; 

2.3 延迟 NSObject分类NSDelayedPerforming方法,添加异步任务,并且是在主线程上执行。

[self performSelector:@selector(add:) withObject:self afterDelay:2]; 

2.4 按照顺序操作 NSRunLoop分类NSOrderedPerform中的方法

[NSThread detachNewThreadWithBlock:^{NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];// 记得添加端口。不然无法调用selector方法[currentRunloop addPort:[NSPort port] forMode:(NSRunLoopMode)kCFRunLoopCommonModes];[currentRunloop performSelector:@selector(add:) target:self argument:@"arg1" order:1 modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]];[currentRunloop performSelector:@selector(add:) target:self argument:@"arg3" order:3 modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]];[currentRunloop run];
}]; 

线程安全

问题:

多个线程可能会同时访问同一块资源。比如多个线程同时访问同一个对象、同一个变量、同一个文件等。当多个线程同时抢夺同一个资源,会引起线程不安全性,可能会造成数据错乱和数据安全问题。

解决:

使用线程同步技术: 可以对可能会被抢夺的资源,在被被竞争的时候加锁。让其保证线程同步状态。而锁具有多种类型:比如读写锁、自旋锁、互斥锁、信号量、条件锁等。在NSThread可能造成资源抢夺情况下,可以使用互斥锁。互斥锁就是多个线程任务按顺序的执行。 如下就使用的情况之一:对需要读写操作的资源,进行加锁操作。

for (NSInteger index = 0 ; index < 100; index ++) {@synchronized (self) {self.allCount -= 5;NSLog(@"%@卖出了车票,还剩%ld",[NSThread currentThread].name,self.allCount);}
} 

线程生命周期。

线程的生命周期是:新建 - 就绪 - 运行 - 阻塞 - 死亡。当线程启动后,它不能一直“霸占”着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也就会随之改变。

1.新建和就绪状态 显式创建,使用initWithTarget:selector:initWithBlock:创建一个线程,未启动,只有发送start消息才会启动,然后处于就行状态。 使用detachNewThreadWithBlock:detachNewThreadSelector:toTarget:显示创建并立即启动。 还有种创建方式,隐式创建并立即启动:performSelectorInBackground:withObject:。2.运行和阻塞状态 如果处于就绪状态的线程获得了CPU资源,开始执行可执行方法的线程执行体(block或者@Selector),则该线程处于运行状态。当发生如下情况下,线程将会进入阻塞状态:

  • 线程调用sleep方法:sleepUntilDate: sleepForTimeInterval:主动放弃所占用的处理器资源。
  • 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞。 线程试图获得一个同步监视器,但该同步监视器正被其他线程锁持有。
  • 线程在等待某个通知(notify)。
  • 程序调用了线程的suspend方法将该线程挂起。不过这个方法容易导致死锁,所以程序应该尽量避免使用该方法。 当前正在执行的线程被阻塞之后,其他线程就可以获得执行的机会了。被阻塞的线程会在合适时候重新进入就绪状态,注意是就绪状态而不是运行状态。也就是 说被阻塞线程的阻塞解除后,必须重新等待线程调度器再次调度它。 针对上面的几种情况,当发生如下特定的情况将可以解除上面的阻塞,让该线程重新进入就绪状态:
  • 调用sleep方法的线程经过了指定时间。
  • 线程调用的阻塞式IO方法已经返回。
  • 线程成功地获得了试图取得同步监视器。
  • 线程正在等待某个通知时,其他线程发出了一个通知。
  • 处于挂起状态的线程被调用了resume恢复方法。

3.线程死亡

  • 可执行方法执行完成,线程正常结束。
  • 程序的意外奔溃。
  • 该线程的发送exit消息来结束该线程。
// 1. 创建:New状态
NSThread * actionTargetThread = [[NSThread alloc] initWithTarget:self selector:@selector(add:) object:nil];
// 2. 启动:就绪状态
[actionTargetThread start];
// 可执行方法
- (void)add:(id)info{// 3. 执行状态NSLog(@"%s,info %@",__func__,info);// 5. 当前线程休眠[NSThread sleepForTimeInterval:1.0];NSLog(@"after");// 4. 程序正常退出
}
// 6. 打取消标签
[actionTargetThread cancel];
// 7. 主动退出
[NSThread exit]; 

注意:

  • NSThread 管理多个线程比较困难,所以不太推荐在多线程任务多的情况下使用。
  • 苹果官方推荐使用GCD和NSOperation。
  • [NSTread currentThread] 跟踪任务所在线程,适用于NSTread,NSOperation,和GCD
  • 用NSThread创建的线程,不会自动添加autoreleasepool

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

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

相关文章

文件minio进阶 分页查询

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、minio使用二、代码1.mino版本是最新的,那么pom中也要最新的2. 部分代码总结前言 就是现在通过minio管理文件,然后不需要其他信息,所以我也就没有用传统方式…

探究视频标注如何支持自动驾驶汽车

自动驾驶汽车技术有望使我们的道路更安全&#xff0c;同时提高运输和交付服务的效率。安全性和可靠性是自动驾驶汽车广泛采用所必需的关键因素。为了保证汽车在现实复杂的交通条件下导航能够有效运行&#xff0c;必须使用标注数据来训练基于计算机视觉的模型&#xff0c;这些数…

verilog学习笔记- 10)按键控制 LED 灯实验

目录 简介&#xff1a; 实验任务&#xff1a; 硬件设计&#xff1a; 程序设计&#xff1a; 下载验证 &#xff1a; 总结与反思&#xff1a; 简介&#xff1a; 按键开关是一种电子开关&#xff0c;属于电子元器件类。我们的开发板上有两种按键开关&#xff1a;第一种是本实…

NeRF制作数据集最后的踩坑

之前对数据集渲染的时候一直出现模糊的问题&#xff1a; 学长说可能是摄像机和poses没有对应。但是为什么会这样呢 后来我发现&#xff0c;确实在制作的过程中&#xff0c;某一步出了问题&#xff0c;导致下标的数字&#xff0c;跟理想的不一样&#xff1a; 原来的12&#xf…

基于yolov5-v7.0开发构建工业机械齿轮瑕疵实例分割检测识别分析系统

在之前的文章中我们已经做了很多基于yolov5完成实例分割的项目&#xff0c;感兴趣的话可以自行移步阅读&#xff1a;《基于YOLOv5-v7.0的药片污染、缺损裂痕实例分割检测识别分析系统》《基于yolov5-v7.0开发构建裸土实例分割检测识别模型》《基于yolov5-v7.0开发实践实例分割模…

深度学习PyTorch 之 DNN-回归(多变量)

深度学习&PyTorch 之 DNN-回归中使用HR数据集进行了实现&#xff0c;但是HR数据集中只有一个变量&#xff0c;这里我们使用多变量在进行模拟一下 流程还是跟前面一样 #mermaid-svg-LN8ayy7UjtqZ6dSj {font-family:"trebuchet ms",verdana,arial,sans-serif;font…

计算机原理三_进程管理

目录儿四、进程管理4.1 什么是进程4.1.1 进程的结构4.1.2 进程的特征4.1.3 进程与线程4.1.4 线程的实现方式用户级线程内核支持线程组合线程的调度4.2 进程是怎么运行的4.2.1 进程状态4.2.2 进程控制4.2.2.1 原语的概念4.2.2.2 挂起与激活4.2.3 进程调度4.2.3.1 调度层次4.2.3.…

CSS入门一、初识

零、文章目录 文章地址 个人博客-CSDN地址&#xff1a;https://blog.csdn.net/liyou123456789个人博客-GiteePages&#xff1a;https://bluecusliyou.gitee.io/techlearn 代码仓库地址 Gitee&#xff1a;https://gitee.com/bluecusliyou/TechLearnGithub&#xff1a;https:…

【BP靶场portswigger-服务端9】服务端请求伪造SSRF漏洞-7个实验(全)

前言&#xff1a; 介绍&#xff1a; 博主&#xff1a;网络安全领域狂热爱好者&#xff08;承诺在CSDN永久无偿分享文章&#xff09;。 殊荣&#xff1a;CSDN网络安全领域优质创作者&#xff0c;2022年双十一业务安全保卫战-某厂第一名&#xff0c;某厂特邀数字业务安全研究员&…

Episode 02 对称密码基础

一、从文字密码到比特序列密码 1、使用对称密钥进行加密 为了使原来的明文无法被推测出来&#xff0c;就要尽可能地打乱密文&#xff0c;这样才能达到加密的目的。密文打乱的是比特序列&#xff0c;无论是文本&#xff0c;图片还是音乐&#xff0c;只要能够将数据转换比特序列…

MSF后渗透持续后门

持续后门 ○ 利用漏洞取得的meterpreter shell运行于内存中&#xff0c;重启失效 ○ 重复exploit漏洞可能造成服务崩溃 ○ 持久后门保证漏洞修复后仍可远程控制 Meterpreter后门 run metsvc -A #删除-r use exploit/multi/handler set PAYLOAD windows/metsvc_bind_tcp se…

[22]. 括号生成

[22]. 括号生成题目算法设计&#xff1a;回溯算法设计&#xff1a;空间换时间题目 传送门&#xff1a;https://leetcode.cn/problems/generate-parentheses/ 算法设计&#xff1a;回溯 括号问题可以分成俩类&#xff1a; 括号的合法性判断&#xff0c;主要是用栈括号的合法生…

【自然语言处理】Word2Vec 词向量模型详解 + Python代码实战

文章目录一、词向量引入二、词向量模型三、训练数据构建四、不同模型对比4.1 CBOW4.2 Skip-gram 模型4.3 CBOW 和 Skip-gram 对比五、词向量训练过程5.1 初始化词向量矩阵5.2 训练模型六、Python 代码实战6.1 Model6.2 DataSet6.3 Main6.4 运行输出一、词向量引入 先来考虑一个…

IDEA远程快速部署SpringBoot项目到Docker环境

1.LInux上先安装docker环境 https://blog.csdn.net/YXWik/article/details/128643662 2.配置Docker远程连接端口 1. vim /usr/lib/systemd/system/docker.service 2. 找到ExecStar 在后面添加 -H tcp://0.0.0.0:2375 3. 退出编辑界面&#xff1a;先按esc&#xff0c;然后"…

【JAVA程序设计】(C00100)基于Springboot+html的前后端分离停车场管理系统

基于Springboothtml的前后端分离停车场管理系统项目简介项目获取开发环境项目技术运行截图项目简介 基于SpringBoothtml的前后端分离的停车场管理系统&#xff0c;本系统分为二种角色&#xff1a;管理员和收银员。 1&#xff0e;登录&#xff1a;管理员可以通过系统分配的账号…

Android 系统框架结构

目录 1.应用层(System Apps)&#xff1a; 2.应用框架层(Java API Framework)&#xff1a; 3.系统运行库层(Native)&#xff1a; 4.硬件抽象层(HAL)&#xff1a; 5.Linux内核层&#xff08;Linux Kernel&#xff09;&#xff1a; 大部分开发的同学是不太清楚Android的系统的…

解决企业微信启动报错:0x0000142无法打开

解决企业微信启动报错&#xff1a;0x0000142无法打开1.问题描述2.问题查找3.问题解决4.事后感悟系统&#xff1a;Win10 WXWork&#xff1a;4.0.20.6020 1.问题描述 不知道从啥时候开始&#xff0c;打开企业微信会报错&#xff08;见下图&#xff09;&#xff0c;报错代码是&am…

【Redis】缓存穿透问题及其解决方案

【Redis】缓存穿透问题及其解决方案 文章目录【Redis】缓存穿透问题及其解决方案1. 缓存穿透概念及原因2. 解决方案2.1 缓存空对象2.1.1 缓存空对象的优缺点2.1.2 改进代码2.2 布隆过滤2.2.1 布隆过滤的优缺点1. 缓存穿透概念及原因 缓存穿透&#xff1a;客户端请求的数据在 缓…

HTML与CSS基础(十)—— 综合项目

应用前面技术知识 完成小兔鲜儿项目设计图素材下载&#xff1a;链接: https://pan.baidu.com/s/1o5mWkgEfaTAA5spxMLuXEQ?pwdex7e 提取码: ex7e 一、Header 部分开发 布局分析&#xff1a;header布局分析&#xff1a;xtx-shortcut ①布局分析&#xff1a;xtx-shortcut ②布局分…

Hudi系列3:Hudi核心概念

文章目录Hudi架构一. 时间轴(TimeLine)1.1 时间轴(TimeLine)概念1.2 Hudi的时间线由组成1.3 时间线上的Instant action操作类型1.4 时间线上State状态类型1.5 时间线官网实例二. 文件布局三. 索引3.1 简介3.2 对比Hive没有索引的区别3.3 Hudi索引类型3.4 全局索引与非全局索引四…