kotlin协程-- 基础概念 ①|创建和使用

引言

首先先说一些相关概念

1.并发与并行

在操作系统中我们曾经学到过并发并行

并发:   是同一个时刻只有一条指令在执行,其他指令没有再执行,但是由于CPU的时间片特别短,导致多个指令来回切换的时间间隔特别短,就好像是同一时间多条指令在执行单核CPU与多核CPU都可以进行并发

并行:  在同一个时刻,多条指令在执行,这个不用想,只能在多核CPU中进行

2.同步与异步

同步操作很常见,我们一般运行一个程序,只能等该程序执行完毕后才能执行其他的程序。

而异步操作,是如果我们程序遇到一个循环10次的函数,我们的程序可能不会直接循环10次,而是跳过这个程序执行其他的程序。

在okhttp3中不就有同步的和异步两种请求方式

同步如下

        OkHttpClient okHttpClient = new OkHttpClient();//1.定义一个clientRequest request = new Request.Builder().url("http://www.baidu.com").build();//2.定义一个requestCall call = okHttpClient.newCall(request);//3.使用client去请求try {String result = call.execute().body().string();//4.获得返回结果System.out.println(result);} catch (IOException e) {e.printStackTrace();}

异步如下

        OkHttpClient okHttpClient = new OkHttpClient();//1.定义一个clientRequest request = new Request.Builder().url("http://www.baidu.com").build();//2.定义一个requestCall call = okHttpClient.newCall(request);//3.使用client去请求call.enqueue(new Callback() {//4.回调方法@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {String result = response.body().string();//5.获得网络数据System.out.println(result);}});

分析:

1、同步获得call之后,就通过call来获得Response然后再将Response转化为String类型。

2、异步的时候,获得call,就调用call的enqueue方法,然后在OnReponse中进行处理对了这enqueu中在handler的post方法中其实也用到过,我们post之后会调用它的sendMessageDelay然后调用sendMessageAtTime然后调用enqueueMessage,所以handler的post方法就是一个异步方法。

3.阻塞

3.1Looper的阻塞
3.1.1 loop的源码
public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}if (me.mInLoop) {Slog.w(TAG, "Loop again would have the queued messages be executed"+ " before this one completed.");}me.mInLoop = true;// Make sure the identity of this thread is that of the local process,// and keep track of what that identity token actually is.Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();// Allow overriding a threshold with a system prop. e.g.// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'final int thresholdOverride =SystemProperties.getInt("log.looper."+ Process.myUid() + "."+ Thread.currentThread().getName()+ ".slow", 0);me.mSlowDeliveryDetected = false;for (;;) {if (!loopOnce(me, ident, thresholdOverride)) {return;}}
}

我们可以看到在最后它有一个for死循环,只有当     !loopOnce(me, ident, thresholdOverride)

我们再看看loopOnce的源码

3.1.2loopOnce源码
private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {Message msg = me.mQueue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return false;}// This must be in a local variable, in case a UI event sets the loggerfinal Printer logging = me.mLogging;if (logging != null) {logging.println(">>>>> Dispatching to " + msg.target + " "+ msg.callback + ": " + msg.what);}// Make sure the observer won't change while processing a transaction.final Observer observer = sObserver;final long traceTag = me.mTraceTag;long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;if (thresholdOverride > 0) {slowDispatchThresholdMs = thresholdOverride;slowDeliveryThresholdMs = thresholdOverride;}final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);final boolean needStartTime = logSlowDelivery || logSlowDispatch;final boolean needEndTime = logSlowDispatch;if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));}final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;final long dispatchEnd;Object token = null;if (observer != null) {token = observer.messageDispatchStarting();}long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);try {msg.target.dispatchMessage(msg);if (observer != null) {observer.messageDispatched(token, msg);}dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} catch (Exception exception) {if (observer != null) {observer.dispatchingThrewException(token, msg, exception);}throw exception;} finally {ThreadLocalWorkSource.restore(origWorkSource);if (traceTag != 0) {Trace.traceEnd(traceTag);}}if (logSlowDelivery) {if (me.mSlowDeliveryDetected) {if ((dispatchStart - msg.when) <= 10) {Slog.w(TAG, "Drained");me.mSlowDeliveryDetected = false;}} else {if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",msg)) {// Once we write a slow delivery log, suppress until the queue drains.me.mSlowDeliveryDetected = true;}}}if (logSlowDispatch) {showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);}if (logging != null) {logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);}// Make sure that during the course of dispatching the// identity of the thread wasn't corrupted.final long newIdent = Binder.clearCallingIdentity();if (ident != newIdent) {Log.wtf(TAG, "Thread identity changed from 0x"+ Long.toHexString(ident) + " to 0x"+ Long.toHexString(newIdent) + " while dispatching to "+ msg.target.getClass().getName() + " "+ msg.callback + " what=" + msg.what);}msg.recycleUnchecked();return true;
}

我们不用看这么多代码,我们只需要知道什么时候返回true,什么时候返回false

我们就会发现当

当成功从消息队列中获取到一条消息时,会执行消息的分发和处理,并在最后通过 return true; 表示成功处理了一条消息,准备继续下一次循环。
当从消息队列中获取到的消息为 null,即消息队列正在退出时,会通过 return false; 表示没有更多的消息需要处理,退出循环。
这时候我们大概就明白,当消息队列不断有message传过来的时候,looper.loop会一直进行下去

当没有message传过来的时候looper.loop的for循环会退出

3.1.3注意
我当时把阻塞和死循环化成等号了,所以一直理解的就是在有消息传过来的时候,就会阻塞Looper,没有消息传过来的时候就不会阻塞Looper。但是阻塞的理解通俗的来说就是这个线程啥都不干,光等着,该线程处于休眠状态了或则长时间执行不完,卡在那里了。

所以根据这个理解的话,当没有Message传过来的时候,Looper处于阻塞状态。当有Message传来的时候,Looper不处于阻塞状态

现在我们来说说:Looper处于死循环是否会导致ANR?

3.2Looper处于死循环是否会导致ANR
3.2.1ANR是什么
在 Android 中,ANR(Application Not Responding)是指应用程序未响应用户交互事件(如触摸屏幕、按键等)的情况。当应用程序长时间未响应事件时,系统会显示一个 ANR 对话框,通知用户该应用程序已停止响应,然后给用户选择 “Force Close”(强制关闭)或 “Wait”(等待)的选项。

简单的说当一个任务占用太多资源就容易造成ANR

3.2.2Looper的死循环是否会导致ANR
面试中常考点是:Looper的死循环,会不会导致ANR?

答案是否定的,looper循环不会导致ANR ,只可能会阻塞主线程(当没有消息传过来) 它有一个消息队列 当消息队列里有消息的时候就会循环去取 当没有消息的时候就会调用epoll.await然后阻塞主线程 当重新有消息的时候会唤醒然后执行 他的阻塞和ANR不是一个概念的 ANR是指应用无响应 假如说我一个点击事件它长时间无响应就会导致ANR 但是阻塞的话是因为没有消息 而不是无响应。

我最开始想不通的一点是假如我looper处理一个特别大的任务,然后为什么不能反驳Looper的死循环是否导致ANR?
后来想了一下处理一个特别大的事件这是它的dispatchMessage处理的和我的死循环没有关系啊,我的死循环只负责把找消息又不负责处理消息。

这是一个学姐给我讲的,我原本以为这样就代表阻塞主线程之后也不会导致ANR

记得我们刚才说的两种导致阻塞的情况

1.没有消息传来等待消息传来

2.处理耗时量大的任务

我们现在是第一种情况,在没有收到消息时,它实际上是在等待新的消息到达。(相当于一个休眠状态)它并不会占用过多的CPU资源或者阻塞其他操作。在这种情况下,主线程依然可以处理用户界面事件和其他操作。所以,当Looper在收不到消息时阻塞主线程,它不会造成ANR。但是,如果Handler在处理消息时执行了耗时操作,这有可能导致ANR。

但这道题其实主要想问的是Looper的epoll与ANR的概念。我想的有点多,有点钻牛角尖了

3.2.3总结
在 Looper 的死循环中,它会不断地从消息队列中取出消息,并将消息分发给对应的 Handler 处理。当消息队列为空时,Looper 会一直循环等待新的消息到达。

在等待消息期间,Looper 的死循环会阻塞主线程,因为它会一直占用主线程的执行时间片。这意味着主线程无法继续执行其他任务或响应用户的输入事件或系统事件。

只有当主线程长时间占用了 CPU 或其他系统资源,并且长时间无法响应用户输入事件或完成关键操作时,才会触发 ANR 错误。

耗时操作本身并不会导致主线程卡死,导致主线程卡死的真正原因是耗时操作之后的操作, 没有在规定的时间内被分发。

4.挂起

挂起就是保存当前状态,等待恢复执行,在 Android 中的体现,挂起就是不影响主线程的工作,更贴切的说法可以理解为切换到了一个指定的线程。

4.1阻塞和挂起的区别

它们两个主要的不同在于释放CPU

挂起是协作式的,即在挂起时会主动将线程让出,使得线程可以执行其他任务。

同时,在协程挂起时,协程的堆栈和上下文会被保存下来,等待挂起结束后恢复执行。

这种方式可以避免线程的切换开销,提高程序的性能和响应速度。

阻塞是强制式的,即阻塞时会将线程一直占用,直到阻塞结束后才会继续执行。

在阻塞时,线程无法执行其他任务,因此会浪费 CPU 资源。

同时,阻塞也会增加线程切换的开销,降低程序的性能和响应速度。

5.多任务

多任务就是操作系统能够同时处理多个任务

多任务中又分出了2个:

一个是抢占式多任务

一个是协作式多任务

抢占式多任务就是操作系统自己来指定每个任务的CPU的占有时间,超过这个时间后。当前的这个任务就无法占有CPU了,交给下一个任务。
但是协作式多任务是除非你那个任务自己放弃对CPU的占有,否则别的任务无法用那个CPU。

协程

协程的作用

线程是非常重量级的,他需要依靠操作系统的调度才能实现不同线程的切换。但是协程却可以仅在编程语言的层面就能实现不同协程之间的切换

协程允许我们在单线程模式下模拟多线程编程的效果

协程的基本用法

导入包:

 implementation'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1'implementation'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
2.1GlobalScope.launch
fun main(){
GlobalScope.launch {println("hello")Log.d("TAG","HELLO WORLD")
}
}

然后在MainActivity中调用main()  , 发现没有打印。

因为:Global.launch每次创建的都是一个顶层协程,这种协程当应用程序结束的时候会跟着一起结束。刚才日志无法打印就是因为代码块的代码还没来的及执行,应用程序就结束了。

我们只需要让程序延迟一段时间结束就可以了

fun main(){
GlobalScope.launch {println("hello")Log.d("TAG","HELLO WORLD")
}Thread.sleep(1000)
}

我们让这个main所在的线程阻塞1000ms,让主线程处于休眠状态,这时候我们会发现打印出来了。

存在一个问题,那么就是如果代码在1000ms内没办法结束,那么就会被强制中断。

fun main(){
GlobalScope.launch {println("hello")delay(1500)Log.d("TAG","HELLO WORLD")
}Thread.sleep(1000)
}

main所在的线程休眠1000ms然后执行GlobalScope.launch里面的代码,我们设置了在println()之后延迟1500ms才会执行Log.d()里面的东西

这时候我们会发现只会打印出println()里面的东西,但是打不出**Log.d()**里面的东西

2.2runBlocking

GlobalScope.launch的时候,因为它是一个顶层协程,会随程序的结束而结束

那么有没有一种协程,等它协程中的代码都执行完后,才会结束程序呢?

runBlocking

fun main1(){runBlocking {println("runBlocking")}
}

 输出  :runBlocking

runBlocking可以保证协程作用域内所有的代码和子协程中没有全部执行完之前,当前线程一直被阻塞。

一般runBlocking在正式开发中性能可能出现问题,所以我们一般只在测试环境下用。

2.3创建多个协程

在刚才的runBlocking里面加上launch就行了

fun main2(){runBlocking{launch {println("launch_0")}launch {println("launch_1")}}
}

输出:

launch_0

launch_1

例子:

fun main2(){runBlocking{launch {println("launch_0")delay(1000)println("launch_0 finished")}launch {println("launch_1")delay(1000)println("launch_1 finished")}}
}

输出:

launch_0
launch_1
launch_0 finished
launch_1 finished

tips:

launch与GlobalScope.launch不同,前者必须在协程的作用域中才能调用,其次它会在当前协程的作用域下创建子协程。

(子协程是一种当外层作用域的协程结束了,该作用域下所有的子协程也会一同结束)

2.4suspend关键字

launch必须在协程的作用域下才能被使用,如果我们把所有的launch都写在runBlocking里面,那么如果launch特别多的话,那么runBlocking所占的行数就更多了

那么可不可以最前面写了runBlocking后面调用方法?

ok,没问题。

fun main3(){runBlocking {main4()main5()}
}
fun main4(){println("没有协程作用域_0")
}
fun main5(){println("没有协程作用域_1")
}

但是呢,你会发现没办法调用类似delay()这种挂起函数,因为没有协程的作用域(通俗讲就是{}的范围就是作用域) 。

我们可以通过suspend关键字解决这个问题:

fun main3(){runBlocking {main4()main5()}
}
suspend fun main4(){delay(1000)println("没有协程作用域_0")
}
fun main5(){println("没有协程作用域_1")
}

这样就成功实现了**delay()**这类挂起函数

这样虽然可以实现delay这类挂起函数,但是我们不能调用launch函数,它只能在协程的作用域中才能调用。

2.5coroutineScope

coroutineScope函数是一个挂起函数,可以在任何其他挂起函数中调用。

它的特点是会继承外部的协程的作用域并创建一个子协程。

fun main6(){runBlocking {main7()main8()}
}
suspend fun main7() = coroutineScope{launch {println("coroutineScope_0")delay(1000)}
}
suspend fun main8() = coroutineScope{launch {println("coroutineScope_1")delay(1000)}
}

它可以保证其作用域内的所有代码和子协程在全部执行完之前,外部的协程会一直被挂起 。

coroutineScoperunBlocking不太一样

前者只会挂起当前协程,不影响其他协程,也不影响任何线程。

但是后者会挂起外部线程,如果在主线程中使用 runBlocking 函数来启动一个长时间运行的协程,就会导致主线程被阻塞,从而导致界面卡死的问题。

2.5.1注意

我当时就是这块没有理解,然后又去看了一眼Looper的死循环是否会导致ANR,我当时候的理解是Looper当没有收到消息,epoll会发出一个await指令,导致主线程被阻塞。但是阻塞不会导致ANR

然后我又看这里:runBlocking会挂起外部线程,如果在主线程中使用 runBlocking 函数来启动一个长时间运行的协程,就会导致主线程被阻塞,从而导致界面卡死的问题。

然后我就昏了,不是阻塞不会导致ANR嘛,这里为什么又导致了ANR,事实证明我当时的理解是错的,具体解析在上面写了。

这里因为处理耗时操作而导致主线程阻塞,时间太长的话就会导致ANR。
 

协程作用域

在讲构造器作用域之前,我们先来了解了解什么叫协程作用域

GlobalScope.launch中,它创建的是一个顶层协程,它里面的代码会随着应用程序的结束而结束。

runBlocking中我们说:runBlocking可以保证协程作用域里面的所有代码和子协程中没有全部执行完之前当前线程一直被阻塞。

然而launch,它必须依赖协程作用域才能执行

coroutineScoperunBlocking的作用效果差不多,它可以在协程作用域或者挂起函数中调用。

coroutineScope:只会挂起外部协程不会影响其他。

runBlocking:会导致直接把线程阻塞。

======================================================================   

什么叫协程作用域?
========================================================================================================

协程作用域(Coroutine Scope)是指协程的生命周期和作用域,用于管理协程的执行范围和生命周期,以确保协程的安全和正确性。

在协程中,通常使用 CoroutineScope 接口来定义协程作用域。CoroutineScope 接口提供了一组协程构建器(Coroutine Builder)协程上下文(Coroutine Context),用于创建和管理协程的生命周期和作用域。协程作用域可以是全局的,也可以是局部的,它们的生命周期可以是短暂的,也可以是长久的。

在协程作用域中,可以创建一个或多个协程,并将它们组织成一个层次结构。每个协程都有自己的作用域和生命周期,它们可以访问和共享作用域内的资源和状态。协程作用域还可以定义协程的取消策略和异常处理方式,以确保协程的安全和正确性。

========================================================================================================

协程必须在协程作用域中才能启动,

协程作用域中定义了一些父子协程的规则,

Kotlin 协程通过协程作用域来管控域中的所有协程

协程作用域间可并列或包含,组成一个树状结构,这就是 Kotlin 协程中的结构化并发。

作用域细分有下述三种:

顶级作用域:没有父协程的协程所在的作用域(GlobalScope.launch)

协同作用域:协程中启动新协程(即子协程),此时子协程所在的作用域默认为协同作用域,子协程抛出的未捕获异常都将传递给父协程处理,父协程同时也会被取消;(coroutineScope)

主从作用域:与协同作用域父子关系一致,区别在于子协程    出现未捕获异常时不会向上传递给父协程

父子协程间的规则

1、父协程如果取消或结束了,那么它下面的所有子协程均被取消或结束。

2、父协程需等待子协程执行完毕后才会最终进入完成状态,而不管父协程本身的代码块是否已执行完。

3、子协程会继承父协程上下文中的元素,如果自身有相同 Key 的成员,则覆盖对应 Key,覆盖效果仅在自身范围内有效。

作用域构造器

场景:我们要关闭一个应用,但是该应用中启动了一个协程来执行网络请求,而这个请求在应用关闭之前还没有完成,那么这个请求将会继续执行,这可能会浪费系统资源,例如网络带宽和 CPU 时间,而且还可能会导致请求结果无法正确处理或者造成其它一些问题。

我们在使用顶层协程进行网络加载操作的时候,可能会遇到这样的问题。

所以我们需要在退出应用之前,先把协程关了。

协程要怎么取消呢?不管是GlobalScope.launch函数还是launch函数,它们都会返回一个Job对象,只需要调用Job对象的**cancle()**方法就可以取消协程了。

val job = GlobalScope.launch { //  处理具体逻辑
}
job.cancel()

如果我们每次创建的都是顶层协程,那么当Activity关闭时,就需要逐个调用所有已创建协程的cancel()方法,这种情况代码就很难维护了。因此,GlobalScope.launch这种协程作用域构建器,在实际项目中也是不太常用的。

实际项目常用的写法
val job = Job()
val scope = CoroutineScope(job)
scope.launch { //  处理具体逻辑
}
job.cancel()

先创建一个Job对象,传入CoroutineScope()函数,CoroutineScope()函数会返回一个CoroutineScope对象,有了这个对象,就可以调用这个CoroutineScope对象的launch函数来创建一个协程了。

所以调用CoroutineScope的launch函数所创建的协程,都会被关联在Job对象的作用域下面,这样只需调用一次job.cancel()方法,就可以将同一作用域内的所有协程全部取消,这就大大降低了协程管理的成本。

而不用像GlobalScope那样,用多个GlobalScope.launch创建job对象,得每个job都**cancel()**一下才能把所有的协程关闭。但是像CoroutineScope,因为我们说过无论是GlobalScope.launch函数还是launch函数,它们都会返回一个Job对象,所以我们的首先的目的就是创建一个Job对象。然后我们将job传进CoroutineScope()获得一个CoroutineScope对象,然后将这个对象.launch。

最后只用将job.cancel()就可以全部取消了。

为什么GlobalScope不可以一次性取消所有的协程,而CoroutineScope可以 ?

简单来说就是GlobalScope一次性只能创建一个launch,但是CoroutineScope一次性可以创建多个launch 。

async

想要创建一个协程并获取它的执行结果,就要用到async函数。

一般情况下我们就是用CoroutineScope.launch但是获得的是Job对象。

示例:

   var job = Job()var c = String()var coroutineScope = CoroutineScope(job)coroutineScope.launch {c = "nihao"}println(job)

输出:

JobImpl{Active}@660a746

fun main00(){runBlocking {val result = async {5+5}.await()println(result)}
}

输出: 10

=====================================================================

async 总结 :

1、async函数必须在协程作用域当中才能调用,它会创建一个新的子协程,并且返回一个Deferred对象。

2、在调用完async函数之后代码块中的代码就会立即执行。

当调用await()方法之后,如果代码块的代码还没有执行完,那么await()方法会将当前协程阻塞住,等待获得async函数的执行结果。

即如果async函数里面的代码块还没有执行完的话,因为有await()方法,所以async所在的协程作用域不会在async执行完前结束,而是先将runBlocking这个协程阻塞,等async 函数执行完成之后再结束。

withContext简化async

fun main() {runBlocking {val result = withContext(Dispatchers.Default) {5 + 5}println(result)}
}

调用withContext()函数之后,会立即执行代码块中的代码,同时将外部协程挂起。当代码块中的代码全部执行完之后,会将最后一行的执行结果作为withContext()函数的返回值返回,我们可以发现withContext的用法和使用async差不多。

我们先把async实现这个的代码写出来:

fun main() {runBlocking {val result = async {5 + 5}.await()println(result)}
}

比较:

withContext与async最大的不同是: Dispatchers.Default

withContext()函数会强制要求指定一个线程参数

协程虽然是很轻量级别的线程,且多个协程可以运行在一个线程里面,但是这并代表着我们就不开线程了。

比如网络请求,我们都知道它是一个耗时很大的操作,在java中我们处理网络请求通常都是另开一个线程,让它在新的线程里面执行,因为我们知道,在主线程里面执行网络请求,容易因为它的耗时大导致ANR。

Dispatchers里面有四种:

分别有Default,Main,IO,Unconfined

至于为什么CPU密集型的计算任务在Dispatcher.Default中执行,因为CPU密集型的计算任务不能在并发特别高的情况下进行。

在我们刚才所学的协程作用域构建器中,除了coroutineScope函数之外,其他所有的函数都是可以指定这样一个线程参数的,只不过withContext()函数是强制要求指定的,而其他函数则是可选的。

结束。

下一篇: kotlin协程-- 基础概念 ② |协程取消和异常处理-CSDN博客

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

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

相关文章

【python】Python常见的面试题解析:深入探索与实践,助你少走弯路

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

若依ruoyi+AI项目二次开发(智能售货机运营管理系统)

(一) 帝可得 - 产品原型 - 腾讯 CoDesign (qq.com)

一些电脑的操作技巧,你知道吗?

我整理了几个电脑使用的实用技巧&#xff0c;能够帮你提升办公效率&#xff0c;一起来看看吧&#xff01; 技巧一&#xff1a;反方向移动单元格 一般来讲&#xff0c;我们按下【Tab】键、【Enter】键的时候&#xff0c;会切换到右边或者下边的单元格&#xff0c;想要反向移动…

第2章 编译SDK

安装编译依赖 sudo apt-get update sudo apt-get install clang-format astyle libncurses5-dev build-essential python-configparser sconssudo apt-get install repo git ssh make gcc libssl-dev liblz4-tool \ expect g patchelf chrpath gawk texinfo chrpath diffstat …

董宇辉离职,我一点都不意外!只不过感觉来的太快

下面这张图&#xff0c;是我在半年多前写的一段随笔&#xff0c;没想到来的这么快&#xff01; 碰巧的是今天中午&#xff0c;在开发者群里有两位老铁自曝&#xff0c;本以为能公司干到老&#xff0c;但公司却不给机会&#xff0c;已经不在是公司员工了。 最近&#xff0c;晓衡…

粗解React 和 Vue 的异同

相同点&#xff1a; 1、都使用虚拟 DOM【Virtural DOM】 Vue与React都使用了 Virtual DOM Diff算法&#xff0c; 不管是Vue的Template模板options api 写法&#xff0c; 还是React的Class或者Function写法,最后都是生成render函数&#xff0c;而render函数执行返回VNode(虚拟…

iOS collectionView 滑动出现空白

iOS collectionView 滑动出现空白 一个很常见的 banner 轮播&#xff0c;滑动的时候&#xff0c;有时候会出现空白&#xff0c;检查了下&#xff0c;发现代码没什么问题&#xff0c;上网查了也没啥结果&#xff0c;最后的解决方法是自定义layout解决 interface TMLoopViewLayo…

创新概念:柯尔莫哥洛夫-阿诺德网络

文章目录 一、说明二、基础概念三、kolmogorov-Arnold 网络性质3.1 KAN 的潜在优势3.2 挑战和注意事项 四、基本 KAN 超参数五、COLAB 代码六、注意点 一、说明 kolmogorov-Arnold 网络 (KAN) 是深度学习领域的一项“创新”&#xff0c;它提供了一种受现有 Kolmogorov-Arnold …

python基础---1.变量、运算符和表达式、基本数据结构

&#x1f388;个人主页&#xff1a;靓仔很忙i &#x1f4bb;B 站主页&#xff1a;&#x1f449;B站&#x1f448; &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;python &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&…

使用Docker搭建MySql的主从同步+ShardingSphere搭建Mysql的读写分离

参考课程 尚硅谷ShardingSphere5实战教程&#xff08;快速入门掌握核心&#xff09;_哔哩哔哩_bilibili 主服务器 创建容器 docker run -d \ -p 3306:3306 \ -v /kira/mysql/master/conf:/etc/mysql/conf.d \ -v /kira/mysql/master/data:/var/lib/mysql \ -e MYSQL_ROOT…

(day26)leecode热题——找到字符串中所有字母异位词

描述 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 异位词 指由相同字母重排列形成的字符串&#xff08;包括相同的字符串&#xff09;。 示例 1: 输入: s "cbaebabacd", p …

【Git-驯化】一文搞懂git中代码冲突的解决方案大全

【Git-驯化】一文搞懂git中代码冲突的解决方案大全 本次修炼方法请往下查看 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我工作、学习、实践 IT领域、真诚分享 踩坑集合&#xff0c;智慧小天地&#xff01; &#x1f387; 免费获取相关内容文档关注&#xff1a…

[C++实战]日期类的实现

&#x1f496;&#x1f496;&#x1f496;欢迎来到我的博客&#xff0c;我是anmory&#x1f496;&#x1f496;&#x1f496; 又和大家见面了 欢迎来到C探索系列 作为一个程序员你不能不掌握的知识 先来自我推荐一波 个人网站欢迎访问以及捐款 推荐阅读 如何低成本搭建个人网站…

ELK安装(Elasticsearch+Logstash+Kibana+Filebeat)

一、简介 1.1、软件简介 ELK其实是Elasticsearch&#xff0c;Logstash 和 Kibana三个产品的首字母缩写&#xff0c;这三款都是开源产品。 1.1.1、Elasticsearch简介 Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎。它能很方便的使大量数据具有搜索、分析…

springboot微信老人健康与饮食管理系统-计算机毕业设计源码82939

基于微信老人健康与饮食管理系统的小程序 摘 要 基于Spring Boot的微信老人健康与饮食管理系统的小程序致力于为老年人提供便捷的健康管理和饮食指导服务。该小程序整合了健康资讯浏览、食谱推荐、健康评估等功能模块&#xff0c;通过系统的设计与实现&#xff0c;旨在帮助老年…

古丝绸之路传闻二:十年败壳精灵显,一介穷神富贵来

古丝绸之路传闻二&#xff1a;十年败壳精灵显&#xff0c;一介穷神富贵来 &#xff08;接上节&#xff1a;古丝绸之路传闻&#xff1a;分内功名匣里财&#xff0c;不关聪慧不关呆&#xff09; 先别说闲话。且说众人带着经纪主人到船上发货&#xff0c;文若虚把之前的事情说了一…

AccessLog| 一款开源的日志分析系统

前言 ClkLog作为分析系列产品中的前端数据分析系统&#xff0c;通过采集前端应用数据进行用户行为分析。其社区版从23年9月发布至今已有近一年&#xff0c;商业版也上线快半年&#xff0c;感谢大家一直以来的关注和支持&#xff0c;ClkLog会继续做好产品升级与服务&#xff0c;…

Linux冯诺依曼体系、操作系统、进程概念、进程状态、进程切换

个人主页&#xff1a;仍有未知等待探索-CSDN博客 专题分栏&#xff1a;Linux 目录 一、冯诺依曼体系结构 二、操作系统 1、概念 2、为什么要有操作系统&#xff1f; 3、理解操作系统 1.管理的本质 2.管理的概念 3.操作系统结构图 4.为什么要有操作系统&#xff1f; 三…

python-NLP:2词性标注与命名实体识别

文章目录 词性标注命名实体识别时间命名实体&#xff08;规则方法&#xff09;CRF 命名实体识别方法 词性标注 词性是词汇基本的语法属性&#xff0c;通常也称为词类。词性标注是在给定句子中判定每个词的语法范畴&#xff0c;确定其词性并加以标注的过程。例如&#xff0c;表示…

Python机器学习实战:分类算法之逻辑回归-泰坦尼克号乘客生还预测

为了解决特定问题而进行的学习是提高效率的最佳途径。这种方法能够使我们专注于最相关的知识和技能&#xff0c;从而更快地掌握解决问题所需的能力。 目录 逻辑回归算法介绍 练习题 Python代码与分析 1、读入数据&#xff0c;观察数据情况 2、各属性与生还情况的关联 3、…