0%

题目

LeetCode.1143.最长公共子序列(中等)

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,”ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

阅读全文 »

题目

LeetCode.304.二维区域和检索 - 矩阵不可变(中等)

给定一个二维矩阵 matrix,以下类型的多个请求:

  • 计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2) 。
    实现 NumMatrix 类:
  • NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化
  • int sumRegion(int row1, int col1, int row2, int col2) 返回左上角 (row1, col1) 、右下角 (row2, col2) 的子矩阵的元素总和。
    阅读全文 »

题目

LeetCode.931.下降路径最小和(中等)

给你一个 n x n 的 方形 整数数组 matrix ,请你找出并返回通过 matrix 的下降路径 的 最小和 。

下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 (row, col) 的下一个元素应当是 (row + 1, col - 1)、(row + 1, col) 或者 (row + 1, col + 1) 。

阅读全文 »

概述

优势

协程核心优势是结构化编程,每个协程都是一个逻辑调度流,并加以各种自定义的控制。
代码结构清晰,可读性增强,便于调试和测试。

协程核心实现

Kotlin协程的实现的核心是围绕Continuation对象和挂起函数展开的。

要理解协程的各个原理和机制,首先要搞明白挂起函数的原理,再搞懂启动一个协程具体的执行步骤和底层实现,然后才能理解其他的机制,比如线程切换、拦截器、任务取消、异常处理等机制。

比如说取消,如果调用了一个系统的挂起函数,是支持取消的,当取消Job的时候,挂起函数就不执行它的Continuation的回调方法,挂起函数后续的逻辑也就不会执行了。

挂起函数和Continuation

挂起函数在编译后,都会给函数增加一个Continuation的参数,Continuation类代表就是一个异步回调,里面的resumeWith()方法就是代表一般意义上回调对象里的onSuccess或者onFail。

回调的onSuccess后执行的是什么呢?
执行的是调用挂起函数的后续逻辑,Continuation等于是把逻辑流给封装了一下。

Continuation是怎么做到封装挂起函数后续逻辑的?

这要从协程的启动说起。

通过CoroutineScope的launch()方法启动一个协程的时候,观察launch函数的参数,协程体代码是写在一个suspend修饰过的lambda表达式里面的,这个suspend lambda最终会编译为一个SuspendLambda类。

launch函数中执行协程,最终是通过调用SuspendLambda类的create()函数返回一个Continuation对象,再调用这个Continuation的resume()方法来执行。

  • SuspendLambda的create()方法返回的Continuation对象就是创建一个SuspendLambda对象返回,SuspendLambda其实是继承Continuation对象的,所以可以被作为Continuation返回;
  • 其resumeWith的方法实现是在继承链中的BaseContinuationImpl对象中,这里会调用invokeSuspend()方法,invokeSuspend()是实现在SuspendLambda中的,协程体里的代码都是在invokeSuspend()中的。

如果在协程体里调用了别的挂起函数,之前说了会需要传一个Continuation对象,传的就是SuspendLambda本身;

如果挂起函数的逻辑要切线程,不在同一个栈中执行,会返回一个挂起的枚举常量,然后invokeSuspend()方法就直接return了,不执行后续的逻辑。

等到这个挂起函数执行完了,会调用Continuation的resumeWith()恢复后续逻辑执行,就会又执行到SuspendLambda的invokeSuspend()方法,执行协程体的后续代码。

状态机

这里是通过状态机来控制执行逻辑,简单理解就是一个读取状态的switch语句,编译时会把挂起函数前后的逻辑划分到不同的状态下,分在不同的case中执行,每次执行一个挂起函数,都会改变状态,每次重新调用invokeSuspend()的时候,会走到switch语句的不同的case,以执行不同的逻辑。

这里使用状态机还有个好处就是,如果协程体里执行了100个挂起函数,给这100个挂起函数传递Continuation参数的时候,只需要传递SuspendLambda一个实例,而不用创建100个Continuation对象,这样节约内存,所以保证了高性能。

suspendCoroutine

还有一个神奇的函数是suspendCoroutine,函数有个参数是一个带Continuation参数的lambda表达式,也是crossline内联的,这个方法的作用是获取到代表后续执行逻辑的Continuation对象,这个方法是suspend修饰的,并且是内联的,因为方法和lambda都是内联的,所以最后lambda里的代码会直接贴到suspendCoroutine的调用处。

因为函数又是suspend的,所以会把SuspendLambda对象传给挂起函数的Continuation参数,SuspendLambda就获取到了后续的代码执行逻辑,其实就是状态机的一个case。

调度器 - 线程切换

线程切换的生效靠的是拦截器机制,每次启动协程的时候,会在创建SuspendLambda之后,调用一下SuspendLambda的intercept()方法,调用intercept()返回的Continuation的resume()来启动协程。

intercept()里面会去到context中去寻找key为CoroutineInterceptor的对象,也就是拦截器对象。
存在的话,会调用一下interceptContinuation()方法,把当前的SuspendLambda传递过去,返回一个新的Continuation对象。

我们一般做协程的线程调度的时候,会在launch的时候传递一个Dispatchers.DefaultDispatchers.IO,这两个对象都是CoroutineDispatcher的实现类,会以CoroutineInterceptor为Key存储在coroutineContext中。

然后就会在intercept()取出来调用,CoroutineDispatcher中会返回一个新的Continuation对象,对原始的Continuation对象也就是SuspendLambda做一个代理,新的Continuation对象里会用线程池来控制原始的Continuation的resumeWith()的执行时机,从而实现了线程的调度。

线程池

线程池是类似于ForkJoinPool的实现,每个线程都有私有队列,私有队列没有任务时会去其他线程私有队列的另外一头窃取任务,这样可以最大程度避免了单个共享队列添加和移除任务的加锁开销,又保证某个线程私有队列没有任务时不会一直空等消耗CPU,任务窃取相当于实现了队列共享。

如果线程的状态跟任务的类型不匹配或者线程还没创建,会先把任务放到一个全局队列,让其他准备好的线程从全局队列取任务,这样减少线程没有准备好的空等消耗。一切机制都是为了最大程度提高线程池整体的吞吐量。

Job

Job可以被编排为一系列的父子层次关系,当取消父Job时会递归的取消所有的子Job。

子Job运行失败抛出异常时会立刻取消父Job和它所有的子Job。

这样的控制可以改变,让任务控制更加的自由灵活,实现自己想要的效果。

序列生成器

序列生成器,也是利用控制yield()这个挂起函数的Continuation对象的执行,来计算下一个值。

参考资料

题目

LeetCode.413.等差数列划分(中等)

如果一个数列 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该数列为等差数列。

例如,[1,3,5,7,9]、[7,7,7,7] 和 [3,-1,-5,-9] 都是等差数列。

给你一个整数数组 nums ,返回数组 nums 中所有为等差数组的 子数组 个数。
子数组 是数组中的一个连续序列。

阅读全文 »

题目

LeetCode.91.解码方法(中等)

一条包含字母 A-Z 的消息通过以下映射进行了 编码 :

‘A’ -> 1
‘B’ -> 2

‘Z’ -> 26

要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,”11106” 可以映射为:

  • “AAJF” ,将消息分组为 (1 1 10 6)
  • “KJF” ,将消息分组为 (11 10 6)

注意,消息不能分组为 (1 11 06) ,因为 “06” 不能映射为 “F” ,这是由于 “6” 和 “06” 在映射中并不等价。

给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。

题目数据保证答案肯定是一个 32 位 的整数。

阅读全文 »