diff --git a/2021/01/30/zero-cost-async-io/CombinatorChain.jpg b/2021/01/30/zero-cost-async-io/CombinatorChain.jpg new file mode 100644 index 0000000..90d6624 Binary files /dev/null and b/2021/01/30/zero-cost-async-io/CombinatorChain.jpg differ diff --git a/2021/01/30/zero-cost-async-io/DanglingPointer.jpg b/2021/01/30/zero-cost-async-io/DanglingPointer.jpg new file mode 100644 index 0000000..ab8a176 Binary files /dev/null and b/2021/01/30/zero-cost-async-io/DanglingPointer.jpg differ diff --git a/2021/01/30/zero-cost-async-io/PollWakeCycle.jpg b/2021/01/30/zero-cost-async-io/PollWakeCycle.jpg new file mode 100644 index 0000000..6f6390c Binary files /dev/null and b/2021/01/30/zero-cost-async-io/PollWakeCycle.jpg differ diff --git a/2021/01/30/zero-cost-async-io/PollWakeCycle1.jpg b/2021/01/30/zero-cost-async-io/PollWakeCycle1.jpg new file mode 100644 index 0000000..1cacb47 Binary files /dev/null and b/2021/01/30/zero-cost-async-io/PollWakeCycle1.jpg differ diff --git a/2021/01/30/zero-cost-async-io/PollWakeCycle2.jpg b/2021/01/30/zero-cost-async-io/PollWakeCycle2.jpg new file mode 100644 index 0000000..c039bb0 Binary files /dev/null and b/2021/01/30/zero-cost-async-io/PollWakeCycle2.jpg differ diff --git a/2021/01/30/zero-cost-async-io/PollWakeCycle3.jpg b/2021/01/30/zero-cost-async-io/PollWakeCycle3.jpg new file mode 100644 index 0000000..c2fd686 Binary files /dev/null and b/2021/01/30/zero-cost-async-io/PollWakeCycle3.jpg differ diff --git a/2021/01/30/zero-cost-async-io/PollWakeCycle4.jpg b/2021/01/30/zero-cost-async-io/PollWakeCycle4.jpg new file mode 100644 index 0000000..2ae10b7 Binary files /dev/null and b/2021/01/30/zero-cost-async-io/PollWakeCycle4.jpg differ diff --git a/2021/01/30/zero-cost-async-io/QuiteFast.jpg b/2021/01/30/zero-cost-async-io/QuiteFast.jpg new file mode 100644 index 0000000..ce447a3 Binary files /dev/null and b/2021/01/30/zero-cost-async-io/QuiteFast.jpg differ diff --git a/2021/01/30/zero-cost-async-io/SelfReferentialFuture.jpg b/2021/01/30/zero-cost-async-io/SelfReferentialFuture.jpg new file mode 100644 index 0000000..e08010c Binary files /dev/null and b/2021/01/30/zero-cost-async-io/SelfReferentialFuture.jpg differ diff --git a/2021/01/30/zero-cost-async-io/StateMachines.jpg b/2021/01/30/zero-cost-async-io/StateMachines.jpg new file mode 100644 index 0000000..d8ce0f0 Binary files /dev/null and b/2021/01/30/zero-cost-async-io/StateMachines.jpg differ diff --git a/2021/01/30/zero-cost-async-io/index.html b/2021/01/30/zero-cost-async-io/index.html new file mode 100644 index 0000000..b9098e7 --- /dev/null +++ b/2021/01/30/zero-cost-async-io/index.html @@ -0,0 +1,567 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 零成本异步IO | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 零成本异步IO +

+ + +
+ + + + +
+ + +
+

这是 Withoutboats 在 2019 年 3 月的 Rust Latam 上所做报告的一个整理。这个报告主要介绍他参与开发了一年半的语言特性,包括 Rust 异步 I/O 的发展历程,以及目前已经稳定的零成本抽象的async/await 语法的关键实现原理。

+

Withoutboats 是就职于 Mozilla 的一名研究员,主要从事 Rust 语言开发。他开发的这个语言特性叫做 async/await,这可能是本年度我们在 Rust 语言上做的最重要的事。这解决了困扰我们很久的问题,即我们如何能在 Rust 中拥有零成本抽象的异步IO。

+

注:因个人水平有限,翻译和整理难免有错误或疏漏之处,欢迎读者批评指正。

+
+

async/await

首先,介绍一下 async/await

+

async 是一个修饰符,它可以应用在函数上,这种函数不会在调用时一句句运行完成,而是立即返回一个 Future 对象,这个 Future 对象最终将给出这个函数的实际返回结果。而在一个这样的 async 函数中,我们可以使用await运算符,将它用在其它会返回 Future 的函数上,直到那些 Future 返回实际结果。通过这种方法,异步并发开发更加方便了。

+
1
2
3
4
5
6
7
8
9
let user = await db.get_user("withoutboats");

impl Database {
async fn get_user(&mut self, user: &str) -> User {
let sql = format!("select FROM users WHERE username = {}", user);
let db_response = await self.query(&sql);
User::from(db_response)
}
}
+

这是一段简短的代码样例,我们具体解释一下 Future 。这段代码基本上做的就是一种类似于 ORM 框架所作的事。你有一个叫 get_user 的函数,它接受一个字符串类型的用户名参数,并通过在数据库中查找对应用户的记录来返回一个User对象。它使用的是异步 I/O ,这意味着它得是一个异步函数,而不是普通函数,因此当你调用它时,你可以异步等待(await)它;然后我们看一下函数的实现,首先是用用户名参数拼接出要执行的 SQL 语句,然后是查询数据库,这就是我们实际执行 I/O 的地方,所以这个查询(query)返回的是 Future ,因为它使用的是异步 I/O 。所以在查询数据库时,你只需要使用异步等待(await)来等待响应,在获得响应后就可以从中解析出用户。这个函数看起来像个玩具,但我想强调的是,它与使用阻塞式 I/O 的唯一区别就是这些注解(指async/await)了,你只需将函数标记为异步(async),并在调用它们时加上 await 就行了,开发的心智负担很小,以至于你会忘了自己是在写异步 I/O 而不是阻塞 I/O 。而 Rust 的这种实现让我尤其感到兴奋的是,它的 async/awaitFuture 都是零成本抽象的。

+

零成本抽象

零成本抽象是 Rust 比较独特的一项准则,这是使 Rust 与其他许多语言相区别的原因之一。在添加新功能时,我们非常关心这些新功能是不是零成本的。不过这并不是我们想出来的点子,它在 C++ 中也很重要,所以我认为最好的解释是 Bjarne Stroustrup 的这句话:

+
+

零成本抽象意味着你不使用的东西,你不用为它付出任何代价,进一步讲,你使用的东西,你无法写出比这更好的代码。

+

Zero Cost Abstractions: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better.

+
+

也就是说零成本抽象有两个方面:

+
    +
  1. 该功能不会给不使用该功能的用户增加成本,因此我们不能为了增加新的特性而增加那些会减慢所有程序运行的全局性开销。
  2. +
  3. 当你确实要使用该功能时,它的速度不会比不使用它的速度慢。如果你觉得,我想使用这个非常好用的功能把开发工作变得轻松,但是它会使我的程序变慢,所以我打算自己造一个,那么这实际上是带来了更大的痛苦。
  4. +
+

所以,我将回顾一下我们如何尝试解决异步 I/O 和 Rust 的问题,以及在我们实现这一目标的过程中,某些未能通过这两项零成本测试的特性。

+

绿色线程的尝试

我们要解决的问题是 异步 I/O 。通常 I/O 处于阻塞状态,因此当你使用 I/O 时,它会阻塞线程,中止你的程序,然后必须通过操作系统重新调度。阻塞式 I/O 的问题是当你尝试通过同一程序提供大量连接时,它无法真正实现扩展。因此对于真正的大规模网络服务,你需要某种形式的非阻塞的或者说异步的 I/O 。尤其是 Rust 是针对具有真正的高性能要求而设计的语言,它是一种系统编程语言,面向那些真正在乎计算资源的人。要在网络的世界中真正取得成功,我们就需要某种解决方案来解决这个异步 I/O 问题。

+

但是 异步 I/O 的最大问题是它的工作方式 :在你调用 I/O 时,系统调用会立即返回,然后你可以继续进行其他工作,但你的程序需要决定如何回到调用该异步 I/O 暂停的那个任务线上,这就使得在编码上,异步 I/O 的代码要比阻塞 I/O 的代码复杂得多。所以,很多,尤其是以可扩展的网络服务这类特性为目标的语言,一直在试图解决这个问题。比如,让它不再是最终用户需要解决的问题,而是编程语言的一部分或者某个库的一部分等等。

+

Rust 最初使用的第一个解决方案是 绿色线程,它已经在许多语言中获得成功。绿色线程基本上就像阻塞式 I/O 一样,使用的时候就像是普通的线程,它们会在执行 I/O 时阻塞,一切看起来就跟你在使用操作系统的原生方式一样。但是,它们被设计为语言运行时的一部分,来对那些需要同时运行成千上万甚至数百万个绿色线程的网络服务用例进行优化。一个使用该模型的典型的成功案例就是 Go 语言,它的绿色线程被称为 goroutine。对于 Go 程序来说,同时运行成千上万个 goroutine 是很正常的,因为与操作系统线程不同,创建它们的成本很低。

+
+ + + + + + + + + + + + + + + + + + + + +
操作系统线程绿色线程
内存开销较大的堆栈,增加大量内存占用初始堆栈非常小
CPU开销上下文切换至操作系统的调度器,成本很高由程序本身的运行时调度
+
+

绿色线程的优点 在于,产生操作系统线程时的内存开销要高得多,因为每个操作系统线程会创建一个很大的堆栈,而绿色线程通常的工作方式是,你将产生一个以很小的堆栈,它只会随着时间的推移而增长,而产生一堆不使用大量内存的新线程并不便宜;并且使用类似操作系统原语的问题还在于你依赖于操作系统调度,这意味着你必须从程序的内存空间切换到内核空间,如果成千上万的线程都在快速切换,上下文切换就会增加很多开销。而将调度保持在同一程序中,你将避免使用这些上下文,进而减少开销。所以我相信绿色线程是一个非常好的模型,适用于许多语言,包括 Go 和 Java。

+

在很长一段时间内, Rust 都有绿色线程,但是在 1.0 版本之前删掉了。我们删掉它是因为它不是零成本抽象的,准确的说就是我在第一个问题中谈到的,它给那些不需要它的人增加了成本。比如你只想编写一个不是网络服务的屏幕打印的 Rust 程序,你必须引入负责调度所有绿色线程的语言运行时。这种方法,尤其是对于试图把 Rust 集成到一个大的 C 应用程序中的人来说,就成为一个问题。很多 Rust 的采用者拥有一些大型C程序,他们想开始使用 Rust 并将 Rust 集成到他们的程序中,只是一小段 Rust 代码。问题是,如果你必须设置运行时才能调用 Rust ,那么这一小部分的 Rust 程序的成本就太高了。因此从 1.0 开始,我们就从语言中删除了绿色线程,并删除了语言的运行时。现在我们都知道它的运行时与 C 基本上相同,这就使得在 Rust 和 C 之间调用非常容易,而且成本很低,这是使 Rust 真正成功的关键因素之一。删除了绿色线程,我们还是需要某种异步 I/O 解决方案;但是我们意识到 这应该是一个基于库的解决方案,我们需要为异步 I/O 提供良好的抽象,它不是语言的一部分,也不是每个程序附带的运行时的一部分,只是可选的并按需使用的库。

+

Future 的解决方案

最成功的库解决方案是一个叫做 Future 的概念,在 JavaScript 中也叫做 PromiseFuture 表示一个尚未得出的值,你可以在它被解决(resolved)以得出那个值之前对它进行各种操作。在许多语言中,对 Future 所做的工作并不多,这种实现支持很多特性比如组合器(Combinator),尤其是能让我们在此基础上实现更符合人体工程学的 async/await 语法。

+

Future 可以表示各种各样的东西,尤其适用于表示异步 I/O :当你发起一次网络请求时,你将立即获得一个 Future 对象,而一旦网络请求完成,它将返回任何响应可能包含的值;你也可以表示诸如“超时”之类的东西,“超时”其实就是一个在过了特定时间后被解决的 Future ;甚至不属于 I/O 的工作或者需要放到某个线程池中运行的CPU密集型的工作,也可以通过一个 Future 来表示,这个 Future 将会在线程池完成工作后被解决。

+
1
2
3
4
5
trait Future {
type Output;
fn schedule<F>(self, callback: F)
where F: FnOnce(Self::Output);
}
+

Future 存在的问题 是它在大多数语言中的表示方式是这种基于回调的方法,使用这种方式时,你可以指定在 Future 被解决之后运行什么回调函数。也就是说, Future 负责弄清楚什么时候被解决,无论你的回调是什么,它都会运行;而所有的不便也都建立在此模型上,它非常难用,因为已经有很多开发者进行了大量的尝试,发现他们不得不写很多分配性的代码以及使用动态派发;实际上,你尝试调度的每个回调都必须获得自己独立的存储空间,例如 crate 对象、堆内存分配,这些分配以及动态派发无处不在。这种方法没有满足零成本抽象的第二个原则,如果你要使用它,它将比你自己写要慢很多,那你为什么还要用它。

+

基于轮询的解决方案

1
2
3
4
5
6
7
8
9
10
11
12
// 基于轮询的 Future

trait Future {
type Output;
fn poll(&mut self, waker: &Waker)
-> Poll<Self::Output>;
}

enum Poll<T> {
Ready(T),
Pending,
}
+

这个非常出色的基于轮询的新方案——我们编写了这个模型,我归功于 Alex 和 Aaron Turon,是他们提出了这个想法——不是由 Future 来调度回调函数,而是由我们去轮询 Future,所以还有另一个被称为执行器(executor)的组件,它负责实际运行 Future ;执行器的工作就是轮询 Future ,而 Future 可能返回“尚未准备就绪(Pending)”,也可能被解决就返回“已就绪(Ready)”。

+

该模型有很多优点。其中一个优点是,你可以非常容易地取消 Future ,因为取消 Future 只需要停止持有 Future。而如果采用基于回调的方法,要通过调度来取消并使其停止就没这么容易了。

+

同时它还能够使我们在程序的不同部分之间建立真正清晰的抽象边界,大多数 Future 库都带有事件循环(event loop),这也是调度你的 Future 执行 I/O 的方法,但你实际上对此没有任何控制权。而在 Rust 中,各组件之间的边界非常整洁,执行器(executor)负责调度你的 Future ,反应器(reactor)处理所有的 I/O ,然后是你的实际代码。因此最终用户可以自行决定使用什么执行器,使用他们想使用的反应器,从而获得更强的控制力,这在系统编程语言中真的很重要。而此模型最重要的真正优势在于,它使我们能够以一种真正零成本的完美方式实现这种状态机式的 Future 。也就是当你编写的 Future 代码被编译成实际的本地(native)代码时,它就像一个状态机;在该状态机中,每次 I/O 的暂停点都有一个变体(variant),而每个变体都保存了恢复执行所需的状态。这表示为一个枚举(enum)结构,即一个包含变体判别式及所有可能状态的联合体(union)。

+

StateMachines

+
+

译者注:报告视频中的幻灯片比较模糊,我对其进行了重绘与翻译,下同。

+
+

上面的幻灯片尽可能直观地表示了这个状态机模型。可以看到,你执行了两个 I/O 事件,所以它有这几个状态。对于每个状态它都提供了所需的内存空间,足够你在 I/O 事件后恢复执行。

+

整个 Future 只需要一次堆内存分配,其大小就是你将这个状态机分配到堆中的大小,并且没有额外的开销。你不需要装箱、回调之类的东西,只有真正零成本的完美模型。这些概念对于很多人来说比较难于理解,所以这是我力求做到最好的幻灯片,直观地呈现这个过程中发生了什么:你创建一个 Future,它被分配到某个内存中特定的位置,然后你可以在执行器(executor)中启动它。

+

PollWakeCycle1

+

执行器会轮询 Future,直到最终 Future 需要执行某种 I/O 。

+

PollWakeCycle2

+

在这种情况下,该 Future 将被移交给处理 I/O 的反应器,即 Future 会等待该特定 I/O 。最终,在该 I/O 事件发生时,反应器将使用你在轮询它时传递的Waker 参数唤醒 Future ,将其传回执行器;

+

PollWakeCycle3

+

然后当需要再次执行I/O时,执行器再将其放回反应器;它将像这样来回穿梭,直到最终被解决(resolved)。在被解决并得出最终结果时,执行器知道它已经完成,就会释放句柄和整个Future,整个调用过程就完成了。

+

PollWakeCycle4

+

总结一下:这种模型形成了一种循环,我们轮询 Future ,然后等待 I/O 将其唤醒,然后一次又一次地轮询和唤醒,直到最终整个过程完成为止。

+

PollWakeCycle

+

并且这种模型相当高效。

+

QuiteFast

+

这是在有关 Future 的第一篇文章中发布的基准测试,与其他语言的许多不同实现进行了对比。柱形越高表示性能越好,我们的 Future 模型在最左侧,所以说我们有了非常出色的零成本抽象,即使是与许多其他语言中最快的异步 I/O 实现相比也是相当有竞争力的。

+

但是,问题在于,你并不希望手动编写这些状态机,把整个应用程序所有状态写成一个状态机并不是件轻松愉快的事。而这种 Future 抽象的真正有用之处在于,我们可以在其之上构建其他 API 。

+

其它API

组合器(Combinator)

我们能使用的第一个解决方案是 Future 组合器(Combinator)。你可以通过将这些组合器方法应用于 Future 来构建状态机,它们的工作方式类似于迭代器(Iterator)的适配器(如 filtermap)。

+
1
2
3
4
5
6
7
8
fn fetch_rust_lang(client: Client)
-> impl Future<Output = String> {
client.get("rust-lang.org").and_then(|response| {
response.concat_body().map(|body| {
String::from_utf8_lossy(body)
})
})
}
+

这个函数的作用是请求 “rust-lang.org”,然后将响应转换为字符串。它并不返回一个字符串,而是返回一个字符串的 Future ,因为它是一个异步函数。函数体中有这样一个 Future,它包含一些会被调用的 I/O 操作,用 and_thenmap 之类的组合器将这些操作全部组合在一起。我们构建了所有这些用处各异的组合器,例如,and_thenmapfiltermap_error等等。我们已经知道这种方式是有一些缺点的,尤其是诸如嵌套回调之类,可读性非常差。

+

async / await 的实现

因为组合器有这样的缺点,所以我们开始尝试实现 async / awaitasync / await 的第一个版本并不是 Rust 语言的一部分,而是由该库像语法插件一样提供的。

+
1
2
3
4
5
6
#[async]
fn fetch_rust_lang(client: Client) -> String {
let response = await!(client.get("rust-lang.org"));
let body = await!(response.concat_body());
String::from_utf9_lossy(body)
}
+

这个函数与之前那个版本的一样,它只是获取 Rust 官网并将其转换为字符串;但是它使用 async / await 来实现,所以更像是顺序执行,看起来更像普通的阻塞 I/O 的工作方式;就像开头那个实例中呈现的一样,它们唯一区别是注解(指 async / await)。我们已经知道,async 注解会将此函数转换为一个返回 Future 的函数,而不是立即返回结果,并且我们需要异步等待(await)这些在函数内部构造的 Future

+
1
2
3
4
5
6
7
8
await!($future) => {
loop {
match $future.poll() {
Poll::Pending => yield Poll::Pending,
Poll::Ready(value) => break value,
}
}
}
+

在我们的轮询模型中,await 是一种语法糖;它会进入上面这种循环,你要做的就是在循环中轮询,在一段时间内你将一直得到“尚未准备就绪(Pending)”,然后一直等到它再次被唤醒,终于你等待的 Future 完成了,然后你使用该值跳出了循环,这就是这些 await 表达式的计算结果。

+

这似乎是一个非常不错的解决方案,async / await 的写法会被编译成我们超棒的零成本的 Future。不过从已发布的 Future 的使用者的反馈看,我们还是发现了一些问题。

+

新的问题

基本上所有试图使用 Future 的人都会遇到非常令人困惑的错误消息。在这些消息中,编译器会提示你的Future的生命周期不是静态的('static)或没有实现某个 trait 等等;这些提示你并不真正理解,但编译器想提出有用的建议,你也就跟着这个建议去做,直到编译成功;你可能会给闭包加上 move 关键字,或者把某些值放到引用计数的指针(Rc)中,然后将复制(clone)它;你将所有这些开销添加到了似乎并不必要的事情上,却不明白为什么要这样做,而当你已经疲于处理这些时,代码已经乱成一锅粥了,所以很多人都被 Future 的问题卡住了。

+

CombinatorChain

+

而且它对于组合器产生这种非常大的类型也没什么办法,你的整个终端窗口(terminal)将被其中一个组合器链的类型填满。你用了and_then,以及又一个 and_then,然后是 map_err 紧跟一个 TCP 流等等等等,你必须仔细研究一下,才能弄清楚所遇到的实际错误是什么。

+
+

“When using Futures, error messages are inscrutable.”
“当使用 Future 时,错误信息难以理解。”
“Having to use RefCell or clone everything for each future leads to overcomplicated code that makes me wish Rust had garbage collection.”
“不得不使用 RefCell 以及为每个 future 克隆所有它需要的值产生了过于复杂的代码,这让我开始期待 Rust 能具备垃圾回收功能了。”

+
+

我在 reddit 上发现了这条消息,我认为它确实很好地总结了所有有关 Future 的抱怨。使用 Future 时,错误消息难以理解;不得不使用 RefCell 以及为每个 future 克隆所有它需要的值产生了过于复杂的代码,这让我开始期待 Rust 能具备垃圾回收功能了(观众笑)。是的,这不是什么好反馈。

+

因此,从大约一年半前的情况来看,很明显,有两个问题需要解决,才能让大家更容易使用。

+

首先,我们需要更好的错误消息,最简单的方法就是将语法构建到语言中,然后它们就可以在你所有的诊断和错误处理代码中加入钩子,从而使你能够真正拥有良好的 async / await 的错误消息。其次,人们遇到的大多数错误实际上是因为他们被一个晦涩难解的问题卡住了——借用问题。正是因为 Future 的设计方式存在着这种根本的局限性,导致一些很普通的编程范式都无法表达。所谓借用问题,就是在最初的 Future 的设计中你不能跨过异步等待点(await point)进行借用,也就是说,如果你要异步等待(await)某件事,你就不能在那个时候持有任何存活的引用。当人们遇到了这种问题,他们通常会被卡住,最终他们会尝试在 await 的时候进行借用然后发现实际上做不到。所以如果我们能够使这种借用被允许,那么大多数这些错误将消失,一切都将变得更易于使用,你可以使用 asyncawait 编写普通的 Rust 代码,并且一切都会正常进行。

+

这种跨越 await 的借用是非常普遍的,因为 Rust 的 API 天然就具有引用。当你实际编译 Future 时,它必须能够恢复所有状态,而当你拥有某些其它东西的引用时,它们位于同一栈帧中,最终你会得到一个自引用Future 结构(Self-Referential Future)。这是开头的那个 get_user 方法,我们有这样一个 SQL 字符串,而在使用 SQL 字符串调用 query 方法时,我们传递的是 SQL 字符串的引用。

+
1
2
let sql: String = format!("SELECT FROM users WHERE username = {}", user);
let db_response = await self.query(&sql);
+

这里存在的问题是,对 SQL 字符串的引用是对存储在相同 Future 状态中的其他内容的引用,因此它成为一种自引用结构。

+

SelfReferentialFuture

+

如果把这个 Future 视作一个真的结构体的话,这就是理论上它所拥有的字段。除了代表数据库句柄的 self 之外,还有 SQL 字符串以及对这个 SQL 字符串的引用,即一个最终指回同一结构体中某个字段的引用。

+

一些新的潜在结构会成为非常棘手的问题,因为我们没有通用的解决方案。当你移动该结构时,我们不允许你使用自引用,是因为我们会在新位置创建一个原有结构的新副本,旧副本会变为无效,但是在复制时,新副本的引用仍指向旧副本的字段,该指针就变成了悬空指针(dangling pointer),而这正是 Rust 必须避免的内存问题。

+

DanglingPointer

+

所以我们不能使用自引用结构,因为如果你移动它们,那么它们将失效。然而,我们实际上并不需要真正移动这些 Future 。如果你还记得在堆中通过句柄使用 Future 的模型,它在反应器和执行器之间来回传递,所以 Future 本身永远不会真正移动;而只要你保证不移动,Future 包含自引用就完全没问题。

+

所以我们需要通过采用某种方式在 Future 的 API 中表达 “在你轮询时,你不允许随意移动它” 来解决这个问题。如果我们能够表达这一点,我们就可以允许 Future 中出现自引用,进而就可以在异步函数中真正使用这些引用,并且一切都会正常工作。因此我们研究了这个问题,最终开发出了被称为 Pin 的新 API 。

+

Pin

Pin 是一种围绕其他指针类型的适配器,可以将其它指针变为 固定引用(pinned reference)。除了原有指针的保证外,它还保证这个引用再也不会被移动,所以我们可以确保它将一直处在同一内存位置,直到最终被释放。如果你的 API 中有一些内容已表明必须使用 Pin,那么你就知道了它再也不会被移动,这样就你可以使用那种自引用的结构体了。因此,我们修改了 Future 的工作方式,现在它变成了一个经过装箱(boxed)的 Future ,实际上是一个由 Pin 封装的 Box<Future> ;这样无论在哪里装箱,我们把它放在堆内存中,它可以保证永远不会再移动;而当你轮询 Future 时,不再使用一个普通引用,而是使用一个固定引用,这样 Future 就知道它不会被移动。而使这一切起作用的诀窍是,要从固定引用中获取非固定引用,你只能使用不安全的代码。

+
1
2
3
4
5
6
7
8
9
10
struct Pin<P>(P);

impl<T> Pin<Box<T>> {
fn new(boxed: Box<T>) -> Pin<Box<T>> { ... }
fn as_mut(self) -> Pin<&mut T> { ... }
}

impl<T> Pin<&mut T> {
unsafe fn get_unchecked_mut(self) -> &mut T { ... }
}
+

所以 Pin API 大致就像这样。在这里你有一个 Pin 结构,它只是另一个指针类型的封装,它没有任何运行时开销或者其它东西,仅仅是将其划分为一个固定的(pinned)对象,然后一个固定的 Box 指针可以转换为一个固定引用,但是将一个固定引用转换为一个非固定引用的唯一方法是使用一个不安全的(unsafe)函数。

+
1
2
3
4
5
6
trait Future {
type Output;

fn poll(self: Pin<&mut self>, waker: &Waker)
-> Poll<Self::Output>;
}
+

这样一来,我们要做的就是修改 Future 的 API ,其轮询方法将不再接受一个普通引用,而是接受一个固定引用,而这其实就是我们将要稳定发布的 Future API。而做了这个修改之后,开头示例的写法就能正常工作了。这样你就可以像写阻塞 I/O 的代码那样编写异步 I/O 的代码了,只需要加上 asyncawait 注解,你就能得到这个出色的零成本抽象的异步实现,而即便你自己手写,这基本上也是你能写出的开销最低的实现了。

+

async / await 的现状及未来

目前的情况是,Pinning 大约在一个月前的最新版本中稳定了,我们正在稳定 Future 的 API ,因此大概会在 1.35,也可能会推到 1.36 稳定,基本上在两三个月内就会知道了。并且我们希望今年的某个时候我们能够拥有 async / await,希望今年夏末能将其稳定下来,这样人们就可以使用这种类似阻塞 I/O 的语法编写无阻塞的 I/O 网络服务了。除了这些稳定化工作,我们也已经开始研究某些更长期的功能,比如流(Stream),我认为它可能是异步的下一个大功能。我们知道一个 Future 只产生一个值,而一个流可以异步地产生很多值;异步地产生值本质上就像是一个异步迭代器,你能够在一个流上进行异步的循环;这个功能对于许多用例来说非常重要,比如流式传输HTTP、WebSocket 推送请求之类的东西,不用像我们的 RPC 模型那样发出网络请求然后获得单个响应,而是能够使用请求流和响应流,在两者之间来回调用。

+

目前使用异步仍然存在一个限制,即不能在 trait 中使用 async。有许多编译器开发工作正在进行,使其能够支持这个特性。除了这些功能,有时候我们也希望能拥有生成器(Generator),类似于 Python 或 JavaScript 的生成器,除了拥有可以 return 的函数,还能使用可以 yield 的函数,这样你就可以在 yield 之后再次恢复执行;并且你可以将这些函数作为编写迭代器和流的方式,就像异步函数能够让你像编写普通函数那样编写 Future 一样。

+

最后,我想回顾一下成就这种零成本异步 I/O 的关键点:首先就是这种基于轮询的 Future,它将这些 Future 编译到这种相当紧凑的状态机中;其次是这种实现 async / await 语法的方式,即因为有了 Pin,我们能够跨越异步等待点使用引用。

+

谢谢大家。

+
+

译者注:报告中计划稳定的特性均已稳定发布,参见 areweasyncyet.rs
按照目前稳定的版本,await 已改为后置运算符 .await,所以本文开头的 get_user 方法应当修改为:

1
2
3
4
5
6
7
8
9
let user = db.get_user("withoutboats").await;

impl Database {
async fn get_user(&mut self, user: &str) -> User {
let sql = format!("select FROM users WHERE username = {}", user);
let db_response = self.query(&sql).await;
User::from(db_response)
}
}

+
+ +
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2021/08/24/design-pattern-1/index.html b/2021/08/24/design-pattern-1/index.html new file mode 100644 index 0000000..7798467 --- /dev/null +++ b/2021/08/24/design-pattern-1/index.html @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 设计模式(一) | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 设计模式(一) +

+ + +
+ + + + +
+ + +

单例模式

一个单一的类,负责创建自己的对象,同时确保只有单个对象被创建。

+

单例模式三种最典型的懒加载单例写法:

+

双重检查锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Singleton {

// 定义为 volatile,防止指令重排序,导致未完成初始化的对象被使用
private static volatile Singleton INSTANCE = null;

public static Singleton getInstance() {
// 基于性能考量,第一次检查,避免每一次都加锁
if (INSTANCE == null) {
// 加锁
synchronized (Singleton1.class) {
// 检查这之前是否有其他线程已经获得过锁并初始化
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}

return INSTANCE;
}
}
+

静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {

// 外部类被加载时,内部类还不会被加载
private static class SingletonHolder {
private final static Singleton INSTANCE = new Singleton();
}

public static Singleton getInstance() {
// 调用这里时,内部类被加载,实现初始化
// 由JVM保证只加载一次,线程安全
return SingletonHolder.INSTANCE;
}

private Singleton() {}
}
+

枚举

1
2
3
4
5
6
7
8
// 懒加载,线程安全,还可以防止反序列化
public enum Singleton {
INSTANCE;

public static Singleton getInstance() {
return Singleton.INSTANCE;
}
}
+
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2021/08/24/design-pattern-2/index.html b/2021/08/24/design-pattern-2/index.html new file mode 100644 index 0000000..44ffada --- /dev/null +++ b/2021/08/24/design-pattern-2/index.html @@ -0,0 +1,487 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 设计模式(二) | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 设计模式(二) +

+ + +
+ + + + +
+ + +

策略模式(Strategy)

策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不通过的对象管理。

+

最典型的例子就是使用比较器(Comparator<T>):

+
1
2
3
4
5
6
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);

// 省略其他...
}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.util.Arrays;
import java.util.Comparator;

public class Cat {
int weight;

public int GetWeight() {
return weight;
}

public Cat(int weight) {
this.weight = weight;
}

@Override
public String toString() {
return "Cat{" +
"weight=" + weight +
'}';
}

public static void main(String[] args) {
Cat[] cats = {new Cat(12), new Cat(21), new Cat(4), new Cat(8)};

// 这里可以new一个实现了Comparator的类,也可以使用Lambda表达式
Arrays.sort(cats, Comparator.comparingInt(Cat::GetWeight));

System.out.println(Arrays.toString(cats));
}
}
+
+

注:

+
    +
  1. 开闭原则(OCP,Open-Closed Principle),对修改关闭,对扩展开放。
  2. +
  3. 自定义静态泛型函数时,将类型参数列表写在 static 和返回值类型之间,例如:
    1
    static <T> void sort(T[] array, Comparator c);
    +
  4. +
+
+
+

抽象工厂(Abstract Factory)

一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。

+

例如,农场可以生产动物、蔬菜。农场A生产牛,白菜,农场B生产羊,花椰菜。编码如下:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
// 农场抽象
public abstract class Farm {
public abstract Animal createAnimal();
public abstract Vegetable createVegetable();
}

// 动物抽象
public interface Animal {
}

// 蔬菜抽象
public interface Vegetable {
}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 农场A
public class FarmA extends Farm {
@Override
public Animal createAnimal() {
return new Cow();
}

@Override
public Vegetable createVegetable() {
return new Cabbage();
}
}

public class Cow implements Animal {
}

public class Cabbage implements Vegetable {
}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 农场B
public class FarmB extends Farm {
@Override
public Animal createAnimal() {
return new Sheep();
}

@Override
public Vegetable createVegetable() {
return new Cauliflower();
}
}

public class Sheep implements Animal {
}

public class Cauliflower implements Vegetable {
}
+
1
2
3
4
5
6
7
8
// 实际调用
public class Main {
public static void main(String[] args) {
Farm farm = new FarmA();
System.out.println(farm.createAnimal().getClass().getSimpleName());
System.out.println(farm.createVegetable().getClass().getSimpleName());
}
}
+

输出:

+
1
2
Cow
Cabbage
+

使用 Rust 语言编写如下:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
use std::fmt::Debug;

// 产品抽象
trait Animal {}
trait Vegetable {}

// 工厂抽象
trait Farm {
type A: Animal;
type V: Vegetable;
const NAME: &'static str;

fn create_animal() -> Self::A;
fn create_vegetable() -> Self::V;
}

#[derive(Debug)]
struct Cow;
impl Animal for Cow {}
#[derive(Debug)]
struct Cabbage;
impl Vegetable for Cabbage {}
struct FarmA;
impl Farm for FarmA {
type A = Cow;
type V = Cabbage;
const NAME: &'static str = "A";

fn create_animal() -> Self::A {
Cow
}

fn create_vegetable() -> Self::V {
Cabbage
}
}

#[derive(Debug)]
struct Sheep;
impl Animal for Sheep {}
#[derive(Debug)]
struct Cauliflower;
impl Vegetable for Cauliflower {}
struct FarmB;
impl Farm for FarmB {
type A = Sheep;
type V = Cauliflower;
const NAME: &'static str = "B";

fn create_animal() -> Self::A {
Sheep
}

fn create_vegetable() -> Self::V {
Cauliflower
}
}

// 实际调用
fn run_a_farm<F>()
where
F: Farm,
F::A: Debug,
F::V: Debug,
{
println!("Farm {}:", F::NAME);
println!("\tAnimal: {:?}", F::create_animal());
println!("\tVegetable: {:?}", F::create_vegetable());
}

fn main() {
run_a_farm::<FarmA>();
run_a_farm::<FarmB>();
}
+

输出结果为:

+
1
2
3
4
5
6
Farm A:
Animal: Cow
Vegetable: Cabbage
Farm B:
Animal: Sheep
Vegetable: Cauliflower
+

典型应用场景

    +
  1. 界面换肤,一款皮肤下,不同的控件的样式;
  2. +
+
+

注:

+
    +
  1. 增加新的产品族,只需要新增工厂实现,满足开闭原则;
  2. +
  3. 产品族需要新增产品的时候,需要修改所有工厂,不满足开闭原则。
  4. +
  5. 当系统只存在一类产品时,抽象工厂退化到工厂方法模式。
  6. +
+
+
+

外观/门面(Facade)

通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。

+

它是迪米特法则的典型应用。降低了子系统与客户端之间的耦合度。

+
+

迪米特法则(LOD,Law of Demeter)又叫作最少知识原则(The Least Knowledge Principle),一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。

+
+ +
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2021/08/24/design-pattern-principle/index.html b/2021/08/24/design-pattern-principle/index.html new file mode 100644 index 0000000..933af54 --- /dev/null +++ b/2021/08/24/design-pattern-principle/index.html @@ -0,0 +1,458 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 设计模式的六大原则 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 设计模式的六大原则 +

+ + +
+ + + + +
+ + +
    +
  1. 开闭原则(Open Close Principle)。对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

    +
  2. +
  3. 里氏代换原则(Liskov Substitution Principle)。里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

    +
  4. +
  5. 依赖倒转原则(Dependence Inversion Principle)。开闭原则的基础,具体内容是针对接口编程,依赖于抽象而不依赖于具体。

    +
  6. +
  7. 接口隔离原则(Interface Segregation Principle)。使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

    +
  8. +
  9. 迪米特法则,又称最少知道原则(Demeter Principle)。一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

    +
  10. +
  11. 合成复用原则(Composite Reuse Principle)。尽量使用合成/聚合的方式,而不是使用继承。

    +
  12. +
+ +
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2021/08/24/how-many-threads/index.html b/2021/08/24/how-many-threads/index.html new file mode 100644 index 0000000..ec81853 --- /dev/null +++ b/2021/08/24/how-many-threads/index.html @@ -0,0 +1,511 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 工作线程数设多少合适 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 工作线程数设多少合适 +

+ + +
+ + + + +
+ + +

遵循以下公式:

+
    +
  • 其中,$N_C$ 为处理器的核的数目,可以通过 Runtime.getRuntime().avaliableProcessors() 得到
  • +
  • $U_C$ 是期望的 CPU 利用率(介于 0 到 1 之间)
  • +
  • $W / C$ 是等待时间与计算时间的比值
  • +
+

例如,对于 CPU 密集型应用,期望 CPU 利用率为 100%,无等待纯计算,则有:

+

即工作线程数设置为处理器的核心数最合适。

+ +
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2021/08/28/search-local-minimum/index.html b/2021/08/28/search-local-minimum/index.html new file mode 100644 index 0000000..807dcae --- /dev/null +++ b/2021/08/28/search-local-minimum/index.html @@ -0,0 +1,525 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 寻找局部最小 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 寻找局部最小 +

+ + +
+ + + + +
+ + +

给你一个数组,长度为N,相邻元素互不相等,请返回任意局部最小值的索引。

+

局部最小定义:

+
    +
  1. 0 位置如果比 1 位置数小,则 0 位置为局部最小;
  2. +
  3. N-1 位置如果比 N-2 位置数小,则 N-1 位置为局部最小;
  4. +
  5. i 位置的数,如果比 i-1i+1 位置的数都小,则 i 位置为局部最小。
  6. +
+
+
    +
  • Java 实现
  • +
+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static int getLocalMinimumIndex(int[] array) {
if (array == null || array.length == 0) {
return -1;
}

if (array.length == 1) {
return 0;
}

if (array[0] < array[1]) {
return 0;
}

if (array[array.length - 1] < array[array.length - 2]) {
return array.length - 1;
}

int left = 1;
int right = array.length - 2;
while (left < right) {
int mid = left + ((right - left) >> 1);
if (array[mid] > array[mid - 1]) {
right = mid - 1;
} else if (array[mid] > array[mid + 1]) {
left = mid + 1;
} else {
return mid;
}
}

return left;
}

    +
  • Rust 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
fn get_local_minimum_index(array: &[i32]) -> Option<usize> {
let len = array.len();
if len == 0 {
return None;
}
if len == 1 {
return Some(0);
}

if array[0] < array[1] {
return Some(0);
}

if array[len - 1] < array[len - 2] {
return Some(len - 1);
}

let mut left = 1;
let mut right = len - 2;
while left < right {
let mid = mid(left, right);
if array[mid] > array[mid - 1] {
right = mid - 1;
} else if array[mid] > array[mid + 1] {
left = mid + 1;
} else {
return Some(mid);
}
}

Some(left)
}

/// 计算两个无符号值的中间值
fn mid<T: Ord + Div<Output = T> + Add<Output = T> + Copy + Sub<Output = T> + From<u8>>(
v1: T,
v2: T,
) -> T {
match v1.cmp(&v2) {
Ordering::Less => {
let v = v2 - v1;
v1 + v / T::from(2u8)
}
Ordering::Equal => v1,
Ordering::Greater => {
let v = v1 - v2;
v2 + v / T::from(2u8)
}
}
}

/// 判断一个索引是否为局部最小值的索引
fn is_local_minimum(array: &[i32], index: usize) -> bool {
let len = array.len();
if len == 0 {
return false;
}
if len == 1 {
return index == 0;
}

let range = 0..len;
if !range.contains(&index) {
return false;
}

// 1. `0` 位置如果比 `1` 位置数小,则 `0` 位置为局部最小;
if index == 0 && array[0] < array[1] {
return true;
}

// 2. `N-1` 位置如果比 `N-2` 位置数小,则 `N-1` 位置为局部最小;
if index == len - 1 && array[len - 1] < array[len - 2] {
return true;
}

// 3. `i` 位置的数,如果比 `i-1` 和 `i+1` 位置的数逗笑,则 `i` 位置为局部最小。
array[index] < array[index - 1] && array[index] < array[index + 1]
}

// 判断一个数组是否符合要求
fn is_array_valid(array: &[i32]) -> bool {
if array.is_empty() {
return false;
}

let mut last = array[0];

for v in &array[1..] {
if last == *v {
return false;
}

last = *v;
}

true
}

/// 测试代码
#[test]
fn test_get_local_minimum_index() {
for _ in 0..1000 {
// 生成随机数组
let array = loop {
// Cargo.toml里加上rand = "0.8"
let array: [i32; 32] = rand::random();
// 确保数组符合条件
if !is_array_valid(&array) {
continue;
}
break array;
};

if let Some(index) = get_local_minimum_index(&array) {
assert!(is_local_minimum(&array, index));
} else {
assert!(false);
}
}
}
+
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2021/08/29/exclusive-or/index.html b/2021/08/29/exclusive-or/index.html new file mode 100644 index 0000000..f2bc890 --- /dev/null +++ b/2021/08/29/exclusive-or/index.html @@ -0,0 +1,510 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 异或运算 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 异或运算 +

+ + +
+ + + + +
+ + +

运算特性

    +
  1. 两个数进行异或运算,二进制位相同为 0,不同为 1
  2. +
  3. 满足交换律和结合律;
  4. +
  5. 异或运算可以认为是无进位相加(二进制);
  6. +
  7. 任何数与 0 异或仍得这个数,即0 ^ N = N
  8. +
  9. 任何数与自身异或得 0,即 N ^ N = N
  10. +
+
+

典型应用场景

1. 交换

1
2
3
4
5
6
int a = 1;
int b = 2;

a = a ^ b; // a = 1 ^ 2, b = 2
b = a ^ b; // a = 1 ^ 2, b = 1 ^ 2 ^ 2 = 1
a = a ^ b; // a = 1 ^ 2 ^ 1 = 2, b = 1
+

数组元素交换时,要确保交换的不是一个空间,可以相等,但不能是同一块内存跟自己进行异或运算:

+
    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
void swap(int[] array, int i, int j) {
if (i == j) {
// 无法确保 i != j 一定要加这个检查,否则该位置值变为 0
return;
}

array[i] = array[i] ^ array[j];
array[j] = array[i] ^ array[j];
array[i] = array[i] ^ array[j];
}
+

2. 找到出现奇数次的数

题目: 一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到这种数。

+
    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
int getOddTimesNumber(int[] array) {
int xor = 0;
for (int i: array) {
xor ^= i;
}
return xor;
}
+
    +
  • Rust 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
fn get_odd_times_number_0(array: &[i32]) -> i32 {
let mut xor = 0;
for i in array {
xor ^= *i;
}
xor
}

fn get_odd_times_number_1(array: &[i32]) -> i32 {
array.iter().fold(0, |a, b| a ^ *b)
}

#[test]
fn test_get_odd_times_number() {
use rand::prelude::*;

let mut rng = thread_rng();

for _ in 0..100 {
let mut vec = Vec::new();
let odd_number = rng.gen_range(0..5);
for i in 0..5 {
let times: usize = rng.gen_range(1..3) * 2 + if i == odd_number { 1 } else { 0 };
for _ in 0..times {
vec.push(i);
}
}
vec.shuffle(&mut rng);

let get0 = get_odd_times_number_0(&vec);
let get1 = get_odd_times_number_1(&vec);
assert_eq!(odd_number, get0);
assert_eq!(odd_number, get1);
}
}
+

3. 提取整型数最右侧的 1

题目: 提取整型数最右侧的 1

+

例如:

1
2
0b10101000 ----> 0b00001000
^---只留这个 `1`

+
    +
  • Java 实现
  • +
+
1
2
3
int getRightmostOne(int value) {
return value & (~value + 1);
}
+
    +
  • Rust 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
fn get_rightmost_one(value: i32) -> i32 {
value & (!value + 1)
}

#[test]
fn test_get_rightmost_one() {
for _ in 0..1000 {
let a: i32 = rand::random();
let b = get_rightmost_one(a);
assert_eq!(b >> b.trailing_zeros(), 1);
let bits = b.leading_zeros() + 1 + b.trailing_zeros();
assert_eq!(bits, size_of::<i32>() as u32 * 8);
}
}
+

4. 找到两个出现奇数次的数

题目: 一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到这两种数。

+
    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int[] getOddTimesNumbers(int[] array) {
int xor = 0;
for (int i: array) {
xor ^= i;
}

// xor == a ^ b
// 因为 a != b (两种数),所以 a ^ b != 0,则必然存在为 1 的二进制位
// 不妨就使用最后一个 1,即
int one = xor & (~xor + 1);
// 假设这个 1 在第 N 位
int a = 0;
for (int i: array) {
// 将数组分为两类:1. N 位上为 1 的;2. N 位上为 0 的。
// a 和 b 一定分别属于这两类,不会同属一类,因为 a ^ b 的 N 位是 1
// 这里只把第 1 类异或起来,得到一种数
if ((i & one) != 0) {
a ^= i;
}
}

// 用之前得到的这种数异或 xor,得到另一种
return new int[]{a, a ^ xor};
}
+
    +
  • Java 实现(函数式)
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int[] getOddTimesNumbers(int[] array) {
int xor = Arrays.stream(array).reduce(0, (a, b) -> a ^ b);

int one = xor & (~xor + 1);
int a = Arrays.stream(array).reduce(0, (v1, v2) -> {
if ((v2 & one) != 0) {
return v1 ^ v2;
} else {
return v1;
}
});

return new int[] {a, xor ^ a};
}
+
    +
  • Rust 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fn get_odd_times_numbers(array: &[i32]) -> (i32, i32) {
let mut xor = 0;
for i in array {
xor ^= *i;
}

let one = xor & (!xor + 1);
let mut a = 0;
for i in array {
if one & *i != 0 {
a ^= *i;
}
}

(a, xor ^ a)
}
+
    +
  • Rust 实现(函数式)
  • +
+
1
2
3
4
5
6
7
8
9
10
11
fn get_odd_times_numbers(array: &[i32]) -> (i32, i32) {
let xor = array.iter().fold(0, |a, b| a ^ *b);

let one = xor & (!xor + 1);
let a = array
.iter()
.fold(0, |a, b| if one & b != 0 { a ^ *b } else { a });

(a, xor ^ a)
}

+
    +
  • Rust 测试代码
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#[test]
fn test_get_odd_times_numbers() {
use rand::prelude::*;

let mut rng = thread_rng();

for _ in 0..1000 {
let mut vec = Vec::new();
let odd_number_a = rng.gen_range(0..5);
let odd_number_b = loop {
let b = rng.gen_range(0..5);
if b != odd_number_a {
break b;
} else {
continue;
}
};
for i in 0..5 {
let times: usize = rng.gen_range(1..3) * 2
+ if i == odd_number_a || i == odd_number_b {
1
} else {
0
};
for _ in 0..times {
vec.push(i);
}
}
vec.shuffle(&mut rng);

let get = get_odd_times_numbers(&vec);
assert!(get == (odd_number_a, odd_number_b) || get == (odd_number_b, odd_number_a));
}
}
+

5. 计算某个数为 1 的二进制位数

例如:

+
1
0b00101100 --> 3
+
    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
int countOnes(int a) {
int count = 0;

while (a != 0) {
count += 1;
int one = a & (~a + 1);
a ^= one; // 把这个 1 给去掉
}

return count;
}

+
    +
  • Rust 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fn count_ones(mut a: i32) -> u32 {
let mut count = 0;

while a != 0 {
count += 1;
// a ^= a & (!a + 1) 在 debug 编译条件下可能溢出,无法通过测试,release 无所谓;wrapping_add 在 debug 条件下也没问题
a ^= a & (!a).wrapping_add(1);
}

count
}


#[test]
fn test_count_ones() {
for _ in 0..1000 {
let a = rand::random();
assert_eq!(count_ones(a), a.count_ones());
}
}
+
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2021/09/06/linked-list-build-reverse/index.html b/2021/09/06/linked-list-build-reverse/index.html new file mode 100644 index 0000000..aa212fb --- /dev/null +++ b/2021/09/06/linked-list-build-reverse/index.html @@ -0,0 +1,452 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 链表的构造和反转 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 链表的构造和反转 +

+ + +
+ + + + +
+ + +

单向链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class Node<T> {
private final T value;
private Node<T> next;

public static void main(String[] args) {
Node<String> list = Node.build("hello", "world", "are", "you", "ok");
System.out.println("build: " + list);
Node<String> reversed = list.reverse();
System.out.println("reversed: " + reversed);
Node<String> origin = reversed.reverse();
System.out.println("origin: " + origin);
}

public static <T> Node<T> build(T ...values) {
Node<T> list = null;
Node<T> cur = null;

for (T value: values) {
if (cur == null) {
cur = new Node<>(value);
list = cur;
} else {
cur.setNext(new Node<>(value));
cur = cur.getNext();
}
}

return list;
}

public Node(T value) {
this.value = value;
this.next = null;
}

public Node setNext(Node<T> next) {
this.next = next;
return this;
}

public Node getNext() {
return this.next;
}

public T getValue() {
return this.value;
}

public Node<T> reverse() {
Node<T> head = this;
Node<T> pre = null;

while (head != null) {
Node<T> next = head.getNext();

head.setNext(pre);
pre = head;

head = next;
}

return pre;
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
Node<T> cur = this;
while (cur != null) {
sb.append(cur.getValue());
sb.append(" -> ");
cur = cur.getNext();
}
sb.append("null");
return sb.toString();
}
}
+

输出内容:

+
1
2
3
build: hello -> world -> are -> you -> ok -> null
reversed: ok -> you -> are -> world -> hello -> null
origin: hello -> world -> are -> you -> ok -> null
+

双向链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
public class DoubleNode<T> {
private final T value;
private DoubleNode<T> previous;
private DoubleNode<T> next;

public static void main(String[] args) {
DoubleNode<String> list = DoubleNode.build("hello", "world", "are", "you", "ok");
System.out.println("build: " + list);
DoubleNode<String> reversed = list.reverse();
System.out.println("reversed: " + reversed);
DoubleNode<String> origin = reversed.reverse();
System.out.println("origin: " + origin);
DoubleNode<String> tail = origin.getTailNode();
System.out.println("back: " + tail.toStringBack());
}

public static <T> DoubleNode<T> build(T ...values) {
DoubleNode<T> list = null;
DoubleNode<T> cur = null;

for (T value: values) {
if (cur == null) {
cur = new DoubleNode<>(value);
list = cur;
} else {
DoubleNode<T> node = new DoubleNode<>(value);
node.setPrevious(cur);
cur.setNext(node);
cur = cur.getNext();
}
}

return list;
}

public DoubleNode(T value) {
this.value = value;
this.previous = this.next = null;
}

public DoubleNode<T> setNext(DoubleNode<T> next) {
this.next = next;
return this;
}

public DoubleNode<T> getNext() {
return this.next;
}

public T getValue() {
return this.value;
}

public DoubleNode<T> getTailNode() {
DoubleNode<T> cur = this;
while (cur.getNext() != null) {
cur = cur.getNext();
}
return cur;
}

public DoubleNode<T> getPrevious() {
return this.previous;
}

public DoubleNode<T> setPrevious(DoubleNode<T> previous) {
this.previous = previous;
return this;
}

public DoubleNode<T> reverse() {
DoubleNode<T> head = this;
DoubleNode<T> pre = null;

while (head != null) {
DoubleNode<T> next = head.getNext();

// 其他都跟单链表一样,指针多设置一个
head.setNext(pre);
head.setPrevious(next);
pre = head;

head = next;
}

return pre;
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
DoubleNode<T> cur = this;
while (cur != null) {
sb.append(cur.getValue());
sb.append(" -> ");
cur = cur.getNext();
}
sb.append("null");
return sb.toString();
}

public String toStringBack() {
StringBuilder sb = new StringBuilder("null");
DoubleNode<T> cur = this;
while (cur != null) {
sb.append(" <- ");
sb.append(cur.getValue());
cur = cur.getPrevious();
}
return sb.toString();
}
}
+

输出内容:

+
1
2
3
4
build: hello -> world -> are -> you -> ok -> null
reversed: ok -> you -> are -> world -> hello -> null
origin: hello -> world -> are -> you -> ok -> null
back: null <- ok <- you <- are <- world <- hello
+
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2021/09/06/recursive-time-complexity-formula/index.html b/2021/09/06/recursive-time-complexity-formula/index.html new file mode 100644 index 0000000..4a0b018 --- /dev/null +++ b/2021/09/06/recursive-time-complexity-formula/index.html @@ -0,0 +1,532 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 递归算法时间复杂度公式 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 递归算法时间复杂度公式 +

+ + +
+ + + + +
+ + +

当递归函数的时间执行函数满足如下的关系式时,可以使用公式法计算时间复杂度:

+
+

其中:

+
    +
  1. $a$ 为递归次数;
  2. +
  3. $\frac {N} {b}$ 为子问题规模;
  4. +
  5. $O(N^d)$ 为每次递归完毕之后额外执行的操作的时间复杂度;
  6. +
+
+
    +
  • 如果 $\log_b a < d$,时间复杂度为 $O(N^d)$
  • +
  • 如果 $\log_b a > d$,时间复杂度为 $O(N^{\log_b a})$
  • +
  • 如果 $\log_b a = d$,时间复杂度为 $O(N^d \times \log N)$
  • +
+
+

例子

求数组 arr[l..r]中的最大值,用递归方法实现。

+
    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class RecursiveMax {
public static void main(String[] args) {
Integer[] array = {6, 3, 5, 2, 1, 4, 0, 1, 7};
System.out.println(getMax(array, 0, array.length - 1));
}

public static <T extends Comparable<T>> T getMax(T[] arr, int l, int r) {
if (l == r) {
return arr[l];
}

int mid = l + ((r - l) >> 1);
T left = getMax(arr, l, mid);
T right = getMax(arr, mid + 1, r);
return left.compareTo(right) >= 0 ? left : right;
}
}
+

其中:

+
    +
  1. 递归次数 $a$ 为 $2$;
  2. +
  3. 子问题规模 $\frac {N} {b}$ 为 $\frac {N} {2}$,即 $b$ 为 $2$;
  4. +
  5. 每次递归完毕之后额外执行的操作的时间复杂度 $O(N^d)$ 为 $O(1)$,即 $d$ 为 $0$。
  6. +
+

满足:

+

所以,该算法复杂度为 $O(N^{\log_2 2}) = O(N)$

+ +
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2021/09/06/stack-and-queue/index.html b/2021/09/06/stack-and-queue/index.html new file mode 100644 index 0000000..90b71a3 --- /dev/null +++ b/2021/09/06/stack-and-queue/index.html @@ -0,0 +1,474 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 栈和队列 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 栈和队列 +

+ + +
+ + + + +
+ + +

1. 环形队列

    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
public class RingBuffer<T> {
// 队列缓冲区
private final Object[] array;

// 队列尺寸
private final int limit;

// 表示即将入队的索引位置
private int pushIndex;

// 表示即将出队的索引位置
private int popIndex;

// 表示当前队列中元素的个数
private int size;

private RingBuffer(int limit) {
if (limit <= 0) {
throw new IllegalArgumentException("limit should be greater than 0");
}
this.limit = limit;
this.array = new Object[limit];
this.popIndex = this.pushIndex = this.size = 0;
}

public static <T> RingBuffer<T> create(int limit) {
return new RingBuffer<>(limit);
}

public Optional<T> pop() {
if (size == 0) {
return Optional.empty();
}

T value = (T) this.array[this.popIndex];
this.array[this.popIndex] = null; // 去掉引用,避免泄漏
this.size -= 1;
this.popIndex = getNextIndex(this.popIndex);
return Optional.of(value);
}

public boolean empty() {
return this.size == 0;
}

public void push(T value) {
if (size == this.limit) {
throw new IllegalArgumentException("The size has reached the limit");
}

this.array[this.pushIndex] = value;
this.size += 1;
this.pushIndex = getNextIndex(this.pushIndex);
}

@Override
public String toString() {
return "RingBuffer{" +
"array=" + Arrays.toString(array) +
", limit=" + limit +
", pushIndex=" + pushIndex +
", popIndex=" + popIndex +
", size=" + size +
'}';
}

private int getNextIndex(int index) {
return index == this.limit - 1 ? 0 : index + 1;
}

public static void main(String[] args) {
RingBuffer<String> rb = RingBuffer.create(4);
System.out.println("new rb: " + rb);
String[] data = {"hello", "world", "are", "you", "ok"};
for (String s: data) {
try {
rb.push(s);
System.out.println("push " + s);
System.out.println("rb: " + rb);
} catch (Exception e) {
System.out.println("Push '" + s + "' error: " + e);
}
}

Optional<String> op = rb.pop();

while (op.isPresent()) {
System.out.println("pop " + op.get());
System.out.println("rb: " + rb);
op = rb.pop();
}
}
}
+

输出内容:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
new rb: RingBuffer{array=[null, null, null, null], limit=4, pushIndex=0, popIndex=0, size=0}
push hello
rb: RingBuffer{array=[hello, null, null, null], limit=4, pushIndex=1, popIndex=0, size=1}
push world
rb: RingBuffer{array=[hello, world, null, null], limit=4, pushIndex=2, popIndex=0, size=2}
push are
rb: RingBuffer{array=[hello, world, are, null], limit=4, pushIndex=3, popIndex=0, size=3}
push you
rb: RingBuffer{array=[hello, world, are, you], limit=4, pushIndex=0, popIndex=0, size=4}
Push 'ok' error: java.lang.IllegalArgumentException: The size has reached the limit
pop hello
rb: RingBuffer{array=[null, world, are, you], limit=4, pushIndex=0, popIndex=1, size=3}
pop world
rb: RingBuffer{array=[null, null, are, you], limit=4, pushIndex=0, popIndex=2, size=2}
pop are
rb: RingBuffer{array=[null, null, null, you], limit=4, pushIndex=0, popIndex=3, size=1}
pop you
rb: RingBuffer{array=[null, null, null, null], limit=4, pushIndex=0, popIndex=0, size=0}
+

2. 时间复杂度为 $O(1)$ 的栈中最小值获取方法

    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
import java.util.Stack;

public class GetMinStack<E extends Comparable<E>> extends Stack<E> {
private final Stack<E> minStack;

public static void main(String[] args) {
GetMinStack<Integer> stack = new GetMinStack<>();
int[] data = {8, 4, 2, 6, 1, 9, -1, 3};
for (int i: data) {
stack.push(i);
Optional<Integer> min = stack.getMin();
System.out.println("push " + i + ", min: " + min.get() + ", stack" + stack + ", minStack: " + stack.getMinStack());
}

while (!stack.empty()) {
int i = stack.pop();
Optional<Integer> min = stack.getMin();
if (min.isPresent()) {
System.out.println("pop " + i + ", min: " + min.get() + ", stack" + stack + ", minStack: " + stack.getMinStack());
} else {
System.out.println("pop " + i + ", stack is empty");
}
}
}

public GetMinStack() {
minStack = new Stack<>();
}

@Override
public synchronized E pop() {
E item = super.pop();
E min = minStack.peek();
// 如果出栈的元素跟最小栈顶元素相等,则最小栈顶也出栈
if (min == item) {
minStack.pop();
}
return item;
}

@Override
public E push(E item) {
if (!minStack.empty()) {
E min = minStack.peek();
// 如果栈不空,看最小栈顶与入栈元素哪个小;
// 一样大或者入栈元素小,则该元素入最小栈;
// 否则不做任何操作
if (min.compareTo(item) >= 0) {
minStack.push(item);
}
} else {
// 栈空就直接入栈
minStack.push(item);
}
return super.push(item);
}

public Optional<E> getMin() {
if (empty()) {
return Optional.empty();
} else {
return Optional.of(minStack.peek());
}
}

public Collection<E> getMinStack() {
return Collections.unmodifiableCollection(this.minStack);
}
}
+

输出内容:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
push 8, min: 8, stack[8], minStack: [8]
push 4, min: 4, stack[8, 4], minStack: [8, 4]
push 2, min: 2, stack[8, 4, 2], minStack: [8, 4, 2]
push 6, min: 2, stack[8, 4, 2, 6], minStack: [8, 4, 2]
push 1, min: 1, stack[8, 4, 2, 6, 1], minStack: [8, 4, 2, 1]
push 9, min: 1, stack[8, 4, 2, 6, 1, 9], minStack: [8, 4, 2, 1]
push -1, min: -1, stack[8, 4, 2, 6, 1, 9, -1], minStack: [8, 4, 2, 1, -1]
push 3, min: -1, stack[8, 4, 2, 6, 1, 9, -1, 3], minStack: [8, 4, 2, 1, -1]
pop 3, min: -1, stack[8, 4, 2, 6, 1, 9, -1], minStack: [8, 4, 2, 1, -1]
pop -1, min: 1, stack[8, 4, 2, 6, 1, 9], minStack: [8, 4, 2, 1]
pop 9, min: 1, stack[8, 4, 2, 6, 1], minStack: [8, 4, 2, 1]
pop 1, min: 2, stack[8, 4, 2, 6], minStack: [8, 4, 2]
pop 6, min: 2, stack[8, 4, 2], minStack: [8, 4, 2]
pop 2, min: 4, stack[8, 4], minStack: [8, 4]
pop 4, min: 8, stack[8], minStack: [8]
pop 8, stack is empty
+

3. 用栈实现队列

    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import java.util.Optional;
import java.util.Stack;

public class StackQueue<E> {
// 用来入队
private final Stack<E> pushStack;
// 用来出队
private final Stack<E> popStack;

public static void main(String[] args) {
StackQueue<String> queue = new StackQueue<>();
String[] data = {"hello", "world", "how", "are", "you"};
for (String s: data) {
queue.push(s);
}
System.out.println(queue);
Optional<String> op = queue.poll();
while (op.isPresent()) {
System.out.println(op.get());
op = queue.poll();
}
System.out.println(queue);
for (String s: data) {
queue.push(s);
}
System.out.println(queue);
op = queue.poll();
for (String s: data) {
queue.push(s);
}
System.out.println(queue);
while (op.isPresent()) {
System.out.println(op.get());
op = queue.poll();
}
System.out.println(queue);
}

public StackQueue() {
pushStack = new Stack<>();
popStack = new Stack<>();
}

public int size() {
return pushStack.size() + popStack.size();
}

public boolean empty() {
return pushStack.empty() && popStack.empty();
}

public boolean contains(Object o) {
return pushStack.contains(o) || popStack.contains(o);
}

public void push(E element) {
pushStack.push(element);
}

public Optional<E> poll() {
if (popStack.empty()) {
while (!pushStack.empty()) {
popStack.push(pushStack.pop());
}
if (popStack.empty()) {
return Optional.empty();
}
}

return Optional.of(popStack.pop());
}

@Override
public String toString() {
// => 表示栈顶
return "StackQueue{" +
"pushStack=" + pushStack +
"=>, popStack=" + popStack +
"=>}";
}
}
+

输出内容:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
StackQueue{pushStack=[hello, world, how, are, you]=>, popStack=[]=>}
hello
world
how
are
you
StackQueue{pushStack=[]=>, popStack=[]=>}
StackQueue{pushStack=[hello, world, how, are, you]=>, popStack=[]=>}
StackQueue{pushStack=[hello, world, how, are, you]=>, popStack=[you, are, how, world]=>}
hello
world
how
are
you
hello
world
how
are
you
StackQueue{pushStack=[]=>, popStack=[]=>}
+

4. 用队列实现栈

    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import java.util.LinkedList;
import java.util.Optional;
import java.util.Queue;

public class QueueStack<E> {
private Queue<E> data;
private Queue<E> help;

public static void main(String[] args) {
QueueStack<String> stack = new QueueStack<>();
String[] data = {"hello", "world", "how", "are", "you"};
for (String s: data) {
stack.push(s);
}
Optional<String> op = stack.pop();
while (op.isPresent()) {
System.out.println(op.get());
op = stack.pop();
}
}

public QueueStack() {
data = new LinkedList<>();
help = new LinkedList<>();
}

public boolean empty() {
return data.isEmpty() && help.isEmpty();
}

public boolean contains(E object) {
return data.contains(object) || help.contains(object);
}

public int size() {
return data.size() + help.size();
}

public void push(E e) {
data.add(e);
}

public Optional<E> pop() {
int size = data.size();
if (size == 0) {
return Optional.empty();
}

for (int i=0;i<size-1;++i) {
help.add(data.poll());
}

E e = data.poll();

// swap
Queue<E> temp = help;
help = data;
data = temp;

return Optional.of(e);
}
}
+

输出内容:

+
1
2
3
4
5
you
are
how
world
hello
+
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2021/09/11/merge-sort/index.html b/2021/09/11/merge-sort/index.html new file mode 100644 index 0000000..530fe1a --- /dev/null +++ b/2021/09/11/merge-sort/index.html @@ -0,0 +1,810 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归并排序及应用 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 归并排序及应用 +

+ + +
+ + + + +
+ + +

归并排序

    +
  • 算法时间复杂度: $O(NlogN)$
  • +
  • 相比冒泡、选择等时间复杂度为 $O(N^2)$ 的排序算法,没有浪费比较行为;
  • +
  • 插入排序时即便使用二分查找插入位置,也需要将插入位置后的元素依次向右移动,每次插入复杂度不是 $O(1)$。
  • +
+

递归方法

    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class MergeSort {

public static void main(String[] args) {
Integer[] data = {12, 3, 2, 6, 4, 8, 6, 0, 9, 3};
System.out.println(Arrays.toString(data));
mergeSort(data);
System.out.println(Arrays.toString(data));
}

static <T extends Comparable> void mergeSort(T[] array) {
T[] help = (T[]) new Comparable[array.length];
doMergeSort(array, help, 0, array.length);
}

static <T extends Comparable> void doMergeSort(T[] array, T[] help, int start, int end) {
if (start == end - 1) {
return;
}

int mid = start + ((end - start) >> 1);
doMergeSort(array, help, start, mid);
doMergeSort(array, help, mid, end);
mergeSorted(array, help, start, mid, end);
}

static <T extends Comparable> void mergeSorted(T[] array, T[] help, int start, int mid, int end) {
int h = start;
int i = start;
int j = mid;

while (i < mid && j < end) {
if (array[i].compareTo(array[j]) <= 0) {
help[h++] = array[i++];
} else {
help[h++] = array[j++];
}
}

while (i < mid) {
help[h++] = array[i++];
}

while (j < end) {
help[h++] = array[j++];
}

assert h == end;
for (int n = start; n < end; n++) {
array[n] = help[n];
}
}
}
+

输出内容:

+
1
2
[12, 3, 2, 6, 4, 8, 6, 0, 9, 3]
[0, 2, 3, 3, 4, 6, 6, 8, 9, 12]
+
    +
  • Rust 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#[test]
fn test_merge_sort() {
for _ in 0..100 {
let mut array0: [u8; 32] = rand::random();
let mut array1 = array0.clone();
array0.merge_sort();
array1.sort();
assert_eq!(array0, array1);
}
}

trait MergeSort {
fn merge_sort(&mut self);
}

impl<T: Ord + Clone> MergeSort for [T] {
fn merge_sort(&mut self) {
let len = self.len();
if len == 0 || len == 1 {
return;
}

let mid = len >> 1;
self[..mid].merge_sort();
self[mid..].merge_sort();

let mut i = 0;
let mut j = mid;
let mut vec = Vec::with_capacity(len);

while i < mid && j < len {
if self[i] <= self[j] {
vec.push(self[i].clone());
i += 1;
} else {
vec.push(self[j].clone());
j += 1;
}
}

while i < mid {
vec.push(self[i].clone());
i += 1;
}

while j < len {
vec.push(self[j].clone());
j += 1;
}

self.clone_from_slice(&vec);
}
}
+

非递归方法

    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static <T extends Comparable<T>> void mergeSortLoop(T[] array) {
if (array == null || array.length < 2) {
return;
}

Comparable[] help = (T[]) new Comparable[array.length];

int mergeSize = 1;

while (mergeSize < array.length) {
int left = 0;

while (left < array.length) {
int mid = left + mergeSize;

if (mid >= array.length) {
break;
}

int right = Math.min(mid + mergeSize, array.length);
mergeSorted(array, help, left, mid, right);
left = right;
}


if (mergeSize >= array.length >> 1) {
break;
}

mergeSize <<= 1;
}
}
+
    +
  • Rust 实现
  • +
+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#[test]
fn test_loop_merge_sort() {
for _ in 0..100 {
let mut array0: [u8; 32] = rand::random();
let mut array1 = array0.clone();
array0.loop_merge_sort();
array1.sort();
assert_eq!(array0, array1);
}
}

impl<T: Ord + Clone> MergeSort for [T] {
fn merge_sort(&mut self) {
//...
}

fn loop_merge_sort(&mut self) {
let len = self.len();
if len < 2 {
return;
}

let mut merge_size: usize = 1;
while merge_size < len {
let mut left = 0;

while left < len {
let mid = left + merge_size;
if mid >= len {
break;
}

let right = (mid + merge_size).min(len);

// merge sorted
let mut vec = Vec::with_capacity(right - left);

let mut i = left;
let mut j = mid;
while i < mid && j < right {
if self[i] <= self[j] {
vec.push(self[i].clone());
i += 1;
} else {
vec.push(self[j].clone());
j += 1;
}
}

while i < mid {
vec.push(self[i].clone());
i += 1;
}

while j < right {
vec.push(self[j].clone());
j += 1;
}

(&mut self[left..right]).clone_from_slice(&vec);

left = right;
}

if merge_size > len >> 1 {
break;
}

merge_size <<= 1;
}
}
}

求数组的小和

在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和加起来叫数组小和。求数组小和。

+
+

例子:[1, 3, 4, 2, 5]

+

1 左边比自己小的数:没有
3 左边比自己小的数:1
4 左边比自己小的数:1 3
2 左边比自己小的数:1
5 左边比自己小的数:1 3 4 2

+

所以,数组的小和为 $1 + 1 + 3 + 1 + 1 + 3 + 4 + 2 = 16$

+
+
    +
  • Java 实现
  • +
+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class SmallSum {
public static void main(String[] args) {
System.out.println(smallSum(new int[]{1, 3, 4, 2, 5}) + " == " + getSmallSumSimple(new int[]{1, 3, 4, 2, 5}));
}

static int getSmallSumSimple(int[] array) {
int smallSum = 0;
for (int i = 0; i < array.length; ++i) {
for (int j = 0; j < i; ++j) {
if (array[j] < array[i]) {
smallSum += array[j];
}
}
}
return smallSum;
}

static int smallSum(int[] array) {
int[] help = new int[array.length];
return doMergeSmallSum(array, help, 0, help.length);
}

static int doMergeSmallSum(int[] array, int[] help, int start, int end) {
if (end - start < 2) {
return 0;
}

int mid = start + ((end - start) >> 1);
int left = doMergeSmallSum(array, help, start, mid);
int right = doMergeSmallSum(array, help, mid, end);
return left + right + doMerge(array, help, start, mid, end);
}

static int doMerge(int[] array, int[] help, int start, int mid, int end) {
int i = start;
int j = mid;
int k = start;
int sum = 0;

while (i < mid && j < end) {
if (array[i] < array[j]) {
int t = array[i++];
help[k++] = t;
sum += t * (end - j);
} else {
help[k++] = array[j++];
}
}

while (i < mid) {
help[k++] = array[i++];
}

while (j < end) {
help[k++] = array[j++];
}

for (k = start; k < end; k++) {
array[k] = help[k];
}

return sum;
}
}

求数组中的降序对

+

例如:[3, 1, 7, 0, 2] 中的降序对有 (3, 1)(3, 0)(3, 2)(1, 0)(7, 0)(7, 2)

+
+

即求数组中每个数右边有多少个数比它小,就有多少个降序对。

+
    +
  • Rust 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#[test]
fn test_get_descending_pairs() {
for _ in 0..100 {
let mut array0: [u8; 32] = rand::random();
let mut array1 = array0.clone();
let set0 = array0.get_descending_pairs();
let set1 = array1.get_descending_pairs_simple();
assert_eq!(set0, set1);
}
}

trait DescendingPair<T> {
fn get_descending_pairs(&mut self) -> HashSet<(T, T)>;
fn get_descending_pairs_simple(&mut self) -> HashSet<(T, T)>;
}

impl<T: Ord + Clone + Hash> DescendingPair<T> for [T] {
fn get_descending_pairs(&mut self) -> HashSet<(T, T)> {
let mut set = HashSet::new();
if self.len() < 2 {
return set;
}

let mid = self.len() >> 1;
let mut left = self[..mid].get_descending_pairs();
let mut right = self[mid..].get_descending_pairs();
set.extend(left.drain());
set.extend(right.drain());

let mut help = Vec::with_capacity(self.len());
let mut j = 0;
let mut k = mid;
let len = self.len();

while j < mid && k < len {
if self[j] > self[k] {
let mut temp = self[k..]
.iter()
.map(|v| (self[j].clone(), v.clone()))
.collect::<HashSet<_>>();
set.extend(temp.drain());

help.push(self[j].clone());
j += 1;
} else {
help.push(self[k].clone());
k += 1;
}
}

help.extend_from_slice(&self[j..mid]);
help.extend_from_slice(&self[k..]);

assert_eq!(help.len(), self.len());
self.clone_from_slice(&help);

set
}

fn get_descending_pairs_simple(&mut self) -> HashSet<(T, T)> {
let mut set = HashSet::new();
for i in 0..self.len() {
for j in 0..i {
if self[j] > self[i] {
set.insert((self[j].clone(), self[i].clone()));
}
}
}
set
}
}
+
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2021/09/13/quick-sort/index.html b/2021/09/13/quick-sort/index.html new file mode 100644 index 0000000..61e3adc --- /dev/null +++ b/2021/09/13/quick-sort/index.html @@ -0,0 +1,668 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 快速排序 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 快速排序 +

+ + +
+ + + + +
+ + +

分区问题

二分

将一个数组分为两个区域,小于等于N在左,大于N的在右;返回右侧的起始位置。

+
    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static int partition(int[] array, int value, int start, int end) {
if (array == null || start >= end) {
return -1;
}

int left = -1;

for (int i = start; i < end; i++) {
if (array[i] <= value) {
if (left == -1) {
left = start;
} else {
left += 1;
}
swap(array, i, left);
}
}

return left + 1;
}

static int partition(int[] array, int value) {
return partition(array, value, 0, array.length);
}
+

输出内容:

+
1
2
3
[2, 3, 5, 1, 2, 6, 4, 3, 8, 4, 3, 5, 1, 3, 5, 8]
10
[2, 3, 1, 2, 4, 3, 4, 3, 1, 3, 6, 5, 8, 5, 5, 8]
+
    +
  • Rust 实现
  • +
+

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#[test]
fn test_partition() {
let mut array = [4, 2, 5, 7, 5, 4, 2, 9, 3, 5, 1];
assert_eq!(array.partition(5), Index::Position(9));
assert_eq!(array.partition(3), Index::Position(4));
assert_eq!(array.partition(11), Index::Greater);
assert_eq!(array.partition(0), Index::Less);
}

#[derive(Debug, Eq, PartialEq)]
pub enum Index {
// 值比所有数组元素都小
Less,
// 能找到一个分区位置
Position(usize),
// 值比所有数组元素都大
Greater,
}

pub trait Partition<T> {
fn partition(&mut self, value: T) -> Index;
}

impl<T: Ord> Partition<T> for [T] {
// 返回大于部分的起始索引
fn partition(&mut self, value: T) -> Index {
let mut left = None;
for i in 0..self.len() {
if self[i] <= value {
if let Some(left_val) = left {
let new_left_val = left_val + 1;
self.swap(i, new_left_val);
left = Some(new_left_val);
} else {
self.swap(i, 0);
left = Some(0);
}
}
}

match left {
None => Index::Less,
Some(i) if i == self.len() - 1 => Index::Greater,
Some(i) => Index::Position(i + 1),
}
}
}

荷兰 🇳🇱 国旗问题(三分)

将一个数组分为三个区域,小于N在左,等于N在中间,大于N的在右;返回中间和右侧的起始位置。

+
    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
static void swap(int[] array, int i, int j) {
if (i == j) {
return;
}

array[i] = array[i] ^ array[j];
array[j] = array[i] ^ array[j];
array[i] = array[i] ^ array[j];
}

static int[] netherlandsFlagPartition(int[] array, int value) {
return netherlandsFlagPartition(array, value, 0, array.length);
}

static int[] netherlandsFlagPartition(int[] array, int value, int start, int end) {
if (array == null || start >= end) {
return null;
}

int left = -1;
int right = end;
int i = start;

while (i < right) {
if (array[i] < value) {
left = left == -1 ? start : (left + 1);
swap(array, left, i);
i += 1;
} else if (array[i] > value) {
right -= 1;
swap(array, right, i);
} else { // array[i] == value
i += 1;
}
}

return new int[]{left + 1, right};
}
+

输出内容:

+
1
2
[10, 13]
[2, 3, 1, 2, 4, 3, 4, 3, 1, 3, 5, 5, 5, 8, 6, 8]
+
    +
  • Rust 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
use std::cmp::Ordering;

#[test]
fn test_netherlands_flag_partition() {
let mut array = [7, 1, 2, 0, 8, 5, 3, 9, 2, 6, 5, 1, 0, 8, 7, 4];
// sorted: [0, 0, 1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 7, 8, 8, 9]
// index(hex): [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F]
assert_eq!(array.netherlands_flag_partition(2), (Some(4), Some(6)));
assert_eq!(array.netherlands_flag_partition(7), (Some(0xB), Some(0xD)));
assert_eq!(array.netherlands_flag_partition(-1), (None, Some(0)));
assert_eq!(array.netherlands_flag_partition(0), (None, Some(2)));
assert_eq!(
array.netherlands_flag_partition(10),
(Some(array.len()), None)
);
assert_eq!(
array.netherlands_flag_partition(9),
(Some(array.len() - 1), None)
);
}

pub trait NetherlandsFlagPartition<T> {
fn netherlands_flag_partition(&mut self, value: T) -> (Option<usize>, Option<usize>);
}

impl<T: Ord> NetherlandsFlagPartition<T> for [T] {
// 返回相等部分和大于部分的起始索引
fn netherlands_flag_partition(&mut self, value: T) -> (Option<usize>, Option<usize>) {
let len = self.len();
let mut left = None;
let mut right = None;
let mut i = 0;
while i < right.unwrap_or(len) {
match self[i].cmp(&value) {
Ordering::Less => {
match left {
None => {
self.swap(0, i);
left = Some(0);
}
Some(left_value) => {
let new_left = left_value + 1;
self.swap(new_left, i);
left = Some(new_left);
}
}
i += 1;
}
Ordering::Equal => {
i += 1;
}
Ordering::Greater => {
match right {
None => {
self.swap(len - 1, i);
right = Some(len - 1);
}
Some(right_value) => {
let new_right = right_value - 1;
self.swap(new_right, i);
right = Some(new_right);
}
}

// i 不要自增,让下一次循环检查新换到前面的值
}
}
}

(left.map(|v| v + 1), right)
}
}
+
    +
  • Rust 实现(用枚举表示结果)
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use std::cmp::Ordering;

#[test]
fn test_netherlands_flag_partition() {
let mut array = [7, 1, 2, 0, 8, 5, 3, 9, 2, 6, 5, 1, 0, 8, 7, 4];
assert_eq!(
array.netherlands_flag_partition(2),
NetherlandsFlagResult::Three(4, 6)
);
assert_eq!(
array.netherlands_flag_partition(7),
NetherlandsFlagResult::Three(11, 13)
);
assert_eq!(
array.netherlands_flag_partition(-1),
NetherlandsFlagResult::Greater
);
assert_eq!(
array.netherlands_flag_partition(0),
NetherlandsFlagResult::ValueStart(2)
);
assert_eq!(
array.netherlands_flag_partition(10),
NetherlandsFlagResult::Less
);
assert_eq!(
array.netherlands_flag_partition(9),
NetherlandsFlagResult::ValueEnd(15)
);

let mut array = [2, 2, 2, 2, 2];
assert_eq!(
array.netherlands_flag_partition(2),
NetherlandsFlagResult::Equal
);
}

#[derive(Debug, Eq, PartialEq)]
pub enum NetherlandsFlagResult {
/// 分为三部分,分别是 < value, == value, > value, 返回后两部分的起始索引
Three(usize, usize),
/// 分为两部分,== value, > value, 返回第二部分的起始索引
ValueStart(usize),
/// 分为两部分,< value, == value, 返回第二部分的起始索引
ValueEnd(usize),
/// 所有值都小于 value
Less,
/// 所有值都大于 value
Greater,
/// 所有值都等于 value
Equal,
}

pub trait NetherlandsFlagPartition<T> {
fn netherlands_flag_partition(&mut self, value: T) -> NetherlandsFlagResult;
}

impl<T: Ord> NetherlandsFlagPartition<T> for [T] {
// 返回相等部分和大于部分的起始索引
fn netherlands_flag_partition(&mut self, value: T) -> NetherlandsFlagResult {
let len = self.len();
let mut left = None;
let mut right = None;
let mut i = 0;
while i < right.unwrap_or(len) {
match self[i].cmp(&value) {
Ordering::Less => {
match left {
None => {
self.swap(0, i);
left = Some(0);
}
Some(left_value) => {
let new_left = left_value + 1;
self.swap(new_left, i);
left = Some(new_left);
}
}
i += 1;
}
Ordering::Equal => {
i += 1;
}
Ordering::Greater => {
match right {
None => {
self.swap(len - 1, i);
right = Some(len - 1);
}
Some(right_value) => {
let new_right = right_value - 1;
self.swap(new_right, i);
right = Some(new_right);
}
}

// i 不要自增,让下一次循环检查新换到前面的值
}
}
}

match (left.map(|v| v + 1), right) {
(None, Some(i)) => {
if i == 0 {
NetherlandsFlagResult::Greater
} else {
NetherlandsFlagResult::ValueStart(i)
}
}
(Some(i), None) => {
if i >= self.len() {
NetherlandsFlagResult::Less
} else {
NetherlandsFlagResult::ValueEnd(i)
}
}
(Some(i), Some(j)) => {
if i >= self.len() {
NetherlandsFlagResult::Greater
} else {
NetherlandsFlagResult::Three(i, j)
}
}
(None, None) => NetherlandsFlagResult::Equal,
}
}
}
+

快速排序

v1.0

步骤:

+
    +
  1. 选择数组最后一个元素 X,在 0..array.length-1 的范围上进行二分,小于等于 X 的在左,大于 X 的在右;
  2. +
  3. X 与右侧的第一个元素交换;
  4. +
  5. X 左侧与右侧的数组分别进行上述操作,进行递归,过程中 X 不需要再移动。
  6. +
+

时间复杂度:$O(N)$

+
    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void quickSort1(int[] array) {
recurse1(array, 0, array.length);
}

static void recurse1(int[] array, int start, int end) {
if (array == null || start >= end - 1) {
return;
}

int pivot = array[end - 1];
int idx = partition(array, pivot, start, end - 1);
swap(array, idx, end - 1);
recurse1(array, start, idx);
recurse1(array, idx + 1, end);
}
+
    +
  • Rust 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#[test]
fn test_quick_sort1() {
for _ in 0..1000 {
let mut array0: [u8; 32] = rand::random();
let mut array1 = array0.clone();

array0.sort();
array1.quick_sort1();

assert_eq!(array0, array1);
}
}

pub trait QuickSort1 {
fn quick_sort1(&mut self);
}

impl<T: Ord + Clone> QuickSort1 for [T] {
fn quick_sort1(&mut self) {
let len = self.len();
if len < 2 {
return;
}

let value = self[len - 1].clone();

match self[..len - 1].partition(value) {
// value 比所有元素都小,把 value 挪到第一个位置,排序剩下的
Index::Less => {
self.swap(0, len - 1);
self[1..].quick_sort1();
}
// 把 value 与第一个大于区的数交换
Index::Position(i) => {
self.swap(i, len - 1);
self[..i].quick_sort1();
self[i + 1..].quick_sort1();
}
// value比所有元素都大,不动,排序前面所有的
Index::Greater => {
self[..len - 1].quick_sort1();
}
}
}
}
+

v2.0

    +
  1. 选择数组一个元素 X,进行荷兰国旗三分,小于 X 的在左,等于 X 的在中间,大于 X 的在右;
  2. +
  3. X 左侧与右侧的数组分别进行上述操作,进行递归,过程中位于中间的所有 X 不需要再移动。
  4. +
+

时间复杂度:$O(N)$

+
    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void quickSort2(int[] array) {
recurse2(array, 0, array.length);
}

static void recurse2(int[] array, int start, int end) {
if (array == null || start >= end - 1) {
return;
}

int pivot = array[end - 1];
int[] idxs = netherlandsFlagPartition(array, pivot, start, end);
if (idxs == null) {
return;
}
recurse2(array, start, idxs[0]);

if (idxs[1] < end - 1) {
recurse2(array, idxs[1], end);
}
}
+
    +
  • Rust 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#[test]
fn test_quick_sort2() {
for _ in 0..1000 {
let mut array0: [i32; 32] = random();
let mut array1 = array0.clone();

array0.sort();
array1.quick_sort2();

assert_eq!(array0, array1);
}
}

pub trait QuickSort2 {
fn quick_sort2(&mut self);
}

impl<T: Ord + Clone> QuickSort2 for [T] {
fn quick_sort2(&mut self) {
if self.len() < 2 {
return;
}

let value = self[0].clone();

match self.netherlands_flag_partition(value) {
NetherlandsFlagResult::Three(start, end) => {
self[..start].quick_sort2();
self[end..].quick_sort2();
}
NetherlandsFlagResult::ValueStart(start) => {
self[start..].quick_sort2();
}
NetherlandsFlagResult::ValueEnd(end) => {
self[..end].quick_sort2();
}
NetherlandsFlagResult::Less | NetherlandsFlagResult::Greater => {
self.quick_sort2();
}
NetherlandsFlagResult::Equal => {
return;
}
}
}
}
+

v3.0

将 v2.0 中选择数组元素改为随机选择,其他不变。

+
+
    +
  1. 划分值越靠近中间,性能越好;越靠近两边性能越差;
  2. +
  3. 随机算一个数进行划分的目的就是让好情况和坏情况都变成概率事件;
  4. +
  5. 把每一种情况都列出来,会有每种情况下的复杂度,但概率都是 $1/N$;
  6. +
  7. 所有情况都考虑,则时间复杂度就是这种概率模型下的长期期望。
  8. +
+
+

时间复杂度:$O(N \times logN)$
额外空间复杂度:$O(N \times logN)$

+
    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void quickSort3(int[] array) {
recurse3(array, 0, array.length);
}

static void recurse3(int[] array, int start, int end) {
if (array == null || start >= end - 1) {
return;
}

int pi = (int) (Math.random() * (end - start));
System.out.println(start + pi);
int pivot = array[start + pi];
int[] idxs = netherlandsFlagPartition(array, pivot, start, end);
if (idxs == null) {
return;
}
recurse3(array, start, idxs[0]);

if (idxs[1] < end - 1) {
recurse3(array, idxs[1], end);
}
}
+
    +
  • Rust 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#[test]
fn test_quick_sort3() {
for _ in 0..1000 {
let mut array0: [i32; 32] = random();
let mut array1 = array0.clone();

array0.sort();
array1.quick_sort3();

assert_eq!(array0, array1);
}
}

pub trait QuickSort3 {
fn quick_sort3(&mut self);
}

impl<T: Ord + Clone> QuickSort for [T] {
fn quick_sort3(&mut self) {
if self.len() < 2 {
return;
}

let index = rand::thread_rng().gen_range(0..self.len());
let value = self[index].clone();

match self.netherlands_flag_partition(value) {
NetherlandsFlagResult::Three(start, end) => {
self[..start].quick_sort3();
self[end..].quick_sort3();
}
NetherlandsFlagResult::ValueStart(start) => {
self[start..].quick_sort3();
}
NetherlandsFlagResult::ValueEnd(end) => {
self[..end].quick_sort3();
}
NetherlandsFlagResult::Less | NetherlandsFlagResult::Greater => {
self.quick_sort3();
}
NetherlandsFlagResult::Equal => {
return;
}
}
}
}
+

Swap 函数

    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
static void swap(int[] array, int i, int j) {
if (i == j) {
return;
}

array[i] = array[i] ^ array[j];
array[j] = array[i] ^ array[j];
array[i] = array[i] ^ array[j];
}
+
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2021/09/14/design-pattern-3/index.html b/2021/09/14/design-pattern-3/index.html new file mode 100644 index 0000000..8fa8662 --- /dev/null +++ b/2021/09/14/design-pattern-3/index.html @@ -0,0 +1,462 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 设计模式(三) | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 设计模式(三) +

+ + +
+ + + + +
+ + +

装饰器(Decorator)

+

Component:定义一个对象接口,可以给这些对象动态地添加职责。
ConcreteComponent:定义对象,可以给这个对象添加一些职责。
Decorator:维持一个指向 Component 对象的指针,并定义一个与 Component 接口一致的接口。
ConcreteDecorator:实际的装饰对象,向组件添加职责。

+
+
    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
public class DecoratorPattern {

public static void main(String[] args) {
Component component0 = new ADecorator(new BDecorator(new Component0()));
component0.doSth();
System.out.println();
Component component1 = new BDecorator(new ADecorator(new BDecorator(new ADecorator(new Component1()))));
component1.doSth();
System.out.println();
}

public interface Component {
void doSth();
}

public static class Component0 implements Component {

@Override
public void doSth() {
System.out.print("0");
}
}

public static class Component1 implements Component {

@Override
public void doSth() {
System.out.print("1");
}
}

public static abstract class Decorator implements Component {
protected Component component;
public Decorator(Component component) {
this.component = component;
}
}

public static class ADecorator extends Decorator {

public ADecorator(Component component) {
super(component);
}

@Override
public void doSth() {
component.doSth();
System.out.print("A");
}
}

public static class BDecorator extends Decorator {

public BDecorator(Component component) {
super(component);
}

@Override
public void doSth() {
component.doSth();
System.out.print("B");
}
}
}
+
    +
  • Rust 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
pub trait Component {
fn do_sth(&self);
}

pub struct Component1;

impl Component for Component1 {
fn do_sth(&self) {
print!("1");
}
}

pub struct Component2;

impl Component for Component2 {
fn do_sth(&self) {
print!("2");
}
}

pub trait Decorator<T: Component>: Component {
fn wrap(inner: T) -> Self;
}

struct DecoratorA<T> {
inner: T,
}

impl<T: Component> Component for DecoratorA<T> {
fn do_sth(&self) {
self.inner.do_sth();
print!("A");
}
}

impl<T: Component> Decorator<T> for DecoratorA<T> {
fn wrap(inner: T) -> Self {
Self { inner }
}
}

struct DecoratorB<T> {
inner: T,
}

impl<T: Component> Component for DecoratorB<T> {
fn do_sth(&self) {
self.inner.do_sth();
print!("B");
}
}

impl<T: Component> Decorator<T> for DecoratorB<T> {
fn wrap(inner: T) -> Self {
Self { inner }
}
}

#[test]
fn test_decorator() {
let c1 = Component1;
c1.do_sth();
println!();
let ab1 = DecoratorA::wrap(DecoratorB::wrap(Component1));
ab1.do_sth();
println!();
let abbaa2 = DecoratorA::wrap(DecoratorB::wrap(DecoratorB::wrap(DecoratorA::wrap(
DecoratorA::wrap(Component2),
))));
abbaa2.do_sth();
println!();

// 在Rust中,如果不需要运行时动态装饰,就没有必要产生很多小对象
// 装饰了好几轮,最后占用内存还是 Component2 的大小
assert_eq!(
std::mem::size_of::<
DecoratorA<DecoratorB<DecoratorB<DecoratorA<DecoratorA<Component2>>>>>,
>(),
0
);
}
+

输出内容:

+
1
2
3
1
1BA
2AABBA
+
+

优点:比继承更灵活,避免类爆炸;可以组合;装饰器和构件可以独立变化,符合开闭原则。
缺点:产生很多小对象,增加系统复杂度和理解难度,调试困难。

+
+
+ +
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2021/09/14/sorting-algorithm-in-rust-std/index.html b/2021/09/14/sorting-algorithm-in-rust-std/index.html new file mode 100644 index 0000000..56cd642 --- /dev/null +++ b/2021/09/14/sorting-algorithm-in-rust-std/index.html @@ -0,0 +1,451 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rust 标准库中的排序算法[WIP] | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + + + + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2021/09/23/redis-basics/index.html b/2021/09/23/redis-basics/index.html new file mode 100644 index 0000000..5212fc1 --- /dev/null +++ b/2021/09/23/redis-basics/index.html @@ -0,0 +1,721 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Redis 学习笔记(基础)[WIP] | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ Redis 学习笔记(基础)[WIP] +

+ + +
+ + + + +
+ + +

1. 基本架构

一个键值数据库需要有哪些功能/结构/特性。

+
    +
  • 支持多种数据结构
  • +
  • PUT、GET、DELETE、SCAN
  • +
  • 设置过期
  • +
  • 索引结构
  • +
  • 对外接口,Socket、动态链接库等等
  • +
  • 存储方式,内存、硬盘(持久化)
  • +
  • 高可用
  • +
  • 横向扩展
  • +
  • 功能可扩展性
  • +
+
+

2. 数据结构

Redis 接收到一个键值对操作后,能以 微秒 级别的速度找到数据,并快速完成操作。

+

Redis 的值有如下数据类型:

+
    +
  • String(字符串)
  • +
  • List(列表)
  • +
  • Hash(哈希)
  • +
  • Set(集合)
  • +
  • Sorted Set(有序集合)
  • +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
数据类型数据结构
String简单动态字符串
List双向链表/压缩列表
Hash压缩列表/哈希表
Set整数数组/哈希表
Sorted Set压缩列表/跳表
+
+

Redis 使用一个全局的哈希表来保存所有键值对,可以在 $O(1)$ 的时间内查找到键值对。

+

一个哈希表就是一个数组,数组元素为哈希桶,每个哈希桶存放一个链表,连接着哈希值映射到该桶中的多个键值对。

+
1
2
3
4
5
|0|1|2|3|4|5|
| |
| ---> entry4
|
---> entry1 ---> entry2 ---> entry3
+

哈希冲突及rehash

当哈希冲突过多时,链表过长,导致查找效率下降,Redis会对哈希表进行rehash。

+

为了 使 rehash 更高效,Redis 使用了两个全局哈希表:哈希表 1 和 哈希表 2 。默认情况下使用哈希表 1 ,哈希表 2 没有分配空间,当键值对增多时,Redis开始进行rehash:

+
    +
  1. 给哈希表 2 分配更大的空间,例如是当前哈希表 1 大小的两倍;
  2. +
  3. 把哈希表 1 中的数据重新映射并拷贝到哈希表 2 中;
  4. +
  5. 释放哈希表 1 的空间。
  6. +
+

然后使用哈希表 2 保存数据,下次rehash再使用哈希表 1 。

+

避免一次性拷贝大量数据造成 Redis 线程阻塞,Redis采用了渐进式rehash。

+

渐进式rehash

Redis可以保持处理用户请求,每处理一个请求,就顺带将哈希表 1 中的一个桶的所有entries拷贝到哈希表 2 中。这样就能把一次性大量拷贝的开销,分摊到了多次请求中,避免了耗时可能造成的阻塞。

+

数据结构的操作效率

整数数组双向链表 都是顺序读写,操作复杂度都是 $O(N)$。

+

压缩列表

压缩列表表头有 列表长度(zlbytes)列表尾偏移量(zltail)列表中entry个数(zllen) 三个字段,表尾有一个 zlend 字段表示 列表结束

+
1
| zlbytes | zltail | zllen | entry1 | entry2 | ... | entryN | zlend |
+

查找表头、表尾元素的复杂度为 $O(1)$,查找其他元素的复杂度为 $O(N)$。

+

跳表

跳表 是在 有序链表 的基础上增加了多级索引,通过索引位置的几次跳转,实现数据快速定位。

+
1
2
3
4
5
1 ----------------------> 27 -----------------------> 100 // 二级索引
↓ ↓ ↓
1 --------> 11 ---------> 27 ---------> 50 ---------> 100 // 一级索引
↓ ↓ ↓ ↓ ↓
1 --> 5 --> 11 --> 20 --> 27 --> 33 --> 50 --> 62 --> 100 --> 156 // 链表
+

跳表 的查找复杂度为 $O(logN)$。

+

时间复杂度

数据结构的时间复杂度

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
数据结构时间复杂度
哈希表$O(1)$
跳表$O(logN)$
双向链表$O(N)$
压缩列表$O(N)$
整数数组$O(N)$
+
+

不同操作的时间复杂度

    +
  1. 单元数操作是基础。每一种集合类型对单个数据实现的增删改查操作,复杂度由数据结构决定。
  2. +
  3. 范围操作很耗时。返回一个范围内的数据,复杂度一般是 $O(N)$,应尽量避免,可以使用 2.8 版本之后的 HSCANSSCANZSCAN 等命令进行渐进式遍历。
  4. +
  5. 统计操作通常高效。集合类型对集合中的元素个数由记录,例如 LLENSCARD
  6. +
  7. 例外情况只有几个。压缩列表和双向列表的会记录表头和表尾的偏移量,对它们的操作复杂度只有 $O(1)$,例如 LPOPRPOPLPUSHRPUSH
  8. +
+

问题

整数数组和压缩列表在查找时间复杂度方面并没有很大优势,为什么Redis还会把它作为底层数据结构?

+

主要有两方面原因:

+
    +
  1. 内存利用率。Redis 是内存数据库,大量数据存储在内存中,整数数组和压缩列表结构比较紧凑,相比链表结构占用内存更少,可以提高内存利用率。
  2. +
  3. 数组对CPU高速缓存支持更友好。集合元素较少时,使用内存紧凑的排列方式,可以利用CPU高速缓存,尤其在一个缓存行内(64字节)有更高效的访问效率。当元素数量超过阈值后,为避免复杂度太高,可以转为哈希表和跳表。
  4. +
+

3. 高性能 IO 模型

Redis 的网络 IO 和键值对读写是由单个线程完成的;其他功能,例如持久化、异步删除、集群数据同步,是由额外的线程执行的。

+

Redis使用 IO多路复用,即一个线程处理多个 IO 流。

+

可能存在的瓶颈(来自Kaito):

+
    +
  1. 单个请求耗时会导致后续所有请求阻塞等待,包括以下几种:
      +
    • 操作bigkey,分配、释放内存(4.0推出了lazy-free,可以异步执行bigkey释放)
    • +
    • 使用复杂度较高的命令,排序、union、查询全量数据
    • +
    • 大量key集中过期
    • +
    • 淘汰策略,内存超过Redis内存上线,每次写入都要淘汰一些key,导致耗时变长
    • +
    • AOF 开启 always
    • +
    • 主从同步生成 RDB 时的 fork
    • +
    +
  2. +
  3. 并发量大时,单线程处理客户端请求,无法利用多核(6.0推出了多线程,可利用多核CPU处理用户请求)
  4. +
+
+

4. AOF(Append Only File)

Redis 的 AOF 是写后日志,先执行命令,把数据写入内存,然后再记录日志。

+

不会阻塞当前写操作,执行完命令还没来得及记日志就宕机的话,会有数据丢失的风险。

+
1
2
3
4
5
6
7
*3 --> 这个命令有三个部分
$3 --> 第一部分的长度
set --> 第一部分的内容
$5
hello
$5
world
+

三种写回策略

    +
  • Always,同步写回,每个命令执行完立马写回磁盘
  • +
  • EverySec,每秒写回,写到AOF内存缓存,每秒写回一次
  • +
  • No,写到AOF内存缓存,由操作系统决定何时将其写回
  • +
+

日志重写

当日志文件过大时,可以对日志进行重写。可以把某些多条修改同一个键值对的命令合并为一条。

+

重写流程:

+
    +
  1. 主线程 fork 出 bgrewriteaof 子进程,主线程在将新指令写入 AOF 缓存时,还会写入 AOF 重写缓存;
  2. +
  3. 子进程将 fork 拷贝出来的内存快照写成命令日志;
  4. +
  5. 写完后将AOF重写缓存中的日志写到这个新的日志中,然后就可以用这个新日志替换旧日志了。
  6. +
+

其他要点

    +
  1. fork 使用的是操作系统的写时复制(Copy On Write)机制,并不会直接拷贝所有内存。但是会拷贝父进程的的内存页表,如果页表较大,可能导致阻塞。
  2. +
  3. 主进程在提供服务时,如果操作的是bigkey,写时复制会导致内存分配;如果开启了内存大页(huge page)机制,内存分配会产生阻塞。(所以使用Redis通常要关闭内存大页,其对使用 fork 的程序不友好)
  4. +
  5. AOF 重写可以使用 bgrewriteaof 命令手动执行,也由两个配置项控制自动触发:
      +
    • auto-aof-rewrite-min-size:表示运行AOF重写的最小大小,默认为 64MB;
    • +
    • auto-aof-rewrite-percentage:当前AOF文件大小和上一次重写后AOF文件大小的差值,除以上一次重写AOF文件大小,即增量和上一次全量的比值,默认为 100。
      AOF 文件大小同时满足这两个配置项,会自动触发 AOF 重写。
    • +
    +
  6. +
+
+

5. 内存快照 RDB

把某一时刻的内存数据以文件的形式写到磁盘上,即快照。这个快照文件即 RDB 文件(Redis DataBase 的缩写)。

+

在数据恢复时,可直接把 RDB 文件读入内存,很快完成恢复。

+

Redis 执行的是全量快照。

+

可以使用 savebgsave 来生成 RDB 文件;save在主线程中执行,会阻塞;bgsave 会创建子进程专门进行 RDB 文件写入,不会阻塞。

+

快照进行时数据如何修改

使用 fork 创建子进程,该子进程会跟主线程共享所有内存数据;利用操作系统的写时复制技术,当主进程修改数据时,会重新分配空间生成该数据的副本,并使用该副本提供服务,bgsave 子进程仍然使用未修改的数据进行快照写入。

+

快照频率

频繁执行全量快照有两方面开销:

+
    +
  1. 给磁盘带来压力
  2. +
  3. fork 本身可能阻塞主线程,如果频繁 fork 会导致主线程阻塞所以,如果有一个 bgsave 在运行,就不会启动第二个了
  4. +
+

4.0 中提出了混合使用AOF和RDB的方法,即快照以一定的频率执行,在快照间以AOF的形式记录这期间的命令。实现:

+
    +
  1. 避免了频繁 fork 对主线程的影响
  2. +
  3. 减少了 AOF 文件大小,提高了数据恢复效率
  4. +
+

在配置文件中设置:

+
1
aof-use-rdb-preamble yes
+

三点建议

    +
  1. 数据不能丢失时,使用 AOF 和 RDB 混合方案
  2. +
  3. 允许分钟级别的数据丢失,使用 RDB
  4. +
  5. 只用 AOF 的话,优先使用 everysec,在可靠性和性能之间比较平衡
  6. +
+

课后题:资源风险(Kaito)

+
    +
  1. 考虑 fork 导致多大程度的写时复制,是否会造成 OOM,或者 swap 到磁盘上导致性能降低;
  2. +
  3. CPU 核数较少时,Redis的其他子进程会跟主线程竞争CPU,导致性能下降;
  4. +
  5. 如果 Redis 进程绑定了 CPU,子进程会继承它的 CPU 亲和属性,与父进程争夺同一个 CPU 资源,导致效率下降。所以如果开启RDB和AOF,一定不要绑定CPU。
  6. +
+
+

6. 主从同步

可靠性分两方面:

+
    +
  1. 数据少丢失(由RDB、AOF保证);
  2. +
  3. 服务少中断(通过增加副本冗余量保证)
  4. +
+

读写分离:主库、从库都可以执行读操作,主库限制性写操作,然后同步给从库。

+

如果都允许写,会造成各实例副本不一致,需要涉及加锁、协商等各种操作,产生巨额开销。

+

主从第一次同步

在从库实例上执行:

+
1
replicaof [主库ip] [主库port]
+

三个阶段

    +
  1. 主从建立链接、协商同步,从库给主库发送 psync 命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync 命令包含了主库的 runID 和复制进度 offset 两个参数。主库收到 psync 命令后,用 FULLRESYNC 响应命令带上两个参数,主库的 runID 和主库目前的复制进度 offset 给从库,从库会记住这两个参数。
  2. +
+
+

runID:每个 Redis 实例启动时自动生成的一个随机 ID,唯一标识一个实例。第一次复制时从库不知道主库的 runID,使用 ?
offset:第一次复制设置为 -1

+
+
    +
  1. 主库执行 bgsave 生成 RDB 文件,并发送给从库。从库收到 RDB 文件后,清空数据库并加载 RDB 文件。此时主库不会停止服务,但在内存中会有专门的 replication buffer,记录 RDB 文件生成后收到的所有写操作。
  2. +
  3. 主库把 replication buffer 中的修改操作发送给从库执行。
  4. +
+

主从级联

使用“主-从-从”模式分担主库压力。二级从库可以选择一个内存较高的从库进行同步。

+

主从间使用基于长连接的命令传播。

+

网络断开

2.8 前网络闪断会造成重新全量复制,2.8 后可以增量复制,实现继续同步。

+

(Kaito)主库除了将所有写命令传给从库之外,也会在 repl_backlog_buffer 中记录一份。当从库断线重连后,会发送 psync $master_runid $offset,主库就能通过 $offsetrepl_backlog_buffer 中找到从库断开的位置,只发送 $offset 之后的增量给从库。

+
+

repl_backlog_buffer:为了解决从库断开后找到主从差异而设计的环形缓冲区。配置大一点,可以减少全量同步的频率。

+

replication buffer:客户端、从库与 Redis 通信时,Redis 都会分配一个内存 buffer 用于交互,把数据写进去并通过 socket 发送到对端。当对端为从库时,该 buffer 只负责传播写命令,通常叫做 replication buffer。这个 buffer 由 client-output-buffer-limit 参数控制,如果过小或者从库处理过慢,Redis 会强制断开这个链接。

+
+

为啥不用 AOF 同步

    +
  1. 相比 RDB 文件更大,效率低;
  2. +
  3. 必须打开 AOF,数据丢失不敏感业务没必要开启
  4. +
+
+

7. 哨兵


+

8. 哨兵集群


+

9. 切片集群


+ +
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2021/09/26/redis-practices/index.html b/2021/09/26/redis-practices/index.html new file mode 100644 index 0000000..e95498f --- /dev/null +++ b/2021/09/26/redis-practices/index.html @@ -0,0 +1,501 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Redis 学习笔记(实践)[WIP] | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ Redis 学习笔记(实践)[WIP] +

+ + +
+ + + + +
+ + +

1. 降低 String 内存开销


+

2. 统计


+

3. GEO


+

4. 时间序列数据

+
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2021/10/08/heap-sort/index.html b/2021/10/08/heap-sort/index.html new file mode 100644 index 0000000..1cee93e --- /dev/null +++ b/2021/10/08/heap-sort/index.html @@ -0,0 +1,518 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 堆排序 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 堆排序 +

+ + +
+ + + + +
+ + +

使用数组结构表示堆结构,元素从索引 0 开始,则索引 i 位置:

+
    +
  • 父节点索引为 $(i - 1) / 2$
  • +
  • 左孩子索引为 $2 \times i + 1$
  • +
  • 右孩子索引为 $2 \times i + 2$
  • +
+
+

实现

    +
  • Java 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
public class Heap {

public static void main(String[] args) {
Heap heap = new Heap(1000);
heap.push(-6);
heap.push(3);
heap.push(-1);
heap.push(0);
heap.push(9);
heap.push(-4);
heap.push(5);
heap.push(7);
heap.push(-2);
heap.push(9);
heap.push(13);

System.out.println(heap);

while (!heap.isEmpty()) {
System.out.println(heap.pop());
System.out.println(heap);
}
}

private final int[] data;
private final int limit;
private int heapSize;

public Heap(int limit) {
this.limit = limit;
this.data = new int[limit];
this.heapSize = 0;
}

public void push(int value) {
if (heapSize >= limit) {
throw new RuntimeException("heapSize must not exceed limit");
}

this.data[heapSize++] = value;
heapInsert(heapSize - 1);
}

public boolean isEmpty() {
return heapSize == 0;
}

public Integer pop() {
if (isEmpty()) {
return null;
}

swap(--heapSize, 0);
int result = this.data[heapSize];
this.data[heapSize] = 0;
heapify(0);
return result;
}

private void heapInsert(int index) {
while (index > 0 && this.data[index] > this.data[(index - 1) / 2]) {
swap(index, (index - 1) / 2);
index = (index - 1) / 2;
}
}

private void swap(int i, int j) {
if (i != j) {
this.data[i] = this.data[i] ^ this.data[j];
this.data[j] = this.data[i] ^ this.data[j];
this.data[i] = this.data[i] ^ this.data[j];
}
}

private void heapify(int index) {
int leftIndex = 2 * index + 1;

while (leftIndex < heapSize) {
int rightIndex = leftIndex + 1;
int greatestIndex = rightIndex < heapSize && this.data[rightIndex] > this.data[leftIndex] ? rightIndex : leftIndex;

if (this.data[greatestIndex] <= this.data[index]) {
break;
}

swap(index, greatestIndex);
index = greatestIndex;
leftIndex = 2 * index + 1;
}
}

@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append('[');
for (int i = 0; i<heapSize; i++) {
b.append(data[i]);
if (i != heapSize - 1)
b.append(", ");
}
b.append(']');

return b.toString();
}
}
+

输出内容:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[13, 9, 5, 3, 9, -4, -1, -6, -2, 0, 7]
13
[9, 9, 5, 3, 7, -4, -1, -6, -2, 0]
9
[9, 7, 5, 3, 0, -4, -1, -6, -2]
9
[7, 3, 5, -2, 0, -4, -1, -6]
7
[5, 3, -1, -2, 0, -4, -6]
5
[3, 0, -1, -2, -6, -4]
3
[0, -2, -1, -4, -6]
0
[-1, -2, -6, -4]
-1
[-2, -4, -6]
-2
[-4, -6]
-4
[-6]
-6
[]
+
    +
  • Rust 实现
  • +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
pub struct Heap<T>(Vec<T>);

impl<T: Ord> Heap<T> {
pub fn new() -> Self {
Self(Vec::new())
}

pub fn push(&mut self, value: T) {
self.0.push(value);
self.heap_insert();
}

pub fn pop(&mut self) -> Option<T> {
if self.0.is_empty() {
return None;
}

let last = self.0.len() - 1;
self.0.swap(0, last);
let result = self.0.pop();
self.heapify();
result
}

fn heapify(&mut self) {
let len = self.0.len();

let mut index = 0;
let mut left = index * 2 + 1;

while left < len {
let right = left + 1;
let greatest = if right < len && self.0[left] < self.0[right] {
right
} else {
left
};

if self.0[greatest] <= self.0[index] {
break;
}

self.0.swap(index, greatest);
index = greatest;
left = index * 2 + 1;
}
}

fn heap_insert(&mut self) {
if self.0.is_empty() {
return;
}

let mut index = self.0.len() - 1;
if index == 0 {
return;
}
let mut parent = (index - 1) / 2;

while self.0[parent] < self.0[index] {
self.0.swap(index, parent);
index = parent;
if index == 0 {
break;
}
parent = (index - 1) / 2;
}
}
}

impl<T: Ord> Iterator for Heap<T> {
type Item = T;

fn next(&mut self) -> Option<Self::Item> {
self.pop()
}
}

#[test]
fn test_heap() {
for _ in 0..1000 {
let array: [u8; 32] = rand::random();
let sorted = {
let mut s = array.clone();
s.sort_by(|a, b| b.cmp(a));
s
};

let mut heap = Heap::new();

for a in array {
heap.push(a);
}

let heap_sorted: Vec<u8> = heap.collect();

assert_eq!(heap_sorted, sorted);
}
}
+
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2022/08/13/sync-async-blocking-nonblocking/index.html b/2022/08/13/sync-async-blocking-nonblocking/index.html new file mode 100644 index 0000000..6c9d2a2 --- /dev/null +++ b/2022/08/13/sync-async-blocking-nonblocking/index.html @@ -0,0 +1,498 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 同步与异步,阻塞与非阻塞 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 同步与异步,阻塞与非阻塞 +

+ + +
+ + + + +
+ + +

想写这样一篇总结也是因为在互联网上看了太多乱七八糟的说法,往往让读者越看越糊涂。这些说法往往给我这样的感觉:

+
    +
  1. 没有先讲清楚在形容什么:同步、异步、阻塞、非阻塞通常作为形容词使用,无论是用在日常语境,还是用在编程领域内部,如果你不知道这些形容词是放在什么名词之前就开始讲什么意思,甚至一段话中掺杂多个名词去形容而不说明,那就会给人不知所云的感觉。
  2. +
  3. 概念与实现混为一谈:同步阻塞、异步非阻塞是非常常见的组合方式,所以在讲同步、异步的概念时,它们忍不住拿这些具体实现来讲概念,也就顶多能让读者理解这些固定搭配,难以活学活用(其实能不能做到前者都存疑,甚至作者自己真的懂吗?)。
  4. +
  5. 不恰当的比喻:比喻的使用是为了帮助表达,本体和喻体之间当然是不同的事物(否则就不是比喻了),但是讲了个生动的比喻,却没有把本体与喻体之间的对应关系讲清楚,或者没有讲清楚这个比喻的边界,给话题引入了不必要的复杂性,起到了相反的作用。
  6. +
+

所以,我打算好好聊一下我对这个话题的理解,尽可能不要犯我认为别人犯过的这些错误,欢迎读者监督。

+

同步与异步

同步这个词在日常语境下与计算机是有着一些不同的。日常语境下,同步经常用在同时进行的事情上,比如“多个平台同步直播卖货”,“推动新时代四化同步发展”,当然它们之间是经过协调一致的,不是无组织无纪律各行其是的。

+

而在编程领域,“同步”和“异步”通常用来是形容“任务”、“函数”等可执行对象的(以下统一称作“任务”);当然也有用来形容“API”的,此时它们表示的是一种具体实现的风格,即“同步风格的API”、“异步风格的API”,它跟前者有关系也有区别。

+

那它们究竟是什么意思呢?

+

无论是同步还是异步,都一定是用来形容多个任务的,我们可以说这两个任务或者这三个函数是同步或者异步执行的,但是一个任务不存在同步或者异步。同步指的是多个任务一个一个执行,不存在时间重叠的情况,异步指的是多个任务不一个一个执行,可能存在时间重叠的情况。

+
+

有的说法硬要在讲清楚这个概念之前,先把这个任务假设成一个异步函数,再把它分成调用和获取结果两个步骤,开始谈什么消息,打比方讲什么先打了个电话订餐,又接了个电话通知,然后告诉你这叫异步,这是很容易造成误解的。即便要拆成俩,单个任务的调用和获取结果也一定是一前一后,不可能先获取结果再调用,也不可能同时进行,这两个步骤是同步的,不以具体实现为转移,它们只是可能跟另一个任务是异步的,拆成俩徒增复杂度,没有任何必要。所以干脆不要拆,也不要聊具体实现,我们说一个任务,它就是一个任务,就是执行一段时间拿到结果,不要拆开。比如“吃外卖”,就等于“下单-收到电话-取餐-吃”这整个过程,反正经过了一段时间,不要在意内部细节

+
+

那么,同步和异步应该怎么使用呢,比如;

+
    +
  1. 我先吃了个外卖,然后打了一会游戏,吃外卖和打游戏这两个任务就是同步的。
  2. +
  3. 我先吃了个外卖,然后打了一会游戏,又写了会代码,这三个任务也是同步的。
  4. +
  5. 在 2. 的基础上,我在吃外卖的过程中还听了一段时间的音乐,那么听音乐和吃外卖是异步的(不要管我什么时候开的音乐,什么时候关的音乐)。
  6. +
  7. 在 2. 的基础上,打游戏期间都是保证把音乐关掉了的,但是吃外卖和写代码期间都听了一会音乐,那么听音乐和玩游戏是同步的,和吃外卖、写代码是异步的。
  8. +
+

是不是很简单,只要按照那个定义去套就行了。但是有一点要注意,4. 里面的 “打游戏期间都是保证把音乐关掉了” 的 “保证” 二字是很关键的,它确保了听音乐和玩游戏之间的 “一个一个执行” 的条件,这需要我在玩游戏之前确保音乐是关掉的,又保证玩游戏期间没有突然去把音乐打开,如果你只是凭运气,只是偶然造成了 “玩游戏期间音乐是关掉的” 的结果,那么它仍然是异步的,因为异步的定义里说的是“可能存在时间重叠”,你没有确保完全排除这个可能,它们就是异步的。

+

我们再回到日常语境下的“同步”,会发现它跟编程的“同步”是相通的。比如“多个平台同步直播卖货”,虽然整体上各平台是同时在直播一个流媒体源,看上去更符合异步的定义,但其实把“直播”拆分成“录制”、“拉流”、“转场”、“互动”、“上链接”等任务时,会发现它们之间还是存在一些比较严格的先后顺序的,比如你不可能在媒体源录制之前拉流,也不可能在某个重要互动之前就享受某些优惠。也就是说,日常语境下的同步,更多的是描述两件相关的大任务,它们的子任务只要存在一定程度的“保证”就可以说“同步”,并没有编程领域这么严格。

+

阻塞与非阻塞

这确实是一对很难脱离具体实现讲的概念。

+

不过相比同步与异步,阻塞与非阻塞更简单,是用来形容单个可执行对象(以下仍统一称作“任务”)的执行状态的。

+

可能被形容的有线程、协程、某一段业务逻辑等等:

+
    +
  1. 我在某线程用 BIO 接口读一个文件,造成该线程无法继续运行,这就是线程阻塞了
  2. +
  3. 我在某协程内使用该语言/框架提供的 sleep 方法,造成协程无法继续运行,这就是协程阻塞了(所在线程并没有阻塞)
  4. +
  5. 我写了一段代码,反正调用 IO 或者 sleep 了,我也不知道运行在哪个线程上,哪个协程上,或者只是一个异步的 setTimeout, 反正就是在等,这就是业务逻辑阻塞了(可能线程和协程都没有被阻塞)
  6. +
+

但其实这样说有点扩大概念外延的意思,99.9% 的情况下,我们说阻塞指的都是线程。而非阻塞,通常指的也是某个任务不会造成线程阻塞。

+

为什么线程阻塞是最常讨论的话题呢?因为:

+
    +
  1. 线程的调度和空间占用,都是比较消耗系统资源的。一个 I/O 密集型业务(比如最常见的 Web 服务),如果会因为 I/O 阻塞线程,那么为了支持多个并发同时进行,就需要为每个独立的并发单独开一个线程。在并发较高时,就需要开大量的线程,占用大量 CPU 和内存资源,所以我们有了 C10K 问题。
  2. +
  3. 这是一个已经被用各种方式花式解决了的问题了。Linux 上的 epoll、Windows 上的 IOCP、MacOS 上的 kqueue(先不要管它们是异步/同步、阻塞/非阻塞,总之 —>),都提供了可以让调用者不必开一堆线程而只需要复用固定数量的线程即可支撑较高并发的接口。不管是 Nodejs,还是 Vert.X 的异步回调,Go 的有栈协程,Rust 的无栈协程,甭管它们封装成异步风格还是同步风格的API,是甜还是咸,都是建立在前者基础上的。简单说,目的只有一个:不阻塞调用者的线程,或者说线程复用
  4. +
+

业务要你等3秒,网络数据包要等几百毫秒才能到,硬盘读写要有时间,这些都会造成业务逻辑的阻塞,如果使用 BIO 接口,则业务逻辑的阻塞也会导致线程的阻塞。而使用了上述的框架/语言,让你不用在这些时候阻塞自己的线程,被阻塞的就只有当前业务逻辑,线程可以去跑别的业务逻辑,实现了线程复用。

+

组合起来

现在,单讲同步、异步、阻塞、非阻塞,应该没什么问题了,我们再聊聊那些常见的搭配。

+

我们经常听说同步阻塞、异步非阻塞这种搭配方式,而如果按照前文的定义,这种搭配方式是很奇怪的——同步和异步是用来形容多个任务的,阻塞和非阻塞时是说单个任务的,这样组合在一起是在干嘛?

+

一般来讲,它们是用来形容某种 API 的风格和实现的。

+

同步和异步是表示这种 API 的接口风格。比如常见的 Linux 的 BIO 接口、Go 的 IO 接口、Rust 里的 async 函数,这些都是同步风格的接口,先后调用这样的两个接口,它们默认是同步执行的,你要异步执行,需要另外开线程或者协程;Node.js、Vert.X 的绝大部分接口,都是异步风格的接口,先后调用这样的两个接口,它们默认是异步执行的,你要同步执行,需要把第二个接口写到前一个接口的回调函数里。

+

阻塞和非阻塞是表示这种 API 需要调用者配合使用的线程模型。比如 Linux 的 BIO 接口会阻塞调用者的线程,它就是阻塞的,而 Go 的 IO 接口、Rust 的 async 函数、Node.js 和 Vert.X 里的异步接口,都不会阻塞调用者的线程,它们就是非阻塞的。

+

在没有协程的年代,同步风格的 BIO 接口就是会导致线程阻塞,它意味着心智负担小,开发方便,但是吃资源;而不会阻塞线程的 NIO/AIO 接口往往是异步风格(或者封装为异步风格),代码写起来心智负担重,性能好,比如 Node.js 和 Vert.X。所以经常有人拿同步代指阻塞,拿异步代指非阻塞,它们成为了同义词两两绑定在一起,即“同步阻塞”和“异步非阻塞”。

+

而 Go 、Rust 等语言提供的协程,相当于是对“异步非阻塞”的一种高级封装,可以像写同步代码那样写不阻塞线程的代码,让你即拥有高性能,又没有很高的心智负担。但是也很少见他们讨论自己是同步还是异步,阻塞还是非阻塞,因为风格上确实是同步的,封装的实际上是异步常用的非阻塞接口,确实不会阻塞线程,但是协程是可能被阻塞。所以不要套概念,理解原理,理解同步具体指的是谁和谁,异步具体指的是谁和谁,阻塞和非阻塞指的具体是谁,搞清楚对象,套定义就可以了。

+

总结

在写这篇文章的时候,我并没有查询很多资料,但自认为这算是一个可以简化问题,帮助理解的模型。

+

希望能给读者一些启发,也欢迎批评指正。

+ +
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2023/03/05/relation-algebra-division/index.html b/2023/03/05/relation-algebra-division/index.html new file mode 100644 index 0000000..3a6aa08 --- /dev/null +++ b/2023/03/05/relation-algebra-division/index.html @@ -0,0 +1,768 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 关系代数除法 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 关系代数除法 +

+ + +
+ + + + +
+ + +

定义

    +
  • $S$、$R$ 为关系模式,$s$、$r$ 为关系实例。
  • +
  • 要计算关系除法,需要满足 $S ⊆ R$。
  • +
  • 给定一个元组 $t$,令 $t[S]$ 代表元组 $t$ 在 $S$ 中属性上的投影;那么,$r ÷ s$ 是 $R-S$ 模式的一个关系。
  • +
  • 元组 $t$ 在 $r ÷ s$ 中的充要条件是满足以下两个
      +
    1. $t$ 在 $Π_{R-S}(r)$ 中
    2. +
    3. 对于 $s$ 中的每个元组 $t_s$,在 $r$ 中存在一个元组 $t_r$ 且满足:
        +
      • $t_r[S] = t_s[S]$
      • +
      • $t_r[R-S] = t$
      • +
      +
    4. +
    +
  • +
  • 例子:
  • +
+

$r$:

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
A1A2
ae
af
be
bg
ce
cf
df
+
+

$s$:

+
+ + + + + + + + + + + + + + +
A2
e
f
+
+

$r ÷ s$:

+
+ + + + + + + + + + + + + + +
A1
a
c
+
+

公式

关系代数除法可以使用其它关系代数运算替代:

+

用上一节的例子就是:

+
+ + + + + + + + + + + + + + + + + + + + +
A1
a
b
c
d
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
A1A2
ae
af
be
bf
ce
cf
de
df
+
+
+

公式中是为了计算差($-$)的时候两个操作数模式相同,对 $r$ 做了一下模式的颠倒,其实还是 $r$。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
A1A2
ae
af
be
bg
ce
cf
df
+
+
+
+ + + + + + + + + + + + + + + + + +
A1A2
bf
de
+
+
+
+ + + + + + + + + + + + + + +
A1
b
d
+
+
+
+ + + + + + + + + + + + + + +
A1
a
c
+
+ +
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2024/04/26/zkp-note-0/index.html b/2024/04/26/zkp-note-0/index.html new file mode 100644 index 0000000..e15f291 --- /dev/null +++ b/2024/04/26/zkp-note-0/index.html @@ -0,0 +1,463 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 零知识证明笔记-0-目标与计划 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 零知识证明笔记-0-目标与计划 +

+ + +
+ + + + +
+ + +

目标

从今天开始,学习零知识证明相关知识,暂定 3 个月(到2024年7月26日完成)的学习目标为:

+
    +
  1. 完成零知识证明常见算法、实现的学习,至少能够知道它们解决的是什么问题,分别适用于什么业务场景。
  2. +
  3. 学会并熟练掌握使用至少一个流行的 Rust 零知识证明库。
  4. +
  5. 参与至少一个零知识证明的(开源?)项目,需要提交有质量的代码。
  6. +
+
+

初步计划

V20240426

    +
  1. 第一个月: World of Z2O-K7E 的学习;产出为“能够在朋友、同事甚至是小学生(如果我有幸能认识一个愿意听我讲的)面前把学到的哲学、原理、应用讲出来,讲清楚”。
  2. +
  3. 第二个月: 完成 World of Z2O-K7E 的学习并开始学习 Rust 相关库,写 demo;学习产出参考上一个月,写 demo 的产出为能够全面反映该 Rust 库的各种功能的一个 repo。
  4. +
  5. 第三个月: 继续写 demo,看相关开源项目源码,参与项目;产出物为 项目地址 / commit / PR 记录等等。
  6. +
+
+

介于本人在该领域是完全的“零知识”,以上仅为初步计划,如果有任何修改(即使是放弃)都需要更新本文。

+
+

20240530

一个多月了,公司的事有点烦。实际上我话太少,并没有找人验证过我是否能证明我能讲出所有我学到的东西,目前学习进度并不快,只是能够把 RSA 和 schnorr 的流程完整复述出来(并用小质数作为例子介绍)。报了一个课,希望能学到我想学的东西,后面打算整理这个课的笔记了。继续加油!

+ +
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2024/05/25/zkp-note-1/index.html b/2024/05/25/zkp-note-1/index.html new file mode 100644 index 0000000..9abdb47 --- /dev/null +++ b/2024/05/25/zkp-note-1/index.html @@ -0,0 +1,561 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 零知识证明笔记-1-密码学基础 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 零知识证明笔记-1-密码学基础 +

+ + +
+ + + + +
+ + +

这是结诚浩的《图解密码技术》一书的读书笔记,我是在去北京的火车上把其中感兴趣的部分草草过完的。World of Z2O-K7E 只是建议过一下,我就只是过一下,感觉还是很有帮助的,至少看过了之后,我能感觉自己的大脑切换到了“密码学的上下文”,接收零知识证明相关的知识似乎更高效了。

+

基本常识

    +
  1. 不要使用保密的密码算法(即隐蔽式的安全性),应该使用得到广为验证的公开的密码算法,而保护好密钥。
  2. +
  3. 使用低强度的密码会给人一种错误的安全感,比不进行加密更危险。
  4. +
  5. 任何密码总有一天会被破解,除了不实用的一次性密码本和传说中的量子密码。
  6. +
  7. 密码只是信息安全的一部分,提防“社会工程学”。
  8. +
+

对称密码

DES / 3DES

一种将 64 位明文加密为 64 位密文的对称密码算法,需要以分组为单位对输入进行处理(这样的密码算法称为分组密码,block cipher)。DES 已经可以在现实时间内被破解。

+

三重DES(3DES)是为了增加 DES 的强度设计的。三重 DES 并不是三轮加密,而是加密、解密、加密。如果三轮的密钥都相同,就跟 DES 一样了。

+

第一轮和第三轮使用同样的密钥则称为 DES-EDE2。
三轮都不同则称为 DES-EDE3。

+

AES

Rijndael 算法在 2000 年被选为 AES。

+

分组长度固定为 128 比特,密钥长度有 128、192、256 比特三种。

+

加密过程:SubBytes -> ShiftRows -> MixColumns -> AddRoundKey

+

解密过程: AddRoundKey -> InvMixColumns -> InvShiftRows -> InvSubBytes

+

如何选择

不用 DES,少用 3DES,尽量用 AES。

+

分组密码的模式

    +
  • ECB(Electronic CodeBook,电子密码本):简单,快速,支持并行计算;明文中重复排列会反映在密文中,并且通过删除、替换密文分组可以对明文进行操作,对包含某些比特错误的密文解密时对应的分组会出错,不能抵御重放攻击。不要用。
  • +
  • CBC(Cipher Book Chaining,密文分组连接模式):推荐使用,重复的明文不会导致重复的密文,可以解密任意密文分组。必须是先加密再 XOR,如果反过来,则其效果等同于 ECB,可以直接从 IV (初始化向量)开始,依次将前后两组密文执行 XOR,得到的每组结果恰好就等于 ECB 每组的结果。
  • +
  • CFB / OFB:不需要填充(padding)。
  • +
  • CTR(CounTeR,计数器模式):推荐使用,主动攻击者反转密文分组中的某些比特时,明文分组中相对应的比特也会被反转。
  • +
+

公钥密码

RSA 的基本步骤:

+
    +
  1. 取两个比较大的质数 p 和 q。
  2. +
  3. 求两者的乘积 $N = p \times q$。
  4. +
  5. ,即 p 和 q 的最小公倍数;有的资料说的是求欧拉函数值 $\phi = (p-1) \times (q-1)$,以下统称 $\phi$。
  6. +
+
+

GPT: 两者都有各自的使用场景,但在实际使用中,求欧拉函数值 ($\phi$) 更为常见。

+
+
    +
  1. 选择公钥 $E$,$E$ 与 $\phi$ 互质并且 $1 < E < \phi$。
  2. +
  3. 求私钥 $D$,$D \times E \equiv 1$ (mod $\phi$),即 D 为 E 的乘法逆元。
  4. +
  5. 这样得到公钥为 $(E, N)$,私钥为 $(D, N)$。
  6. +
+

一个使用小质数实现的简单的 RSA:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
use num::BigInt;
use rand::seq::index::sample;
use rand::{thread_rng, Rng};

const SMALL_PRIMES: [u64; 50] = [
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193,
197, 199, 211, 223, 227, 229,
];

#[derive(Debug)]
struct KeyPair {
private_key: u64,
public_key: u64,
modulus: u64,
}

impl KeyPair {
pub fn generate() -> Self {
let mut rng = thread_rng();
let indices = sample(&mut rng, SMALL_PRIMES.len(), 2);
// use crate rand to select two random prime numbers
let p = SMALL_PRIMES[indices.index(0)];
let q = SMALL_PRIMES[indices.index(1)];
let modulus = p * q;
let phi = (p - 1) * (q - 1);
let public_key = loop {
let public_key = SMALL_PRIMES[thread_rng().gen_range(0..SMALL_PRIMES.len())];
if public_key < phi && gcd(public_key, phi) == 1 {
break public_key;
}
};

let mut private_key = 1;
while (public_key * private_key) % phi != 1 {
private_key += 1;
}

Self {
private_key,
public_key,
modulus,
}
}

pub fn encrypt(&self, message: BigInt) -> BigInt {
message.pow(self.public_key as u32) % self.modulus
}

pub fn decrypt(&self, encrypted_message: BigInt) -> BigInt {
encrypted_message.pow(self.private_key as u32) % self.modulus
}

// getters

pub fn get_private_key(&self) -> (u64, u64) {
(self.private_key, self.modulus)
}

pub fn get_public_key(&self) -> (u64, u64) {
(self.public_key, self.modulus)
}

pub fn get_modulus(&self) -> u64 {
self.modulus
}
}

fn gcd(p0: u64, p1: u64) -> u64 {
let mut p0 = p0;
let mut p1 = p1;
while p1 != 0 {
let temp = p1;
p1 = p0 % p1;
p0 = temp;
}
p0
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_rsa() {
for _ in 0..100 {
let key_pair = KeyPair::generate();
let message = BigInt::from(rand::thread_rng().gen_range(6..key_pair.get_modulus()));
let encrypted_message = key_pair.encrypt(message.clone());
let decrypted_message = key_pair.decrypt(encrypted_message.clone());
assert_eq!(decrypted_message, message);
println!(
"public key: ({}, {}), private key: ({}, {}), message: {}, encrypted: {}, decrypted: {}",
key_pair.get_public_key().0, key_pair.get_public_key().1,
key_pair.get_private_key().0, key_pair.get_private_key().1,
message,
encrypted_message,
decrypted_message
)
}
}
}
+

单向散列函数

单向散列函数(也称为消息摘要函数、哈希函数、杂凑函数),用来保证消息的完整性(integrity),也称为一致性。

+

输入的消息也称为原像(pre-image)。

+

输出的散列值也称为消息摘要(message digest)或者指纹(fingerprint)。

+

难以发现碰撞的性质称为抗碰撞性(collision resistance)。

+

算法选择

MD5 是不安全的。

+

SHA-1 不应该用于新用途。

+

SHA-2 (SHA-256以上) 、SHA-3 是安全的,可以使用。

+

消息认证码

消息认证码(Message Authentication Code)是一种确认完整性并进行认证的技术。

+

实现方法

    +
  • 单向散列函数,例如 HMAC。
  • +
  • 分组密码,例如 AES-CMAC。
  • +
  • 其他,例如流密码和公钥密码。
  • +
+

数字签名

使用公钥密码(比如 RSA、椭圆曲线)来实现。

+
    +
  • 绝对不要对意思不清楚的消息进行签名,可能无意中解密了公钥加密的密文。
  • +
  • 依赖公钥基础设施,公钥必须属于真正的发送者,即使用证书来确认。
  • +
+

密钥交换

Diffie-Hellman

一个使用小质数实现的简单的 Diffie-Hellman:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
use num::BigInt;
use rand::thread_rng;
use rand::Rng;

const P_AND_G: [(u64, u64); 20] = [
(7, 3),
(11, 2),
(13, 2),
(17, 3),
(19, 2),
(23, 5),
(29, 2),
(31, 3),
(37, 2),
(41, 6),
(43, 3),
(47, 5),
(53, 2),
(59, 2),
(61, 2),
(67, 2),
(71, 7),
(73, 5),
(79, 3),
(83, 2),
];

// prime P and generator G
#[derive(Debug)]
pub struct PnG {
prime: u64,
generator: u64,
}

impl PnG {
pub fn generate() -> Self {
let (prime, generator) = P_AND_G[thread_rng().gen_range(0..P_AND_G.len())];
Self { prime, generator }
}

pub fn get_prime(&self) -> u64 {
self.prime
}

pub fn get_generator(&self) -> u64 {
self.generator
}

pub fn generate_random_number(&self) -> u64 {
// generate a number in the range of 1 to prime - 2
thread_rng().gen_range(1..self.prime - 2)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_diffie_hellman() {
for round in 0..10 {
let pg = PnG::generate();
println!(
"{round}) Prime: {}, Generator: {}",
pg.get_prime(),
pg.get_generator()
);
let alice_number = pg.generate_random_number();
let bob_number = pg.generate_random_number();
println!(
"{round}) Alice number: {}, Bob number: {}",
alice_number, bob_number
);
let alice_partial_key =
BigInt::from(pg.get_generator()).pow(alice_number as u32) % pg.get_prime();
let bob_partial_key =
BigInt::from(pg.get_generator()).pow(bob_number as u32) % pg.get_prime();
println!(
"{round}) Alice partial key: {}, Bob partial key: {}",
alice_partial_key, bob_partial_key
);
let alice_full_key =
BigInt::from(bob_partial_key).pow(alice_number as u32) % pg.get_prime();
let bob_full_key =
BigInt::from(alice_partial_key).pow(bob_number as u32) % pg.get_prime();
println!(
"{round}) Alice full key: {}, Bob full key: {}",
alice_full_key, bob_full_key
);
assert_eq!(alice_full_key, bob_full_key);
}
}
}
+

椭圆曲线密码

椭圆曲线密码是利用“椭圆曲线上的离散对数问题”的复杂度来实现的。

+

使用了椭圆曲线的 schnorr 的一个简单实现:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
use k256::{NonZeroScalar, ProjectivePoint, PublicKey};
use rand::rngs::OsRng;

fn main() {
// 使用操作系统级别的 RNG
let mut rng = OsRng;

// Alice 拥有一个私钥 a,计算公钥 aG
// 私钥 a
let a = NonZeroScalar::random(&mut rng);
// 公钥 aG
let a_g = ProjectivePoint::from(PublicKey::from_secret_scalar(&a));

// 知道 G 和 aG,求 a,属于椭圆曲线上的离散对数问题,目前没有有效的解法
let r = NonZeroScalar::random(&mut rng);
// Alice 生成一个随机数 r,并计算 R = rG
let r_g = ProjectivePoint::from(PublicKey::from_secret_scalar(&r));

// Bob 提供一个挑战 c
let c = NonZeroScalar::random(&mut rng);

// Alice 计算响应 z = r + a * c (mod q)
let ac = a * c; // 计算 a * c
let z = r.add(&ac);

let Some(z) = NonZeroScalar::new(z).into() else {
eprintln!("计算 z 为零。");
return;
};

// Alice 将 z 发送给 Bob,Bob 验证 zG ?= R + c*PK
let z_g = ProjectivePoint::from(PublicKey::from_secret_scalar(&z));

// 计算 c * PK
let c_pk = a_g * *c;

// 计算 rG + c*PK
let r_plus_cpk = r_g + c_pk;

// 验证 zG ?= rG + c*PK
// c * PK 与 a * c 的关系是?
// 为什么这个证明是成立的
// 为什么这个证明是不可伪造的
// 回答:
// c * PK = c * a * G = a * c * G = a * c * G = ac * G
// PK = aG ?
if z_g == r_plus_cpk {
println!("验证成功,Alice 证明了她拥有私钥.");
} else {
println!("验证失败.");
}
}

// 与 GPT 的交谈:
// 为什么 Alice 要先生成一个随机数?
// 答:为了证明她拥有私钥,她需要一个随机数 r,然后计算 R = rG,然后计算 z = r + a * c (mod q)
// 可以让 Bob 先生成随机数并发送给 Alice 吗?(然后 Alice 再生成 r ,发送 rG)
// 答:可以,但是这样会暴露 Bob 的私钥,因为 Bob 会发送 c * PK,PK = aG,所以 Bob 的私钥就会暴露
// 不对啊,Bob 只需要发送 c,不会暴露私钥啊
// 答:对,Bob 只需要发送 c,不会暴露私钥,但是这样会暴露 Bob 的公钥,Bob 的公钥是 aG,这样会暴露 Bob 的身份
// 不对啊,aG 是 Alice 使用私钥 a 计算出来的公钥,怎么会是 Bob 的公钥呢?
// 答:对,我理解错了,Bob 只需要发送 c,不会暴露私钥,也不会暴露公钥,这样是可以的
// Bob 先发送随机数给 Alice 真的没问题吗?Alice 不会获得没有 a 但是证明她拥有 a 的超能力吗?
// 答:Alice 不会获得没有 a 但是证明她拥有 a 的超能力,因为 Alice 需要 a 来计算 z = r + a * c (mod q),如果没有 a,Alice 就无法计算 z
// Alice 不能用假的 a' 来计算 z 吗?一直用假的 a', 是否也能通过验证?
// 答:不能,因为 Bob 会用 a' 来计算 c * PK,然后验证 zG ?= R + c * PK,如果 a' 是错误的,那么 c * PK 也是错误的,验证就会失败
// PK 和 zG 都是用 a' 计算出来的,所以 Bob 永远都会验证成功,不管 Alice 用了什么 a',验证都会成功,这样 Alice 是否就获得的超能力
// 答:不会,因为 Alice 无法计算出正确的 z,验证会失败
// z' = r + a' * c (mod q),所以 z'G = R + c * a' * G 验证肯定成功吧?
// 答:不会,因为 c * a' * G != c * a * G,验证会失败
// 你好像没有理解,如果 Bob 实现发送了挑战 c,Alice 可以使用任何除了 a 之外的密钥替换 a(比如 a'),这样整个流程只是证明了 Alice 拥有一个密钥(这个密钥并不一定是 a)
// 答:对,我理解错了,如果 Bob 先发送了挑战 c,Alice 可以使用任何除了 a 之外的密钥替换 a(比如 a'),这样整个流程只是证明了 Alice 拥有一个密钥(这个密钥并不一定是 a)
// 所以 Alice 即使没有任何密钥,她临时编造一个,在这种情况下,Bob 永远都无法验证 Alice 是否拥有私钥,这样 Alice 就获得了超能力
// 所以顺序不能调换,对吗?
// 答:对,顺序不能调换,Alice 必须先生成一个随机数 r,然后计算 R = rG,然后计算 z = r + a * c (mod q),然后发送 z 给 Bob,Bob 验证 zG ?= R + c * PK
+
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2024/05/30/zkp-note-2/commit-and-prove.jpg b/2024/05/30/zkp-note-2/commit-and-prove.jpg new file mode 100644 index 0000000..2280d92 Binary files /dev/null and b/2024/05/30/zkp-note-2/commit-and-prove.jpg differ diff --git a/2024/05/30/zkp-note-2/index.html b/2024/05/30/zkp-note-2/index.html new file mode 100644 index 0000000..2514c9a --- /dev/null +++ b/2024/05/30/zkp-note-2/index.html @@ -0,0 +1,536 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 零知识证明笔记-2-PLONK概述笔记 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 零知识证明笔记-2-PLONK概述笔记 +

+ + +
+ + + + +
+ + +

Why ZKP?

零知识证明 Zero-knowledge Proofs

+

其中的 Proof($\pi$) 必须是个有用的:$Theorem / Statement / Proposition$

+
    +
  1. 不要把 Proof 这个词忘掉,到底证明了什么?
  2. +
  3. 两个角色,证明者(prover)、验证者(verifier),是双方的。写代码的时候要时刻关注写的到底是哪部分,是证明者的代码,还是验证者的代码。
  4. +
+

Prover Code: 需要关注,有没有产生一个有效的证明,产生这个证明的时候有没有泄漏不该泄漏的信息。
Verifier Code:关注,这个证明是不是有效的。

+
+

写代码的时候容易忘掉。
这是一个 Two-party interaction

+
+
    +
  1. 关注以下三个性质:
  2. +
+
    +
  • 完备性(Completeness):诚实的证明者(honest prover)一定能成功证明。
  • +
  • 可靠性(Soundness):恶意的证明者(malicious prover)一定通不过验证。很难测试,需要非常认真、仔细、深入的考虑这个问题:如何保证 verifier 不会被欺骗。
  • +
  • 零知识(Zero-knowledge):恶意的验证者(malicious verifier)无法获得有用的知识。
  • +
+
+

关注 Meta Theorems (Completeness and Soundness)的证明
如何保证我们写的 babyplonk 是 sound 的。

+
+
    +
  • Prover 和 Verifier 之间有大量的交互。Verifier 会发送大量的 challenge 给 Prover。challenge 越多,Proof Size 就越大。
  • +
  • 交互过程中的步骤顺序不能调换。
  • +
  • 实际项目中如果有安全漏洞是非常严重的。
  • +
  • 攻击本身也可能是 ZK 的,不知道谁干的。
  • +
+

Non-interactive

BlockChain 中经常使用 Non-interactive ZKP。 Fiat-Shamir Transformation 能把 public-coin protocol 转换成 non-interactive protocol。

+
+

public-coin 指的是掷硬币,公开的发随机数 challenge。
借助 Random Oracle (RO) 工具来辅助 Prover 来产生这些硬币。随机数不一定是由 verifier 作为来源,由可信第三方来提供也可以。
Prover 与 RO 交互,产生证明 $\pi$ 直接发送给 Verifier。不需要等待 Verifier 回复,Verifier 可以在未来异步验证这个证明。
通常使用一个足够安全的哈希函数(Cryptographic Hash Function)来替代 RO 的角色。没有严格的证明,大概率不会出问题。

+
+

Commitment

    +
  1. $O(1)$ Size。无论数据多大,比如 10 GB 的文件,都可以产生一个很小的串来代表它。
  2. +
  3. Binding。$CM(D1)\not ={CM(D2)} \iff D1\not ={D2}$。
  4. +
  5. Hiding。能够保护数据,可以混入随机数。同一个文件两次 Commit 可以得到不同的 Commitment。
  6. +
+

Blockchain: Put commitment on chain (EIP-4844,Rollup-blob)

+

Commit-and-Prove

alt text

+

Why zkSNARK

+
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2024/11/07/learn-index-0/index.html b/2024/11/07/learn-index-0/index.html new file mode 100644 index 0000000..11c6cdb --- /dev/null +++ b/2024/11/07/learn-index-0/index.html @@ -0,0 +1,528 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 常见索引结构-0 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ 常见索引结构-0 +

+ + +
+ + + + +
+ + +

以下是各种索引算法的简要对比表,包括其原理、性能优势、适用场景和缺点。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
索引算法原理介绍为什么性能高适用场景缺点/性能差的场景
B+ 树B+ 树是一种平衡多叉树,数据仅存储在叶节点,所有叶节点按顺序连接,适合范围查询和顺序访问。结构层次分明,叶节点通过链表相连,实现快速范围查找和顺序遍历。适用于数据库索引、大量顺序读取或范围查询的场景。高维数据或频繁更新的场景性能较差。插入、删除操作复杂,维护成本高。
倒排索引建立词项到文档的映射关系,将每个词项关联到出现该词的文档列表,通常用于全文搜索。通过直接访问词项位置,可快速找到包含该词的所有文档。适用于文本搜索、日志检索等全文检索场景。对于非文本数据无效,处理频繁更新的文档集时性能较差,占用内存大。
HNSWHNSW 基于分层的小世界图结构,通过近似最近邻的方法实现高效向量检索。通过多层图索引和邻居节点搜索,实现快速的近似最近邻查找。适用于向量相似度检索、推荐系统、图像搜索等高维向量场景。构建和维护图的成本较高,尤其是频繁更新和插入向量的情况下。对高精度要求场景不适合。
跳表(Skip List)通过多级链表实现快速搜索,每级链表跳过部分节点,提高查找速度。基于多级链表,可在 O(log n) 时间内查找目标节点。适用于键值存储、顺序查询需求的场景,作为简单替代结构。在超大数据集或高频率更新的场景下不够高效,性能会下降。
LSM 树LSM 树通过分层排序和合并,将数据写入磁盘前缓存在内存,适合高写入量的场景。数据批量写入减少随机写的开销,分层合并提升读取效率。适合写密集型的键值数据库,如 RocksDB、LevelDB。读性能可能较差,特别是大量读操作时,需通过多层合并查询。
Trie 树用树结构保存字符串的前缀,每个节点代表一个字符,用于高效的前缀匹配。可以快速定位字符串的前缀路径,适合匹配和自动补全。适用于自动补全、前缀匹配等需要快速定位前缀的场景。占用内存较大,无法很好地处理非字符串数据,不适合范围查询。
Annoy使用多棵随机 KD 树构建索引,用于近似最近邻查询,查询时从多棵树中查找相似点。随机生成多棵树,提高查找相似度的效率,查询速度快。适用于低维度、高性能要求的向量检索,推荐系统等。处理高维数据时性能较差,存储空间需求大。更新不便。
LSH(Locality-Sensitive Hashing)将相似数据哈希到相同的桶中,快速找到相似数据的近似最近邻索引方法。通过哈希实现分桶,减少相似数据查询范围,查询速度快。适用于低维向量的相似性搜索,处理文本等场景。高维度数据上效率低,近似度精度差,占用较多内存。
+
+

简要总结

    +
  • B+ 树倒排索引 是经典的数据库和全文搜索引擎中常用的索引结构,适合关系型数据和文本数据的检索。
  • +
  • HNSWAnnoy 是面向高维向量的近似最近邻索引结构,常用于推荐系统、语义搜索等场景。
  • +
  • LSM 树 适合写密集的场景,如键值数据库。
  • +
  • Trie 树跳表 更适合简单的键值存储和字符串匹配,但不适合复杂的数据查询和高维向量检索。
  • +
  • LSH 在维度较低的数据上表现较好,但对高维数据表现有限。
  • +
+ +
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2024/12/06/learn-solana-0/index.html b/2024/12/06/learn-solana-0/index.html new file mode 100644 index 0000000..e365527 --- /dev/null +++ b/2024/12/06/learn-solana-0/index.html @@ -0,0 +1,506 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Solana / Anchor 学习笔记 - 账户 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ Solana / Anchor 学习笔记 - 账户 +

+ + +
+ + + + +
+ + +

AccountInfo

+

solana_program::account_info::AccountInfo

+
+

账户信息(AccountInfo),合约中对于账户基本信息的引用(余额和数据可变)。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// Account information
#[derive(Clone)]
#[repr(C)]
pub struct AccountInfo<'a> {
/// Public key of the account
pub key: &'a Pubkey,
/// The lamports in the account. Modifiable by programs.
pub lamports: Rc<RefCell<&'a mut u64>>,
/// The data held in this account. Modifiable by programs.
pub data: Rc<RefCell<&'a mut [u8]>>,
/// Program that owns this account
pub owner: &'a Pubkey,
/// The epoch at which this account will next owe rent
pub rent_epoch: Epoch,
/// Was the transaction signed by this account's public key?
pub is_signer: bool,
/// Is the account writable?
pub is_writable: bool,
/// This account's data contains a loaded program (and is now read-only)
pub executable: bool,
}
+

Account

+

anchor_lang::accounts::account::Account

+
+

账户(Account),对 AccountInfo 的包装,并能够自动反序列化 T

+
1
2
3
4
5
#[derive(Clone)]
pub struct Account<'info, T: AccountSerialize + AccountDeserialize + Clone> {
account: T,
info: &'info AccountInfo<'info>,
}
+

基本功能

    +
  1. 检查 T::owner() 是否等于 info.owner,这就要求 T: Owner。通常使用 #[account] 宏来自动为 T 实现 Owner,并使用 crate::ID() 也就是合约地址作为它的 owner。
  2. +
  3. 此外,它还会保证 !(Account.info.owner == SystemProgram && Account.info.lamports() == 0),即账户所有者不是系统程序(必须是非系统合约),并且账户的 SOL 余额不能为 0。
  4. +
+

AccountLoader

+

anchor_lang::accounts::account_loader::AccountLoader

+
+

账户加载器,用于按需零拷贝反序列化的提取器,类型 T 需要实现 ZeroCopy

+
1
2
3
4
5
#[derive(Clone)]
pub struct AccountLoader<'info, T: ZeroCopy + Owner> {
acc_info: &'info AccountInfo<'info>,
phantom: PhantomData<&'info T>,
}
+

基本功能

load_init: 在初始化时(只能)运行一次,得到一个 RefMut<T>,可读写。
load: 加载只读引用 Ref<T>
load_mut: 加载读写引用 RefMut<T>

+

InterfaceAccount

+

anchor_lang::accounts::interface_account::InterfaceAccount

+
+

接口账户,用来支持 T: Owners 的情况。即有多个程序拥有这种类型的账户数据,引入时是为了支持 token-2022 #2386

+
1
2
3
4
5
6
7
#[derive(Clone)]
pub struct InterfaceAccount<'info, T: AccountSerialize + AccountDeserialize + Clone> {
account: Account<'info, T>,
// The owner here is used to make sure that changes aren't incorrectly propagated
// to an account with a modified owner
owner: Pubkey,
}
+

基本功能

    +
  1. 检查所有者,即 T::owners().contains(InterfaceAccount.info.owner)
  2. +
  3. Account 的第二项。
  4. +
+

Interface

+

anchor_lang::accounts::interface::Interface

+
+

接口,用来表示实现了某种接口的程序中的某一个。

+
1
2
#[derive(Clone)]
pub struct Interface<'info, T>(Program<'info, T>);
+

TODO: 例子。

+

Program

+

anchor_lang::accounts::program::Program

+
+

程序,表示一个程序/合约。

+
1
2
3
4
5
#[derive(Clone)]
pub struct Program<'info, T> {
info: &'info AccountInfo<'info>,
_phantom: PhantomData<T>,
}
+

Signer

+

anchor_lang::accounts::signer::Signer

+
+

签名者,检查 Signer.info.is_signer == true

+
1
2
3
4
#[derive(Debug, Clone)]
pub struct Signer<'info> {
info: &'info AccountInfo<'info>,
}
+

SystemAccount

+

anchor_lang::accounts::system_account::SystemAccount

+
+

系统账户,检查账户的拥有者是不是系统程序(即 SystemAccount.info.owner == SystemProgram)。

+
1
2
3
4
#[derive(Debug, Clone)]
pub struct SystemAccount<'info> {
info: &'info AccountInfo<'info>,
}
+

Sysvar

+

anchor_lang::accounts::sysvar::Sysvar

+
+

系统变量,检查账户是否是系统变量。

+
1
2
3
4
5

pub struct Sysvar<'info, T: solana_program::sysvar::Sysvar> {
info: &'info AccountInfo<'info>,
account: T,
}
+

TODO: 写一篇 post 单独介绍实现了 solana_program::sysvar::Sysvar 的类型。

+

UncheckedAccount

强调不做任何检查的账户,需要手动在合约指令中做检查。

+
1
2
#[derive(Debug, Clone)]
pub struct UncheckedAccount<'info>(&'info AccountInfo<'info>);
+
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2024/12/23/learn-solana-1/index.html b/2024/12/23/learn-solana-1/index.html new file mode 100644 index 0000000..c88c654 --- /dev/null +++ b/2024/12/23/learn-solana-1/index.html @@ -0,0 +1,464 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Solana / Anchor 学习笔记 - PDA 派生 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ Solana / Anchor 学习笔记 - PDA 派生 +

+ + +
+ + + + +
+ + +

Pubkey

1
pub struct Pubkey(pub(crate) [u8; 32]);
+

find_program_address

1
2
3
4
pub fn find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) {
Self::try_find_program_address(seeds, program_id)
.unwrap_or_else(|| panic!("Unable to find a viable program address bump seed"))
}
+

try_find_program_address

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
pub fn try_find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> Option<(Pubkey, u8)> {
// Perform the calculation inline, calling this from within a program is
// not supported
#[cfg(not(target_os = "solana"))]
{
let mut bump_seed = [std::u8::MAX]; // 从 std::u8::MAX 开始尝试
for _ in 0..std::u8::MAX {
{
let mut seeds_with_bump = seeds.to_vec();
seeds_with_bump.push(&bump_seed); // 将 bump 作为种子的最后一部分
match Self::create_program_address(&seeds_with_bump, program_id) {
Ok(address) => return Some((address, bump_seed[0])),
Err(PubkeyError::InvalidSeeds) => (),
_ => break,
}
}
bump_seed[0] -= 1; // 尝试失败了就减 1 再尝试
}
None
}
// Call via a system call to perform the calculation
#[cfg(target_os = "solana")]
{
// ...
}
}
+

create_program_address

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
pub fn create_program_address(
seeds: &[&[u8]],
program_id: &Pubkey,
) -> Result<Pubkey, PubkeyError> {
if seeds.len() > MAX_SEEDS { // 种子最多有 16 个
return Err(PubkeyError::MaxSeedLengthExceeded);
}
for seed in seeds.iter() {
if seed.len() > MAX_SEED_LEN { // 每个种子最长 32
return Err(PubkeyError::MaxSeedLengthExceeded);
}
}

// Perform the calculation inline, calling this from within a program is
// not supported
#[cfg(not(target_os = "solana"))]
{
let mut hasher = crate::hash::Hasher::default(); // 就是一个 Sha256
for seed in seeds.iter() {
hasher.hash(seed);
}
// PDA_MARKER 是一个 21 字节长的字符串
// const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
hasher.hashv(&[program_id.as_ref(), PDA_MARKER]);
let hash = hasher.result();

if bytes_are_curve_point(hash) { // PDA 账户需要确保不在爱德华曲线上
return Err(PubkeyError::InvalidSeeds);
}

Ok(Pubkey::from(hash.to_bytes()))
}
// Call via a system call to perform the calculation
#[cfg(target_os = "solana")]
{
let mut bytes = [0; 32];
let result = unsafe {
crate::syscalls::sol_create_program_address(
seeds as *const _ as *const u8,
seeds.len() as u64,
program_id as *const _ as *const u8,
&mut bytes as *mut _ as *mut u8,
)
};
match result {
crate::entrypoint::SUCCESS => Ok(Pubkey::from(bytes)),
_ => Err(result.into()),
}
}
}
+

bytes_are_curve_point

1
2
3
4
5
6
7
8
9
10
pub fn bytes_are_curve_point<T: AsRef<[u8]>>(_bytes: T) -> bool {
#[cfg(not(target_os = "solana"))]
{
curve25519_dalek::edwards::CompressedEdwardsY::from_slice(_bytes.as_ref())
.decompress()
.is_some()
}
#[cfg(target_os = "solana")]
unimplemented!();
}
+

总结

整体流程

    +
  1. [std::u8::MAX] (即 [255]) 开始作为 bump 尝试,值依次递减至 0,bump 就是最后一个种子。
  2. +
  3. 检查种子数是否不超过 16 (包含 bump,也就是用户能输入的只有 15 个),所有种子长度不超过 32.
  4. +
  5. 使用所有种子依次输入 Sha256 进行哈希,然后将 program_id 及一个 21 字节的常量 PDA_MARKER (b"ProgramDerivedAddress") 输入进行哈希。
  6. +
  7. 计算哈希结果并判断是否在 Curve25519 上,如果不在,表示是合法的 PDA 地址,否则 bump 自减 1 从第二步开始重试。
  8. +
  9. 如果从 [255][0] 都找不到,则返回 None / 报错。
  10. +
+
+

Curve25519 是一种特定的爱德华曲线,它设计用于实现高效、安全的 Diffie-Hellman 密钥交换。

+
+ +
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2024/12/23/learn-solana-2/PoHSequence0.png b/2024/12/23/learn-solana-2/PoHSequence0.png new file mode 100644 index 0000000..fd42df8 Binary files /dev/null and b/2024/12/23/learn-solana-2/PoHSequence0.png differ diff --git a/2024/12/23/learn-solana-2/PoHSequence1.png b/2024/12/23/learn-solana-2/PoHSequence1.png new file mode 100644 index 0000000..1893f2f Binary files /dev/null and b/2024/12/23/learn-solana-2/PoHSequence1.png differ diff --git a/2024/12/23/learn-solana-2/PoHSequence2.png b/2024/12/23/learn-solana-2/PoHSequence2.png new file mode 100644 index 0000000..4593aed Binary files /dev/null and b/2024/12/23/learn-solana-2/PoHSequence2.png differ diff --git a/2024/12/23/learn-solana-2/PoHSequence3.png b/2024/12/23/learn-solana-2/PoHSequence3.png new file mode 100644 index 0000000..38d1f52 Binary files /dev/null and b/2024/12/23/learn-solana-2/PoHSequence3.png differ diff --git a/2024/12/23/learn-solana-2/PoHSequence4.png b/2024/12/23/learn-solana-2/PoHSequence4.png new file mode 100644 index 0000000..8914cc7 Binary files /dev/null and b/2024/12/23/learn-solana-2/PoHSequence4.png differ diff --git a/2024/12/23/learn-solana-2/TransactionFlowThroughoutTheNetwork.png b/2024/12/23/learn-solana-2/TransactionFlowThroughoutTheNetwork.png new file mode 100644 index 0000000..40eb3d7 Binary files /dev/null and b/2024/12/23/learn-solana-2/TransactionFlowThroughoutTheNetwork.png differ diff --git a/2024/12/23/learn-solana-2/index.html b/2024/12/23/learn-solana-2/index.html new file mode 100644 index 0000000..eab57f2 --- /dev/null +++ b/2024/12/23/learn-solana-2/index.html @@ -0,0 +1,528 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Solana / Anchor 学习笔记 - Solana 白皮书(一) | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ Solana / Anchor 学习笔记 - Solana 白皮书(一) +

+ + +
+ + + + +
+ + +
+

PoH (Proof of History) 是⼀种⽤于验证事件之间的顺序和时间流逝的证明。
PoH is a proof for verifying order and passage of time between events.

+
+

1. 简介

区块链本质上是一个容错复制状态机。

+

使用 PoH,网络中的节点可以不依靠其他信任机制验证账本记录的时间流逝。

+
    +
  • 问题
  • +
+
    +
  1. PoH 如何生成可验证的时间间隔。2. PoH 如何生成可验证的事件执行时间。
  2. +
+

2. 提纲

3. 网络设计

    +
  1. 某个节点被选举为领导者(Leader),负责生成历史证明序列(提供网络全局读取一致性以及可验证的时间流逝)。
  2. +
  3. 领导者搜集用户消息并排序。
  4. +
  5. 领导者对内存中的交易进行处理,并将交易和签名发送到验证复制节点(Verifier)。
  6. +
  7. 验证复制节点在其状态副本上执行相同的交易,并发布处理的状态签名,作为交易确认及共识算法投票。
  8. +
+

整体网络的交易流向

+
+

Leader 选举是基于 PoS 实现的。

+
+

4. 历史证明

    +
  1. 历史证明是一个计算序列。
  2. +
  3. 依赖一个加密的安全函数,无法通过输入预测输出,必须完全执行函数才能生成输出。
  4. +
  5. 函数在单个核心上按顺序执行,上一步的输出作为下一步输入,定期记录当前输出及执行的次数。
  6. +
  7. 外部计算机可以通过检查其中的每个序列段来重新计算并验证输出。
  8. +
+
    +
  • 问题
  • +
+

原文有 Data can be timestamped into this sequence by appending the data (or a hash of some data) into the state of the function. 这句话里的 timestamped 应该不是传统意义的时间戳的意思,而是某种反映执行顺序(和次数?)的更本质的标记。也就说,这种机制提供了“时间戳“的实现。

+

4.1 说明

PoH序列

+

只需要每隔一段时间发布哈希和索引的子集,即:

+

PoH序列

+

只要选择的哈希函数是抗冲突的、不可预测结果的,产生的序列就只能由一个计算机线程按顺序计算得到。也就是,如果不从起始值开始计算 N 次,就无法获得索引 N 处的哈希值。

+

哈希的抗冲突、不可预测性和顺序计算,使得我们能够确定,某段序列的产生过程中发生了时间的流逝。

+

历史证明序列

+

4.2 事件时间戳

将数据或数据的哈希通过“组合函数”插入到这个序列中参与计算,则这些附加的数据就拥有了时间顺序。

+

带数据的历史证明序列

+

4.3 验证

可以利用多核,同时验证一个序列的多段。N 个核就可以同时验证 N 段。产生一段序列的预期时间如果是 T,验证时使用的核数是 C,则验证这段序列需要的时间只有 T / C

+

4.4 横向扩容

两个 PoH 生成节点互相观察对方最近一次的状态,加入自己下一步哈希的数据,实现多个 PoH 链的混合 / 同步。

+

通过定期同步生成器,每个生成器都可以处理一部分外部流量,整个系统就可以处理更多事件。

+
    +
  • 问题
  • +
+
    +
  1. 混合的意义是什么,不混合仍然可以横向扩展多个 PoH 链。是为了保证系统整体的一致性,提高防篡改性?
  2. +
+

4.5 一致性

为了防止恶意重排序攻击,客户端在创建事件时,需要获取最近的哈希,把该事件挂到某个 PoH 链上,即为事件打上 PoH 的时间戳。

+

为了防止恶意 PoH 生成器篡改客户端提交的事件关联的哈希,客户端可以提交数据和关联哈希的签名,而不仅仅是数据的签名。

+

客户端提交数据和最近哈希签名到PoH链

+

验证时只需验证两点:

+
    +
  1. 签名是否正确。
  2. +
  3. 关联的哈希在 PoH 链上是否在当前的事件前面。
  4. +
+
1
2
3
4
5
(Signature, PublicKey, hash30a, event3 data) = Event3

Verify(Signature, PublicKey, Event3)

Lookup(hash30a, PoHSequence)
+

4.6 开销

每秒 4000 个哈希产生 160KB 额外数据。

+

这些数据,使用 4000 核的 GPU 需要 0.25-0.75 毫秒进行验证。

+

4.7 攻击

4.7.1 逆序

恶意攻击者需要从第二个事件开始伪造,但是这个延迟已经足够让其他正常节点对于初始序列达成共识。

+

4.7.2 速度

使用多个 PoH 生成器可以增强抗攻击性。

+

一个生成器有高带宽,用来接收并混合事件,另一个高速低带宽,定期与高带宽链同步混合,它会生成辅助序列,让攻击者逆序更难。

+

4.7.3 长程攻击

    +
  • PoH 提供了时间上的不可篡改性。
  • +
  • PoRep 提供了存储数据的真实性证明。
  • +
  • 两者结合提供了对伪造账本的防御能力。
  • +
+

攻击者不仅需要耗费巨大的时间成本来伪造时间戳,还需要在存储上符合 PoRep 的要求。

+ +
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2024/12/24/learn-solana-3/index.html b/2024/12/24/learn-solana-3/index.html new file mode 100644 index 0000000..bf4d82f --- /dev/null +++ b/2024/12/24/learn-solana-3/index.html @@ -0,0 +1,512 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Solana / Anchor 学习笔记 - Solana 白皮书(二) | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ Solana / Anchor 学习笔记 - Solana 白皮书(二) +

+ + +
+ + + + +
+ + +

5. 权益证明共识机制

5.1 描述

这个机制是用来:

+
    +
  1. 快速确认由 PoH 生成器生成的当前序列。
  2. +
  3. 投票选出下一个 PoH 生成器。
  4. +
  5. 惩罚不当行为的验证者。
  6. +
+

这个算法依赖于所有参与节点在规定超时时间内最终接收到的消息。

+

5.2 术语

    +
  • 押金(bonds): 验证者在验证交易时的承诺作为抵押的代币。
  • +
  • 惩罚机制(slashing): 为解决无利害关系问题提出的方案,当不同的分支被发布时,当前分支可以销毁验证者的押金。
  • +
  • 超级多数(super majority): 由押金加权之后,验证者的总票数达到三分之二。这意味着发起攻击的经济成本等同于代币市值的三分之一。
  • +
+
+

无利害关系问题(Nothing at Stake Problem): 在权益证明系统中,验证者不需要消耗物理资源(如电力),而是依赖于其押金(Stake)。因此,如果网络分叉,验证者可以选择在多个分支上投票,因为这样不会有额外成本。他们会试图最大化收益,而不关心网络的最终一致性。

+
+

5.3 质押

用户将代币转账到一个自己名下的质押账户,这笔押金在赎回之前需要保持不动。

+

质押账户有锁定期,锁定期结束才能赎回。

+

超级多数确认了质押交易之后,质押才有效。

+

5.4 投票

    +
  1. PoH 生成器会在预定义好的周期内发布一个自身状态的签名。
  2. +
  3. 每个质押的身份需要发布一份状态的签名,作为投票。
  4. +
  5. 投票只有同意票,没有反对票。
  6. +
  7. 如果在超时时间内,绝对多数达成了共识,这个分支就会被认为是合法的。
  8. +
+

5.5 解质押

如果用户未能按时参与投票,质押的币会被标记为“过期”,旨在强制参与者保持活跃,否则将失去投票资格。

+

N 作为投票失效的次数阈值,会随着过期投票的数量增加而增长,N 的动态调整也可以为网络的自愈能力提供支持,减少对投票延迟的影响。

+

5.6 选举

    +
  1. 检测到 PoH 生成器故障时,新 PoH 生成器选举就会发生。
  2. +
  3. 拥有最高投票权重、更高的公钥地址的验证者会被选为新的 PoH 生成器。
  4. +
  5. 需要超级多数确认,如果在完成确认前新的生成器故障,就重新选择下一个新的 PoH 生成器。
  6. +
  7. 切换选票时,投票的验证者需要在一个更高的 PoH 序列计数器上投票,否则会触发惩罚。
  8. +
  9. 会选择一个次级生成器,用于主生成器故障时快速切换。
  10. +
+

5.7 选举触发条件

5.7.1 分叉的 Proof of History 生成器

    +
  1. 只有当 PoH 生成器的标识被攻破时,才可能发生分叉。
  2. +
  3. 分叉的检测依据是同一个 PoH 标识发布了两个不同的历史记录。
  4. +
+

5.7.2 运行时异常

    +
  1. Bug、硬件故障、人为错误可能导致生成器生成无效状态,并发布与本地验证者不一致的签名。
  2. +
  3. 验证者将通过 Gossip 协议发布正确的签名,这一事件将触发新的选举。
  4. +
  5. 接收无效状态的验证者将收到惩罚。
  6. +
+

5.7.3 网络超时

5.8 惩罚

    +
  1. 恶意投票:对两个不同的序列投票,受质押削减惩罚。
  2. +
  3. 非恶意的竞选序列冲突,投票会被撤销,不会惩罚。
  4. +
  5. 对 PoH 生成器的无效哈希进行投票,受质押削减惩罚。
  6. +
+

5.9 次级选举

    +
  1. 次级选举是在主序列上被提出的。
  2. +
  3. 在超时时间内通过绝对多数投票,次级生成器就当选了。
  4. +
  5. 主生成器通过在序列中插入指示交接的消息,来实现软交接。
  6. +
  7. 如果主生成器发生故障,已当选的次生成器会被作为第一备选。
  8. +
+

5.10 可用性

    +
  1. CAP 中 Solana 选择 A 可用性。
  2. +
  3. 最终一致性:在合理的人类时间范围内保持一定程度的一致性。
  4. +
  5. 利用 PoH(Proof of History)机制提供了时间的客观测量,结合 PoS 投票记录,可以动态地取消不可用验证者的质押,调整网络的活性和一致性需求。
  6. +
  7. 在合理的时间内,随着不可用验证者被逐步取消质押,网络可以逐步恢复到满足 2/3 共识的状态,从而达成一致性。
  8. +
+ +
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2024/12/26/dex-dev-0/index.html b/2024/12/26/dex-dev-0/index.html new file mode 100644 index 0000000..7f39d84 --- /dev/null +++ b/2024/12/26/dex-dev-0/index.html @@ -0,0 +1,573 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DEX 开发笔记 - Raydium 恒定乘积交换合约源码阅读 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+ + + + + +
+

+ DEX 开发笔记 - Raydium 恒定乘积交换合约源码阅读 +

+ + +
+ + + + +
+ + +

Raydium 是一个 Solana 平台上的 DEX,提供了各种代币的流动性池,为用户提供流动性挖矿、购买代币的功能。为了给 DEX 项目引入流动性池、对接 Raydium 的交换协议,对 raydium 恒定乘积交换合约的源码进行深入学习,这也是 Raydium 上最新的标准 AMM。

+
+

Repository: https://github.com/raydium-io/raydium-cp-swap

+
+

流动性池状态及创建时的基本流程

流动性池状态定义如下:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#[account(zero_copy(unsafe))]
#[repr(packed)]
#[derive(Default, Debug)]
pub struct PoolState {
/// 使用的配置,主要包括各种费率,控制标志,拥有者 / 受益者 等信息
pub amm_config: Pubkey,
/// 流动性池创建者
pub pool_creator: Pubkey,
/// Token A 的金库,用来存储流动性 Token 及收益
pub token_0_vault: Pubkey,
/// Token B 的金库,用来存储流动性 Token 及收益
pub token_1_vault: Pubkey,

/// 流动性池 token 会在存入 A 或 B 时发放
/// 流动性池 token 可以被提取为对应的 A 或 B
pub lp_mint: Pubkey,
/// A 的铸币厂
pub token_0_mint: Pubkey,
/// B 的铸币厂
pub token_1_mint: Pubkey,

/// A 使用的 Token 程序,比如旧版本或者 2022
pub token_0_program: Pubkey,
/// B 使用的 Token 程序
pub token_1_program: Pubkey,

/// TWAP 计价账户的地址
pub observation_key: Pubkey,

/// 用于 PDA 签名时使用的 bump
pub auth_bump: u8,
/// Bitwise representation of the state of the pool
/// bit0, 1: disable deposit(vaule is 1), 0: normal
/// bit1, 1: disable withdraw(vaule is 2), 0: normal
/// bit2, 1: disable swap(vaule is 4), 0: normal
pub status: u8,

pub lp_mint_decimals: u8,
/// mint0 and mint1 decimals
pub mint_0_decimals: u8,
pub mint_1_decimals: u8,

/// True circulating supply without burns and lock ups
pub lp_supply: u64,
/// 金库中欠流动性提供者的 A 和 B 的数额.
pub protocol_fees_token_0: u64,
pub protocol_fees_token_1: u64,

pub fund_fees_token_0: u64,
pub fund_fees_token_1: u64,

/// The timestamp allowed for swap in the pool.
pub open_time: u64,
/// recent epoch
pub recent_epoch: u64,
/// padding for future updates
pub padding: [u64; 31],
}
+

初始化流动性池的指令函数签名如下:

+
1
2
3
4
5
6
pub fn initialize(
ctx: Context<Initialize>, // 上下文
init_amount_0: u64, // A 资产初始数额
init_amount_1: u64, // B 资产初始数额
mut open_time: u64, // 开始交易时间
) -> Result<()>;
+

初始化流动性池账户定义如下:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#[derive(Accounts)]
pub struct Initialize<'info> {
/// Address paying to create the pool. Can be anyone
#[account(mut)]
pub creator: Signer<'info>,

/// Which config the pool belongs to.
pub amm_config: Box<Account<'info, AmmConfig>>,

/// CHECK: pool vault and lp mint authority
#[account(
seeds = [
crate::AUTH_SEED.as_bytes(),
],
bump,
)]
pub authority: UncheckedAccount<'info>,

/// CHECK: Initialize an account to store the pool state
/// PDA account:
/// seeds = [
///     POOL_SEED.as_bytes(),
///     amm_config.key().as_ref(),
///     token_0_mint.key().as_ref(),
///     token_1_mint.key().as_ref(),
/// ],
///
/// Or random account: must be signed by cli
#[account(mut)]
pub pool_state: UncheckedAccount<'info>,

/// Token_0 mint, the key must smaller then token_1 mint.
#[account(
constraint = token_0_mint.key() < token_1_mint.key(),
mint::token_program = token_0_program,
)]
pub token_0_mint: Box<InterfaceAccount<'info, Mint>>,

/// Token_1 mint, the key must grater then token_0 mint.
#[account(
mint::token_program = token_1_program,
)]
pub token_1_mint: Box<InterfaceAccount<'info, Mint>>,

/// pool lp mint
#[account(
init,
seeds = [
POOL_LP_MINT_SEED.as_bytes(),
pool_state.key().as_ref(),
],
bump,
mint::decimals = 9,
mint::authority = authority,
payer = creator,
mint::token_program = token_program,
)]
pub lp_mint: Box<InterfaceAccount<'info, Mint>>,

/// payer token0 account
#[account(
mut,
token::mint = token_0_mint,
token::authority = creator,
)]
pub creator_token_0: Box<InterfaceAccount<'info, TokenAccount>>,

/// creator token1 account
#[account(
mut,
token::mint = token_1_mint,
token::authority = creator,
)]
pub creator_token_1: Box<InterfaceAccount<'info, TokenAccount>>,

/// creator lp token account
#[account(
init,
associated_token::mint = lp_mint,
associated_token::authority = creator,
payer = creator,
token::token_program = token_program,
)]
pub creator_lp_token: Box<InterfaceAccount<'info, TokenAccount>>,

/// CHECK: Token_0 vault for the pool, create by contract
#[account(
mut,
seeds = [
POOL_VAULT_SEED.as_bytes(),
pool_state.key().as_ref(),
token_0_mint.key().as_ref()
],
bump,
)]
pub token_0_vault: UncheckedAccount<'info>,

/// CHECK: Token_1 vault for the pool, create by contract
#[account(
mut,
seeds = [
POOL_VAULT_SEED.as_bytes(),
pool_state.key().as_ref(),
token_1_mint.key().as_ref()
],
bump,
)]
pub token_1_vault: UncheckedAccount<'info>,

/// create pool fee account
#[account(
mut,
address= crate::create_pool_fee_reveiver::id(),
)]
pub create_pool_fee: Box<InterfaceAccount<'info, TokenAccount>>,

/// an account to store oracle observations
#[account(
init,
seeds = [
OBSERVATION_SEED.as_bytes(),
pool_state.key().as_ref(),
],
bump,
payer = creator,
space = ObservationState::LEN
)]
pub observation_state: AccountLoader<'info, ObservationState>,

/// Program to create mint account and mint tokens
pub token_program: Program<'info, Token>,
/// Spl token program or token program 2022
pub token_0_program: Interface<'info, TokenInterface>,
/// Spl token program or token program 2022
pub token_1_program: Interface<'info, TokenInterface>,
/// Program to create an ATA for receiving position NFT
pub associated_token_program: Program<'info, AssociatedToken>,
/// To create a new program account
pub system_program: Program<'info, System>,
/// Sysvar for program account
pub rent: Sysvar<'info, Rent>,
}
+

初始化流动性池流程如下:

+
    +
  1. 判断两种资产的 Mint 账户是否合法,判断 AMM 配置中是否关闭创建 Pool。
  2. +
  3. 设定开始交易时间。
  4. +
  5. 创建两种资产的金库。
  6. +
  7. 创建 PoolState 数据账户。
  8. +
  9. 创建 ObservationState 数据账户。
  10. +
  11. 将两种初始资产从创建者账户转账到金库账户。
  12. +
  13. 判断两个金库账户的数额是否合法(实际上只要大于 0 就合法)。
  14. +
  15. 计算流动性值: $liquidity = \sqrt(amount0 * amount1)$。
  16. +
  17. 固定锁定 100 个流动性值: let lock_lp_amount = 100
  18. +
  19. 发放 liquidity - lock_lp_amount 个 LP token 给创建者。
  20. +
  21. 从创建者账户里收取创建费(lamports)到一个专门存放创建费的账户中(地址硬编码在合约里)。
  22. +
  23. 初始化流动性池数据的各个字段。
  24. +
+

资产交换

资产交换分为两类:

+
    +
  1. 基于输入资产: 输入资产额度固定,一部分会作为手续费,一部分作为购买资金输入。
  2. +
  3. 基于输出资产: 输出资产额度固定,需要额外购买一部分输出资产作为手续费,其他作为购买到的资产输出。
  4. +
+

基于输入资产

指令函数签名:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
pub fn swap_base_input(
ctx: Context<Swap>, // 上下文
amount_in: u64, // 输入资产
minimum_amount_out: u64 // 最小输出资产,由调用者按照当前价格及滑点计算得出
) -> Result<()>;

#[derive(Accounts)]
pub struct Swap<'info> {
// 交换者
pub payer: Signer<'info>,

// 流动性池的金库和流动性 token mint 的权限账户
/// CHECK: pool vault and lp mint authority
#[account(
seeds = [
crate::AUTH_SEED.as_bytes(),
],
bump,
)]
pub authority: UncheckedAccount<'info>,

/// 用于读取协议费用
#[account(address = pool_state.load()?.amm_config)]
pub amm_config: Box<Account<'info, AmmConfig>>,

/// 流动性池数据账户
#[account(mut)]
pub pool_state: AccountLoader<'info, PoolState>,

/// 用户购买使用的 token ATA,即输入 token 的 ATA
#[account(mut)]
pub input_token_account: Box<InterfaceAccount<'info, TokenAccount>>,

/// 用户希望购买到的 token ATA,即输出 token 的 ATA
#[account(mut)]
pub output_token_account: Box<InterfaceAccount<'info, TokenAccount>>,

/// 接收用户购买使用的 token 的金库
#[account(
mut,
constraint = input_vault.key() == pool_state.load()?.token_0_vault || input_vault.key() == pool_state.load()?.token_1_vault
)]
pub input_vault: Box<InterfaceAccount<'info, TokenAccount>>,

/// 输出用户希望购买到的 token 的金库
#[account(
mut,
constraint = output_vault.key() == pool_state.load()?.token_0_vault || output_vault.key() == pool_state.load()?.token_1_vault
)]
pub output_vault: Box<InterfaceAccount<'info, TokenAccount>>,

/// 输入 token 的程序(可能是 2022)
pub input_token_program: Interface<'info, TokenInterface>,

/// 输出 token 的程序(可能是 2022)
pub output_token_program: Interface<'info, TokenInterface>,

/// 输入 token 的铸币厂
#[account(
address = input_vault.mint
)]
pub input_token_mint: Box<InterfaceAccount<'info, Mint>>,

/// 输出 token 的铸币厂
#[account(
address = output_vault.mint
)]
pub output_token_mint: Box<InterfaceAccount<'info, Mint>>,

/// 记录价格的数据账户,用于计算 TWAP 价格
#[account(mut, address = pool_state.load()?.observation_key)]
pub observation_state: AccountLoader<'info, ObservationState>,
}
+

基于输入的资产交换流程如下:

+
    +
  1. 检查时间、状态等是否允许交换。
  2. +
  3. 计算转账(应该指的是向金库中转账)费用,从总的输入费用中减去这部分,将剩余部分 actual_amount_in 作为实际购买资金。
  4. +
  5. 计算两个金库扣除协议费用和资金费用之后剩余的部分,即实际上提供流动性的两种资金。
  6. +
  7. 按照上一步计算得到了两种资金,计算两种资金置换另一种的价格,$A / B$ 和 $B / A$,同时转换为 u128 左移 32 位使用定点数来保存精度。
  8. +
  9. 将第 3 步得到的两种资金额度相乘,得到恒定乘积 $A * B$。
  10. +
  11. 计算交换结果,包括各种额度、费用。
  12. +
  13. 将上一步得到的计算结果中的交换后的的源资产额度减去交易费用,再乘以这个结果中交换后的目标资产额度,得到交换后的恒定乘积 $A’ * B’$。
  14. +
  15. 验证第 6 步计算结果中交换的源资产额度是否等于 actual_amount_in
  16. +
  17. 检查第 6 步计算结果中交换得到的目标资产额度减去转账费用之后,是否仍然大于 0 ;同时检查,是否大于等于 minimum_amount_out,如果不满足表示超过滑点限制。
  18. +
  19. 更新流动性池状态的协议费用及资金费用,用于记录金库中非流动性的部分的额度。
  20. +
  21. 发送一个 SwapEvent 事件。(为什么?怎么利用?)
  22. +
  23. 验证交换后的恒定乘积大于等于交换前的恒定乘积,即 $A’ B’ \geq A B$。
  24. +
  25. 根据计算后的结果,将相应额度的输入资产从用户账户转账到金库账户,将相应额度的输出资产从金库账户转账到用户账户。
  26. +
  27. 更新观测状态(ObservationState)的值。
  28. +
+

基于输出资产

TODO

+

观测状态 / TWAP 计价器

观测值及观测状态结构定义如下:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#[zero_copy(unsafe)]
#[repr(packed)]
#[derive(Default, Debug)]
pub struct Observation {
/// The block timestamp of the observation
pub block_timestamp: u64,
/// the cumulative of token0 price during the duration time, Q32.32, the remaining 64 bit for overflow
pub cumulative_token_0_price_x32: u128,
/// the cumulative of token1 price during the duration time, Q32.32, the remaining 64 bit for overflow
pub cumulative_token_1_price_x32: u128,
}

#[account(zero_copy(unsafe))]
#[repr(packed)]
#[cfg_attr(feature = "client", derive(Debug))]
pub struct ObservationState {
/// Whether the ObservationState is initialized
pub initialized: bool,
/// the most-recently updated index of the observations array
pub observation_index: u16,
pub pool_id: Pubkey,
/// 固定长度的环形缓冲区,用于循环写入观测记录
pub observations: [Observation; OBSERVATION_NUM], // OBSERVATION_NUM == 100
/// padding for feature update
pub padding: [u64; 4],
}
+

其中最重要的函数是更新:

+
1
2
3
4
5
6
pub fn update(
&mut self,
block_timestamp: u64,
token_0_price_x32: u128,
token_1_price_x32: u128,
);
+
    +
  1. 该函数将一个 Oracle 观测值写入到当前状态中,并将当前索引自增一模 OBSERVATION_NUM,即循环写入。
  2. +
  3. 该函数一秒钟最多执行一次。
  4. +
  5. 将两种资产的当前价格与跟上一个记录的时间差值相乘,即: $Price * \Delta T$。
  6. +
  7. 然后将上一步求出的两个结果与上一个记录的两个值累加起来 (wrapping_add),作为新记录的两个值。
  8. +
  9. 更新索引。
  10. +
+

原理

TWAP ,即时间加权平均价格(Time Weighted Average Price);用来平滑价格波动,减少大宗交易对市场价格的冲击。

+

计算公式为:

+

即某个特定时间内的 TWAP 为:每小段时间乘以当时的价格求和,除以总时间。

+

设计理由(猜测)

我能想到的另外一种方案是:

+
    +
  1. 观测状态中的每个观测记录,只记录 $Price * \Delta T$ 和时间戳即可。
  2. +
  3. 当要求某段时间的 TWAP 时,将这段时间的所有记录累加,除以总时长即可,时间复杂度 $O(n)$。
  4. +
  5. 这样看起来好像可以避免 wrapping_add,源码中不断累加更可能遇到这种情况。
  6. +
+

而源码在计算某段时间的 TWAP 时,只需要将最后一个记录的值和第一个记录的值的差除以总时间即可,即这种方案时间复杂度只有 $O(1)$ 。而且实际上 wrapping_add 得到的累加值在相减的时候仍然可以得到正确的结果,只要在这段时间内没有溢出两次就行了。

+ +
+ + + + + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..bfe3da0 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +gteng.org diff --git a/archives/2021/01/index.html b/archives/2021/01/index.html new file mode 100644 index 0000000..c33fec5 --- /dev/null +++ b/archives/2021/01/index.html @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2021 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2021/08/index.html b/archives/2021/08/index.html new file mode 100644 index 0000000..4fccd7a --- /dev/null +++ b/archives/2021/08/index.html @@ -0,0 +1,488 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2021 +
+ + + + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2021/09/index.html b/archives/2021/09/index.html new file mode 100644 index 0000000..b556e67 --- /dev/null +++ b/archives/2021/09/index.html @@ -0,0 +1,548 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2021 +
+ + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2021/10/index.html b/archives/2021/10/index.html new file mode 100644 index 0000000..1b1ae38 --- /dev/null +++ b/archives/2021/10/index.html @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2021 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2021/index.html b/archives/2021/index.html new file mode 100644 index 0000000..a26ee44 --- /dev/null +++ b/archives/2021/index.html @@ -0,0 +1,571 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2021 +
+ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2021/page/2/index.html b/archives/2021/page/2/index.html new file mode 100644 index 0000000..784cf39 --- /dev/null +++ b/archives/2021/page/2/index.html @@ -0,0 +1,511 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2021 +
+ + + + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2022/08/index.html b/archives/2022/08/index.html new file mode 100644 index 0000000..cb0db42 --- /dev/null +++ b/archives/2022/08/index.html @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2022 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2022/index.html b/archives/2022/index.html new file mode 100644 index 0000000..d122cdf --- /dev/null +++ b/archives/2022/index.html @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2022 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2023/03/index.html b/archives/2023/03/index.html new file mode 100644 index 0000000..21019e8 --- /dev/null +++ b/archives/2023/03/index.html @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2023 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2023/index.html b/archives/2023/index.html new file mode 100644 index 0000000..d51fd74 --- /dev/null +++ b/archives/2023/index.html @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2023 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2024/04/index.html b/archives/2024/04/index.html new file mode 100644 index 0000000..e938a6e --- /dev/null +++ b/archives/2024/04/index.html @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2024 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2024/05/index.html b/archives/2024/05/index.html new file mode 100644 index 0000000..aec052c --- /dev/null +++ b/archives/2024/05/index.html @@ -0,0 +1,408 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2024 +
+ + + + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2024/11/index.html b/archives/2024/11/index.html new file mode 100644 index 0000000..d6a8939 --- /dev/null +++ b/archives/2024/11/index.html @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2024 +
+ + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2024/12/index.html b/archives/2024/12/index.html new file mode 100644 index 0000000..8ff8482 --- /dev/null +++ b/archives/2024/12/index.html @@ -0,0 +1,468 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2024 +
+ + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2024/index.html b/archives/2024/index.html new file mode 100644 index 0000000..8b04d43 --- /dev/null +++ b/archives/2024/index.html @@ -0,0 +1,548 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2024 +
+ + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/index.html b/archives/index.html new file mode 100644 index 0000000..ca590ce --- /dev/null +++ b/archives/index.html @@ -0,0 +1,574 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2024 +
+ + + + + + + + + + + + + + + + + + +
+ 2023 +
+ + + + +
+
+ + + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/page/2/index.html b/archives/page/2/index.html new file mode 100644 index 0000000..a94bb9b --- /dev/null +++ b/archives/page/2/index.html @@ -0,0 +1,574 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2022 +
+ + +
+ 2021 +
+ + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/page/3/index.html b/archives/page/3/index.html new file mode 100644 index 0000000..272ca27 --- /dev/null +++ b/archives/page/3/index.html @@ -0,0 +1,531 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 归档 | 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + + +
+
+
+ 嗯..! 目前共计 28 篇日志。 继续努力。 +
+ + +
+ 2021 +
+ + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..21b011f --- /dev/null +++ b/css/main.css @@ -0,0 +1,2585 @@ +:root { + --body-bg-color: #eee; + --content-bg-color: #fff; + --card-bg-color: #f5f5f5; + --text-color: #555; + --blockquote-color: #666; + --link-color: #555; + --link-hover-color: #222; + --brand-color: #fff; + --brand-hover-color: #fff; + --table-row-odd-bg-color: #f9f9f9; + --table-row-hover-bg-color: #f5f5f5; + --menu-item-bg-color: #f5f5f5; + --btn-default-bg: #fff; + --btn-default-color: #555; + --btn-default-border-color: #555; + --btn-default-hover-bg: #222; + --btn-default-hover-color: #fff; + --btn-default-hover-border-color: #222; +} +html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} +body { + margin: 0; +} +main { + display: block; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} +a { + background: transparent; +} +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} +b, +strong { + font-weight: bolder; +} +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} +small { + font-size: 80%; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sub { + bottom: -0.25em; +} +sup { + top: -0.5em; +} +img { + border-style: none; +} +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} +button, +input { +/* 1 */ + overflow: visible; +} +button, +select { +/* 1 */ + text-transform: none; +} +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; +} +button::-moz-focus-inner, +[type='button']::-moz-focus-inner, +[type='reset']::-moz-focus-inner, +[type='submit']::-moz-focus-inner { + border-style: none; + padding: 0; +} +button:-moz-focusring, +[type='button']:-moz-focusring, +[type='reset']:-moz-focusring, +[type='submit']:-moz-focusring { + outline: 1px dotted ButtonText; +} +fieldset { + padding: 0.35em 0.75em 0.625em; +} +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} +progress { + vertical-align: baseline; +} +textarea { + overflow: auto; +} +[type='checkbox'], +[type='radio'] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} +[type='number']::-webkit-inner-spin-button, +[type='number']::-webkit-outer-spin-button { + height: auto; +} +[type='search'] { + outline-offset: -2px; /* 2 */ + -webkit-appearance: textfield; /* 1 */ +} +[type='search']::-webkit-search-decoration { + -webkit-appearance: none; +} +::-webkit-file-upload-button { + font: inherit; /* 2 */ + -webkit-appearance: button; /* 1 */ +} +details { + display: block; +} +summary { + display: list-item; +} +template { + display: none; +} +[hidden] { + display: none; +} +::selection { + background: #262a30; + color: #eee; +} +html, +body { + height: 100%; +} +body { + background: var(--body-bg-color); + color: var(--text-color); + font-family: 'Lato', "PingFang SC", "Microsoft YaHei", sans-serif; + font-size: 1em; + line-height: 2; +} +@media (max-width: 991px) { + body { + padding-left: 0 !important; + padding-right: 0 !important; + } +} +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: 'Lato', "PingFang SC", "Microsoft YaHei", sans-serif; + font-weight: bold; + line-height: 1.5; + margin: 20px 0 15px; +} +h1 { + font-size: 1.5em; +} +h2 { + font-size: 1.375em; +} +h3 { + font-size: 1.25em; +} +h4 { + font-size: 1.125em; +} +h5 { + font-size: 1em; +} +h6 { + font-size: 0.875em; +} +p { + margin: 0 0 20px 0; +} +a, +span.exturl { + border-bottom: 1px solid #999; + color: var(--link-color); + outline: 0; + text-decoration: none; + overflow-wrap: break-word; + word-wrap: break-word; + cursor: pointer; +} +a:hover, +span.exturl:hover { + border-bottom-color: var(--link-hover-color); + color: var(--link-hover-color); +} +iframe, +img, +video { + display: block; + margin-left: auto; + margin-right: auto; + max-width: 100%; +} +hr { + background-image: repeating-linear-gradient(-45deg, #ddd, #ddd 4px, transparent 4px, transparent 8px); + border: 0; + height: 3px; + margin: 40px 0; +} +blockquote { + border-left: 4px solid #ddd; + color: var(--blockquote-color); + margin: 0; + padding: 0 15px; +} +blockquote cite::before { + content: '-'; + padding: 0 5px; +} +dt { + font-weight: bold; +} +dd { + margin: 0; + padding: 0; +} +kbd { + background-color: #f5f5f5; + background-image: linear-gradient(#eee, #fff, #eee); + border: 1px solid #ccc; + border-radius: 0.2em; + box-shadow: 0.1em 0.1em 0.2em rgba(0,0,0,0.1); + color: #555; + font-family: inherit; + padding: 0.1em 0.3em; + white-space: nowrap; +} +.table-container { + overflow: auto; +} +table { + border-collapse: collapse; + border-spacing: 0; + font-size: 0.875em; + margin: 0 0 20px 0; + width: 100%; +} +tbody tr:nth-of-type(odd) { + background: var(--table-row-odd-bg-color); +} +tbody tr:hover { + background: var(--table-row-hover-bg-color); +} +caption, +th, +td { + font-weight: normal; + padding: 8px; + vertical-align: middle; +} +th, +td { + border: 1px solid #ddd; + border-bottom: 3px solid #ddd; +} +th { + font-weight: 700; + padding-bottom: 10px; +} +td { + border-bottom-width: 1px; +} +.btn { + background: var(--btn-default-bg); + border: 2px solid var(--btn-default-border-color); + border-radius: 2px; + color: var(--btn-default-color); + display: inline-block; + font-size: 0.875em; + line-height: 2; + padding: 0 20px; + text-decoration: none; + transition-property: background-color; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} +.btn:hover { + background: var(--btn-default-hover-bg); + border-color: var(--btn-default-hover-border-color); + color: var(--btn-default-hover-color); +} +.btn + .btn { + margin: 0 0 8px 8px; +} +.btn .fa-fw { + text-align: left; + width: 1.285714285714286em; +} +.toggle { + line-height: 0; +} +.toggle .toggle-line { + background: #fff; + display: inline-block; + height: 2px; + left: 0; + position: relative; + top: 0; + transition: all 0.4s; + vertical-align: top; + width: 100%; +} +.toggle .toggle-line:not(:first-child) { + margin-top: 3px; +} +.toggle.toggle-arrow .toggle-line-first { + left: 50%; + top: 2px; + transform: rotate(45deg); + width: 50%; +} +.toggle.toggle-arrow .toggle-line-middle { + left: 2px; + width: 90%; +} +.toggle.toggle-arrow .toggle-line-last { + left: 50%; + top: -2px; + transform: rotate(-45deg); + width: 50%; +} +.toggle.toggle-close .toggle-line-first { + transform: rotate(-45deg); + top: 5px; +} +.toggle.toggle-close .toggle-line-middle { + opacity: 0; +} +.toggle.toggle-close .toggle-line-last { + transform: rotate(45deg); + top: -5px; +} +.highlight, +pre { + background: #f7f7f7; + color: #4d4d4c; + line-height: 1.6; + margin: 0 auto 20px; +} +pre, +code { + font-family: consolas, Menlo, monospace, "PingFang SC", "Microsoft YaHei"; +} +code { + background: #eee; + border-radius: 3px; + color: #555; + padding: 2px 4px; + overflow-wrap: break-word; + word-wrap: break-word; +} +.highlight *::selection { + background: #d6d6d6; +} +.highlight pre { + border: 0; + margin: 0; + padding: 10px 0; +} +.highlight table { + border: 0; + margin: 0; + width: auto; +} +.highlight td { + border: 0; + padding: 0; +} +.highlight figcaption { + background: #eff2f3; + color: #4d4d4c; + display: flex; + font-size: 0.875em; + justify-content: space-between; + line-height: 1.2; + padding: 0.5em; +} +.highlight figcaption a { + color: #4d4d4c; +} +.highlight figcaption a:hover { + border-bottom-color: #4d4d4c; +} +.highlight .gutter { + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + user-select: none; +} +.highlight .gutter pre { + background: #eff2f3; + color: #869194; + padding-left: 10px; + padding-right: 10px; + text-align: right; +} +.highlight .code pre { + background: #f7f7f7; + padding-left: 10px; + width: 100%; +} +.gist table { + width: auto; +} +.gist table td { + border: 0; +} +pre { + overflow: auto; + padding: 10px; +} +pre code { + background: none; + color: #4d4d4c; + font-size: 0.875em; + padding: 0; + text-shadow: none; +} +pre .deletion { + background: #fdd; +} +pre .addition { + background: #dfd; +} +pre .meta { + color: #eab700; + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + user-select: none; +} +pre .comment { + color: #8e908c; +} +pre .variable, +pre .attribute, +pre .tag, +pre .name, +pre .regexp, +pre .ruby .constant, +pre .xml .tag .title, +pre .xml .pi, +pre .xml .doctype, +pre .html .doctype, +pre .css .id, +pre .css .class, +pre .css .pseudo { + color: #c82829; +} +pre .number, +pre .preprocessor, +pre .built_in, +pre .builtin-name, +pre .literal, +pre .params, +pre .constant, +pre .command { + color: #f5871f; +} +pre .ruby .class .title, +pre .css .rules .attribute, +pre .string, +pre .symbol, +pre .value, +pre .inheritance, +pre .header, +pre .ruby .symbol, +pre .xml .cdata, +pre .special, +pre .formula { + color: #718c00; +} +pre .title, +pre .css .hexcolor { + color: #3e999f; +} +pre .function, +pre .python .decorator, +pre .python .title, +pre .ruby .function .title, +pre .ruby .title .keyword, +pre .perl .sub, +pre .javascript .title, +pre .coffeescript .title { + color: #4271ae; +} +pre .keyword, +pre .javascript .function { + color: #8959a8; +} +.blockquote-center { + border-left: none; + margin: 40px 0; + padding: 0; + position: relative; + text-align: center; +} +.blockquote-center .fa { + display: block; + opacity: 0.6; + position: absolute; + width: 100%; +} +.blockquote-center .fa-quote-left { + border-top: 1px solid #ccc; + text-align: left; + top: -20px; +} +.blockquote-center .fa-quote-right { + border-bottom: 1px solid #ccc; + text-align: right; + bottom: -20px; +} +.blockquote-center p, +.blockquote-center div { + text-align: center; +} +.post-body .group-picture img { + margin: 0 auto; + padding: 0 3px; +} +.group-picture-row { + margin-bottom: 6px; + overflow: hidden; +} +.group-picture-column { + float: left; + margin-bottom: 10px; +} +.post-body .label { + color: #555; + display: inline; + padding: 0 2px; +} +.post-body .label.default { + background: #f0f0f0; +} +.post-body .label.primary { + background: #efe6f7; +} +.post-body .label.info { + background: #e5f2f8; +} +.post-body .label.success { + background: #e7f4e9; +} +.post-body .label.warning { + background: #fcf6e1; +} +.post-body .label.danger { + background: #fae8eb; +} +.post-body .tabs { + margin-bottom: 20px; +} +.post-body .tabs, +.tabs-comment { + display: block; + padding-top: 10px; + position: relative; +} +.post-body .tabs ul.nav-tabs, +.tabs-comment ul.nav-tabs { + display: flex; + flex-wrap: wrap; + margin: 0; + margin-bottom: -1px; + padding: 0; +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs, + .tabs-comment ul.nav-tabs { + display: block; + margin-bottom: 5px; + } +} +.post-body .tabs ul.nav-tabs li.tab, +.tabs-comment ul.nav-tabs li.tab { + border-bottom: 1px solid #ddd; + border-left: 1px solid transparent; + border-right: 1px solid transparent; + border-top: 3px solid transparent; + flex-grow: 1; + list-style-type: none; + border-radius: 0 0 0 0; +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs li.tab, + .tabs-comment ul.nav-tabs li.tab { + border-bottom: 1px solid transparent; + border-left: 3px solid transparent; + border-right: 1px solid transparent; + border-top: 1px solid transparent; + } +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs li.tab, + .tabs-comment ul.nav-tabs li.tab { + border-radius: 0; + } +} +.post-body .tabs ul.nav-tabs li.tab a, +.tabs-comment ul.nav-tabs li.tab a { + border-bottom: initial; + display: block; + line-height: 1.8; + outline: 0; + padding: 0.25em 0.75em; + text-align: center; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-out; +} +.post-body .tabs ul.nav-tabs li.tab a i, +.tabs-comment ul.nav-tabs li.tab a i { + width: 1.285714285714286em; +} +.post-body .tabs ul.nav-tabs li.tab.active, +.tabs-comment ul.nav-tabs li.tab.active { + border-bottom: 1px solid transparent; + border-left: 1px solid #ddd; + border-right: 1px solid #ddd; + border-top: 3px solid #fc6423; +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs li.tab.active, + .tabs-comment ul.nav-tabs li.tab.active { + border-bottom: 1px solid #ddd; + border-left: 3px solid #fc6423; + border-right: 1px solid #ddd; + border-top: 1px solid #ddd; + } +} +.post-body .tabs ul.nav-tabs li.tab.active a, +.tabs-comment ul.nav-tabs li.tab.active a { + color: var(--link-color); + cursor: default; +} +.post-body .tabs .tab-content .tab-pane, +.tabs-comment .tab-content .tab-pane { + border: 1px solid #ddd; + border-top: 0; + padding: 20px 20px 0 20px; + border-radius: 0; +} +.post-body .tabs .tab-content .tab-pane:not(.active), +.tabs-comment .tab-content .tab-pane:not(.active) { + display: none; +} +.post-body .tabs .tab-content .tab-pane.active, +.tabs-comment .tab-content .tab-pane.active { + display: block; +} +.post-body .tabs .tab-content .tab-pane.active:nth-of-type(1), +.tabs-comment .tab-content .tab-pane.active:nth-of-type(1) { + border-radius: 0 0 0 0; +} +@media (max-width: 413px) { + .post-body .tabs .tab-content .tab-pane.active:nth-of-type(1), + .tabs-comment .tab-content .tab-pane.active:nth-of-type(1) { + border-radius: 0; + } +} +.post-body .note { + border-radius: 3px; + margin-bottom: 20px; + padding: 1em; + position: relative; + border: 1px solid #eee; + border-left-width: 5px; +} +.post-body .note h2, +.post-body .note h3, +.post-body .note h4, +.post-body .note h5, +.post-body .note h6 { + margin-top: 0; + border-bottom: initial; + margin-bottom: 0; + padding-top: 0; +} +.post-body .note p:first-child, +.post-body .note ul:first-child, +.post-body .note ol:first-child, +.post-body .note table:first-child, +.post-body .note pre:first-child, +.post-body .note blockquote:first-child, +.post-body .note img:first-child { + margin-top: 0; +} +.post-body .note p:last-child, +.post-body .note ul:last-child, +.post-body .note ol:last-child, +.post-body .note table:last-child, +.post-body .note pre:last-child, +.post-body .note blockquote:last-child, +.post-body .note img:last-child { + margin-bottom: 0; +} +.post-body .note.default { + border-left-color: #777; +} +.post-body .note.default h2, +.post-body .note.default h3, +.post-body .note.default h4, +.post-body .note.default h5, +.post-body .note.default h6 { + color: #777; +} +.post-body .note.primary { + border-left-color: #6f42c1; +} +.post-body .note.primary h2, +.post-body .note.primary h3, +.post-body .note.primary h4, +.post-body .note.primary h5, +.post-body .note.primary h6 { + color: #6f42c1; +} +.post-body .note.info { + border-left-color: #428bca; +} +.post-body .note.info h2, +.post-body .note.info h3, +.post-body .note.info h4, +.post-body .note.info h5, +.post-body .note.info h6 { + color: #428bca; +} +.post-body .note.success { + border-left-color: #5cb85c; +} +.post-body .note.success h2, +.post-body .note.success h3, +.post-body .note.success h4, +.post-body .note.success h5, +.post-body .note.success h6 { + color: #5cb85c; +} +.post-body .note.warning { + border-left-color: #f0ad4e; +} +.post-body .note.warning h2, +.post-body .note.warning h3, +.post-body .note.warning h4, +.post-body .note.warning h5, +.post-body .note.warning h6 { + color: #f0ad4e; +} +.post-body .note.danger { + border-left-color: #d9534f; +} +.post-body .note.danger h2, +.post-body .note.danger h3, +.post-body .note.danger h4, +.post-body .note.danger h5, +.post-body .note.danger h6 { + color: #d9534f; +} +.pagination .prev, +.pagination .next, +.pagination .page-number, +.pagination .space { + display: inline-block; + margin: 0 10px; + padding: 0 11px; + position: relative; + top: -1px; +} +@media (max-width: 767px) { + .pagination .prev, + .pagination .next, + .pagination .page-number, + .pagination .space { + margin: 0 5px; + } +} +.pagination { + border-top: 1px solid #eee; + margin: 120px 0 0; + text-align: center; +} +.pagination .prev, +.pagination .next, +.pagination .page-number { + border-bottom: 0; + border-top: 1px solid #eee; + transition-property: border-color; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} +.pagination .prev:hover, +.pagination .next:hover, +.pagination .page-number:hover { + border-top-color: #222; +} +.pagination .space { + margin: 0; + padding: 0; +} +.pagination .prev { + margin-left: 0; +} +.pagination .next { + margin-right: 0; +} +.pagination .page-number.current { + background: #ccc; + border-top-color: #ccc; + color: #fff; +} +@media (max-width: 767px) { + .pagination { + border-top: none; + } + .pagination .prev, + .pagination .next, + .pagination .page-number { + border-bottom: 1px solid #eee; + border-top: 0; + margin-bottom: 10px; + padding: 0 10px; + } + .pagination .prev:hover, + .pagination .next:hover, + .pagination .page-number:hover { + border-bottom-color: #222; + } +} +.comments { + margin-top: 60px; + overflow: hidden; +} +.comment-button-group { + display: flex; + flex-wrap: wrap-reverse; + justify-content: center; + margin: 1em 0; +} +.comment-button-group .comment-button { + margin: 0.1em 0.2em; +} +.comment-button-group .comment-button.active { + background: var(--btn-default-hover-bg); + border-color: var(--btn-default-hover-border-color); + color: var(--btn-default-hover-color); +} +.comment-position { + display: none; +} +.comment-position.active { + display: block; +} +.tabs-comment { + background: var(--content-bg-color); + margin-top: 4em; + padding-top: 0; +} +.tabs-comment .comments { + border: 0; + box-shadow: none; + margin-top: 0; + padding-top: 0; +} +.container { + min-height: 100%; + position: relative; +} +.main-inner { + margin: 0 auto; + width: calc(100% - 20px); +} +@media (min-width: 1200px) { + .main-inner { + width: 1160px; + } +} +@media (min-width: 1600px) { + .main-inner { + width: 73%; + } +} +@media (max-width: 767px) { + .content-wrap { + padding: 0 20px; + } +} +.header { + background: transparent; +} +.header-inner { + margin: 0 auto; + width: calc(100% - 20px); +} +@media (min-width: 1200px) { + .header-inner { + width: 1160px; + } +} +@media (min-width: 1600px) { + .header-inner { + width: 73%; + } +} +.site-brand-container { + display: flex; + flex-shrink: 0; + padding: 0 10px; +} +.headband { + background: #222; + height: 3px; +} +.site-meta { + flex-grow: 1; + text-align: center; +} +@media (max-width: 767px) { + .site-meta { + text-align: center; + } +} +.brand { + border-bottom: none; + color: var(--brand-color); + display: inline-block; + line-height: 1.375em; + padding: 0 40px; + position: relative; +} +.brand:hover { + color: var(--brand-hover-color); +} +.site-title { + font-family: 'Lato', "PingFang SC", "Microsoft YaHei", sans-serif; + font-size: 1.375em; + font-weight: normal; + margin: 0; +} +.site-subtitle { + color: #ddd; + font-size: 0.8125em; + margin: 10px 0; +} +.use-motion .brand { + opacity: 0; +} +.use-motion .site-title, +.use-motion .site-subtitle, +.use-motion .custom-logo-image { + opacity: 0; + position: relative; + top: -10px; +} +.site-nav-toggle, +.site-nav-right { + display: none; +} +@media (max-width: 767px) { + .site-nav-toggle, + .site-nav-right { + display: flex; + flex-direction: column; + justify-content: center; + } +} +.site-nav-toggle .toggle, +.site-nav-right .toggle { + color: var(--text-color); + padding: 10px; + width: 22px; +} +.site-nav-toggle .toggle .toggle-line, +.site-nav-right .toggle .toggle-line { + background: var(--text-color); + border-radius: 1px; +} +.site-nav { + display: block; +} +@media (max-width: 767px) { + .site-nav { + clear: both; + display: none; + } +} +.site-nav.site-nav-on { + display: block; +} +.menu { + margin-top: 20px; + padding-left: 0; + text-align: center; +} +.menu-item { + display: inline-block; + list-style: none; + margin: 0 10px; +} +@media (max-width: 767px) { + .menu-item { + display: block; + margin-top: 10px; + } + .menu-item.menu-item-search { + display: none; + } +} +.menu-item a, +.menu-item span.exturl { + border-bottom: 0; + display: block; + font-size: 0.8125em; + transition-property: border-color; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} +@media (hover: none) { + .menu-item a:hover, + .menu-item span.exturl:hover { + border-bottom-color: transparent !important; + } +} +.menu-item .fa, +.menu-item .fab, +.menu-item .far, +.menu-item .fas { + margin-right: 8px; +} +.menu-item .badge { + display: inline-block; + font-weight: bold; + line-height: 1; + margin-left: 0.35em; + margin-top: 0.35em; + text-align: center; + white-space: nowrap; +} +@media (max-width: 767px) { + .menu-item .badge { + float: right; + margin-left: 0; + } +} +.menu-item-active a, +.menu .menu-item a:hover, +.menu .menu-item span.exturl:hover { + background: var(--menu-item-bg-color); +} +.use-motion .menu-item { + opacity: 0; +} +.sidebar { + background: #222; + bottom: 0; + box-shadow: inset 0 2px 6px #000; + position: fixed; + top: 0; +} +@media (max-width: 991px) { + .sidebar { + display: none; + } +} +.sidebar-inner { + color: #999; + padding: 18px 10px; + text-align: center; +} +.cc-license { + margin-top: 10px; + text-align: center; +} +.cc-license .cc-opacity { + border-bottom: none; + opacity: 0.7; +} +.cc-license .cc-opacity:hover { + opacity: 0.9; +} +.cc-license img { + display: inline-block; +} +.site-author-image { + border: 1px solid #eee; + display: block; + margin: 0 auto; + max-width: 120px; + padding: 2px; +} +.site-author-name { + color: var(--text-color); + font-weight: 600; + margin: 0; + text-align: center; +} +.site-description { + color: #999; + font-size: 0.8125em; + margin-top: 0; + text-align: center; +} +.links-of-author { + margin-top: 15px; +} +.links-of-author a, +.links-of-author span.exturl { + border-bottom-color: #555; + display: inline-block; + font-size: 0.8125em; + margin-bottom: 10px; + margin-right: 10px; + vertical-align: middle; +} +.links-of-author a::before, +.links-of-author span.exturl::before { + background: #ff6e78; + border-radius: 50%; + content: ' '; + display: inline-block; + height: 4px; + margin-right: 3px; + vertical-align: middle; + width: 4px; +} +.sidebar-button { + margin-top: 15px; +} +.sidebar-button a { + border: 1px solid #fc6423; + border-radius: 4px; + color: #fc6423; + display: inline-block; + padding: 0 15px; +} +.sidebar-button a .fa, +.sidebar-button a .fab, +.sidebar-button a .far, +.sidebar-button a .fas { + margin-right: 5px; +} +.sidebar-button a:hover { + background: #fc6423; + border: 1px solid #fc6423; + color: #fff; +} +.sidebar-button a:hover .fa, +.sidebar-button a:hover .fab, +.sidebar-button a:hover .far, +.sidebar-button a:hover .fas { + color: #fff; +} +.links-of-blogroll { + font-size: 0.8125em; + margin-top: 10px; +} +.links-of-blogroll-title { + font-size: 0.875em; + font-weight: 600; + margin-top: 0; +} +.links-of-blogroll-list { + list-style: none; + margin: 0; + padding: 0; +} +#sidebar-dimmer { + display: none; +} +@media (max-width: 767px) { + #sidebar-dimmer { + background: #000; + display: block; + height: 100%; + left: 100%; + opacity: 0; + position: fixed; + top: 0; + width: 100%; + z-index: 1100; + } + .sidebar-active + #sidebar-dimmer { + opacity: 0.7; + transform: translateX(-100%); + transition: opacity 0.5s; + } +} +.sidebar-nav { + margin: 0; + padding-bottom: 20px; + padding-left: 0; +} +.sidebar-nav li { + border-bottom: 1px solid transparent; + color: var(--text-color); + cursor: pointer; + display: inline-block; + font-size: 0.875em; +} +.sidebar-nav li.sidebar-nav-overview { + margin-left: 10px; +} +.sidebar-nav li:hover { + color: #fc6423; +} +.sidebar-nav .sidebar-nav-active { + border-bottom-color: #fc6423; + color: #fc6423; +} +.sidebar-nav .sidebar-nav-active:hover { + color: #fc6423; +} +.sidebar-panel { + display: none; + overflow-x: hidden; + overflow-y: auto; +} +.sidebar-panel-active { + display: block; +} +.sidebar-toggle { + background: #222; + bottom: 45px; + cursor: pointer; + height: 14px; + left: 30px; + padding: 5px; + position: fixed; + width: 14px; + z-index: 1300; +} +@media (max-width: 991px) { + .sidebar-toggle { + left: 20px; + opacity: 0.8; + display: none; + } +} +.sidebar-toggle:hover .toggle-line { + background: #fc6423; +} +.post-toc { + font-size: 0.875em; +} +.post-toc ol { + list-style: none; + margin: 0; + padding: 0 2px 5px 10px; + text-align: left; +} +.post-toc ol > ol { + padding-left: 0; +} +.post-toc ol a { + transition-property: all; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} +.post-toc .nav-item { + line-height: 1.8; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.post-toc .nav .nav-child { + display: none; +} +.post-toc .nav .active > .nav-child { + display: block; +} +.post-toc .nav .active-current > .nav-child { + display: block; +} +.post-toc .nav .active-current > .nav-child > .nav-item { + display: block; +} +.post-toc .nav .active > a { + border-bottom-color: #fc6423; + color: #fc6423; +} +.post-toc .nav .active-current > a { + color: #fc6423; +} +.post-toc .nav .active-current > a:hover { + color: #fc6423; +} +.site-state { + display: flex; + justify-content: center; + line-height: 1.4; + margin-top: 10px; + overflow: hidden; + text-align: center; + white-space: nowrap; +} +.site-state-item { + padding: 0 15px; +} +.site-state-item:not(:first-child) { + border-left: 1px solid #eee; +} +.site-state-item a { + border-bottom: none; +} +.site-state-item-count { + display: block; + font-size: 1em; + font-weight: 600; + text-align: center; +} +.site-state-item-name { + color: #999; + font-size: 0.8125em; +} +.footer { + color: #999; + font-size: 0.875em; + padding: 20px 0; +} +.footer.footer-fixed { + bottom: 0; + left: 0; + position: absolute; + right: 0; +} +.footer-inner { + box-sizing: border-box; + margin: 0 auto; + text-align: center; + width: calc(100% - 20px); +} +@media (min-width: 1200px) { + .footer-inner { + width: 1160px; + } +} +@media (min-width: 1600px) { + .footer-inner { + width: 73%; + } +} +.languages { + display: inline-block; + font-size: 1.125em; + position: relative; +} +.languages .lang-select-label span { + margin: 0 0.5em; +} +.languages .lang-select { + height: 100%; + left: 0; + opacity: 0; + position: absolute; + top: 0; + width: 100%; +} +.with-love { + color: #ff0000; + display: inline-block; + margin: 0 5px; +} +.powered-by, +.theme-info { + display: inline-block; +} +@-moz-keyframes iconAnimate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.9); + } + 20%, 40%, 60%, 80% { + transform: scale(1.1); + } + 50%, 70% { + transform: scale(1.1); + } +} +@-webkit-keyframes iconAnimate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.9); + } + 20%, 40%, 60%, 80% { + transform: scale(1.1); + } + 50%, 70% { + transform: scale(1.1); + } +} +@-o-keyframes iconAnimate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.9); + } + 20%, 40%, 60%, 80% { + transform: scale(1.1); + } + 50%, 70% { + transform: scale(1.1); + } +} +@keyframes iconAnimate { + 0%, 100% { + transform: scale(1); + } + 10%, 30% { + transform: scale(0.9); + } + 20%, 40%, 60%, 80% { + transform: scale(1.1); + } + 50%, 70% { + transform: scale(1.1); + } +} +.back-to-top { + font-size: 12px; + text-align: center; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} +.back-to-top { + background: #222; + bottom: -100px; + box-sizing: border-box; + color: #fff; + cursor: pointer; + left: 30px; + opacity: 0.6; + padding: 0 6px; + position: fixed; + transition-property: bottom; + z-index: 1300; + width: 24px; +} +.back-to-top span { + display: none; +} +.back-to-top:hover { + color: #fc6423; +} +.back-to-top.back-to-top-on { + bottom: 30px; +} +@media (max-width: 991px) { + .back-to-top { + left: 20px; + opacity: 0.8; + } +} +.reading-progress-bar { + background: #000000; + display: block; + height: 3px; + left: 0; + position: fixed; + width: 0; + z-index: 1500; + top: 0; +} +.post-body { + font-family: 'Lato', "PingFang SC", "Microsoft YaHei", sans-serif; + overflow-wrap: break-word; + word-wrap: break-word; +} +@media (min-width: 1200px) { + .post-body { + font-size: 1.125em; + } +} +.post-body .exturl .fa { + font-size: 0.875em; + margin-left: 4px; +} +.post-body .image-caption, +.post-body .figure .caption { + color: #999; + font-size: 0.875em; + font-weight: bold; + line-height: 1; + margin: -20px auto 15px; + text-align: center; +} +.post-sticky-flag { + display: inline-block; + transform: rotate(30deg); +} +.post-button { + margin-top: 40px; + text-align: center; +} +.use-motion .post-block, +.use-motion .pagination, +.use-motion .comments { + opacity: 0; +} +.use-motion .post-header { + opacity: 0; +} +.use-motion .post-body { + opacity: 0; +} +.use-motion .collection-header { + opacity: 0; +} +.posts-collapse { + margin-left: 35px; + position: relative; +} +@media (max-width: 767px) { + .posts-collapse { + margin-left: 0px; + margin-right: 0px; + } +} +.posts-collapse .collection-title { + font-size: 1.125em; + position: relative; +} +.posts-collapse .collection-title::before { + background: #999; + border: 1px solid #fff; + border-radius: 50%; + content: ' '; + height: 10px; + left: 0; + margin-left: -6px; + margin-top: -4px; + position: absolute; + top: 50%; + width: 10px; +} +.posts-collapse .collection-year { + font-size: 1.5em; + font-weight: bold; + margin: 60px 0; + position: relative; +} +.posts-collapse .collection-year::before { + background: #bbb; + border-radius: 50%; + content: ' '; + height: 8px; + left: 0; + margin-left: -4px; + margin-top: -4px; + position: absolute; + top: 50%; + width: 8px; +} +.posts-collapse .collection-header { + display: block; + margin: 0 0 0 20px; +} +.posts-collapse .collection-header small { + color: #bbb; + margin-left: 5px; +} +.posts-collapse .post-header { + border-bottom: 1px dashed #ccc; + margin: 30px 0; + padding-left: 15px; + position: relative; + transition-property: border; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} +.posts-collapse .post-header::before { + background: #bbb; + border: 1px solid #fff; + border-radius: 50%; + content: ' '; + height: 6px; + left: 0; + margin-left: -4px; + position: absolute; + top: 0.75em; + transition-property: background; + width: 6px; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} +.posts-collapse .post-header:hover { + border-bottom-color: #666; +} +.posts-collapse .post-header:hover::before { + background: #222; +} +.posts-collapse .post-meta { + display: inline; + font-size: 0.75em; + margin-right: 10px; +} +.posts-collapse .post-title { + display: inline; +} +.posts-collapse .post-title a, +.posts-collapse .post-title span.exturl { + border-bottom: none; + color: var(--link-color); +} +.posts-collapse .post-title .fa-external-link-alt { + font-size: 0.875em; + margin-left: 5px; +} +.posts-collapse::before { + background: #f5f5f5; + content: ' '; + height: 100%; + left: 0; + margin-left: -2px; + position: absolute; + top: 1.25em; + width: 4px; +} +.post-eof { + background: #ccc; + height: 1px; + margin: 80px auto 60px; + text-align: center; + width: 8%; +} +.post-block:last-of-type .post-eof { + display: none; +} +.content { + padding-top: 40px; +} +@media (min-width: 992px) { + .post-body { + text-align: justify; + } +} +@media (max-width: 991px) { + .post-body { + text-align: justify; + } +} +.post-body h1, +.post-body h2, +.post-body h3, +.post-body h4, +.post-body h5, +.post-body h6 { + padding-top: 10px; +} +.post-body h1 .header-anchor, +.post-body h2 .header-anchor, +.post-body h3 .header-anchor, +.post-body h4 .header-anchor, +.post-body h5 .header-anchor, +.post-body h6 .header-anchor { + border-bottom-style: none; + color: #ccc; + float: right; + margin-left: 10px; + visibility: hidden; +} +.post-body h1 .header-anchor:hover, +.post-body h2 .header-anchor:hover, +.post-body h3 .header-anchor:hover, +.post-body h4 .header-anchor:hover, +.post-body h5 .header-anchor:hover, +.post-body h6 .header-anchor:hover { + color: inherit; +} +.post-body h1:hover .header-anchor, +.post-body h2:hover .header-anchor, +.post-body h3:hover .header-anchor, +.post-body h4:hover .header-anchor, +.post-body h5:hover .header-anchor, +.post-body h6:hover .header-anchor { + visibility: visible; +} +.post-body iframe, +.post-body img, +.post-body video { + margin-bottom: 20px; +} +.post-body .video-container { + height: 0; + margin-bottom: 20px; + overflow: hidden; + padding-top: 75%; + position: relative; + width: 100%; +} +.post-body .video-container iframe, +.post-body .video-container object, +.post-body .video-container embed { + height: 100%; + left: 0; + margin: 0; + position: absolute; + top: 0; + width: 100%; +} +.post-gallery { + align-items: center; + display: grid; + grid-gap: 10px; + grid-template-columns: 1fr 1fr 1fr; + margin-bottom: 20px; +} +@media (max-width: 767px) { + .post-gallery { + grid-template-columns: 1fr 1fr; + } +} +.post-gallery a { + border: 0; +} +.post-gallery img { + margin: 0; +} +.posts-expand .post-header { + font-size: 1.125em; +} +.posts-expand .post-title { + font-size: 1.5em; + font-weight: normal; + margin: initial; + text-align: center; + overflow-wrap: break-word; + word-wrap: break-word; +} +.posts-expand .post-title-link { + border-bottom: none; + color: var(--link-color); + display: inline-block; + position: relative; + vertical-align: top; +} +.posts-expand .post-title-link::before { + background: var(--link-color); + bottom: 0; + content: ''; + height: 2px; + left: 0; + position: absolute; + transform: scaleX(0); + visibility: hidden; + width: 100%; + transition-delay: 0s; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; +} +.posts-expand .post-title-link:hover::before { + transform: scaleX(1); + visibility: visible; +} +.posts-expand .post-title-link .fa-external-link-alt { + font-size: 0.875em; + margin-left: 5px; +} +.posts-expand .post-meta { + color: #999; + font-family: 'Lato', "PingFang SC", "Microsoft YaHei", sans-serif; + font-size: 0.75em; + margin: 3px 0 60px 0; + text-align: center; +} +.posts-expand .post-meta .post-description { + font-size: 0.875em; + margin-top: 2px; +} +.posts-expand .post-meta time { + border-bottom: 1px dashed #999; + cursor: pointer; +} +.post-meta .post-meta-item + .post-meta-item::before { + content: '|'; + margin: 0 0.5em; +} +.post-meta-divider { + margin: 0 0.5em; +} +.post-meta-item-icon { + margin-right: 3px; +} +@media (max-width: 991px) { + .post-meta-item-icon { + display: inline-block; + } +} +@media (max-width: 991px) { + .post-meta-item-text { + display: none; + } +} +.post-nav { + border-top: 1px solid #eee; + display: flex; + justify-content: space-between; + margin-top: 15px; + padding: 10px 5px 0; +} +.post-nav-item { + flex: 1; +} +.post-nav-item a { + border-bottom: none; + display: block; + font-size: 0.875em; + line-height: 1.6; + position: relative; +} +.post-nav-item a:active { + top: 2px; +} +.post-nav-item .fa { + font-size: 0.75em; +} +.post-nav-item:first-child { + margin-right: 15px; +} +.post-nav-item:first-child .fa { + margin-right: 5px; +} +.post-nav-item:last-child { + margin-left: 15px; + text-align: right; +} +.post-nav-item:last-child .fa { + margin-left: 5px; +} +.rtl.post-body p, +.rtl.post-body a, +.rtl.post-body h1, +.rtl.post-body h2, +.rtl.post-body h3, +.rtl.post-body h4, +.rtl.post-body h5, +.rtl.post-body h6, +.rtl.post-body li, +.rtl.post-body ul, +.rtl.post-body ol { + direction: rtl; + font-family: UKIJ Ekran; +} +.rtl.post-title { + font-family: UKIJ Ekran; +} +.post-tags { + margin-top: 40px; + text-align: center; +} +.post-tags a { + display: inline-block; + font-size: 0.8125em; +} +.post-tags a:not(:last-child) { + margin-right: 10px; +} +.post-widgets { + border-top: 1px solid #eee; + margin-top: 15px; + text-align: center; +} +.wp_rating { + height: 20px; + line-height: 20px; + margin-top: 10px; + padding-top: 6px; + text-align: center; +} +.social-like { + display: flex; + font-size: 0.875em; + justify-content: center; + text-align: center; +} +.reward-container { + margin: 20px auto; + padding: 10px 0; + text-align: center; + width: 90%; +} +.reward-container button { + background: transparent; + border: 1px solid #fc6423; + border-radius: 0; + color: #fc6423; + cursor: pointer; + line-height: 2; + outline: 0; + padding: 0 15px; + vertical-align: text-top; +} +.reward-container button:hover { + background: #fc6423; + border: 1px solid transparent; + color: #fa9366; +} +#qr { + padding-top: 20px; +} +#qr a { + border: 0; +} +#qr img { + display: inline-block; + margin: 0.8em 2em 0 2em; + max-width: 100%; + width: 180px; +} +#qr p { + text-align: center; +} +.category-all-page .category-all-title { + text-align: center; +} +.category-all-page .category-all { + margin-top: 20px; +} +.category-all-page .category-list { + list-style: none; + margin: 0; + padding: 0; +} +.category-all-page .category-list-item { + margin: 5px 10px; +} +.category-all-page .category-list-count { + color: #bbb; +} +.category-all-page .category-list-count::before { + content: ' ('; + display: inline; +} +.category-all-page .category-list-count::after { + content: ') '; + display: inline; +} +.category-all-page .category-list-child { + padding-left: 10px; +} +.event-list { + padding: 0; +} +.event-list hr { + background: #222; + margin: 20px 0 45px 0; +} +.event-list hr::after { + background: #222; + color: #fff; + content: 'NOW'; + display: inline-block; + font-weight: bold; + padding: 0 5px; + text-align: right; +} +.event-list .event { + background: #222; + margin: 20px 0; + min-height: 40px; + padding: 15px 0 15px 10px; +} +.event-list .event .event-summary { + color: #fff; + margin: 0; + padding-bottom: 3px; +} +.event-list .event .event-summary::before { + animation: dot-flash 1s alternate infinite ease-in-out; + color: #fff; + content: '\f111'; + display: inline-block; + font-size: 10px; + margin-right: 25px; + vertical-align: middle; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; +} +.event-list .event .event-relative-time { + color: #bbb; + display: inline-block; + font-size: 12px; + font-weight: normal; + padding-left: 12px; +} +.event-list .event .event-details { + color: #fff; + display: block; + line-height: 18px; + margin-left: 56px; + padding-bottom: 6px; + padding-top: 3px; + text-indent: -24px; +} +.event-list .event .event-details::before { + color: #fff; + display: inline-block; + margin-right: 9px; + text-align: center; + text-indent: 0; + width: 14px; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; +} +.event-list .event .event-details.event-location::before { + content: '\f041'; +} +.event-list .event .event-details.event-duration::before { + content: '\f017'; +} +.event-list .event-past { + background: #f5f5f5; +} +.event-list .event-past .event-summary, +.event-list .event-past .event-details { + color: #bbb; + opacity: 0.9; +} +.event-list .event-past .event-summary::before, +.event-list .event-past .event-details::before { + animation: none; + color: #bbb; +} +@-moz-keyframes dot-flash { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.8); + } +} +@-webkit-keyframes dot-flash { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.8); + } +} +@-o-keyframes dot-flash { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.8); + } +} +@keyframes dot-flash { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.8); + } +} +ul.breadcrumb { + font-size: 0.75em; + list-style: none; + margin: 1em 0; + padding: 0 2em; + text-align: center; +} +ul.breadcrumb li { + display: inline; +} +ul.breadcrumb li + li::before { + content: '/\00a0'; + font-weight: normal; + padding: 0.5em; +} +ul.breadcrumb li + li:last-child { + font-weight: bold; +} +.tag-cloud { + text-align: center; +} +.tag-cloud a { + display: inline-block; + margin: 10px; +} +.tag-cloud a:hover { + color: var(--link-hover-color) !important; +} +mjx-container[jax="CHTML"][display="true"], +.has-jax { + overflow: auto hidden; +} +mjx-container + br { + display: none; +} +.header { + margin: 0 auto; + position: relative; + width: calc(100% - 20px); +} +@media (min-width: 1200px) { + .header { + width: 1160px; + } +} +@media (min-width: 1600px) { + .header { + width: 73%; + } +} +@media (max-width: 991px) { + .header { + width: auto; + } +} +.header-inner { + background: var(--content-bg-color); + border-radius: initial; + box-shadow: 0 2px 2px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.06), 0 1px 5px 0 rgba(0,0,0,0.12); + overflow: hidden; + padding: 0; + position: absolute; + top: 0; + width: 240px; +} +@media (min-width: 1200px) { + .header-inner { + width: 240px; + } +} +@media (max-width: 991px) { + .header-inner { + border-radius: initial; + position: relative; + width: auto; + } +} +.main-inner { + align-items: flex-start; + display: flex; + justify-content: space-between; + flex-direction: row-reverse; +} +@media (max-width: 991px) { + .main-inner { + width: auto; + } +} +.content-wrap { + background: var(--content-bg-color); + border-radius: initial; + box-shadow: 0 2px 2px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.06), 0 1px 5px 0 rgba(0,0,0,0.12); + box-sizing: border-box; + padding: 40px; + width: calc(100% - 252px); +} +@media (max-width: 991px) { + .content-wrap { + border-radius: initial; + padding: 20px; + width: 100%; + } +} +.footer-inner { + padding-left: 260px; +} +.back-to-top { + left: auto; + right: 30px; +} +@media (max-width: 991px) { + .back-to-top { + right: 20px; + } +} +@media (max-width: 991px) { + .footer-inner { + padding-left: 0; + padding-right: 0; + width: auto; + } +} +.site-brand-container { + background: #222; +} +@media (max-width: 991px) { + .site-brand-container { + box-shadow: 0 0 16px rgba(0,0,0,0.5); + } +} +.site-meta { + padding: 20px 0; +} +.brand { + padding: 0; +} +.site-subtitle { + margin: 10px 10px 0; +} +.custom-logo-image { + margin-top: 20px; +} +@media (max-width: 991px) { + .custom-logo-image { + display: none; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .site-nav-toggle, + .site-nav-right { + display: flex; + flex-direction: column; + justify-content: center; + } +} +.site-nav-toggle .toggle, +.site-nav-right .toggle { + color: #fff; +} +.site-nav-toggle .toggle .toggle-line, +.site-nav-right .toggle .toggle-line { + background: #fff; +} +@media (min-width: 768px) and (max-width: 991px) { + .site-nav { + display: none; + } +} +.menu .menu-item { + display: block; + margin: 0; +} +.menu .menu-item a, +.menu .menu-item span.exturl { + padding: 5px 20px; + position: relative; + text-align: left; + transition-property: background-color; +} +@media (max-width: 991px) { + .menu .menu-item.menu-item-search { + display: none; + } +} +.menu .menu-item .badge { + background: #ccc; + border-radius: 10px; + color: #fff; + float: right; + padding: 2px 5px; + text-shadow: 1px 1px 0 rgba(0,0,0,0.1); + vertical-align: middle; +} +.main-menu .menu-item-active a::after { + background: #bbb; + border-radius: 50%; + content: ' '; + height: 6px; + margin-top: -3px; + position: absolute; + right: 15px; + top: 50%; + width: 6px; +} +.sub-menu { + background: var(--content-bg-color); + border-bottom: 1px solid #ddd; + margin: 0; + padding: 6px 0; +} +.sub-menu .menu-item { + display: inline-block; +} +.sub-menu .menu-item a, +.sub-menu .menu-item span.exturl { + background: transparent; + margin: 5px 10px; + padding: initial; +} +.sub-menu .menu-item a:hover, +.sub-menu .menu-item span.exturl:hover { + background: transparent; + color: #fc6423; +} +.sub-menu .menu-item-active a { + border-bottom-color: #fc6423; + color: #fc6423; +} +.sub-menu .menu-item-active a:hover { + border-bottom-color: #fc6423; +} +.sidebar { + background: var(--body-bg-color); + box-shadow: none; + margin-top: 100%; + position: static; + width: 240px; +} +@media (max-width: 991px) { + .sidebar { + display: none; + } +} +.sidebar-toggle { + display: none; +} +.sidebar-inner { + background: var(--content-bg-color); + border-radius: initial; + box-shadow: 0 2px 2px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.06), 0 1px 5px 0 rgba(0,0,0,0.12), 0 -1px 0.5px 0 rgba(0,0,0,0.09); + box-sizing: border-box; + color: var(--text-color); + width: 240px; + opacity: 0; +} +.sidebar-inner.affix { + position: fixed; + top: 12px; +} +.sidebar-inner.affix-bottom { + position: absolute; +} +.site-state-item { + padding: 0 10px; +} +.sidebar-button { + border-bottom: 1px dotted #ccc; + border-top: 1px dotted #ccc; + margin-top: 10px; + text-align: center; +} +.sidebar-button a { + border: 0; + color: #fc6423; + display: block; +} +.sidebar-button a:hover { + background: none; + border: 0; + color: #e34603; +} +.sidebar-button a:hover .fa, +.sidebar-button a:hover .fab, +.sidebar-button a:hover .far, +.sidebar-button a:hover .fas { + color: #e34603; +} +.links-of-author { + display: flex; + flex-wrap: wrap; + margin-top: 10px; + justify-content: center; +} +.links-of-author-item { + margin: 5px 0 0; + width: 50%; +} +.links-of-author-item a, +.links-of-author-item span.exturl { + box-sizing: border-box; + display: inline-block; + margin-bottom: 0; + margin-right: 0; + max-width: 216px; + overflow: hidden; + padding: 0 5px; + text-overflow: ellipsis; + white-space: nowrap; +} +.links-of-author-item a, +.links-of-author-item span.exturl { + border-bottom: none; + display: block; + text-decoration: none; +} +.links-of-author-item a::before, +.links-of-author-item span.exturl::before { + display: none; +} +.links-of-author-item a:hover, +.links-of-author-item span.exturl:hover { + background: var(--body-bg-color); + border-radius: 4px; +} +.links-of-author-item .fa, +.links-of-author-item .fab, +.links-of-author-item .far, +.links-of-author-item .fas { + margin-right: 2px; +} +.links-of-blogroll-item { + padding: 0; +} +.content-wrap { + background: initial; + box-shadow: initial; + padding: initial; +} +.post-block { + background: var(--content-bg-color); + border-radius: initial; + box-shadow: 0 2px 2px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.06), 0 1px 5px 0 rgba(0,0,0,0.12); + padding: 40px; +} +.post-block + .post-block { + border-radius: initial; + box-shadow: 0 2px 2px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.06), 0 1px 5px 0 rgba(0,0,0,0.12), 0 -1px 0.5px 0 rgba(0,0,0,0.09); + margin-top: 12px; +} +.comments { + background: var(--content-bg-color); + border-radius: initial; + box-shadow: 0 2px 2px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.06), 0 1px 5px 0 rgba(0,0,0,0.12), 0 -1px 0.5px 0 rgba(0,0,0,0.09); + margin-top: 12px; + padding: 40px; +} +.tabs-comment { + margin-top: 1em; +} +.content { + padding-top: initial; +} +.post-eof { + display: none; +} +.pagination { + background: var(--content-bg-color); + border-radius: initial; + border-top: initial; + box-shadow: 0 2px 2px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.06), 0 1px 5px 0 rgba(0,0,0,0.12), 0 -1px 0.5px 0 rgba(0,0,0,0.09); + margin: 12px 0 0; + padding: 10px 0 10px; +} +.pagination .prev, +.pagination .next, +.pagination .page-number { + margin-bottom: initial; + top: initial; +} +.main { + padding-bottom: initial; +} +.footer { + bottom: auto; +} +.sub-menu { + border-bottom: initial; + box-shadow: 0 2px 2px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.06), 0 1px 5px 0 rgba(0,0,0,0.12); +} +.sub-menu + .content .post-block { + box-shadow: 0 2px 2px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.06), 0 1px 5px 0 rgba(0,0,0,0.12), 0 -1px 0.5px 0 rgba(0,0,0,0.09); + margin-top: 12px; +} +@media (min-width: 768px) and (max-width: 991px) { + .sub-menu + .content .post-block { + margin-top: 10px; + } +} +@media (max-width: 767px) { + .sub-menu + .content .post-block { + margin-top: 8px; + } +} +.post-body h1, +.post-body h2 { + border-bottom: 1px solid #eee; +} +.post-body h3 { + border-bottom: 1px dotted #eee; +} +@media (min-width: 768px) and (max-width: 991px) { + .content-wrap { + padding: 10px; + } + .posts-expand .post-button { + margin-top: 20px; + } + .post-block { + border-radius: initial; + box-shadow: 0 2px 2px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.06), 0 1px 5px 0 rgba(0,0,0,0.12), 0 -1px 0.5px 0 rgba(0,0,0,0.09); + padding: 20px; + } + .post-block + .post-block { + margin-top: 10px; + } + .comments { + margin-top: 10px; + padding: 10px 20px; + } + .pagination { + margin: 10px 0 0; + } +} +@media (max-width: 767px) { + .content-wrap { + padding: 8px; + } + .posts-expand .post-button { + margin: 12px 0; + } + .post-block { + border-radius: initial; + box-shadow: 0 2px 2px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.06), 0 1px 5px 0 rgba(0,0,0,0.12), 0 -1px 0.5px 0 rgba(0,0,0,0.09); + min-height: auto; + padding: 12px; + } + .post-block + .post-block { + margin-top: 8px; + } + .comments { + margin-top: 8px; + padding: 10px 12px; + } + .pagination { + margin: 8px 0 0; + } +} +.utterances { + max-width: unset; +} diff --git a/images/algolia_logo.svg b/images/algolia_logo.svg new file mode 100644 index 0000000..4702423 --- /dev/null +++ b/images/algolia_logo.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/images/apple-touch-icon-next.png b/images/apple-touch-icon-next.png new file mode 100644 index 0000000..86a0d1d Binary files /dev/null and b/images/apple-touch-icon-next.png differ diff --git a/images/avatar.gif b/images/avatar.gif new file mode 100644 index 0000000..28411fd Binary files /dev/null and b/images/avatar.gif differ diff --git a/images/cc-by-nc-nd.svg b/images/cc-by-nc-nd.svg new file mode 100644 index 0000000..79a4f2e --- /dev/null +++ b/images/cc-by-nc-nd.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/images/cc-by-nc-sa.svg b/images/cc-by-nc-sa.svg new file mode 100644 index 0000000..bf6bc26 --- /dev/null +++ b/images/cc-by-nc-sa.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/images/cc-by-nc.svg b/images/cc-by-nc.svg new file mode 100644 index 0000000..3697349 --- /dev/null +++ b/images/cc-by-nc.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/images/cc-by-nd.svg b/images/cc-by-nd.svg new file mode 100644 index 0000000..934c61e --- /dev/null +++ b/images/cc-by-nd.svg @@ -0,0 +1,117 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/images/cc-by-sa.svg b/images/cc-by-sa.svg new file mode 100644 index 0000000..463276a --- /dev/null +++ b/images/cc-by-sa.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/images/cc-by.svg b/images/cc-by.svg new file mode 100644 index 0000000..4bccd14 --- /dev/null +++ b/images/cc-by.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/images/cc-zero.svg b/images/cc-zero.svg new file mode 100644 index 0000000..0f86639 --- /dev/null +++ b/images/cc-zero.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/favicon-16x16-next.png b/images/favicon-16x16-next.png new file mode 100644 index 0000000..de8c5d3 Binary files /dev/null and b/images/favicon-16x16-next.png differ diff --git a/images/favicon-32x32-next.png b/images/favicon-32x32-next.png new file mode 100644 index 0000000..e02f5f4 Binary files /dev/null and b/images/favicon-32x32-next.png differ diff --git a/images/logo.svg b/images/logo.svg new file mode 100644 index 0000000..cbb3937 --- /dev/null +++ b/images/logo.svg @@ -0,0 +1,23 @@ + +image/svg+xml diff --git a/index.html b/index.html new file mode 100644 index 0000000..013bcc6 --- /dev/null +++ b/index.html @@ -0,0 +1,1777 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 耿腾的博客 + + + + + + + + + + + + + + + + +
+
+ +
+
+ + +
+ + + +

耿腾的博客

+ +
+

常期望安定,还期望即兴。

+
+ + +
+ + + + + + + + + +
+
+ + +
+ + 0% +
+
+ + +
+
+
+ + +
+ + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

Raydium 是一个 Solana 平台上的 DEX,提供了各种代币的流动性池,为用户提供流动性挖矿、购买代币的功能。为了给 DEX 项目引入流动性池、对接 Raydium 的交换协议,对 raydium 恒定乘积交换合约的源码进行深入学习,这也是 Raydium 上最新的标准 AMM。

+
+

Repository: https://github.com/raydium-io/raydium-cp-swap

+
+

流动性池状态及创建时的基本流程

流动性池状态定义如下:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#[account(zero_copy(unsafe))]
#[repr(packed)]
#[derive(Default, Debug)]
pub struct PoolState {
/// 使用的配置,主要包括各种费率,控制标志,拥有者 / 受益者 等信息
pub amm_config: Pubkey,
/// 流动性池创建者
pub pool_creator: Pubkey,
/// Token A 的金库,用来存储流动性 Token 及收益
pub token_0_vault: Pubkey,
/// Token B 的金库,用来存储流动性 Token 及收益
pub token_1_vault: Pubkey,

/// 流动性池 token 会在存入 A 或 B 时发放
/// 流动性池 token 可以被提取为对应的 A 或 B
pub lp_mint: Pubkey,
/// A 的铸币厂
pub token_0_mint: Pubkey,
/// B 的铸币厂
pub token_1_mint: Pubkey,

/// A 使用的 Token 程序,比如旧版本或者 2022
pub token_0_program: Pubkey,
/// B 使用的 Token 程序
pub token_1_program: Pubkey,

/// TWAP 计价账户的地址
pub observation_key: Pubkey,

/// 用于 PDA 签名时使用的 bump
pub auth_bump: u8,
/// Bitwise representation of the state of the pool
/// bit0, 1: disable deposit(vaule is 1), 0: normal
/// bit1, 1: disable withdraw(vaule is 2), 0: normal
/// bit2, 1: disable swap(vaule is 4), 0: normal
pub status: u8,

pub lp_mint_decimals: u8,
/// mint0 and mint1 decimals
pub mint_0_decimals: u8,
pub mint_1_decimals: u8,

/// True circulating supply without burns and lock ups
pub lp_supply: u64,
/// 金库中欠流动性提供者的 A 和 B 的数额.
pub protocol_fees_token_0: u64,
pub protocol_fees_token_1: u64,

pub fund_fees_token_0: u64,
pub fund_fees_token_1: u64,

/// The timestamp allowed for swap in the pool.
pub open_time: u64,
/// recent epoch
pub recent_epoch: u64,
/// padding for future updates
pub padding: [u64; 31],
}
+

初始化流动性池的指令函数签名如下:

+
1
2
3
4
5
6
pub fn initialize(
ctx: Context<Initialize>, // 上下文
init_amount_0: u64, // A 资产初始数额
init_amount_1: u64, // B 资产初始数额
mut open_time: u64, // 开始交易时间
) -> Result<()>;
+

初始化流动性池账户定义如下:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#[derive(Accounts)]
pub struct Initialize<'info> {
/// Address paying to create the pool. Can be anyone
#[account(mut)]
pub creator: Signer<'info>,

/// Which config the pool belongs to.
pub amm_config: Box<Account<'info, AmmConfig>>,

/// CHECK: pool vault and lp mint authority
#[account(
seeds = [
crate::AUTH_SEED.as_bytes(),
],
bump,
)]
pub authority: UncheckedAccount<'info>,

/// CHECK: Initialize an account to store the pool state
/// PDA account:
/// seeds = [
///     POOL_SEED.as_bytes(),
///     amm_config.key().as_ref(),
///     token_0_mint.key().as_ref(),
///     token_1_mint.key().as_ref(),
/// ],
///
/// Or random account: must be signed by cli
#[account(mut)]
pub pool_state: UncheckedAccount<'info>,

/// Token_0 mint, the key must smaller then token_1 mint.
#[account(
constraint = token_0_mint.key() < token_1_mint.key(),
mint::token_program = token_0_program,
)]
pub token_0_mint: Box<InterfaceAccount<'info, Mint>>,

/// Token_1 mint, the key must grater then token_0 mint.
#[account(
mint::token_program = token_1_program,
)]
pub token_1_mint: Box<InterfaceAccount<'info, Mint>>,

/// pool lp mint
#[account(
init,
seeds = [
POOL_LP_MINT_SEED.as_bytes(),
pool_state.key().as_ref(),
],
bump,
mint::decimals = 9,
mint::authority = authority,
payer = creator,
mint::token_program = token_program,
)]
pub lp_mint: Box<InterfaceAccount<'info, Mint>>,

/// payer token0 account
#[account(
mut,
token::mint = token_0_mint,
token::authority = creator,
)]
pub creator_token_0: Box<InterfaceAccount<'info, TokenAccount>>,

/// creator token1 account
#[account(
mut,
token::mint = token_1_mint,
token::authority = creator,
)]
pub creator_token_1: Box<InterfaceAccount<'info, TokenAccount>>,

/// creator lp token account
#[account(
init,
associated_token::mint = lp_mint,
associated_token::authority = creator,
payer = creator,
token::token_program = token_program,
)]
pub creator_lp_token: Box<InterfaceAccount<'info, TokenAccount>>,

/// CHECK: Token_0 vault for the pool, create by contract
#[account(
mut,
seeds = [
POOL_VAULT_SEED.as_bytes(),
pool_state.key().as_ref(),
token_0_mint.key().as_ref()
],
bump,
)]
pub token_0_vault: UncheckedAccount<'info>,

/// CHECK: Token_1 vault for the pool, create by contract
#[account(
mut,
seeds = [
POOL_VAULT_SEED.as_bytes(),
pool_state.key().as_ref(),
token_1_mint.key().as_ref()
],
bump,
)]
pub token_1_vault: UncheckedAccount<'info>,

/// create pool fee account
#[account(
mut,
address= crate::create_pool_fee_reveiver::id(),
)]
pub create_pool_fee: Box<InterfaceAccount<'info, TokenAccount>>,

/// an account to store oracle observations
#[account(
init,
seeds = [
OBSERVATION_SEED.as_bytes(),
pool_state.key().as_ref(),
],
bump,
payer = creator,
space = ObservationState::LEN
)]
pub observation_state: AccountLoader<'info, ObservationState>,

/// Program to create mint account and mint tokens
pub token_program: Program<'info, Token>,
/// Spl token program or token program 2022
pub token_0_program: Interface<'info, TokenInterface>,
/// Spl token program or token program 2022
pub token_1_program: Interface<'info, TokenInterface>,
/// Program to create an ATA for receiving position NFT
pub associated_token_program: Program<'info, AssociatedToken>,
/// To create a new program account
pub system_program: Program<'info, System>,
/// Sysvar for program account
pub rent: Sysvar<'info, Rent>,
}
+

初始化流动性池流程如下:

+
    +
  1. 判断两种资产的 Mint 账户是否合法,判断 AMM 配置中是否关闭创建 Pool。
  2. +
  3. 设定开始交易时间。
  4. +
  5. 创建两种资产的金库。
  6. +
  7. 创建 PoolState 数据账户。
  8. +
  9. 创建 ObservationState 数据账户。
  10. +
  11. 将两种初始资产从创建者账户转账到金库账户。
  12. +
  13. 判断两个金库账户的数额是否合法(实际上只要大于 0 就合法)。
  14. +
  15. 计算流动性值: $liquidity = \sqrt(amount0 * amount1)$。
  16. +
  17. 固定锁定 100 个流动性值: let lock_lp_amount = 100
  18. +
  19. 发放 liquidity - lock_lp_amount 个 LP token 给创建者。
  20. +
  21. 从创建者账户里收取创建费(lamports)到一个专门存放创建费的账户中(地址硬编码在合约里)。
  22. +
  23. 初始化流动性池数据的各个字段。
  24. +
+

资产交换

资产交换分为两类:

+
    +
  1. 基于输入资产: 输入资产额度固定,一部分会作为手续费,一部分作为购买资金输入。
  2. +
  3. 基于输出资产: 输出资产额度固定,需要额外购买一部分输出资产作为手续费,其他作为购买到的资产输出。
  4. +
+

基于输入资产

指令函数签名:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
pub fn swap_base_input(
ctx: Context<Swap>, // 上下文
amount_in: u64, // 输入资产
minimum_amount_out: u64 // 最小输出资产,由调用者按照当前价格及滑点计算得出
) -> Result<()>;

#[derive(Accounts)]
pub struct Swap<'info> {
// 交换者
pub payer: Signer<'info>,

// 流动性池的金库和流动性 token mint 的权限账户
/// CHECK: pool vault and lp mint authority
#[account(
seeds = [
crate::AUTH_SEED.as_bytes(),
],
bump,
)]
pub authority: UncheckedAccount<'info>,

/// 用于读取协议费用
#[account(address = pool_state.load()?.amm_config)]
pub amm_config: Box<Account<'info, AmmConfig>>,

/// 流动性池数据账户
#[account(mut)]
pub pool_state: AccountLoader<'info, PoolState>,

/// 用户购买使用的 token ATA,即输入 token 的 ATA
#[account(mut)]
pub input_token_account: Box<InterfaceAccount<'info, TokenAccount>>,

/// 用户希望购买到的 token ATA,即输出 token 的 ATA
#[account(mut)]
pub output_token_account: Box<InterfaceAccount<'info, TokenAccount>>,

/// 接收用户购买使用的 token 的金库
#[account(
mut,
constraint = input_vault.key() == pool_state.load()?.token_0_vault || input_vault.key() == pool_state.load()?.token_1_vault
)]
pub input_vault: Box<InterfaceAccount<'info, TokenAccount>>,

/// 输出用户希望购买到的 token 的金库
#[account(
mut,
constraint = output_vault.key() == pool_state.load()?.token_0_vault || output_vault.key() == pool_state.load()?.token_1_vault
)]
pub output_vault: Box<InterfaceAccount<'info, TokenAccount>>,

/// 输入 token 的程序(可能是 2022)
pub input_token_program: Interface<'info, TokenInterface>,

/// 输出 token 的程序(可能是 2022)
pub output_token_program: Interface<'info, TokenInterface>,

/// 输入 token 的铸币厂
#[account(
address = input_vault.mint
)]
pub input_token_mint: Box<InterfaceAccount<'info, Mint>>,

/// 输出 token 的铸币厂
#[account(
address = output_vault.mint
)]
pub output_token_mint: Box<InterfaceAccount<'info, Mint>>,

/// 记录价格的数据账户,用于计算 TWAP 价格
#[account(mut, address = pool_state.load()?.observation_key)]
pub observation_state: AccountLoader<'info, ObservationState>,
}
+

基于输入的资产交换流程如下:

+
    +
  1. 检查时间、状态等是否允许交换。
  2. +
  3. 计算转账(应该指的是向金库中转账)费用,从总的输入费用中减去这部分,将剩余部分 actual_amount_in 作为实际购买资金。
  4. +
  5. 计算两个金库扣除协议费用和资金费用之后剩余的部分,即实际上提供流动性的两种资金。
  6. +
  7. 按照上一步计算得到了两种资金,计算两种资金置换另一种的价格,$A / B$ 和 $B / A$,同时转换为 u128 左移 32 位使用定点数来保存精度。
  8. +
  9. 将第 3 步得到的两种资金额度相乘,得到恒定乘积 $A * B$。
  10. +
  11. 计算交换结果,包括各种额度、费用。
  12. +
  13. 将上一步得到的计算结果中的交换后的的源资产额度减去交易费用,再乘以这个结果中交换后的目标资产额度,得到交换后的恒定乘积 $A’ * B’$。
  14. +
  15. 验证第 6 步计算结果中交换的源资产额度是否等于 actual_amount_in
  16. +
  17. 检查第 6 步计算结果中交换得到的目标资产额度减去转账费用之后,是否仍然大于 0 ;同时检查,是否大于等于 minimum_amount_out,如果不满足表示超过滑点限制。
  18. +
  19. 更新流动性池状态的协议费用及资金费用,用于记录金库中非流动性的部分的额度。
  20. +
  21. 发送一个 SwapEvent 事件。(为什么?怎么利用?)
  22. +
  23. 验证交换后的恒定乘积大于等于交换前的恒定乘积,即 $A’ B’ \geq A B$。
  24. +
  25. 根据计算后的结果,将相应额度的输入资产从用户账户转账到金库账户,将相应额度的输出资产从金库账户转账到用户账户。
  26. +
  27. 更新观测状态(ObservationState)的值。
  28. +
+

基于输出资产

TODO

+

观测状态 / TWAP 计价器

观测值及观测状态结构定义如下:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#[zero_copy(unsafe)]
#[repr(packed)]
#[derive(Default, Debug)]
pub struct Observation {
/// The block timestamp of the observation
pub block_timestamp: u64,
/// the cumulative of token0 price during the duration time, Q32.32, the remaining 64 bit for overflow
pub cumulative_token_0_price_x32: u128,
/// the cumulative of token1 price during the duration time, Q32.32, the remaining 64 bit for overflow
pub cumulative_token_1_price_x32: u128,
}

#[account(zero_copy(unsafe))]
#[repr(packed)]
#[cfg_attr(feature = "client", derive(Debug))]
pub struct ObservationState {
/// Whether the ObservationState is initialized
pub initialized: bool,
/// the most-recently updated index of the observations array
pub observation_index: u16,
pub pool_id: Pubkey,
/// 固定长度的环形缓冲区,用于循环写入观测记录
pub observations: [Observation; OBSERVATION_NUM], // OBSERVATION_NUM == 100
/// padding for feature update
pub padding: [u64; 4],
}
+

其中最重要的函数是更新:

+
1
2
3
4
5
6
pub fn update(
&mut self,
block_timestamp: u64,
token_0_price_x32: u128,
token_1_price_x32: u128,
);
+
    +
  1. 该函数将一个 Oracle 观测值写入到当前状态中,并将当前索引自增一模 OBSERVATION_NUM,即循环写入。
  2. +
  3. 该函数一秒钟最多执行一次。
  4. +
  5. 将两种资产的当前价格与跟上一个记录的时间差值相乘,即: $Price * \Delta T$。
  6. +
  7. 然后将上一步求出的两个结果与上一个记录的两个值累加起来 (wrapping_add),作为新记录的两个值。
  8. +
  9. 更新索引。
  10. +
+

原理

TWAP ,即时间加权平均价格(Time Weighted Average Price);用来平滑价格波动,减少大宗交易对市场价格的冲击。

+

计算公式为:

+

即某个特定时间内的 TWAP 为:每小段时间乘以当时的价格求和,除以总时间。

+

设计理由(猜测)

我能想到的另外一种方案是:

+
    +
  1. 观测状态中的每个观测记录,只记录 $Price * \Delta T$ 和时间戳即可。
  2. +
  3. 当要求某段时间的 TWAP 时,将这段时间的所有记录累加,除以总时长即可,时间复杂度 $O(n)$。
  4. +
  5. 这样看起来好像可以避免 wrapping_add,源码中不断累加更可能遇到这种情况。
  6. +
+

而源码在计算某段时间的 TWAP 时,只需要将最后一个记录的值和第一个记录的值的差除以总时间即可,即这种方案时间复杂度只有 $O(1)$ 。而且实际上 wrapping_add 得到的累加值在相减的时候仍然可以得到正确的结果,只要在这段时间内没有溢出两次就行了。

+ + +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

5. 权益证明共识机制

5.1 描述

这个机制是用来:

+
    +
  1. 快速确认由 PoH 生成器生成的当前序列。
  2. +
  3. 投票选出下一个 PoH 生成器。
  4. +
  5. 惩罚不当行为的验证者。
  6. +
+

这个算法依赖于所有参与节点在规定超时时间内最终接收到的消息。

+

5.2 术语

    +
  • 押金(bonds): 验证者在验证交易时的承诺作为抵押的代币。
  • +
  • 惩罚机制(slashing): 为解决无利害关系问题提出的方案,当不同的分支被发布时,当前分支可以销毁验证者的押金。
  • +
  • 超级多数(super majority): 由押金加权之后,验证者的总票数达到三分之二。这意味着发起攻击的经济成本等同于代币市值的三分之一。
  • +
+
+

无利害关系问题(Nothing at Stake Problem): 在权益证明系统中,验证者不需要消耗物理资源(如电力),而是依赖于其押金(Stake)。因此,如果网络分叉,验证者可以选择在多个分支上投票,因为这样不会有额外成本。他们会试图最大化收益,而不关心网络的最终一致性。

+
+

5.3 质押

用户将代币转账到一个自己名下的质押账户,这笔押金在赎回之前需要保持不动。

+

质押账户有锁定期,锁定期结束才能赎回。

+

超级多数确认了质押交易之后,质押才有效。

+

5.4 投票

    +
  1. PoH 生成器会在预定义好的周期内发布一个自身状态的签名。
  2. +
  3. 每个质押的身份需要发布一份状态的签名,作为投票。
  4. +
  5. 投票只有同意票,没有反对票。
  6. +
  7. 如果在超时时间内,绝对多数达成了共识,这个分支就会被认为是合法的。
  8. +
+

5.5 解质押

如果用户未能按时参与投票,质押的币会被标记为“过期”,旨在强制参与者保持活跃,否则将失去投票资格。

+

N 作为投票失效的次数阈值,会随着过期投票的数量增加而增长,N 的动态调整也可以为网络的自愈能力提供支持,减少对投票延迟的影响。

+

5.6 选举

    +
  1. 检测到 PoH 生成器故障时,新 PoH 生成器选举就会发生。
  2. +
  3. 拥有最高投票权重、更高的公钥地址的验证者会被选为新的 PoH 生成器。
  4. +
  5. 需要超级多数确认,如果在完成确认前新的生成器故障,就重新选择下一个新的 PoH 生成器。
  6. +
  7. 切换选票时,投票的验证者需要在一个更高的 PoH 序列计数器上投票,否则会触发惩罚。
  8. +
  9. 会选择一个次级生成器,用于主生成器故障时快速切换。
  10. +
+

5.7 选举触发条件

5.7.1 分叉的 Proof of History 生成器

    +
  1. 只有当 PoH 生成器的标识被攻破时,才可能发生分叉。
  2. +
  3. 分叉的检测依据是同一个 PoH 标识发布了两个不同的历史记录。
  4. +
+

5.7.2 运行时异常

    +
  1. Bug、硬件故障、人为错误可能导致生成器生成无效状态,并发布与本地验证者不一致的签名。
  2. +
  3. 验证者将通过 Gossip 协议发布正确的签名,这一事件将触发新的选举。
  4. +
  5. 接收无效状态的验证者将收到惩罚。
  6. +
+

5.7.3 网络超时

5.8 惩罚

    +
  1. 恶意投票:对两个不同的序列投票,受质押削减惩罚。
  2. +
  3. 非恶意的竞选序列冲突,投票会被撤销,不会惩罚。
  4. +
  5. 对 PoH 生成器的无效哈希进行投票,受质押削减惩罚。
  6. +
+

5.9 次级选举

    +
  1. 次级选举是在主序列上被提出的。
  2. +
  3. 在超时时间内通过绝对多数投票,次级生成器就当选了。
  4. +
  5. 主生成器通过在序列中插入指示交接的消息,来实现软交接。
  6. +
  7. 如果主生成器发生故障,已当选的次生成器会被作为第一备选。
  8. +
+

5.10 可用性

    +
  1. CAP 中 Solana 选择 A 可用性。
  2. +
  3. 最终一致性:在合理的人类时间范围内保持一定程度的一致性。
  4. +
  5. 利用 PoH(Proof of History)机制提供了时间的客观测量,结合 PoS 投票记录,可以动态地取消不可用验证者的质押,调整网络的活性和一致性需求。
  6. +
  7. 在合理的时间内,随着不可用验证者被逐步取消质押,网络可以逐步恢复到满足 2/3 共识的状态,从而达成一致性。
  8. +
+ + +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +
+

PoH (Proof of History) 是⼀种⽤于验证事件之间的顺序和时间流逝的证明。
PoH is a proof for verifying order and passage of time between events.

+
+

1. 简介

区块链本质上是一个容错复制状态机。

+

使用 PoH,网络中的节点可以不依靠其他信任机制验证账本记录的时间流逝。

+
    +
  • 问题
  • +
+
    +
  1. PoH 如何生成可验证的时间间隔。2. PoH 如何生成可验证的事件执行时间。
  2. +
+

2. 提纲

3. 网络设计

    +
  1. 某个节点被选举为领导者(Leader),负责生成历史证明序列(提供网络全局读取一致性以及可验证的时间流逝)。
  2. +
  3. 领导者搜集用户消息并排序。
  4. +
  5. 领导者对内存中的交易进行处理,并将交易和签名发送到验证复制节点(Verifier)。
  6. +
  7. 验证复制节点在其状态副本上执行相同的交易,并发布处理的状态签名,作为交易确认及共识算法投票。
  8. +
+

整体网络的交易流向

+
+

Leader 选举是基于 PoS 实现的。

+
+

4. 历史证明

    +
  1. 历史证明是一个计算序列。
  2. +
  3. 依赖一个加密的安全函数,无法通过输入预测输出,必须完全执行函数才能生成输出。
  4. +
  5. 函数在单个核心上按顺序执行,上一步的输出作为下一步输入,定期记录当前输出及执行的次数。
  6. +
  7. 外部计算机可以通过检查其中的每个序列段来重新计算并验证输出。
  8. +
+
    +
  • 问题
  • +
+

原文有 Data can be timestamped into this sequence by appending the data (or a hash of some data) into the state of the function. 这句话里的 timestamped 应该不是传统意义的时间戳的意思,而是某种反映执行顺序(和次数?)的更本质的标记。也就说,这种机制提供了“时间戳“的实现。

+

4.1 说明

PoH序列

+

只需要每隔一段时间发布哈希和索引的子集,即:

+

PoH序列

+

只要选择的哈希函数是抗冲突的、不可预测结果的,产生的序列就只能由一个计算机线程按顺序计算得到。也就是,如果不从起始值开始计算 N 次,就无法获得索引 N 处的哈希值。

+

哈希的抗冲突、不可预测性和顺序计算,使得我们能够确定,某段序列的产生过程中发生了时间的流逝。

+

历史证明序列

+

4.2 事件时间戳

将数据或数据的哈希通过“组合函数”插入到这个序列中参与计算,则这些附加的数据就拥有了时间顺序。

+

带数据的历史证明序列

+

4.3 验证

可以利用多核,同时验证一个序列的多段。N 个核就可以同时验证 N 段。产生一段序列的预期时间如果是 T,验证时使用的核数是 C,则验证这段序列需要的时间只有 T / C

+

4.4 横向扩容

两个 PoH 生成节点互相观察对方最近一次的状态,加入自己下一步哈希的数据,实现多个 PoH 链的混合 / 同步。

+

通过定期同步生成器,每个生成器都可以处理一部分外部流量,整个系统就可以处理更多事件。

+
    +
  • 问题
  • +
+
    +
  1. 混合的意义是什么,不混合仍然可以横向扩展多个 PoH 链。是为了保证系统整体的一致性,提高防篡改性?
  2. +
+

4.5 一致性

为了防止恶意重排序攻击,客户端在创建事件时,需要获取最近的哈希,把该事件挂到某个 PoH 链上,即为事件打上 PoH 的时间戳。

+

为了防止恶意 PoH 生成器篡改客户端提交的事件关联的哈希,客户端可以提交数据和关联哈希的签名,而不仅仅是数据的签名。

+

客户端提交数据和最近哈希签名到PoH链

+

验证时只需验证两点:

+
    +
  1. 签名是否正确。
  2. +
  3. 关联的哈希在 PoH 链上是否在当前的事件前面。
  4. +
+
1
2
3
4
5
(Signature, PublicKey, hash30a, event3 data) = Event3

Verify(Signature, PublicKey, Event3)

Lookup(hash30a, PoHSequence)
+

4.6 开销

每秒 4000 个哈希产生 160KB 额外数据。

+

这些数据,使用 4000 核的 GPU 需要 0.25-0.75 毫秒进行验证。

+

4.7 攻击

4.7.1 逆序

恶意攻击者需要从第二个事件开始伪造,但是这个延迟已经足够让其他正常节点对于初始序列达成共识。

+

4.7.2 速度

使用多个 PoH 生成器可以增强抗攻击性。

+

一个生成器有高带宽,用来接收并混合事件,另一个高速低带宽,定期与高带宽链同步混合,它会生成辅助序列,让攻击者逆序更难。

+

4.7.3 长程攻击

    +
  • PoH 提供了时间上的不可篡改性。
  • +
  • PoRep 提供了存储数据的真实性证明。
  • +
  • 两者结合提供了对伪造账本的防御能力。
  • +
+

攻击者不仅需要耗费巨大的时间成本来伪造时间戳,还需要在存储上符合 PoRep 的要求。

+ + +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

Pubkey

1
pub struct Pubkey(pub(crate) [u8; 32]);
+

find_program_address

1
2
3
4
pub fn find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> (Pubkey, u8) {
Self::try_find_program_address(seeds, program_id)
.unwrap_or_else(|| panic!("Unable to find a viable program address bump seed"))
}
+

try_find_program_address

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
pub fn try_find_program_address(seeds: &[&[u8]], program_id: &Pubkey) -> Option<(Pubkey, u8)> {
// Perform the calculation inline, calling this from within a program is
// not supported
#[cfg(not(target_os = "solana"))]
{
let mut bump_seed = [std::u8::MAX]; // 从 std::u8::MAX 开始尝试
for _ in 0..std::u8::MAX {
{
let mut seeds_with_bump = seeds.to_vec();
seeds_with_bump.push(&bump_seed); // 将 bump 作为种子的最后一部分
match Self::create_program_address(&seeds_with_bump, program_id) {
Ok(address) => return Some((address, bump_seed[0])),
Err(PubkeyError::InvalidSeeds) => (),
_ => break,
}
}
bump_seed[0] -= 1; // 尝试失败了就减 1 再尝试
}
None
}
// Call via a system call to perform the calculation
#[cfg(target_os = "solana")]
{
// ...
}
}
+

create_program_address

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
pub fn create_program_address(
seeds: &[&[u8]],
program_id: &Pubkey,
) -> Result<Pubkey, PubkeyError> {
if seeds.len() > MAX_SEEDS { // 种子最多有 16 个
return Err(PubkeyError::MaxSeedLengthExceeded);
}
for seed in seeds.iter() {
if seed.len() > MAX_SEED_LEN { // 每个种子最长 32
return Err(PubkeyError::MaxSeedLengthExceeded);
}
}

// Perform the calculation inline, calling this from within a program is
// not supported
#[cfg(not(target_os = "solana"))]
{
let mut hasher = crate::hash::Hasher::default(); // 就是一个 Sha256
for seed in seeds.iter() {
hasher.hash(seed);
}
// PDA_MARKER 是一个 21 字节长的字符串
// const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";
hasher.hashv(&[program_id.as_ref(), PDA_MARKER]);
let hash = hasher.result();

if bytes_are_curve_point(hash) { // PDA 账户需要确保不在爱德华曲线上
return Err(PubkeyError::InvalidSeeds);
}

Ok(Pubkey::from(hash.to_bytes()))
}
// Call via a system call to perform the calculation
#[cfg(target_os = "solana")]
{
let mut bytes = [0; 32];
let result = unsafe {
crate::syscalls::sol_create_program_address(
seeds as *const _ as *const u8,
seeds.len() as u64,
program_id as *const _ as *const u8,
&mut bytes as *mut _ as *mut u8,
)
};
match result {
crate::entrypoint::SUCCESS => Ok(Pubkey::from(bytes)),
_ => Err(result.into()),
}
}
}
+

bytes_are_curve_point

1
2
3
4
5
6
7
8
9
10
pub fn bytes_are_curve_point<T: AsRef<[u8]>>(_bytes: T) -> bool {
#[cfg(not(target_os = "solana"))]
{
curve25519_dalek::edwards::CompressedEdwardsY::from_slice(_bytes.as_ref())
.decompress()
.is_some()
}
#[cfg(target_os = "solana")]
unimplemented!();
}
+

总结

整体流程

    +
  1. [std::u8::MAX] (即 [255]) 开始作为 bump 尝试,值依次递减至 0,bump 就是最后一个种子。
  2. +
  3. 检查种子数是否不超过 16 (包含 bump,也就是用户能输入的只有 15 个),所有种子长度不超过 32.
  4. +
  5. 使用所有种子依次输入 Sha256 进行哈希,然后将 program_id 及一个 21 字节的常量 PDA_MARKER (b"ProgramDerivedAddress") 输入进行哈希。
  6. +
  7. 计算哈希结果并判断是否在 Curve25519 上,如果不在,表示是合法的 PDA 地址,否则 bump 自减 1 从第二步开始重试。
  8. +
  9. 如果从 [255][0] 都找不到,则返回 None / 报错。
  10. +
+
+

Curve25519 是一种特定的爱德华曲线,它设计用于实现高效、安全的 Diffie-Hellman 密钥交换。

+
+ + +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

AccountInfo

+

solana_program::account_info::AccountInfo

+
+

账户信息(AccountInfo),合约中对于账户基本信息的引用(余额和数据可变)。

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/// Account information
#[derive(Clone)]
#[repr(C)]
pub struct AccountInfo<'a> {
/// Public key of the account
pub key: &'a Pubkey,
/// The lamports in the account. Modifiable by programs.
pub lamports: Rc<RefCell<&'a mut u64>>,
/// The data held in this account. Modifiable by programs.
pub data: Rc<RefCell<&'a mut [u8]>>,
/// Program that owns this account
pub owner: &'a Pubkey,
/// The epoch at which this account will next owe rent
pub rent_epoch: Epoch,
/// Was the transaction signed by this account's public key?
pub is_signer: bool,
/// Is the account writable?
pub is_writable: bool,
/// This account's data contains a loaded program (and is now read-only)
pub executable: bool,
}
+

Account

+

anchor_lang::accounts::account::Account

+
+

账户(Account),对 AccountInfo 的包装,并能够自动反序列化 T

+
1
2
3
4
5
#[derive(Clone)]
pub struct Account<'info, T: AccountSerialize + AccountDeserialize + Clone> {
account: T,
info: &'info AccountInfo<'info>,
}
+

基本功能

    +
  1. 检查 T::owner() 是否等于 info.owner,这就要求 T: Owner。通常使用 #[account] 宏来自动为 T 实现 Owner,并使用 crate::ID() 也就是合约地址作为它的 owner。
  2. +
  3. 此外,它还会保证 !(Account.info.owner == SystemProgram && Account.info.lamports() == 0),即账户所有者不是系统程序(必须是非系统合约),并且账户的 SOL 余额不能为 0。
  4. +
+

AccountLoader

+

anchor_lang::accounts::account_loader::AccountLoader

+
+

账户加载器,用于按需零拷贝反序列化的提取器,类型 T 需要实现 ZeroCopy

+
1
2
3
4
5
#[derive(Clone)]
pub struct AccountLoader<'info, T: ZeroCopy + Owner> {
acc_info: &'info AccountInfo<'info>,
phantom: PhantomData<&'info T>,
}
+

基本功能

load_init: 在初始化时(只能)运行一次,得到一个 RefMut<T>,可读写。
load: 加载只读引用 Ref<T>
load_mut: 加载读写引用 RefMut<T>

+

InterfaceAccount

+

anchor_lang::accounts::interface_account::InterfaceAccount

+
+

接口账户,用来支持 T: Owners 的情况。即有多个程序拥有这种类型的账户数据,引入时是为了支持 token-2022 #2386

+
1
2
3
4
5
6
7
#[derive(Clone)]
pub struct InterfaceAccount<'info, T: AccountSerialize + AccountDeserialize + Clone> {
account: Account<'info, T>,
// The owner here is used to make sure that changes aren't incorrectly propagated
// to an account with a modified owner
owner: Pubkey,
}
+

基本功能

    +
  1. 检查所有者,即 T::owners().contains(InterfaceAccount.info.owner)
  2. +
  3. Account 的第二项。
  4. +
+

Interface

+

anchor_lang::accounts::interface::Interface

+
+

接口,用来表示实现了某种接口的程序中的某一个。

+
1
2
#[derive(Clone)]
pub struct Interface<'info, T>(Program<'info, T>);
+

TODO: 例子。

+

Program

+

anchor_lang::accounts::program::Program

+
+

程序,表示一个程序/合约。

+
1
2
3
4
5
#[derive(Clone)]
pub struct Program<'info, T> {
info: &'info AccountInfo<'info>,
_phantom: PhantomData<T>,
}
+

Signer

+

anchor_lang::accounts::signer::Signer

+
+

签名者,检查 Signer.info.is_signer == true

+
1
2
3
4
#[derive(Debug, Clone)]
pub struct Signer<'info> {
info: &'info AccountInfo<'info>,
}
+

SystemAccount

+

anchor_lang::accounts::system_account::SystemAccount

+
+

系统账户,检查账户的拥有者是不是系统程序(即 SystemAccount.info.owner == SystemProgram)。

+
1
2
3
4
#[derive(Debug, Clone)]
pub struct SystemAccount<'info> {
info: &'info AccountInfo<'info>,
}
+

Sysvar

+

anchor_lang::accounts::sysvar::Sysvar

+
+

系统变量,检查账户是否是系统变量。

+
1
2
3
4
5

pub struct Sysvar<'info, T: solana_program::sysvar::Sysvar> {
info: &'info AccountInfo<'info>,
account: T,
}
+

TODO: 写一篇 post 单独介绍实现了 solana_program::sysvar::Sysvar 的类型。

+

UncheckedAccount

强调不做任何检查的账户,需要手动在合约指令中做检查。

+
1
2
#[derive(Debug, Clone)]
pub struct UncheckedAccount<'info>(&'info AccountInfo<'info>);
+ +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

以下是各种索引算法的简要对比表,包括其原理、性能优势、适用场景和缺点。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
索引算法原理介绍为什么性能高适用场景缺点/性能差的场景
B+ 树B+ 树是一种平衡多叉树,数据仅存储在叶节点,所有叶节点按顺序连接,适合范围查询和顺序访问。结构层次分明,叶节点通过链表相连,实现快速范围查找和顺序遍历。适用于数据库索引、大量顺序读取或范围查询的场景。高维数据或频繁更新的场景性能较差。插入、删除操作复杂,维护成本高。
倒排索引建立词项到文档的映射关系,将每个词项关联到出现该词的文档列表,通常用于全文搜索。通过直接访问词项位置,可快速找到包含该词的所有文档。适用于文本搜索、日志检索等全文检索场景。对于非文本数据无效,处理频繁更新的文档集时性能较差,占用内存大。
HNSWHNSW 基于分层的小世界图结构,通过近似最近邻的方法实现高效向量检索。通过多层图索引和邻居节点搜索,实现快速的近似最近邻查找。适用于向量相似度检索、推荐系统、图像搜索等高维向量场景。构建和维护图的成本较高,尤其是频繁更新和插入向量的情况下。对高精度要求场景不适合。
跳表(Skip List)通过多级链表实现快速搜索,每级链表跳过部分节点,提高查找速度。基于多级链表,可在 O(log n) 时间内查找目标节点。适用于键值存储、顺序查询需求的场景,作为简单替代结构。在超大数据集或高频率更新的场景下不够高效,性能会下降。
LSM 树LSM 树通过分层排序和合并,将数据写入磁盘前缓存在内存,适合高写入量的场景。数据批量写入减少随机写的开销,分层合并提升读取效率。适合写密集型的键值数据库,如 RocksDB、LevelDB。读性能可能较差,特别是大量读操作时,需通过多层合并查询。
Trie 树用树结构保存字符串的前缀,每个节点代表一个字符,用于高效的前缀匹配。可以快速定位字符串的前缀路径,适合匹配和自动补全。适用于自动补全、前缀匹配等需要快速定位前缀的场景。占用内存较大,无法很好地处理非字符串数据,不适合范围查询。
Annoy使用多棵随机 KD 树构建索引,用于近似最近邻查询,查询时从多棵树中查找相似点。随机生成多棵树,提高查找相似度的效率,查询速度快。适用于低维度、高性能要求的向量检索,推荐系统等。处理高维数据时性能较差,存储空间需求大。更新不便。
LSH(Locality-Sensitive Hashing)将相似数据哈希到相同的桶中,快速找到相似数据的近似最近邻索引方法。通过哈希实现分桶,减少相似数据查询范围,查询速度快。适用于低维向量的相似性搜索,处理文本等场景。高维度数据上效率低,近似度精度差,占用较多内存。
+
+

简要总结

    +
  • B+ 树倒排索引 是经典的数据库和全文搜索引擎中常用的索引结构,适合关系型数据和文本数据的检索。
  • +
  • HNSWAnnoy 是面向高维向量的近似最近邻索引结构,常用于推荐系统、语义搜索等场景。
  • +
  • LSM 树 适合写密集的场景,如键值数据库。
  • +
  • Trie 树跳表 更适合简单的键值存储和字符串匹配,但不适合复杂的数据查询和高维向量检索。
  • +
  • LSH 在维度较低的数据上表现较好,但对高维数据表现有限。
  • +
+ + +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

Why ZKP?

零知识证明 Zero-knowledge Proofs

+

其中的 Proof($\pi$) 必须是个有用的:$Theorem / Statement / Proposition$

+
    +
  1. 不要把 Proof 这个词忘掉,到底证明了什么?
  2. +
  3. 两个角色,证明者(prover)、验证者(verifier),是双方的。写代码的时候要时刻关注写的到底是哪部分,是证明者的代码,还是验证者的代码。
  4. +
+

Prover Code: 需要关注,有没有产生一个有效的证明,产生这个证明的时候有没有泄漏不该泄漏的信息。
Verifier Code:关注,这个证明是不是有效的。

+
+

写代码的时候容易忘掉。
这是一个 Two-party interaction

+
+
    +
  1. 关注以下三个性质:
  2. +
+
    +
  • 完备性(Completeness):诚实的证明者(honest prover)一定能成功证明。
  • +
  • 可靠性(Soundness):恶意的证明者(malicious prover)一定通不过验证。很难测试,需要非常认真、仔细、深入的考虑这个问题:如何保证 verifier 不会被欺骗。
  • +
  • 零知识(Zero-knowledge):恶意的验证者(malicious verifier)无法获得有用的知识。
  • +
+
+

关注 Meta Theorems (Completeness and Soundness)的证明
如何保证我们写的 babyplonk 是 sound 的。

+
+
    +
  • Prover 和 Verifier 之间有大量的交互。Verifier 会发送大量的 challenge 给 Prover。challenge 越多,Proof Size 就越大。
  • +
  • 交互过程中的步骤顺序不能调换。
  • +
  • 实际项目中如果有安全漏洞是非常严重的。
  • +
  • 攻击本身也可能是 ZK 的,不知道谁干的。
  • +
+

Non-interactive

BlockChain 中经常使用 Non-interactive ZKP。 Fiat-Shamir Transformation 能把 public-coin protocol 转换成 non-interactive protocol。

+
+

public-coin 指的是掷硬币,公开的发随机数 challenge。
借助 Random Oracle (RO) 工具来辅助 Prover 来产生这些硬币。随机数不一定是由 verifier 作为来源,由可信第三方来提供也可以。
Prover 与 RO 交互,产生证明 $\pi$ 直接发送给 Verifier。不需要等待 Verifier 回复,Verifier 可以在未来异步验证这个证明。
通常使用一个足够安全的哈希函数(Cryptographic Hash Function)来替代 RO 的角色。没有严格的证明,大概率不会出问题。

+
+

Commitment

    +
  1. $O(1)$ Size。无论数据多大,比如 10 GB 的文件,都可以产生一个很小的串来代表它。
  2. +
  3. Binding。$CM(D1)\not ={CM(D2)} \iff D1\not ={D2}$。
  4. +
  5. Hiding。能够保护数据,可以混入随机数。同一个文件两次 Commit 可以得到不同的 Commitment。
  6. +
+

Blockchain: Put commitment on chain (EIP-4844,Rollup-blob)

+

Commit-and-Prove

alt text

+

Why zkSNARK

+ +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

这是结诚浩的《图解密码技术》一书的读书笔记,我是在去北京的火车上把其中感兴趣的部分草草过完的。World of Z2O-K7E 只是建议过一下,我就只是过一下,感觉还是很有帮助的,至少看过了之后,我能感觉自己的大脑切换到了“密码学的上下文”,接收零知识证明相关的知识似乎更高效了。

+

基本常识

    +
  1. 不要使用保密的密码算法(即隐蔽式的安全性),应该使用得到广为验证的公开的密码算法,而保护好密钥。
  2. +
  3. 使用低强度的密码会给人一种错误的安全感,比不进行加密更危险。
  4. +
  5. 任何密码总有一天会被破解,除了不实用的一次性密码本和传说中的量子密码。
  6. +
  7. 密码只是信息安全的一部分,提防“社会工程学”。
  8. +
+

对称密码

DES / 3DES

一种将 64 位明文加密为 64 位密文的对称密码算法,需要以分组为单位对输入进行处理(这样的密码算法称为分组密码,block cipher)。DES 已经可以在现实时间内被破解。

+

三重DES(3DES)是为了增加 DES 的强度设计的。三重 DES 并不是三轮加密,而是加密、解密、加密。如果三轮的密钥都相同,就跟 DES 一样了。

+

第一轮和第三轮使用同样的密钥则称为 DES-EDE2。
三轮都不同则称为 DES-EDE3。

+

AES

Rijndael 算法在 2000 年被选为 AES。

+

分组长度固定为 128 比特,密钥长度有 128、192、256 比特三种。

+

加密过程:SubBytes -> ShiftRows -> MixColumns -> AddRoundKey

+

解密过程: AddRoundKey -> InvMixColumns -> InvShiftRows -> InvSubBytes

+

如何选择

不用 DES,少用 3DES,尽量用 AES。

+

分组密码的模式

    +
  • ECB(Electronic CodeBook,电子密码本):简单,快速,支持并行计算;明文中重复排列会反映在密文中,并且通过删除、替换密文分组可以对明文进行操作,对包含某些比特错误的密文解密时对应的分组会出错,不能抵御重放攻击。不要用。
  • +
  • CBC(Cipher Book Chaining,密文分组连接模式):推荐使用,重复的明文不会导致重复的密文,可以解密任意密文分组。必须是先加密再 XOR,如果反过来,则其效果等同于 ECB,可以直接从 IV (初始化向量)开始,依次将前后两组密文执行 XOR,得到的每组结果恰好就等于 ECB 每组的结果。
  • +
  • CFB / OFB:不需要填充(padding)。
  • +
  • CTR(CounTeR,计数器模式):推荐使用,主动攻击者反转密文分组中的某些比特时,明文分组中相对应的比特也会被反转。
  • +
+

公钥密码

RSA 的基本步骤:

+
    +
  1. 取两个比较大的质数 p 和 q。
  2. +
  3. 求两者的乘积 $N = p \times q$。
  4. +
  5. ,即 p 和 q 的最小公倍数;有的资料说的是求欧拉函数值 $\phi = (p-1) \times (q-1)$,以下统称 $\phi$。
  6. +
+
+

GPT: 两者都有各自的使用场景,但在实际使用中,求欧拉函数值 ($\phi$) 更为常见。

+
+
    +
  1. 选择公钥 $E$,$E$ 与 $\phi$ 互质并且 $1 < E < \phi$。
  2. +
  3. 求私钥 $D$,$D \times E \equiv 1$ (mod $\phi$),即 D 为 E 的乘法逆元。
  4. +
  5. 这样得到公钥为 $(E, N)$,私钥为 $(D, N)$。
  6. +
+

一个使用小质数实现的简单的 RSA:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
use num::BigInt;
use rand::seq::index::sample;
use rand::{thread_rng, Rng};

const SMALL_PRIMES: [u64; 50] = [
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193,
197, 199, 211, 223, 227, 229,
];

#[derive(Debug)]
struct KeyPair {
private_key: u64,
public_key: u64,
modulus: u64,
}

impl KeyPair {
pub fn generate() -> Self {
let mut rng = thread_rng();
let indices = sample(&mut rng, SMALL_PRIMES.len(), 2);
// use crate rand to select two random prime numbers
let p = SMALL_PRIMES[indices.index(0)];
let q = SMALL_PRIMES[indices.index(1)];
let modulus = p * q;
let phi = (p - 1) * (q - 1);
let public_key = loop {
let public_key = SMALL_PRIMES[thread_rng().gen_range(0..SMALL_PRIMES.len())];
if public_key < phi && gcd(public_key, phi) == 1 {
break public_key;
}
};

let mut private_key = 1;
while (public_key * private_key) % phi != 1 {
private_key += 1;
}

Self {
private_key,
public_key,
modulus,
}
}

pub fn encrypt(&self, message: BigInt) -> BigInt {
message.pow(self.public_key as u32) % self.modulus
}

pub fn decrypt(&self, encrypted_message: BigInt) -> BigInt {
encrypted_message.pow(self.private_key as u32) % self.modulus
}

// getters

pub fn get_private_key(&self) -> (u64, u64) {
(self.private_key, self.modulus)
}

pub fn get_public_key(&self) -> (u64, u64) {
(self.public_key, self.modulus)
}

pub fn get_modulus(&self) -> u64 {
self.modulus
}
}

fn gcd(p0: u64, p1: u64) -> u64 {
let mut p0 = p0;
let mut p1 = p1;
while p1 != 0 {
let temp = p1;
p1 = p0 % p1;
p0 = temp;
}
p0
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_rsa() {
for _ in 0..100 {
let key_pair = KeyPair::generate();
let message = BigInt::from(rand::thread_rng().gen_range(6..key_pair.get_modulus()));
let encrypted_message = key_pair.encrypt(message.clone());
let decrypted_message = key_pair.decrypt(encrypted_message.clone());
assert_eq!(decrypted_message, message);
println!(
"public key: ({}, {}), private key: ({}, {}), message: {}, encrypted: {}, decrypted: {}",
key_pair.get_public_key().0, key_pair.get_public_key().1,
key_pair.get_private_key().0, key_pair.get_private_key().1,
message,
encrypted_message,
decrypted_message
)
}
}
}
+

单向散列函数

单向散列函数(也称为消息摘要函数、哈希函数、杂凑函数),用来保证消息的完整性(integrity),也称为一致性。

+

输入的消息也称为原像(pre-image)。

+

输出的散列值也称为消息摘要(message digest)或者指纹(fingerprint)。

+

难以发现碰撞的性质称为抗碰撞性(collision resistance)。

+

算法选择

MD5 是不安全的。

+

SHA-1 不应该用于新用途。

+

SHA-2 (SHA-256以上) 、SHA-3 是安全的,可以使用。

+

消息认证码

消息认证码(Message Authentication Code)是一种确认完整性并进行认证的技术。

+

实现方法

    +
  • 单向散列函数,例如 HMAC。
  • +
  • 分组密码,例如 AES-CMAC。
  • +
  • 其他,例如流密码和公钥密码。
  • +
+

数字签名

使用公钥密码(比如 RSA、椭圆曲线)来实现。

+
    +
  • 绝对不要对意思不清楚的消息进行签名,可能无意中解密了公钥加密的密文。
  • +
  • 依赖公钥基础设施,公钥必须属于真正的发送者,即使用证书来确认。
  • +
+

密钥交换

Diffie-Hellman

一个使用小质数实现的简单的 Diffie-Hellman:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
use num::BigInt;
use rand::thread_rng;
use rand::Rng;

const P_AND_G: [(u64, u64); 20] = [
(7, 3),
(11, 2),
(13, 2),
(17, 3),
(19, 2),
(23, 5),
(29, 2),
(31, 3),
(37, 2),
(41, 6),
(43, 3),
(47, 5),
(53, 2),
(59, 2),
(61, 2),
(67, 2),
(71, 7),
(73, 5),
(79, 3),
(83, 2),
];

// prime P and generator G
#[derive(Debug)]
pub struct PnG {
prime: u64,
generator: u64,
}

impl PnG {
pub fn generate() -> Self {
let (prime, generator) = P_AND_G[thread_rng().gen_range(0..P_AND_G.len())];
Self { prime, generator }
}

pub fn get_prime(&self) -> u64 {
self.prime
}

pub fn get_generator(&self) -> u64 {
self.generator
}

pub fn generate_random_number(&self) -> u64 {
// generate a number in the range of 1 to prime - 2
thread_rng().gen_range(1..self.prime - 2)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_diffie_hellman() {
for round in 0..10 {
let pg = PnG::generate();
println!(
"{round}) Prime: {}, Generator: {}",
pg.get_prime(),
pg.get_generator()
);
let alice_number = pg.generate_random_number();
let bob_number = pg.generate_random_number();
println!(
"{round}) Alice number: {}, Bob number: {}",
alice_number, bob_number
);
let alice_partial_key =
BigInt::from(pg.get_generator()).pow(alice_number as u32) % pg.get_prime();
let bob_partial_key =
BigInt::from(pg.get_generator()).pow(bob_number as u32) % pg.get_prime();
println!(
"{round}) Alice partial key: {}, Bob partial key: {}",
alice_partial_key, bob_partial_key
);
let alice_full_key =
BigInt::from(bob_partial_key).pow(alice_number as u32) % pg.get_prime();
let bob_full_key =
BigInt::from(alice_partial_key).pow(bob_number as u32) % pg.get_prime();
println!(
"{round}) Alice full key: {}, Bob full key: {}",
alice_full_key, bob_full_key
);
assert_eq!(alice_full_key, bob_full_key);
}
}
}
+

椭圆曲线密码

椭圆曲线密码是利用“椭圆曲线上的离散对数问题”的复杂度来实现的。

+

使用了椭圆曲线的 schnorr 的一个简单实现:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
use k256::{NonZeroScalar, ProjectivePoint, PublicKey};
use rand::rngs::OsRng;

fn main() {
// 使用操作系统级别的 RNG
let mut rng = OsRng;

// Alice 拥有一个私钥 a,计算公钥 aG
// 私钥 a
let a = NonZeroScalar::random(&mut rng);
// 公钥 aG
let a_g = ProjectivePoint::from(PublicKey::from_secret_scalar(&a));

// 知道 G 和 aG,求 a,属于椭圆曲线上的离散对数问题,目前没有有效的解法
let r = NonZeroScalar::random(&mut rng);
// Alice 生成一个随机数 r,并计算 R = rG
let r_g = ProjectivePoint::from(PublicKey::from_secret_scalar(&r));

// Bob 提供一个挑战 c
let c = NonZeroScalar::random(&mut rng);

// Alice 计算响应 z = r + a * c (mod q)
let ac = a * c; // 计算 a * c
let z = r.add(&ac);

let Some(z) = NonZeroScalar::new(z).into() else {
eprintln!("计算 z 为零。");
return;
};

// Alice 将 z 发送给 Bob,Bob 验证 zG ?= R + c*PK
let z_g = ProjectivePoint::from(PublicKey::from_secret_scalar(&z));

// 计算 c * PK
let c_pk = a_g * *c;

// 计算 rG + c*PK
let r_plus_cpk = r_g + c_pk;

// 验证 zG ?= rG + c*PK
// c * PK 与 a * c 的关系是?
// 为什么这个证明是成立的
// 为什么这个证明是不可伪造的
// 回答:
// c * PK = c * a * G = a * c * G = a * c * G = ac * G
// PK = aG ?
if z_g == r_plus_cpk {
println!("验证成功,Alice 证明了她拥有私钥.");
} else {
println!("验证失败.");
}
}

// 与 GPT 的交谈:
// 为什么 Alice 要先生成一个随机数?
// 答:为了证明她拥有私钥,她需要一个随机数 r,然后计算 R = rG,然后计算 z = r + a * c (mod q)
// 可以让 Bob 先生成随机数并发送给 Alice 吗?(然后 Alice 再生成 r ,发送 rG)
// 答:可以,但是这样会暴露 Bob 的私钥,因为 Bob 会发送 c * PK,PK = aG,所以 Bob 的私钥就会暴露
// 不对啊,Bob 只需要发送 c,不会暴露私钥啊
// 答:对,Bob 只需要发送 c,不会暴露私钥,但是这样会暴露 Bob 的公钥,Bob 的公钥是 aG,这样会暴露 Bob 的身份
// 不对啊,aG 是 Alice 使用私钥 a 计算出来的公钥,怎么会是 Bob 的公钥呢?
// 答:对,我理解错了,Bob 只需要发送 c,不会暴露私钥,也不会暴露公钥,这样是可以的
// Bob 先发送随机数给 Alice 真的没问题吗?Alice 不会获得没有 a 但是证明她拥有 a 的超能力吗?
// 答:Alice 不会获得没有 a 但是证明她拥有 a 的超能力,因为 Alice 需要 a 来计算 z = r + a * c (mod q),如果没有 a,Alice 就无法计算 z
// Alice 不能用假的 a' 来计算 z 吗?一直用假的 a', 是否也能通过验证?
// 答:不能,因为 Bob 会用 a' 来计算 c * PK,然后验证 zG ?= R + c * PK,如果 a' 是错误的,那么 c * PK 也是错误的,验证就会失败
// PK 和 zG 都是用 a' 计算出来的,所以 Bob 永远都会验证成功,不管 Alice 用了什么 a',验证都会成功,这样 Alice 是否就获得的超能力
// 答:不会,因为 Alice 无法计算出正确的 z,验证会失败
// z' = r + a' * c (mod q),所以 z'G = R + c * a' * G 验证肯定成功吧?
// 答:不会,因为 c * a' * G != c * a * G,验证会失败
// 你好像没有理解,如果 Bob 实现发送了挑战 c,Alice 可以使用任何除了 a 之外的密钥替换 a(比如 a'),这样整个流程只是证明了 Alice 拥有一个密钥(这个密钥并不一定是 a)
// 答:对,我理解错了,如果 Bob 先发送了挑战 c,Alice 可以使用任何除了 a 之外的密钥替换 a(比如 a'),这样整个流程只是证明了 Alice 拥有一个密钥(这个密钥并不一定是 a)
// 所以 Alice 即使没有任何密钥,她临时编造一个,在这种情况下,Bob 永远都无法验证 Alice 是否拥有私钥,这样 Alice 就获得了超能力
// 所以顺序不能调换,对吗?
// 答:对,顺序不能调换,Alice 必须先生成一个随机数 r,然后计算 R = rG,然后计算 z = r + a * c (mod q),然后发送 z 给 Bob,Bob 验证 zG ?= R + c * PK
+ +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

目标

从今天开始,学习零知识证明相关知识,暂定 3 个月(到2024年7月26日完成)的学习目标为:

+
    +
  1. 完成零知识证明常见算法、实现的学习,至少能够知道它们解决的是什么问题,分别适用于什么业务场景。
  2. +
  3. 学会并熟练掌握使用至少一个流行的 Rust 零知识证明库。
  4. +
  5. 参与至少一个零知识证明的(开源?)项目,需要提交有质量的代码。
  6. +
+
+

初步计划

V20240426

    +
  1. 第一个月: World of Z2O-K7E 的学习;产出为“能够在朋友、同事甚至是小学生(如果我有幸能认识一个愿意听我讲的)面前把学到的哲学、原理、应用讲出来,讲清楚”。
  2. +
  3. 第二个月: 完成 World of Z2O-K7E 的学习并开始学习 Rust 相关库,写 demo;学习产出参考上一个月,写 demo 的产出为能够全面反映该 Rust 库的各种功能的一个 repo。
  4. +
  5. 第三个月: 继续写 demo,看相关开源项目源码,参与项目;产出物为 项目地址 / commit / PR 记录等等。
  6. +
+
+

介于本人在该领域是完全的“零知识”,以上仅为初步计划,如果有任何修改(即使是放弃)都需要更新本文。

+
+

20240530

一个多月了,公司的事有点烦。实际上我话太少,并没有找人验证过我是否能证明我能讲出所有我学到的东西,目前学习进度并不快,只是能够把 RSA 和 schnorr 的流程完整复述出来(并用小质数作为例子介绍)。报了一个课,希望能学到我想学的东西,后面打算整理这个课的笔记了。继续加油!

+ + +
+ + + + +
+
+
+
+ + + + + + + +
+ + + + + +
+

+ + +

+ + +
+ + + + +
+ + +

定义

    +
  • $S$、$R$ 为关系模式,$s$、$r$ 为关系实例。
  • +
  • 要计算关系除法,需要满足 $S ⊆ R$。
  • +
  • 给定一个元组 $t$,令 $t[S]$ 代表元组 $t$ 在 $S$ 中属性上的投影;那么,$r ÷ s$ 是 $R-S$ 模式的一个关系。
  • +
  • 元组 $t$ 在 $r ÷ s$ 中的充要条件是满足以下两个
      +
    1. $t$ 在 $Π_{R-S}(r)$ 中
    2. +
    3. 对于 $s$ 中的每个元组 $t_s$,在 $r$ 中存在一个元组 $t_r$ 且满足:
        +
      • $t_r[S] = t_s[S]$
      • +
      • $t_r[R-S] = t$
      • +
      +
    4. +
    +
  • +
  • 例子:
  • +
+

$r$:

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
A1A2
ae
af
be
bg
ce
cf
df
+
+

$s$:

+
+ + + + + + + + + + + + + + +
A2
e
f
+
+

$r ÷ s$:

+
+ + + + + + + + + + + + + + +
A1
a
c
+
+

公式

关系代数除法可以使用其它关系代数运算替代:

+

用上一节的例子就是:

+
+ + + + + + + + + + + + + + + + + + + + +
A1
a
b
c
d
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
A1A2
ae
af
be
bf
ce
cf
de
df
+
+
+

公式中是为了计算差($-$)的时候两个操作数模式相同,对 $r$ 做了一下模式的颠倒,其实还是 $r$。

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
A1A2
ae
af
be
bg
ce
cf
df
+
+
+
+ + + + + + + + + + + + + + + + + +
A1A2
bf
de
+
+
+
+ + + + + + + + + + + + + + +
A1
b
d
+
+
+
+ + + + + + + + + + + + + + +
A1
a
c
+
+ + +
+ + + + +
+
+
+
+ + + + + + + + + + +
+ + + + +
+ + + + + + + + +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/js/algolia-search.js b/js/algolia-search.js new file mode 100644 index 0000000..01a5f0b --- /dev/null +++ b/js/algolia-search.js @@ -0,0 +1,124 @@ +/* global instantsearch, algoliasearch, CONFIG */ + +document.addEventListener('DOMContentLoaded', () => { + const algoliaSettings = CONFIG.algolia; + const { indexName, appID, apiKey } = algoliaSettings; + + let search = instantsearch({ + indexName, + searchClient : algoliasearch(appID, apiKey), + searchFunction: helper => { + let searchInput = document.querySelector('.search-input'); + if (searchInput.value) { + helper.search(); + } + } + }); + + window.pjax && search.on('render', () => { + window.pjax.refresh(document.getElementById('algolia-hits')); + }); + + // Registering Widgets + search.addWidgets([ + instantsearch.widgets.configure({ + hitsPerPage: algoliaSettings.hits.per_page || 10 + }), + + instantsearch.widgets.searchBox({ + container : '.search-input-container', + placeholder : algoliaSettings.labels.input_placeholder, + // Hide default icons of algolia search + showReset : false, + showSubmit : false, + showLoadingIndicator: false, + cssClasses : { + input: 'search-input' + } + }), + + instantsearch.widgets.stats({ + container: '#algolia-stats', + templates: { + text: data => { + let stats = algoliaSettings.labels.hits_stats + .replace(/\$\{hits}/, data.nbHits) + .replace(/\$\{time}/, data.processingTimeMS); + return `${stats} + + Algolia + +
`; + } + } + }), + + instantsearch.widgets.hits({ + container: '#algolia-hits', + templates: { + item: data => { + let link = data.permalink ? data.permalink : CONFIG.root + data.path; + return `${data._highlightResult.title.value}`; + }, + empty: data => { + return `
+ ${algoliaSettings.labels.hits_empty.replace(/\$\{query}/, data.query)} +
`; + } + }, + cssClasses: { + item: 'algolia-hit-item' + } + }), + + instantsearch.widgets.pagination({ + container: '#algolia-pagination', + scrollTo : false, + showFirst: false, + showLast : false, + templates: { + first : '', + last : '', + previous: '', + next : '' + }, + cssClasses: { + root : 'pagination', + item : 'pagination-item', + link : 'page-number', + selectedItem: 'current', + disabledItem: 'disabled-item' + } + }) + ]); + + search.start(); + + // Handle and trigger popup window + document.querySelectorAll('.popup-trigger').forEach(element => { + element.addEventListener('click', () => { + document.body.style.overflow = 'hidden'; + document.querySelector('.search-pop-overlay').classList.add('search-active'); + document.querySelector('.search-input').focus(); + }); + }); + + // Monitor main search box + const onPopupClose = () => { + document.body.style.overflow = ''; + document.querySelector('.search-pop-overlay').classList.remove('search-active'); + }; + + document.querySelector('.search-pop-overlay').addEventListener('click', event => { + if (event.target === document.querySelector('.search-pop-overlay')) { + onPopupClose(); + } + }); + document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose); + window.addEventListener('pjax:success', onPopupClose); + window.addEventListener('keyup', event => { + if (event.key === 'Escape') { + onPopupClose(); + } + }); +}); diff --git a/js/bookmark.js b/js/bookmark.js new file mode 100644 index 0000000..7c2438e --- /dev/null +++ b/js/bookmark.js @@ -0,0 +1,56 @@ +/* global CONFIG */ + +document.addEventListener('DOMContentLoaded', () => { + 'use strict'; + + var doSaveScroll = () => { + localStorage.setItem('bookmark' + location.pathname, window.scrollY); + }; + + var scrollToMark = () => { + var top = localStorage.getItem('bookmark' + location.pathname); + top = parseInt(top, 10); + // If the page opens with a specific hash, just jump out + if (!isNaN(top) && location.hash === '') { + // Auto scroll to the position + window.anime({ + targets : document.scrollingElement, + duration : 200, + easing : 'linear', + scrollTop: top + }); + } + }; + // Register everything + var init = function(trigger) { + // Create a link element + var link = document.querySelector('.book-mark-link'); + // Scroll event + window.addEventListener('scroll', () => link.classList.toggle('book-mark-link-fixed', window.scrollY === 0)); + // Register beforeunload event when the trigger is auto + if (trigger === 'auto') { + // Register beforeunload event + window.addEventListener('beforeunload', doSaveScroll); + window.addEventListener('pjax:send', doSaveScroll); + } + // Save the position by clicking the icon + link.addEventListener('click', () => { + doSaveScroll(); + window.anime({ + targets : link, + duration: 200, + easing : 'linear', + top : -30, + complete: () => { + setTimeout(() => { + link.style.top = ''; + }, 400); + } + }); + }); + scrollToMark(); + window.addEventListener('pjax:success', scrollToMark); + }; + + init(CONFIG.bookmark.save); +}); diff --git a/js/local-search.js b/js/local-search.js new file mode 100644 index 0000000..31f945f --- /dev/null +++ b/js/local-search.js @@ -0,0 +1,278 @@ +/* global CONFIG */ + +document.addEventListener('DOMContentLoaded', () => { + // Popup Window + let isfetched = false; + let datas; + let isXml = true; + // Search DB path + let searchPath = CONFIG.path; + if (searchPath.length === 0) { + searchPath = 'search.xml'; + } else if (searchPath.endsWith('json')) { + isXml = false; + } + const input = document.querySelector('.search-input'); + const resultContent = document.getElementById('search-result'); + + const getIndexByWord = (word, text, caseSensitive) => { + if (CONFIG.localsearch.unescape) { + let div = document.createElement('div'); + div.innerText = word; + word = div.innerHTML; + } + let wordLen = word.length; + if (wordLen === 0) return []; + let startPosition = 0; + let position = []; + let index = []; + if (!caseSensitive) { + text = text.toLowerCase(); + word = word.toLowerCase(); + } + while ((position = text.indexOf(word, startPosition)) > -1) { + index.push({ position, word }); + startPosition = position + wordLen; + } + return index; + }; + + // Merge hits into slices + const mergeIntoSlice = (start, end, index, searchText) => { + let item = index[index.length - 1]; + let { position, word } = item; + let hits = []; + let searchTextCountInSlice = 0; + while (position + word.length <= end && index.length !== 0) { + if (word === searchText) { + searchTextCountInSlice++; + } + hits.push({ + position, + length: word.length + }); + let wordEnd = position + word.length; + + // Move to next position of hit + index.pop(); + while (index.length !== 0) { + item = index[index.length - 1]; + position = item.position; + word = item.word; + if (wordEnd > position) { + index.pop(); + } else { + break; + } + } + } + return { + hits, + start, + end, + searchTextCount: searchTextCountInSlice + }; + }; + + // Highlight title and content + const highlightKeyword = (text, slice) => { + let result = ''; + let prevEnd = slice.start; + slice.hits.forEach(hit => { + result += text.substring(prevEnd, hit.position); + let end = hit.position + hit.length; + result += `${text.substring(hit.position, end)}`; + prevEnd = end; + }); + result += text.substring(prevEnd, slice.end); + return result; + }; + + const inputEventFunction = () => { + if (!isfetched) return; + let searchText = input.value.trim().toLowerCase(); + let keywords = searchText.split(/[-\s]+/); + if (keywords.length > 1) { + keywords.push(searchText); + } + let resultItems = []; + if (searchText.length > 0) { + // Perform local searching + datas.forEach(({ title, content, url }) => { + let titleInLowerCase = title.toLowerCase(); + let contentInLowerCase = content.toLowerCase(); + let indexOfTitle = []; + let indexOfContent = []; + let searchTextCount = 0; + keywords.forEach(keyword => { + indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false)); + indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false)); + }); + + // Show search results + if (indexOfTitle.length > 0 || indexOfContent.length > 0) { + let hitCount = indexOfTitle.length + indexOfContent.length; + // Sort index by position of keyword + [indexOfTitle, indexOfContent].forEach(index => { + index.sort((itemLeft, itemRight) => { + if (itemRight.position !== itemLeft.position) { + return itemRight.position - itemLeft.position; + } + return itemLeft.word.length - itemRight.word.length; + }); + }); + + let slicesOfTitle = []; + if (indexOfTitle.length !== 0) { + let tmp = mergeIntoSlice(0, title.length, indexOfTitle, searchText); + searchTextCount += tmp.searchTextCountInSlice; + slicesOfTitle.push(tmp); + } + + let slicesOfContent = []; + while (indexOfContent.length !== 0) { + let item = indexOfContent[indexOfContent.length - 1]; + let { position, word } = item; + // Cut out 100 characters + let start = position - 20; + let end = position + 80; + if (start < 0) { + start = 0; + } + if (end < position + word.length) { + end = position + word.length; + } + if (end > content.length) { + end = content.length; + } + let tmp = mergeIntoSlice(start, end, indexOfContent, searchText); + searchTextCount += tmp.searchTextCountInSlice; + slicesOfContent.push(tmp); + } + + // Sort slices in content by search text's count and hits' count + slicesOfContent.sort((sliceLeft, sliceRight) => { + if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) { + return sliceRight.searchTextCount - sliceLeft.searchTextCount; + } else if (sliceLeft.hits.length !== sliceRight.hits.length) { + return sliceRight.hits.length - sliceLeft.hits.length; + } + return sliceLeft.start - sliceRight.start; + }); + + // Select top N slices in content + let upperBound = parseInt(CONFIG.localsearch.top_n_per_article, 10); + if (upperBound >= 0) { + slicesOfContent = slicesOfContent.slice(0, upperBound); + } + + let resultItem = ''; + + if (slicesOfTitle.length !== 0) { + resultItem += `
  • ${highlightKeyword(title, slicesOfTitle[0])}`; + } else { + resultItem += `
  • ${title}`; + } + + slicesOfContent.forEach(slice => { + resultItem += `

    ${highlightKeyword(content, slice)}...

    `; + }); + + resultItem += '
  • '; + resultItems.push({ + item: resultItem, + id : resultItems.length, + hitCount, + searchTextCount + }); + } + }); + } + if (keywords.length === 1 && keywords[0] === '') { + resultContent.innerHTML = '
    '; + } else if (resultItems.length === 0) { + resultContent.innerHTML = '
    '; + } else { + resultItems.sort((resultLeft, resultRight) => { + if (resultLeft.searchTextCount !== resultRight.searchTextCount) { + return resultRight.searchTextCount - resultLeft.searchTextCount; + } else if (resultLeft.hitCount !== resultRight.hitCount) { + return resultRight.hitCount - resultLeft.hitCount; + } + return resultRight.id - resultLeft.id; + }); + resultContent.innerHTML = ``; + window.pjax && window.pjax.refresh(resultContent); + } + }; + + const fetchData = () => { + fetch(CONFIG.root + searchPath) + .then(response => response.text()) + .then(res => { + // Get the contents from search data + isfetched = true; + datas = isXml ? [...new DOMParser().parseFromString(res, 'text/xml').querySelectorAll('entry')].map(element => { + return { + title : element.querySelector('title').textContent, + content: element.querySelector('content').textContent, + url : element.querySelector('url').textContent + }; + }) : JSON.parse(res); + // Only match articles with not empty titles + datas = datas.filter(data => data.title).map(data => { + data.title = data.title.trim(); + data.content = data.content ? data.content.trim().replace(/<[^>]+>/g, '') : ''; + data.url = decodeURIComponent(data.url).replace(/\/{2,}/g, '/'); + return data; + }); + // Remove loading animation + document.getElementById('no-result').innerHTML = ''; + inputEventFunction(); + }); + }; + + if (CONFIG.localsearch.preload) { + fetchData(); + } + + if (CONFIG.localsearch.trigger === 'auto') { + input.addEventListener('input', inputEventFunction); + } else { + document.querySelector('.search-icon').addEventListener('click', inputEventFunction); + input.addEventListener('keypress', event => { + if (event.key === 'Enter') { + inputEventFunction(); + } + }); + } + + // Handle and trigger popup window + document.querySelectorAll('.popup-trigger').forEach(element => { + element.addEventListener('click', () => { + document.body.style.overflow = 'hidden'; + document.querySelector('.search-pop-overlay').classList.add('search-active'); + input.focus(); + if (!isfetched) fetchData(); + }); + }); + + // Monitor main search box + const onPopupClose = () => { + document.body.style.overflow = ''; + document.querySelector('.search-pop-overlay').classList.remove('search-active'); + }; + + document.querySelector('.search-pop-overlay').addEventListener('click', event => { + if (event.target === document.querySelector('.search-pop-overlay')) { + onPopupClose(); + } + }); + document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose); + window.addEventListener('pjax:success', onPopupClose); + window.addEventListener('keyup', event => { + if (event.key === 'Escape') { + onPopupClose(); + } + }); +}); diff --git a/js/motion.js b/js/motion.js new file mode 100644 index 0000000..026199a --- /dev/null +++ b/js/motion.js @@ -0,0 +1,177 @@ +/* global NexT, CONFIG, Velocity */ + +if (window.$ && window.$.Velocity) window.Velocity = window.$.Velocity; + +NexT.motion = {}; + +NexT.motion.integrator = { + queue : [], + cursor: -1, + init : function() { + this.queue = []; + this.cursor = -1; + return this; + }, + add: function(fn) { + this.queue.push(fn); + return this; + }, + next: function() { + this.cursor++; + var fn = this.queue[this.cursor]; + typeof fn === 'function' && fn(NexT.motion.integrator); + }, + bootstrap: function() { + this.next(); + } +}; + +NexT.motion.middleWares = { + logo: function(integrator) { + var sequence = []; + var brand = document.querySelector('.brand'); + var image = document.querySelector('.custom-logo-image'); + var title = document.querySelector('.site-title'); + var subtitle = document.querySelector('.site-subtitle'); + var logoLineTop = document.querySelector('.logo-line-before i'); + var logoLineBottom = document.querySelector('.logo-line-after i'); + + brand && sequence.push({ + e: brand, + p: {opacity: 1}, + o: {duration: 200} + }); + + function getMistLineSettings(element, translateX) { + return { + e: element, + p: {translateX}, + o: { + duration : 500, + sequenceQueue: false + } + }; + } + + function pushImageToSequence() { + sequence.push({ + e: image, + p: {opacity: 1, top: 0}, + o: {duration: 200} + }); + } + + CONFIG.scheme === 'Mist' && logoLineTop && logoLineBottom + && sequence.push( + getMistLineSettings(logoLineTop, '100%'), + getMistLineSettings(logoLineBottom, '-100%') + ); + + CONFIG.scheme === 'Muse' && image && pushImageToSequence(); + + title && sequence.push({ + e: title, + p: {opacity: 1, top: 0}, + o: {duration: 200} + }); + + subtitle && sequence.push({ + e: subtitle, + p: {opacity: 1, top: 0}, + o: {duration: 200} + }); + + (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') && image && pushImageToSequence(); + + if (sequence.length > 0) { + sequence[sequence.length - 1].o.complete = function() { + integrator.next(); + }; + Velocity.RunSequence(sequence); + } else { + integrator.next(); + } + + if (CONFIG.motion.async) { + integrator.next(); + } + }, + + menu: function(integrator) { + Velocity(document.querySelectorAll('.menu-item'), 'transition.slideDownIn', { + display : null, + duration: 200, + complete: function() { + integrator.next(); + } + }); + + if (CONFIG.motion.async) { + integrator.next(); + } + }, + + subMenu: function(integrator) { + var subMenuItem = document.querySelectorAll('.sub-menu .menu-item'); + if (subMenuItem.length > 0) { + subMenuItem.forEach(element => { + element.style.opacity = 1; + }); + } + integrator.next(); + }, + + postList: function(integrator) { + var postBlock = document.querySelectorAll('.post-block, .pagination, .comments'); + var postBlockTransition = CONFIG.motion.transition.post_block; + var postHeader = document.querySelectorAll('.post-header'); + var postHeaderTransition = CONFIG.motion.transition.post_header; + var postBody = document.querySelectorAll('.post-body'); + var postBodyTransition = CONFIG.motion.transition.post_body; + var collHeader = document.querySelectorAll('.collection-header'); + var collHeaderTransition = CONFIG.motion.transition.coll_header; + + if (postBlock.length > 0) { + var postMotionOptions = window.postMotionOptions || { + stagger : 100, + drag : true, + complete: function() { + integrator.next(); + } + }; + + if (CONFIG.motion.transition.post_block) { + Velocity(postBlock, 'transition.' + postBlockTransition, postMotionOptions); + } + if (CONFIG.motion.transition.post_header) { + Velocity(postHeader, 'transition.' + postHeaderTransition, postMotionOptions); + } + if (CONFIG.motion.transition.post_body) { + Velocity(postBody, 'transition.' + postBodyTransition, postMotionOptions); + } + if (CONFIG.motion.transition.coll_header) { + Velocity(collHeader, 'transition.' + collHeaderTransition, postMotionOptions); + } + } + if (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') { + integrator.next(); + } + }, + + sidebar: function(integrator) { + var sidebarAffix = document.querySelector('.sidebar-inner'); + var sidebarAffixTransition = CONFIG.motion.transition.sidebar; + // Only for Pisces | Gemini. + if (sidebarAffixTransition && (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini')) { + Velocity(sidebarAffix, 'transition.' + sidebarAffixTransition, { + display : null, + duration: 200, + complete: function() { + // After motion complete need to remove transform from sidebar to let affix work on Pisces | Gemini. + sidebarAffix.style.transform = 'initial'; + } + }); + } + integrator.next(); + } +}; diff --git a/js/next-boot.js b/js/next-boot.js new file mode 100644 index 0000000..52ec9ae --- /dev/null +++ b/js/next-boot.js @@ -0,0 +1,114 @@ +/* global NexT, CONFIG, Velocity */ + +NexT.boot = {}; + +NexT.boot.registerEvents = function() { + + NexT.utils.registerScrollPercent(); + NexT.utils.registerCanIUseTag(); + + // Mobile top menu bar. + document.querySelector('.site-nav-toggle .toggle').addEventListener('click', () => { + event.currentTarget.classList.toggle('toggle-close'); + var siteNav = document.querySelector('.site-nav'); + var animateAction = siteNav.classList.contains('site-nav-on') ? 'slideUp' : 'slideDown'; + + if (typeof Velocity === 'function') { + Velocity(siteNav, animateAction, { + duration: 200, + complete: function() { + siteNav.classList.toggle('site-nav-on'); + } + }); + } else { + siteNav.classList.toggle('site-nav-on'); + } + }); + + var TAB_ANIMATE_DURATION = 200; + document.querySelectorAll('.sidebar-nav li').forEach((element, index) => { + element.addEventListener('click', event => { + var item = event.currentTarget; + var activeTabClassName = 'sidebar-nav-active'; + var activePanelClassName = 'sidebar-panel-active'; + if (item.classList.contains(activeTabClassName)) return; + + var targets = document.querySelectorAll('.sidebar-panel'); + var target = targets[index]; + var currentTarget = targets[1 - index]; + window.anime({ + targets : currentTarget, + duration: TAB_ANIMATE_DURATION, + easing : 'linear', + opacity : 0, + complete: () => { + // Prevent adding TOC to Overview if Overview was selected when close & open sidebar. + currentTarget.classList.remove(activePanelClassName); + target.style.opacity = 0; + target.classList.add(activePanelClassName); + window.anime({ + targets : target, + duration: TAB_ANIMATE_DURATION, + easing : 'linear', + opacity : 1 + }); + } + }); + + [...item.parentNode.children].forEach(element => { + element.classList.remove(activeTabClassName); + }); + item.classList.add(activeTabClassName); + }); + }); + + window.addEventListener('resize', NexT.utils.initSidebarDimension); + + window.addEventListener('hashchange', () => { + var tHash = location.hash; + if (tHash !== '' && !tHash.match(/%\S{2}/)) { + var target = document.querySelector(`.tabs ul.nav-tabs li a[href="${tHash}"]`); + target && target.click(); + } + }); +}; + +NexT.boot.refresh = function() { + + /** + * Register JS handlers by condition option. + * Need to add config option in Front-End at 'layout/_partials/head.swig' file. + */ + CONFIG.fancybox && NexT.utils.wrapImageWithFancyBox(); + CONFIG.mediumzoom && window.mediumZoom('.post-body :not(a) > img, .post-body > img'); + CONFIG.lazyload && window.lozad('.post-body img').observe(); + CONFIG.pangu && window.pangu.spacingPage(); + + CONFIG.exturl && NexT.utils.registerExtURL(); + CONFIG.copycode.enable && NexT.utils.registerCopyCode(); + NexT.utils.registerTabsTag(); + NexT.utils.registerActiveMenuItem(); + NexT.utils.registerLangSelect(); + NexT.utils.registerSidebarTOC(); + NexT.utils.wrapTableWithBox(); + NexT.utils.registerVideoIframe(); +}; + +NexT.boot.motion = function() { + // Define Motion Sequence & Bootstrap Motion. + if (CONFIG.motion.enable) { + NexT.motion.integrator + .add(NexT.motion.middleWares.logo) + .add(NexT.motion.middleWares.menu) + .add(NexT.motion.middleWares.postList) + .add(NexT.motion.middleWares.sidebar) + .bootstrap(); + } + NexT.utils.updateSidebarPosition(); +}; + +document.addEventListener('DOMContentLoaded', () => { + NexT.boot.registerEvents(); + NexT.boot.refresh(); + NexT.boot.motion(); +}); diff --git a/js/schemes/muse.js b/js/schemes/muse.js new file mode 100644 index 0000000..f4be56d --- /dev/null +++ b/js/schemes/muse.js @@ -0,0 +1,113 @@ +/* global NexT, CONFIG, Velocity */ + +document.addEventListener('DOMContentLoaded', () => { + + var isRight = CONFIG.sidebar.position === 'right'; + var SIDEBAR_WIDTH = CONFIG.sidebar.width || 320; + var SIDEBAR_DISPLAY_DURATION = 200; + var mousePos = {}; + + var sidebarToggleLines = { + lines: document.querySelector('.sidebar-toggle'), + init : function() { + this.lines.classList.remove('toggle-arrow', 'toggle-close'); + }, + arrow: function() { + this.lines.classList.remove('toggle-close'); + this.lines.classList.add('toggle-arrow'); + }, + close: function() { + this.lines.classList.remove('toggle-arrow'); + this.lines.classList.add('toggle-close'); + } + }; + + var sidebarToggleMotion = { + sidebarEl : document.querySelector('.sidebar'), + isSidebarVisible: false, + init : function() { + sidebarToggleLines.init(); + + window.addEventListener('mousedown', this.mousedownHandler.bind(this)); + window.addEventListener('mouseup', this.mouseupHandler.bind(this)); + document.querySelector('#sidebar-dimmer').addEventListener('click', this.clickHandler.bind(this)); + document.querySelector('.sidebar-toggle').addEventListener('click', this.clickHandler.bind(this)); + document.querySelector('.sidebar-toggle').addEventListener('mouseenter', this.mouseEnterHandler.bind(this)); + document.querySelector('.sidebar-toggle').addEventListener('mouseleave', this.mouseLeaveHandler.bind(this)); + window.addEventListener('sidebar:show', this.showSidebar.bind(this)); + window.addEventListener('sidebar:hide', this.hideSidebar.bind(this)); + }, + mousedownHandler: function(event) { + mousePos.X = event.pageX; + mousePos.Y = event.pageY; + }, + mouseupHandler: function(event) { + var deltaX = event.pageX - mousePos.X; + var deltaY = event.pageY - mousePos.Y; + var clickingBlankPart = Math.sqrt((deltaX * deltaX) + (deltaY * deltaY)) < 20 && event.target.matches('.main'); + if (this.isSidebarVisible && (clickingBlankPart || event.target.matches('img.medium-zoom-image, .fancybox img'))) { + this.hideSidebar(); + } + }, + clickHandler: function() { + this.isSidebarVisible ? this.hideSidebar() : this.showSidebar(); + }, + mouseEnterHandler: function() { + if (!this.isSidebarVisible) { + sidebarToggleLines.arrow(); + } + }, + mouseLeaveHandler: function() { + if (!this.isSidebarVisible) { + sidebarToggleLines.init(); + } + }, + showSidebar: function() { + this.isSidebarVisible = true; + this.sidebarEl.classList.add('sidebar-active'); + if (typeof Velocity === 'function') { + Velocity(document.querySelectorAll('.sidebar .motion-element'), isRight ? 'transition.slideRightIn' : 'transition.slideLeftIn', { + stagger: 50, + drag : true + }); + } + + sidebarToggleLines.close(); + NexT.utils.isDesktop() && window.anime(Object.assign({ + targets : document.body, + duration: SIDEBAR_DISPLAY_DURATION, + easing : 'linear' + }, isRight ? { + 'padding-right': SIDEBAR_WIDTH + } : { + 'padding-left': SIDEBAR_WIDTH + })); + }, + hideSidebar: function() { + this.isSidebarVisible = false; + this.sidebarEl.classList.remove('sidebar-active'); + + sidebarToggleLines.init(); + NexT.utils.isDesktop() && window.anime(Object.assign({ + targets : document.body, + duration: SIDEBAR_DISPLAY_DURATION, + easing : 'linear' + }, isRight ? { + 'padding-right': 0 + } : { + 'padding-left': 0 + })); + } + }; + sidebarToggleMotion.init(); + + function updateFooterPosition() { + var footer = document.querySelector('.footer'); + var containerHeight = document.querySelector('.header').offsetHeight + document.querySelector('.main').offsetHeight + footer.offsetHeight; + footer.classList.toggle('footer-fixed', containerHeight <= window.innerHeight); + } + + updateFooterPosition(); + window.addEventListener('resize', updateFooterPosition); + window.addEventListener('scroll', updateFooterPosition); +}); diff --git a/js/schemes/pisces.js b/js/schemes/pisces.js new file mode 100644 index 0000000..41633ea --- /dev/null +++ b/js/schemes/pisces.js @@ -0,0 +1,86 @@ +/* global NexT, CONFIG */ + +var Affix = { + init: function(element, options) { + this.element = element; + this.offset = options || 0; + this.affixed = null; + this.unpin = null; + this.pinnedOffset = null; + this.checkPosition(); + window.addEventListener('scroll', this.checkPosition.bind(this)); + window.addEventListener('click', this.checkPositionWithEventLoop.bind(this)); + window.matchMedia('(min-width: 992px)').addListener(event => { + if (event.matches) { + this.offset = NexT.utils.getAffixParam(); + this.checkPosition(); + } + }); + }, + getState: function(scrollHeight, height, offsetTop, offsetBottom) { + let scrollTop = window.scrollY; + let targetHeight = window.innerHeight; + if (offsetTop != null && this.affixed === 'top') { + if (document.querySelector('.content-wrap').offsetHeight < offsetTop) return 'top'; + return scrollTop < offsetTop ? 'top' : false; + } + if (this.affixed === 'bottom') { + if (offsetTop != null) return this.unpin <= this.element.getBoundingClientRect().top ? false : 'bottom'; + return scrollTop + targetHeight <= scrollHeight - offsetBottom ? false : 'bottom'; + } + let initializing = this.affixed === null; + let colliderTop = initializing ? scrollTop : this.element.getBoundingClientRect().top + scrollTop; + let colliderHeight = initializing ? targetHeight : height; + if (offsetTop != null && scrollTop <= offsetTop) return 'top'; + if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'; + return false; + }, + getPinnedOffset: function() { + if (this.pinnedOffset) return this.pinnedOffset; + this.element.classList.remove('affix-top', 'affix-bottom'); + this.element.classList.add('affix'); + return (this.pinnedOffset = this.element.getBoundingClientRect().top); + }, + checkPositionWithEventLoop() { + setTimeout(this.checkPosition.bind(this), 1); + }, + checkPosition: function() { + if (window.getComputedStyle(this.element).display === 'none') return; + let height = this.element.offsetHeight; + let { offset } = this; + let offsetTop = offset.top; + let offsetBottom = offset.bottom; + let { scrollHeight } = document.body; + let affix = this.getState(scrollHeight, height, offsetTop, offsetBottom); + if (this.affixed !== affix) { + if (this.unpin != null) this.element.style.top = ''; + let affixType = 'affix' + (affix ? '-' + affix : ''); + this.affixed = affix; + this.unpin = affix === 'bottom' ? this.getPinnedOffset() : null; + this.element.classList.remove('affix', 'affix-top', 'affix-bottom'); + this.element.classList.add(affixType); + } + if (affix === 'bottom') { + this.element.style.top = scrollHeight - height - offsetBottom + 'px'; + } + } +}; + +NexT.utils.getAffixParam = function() { + const sidebarOffset = CONFIG.sidebar.offset || 12; + + let headerOffset = document.querySelector('.header-inner').offsetHeight; + let footerOffset = document.querySelector('.footer').offsetHeight; + + document.querySelector('.sidebar').style.marginTop = headerOffset + sidebarOffset + 'px'; + + return { + top : headerOffset, + bottom: footerOffset + }; +}; + +document.addEventListener('DOMContentLoaded', () => { + + Affix.init(document.querySelector('.sidebar-inner'), NexT.utils.getAffixParam()); +}); diff --git a/js/utils.js b/js/utils.js new file mode 100644 index 0000000..74a6dfd --- /dev/null +++ b/js/utils.js @@ -0,0 +1,415 @@ +/* global NexT, CONFIG */ + +HTMLElement.prototype.wrap = function(wrapper) { + this.parentNode.insertBefore(wrapper, this); + this.parentNode.removeChild(this); + wrapper.appendChild(this); +}; + +NexT.utils = { + + /** + * Wrap images with fancybox. + */ + wrapImageWithFancyBox: function() { + document.querySelectorAll('.post-body :not(a) > img, .post-body > img').forEach(element => { + var $image = $(element); + var imageLink = $image.attr('data-src') || $image.attr('src'); + var $imageWrapLink = $image.wrap(``).parent('a'); + if ($image.is('.post-gallery img')) { + $imageWrapLink.attr('data-fancybox', 'gallery').attr('rel', 'gallery'); + } else if ($image.is('.group-picture img')) { + $imageWrapLink.attr('data-fancybox', 'group').attr('rel', 'group'); + } else { + $imageWrapLink.attr('data-fancybox', 'default').attr('rel', 'default'); + } + + var imageTitle = $image.attr('title') || $image.attr('alt'); + if (imageTitle) { + $imageWrapLink.append(`

    ${imageTitle}

    `); + // Make sure img title tag will show correctly in fancybox + $imageWrapLink.attr('title', imageTitle).attr('data-caption', imageTitle); + } + }); + + $.fancybox.defaults.hash = false; + $('.fancybox').fancybox({ + loop : true, + helpers: { + overlay: { + locked: false + } + } + }); + }, + + registerExtURL: function() { + document.querySelectorAll('span.exturl').forEach(element => { + let link = document.createElement('a'); + // https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings + link.href = decodeURIComponent(atob(element.dataset.url).split('').map(c => { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }).join('')); + link.rel = 'noopener external nofollow noreferrer'; + link.target = '_blank'; + link.className = element.className; + link.title = element.title; + link.innerHTML = element.innerHTML; + element.parentNode.replaceChild(link, element); + }); + }, + + /** + * One-click copy code support. + */ + registerCopyCode: function() { + document.querySelectorAll('figure.highlight').forEach(element => { + const box = document.createElement('div'); + element.wrap(box); + box.classList.add('highlight-container'); + box.insertAdjacentHTML('beforeend', '
    '); + var button = element.parentNode.querySelector('.copy-btn'); + button.addEventListener('click', event => { + var target = event.currentTarget; + var code = [...target.parentNode.querySelectorAll('.code .line')].map(line => line.innerText).join('\n'); + var ta = document.createElement('textarea'); + ta.style.top = window.scrollY + 'px'; // Prevent page scrolling + ta.style.position = 'absolute'; + ta.style.opacity = '0'; + ta.readOnly = true; + ta.value = code; + document.body.append(ta); + const selection = document.getSelection(); + const selected = selection.rangeCount > 0 ? selection.getRangeAt(0) : false; + ta.select(); + ta.setSelectionRange(0, code.length); + ta.readOnly = false; + var result = document.execCommand('copy'); + if (CONFIG.copycode.show_result) { + target.querySelector('i').className = result ? 'fa fa-check fa-fw' : 'fa fa-times fa-fw'; + } + ta.blur(); // For iOS + target.blur(); + if (selected) { + selection.removeAllRanges(); + selection.addRange(selected); + } + document.body.removeChild(ta); + }); + button.addEventListener('mouseleave', event => { + setTimeout(() => { + event.target.querySelector('i').className = 'fa fa-clipboard fa-fw'; + }, 300); + }); + }); + }, + + wrapTableWithBox: function() { + document.querySelectorAll('table').forEach(element => { + const box = document.createElement('div'); + box.className = 'table-container'; + element.wrap(box); + }); + }, + + registerVideoIframe: function() { + document.querySelectorAll('iframe').forEach(element => { + const supported = [ + 'www.youtube.com', + 'player.vimeo.com', + 'player.youku.com', + 'player.bilibili.com', + 'www.tudou.com' + ].some(host => element.src.includes(host)); + if (supported && !element.parentNode.matches('.video-container')) { + const box = document.createElement('div'); + box.className = 'video-container'; + element.wrap(box); + let width = Number(element.width); + let height = Number(element.height); + if (width && height) { + element.parentNode.style.paddingTop = (height / width * 100) + '%'; + } + } + }); + }, + + registerScrollPercent: function() { + var THRESHOLD = 50; + var backToTop = document.querySelector('.back-to-top'); + var readingProgressBar = document.querySelector('.reading-progress-bar'); + // For init back to top in sidebar if page was scrolled after page refresh. + window.addEventListener('scroll', () => { + if (backToTop || readingProgressBar) { + var docHeight = document.querySelector('.container').offsetHeight; + var winHeight = window.innerHeight; + var contentVisibilityHeight = docHeight > winHeight ? docHeight - winHeight : document.body.scrollHeight - winHeight; + var scrollPercent = Math.min(100 * window.scrollY / contentVisibilityHeight, 100); + if (backToTop) { + backToTop.classList.toggle('back-to-top-on', window.scrollY > THRESHOLD); + backToTop.querySelector('span').innerText = Math.round(scrollPercent) + '%'; + } + if (readingProgressBar) { + readingProgressBar.style.width = scrollPercent.toFixed(2) + '%'; + } + } + }); + + backToTop && backToTop.addEventListener('click', () => { + window.anime({ + targets : document.scrollingElement, + duration : 500, + easing : 'linear', + scrollTop: 0 + }); + }); + }, + + /** + * Tabs tag listener (without twitter bootstrap). + */ + registerTabsTag: function() { + // Binding `nav-tabs` & `tab-content` by real time permalink changing. + document.querySelectorAll('.tabs ul.nav-tabs .tab').forEach(element => { + element.addEventListener('click', event => { + event.preventDefault(); + var target = event.currentTarget; + // Prevent selected tab to select again. + if (!target.classList.contains('active')) { + // Add & Remove active class on `nav-tabs` & `tab-content`. + [...target.parentNode.children].forEach(element => { + element.classList.remove('active'); + }); + target.classList.add('active'); + var tActive = document.getElementById(target.querySelector('a').getAttribute('href').replace('#', '')); + [...tActive.parentNode.children].forEach(element => { + element.classList.remove('active'); + }); + tActive.classList.add('active'); + // Trigger event + tActive.dispatchEvent(new Event('tabs:click', { + bubbles: true + })); + } + }); + }); + + window.dispatchEvent(new Event('tabs:register')); + }, + + registerCanIUseTag: function() { + // Get responsive height passed from iframe. + window.addEventListener('message', ({ data }) => { + if ((typeof data === 'string') && data.includes('ciu_embed')) { + var featureID = data.split(':')[1]; + var height = data.split(':')[2]; + document.querySelector(`iframe[data-feature=${featureID}]`).style.height = parseInt(height, 10) + 5 + 'px'; + } + }, false); + }, + + registerActiveMenuItem: function() { + document.querySelectorAll('.menu-item').forEach(element => { + var target = element.querySelector('a[href]'); + if (!target) return; + var isSamePath = target.pathname === location.pathname || target.pathname === location.pathname.replace('index.html', ''); + var isSubPath = !CONFIG.root.startsWith(target.pathname) && location.pathname.startsWith(target.pathname); + element.classList.toggle('menu-item-active', target.hostname === location.hostname && (isSamePath || isSubPath)); + }); + }, + + registerLangSelect: function() { + let selects = document.querySelectorAll('.lang-select'); + selects.forEach(sel => { + sel.value = CONFIG.page.lang; + sel.addEventListener('change', () => { + let target = sel.options[sel.selectedIndex]; + document.querySelectorAll('.lang-select-label span').forEach(span => span.innerText = target.text); + let url = target.dataset.href; + window.pjax ? window.pjax.loadUrl(url) : window.location.href = url; + }); + }); + }, + + registerSidebarTOC: function() { + const navItems = document.querySelectorAll('.post-toc li'); + const sections = [...navItems].map(element => { + var link = element.querySelector('a.nav-link'); + var target = document.getElementById(decodeURI(link.getAttribute('href')).replace('#', '')); + // TOC item animation navigate. + link.addEventListener('click', event => { + event.preventDefault(); + var offset = target.getBoundingClientRect().top + window.scrollY; + window.anime({ + targets : document.scrollingElement, + duration : 500, + easing : 'linear', + scrollTop: offset + 10 + }); + }); + return target; + }); + + var tocElement = document.querySelector('.post-toc-wrap'); + function activateNavByIndex(target) { + if (target.classList.contains('active-current')) return; + + document.querySelectorAll('.post-toc .active').forEach(element => { + element.classList.remove('active', 'active-current'); + }); + target.classList.add('active', 'active-current'); + var parent = target.parentNode; + while (!parent.matches('.post-toc')) { + if (parent.matches('li')) parent.classList.add('active'); + parent = parent.parentNode; + } + // Scrolling to center active TOC element if TOC content is taller then viewport. + window.anime({ + targets : tocElement, + duration : 200, + easing : 'linear', + scrollTop: tocElement.scrollTop - (tocElement.offsetHeight / 2) + target.getBoundingClientRect().top - tocElement.getBoundingClientRect().top + }); + } + + function findIndex(entries) { + let index = 0; + let entry = entries[index]; + if (entry.boundingClientRect.top > 0) { + index = sections.indexOf(entry.target); + return index === 0 ? 0 : index - 1; + } + for (; index < entries.length; index++) { + if (entries[index].boundingClientRect.top <= 0) { + entry = entries[index]; + } else { + return sections.indexOf(entry.target); + } + } + return sections.indexOf(entry.target); + } + + function createIntersectionObserver(marginTop) { + marginTop = Math.floor(marginTop + 10000); + let intersectionObserver = new IntersectionObserver((entries, observe) => { + let scrollHeight = document.documentElement.scrollHeight + 100; + if (scrollHeight > marginTop) { + observe.disconnect(); + createIntersectionObserver(scrollHeight); + return; + } + let index = findIndex(entries); + activateNavByIndex(navItems[index]); + }, { + rootMargin: marginTop + 'px 0px -100% 0px', + threshold : 0 + }); + sections.forEach(element => { + element && intersectionObserver.observe(element); + }); + } + createIntersectionObserver(document.documentElement.scrollHeight); + }, + + hasMobileUA: function() { + let ua = navigator.userAgent; + let pa = /iPad|iPhone|Android|Opera Mini|BlackBerry|webOS|UCWEB|Blazer|PSP|IEMobile|Symbian/g; + return pa.test(ua); + }, + + isTablet: function() { + return window.screen.width < 992 && window.screen.width > 767 && this.hasMobileUA(); + }, + + isMobile: function() { + return window.screen.width < 767 && this.hasMobileUA(); + }, + + isDesktop: function() { + return !this.isTablet() && !this.isMobile(); + }, + + supportsPDFs: function() { + let ua = navigator.userAgent; + let isFirefoxWithPDFJS = ua.includes('irefox') && parseInt(ua.split('rv:')[1].split('.')[0], 10) > 18; + let supportsPdfMimeType = typeof navigator.mimeTypes['application/pdf'] !== 'undefined'; + let isIOS = /iphone|ipad|ipod/i.test(ua.toLowerCase()); + return isFirefoxWithPDFJS || (supportsPdfMimeType && !isIOS); + }, + + /** + * Init Sidebar & TOC inner dimensions on all pages and for all schemes. + * Need for Sidebar/TOC inner scrolling if content taller then viewport. + */ + initSidebarDimension: function() { + var sidebarNav = document.querySelector('.sidebar-nav'); + var sidebarNavHeight = sidebarNav.style.display !== 'none' ? sidebarNav.offsetHeight : 0; + var sidebarOffset = CONFIG.sidebar.offset || 12; + var sidebarb2tHeight = CONFIG.back2top.enable && CONFIG.back2top.sidebar ? document.querySelector('.back-to-top').offsetHeight : 0; + var sidebarSchemePadding = (CONFIG.sidebar.padding * 2) + sidebarNavHeight + sidebarb2tHeight; + // Margin of sidebar b2t: -4px -10px -18px, brings a different of 22px. + if (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') sidebarSchemePadding += (sidebarOffset * 2) - 22; + // Initialize Sidebar & TOC Height. + var sidebarWrapperHeight = document.body.offsetHeight - sidebarSchemePadding + 'px'; + document.querySelector('.site-overview-wrap').style.maxHeight = sidebarWrapperHeight; + document.querySelector('.post-toc-wrap').style.maxHeight = sidebarWrapperHeight; + }, + + updateSidebarPosition: function() { + var sidebarNav = document.querySelector('.sidebar-nav'); + var hasTOC = document.querySelector('.post-toc'); + if (hasTOC) { + sidebarNav.style.display = ''; + sidebarNav.classList.add('motion-element'); + document.querySelector('.sidebar-nav-toc').click(); + } else { + sidebarNav.style.display = 'none'; + sidebarNav.classList.remove('motion-element'); + document.querySelector('.sidebar-nav-overview').click(); + } + NexT.utils.initSidebarDimension(); + if (!this.isDesktop() || CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') return; + // Expand sidebar on post detail page by default, when post has a toc. + var display = CONFIG.page.sidebar; + if (typeof display !== 'boolean') { + // There's no definition sidebar in the page front-matter. + display = CONFIG.sidebar.display === 'always' || (CONFIG.sidebar.display === 'post' && hasTOC); + } + if (display) { + window.dispatchEvent(new Event('sidebar:show')); + } + }, + + getScript: function(url, callback, condition) { + if (condition) { + callback(); + } else { + var script = document.createElement('script'); + script.onload = script.onreadystatechange = function(_, isAbort) { + if (isAbort || !script.readyState || /loaded|complete/.test(script.readyState)) { + script.onload = script.onreadystatechange = null; + script = undefined; + if (!isAbort && callback) setTimeout(callback, 0); + } + }; + script.src = url; + document.head.appendChild(script); + } + }, + + loadComments: function(element, callback) { + if (!CONFIG.comments.lazyload || !element) { + callback(); + return; + } + let intersectionObserver = new IntersectionObserver((entries, observer) => { + let entry = entries[0]; + if (entry.isIntersecting) { + callback(); + observer.disconnect(); + } + }); + intersectionObserver.observe(element); + return intersectionObserver; + } +}; diff --git a/lib/anime.min.js b/lib/anime.min.js new file mode 100644 index 0000000..99b263a --- /dev/null +++ b/lib/anime.min.js @@ -0,0 +1,8 @@ +/* + * anime.js v3.1.0 + * (c) 2019 Julian Garnier + * Released under the MIT license + * animejs.com + */ + +!function(n,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):n.anime=e()}(this,function(){"use strict";var n={update:null,begin:null,loopBegin:null,changeBegin:null,change:null,changeComplete:null,loopComplete:null,complete:null,loop:1,direction:"normal",autoplay:!0,timelineOffset:0},e={duration:1e3,delay:0,endDelay:0,easing:"easeOutElastic(1, .5)",round:0},r=["translateX","translateY","translateZ","rotate","rotateX","rotateY","rotateZ","scale","scaleX","scaleY","scaleZ","skew","skewX","skewY","perspective"],t={CSS:{},springs:{}};function a(n,e,r){return Math.min(Math.max(n,e),r)}function o(n,e){return n.indexOf(e)>-1}function u(n,e){return n.apply(null,e)}var i={arr:function(n){return Array.isArray(n)},obj:function(n){return o(Object.prototype.toString.call(n),"Object")},pth:function(n){return i.obj(n)&&n.hasOwnProperty("totalLength")},svg:function(n){return n instanceof SVGElement},inp:function(n){return n instanceof HTMLInputElement},dom:function(n){return n.nodeType||i.svg(n)},str:function(n){return"string"==typeof n},fnc:function(n){return"function"==typeof n},und:function(n){return void 0===n},hex:function(n){return/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(n)},rgb:function(n){return/^rgb/.test(n)},hsl:function(n){return/^hsl/.test(n)},col:function(n){return i.hex(n)||i.rgb(n)||i.hsl(n)},key:function(r){return!n.hasOwnProperty(r)&&!e.hasOwnProperty(r)&&"targets"!==r&&"keyframes"!==r}};function c(n){var e=/\(([^)]+)\)/.exec(n);return e?e[1].split(",").map(function(n){return parseFloat(n)}):[]}function s(n,e){var r=c(n),o=a(i.und(r[0])?1:r[0],.1,100),u=a(i.und(r[1])?100:r[1],.1,100),s=a(i.und(r[2])?10:r[2],.1,100),f=a(i.und(r[3])?0:r[3],.1,100),l=Math.sqrt(u/o),d=s/(2*Math.sqrt(u*o)),p=d<1?l*Math.sqrt(1-d*d):0,h=1,v=d<1?(d*l-f)/p:-f+l;function g(n){var r=e?e*n/1e3:n;return r=d<1?Math.exp(-r*d*l)*(h*Math.cos(p*r)+v*Math.sin(p*r)):(h+v*r)*Math.exp(-r*l),0===n||1===n?n:1-r}return e?g:function(){var e=t.springs[n];if(e)return e;for(var r=0,a=0;;)if(1===g(r+=1/6)){if(++a>=16)break}else a=0;var o=r*(1/6)*1e3;return t.springs[n]=o,o}}function f(n){return void 0===n&&(n=10),function(e){return Math.round(e*n)*(1/n)}}var l,d,p=function(){var n=11,e=1/(n-1);function r(n,e){return 1-3*e+3*n}function t(n,e){return 3*e-6*n}function a(n){return 3*n}function o(n,e,o){return((r(e,o)*n+t(e,o))*n+a(e))*n}function u(n,e,o){return 3*r(e,o)*n*n+2*t(e,o)*n+a(e)}return function(r,t,a,i){if(0<=r&&r<=1&&0<=a&&a<=1){var c=new Float32Array(n);if(r!==t||a!==i)for(var s=0;s=.001?function(n,e,r,t){for(var a=0;a<4;++a){var i=u(e,r,t);if(0===i)return e;e-=(o(e,r,t)-n)/i}return e}(t,l,r,a):0===d?l:function(n,e,r,t,a){for(var u,i,c=0;(u=o(i=e+(r-e)/2,t,a)-n)>0?r=i:e=i,Math.abs(u)>1e-7&&++c<10;);return i}(t,i,i+e,r,a)}}}(),h=(l={linear:function(){return function(n){return n}}},d={Sine:function(){return function(n){return 1-Math.cos(n*Math.PI/2)}},Circ:function(){return function(n){return 1-Math.sqrt(1-n*n)}},Back:function(){return function(n){return n*n*(3*n-2)}},Bounce:function(){return function(n){for(var e,r=4;n<((e=Math.pow(2,--r))-1)/11;);return 1/Math.pow(4,3-r)-7.5625*Math.pow((3*e-2)/22-n,2)}},Elastic:function(n,e){void 0===n&&(n=1),void 0===e&&(e=.5);var r=a(n,1,10),t=a(e,.1,2);return function(n){return 0===n||1===n?n:-r*Math.pow(2,10*(n-1))*Math.sin((n-1-t/(2*Math.PI)*Math.asin(1/r))*(2*Math.PI)/t)}}},["Quad","Cubic","Quart","Quint","Expo"].forEach(function(n,e){d[n]=function(){return function(n){return Math.pow(n,e+2)}}}),Object.keys(d).forEach(function(n){var e=d[n];l["easeIn"+n]=e,l["easeOut"+n]=function(n,r){return function(t){return 1-e(n,r)(1-t)}},l["easeInOut"+n]=function(n,r){return function(t){return t<.5?e(n,r)(2*t)/2:1-e(n,r)(-2*t+2)/2}}}),l);function v(n,e){if(i.fnc(n))return n;var r=n.split("(")[0],t=h[r],a=c(n);switch(r){case"spring":return s(n,e);case"cubicBezier":return u(p,a);case"steps":return u(f,a);default:return u(t,a)}}function g(n){try{return document.querySelectorAll(n)}catch(n){return}}function m(n,e){for(var r=n.length,t=arguments.length>=2?arguments[1]:void 0,a=[],o=0;o1&&(r-=1),r<1/6?n+6*(e-n)*r:r<.5?e:r<2/3?n+(e-n)*(2/3-r)*6:n}if(0==u)e=r=t=i;else{var f=i<.5?i*(1+u):i+u-i*u,l=2*i-f;e=s(l,f,o+1/3),r=s(l,f,o),t=s(l,f,o-1/3)}return"rgba("+255*e+","+255*r+","+255*t+","+c+")"}(n):void 0;var e,r,t,a}function C(n){var e=/[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?(%|px|pt|em|rem|in|cm|mm|ex|ch|pc|vw|vh|vmin|vmax|deg|rad|turn)?$/.exec(n);if(e)return e[1]}function B(n,e){return i.fnc(n)?n(e.target,e.id,e.total):n}function P(n,e){return n.getAttribute(e)}function I(n,e,r){if(M([r,"deg","rad","turn"],C(e)))return e;var a=t.CSS[e+r];if(!i.und(a))return a;var o=document.createElement(n.tagName),u=n.parentNode&&n.parentNode!==document?n.parentNode:document.body;u.appendChild(o),o.style.position="absolute",o.style.width=100+r;var c=100/o.offsetWidth;u.removeChild(o);var s=c*parseFloat(e);return t.CSS[e+r]=s,s}function T(n,e,r){if(e in n.style){var t=e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),a=n.style[e]||getComputedStyle(n).getPropertyValue(t)||"0";return r?I(n,a,r):a}}function D(n,e){return i.dom(n)&&!i.inp(n)&&(P(n,e)||i.svg(n)&&n[e])?"attribute":i.dom(n)&&M(r,e)?"transform":i.dom(n)&&"transform"!==e&&T(n,e)?"css":null!=n[e]?"object":void 0}function E(n){if(i.dom(n)){for(var e,r=n.style.transform||"",t=/(\w+)\(([^)]*)\)/g,a=new Map;e=t.exec(r);)a.set(e[1],e[2]);return a}}function F(n,e,r,t){var a,u=o(e,"scale")?1:0+(o(a=e,"translate")||"perspective"===a?"px":o(a,"rotate")||o(a,"skew")?"deg":void 0),i=E(n).get(e)||u;return r&&(r.transforms.list.set(e,i),r.transforms.last=e),t?I(n,i,t):i}function N(n,e,r,t){switch(D(n,e)){case"transform":return F(n,e,t,r);case"css":return T(n,e,r);case"attribute":return P(n,e);default:return n[e]||0}}function A(n,e){var r=/^(\*=|\+=|-=)/.exec(n);if(!r)return n;var t=C(n)||0,a=parseFloat(e),o=parseFloat(n.replace(r[0],""));switch(r[0][0]){case"+":return a+o+t;case"-":return a-o+t;case"*":return a*o+t}}function L(n,e){if(i.col(n))return O(n);if(/\s/g.test(n))return n;var r=C(n),t=r?n.substr(0,n.length-r.length):n;return e?t+e:t}function j(n,e){return Math.sqrt(Math.pow(e.x-n.x,2)+Math.pow(e.y-n.y,2))}function S(n){for(var e,r=n.points,t=0,a=0;a0&&(t+=j(e,o)),e=o}return t}function q(n){if(n.getTotalLength)return n.getTotalLength();switch(n.tagName.toLowerCase()){case"circle":return o=n,2*Math.PI*P(o,"r");case"rect":return 2*P(a=n,"width")+2*P(a,"height");case"line":return j({x:P(t=n,"x1"),y:P(t,"y1")},{x:P(t,"x2"),y:P(t,"y2")});case"polyline":return S(n);case"polygon":return r=(e=n).points,S(e)+j(r.getItem(r.numberOfItems-1),r.getItem(0))}var e,r,t,a,o}function $(n,e){var r=e||{},t=r.el||function(n){for(var e=n.parentNode;i.svg(e)&&i.svg(e.parentNode);)e=e.parentNode;return e}(n),a=t.getBoundingClientRect(),o=P(t,"viewBox"),u=a.width,c=a.height,s=r.viewBox||(o?o.split(" "):[0,0,u,c]);return{el:t,viewBox:s,x:s[0]/1,y:s[1]/1,w:u/s[2],h:c/s[3]}}function X(n,e){function r(r){void 0===r&&(r=0);var t=e+r>=1?e+r:0;return n.el.getPointAtLength(t)}var t=$(n.el,n.svg),a=r(),o=r(-1),u=r(1);switch(n.property){case"x":return(a.x-t.x)*t.w;case"y":return(a.y-t.y)*t.h;case"angle":return 180*Math.atan2(u.y-o.y,u.x-o.x)/Math.PI}}function Y(n,e){var r=/[+-]?\d*\.?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g,t=L(i.pth(n)?n.totalLength:n,e)+"";return{original:t,numbers:t.match(r)?t.match(r).map(Number):[0],strings:i.str(n)||e?t.split(r):[]}}function Z(n){return m(n?y(i.arr(n)?n.map(b):b(n)):[],function(n,e,r){return r.indexOf(n)===e})}function Q(n){var e=Z(n);return e.map(function(n,r){return{target:n,id:r,total:e.length,transforms:{list:E(n)}}})}function V(n,e){var r=x(e);if(/^spring/.test(r.easing)&&(r.duration=s(r.easing)),i.arr(n)){var t=n.length;2===t&&!i.obj(n[0])?n={value:n}:i.fnc(e.duration)||(r.duration=e.duration/t)}var a=i.arr(n)?n:[n];return a.map(function(n,r){var t=i.obj(n)&&!i.pth(n)?n:{value:n};return i.und(t.delay)&&(t.delay=r?0:e.delay),i.und(t.endDelay)&&(t.endDelay=r===a.length-1?e.endDelay:0),t}).map(function(n){return k(n,r)})}function z(n,e){var r=[],t=e.keyframes;for(var a in t&&(e=k(function(n){for(var e=m(y(n.map(function(n){return Object.keys(n)})),function(n){return i.key(n)}).reduce(function(n,e){return n.indexOf(e)<0&&n.push(e),n},[]),r={},t=function(t){var a=e[t];r[a]=n.map(function(n){var e={};for(var r in n)i.key(r)?r==a&&(e.value=n[r]):e[r]=n[r];return e})},a=0;a-1&&(_.splice(o,1),r=_.length)}else a.tick(e);t++}n()}else U=cancelAnimationFrame(U)}return n}();function rn(r){void 0===r&&(r={});var t,o=0,u=0,i=0,c=0,s=null;function f(n){var e=window.Promise&&new Promise(function(n){return s=n});return n.finished=e,e}var l,d,p,h,v,g,y,b,M=(d=w(n,l=r),p=w(e,l),h=z(p,l),v=Q(l.targets),g=W(v,h),y=J(g,p),b=K,K++,k(d,{id:b,children:[],animatables:v,animations:g,duration:y.duration,delay:y.delay,endDelay:y.endDelay}));f(M);function x(){var n=M.direction;"alternate"!==n&&(M.direction="normal"!==n?"normal":"reverse"),M.reversed=!M.reversed,t.forEach(function(n){return n.reversed=M.reversed})}function O(n){return M.reversed?M.duration-n:n}function C(){o=0,u=O(M.currentTime)*(1/rn.speed)}function B(n,e){e&&e.seek(n-e.timelineOffset)}function P(n){for(var e=0,r=M.animations,t=r.length;e2||(b=Math.round(b*p)/p)),h.push(b)}var k=d.length;if(k){g=d[0];for(var O=0;O0&&(M.began=!0,I("begin")),!M.loopBegan&&M.currentTime>0&&(M.loopBegan=!0,I("loopBegin")),d<=r&&0!==M.currentTime&&P(0),(d>=l&&M.currentTime!==e||!e)&&P(e),d>r&&d=e&&(u=0,M.remaining&&!0!==M.remaining&&M.remaining--,M.remaining?(o=i,I("loopComplete"),M.loopBegan=!1,"alternate"===M.direction&&x()):(M.paused=!0,M.completed||(M.completed=!0,I("loopComplete"),I("complete"),!M.passThrough&&"Promise"in window&&(s(),f(M)))))}return M.reset=function(){var n=M.direction;M.passThrough=!1,M.currentTime=0,M.progress=0,M.paused=!0,M.began=!1,M.loopBegan=!1,M.changeBegan=!1,M.completed=!1,M.changeCompleted=!1,M.reversePlayback=!1,M.reversed="reverse"===n,M.remaining=M.loop,t=M.children;for(var e=c=t.length;e--;)M.children[e].reset();(M.reversed&&!0!==M.loop||"alternate"===n&&1===M.loop)&&M.remaining++,P(M.reversed?M.duration:0)},M.set=function(n,e){return R(n,e),M},M.tick=function(n){i=n,o||(o=i),T((i+(u-o))*rn.speed)},M.seek=function(n){T(O(n))},M.pause=function(){M.paused=!0,C()},M.play=function(){M.paused&&(M.completed&&M.reset(),M.paused=!1,_.push(M),C(),U||en())},M.reverse=function(){x(),C()},M.restart=function(){M.reset(),M.play()},M.reset(),M.autoplay&&M.play(),M}function tn(n,e){for(var r=e.length;r--;)M(n,e[r].animatable.target)&&e.splice(r,1)}return"undefined"!=typeof document&&document.addEventListener("visibilitychange",function(){document.hidden?(_.forEach(function(n){return n.pause()}),nn=_.slice(0),rn.running=_=[]):nn.forEach(function(n){return n.play()})}),rn.version="3.1.0",rn.speed=1,rn.running=_,rn.remove=function(n){for(var e=Z(n),r=_.length;r--;){var t=_[r],a=t.animations,o=t.children;tn(e,a);for(var u=o.length;u--;){var i=o[u],c=i.animations;tn(e,c),c.length||i.children.length||o.splice(u,1)}a.length||o.length||t.pause()}},rn.get=N,rn.set=R,rn.convertPx=I,rn.path=function(n,e){var r=i.str(n)?g(n)[0]:n,t=e||100;return function(n){return{property:n,el:r,svg:$(r),totalLength:q(r)*(t/100)}}},rn.setDashoffset=function(n){var e=q(n);return n.setAttribute("stroke-dasharray",e),e},rn.stagger=function(n,e){void 0===e&&(e={});var r=e.direction||"normal",t=e.easing?v(e.easing):null,a=e.grid,o=e.axis,u=e.from||0,c="first"===u,s="center"===u,f="last"===u,l=i.arr(n),d=l?parseFloat(n[0]):parseFloat(n),p=l?parseFloat(n[1]):0,h=C(l?n[1]:n)||0,g=e.start||0+(l?d:0),m=[],y=0;return function(n,e,i){if(c&&(u=0),s&&(u=(i-1)/2),f&&(u=i-1),!m.length){for(var v=0;v-1&&_.splice(o,1);for(var s=0;sli{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-webkit-transform:scaleY(-1);transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{-webkit-transform:scale(-1);transform:scale(-1)}:root .fa-flip-both,:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-500px:before{content:"\f26e"}.fa-accessible-icon:before{content:"\f368"}.fa-accusoft:before{content:"\f369"}.fa-acquisitions-incorporated:before{content:"\f6af"}.fa-ad:before{content:"\f641"}.fa-address-book:before{content:"\f2b9"}.fa-address-card:before{content:"\f2bb"}.fa-adjust:before{content:"\f042"}.fa-adn:before{content:"\f170"}.fa-adobe:before{content:"\f778"}.fa-adversal:before{content:"\f36a"}.fa-affiliatetheme:before{content:"\f36b"}.fa-air-freshener:before{content:"\f5d0"}.fa-airbnb:before{content:"\f834"}.fa-algolia:before{content:"\f36c"}.fa-align-center:before{content:"\f037"}.fa-align-justify:before{content:"\f039"}.fa-align-left:before{content:"\f036"}.fa-align-right:before{content:"\f038"}.fa-alipay:before{content:"\f642"}.fa-allergies:before{content:"\f461"}.fa-amazon:before{content:"\f270"}.fa-amazon-pay:before{content:"\f42c"}.fa-ambulance:before{content:"\f0f9"}.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-amilia:before{content:"\f36d"}.fa-anchor:before{content:"\f13d"}.fa-android:before{content:"\f17b"}.fa-angellist:before{content:"\f209"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-down:before{content:"\f107"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angry:before{content:"\f556"}.fa-angrycreative:before{content:"\f36e"}.fa-angular:before{content:"\f420"}.fa-ankh:before{content:"\f644"}.fa-app-store:before{content:"\f36f"}.fa-app-store-ios:before{content:"\f370"}.fa-apper:before{content:"\f371"}.fa-apple:before{content:"\f179"}.fa-apple-alt:before{content:"\f5d1"}.fa-apple-pay:before{content:"\f415"}.fa-archive:before{content:"\f187"}.fa-archway:before{content:"\f557"}.fa-arrow-alt-circle-down:before{content:"\f358"}.fa-arrow-alt-circle-left:before{content:"\f359"}.fa-arrow-alt-circle-right:before{content:"\f35a"}.fa-arrow-alt-circle-up:before{content:"\f35b"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-down:before{content:"\f063"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrows-alt:before{content:"\f0b2"}.fa-arrows-alt-h:before{content:"\f337"}.fa-arrows-alt-v:before{content:"\f338"}.fa-artstation:before{content:"\f77a"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asterisk:before{content:"\f069"}.fa-asymmetrik:before{content:"\f372"}.fa-at:before{content:"\f1fa"}.fa-atlas:before{content:"\f558"}.fa-atlassian:before{content:"\f77b"}.fa-atom:before{content:"\f5d2"}.fa-audible:before{content:"\f373"}.fa-audio-description:before{content:"\f29e"}.fa-autoprefixer:before{content:"\f41c"}.fa-avianex:before{content:"\f374"}.fa-aviato:before{content:"\f421"}.fa-award:before{content:"\f559"}.fa-aws:before{content:"\f375"}.fa-baby:before{content:"\f77c"}.fa-baby-carriage:before{content:"\f77d"}.fa-backspace:before{content:"\f55a"}.fa-backward:before{content:"\f04a"}.fa-bacon:before{content:"\f7e5"}.fa-bahai:before{content:"\f666"}.fa-balance-scale:before{content:"\f24e"}.fa-balance-scale-left:before{content:"\f515"}.fa-balance-scale-right:before{content:"\f516"}.fa-ban:before{content:"\f05e"}.fa-band-aid:before{content:"\f462"}.fa-bandcamp:before{content:"\f2d5"}.fa-barcode:before{content:"\f02a"}.fa-bars:before{content:"\f0c9"}.fa-baseball-ball:before{content:"\f433"}.fa-basketball-ball:before{content:"\f434"}.fa-bath:before{content:"\f2cd"}.fa-battery-empty:before{content:"\f244"}.fa-battery-full:before{content:"\f240"}.fa-battery-half:before{content:"\f242"}.fa-battery-quarter:before{content:"\f243"}.fa-battery-three-quarters:before{content:"\f241"}.fa-battle-net:before{content:"\f835"}.fa-bed:before{content:"\f236"}.fa-beer:before{content:"\f0fc"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-bell:before{content:"\f0f3"}.fa-bell-slash:before{content:"\f1f6"}.fa-bezier-curve:before{content:"\f55b"}.fa-bible:before{content:"\f647"}.fa-bicycle:before{content:"\f206"}.fa-biking:before{content:"\f84a"}.fa-bimobject:before{content:"\f378"}.fa-binoculars:before{content:"\f1e5"}.fa-biohazard:before{content:"\f780"}.fa-birthday-cake:before{content:"\f1fd"}.fa-bitbucket:before{content:"\f171"}.fa-bitcoin:before{content:"\f379"}.fa-bity:before{content:"\f37a"}.fa-black-tie:before{content:"\f27e"}.fa-blackberry:before{content:"\f37b"}.fa-blender:before{content:"\f517"}.fa-blender-phone:before{content:"\f6b6"}.fa-blind:before{content:"\f29d"}.fa-blog:before{content:"\f781"}.fa-blogger:before{content:"\f37c"}.fa-blogger-b:before{content:"\f37d"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-bold:before{content:"\f032"}.fa-bolt:before{content:"\f0e7"}.fa-bomb:before{content:"\f1e2"}.fa-bone:before{content:"\f5d7"}.fa-bong:before{content:"\f55c"}.fa-book:before{content:"\f02d"}.fa-book-dead:before{content:"\f6b7"}.fa-book-medical:before{content:"\f7e6"}.fa-book-open:before{content:"\f518"}.fa-book-reader:before{content:"\f5da"}.fa-bookmark:before{content:"\f02e"}.fa-bootstrap:before{content:"\f836"}.fa-border-all:before{content:"\f84c"}.fa-border-none:before{content:"\f850"}.fa-border-style:before{content:"\f853"}.fa-bowling-ball:before{content:"\f436"}.fa-box:before{content:"\f466"}.fa-box-open:before{content:"\f49e"}.fa-box-tissue:before{content:"\f95b"}.fa-boxes:before{content:"\f468"}.fa-braille:before{content:"\f2a1"}.fa-brain:before{content:"\f5dc"}.fa-bread-slice:before{content:"\f7ec"}.fa-briefcase:before{content:"\f0b1"}.fa-briefcase-medical:before{content:"\f469"}.fa-broadcast-tower:before{content:"\f519"}.fa-broom:before{content:"\f51a"}.fa-brush:before{content:"\f55d"}.fa-btc:before{content:"\f15a"}.fa-buffer:before{content:"\f837"}.fa-bug:before{content:"\f188"}.fa-building:before{content:"\f1ad"}.fa-bullhorn:before{content:"\f0a1"}.fa-bullseye:before{content:"\f140"}.fa-burn:before{content:"\f46a"}.fa-buromobelexperte:before{content:"\f37f"}.fa-bus:before{content:"\f207"}.fa-bus-alt:before{content:"\f55e"}.fa-business-time:before{content:"\f64a"}.fa-buy-n-large:before{content:"\f8a6"}.fa-buysellads:before{content:"\f20d"}.fa-calculator:before{content:"\f1ec"}.fa-calendar:before{content:"\f133"}.fa-calendar-alt:before{content:"\f073"}.fa-calendar-check:before{content:"\f274"}.fa-calendar-day:before{content:"\f783"}.fa-calendar-minus:before{content:"\f272"}.fa-calendar-plus:before{content:"\f271"}.fa-calendar-times:before{content:"\f273"}.fa-calendar-week:before{content:"\f784"}.fa-camera:before{content:"\f030"}.fa-camera-retro:before{content:"\f083"}.fa-campground:before{content:"\f6bb"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-candy-cane:before{content:"\f786"}.fa-cannabis:before{content:"\f55f"}.fa-capsules:before{content:"\f46b"}.fa-car:before{content:"\f1b9"}.fa-car-alt:before{content:"\f5de"}.fa-car-battery:before{content:"\f5df"}.fa-car-crash:before{content:"\f5e1"}.fa-car-side:before{content:"\f5e4"}.fa-caravan:before{content:"\f8ff"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-caret-square-down:before{content:"\f150"}.fa-caret-square-left:before{content:"\f191"}.fa-caret-square-right:before{content:"\f152"}.fa-caret-square-up:before{content:"\f151"}.fa-caret-up:before{content:"\f0d8"}.fa-carrot:before{content:"\f787"}.fa-cart-arrow-down:before{content:"\f218"}.fa-cart-plus:before{content:"\f217"}.fa-cash-register:before{content:"\f788"}.fa-cat:before{content:"\f6be"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-apple-pay:before{content:"\f416"}.fa-cc-diners-club:before{content:"\f24c"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-cc-visa:before{content:"\f1f0"}.fa-centercode:before{content:"\f380"}.fa-centos:before{content:"\f789"}.fa-certificate:before{content:"\f0a3"}.fa-chair:before{content:"\f6c0"}.fa-chalkboard:before{content:"\f51b"}.fa-chalkboard-teacher:before{content:"\f51c"}.fa-charging-station:before{content:"\f5e7"}.fa-chart-area:before{content:"\f1fe"}.fa-chart-bar:before{content:"\f080"}.fa-chart-line:before{content:"\f201"}.fa-chart-pie:before{content:"\f200"}.fa-check:before{content:"\f00c"}.fa-check-circle:before{content:"\f058"}.fa-check-double:before{content:"\f560"}.fa-check-square:before{content:"\f14a"}.fa-cheese:before{content:"\f7ef"}.fa-chess:before{content:"\f439"}.fa-chess-bishop:before{content:"\f43a"}.fa-chess-board:before{content:"\f43c"}.fa-chess-king:before{content:"\f43f"}.fa-chess-knight:before{content:"\f441"}.fa-chess-pawn:before{content:"\f443"}.fa-chess-queen:before{content:"\f445"}.fa-chess-rook:before{content:"\f447"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-down:before{content:"\f078"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-chevron-up:before{content:"\f077"}.fa-child:before{content:"\f1ae"}.fa-chrome:before{content:"\f268"}.fa-chromecast:before{content:"\f838"}.fa-church:before{content:"\f51d"}.fa-circle:before{content:"\f111"}.fa-circle-notch:before{content:"\f1ce"}.fa-city:before{content:"\f64f"}.fa-clinic-medical:before{content:"\f7f2"}.fa-clipboard:before{content:"\f328"}.fa-clipboard-check:before{content:"\f46c"}.fa-clipboard-list:before{content:"\f46d"}.fa-clock:before{content:"\f017"}.fa-clone:before{content:"\f24d"}.fa-closed-captioning:before{content:"\f20a"}.fa-cloud:before{content:"\f0c2"}.fa-cloud-download-alt:before{content:"\f381"}.fa-cloud-meatball:before{content:"\f73b"}.fa-cloud-moon:before{content:"\f6c3"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-cloud-rain:before{content:"\f73d"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-cloud-sun:before{content:"\f6c4"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-cloud-upload-alt:before{content:"\f382"}.fa-cloudscale:before{content:"\f383"}.fa-cloudsmith:before{content:"\f384"}.fa-cloudversify:before{content:"\f385"}.fa-cocktail:before{content:"\f561"}.fa-code:before{content:"\f121"}.fa-code-branch:before{content:"\f126"}.fa-codepen:before{content:"\f1cb"}.fa-codiepie:before{content:"\f284"}.fa-coffee:before{content:"\f0f4"}.fa-cog:before{content:"\f013"}.fa-cogs:before{content:"\f085"}.fa-coins:before{content:"\f51e"}.fa-columns:before{content:"\f0db"}.fa-comment:before{content:"\f075"}.fa-comment-alt:before{content:"\f27a"}.fa-comment-dollar:before{content:"\f651"}.fa-comment-dots:before{content:"\f4ad"}.fa-comment-medical:before{content:"\f7f5"}.fa-comment-slash:before{content:"\f4b3"}.fa-comments:before{content:"\f086"}.fa-comments-dollar:before{content:"\f653"}.fa-compact-disc:before{content:"\f51f"}.fa-compass:before{content:"\f14e"}.fa-compress:before{content:"\f066"}.fa-compress-alt:before{content:"\f422"}.fa-compress-arrows-alt:before{content:"\f78c"}.fa-concierge-bell:before{content:"\f562"}.fa-confluence:before{content:"\f78d"}.fa-connectdevelop:before{content:"\f20e"}.fa-contao:before{content:"\f26d"}.fa-cookie:before{content:"\f563"}.fa-cookie-bite:before{content:"\f564"}.fa-copy:before{content:"\f0c5"}.fa-copyright:before{content:"\f1f9"}.fa-cotton-bureau:before{content:"\f89e"}.fa-couch:before{content:"\f4b8"}.fa-cpanel:before{content:"\f388"}.fa-creative-commons:before{content:"\f25e"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-credit-card:before{content:"\f09d"}.fa-critical-role:before{content:"\f6c9"}.fa-crop:before{content:"\f125"}.fa-crop-alt:before{content:"\f565"}.fa-cross:before{content:"\f654"}.fa-crosshairs:before{content:"\f05b"}.fa-crow:before{content:"\f520"}.fa-crown:before{content:"\f521"}.fa-crutch:before{content:"\f7f7"}.fa-css3:before{content:"\f13c"}.fa-css3-alt:before{content:"\f38b"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-cut:before{content:"\f0c4"}.fa-cuttlefish:before{content:"\f38c"}.fa-d-and-d:before{content:"\f38d"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-dailymotion:before{content:"\f952"}.fa-dashcube:before{content:"\f210"}.fa-database:before{content:"\f1c0"}.fa-deaf:before{content:"\f2a4"}.fa-delicious:before{content:"\f1a5"}.fa-democrat:before{content:"\f747"}.fa-deploydog:before{content:"\f38e"}.fa-deskpro:before{content:"\f38f"}.fa-desktop:before{content:"\f108"}.fa-dev:before{content:"\f6cc"}.fa-deviantart:before{content:"\f1bd"}.fa-dharmachakra:before{content:"\f655"}.fa-dhl:before{content:"\f790"}.fa-diagnoses:before{content:"\f470"}.fa-diaspora:before{content:"\f791"}.fa-dice:before{content:"\f522"}.fa-dice-d20:before{content:"\f6cf"}.fa-dice-d6:before{content:"\f6d1"}.fa-dice-five:before{content:"\f523"}.fa-dice-four:before{content:"\f524"}.fa-dice-one:before{content:"\f525"}.fa-dice-six:before{content:"\f526"}.fa-dice-three:before{content:"\f527"}.fa-dice-two:before{content:"\f528"}.fa-digg:before{content:"\f1a6"}.fa-digital-ocean:before{content:"\f391"}.fa-digital-tachograph:before{content:"\f566"}.fa-directions:before{content:"\f5eb"}.fa-discord:before{content:"\f392"}.fa-discourse:before{content:"\f393"}.fa-disease:before{content:"\f7fa"}.fa-divide:before{content:"\f529"}.fa-dizzy:before{content:"\f567"}.fa-dna:before{content:"\f471"}.fa-dochub:before{content:"\f394"}.fa-docker:before{content:"\f395"}.fa-dog:before{content:"\f6d3"}.fa-dollar-sign:before{content:"\f155"}.fa-dolly:before{content:"\f472"}.fa-dolly-flatbed:before{content:"\f474"}.fa-donate:before{content:"\f4b9"}.fa-door-closed:before{content:"\f52a"}.fa-door-open:before{content:"\f52b"}.fa-dot-circle:before{content:"\f192"}.fa-dove:before{content:"\f4ba"}.fa-download:before{content:"\f019"}.fa-draft2digital:before{content:"\f396"}.fa-drafting-compass:before{content:"\f568"}.fa-dragon:before{content:"\f6d5"}.fa-draw-polygon:before{content:"\f5ee"}.fa-dribbble:before{content:"\f17d"}.fa-dribbble-square:before{content:"\f397"}.fa-dropbox:before{content:"\f16b"}.fa-drum:before{content:"\f569"}.fa-drum-steelpan:before{content:"\f56a"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-drupal:before{content:"\f1a9"}.fa-dumbbell:before{content:"\f44b"}.fa-dumpster:before{content:"\f793"}.fa-dumpster-fire:before{content:"\f794"}.fa-dungeon:before{content:"\f6d9"}.fa-dyalog:before{content:"\f399"}.fa-earlybirds:before{content:"\f39a"}.fa-ebay:before{content:"\f4f4"}.fa-edge:before{content:"\f282"}.fa-edit:before{content:"\f044"}.fa-egg:before{content:"\f7fb"}.fa-eject:before{content:"\f052"}.fa-elementor:before{content:"\f430"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-ello:before{content:"\f5f1"}.fa-ember:before{content:"\f423"}.fa-empire:before{content:"\f1d1"}.fa-envelope:before{content:"\f0e0"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-text:before{content:"\f658"}.fa-envelope-square:before{content:"\f199"}.fa-envira:before{content:"\f299"}.fa-equals:before{content:"\f52c"}.fa-eraser:before{content:"\f12d"}.fa-erlang:before{content:"\f39d"}.fa-ethereum:before{content:"\f42e"}.fa-ethernet:before{content:"\f796"}.fa-etsy:before{content:"\f2d7"}.fa-euro-sign:before{content:"\f153"}.fa-evernote:before{content:"\f839"}.fa-exchange-alt:before{content:"\f362"}.fa-exclamation:before{content:"\f12a"}.fa-exclamation-circle:before{content:"\f06a"}.fa-exclamation-triangle:before{content:"\f071"}.fa-expand:before{content:"\f065"}.fa-expand-alt:before{content:"\f424"}.fa-expand-arrows-alt:before{content:"\f31e"}.fa-expeditedssl:before{content:"\f23e"}.fa-external-link-alt:before{content:"\f35d"}.fa-external-link-square-alt:before{content:"\f360"}.fa-eye:before{content:"\f06e"}.fa-eye-dropper:before{content:"\f1fb"}.fa-eye-slash:before{content:"\f070"}.fa-facebook:before{content:"\f09a"}.fa-facebook-f:before{content:"\f39e"}.fa-facebook-messenger:before{content:"\f39f"}.fa-facebook-square:before{content:"\f082"}.fa-fan:before{content:"\f863"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-fast-backward:before{content:"\f049"}.fa-fast-forward:before{content:"\f050"}.fa-faucet:before{content:"\f905"}.fa-fax:before{content:"\f1ac"}.fa-feather:before{content:"\f52d"}.fa-feather-alt:before{content:"\f56b"}.fa-fedex:before{content:"\f797"}.fa-fedora:before{content:"\f798"}.fa-female:before{content:"\f182"}.fa-fighter-jet:before{content:"\f0fb"}.fa-figma:before{content:"\f799"}.fa-file:before{content:"\f15b"}.fa-file-alt:before{content:"\f15c"}.fa-file-archive:before{content:"\f1c6"}.fa-file-audio:before{content:"\f1c7"}.fa-file-code:before{content:"\f1c9"}.fa-file-contract:before{content:"\f56c"}.fa-file-csv:before{content:"\f6dd"}.fa-file-download:before{content:"\f56d"}.fa-file-excel:before{content:"\f1c3"}.fa-file-export:before{content:"\f56e"}.fa-file-image:before{content:"\f1c5"}.fa-file-import:before{content:"\f56f"}.fa-file-invoice:before{content:"\f570"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-file-medical:before{content:"\f477"}.fa-file-medical-alt:before{content:"\f478"}.fa-file-pdf:before{content:"\f1c1"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-file-prescription:before{content:"\f572"}.fa-file-signature:before{content:"\f573"}.fa-file-upload:before{content:"\f574"}.fa-file-video:before{content:"\f1c8"}.fa-file-word:before{content:"\f1c2"}.fa-fill:before{content:"\f575"}.fa-fill-drip:before{content:"\f576"}.fa-film:before{content:"\f008"}.fa-filter:before{content:"\f0b0"}.fa-fingerprint:before{content:"\f577"}.fa-fire:before{content:"\f06d"}.fa-fire-alt:before{content:"\f7e4"}.fa-fire-extinguisher:before{content:"\f134"}.fa-firefox:before{content:"\f269"}.fa-firefox-browser:before{content:"\f907"}.fa-first-aid:before{content:"\f479"}.fa-first-order:before{content:"\f2b0"}.fa-first-order-alt:before{content:"\f50a"}.fa-firstdraft:before{content:"\f3a1"}.fa-fish:before{content:"\f578"}.fa-fist-raised:before{content:"\f6de"}.fa-flag:before{content:"\f024"}.fa-flag-checkered:before{content:"\f11e"}.fa-flag-usa:before{content:"\f74d"}.fa-flask:before{content:"\f0c3"}.fa-flickr:before{content:"\f16e"}.fa-flipboard:before{content:"\f44d"}.fa-flushed:before{content:"\f579"}.fa-fly:before{content:"\f417"}.fa-folder:before{content:"\f07b"}.fa-folder-minus:before{content:"\f65d"}.fa-folder-open:before{content:"\f07c"}.fa-folder-plus:before{content:"\f65e"}.fa-font:before{content:"\f031"}.fa-font-awesome:before{content:"\f2b4"}.fa-font-awesome-alt:before{content:"\f35c"}.fa-font-awesome-flag:before{content:"\f425"}.fa-font-awesome-logo-full:before{content:"\f4e6"}.fa-fonticons:before{content:"\f280"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-football-ball:before{content:"\f44e"}.fa-fort-awesome:before{content:"\f286"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-forumbee:before{content:"\f211"}.fa-forward:before{content:"\f04e"}.fa-foursquare:before{content:"\f180"}.fa-free-code-camp:before{content:"\f2c5"}.fa-freebsd:before{content:"\f3a4"}.fa-frog:before{content:"\f52e"}.fa-frown:before{content:"\f119"}.fa-frown-open:before{content:"\f57a"}.fa-fulcrum:before{content:"\f50b"}.fa-funnel-dollar:before{content:"\f662"}.fa-futbol:before{content:"\f1e3"}.fa-galactic-republic:before{content:"\f50c"}.fa-galactic-senate:before{content:"\f50d"}.fa-gamepad:before{content:"\f11b"}.fa-gas-pump:before{content:"\f52f"}.fa-gavel:before{content:"\f0e3"}.fa-gem:before{content:"\f3a5"}.fa-genderless:before{content:"\f22d"}.fa-get-pocket:before{content:"\f265"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-ghost:before{content:"\f6e2"}.fa-gift:before{content:"\f06b"}.fa-gifts:before{content:"\f79c"}.fa-git:before{content:"\f1d3"}.fa-git-alt:before{content:"\f841"}.fa-git-square:before{content:"\f1d2"}.fa-github:before{content:"\f09b"}.fa-github-alt:before{content:"\f113"}.fa-github-square:before{content:"\f092"}.fa-gitkraken:before{content:"\f3a6"}.fa-gitlab:before{content:"\f296"}.fa-gitter:before{content:"\f426"}.fa-glass-cheers:before{content:"\f79f"}.fa-glass-martini:before{content:"\f000"}.fa-glass-martini-alt:before{content:"\f57b"}.fa-glass-whiskey:before{content:"\f7a0"}.fa-glasses:before{content:"\f530"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-globe:before{content:"\f0ac"}.fa-globe-africa:before{content:"\f57c"}.fa-globe-americas:before{content:"\f57d"}.fa-globe-asia:before{content:"\f57e"}.fa-globe-europe:before{content:"\f7a2"}.fa-gofore:before{content:"\f3a7"}.fa-golf-ball:before{content:"\f450"}.fa-goodreads:before{content:"\f3a8"}.fa-goodreads-g:before{content:"\f3a9"}.fa-google:before{content:"\f1a0"}.fa-google-drive:before{content:"\f3aa"}.fa-google-play:before{content:"\f3ab"}.fa-google-plus:before{content:"\f2b3"}.fa-google-plus-g:before{content:"\f0d5"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-wallet:before{content:"\f1ee"}.fa-gopuram:before{content:"\f664"}.fa-graduation-cap:before{content:"\f19d"}.fa-gratipay:before{content:"\f184"}.fa-grav:before{content:"\f2d6"}.fa-greater-than:before{content:"\f531"}.fa-greater-than-equal:before{content:"\f532"}.fa-grimace:before{content:"\f57f"}.fa-grin:before{content:"\f580"}.fa-grin-alt:before{content:"\f581"}.fa-grin-beam:before{content:"\f582"}.fa-grin-beam-sweat:before{content:"\f583"}.fa-grin-hearts:before{content:"\f584"}.fa-grin-squint:before{content:"\f585"}.fa-grin-squint-tears:before{content:"\f586"}.fa-grin-stars:before{content:"\f587"}.fa-grin-tears:before{content:"\f588"}.fa-grin-tongue:before{content:"\f589"}.fa-grin-tongue-squint:before{content:"\f58a"}.fa-grin-tongue-wink:before{content:"\f58b"}.fa-grin-wink:before{content:"\f58c"}.fa-grip-horizontal:before{content:"\f58d"}.fa-grip-lines:before{content:"\f7a4"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-grip-vertical:before{content:"\f58e"}.fa-gripfire:before{content:"\f3ac"}.fa-grunt:before{content:"\f3ad"}.fa-guitar:before{content:"\f7a6"}.fa-gulp:before{content:"\f3ae"}.fa-h-square:before{content:"\f0fd"}.fa-hacker-news:before{content:"\f1d4"}.fa-hacker-news-square:before{content:"\f3af"}.fa-hackerrank:before{content:"\f5f7"}.fa-hamburger:before{content:"\f805"}.fa-hammer:before{content:"\f6e3"}.fa-hamsa:before{content:"\f665"}.fa-hand-holding:before{content:"\f4bd"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-hand-holding-medical:before{content:"\f95c"}.fa-hand-holding-usd:before{content:"\f4c0"}.fa-hand-holding-water:before{content:"\f4c1"}.fa-hand-lizard:before{content:"\f258"}.fa-hand-middle-finger:before{content:"\f806"}.fa-hand-paper:before{content:"\f256"}.fa-hand-peace:before{content:"\f25b"}.fa-hand-point-down:before{content:"\f0a7"}.fa-hand-point-left:before{content:"\f0a5"}.fa-hand-point-right:before{content:"\f0a4"}.fa-hand-point-up:before{content:"\f0a6"}.fa-hand-pointer:before{content:"\f25a"}.fa-hand-rock:before{content:"\f255"}.fa-hand-scissors:before{content:"\f257"}.fa-hand-sparkles:before{content:"\f95d"}.fa-hand-spock:before{content:"\f259"}.fa-hands:before{content:"\f4c2"}.fa-hands-helping:before{content:"\f4c4"}.fa-hands-wash:before{content:"\f95e"}.fa-handshake:before{content:"\f2b5"}.fa-handshake-alt-slash:before{content:"\f95f"}.fa-handshake-slash:before{content:"\f960"}.fa-hanukiah:before{content:"\f6e6"}.fa-hard-hat:before{content:"\f807"}.fa-hashtag:before{content:"\f292"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-hat-wizard:before{content:"\f6e8"}.fa-hdd:before{content:"\f0a0"}.fa-head-side-cough:before{content:"\f961"}.fa-head-side-cough-slash:before{content:"\f962"}.fa-head-side-mask:before{content:"\f963"}.fa-head-side-virus:before{content:"\f964"}.fa-heading:before{content:"\f1dc"}.fa-headphones:before{content:"\f025"}.fa-headphones-alt:before{content:"\f58f"}.fa-headset:before{content:"\f590"}.fa-heart:before{content:"\f004"}.fa-heart-broken:before{content:"\f7a9"}.fa-heartbeat:before{content:"\f21e"}.fa-helicopter:before{content:"\f533"}.fa-highlighter:before{content:"\f591"}.fa-hiking:before{content:"\f6ec"}.fa-hippo:before{content:"\f6ed"}.fa-hips:before{content:"\f452"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-history:before{content:"\f1da"}.fa-hockey-puck:before{content:"\f453"}.fa-holly-berry:before{content:"\f7aa"}.fa-home:before{content:"\f015"}.fa-hooli:before{content:"\f427"}.fa-hornbill:before{content:"\f592"}.fa-horse:before{content:"\f6f0"}.fa-horse-head:before{content:"\f7ab"}.fa-hospital:before{content:"\f0f8"}.fa-hospital-alt:before{content:"\f47d"}.fa-hospital-symbol:before{content:"\f47e"}.fa-hospital-user:before{content:"\f80d"}.fa-hot-tub:before{content:"\f593"}.fa-hotdog:before{content:"\f80f"}.fa-hotel:before{content:"\f594"}.fa-hotjar:before{content:"\f3b1"}.fa-hourglass:before{content:"\f254"}.fa-hourglass-end:before{content:"\f253"}.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-start:before{content:"\f251"}.fa-house-damage:before{content:"\f6f1"}.fa-house-user:before{content:"\f965"}.fa-houzz:before{content:"\f27c"}.fa-hryvnia:before{content:"\f6f2"}.fa-html5:before{content:"\f13b"}.fa-hubspot:before{content:"\f3b2"}.fa-i-cursor:before{content:"\f246"}.fa-ice-cream:before{content:"\f810"}.fa-icicles:before{content:"\f7ad"}.fa-icons:before{content:"\f86d"}.fa-id-badge:before{content:"\f2c1"}.fa-id-card:before{content:"\f2c2"}.fa-id-card-alt:before{content:"\f47f"}.fa-ideal:before{content:"\f913"}.fa-igloo:before{content:"\f7ae"}.fa-image:before{content:"\f03e"}.fa-images:before{content:"\f302"}.fa-imdb:before{content:"\f2d8"}.fa-inbox:before{content:"\f01c"}.fa-indent:before{content:"\f03c"}.fa-industry:before{content:"\f275"}.fa-infinity:before{content:"\f534"}.fa-info:before{content:"\f129"}.fa-info-circle:before{content:"\f05a"}.fa-instagram:before{content:"\f16d"}.fa-instagram-square:before{content:"\f955"}.fa-intercom:before{content:"\f7af"}.fa-internet-explorer:before{content:"\f26b"}.fa-invision:before{content:"\f7b0"}.fa-ioxhost:before{content:"\f208"}.fa-italic:before{content:"\f033"}.fa-itch-io:before{content:"\f83a"}.fa-itunes:before{content:"\f3b4"}.fa-itunes-note:before{content:"\f3b5"}.fa-java:before{content:"\f4e4"}.fa-jedi:before{content:"\f669"}.fa-jedi-order:before{content:"\f50e"}.fa-jenkins:before{content:"\f3b6"}.fa-jira:before{content:"\f7b1"}.fa-joget:before{content:"\f3b7"}.fa-joint:before{content:"\f595"}.fa-joomla:before{content:"\f1aa"}.fa-journal-whills:before{content:"\f66a"}.fa-js:before{content:"\f3b8"}.fa-js-square:before{content:"\f3b9"}.fa-jsfiddle:before{content:"\f1cc"}.fa-kaaba:before{content:"\f66b"}.fa-kaggle:before{content:"\f5fa"}.fa-key:before{content:"\f084"}.fa-keybase:before{content:"\f4f5"}.fa-keyboard:before{content:"\f11c"}.fa-keycdn:before{content:"\f3ba"}.fa-khanda:before{content:"\f66d"}.fa-kickstarter:before{content:"\f3bb"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-kiss:before{content:"\f596"}.fa-kiss-beam:before{content:"\f597"}.fa-kiss-wink-heart:before{content:"\f598"}.fa-kiwi-bird:before{content:"\f535"}.fa-korvue:before{content:"\f42f"}.fa-landmark:before{content:"\f66f"}.fa-language:before{content:"\f1ab"}.fa-laptop:before{content:"\f109"}.fa-laptop-code:before{content:"\f5fc"}.fa-laptop-house:before{content:"\f966"}.fa-laptop-medical:before{content:"\f812"}.fa-laravel:before{content:"\f3bd"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-laugh:before{content:"\f599"}.fa-laugh-beam:before{content:"\f59a"}.fa-laugh-squint:before{content:"\f59b"}.fa-laugh-wink:before{content:"\f59c"}.fa-layer-group:before{content:"\f5fd"}.fa-leaf:before{content:"\f06c"}.fa-leanpub:before{content:"\f212"}.fa-lemon:before{content:"\f094"}.fa-less:before{content:"\f41d"}.fa-less-than:before{content:"\f536"}.fa-less-than-equal:before{content:"\f537"}.fa-level-down-alt:before{content:"\f3be"}.fa-level-up-alt:before{content:"\f3bf"}.fa-life-ring:before{content:"\f1cd"}.fa-lightbulb:before{content:"\f0eb"}.fa-line:before{content:"\f3c0"}.fa-link:before{content:"\f0c1"}.fa-linkedin:before{content:"\f08c"}.fa-linkedin-in:before{content:"\f0e1"}.fa-linode:before{content:"\f2b8"}.fa-linux:before{content:"\f17c"}.fa-lira-sign:before{content:"\f195"}.fa-list:before{content:"\f03a"}.fa-list-alt:before{content:"\f022"}.fa-list-ol:before{content:"\f0cb"}.fa-list-ul:before{content:"\f0ca"}.fa-location-arrow:before{content:"\f124"}.fa-lock:before{content:"\f023"}.fa-lock-open:before{content:"\f3c1"}.fa-long-arrow-alt-down:before{content:"\f309"}.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-long-arrow-alt-right:before{content:"\f30b"}.fa-long-arrow-alt-up:before{content:"\f30c"}.fa-low-vision:before{content:"\f2a8"}.fa-luggage-cart:before{content:"\f59d"}.fa-lungs:before{content:"\f604"}.fa-lungs-virus:before{content:"\f967"}.fa-lyft:before{content:"\f3c3"}.fa-magento:before{content:"\f3c4"}.fa-magic:before{content:"\f0d0"}.fa-magnet:before{content:"\f076"}.fa-mail-bulk:before{content:"\f674"}.fa-mailchimp:before{content:"\f59e"}.fa-male:before{content:"\f183"}.fa-mandalorian:before{content:"\f50f"}.fa-map:before{content:"\f279"}.fa-map-marked:before{content:"\f59f"}.fa-map-marked-alt:before{content:"\f5a0"}.fa-map-marker:before{content:"\f041"}.fa-map-marker-alt:before{content:"\f3c5"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-markdown:before{content:"\f60f"}.fa-marker:before{content:"\f5a1"}.fa-mars:before{content:"\f222"}.fa-mars-double:before{content:"\f227"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mask:before{content:"\f6fa"}.fa-mastodon:before{content:"\f4f6"}.fa-maxcdn:before{content:"\f136"}.fa-mdb:before{content:"\f8ca"}.fa-medal:before{content:"\f5a2"}.fa-medapps:before{content:"\f3c6"}.fa-medium:before{content:"\f23a"}.fa-medium-m:before{content:"\f3c7"}.fa-medkit:before{content:"\f0fa"}.fa-medrt:before{content:"\f3c8"}.fa-meetup:before{content:"\f2e0"}.fa-megaport:before{content:"\f5a3"}.fa-meh:before{content:"\f11a"}.fa-meh-blank:before{content:"\f5a4"}.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-memory:before{content:"\f538"}.fa-mendeley:before{content:"\f7b3"}.fa-menorah:before{content:"\f676"}.fa-mercury:before{content:"\f223"}.fa-meteor:before{content:"\f753"}.fa-microblog:before{content:"\f91a"}.fa-microchip:before{content:"\f2db"}.fa-microphone:before{content:"\f130"}.fa-microphone-alt:before{content:"\f3c9"}.fa-microphone-alt-slash:before{content:"\f539"}.fa-microphone-slash:before{content:"\f131"}.fa-microscope:before{content:"\f610"}.fa-microsoft:before{content:"\f3ca"}.fa-minus:before{content:"\f068"}.fa-minus-circle:before{content:"\f056"}.fa-minus-square:before{content:"\f146"}.fa-mitten:before{content:"\f7b5"}.fa-mix:before{content:"\f3cb"}.fa-mixcloud:before{content:"\f289"}.fa-mixer:before{content:"\f956"}.fa-mizuni:before{content:"\f3cc"}.fa-mobile:before{content:"\f10b"}.fa-mobile-alt:before{content:"\f3cd"}.fa-modx:before{content:"\f285"}.fa-monero:before{content:"\f3d0"}.fa-money-bill:before{content:"\f0d6"}.fa-money-bill-alt:before{content:"\f3d1"}.fa-money-bill-wave:before{content:"\f53a"}.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-money-check:before{content:"\f53c"}.fa-money-check-alt:before{content:"\f53d"}.fa-monument:before{content:"\f5a6"}.fa-moon:before{content:"\f186"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-mosque:before{content:"\f678"}.fa-motorcycle:before{content:"\f21c"}.fa-mountain:before{content:"\f6fc"}.fa-mouse:before{content:"\f8cc"}.fa-mouse-pointer:before{content:"\f245"}.fa-mug-hot:before{content:"\f7b6"}.fa-music:before{content:"\f001"}.fa-napster:before{content:"\f3d2"}.fa-neos:before{content:"\f612"}.fa-network-wired:before{content:"\f6ff"}.fa-neuter:before{content:"\f22c"}.fa-newspaper:before{content:"\f1ea"}.fa-nimblr:before{content:"\f5a8"}.fa-node:before{content:"\f419"}.fa-node-js:before{content:"\f3d3"}.fa-not-equal:before{content:"\f53e"}.fa-notes-medical:before{content:"\f481"}.fa-npm:before{content:"\f3d4"}.fa-ns8:before{content:"\f3d5"}.fa-nutritionix:before{content:"\f3d6"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-oil-can:before{content:"\f613"}.fa-old-republic:before{content:"\f510"}.fa-om:before{content:"\f679"}.fa-opencart:before{content:"\f23d"}.fa-openid:before{content:"\f19b"}.fa-opera:before{content:"\f26a"}.fa-optin-monster:before{content:"\f23c"}.fa-orcid:before{content:"\f8d2"}.fa-osi:before{content:"\f41a"}.fa-otter:before{content:"\f700"}.fa-outdent:before{content:"\f03b"}.fa-page4:before{content:"\f3d7"}.fa-pagelines:before{content:"\f18c"}.fa-pager:before{content:"\f815"}.fa-paint-brush:before{content:"\f1fc"}.fa-paint-roller:before{content:"\f5aa"}.fa-palette:before{content:"\f53f"}.fa-palfed:before{content:"\f3d8"}.fa-pallet:before{content:"\f482"}.fa-paper-plane:before{content:"\f1d8"}.fa-paperclip:before{content:"\f0c6"}.fa-parachute-box:before{content:"\f4cd"}.fa-paragraph:before{content:"\f1dd"}.fa-parking:before{content:"\f540"}.fa-passport:before{content:"\f5ab"}.fa-pastafarianism:before{content:"\f67b"}.fa-paste:before{content:"\f0ea"}.fa-patreon:before{content:"\f3d9"}.fa-pause:before{content:"\f04c"}.fa-pause-circle:before{content:"\f28b"}.fa-paw:before{content:"\f1b0"}.fa-paypal:before{content:"\f1ed"}.fa-peace:before{content:"\f67c"}.fa-pen:before{content:"\f304"}.fa-pen-alt:before{content:"\f305"}.fa-pen-fancy:before{content:"\f5ac"}.fa-pen-nib:before{content:"\f5ad"}.fa-pen-square:before{content:"\f14b"}.fa-pencil-alt:before{content:"\f303"}.fa-pencil-ruler:before{content:"\f5ae"}.fa-penny-arcade:before{content:"\f704"}.fa-people-arrows:before{content:"\f968"}.fa-people-carry:before{content:"\f4ce"}.fa-pepper-hot:before{content:"\f816"}.fa-percent:before{content:"\f295"}.fa-percentage:before{content:"\f541"}.fa-periscope:before{content:"\f3da"}.fa-person-booth:before{content:"\f756"}.fa-phabricator:before{content:"\f3db"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-phoenix-squadron:before{content:"\f511"}.fa-phone:before{content:"\f095"}.fa-phone-alt:before{content:"\f879"}.fa-phone-slash:before{content:"\f3dd"}.fa-phone-square:before{content:"\f098"}.fa-phone-square-alt:before{content:"\f87b"}.fa-phone-volume:before{content:"\f2a0"}.fa-photo-video:before{content:"\f87c"}.fa-php:before{content:"\f457"}.fa-pied-piper:before{content:"\f2ae"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-square:before{content:"\f91e"}.fa-piggy-bank:before{content:"\f4d3"}.fa-pills:before{content:"\f484"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-p:before{content:"\f231"}.fa-pinterest-square:before{content:"\f0d3"}.fa-pizza-slice:before{content:"\f818"}.fa-place-of-worship:before{content:"\f67f"}.fa-plane:before{content:"\f072"}.fa-plane-arrival:before{content:"\f5af"}.fa-plane-departure:before{content:"\f5b0"}.fa-plane-slash:before{content:"\f969"}.fa-play:before{content:"\f04b"}.fa-play-circle:before{content:"\f144"}.fa-playstation:before{content:"\f3df"}.fa-plug:before{content:"\f1e6"}.fa-plus:before{content:"\f067"}.fa-plus-circle:before{content:"\f055"}.fa-plus-square:before{content:"\f0fe"}.fa-podcast:before{content:"\f2ce"}.fa-poll:before{content:"\f681"}.fa-poll-h:before{content:"\f682"}.fa-poo:before{content:"\f2fe"}.fa-poo-storm:before{content:"\f75a"}.fa-poop:before{content:"\f619"}.fa-portrait:before{content:"\f3e0"}.fa-pound-sign:before{content:"\f154"}.fa-power-off:before{content:"\f011"}.fa-pray:before{content:"\f683"}.fa-praying-hands:before{content:"\f684"}.fa-prescription:before{content:"\f5b1"}.fa-prescription-bottle:before{content:"\f485"}.fa-prescription-bottle-alt:before{content:"\f486"}.fa-print:before{content:"\f02f"}.fa-procedures:before{content:"\f487"}.fa-product-hunt:before{content:"\f288"}.fa-project-diagram:before{content:"\f542"}.fa-pump-medical:before{content:"\f96a"}.fa-pump-soap:before{content:"\f96b"}.fa-pushed:before{content:"\f3e1"}.fa-puzzle-piece:before{content:"\f12e"}.fa-python:before{content:"\f3e2"}.fa-qq:before{content:"\f1d6"}.fa-qrcode:before{content:"\f029"}.fa-question:before{content:"\f128"}.fa-question-circle:before{content:"\f059"}.fa-quidditch:before{content:"\f458"}.fa-quinscape:before{content:"\f459"}.fa-quora:before{content:"\f2c4"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-quran:before{content:"\f687"}.fa-r-project:before{content:"\f4f7"}.fa-radiation:before{content:"\f7b9"}.fa-radiation-alt:before{content:"\f7ba"}.fa-rainbow:before{content:"\f75b"}.fa-random:before{content:"\f074"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-ravelry:before{content:"\f2d9"}.fa-react:before{content:"\f41b"}.fa-reacteurope:before{content:"\f75d"}.fa-readme:before{content:"\f4d5"}.fa-rebel:before{content:"\f1d0"}.fa-receipt:before{content:"\f543"}.fa-record-vinyl:before{content:"\f8d9"}.fa-recycle:before{content:"\f1b8"}.fa-red-river:before{content:"\f3e3"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-alien:before{content:"\f281"}.fa-reddit-square:before{content:"\f1a2"}.fa-redhat:before{content:"\f7bc"}.fa-redo:before{content:"\f01e"}.fa-redo-alt:before{content:"\f2f9"}.fa-registered:before{content:"\f25d"}.fa-remove-format:before{content:"\f87d"}.fa-renren:before{content:"\f18b"}.fa-reply:before{content:"\f3e5"}.fa-reply-all:before{content:"\f122"}.fa-replyd:before{content:"\f3e6"}.fa-republican:before{content:"\f75e"}.fa-researchgate:before{content:"\f4f8"}.fa-resolving:before{content:"\f3e7"}.fa-restroom:before{content:"\f7bd"}.fa-retweet:before{content:"\f079"}.fa-rev:before{content:"\f5b2"}.fa-ribbon:before{content:"\f4d6"}.fa-ring:before{content:"\f70b"}.fa-road:before{content:"\f018"}.fa-robot:before{content:"\f544"}.fa-rocket:before{content:"\f135"}.fa-rocketchat:before{content:"\f3e8"}.fa-rockrms:before{content:"\f3e9"}.fa-route:before{content:"\f4d7"}.fa-rss:before{content:"\f09e"}.fa-rss-square:before{content:"\f143"}.fa-ruble-sign:before{content:"\f158"}.fa-ruler:before{content:"\f545"}.fa-ruler-combined:before{content:"\f546"}.fa-ruler-horizontal:before{content:"\f547"}.fa-ruler-vertical:before{content:"\f548"}.fa-running:before{content:"\f70c"}.fa-rupee-sign:before{content:"\f156"}.fa-sad-cry:before{content:"\f5b3"}.fa-sad-tear:before{content:"\f5b4"}.fa-safari:before{content:"\f267"}.fa-salesforce:before{content:"\f83b"}.fa-sass:before{content:"\f41e"}.fa-satellite:before{content:"\f7bf"}.fa-satellite-dish:before{content:"\f7c0"}.fa-save:before{content:"\f0c7"}.fa-schlix:before{content:"\f3ea"}.fa-school:before{content:"\f549"}.fa-screwdriver:before{content:"\f54a"}.fa-scribd:before{content:"\f28a"}.fa-scroll:before{content:"\f70e"}.fa-sd-card:before{content:"\f7c2"}.fa-search:before{content:"\f002"}.fa-search-dollar:before{content:"\f688"}.fa-search-location:before{content:"\f689"}.fa-search-minus:before{content:"\f010"}.fa-search-plus:before{content:"\f00e"}.fa-searchengin:before{content:"\f3eb"}.fa-seedling:before{content:"\f4d8"}.fa-sellcast:before{content:"\f2da"}.fa-sellsy:before{content:"\f213"}.fa-server:before{content:"\f233"}.fa-servicestack:before{content:"\f3ec"}.fa-shapes:before{content:"\f61f"}.fa-share:before{content:"\f064"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-share-square:before{content:"\f14d"}.fa-shekel-sign:before{content:"\f20b"}.fa-shield-alt:before{content:"\f3ed"}.fa-shield-virus:before{content:"\f96c"}.fa-ship:before{content:"\f21a"}.fa-shipping-fast:before{content:"\f48b"}.fa-shirtsinbulk:before{content:"\f214"}.fa-shoe-prints:before{content:"\f54b"}.fa-shopify:before{content:"\f957"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-shopping-cart:before{content:"\f07a"}.fa-shopware:before{content:"\f5b5"}.fa-shower:before{content:"\f2cc"}.fa-shuttle-van:before{content:"\f5b6"}.fa-sign:before{content:"\f4d9"}.fa-sign-in-alt:before{content:"\f2f6"}.fa-sign-language:before{content:"\f2a7"}.fa-sign-out-alt:before{content:"\f2f5"}.fa-signal:before{content:"\f012"}.fa-signature:before{content:"\f5b7"}.fa-sim-card:before{content:"\f7c4"}.fa-simplybuilt:before{content:"\f215"}.fa-sistrix:before{content:"\f3ee"}.fa-sitemap:before{content:"\f0e8"}.fa-sith:before{content:"\f512"}.fa-skating:before{content:"\f7c5"}.fa-sketch:before{content:"\f7c6"}.fa-skiing:before{content:"\f7c9"}.fa-skiing-nordic:before{content:"\f7ca"}.fa-skull:before{content:"\f54c"}.fa-skull-crossbones:before{content:"\f714"}.fa-skyatlas:before{content:"\f216"}.fa-skype:before{content:"\f17e"}.fa-slack:before{content:"\f198"}.fa-slack-hash:before{content:"\f3ef"}.fa-slash:before{content:"\f715"}.fa-sleigh:before{content:"\f7cc"}.fa-sliders-h:before{content:"\f1de"}.fa-slideshare:before{content:"\f1e7"}.fa-smile:before{content:"\f118"}.fa-smile-beam:before{content:"\f5b8"}.fa-smile-wink:before{content:"\f4da"}.fa-smog:before{content:"\f75f"}.fa-smoking:before{content:"\f48d"}.fa-smoking-ban:before{content:"\f54d"}.fa-sms:before{content:"\f7cd"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-snowboarding:before{content:"\f7ce"}.fa-snowflake:before{content:"\f2dc"}.fa-snowman:before{content:"\f7d0"}.fa-snowplow:before{content:"\f7d2"}.fa-soap:before{content:"\f96e"}.fa-socks:before{content:"\f696"}.fa-solar-panel:before{content:"\f5ba"}.fa-sort:before{content:"\f0dc"}.fa-sort-alpha-down:before{content:"\f15d"}.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-sort-alpha-up:before{content:"\f15e"}.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-sort-amount-down:before{content:"\f160"}.fa-sort-amount-down-alt:before{content:"\f884"}.fa-sort-amount-up:before{content:"\f161"}.fa-sort-amount-up-alt:before{content:"\f885"}.fa-sort-down:before{content:"\f0dd"}.fa-sort-numeric-down:before{content:"\f162"}.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-sort-numeric-up:before{content:"\f163"}.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-sort-up:before{content:"\f0de"}.fa-soundcloud:before{content:"\f1be"}.fa-sourcetree:before{content:"\f7d3"}.fa-spa:before{content:"\f5bb"}.fa-space-shuttle:before{content:"\f197"}.fa-speakap:before{content:"\f3f3"}.fa-speaker-deck:before{content:"\f83c"}.fa-spell-check:before{content:"\f891"}.fa-spider:before{content:"\f717"}.fa-spinner:before{content:"\f110"}.fa-splotch:before{content:"\f5bc"}.fa-spotify:before{content:"\f1bc"}.fa-spray-can:before{content:"\f5bd"}.fa-square:before{content:"\f0c8"}.fa-square-full:before{content:"\f45c"}.fa-square-root-alt:before{content:"\f698"}.fa-squarespace:before{content:"\f5be"}.fa-stack-exchange:before{content:"\f18d"}.fa-stack-overflow:before{content:"\f16c"}.fa-stackpath:before{content:"\f842"}.fa-stamp:before{content:"\f5bf"}.fa-star:before{content:"\f005"}.fa-star-and-crescent:before{content:"\f699"}.fa-star-half:before{content:"\f089"}.fa-star-half-alt:before{content:"\f5c0"}.fa-star-of-david:before{content:"\f69a"}.fa-star-of-life:before{content:"\f621"}.fa-staylinked:before{content:"\f3f5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-steam-symbol:before{content:"\f3f6"}.fa-step-backward:before{content:"\f048"}.fa-step-forward:before{content:"\f051"}.fa-stethoscope:before{content:"\f0f1"}.fa-sticker-mule:before{content:"\f3f7"}.fa-sticky-note:before{content:"\f249"}.fa-stop:before{content:"\f04d"}.fa-stop-circle:before{content:"\f28d"}.fa-stopwatch:before{content:"\f2f2"}.fa-stopwatch-20:before{content:"\f96f"}.fa-store:before{content:"\f54e"}.fa-store-alt:before{content:"\f54f"}.fa-store-alt-slash:before{content:"\f970"}.fa-store-slash:before{content:"\f971"}.fa-strava:before{content:"\f428"}.fa-stream:before{content:"\f550"}.fa-street-view:before{content:"\f21d"}.fa-strikethrough:before{content:"\f0cc"}.fa-stripe:before{content:"\f429"}.fa-stripe-s:before{content:"\f42a"}.fa-stroopwafel:before{content:"\f551"}.fa-studiovinari:before{content:"\f3f8"}.fa-stumbleupon:before{content:"\f1a4"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-subscript:before{content:"\f12c"}.fa-subway:before{content:"\f239"}.fa-suitcase:before{content:"\f0f2"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-sun:before{content:"\f185"}.fa-superpowers:before{content:"\f2dd"}.fa-superscript:before{content:"\f12b"}.fa-supple:before{content:"\f3f9"}.fa-surprise:before{content:"\f5c2"}.fa-suse:before{content:"\f7d6"}.fa-swatchbook:before{content:"\f5c3"}.fa-swift:before{content:"\f8e1"}.fa-swimmer:before{content:"\f5c4"}.fa-swimming-pool:before{content:"\f5c5"}.fa-symfony:before{content:"\f83d"}.fa-synagogue:before{content:"\f69b"}.fa-sync:before{content:"\f021"}.fa-sync-alt:before{content:"\f2f1"}.fa-syringe:before{content:"\f48e"}.fa-table:before{content:"\f0ce"}.fa-table-tennis:before{content:"\f45d"}.fa-tablet:before{content:"\f10a"}.fa-tablet-alt:before{content:"\f3fa"}.fa-tablets:before{content:"\f490"}.fa-tachometer-alt:before{content:"\f3fd"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-tape:before{content:"\f4db"}.fa-tasks:before{content:"\f0ae"}.fa-taxi:before{content:"\f1ba"}.fa-teamspeak:before{content:"\f4f9"}.fa-teeth:before{content:"\f62e"}.fa-teeth-open:before{content:"\f62f"}.fa-telegram:before{content:"\f2c6"}.fa-telegram-plane:before{content:"\f3fe"}.fa-temperature-high:before{content:"\f769"}.fa-temperature-low:before{content:"\f76b"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-tenge:before{content:"\f7d7"}.fa-terminal:before{content:"\f120"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-th:before{content:"\f00a"}.fa-th-large:before{content:"\f009"}.fa-th-list:before{content:"\f00b"}.fa-the-red-yeti:before{content:"\f69d"}.fa-theater-masks:before{content:"\f630"}.fa-themeco:before{content:"\f5c6"}.fa-themeisle:before{content:"\f2b2"}.fa-thermometer:before{content:"\f491"}.fa-thermometer-empty:before{content:"\f2cb"}.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-think-peaks:before{content:"\f731"}.fa-thumbs-down:before{content:"\f165"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbtack:before{content:"\f08d"}.fa-ticket-alt:before{content:"\f3ff"}.fa-times:before{content:"\f00d"}.fa-times-circle:before{content:"\f057"}.fa-tint:before{content:"\f043"}.fa-tint-slash:before{content:"\f5c7"}.fa-tired:before{content:"\f5c8"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-toilet:before{content:"\f7d8"}.fa-toilet-paper:before{content:"\f71e"}.fa-toilet-paper-slash:before{content:"\f972"}.fa-toolbox:before{content:"\f552"}.fa-tools:before{content:"\f7d9"}.fa-tooth:before{content:"\f5c9"}.fa-torah:before{content:"\f6a0"}.fa-torii-gate:before{content:"\f6a1"}.fa-tractor:before{content:"\f722"}.fa-trade-federation:before{content:"\f513"}.fa-trademark:before{content:"\f25c"}.fa-traffic-light:before{content:"\f637"}.fa-trailer:before{content:"\f941"}.fa-train:before{content:"\f238"}.fa-tram:before{content:"\f7da"}.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-trash:before{content:"\f1f8"}.fa-trash-alt:before{content:"\f2ed"}.fa-trash-restore:before{content:"\f829"}.fa-trash-restore-alt:before{content:"\f82a"}.fa-tree:before{content:"\f1bb"}.fa-trello:before{content:"\f181"}.fa-tripadvisor:before{content:"\f262"}.fa-trophy:before{content:"\f091"}.fa-truck:before{content:"\f0d1"}.fa-truck-loading:before{content:"\f4de"}.fa-truck-monster:before{content:"\f63b"}.fa-truck-moving:before{content:"\f4df"}.fa-truck-pickup:before{content:"\f63c"}.fa-tshirt:before{content:"\f553"}.fa-tty:before{content:"\f1e4"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-tv:before{content:"\f26c"}.fa-twitch:before{content:"\f1e8"}.fa-twitter:before{content:"\f099"}.fa-twitter-square:before{content:"\f081"}.fa-typo3:before{content:"\f42b"}.fa-uber:before{content:"\f402"}.fa-ubuntu:before{content:"\f7df"}.fa-uikit:before{content:"\f403"}.fa-umbraco:before{content:"\f8e8"}.fa-umbrella:before{content:"\f0e9"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-underline:before{content:"\f0cd"}.fa-undo:before{content:"\f0e2"}.fa-undo-alt:before{content:"\f2ea"}.fa-uniregistry:before{content:"\f404"}.fa-unity:before{content:"\f949"}.fa-universal-access:before{content:"\f29a"}.fa-university:before{content:"\f19c"}.fa-unlink:before{content:"\f127"}.fa-unlock:before{content:"\f09c"}.fa-unlock-alt:before{content:"\f13e"}.fa-untappd:before{content:"\f405"}.fa-upload:before{content:"\f093"}.fa-ups:before{content:"\f7e0"}.fa-usb:before{content:"\f287"}.fa-user:before{content:"\f007"}.fa-user-alt:before{content:"\f406"}.fa-user-alt-slash:before{content:"\f4fa"}.fa-user-astronaut:before{content:"\f4fb"}.fa-user-check:before{content:"\f4fc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-clock:before{content:"\f4fd"}.fa-user-cog:before{content:"\f4fe"}.fa-user-edit:before{content:"\f4ff"}.fa-user-friends:before{content:"\f500"}.fa-user-graduate:before{content:"\f501"}.fa-user-injured:before{content:"\f728"}.fa-user-lock:before{content:"\f502"}.fa-user-md:before{content:"\f0f0"}.fa-user-minus:before{content:"\f503"}.fa-user-ninja:before{content:"\f504"}.fa-user-nurse:before{content:"\f82f"}.fa-user-plus:before{content:"\f234"}.fa-user-secret:before{content:"\f21b"}.fa-user-shield:before{content:"\f505"}.fa-user-slash:before{content:"\f506"}.fa-user-tag:before{content:"\f507"}.fa-user-tie:before{content:"\f508"}.fa-user-times:before{content:"\f235"}.fa-users:before{content:"\f0c0"}.fa-users-cog:before{content:"\f509"}.fa-usps:before{content:"\f7e1"}.fa-ussunnah:before{content:"\f407"}.fa-utensil-spoon:before{content:"\f2e5"}.fa-utensils:before{content:"\f2e7"}.fa-vaadin:before{content:"\f408"}.fa-vector-square:before{content:"\f5cb"}.fa-venus:before{content:"\f221"}.fa-venus-double:before{content:"\f226"}.fa-venus-mars:before{content:"\f228"}.fa-viacoin:before{content:"\f237"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-vial:before{content:"\f492"}.fa-vials:before{content:"\f493"}.fa-viber:before{content:"\f409"}.fa-video:before{content:"\f03d"}.fa-video-slash:before{content:"\f4e2"}.fa-vihara:before{content:"\f6a7"}.fa-vimeo:before{content:"\f40a"}.fa-vimeo-square:before{content:"\f194"}.fa-vimeo-v:before{content:"\f27d"}.fa-vine:before{content:"\f1ca"}.fa-virus:before{content:"\f974"}.fa-virus-slash:before{content:"\f975"}.fa-viruses:before{content:"\f976"}.fa-vk:before{content:"\f189"}.fa-vnv:before{content:"\f40b"}.fa-voicemail:before{content:"\f897"}.fa-volleyball-ball:before{content:"\f45f"}.fa-volume-down:before{content:"\f027"}.fa-volume-mute:before{content:"\f6a9"}.fa-volume-off:before{content:"\f026"}.fa-volume-up:before{content:"\f028"}.fa-vote-yea:before{content:"\f772"}.fa-vr-cardboard:before{content:"\f729"}.fa-vuejs:before{content:"\f41f"}.fa-walking:before{content:"\f554"}.fa-wallet:before{content:"\f555"}.fa-warehouse:before{content:"\f494"}.fa-water:before{content:"\f773"}.fa-wave-square:before{content:"\f83e"}.fa-waze:before{content:"\f83f"}.fa-weebly:before{content:"\f5cc"}.fa-weibo:before{content:"\f18a"}.fa-weight:before{content:"\f496"}.fa-weight-hanging:before{content:"\f5cd"}.fa-weixin:before{content:"\f1d7"}.fa-whatsapp:before{content:"\f232"}.fa-whatsapp-square:before{content:"\f40c"}.fa-wheelchair:before{content:"\f193"}.fa-whmcs:before{content:"\f40d"}.fa-wifi:before{content:"\f1eb"}.fa-wikipedia-w:before{content:"\f266"}.fa-wind:before{content:"\f72e"}.fa-window-close:before{content:"\f410"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-windows:before{content:"\f17a"}.fa-wine-bottle:before{content:"\f72f"}.fa-wine-glass:before{content:"\f4e3"}.fa-wine-glass-alt:before{content:"\f5ce"}.fa-wix:before{content:"\f5cf"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-won-sign:before{content:"\f159"}.fa-wordpress:before{content:"\f19a"}.fa-wordpress-simple:before{content:"\f411"}.fa-wpbeginner:before{content:"\f297"}.fa-wpexplorer:before{content:"\f2de"}.fa-wpforms:before{content:"\f298"}.fa-wpressr:before{content:"\f3e4"}.fa-wrench:before{content:"\f0ad"}.fa-x-ray:before{content:"\f497"}.fa-xbox:before{content:"\f412"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-y-combinator:before{content:"\f23b"}.fa-yahoo:before{content:"\f19e"}.fa-yammer:before{content:"\f840"}.fa-yandex:before{content:"\f413"}.fa-yandex-international:before{content:"\f414"}.fa-yarn:before{content:"\f7e3"}.fa-yelp:before{content:"\f1e9"}.fa-yen-sign:before{content:"\f157"}.fa-yin-yang:before{content:"\f6ad"}.fa-yoast:before{content:"\f2b1"}.fa-youtube:before{content:"\f167"}.fa-youtube-square:before{content:"\f431"}.fa-zhihu:before{content:"\f63f"}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands"}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-regular-400.eot);src:url(../webfonts/fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-regular-400.woff2) format("woff2"),url(../webfonts/fa-regular-400.woff) format("woff"),url(../webfonts/fa-regular-400.ttf) format("truetype"),url(../webfonts/fa-regular-400.svg#fontawesome) format("svg")}.fab,.far{font-weight:400}@font-face{font-family:"Font Awesome 5 Free";font-style:normal;font-weight:900;font-display:block;src:url(../webfonts/fa-solid-900.eot);src:url(../webfonts/fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-solid-900.woff2) format("woff2"),url(../webfonts/fa-solid-900.woff) format("woff"),url(../webfonts/fa-solid-900.ttf) format("truetype"),url(../webfonts/fa-solid-900.svg#fontawesome) format("svg")}.fa,.far,.fas{font-family:"Font Awesome 5 Free"}.fa,.fas{font-weight:900} \ No newline at end of file diff --git a/lib/font-awesome/webfonts/fa-brands-400.woff2 b/lib/font-awesome/webfonts/fa-brands-400.woff2 new file mode 100644 index 0000000..141a90a Binary files /dev/null and b/lib/font-awesome/webfonts/fa-brands-400.woff2 differ diff --git a/lib/font-awesome/webfonts/fa-regular-400.woff2 b/lib/font-awesome/webfonts/fa-regular-400.woff2 new file mode 100644 index 0000000..7e0118e Binary files /dev/null and b/lib/font-awesome/webfonts/fa-regular-400.woff2 differ diff --git a/lib/font-awesome/webfonts/fa-solid-900.woff2 b/lib/font-awesome/webfonts/fa-solid-900.woff2 new file mode 100644 index 0000000..978a681 Binary files /dev/null and b/lib/font-awesome/webfonts/fa-solid-900.woff2 differ diff --git a/lib/velocity/velocity.min.js b/lib/velocity/velocity.min.js new file mode 100644 index 0000000..58244c8 --- /dev/null +++ b/lib/velocity/velocity.min.js @@ -0,0 +1,4 @@ +/*! VelocityJS.org (1.2.2). (C) 2014 Julian Shapiro. MIT @license: en.wikipedia.org/wiki/MIT_License */ +/*! VelocityJS.org jQuery Shim (1.0.1). (C) 2014 The jQuery Foundation. MIT @license: en.wikipedia.org/wiki/MIT_License. */ +!function(e){function t(e){var t=e.length,r=$.type(e);return"function"===r||$.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===r||0===t||"number"==typeof t&&t>0&&t-1 in e}if(!e.jQuery){var $=function(e,t){return new $.fn.init(e,t)};$.isWindow=function(e){return null!=e&&e==e.window},$.type=function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?a[o.call(e)]||"object":typeof e},$.isArray=Array.isArray||function(e){return"array"===$.type(e)},$.isPlainObject=function(e){var t;if(!e||"object"!==$.type(e)||e.nodeType||$.isWindow(e))return!1;try{if(e.constructor&&!n.call(e,"constructor")&&!n.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}for(t in e);return void 0===t||n.call(e,t)},$.each=function(e,r,a){var n,o=0,i=e.length,s=t(e);if(a){if(s)for(;i>o&&(n=r.apply(e[o],a),n!==!1);o++);else for(o in e)if(n=r.apply(e[o],a),n===!1)break}else if(s)for(;i>o&&(n=r.call(e[o],o,e[o]),n!==!1);o++);else for(o in e)if(n=r.call(e[o],o,e[o]),n===!1)break;return e},$.data=function(e,t,a){if(void 0===a){var n=e[$.expando],o=n&&r[n];if(void 0===t)return o;if(o&&t in o)return o[t]}else if(void 0!==t){var n=e[$.expando]||(e[$.expando]=++$.uuid);return r[n]=r[n]||{},r[n][t]=a,a}},$.removeData=function(e,t){var a=e[$.expando],n=a&&r[a];n&&$.each(t,function(e,t){delete n[t]})},$.extend=function(){var e,t,r,a,n,o,i=arguments[0]||{},s=1,l=arguments.length,u=!1;for("boolean"==typeof i&&(u=i,i=arguments[s]||{},s++),"object"!=typeof i&&"function"!==$.type(i)&&(i={}),s===l&&(i=this,s--);l>s;s++)if(null!=(n=arguments[s]))for(a in n)e=i[a],r=n[a],i!==r&&(u&&r&&($.isPlainObject(r)||(t=$.isArray(r)))?(t?(t=!1,o=e&&$.isArray(e)?e:[]):o=e&&$.isPlainObject(e)?e:{},i[a]=$.extend(u,o,r)):void 0!==r&&(i[a]=r));return i},$.queue=function(e,r,a){function n(e,r){var a=r||[];return null!=e&&(t(Object(e))?!function(e,t){for(var r=+t.length,a=0,n=e.length;r>a;)e[n++]=t[a++];if(r!==r)for(;void 0!==t[a];)e[n++]=t[a++];return e.length=n,e}(a,"string"==typeof e?[e]:e):[].push.call(a,e)),a}if(e){r=(r||"fx")+"queue";var o=$.data(e,r);return a?(!o||$.isArray(a)?o=$.data(e,r,n(a)):o.push(a),o):o||[]}},$.dequeue=function(e,t){$.each(e.nodeType?[e]:e,function(e,r){t=t||"fx";var a=$.queue(r,t),n=a.shift();"inprogress"===n&&(n=a.shift()),n&&("fx"===t&&a.unshift("inprogress"),n.call(r,function(){$.dequeue(r,t)}))})},$.fn=$.prototype={init:function(e){if(e.nodeType)return this[0]=e,this;throw new Error("Not a DOM node.")},offset:function(){var t=this[0].getBoundingClientRect?this[0].getBoundingClientRect():{top:0,left:0};return{top:t.top+(e.pageYOffset||document.scrollTop||0)-(document.clientTop||0),left:t.left+(e.pageXOffset||document.scrollLeft||0)-(document.clientLeft||0)}},position:function(){function e(){for(var e=this.offsetParent||document;e&&"html"===!e.nodeType.toLowerCase&&"static"===e.style.position;)e=e.offsetParent;return e||document}var t=this[0],e=e.apply(t),r=this.offset(),a=/^(?:body|html)$/i.test(e.nodeName)?{top:0,left:0}:$(e).offset();return r.top-=parseFloat(t.style.marginTop)||0,r.left-=parseFloat(t.style.marginLeft)||0,e.style&&(a.top+=parseFloat(e.style.borderTopWidth)||0,a.left+=parseFloat(e.style.borderLeftWidth)||0),{top:r.top-a.top,left:r.left-a.left}}};var r={};$.expando="velocity"+(new Date).getTime(),$.uuid=0;for(var a={},n=a.hasOwnProperty,o=a.toString,i="Boolean Number String Function Array Date RegExp Object Error".split(" "),s=0;sn;++n){var o=u(r,e,a);if(0===o)return r;var i=l(r,e,a)-t;r-=i/o}return r}function p(){for(var t=0;b>t;++t)w[t]=l(t*x,e,a)}function f(t,r,n){var o,i,s=0;do i=r+(n-r)/2,o=l(i,e,a)-t,o>0?n=i:r=i;while(Math.abs(o)>h&&++s=y?c(t,s):0==l?s:f(t,r,r+x)}function g(){V=!0,(e!=r||a!=n)&&p()}var m=4,y=.001,h=1e-7,v=10,b=11,x=1/(b-1),S="Float32Array"in t;if(4!==arguments.length)return!1;for(var P=0;4>P;++P)if("number"!=typeof arguments[P]||isNaN(arguments[P])||!isFinite(arguments[P]))return!1;e=Math.min(e,1),a=Math.min(a,1),e=Math.max(e,0),a=Math.max(a,0);var w=S?new Float32Array(b):new Array(b),V=!1,C=function(t){return V||g(),e===r&&a===n?t:0===t?0:1===t?1:l(d(t),r,n)};C.getControlPoints=function(){return[{x:e,y:r},{x:a,y:n}]};var T="generateBezier("+[e,r,a,n]+")";return C.toString=function(){return T},C}function u(e,t){var r=e;return g.isString(e)?v.Easings[e]||(r=!1):r=g.isArray(e)&&1===e.length?s.apply(null,e):g.isArray(e)&&2===e.length?b.apply(null,e.concat([t])):g.isArray(e)&&4===e.length?l.apply(null,e):!1,r===!1&&(r=v.Easings[v.defaults.easing]?v.defaults.easing:h),r}function c(e){if(e){var t=(new Date).getTime(),r=v.State.calls.length;r>1e4&&(v.State.calls=n(v.State.calls));for(var o=0;r>o;o++)if(v.State.calls[o]){var s=v.State.calls[o],l=s[0],u=s[2],f=s[3],d=!!f,m=null;f||(f=v.State.calls[o][3]=t-16);for(var y=Math.min((t-f)/u.duration,1),h=0,b=l.length;b>h;h++){var S=l[h],w=S.element;if(i(w)){var V=!1;if(u.display!==a&&null!==u.display&&"none"!==u.display){if("flex"===u.display){var C=["-webkit-box","-moz-box","-ms-flexbox","-webkit-flex"];$.each(C,function(e,t){x.setPropertyValue(w,"display",t)})}x.setPropertyValue(w,"display",u.display)}u.visibility!==a&&"hidden"!==u.visibility&&x.setPropertyValue(w,"visibility",u.visibility);for(var T in S)if("element"!==T){var k=S[T],A,F=g.isString(k.easing)?v.Easings[k.easing]:k.easing;if(1===y)A=k.endValue;else{var E=k.endValue-k.startValue;if(A=k.startValue+E*F(y,u,E),!d&&A===k.currentValue)continue}if(k.currentValue=A,"tween"===T)m=A;else{if(x.Hooks.registered[T]){var j=x.Hooks.getRoot(T),H=i(w).rootPropertyValueCache[j];H&&(k.rootPropertyValue=H)}var N=x.setPropertyValue(w,T,k.currentValue+(0===parseFloat(A)?"":k.unitType),k.rootPropertyValue,k.scrollData);x.Hooks.registered[T]&&(i(w).rootPropertyValueCache[j]=x.Normalizations.registered[j]?x.Normalizations.registered[j]("extract",null,N[1]):N[1]),"transform"===N[0]&&(V=!0)}}u.mobileHA&&i(w).transformCache.translate3d===a&&(i(w).transformCache.translate3d="(0px, 0px, 0px)",V=!0),V&&x.flushTransformCache(w)}}u.display!==a&&"none"!==u.display&&(v.State.calls[o][2].display=!1),u.visibility!==a&&"hidden"!==u.visibility&&(v.State.calls[o][2].visibility=!1),u.progress&&u.progress.call(s[1],s[1],y,Math.max(0,f+u.duration-t),f,m),1===y&&p(o)}}v.State.isTicking&&P(c)}function p(e,t){if(!v.State.calls[e])return!1;for(var r=v.State.calls[e][0],n=v.State.calls[e][1],o=v.State.calls[e][2],s=v.State.calls[e][4],l=!1,u=0,c=r.length;c>u;u++){var p=r[u].element;if(t||o.loop||("none"===o.display&&x.setPropertyValue(p,"display",o.display),"hidden"===o.visibility&&x.setPropertyValue(p,"visibility",o.visibility)),o.loop!==!0&&($.queue(p)[1]===a||!/\.velocityQueueEntryFlag/i.test($.queue(p)[1]))&&i(p)){i(p).isAnimating=!1,i(p).rootPropertyValueCache={};var f=!1;$.each(x.Lists.transforms3D,function(e,t){var r=/^scale/.test(t)?1:0,n=i(p).transformCache[t];i(p).transformCache[t]!==a&&new RegExp("^\\("+r+"[^.]").test(n)&&(f=!0,delete i(p).transformCache[t])}),o.mobileHA&&(f=!0,delete i(p).transformCache.translate3d),f&&x.flushTransformCache(p),x.Values.removeClass(p,"velocity-animating")}if(!t&&o.complete&&!o.loop&&u===c-1)try{o.complete.call(n,n)}catch(d){setTimeout(function(){throw d},1)}s&&o.loop!==!0&&s(n),i(p)&&o.loop===!0&&!t&&($.each(i(p).tweensContainer,function(e,t){/^rotate/.test(e)&&360===parseFloat(t.endValue)&&(t.endValue=0,t.startValue=360),/^backgroundPosition/.test(e)&&100===parseFloat(t.endValue)&&"%"===t.unitType&&(t.endValue=0,t.startValue=100)}),v(p,"reverse",{loop:!0,delay:o.delay})),o.queue!==!1&&$.dequeue(p,o.queue)}v.State.calls[e]=!1;for(var g=0,m=v.State.calls.length;m>g;g++)if(v.State.calls[g]!==!1){l=!0;break}l===!1&&(v.State.isTicking=!1,delete v.State.calls,v.State.calls=[])}var f=function(){if(r.documentMode)return r.documentMode;for(var e=7;e>4;e--){var t=r.createElement("div");if(t.innerHTML="",t.getElementsByTagName("span").length)return t=null,e}return a}(),d=function(){var e=0;return t.webkitRequestAnimationFrame||t.mozRequestAnimationFrame||function(t){var r=(new Date).getTime(),a;return a=Math.max(0,16-(r-e)),e=r+a,setTimeout(function(){t(r+a)},a)}}(),g={isString:function(e){return"string"==typeof e},isArray:Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)},isFunction:function(e){return"[object Function]"===Object.prototype.toString.call(e)},isNode:function(e){return e&&e.nodeType},isNodeList:function(e){return"object"==typeof e&&/^\[object (HTMLCollection|NodeList|Object)\]$/.test(Object.prototype.toString.call(e))&&e.length!==a&&(0===e.length||"object"==typeof e[0]&&e[0].nodeType>0)},isWrapped:function(e){return e&&(e.jquery||t.Zepto&&t.Zepto.zepto.isZ(e))},isSVG:function(e){return t.SVGElement&&e instanceof t.SVGElement},isEmptyObject:function(e){for(var t in e)return!1;return!0}},$,m=!1;if(e.fn&&e.fn.jquery?($=e,m=!0):$=t.Velocity.Utilities,8>=f&&!m)throw new Error("Velocity: IE8 and below require jQuery to be loaded before Velocity.");if(7>=f)return void(jQuery.fn.velocity=jQuery.fn.animate);var y=400,h="swing",v={State:{isMobile:/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),isAndroid:/Android/i.test(navigator.userAgent),isGingerbread:/Android 2\.3\.[3-7]/i.test(navigator.userAgent),isChrome:t.chrome,isFirefox:/Firefox/i.test(navigator.userAgent),prefixElement:r.createElement("div"),prefixMatches:{},scrollAnchor:null,scrollPropertyLeft:null,scrollPropertyTop:null,isTicking:!1,calls:[]},CSS:{},Utilities:$,Redirects:{},Easings:{},Promise:t.Promise,defaults:{queue:"",duration:y,easing:h,begin:a,complete:a,progress:a,display:a,visibility:a,loop:!1,delay:!1,mobileHA:!0,_cacheValues:!0},init:function(e){$.data(e,"velocity",{isSVG:g.isSVG(e),isAnimating:!1,computedStyle:null,tweensContainer:null,rootPropertyValueCache:{},transformCache:{}})},hook:null,mock:!1,version:{major:1,minor:2,patch:2},debug:!1};t.pageYOffset!==a?(v.State.scrollAnchor=t,v.State.scrollPropertyLeft="pageXOffset",v.State.scrollPropertyTop="pageYOffset"):(v.State.scrollAnchor=r.documentElement||r.body.parentNode||r.body,v.State.scrollPropertyLeft="scrollLeft",v.State.scrollPropertyTop="scrollTop");var b=function(){function e(e){return-e.tension*e.x-e.friction*e.v}function t(t,r,a){var n={x:t.x+a.dx*r,v:t.v+a.dv*r,tension:t.tension,friction:t.friction};return{dx:n.v,dv:e(n)}}function r(r,a){var n={dx:r.v,dv:e(r)},o=t(r,.5*a,n),i=t(r,.5*a,o),s=t(r,a,i),l=1/6*(n.dx+2*(o.dx+i.dx)+s.dx),u=1/6*(n.dv+2*(o.dv+i.dv)+s.dv);return r.x=r.x+l*a,r.v=r.v+u*a,r}return function a(e,t,n){var o={x:-1,v:0,tension:null,friction:null},i=[0],s=0,l=1e-4,u=.016,c,p,f;for(e=parseFloat(e)||500,t=parseFloat(t)||20,n=n||null,o.tension=e,o.friction=t,c=null!==n,c?(s=a(e,t),p=s/n*u):p=u;;)if(f=r(f||o,p),i.push(1+f.x),s+=16,!(Math.abs(f.x)>l&&Math.abs(f.v)>l))break;return c?function(e){return i[e*(i.length-1)|0]}:s}}();v.Easings={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},spring:function(e){return 1-Math.cos(4.5*e*Math.PI)*Math.exp(6*-e)}},$.each([["ease",[.25,.1,.25,1]],["ease-in",[.42,0,1,1]],["ease-out",[0,0,.58,1]],["ease-in-out",[.42,0,.58,1]],["easeInSine",[.47,0,.745,.715]],["easeOutSine",[.39,.575,.565,1]],["easeInOutSine",[.445,.05,.55,.95]],["easeInQuad",[.55,.085,.68,.53]],["easeOutQuad",[.25,.46,.45,.94]],["easeInOutQuad",[.455,.03,.515,.955]],["easeInCubic",[.55,.055,.675,.19]],["easeOutCubic",[.215,.61,.355,1]],["easeInOutCubic",[.645,.045,.355,1]],["easeInQuart",[.895,.03,.685,.22]],["easeOutQuart",[.165,.84,.44,1]],["easeInOutQuart",[.77,0,.175,1]],["easeInQuint",[.755,.05,.855,.06]],["easeOutQuint",[.23,1,.32,1]],["easeInOutQuint",[.86,0,.07,1]],["easeInExpo",[.95,.05,.795,.035]],["easeOutExpo",[.19,1,.22,1]],["easeInOutExpo",[1,0,0,1]],["easeInCirc",[.6,.04,.98,.335]],["easeOutCirc",[.075,.82,.165,1]],["easeInOutCirc",[.785,.135,.15,.86]]],function(e,t){v.Easings[t[0]]=l.apply(null,t[1])});var x=v.CSS={RegEx:{isHex:/^#([A-f\d]{3}){1,2}$/i,valueUnwrap:/^[A-z]+\((.*)\)$/i,wrappedValueAlreadyExtracted:/[0-9.]+ [0-9.]+ [0-9.]+( [0-9.]+)?/,valueSplit:/([A-z]+\(.+\))|(([A-z0-9#-.]+?)(?=\s|$))/gi},Lists:{colors:["fill","stroke","stopColor","color","backgroundColor","borderColor","borderTopColor","borderRightColor","borderBottomColor","borderLeftColor","outlineColor"],transformsBase:["translateX","translateY","scale","scaleX","scaleY","skewX","skewY","rotateZ"],transforms3D:["transformPerspective","translateZ","scaleZ","rotateX","rotateY"]},Hooks:{templates:{textShadow:["Color X Y Blur","black 0px 0px 0px"],boxShadow:["Color X Y Blur Spread","black 0px 0px 0px 0px"],clip:["Top Right Bottom Left","0px 0px 0px 0px"],backgroundPosition:["X Y","0% 0%"],transformOrigin:["X Y Z","50% 50% 0px"],perspectiveOrigin:["X Y","50% 50%"]},registered:{},register:function(){for(var e=0;e=f)switch(e){case"name":return"filter";case"extract":var a=r.toString().match(/alpha\(opacity=(.*)\)/i);return r=a?a[1]/100:1;case"inject":return t.style.zoom=1,parseFloat(r)>=1?"":"alpha(opacity="+parseInt(100*parseFloat(r),10)+")"}else switch(e){case"name":return"opacity";case"extract":return r;case"inject":return r}}},register:function(){9>=f||v.State.isGingerbread||(x.Lists.transformsBase=x.Lists.transformsBase.concat(x.Lists.transforms3D));for(var e=0;en&&(n=1),o=!/(\d)$/i.test(n);break;case"skew":o=!/(deg|\d)$/i.test(n);break;case"rotate":o=!/(deg|\d)$/i.test(n)}return o||(i(r).transformCache[t]="("+n+")"),i(r).transformCache[t]}}}();for(var e=0;e=f||3!==o.split(" ").length||(o+=" 1"),o;case"inject":return 8>=f?4===n.split(" ").length&&(n=n.split(/\s+/).slice(0,3).join(" ")):3===n.split(" ").length&&(n+=" 1"),(8>=f?"rgb":"rgba")+"("+n.replace(/\s+/g,",").replace(/\.(\d)+(?=,)/g,"")+")"}}}()}},Names:{camelCase:function(e){return e.replace(/-(\w)/g,function(e,t){return t.toUpperCase()})},SVGAttribute:function(e){var t="width|height|x|y|cx|cy|r|rx|ry|x1|x2|y1|y2";return(f||v.State.isAndroid&&!v.State.isChrome)&&(t+="|transform"),new RegExp("^("+t+")$","i").test(e)},prefixCheck:function(e){if(v.State.prefixMatches[e])return[v.State.prefixMatches[e],!0];for(var t=["","Webkit","Moz","ms","O"],r=0,a=t.length;a>r;r++){var n;if(n=0===r?e:t[r]+e.replace(/^\w/,function(e){return e.toUpperCase()}),g.isString(v.State.prefixElement.style[n]))return v.State.prefixMatches[e]=n,[n,!0]}return[e,!1]}},Values:{hexToRgb:function(e){var t=/^#?([a-f\d])([a-f\d])([a-f\d])$/i,r=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i,a;return e=e.replace(t,function(e,t,r,a){return t+t+r+r+a+a}),a=r.exec(e),a?[parseInt(a[1],16),parseInt(a[2],16),parseInt(a[3],16)]:[0,0,0]},isCSSNullValue:function(e){return 0==e||/^(none|auto|transparent|(rgba\(0, ?0, ?0, ?0\)))$/i.test(e)},getUnitType:function(e){return/^(rotate|skew)/i.test(e)?"deg":/(^(scale|scaleX|scaleY|scaleZ|alpha|flexGrow|flexHeight|zIndex|fontWeight)$)|((opacity|red|green|blue|alpha)$)/i.test(e)?"":"px"},getDisplayType:function(e){var t=e&&e.tagName.toString().toLowerCase();return/^(b|big|i|small|tt|abbr|acronym|cite|code|dfn|em|kbd|strong|samp|var|a|bdo|br|img|map|object|q|script|span|sub|sup|button|input|label|select|textarea)$/i.test(t)?"inline":/^(li)$/i.test(t)?"list-item":/^(tr)$/i.test(t)?"table-row":/^(table)$/i.test(t)?"table":/^(tbody)$/i.test(t)?"table-row-group":"block"},addClass:function(e,t){e.classList?e.classList.add(t):e.className+=(e.className.length?" ":"")+t},removeClass:function(e,t){e.classList?e.classList.remove(t):e.className=e.className.toString().replace(new RegExp("(^|\\s)"+t.split(" ").join("|")+"(\\s|$)","gi")," ")}},getPropertyValue:function(e,r,n,o){function s(e,r){function n(){u&&x.setPropertyValue(e,"display","none")}var l=0;if(8>=f)l=$.css(e,r);else{var u=!1;if(/^(width|height)$/.test(r)&&0===x.getPropertyValue(e,"display")&&(u=!0,x.setPropertyValue(e,"display",x.Values.getDisplayType(e))),!o){if("height"===r&&"border-box"!==x.getPropertyValue(e,"boxSizing").toString().toLowerCase()){var c=e.offsetHeight-(parseFloat(x.getPropertyValue(e,"borderTopWidth"))||0)-(parseFloat(x.getPropertyValue(e,"borderBottomWidth"))||0)-(parseFloat(x.getPropertyValue(e,"paddingTop"))||0)-(parseFloat(x.getPropertyValue(e,"paddingBottom"))||0);return n(),c}if("width"===r&&"border-box"!==x.getPropertyValue(e,"boxSizing").toString().toLowerCase()){var p=e.offsetWidth-(parseFloat(x.getPropertyValue(e,"borderLeftWidth"))||0)-(parseFloat(x.getPropertyValue(e,"borderRightWidth"))||0)-(parseFloat(x.getPropertyValue(e,"paddingLeft"))||0)-(parseFloat(x.getPropertyValue(e,"paddingRight"))||0);return n(),p}}var d;d=i(e)===a?t.getComputedStyle(e,null):i(e).computedStyle?i(e).computedStyle:i(e).computedStyle=t.getComputedStyle(e,null),"borderColor"===r&&(r="borderTopColor"),l=9===f&&"filter"===r?d.getPropertyValue(r):d[r],(""===l||null===l)&&(l=e.style[r]),n()}if("auto"===l&&/^(top|right|bottom|left)$/i.test(r)){var g=s(e,"position");("fixed"===g||"absolute"===g&&/top|left/i.test(r))&&(l=$(e).position()[r]+"px")}return l}var l;if(x.Hooks.registered[r]){var u=r,c=x.Hooks.getRoot(u);n===a&&(n=x.getPropertyValue(e,x.Names.prefixCheck(c)[0])),x.Normalizations.registered[c]&&(n=x.Normalizations.registered[c]("extract",e,n)),l=x.Hooks.extractValue(u,n)}else if(x.Normalizations.registered[r]){var p,d;p=x.Normalizations.registered[r]("name",e),"transform"!==p&&(d=s(e,x.Names.prefixCheck(p)[0]),x.Values.isCSSNullValue(d)&&x.Hooks.templates[r]&&(d=x.Hooks.templates[r][1])),l=x.Normalizations.registered[r]("extract",e,d)}if(!/^[\d-]/.test(l))if(i(e)&&i(e).isSVG&&x.Names.SVGAttribute(r))if(/^(height|width)$/i.test(r))try{l=e.getBBox()[r]}catch(g){l=0}else l=e.getAttribute(r);else l=s(e,x.Names.prefixCheck(r)[0]);return x.Values.isCSSNullValue(l)&&(l=0),v.debug>=2&&console.log("Get "+r+": "+l),l},setPropertyValue:function(e,r,a,n,o){var s=r;if("scroll"===r)o.container?o.container["scroll"+o.direction]=a:"Left"===o.direction?t.scrollTo(a,o.alternateValue):t.scrollTo(o.alternateValue,a);else if(x.Normalizations.registered[r]&&"transform"===x.Normalizations.registered[r]("name",e))x.Normalizations.registered[r]("inject",e,a),s="transform",a=i(e).transformCache[r];else{if(x.Hooks.registered[r]){var l=r,u=x.Hooks.getRoot(r);n=n||x.getPropertyValue(e,u),a=x.Hooks.injectValue(l,a,n),r=u}if(x.Normalizations.registered[r]&&(a=x.Normalizations.registered[r]("inject",e,a),r=x.Normalizations.registered[r]("name",e)),s=x.Names.prefixCheck(r)[0],8>=f)try{e.style[s]=a}catch(c){v.debug&&console.log("Browser does not support ["+a+"] for ["+s+"]")}else i(e)&&i(e).isSVG&&x.Names.SVGAttribute(r)?e.setAttribute(r,a):e.style[s]=a;v.debug>=2&&console.log("Set "+r+" ("+s+"): "+a)}return[s,a]},flushTransformCache:function(e){function t(t){return parseFloat(x.getPropertyValue(e,t))}var r="";if((f||v.State.isAndroid&&!v.State.isChrome)&&i(e).isSVG){var a={translate:[t("translateX"),t("translateY")],skewX:[t("skewX")],skewY:[t("skewY")],scale:1!==t("scale")?[t("scale"),t("scale")]:[t("scaleX"),t("scaleY")],rotate:[t("rotateZ"),0,0]};$.each(i(e).transformCache,function(e){/^translate/i.test(e)?e="translate":/^scale/i.test(e)?e="scale":/^rotate/i.test(e)&&(e="rotate"),a[e]&&(r+=e+"("+a[e].join(" ")+") ",delete a[e])})}else{var n,o;$.each(i(e).transformCache,function(t){return n=i(e).transformCache[t],"transformPerspective"===t?(o=n,!0):(9===f&&"rotateZ"===t&&(t="rotate"),void(r+=t+n+" "))}),o&&(r="perspective"+o+" "+r)}x.setPropertyValue(e,"transform",r)}};x.Hooks.register(),x.Normalizations.register(),v.hook=function(e,t,r){var n=a;return e=o(e),$.each(e,function(e,o){if(i(o)===a&&v.init(o),r===a)n===a&&(n=v.CSS.getPropertyValue(o,t));else{var s=v.CSS.setPropertyValue(o,t,r);"transform"===s[0]&&v.CSS.flushTransformCache(o),n=s}}),n};var S=function(){function e(){return l?T.promise||null:f}function n(){function e(e){function p(e,t){var r=a,i=a,s=a;return g.isArray(e)?(r=e[0],!g.isArray(e[1])&&/^[\d-]/.test(e[1])||g.isFunction(e[1])||x.RegEx.isHex.test(e[1])?s=e[1]:(g.isString(e[1])&&!x.RegEx.isHex.test(e[1])||g.isArray(e[1]))&&(i=t?e[1]:u(e[1],o.duration),e[2]!==a&&(s=e[2]))):r=e,t||(i=i||o.easing),g.isFunction(r)&&(r=r.call(n,w,P)),g.isFunction(s)&&(s=s.call(n,w,P)),[r||0,i,s]}function f(e,t){var r,a;return a=(t||"0").toString().toLowerCase().replace(/[%A-z]+$/,function(e){return r=e,""}),r||(r=x.Values.getUnitType(e)),[a,r]}function d(){var e={myParent:n.parentNode||r.body,position:x.getPropertyValue(n,"position"),fontSize:x.getPropertyValue(n,"fontSize")},a=e.position===N.lastPosition&&e.myParent===N.lastParent,o=e.fontSize===N.lastFontSize;N.lastParent=e.myParent,N.lastPosition=e.position,N.lastFontSize=e.fontSize;var s=100,l={};if(o&&a)l.emToPx=N.lastEmToPx,l.percentToPxWidth=N.lastPercentToPxWidth,l.percentToPxHeight=N.lastPercentToPxHeight;else{var u=i(n).isSVG?r.createElementNS("http://www.w3.org/2000/svg","rect"):r.createElement("div");v.init(u),e.myParent.appendChild(u),$.each(["overflow","overflowX","overflowY"],function(e,t){v.CSS.setPropertyValue(u,t,"hidden")}),v.CSS.setPropertyValue(u,"position",e.position),v.CSS.setPropertyValue(u,"fontSize",e.fontSize),v.CSS.setPropertyValue(u,"boxSizing","content-box"),$.each(["minWidth","maxWidth","width","minHeight","maxHeight","height"],function(e,t){v.CSS.setPropertyValue(u,t,s+"%")}),v.CSS.setPropertyValue(u,"paddingLeft",s+"em"),l.percentToPxWidth=N.lastPercentToPxWidth=(parseFloat(x.getPropertyValue(u,"width",null,!0))||1)/s,l.percentToPxHeight=N.lastPercentToPxHeight=(parseFloat(x.getPropertyValue(u,"height",null,!0))||1)/s,l.emToPx=N.lastEmToPx=(parseFloat(x.getPropertyValue(u,"paddingLeft"))||1)/s,e.myParent.removeChild(u)}return null===N.remToPx&&(N.remToPx=parseFloat(x.getPropertyValue(r.body,"fontSize"))||16),null===N.vwToPx&&(N.vwToPx=parseFloat(t.innerWidth)/100,N.vhToPx=parseFloat(t.innerHeight)/100),l.remToPx=N.remToPx,l.vwToPx=N.vwToPx,l.vhToPx=N.vhToPx,v.debug>=1&&console.log("Unit ratios: "+JSON.stringify(l),n),l}if(o.begin&&0===w)try{o.begin.call(m,m)}catch(y){setTimeout(function(){throw y},1)}if("scroll"===k){var S=/^x$/i.test(o.axis)?"Left":"Top",V=parseFloat(o.offset)||0,C,A,F;o.container?g.isWrapped(o.container)||g.isNode(o.container)?(o.container=o.container[0]||o.container,C=o.container["scroll"+S],F=C+$(n).position()[S.toLowerCase()]+V):o.container=null:(C=v.State.scrollAnchor[v.State["scrollProperty"+S]],A=v.State.scrollAnchor[v.State["scrollProperty"+("Left"===S?"Top":"Left")]],F=$(n).offset()[S.toLowerCase()]+V),s={scroll:{rootPropertyValue:!1,startValue:C,currentValue:C,endValue:F,unitType:"",easing:o.easing,scrollData:{container:o.container,direction:S,alternateValue:A}},element:n},v.debug&&console.log("tweensContainer (scroll): ",s.scroll,n)}else if("reverse"===k){if(!i(n).tweensContainer)return void $.dequeue(n,o.queue);"none"===i(n).opts.display&&(i(n).opts.display="auto"),"hidden"===i(n).opts.visibility&&(i(n).opts.visibility="visible"),i(n).opts.loop=!1,i(n).opts.begin=null,i(n).opts.complete=null,b.easing||delete o.easing,b.duration||delete o.duration,o=$.extend({},i(n).opts,o);var E=$.extend(!0,{},i(n).tweensContainer);for(var j in E)if("element"!==j){var H=E[j].startValue;E[j].startValue=E[j].currentValue=E[j].endValue,E[j].endValue=H,g.isEmptyObject(b)||(E[j].easing=o.easing),v.debug&&console.log("reverse tweensContainer ("+j+"): "+JSON.stringify(E[j]),n)}s=E}else if("start"===k){var E;i(n).tweensContainer&&i(n).isAnimating===!0&&(E=i(n).tweensContainer),$.each(h,function(e,t){if(RegExp("^"+x.Lists.colors.join("$|^")+"$").test(e)){var r=p(t,!0),n=r[0],o=r[1],i=r[2];if(x.RegEx.isHex.test(n)){for(var s=["Red","Green","Blue"],l=x.Values.hexToRgb(n),u=i?x.Values.hexToRgb(i):a,c=0;cO;O++){var z={delay:F.delay,progress:F.progress};O===R-1&&(z.display=F.display,z.visibility=F.visibility,z.complete=F.complete),S(m,"reverse",z)}return e()}};v=$.extend(S,v),v.animate=S;var P=t.requestAnimationFrame||d;return v.State.isMobile||r.hidden===a||r.addEventListener("visibilitychange",function(){r.hidden?(P=function(e){return setTimeout(function(){e(!0)},16)},c()):P=t.requestAnimationFrame||d}),e.Velocity=v,e!==t&&(e.fn.velocity=S,e.fn.velocity.defaults=v.defaults),$.each(["Down","Up"],function(e,t){v.Redirects["slide"+t]=function(e,r,n,o,i,s){var l=$.extend({},r),u=l.begin,c=l.complete,p={height:"",marginTop:"",marginBottom:"",paddingTop:"",paddingBottom:""},f={};l.display===a&&(l.display="Down"===t?"inline"===v.CSS.Values.getDisplayType(e)?"inline-block":"block":"none"),l.begin=function(){u&&u.call(i,i);for(var r in p){f[r]=e.style[r];var a=v.CSS.getPropertyValue(e,r);p[r]="Down"===t?[a,0]:[0,a]}f.overflow=e.style.overflow,e.style.overflow="hidden"},l.complete=function(){for(var t in f)e.style[t]=f[t];c&&c.call(i,i),s&&s.resolver(i)},v(e,p,l)}}),$.each(["In","Out"],function(e,t){v.Redirects["fade"+t]=function(e,r,n,o,i,s){var l=$.extend({},r),u={opacity:"In"===t?1:0},c=l.complete;l.complete=n!==o-1?l.begin=null:function(){c&&c.call(i,i),s&&s.resolver(i)},l.display===a&&(l.display="In"===t?"auto":"none"),v(this,u,l)}}),v}(window.jQuery||window.Zepto||window,window,document)}); \ No newline at end of file diff --git a/lib/velocity/velocity.ui.min.js b/lib/velocity/velocity.ui.min.js new file mode 100644 index 0000000..8706945 --- /dev/null +++ b/lib/velocity/velocity.ui.min.js @@ -0,0 +1,2 @@ +/* VelocityJS.org UI Pack (5.0.4). (C) 2014 Julian Shapiro. MIT @license: en.wikipedia.org/wiki/MIT_License. Portions copyright Daniel Eden, Christian Pucci. */ +!function(t){"function"==typeof require&&"object"==typeof exports?module.exports=t():"function"==typeof define&&define.amd?define(["velocity"],t):t()}(function(){return function(t,a,e,r){function n(t,a){var e=[];return t&&a?($.each([t,a],function(t,a){var r=[];$.each(a,function(t,a){for(;a.toString().length<5;)a="0"+a;r.push(a)}),e.push(r.join(""))}),parseFloat(e[0])>parseFloat(e[1])):!1}if(!t.Velocity||!t.Velocity.Utilities)return void(a.console&&console.log("Velocity UI Pack: Velocity must be loaded first. Aborting."));var i=t.Velocity,$=i.Utilities,s=i.version,o={major:1,minor:1,patch:0};if(n(o,s)){var l="Velocity UI Pack: You need to update Velocity (jquery.velocity.js) to a newer version. Visit http://github.com/julianshapiro/velocity.";throw alert(l),new Error(l)}i.RegisterEffect=i.RegisterUI=function(t,a){function e(t,a,e,r){var n=0,s;$.each(t.nodeType?[t]:t,function(t,a){r&&(e+=t*r),s=a.parentNode,$.each(["height","paddingTop","paddingBottom","marginTop","marginBottom"],function(t,e){n+=parseFloat(i.CSS.getPropertyValue(a,e))})}),i.animate(s,{height:("In"===a?"+":"-")+"="+n},{queue:!1,easing:"ease-in-out",duration:e*("In"===a?.6:1)})}return i.Redirects[t]=function(n,s,o,l,c,u){function f(){s.display!==r&&"none"!==s.display||!/Out$/.test(t)||$.each(c.nodeType?[c]:c,function(t,a){i.CSS.setPropertyValue(a,"display","none")}),s.complete&&s.complete.call(c,c),u&&u.resolver(c||n)}var p=o===l-1;a.defaultDuration="function"==typeof a.defaultDuration?a.defaultDuration.call(c,c):parseFloat(a.defaultDuration);for(var d=0;d1&&($.each(a.reverse(),function(t,e){var r=a[t+1];if(r){var n=e.o||e.options,s=r.o||r.options,o=n&&n.sequenceQueue===!1?"begin":"complete",l=s&&s[o],c={};c[o]=function(){var t=r.e||r.elements,a=t.nodeType?[t]:t;l&&l.call(a,a),i(e)},r.o?r.o=$.extend({},s,c):r.options=$.extend({},s,c)}}),a.reverse()),i(a[0])}}(window.jQuery||window.Zepto||window,window,document)}); \ No newline at end of file diff --git a/page/2/index.html b/page/2/index.html new file mode 100644 index 0000000..676587d --- /dev/null +++ b/page/2/index.html @@ -0,0 +1,1516 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    想写这样一篇总结也是因为在互联网上看了太多乱七八糟的说法,往往让读者越看越糊涂。这些说法往往给我这样的感觉:

    +
      +
    1. 没有先讲清楚在形容什么:同步、异步、阻塞、非阻塞通常作为形容词使用,无论是用在日常语境,还是用在编程领域内部,如果你不知道这些形容词是放在什么名词之前就开始讲什么意思,甚至一段话中掺杂多个名词去形容而不说明,那就会给人不知所云的感觉。
    2. +
    3. 概念与实现混为一谈:同步阻塞、异步非阻塞是非常常见的组合方式,所以在讲同步、异步的概念时,它们忍不住拿这些具体实现来讲概念,也就顶多能让读者理解这些固定搭配,难以活学活用(其实能不能做到前者都存疑,甚至作者自己真的懂吗?)。
    4. +
    5. 不恰当的比喻:比喻的使用是为了帮助表达,本体和喻体之间当然是不同的事物(否则就不是比喻了),但是讲了个生动的比喻,却没有把本体与喻体之间的对应关系讲清楚,或者没有讲清楚这个比喻的边界,给话题引入了不必要的复杂性,起到了相反的作用。
    6. +
    +

    所以,我打算好好聊一下我对这个话题的理解,尽可能不要犯我认为别人犯过的这些错误,欢迎读者监督。

    +

    同步与异步

    同步这个词在日常语境下与计算机是有着一些不同的。日常语境下,同步经常用在同时进行的事情上,比如“多个平台同步直播卖货”,“推动新时代四化同步发展”,当然它们之间是经过协调一致的,不是无组织无纪律各行其是的。

    +

    而在编程领域,“同步”和“异步”通常用来是形容“任务”、“函数”等可执行对象的(以下统一称作“任务”);当然也有用来形容“API”的,此时它们表示的是一种具体实现的风格,即“同步风格的API”、“异步风格的API”,它跟前者有关系也有区别。

    +

    那它们究竟是什么意思呢?

    +

    无论是同步还是异步,都一定是用来形容多个任务的,我们可以说这两个任务或者这三个函数是同步或者异步执行的,但是一个任务不存在同步或者异步。同步指的是多个任务一个一个执行,不存在时间重叠的情况,异步指的是多个任务不一个一个执行,可能存在时间重叠的情况。

    +
    +

    有的说法硬要在讲清楚这个概念之前,先把这个任务假设成一个异步函数,再把它分成调用和获取结果两个步骤,开始谈什么消息,打比方讲什么先打了个电话订餐,又接了个电话通知,然后告诉你这叫异步,这是很容易造成误解的。即便要拆成俩,单个任务的调用和获取结果也一定是一前一后,不可能先获取结果再调用,也不可能同时进行,这两个步骤是同步的,不以具体实现为转移,它们只是可能跟另一个任务是异步的,拆成俩徒增复杂度,没有任何必要。所以干脆不要拆,也不要聊具体实现,我们说一个任务,它就是一个任务,就是执行一段时间拿到结果,不要拆开。比如“吃外卖”,就等于“下单-收到电话-取餐-吃”这整个过程,反正经过了一段时间,不要在意内部细节

    +
    +

    那么,同步和异步应该怎么使用呢,比如;

    +
      +
    1. 我先吃了个外卖,然后打了一会游戏,吃外卖和打游戏这两个任务就是同步的。
    2. +
    3. 我先吃了个外卖,然后打了一会游戏,又写了会代码,这三个任务也是同步的。
    4. +
    5. 在 2. 的基础上,我在吃外卖的过程中还听了一段时间的音乐,那么听音乐和吃外卖是异步的(不要管我什么时候开的音乐,什么时候关的音乐)。
    6. +
    7. 在 2. 的基础上,打游戏期间都是保证把音乐关掉了的,但是吃外卖和写代码期间都听了一会音乐,那么听音乐和玩游戏是同步的,和吃外卖、写代码是异步的。
    8. +
    +

    是不是很简单,只要按照那个定义去套就行了。但是有一点要注意,4. 里面的 “打游戏期间都是保证把音乐关掉了” 的 “保证” 二字是很关键的,它确保了听音乐和玩游戏之间的 “一个一个执行” 的条件,这需要我在玩游戏之前确保音乐是关掉的,又保证玩游戏期间没有突然去把音乐打开,如果你只是凭运气,只是偶然造成了 “玩游戏期间音乐是关掉的” 的结果,那么它仍然是异步的,因为异步的定义里说的是“可能存在时间重叠”,你没有确保完全排除这个可能,它们就是异步的。

    +

    我们再回到日常语境下的“同步”,会发现它跟编程的“同步”是相通的。比如“多个平台同步直播卖货”,虽然整体上各平台是同时在直播一个流媒体源,看上去更符合异步的定义,但其实把“直播”拆分成“录制”、“拉流”、“转场”、“互动”、“上链接”等任务时,会发现它们之间还是存在一些比较严格的先后顺序的,比如你不可能在媒体源录制之前拉流,也不可能在某个重要互动之前就享受某些优惠。也就是说,日常语境下的同步,更多的是描述两件相关的大任务,它们的子任务只要存在一定程度的“保证”就可以说“同步”,并没有编程领域这么严格。

    +

    阻塞与非阻塞

    这确实是一对很难脱离具体实现讲的概念。

    +

    不过相比同步与异步,阻塞与非阻塞更简单,是用来形容单个可执行对象(以下仍统一称作“任务”)的执行状态的。

    +

    可能被形容的有线程、协程、某一段业务逻辑等等:

    +
      +
    1. 我在某线程用 BIO 接口读一个文件,造成该线程无法继续运行,这就是线程阻塞了
    2. +
    3. 我在某协程内使用该语言/框架提供的 sleep 方法,造成协程无法继续运行,这就是协程阻塞了(所在线程并没有阻塞)
    4. +
    5. 我写了一段代码,反正调用 IO 或者 sleep 了,我也不知道运行在哪个线程上,哪个协程上,或者只是一个异步的 setTimeout, 反正就是在等,这就是业务逻辑阻塞了(可能线程和协程都没有被阻塞)
    6. +
    +

    但其实这样说有点扩大概念外延的意思,99.9% 的情况下,我们说阻塞指的都是线程。而非阻塞,通常指的也是某个任务不会造成线程阻塞。

    +

    为什么线程阻塞是最常讨论的话题呢?因为:

    +
      +
    1. 线程的调度和空间占用,都是比较消耗系统资源的。一个 I/O 密集型业务(比如最常见的 Web 服务),如果会因为 I/O 阻塞线程,那么为了支持多个并发同时进行,就需要为每个独立的并发单独开一个线程。在并发较高时,就需要开大量的线程,占用大量 CPU 和内存资源,所以我们有了 C10K 问题。
    2. +
    3. 这是一个已经被用各种方式花式解决了的问题了。Linux 上的 epoll、Windows 上的 IOCP、MacOS 上的 kqueue(先不要管它们是异步/同步、阻塞/非阻塞,总之 —>),都提供了可以让调用者不必开一堆线程而只需要复用固定数量的线程即可支撑较高并发的接口。不管是 Nodejs,还是 Vert.X 的异步回调,Go 的有栈协程,Rust 的无栈协程,甭管它们封装成异步风格还是同步风格的API,是甜还是咸,都是建立在前者基础上的。简单说,目的只有一个:不阻塞调用者的线程,或者说线程复用
    4. +
    +

    业务要你等3秒,网络数据包要等几百毫秒才能到,硬盘读写要有时间,这些都会造成业务逻辑的阻塞,如果使用 BIO 接口,则业务逻辑的阻塞也会导致线程的阻塞。而使用了上述的框架/语言,让你不用在这些时候阻塞自己的线程,被阻塞的就只有当前业务逻辑,线程可以去跑别的业务逻辑,实现了线程复用。

    +

    组合起来

    现在,单讲同步、异步、阻塞、非阻塞,应该没什么问题了,我们再聊聊那些常见的搭配。

    +

    我们经常听说同步阻塞、异步非阻塞这种搭配方式,而如果按照前文的定义,这种搭配方式是很奇怪的——同步和异步是用来形容多个任务的,阻塞和非阻塞时是说单个任务的,这样组合在一起是在干嘛?

    +

    一般来讲,它们是用来形容某种 API 的风格和实现的。

    +

    同步和异步是表示这种 API 的接口风格。比如常见的 Linux 的 BIO 接口、Go 的 IO 接口、Rust 里的 async 函数,这些都是同步风格的接口,先后调用这样的两个接口,它们默认是同步执行的,你要异步执行,需要另外开线程或者协程;Node.js、Vert.X 的绝大部分接口,都是异步风格的接口,先后调用这样的两个接口,它们默认是异步执行的,你要同步执行,需要把第二个接口写到前一个接口的回调函数里。

    +

    阻塞和非阻塞是表示这种 API 需要调用者配合使用的线程模型。比如 Linux 的 BIO 接口会阻塞调用者的线程,它就是阻塞的,而 Go 的 IO 接口、Rust 的 async 函数、Node.js 和 Vert.X 里的异步接口,都不会阻塞调用者的线程,它们就是非阻塞的。

    +

    在没有协程的年代,同步风格的 BIO 接口就是会导致线程阻塞,它意味着心智负担小,开发方便,但是吃资源;而不会阻塞线程的 NIO/AIO 接口往往是异步风格(或者封装为异步风格),代码写起来心智负担重,性能好,比如 Node.js 和 Vert.X。所以经常有人拿同步代指阻塞,拿异步代指非阻塞,它们成为了同义词两两绑定在一起,即“同步阻塞”和“异步非阻塞”。

    +

    而 Go 、Rust 等语言提供的协程,相当于是对“异步非阻塞”的一种高级封装,可以像写同步代码那样写不阻塞线程的代码,让你即拥有高性能,又没有很高的心智负担。但是也很少见他们讨论自己是同步还是异步,阻塞还是非阻塞,因为风格上确实是同步的,封装的实际上是异步常用的非阻塞接口,确实不会阻塞线程,但是协程是可能被阻塞。所以不要套概念,理解原理,理解同步具体指的是谁和谁,异步具体指的是谁和谁,阻塞和非阻塞指的具体是谁,搞清楚对象,套定义就可以了。

    +

    总结

    在写这篇文章的时候,我并没有查询很多资料,但自认为这算是一个可以简化问题,帮助理解的模型。

    +

    希望能给读者一些启发,也欢迎批评指正。

    + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    使用数组结构表示堆结构,元素从索引 0 开始,则索引 i 位置:

    +
      +
    • 父节点索引为 $(i - 1) / 2$
    • +
    • 左孩子索引为 $2 \times i + 1$
    • +
    • 右孩子索引为 $2 \times i + 2$
    • +
    +
    +

    实现

      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    public class Heap {

    public static void main(String[] args) {
    Heap heap = new Heap(1000);
    heap.push(-6);
    heap.push(3);
    heap.push(-1);
    heap.push(0);
    heap.push(9);
    heap.push(-4);
    heap.push(5);
    heap.push(7);
    heap.push(-2);
    heap.push(9);
    heap.push(13);

    System.out.println(heap);

    while (!heap.isEmpty()) {
    System.out.println(heap.pop());
    System.out.println(heap);
    }
    }

    private final int[] data;
    private final int limit;
    private int heapSize;

    public Heap(int limit) {
    this.limit = limit;
    this.data = new int[limit];
    this.heapSize = 0;
    }

    public void push(int value) {
    if (heapSize >= limit) {
    throw new RuntimeException("heapSize must not exceed limit");
    }

    this.data[heapSize++] = value;
    heapInsert(heapSize - 1);
    }

    public boolean isEmpty() {
    return heapSize == 0;
    }

    public Integer pop() {
    if (isEmpty()) {
    return null;
    }

    swap(--heapSize, 0);
    int result = this.data[heapSize];
    this.data[heapSize] = 0;
    heapify(0);
    return result;
    }

    private void heapInsert(int index) {
    while (index > 0 && this.data[index] > this.data[(index - 1) / 2]) {
    swap(index, (index - 1) / 2);
    index = (index - 1) / 2;
    }
    }

    private void swap(int i, int j) {
    if (i != j) {
    this.data[i] = this.data[i] ^ this.data[j];
    this.data[j] = this.data[i] ^ this.data[j];
    this.data[i] = this.data[i] ^ this.data[j];
    }
    }

    private void heapify(int index) {
    int leftIndex = 2 * index + 1;

    while (leftIndex < heapSize) {
    int rightIndex = leftIndex + 1;
    int greatestIndex = rightIndex < heapSize && this.data[rightIndex] > this.data[leftIndex] ? rightIndex : leftIndex;

    if (this.data[greatestIndex] <= this.data[index]) {
    break;
    }

    swap(index, greatestIndex);
    index = greatestIndex;
    leftIndex = 2 * index + 1;
    }
    }

    @Override
    public String toString() {
    StringBuilder b = new StringBuilder();
    b.append('[');
    for (int i = 0; i<heapSize; i++) {
    b.append(data[i]);
    if (i != heapSize - 1)
    b.append(", ");
    }
    b.append(']');

    return b.toString();
    }
    }
    +

    输出内容:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    [13, 9, 5, 3, 9, -4, -1, -6, -2, 0, 7]
    13
    [9, 9, 5, 3, 7, -4, -1, -6, -2, 0]
    9
    [9, 7, 5, 3, 0, -4, -1, -6, -2]
    9
    [7, 3, 5, -2, 0, -4, -1, -6]
    7
    [5, 3, -1, -2, 0, -4, -6]
    5
    [3, 0, -1, -2, -6, -4]
    3
    [0, -2, -1, -4, -6]
    0
    [-1, -2, -6, -4]
    -1
    [-2, -4, -6]
    -2
    [-4, -6]
    -4
    [-6]
    -6
    []
    +
      +
    • Rust 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    pub struct Heap<T>(Vec<T>);

    impl<T: Ord> Heap<T> {
    pub fn new() -> Self {
    Self(Vec::new())
    }

    pub fn push(&mut self, value: T) {
    self.0.push(value);
    self.heap_insert();
    }

    pub fn pop(&mut self) -> Option<T> {
    if self.0.is_empty() {
    return None;
    }

    let last = self.0.len() - 1;
    self.0.swap(0, last);
    let result = self.0.pop();
    self.heapify();
    result
    }

    fn heapify(&mut self) {
    let len = self.0.len();

    let mut index = 0;
    let mut left = index * 2 + 1;

    while left < len {
    let right = left + 1;
    let greatest = if right < len && self.0[left] < self.0[right] {
    right
    } else {
    left
    };

    if self.0[greatest] <= self.0[index] {
    break;
    }

    self.0.swap(index, greatest);
    index = greatest;
    left = index * 2 + 1;
    }
    }

    fn heap_insert(&mut self) {
    if self.0.is_empty() {
    return;
    }

    let mut index = self.0.len() - 1;
    if index == 0 {
    return;
    }
    let mut parent = (index - 1) / 2;

    while self.0[parent] < self.0[index] {
    self.0.swap(index, parent);
    index = parent;
    if index == 0 {
    break;
    }
    parent = (index - 1) / 2;
    }
    }
    }

    impl<T: Ord> Iterator for Heap<T> {
    type Item = T;

    fn next(&mut self) -> Option<Self::Item> {
    self.pop()
    }
    }

    #[test]
    fn test_heap() {
    for _ in 0..1000 {
    let array: [u8; 32] = rand::random();
    let sorted = {
    let mut s = array.clone();
    s.sort_by(|a, b| b.cmp(a));
    s
    };

    let mut heap = Heap::new();

    for a in array {
    heap.push(a);
    }

    let heap_sorted: Vec<u8> = heap.collect();

    assert_eq!(heap_sorted, sorted);
    }
    }
    + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    1. 降低 String 内存开销


    +

    2. 统计


    +

    3. GEO


    +

    4. 时间序列数据

    + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    1. 基本架构

    一个键值数据库需要有哪些功能/结构/特性。

    +
      +
    • 支持多种数据结构
    • +
    • PUT、GET、DELETE、SCAN
    • +
    • 设置过期
    • +
    • 索引结构
    • +
    • 对外接口,Socket、动态链接库等等
    • +
    • 存储方式,内存、硬盘(持久化)
    • +
    • 高可用
    • +
    • 横向扩展
    • +
    • 功能可扩展性
    • +
    +
    +

    2. 数据结构

    Redis 接收到一个键值对操作后,能以 微秒 级别的速度找到数据,并快速完成操作。

    +

    Redis 的值有如下数据类型:

    +
      +
    • String(字符串)
    • +
    • List(列表)
    • +
    • Hash(哈希)
    • +
    • Set(集合)
    • +
    • Sorted Set(有序集合)
    • +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    数据类型数据结构
    String简单动态字符串
    List双向链表/压缩列表
    Hash压缩列表/哈希表
    Set整数数组/哈希表
    Sorted Set压缩列表/跳表
    +
    +

    Redis 使用一个全局的哈希表来保存所有键值对,可以在 $O(1)$ 的时间内查找到键值对。

    +

    一个哈希表就是一个数组,数组元素为哈希桶,每个哈希桶存放一个链表,连接着哈希值映射到该桶中的多个键值对。

    +
    1
    2
    3
    4
    5
    |0|1|2|3|4|5|
    | |
    | ---> entry4
    |
    ---> entry1 ---> entry2 ---> entry3
    +

    哈希冲突及rehash

    当哈希冲突过多时,链表过长,导致查找效率下降,Redis会对哈希表进行rehash。

    +

    为了 使 rehash 更高效,Redis 使用了两个全局哈希表:哈希表 1 和 哈希表 2 。默认情况下使用哈希表 1 ,哈希表 2 没有分配空间,当键值对增多时,Redis开始进行rehash:

    +
      +
    1. 给哈希表 2 分配更大的空间,例如是当前哈希表 1 大小的两倍;
    2. +
    3. 把哈希表 1 中的数据重新映射并拷贝到哈希表 2 中;
    4. +
    5. 释放哈希表 1 的空间。
    6. +
    +

    然后使用哈希表 2 保存数据,下次rehash再使用哈希表 1 。

    +

    避免一次性拷贝大量数据造成 Redis 线程阻塞,Redis采用了渐进式rehash。

    +

    渐进式rehash

    Redis可以保持处理用户请求,每处理一个请求,就顺带将哈希表 1 中的一个桶的所有entries拷贝到哈希表 2 中。这样就能把一次性大量拷贝的开销,分摊到了多次请求中,避免了耗时可能造成的阻塞。

    +

    数据结构的操作效率

    整数数组双向链表 都是顺序读写,操作复杂度都是 $O(N)$。

    +

    压缩列表

    压缩列表表头有 列表长度(zlbytes)列表尾偏移量(zltail)列表中entry个数(zllen) 三个字段,表尾有一个 zlend 字段表示 列表结束

    +
    1
    | zlbytes | zltail | zllen | entry1 | entry2 | ... | entryN | zlend |
    +

    查找表头、表尾元素的复杂度为 $O(1)$,查找其他元素的复杂度为 $O(N)$。

    +

    跳表

    跳表 是在 有序链表 的基础上增加了多级索引,通过索引位置的几次跳转,实现数据快速定位。

    +
    1
    2
    3
    4
    5
    1 ----------------------> 27 -----------------------> 100 // 二级索引
    ↓ ↓ ↓
    1 --------> 11 ---------> 27 ---------> 50 ---------> 100 // 一级索引
    ↓ ↓ ↓ ↓ ↓
    1 --> 5 --> 11 --> 20 --> 27 --> 33 --> 50 --> 62 --> 100 --> 156 // 链表
    +

    跳表 的查找复杂度为 $O(logN)$。

    +

    时间复杂度

    数据结构的时间复杂度

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    数据结构时间复杂度
    哈希表$O(1)$
    跳表$O(logN)$
    双向链表$O(N)$
    压缩列表$O(N)$
    整数数组$O(N)$
    +
    +

    不同操作的时间复杂度

      +
    1. 单元数操作是基础。每一种集合类型对单个数据实现的增删改查操作,复杂度由数据结构决定。
    2. +
    3. 范围操作很耗时。返回一个范围内的数据,复杂度一般是 $O(N)$,应尽量避免,可以使用 2.8 版本之后的 HSCANSSCANZSCAN 等命令进行渐进式遍历。
    4. +
    5. 统计操作通常高效。集合类型对集合中的元素个数由记录,例如 LLENSCARD
    6. +
    7. 例外情况只有几个。压缩列表和双向列表的会记录表头和表尾的偏移量,对它们的操作复杂度只有 $O(1)$,例如 LPOPRPOPLPUSHRPUSH
    8. +
    +

    问题

    整数数组和压缩列表在查找时间复杂度方面并没有很大优势,为什么Redis还会把它作为底层数据结构?

    +

    主要有两方面原因:

    +
      +
    1. 内存利用率。Redis 是内存数据库,大量数据存储在内存中,整数数组和压缩列表结构比较紧凑,相比链表结构占用内存更少,可以提高内存利用率。
    2. +
    3. 数组对CPU高速缓存支持更友好。集合元素较少时,使用内存紧凑的排列方式,可以利用CPU高速缓存,尤其在一个缓存行内(64字节)有更高效的访问效率。当元素数量超过阈值后,为避免复杂度太高,可以转为哈希表和跳表。
    4. +
    +

    3. 高性能 IO 模型

    Redis 的网络 IO 和键值对读写是由单个线程完成的;其他功能,例如持久化、异步删除、集群数据同步,是由额外的线程执行的。

    +

    Redis使用 IO多路复用,即一个线程处理多个 IO 流。

    +

    可能存在的瓶颈(来自Kaito):

    +
      +
    1. 单个请求耗时会导致后续所有请求阻塞等待,包括以下几种:
        +
      • 操作bigkey,分配、释放内存(4.0推出了lazy-free,可以异步执行bigkey释放)
      • +
      • 使用复杂度较高的命令,排序、union、查询全量数据
      • +
      • 大量key集中过期
      • +
      • 淘汰策略,内存超过Redis内存上线,每次写入都要淘汰一些key,导致耗时变长
      • +
      • AOF 开启 always
      • +
      • 主从同步生成 RDB 时的 fork
      • +
      +
    2. +
    3. 并发量大时,单线程处理客户端请求,无法利用多核(6.0推出了多线程,可利用多核CPU处理用户请求)
    4. +
    +
    +

    4. AOF(Append Only File)

    Redis 的 AOF 是写后日志,先执行命令,把数据写入内存,然后再记录日志。

    +

    不会阻塞当前写操作,执行完命令还没来得及记日志就宕机的话,会有数据丢失的风险。

    +
    1
    2
    3
    4
    5
    6
    7
    *3 --> 这个命令有三个部分
    $3 --> 第一部分的长度
    set --> 第一部分的内容
    $5
    hello
    $5
    world
    +

    三种写回策略

      +
    • Always,同步写回,每个命令执行完立马写回磁盘
    • +
    • EverySec,每秒写回,写到AOF内存缓存,每秒写回一次
    • +
    • No,写到AOF内存缓存,由操作系统决定何时将其写回
    • +
    +

    日志重写

    当日志文件过大时,可以对日志进行重写。可以把某些多条修改同一个键值对的命令合并为一条。

    +

    重写流程:

    +
      +
    1. 主线程 fork 出 bgrewriteaof 子进程,主线程在将新指令写入 AOF 缓存时,还会写入 AOF 重写缓存;
    2. +
    3. 子进程将 fork 拷贝出来的内存快照写成命令日志;
    4. +
    5. 写完后将AOF重写缓存中的日志写到这个新的日志中,然后就可以用这个新日志替换旧日志了。
    6. +
    +

    其他要点

      +
    1. fork 使用的是操作系统的写时复制(Copy On Write)机制,并不会直接拷贝所有内存。但是会拷贝父进程的的内存页表,如果页表较大,可能导致阻塞。
    2. +
    3. 主进程在提供服务时,如果操作的是bigkey,写时复制会导致内存分配;如果开启了内存大页(huge page)机制,内存分配会产生阻塞。(所以使用Redis通常要关闭内存大页,其对使用 fork 的程序不友好)
    4. +
    5. AOF 重写可以使用 bgrewriteaof 命令手动执行,也由两个配置项控制自动触发:
        +
      • auto-aof-rewrite-min-size:表示运行AOF重写的最小大小,默认为 64MB;
      • +
      • auto-aof-rewrite-percentage:当前AOF文件大小和上一次重写后AOF文件大小的差值,除以上一次重写AOF文件大小,即增量和上一次全量的比值,默认为 100。
        AOF 文件大小同时满足这两个配置项,会自动触发 AOF 重写。
      • +
      +
    6. +
    +
    +

    5. 内存快照 RDB

    把某一时刻的内存数据以文件的形式写到磁盘上,即快照。这个快照文件即 RDB 文件(Redis DataBase 的缩写)。

    +

    在数据恢复时,可直接把 RDB 文件读入内存,很快完成恢复。

    +

    Redis 执行的是全量快照。

    +

    可以使用 savebgsave 来生成 RDB 文件;save在主线程中执行,会阻塞;bgsave 会创建子进程专门进行 RDB 文件写入,不会阻塞。

    +

    快照进行时数据如何修改

    使用 fork 创建子进程,该子进程会跟主线程共享所有内存数据;利用操作系统的写时复制技术,当主进程修改数据时,会重新分配空间生成该数据的副本,并使用该副本提供服务,bgsave 子进程仍然使用未修改的数据进行快照写入。

    +

    快照频率

    频繁执行全量快照有两方面开销:

    +
      +
    1. 给磁盘带来压力
    2. +
    3. fork 本身可能阻塞主线程,如果频繁 fork 会导致主线程阻塞所以,如果有一个 bgsave 在运行,就不会启动第二个了
    4. +
    +

    4.0 中提出了混合使用AOF和RDB的方法,即快照以一定的频率执行,在快照间以AOF的形式记录这期间的命令。实现:

    +
      +
    1. 避免了频繁 fork 对主线程的影响
    2. +
    3. 减少了 AOF 文件大小,提高了数据恢复效率
    4. +
    +

    在配置文件中设置:

    +
    1
    aof-use-rdb-preamble yes
    +

    三点建议

      +
    1. 数据不能丢失时,使用 AOF 和 RDB 混合方案
    2. +
    3. 允许分钟级别的数据丢失,使用 RDB
    4. +
    5. 只用 AOF 的话,优先使用 everysec,在可靠性和性能之间比较平衡
    6. +
    +

    课后题:资源风险(Kaito)

    +
      +
    1. 考虑 fork 导致多大程度的写时复制,是否会造成 OOM,或者 swap 到磁盘上导致性能降低;
    2. +
    3. CPU 核数较少时,Redis的其他子进程会跟主线程竞争CPU,导致性能下降;
    4. +
    5. 如果 Redis 进程绑定了 CPU,子进程会继承它的 CPU 亲和属性,与父进程争夺同一个 CPU 资源,导致效率下降。所以如果开启RDB和AOF,一定不要绑定CPU。
    6. +
    +
    +

    6. 主从同步

    可靠性分两方面:

    +
      +
    1. 数据少丢失(由RDB、AOF保证);
    2. +
    3. 服务少中断(通过增加副本冗余量保证)
    4. +
    +

    读写分离:主库、从库都可以执行读操作,主库限制性写操作,然后同步给从库。

    +

    如果都允许写,会造成各实例副本不一致,需要涉及加锁、协商等各种操作,产生巨额开销。

    +

    主从第一次同步

    在从库实例上执行:

    +
    1
    replicaof [主库ip] [主库port]
    +

    三个阶段

      +
    1. 主从建立链接、协商同步,从库给主库发送 psync 命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync 命令包含了主库的 runID 和复制进度 offset 两个参数。主库收到 psync 命令后,用 FULLRESYNC 响应命令带上两个参数,主库的 runID 和主库目前的复制进度 offset 给从库,从库会记住这两个参数。
    2. +
    +
    +

    runID:每个 Redis 实例启动时自动生成的一个随机 ID,唯一标识一个实例。第一次复制时从库不知道主库的 runID,使用 ?
    offset:第一次复制设置为 -1

    +
    +
      +
    1. 主库执行 bgsave 生成 RDB 文件,并发送给从库。从库收到 RDB 文件后,清空数据库并加载 RDB 文件。此时主库不会停止服务,但在内存中会有专门的 replication buffer,记录 RDB 文件生成后收到的所有写操作。
    2. +
    3. 主库把 replication buffer 中的修改操作发送给从库执行。
    4. +
    +

    主从级联

    使用“主-从-从”模式分担主库压力。二级从库可以选择一个内存较高的从库进行同步。

    +

    主从间使用基于长连接的命令传播。

    +

    网络断开

    2.8 前网络闪断会造成重新全量复制,2.8 后可以增量复制,实现继续同步。

    +

    (Kaito)主库除了将所有写命令传给从库之外,也会在 repl_backlog_buffer 中记录一份。当从库断线重连后,会发送 psync $master_runid $offset,主库就能通过 $offsetrepl_backlog_buffer 中找到从库断开的位置,只发送 $offset 之后的增量给从库。

    +
    +

    repl_backlog_buffer:为了解决从库断开后找到主从差异而设计的环形缓冲区。配置大一点,可以减少全量同步的频率。

    +

    replication buffer:客户端、从库与 Redis 通信时,Redis 都会分配一个内存 buffer 用于交互,把数据写进去并通过 socket 发送到对端。当对端为从库时,该 buffer 只负责传播写命令,通常叫做 replication buffer。这个 buffer 由 client-output-buffer-limit 参数控制,如果过小或者从库处理过慢,Redis 会强制断开这个链接。

    +
    +

    为啥不用 AOF 同步

      +
    1. 相比 RDB 文件更大,效率低;
    2. +
    3. 必须打开 AOF,数据丢失不敏感业务没必要开启
    4. +
    +
    +

    7. 哨兵


    +

    8. 哨兵集群


    +

    9. 切片集群


    + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    TimSort


    +

    Pattern-defeating Quicksort(pdqsort)


    + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    装饰器(Decorator)

    +

    Component:定义一个对象接口,可以给这些对象动态地添加职责。
    ConcreteComponent:定义对象,可以给这个对象添加一些职责。
    Decorator:维持一个指向 Component 对象的指针,并定义一个与 Component 接口一致的接口。
    ConcreteDecorator:实际的装饰对象,向组件添加职责。

    +
    +
      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    public class DecoratorPattern {

    public static void main(String[] args) {
    Component component0 = new ADecorator(new BDecorator(new Component0()));
    component0.doSth();
    System.out.println();
    Component component1 = new BDecorator(new ADecorator(new BDecorator(new ADecorator(new Component1()))));
    component1.doSth();
    System.out.println();
    }

    public interface Component {
    void doSth();
    }

    public static class Component0 implements Component {

    @Override
    public void doSth() {
    System.out.print("0");
    }
    }

    public static class Component1 implements Component {

    @Override
    public void doSth() {
    System.out.print("1");
    }
    }

    public static abstract class Decorator implements Component {
    protected Component component;
    public Decorator(Component component) {
    this.component = component;
    }
    }

    public static class ADecorator extends Decorator {

    public ADecorator(Component component) {
    super(component);
    }

    @Override
    public void doSth() {
    component.doSth();
    System.out.print("A");
    }
    }

    public static class BDecorator extends Decorator {

    public BDecorator(Component component) {
    super(component);
    }

    @Override
    public void doSth() {
    component.doSth();
    System.out.print("B");
    }
    }
    }
    +
      +
    • Rust 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    pub trait Component {
    fn do_sth(&self);
    }

    pub struct Component1;

    impl Component for Component1 {
    fn do_sth(&self) {
    print!("1");
    }
    }

    pub struct Component2;

    impl Component for Component2 {
    fn do_sth(&self) {
    print!("2");
    }
    }

    pub trait Decorator<T: Component>: Component {
    fn wrap(inner: T) -> Self;
    }

    struct DecoratorA<T> {
    inner: T,
    }

    impl<T: Component> Component for DecoratorA<T> {
    fn do_sth(&self) {
    self.inner.do_sth();
    print!("A");
    }
    }

    impl<T: Component> Decorator<T> for DecoratorA<T> {
    fn wrap(inner: T) -> Self {
    Self { inner }
    }
    }

    struct DecoratorB<T> {
    inner: T,
    }

    impl<T: Component> Component for DecoratorB<T> {
    fn do_sth(&self) {
    self.inner.do_sth();
    print!("B");
    }
    }

    impl<T: Component> Decorator<T> for DecoratorB<T> {
    fn wrap(inner: T) -> Self {
    Self { inner }
    }
    }

    #[test]
    fn test_decorator() {
    let c1 = Component1;
    c1.do_sth();
    println!();
    let ab1 = DecoratorA::wrap(DecoratorB::wrap(Component1));
    ab1.do_sth();
    println!();
    let abbaa2 = DecoratorA::wrap(DecoratorB::wrap(DecoratorB::wrap(DecoratorA::wrap(
    DecoratorA::wrap(Component2),
    ))));
    abbaa2.do_sth();
    println!();

    // 在Rust中,如果不需要运行时动态装饰,就没有必要产生很多小对象
    // 装饰了好几轮,最后占用内存还是 Component2 的大小
    assert_eq!(
    std::mem::size_of::<
    DecoratorA<DecoratorB<DecoratorB<DecoratorA<DecoratorA<Component2>>>>>,
    >(),
    0
    );
    }
    +

    输出内容:

    +
    1
    2
    3
    1
    1BA
    2AABBA
    +
    +

    优点:比继承更灵活,避免类爆炸;可以组合;装饰器和构件可以独立变化,符合开闭原则。
    缺点:产生很多小对象,增加系统复杂度和理解难度,调试困难。

    +
    +
    + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    分区问题

    二分

    将一个数组分为两个区域,小于等于N在左,大于N的在右;返回右侧的起始位置。

    +
      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    static int partition(int[] array, int value, int start, int end) {
    if (array == null || start >= end) {
    return -1;
    }

    int left = -1;

    for (int i = start; i < end; i++) {
    if (array[i] <= value) {
    if (left == -1) {
    left = start;
    } else {
    left += 1;
    }
    swap(array, i, left);
    }
    }

    return left + 1;
    }

    static int partition(int[] array, int value) {
    return partition(array, value, 0, array.length);
    }
    +

    输出内容:

    +
    1
    2
    3
    [2, 3, 5, 1, 2, 6, 4, 3, 8, 4, 3, 5, 1, 3, 5, 8]
    10
    [2, 3, 1, 2, 4, 3, 4, 3, 1, 3, 6, 5, 8, 5, 5, 8]
    +
      +
    • Rust 实现
    • +
    +

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    #[test]
    fn test_partition() {
    let mut array = [4, 2, 5, 7, 5, 4, 2, 9, 3, 5, 1];
    assert_eq!(array.partition(5), Index::Position(9));
    assert_eq!(array.partition(3), Index::Position(4));
    assert_eq!(array.partition(11), Index::Greater);
    assert_eq!(array.partition(0), Index::Less);
    }

    #[derive(Debug, Eq, PartialEq)]
    pub enum Index {
    // 值比所有数组元素都小
    Less,
    // 能找到一个分区位置
    Position(usize),
    // 值比所有数组元素都大
    Greater,
    }

    pub trait Partition<T> {
    fn partition(&mut self, value: T) -> Index;
    }

    impl<T: Ord> Partition<T> for [T] {
    // 返回大于部分的起始索引
    fn partition(&mut self, value: T) -> Index {
    let mut left = None;
    for i in 0..self.len() {
    if self[i] <= value {
    if let Some(left_val) = left {
    let new_left_val = left_val + 1;
    self.swap(i, new_left_val);
    left = Some(new_left_val);
    } else {
    self.swap(i, 0);
    left = Some(0);
    }
    }
    }

    match left {
    None => Index::Less,
    Some(i) if i == self.len() - 1 => Index::Greater,
    Some(i) => Index::Position(i + 1),
    }
    }
    }

    荷兰 🇳🇱 国旗问题(三分)

    将一个数组分为三个区域,小于N在左,等于N在中间,大于N的在右;返回中间和右侧的起始位置。

    +
      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    static void swap(int[] array, int i, int j) {
    if (i == j) {
    return;
    }

    array[i] = array[i] ^ array[j];
    array[j] = array[i] ^ array[j];
    array[i] = array[i] ^ array[j];
    }

    static int[] netherlandsFlagPartition(int[] array, int value) {
    return netherlandsFlagPartition(array, value, 0, array.length);
    }

    static int[] netherlandsFlagPartition(int[] array, int value, int start, int end) {
    if (array == null || start >= end) {
    return null;
    }

    int left = -1;
    int right = end;
    int i = start;

    while (i < right) {
    if (array[i] < value) {
    left = left == -1 ? start : (left + 1);
    swap(array, left, i);
    i += 1;
    } else if (array[i] > value) {
    right -= 1;
    swap(array, right, i);
    } else { // array[i] == value
    i += 1;
    }
    }

    return new int[]{left + 1, right};
    }
    +

    输出内容:

    +
    1
    2
    [10, 13]
    [2, 3, 1, 2, 4, 3, 4, 3, 1, 3, 5, 5, 5, 8, 6, 8]
    +
      +
    • Rust 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    use std::cmp::Ordering;

    #[test]
    fn test_netherlands_flag_partition() {
    let mut array = [7, 1, 2, 0, 8, 5, 3, 9, 2, 6, 5, 1, 0, 8, 7, 4];
    // sorted: [0, 0, 1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 7, 8, 8, 9]
    // index(hex): [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F]
    assert_eq!(array.netherlands_flag_partition(2), (Some(4), Some(6)));
    assert_eq!(array.netherlands_flag_partition(7), (Some(0xB), Some(0xD)));
    assert_eq!(array.netherlands_flag_partition(-1), (None, Some(0)));
    assert_eq!(array.netherlands_flag_partition(0), (None, Some(2)));
    assert_eq!(
    array.netherlands_flag_partition(10),
    (Some(array.len()), None)
    );
    assert_eq!(
    array.netherlands_flag_partition(9),
    (Some(array.len() - 1), None)
    );
    }

    pub trait NetherlandsFlagPartition<T> {
    fn netherlands_flag_partition(&mut self, value: T) -> (Option<usize>, Option<usize>);
    }

    impl<T: Ord> NetherlandsFlagPartition<T> for [T] {
    // 返回相等部分和大于部分的起始索引
    fn netherlands_flag_partition(&mut self, value: T) -> (Option<usize>, Option<usize>) {
    let len = self.len();
    let mut left = None;
    let mut right = None;
    let mut i = 0;
    while i < right.unwrap_or(len) {
    match self[i].cmp(&value) {
    Ordering::Less => {
    match left {
    None => {
    self.swap(0, i);
    left = Some(0);
    }
    Some(left_value) => {
    let new_left = left_value + 1;
    self.swap(new_left, i);
    left = Some(new_left);
    }
    }
    i += 1;
    }
    Ordering::Equal => {
    i += 1;
    }
    Ordering::Greater => {
    match right {
    None => {
    self.swap(len - 1, i);
    right = Some(len - 1);
    }
    Some(right_value) => {
    let new_right = right_value - 1;
    self.swap(new_right, i);
    right = Some(new_right);
    }
    }

    // i 不要自增,让下一次循环检查新换到前面的值
    }
    }
    }

    (left.map(|v| v + 1), right)
    }
    }
    +
      +
    • Rust 实现(用枚举表示结果)
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    use std::cmp::Ordering;

    #[test]
    fn test_netherlands_flag_partition() {
    let mut array = [7, 1, 2, 0, 8, 5, 3, 9, 2, 6, 5, 1, 0, 8, 7, 4];
    assert_eq!(
    array.netherlands_flag_partition(2),
    NetherlandsFlagResult::Three(4, 6)
    );
    assert_eq!(
    array.netherlands_flag_partition(7),
    NetherlandsFlagResult::Three(11, 13)
    );
    assert_eq!(
    array.netherlands_flag_partition(-1),
    NetherlandsFlagResult::Greater
    );
    assert_eq!(
    array.netherlands_flag_partition(0),
    NetherlandsFlagResult::ValueStart(2)
    );
    assert_eq!(
    array.netherlands_flag_partition(10),
    NetherlandsFlagResult::Less
    );
    assert_eq!(
    array.netherlands_flag_partition(9),
    NetherlandsFlagResult::ValueEnd(15)
    );

    let mut array = [2, 2, 2, 2, 2];
    assert_eq!(
    array.netherlands_flag_partition(2),
    NetherlandsFlagResult::Equal
    );
    }

    #[derive(Debug, Eq, PartialEq)]
    pub enum NetherlandsFlagResult {
    /// 分为三部分,分别是 < value, == value, > value, 返回后两部分的起始索引
    Three(usize, usize),
    /// 分为两部分,== value, > value, 返回第二部分的起始索引
    ValueStart(usize),
    /// 分为两部分,< value, == value, 返回第二部分的起始索引
    ValueEnd(usize),
    /// 所有值都小于 value
    Less,
    /// 所有值都大于 value
    Greater,
    /// 所有值都等于 value
    Equal,
    }

    pub trait NetherlandsFlagPartition<T> {
    fn netherlands_flag_partition(&mut self, value: T) -> NetherlandsFlagResult;
    }

    impl<T: Ord> NetherlandsFlagPartition<T> for [T] {
    // 返回相等部分和大于部分的起始索引
    fn netherlands_flag_partition(&mut self, value: T) -> NetherlandsFlagResult {
    let len = self.len();
    let mut left = None;
    let mut right = None;
    let mut i = 0;
    while i < right.unwrap_or(len) {
    match self[i].cmp(&value) {
    Ordering::Less => {
    match left {
    None => {
    self.swap(0, i);
    left = Some(0);
    }
    Some(left_value) => {
    let new_left = left_value + 1;
    self.swap(new_left, i);
    left = Some(new_left);
    }
    }
    i += 1;
    }
    Ordering::Equal => {
    i += 1;
    }
    Ordering::Greater => {
    match right {
    None => {
    self.swap(len - 1, i);
    right = Some(len - 1);
    }
    Some(right_value) => {
    let new_right = right_value - 1;
    self.swap(new_right, i);
    right = Some(new_right);
    }
    }

    // i 不要自增,让下一次循环检查新换到前面的值
    }
    }
    }

    match (left.map(|v| v + 1), right) {
    (None, Some(i)) => {
    if i == 0 {
    NetherlandsFlagResult::Greater
    } else {
    NetherlandsFlagResult::ValueStart(i)
    }
    }
    (Some(i), None) => {
    if i >= self.len() {
    NetherlandsFlagResult::Less
    } else {
    NetherlandsFlagResult::ValueEnd(i)
    }
    }
    (Some(i), Some(j)) => {
    if i >= self.len() {
    NetherlandsFlagResult::Greater
    } else {
    NetherlandsFlagResult::Three(i, j)
    }
    }
    (None, None) => NetherlandsFlagResult::Equal,
    }
    }
    }
    +

    快速排序

    v1.0

    步骤:

    +
      +
    1. 选择数组最后一个元素 X,在 0..array.length-1 的范围上进行二分,小于等于 X 的在左,大于 X 的在右;
    2. +
    3. X 与右侧的第一个元素交换;
    4. +
    5. X 左侧与右侧的数组分别进行上述操作,进行递归,过程中 X 不需要再移动。
    6. +
    +

    时间复杂度:$O(N)$

    +
      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    static void quickSort1(int[] array) {
    recurse1(array, 0, array.length);
    }

    static void recurse1(int[] array, int start, int end) {
    if (array == null || start >= end - 1) {
    return;
    }

    int pivot = array[end - 1];
    int idx = partition(array, pivot, start, end - 1);
    swap(array, idx, end - 1);
    recurse1(array, start, idx);
    recurse1(array, idx + 1, end);
    }
    +
      +
    • Rust 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    #[test]
    fn test_quick_sort1() {
    for _ in 0..1000 {
    let mut array0: [u8; 32] = rand::random();
    let mut array1 = array0.clone();

    array0.sort();
    array1.quick_sort1();

    assert_eq!(array0, array1);
    }
    }

    pub trait QuickSort1 {
    fn quick_sort1(&mut self);
    }

    impl<T: Ord + Clone> QuickSort1 for [T] {
    fn quick_sort1(&mut self) {
    let len = self.len();
    if len < 2 {
    return;
    }

    let value = self[len - 1].clone();

    match self[..len - 1].partition(value) {
    // value 比所有元素都小,把 value 挪到第一个位置,排序剩下的
    Index::Less => {
    self.swap(0, len - 1);
    self[1..].quick_sort1();
    }
    // 把 value 与第一个大于区的数交换
    Index::Position(i) => {
    self.swap(i, len - 1);
    self[..i].quick_sort1();
    self[i + 1..].quick_sort1();
    }
    // value比所有元素都大,不动,排序前面所有的
    Index::Greater => {
    self[..len - 1].quick_sort1();
    }
    }
    }
    }
    +

    v2.0

      +
    1. 选择数组一个元素 X,进行荷兰国旗三分,小于 X 的在左,等于 X 的在中间,大于 X 的在右;
    2. +
    3. X 左侧与右侧的数组分别进行上述操作,进行递归,过程中位于中间的所有 X 不需要再移动。
    4. +
    +

    时间复杂度:$O(N)$

    +
      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    static void quickSort2(int[] array) {
    recurse2(array, 0, array.length);
    }

    static void recurse2(int[] array, int start, int end) {
    if (array == null || start >= end - 1) {
    return;
    }

    int pivot = array[end - 1];
    int[] idxs = netherlandsFlagPartition(array, pivot, start, end);
    if (idxs == null) {
    return;
    }
    recurse2(array, start, idxs[0]);

    if (idxs[1] < end - 1) {
    recurse2(array, idxs[1], end);
    }
    }
    +
      +
    • Rust 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    #[test]
    fn test_quick_sort2() {
    for _ in 0..1000 {
    let mut array0: [i32; 32] = random();
    let mut array1 = array0.clone();

    array0.sort();
    array1.quick_sort2();

    assert_eq!(array0, array1);
    }
    }

    pub trait QuickSort2 {
    fn quick_sort2(&mut self);
    }

    impl<T: Ord + Clone> QuickSort2 for [T] {
    fn quick_sort2(&mut self) {
    if self.len() < 2 {
    return;
    }

    let value = self[0].clone();

    match self.netherlands_flag_partition(value) {
    NetherlandsFlagResult::Three(start, end) => {
    self[..start].quick_sort2();
    self[end..].quick_sort2();
    }
    NetherlandsFlagResult::ValueStart(start) => {
    self[start..].quick_sort2();
    }
    NetherlandsFlagResult::ValueEnd(end) => {
    self[..end].quick_sort2();
    }
    NetherlandsFlagResult::Less | NetherlandsFlagResult::Greater => {
    self.quick_sort2();
    }
    NetherlandsFlagResult::Equal => {
    return;
    }
    }
    }
    }
    +

    v3.0

    将 v2.0 中选择数组元素改为随机选择,其他不变。

    +
    +
      +
    1. 划分值越靠近中间,性能越好;越靠近两边性能越差;
    2. +
    3. 随机算一个数进行划分的目的就是让好情况和坏情况都变成概率事件;
    4. +
    5. 把每一种情况都列出来,会有每种情况下的复杂度,但概率都是 $1/N$;
    6. +
    7. 所有情况都考虑,则时间复杂度就是这种概率模型下的长期期望。
    8. +
    +
    +

    时间复杂度:$O(N \times logN)$
    额外空间复杂度:$O(N \times logN)$

    +
      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    static void quickSort3(int[] array) {
    recurse3(array, 0, array.length);
    }

    static void recurse3(int[] array, int start, int end) {
    if (array == null || start >= end - 1) {
    return;
    }

    int pi = (int) (Math.random() * (end - start));
    System.out.println(start + pi);
    int pivot = array[start + pi];
    int[] idxs = netherlandsFlagPartition(array, pivot, start, end);
    if (idxs == null) {
    return;
    }
    recurse3(array, start, idxs[0]);

    if (idxs[1] < end - 1) {
    recurse3(array, idxs[1], end);
    }
    }
    +
      +
    • Rust 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    #[test]
    fn test_quick_sort3() {
    for _ in 0..1000 {
    let mut array0: [i32; 32] = random();
    let mut array1 = array0.clone();

    array0.sort();
    array1.quick_sort3();

    assert_eq!(array0, array1);
    }
    }

    pub trait QuickSort3 {
    fn quick_sort3(&mut self);
    }

    impl<T: Ord + Clone> QuickSort for [T] {
    fn quick_sort3(&mut self) {
    if self.len() < 2 {
    return;
    }

    let index = rand::thread_rng().gen_range(0..self.len());
    let value = self[index].clone();

    match self.netherlands_flag_partition(value) {
    NetherlandsFlagResult::Three(start, end) => {
    self[..start].quick_sort3();
    self[end..].quick_sort3();
    }
    NetherlandsFlagResult::ValueStart(start) => {
    self[start..].quick_sort3();
    }
    NetherlandsFlagResult::ValueEnd(end) => {
    self[..end].quick_sort3();
    }
    NetherlandsFlagResult::Less | NetherlandsFlagResult::Greater => {
    self.quick_sort3();
    }
    NetherlandsFlagResult::Equal => {
    return;
    }
    }
    }
    }
    +

    Swap 函数

      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    static void swap(int[] array, int i, int j) {
    if (i == j) {
    return;
    }

    array[i] = array[i] ^ array[j];
    array[j] = array[i] ^ array[j];
    array[i] = array[i] ^ array[j];
    }
    + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    归并排序

      +
    • 算法时间复杂度: $O(NlogN)$
    • +
    • 相比冒泡、选择等时间复杂度为 $O(N^2)$ 的排序算法,没有浪费比较行为;
    • +
    • 插入排序时即便使用二分查找插入位置,也需要将插入位置后的元素依次向右移动,每次插入复杂度不是 $O(1)$。
    • +
    +

    递归方法

      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    public class MergeSort {

    public static void main(String[] args) {
    Integer[] data = {12, 3, 2, 6, 4, 8, 6, 0, 9, 3};
    System.out.println(Arrays.toString(data));
    mergeSort(data);
    System.out.println(Arrays.toString(data));
    }

    static <T extends Comparable> void mergeSort(T[] array) {
    T[] help = (T[]) new Comparable[array.length];
    doMergeSort(array, help, 0, array.length);
    }

    static <T extends Comparable> void doMergeSort(T[] array, T[] help, int start, int end) {
    if (start == end - 1) {
    return;
    }

    int mid = start + ((end - start) >> 1);
    doMergeSort(array, help, start, mid);
    doMergeSort(array, help, mid, end);
    mergeSorted(array, help, start, mid, end);
    }

    static <T extends Comparable> void mergeSorted(T[] array, T[] help, int start, int mid, int end) {
    int h = start;
    int i = start;
    int j = mid;

    while (i < mid && j < end) {
    if (array[i].compareTo(array[j]) <= 0) {
    help[h++] = array[i++];
    } else {
    help[h++] = array[j++];
    }
    }

    while (i < mid) {
    help[h++] = array[i++];
    }

    while (j < end) {
    help[h++] = array[j++];
    }

    assert h == end;
    for (int n = start; n < end; n++) {
    array[n] = help[n];
    }
    }
    }
    +

    输出内容:

    +
    1
    2
    [12, 3, 2, 6, 4, 8, 6, 0, 9, 3]
    [0, 2, 3, 3, 4, 6, 6, 8, 9, 12]
    +
      +
    • Rust 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    #[test]
    fn test_merge_sort() {
    for _ in 0..100 {
    let mut array0: [u8; 32] = rand::random();
    let mut array1 = array0.clone();
    array0.merge_sort();
    array1.sort();
    assert_eq!(array0, array1);
    }
    }

    trait MergeSort {
    fn merge_sort(&mut self);
    }

    impl<T: Ord + Clone> MergeSort for [T] {
    fn merge_sort(&mut self) {
    let len = self.len();
    if len == 0 || len == 1 {
    return;
    }

    let mid = len >> 1;
    self[..mid].merge_sort();
    self[mid..].merge_sort();

    let mut i = 0;
    let mut j = mid;
    let mut vec = Vec::with_capacity(len);

    while i < mid && j < len {
    if self[i] <= self[j] {
    vec.push(self[i].clone());
    i += 1;
    } else {
    vec.push(self[j].clone());
    j += 1;
    }
    }

    while i < mid {
    vec.push(self[i].clone());
    i += 1;
    }

    while j < len {
    vec.push(self[j].clone());
    j += 1;
    }

    self.clone_from_slice(&vec);
    }
    }
    +

    非递归方法

      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public static <T extends Comparable<T>> void mergeSortLoop(T[] array) {
    if (array == null || array.length < 2) {
    return;
    }

    Comparable[] help = (T[]) new Comparable[array.length];

    int mergeSize = 1;

    while (mergeSize < array.length) {
    int left = 0;

    while (left < array.length) {
    int mid = left + mergeSize;

    if (mid >= array.length) {
    break;
    }

    int right = Math.min(mid + mergeSize, array.length);
    mergeSorted(array, help, left, mid, right);
    left = right;
    }


    if (mergeSize >= array.length >> 1) {
    break;
    }

    mergeSize <<= 1;
    }
    }
    +
      +
    • Rust 实现
    • +
    +

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    #[test]
    fn test_loop_merge_sort() {
    for _ in 0..100 {
    let mut array0: [u8; 32] = rand::random();
    let mut array1 = array0.clone();
    array0.loop_merge_sort();
    array1.sort();
    assert_eq!(array0, array1);
    }
    }

    impl<T: Ord + Clone> MergeSort for [T] {
    fn merge_sort(&mut self) {
    //...
    }

    fn loop_merge_sort(&mut self) {
    let len = self.len();
    if len < 2 {
    return;
    }

    let mut merge_size: usize = 1;
    while merge_size < len {
    let mut left = 0;

    while left < len {
    let mid = left + merge_size;
    if mid >= len {
    break;
    }

    let right = (mid + merge_size).min(len);

    // merge sorted
    let mut vec = Vec::with_capacity(right - left);

    let mut i = left;
    let mut j = mid;
    while i < mid && j < right {
    if self[i] <= self[j] {
    vec.push(self[i].clone());
    i += 1;
    } else {
    vec.push(self[j].clone());
    j += 1;
    }
    }

    while i < mid {
    vec.push(self[i].clone());
    i += 1;
    }

    while j < right {
    vec.push(self[j].clone());
    j += 1;
    }

    (&mut self[left..right]).clone_from_slice(&vec);

    left = right;
    }

    if merge_size > len >> 1 {
    break;
    }

    merge_size <<= 1;
    }
    }
    }

    求数组的小和

    在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和加起来叫数组小和。求数组小和。

    +
    +

    例子:[1, 3, 4, 2, 5]

    +

    1 左边比自己小的数:没有
    3 左边比自己小的数:1
    4 左边比自己小的数:1 3
    2 左边比自己小的数:1
    5 左边比自己小的数:1 3 4 2

    +

    所以,数组的小和为 $1 + 1 + 3 + 1 + 1 + 3 + 4 + 2 = 16$

    +
    +
      +
    • Java 实现
    • +
    +

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    public class SmallSum {
    public static void main(String[] args) {
    System.out.println(smallSum(new int[]{1, 3, 4, 2, 5}) + " == " + getSmallSumSimple(new int[]{1, 3, 4, 2, 5}));
    }

    static int getSmallSumSimple(int[] array) {
    int smallSum = 0;
    for (int i = 0; i < array.length; ++i) {
    for (int j = 0; j < i; ++j) {
    if (array[j] < array[i]) {
    smallSum += array[j];
    }
    }
    }
    return smallSum;
    }

    static int smallSum(int[] array) {
    int[] help = new int[array.length];
    return doMergeSmallSum(array, help, 0, help.length);
    }

    static int doMergeSmallSum(int[] array, int[] help, int start, int end) {
    if (end - start < 2) {
    return 0;
    }

    int mid = start + ((end - start) >> 1);
    int left = doMergeSmallSum(array, help, start, mid);
    int right = doMergeSmallSum(array, help, mid, end);
    return left + right + doMerge(array, help, start, mid, end);
    }

    static int doMerge(int[] array, int[] help, int start, int mid, int end) {
    int i = start;
    int j = mid;
    int k = start;
    int sum = 0;

    while (i < mid && j < end) {
    if (array[i] < array[j]) {
    int t = array[i++];
    help[k++] = t;
    sum += t * (end - j);
    } else {
    help[k++] = array[j++];
    }
    }

    while (i < mid) {
    help[k++] = array[i++];
    }

    while (j < end) {
    help[k++] = array[j++];
    }

    for (k = start; k < end; k++) {
    array[k] = help[k];
    }

    return sum;
    }
    }

    求数组中的降序对

    +

    例如:[3, 1, 7, 0, 2] 中的降序对有 (3, 1)(3, 0)(3, 2)(1, 0)(7, 0)(7, 2)

    +
    +

    即求数组中每个数右边有多少个数比它小,就有多少个降序对。

    +
      +
    • Rust 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    #[test]
    fn test_get_descending_pairs() {
    for _ in 0..100 {
    let mut array0: [u8; 32] = rand::random();
    let mut array1 = array0.clone();
    let set0 = array0.get_descending_pairs();
    let set1 = array1.get_descending_pairs_simple();
    assert_eq!(set0, set1);
    }
    }

    trait DescendingPair<T> {
    fn get_descending_pairs(&mut self) -> HashSet<(T, T)>;
    fn get_descending_pairs_simple(&mut self) -> HashSet<(T, T)>;
    }

    impl<T: Ord + Clone + Hash> DescendingPair<T> for [T] {
    fn get_descending_pairs(&mut self) -> HashSet<(T, T)> {
    let mut set = HashSet::new();
    if self.len() < 2 {
    return set;
    }

    let mid = self.len() >> 1;
    let mut left = self[..mid].get_descending_pairs();
    let mut right = self[mid..].get_descending_pairs();
    set.extend(left.drain());
    set.extend(right.drain());

    let mut help = Vec::with_capacity(self.len());
    let mut j = 0;
    let mut k = mid;
    let len = self.len();

    while j < mid && k < len {
    if self[j] > self[k] {
    let mut temp = self[k..]
    .iter()
    .map(|v| (self[j].clone(), v.clone()))
    .collect::<HashSet<_>>();
    set.extend(temp.drain());

    help.push(self[j].clone());
    j += 1;
    } else {
    help.push(self[k].clone());
    k += 1;
    }
    }

    help.extend_from_slice(&self[j..mid]);
    help.extend_from_slice(&self[k..]);

    assert_eq!(help.len(), self.len());
    self.clone_from_slice(&help);

    set
    }

    fn get_descending_pairs_simple(&mut self) -> HashSet<(T, T)> {
    let mut set = HashSet::new();
    for i in 0..self.len() {
    for j in 0..i {
    if self[j] > self[i] {
    set.insert((self[j].clone(), self[i].clone()));
    }
    }
    }
    set
    }
    }
    + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    当递归函数的时间执行函数满足如下的关系式时,可以使用公式法计算时间复杂度:

    +
    +

    其中:

    +
      +
    1. $a$ 为递归次数;
    2. +
    3. $\frac {N} {b}$ 为子问题规模;
    4. +
    5. $O(N^d)$ 为每次递归完毕之后额外执行的操作的时间复杂度;
    6. +
    +
    +
      +
    • 如果 $\log_b a < d$,时间复杂度为 $O(N^d)$
    • +
    • 如果 $\log_b a > d$,时间复杂度为 $O(N^{\log_b a})$
    • +
    • 如果 $\log_b a = d$,时间复杂度为 $O(N^d \times \log N)$
    • +
    +
    +

    例子

    求数组 arr[l..r]中的最大值,用递归方法实现。

    +
      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class RecursiveMax {
    public static void main(String[] args) {
    Integer[] array = {6, 3, 5, 2, 1, 4, 0, 1, 7};
    System.out.println(getMax(array, 0, array.length - 1));
    }

    public static <T extends Comparable<T>> T getMax(T[] arr, int l, int r) {
    if (l == r) {
    return arr[l];
    }

    int mid = l + ((r - l) >> 1);
    T left = getMax(arr, l, mid);
    T right = getMax(arr, mid + 1, r);
    return left.compareTo(right) >= 0 ? left : right;
    }
    }
    +

    其中:

    +
      +
    1. 递归次数 $a$ 为 $2$;
    2. +
    3. 子问题规模 $\frac {N} {b}$ 为 $\frac {N} {2}$,即 $b$ 为 $2$;
    4. +
    5. 每次递归完毕之后额外执行的操作的时间复杂度 $O(N^d)$ 为 $O(1)$,即 $d$ 为 $0$。
    6. +
    +

    满足:

    +

    所以,该算法复杂度为 $O(N^{\log_2 2}) = O(N)$

    + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    1. 环形队列

      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    public class RingBuffer<T> {
    // 队列缓冲区
    private final Object[] array;

    // 队列尺寸
    private final int limit;

    // 表示即将入队的索引位置
    private int pushIndex;

    // 表示即将出队的索引位置
    private int popIndex;

    // 表示当前队列中元素的个数
    private int size;

    private RingBuffer(int limit) {
    if (limit <= 0) {
    throw new IllegalArgumentException("limit should be greater than 0");
    }
    this.limit = limit;
    this.array = new Object[limit];
    this.popIndex = this.pushIndex = this.size = 0;
    }

    public static <T> RingBuffer<T> create(int limit) {
    return new RingBuffer<>(limit);
    }

    public Optional<T> pop() {
    if (size == 0) {
    return Optional.empty();
    }

    T value = (T) this.array[this.popIndex];
    this.array[this.popIndex] = null; // 去掉引用,避免泄漏
    this.size -= 1;
    this.popIndex = getNextIndex(this.popIndex);
    return Optional.of(value);
    }

    public boolean empty() {
    return this.size == 0;
    }

    public void push(T value) {
    if (size == this.limit) {
    throw new IllegalArgumentException("The size has reached the limit");
    }

    this.array[this.pushIndex] = value;
    this.size += 1;
    this.pushIndex = getNextIndex(this.pushIndex);
    }

    @Override
    public String toString() {
    return "RingBuffer{" +
    "array=" + Arrays.toString(array) +
    ", limit=" + limit +
    ", pushIndex=" + pushIndex +
    ", popIndex=" + popIndex +
    ", size=" + size +
    '}';
    }

    private int getNextIndex(int index) {
    return index == this.limit - 1 ? 0 : index + 1;
    }

    public static void main(String[] args) {
    RingBuffer<String> rb = RingBuffer.create(4);
    System.out.println("new rb: " + rb);
    String[] data = {"hello", "world", "are", "you", "ok"};
    for (String s: data) {
    try {
    rb.push(s);
    System.out.println("push " + s);
    System.out.println("rb: " + rb);
    } catch (Exception e) {
    System.out.println("Push '" + s + "' error: " + e);
    }
    }

    Optional<String> op = rb.pop();

    while (op.isPresent()) {
    System.out.println("pop " + op.get());
    System.out.println("rb: " + rb);
    op = rb.pop();
    }
    }
    }
    +

    输出内容:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    new rb: RingBuffer{array=[null, null, null, null], limit=4, pushIndex=0, popIndex=0, size=0}
    push hello
    rb: RingBuffer{array=[hello, null, null, null], limit=4, pushIndex=1, popIndex=0, size=1}
    push world
    rb: RingBuffer{array=[hello, world, null, null], limit=4, pushIndex=2, popIndex=0, size=2}
    push are
    rb: RingBuffer{array=[hello, world, are, null], limit=4, pushIndex=3, popIndex=0, size=3}
    push you
    rb: RingBuffer{array=[hello, world, are, you], limit=4, pushIndex=0, popIndex=0, size=4}
    Push 'ok' error: java.lang.IllegalArgumentException: The size has reached the limit
    pop hello
    rb: RingBuffer{array=[null, world, are, you], limit=4, pushIndex=0, popIndex=1, size=3}
    pop world
    rb: RingBuffer{array=[null, null, are, you], limit=4, pushIndex=0, popIndex=2, size=2}
    pop are
    rb: RingBuffer{array=[null, null, null, you], limit=4, pushIndex=0, popIndex=3, size=1}
    pop you
    rb: RingBuffer{array=[null, null, null, null], limit=4, pushIndex=0, popIndex=0, size=0}
    +

    2. 时间复杂度为 $O(1)$ 的栈中最小值获取方法

      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    import java.util.Collection;
    import java.util.Collections;
    import java.util.Optional;
    import java.util.Stack;

    public class GetMinStack<E extends Comparable<E>> extends Stack<E> {
    private final Stack<E> minStack;

    public static void main(String[] args) {
    GetMinStack<Integer> stack = new GetMinStack<>();
    int[] data = {8, 4, 2, 6, 1, 9, -1, 3};
    for (int i: data) {
    stack.push(i);
    Optional<Integer> min = stack.getMin();
    System.out.println("push " + i + ", min: " + min.get() + ", stack" + stack + ", minStack: " + stack.getMinStack());
    }

    while (!stack.empty()) {
    int i = stack.pop();
    Optional<Integer> min = stack.getMin();
    if (min.isPresent()) {
    System.out.println("pop " + i + ", min: " + min.get() + ", stack" + stack + ", minStack: " + stack.getMinStack());
    } else {
    System.out.println("pop " + i + ", stack is empty");
    }
    }
    }

    public GetMinStack() {
    minStack = new Stack<>();
    }

    @Override
    public synchronized E pop() {
    E item = super.pop();
    E min = minStack.peek();
    // 如果出栈的元素跟最小栈顶元素相等,则最小栈顶也出栈
    if (min == item) {
    minStack.pop();
    }
    return item;
    }

    @Override
    public E push(E item) {
    if (!minStack.empty()) {
    E min = minStack.peek();
    // 如果栈不空,看最小栈顶与入栈元素哪个小;
    // 一样大或者入栈元素小,则该元素入最小栈;
    // 否则不做任何操作
    if (min.compareTo(item) >= 0) {
    minStack.push(item);
    }
    } else {
    // 栈空就直接入栈
    minStack.push(item);
    }
    return super.push(item);
    }

    public Optional<E> getMin() {
    if (empty()) {
    return Optional.empty();
    } else {
    return Optional.of(minStack.peek());
    }
    }

    public Collection<E> getMinStack() {
    return Collections.unmodifiableCollection(this.minStack);
    }
    }
    +

    输出内容:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    push 8, min: 8, stack[8], minStack: [8]
    push 4, min: 4, stack[8, 4], minStack: [8, 4]
    push 2, min: 2, stack[8, 4, 2], minStack: [8, 4, 2]
    push 6, min: 2, stack[8, 4, 2, 6], minStack: [8, 4, 2]
    push 1, min: 1, stack[8, 4, 2, 6, 1], minStack: [8, 4, 2, 1]
    push 9, min: 1, stack[8, 4, 2, 6, 1, 9], minStack: [8, 4, 2, 1]
    push -1, min: -1, stack[8, 4, 2, 6, 1, 9, -1], minStack: [8, 4, 2, 1, -1]
    push 3, min: -1, stack[8, 4, 2, 6, 1, 9, -1, 3], minStack: [8, 4, 2, 1, -1]
    pop 3, min: -1, stack[8, 4, 2, 6, 1, 9, -1], minStack: [8, 4, 2, 1, -1]
    pop -1, min: 1, stack[8, 4, 2, 6, 1, 9], minStack: [8, 4, 2, 1]
    pop 9, min: 1, stack[8, 4, 2, 6, 1], minStack: [8, 4, 2, 1]
    pop 1, min: 2, stack[8, 4, 2, 6], minStack: [8, 4, 2]
    pop 6, min: 2, stack[8, 4, 2], minStack: [8, 4, 2]
    pop 2, min: 4, stack[8, 4], minStack: [8, 4]
    pop 4, min: 8, stack[8], minStack: [8]
    pop 8, stack is empty
    +

    3. 用栈实现队列

      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    import java.util.Optional;
    import java.util.Stack;

    public class StackQueue<E> {
    // 用来入队
    private final Stack<E> pushStack;
    // 用来出队
    private final Stack<E> popStack;

    public static void main(String[] args) {
    StackQueue<String> queue = new StackQueue<>();
    String[] data = {"hello", "world", "how", "are", "you"};
    for (String s: data) {
    queue.push(s);
    }
    System.out.println(queue);
    Optional<String> op = queue.poll();
    while (op.isPresent()) {
    System.out.println(op.get());
    op = queue.poll();
    }
    System.out.println(queue);
    for (String s: data) {
    queue.push(s);
    }
    System.out.println(queue);
    op = queue.poll();
    for (String s: data) {
    queue.push(s);
    }
    System.out.println(queue);
    while (op.isPresent()) {
    System.out.println(op.get());
    op = queue.poll();
    }
    System.out.println(queue);
    }

    public StackQueue() {
    pushStack = new Stack<>();
    popStack = new Stack<>();
    }

    public int size() {
    return pushStack.size() + popStack.size();
    }

    public boolean empty() {
    return pushStack.empty() && popStack.empty();
    }

    public boolean contains(Object o) {
    return pushStack.contains(o) || popStack.contains(o);
    }

    public void push(E element) {
    pushStack.push(element);
    }

    public Optional<E> poll() {
    if (popStack.empty()) {
    while (!pushStack.empty()) {
    popStack.push(pushStack.pop());
    }
    if (popStack.empty()) {
    return Optional.empty();
    }
    }

    return Optional.of(popStack.pop());
    }

    @Override
    public String toString() {
    // => 表示栈顶
    return "StackQueue{" +
    "pushStack=" + pushStack +
    "=>, popStack=" + popStack +
    "=>}";
    }
    }
    +

    输出内容:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    StackQueue{pushStack=[hello, world, how, are, you]=>, popStack=[]=>}
    hello
    world
    how
    are
    you
    StackQueue{pushStack=[]=>, popStack=[]=>}
    StackQueue{pushStack=[hello, world, how, are, you]=>, popStack=[]=>}
    StackQueue{pushStack=[hello, world, how, are, you]=>, popStack=[you, are, how, world]=>}
    hello
    world
    how
    are
    you
    hello
    world
    how
    are
    you
    StackQueue{pushStack=[]=>, popStack=[]=>}
    +

    4. 用队列实现栈

      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    import java.util.LinkedList;
    import java.util.Optional;
    import java.util.Queue;

    public class QueueStack<E> {
    private Queue<E> data;
    private Queue<E> help;

    public static void main(String[] args) {
    QueueStack<String> stack = new QueueStack<>();
    String[] data = {"hello", "world", "how", "are", "you"};
    for (String s: data) {
    stack.push(s);
    }
    Optional<String> op = stack.pop();
    while (op.isPresent()) {
    System.out.println(op.get());
    op = stack.pop();
    }
    }

    public QueueStack() {
    data = new LinkedList<>();
    help = new LinkedList<>();
    }

    public boolean empty() {
    return data.isEmpty() && help.isEmpty();
    }

    public boolean contains(E object) {
    return data.contains(object) || help.contains(object);
    }

    public int size() {
    return data.size() + help.size();
    }

    public void push(E e) {
    data.add(e);
    }

    public Optional<E> pop() {
    int size = data.size();
    if (size == 0) {
    return Optional.empty();
    }

    for (int i=0;i<size-1;++i) {
    help.add(data.poll());
    }

    E e = data.poll();

    // swap
    Queue<E> temp = help;
    help = data;
    data = temp;

    return Optional.of(e);
    }
    }
    +

    输出内容:

    +
    1
    2
    3
    4
    5
    you
    are
    how
    world
    hello
    + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/page/3/index.html b/page/3/index.html new file mode 100644 index 0000000..dd2bb6d --- /dev/null +++ b/page/3/index.html @@ -0,0 +1,1181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    单向链表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    public class Node<T> {
    private final T value;
    private Node<T> next;

    public static void main(String[] args) {
    Node<String> list = Node.build("hello", "world", "are", "you", "ok");
    System.out.println("build: " + list);
    Node<String> reversed = list.reverse();
    System.out.println("reversed: " + reversed);
    Node<String> origin = reversed.reverse();
    System.out.println("origin: " + origin);
    }

    public static <T> Node<T> build(T ...values) {
    Node<T> list = null;
    Node<T> cur = null;

    for (T value: values) {
    if (cur == null) {
    cur = new Node<>(value);
    list = cur;
    } else {
    cur.setNext(new Node<>(value));
    cur = cur.getNext();
    }
    }

    return list;
    }

    public Node(T value) {
    this.value = value;
    this.next = null;
    }

    public Node setNext(Node<T> next) {
    this.next = next;
    return this;
    }

    public Node getNext() {
    return this.next;
    }

    public T getValue() {
    return this.value;
    }

    public Node<T> reverse() {
    Node<T> head = this;
    Node<T> pre = null;

    while (head != null) {
    Node<T> next = head.getNext();

    head.setNext(pre);
    pre = head;

    head = next;
    }

    return pre;
    }

    @Override
    public String toString() {
    StringBuilder sb = new StringBuilder();
    Node<T> cur = this;
    while (cur != null) {
    sb.append(cur.getValue());
    sb.append(" -> ");
    cur = cur.getNext();
    }
    sb.append("null");
    return sb.toString();
    }
    }
    +

    输出内容:

    +
    1
    2
    3
    build: hello -> world -> are -> you -> ok -> null
    reversed: ok -> you -> are -> world -> hello -> null
    origin: hello -> world -> are -> you -> ok -> null
    +

    双向链表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    public class DoubleNode<T> {
    private final T value;
    private DoubleNode<T> previous;
    private DoubleNode<T> next;

    public static void main(String[] args) {
    DoubleNode<String> list = DoubleNode.build("hello", "world", "are", "you", "ok");
    System.out.println("build: " + list);
    DoubleNode<String> reversed = list.reverse();
    System.out.println("reversed: " + reversed);
    DoubleNode<String> origin = reversed.reverse();
    System.out.println("origin: " + origin);
    DoubleNode<String> tail = origin.getTailNode();
    System.out.println("back: " + tail.toStringBack());
    }

    public static <T> DoubleNode<T> build(T ...values) {
    DoubleNode<T> list = null;
    DoubleNode<T> cur = null;

    for (T value: values) {
    if (cur == null) {
    cur = new DoubleNode<>(value);
    list = cur;
    } else {
    DoubleNode<T> node = new DoubleNode<>(value);
    node.setPrevious(cur);
    cur.setNext(node);
    cur = cur.getNext();
    }
    }

    return list;
    }

    public DoubleNode(T value) {
    this.value = value;
    this.previous = this.next = null;
    }

    public DoubleNode<T> setNext(DoubleNode<T> next) {
    this.next = next;
    return this;
    }

    public DoubleNode<T> getNext() {
    return this.next;
    }

    public T getValue() {
    return this.value;
    }

    public DoubleNode<T> getTailNode() {
    DoubleNode<T> cur = this;
    while (cur.getNext() != null) {
    cur = cur.getNext();
    }
    return cur;
    }

    public DoubleNode<T> getPrevious() {
    return this.previous;
    }

    public DoubleNode<T> setPrevious(DoubleNode<T> previous) {
    this.previous = previous;
    return this;
    }

    public DoubleNode<T> reverse() {
    DoubleNode<T> head = this;
    DoubleNode<T> pre = null;

    while (head != null) {
    DoubleNode<T> next = head.getNext();

    // 其他都跟单链表一样,指针多设置一个
    head.setNext(pre);
    head.setPrevious(next);
    pre = head;

    head = next;
    }

    return pre;
    }

    @Override
    public String toString() {
    StringBuilder sb = new StringBuilder();
    DoubleNode<T> cur = this;
    while (cur != null) {
    sb.append(cur.getValue());
    sb.append(" -> ");
    cur = cur.getNext();
    }
    sb.append("null");
    return sb.toString();
    }

    public String toStringBack() {
    StringBuilder sb = new StringBuilder("null");
    DoubleNode<T> cur = this;
    while (cur != null) {
    sb.append(" <- ");
    sb.append(cur.getValue());
    cur = cur.getPrevious();
    }
    return sb.toString();
    }
    }
    +

    输出内容:

    +
    1
    2
    3
    4
    build: hello -> world -> are -> you -> ok -> null
    reversed: ok -> you -> are -> world -> hello -> null
    origin: hello -> world -> are -> you -> ok -> null
    back: null <- ok <- you <- are <- world <- hello
    + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    运算特性

      +
    1. 两个数进行异或运算,二进制位相同为 0,不同为 1
    2. +
    3. 满足交换律和结合律;
    4. +
    5. 异或运算可以认为是无进位相加(二进制);
    6. +
    7. 任何数与 0 异或仍得这个数,即0 ^ N = N
    8. +
    9. 任何数与自身异或得 0,即 N ^ N = N
    10. +
    +
    +

    典型应用场景

    1. 交换

    1
    2
    3
    4
    5
    6
    int a = 1;
    int b = 2;

    a = a ^ b; // a = 1 ^ 2, b = 2
    b = a ^ b; // a = 1 ^ 2, b = 1 ^ 2 ^ 2 = 1
    a = a ^ b; // a = 1 ^ 2 ^ 1 = 2, b = 1
    +

    数组元素交换时,要确保交换的不是一个空间,可以相等,但不能是同一块内存跟自己进行异或运算:

    +
      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void swap(int[] array, int i, int j) {
    if (i == j) {
    // 无法确保 i != j 一定要加这个检查,否则该位置值变为 0
    return;
    }

    array[i] = array[i] ^ array[j];
    array[j] = array[i] ^ array[j];
    array[i] = array[i] ^ array[j];
    }
    +

    2. 找到出现奇数次的数

    题目: 一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到这种数。

    +
      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    int getOddTimesNumber(int[] array) {
    int xor = 0;
    for (int i: array) {
    xor ^= i;
    }
    return xor;
    }
    +
      +
    • Rust 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    fn get_odd_times_number_0(array: &[i32]) -> i32 {
    let mut xor = 0;
    for i in array {
    xor ^= *i;
    }
    xor
    }

    fn get_odd_times_number_1(array: &[i32]) -> i32 {
    array.iter().fold(0, |a, b| a ^ *b)
    }

    #[test]
    fn test_get_odd_times_number() {
    use rand::prelude::*;

    let mut rng = thread_rng();

    for _ in 0..100 {
    let mut vec = Vec::new();
    let odd_number = rng.gen_range(0..5);
    for i in 0..5 {
    let times: usize = rng.gen_range(1..3) * 2 + if i == odd_number { 1 } else { 0 };
    for _ in 0..times {
    vec.push(i);
    }
    }
    vec.shuffle(&mut rng);

    let get0 = get_odd_times_number_0(&vec);
    let get1 = get_odd_times_number_1(&vec);
    assert_eq!(odd_number, get0);
    assert_eq!(odd_number, get1);
    }
    }
    +

    3. 提取整型数最右侧的 1

    题目: 提取整型数最右侧的 1

    +

    例如:

    1
    2
    0b10101000 ----> 0b00001000
    ^---只留这个 `1`

    +
      +
    • Java 实现
    • +
    +
    1
    2
    3
    int getRightmostOne(int value) {
    return value & (~value + 1);
    }
    +
      +
    • Rust 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    fn get_rightmost_one(value: i32) -> i32 {
    value & (!value + 1)
    }

    #[test]
    fn test_get_rightmost_one() {
    for _ in 0..1000 {
    let a: i32 = rand::random();
    let b = get_rightmost_one(a);
    assert_eq!(b >> b.trailing_zeros(), 1);
    let bits = b.leading_zeros() + 1 + b.trailing_zeros();
    assert_eq!(bits, size_of::<i32>() as u32 * 8);
    }
    }
    +

    4. 找到两个出现奇数次的数

    题目: 一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到这两种数。

    +
      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    int[] getOddTimesNumbers(int[] array) {
    int xor = 0;
    for (int i: array) {
    xor ^= i;
    }

    // xor == a ^ b
    // 因为 a != b (两种数),所以 a ^ b != 0,则必然存在为 1 的二进制位
    // 不妨就使用最后一个 1,即
    int one = xor & (~xor + 1);
    // 假设这个 1 在第 N 位
    int a = 0;
    for (int i: array) {
    // 将数组分为两类:1. N 位上为 1 的;2. N 位上为 0 的。
    // a 和 b 一定分别属于这两类,不会同属一类,因为 a ^ b 的 N 位是 1
    // 这里只把第 1 类异或起来,得到一种数
    if ((i & one) != 0) {
    a ^= i;
    }
    }

    // 用之前得到的这种数异或 xor,得到另一种
    return new int[]{a, a ^ xor};
    }
    +
      +
    • Java 实现(函数式)
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    int[] getOddTimesNumbers(int[] array) {
    int xor = Arrays.stream(array).reduce(0, (a, b) -> a ^ b);

    int one = xor & (~xor + 1);
    int a = Arrays.stream(array).reduce(0, (v1, v2) -> {
    if ((v2 & one) != 0) {
    return v1 ^ v2;
    } else {
    return v1;
    }
    });

    return new int[] {a, xor ^ a};
    }
    +
      +
    • Rust 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    fn get_odd_times_numbers(array: &[i32]) -> (i32, i32) {
    let mut xor = 0;
    for i in array {
    xor ^= *i;
    }

    let one = xor & (!xor + 1);
    let mut a = 0;
    for i in array {
    if one & *i != 0 {
    a ^= *i;
    }
    }

    (a, xor ^ a)
    }
    +
      +
    • Rust 实现(函数式)
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    fn get_odd_times_numbers(array: &[i32]) -> (i32, i32) {
    let xor = array.iter().fold(0, |a, b| a ^ *b);

    let one = xor & (!xor + 1);
    let a = array
    .iter()
    .fold(0, |a, b| if one & b != 0 { a ^ *b } else { a });

    (a, xor ^ a)
    }

    +
      +
    • Rust 测试代码
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    #[test]
    fn test_get_odd_times_numbers() {
    use rand::prelude::*;

    let mut rng = thread_rng();

    for _ in 0..1000 {
    let mut vec = Vec::new();
    let odd_number_a = rng.gen_range(0..5);
    let odd_number_b = loop {
    let b = rng.gen_range(0..5);
    if b != odd_number_a {
    break b;
    } else {
    continue;
    }
    };
    for i in 0..5 {
    let times: usize = rng.gen_range(1..3) * 2
    + if i == odd_number_a || i == odd_number_b {
    1
    } else {
    0
    };
    for _ in 0..times {
    vec.push(i);
    }
    }
    vec.shuffle(&mut rng);

    let get = get_odd_times_numbers(&vec);
    assert!(get == (odd_number_a, odd_number_b) || get == (odd_number_b, odd_number_a));
    }
    }
    +

    5. 计算某个数为 1 的二进制位数

    例如:

    +
    1
    0b00101100 --> 3
    +
      +
    • Java 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int countOnes(int a) {
    int count = 0;

    while (a != 0) {
    count += 1;
    int one = a & (~a + 1);
    a ^= one; // 把这个 1 给去掉
    }

    return count;
    }

    +
      +
    • Rust 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    fn count_ones(mut a: i32) -> u32 {
    let mut count = 0;

    while a != 0 {
    count += 1;
    // a ^= a & (!a + 1) 在 debug 编译条件下可能溢出,无法通过测试,release 无所谓;wrapping_add 在 debug 条件下也没问题
    a ^= a & (!a).wrapping_add(1);
    }

    count
    }


    #[test]
    fn test_count_ones() {
    for _ in 0..1000 {
    let a = rand::random();
    assert_eq!(count_ones(a), a.count_ones());
    }
    }
    + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    给你一个数组,长度为N,相邻元素互不相等,请返回任意局部最小值的索引。

    +

    局部最小定义:

    +
      +
    1. 0 位置如果比 1 位置数小,则 0 位置为局部最小;
    2. +
    3. N-1 位置如果比 N-2 位置数小,则 N-1 位置为局部最小;
    4. +
    5. i 位置的数,如果比 i-1i+1 位置的数都小,则 i 位置为局部最小。
    6. +
    +
    +
      +
    • Java 实现
    • +
    +

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    public static int getLocalMinimumIndex(int[] array) {
    if (array == null || array.length == 0) {
    return -1;
    }

    if (array.length == 1) {
    return 0;
    }

    if (array[0] < array[1]) {
    return 0;
    }

    if (array[array.length - 1] < array[array.length - 2]) {
    return array.length - 1;
    }

    int left = 1;
    int right = array.length - 2;
    while (left < right) {
    int mid = left + ((right - left) >> 1);
    if (array[mid] > array[mid - 1]) {
    right = mid - 1;
    } else if (array[mid] > array[mid + 1]) {
    left = mid + 1;
    } else {
    return mid;
    }
    }

    return left;
    }

      +
    • Rust 实现
    • +
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    fn get_local_minimum_index(array: &[i32]) -> Option<usize> {
    let len = array.len();
    if len == 0 {
    return None;
    }
    if len == 1 {
    return Some(0);
    }

    if array[0] < array[1] {
    return Some(0);
    }

    if array[len - 1] < array[len - 2] {
    return Some(len - 1);
    }

    let mut left = 1;
    let mut right = len - 2;
    while left < right {
    let mid = mid(left, right);
    if array[mid] > array[mid - 1] {
    right = mid - 1;
    } else if array[mid] > array[mid + 1] {
    left = mid + 1;
    } else {
    return Some(mid);
    }
    }

    Some(left)
    }

    /// 计算两个无符号值的中间值
    fn mid<T: Ord + Div<Output = T> + Add<Output = T> + Copy + Sub<Output = T> + From<u8>>(
    v1: T,
    v2: T,
    ) -> T {
    match v1.cmp(&v2) {
    Ordering::Less => {
    let v = v2 - v1;
    v1 + v / T::from(2u8)
    }
    Ordering::Equal => v1,
    Ordering::Greater => {
    let v = v1 - v2;
    v2 + v / T::from(2u8)
    }
    }
    }

    /// 判断一个索引是否为局部最小值的索引
    fn is_local_minimum(array: &[i32], index: usize) -> bool {
    let len = array.len();
    if len == 0 {
    return false;
    }
    if len == 1 {
    return index == 0;
    }

    let range = 0..len;
    if !range.contains(&index) {
    return false;
    }

    // 1. `0` 位置如果比 `1` 位置数小,则 `0` 位置为局部最小;
    if index == 0 && array[0] < array[1] {
    return true;
    }

    // 2. `N-1` 位置如果比 `N-2` 位置数小,则 `N-1` 位置为局部最小;
    if index == len - 1 && array[len - 1] < array[len - 2] {
    return true;
    }

    // 3. `i` 位置的数,如果比 `i-1` 和 `i+1` 位置的数逗笑,则 `i` 位置为局部最小。
    array[index] < array[index - 1] && array[index] < array[index + 1]
    }

    // 判断一个数组是否符合要求
    fn is_array_valid(array: &[i32]) -> bool {
    if array.is_empty() {
    return false;
    }

    let mut last = array[0];

    for v in &array[1..] {
    if last == *v {
    return false;
    }

    last = *v;
    }

    true
    }

    /// 测试代码
    #[test]
    fn test_get_local_minimum_index() {
    for _ in 0..1000 {
    // 生成随机数组
    let array = loop {
    // Cargo.toml里加上rand = "0.8"
    let array: [i32; 32] = rand::random();
    // 确保数组符合条件
    if !is_array_valid(&array) {
    continue;
    }
    break array;
    };

    if let Some(index) = get_local_minimum_index(&array) {
    assert!(is_local_minimum(&array, index));
    } else {
    assert!(false);
    }
    }
    }
    + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    遵循以下公式:

    +
      +
    • 其中,$N_C$ 为处理器的核的数目,可以通过 Runtime.getRuntime().avaliableProcessors() 得到
    • +
    • $U_C$ 是期望的 CPU 利用率(介于 0 到 1 之间)
    • +
    • $W / C$ 是等待时间与计算时间的比值
    • +
    +

    例如,对于 CPU 密集型应用,期望 CPU 利用率为 100%,无等待纯计算,则有:

    +

    即工作线程数设置为处理器的核心数最合适。

    + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +
      +
    1. 开闭原则(Open Close Principle)。对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

      +
    2. +
    3. 里氏代换原则(Liskov Substitution Principle)。里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

      +
    4. +
    5. 依赖倒转原则(Dependence Inversion Principle)。开闭原则的基础,具体内容是针对接口编程,依赖于抽象而不依赖于具体。

      +
    6. +
    7. 接口隔离原则(Interface Segregation Principle)。使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

      +
    8. +
    9. 迪米特法则,又称最少知道原则(Demeter Principle)。一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

      +
    10. +
    11. 合成复用原则(Composite Reuse Principle)。尽量使用合成/聚合的方式,而不是使用继承。

      +
    12. +
    + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    单例模式

    一个单一的类,负责创建自己的对象,同时确保只有单个对象被创建。

    +

    单例模式三种最典型的懒加载单例写法:

    +

    双重检查锁

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public class Singleton {

    // 定义为 volatile,防止指令重排序,导致未完成初始化的对象被使用
    private static volatile Singleton INSTANCE = null;

    public static Singleton getInstance() {
    // 基于性能考量,第一次检查,避免每一次都加锁
    if (INSTANCE == null) {
    // 加锁
    synchronized (Singleton1.class) {
    // 检查这之前是否有其他线程已经获得过锁并初始化
    if (INSTANCE == null) {
    INSTANCE = new Singleton();
    }
    }
    }

    return INSTANCE;
    }
    }
    +

    静态内部类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Singleton {

    // 外部类被加载时,内部类还不会被加载
    private static class SingletonHolder {
    private final static Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
    // 调用这里时,内部类被加载,实现初始化
    // 由JVM保证只加载一次,线程安全
    return SingletonHolder.INSTANCE;
    }

    private Singleton() {}
    }
    +

    枚举

    1
    2
    3
    4
    5
    6
    7
    8
    // 懒加载,线程安全,还可以防止反序列化
    public enum Singleton {
    INSTANCE;

    public static Singleton getInstance() {
    return Singleton.INSTANCE;
    }
    }
    + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +

    策略模式(Strategy)

    策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不通过的对象管理。

    +

    最典型的例子就是使用比较器(Comparator<T>):

    +
    1
    2
    3
    4
    5
    6
    @FunctionalInterface
    public interface Comparator<T> {
    int compare(T o1, T o2);

    // 省略其他...
    }
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    import java.util.Arrays;
    import java.util.Comparator;

    public class Cat {
    int weight;

    public int GetWeight() {
    return weight;
    }

    public Cat(int weight) {
    this.weight = weight;
    }

    @Override
    public String toString() {
    return "Cat{" +
    "weight=" + weight +
    '}';
    }

    public static void main(String[] args) {
    Cat[] cats = {new Cat(12), new Cat(21), new Cat(4), new Cat(8)};

    // 这里可以new一个实现了Comparator的类,也可以使用Lambda表达式
    Arrays.sort(cats, Comparator.comparingInt(Cat::GetWeight));

    System.out.println(Arrays.toString(cats));
    }
    }
    +
    +

    注:

    +
      +
    1. 开闭原则(OCP,Open-Closed Principle),对修改关闭,对扩展开放。
    2. +
    3. 自定义静态泛型函数时,将类型参数列表写在 static 和返回值类型之间,例如:
      1
      static <T> void sort(T[] array, Comparator c);
      +
    4. +
    +
    +
    +

    抽象工厂(Abstract Factory)

    一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。

    +

    例如,农场可以生产动物、蔬菜。农场A生产牛,白菜,农场B生产羊,花椰菜。编码如下:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 农场抽象
    public abstract class Farm {
    public abstract Animal createAnimal();
    public abstract Vegetable createVegetable();
    }

    // 动物抽象
    public interface Animal {
    }

    // 蔬菜抽象
    public interface Vegetable {
    }
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 农场A
    public class FarmA extends Farm {
    @Override
    public Animal createAnimal() {
    return new Cow();
    }

    @Override
    public Vegetable createVegetable() {
    return new Cabbage();
    }
    }

    public class Cow implements Animal {
    }

    public class Cabbage implements Vegetable {
    }
    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 农场B
    public class FarmB extends Farm {
    @Override
    public Animal createAnimal() {
    return new Sheep();
    }

    @Override
    public Vegetable createVegetable() {
    return new Cauliflower();
    }
    }

    public class Sheep implements Animal {
    }

    public class Cauliflower implements Vegetable {
    }
    +
    1
    2
    3
    4
    5
    6
    7
    8
    // 实际调用
    public class Main {
    public static void main(String[] args) {
    Farm farm = new FarmA();
    System.out.println(farm.createAnimal().getClass().getSimpleName());
    System.out.println(farm.createVegetable().getClass().getSimpleName());
    }
    }
    +

    输出:

    +
    1
    2
    Cow
    Cabbage
    +

    使用 Rust 语言编写如下:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    use std::fmt::Debug;

    // 产品抽象
    trait Animal {}
    trait Vegetable {}

    // 工厂抽象
    trait Farm {
    type A: Animal;
    type V: Vegetable;
    const NAME: &'static str;

    fn create_animal() -> Self::A;
    fn create_vegetable() -> Self::V;
    }

    #[derive(Debug)]
    struct Cow;
    impl Animal for Cow {}
    #[derive(Debug)]
    struct Cabbage;
    impl Vegetable for Cabbage {}
    struct FarmA;
    impl Farm for FarmA {
    type A = Cow;
    type V = Cabbage;
    const NAME: &'static str = "A";

    fn create_animal() -> Self::A {
    Cow
    }

    fn create_vegetable() -> Self::V {
    Cabbage
    }
    }

    #[derive(Debug)]
    struct Sheep;
    impl Animal for Sheep {}
    #[derive(Debug)]
    struct Cauliflower;
    impl Vegetable for Cauliflower {}
    struct FarmB;
    impl Farm for FarmB {
    type A = Sheep;
    type V = Cauliflower;
    const NAME: &'static str = "B";

    fn create_animal() -> Self::A {
    Sheep
    }

    fn create_vegetable() -> Self::V {
    Cauliflower
    }
    }

    // 实际调用
    fn run_a_farm<F>()
    where
    F: Farm,
    F::A: Debug,
    F::V: Debug,
    {
    println!("Farm {}:", F::NAME);
    println!("\tAnimal: {:?}", F::create_animal());
    println!("\tVegetable: {:?}", F::create_vegetable());
    }

    fn main() {
    run_a_farm::<FarmA>();
    run_a_farm::<FarmB>();
    }
    +

    输出结果为:

    +
    1
    2
    3
    4
    5
    6
    Farm A:
    Animal: Cow
    Vegetable: Cabbage
    Farm B:
    Animal: Sheep
    Vegetable: Cauliflower
    +

    典型应用场景

      +
    1. 界面换肤,一款皮肤下,不同的控件的样式;
    2. +
    +
    +

    注:

    +
      +
    1. 增加新的产品族,只需要新增工厂实现,满足开闭原则;
    2. +
    3. 产品族需要新增产品的时候,需要修改所有工厂,不满足开闭原则。
    4. +
    5. 当系统只存在一类产品时,抽象工厂退化到工厂方法模式。
    6. +
    +
    +
    +

    外观/门面(Facade)

    通过为多个复杂的子系统提供一个一致的接口,而使这些子系统更加容易被访问的模式。

    +

    它是迪米特法则的典型应用。降低了子系统与客户端之间的耦合度。

    +
    +

    迪米特法则(LOD,Law of Demeter)又叫作最少知识原则(The Least Knowledge Principle),一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。

    +
    + + +
    + + + + +
    +
    +
    +
    + + + + + + + +
    + + + + + +
    +

    + + +

    + + +
    + + + + +
    + + +
    +

    这是 Withoutboats 在 2019 年 3 月的 Rust Latam 上所做报告的一个整理。这个报告主要介绍他参与开发了一年半的语言特性,包括 Rust 异步 I/O 的发展历程,以及目前已经稳定的零成本抽象的async/await 语法的关键实现原理。

    +

    Withoutboats 是就职于 Mozilla 的一名研究员,主要从事 Rust 语言开发。他开发的这个语言特性叫做 async/await,这可能是本年度我们在 Rust 语言上做的最重要的事。这解决了困扰我们很久的问题,即我们如何能在 Rust 中拥有零成本抽象的异步IO。

    +

    注:因个人水平有限,翻译和整理难免有错误或疏漏之处,欢迎读者批评指正。

    +
    +

    async/await

    首先,介绍一下 async/await

    +

    async 是一个修饰符,它可以应用在函数上,这种函数不会在调用时一句句运行完成,而是立即返回一个 Future 对象,这个 Future 对象最终将给出这个函数的实际返回结果。而在一个这样的 async 函数中,我们可以使用await运算符,将它用在其它会返回 Future 的函数上,直到那些 Future 返回实际结果。通过这种方法,异步并发开发更加方便了。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    let user = await db.get_user("withoutboats");

    impl Database {
    async fn get_user(&mut self, user: &str) -> User {
    let sql = format!("select FROM users WHERE username = {}", user);
    let db_response = await self.query(&sql);
    User::from(db_response)
    }
    }
    +

    这是一段简短的代码样例,我们具体解释一下 Future 。这段代码基本上做的就是一种类似于 ORM 框架所作的事。你有一个叫 get_user 的函数,它接受一个字符串类型的用户名参数,并通过在数据库中查找对应用户的记录来返回一个User对象。它使用的是异步 I/O ,这意味着它得是一个异步函数,而不是普通函数,因此当你调用它时,你可以异步等待(await)它;然后我们看一下函数的实现,首先是用用户名参数拼接出要执行的 SQL 语句,然后是查询数据库,这就是我们实际执行 I/O 的地方,所以这个查询(query)返回的是 Future ,因为它使用的是异步 I/O 。所以在查询数据库时,你只需要使用异步等待(await)来等待响应,在获得响应后就可以从中解析出用户。这个函数看起来像个玩具,但我想强调的是,它与使用阻塞式 I/O 的唯一区别就是这些注解(指async/await)了,你只需将函数标记为异步(async),并在调用它们时加上 await 就行了,开发的心智负担很小,以至于你会忘了自己是在写异步 I/O 而不是阻塞 I/O 。而 Rust 的这种实现让我尤其感到兴奋的是,它的 async/awaitFuture 都是零成本抽象的。

    +

    零成本抽象

    零成本抽象是 Rust 比较独特的一项准则,这是使 Rust 与其他许多语言相区别的原因之一。在添加新功能时,我们非常关心这些新功能是不是零成本的。不过这并不是我们想出来的点子,它在 C++ 中也很重要,所以我认为最好的解释是 Bjarne Stroustrup 的这句话:

    +
    +

    零成本抽象意味着你不使用的东西,你不用为它付出任何代价,进一步讲,你使用的东西,你无法写出比这更好的代码。

    +

    Zero Cost Abstractions: What you don’t use, you don’t pay for. And further: What you do use, you couldn’t hand code any better.

    +
    +

    也就是说零成本抽象有两个方面:

    +
      +
    1. 该功能不会给不使用该功能的用户增加成本,因此我们不能为了增加新的特性而增加那些会减慢所有程序运行的全局性开销。
    2. +
    3. 当你确实要使用该功能时,它的速度不会比不使用它的速度慢。如果你觉得,我想使用这个非常好用的功能把开发工作变得轻松,但是它会使我的程序变慢,所以我打算自己造一个,那么这实际上是带来了更大的痛苦。
    4. +
    +

    所以,我将回顾一下我们如何尝试解决异步 I/O 和 Rust 的问题,以及在我们实现这一目标的过程中,某些未能通过这两项零成本测试的特性。

    +

    绿色线程的尝试

    我们要解决的问题是 异步 I/O 。通常 I/O 处于阻塞状态,因此当你使用 I/O 时,它会阻塞线程,中止你的程序,然后必须通过操作系统重新调度。阻塞式 I/O 的问题是当你尝试通过同一程序提供大量连接时,它无法真正实现扩展。因此对于真正的大规模网络服务,你需要某种形式的非阻塞的或者说异步的 I/O 。尤其是 Rust 是针对具有真正的高性能要求而设计的语言,它是一种系统编程语言,面向那些真正在乎计算资源的人。要在网络的世界中真正取得成功,我们就需要某种解决方案来解决这个异步 I/O 问题。

    +

    但是 异步 I/O 的最大问题是它的工作方式 :在你调用 I/O 时,系统调用会立即返回,然后你可以继续进行其他工作,但你的程序需要决定如何回到调用该异步 I/O 暂停的那个任务线上,这就使得在编码上,异步 I/O 的代码要比阻塞 I/O 的代码复杂得多。所以,很多,尤其是以可扩展的网络服务这类特性为目标的语言,一直在试图解决这个问题。比如,让它不再是最终用户需要解决的问题,而是编程语言的一部分或者某个库的一部分等等。

    +

    Rust 最初使用的第一个解决方案是 绿色线程,它已经在许多语言中获得成功。绿色线程基本上就像阻塞式 I/O 一样,使用的时候就像是普通的线程,它们会在执行 I/O 时阻塞,一切看起来就跟你在使用操作系统的原生方式一样。但是,它们被设计为语言运行时的一部分,来对那些需要同时运行成千上万甚至数百万个绿色线程的网络服务用例进行优化。一个使用该模型的典型的成功案例就是 Go 语言,它的绿色线程被称为 goroutine。对于 Go 程序来说,同时运行成千上万个 goroutine 是很正常的,因为与操作系统线程不同,创建它们的成本很低。

    +
    + + + + + + + + + + + + + + + + + + + + +
    操作系统线程绿色线程
    内存开销较大的堆栈,增加大量内存占用初始堆栈非常小
    CPU开销上下文切换至操作系统的调度器,成本很高由程序本身的运行时调度
    +
    +

    绿色线程的优点 在于,产生操作系统线程时的内存开销要高得多,因为每个操作系统线程会创建一个很大的堆栈,而绿色线程通常的工作方式是,你将产生一个以很小的堆栈,它只会随着时间的推移而增长,而产生一堆不使用大量内存的新线程并不便宜;并且使用类似操作系统原语的问题还在于你依赖于操作系统调度,这意味着你必须从程序的内存空间切换到内核空间,如果成千上万的线程都在快速切换,上下文切换就会增加很多开销。而将调度保持在同一程序中,你将避免使用这些上下文,进而减少开销。所以我相信绿色线程是一个非常好的模型,适用于许多语言,包括 Go 和 Java。

    +

    在很长一段时间内, Rust 都有绿色线程,但是在 1.0 版本之前删掉了。我们删掉它是因为它不是零成本抽象的,准确的说就是我在第一个问题中谈到的,它给那些不需要它的人增加了成本。比如你只想编写一个不是网络服务的屏幕打印的 Rust 程序,你必须引入负责调度所有绿色线程的语言运行时。这种方法,尤其是对于试图把 Rust 集成到一个大的 C 应用程序中的人来说,就成为一个问题。很多 Rust 的采用者拥有一些大型C程序,他们想开始使用 Rust 并将 Rust 集成到他们的程序中,只是一小段 Rust 代码。问题是,如果你必须设置运行时才能调用 Rust ,那么这一小部分的 Rust 程序的成本就太高了。因此从 1.0 开始,我们就从语言中删除了绿色线程,并删除了语言的运行时。现在我们都知道它的运行时与 C 基本上相同,这就使得在 Rust 和 C 之间调用非常容易,而且成本很低,这是使 Rust 真正成功的关键因素之一。删除了绿色线程,我们还是需要某种异步 I/O 解决方案;但是我们意识到 这应该是一个基于库的解决方案,我们需要为异步 I/O 提供良好的抽象,它不是语言的一部分,也不是每个程序附带的运行时的一部分,只是可选的并按需使用的库。

    +

    Future 的解决方案

    最成功的库解决方案是一个叫做 Future 的概念,在 JavaScript 中也叫做 PromiseFuture 表示一个尚未得出的值,你可以在它被解决(resolved)以得出那个值之前对它进行各种操作。在许多语言中,对 Future 所做的工作并不多,这种实现支持很多特性比如组合器(Combinator),尤其是能让我们在此基础上实现更符合人体工程学的 async/await 语法。

    +

    Future 可以表示各种各样的东西,尤其适用于表示异步 I/O :当你发起一次网络请求时,你将立即获得一个 Future 对象,而一旦网络请求完成,它将返回任何响应可能包含的值;你也可以表示诸如“超时”之类的东西,“超时”其实就是一个在过了特定时间后被解决的 Future ;甚至不属于 I/O 的工作或者需要放到某个线程池中运行的CPU密集型的工作,也可以通过一个 Future 来表示,这个 Future 将会在线程池完成工作后被解决。

    +
    1
    2
    3
    4
    5
    trait Future {
    type Output;
    fn schedule<F>(self, callback: F)
    where F: FnOnce(Self::Output);
    }
    +

    Future 存在的问题 是它在大多数语言中的表示方式是这种基于回调的方法,使用这种方式时,你可以指定在 Future 被解决之后运行什么回调函数。也就是说, Future 负责弄清楚什么时候被解决,无论你的回调是什么,它都会运行;而所有的不便也都建立在此模型上,它非常难用,因为已经有很多开发者进行了大量的尝试,发现他们不得不写很多分配性的代码以及使用动态派发;实际上,你尝试调度的每个回调都必须获得自己独立的存储空间,例如 crate 对象、堆内存分配,这些分配以及动态派发无处不在。这种方法没有满足零成本抽象的第二个原则,如果你要使用它,它将比你自己写要慢很多,那你为什么还要用它。

    +

    基于轮询的解决方案

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 基于轮询的 Future

    trait Future {
    type Output;
    fn poll(&mut self, waker: &Waker)
    -> Poll<Self::Output>;
    }

    enum Poll<T> {
    Ready(T),
    Pending,
    }
    +

    这个非常出色的基于轮询的新方案——我们编写了这个模型,我归功于 Alex 和 Aaron Turon,是他们提出了这个想法——不是由 Future 来调度回调函数,而是由我们去轮询 Future,所以还有另一个被称为执行器(executor)的组件,它负责实际运行 Future ;执行器的工作就是轮询 Future ,而 Future 可能返回“尚未准备就绪(Pending)”,也可能被解决就返回“已就绪(Ready)”。

    +

    该模型有很多优点。其中一个优点是,你可以非常容易地取消 Future ,因为取消 Future 只需要停止持有 Future。而如果采用基于回调的方法,要通过调度来取消并使其停止就没这么容易了。

    +

    同时它还能够使我们在程序的不同部分之间建立真正清晰的抽象边界,大多数 Future 库都带有事件循环(event loop),这也是调度你的 Future 执行 I/O 的方法,但你实际上对此没有任何控制权。而在 Rust 中,各组件之间的边界非常整洁,执行器(executor)负责调度你的 Future ,反应器(reactor)处理所有的 I/O ,然后是你的实际代码。因此最终用户可以自行决定使用什么执行器,使用他们想使用的反应器,从而获得更强的控制力,这在系统编程语言中真的很重要。而此模型最重要的真正优势在于,它使我们能够以一种真正零成本的完美方式实现这种状态机式的 Future 。也就是当你编写的 Future 代码被编译成实际的本地(native)代码时,它就像一个状态机;在该状态机中,每次 I/O 的暂停点都有一个变体(variant),而每个变体都保存了恢复执行所需的状态。这表示为一个枚举(enum)结构,即一个包含变体判别式及所有可能状态的联合体(union)。

    +

    StateMachines

    +
    +

    译者注:报告视频中的幻灯片比较模糊,我对其进行了重绘与翻译,下同。

    +
    +

    上面的幻灯片尽可能直观地表示了这个状态机模型。可以看到,你执行了两个 I/O 事件,所以它有这几个状态。对于每个状态它都提供了所需的内存空间,足够你在 I/O 事件后恢复执行。

    +

    整个 Future 只需要一次堆内存分配,其大小就是你将这个状态机分配到堆中的大小,并且没有额外的开销。你不需要装箱、回调之类的东西,只有真正零成本的完美模型。这些概念对于很多人来说比较难于理解,所以这是我力求做到最好的幻灯片,直观地呈现这个过程中发生了什么:你创建一个 Future,它被分配到某个内存中特定的位置,然后你可以在执行器(executor)中启动它。

    +

    PollWakeCycle1

    +

    执行器会轮询 Future,直到最终 Future 需要执行某种 I/O 。

    +

    PollWakeCycle2

    +

    在这种情况下,该 Future 将被移交给处理 I/O 的反应器,即 Future 会等待该特定 I/O 。最终,在该 I/O 事件发生时,反应器将使用你在轮询它时传递的Waker 参数唤醒 Future ,将其传回执行器;

    +

    PollWakeCycle3

    +

    然后当需要再次执行I/O时,执行器再将其放回反应器;它将像这样来回穿梭,直到最终被解决(resolved)。在被解决并得出最终结果时,执行器知道它已经完成,就会释放句柄和整个Future,整个调用过程就完成了。

    +

    PollWakeCycle4

    +

    总结一下:这种模型形成了一种循环,我们轮询 Future ,然后等待 I/O 将其唤醒,然后一次又一次地轮询和唤醒,直到最终整个过程完成为止。

    +

    PollWakeCycle

    +

    并且这种模型相当高效。

    +

    QuiteFast

    +

    这是在有关 Future 的第一篇文章中发布的基准测试,与其他语言的许多不同实现进行了对比。柱形越高表示性能越好,我们的 Future 模型在最左侧,所以说我们有了非常出色的零成本抽象,即使是与许多其他语言中最快的异步 I/O 实现相比也是相当有竞争力的。

    +

    但是,问题在于,你并不希望手动编写这些状态机,把整个应用程序所有状态写成一个状态机并不是件轻松愉快的事。而这种 Future 抽象的真正有用之处在于,我们可以在其之上构建其他 API 。

    +

    其它API

    组合器(Combinator)

    我们能使用的第一个解决方案是 Future 组合器(Combinator)。你可以通过将这些组合器方法应用于 Future 来构建状态机,它们的工作方式类似于迭代器(Iterator)的适配器(如 filtermap)。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    fn fetch_rust_lang(client: Client)
    -> impl Future<Output = String> {
    client.get("rust-lang.org").and_then(|response| {
    response.concat_body().map(|body| {
    String::from_utf8_lossy(body)
    })
    })
    }
    +

    这个函数的作用是请求 “rust-lang.org”,然后将响应转换为字符串。它并不返回一个字符串,而是返回一个字符串的 Future ,因为它是一个异步函数。函数体中有这样一个 Future,它包含一些会被调用的 I/O 操作,用 and_thenmap 之类的组合器将这些操作全部组合在一起。我们构建了所有这些用处各异的组合器,例如,and_thenmapfiltermap_error等等。我们已经知道这种方式是有一些缺点的,尤其是诸如嵌套回调之类,可读性非常差。

    +

    async / await 的实现

    因为组合器有这样的缺点,所以我们开始尝试实现 async / awaitasync / await 的第一个版本并不是 Rust 语言的一部分,而是由该库像语法插件一样提供的。

    +
    1
    2
    3
    4
    5
    6
    #[async]
    fn fetch_rust_lang(client: Client) -> String {
    let response = await!(client.get("rust-lang.org"));
    let body = await!(response.concat_body());
    String::from_utf9_lossy(body)
    }
    +

    这个函数与之前那个版本的一样,它只是获取 Rust 官网并将其转换为字符串;但是它使用 async / await 来实现,所以更像是顺序执行,看起来更像普通的阻塞 I/O 的工作方式;就像开头那个实例中呈现的一样,它们唯一区别是注解(指 async / await)。我们已经知道,async 注解会将此函数转换为一个返回 Future 的函数,而不是立即返回结果,并且我们需要异步等待(await)这些在函数内部构造的 Future

    +
    1
    2
    3
    4
    5
    6
    7
    8
    await!($future) => {
    loop {
    match $future.poll() {
    Poll::Pending => yield Poll::Pending,
    Poll::Ready(value) => break value,
    }
    }
    }
    +

    在我们的轮询模型中,await 是一种语法糖;它会进入上面这种循环,你要做的就是在循环中轮询,在一段时间内你将一直得到“尚未准备就绪(Pending)”,然后一直等到它再次被唤醒,终于你等待的 Future 完成了,然后你使用该值跳出了循环,这就是这些 await 表达式的计算结果。

    +

    这似乎是一个非常不错的解决方案,async / await 的写法会被编译成我们超棒的零成本的 Future。不过从已发布的 Future 的使用者的反馈看,我们还是发现了一些问题。

    +

    新的问题

    基本上所有试图使用 Future 的人都会遇到非常令人困惑的错误消息。在这些消息中,编译器会提示你的Future的生命周期不是静态的('static)或没有实现某个 trait 等等;这些提示你并不真正理解,但编译器想提出有用的建议,你也就跟着这个建议去做,直到编译成功;你可能会给闭包加上 move 关键字,或者把某些值放到引用计数的指针(Rc)中,然后将复制(clone)它;你将所有这些开销添加到了似乎并不必要的事情上,却不明白为什么要这样做,而当你已经疲于处理这些时,代码已经乱成一锅粥了,所以很多人都被 Future 的问题卡住了。

    +

    CombinatorChain

    +

    而且它对于组合器产生这种非常大的类型也没什么办法,你的整个终端窗口(terminal)将被其中一个组合器链的类型填满。你用了and_then,以及又一个 and_then,然后是 map_err 紧跟一个 TCP 流等等等等,你必须仔细研究一下,才能弄清楚所遇到的实际错误是什么。

    +
    +

    “When using Futures, error messages are inscrutable.”
    “当使用 Future 时,错误信息难以理解。”
    “Having to use RefCell or clone everything for each future leads to overcomplicated code that makes me wish Rust had garbage collection.”
    “不得不使用 RefCell 以及为每个 future 克隆所有它需要的值产生了过于复杂的代码,这让我开始期待 Rust 能具备垃圾回收功能了。”

    +
    +

    我在 reddit 上发现了这条消息,我认为它确实很好地总结了所有有关 Future 的抱怨。使用 Future 时,错误消息难以理解;不得不使用 RefCell 以及为每个 future 克隆所有它需要的值产生了过于复杂的代码,这让我开始期待 Rust 能具备垃圾回收功能了(观众笑)。是的,这不是什么好反馈。

    +

    因此,从大约一年半前的情况来看,很明显,有两个问题需要解决,才能让大家更容易使用。

    +

    首先,我们需要更好的错误消息,最简单的方法就是将语法构建到语言中,然后它们就可以在你所有的诊断和错误处理代码中加入钩子,从而使你能够真正拥有良好的 async / await 的错误消息。其次,人们遇到的大多数错误实际上是因为他们被一个晦涩难解的问题卡住了——借用问题。正是因为 Future 的设计方式存在着这种根本的局限性,导致一些很普通的编程范式都无法表达。所谓借用问题,就是在最初的 Future 的设计中你不能跨过异步等待点(await point)进行借用,也就是说,如果你要异步等待(await)某件事,你就不能在那个时候持有任何存活的引用。当人们遇到了这种问题,他们通常会被卡住,最终他们会尝试在 await 的时候进行借用然后发现实际上做不到。所以如果我们能够使这种借用被允许,那么大多数这些错误将消失,一切都将变得更易于使用,你可以使用 asyncawait 编写普通的 Rust 代码,并且一切都会正常进行。

    +

    这种跨越 await 的借用是非常普遍的,因为 Rust 的 API 天然就具有引用。当你实际编译 Future 时,它必须能够恢复所有状态,而当你拥有某些其它东西的引用时,它们位于同一栈帧中,最终你会得到一个自引用Future 结构(Self-Referential Future)。这是开头的那个 get_user 方法,我们有这样一个 SQL 字符串,而在使用 SQL 字符串调用 query 方法时,我们传递的是 SQL 字符串的引用。

    +
    1
    2
    let sql: String = format!("SELECT FROM users WHERE username = {}", user);
    let db_response = await self.query(&sql);
    +

    这里存在的问题是,对 SQL 字符串的引用是对存储在相同 Future 状态中的其他内容的引用,因此它成为一种自引用结构。

    +

    SelfReferentialFuture

    +

    如果把这个 Future 视作一个真的结构体的话,这就是理论上它所拥有的字段。除了代表数据库句柄的 self 之外,还有 SQL 字符串以及对这个 SQL 字符串的引用,即一个最终指回同一结构体中某个字段的引用。

    +

    一些新的潜在结构会成为非常棘手的问题,因为我们没有通用的解决方案。当你移动该结构时,我们不允许你使用自引用,是因为我们会在新位置创建一个原有结构的新副本,旧副本会变为无效,但是在复制时,新副本的引用仍指向旧副本的字段,该指针就变成了悬空指针(dangling pointer),而这正是 Rust 必须避免的内存问题。

    +

    DanglingPointer

    +

    所以我们不能使用自引用结构,因为如果你移动它们,那么它们将失效。然而,我们实际上并不需要真正移动这些 Future 。如果你还记得在堆中通过句柄使用 Future 的模型,它在反应器和执行器之间来回传递,所以 Future 本身永远不会真正移动;而只要你保证不移动,Future 包含自引用就完全没问题。

    +

    所以我们需要通过采用某种方式在 Future 的 API 中表达 “在你轮询时,你不允许随意移动它” 来解决这个问题。如果我们能够表达这一点,我们就可以允许 Future 中出现自引用,进而就可以在异步函数中真正使用这些引用,并且一切都会正常工作。因此我们研究了这个问题,最终开发出了被称为 Pin 的新 API 。

    +

    Pin

    Pin 是一种围绕其他指针类型的适配器,可以将其它指针变为 固定引用(pinned reference)。除了原有指针的保证外,它还保证这个引用再也不会被移动,所以我们可以确保它将一直处在同一内存位置,直到最终被释放。如果你的 API 中有一些内容已表明必须使用 Pin,那么你就知道了它再也不会被移动,这样就你可以使用那种自引用的结构体了。因此,我们修改了 Future 的工作方式,现在它变成了一个经过装箱(boxed)的 Future ,实际上是一个由 Pin 封装的 Box<Future> ;这样无论在哪里装箱,我们把它放在堆内存中,它可以保证永远不会再移动;而当你轮询 Future 时,不再使用一个普通引用,而是使用一个固定引用,这样 Future 就知道它不会被移动。而使这一切起作用的诀窍是,要从固定引用中获取非固定引用,你只能使用不安全的代码。

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    struct Pin<P>(P);

    impl<T> Pin<Box<T>> {
    fn new(boxed: Box<T>) -> Pin<Box<T>> { ... }
    fn as_mut(self) -> Pin<&mut T> { ... }
    }

    impl<T> Pin<&mut T> {
    unsafe fn get_unchecked_mut(self) -> &mut T { ... }
    }
    +

    所以 Pin API 大致就像这样。在这里你有一个 Pin 结构,它只是另一个指针类型的封装,它没有任何运行时开销或者其它东西,仅仅是将其划分为一个固定的(pinned)对象,然后一个固定的 Box 指针可以转换为一个固定引用,但是将一个固定引用转换为一个非固定引用的唯一方法是使用一个不安全的(unsafe)函数。

    +
    1
    2
    3
    4
    5
    6
    trait Future {
    type Output;

    fn poll(self: Pin<&mut self>, waker: &Waker)
    -> Poll<Self::Output>;
    }
    +

    这样一来,我们要做的就是修改 Future 的 API ,其轮询方法将不再接受一个普通引用,而是接受一个固定引用,而这其实就是我们将要稳定发布的 Future API。而做了这个修改之后,开头示例的写法就能正常工作了。这样你就可以像写阻塞 I/O 的代码那样编写异步 I/O 的代码了,只需要加上 asyncawait 注解,你就能得到这个出色的零成本抽象的异步实现,而即便你自己手写,这基本上也是你能写出的开销最低的实现了。

    +

    async / await 的现状及未来

    目前的情况是,Pinning 大约在一个月前的最新版本中稳定了,我们正在稳定 Future 的 API ,因此大概会在 1.35,也可能会推到 1.36 稳定,基本上在两三个月内就会知道了。并且我们希望今年的某个时候我们能够拥有 async / await,希望今年夏末能将其稳定下来,这样人们就可以使用这种类似阻塞 I/O 的语法编写无阻塞的 I/O 网络服务了。除了这些稳定化工作,我们也已经开始研究某些更长期的功能,比如流(Stream),我认为它可能是异步的下一个大功能。我们知道一个 Future 只产生一个值,而一个流可以异步地产生很多值;异步地产生值本质上就像是一个异步迭代器,你能够在一个流上进行异步的循环;这个功能对于许多用例来说非常重要,比如流式传输HTTP、WebSocket 推送请求之类的东西,不用像我们的 RPC 模型那样发出网络请求然后获得单个响应,而是能够使用请求流和响应流,在两者之间来回调用。

    +

    目前使用异步仍然存在一个限制,即不能在 trait 中使用 async。有许多编译器开发工作正在进行,使其能够支持这个特性。除了这些功能,有时候我们也希望能拥有生成器(Generator),类似于 Python 或 JavaScript 的生成器,除了拥有可以 return 的函数,还能使用可以 yield 的函数,这样你就可以在 yield 之后再次恢复执行;并且你可以将这些函数作为编写迭代器和流的方式,就像异步函数能够让你像编写普通函数那样编写 Future 一样。

    +

    最后,我想回顾一下成就这种零成本异步 I/O 的关键点:首先就是这种基于轮询的 Future,它将这些 Future 编译到这种相当紧凑的状态机中;其次是这种实现 async / await 语法的方式,即因为有了 Pin,我们能够跨越异步等待点使用引用。

    +

    谢谢大家。

    +
    +

    译者注:报告中计划稳定的特性均已稳定发布,参见 areweasyncyet.rs
    按照目前稳定的版本,await 已改为后置运算符 .await,所以本文开头的 get_user 方法应当修改为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let user = db.get_user("withoutboats").await;

    impl Database {
    async fn get_user(&mut self, user: &str) -> User {
    let sql = format!("select FROM users WHERE username = {}", user);
    let db_response = self.query(&sql).await;
    User::from(db_response)
    }
    }

    +
    + + +
    + + + + +
    +
    +
    +
    + + + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/placeholder b/placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..4e57019 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,512 @@ + + + + + https://gteng.org/2024/04/26/zkp-note-0/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2024/05/25/zkp-note-1/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2024/05/30/zkp-note-2/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2021/09/23/redis-basics/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2021/09/26/redis-practices/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2023/03/05/relation-algebra-division/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2021/08/28/search-local-minimum/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2021/09/14/sorting-algorithm-in-rust-std/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2021/09/06/stack-and-queue/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2022/08/13/sync-async-blocking-nonblocking/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2021/01/30/zero-cost-async-io/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2024/12/24/learn-solana-3/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2021/09/06/linked-list-build-reverse/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2021/09/11/merge-sort/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2021/09/06/recursive-time-complexity-formula/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2021/09/13/quick-sort/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2021/08/24/design-pattern-1/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2021/08/24/design-pattern-2/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2021/09/14/design-pattern-3/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2021/08/24/design-pattern-principle/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2024/12/26/dex-dev-0/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2021/08/29/exclusive-or/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2021/10/08/heap-sort/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2021/08/24/how-many-threads/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2024/11/07/learn-index-0/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2024/12/06/learn-solana-0/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2024/12/23/learn-solana-1/ + + 2025-01-02 + + monthly + 0.6 + + + + https://gteng.org/2024/12/23/learn-solana-2/ + + 2025-01-02 + + monthly + 0.6 + + + + + https://gteng.org/ + 2025-01-02 + daily + 1.0 + + + + + https://gteng.org/tags/Design-Pattern/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/solana/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/web3/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/dex/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/amm/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/algorithm/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/xor/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/sort/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/heap/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/%E9%AB%98%E5%B9%B6%E5%8F%91/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/formula/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/index/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/data-structure/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/anchor/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/PDA/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/white-paper/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/PoH/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/PoS/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/linked-list/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/recursive/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/Redis/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/database/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/relation-algebra/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/binary-search/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/Rust/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/queue/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/stack/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/ring-buffer/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/synchronous/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/asynchronous/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/blocking/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/nonblocking/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/Zero-Knowledge-Proof/ + 2025-01-02 + weekly + 0.2 + + + + https://gteng.org/tags/Web3/ + 2025-01-02 + weekly + 0.2 + + + + + \ No newline at end of file diff --git a/tags/Design-Pattern/index.html b/tags/Design-Pattern/index.html new file mode 100644 index 0000000..a5efefd --- /dev/null +++ b/tags/Design-Pattern/index.html @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: Design Pattern | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    Design Pattern + 标签 +

    +
    + + +
    + 2021 +
    + + + + + + + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/PDA/index.html b/tags/PDA/index.html new file mode 100644 index 0000000..7c8a914 --- /dev/null +++ b/tags/PDA/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: PDA | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    PDA + 标签 +

    +
    + + +
    + 2024 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/PoH/index.html b/tags/PoH/index.html new file mode 100644 index 0000000..4e91390 --- /dev/null +++ b/tags/PoH/index.html @@ -0,0 +1,409 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: PoH | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    PoH + 标签 +

    +
    + + +
    + 2024 +
    + + + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/PoS/index.html b/tags/PoS/index.html new file mode 100644 index 0000000..ba9b2ce --- /dev/null +++ b/tags/PoS/index.html @@ -0,0 +1,409 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: PoS | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    PoS + 标签 +

    +
    + + +
    + 2024 +
    + + + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Redis/index.html b/tags/Redis/index.html new file mode 100644 index 0000000..30bf6e8 --- /dev/null +++ b/tags/Redis/index.html @@ -0,0 +1,409 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: Redis | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    Redis + 标签 +

    +
    + + +
    + 2021 +
    + + + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Rust/index.html b/tags/Rust/index.html new file mode 100644 index 0000000..2b0e5f4 --- /dev/null +++ b/tags/Rust/index.html @@ -0,0 +1,409 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: Rust | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    Rust + 标签 +

    +
    + + +
    + 2021 +
    + + + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Web3/index.html b/tags/Web3/index.html new file mode 100644 index 0000000..d2c6546 --- /dev/null +++ b/tags/Web3/index.html @@ -0,0 +1,409 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: Web3 | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    Web3 + 标签 +

    +
    + + +
    + 2024 +
    + + + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Zero-Knowledge-Proof/index.html b/tags/Zero-Knowledge-Proof/index.html new file mode 100644 index 0000000..4151e59 --- /dev/null +++ b/tags/Zero-Knowledge-Proof/index.html @@ -0,0 +1,409 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: Zero-Knowledge Proof | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    Zero-Knowledge Proof + 标签 +

    +
    + + +
    + 2024 +
    + + + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/algorithm/index.html b/tags/algorithm/index.html new file mode 100644 index 0000000..34305b3 --- /dev/null +++ b/tags/algorithm/index.html @@ -0,0 +1,572 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: algorithm | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    algorithm + 标签 +

    +
    + + +
    + 2024 +
    + + +
    + 2021 +
    + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/amm/index.html b/tags/amm/index.html new file mode 100644 index 0000000..334c134 --- /dev/null +++ b/tags/amm/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: amm | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    amm + 标签 +

    +
    + + +
    + 2024 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/anchor/index.html b/tags/anchor/index.html new file mode 100644 index 0000000..ce62e00 --- /dev/null +++ b/tags/anchor/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: anchor | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    anchor + 标签 +

    +
    + + +
    + 2024 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/asynchronous/index.html b/tags/asynchronous/index.html new file mode 100644 index 0000000..f0a93bc --- /dev/null +++ b/tags/asynchronous/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: asynchronous | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    asynchronous + 标签 +

    +
    + + +
    + 2022 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/binary-search/index.html b/tags/binary-search/index.html new file mode 100644 index 0000000..db1255c --- /dev/null +++ b/tags/binary-search/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: binary search | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    binary search + 标签 +

    +
    + + +
    + 2021 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/blocking/index.html b/tags/blocking/index.html new file mode 100644 index 0000000..3224135 --- /dev/null +++ b/tags/blocking/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: blocking | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    blocking + 标签 +

    +
    + + +
    + 2022 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/data-structure/index.html b/tags/data-structure/index.html new file mode 100644 index 0000000..6d332d6 --- /dev/null +++ b/tags/data-structure/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: data structure | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    data structure + 标签 +

    +
    + + +
    + 2024 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/database/index.html b/tags/database/index.html new file mode 100644 index 0000000..feb81f9 --- /dev/null +++ b/tags/database/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: database | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    database + 标签 +

    +
    + + +
    + 2023 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/dex/index.html b/tags/dex/index.html new file mode 100644 index 0000000..cb9a636 --- /dev/null +++ b/tags/dex/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: dex | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    dex + 标签 +

    +
    + + +
    + 2024 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/formula/index.html b/tags/formula/index.html new file mode 100644 index 0000000..7355a84 --- /dev/null +++ b/tags/formula/index.html @@ -0,0 +1,409 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: formula | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    formula + 标签 +

    +
    + + +
    + 2021 +
    + + + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/heap/index.html b/tags/heap/index.html new file mode 100644 index 0000000..3f9cbdb --- /dev/null +++ b/tags/heap/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: heap | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    heap + 标签 +

    +
    + + +
    + 2021 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/index.html b/tags/index.html new file mode 100644 index 0000000..6233c56 --- /dev/null +++ b/tags/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签 | 耿腾的博客 + + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + + + + + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/index/index.html b/tags/index/index.html new file mode 100644 index 0000000..6f15957 --- /dev/null +++ b/tags/index/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: index | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    index + 标签 +

    +
    + + +
    + 2024 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/linked-list/index.html b/tags/linked-list/index.html new file mode 100644 index 0000000..14c8244 --- /dev/null +++ b/tags/linked-list/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: linked list | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    linked list + 标签 +

    +
    + + +
    + 2021 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/nonblocking/index.html b/tags/nonblocking/index.html new file mode 100644 index 0000000..b29811d --- /dev/null +++ b/tags/nonblocking/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: nonblocking | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    nonblocking + 标签 +

    +
    + + +
    + 2022 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/queue/index.html b/tags/queue/index.html new file mode 100644 index 0000000..3eb3fc5 --- /dev/null +++ b/tags/queue/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: queue | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    queue + 标签 +

    +
    + + +
    + 2021 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/recursive/index.html b/tags/recursive/index.html new file mode 100644 index 0000000..3a5cb0d --- /dev/null +++ b/tags/recursive/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: recursive | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    recursive + 标签 +

    +
    + + +
    + 2021 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/relation-algebra/index.html b/tags/relation-algebra/index.html new file mode 100644 index 0000000..c2b7744 --- /dev/null +++ b/tags/relation-algebra/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: relation algebra | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    relation algebra + 标签 +

    +
    + + +
    + 2023 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/ring-buffer/index.html b/tags/ring-buffer/index.html new file mode 100644 index 0000000..9f9b830 --- /dev/null +++ b/tags/ring-buffer/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: ring buffer | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    ring buffer + 标签 +

    +
    + + +
    + 2021 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/solana/index.html b/tags/solana/index.html new file mode 100644 index 0000000..a802a86 --- /dev/null +++ b/tags/solana/index.html @@ -0,0 +1,469 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: solana | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    solana + 标签 +

    +
    + + +
    + 2024 +
    + + + + + + + + + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/sort/index.html b/tags/sort/index.html new file mode 100644 index 0000000..72c57f8 --- /dev/null +++ b/tags/sort/index.html @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: sort | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    sort + 标签 +

    +
    + + +
    + 2021 +
    + + + + + + + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/stack/index.html b/tags/stack/index.html new file mode 100644 index 0000000..358e8fa --- /dev/null +++ b/tags/stack/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: stack | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    stack + 标签 +

    +
    + + +
    + 2021 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/synchronous/index.html b/tags/synchronous/index.html new file mode 100644 index 0000000..f43bbdf --- /dev/null +++ b/tags/synchronous/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: synchronous | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    synchronous + 标签 +

    +
    + + +
    + 2022 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/web3/index.html b/tags/web3/index.html new file mode 100644 index 0000000..f12f254 --- /dev/null +++ b/tags/web3/index.html @@ -0,0 +1,469 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: web3 | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    web3 + 标签 +

    +
    + + +
    + 2024 +
    + + + + + + + + + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/white-paper/index.html b/tags/white-paper/index.html new file mode 100644 index 0000000..6fa3be4 --- /dev/null +++ b/tags/white-paper/index.html @@ -0,0 +1,409 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: white paper | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    white paper + 标签 +

    +
    + + +
    + 2024 +
    + + + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/xor/index.html b/tags/xor/index.html new file mode 100644 index 0000000..1f609aa --- /dev/null +++ b/tags/xor/index.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: xor | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    xor + 标签 +

    +
    + + +
    + 2021 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\345\244\232\347\272\277\347\250\213/index.html" "b/tags/\345\244\232\347\272\277\347\250\213/index.html" new file mode 100644 index 0000000..2f32dd7 --- /dev/null +++ "b/tags/\345\244\232\347\272\277\347\250\213/index.html" @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: 多线程 | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    多线程 + 标签 +

    +
    + + +
    + 2021 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/tags/\351\253\230\345\271\266\345\217\221/index.html" "b/tags/\351\253\230\345\271\266\345\217\221/index.html" new file mode 100644 index 0000000..a470f4b --- /dev/null +++ "b/tags/\351\253\230\345\271\266\345\217\221/index.html" @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签: 高并发 | 耿腾的博客 + + + + + + + + + + + + + + + + +
    +
    + +
    +
    + + +
    + + + +

    耿腾的博客

    + +
    +

    常期望安定,还期望即兴。

    +
    + + +
    + + + + + + + + + +
    +
    + + +
    + + 0% +
    +
    + + +
    +
    +
    + + +
    + + + + + +
    +
    +
    +

    高并发 + 标签 +

    +
    + + +
    + 2021 +
    + + + +
    +
    + + + + + + + + +
    + + + + +
    + + + + + + + + +
    +
    + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +