Merge remote-tracking branch 'refs/remotes/CyC2018/master'

This commit is contained in:
MachineChen 2018-03-14 09:10:31 +08:00
commit 1d36ebecb6
56 changed files with 1504 additions and 284 deletions

View File

@ -89,7 +89,17 @@ File, InputStream OutputStream, Reader Writer, Serializable, Socket, NIO
整理了一些常见考点。 整理了一些常见考点。
## 编码实践 :hammer: ## 工具 :hammer:
> [Git](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Git.md)
整理一些 Git 的使用和概念。
> [正则表达式](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/正则表达式.md)
整理自《正则表达式必知必会》
## 编码实践 :speak_no_evil:
> [重构](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/重构.md) > [重构](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/重构.md)

156
notes/Git.md Normal file
View File

@ -0,0 +1,156 @@
<!-- GFM-TOC -->
* [学习资料](#学习资料)
* [集中式与分布式](#集中式与分布式)
* [Git 的中心服务器](#git-的中心服务器)
* [Git 工作流](#git-工作流)
* [分支实现](#分支实现)
* [冲突](#冲突)
* [Fast forward](#fast-forward)
* [分支管理策略](#分支管理策略)
* [储藏Stashing](#储藏stashing)
* [SSH 传输设置](#ssh-传输设置)
* [.gitignore 文件](#gitignore-文件)
* [Git 命令一览](#git-命令一览)
<!-- GFM-TOC -->
# 学习资料
- [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 属于集中式。
集中式版本控制只有中心服务器拥有一份代码,而分布式版本控制每个人的电脑上就有一份完整的代码。
集中式版本控制有安全性问题,当中心服务器挂了所有人都没办法工作了。
集中式版本控制需要连网才能工作,如果网速过慢,那么提交一个文件的会慢的无法让人忍受。而分布式版本控制不需要连网就能工作。
分布式版本控制新建分支、合并分支操作速度非常快,而集中式版本控制新建一个分支相当于复制一份完整代码。
# Git 的中心服务器
Git 的中心服务器用来交换每个用户的修改。没有中心服务器也能工作,但是中心服务器能够 24 小时保持开机状态这样就能更方便的交换修改。Github 就是一种 Git 中心服务器。
# Git 工作流
<div align="center"> <img src="../pics//a1198642-9159-4d88-8aec-c3b04e7a2563.jpg"/> </div><br>
新建一个仓库之后,当前目录就成为了工作区,工作区下有一个隐藏目录 .git它属于 Git 的版本库。
Git 版本库有一个称为 stage 的暂存区,还有自动创建的 master 分支以及指向分支的 HEAD 指针。
<div align="center"> <img src="../pics//46f66e88-e65a-4ad0-a060-3c63fe22947c.png"/> </div><br>
- git add files 把文件的修改添加到暂存区
- git commit 把暂存区的修改提交到当前分支,提交之后暂存区就被清空了
- git reset -- files 使用当前分支上的修改覆盖暂缓区,用来撤销最后一次 git add files
- git checkout -- files 使用暂存区的修改覆盖工作目录,用来撤销本地修改
<div align="center"> <img src="../pics//17976404-95f5-480e-9cb4-250e6aa1d55f.png"/> </div><br>
可以跳过暂存区域直接从分支中取出修改或者直接提交修改到分支中
- git commit -a 直接把所有文件的修改添加到暂缓区然后执行提交
- git checkout HEAD -- files 取出最后一次修改,可以用来进行回滚操作
# 分支实现
Git 把每次提交都连成一条时间线。分支使用指针来实现,例如 master 分支指针指向时间线的最后一个节点也就是最后一次提交。HEAD 指针指向的是当前分支。
<div align="center"> <img src="../pics//fb546e12-e1fb-4b72-a1fb-8a7f5000dce6.jpg"/> </div><br>
新建分支是新建一个指针指向时间线的最后一个节点,并让 HEAD 指针指向新分支表示新分支成为当前分支。
<div align="center"> <img src="../pics//bc775758-89ab-4805-9f9c-78b8739cf780.jpg"/> </div><br>
每次提交只会让当前分支向前移动,而其它分支不会移动。
<div align="center"> <img src="../pics//5292faa6-0141-4638-bf0f-bb95b081dcba.jpg"/> </div><br>
合并分支也只需要改变指针即可。
<div align="center"> <img src="../pics//1164a71f-413d-494a-9cc8-679fb6a2613d.jpg"/> </div><br>
# 冲突
当两个分支都对同一个文件进行了修改,在分支合并时就会产生冲突。
<div align="center"> <img src="../pics//58e57a21-6b6b-40b6-af85-956dd4e0f55a.jpg"/> </div><br>
Git 会使用 <<<<<<< ======= >>>>>>> 标记出不同分支的内容,只需要把不同分支中冲突部分修改成一样就能解决冲突。
```
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1
```
# Fast forward
"快进式合并"fast-farward merge会直接将 master 分支指向合并的分支,这种模式下进行分支合并会丢失分支信息,也就不能在分支历史上看出分支信息。
可以在合并时加上 --no-ff 参数来禁用 Fast forward 模式,并且加上 -m 参数让合并时产生一个新的 commit。
```
$ git merge --no-ff -m "merge with no-ff" dev
```
<div align="center"> <img src="../pics//dd78a1fe-1ff3-4bcf-a56f-8c003995beb6.jpg"/> </div><br>
# 分支管理策略
master 分支应该是非常稳定的,只用来发布新版本;
日常开发在开发分支 dev 上进行。
<div align="center"> <img src="../pics//245fd2fb-209c-4ad5-bc5e-eb5664966a0e.jpg"/> </div><br>
# 储藏Stashing
在一个分支上操作之后,如果还没有将修改提交到分支上,此时进行切换分支,那么另一个分支上也能看到新的修改。这是因为所有分支都共用一个工作区的缘故。
可以使用 git stash 将当前分支的修改储藏起来,此时当前工作区的所有修改都会被存到栈上,也就是说当前工作区是干净的,没有任何未提交的修改。此时就可以安全的切换到其它分支上了。
```
$ git stash
Saved working directory and index state \ "WIP on master: 049d078 added the index file"
HEAD is now at 049d078 added the index file (To restore them type "git stash apply")
```
该功能可以用于 bug 分支的实现。如果当前正在 dev 分支上进行开发,但是此时 master 上有个 bug 需要修复,但是 dev 分支上的开发还未完成,不想立即提交。在新建 bug 分支并切换到 bug 分支之前就需要使用 git stash 将 dev 分支的未提交修改储藏起来。
# SSH 传输设置
Git 仓库和 Github 中心仓库之间是通过 SSH 加密。
如果工作区下没有 .ssh 目录,或者该目录下没有 id_rsa 和 id_rsa.pub 这两个文件,可以通过以下命令来创建 SSH Key
```
$ ssh-keygen -t rsa -C "youremail@example.com"
```
然后把公钥 id_rsa.pub 的内容复制到 Github "Account settings" 的 SSH Keys 中。
# .gitignore 文件
忽略以下文件:
1. 操作系统自动生成的文件,比如缩略图;
2. 编译生成的中间文件,比如 Java 编译产生的 .class 文件;
3. 自己的敏感信息,比如存放口令的配置文件。
不需要全部自己编写,可以到 [https://github.com/github/gitignore](https://github.com/github/gitignore) 中进行查询。
# Git 命令一览
<div align="center"> <img src="../pics//7a29acce-f243-4914-9f00-f2988c528412.jpg"/> </div><br>
比较详细的地址http://www.cheat-sheets.org/saved-copy/git-cheat-sheet.pdf

View File

@ -37,7 +37,9 @@
* [加密](#加密) * [加密](#加密)
* [认证](#认证) * [认证](#认证)
* [完整性](#完整性) * [完整性](#完整性)
* [HTTP/1.0 与 HTTP/1.1 的区别](#http10-与-http11-的区别) * [各版本比较](#各版本比较)
* [HTTP/1.0 与 HTTP/1.1 的区别](#http10-与-http11-的区别)
* [HTTP/1.1 与 HTTP/2.0 的区别](#http11-与-http20-的区别)
<!-- GFM-TOC --> <!-- GFM-TOC -->
@ -398,7 +400,9 @@ HTTPs 采用 **混合的加密机制** ,使用公开密钥加密用于传输
SSL 提供摘要功能来验证完整性。 SSL 提供摘要功能来验证完整性。
# HTTP/1.0 与 HTTP/1.1 的区别 # 各版本比较
## HTTP/1.0 与 HTTP/1.1 的区别
HTTP/1.1 新增了以下内容: HTTP/1.1 新增了以下内容:
@ -407,3 +411,21 @@ HTTP/1.1 新增了以下内容:
- 提供了虚拟主机的功能; - 提供了虚拟主机的功能;
- 多了一些缓存处理字段; - 多了一些缓存处理字段;
- 多了一些状态码; - 多了一些状态码;
## HTTP/1.1 与 HTTP/2.0 的区别
**多路复用**
HTTP/2.0 使用多路复用技术,即使用同一个 TCP 连接来处理多个请求。
**首部压缩**
HTTP1.1 的首部带有大量信息而且每次都要重复发送HTTP/2.0 要求通讯双方各自缓存一份首部字段表,从而避免了重复传输。
**服务端推送**
在客户端请求一个资源时,会把相关的资源一起发送给客户端,客户端就不需要再次发起请求了。例如客户端请求 index.html 页面,服务端就把 index.js 一起发给客户端。
**二进制格式**
HTTP1.1 的解析是基于文本的,而 HTTP2.0 采用二进制格式。

View File

@ -171,7 +171,7 @@ Object obj = new Object();
```java ```java
Object obj = new Object(); Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj); SoftReference<Object> sf = new SoftReference<Object>(obj);
``` ```
#### 1.3.3 弱引用 #### 1.3.3 弱引用

View File

@ -20,6 +20,9 @@
* [1. 阻塞](#1-阻塞) * [1. 阻塞](#1-阻塞)
* [2. 中断](#2-中断) * [2. 中断](#2-中断)
* [线程状态转换](#线程状态转换) * [线程状态转换](#线程状态转换)
* [volatile](#volatile)
* [1. 内存可见性](#1-内存可见性)
* [2. 禁止指令重排](#2-禁止指令重排)
* [内存模型](#内存模型) * [内存模型](#内存模型)
* [1. 硬件的效率与一致性](#1-硬件的效率与一致性) * [1. 硬件的效率与一致性](#1-硬件的效率与一致性)
* [2. Java 内存模型](#2-java-内存模型) * [2. Java 内存模型](#2-java-内存模型)
@ -423,6 +426,26 @@ interrupted() 方法在检查完中断状态之后会清除中断状态,这样
- LockSupport.parkNanos() 方法 - LockSupport.parkNanos() 方法
- LockSupport.parkUntil() 方法 - LockSupport.parkUntil() 方法
# volatile
保证了内存可见性和禁止指令重排,没法保证原子性。
## 1. 内存可见性
普通共享变量被修改之后,什么时候被写入主存是不确定的。
volatile 关键字会保证每次修改共享变量之后该值会立即更新到内存中,并且在读取时会从内存中读取值。
synchronized 和 Lock 也能够保证内存可见性。它们能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。不过只有对共享变量的 set() 和 get() 方法都加上 synchronized 才能保证可见性,如果只有 set() 方法加了 synchronized那么 get() 方法并不能保证会从内存中读取最新的数据。
## 2. 禁止指令重排
在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
volatile 关键字通过添加内存屏障的方式来进制指令重排,即重排序时不能把后面的指令放到内存屏障之前。
可以通过 synchronized 和 Lock 来保证有序性,它们保证每个时刻只有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
# 内存模型 # 内存模型
## 1. 硬件的效率与一致性 ## 1. 硬件的效率与一致性

View File

@ -272,6 +272,12 @@ customer_id_selectivity: 0.0373
索引包含所有需要查询的字段的值。 索引包含所有需要查询的字段的值。
**优点**
1. 因为索引条目通常远小于数据行的大小,所以若只读取索引,能大大减少数据访问量。
2. 一些存储引擎(例如 MyISAM在内存中只缓存索引而数据依赖于操作系统来缓存。因此只访问索引可以不使用系统调用通常比较费时
3. 对于 InnoDB 引擎,若二级索引能够覆盖查询,则无需访问聚簇索引。
## 4. B-Tree 和 B+Tree 原理 ## 4. B-Tree 和 B+Tree 原理
### 4. 1 B-Tree ### 4. 1 B-Tree

View File

@ -193,21 +193,13 @@ public String replaceSpace(StringBuffer str) {
## 6. 从尾到头打印链表 ## 6. 从尾到头打印链表
正向遍历然后调用 Collections.reverse()。 **题目描述**
```java 输入链表的第一个节点,从尾到头反过来打印出每个结点的值。
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> ret = new ArrayList<>();
while (listNode != null) {
ret.add(listNode.val);
listNode = listNode.next;
}
Collections.reverse(ret);
return ret;
}
```
使用 Stack **解题思路**
```java ```java
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) { public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
@ -237,7 +229,23 @@ public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
} }
``` ```
不使用库函数,并且不使用递归的迭代实现,利用链表的头插法为逆序的特性。 正向遍历然后调用 Collections.reverse()。
```java
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> ret = new ArrayList<>();
while (listNode != null) {
ret.add(listNode.val);
listNode = listNode.next;
}
Collections.reverse(ret);
return ret;
}
```
不使用库函数,并且不使用递归。利用链表头插法为逆序的特点。
头结点和第一个节点的区别:头结点是在头插法中使用的一个额外节点,这个节点不存储值;第一个节点就是链表的第一个真正存储值的节点。
```java ```java
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) { public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
@ -597,6 +605,19 @@ public int NumberOf1(int n) {
## 16. 数值的整数次方 ## 16. 数值的整数次方
**题目描述**
给定一个 double 类型的浮点数 base 和 int 类型的整数 exponent。求 base 的 exponent 次方。
**解题思路**
下面的讨论中 x 代表 baseN 代表 exponent。
- 当 x 为偶数时x<sup>N</sup> = (x \* x)<sup>N / 2</sup>
- 当 x 为奇数时x<sup>N</sup> = x \* (x \* x)<sup>N / 2</sup>
因为 (x \* x)<sup>N / 2</sup> 可以通过递归求解并且每递归一次N 都减小一半,因此整个算法的时间复杂度为 logN。
```java ```java
public double Power(double base, int exponent) { public double Power(double base, int exponent) {
if (exponent == 0) return 1; if (exponent == 0) return 1;
@ -654,6 +675,19 @@ private void printNumber(char[] number) {
## 18.1 在 O(1) 时间内删除链表节点 ## 18.1 在 O(1) 时间内删除链表节点
**解题思路**
- 如果链表不是尾节点,那么可以直接将下一个节点的值赋给节点,令节点指向下下个节点,然后删除下一个节点,时间复杂度为 O(1)。
<div align="center"> <img src="../pics//72f9bc11-06a9-40b4-8939-14f72e5cb4c3.png"/> </div><br>
- 否则,就需要先遍历链表,找到节点的前一个节点,然后让前一个节点指向节点的下一个节点,时间复杂度为 O(N)。
<div align="center"> <img src="../pics//2a398239-ee47-4ea1-b2d8-0ced638839ef.png"/> </div><br>
- 综上,如果进行 N 次操作,那么大约需要移动节点的次数为 N-1+N=2N-1其中 N-1 表示不是链表尾节点情况下的移动次数N 表示是尾节点情况下的移动次数。那么增长数量级为 (2N-1)/N \~ 2因此该算法的时间复杂度为 O(1)。
```java ```java
public ListNode deleteNode(ListNode head, ListNode tobeDelete) { public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
if (head == null || head.next == null || tobeDelete == null) return null; if (head == null || head.next == null || tobeDelete == null) return null;
@ -673,6 +707,17 @@ public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
## 18.2 删除链表中重复的结点 ## 18.2 删除链表中重复的结点
**题目描述**
```html
Input : 1->2->3->3->4->4->5
Output : 1->2->5
```
**解题描述**
递归。
```java ```java
public ListNode deleteDuplication(ListNode pHead) { public ListNode deleteDuplication(ListNode pHead) {
if (pHead == null) return null; if (pHead == null) return null;
@ -980,7 +1025,7 @@ private int height(TreeNode root) {
**题目描述** **题目描述**
下图的矩阵顺时针打印结果为1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10 下图的矩阵顺时针打印结果为1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10
<div align="center"> <img src="../pics//8615d9f7-bd1d-4240-8bb4-02b941d54a6f.png"/> </div><br> <div align="center"> <img src="../pics//8615d9f7-bd1d-4240-8bb4-02b941d54a6f.png"/> </div><br>
@ -1001,6 +1046,10 @@ public ArrayList<Integer> printMatrix(int[][] matrix) {
## 30. 包含 min 函数的栈 ## 30. 包含 min 函数的栈
**题目描述**
定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的 min 函数。
```java ```java
private Stack<Integer> stack = new Stack<>(); private Stack<Integer> stack = new Stack<>();
private Stack<Integer> minStack = new Stack<>(); private Stack<Integer> minStack = new Stack<>();
@ -1361,6 +1410,12 @@ private void backtracking(char[] chars, boolean[] hasUsed, StringBuffer s) {
## 39. 数组中出现次数超过一半的数字 ## 39. 数组中出现次数超过一半的数字
**解题思路**
多数投票问题,可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题,使得时间复杂度为 O(n)。
使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素不想等时,令 cnt--。如果前面查找了 i 个元素,且 cnt == 0 ,说明前 i 个元素没有 majority或者有 majority但是出现的次数少于 i / 2 ,因为如果多于 i / 2 的话 cnt 就一定不会为 0 。此时剩下的 n - i 个元素中majority 的数目依然多于 (n - i) / 2因此继续查找就能找出 majority。
```java ```java
public int MoreThanHalfNum_Solution(int[] nums) { public int MoreThanHalfNum_Solution(int[] nums) {
int cnt = 1, num = nums[0]; int cnt = 1, num = nums[0];
@ -1805,6 +1860,8 @@ private void merge(int[] nums, int start, int mid, int end) {
## 52. 两个链表的第一个公共结点 ## 52. 两个链表的第一个公共结点
**题目描述**
```html ```html
A: a1 → a2 A: a1 → a2
@ -1813,6 +1870,8 @@ A: a1 → a2
B: b1 → b2 → b3 B: b1 → b2 → b3
``` ```
**解题思路**
设 A 的长度为 a + cB 的长度为 b + c其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。 设 A 的长度为 a + cB 的长度为 b + c其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。
当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B同样地当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。 当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B同样地当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。
@ -1832,23 +1891,56 @@ public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
## 53 数字在排序数组中出现的次数 ## 53 数字在排序数组中出现的次数
**题目描述**
```html
Input:
1, 2, 3, 3, 3, 3, 4, 6
3
Output:
4
````
**解题思路**
可以用二分查找找出数字在数组的最左端和最右端。
```java ```java
public int GetNumberOfK(int[] array, int num) { public int GetNumberOfK(int[] nums, int K) {
int l = 0, h = array.length - 1; int first = getFirstK(nums, K);
// 先找出 num 在数组最左端的位置,可以控制二分查找结束时 l 指向该位置 int last = getLastK(nums, K);
return first == -1 || last == -1 ? 0 : last - first + 1;
}
private int getFirstK(int[] nums, int K) {
int l = 0, h = nums.length - 1;
while (l <= h) { while (l <= h) {
int m = l + (h - l) / 2; int m = l + (h - l) / 2;
if (array[m] >= num) h = m - 1; if (nums[m] >= K) h = m - 1;
else l = m + 1; else l = m + 1;
} }
int cnt = 0; if (l > nums.length - 1 || nums[l] != K) return -1;
while (l < array.length && array[l++] == num) cnt++; return l;
return cnt; }
private int getLastK(int[] nums, int K) {
int l = 0, h = nums.length - 1;
while (l <= h) {
int m = l + (h - l) / 2;
if (nums[m] > K) h = m - 1;
else l = m + 1;
}
if (h < 0 || nums[h] != K) return -1;
return h;
} }
``` ```
## 54. 二叉搜索树的第 k 个结点 ## 54. 二叉搜索树的第 k 个结点
**解题思路**
利用二叉搜索数中序遍历有序的特点。
```java ```java
TreeNode ret; TreeNode ret;
int cnt = 0; int cnt = 0;
@ -1909,6 +2001,12 @@ public void FindNumsAppearOnce(int[] array, int num1[], int num2[]) {
输入一个递增排序的数组和一个数字 S在数组中查找两个数是的他们的和正好是 S如果有多对数字的和等于 S输出两个数的乘积最小的。 输入一个递增排序的数组和一个数字 S在数组中查找两个数是的他们的和正好是 S如果有多对数字的和等于 S输出两个数的乘积最小的。
**解题思路**
使用双指针,一个指针指向元素较小的值,一个指针指向元素较大的值。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。
如果两个指针指向元素的和 sum == target那么得到要求的结果如果 sum > target移动较大的元素使 sum 变小一些;如果 sum < target移动较小的元素使 sum 变大一些
```java ```java
public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum) { public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum) {
int i = 0, j = array.length - 1; int i = 0, j = array.length - 1;

409
notes/正则表达式.md Normal file
View File

@ -0,0 +1,409 @@
<!-- GFM-TOC -->
* [概述](#概述)
* [匹配单个字符](#匹配单个字符)
* [匹配一组字符](#匹配一组字符)
* [使用元字符](#使用元字符)
* [匹配空白字符](#匹配空白字符)
* [匹配特定的字符类别](#匹配特定的字符类别)
* [使用 POSIX 字符类](#使用-posix-字符类)
* [重复匹配](#重复匹配)
* [位置匹配](#位置匹配)
* [单词边界](#单词边界)
* [字符串边界](#字符串边界)
* [使用子表达式](#使用子表达式)
* [回溯引用](#回溯引用)
* [替换](#替换)
* [大小写转换](#大小写转换)
* [前后查找](#前后查找)
* [嵌入条件](#嵌入条件)
* [回溯引用条件](#回溯引用条件)
* [前后查找条件](#前后查找条件)
<!-- GFM-TOC -->
# 概述
正则表达式用于文本内容的查找和替换。
正则表达式内置于其它语言或者软件产品中,它本身不是一种语言或者软件。
一个问题往往可以用多种正则表达式方案来解决。
[正则表达式在线工具](http://tool.chinaz.com/regex)
# 匹配单个字符
正则表达式一般是区分大小写的,但是也有些实现是不区分。
**.** 可以用来匹配任何的单个字符,但是在绝大多数实现里面,不能匹配换行符;
**\\** 是元字符,表示它有特殊的含义,而不是字符本身的含义。如果需要匹配 . ,那么要用 \ 进行转义,即在 . 前面加上 \ 。
**正则表达式**
```
nam.
```
**匹配结果**
My **name** is Zheng.
# 匹配一组字符
**[ ]** 定义一个字符集合;
0-9、a-z 定义了一个字符区间,区间使用 ASCII 码来确定。字符区间只能用在 [ ] 之间,因此 **-** 元字符只有在 [ ] 之间才是元字符,在 [ ] 之外就是一个普通字符;
**^** 是取非操作,必须在 [ ] 字符集合中使用;
**应用**
匹配以 abc 为开头,并且最后一个字母不为数字的字符串:
**正则表达式**
```
abc[^0-9]
```
**匹配结果**
1. **abcd**
2. abc1
3. abc2
# 使用元字符
## 匹配空白字符
| 元字符 | 说明 |
| ------------ | ------------ |
| [\b] | 回退(删除)一个字符 |
| \f | 换页符 |
| \n | 换行符 |
| \r | 回车符 |
| \t | 制表符 |
| \v | 垂直制表符 |
\r\n 是 Windows 中的文本行结束标签,在 Unix/Linux 则是 \n \r\n\r\n 可以匹配 Windows 下的空白行,因为它将匹配两个连续的行尾标签,而这正是两条记录之间的空白行;
. 是元字符,前提是没有对它们进行转义; f 和 n 也是元字符,但是前提是对他们进行了转义。
## 匹配特定的字符类别
**1. 数字元字符**
| 元字符 | 说明 |
| ------------ | ------------ |
| \d | 数字字符,等价于 [0-9] |
| \D | 非数字字符,等价于 [^0-9] |
**2. 字母数字元字符**
| 元字符 | 说明 |
| ------------ | ------------ |
| \w | 大小写字母,下划线和数字,等价于 [a-zA-Z0-9\_] |
| \W | 对 \w 取非 |
**3. 空白字符元字符**
| 元字符 | 说明 |
| ------------ | ------------ |
| \s | 任何一个空白字符,等价于 [\f\n\r\t\v] |
| \S | 对 \s 取非 |
\x 匹配十六进制字符,\0 匹配八进制,例如 \x0A 对应 ASCII 字符 10 ,等价于 \n也就是它会匹配 \n 。
## 使用 POSIX 字符类
| 字符类 | 说明 |
| --- | --- |
| [:alnum:] | 字母数字字符 |
| [:alpha:] | 字母字符 |
| [:cntrl:] | 控制字符 |
| [:digit:] | 数字字符 |
| [:graph:] | 非空白字符 ( 非空格、控制字符等 ) |
| [:lower:] | 小写字母 |
| [:print:] | 与 [:graph:] 相似,但是包含空格字符 |
| [:punct:] | 标点字符 |
| [:space:] | 所有的空白字符 ( 换行符、空格、制表符 ) |
| [:upper:] | 大写字母 |
| [:xdigit:] | 允许十六进制的数字 (0-9a-fA-F) |
并不是所有正则表达式实现都支持 POSIX 字符类,也不一定使用它。
使用时需要用两对方括号,例如 [[:alpha:]]。
# 重复匹配
**\+** 匹配 1 个或者多个字符, **\*** 匹配 0 个或者多个,**?** 匹配 0 个或者 1 个。
**应用**
匹配邮箱地址。
**正则表达式**
```
[\w.]+@\w+.\w+
```
[\w.] 匹配的是字母数字或者 . ,在其后面加上 + ,表示匹配多次。在字符集合 [ ] 里,. 不是元字符;
**匹配结果**
**abc.def<span>@</span>qq.com**
为了可读性,常常把转义的字符放到字符集合 [ ] 中,但是含义是相同的。
```
\w+@\w+.\w+
[\w]+@[\w]+.[\w]+
```
**{n}** 匹配 n 个字符,**{m, n}** 匹配 m\~n 个字符,**{m,}** 至少匹配 m 个字符;
\* 和 + 都是贪婪型元字符,会匹配最多的内容,在元字符后面加 ? 可以转换为懒惰型元字符,例如 \*?、+? 和 {m, n}? 。
**正则表达式**
```
a.+c
```
由于 + 是贪婪型的,因此 .+ 会匹配更可能多的内容,所以会把整个 abcabcabc 文本都匹配,而不是只匹配前面的 abc 文本。用懒惰型可以实现匹配前面的。
**匹配结果**
**abcabcabc**
# 位置匹配
## 单词边界
**\b** 可以匹配一个单词的边界,边界是指位于 \w 和 \W 之间的位置;**\B** 匹配一个不是单词边界的位置。
\b 只匹配位置,不匹配字符,因此 \babc\b 匹配出来的结果为 3 个字符。
## 字符串边界
**^** 匹配整个字符串的开头,**$** 匹配结尾。
^ 元字符在字符集合中用作求非,在字符集合外用作匹配字符串的开头。
使用 (?m) 来打开分行匹配模式,在该模式下,换行被当做字符串的边界。
**应用**
匹配代码中以 // 开始的注释行
**正则表达式**
```
(?m)^\s*//.*$
```
如果没用 (?m),则只会匹配 // 注释 1 以及之后的所有内容,因为 * 是贪婪型的。用了分行匹配模式之后,换行符被当成是字符串分隔符,因此能正确匹配出两个注释内容。
**匹配结果**
1. public void fun() {
2. &nbsp;&nbsp;&nbsp;&nbsp; **// 注释 1**
3. &nbsp;&nbsp;&nbsp;&nbsp; int a = 1;
4. &nbsp;&nbsp;&nbsp;&nbsp; int b = 2;
5. &nbsp;&nbsp;&nbsp;&nbsp; **// 注释 2**
6. &nbsp;&nbsp;&nbsp;&nbsp; int c = a + b;
7. }
# 使用子表达式
使用 **( )** 定义一个子表达式。子表达式的内容可以当成一个独立元素,即可以将它看成一个字符,并且使用 * 等元字符。
子表达式可以嵌套,但是嵌套层次过深会变得很难理解。
**正则表达式**
```
(ab) {2,}
```
**匹配结果**
**ababab**
**|** 是或元字符,它把左边和右边所有的部分都看成单独的两个部分,两个部分只要有一个匹配就行。
```
(19|20)\d{2}
```
**匹配结果**
1. **1900**
2. **2010**
3. 1020
**应用**
匹配 IP 地址。IP 地址中每部分都是 0-255 的数字,用正则表达式匹配时以下情况是合法的:
1. 一位或者两位的数字
2. 1 开头的三位数
3. 2 开头,第 2 位是 0-4 的三位数
4. 25 开头,第 3 位是 0-5 的三位数
**正则表达式**
```
(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.) {3}(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5])))
```
**匹配结果**
1. **192.168.0.1**
2. 555.555.555.555
# 回溯引用
回溯引用使用 **\n** 来引用某个子表达式,其中 n 代表的是子表达式的序号,从 1 开始。它和子表达式匹配的内容一致,比如子表达式匹配到 abc ,那么回溯引用部分也需要匹配 abc 。
**应用**
匹配 HTML 中合法的标题元素。
**正则表达式**
\1 将回溯引用子表达式 (h[1-6]) 匹配的内容,也就是说必须和子表达式匹配的内容一致。
```
<(h[1-6])>\w*?</\1>
```
**匹配结果**
1. **&lt;h1>x&lt;/h1>**
2. **&lt;h2>x&lt;/h2>**
3. &lt;h3>x&lt;/h1>
## 替换
需要用到两个正则表达式。
**应用**
修改电话号码格式。
**文本**
313-555-1234
**查找正则表达式**
```
(\d{3})(-)(\d{3})(-)(\d{4})
```
**替换正则表达式**
在第一个子表达式查找的结果加上 () ,然后加一个空格,在第三个和第五个字表达式查找的结果中间加上 - 进行分隔。
```
($1) $3-$5
```
**结果**
(313) 555-1234
## 大小写转换
| 元字符 | 说明 |
| ---| ---|
| \l | 把下个字符转换为小写 |
| \u| 把下个字符转换为大写 |
| \L | 把\L 和\E 之间的字符全部转换为小写 |
| \U | 把\U 和\E 之间的字符全部转换为大写 |
| \E | 结束\L 或者\U |
**应用**
把文本的第二个和第三个字符转换为大写。
**文本**
abcd
**查找**
```
(\w)(\w{2})(\w)
```
**替换**
```
$1\U$2\E$3
```
**结果**
aBCd
# 前后查找
前后查找规定了匹配的内容首尾应该匹配的内容,但是又不包含首尾匹配的内容。向前查找用 **?=** 来定义,它规定了尾部匹配的内容,这个匹配的内容在 ?= 之后定义。所谓向前查找,就是规定了一个匹配的内容,然后以这个内容为尾部向前面查找需要匹配的内容。向后匹配用 ?<= 定义。
**应用**
查找出邮件地址 @ 字符前面的部分。
**正则表达式**
```
\w+(?=@)
```
**结果**
**abc** @qq.com
对向前和向后查找取非,只要把 = 替换成 ! 即可,比如 (?=) 替换成 (?!) 。取非操作使得匹配那些首尾不符合要求的内容。
# 嵌入条件
## 回溯引用条件
条件判断为某个子表达式是否匹配,如果匹配则需要继续匹配条件表达式后面的内容。
**正则表达式**
子表达式 (\\() 匹配一个左括号,其后的 ? 表示匹配 0 个或者 1 个。 ?(1) 为条件,当子表达式 1 匹配时条件成立,需要执行 \) 匹配,也就是匹配右括号。
```
(\()?abc(?(1)\))
```
**结果**
1. **(abc)**
2. **abc**
3. (abc
## 前后查找条件
条件为定义的首尾是否匹配,如果匹配,则继续执行后面的匹配。注意,首尾不包含在匹配的内容中。
**正则表达式**
?(?=-) 为前向查找条件,只有在以 - 为前向查找的结尾能匹配 \d{5} ,才继续匹配 -\d{4} 。
```
\d{5}(?(?=-)-\d{4})
```
**结果**
1. **11111**
2. 22222-
3. **33333-4444**

View File

@ -5,6 +5,15 @@
* [3. ThreeSum](#3-threesum) * [3. ThreeSum](#3-threesum)
* [4. 倍率实验](#4-倍率实验) * [4. 倍率实验](#4-倍率实验)
* [5. 注意事项](#5-注意事项) * [5. 注意事项](#5-注意事项)
* [栈和队列](#栈和队列)
* [1. 栈](#1-栈)
* [2. 队列](#2-队列)
* [union-find](#union-find)
* [1. quick-find 算法](#1-quick-find-算法)
* [2. quick-union 算法](#2-quick-union-算法)
* [3. 加权 quick-union 算法](#3-加权-quick-union-算法)
* [4. 路径压缩的加权 quick-union 算法](#4-路径压缩的加权-quick-union-算法)
* [5. 各种 union-find 算法的比较](#5-各种-union-find-算法的比较)
* [排序](#排序) * [排序](#排序)
* [1. 初级排序算法](#1-初级排序算法) * [1. 初级排序算法](#1-初级排序算法)
* [1.1 约定](#11-约定) * [1.1 约定](#11-约定)
@ -191,6 +200,297 @@ public class ThreeSumFast {
将所有操作的总成本除于操作总数来将成本均摊。例如对一个空栈进行 N 次连续的 push() 调用需要访问数组的元素为 N+4+8+16+...+2N=5N-4N 是向数组写入元素,其余的都是调整数组大小时进行复制需要的访问数组操作),均摊后每次操作访问数组的平均次数为常数。 将所有操作的总成本除于操作总数来将成本均摊。例如对一个空栈进行 N 次连续的 push() 调用需要访问数组的元素为 N+4+8+16+...+2N=5N-4N 是向数组写入元素,其余的都是调整数组大小时进行复制需要的访问数组操作),均摊后每次操作访问数组的平均次数为常数。
# 栈和队列
## 1. 栈
**数组实现**
```java
public class ResizeArrayStack<Item> implements Iterable<Item> {
private Item[] a = (Item[]) new Object[1];
private int N = 0;
public void push(Item item) {
if (N >= a.length) {
resize(2 * a.length);
}
a[N++] = item;
}
public Item pop() {
Item item = a[--N];
if (N <= a.length / 4) {
resize(a.length / 2);
}
return item;
}
// 调整数组大小,使得栈具有伸缩性
private void resize(int size) {
Item[] tmp = (Item[]) new Object[size];
for (int i = 0; i < N; i++) {
tmp[i] = a[i];
}
a = tmp;
}
public boolean isEmpty() {
return N == 0;
}
public int size() {
return N;
}
@Override
public Iterator<Item> iterator() {
// 需要返回逆序遍历的迭代器
return new ReverseArrayIterator();
}
private class ReverseArrayIterator implements Iterator<Item> {
private int i = N;
@Override
public boolean hasNext() {
return i > 0;
}
@Override
public Item next() {
return a[--i];
}
}
}
```
上面实现使用了泛型Java 不能直接创建泛型数组,只能使用转型来创建。
```java
Item[] arr = (Item[]) new Object[N];
```
**链表实现**
需要使用链表的头插法来实现,因为头插法中最后压入栈的元素在链表的开头,它的 next 指针指向前一个压入栈的元素,在弹出元素使就可以让前一个压入栈的元素称为栈顶元素。
```java
public class Stack<Item> {
private Node top = null;
private int N = 0;
private class Node {
Item item;
Node next;
}
public boolean isEmpty() {
return N == 0;
}
public int size() {
return N;
}
public void push(Item item) {
Node newTop = new Node();
newTop.item = item;
newTop.next = top;
top = newTop;
N++;
}
public Item pop() {
Item item = top.item;
top = top.next;
N--;
return item;
}
}
```
## 2. 队列
下面是队列的链表实现,需要维护 first 和 last 节点指针,分别指向队首和队尾。
这里需要考虑让哪个指针指针链表头部节点,哪个指针指向链表尾部节点。因为出队列操作需要让队首元素的下一个元素成为队首,就需要容易获取下一个元素,而链表的头部节点的 next 指针指向下一个元素,因此让队首指针 first 指针链表的开头。
```java
public class Queue<Item> {
private Node first;
private Node last;
int N = 0;
private class Node{
Item item;
Node next;
}
public boolean isEmpty(){
return N == 0;
}
public int size(){
return N;
}
// 入队列
public void enqueue(Item item){
Node newNode = new Node();
newNode.item = item;
newNode.next = null;
if(isEmpty()){
last = newNode;
first = newNode;
} else{
last.next = newNode;
last = newNode;
}
N++;
}
// 出队列
public Item dequeue(){
Node node = first;
first = first.next;
N--;
return node.item;
}
}
```
# union-find
**概览**
用于解决动态连通性问题,能动态连接两个点,并且判断两个点是否连接。
<div align="center"> <img src="../pics//365e5a18-cf63-4b80-bb12-da6b650653f7.jpg"/> </div><br>
**API**
<div align="center"> <img src="../pics//f60c2116-fd19-4431-a57c-102fcc41ebd9.jpg"/> </div><br>
**基本数据结构**
```java
public class UF {
// 使用 id 数组来保存点的连通信息
private int[] id;
public UF(int N) {
id = new int[N];
for (int i = 0; i < N; i++) {
id[i] = i;
}
}
public boolean connected(int p, int q) {
return find(p) == find(q);
}
}
```
## 1. quick-find 算法
保证在同一连通分量的所有触点的 id 值相等。
这种方法可以快速取得一个触点的 id 值,并且判断两个触点是否连通,但是 union 的操作代价却很高,需要将其中一个连通分量中的所有节点 id 值都修改为另一个节点的 id 值。
```java
public int find(int p) {
return id[p];
}
public void union(int p, int q) {
int pID = find(p);
int qID = find(q);
if (pID == qID) return;
for (int i = 0; i < id.length; i++) {
if (id[i] == pID) id[i] = qID;
}
}
```
## 2. quick-union 算法
在 union 时只将触点的 id 值指向另一个触点 id 值,不直接用 id 来存储所属的连通分量。这样就构成一个倒置的树形结构,根节点需要指向自己。在进行查找一个节点所属的连通分量时,要一直向上查找直到根节点,并使用根节点的 id 值作为本连通分量的 id 值。
<div align="center"> <img src="../pics//81a75fed-5c1d-4e4c-af4a-4c38c2a48927.jpg"/> </div><br>
```java
public int find(int p) {
while (p != id[p]) p = id[p];
return p;
}
public void union(int p, int q) {
int pRoot = find(p);
int qRoot = find(q);
if (pRoot == qRoot) return;
id[pRoot] = qRoot;
}
```
这种方法可以快速进行 union 操作,但是 find 操作和树高成正比,最坏的情况下树的高度为触点的数目。
<div align="center"> <img src="../pics//70a09383-f432-4b0f-ba42-b5b30d104f0b.jpg"/> </div><br>
## 3. 加权 quick-union 算法
为了解决 quick-union 的树通常会很高的问题,加权 quick-union 在 union 操作时会让较小的树连接较大的树上面。
理论研究证明,加权 quick-union 算法构造的树深度最多不超过 lgN。
<div align="center"> <img src="../pics//b0d94736-e157-4886-aff2-c303735b0a24.jpg"/> </div><br>
```java
public class WeightedQuickUnionUF {
private int[] id;
// 保存节点的数量信息
private int[] sz;
public WeightedQuickUnionUF(int N) {
id = new int[N];
sz = new int[N];
for (int i = 0; i < N; i++) {
id[i] = i;
sz[i] = 1;
}
}
public boolean connected(int p, int q) {
return find(p) == find(q);
}
public int find(int p) {
while (p != id[p]) p = id[p];
return p;
}
public void union(int p, int q) {
int i = find(p);
int j = find(q);
if (i == j) return;
if (sz[i] < sz[j]) {
id[i] = j;
sz[j] += sz[i];
} else {
id[j] = i;
sz[i] += sz[j];
}
}
}
```
## 4. 路径压缩的加权 quick-union 算法
在检查节点的同时将它们直接链接到根节点,只需要在 find 中添加一个循环即可。
## 5. 各种 union-find 算法的比较
<div align="center"> <img src="../pics//2b6037b2-ec69-4235-ad0e-886fa320d645.jpg"/> </div><br>
# 排序 # 排序
## 1. 初级排序算法 ## 1. 初级排序算法

View File

@ -25,9 +25,9 @@
* [1.2 短作业优先](#12-短作业优先) * [1.2 短作业优先](#12-短作业优先)
* [1.3 最短剩余时间优先](#13-最短剩余时间优先) * [1.3 最短剩余时间优先](#13-最短剩余时间优先)
* [2. 交互式系统中的调度](#2-交互式系统中的调度) * [2. 交互式系统中的调度](#2-交互式系统中的调度)
* [2.1 轮转调度](#21-轮转调度) * [2.1 优先级调度](#21-优先级调度)
* [2.2 优先级调度](#22-优先级调度) * [2.2 时间片轮转](#22-时间片轮转)
* [2.3 多级队列](#23-多级队列) * [2.3 多级反馈队列](#23-多级反馈队列)
* [3. 实时系统中的调度](#3-实时系统中的调度) * [3. 实时系统中的调度](#3-实时系统中的调度)
* [进程同步](#进程同步) * [进程同步](#进程同步)
* [1. 临界区](#1-临界区) * [1. 临界区](#1-临界区)
@ -48,18 +48,19 @@
* [死锁的必要条件](#死锁的必要条件) * [死锁的必要条件](#死锁的必要条件)
* [死锁的处理方法](#死锁的处理方法) * [死锁的处理方法](#死锁的处理方法)
* [1. 鸵鸟策略](#1-鸵鸟策略) * [1. 鸵鸟策略](#1-鸵鸟策略)
* [2. 死锁预防](#2-死锁预防) * [2. 死锁检测与死锁恢复](#2-死锁检测与死锁恢复)
* [2.1 破坏互斥条件](#21-破坏互斥条件) * [2.1 每种类型一个资源的死锁检测](#21-每种类型一个资源的死锁检测)
* [2.2 破坏占有和等待条件](#22-破坏占有和等待条件) * [2.2 每种类型多个资源的死锁检测](#22-每种类型多个资源的死锁检测)
* [2.3 破坏不可抢占条件](#23-破坏不可抢占条件) * [2.3 死锁恢复](#23-死锁恢复)
* [2.4 破坏环路等待](#24-破坏环路等待) * [3. 死锁预防](#3-死锁预防)
* [3. 死锁避免](#3-死锁避免) * [3.1 破坏互斥条件](#31-破坏互斥条件)
* [3.1 安全状态](#31-安全状态) * [3.2 破坏占有和等待条件](#32-破坏占有和等待条件)
* [3.2 单个资源的银行家算法](#32-单个资源的银行家算法) * [3.3 破坏不可抢占条件](#33-破坏不可抢占条件)
* [3.3 多个资源的银行家算法](#33-多个资源的银行家算法) * [3.4 破坏环路等待](#34-破坏环路等待)
* [4. 死锁检测与死锁恢复](#4-死锁检测与死锁恢复) * [4. 死锁避免](#4-死锁避免)
* [4.1 死锁检测算法](#41-死锁检测算法) * [4.1 安全状态](#41-安全状态)
* [4.2 死锁恢复](#42-死锁恢复) * [4.2 单个资源的银行家算法](#42-单个资源的银行家算法)
* [4.3 多个资源的银行家算法](#43-多个资源的银行家算法)
* [第四章 存储器管理](#第四章-存储器管理) * [第四章 存储器管理](#第四章-存储器管理)
* [虚拟内存](#虚拟内存) * [虚拟内存](#虚拟内存)
* [分页与分段](#分页与分段) * [分页与分段](#分页与分段)
@ -210,13 +211,7 @@ shortest remaining time nextSRTN
### 2. 交互式系统中的调度 ### 2. 交互式系统中的调度
#### 2.1 轮转调度 #### 2.1 优先级调度
将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 分配给队首的进程。
时间片轮转算法的效率和时间片的大小有很大关系。如果时间片设置太短会导致过多的进程切换,减低 CPU 效率;而设得太长有可能引起对短的交互请求的响应时间变长。
#### 2.2 优先级调度
除了可以手动赋予优先权之外,还可以把响应比作为优先权,这种调度方式叫做高响应比优先调度算法。 除了可以手动赋予优先权之外,还可以把响应比作为优先权,这种调度方式叫做高响应比优先调度算法。
@ -224,7 +219,13 @@ shortest remaining time nextSRTN
这种调度算法主要是为了解决 SJF 中长作业可能会饿死的问题,因为随着等待时间的增长,响应比也会越来越高。 这种调度算法主要是为了解决 SJF 中长作业可能会饿死的问题,因为随着等待时间的增长,响应比也会越来越高。
#### 2.3 多级队列 #### 2.2 时间片轮转
将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 分配给队首的进程。
时间片轮转算法的效率和时间片的大小有很大关系。因为每次进程切换都要保存进程的信息并且载入新进程的信息,如果时间片太小,进程切换太频繁,在进程切换上就会花过多时间。
#### 2.3 多级反馈队列
<div align="center"> <img src="../pics//042cf928-3c8e-4815-ae9c-f2780202c68f.png"/> </div><br> <div align="center"> <img src="../pics//042cf928-3c8e-4815-ae9c-f2780202c68f.png"/> </div><br>
@ -272,15 +273,15 @@ down 和 up 操作需要被设计成原语,不可分割,通常的做法是
typedef int semaphore; typedef int semaphore;
semaphore mutex = 1; semaphore mutex = 1;
void P1() { void P1() {
down(mutex); down(&mutex);
// 临界区 // 临界区
up(mutex); up(&mutex);
} }
void P2() { void P2() {
down(mutex); down(&mutex);
// 临界区 // 临界区
up(mutex); up(&mutex);
} }
``` ```
@ -290,9 +291,9 @@ void P2() {
需要使用一个互斥量 mutex 来对缓冲区这个临界资源进行互斥访问。 需要使用一个互斥量 mutex 来对缓冲区这个临界资源进行互斥访问。
为了控制生产者和消费者的行为需要记录缓冲区中物品的数量。数量可以使用信号量来进行统计这里需要使用两个信号量empty 记录空缓冲区的数量full 记录满缓冲区的数量。其中empty 信号量是在生产者进程中使用,当 empty 不为 0 时生产者才可以放入物品full 信号量是在消费者进行中使用,当 full 信号量不为 0 时,消费者才可以取走物品。 为了同步生产者和消费者的行为需要记录缓冲区中物品的数量。数量可以使用信号量来进行统计这里需要使用两个信号量empty 记录空缓冲区的数量full 记录满缓冲区的数量。其中empty 信号量是在生产者进程中使用,当 empty 不为 0 时生产者才可以放入物品full 信号量是在消费者进行中使用,当 full 信号量不为 0 时,消费者才可以取走物品。
注意,不能先对缓冲区进行加锁,再测试信号量。也就是说,不能先执行 down(mutex) 再执行 down(empty)。如果这么做了,那么可能会出现这种情况:生产者对缓冲区加锁后,执行 down(empty) 操作,发现 empty=0此时生成者睡眠。消费者此时不能进入临界区,因为生产者对缓冲区加锁了,也就无法执行 up(empty) 操作,那么生产者和消费者就会一直等待下去。 注意,不能先对缓冲区进行加锁,再测试信号量。也就是说,不能先执行 down(mutex) 再执行 down(empty)。如果这么做了,那么可能会出现这种情况:生产者对缓冲区加锁后,执行 down(empty) 操作,发现 empty = 0此时生产者睡眠。消费者不能进入临界区,因为生产者对缓冲区加锁了,也就无法执行 up(empty) 操作,empty 永远都为 0那么生产者和消费者就会一直等待下去,造成死锁
```c ```c
#define N 100 #define N 100
@ -303,22 +304,22 @@ semaphore full = 0;
void producer() { void producer() {
while(TRUE){ while(TRUE){
int item = produce_item; int item = produce_item();
down(empty); down(&empty);
down(mutex); down(&mutex);
insert_item(item); insert_item(item);
up(mutex); up(&mutex);
up(full); up(&full);
} }
} }
void consumer() { void consumer() {
while(TRUE){ while(TRUE){
down(full); down(&full);
down(mutex); down(&mutex);
int item = remove_item(item); int item = remove_item();
up(mutex); up(&mutex);
up(empty); up(&empty);
consume_item(item); consume_item(item);
} }
} }
@ -401,9 +402,7 @@ end;
### 1. 管道 ### 1. 管道
管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。写进程在管道的尾端写入数据,读进程在管道的首端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。 写进程在管道的尾端写入数据,读进程在管道的首端读出数据。管道提供了简单的流控制机制,进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样地,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。
管道提供了简单的流控制机制,进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样地,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。
Linux 中管道是通过空文件来实现。 Linux 中管道是通过空文件来实现。
@ -415,7 +414,7 @@ Linux 中管道是通过空文件来实现。
### 2. 信号量 ### 2. 信号量
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止进程正在访问共享资源时,其它进程也访问该资源。主要作为进程间以及同一进程内不同线程之间的同步手段。 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止一个进程正在访问共享资源时,其它进程也访问该资源。主要作为进程间以及同一进程内不同线程之间的同步手段。
### 3. 消息队列 ### 3. 消息队列
@ -453,23 +452,23 @@ int count = 0;
void reader() { void reader() {
while(TRUE) { while(TRUE) {
down(count_mutex); down(&count_mutex);
count++; count++;
if(count == 1) down(data_mutex); // 第一个读者需要对数据进行加锁,防止写进程访问 if(count == 1) down(&data_mutex); // 第一个读者需要对数据进行加锁,防止写进程访问
up(count_mutex); up(&count_mutex);
read(); read();
down(count_mutex); down(&count_mutex);
count--; count--;
if(count == 0) up(data_mutex); if(count == 0) up(&data_mutex);
up(count_mutex); up(&count_mutex);
} }
} }
void writer() { void writer() {
while(TRUE) { while(TRUE) {
down(data_mutex); down(&data_mutex);
write(); write();
up(data_mutex); up(&data_mutex);
} }
} }
``` ```
@ -478,50 +477,78 @@ void writer() {
<div align="center"> <img src="../pics//a9077f06-7584-4f2b-8c20-3a8e46928820.jpg"/> </div><br> <div align="center"> <img src="../pics//a9077f06-7584-4f2b-8c20-3a8e46928820.jpg"/> </div><br>
五个哲学家围着一张圆桌,每个哲学家面前放着。哲学家的生活有两种交替活动:吃饭以及思考。当一个哲学家吃饭时,需要先一根一根拿起左右两边的筷子。 五个哲学家围着一张圆桌,每个哲学家面前放着食物。哲学家的生活有两种交替活动:吃饭以及思考。当一个哲学家吃饭时,需要先拿起筷子左右的两根筷子,并且一次只能拿起一根筷子。
下面是一种错误的解法,考虑到如果每个哲学家同时拿起左手边的筷子,那么就无法拿起右手边的筷子,造成死锁。 下面是一种错误的解法,考虑到如果所有哲学家同时拿起左手边的筷子,那么就无法拿起右手边的筷子,造成死锁。
```c ```c
#define N 5 #define N 5
#define LEFT (i + N - 1) % N
#define RIGHT (i + N) % N
typedef int semaphore;
semaphore chopstick[N];
void philosopher(int i) { void philosopher(int i) {
while(TURE){ while(TRUE) {
think(); think();
down(chopstick[LEFT[i]]); take(i); // 拿起左边的筷子
down(chopstick[RIGHT[i]]); take((i+1)%N); // 拿起右边的筷子
eat(); eat();
up(chopstick[RIGHT[i]]); put(i);
up(chopstick[LEFT[i]]); put((i+1)%N);
} }
} }
``` ```
为了防止死锁的发生,可以加一点限制,只允许同时拿起左右两边的筷子。方法是引入一个互斥量,对拿起两个筷子的那段代码加锁。 为了防止死锁的发生,可以两个条件:
1. 必须同时拿起左右两个筷子;
2. 只有在两个邻居都没有进餐的情况下才允许进餐。
```c ```c
semaphore mutex = 1; #define N 5
#define LEFT (i + N - 1) % N // 左邻居
#define RIGHT (i + 1) % N // 右邻居
#define THINKING 0
#define HUNGRY 1
#define EATING 2
typedef int semaphore;
int state[N]; // 跟踪每个哲学家的状态
semaphore mutex = 1; // 临界区的互斥
semaphore s[N]; // 每个哲学家一个信号量
void philosopher(int i) { void philosopher(int i) {
while(TURE){ while(TRUE) {
think(); think();
down(mutex); take_two(i);
down(chopstick[LEFT[i]]);
down(chopstick[RIGHT[i]]);
up(mutex);
eat(); eat();
down(mutex); put_tow(i);
up(chopstick[RIGHT[i]]); }
up(chopstick[LEFT[i]]); }
up(mutex);
void take_two(int i) {
down(&mutex);
state[i] = HUNGRY;
test(i);
up(&mutex);
down(&s[i]);
}
void put_tow(i) {
down(&mutex);
state[i] = THINKING;
text(LEFT);
test(RIGHT);
up(&mutex);
}
void test(i) { // 尝试拿起两把筷子
if(state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] !=EATING) {
state[i] = EATING;
up(&s[i]);
} }
} }
``` ```
# 第三章 死锁 # 第三章 死锁
## 死锁的必要条件 ## 死锁的必要条件
@ -539,65 +566,25 @@ void philosopher(int i) {
把头埋在沙子里,假装根本没发生问题。 把头埋在沙子里,假装根本没发生问题。
这种策略不可取 因为解决死锁问题的代价很高,因此鸵鸟策略这种不采取任务措施的方案会获得更高的性能。当发生死锁时不会对用户造成多大影响,或发生死锁的概率很低,可以采用鸵鸟策略
### 2. 死锁预防 大多数操作系统,包括 UnixLinux 和 Windows处理死锁问题的办法仅仅是忽略它。
在程序运行之前预防发生死锁。 ### 2. 死锁检测与死锁恢复
#### 2.1 破坏互斥条件
例如假脱机打印机技术允许若干个进程同时输出,唯一真正请求物理打印机的进程是打印机守护进程。
#### 2.2 破坏占有和等待条件
一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。
#### 2.3 破坏不可抢占条件
#### 2.4 破坏环路等待
给资源统一编号,进程只能按编号顺序来请求资源。
### 3. 死锁避免
在程序运行时避免发生死锁。
#### 3.1 安全状态
<div align="center"> <img src="../pics//ed523051-608f-4c3f-b343-383e2d194470.png"/> </div><br>
图 a 的第二列 has 表示已拥有的资源数,第三列 max 表示总共需要的资源数free 表示还有可以使用的资源数。从图 a 开始出发,先让 B 拥有所需的所有资源,运行结束后释放 B此时 free 变为 4接着以同样的方式运行 C 和 A使得所有进程都能成功运行因此可以称图 a 所示的状态时安全的。
定义:如果没有死锁发生,并且即使所有进程突然请求对资源的最大需求,也仍然存在某种调度次序能够使得每一个进程运行完毕,则称该状态是安全的。
#### 3.2 单个资源的银行家算法
一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。
<div align="center"> <img src="../pics//d160ec2e-cfe2-4640-bda7-62f53e58b8c0.png"/> </div><br>
上图 c 为不安全状态,因此算法会拒绝之前的请求,从而避免进入图 c 中的状态。
#### 3.3 多个资源的银行家算法
<div align="center"> <img src="../pics//62e0dd4f-44c3-43ee-bb6e-fedb9e068519.png"/> </div><br>
上图中有五个进程,四个资源。左边的图表示已经分配的资源,右边的图表示还需要分配的资源。最右边的 E、P 以及 A 分别表示:总资源、已分配资源以及可用资源,注意这三个为向量,而不是具体数值,例如 A=(1020),表示 4 个资源分别还剩下 1/0/2/0。
检查一个状态是否安全的算法如下:
- 查找右边的矩阵是否存在一行小于等于向量 A。如果不存在这样的行那么系统将会发生死锁状态是不安全的。
- 假若找到这样一行,将该进程标记为终止,并将其已分配资源加到 A 中。
- 重复以上两步,直到所有进程都标记为终止,则状态时安全的。
### 4. 死锁检测与死锁恢复
不试图阻止死锁,而是当检测到死锁发生时,采取措施进行恢复。 不试图阻止死锁,而是当检测到死锁发生时,采取措施进行恢复。
#### 4.1 死锁检测算法 #### 2.1 每种类型一个资源的死锁检测
死锁检测的基本思想是,如果一个进程所请求的资源能够被满足,那么就让它执行,释放它拥有的所有资源,然后让其它能满足条件的进程执行。 <div align="center"> <img src="../pics//b1fa0453-a4b0-4eae-a352-48acca8fff74.png"/> </div><br>
上图为资源分配图,其中方框表示资源,圆圈表示进程。资源指向进程表示该资源已经分配给该进程,进程指向资源表示进程请求获取该资源。
图 a 可以抽取出环,如图 b它满足了环路等待条件因此会发生死锁。
每种类型一个资源的死锁检测算法是通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。
#### 2.2 每种类型多个资源的死锁检测
<div align="center"> <img src="../pics//e1eda3d5-5ec8-4708-8e25-1a04c5e11f48.png"/> </div><br> <div align="center"> <img src="../pics//e1eda3d5-5ec8-4708-8e25-1a04c5e11f48.png"/> </div><br>
@ -618,32 +605,87 @@ void philosopher(int i) {
2. 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 1。 2. 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 1。
3. 如果没有这样一个进程,算法终止。 3. 如果没有这样一个进程,算法终止。
可以看到,死锁检测和银行家算法中判断是否为安全状态的方法类似。 #### 2.3 死锁恢复
#### 4.2 死锁恢复
- 利用抢占恢复 - 利用抢占恢复
- 杀死进程 - 利用回滚恢复
- 通过杀死进程恢复
### 3. 死锁预防
在程序运行之前预防发生死锁。
#### 3.1 破坏互斥条件
例如假脱机打印机技术允许若干个进程同时输出,唯一真正请求物理打印机的进程是打印机守护进程。
#### 3.2 破坏占有和等待条件
一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。
#### 3.3 破坏不可抢占条件
#### 3.4 破坏环路等待
给资源统一编号,进程只能按编号顺序来请求资源。
### 4. 死锁避免
在程序运行时避免发生死锁。
#### 4.1 安全状态
<div align="center"> <img src="../pics//ed523051-608f-4c3f-b343-383e2d194470.png"/> </div><br>
图 a 的第二列 Has 表示已拥有的资源数,第三列 Max 表示总共需要的资源数Free 表示还有可以使用的资源数。从图 a 开始出发,先让 B 拥有所需的所有资源(图 b运行结束后释放 B此时 Free 变为 5图 c接着以同样的方式运行 C 和 A使得所有进程都能成功运行因此可以称图 a 所示的状态时安全的。
定义:如果没有死锁发生,并且即使所有进程突然请求对资源的最大需求,也仍然存在某种调度次序能够使得每一个进程运行完毕,则称该状态是安全的。
安全状态的检测与死锁的检测类似,因为安全状态必须要求不能发生死锁。下面的银行家算法与死锁检测算法非常类似,可以结合着做参考对比。
#### 4.2 单个资源的银行家算法
一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。
<div align="center"> <img src="../pics//d160ec2e-cfe2-4640-bda7-62f53e58b8c0.png"/> </div><br>
上图 c 为不安全状态,因此算法会拒绝之前的请求,从而避免进入图 c 中的状态。
#### 4.3 多个资源的银行家算法
<div align="center"> <img src="../pics//62e0dd4f-44c3-43ee-bb6e-fedb9e068519.png"/> </div><br>
上图中有五个进程,四个资源。左边的图表示已经分配的资源,右边的图表示还需要分配的资源。最右边的 E、P 以及 A 分别表示:总资源、已分配资源以及可用资源,注意这三个为向量,而不是具体数值,例如 A=(1020),表示 4 个资源分别还剩下 1/0/2/0。
检查一个状态是否安全的算法如下:
- 查找右边的矩阵是否存在一行小于等于向量 A。如果不存在这样的行那么系统将会发生死锁状态是不安全的。
- 假若找到这样一行,将该进程标记为终止,并将其已分配资源加到 A 中。
- 重复以上两步,直到所有进程都标记为终止,则状态时安全的。
# 第四章 存储器管理 # 第四章 存储器管理
## 虚拟内存 ## 虚拟内存
每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。 每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。当程序引用到一部分不在物理内存中的地址空间时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。
当程序引用到一部分在物理内存中的地址空间时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。
## 分页与分段 ## 分页与分段
### 1. 分页 ### 1. 分页
用户程序的地址空间被划分为若干固定大小的区域,称为“页”。相应地,内存空间分成若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配,由一个页表来维护它们之间的映射关系。 大部分虚拟内存系统都使用分页技术。把由程序产生的地址称为虚拟地址,它们构成了一个虚拟地址空间。例如有一台计算机可以产生 16 位地址,它的虚拟地址空间为 0\~64K然而计算机只有 32KB 的物理内存,因此虽然可以编写 64KB 的程序,但它们不能被完全调入内存运行。
<div align="center"> <img src="../pics//7b281b1e-0595-402b-ae35-8c91084c33c1.png"/> </div><br>
虚拟地址空间划分成固定大小的页,在物理内存中对应的单元称为页框,页和页框大小通常相同,它们之间通过页表进行映射。
程序最开始只将一部分页调入页框中,当程序引用到没有在页框的页时,产生缺页中断,进行页面置换,按一定的原则将一部分页框换出,并将页调入。
### 2. 分段 ### 2. 分段
<div align="center"> <img src="../pics//22de0538-7c6e-4365-bd3b-8ce3c5900216.png"/> </div><br> <div align="center"> <img src="../pics//22de0538-7c6e-4365-bd3b-8ce3c5900216.png"/> </div><br>
上图为一个编译器在编译过程中建立的多个表,有 4 个表是动态增长的,如果使用分页系统的一维地址空间,动态递增的特点会导致覆盖问题的出现。 上图为一个编译器在编译过程中建立的多个表,有 4 个表是动态增长的,如果使用分页系统的一维地址空间,动态增的特点会导致覆盖问题的出现。
<div align="center"> <img src="../pics//e0900bb2-220a-43b7-9aa9-1d5cd55ff56e.png"/> </div><br> <div align="center"> <img src="../pics//e0900bb2-220a-43b7-9aa9-1d5cd55ff56e.png"/> </div><br>

View File

@ -85,6 +85,10 @@
* [动态主机配置协议 DHCP](#动态主机配置协议-dhcp) * [动态主机配置协议 DHCP](#动态主机配置协议-dhcp)
* [点对点传输 P2P](#点对点传输-p2p) * [点对点传输 P2P](#点对点传输-p2p)
* [Web 页面请求过程](#web-页面请求过程) * [Web 页面请求过程](#web-页面请求过程)
* [1. DHCP 配置主机信息](#1-dhcp-配置主机信息)
* [2. ARP 解析 MAC 地址](#2-arp-解析-mac-地址)
* [3. DNS 解析域名](#3-dns-解析域名)
* [4. HTTP 请求页面](#4-http-请求页面)
* [常用端口](#常用端口) * [常用端口](#常用端口)
* [参考资料](#参考资料) * [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
@ -327,7 +331,7 @@ CSMA/CD 表示载波监听多点接入 / 碰撞检测。
- **多点接入** :说明这是总线型网络,许多计算机以多点的方式连接到总线上。 - **多点接入** :说明这是总线型网络,许多计算机以多点的方式连接到总线上。
- **载波监听** :每个站都必须不停地监听信道。在发送前,如果检听信道正在使用,就必须等待。 - **载波监听** :每个站都必须不停地监听信道。在发送前,如果检听信道正在使用,就必须等待。
- **碰撞检测** :在发送中,如果检听到信道已有其它站正在发送数据,就表示发生了碰撞。虽然每一个站在发送数据之前都已经检听到信道为空闲,但是由于电磁波的传播时延的存在,还是有可能会发生碰撞。 - **碰撞检测** :在发送中,如果监听 到信道已有其它站正在发送数据,就表示发生了碰撞。虽然每一个站在发送数据之前都已经检听到信道为空闲,但是由于电磁波的传播时延的存在,还是有可能会发生碰撞。
<div align="center"> <img src="../pics//f9ed4da5-0032-41e6-991a-36d995ec28fd.png"/> </div><br> <div align="center"> <img src="../pics//f9ed4da5-0032-41e6-991a-36d995ec28fd.png"/> </div><br>
@ -831,22 +835,63 @@ P2P 是一个分布式系统,任何时候都有对等方加入或者退出。
## Web 页面请求过程 ## Web 页面请求过程
1. 向 DNS 服务器发送 DNS 查询报文来解析域名。 ### 1. DHCP 配置主机信息
2. 开始进行 HTTP 会话,需要先建立 TCP 连接 1. 假设主机最开始没有 IP 地址以及其它信息,那么就需要先使用 DHCP 来获取
3. 在运输层的传输过程中HTTP 报文被封装进 TCP 中。HTTP 请求报文使用端口号 80因为服务器监听的是 80 端口。连接建立之后,服务器会随机分配一个端口号给特定的客户端,之后的 TCP 传输都是用这个分配的端口号 2. 主机生成一个 DHCP 请求报文,并将这个报文放入具有目的端口 67 和源端口 68 的 UDP 报文段中
4. 在网络层的传输过程中TCP 报文段会被封装进 IP 分组中IP 分组经过路由选择,最后到达目的地 3. 该报文段则被放入在一个具有广播 IP 目的地址(255.255.255.255) 和源 IP 地址0.0.0.0)的 IP 数据报中
5. 在链路层IP 分组会被封装进 MAC 帧中IP 地址解析成 MAC 地址需要使用 ARP 4. 该数据报则被放置在 MAC 帧中,该帧具有目的地址 FF:FF:FF:FF:FF:FF将广播到与交换机连接的所有设备
6. 客户端发送 HTTP 请求报文,请求获取页面 5. 连接在交换机的 DHCP 服务器收到广播帧之后,不断地向上分解得到 IP 数据报、UDP 报文段、DHCP 请求报文,之后生成 DHCP ACK 报文该报文包含以下信息IP 地址、DNS 服务器的 IP 地址、默认网关路由器的 IP 地址和子网掩码。该报文被放入 UDP 报文段中UDP 报文段有被放入 IP 数据报中,最后放入 MAC 帧中
7. 服务器发送 HTTP 相应报文,客户端从而获取该页面 8. 该帧的目的地址是请求主机的 MAC 地址,因为交换机具有自学习能力,之前主机发送了广播帧之后就记录了 MAC 地址到其转发接口的交换表项,因此现在交换机就可以直接知道应该向哪个接口发送该帧
8. 浏览器得到页面内容之后,解析并渲染,向用户展示页面 9. 主机收到该帧后,不断分解得到 DHCP 报文。之后就配置它的 IP 地址、子网掩码和 DNS 服务器的 IP 地址,并在其 IP 转发表中安装默认网关
### 2. ARP 解析 MAC 地址
1. 主机通过浏览器生成一个 TCP 套接字,套接字向 HTTP 服务器发送 HTTP 请求。为了生成该套接字,主机需要知道网站的域名对应的 IP 地址。
2. 主机生成一个 DNS 查询报文,该报文具有 53 号端口,因为 DNS 服务器的端口号是 53。
3. 该 DNS 查询报文被放入目的地址为 DNS 服务器 IP 地址的 IP 数据报中。
4. 该 IP 数据报被放入一个以太网帧中,该帧将发送到网关路由器。
5. DHCP 过程只知道网关路由器的 IP 地址,为了获取网关路由器的 MAC 地址,需要使用 ARP 协议。
6. 主机生成一个包含目的地址为网关路由器 IP 地址的 ARP 查询报文,将该 ARP 查询报文放入一个具有广播目的地址FF:FF:FF:FF:FF:FF的以太网帧中并向交换机发送该以太网帧交换机将该帧转发给所有的连接设备包括网关路由器。
7. 网关路由器接收到该帧后,不断向上分解得到 ARP 报文,发现其中的 IP 地址与其接口的 IP 地址匹配,因此就发送一个 ARP 回答报文,包含了它的 MAC 地址,发回给主机。
### 3. DNS 解析域名
1. 知道了网关路由器的 MAC 地址之后,就可以继续 DNS 的解析过程了。
2. 网关路由器接收到包含 DNS 查询报文的以太网帧后,抽取出 IP 数据报,并根据转发表决定该 IP 数据报应该转发的路由器。
3. 因为路由器具有内部网关协议RIP、OSPF和外部网关协议BGP这两种路由选择协议因此路由表中已经配置了网关路由器到达 DNS 服务器的路由表项。
4. 到达 DNS 服务器之后DNS 服务器抽取出 DNS 查询报文,并在 DNS 数据库中查找待解析的域名。
5. 找到 DNS 记录之后,发送 DNS 回答报文,将该回答报文放入 UDP 报文段中,然后放入 IP 数据报中,通过路由器反向转发回网关路由器,并经过以太网交换机到达主机。
### 4. HTTP 请求页面
1. 有了 HTTP 服务器的 IP 地址之后,主机就能够生成 TCP 套接字,该套接字将用于向 Web 服务器发送 HTTP GET 报文。
2. 在生成 TCP 套接字之前,必须先与 HTTP 服务器进行三次握手来建立连接。生成一个具有目的端口 80 的 TCP SYN 报文段,并向 HTTP 服务器发送该报文段。
3. HTTP 服务器收到该报文段之后,生成 TCP SYNACK 报文段,发回给主机。
4. 连接建立之后,浏览器生成 HTTP GET 报文,并交付给 HTTP 服务器。
5. HTTP 服务器从 TCP 套接字读取 HTTP GET 报文,生成一个 HTTP 响应报文,将 Web 页面内容放入报文主体中,发回给主机。
6. 浏览器收到 HTTP 响应报文后,抽取出 Web 页面内容,之后进行渲染,显示 Web 页面。
## 常用端口 ## 常用端口

View File

@ -1,28 +1,25 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [前言](#前言) * [前言](#前言)
* [第一章 设计模式入门](#第一章-设计模式入门) * [设计模式入门](#设计模式入门)
* [第二章 观察者模式](#第二章-观察者模式) * [观察者模式](#观察者模式)
* [第三章 装饰模式](#第三章-装饰模式) * [装饰模式](#装饰模式)
* [第四章 工厂模式](#第四章-工厂模式) * [工厂模式](#工厂模式)
* [1. 简单工厂](#1-简单工厂) * [1. 简单工厂](#1-简单工厂)
* [2. 工厂方法模式](#2--工厂方法模式) * [2. 工厂方法模式](#2--工厂方法模式)
* [3. 抽象工厂模式](#3--抽象工厂模式) * [3. 抽象工厂模式](#3--抽象工厂模式)
* [第五章 单件模式](#第五章-单件模式) * [单件模式](#单件模式)
* [第六章 命令模式](#第六章-命令模式) * [命令模式](#命令模式)
* [第七章 适配器模式与外观模式](#第七章-适配器模式与外观模式) * [适配器模式](#适配器模式)
* [1. 适配器模式](#1-适配器模式) * [外观模式](#外观模式)
* [2. 外观模式](#2-外观模式) * [模板方法模式](#模板方法模式)
* [第八章 模板方法模式](#第八章-模板方法模式) * [迭代器模式](#迭代器模式)
* [第九章 迭代器和组合模式](#第九章-迭代器和组合模式) * [组合模式](#组合模式)
* [1. 迭代器模式](#1-迭代器模式) * [状态模式](#状态模式)
* [2. Java 内置的迭代器](#2-java-内置的迭代器) * [代理模式](#代理模式)
* [3. 组合模式](#3-组合模式) * [复合模式](#复合模式)
* [第十章 状态模式](#第十章-状态模式)
* [第十一章 代理模式](#第十一章-代理模式)
* [第十二章 复合模式](#第十二章-复合模式)
* [MVC](#mvc) * [MVC](#mvc)
* [第十三章 与设计模式相处](#第十三章-与设计模式相处) * [与设计模式相处](#与设计模式相处)
* [第十四章 剩下的模式](#第十四章-剩下的模式) * [剩下的模式](#剩下的模式)
<!-- GFM-TOC --> <!-- GFM-TOC -->
@ -34,7 +31,7 @@
<div align="center"> <img src="../pics//09e398d8-9c6e-48f6-b48b-8b4f9de61d1d.png"/> </div><br> <div align="center"> <img src="../pics//09e398d8-9c6e-48f6-b48b-8b4f9de61d1d.png"/> </div><br>
# 第一章 设计模式入门 # 设计模式入门
**1. 设计模式概念** **1. 设计模式概念**
@ -70,7 +67,7 @@
**5. 整体设计图** **5. 整体设计图**
<div align="center"> <img src="../pics//e13833c8-e215-462e-855c-1d362bb8d4a0.jpg"/> </div><br> <div align="center"> <img src="../pics//d887219c-963a-4392-abe7-d3967546e96d.jpg"/> </div><br>
**6. 模式定义** **6. 模式定义**
@ -179,11 +176,11 @@ FlyBehavior.FlyWithWings
FlyBehavior.FlyNoWay FlyBehavior.FlyNoWay
``` ```
# 第二章 观察者模式 # 观察者模式
**1. 模式定义** **1. 模式定义**
定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会到通知并自动更新。主题Subject是被观察的对象而其所有依赖者Observer成为观察者。 定义了对象之间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会到通知并自动更新。主题Subject是被观察的对象而其所有依赖者Observer成为观察者。
<div align="center"> <img src="../pics//26cb5e7e-6fa3-44ad-854e-fe24d1a5278c.jpg"/> </div><br> <div align="center"> <img src="../pics//26cb5e7e-6fa3-44ad-854e-fe24d1a5278c.jpg"/> </div><br>
@ -193,7 +190,7 @@ FlyBehavior.FlyNoWay
观察者拥有一个主题对象的引用,因为注册、移除还有数据都在主题当中,必须通过操作主题才能完成相应功能。 观察者拥有一个主题对象的引用,因为注册、移除还有数据都在主题当中,必须通过操作主题才能完成相应功能。
<div align="center"> <img src="../pics//5c558190-fccd-4b5e-98ed-1896653fc97f.jpg"/> </div><br> <div align="center"> <img src="../pics//58b9926c-b56c-42f7-82e3-86aa0c164d0a.jpg"/> </div><br>
**3. 问题描述** **3. 问题描述**
@ -201,7 +198,7 @@ FlyBehavior.FlyNoWay
**4. 解决方案类图** **4. 解决方案类图**
<div align="center"> <img src="../pics//760a5d63-d96d-4dd9-bf9a-c3d126b2f401.jpg"/> </div><br> <div align="center"> <img src="../pics//73ecb593-664e-490e-80e9-4319773113ef.png"/> </div><br>
**5. 设计原则** **5. 设计原则**
@ -217,8 +214,6 @@ public interface Subject {
} }
``` ```
```java ```java
import java.util.ArrayList;
import java.util.List;
public class WeatherData implements Subject { public class WeatherData implements Subject {
private List<Observer> observers; private List<Observer> observers;
@ -313,7 +308,7 @@ CurrentConditionsDisplay.update:1.0 1.0 1.0
StatisticsDisplay.update:1.0 1.0 1.0 StatisticsDisplay.update:1.0 1.0 1.0
``` ```
# 第三章 装饰模式 # 装饰模式
**1. 问题描述** **1. 问题描述**
@ -335,7 +330,7 @@ StatisticsDisplay.update:1.0 1.0 1.0
**4. 问题解决方案的类图** **4. 问题解决方案的类图**
<div align="center"> <img src="../pics//9c997ac5-c8a7-44fe-bf45-2c10eb773e53.jpg"/> </div><br> <div align="center"> <img src="../pics//dac28811-79b6-4b75-bfa7-6b228e8ac3fb.png"/> </div><br>
**5. 设计原则** **5. 设计原则**
@ -343,7 +338,7 @@ StatisticsDisplay.update:1.0 1.0 1.0
**6. Java I/O 中的装饰者模式** **6. Java I/O 中的装饰者模式**
<div align="center"> <img src="../pics//2a40042a-03c8-4556-ad1f-72d89f8c555c.jpg"/> </div><br> <div align="center"> <img src="../pics//14583c71-8f57-4939-a9fc-065469b1bb7a.png"/> </div><br>
**7. 代码实现** **7. 代码实现**
@ -416,7 +411,7 @@ public class StartbuzzCoffee {
3.0 3.0
``` ```
# 第四章 工厂模式 # 工厂模式
## 1. 简单工厂 ## 1. 简单工厂
@ -426,15 +421,16 @@ public class StartbuzzCoffee {
**2. 定义** **2. 定义**
简单工厂不是设计模式,更像是一种编程习惯。在实例化一个超类的对象时,可以用它的所有子类来进行实例化,要根据具体需求来决定使用哪个子类。在这种情况下,把实例化的操作放到工厂来中,让工厂来决定应该用哪个子类来实例化。这样做把客户对象和具体子类的实现解耦,客户对象不再需要知道有哪些子类以及实例化哪个子类。因为客户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节,一旦子类发生改变,例如增加子类,那么所有的客户类都要发生改变。 简单工厂不是设计模式,更像是一种编程习惯。在实例化一个超类的对象时,可以用它的所有子类来进行实例化,要根据具体情况来决定使用哪个子类。在这种情况下,把实例化的操作放到简单工厂来中,让简单工厂来决定应该用哪个子类来实例化。
<div align="center"> <img src="../pics//c470eb9b-fb05-45c5-8bb7-1057dc3c16de.jpg"/> </div><br> 这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及实例化哪个子类。因为客户类往往有多个,如果不使用简单工厂,所有的客户类都要知道所有子类的细节。一旦子类发生改变,例如增加子类,那么所有的客户类都要发生改变。
<div align="center"> <img src="../pics//ec2f0a65-82ad-4ab9-940f-70ee9f6992cc.png"/> </div><br>
**3. 解决方案类图** **3. 解决方案类图**
<div align="center"> <img src="../pics//dc3e704c-7c57-42b8-93ea-ddd068665964.jpg"/> </div><br> <div align="center"> <img src="../pics//dc3e704c-7c57-42b8-93ea-ddd068665964.jpg"/> </div><br>
**4. 代码实现** **4. 代码实现**
```java ```java
@ -495,21 +491,27 @@ CheesePizza
**2. 模式定义** **2. 模式定义**
定义了一个创建对象的接口,但由子类决定要实例化的类是个。工厂方法让类把实例化推迟到子类。 定义了一个创建对象的接口,但由子类决定要实例化哪个。工厂方法让类把实例化推迟到子类。
**3. 模式类图** **3. 模式类图**
在简单工厂中创建对象的是另一个类而在工厂方法中是由子类来创建对象。下图中Creator 有一个 anOperation() 方法,这个方法需要用到一组产品类,这组产品类由每个子类来创建。 在简单工厂中创建对象的是另一个类而在工厂方法中是由子类来创建对象。下图中Creator 有一个 anOperation() 方法,这个方法需要用到一组产品类,这组产品类由 factoryMethod() 方法创建。该方法是抽象的,需要由子类去实现。
可以为每个子类创建单独的简单工厂来创建每一个产品类,但是把简单工厂中创建对象的代码放到子类中来可以减少类的数目,因为子类不算是产品类,因此完全可以这么做。
<div align="center"> <img src="../pics//903093ec-acc8-4f9b-bf2c-b990b9a5390c.jpg"/> </div><br> <div align="center"> <img src="../pics//903093ec-acc8-4f9b-bf2c-b990b9a5390c.jpg"/> </div><br>
**4. 解决方案类图** **4. 解决方案类图**
<div align="center"> <img src="../pics//664f8901-5dc7-4644-a072-dad88cc5133a.jpg"/> </div><br> PizzaStore 由 orderPizza() 方法,顾客可以用它来下单。下单之后需要先使用 createPizza() 来制作 Pizza这里的 createPizza() 就是 factoryMethod(),不同的 PizzaStore 子类实现了不同的 createPizza()。
**5. 代码实现** <div align="center"> <img src="../pics//cfb05050-47aa-4fd1-86eb-a7c86320f81b.png"/> </div><br>
**5. 设计原则**
**依赖倒置原则** :要依赖抽象,不要依赖具体类。听起来像是针对接口编程,不针对实现编程,但是这个原则说明了:不能让高层组件依赖底层组件,而且,不管高层或底层组件,两者都应该依赖于抽象。例如,下图中 Pizza 是抽象类PizzaStore 和 Pizza 子类都依赖于 Pizza 这个抽象类。
<div align="center"> <img src="../pics//ddf72ca9-c0be-49d7-ab81-57a99a974c8e.jpg"/> </div><br>
**6. 代码实现**
```java ```java
public interface Pizza { public interface Pizza {
@ -607,27 +609,27 @@ ChicagoStyleCheesePizza is making..
## 3. 抽象工厂模式 ## 3. 抽象工厂模式
**1. 设计原则** **1. 模式定义**
**依赖倒置原则** :要依赖抽象,不要依赖具体类。听起来像是针对接口编程,不针对实现编程,但是这个原则说明了:不能让高层组件依赖底层组件,而且,不管高层或底层组件,两者都应该依赖于抽象。例如,下图中 PizzaStore 属于高层组件,它依赖的是 Pizza 的抽象类,这样就可以不用关心 Pizza 的具体实现细节 提供一个接口,用于创建 **相关对象家族** ,而不需要明确指定具体类
<div align="center"> <img src="../pics//ddf72ca9-c0be-49d7-ab81-57a99a974c8e.jpg"/> </div><br> **2. 模式类图**
**2. 模式定义** 抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂模式只是用于创建一个对象,这和抽象工厂模式有很大不同。
提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类 抽象工厂模式用到了工厂模式来创建单一对象在类图左部AbstractFactory 中的 CreateProductA 和 CreateProductB 方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂模式的定义
**3. 模式类图** 至于创建对象的家族这一概念是在 Client 体现Client 要通过 AbstractFactory 同时调用两个方法来创建出两个对象在这里这两个对象就有很大的相关性Client 需要这两个对象的协作才能完成任务。
抽象工厂模式创建的是对象家族也就是很多对象而不是一个对象并且这些对象是相关的也就是说必须一起创建出来。而工厂模式只是用于创建一个对象这和抽象工厂模式有很大不同。并且抽象工厂模式也用到了工厂模式来创建单一对象在类图左部AbstractFactory 中的 CreateProductA 和 CreateProductB 方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂模式的定义。至于创建对象的家族这一概念是在 Client 体现Client 要通过 AbstractFactory 同时调用两个方法来创建出两个对象在这里这两个对象就有很大的相关性Client 需要这两个对象的协作才能完成任务。从高层次来看,抽象工厂使用了组合,即 Cilent 组合了 AbstractFactory ,而工厂模式使用了继承。 从高层次来看,抽象工厂使用了组合,即 Cilent 组合了 AbstractFactory ,而工厂模式使用了继承。
<div align="center"> <img src="../pics//d301774f-e0d2-41f3-95f4-bfe39859b52e.jpg"/> </div><br> <div align="center"> <img src="../pics//0de18cdb-e974-47a3-af47-9538edafe857.png"/> </div><br>
**4. 解决方案类图** **3. 解决方案类图**
<div align="center"> <img src="../pics//8785dabd-1285-4bd0-b3aa-b05cc060a24a.jpg"/> </div><br> <div align="center"> <img src="../pics//967b2f5a-6ade-4ceb-bb41-493483fd3dff.png"/> </div><br>
**5. 代码实现** **4. 代码实现**
```java ```java
public interface Dough { public interface Dough {
@ -736,7 +738,7 @@ ThickCrustDough
MarinaraSauce MarinaraSauce
``` ```
# 第五章 单件模式 # 单件模式
**1. 模式定义** **1. 模式定义**
@ -796,8 +798,7 @@ private static Singleton uniqueInstance = new Singleton();
因为 uniqueInstance 只需要被初始化一次,之后就可以直接使用了。加锁操作只需要对初始化那部分的代码进行,也就是说,只有当 uniqueInstance 没有被初始化时,才需要进行加锁。 因为 uniqueInstance 只需要被初始化一次,之后就可以直接使用了。加锁操作只需要对初始化那部分的代码进行,也就是说,只有当 uniqueInstance 没有被初始化时,才需要进行加锁。
双重校验锁先判断 uniqueInstance 是否已经被初始化了,如果没有被初始化,那么才对初始化的语句进行加锁。如果只做一次判断,那么多个线程还是有可能同时进入实例化语句块的,因此需要仅此第二次的判断。 双重校验锁先判断 uniqueInstance 是否已经被初始化了,如果没有被初始化,那么才对初始化的语句进行加锁。
```java ```java
public class Singleton { public class Singleton {
@ -820,7 +821,17 @@ public class Singleton {
} }
``` ```
# 第六章 命令模式 考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程同时执行 if 语句,那么两个线程就会同时进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 uniqueInstance = new Singleton(); 这条语句,只是早晚的问题,也就是说会进行两次实例化,从而产生了两个实例。
```java
if (uniqueInstance == null) {
synchronized (Singleton.class) {
uniqueInstance = new Singleton();
}
}
```
# 命令模式
**1. 问题描述** **1. 问题描述**
@ -828,9 +839,9 @@ public class Singleton {
有非常多的家电,并且之后会增加家电。 有非常多的家电,并且之后会增加家电。
<div align="center"> <img src="../pics//7b8f0d8e-a4fa-4c9d-b9a0-3e6a11cb3e33.jpg"/> </div><br> <div align="center"> <img src="../pics//f6be22cb-d64f-4ee5-87b7-cbc4e6255c0e.jpg"/> </div><br>
<div align="center"> <img src="../pics//c3ca36b2-8459-4cf1-98b0-cc95a0e94f20.jpg"/> </div><br> <div align="center"> <img src="../pics//5b832bde-d05e-42db-b648-42e274571ad9.jpg"/> </div><br>
**2. 模式定义** **2. 模式定义**
@ -846,11 +857,11 @@ public class Singleton {
- RemoteLoader 是客户端,注意它与 RemoteControl 的区别。因为 RemoteControl 不能主动地调用自身的方法,因此也就不能当成是客户端。客户端好比人,只有人才能去真正去使用遥控器。 - RemoteLoader 是客户端,注意它与 RemoteControl 的区别。因为 RemoteControl 不能主动地调用自身的方法,因此也就不能当成是客户端。客户端好比人,只有人才能去真正去使用遥控器。
<div align="center"> <img src="../pics//5ef94f62-98ce-464d-a646-842d9c72c8b8.jpg"/> </div><br> <div align="center"> <img src="../pics//b7b1f5c6-ff8a-4353-8060-44bbc4b9e02e.jpg"/> </div><br>
**4. 模式类图** **4. 模式类图**
<div align="center"> <img src="../pics//1e09d75f-6268-4425-acf8-8ecd1b4a0ef3.jpg"/> </div><br> <div align="center"> <img src="../pics//26ccd069-55ec-4a28-aeb3-025e39e5810f.jpg"/> </div><br>
**5. 代码实现** **5. 代码实现**
@ -931,15 +942,13 @@ public class RemoteLoader {
Light is on! Light is on!
``` ```
# 第七章 适配器模式与外观模式 # 适配器模式
## 1. 适配器模式
**1. 模式定义** **1. 模式定义**
将一个类的接口,转换为客户期望的另一个接口。适配器让原本不兼容的类可以合作无间。 将一个类的接口,转换为客户期望的另一个接口。适配器让原本不兼容的类可以合作无间。
<div align="center"> <img src="../pics//8e8ba824-7a9e-4934-a212-e6a41dcc1602.jpg"/> </div><br> <div align="center"> <img src="../pics//c484b07d-be3d-4699-9e28-f035de8a274c.jpg"/> </div><br>
**2. 模式类图** **2. 模式类图**
@ -953,11 +962,11 @@ Light is on!
鸭子Duck和火鸡Turkey拥有不同的叫声Duck 调用的是 quack() 方法,而 Turkey 调用 gobble() 方法。 鸭子Duck和火鸡Turkey拥有不同的叫声Duck 调用的是 quack() 方法,而 Turkey 调用 gobble() 方法。
要求将 Turkey 的 gobble() 方法适配成 Duck 的 quack() 方法。 要求将 Turkey 的 gobble() 方法适配成 Duck 的 quack() 方法,从而让火鸡冒充鸭子
**4. 解决方案类图** **4. 解决方案类图**
<div align="center"> <img src="../pics//1a511c76-bb6b-40ab-b8aa-39eeb619d673.jpg"/> </div><br> <div align="center"> <img src="../pics//b8ceb9db-180e-4d01-932c-593fa2a6f515.jpg"/> </div><br>
**5. 代码实现** **5. 代码实现**
@ -1013,7 +1022,11 @@ public class DuckTestDrive {
gobble! gobble!
``` ```
## 2. 外观模式 **6. Enumration 适配成 Iterator**
<div align="center"> <img src="../pics//aa340e1a-f366-436b-a5a5-29a90425c10d.png"/> </div><br>
# 外观模式
**1. 模式定义** **1. 模式定义**
@ -1031,7 +1044,7 @@ gobble!
**4. 解决方案类图** **4. 解决方案类图**
<div align="center"> <img src="../pics//25387681-89f8-4365-a2fa-83b86449ee84.jpg"/> </div><br> <div align="center"> <img src="../pics//a0339a9f-f44f-4e37-a37f-169bc735536d.jpg"/> </div><br>
**5. 设计原则** **5. 设计原则**
@ -1041,7 +1054,7 @@ gobble!
过于简单,无实现。 过于简单,无实现。
# 第八章 模板方法模式 # 模板方法模式
**1. 模式定义** **1. 模式定义**
@ -1053,7 +1066,7 @@ gobble!
模板方法 templateMethod() 定义了算法的骨架,确定了 primitiveOperation1() 和 primitiveOperation2() 方法执行的顺序,而 primitiveOperation1() 和 primitiveOperation2() 让子类去实现。 模板方法 templateMethod() 定义了算法的骨架,确定了 primitiveOperation1() 和 primitiveOperation2() 方法执行的顺序,而 primitiveOperation1() 和 primitiveOperation2() 让子类去实现。
<div align="center"> <img src="../pics//ed62f400-192c-4185-899b-187958201f0c.jpg"/> </div><br> <div align="center"> <img src="../pics//87ffaf7f-4aa5-4da0-af84-994de62fa440.jpg"/> </div><br>
**3. 问题描述** **3. 问题描述**
@ -1073,7 +1086,7 @@ gobble!
**6. 钩子** **6. 钩子**
钩子hock某些步骤在不同实现中可有可无,可以先定义一个什么都不做的方法,把它加到模板方法中,如果子类需要它就覆盖默认实现并加上自己的实现。 某些步骤在不同实现中可有可无,可以先定义一个什么都不做的方法,把它加到模板方法中,如果子类需要它就覆盖默认实现并加上自己的实现。
**7. 代码实现** **7. 代码实现**
@ -1155,9 +1168,7 @@ pourInCup
Tea.addCondiments Tea.addCondiments
``` ```
# 第九章 迭代器和组合模式 # 迭代器模式
## 1. 迭代器模式
**1. 模式定义** **1. 模式定义**
@ -1247,9 +1258,7 @@ public class Client {
9 9
``` ```
## 2. Java 内置的迭代器 **4. Java 内置的迭代器**
**1. 实现接口**
在使用 Java 的迭代器实现时,需要让聚合对象去实现 Iterable 接口,该接口有一个 iterator() 方法会返回一个 Iterator 对象。 在使用 Java 的迭代器实现时,需要让聚合对象去实现 Iterable 接口,该接口有一个 iterator() 方法会返回一个 Iterator 对象。
@ -1257,8 +1266,6 @@ public class Client {
Java 中的集合类基本都实现了 Iterable 接口。 Java 中的集合类基本都实现了 Iterable 接口。
**2. 代码实现**
```java ```java
import java.util.Iterator; import java.util.Iterator;
@ -1313,7 +1320,7 @@ public class Client {
} }
``` ```
## 3. 组合模式 # 组合模式
**1. 设计原则** **1. 设计原则**
@ -1331,7 +1338,7 @@ public class Client {
组合类拥有一个组件对象,因此组合类的操作可以委托给组件对象去处理,而组件对象可以是另一个组合类或者叶子类。 组合类拥有一个组件对象,因此组合类的操作可以委托给组件对象去处理,而组件对象可以是另一个组合类或者叶子类。
<div align="center"> <img src="../pics//f99c019e-7e91-4c2e-b94d-b031c402dcb5.jpg"/> </div><br> <div align="center"> <img src="../pics//cf08a51d-14c0-4bfc-863b-c8672d9c2b02.jpg"/> </div><br>
**4. 代码实现** **4. 代码实现**
@ -1434,7 +1441,7 @@ Composite:root
--left:3 --left:3
``` ```
# 第十章 状态模式 # 状态模式
**1. 模式定义** **1. 模式定义**
@ -1452,9 +1459,9 @@ Context 的 request() 方法委托给 State 对象去处理。当 Context 组合
但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。 但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。
所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,主要必须要是在运行过程中。 所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。
状态模式主要是用来解决状态转移的问题,当状态发生庄毅了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 需要使用哪个算法。 状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 需要使用哪个算法。
**4. 问题描述** **4. 问题描述**
@ -1474,17 +1481,17 @@ Context 的 request() 方法委托给 State 对象去处理。当 Context 组合
糖果销售机即 Context。 糖果销售机即 Context。
下面的实现中每个 State 都组合了 Context 对象,这是因为状态转移的操作在 State 对象中,而状态转移过程又必须改变 Context 对象的 state 对象,因此 State 必须拥有 Context 对象。 下面的实现中每个 State 都组合了 Context 对象,这是因为状态转移的操作在 State 对象中,而状态转移过程又必须改变 Context 对象的 state 对象,因此 State 必须组合 Context 对象。
```java ```java
public interface State { public interface State {
/** /**
* 投入25 分钱 * 投入 25 分钱
*/ */
void insertQuarter(); void insertQuarter();
/** /**
* 退回25 分钱 * 退回 25 分钱
*/ */
void ejectQuarter(); void ejectQuarter();
@ -1501,6 +1508,7 @@ public interface State {
``` ```
```java ```java
public class HasQuarterState implements State{ public class HasQuarterState implements State{
private GumballMachine gumballMachine; private GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine){ public HasQuarterState(GumballMachine gumballMachine){
@ -1532,6 +1540,7 @@ public class HasQuarterState implements State{
``` ```
```java ```java
public class NoQuarterState implements State { public class NoQuarterState implements State {
GumballMachine gumballMachine; GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) { public NoQuarterState(GumballMachine gumballMachine) {
@ -1592,6 +1601,7 @@ public class SoldOutState implements State {
``` ```
```java ```java
public class SoldState implements State { public class SoldState implements State {
GumballMachine gumballMachine; GumballMachine gumballMachine;
public SoldState(GumballMachine gumballMachine) { public SoldState(GumballMachine gumballMachine) {
@ -1627,6 +1637,7 @@ public class SoldState implements State {
``` ```
```java ```java
public class GumballMachine { public class GumballMachine {
private State soldOutState; private State soldOutState;
private State noQuarterState; private State noQuarterState;
private State hasQuarterState; private State hasQuarterState;
@ -1696,6 +1707,7 @@ public class GumballMachine {
``` ```
```java ```java
public class GumballMachineTestDrive { public class GumballMachineTestDrive {
public static void main(String[] args) { public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine(5); GumballMachine gumballMachine = new GumballMachine(5);
@ -1751,9 +1763,9 @@ You turned, but there are no gumballs
No gumball dispensed No gumball dispensed
``` ```
# 第十一章 代理模式 # 代理模式
# 第十二章 复合模式 # 复合模式
## MVC ## MVC
@ -1769,16 +1781,16 @@ No gumball dispensed
<div align="center"> <img src="../pics//1dd56e61-2970-4d27-97c2-6e81cee86978.jpg"/> </div><br> <div align="center"> <img src="../pics//1dd56e61-2970-4d27-97c2-6e81cee86978.jpg"/> </div><br>
# 第十三章 与设计模式相处 # 与设计模式相处
定义:在某 **情境** 下,针对某 **问题** 的某种 **解决方案** 定义:在某 **情境** 下,针对某 **问题** 的某种 **解决方案**
过度使用设计模式可能导致代码被过度工程化,应该总是用最简单的解决方案完成工作,并在真正需要模式的地方才使用它。 过度使用设计模式可能导致代码被过度工程化,应该总是用最简单的解决方案完成工作,并在真正需要模式的地方才使用它。
反模式:不好的解决方案来解决一个问题。主要作用是为了警告不要使用这些解决方案。 反模式:不好的解决方案来解决一个问题。主要作用是为了警告人们不要使用这些解决方案。
模式分类: 模式分类:
<div align="center"> <img src="../pics//524a237c-ffd7-426f-99c2-929a6bf4c847.jpg"/> </div><br> <div align="center"> <img src="../pics//524a237c-ffd7-426f-99c2-929a6bf4c847.jpg"/> </div><br>
# 第十四章 剩下的模式 # 剩下的模式

View File

@ -24,37 +24,60 @@
# S.O.L.I.D # S.O.L.I.D
S.O.L.I.D 是面向对象设计和编程 (OOD&OOP) 中几个重要编码原则 (Programming Priciple) 的首字母缩写。 > [Design Principles](http://www.oodesign.com/design-principles.html)
| 简写 | 全拼 | 中文翻译 | 设计原则可以帮助我们避免那些糟糕的设计,这些原则被归纳在《敏捷软件开发:原则、模式与实践》
| 简写 | 全拼 | 中文翻译 |
| -- | -- | -- | | -- | -- | -- |
|SRP| The Single Responsibility Principle | 单一责任原则 | | SRP | The Single Responsibility Principle | 单一责任原则 |
|OCP| The Open Closed Principle | 开放封闭原则 | | OCP | The Open Closed Principle | 开放封闭原则 |
|LSP| The Liskov Substitution Principle | 里氏替换原则 | | LSP | The Liskov Substitution Principle | 里氏替换原则 |
|ISP| The Interface Segregation Principle | 接口分离原则 | | ISP | The Interface Segregation Principle | 接口分离原则 |
|DIP| The Dependency Inversion Principle | 依赖倒置原则 | | DIP | The Dependency Inversion Principle | 依赖倒置原则 |
## 1. 单一责任原则 ## 1. 单一责任原则
当需要修改某个类的时候原因有且只有一个。换句话说就是让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类。 **修改一个类的原因应该只有一个。**
换句话说就是让一个类只负责一件事,当这个类需要做过多事情的时候,就需要分解这个类。
如果一个类承担的职责过多,就等于把这些职责耦合在了一起,一个职责的变化可能会削弱这个类完成其它职责的能力。
## 2. 开放封闭原则 ## 2. 开放封闭原则
软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。 **类应该对扩展开放,对修改关闭。**
扩展就是添加新功能的意思,因此该原则要求在添加新功能时不需要修改代码。
符合开闭原则最典型的设计模式是装饰者模式,它可以动态地将责任附加到对象上,而不用去修改类的代码。
## 3. 里氏替换原则 ## 3. 里氏替换原则
当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有 is-a 关系。 **子类实例应该能够替换掉所有父类实例。**
继承是一种 IS-A 关系,子类需要能够当成父类来使用,并且需要比父类更特殊。
如果不满足这个原则,那么各个子类的行为上就会有很大差异,增加继承体系的复杂度。
## 4. 接口分离原则 ## 4. 接口分离原则
不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。 **不应该强迫客户依赖于它们不用的方法。**
因此使用多个专门的接口比使用单一的总接口总要好。
## 5. 依赖倒置原则 ## 5. 依赖倒置原则
1. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象 - **高层模块不应该依赖于低层模块,二者都应该依赖于抽象**
2. 抽象不应该依赖于细节,细节应该依赖于抽象 - **抽象不应该依赖于细节,细节应该依赖于抽象**
高层模块包含一个应用程序中重要的策略选择和业务模块,如果高层模块依赖于底层模块,那么底层模块的改动就会直接影响到高层模块,从而迫使高层模块也需要改动。
依赖于抽象意味着:
- 任何变量都不应该持有一个指向具体类的指针或者引用;
- 任何类都不应该从具体类派生;
- 任何方法都不应该覆写它的任何基类中的已经实现的方法。
# 其他常见原则 # 其他常见原则
@ -62,9 +85,9 @@ S.O.L.I.D 是面向对象设计和编程 (OOD&OOP) 中几个重要编码原则 (
| 简写 | 全拼 | 中文翻译 | | 简写 | 全拼 | 中文翻译 |
| -- | -- | -- | | -- | -- | -- |
|LoD| The Law of Demeter | 迪米特法则 | |LoD| The Law of Demeter | 迪米特法则 |
|CRP| The Composite Reuse Principle | 合成复用原则 | |CRP| The Composite Reuse Principle | 合成复用原则 |
|CCP| The Common Closure Principle | 共同封闭原则 | |CCP| The Common Closure Principle | 共同封闭原则 |
|SAP| The Stable Abstractions Principle | 稳定抽象原则 | |SAP| The Stable Abstractions Principle | 稳定抽象原则 |
|SDP| The Stable Dependencies Principle | 稳定依赖原则 | |SDP| The Stable Dependencies Principle | 稳定依赖原则 |

View File

@ -1,6 +1,3 @@
# 科普
<a href="https://pan.baidu.com/s/1fKo7ntvQUettvjaTQqyCEw"> <img src="s1113106.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1dNFZcBdDhA80-pWT1qcQSg"> <img src="s9114855.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1s0vhcWxN_36PpZeJoOHrKA"> <img src="s4669554.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1fII84UPuo8aIxDkOakvUVg"> <img src="s4379914.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1YTp7vDp0VG2Ckz5_I91FIg"> <img src="s4687321.jpg" width="130"/> </a>
# 网络 # 网络
@ -40,8 +37,9 @@
# 编码实践 # 编码实践
<a href="https://pan.baidu.com/s/1H1ilY54BISk7oDaKYpcrwA"> <img src="s1495029.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1pWGwRRVxtpSmlsK7B1uU7Q"> <img src="s4157180.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1HGHeahqtscz7iczhK7ps-Q"> <img src="s1671095.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/14uxNIdeXKLOnUJ6LMRndPg"> <img src="s10328621.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/17sIRZxCf_uJMZNnqAHEDkA"> <img src="s11194203.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1MaNeNsoqlTMn2uuT1QrsHQ"> <img src="s1086045.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1s0vhcWxN_36PpZeJoOHrKA"> <img src="s4669554.jpg" width="130"/> </a> <a href="https://pan.baidu.com/s/1H1ilY54BISk7oDaKYpcrwA"> <img src="s1495029.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1pWGwRRVxtpSmlsK7B1uU7Q"> <img src="s4157180.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1HGHeahqtscz7iczhK7ps-Q"> <img src="s1671095.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/14uxNIdeXKLOnUJ6LMRndPg"> <img src="s10328621.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/17sIRZxCf_uJMZNnqAHEDkA"> <img src="s11194203.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1MaNeNsoqlTMn2uuT1QrsHQ"> <img src="s1086045.jpg" width="130"/> </a> &nbsp;&nbsp;
# 打包 # 科普
<a href="https://pan.baidu.com/s/1fKo7ntvQUettvjaTQqyCEw"> <img src="s1113106.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1dNFZcBdDhA80-pWT1qcQSg"> <img src="s9114855.jpg" width="130"/> </a> &nbsp;&nbsp; &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1fII84UPuo8aIxDkOakvUVg"> <img src="s4379914.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1XarJowXrxoBtKdmVCGcm1w"> <img src="s4687321.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1s0vhcWxN_36PpZeJoOHrKA"> <img src="s4669554.jpg" width="130"/> </a>
> [百度网盘](https://pan.baidu.com/s/1o9oD1s2#list/path=%2F&parentPath=%2F)

76
other/download2.md Normal file
View File

@ -0,0 +1,76 @@
# 关于
计算机经典书籍 PDF 下载
# 网络
- [计算机网络.pdf](https://pan.baidu.com/s/1EXaJbNckzuQMOCyamzjL_Q)
- [TCP/IP详解.pdf](https://pan.baidu.com/s/1oBbA9LOevcJ_reg8y5kOvw)
- [图解 HTTP.pdf](https://pan.baidu.com/s/1M0AHXqG9sP9Bxne6u0JK8A)
- [图解 TCP/IP.pdf](https://pan.baidu.com/s/1y0P-VFlWKdOPW7YB60OWlw)
# 操作系统
- [计算机操作系统.pdf](https://pan.baidu.com/s/1C-MgvslLKd1buwmebti6Qg)
- [鸟哥的 Linux 私房菜](https://pan.baidu.com/s/1Qm2G4rghPorQeH5J9fDHTg)
- [深入理解计算机系统.pdf](https://pan.baidu.com/s/1OoyVI90fK1Q9eixzH9jnpQ)
- [现代操作系统.pdf](https://pan.baidu.com/s/12mTkrpLsb7tz11cGn_KZ4w)
# 算法
- [算法.pdf](https://pan.baidu.com/s/1Va1R66d13ynmita8nfkRPg)
- [剑指 Offer.pdf](https://pan.baidu.com/s/1HmGwXvTcHDrQnUAL1wWE3g)
- [编程之美.pdf](https://pan.baidu.com/s/1SZGUbvKpKOomM-iYxe_GGw)
- [程序员代码面试指南.pdf](https://pan.baidu.com/s/10EoXyW33MnYJUX5YeD5pPg)
# 设计模式
- [Head First 设计模式.pdf](https://pan.baidu.com/s/1JOO4M3c6EGB5xHz_-aGtDQ)
- [设计模式 可复用面试对象软件的基础.pdf](https://pan.baidu.com/s/1n41aEgGuRg9hQ-9iwOxc5A)
# 数据库
- [数据库系统概论.pdf](https://pan.baidu.com/s/1xhYsZUi2fugLf9jxSWA0pQ)
- [高性能 MySQL.pdf](https://pan.baidu.com/s/1aXRWznphuiEc4XRXpM1qLA)
- [MySQL 必知必会.pdf](https://pan.baidu.com/s/182JK19-rvbISYAv4aLk7xg)
# Redis
- [Redis 设计与实现.pdf](https://pan.baidu.com/s/1XovYaApdsVsd97pLCwAvpA)
- [Reids 实战.pdf](https://pan.baidu.com/s/1bfbiPjoBEaNUs6qLWVEIJw)
# Java
- [Java 编程思想.pdf](https://pan.baidu.com/s/1iNBkY9ANUcmeSp4VjBGhRQ)
- [深入理解 Java 虚拟机.pdf](https://pan.baidu.com/s/1zdATX8Qs-RMk6DN7iqECYw)
- [Java 并发编程实战.pdf](https://pan.baidu.com/s/1LkPVPrT_3BYFkfxieBkeVw)
# C++
- [C++ Promer 第五版.pdf](https://pan.baidu.com/s/1VhhqN7oVcrv0KhF32CXRLQ)
- [C 和指针.pdf](https://pan.baidu.com/s/1u3-QrdnkHo5ScUK84v7C5w)
- [Unix 环境高级编程.pdf](https://pan.baidu.com/s/1K6xm3YlV53trCxyGR0j_gQ)
- [Unix 网络编程.pdf](https://pan.baidu.com/s/10iFqDOHSveJC3VC7dl1vMw)
- [Effective C++.pdf](https://pan.baidu.com/s/1o-hgLJ4XvXAHeFhWAuuiFQ)
# 工具
- [Pro Git.pdf](https://pan.baidu.com/s/1zYoS3lB1yCCT-So1YeoRuA)
- [正则表达式必知必会.pdf](https://pan.baidu.com/s/1ybA1qvjx4p844Pd8zDlx7Q)
# 编码实践
- [代码大全.pdf](https://pan.baidu.com/s/1H1ilY54BISk7oDaKYpcrwA)
- [重构.pdf](https://pan.baidu.com/s/1pWGwRRVxtpSmlsK7B1uU7Q)
- [敏捷软件开发.pdf](https://pan.baidu.com/s/1HGHeahqtscz7iczhK7ps-Q)
- [编写可读代码的艺术.pdf](https://pan.baidu.com/s/14uxNIdeXKLOnUJ6LMRndPg)
- [程序员的职业素养.pdf](https://pan.baidu.com/s/1MaNeNsoqlTMn2uuT1QrsHQ)
- [人月神话.pdf](https://pan.baidu.com/s/17sIRZxCf_uJMZNnqAHEDkA)
- [黑客与画家.pdf](https://pan.baidu.com/s/1s0vhcWxN_36PpZeJoOHrKA)
# 科普
- [计算机程序的构造与解释.pdf](https://pan.baidu.com/s/1fKo7ntvQUettvjaTQqyCEw)
- [数学之美.pdf](https://pan.baidu.com/s/1dNFZcBdDhA80-pWT1qcQSg)
- [编码.pdf](https://pan.baidu.com/s/1fII84UPuo8aIxDkOakvUVg)
- [编程珠玑.pdf](https://pan.baidu.com/s/1XarJowXrxoBtKdmVCGcm1w)

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB