【iOS】GCD

news2024/9/20 17:52:37

参考文章:GCD函数和队列原理探索

之前写项目的时候,进行耗时的网络请求使用GCD处理过异步请求,但对一些概念都很模糊,这次就来系统学习一下GCD相关


相关概念

什么是GCD?

Grand Center Dispatch简称GCD,是苹果公司开发的技术,以优化应用程序支持多核心处理器

GCD的优势:

  • GCD 是苹果公司为多核的并⾏运算提出的解决⽅案
  • GCD 会⾃动利⽤更多的CPU内核(⽐如双核、四核)
  • GCD 会⾃动管理线程的⽣命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉GCD想要执⾏什么任务,不需要编写任何线程管理代码

GCD要做的事情就是:GCD将任务添加到队列,并指定执行任务的函数

「任务」

任务: 执行操作,就是在线程中执行的那段代码。在GCD中是放在Block中的。执行任务有两种方式:

  • 同步执行(sync):
    • 必须等待当前语句执⾏完毕,才会执⾏下⼀条语句
    • 不会开启线程
    • 只能在当前线程中执行任务,不具备开启新线程的能力
  • 异步执行(async):
    • 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务
    • 可以在新的线程中执行任务,具备开启新线程的能力
    • 异步是多线程的代名词

注意:异步执行(async) 虽然具有开启新线程的能力,但是并不一定开启新线程,这跟任务指定的队列类型有关

两者主要的区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力

「队列」

队列(Dispatch Queue): 这里的队列指执行任务的等待队列,即用来存放任务的队列。每读取一个任务,则从队列中释放一个任务,参考下图:

请添加图片描述

队列分为两种:串行队列和并发队列。不同的队列中,任务排列的方式是不一样的,任务通过队列的调度,由线程池安排的线程来执行
两者都符合FIFO(先进先出)的原则:

  • 串行队列(Serial Dispatch Queue):每次只有一个任务被执行,让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
  • 并发队列(Concurrent Dispatch Queue):可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)

注意:并发队列的并发功能只有在异步(dispatch_async)方法下才有效

两者的主要区别是:执行顺序不同,以及开启线程数不同。参考下图:

请添加图片描述

创建队列:

// 串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_SERIAL);

// 并行队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_CONCURRENT);

MRC下,生成的队列需由程序员负责持有或释放,通过以下两个函数的引用计数来管理内存:

dispatch_retain(serialQueue);
dispatch_release(serialQueue);

队列与函数组合

队列用来调用任务,函数用来执行任务,那么队列和函数不同的配合会有怎样的运行效果呢?

  • 同步函数串行队列
    1. 不会开启线程,在当前线程中执行任务
    2. 任务串行执行,任务一个接着一个执行
    3. 会产生阻塞
  • 同步函数并发队列
    1. 不会开启线程,在当前线程中执行任务
    2. 任务一个接着一个执行
  • 异步函数串行队列
    1. 会开启一个线程
    2. 任务一个接着一个执行
  • 异步函数并发队列
    1. 开启线程,在当前线程执行任务
    2. 任务异步执行,没有顺序,CPU调度有关

GCD中的队列

主队列

  1. The main queue:系统自带的一个队列,放到这个队列中的代码会被系统分配到主线程中执行。Main queue可以调用dispatch_get_main_queue()来获得。因为main queue是与主线程相关的,所以这是一个串行队列, 交至其中的任务顺序执行(一个任务执行完毕后,再执行下一个任务)

    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    

全局队列

  1. Global queues:整个应用程序存在4个全局队列(系统已经创建好,只需获得即可):高、中(默认)、后台三个优先级队列,可以调用dispatch_get_global_queue函数传入有下级来访问队列。全局队列是并行队列,可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务),并发功能只有在异步(dispatch_async)函数下才有效

    dispatch_queue_t globalQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    dispatch_queue_t globalQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t globalQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    dispatch_queue_t globalQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    

自定义队列

  1. 用户自己创建队列:dispatch_queue_create创建的队列可以是串行的,也可以是并行的,因为系统已经给我们供了并行、串行队列,所以一般情况下我们不再需要在创建自己的队列。用户创建的队列可以有任意多个

注意: 分线程不能刷新UI,刷新UI只能在主线程。如果每个线程都刷新UI,将会很容易造成UI冲突,会出现不同步的情况,所以只有主线程中能刷新UI系统是为了降低编程的复杂程度,最大程度的避免冲突

相关案例分析

耗时性

任务是耗时的,不同函数只要执行任务,都会耗时

异步函数会开启线程,执行的耗时相对较少,在实际开发中,异步可以用来解决并发、多线程等问题

六种情况示例

主队列添加同步任务

在当前的main队列中添加一个任务,并同步执行该任务:

void mainSyncTest(void) {
    /*
     主队列同步
     不会开启线程
     会崩溃!
     */
    NSLog(@"start");
    
    // dispatch_queue_t concurrentQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_queue_t serialQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serialQueue, ^{
        NSLog(@"a");
    });
    
    NSLog(@"b");
}

在当前流程中,默认队列就是主队列,也是一个串行队列,任务执行顺序应为:

在这里插入图片描述

而到了第二步的块任务,会向当前的主队列中添加一个任务NSLog(@"a");,因为主队列是一个串行队列,现在要执行b,必须要等a(任务块)执行完成,而a又必须等b执行完成,产生了相互等待问题,造成了死锁!见下图:

在这里插入图片描述

运行结果就是崩溃:

在这里插入图片描述

解决办法就是将主队列改成自定义的串行队列或并发队列:

NSLog(@"start");

// dispatch_queue_t concurrentQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t serialQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
    NSLog(@"a");
});

NSLog(@"b");

运行结果:

在这里插入图片描述

主队列添加异步任务
void mainAyncTest(void) {
    NSLog(@"start");
    
    dispatch_queue_t serialQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
        NSLog(@"a");
    });
    
    NSLog(@"b");
}

主队列添加异步任务不会阻塞,不会崩溃

运行结果:

在这里插入图片描述

并发队列添加异步任务
void concurrentAsyncTest(void) {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1 --- %@", [NSThread currentThread]);
    
    dispatch_async(concurrentQueue, ^{
        NSLog(@"2 --- %@", [NSThread currentThread]);
        
        dispatch_async(concurrentQueue, ^{
            NSLog(@"3 --- %@", [NSThread currentThread]);
        });
        
        NSLog(@"4 --- %@", [NSThread currentThread]);
    });
    
    NSLog(@"5 --- %@", [NSThread currentThread]);
}

并发队列,通道比较宽,不会导致任务的阻塞
每个任务复杂度基本一致,异步不会堵塞主线程,dispatch_async会开启一个新的线程去执行其中的任务块

运行结果:

在这里插入图片描述

并发队列添加同步任务
void concurrentSyncTest(void) {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1 --- %@", [NSThread currentThread]);
    
    dispatch_sync(concurrentQueue, ^{
        NSLog(@"2 --- %@", [NSThread currentThread]);
        
        dispatch_sync(concurrentQueue, ^{
            NSLog(@"3 --- %@", [NSThread currentThread]);
        });
        
        NSLog(@"4 --- %@", [NSThread currentThread]);
    });
    
    NSLog(@"5 --- %@", [NSThread currentThread]);
}

因为并发队列,所以不会导致队列任务的阻塞,同时因为是同步执行,所以不会开启新的线程,按照顺序去执行流:

在这里插入图片描述

串行队列添加异步任务
void serialAsyncTest(void) {
    // 串行队列
    NSLog(@"start --- %@", [NSThread currentThread]);
    
    dispatch_queue_t serialQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 5; ++i) {
        dispatch_async(serialQueue, ^{
            NSLog(@"%d --- %@", i, [NSThread currentThread]);
        });
    }
    
    NSLog(@"hello queue");
    NSLog(@"end --- %@", [NSThread currentThread]);
}

运行结果:

在这里插入图片描述

串行队列添加异步任务,开启了一条新线程,但是任务还是串行,所以任务是一个一个执行
另一方面可以看出,所有任务是在打印的start和end之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始同步执行

串行队列添加同步任务
void serialSyncTest(void) {
    NSLog(@"start --- %@", [NSThread currentThread]);
    
    dispatch_queue_t serialQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 5; ++i) {
        dispatch_sync(serialQueue, ^{
            NSLog(@"%d --- %@", i, [NSThread currentThread]);
        });
    }
    
    NSLog(@"hello queue");
    NSLog(@"end --- %@", [NSThread currentThread]);
}

运行结果:

在这里插入图片描述

串行队列同步执行任务,所有任务都是在主线程中执行的,并没有开启新的线程。而且由于串行队列,所以按顺序一个一个执行
同时我们还可以看到,所有任务都在打印的start和end之间,这说明任务是添加到队列中马上执行的

串行队列添加同步和异步任务混合

void serialSyncAndAsyncTest(void) {
    NSLog(@"start --- %@", [NSThread currentThread]);
    
    dispatch_queue_t serialQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue, ^{
        NSLog(@"a --- %@", [NSThread currentThread]);
        dispatch_sync(serialQueue, ^{
            NSLog(@"b --- %@", [NSThread currentThread]);
        });
        NSLog(@"d --- %@", [NSThread currentThread]);
    });
    
    NSLog(@"hello queue");
    NSLog(@"end --- %@", [NSThread currentThread]);
}

在异步添加的线程中,情况类似主队列添加同步函数,b(任务块)和d相互等待了,导致死锁!

运行结果:

在这里插入图片描述

并发队列多任务

void concurrentSyncAndAsyncTest(void) {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(concurrentQueue, ^{
        NSLog(@"1");
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"2");
    });
    
    dispatch_sync(concurrentQueue, ^{
        NSLog(@"3");
    });
    
    NSLog(@"0");
    
    dispatch_async(concurrentQueue, ^{
        NSLog(@"7");
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"8");
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"9");
    });
}
// 3 0 1 2 7 9 8
// 3 0 1 7 9 8 2
// 3 0 1 2 7 8 9
// 3 1 0 2 7 8 9
// 3 2 1 0 7 8 9
// 3 1 2 0 7 9 8

结果分析:

  • 主队列共10个任务
  • 1、2、3的顺序不确定
  • 3、0是同步任务,所以3一定在0前面
  • 7、8、9一定在0后面

GCD线程间通信

在iOS开发过程中,我们一般在主线程里边进行UI刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯

void communicateAmongThread(void) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        for (int i = 0; i < 6; ++i) {
            NSLog(@"1 --- %@", [NSThread currentThread]);
        }
         
        // 回到主线程
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"2 --- %@", [NSThread currentThread]);
        });
    });
}

运行结果:

在这里插入图片描述

可以看到在其他线程中先执行操作,执行完了之后回到主线程执行主线程的相应操作

GCD的栅栏方法

我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏

这是没添加栅栏函数之前的结果,顺序不确定:

在这里插入图片描述

void barrierFunc(void) {
    dispatch_queue_t queue = dispatch_queue_create("666", DISPATCH_QUEUE_CONCURRENT);
 
    dispatch_async(queue, ^{
        NSLog(@"1 --- %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2 --- %@", [NSThread currentThread]);
    });
 
    dispatch_barrier_async(queue, ^{
        NSLog(@"----barrier-----%@", [NSThread currentThread]);
    });
 
    dispatch_async(queue, ^{
        NSLog(@"3 --- %@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"4 --- %@", [NSThread currentThread]);
    });
}

栅栏函数可保证异步操作的执行顺序,这里保证了1或2先执行,3或4后执行:

在这里插入图片描述

GCD的延时方法

当我们需要延迟执行一段代码时,就需要用到GCD的dispatch_after方法

void delayExec(void) {
    NSLog(@"run -- 0");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 3秒后异步执行这里的代码...
       NSLog(@"run -- 2");
    });
}

运行结果:

在这里插入图片描述

GCD的一次性代码(只执行一次)

我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了GCD的dispatch_once方法。使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次

void onceExec(void) {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行一次的代码(这里面是默认线程安全的)
        NSLog(@"Hhhhhhhh");
    });
}

onceExec();
onceExec();

调用两次只会执行一次:

在这里插入图片描述

GCD的队列组

有时候我们会有这样的需求:分别异步执行2个耗时操作,然后当2个耗时操作都执行完毕后再回到主线程执行操作。这时候我们可以用到GCD的队列组。

  • 我们可以先把任务放到队列中,然后将队列放入队列组中
  • 调用队列组的dispatch_group_notify回到主线程执行操作
void queueGroup(void) {
    // GCD的队列组
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 执行1个耗时的异步操作
        int i = 0;
        while (i < 100) {
            NSLog(@"1");
            i++;
        }
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 执行1个耗时的异步操作
        int i = 0;
        while (i < 100) {
            NSLog(@"2");
            i++;
        }
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步操作都执行完毕后,回到主线程...
        NSLog(@"3");
    });
}

运行结果:

在这里插入图片描述

dispatch_set_target_queue

这个函数有两个作用:

  1. 改变队列的优先级
  2. 防止多个串行队列的并发执行

改变队列的优先级

dispatch_queue_create函数生成的串行队列和并发队列,都使用 与默认优先级的Global Dispatch Queue 相同执行优先级 的线程

void serialBackgroundQueue(void) {
    // 需求:生成一个后台的串行队列
    dispatch_queue_t serialQueue = dispatch_queue_create("bySelf", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t globalQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    
    // 第1个参数:需要改变优先级的队列
    // 第2个参数:目标队列
    dispatch_set_target_queue(serialQueue, globalQueueBackground);
}

防止多个串行队列的并发执行

如果是将任务追加到3个串行队列中,那么这些任务就会并发执行。因为每个串行队列都会创建一个线程,这些线程会并发执行
如果将多个串行的queue使用dispatch_set_target_queue指定到了同一目标,那么这多个串行queue在目标queue上就是同步执行的,不再是并行执行
将串行队列加入指定优先级队列,会按照加入优先级队列的顺序依次执行串行队列

未加入优先级队列:

在这里插入图片描述

void abandonSerialQueuesToConcurrent(void) {
    dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL);
 
    dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);
    
//    dispatch_set_target_queue(queue1, targetQueue);
//    dispatch_set_target_queue(queue2, targetQueue);
//    dispatch_set_target_queue(queue3, targetQueue);
        
    dispatch_async(queue1, ^{
        NSLog(@"1 in");
        [NSThread sleepForTimeInterval:3.f];
        NSLog(@"1 out");
    });
 
    dispatch_async(queue2, ^{
        NSLog(@"2 in");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"2 out");
    });
    dispatch_async(queue3, ^{
        NSLog(@"3 in");
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"3 out");
    });
}

加入后:

在这里插入图片描述

dispatch_suspend/dispatch_resume

dispatch_suspend并不会立即暂停正在运行的block,而是在当前block执行完成后,暂停后续的block执行

挂起指定队列:dispatch_suspend(serialQueue);
恢复指定队列:dispatchp_resume(serialQueue);

void suspendOrResumeQueue(void) {
    dispatch_queue_t queue = dispatch_queue_create("com.test.gcd", DISPATCH_QUEUE_SERIAL);
    //提交第一个block,延时5秒打印。
    dispatch_async(queue, ^{
        sleep(5);
        NSLog(@"After 5 seconds...");
    });
    //提交第二个block,也是延时5秒打印
    dispatch_async(queue, ^{
        sleep(5);
        NSLog(@"After 5 seconds again...");
    });
    //延时一秒
    NSLog(@"sleep 1 second...");
    sleep(1);
    //挂起队列
    NSLog(@"suspend...");
    dispatch_suspend(queue);
    //延时10秒
    NSLog(@"sleep 17 second...");
    sleep(17);
    //恢复队列
    NSLog(@"resume...");
    dispatch_resume(queue);
}

线程安全

为了保证线程安全,我们之前了解过互斥锁,在书上给了我们dispatch_semaphore方法,我们总结为三点:

  1. synchronized加锁,属于互斥锁,当有线程执行锁住的代码的时候,其他线程会进入休眠,需要唤醒后才能继续执行,性能较低

    - (void)synchronizedSecurity {
     dispatch_async(dispatch_get_global_queue(0, 0), ^{
         // 加锁保证block中执行完成才会执行其他的
             @synchronized (self) {
                 NSLog(@"1开始");
                 sleep(2);
                 NSLog(@"1结束");
             }
         });
         dispatch_async(dispatch_get_global_queue(0, 0), ^{
             @synchronized (self) {
                 NSLog(@"2开始");
                 sleep(2);
                 NSLog(@"2结束");
             }
         });
    }
    
  2. 信号量semaphore加锁,属于自旋锁,当有线程执行锁住的代码的时候,其他线程会进入死循环的等待,当解锁后会立即执行,性能较高
    提供了三种函数:

    • dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量(同时有几个线程可以执行,一般是1)

    • dispatch_semaphore_wait:可以使总信号量减1,当信号总量小于0时就会一直等待(阻塞所在线程),否则就可以正常执行,这个放在要执行的代码的前面。

    • dispatch_semaphore_signal:发送一个信号,让信号总量加1,代码执行完成之后使用,使其他线程可以继续执行

      void semaphoreSecurity(void) {
        dispatch_semaphore_t semalook = dispatch_semaphore_create(1);
      
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            dispatch_semaphore_wait(semalook, DISPATCH_TIME_FOREVER);
            NSLog(@"1开始");
            sleep(2);
            NSLog(@"1结束");
            dispatch_semaphore_signal(semalook);
        });
      
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            dispatch_semaphore_wait(semalook, DISPATCH_TIME_FOREVER);
            NSLog(@"2开始");
            sleep(2);
            NSLog(@"2结束");
            dispatch_semaphore_signal(semalook);
        });
      }
      
  3. NSLock

    void lockSecurity(void) {
     NSLock *lock = [[NSLock alloc]init];
     dispatch_async(dispatch_get_global_queue(0, 0), ^{
         [lock lock];
         NSLog(@"1开始");
         sleep(2);
         NSLog(@"1结束");
         [lock unlock];
     });
      
     dispatch_async(dispatch_get_global_queue(0, 0), ^{
         [lock lock];
         NSLog(@"2开始");
         sleep(2);
         NSLog(@"2结束");
         [lock unlock];
     });
    }
    

    运行结果:

    在这里插入图片描述

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

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

相关文章

python-docx 如何将列表中的值提取到段落中的run以及保存为多个文档?

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

使用pfld模型进行表盘读数检测

目录 1. 下载项目和数据集2. 配置环境3. 训练和测试3.1 训练3.2 测试 4. 参考 使用pfld模型对压力表进行读表检测 1. 下载项目和数据集 下载项目&#xff1a; git clone https://github.com/zhouayi/pfld.git下载数据集&#xff1a; wget https://github.com/zhouayi/pfld/r…

Vue3扁平化Tree组件的前端分页实现

大家好&#xff0c;我是小卷。得益于JuanTree的扁平化设计&#xff0c;在数据量很大的情况下除了懒加载&#xff0c;使用前端分页也是一种解决渲染性能问题的可选方案。 用法 要实现的文档&#xff1a; 分页效果&#xff1a; 实现 新增属性&#xff1a; 组件setup方法中新增…

java 对象模型的个人理解

文章目录 一、OOP-KCLASS 模型二、疑惑2.1 为什么还需要一个 Class对象&#xff1f;2.2 new 关键字和 Class.newInstance() 的区别&#xff1f; 一、OOP-KCLASS 模型 java 采用了field和method分离的方式&#xff0c;field组成实例 obj &#xff0c;存储在堆区&#xff0c;而m…

web小项目-曼波生日录(Servlet+JSP+MySQL)

效果演示&#xff1a; 当记录条数过多时会自动出现滚轮&#xff0c;数据不会超出紫框 数据库实时记录&#xff1a; 项目源代码以及所用到的资源&#xff1a; 链接: https://pan.baidu.com/s/1w0czmH9xBfetk7CZ7RNbtQ?pwd6666 提取码: 6666 复制这段内容后打开百度网盘手机App…

【数据结构】堆,优先级队列

目录 堆堆的性质大根堆的模拟实现接口实现构造方法建堆入堆判满删除判空获取堆顶元素 Java中的PriorityQueue实现的接口构造方法常用方法PriorityQueue注意事项 练习 堆 如果有一个集合K {k0&#xff0c;k1&#xff0c; k2&#xff0c;…&#xff0c;kn-1}&#xff0c;把它的…

Listen(sockfd,backlog)监听函数的第二个参数到底是什么?深度解释

listen队列剖析 int listen(int sockfd,int backlog) backlog : 监听套接字队列 对于一个调用监听的套接字&#xff0c;系统会维护给这个套接字两个队列 1. 未完成连接队列 //当客户端发生三次握手的第一次syn包给服务器的时候&#xff0c;服务器就会再未完成队列中创建…

数据库处理表

首先先创建库&#xff0c;然后创建需要的这三个表 用dese表名查看 然后题目要求对表进行修改 用alter table这个语法来对表进行修改 modify为修改字段 需要修改的字段的属性类型改变为的属性 最后用descStudent查看 第二题需要创建索引 创建索引createindex索引名称 cre…

世界启动Ⅷ--AI视频制作-方案与创新

1.文本/图片生成视频顾名思义&#xff0c;就是输入一段文本描述/上传一张图片即可生成对应的视频。我们常见的Runway、Pika、NeverEnds、Pixverse、svd等都属于此类。比如runway的影视风格Pika的动漫风格NeverEnds的人像模特当然还有一些外延应用&#xff0c;例如最近比较火的阿…

C++客户端Qt开发——Qt窗口(工具栏)

2.工具栏 使用QToolBar表示工具栏对象&#xff0c;一个窗口可以有多个工具栏&#xff0c;也可以没有&#xff0c;工具栏往往也可以手动移动位置 ①设置工具栏 #include "mainwindow.h" #include "ui_mainwindow.h" #include<QToolBar> #include<…

JavaSE--基础语法--继承和多态(第三期)

一.继承 1.1我们为什么需要继承? 首先&#xff0c;Java中使用类对现实世界中实体来进行描述&#xff0c;类经过实例化之后的产物对象&#xff0c;则可以用来表示现实中的实体&#xff0c;但是 现实世界错综复杂&#xff0c;事物之间可能会存在一些关联&#xff0c;那在设计程…

开发AI自动直播工具需要了解的源代码!

随着人工智能技术的快速发展&#xff0c;AI自动直播工具成为了现代直播领域的一大创新&#xff0c;这些工具利用先进的算法和机器学习模型&#xff0c;能够自动化地生成、编辑和播出直播内容&#xff0c;极大地提高了直播的效率和质量。 然而&#xff0c;要开发一款功能强大的…

10 个顶级的PPT生成AI工具盘点,一文把所有好用软件尽收囊中!

你是否希望在工作中制作 PPT 演示文稿&#xff0c;与他人分享你的洞见&#xff0c;或是发表演讲&#xff1f;然而&#xff0c;使用传统的 PPT 制作方式既耗时又费力&#xff0c;步入 AI 时代后&#xff0c;人们寻求更智能、更简便的 PPT 演示文稿制作方法。 目前市场上出现了一…

谷粒商城实战笔记-65-商品服务-API-品牌管理-表单校验自定义校验器

文章目录 1&#xff0c;el-form品牌logo图片自定义显示2&#xff0c;重新导入和注册element-ui组件3&#xff0c;修改brand-add-or-update.vue控件的表单校验规则firstLetter 校验规则sort 校验规则 1&#xff0c;el-form品牌logo图片自定义显示 为了在品牌列表中自定义显示品…

本地部署VMware ESXi服务实现无公网IP远程访问管理服务器

文章目录 前言1. 下载安装ESXi2. 安装Cpolar工具3. 配置ESXi公网地址4. 远程访问ESXi5. 固定ESXi公网地址 前言 在虚拟化技术日益成熟的今天&#xff0c;VMware ESXi以其卓越的性能和稳定性&#xff0c;成为了众多企业构建虚拟化环境的首选。然而&#xff0c;随着远程办公和跨…

Codeforces Round 955 (Div. 2, with prizes from NEAR!) B. Collatz Conjecture(数学)

这道题考察的主要是通过数学对过程进行优化&#xff0c;而不是通过数学而得到结论&#xff08;让人摸不着头脑&#xff09;。 我们不需要把k次直接一次次的加&#xff0c;这样时间复杂度太大&#xff0c;那么我们现在探讨一次要加多少。 我们想要实现加一个数n&#xff0c;满足…

事务、函数和索引

什么是事务&#xff1f; 事务&#xff08;Transaction&#xff09;&#xff0c;就是将一组SQL语句放在同一批次内去执行&#xff0c;如果一个SQL语句出错&#xff0c;则该批次内 的所有SQL都将被取消执行。 特点 一个事务中如果有一个数据库操作失败&#xff0c;那么整个事务…

Linux网络-pingtelnet

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注我&#xff0c;我尽量把自己会的都分享给大家&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 Linux服务器作为一个常用的网络服务器&#xff0c;主要的作用就是向客户端提供网络…

vue3前端开发-小兔鲜项目-关于详情页图片渲染的一些技术

vue3前端开发-小兔鲜项目-关于详情页图片渲染的一些技术&#xff01;经过前面几天的努力&#xff0c;我们现在已经可以正常渲染产品详情了。是时候汇总一下&#xff0c;基础的技术知识点了。 1&#xff1a;单页面组件内的抽离&#xff0c;是一种很重要的思想。当我们遇到了&…

leetcode日记(49)旋转链表

其实不难&#xff0c;就是根据kk%len判断需要旋转的位置&#xff0c;再将后半段接在前半段前面就行。 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : …