diff --git a/README.md b/README.md
index 28755cad..a1b6f399 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,7 @@
整理自《数据库系统概论 第四版》
-> [SQL 语法](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/SQL%20语法.md)
+> [SQL](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/SQL.md)
整理自《SQL 必知必会》
@@ -67,6 +67,10 @@
整理自《高性能 MySQL》,整理了一些重点内容。
+> [Redis](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Redis.md)
+
+整理自《Redis 设计与实现》和《Redis 实战》
+
## Java :coffee:
> [JVM](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/JVM.md)
diff --git a/notes/Git.md b/notes/Git.md
index 3418d95e..a14b8e9c 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 bb0a2577..1ae9578e 100644
--- a/notes/HTTP.md
+++ b/notes/HTTP.md
@@ -8,10 +8,11 @@
* [POST:传输实体主体](#post传输实体主体)
* [HEAD:获取报文首部](#head获取报文首部)
* [PUT:上传文件](#put上传文件)
+ * [PATCH:对资源进行部分修改](#patch对资源进行部分修改)
* [DELETE:删除文件](#delete删除文件)
* [OPTIONS:查询支持的方法](#options查询支持的方法)
- * [TRACE:追踪路径](#trace追踪路径)
* [CONNECT:要求用隧道协议连接代理](#connect要求用隧道协议连接代理)
+ * [TRACE:追踪路径](#trace追踪路径)
* [HTTP 状态码](#http-状态码)
* [2XX 成功](#2xx-成功)
* [3XX 重定向](#3xx-重定向)
@@ -40,6 +41,7 @@
* [各版本比较](#各版本比较)
* [HTTP/1.0 与 HTTP/1.1 的区别](#http10-与-http11-的区别)
* [HTTP/1.1 与 HTTP/2.0 的区别](#http11-与-http20-的区别)
+* [参考资料](#参考资料)
@@ -59,40 +61,41 @@
URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。
-
+
## 请求和响应报文
**请求报文**
-
+
**响应报文**
-
+
# HTTP 方法
-客户端发送的请求报文第一行为请求行,包含了方法字段。
+客户端发送的 **请求报文** 第一行为请求行,包含了方法字段。
## GET:获取资源
## POST:传输实体主体
-POST 主要目的不是获取资源,而是传输实体主体数据。
+POST 主要目的不是获取资源,而是传输存储在内容实体中的数据。
-GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在实体主体部分。
+GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在内容实体。
```
GET /test/demo_form.asp?name1=value1&name2=value2 HTTP/1.1
```
+
```
POST /test/demo_form.asp HTTP/1.1
Host: w3schools.com
name1=value1&name2=value2
```
-GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 URL 是可见的,可能会泄露私密信息。并且 GET 只支持 ASCII 字符,如果参数为中文则可能会出现乱码,而 POST 支持标准字符集。
+GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 URL 中是可见的,可能会泄露私密信息。并且 GET 只支持 ASCII 字符,如果参数为中文则可能会出现乱码,而 POST 支持标准字符集。
## HEAD:获取报文首部
@@ -102,37 +105,68 @@ GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 U
## PUT:上传文件
-由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般 WEB 网站不使用该方法。
+由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般不使用该方法。
+
+```html
+PUT /new.html HTTP/1.1
+Host: example.com
+Content-type: text/html
+Content-length: 16
+
+New File
+```
+
+## PATCH:对资源进行部分修改
+
+PUT 也可以用于修改资源,但是只能完全替代原始资源,PATCH 允许部分修改。
+
+```html
+PATCH /file.txt HTTP/1.1
+Host: www.example.com
+Content-Type: application/example
+If-Match: "e0023aa4e"
+Content-Length: 100
+
+[description of changes]
+```
## DELETE:删除文件
与 PUT 功能相反,并且同样不带验证机制。
+```html
+DELETE /file.html HTTP/1.1
+```
+
## OPTIONS:查询支持的方法
查询指定的 URL 能够支持的方法。
会返回 Allow: GET, POST, HEAD, OPTIONS 这样的内容。
+## CONNECT:要求用隧道协议连接代理
+
+要求在于代理服务器通信时建立隧道,使用 SSL(Secure Sokets Layer,安全套接字)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。
+
+```html
+CONNECT www.example.com:443 HTTP/1.1
+```
+
+
+
## TRACE:追踪路径
服务器会将通信路径返回给客户端。
发送请求时,在 Max-Forwards 首部字段中填入数值,每经过一个服务器就会减 1,当数值为 0 时就停止传输。
-TRACE 一般不会使用,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪),因此更不会去使用它。
+通常不会使用 TRACE,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪),因此更不会去使用它。
-
-
-## CONNECT:要求用隧道协议连接代理
-
-主要使用 SSL(Secure Sokets Layer,安全套接字)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。
-
-
+
# HTTP 状态码
-服务器返回的响应报文中第一行为状态行,包含了状态码以及原因短语,来告知客户端请求的结果。
+服务器返回的 **响应报文** 中第一行为状态行,包含了状态码以及原因短语,用来告知客户端请求的结果。
| 状态码 | 类别 | 原因短语 |
| --- | --- | --- |
@@ -148,7 +182,7 @@ TRACE 一般不会使用,并且它容易受到 XST 攻击(Cross-Site Tracing
- **204 No Content** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。
-- **206 Partial Content**
+- **206 Partial Content** :表示客户端进行了范围请求。响应报文包含由 Content-Range 指定范围的实体内容。
## 3XX 重定向
@@ -156,7 +190,7 @@ TRACE 一般不会使用,并且它容易受到 XST 攻击(Cross-Site Tracing
- **302 Found** :临时性重定向
-- **303 See Other**
+- **303 See Other** :和 302 有着相同的功能,但是 303 明确要求客户端应该采用 GET 方法获取资源。
- 注:虽然 HTTP 协议规定 301、302 状态下重定向时不允许把 POST 方法改成 GET 方法,但是大多数浏览器都会 在 301、302 和 303 状态下的重定向把 POST 方法改成 GET 方法。
@@ -166,11 +200,11 @@ TRACE 一般不会使用,并且它容易受到 XST 攻击(Cross-Site Tracing
## 4XX 客户端错误
-- **400 Bad Request** :请求报文中存在语法错误
+- **400 Bad Request** :请求报文中存在语法错误。
-- **401 Unauthorized** :该状态码表示发送的请求需要有通过 HTTP 认证(BASIC 认证、DIGEST 认证)的认证信息。如果之前已进行过一次请求,则表示用户认证失败。
+- **401 Unauthorized** :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。
-
+
- **403 Forbidden** :请求被拒绝,服务器端没有必要给出拒绝的详细理由。
@@ -178,9 +212,9 @@ TRACE 一般不会使用,并且它容易受到 XST 攻击(Cross-Site Tracing
## 5XX 服务器错误
-- **500 Internal Server Error** :服务器正在执行请求时发生错误
+- **500 Internal Server Error** :服务器正在执行请求时发生错误。
-- **503 Service Unavilable** :该状态码表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。
+- **503 Service Unavilable** :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。
# HTTP 首部
@@ -193,7 +227,7 @@ TRACE 一般不会使用,并且它容易受到 XST 攻击(Cross-Site Tracing
| 首部字段名 | 说明 |
| -- | -- |
| Cache-Control | 控制缓存的行为 |
-| Connection | 逐跳首部、 连接的管理 |
+| Connection | 控制不再转发给代理的首部字段;管理持久连接|
| Date | 创建报文的日期时间 |
| Pragma | 报文指令 |
| Trailer | 报文末端的首部一览 |
@@ -247,7 +281,7 @@ TRACE 一般不会使用,并且它容易受到 XST 攻击(Cross-Site Tracing
| Allow | 资源可支持的 HTTP 方法 |
| Content-Encoding | 实体主体适用的编码方式 |
| Content-Language | 实体主体的自然语言 |
-| Content-Length | 实体主体的大小(单位: 字节) |
+| Content-Length | 实体主体的大小(单位:字节) |
| Content-Location | 替代对应资源的 URI |
| Content-MD5 | 实体主体的报文摘要 |
| Content-Range | 实体主体的位置范围 |
@@ -261,18 +295,37 @@ TRACE 一般不会使用,并且它容易受到 XST 攻击(Cross-Site Tracing
HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。
-服务器发送的响应报文包含 Set-Cookie 字段,客户端得到响应报文后把 Cookie 内容保存到浏览器中。下次再发送请求时,从浏览器中读出 Cookie 值,在请求报文中包含 Cookie 字段,这样服务器就知道客户端的状态信息了。Cookie 状态信息保存在客户端浏览器中,而不是服务器上。
+Cookie 是服务器发送给客户端的数据,该数据会被保存在浏览器中,并且在下一次发送请求时包含该数据。通过 Cookie 可以让服务器知道两个请求是否来自于同一个客户端,从而实现保持登录状态等功能。
-
+**创建过程**
-Set-Cookie 字段有以下属性:
+服务器发送的响应报文包含 Set-Cookie 字段,客户端得到响应报文后把 Cookie 内容保存到浏览器中。
+
+```html
+HTTP/1.0 200 OK
+Content-type: text/html
+Set-Cookie: yummy_cookie=choco
+Set-Cookie: tasty_cookie=strawberry
+
+[page content]
+```
+
+客户端之后发送请求时,会从浏览器中读出 Cookie 值,在请求报文中包含 Cookie 字段。
+
+```html
+GET /sample_page.html HTTP/1.1
+Host: www.example.org
+Cookie: yummy_cookie=choco; tasty_cookie=strawberry
+```
+
+**Set-Cookie**
| 属性 | 说明 |
| -- | -- |
| NAME=VALUE | 赋予 Cookie 的名称和其值(必需项) |
| expires=DATE | Cookie 的有效期(若不明确指定则默认为浏览器关闭前为止) |
| path=PATH | 将服务器上的文件目录作为 Cookie 的适用对象(若不指定则默认为文档所在的文件目录) |
-| domain= 域名 | 作为 Cookie 适用对象的域名(若不指定则默认为创建 Cookie 的服务器的域名) |
+| domain=域名 | 作为 Cookie 适用对象的域名(若不指定则默认为创建 Cookie 的服务器的域名) |
| Secure | 仅在 HTTPS 安全通信时才会发送 Cookie |
| HttpOnly | 加以限制,使 Cookie 不能被 JavaScript 脚本访问 |
@@ -286,27 +339,59 @@ Session 是服务器用来跟踪用户的一种手段,每个 Session 都有一
**使用 Cookie 实现用户名和密码的自动填写**
-网站脚本会自动从 Cookie 中读取用户名和密码,从而实现自动填写。
+网站脚本会自动从保存在浏览器中的 Cookie 读取用户名和密码,从而实现自动填写。
## 缓存
-有两种缓存方法:让代理服务器进行缓存和让客户端浏览器进行缓存。
+**优点**
-Cache-Control 用于控制缓存的行为。Cache-Control: no-cache 有两种含义,如果是客户端向缓存服务器发送的请求报文中含有该指令,表示客户端不想要缓存的资源;如果是源服务器向缓存服务器发送的响应报文中含有该指令,表示缓存服务器不能对资源进行缓存。
+1. 降低服务器的负担;
+2. 提高响应速度(缓存资源比服务器上的资源离客户端更近)。
-Expires 字段可以用于告知缓存服务器该资源什么时候会过期。当首部字段 Cache-Control 有指定 max-age 指令时,比起首部字段 Expires,会优先处理 max-age 指令。
+**实现方法**
+
+1. 让代理服务器进行缓存;
+2. 让客户端浏览器进行缓存。
+
+**Cache-Control 字段**
+
+HTTP 通过 Cache-Control 首部字段来控制缓存。
+
+```html
+Cache-Control: private, max-age=0, no-cache
+```
+
+**no-cache 指令**
+
+该指令出现在请求报文的 Cache-Control 字段中,表示缓存服务器需要先向原服务器验证缓存资源是否过期;
+
+该指令出现在响应报文的 Cache-Control 字段中,表示缓存服务器在进行缓存之前需要先验证缓存资源的有效性。
+
+**no-store 指令**
+
+该指令表示缓存服务器不能对请求或响应的任何一部分进行缓存。
+
+no-cache 不表示不缓存,而是缓存之前需要先进行验证,no-store 才是不进行缓存。
+
+**max-age 指令**
+
+该指令出现在请求报文的 Cache-Control 字段中,如果缓存资源的缓存时间小于该指令指定的时间,那么就能接受该缓存。
+
+该指令出现在响应报文的 Cache-Control 字段中,表示缓存资源在缓存服务器中保存的时间。
+
+Expires 字段也可以用于告知缓存服务器该资源什么时候会过期。在 HTTP/1.1 中,会优先处理 Cache-Control : max-age 指令;而在 HTTP/1.0 中,Cache-Control : max-age 指令会被忽略掉。
## 持久连接
-当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源,如果每进行一次 HTTP 通信就要断开一次 TCP 连接,连接建立和断开的开销会很大。 **持久连接** 只需要进行一次 TCP 连接就能进行多次 HTTP 通信。HTTP/1.1 开始,所有的连接默认都是持久连接。
+当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源,如果每进行一次 HTTP 通信就要断开一次 TCP 连接,连接建立和断开的开销会很大。持久连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
-
+
-持久连接需要使用 Connection 首部字段进行管理。HTTP/1.1 开始 HTTP 默认是持久化连接的,如果要断开 TCP 连接,需要由客户端或者服务器端提出断开,使用 Connection: close;而在 HTTP/1.1 之前默认是非持久化连接的,如果要维持持续连接,需要使用 Keep-Alive。
+持久连接需要使用 Connection 首部字段进行管理。HTTP/1.1 开始 HTTP 默认是持久化连接的,如果要断开 TCP 连接,需要由客户端或者服务器端提出断开,使用 Connection : close;而在 HTTP/1.1 之前默认是非持久化连接的,如果要维持持续连接,需要使用 Connection : Keep-Alive。
-管线化方式可以同时发送多个请求和响应,而不需要发送一个请求然后等待响应之后再发下一个请求。
+**管线化方式** 可以同时发送多个请求和响应,而不需要发送一个请求然后等待响应之后再发下一个请求。
-
+
## 编码
@@ -318,11 +403,24 @@ Expires 字段可以用于告知缓存服务器该资源什么时候会过期。
## 多部分对象集合
-一份报文主体内可含有多种类型的实体同时发送,每个部分之间用 boundary 字段定义的分隔符进行分隔;每个部分都可以有首部字段。
+一份报文主体内可含有多种类型的实体同时发送,每个部分之间用 boundary 字段定义的分隔符进行分隔,每个部分都可以有首部字段。
例如,上传多个表单时可以使用如下方式:
-
+```html
+Content-Type: multipart/form-data; boundary=AaB03x
+
+--AaB03x
+Content-Disposition: form-data; name="submit-name"
+
+Larry
+--AaB03x
+Content-Disposition: form-data; name="files"; filename="file1.txt"
+Content-Type: text/plain
+
+... contents of file1.txt ...
+--AaB03x--
+```
## 范围请求
@@ -330,12 +428,28 @@ Expires 字段可以用于告知缓存服务器该资源什么时候会过期。
在请求报文首部中添加 Range 字段,然后指定请求的范围,例如 Range:bytes=5001-10000。请求成功的话服务器发送 206 Partial Content 状态。
+```html
+GET /z4d4kWk.jpg HTTP/1.1
+Host: i.imgur.com
+Range: bytes=0-1023
+```
+
+```html
+HTTP/1.1 206 Partial Content
+Content-Range: bytes 0-1023/146515
+Content-Length: 1024
+...
+(binary content)
+```
+
## 内容协商
通过内容协商返回最合适的内容,例如根据浏览器的默认语言选择返回中文界面还是英文界面。
涉及以下首部字段:Accept、Accept-Charset、Accept-Encoding、Accept-Language、Content-Language。
+
+
## 虚拟主机
使用虚拟主机技术,使得一台服务器拥有多个域名,并且在逻辑上可以看成多个服务器。
@@ -350,19 +464,19 @@ Expires 字段可以用于告知缓存服务器该资源什么时候会过期。
使用代理的主要目的是:缓存、网络访问控制以及访问日志记录。
-
+
**网关**
与代理服务器不同的是,网关服务器会将 HTTP 转化为其它协议进行通信,从而请求其它非 HTTP 服务器的服务。
-
+
**隧道**
使用 SSL 等加密手段,为客户端和服务器之间建立一条安全的通信线路。
-
+
# HTTPs
@@ -382,20 +496,20 @@ HTTPs 并不是新协议,而是 HTTP 先和 SSL(Secure Socket Layer)通信
HTTPs 采用 **混合的加密机制** ,使用公开密钥加密用于传输对称密钥,之后使用对称密钥加密进行通信。(下图中,共享密钥即对称密钥)
-
+
## 认证
-通过使用 **证书** 来对通信方进行认证。证书中有公开密钥数据,如果可以验证公开密钥的确属于通信方的,那么就可以确定通信方是可靠的。
+通过使用 **证书** 来对通信方进行认证。
-数字证书认证机构(CA,Certificate Authority)可以对其颁发的公开密钥证书对其进行验证。
+数字证书认证机构(CA,Certificate Authority)是客户端与服务器双方都可信赖的第三方机构。服务器的运营人员向 CA 提出公开密钥的申请,CA 在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公开密钥证书后绑定在一起。
-进行 HTTPs 通信时,服务器会把证书发送给客户端,客户端取得其中的公开密钥之后,就可以开始通信。
+进行 HTTPs 通信时,服务器会把证书发送给客户端,客户端取得其中的公开密钥之后,先进行验证,如果验证通过,就可以开始通信。
+
+除了上诉提到的服务器端证书之外,还有客户端证书,客户端证书的目的就是让服务器对客户端进行验证。客户端证书需要用户自行安装,只有在业务需要非常高的安全性时才使用客户端证书,例如网上银行。
使用 OpenSSL 这套开源程序,每个人都可以构建一套属于自己的认证机构,从而自己给自己颁发服务器证书。浏览器在访问该服务器时,会显示“无法确认连接安全性”或“该网站的安全证书存在问题”等警告消息。
-客户端证书需要用户自行安装,只有在业务需要非常高的安全性时才使用客户端证书,例如网上银行。
-
## 完整性
SSL 提供摘要功能来验证完整性。
@@ -416,11 +530,11 @@ HTTP/1.1 新增了以下内容:
**多路复用**
-HTTP/2.0 使用多路复用技术,即使用同一个 TCP 连接来处理多个请求。
+HTTP/2.0 使用多路复用技术,使用同一个 TCP 连接来处理多个请求。
**首部压缩**
-HTTP1.1 的首部带有大量信息,而且每次都要重复发送,HTTP/2.0 要求通讯双方各自缓存一份首部字段表,从而避免了重复传输。
+HTTP/1.1 的首部带有大量信息,而且每次都要重复发送。HTTP/2.0 要求通讯双方各自缓存一份首部字段表,从而避免了重复传输。
**服务端推送**
@@ -428,4 +542,9 @@ HTTP1.1 的首部带有大量信息,而且每次都要重复发送,HTTP/2.0
**二进制格式**
-HTTP1.1 的解析是基于文本的,而 HTTP2.0 采用二进制格式。
+HTTP/1.1 的解析是基于文本的,而 HTTP/2.0 采用二进制格式。
+
+# 参考资料
+
+- [图解 HTTP](https://pan.baidu.com/s/1M0AHXqG9sP9Bxne6u0JK8A)
+- [MDN : HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)
diff --git a/notes/JVM.md b/notes/JVM.md
index a49135b3..78d24f77 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 4bfb4d46..58709c31 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 33938e9a..7c4964a2 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 2da89cca..73a831fd 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 23fc6d1b..bb498cb0 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 cf67cdca..b0d25164 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];
@@ -1820,7 +1820,7 @@ public int minPathSum(int[][] grid) {
定义一个数组 dp 存储上楼梯的方法数(为了方便讨论,数组下标从 1 开始),dp[i] 表示走到第 i 个楼梯的方法数目。第 i 个楼梯可以从第 i-1 和 i-2 个楼梯再走一步到达,走到第 i 个楼梯的方法数为走到第 i-1 和第 i-2 个楼梯的方法数之和。
-
+
dp[N] 即为所求。
@@ -1849,7 +1849,7 @@ public int climbStairs(int n) {
第 i 年成熟的牛的数量为:
-
+
**强盗抢劫**
@@ -1859,7 +1859,7 @@ public int climbStairs(int n) {
定义 dp 数组用来存储最大的抢劫量,其中 dp[i] 表示抢到第 i 个住户时的最大抢劫量。由于不能抢劫邻近住户,因此如果抢劫了第 i 个住户那么只能抢劫 i - 2 和 i - 3 的住户,所以
-
+
O(n) 空间复杂度实现方法:
@@ -1938,7 +1938,7 @@ private int rob(int[] nums, int s, int e) {
综上所述,错误装信数量方式数量为:
-
+
dp[N] 即为所求。
@@ -1954,7 +1954,7 @@ dp[N] 即为所求。
因为在求 dp[n] 时可能无法找到一个满足条件的递增子序列,此时 {Sn} 就构成了递增子序列,因此需要对前面的求解方程做修改,令 dp[n] 最小为 1,即:
-
+
对于一个长度为 N 的序列,最长子序列并不一定会以 SN 为结尾,因此 dp[N] 不是序列的最长递增子序列的长度,需要遍历 dp 数组找出最大值才是所要的结果,即 max{ dp[i] | 1 <= i <= N} 即为所求。
@@ -2050,7 +2050,7 @@ public int wiggleMaxLength(int[] nums) {
综上,最长公共子系列的状态转移方程为:
-
+
对于长度为 N 的序列 S1 和 长度为 M 的序列 S2,dp[N][M] 就是序列 S1 和序列 S2 的最长公共子序列长度。
@@ -2087,7 +2087,7 @@ public int lengthOfLCS(int[] nums1, int[] nums2) {
综上,0-1 背包的状态转移方程为:
-
+
```java
public int knapsack(int W, int N, int[] weights, int[] values) {
@@ -2111,7 +2111,7 @@ public int knapsack(int W, int N, int[] weights, int[] values) {
在程序实现时可以对 0-1 背包做优化。观察状态转移方程可以知道,前 i 件物品的状态仅由前 i-1 件物品的状态有关,因此可以将 dp 定义为一维数组,其中 dp[j] 既可以表示 dp[i-1][j] 也可以表示 dp[i][j]。此时,
-
+
因为 dp[j-w] 表示 dp[i-1][j-w],因此不能先求 dp[i][j-w] 防止将 dp[i-1][j-w] 覆盖。也就是说要先计算 dp[i][j] 再计算 dp[i][j-w],在程序实现时需要按倒序来循环求解。
@@ -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 341de0aa..cf76b2c9 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 d63256c5..cb67acd3 100644
--- a/notes/MySQL.md
+++ b/notes/MySQL.md
@@ -221,11 +221,11 @@ SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;
### 3.3 多列索引
-在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 file_id 设置为多列索引。
+在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 film_id 设置为多列索引。
```sql
-SELECT file_id, actor_ id FROM sakila.film_actor
-WhERE actor_id = 1 OR film_id = 1;
+SELECT film_id, actor_ id FROM sakila.film_actor
+WhERE actor_id = 1 AND film_id = 1;
```
### 3.4 索引列的顺序
@@ -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
new file mode 100644
index 00000000..36c036f9
--- /dev/null
+++ b/notes/Redis.md
@@ -0,0 +1,437 @@
+
+* [Redis 是什么](#redis-是什么)
+* [Redis 的五种基本类型](#redis-的五种基本类型)
+ * [Strings](#strings)
+ * [Lists](#lists)
+ * [Sets](#sets)
+ * [Hashs](#hashs)
+ * [Sorted Sets](#sorted-sets)
+* [键的过期时间](#键的过期时间)
+* [发布与订阅](#发布与订阅)
+* [事务](#事务)
+* [持久化](#持久化)
+ * [1. 快照持久化](#1-快照持久化)
+ * [2. AOF 持久化](#2-aof-持久化)
+* [复制](#复制)
+* [处理故障](#处理故障)
+* [分片](#分片)
+* [事件](#事件)
+* [Redis 与 Memcached 的区别](#redis-与-memcached-的区别)
+* [Redis 适用场景](#redis-适用场景)
+* [数据淘汰策略](#数据淘汰策略)
+* [一个简单的论坛系统分析](#一个简单的论坛系统分析)
+* [参考资料](#参考资料)
+
+
+
+# Redis 是什么
+
+Redis 是速度非常快的非关系型(NoSQL)内存键值数据库,可以存储键和五种不同类型的值之间的映射。
+
+五种类型数据类型为:字符串、列表、集合、有序集合、散列表。
+
+Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片来扩展写性能。
+
+# Redis 的五种基本类型
+
+| 数据类型 | 可以存储的值 | 操作 |
+| -- | -- | -- |
+| Strings | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作 对整数和浮点数执行自增或者自减操作 |
+| Lists | 链表 | 从两端压入或者弹出元素 读取单个或者多个元素 进行修剪,只保留一个范围内的元素 |
+| Sets | 无序集合 | 添加、获取、移除单个元素 检查一个元素是否存在于集合中 计算交集、并集、差集 从集合里面随机获取元素 |
+| Hashs | 包含键值对的无序散列表 | 添加、获取、移除单个键值对 获取所有键值对 检查某个键是否存在|
+| Sorted Sets | 有序集合 | 添加、获取、删除元素个元素 根据分值范围或者成员来获取元素 计算一个键的排名 |
+
+> [What Redis data structures look like](https://redislabs.com/ebook/part-1-getting-started/chapter-1-getting-to-know-redis/1-2-what-redis-data-structures-look-like/)
+
+## Strings
+
+
+
+```html
+> set hello world
+OK
+> get hello
+"world"
+> del hello
+(integer) 1
+> get hello
+(nil)
+```
+
+## Lists
+
+
+
+```html
+> rpush list-key item
+(integer) 1
+> rpush list-key item2
+(integer) 2
+> rpush list-key item
+(integer) 3
+
+> lrange list-key 0 -1
+1) "item"
+2) "item2"
+3) "item"
+
+> lindex list-key 1
+"item2"
+
+> lpop list-key
+"item"
+
+> lrange list-key 0 -1
+1) "item2"
+2) "item"
+```
+
+## Sets
+
+
+
+```html
+> sadd set-key item
+(integer) 1
+> sadd set-key item2
+(integer) 1
+> sadd set-key item3
+(integer) 1
+> sadd set-key item
+(integer) 0
+
+> smembers set-key
+1) "item"
+2) "item2"
+3) "item3"
+
+> sismember set-key item4
+(integer) 0
+> sismember set-key item
+(integer) 1
+
+> srem set-key item2
+(integer) 1
+> srem set-key item2
+(integer) 0
+
+> smembers set-key
+1) "item"
+2) "item3"
+```
+
+## Hashs
+
+
+
+```html
+> hset hash-key sub-key1 value1
+(integer) 1
+> hset hash-key sub-key2 value2
+(integer) 1
+> hset hash-key sub-key1 value1
+(integer) 0
+
+> hgetall hash-key
+1) "sub-key1"
+2) "value1"
+3) "sub-key2"
+4) "value2"
+
+> hdel hash-key sub-key2
+(integer) 1
+> hdel hash-key sub-key2
+(integer) 0
+
+> hget hash-key sub-key1
+"value1"
+
+> hgetall hash-key
+1) "sub-key1"
+2) "value1"
+```
+
+## Sorted Sets
+
+
+
+
+```html
+> zadd zset-key 728 member1
+(integer) 1
+> zadd zset-key 982 member0
+(integer) 1
+> zadd zset-key 982 member0
+(integer) 0
+
+> zrange zset-key 0 -1 withscores
+1) "member1"
+2) "728"
+3) "member0"
+4) "982"
+
+> zrangebyscore zset-key 0 800 withscores
+1) "member1"
+2) "728"
+
+> zrem zset-key member1
+(integer) 1
+> zrem zset-key member1
+(integer) 0
+
+> zrange zset-key 0 -1 withscores
+1) "member0"
+2) "982"
+```
+
+# 键的过期时间
+
+Redis 可以为每个键设置过期时间,当键过期时,会自动删除该键。
+
+对于散列表这种容器,只能为整个键设置过期时间(整个散列表),而不能为键里面的单个元素设置过期时间。
+
+过期时间对于清理缓存数据非常有用。
+
+# 发布与订阅
+
+发布与订阅实际上是观察者模式,订阅者订阅了频道之后,发布者向频道发送字符串消息会被所有订阅者接收到。
+
+发布与订阅有一些问题,很少使用它,而是使用替代的解决方案。问题如下:
+
+1. 如果订阅者读取消息的速度很慢,会使得消息不断积压在发布者的输出缓存区中,造成内存占用过多;
+2. 如果订阅者在执行订阅的过程中网络出现问题,那么就会丢失断线期间发送的所有消息。
+
+# 事务
+
+Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操作包围起来。
+
+MULTI 和 EXEC 中的操作将会一次性发送给服务器,而不是一条一条发送,这种方式称为流水线,它可以减少客户端与服务器之间的网络通信次数从而提升性能。
+
+# 持久化
+
+Redis 是内存型数据库,为了保证数据在断电后不会丢失,需要将内存中的数据持久化到硬盘上。
+
+## 1. 快照持久化
+
+将某个时间点的所有数据都存放到硬盘上。
+
+可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。
+
+如果系统发生故障,将会丢失最后一次创建快照之后的数据。并且如果数据量很大,保存快照的时间也会很长。
+
+## 2. AOF 持久化
+
+AOF 持久化将写命令添加到 AOF 文件(Append Only File)的末尾。
+
+对硬盘的文件进行写入时,写入的内容首先会被存储到缓冲区,然后由操作系统决定什么时候将该内容同步到硬盘,用户可以调用 file.flush() 方法请求操作系统尽快将缓冲区存储的数据同步到硬盘。因此将写命令添加到 AOF 文件时,要根据需求来保证何时将添加的数据同步到硬盘上,有以下同步选项:
+
+| 选项 | 同步频率 |
+| -- | -- |
+| always | 每个写命令都同步 |
+| everysec | 每秒同步一次 |
+| no | 让操作系统来决定何时同步 |
+
+always 选项会严重减低服务器的性能;everysec 选项比较合适,可以保证系统奔溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响;no 选项并不能给服务器性能带来多大的提升,而且也会增加系统奔溃时数据丢失的数量。
+
+随着服务器写请求的增多,AOF 文件会越来越大;Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。
+
+# 复制
+
+通过使用 slaveof host port 命令来让一个服务器成为另一个服务器的从服务器。
+
+一个从服务器只能有一个主服务器,并且不支持主主复制。
+
+**1. 从服务器连接主服务器的过程**
+
+(1) 主服务器创建快照文件,发送给从服务器,并在发送期间使用缓冲区记录执行的写命令。快照文件发送完毕之后,开始向从服务器发送存储在缓冲区中的写命令;
+(2) 从服务器丢弃所有旧数据,载入主服务器发来的快照文件,之后从服务器开始接受主服务器发来的写命令;
+(3) 主服务器每执行一次写命令,就向从服务器发送相同的写命令。
+
+**2. 主从链**
+
+随着负载不断上升,主服务器可能无法很快地更新所有从服务器,或者重新连接和重新同步从服务器而导致系统超载。为了解决这个问题,可以创建一个中间层来分担主服务器的复制工作。中间层的服务器是最上层服务器的从服务器,又是最下层服务器的主服务器。
+
+
+
+# 处理故障
+
+要用到持久化文件来恢复服务器的数据。
+
+持久化文件可能因为服务器出错也有错误,因此要先对持久化文件进行验证和修复。对 AOF 文件就行验证和修复很容易,修复操作将第一个出错命令和其后的所有命令都删除;但是只能验证快照文件,无法对快照文件进行修复,因为快照文件进行了压缩,出现在快照文件中间的错误可能会导致整个快照文件的剩余部分无法读取。
+
+当主服务器出现故障时,Redis 常用的做法是新开一台服务器作为主服务器,具体步骤如下:假设 A 为主服务器,B 为从服务器,当 A 出现故障时,让 B 生成一个快照文件,将快照文件发送给 C,并让 C 恢复快照文件的数据。最后,让 B 成为 C 的从服务器。
+
+# 分片
+
+Redis 中的分片类似于 MySQL 的分表操作,分片是将数据划分为多个部分的方法,对数据的划分可以基于键包含的 ID、基于键的哈希值,或者基于以上两者的某种组合。通过对数据进行分片,用户可以将数据存储到多台机器里面,也可以从多台机器里面获取数据,这种方法在解决某些问题时可以获得线性级别的性能提升。
+
+假设有 4 个 Reids 实例 R0,R1,R2,R3,还有很多表示用户的键 user:1,user:2,... 等等,有不同的方式来选择一个指定的键存储在哪个实例中。最简单的方式是范围分片,例如用户 id 从 0\~1000 的存储到实例 R0 中,用户 id 从 1001\~2000 的存储到实例 R1 中,等等。但是这样需要维护一张映射范围表,维护操作代价很高。还有一种方式是哈希分片,使用 CRC32 哈希函数将键转换为一个数字,再对实例数量求模就能知道应该存储的实例。
+
+**1. 客户端分片**
+
+客户端使用一致性哈希等算法决定键应当分布到哪个节点。
+
+**2. 代理分片**
+
+将客户端请求发送到代理上,由代理转发请求到正确的节点上。
+
+**3. 服务器分片**
+
+Redis Cluster。
+
+# 事件
+
+**1. 事件类型**
+
+(1) 文件事件:服务器有许多套接字,事件产生时会对这些套接字进行操作,服务器通过监听套接字来处理事件。常见的文件事件有:客户端的连接事件;客户端的命令请求事件;服务器向客户端返回命令结果的事件;
+
+(2) 时间事件:又分为两类,定时事件是让一段程序在指定的时间之内执行一次;周期性时间是让一段程序每隔指定时间就执行一次。
+
+**2. 事件的调度与执行**
+
+服务器需要不断监听文件事件的套接字才能得到待处理的文件事件,但是不能监听太久,否则时间事件无法在规定的时间内执行,因此监听时间应该根据距离现在最近的时间事件来决定。
+
+事件调度与执行由 aeProcessEvents 函数负责,伪代码如下:
+
+```python
+def aeProcessEvents():
+
+ # 获取到达时间离当前时间最接近的时间事件
+ time_event = aeSearchNearestTimer()
+
+ #计算最接近的时间事件距离到达还有多少毫秒
+ remaind_ms = time_event.when - unix_ts_now()
+
+ # 如果事件已到达,那么 remaind_ms 的值可能为负数,将它设为 0
+ if remaind_ms < 0:
+ remaind_ms = 0
+
+ # 根据 remaind_ms 的值,创建 timeval
+ timeval = create_timeval_with_ms(remaind_ms)
+
+ # 阻塞并等待文件事件产生,最大阻塞时间由传入的 timeval 决定
+ aeApiPoll(timeval)
+
+ # 处理所有已产生的文件事件
+ procesFileEvents()
+
+ # 处理所有已到达的时间事件
+ processTimeEvents()
+```
+
+将 aeProcessEvents 函数置于一个循环里面,加上初始化和清理函数,就构成了 Redis 服务器的主函数,伪代码如下:
+
+```python
+def main():
+
+ # 初始化服务器
+ init_server()
+
+ # 一直处理事件,直到服务器关闭为止
+ while server_is_not_shutdown():
+ aeProcessEvents()
+
+ # 服务器关闭,执行清理操作
+ clean_server()
+```
+
+事件处理的角度下服务器运行流程如下:
+
+
+
+# Redis 与 Memcached 的区别
+
+两者都是非关系型内存键值数据库。有以下主要不同:
+
+**1. 数据类型**
+
+Memcached 仅支持字符串类型,而 Redis 支持五种不同种类的数据类型,使得它可以更灵活地解决问题。
+
+**2. 数据持久化**
+
+Redis 支持两种持久化策略:RDB 快照和 AOF 日志,而 Memcached 不支持持久化。
+
+**3. 分布式**
+
+Memcached 不支持分布式,只能通过在客户端使用像一致性哈希这样的分布式算法来实现分布式存储,这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点。
+
+Redis Cluster 实现了分布式的支持。
+
+**4. 内存管理机制**
+
+在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘。而 Memcached 的数据则会一直在内存中。
+
+Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题,但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。
+
+# Redis 适用场景
+
+**1. 缓存**
+
+适用 Redis 作为缓存,将热点数据放到内存中。
+
+**2. 消息队列**
+
+Redis 的 list 类型是双向链表,很适合用于消息队列。
+
+**3. 计数器**
+
+Redis 这种内存数据库才能支持计数器的频繁读写操作。
+
+**4. 好友关系**
+
+使用 set 类型的交集很容易就可以知道两个用户的共同好友。
+
+# 数据淘汰策略
+
+可以设置内存最大使用量,当内存使用量超过时施行淘汰策略,具体有 6 种淘汰策略。
+
+| 策略 | 描述 |
+| -- | -- |
+| volatile-lru | 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰 |
+| volatile-ttl | 从已设置过期时间的数据集中挑选将要过期的数据淘汰 |
+|volatile-random | 从已设置过期时间的数据集中任意选择数据淘汰 |
+| allkeys-lru | 从所有数据集中挑选最近最少使用的数据淘汰 |
+| allkeys-random | 从所有数据集中任意选择数据进行淘汰 |
+| no-envicition | 禁止驱逐数据 |
+
+如果使用 Redis 来缓存数据时,要保证所有数据都是热点数据,可以将内存最大使用量设置为热点数据占用的内存量,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰。
+
+# 一个简单的论坛系统分析
+
+该论坛系统功能如下:
+
+1. 可以发布文章;
+2. 可以对文章进行点赞;
+3. 在首页可以按文章的发布时间或者文章的点赞数进行排序显示;
+
+**1. 文章信息**
+
+文章包括标题、作者、赞数等信息,在关系型数据库中很容易构建一张表来存储这些信息,在 Redis 中可以使用 HASH 来存储每种信息以及其对应的值的映射。
+
+Redis 没有表的概念将同类型的数据存放在一起,而是使用命名空间的方式来实现这一功能。键名的前面部分存储命名空间,后面部分的内容存储 ID,通常使用 : 来进行分隔。例如下面的 HASH 的键名为 article:92617,其中 article 为命名空间,ID 为 92617。
+
+
+
+**2. 点赞功能**
+
+当有用户为一篇文章点赞时,除了要对该文章的 votes 字段进行加 1 操作,还必须记录该用户已经对该文章进行了点赞,防止用户不断点赞。可以建立文章的已投票用户集合来进行记录。
+
+为了节约内存,规定一篇文章发布满一周之后,就不能再对它进行投票,而文章的已投票集合也会被删除,可以为文章的已投票集合设置一个一周的过期时间就能实现这个规定。
+
+
+
+**3. 对文章进行排序**
+
+为了按发布时间和点赞数进行排序,可以建立一个文章发布时间的有序集合和一个文章点赞数的有序集合。(下图中的 score 就是这里所说的点赞数;下面所示的有序集合分值并不直接是时间和点赞数,而是根据它们间接计算出来的)
+
+
+
+# 参考资料
+
+- Redis 实战
+- Reids 设计与实现
+- [REDIS IN ACTION](https://redislabs.com/ebook/foreword/)
+- [论述 Redis 和 Memcached 的差异](http://www.cnblogs.com/loveincode/p/7411911.html)
+- [Redis 3.0 中文版- 分片](http://wiki.jikexueyuan.com/project/redis-guide)
+- [Redis 应用场景](http://www.scienjus.com/redis-use-case/)
diff --git a/notes/SQL 语法.md b/notes/SQL.md
similarity index 99%
rename from notes/SQL 语法.md
rename to notes/SQL.md
index 20d5c00d..d9a82b2d 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 0c5ab772..28b61ed8 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 2e19e146..d7f91dbb 100644
--- a/notes/剑指 offer 题解.md
+++ b/notes/剑指 offer 题解.md
@@ -161,7 +161,7 @@ public boolean Find(int target, int [][] array) {
**题目要求**
-以 O(1) 的空间复杂度和 O(n) 的空间复杂度来求解。
+以 O(1) 的空间复杂度和 O(n) 的时间复杂度来求解。
**解题思路**
@@ -302,7 +302,7 @@ private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int[] in,
- 如果一个节点有右子树不为空,那么该节点的下一个节点是右子树的最左节点;
- 否则,向上找第一个左链接指向的树包含该节点的祖先节点。
-
+
```java
public TreeLinkNode GetNext(TreeLinkNode pNode) {
@@ -679,14 +679,14 @@ private void printNumber(char[] number) {
- 如果链表不是尾节点,那么可以直接将下一个节点的值赋给节点,令节点指向下下个节点,然后删除下一个节点,时间复杂度为 O(1)。
-
+
- 否则,就需要先遍历链表,找到节点的前一个节点,然后让前一个节点指向节点的下一个节点,时间复杂度为 O(N)。
-
+
-- 综上,如果进行 N 次操作,那么大约需要移动节点的次数为 N-1+N=2N-1,其中 N-1 表示不是链表尾节点情况下的移动次数,N 表示是尾节点情况下的移动次数。那么增长数量级为 (2N-1)/N \~ 2,因此该算法的时间复杂度为 O(1)。
+- 综上,如果进行 N 次操作,那么大约需要移动节点的次数为 N-1+N=2N-1,其中 N-1 表示不是链表尾节点情况下的移动次数,N 表示是尾节点情况下的移动次数。(2N-1)/N \~ 2,因此该算法的时间复杂度为 O(1)。
```java
public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
@@ -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 c8e5ef20..47394ee8 100644
--- a/notes/数据库系统原理.md
+++ b/notes/数据库系统原理.md
@@ -76,7 +76,7 @@ T1 和 T2 两个事务同时对一个数据进行修改,
## 2. 读脏数据
-T1 做修改后写入数据库,T2 读取这个修改后的数据,但是如果 T1 撤销了这次修改,使得 T2 读取的数据是脏数据。
+T1 修改后写入数据库,T2 读取这个修改后的数据,但是如果 T1 撤销了这次修改,使得 T2 读取的数据是脏数据。
## 3. 不可重复读
@@ -92,7 +92,7 @@ T1 读入某个数据,T2 对该数据做了修改,如
## 2. 提交读(READ COMMITTED)
-一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所在的修改在提交之前对其它事务使不可见的。这个级别有时候也叫做不可重复读,因为两次执行同样的查询,可能会得到不一样的结果。
+一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所在的修改在提交之前对其它事务是不可见的。这个级别有时候也叫做不可重复读,因为两次执行同样的查询,可能会得到不一样的结果。
## 3. 可重复读(REPEATABLE READ)
@@ -112,40 +112,39 @@ T1 读入某个数据,T2 对该数据做了修改,如
排它锁 (X 锁)和共享锁 (S 锁),又称写锁和读锁。
-- 一个事务对数据对象 A 加了 X 锁,就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何其它锁;
+- 一个事务对数据对象 A 加了 X 锁,就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何锁;
- 一个事务对数据对象 A 加了 S 锁,可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁,但是不能加 X 锁。
-
# 封锁粒度
-应该尽量只锁定需要修改的部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就更小,则系统的并发程度越高。
+应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。
-但是加锁需要消耗资源,锁的各种操作,包括获取锁,检查所是否已经解除、释放锁,都会增加系统开销。因此需要在锁开销以及数据安全性之间做一个权衡。
+但是加锁需要消耗资源,锁的各种操作,包括获取锁,检查所是否已经解除、释放锁,都会增加系统开销。因此封锁粒度越小,系统开销就越大。需要在锁开销以及数据安全性之间做一个权衡。
-MySQL 中主要提供了两种锁粒度:行解锁以及表级锁。
+MySQL 中主要提供了两种封锁粒度:行级锁以及表级锁。
# 封锁协议
## 三级封锁协议
-
+
-**1 级封锁协议**
+**一级封锁协议**
事务 T 要修改数据 A 时必须加 X 锁,直到事务结束才释放锁。
可以解决丢失修改问题;
-**2 级封锁协议**
+**二级封锁协议**
-在 1 级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。
+在一级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。
可以解决读脏数据问题,因为如果一个事务在对数据 A 进行修改,根据 1 级封锁协议,会加 X 锁,那么就不能再加 S 锁了,也就是不会读入数据。
-**3 级封锁协议**
+**三级封锁协议**
-在 2 级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁。
+在二级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁。
可以解决不可重复读的问题,因为读 A 时,其它事务不能对 A 加 X 锁,从而避免了在读的期间数据发生改变。
@@ -188,11 +187,11 @@ MySQL InnoDB 采用的是两阶段锁协议。在事务执行过程中,随时
如果 {A1,A2,... ,An} 是关系的一个或多个属性的集合,该集合决定了关系的其它所有属性并且是最小的,那么该集合就称为键码。
-对于函数依赖 W->A,如果能找到 W 的真子集使得 A 依赖于这个真子集,那么就是部分依赖,否则就是完全依赖;
+对于 W->A,如果能找到 W 的真子集 W',使得 W'-> A,那么 W->A 就是部分函数依赖,否则就是完全函数依赖;
-以下关系中,Sno 表示学号,Sname 表示学生姓名,Sdept 表示学院,Cname 表示课程名,Mname 表示院长姓名。函数依赖为 (Sno, Cname) -> (Sname, Sdept, Mname)。注:实际开发过程中,不会出现这种表,而是每个实体都放在单独一张表中,然后实体之间的联系表用实体 id 来表示。
+以下关系中,Sno 表示学号,Sname 表示学生姓名,Sdept 表示学院,Cname 表示课程名,Mname 表示院长姓名。函数依赖为 {Sno, Cname} -> {Sname, Sdept, Mname}。
-
+
不符合范式的关系,会产生很多异常。主要有以下四种异常:
@@ -217,25 +216,25 @@ MySQL InnoDB 采用的是两阶段锁协议。在事务执行过程中,随时
**分解前**
-
+
-
+
**分解后**
-
+
-
+
-
+
-
+
-
+
-
+
-
+
## 第三范式 (3NF)
@@ -243,7 +242,7 @@ MySQL InnoDB 采用的是两阶段锁协议。在事务执行过程中,随时
上述 S1 存在传递依赖,Mname 依赖于 Sdept,而 Sdept 又依赖于 Sno,可以继续分解。
-
+
## BC 范式(BCNF)
@@ -251,15 +250,15 @@ MySQL InnoDB 采用的是两阶段锁协议。在事务执行过程中,随时
关系模式 STC(Sname, Tname, Cname, Grade),其中四个属性分别为学生姓名、教师姓名、课程名和成绩。有以下函数依赖:
-
+
-
+
-
+
-
+
-
+
分解成 SC(Sname, Cname, Grade) 和 ST(Sname, Tname),对于 ST,属性之间是多对多关系,无函数依赖。
@@ -267,7 +266,9 @@ MySQL InnoDB 采用的是两阶段锁协议。在事务执行过程中,随时
## 键码
-用于唯一表示一个实体。键码可以由多个属性构成,每个构成键码的属性成为码。
+用于唯一表示一个实体。
+
+键码可以由多个属性构成,每个构成键码的属性称为码。
## 单值约束
@@ -287,15 +288,19 @@ MySQL InnoDB 采用的是两阶段锁协议。在事务执行过程中,随时
# 数据库的三层模式和两层映像
-外模式:局部逻辑结构;模式:全局逻辑结构;内模式:物理结构。
+- 外模式:局部逻辑结构
+- 模式:全局逻辑结构
+- 内模式:物理结构
## 外模式
-又称用户模式,是用户和数据库系统的接口,特定的用户只能访问数据库系统提供给他的外模式中的数据。例如不同的用户创建了不同数据库,那么一个用户只能访问他有权限访问的数据库。一个数据库可以有多个外模式,一个用户只能有一个外模式,但是一个外模式可以给多个用户使用。
+又称用户模式,是用户和数据库系统的接口,特定的用户只能访问数据库系统提供给他的外模式中的数据。例如不同的用户创建了不同数据库,那么一个用户只能访问他有权限访问的数据库。
+
+一个数据库可以有多个外模式,一个用户只能有一个外模式,但是一个外模式可以给多个用户使用。
## 模式
-可以分为概念模式和逻辑模式,概念模式可以用概念 - 关系来描述;逻辑模式使用特定的数据模式(比如关系模型)来描述数据的逻辑结构,这种逻辑结构包括数据的组成、数据项的名称、类型、取值范围。不仅如此,逻辑模式还要描述数据之间的关系,数据的完整性与安全性要求。
+可以分为概念模式和逻辑模式,概念模式可以用概念-关系来描述;逻辑模式使用特定的数据模式(比如关系模型)来描述数据的逻辑结构,这种逻辑结构包括数据的组成、数据项的名称、类型、取值范围。不仅如此,逻辑模式还要描述数据之间的关系,数据的完整性与安全性要求。
## 内模式
@@ -311,43 +316,47 @@ MySQL InnoDB 采用的是两阶段锁协议。在事务执行过程中,随时
# ER 图
-Entity-Relationship,包含三个部分:实体、属性、联系。
+Entity-Relationship,有三个组成部分:实体、属性、联系。
## 实体的三种联系
联系包含 1 对 1,1 对多,多对多三种。
-如果 A 到 B 是 1 对多关系,那么画个带箭头的线段指向 B;如果是 1 对 1,画两个带箭头的线段;如果是多对多,画两个不带箭头的线段。
+如果 A 到 B 是 1 对多关系,那么画个带箭头的线段指向 B;如果是 1 对 1,画两个带箭头的线段;如果是多对多,画两个不带箭头的线段。下图的 Course 和 Student 是 1 对多的关系。
-
+
## 表示出现多次的关系
-一个实体在联系出现几次,就要用几条线连接。如下表示一个课程的先修关系,先修关系中,应当出现两个 Course 实体,第一个是先修课程,后一个是后修课程,因此需要用两条线来表示这种关系。
+一个实体在联系出现几次,就要用几条线连接。下图表示一个课程的先修关系,先修关系出现两个 Course 实体,第一个是先修课程,后一个是后修课程,因此需要用两条线来表示这种关系。
-
+
## 联系的多向性
-下图中一个联系表示三个实体的关系。虽然老师可以开设多门课,并且可以教授多名学生,但是对于特定的学生和课程,只有一个老师教授,这就构成了一个三元联系。
+虽然老师可以开设多门课,并且可以教授多名学生,但是对于特定的学生和课程,只有一个老师教授,这就构成了一个三元联系。
-
+
一般只使用二元联系,可以把多元关系转换为二元关系。
-
+
## 表示子类
-用 is-a 联系来表示子类,具体做法是用一个三角形和两条线来连接类和子类。与子类有关的属性和联系都连到子类上,而与父类和子类都有关的连到父类上。
+用 IS-A 联系来表示子类,具体做法是用一个三角形和两条线来连接类和子类。与子类有关的属性和联系都连到子类上,而与父类和子类都有关的连到父类上。
-
+
# 一些概念
-**数据模型** 由数据结构、数据操作和完整性三个要素组成。
+**数据模型**
-**数据库系统** 包括了数据库,数据库管理系统,应用程序以及数据库管理员和用户,还包括相关的硬件和软件。也就是说数据库系统包含所有与数据库相关的内容。
+由数据结构、数据操作和完整性三个要素组成。
+
+**数据库系统**
+
+数据库系统包含所有与数据库相关的内容,包括数据库、数据库管理系统、应用程序以及数据库管理员和用户,还包括相关的硬件和软件。
# 参考资料
diff --git a/notes/算法.md b/notes/算法.md
index eb9acd1d..06a18cf7 100644
--- a/notes/算法.md
+++ b/notes/算法.md
@@ -93,9 +93,9 @@
指数函数可以转换为线性函数,从而在函数图像上显示的更直观。
- 转换为
+ 转换为
-
+
## 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 86d9ddbb..1ad98c99 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 个表是动态增长的,如果使用分页系统的一维地址空间,动态增长的特点会导致覆盖问题的出现。
-
+
分段的做法是把每个表分成段,一个段构成一个独立的地址空间。每个段的长度可以不同,并且可以动态增长。
@@ -723,7 +723,7 @@ void test(i) { // 尝试拿起两把筷子
举例:一个系统为某进程分配了三个物理块,并有如下页面引用序列:
-
+
进程运行时,先将 7,0,1 三个页面装入内存。当进程要访问页面 2 时,产生缺页中断,会将页面 7 换出,因为页面 7 再次被访问的时间最长。
@@ -739,9 +739,9 @@ void test(i) { // 尝试拿起两把筷子
可以用栈来实现该算法,栈中存储页面的页面号。当进程访问一个页面时,将该页面的页面号从栈移除,并将它压入栈顶。这样,最近被访问的页面的页面号总是在栈顶,而最近最久未使用的页面的页面号总是在栈底。
-
+
-
+
### 4. 时钟(Clock)
diff --git a/notes/计算机网络.md b/notes/计算机网络.md
index 99255071..7adb340a 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,13 +155,13 @@
总时延 = 发送时延 + 传播时延 + 处理时延 + 排队时延
-
+
### 1. 发送时延
主机或路由器发送数据帧所需要的时间。
-
+
其中 l 表示数据帧的长度,v 表示发送速率。
@@ -169,7 +169,7 @@
电磁波在信道中传播一定的距离需要花费的时间,电磁波传播速度接近光速。
-
+
其中 l 表示信道长度,v 表示电磁波在信道上的传播速率。
@@ -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,41 +246,41 @@ TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中
使用这两种方式进行通信,在通信的过程中用户会一直占用一部分信道资源。但是由于计算机数据的突发性质,没必要一直占用信道资源而不让出给其它用户使用,因此这两种方式对信道的利用率都不高。
-
+
### 2. 统计时分复用
是对时分复用的一种改进,不固定每个用户在时分复用帧中的位置,只要有数据就集中起来组成统计时分复用帧然后发送。
-
+
### 3. 波分复用
光的频分复用。由于光的频率很高,因此习惯上用波长而不是频率来表示所使用的光载波。
-
+
### 4. 码分复用
-为每个用户分配 m bit 的码片,并且所有的码片正交,对于任意两个码片 和 有
+ 为每个用户分配 m bit 的码片,并且所有的码片正交,对于任意两个码片
和
有
-
+
-为了方便,取 m=8,设码片 为 00011011。在拥有该码片的用户发送比特 1 时就发送该码片,发送比特 0 时就发送该码片的反码 11100100。
+ 为了方便,取 m=8,设码片
为 00011011。在拥有该码片的用户发送比特 1 时就发送该码片,发送比特 0 时就发送该码片的反码 11100100。
在计算时将 00011011 记作 (-1 -1 -1 +1 +1 -1 +1 +1),可以得到
-
+
-
+
-其中 为 的反码。
+ 其中
为
的反码。
-利用上面的式子我们知道,当接收端使用码片 对接收到的数据进行内积运算时,结果为 0 的是其它用户发送的数据,结果为 1 的是用户发送的比特 1,结果为 -1 的是用户发送的比特 0。
+ 利用上面的式子我们知道,当接收端使用码片
对接收到的数据进行内积运算时,结果为 0 的是其它用户发送的数据,结果为 1 的是用户发送的比特 1,结果为 -1 的是用户发送的比特 0。
码分复用需要发送的数据量为原先的 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 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小。
@@ -689,11 +689,11 @@ TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文
一个报文段从发送再到接收到确认所经过的时间称为往返时间 RTT,加权平均往返时间 RTTs 计算如下:
-
+
超时时间 RTO 应该略大于 RRTs,TCP 使用的超时时间计算如下:
-
+
其中 RTTd 为偏差,它与新的 RRT 和 RRTs 有关。
@@ -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 4da4c967..67f88866 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 8cf08472..fa855b12 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 55cdf5e0..efcf1ec6 100644
--- a/notes/面向对象思想.md
+++ b/notes/面向对象思想.md
@@ -1,32 +1,41 @@
-* [S.O.L.I.D](#solid)
- * [1. 单一责任原则](#1-单一责任原则)
- * [2. 开放封闭原则](#2-开放封闭原则)
- * [3. 里氏替换原则](#3-里氏替换原则)
- * [4. 接口分离原则](#4-接口分离原则)
- * [5. 依赖倒置原则](#5-依赖倒置原则)
-* [其他常见原则](#其他常见原则)
- * [1. 迪米特法则](#1-迪米特法则)
- * [2. 合成复用原则](#2-合成复用原则)
- * [3. 共同封闭原则](#3-共同封闭原则)
- * [4. 稳定抽象原则](#4-稳定抽象原则)
- * [5. 稳定依赖原则](#5-稳定依赖原则)
-* [封装、继承、多态](#封装继承多态)
+* [设计原则](#设计原则)
+ * [1. S.O.L.I.D](#1-solid)
+ * [1.1 单一责任原则](#11-单一责任原则)
+ * [1.2 开放封闭原则](#12-开放封闭原则)
+ * [1.3 里氏替换原则](#13-里氏替换原则)
+ * [1.4 接口分离原则](#14-接口分离原则)
+ * [1.5 依赖倒置原则](#15-依赖倒置原则)
+ * [2. 其他常见原则](#2-其他常见原则)
+ * [2.1 迪米特法则](#21-迪米特法则)
+ * [2.2 合成复用原则](#22-合成复用原则)
+ * [2.3 共同封闭原则](#23-共同封闭原则)
+ * [2.4 稳定抽象原则](#24-稳定抽象原则)
+ * [2.5 稳定依赖原则](#25-稳定依赖原则)
+* [三大特性](#三大特性)
* [1. 封装](#1-封装)
* [2. 继承](#2-继承)
* [3. 多态](#3-多态)
* [UML](#uml)
* [1. 类图](#1-类图)
+ * [1.1 继承相关](#11-继承相关)
+ * [1.2 整体和部分](#12-整体和部分)
+ * [1.3 相互联系](#13-相互联系)
* [2. 时序图](#2-时序图)
+ * [2.1 定义](#21-定义)
+ * [2.2 赤壁之战时序图](#22-赤壁之战时序图)
+ * [2.3 活动图、时序图之间的关系](#23-活动图时序图之间的关系)
+ * [2.4 类图与时序图的关系](#24-类图与时序图的关系)
+ * [2.5 时序图的组成](#25-时序图的组成)
* [参考资料](#参考资料)
-# S.O.L.I.D
+# 设计原则
-> [Design Principles](http://www.oodesign.com/design-principles.html)
+设计原则可以帮助我们避免那些糟糕的设计,这些原则被归纳在《敏捷软件开发:原则、模式与实践》这本书中。
-设计原则可以帮助我们避免那些糟糕的设计,这些原则被归纳在《敏捷软件开发:原则、模式与实践》
+## 1. S.O.L.I.D
| 简写 | 全拼 | 中文翻译 |
| -- | -- | -- |
@@ -36,7 +45,7 @@
| ISP | The Interface Segregation Principle | 接口分离原则 |
| DIP | The Dependency Inversion Principle | 依赖倒置原则 |
-## 1. 单一责任原则
+### 1.1 单一责任原则
**修改一个类的原因应该只有一个。**
@@ -44,7 +53,7 @@
如果一个类承担的职责过多,就等于把这些职责耦合在了一起,一个职责的变化可能会削弱这个类完成其它职责的能力。
-## 2. 开放封闭原则
+### 1.2 开放封闭原则
**类应该对扩展开放,对修改关闭。**
@@ -52,21 +61,21 @@
符合开闭原则最典型的设计模式是装饰者模式,它可以动态地将责任附加到对象上,而不用去修改类的代码。
-## 3. 里氏替换原则
+### 1.3 里氏替换原则
-**子类实例应该能够替换掉所有父类实例。**
+**子类对象必须能够替换掉所有父类对象。**
继承是一种 IS-A 关系,子类需要能够当成父类来使用,并且需要比父类更特殊。
如果不满足这个原则,那么各个子类的行为上就会有很大差异,增加继承体系的复杂度。
-## 4. 接口分离原则
+### 1.4 接口分离原则
**不应该强迫客户依赖于它们不用的方法。**
因此使用多个专门的接口比使用单一的总接口总要好。
-## 5. 依赖倒置原则
+### 1.5 依赖倒置原则
- **高层模块不应该依赖于低层模块,二者都应该依赖于抽象**
- **抽象不应该依赖于细节,细节应该依赖于抽象**
@@ -79,7 +88,7 @@
- 任何类都不应该从具体类派生;
- 任何方法都不应该覆写它的任何基类中的已经实现的方法。
-# 其他常见原则
+## 2. 其他常见原则
除了上述的经典原则,在实际开发中还有下面这些常见的设计原则。
@@ -91,44 +100,41 @@
|SAP| The Stable Abstractions Principle | 稳定抽象原则 |
|SDP| The Stable Dependencies Principle | 稳定依赖原则 |
-## 1. 迪米特法则
+### 2.1 迪米特法则
迪米特法则又叫作最少知道原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。
-## 2. 合成复用原则
+### 2.2 合成复用原则
尽量使用对象组合,而不是继承来达到复用的目的。
-## 3. 共同封闭原则
+### 2.3 共同封闭原则
一起修改的类,应该组合在一起(同一个包里)。如果必须修改应用程序里的代码,我们希望所有的修改都发生在一个包里(修改关闭),而不是遍布在很多包里。
-## 4. 稳定抽象原则
+### 2.4 稳定抽象原则
最稳定的包应该是最抽象的包,不稳定的包应该是具体的包,即包的抽象程度跟它的稳定性成正比。
-## 5. 稳定依赖原则
+### 2.5 稳定依赖原则
包之间的依赖关系都应该是稳定方向依赖的,包要依赖的包要比自己更具有稳定性。
-# 封装、继承、多态
-
-封装、继承、多态是面向对象的三大特性。
+# 三大特性
## 1. 封装
-利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户是无需知道对象内部的细节,但可以通过该对象对外的提供的接口来访问该对象。
+利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户无需知道对象内部的细节,但可以通过对象对外提供的接口来访问该对象。
封装有三大好处:
-1. 良好的封装能够减少耦合。
-2. 类内部的结构可以自由修改。
-3. 可以对成员进行更精确的控制。
-4. 隐藏信息,实现细节。
+1. 减少耦合
+2. 隐藏内部细节,因此内部结构可以自由修改
+3. 可以对成员进行更精确的控制
以下 Person 类封装 name、gender、age 等属性,外界只能通过 get() 方法获取一个 Person 对象的 name 属性和 gender 属性,而无法获取 age 属性,但是 age 属性可以供 work() 方法使用。
-注意到 gender 属性使用 int 数据类型进行存储,封装使得用户注意不到这种实现细节。并且在需要修改使用的数据类型时,也可以在不影响客户端代码的情况下进行。
+注意到 gender 属性使用 int 数据类型进行存储,封装使得用户注意不到这种实现细节。并且在需要修改 gender 属性使用的数据类型时,也可以在不影响客户端代码的情况下进行。
```java
public class Person {
@@ -148,7 +154,7 @@ public class Person {
if(18 <= age && age <= 50) {
System.out.println(name + " is working very hard!");
} else {
- System.out.println(name + " can't work!");
+ System.out.println(name + " can't work any more!");
}
}
}
@@ -156,23 +162,27 @@ public class Person {
## 2. 继承
-继承实现了 **is-a** 关系,例如 Cat 和 Animal 就是一种 is-a 关系,因此可以将 Cat 继承自 Animal,从而获得 Animal 非 private 的属性和方法。
+继承实现了 **IS-A** 关系,例如 Cat 和 Animal 就是一种 IS-A 关系,因此 Cat 可以继承自 Animal,从而获得 Animal 非 private 的属性和方法。
-Cat 可以当做 Animal 来使用,也就是可以使用 Animal 引用 Cat 对象,这种子类转换为父类称为 **向上转型** 。
-
-继承应该遵循里氏替换原则:当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有 is-a 关系。
+Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat 对象。父类引用指向子类对象称为 **向上转型** 。
```java
Animal animal = new Cat();
```
+继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。
+
## 3. 多态
-多态分为编译时多态和运行时多态。编译时多态主要指方法的重装,运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定。
+多态分为编译时多态和运行时多态。编译时多态主要指方法的重载,运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定。
-多态有三个条件:1. 继承;2. 覆盖父类方法;3. 向上转型。
+运行时多态有三个条件:
-下面的代码中,乐器类(Instrument)有两个子类:Wind 和 Percussion,它们都覆盖了 play() 方法,并且在 main() 方法中使用父类 Instrument 来引用 Wind 和 Percussion 对象。在 Instrument 引用调用 play() 方法时,会执行实际引用对象所在类的 play() 方法,而不是 Instrument 类的方法。
+1. 继承
+2. 覆盖
+3. 向上转型
+
+下面的代码中,乐器类(Instrument)有两个子类:Wind 和 Percussion,它们都覆盖了父类的 play() 方法,并且在 main() 方法中使用父类 Instrument 来引用 Wind 和 Percussion 对象。在 Instrument 引用调用 play() 方法时,会执行实际引用对象所在类的 play() 方法,而不是 Instrument 类的方法。
```java
public class Instrument {
@@ -203,72 +213,67 @@ public class Music {
}
}
}
-
```
-
-
# UML
## 1. 类图
-**1.1 继承相关**
+### 1.1 继承相关
-继承有两种形式 : 泛化(generalize)和实现(realize),表现为 is-a 关系。
+继承有两种形式 : 泛化(Generalize)和实现(Realize),表现为 IS-A 关系。
-① 泛化关系 (generalization)
+**泛化关系 (Generalize)**
-从具体类中继承
+从具体类中继承。
-
+
-② 实现关系 (realize)
+**实现关系 (Realize)**
-从抽象类或者接口中继承
+从抽象类或者接口中继承。
-
+
-**1.2 整体和部分**
+### 1.2 整体和部分
-① 聚合关系 (aggregation)
+**聚合关系 (Aggregation)**
表示整体由部分组成,但是整体和部分不是强依赖的,整体不存在了部分还是会存在。以下表示 B 由 A 组成:
-
+
-② 组合关系 (composition)
+**组合关系 (Composition)**
和聚合不同,组合中整体和部分是强依赖的,整体不存在了部分也不存在了。比如公司和部门,公司没了部门就不存在了。但是公司和员工就属于聚合关系了,因为公司没了员工还在。
-
+
-**1.3 相互联系**
+### 1.3 相互联系
-① 关联关系 (association)
+**关联关系 (Association)**
表示不同类对象之间有关联,这是一种静态关系,与运行过程的状态无关,在最开始就可以确定。因此也可以用 1 对 1、多对 1、多对多这种关联关系来表示。比如学生和学校就是一种关联关系,一个学校可以有很多学生,但是一个学生只属于一个学校,因此这是一种多对一的关系,在运行开始之前就可以确定。
-
+
-② 依赖关系 (dependency)
+**依赖关系 (Dependency)**
和关联关系不同的是 , 依赖关系是在运行过程中起作用的。一般依赖作为类的构造器或者方法的参数传入。双向依赖时一种不好的设计。
-
+
## 2. 时序图
-http://www.cnblogs.com/wolf-sun/p/UML-Sequence-diagram.html
-
-**2.1 定义**
+### 2.1 定义
时序图描述了对象之间传递消息的时间顺序,它用来表示用例的行为顺序。它的主要作用是通过对象间的交互来描述用例(注意是对象),从而寻找类的操作。
-**2.2 赤壁之战时序图**
+### 2.2 赤壁之战时序图
从虚线从上往下表示时间的推进。
-
+
可见,通过时序图可以知道每个类具有以下操作:
@@ -296,23 +301,23 @@ public class 孙权 {
}
```
-**2.3 活动图、时序图之间的关系**
+### 2.3 活动图、时序图之间的关系
活动图示从用户的角度来描述用例;
时序图是从计算机的角度(对象间的交互)描述用例。
-**2.4 类图与时序图的关系**
+### 2.4 类图与时序图的关系
类图描述系统的静态结构,时序图描述系统的动态行为。
-**2.5 时序图的组成**
+### 2.5 时序图的组成
-① 对象
+**对象**
有三种表现形式
-
+
在画图时,应该遵循以下原则:
@@ -320,13 +325,13 @@ public class 孙权 {
2. 把初始化整个交互活动的对象(有时是一个参与者)放置在最左边。
-② 生命线
+**生命线**
生命线从对象的创建开始到对象销毁时终止
-
+
-③ 消息
+**消息**
对象之间的交互式通过发送消息来实现的。
@@ -334,29 +339,29 @@ public class 孙权 {
1\. 简单消息,不区分同步异步。
-
+
2\. 同步消息,发送消息之后需要暂停活动来等待回应。
-
+
3\. 异步消息,发送消息之后不需要等待。
-
+
4\. 返回消息,可选。
-④ 激活
+**激活**
生命线上的方框表示激活状态,其它时间处于休眠状态。
-
-
+
# 参考资料
- Java 编程思想
-- [ 面向对象设计的 SOLID 原则 ](http://www.cnblogs.com/shanyou/archive/2009/09/21/1570716.html)
-- [ 看懂 UML 类图和时序图 ](http://design-patterns.readthedocs.io/zh_CN/latest/read_uml.html#generalization)
+- 敏捷软件开发:原则、模式与实践
+- [面向对象设计的 SOLID 原则](http://www.cnblogs.com/shanyou/archive/2009/09/21/1570716.html)
+- [看懂 UML 类图和时序图](http://design-patterns.readthedocs.io/zh_CN/latest/read_uml.html#generalization)
- [UML 系列——时序图(顺序图)sequence diagram](http://www.cnblogs.com/wolf-sun/p/UML-Sequence-diagram.html)
-- [ 面向对象编程三大特性 ------ 封装、继承、多态 ](http://blog.csdn.net/jianyuerensheng/article/details/51602015)
+- [面向对象编程三大特性 ------ 封装、继承、多态](http://blog.csdn.net/jianyuerensheng/article/details/51602015)
diff --git a/pics/0e4c8a7f-f84c-4c4e-9544-49cd40167af8.png b/pics/0e4c8a7f-f84c-4c4e-9544-49cd40167af8.png
new file mode 100644
index 00000000..6caf2f25
Binary files /dev/null and b/pics/0e4c8a7f-f84c-4c4e-9544-49cd40167af8.png differ
diff --git a/pics/1202b2d6-9469-4251-bd47-ca6034fb6116.png b/pics/1202b2d6-9469-4251-bd47-ca6034fb6116.png
new file mode 100644
index 00000000..b44fa996
Binary files /dev/null and b/pics/1202b2d6-9469-4251-bd47-ca6034fb6116.png differ
diff --git a/pics/2d078e08-3a49-46d0-b784-df780b7e4bc3.jpg b/pics/2d078e08-3a49-46d0-b784-df780b7e4bc3.jpg
new file mode 100644
index 00000000..9df4fe3f
Binary files /dev/null and b/pics/2d078e08-3a49-46d0-b784-df780b7e4bc3.jpg differ
diff --git a/pics/39a27cca-c9af-482a-8a87-5522557a309e.jpg b/pics/39a27cca-c9af-482a-8a87-5522557a309e.jpg
new file mode 100644
index 00000000..6e9ac942
Binary files /dev/null and b/pics/39a27cca-c9af-482a-8a87-5522557a309e.jpg differ
diff --git a/pics/6019b2db-bc3e-4408-b6d8-96025f4481d6.png b/pics/6019b2db-bc3e-4408-b6d8-96025f4481d6.png
new file mode 100644
index 00000000..900ee963
Binary files /dev/null and b/pics/6019b2db-bc3e-4408-b6d8-96025f4481d6.png differ
diff --git a/pics/73b73189-9e95-47e5-91d0-9378b8462e15.png b/pics/73b73189-9e95-47e5-91d0-9378b8462e15.png
new file mode 100644
index 00000000..3f3b32e5
Binary files /dev/null and b/pics/73b73189-9e95-47e5-91d0-9378b8462e15.png differ
diff --git a/pics/7bd202a7-93d4-4f3a-a878-af68ae25539a.png b/pics/7bd202a7-93d4-4f3a-a878-af68ae25539a.png
new file mode 100644
index 00000000..711fb45b
Binary files /dev/null and b/pics/7bd202a7-93d4-4f3a-a878-af68ae25539a.png differ
diff --git a/pics/b242fafc-5945-42a8-805e-6e3f1f2f89b4.jpg b/pics/b242fafc-5945-42a8-805e-6e3f1f2f89b4.jpg
new file mode 100644
index 00000000..13d2bf61
Binary files /dev/null and b/pics/b242fafc-5945-42a8-805e-6e3f1f2f89b4.jpg differ
diff --git a/pics/cd5fbcff-3f35-43a6-8ffa-082a93ce0f0e.png b/pics/cd5fbcff-3f35-43a6-8ffa-082a93ce0f0e.png
new file mode 100644
index 00000000..f8550a11
Binary files /dev/null and b/pics/cd5fbcff-3f35-43a6-8ffa-082a93ce0f0e.png differ
diff --git a/pics/e84dd187-779f-4ffc-8ccc-40d8c03f5324.jpg b/pics/e84dd187-779f-4ffc-8ccc-40d8c03f5324.jpg
new file mode 100644
index 00000000..76c44279
Binary files /dev/null and b/pics/e84dd187-779f-4ffc-8ccc-40d8c03f5324.jpg differ
diff --git a/pics/ea5e434a-a218-44b5-aa72-4cd08991abcf.jpg b/pics/ea5e434a-a218-44b5-aa72-4cd08991abcf.jpg
new file mode 100644
index 00000000..386f0a6b
Binary files /dev/null and b/pics/ea5e434a-a218-44b5-aa72-4cd08991abcf.jpg differ
diff --git a/pics/fb327611-7e2b-4f2f-9f5b-38592d408f07.png b/pics/fb327611-7e2b-4f2f-9f5b-38592d408f07.png
new file mode 100644
index 00000000..774ecf10
Binary files /dev/null and b/pics/fb327611-7e2b-4f2f-9f5b-38592d408f07.png differ