diff --git a/notes/Git.md b/notes/Git.md
index a14b8e9c..3418d95e 100644
--- a/notes/Git.md
+++ b/notes/Git.md
@@ -39,20 +39,20 @@ Git 的中心服务器用来交换每个用户的修改。没有中心服务器
# Git 工作流
-
+
新建一个仓库之后,当前目录就成为了工作区,工作区下有一个隐藏目录 .git,它属于 Git 的版本库。
Git 版本库有一个称为 stage 的暂存区,还有自动创建的 master 分支以及指向分支的 HEAD 指针。
-
+
- git add files 把文件的修改添加到暂存区
- git commit 把暂存区的修改提交到当前分支,提交之后暂存区就被清空了
- git reset -- files 使用当前分支上的修改覆盖暂缓区,用来撤销最后一次 git add files
- git checkout -- files 使用暂存区的修改覆盖工作目录,用来撤销本地修改
-
+
可以跳过暂存区域直接从分支中取出修改或者直接提交修改到分支中
@@ -63,25 +63,25 @@ Git 版本库有一个称为 stage 的暂存区,还有自动创建的 master
Git 把每次提交都连成一条时间线。分支使用指针来实现,例如 master 分支指针指向时间线的最后一个节点,也就是最后一次提交。HEAD 指针指向的是当前分支。
-
+
新建分支是新建一个指针指向时间线的最后一个节点,并让 HEAD 指针指向新分支表示新分支成为当前分支。
-
+
每次提交只会让当前分支向前移动,而其它分支不会移动。
-
+
合并分支也只需要改变指针即可。
-
+
# 冲突
当两个分支都对同一个文件进行了修改,在分支合并时就会产生冲突。
-
+
Git 会使用 <<<<<<< ,======= ,>>>>>>> 标记出不同分支的内容,只需要把不同分支中冲突部分修改成一样就能解决冲突。
@@ -103,7 +103,7 @@ Creating a new branch is quick AND simple.
$ git merge --no-ff -m "merge with no-ff" dev
```
-
+
# 分支管理策略
@@ -111,7 +111,7 @@ master 分支应该是非常稳定的,只用来发布新版本;
日常开发在开发分支 dev 上进行。
-
+
# 储藏(Stashing)
@@ -151,6 +151,6 @@ $ ssh-keygen -t rsa -C "youremail@example.com"
# Git 命令一览
-
+
比较详细的地址:http://www.cheat-sheets.org/saved-copy/git-cheat-sheet.pdf
diff --git a/notes/HTTP.md b/notes/HTTP.md
index 1ae9578e..e038a98d 100644
--- a/notes/HTTP.md
+++ b/notes/HTTP.md
@@ -61,17 +61,17 @@
URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。
-
+
## 请求和响应报文
**请求报文**
-
+
**响应报文**
-
+
# HTTP 方法
@@ -152,7 +152,7 @@ DELETE /file.html HTTP/1.1
CONNECT www.example.com:443 HTTP/1.1
```
-
+
## TRACE:追踪路径
@@ -162,7 +162,7 @@ CONNECT www.example.com:443 HTTP/1.1
通常不会使用 TRACE,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪),因此更不会去使用它。
-
+
# HTTP 状态码
@@ -204,7 +204,7 @@ CONNECT www.example.com:443 HTTP/1.1
- **401 Unauthorized** :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。
-
+
- **403 Forbidden** :请求被拒绝,服务器端没有必要给出拒绝的详细理由。
@@ -385,13 +385,13 @@ Expires 字段也可以用于告知缓存服务器该资源什么时候会过期
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源,如果每进行一次 HTTP 通信就要断开一次 TCP 连接,连接建立和断开的开销会很大。持久连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
-
+
持久连接需要使用 Connection 首部字段进行管理。HTTP/1.1 开始 HTTP 默认是持久化连接的,如果要断开 TCP 连接,需要由客户端或者服务器端提出断开,使用 Connection : close;而在 HTTP/1.1 之前默认是非持久化连接的,如果要维持持续连接,需要使用 Connection : Keep-Alive。
**管线化方式** 可以同时发送多个请求和响应,而不需要发送一个请求然后等待响应之后再发下一个请求。
-
+
## 编码
@@ -448,7 +448,7 @@ Content-Length: 1024
涉及以下首部字段:Accept、Accept-Charset、Accept-Encoding、Accept-Language、Content-Language。
-
+
## 虚拟主机
@@ -464,19 +464,19 @@ Content-Length: 1024
使用代理的主要目的是:缓存、网络访问控制以及访问日志记录。
-
+
**网关**
与代理服务器不同的是,网关服务器会将 HTTP 转化为其它协议进行通信,从而请求其它非 HTTP 服务器的服务。
-
+
**隧道**
使用 SSL 等加密手段,为客户端和服务器之间建立一条安全的通信线路。
-
+
# HTTPs
@@ -496,7 +496,7 @@ HTTPs 并不是新协议,而是 HTTP 先和 SSL(Secure Socket Layer)通信
HTTPs 采用 **混合的加密机制** ,使用公开密钥加密用于传输对称密钥,之后使用对称密钥加密进行通信。(下图中,共享密钥即对称密钥)
-
+
## 认证
diff --git a/notes/JVM.md b/notes/JVM.md
index 78d24f77..a49135b3 100644
--- a/notes/JVM.md
+++ b/notes/JVM.md
@@ -65,7 +65,7 @@
# 内存模型
-
+
注:白色区域为线程私有的,蓝色区域为线程共享的。
@@ -224,7 +224,7 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
### 2.1 标记-清除算法
-
+
将需要回收的对象进行标记,然后清除。
@@ -237,7 +237,7 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
### 2.2 复制算法
-
+
将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。
@@ -247,7 +247,7 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
### 2.3 标记-整理算法
-
+
让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
@@ -262,13 +262,13 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
## 3. 垃圾收集器
-
+
以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。
### 3.1 Serial 收集器
-
+
它是单线程的收集器,不仅意味着只会使用一个线程进行垃圾收集工作,更重要的是它在进行垃圾收集时,必须暂停所有其他工作线程,往往造成过长的等待时间。
@@ -278,7 +278,7 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
### 3.2 ParNew 收集器
-
+
它是 Serial 收集器的多线程版本。
@@ -300,7 +300,7 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
### 3.4 Serial Old 收集器
-
+
Serial Old 是 Serial 收集器的老年代版本,也是给 Client 模式下的虚拟机使用。如果用在 Server 模式下,它有两大用途:
@@ -309,7 +309,7 @@ Serial Old 是 Serial 收集器的老年代版本,也是给 Client 模式下
### 3.5 Parallel Old 收集器
-
+
是 Parallel Scavenge 收集器的老年代版本。
@@ -317,7 +317,7 @@ Serial Old 是 Serial 收集器的老年代版本,也是给 Client 模式下
### 3.6 CMS 收集器
-
+
CMS(Concurrent Mark Sweep),从 Mark Sweep 可以知道它是基于标记-清除算法实现的。
@@ -342,7 +342,7 @@ CMS(Concurrent Mark Sweep),从 Mark Sweep 可以知道它是基于标记-
### 3.7 G1 收集器
-
+
G1(Garbage-First)收集器是当今收集器技术发展最前沿的成果之一,它是一款面向服务端应用的垃圾收集器,HotSpot 开发团队赋予它的使命是(在比较长期的)未来可以替换掉 JDK 1.5 中发布的 CMS 收集器。
@@ -439,7 +439,7 @@ JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才
## 1 类的生命周期
-
+
包括以下 7 个阶段:
@@ -627,7 +627,7 @@ public static void main(String[] args) {
应用程序都是由三种类加载器相互配合进行加载的,如果有必要,还可以加入自己定义的类加载器。下图展示的类加载器之间的层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。该模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器,这里类加载器之间的父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)的关系实现。
-
+
**工作过程**
diff --git a/notes/Java IO.md b/notes/Java IO.md
index 58709c31..4bfb4d46 100644
--- a/notes/Java IO.md
+++ b/notes/Java IO.md
@@ -49,7 +49,7 @@ File 类可以用于表示文件和目录,但是它只用于表示文件的信
# 字节操作
-
+
Java I/O 使用了装饰者模式来实现。以 InputStream 为例,InputStream 是抽象组件,FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作。FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能,例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。
@@ -150,7 +150,7 @@ is.close();
- Socket:客户端类
- 服务器和客户端通过 InputStream 和 OutputStream 进行输入输出。
-
+
## 4. Datagram
@@ -211,19 +211,19 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重
状态变量的改变过程:
1. 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 9。capacity 变量不会改变,下面的讨论会忽略它。
-
+
2. 从输入通道中读取 3 个字节数据写入缓冲区中,此时 position 移动设为 3,limit 保持不变。
-
+
3. 以下图例为已经从输入通道读取了 5 个字节数据写入缓冲区中。在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0。
-
+
4. 从缓冲区中取 4 个字节到输出缓冲中,此时 position 设为 4。
-
+
5. 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。
-
+
## 4. 文件 NIO 实例
@@ -284,7 +284,7 @@ buffer.clear();
服务端都会为每个连接的客户端创建一个线程来处理读写请求,阻塞式的特点会造成服务器会创建大量线程,并且大部分线程处于阻塞的状态,因此对服务器的性能会有很大的影响。
-
+
### 5.2 非阻塞式 I/O
@@ -294,7 +294,7 @@ buffer.clear();
线程通信:线程之间通过 wait()、notify() 等方式通信,保证每次上下文切换都是有意义的,减少无谓的线程切换。
-
+
## 6. 套接字 NIO 实例
diff --git a/notes/Java 基础.md b/notes/Java 基础.md
index 7c4964a2..33938e9a 100644
--- a/notes/Java 基础.md
+++ b/notes/Java 基础.md
@@ -145,13 +145,13 @@ protected void finalize() throws Throwable {}
引用类型引用同一个对象。clone() 方法默认就是浅拷贝实现。
-
+
**深拷贝**
可以使用序列化实现。
-
+
> [How do I copy an object in Java?](https://stackoverflow.com/questions/869033/how-do-i-copy-an-object-in-java)
@@ -293,7 +293,7 @@ StringBuilder 不是线程安全的;StringBuffer 是线程安全的,使用 s
如果 String 已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
-
+
**安全性**
@@ -448,7 +448,7 @@ Throwable 可以用来表示任何可以作为异常抛出的类,分为两种
Exception 分为两种: **受检异常** 和 **非受检异常**。受检异常需要用 try...catch... 语句捕获并进行处理,并且可以从异常中恢复;非受检异常是程序运行时错误,例如除 0 会引发 Arithmetic Exception,此时程序奔溃并且无法恢复。
-
+
更详细的内容:
- [Java 入门之异常处理](https://www.tianmaying.com/tutorial/Java-Exception)
diff --git a/notes/Java 容器.md b/notes/Java 容器.md
index 73a831fd..2da89cca 100644
--- a/notes/Java 容器.md
+++ b/notes/Java 容器.md
@@ -32,7 +32,7 @@
# 概览
-
+
容器主要包括 Collection 和 Map 两种,Collection 又包含了 List、Set 以及 Queue。
@@ -257,13 +257,13 @@ transient Entry[] table;
其中,Entry 就是存储数据的键值对,它包含了四个字段。从 next 字段我们可以看出 Entry 是一个链表,即每个桶会存放一个链表。
-
+
### 拉链法的工作原理
使用默认构造函数新建一个 HashMap,默认大小为 16。Entry 的类型为 <String, Integer>。先后插入三个元素:("sachin", 30), ("vishal", 20) 和 ("vaibhav", 20)。计算 "sachin" 的 hashcode 为 115,使用除留余数法得到 115 % 16 = 3,因此 ("sachin", 30) 键值对放到第 3 个桶上。同样得到 ("vishal", 20) 和 ("vaibhav", 20) 都应该放到第 6 个桶上,因此需要把 ("vaibhav", 20) 链接到 ("vishal", 20) 之后。
-
+
当进行查找时,需要分成两步进行,第一步是先根据 hashcode 计算出所在的桶,第二步是在链表上顺序查找。由于 table 是数组形式的,具有随机读取的特性,因此这一步的时间复杂度为 O(1),而第二步需要在链表上顺序查找,时间复杂度显然和链表的长度成正比。
diff --git a/notes/Java 并发.md b/notes/Java 并发.md
index bb498cb0..23fc6d1b 100644
--- a/notes/Java 并发.md
+++ b/notes/Java 并发.md
@@ -403,7 +403,7 @@ interrupted() 方法在检查完中断状态之后会清除中断状态,这样
# 线程状态转换
-
+
1. NEW(新建):创建后尚未启动的线程。
2. RUNNABLE(运行):处于此状态的线程有可能正在执行,也有可能正在等待着 CPU 为它分配执行时间。
@@ -454,7 +454,7 @@ volatile 关键字通过添加内存屏障的方式来进制指令重排,即
每个处理器都有一个高速缓存,但是所有处理器共用一个主内存,因此高速缓存引入了一个新问题:缓存一致性。当多个处理器的运算都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致。缓存不一致问题通常需要使用一些协议来解决。
-
+
除了增加高速缓存之外,为了使得处理器内部的运算单元能尽量被充分利用,处理器可能会对输入代码进行乱序执行(Out-Of-Order Execution)优化,处理器会在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果是一致的,但并不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致,因此,如果存在一个计算任务依赖另外一个计算任务的中间结果,那么其顺序性并不能靠代码的先后顺序来保证。与处理器的乱序执行优化类似,Java 虚拟机的即时编译器中也有类似的指令重排序(Instruction Reorder)优化。
@@ -468,7 +468,7 @@ Java 内存模型的主要目标是定义程序中各个变量的访问规则,
Java 内存模型规定了所有的变量都存储在主内存(Main Memory)中。每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成,线程、主内存、工作内存三者的交互关系如图所示。
-
+
## 4. 内存间交互操作
@@ -718,10 +718,10 @@ Thread printThread = new Thread(new Runnable() {
如果需要使用上述功能,选用 ReentrantLock 是一个很好的选择,那如果是基于性能考虑呢?关于 synchronized 和 ReentrantLock 的性能问题,Brian Goetz 对这两种锁在 JDK 1.5 与单核处理器,以及 JDK 1.5 与双 Xeon 处理器环境下做了一组吞吐量对比的实验,实验结果如图 13-1 和图 13-2 所示。
-
+
JDK 1.5、单核处理器下两种锁的吞吐量对比
-
+
JDK 1.5、双 Xeon 处理器下两种锁的吞吐量对比
多线程环境下 synchronized 的吞吐量下降得非常严重,而 ReentrantLock 则能基本保持在同一个比较稳定的水平上。与其说 ReentrantLock 性能好,还不如说 synchronized 还有非常大的优化余地。后续的技术发展也证明了这一点,JDK 1.6 中加入了很多针对锁的优化措施,JDK 1.6 发布之后,人们就发现 synchronized 与 ReentrantLock 的性能基本上是完全持平了。因此,如果读者的程序是使用 JDK 1.6 或以上部署的话,性能因素就不再是选择 ReentrantLock 的理由了,虚拟机在未来的性能改进中肯定也会更加偏向于原生的 synchronized,所以还是提倡在 synchronized 能实现需求的情况下,优先考虑使用 synchronized 来进行同步。
@@ -886,15 +886,15 @@ public static String concatString(String s1, String s2, String s3) {
对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Work 被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。例如,在 32 位的 HotSpot 虚拟机中对象未被锁定的状态下,Mark Word 的 32bit 空间中的 25bit 用于存储对象哈希码(HashCode),4bit 用于存储对象分代年龄,2bit 用于存储锁标志位,1bit 固定为 0,在其他状态(轻量级锁定、重量级锁定、GC 标记、可偏向)下对象的存储内容见表 13-1。
-
+
简单地介绍了对象的内存布局后,我们把话题返回到轻量级锁的执行过程上。在代码进入同步块的时候,如果此同步对象没有被锁定(锁标志位为 “01” 状态)虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的 Mark Word 的拷贝(官方把这份拷贝加上了一个 Displaced 前缀,即 Displaced Mark Word),这时候线程堆栈与对象头的状态如图 13-3 所示。
-
+
然后,虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象 Mark Word 的锁标志位 (Mark Word 的最后 2bit)将转变为 “00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图 12-4 所示。
-
+
如果这个更新操作失败了,虚拟机首先会检查对象的 Mark Word 是否指向当前线程的栈帧,如果只说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明这个锁对象以及被其他线程线程抢占了。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,所标志的状态变为 “10”,Mark Word 中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。
@@ -912,7 +912,7 @@ public static String concatString(String s1, String s2, String s3) {
当有另外一个线程去尝试获取这个锁时,偏向模式就宣告结束。根据锁对象目前是否处于被锁定的状态,撤销偏向(Revoke Bias)后恢复到未锁定(标志位为 “01”)或轻量级锁定(标志位为 “00”)的状态,后续的同步操作就如上面介绍的轻量级锁那样执行。偏向锁、轻量级锁的状态转换及对象 Mark Word 的关系如图 13-5 所示。
-
+
偏向锁可以提高带有同步但无竞争的程序性能。它同样是一个带有效益权衡(Trade Off)性质的优化,也就是说,它并不一定总是对程序运行有利,如果程序中大多数的锁总是被多个不同的线程访问,那偏向模式就是多余的。在具体问题具体分析的前提下,有时候使用参数 -XX:-UseBiasedLocking 来禁止偏向锁优化反而可以提升性能。
diff --git a/notes/Leetcode 题解.md b/notes/Leetcode 题解.md
index b0d25164..8ba46162 100644
--- a/notes/Leetcode 题解.md
+++ b/notes/Leetcode 题解.md
@@ -745,7 +745,7 @@ public List topKFrequent(int[] nums, int k) {
### BFS
-
+
广度优先搜索的搜索过程有点像一层一层地进行遍历:从节点 0 出发,遍历到 6、2、1 和 5 这四个新节点。
@@ -801,7 +801,7 @@ private class Position {
### DFS
-
+
广度优先搜索一层一层遍历,每一层遍历到的所有新节点,要用队列先存储起来以备下一层遍历的时候再遍历;而深度优先搜索在遍历到一个新节点时立马对新节点进行遍历:从节点 0 出发开始遍历,得到到新节点 6 时,立马对新节点 6 进行遍历,得到新节点 4;如此反复以这种方式遍历新节点,直到没有新节点了,此时返回。返回到根节点 0 的情况是,继续对根节点 0 进行遍历,得到新节点 2,然后继续以上步骤。
@@ -1087,7 +1087,7 @@ private void dfs(int r, int c, boolean[][] canReach) {
[Leetcode : 51. N-Queens (Hard)](https://leetcode.com/problems/n-queens/description/)
-
+
题目描述:在 n\*n 的矩阵中摆放 n 个皇后,并且每个皇后不能在同一行,同一列,同一对角线上,要求解所有的 n 皇后解。
@@ -1095,11 +1095,11 @@ private void dfs(int r, int c, boolean[][] canReach) {
45 度对角线标记数组的维度为 2\*n - 1,通过下图可以明确 (r,c) 的位置所在的数组下标为 r + c。
-
+
135 度对角线标记数组的维度也是 2\*n - 1,(r,c) 的位置所在的数组下标为 n - 1 - (r - c)。
-
+
```java
private List> ret;
@@ -1156,7 +1156,7 @@ private void backstracking(int row) {
[Leetcode : 17. Letter Combinations of a Phone Number (Medium)](https://leetcode.com/problems/letter-combinations-of-a-phone-number/description/)
-
+
```html
Input:Digit string "23"
@@ -1598,7 +1598,7 @@ private boolean isPalindrome(String s, int begin, int end) {
[Leetcode : 37. Sudoku Solver (Hard)](https://leetcode.com/problems/sudoku-solver/description/)
-
+
```java
private boolean[][] rowsUsed = new boolean[9][10];
@@ -2519,7 +2519,7 @@ public int minDistance(String word1, String word2) {
题目描述:交易之后需要有一天的冷却时间。
-
+
```html
s0[i] = max(s0[i - 1], s2[i - 1]); // Stay at s0, or rest from s2
@@ -4797,7 +4797,7 @@ private void inorder(TreeNode node, int k) {
### Trie
-
+
Trie,又称前缀树或字典树,用于判断字符串是否存在或者是否具有某种字符串前缀。
diff --git a/notes/Linux.md b/notes/Linux.md
index cf76b2c9..341de0aa 100644
--- a/notes/Linux.md
+++ b/notes/Linux.md
@@ -219,7 +219,7 @@ GPT 第 1 个区块记录了 MBR,紧接着是 33 个区块记录分区信息
GPT 没有扩展分区概念,都是主分区,最多可以分 128 个分区。
-
+
## 开机检测程序
@@ -229,7 +229,7 @@ BIOS 是开机的时候计算机执行的第一个程序,这个程序知道可
MBR 中的开机管理程序提供以下功能:选单、载入核心文件以及转交其它开机管理程序。转交这个功能可以用来实现了多重引导,只需要将另一个操作系统的开机管理程序安装在其它分区的启动扇区上,在启动 MBR 中的开机管理程序时,就可以选择启动当前的操作系统或者转交给其它开机管理程序从而启动另一个操作系统。
-
+
安装多重引导,最好先安装 Windows 再安装 Linux。因为安装 Windows 时会覆盖掉 MBR,而 Linux 可以选择将开机管理程序安装在 MBR 或者其它分区的启动扇区,并且可以设置开机管理程序的选单。
@@ -241,7 +241,7 @@ UEFI 相比于 BIOS 来说功能更为全面,也更为安全。
挂载利用目录作为分区的进入点,也就是说,进入目录之后就可以读取分区的数据。
-
+
# 文件权限与目录配置
@@ -340,7 +340,7 @@ UEFI 相比于 BIOS 来说功能更为全面,也更为安全。
完整的目录树如下:
-
+
# 文件与目录
@@ -501,7 +501,7 @@ find 可以使用文件的属性和权限进行搜索。
+4、4 和 -4 的指示的时间范围如下:
-
+
#### 4.2 与文件拥有者和所属群组有关的选项
@@ -543,7 +543,7 @@ find 可以使用文件的属性和权限进行搜索。
Ext2 文件系统使用了上述的文件结构,并在此之上加入了 block 群组的概念,也就是将一个文件系统划分为多个 block 群组,方便管理。
-
+
## inode
@@ -551,7 +551,7 @@ Ext2 文件系统支持的 block 大小有 1k、2k 和 4k 三种,不同的 blo
inode 中记录了文件内容所在的 block,但是每个 block 非常小,一个大文件随便都需要几十万的 block。而一个 inode 大小有限,无法直接引用这么多 block。因此引入了间接、双间接、三间接引用。间接引用是指,让 inode 记录的引用 block 块当成 inode 用来记录引用信息。
-
+
inode 具体包含以下信息:
@@ -1030,7 +1030,7 @@ daemon 2
# vim 三个模式
-
+
在指令列模式下,有以下命令用于离开或者存储文件。
diff --git a/notes/MySQL.md b/notes/MySQL.md
index cb67acd3..53b18b43 100644
--- a/notes/MySQL.md
+++ b/notes/MySQL.md
@@ -247,7 +247,7 @@ customer_id_selectivity: 0.0373
### 3.5 聚簇索引
-
+
聚簇索引并不是一种索引类型,而是一种数据存储方式。
@@ -282,7 +282,7 @@ customer_id_selectivity: 0.0373
### 4. 1 B-Tree
-
+
为了描述 B-Tree,首先定义一条数据记录为一个二元组 [key, data],key 为记录的键,data 为数据记录除 key 外的数据。
@@ -298,7 +298,7 @@ B-Tree 是满足下列条件的数据结构:
### 4.2 B+Tree
-
+
与 B-Tree 相比,B+Tree 有以下不同点:
@@ -307,7 +307,7 @@ B-Tree 是满足下列条件的数据结构:
### 4.3 带有顺序访问指针的 B+Tree
-
+
一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 基础上进行了优化,在叶子节点增加了顺序访问指针,做这个优化的目的是为了提高区间访问的性能。
@@ -424,7 +424,7 @@ do {
通过代理,可以路由流量到可以使用的服务器上。
-
+
**在应用中处理故障转移**
diff --git a/notes/Redis.md b/notes/Redis.md
index 36c036f9..9e941e8c 100644
--- a/notes/Redis.md
+++ b/notes/Redis.md
@@ -10,9 +10,11 @@
* [发布与订阅](#发布与订阅)
* [事务](#事务)
* [持久化](#持久化)
- * [1. 快照持久化](#1-快照持久化)
- * [2. AOF 持久化](#2-aof-持久化)
+ * [快照持久化](#快照持久化)
+ * [AOF 持久化](#aof-持久化)
* [复制](#复制)
+ * [从服务器连接主服务器的过程](#从服务器连接主服务器的过程)
+ * [主从链](#主从链)
* [处理故障](#处理故障)
* [分片](#分片)
* [事件](#事件)
@@ -212,7 +214,7 @@ MULTI 和 EXEC 中的操作将会一次性发送给服务器,而不是一条
Redis 是内存型数据库,为了保证数据在断电后不会丢失,需要将内存中的数据持久化到硬盘上。
-## 1. 快照持久化
+## 快照持久化
将某个时间点的所有数据都存放到硬盘上。
@@ -220,7 +222,7 @@ Redis 是内存型数据库,为了保证数据在断电后不会丢失,需
如果系统发生故障,将会丢失最后一次创建快照之后的数据。并且如果数据量很大,保存快照的时间也会很长。
-## 2. AOF 持久化
+## AOF 持久化
AOF 持久化将写命令添加到 AOF 文件(Append Only File)的末尾。
@@ -242,17 +244,17 @@ always 选项会严重减低服务器的性能;everysec 选项比较合适,
一个从服务器只能有一个主服务器,并且不支持主主复制。
-**1. 从服务器连接主服务器的过程**
+## 从服务器连接主服务器的过程
-(1) 主服务器创建快照文件,发送给从服务器,并在发送期间使用缓冲区记录执行的写命令。快照文件发送完毕之后,开始向从服务器发送存储在缓冲区中的写命令;
-(2) 从服务器丢弃所有旧数据,载入主服务器发来的快照文件,之后从服务器开始接受主服务器发来的写命令;
-(3) 主服务器每执行一次写命令,就向从服务器发送相同的写命令。
+- 主服务器创建快照文件,发送给从服务器,并在发送期间使用缓冲区记录执行的写命令。快照文件发送完毕之后,开始向从服务器发送存储在缓冲区中的写命令;
+- 从服务器丢弃所有旧数据,载入主服务器发来的快照文件,之后从服务器开始接受主服务器发来的写命令;
+- 主服务器每执行一次写命令,就向从服务器发送相同的写命令。
-**2. 主从链**
+## 主从链
-随着负载不断上升,主服务器可能无法很快地更新所有从服务器,或者重新连接和重新同步从服务器而导致系统超载。为了解决这个问题,可以创建一个中间层来分担主服务器的复制工作。中间层的服务器是最上层服务器的从服务器,又是最下层服务器的主服务器。
+随着负载不断上升,主服务器可能无法很快地更新所有从服务器,或者重新连接和重新同步从服务器将导致系统超载。为了解决这个问题,可以创建一个中间层来分担主服务器的复制工作。中间层的服务器是最上层服务器的从服务器,又是最下层服务器的主服务器。
-
+
# 处理故障
@@ -338,7 +340,7 @@ def main():
事件处理的角度下服务器运行流程如下:
-
+
# Redis 与 Memcached 的区别
@@ -411,7 +413,7 @@ Redis 这种内存数据库才能支持计数器的频繁读写操作。
Redis 没有表的概念将同类型的数据存放在一起,而是使用命名空间的方式来实现这一功能。键名的前面部分存储命名空间,后面部分的内容存储 ID,通常使用 : 来进行分隔。例如下面的 HASH 的键名为 article:92617,其中 article 为命名空间,ID 为 92617。
-
+
**2. 点赞功能**
@@ -419,13 +421,13 @@ Redis 没有表的概念将同类型的数据存放在一起,而是使用命
为了节约内存,规定一篇文章发布满一周之后,就不能再对它进行投票,而文章的已投票集合也会被删除,可以为文章的已投票集合设置一个一周的过期时间就能实现这个规定。
-
+
**3. 对文章进行排序**
为了按发布时间和点赞数进行排序,可以建立一个文章发布时间的有序集合和一个文章点赞数的有序集合。(下图中的 score 就是这里所说的点赞数;下面所示的有序集合分值并不直接是时间和点赞数,而是根据它们间接计算出来的)
-
+
# 参考资料
diff --git a/notes/SQL.md b/notes/SQL.md
index d9a82b2d..20d5c00d 100644
--- a/notes/SQL.md
+++ b/notes/SQL.md
@@ -710,7 +710,7 @@ SHOW GRANTS FOR myuser;
GRANT SELECT, INSERT ON mydatabase.* TO myuser;
```
-
+
账户用 username@host 的形式定义,username@% 使用的是默认主机名。
diff --git a/notes/代码可读性.md b/notes/代码可读性.md
index 28b61ed8..0c5ab772 100644
--- a/notes/代码可读性.md
+++ b/notes/代码可读性.md
@@ -44,7 +44,7 @@
用 min、max 表示数量范围;用 first、last 表示访问空间的包含范围,begin、end 表示访问空间的排除范围,即 end 不包含尾部。
-
+
布尔相关的命名加上 is、can、should、has 等前缀。
diff --git a/notes/剑指 offer 题解.md b/notes/剑指 offer 题解.md
index d7f91dbb..f7492747 100644
--- a/notes/剑指 offer 题解.md
+++ b/notes/剑指 offer 题解.md
@@ -302,7 +302,7 @@ private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int[] in,
- 如果一个节点有右子树不为空,那么该节点的下一个节点是右子树的最左节点;
- 否则,向上找第一个左链接指向的树包含该节点的祖先节点。
-
+
```java
public TreeLinkNode GetNext(TreeLinkNode pNode) {
@@ -679,12 +679,12 @@ private void printNumber(char[] number) {
- 如果链表不是尾节点,那么可以直接将下一个节点的值赋给节点,令节点指向下下个节点,然后删除下一个节点,时间复杂度为 O(1)。
-
+
- 否则,就需要先遍历链表,找到节点的前一个节点,然后让前一个节点指向节点的下一个节点,时间复杂度为 O(N)。
-
+
- 综上,如果进行 N 次操作,那么大约需要移动节点的次数为 N-1+N=2N-1,其中 N-1 表示不是链表尾节点情况下的移动次数,N 表示是尾节点情况下的移动次数。(2N-1)/N \~ 2,因此该算法的时间复杂度为 O(1)。
@@ -832,7 +832,7 @@ public void reOrderArray(int[] array) {
设链表的长度为 N。设两个指针 P1 和 P2,先让 P1 移动 K 个节点,则还有 N - K 个节点可以移动。此时让 P1 和 P2 同时移动,可以知道当 P1 移动到链表结尾时,P2 移动到 N - K 个节点处,该位置就是倒数第 K 个节点。
-
+
```java
public ListNode FindKthToTail(ListNode head, int k) {
@@ -857,7 +857,7 @@ public ListNode FindKthToTail(ListNode head, int k) {
在相遇点,slow 要到环的入口点还需要移动 z 个节点,如果让 fast 重新从头开始移动,并且速度变为每次移动一个节点,那么它到环入口点还需要移动 x 个节点。在上面已经推导出 x=z,因此 fast 和 slow 将在环入口点相遇。
-
+
```java
public ListNode EntryNodeOfLoop(ListNode pHead) {
@@ -902,7 +902,7 @@ public ListNode ReverseList(ListNode head) {
**题目描述**
-
+
```java
public ListNode Merge(ListNode list1, ListNode list2) {
@@ -928,7 +928,7 @@ public ListNode Merge(ListNode list1, ListNode list2) {
**题目描述**
-
+
```java
public boolean HasSubtree(TreeNode root1, TreeNode root2) {
@@ -951,7 +951,7 @@ private boolean isSubtree(TreeNode root1, TreeNode root2) {
**题目描述**
-
+
```java
public void Mirror(TreeNode root) {
@@ -1027,7 +1027,7 @@ private int height(TreeNode root) {
下图的矩阵顺时针打印结果为:1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10
-
+
```java
public ArrayList printMatrix(int[][] matrix) {
@@ -1105,7 +1105,7 @@ public boolean IsPopOrder(int[] pushA, int[] popA) {
例如,以下二叉树层次遍历的结果为 8, 6, 10, 5, 7, 9, 11
-
+
**解题思路**
@@ -1201,7 +1201,7 @@ public ArrayList> Print(TreeNode pRoot) {
例如,下图中后序遍历序列 5, 7, 6, 9, 11, 10, 8 所对应的二叉搜索树。
-
+
```java
public boolean VerifySquenceOfBST(int[] sequence) {
@@ -1229,7 +1229,7 @@ private boolean verify(int[] sequence, int first, int last) {
下图的二叉树有两条和为 22 的路径:10, 5, 7 和 10, 12
-
+
```java
private ArrayList> ret = new ArrayList<>();
@@ -1259,21 +1259,21 @@ private void dfs(TreeNode node, int target, int curSum, ArrayList path)
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的 head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
-
+
**解题思路**
第一步,在每个节点的后面插入复制的节点。
-
+
第二步,对复制节点的 random 链接进行赋值。
-
+
第三步,拆分。
-
+
```java
public RandomListNode Clone(RandomListNode pHead) {
@@ -1315,7 +1315,7 @@ public RandomListNode Clone(RandomListNode pHead) {
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
-
+
```java
private TreeNode pre = null;
diff --git a/notes/数据库系统原理.md b/notes/数据库系统原理.md
index 47394ee8..a38880b7 100644
--- a/notes/数据库系统原理.md
+++ b/notes/数据库系统原理.md
@@ -128,7 +128,7 @@ MySQL 中主要提供了两种封锁粒度:行级锁以及表级锁。
## 三级封锁协议
-
+
**一级封锁协议**
@@ -191,7 +191,7 @@ MySQL InnoDB 采用的是两阶段锁协议。在事务执行过程中,随时
以下关系中,Sno 表示学号,Sname 表示学生姓名,Sdept 表示学院,Cname 表示课程名,Mname 表示院长姓名。函数依赖为 {Sno, Cname} -> {Sname, Sdept, Mname}。
-
+
不符合范式的关系,会产生很多异常。主要有以下四种异常:
@@ -228,13 +228,13 @@ MySQL InnoDB 采用的是两阶段锁协议。在事务执行过程中,随时
-
+
-
+
## 第三范式 (3NF)
@@ -242,7 +242,7 @@ MySQL InnoDB 采用的是两阶段锁协议。在事务执行过程中,随时
上述 S1 存在传递依赖,Mname 依赖于 Sdept,而 Sdept 又依赖于 Sno,可以继续分解。
-
+
## BC 范式(BCNF)
@@ -324,29 +324,29 @@ Entity-Relationship,有三个组成部分:实体、属性、联系。
如果 A 到 B 是 1 对多关系,那么画个带箭头的线段指向 B;如果是 1 对 1,画两个带箭头的线段;如果是多对多,画两个不带箭头的线段。下图的 Course 和 Student 是 1 对多的关系。
-
+
## 表示出现多次的关系
一个实体在联系出现几次,就要用几条线连接。下图表示一个课程的先修关系,先修关系出现两个 Course 实体,第一个是先修课程,后一个是后修课程,因此需要用两条线来表示这种关系。
-
+
## 联系的多向性
虽然老师可以开设多门课,并且可以教授多名学生,但是对于特定的学生和课程,只有一个老师教授,这就构成了一个三元联系。
-
+
一般只使用二元联系,可以把多元关系转换为二元关系。
-
+
## 表示子类
用 IS-A 联系来表示子类,具体做法是用一个三角形和两条线来连接类和子类。与子类有关的属性和联系都连到子类上,而与父类和子类都有关的连到父类上。
-
+
# 一些概念
diff --git a/notes/算法.md b/notes/算法.md
index 06a18cf7..1f02fbc1 100644
--- a/notes/算法.md
+++ b/notes/算法.md
@@ -95,7 +95,7 @@
转换为
-
+
## 2. 数学模型
@@ -103,13 +103,13 @@
使用 \~f(N) 来表示所有随着 N 的增大除以 f(N) 的结果趋近于 1 的函数 , 例如 N3/6-N2/2+N/3 \~ N3/6。
-
+
**增长数量级**
增长数量级将算法与它的实现隔离开来,一个算法的增长数量级为 N3 与它是否用 Java 实现,是否运行于特定计算机上无关。
-
+
**内循环**
@@ -174,7 +174,7 @@ public class ThreeSumFast {
如果 T(N) \~ aNblgN,那么 T(2N)/T(N) \~ 2b,例如对于暴力方法的 ThreeSum 算法,近似时间为 \~N3/6,对它进行倍率实验得到如下结果:
-
+
可见 T(2N)/T(N)\~23,也就是 b 为 3。
@@ -365,11 +365,11 @@ public class Queue- {
用于解决动态连通性问题,能动态连接两个点,并且判断两个点是否连接。
-
+
**API**
-
+
**基本数据结构**
@@ -416,7 +416,7 @@ public class UF {
在 union 时只将触点的 id 值指向另一个触点 id 值,不直接用 id 来存储所属的连通分量。这样就构成一个倒置的树形结构,根节点需要指向自己。在进行查找一个节点所属的连通分量时,要一直向上查找直到根节点,并使用根节点的 id 值作为本连通分量的 id 值。
-
+
```java
public int find(int p) {
@@ -434,7 +434,7 @@ public class UF {
这种方法可以快速进行 union 操作,但是 find 操作和树高成正比,最坏的情况下树的高度为触点的数目。
-
+
## 3. 加权 quick-union 算法
@@ -442,7 +442,7 @@ public class UF {
理论研究证明,加权 quick-union 算法构造的树深度最多不超过 lgN。
-
+
```java
public class WeightedQuickUnionUF {
@@ -489,7 +489,7 @@ public class WeightedQuickUnionUF {
## 5. 各种 union-find 算法的比较
-
+
# 排序
@@ -519,7 +519,7 @@ private void exch(Comparable[] a, int i, int j){
找到数组中的最小元素,将它与数组的第一个元素交换位置。再从剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。
-
+
```java
public class Selection {
@@ -542,7 +542,7 @@ public class Selection {
入排序从左到右进行,每次都将当前元素插入到左部已经排序的数组中,使得插入之后左部数组依然有序。
-
+
```java
public class Insertion {
@@ -573,7 +573,7 @@ public class Insertion {
希尔排序使用插入排序对间隔 h 的序列进行排序,如果 h 很大,那么元素就能很快的移到很远的地方。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序的。
-
+
```java
public class Shell {
@@ -601,7 +601,7 @@ public class Shell {
归并排序的思想是将数组分成两部分,分别进行排序,然后归并起来。
-
+
### 2.1 归并方法
@@ -645,9 +645,9 @@ private static void sort(Comparable[] a, int lo, int hi) {
}
```
-
+
-
+
因为每次都将问题对半分成两个子问题,而这种对半分的算法复杂度一般为 O(NlgN),因此该归并排序方法的时间复杂度也为 O(NlgN)。
@@ -657,7 +657,7 @@ private static void sort(Comparable[] a, int lo, int hi) {
先归并那些微型数组,然后成对归并得到的子数组。
-
+
```java
public static void busort(Comparable[] a) {
@@ -677,7 +677,7 @@ public static void busort(Comparable[] a) {
归并排序将数组分为两个子数组分别排序,并将有序的子数组归并使得整个数组排序;快速排序通过一个切分元素将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。
-
+
```java
public class QuickSort {
@@ -699,7 +699,7 @@ public class QuickSort {
取 a[lo] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素,并不断继续这个过程,就可以保证左指针的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[lo] 和左子数组最右侧的元素 a[j] 交换然后返回 j 即可。
-
+
```java
private static int partition(Comparable[] a, int lo, int hi) {
@@ -740,7 +740,7 @@ private static int partition(Comparable[] a, int lo, int hi) {
三向切分快速排序对于只有若干不同主键的随机数组可以在线性时间内完成排序。
-
+
```java
public class Quick3Way {
@@ -770,7 +770,7 @@ public class Quick3Way {
堆可以用数组来表示,因为堆是一种完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。这里我们不使用数组索引为 0 的位置,是为了更清晰地理解节点的关系。
-
+
```java
public class MaxPQ {
@@ -861,7 +861,7 @@ public Key delMax() {
无序数组建立堆最直接的方法是从左到右遍历数组,然后进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,因此可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。
-
+
```java
public static void sort(Comparable[] a){
@@ -890,7 +890,7 @@ public static void sort(Comparable[] a){
### 5.1 排序算法的比较
-
+
快速排序时最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间增长数量级为 \~cNlgN,这里的 c 比其他线性对数级别的排序算法都要小。使用三向切分之后,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。
@@ -927,11 +927,11 @@ public static Comparable select(Comparable[] a, int k) {
### 1.1 无序符号表
-
+
### 1.2 有序符号表
-
+
有序符号表的键需要实现 Comparable 接口。
@@ -1010,7 +1010,7 @@ public class BinarySearchST, Value> {
**二叉查找树** (BST)是一颗二叉树,并且每个节点的键都大于其左子树中的任意节点的键而小于右子树的任意节点的键。
-
+
二叉查找树的查找操作每次迭代都会让区间减少一半,和二分查找类似。
@@ -1083,7 +1083,7 @@ private Node put(Node x, Key key, Value val) {
二叉查找树的算法运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。最好的情况下树是完全平衡的,每条空链接和根节点的距离都为 lgN。在最坏的情况下,树的高度为 N。
-
+
复杂度:查找和插入操作都为对数级别。
@@ -1139,7 +1139,7 @@ private Node min(Node x) {
令指向最小节点的链接指向最小节点的右子树。
-
+
```java
public void deleteMin() {
@@ -1157,7 +1157,7 @@ public Node deleteMin(Node x) {
如果待删除的节点只有一个子树,那么只需要让指向待删除节点的链接指向唯一的子树即可;否则,让右子树的最小节点替换该节点。
-
+
```java
public void delete(Key key) {
@@ -1209,7 +1209,7 @@ private void keys(Node x, Queue queue, Key lo, Key hi) {
### 3.1 2-3 查找树
-
+
一颗完美平衡的 2-3 查找树的所有空链接到根节点的距离应该是相同的。
@@ -1217,7 +1217,7 @@ private void keys(Node x, Queue queue, Key lo, Key hi) {
当插入之后产生一个临时 4- 节点时,需要将 4- 节点分裂成 3 个 2- 节点,并将中间的 2- 节点移到上层节点中。如果上移操作继续产生临时 4- 节点则一直进行分裂上移,直到不存在临时 4- 节点。
-
+
#### 3.1.2 性质
@@ -1229,7 +1229,7 @@ private void keys(Node x, Queue queue, Key lo, Key hi) {
2-3 查找树需要用到 2- 节点和 3- 节点,红黑树使用红链接来实现 3- 节点。指向一个节点的链接颜色如果为红色,那么这个节点和上层节点表示的是一个 3- 节点,而黑色则是普通链接。
-
+
红黑树具有以下性质:
@@ -1238,7 +1238,7 @@ private void keys(Node x, Queue queue, Key lo, Key hi) {
画红黑树时可以将红链接画平。
-
+
```java
public class RedBlackBST, Value> {
@@ -1272,9 +1272,9 @@ public class RedBlackBST, Value> {
因为合法的红链接都为左链接,如果出现右链接为红链接,那么就需要进行左旋转操作。
-
+
-
+
```java
public Node rotateLeft(Node h) {
@@ -1293,9 +1293,9 @@ public Node rotateLeft(Node h) {
进行右旋转是为了转换两个连续的左红链接,这会在之后的插入过程中探讨。
-
+
-
+
```java
public Node rotateRight(Node h) {
@@ -1313,9 +1313,9 @@ public Node rotateRight(Node h) {
一个 4- 节点在红黑树中表现为一个节点的左右子节点都是红色的。分裂 4- 节点除了需要将子节点的颜色由红变黑之外,同时需要将父节点的颜色由黑变红,从 2-3 树的角度看就是将中间节点移到上层节点。
-
+
-
+
```java
void flipColors(Node h){
@@ -1333,7 +1333,7 @@ void flipColors(Node h){
- 如果左子节点是红色的且它的左子节点也是红色的,进行右旋转;
- 如果左右子节点均为红色的,进行颜色转换。
-
+
```java
public void put(Key key, Value val) {
@@ -1369,11 +1369,11 @@ private Node put(Node x, Key key, Value val) {
2. 如果当前节点的左子节点是 2- 节点而它的兄弟节点不是 2- 节点,向兄弟节点拿一个 key 过来;
3. 如果当前节点的左子节点和它的兄弟节点都是 2- 节点,将左子节点、父节点中的最小键和最近的兄弟节点合并为一个 4- 节点。
-
+
最后得到一个含有最小键的 3- 节点或者 4- 节点,直接从中删除。然后再从头分解所有临时的 4- 节点。
-
+
#### 3.2.6 分析
@@ -1449,7 +1449,7 @@ public class Transaction{
拉链法使用链表来存储 hash 值相同的键,从而解决冲突。此时查找需要分两步,首先查找 Key 所在的链表,然后在链表中顺序查找。
-
+
对于 N 个键,M 条链表 (N>M),如果哈希函数能够满足均匀性的条件,每条链表的大小趋向于 N/M,因此未命中的查找和插入操作所需要的比较次数为 \~N/M。
@@ -1457,7 +1457,7 @@ public class Transaction{
线性探测法使用空位来解决冲突,当冲突发生时,向前探测一个空位来存储冲突的键。使用线程探测法,数组的大小 M 应当大于键的个数 N(M>N)。
-
+
```java
public class LinearProbingHashST {
@@ -1551,7 +1551,7 @@ public void delete(Key key) {
α = N/M,把 α 称为利用率。理论证明,当 α 小于 1/2 时探测的预计次数只在 1.5 到 2.5 之间。
-
+
为了保证散列表的性能,应当调整数组的大小,使得 α 在 [1/4, 1/2] 之间。
@@ -1576,13 +1576,13 @@ private void resize(int cap) {
虽然每次重新调整数组都需要重新把每个键值对插入到散列表,但是从摊还分析的角度来看,所需要的代价却是很小的。从下图可以看出,每次数组长度加倍后,累计平均值都会增加 1,因为表中每个键都需要重新计算散列值,但是随后平均值会下降。
-
+
## 5. 应用
### 5.1 各种符号表实现的比较
-
+
应当优先考虑散列表,当需要有序性操作时使用红黑树。
diff --git a/notes/计算机操作系统.md b/notes/计算机操作系统.md
index 1ad98c99..6a85d3ab 100644
--- a/notes/计算机操作系统.md
+++ b/notes/计算机操作系统.md
@@ -177,7 +177,7 @@
## 进程状态的切换
-
+
阻塞状态是缺少需要的资源从而由运行状态转换而来,但是该资源不包括 CPU,缺少 CPU 会让进程从运行态转换为就绪态。
@@ -227,7 +227,7 @@ shortest remaining time next(SRTN)。
#### 2.3 多级反馈队列
-
+
如果一个进程需要执行 100 个时间片,如果采用轮转调度算法,那么需要交换 100 次。多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如 1,2,4,8,..。进程在第一个队列没执行完,就会被移到下一个队列。这种方式下,之前的进程只需要 7 (包括最初的装入)的交换。
@@ -475,7 +475,7 @@ void writer() {
### 2. 哲学家进餐问题
-
+
五个哲学家围着一张圆桌,每个哲学家面前放着食物。哲学家的生活有两种交替活动:吃饭以及思考。当一个哲学家吃饭时,需要先拿起筷子左右的两根筷子,并且一次只能拿起一根筷子。
@@ -553,7 +553,7 @@ void test(i) { // 尝试拿起两把筷子
## 死锁的必要条件
-
+
1. 互斥:每个资源要么已经分配给了一个进程,要么就是可用的。
2. 占有和等待:已经得到了某个资源的进程可以再请求新的资源。
@@ -576,7 +576,7 @@ void test(i) { // 尝试拿起两把筷子
#### 2.1 每种类型一个资源的死锁检测
-
+
上图为资源分配图,其中方框表示资源,圆圈表示进程。资源指向进程表示该资源已经分配给该进程,进程指向资源表示进程请求获取该资源。
@@ -586,7 +586,7 @@ void test(i) { // 尝试拿起两把筷子
#### 2.2 每种类型多个资源的死锁检测
-
+
上图中,有三个进程四个资源,每个数据代表的含义如下:
@@ -635,7 +635,7 @@ void test(i) { // 尝试拿起两把筷子
#### 4.1 安全状态
-
+
图 a 的第二列 Has 表示已拥有的资源数,第三列 Max 表示总共需要的资源数,Free 表示还有可以使用的资源数。从图 a 开始出发,先让 B 拥有所需的所有资源(图 b),运行结束后释放 B,此时 Free 变为 5(图 c);接着以同样的方式运行 C 和 A,使得所有进程都能成功运行,因此可以称图 a 所示的状态时安全的。
@@ -647,13 +647,13 @@ void test(i) { // 尝试拿起两把筷子
一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。
-
+
上图 c 为不安全状态,因此算法会拒绝之前的请求,从而避免进入图 c 中的状态。
#### 4.3 多个资源的银行家算法
-
+
上图中有五个进程,四个资源。左边的图表示已经分配的资源,右边的图表示还需要分配的资源。最右边的 E、P 以及 A 分别表示:总资源、已分配资源以及可用资源,注意这三个为向量,而不是具体数值,例如 A=(1020),表示 4 个资源分别还剩下 1/0/2/0。
@@ -675,7 +675,7 @@ void test(i) { // 尝试拿起两把筷子
大部分虚拟内存系统都使用分页技术。把由程序产生的地址称为虚拟地址,它们构成了一个虚拟地址空间。例如有一台计算机可以产生 16 位地址,它的虚拟地址空间为 0\~64K,然而计算机只有 32KB 的物理内存,因此虽然可以编写 64KB 的程序,但它们不能被完全调入内存运行。
-
+
虚拟地址空间划分成固定大小的页,在物理内存中对应的单元称为页框,页和页框大小通常相同,它们之间通过页表进行映射。
@@ -683,11 +683,11 @@ void test(i) { // 尝试拿起两把筷子
### 2. 分段
-
+
上图为一个编译器在编译过程中建立的多个表,有 4 个表是动态增长的,如果使用分页系统的一维地址空间,动态增长的特点会导致覆盖问题的出现。
-
+
分段的做法是把每个表分成段,一个段构成一个独立的地址空间。每个段的长度可以不同,并且可以动态增长。
@@ -741,7 +741,7 @@ void test(i) { // 尝试拿起两把筷子
-
+
### 4. 时钟(Clock)
diff --git a/notes/计算机网络.md b/notes/计算机网络.md
index 7adb340a..a9ebd753 100644
--- a/notes/计算机网络.md
+++ b/notes/计算机网络.md
@@ -100,7 +100,7 @@
网络把主机连接起来,而互联网是把多种不同的网络连接起来,因此互联网是网络的网络。
-
+
## ISP
@@ -110,14 +110,14 @@
互联网交换点 IXP 允许两个 ISP 直接相连而不用经过第三个 ISP。
-
+
## 互联网的组成
1. 边缘部分:所有连接在互联网上的主机,用户可以直接使用;
2. 核心部分:由大量的网络和连接这些网络的路由器组成,为边缘部分的主机提供服务。
-
+
## 主机之间的通信方式
@@ -131,7 +131,7 @@
## 电路交换与分组交换
-
+
### 1. 电路交换
@@ -145,7 +145,7 @@
分组交换也使用了存储转发,但是转发的是分组而不是报文。把整块数据称为一个报文,由于一个报文可能很长,需要先进行切分,来满足分组能处理的大小。在每个切分的数据前面加上首部之后就成为了分组,首部包含了目的地址和源地址等控制信息。
-
+
存储转发允许在一条传输线路上传送多个主机的分组,因此两个用户之间的通信不需要占用端到端的线路资源。
@@ -155,7 +155,7 @@
总时延 = 发送时延 + 传播时延 + 处理时延 + 排队时延
-
+
### 1. 发送时延
@@ -183,7 +183,7 @@
## 计算机网络体系结构*
-
+
### 1. 七层协议
@@ -210,7 +210,7 @@
路由器只有下面三层协议,因为路由器位于网络核心中,不需要为进程或者应用程序提供服务,因此也就不需要运输层和应用层。
-
+
### 4. TCP/IP 体系结构
@@ -218,11 +218,11 @@
现在的 TCP/IP 体系结构不严格遵循 OSI 分层概念,应用层可能会直接使用 IP 层或者网络接口层。
-
+
TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中占用举足轻重的地位。
-
+
# 第二章 物理层
@@ -236,7 +236,7 @@ TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中
模拟信号是连续的信号,数字信号是离散的信号。带通调制把数字信号转换为模拟信号。
-
+
## 信道复用技术
@@ -246,19 +246,19 @@ TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中
使用这两种方式进行通信,在通信的过程中用户会一直占用一部分信道资源。但是由于计算机数据的突发性质,没必要一直占用信道资源而不让出给其它用户使用,因此这两种方式对信道的利用率都不高。
-
+
### 2. 统计时分复用
是对时分复用的一种改进,不固定每个用户在时分复用帧中的位置,只要有数据就集中起来组成统计时分复用帧然后发送。
-
+
### 3. 波分复用
光的频分复用。由于光的频率很高,因此习惯上用波长而不是频率来表示所使用的光载波。
-
+
### 4. 码分复用
@@ -280,7 +280,7 @@ TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中
码分复用需要发送的数据量为原先的 m 倍。
-
+
# 第三章 数据链路层
@@ -290,7 +290,7 @@ TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中
将网络层传下来的分组添加首部和尾部,用于标记帧的开始和结束。
-
+
### 2. 透明传输
@@ -298,7 +298,7 @@ TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中
帧中有首部和尾部,如果帧的数据部分含有和首部尾部相同的内容,那么帧的开始和结束位置就会被错误的判定。需要在数据中出现首部尾部相同的内容前面插入转义字符,如果需要传输的内容正好就是转义字符,那么就在转义字符前面再加个转义字符,在接收端进行处理之后可以还原出原始数据。这个过程透明传输的内容是转义字符,用户察觉不到转义字符的存在。
-
+
### 3. 差错检测
@@ -308,7 +308,7 @@ TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中
互联网用户通常需要连接到某个 ISP 之后才能接入到互联网,PPP 协议就是用户计算机和 ISP 进行通信时所使用的数据链路层协议。
-
+
在 PPP 的帧中
@@ -317,11 +317,11 @@ TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中
- FCS 字段是使用 CRC 的检验序列
- 信息部分的长度不超过 1500
-
+
## 局域网的拓扑
-
+
## 广播信道 - CSMA/CD 协议*
@@ -333,7 +333,7 @@ CSMA/CD 表示载波监听多点接入 / 碰撞检测。
- **载波监听** :每个站都必须不停地监听信道。在发送前,如果检听信道正在使用,就必须等待。
- **碰撞检测** :在发送中,如果监听 到信道已有其它站正在发送数据,就表示发生了碰撞。虽然每一个站在发送数据之前都已经检听到信道为空闲,但是由于电磁波的传播时延的存在,还是有可能会发生碰撞。
-
+
记端到端的传播时延为 τ,最先发送的站点最多经过 2τ 就可以知道是否发生了碰撞,称 2τ 为 **争用期** 。只有经过争用期之后还没有检测到碰撞,才能肯定这次发送不会发生碰撞。
@@ -351,7 +351,7 @@ CSMA/CD 表示载波监听多点接入 / 碰撞检测。
集线器是一种共享式的传输设备,意味着同一时刻只能传输一组数据帧。
-
+
### 2. 在链路层进行扩展
@@ -363,19 +363,19 @@ CSMA/CD 表示载波监听多点接入 / 碰撞检测。
交换机具有自学习能力,学习的是交换表的内容,交换表中存储着 MAC 地址到 接口的映射。下图中,交换机有 4 个接口,主机 A 向主机 B 发送数据帧时,交换机把主机 A 到接口 1 的映射写入交换表中。为了发送数据帧到 B,先查交换表,此时没有主机 B 的表项,那么主机 A 就发送广播帧,主机 C 和主机 D 会丢弃该帧,主机 B 收下之后,查找交换表得到主机 A 映射的接口为 1,因此就把帧发送给主机 A,同时交换机添加主机 B 到接口 3 的映射。
-
+
### 3. 虚拟局域网
虚拟局域网可以建立与物理位置无关的逻辑组,只有在同一个虚拟局域网中的成员才会收到链路层广播信息,例如下图中 (A1, A2, A3, A4) 属于一个虚拟局域网,A1 发送的广播会被 A2、A3、A4 收到,而其它站点收不到。
-
+
## MAC 层*
MAC 地址是 6 字节(48 位)的地址,用于唯一表示网络适配器(网卡),一台主机拥有多少个适配器就有多少个 MAC 地址,例如笔记本电脑普遍存在无线网络适配器和有线网络适配器。
-
+
- **类型** :标记上层使用的协议;
- **数据** :长度在 46-1500 之间,如果太小则需要填充;
@@ -390,7 +390,7 @@ MAC 地址是 6 字节(48 位)的地址,用于唯一表示网络适配器
使用 IP 协议,可以把异构的物理网络连接起来,使得在网络层看起来好像是一个统一的网络。
-
+
与 IP 协议配套使用的还有三个协议:
@@ -398,11 +398,11 @@ MAC 地址是 6 字节(48 位)的地址,用于唯一表示网络适配器
2. 网际控制报文协议 ICMP(Internet Control Message Protocol)
3. 网际组管理协议 IGMP(Internet Group Management Protocol)
-
+
## IP 数据报格式
-
+
- **版本** : 有 4(IPv4)和 6(IPv6)两个值;
@@ -416,7 +416,7 @@ MAC 地址是 6 字节(48 位)的地址,用于唯一表示网络适配器
- **片偏移** : 和标识符一起,用于发生分片的情况。片偏移的单位为 8 字节。
-
+
- **生存时间** :TTL,它的存在是为了防止无法交付的数据报在互联网中不断兜圈子。以路由器跳数为单位,当 TTL 为 0 时就丢弃数据报。
@@ -438,7 +438,7 @@ IP 地址的编址方式经历了三个历史阶段:
IP 地址 ::= {< 网络号 >, < 主机号 >}
-
+
### 2. 子网划分
@@ -466,19 +466,19 @@ CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为
网络层实现主机之间的通信,而链路层实现具体每段链路之间的通信。因此在通信过程中,IP 数据报的源地址和目的地址始终不变,而 MAC 地址随着链路的改变而改变。
-
+
## 地址解析协议 ARP
实现由 IP 地址得到 MAC 地址。
-
+
每个主机都有一个 ARP 高速缓存,里面有本局域网上的各主机和路由器的 IP 地址到硬件地址的映射表。
如果主机 A 知道主机 B 的 IP 地址,但是 ARP 高速缓存中没有该 IP 地址到 MAC 地址的映射,此时主机 A 通过广播的方式发送 ARP 请求分组,主机 B 收到该请求后会发送 ARP 响应分组给主机 A 告知其 MAC 地址,随后主机 A 向其高速缓存中写入主机 B 的 IP 地址到硬件地址的映射。
-
+
## 路由器的结构
@@ -486,11 +486,11 @@ CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为
分组转发部分由三部分组成:交换结构、一组输入端口和一组输出端口。
-
+
交换结构的交换网络有以下三种实现方式:
-
+
## 路由器分组转发流程
@@ -502,7 +502,7 @@ CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为
5. 若路由表中有一个默认路由,则把数据报传送给路由表中所指明的默认路由器;
6. 报告转发分组出错。
-
+
## 路由选择协议
@@ -515,7 +515,7 @@ CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为
1. 内部网关协议 IGP(Interior Gateway Protocol):在 AS 内部使用,如 RIP 和 OSPF。
2. 外部网关协议 EGP(External Gateway Protocol):在 AS 之间使用,如 BGP。
-
+
### 1. 内部网关协议 RIP
@@ -555,17 +555,17 @@ BGP 只能寻找一条比较好的路由,而不是最佳路由。它采用路
每个 AS 都必须配置 BGP 发言人,通过在两个相邻 BGP 发言人之间建立 TCP 连接来交换路由信息。
-
+
## 网际控制报文协议 ICMP
ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。它封装在 IP 数据报中,但是不属于高层协议。
-
+
ICMP 报文分为差错报告报文和询问报文。
-
+
## 分组网间探测 PING
@@ -580,7 +580,7 @@ PING 的过程:
在一对多的通信中,多播不需要将分组复制多份,从而大大节约网络资源。
-
+
## 虚拟专用网 VPN
@@ -596,7 +596,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。
下图中,场所 A 和 B 的通信部经过互联网,如果场所 A 的主机 X 要和另一个场所 B 的主机 Y 通信,IP 数据报的源地址是 10.1.0.1,目的地址是 10.2.0.3。数据报先发送到与互联网相连的路由器 R1,R1 对内部数据进行加密,然后重新加上数据报的首部,源地址是路由器 R1 的全球地址 125.1.2.3,目的地址是路由器 R2 的全球地址 194.4.5.6。路由器 R2 收到数据报后将数据部分进行解密,恢复原来的数据报,此时目的地址为 10.2.0.3,就交付给 Y。
-
+
## 网络地址转换 NAT
@@ -604,7 +604,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。
在以前,NAT 将本地 IP 和全球 IP 一一对应,这种方式下拥有 n 个全球 IP 地址的专用网内最多只可以同时有 n 台主机接入互联网。为了更有效地利用全球 IP 地址,现在常用的 NAT 转换表把运输层的端口号也用上了,使得多个专用网内部的主机共用一个全球 IP 地址。使用端口号的 NAT 也叫做网络地址与端口转换 NAPT。
-
+
# 第五章 运输层*
@@ -620,13 +620,13 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。
## UDP 首部格式
-
+
首部字段只有 8 个字节,包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和而临时添加的。
## TCP 首部格式
-
+
- **序号** :用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。
@@ -644,7 +644,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。
## TCP 的三次握手
-
+
假设 A 为客户端,B 为服务器端。
@@ -657,7 +657,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。
## TCP 的四次挥手
-
+
以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK,因为 ACK 在连接建立之后都为 1。
@@ -675,7 +675,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。
## TCP 滑动窗口
-
+
窗口是缓存的一部分,用来暂时存放字节流。发送方和接收方各有一个窗口,接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小。
@@ -707,7 +707,7 @@ TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文
如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接受,而拥塞控制是为了降低整个网络的拥塞程度。
-
+
TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。发送方需要维护有一个叫做拥塞窗口(cwnd)的状态变量。注意拥塞窗口与发送方窗口的区别,拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送方窗口。
@@ -716,7 +716,7 @@ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、
1. 接收方有足够大的接收缓存,因此不会发生流量控制;
2. 虽然 TCP 的窗口基于字节,但是这里设窗口的大小单位为报文段。
-
+
### 慢开始与拥塞避免
@@ -734,7 +734,7 @@ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、
在这种情况下,只是丢失个别报文段,而不是网络拥塞,因此执行快恢复,令 ssthresh = cwnd / 2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。
-
+
# 第六章 应用层*
@@ -748,9 +748,9 @@ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、
一个域名由多个层次构成,从上层到下层分别为顶级域名、二级域名、三级域名以及四级域名。所有域名可以画成一颗域名树。
-
+
-
+
域名服务器可以分为以下四类:
@@ -761,11 +761,11 @@ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、
区和域的概念不同,可以在一个域中划分多个区。图 b 在域 abc.com 中划分了两个区:abc.com 和 y.abc.com
-
+
因此就需要两个权限域名服务器:
-
+
### 2. 解析过程
@@ -773,13 +773,13 @@ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、
迭代的方式下,本地域名服务器向一个域名服务器解析请求解析之后,结果返回到本地域名服务器,然后本地域名服务器继续向其它域名服务器请求解析;而递归地方式下,结果不是直接返回的,而是继续向前请求解析,最后的结果才会返回。
-
+
## 文件传输协议 FTP
FTP 在运输层使用 TCP,并且需要建立两个并行的 TCP 连接:控制连接和数据连接。控制连接在整个会话期间一直保持打开,而数据连接在数据传送完毕之后就关闭。控制连接使用端口号 21,数据连接使用端口号 20。
-
+
## 远程终端协议 TELNET
@@ -795,7 +795,7 @@ TELNET 可以适应许多计算机和操作系统的差异,例如不同操作
一个电子邮件系统由三部分组成:用户代理、邮件服务器以及邮件发送协议和读取协议。其中发送协议常用 SMTP,读取协议常用 POP3 和 IMAP。
-
+
### POP3
@@ -809,7 +809,7 @@ IMAP 协议中客户端和服务器上的邮件保持同步,如果不去手动
SMTP 只能发送 ASCII 码,而互联网邮件扩充 MIME 可以发送二进制文件。MIME 并没有改动或者取代 SMTP,而是增加邮件主题的结构,定义了非 ASCII 码的编码规则。
-
+
## 动态主机配置协议 DHCP
diff --git a/notes/设计模式.md b/notes/设计模式.md
index 67f88866..4da4c967 100644
--- a/notes/设计模式.md
+++ b/notes/设计模式.md
@@ -29,7 +29,7 @@
需要说明的一点是,文中的 UML 类图和规范的 UML 类图不大相同,其中组合关系使用以下箭头表示:
-
+
# 设计模式入门
@@ -47,7 +47,7 @@
使用继承的解决方案如下,这种方案代码无法复用,如果两个鸭子类拥有同样的飞行方式,就有两份重复的代码。
-
+
**4. 设计原则**
@@ -57,17 +57,17 @@
运用这一原则,将叫和飞行的行为抽象出来,实现多种不同的叫和飞行的子类,让子类去实现具体的叫和飞行方式。
-
+
**多用组合,少用继承** 组合也就是 has-a 关系,通过组合,可以在运行时动态改变实现,只要通过改变父类对象具体指向哪个子类即可。而继承就不能做到这些,继承体系在创建类时就已经确定。
运用这一原则,在 Duck 类中组合 FlyBehavior 和 QuackBehavior 类,performQuack() 和 performFly() 方法委托给这两个类去处理。通过这种方式,一个 Duck 子类可以根据需要去实例化 FlyBehavior 和 QuackBehavior 的子类对象,并且也可以动态地进行改变。
-
+
**5. 整体设计图**
-
+
**6. 模式定义**
@@ -182,7 +182,7 @@ FlyBehavior.FlyNoWay
定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。主题(Subject)是被观察的对象,而其所有依赖者(Observer)成为观察者。
-
+
**2. 模式类图**
@@ -190,7 +190,7 @@ FlyBehavior.FlyNoWay
观察者拥有一个主题对象的引用,因为注册、移除还有数据都在主题当中,必须通过操作主题才能完成相应功能。
-
+
**3. 问题描述**
@@ -198,7 +198,7 @@ FlyBehavior.FlyNoWay
**4. 解决方案类图**
-
+
**5. 设计原则**
@@ -320,17 +320,17 @@ StatisticsDisplay.update:1.0 1.0 1.0
下图中 DarkRoast 对象被 Mocha 包裹,Mocha 对象又被 Whip 包裹,并且他们都继承自相同父类,都有 cost() 方法,但是外层对象的 cost() 方法实现调用了内层对象的 cost() 方法。因此,如果要在 DarkRoast 上添加 Mocha,那么只需要用 Mocha 包裹 DarkRoast,如果还需要 Whip ,就用 Whip 包裹 Mocha,最后调用 cost() 方法能把三种对象的价格都包含进去。
-
+
**3. 模式类图**
装饰者和具体组件都继承自组件类型,其中具体组件的方法实现不需要依赖于其它对象,而装饰者拥有一个组件类型对象,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰的对象之外,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件有直接实现而不需要委托给其它对象去处理。
-
+
**4. 问题解决方案的类图**
-
+
**5. 设计原则**
@@ -338,7 +338,7 @@ StatisticsDisplay.update:1.0 1.0 1.0
**6. Java I/O 中的装饰者模式**
-
+
**7. 代码实现**
@@ -425,11 +425,11 @@ public class StartbuzzCoffee {
这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及实例化哪个子类。因为客户类往往有多个,如果不使用简单工厂,所有的客户类都要知道所有子类的细节。一旦子类发生改变,例如增加子类,那么所有的客户类都要发生改变。
-
+
**3. 解决方案类图**
-
+
**4. 代码实现**
@@ -497,19 +497,19 @@ CheesePizza
在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。下图中,Creator 有一个 anOperation() 方法,这个方法需要用到一组产品类,这组产品类由 factoryMethod() 方法创建。该方法是抽象的,需要由子类去实现。
-
+
**4. 解决方案类图**
PizzaStore 由 orderPizza() 方法,顾客可以用它来下单。下单之后需要先使用 createPizza() 来制作 Pizza,这里的 createPizza() 就是 factoryMethod(),不同的 PizzaStore 子类实现了不同的 createPizza()。
-
+
**5. 设计原则**
**依赖倒置原则** :要依赖抽象,不要依赖具体类。听起来像是针对接口编程,不针对实现编程,但是这个原则说明了:不能让高层组件依赖底层组件,而且,不管高层或底层组件,两者都应该依赖于抽象。例如,下图中 Pizza 是抽象类,PizzaStore 和 Pizza 子类都依赖于 Pizza 这个抽象类。
-
+
**6. 代码实现**
@@ -623,11 +623,11 @@ ChicagoStyleCheesePizza is making..
从高层次来看,抽象工厂使用了组合,即 Cilent 组合了 AbstractFactory ,而工厂模式使用了继承。
-
+
**3. 解决方案类图**
-
+
**4. 代码实现**
@@ -748,7 +748,7 @@ MarinaraSauce
使用一个私有构造器、一个私有静态变量以及一个公有静态函数来实现。私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。
-
+
**3. 懒汉式-线程不安全**
@@ -839,9 +839,9 @@ if (uniqueInstance == null) {
有非常多的家电,并且之后会增加家电。
-
+
-
+
**2. 模式定义**
@@ -857,11 +857,11 @@ if (uniqueInstance == null) {
- RemoteLoader 是客户端,注意它与 RemoteControl 的区别。因为 RemoteControl 不能主动地调用自身的方法,因此也就不能当成是客户端。客户端好比人,只有人才能去真正去使用遥控器。
-
+
**4. 模式类图**
-
+
**5. 代码实现**
@@ -948,15 +948,15 @@ Light is on!
将一个类的接口,转换为客户期望的另一个接口。适配器让原本不兼容的类可以合作无间。
-
+
**2. 模式类图**
有两种适配器模式的实现,一种是对象方式,一种是类方式。对象方式是通过组合的方法,让适配器类(Adapter)拥有一个待适配的对象(Adaptee),从而把相应的处理委托给待适配的对象。类方式用到多重继承,Adapter 继承 Target 和 Adaptee,先把 Adapter 当成 Adaptee 类型然后实例化一个对象,再把它当成 Target 类型的,这样 Client 就可以把这个对象当成 Target 的对象来处理,同时拥有 Adaptee 的方法。
-
+
-
+
**3. 问题描述**
@@ -966,7 +966,7 @@ Light is on!
**4. 解决方案类图**
-
+
**5. 代码实现**
@@ -1024,7 +1024,7 @@ gobble!
**6. Enumration 适配成 Iterator**
-
+
# 外观模式
@@ -1034,17 +1034,17 @@ gobble!
**2. 模式类图**
-
+
**3. 问题描述**
家庭影院中有众多电器,当要进行观看电影时需要对很多电器进行操作。要求简化这些操作,使得家庭影院类只提供一个简化的接口,例如提供一个看电影相关的接口。
-
+
**4. 解决方案类图**
-
+
**5. 设计原则**
@@ -1066,19 +1066,19 @@ gobble!
模板方法 templateMethod() 定义了算法的骨架,确定了 primitiveOperation1() 和 primitiveOperation2() 方法执行的顺序,而 primitiveOperation1() 和 primitiveOperation2() 让子类去实现。
-
+
**3. 问题描述**
冲咖啡和冲茶都有类似的流程,但是某些步骤会有点不一样,要求复用那些相同步骤的代码。
-
+
**4. 解决方案类图**
其中 prepareRecipe() 方法就是模板方法,它确定了其它四个方法的具体执行步骤。其中 brew() 和 addCondiments() 方法在子类中实现。
-
+
**5. 设计原则**
@@ -1182,7 +1182,7 @@ Tea.addCondiments
- Client 需要拥有一个 Aggregate 对象,这是很明显的。为了迭代变量 Aggregate 对象,也需要拥有 Iterator 对象。
-
+
**3. 代码实现**
@@ -1338,7 +1338,7 @@ public class Client {
组合类拥有一个组件对象,因此组合类的操作可以委托给组件对象去处理,而组件对象可以是另一个组合类或者叶子类。
-
+
**4. 代码实现**
@@ -1451,7 +1451,7 @@ Composite:root
Context 的 request() 方法委托给 State 对象去处理。当 Context 组合的 State 对象发生改变时,它的行为也就发生了改变。
-
+
**3. 与策略模式的比较**
@@ -1467,7 +1467,7 @@ Context 的 request() 方法委托给 State 对象去处理。当 Context 组合
糖果销售机有多种状态,每种状态下销售机有不同的行为,状态可以发生转移,使得销售机的行为也发生改变。
-
+
**5. 直接解决方案**
@@ -1475,7 +1475,7 @@ Context 的 request() 方法委托给 State 对象去处理。当 Context 组合
这种解决方案在需要增加状态的时候,必须对每个操作的代码都进行修改。
-
+
**6 代码实现**
@@ -1773,13 +1773,13 @@ No gumball dispensed
视图使用组合模式,模型使用了观察者模式,控制器使用了策略模式。
-
+
**Web 中的 MVC**
模式不再使用观察者模式。
-
+
# 与设计模式相处
@@ -1791,6 +1791,6 @@ No gumball dispensed
模式分类:
-
+
# 剩下的模式
diff --git a/notes/重构.md b/notes/重构.md
index fa855b12..8cf08472 100644
--- a/notes/重构.md
+++ b/notes/重构.md
@@ -124,7 +124,7 @@
包括三个类:Movie、Rental 和 Customer,Rental 包含租赁的 Movie 以及天数。
-
+
最开始的实现是把所有的计费代码都放在 Customer 类中。
@@ -159,19 +159,19 @@ double getTotalCharge() {
以下是继承 Movie 的多态解决方案,这种方案可以解决上述的 switch 问题,因为每种电影类别的计费方式都被放到了对应 Movie 子类中,当变化发生时,只需要去修改对应子类中的代码即可。
-
+
但是由于 Movie 可以在其生命周期内修改自己的类别,一个对象却不能在生命周期内修改自己所属的类,因此这种方案不可行。可以使用策略模式来解决这个问题(原书写的是使用状态模式,但是这里应该为策略模式,具体可以参考设计模式内容)。
下图中,Price 有多种实现,Movie 组合了一个 Price 对象,并且在运行时可以改变组合的 Price 对象,从而使得它的计费方式发生改变。
-
+
重构后整体的类图和时序图如下:
-
+
-
+
# 重构原则
@@ -579,7 +579,7 @@ Hide Delegate 有很大好处,但是它的代价是:每当客户要使用受
将该数据赋值到一个领域对象中,建立一个 Oberver 模式,用以同步领域对象和 GUI 对象内的重复数据。
-
+
## 7. Change Unidirectional Association to Bidirectional(将单向关联改为双向关联)
@@ -636,13 +636,13 @@ public 字段应当改为 private,并提供相应的访问函数。
类中有一个数值类型码,但它并不影响类的行为,就用一个新类替换该数值类型码。如果类型码出现在 switch 语句中,需要使用 Replace Conditional with Polymorphism 去掉 switch,首先必须运用 Replace Type Code with Subcalss 或 Replace Type Code with State/Strategy 去掉类型码。
-
+
## 14. Replace Type Code with Subcalsses(以子类取代类型码)
有一个不可变的类型码,它会影响类的行为,以子类取代这个类型码。
-
+
## 15. Replace Type Code with State/Strategy (以 State/Strategy 取代类型码)
@@ -650,13 +650,13 @@ public 字段应当改为 private,并提供相应的访问函数。
和 Replace Type Code with Subcalsses 的区别是 Replace Type Code with State/Strategy 的类型码是动态可变的,前者通过继承的方式来实现,后者通过组合的方式来实现。因为类型码可变,如果通过继承的方式,一旦一个对象的类型码改变,那么就要改变用新的对象来取代旧对象,而客户端难以改变新的对象。但是通过组合的方式,改变引用的状态类是很容易的。
-
+
## 16. Replace Subclass with Fields(以字段取代子类)
各个子类的唯一差别只在“返回常量数据”的函数上。
-
+
# 简化条件表达式
@@ -776,7 +776,7 @@ double getSpeed() {
}
```
-
+
## 7. Introduce Null Object(引入Null对象)
@@ -916,7 +916,7 @@ double finalPrice = discountedPrice (basePrice);
以一个对象取代这些参数。
-
+
## 10. Remove Setting Method(移除设值函数)
diff --git a/notes/面向对象思想.md b/notes/面向对象思想.md
index efcf1ec6..1b88525f 100644
--- a/notes/面向对象思想.md
+++ b/notes/面向对象思想.md
@@ -227,13 +227,13 @@ public class Music {
从具体类中继承。
-
+
**实现关系 (Realize)**
从抽象类或者接口中继承。
-
+
### 1.2 整体和部分
@@ -241,13 +241,13 @@ public class Music {
表示整体由部分组成,但是整体和部分不是强依赖的,整体不存在了部分还是会存在。以下表示 B 由 A 组成:
-
+
**组合关系 (Composition)**
和聚合不同,组合中整体和部分是强依赖的,整体不存在了部分也不存在了。比如公司和部门,公司没了部门就不存在了。但是公司和员工就属于聚合关系了,因为公司没了员工还在。
-
+
### 1.3 相互联系
@@ -255,13 +255,13 @@ public class Music {
表示不同类对象之间有关联,这是一种静态关系,与运行过程的状态无关,在最开始就可以确定。因此也可以用 1 对 1、多对 1、多对多这种关联关系来表示。比如学生和学校就是一种关联关系,一个学校可以有很多学生,但是一个学生只属于一个学校,因此这是一种多对一的关系,在运行开始之前就可以确定。
-
+
**依赖关系 (Dependency)**
和关联关系不同的是 , 依赖关系是在运行过程中起作用的。一般依赖作为类的构造器或者方法的参数传入。双向依赖时一种不好的设计。
-
+
## 2. 时序图
@@ -273,7 +273,7 @@ public class Music {
从虚线从上往下表示时间的推进。
-
+
可见,通过时序图可以知道每个类具有以下操作:
@@ -317,7 +317,7 @@ public class 孙权 {
有三种表现形式
-
+
在画图时,应该遵循以下原则:
@@ -329,7 +329,7 @@ public class 孙权 {
生命线从对象的创建开始到对象销毁时终止
-
+
**消息**
@@ -339,15 +339,15 @@ public class 孙权 {
1\. 简单消息,不区分同步异步。
-
+
2\. 同步消息,发送消息之后需要暂停活动来等待回应。
-
+
3\. 异步消息,发送消息之后不需要等待。
-
+
4\. 返回消息,可选。
@@ -355,7 +355,7 @@ public class 孙权 {
生命线上的方框表示激活状态,其它时间处于休眠状态。
-
+
# 参考资料