From 56a271b4337ec2f25f46bc3d79267acb7d62946d Mon Sep 17 00:00:00 2001 From: wangxujian-laixuzhui <33362969+wangxujian-laixuzhui@users.noreply.github.com> Date: Thu, 16 Aug 2018 08:49:34 +0800 Subject: [PATCH 001/136] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=BA=BF=E7=A8=8B?= =?UTF-8?q?=E4=B8=89=E7=A7=8D=E5=AE=9E=E7=8E=B0=E6=96=B9=E5=BC=8F=E7=9A=84?= =?UTF-8?q?=E6=AF=94=E8=BE=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- notes/Java 并发.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/notes/Java 并发.md b/notes/Java 并发.md index 9cd5955b..1a1bacc0 100644 --- a/notes/Java 并发.md +++ b/notes/Java 并发.md @@ -182,7 +182,10 @@ public static void main(String[] args) { mt.start(); } ``` - +## 三种实现方式的比较 +- 实现Runnable接又可以避免Java单继承特性而带来的局限;增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;适合多个相同程序代码的线程区处理同一资源的情况。 +- 继承Thread类和实现Runnable方法启动线 程都是使用start方法,然后JVM虚拟机将此线程放到就绪队列中,如果有处理机可用, 则执行run方法。 +- 实现Callable接又要实现call方法,并且线 程执行完毕后会有返回值。其他的两种都是 重写run方法,没有返回值。 ## 实现接口 VS 继承 Thread 实现接口会更好一些,因为: From c6b517553a562ecec4505f453bff14fd818e3b59 Mon Sep 17 00:00:00 2001 From: CyC2018 <1029579233@qq.com> Date: Fri, 17 Aug 2018 21:19:16 +0800 Subject: [PATCH 002/136] auto commit --- notes/Java 容器.md | 2 +- notes/Java 并发.md | 2 + notes/Leetcode 题解.md | 180 +++++++++++------------ notes/Linux.md | 20 +-- notes/MySQL.md | 314 ++++++++++++++++++++-------------------- notes/Redis.md | 6 +- notes/数据库系统原理.md | 86 +++++------ notes/设计模式.md | 76 +++++----- notes/面向对象思想.md | 15 +- 9 files changed, 349 insertions(+), 352 deletions(-) diff --git a/notes/Java 容器.md b/notes/Java 容器.md index 9959e2ca..001f864c 100644 --- a/notes/Java 容器.md +++ b/notes/Java 容器.md @@ -742,7 +742,7 @@ new capacity : 00100000 对于一个 Key, -- 它的哈希值如果在第 6 位上为 0,那么取模得到的结果和之前一样; +- 它的哈希值如果在第 5 位上为 0,那么取模得到的结果和之前一样; - 如果为 1,那么得到的结果为原来的结果 +16。 ### 7. 扩容-计算数组容量 diff --git a/notes/Java 并发.md b/notes/Java 并发.md index 9cd5955b..d3682d33 100644 --- a/notes/Java 并发.md +++ b/notes/Java 并发.md @@ -168,6 +168,8 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc 同样也是需要实现 run() 方法,因为 Thread 类也实现了 Runable 接口。 +当调用 start() 方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 run() 方法。 + ```java public class MyThread extends Thread { public void run() { diff --git a/notes/Leetcode 题解.md b/notes/Leetcode 题解.md index 44f3d9dc..68af3cb7 100644 --- a/notes/Leetcode 题解.md +++ b/notes/Leetcode 题解.md @@ -2389,8 +2389,6 @@ private void backtracking(int row) {
(JFW|j
z{L9=K(u@@9lj )8qqq{UBW=@h|qSQAun)eBC6V%yk|
zYF-ZG%rj<}-n5UouYU8M&i^&RwgLZyrP}j-$9u~TNqgl$If*vyhGge7`6avD_f5%u
z_E~-I8`By%!J47g-z{?pFJJJX%1N%w(nd;`H!IY%pfxY_=B}ZI6`4OkeS%Q$pr(
z51;z&2v>_xWmyM$rwolJbX>UBR`ETlf+lQfvWuFKRD#udo`4O)%-P*E=aIhn8kV%R
z?jd=8YR>XSm2bEUO(9C6=F~@-r&X|XW5}ZWU3?68q>w{4Jx;ObZ67SKP{je`Onnd6E9u|t
za)aAE%Xm1^D~fi~gW&dlEKV^KP@AOGecmAH=JQrUHu0)#9XqVJVGE*gYYu(qiDX}@
zPLZ{d5Nd@GHP-BGG<_77JKoTu|K^A|PJ1(TINsT8K#QMF2FyaOz`i9Wqh!o5&h-6b
zq9z6#S=fLvWwlztkz6LS;#JNud{)|*=iAa}Av2czzZKS(Z7!fe!da_
z93h7$c7uw(BQ!j<2*7!;xr*U7{P+v^{qD
@@ -96,21 +97,15 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示
一个节点未获得锁,需要监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知(羊群效应),而我们只希望它的后一个子节点收到通知。
-参考:
-
-- [Distributed locks with Redis](https://redis.io/topics/distlock)
-- [浅谈分布式锁](http://www.linkedkeeper.com/detail/blog.action?bid=1023)
-- [基于 Zookeeper 的分布式锁](http://www.dengshenyu.com/java/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/2017/10/23/zookeeper-distributed-lock.html)
-
# 二、分布式事务
-指事务的操作位于不同的节点上,需要保证事务的 AICD 特性。例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。
+指事务的操作位于不同的节点上,需要保证事务的 AICD 特性。
+
+例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。
## 本地消息表
-### 1. 原理
-
-本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性。
+本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。
1. 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
2. 之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。
@@ -118,23 +113,19 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示
-### 2. 分析
-
-本地消息表利用了本地事务来实现分布式事务,并且使用了消息队列来保证最终一致性。
-
## 2PC
两阶段提交(Two-phase Commit,2PC),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。
### 1. 运行过程
-(一)准备阶段
+#### 1.1 准备阶段
协调者询问参与者事务是否执行成功,参与者发回事务执行结果。
-(二)提交阶段
+#### 1.2 提交阶段
如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
@@ -144,28 +135,22 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示
### 2. 存在的问题
-(一)同步阻塞
+#### 2.1 同步阻塞
所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。
-(二)单点问题
+#### 2.2 单点问题
协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。
-(三)数据不一致
+#### 2.3 数据不一致
在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
-(四)太过保守
+#### 2.4 太过保守
任意一个节点失败就会导致整个事务失败,没有完善的容错机制。
-参考:
-
-- [聊聊分布式事务,再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html)
-- [分布式系统的事务处理](https://coolshell.cn/articles/10910.html)
-- [深入理解分布式事务](https://juejin.im/entry/577c6f220a2b5800573492be)
-
# 三、CAP
分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容忍性(P:Partition Tolerance),最多只能同时满足其中两项。
@@ -184,7 +169,7 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示
可用性指分布式系统在面对各种异常时可以提供正常服务的能力,可以用系统可用时间占总时间的比值来衡量,4 个 9 的可用性表示系统 99.99% 的时间是可用的。
-在可用性条件下,系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
+在可用性条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
## 分区容忍性
@@ -198,16 +183,11 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示
可用性和一致性往往是冲突的,很难都使它们同时满足。在多个节点之间进行数据同步时,
-* 为了保证一致性(CP),就需要让所有节点下线成为不可用的状态,等待同步完成;
-* 为了保证可用性(AP),在同步过程中允许读取所有节点的数据,但是数据可能不一致。
+- 为了保证一致性(CP),就需要让所有节点下线成为不可用的状态,等待同步完成;
+- 为了保证可用性(AP),在同步过程中允许读取所有节点的数据,但是数据可能不一致。
-参考:
-
-- 倪超. 从 Paxos 到 ZooKeeper : 分布式一致性原理与实践 [M]. 电子工业出版社, 2015.
-- [What is CAP theorem in distributed database system?](http://www.colooshiki.com/index.php/2017/04/20/what-is-cap-theorem-in-distributed-database-system/)
-
# 四、BASE
BASE 是基本可用(Basically Available)、软状态(Soft State)和最终一致性(Eventually Consistent)三个短语的缩写。
@@ -292,66 +272,69 @@ Acceptor 接收到接受请求时,如果序号大于等于该 Acceptor 承诺
Paxos 协议能够让 Proposer 发送的提议朝着能被大多数 Acceptor 接受的那个提议靠拢,因此能够保证可终止性。
-参考:
+# 六、Raft
-- [NEAT ALGORITHMS - PAXOS](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/)
-- [Paxos By Example](https://angus.nyc/2012/paxos-by-example/)
-
-# 五、Raft
-
-Raft 和 Paxos 类似,但是更容易理解,也更容易实现。
-
-Raft 主要是用来竞选主节点。
+Raft 也是分布式一致性协议,主要是用来竞选主节点。
## 单个 Candidate 的竞选
有三种节点:Follower、Candidate 和 Leader。Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间,一般为 150ms\~300ms,如果在这个时间内没有收到 Leader 的心跳包,就会变成 Candidate,进入竞选阶段。
-* 下图表示一个分布式系统的最初阶段,此时只有 Follower,没有 Leader。Follower A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。
+- 下图展示一个分布式系统的最初阶段,此时只有 Follower 没有 Leader。Node A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。
-* 此时 A 发送投票请求给其它所有节点。
+- 此时 Node A 发送投票请求给其它所有节点。
-* 其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。
+- 其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。
-* 之后 Leader 会周期性地发送心跳包给 Follower,Follower 接收到心跳包,会重新开始计时。
+- 之后 Leader 会周期性地发送心跳包给 Follower,Follower 接收到心跳包,会重新开始计时。
## 多个 Candidate 竞选
-* 如果有多个 Follower 成为 Candidate,并且所获得票数相同,那么就需要重新开始投票,例如下图中 Candidate B 和 Candidate D 都获得两票,因此需要重新开始投票。
+- 如果有多个 Follower 成为 Candidate,并且所获得票数相同,那么就需要重新开始投票。例如下图中 Node B 和 Node D 都获得两票,需要重新开始投票。
-* 当重新开始投票时,由于每个节点设置的随机竞选超时时间不同,因此能下一次再次出现多个 Candidate 并获得同样票数的概率很低。
+- 由于每个节点设置的随机竞选超时时间不同,因此下一次再次出现多个 Candidate 并获得同样票数的概率很低。
-## 日志复制
+## 数据同步
-* 来自客户端的修改都会被传入 Leader。注意该修改还未被提交,只是写入日志中。
+- 来自客户端的修改都会被传入 Leader。注意该修改还未被提交,只是写入日志中。
-* Leader 会把修改复制到所有 Follower。
+- Leader 会把修改复制到所有 Follower。
-* Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。
+- Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。
-* 此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。
+- 此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。
-参考:
+# 参考
+- 倪超. 从 Paxos 到 ZooKeeper : 分布式一致性原理与实践 [M]. 电子工业出版社, 2015.
+- [Distributed locks with Redis](https://redis.io/topics/distlock)
+- [浅谈分布式锁](http://www.linkedkeeper.com/detail/blog.action?bid=1023)
+- [基于 Zookeeper 的分布式锁](http://www.dengshenyu.com/java/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/2017/10/23/zookeeper-distributed-lock.html)
- [Raft: Understandable Distributed Consensus](http://thesecretlivesofdata.com/raft)
+- [聊聊分布式事务,再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html)
+- [分布式系统的事务处理](https://coolshell.cn/articles/10910.html)
+- [深入理解分布式事务](https://juejin.im/entry/577c6f220a2b5800573492be)
+- [What is CAP theorem in distributed database system?](http://www.colooshiki.com/index.php/2017/04/20/what-is-cap-theorem-in-distributed-database-system/)
+- [NEAT ALGORITHMS - PAXOS](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/)
+- [Paxos By Example](https://angus.nyc/2012/paxos-by-example/)
diff --git a/notes/系统设计基础.md b/notes/系统设计基础.md
index 805764a9..b6a3ddf5 100644
--- a/notes/系统设计基础.md
+++ b/notes/系统设计基础.md
@@ -68,7 +68,7 @@
应用服务器只要不具有状态,那么就可以很容易地通过负载均衡器向集群中添加新的服务器。
-关系型数据库的伸缩性通过 Sharding 来实现,将数据按一定的规则分布到不同的节点上,从而解决单台存储服务器存储空间限制。
+关系型数据库的伸缩性通过 Sharding 来实现,将数据按一定的规则分布到不同的节点上,从而解决单台存储服务器的存储空间限制。
对于非关系型数据库,它们天生就是为海量数据而诞生,对伸缩性的支持特别好。
@@ -78,7 +78,7 @@
实现可扩展主要有两种方式:
-- 使用消息队列进行解耦,应用之间通过消息传递的方式进行通信;
+- 使用消息队列进行解耦,应用之间通过消息传递进行通信;
- 使用分布式服务将业务和可复用的服务分离开来,业务使用分布式服务框架调用可复用的服务。新增的产品可以通过调用可复用的服务来实现业务逻辑,对其它产品没有影响。
# 四、可用性
@@ -87,7 +87,7 @@
保证高可用的主要手段是使用冗余,当某个服务器故障时就请求其它服务器。
-应用服务器的冗余比较容易实现,只要保证应用服务器不具有状态,那么某个应用服务器故障时,负载均衡器将该应用服务器原先的用户请求转发到另一个应用服务器上不会对用户有任何影响。
+应用服务器的冗余比较容易实现,只要保证应用服务器不具有状态,那么某个应用服务器故障时,负载均衡器将该应用服务器原先的用户请求转发到另一个应用服务器上,不会对用户有任何影响。
存储服务器的冗余需要使用主从复制来实现,当主服务器故障时,需要提升从服务器为主服务器,这个过程称为切换。
@@ -97,7 +97,7 @@
## 服务降级
-服务器降级是系统为了应对大量的请求,主动关闭部分功能,从而保证核心功能可用。
+服务降级是系统为了应对大量的请求,主动关闭部分功能,从而保证核心功能可用。
# 五、安全性
From 762c3c698c936492dbc03dec7bf821da4ad5364b Mon Sep 17 00:00:00 2001
From: CyC2018 <1029579233@qq.com>
Date: Thu, 23 Aug 2018 21:37:51 +0800
Subject: [PATCH 018/136] auto commit
---
notes/Redis.md | 2 +-
notes/剑指 offer 题解.md | 2 +-
notes/构建工具.md | 2 +-
notes/算法.md | 10 +++++-----
4 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/notes/Redis.md b/notes/Redis.md
index 8f231382..629fcdd8 100644
--- a/notes/Redis.md
+++ b/notes/Redis.md
@@ -549,7 +549,7 @@ def main():
# 十二、Sentinel
-Sentinel(哨兵)可以监听主服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。
+Sentinel(哨兵)可以监听集群中的服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。
# 十三、分片
diff --git a/notes/剑指 offer 题解.md b/notes/剑指 offer 题解.md
index 1bac6bbb..72c20173 100644
--- a/notes/剑指 offer 题解.md
+++ b/notes/剑指 offer 题解.md
@@ -1082,7 +1082,7 @@ false
```java
public boolean isNumeric(char[] str) {
- if (str == null)
+ if (str == null || str.length == 0)
return false;
return new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?");
}
diff --git a/notes/构建工具.md b/notes/构建工具.md
index c0e4acd5..ce157934 100644
--- a/notes/构建工具.md
+++ b/notes/构建工具.md
@@ -15,7 +15,7 @@
## 运行单元测试
-不再需要在项目代码中添加测试代码,从而污染项目代码。
+不再需要在项目代码中添加测试代码,从而避免了污染项目代码。
## 将源代码转化为可执行文件
diff --git a/notes/算法.md b/notes/算法.md
index 25d1550e..a9f9e689 100644
--- a/notes/算法.md
+++ b/notes/算法.md
@@ -23,7 +23,7 @@
* [五、栈和队列](#五栈和队列)
* [栈](#栈)
* [队列](#队列)
-* [六、查找](#六查找)
+* [六、符号表](#六符号表)
* [初级实现](#初级实现)
* [二叉查找树](#二叉查找树)
* [2-3 查找树](#2-3-查找树)
@@ -1290,7 +1290,7 @@ public class ListQueue
From 4f3fa83fc27e18a371b5940e937cdc34c0ef0bea Mon Sep 17 00:00:00 2001
From: CyC2018 <1029579233@qq.com>
Date: Sat, 25 Aug 2018 23:12:21 +0800
Subject: [PATCH 020/136] auto commit
---
notes/Docker.md | 11 +--
notes/Java 基础.md | 2 +-
notes/Java 容器.md | 2 +-
notes/Java 并发.md | 4 +-
notes/Java 虚拟机.md | 2 +-
notes/Leetcode 题解.md | 189 ++++++++++++++++++---------------------
notes/Linux.md | 26 +++---
notes/Redis.md | 2 +-
notes/分布式.md | 105 +++++++++-------------
notes/剑指 offer 题解.md | 14 ++-
notes/攻击技术.md | 2 +-
notes/构建工具.md | 2 +-
notes/消息队列.md | 13 +--
notes/算法.md | 14 +--
notes/系统设计基础.md | 8 +-
notes/缓存.md | 30 ++-----
notes/设计模式.md | 76 ++++++++--------
notes/面向对象思想.md | 15 ++--
18 files changed, 248 insertions(+), 269 deletions(-)
diff --git a/notes/Docker.md b/notes/Docker.md
index f230f762..80108896 100644
--- a/notes/Docker.md
+++ b/notes/Docker.md
@@ -30,15 +30,15 @@ Docker 主要解决环境配置问题,它是一种虚拟化技术,对进程
## 启动速度
-启动虚拟机需要启动虚拟机的操作系统,再启动相应的应用,这个过程会非常慢;
+启动虚拟机需要启动虚拟机的操作系统,再启动应用,这个过程非常慢;
而启动 Docker 相当于启动宿主操作系统上的一个进程。
## 占用资源
-虚拟机是一个完整的操作系统,需要占用大量的磁盘空间、内存和 CPU,一台机器只能开启几十个的虚拟机。
+虚拟机是一个完整的操作系统,需要占用大量的磁盘、内存和 CPU,一台机器只能开启几十个的虚拟机。
-而 Docker 只是一个进程,只需要将应用以及相应的组件打包,在运行时占用很少的资源,一台机器可以开启成千上万个 Docker。
+而 Docker 只是一个进程,只需要将应用以及相关的组件打包,在运行时占用很少的资源,一台机器可以开启成千上万个 Docker。
参考资料:
@@ -58,7 +58,7 @@ Docker 使用分层技术和镜像,使得应用可以更容易复用重复部
## 更容易扩展
-可以使用基础镜像进一步扩展得到新的镜像,并且官方和开源社区提供了大量的镜像,通过扩展这些镜像得到我们想要的镜像非常容易。
+可以使用基础镜像进一步扩展得到新的镜像,并且官方和开源社区提供了大量的镜像,通过扩展这些镜像可以非常容易得到我们想要的镜像。
参考资料:
@@ -91,7 +91,7 @@ Docker 轻量级的特点使得它很适合用于部署、维护、组合微服
镜像包含着容器运行时所需要的代码以及其它组件,它是一种分层结构,每一层都是只读的(read-only layers)。构建镜像时,会一层一层构建,前一层是后一层的基础。镜像的这种分层存储结构很适合镜像的复用以及定制。
-在构建容器时,通过在镜像的基础上添加一个可写层(writable layer),用来保存着容器运行过程中的修改。
+构建容器时,通过在镜像的基础上添加一个可写层(writable layer),用来保存着容器运行过程中的修改。
@@ -100,3 +100,4 @@ Docker 轻量级的特点使得它很适合用于部署、维护、组合微服
- [How to Create Docker Container using Dockerfile](https://linoxide.com/linux-how-to/dockerfile-create-docker-container/)
- [理解 Docker(2):Docker 镜像](http://www.cnblogs.com/sammyliu/p/5877964.html)
+
diff --git a/notes/Java 基础.md b/notes/Java 基础.md
index b329e0bc..44ca873a 100644
--- a/notes/Java 基础.md
+++ b/notes/Java 基础.md
@@ -707,7 +707,7 @@ public class EqualExample {
## hashCode()
-hasCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。
+hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。
在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。
diff --git a/notes/Java 容器.md b/notes/Java 容器.md
index 9959e2ca..001f864c 100644
--- a/notes/Java 容器.md
+++ b/notes/Java 容器.md
@@ -742,7 +742,7 @@ new capacity : 00100000
对于一个 Key,
-- 它的哈希值如果在第 6 位上为 0,那么取模得到的结果和之前一样;
+- 它的哈希值如果在第 5 位上为 0,那么取模得到的结果和之前一样;
- 如果为 1,那么得到的结果为原来的结果 +16。
### 7. 扩容-计算数组容量
diff --git a/notes/Java 并发.md b/notes/Java 并发.md
index 9cd5955b..1403f764 100644
--- a/notes/Java 并发.md
+++ b/notes/Java 并发.md
@@ -168,6 +168,8 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc
同样也是需要实现 run() 方法,因为 Thread 类也实现了 Runable 接口。
+当调用 start() 方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 run() 方法。
+
```java
public class MyThread extends Thread {
public void run() {
@@ -1018,7 +1020,7 @@ ForkJoinPool 实现了工作窃取算法来提高 CPU 的利用率。每个线
如果多个线程对同一个共享数据进行访问而不采取同步操作的话,那么操作的结果是不一致的。
-以下代码演示了 1000 个线程同时对 cnt 执行自增操作,操作结束之后它的值为 997 而不是 1000。
+以下代码演示了 1000 个线程同时对 cnt 执行自增操作,操作结束之后它的值有可能小于 1000。
```java
public class ThreadUnsafeExample {
diff --git a/notes/Java 虚拟机.md b/notes/Java 虚拟机.md
index a4a4ba12..1bba557b 100644
--- a/notes/Java 虚拟机.md
+++ b/notes/Java 虚拟机.md
@@ -92,7 +92,7 @@ java -Xms1M -Xmx2M HackTheJava
对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。
-JDK 1.7 之前,HotSpot 虚拟机把它当成永久代来进行垃圾回收。但是从 JDK 1.7 开始,已经把原本放在永久代的字符串常量池移到 Native Method 中。
+HotSpot 虚拟机把它当成永久代来进行垃圾回收。但是很难确定永久代的大小,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。
## 运行时常量池
diff --git a/notes/Leetcode 题解.md b/notes/Leetcode 题解.md
index 44f3d9dc..04a751c7 100644
--- a/notes/Leetcode 题解.md
+++ b/notes/Leetcode 题解.md
@@ -2389,8 +2389,6 @@ private void backtracking(int row) {
-dp[N] 即为所求。
-
考虑到 dp[i] 只与 dp[i - 1] 和 dp[i - 2] 有关,因此可以只用两个变量来存储 dp[i - 1] 和 dp[i - 2],使得原来的 O(N) 空间复杂度优化为 O(1) 复杂度。
```java
@@ -2468,16 +2466,6 @@ private int rob(int[] nums, int first, int last) {
}
```
-**母牛生产**
-
-[程序员代码面试指南-P181](#)
-
-题目描述:假设农场中成熟的母牛每年都会生 1 头小母牛,并且永远不会死。第一年有 1 只小母牛,从第二年开始,母牛开始生小母牛。每只小母牛 3 年之后成熟又可以生小母牛。给定整数 N,求 N 年后牛的数量。
-
-第 i 年成熟的牛的数量为:
-
-
-
**信件错排**
题目描述:有 N 个 信 和 信封,它们被打乱,求错误装信方式的数量。
@@ -2491,7 +2479,15 @@ private int rob(int[] nums, int first, int last) {
-dp[N] 即为所求。
+**母牛生产**
+
+[程序员代码面试指南-P181](#)
+
+题目描述:假设农场中成熟的母牛每年都会生 1 头小母牛,并且永远不会死。第一年有 1 只小母牛,从第二年开始,母牛开始生小母牛。每只小母牛 3 年之后成熟又可以生小母牛。给定整数 N,求 N 年后牛的数量。
+
+第 i 年成熟的牛的数量为:
+
+
### 矩阵路径
@@ -2517,10 +2513,8 @@ public int minPathSum(int[][] grid) {
int[] dp = new int[n];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
- if (j == 0) {
- dp[j] = dp[j]; // 只能从上侧走到该位置
- } else if (i == 0) {
- dp[j] = dp[j - 1]; // 只能从左侧走到该位置
+ if (i == 0) {
+ dp[j] = dp[j - 1];
} else {
dp[j] = Math.min(dp[j - 1], dp[j]);
}
@@ -2584,17 +2578,18 @@ sumRange(0, 5) -> -3
```java
class NumArray {
+
private int[] sums;
public NumArray(int[] nums) {
- sums = new int[nums.length];
- for (int i = 0; i < nums.length; i++) {
- sums[i] = i == 0 ? nums[0] : sums[i - 1] + nums[i];
+ sums = new int[nums.length + 1];
+ for (int i = 1; i <= nums.length; i++) {
+ sums[i] = sums[i - 1] + nums[i - 1];
}
}
public int sumRange(int i, int j) {
- return i == 0 ? sums[j] : sums[j] - sums[i - 1];
+ return sums[j + 1] - sums[i];
}
}
```
@@ -2634,7 +2629,7 @@ return: 3, for 3 arithmetic slices in A: [1, 2, 3], [2, 3, 4] and [1, 2, 3, 4] i
dp[i] 表示以 A[i] 为结尾的等差递增子区间的个数。
-如果 A[i] - A[i - 1] == A[i - 1] - A[i - 2],表示 [A[i - 2], A[i - 1], A[i]] 是一个等差递增子区间。如果 [A[i - 3], A[i - 2], A[i - 1]] 是一个等差递增子区间,那么 [A[i - 3], A[i - 2], A[i - 1], A[i]] 也是。因此在这个条件下,dp[i] = dp[i-1] + 1。
+在 A[i] - A[i - 1] == A[i - 1] - A[i - 2] 的条件下,{A[i - 2], A[i - 1], A[i]} 是一个等差递增子区间。如果 {A[i - 3], A[i - 2], A[i - 1]} 是一个等差递增子区间,那么 {A[i - 3], A[i - 2], A[i - 1], A[i]} 也是等差递增子区间,dp[i] = dp[i-1] + 1。
```java
public int numberOfArithmeticSlices(int[] A) {
@@ -2747,17 +2742,17 @@ public int numDecodings(String s) {
### 最长递增子序列
-已知一个序列 {S1, S2,...,Sn} ,取出若干数组成新的序列 {Si1, Si2,..., Sim},其中 i1、i2 ... im 保持递增,即新序列中各个数仍然保持原数列中的先后顺序,称新序列为原序列的一个 **子序列** 。
+已知一个序列 {S1, S2,...,Sn},取出若干数组成新的序列 {Si1, Si2,..., Sim},其中 i1、i2 ... im 保持递增,即新序列中各个数仍然保持原数列中的先后顺序,称新序列为原序列的一个 **子序列** 。
如果在子序列中,当下标 ix > iy 时,Six > Siy,称子序列为原序列的一个 **递增子序列** 。
-定义一个数组 dp 存储最长递增子序列的长度,dp[n] 表示以 Sn 结尾的序列的最长递增子序列长度。对于一个递增子序列 {Si1, Si2,...,Sim},如果 im < n 并且 Sim < Sn ,此时 {Si1, Si2,..., Sim, Sn} 为一个递增子序列,递增子序列的长度增加 1。满足上述条件的递增子序列中,长度最长的那个递增子序列就是要找的,在长度最长的递增子序列上加上 Sn 就构成了以 Sn 为结尾的最长递增子序列。因此 dp[n] = max{ dp[i]+1 | Si < Sn && i < n} 。
+定义一个数组 dp 存储最长递增子序列的长度,dp[n] 表示以 Sn 结尾的序列的最长递增子序列长度。对于一个递增子序列 {Si1, Si2,...,Sim},如果 im < n 并且 Sim < Sn,此时 {Si1, Si2,..., Sim, Sn} 为一个递增子序列,递增子序列的长度增加 1。满足上述条件的递增子序列中,长度最长的那个递增子序列就是要找的,在长度最长的递增子序列上加上 Sn 就构成了以 Sn 为结尾的最长递增子序列。因此 dp[n] = max{ dp[i]+1 | Si < Sn && i < n} 。
因为在求 dp[n] 时可能无法找到一个满足条件的递增子序列,此时 {Sn} 就构成了递增子序列,需要对前面的求解方程做修改,令 dp[n] 最小为 1,即:
-对于一个长度为 N 的序列,最长递增子序列并不一定会以 SN 为结尾,因此 dp[N] 不是序列的最长递增子序列的长度,需要遍历 dp 数组找出最大值才是所要的结果,即 max{ dp[i] | 1 <= i <= N} 即为所求。
+对于一个长度为 N 的序列,最长递增子序列并不一定会以 SN 为结尾,因此 dp[N] 不是序列的最长递增子序列的长度,需要遍历 dp 数组找出最大值才是所要的结果,max{ dp[i] | 1 <= i <= N} 即为所求。
**最长递增子序列**
@@ -2790,7 +2785,7 @@ for (int i = 0; i < n; i++) {
return ret;
```
-以上解法的时间复杂度为 O(N2) ,可以使用二分查找将时间复杂度降低为 O(NlogN)。
+以上解法的时间复杂度为 O(N2),可以使用二分查找将时间复杂度降低为 O(NlogN)。
定义一个 tails 数组,其中 tails[i] 存储长度为 i + 1 的最长递增子序列的最后一个元素。对于一个元素 x,
@@ -2915,19 +2910,19 @@ public int wiggleMaxLength(int[] nums) {
定义一个二维数组 dp 用来存储最长公共子序列的长度,其中 dp[i][j] 表示 S1 的前 i 个字符与 S2 的前 j 个字符最长公共子序列的长度。考虑 S1i 与 S2j 值是否相等,分为两种情况:
-- 当 S1i==S2j 时,那么就能在 S1 的前 i-1 个字符与 S2 的前 j-1 个字符最长公共子序列的基础上再加上 S1i 这个值,最长公共子序列长度加 1 ,即 dp[i][j] = dp[i-1][j-1] + 1。
-- 当 S1i != S2j 时,此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前 j 个字符最长公共子序列,与 S1 的前 i 个字符和 S2 的前 j-1 个字符最长公共子序列,它们的最大者,即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。
+- 当 S1i==S2j 时,那么就能在 S1 的前 i-1 个字符与 S2 的前 j-1 个字符最长公共子序列的基础上再加上 S1i 这个值,最长公共子序列长度加 1,即 dp[i][j] = dp[i-1][j-1] + 1。
+- 当 S1i != S2j 时,此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前 j 个字符最长公共子序列,或者 S1 的前 i 个字符和 S2 的前 j-1 个字符最长公共子序列,取它们的最大者,即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。
综上,最长公共子序列的状态转移方程为:
-对于长度为 N 的序列 S1 和 长度为 M 的序列 S2,dp[N][M] 就是序列 S1 和序列 S2 的最长公共子序列长度。
+对于长度为 N 的序列 S1 和长度为 M 的序列 S2,dp[N][M] 就是序列 S1 和序列 S2 的最长公共子序列长度。
与最长递增子序列相比,最长公共子序列有以下不同点:
- 针对的是两个序列,求它们的最长公共子序列。
-- 在最长递增子序列中,dp[i] 表示以 Si 为结尾的最长递增子序列长度,子序列必须包含 Si ;在最长公共子序列中,dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度,不一定包含 S1i 和 S2j 。
+- 在最长递增子序列中,dp[i] 表示以 Si 为结尾的最长递增子序列长度,子序列必须包含 Si ;在最长公共子序列中,dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度,不一定包含 S1i 和 S2j。
- 在求最终解时,最长公共子序列中 dp[N][M] 就是最终解,而最长递增子序列中 dp[N] 不是最终解,因为以 SN 为结尾的最长递增子序列不一定是整个序列最长递增子序列,需要遍历一遍 dp 数组找到最大者。
```java
@@ -2956,9 +2951,7 @@ public int lengthOfLCS(int[] nums1, int[] nums2) {
- 第 i 件物品没添加到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值,dp[i][j] = dp[i-1][j]。
- 第 i 件物品添加到背包中,dp[i][j] = dp[i-1][j-w] + v。
-第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。
-
-综上,0-1 背包的状态转移方程为:
+第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。因此,0-1 背包的状态转移方程为:
@@ -2981,11 +2974,11 @@ public int knapsack(int W, int N, int[] weights, int[] values) {
**空间优化**
-在程序实现时可以对 0-1 背包做优化。观察状态转移方程可以知道,前 i 件物品的状态仅由前 i-1 件物品的状态有关,因此可以将 dp 定义为一维数组,其中 dp[j] 既可以表示 dp[i-1][j] 也可以表示 dp[i][j]。此时,
+在程序实现时可以对 0-1 背包做优化。观察状态转移方程可以知道,前 i 件物品的状态仅与前 i-1 件物品的状态有关,因此可以将 dp 定义为一维数组,其中 dp[j] 既可以表示 dp[i-1][j] 也可以表示 dp[i][j]。此时,
-因为 dp[j-w] 表示 dp[i-1][j-w],因此不能先求 dp[i][j-w],以防止将 dp[i-1][j-w] 覆盖。也就是说要先计算 dp[i][j] 再计算 dp[i][j-w],在程序实现时需要按倒序来循环求解。
+因为 dp[j-w] 表示 dp[i-1][j-w],因此不能先求 dp[i][j-w],以防将 dp[i-1][j-w] 覆盖。也就是说要先计算 dp[i][j] 再计算 dp[i][j-w],在程序实现时需要按倒序来循环求解。
```java
public int knapsack(int W, int N, int[] weights, int[] values) {
@@ -3205,7 +3198,7 @@ public int findMaxForm(String[] strs, int m, int n) {
}
```
-**找零钱的方法数**
+**找零钱的最少硬币数**
[322. Coin Change (Medium)](https://leetcode.com/problems/coin-change/description/)
@@ -3286,60 +3279,6 @@ public int combinationSum4(int[] nums, int target) {
}
```
-**只能进行 k 次的股票交易**
-
-[188. Best Time to Buy and Sell Stock IV (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/description/)
-
-```java
-public int maxProfit(int k, int[] prices) {
- int n = prices.length;
- if (k >= n / 2) { // 这种情况下该问题退化为普通的股票交易问题
- int maxProfit = 0;
- for (int i = 1; i < n; i++) {
- if (prices[i] > prices[i - 1]) {
- maxProfit += prices[i] - prices[i - 1];
- }
- }
- return maxProfit;
- }
- int[][] maxProfit = new int[k + 1][n];
- for (int i = 1; i <= k; i++) {
- int localMax = maxProfit[i - 1][0] - prices[0];
- for (int j = 1; j < n; j++) {
- maxProfit[i][j] = Math.max(maxProfit[i][j - 1], prices[j] + localMax);
- localMax = Math.max(localMax, maxProfit[i - 1][j] - prices[j]);
- }
- }
- return maxProfit[k][n - 1];
-}
-```
-
-**只能进行两次的股票交易**
-
-[123. Best Time to Buy and Sell Stock III (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/description/)
-
-```java
-public int maxProfit(int[] prices) {
- int firstBuy = Integer.MIN_VALUE, firstSell = 0;
- int secondBuy = Integer.MIN_VALUE, secondSell = 0;
- for (int curPrice : prices) {
- if (firstBuy < -curPrice) {
- firstBuy = -curPrice;
- }
- if (firstSell < firstBuy + curPrice) {
- firstSell = firstBuy + curPrice;
- }
- if (secondBuy < firstSell - curPrice) {
- secondBuy = firstSell - curPrice;
- }
- if (secondSell < secondBuy + curPrice) {
- secondSell = secondBuy + curPrice;
- }
- }
- return secondSell;
-}
-```
-
### 股票交易
**需要冷却期的股票交易**
@@ -3432,6 +3371,60 @@ public int maxProfit(int[] prices) {
}
```
+**只能进行两次的股票交易**
+
+[123. Best Time to Buy and Sell Stock III (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/description/)
+
+```java
+public int maxProfit(int[] prices) {
+ int firstBuy = Integer.MIN_VALUE, firstSell = 0;
+ int secondBuy = Integer.MIN_VALUE, secondSell = 0;
+ for (int curPrice : prices) {
+ if (firstBuy < -curPrice) {
+ firstBuy = -curPrice;
+ }
+ if (firstSell < firstBuy + curPrice) {
+ firstSell = firstBuy + curPrice;
+ }
+ if (secondBuy < firstSell - curPrice) {
+ secondBuy = firstSell - curPrice;
+ }
+ if (secondSell < secondBuy + curPrice) {
+ secondSell = secondBuy + curPrice;
+ }
+ }
+ return secondSell;
+}
+```
+
+**只能进行 k 次的股票交易**
+
+[188. Best Time to Buy and Sell Stock IV (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/description/)
+
+```java
+public int maxProfit(int k, int[] prices) {
+ int n = prices.length;
+ if (k >= n / 2) { // 这种情况下该问题退化为普通的股票交易问题
+ int maxProfit = 0;
+ for (int i = 1; i < n; i++) {
+ if (prices[i] > prices[i - 1]) {
+ maxProfit += prices[i] - prices[i - 1];
+ }
+ }
+ return maxProfit;
+ }
+ int[][] maxProfit = new int[k + 1][n];
+ for (int i = 1; i <= k; i++) {
+ int localMax = maxProfit[i - 1][0] - prices[0];
+ for (int j = 1; j < n; j++) {
+ maxProfit[i][j] = Math.max(maxProfit[i][j - 1], prices[j] + localMax);
+ localMax = Math.max(localMax, maxProfit[i - 1][j] - prices[j]);
+ }
+ }
+ return maxProfit[k][n - 1];
+}
+```
+
### 字符串编辑
**删除两个字符串的字符使它们相等**
@@ -3450,11 +3443,8 @@ Explanation: You need one step to make "sea" to "ea" and another step to make "e
public int minDistance(String word1, String word2) {
int m = word1.length(), n = word2.length();
int[][] dp = new int[m + 1][n + 1];
- for (int i = 0; i <= m; i++) {
- for (int j = 0; j <= n; j++) {
- if (i == 0 || j == 0) {
- continue;
- }
+ for (int i = 1; i <= m; i++) {
+ for (int j = 1; j <= n; j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
@@ -3612,7 +3602,7 @@ public int countPrimes(int n) {
```java
int gcd(int a, int b) {
- return b == 0 ? a : gcd(b, a% b);
+ return b == 0 ? a : gcd(b, a % b);
}
```
@@ -3683,7 +3673,7 @@ public String convertToBase7(int num) {
}
```
-Java 中 static String toString(int num, int radix) 可以将一个整数转换为 redix 进制表示的字符串。
+Java 中 static String toString(int num, int radix) 可以将一个整数转换为 radix 进制表示的字符串。
```java
public String convertToBase7(int num) {
@@ -6413,7 +6403,6 @@ public int maxChunksToSorted(int[] arr) {
}
```
-
## 图
### 二分图
@@ -6630,6 +6619,7 @@ public int[] findRedundantConnection(int[][] edges) {
}
private class UF {
+
private int[] id;
UF(int N) {
@@ -6675,7 +6665,7 @@ x ^ x = 0 x & x = x x | x = x
```
- 利用 x ^ 1s = \~x 的特点,可以将位级表示翻转;利用 x ^ x = 0 的特点,可以将三个数中重复的两个数去除,只留下另一个数。
-- 利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。一个数 num 与 mask :00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位。
+- 利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。一个数 num 与 mask:00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位。
- 利用 x | 0s = x 和 x | 1s = 1s 的特点,可以实现设值操作。一个数 num 与 mask:00111100 进行位或操作,将 num 中与 mask 的 1 部分相对应的位都设置为 1。
位与运算技巧:
@@ -6920,7 +6910,6 @@ public boolean isPowerOfFour(int num) {
}
```
-
**判断一个数的位级表示是否不会出现连续的 0 和 1**
[693. Binary Number with Alternating Bits (Easy)](https://leetcode.com/problems/binary-number-with-alternating-bits/description/)
diff --git a/notes/Linux.md b/notes/Linux.md
index 14d3b317..73a2e9ec 100644
--- a/notes/Linux.md
+++ b/notes/Linux.md
@@ -129,15 +129,16 @@ info 与 man 类似,但是 info 将文档分成一个个页面,每个页面
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
```
-env 命令可以获取当前终端的环境变量。
-
## sudo
sudo 允许一般用户使用 root 可执行的命令,不过只有在 /etc/sudoers 配置文件中添加的用户才能使用该指令。
## 包管理工具
-RPM 和 DPKG 为最常见的两类软件包管理工具。RPM 全称为 Redhat Package Manager,最早由 Red Hat 公司制定实施,随后被 GNU 开源操作系统接受并成为很多 Linux 系统 (RHEL) 的既定软件标准。与 RPM 进行竞争的是基于 Debian 操作系统 (UBUNTU) 的 DEB 软件包管理工具 DPKG,全称为 Debian Package,功能方面与 RPM 相似。
+RPM 和 DPKG 为最常见的两类软件包管理工具:
+
+- RPM 全称为 Redhat Package Manager,最早由 Red Hat 公司制定实施,随后被 GNU 开源操作系统接受并成为很多 Linux 系统 (RHEL) 的既定软件标准。
+- 与 RPM 进行竞争的是基于 Debian 操作系统 (Ubuntu) 的 DEB 软件包管理工具 DPKG,全称为 Debian Package,功能方面与 RPM 相似。
YUM 基于 RPM,具有依赖管理功能,并具有软件升级的功能。
@@ -194,13 +195,13 @@ IDE(ATA)全称 Advanced Technology Attachment,接口速度最大为 133MB/
### 2. SATA
-SATA 全称 Serial ATA,也就是使用串口的 ATA 接口,因抗干扰性强,且对数据线的长度要求比 ATA 低很多,支持热插拔等功能,SATA-II 的接口速度为 300MiB/s,而新的 SATA-III 标准可达到 600MiB/s 的传输速度。SATA 的数据线也比 ATA 的细得多,有利于机箱内的空气流通,整理线材也比较方便。
+SATA 全称 Serial ATA,也就是使用串口的 ATA 接口,抗干扰性强,且对数据线的长度要求比 ATA 低很多,支持热插拔等功能。SATA-II 的接口速度为 300MiB/s,而新的 SATA-III 标准可达到 600MiB/s 的传输速度。SATA 的数据线也比 ATA 的细得多,有利于机箱内的空气流通,整理线材也比较方便。
### 3. SCSI
-SCSI 全称是 Small Computer System Interface(小型机系统接口),经历多代的发展,从早期的 SCSI-II,到目前的 Ultra320 SCSI 以及 Fiber-Channel(光纤通道),接口型式也多种多样。SCSI 硬盘广为工作站级个人电脑以及服务器所使用,因此会使用较为先进的技术,如碟片转速 15000rpm 的高转速,且资料传输时 CPU 占用率较低,但是单价也比相同容量的 ATA 及 SATA 硬盘更加昂贵。
+SCSI 全称是 Small Computer System Interface(小型机系统接口),经历多代的发展,从早期的 SCSI-II 到目前的 Ultra320 SCSI 以及 Fiber-Channel(光纤通道),接口型式也多种多样。SCSI 硬盘广为工作站级个人电脑以及服务器所使用,因此会使用较为先进的技术,如碟片转速 15000rpm 的高转速,且传输时 CPU 占用率较低,但是单价也比相同容量的 ATA 及 SATA 硬盘更加昂贵。
@@ -229,7 +230,7 @@ Linux 中每个硬件都被当做一个文件,包括磁盘。磁盘以磁盘
MBR 中,第一个扇区最重要,里面有主要开机记录(Master boot record, MBR)及分区表(partition table),其中主要开机记录占 446 bytes,分区表占 64 bytes。
-分区表只有 64 bytes,最多只能存储 4 个分区,这 4 个分区为主分区(Primary)和扩展分区(Extended)。其中扩展分区只有一个,它将其它扇区用来记录分区表,因此通过扩展分区可以分出更多分区,这些分区称为逻辑分区。
+分区表只有 64 bytes,最多只能存储 4 个分区,这 4 个分区为主分区(Primary)和扩展分区(Extended)。其中扩展分区只有一个,它使用其它扇区用记录额外的分区表,因此通过扩展分区可以分出更多分区,这些分区称为逻辑分区。
Linux 也把分区当成文件,分区文件的命名方式为:磁盘文件名 + 编号,例如 /dev/sda1。注意,逻辑分区的编号从 5 开始。
@@ -251,10 +252,10 @@ MBR 不支持 2.2 TB 以上的硬盘,GPT 则最多支持到 233 TB
BIOS(Basic Input/Output System,基本输入输出系统),它是一个固件(嵌入在硬件中的软件),BIOS 程序存放在断电后内容不会丢失的只读内存中。
-BIOS 是开机的时候计算机执行的第一个程序,这个程序知道可以开机的磁盘,并读取磁盘第一个扇区的主要开机记录(MBR),由主要开机记录(MBR)执行其中的开机管理程序,这个开机管理程序会加载操作系统的核心文件。
-
+BIOS 是开机的时候计算机执行的第一个程序,这个程序知道可以开机的磁盘,并读取磁盘第一个扇区的主要开机记录(MBR),由主要开机记录(MBR)执行其中的开机管理程序,这个开机管理程序会加载操作系统的核心文件。
+
主要开机记录(MBR)中的开机管理程序提供以下功能:选单、载入核心文件以及转交其它开机管理程序。转交这个功能可以用来实现了多重引导,只需要将另一个操作系统的开机管理程序安装在其它分区的启动扇区上,在启动开机管理程序时,就可以通过选单选择启动当前的操作系统或者转交给其它开机管理程序从而启动另一个操作系统。
下图中,第一扇区的主要开机记录(MBR)中的开机管理程序提供了两个选单:M1、M2,M1 指向了 Windows 操作系统,而 M2 指向其它分区的启动扇区,里面包含了另外一个开机管理程序,提供了一个指向 Linux 的选单。
@@ -283,11 +284,10 @@ BIOS 不可以读取 GPT 分区表,而 UEFI 可以。
除此之外还包括:
- superblock:记录文件系统的整体信息,包括 inode 和 block 的总量、使用量、剩余量,以及文件系统的格式与相关信息等;
-- block bitmap:记录 block 是否被使用的位域;
+- block bitmap:记录 block 是否被使用的位域。
-
## 文件读取
对于 Ext2 文件系统,当要读取一个文件的内容时,先在 inode 中去查找文件内容所在的所有 block,然后把所有 block 的内容读出来。
@@ -1169,7 +1169,7 @@ dmtsai lines: 5 columns: 9
| S | interruptible sleep (waiting for an event to complete) |
| Z | zombie (terminated but not reaped by its parent) |
| T | stopped (either by a job control signal or because it is being traced) |
-
+
## SIGCHLD
@@ -1179,12 +1179,12 @@ dmtsai lines: 5 columns: 9
- 得到 SIGCHLD 信号;
- waitpid() 或者 wait() 调用会返回。
-
-
其中子进程发送的 SIGCHLD 信号包含了子进程的信息,包含了进程 ID、进程状态、进程使用 CPU 的时间等。
在子进程退出时,它的进程描述符不会立即释放,这是为了让父进程得到子进程信息,父进程通过 wait() 和 waitpid() 来获得一个已经退出的子进程的信息。
+
+
## wait()
```c
diff --git a/notes/Redis.md b/notes/Redis.md
index 8f231382..629fcdd8 100644
--- a/notes/Redis.md
+++ b/notes/Redis.md
@@ -549,7 +549,7 @@ def main():
# 十二、Sentinel
-Sentinel(哨兵)可以监听主服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。
+Sentinel(哨兵)可以监听集群中的服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。
# 十三、分片
diff --git a/notes/分布式.md b/notes/分布式.md
index c048b43e..e98f2860 100644
--- a/notes/分布式.md
+++ b/notes/分布式.md
@@ -19,10 +19,11 @@
* [五、Paxos](#五paxos)
* [执行过程](#执行过程)
* [约束条件](#约束条件)
-* [五、Raft](#五raft)
+* [六、Raft](#六raft)
* [单个 Candidate 的竞选](#单个-candidate-的竞选)
* [多个 Candidate 竞选](#多个-candidate-竞选)
- * [日志复制](#日志复制)
+ * [数据同步](#数据同步)
+* [参考](#参考)
@@ -35,7 +36,7 @@
- 互斥量为 1 表示有其它进程在使用锁,此时处于锁定状态;
- 互斥量为 0 表示未锁定状态。
-1 和 0 可以用一个整型值表示,也可以用某个数据存在或者不存在表示,存在表示互斥量为 1。
+1 和 0 可以用一个整型值表示,也可以用某个数据是否存在表示,存在表示互斥量为 1。
## 数据库的唯一索引
@@ -43,7 +44,7 @@
存在以下几个问题:
-- 锁没有失效时间,解锁失败的话其它进程无法再获得锁。
+- 锁没有失效时间,解锁失败的话其它进程无法再获得该锁。
- 只能是非阻塞锁,插入失败直接就报错了,无法重试。
- 不可重入,已经获得锁的进程也必须重新获取锁。
@@ -59,7 +60,7 @@ EXPIRE 指令可以为一个键值对设置一个过期时间,从而避免了
使用了多个 Redis 实例来实现分布式锁,这是为了保证在发生单点故障时仍然可用。
-- 尝试从 N 个相互独立 Redis 实例获取锁,如果一个实例不可用,应该尽快尝试下一个;
+- 尝试从 N 个相互独立 Redis 实例获取锁;
- 计算获取锁消耗的时间,只有当这个时间小于锁的过期时间,并且从大多数(N / 2 + 1)实例上获取了锁,那么就认为锁获取成功了;
- 如果锁获取失败,就到每个实例上释放锁。
@@ -67,7 +68,7 @@ EXPIRE 指令可以为一个键值对设置一个过期时间,从而避免了
### 1. Zookeeper 抽象模型
-Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示它的父节点为 /app1。
+Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点的父节点为 /app1。
@@ -96,21 +97,15 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示
一个节点未获得锁,需要监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知(羊群效应),而我们只希望它的后一个子节点收到通知。
-参考:
-
-- [Distributed locks with Redis](https://redis.io/topics/distlock)
-- [浅谈分布式锁](http://www.linkedkeeper.com/detail/blog.action?bid=1023)
-- [基于 Zookeeper 的分布式锁](http://www.dengshenyu.com/java/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/2017/10/23/zookeeper-distributed-lock.html)
-
# 二、分布式事务
-指事务的操作位于不同的节点上,需要保证事务的 AICD 特性。例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。
+指事务的操作位于不同的节点上,需要保证事务的 AICD 特性。
+
+例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。
## 本地消息表
-### 1. 原理
-
-本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性。
+本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。
1. 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
2. 之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。
@@ -118,23 +113,19 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示
-### 2. 分析
-
-本地消息表利用了本地事务来实现分布式事务,并且使用了消息队列来保证最终一致性。
-
## 2PC
两阶段提交(Two-phase Commit,2PC),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。
### 1. 运行过程
-(一)准备阶段
+#### 1.1 准备阶段
协调者询问参与者事务是否执行成功,参与者发回事务执行结果。
-(二)提交阶段
+#### 1.2 提交阶段
如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
@@ -144,28 +135,22 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示
### 2. 存在的问题
-(一)同步阻塞
+#### 2.1 同步阻塞
所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。
-(二)单点问题
+#### 2.2 单点问题
协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。
-(三)数据不一致
+#### 2.3 数据不一致
在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
-(四)太过保守
+#### 2.4 太过保守
任意一个节点失败就会导致整个事务失败,没有完善的容错机制。
-参考:
-
-- [聊聊分布式事务,再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html)
-- [分布式系统的事务处理](https://coolshell.cn/articles/10910.html)
-- [深入理解分布式事务](https://juejin.im/entry/577c6f220a2b5800573492be)
-
# 三、CAP
分布式系统不可能同时满足一致性(C:Consistency)、可用性(A:Availability)和分区容忍性(P:Partition Tolerance),最多只能同时满足其中两项。
@@ -184,7 +169,7 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示
可用性指分布式系统在面对各种异常时可以提供正常服务的能力,可以用系统可用时间占总时间的比值来衡量,4 个 9 的可用性表示系统 99.99% 的时间是可用的。
-在可用性条件下,系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
+在可用性条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
## 分区容忍性
@@ -198,16 +183,11 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示
可用性和一致性往往是冲突的,很难都使它们同时满足。在多个节点之间进行数据同步时,
-* 为了保证一致性(CP),就需要让所有节点下线成为不可用的状态,等待同步完成;
-* 为了保证可用性(AP),在同步过程中允许读取所有节点的数据,但是数据可能不一致。
+- 为了保证一致性(CP),就需要让所有节点下线成为不可用的状态,等待同步完成;
+- 为了保证可用性(AP),在同步过程中允许读取所有节点的数据,但是数据可能不一致。
-参考:
-
-- 倪超. 从 Paxos 到 ZooKeeper : 分布式一致性原理与实践 [M]. 电子工业出版社, 2015.
-- [What is CAP theorem in distributed database system?](http://www.colooshiki.com/index.php/2017/04/20/what-is-cap-theorem-in-distributed-database-system/)
-
# 四、BASE
BASE 是基本可用(Basically Available)、软状态(Soft State)和最终一致性(Eventually Consistent)三个短语的缩写。
@@ -292,66 +272,69 @@ Acceptor 接收到接受请求时,如果序号大于等于该 Acceptor 承诺
Paxos 协议能够让 Proposer 发送的提议朝着能被大多数 Acceptor 接受的那个提议靠拢,因此能够保证可终止性。
-参考:
+# 六、Raft
-- [NEAT ALGORITHMS - PAXOS](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/)
-- [Paxos By Example](https://angus.nyc/2012/paxos-by-example/)
-
-# 五、Raft
-
-Raft 和 Paxos 类似,但是更容易理解,也更容易实现。
-
-Raft 主要是用来竞选主节点。
+Raft 也是分布式一致性协议,主要是用来竞选主节点。
## 单个 Candidate 的竞选
有三种节点:Follower、Candidate 和 Leader。Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间,一般为 150ms\~300ms,如果在这个时间内没有收到 Leader 的心跳包,就会变成 Candidate,进入竞选阶段。
-* 下图表示一个分布式系统的最初阶段,此时只有 Follower,没有 Leader。Follower A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。
+- 下图展示一个分布式系统的最初阶段,此时只有 Follower 没有 Leader。Node A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。
-* 此时 A 发送投票请求给其它所有节点。
+- 此时 Node A 发送投票请求给其它所有节点。
-* 其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。
+- 其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。
-* 之后 Leader 会周期性地发送心跳包给 Follower,Follower 接收到心跳包,会重新开始计时。
+- 之后 Leader 会周期性地发送心跳包给 Follower,Follower 接收到心跳包,会重新开始计时。
## 多个 Candidate 竞选
-* 如果有多个 Follower 成为 Candidate,并且所获得票数相同,那么就需要重新开始投票,例如下图中 Candidate B 和 Candidate D 都获得两票,因此需要重新开始投票。
+- 如果有多个 Follower 成为 Candidate,并且所获得票数相同,那么就需要重新开始投票。例如下图中 Node B 和 Node D 都获得两票,需要重新开始投票。
-* 当重新开始投票时,由于每个节点设置的随机竞选超时时间不同,因此能下一次再次出现多个 Candidate 并获得同样票数的概率很低。
+- 由于每个节点设置的随机竞选超时时间不同,因此下一次再次出现多个 Candidate 并获得同样票数的概率很低。
-## 日志复制
+## 数据同步
-* 来自客户端的修改都会被传入 Leader。注意该修改还未被提交,只是写入日志中。
+- 来自客户端的修改都会被传入 Leader。注意该修改还未被提交,只是写入日志中。
-* Leader 会把修改复制到所有 Follower。
+- Leader 会把修改复制到所有 Follower。
-* Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。
+- Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。
-* 此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。
+- 此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。
-参考:
+# 参考
+- 倪超. 从 Paxos 到 ZooKeeper : 分布式一致性原理与实践 [M]. 电子工业出版社, 2015.
+- [Distributed locks with Redis](https://redis.io/topics/distlock)
+- [浅谈分布式锁](http://www.linkedkeeper.com/detail/blog.action?bid=1023)
+- [基于 Zookeeper 的分布式锁](http://www.dengshenyu.com/java/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/2017/10/23/zookeeper-distributed-lock.html)
- [Raft: Understandable Distributed Consensus](http://thesecretlivesofdata.com/raft)
+- [聊聊分布式事务,再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html)
+- [分布式系统的事务处理](https://coolshell.cn/articles/10910.html)
+- [深入理解分布式事务](https://juejin.im/entry/577c6f220a2b5800573492be)
+- [What is CAP theorem in distributed database system?](http://www.colooshiki.com/index.php/2017/04/20/what-is-cap-theorem-in-distributed-database-system/)
+- [NEAT ALGORITHMS - PAXOS](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/)
+- [Paxos By Example](https://angus.nyc/2012/paxos-by-example/)
diff --git a/notes/剑指 offer 题解.md b/notes/剑指 offer 题解.md
index 1bac6bbb..d88b6168 100644
--- a/notes/剑指 offer 题解.md
+++ b/notes/剑指 offer 题解.md
@@ -1082,7 +1082,7 @@ false
```java
public boolean isNumeric(char[] str) {
- if (str == null)
+ if (str == null || str.length == 0)
return false;
return new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?");
}
@@ -2779,19 +2779,25 @@ public List
-参考:
-
-- [Observer vs Pub-Sub](http://developers-club.com/posts/270339/)
-- [消息队列中点对点与发布订阅区别](https://blog.csdn.net/lizhitao/article/details/47723105)
-
# 二、使用场景
## 异步处理
@@ -78,3 +74,8 @@
- 保证接收端处理消息的业务逻辑具有幂等性:只要具有幂等性,那么消费多少次消息,最后处理的结果都是一样的。
- 保证消息具有唯一编号,并使用一张日志表来记录已经消费的消息编号。
+
+# 参考资料
+
+- [Observer vs Pub-Sub](http://developers-club.com/posts/270339/)
+- [消息队列中点对点与发布订阅区别](https://blog.csdn.net/lizhitao/article/details/47723105)
diff --git a/notes/算法.md b/notes/算法.md
index 30467b42..a9f9e689 100644
--- a/notes/算法.md
+++ b/notes/算法.md
@@ -23,7 +23,7 @@
* [五、栈和队列](#五栈和队列)
* [栈](#栈)
* [队列](#队列)
-* [六、查找](#六查找)
+* [六、符号表](#六符号表)
* [初级实现](#初级实现)
* [二叉查找树](#二叉查找树)
* [2-3 查找树](#2-3-查找树)
@@ -507,7 +507,7 @@ public class QuickSort
+
```java
private int partition(T[] nums, int l, int h) {
@@ -770,7 +770,7 @@ public class HeapSort
-参考资料:
-
-- [内容分发网络](https://zh.wikipedia.org/wiki/%E5%85%A7%E5%AE%B9%E5%82%B3%E9%81%9E%E7%B6%B2%E8%B7%AF)
-- [How Aspiration CDN helps to improve your website loading speed?](https://www.aspirationhosting.com/aspiration-cdn/)
-
# 五、缓存问题
## 缓存穿透
@@ -213,7 +201,7 @@ CDN 主要有以下优点:
- 为了防止缓存在同一时间大面积过期导致的缓存雪崩,可以通过观察用户行为,合理设置缓存过期时间来实现;
- 为了防止缓存服务器宕机出现的缓存雪崩,可以使用分布式缓存,分布式缓存中每一个节点只缓存部分的数据,当某个节点宕机时可以保证其它节点的缓存仍然可用。
-- 也可以在进行缓存预热,避免在系统刚启动不久由于还未将大量数据进行缓存而导致缓存雪崩。
+- 也可以进行缓存预热,避免在系统刚启动不久由于还未将大量数据进行缓存而导致缓存雪崩。
## 缓存一致性
@@ -243,10 +231,6 @@ CDN 主要有以下优点:
- 能保持数据原有的顺序;
- 并且能够准确控制每台服务器存储的数据量,从而使得存储空间的利用率最大。
-参考资料:
-
-- 大规模分布式存储系统
-
# 七、一致性哈希
Distributed Hash Table(DHT) 是一种哈希分布方式,其目的是为了克服传统哈希分布在服务器节点数量变化时大量数据失效的问题。
@@ -267,6 +251,10 @@ Distributed Hash Table(DHT) 是一种哈希分布方式,其目的是为了
数据不均匀主要是因为节点在哈希环上分布的不均匀,这种情况在节点数量很少的情况下尤其明显。解决方式是通过增加虚拟节点,然后将虚拟节点映射到真实节点上。虚拟节点的数量比真实节点来得大,那么虚拟节点在哈希环上分布的均匀性就会比原来的真实节点好,从而使得数据分布也更加均匀。
-参考资料:
+# 参考资料
+- 大规模分布式存储系统
+- [缓存那些事](https://tech.meituan.com/cache_about.html)
- [一致性哈希算法](https://my.oschina.net/jayhu/blog/732849)
+- [内容分发网络](https://zh.wikipedia.org/wiki/%E5%85%A7%E5%AE%B9%E5%82%B3%E9%81%9E%E7%B6%B2%E8%B7%AF)
+- [How Aspiration CDN helps to improve your website loading speed?](https://www.aspirationhosting.com/aspiration-cdn/)
diff --git a/notes/设计模式.md b/notes/设计模式.md
index b8f4dc9b..531595e3 100644
--- a/notes/设计模式.md
+++ b/notes/设计模式.md
@@ -60,9 +60,9 @@
(一)懒汉式-线程不安全
-以下实现中,私有静态变量 uniqueInstance 被延迟化实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance,从而节约资源。
+以下实现中,私有静态变量 uniqueInstance 被延迟实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance,从而节约资源。
-这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 `if (uniqueInstance == null)` ,并且此时 uniqueInstance 为 null,那么多个线程会执行 `uniqueInstance = new Singleton();` 语句,这将导致多次实例化 uniqueInstance。
+这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 `if (uniqueInstance == null)` ,并且此时 uniqueInstance 为 null,那么会有多个线程执行 `uniqueInstance = new Singleton();` 语句,这将导致多次实例化 uniqueInstance。
```java
public class Singleton {
@@ -81,11 +81,21 @@ public class Singleton {
}
```
-(二)懒汉式-线程安全
+(二)饿汉式-线程安全
-只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了对 uniqueInstance 进行多次实例化的问题。
+线程不安全问题主要是由于 uniqueInstance 被多次实例化,采取直接实例化 uniqueInstance 的方式就不会产生线程不安全问题。
-但是这样有一个问题,就是当一个线程进入该方法之后,其它线程试图进入该方法都必须等待,因此性能上有一定的损耗。
+但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。
+
+```java
+private static Singleton uniqueInstance = new Singleton();
+```
+
+(三)懒汉式-线程安全
+
+只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了多次实例化 uniqueInstance 的问题。
+
+但是当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,因此性能上有一定的损耗。
```java
public static synchronized Singleton getUniqueInstance() {
@@ -96,16 +106,6 @@ public static synchronized Singleton getUniqueInstance() {
}
```
-(三)饿汉式-线程安全
-
-线程不安全问题主要是由于 uniqueInstance 被实例化了多次,如果 uniqueInstance 采用直接实例化的话,就不会被实例化多次,也就不会产生线程不安全问题。
-
-但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。
-
-```java
-private static Singleton uniqueInstance = new Singleton();
-```
-
(四)双重校验锁-线程安全
uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。
@@ -175,7 +175,7 @@ public class Singleton {
}
```
-(五)枚举实现
+(六)枚举实现
这是单例模式的最佳实践,它实现简单,并且在面对复杂的序列化或者反射攻击的时候,能够防止实例化多次。
@@ -231,27 +231,9 @@ public class Singleton implements Serializable {
简单工厂不是设计模式,更像是一种编程习惯。它把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。
-
-
这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。因为客户类往往有多个,如果不使用简单工厂,所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。
-如果存在下面这种代码,就需要使用简单工厂将对象实例化的部分放到简单工厂中。
-
-```java
-public class Client {
- public static void main(String[] args) {
- int type = 1;
- Product product;
- if (type == 1) {
- product = new ConcreteProduct1();
- } else if (type == 2) {
- product = new ConcreteProduct2();
- } else {
- product = new ConcreteProduct();
- }
- }
-}
-```
+
### 实现
@@ -275,6 +257,27 @@ public class ConcreteProduct2 implements Product {
}
```
+以下的 Client 类中包含了实例化的代码,这是一种错误的实现,如果在客户类中存在实例化代码,就需要将代码放到简单工厂中。
+
+```java
+public class Client {
+ public static void main(String[] args) {
+ int type = 1;
+ Product product;
+ if (type == 1) {
+ product = new ConcreteProduct1();
+ } else if (type == 2) {
+ product = new ConcreteProduct2();
+ } else {
+ product = new ConcreteProduct();
+ }
+ // do something with the product
+ }
+}
+```
+
+以下的 SimpleFactory 是简单工厂实现,它被所有需要进行实例化的客户类调用。
+
```java
public class SimpleFactory {
public Product createProduct(int type) {
@@ -293,6 +296,7 @@ public class Client {
public static void main(String[] args) {
SimpleFactory simpleFactory = new SimpleFactory();
Product product = simpleFactory.createProduct(1);
+ // do something with the product
}
}
```
@@ -2906,7 +2910,7 @@ Java 利用缓存来加速大量小对象的访问时间。
- 远程代理(Remote Proxy):控制对远程对象(不同地址空间)的访问,它负责将请求及其参数进行编码,并向不同地址空间中的对象发送已经编码的请求。
- 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一个很大图片时,不能马上完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。
- 保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。
-- 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数,比如智能智能;当第一次引用一个持久化对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。
+- 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个持久化对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。
diff --git a/notes/面向对象思想.md b/notes/面向对象思想.md
index 9b59e43d..203c7ebf 100644
--- a/notes/面向对象思想.md
+++ b/notes/面向对象思想.md
@@ -37,6 +37,7 @@
```java
public class Person {
+
private String name;
private int gender;
private int age;
@@ -63,17 +64,20 @@ public class Person {
继承实现了 **IS-A** 关系,例如 Cat 和 Animal 就是一种 IS-A 关系,因此 Cat 可以继承自 Animal,从而获得 Animal 非 private 的属性和方法。
+继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。
+
Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat 对象。父类引用指向子类对象称为 **向上转型** 。
```java
Animal animal = new Cat();
```
-继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。
-
## 多态
-多态分为编译时多态和运行时多态。编译时多态主要指方法的重载,运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定。
+多态分为编译时多态和运行时多态:
+
+- 编译时多态主要指方法的重载
+- 运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定
运行时多态有三个条件:
@@ -116,7 +120,7 @@ public class Music {
# 二、类图
-以下类图使用 [PlantUML](https://www.planttext.com/) 绘制,更多语法及使用请参考:http://plantuml.com/
+以下类图使用 [PlantUML](https://www.planttext.com/) 绘制,更多语法及使用请参考:http://plantuml.com/ 。
## 泛化关系 (Generalization)
@@ -327,7 +331,7 @@ Vihicle .. N
### 2. 合成复用原则
-尽量使用对象组合,而不是继承来达到复用的目的。
+尽量使用对象组合,而不是通过继承来达到复用的目的。
### 3. 共同封闭原则
@@ -349,3 +353,4 @@ Vihicle .. N
- [看懂 UML 类图和时序图](http://design-patterns.readthedocs.io/zh_CN/latest/read_uml.html#generalization)
- [UML 系列——时序图(顺序图)sequence diagram](http://www.cnblogs.com/wolf-sun/p/UML-Sequence-diagram.html)
- [面向对象编程三大特性 ------ 封装、继承、多态](http://blog.csdn.net/jianyuerensheng/article/details/51602015)
+
From a8104d3f5e7df44fe56b55e16b07639d5811e381 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=83=91=E6=B0=B8=E5=B7=9D?= <1029579233@qq.com>
Date: Sat, 25 Aug 2018 23:30:03 +0800
Subject: [PATCH 021/136] Update README.md
---
README.md | 36 ++++++++++++++++++------------------
1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/README.md b/README.md
index d511fa7c..27edaf24 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@
-## :pencil2: 算法
+### :pencil2: 算法
- [剑指 Offer 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/剑指%20offer%20题解.md)
@@ -25,7 +25,7 @@
排序、并查集、栈和队列、红黑树、散列表。
-## :computer: 操作系统
+### :computer: 操作系统
- [计算机操作系统](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/计算机操作系统.md)
@@ -35,7 +35,7 @@
基本实现原理以及基本操作。
-## :cloud: 网络
+### :cloud: 网络
- [计算机网络](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/计算机网络.md)
@@ -49,7 +49,7 @@
I/O 模型、I/O 多路复用。
-## :couple: 面向对象
+### :couple: 面向对象
- [设计模式](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/设计模式.md)
@@ -59,7 +59,7 @@
三大原则(继承、封装、多态)、类图、设计原则。
-## :floppy_disk: 数据库
+### :floppy_disk: 数据库
- [数据库系统原理](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/数据库系统原理.md)
@@ -81,7 +81,7 @@
五种数据类型、字典和跳跃表数据结构、使用场景、和 Memcache 的比较、淘汰策略、持久化、文件事件的 Reactor 模式、复制。
-## :coffee: Java
+### :coffee: Java
- [Java 基础](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20基础.md)
@@ -103,7 +103,7 @@
NIO 的原理以及实例。
-## :bulb: 系统设计
+### :bulb: 系统设计
- [系统设计基础](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/系统设计基础.md)
@@ -129,7 +129,7 @@
消息处理模型、使用场景、可靠性
-## :hammer: 工具
+### :hammer: 工具
- [Git](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Git.md)
@@ -147,7 +147,7 @@
构建工具的基本概念、主流构建工具介绍。
-## :speak_no_evil: 编码实践
+### :speak_no_evil: 编码实践
- [重构](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/重构.md)
@@ -161,9 +161,9 @@
Google 开源项目的代码风格规范。
-## :memo: 后记
+### :memo: 后记
-### About
+#### About
这个仓库是笔者的一个学习笔记,主要总结一些比较重要的知识点,希望对大家有所帮助。
@@ -171,7 +171,7 @@
[BOOKLIST](https://github.com/CyC2018/Interview-Notebook/blob/master/BOOKLIST.md),这个书单是笔者至今看的一些比较好的技术书籍,虽然没有全都看完,但每本书多多少少都看了一部分。
-### How To Contribute
+#### How To Contribute
笔记内容是笔者一个字一个字打上去的,难免会有一些笔误,如果发现笔误可直接在相应文档进行编辑修改。
@@ -179,7 +179,7 @@
欢迎在 Issue 中提交对本仓库的改进建议~
-### Typesetting
+#### Typesetting
笔记内容按照 [中文文案排版指北](http://mazhuang.org/wiki/chinese-copywriting-guidelines/) 进行排版,以保证内容的可读性。
@@ -187,7 +187,7 @@
笔者将自己实现的文档排版功能提取出来,放在 Github Page 中,无需下载安装即可免费使用:[Text-Typesetting](https://github.com/CyC2018/Markdown-Typesetting)。
-### Uploading
+#### Uploading
笔者在本地使用为知笔记软件进行书写,为了方便将本地笔记内容上传到 Github 上,实现了一整套自动化上传方案,包括文本文件的导出、提取图片、Markdown 文档转换、Git 同步。
@@ -195,15 +195,15 @@
笔者将自己实现文档转换功能提取出来,方便大家在需要将本地 Markdown 上传到 Github,或者制作项目 README 文档时生成目录时使用:[GFM-Converter](https://github.com/CyC2018/GFM-Converter)。
-### Statement
+#### Statement
本仓库不参与商业行为,不向读者收取任何费用。(This repository is not engaging in business activities, and does not charge readers any fee.)
-### Logo
+#### Logo
Power by [logomakr](https://logomakr.com/).
-### Acknowledgements
+#### Acknowledgements
感谢以下人员对本仓库做出的贡献,当然不仅仅只有这些贡献者,这里就不一一列举了。如果你希望被添加到这个名单中,并且提交过 Issue 或者 PR,请与笔者联系。
@@ -229,7 +229,7 @@ Power by [logomakr](https://logomakr.com/).
@@ -92,7 +92,7 @@ InnoDB 的 B+Tree 索引分为主索引和辅助索引。
### 2. 哈希索引
-哈希索引能以 O(1) 时间进行查找,但是失去了有序性,它具有以下限制:
+哈希索引能以 O(1) 时间进行查找,但是失去了有序性:
- 无法用于排序与分组;
- 只支持精确查找,无法用于部分查找和范围查找。
@@ -101,9 +101,11 @@ InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当
### 3. 全文索引
-MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。查找条件使用 MATCH AGAINST,而不是普通的 WHERE。
+MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。
-全文索引一般使用倒排索引实现,它记录着关键词到其所在文档的映射。
+查找条件使用 MATCH AGAINST,而不是普通的 WHERE。
+
+全文索引使用倒排索引实现,它记录着关键词到其所在文档的映射。
InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。
@@ -136,7 +138,9 @@ WHERE actor_id = 1 AND film_id = 1;
### 3. 索引列的顺序
-让选择性最强的索引列放在前面,索引的选择性是指:不重复的索引值和记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,查询效率也越高。
+让选择性最强的索引列放在前面。
+
+索引的选择性是指:不重复的索引值和记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,查询效率也越高。
例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。
@@ -173,14 +177,16 @@ customer_id_selectivity: 0.0373
- 大大减少了服务器需要扫描的数据行数。
-- 帮助服务器避免进行排序和分组,也就不需要创建临时表(B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,因为不需要排序和分组,也就不需要创建临时表)。
+- 帮助服务器避免进行排序和分组,以及避免创建临时表(B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,因为不需要排序和分组,也就不需要创建临时表)。
-- 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,也就将相邻的数据都存储在一起)。
+- 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,会将相邻的数据都存储在一起)。
-## 索引的使用场景
+## 索引的使用条件
+
+- 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效;
+
+- 对于中到大型的表,索引就非常有效;
-- 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效。
-- 对于中到大型的表,索引就非常有效。
- 但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。
# 二、查询性能优化
@@ -214,7 +220,7 @@ Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explai
一个大查询如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。
```sql
-DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH);
+DELEFT FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH);
```
```sql
@@ -342,7 +348,7 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提
水平切分又称为 Sharding,它是将同一个表中的记录拆分到多个结构相同的表中。
-当一个表的数据不断增多时,Sharding 是必然的选择,它可以将数据分布到集群的不同节点上,从而缓解单个数据库的压力。
+当一个表的数据不断增多时,Sharding 是必然的选择,它可以将数据分布到集群的不同节点上,从而缓存单个数据库的压力。
From e57025b873e8f330c8a36f9601965e9997e0e4e8 Mon Sep 17 00:00:00 2001
From: CyC2018 <1029579233@qq.com>
Date: Sun, 26 Aug 2018 15:41:50 +0800
Subject: [PATCH 023/136] auto commit
---
notes/Java 基础.md | 2 +-
notes/Java 虚拟机.md | 16 +++++-----------
notes/MySQL.md | 23 ++++++++++-------------
3 files changed, 16 insertions(+), 25 deletions(-)
diff --git a/notes/Java 基础.md b/notes/Java 基础.md
index 44ca873a..1f172f30 100644
--- a/notes/Java 基础.md
+++ b/notes/Java 基础.md
@@ -215,7 +215,7 @@ String s5 = "bbb";
System.out.println(s4 == s5); // true
```
-在 Java 7 之前,字符串常量池被放在运行时常量池中,它属于永久代。而在 Java 7,字符串常量池被移到 Native Method 中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。
+在 Java 7 之前,字符串常量池被放在运行时常量池中,它属于永久代。而在 Java 7,字符串常量池被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。
- [StackOverflow : What is String interning?](https://stackoverflow.com/questions/10578984/what-is-string-interning)
- [深入解析 String#intern](https://tech.meituan.com/in_depth_understanding_string_intern.html)
diff --git a/notes/Java 虚拟机.md b/notes/Java 虚拟机.md
index 1bba557b..edc43e8f 100644
--- a/notes/Java 虚拟机.md
+++ b/notes/Java 虚拟机.md
@@ -38,7 +38,7 @@
## Java 虚拟机栈
-每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息,从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
+每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
@@ -55,27 +55,21 @@ java -Xss512M HackTheJava
## 本地方法栈
-本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。
-
本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
+本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。
+
## 堆
所有对象都在这里分配内存,是垃圾收集的主要区域("GC 堆")。
-现代的垃圾收集器基本都是采用分代收集算法,针对不同类型的对象采取不同的垃圾回收算法,可以将堆分成两块:
+现代的垃圾收集器基本都是采用分代收集算法,其主要的思想是针对不同类型的对象采取不同的垃圾回收算法,可以将堆分成两块:
- 新生代(Young Generation)
- 老年代(Old Generation)
-新生代可以继续划分成以下三个空间:
-
-- Eden(伊甸园)
-- From Survivor(幸存者)
-- To Survivor
-
堆不需要连续内存,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。
可以通过 -Xms 和 -Xmx 两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。
@@ -104,7 +98,7 @@ Class 文件中的常量池(编译器生成的各种字面量和符号引用
## 直接内存
-在 JDK 1.4 中新加入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。
+在 JDK 1.4 中新加入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存(Native 堆),然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。
这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
diff --git a/notes/MySQL.md b/notes/MySQL.md
index 04b6ea61..bf795d34 100644
--- a/notes/MySQL.md
+++ b/notes/MySQL.md
@@ -354,17 +354,17 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提
## 垂直切分
-
-
垂直切分是将一张表按列切分成多个表,通常是按照列的关系密集程度进行切分,也可以利用垂直切分将经常被使用的列和不经常被使用的列切分到不同的表中。
在数据库的层面使用垂直切分将按数据库中表的密集程度部署到不同的库中,例如将原来的电商数据库垂直切分成商品数据库、用户数据库等。
+
+
## Sharding 策略
-- 哈希取模:hash(key) % NUM_DB
-- 范围:可以是 ID 范围也可以是时间范围
-- 映射表:使用单独的一个数据库来存储映射关系
+- 哈希取模:hash(key) % N;
+- 范围:可以是 ID 范围也可以是时间范围;
+- 映射表:使用单独的一个数据库来存储映射关系。
## Sharding 存在的问题及解决方案
@@ -382,20 +382,15 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提
- 为每个分片指定一个 ID 范围
- 分布式 ID 生成器 (如 Twitter 的 Snowflake 算法)
-更多内容请参考:
-
-- [How Sharding Works](https://medium.com/@jeeyoungk/how-sharding-works-b4dec46b3f6)
-- [大众点评订单系统分库分表实践](https://tech.meituan.com/dianping_order_db_sharding.html)
-
# 六、复制
## 主从复制
主要涉及三个线程:binlog 线程、I/O 线程和 SQL 线程。
-- **binlog 线程** :负责将主服务器上的数据更改写入二进制日志中。
-- **I/O 线程** :负责从主服务器上读取二进制日志,并写入从服务器的中继日志中。
-- **SQL 线程** :负责读取中继日志并重放其中的 SQL 语句。
+- **binlog 线程** :负责将主服务器上的数据更改写入二进制日志(Binary log)中。
+- **I/O 线程** :负责从主服务器上读取二进制日志,并写入从服务器的重放日志(Replay log)中。
+- **SQL 线程** :负责读取重放日志并重放其中的 SQL 语句。
@@ -423,3 +418,5 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提
- [SQL Azure Federation – Introduction](http://geekswithblogs.net/shaunxu/archive/2012/01/07/sql-azure-federation-ndash-introduction.aspx "Title of this entry.")
- [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html)
- [MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735)
+- [How Sharding Works](https://medium.com/@jeeyoungk/how-sharding-works-b4dec46b3f6)
+- [大众点评订单系统分库分表实践](https://tech.meituan.com/dianping_order_db_sharding.html)
From 4a6a3583f0adf864a81dafa51546893dbad71d72 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E9=83=91=E6=B0=B8=E5=B7=9D?= <1029579233@qq.com>
Date: Sun, 26 Aug 2018 15:44:44 +0800
Subject: [PATCH 024/136] Update BOOKLIST.md
---
BOOKLIST.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/BOOKLIST.md b/BOOKLIST.md
index 4805a7dc..e88ba706 100644
--- a/BOOKLIST.md
+++ b/BOOKLIST.md
@@ -54,7 +54,7 @@
- [STL 源码剖析](https://book.douban.com/subject/1110934/)
- [深度探索 C++ 对象模型](https://book.douban.com/subject/1091086/)
-# 网站架构/分布式
+# 系统设计
- [大规模分布式存储系统](https://book.douban.com/subject/25723658/)
- [从 Paxos 到 Zookeeper](https://book.douban.com/subject/26292004/)
From 1c85575ed1cc2894bae203cb305f6e8c2de3bba9 Mon Sep 17 00:00:00 2001
From: CyC2018 <1029579233@qq.com>
Date: Sun, 26 Aug 2018 16:17:09 +0800
Subject: [PATCH 025/136] auto commit
---
notes/Java 虚拟机.md | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/notes/Java 虚拟机.md b/notes/Java 虚拟机.md
index edc43e8f..c3ae5889 100644
--- a/notes/Java 虚拟机.md
+++ b/notes/Java 虚拟机.md
@@ -136,8 +136,6 @@ public class ReferenceCountingGC {
通过 GC Roots 作为起始点进行搜索,能够到达到的对象都是存活的,不可达的对象可被回收。
-
-
Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC Roots 一般包含以下内容:
- 虚拟机栈中引用的对象
@@ -145,6 +143,8 @@ Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC
- 方法区中类静态属性引用的对象
- 方法区中的常量引用的对象
+
+
### 3. 方法区的回收
因为方法区主要存放永久代对象,而永久代对象的回收率比新生代低很多,因此在方法区上进行回收性价比不高。
@@ -165,13 +165,13 @@ Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC
finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。但是 try-finally 等方式可以做的更好,并且该方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。
-当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能通过在该方法中让对象重新被引用,从而实现自救。自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会调用 finalize() 方法。
+当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能在该方法中让对象重新被引用,从而实现自救。自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会调用 finalize() 方法。
## 引用类型
无论是通过引用计算算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判定对象是否可被回收都与引用有关。
-Java 具有四种强度不同的引用类型。
+Java 提供了四种强度不同的引用类型。
### 1. 强引用
@@ -280,7 +280,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
它的优点是简单高效,对于单个 CPU 环境来说,由于没有线程交互的开销,因此拥有最高的单线程收集效率。
-它是 Client 模式下的默认新生代收集器,因为在用户的桌面应用场景下,分配给虚拟机管理的内存一般来说不会很大。Serial 收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。
+它是 Client 模式下的默认新生代收集器,因为在该应用场景下,分配给虚拟机管理的内存一般来说不会很大。Serial 收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。
### 2. ParNew 收集器
@@ -298,11 +298,11 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
其它收集器关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是达到一个可控制的吞吐量,它被称为“吞吐量优先”收集器。这里的吞吐量指 CPU 用于运行用户代码的时间占总时间的比值。
-停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
+停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。
缩短停顿时间是以牺牲吞吐量和新生代空间来换取的:新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。
-可以通过一个开关参数打卡 GC 自适应的调节策略(GC Ergonomics),就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 区的比例、晋升老年代对象年龄等细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种方式称为 。
+可以通过一个开关参数打开 GC 自适应的调节策略(GC Ergonomics),就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 区的比例、晋升老年代对象年龄等细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。
### 4. Serial Old 收集器
@@ -372,8 +372,6 @@ G1 把堆划分成多个大小相等的独立区域(Region),新生代和
- 空间整合:整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。
- 可预测的停顿:能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒。
-更详细内容请参考:[Getting Started with the G1 Garbage Collector](http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html)
-
# 三、内存分配与回收策略
## Minor GC 和 Full GC
@@ -738,6 +736,7 @@ public class FileSystemClassLoader extends ClassLoader {
- 周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011.
- [Chapter 2. The Structure of the Java Virtual Machine](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.4)
- [Jvm memory](https://www.slideshare.net/benewu/jvm-memory)
+[Getting Started with the G1 Garbage Collector](http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html)
- [JNI Part1: Java Native Interface Introduction and “Hello World” application](http://electrofriends.com/articles/jni/jni-part1-java-native-interface/)
- [Memory Architecture Of JVM(Runtime Data Areas)](https://hackthejava.wordpress.com/2015/01/09/memory-architecture-by-jvmruntime-data-areas/)
- [JVM Run-Time Data Areas](https://www.programcreek.com/2013/04/jvm-run-time-data-areas/)
From dce6d0ee3f0605e5553db6d46b15ea0ff58540c6 Mon Sep 17 00:00:00 2001
From: CyC2018 <1029579233@qq.com>
Date: Sun, 26 Aug 2018 19:57:38 +0800
Subject: [PATCH 026/136] auto commit
---
notes/Java 容器.md | 187 ++++++++++++++++++++-----------------------
notes/Java 虚拟机.md | 2 +-
2 files changed, 87 insertions(+), 102 deletions(-)
diff --git a/notes/Java 容器.md b/notes/Java 容器.md
index 001f864c..57e5f475 100644
--- a/notes/Java 容器.md
+++ b/notes/Java 容器.md
@@ -126,7 +126,91 @@ public class ArrayList
## I/O 复用
-使用 select 或者 poll 等待数据,并且可以等待多个套接字中的任何一个变为可读,这一过程会被阻塞,当某一个套接字可读时返回。之后再使用 recvfrom 把数据从内核复制到进程中。
+使用 select 或者 poll 等待数据,并且可以等待多个套接字中的任何一个变为可读。这一过程会被阻塞,当某一个套接字可读时返回,之后再使用 recvfrom 把数据从内核复制到进程中。
它可以让单个进程具有处理多个 I/O 事件的能力。又被称为 Event Driven I/O,即事件驱动 I/O。
@@ -77,7 +77,7 @@ ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *
## 异步 I/O
-进行 aio_read 系统调用会立即返回,应用进程继续执行,不会被阻塞,内核会在所有操作完成之后向应用进程发送信号。
+应用进程执行 aio_read 系统调用会立即返回,应用进程可以继续执行,不会被阻塞,内核会在所有操作完成之后向应用进程发送信号。
异步 I/O 与信号驱动 I/O 的区别在于,异步 I/O 的信号是通知应用进程 I/O 完成,而信号驱动 I/O 的信号是通知应用进程可以开始 I/O。
@@ -198,7 +198,7 @@ else
select 和 poll 的功能基本相同,不过在一些实现细节上有所不同。
- select 会修改描述符,而 poll 不会;
-- select 的描述符类型使用数组实现,FD_SETSIZE 大小默认为 1024,因此默认只能监听 1024 个描述符。如果要监听更多描述符的话,需要修改 FD_SETSIZE 之后重新编译;而 poll 的描述符类型使用链表实现,没有描述符的数量的限制;
+- select 的描述符类型使用数组实现,FD_SETSIZE 大小默认为 1024,因此默认只能监听 1024 个描述符。如果要监听更多描述符的话,需要修改 FD_SETSIZE 之后重新编译;而 poll 的描述符类型使用链表实现,没有描述符数量的限制;
- poll 提供了更多的事件类型,并且对描述符的重复利用上比 select 高。
- 如果一个线程对某个描述符调用了 select 或者 poll,另一个线程关闭了该描述符,会导致调用结果不确定。
diff --git a/notes/剑指 offer 题解.md b/notes/剑指 offer 题解.md
index 83f03d8e..63cf170e 100644
--- a/notes/剑指 offer 题解.md
+++ b/notes/剑指 offer 题解.md
@@ -230,9 +230,9 @@ Output:
```java
public String replaceSpace(StringBuffer str) {
int P1 = str.length() - 1;
- for (int i = 0; i < P1+1; i++)
+ for (int i = 0; i < P1 + 1; i++)
if (str.charAt(i) == ' ')
- str.append("aa");
+ str.append(" ");
int P2 = str.length() - 1;
while (P1 >= 0 && P2 > P1) {
diff --git a/notes/计算机操作系统.md b/notes/计算机操作系统.md
index 6bfb559c..b3b0a41b 100644
--- a/notes/计算机操作系统.md
+++ b/notes/计算机操作系统.md
@@ -791,12 +791,11 @@ FIFO 算法可能会把经常使用的页面置换出去,为了避免这一问
-
### 6. 时钟
> Clock
-第二次机会算法需要在链表中移动页面,降低了效率。时钟算法使用环形链表将页面链接起来,再使用一个指针指向最老的页面。
+第二次机会算法需要在链表中移动页面,降低了效率。时钟算法使用环形链表将页面连接起来,再使用一个指针指向最老的页面。
diff --git a/notes/计算机网络.md b/notes/计算机网络.md
index e80ba7f8..99a6fb43 100644
--- a/notes/计算机网络.md
+++ b/notes/计算机网络.md
@@ -59,7 +59,7 @@
网络把主机连接起来,而互联网是把多种不同的网络连接起来,因此互联网是网络的网络。
-
+
## ISP
@@ -67,9 +67,7 @@
-目前的互联网是一种多层次 ISP 结构,ISP 根据覆盖面积的大小分为第一层 ISP、区域 ISP 和接入 ISP。
-
-互联网交换点 IXP 允许两个 ISP 直接相连而不用经过第三个 ISP。
+目前的互联网是一种多层次 ISP 结构,ISP 根据覆盖面积的大小分为第一层 ISP、区域 ISP 和接入 ISP。互联网交换点 IXP 允许两个 ISP 直接相连而不用经过第三个 ISP。
@@ -125,7 +123,7 @@
## 计算机网络体系结构*
-
+
### 1. 五层协议
@@ -143,7 +141,7 @@
其中表示层和会话层用途如下:
-- **表示层** :数据压缩、加密以及数据描述,这使得应用程序不必担心在各台主机中数据内部格式不同的问题。
+- **表示层** :数据压缩、加密以及数据描述,这使得应用程序不必关心在各台主机中数据内部格式不同的问题。
- **会话层** :建立及管理会话。
@@ -700,7 +698,7 @@ TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文
-TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。
+TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。
发送方需要维护一个叫做拥塞窗口(cwnd)的状态变量,注意拥塞窗口与发送方窗口的区别:拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送方窗口。
@@ -713,11 +711,11 @@ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、
### 1. 慢开始与拥塞避免
-发送的最初执行慢开始,令 cwnd=1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段数量为:2、4、8 ...
+发送的最初执行慢开始,令 cwnd = 1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段数量为:2、4、8 ...
-注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能也就更高。设置一个慢开始门限 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。
+注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能性也就更高。设置一个慢开始门限 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。
-如果出现了超时,则令 ssthresh = cwnd/2,然后重新执行慢开始。
+如果出现了超时,则令 ssthresh = cwnd / 2,然后重新执行慢开始。
### 2. 快重传与快恢复
@@ -725,7 +723,7 @@ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、
在发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。例如收到三个 M2,则 M3 丢失,立即重传 M3。
-在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此执行快恢复,令 ssthresh = cwnd/2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。
+在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此执行快恢复,令 ssthresh = cwnd / 2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。
慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh。
@@ -743,8 +741,8 @@ DNS 是一个分布式数据库,提供了主机名和 IP 地址之间相互转
DNS 可以使用 UDP 或者 TCP 进行传输,使用的端口号都为 53。大多数情况下 DNS 使用 UDP 进行传输,这就要求域名解析器和域名服务器都必须自己处理超时和重传来保证可靠性。在两种情况下会使用 TCP 进行传输:
-- 如果返回的响应超过的 512 字节就改用 TCP 进行传输(UDP 最大只支持 512 字节的数据)。
-- 区域传送需要使用 TCP 进行传输(区域传送是主域名服务器向辅助域名服务器传送变化的那部分数据)。
+- 如果返回的响应超过的 512 字节(UDP 最大只支持 512 字节的数据)。
+- 区域传送(区域传送是主域名服务器向辅助域名服务器传送变化的那部分数据)。
## 文件传送协议
@@ -806,7 +804,7 @@ POP3 的特点是只要用户从服务器上读取了邮件,就把该邮件删
### 3. IMAP
-IMAP 协议中客户端和服务器上的邮件保持同步,如果不去手动删除邮件,那么服务器上的邮件也不会被删除。IMAP 这种做法可以让用户随时随地去访问服务器上的邮件。
+IMAP 协议中客户端和服务器上的邮件保持同步,如果不手动删除邮件,那么服务器上的邮件也不会被删除。IMAP 这种做法可以让用户随时随地去访问服务器上的邮件。
## 常用端口
From 03bb846a22ab22acd608af2e27d0063c44ae1a7a Mon Sep 17 00:00:00 2001
From: CyC2018 <1029579233@qq.com>
Date: Tue, 28 Aug 2018 22:02:30 +0800
Subject: [PATCH 035/136] auto commit
---
notes/Java 容器.md | 2 +-
notes/计算机操作系统.md | 6 +++++-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/notes/Java 容器.md b/notes/Java 容器.md
index 57e5f475..fd1ee97e 100644
--- a/notes/Java 容器.md
+++ b/notes/Java 容器.md
@@ -1182,7 +1182,7 @@ ListIterator <-- List
- Eckel B. Java 编程思想 [M]. 机械工业出版社, 2002.
- [Java Collection Framework](https://www.w3resource.com/java-tutorial/java-collections.php)
- [Iterator 模式](https://openhome.cc/Gossip/DesignPattern/IteratorPattern.htm)
-- [Java 8 系列之重新认识 HashMap](https://tech.meituan.com/java-hashmap.html)
+- [Java 8 系列之重新认识 HashMap](https://tech.meituan.com/java_hashmap.html)
- [What is difference between HashMap and Hashtable in Java?](http://javarevisited.blogspot.hk/2010/10/difference-between-hashmap-and.html)
- [Java 集合之 HashMap](http://www.zhangchangle.com/2018/02/07/Java%E9%9B%86%E5%90%88%E4%B9%8BHashMap/)
- [The principle of ConcurrentHashMap analysis](http://www.programering.com/a/MDO3QDNwATM.html)
diff --git a/notes/计算机操作系统.md b/notes/计算机操作系统.md
index 4154cf66..73f67c62 100644
--- a/notes/计算机操作系统.md
+++ b/notes/计算机操作系统.md
@@ -58,7 +58,11 @@
虚拟技术把一个物理实体转换为多个逻辑实体。
-主要有两种虚拟技术:时分复用技术和空分复用技术。例如多个进程能在同一个处理器上并发执行使用了时分复用技术,让每个进程轮流占有处理器,每次只执行一小个时间片并快速切换。例如多个进程占用的总内存可以超过电脑实际安装的物理内存,但是采用虚拟内存 swap 和页面调度等技术,让每个进程工作时使用物理内存,进程切换的时候将内存内容写入到虚拟内存保存,通过工作在物理内存保存在虚拟内存的方式实现空分复用技术。
+主要有两种虚拟技术:时分复用技术和空分复用技术。
+
+多个进程能在同一个处理器上并发执行使用了时分复用技术,让每个进程轮流占有处理器,每次只执行一小个时间片并快速切换。
+
+虚拟内存使用了空分复用技术,它将物理内存抽象为地址空间,每个进程都有各自的地址空间。地址空间和物理内存使用页进行交换,地址空间的页并不需要全部在物理内存中,当使用到一个没有在物理内存的页时,执行页面置换算法,将该页置换到内存中。
### 4. 异步
From 06190f2f1947f4421234ea7912c51fb07d5c79a9 Mon Sep 17 00:00:00 2001
From: CyC2018 <1029579233@qq.com>
Date: Tue, 28 Aug 2018 22:55:26 +0800
Subject: [PATCH 036/136] auto commit
---
notes/算法.md | 78 +++++++++++++++++++++++++++++++++++++--------------
1 file changed, 57 insertions(+), 21 deletions(-)
diff --git a/notes/算法.md b/notes/算法.md
index a9f9e689..53edc1c1 100644
--- a/notes/算法.md
+++ b/notes/算法.md
@@ -104,17 +104,21 @@ public class ThreeSumSlow implements ThreeSum {
public int count(int[] nums) {
int N = nums.length;
int cnt = 0;
- for (int i = 0; i < N; i++)
- for (int j = i + 1; j < N; j++)
- for (int k = j + 1; k < N; k++)
- if (nums[i] + nums[j] + nums[k] == 0)
+ for (int i = 0; i < N; i++) {
+ for (int j = i + 1; j < N; j++) {
+ for (int k = j + 1; k < N; k++) {
+ if (nums[i] + nums[j] + nums[k] == 0) {
cnt++;
+ }
+ }
+ }
+ }
return cnt;
}
}
```
-### 2. ThreeSumFast
+### 2. ThreeSumBinarySearch
通过将数组先排序,对两个元素求和,并用二分查找方法查找是否存在该和的相反数,如果存在,就说明存在三元组的和为 0。
@@ -123,19 +127,23 @@ public class ThreeSumSlow implements ThreeSum {
该方法可以将 ThreeSum 算法增长数量级降低为 O(N2logN)。
```java
-public class ThreeSumFast {
- public static int count(int[] nums) {
+public class ThreeSumBinarySearch implements ThreeSum {
+
+ @Override
+ public int count(int[] nums) {
Arrays.sort(nums);
int N = nums.length;
int cnt = 0;
- for (int i = 0; i < N; i++)
+ for (int i = 0; i < N; i++) {
for (int j = i + 1; j < N; j++) {
int target = -nums[i] - nums[j];
int index = BinarySearch.search(nums, target);
// 应该注意这里的下标必须大于 j,否则会重复统计。
- if (index > j)
+ if (index > j) {
cnt++;
+ }
}
+ }
return cnt;
}
}
@@ -143,22 +151,59 @@ public class ThreeSumFast {
```java
public class BinarySearch {
+
public static int search(int[] nums, int target) {
int l = 0, h = nums.length - 1;
while (l <= h) {
int m = l + (h - l) / 2;
- if (target == nums[m])
+ if (target == nums[m]) {
return m;
- else if (target > nums[m])
+ } else if (target > nums[m]) {
l = m + 1;
- else
+ } else {
h = m - 1;
+ }
}
return -1;
}
}
```
+### 3. ThreeSumTwoPointer
+
+更有效的方法是先将数组排序,然后使用双指针进行查找,时间复杂度为 O(N2)。
+
+```java
+public class ThreeSumTwoPointer implements ThreeSum {
+
+ @Override
+ public int count(int[] nums) {
+ int N = nums.length;
+ int cnt = 0;
+ Arrays.sort(nums);
+ for (int i = 0; i < N - 2; i++) {
+ int l = i + 1, h = N - 1, target = -nums[i];
+ if (i > 0 && nums[i] == nums[i - 1]) continue;
+ while (l < h) {
+ int sum = nums[l] + nums[h];
+ if (sum == target) {
+ cnt++;
+ while (l < h && nums[l] == nums[l + 1]) l++;
+ while (l < h && nums[h] == nums[h - 1]) h--;
+ l++;
+ h--;
+ } else if (sum < target) {
+ l++;
+ } else {
+ h--;
+ }
+ }
+ }
+ return cnt;
+ }
+}
+```
+
## 倍率实验
如果 T(N) \~ aNblogN,那么 T(2N)/T(N) \~ 2b。
@@ -180,29 +225,20 @@ public class BinarySearch {
public class RatioTest {
public static void main(String[] args) {
-
int N = 500;
int loopTimes = 7;
double preTime = -1;
-
while (loopTimes-- > 0) {
-
int[] nums = new int[N];
-
StopWatch.start();
-
ThreeSum threeSum = new ThreeSumSlow();
-
int cnt = threeSum.count(nums);
System.out.println(cnt);
-
double elapsedTime = StopWatch.elapsedTime();
double ratio = preTime == -1 ? 0 : elapsedTime / preTime;
System.out.println(N + " " + elapsedTime + " " + ratio);
-
preTime = elapsedTime;
N *= 2;
-
}
}
}
From 5426af758eff2c61373154c7b888eab5d70556cd Mon Sep 17 00:00:00 2001
From: Zhiwei Tian
From 90e46ab379964e38a9e16b8ee08493ff9ede383c Mon Sep 17 00:00:00 2001
From: showCodes <22638002+showCodes@users.noreply.github.com>
Date: Thu, 30 Aug 2018 11:02:33 +0800
Subject: [PATCH 039/136] =?UTF-8?q?Update=20Java=20=E8=99=9A=E6=8B=9F?=
=?UTF-8?q?=E6=9C=BA.md?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
notes/Java 虚拟机.md | 26 +++++++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/notes/Java 虚拟机.md b/notes/Java 虚拟机.md
index 1a1adc5a..40972bfd 100644
--- a/notes/Java 虚拟机.md
+++ b/notes/Java 虚拟机.md
@@ -7,6 +7,8 @@
* [方法区](#方法区)
* [运行时常量池](#运行时常量池)
* [直接内存](#直接内存)
+ * [类的创建过程](#类的创建过程)
+ * [对象的结构](#对象的结构)
* [二、垃圾收集](#二垃圾收集)
* [判断一个对象是否可被回收](#判断一个对象是否可被回收)
* [引用类型](#引用类型)
@@ -102,6 +104,28 @@ Class 文件中的常量池(编译器生成的各种字面量和符号引用
这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
+## 对象的创建过程
+
+[图解JAVA对象的创建过程](https://www.cnblogs.com/chenyangyao/p/5296807.html)
+
+
+
+## 对象的结构
+
+对象包含 3 个部分:
+1. Header(对象头):如下图所示;
+2. InstanceData(实例数据):将等宽的类型放在一起;
+3. Padding(对齐填充):Hotspot 虚拟机的内存管理系统要求对象的起始地址要是 8 个字节的整数倍,而对象头就是 8 个字节的整数倍。padding 是用来填充实例数据不足 8 个字节整数倍的部分,可以理解为占位符。
+
+
+
+## 对象的地址访问
+
+1. 直接指针:对象的引用直接指向堆中的内存地址 (Hotspot 采用的方式);
+2. 使用句柄:Java 堆中划分出一块内存作为句柄池,对象的引用指向句柄池。
+
+
+
# 二、垃圾收集
垃圾收集主要是针对堆和方法区进行。
@@ -138,7 +162,7 @@ public class ReferenceCountingGC {
Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC Roots 一般包含以下内容:
-- 虚拟机栈中引用的对象
+- 虚拟机栈 (局部变量表) 中引用的对象
- 本地方法栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中的常量引用的对象
From 3edd3984f48e76a22a95add26f9f29f8b232926b Mon Sep 17 00:00:00 2001
From: CyC2018 <1029579233@qq.com>
Date: Thu, 30 Aug 2018 20:25:57 +0800
Subject: [PATCH 040/136] auto commit
---
notes/Docker.md | 32 +++++++-------------
notes/Git.md | 32 ++++++++++----------
notes/HTTP.md | 46 ++++++++++++++--------------
notes/Java 并发.md | 33 ++++++++++----------
notes/Java 虚拟机.md | 30 ++-----------------
notes/Linux.md | 10 +++----
notes/分布式.md | 26 ++++++++--------
notes/构建工具.md | 29 +++++++-----------
notes/消息队列.md | 2 +-
notes/算法.md | 70 +++++++++++++++++++++----------------------
notes/缓存.md | 26 ++++++++--------
notes/计算机网络.md | 6 ++--
notes/设计模式.md | 13 +++++---
notes/重构.md | 6 ++--
notes/集群.md | 6 ++--
notes/面向对象思想.md | 5 +++-
16 files changed, 169 insertions(+), 203 deletions(-)
diff --git a/notes/Docker.md b/notes/Docker.md
index 80108896..1b61a7fe 100644
--- a/notes/Docker.md
+++ b/notes/Docker.md
@@ -4,6 +4,7 @@
* [三、优势](#三优势)
* [四、使用场景](#四使用场景)
* [五、镜像与容器](#五镜像与容器)
+* [参考资料](#参考资料)
@@ -15,11 +16,6 @@
Docker 主要解决环境配置问题,它是一种虚拟化技术,对进程进行隔离,被隔离的进程独立于宿主操作系统和其它隔离的进程。使用 Docker 可以不修改应用程序代码,不需要开发人员学习特定环境下的技术,就能够将现有的应用程序部署在其他机器中。
-参考资料:
-
-- [DOCKER 101: INTRODUCTION TO DOCKER WEBINAR RECAP](https://blog.docker.com/2017/08/docker-101-introduction-docker-webinar-recap/)
-- [Docker 入门教程](http://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html)
-
# 二、与虚拟机的比较
虚拟机也是一种虚拟化技术,它与 Docker 最大的区别在于它是通过模拟硬件,并在硬件上安装操作系统来实现。
@@ -40,30 +36,22 @@ Docker 主要解决环境配置问题,它是一种虚拟化技术,对进程
而 Docker 只是一个进程,只需要将应用以及相关的组件打包,在运行时占用很少的资源,一台机器可以开启成千上万个 Docker。
-参考资料:
-
-- [Docker container vs Virtual machine](http://www.bogotobogo.com/DevOps/Docker/Docker_Container_vs_Virtual_Machine.php)
-
# 三、优势
除了启动速度快以及占用资源少之外,Docker 具有以下优势:
## 更容易迁移
-Docker 可以提供一致性的运行环境,可以在不同的机器上进行迁移,而不用担心环境变化导致无法运行。
+提供一致性的运行环境,可以在不同的机器上进行迁移,而不用担心环境变化导致无法运行。
## 更容易维护
-Docker 使用分层技术和镜像,使得应用可以更容易复用重复部分。复用程度越高,维护工作也越容易。
+使用分层技术和镜像,使得应用可以更容易复用重复部分。复用程度越高,维护工作也越容易。
## 更容易扩展
可以使用基础镜像进一步扩展得到新的镜像,并且官方和开源社区提供了大量的镜像,通过扩展这些镜像可以非常容易得到我们想要的镜像。
-参考资料:
-
-- [为什么要使用 Docker?](https://yeasy.gitbooks.io/docker_practice/introduction/why.html)
-
# 四、使用场景
## 持续集成
@@ -80,11 +68,6 @@ Docker 具有轻量级以及隔离性的特点,在将代码集成到一个 Doc
Docker 轻量级的特点使得它很适合用于部署、维护、组合微服务。
-参考资料:
-
-- [What is Docker](https://www.docker.com/what-docker)
-- [持续集成是什么?](http://www.ruanyifeng.com/blog/2015/09/continuous-integration.html)
-
# 五、镜像与容器
镜像是一种静态的结构,可以看成面向对象里面的类,而容器是镜像的一个实例。
@@ -95,9 +78,14 @@ Docker 轻量级的特点使得它很适合用于部署、维护、组合微服
-参考资料:
+# 参考资料
+- [DOCKER 101: INTRODUCTION TO DOCKER WEBINAR RECAP](https://blog.docker.com/2017/08/docker-101-introduction-docker-webinar-recap/)
+- [Docker 入门教程](http://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html)
+- [Docker container vs Virtual machine](http://www.bogotobogo.com/DevOps/Docker/Docker_Container_vs_Virtual_Machine.php)
- [How to Create Docker Container using Dockerfile](https://linoxide.com/linux-how-to/dockerfile-create-docker-container/)
- [理解 Docker(2):Docker 镜像](http://www.cnblogs.com/sammyliu/p/5877964.html)
-
+- [为什么要使用 Docker?](https://yeasy.gitbooks.io/docker_practice/introduction/why.html)
+- [What is Docker](https://www.docker.com/what-docker)
+- [持续集成是什么?](http://www.ruanyifeng.com/blog/2015/09/continuous-integration.html)
diff --git a/notes/Git.md b/notes/Git.md
index 4bb0a543..71ff502c 100644
--- a/notes/Git.md
+++ b/notes/Git.md
@@ -1,8 +1,7 @@
-* [学习资料](#学习资料)
* [集中式与分布式](#集中式与分布式)
-* [Git 的中心服务器](#git-的中心服务器)
-* [Git 工作流](#git-工作流)
+* [中心服务器](#中心服务器)
+* [工作流](#工作流)
* [分支实现](#分支实现)
* [冲突](#冲突)
* [Fast forward](#fast-forward)
@@ -11,16 +10,10 @@
* [SSH 传输设置](#ssh-传输设置)
* [.gitignore 文件](#gitignore-文件)
* [Git 命令一览](#git-命令一览)
+* [参考资料](#参考资料)
-# 学习资料
-
-- [Git - 简明指南](http://rogerdudler.github.io/git-guide/index.zh.html)
-- [图解 Git](http://marklodato.github.io/visual-git-guide/index-zh-cn.html)
-- [廖雪峰 : Git 教程](https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000)
-- [Learn Git Branching](https://learngitbranching.js.org/)
-
# 集中式与分布式
Git 属于分布式版本控制系统,而 SVN 属于集中式。
@@ -33,11 +26,13 @@ Git 属于分布式版本控制系统,而 SVN 属于集中式。
分布式版本控制新建分支、合并分支操作速度非常快,而集中式版本控制新建一个分支相当于复制一份完整代码。
-# Git 的中心服务器
+# 中心服务器
-Git 的中心服务器用来交换每个用户的修改。没有中心服务器也能工作,但是中心服务器能够 24 小时保持开机状态,这样就能更方便的交换修改。Github 就是一种 Git 中心服务器。
+中心服务器用来交换每个用户的修改,没有中心服务器也能工作,但是中心服务器能够 24 小时保持开机状态,这样就能更方便的交换修改。
-# Git 工作流
+Github 就是一个中心服务器。
+
+# 工作流
@@ -54,14 +49,14 @@ Git 版本库有一个称为 stage 的暂存区,还有自动创建的 master
-可以跳过暂存区域直接从分支中取出修改或者直接提交修改到分支中
+可以跳过暂存区域直接从分支中取出修改,或者直接提交修改到分支中。
- git commit -a 直接把所有文件的修改添加到暂缓区然后执行提交
- git checkout HEAD -- files 取出最后一次修改,可以用来进行回滚操作
# 分支实现
-Git 把每次提交都连成一条时间线。分支使用指针来实现,例如 master 分支指针指向时间线的最后一个节点,也就是最后一次提交。HEAD 指针指向的是当前分支。
+使用指针将每个提交连接成一条时间线,HEAD 指针指向当前分支指针。
@@ -69,7 +64,7 @@ Git 把每次提交都连成一条时间线。分支使用指针来实现,例
-每次提交只会让当前分支向前移动,而其它分支不会移动。
+每次提交只会让当前分支指针向前移动,而其它分支指针不会移动。
@@ -155,4 +150,9 @@ $ ssh-keygen -t rsa -C "youremail@example.com"
比较详细的地址:http://www.cheat-sheets.org/saved-copy/git-cheat-sheet.pdf
+# 参考资料
+- [Git - 简明指南](http://rogerdudler.github.io/git-guide/index.zh.html)
+- [图解 Git](http://marklodato.github.io/visual-git-guide/index-zh-cn.html)
+- [廖雪峰 : Git 教程](https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000)
+- [Learn Git Branching](https://learngitbranching.js.org/)
diff --git a/notes/HTTP.md b/notes/HTTP.md
index 537b24fe..aae807e6 100644
--- a/notes/HTTP.md
+++ b/notes/HTTP.md
@@ -25,6 +25,7 @@
* [实体首部字段](#实体首部字段)
* [五、具体应用](#五具体应用)
* [Cookie](#cookie)
+ * [6. Secure](#6-secure)
* [缓存](#缓存)
* [连接管理](#连接管理)
* [内容协商](#内容协商)
@@ -310,7 +311,7 @@ HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使
Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。
-Cookie 曾一度用于客户端数据的存储,因为当时并没有其它合适的存储办法而作为唯一的存储手段,但现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API (本地存储和会话存储)或 IndexedDB。
+Cookie 曾一度用于客户端数据的存储,因为当时并没有其它合适的存储办法而作为唯一的存储手段,但现在随着现代浏览器开始支持各种各样的存储方式,Cookie 渐渐被淘汰。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API(本地存储和会话存储)或 IndexedDB。
### 1. 用途
@@ -348,7 +349,17 @@ Cookie: yummy_cookie=choco; tasty_cookie=strawberry
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
```
-### 4. JavaScript 获取 Cookie
+### 4. 作用域
+
+Domain 标识指定了哪些主机可以接受 Cookie。如果不指定,默认为当前文档的主机(不包含子域名)。如果指定了 Domain,则一般包含子域名。例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如 developer.mozilla.org)。
+
+Path 标识指定了主机下的哪些路径可以接受 Cookie(该 URL 路径必须存在于请求 URL 中)。以字符 %x2F ("/") 作为路径分隔符,子路径也会被匹配。例如,设置 Path=/docs,则以下地址都会匹配:
+
+- /docs
+- /docs/Web/
+- /docs/Web/HTTP
+
+### 5. JavaScript
通过 `Document.cookie` 属性可创建新的 Cookie,也可通过该属性访问非 HttpOnly 标记的 Cookie。
@@ -358,9 +369,7 @@ document.cookie = "tasty_cookie=strawberry";
console.log(document.cookie);
```
-### 5. Secure 和 HttpOnly
-
-标记为 Secure 的 Cookie 只能通过被 HTTPS 协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为 Cookie 有其固有的不安全性,Secure 标记也无法提供确实的安全保障。
+### 6. HttpOnly
标记为 HttpOnly 的 Cookie 不能被 JavaScript 脚本调用。跨站脚本攻击 (XSS) 常常使用 JavaScript 的 `Document.cookie` API 窃取用户的 Cookie 信息,因此使用 HttpOnly 标记可以在一定程度上避免 XSS 攻击。
@@ -368,15 +377,9 @@ console.log(document.cookie);
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
```
-### 6. 作用域
+## 6. Secure
-Domain 标识指定了哪些主机可以接受 Cookie。如果不指定,默认为当前文档的主机(不包含子域名)。如果指定了 Domain,则一般包含子域名。例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如 developer.mozilla.org)。
-
-Path 标识指定了主机下的哪些路径可以接受 Cookie(该 URL 路径必须存在于请求 URL 中)。以字符 %x2F ("/") 作为路径分隔符,子路径也会被匹配。例如,设置 Path=/docs,则以下地址都会匹配:
-
-- /docs
-- /docs/Web/
-- /docs/Web/HTTP
+标记为 Secure 的 Cookie 只能通过被 HTTPS 协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为 Cookie 有其固有的不安全性,Secure 标记也无法提供确实的安全保障。
### 7. Session
@@ -387,8 +390,7 @@ Session 可以存储在服务器上的文件、数据库或者内存中。也可
使用 Session 维护用户登录状态的过程如下:
- 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中;
-- 服务器验证该用户名和密码;
-- 如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID;
+- 服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID;
- 服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID,客户端收到响应报文之后将该 Cookie 值存入浏览器中;
- 客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID,从 Redis 中取出用户信息,继续之前的业务操作。
@@ -462,13 +464,13 @@ Cache-Control: max-age=31536000
Expires 首部字段也可以用于告知缓存服务器该资源什么时候会过期。
-- 在 HTTP/1.1 中,会优先处理 max-age 指令;
-- 在 HTTP/1.0 中,max-age 指令会被忽略掉。
-
```html
Expires: Wed, 04 Jul 2012 08:26:05 GMT
```
+- 在 HTTP/1.1 中,会优先处理 max-age 指令;
+- 在 HTTP/1.0 中,max-age 指令会被忽略掉。
+
### 4. 缓存验证
需要先了解 ETag 首部字段的含义,它是资源的唯一标识。URL 不能唯一表示资源,例如 `http://www.google.com/` 有中文和英文两个资源,只有 ETag 才能对这两个资源进行唯一标识。
@@ -727,7 +729,7 @@ HTTPs 的报文摘要功能之所以安全,是因为它结合了加密和认
## HTTP/1.x 缺陷
- HTTP/1.x 实现简单是以牺牲性能为代价的:
+HTTP/1.x 实现简单是以牺牲性能为代价的:
- 客户端需要使用多个连接才能实现并发和缩短延迟;
- 不会压缩请求和响应首部,从而导致不必要的网络流量;
@@ -741,9 +743,9 @@ HTTP/2.0 将报文分成 HEADERS 帧和 DATA 帧,它们都是二进制格式
在通信过程中,只会有一个 TCP 连接存在,它承载了任意数量的双向数据流(Stream)。
-- 一个数据流都有一个唯一标识符和可选的优先级信息,用于承载双向信息。
-- 消息(Message)是与逻辑请求或响应消息对应的完整的一系列帧。
-- 帧(Fram)是最小的通信单位,来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装。
+- 一个数据流(Stream)都有一个唯一标识符和可选的优先级信息,用于承载双向信息。
+- 消息(Message)是与逻辑请求或响应对应的完整的一系列帧。
+- 帧(Frame)是最小的通信单位,来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装。
diff --git a/notes/Java 并发.md b/notes/Java 并发.md
index 1403f764..03aaa9fd 100644
--- a/notes/Java 并发.md
+++ b/notes/Java 并发.md
@@ -637,6 +637,7 @@ B
```java
public class WaitNotifyExample {
+
public synchronized void before() {
System.out.println("before");
notifyAll();
@@ -674,12 +675,15 @@ after
## await() signal() signalAll()
-java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。
+java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。
+
+相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。
使用 Lock 来获取一个 Condition 对象。
```java
public class AwaitSignalExample {
+
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
@@ -809,7 +813,7 @@ before..before..before..before..before..before..before..before..before..before..
## Semaphore
-Semaphore 就是操作系统中的信号量,可以控制对互斥资源的访问线程数。
+Semaphore 类似于操作系统中的信号量,可以控制对互斥资源的访问线程数。
@@ -1098,11 +1102,11 @@ Java 内存模型定义了 8 个操作来完成主内存和工作内存的交互
Java 内存模型保证了 read、load、use、assign、store、write、lock 和 unlock 操作具有原子性,例如对一个 int 类型的变量执行 assign 赋值操作,这个操作就是原子性的。但是 Java 内存模型允许虚拟机将没有被 volatile 修饰的 64 位数据(long,double)的读写操作划分为两次 32 位的操作来进行,即 load、store、read 和 write 操作可以不具备原子性。
-有一个错误认识就是,int 等原子性的变量在多线程环境中不会出现线程安全问题。前面的线程不安全示例代码中,cnt 变量属于 int 类型变量,1000 个线程对它进行自增操作之后,得到的值为 997 而不是 1000。
+有一个错误认识就是,int 等原子性的类型在多线程环境中不会出现线程安全问题。前面的线程不安全示例代码中,cnt 属于 int 类型变量,1000 个线程对它进行自增操作之后,得到的值为 997 而不是 1000。
为了方便讨论,将内存间的交互操作简化为 3 个:load、assign、store。
-下图演示了两个线程同时对 cnt 变量进行操作,load、assign、store 这一系列操作整体上看不具备原子性,那么在 T1 修改 cnt 并且还没有将修改后的值写入主内存,T2 依然可以读入该变量的值。可以看出,这两个线程虽然执行了两次自增运算,但是主内存中 cnt 的值最后为 1 而不是 2。因此对 int 类型读写操作满足原子性只是说明 load、assign、store 这些单个操作具备原子性。
+下图演示了两个线程同时对 cnt 进行操作,load、assign、store 这一系列操作整体上看不具备原子性,那么在 T1 修改 cnt 并且还没有将修改后的值写入主内存,T2 依然可以读入旧值。可以看出,这两个线程虽然执行了两次自增运算,但是主内存中 cnt 的值最后为 1 而不是 2。因此对 int 类型读写操作满足原子性只是说明 load、assign、store 这些单个操作具备原子性。
@@ -1200,9 +1204,7 @@ public static void main(String[] args) throws InterruptedException {
### 3. 有序性
-有序性是指:在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排序。
-
-在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
+有序性是指:在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排序。在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
volatile 关键字通过添加内存屏障的方式来禁止指令重排,即重排序时不能把后面的指令放到内存屏障之前。
@@ -1413,7 +1415,7 @@ synchronized 和 ReentrantLock。
**(二)AtomicInteger**
-J.U.C 包里面的整数原子类 AtomicInteger,其中的 compareAndSet() 和 getAndIncrement() 等方法都使用了 Unsafe 类的 CAS 操作。
+J.U.C 包里面的整数原子类 AtomicInteger 的方法调用了 Unsafe 类的 CAS 操作。
以下代码使用了 AtomicInteger 执行了自增的操作。
@@ -1425,7 +1427,7 @@ public void add() {
}
```
-以下代码是 incrementAndGet() 的源码,它调用了 unsafe 的 getAndAddInt() 。
+以下代码是 incrementAndGet() 的源码,它调用了 Unsafe 的 getAndAddInt() 。
```java
public final int incrementAndGet() {
@@ -1463,9 +1465,6 @@ J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference
多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的。
```java
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
public class StackClosedExample {
public void add100() {
int cnt = 0;
@@ -1555,7 +1554,7 @@ public class ThreadLocalExample1 {
-每个 Thread 都有一个 ThreadLocal.ThreadLocalMap 对象,Thread 类中就定义了 ThreadLocal.ThreadLocalMap 成员。
+每个 Thread 都有一个 ThreadLocal.ThreadLocalMap 对象。
```java
/* ThreadLocal values pertaining to this thread. This map is maintained
@@ -1686,15 +1685,15 @@ JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:
- 缩小同步范围,从而减少锁争用。例如对于 synchronized,应该尽量使用同步块而不是同步方法。
-- 多用同步工具少用 wait() 和 notify()。首先,CountDownLatch, CyclicBarrier, Semaphore 和 Exchanger 这些同步类简化了编码操作,而用 wait() 和 notify() 很难实现复杂控制流;其次,这些同步类是由最好的企业编写和维护,在后续的 JDK 中还会不断优化和完善,使用这些更高等级的同步工具你的程序可以不费吹灰之力获得优化。
+- 多用同步工具少用 wait() 和 notify()。首先,CountDownLatch, CyclicBarrier, Semaphore 和 Exchanger 这些同步类简化了编码操作,而用 wait() 和 notify() 很难实现复杂控制流;其次,这些同步类是由最好的企业编写和维护,在后续的 JDK 中还会不断优化和完善。
+
+- 使用 BlockingQueue 实现生产者消费者问题。
- 多用并发集合少用同步集合,例如应该使用 ConcurrentHashMap 而不是 Hashtable。
- 使用本地变量和不可变类来保证线程安全。
-- 使用线程池而不是直接创建 Thread 对象,这是因为创建线程代价很高,线程池可以有效地利用有限的线程来启动任务。
-
-- 使用 BlockingQueue 实现生产者消费者问题。
+- 使用线程池而不是直接创建线程,这是因为创建线程代价很高,线程池可以有效地利用有限的线程来启动任务。
# 参考资料
diff --git a/notes/Java 虚拟机.md b/notes/Java 虚拟机.md
index 40972bfd..1dc4d1d6 100644
--- a/notes/Java 虚拟机.md
+++ b/notes/Java 虚拟机.md
@@ -7,8 +7,6 @@
* [方法区](#方法区)
* [运行时常量池](#运行时常量池)
* [直接内存](#直接内存)
- * [类的创建过程](#类的创建过程)
- * [对象的结构](#对象的结构)
* [二、垃圾收集](#二垃圾收集)
* [判断一个对象是否可被回收](#判断一个对象是否可被回收)
* [引用类型](#引用类型)
@@ -104,28 +102,6 @@ Class 文件中的常量池(编译器生成的各种字面量和符号引用
这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
-## 对象的创建过程
-
-[图解JAVA对象的创建过程](https://www.cnblogs.com/chenyangyao/p/5296807.html)
-
-
-
-## 对象的结构
-
-对象包含 3 个部分:
-1. Header(对象头):如下图所示;
-2. InstanceData(实例数据):将等宽的类型放在一起;
-3. Padding(对齐填充):Hotspot 虚拟机的内存管理系统要求对象的起始地址要是 8 个字节的整数倍,而对象头就是 8 个字节的整数倍。padding 是用来填充实例数据不足 8 个字节整数倍的部分,可以理解为占位符。
-
-
-
-## 对象的地址访问
-
-1. 直接指针:对象的引用直接指向堆中的内存地址 (Hotspot 采用的方式);
-2. 使用句柄:Java 堆中划分出一块内存作为句柄池,对象的引用指向句柄池。
-
-
-
# 二、垃圾收集
垃圾收集主要是针对堆和方法区进行。
@@ -162,8 +138,8 @@ public class ReferenceCountingGC {
Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC Roots 一般包含以下内容:
-- 虚拟机栈 (局部变量表) 中引用的对象
-- 本地方法栈中引用的对象
+- 虚拟机栈中局部变量表中引用的对象
+- 本地方法栈中 JNI 中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中的常量引用的对象
@@ -292,7 +268,7 @@ HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1,保证了
以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。
- 单线程与多线程:单线程指的是垃圾收集器只使用一个线程进行收集,而多线程使用多个线程;
-- 串行与并行:串行指的是垃圾收集器与用户程序交替执行,这意味着在执行垃圾收集的时候需要停顿用户程序;并行指的是垃圾收集器和用户程序同时执行。除了 CMS 和 G1 之外,其它垃圾收集器都是以串行的方式执行。
+- 串行与并行:串行指的是垃圾收集器与用户程序交替执行,这意味着在执行垃圾收集的时候需要停顿用户程序;并形指的是垃圾收集器和用户程序同时执行。除了 CMS 和 G1 之外,其它垃圾收集器都是以串行的方式执行。
### 1. Serial 收集器
diff --git a/notes/Linux.md b/notes/Linux.md
index 73a2e9ec..4ba647a7 100644
--- a/notes/Linux.md
+++ b/notes/Linux.md
@@ -644,7 +644,7 @@ locate 使用 /var/lib/mlocate/ 这个数据库来进行搜索,它存储在内
example: find . -name "shadow*"
```
-(一)与时间有关的选项
+**① 与时间有关的选项**
```html
-mtime n :列出在 n 天前的那一天修改过内容的文件
@@ -657,7 +657,7 @@ example: find . -name "shadow*"
-(二)与文件拥有者和所属群组有关的选项
+**② 与文件拥有者和所属群组有关的选项**
```html
-uid n
@@ -668,7 +668,7 @@ example: find . -name "shadow*"
-nogroup:搜索所属群组不存在于 /etc/group 的文件
```
-(三)与文件权限和名称有关的选项
+**③ 与文件权限和名称有关的选项**
```html
-name filename
@@ -1038,9 +1038,7 @@ $ grep -n 'go\{2,5\}g' regular_express.txt
## printf
-用于格式化输出。
-
-它不属于管道命令,在给 printf 传数据时需要使用 $( ) 形式。
+用于格式化输出。它不属于管道命令,在给 printf 传数据时需要使用 $( ) 形式。
```html
$ printf '%10s %5i %5i %5i %8.2f \n' $(cat printf.txt)
diff --git a/notes/分布式.md b/notes/分布式.md
index e98f2860..113837ce 100644
--- a/notes/分布式.md
+++ b/notes/分布式.md
@@ -29,18 +29,18 @@
# 一、分布式锁
-在单机场景下,可以使用 Java 提供的内置锁来实现进程同步。但是在分布式场景下,需要同步的进程可能位于不同的节点上,那么就需要使用分布式锁。
+在单机场景下,可以使用语言的内置锁来实现进程同步。但是在分布式场景下,需要同步的进程可能位于不同的节点上,那么就需要使用分布式锁。
阻塞锁通常使用互斥量来实现:
-- 互斥量为 1 表示有其它进程在使用锁,此时处于锁定状态;
-- 互斥量为 0 表示未锁定状态。
+- 互斥量为 0 表示有其它进程在使用锁,此时处于锁定状态;
+- 互斥量为 1 表示未锁定状态。
-1 和 0 可以用一个整型值表示,也可以用某个数据是否存在表示,存在表示互斥量为 1。
+1 和 0 可以用一个整型值表示,也可以用某个数据是否存在表示。
## 数据库的唯一索引
-当想要获得锁时,就向表中插入一条记录,释放锁时就删除这条记录。唯一索引可以保证该记录只被插入一次,那么就可以用这个记录是否存在来判断是否存于锁定状态。
+获得锁时向表中插入一条记录,释放锁时删除这条记录。唯一索引可以保证该记录只被插入一次,那么就可以用这个记录是否存在来判断是否存于锁定状态。
存在以下几个问题:
@@ -91,11 +91,11 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点的父
### 5. 会话超时
-如果一个已经获得锁的会话超时了,因为创建的是临时节点,所以该会话对应的临时节点会被删除,其它会话就可以获得锁了。可以看到,Zookeeper 分布式锁不会出现数据库的唯一索引实现分布式锁的释放锁失败问题。
+如果一个已经获得锁的会话超时了,因为创建的是临时节点,所以该会话对应的临时节点会被删除,其它会话就可以获得锁了。可以看到,Zookeeper 分布式锁不会出现数据库的唯一索引实现的分布式锁释放锁失败问题。
### 6. 羊群效应
-一个节点未获得锁,需要监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知(羊群效应),而我们只希望它的后一个子节点收到通知。
+一个节点未获得锁,只需要监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知(羊群效应),而我们只希望它的后一个子节点收到通知。
# 二、分布式事务
@@ -159,9 +159,7 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点的父
## 一致性
-一致性指的是多个数据副本是否能保持一致的特性。
-
-在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态。
+一致性指的是多个数据副本是否能保持一致的特性,在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态。
对系统的一个数据更新成功之后,如果所有用户都能够读取到最新的值,该系统就被认为具有强一致性。
@@ -169,7 +167,7 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点的父
可用性指分布式系统在面对各种异常时可以提供正常服务的能力,可以用系统可用时间占总时间的比值来衡量,4 个 9 的可用性表示系统 99.99% 的时间是可用的。
-在可用性条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
+在可用性条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操,请求总是能够在有限的时间内返回结果。
## 分区容忍性
@@ -179,9 +177,9 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点的父
## 权衡
-在分布式系统中,分区容忍性必不可少,因为需要总是假设网络是不可靠的。因此,CAP 理论实际在是要在可用性和一致性之间做权衡。
+在分布式系统中,分区容忍性必不可少,因为需要总是假设网络是不可靠的。因此,CAP 理论实际上是要在可用性和一致性之间做权衡。
-可用性和一致性往往是冲突的,很难都使它们同时满足。在多个节点之间进行数据同步时,
+可用性和一致性往往是冲突的,很难使它们同时满足。在多个节点之间进行数据同步时,
- 为了保证一致性(CP),就需要让所有节点下线成为不可用的状态,等待同步完成;
- 为了保证可用性(AP),在同步过程中允许读取所有节点的数据,但是数据可能不一致。
@@ -204,7 +202,7 @@ BASE 理论是对 CAP 中一致性和可用性权衡的结果,它的核心思
## 软状态
-指允许系统中的数据存在中间状态,并认为该中间状态不会影响系统整体可用性,即允许系统不同节点的数据副本之间进行同步的过程存在延时。
+指允许系统中的数据存在中间状态,并认为该中间状态不会影响系统整体可用性,即允许系统不同节点的数据副本之间进行同步的过程存在时延。
## 最终一致性
diff --git a/notes/构建工具.md b/notes/构建工具.md
index ce157934..34fef54b 100644
--- a/notes/构建工具.md
+++ b/notes/构建工具.md
@@ -1,11 +1,12 @@
-* [一、什么是构建工具](#一什么是构建工具)
+* [一、构建工具的作用](#一构建工具的作用)
* [二、Java 主流构建工具](#二java-主流构建工具)
* [三、Maven](#三maven)
+* [参考资料](#参考资料)
-# 一、什么是构建工具
+# 一、构建工具的作用
构建工具是用于构建项目的自动化工具,主要包含以下工作:
@@ -29,10 +30,6 @@
不再需要通过 FTP 将 Jar 包上传到服务器上。
-参考资料:
-
-- [What is a build tool?](https://stackoverflow.com/questions/7249871/what-is-a-build-tool)
-
# 二、Java 主流构建工具
主要包括 Ant、Maven 和 Gradle。
@@ -72,12 +69,6 @@ dependencies {
}
```
-参考资料:
-
-- [Java Build Tools Comparisons: Ant vs Maven vs Gradle](https://programmingmitra.blogspot.com/2016/05/java-build-tools-comparisons-ant-vs.html)
-- [maven 2 gradle](http://sagioto.github.io/maven2gradle/)
-- [新一代构建工具 gradle](https://www.imooc.com/learn/833)
-
# 三、Maven
## 概述
@@ -114,7 +105,7 @@ POM 代表项目对象模型,它是一个 XML 文件,保存在项目根目
## 依赖原则
-### 依赖路径最短优先原则
+### 1. 依赖路径最短优先原则
```html
A -> B -> C -> X(1.0)
@@ -122,7 +113,7 @@ A -> D -> X(2.0)
```
由于 X(2.0) 路径最短,所以使用 X(2.0)。
-### 声明顺序优先原则
+### 2. 声明顺序优先原则
```html
A -> B -> X(1.0)
@@ -131,7 +122,7 @@ A -> C -> X(2.0)
在 POM 中最先声明的优先,上面的两个依赖如果先声明 B,那么最后使用 X(1.0)。
-### 覆写优先原则
+### 3. 覆写优先原则
子 POM 内声明的依赖优先于父 POM 中声明的依赖。
@@ -139,9 +130,11 @@ A -> C -> X(2.0)
找到 Maven 加载的 Jar 包版本,使用 `mvn dependency:tree` 查看依赖树,根据依赖原则来调整依赖在 POM 文件的声明顺序。
-参考资料:
+# 参考资料
- [POM Reference](http://maven.apache.org/pom.html#Dependency_Version_Requirement_Specification)
-
-
+- [What is a build tool?](https://stackoverflow.com/questions/7249871/what-is-a-build-tool)
+- [Java Build Tools Comparisons: Ant vs Maven vs Gradle](https://programmingmitra.blogspot.com/2016/05/java-build-tools-comparisons-ant-vs.html)
+- [maven 2 gradle](http://sagioto.github.io/maven2gradle/)
+- [新一代构建工具 gradle](https://www.imooc.com/learn/833)
diff --git a/notes/消息队列.md b/notes/消息队列.md
index 209d962a..bc80894d 100644
--- a/notes/消息队列.md
+++ b/notes/消息队列.md
@@ -40,7 +40,7 @@
发送者将消息发送给消息队列之后,不需要同步等待消息接收者处理完毕,而是立即返回进行其它操作。消息接收者从消息队列中订阅消息之后异步处理。
-例如在注册流程中通常需要发送验证邮件来确保注册用户的身份合法,可以使用消息队列使发送验证邮件的操作异步处理,用户在填写完注册信息之后就可以完成注册,而将发送验证邮件这一消息发送到消息队列中。
+例如在注册流程中通常需要发送验证邮件来确保注册用户身份的合法性,可以使用消息队列使发送验证邮件的操作异步处理,用户在填写完注册信息之后就可以完成注册,而将发送验证邮件这一消息发送到消息队列中。
只有在业务流程允许异步处理的情况下才能这么做,例如上面的注册流程中,如果要求用户对验证邮件进行点击之后才能完成注册的话,就不能再使用消息队列。
diff --git a/notes/算法.md b/notes/算法.md
index 53edc1c1..cb83830b 100644
--- a/notes/算法.md
+++ b/notes/算法.md
@@ -379,7 +379,7 @@ public class Insertion
@@ -739,15 +739,15 @@ public T delMax() {
### 5. 堆排序
-由于堆可以很容易得到最大的元素并删除它,不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列。因此很容易使用堆来进行排序。并且堆排序是原地排序,不占用额外空间。
+把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列,这就是堆排序。
-(一)构建堆
+#### 5.1 构建堆
-无序数组建立堆最直接的方法是从左到右遍历数组,然后进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。
+无序数组建立堆最直接的方法是从左到右遍历数组进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。
-(二)交换堆顶元素与最后一个元素
+#### 5.2 交换堆顶元素与最后一个元素
交换之后需要进行下沉操作维持堆的有序状态。
@@ -804,7 +804,7 @@ public class HeapSort
@@ -1588,7 +1585,7 @@ public class BST
-Sanitize untrusted HTML (to prevent XSS) with a configuration specified by a Whitelist. -
+123
hello-
- http -
- --Sanitize untrusted HTML (to prevent XSS) with a configuration specified by a Whitelist. -
+123
<form> <input type="text" name="q" value="test"> - <button id="submit">Submit</button> </form>hello-
- http -
- --Sanitize untrusted HTML (to prevent XSS) with a configuration specified by a Whitelist. -
+123
hello-
- http -
- --Sanitize untrusted HTML (to prevent XSS) with a configuration specified by a Whitelist. -
+123
<form> <input type="text" name="q" value="test"> - <button id="submit">Submit</button> </form>hello-
- http -
- -C((tLu7Y_hXWzc z(Q!p4&$z05Yjde(K$mYG;@zeAiJMzvk?Mxbrd#V2yM~C9?x5E=2q$qc+-9BB{r!fD z_$k|>P?MY8Rd9NwW+- zOqhqmG;GI_)VjV3kGxgwCtPp_LW=x$d`X4x>l5^^%UW=T)$O)G^5IlkBw_|jW4y)e zddKXlWUhNQ$4S^%q-M+jA)5P<|B3uu9P;lsadu(jqIfCqa;~15ZHn+Nb~!EjcbeH& z8aLfpYw_a|IM^{MsFVEx5JA~OSBB C~i{ZEI2P&;VRdSIMo3`?eP d zq2yN4^;D*i?{!#rHJ|NP5Gt&> zf8$n!!}PyVvxFVSE%AhSO?;e5T~fwHZT(c*+eeNb;f46Zq2TtAnM=&|eVgd7g&UL7 zUD=%5p~AUpQVVc;1|0Ui;%UwsX>+sr_>k8A8ncgyPy?}yiv@Xb_|M|#O0P{`*+9(d zfOm@!?su}5WIE^1T?KS}e7q<8n8F=ds-&S!foi|)wR(^s>{_HZvbD4Cv=KktinHOo z1aw#S=k}jbmenJgQquZ9Djni%!F0nvzgGKftlU8`@!^fZJ=fWr^CHTeMj!jE^jO+= z$*RRln0?(xU~an#xkRArqI#?tQ9vtwDAl%o+5p8$j~8%he0A17?ivv $|JZ zD|4Rw1zSqvsFYWynsAza4aihp22_0U=-@&+8f(tWp@ZVP82c71yv8($53k<{nL#Re zWr6y}h(DVSs@8lj^d-}XKK{6MUt*^Z08E_}Wi~{D+JYQ0po4lM!|dDrEZ~)uA`1*V z SO5GuVpTil{w!|z}yA2UFw;ZXxsb_n(s;pHHuNw z!DV&6zh4v{|1zLsj%*59T+9#T_;b}l2&~FM`77y_Ij2-$t)JaA=RrQ-T<%bx9~rFl zblHG(u=|ZN*Qp3D@f`Q l|%K~*C1?=rTJm#q=*dUt>PyJ1V7k(<94*DLsu<0@K+U1 za^eFRVi5E?cS`lmko?lmw)aR6r@K;A*gcrH5Fy7-?i3)^>w;>ro$szPd3+o!v5SQ+ zekC71Z}>Ae3E1Fb3Ex3vAQ0iwvgLyqfu?@S1}&W0vL(J)Ks)nJj(%}=Vqz`%*#|)P z$z9Lb1Pxgl$PP7gc2uZCq?x>$`P`HlQr>C_A42}DMBQiacqEam{ZalAF%mAR6;Zyw z8%_@#egs64FUQ%%r9a4bvia@6VU( _U+|7^$sw5F9Q_nB!X}})ZR*Ymk?-8Ve?++@?#xR zm)RPF7kkzDd%US%)U7p94u<{|hHi(PtMk9~hxVX;x$ftcp3Th=u3C=x=TRnN9H@_I z-1s_W2H9~pth7O15Ulvhm^BP&;LJ-V_VYhr^UaQYd2VmM3TkqWYRn~_LabT^L`$YN zkNLjE7j&psiiKFBp+lk9+XLre>Hk_oBzTb3SGG46DyaOoHdZ~7^p?=-Z!Ub4-u`12 zi?G{7UX;eAD(_YIBu~n87mPLtf{-EzGZW{>tQniEgLbl4pqAimLWMe$3pBaH#tPCl zj2klHnU`>SuCPXE24Qo|zcl)zG@Q4KTtu+nf0gi^gSkHW4nI!E%z1*^*?EbO5xfUJ zXKX5u&zxl*h6y{94}z^UYs8jJmet` znc%nb0tKs6Vn^;1ErHPS_xlcIe2)>C3~0@ksac=SLLkxt11thJ zL@y& =ufN}Yt0T)N1@agIx{rq%hv&JdH>VdyOaWx2a8`ostsN0 zDYUGrLiwj9}NVTclRw{+_e@)@JoD+}PcU0WKklqPml&5^`WI8KxXL$=yX(9j`V5 zaTV@!1t4p9)tp5_wYnXI>Zh%n5EFN^%fE_{QOcS)`_A-RjXrWk0N=>K{yAcAGbC}; zYC;?Mu3RHBhyr)SxMXLA?tI{DyTwY<$+&$|m-FEcEaCO%(mv~@Y}8bI^QkxA;u)g< z2bx%{*a86#z`@zBRzrP?jA@ZLY3En@RZXiJdw!)CMIgZ?_eIZNtK#;*a58w}-x%f? zT1Gx2XreuL=j_~J(mJm(0mnG%)^YS*Sy|cH)s%``4)lDg2rK$_wq)i<)sG@CW;`pN zH>p!zgd)@SdFHTMr9F!EsJx52Kyj<;mO6S7y8S(q`q2{r;Bz(2J1=Ht==eAph&^Xt zA)mVk5G7;<0o2i|3uLG^3WN3mxwaMa|Zzes$ZU $!+59=x zs=B?j+8*^Tz}w9II65uq0RDXs=*LJLG94Xw^Ok{Vg?luC6O(sABMARTDF%C5-i4Dh zNdh0*dX$JT@sbbop$7Yn341%(d%|C8^5S-dfd@A@lh)e)^YOZJjQtOn9z4Moim1L^ z7i{GlM^%73hF!zmRNzM|75?2ns_w8m&VEy#i3`1X& VyHSBzZTwsBuE@{E)3nrKy>Jfr#b)-&H6iFzg^}(+pIV3i0^Z=H+>8W~S z_w65+Vw#PYBeipM`t?g1ih14RJjjPamdYt^AS`@&Y^HNMZJ0)i*yHN3&tO)ekh)-7 zlQJdKzLnO|(qdA=Kc0WmE@qP#5vS${ji1XSfGH?{gki=_^KSE8r{H2?mwu5|f@oM` z`*HEKWzZyM+sl3zu5@E;2vq2WS9MVE&naYqS=$SQ$PW@^4E0EJ!Z1#ryk>CI{bj zZ5-75>hQmZ;5)G_k#+FPQqLhg!HyjDry@f;UE^c9+YI*kVW9$ 9#tr{!`6& ze%H=`JmAr4la9|FGgIj;7 8Pw8&<^6b2 y)t{HQK zHG@c{W W8ILf$-NNm%8FM}N>*5k-Wm8g?X}EnjWG4L zt0d#VKEFiJa#Z$Q67~C>;p;2*7)zU1mt)&g(4BsZA9UCX020lIg^g{!S7!5(xMRY- zJu`s`vfAQyJ=RmS(5SGX(hhT?qKh-Geo!V@h9j5c%#jH#c9OoErIep-pX^A!J3k<# zLYn`qqmiVyDkF0SG?R9p&dF_`#o)O~-XkY|22a?QVIB*$C+Y1tT? z|CW-1wP~fj^|YG~G3o`0bkgKPe5yqgGKblT*|l*;!y#kbbDQqWudKX+`O_j31 6`c&*+B-e)O>{->Wu8?x|AVdi_KJ%X<4hsL z;)SG9WkIy1{!-xUo%~ qwrq!HD_FBr2Yg%gr?c` z{4P%sL}#5aYfrv0@BM;Pw9!Y}WkPkeO!^eB3WN_!xyM*P*~Ds(->RI}7SEQ#-cB zd<&t5X1s@<2l)FYRrbE;xz*aSS;HEYX_er$PttALoc~(LhrH_b#uA4?p6lv`gFLI{ zXD=Y?l+n>c*&8g}3Ej^h>X%g7Pcag$eOsJCkvD27R2Eq-D>==J0BFndn;WDg*Fi4r zXGsD6>jNO+c_q^KEU5nW#& eey5qH^fEo_A? z8(nJyO}bCDpIpfL 4YNK{g5;*f>KnJ%I8 zHW$s`eY!sCizpySXo5rYurqOWLFT-URQB|PWlKNlPh)0 T0sqU`45rfib}~_`ese0fD8>4;hG!r?yu+@9;1` zRChCM4Q<`Tz1&aqHB$e5c`joD1=3`(+Qy;w&ABNY#6VTSCbgJLU!YE970kk00@%jO zJ6`ojUhKfopFHj1C?P50)Pw$ZZoKxhwybE(Ju(;To9QFW`#W2(9Cj$DgNnEnSgAV3 z@79kdl%9W)t^HtdYKOk#chlbiUD}x7n7rhs;(_ayRiOB1#d`uhJ{z{T08Zwk(0_8> zmo=FqkPg9Kv(>dd;58$(>O;T(=$}?pZ9DG&n*SE@){$EaJwOZyw(gmu4?)xV&lI_c zAMD6JtaDcf3DI1uUuPBnBdbL{2<__3Sc Tc zPX29+-25=v|7aui>f6m34MT%6=W&EoY=j ?fp6@Fg-TE4?u?sRj0?25{BgZ;aTS4LdnOYXqMQjnskm$M0 z7ANZ9(`pBs@cacos1BZ8rEhbe@3R5|z2a-|M}9A{b4T$XKp`O^0Tz>rA4T9<`Lg0h z<8Kah!zG0e%wPGy)jSOjMBQdWzJCyAJcv^M`mE-u@fAK!iaTn=9*h|}HNHL`#0sD* z1UA#N#jS?D6q%I%5&fM{FhB@yU76Z0NqSWuGgi_H5!f`}oFL->FzV@xhe!$|2Yz^^ zk2_CXPq?6OIOBJ}|J~UTQVzIi=Ige i2pI zEOg#q3#F%aamHaw5`Q7AlKA8Rf8Q6M-Xbf6x!ppOZvhYb>zIT#E_GH2fzMVTs}Ghw z_YUKcssxH#-*#LLue;+EPzHy6pp5}hr+SZqo62aY*mbqP!ef_;ugw;=@Gvw4#g;@1 zn+!;D{!Q|j%`?D~G{c6Vg1 p5w1SH)aMY6w=Wj-|dtFW~ PJ`ntAF7U} zExR1B2&wv25nU#mDJ3Wr&uK6q8DX?XzRFI22Ma{FJ2( 2i1 zf|w`kphv8h9PilG7=X!~Z3AJT$h_e%_BU*CY>6*pU GH zI%0TIO3}+QpB$9q z<{u}FunU|PP@(NWgSiBmr*;$8sk^{Jg%tVjH8m9q3o+6?YiFwW2f@Ll!e#OWfj$PQ zOlUl37fvN`pdvQGVFeqE*?Ych(spNi3);C1?Z{>j^jyL+m97dJQ^|^HP6lAv+C^`P z62G56#Epz*$vYO9-_?ryi|;@nDR@r(8!T;oln)S63|aV*wB* hK7Sn zte9ONvl;C5&PA$k0 pm-Jdr7q$J- zDxj17Grs&79u`NL2*=a*XKPX}K`)EexZJgYOoLiXE(qD)+NhW@2HV&iB1HVHl4e}? z{GD92`MW8%TCYRsVup;rcq|Arh_e-hLv!%-fU@!oX|*4`R(C~hy1}$+-2)B4@n97m z^xLTdai|`O<99z&I|#mm^-b)|@bDb@{1m!(c^!|4hS0quunrrr>BL5Qet&b(eQ83? zfd%<8-yytvE`-tCW(O$1yU%nBftaZno9mEi(oIq8_zqj8nH+cqNjpTO1Oavmog95p z#CS;%y#ubN0)VOY1yh5XfiL`BM=L#@r%^{R_Ho`3sZ- NTu_WSJw{h!zm< z2D !)mb z=cgqo{* }PW=K_I!?Rr3be zH$#6A6MJ7lfxeJRM;nyy+Q^Z{aI)3 ?8atGh*kJ )ks0J%+DSm#Fb)0uGt|!| z)wnT!iGRc$c `d6cz0Ec)`B5h4j2q&+B*JdqGp8 z9!fw~P) 1m#jzC- zJ1SyC!K-Hh#V;odj^2sES=h*yFg5|_cy0S_*+r*;&9Gc4yf>77#vm}$-VWzRn!tOx zfv7ZbShv+M6A|_xUlF&;K%{%R0wP@bRoYyWnt7k`Po--#U+O(w2EiU`H}O5DJNd>z z%!GUj2W;2HmE&9xZxtU#Ep8z;PoU^JWxuz|w838^od@)XsS~kAS5M3RJslSj=7m&v zGj;RqTdpjH#OfLcqFx%oaE>cv(KkbAru;q;JU$i%H6*tssWi@42PcSr4IaU^-@au$ z6|-ENcvb(p=m7q}D^&*KKPpC W^ejtP@r{(citRR0zOWyK1gEeh$W#2d;jvvW`uo8k_z1+xt6S-g+} z=7J2LOs=TIKp&{+ukSRAWykJ(=1|EtSorwV7%L+?={7Ut)q2}U%?ShwxxJF;nWIx3 z4(1~Ztgg6j%DwT%*Y-&%0ALX9um24rMLcVgqX(%GU4l4)g}v&CDb+ew=Gfr`LCdxH z34TN3No 8ki9Z(41$%rCT7T1b*VOgi7&=zt3G!@s&c zM;$gcXNu *k7&Wr3i>JE_)N;2zj8>9U<1bQ<+f=Nu zZ*z8Grx#k!6JqUpJ;sx^PFg*}?m79Sbu)P~w60J&hOSDc!K-GedP8y#8(y?j{g|F< zncGY^3`v~*9WRETr2xqz=D0l-sjp*Y{Hn(%C&>U%`VVP9RC2_)QyA0`b$I $j#KJYVAL@sXx} 9q-$G+9o@)_&MKzrd=6>t;+Am})OJgE(tHbTJ&syoa@H(&ha0 zS+80g)i>UNV!ge^M@amosh2`s2RGTg*nBBc^vnrk(yQJPl#6TPQqNXqVzfsM@r5A5 zgOmUKbmgtQrC<38dhbsWjXWP$pZeK)1)Nrx`=!4kw)_#;WV6R id5y#cTgR*P zmagqKvOUY$AoMdH+st(c!QeJ~JeOAK#iqPZw=g93TwMOs1LQOZEI$VM!@hg#JH+g_ zk#|tbJnX6_GRgc50nnlkE7xXj<@0dNcyG|eZq9N3&?+UVhQ?zR<&bYqvF+{! zq;-?E(T2~mGR*gs-BiOvV`U!JdD8TOY5>iYu4R!_s!TEqJ6F4$Yo?AQYW003^2!ZY zQXxL8Z=4X8v`wb`)QKPCDmNXhkg+gMGVgl7x)}=URfV#-AOZUA30k7tb!j21((>wm zH1T&}`=`DC^DhmUoqtQs!xLe$jsqyfK4u;|7VG!6oH<9Eyg%QHO;CH{-=A%6ZhmGi zC^Cq-WO3VG-2+VBacqYU!NUGpuDjx(oaHnybOR!=c__$OdSL&|fm58)8}GO6$&QCF z+d^_w_f)<7KgncmhhWaq1hA(P%LJe9UZ{{c`)PzO(e2vdY@nh^ruByhqpf2lJ#w`( zZhr}yzzja<5OWxa`(2u52kwC)P>{T9gu;2`5-`X$0;XI>BG9`Ms0O^Mr|lAV_>~NM zhxG$O_K=gArIGXyfm9$5%H|y|7(j9f)NT+5j4n^orM<5oq7W2~N*RLFv@+nmQ0Axs z6mtWl@mX+O4jA)!gV4c?ly%7Ix87%aE=@cwzxNvZ^9t499>j(i5lh>l+5n`gs5@DS zC2~5ju=@FJQKEB eEu9R6(yBf4LFOy z{v;U6IAxe1VBdwn|4Ki^9euSWW3vIWUsWFUC3eJ#{J~?v*c(!Z(S2M`K??X?zuEVd z4$slPn}bTJnD6HO)l?0)O<&m+181DhLZLFoAE}J{hugx1kb(Q7%%qOgj{#ue=U_Sq zA?R^uxtk69i%6P+owC7YKR=|1t4kGwxL1!M?pK5(!$qR2S>UfhQZA*P>Q-uuY4ygu z Po-$i8y}cZY>JAL Yf?Usob +yQ(Xhk$0&&xb^;8d_zR`jq+$pkyduhmRtOW+X!i+u~AZ^5huuFp0m#5}$C_9IW zRDFMP9M;)9@l!BMg$04_N+*0|2GHE1_0XkWSY_K^LmzX+Oj51^D=CKDPM%w$r? u0ug@k5To52M14cX1)GMCfOllVqqYm(Pj=4R;rWE5T3Y}f*C>%hRq zp}68VVZ6x!X8 RBmN|bxgbiKiq}o0}#JkX$ z{Rum~r{W{8WzbmdAqc3cs*zIFxG*^+D7{v_9~ng|f2*Np9as8p@iL^5EbAWaMLY|b zoVdeN5=b4M{RuD9L WH~w#q@zbk&^aq+{70M#~ zdUc64ONa@3=Kbq)qMw=o3D07o A^#j_zJBJg!Rlj#nG)P?ssj4BY<& zr7|p{U)A{evB!6rab3m7_OkrD2 zN@qV q)aQgIj2IC_+c0uKn1C({7Is(wxtjdex+|ijavHaF!0I{5rq54|IF9PS8=iesa zmwSl VxsP>m7q`FWA2Oz@SQx*JRb@?)Lcl)TDns!>bCG!|3 zTyKjF8U;c=&<4(lqx);VD`_@Z(JB*PwB;CJba{D;<(|uyBL`Xq#}_SMP*)&dSXU_T znjr0CD6|a9&n|v$gX(wmdFC@+eQn$Rx0#o{7r(IgYdIsV=mm8FU|Cc_NaH1~c=%_N z(tw9>PK&_|Ko-oayth!ZQ*AJ;w$YLxv*2@eiXR=lU+oydZANExA*bxUXsde45q!BL zNFXb4A;4RS8l)xJsP^M*cx?p{{tzhf1f60&+WA-i^i$u3s@1+kVT$YygzS_0Pe!3Q z?rvgW_-Nwp*?n2ko`6|*=}@_al-m)g!wG(Q(sW!x_A b&CA;%24ilHnbl@b+4{ z*N*A*`liZZ^Kj(7F jbsT=^ZJu{_`s)jB8_-PLs=@_^k-%=yc@HdJxxxCH5mN_t-s7HK z3Q~Y}EsN{m=IRRn&X16?L8S6}&<=RbZ61Smo!%rpxi#b(CDRwYiC)$pgcQRq2`o}J zVH4DOr2mXY5qveMU)LJ~Y(RvtXS~GBs_CyIR3xpNm*N%S_h* $lN^mEa~b>QpU^FfWV1n&*lP5X6$AS;V=Lz7lmmFtL>Ca9;09eVukV}tqwiC? zVKIi+!01wpNWdmkiD!T_)gFX