Merge pull request #1 from CyC2018/master

update to master
This commit is contained in:
resolvewang 2018-03-18 16:07:24 +08:00 committed by GitHub
commit 3379597d03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
219 changed files with 5019 additions and 2313 deletions

View File

@ -41,7 +41,7 @@
> [Leetcode 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Leetcode%20题解.md) > [Leetcode 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Leetcode%20题解.md)
对题目做了一个分类,并对每种题型的解题思路做了总结。已经整理了 300+ 的题目,基本涵盖所有经典题目。 对题目做了一个分类,并对每种题型的解题思路做了总结。
## 面向对象 :couple: ## 面向对象 :couple:
@ -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 必知必会》 整理自《SQL 必知必会》
@ -67,6 +67,10 @@
整理自《高性能 MySQL》整理了一些重点内容。 整理自《高性能 MySQL》整理了一些重点内容。
> [Redis](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Redis.md)
整理自《Redis 设计与实现》和《Redis 实战》
## Java :coffee: ## Java :coffee:
> [JVM](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/JVM.md) > [JVM](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/JVM.md)
@ -89,7 +93,31 @@ File, InputStream OutputStream, Reader Writer, Serializable, Socket, NIO
整理了一些常见考点。 整理了一些常见考点。
## 编码实践 :hammer: ## 分布式 :sweat_drops:
> [分布式基础](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/分布式基础.md)
整理自《大规模分布式存储系统》
> [一致性协议](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/一致性协议.md)
两阶段提交、Paxos、Raft、拜占庭将军问题。
> [分布式问题分析](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/分布式问题分析.md)
分布式事务、复杂均衡算法与实现、分布式锁、分布式 Session、分库分表的分布式困境与应对之策。
## 工具 :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)
@ -103,11 +131,11 @@ File, InputStream OutputStream, Reader Writer, Serializable, Socket, NIO
Google 开源项目的代码风格规范。 Google 开源项目的代码风格规范。
<!-- ## 资料下载 :arrow_down: ## 资料下载 :arrow_down:
> [百度网盘](https://pan.baidu.com/s/1o9oD1s2#list/path=%2F) > [Download](https://github.com/CyC2018/Interview-Notebook/blob/master/other/download.md)
一些 PDF 书籍 --> 一些 PDF 书籍
## 后记 :memo: ## 后记 :memo:

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

@ -1,28 +1,29 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [基础概念](#基础概念) * [一 、基础概念](#一-基础概念)
* [Web 基础](#web-基础) * [Web 基础](#web-基础)
* [URL](#url) * [URL](#url)
* [请求和响应报文](#请求和响应报文) * [请求和响应报文](#请求和响应报文)
* [HTTP 方法](#http-方法) * [二、HTTP 方法](#http-方法)
* [GET获取资源](#get获取资源) * [GET获取资源](#get获取资源)
* [POST传输实体主体](#post传输实体主体) * [POST传输实体主体](#post传输实体主体)
* [HEAD获取报文首部](#head获取报文首部) * [HEAD获取报文首部](#head获取报文首部)
* [PUT上传文件](#put上传文件) * [PUT上传文件](#put上传文件)
* [PATCH对资源进行部分修改](#patch对资源进行部分修改)
* [DELETE删除文件](#delete删除文件) * [DELETE删除文件](#delete删除文件)
* [OPTIONS查询支持的方法](#options查询支持的方法) * [OPTIONS查询支持的方法](#options查询支持的方法)
* [TRACE追踪路径](#trace追踪路径)
* [CONNECT要求用隧道协议连接代理](#connect要求用隧道协议连接代理) * [CONNECT要求用隧道协议连接代理](#connect要求用隧道协议连接代理)
* [HTTP 状态码](#http-状态码) * [TRACE追踪路径](#trace追踪路径)
* [三、HTTP 状态码](#三http-状态码)
* [2XX 成功](#2xx-成功) * [2XX 成功](#2xx-成功)
* [3XX 重定向](#3xx-重定向) * [3XX 重定向](#3xx-重定向)
* [4XX 客户端错误](#4xx-客户端错误) * [4XX 客户端错误](#4xx-客户端错误)
* [5XX 服务器错误](#5xx-服务器错误) * [5XX 服务器错误](#5xx-服务器错误)
* [HTTP 首部](#http-首部) * [四、HTTP 首部](#http-首部)
* [通用首部字段](#通用首部字段) * [通用首部字段](#通用首部字段)
* [请求首部字段](#请求首部字段) * [请求首部字段](#请求首部字段)
* [响应首部字段](#响应首部字段) * [响应首部字段](#响应首部字段)
* [实体首部字段](#实体首部字段) * [实体首部字段](#实体首部字段)
* [具体应用](#具体应用) * [五、具体应用](#具体应用)
* [Cookie](#cookie) * [Cookie](#cookie)
* [缓存](#缓存) * [缓存](#缓存)
* [持久连接](#持久连接) * [持久连接](#持久连接)
@ -33,15 +34,18 @@
* [内容协商](#内容协商) * [内容协商](#内容协商)
* [虚拟主机](#虚拟主机) * [虚拟主机](#虚拟主机)
* [通信数据转发](#通信数据转发) * [通信数据转发](#通信数据转发)
* [HTTPs](#https) * [六、HTTPs](#https)
* [加密](#加密) * [加密](#加密)
* [认证](#认证) * [认证](#认证)
* [完整性](#完整性) * [完整性](#完整性)
* [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 -->
# 基础概念 # 一 、基础概念
## Web 基础 ## Web 基础
@ -61,36 +65,37 @@ URI 包含 URL 和 URN目前 WEB 只有 URL 比较流行,所以见到的基
## 请求和响应报文 ## 请求和响应报文
**请求报文** ### 1. 请求报文
<div align="center"> <img src="../pics//22b39f77-ac47-4978-91ed-84aaf457644c.jpg"/> </div><br> <div align="center"> <img src="../pics//22b39f77-ac47-4978-91ed-84aaf457644c.jpg"/> </div><br>
**响应报文** ### 2. 响应报文
<div align="center"> <img src="../pics//00d8d345-cd4a-48af-919e-209d2788eca7.jpg"/> </div><br> <div align="center"> <img src="../pics//00d8d345-cd4a-48af-919e-209d2788eca7.jpg"/> </div><br>
# HTTP 方法 # 二、HTTP 方法
客户端发送的请求报文第一行为请求行,包含了方法字段。 客户端发送的 **请求报文** 第一行为请求行,包含了方法字段。
## GET获取资源 ## GET获取资源
## POST传输实体主体 ## 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 GET /test/demo_form.asp?name1=value1&name2=value2 HTTP/1.1
``` ```
``` ```
POST /test/demo_form.asp HTTP/1.1 POST /test/demo_form.asp HTTP/1.1
Host: w3schools.com Host: w3schools.com
name1=value1&name2=value2 name1=value1&name2=value2
``` ```
GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 URL 是可见的,可能会泄露私密信息。并且 GET 只支持 ASCII 字符,如果参数为中文则可能会出现乱码,而 POST 支持标准字符集。 GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 URL 是可见的,可能会泄露私密信息。并且 GET 只支持 ASCII 字符,如果参数为中文则可能会出现乱码,而 POST 支持标准字符集。
## HEAD获取报文首部 ## HEAD获取报文首部
@ -100,37 +105,68 @@ GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 U
## PUT上传文件 ## PUT上传文件
由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般 WEB 网站不使用该方法。 由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般不使用该方法。
```html
PUT /new.html HTTP/1.1
Host: example.com
Content-type: text/html
Content-length: 16
<p>New File</p>
```
## 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删除文件 ## DELETE删除文件
与 PUT 功能相反,并且同样不带验证机制。 与 PUT 功能相反,并且同样不带验证机制。
```html
DELETE /file.html HTTP/1.1
```
## OPTIONS查询支持的方法 ## OPTIONS查询支持的方法
查询指定的 URL 能够支持的方法。 查询指定的 URL 能够支持的方法。
会返回 Allow: GET, POST, HEAD, OPTIONS 这样的内容。 会返回 Allow: GET, POST, HEAD, OPTIONS 这样的内容。
## CONNECT要求用隧道协议连接代理
要求在于代理服务器通信时建立隧道,使用 SSLSecure Sokets Layer安全套接字和 TLSTransport Layer Security传输层安全协议把通信内容加密后经网络隧道传输。
```html
CONNECT www.example.com:443 HTTP/1.1
```
<div align="center"> <img src="../pics//5994928c-3d2d-45bd-abb1-adc4f5f4d775.jpg"/> </div><br>
## TRACE追踪路径 ## TRACE追踪路径
服务器会将通信路径返回给客户端。 服务器会将通信路径返回给客户端。
发送请求时,在 Max-Forwards 首部字段中填入数值,每经过一个服务器就会减 1当数值为 0 时就停止传输。 发送请求时,在 Max-Forwards 首部字段中填入数值,每经过一个服务器就会减 1当数值为 0 时就停止传输。
TRACE 一般不会使用,并且它容易受到 XST 攻击Cross-Site Tracing跨站追踪因此更不会去使用它。 通常不会使用 TRACE并且它容易受到 XST 攻击Cross-Site Tracing跨站追踪因此更不会去使用它。
<div align="center"> <img src="../pics//c8637fd2-3aaa-46c4-b7d9-f24d3fa04781.jpg"/> </div><br> <div align="center"> <img src="../pics//c8637fd2-3aaa-46c4-b7d9-f24d3fa04781.jpg"/> </div><br>
## CONNECT要求用隧道协议连接代理 # 三、HTTP 状态码
主要使用 SSLSecure Sokets Layer安全套接字和 TLSTransport Layer Security传输层安全协议把通信内容加密后经网络隧道传输。 服务器返回的 **响应报文** 中第一行为状态行,包含了状态码以及原因短语,用来告知客户端请求的结果。
<div align="center"> <img src="../pics//5994928c-3d2d-45bd-abb1-adc4f5f4d775.jpg"/> </div><br>
# HTTP 状态码
服务器返回的响应报文中第一行为状态行,包含了状态码以及原因短语,来告知客户端请求的结果。
| 状态码 | 类别 | 原因短语 | | 状态码 | 类别 | 原因短语 |
| --- | --- | --- | | --- | --- | --- |
@ -146,7 +182,7 @@ TRACE 一般不会使用,并且它容易受到 XST 攻击Cross-Site Tracing
- **204 No Content** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。 - **204 No Content** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。
- **206 Partial Content** - **206 Partial Content** :表示客户端进行了范围请求。响应报文包含由 Content-Range 指定范围的实体内容。
## 3XX 重定向 ## 3XX 重定向
@ -154,7 +190,7 @@ TRACE 一般不会使用,并且它容易受到 XST 攻击Cross-Site Tracing
- **302 Found** :临时性重定向 - **302 Found** :临时性重定向
- **303 See Other** - **303 See Other** :和 302 有着相同的功能,但是 303 明确要求客户端应该采用 GET 方法获取资源。
- 注:虽然 HTTP 协议规定 301、302 状态下重定向时不允许把 POST 方法改成 GET 方法,但是大多数浏览器都会 在 301、302 和 303 状态下的重定向把 POST 方法改成 GET 方法。 - 注:虽然 HTTP 协议规定 301、302 状态下重定向时不允许把 POST 方法改成 GET 方法,但是大多数浏览器都会 在 301、302 和 303 状态下的重定向把 POST 方法改成 GET 方法。
@ -164,9 +200,9 @@ TRACE 一般不会使用,并且它容易受到 XST 攻击Cross-Site Tracing
## 4XX 客户端错误 ## 4XX 客户端错误
- **400 Bad Request** :请求报文中存在语法错误 - **400 Bad Request** :请求报文中存在语法错误
- **401 Unauthorized** :该状态码表示发送的请求需要有通过 HTTP 认证BASIC 认证、DIGEST 认证)的认证信息。如果之前已进行过一次请求,则表示用户认证失败。 - **401 Unauthorized** :该状态码表示发送的请求需要有认证信息BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。
<div align="center"> <img src="../pics//b1b4cf7d-c54a-4ff1-9741-cd2eea331123.jpg"/> </div><br> <div align="center"> <img src="../pics//b1b4cf7d-c54a-4ff1-9741-cd2eea331123.jpg"/> </div><br>
@ -176,11 +212,11 @@ TRACE 一般不会使用,并且它容易受到 XST 攻击Cross-Site Tracing
## 5XX 服务器错误 ## 5XX 服务器错误
- **500 Internal Server Error** :服务器正在执行请求时发生错误 - **500 Internal Server Error** :服务器正在执行请求时发生错误
- **503 Service Unavilable** 该状态码表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。 - **503 Service Unavilable** :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。
# HTTP 首部 # 四、HTTP 首部
有 4 种类型的首部字段:通用首部字段、请求首部字段、响应首部字段和实体首部字段。 有 4 种类型的首部字段:通用首部字段、请求首部字段、响应首部字段和实体首部字段。
@ -191,7 +227,7 @@ TRACE 一般不会使用,并且它容易受到 XST 攻击Cross-Site Tracing
| 首部字段名 | 说明 | | 首部字段名 | 说明 |
| -- | -- | | -- | -- |
| Cache-Control | 控制缓存的行为 | | Cache-Control | 控制缓存的行为 |
| Connection | 逐跳首部、 连接的管理 | | Connection | 控制不再转发给代理的首部字段;管理持久连接|
| Date | 创建报文的日期时间 | | Date | 创建报文的日期时间 |
| Pragma | 报文指令 | | Pragma | 报文指令 |
| Trailer | 报文末端的首部一览 | | Trailer | 报文末端的首部一览 |
@ -245,7 +281,7 @@ TRACE 一般不会使用,并且它容易受到 XST 攻击Cross-Site Tracing
| Allow | 资源可支持的 HTTP 方法 | | Allow | 资源可支持的 HTTP 方法 |
| Content-Encoding | 实体主体适用的编码方式 | | Content-Encoding | 实体主体适用的编码方式 |
| Content-Language | 实体主体的自然语言 | | Content-Language | 实体主体的自然语言 |
| Content-Length | 实体主体的大小(单位: 字节) | | Content-Length | 实体主体的大小(单位:字节) |
| Content-Location | 替代对应资源的 URI | | Content-Location | 替代对应资源的 URI |
| Content-MD5 | 实体主体的报文摘要 | | Content-MD5 | 实体主体的报文摘要 |
| Content-Range | 实体主体的位置范围 | | Content-Range | 实体主体的位置范围 |
@ -253,56 +289,107 @@ TRACE 一般不会使用,并且它容易受到 XST 攻击Cross-Site Tracing
| Expires | 实体主体过期的日期时间 | | Expires | 实体主体过期的日期时间 |
| Last-Modified | 资源的最后修改日期时间 | | Last-Modified | 资源的最后修改日期时间 |
# 具体应用 # 五、具体应用
## Cookie ## Cookie
HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。 HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。
服务器发送的响应报文包含 Set-Cookie 字段,客户端得到响应报文后把 Cookie 内容保存到浏览器中。下次再发送请求时,从浏览器中读出 Cookie 值,在请求报文中包含 Cookie 字段这样服务器就知道客户端的状态信息了。Cookie 状态信息保存在客户端浏览器中,而不是服务器上 Cookie 是服务器发送给客户端的数据,该数据会被保存在浏览器中,并且在下一次发送请求时包含该数据。通过 Cookie 可以让服务器知道两个请求是否来自于同一个客户端,从而实现保持登录状态等功能
<div align="center"> <img src="../pics//ff17c103-750a-4bb8-9afa-576327023af9.png"/> </div><br> ### 1. 创建过程
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
```
### 2. Set-Cookie
| 属性 | 说明 | | 属性 | 说明 |
| -- | -- | | -- | -- |
| NAME=VALUE | 赋予 Cookie 的名称和其值(必需项) | | NAME=VALUE | 赋予 Cookie 的名称和其值(必需项) |
| expires=DATE | Cookie 的有效期(若不明确指定则默认为浏览器关闭前为止) | | expires=DATE | Cookie 的有效期(若不明确指定则默认为浏览器关闭前为止) |
| path=PATH | 将服务器上的文件目录作为 Cookie 的适用对象(若不指定则默认为文档所在的文件目录) | | path=PATH | 将服务器上的文件目录作为 Cookie 的适用对象(若不指定则默认为文档所在的文件目录) |
| domain= 域名 | 作为 Cookie 适用对象的域名(若不指定则默认为创建 Cookie 的服务器的域名) | | domain=域名 | 作为 Cookie 适用对象的域名(若不指定则默认为创建 Cookie 的服务器的域名) |
| Secure | 仅在 HTTPS 安全通信时才会发送 Cookie | | Secure | 仅在 HTTPS 安全通信时才会发送 Cookie |
| HttpOnly | 加以限制,使 Cookie 不能被 JavaScript 脚本访问 | | HttpOnly | 加以限制,使 Cookie 不能被 JavaScript 脚本访问 |
**Session 和 Cookie 区别** ### 3. Session 和 Cookie 区别
Session 是服务器用来跟踪用户的一种手段,每个 Session 都有一个唯一标识Session ID。当服务器创建了一个 Session 时,给客户端发送的响应报文就包含了 Set-Cookie 字段,其中有一个名为 sid 的键值对,这个键值对就是 Session ID。客户端收到后就把 Cookie 保存在浏览器中,并且之后发送的请求报文都包含 Session ID。HTTP 就是通过 Session 和 Cookie 这两种方式一起合作来实现跟踪用户状态的Session 用于服务器端Cookie 用于客户端。 Session 是服务器用来跟踪用户的一种手段,每个 Session 都有一个唯一标识Session ID。当服务器创建了一个 Session 时,给客户端发送的响应报文就包含了 Set-Cookie 字段,其中有一个名为 sid 的键值对,这个键值对就是 Session ID。客户端收到后就把 Cookie 保存在浏览器中,并且之后发送的请求报文都包含 Session ID。HTTP 就是通过 Session 和 Cookie 这两种方式一起合作来实现跟踪用户状态的Session 用于服务器端Cookie 用于客户端。
**浏览器禁用 Cookie 的情况** ### 4. 浏览器禁用 Cookie 的情况
会使用 URL 重写技术,在 URL 后面加上 sid=xxx 。 会使用 URL 重写技术,在 URL 后面加上 sid=xxx 。
**使用 Cookie 实现用户名和密码的自动填写** ### 5. 使用 Cookie 实现用户名和密码的自动填写
网站脚本会自动从 Cookie 读取用户名和密码,从而实现自动填写。 网站脚本会自动从保存在浏览器中的 Cookie 读取用户名和密码,从而实现自动填写。
## 缓存 ## 缓存
有两种缓存方法:让代理服务器进行缓存和让客户端浏览器进行缓存。 ### 1. 优点
Cache-Control 用于控制缓存的行为。Cache-Control: no-cache 有两种含义,如果是客户端向缓存服务器发送的请求报文中含有该指令,表示客户端不想要缓存的资源;如果是源服务器向缓存服务器发送的响应报文中含有该指令,表示缓存服务器不能对资源进行缓存。 1. 降低服务器的负担;
2. 提高响应速度(缓存资源比服务器上的资源离客户端更近)。
Expires 字段可以用于告知缓存服务器该资源什么时候会过期。当首部字段 Cache-Control 有指定 max-age 指令时,比起首部字段 Expires会优先处理 max-age 指令。 ### 2. 实现方法
1. 让代理服务器进行缓存;
2. 让客户端浏览器进行缓存。
### 3. Cache-Control 字段
HTTP 通过 Cache-Control 首部字段来控制缓存。
```html
Cache-Control: private, max-age=0, no-cache
```
### 4. no-cache 指令
该指令出现在请求报文的 Cache-Control 字段中,表示缓存服务器需要先向原服务器验证缓存资源是否过期;
该指令出现在响应报文的 Cache-Control 字段中,表示缓存服务器在进行缓存之前需要先验证缓存资源的有效性。
### 5. no-store 指令
该指令表示缓存服务器不能对请求或响应的任何一部分进行缓存。
no-cache 不表示不缓存而是缓存之前需要先进行验证no-store 才是不进行缓存。
### 6. 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 通信。
<div align="center"> <img src="../pics//c73a0b78-5f46-4d2d-a009-dab2a999b5d8.jpg"/> </div><br> <div align="center"> <img src="../pics//c73a0b78-5f46-4d2d-a009-dab2a999b5d8.jpg"/> </div><br>
持久连接需要使用 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。
管线化方式可以同时发送多个请求和响应,而不需要发送一个请求然后等待响应之后再发下一个请求。 **管线化方式** 可以同时发送多个请求和响应,而不需要发送一个请求然后等待响应之后再发下一个请求。
<div align="center"> <img src="../pics//6943e2af-5a70-4004-8bee-b33d60f39da3.jpg"/> </div><br> <div align="center"> <img src="../pics//6943e2af-5a70-4004-8bee-b33d60f39da3.jpg"/> </div><br>
@ -316,11 +403,24 @@ Expires 字段可以用于告知缓存服务器该资源什么时候会过期。
## 多部分对象集合 ## 多部分对象集合
一份报文主体内可含有多种类型的实体同时发送,每个部分之间用 boundary 字段定义的分隔符进行分隔每个部分都可以有首部字段。 一份报文主体内可含有多种类型的实体同时发送,每个部分之间用 boundary 字段定义的分隔符进行分隔每个部分都可以有首部字段。
例如,上传多个表单时可以使用如下方式: 例如,上传多个表单时可以使用如下方式:
<div align="center"> <img src="../pics//2279cc60-9714-4e0e-aac9-4c348e0c2165.png"/> </div><br> ```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--
```
## 范围请求 ## 范围请求
@ -328,6 +428,20 @@ Expires 字段可以用于告知缓存服务器该资源什么时候会过期。
在请求报文首部中添加 Range 字段,然后指定请求的范围,例如 Range:bytes=5001-10000。请求成功的话服务器发送 206 Partial Content 状态。 在请求报文首部中添加 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)
```
## 内容协商 ## 内容协商
通过内容协商返回最合适的内容,例如根据浏览器的默认语言选择返回中文界面还是英文界面。 通过内容协商返回最合适的内容,例如根据浏览器的默认语言选择返回中文界面还是英文界面。
@ -340,7 +454,7 @@ Expires 字段可以用于告知缓存服务器该资源什么时候会过期。
## 通信数据转发 ## 通信数据转发
**代理** ### 1. 代理
代理服务器接受客户端的请求,并且转发给其它服务器。 代理服务器接受客户端的请求,并且转发给其它服务器。
@ -350,19 +464,19 @@ Expires 字段可以用于告知缓存服务器该资源什么时候会过期。
<div align="center"> <img src="../pics//c07035c3-a9ba-4508-8e3c-d8ae4c6ee9ee.jpg"/> </div><br> <div align="center"> <img src="../pics//c07035c3-a9ba-4508-8e3c-d8ae4c6ee9ee.jpg"/> </div><br>
**网关** ### 2. 网关
与代理服务器不同的是,网关服务器会将 HTTP 转化为其它协议进行通信,从而请求其它非 HTTP 服务器的服务。 与代理服务器不同的是,网关服务器会将 HTTP 转化为其它协议进行通信,从而请求其它非 HTTP 服务器的服务。
<div align="center"> <img src="../pics//81375888-6be1-476f-9521-42eea3e3154f.jpg"/> </div><br> <div align="center"> <img src="../pics//81375888-6be1-476f-9521-42eea3e3154f.jpg"/> </div><br>
**隧道** ### 3. 隧道
使用 SSL 等加密手段,为客户端和服务器之间建立一条安全的通信线路。 使用 SSL 等加密手段,为客户端和服务器之间建立一条安全的通信线路。
<div align="center"> <img src="../pics//64b95403-d976-421a-8b45-bac89c0b5185.jpg"/> </div><br> <div align="center"> <img src="../pics//64b95403-d976-421a-8b45-bac89c0b5185.jpg"/> </div><br>
# HTTPs # 六、HTTPs
HTTP 有以下安全性问题: HTTP 有以下安全性问题:
@ -384,21 +498,23 @@ HTTPs 采用 **混合的加密机制** ,使用公开密钥加密用于传输
## 认证 ## 认证
通过使用 **证书** 来对通信方进行认证。证书中有公开密钥数据,如果可以验证公开密钥的确属于通信方的,那么就可以确定通信方是可靠的。 通过使用 **证书** 来对通信方进行认证。
数字证书认证机构CACertificate Authority可以对其颁发的公开密钥证书对其进行验证 数字证书认证机构CACertificate Authority是客户端与服务器双方都可信赖的第三方机构。服务器的运营人员向 CA 提出公开密钥的申请CA 在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公开密钥证书后绑定在一起
进行 HTTPs 通信时,服务器会把证书发送给客户端,客户端取得其中的公开密钥之后,就可以开始通信。 进行 HTTPs 通信时,服务器会把证书发送给客户端,客户端取得其中的公开密钥之后,先进行验证,如果验证通过,就可以开始通信。
除了上诉提到的服务器端证书之外,还有客户端证书,客户端证书的目的就是让服务器对客户端进行验证。客户端证书需要用户自行安装,只有在业务需要非常高的安全性时才使用客户端证书,例如网上银行。
使用 OpenSSL 这套开源程序,每个人都可以构建一套属于自己的认证机构,从而自己给自己颁发服务器证书。浏览器在访问该服务器时,会显示“无法确认连接安全性”或“该网站的安全证书存在问题”等警告消息。 使用 OpenSSL 这套开源程序,每个人都可以构建一套属于自己的认证机构,从而自己给自己颁发服务器证书。浏览器在访问该服务器时,会显示“无法确认连接安全性”或“该网站的安全证书存在问题”等警告消息。
客户端证书需要用户自行安装,只有在业务需要非常高的安全性时才使用客户端证书,例如网上银行。
## 完整性 ## 完整性
SSL 提供摘要功能来验证完整性。 SSL 提供摘要功能来验证完整性。
# HTTP/1.0 与 HTTP/1.1 的区别 # 七、各版本比较
## HTTP/1.0 与 HTTP/1.1 的区别
HTTP/1.1 新增了以下内容: HTTP/1.1 新增了以下内容:
@ -407,3 +523,26 @@ HTTP/1.1 新增了以下内容:
- 提供了虚拟主机的功能; - 提供了虚拟主机的功能;
- 多了一些缓存处理字段; - 多了一些缓存处理字段;
- 多了一些状态码; - 多了一些状态码;
## HTTP/1.1 与 HTTP/2.0 的区别
### 1. 多路复用
HTTP/2.0 使用多路复用技术,使用同一个 TCP 连接来处理多个请求。
### 2. 首部压缩
HTTP/1.1 的首部带有大量信息而且每次都要重复发送。HTTP/2.0 要求通讯双方各自缓存一份首部字段表,从而避免了重复传输。
### 3. 服务端推送
在客户端请求一个资源时,会把相关的资源一起发送给客户端,客户端就不需要再次发起请求了。例如客户端请求 index.html 页面,服务端就把 index.js 一起发给客户端。
### 4. 二进制格式
HTTP/1.1 的解析是基于文本的,而 HTTP/2.0 采用二进制格式。
# 参考资料
- [图解 HTTP](https://pan.baidu.com/s/1M0AHXqG9sP9Bxne6u0JK8A)
- [MDN : HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)

View File

@ -1,79 +1,65 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [内存模型](#内存模型) * [一、内存模型](#一内存模型)
* [1. 程序计数器](#1-程序计数器) * [程序计数器](#程序计数器)
* [2. Java 虚拟机栈](#2-java-虚拟机栈) * [Java 虚拟机栈](#java-虚拟机栈)
* [3. 本地方法栈](#3-本地方法栈) * [本地方法栈](#本地方法栈)
* [4. Java 堆](#4-java-堆) * [Java 堆](#java-堆)
* [5. 方法区](#5-方法区) * [方法区](#方法区)
* [6. 运行时常量池](#6-运行时常量池) * [运行时常量池](#运行时常量池)
* [7. 直接内存](#7-直接内存) * [直接内存](#直接内存)
* [垃圾收集](#垃圾收集) * [二、垃圾收集](#二垃圾收集)
* [1. 判断一个对象是否可回收](#1-判断一个对象是否可回收) * [判断一个对象是否可回收](#判断一个对象是否可回收)
* [1.1 引用计数](#11-引用计数) * [1. 引用计数](#1-引用计数)
* [1.2 可达性](#12-可达性) * [2. 可达性](#2-可达性)
* [1.3 引用类型](#13-引用类型) * [3. 引用类型](#3-引用类型)
* [1.3.1 强引用](#131-强引用) * [4. 方法区的回收](#4-方法区的回收)
* [1.3.2 软引用](#132-软引用) * [5. finalize()](#5-finalize)
* [1.3.3 弱引用](#133-弱引用) * [垃圾收集算法](#垃圾收集算法)
* [1.3.4 虚引用](#134-虚引用) * [1. 标记-清除算法](#1-标记-清除算法)
* [1.3 方法区的回收](#13-方法区的回收) * [2. 复制算法](#2-复制算法)
* [1.4 finalize()](#14-finalize) * [3. 标记-整理算法](#3-标记-整理算法)
* [2. 垃圾收集算法](#2-垃圾收集算法) * [4. 分代收集算法](#4-分代收集算法)
* [2.1 标记-清除算法](#21-标记-清除算法) * [垃圾收集器](#垃圾收集器)
* [2.2 复制算法](#22-复制算法) * [1. Serial 收集器](#1-serial-收集器)
* [2.3 标记-整理算法](#23-标记-整理算法) * [2. ParNew 收集器](#2-parnew-收集器)
* [2.4 分代收集算法](#24-分代收集算法) * [3. Parallel Scavenge 收集器](#3-parallel-scavenge-收集器)
* [3. 垃圾收集器](#3-垃圾收集器) * [4. Serial Old 收集器](#4-serial-old-收集器)
* [3.1 Serial 收集器](#31-serial-收集器) * [5. Parallel Old 收集器](#5-parallel-old-收集器)
* [3.2 ParNew 收集器](#32-parnew-收集器) * [6. CMS 收集器](#6-cms-收集器)
* [3.3 Parallel Scavenge 收集器](#33-parallel-scavenge-收集器) * [7. G1 收集器](#7-g1-收集器)
* [3.4 Serial Old 收集器](#34-serial-old-收集器) * [8. 七种垃圾收集器的比较](#8-七种垃圾收集器的比较)
* [3.5 Parallel Old 收集器](#35-parallel-old-收集器) * [内存分配与回收策略](#内存分配与回收策略)
* [3.6 CMS 收集器](#36-cms-收集器) * [Full GC 的触发条件](#full-gc-的触发条件)
* [3.7 G1 收集器](#37-g1-收集器) * [三、类加载机制](#三类加载机制)
* [3.8 七种垃圾收集器的比较](#38-七种垃圾收集器的比较) * [类的生命周期](#类的生命周期)
* [4. 内存分配与回收策略](#4-内存分配与回收策略) * [类初始化时机](#类初始化时机)
* [4.1 优先在 Eden 分配](#41-优先在-eden-分配) * [类加载过程](#类加载过程)
* [4.2 大对象直接进入老年代](#42-大对象直接进入老年代) * [1. 加载](#1-加载)
* [4.3 长期存活的对象进入老年代](#43-长期存活的对象进入老年代) * [2. 验证](#2-验证)
* [4.4 动态对象年龄判定](#44-动态对象年龄判定) * [3. 准备](#3-准备)
* [4.5 空间分配担保](#45-空间分配担保) * [4. 解析](#4-解析)
* [5. Full GC 的触发条件](#5-full-gc-的触发条件) * [5. 初始化](#5-初始化)
* [5.1 调用 System.gc()](#51-调用-systemgc) * [类加载器](#类加载器)
* [5.2 老年代空间不足](#52-老年代空间不足) * [1. 类与类加载器](#1-类与类加载器)
* [5.3 空间分配担保失败](#53-空间分配担保失败) * [2. 类加载器分类](#2-类加载器分类)
* [5.4 JDK 1.7 及以前的永久代空间不足](#54-jdk-17-及以前的永久代空间不足) * [3. 双亲委派模型](#3-双亲委派模型)
* [5.5 Concurrent Mode Failure](#55-concurrent-mode-failure) * [四、JVM 参数](#四jvm-参数)
* [类加载机制](#类加载机制)
* [1 类的生命周期](#1-类的生命周期)
* [2. 类初始化时机](#2-类初始化时机)
* [3. 类加载过程](#3-类加载过程)
* [3.1 加载](#31-加载)
* [3.2 验证](#32-验证)
* [3.3 准备](#33-准备)
* [3.4 解析](#34-解析)
* [3.5 初始化](#35-初始化)
* [4. 类加载器](#4-类加载器)
* [4.1 类与类加载器](#41-类与类加载器)
* [4.2 类加载器分类](#42-类加载器分类)
* [4.3 双亲委派模型](#43-双亲委派模型)
* [JVM 参数](#jvm-参数)
* [GC 优化配置](#gc-优化配置) * [GC 优化配置](#gc-优化配置)
* [GC 类型设置](#gc-类型设置) * [GC 类型设置](#gc-类型设置)
<!-- GFM-TOC --> <!-- GFM-TOC -->
# 内存模型 # 一、内存模型
<div align="center"> <img src="../pics//dc695f48-4189-4fc7-b950-ed25f6c80f82.jpg"/> </div><br> <div align="center"> <img src="../pics//dc695f48-4189-4fc7-b950-ed25f6c80f82.jpg"/> </div><br>
注:白色区域为线程私有的,蓝色区域为线程共享的。 注:白色区域为线程私有的,蓝色区域为线程共享的。
## 1. 程序计数器 ## 程序计数器
记录正在执行的虚拟机字节码指令的地址(如果正在执行的是 Native 方法则为空)。 记录正在执行的虚拟机字节码指令的地址(如果正在执行的是 Native 方法则为空)。
## 2. Java 虚拟机栈 ## Java 虚拟机栈
每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。 每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
@ -82,11 +68,11 @@
1. 当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常; 1. 当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常;
2. 栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。 2. 栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。
## 3. 本地方法栈 ## 本地方法栈
与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。 与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
## 4. Java 堆 ## Java 堆
所有对象实例都在这里分配内存。 所有对象实例都在这里分配内存。
@ -94,7 +80,7 @@
不需要连续内存,可以通过 -Xmx 和 -Xms 来控制动态扩展内存大小,如果动态扩展失败会抛出 OutOfMemoryError 异常。 不需要连续内存,可以通过 -Xmx 和 -Xms 来控制动态扩展内存大小,如果动态扩展失败会抛出 OutOfMemoryError 异常。
## 5. 方法区 ## 方法区
用于存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 用于存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
@ -102,7 +88,7 @@
对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载但是一般比较难实现HotSpot 虚拟机把它当成永久代来进行垃圾回收。 对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载但是一般比较难实现HotSpot 虚拟机把它当成永久代来进行垃圾回收。
## 6. 运行时常量池 ## 运行时常量池
运行时常量池是方法区的一部分。 运行时常量池是方法区的一部分。
@ -110,17 +96,17 @@
在运行期间也可以用过 String 类的 intern() 方法将新的常量放入该区域。 在运行期间也可以用过 String 类的 intern() 方法将新的常量放入该区域。
## 7. 直接内存 ## 直接内存
在 JDK 1.4 中新加入了 NIO 类引入了一种基于通道Channel与缓冲区Buffer的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。 在 JDK 1.4 中新加入了 NIO 类引入了一种基于通道Channel与缓冲区Buffer的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
# 垃圾收集 # 二、垃圾收集
程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后也会消失,因此不需要对这三个区域进行垃圾回收。垃圾回收主要是针对 Java 堆和方法区进行。 程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后也会消失,因此不需要对这三个区域进行垃圾回收。垃圾回收主要是针对 Java 堆和方法区进行。
## 1. 判断一个对象是否可回收 ## 判断一个对象是否可回收
### 1.1 引用计数 ### 1. 引用计数
给对象添加一个引用计数器,当对象增加一个引用时计数器加 1引用失效时计数器减 1。引用计数为 0 的对象可被回收。 给对象添加一个引用计数器,当对象增加一个引用时计数器加 1引用失效时计数器减 1。引用计数为 0 的对象可被回收。
@ -131,7 +117,7 @@ objA.instance = objB;
objB.instance = objA; objB.instance = objA;
``` ```
### 1.2 可达性 ### 2. 可达性
通过 GC Roots 作为起始点进行搜索,能够到达到的对象都是都是可用的,不可达的对象可被回收。 通过 GC Roots 作为起始点进行搜索,能够到达到的对象都是都是可用的,不可达的对象可被回收。
@ -142,13 +128,13 @@ GC Roots 一般包含以下内容:
3. 方法区中的常量引用的对象 3. 方法区中的常量引用的对象
4. 本地方法栈中引用的对象 4. 本地方法栈中引用的对象
### 1.3 引用类型 ### 3. 引用类型
无论是通过引用计算算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。 无论是通过引用计算算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。
Java 对引用的概念进行了扩充,引入四种强度不同的引用类型。 Java 对引用的概念进行了扩充,引入四种强度不同的引用类型。
#### 1.3.1 强引用 #### 3.1 强引用
只要强引用存在,垃圾回收器永远不会回收调掉被引用的对象。 只要强引用存在,垃圾回收器永远不会回收调掉被引用的对象。
@ -158,7 +144,7 @@ Java 对引用的概念进行了扩充,引入四种强度不同的引用类型
Object obj = new Object(); Object obj = new Object();
``` ```
#### 1.3.2 软引用 #### 3.2 软引用
用来描述一些还有用但是并非必需的对象。 用来描述一些还有用但是并非必需的对象。
@ -170,10 +156,10 @@ 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 弱引用 #### 3.3 弱引用
只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会被回收。 只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会被回收。
@ -184,7 +170,7 @@ Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj); WeakReference<Object> wf = new WeakReference<Object>(obj);
``` ```
#### 1.3.4 虚引用 #### 3.4 虚引用
又称为幽灵引用或者幻影引用.一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。 又称为幽灵引用或者幻影引用.一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
@ -195,7 +181,7 @@ Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj); PhantomReference<Object> pf = new PhantomReference<Object>(obj);
``` ```
### 1.3 方法区的回收 ### 4. 方法区的回收
因为方法区主要存放永久代对象,而永久代对象的回收率比新生代差很多,因此在方法区上进行回收性价比不高。 因为方法区主要存放永久代对象,而永久代对象的回收率比新生代差很多,因此在方法区上进行回收性价比不高。
@ -213,15 +199,15 @@ PhantomReference<Object> pf = new PhantomReference<Object>(obj);
在大量使用反射、动态代理、CGLib 等 ByteCode 框架、动态生成 JSP 以及 OSGo 这类频繁自定义 ClassLoader 的场景都需要虚拟机具备类卸载功能,以保证不会出现内存溢出。 在大量使用反射、动态代理、CGLib 等 ByteCode 框架、动态生成 JSP 以及 OSGo 这类频繁自定义 ClassLoader 的场景都需要虚拟机具备类卸载功能,以保证不会出现内存溢出。
### 1.4 finalize() ### 5. finalize()
finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。但是 try-finally 等方式可以做的更好,并且该方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。 finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。但是 try-finally 等方式可以做的更好,并且该方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。
当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能可能通过在该方法中让对象重新被引用,从而实现自救。 当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能通过在该方法中让对象重新被引用,从而实现自救。
## 2. 垃圾收集算法 ## 垃圾收集算法
### 2.1 标记-清除算法 ### 1. 标记-清除算法
<div align="center"> <img src="../pics//a4248c4b-6c1d-4fb8-a557-86da92d3a294.jpg"/> </div><br> <div align="center"> <img src="../pics//a4248c4b-6c1d-4fb8-a557-86da92d3a294.jpg"/> </div><br>
@ -234,7 +220,7 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
之后的算法都是基于该算法进行改进。 之后的算法都是基于该算法进行改进。
### 2.2 复制算法 ### 2. 复制算法
<div align="center"> <img src="../pics//e6b733ad-606d-4028-b3e8-83c3a73a3797.jpg"/> </div><br> <div align="center"> <img src="../pics//e6b733ad-606d-4028-b3e8-83c3a73a3797.jpg"/> </div><br>
@ -244,13 +230,13 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
现在的商业虚拟机都采用这种收集算法来回收新生代,但是并不是将内存划分为大小相等的两块,而是分为一块较大的 Eden 空间和两块较小的 Survior 空间,每次使用 Eden 空间和其中一块 Survivor。在回收时将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和 使用过的那一块 Survivor。HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1保证了内存的利用率达到 90 %。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,此时需要依赖于老年代进行分配担保,也就是借用老年代的空间。 现在的商业虚拟机都采用这种收集算法来回收新生代,但是并不是将内存划分为大小相等的两块,而是分为一块较大的 Eden 空间和两块较小的 Survior 空间,每次使用 Eden 空间和其中一块 Survivor。在回收时将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和 使用过的那一块 Survivor。HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1保证了内存的利用率达到 90 %。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,此时需要依赖于老年代进行分配担保,也就是借用老年代的空间。
### 2.3 标记-整理算法 ### 3. 标记-整理算法
<div align="center"> <img src="../pics//902b83ab-8054-4bd2-898f-9a4a0fe52830.jpg"/> </div><br> <div align="center"> <img src="../pics//902b83ab-8054-4bd2-898f-9a4a0fe52830.jpg"/> </div><br>
让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。 让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
### 2.4 分代收集算法 ### 4. 分代收集算法
现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。 现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。
@ -259,13 +245,13 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
1. 新生代使用:复制算法 1. 新生代使用:复制算法
2. 老年代使用:标记-清理 或者 标记-整理 算法。 2. 老年代使用:标记-清理 或者 标记-整理 算法。
## 3. 垃圾收集器 ## 垃圾收集器
<div align="center"> <img src="../pics//c625baa0-dde6-449e-93df-c3a67f2f430f.jpg"/> </div><br> <div align="center"> <img src="../pics//c625baa0-dde6-449e-93df-c3a67f2f430f.jpg"/> </div><br>
以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。 以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。
### 3.1 Serial 收集器 ### 1. Serial 收集器
<div align="center"> <img src="../pics//22fda4ae-4dd5-489d-ab10-9ebfdad22ae0.jpg"/> </div><br> <div align="center"> <img src="../pics//22fda4ae-4dd5-489d-ab10-9ebfdad22ae0.jpg"/> </div><br>
@ -275,7 +261,7 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
在 Client 应用场景中,分配给虚拟机管理的内存一般来说不会很大,该收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。 在 Client 应用场景中,分配给虚拟机管理的内存一般来说不会很大,该收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。
### 3.2 ParNew 收集器 ### 2. ParNew 收集器
<div align="center"> <img src="../pics//81538cd5-1bcf-4e31-86e5-e198df1e013b.jpg"/> </div><br> <div align="center"> <img src="../pics//81538cd5-1bcf-4e31-86e5-e198df1e013b.jpg"/> </div><br>
@ -285,7 +271,7 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
默认开始的线程数量与 CPU 数量相同,可以使用 -XX:ParallelGCThreads 参数来设置线程数。 默认开始的线程数量与 CPU 数量相同,可以使用 -XX:ParallelGCThreads 参数来设置线程数。
### 3.3 Parallel Scavenge 收集器 ### 3. Parallel Scavenge 收集器
是并行的多线程收集器。 是并行的多线程收集器。
@ -297,7 +283,7 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
还提供了一个参数 -XX:+UseAdaptiveSizePolicy这是一个开关参数打开参数后就不需要手工指定新生代的大小-Xmn、Eden 和 Survivor 区的比例(-XX:SurvivorRatio、晋升老年代对象年龄-XX:PretenureSizeThreshold等细节参数了虚拟机会根据当前系统的运行情况收集性能监控信息动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量这种方式称为 GC 自适应的调节策略GC Ergonomics。自适应调节策略也是它与 ParNew 收集器的一个重要区别。 还提供了一个参数 -XX:+UseAdaptiveSizePolicy这是一个开关参数打开参数后就不需要手工指定新生代的大小-Xmn、Eden 和 Survivor 区的比例(-XX:SurvivorRatio、晋升老年代对象年龄-XX:PretenureSizeThreshold等细节参数了虚拟机会根据当前系统的运行情况收集性能监控信息动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量这种方式称为 GC 自适应的调节策略GC Ergonomics。自适应调节策略也是它与 ParNew 收集器的一个重要区别。
### 3.4 Serial Old 收集器 ### 4. Serial Old 收集器
<div align="center"> <img src="../pics//08f32fd3-f736-4a67-81ca-295b2a7972f2.jpg"/> </div><br> <div align="center"> <img src="../pics//08f32fd3-f736-4a67-81ca-295b2a7972f2.jpg"/> </div><br>
@ -306,7 +292,7 @@ Serial Old 是 Serial 收集器的老年代版本,也是给 Client 模式下
1. 在 JDK 1.5 以及之前版本Parallel Old 诞生以前)中与 Parallel Scavenge 收集器搭配使用。 1. 在 JDK 1.5 以及之前版本Parallel Old 诞生以前)中与 Parallel Scavenge 收集器搭配使用。
2. 作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。 2. 作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。
### 3.5 Parallel Old 收集器 ### 5. Parallel Old 收集器
<div align="center"> <img src="../pics//278fe431-af88-4a95-a895-9c3b80117de3.jpg"/> </div><br> <div align="center"> <img src="../pics//278fe431-af88-4a95-a895-9c3b80117de3.jpg"/> </div><br>
@ -314,7 +300,7 @@ Serial Old 是 Serial 收集器的老年代版本,也是给 Client 模式下
在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。 在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。
### 3.6 CMS 收集器 ### 6. CMS 收集器
<div align="center"> <img src="../pics//62e77997-6957-4b68-8d12-bfd609bb2c68.jpg"/> </div><br> <div align="center"> <img src="../pics//62e77997-6957-4b68-8d12-bfd609bb2c68.jpg"/> </div><br>
@ -339,7 +325,7 @@ CMSConcurrent Mark Sweep从 Mark Sweep 可以知道它是基于标记-
3. 标记-清除算法导致的空间碎片,给大对象分配带来很大麻烦,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前出发一次 Full GC。 3. 标记-清除算法导致的空间碎片,给大对象分配带来很大麻烦,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前出发一次 Full GC。
### 3.7 G1 收集器 ### 7. G1 收集器
<div align="center"> <img src="../pics//f99ee771-c56f-47fb-9148-c0036695b5fe.jpg"/> </div><br> <div align="center"> <img src="../pics//f99ee771-c56f-47fb-9148-c0036695b5fe.jpg"/> </div><br>
@ -365,7 +351,7 @@ Region 不可能是孤立的,一个对象分配在某个 Region 中,可以
3. 最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。 3. 最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。
4. 筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿是时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region时间是用户可控制的而且停顿用户线程将大幅度提高收集效率。 4. 筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿是时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region时间是用户可控制的而且停顿用户线程将大幅度提高收集效率。
### 3.8 七种垃圾收集器的比较 ### 8. 七种垃圾收集器的比较
| 收集器 | 串行、并行 or 并发 | 新生代 / 老年代 | 算法 | 目标 | 适用场景 | | 收集器 | 串行、并行 or 并发 | 新生代 / 老年代 | 算法 | 目标 | 适用场景 |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
@ -377,11 +363,11 @@ Region 不可能是孤立的,一个对象分配在某个 Region 中,可以
| **CMS** | 并发 | 老年代 | 标记-清除 | 响应速度优先 | 集中在互联网站或 B/S 系统服务端上的 Java 应用 | | **CMS** | 并发 | 老年代 | 标记-清除 | 响应速度优先 | 集中在互联网站或 B/S 系统服务端上的 Java 应用 |
| **G1** | 并发 | both | 标记-整理 + 复制算法 | 响应速度优先 | 面向服务端应用,将来替换 CMS | | **G1** | 并发 | both | 标记-整理 + 复制算法 | 响应速度优先 | 面向服务端应用,将来替换 CMS |
## 4. 内存分配与回收策略 ## 内存分配与回收策略
对象的内存分配,也就是在堆上分配。主要分配在新生代的 Eden 区上,少数情况下也可能直接分配在老年代中。 对象的内存分配,也就是在堆上分配。主要分配在新生代的 Eden 区上,少数情况下也可能直接分配在老年代中。
### 4.1 优先在 Eden 分配 #### 1. 优先在 Eden 分配
大多数情况下,对象在新生代 Eden 区分配,当 Eden 区空间不够时,发起 Minor GC 大多数情况下,对象在新生代 Eden 区分配,当 Eden 区空间不够时,发起 Minor GC
@ -390,53 +376,53 @@ Region 不可能是孤立的,一个对象分配在某个 Region 中,可以
- Minor GC发生在新生代上因为新生代对象存活时间很短因此 Minor GC 会频繁执行,执行的速度一般也会比较快。 - Minor GC发生在新生代上因为新生代对象存活时间很短因此 Minor GC 会频繁执行,执行的速度一般也会比较快。
- Full GC发生在老年代上老年代对象和新生代的相反其存活时间长因此 Full GC 很少执行,而且执行速度会比 Minor GC 慢很多。 - Full GC发生在老年代上老年代对象和新生代的相反其存活时间长因此 Full GC 很少执行,而且执行速度会比 Minor GC 慢很多。
### 4.2 大对象直接进入老年代 #### 2. 大对象直接进入老年代
大对象是指需要连续内存空间的对象,最典型的大对象是那种很长的字符串以及数组。经常出现大对象会提前触发垃圾收集以获取足够的连续空间分配给大对象。 大对象是指需要连续内存空间的对象,最典型的大对象是那种很长的字符串以及数组。经常出现大对象会提前触发垃圾收集以获取足够的连续空间分配给大对象。
提供 -XX:PretenureSizeThreshold 参数,大于此值的对象直接在老年代分配,避免在 Eden 区和 Survivor 区之间的大量内存复制; 提供 -XX:PretenureSizeThreshold 参数,大于此值的对象直接在老年代分配,避免在 Eden 区和 Survivor 区之间的大量内存复制;
### 4.3 长期存活的对象进入老年代 #### 3. 长期存活的对象进入老年代
JVM 为对象定义年龄计数器,经过 Minor GC 依然存活,并且能被 Survivor 区容纳的,移被移到 Survivor 区,年龄就增加 1 岁,增加到一定年龄则移动到老年代中(默认 15 岁,通过 -XX:MaxTenuringThreshold 设置); JVM 为对象定义年龄计数器,经过 Minor GC 依然存活,并且能被 Survivor 区容纳的,移被移到 Survivor 区,年龄就增加 1 岁,增加到一定年龄则移动到老年代中(默认 15 岁,通过 -XX:MaxTenuringThreshold 设置);
### 4.4 动态对象年龄判定 #### 4. 动态对象年龄判定
JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 区中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无序等待 MaxTenuringThreshold 中要求的年龄。 JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 区中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无序等待 MaxTenuringThreshold 中要求的年龄。
### 4.5 空间分配担保 #### 5. 空间分配担保
在发生 Minor GC 之前JVM 先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC 可以确认是安全的;如果不成立的话 JVM 会查看 HandlePromotionFailure 设置值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC尽管这次 Minor GC 是有风险的;如果小于,或者 HandlePromotionFailure 设置不允许冒险,那这时也要改为进行一次 Full GC。 在发生 Minor GC 之前JVM 先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC 可以确认是安全的;如果不成立的话 JVM 会查看 HandlePromotionFailure 设置值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC尽管这次 Minor GC 是有风险的;如果小于,或者 HandlePromotionFailure 设置不允许冒险,那这时也要改为进行一次 Full GC。
## 5. Full GC 的触发条件 ## Full GC 的触发条件
对于 Minor GC其触发条件非常简单当 Eden 区空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件: 对于 Minor GC其触发条件非常简单当 Eden 区空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件:
### 5.1 调用 System.gc() #### 1. 调用 System.gc()
此方法的调用是建议 JVM 进行 Full GC虽然只是建议而非一定但很多情况下它会触发 Full GC从而增加 Full GC 的频率,也即增加了间歇性停顿的次数。因此强烈建议能不使用此方法就不要使用,让虚拟机自己去管理它的内存。可通过 -XX:+ DisableExplicitGC 来禁止 RMI 调用 System.gc()。 此方法的调用是建议 JVM 进行 Full GC虽然只是建议而非一定但很多情况下它会触发 Full GC从而增加 Full GC 的频率,也即增加了间歇性停顿的次数。因此强烈建议能不使用此方法就不要使用,让虚拟机自己去管理它的内存。可通过 -XX:+ DisableExplicitGC 来禁止 RMI 调用 System.gc()。
### 5.2 老年代空间不足 #### 2. 老年代空间不足
老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等,当执行 Full GC 后空间仍然不足,则抛出 Java.lang.OutOfMemoryError。为避免以上原因引起的 Full GC调优时应尽量做到让对象在 Minor GC 阶段被回收、让对象在新生代多存活一段时间以及不要创建过大的对象及数组。 老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等,当执行 Full GC 后空间仍然不足,则抛出 Java.lang.OutOfMemoryError。为避免以上原因引起的 Full GC调优时应尽量做到让对象在 Minor GC 阶段被回收、让对象在新生代多存活一段时间以及不要创建过大的对象及数组。
### 5.3 空间分配担保失败 #### 3. 空间分配担保失败
使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果出现了 HandlePromotionFailure 担保失败,则会触发 Full GC。 使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果出现了 HandlePromotionFailure 担保失败,则会触发 Full GC。
### 5.4 JDK 1.7 及以前的永久代空间不足 #### 4. JDK 1.7 及以前的永久代空间不足
在 JDK 1.7 及以前HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 class 的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么 JVM 会抛出 java.lang.OutOfMemoryError为避免以上原因引起的 Full GC可采用的方法为增大永久代空间或转为使用 CMS GC。 在 JDK 1.7 及以前HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 class 的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么 JVM 会抛出 java.lang.OutOfMemoryError为避免以上原因引起的 Full GC可采用的方法为增大永久代空间或转为使用 CMS GC。
### 5.5 Concurrent Mode Failure #### 5. Concurrent Mode Failure
执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(有时候“空间不足”是 CMS GC 时当前的浮动垃圾过多导致暂时性的空间不足触发 Full GC便会报 Concurrent Mode Failure 错误,并触发 Full GC。 执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(有时候“空间不足”是 CMS GC 时当前的浮动垃圾过多导致暂时性的空间不足触发 Full GC便会报 Concurrent Mode Failure 错误,并触发 Full GC。
# 类加载机制 # 三、类加载机制
类是在运行期间动态加载的。 类是在运行期间动态加载的。
## 1 类的生命周期 ## 类的生命周期
<div align="center"> <img src="../pics//32b8374a-e822-4720-af0b-c0f485095ea2.jpg"/> </div><br> <div align="center"> <img src="../pics//32b8374a-e822-4720-af0b-c0f485095ea2.jpg"/> </div><br>
@ -452,7 +438,7 @@ JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才
其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。 其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。
## 2. 类初始化时机 ## 类初始化时机
虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随着发生): 虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随着发生):
@ -486,11 +472,11 @@ SuperClass[] sca = new SuperClass[10];
System.out.println(ConstClass.HELLOWORLD); System.out.println(ConstClass.HELLOWORLD);
``` ```
## 3. 类加载过程 ## 类加载过程
包含了加载、验证、准备、解析和初始化这 5 个阶段。 包含了加载、验证、准备、解析和初始化这 5 个阶段。
### 3.1 加载 ### 1. 加载
加载是类加载的一个阶段,注意不要混淆。 加载是类加载的一个阶段,注意不要混淆。
@ -509,29 +495,29 @@ System.out.println(ConstClass.HELLOWORLD);
- 从数据库读取,这种场景相对少见,例如有些中间件服务器(如 SAP Netweaver可以选择把程序安装到数据库中来完成程序代码在集群间的分发。 - 从数据库读取,这种场景相对少见,例如有些中间件服务器(如 SAP Netweaver可以选择把程序安装到数据库中来完成程序代码在集群间的分发。
... ...
### 3.2 验证 ### 2. 验证
确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。 确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
主要有以下 4 个阶段: 主要有以下 4 个阶段:
**1. 文件格式验证** #### 1. 文件格式验证
验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。 验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。
**2. 元数据验证** #### 2. 元数据验证
对字节码描述的信息进行语义分析,以保证其描述的信息符合 Java 语言规范的要求。 对字节码描述的信息进行语义分析,以保证其描述的信息符合 Java 语言规范的要求。
**3. 字节码验证** #### 3. 字节码验证
通过数据流和控制流分析,确保程序语义是合法、符合逻辑的。 通过数据流和控制流分析,确保程序语义是合法、符合逻辑的。
**4. 符号引用验证** #### 4. 符号引用验证
发生在虚拟机将符号引用转换为直接引用的时候,对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。 发生在虚拟机将符号引用转换为直接引用的时候,对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。
### 3.3 准备 ### 3. 准备
类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。 类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。
@ -549,11 +535,11 @@ public static int value = 123;
public static final int value = 123; public static final int value = 123;
``` ```
### 3.4 解析 ### 4. 解析
将常量池的符号引用替换为直接引用的过程。 将常量池的符号引用替换为直接引用的过程。
### 3.5 初始化 ### 5. 初始化
初始化阶段才真正开始执行类中的定义的 Java 程序代码。初始化阶段即虚拟机执行类构造器 &lt;clinit>() 方法的过程。 初始化阶段才真正开始执行类中的定义的 Java 程序代码。初始化阶段即虚拟机执行类构造器 &lt;clinit>() 方法的过程。
@ -600,15 +586,15 @@ public static void main(String[] args) {
- 虚拟机会保证一个类的 &lt;clinit>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 &lt;clinit>() 方法,其它线程都会阻塞等待,直到活动线程执行 &lt;clinit>() 方法完毕。如果在一个类的 &lt;clinit>() 方法中有耗时的操作,就可能造成多个进程阻塞,在实际过程中此种阻塞很隐蔽。 - 虚拟机会保证一个类的 &lt;clinit>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 &lt;clinit>() 方法,其它线程都会阻塞等待,直到活动线程执行 &lt;clinit>() 方法完毕。如果在一个类的 &lt;clinit>() 方法中有耗时的操作,就可能造成多个进程阻塞,在实际过程中此种阻塞很隐蔽。
## 4. 类加载器 ## 类加载器
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流 ( 即字节码 )”这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。 虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流 ( 即字节码 )”这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。
### 4.1 类与类加载器 ### 1. 类与类加载器
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。通俗而言:比较两个类是否“相等”(这里所指的“相等”,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果,也包括使用 instanceof() 关键字做对象所属关系判定等情况),只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。 对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。通俗而言:比较两个类是否“相等”(这里所指的“相等”,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果,也包括使用 instanceof() 关键字做对象所属关系判定等情况),只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
### 4.2 类加载器分类 ### 2. 类加载器分类
从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器: 从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器:
@ -622,21 +608,21 @@ public static void main(String[] args) {
- 应用程序类加载器Application ClassLoader 这个类加载器是由 AppClassLoadersun.misc.Launcher$AppClassLoader实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值因此一般称为系统类加载器。它负责加载用户类路径ClassPath上所指定的类库开发者可以直接使用这个类加载器如果应用程序中没有自定义过自己的类加载器一般情况下这个就是程序中默认的类加载器。 - 应用程序类加载器Application ClassLoader 这个类加载器是由 AppClassLoadersun.misc.Launcher$AppClassLoader实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值因此一般称为系统类加载器。它负责加载用户类路径ClassPath上所指定的类库开发者可以直接使用这个类加载器如果应用程序中没有自定义过自己的类加载器一般情况下这个就是程序中默认的类加载器。
### 4.3 双亲委派模型 ### 3. 双亲委派模型
应用程序都是由三种类加载器相互配合进行加载的如果有必要还可以加入自己定义的类加载器。下图展示的类加载器之间的层次关系称为类加载器的双亲委派模型Parents Delegation Model。该模型要求除了顶层的启动类加载器外其余的类加载器都应有自己的父类加载器这里类加载器之间的父子关系一般通过组合Composition关系来实现而不是通过继承Inheritance的关系实现。 应用程序都是由三种类加载器相互配合进行加载的如果有必要还可以加入自己定义的类加载器。下图展示的类加载器之间的层次关系称为类加载器的双亲委派模型Parents Delegation Model。该模型要求除了顶层的启动类加载器外其余的类加载器都应有自己的父类加载器这里类加载器之间的父子关系一般通过组合Composition关系来实现而不是通过继承Inheritance的关系实现。
<div align="center"> <img src="../pics//2cdc3ce2-fa82-4c22-baaa-000c07d10473.jpg"/> </div><br> <div align="center"> <img src="../pics//2cdc3ce2-fa82-4c22-baaa-000c07d10473.jpg"/> </div><br>
**工作过程** #### 3.1 工作过程
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载,而是把这个请求委派给父类加载器,每一个层次的加载器都是如此,依次递归。因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成此加载请求(它搜索范围中没有找到所需类)时,子加载器才会尝试自己加载。 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载,而是把这个请求委派给父类加载器,每一个层次的加载器都是如此,依次递归。因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成此加载请求(它搜索范围中没有找到所需类)时,子加载器才会尝试自己加载。
**好处** #### 3.2 好处
使用双亲委派模型来组织类加载器之间的关系,使得 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类 java.lang.Object它存放在 rt.jar 中,无论哪个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此 Object 类在程序的各种类加载器环境中都是同一个类。相反如果没有双亲委派模型由各个类加载器自行加载的话如果用户编写了一个称为java.lang.Object 的类,并放在程序的 ClassPath 中,那系统中将会出现多个不同的 Object 类,程序将变得一片混乱。如果开发者尝试编写一个与 rt.jar 类库中已有类重名的 Java 类,将会发现可以正常编译,但是永远无法被加载运行。 使用双亲委派模型来组织类加载器之间的关系,使得 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类 java.lang.Object它存放在 rt.jar 中,无论哪个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此 Object 类在程序的各种类加载器环境中都是同一个类。相反如果没有双亲委派模型由各个类加载器自行加载的话如果用户编写了一个称为java.lang.Object 的类,并放在程序的 ClassPath 中,那系统中将会出现多个不同的 Object 类,程序将变得一片混乱。如果开发者尝试编写一个与 rt.jar 类库中已有类重名的 Java 类,将会发现可以正常编译,但是永远无法被加载运行。
**实现** #### 3.3 实现
```java ```java
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
@ -663,7 +649,7 @@ protected synchronized Class<?> loadClass(String name, boolean resolve) throws C
} }
``` ```
# JVM 参数 # 四、JVM 参数
## GC 优化配置 ## GC 优化配置

View File

@ -1,38 +1,27 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [概览](#概览) * [一、概览](#一概览)
* [磁盘操作](#磁盘操作) * [二、磁盘操作](#二磁盘操作)
* [字节操作](#字节操作) * [三、字节操作](#三字节操作)
* [字符操作](#字符操作) * [四、字符操作](#四字符操作)
* [对象操作](#对象操作) * [五、对象操作](#五对象操作)
* [网络操作](#网络操作) * [六、网络操作](#六网络操作)
* [1. InetAddress](#1-inetaddress) * [InetAddress](#inetaddress)
* [2. URL](#2-url) * [URL](#url)
* [3. Sockets](#3-sockets) * [Sockets](#sockets)
* [4. Datagram](#4-datagram) * [Datagram](#datagram)
* [NIO](#nio) * [七、NIO](#七nio)
* [1. 流与块](#1-流与块) * [流与块](#流与块)
* [2. 通道与缓冲区](#2-通道与缓冲区) * [通道与缓冲区](#通道与缓冲区)
* [2.1 通道](#21-通道) * [缓冲区状态变量](#缓冲区状态变量)
* [2.2 缓冲区](#22-缓冲区) * [文件 NIO 实例](#文件-nio-实例)
* [3. 缓冲区状态变量](#3-缓冲区状态变量) * [阻塞与非阻塞](#阻塞与非阻塞)
* [4. 文件 NIO 实例](#4-文件-nio-实例) * [套接字 NIO 实例](#套接字-nio-实例)
* [5. 阻塞与非阻塞](#5-阻塞与非阻塞) * [内存映射文件](#内存映射文件)
* [5.1 阻塞式 I/O](#51-阻塞式-io) * [八、参考资料](#八参考资料)
* [5.2 非阻塞式 I/O](#52-非阻塞式-io)
* [6. 套接字 NIO 实例](#6-套接字-nio-实例)
* [6.1 ServerSocketChannel](#61-serversocketchannel)
* [6.2 Selectors](#62-selectors)
* [6.3 主循环](#63-主循环)
* [6.4 监听新连接](#64-监听新连接)
* [6.5 接受新的连接](#65-接受新的连接)
* [6.6 删除处理过的 SelectionKey](#66-删除处理过的-selectionkey)
* [6.7 传入的 I/O](#67-传入的-io)
* [7. 内存映射文件](#7-内存映射文件)
* [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
# 概览 # 一、概览
Java 的 I/O 大概可以分成以下几类 Java 的 I/O 大概可以分成以下几类
@ -43,11 +32,11 @@ Java 的 I/O 大概可以分成以下几类
5. 网络操作Socket 5. 网络操作Socket
6. 新的输入/输出NIO 6. 新的输入/输出NIO
# 磁盘操作 # 二、磁盘操作
File 类可以用于表示文件和目录,但是它只用于表示文件的信息,而不表示文件的内容。 File 类可以用于表示文件和目录,但是它只用于表示文件的信息,而不表示文件的内容。
# 字节操作 # 三、字节操作
<div align="center"> <img src="../pics//8143787f-12eb-46ea-9bc3-c66d22d35285.jpg"/> </div><br> <div align="center"> <img src="../pics//8143787f-12eb-46ea-9bc3-c66d22d35285.jpg"/> </div><br>
@ -72,7 +61,7 @@ while((bytes = in.read(buf, 0 , buf.length)) != -1) {
} }
``` ```
# 字符操作 # 四、字符操作
不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符,所以 I/O 操作的都是字节而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。 不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符,所以 I/O 操作的都是字节而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。
@ -89,7 +78,7 @@ GBK 编码中,中文占 2 个字节,英文占 1 个字节UTF-8 编码中
如果编码和解码过程使用不同的编码方式那么就出现了乱码。 如果编码和解码过程使用不同的编码方式那么就出现了乱码。
# 对象操作 # 五、对象操作
序列化就是将一个对象转换成字节序列,方便存储和传输。 序列化就是将一个对象转换成字节序列,方便存储和传输。
@ -107,7 +96,7 @@ transient 关键字可以使一些属性不会被序列化。
private transient Object[] elementData; private transient Object[] elementData;
``` ```
# 网络操作 # 六、网络操作
Java 中的网络支持: Java 中的网络支持:
@ -116,7 +105,7 @@ Java 中的网络支持:
3. Sockets使用 TCP 协议实现网络通信; 3. Sockets使用 TCP 协议实现网络通信;
4. Datagram使用 UDP 协议实现网络通信。 4. Datagram使用 UDP 协议实现网络通信。
## 1. InetAddress ## InetAddress
没有公有构造函数,只能通过静态方法来创建实例。 没有公有构造函数,只能通过静态方法来创建实例。
@ -125,7 +114,7 @@ InetAddress.getByName(String host);
InetAddress.getByAddress(byte[] addr); InetAddress.getByAddress(byte[] addr);
``` ```
## 2. URL ## URL
可以直接从 URL 中读取字节流数据 可以直接从 URL 中读取字节流数据
@ -144,7 +133,7 @@ isr.close();
is.close(); is.close();
``` ```
## 3. Sockets ## Sockets
- ServerSocket服务器端类 - ServerSocket服务器端类
- Socket客户端类 - Socket客户端类
@ -152,16 +141,16 @@ is.close();
<div align="center"> <img src="../pics//fa4101d7-19ce-4a69-a84f-20bbe64320e5.jpg"/> </div><br> <div align="center"> <img src="../pics//fa4101d7-19ce-4a69-a84f-20bbe64320e5.jpg"/> </div><br>
## 4. Datagram ## Datagram
- DatagramPacket数据包类 - DatagramPacket数据包类
- DatagramSocket通信类 - DatagramSocket通信类
# NIO # 七、NIO
新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的。NIO 弥补了原来的 I/O 的不足,它在标准 Java 代码中提供了高速的、面向块的 I/O。 新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的。NIO 弥补了原来的 I/O 的不足,它在标准 Java 代码中提供了高速的、面向块的 I/O。
## 1. 流与块 ## 流与块
I/O 与 NIO 最重要的区别是数据打包和传输的方式I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。 I/O 与 NIO 最重要的区别是数据打包和传输的方式I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。
@ -171,9 +160,9 @@ I/O 与 NIO 最重要的区别是数据打包和传输的方式I/O 以流的
I/O 包和 NIO 已经很好地集成了java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如, java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。 I/O 包和 NIO 已经很好地集成了java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如, java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。
## 2. 通道与缓冲区 ## 通道与缓冲区
### 2.1 通道 ### 1. 通道
通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。 通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。
@ -186,7 +175,7 @@ I/O 包和 NIO 已经很好地集成了java.io.\* 已经以 NIO 为基础重
- SocketChannel通过 TCP 读写网络中数据; - SocketChannel通过 TCP 读写网络中数据;
- ServerSocketChannel可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。 - ServerSocketChannel可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。
### 2.2 缓冲区 ### 2. 缓冲区
发送给一个通道的所有对象都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。 发送给一个通道的所有对象都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。
@ -202,7 +191,7 @@ I/O 包和 NIO 已经很好地集成了java.io.\* 已经以 NIO 为基础重
- FloatBuffer - FloatBuffer
- DoubleBuffer - DoubleBuffer
## 3. 缓冲区状态变量 ## 缓冲区状态变量
- capacity最大容量 - capacity最大容量
- position当前已经读写的字节数 - position当前已经读写的字节数
@ -225,7 +214,7 @@ I/O 包和 NIO 已经很好地集成了java.io.\* 已经以 NIO 为基础重
5. 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。 5. 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。
<div align="center"> <img src="../pics//67bf5487-c45d-49b6-b9c0-a058d8c68902.png"/> </div><br> <div align="center"> <img src="../pics//67bf5487-c45d-49b6-b9c0-a058d8c68902.png"/> </div><br>
## 4. 文件 NIO 实例 ## 文件 NIO 实例
1\. 为要读取的文件创建 FileInputStream之后通过 FileInputStream 获取输入 FileChannel 1\. 为要读取的文件创建 FileInputStream之后通过 FileInputStream 获取输入 FileChannel
@ -274,11 +263,11 @@ foc.write(buffer);
buffer.clear(); buffer.clear();
``` ```
## 5. 阻塞与非阻塞 ## 阻塞与非阻塞
应当注意FileChannel 不能切换到非阻塞模式,套接字 Channel 可以。 应当注意FileChannel 不能切换到非阻塞模式,套接字 Channel 可以。
### 5.1 阻塞式 I/O ### 1. 阻塞式 I/O
阻塞式 I/O 在调用 InputStream.read() 方法时会一直等到数据到来时(或超时)才会返回,在调用 ServerSocket.accept() 方法时,也会一直阻塞到有客户端连接才会返回。 阻塞式 I/O 在调用 InputStream.read() 方法时会一直等到数据到来时(或超时)才会返回,在调用 ServerSocket.accept() 方法时,也会一直阻塞到有客户端连接才会返回。
@ -286,7 +275,7 @@ buffer.clear();
<div align="center"> <img src="../pics//edc23f99-c46c-4200-b64e-07516828720d.jpg"/> </div><br> <div align="center"> <img src="../pics//edc23f99-c46c-4200-b64e-07516828720d.jpg"/> </div><br>
### 5.2 非阻塞式 I/O ### 2. 非阻塞式 I/O
由一个专门的线程来处理所有的 I/O 事件,并负责分发。 由一个专门的线程来处理所有的 I/O 事件,并负责分发。
@ -296,9 +285,9 @@ buffer.clear();
<div align="center"> <img src="../pics//7fcb2fb0-2cd9-4396-bc2d-282becf963c3.jpg"/> </div><br> <div align="center"> <img src="../pics//7fcb2fb0-2cd9-4396-bc2d-282becf963c3.jpg"/> </div><br>
## 6. 套接字 NIO 实例 ## 套接字 NIO 实例
### 6.1 ServerSocketChannel ### 1. ServerSocketChannel
每一个监听端口都需要有一个 ServerSocketChannel 用来监听连接。 每一个监听端口都需要有一个 ServerSocketChannel 用来监听连接。
@ -311,7 +300,7 @@ InetSocketAddress address = new InetSocketAddress(ports[i]);
ss.bind(address); // 绑定端口号 ss.bind(address); // 绑定端口号
``` ```
### 6.2 Selectors ### 2. Selectors
异步 I/O 通过 Selector 注册对特定 I/O 事件的兴趣 ― 可读的数据的到达、新的套接字连接等等,在发生这样的事件时,系统将会发送通知。 异步 I/O 通过 Selector 注册对特定 I/O 事件的兴趣 ― 可读的数据的到达、新的套接字连接等等,在发生这样的事件时,系统将会发送通知。
@ -324,7 +313,7 @@ Selector selector = Selector.open();
SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT); SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);
``` ```
### 6.3 主循环 ### 3. 主循环
首先,我们调用 Selector 的 select() 方法。这个方法会阻塞直到至少有一个已注册的事件发生。当一个或者更多的事件发生时select() 方法将返回所发生的事件的数量。 首先,我们调用 Selector 的 select() 方法。这个方法会阻塞直到至少有一个已注册的事件发生。当一个或者更多的事件发生时select() 方法将返回所发生的事件的数量。
@ -344,7 +333,7 @@ while (it.hasNext()) {
} }
``` ```
### 6.4 监听新连接 ### 4. 监听新连接
程序执行到这里,我们仅注册了 ServerSocketChannel并且仅注册它们“接收”事件。为确认这一点我们对 SelectionKey 调用 readyOps() 方法,并检查发生了什么类型的事件: 程序执行到这里,我们仅注册了 ServerSocketChannel并且仅注册它们“接收”事件。为确认这一点我们对 SelectionKey 调用 readyOps() 方法,并检查发生了什么类型的事件:
@ -358,7 +347,7 @@ if ((key.readyOps() & SelectionKey.OP_ACCEPT)
可以肯定地说readOps() 方法告诉我们该事件是新的连接。 可以肯定地说readOps() 方法告诉我们该事件是新的连接。
### 6.5 接受新的连接 ### 5. 接受新的连接
因为我们知道这个服务器套接字上有一个传入连接在等待,所以可以安全地接受它;也就是说,不用担心 accept() 操作会阻塞: 因为我们知道这个服务器套接字上有一个传入连接在等待,所以可以安全地接受它;也就是说,不用担心 accept() 操作会阻塞:
@ -376,7 +365,7 @@ SelectionKey newKey = sc.register(selector, SelectionKey.OP_READ);
注意我们使用 register() 的 OP_READ 参数,将 SocketChannel 注册用于读取而不是接受新连接。 注意我们使用 register() 的 OP_READ 参数,将 SocketChannel 注册用于读取而不是接受新连接。
### 6.6 删除处理过的 SelectionKey ### 6. 删除处理过的 SelectionKey
在处理 SelectionKey 之后,我们几乎可以返回主循环了。但是我们必须首先将处理过的 SelectionKey 从选定的键集合中删除。如果我们没有删除处理过的键,那么它仍然会在主集合中以一个激活的键出现,这会导致我们尝试再次处理它。我们调用迭代器的 remove() 方法来删除处理过的 SelectionKey 在处理 SelectionKey 之后,我们几乎可以返回主循环了。但是我们必须首先将处理过的 SelectionKey 从选定的键集合中删除。如果我们没有删除处理过的键,那么它仍然会在主集合中以一个激活的键出现,这会导致我们尝试再次处理它。我们调用迭代器的 remove() 方法来删除处理过的 SelectionKey
@ -386,7 +375,7 @@ it.remove();
现在我们可以返回主循环并接受从一个套接字中传入的数据 (或者一个传入的 I/O 事件) 了。 现在我们可以返回主循环并接受从一个套接字中传入的数据 (或者一个传入的 I/O 事件) 了。
### 6.7 传入的 I/O ### 7. 传入的 I/O
当来自一个套接字的数据到达时,它会触发一个 I/O 事件。这会导致在主循环中调用 Selector.select(),并返回一个或者多个 I/O 事件。这一次, SelectionKey 将被标记为 OP_READ 事件,如下所示: 当来自一个套接字的数据到达时,它会触发一个 I/O 事件。这会导致在主循环中调用 Selector.select(),并返回一个或者多个 I/O 事件。这一次, SelectionKey 将被标记为 OP_READ 事件,如下所示:
@ -399,7 +388,7 @@ it.remove();
} }
``` ```
## 7. 内存映射文件 ## 内存映射文件
内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。 内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。
@ -415,7 +404,7 @@ it.remove();
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024); MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
``` ```
# 参考资料 # 八、参考资料
- Eckel B, 埃克尔, 昊鹏, 等. Java 编程思想 [M]. 机械工业出版社, 2002. - Eckel B, 埃克尔, 昊鹏, 等. Java 编程思想 [M]. 机械工业出版社, 2002.
- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html) - [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html)

View File

@ -1,74 +1,74 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [关键字](#关键字) * [一、关键字](#关键字)
* [1. final](#1-final) * [final](#final)
* [2. static](#2-static) * [static](#static)
* [Object 通用方法](#object-通用方法) * [二、Object 通用方法](#object-通用方法)
* [1. 概览](#1-概览) * [概览](#概览)
* [2. clone()](#2-clone) * [clone()](#clone)
* [3. equals()](#3-equals) * [equals()](#equals)
* [继承](#继承) * [四、继承](#继承)
* [1. 访问权限](#1-访问权限) * [访问权限](#访问权限)
* [2. 抽象类与接口](#2-抽象类与接口) * [抽象类与接口](#抽象类与接口)
* [3. super](#3-super) * [super](#super)
* [4. 重载与重写](#4-重载与重写) * [重载与重写](#重载与重写)
* [String](#string) * [五、String](#string)
* [1. String, StringBuffer and StringBuilder](#1-string,-stringbuffer-and-stringbuilder) * [String, StringBuffer and StringBuilder](#string,-stringbuffer-and-stringbuilder)
* [2. String 不可变的原因](#2-string-不可变的原因) * [String 不可变的原因](#string-不可变的原因)
* [3. String.intern()](#3-stringintern) * [String.intern()](#stringintern)
* [基本类型与运算](#基本类型与运算) * [六、基本类型与运算](#基本类型与运算)
* [1. 包装类型](#1-包装类型) * [包装类型](#包装类型)
* [2. switch](#2-switch) * [switch](#switch)
* [反射](#反射) * [七、反射](#反射)
* [异常](#异常) * [八、异常](#异常)
* [泛型](#泛型) * [九、泛型](#泛型)
* [注解](#注解) * [十、注解](#注解)
* [特性](#特性) * [十一、特性](#十一特性)
* [1. 三大特性](#1-三大特性) * [面向对象三大特性](#面向对象三大特性)
* [2. Java 各版本的新特性](#2-java-各版本的新特性) * [Java 各版本的新特性](#java-各版本的新特性)
* [3. Java 与 C++ 的区别](#3-java-与-c++-的区别) * [Java 与 C++ 的区别](#java-与-c++-的区别)
* [4. JRE or JDK](#4-jre-or-jdk) * [JRE or JDK](#jre-or-jdk)
<!-- GFM-TOC --> <!-- GFM-TOC -->
# 关键字 # 一、关键字
## 1. final ## final
**数据** ### 1. 数据
声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。 声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。
- 对于基本类型final 使数值不变; - 对于基本类型final 使数值不变;
- 对于引用类型final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。 - 对于引用类型final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
**方法** ### 2. 方法
声明方法不能被子类覆盖。 声明方法不能被子类覆盖。
private 方法隐式地被指定为 final如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是覆盖基类方法,而是重载了。 private 方法隐式地被指定为 final如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是覆盖基类方法,而是重载了。
**类** ### 3. 类
声明类不允许被继承。 声明类不允许被继承。
## 2. static ## static
**静态变量** ### 1. 静态变量
静态变量在内存中只存在一份,只在类第一次实例化时初始化一次。 静态变量在内存中只存在一份,只在类第一次实例化时初始化一次。
- 静态变量: 类所有的实例都共享静态变量,可以直接通过类名来访问它; - 静态变量: 类所有的实例都共享静态变量,可以直接通过类名来访问它;
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。 - 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
**静态方法** ### 2. 静态方法
静态方法在类加载的时候就存在了,它不依赖于任何实例,所以 static 方法必须实现也就是说它不能是抽象方法abstract 静态方法在类加载的时候就存在了,它不依赖于任何实例,所以 static 方法必须实现也就是说它不能是抽象方法abstract
**静态语句块** ### 3. 静态语句块
静态语句块和静态变量一样在类第一次实例化时运行一次。 静态语句块和静态变量一样在类第一次实例化时运行一次。
**初始化顺序** ### 4. 初始化顺序
静态数据优先于其它数据的初始化,静态变量和静态语句块哪个先运行取决于它们在代码中的顺序。 静态数据优先于其它数据的初始化,静态变量和静态语句块哪个先运行取决于它们在代码中的顺序。
@ -111,9 +111,9 @@ public InitialOrderTest() {
5. 子类(实例变量、普通语句块) 5. 子类(实例变量、普通语句块)
6. 子类(构造函数) 6. 子类(构造函数)
# Object 通用方法 # 二、Object 通用方法
## 1. 概览 ## 概览
```java ```java
public final native Class<?> getClass() public final native Class<?> getClass()
@ -139,15 +139,15 @@ public final void wait() throws InterruptedException
protected void finalize() throws Throwable {} protected void finalize() throws Throwable {}
``` ```
## 2. clone() ## clone()
**浅拷贝** ### 1. 浅拷贝
引用类型引用同一个对象。clone() 方法默认就是浅拷贝实现。 引用类型引用同一个对象。clone() 方法默认就是浅拷贝实现。
<div align="center"> <img src="../pics//d990c0e7-64d1-4ba3-8356-111bc91e53c5.png"/> </div><br> <div align="center"> <img src="../pics//d990c0e7-64d1-4ba3-8356-111bc91e53c5.png"/> </div><br>
**深拷贝** ### 2. 深拷贝
可以使用序列化实现。 可以使用序列化实现。
@ -155,21 +155,21 @@ protected void finalize() throws Throwable {}
> [How do I copy an object in Java?](https://stackoverflow.com/questions/869033/how-do-i-copy-an-object-in-java) > [How do I copy an object in Java?](https://stackoverflow.com/questions/869033/how-do-i-copy-an-object-in-java)
## 3. equals() ## equals()
**== 与 equals() 区别** ### 1. == 与 equals() 区别
- 对于基本类型,== 就是判断两个值是否相等; - 对于基本类型,== 判断两个值是否相等;
- 对于引用类型,== 判断两个引用是否引用同一个对象,而 equals() 判断引用的对象是否等价。 - 对于引用类型,== 判断两个引用是否引用同一个对象,而 equals() 判断引用的对象是否等价。
**等价性** ### 2. 等价性
[散列](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Java%20%E5%AE%B9%E5%99%A8.md#%E6%95%A3%E5%88%97) [散列](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Java%20%E5%AE%B9%E5%99%A8.md#%E6%95%A3%E5%88%97)
# 继承 # 四、继承
## 1. 访问权限 ## 访问权限
Java 中有三个访问权限修饰符private、protected 以及 public如果不加访问修饰符表示包级可见。 Java 中有三个访问权限修饰符private、protected 以及 public如果不加访问修饰符表示包级可见。
@ -179,9 +179,9 @@ protected 用于修饰成员,表示在继承体系中成员对于子类可见
更详细的内容:[浅析 Java 中的访问权限控制](http://www.importnew.com/18097.html) 更详细的内容:[浅析 Java 中的访问权限控制](http://www.importnew.com/18097.html)
## 2. 抽象类与接口 ## 抽象类与接口
抽象类至少包含一个抽象方法,该抽象方法必须在子类中实现。由于抽象类没有抽象方法的具体实现,因此不能对抽象类进行实例化 抽象类和抽象方法都使用 abstract 进行声明。抽象类一般会包含抽象方法,但是少数情况下可以不包含,例如 HttpServlet 类,但是抽象方法一定位于抽象类中。抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类
```java ```java
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable { public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
@ -220,9 +220,9 @@ public interface Externalizable extends Serializable {
> [Java 抽象类与接口的区别](http://www.importnew.com/12399.html) > [Java 抽象类与接口的区别](http://www.importnew.com/12399.html)
## 3. super ## super
**访问父类的成员** ### 1. 访问父类的成员
如果子类覆盖了父类的中某个方法的实现,那么就可以通过使用 super 关键字来引用父类的方法实现。 如果子类覆盖了父类的中某个方法的实现,那么就可以通过使用 super 关键字来引用父类的方法实现。
@ -248,7 +248,7 @@ public class Subclass extends Superclass {
} }
``` ```
**访问父类的构造函数** ### 2. 访问父类的构造函数
可以使用 super() 函数访问父类的构造函数,从而完成一些初始化的工作。 可以使用 super() 函数访问父类的构造函数,从而完成一些初始化的工作。
@ -261,21 +261,21 @@ public MountainBike(int startHeight, int startCadence, int startSpeed, int start
> [Using the Keyword super](https://docs.oracle.com/javase/tutorial/java/IandI/super.html) > [Using the Keyword super](https://docs.oracle.com/javase/tutorial/java/IandI/super.html)
## 4. 重载与重写 ## 重载与重写
- 重写存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法; - 重写存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法;
- 重载即存在于继承体系中,也存在于同一个类中,指一个方法与已经存在的方法或者父类的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。应该注意的是,返回值不同,其它都相同不算是重载。 - 重载即存在于继承体系中,也存在于同一个类中,指一个方法与已经存在的方法或者父类的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。应该注意的是,返回值不同,其它都相同不算是重载。
# String # 五、String
## 1. String, StringBuffer and StringBuilder ## String, StringBuffer and StringBuilder
**是否可变** ### 1. 是否可变
String 不可变StringBuffer 和 StringBuilder 可变。 String 不可变StringBuffer 和 StringBuilder 可变。
**是否线程安全** ### 2. 是否线程安全
String 不可变,因此是线程安全的。 String 不可变,因此是线程安全的。
@ -283,37 +283,37 @@ StringBuilder 不是线程安全的StringBuffer 是线程安全的,使用 s
> [String, StringBuffer, and StringBuilder](https://stackoverflow.com/questions/2971315/string-stringbuffer-and-stringbuilder) > [String, StringBuffer, and StringBuilder](https://stackoverflow.com/questions/2971315/string-stringbuffer-and-stringbuilder)
## 2. String 不可变的原因 ## String 不可变的原因
**可以缓存 hash 值** ### 1.1 可以缓存 hash 值
因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 等。不可变的特性可以使得 hash 值也不可变,因此就只需要进行一次计算。 因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 等。不可变的特性可以使得 hash 值也不可变,因此就只需要进行一次计算。
**String Pool 的需要** ### 2. String Pool 的需要
如果 String 已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。 如果 String 已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
<div align="center"> <img src="../pics//f76067a5-7d5f-4135-9549-8199c77d8f1c.jpg"/> </div><br> <div align="center"> <img src="../pics//f76067a5-7d5f-4135-9549-8199c77d8f1c.jpg"/> </div><br>
**安全性** ### 3. 安全性
String 经常作为参数String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的那么在网络连接过程中String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。 String 经常作为参数String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的那么在网络连接过程中String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。
**线程安全** ### 4. 线程安全
String 不可变性天生具备线程安全,可以在多个线程中使用。 String 不可变性天生具备线程安全,可以在多个线程中使用。
> [Why String is immutable in Java?](https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/) > [Why String is immutable in Java?](https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/)
## 3. String.intern() ## String.intern()
使用 String.intern() 可以保证所有相同内容的字符串变量引用相同的内存对象。 使用 String.intern() 可以保证所有相同内容的字符串变量引用相同的内存对象。
更详细的内容:[揭开 String.intern() 那神秘的面纱](https://www.jianshu.com/p/95f516cb75ef) 更详细的内容:[揭开 String.intern() 那神秘的面纱](https://www.jianshu.com/p/95f516cb75ef)
# 基本类型与运算 # 六、基本类型与运算
## 1. 包装类型 ## 包装类型
八个基本类型boolean 1 byte 8 char 16 short 16 int 32 float 32 long 64 double 64 八个基本类型boolean 1 byte 8 char 16 short 16 int 32 float 32 long 64 double 64
@ -374,7 +374,7 @@ System.out.println(c==d); // true
> [Differences between new Integer(123), Integer.valueOf(123) and just 123 > [Differences between new Integer(123), Integer.valueOf(123) and just 123
](https://stackoverflow.com/questions/9030817/differences-between-new-integer123-integer-valueof123-and-just-123) ](https://stackoverflow.com/questions/9030817/differences-between-new-integer123-integer-valueof123-and-just-123)
## 2. switch ## switch
A switch works with the byte, short, char, and int primitive data types. It also works with enumerated types and a few special classes that "wrap" certain primitive types: Character, Byte, Short, and Integer. A switch works with the byte, short, char, and int primitive data types. It also works with enumerated types and a few special classes that "wrap" certain primitive types: Character, Byte, Short, and Integer.
@ -410,7 +410,7 @@ public static void main(java.lang.String[]);
> [How does Java's switch work under the hood?](https://stackoverflow.com/questions/12020048/how-does-javas-switch-work-under-the-hood) > [How does Java's switch work under the hood?](https://stackoverflow.com/questions/12020048/how-does-javas-switch-work-under-the-hood)
# 反射 # 七、反射
每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。 每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。
@ -425,13 +425,13 @@ IDE 使用反射机制获取类的信息,在使用一个类的对象时,能
更详细的内容:[深入解析 Java 反射1- 基础](http://www.sczyh30.com/posts/Java/java-reflection-1/) 更详细的内容:[深入解析 Java 反射1- 基础](http://www.sczyh30.com/posts/Java/java-reflection-1/)
**Advantages of Using Reflection:** ### Advantages of Using Reflection:
- **Extensibility Features** : An application may make use of external, user-defined classes by creating instances of extensibility objects using their fully-qualified names. - **Extensibility Features** : An application may make use of external, user-defined classes by creating instances of extensibility objects using their fully-qualified names.
- **Class Browsers and Visual Development Environments** : A class browser needs to be able to enumerate the members of classes. Visual development environments can benefit from making use of type information available in reflection to aid the developer in writing correct code. - **Class Browsers and Visual Development Environments** : A class browser needs to be able to enumerate the members of classes. Visual development environments can benefit from making use of type information available in reflection to aid the developer in writing correct code.
- **Debuggers and Test Tools** : Debuggers need to be able to examine private members on classes. Test harnesses can make use of reflection to systematically call a discoverable set APIs defined on a class, to insure a high level of code coverage in a test suite. - **Debuggers and Test Tools** : Debuggers need to be able to examine private members on classes. Test harnesses can make use of reflection to systematically call a discoverable set APIs defined on a class, to insure a high level of code coverage in a test suite.
**Drawbacks of Reflection** ### Drawbacks of Reflection
Reflection is powerful, but should not be used indiscriminately. If it is possible to perform an operation without using reflection, then it is preferable to avoid using it. The following concerns should be kept in mind when accessing code via reflection. Reflection is powerful, but should not be used indiscriminately. If it is possible to perform an operation without using reflection, then it is preferable to avoid using it. The following concerns should be kept in mind when accessing code via reflection.
@ -441,8 +441,7 @@ Reflection is powerful, but should not be used indiscriminately. If it is possib
> [Trail: The Reflection API](https://docs.oracle.com/javase/tutorial/reflect/index.html) > [Trail: The Reflection API](https://docs.oracle.com/javase/tutorial/reflect/index.html)
# 八、异常
# 异常
Throwable 可以用来表示任何可以作为异常抛出的类,分为两种: **Error****Exception**,其中 Error 用来表示编译时系统错误。 Throwable 可以用来表示任何可以作为异常抛出的类,分为两种: **Error****Exception**,其中 Error 用来表示编译时系统错误。
@ -454,7 +453,7 @@ Exception 分为两种: **受检异常** 和 **非受检异常**。受检异
- [Java 入门之异常处理](https://www.tianmaying.com/tutorial/Java-Exception) - [Java 入门之异常处理](https://www.tianmaying.com/tutorial/Java-Exception)
- [Java 异常的面试问题及答案 -Part 1](http://www.importnew.com/7383.html) - [Java 异常的面试问题及答案 -Part 1](http://www.importnew.com/7383.html)
# 泛型 # 九、泛型
```java ```java
public class Box<T> { public class Box<T> {
@ -470,19 +469,19 @@ public class Box<T> {
- [Java 泛型详解](https://www.ziwenxie.site/2017/03/01/java-generic/) - [Java 泛型详解](https://www.ziwenxie.site/2017/03/01/java-generic/)
- [10 道 Java 泛型面试题](https://cloud.tencent.com/developer/article/1033693) - [10 道 Java 泛型面试题](https://cloud.tencent.com/developer/article/1033693)
# 注解 # 十、注解
Java注解是附加在代码中的一些元信息用于一些工具在编译、运行时进行解析和使用起到说明、配置的功能。注解不会也不能影响代码的实际逻辑仅仅起到辅助性的作用。 Java注解是附加在代码中的一些元信息用于一些工具在编译、运行时进行解析和使用起到说明、配置的功能。注解不会也不能影响代码的实际逻辑仅仅起到辅助性的作用。
更多详细内容:[注解Annotation实现原理与自定义注解例子](https://www.cnblogs.com/acm-bingzi/p/javaAnnotation.html) 更多详细内容:[注解Annotation实现原理与自定义注解例子](https://www.cnblogs.com/acm-bingzi/p/javaAnnotation.html)
# 特性 # 十一、特性
## 1. 三大特性 ## 面向对象三大特性
[封装、继承、多态](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E6%80%9D%E6%83%B3.md#%E5%B0%81%E8%A3%85%E7%BB%A7%E6%89%BF%E5%A4%9A%E6%80%81) [封装、继承、多态](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E6%80%9D%E6%83%B3.md#%E5%B0%81%E8%A3%85%E7%BB%A7%E6%89%BF%E5%A4%9A%E6%80%81)
## 2. Java 各版本的新特性 ## Java 各版本的新特性
New highlights in Java SE 8 New highlights in Java SE 8
@ -511,7 +510,7 @@ New highlights in Java SE 7
更详细的内容:[Java 8 特性 ](http://www.importnew.com/19345.html) 更详细的内容:[Java 8 特性 ](http://www.importnew.com/19345.html)
## 3. Java 与 C++ 的区别 ## Java 与 C++ 的区别
Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.ObjectC++ 为了兼容 C 即支持面向对象也支持面向过程。 Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.ObjectC++ 为了兼容 C 即支持面向对象也支持面向过程。
@ -533,7 +532,7 @@ Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Obje
> [What are the main differences between Java and C++?](http://cs-fundamentals.com/tech-interview/java/differences-between-java-and-cpp.php) > [What are the main differences between Java and C++?](http://cs-fundamentals.com/tech-interview/java/differences-between-java-and-cpp.php)
## 4. JRE or JDK ## JRE or JDK
- JRE is the JVM program, Java application need to run on JRE. - JRE is the JVM program, Java application need to run on JRE.
- JDK is a superset of JRE, JRE + tools for developing java programs. e.g, it provides the compiler "javac" - JDK is a superset of JRE, JRE + tools for developing java programs. e.g, it provides the compiler "javac"

View File

@ -1,48 +1,39 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [概览](#概览) * [一、概览](#一概览)
* [1. List](#1-list) * [List](#list)
* [2. Set](#2-set) * [Set](#set)
* [3. Queue](#3-queue) * [Queue](#queue)
* [4. Map](#4-map) * [Map](#map)
* [5. Java 1.0/1.1 容器](#5-java-1011-容器) * [Java 1.0/1.1 容器](#java-1011-容器)
* [容器中的设计模式](#容器中的设计模式) * [二、容器中的设计模式](#二容器中的设计模式)
* [1. 迭代器模式](#1-迭代器模式) * [迭代器模式](#迭代器模式)
* [2. 适配器模式](#2-适配器模式) * [适配器模式](#适配器模式)
* [散列](#散列) * [三、散列](#三散列)
* [源码分析](#源码分析) * [四、源码分析](#四源码分析)
* [1. ArrayList](#1-arraylist) * [ArrayList](#arraylist)
* [概览](#概览) * [Vector 与 Stack](#vector-与-stack)
* [Fail-Fast](#fail-fast) * [LinkedList](#linkedlist)
* [和 Vector 的区别](#和-vector-的区别) * [TreeMap](#treemap)
* [和 LinkedList 的区别](#和-linkedlist-的区别) * [HashMap](#hashmap)
* [2. Vector 与 Stack](#2-vector-与-stack) * [LinkedHashMap](#linkedhashmap)
* [3. LinkedList](#3-linkedlist) * [ConcurrentHashMap](#concurrenthashmap)
* [4. TreeMap](#4-treemap) * [五、参考资料](#五参考资料)
* [5. HashMap](#5-hashmap)
* [基本数据结构](#基本数据结构)
* [拉链法的工作原理](#拉链法的工作原理)
* [扩容](#扩容)
* [null 值](#null-值)
* [与 HashTable 的区别](#与-hashtable-的区别)
* [6. LinkedHashMap](#6-linkedhashmap)
* [7. ConcurrentHashMap](#7-concurrenthashmap)
* [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
# 概览 # 一、概览
<div align="center"> <img src="../pics//ebf03f56-f957-4435-9f8f-0f605661484d.jpg"/> </div><br> <div align="center"> <img src="../pics//ebf03f56-f957-4435-9f8f-0f605661484d.jpg"/> </div><br>
容器主要包括 Collection 和 Map 两种Collection 又包含了 List、Set 以及 Queue。 容器主要包括 Collection 和 Map 两种Collection 又包含了 List、Set 以及 Queue。
## 1. List ## List
- ArrayList基于动态数组实现支持随机访问 - ArrayList基于动态数组实现支持随机访问
- LinkedList基于双向循环链表实现只能顺序访问但是可以快速地在链表中间插入和删除元素。不仅如此LinkedList 还可以用作栈、队列和双端队列。 - LinkedList基于双向循环链表实现只能顺序访问但是可以快速地在链表中间插入和删除元素。不仅如此LinkedList 还可以用作栈、队列和双端队列。
## 2. Set ## Set
- HashSet基于 Hash 实现,支持快速查找,但是失去有序性; - HashSet基于 Hash 实现,支持快速查找,但是失去有序性;
@ -50,11 +41,11 @@
- LinkedHashSet具有 HashSet 的查找效率,且内部使用链表维护元素的插入顺序,因此具有有序性。 - LinkedHashSet具有 HashSet 的查找效率,且内部使用链表维护元素的插入顺序,因此具有有序性。
## 3. Queue ## Queue
只有两个实现LinkedList 和 PriorityQueue其中 LinkedList 支持双向队列PriorityQueue 是基于堆结构实现。 只有两个实现LinkedList 和 PriorityQueue其中 LinkedList 支持双向队列PriorityQueue 是基于堆结构实现。
## 4. Map ## Map
- HashMap基于 Hash 实现。 - HashMap基于 Hash 实现。
@ -64,7 +55,7 @@
- ConcurrentHashMap线程安全 Map不涉及类似于 HashTable 的同步加锁。 - ConcurrentHashMap线程安全 Map不涉及类似于 HashTable 的同步加锁。
## 5. Java 1.0/1.1 容器 ## Java 1.0/1.1 容器
对于旧的容器,我们决不应该使用它们,只需要对它们进行了解。 对于旧的容器,我们决不应该使用它们,只需要对它们进行了解。
@ -72,15 +63,15 @@
- HashTable和 HashMap 类似,但它是线程安全的。 - HashTable和 HashMap 类似,但它是线程安全的。
# 容器中的设计模式 # 二、容器中的设计模式
## 1. 迭代器模式 ## 迭代器模式
从概览图可以看到,每个集合类都有一个 Iterator 对象,可以通过这个迭代器对象来遍历集合中的元素。 从概览图可以看到,每个集合类都有一个 Iterator 对象,可以通过这个迭代器对象来遍历集合中的元素。
[Java 中的迭代器模式](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md#1-%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F) [Java 中的迭代器模式](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md#1-%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F)
## 2. 适配器模式 ## 适配器模式
java.util.Arrays#asList() 可以把数组类型转换为 List 类型。 java.util.Arrays#asList() 可以把数组类型转换为 List 类型。
@ -90,7 +81,7 @@ java.util.Arrays#asList() 可以把数组类型转换为 List 类型。
list = Arrays.asList(arr); list = Arrays.asList(arr);
``` ```
# 散列 # 三、散列
使用 hasCode() 来返回散列值,使用的是对象的地址。 使用 hasCode() 来返回散列值,使用的是对象的地址。
@ -102,19 +93,19 @@ java.util.Arrays#asList() 可以把数组类型转换为 List 类型。
2. 对称性 2. 对称性
3. 传递性 3. 传递性
4. 一致性(多次调用 x.equals(y),结果不变) 4. 一致性(多次调用 x.equals(y),结果不变)
5. 对任何不是 null 的对象 x 调用 x.equals(nul) 结果都为 false 5. 对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false
# 源码分析 # 四、源码分析
建议先阅读 [算法 - 查找](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E7%AE%97%E6%B3%95.md#%E6%9F%A5%E6%89%BE) 部分,对集合类源码的理解有很大帮助。 建议先阅读 [算法-查找](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E7%AE%97%E6%B3%95.md#%E6%9F%A5%E6%89%BE) 部分,对集合类源码的理解有很大帮助。
源码下载:[OpenJDK 1.7](http://download.java.net/openjdk/jdk7) 源码下载:[OpenJDK 1.7](http://download.java.net/openjdk/jdk7)
## 1. ArrayList ## ArrayList
[ArraList.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/ArrayList.java) [ArraList.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/ArrayList.java)
### 概览 ### 1. 概览
实现了 RandomAccess 接口,因此支持随机访问,这是理所当然的,因为 ArrayList 是基于数组实现的。 实现了 RandomAccess 接口,因此支持随机访问,这是理所当然的,因为 ArrayList 是基于数组实现的。
@ -193,7 +184,7 @@ private static int hugeCapacity(int minCapacity) {
} }
``` ```
### Fail-Fast ### 2. Fail-Fast
modCount 用来记录 ArrayList 结构发生变化的次数,结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。 modCount 用来记录 ArrayList 结构发生变化的次数,结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。
@ -218,36 +209,36 @@ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOExceptio
} }
``` ```
### 和 Vector 的区别 ### 3. 和 Vector 的区别
1. Vector 和 ArrayList 几乎是完全相同的,唯一的区别在于 Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector因为同步操作完全可以由程序员自己来控制 1. Vector 和 ArrayList 几乎是完全相同的,唯一的区别在于 Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector因为同步操作完全可以由程序员自己来控制
2. Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。 2. Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。
为了获得线程安全的 ArrayList可以调用 Collections.synchronizedList(new ArrayList<>()); 返回一个线程安全的 ArrayList也可以使用 concurrent 并发包下的 CopyOnWriteArrayList 类; 为了获得线程安全的 ArrayList可以调用 Collections.synchronizedList(new ArrayList<>()); 返回一个线程安全的 ArrayList也可以使用 concurrent 并发包下的 CopyOnWriteArrayList 类;
### 和 LinkedList 的区别 ### 4. 和 LinkedList 的区别
1. ArrayList 基于动态数组实现LinkedList 基于双向循环链表实现; 1. ArrayList 基于动态数组实现LinkedList 基于双向循环链表实现;
2. ArrayList 支持随机访问LinkedList 不支持; 2. ArrayList 支持随机访问LinkedList 不支持;
3. LinkedList 在任意位置添加删除元素更快。 3. LinkedList 在任意位置添加删除元素更快。
## 2. Vector 与 Stack ## Vector 与 Stack
[Vector.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/Vector.java) [Vector.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/Vector.java)
## 3. LinkedList ## LinkedList
[LinkedList.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/LinkedList.java) [LinkedList.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/LinkedList.java)
## 4. TreeMap ## TreeMap
[TreeMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/TreeMap.java) [TreeMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/TreeMap.java)
## 5. HashMap ## HashMap
[HashMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/HashMap.java) [HashMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/HashMap.java)
### 基本数据结构 ### 1. 基本数据结构
使用拉链法来解决冲突,内部包含了一个 Entry 类型的数组 table数组中的每个位置被当成一个桶。 使用拉链法来解决冲突,内部包含了一个 Entry 类型的数组 table数组中的每个位置被当成一个桶。
@ -259,7 +250,7 @@ transient Entry[] table;
<div align="center"> <img src="../pics//ce039f03-6588-4f0c-b35b-a494de0eac47.png"/> </div><br> <div align="center"> <img src="../pics//ce039f03-6588-4f0c-b35b-a494de0eac47.png"/> </div><br>
### 拉链法的工作原理 ### 2. 拉链法的工作原理
使用默认构造函数新建一个 HashMap默认大小为 16。Entry 的类型为 &lt;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) 之后。 使用默认构造函数新建一个 HashMap默认大小为 16。Entry 的类型为 &lt;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) 之后。
@ -267,7 +258,7 @@ transient Entry[] table;
当进行查找时,需要分成两步进行,第一步是先根据 hashcode 计算出所在的桶,第二步是在链表上顺序查找。由于 table 是数组形式的,具有随机读取的特性,因此这一步的时间复杂度为 O(1),而第二步需要在链表上顺序查找,时间复杂度显然和链表的长度成正比。 当进行查找时,需要分成两步进行,第一步是先根据 hashcode 计算出所在的桶,第二步是在链表上顺序查找。由于 table 是数组形式的,具有随机读取的特性,因此这一步的时间复杂度为 O(1),而第二步需要在链表上顺序查找,时间复杂度显然和链表的长度成正比。
### 扩容 ### 3. 扩容
设 HashMap 的 table 长度为 M需要存储的键值对数量为 N如果哈希函数满足均匀性的要求那么每条链表的长度大约为 N/M因此平均查找次数的数量级为 O(N/M)。 设 HashMap 的 table 长度为 M需要存储的键值对数量为 N如果哈希函数满足均匀性的要求那么每条链表的长度大约为 N/M因此平均查找次数的数量级为 O(N/M)。
@ -346,7 +337,7 @@ void transfer(Entry[] newTable) {
} }
``` ```
### null 值 ### 4. null 值
get() 操作需要分成两种情况key 为 null 和不为 null从中可以看出 HashMap 允许插入 null 作为键。 get() 操作需要分成两种情况key 为 null 和不为 null从中可以看出 HashMap 允许插入 null 作为键。
@ -404,7 +395,7 @@ private V putForNullKey(V value) {
} }
``` ```
### 与 HashTable 的区别 ### 5. 与 HashTable 的区别
- HashMap 几乎可以等价于 Hashtable除了 HashMap 是非 synchronized 的,并可以接受 null(HashMap 可以接受为 null 的键值 (key) 和值 (value),而 Hashtable 则不行)。 - HashMap 几乎可以等价于 Hashtable除了 HashMap 是非 synchronized 的,并可以接受 null(HashMap 可以接受为 null 的键值 (key) 和值 (value),而 Hashtable 则不行)。
- HashMap 是非 synchronized而 Hashtable 是 synchronized这意味着 Hashtable 是线程安全的,多个线程可以共享一个 Hashtable而如果没有正确的同步的话多个线程是不能共享 HashMap 的。Java 5 提供了 ConcurrentHashMap它是 HashTable 的替代,比 HashTable 的扩展性更好。 - HashMap 是非 synchronized而 Hashtable 是 synchronized这意味着 Hashtable 是线程安全的,多个线程可以共享一个 Hashtable而如果没有正确的同步的话多个线程是不能共享 HashMap 的。Java 5 提供了 ConcurrentHashMap它是 HashTable 的替代,比 HashTable 的扩展性更好。
@ -414,16 +405,16 @@ private V putForNullKey(V value) {
> [What is difference between HashMap and Hashtable in Java?](http://javarevisited.blogspot.hk/2010/10/difference-between-hashmap-and.html) > [What is difference between HashMap and Hashtable in Java?](http://javarevisited.blogspot.hk/2010/10/difference-between-hashmap-and.html)
## 6. LinkedHashMap ## LinkedHashMap
[LinkedHashMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/HashMap.java) [LinkedHashMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/HashMap.java)
## 7. ConcurrentHashMap ## ConcurrentHashMap
[ConcurrentHashMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/HashMap.java) [ConcurrentHashMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/HashMap.java)
[探索 ConcurrentHashMap 高并发性的实现机制](https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/) [探索 ConcurrentHashMap 高并发性的实现机制](https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/)
# 参考资料 # 五、参考资料
- Java 编程思想 - Java 编程思想

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-内存模型)
@ -127,7 +130,8 @@ class MyThread extends Thread {
Executor 管理多个异步任务的执行,而无需程序员显示地管理线程的生命周期。 Executor 管理多个异步任务的执行,而无需程序员显示地管理线程的生命周期。
主要有三种 Excutor 主要有三种 Executor
1. CachedTreadPool一个任务创建一个线程 1. CachedTreadPool一个任务创建一个线程
2. FixedThreadPool所有任务只能使用固定大小的线程 2. FixedThreadPool所有任务只能使用固定大小的线程
@ -258,9 +262,12 @@ public void func(String name) {
```java ```java
private Lock lock; private Lock lock;
public int func(int value) { public int func(int value) {
try {
lock.lock(); lock.lock();
// ... // ...
} finally {
lock.unlock(); lock.unlock();
}
} }
``` ```
@ -331,14 +338,14 @@ public class Client {
public static void main(String[] args) { public static void main(String[] args) {
BlockingQueue<String> queue = new LinkedBlockingQueue<>(5); BlockingQueue<String> queue = new LinkedBlockingQueue<>(5);
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
new Thread(new Consumer(queue), "Producer" + i).start(); new Thread(new Consumer(queue), "Consumer" + i).start();
} }
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
// 只有两个 Product因此只能消费两个其它三个消费者被阻塞 // 只有两个 Product因此只能消费两个其它三个消费者被阻塞
new Thread(new Producer(queue), "Consumer" + i).start(); new Thread(new Producer(queue), "Producer" + i).start();
} }
for (int i = 2; i < 5; i++) { for (int i = 2; i < 5; i++) {
new Thread(new Consumer(queue), "Producer" + i).start(); new Thread(new Consumer(queue), "Consumer" + i).start();
} }
} }
} }
@ -346,16 +353,16 @@ public class Client {
```html ```html
// 运行结果 // 运行结果
Consumer0 is making product... Producer0 is making product...
Producer0 is consuming product made by Consumer0... Consumer0 is consuming product made by Consumer0...
Consumer1 is making product... Producer1 is making product...
Producer1 is consuming product made by Consumer1... Consumer1 is consuming product made by Consumer1...
Consumer2 is making product... Producer2 is making product...
Consumer3 is making product... Producer3 is making product...
Consumer4 is making product... Producer4 is making product...
Producer2 is consuming product made by Consumer2... Consumer2 is consuming product made by Consumer2...
Producer3 is consuming product made by Consumer3... Consumer3 is consuming product made by Consumer3...
Producer4 is consuming product made by Consumer4... Consumer4 is consuming product made by Consumer4...
``` ```
# 结束线程 # 结束线程
@ -383,7 +390,7 @@ Producer4 is consuming product made by Consumer4...
**Executor 的中断操作** **Executor 的中断操作**
Executor 避免对 Thread 对象的直接操作,但是使用 interrupt() 方法必须持有 Thread 对象。Executor 使用 shutdownNow() 方法来中断所有它里面的所有线程shutdownNow() 方法会发送 interrupt() 调用给所有线程。 Executor 避免对 Thread 对象的直接操作,但是使用 interrupt() 方法必须持有 Thread 对象。Executor 使用 shutdownNow() 方法来中断它里面的所有线程shutdownNow() 方法会发送 interrupt() 调用给所有线程。
如果只想中断一个线程,那么使用 Executor 的 submit() 而不是 executor() 来启动线程就可以持有线程的上下文。submit() 将返回一个泛型 Futrue可以在它之上调用 cancel(),如果将 true 传递给 cancel(),那么它将会发送 interrupt() 调用给特定的线程。 如果只想中断一个线程,那么使用 Executor 的 submit() 而不是 executor() 来启动线程就可以持有线程的上下文。submit() 将返回一个泛型 Futrue可以在它之上调用 cancel(),如果将 true 传递给 cancel(),那么它将会发送 interrupt() 调用给特定的线程。
@ -420,6 +427,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

@ -1,5 +1,5 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [常用操作以及概念](#常用操作以及概念) * [一、常用操作以及概念](#常用操作以及概念)
* [求助](#求助) * [求助](#求助)
* [关机](#关机) * [关机](#关机)
* [查看进程](#查看进程) * [查看进程](#查看进程)
@ -9,91 +9,55 @@
* [sudo](#sudo) * [sudo](#sudo)
* [GNU](#gnu) * [GNU](#gnu)
* [包管理工具](#包管理工具) * [包管理工具](#包管理工具)
* [常见发行版](#常见发行版) * [发行版](#发行版)
* [分区](#分区) * [二、分区](#分区)
* [磁盘的文件名](#磁盘的文件名) * [磁盘的文件名](#磁盘的文件名)
* [分区表](#分区表) * [分区表](#分区表)
* [1. MBR](#1-mbr)
* [2. GPT](#2-gpt)
* [开机检测程序](#开机检测程序) * [开机检测程序](#开机检测程序)
* [1. BIOS](#1-bios)
* [2. UEFI](#2-uefi)
* [挂载](#挂载) * [挂载](#挂载)
* [文件权限与目录配置](#文件权限与目录配置) * [三、文件权限与目录配置](#三文件权限与目录配置)
* [文件权限概念](#文件权限概念) * [文件权限概念](#文件权限概念)
* [文件属性以及权限的修改](#文件属性以及权限的修改) * [文件属性以及权限的修改](#文件属性以及权限的修改)
* [1. 修改文件所属群组](#1-修改文件所属群组)
* [2. 修改文件拥有者](#2-修改文件拥有者)
* [3. 修改权限](#3-修改权限)
* [目录的权限](#目录的权限) * [目录的权限](#目录的权限)
* [文件默认权限](#文件默认权限) * [文件默认权限](#文件默认权限)
* [目录配置](#目录配置) * [目录配置](#目录配置)
* [文件与目录](#文件与目录) * [四、文件与目录](#文件与目录)
* [文件时间](#文件时间) * [文件时间](#文件时间)
* [文件与目录的基本操作](#文件与目录的基本操作) * [文件与目录的基本操作](#文件与目录的基本操作)
* [1. ls](#1-ls)
* [2. cp](#2-cp)
* [3. rm](#3-rm)
* [4. mv](#4-mv)
* [获取文件内容](#获取文件内容) * [获取文件内容](#获取文件内容)
* [1. cat](#1-cat)
* [2. tac](#2-tac)
* [3. more](#3-more)
* [4. less](#4-less)
* [5. head](#5-head)
* [6. tail](#6-tail)
* [7. od](#7-od)
* [8. touch](#8-touch)
* [指令与文件搜索](#指令与文件搜索) * [指令与文件搜索](#指令与文件搜索)
* [1. which](#1-which) * [五、磁盘与文件系统](#五磁盘与文件系统)
* [2. whereis](#2-whereis)
* [3. locate](#3-locate)
* [4. find](#4-find)
* [4.1 与时间有关的选项](#41-与时间有关的选项)
* [4.2 与文件拥有者和所属群组有关的选项](#42-与文件拥有者和所属群组有关的选项)
* [4.3 与文件权限和名称有关的选项](#43-与文件权限和名称有关的选项)
* [磁盘与文件系统](#磁盘与文件系统)
* [文件系统的组成](#文件系统的组成) * [文件系统的组成](#文件系统的组成)
* [inode](#inode) * [inode](#inode)
* [目录的 inode 与 block](#目录的-inode-与-block) * [目录的 inode 与 block](#目录的-inode-与-block)
* [实体链接与符号链接](#实体链接与符号链接) * [实体链接与符号链接](#实体链接与符号链接)
* [1. 实体链接](#1-实体链接) * [六、压缩与打包](#六压缩与打包)
* [2. 符号链接](#2-符号链接)
* [压缩与打包](#压缩与打包)
* [压缩](#压缩) * [压缩](#压缩)
* [1. gzip](#1-gzip)
* [2. bzip2](#2-bzip2)
* [3. xz](#3-xz)
* [打包](#打包) * [打包](#打包)
* [Bash](#bash) * [七、Bash](#七bash)
* [Bash 特性](#bash-特性) * [Bash 特性](#bash-特性)
* [变量操作](#变量操作) * [变量操作](#变量操作)
* [指令搜索顺序](#指令搜索顺序) * [指令搜索顺序](#指令搜索顺序)
* [数据流重定向](#数据流重定向) * [数据流重定向](#数据流重定向)
* [管线指令](#管线指令) * [管线指令](#管线指令)
* [1. 提取指令cut](#1-提取指令cut) * [八、正规表示法与文件格式化处理](#八正规表示法与文件格式化处理)
* [2. 排序命令sort、uniq](#2-排序命令sortuniq)
* [3. 双向输出重定向tee](#3-双向输出重定向tee)
* [4. 字符转换指令tr、col、expand、join、paste](#4-字符转换指令trcolexpandjoinpaste)
* [5. 分区指令split](#5-分区指令split)
* [正规表示法与文件格式化处理](#正规表示法与文件格式化处理)
* [grep](#grep) * [grep](#grep)
* [printf](#printf) * [printf](#printf)
* [awk](#awk) * [awk](#awk)
* [vim 三个模式](#vim-三个模式) * [九、vim 三个模式](#九vim-三个模式)
* [参考资料](#参考资料) * [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
# 常用操作以及概念 # 一、常用操作以及概念
## 求助 ## 求助
**1. --help** ### 1. --help
指令的基本用法与选项介绍。 指令的基本用法与选项介绍。
**2. man** ### 2. man
man 是 manual 的缩写,将指令的具体信息显示出来。 man 是 manual 的缩写,将指令的具体信息显示出来。
@ -105,17 +69,17 @@ man 是 manual 的缩写,将指令的具体信息显示出来。
| 5 | 配置文件 | | 5 | 配置文件 |
| 8 | 系统管理员可以使用的管理指令 | | 8 | 系统管理员可以使用的管理指令 |
**3. info** ### 3. info
info 与 man 类似,但是 info 将文档分成一个个页面,每个页面可以进行跳转。 info 与 man 类似,但是 info 将文档分成一个个页面,每个页面可以进行跳转。
## 关机 ## 关机
**1. sync** ### 1. sync
为了加快对磁盘上文件的读写速度,位于内存中的文件数据不会立即同步到磁盘上,因此关机之前需要先进行 sync 同步操作。 为了加快对磁盘上文件的读写速度,位于内存中的文件数据不会立即同步到磁盘上,因此关机之前需要先进行 sync 同步操作。
**2. shutdown** ### 2. shutdown
```html ```html
# /sbin/shutdown [-krhc] [时间] [警告讯息] # /sbin/shutdown [-krhc] [时间] [警告讯息]
@ -125,7 +89,7 @@ info 与 man 类似,但是 info 将文档分成一个个页面,每个页面
-c 取消已经在进行的 shutdown 指令内容 -c 取消已经在进行的 shutdown 指令内容
``` ```
**3. 其它关机指令** ### 3. 其它关机指令
reboot、halt、poweroff。 reboot、halt、poweroff。
@ -165,7 +129,7 @@ netstat -anp | grep 80
## GNU ## GNU
GNU 计划,译为革奴计划,它的目标是创建一套完全自由的操作系统,称为 GNU其内容软件完全以 GPL 方式发布。其中 GPL 全称为 GNU 通用公共许可协议,包含了以下内容: GNU 计划,译为革奴计划,它的目标是创建一套完全自由的操作系统,称为 GNU其内容软件完全以 GPL 方式发布。其中 GPL 全称为 GNU 通用公共许可协议,包含了以下内容:
- 以任何目的运行此程序的自由; - 以任何目的运行此程序的自由;
- 再复制的自由; - 再复制的自由;
@ -177,7 +141,7 @@ RPM 和 DPKG 为最常见的两类软件包管理工具。RPM 全称为 Redhat P
YUM 基于 RPM 包管理工具,具有依赖管理功能,并具有软件升级的功能。 YUM 基于 RPM 包管理工具,具有依赖管理功能,并具有软件升级的功能。
## 常见发行版 ## 发行版
Linux 发行版是 Linux 内核及各种应用软件的集成版本。 Linux 发行版是 Linux 内核及各种应用软件的集成版本。
@ -186,7 +150,7 @@ Linux 发行版是 Linux 内核及各种应用软件的集成版本。
| DPKG | Ubuntu | Debian | | DPKG | Ubuntu | Debian |
| RPM | Red Hat | Fedora / CentOS | | RPM | Red Hat | Fedora / CentOS |
# 分区 # 二、分区
## 磁盘的文件名 ## 磁盘的文件名
@ -207,7 +171,7 @@ Linux 中每个硬件都被当做一个文件。
MBR 中第一个扇区最重要里面有主要开机记录Master boot record, MBR及分区表partition table其中 MBR 占 446 bytespartition table 占 64 bytes。 MBR 中第一个扇区最重要里面有主要开机记录Master boot record, MBR及分区表partition table其中 MBR 占 446 bytespartition table 占 64 bytes。
分区表只有 64 bytes最多只能存储 4 个分区,这 4 个分区为主分区Primary和扩展分区Extended。其中扩展分区只有一个它将其它空间用来记录分区表因此通过扩展分区可以分出更多分,这些分区称为逻辑分区。 分区表只有 64 bytes最多只能存储 4 个分区,这 4 个分区为主分区Primary和扩展分区Extended。其中扩展分区只有一个它将其它空间用来记录分区表因此通过扩展分区可以分出更多分,这些分区称为逻辑分区。
Linux 也把分区当成文件,分区文件的命名方式为:磁盘文件名 + 编号,例如 /dev/sda1。注意逻辑分区的编号从 5 开始。 Linux 也把分区当成文件,分区文件的命名方式为:磁盘文件名 + 编号,例如 /dev/sda1。注意逻辑分区的编号从 5 开始。
@ -225,7 +189,7 @@ GPT 没有扩展分区概念,都是主分区,最多可以分 128 个分区
### 1. BIOS ### 1. BIOS
BIOS 是开机的时候计算机执行的第一个程序,这个程序知道可以开机的磁盘,并读取磁盘第一个扇区的 MBR由 MBR 执行其中的开机管理程序,这个开机管理程序会加载操作系统的核心文件。 BIOS 是开机的时候计算机执行的第一个程序,这个程序知道可以开机的磁盘,并读取磁盘第一个扇区的 MBR由 MBR 执行其中的开机管理程序,这个开机管理程序会加载操作系统的核心文件。
MBR 中的开机管理程序提供以下功能:选单、载入核心文件以及转交其它开机管理程序。转交这个功能可以用来实现了多重引导,只需要将另一个操作系统的开机管理程序安装在其它分区的启动扇区上,在启动 MBR 中的开机管理程序时,就可以选择启动当前的操作系统或者转交给其它开机管理程序从而启动另一个操作系统。 MBR 中的开机管理程序提供以下功能:选单、载入核心文件以及转交其它开机管理程序。转交这个功能可以用来实现了多重引导,只需要将另一个操作系统的开机管理程序安装在其它分区的启动扇区上,在启动 MBR 中的开机管理程序时,就可以选择启动当前的操作系统或者转交给其它开机管理程序从而启动另一个操作系统。
@ -243,7 +207,7 @@ UEFI 相比于 BIOS 来说功能更为全面,也更为安全。
<div align="center"> <img src="../pics//249f3bb1-feee-4805-a259-a72699d638ca.jpg"/> </div><br> <div align="center"> <img src="../pics//249f3bb1-feee-4805-a259-a72699d638ca.jpg"/> </div><br>
# 文件权限与目录配置 # 三、文件权限与目录配置
## 文件权限概念 ## 文件权限概念
@ -342,7 +306,7 @@ UEFI 相比于 BIOS 来说功能更为全面,也更为安全。
<div align="center"> <img src="../pics//27ace615-558f-4dfb-8ad4-7ac769c10118.jpg"/> </div><br> <div align="center"> <img src="../pics//27ace615-558f-4dfb-8ad4-7ac769c10118.jpg"/> </div><br>
# 文件与目录 # 四、文件与目录
## 文件时间 ## 文件时间
@ -448,8 +412,8 @@ cp [-adfilprsu] source destination
-a 更新 atime -a 更新 atime
-c 更新 ctime若该文件不存在则不建立新文件 -c 更新 ctime若该文件不存在则不建立新文件
-m 更新 mtime -m 更新 mtime
-d 后面可以接更新日期而不用当前日期,也可以使用 --date="日期或时间" -d 后面可以接更新日期而不使用当前日期,也可以使用 --date="日期或时间"
-t 后面可以接更新时间而不用当前时间,格式为[YYYYMMDDhhmm] -t 后面可以接更新时间而不使用当前时间,格式为[YYYYMMDDhhmm]
``` ```
## 指令与文件搜索 ## 指令与文件搜索
@ -525,7 +489,7 @@ find 可以使用文件的属性和权限进行搜索。
-perm /mode :搜索权限包含任一 mode 的文件 -perm /mode :搜索权限包含任一 mode 的文件
``` ```
# 磁盘与文件系统 # 五、磁盘与文件系统
## 文件系统的组成 ## 文件系统的组成
@ -578,7 +542,9 @@ inode 具体包含以下信息:
### 1. 实体链接 ### 1. 实体链接
hard link 只是在某个目录下新增一个条目,使得新增的条目链接到文件的 inode 上。删除任意一个条目,文件还是存在,只要引用数量不为 0。 它和普通文件类似,实体链接文件的 inode 都指向源文件所在的 block 上,也就是说读取文件直接从源文件的 block 上读取。
删除任意一个条目,文件还是存在,只要引用数量不为 0。
有以下限制:不能跨越 File System不能对目录进行链接。 有以下限制:不能跨越 File System不能对目录进行链接。
@ -591,9 +557,11 @@ hard link 只是在某个目录下新增一个条目,使得新增的条目链
### 2. 符号链接 ### 2. 符号链接
symbolic link 可以理解为 Windows 的快捷方式,通过建立一个独立的文件,这个文件的数据的读取指向链接的那个文件。当源文件被删除了,链接文件就打不开了 符号链接文件保存着源文件所在的绝对路径,在读取时会定位到源文件上,可以理解为 Windows 的快捷方式
symbolic link 可以为目录建立链接。 当源文件被删除了,链接文件就打不开了。
可以为目录建立链接。
```html ```html
# ll -i /etc/crontab /root/crontab2 # ll -i /etc/crontab /root/crontab2
@ -601,7 +569,7 @@ symbolic link 可以为目录建立链接。
53745909 lrwxrwxrwx. 1 root root 12 Jun 23 22:31 /root/crontab2 -> /etc/crontab 53745909 lrwxrwxrwx. 1 root root 12 Jun 23 22:31 /root/crontab2 -> /etc/crontab
``` ```
# 压缩与打包 # 六、压缩与打包
## 压缩 ## 压缩
@ -686,29 +654,21 @@ $ tar [-z|-j|-J] [xv] [-f 已有的 tar 文件] [-C 目录] ==解压缩
| 查 看 | tar -jtv -f filename.tar.bz2 | | 查 看 | tar -jtv -f filename.tar.bz2 |
| 解压缩 | tar -jxv -f filename.tar.bz2 -C 要解压缩的目录 | | 解压缩 | tar -jxv -f filename.tar.bz2 -C 要解压缩的目录 |
# Bash # 七、Bash
可以通过 Shell 请求内核提供服务Bash 正是 Shell 的一种。 可以通过 Shell 请求内核提供服务Bash 正是 Shell 的一种。
## Bash 特性 ## Bash 特性
**1. 命令历史** 1. 命令历史:记录使用过的命令。本次登录所执行的命令都会暂时存放到内存中, \~/.bash_history 文件中记录的是前一次登录所执行过的命令。
记录使用过的命令。本次登录所执行的命令都会暂时存放到内存中, \~/.bash_history 文件中记录的是前一次登录所执行过的命令 2. 命令与文件补全快捷键tab
**2. 命令与文件补全** 3. 命名别名:例如 lm 是 ls -al 的别名。
快捷键tab 4. shell scripts。
**3. 命名别名** 5. 通配符:例如 ls -l /usr/bin/X\* 列出 /usr/bin 下面所有以 X 开头的文件。
例如 lm 是 ls -al 的别名。
**4. shell scripts**
**5. 通配符**
例如 ls -l /usr/bin/X\* 列出 /usr/bin 下面所有以 X 开头的文件。
## 变量操作 ## 变量操作
@ -925,7 +885,7 @@ $ split [-bl] file PREFIX
- PREFIX :分区文件的前导名称 - PREFIX :分区文件的前导名称
``` ```
# 正规表示法与文件格式化处理 # 八、正规表示法与文件格式化处理
## grep ## grep
@ -952,7 +912,7 @@ $ grep -n 'the' regular_express.txt
18:google is the best tools for search keyword 18:google is the best tools for search keyword
``` ```
因为 { 与 } 的符号在 shell 是有特殊意义的,因此必须要使用使用转义字符进行转义。 因为 { 与 } 的符号在 shell 是有特殊意义的,因此必须要使用转义字符进行转义。
```html ```html
$ grep -n 'go\{2,5\}g' regular_express.txt $ grep -n 'go\{2,5\}g' regular_express.txt
@ -973,6 +933,8 @@ $ printf '%10s %5i %5i %5i %8.2f \n' $(cat printf.txt)
## awk ## awk
可以根据字段的某些条件进行匹配,例如匹配字段小于某个值的那一行数据。
```html ```html
$ awk '条件类型 1 {动作 1} 条件类型 2 {动作 2} ...' filename $ awk '条件类型 1 {动作 1} 条件类型 2 {动作 2} ...' filename
``` ```
@ -1022,7 +984,7 @@ bin 1
daemon 2 daemon 2
``` ```
# vim 三个模式 # 九、vim 三个模式
<div align="center"> <img src="../pics//341c632a-1fc1-4068-9b9f-bf7ef68ebb4c.jpg"/> </div><br> <div align="center"> <img src="../pics//341c632a-1fc1-4068-9b9f-bf7ef68ebb4c.jpg"/> </div><br>

View File

@ -1,48 +1,33 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [存储引擎](#存储引擎) * [一、存储引擎](#一存储引擎)
* [1. InnoDB](#1-innodb) * [InnoDB](#innodb)
* [2. MyISAM](#2-myisam) * [MyISAM](#myisam)
* [3. InnoDB 与 MyISAM 的比较](#3-innodb-与-myisam-的比较) * [比较](#比较)
* [数据类型](#数据类型) * [二、数据类型](#二数据类型)
* [1. 整型](#1-整型) * [整型](#整型)
* [2. 浮点数](#2-浮点数) * [浮点数](#浮点数)
* [3. 字符串](#3-字符串) * [字符串](#字符串)
* [4. 时间和日期](#4-时间和日期) * [时间和日期](#时间和日期)
* [索引](#索引) * [三、索引](#三索引)
* [1. 索引分类](#1-索引分类) * [索引分类](#索引分类)
* [1.1 B-Tree 索引](#11-b-tree-索引) * [索引的优点](#索引的优点)
* [1.2 哈希索引](#12-哈希索引) * [索引优化](#索引优化)
* [1.3. 空间索引R-Tree](#13-空间索引r-tree) * [B-Tree 和 B+Tree 原理](#b-tree-和-b+tree-原理)
* [1.4 全文索引](#14-全文索引) * [四、查询性能优化](#四查询性能优化)
* [2. 索引的优点](#2-索引的优点) * [五、分库与分表](#五分库与分表)
* [3. 索引优化](#3-索引优化) * [原因](#原因)
* [3.1 独立的列](#31-独立的列) * [实现方式](#实现方式)
* [3.2 前缀索引](#32-前缀索引) * [Merge 存储引擎](#merge-存储引擎)
* [3.3 多列索引](#33-多列索引) * [存在的问题](#存在的问题)
* [3.4 索引列的顺序](#34-索引列的顺序) * [分表与分区的不同](#分表与分区的不同)
* [3.5 聚簇索引](#35-聚簇索引) * [六、故障转移和故障恢复](#六故障转移和故障恢复)
* [3.6 覆盖索引](#36-覆盖索引)
* [4. B-Tree 和 B+Tree 原理](#4-b-tree-和-b+tree-原理)
* [4. 1 B-Tree](#4-1-b-tree)
* [4.2 B+Tree](#42-b+tree)
* [4.3 带有顺序访问指针的 B+Tree](#43-带有顺序访问指针的-b+tree)
* [4.4 为什么使用 B-Tree 和 B+Tree](#44-为什么使用-b-tree-和-b+tree)
* [查询性能优化](#查询性能优化)
* [1. Explain](#1-explain)
* [2. 减少返回的列](#2-减少返回的列)
* [3. 减少返回的行](#3-减少返回的行)
* [4. 拆分大的 DELETE 或 INSERT 语句](#4-拆分大的-delete-或-insert-语句)
* [分库与分表](#分库与分表)
* [故障转移和故障恢复](#故障转移和故障恢复)
* [1. 故障转移](#1-故障转移)
* [2. 故障恢复](#2-故障恢复)
* [参考资料](#参考资料) * [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
# 存储引擎 # 一、存储引擎
## 1. InnoDB ## InnoDB
InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支持的特性时,才考虑使用其它存储引擎。 InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支持的特性时,才考虑使用其它存储引擎。
@ -54,7 +39,7 @@ InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支
通过一些机制和工具支持真正的热备份。 通过一些机制和工具支持真正的热备份。
## 2. MyISAM ## MyISAM
MyISAM 提供了大量的特性包括全文索引、压缩、空间函数GIS等。但 MyISAM 不支持事务和行级锁,而且崩溃后无法安全恢复。 MyISAM 提供了大量的特性包括全文索引、压缩、空间函数GIS等。但 MyISAM 不支持事务和行级锁,而且崩溃后无法安全恢复。
@ -72,43 +57,29 @@ MyISAM 提供了大量的特性包括全文索引、压缩、空间函数G
MyISAM 设计简单,数据以紧密格式存储,所以在某些场景下性能很好。 MyISAM 设计简单,数据以紧密格式存储,所以在某些场景下性能很好。
## 3. InnoDB 与 MyISAM 的比较 ## 比较
**事务** 1. 事务InnoDB 是事务型的。
2. 备份InnoDB 支持在线热备份。
3. 崩溃恢复MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。
4. 并发MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。
5. 其它特性MyISAM 支持全文索引,地理空间索引。
InnoDB 是事务型的。 # 二、数据类型
**备份** ## 整型
InnoDB 支持在线热备份。
**崩溃恢复**
MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。
**并发**
MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。
**其它特性**
MyISAM 支持全文索引,地理空间索引。
# 数据类型
## 1. 整型
TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT 分别使用 8, 16, 24, 32, 64 位存储空间,一般情况下越小的列越好。 TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT 分别使用 8, 16, 24, 32, 64 位存储空间,一般情况下越小的列越好。
INT(11) 中的数字只是规定了交互工具显示字符的个数,对于存储和计算来说是没有意义的。 INT(11) 中的数字只是规定了交互工具显示字符的个数,对于存储和计算来说是没有意义的。
## 2. 浮点数 ## 浮点数
FLOAT 和 DOUBLE 为浮点类型DECIMAL 为高精度小数类型。CPU 原生支持浮点运算,但是不支持 DECIMAl 类型的计算,因此 DECIMAL 的计算比浮点类型需要更高的代价。 FLOAT 和 DOUBLE 为浮点类型DECIMAL 为高精度小数类型。CPU 原生支持浮点运算,但是不支持 DECIMAl 类型的计算,因此 DECIMAL 的计算比浮点类型需要更高的代价。
FLOAT、DOUBLE 和 DECIMAL 都可以指定列宽,例如 DECIMAL(18, 9) 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分。 FLOAT、DOUBLE 和 DECIMAL 都可以指定列宽,例如 DECIMAL(18, 9) 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分。
## 3. 字符串 ## 字符串
主要有 CHAR 和 VARCHAR 两种类型,一种是定长的,一种是变长的。 主要有 CHAR 和 VARCHAR 两种类型,一种是定长的,一种是变长的。
@ -116,11 +87,11 @@ VARCHAR 这种变长类型能够节省空间,因为只需要存储必要的内
VARCHAR 会保留字符串末尾的空格,而 CHAR 会删除。 VARCHAR 会保留字符串末尾的空格,而 CHAR 会删除。
## 4. 时间和日期 ## 时间和日期
MySQL 提供了两种相似的日期时间类型DATATIME 和 TIMESTAMP。 MySQL 提供了两种相似的日期时间类型DATATIME 和 TIMESTAMP。
**DATATIME** ### 1. DATATIME
能够保存从 1001 年到 9999 年的日期和时间,精度为秒,使用 8 字节的存储空间。 能够保存从 1001 年到 9999 年的日期和时间,精度为秒,使用 8 字节的存储空间。
@ -128,7 +99,7 @@ MySQL 提供了两种相似的日期时间类型DATATIME 和 TIMESTAMP。
默认情况下MySQL 以一种可排序的、无歧义的格式显示 DATATIME 值例如“2008-01-16 22:37:08”这是 ANSI 标准定义的日期和时间表示方法。 默认情况下MySQL 以一种可排序的、无歧义的格式显示 DATATIME 值例如“2008-01-16 22:37:08”这是 ANSI 标准定义的日期和时间表示方法。
**TIMESTAMP** ### 2. TIMESTAMP
和 UNIX 时间戳相同,保存从 1970 年 1 月 1 日午夜(格林威治时间)以来的秒数,使用 4 个字节,只能表示从 1970 年 到 2038 年。 和 UNIX 时间戳相同,保存从 1970 年 1 月 1 日午夜(格林威治时间)以来的秒数,使用 4 个字节,只能表示从 1970 年 到 2038 年。
@ -140,7 +111,7 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提
应该尽量使用 TIMESTAMP因为它比 DATETIME 空间效率更高。 应该尽量使用 TIMESTAMP因为它比 DATETIME 空间效率更高。
# 索引 # 三、索引
索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。 索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。
@ -148,9 +119,9 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提
对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效。对于中到大型的表,索引就非常有效。但是对于特大型的表,建立和使用索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效。对于中到大型的表,索引就非常有效。但是对于特大型的表,建立和使用索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。
## 1. 索引分类 ## 索引分类
### 1.1 B-Tree 索引 ### 1. B-Tree 索引
B-Tree 索引是大多数 MySQL 存储引擎的默认索引类型。 B-Tree 索引是大多数 MySQL 存储引擎的默认索引类型。
@ -162,7 +133,7 @@ B-Tree 索引是大多数 MySQL 存储引擎的默认索引类型。
如果不是按照索引列的顺序进行查找,则无法使用索引。 如果不是按照索引列的顺序进行查找,则无法使用索引。
### 1.2 哈希索引 ### 2. 哈希索引
基于哈希表实现,优点是查找非常快。 基于哈希表实现,优点是查找非常快。
@ -172,19 +143,19 @@ InnoDB 引擎有一个特殊的功能叫“自适应哈希索引”,当某个
限制:哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能影响并不明显;无法用于分组与排序;只支持精确查找,无法用于部分查找和范围查找;如果哈希冲突很多,查找速度会变得很慢。 限制:哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能影响并不明显;无法用于分组与排序;只支持精确查找,无法用于部分查找和范围查找;如果哈希冲突很多,查找速度会变得很慢。
### 1.3. 空间索引R-Tree ### 3. 空间索引R-Tree
MyISAM 存储引擎支持空间索引,可以用于地理数据存储。 MyISAM 存储引擎支持空间索引,可以用于地理数据存储。
空间索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。 空间索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。
### 1.4 全文索引 ### 4. 全文索引
MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较索引中的值。 MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较索引中的值。
使用 MATCH AGAINST而不是普通的 WHERE。 使用 MATCH AGAINST而不是普通的 WHERE。
## 2. 索引的优点 ## 索引的优点
- 大大减少了服务器需要扫描的数据量; - 大大减少了服务器需要扫描的数据量;
@ -192,9 +163,9 @@ MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而
- 将随机 I/O 变为顺序 I/O。 - 将随机 I/O 变为顺序 I/O。
## 3. 索引优化 ## 索引优化
### 3.1 独立的列 ### 1. 独立的列
在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。 在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。
@ -204,22 +175,22 @@ MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而
SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5; SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;
``` ```
### 3.2 前缀索引 ### 2. 前缀索引
对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。 对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。
对于前缀长度的选取需要根据 **索引选择性** 来确定:不重复的索引值和记录总数的比值。选择性越高,查询效率也越高。最大值为 1 ,此时每个记录都有唯一的索引与其对应。 对于前缀长度的选取需要根据 **索引选择性** 来确定:不重复的索引值和记录总数的比值。选择性越高,查询效率也越高。最大值为 1 ,此时每个记录都有唯一的索引与其对应。
### 3.3 多列索引 ### 3. 多列索引
在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 file_id 设置为多列索引。 在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 film_id 设置为多列索引。
```sql ```sql
SELECT file_id, actor_ id FROM sakila.film_actor SELECT film_id, actor_ id FROM sakila.film_actor
WhERE actor_id = 1 OR film_id = 1; WhERE actor_id = 1 AND film_id = 1;
``` ```
### 3.4 索引列的顺序 ### 4. 索引列的顺序
让选择性最强的索引列放在前面,例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。 让选择性最强的索引列放在前面,例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。
@ -236,7 +207,7 @@ customer_id_selectivity: 0.0373
COUNT(*): 16049 COUNT(*): 16049
``` ```
### 3.5 聚簇索引 ### 5. 聚簇索引
<div align="center"> <img src="../pics//b9e9ae8c-e216-4c01-b267-a50dbeb98fa4.jpg"/> </div><br> <div align="center"> <img src="../pics//b9e9ae8c-e216-4c01-b267-a50dbeb98fa4.jpg"/> </div><br>
@ -246,12 +217,12 @@ customer_id_selectivity: 0.0373
因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。 因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
**优点** #### 优点
1. 可以把相关数据保存在一起,减少 I/O 操作; 1. 可以把相关数据保存在一起,减少 I/O 操作;
2. 因为数据保存在 B-Tree 中,因此数据访问更快。 2. 因为数据保存在 B-Tree 中,因此数据访问更快。
**缺点** #### 缺点
1. 聚簇索引最大限度提高了 I/O 密集型应用的性能,但是如果数据全部放在内存,就没必要用聚簇索引。 1. 聚簇索引最大限度提高了 I/O 密集型应用的性能,但是如果数据全部放在内存,就没必要用聚簇索引。
2. 插入速度严重依赖于插入顺序,按主键的顺序插入是最快的。 2. 插入速度严重依赖于插入顺序,按主键的顺序插入是最快的。
@ -259,13 +230,19 @@ customer_id_selectivity: 0.0373
4. 当插入到某个已满的页中,存储引擎会将该页分裂成两个页面来容纳该行,页分裂会导致表占用更多的磁盘空间。 4. 当插入到某个已满的页中,存储引擎会将该页分裂成两个页面来容纳该行,页分裂会导致表占用更多的磁盘空间。
5. 如果行比较稀疏,或者由于页分裂导致数据存储不连续时,聚簇索引可能导致全表扫描速度变慢。 5. 如果行比较稀疏,或者由于页分裂导致数据存储不连续时,聚簇索引可能导致全表扫描速度变慢。
### 3.6 覆盖索引 ### 6. 覆盖索引
索引包含所有需要查询的字段的值。 索引包含所有需要查询的字段的值。
## 4. B-Tree 和 B+Tree 原理 #### 优点
### 4. 1 B-Tree 1. 因为索引条目通常远小于数据行的大小,所以若只读取索引,能大大减少数据访问量。
2. 一些存储引擎(例如 MyISAM在内存中只缓存索引而数据依赖于操作系统来缓存。因此只访问索引可以不使用系统调用通常比较费时
3. 对于 InnoDB 引擎,若二级索引能够覆盖查询,则无需访问聚簇索引。
## B-Tree 和 B+Tree 原理
### 1. B-Tree
<div align="center"> <img src="../pics//5ed71283-a070-4b21-85ae-f2cbfd6ba6e1.jpg"/> </div><br> <div align="center"> <img src="../pics//5ed71283-a070-4b21-85ae-f2cbfd6ba6e1.jpg"/> </div><br>
@ -281,7 +258,7 @@ B-Tree 是满足下列条件的数据结构:
由于插入删除新的数据记录会破坏 B-Tree 的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作以保持 B-Tree 性质。 由于插入删除新的数据记录会破坏 B-Tree 的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作以保持 B-Tree 性质。
### 4.2 B+Tree ### 2. B+Tree
<div align="center"> <img src="../pics//63cd5b50-d6d8-4df6-8912-ef4a1dd5ba13.jpg"/> </div><br> <div align="center"> <img src="../pics//63cd5b50-d6d8-4df6-8912-ef4a1dd5ba13.jpg"/> </div><br>
@ -290,13 +267,13 @@ B-Tree 是满足下列条件的数据结构:
- 每个节点的指针上限为 2d 而不是 2d+1 - 每个节点的指针上限为 2d 而不是 2d+1
- 内节点不存储 data只存储 key叶子节点不存储指针。 - 内节点不存储 data只存储 key叶子节点不存储指针。
### 4.3 带有顺序访问指针的 B+Tree ### 3. 带有顺序访问指针的 B+Tree
<div align="center"> <img src="../pics//1ee5f0a5-b8df-43b9-95ab-c516c54ec797.jpg"/> </div><br> <div align="center"> <img src="../pics//1ee5f0a5-b8df-43b9-95ab-c516c54ec797.jpg"/> </div><br>
一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 基础上进行了优化,在叶子节点增加了顺序访问指针,做这个优化的目的是为了提高区间访问的性能。 一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 基础上进行了优化,在叶子节点增加了顺序访问指针,做这个优化的目的是为了提高区间访问的性能。
### 4.4 为什么使用 B-Tree 和 B+Tree ### 4. 为什么使用 B-Tree 和 B+Tree
红黑树等数据结构也可以用来实现索引,但是文件系统及数据库系统普遍采用 B-/+Tree 作为索引结构。 红黑树等数据结构也可以用来实现索引,但是文件系统及数据库系统普遍采用 B-/+Tree 作为索引结构。
@ -306,9 +283,9 @@ B-Tree 是满足下列条件的数据结构:
B+Tree 更适合外存索引,原因和内节点出度 d 有关。由于 B+Tree 内节点去掉了 data 域,因此可以拥有更大的出度,拥有更好的性能。 B+Tree 更适合外存索引,原因和内节点出度 d 有关。由于 B+Tree 内节点去掉了 data 域,因此可以拥有更大的出度,拥有更好的性能。
# 查询性能优化 # 四、查询性能优化
## 1. Explain ### Explain
用来分析 SQL 语句,分析结果中比较重要的字段有: 用来分析 SQL 语句,分析结果中比较重要的字段有:
@ -318,23 +295,23 @@ B+Tree 更适合外存索引,原因和内节点出度 d 有关。由于 B+Tree
- rows : 扫描的行数 - rows : 扫描的行数
## 2. 减少返回的列 ### 减少返回的列
慢查询主要是因为访问了过多数据,除了访问过多行之外,也包括访问过多列。 慢查询主要是因为访问了过多数据,除了访问过多行之外,也包括访问过多列。
最好不要使用 SELECT * 语句,要根据需要选择查询的列。 最好不要使用 SELECT * 语句,要根据需要选择查询的列。
## 3. 减少返回的行 ### 减少返回的行
最好使用 LIMIT 语句来取出想要的那些行。 最好使用 LIMIT 语句来取出想要的那些行。
还可以建立索引来减少条件语句的全表扫描。例如对于下面的语句,不用索引的情况下需要进行全表扫描,而使用索引只需要扫描几行记录即可,使用 Explain 语句可以通过观察 rows 字段来看出这种差异。 还可以建立索引来减少条件语句的全表扫描。例如对于下面的语句,不使用索引的情况下需要进行全表扫描,而使用索引只需要扫描几行记录即可,使用 Explain 语句可以通过观察 rows 字段来看出这种差异。
```sql ```sql
SELECT * FROM sakila.film_actor WHERE film_id = 1; SELECT * FROM sakila.film_actor WHERE film_id = 1;
``` ```
## 4. 拆分大的 DELETE 或 INSERT 语句 ### 拆分大的 DELETE 或 INSERT 语句
如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。 如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。
@ -349,74 +326,75 @@ do {
} while rows_affected > 0 } while rows_affected > 0
``` ```
# 分库与分表 # 五、分库与分表
**1. 分表与分区的不同** ## 原因
分表,就是讲一张表分成多个小表,这些小表拥有不同的表名;而分区是将一张表的数据分为多个区块,这些区块可以存储在同一个磁盘上,也可以存储在不同的磁盘上,这种方式下表仍然只有一个。
**2. 使用分库与分表的原因**
随着时间和业务的发展,数据库中的表会越来越多,并且表中的数据量也会越来越大,那么读写操作的开销也会随着增大。 随着时间和业务的发展,数据库中的表会越来越多,并且表中的数据量也会越来越大,那么读写操作的开销也会随着增大。
**3. 垂直切分** ## 实现方式
### 1. 垂直切分
将表按功能模块、关系密切程度划分出来,部署到不同的库上。例如,我们会建立商品数据库 payDB、用户数据库 userDB 等,分别用来存储项目与商品有关的表和与用户有关的表。 将表按功能模块、关系密切程度划分出来,部署到不同的库上。例如,我们会建立商品数据库 payDB、用户数据库 userDB 等,分别用来存储项目与商品有关的表和与用户有关的表。
**4. 水平切分** ### 2. 水平切分
把表中的数据按照某种规则存储到多个结构相同的表中,例如按 id 的散列值、性别等进行划分 把表中的数据按照某种规则存储到多个结构相同的表中,例如按 id 的散列值、性别等进行划分
**5. 垂直切分与水平切分的选择** ### 3. 切分的选择
如果数据库中的表太多,并且项目各项业务逻辑清晰,那么垂直切分是首选。 如果数据库中的表太多,并且项目各项业务逻辑清晰,那么垂直切分是首选。
如果数据库的表不多,但是单表的数据量很大,应该选择水平切分。 如果数据库的表不多,但是单表的数据量很大,应该选择水平切分。
**6. 水平切分的实现方式** ## Merge 存储引擎
最简单的是使用 merge 存储引擎 该存储引擎支持分表
**7. 分库与分表存在的问题** ## 存在的问题
(1) 事务问题 ### 1. 事务问题
在执行分库分表之后,由于数据存储到了不同的库上,数据库事务管理出现了困难。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价;如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。 在执行分库分表之后,由于数据存储到了不同的库上,数据库事务管理出现了困难。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价;如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。
(2) 跨库跨表连接问题 ### 2. 跨库跨表连接问题
在执行了分库分表之后,难以避免会将原本逻辑关联性很强的数据划分到不同的表、不同的库上。这时,表的连接操作将受到限制,我们无法连接位于不同分库的表,也无法连接分表粒度不同的表,导致原本只需要一次查询就能够完成的业务需要进行多次才能完成。 在执行了分库分表之后,难以避免会将原本逻辑关联性很强的数据划分到不同的表、不同的库上。这时,表的连接操作将受到限制,我们无法连接位于不同分库的表,也无法连接分表粒度不同的表,导致原本只需要一次查询就能够完成的业务需要进行多次才能完成。
# 故障转移和故障恢复 ### 3. 额外的数据管理负担和数据运算压力
最显而易见的就是数据的定位问题和数据的增删改查的重复执行问题,这些都可以通过应用程序解决,但必然引起额外的逻辑运算。
## 分表与分区的不同
分表,就是将一张表分成多个小表,这些小表拥有不同的表名;而分区是将一张表的数据分为多个区块,这些区块可以存储在同一个磁盘上,也可以存储在不同的磁盘上,这种方式下表仍然只有一个。
# 六、故障转移和故障恢复
故障转移也叫做切换,当主库出现故障时就切换到备库,使备库成为主库。故障恢复顾名思义就是从故障中恢复过来,并且保证数据的正确性。 故障转移也叫做切换,当主库出现故障时就切换到备库,使备库成为主库。故障恢复顾名思义就是从故障中恢复过来,并且保证数据的正确性。
## 1. 故障转移 ### 提升备库或切换角色
**1.1 提升备库或切换角色**
提升一台备库为主库,或者在一个主-主复制结构中调整主动和被动角色。 提升一台备库为主库,或者在一个主-主复制结构中调整主动和被动角色。
**1.2 虚拟 IP 地址和 IP 托管** ### 虚拟 IP 地址和 IP 托管
为 MySQL 实例指定一个逻辑 IP 地址,当 MySQL 实例失效时,可以将 IP 地址转移到另一台 MySQL 服务器上。 为 MySQL 实例指定一个逻辑 IP 地址,当 MySQL 实例失效时,可以将 IP 地址转移到另一台 MySQL 服务器上。
**1.3 中间件解决方案** ### 中间件解决方案
通过代理,可以路由流量到可以使用的服务器上。 通过代理,可以路由流量到可以使用的服务器上。
<div align="center"> <img src="../pics//fabd5fa0-b75e-48d0-9e2c-31471945ceb9.jpg"/> </div><br> <div align="center"> <img src="../pics//fabd5fa0-b75e-48d0-9e2c-31471945ceb9.jpg"/> </div><br>
**1.4 在应用中处理故障转移** ### 在应用中处理故障转移
将故障转移整合到应用中可能导致应用变得太过笨拙。 将故障转移整合到应用中可能导致应用变得太过笨拙。
## 2. 故障恢复
# 参考资料 # 参考资料
- 高性能 MySQL - 高性能 MySQL
- [MySQL 索引背后的数据结构及算法原理 ](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) - [MySQL 索引背后的数据结构及算法原理 ](http://blog.codinglabs.org/articles/theory-of-mysql-index.html)
- [MySQL 索引优化全攻略 ](http://www.runoob.com/w3cnote/mysql-index.html)
- [20+ 条 MySQL 性能优化的最佳经验 ](https://www.jfox.info/20-tiao-mysql-xing-nen-you-hua-de-zui-jia-jing-yan.html) - [20+ 条 MySQL 性能优化的最佳经验 ](https://www.jfox.info/20-tiao-mysql-xing-nen-you-hua-de-zui-jia-jing-yan.html)
- [数据库为什么分库分表mysql的分库分表方案](https://www.i3geek.com/archives/1108)

460
notes/Redis.md Normal file
View File

@ -0,0 +1,460 @@
<!-- GFM-TOC -->
* [一、Redis 是什么](#一redis-是什么)
* [二、五种基本类型](#二五种基本类型)
* [1. STRING](#1-string)
* [2. LIST](#2-list)
* [3. SET](#3-set)
* [4. HASH](#4-hash)
* [5. ZSET](#5-zset)
* [三、键的过期时间](#三键的过期时间)
* [四、发布与订阅](#四发布与订阅)
* [五、事务](#五事务)
* [六、持久化](#六持久化)
* [1. 快照持久化](#1-快照持久化)
* [2. AOF 持久化](#2-aof-持久化)
* [七、复制](#七复制)
* [从服务器连接主服务器的过程](#从服务器连接主服务器的过程)
* [主从链](#主从链)
* [八、处理故障](#八处理故障)
* [九、分片](#九分片)
* [1. 客户端分片](#1-客户端分片)
* [2. 代理分片](#2-代理分片)
* [3. 服务器分片](#3-服务器分片)
* [十、事件](#十事件)
* [事件类型](#事件类型)
* [事件的调度与执行](#事件的调度与执行)
* [十一、Redis 与 Memcached 的区别](#十一redis-与-memcached-的区别)
* [数据类型](#数据类型)
* [数据持久化](#数据持久化)
* [分布式](#分布式)
* [内存管理机制](#内存管理机制)
* [十二、Redis 适用场景](#十二redis-适用场景)
* [缓存](#缓存)
* [消息队列](#消息队列)
* [计数器](#计数器)
* [好友关系](#好友关系)
* [十三、数据淘汰策略](#十三数据淘汰策略)
* [十四、一个简单的论坛系统分析](#十四一个简单的论坛系统分析)
* [文章信息](#文章信息)
* [点赞功能](#点赞功能)
* [对文章进行排序](#对文章进行排序)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
# 一、Redis 是什么
Redis 是速度非常快的非关系型NoSQL内存键值数据库可以存储键和五种不同类型的值之间的映射。
五种类型数据类型为:字符串、列表、集合、有序集合、散列表。
Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片来扩展写性能。
# 二、五种基本类型
| 数据类型 | 可以存储的值 | 操作 |
| -- | -- | -- |
| STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作</br> 对整数和浮点数执行自增或者自减操作 |
| LIST | 链表 | 从两端压入或者弹出元素</br> 读取单个或者多个元素</br> 进行修剪,只保留一个范围内的元素 |
| SET | 无序集合 | 添加、获取、移除单个元素</br> 检查一个元素是否存在于集合中</br> 计算交集、并集、差集</br> 从集合里面随机获取元素 |
| HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对</br> 获取所有键值对</br> 检查某个键是否存在|
| ZSET | 有序集合 | 添加、获取、删除元素个元素</br> 根据分值范围或者成员来获取元素</br> 计算一个键的排名 |
> [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/)
## 1. STRING
<div align="center"> <img src="../pics//6019b2db-bc3e-4408-b6d8-96025f4481d6.png" width="400"/> </div><br>
```html
> set hello world
OK
> get hello
"world"
> del hello
(integer) 1
> get hello
(nil)
```
## 2. LIST
<div align="center"> <img src="../pics//fb327611-7e2b-4f2f-9f5b-38592d408f07.png" width="400"/> </div><br>
```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"
```
## 3. SET
<div align="center"> <img src="../pics//cd5fbcff-3f35-43a6-8ffa-082a93ce0f0e.png" width="400"/> </div><br>
```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"
```
## 4. HASH
<div align="center"> <img src="../pics//7bd202a7-93d4-4f3a-a878-af68ae25539a.png" width="400"/> </div><br>
```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"
```
## 5. ZSET
<div align="center"> <img src="../pics//1202b2d6-9469-4251-bd47-ca6034fb6116.png" width="400"/> </div><br>
```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. 主服务器创建快照文件,发送给从服务器,并在发送期间使用缓冲区记录执行的写命令。快照文件发送完毕之后,开始向从服务器发送存储在缓冲区中的写命令;
2. 从服务器丢弃所有旧数据,载入主服务器发来的快照文件,之后从服务器开始接受主服务器发来的写命令;
3. 主服务器每执行一次写命令,就向从服务器发送相同的写命令。
## 主从链
随着负载不断上升,主服务器可能无法很快地更新所有从服务器,或者重新连接和重新同步从服务器将导致系统超载。为了解决这个问题,可以创建一个中间层来分担主服务器的复制工作。中间层的服务器是最上层服务器的从服务器,又是最下层服务器的主服务器。
<div align="center"> <img src="../pics//395a9e83-b1a1-4a1d-b170-d081e7bb5bab.png" width="600"/> </div><br>
# 八、处理故障
要用到持久化文件来恢复服务器的数据。
持久化文件可能因为服务器出错也有错误,因此要先对持久化文件进行验证和修复。对 AOF 文件就行验证和修复很容易,修复操作将第一个出错命令和其后的所有命令都删除;但是只能验证快照文件,无法对快照文件进行修复,因为快照文件进行了压缩,出现在快照文件中间的错误可能会导致整个快照文件的剩余部分无法读取。
当主服务器出现故障时Redis 常用的做法是新开一台服务器作为主服务器,具体步骤如下:假设 A 为主服务器B 为从服务器,当 A 出现故障时,让 B 生成一个快照文件,将快照文件发送给 C并让 C 恢复快照文件的数据。最后,让 B 成为 C 的从服务器。
# 九、分片
Redis 中的分片类似于 MySQL 的分表操作,分片是将数据划分为多个部分的方法,对数据的划分可以基于键包含的 ID、基于键的哈希值或者基于以上两者的某种组合。通过对数据进行分片用户可以将数据存储到多台机器里面也可以从多台机器里面获取数据这种方法在解决某些问题时可以获得线性级别的性能提升。
假设有 4 个 Reids 实例 R0R1R2R3还有很多表示用户的键 user:1user:2... 等等,有不同的方式来选择一个指定的键存储在哪个实例中。最简单的方式是范围分片,例如用户 id 从 0\~1000 的存储到实例 R0 中,用户 id 从 1001\~2000 的存储到实例 R1 中,等等。但是这样需要维护一张映射范围表,维护操作代价很高。还有一种方式是哈希分片,使用 CRC32 哈希函数将键转换为一个数字,再对实例数量求模就能知道应该存储的实例。
## 1. 客户端分片
客户端使用一致性哈希等算法决定键应当分布到哪个节点。
## 2. 代理分片
将客户端请求发送到代理上,由代理转发请求到正确的节点上。
## 3. 服务器分片
Redis Cluster。
# 十、事件
## 事件类型
### 1. 文件事件
服务器有许多套接字,事件产生时会对这些套接字进行操作,服务器通过监听套接字来处理事件。常见的文件事件有:客户端的连接事件;客户端的命令请求事件;服务器向客户端返回命令结果的事件;
### 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()
```
从事件处理的角度来看,服务器运行流程如下:
<div align="center"> <img src="../pics//73b73189-9e95-47e5-91d0-9378b8462e15.png"/> </div><br>
# 十一、Redis 与 Memcached 的区别
两者都是非关系型内存键值数据库。有以下主要不同:
## 数据类型
Memcached 仅支持字符串类型,而 Redis 支持五种不同种类的数据类型,使得它可以更灵活地解决问题。
## 数据持久化
Redis 支持两种持久化策略RDB 快照和 AOF 日志,而 Memcached 不支持持久化。
## 分布式
Memcached 不支持分布式,只能通过在客户端使用像一致性哈希这样的分布式算法来实现分布式存储,这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点。
Redis Cluster 实现了分布式的支持。
## 内存管理机制
在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘。而 Memcached 的数据则会一直在内存中。
Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题,但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。
# 十二、Redis 适用场景
## 缓存
适用 Redis 作为缓存,将热点数据放到内存中。
## 消息队列
Redis 的 List 类型是双向链表,很适合用于消息队列。
## 计数器
Redis 这种内存数据库才能支持计数器的频繁读写操作。
## 好友关系
使用 set 类型的交集很容易就可以知道两个用户的共同好友。
# 十三、数据淘汰策略
可以设置内存最大使用量,当内存使用量超过时施行淘汰策略,具体有 6 种淘汰策略。
| 策略 | 描述 |
| -- | -- |
| volatile-lru | 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰 |
| volatile-ttl | 从已设置过期时间的数据集中挑选将要过期的数据淘汰 |
|volatile-random | 从已设置过期时间的数据集中任意选择数据淘汰 |
| allkeys-lru | 从所有数据集中挑选最近最少使用的数据淘汰 |
| allkeys-random | 从所有数据集中任意选择数据进行淘汰 |
| no-envicition | 禁止驱逐数据 |
如果使用 Redis 来缓存数据时,要保证所有数据都是热点数据,可以将内存最大使用量设置为热点数据占用的内存量,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰。
# 十四、一个简单的论坛系统分析
该论坛系统功能如下:
- 可以发布文章;
- 可以对文章进行点赞;
- 在首页可以按文章的发布时间或者文章的点赞数进行排序显示;
## 文章信息
文章包括标题、作者、赞数等信息,在关系型数据库中很容易构建一张表来存储这些信息,在 Redis 中可以使用 HASH 来存储每种信息以及其对应的值的映射。
Redis 没有关系型数据库中的表这一概念来将同类型的数据存放在一起,而是使用命名空间的方式来实现这一功能。键名的前面部分存储命名空间,后面部分的内容存储 ID通常使用 : 来进行分隔。例如下面的 HASH 的键名为 article:92617其中 article 为命名空间ID 为 92617。
<div align="center"> <img src="../pics//7c54de21-e2ff-402e-bc42-4037de1c1592.png" width="400"/> </div><br>
## 点赞功能
当有用户为一篇文章点赞时,除了要对该文章的 votes 字段进行加 1 操作,还必须记录该用户已经对该文章进行了点赞,防止用户点赞次数超过 1。可以建立文章的已投票用户集合来进行记录。
为了节约内存,规定一篇文章发布满一周之后,就不能再对它进行投票,而文章的已投票集合也会被删除,可以为文章的已投票集合设置一个一周的过期时间就能实现这个规定。
<div align="center"> <img src="../pics//485fdf34-ccf8-4185-97c6-17374ee719a0.png" width="400"/> </div><br>
## 对文章进行排序
为了按发布时间和点赞数进行排序,可以建立一个文章发布时间的有序集合和一个文章点赞数的有序集合。(下图中的 score 就是这里所说的点赞数;下面所示的有序集合分值并不直接是时间和点赞数,而是根据时间和点赞数间接计算出来的)
<div align="center"> <img src="../pics//f7d170a3-e446-4a64-ac2d-cb95028f81a8.png" width="800"/> </div><br>
# 参考资料
- 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/)

View File

@ -1,39 +1,31 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [基础](#基础) * [一、基础](#一基础)
* [创建表](#创建表) * [二、创建表](#二创建表)
* [插入](#插入) * [三、修改表](#三修改表)
* [更新](#更新) * [四、插入](#四插入)
* [删除](#删除) * [五、更新](#五更新)
* [修改表](#修改表) * [六、删除](#六删除)
* [查询](#查询) * [七、查询](#七查询)
* [排序](#排序) * [八、排序](#八排序)
* [过滤](#过滤) * [九、过滤](#九过滤)
* [通配符](#通配符) * [十、通配符](#十通配符)
* [计算字段](#计算字段) * [十一、计算字段](#十一计算字段)
* [函数](#函数) * [十二、函数](#十二函数)
* [文本处理](#文本处理) * [十三、分组](#十三分组)
* [日期和时间处理](#日期和时间处理) * [十四、子查询](#十四子查询)
* [数值处理](#数值处理) * [十五、连接](#十五连接)
* [汇总](#汇总) * [十六、组合查询](#十六组合查询)
* [分组](#分组) * [十七、视图](#十七视图)
* [子查询](#子查询) * [十八、存储过程](#十八存储过程)
* [连接](#连接) * [十九、游标](#十九游标)
* [内连接](#内连接) * [二十、触发器](#二十触发器)
* [自连接](#自连接) * [二十一、事务处理](#二十一事务处理)
* [自然连接](#自然连接) * [二十二、字符集](#二十二字符集)
* [外连接](#外连接) * [二十三、权限管理](#二十三权限管理)
* [组合查询](#组合查询)
* [视图](#视图)
* [存储过程](#存储过程)
* [游标](#游标)
* [触发器](#触发器)
* [事务处理](#事务处理)
* [字符集](#字符集)
* [权限管理](#权限管理)
<!-- GFM-TOC --> <!-- GFM-TOC -->
# 基础 # 一、基础
模式定义了数据如何存储、存储什么样的数据以及数据如何分解等信息,数据库和表都有模式。 模式定义了数据如何存储、存储什么样的数据以及数据如何分解等信息,数据库和表都有模式。
@ -53,7 +45,7 @@ FROM mytable; -- 注释
注释2 */ 注释2 */
``` ```
# 创建表 # 二、创建表
```sql ```sql
CREATE TABLE mytable ( CREATE TABLE mytable (
@ -64,16 +56,38 @@ CREATE TABLE mytable (
PRIMARY KEY (`id`)); PRIMARY KEY (`id`));
``` ```
# 插入 # 三、修改表
**普通插入** 添加列
```sql
ALTER TABLE mytable
ADD col CHAR(20);
```
删除列
```sql
ALTER TABLE mytable
DROP COLUMN col;
```
删除表
```sql
DROP TABLE mytable;
```
# 四、插入
普通插入
```sql ```sql
INSERT INTO mytable(col1, col2) INSERT INTO mytable(col1, col2)
VALUES(val1, val2); VALUES(val1, val2);
``` ```
**插入检索出来的数据** 插入检索出来的数据
```sql ```sql
INSERT INTO mytable1(col1, col2) INSERT INTO mytable1(col1, col2)
@ -81,14 +95,14 @@ SELECT col1, col2
FROM mytable2; FROM mytable2;
``` ```
**将一个表的内容复制到一个新表** 将一个表的内容插入到一个新表
```sql ```sql
CREATE TABLE newtable AS CREATE TABLE newtable AS
SELECT * FROM mytable; SELECT * FROM mytable;
``` ```
# 更新 # 五、更新
```sql ```sql
UPDATE mytable UPDATE mytable
@ -96,7 +110,7 @@ SET col = val
WHERE id = 1; WHERE id = 1;
``` ```
# 删除 # 六、删除
```sql ```sql
DELETE FROM mytable DELETE FROM mytable
@ -107,31 +121,9 @@ WHERE id = 1;
使用更新和删除操作时一定要用 WHERE 子句,不然会把整张表的数据都破坏。可以先用 SELECT 语句进行测试,防止错误删除。 使用更新和删除操作时一定要用 WHERE 子句,不然会把整张表的数据都破坏。可以先用 SELECT 语句进行测试,防止错误删除。
# 修改表 # 七、查询
**添加列** ## DISTINCT
```sql
ALTER TABLE mytable
ADD col CHAR(20);
```
**删除列**
```sql
ALTER TABLE mytable
DROP COLUMN col;
```
**删除表**
```sql
DROP TABLE mytable;
```
# 查询
**DISTINCT**
相同值只会出现一次。它作用于所有列,也就是说所有列的值都相同才算相同。 相同值只会出现一次。它作用于所有列,也就是说所有列的值都相同才算相同。
@ -140,7 +132,7 @@ SELECT DISTINCT col1, col2
FROM mytable; FROM mytable;
``` ```
**LIMIT** ## LIMIT
限制返回的行数。可以有两个参数,第一个参数为起始行,从 0 开始;第二个参数为返回的总行数。 限制返回的行数。可以有两个参数,第一个参数为起始行,从 0 开始;第二个参数为返回的总行数。
@ -167,7 +159,7 @@ LIMIT 2, 3;
``` ```
# 排序 # 八、排序
- **ASC** :升序(默认) - **ASC** :升序(默认)
- **DESC** :降序 - **DESC** :降序
@ -180,7 +172,7 @@ FROM mytable
ORDER BY col1 DESC, col2 ASC; ORDER BY col1 DESC, col2 ASC;
``` ```
# 过滤 # 九、过滤
不进行过滤的数据非常大,导致通过网络传输了很多多余的数据,从而浪费了网络带宽。因此尽量使用 SQL 语句来过滤不必要的数据,而不是传输所有的数据到客户端中然后由客户端进行过滤。 不进行过滤的数据非常大,导致通过网络传输了很多多余的数据,从而浪费了网络带宽。因此尽量使用 SQL 语句来过滤不必要的数据,而不是传输所有的数据到客户端中然后由客户端进行过滤。
@ -209,7 +201,7 @@ WHERE col IS NULL;
**NOT** 操作符用于否定一个条件。 **NOT** 操作符用于否定一个条件。
# 通配符 # 十、通配符
通配符也是用在过滤语句中,但它只能用于文本字段。 通配符也是用在过滤语句中,但它只能用于文本字段。
@ -228,7 +220,7 @@ WHERE col LIKE '[^AB]%' -- 不以 A 和 B 开头的任意文本
``` ```
不要滥用通配符,通配符位于开头处匹配会非常慢。 不要滥用通配符,通配符位于开头处匹配会非常慢。
# 计算字段 # 十一、计算字段
在数据库服务器上完成数据的转换和格式化的工作往往比客户端上快得多,并且转换和格式化后的数据量更少的话可以减少网络通信量。 在数据库服务器上完成数据的转换和格式化的工作往往比客户端上快得多,并且转换和格式化后的数据量更少的话可以减少网络通信量。
@ -239,14 +231,14 @@ SELECT col1*col2 AS alias
FROM mytable FROM mytable
``` ```
**Concat()** 用于连接两个字段。许多数据库会使用空格把一个值填充为列宽,因此连接的结果会出现一些不必要的空格,使用 **TRIM()** 可以去除首尾空格。 **CONCAT()** 用于连接两个字段。许多数据库会使用空格把一个值填充为列宽,因此连接的结果会出现一些不必要的空格,使用 **TRIM()** 可以去除首尾空格。
```sql ```sql
SELECT Concat(TRIM(col1), ' (', TRIM(col2), ')') SELECT CONCAT(TRIM(col1), ' (', TRIM(col2), ')')
FROM mytable FROM mytable
``` ```
# 函数 # 十二、函数
各个 DBMS 的函数都是不相同的,因此不可移植。 各个 DBMS 的函数都是不相同的,因此不可移植。
@ -331,7 +323,7 @@ SELECT AVG(DISTINCT col1) AS avg_col
FROM mytable FROM mytable
``` ```
# 分组 # 十三、分组
分组就是把具有相同的数据值的行放在同一组中。 分组就是把具有相同的数据值的行放在同一组中。
@ -371,7 +363,7 @@ ORDER BY num;
3. NULL 的行会单独分为一组; 3. NULL 的行会单独分为一组;
4. 大多数 SQL 实现不支持 GROUP BY 列具有可变长度的数据类型。 4. 大多数 SQL 实现不支持 GROUP BY 列具有可变长度的数据类型。
# 子查询 # 十四、子查询
子查询中只能返回一个字段的数据。 子查询中只能返回一个字段的数据。
@ -395,7 +387,7 @@ FROM Customers
ORDER BY cust_name; ORDER BY cust_name;
``` ```
# 连接 # 十五、连接
连接用于连接多个表,使用 JOIN 关键字,并且条件语句使用 ON 而不是 Where。 连接用于连接多个表,使用 JOIN 关键字,并且条件语句使用 ON 而不是 Where。
@ -429,7 +421,7 @@ where A.key = B.key
一张员工表,包含员工姓名和员工所属部门,要找出与 Jim 处在同一部门的所有员工姓名。 一张员工表,包含员工姓名和员工所属部门,要找出与 Jim 处在同一部门的所有员工姓名。
**子查询版本** 子查询版本
```sql ```sql
select name select name
@ -440,7 +432,7 @@ where department = (
where name = "Jim"); where name = "Jim");
``` ```
**自连接版本** 自连接版本
```sql ```sql
select name select name
@ -484,7 +476,7 @@ on Customers.cust_id = Orders.curt_id
group by Customers.cust_id; group by Customers.cust_id;
``` ```
# 组合查询 # 十六、组合查询
使用 **UNION** 来组合两个查询,如果第一个查询返回 M 行,第二个查询返回 N 行,那么组合查询的结果为 M+N 行。 使用 **UNION** 来组合两个查询,如果第一个查询返回 M 行,第二个查询返回 N 行,那么组合查询的结果为 M+N 行。
@ -504,7 +496,7 @@ FROM mytable
WHERE col =2; WHERE col =2;
``` ```
# 视图 # 十七、视图
视图是虚拟的表,本身不包含数据,也就不能对其进行索引操作。对视图的操作和对普通表的操作一样。 视图是虚拟的表,本身不包含数据,也就不能对其进行索引操作。对视图的操作和对普通表的操作一样。
@ -522,17 +514,19 @@ FROM mytable
WHERE col5 = val; WHERE col5 = val;
``` ```
# 存储过程 # 十八、存储过程
存储过程可以看成是对一系列 SQL 操作的批处理; 存储过程可以看成是对一系列 SQL 操作的批处理;
**使用存储过程的好处** ## 使用存储过程的好处
1. 代码封装,保证了一定的安全性; 1. 代码封装,保证了一定的安全性;
2. 代码复用; 2. 代码复用;
3. 由于是预先编译,因此具有很高的性能。 3. 由于是预先编译,因此具有很高的性能。
**创建存储过程** ## 创建存储过程
命令行中创建存储过程需要自定义分隔符,因为命令行是以 ; 为结束符,而存储过程中也包含了分号,因此会错误把这部分分号当成是结束符,造成语法错误。 命令行中创建存储过程需要自定义分隔符,因为命令行是以 ; 为结束符,而存储过程中也包含了分号,因此会错误把这部分分号当成是结束符,造成语法错误。
@ -561,13 +555,13 @@ call myprocedure(@ret);
select @ret; select @ret;
``` ```
# 游标 # 十九、游标
在存储过程中使用游标可以对一个结果集进行移动遍历。 在存储过程中使用游标可以对一个结果集进行移动遍历。
游标主要用于交互式应用,其中用户需要对数据集中的任意行进行浏览和修改。 游标主要用于交互式应用,其中用户需要对数据集中的任意行进行浏览和修改。
**使用游标的四个步骤:** 使用游标的四个步骤:
1. 声明游标,这个过程没有实际检索出数据; 1. 声明游标,这个过程没有实际检索出数据;
2. 打开游标; 2. 打开游标;
@ -597,7 +591,7 @@ create procedure myprocedure(out ret int)
delimiter ; delimiter ;
``` ```
# 触发器 # 二十、触发器
触发器会在某个表执行以下语句时而自动执行DELETE、INSERT、UPDATE 触发器会在某个表执行以下语句时而自动执行DELETE、INSERT、UPDATE
@ -618,9 +612,9 @@ UPDATE 触发器包含一个名为 NEW 和一个名为 OLD 的虚拟表,其中
MySQL 不允许在触发器中使用 CALL 语句 ,也就是不能调用存储过程。 MySQL 不允许在触发器中使用 CALL 语句 ,也就是不能调用存储过程。
# 事务处理 # 二十一、事务处理
**基本术语** 基本术语:
1. 事务transaction指一组 SQL 语句; 1. 事务transaction指一组 SQL 语句;
2. 回退rollback指撤销指定 SQL 语句的过程; 2. 回退rollback指撤销指定 SQL 语句的过程;
@ -645,9 +639,9 @@ ROLLBACK TO delete1
COMMIT COMMIT
``` ```
# 字符集 # 二十二、字符集
**基本术语** 基本术语:
1. 字符集为字母和符号的集合; 1. 字符集为字母和符号的集合;
2. 编码为某个字符集成员的内部表示; 2. 编码为某个字符集成员的内部表示;
@ -669,7 +663,7 @@ FROM mytable
ORDER BY col COLLATE latin1_general_ci; ORDER BY col COLLATE latin1_general_ci;
``` ```
# 权限管理 # 二十三、权限管理
MySQL 的账户信息保存在 mysql 这个数据库中。 MySQL 的账户信息保存在 mysql 这个数据库中。
@ -678,7 +672,7 @@ USE mysql;
SELECT user FROM user; SELECT user FROM user;
``` ```
**创建账户** ## 创建账户
```sql ```sql
CREATE USER myuser IDENTIFIED BY 'mypassword'; CREATE USER myuser IDENTIFIED BY 'mypassword';
@ -686,25 +680,25 @@ CREATE USER myuser IDENTIFIED BY 'mypassword';
新创建的账户没有任何权限。 新创建的账户没有任何权限。
**修改账户名** ## 修改账户名
```sql ```sql
RENAME myuser TO newuser; RENAME myuser TO newuser;
``` ```
**删除账户** ## 删除账户
```sql ```sql
DROP USER myuser; DROP USER myuser;
``` ```
**查看权限** ## 查看权限
```sql ```sql
SHOW GRANTS FOR myuser; SHOW GRANTS FOR myuser;
``` ```
**授予权限** ## 授予权限
```sql ```sql
GRANT SELECT, INSERT ON mydatabase.* TO myuser; GRANT SELECT, INSERT ON mydatabase.* TO myuser;
@ -714,7 +708,7 @@ GRANT SELECT, INSERT ON mydatabase.* TO myuser;
账户用 username@host 的形式定义username@% 使用的是默认主机名。 账户用 username@host 的形式定义username@% 使用的是默认主机名。
**删除权限** ## 删除权限
```sql ```sql
REVOKE SELECT, INSERT ON mydatabase.* FROM myuser; REVOKE SELECT, INSERT ON mydatabase.* FROM myuser;
@ -728,7 +722,7 @@ GRANT 和 REVOKE 可在几个层次上控制访问权限:
- 特定的列; - 特定的列;
- 特定的存储过程。 - 特定的存储过程。
**更改密码** ## 更改密码
必须使用 Password() 函数 必须使用 Password() 函数

155
notes/一致性协议.md Normal file
View File

@ -0,0 +1,155 @@
<!-- GFM-TOC -->
* [一、两阶段提交协议](#一两阶段提交协议)
* [二、Paxos 协议](#二paxos-协议)
* [三、Raft 协议](#三raft-协议)
* [四、拜占庭将军问题](#四拜占庭将军问题)
* [五、参考资料](#五参考资料)
<!-- GFM-TOC -->
# 一、两阶段提交协议
Two-phase Commit2PC
可以保证一个事务跨越多个节点时保持 ACID 特性。
两类节点协调者Coordinator和参与者Participants协调者只有一个参与者可以有多个。
## 运行过程
1. 准备阶段:协调者询问参与者事务是否执行成功;
2. 提交阶段:如果事务在每个参与者上都执行成功,协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
<div align="center"> <img src="../pics//07717718-1230-4347-aa18-2041c315e670.jpg"/> </div><br>
需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段协接收到协调者发来的通知后,才进行提交或者回滚。
## 存在的问题
- 参与者发生故障。解决方案:可以给事务设置一个超时时间,如果某个参与者一直不响应,那么认为事务执行失败。
- 协调者发生故障。解决方案:将操作日志同步到备用协调者,让备用协调者接替后续工作。
# 二、Paxos 协议
用于达成共识性问题,即对多个节点产生的值,该算法能保证只选出唯一一个值。
主要有三类节点:
1. 提议者Proposer提议一个值
2. 接受者Acceptor对每个提议进行投票
3. 告知者Learner被告知投票的结果不参与投票的过程。
<div align="center"> <img src="../pics//0aaf4630-d2a2-4783-b3f7-a2b6a7dfc01b.jpg"/> </div><br>
## 执行过程
规定一个提议包含两个字段:[n, v],其中 n 为序号具有唯一性v 为提议值。
下图演示了两个 Proposer 和三个 Acceptor 的系统中运行该算法的初始过程,每个 Proposer 都会向每个 Acceptor 发送提议请求。
<div align="center"> <img src="../pics//2bf2fd8f-5ade-48ba-a2b3-74195ac77c4b.png" width="500"/> </div><br>
当 Acceptor 接收到一个提议请求,包含的提议为 [n1, v1],并且之前还未接收过提议请求,那么发送一个提议响应,设置当前接收的提议为 [n1, v1],并且保证以后不会再接受提议值小于 n1 的提议。
如下图Acceptor X 在收到 [n=2, v=8] 的提议请求时,由于之前没有接收过提议,因此就发送一个 [no previous] 的提议响应,并且设置当前接收的提议为 [n=2, v=8],并且保证以后不会再接受提议值小于 2 的提议。其它的 Acceptor 类似。
<div align="center"> <img src="../pics//3f5bba4b-7813-4aea-b578-970c7e3f6bf3.jpg" width="600"/> </div><br>
如果 Acceptor 接受到一个提议请求,包含的提议为 [n2, v2],并且之前已经接收过提议 [n1, v1]。如果 n1 > n2那么就丢弃该提议请求否则发送提议响应该提议响应包含之前已经接收过的提议 [n1, v1],设置当前接收的提议为 [n2, v2],并且保证以后不会再接受提议值小于 n2 的提议。
如下图Acceptor Z 收到 Proposer A 发来的 [n=2, v=8] 的提议请求,由于之前已经接收过 [n=4, v=5] 的提议,并且 n > 2因此就抛弃该提议请求Acceptor X 收到 Proposer B 发来的 [n=4, v=5] 的提议请求,因为之前接收的提议为 [n=2, v=8],并且 2 <= 4因此就发送 [n=2, v=8] 的提议响应,设置当前接收的提议为 [n=4, v=5],并且保证以后不会再接受提议值小于 4 的提议。Acceptor Y 类似。
<div align="center"> <img src="../pics//9b829410-86c4-40aa-ba8d-9e8e26c0eeb8.jpg" width="600"/> </div><br>
当一个 Proposer 接收到超过一半 Acceptor 的提议响应时,就可以发送接受请求。
如下图Proposer A 接受到两个提议响应之后,就发送 [n=2, v=8] 接受请求。该接受请求会被所有 Acceptor 丢弃,因为此时所有 Acceptor 都保证不接受提议值小于 4 的提议。Proposer B 过后也收到了两个提议响应,因此也开始发送接受请求。需要注意的是,接受请求的 v 需要取它收到的最大 v 值,也就是 8。因此它发送 [n=4, v=8] 的接受请求。
<div align="center"> <img src="../pics//2c4556e4-0751-4377-ab08-e7b89d697ca7.png" width="400"/> </div><br>
Acceptor 接收到接受请求时,如果提议号大于等于该 Acceptor 承诺的最小提议号,那么就发送通知给所有的 Learner。当 Learner 发现有大多数的 Acceptor 接收了某个提议,那么该提议的提议值就被 Paxos 选择出来。
<div align="center"> <img src="../pics//8adb2591-d3f1-4632-84cb-823fb9c5eb09.jpg" width="500"/> </div><br>
## 约束条件
### 1. 正确性
只有一个提议值会生效。
因为 Paxos 协议要求每个生效的提议被多数 Acceptor 接收,并且 Acceptor 不会接受两个不同的提议,因此可以保证正确性。
### 2. 可终止性
最后总会有一个提议生效。
Paxos 协议能够让 Proposer 发送的提议朝着能被大多数 Acceptor 接受的那个提议靠拢,因此能够保证可终止性。
# 三、Raft 协议
Raft 和 Poxas 类似,但是更容易理解,也更容易实现。
Raft 主要是用来竞选主节点。
## 单个 Candidate 的竞选
有三种节点Follower、Candidate 和 Leader。Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间,一般为 150ms\~300ms如果在这个时间内没有收到 Leader 的心跳包,就会变成 Candidate进入竞选阶段。
① 下图表示一个分布式系统的最初阶段,此时只有 Follower没有 Leader。Follower A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。
<div align="center"> <img src="../pics//111521118015898.gif"/> </div><br>
② 此时 A 发送投票请求给其它所有节点。
<div align="center"> <img src="../pics//111521118445538.gif"/> </div><br>
③ 其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。
<div align="center"> <img src="../pics//111521118483039.gif"/> </div><br>
④ 之后 Leader 会周期性地发送心跳包给 FollowerFollower 接收到心跳包,会重新开始计时。
<div align="center"> <img src="../pics//111521118640738.gif"/> </div><br>
## 多个 Candidate 竞选
① 如果有多个 Follower 成为 Candidate并且所获得票数相同那么就需要重新开始投票例如下图中 Candidate B 和 Candidate D 都获得两票,因此需要重新开始投票。
<div align="center"> <img src="../pics//111521119203347.gif"/> </div><br>
② 当重新开始投票时,由于每个节点设置的随机竞选超时时间不同,因此能下一次再次出现多个 Candidate 并获得同样票数的概率很低。
<div align="center"> <img src="../pics//111521119368714.gif"/> </div><br>
## 日志复制
① 来自客户端的修改都会被传入 Leader。注意该修改还未被提交只是写入日志中。
<div align="center"> <img src="../pics//7.gif"/> </div><br>
② Leader 会把修改复制到所有 Follower。
<div align="center"> <img src="../pics//9.gif"/> </div><br>
③ Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。
<div align="center"> <img src="../pics//10.gif"/> </div><br>
④ 此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。
<div align="center"> <img src="../pics//11.gif"/> </div><br>
# 四、拜占庭将军问题
> [拜占庭将军问题深入探讨](http://www.8btc.com/baizhantingjiangjun)
# 五、参考资料
- 杨传辉. 大规模分布式存储系统: 原理解析与架构实战[M]. 机械工业出版社, 2013.
- [区块链技术指南](https://www.gitbook.com/book/yeasy/blockchain_guide/details)
- [NEAT ALGORITHMS - PAXOS](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/)
- [Raft: Understandable Distributed Consensus](http://thesecretlivesofdata.com/raft)
- [Paxos By Example](https://angus.nyc/2012/paxos-by-example/)

206
notes/分布式基础.md Normal file
View File

@ -0,0 +1,206 @@
<!-- GFM-TOC -->
* [一、基本概念](#一基本概念)
* [异常](#异常)
* [超时](#超时)
* [衡量指标](#衡量指标)
* [二、数据分布](#二数据分布)
* [哈希分布](#哈希分布)
* [顺序分布](#顺序分布)
* [三、负载均衡](#三负载均衡)
* [四、复制](#四复制)
* [强同步复制协议](#强同步复制协议)
* [异步复制协议](#异步复制协议)
* [五、CAP](#五cap)
* [六、BASE](#六base)
* [基本可用](#基本可用)
* [软状态](#软状态)
* [最终一致性](#最终一致性)
* [七、容错](#七容错)
* [故障检测](#故障检测)
* [故障恢复](#故障恢复)
* [八、CDN 架构](#八cdn-架构)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
# 一、基本概念
## 异常
### 1. 服务器宕机
内存错误、服务器停电等都会导致服务器宕机,此时节点无法正常工作,称为不可用。
服务器宕机会导致节点失去所有内存信息,因此需要将内存信息保存到持久化介质上。
### 2. 网络异常
有一种特殊的网络异常称为 **网络分区** ,即集群的所有节点被划分为多个区域,每个区域内部可以通信,但是区域之间无法通信。
### 3. 磁盘故障
磁盘故障是一种发生概率很高的异常。
使用冗余机制,将数据存储到多台服务器。
## 超时
在分布式系统中,一个请求除了成功和失败两种状态,还存在着超时状态。
<div align="center"> <img src="../pics//b0e8ef47-2f23-4379-8c64-10d5cb44d438.jpg"/> </div><br>
可以将服务器的操作设计为具有 **幂等性** ,即执行多次的结果与执行一次的结果相同。如果使用这种方式,当出现超时的时候,可以不断地重新请求直到成功。
## 衡量指标
### 1. 性能
常见的性能指标有:吞吐量、响应时间。
其中,吞吐量指系统在某一段时间可以处理的请求总数,通常为每秒的读操作数或者写操作数;响应时间指从某个请求发出到接收到返回结果消耗的时间。
这两个指标往往是矛盾的,追求高吞吐的系统,往往很难做到低响应时间,解释如下:
- 在无并发的系统中,吞吐量为响应时间的倒数,例如响应时间为 10 ms那么吞吐量为 100 req/s因此高吞吐也就意味着低响应时间。
- 但是在并发的系统中,由于一个请求在调用 I/O 资源的时候,需要进行等待。服务器端一般使用的是异步等待方式,即等待的请求被阻塞之后不需要一直占用 CPU 资源。这种方式能大大提高 CPU 资源的利用率,例如上面的例子中,单个请求在无并发的系统中响应时间为 10 ms如果在并发的系统中那么吞吐量将大于 100 req/s。因此为了追求高吞吐量通常会提高并发程度。但是并发程度的增加会导致请求的平均响应时间也增加因为请求不能马上被处理需要和其它请求一起进行并发处理响应时间自然就会增高。
### 2. 可用性
可用性指系统在面对各种异常时可以提供正常服务的能力。可以用系统可用时间占总时间的比值来衡量4 个 9 的可用性表示系统 99.99% 的时间是可用的。
### 3. 一致性
可以从两个角度理解一致性:从客户端的角度,读写操作是否满足某种特性;从服务器的角度,多个数据副本之间是否一致。
有以下三种一致性模型:
1. 强一致性:新数据写入之后,在任何数据副本上都能读取到最新值;
2. 弱一致性:新数据写入之后,不能保证在数据副本上能读取到最新值;
3. 最终一致性:新数据写入之后,只能保证过一了一个时间窗口才能读取到最新值;
### 4. 可扩展性
指系统通过扩展集群服务器规模来提高性能的能力。理想的分布式系统需要实现“线性可扩展”,即随着集群规模的增加,系统的整体性能也会线程增加。
# 二、数据分布
分布式系统的数据分布在多个节点中,常用的数据分布方式有哈希分布和顺序分布。
## 哈希分布
哈希分布就是将数据计算哈希值之后,按照哈希值分配到不同的节点上。例如有 N 个节点,数据的主键为 key则将该数据分配的节点序号为hash(key)%N。
传统的哈希分布算法存在一个问题:当节点数量变化时,也就是 N 值变化,那么几乎所有的数据都需要重新分布,将导致大量的数据迁移。
#### 一致性哈希
Distributed Hash TableDHT对于哈希空间 0\~2<sup>n</sup>,将该哈希空间看成一个哈希环,将每个节点都配置到哈希环上。每个数据对象通过哈希取模得到哈希值之后,存放到哈希环中顺时针方向第一个大于等于该哈希值的节点上。
<div align="center"> <img src="../pics//d2d34239-e7c1-482b-b33e-3170c5943556.jpg"/> </div><br>
一致性哈希的优点是在加入或者删除节点时只会影响到哈希环中相邻的节点,例如下图中新增节点 X只需要将数据对象 C 重新存放到节点 X 上即可,对于节点 A、B、D 都没有影响。
<div align="center"> <img src="../pics//91ef04e4-923a-4277-99c0-6be4ce81e5ac.jpg"/> </div><br>
## 顺序分布
哈希分布式破坏了数据的有序性,顺序分布则不会。
顺序分布的数据划分为多个连续的部分按一定策略分布到不同节点上。例如下图中User 表的主键范围为 1 \~ 7000使用顺序分布可以将其划分成多个子表对应的主键范围为 1 \~ 10001001 \~ 2000...6001 \~ 7000。
其中 Meta 表是为了支持更大的集群规模,它将原来的一层索引结分成两层,使用 Meta 表来维护 User 子表所在的节点,从而减轻 Root 节点的负担。
<div align="center"> <img src="../pics//8f64e9c5-7682-4feb-9312-dea09514e160.jpg"/> </div><br>
# 三、负载均衡
衡量负载的因素很多,如 CPU、内存、磁盘等资源使用情况、读写请求数等。分布式系统应当能够自动负载均衡当某个节点的负载较高将它的部分数据迁移到其它节点。
每个集群都有一个总控节点其它节点为工作节点由总控节点根据全局负载信息进行整体调度工作节点定时发送心跳包Heartbeat将节点负载相关的信息发送给总控节点。
一个新上线的工作节点,由于其负载较低,如果不加控制,总控节点会将大量数据同时迁移到该节点上,造成该节点一段时间内无法工作。因此负载均衡操作需要平滑进行,新加入的节点需要较长的一段时间来达到比较均衡的状态。
# 四、复制
复制是保证分布式系统高可用的基础,让一个数据存储多个副本,当某个副本所在的节点出现故障时,能够自动切换到其它副本上,从而实现故障恢复。
多个副本通常有一个为主副本,其它为备副本。主副本用来处理写请求,备副本主要用来处理读请求,实现读写分离。主副本将同步操作日志发送给备副本,备副本通过回放操作日志获取最新修改。
<div align="center"> <img src="../pics//44e4a7ab-215c-41a1-8e34-f55f6c09e517.jpg"/> </div><br>
主备副本之间有两种复制协议,一种是强同步复制协议,一种是异步复制协议。
## 强同步复制协议
要求主副本将同步操作日志发给备副本之后进行等待,要求至少一个备副本返回成功后,才开始修改主副本,修改完成之后通知客户端操作成功。
优点:至少有一个备副本拥有完整的数据,出现故障时可以安全地切换到该备副本,因此一致性好。
缺点:可用性差,因为主副本需要等待,那么整个分布式系统的可用时间就会降低。
## 异步复制协议
主副本将同步操作日志发给备副本之后不需要进行等待,直接修改主副本并通知客户端操作成功。
优点:可用性好。
缺点:一致性差。
# 五、CAP
分布式系统不可能同时满足一致性CConsistency、可用性AAvailability和分区容忍性PPartition tolerance最多只能同时满足其中两项。这三个概念上文中已经提到。
在设计分布式系统时需要根据实际需求弱化某一要求。因此就有了下图中的三种设计CA、CP 和 AP。
<div align="center"> <img src="../pics//f50bc364-fdc2-4a46-9b8f-f8f5b6add3b8.jpg" width="300"/> </div><br>
需要注意的是,分区容忍性必不可少,因为需要总是假设网络是不可靠的。因此实际上设计分布式系统需要在一致性和可用性之间做权衡。
# 六、BASE
BASE 是 Basically Available基本可用、Soft state软状态和 Eventually consistent最终一致性三个短语的缩写。BASE 理论是对 CAP 中一致性和可用性权衡的结果,是基于 CAP 定理逐步演化而来的。BASE 理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
## 基本可用
指分布式系统在出现故障的时候,保证核心可用,允许损失部分可用性。
例如,电商在做促销时,服务层可能只提供降级服务,部分用户可能会被引导到降级页面上。
## 软状态
指允许系统存在中间状态,而该中间状态不会影响系统整体可用性,即不同节点的数据副本之间进行同步的过程允许存在延时。
## 最终一致性
指所有的数据副本,在经过一段时间的同步之后,最终都能够达到一致的状态。
强一致性需要保证数据副本实时一致,而最终一致性只需要保证过一段时间是一致的。
ACID 是传统数据库系统常用的设计理论追求强一致性模型。BASE 常用于大型分布式系统,只需要保证最终一致性。在实际的分布式场景中,不同业务单元和组件对一致性的要求是不同的,因此 ACID 和 BASE 往往会结合在一起使用。
# 七、容错
分布式系统故障发生的概率很大,为了实现高可用以及减少人工运维成本,需要实现自动化容错。
## 故障检测
通过 **租约机制** 来对故障进行检测。假设节点 A 为主控节点,节点 A 向节点 B 发送租约,节点 B 在租约规定的期限内才能提供服务。期限快到达时,节点 B 需要向 A 重新申请租约。
如果过期,那么 B 不再提供服务,并且 A 也能知道 B 此时可能发生故障并已经停止服务。可以看到通过这种机制A 和 B 都能对 B 发生故障这一事实达成一致。
## 故障恢复
当某个节点故障时,就将它上面的服务迁移到其它节点。
# 八、CDN 架构
CND 通过将内容发布到靠近用户的边缘节点,使不同地域的用户在访问相同网页时可以就近获取。不仅可以减轻服务器的负担,也可以提高用户的访问速度。
从下图可以看出DNS 在对域名解析时不再向用户返回源服务器的 IP 地址,而是返回边缘节点的 IP 地址,所以用户最终访问的是边缘节点。边缘节点会先从源服务器中获取用户所需的数据,如果请求成功,边缘节点会将页面缓存下来,下次用户访问时可以直接读取。
<div align="center"> <img src="../pics//dbd60b1f-b700-4da6-a993-62578e892333.jpg"/> </div><br>
# 参考资料
- 杨传辉. 大规模分布式存储系统: 原理解析与架构实战[M]. 机械工业出版社, 2013.

View File

@ -0,0 +1,363 @@
<!-- GFM-TOC -->
* [一、谈谈业务中使用分布式的场景](#一谈谈业务中使用分布式的场景)
* [二、分布式事务](#二分布式事务)
* [产生原因](#产生原因)
* [应用场景](#应用场景)
* [解决方案](#解决方案)
* [三、负载均衡的算法与实现](#三负载均衡的算法与实现)
* [算法](#算法)
* [实现](#实现)
* [四、分布式锁](#四分布式锁)
* [使用场景](#使用场景)
* [实现方式](#实现方式)
* [五、分布式 Session](#五分布式-session)
* [1. 粘性 Session](#1-粘性-session)
* [2. 服务器 Session 复制](#2-服务器-session-复制)
* [3. Session 共享机制](#3-session-共享机制)
* [4. Session 持久化到数据库](#4-session-持久化到数据库)
* [5. Terracotta 实现 Session 复制](#5-terracotta-实现-session-复制)
* [六、分库与分表带来的分布式困境与应对之策](#六分库与分表带来的分布式困境与应对之策)
* [事务问题](#事务问题)
* [查询问题](#查询问题)
* [ID 唯一性](#id-唯一性)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
# 一、谈谈业务中使用分布式的场景
分布式主要是为了提供可扩展性以及高可用性,业务中使用分布式的场景主要有分布式存储以及分布式计算。
分布式存储中可以将数据分片到多个节点上,不仅可以提高性能(可扩展性),同时也可以使用多个节点对同一份数据进行备份。
至于分布式计算就是将一个大的计算任务分解成小任务分配到多台节点上去执行再汇总每个小任务的执行结果得到最终结果。MapReduce 是分布式计算的最好例子。
# 二、分布式事务
指事务的操作位于不同的节点上,需要保证事务的 AICD 特性。
## 产生原因
- 数据库分库分表;
- SOA 架构,比如一个电商网站将订单业务和库存业务分离出来放到不同的节点上。
## 应用场景
- 下单:减少库存、更新订单状态。库存和订单不在不同一个数据库,因此涉及分布式事务。
- 支付:买家账户扣款、卖家账户入账。买家和卖家账户信息不在同一个数据库,因此涉及分布式事务。
## 解决方案
### 1. 两阶段提交协议
[两阶段提交](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E4%B8%80%E8%87%B4%E6%80%A7%E5%8D%8F%E8%AE%AE.md#%E4%B8%A4%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4%E5%8D%8F%E8%AE%AE)
两阶段提交协议可以很好得解决分布式事务问题,它可以使用 XA 来实现XA 它包含两个部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如 Oracle、DB2 这些商业数据库都实现了 XA 接口;而事务管理器作为全局的协调者,负责各个本地资源的提交和回滚。
### 2. 消息中间件
消息中间件也可称作消息系统 (MQ),它本质上是一个暂存转发消息的一个中间件。在分布式应用当中,我们可以把一个业务操作转换成一个消息,比如支付宝的余额转入余额宝操作,支付宝系统执行减少余额操作之后向消息系统发送一个消息,余额宝系统订阅这条消息然后进行增加余额宝操作。
#### 2.1 消息处理模型
<font size=3> **点对点** </font></br>
<div align="center"> <img src="../pics//96b63e13-e2d8-4ddb-9aa1-a38959ca96e5.jpg" width="600"/> </div><br>
<font size=3> **发布/订阅** </font></br>
<div align="center"> <img src="../pics//654acfed-a6a5-4fc7-8f40-3fdcae57bae8.jpg" width="700"/> </div><br>
#### 2.2 消息的可靠性
消息的发送端的可靠性:发送端完成操作后一定能将消息成功发送到消息系统。
消息的接收端的可靠性:接收端仅且能够从消息中间件成功消费一次消息。
<font size=3> **发送端的可靠性** </font></br>
在本地数据建一张消息表,将消息数据与业务数据保存在同一数据库实例里,这样就可以利用本地数据库的事务机制。事务提交成功后,将消息表中的消息转移到消息中间件,若转移消息成功则删除消息表中的数据,否则继续重传。
<font size=3> **接收端的可靠性** </font></br>
保证接收端处理消息的业务逻辑具有幂等性:只要具有幂等性,那么消费多少次消息,最后处理的结果都是一样的。
保证消息具有唯一编号,并使用一张日志表来记录已经消费的消息编号。
# 三、负载均衡的算法与实现
## 算法
### 1. 轮询Round Robin
轮询算法把每个请求轮流发送到每个服务器上。下图中,一共有 6 个客户端产生了 6 个请求,这 6 个请求按 (1, 2, 3, 4, 5, 6) 的顺序发送。最后,(1, 3, 5) 的请求会被发送到服务器 1(2, 4, 6) 的请求会被发送到服务器 2。
<div align="center"> <img src="../pics//2766d04f-7dad-42e4-99d1-60682c9d5c61.jpg"/> </div><br>
该算法比较适合每个服务器的性能差不多的场景,如果有性能存在差异的情况下,那么性能较差的服务器可能无法承担多大的负载。下图中,服务器 2 的性能比服务器 1 差,那么服务器 2 可能无法承担多大的负载。
<div align="center"> <img src="../pics//f7ecbb8d-bb8b-4d45-a3b7-f49425d6d83d.jpg"/> </div><br>
### 2. 加权轮询Weighted Round Robbin
加权轮询是在轮询的基础上,根据服务器的性能差异,为服务器赋予一定的权值。例如下图中,服务器 1 被赋予的权值为 5服务器 2 被赋予的权值为 1那么 (1, 2, 3, 4, 5) 请求会被发送到服务器 1(6) 请求会被发送到服务器 2。
<div align="center"> <img src="../pics//211c60d4-75ca-4acd-8a4f-171458ed58b4.jpg"/> </div><br>
### 3. 最少连接least Connections
由于每个请求的连接时间不一样,使用轮询或者加权轮询算法的话,可能会让一台服务器当前连接数多大,而另一台服务器的连接多小,造成负载不均衡。例如下图中,(1, 3, 5) 请求会被发送到服务器 1但是 (1, 3) 很快就断开连接,此时只有 (5) 请求连接服务器 1(2, 4, 6) 请求被发送到服务器 2只有 (2) 的连接断开。该系统继续运行时,服务器 2 会承担多大的负载。
<div align="center"> <img src="../pics//3b0d1aa8-d0e0-46c2-8fd1-736bf08a11aa.jpg"/> </div><br>
最少连接算法就是将请求发送给当前最少连接数的服务器上。例如下图中,服务器 1 当前连接数最小,那么新到来的请求 6 就会被发送到服务器 1 上。
<div align="center"> <img src="../pics//1f4a7f10-52b2-4bd7-a67d-a9581d66dc62.jpg"/> </div><br>
### 4. 加权最小连接Weighted Least Connection
在最小连接的基础上,根据服务器的性能为每台服务器分配权重,根据权重计算出每台服务器能处理的连接数。
<div align="center"> <img src="../pics//44edefb7-4b58-4519-b8ee-4aca01697b78.jpg"/> </div><br>
### 5. 随机算法Random
把请求随机发送到服务器上。和轮询算法类似,该算法比较适合服务器性能差不多的场景。
<div align="center"> <img src="../pics//0ee0f61b-c782-441e-bf34-665650198ae0.jpg"/> </div><br>
## 实现
### 1. DNS 解析
使用 DNS 作为负载均衡器,根据负载情况返回不同服务器的 IP 地址。大型网站基本使用了这种方式最为第一级负载均衡手段,然后在内部使用其它方式做第二级负载均衡。
<div align="center"> <img src="../pics//42e17a80-b9fc-42a2-9ba8-68364fae3710.jpg" width="500"/> </div><br>
### 2. 修改 MAC 地址
使用 LVSLinux Virtual Server这种链路层负载均衡器根据负载情况修改请求的 MAC 地址。
<div align="center"> <img src="../pics//6fea6dd4-9232-4df8-abdf-f1b528a18b17.jpg" width="500"/> </div><br>
### 3. 修改 IP 地址
在网络层修改请求的目的 IP 地址。
<div align="center"> <img src="../pics//4ff7eacd-0081-452e-9686-60a81e11bf73.jpg" width="500"/> </div><br>
### 4. HTTP 重定向
HTTP 重定向负载均衡服务器收到 HTTP 请求之后会返回服务器的地址,并将该地址写入 HTTP 重定向响应中返回给浏览器,浏览器收到后需要再次发送请求。
<div align="center"> <img src="../pics//9208563b-014d-4745-aa1c-492c9f7f7a7f.jpg" width="500"/> </div><br>
### 5. 反向代理
正向代理:发生在客户端,是由用户主动发起的。比如翻墙,客户端通过主动访问代理服务器,让代理服务器获得需要的外网数据,然后转发回客户端。
反向代理:发生在服务器端,用户不知道代理的存在。
<div align="center"> <img src="../pics//b750eb3e-8a80-475c-95df-2e971b277603.jpg" width="500"/> </div><br>
# 四、分布式锁
Java 提供了两种内置的锁的实现,一种是由 JVM 实现的 synchronized 和 JDK 提供的 Lock当你的应用是单机或者说单进程应用时可以使用 synchronized 或 Lock 来实现锁。当应用涉及到多机、多进程共同完成时,那么这时候就需要一个全局锁来实现多个进程之间的同步。
## 使用场景
例如一个应用有手机 APP 端和 Web 端,如果在两个客户端同时进行一项操作时,那么就会导致这项操作重复进行。
## 实现方式
### 1. 数据库分布式锁
#### 1.1 基于 MySQL 锁表
该实现方式完全依靠数据库唯一索引来实现。当想要获得锁时,就向数据库中插入一条记录,释放锁时就删除这条记录。如果记录具有唯一索引,就不会同时插入同一条记录。这种方式存在以下几个问题:
1. 锁没有失效时间,解锁失败会导致死锁,其他线程无法再获得锁。
2. 只能是非阻塞锁,插入失败直接就报错了,无法重试。
3. 不可重入,同一线程在没有释放锁之前无法再获得锁。
#### 1.2 采用乐观锁增加版本号
根据版本号来判断更新之前有没有其他线程更新过,如果被更新过,则获取锁失败。
### 2. Redis 分布式锁
#### 2.1 基于 SETNX、EXPIRE
使用 SETNXset if not exist命令插入一个键值对时如果 Key 已经存在,那么会返回 False否则插入成功并返回 True。因此客户端在尝试获得锁时先使用 SETNX 向 Redis 中插入一个记录,如果返回 True 表示获得锁,返回 False 表示已经有客户端占用锁。
EXPIRE 可以为一个键值对设置一个过期时间,从而避免了死锁的发生。
#### 2.2 RedLock 算法
ReadLock 算法使用了多个 Redis 实例来实现分布式锁,这是为了保证在发生单点故障时还可用。
1. 尝试从 N 个相互独立 Redis 实例获取锁,如果一个实例不可用,应该尽快尝试下一个。
2. 计算获取锁消耗的时间只有当这个时间小于锁的过期时间并且从大多数N/2+1实例上获取了锁那么就认为锁获取成功了。
3. 如果锁获取失败,会到每个实例上释放锁。
### 3. Zookeeper 分布式锁
Zookeeper 是一个为分布式应用提供一致性服务的软件,例如配置管理、分布式协同以及命名的中心化等,这些都是分布式系统中非常底层而且是必不可少的基本功能,但是如果自己实现这些功能而且要达到高吞吐、低延迟同时还要保持一致性和可用性,实际上非常困难。
#### 3.1 抽象模型
Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示它的父节点为 /app1。
<div align="center"> <img src="../pics//31d99967-1171-448e-8531-bccf5c14cffe.jpg" width="400"/> </div><br>
#### 3.2 节点类型
- 永久节点:不会因为会话结束或者超时而消失;
- 临时节点:如果会话结束或者超时就会消失;
- 有序节点:会在节点名的后面加一个数字后缀,并且是有序的,例如生成的有序节点为 /lock/node-0000000000它的下一个有序节点则为 /lock/node-0000000001依次类推。
#### 3.3 监听器
为一个节点注册监听器,在节点状态发生改变时,会给客户端发送消息。
#### 3.4 分布式锁实现
1. 创建一个锁目录 /lock。
1. 在 /lock 下创建临时的且有序的子节点,第一个客户端对应的子节点为 /lock/lock-0000000000第二个为 /lock/lock-0000000001以此类推。
2. 客户端获取 /lock 下的子节点列表,判断自己创建的子节点是否为当前子节点列表中序号最小的子节点,如果是则认为获得锁,否则监听自己的前一个子节点,获得子节点的变更通知后重复此步骤直至获得锁;
3. 执行业务代码,完成后,删除对应的子节点。
#### 3.5 会话超时
如果一个已经获得锁的会话超时了因为创建的是临时节点因此该会话对应的临时节点会被删除其它会话就可以获得锁了。可以看到Zookeeper 分布式锁不会出现数据库分布式锁的死锁问题。
#### 3.6 羊群效应
在步骤二,一个节点未获得锁,需要监听监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知,而我们只希望它的下一个子节点收到通知。
# 五、分布式 Session
如果不做任何处理的话,用户将出现频繁登录的现象,比如集群中存在 A、B 两台服务器用户在第一次访问网站时Nginx 通过其负载均衡机制将用户请求转发到 A 服务器,这时 A 服务器就会给用户创建一个 Session。当用户第二次发送请求时Nginx 将其负载均衡到 B 服务器,而这时候 B 服务器并不存在 Session所以就会将用户踢到登录页面。这将大大降低用户体验度导致用户的流失这种情况是项目绝不应该出现的。
## 1. 粘性 Session
### 原理
粘性 Session 是指将用户锁定到某一个服务器上,比如上面说的例子,用户第一次请求时,负载均衡器将用户的请求转发到了 A 服务器上,如果负载均衡器设置了粘性 Session 的话,那么用户以后的每次请求都会转发到 A 服务器上,相当于把用户和 A 服务器粘到了一块,这就是粘性 Session 机制。
### 优点
简单,不需要对 Session 做任何处理。
### 缺点
缺乏容错性,如果当前访问的服务器发生故障,用户被转移到第二个服务器上时,他的 Session 信息都将失效。
### 适用场景
- 发生故障对客户产生的影响较小;
- 服务器发生故障是低概率事件。
## 2. 服务器 Session 复制
### 原理
任何一个服务器上的 Session 发生改变,该节点会把这个 Session 的所有内容序列化,然后广播给所有其它节点,不管其他服务器需不需要 Session以此来保证 Session 同步。
### 优点
可容错,各个服务器间 Session 能够实时响应。
### 缺点
会对网络负荷造成一定压力,如果 Session 量大的话可能会造成网络堵塞,拖慢服务器性能。
### 实现方式
1. 设置 Tomcat 的 server.xml 开启 tomcat 集群功能。
2. 在应用里增加信息:通知应用当前处于集群环境中,支持分布式,即在 web.xml 中添加&lt;distributable/> 选项。
## 3. Session 共享机制
使用分布式缓存方案比如 Memcached、Redis但是要求 Memcached 或 Redis 必须是集群。
使用 Session 共享也分两种机制,两种情况如下:
### 3.1 粘性 Session 共享机制
和粘性 Session 一样,一个用户的 Session 会绑定到一个 Tomcat 上。Memcached 只是起到备份作用。
<div align="center"> <img src="../pics//93a28704-6401-4671-9758-051fadfbeb47.jpg" width="400"/> </div><br>
### 3.2 非粘性 Session 共享机制
#### 原理
Tomcat 本身不存储 Session而是存入 Memcached 中。Memcached 集群构建主从复制架构。
<div align="center"> <img src="../pics//ce0fa5d0-866b-46e6-a873-8eb1f78c2882.jpg" width="400"/> </div><br>
#### 优点
可容错Session 实时响应。
#### 实现方式
用开源的 msm 插件解决 Tomcat 之间的 Session 共享Memcached_Session_ManagerMSM
## 4. Session 持久化到数据库
### 原理
拿出一个数据库,专门用来存储 Session 信息。保证 Session 的持久化。
### 优点
服务器出现问题Session 不会丢失
### 缺点
如果网站的访问量很大,把 Session 存储到数据库中,会对数据库造成很大压力,还需要增加额外的开销维护数据库。
## 5. Terracotta 实现 Session 复制
### 原理
Terracotta 的基本原理是对于集群间共享的数据当在一个节点发生变化的时候Terracotta 只把变化的部分发送给 Terracotta 服务器,然后由服务器把它转发给真正需要这个数据的节点。它是服务器 Session 复制的优化。
<div align="center"> <img src="../pics//b6acae0d-7148-41de-adc3-ff5ff8dca3ae.jpg"/> </div><br>
### 优点
这样对网络的压力就非常小,各个节点也不必浪费 CPU 时间和内存进行大量的序列化操作。把这种集群间数据共享的机制应用在 Session 同步上,既避免了对数据库的依赖,又能达到负载均衡和灾难恢复的效果。
# 六、分库与分表带来的分布式困境与应对之策
<div align="center"> <img src="../pics//f3d3e072-e947-43e9-b999-22385fd569a0.jpg"/> </div><br>
## 事务问题
使用分布式事务。
## 查询问题
使用汇总表。
## ID 唯一性
- 使用全局唯一 IDGUID
- 为每个分片指定一个 ID 范围。
# 参考资料
- [Comparing Load Balancing Algorithms](http://www.jscape.com/blog/load-balancing-algorithms)
- [负载均衡算法及手段](https://segmentfault.com/a/1190000004492447)
- [集群/分布式环境下 5 种 Session 处理策略](http://blog.csdn.net/u010028869/article/details/50773174?ref=myread)
- [浅谈分布式锁](http://www.linkedkeeper.com/detail/blog.action?bid=1023)
- [深入理解分布式事务](https://juejin.im/entry/577c6f220a2b5800573492be)
- [分布式系统的事务处理](https://coolshell.cn/articles/10910.html)
- [关于分布式事务](http://blog.csdn.net/suifeng3051/article/details/52691210)
- [基于 Zookeeper 的分布式锁](http://www.dengshenyu.com/java/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/2017/10/23/zookeeper-distributed-lock.html)
- [How Sharding Works](https://medium.com/@jeeyoungk/how-sharding-works-b4dec46b3f6)
- [服务端指南 数据存储篇 | MySQL09 分库与分表带来的分布式困境与应对之策](http://blog.720ui.com/2017/mysql_core_09_multi_db_table2/ "服务端指南 数据存储篇 | MySQL09 分库与分表带来的分布式困境与应对之策")
- [How to create unique row ID in sharded databases?](https://stackoverflow.com/questions/788829/how-to-create-unique-row-id-in-sharded-databases)

File diff suppressed because it is too large Load Diff

View File

@ -1,193 +1,302 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [事务四大特性](#事务四大特性) * [一、事务](#一事务)
* [原子性](#原子性) * [概念](#概念)
* [一致性](#一致性) * [四大特性](#四大特性)
* [隔离性](#隔离性) * [二、并发一致性问题](#二并发一致性问题)
* [持久性](#持久性) * [问题](#问题)
* [数据不一致](#数据不一致) * [解决方法](#解决方法)
* [丢失修改](#丢失修改) * [三、封锁](#三封锁)
* [读脏数据](#读脏数据) * [封锁类型](#封锁类型)
* [不可重复读](#不可重复读) * [封锁粒度](#封锁粒度)
* [隔离级别](#隔离级别) * [封锁协议](#封锁协议)
* [未提交读READ UNCOMMITTED](#未提交读read-uncommitted) * [四、隔离级别](#四隔离级别)
* [提交读READ COMMITTED](#提交读read-committed) * [五、数据库系统概述](#五数据库系统概述)
* [可重复读REPEATABLE READ](#可重复读repeatable-read) * [基本术语](#基本术语)
* [可串行化SERIALIXABLE](#可串行化serialixable) * [数据库的三层模式和两层映像](#数据库的三层模式和两层映像)
* [可串行化调度](#可串行化调度) * [六、关系数据库建模](#六关系数据库建模)
* [封锁类型](#封锁类型) * [ER 图](#er-图)
* [封锁粒度](#封锁粒度) * [约束](#约束)
* [封锁协议](#封锁协议) * [七、关系数据库设计理论](#七关系数据库设计理论)
* [三级封锁协议](#三级封锁协议) * [函数依赖](#函数依赖)
* [两段锁协议](#两段锁协议) * [异常](#异常)
* [乐观锁和悲观锁](#乐观锁和悲观锁) * [范式](#范式)
* [悲观锁](#悲观锁)
* [乐观锁](#乐观锁)
* [MySQL 隐式和显示锁定](#mysql-隐式和显示锁定)
* [范式](#范式)
* [第一范式 (1NF)](#第一范式-1nf)
* [第二范式 (2NF)](#第二范式-2nf)
* [第三范式 (3NF)](#第三范式-3nf)
* [BC 范式BCNF](#bc-范式bcnf)
* [约束](#约束)
* [键码](#键码)
* [单值约束](#单值约束)
* [引用完整性约束](#引用完整性约束)
* [域约束](#域约束)
* [一般约束](#一般约束)
* [数据库的三层模式和两层映像](#数据库的三层模式和两层映像)
* [外模式](#外模式)
* [模式](#模式)
* [内模式](#内模式)
* [外模式/模式映像](#外模式模式映像)
* [模式/内模式映像](#模式内模式映像)
* [ER 图](#er-图)
* [实体的三种联系](#实体的三种联系)
* [表示出现多次的关系](#表示出现多次的关系)
* [联系的多向性](#联系的多向性)
* [表示子类](#表示子类)
* [一些概念](#一些概念)
* [参考资料](#参考资料) * [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
# 事务四大特性 # 一、事务
## 原子性 ## 概念
要么都执行,要么都不执行。 <div align="center"> <img src="../pics//4f4deaf4-8487-4de2-9d62-5ad017ee9589.png"/> </div><br>
## 一致性 事务指的是满足 ACID 特性的一系列操作。
在数据库中,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回退。
## 四大特性
<div align="center"> <img src="../pics//fd945daf-4a6c-4f20-b9c2-5390f5955ce5.jpg" width="500"/> </div><br>
<font size=4> **1. 原子性Atomicity** </font> </br>
事务被视为不可分割的最小单元,要么全部提交成功,要么全部失败回滚。
<font size=4> **2. 一致性Consistency** </font> </br>
事务执行前后都保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的。 事务执行前后都保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的。
## 隔离性 <font size=4> **3. 隔离性Isolation** </font> </br>
多个事务单独执行,互不影响。 一个事务所做的修改在最终提交以前,对其它事务是不可见的。也可以理解为多个事务单独执行,互不影响。
## 持久性 <font size=4> **4. 持久性Durability** </font> </br>
即使系统发生故障,事务执行的结果也不能丢失。持久性通过数据库备份和恢复来保证。 一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。可以通过数据库备份和恢复来保证持久性
# 数据不一致 # 二、并发一致性问题
## 丢失修改 在并发环境下,一个事务如果受到另一个事务的影响,那么事务操作就无法满足一致性条件。
## 问题
<font size=4> **1. 丢失修改** </font> </br>
T<sub>1</sub> 和 T<sub>2</sub> 两个事务同时对一个数据进行修改T<sub>1</sub> 先修改T<sub>2</sub> 随后修改T<sub>2</sub> 的修改覆盖了 T<sub>1</sub> 的修改。 T<sub>1</sub> 和 T<sub>2</sub> 两个事务同时对一个数据进行修改T<sub>1</sub> 先修改T<sub>2</sub> 随后修改T<sub>2</sub> 的修改覆盖了 T<sub>1</sub> 的修改。
## 读脏数据 <font size=4> **2. 读脏数据** </font> </br>
T<sub>1</sub> 做修改后写入数据库T<sub>2</sub> 读取这个修改后的数据,但是如果 T<sub>1</sub> 撤销了这次修改,使得 T<sub>2</sub> 读取的数据是脏数据。 T<sub>1</sub> 修改一个数据T<sub>2</sub> 随后读取这个数据。如果 T<sub>1</sub> 撤销了这次修改,那么 T<sub>2</sub> 读取的数据是脏数据。
## 不可重复读 <div align="center"> <img src="../pics//d1ab24fa-1a25-4804-aa91-513df55cbaa6.jpg" width="800"/> </div><br>
T<sub>1</sub> 读入某个数据T<sub>2</sub> 对该数据做了修改,如果 T<sub>1</sub> 再读这个数据,该数据已经改变,和最开始读入的是不一样的。 <font size=4> **3. 不可重复读** </font> </br>
# 隔离级别 T<sub>2</sub> 读取一个数据T<sub>1</sub> 对该数据做了修改。如果 T<sub>2</sub> 再次读取这个数据,此时读取的结果和和第一次读取的结果不同。
数据库管理系统需要防止数据出现不一致,并且有多种级别可以实现,这些级别称为隔离级别。 <div align="center"> <img src="../pics//d0175e0c-859e-4991-b263-8378e52f7ee5.jpg" width="800"/> </div><br>
## 未提交读READ UNCOMMITTED <font size=4> **4. 幻影读** </font> </br>
一个事务可以读取自己的未提交数据,也被称为脏读 T<sub>1</sub> 读取某个范围的数据T<sub>2</sub> 在这个范围内插入新的数据T<sub>1</sub> 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同
## 提交读READ COMMITTED <div align="center"> <img src="../pics//d589eca6-c7cf-49c5-ac96-8e4ca0cccadd.jpg" width="800"/> </div><br>
一个事务可以读取自己的已提交数据,但是该数据可能过后就会被其它事务改变,因此也称为不可重复读。 ## 解决方法
## 可重复读REPEATABLE READ 产生并发不一致性问题主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。
保证在同一个事务中多次读取同样的记录结果是一致的。但是会出现幻读的问题,所谓幻读,指的是某个事务在读取某个范围内的记录时,其它事务会在范围内插入数据,产生幻行 在没有并发的情况下,事务以串行的方式执行,互不干扰,因此可以保证隔离性。在并发的情况下,如果能通过并发控制,让事务的执行结果和某一个串行执行的结果相同,就认为事务的执行结果满足隔离性要求,也就是说是正确的。把这种事务执行方式成为 **可串行化调度**
## 可串行化SERIALIXABLE 并发控制可以通过封锁来实现,但是封锁操作都要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。
强制事务串行执行,避免幻读。 # 三、封锁
# 可串行化调度 ## 封锁类型
如果并行的事务的执行结果和某一个串行的方式执行的结果一样,那么可以认为结果是正确的。 - 排它锁Exclusive简写为 X 锁,又称写锁。
- 共享锁Shared简写为 S 锁,又称读锁。
- 一个事务对数据对象 A 加了 X 锁,就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何锁;
- 一个事务对数据对象 A 加了 S 锁,可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁,但是不能加 X 锁。
# 封锁类型 以上加锁规定总结如下:
排它锁 (X 锁),共享锁 (S 锁) <div align="center"> <img src="../pics//3e87de44-1c69-4365-8139-b22e8d4be347.png"/> </div><br>
一个事务 T 对数据对象 A 加了 X 锁T 就可以对 A 进行读取和更新。加锁期间其它事务不能对数据对象 A 加任何其它锁; ## 封锁粒度
一个事务 T 对数据对象加了 S 锁T 可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对数据对象 A 加 S 锁,但是不能加 X 锁。 <div align="center"> <img src="../pics//1a851e90-0d5c-4d4f-ac54-34c20ecfb903.jpg" width="300"/> </div><br>
# 封锁粒度 应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。
粒度可以是整个数据库,也可以是表,行,或者分量 但是加锁需要消耗资源,锁的各种操作,包括获取锁,检查锁是否已经解除、释放锁,都会增加系统开销。因此封锁粒度越小,系统开销就越大。需要在锁开销以及数据安全性之间做一个权衡
粒度越小,开销越大 MySQL 中提供了两种封锁粒度:行级锁以及表级锁
# 封锁协议 ## 封锁协议
## 三级封锁协议 ### 1. 三级封锁协议
<div align="center"> <img src="../pics//785806ed-c46b-4dca-b756-cebe7bf8ac3a.jpg"/> </div><br> <font size=4> **1.1 一级封锁协议** </font> </br>
**1 级封锁协议**
事务 T 要修改数据 A 时必须加 X 锁,直到事务结束才释放锁。 事务 T 要修改数据 A 时必须加 X 锁,直到事务结束才释放锁。
可以解决丢失修改问题; 可以解决丢失修改问题;
**2 级封锁协议** <font size=4> **1.2 级封锁协议** </font> </br>
1 级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。 级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。
可以解决读脏数据问题,因为如果一个事务在对数据 A 进行修改,根据 1 级封锁协议,会加 X 锁,那么就不能再加 S 锁了,也就是不会读入数据。 可以解决读脏数据问题,因为如果一个事务在对数据 A 进行修改,根据 1 级封锁协议,会加 X 锁,那么就不能再加 S 锁了,也就是不会读入数据。
**3 级封锁协议** <font size=4> **1.3 级封锁协议** </font> </br>
2 级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁。 级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁。
可以解决不可重复读的问题,因为读 A 时,其它事务不能对 A 加 X 锁,从而避免了在读的期间数据发生改变。 可以解决不可重复读的问题,因为读 A 时,其它事务不能对 A 加 X 锁,从而避免了在读的期间数据发生改变。
## 两段锁协议 <div align="center"> <img src="../pics//20368ec9-972e-4d6a-8050-3948334bcda0.jpg" width="800"/> </div><br>
加锁和解锁分为两个阶段进行。两段锁是并行事务可串行化的充分条件,但不是必要条件。 ### 2. 两段锁协议
加锁和解锁分为两个阶段进行,事务 T 对数据 A 进行或者写操作之前,必须先获得对 A 的封锁并且在释放一个封锁之前T 不能再获得任何的其它锁。
事务遵循两段锁协议是保证并发操作可串行化调度的充分条件。例如以下操作满足两段锁协议,它是可串行化调度。
```html ```html
lock-x(A)...lock-s(B)...lock-s(c)...unlock(A)...unlock(C)...unlock(B) lock-x(A)...lock-s(B)...lock-s(c)...unlock(A)...unlock(C)...unlock(B)
``` ```
# 乐观锁和悲观锁 但不是必要条件,例如以下操作不满足两段锁协议,但是它还是可串行化调度。
## 悲观锁 ```html
lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(c)...unlock(C)...
```
假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。 # 四、隔离级别
Java synchronized 就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被阻塞。 <font size=4> **1. 未提交读READ UNCOMMITTED** </font> </br>
## 乐观锁 事务中的修改,即使没有提交,对其它事务也是可见的。事务可以读取未提交的数据,这也被称为脏读。
假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。 <font size=4> **2. 提交读READ COMMITTED** </font> </br>
Java JUC 中的 Atomic 包就是乐观锁的一种实现AtomicInteger 通过 CASCompare And Set操作实现线程安全的自增操作 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所在的修改在提交之前对其它事务是不可见的
乐观锁有两种实现方式,数据版本和时间戳。它们都需要在数据库表中增加一个字段,使用这个字段来判断数据是否过期。例如,数据版本实现方式中,需要在数据库表中增加一个数字类型的 version 字段,当读取数据时,将 version 字段的值一同读出。随后数据每更新一次,对此 version 值加 1。当提交更新的时候判断读出的 version 和数据库表中的 version 是否一致,如果一致,则予以更新;否则认为是过期数据。 <font size=4> **3. 可重复读REPEATABLE READ** </font> </br>
## MySQL 隐式和显示锁定 解决了脏读的问题,保证在同一个事务中多次读取同样的记录结果是一致的。
MySQL InnoDB 采用的是两阶段锁协议。在事务执行过程中,随时都可以执行锁定,锁只有在执行 COMMIT 或者 ROLLBACK 的时候才会释放并且所有的锁是在同一时刻被释放。前面描述的锁定都是隐式锁定InnoDB 会根据事务隔离级别在需要的时候自动加锁。 <font size=4> **4. 可串行化SERIALIXABLE** </font> </br>
另外InnoDB 也支持通过特定的语句进行显示锁定,这些语句不属于 SQL 规范: 强制事务串行执行。
- SELECT ... LOCK IN SHARE MODE <font size=4> **5. 总结** </font> </br>
- SELECT ... FOR UPDATE
# 范式 | 隔离级别 | 脏读 | 不可重复读 | 幻影读 |
| :---: | :---: | :---:| :---: |
| 未提交读 | YES | YES | YES |
| 提交读 | NO | YES | YES |
| 可重复读 | NO | NO | YES |
| 可串行化 | NO | NO | NO |
记 A->B 表示 A 函数决定于 B也可以说 B 函数依赖于 A。 # 五、数据库系统概述
## 基本术语
### 1. 数据模型
由数据结构、数据操作和完整性三个要素组成。
### 2. 数据库系统
数据库系统包含所有与数据库相关的内容,包括数据库、数据库管理系统、应用程序以及数据库管理员和用户,还包括相关的硬件和软件。
## 数据库的三层模式和两层映像
- 外模式:局部逻辑结构
- 模式:全局逻辑结构
- 内模式:物理结构
<div align="center"> <img src="../pics//20150928140509757.png" width="600"/> </div><br>
### 1. 外模式
又称用户模式,是用户和数据库系统的接口,特定的用户只能访问数据库系统提供给他的外模式中的数据。例如不同的用户创建了不同数据库,那么一个用户只能访问他有权限访问的数据库。
一个数据库可以有多个外模式,一个用户只能有一个外模式,但是一个外模式可以给多个用户使用。
### 2. 模式
可以分为概念模式和逻辑模式,概念模式可以用概念-关系来描述;逻辑模式使用特定的数据模式(比如关系模型)来描述数据的逻辑结构,这种逻辑结构包括数据的组成、数据项的名称、类型、取值范围。不仅如此,逻辑模式还要描述数据之间的关系、数据的完整性与安全性要求。
### 3. 内模式
又称为存储模式,描述记录的存储方式,例如索引的组织方式、数据是否压缩以及是否加密等等。
### 4. 外模式/模式映像
把外模式的局部逻辑结构和模式的全局逻辑结构联系起来。该映像可以保证数据和应用程序的逻辑独立性。
### 5. 模式/内模式映像
把模式的全局逻辑结构和内模式的物理结构联系起来,该映像可以保证数据和应用程序的物理独立性。
# 六、关系数据库建模
## ER 图
Entity-Relationship有三个组成部分实体、属性、联系。
### 1. 实体的三种联系
联系包含一对一,一对多,多对多三种。
如果 A 到 B 是一对多关系,那么画个带箭头的线段指向 B如果是一对一画两个带箭头的线段如果是多对多画两个不带箭头的线段。下图的 Course 和 Student 是一对多的关系。
<div align="center"> <img src="../pics//292b4a35-4507-4256-84ff-c218f108ee31.jpg"/> </div><br>
### 2. 表示出现多次的关系
一个实体在联系出现几次,就要用几条线连接。下图表示一个课程的先修关系,先修关系出现两个 Course 实体,第一个是先修课程,后一个是后修课程,因此需要用两条线来表示这种关系。
<div align="center"> <img src="../pics//8b798007-e0fb-420c-b981-ead215692417.jpg"/> </div><br>
### 3. 联系的多向性
虽然老师可以开设多门课,并且可以教授多名学生,但是对于特定的学生和课程,只有一个老师教授,这就构成了一个三元联系。
<div align="center"> <img src="../pics//423f2a40-bee1-488e-b460-8e76c48ee560.png"/> </div><br>
一般只使用二元联系,可以把多元关系转换为二元关系。
<div align="center"> <img src="../pics//de9b9ea0-1327-4865-93e5-6f805c48bc9e.png"/> </div><br>
### 4. 表示子类
用一个三角形和两条线来连接类和子类,与子类有关的属性和联系都连到子类上,而与父类和子类都有关的连到父类上。
<div align="center"> <img src="../pics//7ec9d619-fa60-4a2b-95aa-bf1a62aad408.jpg"/> </div><br>
## 约束
### 1. 键码
用于唯一表示一个实体。
键码可以由多个属性构成,每个构成键码的属性称为码。
### 2. 单值约束
某个属性的值是唯一的。
### 3. 引用完整性约束
一个实体的属性引用的值在另一个实体的某个属性中存在。
### 4. 域约束
某个属性的值在特定范围之内。
### 5. 一般约束
比如大小约束,数量约束。
# 七、关系数据库设计理论
## 函数依赖
记 A->B 表示 A 函数决定 B也可以说 B 函数依赖于 A。
如果 {A1A2... An} 是关系的一个或多个属性的集合,该集合决定了关系的其它所有属性并且是最小的,那么该集合就称为键码。 如果 {A1A2... An} 是关系的一个或多个属性的集合,该集合决定了关系的其它所有属性并且是最小的,那么该集合就称为键码。
对于函数依赖 W->A如果能找到 W 的真子集使得 A 依赖于这个真子集,那么就是部分依赖,否则就是完全依赖; 对于 W->A如果能找到 W 的真子集 W',使得 W'-> A那么 W->A 就是部分函数依赖,否则就是完全函数依赖;
以下关系中Sno 表示学号Sname 表示学生姓名Sdept 表示学院Cname 表示课程名Mname 表示院长姓名。函数依赖为 (Sno, Cname) -> (Sname, Sdept, Mname)。注:实际开发过程中,不会出现这种表,而是每个实体都放在单独一张表中,然后实体之间的联系表用实体 id 来表示。 ## 异常
<div align="center"> <img src="../pics//b6a678c0-c875-4038-afba-301846620786.jpg"/> </div><br> 以下关系中Sno 表示学号Sname 表示学生姓名Sdept 表示学院Cname 表示课程名Mname 表示院长姓名。函数依赖为 {Sno, Cname} -> {Sname, Sdept, Mname}。
<div align="center"> <img src="../pics//0b304499-0d7c-49cc-b784-3e7a805c9fba.jpg"/> </div><br>
不符合范式的关系,会产生很多异常。主要有以下四种异常: 不符合范式的关系,会产生很多异常。主要有以下四种异常:
@ -196,43 +305,45 @@ MySQL InnoDB 采用的是两阶段锁协议。在事务执行过程中,随时
3. 删除异常 3. 删除异常
4. 插入异常,比如如果新插入一个学生的信息,而这个学生还没选课,那么就无法插入该学生。 4. 插入异常,比如如果新插入一个学生的信息,而这个学生还没选课,那么就无法插入该学生。
关系数据库的范式理论就是是为了解决这四种异常。 ## 范式
范式理论是为了解决以上提到四种异常。
高级别范式的依赖基于低级别的范式。 高级别范式的依赖基于低级别的范式。
## 第一范式 (1NF) ### 1. 第一范式 (1NF)
属性不可分。 属性不可分。
## 第二范式 (2NF) ### 2. 第二范式 (2NF)
每个非主属性完全函数依赖于键码。 每个非主属性完全函数依赖于键码。
可以通过分解来满足。 可以通过分解来满足。
**分解前** <font size=4> **分解前** </font><br>
<div align="center"><img src="https://latex.codecogs.com/gif.latex?S(Sno,Cname,Sname,Sdept,Mname)"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?S(Sno,Cname,Sname,Sdept,Mname)"/></div> <br>
<div align="center"><img src="https://latex.codecogs.com/gif.latex?(Sno,Cname)->(Sname,Sdept,Mname)"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?Sno,Cname->Sname,Sdept,Mname"/></div> <br>
**分解后** <font size=4> **分解后** </font><br>
<div align="center"><img src="https://latex.codecogs.com/gif.latex?S1(Sno,Sname,Sdept,Mname)"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?S1(Sno,Sname,Sdept,Mname)"/></div> <br>
<div align="center"><img src="https://latex.codecogs.com/gif.latex?(Sno)->(Sname,Sdept,Mname"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?Sno->Sname,Sdept,Mname"/></div> <br>
<div align="center"><img src="https://latex.codecogs.com/gif.latex?(Sdept)->(Mname)"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?Sdept->Mname"/></div> <br>
<div align="center"> <img src="../pics//8ef22836-8800-4765-b4b8-ade80096b323.jpg"/> </div><br> <div align="center"> <img src="../pics//8ef22836-8800-4765-b4b8-ade80096b323.jpg"/> </div><br>
<div align="center"><img src="https://latex.codecogs.com/gif.latex?S2(Sno,Cname,Grade)"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?S2(Sno,Cname,Grade)"/></div> <br>
<div align="center"><img src="https://latex.codecogs.com/gif.latex?(Sno,Cname)->(Grade)"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?Sno,Cname->Grade"/></div> <br>
<div align="center"> <img src="../pics//b0748916-1acd-4138-b24c-69326cb452fe.jpg"/> </div><br> <div align="center"> <img src="../pics//b0748916-1acd-4138-b24c-69326cb452fe.jpg"/> </div><br>
## 第三范式 (3NF) ### 3. 第三范式 (3NF)
非主属性不传递依赖于键码。 非主属性不传递依赖于键码。
@ -240,111 +351,30 @@ MySQL InnoDB 采用的是两阶段锁协议。在事务执行过程中,随时
<div align="center"> <img src="../pics//923896c1-937e-4a38-b8a6-cec3040b4e2a.jpg"/> </div><br> <div align="center"> <img src="../pics//923896c1-937e-4a38-b8a6-cec3040b4e2a.jpg"/> </div><br>
## BC 范式BCNF ### 4. BC 范式BCNF
所有属性不传递依赖于键码。 所有属性不传递依赖于键码。
关系模式 STC(Sname, Tname, Cname, Grade),其中四个属性分别为学生姓名、教师姓名、课程名和成绩。有以下函数依赖: 关系模式 STC(Sname, Tname, Cname, Grade),其中四个属性分别为学生姓名、教师姓名、课程名和成绩。有以下函数依赖:
<div align="center"><img src="https://latex.codecogs.com/gif.latex?(Sname,Cname)->(Tname)"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?Sname,Cname->Tname"/></div> <br>
<div align="center"><img src="https://latex.codecogs.com/gif.latex?(Sname,Cname)->(Grade)"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?Sname,Cname->Grade"/></div> <br>
<div align="center"><img src="https://latex.codecogs.com/gif.latex?(Sname,Tname)->(Cname)"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?Sname,Tname->Cname"/></div> <br>
<div align="center"><img src="https://latex.codecogs.com/gif.latex?(Sname,Tname)->(Grade)"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?Sname,Tname->Grade"/></div> <br>
<div align="center"><img src="https://latex.codecogs.com/gif.latex?(Tname)->(Cname)"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?Tname->Cname"/></div> <br>
分解成 SC(Sname, Cname, Grade) 和 ST(Sname, Tname),对于 ST属性之间是多对多关系无函数依赖。 分解成 SC(Sname, Cname, Grade) 和 ST(Sname, Tname),对于 ST属性之间是多对多关系无函数依赖。
# 约束
## 键码
用于唯一表示一个实体。键码可以由多个属性构成,每个构成键码的属性成为码。
## 单值约束
某个属性的值是唯一的。
## 引用完整性约束
一个实体的属性引用的值在另一个实体的某个属性中存在。
## 域约束
某个属性的值在特定范围之内。
## 一般约束
一般性约束,比如大小约束,数量约束。
# 数据库的三层模式和两层映像
外模式:局部逻辑结构;模式:全局逻辑结构;内模式:物理结构。
## 外模式
又称用户模式,是用户和数据库系统的接口,特定的用户只能访问数据库系统提供给他的外模式中的数据。例如不同的用户创建了不同数据库,那么一个用户只能访问他有权限访问的数据库。一个数据库可以有多个外模式,一个用户只能有一个外模式,但是一个外模式可以给多个用户使用。
## 模式
可以分为概念模式和逻辑模式,概念模式可以用概念 - 关系来描述;逻辑模式使用特定的数据模式(比如关系模型)来描述数据的逻辑结构,这种逻辑结构包括数据的组成、数据项的名称、类型、取值范围。不仅如此,逻辑模式还要描述数据之间的关系,数据的完整性与安全性要求。
## 内模式
又称为存储模式,描述记录的存储方式,例如索引的组织方式、数据是否压缩以及是否加密等等。
## 外模式/模式映像
把外模式的局部逻辑结构和模式的全局逻辑结构联系起来。该映像可以保证数据和应用程序的逻辑独立性。
## 模式/内模式映像
把模式的全局逻辑结构和内模式的物理结构联系起来,该映像可以保证数据和应用程序的物理独立性。
# ER 图
Entity-Relationship包含三个部分实体、属性、联系。
## 实体的三种联系
联系包含 1 对 11 对多,多对多三种。
如果 A 到 B 是 1 对多关系,那么画个带箭头的线段指向 B如果是 1 对 1画两个带箭头的线段如果是多对多画两个不带箭头的线段。
<div align="center"> <img src="../pics//292b4a35-4507-4256-84ff-c218f108ee31.jpg"/> </div><br>
## 表示出现多次的关系
一个实体在联系出现几次,就要用几条线连接。如下表示一个课程的先修关系,先修关系中,应当出现两个 Course 实体,第一个是先修课程,后一个是后修课程,因此需要用两条线来表示这种关系。
<div align="center"> <img src="../pics//8b798007-e0fb-420c-b981-ead215692417.jpg"/> </div><br>
## 联系的多向性
下图中一个联系表示三个实体的关系。虽然老师可以开设多门课,并且可以教授多名学生,但是对于特定的学生和课程,只有一个老师教授,这就构成了一个三元联系。
<div align="center"> <img src="../pics//423f2a40-bee1-488e-b460-8e76c48ee560.png"/> </div><br>
一般只使用二元联系,可以把多元关系转换为二元关系。
<div align="center"> <img src="../pics//de9b9ea0-1327-4865-93e5-6f805c48bc9e.png"/> </div><br>
## 表示子类
用 is-a 联系来表示子类,具体做法是用一个三角形和两条线来连接类和子类。与子类有关的属性和联系都连到子类上,而与父类和子类都有关的连到父类上。
<div align="center"> <img src="../pics//7ec9d619-fa60-4a2b-95aa-bf1a62aad408.jpg"/> </div><br>
# 一些概念
**数据模型** 由数据结构、数据操作和完整性三个要素组成。
**数据库系统** 包括了数据库,数据库管理系统,应用程序以及数据库管理员和用户,还包括相关的硬件和软件。也就是说数据库系统包含所有与数据库相关的内容。
# 参考资料 # 参考资料
- 史嘉权. 数据库系统概论[M]. 清华大学出版社有限公司, 2006. - 史嘉权. 数据库系统概论[M]. 清华大学出版社有限公司, 2006.
- 施瓦茨. 高性能MYSQL(第3版)[M]. 电子工业出版社, 2013.
- [MySQL 乐观锁与悲观锁 ](https://www.jianshu.com/p/f5ff017db62a) - [MySQL 乐观锁与悲观锁 ](https://www.jianshu.com/p/f5ff017db62a)
- [Transaction isolation levels](https://www.slideshare.net/ErnestoHernandezRodriguez/transaction-isolation-levels)
- [Concurrency Control](http://scanftree.com/dbms/2-phase-locking-protocol)
- [The Nightmare of Locking, Blocking and Isolation Levels!](https://www.slideshare.net/brshristov/the-nightmare-of-locking-blocking-and-isolation-levels-46391666)
- [三级模式与两级映像](http://blog.csdn.net/d2457638978/article/details/48783923)

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

@ -0,0 +1,400 @@
<!-- GFM-TOC -->
* [一、概述](#一概述)
* [二、匹配单个字符](#二匹配单个字符)
* [三、匹配一组字符](#三匹配一组字符)
* [四、使用元字符](#四使用元字符)
* [五、重复匹配](#五重复匹配)
* [六、位置匹配](#六位置匹配)
* [七、使用子表达式](#七使用子表达式)
* [八、回溯引用](#八回溯引用)
* [九、前后查找](#九前后查找)
* [十、嵌入条件](#十嵌入条件)
<!-- 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**

File diff suppressed because it is too large Load Diff

View File

@ -1,89 +1,30 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [第一章 概述](#第一章-概述) * [一、 概述](#一-概述)
* [操作系统基本特征](#操作系统基本特征) * [操作系统基本特征](#操作系统基本特征)
* [1. 并发](#1-并发)
* [2. 共享](#2-共享)
* [3. 虚拟](#3-虚拟)
* [4. 异步](#4-异步)
* [系统调用](#系统调用) * [系统调用](#系统调用)
* [中断分类](#中断分类) * [中断分类](#中断分类)
* [1. 外中断](#1-外中断)
* [2. 异常](#2-异常)
* [3. 陷入](#3-陷入)
* [大内核和微内核](#大内核和微内核) * [大内核和微内核](#大内核和微内核)
* [1. 大内核](#1-大内核) * [二、 进程管理](#二-进程管理)
* [2. 微内核](#2-微内核)
* [第二章 进程管理](#第二章-进程管理)
* [进程与线程](#进程与线程) * [进程与线程](#进程与线程)
* [1. 进程](#1-进程)
* [2. 线程](#2-线程)
* [3. 区别](#3-区别)
* [进程状态的切换](#进程状态的切换) * [进程状态的切换](#进程状态的切换)
* [调度算法](#调度算法) * [调度算法](#调度算法)
* [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-短进程优先)
* [3. 实时系统中的调度](#3-实时系统中的调度)
* [进程同步](#进程同步) * [进程同步](#进程同步)
* [1. 临界区](#1-临界区)
* [2. 同步与互斥](#2-同步与互斥)
* [3. 信号量](#3-信号量)
* [4. 管程](#4-管程)
* [进程通信](#进程通信) * [进程通信](#进程通信)
* [1. 管道](#1-管道)
* [2. 信号量](#2-信号量)
* [3. 消息队列](#3-消息队列)
* [4. 信号](#4-信号)
* [5. 共享内存](#5-共享内存)
* [6. 套接字](#6-套接字)
* [经典同步问题](#经典同步问题) * [经典同步问题](#经典同步问题)
* [1. 读者-写者问题](#1-读者-写者问题) * [三、死锁](#三死锁)
* [2. 哲学家进餐问题](#2-哲学家进餐问题)
* [第三章 死锁](#第三章-死锁)
* [死锁的必要条件](#死锁的必要条件) * [死锁的必要条件](#死锁的必要条件)
* [死锁的处理方法](#死锁的处理方法) * [死锁的处理方法](#死锁的处理方法)
* [1. 鸵鸟策略](#1-鸵鸟策略) * [四、存储器管理](#四存储器管理)
* [2. 死锁预防](#2-死锁预防)
* [2.1 破坏互斥条件](#21-破坏互斥条件)
* [2.2 破坏占有和等待条件](#22-破坏占有和等待条件)
* [2.3 破坏不可抢占条件](#23-破坏不可抢占条件)
* [2.4 破坏环路等待](#24-破坏环路等待)
* [3. 死锁避免](#3-死锁避免)
* [3.1 安全状态](#31-安全状态)
* [3.2 单个资源的银行家算法](#32-单个资源的银行家算法)
* [3.3 多个资源的银行家算法](#33-多个资源的银行家算法)
* [4. 死锁检测与死锁恢复](#4-死锁检测与死锁恢复)
* [4.1 死锁检测算法](#41-死锁检测算法)
* [4.2 死锁恢复](#42-死锁恢复)
* [第四章 存储器管理](#第四章-存储器管理)
* [虚拟内存](#虚拟内存) * [虚拟内存](#虚拟内存)
* [分页与分段](#分页与分段) * [分页与分段](#分页与分段)
* [1. 分页](#1-分页)
* [2. 分段](#2-分段)
* [3. 段页式](#3-段页式)
* [4. 分页与分段区别](#4-分页与分段区别)
* [页面置换算法](#页面置换算法) * [页面置换算法](#页面置换算法)
* [1. 最佳Optimal](#1-最佳optimal) * [五、设备管理](#五设备管理)
* [2. 先进先出FIFO](#2-先进先出fifo)
* [3. 最近最久未使用LRU, Least Recently Used](#3-最近最久未使用lru,-least-recently-used)
* [4. 时钟Clock](#4-时钟clock)
* [第五章 设备管理](#第五章-设备管理)
* [磁盘调度算法](#磁盘调度算法) * [磁盘调度算法](#磁盘调度算法)
* [1. 先来先服务FCFS, First Come First Serverd](#1-先来先服务fcfs,-first-come-first-serverd) * [六、参考资料](#六参考资料)
* [2. 最短寻道时间优先SSTF, Shortest Seek Time First](#2-最短寻道时间优先sstf,-shortest-seek-time-first)
* [3. 扫描算法SCAN](#3-扫描算法scan)
* [4. 循环扫描算法CSCAN](#4-循环扫描算法cscan)
* [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
# 第一章 概述 # 一、 概述
## 操作系统基本特征 ## 操作系统基本特征
@ -111,11 +52,11 @@
### 4. 异步 ### 4. 异步
异步指进程不是一次性执行完毕,而是走走停停,以不可知的速度向前推进。 异步指进程不是一次性执行完毕,而是走走停停,以不可知的速度向前推进。
## 系统调用 ## 系统调用
如果一个进程在用户态需要用到操作系统的一些功能,就需要使用系统调用从而陷入内核,由操作系统代为完成。 如果一个进程在用户态需要用到操作系统的一些功能,就使用系统调用从而陷入内核,由操作系统代为完成。
可以由系统调用请求的功能有设备管理、文件管理、进程管理、进程通信、存储器管理等。 可以由系统调用请求的功能有设备管理、文件管理、进程管理、进程通信、存储器管理等。
@ -147,7 +88,7 @@
因为需要频繁地在用户态和核心态之间进行切换,所以会有一定的性能损失。 因为需要频繁地在用户态和核心态之间进行切换,所以会有一定的性能损失。
# 第二章 进程管理 # 二、 进程管理
## 进程与线程 ## 进程与线程
@ -179,7 +120,7 @@
<div align="center"> <img src="../pics//1706ce58-a081-4fed-9b36-c3c0d7e22b3a.jpg"/> </div><br> <div align="center"> <img src="../pics//1706ce58-a081-4fed-9b36-c3c0d7e22b3a.jpg"/> </div><br>
阻塞状态是缺少需要的资源从而由运行状态转换而来,但是该资源不包括 CPU缺少 CPU 会让进程从运行态转换为就绪态。 阻塞状态是缺少需要的资源从而由运行状态转换而来,但是该资源不包括 CPU缺少 CPU 会从运行态转换为就绪态。
只有就绪态和运行态可以相互转换,其它的都是单向转换。就绪状态的进程通过调度算法从而获得 CPU 时间,转为运行状态;而运行状态的进程,在分配给它的 CPU 时间片用完之后就会转为就绪状态,等待下一次调度。 只有就绪态和运行态可以相互转换,其它的都是单向转换。就绪状态的进程通过调度算法从而获得 CPU 时间,转为运行状态;而运行状态的进程,在分配给它的 CPU 时间片用完之后就会转为就绪状态,等待下一次调度。
@ -211,7 +152,7 @@ shortest remaining time nextSRTN
### 2. 交互式系统中的调度 ### 2. 交互式系统中的调度
#### 2.1 优先权优先 #### 2.1 优先级调度
除了可以手动赋予优先权之外,还可以把响应比作为优先权,这种调度方式叫做高响应比优先调度算法。 除了可以手动赋予优先权之外,还可以把响应比作为优先权,这种调度方式叫做高响应比优先调度算法。
@ -223,21 +164,15 @@ shortest remaining time nextSRTN
将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 分配给队首的进程。 将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 分配给队首的进程。
时间片轮转算法的效率和时间片的大小有很大关系。因为每次进程切换都要保存进程的信息并且载入新进程的信息,如果时间片太小,进程切换太频繁,在进程切换上就会花过多时间。 时间片轮转算法的效率和时间片的大小有很大关系。因为进程切换都要保存进程的信息并且载入新进程的信息,如果时间片太小,会导致进程切换太频繁,在进程切换上就会花过多时间。
#### 2.3 多级反馈队列 #### 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>
1. 设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权越高的队列中,为每个进程所规定的执行时间片就越小 如果一个进程需要执行 100 个时间片,如果采用轮转调度算法,那么需要交换 100 次。多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如 1,2,4,8,..。进程在第一个队列没执行完,就会被移到下一个队列。这种方式下,之前的进程只需要 7 (包括最初的装入)的交换
2. 当一个新进程进入内存后,首先将它放入第一队列的末尾,按 FCFS 原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入下一个队列的队尾。 每个队列的优先权也不同,最上面的优先权最高。因此只有上一个队列没有进程在排队,才能调度当前队列上的进程。
3. 仅当前 i-1 个队列均空时,才会调度第 i 个队列中的进程。
优点:实时性好,同时适合运行短作业和长作业。
#### 2.4 短进程优先
### 3. 实时系统中的调度 ### 3. 实时系统中的调度
@ -261,15 +196,15 @@ shortest remaining time nextSRTN
### 2. 同步与互斥 ### 2. 同步与互斥
- 同步多个进程按一定顺序执行; - 同步多个进程按一定顺序执行;
- 互斥多个进程在同一时刻只有一个进程能进入临界区。 - 互斥多个进程在同一时刻只有一个进程能进入临界区。
### 3. 信号量 ### 3. 信号量
**信号量Semaphore** 是一个整型变量,可以对其执行 down 和 up 操作,也就是常见的 P 和 V 操作。 **信号量Semaphore** 是一个整型变量,可以对其执行 down 和 up 操作,也就是常见的 P 和 V 操作。
- **down** : 如果信号量大于 0 ,执行 -1 操作;如果信号量等于 0进程睡眠,等待信号量大于 0 - **down** : 如果信号量大于 0 ,执行 -1 操作;如果信号量等于 0进程睡眠等待信号量大于 0
- **up** :对信号量执行 +1 操作,并且唤醒睡眠的进程,让进程完成 down 操作。 - **up** :对信号量执行 +1 操作,唤醒睡眠的进程让其完成 down 操作。
down 和 up 操作需要被设计成原语,不可分割,通常的做法是在执行这些操作的时候屏蔽中断。 down 和 up 操作需要被设计成原语,不可分割,通常的做法是在执行这些操作的时候屏蔽中断。
@ -279,27 +214,27 @@ 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);
} }
``` ```
**使用信号量实现生产者-消费者问题** #### 使用信号量实现生产者-消费者问题
使用一个缓冲区来保存物品,只有缓冲区没有满,生产者才可以放入物品;只有缓冲区不为空,消费者才可以拿走物品。 使用一个缓冲区来保存物品,只有缓冲区没有满,生产者才可以放入物品;只有缓冲区不为空,消费者才可以拿走物品。
需要使用一个互斥量 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
@ -310,22 +245,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);
} }
} }
@ -358,7 +293,7 @@ end monitor;
管程引入了 **条件变量** 以及相关的操作:**wait()** 和 **signal()** 来实现同步操作。对条件变量执行 wait() 操作会导致调用进程阻塞把管程让出来让另一个进程持有。signal() 操作用于唤醒被阻塞的进程。 管程引入了 **条件变量** 以及相关的操作:**wait()** 和 **signal()** 来实现同步操作。对条件变量执行 wait() 操作会导致调用进程阻塞把管程让出来让另一个进程持有。signal() 操作用于唤醒被阻塞的进程。
**使用管程实现生成者-消费者问题** #### 使用管程实现生成者-消费者问题
```pascal ```pascal
monitor ProducerConsumer monitor ProducerConsumer
@ -408,9 +343,7 @@ end;
### 1. 管道 ### 1. 管道
管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。写进程在管道的尾端写入数据,读进程在管道的首端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。 写进程在管道的尾端写入数据,读进程在管道的首端读出数据。管道提供了简单的流控制机制,进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样地,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。
管道提供了简单的流控制机制,进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样地,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。
Linux 中管道是通过空文件来实现。 Linux 中管道是通过空文件来实现。
@ -422,7 +355,7 @@ Linux 中管道是通过空文件来实现。
### 2. 信号量 ### 2. 信号量
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止进程正在访问共享资源时,其它进程也访问该资源。主要作为进程间以及同一进程内不同线程之间的同步手段。 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止一个进程正在访问共享资源时,其它进程也访问该资源。主要作为进程间以及同一进程内不同线程之间的同步手段。
### 3. 消息队列 ### 3. 消息队列
@ -460,23 +393,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);
} }
} }
``` ```
@ -485,51 +418,79 @@ 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;
test(LEFT);
test(RIGHT);
up(&mutex);
}
void test(i) { // 尝试拿起两把筷子
if(state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] !=EATING) {
state[i] = EATING;
up(&s[i]);
} }
} }
``` ```
# 第三章 死锁
# 三、死锁
## 死锁的必要条件 ## 死锁的必要条件
@ -546,39 +507,84 @@ void philosopher(int i) {
把头埋在沙子里,假装根本没发生问题。 把头埋在沙子里,假装根本没发生问题。
这种策略不可取 因为解决死锁问题的代价很高,因此鸵鸟策略这种不采取任务措施的方案会获得更高的性能。当发生死锁时不会对用户造成多大影响,或发生死锁的概率很低,可以采用鸵鸟策略
### 2. 死锁预防 大多数操作系统,包括 UnixLinux 和 Windows处理死锁问题的办法仅仅是忽略它。
### 2. 死锁检测与死锁恢复
不试图阻止死锁,而是当检测到死锁发生时,采取措施进行恢复。
#### 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>
上图中,有三个进程四个资源,每个数据代表的含义如下:
- E 向量:资源总量
- A 向量:资源剩余量
- C 矩阵:每个进程所拥有的资源数量,每一行都代表一个进程拥有资源的数量
- R 矩阵:每个进程请求的资源数量
进程 P<sub>1</sub> 和 P<sub>2</sub> 所请求的资源都得不到满足,只有进程 P<sub>3</sub> 可以,让 P<sub>3</sub> 执行,之后释放 P<sub>3</sub> 拥有的资源,此时 A = (2 2 2 0)。P<sub>2</sub> 可以执行,执行后释放 P<sub>2</sub> 拥有的资源A = (4 2 2 1) 。P<sub>1</sub> 也可以执行。所有进程都可以顺利执行,没有死锁。
算法总结如下:
每个进程最开始时都不被标记,执行过程有可能被标记。当算法结束时,任何没有被标记的进程都是死锁进程。
1. 寻找一个没有标记的进程 P<sub>i</sub>,它所请求的资源小于等于 A。
2. 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 1。
3. 如果没有这样一个进程,算法终止。
#### 2.3 死锁恢复
- 利用抢占恢复
- 利用回滚恢复
- 通过杀死进程恢复
### 3. 死锁预防
在程序运行之前预防发生死锁。 在程序运行之前预防发生死锁。
#### 2.1 破坏互斥条件 #### 3.1 破坏互斥条件
例如假脱机打印机技术允许若干个进程同时输出,唯一真正请求物理打印机的进程是打印机守护进程。 例如假脱机打印机技术允许若干个进程同时输出,唯一真正请求物理打印机的进程是打印机守护进程。
#### 2.2 破坏占有和等待条件 #### 3.2 破坏占有和等待条件
一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。 一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。
#### 2.3 破坏不可抢占条件 #### 3.3 破坏不可抢占条件
#### 2.4 破坏环路等待 #### 3.4 破坏环路等待
给资源统一编号,进程只能按编号顺序来请求资源。 给资源统一编号,进程只能按编号顺序来请求资源。
### 3. 死锁避免 ### 4. 死锁避免
在程序运行时避免发生死锁。 在程序运行时避免发生死锁。
#### 3.1 安全状态 #### 4.1 安全状态
<div align="center"> <img src="../pics//ed523051-608f-4c3f-b343-383e2d194470.png"/> </div><br> <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 所示的状态时安全的。 图 a 的第二列 Has 表示已拥有的资源数,第三列 Max 表示总共需要的资源数Free 表示还有可以使用的资源数。从图 a 开始出发,先让 B 拥有所需的所有资源(图 b运行结束后释放 B此时 Free 变为 5图 c;接着以同样的方式运行 C 和 A使得所有进程都能成功运行因此可以称图 a 所示的状态时安全的。
定义:如果没有死锁发生,并且即使所有进程突然请求对资源的最大需求,也仍然存在某种调度次序能够使得每一个进程运行完毕,则称该状态是安全的。 定义:如果没有死锁发生,并且即使所有进程突然请求对资源的最大需求,也仍然存在某种调度次序能够使得每一个进程运行完毕,则称该状态是安全的。
#### 3.2 单个资源的银行家算法 安全状态的检测与死锁的检测类似,因为安全状态必须要求不能发生死锁。下面的银行家算法与死锁检测算法非常类似,可以结合着做参考对比。
#### 4.2 单个资源的银行家算法
一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。 一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。
@ -586,7 +592,7 @@ void philosopher(int i) {
上图 c 为不安全状态,因此算法会拒绝之前的请求,从而避免进入图 c 中的状态。 上图 c 为不安全状态,因此算法会拒绝之前的请求,从而避免进入图 c 中的状态。
#### 3.3 多个资源的银行家算法 #### 4.3 多个资源的银行家算法
<div align="center"> <img src="../pics//62e0dd4f-44c3-43ee-bb6e-fedb9e068519.png"/> </div><br> <div align="center"> <img src="../pics//62e0dd4f-44c3-43ee-bb6e-fedb9e068519.png"/> </div><br>
@ -598,59 +604,29 @@ void philosopher(int i) {
- 假若找到这样一行,将该进程标记为终止,并将其已分配资源加到 A 中。 - 假若找到这样一行,将该进程标记为终止,并将其已分配资源加到 A 中。
- 重复以上两步,直到所有进程都标记为终止,则状态时安全的。 - 重复以上两步,直到所有进程都标记为终止,则状态时安全的。
### 4. 死锁检测与死锁恢复 # 四、存储器管理
不试图阻止死锁,而是当检测到死锁发生时,采取措施进行恢复。
#### 4.1 死锁检测算法
死锁检测的基本思想是,如果一个进程所请求的资源能够被满足,那么就让它执行,释放它拥有的所有资源,然后让其它能满足条件的进程执行。
<div align="center"> <img src="../pics//e1eda3d5-5ec8-4708-8e25-1a04c5e11f48.png"/> </div><br>
上图中,有三个进程四个资源,每个数据代表的含义如下:
- E 向量:资源总量
- A 向量:资源剩余量
- C 矩阵:每个进程所拥有的资源数量,每一行都代表一个进程拥有资源的数量
- R 矩阵:每个进程请求的资源数量
进程 P<sub>1</sub> 和 P<sub>2</sub> 所请求的资源都得不到满足,只有进程 P<sub>3</sub> 可以,让 P<sub>3</sub> 执行,之后释放 P<sub>3</sub> 拥有的资源,此时 A = (2 2 2 0)。P<sub>1</sub> 可以执行,执行后释放 P<sub>1</sub> 拥有的资源A = (4 2 2 2) P<sub>2</sub> 也可以执行。所有进程都可以顺利执行,没有死锁。
算法总结如下:
每个进程最开始时都不被标记,执行过程有可能被标记。当算法结束时,任何没有被标记的进程都是死锁进程。
1. 寻找一个没有标记的进程 P<sub>i</sub>,它所请求的资源小于等于 A。
2. 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 1。
3. 如果没有这样一个进程,算法终止。
可以看到,死锁检测和银行家算法中判断是否为安全状态的方法类似。
#### 4.2 死锁恢复
- 利用抢占恢复
- 杀死进程
# 第四章 存储器管理
## 虚拟内存 ## 虚拟内存
每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。 每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。当程序引用到一部分不在物理内存中的地址空间时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。
当程序引用到一部分在物理内存中的地址空间时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。
## 分页与分段 ## 分页与分段
### 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>
@ -690,7 +666,7 @@ void philosopher(int i) {
<div align="center"><img src="https://latex.codecogs.com/gif.latex?70120304230321201701"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?70120304230321201701"/></div> <br>
进程运行时,先将 7,0,1 三个页面装入内存。当进程要访问页面 2 时,产生缺页中断,会将页面 7 换出,因为页面 7 再次被访问的时间最长。 进程运行时,先将 7, 0, 1 三个页面装入内存。当进程要访问页面 2 时,产生缺页中断,会将页面 7 换出,因为页面 7 再次被访问的时间最长。
### 2. 先进先出FIFO ### 2. 先进先出FIFO
@ -702,7 +678,7 @@ void philosopher(int i) {
虽然无法知道将来要使用的页面情况但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出。 虽然无法知道将来要使用的页面情况但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出。
可以用栈来实现该算法,栈中存储页面的页面号。当进程访问一个页面时,将该页面的页面号从栈移除,并将它压入栈顶。这样,最近被访问的页面的页面号总是在栈顶,而最近最久未使用的页面的页面号总是在栈底。 可以用栈来实现该算法,栈中存储页面的页面号。当进程访问一个页面时,将该页面的页面号从栈移除,并将它压入栈顶。这样,最近被访问的页面总是在栈顶,而最近最久未使用的页面总是在栈底。
<div align="center"><img src="https://latex.codecogs.com/gif.latex?47071012126"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?47071012126"/></div> <br>
@ -714,13 +690,13 @@ Clock 页面置换算法需要用到一个访问位,当一个页面被访问
首先,将内存中的所有页面链接成一个循环队列,当缺页中断发生时,检查当前指针所指向页面的访问位,如果访问位为 0就将该页面换出否则将该页的访问位设置为 0给该页面第二次的机会移动指针继续检查。 首先,将内存中的所有页面链接成一个循环队列,当缺页中断发生时,检查当前指针所指向页面的访问位,如果访问位为 0就将该页面换出否则将该页的访问位设置为 0给该页面第二次的机会移动指针继续检查。
# 第五章 设备管理 # 五、设备管理
## 磁盘调度算法 ## 磁盘调度算法
当多个进程同时请求访问磁盘时,需要进行磁盘调度来控制对磁盘的访问。磁盘调度的主要目标是使磁盘的平均寻道时间最少。 当多个进程同时请求访问磁盘时,需要进行磁盘调度来控制对磁盘的访问。磁盘调度的主要目标是使磁盘的平均寻道时间最少。
### 1. 先来先服务FCFS, First Come First Serverd ### 1. 先来先服务FCFS, First Come First Served
根据进程请求访问磁盘的先后次序来进行调度。优点是公平和简单,缺点也很明显,因为未对寻道做任何优化,使平均寻道时间可能较长。 根据进程请求访问磁盘的先后次序来进行调度。优点是公平和简单,缺点也很明显,因为未对寻道做任何优化,使平均寻道时间可能较长。
@ -740,7 +716,7 @@ SCAN 算法在 SSTF 算法之上考虑了磁头的移动方向,要求所请求
CSCAN 对 SCAN 进行了改动,要求磁头始终沿着一个方向移动。 CSCAN 对 SCAN 进行了改动,要求磁头始终沿着一个方向移动。
# 参考资料 # 六、参考资料
- Tanenbaum A S, Bos H. Modern operating systems[M]. Prentice Hall Press, 2014. - Tanenbaum A S, Bos H. Modern operating systems[M]. Prentice Hall Press, 2014.
- 汤子瀛, 哲凤屏, 汤小丹. 计算机操作系统[M]. 西安电子科技大学出版社, 2001. - 汤子瀛, 哲凤屏, 汤小丹. 计算机操作系统[M]. 西安电子科技大学出版社, 2001.

View File

@ -5,60 +5,34 @@
* [互联网的组成](#互联网的组成) * [互联网的组成](#互联网的组成)
* [主机之间的通信方式](#主机之间的通信方式) * [主机之间的通信方式](#主机之间的通信方式)
* [电路交换与分组交换](#电路交换与分组交换) * [电路交换与分组交换](#电路交换与分组交换)
* [1. 电路交换](#1-电路交换)
* [2. 报文交换](#2-报文交换)
* [3. 分组交换](#3-分组交换)
* [时延](#时延) * [时延](#时延)
* [1. 发送时延](#1-发送时延) * [计算机网络体系结构 *](#计算机网络体系结构-)
* [2. 传播时延](#2-传播时延)
* [3. 处理时延](#3-处理时延)
* [4. 排队时延](#4-排队时延)
* [计算机网络体系结构*](#计算机网络体系结构)
* [1. 七层协议](#1-七层协议)
* [2. 五层协议](#2-五层协议)
* [3. 数据在各层之间的传递过程](#3-数据在各层之间的传递过程)
* [4. TCP/IP 体系结构](#4-tcpip-体系结构)
* [第二章 物理层](#第二章-物理层) * [第二章 物理层](#第二章-物理层)
* [通信方式](#通信方式) * [通信方式](#通信方式)
* [带通调制](#带通调制) * [带通调制](#带通调制)
* [信道复用技术](#信道复用技术) * [信道复用技术](#信道复用技术)
* [1. 频分复用、时分复用](#1-频分复用时分复用)
* [2. 统计时分复用](#2-统计时分复用)
* [3. 波分复用](#3-波分复用)
* [4. 码分复用](#4-码分复用)
* [第三章 数据链路层](#第三章-数据链路层) * [第三章 数据链路层](#第三章-数据链路层)
* [三个基本问题](#三个基本问题) * [三个基本问题](#三个基本问题)
* [1. 封装成帧](#1-封装成帧)
* [2. 透明传输](#2-透明传输)
* [3. 差错检测](#3-差错检测)
* [点对点信道 - PPP 协议](#点对点信道---ppp-协议) * [点对点信道 - PPP 协议](#点对点信道---ppp-协议)
* [局域网的拓扑](#局域网的拓扑) * [局域网的拓扑](#局域网的拓扑)
* [广播信道 - CSMA/CD 协议*](#广播信道---csmacd-协议) * [广播信道- CSMA/CD 协议 *](#广播信道--csmacd-协议-)
* [集线器](#集线器) * [扩展局域网 *](#扩展局域网-)
* [MAC 层*](#mac-层) * [MAC 层 *](#mac-层-)
* [虚拟局域网](#虚拟局域网) * [第四章 网络层 *](#第四章-网络层-)
* [第四章 网络层*](#第四章-网络层)
* [网际协议 IP 概述](#网际协议-ip-概述) * [网际协议 IP 概述](#网际协议-ip-概述)
* [IP 数据报格式](#ip-数据报格式) * [IP 数据报格式](#ip-数据报格式)
* [IP 地址编址](#ip-地址编址) * [IP 地址编址](#ip-地址编址)
* [1. 分类](#1-分类)
* [2. 子网划分](#2-子网划分)
* [3. 无分类](#3-无分类)
* [IP 地址和 MAC 地址](#ip-地址和-mac-地址) * [IP 地址和 MAC 地址](#ip-地址和-mac-地址)
* [地址解析协议 ARP](#地址解析协议-arp) * [地址解析协议 ARP](#地址解析协议-arp)
* [路由器的结构](#路由器的结构) * [路由器的结构](#路由器的结构)
* [交换机与路由器的区别](#交换机与路由器的区别)
* [路由器分组转发流程](#路由器分组转发流程) * [路由器分组转发流程](#路由器分组转发流程)
* [路由选择协议](#路由选择协议) * [路由选择协议](#路由选择协议)
* [1. 内部网关协议 RIP](#1-内部网关协议-rip)
* [2. 内部网关协议 OSPF](#2-内部网关协议-ospf)
* [3. 外部网关协议 BGP](#3-外部网关协议-bgp)
* [网际控制报文协议 ICMP](#网际控制报文协议-icmp) * [网际控制报文协议 ICMP](#网际控制报文协议-icmp)
* [分组网间探测 PING](#分组网间探测-ping) * [分组网间探测 PING](#分组网间探测-ping)
* [IP 多播](#ip-多播) * [IP 多播](#ip-多播)
* [虚拟专用网 VPN](#虚拟专用网-vpn) * [虚拟专用网 VPN](#虚拟专用网-vpn)
* [网络地址转换 NAT](#网络地址转换-nat) * [网络地址转换 NAT](#网络地址转换-nat)
* [第五章 运输层*](#第五章-运输层) * [第五章 运输层 *](#第五章-运输层-)
* [UDP 和 TCP 的特点](#udp-和-tcp-的特点) * [UDP 和 TCP 的特点](#udp-和-tcp-的特点)
* [UDP 首部格式](#udp-首部格式) * [UDP 首部格式](#udp-首部格式)
* [TCP 首部格式](#tcp-首部格式) * [TCP 首部格式](#tcp-首部格式)
@ -68,19 +42,12 @@
* [TCP 可靠传输](#tcp-可靠传输) * [TCP 可靠传输](#tcp-可靠传输)
* [TCP 流量控制](#tcp-流量控制) * [TCP 流量控制](#tcp-流量控制)
* [TCP 拥塞控制](#tcp-拥塞控制) * [TCP 拥塞控制](#tcp-拥塞控制)
* [慢开始与拥塞避免](#慢开始与拥塞避免) * [第六章 应用层 *](#第六章-应用层-)
* [快重传与快恢复](#快重传与快恢复)
* [第六章 应用层*](#第六章-应用层)
* [域名系统 DNS](#域名系统-dns) * [域名系统 DNS](#域名系统-dns)
* [1. 层次结构](#1-层次结构)
* [2. 解析过程](#2-解析过程)
* [文件传输协议 FTP](#文件传输协议-ftp) * [文件传输协议 FTP](#文件传输协议-ftp)
* [远程终端协议 TELNET](#远程终端协议-telnet) * [远程终端协议 TELNET](#远程终端协议-telnet)
* [万维网 WWW](#万维网-www) * [万维网 WWW](#万维网-www)
* [电子邮件协议](#电子邮件协议) * [电子邮件协议](#电子邮件协议)
* [POP3](#pop3)
* [IMAP](#imap)
* [SMTP](#smtp)
* [动态主机配置协议 DHCP](#动态主机配置协议-dhcp) * [动态主机配置协议 DHCP](#动态主机配置协议-dhcp)
* [点对点传输 P2P](#点对点传输-p2p) * [点对点传输 P2P](#点对点传输-p2p)
* [Web 页面请求过程](#web-页面请求过程) * [Web 页面请求过程](#web-页面请求过程)
@ -110,19 +77,16 @@
## 互联网的组成 ## 互联网的组成
1. 边缘部分:所有连接在互联网上的主机,用户可以直接使用; 1. 边缘部分:所有连接在互联网上的主机,用户可以直接使用;
2. 核心部分:由大量的网络和连接这些网络的路由器组成,为边缘部分的主机提供服务。 2. 核心部分:由大量的网络和连接这些网络的路由器组成,为边缘部分的主机提供服务。
<div align="center"> <img src="../pics//8ab40d6d-bd7c-47d3-afe8-6a8bc9f5d04c.jpg"/> </div><br> <div align="center"> <img src="../pics//8ab40d6d-bd7c-47d3-afe8-6a8bc9f5d04c.jpg"/> </div><br>
## 主机之间的通信方式 ## 主机之间的通信方式
**1. 客户-服务器C/S** 1. 客户-服务器C/S客户是服务的请求方服务器是服务的提供方。
客户是服务的请求方,服务器是服务的提供方。 2. 对等P2P不区分客户和服务器。
**2. 对等P2P**
不区分客户和服务器。
## 电路交换与分组交换 ## 电路交换与分组交换
@ -176,7 +140,7 @@
分组在路由器的输入队列和输出队列中排队等待的时间,取决于网络当前的通信量。 分组在路由器的输入队列和输出队列中排队等待的时间,取决于网络当前的通信量。
## 计算机网络体系结构* ## 计算机网络体系结构 *
<div align="center"> <img src="../pics//1005dc9d-9049-4b06-9524-6171e56ebd8c.png"/> </div><br> <div align="center"> <img src="../pics//1005dc9d-9049-4b06-9524-6171e56ebd8c.png"/> </div><br>
@ -193,11 +157,11 @@
2. 运输层:提供的是进程间的通用数据传输服务。由于应用层协议很多,定义通用的运输层协议就可以支持不断增多的应用层协议。运输层包括两种协议:传输控制协议 TCP提供面向连接、可靠的数据传输服务数据单位为报文段用户数据报协议 UDP提供无连接、尽最大努力的数据传输服务数据单位为用户数据报。TCP 主要提供完整性服务UDP 主要提供及时性服务。 2. 运输层:提供的是进程间的通用数据传输服务。由于应用层协议很多,定义通用的运输层协议就可以支持不断增多的应用层协议。运输层包括两种协议:传输控制协议 TCP提供面向连接、可靠的数据传输服务数据单位为报文段用户数据报协议 UDP提供无连接、尽最大努力的数据传输服务数据单位为用户数据报。TCP 主要提供完整性服务UDP 主要提供及时性服务。
3. 网络层:为主机之间提供服务,而不是像运输层协议那样是为主机中的进程提供服务。网络层把运输层传递下来的报文段或者用户数据报封装成分组来进行传输 3. 网络层:为主机之间提供数据传输服务,而像运输层协议那样是为主机中的进程提供服务。网络层把运输层传递下来的报文段或者用户数据报封装成分组。
4. 数据链路层:网络层针对的还是主机之间,而主机之间可以有很多链路,链路层协议就是为相邻结点之间提供服务。数据链路层把网络层传来的分组封装成帧。 4. 数据链路层:网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为相邻结点之间提供服务。数据链路层把网络层传来的分组封装成帧。
5. 物理层:考虑的是怎样在传输媒体上传输数据比特流,而不是指具体的传输媒体。物理层的作用是尽可能屏蔽传输媒体和通信手段的差异,使物理层上的数据链路层感觉不到这些差异。 5. 物理层:考虑的是怎样在传输媒体上传输数据比特流,而不是指具体的传输媒体。物理层的作用是尽可能屏蔽传输媒体和通信手段的差异,使数据链路层感觉不到这些差异。
### 3. 数据在各层之间的传递过程 ### 3. 数据在各层之间的传递过程
@ -259,7 +223,7 @@ TCP/IP 协议族是一种沙漏形状中间小两边大IP 协议在其中
为每个用户分配 m bit 的码片,并且所有的码片正交,对于任意两个码片 <img src="https://latex.codecogs.com/gif.latex?\vec{S}"/><img src="https://latex.codecogs.com/gif.latex?\vec{T}"/> 为每个用户分配 m bit 的码片,并且所有的码片正交,对于任意两个码片 <img src="https://latex.codecogs.com/gif.latex?\vec{S}"/><img src="https://latex.codecogs.com/gif.latex?\vec{T}"/>
<div align="center"><img src="https://latex.codecogs.com/gif.latex?\vec{S}\cdot\vec{T}=0"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?\frac{1}{m}\vec{S}\cdot\vec{T}=0"/></div> <br>
为了方便,取 m=8设码片 <img src="https://latex.codecogs.com/gif.latex?\vec{S}"/> 为 00011011。在拥有该码片的用户发送比特 1 时就发送该码片,发送比特 0 时就发送该码片的反码 11100100。 为了方便,取 m=8设码片 <img src="https://latex.codecogs.com/gif.latex?\vec{S}"/> 为 00011011。在拥有该码片的用户发送比特 1 时就发送该码片,发送比特 0 时就发送该码片的反码 11100100。
@ -291,7 +255,7 @@ TCP/IP 协议族是一种沙漏形状中间小两边大IP 协议在其中
透明表示一个实际存在的事物看起来好像不存在一样。 透明表示一个实际存在的事物看起来好像不存在一样。
中有首部和尾部,如果帧的数据部分含有和首部尾部相同的内容,那么帧的开始和结束位置就会被错误的判定。需要在数据中出现首部尾部相同的内容前面插入转义字符,如果需要传输的内容正好就是转义字符,那么就在转义字符前面再加个转义字符,在接收端进行处理之后可以还原出原始数据。这个过程透明传输的内容是转义字符,用户察觉不到转义字符的存在。 使用首部和尾部进行定界,如果帧的数据部分含有和首部尾部相同的内容,那么帧的开始和结束位置就会被错误的判定。需要在数据部分出现首部尾部相同的内容前面插入转义字符,如果出现转移字符,那么就在转义字符前面再加个转义字符,在接收端进行处理之后可以还原出原始数据。这个过程透明传输的内容是转义字符,用户察觉不到转义字符的存在。
<div align="center"> <img src="../pics//4146e14b-56b9-433c-8e3d-74b1b325399c.jpg"/> </div><br> <div align="center"> <img src="../pics//4146e14b-56b9-433c-8e3d-74b1b325399c.jpg"/> </div><br>
@ -318,15 +282,17 @@ TCP/IP 协议族是一种沙漏形状中间小两边大IP 协议在其中
<div align="center"> <img src="../pics//8b15e36f-69b4-46b6-a07c-7234ac7c7927.jpg"/> </div><br> <div align="center"> <img src="../pics//8b15e36f-69b4-46b6-a07c-7234ac7c7927.jpg"/> </div><br>
## 广播信道 - CSMA/CD 协议* ## 广播信道- CSMA/CD 协议 *
在广播信道上,同一时间只能允许一台计算机发送数据。 在广播信道上,同一时间只能允许一台计算机发送数据。
CSMA/CD 表示载波监听多点接入 / 碰撞检测。 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>
@ -334,32 +300,53 @@ CSMA/CD 表示载波监听多点接入 / 碰撞检测。
当发生碰撞时,站点要停止发送,等待一段时间再发送。这个时间采用 **截断二进制指数退避算法** 来确定,从离散的整数集合 {0, 1, .., (2<sup>k</sup>-1)} 中随机取出一个数,记作 r然后取 r 倍的争用期作为重传等待时间。 当发生碰撞时,站点要停止发送,等待一段时间再发送。这个时间采用 **截断二进制指数退避算法** 来确定,从离散的整数集合 {0, 1, .., (2<sup>k</sup>-1)} 中随机取出一个数,记作 r然后取 r 倍的争用期作为重传等待时间。
## 集线器 ## 扩展局域网 *
从表面上看,使用集线器的局域网在物理上是一个星型网。但是集线器使用电子器件来模拟实际缆线的工作,逻辑上仍是一个总线网,整个系统仍像一个传统以太网那样运行。 ### 1. 在物理层进行扩展
<div align="center"> <img src="../pics//3294ff06-f942-425e-aecc-ca04e45566d4.png"/> </div><br> 使用集线器进行扩展。
<div align="center"> <img src="../pics//b56ef52e-3d0f-4cdd-97dc-eaed893444a5.jpg"/> </div><br> 集线器的主要功能是对接收到的信号进行放大,以扩大网络的传输距离。
## MAC 层* 集线器不能根据 MAC 地址进行转发,而是以广播的方式发送数据帧。
MAC 地址是 6 字节48 位)的地址,用于唯一表示网络适配器(网卡),一台主机拥有多少个适配器就有多少个 MAC 地址,例如笔记本电脑普遍存在无线网络适配器和有线网络适配器 集线器是一种共享式的传输设备,意味着同一时刻只能传输一组数据帧
<div align="center"> <img src="../pics//50d38e84-238f-4081-8876-14ef6d7938b5.jpg"/> </div><br> <div align="center"> <img src="../pics//823cdab7-3779-4e3a-a951-dc2d154e0ee6.jpg"/> </div><br>
- **类型** :标记上层使用的协议; ### 2. 在链路层进行扩展
- **数据** :长度在 46-1500 之间,如果太小则需要填充;
- **FCS** :帧检验序列,使用的是 CRC 检验方法;
- **前同步码** :只是为了计算 FCS 临时加入的,计算结束之后会丢弃。
## 虚拟局域网 最开始使用的是网桥,它收到一个帧时,根据帧的 MAC 地址,查找网桥中的地址表,确定将帧转发的接口。
网桥不是共享式设备,因此性能比集线器这种共享式设备更高。
交换机的问世很快就淘汰了网桥,它实质上是一个多接口网桥,而网桥是两接口。交换机的每个接口都能直接与一个主机或者另一个交换机相连,并且一般都工作在全双工方式。
交换机具有自学习能力,学习的是交换表的内容,交换表中存储着 MAC 地址到接口的映射。下图中,交换机有 4 个接口,主机 A 向主机 B 发送数据帧时,交换机把主机 A 到接口 1 的映射写入交换表中。为了发送数据帧到 B先查交换表此时没有主机 B 的表项,那么主机 A 就发送广播帧,主机 C 和主机 D 会丢弃该帧,主机 B 收下之后,查找交换表得到主机 A 映射的接口为 1因此就把帧发送给主机 A同时交换机添加主机 B 到接口 3 的映射。
<div align="center"> <img src="../pics//c9cfcd20-c901-435f-9a07-3e46830c359f.jpg"/> </div><br>
### 3. 虚拟局域网
虚拟局域网可以建立与物理位置无关的逻辑组,只有在同一个虚拟局域网中的成员才会收到链路层广播信息,例如下图中 (A1, A2, A3, A4) 属于一个虚拟局域网A1 发送的广播会被 A2、A3、A4 收到,而其它站点收不到。 虚拟局域网可以建立与物理位置无关的逻辑组,只有在同一个虚拟局域网中的成员才会收到链路层广播信息,例如下图中 (A1, A2, A3, A4) 属于一个虚拟局域网A1 发送的广播会被 A2、A3、A4 收到,而其它站点收不到。
<div align="center"> <img src="../pics//a74b70ac-323a-4b31-b4d5-90569b8a944b.png"/> </div><br> <div align="center"> <img src="../pics//a74b70ac-323a-4b31-b4d5-90569b8a944b.png"/> </div><br>
# 第四章 网络层* ## MAC 层 *
MAC 地址是 6 字节48 位)的地址,用于唯一标识网络适配器(网卡),一台主机拥有多少个适配器就有多少个 MAC 地址,例如笔记本电脑普遍存在无线网络适配器和有线网络适配器。
<div align="center"> <img src="../pics//50d38e84-238f-4081-8876-14ef6d7938b5.jpg"/> </div><br>
- **类型** :标记上层使用的协议;
- **数据** :长度在 46-1500 之间,如果太小则需要填充;
- **FCS** :帧检验序列,使用的是 CRC 检验方法;
- **前同步码** :只是为了计算 FCS 临时加入的,计算结束之后会丢弃。
# 第四章 网络层 *
## 网际协议 IP 概述 ## 网际协议 IP 概述
@ -423,7 +410,7 @@ IP 地址 ::= {< 网络号 >, < 主机号 >}
IP 地址 ::= {< 网络号 >, < 子网号 >, < 主机号 >} IP 地址 ::= {< 网络号 >, < 子网号 >, < 主机号 >}
要使用子网,必须配置子网掩码。一个 B 类地址的默认子网掩码为 255.255.0.0,如果 B 类地址的子网占两个比特,那么子网掩码为 11111111 11111111 11000000 000000也就是 255.255.192.0。 要使用子网,必须配置子网掩码。一个 B 类地址的默认子网掩码为 255.255.0.0,如果 B 类地址的子网占两个比特,那么子网掩码为 11111111 11111111 11000000 00000000,也就是 255.255.192.0。
### 3. 无分类 ### 3. 无分类
@ -449,7 +436,11 @@ CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为
实现由 IP 地址得到 MAC 地址。 实现由 IP 地址得到 MAC 地址。
每个主机都有一个 ARP 高速缓存,存放映射表。如果一个 IP 地址到 MAC 地址的映射不在该表中,主机通过广播的方式发送 ARP 请求分组,匹配 IP 地址的主机会发送 ARP 响应分组告知其 MAC 地址。 <div align="center"> <img src="../pics//1b4d6737-d834-46ed-8f9d-6f123e29c8dd.jpg"/> </div><br>
每个主机都有一个 ARP 高速缓存,里面有本局域网上的各主机和路由器的 IP 地址到硬件地址的映射表。
如果主机 A 知道主机 B 的 IP 地址,但是 ARP 高速缓存中没有该 IP 地址到 MAC 地址的映射,此时主机 A 通过广播的方式发送 ARP 请求分组,主机 B 收到该请求后会发送 ARP 响应分组给主机 A 告知其 MAC 地址,随后主机 A 向其高速缓存中写入主机 B 的 IP 地址到硬件地址的映射。
<div align="center"> <img src="../pics//8bc6fc2c-d198-4759-b06c-18d94d851e97.png"/> </div><br> <div align="center"> <img src="../pics//8bc6fc2c-d198-4759-b06c-18d94d851e97.png"/> </div><br>
@ -465,18 +456,19 @@ CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为
<div align="center"> <img src="../pics//7f82fd18-7f16-4125-ada6-bb6b795b4fda.png"/> </div><br> <div align="center"> <img src="../pics//7f82fd18-7f16-4125-ada6-bb6b795b4fda.png"/> </div><br>
## 交换机与路由器的区别
- 交换机工作于数据链路层,能识别 MAC 地址,根据 MAC 地址转发链路层数据帧。具有自学机制来维护 IP 地址与 MAC 地址的映射。
- 路由器位于网络层,能识别 IP 地址并根据 IP 地址转发分组。维护着路由表,根据路由表选择最佳路线。
## 路由器分组转发流程 ## 路由器分组转发流程
1. 从数据报的首部提取目的主机的 IP 地址 D得到目的网络地址 N。路由表项是网络号而不是 IP 地址,这样做大大减少了路由表条目数量) 1. 从数据报的首部提取目的主机的 IP 地址 D得到目的网络地址 N。路由表项是网络号而不是 IP 地址,这样做大大减少了路由表条目数量);
2. 若 N 就是与此路由器直接相连的某个网络地址,则进行直接交付; 2. 若 N 就是与此路由器直接相连的某个网络地址,则进行直接交付;
3. 若路由表中有目的地址为 D 的特定主机路由,则把数据报传送给表中所指明的下一跳路由器; 3. 若路由表中有目的地址为 D 的特定主机路由,则把数据报传送给表中所指明的下一跳路由器;
4. 若路由表中有到达网络 N 的路由,则把数据报传送给路由表中所指明的下一跳路由器; 4. 若路由表中有到达网络 N 的路由,则把数据报传送给路由表中所指明的下一跳路由器;
5. 若路由表中有一个默认路由,则把数据报传送给路由表中所指明的默认路由器; 5. 若路由表中有一个默认路由,则把数据报传送给路由表中所指明的默认路由器;
6. 报告转发分组出错。 6. 报告转发分组出错。
<div align="center"> <img src="../pics//8d211911-0e62-4190-ab00-d8610adec4a0.jpg"/> </div><br> <div align="center"> <img src="../pics//8d211911-0e62-4190-ab00-d8610adec4a0.jpg"/> </div><br>
@ -583,7 +575,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。
<div align="center"> <img src="../pics//0f31bc7a-d60b-48a6-8e3f-597708369e52.png"/> </div><br> <div align="center"> <img src="../pics//0f31bc7a-d60b-48a6-8e3f-597708369e52.png"/> </div><br>
# 第五章 运输层* # 第五章 运输层 *
网络层只把分组发送到目的主机,但是真正通信的并不是主机而是主机中的进程。 网络层只把分组发送到目的主机,但是真正通信的并不是主机而是主机中的进程。
@ -599,7 +591,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。
<div align="center"> <img src="../pics//bd6c05f3-02ee-4c8a-b374-40c87154a898.jpg"/> </div><br> <div align="center"> <img src="../pics//bd6c05f3-02ee-4c8a-b374-40c87154a898.jpg"/> </div><br>
首部字段只有 8 个字节包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和临时添加的。 首部字段只有 8 个字节包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和临时添加的。
## TCP 首部格式 ## TCP 首部格式
@ -626,8 +618,11 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。
假设 A 为客户端B 为服务器端。 假设 A 为客户端B 为服务器端。
1. 首先 B 处于 LISTEN监听状态等待客户的连接请求。 1. 首先 B 处于 LISTEN监听状态等待客户的连接请求。
2. A 向 B 发送连接请求报文段SYN=1ACK=0选择一个初始的序号 x。 2. A 向 B 发送连接请求报文段SYN=1ACK=0选择一个初始的序号 x。
3. B 收到连接请求报文段,如果同意建立连接,则向 A 发送连接确认报文段SYN=1ACK=1确认号为 x+1同时也选择一个初始的序号 y。 3. B 收到连接请求报文段,如果同意建立连接,则向 A 发送连接确认报文段SYN=1ACK=1确认号为 x+1同时也选择一个初始的序号 y。
4. A 收到 B 的连接确认报文段后,还要向 B 发出确认,确认号为 y+1序号为 x+1。 4. A 收到 B 的连接确认报文段后,还要向 B 发出确认,确认号为 y+1序号为 x+1。
5. B 收到 A 的确认后,连接建立。 5. B 收到 A 的确认后,连接建立。
@ -639,15 +634,19 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。
以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK因为 ACK 在连接建立之后都为 1。 以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 ACK因为 ACK 在连接建立之后都为 1。
1. A 发送连接释放报文段FIN=1 1. A 发送连接释放报文段FIN=1
2. B 收到之后发出确认,此时 TCP 属于半关闭状态B 能向 A 发送数据但是 A 不能向 B 发送数据; 2. B 收到之后发出确认,此时 TCP 属于半关闭状态B 能向 A 发送数据但是 A 不能向 B 发送数据;
3. 当 B 要不再需要连接时发送连接释放请求报文段FIN=1 3. 当 B 要不再需要连接时发送连接释放请求报文段FIN=1
4. A 收到后发出确认,此时连接释放。 4. A 收到后发出确认,此时连接释放。
**TIME_WAIT** ### TIME_WAIT
客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间。这么做有两个理由: 客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间。这么做有两个理由:
1. 确保最后一个确认报文段能够到达。如果 B 没收到 A 发送来的确认报文段那么就会重新发送连接释放请求报文段A 等待一段时间就是为了处理这种情况的发生。 1. 确保最后一个确认报文段能够到达。如果 B 没收到 A 发送来的确认报文段那么就会重新发送连接释放请求报文段A 等待一段时间就是为了处理这种情况的发生。
2. 可能存在“已失效的连接请求报文段”,为了防止这种报文段出现在本次连接之外,需要等待一段时间。 2. 可能存在“已失效的连接请求报文段”,为了防止这种报文段出现在本次连接之外,需要等待一段时间。
## TCP 滑动窗口 ## TCP 滑动窗口
@ -695,7 +694,7 @@ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、
<div align="center"> <img src="../pics//346244ff-98c1-4f12-9a87-d0832e8c04cf.jpg"/> </div><br> <div align="center"> <img src="../pics//346244ff-98c1-4f12-9a87-d0832e8c04cf.jpg"/> </div><br>
### 慢开始与拥塞避免 ### 1. 慢开始与拥塞避免
发送的最初执行慢开始,令 cwnd=1发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍因此之后发送方能够发送的报文段为2、4、8 ... 发送的最初执行慢开始,令 cwnd=1发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍因此之后发送方能够发送的报文段为2、4、8 ...
@ -703,7 +702,7 @@ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、
如果出现了超时,则令 ssthresh = cwnd / 2然后重新执行慢开始。 如果出现了超时,则令 ssthresh = cwnd / 2然后重新执行慢开始。
### 快重传与快恢复 ### 2. 快重传与快恢复
在接收方,要求每次接收到报文段都应该发送对已收到有序报文段的确认,例如已经接收到 M<sub>1</sub> 和 M<sub>2</sub>,此时收到 M<sub>4</sub>,应当发送对 M<sub>2</sub> 的确认。 在接收方,要求每次接收到报文段都应该发送对已收到有序报文段的确认,例如已经接收到 M<sub>1</sub> 和 M<sub>2</sub>,此时收到 M<sub>4</sub>,应当发送对 M<sub>2</sub> 的确认。
@ -713,7 +712,7 @@ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、
<div align="center"> <img src="../pics//b18d679b-c8e2-4564-88ee-7600090e46da.jpg"/> </div><br> <div align="center"> <img src="../pics//b18d679b-c8e2-4564-88ee-7600090e46da.jpg"/> </div><br>
# 第六章 应用层* # 第六章 应用层 *
## 域名系统 DNS ## 域名系统 DNS
@ -774,15 +773,15 @@ TELNET 可以适应许多计算机和操作系统的差异,例如不同操作
<div align="center"> <img src="../pics//de1e46d2-748f-4da3-a29e-7de7bc840366.jpg"/> </div><br> <div align="center"> <img src="../pics//de1e46d2-748f-4da3-a29e-7de7bc840366.jpg"/> </div><br>
### POP3 ### 1. POP3
POP3 的特点是只要用户从服务器上读取了邮件,就把该邮件删除。 POP3 的特点是只要用户从服务器上读取了邮件,就把该邮件删除。
### IMAP ### 2. IMAP
IMAP 协议中客户端和服务器上的邮件保持同步如果不去手动删除邮件那么服务器上的邮件也不会被删除。IMAP 这种做法可以让用户随时随地去访问服务器上的邮件。IMAP 协议也支持创建自定义的文件夹。 IMAP 协议中客户端和服务器上的邮件保持同步如果不去手动删除邮件那么服务器上的邮件也不会被删除。IMAP 这种做法可以让用户随时随地去访问服务器上的邮件。IMAP 协议也支持创建自定义的文件夹。
### SMTP ### 3. SMTP
SMTP 只能发送 ASCII 码,而互联网邮件扩充 MIME 可以发送二进制文件。MIME 并没有改动或者取代 SMTP而是增加邮件主题的结构定义了非 ASCII 码的编码规则。 SMTP 只能发送 ASCII 码,而互联网邮件扩充 MIME 可以发送二进制文件。MIME 并没有改动或者取代 SMTP而是增加邮件主题的结构定义了非 ASCII 码的编码规则。
@ -812,22 +811,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,82 +1,77 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [前言](#前言) * [一、前言](#一前言)
* [第一章 设计模式入门](#第一章-设计模式入门) * [二、设计模式概念](#二设计模式概念)
* [第二章 观察者模式](#第二章-观察者模式) * [三、策略模式](#三策略模式)
* [第三章 装饰模式](#第三章-装饰模式) * [三、观察者模式](#三观察者模式)
* [第四章 工厂模式](#第四章-工厂模式) * [四、装饰模式](#四装饰模式)
* [1. 简单工厂](#1-简单工厂) * [五、简单工厂](#五简单工厂)
* [2. 工厂方法模式](#2--工厂方法模式) * [六、工厂方法模式](#六工厂方法模式)
* [3. 抽象工厂模式](#3--抽象工厂模式) * [七、抽象工厂模式](#七抽象工厂模式)
* [第五章 单件模式](#第五章-单件模式) * [八、单例模式](#八单例模式)
* [第六章 命令模式](#第六章-命令模式) * [九、命令模式](#九命令模式)
* [第七章 适配器模式与外观模式](#第七章-适配器模式与外观模式) * [十、适配器模式](#十适配器模式)
* [1. 适配器模式](#1-适配器模式) * [十、外观模式](#十外观模式)
* [2. 外观模式](#2-外观模式) * [十一、模板方法模式](#十一模板方法模式)
* [第八章 模板方法模式](#第八章-模板方法模式) * [十二、迭代器模式](#十二迭代器模式)
* [第九章 迭代器和组合模式](#第九章-迭代器和组合模式) * [十三、组合模式](#十三组合模式)
* [1. 迭代器模式](#1-迭代器模式) * [十四、状态模式](#十四状态模式)
* [2. Java 内置的迭代器](#2-java-内置的迭代器) * [十五、代理模式](#十五代理模式)
* [3. 组合模式](#3-组合模式) * [十六、MVC](#十六mvc)
* [第十章 状态模式](#第十章-状态模式) * [十七、与设计模式相处](#十七与设计模式相处)
* [第十一章 代理模式](#第十一章-代理模式)
* [第十二章 复合模式](#第十二章-复合模式)
* [MVC](#mvc)
* [第十三章 与设计模式相处](#第十三章-与设计模式相处)
* [第十四章 剩下的模式](#第十四章-剩下的模式)
<!-- GFM-TOC --> <!-- GFM-TOC -->
# 前言 # 一、前言
文中涉及一些 UML 类图,为了更好地理解,可以先阅读 [UML 类图](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E6%80%9D%E6%83%B3.md#1-%E7%B1%BB%E5%9B%BE)。 文中涉及一些 UML 类图,为了更好地理解,可以先阅读 [UML 类图](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E6%80%9D%E6%83%B3.md#%E7%AC%AC%E4%B8%89%E7%AB%A0-uml)。
需要说明的一点是,文中的 UML 类图和规范的 UML 类图不大相同,其中组合关系使用以下箭头表示: 需要说明的一点是,文中的 UML 类图和规范的 UML 类图不大相同,其中组合关系使用以下箭头表示:
<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. 设计模式概念**
设计模式不是代码,而是解决问题的方案,学习现有的设计模式可以做到经验复用。 设计模式不是代码,而是解决问题的方案,学习现有的设计模式可以做到经验复用。
拥有设计模式词汇,在沟通时就能用更少的词汇来讨论,并且不需要了解底层细节。 拥有设计模式词汇,在沟通时就能用更少的词汇来讨论,并且不需要了解底层细节。
**2. 问题描述** # 三、策略模式
## 模式定义
定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
## 问题描述
设计不同种类的鸭子拥有不同的叫声和飞行方式。 设计不同种类的鸭子拥有不同的叫声和飞行方式。
**3. 简单实现方案** ## 简单实现方案
使用继承的解决方案如下,这种方案代码无法复用,如果两个鸭子类拥有同样的飞行方式,就有两份重复的代码。 使用继承的解决方案如下,这种方案代码无法复用,如果两个鸭子类拥有同样的飞行方式,就有两份重复的代码。
<div align="center"> <img src="../pics//144d28a0-1dc5-4aba-8961-ced5bc88428a.jpg"/> </div><br> <div align="center"> <img src="../pics//144d28a0-1dc5-4aba-8961-ced5bc88428a.jpg"/> </div><br>
**4. 设计原则** ## 设计原则
**封装变化** 在这里变化的是鸭子叫和飞行的行为方式。 **封装变化** 在这里变化的是鸭子叫和飞行的行为方式。
**针对接口编程,而不是针对实现编程** 变量声明的类型为父类,而不是具体的某个子类。父类中的方法实现不在父类,而是在各个子类。程序在运行时可以动态改变变量所指向的子类类型。 **针对接口编程,而不是针对实现编程** 变量声明的类型为父类,而不是具体的某个子类。父类中的方法实现不在父类,而是在各个子类。程序在运行时可以动态改变变量所指向的子类类型。
运用这一原则,将叫和飞行的行为抽象出来,实现多种不同的叫和飞行的子类,让子类去实现具体的叫和飞行方式。 运用这一原则,将叫和飞行的行为抽象出来,实现多种不同的叫和飞行的子类,让子类去实现具体的叫和飞行方式。
<div align="center"> <img src="../pics//1c8ccf5c-7ecd-4b8a-b160-3f72a510ce26.png"/> </div><br> <div align="center"> <img src="../pics//1c8ccf5c-7ecd-4b8a-b160-3f72a510ce26.png"/> </div><br>
**多用组合,少用继承** 组合也就是 has-a 关系,通过组合,可以在运行时动态改变实现,只要通过改变父类对象具体指向哪个子类即可。而继承就不能做到这些,继承体系在创建类时就已经确定。 **多用组合,少用继承** :组合也就是 HAS-A 关系,通过组合,可以在运行时动态改变实现,只要通过改变父类对象具体指向哪个子类即可。而继承就不能做到这些,继承体系在创建类时就已经确定。
运用这一原则,在 Duck 类中组合 FlyBehavior 和 QuackBehavior 类performQuack() 和 performFly() 方法委托给这两个类去处理。通过这种方式,一个 Duck 子类可以根据需要去实例化 FlyBehavior 和 QuackBehavior 的子类对象,并且也可以动态地进行改变。 运用这一原则,在 Duck 类中组合 FlyBehavior 和 QuackBehavior 类performQuack() 和 performFly() 方法委托给这两个类去处理。通过这种方式,一个 Duck 子类可以根据需要去初始化 FlyBehavior 和 QuackBehavior 的子类对象,并且也可以动态地进行改变。
<div align="center"> <img src="../pics//29574e6f-295c-444e-83c7-b162e8a73a83.jpg"/> </div><br> <div align="center"> <img src="../pics//29574e6f-295c-444e-83c7-b162e8a73a83.jpg"/> </div><br>
**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. 模式定义** ## 代码实现
**策略模式** :定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
**7. 实现代码**
```java ```java
public abstract class Duck { public abstract class Duck {
@ -104,8 +99,8 @@ public abstract class Duck {
} }
``` ```
```java ```java
public class MallarDuck extends Duck{ public class MallardDuck extends Duck{
public MallarDuck(){ public MallardDuck(){
flyBehavior = new FlyWithWings(); flyBehavior = new FlyWithWings();
quackBehavior = new Quack(); quackBehavior = new Quack();
} }
@ -164,11 +159,11 @@ public class Squeak implements QuackBehavior{
```java ```java
public class MiniDuckSimulator { public class MiniDuckSimulator {
public static void main(String[] args) { public static void main(String[] args) {
Duck mallarDuck = new MallarDuck(); Duck mallardDuck = new MallardDuck();
mallarDuck.performQuack(); mallardDuck.performQuack();
mallarDuck.performFly(); mallardDuck.performFly();
mallarDuck.setFlyBehavior(new FlyNoWay()); mallardDuck.setFlyBehavior(new FlyNoWay());
mallarDuck.performFly(); mallardDuck.performFly();
} }
} }
``` ```
@ -179,35 +174,35 @@ FlyBehavior.FlyWithWings
FlyBehavior.FlyNoWay FlyBehavior.FlyNoWay
``` ```
# 第二章 观察者模式 # 三、观察者模式
**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>
**2. 模式类图** ## 模式类图
主题中具有注册和移除观察者,并通知所有注册者的功能,主题是通过维护一张观察者列表来实现这些操作的。 主题具有注册和移除观察者、并通知所有注册者的功能,主题是通过维护一张观察者列表来实现这些操作的。
观察者拥有一个主题对象的引用,因为注册、移除还有数据都在主题当中,必须通过操作主题才能完成相应功能 观察者拥有一个主题对象的引用,因为注册、移除观察者功能,还有数据都在主题当中,必须通过操作主题才能完成相应操作
<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. 问题描述** ## 问题描述
天气数据布告板会在天气信息发生改变时更新其内容,布告板有多个,并且在将来会继续增加。 天气数据布告板会在天气信息发生改变时更新其内容,布告板有多个,并且在将来会继续增加。
**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. 设计原则** ## 设计原则
**为交互对象之间的松耦合设计而努力** 当两个对象之间松耦合,它们依然可以交互,但是不清楚彼此的细节。由于松耦合的两个对象之间互相依赖程度很低,因此系统具有弹性,能够应对变化。 为交互对象之间的松耦合设计而努力:当两个对象之间松耦合,它们依然可以交互,但是不清楚彼此的细节。由于松耦合的两个对象之间互相依赖程度很低,因此系统具有弹性,能够应对变化。
**6. 实现代码** ## 代码实现
```java ```java
public interface Subject { public interface Subject {
@ -217,8 +212,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,39 +306,39 @@ 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. 问题描述** ## 问题描述
设计不同种类的饮料,并且每种饮料可以动态添加新的材料,比如可以添加牛奶。计算一种饮料的价格。 设计不同种类的饮料,饮料可以添加配料,比如可以添加牛奶,并且支持动态添加新配料。每增加一种配料,该饮料的价格就会增加,要求计算一种饮料的价格。
**2. 模式定义** ## 模式定义
动态地将责任附加到对象上。在扩展功能上,装饰者提供了比继承更有弹性的替代方案。 动态地将责任附加到对象上。在扩展功能上,装饰者提供了比继承更有弹性的替代方案。
下图中 DarkRoast 对象被 Mocha 包裹Mocha 对象又被 Whip 包裹,并且他们都继承自相同父类,都有 cost() 方法,但是外层对象的 cost() 方法实现调用了内层对象的 cost() 方法。因此,如果要在 DarkRoast 上添加 Mocha那么只需要用 Mocha 包裹 DarkRoast如果还需要 Whip ,就用 Whip 包裹 Mocha最后调用 cost() 方法能把三种对象的价格都包含进去 下图表示在 DarkRoast 饮料上新增新添加 Mocha 配料,之后又添加了 Whip 配料。DarkRoast 被 Mocha 包裹Mocha 又被 Whip 包裹。它们都继承自相同父类,都有 cost() 方法,外层类的 cost() 方法调用了内层类的 cost() 方法
<div align="center"> <img src="../pics//41a4cb30-f393-4b3b-abe4-9941ccf8fa1f.jpg"/> </div><br> <div align="center"> <img src="../pics//41a4cb30-f393-4b3b-abe4-9941ccf8fa1f.jpg"/> </div><br>
**3. 模式类图** ## 模式类图
装饰者和具体组件都继承自组件类型,其中具体组件的方法实现不需要依赖于其它对象,而装饰者拥有一个组件类型对象,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰的对象之外,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件有直接实现而不需要委托给其它对象去处理 装饰者Decorator和具体组件ConcreteComponent都继承自组件Component具体组件的方法实现不需要依赖于其它对象而装饰者组合了一个组件,这样它可以装饰其它装饰者或者具体组件。所谓装饰,就是把这个装饰者套在被装饰,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件的方法实现不需要依赖于其它对象
<div align="center"> <img src="../pics//3dc454fb-efd4-4eb8-afde-785b2182caeb.jpg"/> </div><br> <div align="center"> <img src="../pics//3dc454fb-efd4-4eb8-afde-785b2182caeb.jpg"/> </div><br>
**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. 设计原则** ## 设计原则
**类应该对扩展开放,对修改关闭。** 也就是添加新功能时不需要修改代码。在本章问题中该原则体现在,在饮料中添加新的材料,而不需要去修改饮料的代码。观察则模式也符合这个原则。不可能所有类都能实现这个原则,应当把该原则应用于设计中最有可能改变的地方。 类应该对扩展开放,对修改关闭:也就是添加新功能时不需要修改代码。在本章问题中该原则体现在,饮料可以动态添加新的配料,而不需要去修改饮料的代码。观察则模式也符合这个原则。不可能把所有的类设计成都满足这一原则,应当把该原则应用于最有可能发生改变的地方。
**6. Java I/O 中的装饰者模式** ## 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. 代码实现** ## 代码实现
```java ```java
public interface Beverage { public interface Beverage {
@ -416,32 +409,32 @@ public class StartbuzzCoffee {
3.0 3.0
``` ```
# 第四章 工厂模式 # 五、简单工厂
## 1. 简单工厂 ## 问题描述
**1. 问题描述** Pizza 类有很多子类,要求根据不同的情况用不同的子类实例化一个 Pizza 对象。
有不同的 Pizza根据不同的情况用不同的子类实例化一个 Pizza 对象。 ## 模式定义
**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. 解决方案类图** ## 问题的解决方案类图
<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. 代码实现**
```java ```java
public interface Pizza { public interface Pizza {
public void make(); public void make();
} }
``` ```
```java ```java
public class CheesePizza implements Pizza{ public class CheesePizza implements Pizza{
@Override @Override
@ -450,6 +443,7 @@ public class CheesePizza implements Pizza{
} }
} }
``` ```
```java ```java
public class GreekPizza implements Pizza{ public class GreekPizza implements Pizza{
@Override @Override
@ -458,6 +452,7 @@ public class GreekPizza implements Pizza{
} }
} }
``` ```
```java ```java
public class SimplePizzaFactory { public class SimplePizzaFactory {
public Pizza createPizza(String type) { public Pizza createPizza(String type) {
@ -471,6 +466,7 @@ public class SimplePizzaFactory {
} }
} }
``` ```
```java ```java
public class PizzaStore { public class PizzaStore {
public static void main(String[] args) { public static void main(String[] args) {
@ -487,29 +483,37 @@ public class PizzaStore {
CheesePizza CheesePizza
``` ```
## 2. 工厂方法模式 # 六、工厂方法模式
**1. 问题描述** ## 问题描述
每个地区的 Pizza 店虽然种类相同,但是都有自己的风味,需要单独区分。例如,一个客户点了纽约的 cheese 种类的 Pizza 和在芝加哥点的相同种类的 Pizza 是不同的。 每个地区的 PizzaStore 卖的 Pizza 虽然种类相同,但是都有自己的风味。一个客户点了纽约的 cheese 种类的 Pizza 和在芝加哥点的相同种类的 Pizza 是不同的。要求设计出满足条件的 PizzaStore。
**2. 模式定义** ## 模式定义
定义了一个创建对象的接口,但由子类决定要实例化的类是个。工厂方法让类把实例化推迟到子类。 定义了一个创建对象的接口,但由子类决定要实例化哪个。工厂方法把实例化推迟到子类。
**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. 解决方案类图** ## 问题的解决方案类图
<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>
## 设计原则
依赖倒置原则:要依赖抽象,不要依赖具体类。听起来像是针对接口编程,不针对实现编程,但是这个原则说明了:不能让高层组件依赖底层组件,而且,不管高层或底层组件,两者都应该依赖于抽象。例如,下图中 Pizza 是抽象类PizzaStore 和 Pizza 子类都依赖于 Pizza 这个抽象类。
<div align="center"> <img src="../pics//ddf72ca9-c0be-49d7-ab81-57a99a974c8e.jpg"/> </div><br>
## 代码实现
```java ```java
public interface Pizza { public interface Pizza {
@ -605,29 +609,29 @@ NYStyleCheesePizza is making..
ChicagoStyleCheesePizza is making.. ChicagoStyleCheesePizza is making..
``` ```
## 3. 抽象工厂模式 # 七、抽象工厂模式
**1. 设计原则** ## 模式定义
**依赖倒置原则** :要依赖抽象,不要依赖具体类。听起来像是针对接口编程,不针对实现编程,但是这个原则说明了:不能让高层组件依赖底层组件,而且,不管高层或底层组件,两者都应该依赖于抽象。例如,下图中 PizzaStore 属于高层组件,它依赖的是 Pizza 的抽象类,这样就可以不用关心 Pizza 的具体实现细节 提供一个接口,用于创建 **相关的对象家族**
<div align="center"> <img src="../pics//ddf72ca9-c0be-49d7-ab81-57a99a974c8e.jpg"/> </div><br> ## 模式类图
**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. 解决方案类图** ## 解决方案类图
<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. 代码实现** ## 代码实现
```java ```java
public interface Dough { public interface Dough {
@ -736,23 +740,25 @@ ThickCrustDough
MarinaraSauce MarinaraSauce
``` ```
# 第五章 单件模式 # 八、单例模式
**1. 模式定义** ## 模式定义
确保一个类只有一个实例,并提供了一个全局访问点。 确保一个类只有一个实例,并提供了一个全局访问点。
**2. 模式类图** ## 模式类图
使用一个私有构造器、一个私有静态变量以及一个公有静态函数来实现。私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。 使用一个私有构造器、一个私有静态变量以及一个公有静态函数来实现。
<div align="center"> <img src="../pics//59aff6c1-8bc5-48e4-9e9c-082baeb2f274.jpg"/> </div><br> 私有构造函数保证了不能通过构造函数来创建对象实例,只能通过公有静态函数返回唯一的私有静态变量。
**3. 懒汉式-线程不安全** <div align="center"> <img src="../pics//da5dbeae-f247-400b-84d8-af48f0241bc9.png"/> </div><br>
以下实现中,私有静态变量被延迟化实例化,这样做的好处是,如果没有用到该类,那么就不会创建私有静态变量,从而节约资源。 ## 懒汉式-线程不安全
这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 if(uniqueInstance == null) 内的语句块,那么就会多次实例化 uniqueInstance 私有静态变量。 以下实现中,私有静态变量 uniqueInstance 被延迟化实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance从而节约资源。
这个实现在多线程环境下是不安全的,如果多个线程能够同时进入`if(uniqueInstance == null)` ,那么就会多次实例化 uniqueInstance。
```java ```java
public class Singleton { public class Singleton {
@ -771,33 +777,34 @@ public class Singleton {
} }
``` ```
**4. 懒汉式-线程安全** ## 懒汉式-线程安全
只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了对 uniqueInstance 进行多次实例化的问题。但是这样有一个问题,就是当一个线程进入该方法之后,其它线程视图进入该方法都必须等待,因此性能上有一定的损耗。 只需要对 `getUniqueInstance()` 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了对 uniqueInstance 进行多次实例化的问题。
但是这样有一个问题,就是当一个线程进入该方法之后,其它线程试图进入该方法都必须等待,因此性能上有一定的损耗。
```java ```java
public static synchronized Singleton getUniqueInstance() { public static synchronized Singleton getUniqueInstance() {
if (uniqueInstance == null) { if (uniqueInstance == null) {
uniqueInstance = new Singleton(); uniqueInstance = new Singleton();
} }
return uniqueInstance; return uniqueInstance;
} }
``` ```
**5. 饿汉式-线程安全** ## 饿汉式-线程安全
线程不安全问题主要是由于静态实例变量被初始化了多次,那么静态实例变量采用直接实例化就可以解决问题。但是直接初始化的方法也丢失了延迟初始化节约资源的优势。 线程不安全问题主要是由于 uniqueInstance 被实例化了多次,如果 uniqueInstance 采用直接实例化的话,就不会被实例化多次,也就不会产生线程不安全问题。但是直接实例化的方式也丢失了延迟实例化带来的节约资源的优势。
```java ```java
private static Singleton uniqueInstance = new Singleton(); private static Singleton uniqueInstance = new Singleton();
``` ```
**6. 双重校验锁-线程安全** ## 双重校验锁-线程安全
因为 uniqueInstance 只需要被初始化一次,之后就可以直接使用了。加锁操作只需要对初始化那部分的代码进行,也就是说,只有当 uniqueInstance 没有被初始化时,才需要进行加锁。 uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行。也就是说,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。
双重校验锁先判断 uniqueInstance 是否已经被初始化了,如果没有被初始化,那么才对初始化的语句进行加锁。如果只做一次判断,那么多个线程还是有可能同时进入实例化语句块的,因此需要仅此第二次的判断。
双重校验锁先判断 uniqueInstance 是否已经被初始化了,如果没有被实例化,那么才对实例化语句进行加锁。
```java ```java
public class Singleton { public class Singleton {
@ -820,39 +827,49 @@ public class Singleton {
} }
``` ```
# 第六章 命令模式 考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程同时执行 if 语句,那么两个线程就会同时进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行`uniqueInstance = new Singleton();`这条语句,只是早晚的问题,也就是说会进行两次实例化,从而产生了两个实例。因此必须使用双重校验锁,也就是需要使用两个 if 判断。
**1. 问题描述** ```java
if (uniqueInstance == null) {
synchronized (Singleton.class) {
uniqueInstance = new Singleton();
}
}
```
设计一个遥控器,它有很多按钮,每个按钮可以发起一个命令,让一个家电完成相应操作。 # 九、命令模式
## 问题描述
设计一个遥控器,它有很多按钮,每个按钮可以发起一个命令,命令会让一个家电完成相应操作。有非常多的家电,并且之后会增加家电。
<div align="center"> <img src="../pics//f6be22cb-d64f-4ee5-87b7-cbc4e6255c0e.jpg" width="700"/> </div><br>
有非常多的家电,并且之后会增加家电。 有非常多的家电,并且之后会增加家电。
<div align="center"> <img src="../pics//7b8f0d8e-a4fa-4c9d-b9a0-3e6a11cb3e33.jpg"/> </div><br> <div align="center"> <img src="../pics//5b832bde-d05e-42db-b648-42e274571ad9.jpg" width="700"/> </div><br>
<div align="center"> <img src="../pics//c3ca36b2-8459-4cf1-98b0-cc95a0e94f20.jpg"/> </div><br> ## 模式定义
**2. 模式定义**
将命令封装成对象,以便使用不同的命令来参数化其它对象。 将命令封装成对象,以便使用不同的命令来参数化其它对象。
**3. 解决方案类图** ## 问题的解决方案类图
- RemoteControl 是遥控器,它可以为每个按钮设置命令对象,并且调用命令对象的 execute() 方法 - RemoteControl 是遥控器,它可以为每个按钮设置命令对象,并且执行命令
- Command 是命令对象,命令模式正式将各种命令封装成 Commad 对象来实现的 - Command 是命令对象。
- Light 是命令真正的执行者。可以注意到 LightOnCommand 和 LightOffCommand 类组合了一个 Light 对象,通过组合的方法,就可以将 excute() 方法委托给 Light 对象来执行 - Light(电灯)是命令真正的执行者
- 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" width="1000"/> </div><br>
**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" width="1000"/> </div><br>
**5. 代码实现** ## 代码实现
```java ```java
public interface Command { public interface Command {
@ -931,35 +948,31 @@ public class RemoteLoader {
Light is on! Light is on!
``` ```
# 第七章 适配器模式与外观模式 # 十、适配器模式
## 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" width="800"/> </div><br>
**2. 模式类图** ## 模式类图
有两种适配器模式的实现一种是对象方式一种是类方式。对象方式是通过组合的方法让适配器类Adapter拥有一个待适配的对象Adaptee从而把相应的处理委托给待适配的对象。类方式用到多重继承Adapter 继承 Target 和 Adaptee先把 Adapter 当成 Adaptee 类型然后实例化一个对象,再把它当成 Target 类型的,这样 Client 就可以把这个对象当成 Target 的对象来处理,同时拥有 Adaptee 的方法 适配器Adapter组合一个适配者AdapteeAdapter 把操作委托给 Adaptee
<div align="center"> <img src="../pics//253bd869-ea48-4092-9aed-6906ccb2f3b0.jpg"/> </div><br> <div align="center"> <img src="../pics//253bd869-ea48-4092-9aed-6906ccb2f3b0.jpg"/> </div><br>
<div align="center"> <img src="../pics//a797959a-0ed5-475b-8d97-df157c672019.jpg"/> </div><br> ## 问题描述
**3. 问题描述** 鸭子Duck和火鸡Turkey拥有不同的叫声Duck 的叫声调用 quack() 方法,而 Turkey 调用 gobble() 方法。
鸭子Duck和火鸡Turkey拥有不同的叫声Duck 调用的是 quack() 方法,而 Turkey 调用 gobble() 方法。 要求将 Turkey 的 gobble() 方法适配成 Duck 的 quack() 方法,从而让火鸡冒充鸭子!
要求将 Turkey 的 gobble() 方法适配成 Duck 的 quack() 方法。 ## 问题的解决方案类图
**4. 解决方案类图** <div align="center"> <img src="../pics//b8ceb9db-180e-4d01-932c-593fa2a6f515.jpg"/> </div><br>
<div align="center"> <img src="../pics//1a511c76-bb6b-40ab-b8aa-39eeb619d673.jpg"/> </div><br> ## 代码实现
**5. 代码实现**
```java ```java
public interface Duck { public interface Duck {
@ -1013,69 +1026,69 @@ public class DuckTestDrive {
gobble! gobble!
``` ```
## 2. 外观模式 # 十、外观模式
**1. 模式定义** ## 模式定义
提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用。 提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用。
**2. 模式类图** ## 模式类图
<div align="center"> <img src="../pics//78f2314e-2643-41df-8f3d-b7e28294094b.jpg"/> </div><br> <div align="center"> <img src="../pics//78f2314e-2643-41df-8f3d-b7e28294094b.jpg" width="1000"/> </div><br>
**3. 问题描述** ## 问题描述
家庭影院中有众多电器,当要进行观看电影时需要对很多电器进行操作。要求简化这些操作,使得家庭影院类只提供一个简化的接口,例如提供一个看电影相关的接口。 家庭影院中有众多电器,当要进行观看电影时需要对很多电器进行操作。要求简化这些操作,使得家庭影院类只提供一个简化的接口,例如提供一个看电影相关的接口。
<div align="center"> <img src="../pics//106f5585-b2e7-4718-be5d-3b322d1ef42a.jpg"/> </div><br> <div align="center"> <img src="../pics//106f5585-b2e7-4718-be5d-3b322d1ef42a.jpg" width="500"/> </div><br>
**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. 设计原则** ## 设计原则
**最少知识原则** :只和你的密友谈话。也就是应当使得客户对象所需要交互的对象尽可能少。 **最少知识原则** :只和你的密友谈话。也就是客户对象所需要交互的对象应当尽可能少。
**6. 代码实现** ## 代码实现
过于简单,无实现。 过于简单,无实现。
# 第八章 模板方法模式 # 十一、模板方法模式
**1. 模式定义** ## 模式定义
在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。 在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。
模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。 模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
**2. 模式类图** ## 模式类图
模板方法 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. 问题描述** ## 问题描述
冲咖啡和冲茶都有类似的流程,但是某些步骤会有点不一样,要求复用那些相同步骤的代码。 冲咖啡和冲茶都有类似的流程,但是某些步骤会有点不一样,要求复用那些相同步骤的代码。
<div align="center"> <img src="../pics//d8f873fc-00bc-41ee-a87c-c1b4c0172844.png"/> </div><br> <div align="center"> <img src="../pics//d8f873fc-00bc-41ee-a87c-c1b4c0172844.png"/> </div><br>
**4. 解决方案类图** ## 问题的解决方案类图
其中 prepareRecipe() 方法就是模板方法,它确定了其它四个方法的具体执行步骤。其中 brew() 和 addCondiments() 方法在子类中实现。 prepareRecipe() 方法就是模板方法,它确定了其它四个方法的具体执行步骤。其中 brew() 和 addCondiments() 方法在子类中实现。
<div align="center"> <img src="../pics//aa20c123-b6b5-432a-83d3-45dc39172192.jpg"/> </div><br> <div align="center"> <img src="../pics//aa20c123-b6b5-432a-83d3-45dc39172192.jpg"/> </div><br>
**5. 设计原则** ## 设计原则
**好莱坞原则** :别调用(打电话给)我们,我们会调用(打电话给)你。这一原则可以防止依赖腐败,即防止高层组件依赖低层组件,低层组件又依赖高层组件。该原则在模板方法的体现为,只有父类会调用子类,子类不会调用父类。 **好莱坞原则** :别调用(打电话给)我们,我们会调用(打电话给)你。这一原则可以防止依赖腐败,即防止高层组件依赖低层组件,低层组件又依赖高层组件。该原则在模板方法的体现为,只有父类会调用子类,子类不会调用父类。
**6. 钩子** ## 钩子
钩子hock某些步骤在不同实现中可有可无,可以先定义一个什么都不做的方法,把它加到模板方法中,如果子类需要它就覆盖默认实现并加上自己的实现。 某些步骤在不同实现中可有可无,可以先定义一个什么都不做的方法,把它加到模板方法中,如果子类需要它就覆盖默认实现并加上自己的实现。
**7. 代码实现** ## 代码实现
```java ```java
public abstract class CaffeineBeverage { public abstract class CaffeineBeverage {
@ -1155,25 +1168,23 @@ pourInCup
Tea.addCondiments Tea.addCondiments
``` ```
# 第九章 迭代器和组合模式 # 十二、迭代器模式
## 1. 迭代器模式 ## 模式定义
**1. 模式定义**
提供顺序访问一个聚合对象中的各个元素的方法,而又不暴露聚合对象内部的表示。 提供顺序访问一个聚合对象中的各个元素的方法,而又不暴露聚合对象内部的表示。
**2. 模式类图** ## 模式类图
- Aggregate 是聚合类,其中 createIterator() 方法可以产生一个 Iterator 对象 - Aggregate 是聚合类,其中 createIterator() 方法可以产生一个 Iterator
- Iterator 主要定义了 hasNext() 和 next() 方法。 - Iterator 主要定义了 hasNext() 和 next() 方法。
- Client 需要拥有一个 Aggregate 对象,这是很明显的。为了迭代变量 Aggregate 对象,也需要拥有 Iterator 对象 - Client 组合了 Aggregate为了迭代遍历 Aggregate也需要组合 Iterator
<div align="center"> <img src="../pics//439deca7-fed0-4c89-87e5-7088d10f1fdb.jpg"/> </div><br> <div align="center"> <img src="../pics//439deca7-fed0-4c89-87e5-7088d10f1fdb.jpg"/> </div><br>
**3. 代码实现** ## 代码实现
```java ```java
public class Aggregate { public class Aggregate {
@ -1247,18 +1258,14 @@ public class Client {
9 9
``` ```
## 2. Java 内置的迭代器 ## Java 内置的迭代器
**1. 实现接口** 需要让聚合类实现 Iterable 接口,该接口有一个 iterator() 方法会返回一个 Iterator 对象。
在使用 Java 的迭代器实现时,需要让聚合对象去实现 Iterable 接口,该接口有一个 iterator() 方法会返回一个 Iterator 对象。 可以使用 foreach 循环来顺序访问聚合对象中的每个元素。
使用 Java 内置的迭代器实现,客户对象可以使用 foreach 循环来遍历聚合对象中的每个元素。
Java 中的集合类基本都实现了 Iterable 接口。 Java 中的集合类基本都实现了 Iterable 接口。
**2. 代码实现**
```java ```java
import java.util.Iterator; import java.util.Iterator;
@ -1313,27 +1320,27 @@ public class Client {
} }
``` ```
## 3. 组合模式 # 十三、组合模式
**1. 设计原则** ## 设计原则
一个类应该只有一个引起改变的原因。 一个类应该只有一个引起改变的原因。
**2. 模式定义** ## 模式定义
允许将对象组合成树形结构来表现“整体/部分”层次结构 允许将对象组合成树形结构来表现“整体/部分”关系
组合能让客户以一致的方式处理个别对象以及组合对象。 组合能让客户以一致的方式处理个别对象以及组合对象。
**3. 模式类图** ## 模式类图
组件Component类是组合类Composite和叶子类Leaf的父类可以把组合类看成是树的中间节点。 组件Component类是组合类Composite和叶子类Leaf的父类可以把组合类看成是树的中间节点。
组合类拥有一个组件对象,因此组合类的操作可以委托给组件对象去处理,而组件对象可以是另一个组合类或者叶子类 组合对象拥有一个组件对象,因此组合对象的操作可以委托给组件对象去处理,而组件对象可以是另一个组合对象或者叶子对象
<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. 代码实现** ## 代码实现
```java ```java
public abstract class Component { public abstract class Component {
@ -1434,35 +1441,35 @@ Composite:root
--left:3 --left:3
``` ```
# 第十章 状态模式 # 十四、状态模式
**1. 模式定义** ## 模式定义
允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它所属的类。 允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它所属的类。
**2. 模式类图** ## 模式类图
Context 的 request() 方法委托给 State 对象去处理。当 Context 组合的 State 对象发生改变时,它的行为也就发生了改变。 Context 的 request() 方法委托给 State 对象去处理。当 Context 组合的 State 对象发生改变时,它的行为也就发生了改变。
<div align="center"> <img src="../pics//c28fd93a-0d55-4a19-810f-72652feee00d.jpg"/> </div><br> <div align="center"> <img src="../pics//c28fd93a-0d55-4a19-810f-72652feee00d.jpg"/> </div><br>
**3. 与策略模式的比较** ## 与策略模式的比较
状态模式的类图和策略模式一样,并且都是能够动态改变对象的行为。 状态模式的类图和策略模式一样,并且都是能够动态改变对象的行为。
但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。 但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。
所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,主要必须要是在运行过程中。 所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。
状态模式主要是用来解决状态转移的问题,当状态发生庄毅了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 需要使用哪个算法。 状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 需要使用哪个算法。
**4. 问题描述** ## 问题描述
糖果销售机有多种状态,每种状态下销售机有不同的行为,状态可以发生转移,使得销售机的行为也发生改变。 糖果销售机有多种状态,每种状态下销售机有不同的行为,状态可以发生转移,使得销售机的行为也发生改变。
<div align="center"> <img src="../pics//f7d880c9-740a-4a16-ac6d-be502281b4b2.jpg"/> </div><br> <div align="center"> <img src="../pics//f7d880c9-740a-4a16-ac6d-be502281b4b2.jpg"/> </div><br>
**5. 直接解决方案** ## 直接解决方案
在糖果机的每个操作函数里面,判断当前的状态,根据不同的状态进行不同的处理,并且发生不同的状态转移。 在糖果机的每个操作函数里面,判断当前的状态,根据不同的状态进行不同的处理,并且发生不同的状态转移。
@ -1470,21 +1477,21 @@ Context 的 request() 方法委托给 State 对象去处理。当 Context 组合
<div align="center"> <img src="../pics//62ebbb63-8fd7-4488-a866-76a9dc911662.png"/> </div><br> <div align="center"> <img src="../pics//62ebbb63-8fd7-4488-a866-76a9dc911662.png"/> </div><br>
**6 代码实现** ## 代码实现
糖果销售机即 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,34 +1763,36 @@ You turned, but there are no gumballs
No gumball dispensed No gumball dispensed
``` ```
# 第十一章 代理模式 # 十五、代理模式
# 第十二章 复合模式 # 十六、MVC
## MVC ## 传统 MVC
**传统 MVC**
视图使用组合模式,模型使用了观察者模式,控制器使用了策略模式。 视图使用组合模式,模型使用了观察者模式,控制器使用了策略模式。
<div align="center"> <img src="../pics//4f67611d-492f-4958-9fa0-4948010e345f.jpg"/> </div><br> <div align="center"> <img src="../pics//4f67611d-492f-4958-9fa0-4948010e345f.jpg"/> </div><br>
**Web 中的 MVC** ## Web 中的 MVC
模式不再使用观察者模式。 模式不再使用观察者模式。
<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

@ -1,62 +1,76 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [S.O.L.I.D](#solid) * [第一章 设计原则](#第一章-设计原则)
* [1. 单一责任原则](#1-单一责任原则) * [S.O.L.I.D](#solid)
* [2. 开放封闭原则](#2-开放封闭原则) * [其他常见原则](#其他常见原则)
* [3. 里氏替换原则](#3-里氏替换原则) * [第二章 三大特性](#第二章-三大特性)
* [4. 接口分离原则](#4-接口分离原则) * [封装](#封装)
* [5. 依赖倒置原则](#5-依赖倒置原则) * [继承](#继承)
* [其他常见原则](#其他常见原则) * [多态](#多态)
* [1. 迪米特法则](#1-迪米特法则) * [第三章 UML](#第三章-uml)
* [2. 合成复用原则](#2-合成复用原则) * [类图](#类图)
* [3. 共同封闭原则](#3-共同封闭原则) * [时序图](#时序图)
* [4. 稳定抽象原则](#4-稳定抽象原则)
* [5. 稳定依赖原则](#5-稳定依赖原则)
* [封装、继承、多态](#封装继承多态)
* [1. 封装](#1-封装)
* [2. 继承](#2-继承)
* [3. 多态](#3-多态)
* [UML](#uml)
* [1. 类图](#1-类图)
* [2. 时序图](#2-时序图)
* [参考资料](#参考资料) * [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
# S.O.L.I.D # 第一章 设计原则
S.O.L.I.D 是面向对象设计和编程 (OOD&OOP) 中几个重要编码原则 (Programming Priciple) 的首字母缩写。 设计原则可以帮助我们避免那些糟糕的设计,这些原则被归纳在《敏捷软件开发:原则、模式与实践》这本书中。
## S.O.L.I.D
| 简写 | 全拼 | 中文翻译 | | 简写 | 全拼 | 中文翻译 |
| -- | -- | -- | | -- | -- | -- |
|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. 里氏替换原则 **类应该对扩展开放,对修改关闭。**
当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有 is-a 关系 扩展就是添加新功能的意思,因此该原则要求在添加新功能时不需要修改代码
## 4. 接口分离原则 符合开闭原则最典型的设计模式是装饰者模式,它可以动态地将责任附加到对象上,而不用去修改类的代码。
不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。 ### 3. 里氏替换原则
## 5. 依赖倒置原则 **子类对象必须能够替换掉所有父类对象。**
1. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象 继承是一种 IS-A 关系,子类需要能够当成父类来使用,并且需要比父类更特殊。
2. 抽象不应该依赖于细节,细节应该依赖于抽象
# 其他常见原则 如果不满足这个原则,那么各个子类的行为上就会有很大差异,增加继承体系的复杂度。
### 4. 接口分离原则
**不应该强迫客户依赖于它们不用的方法。**
因此使用多个专门的接口比使用单一的总接口总要好。
### 5. 依赖倒置原则
- **高层模块不应该依赖于低层模块,二者都应该依赖于抽象**
- **抽象不应该依赖于细节,细节应该依赖于抽象**
高层模块包含一个应用程序中重要的策略选择和业务模块,如果高层模块依赖于底层模块,那么底层模块的改动就会直接影响到高层模块,从而迫使高层模块也需要改动。
依赖于抽象意味着:
- 任何变量都不应该持有一个指向具体类的指针或者引用;
- 任何类都不应该从具体类派生;
- 任何方法都不应该覆写它的任何基类中的已经实现的方法。
## 其他常见原则
除了上述的经典原则,在实际开发中还有下面这些常见的设计原则。 除了上述的经典原则,在实际开发中还有下面这些常见的设计原则。
@ -68,44 +82,41 @@ S.O.L.I.D 是面向对象设计和编程 (OOD&OOP) 中几个重要编码原则 (
|SAP| The Stable Abstractions Principle | 稳定抽象原则 | |SAP| The Stable Abstractions Principle | 稳定抽象原则 |
|SDP| The Stable Dependencies Principle | 稳定依赖原则 | |SDP| The Stable Dependencies Principle | 稳定依赖原则 |
## 1. 迪米特法则 ### 1. 迪米特法则
迪米特法则又叫作最少知道原则Least Knowledge Principle 简写LKP就是说一个对象应当对其他对象有尽可能少的了解不和陌生人说话。 迪米特法则又叫作最少知道原则Least Knowledge Principle 简写LKP就是说一个对象应当对其他对象有尽可能少的了解不和陌生人说话。
## 2. 合成复用原则 ### 2. 合成复用原则
尽量使用对象组合,而不是继承来达到复用的目的。 尽量使用对象组合,而不是继承来达到复用的目的。
## 3. 共同封闭原则 ### 3. 共同封闭原则
一起修改的类,应该组合在一起(同一个包里)。如果必须修改应用程序里的代码,我们希望所有的修改都发生在一个包里(修改关闭),而不是遍布在很多包里。 一起修改的类,应该组合在一起(同一个包里)。如果必须修改应用程序里的代码,我们希望所有的修改都发生在一个包里(修改关闭),而不是遍布在很多包里。
## 4. 稳定抽象原则 ### 4. 稳定抽象原则
最稳定的包应该是最抽象的包,不稳定的包应该是具体的包,即包的抽象程度跟它的稳定性成正比。 最稳定的包应该是最抽象的包,不稳定的包应该是具体的包,即包的抽象程度跟它的稳定性成正比。
## 5. 稳定依赖原则 ### 5. 稳定依赖原则
包之间的依赖关系都应该是稳定方向依赖的,包要依赖的包要比自己更具有稳定性。 包之间的依赖关系都应该是稳定方向依赖的,包要依赖的包要比自己更具有稳定性。
# 封装、继承、多态 # 第二章 三大特性
封装、继承、多态是面向对象的三大特性。 ## 封装
## 1. 封装 利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户无需知道对象内部的细节,但可以通过对象对外提供的接口来访问该对象。
利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户是无需知道对象内部的细节,但可以通过该对象对外的提供的接口来访问该对象。
封装有三大好处: 封装有三大好处:
1. 良好的封装能够减少耦合。 1. 减少耦合
2. 类内部的结构可以自由修改。 2. 隐藏内部细节,因此内部结构可以自由修改
3. 可以对成员进行更精确的控制。 3. 可以对成员进行更精确的控制
4. 隐藏信息,实现细节。
以下 Person 类封装 name、gender、age 等属性,外界只能通过 get() 方法获取一个 Person 对象的 name 属性和 gender 属性,而无法获取 age 属性,但是 age 属性可以供 work() 方法使用。 以下 Person 类封装 name、gender、age 等属性,外界只能通过 get() 方法获取一个 Person 对象的 name 属性和 gender 属性,而无法获取 age 属性,但是 age 属性可以供 work() 方法使用。
注意到 gender 属性使用 int 数据类型进行存储,封装使得用户注意不到这种实现细节。并且在需要修改使用的数据类型时,也可以在不影响客户端代码的情况下进行。 注意到 gender 属性使用 int 数据类型进行存储,封装使得用户注意不到这种实现细节。并且在需要修改 gender 属性使用的数据类型时,也可以在不影响客户端代码的情况下进行。
```java ```java
public class Person { public class Person {
@ -125,31 +136,35 @@ public class Person {
if(18 <= age && age <= 50) { if(18 <= age && age <= 50) {
System.out.println(name + " is working very hard!"); System.out.println(name + " is working very hard!");
} else { } else {
System.out.println(name + " can't work!"); System.out.println(name + " can't work any more!");
} }
} }
} }
``` ```
## 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 对象,这种子类转换为父类称为 **向上转型** Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat 对象。父类引用指向子类对象称为 **向上转型**
继承应该遵循里氏替换原则:当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有 is-a 关系。
```java ```java
Animal animal = new Cat(); 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 ```java
public class Instrument { public class Instrument {
@ -180,68 +195,63 @@ public class Music {
} }
} }
} }
``` ```
# 第三章 UML
## 类图
# UML ### 1. 继承相关
## 1. 类图 继承有两种形式 : 泛化Generalize和实现Realize表现为 IS-A 关系。
**1.1 继承相关** #### 泛化关系 (Generalize)
继承有两种形式 : 泛化generalize和实现realize表现为 is-a 关系。 从具体类中继承。
① 泛化关系 (generalization)
从具体类中继承
<div align="center"> <img src="../pics//29badd92-109f-4f29-abb9-9857f5973928.png"/> </div><br> <div align="center"> <img src="../pics//29badd92-109f-4f29-abb9-9857f5973928.png"/> </div><br>
② 实现关系 (realize) #### 实现关系 (Realize)
从抽象类或者接口中继承 从抽象类或者接口中继承
<div align="center"> <img src="../pics//4b16e1d3-3a60-472c-9756-2f31b1c48abe.png"/> </div><br> <div align="center"> <img src="../pics//4b16e1d3-3a60-472c-9756-2f31b1c48abe.png"/> </div><br>
**1.2 整体和部分** ### 2. 整体和部分
① 聚合关系 (aggregation) #### 聚合关系 (Aggregation)
表示整体由部分组成,但是整体和部分不是强依赖的,整体不存在了部分还是会存在。以下表示 B 由 A 组成: 表示整体由部分组成,但是整体和部分不是强依赖的,整体不存在了部分还是会存在。以下表示 B 由 A 组成:
<div align="center"> <img src="../pics//34259bb8-ca3a-4872-8771-9e946782d9c3.png"/> </div><br> <div align="center"> <img src="../pics//34259bb8-ca3a-4872-8771-9e946782d9c3.png"/> </div><br>
② 组合关系 (composition) #### 组合关系 (Composition)
和聚合不同,组合中整体和部分是强依赖的,整体不存在了部分也不存在了。比如公司和部门,公司没了部门就不存在了。但是公司和员工就属于聚合关系了,因为公司没了员工还在。 和聚合不同,组合中整体和部分是强依赖的,整体不存在了部分也不存在了。比如公司和部门,公司没了部门就不存在了。但是公司和员工就属于聚合关系了,因为公司没了员工还在。
<div align="center"> <img src="../pics//7dda050d-ac35-4f47-9f51-18f18ed6fa9a.png"/> </div><br> <div align="center"> <img src="../pics//7dda050d-ac35-4f47-9f51-18f18ed6fa9a.png"/> </div><br>
**1.3 相互联系** ### 3. 相互联系
① 关联关系 (association) #### 关联关系 (Association)
表示不同类对象之间有关联,这是一种静态关系,与运行过程的状态无关,在最开始就可以确定。因此也可以用 1 对 1、多对 1、多对多这种关联关系来表示。比如学生和学校就是一种关联关系一个学校可以有很多学生但是一个学生只属于一个学校因此这是一种多对一的关系在运行开始之前就可以确定。 表示不同类对象之间有关联,这是一种静态关系,与运行过程的状态无关,在最开始就可以确定。因此也可以用 1 对 1、多对 1、多对多这种关联关系来表示。比如学生和学校就是一种关联关系一个学校可以有很多学生但是一个学生只属于一个学校因此这是一种多对一的关系在运行开始之前就可以确定。
<div align="center"> <img src="../pics//4ccd294c-d6b2-421b-839e-d88336ff5fb7.png"/> </div><br> <div align="center"> <img src="../pics//4ccd294c-d6b2-421b-839e-d88336ff5fb7.png"/> </div><br>
② 依赖关系 (dependency) #### 依赖关系 (Dependency)
和关联关系不同的是 , 依赖关系是在运行过程中起作用的。一般依赖作为类的构造器或者方法的参数传入。双向依赖时一种不好的设计。 和关联关系不同的是 , 依赖关系是在运行过程中起作用的。一般依赖作为类的构造器或者方法的参数传入。双向依赖时一种不好的设计。
<div align="center"> <img src="../pics//47ca2614-509f-476e-98fc-50ec9f9d43c0.png"/> </div><br> <div align="center"> <img src="../pics//47ca2614-509f-476e-98fc-50ec9f9d43c0.png"/> </div><br>
## 2. 时序图 ## 时序图
http://www.cnblogs.com/wolf-sun/p/UML-Sequence-diagram.html ### 1. 定义
**2.1 定义**
时序图描述了对象之间传递消息的时间顺序,它用来表示用例的行为顺序。它的主要作用是通过对象间的交互来描述用例(注意是对象),从而寻找类的操作。 时序图描述了对象之间传递消息的时间顺序,它用来表示用例的行为顺序。它的主要作用是通过对象间的交互来描述用例(注意是对象),从而寻找类的操作。
**2.2 赤壁之战时序图** ### 2. 赤壁之战时序图
从虚线从上往下表示时间的推进。 从虚线从上往下表示时间的推进。
@ -273,19 +283,19 @@ public class 孙权 {
} }
``` ```
**2.3 活动图、时序图之间的关系** ### 3. 活动图、时序图之间的关系
活动图示从用户的角度来描述用例; 活动图示从用户的角度来描述用例;
时序图是从计算机的角度(对象间的交互)描述用例。 时序图是从计算机的角度(对象间的交互)描述用例。
**2.4 类图与时序图的关系** ### 4. 类图与时序图的关系
类图描述系统的静态结构,时序图描述系统的动态行为。 类图描述系统的静态结构,时序图描述系统的动态行为。
**2.5 时序图的组成** ### 5. 时序图的组成
对象 #### 对象
有三种表现形式 有三种表现形式
@ -297,13 +307,13 @@ public class 孙权 {
2. 把初始化整个交互活动的对象(有时是一个参与者)放置在最左边。 2. 把初始化整个交互活动的对象(有时是一个参与者)放置在最左边。
生命线 #### 生命线
生命线从对象的创建开始到对象销毁时终止 生命线从对象的创建开始到对象销毁时终止
<div align="center"> <img src="../pics//b7b0eac6-e7ea-4fb6-8bfb-95fec6f235e2.png"/> </div><br> <div align="center"> <img src="../pics//b7b0eac6-e7ea-4fb6-8bfb-95fec6f235e2.png"/> </div><br>
消息 #### 消息
对象之间的交互式通过发送消息来实现的。 对象之间的交互式通过发送消息来实现的。
@ -323,17 +333,17 @@ public class 孙权 {
4\. 返回消息,可选。 4\. 返回消息,可选。
激活 #### 激活
生命线上的方框表示激活状态,其它时间处于休眠状态。 生命线上的方框表示激活状态,其它时间处于休眠状态。
<div align="center"> <img src="../pics//6ab5de9b-1c1e-4118-b2c3-fb6c7ed7de6f.png"/> </div><br> <div align="center"> <img src="../pics//6ab5de9b-1c1e-4118-b2c3-fb6c7ed7de6f.png"/> </div><br>
# 参考资料 # 参考资料
- Java 编程思想 - 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) - [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)

45
other/download.md Normal file
View File

@ -0,0 +1,45 @@
# 网络
<a href="https://pan.baidu.com/s/1EXaJbNckzuQMOCyamzjL_Q"> <img src="s3296854.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1oBbA9LOevcJ_reg8y5kOvw"> <img src="s28845534.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1M0AHXqG9sP9Bxne6u0JK8A"> <img src="s27283822.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1y0P-VFlWKdOPW7YB60OWlw"> <img src="s26676928.jpg" width="130"/> </a>
# 操作系统
<a href="https://pan.baidu.com/s/1C-MgvslLKd1buwmebti6Qg"> <img src="s1650904.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1Qm2G4rghPorQeH5J9fDHTg"> <img src="s4399937.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1OoyVI90fK1Q9eixzH9jnpQ"> <img src="s4510534.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/12mTkrpLsb7tz11cGn_KZ4w"> <img src="s3895413.jpg" width="130"/> </a>
# 算法
<a href="https://pan.baidu.com/s/1Va1R66d13ynmita8nfkRPg"> <img src="s28322244.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1HmGwXvTcHDrQnUAL1wWE3g"> <img src="s7038106.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1SZGUbvKpKOomM-iYxe_GGw"> <img src="s2992671.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/10EoXyW33MnYJUX5YeD5pPg"> <img src="s28313721.jpg" width="130"/> </a>
# 设计模式
<a href="https://pan.baidu.com/s/1JOO4M3c6EGB5xHz_-aGtDQ"> <img src="s2686916.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1n41aEgGuRg9hQ-9iwOxc5A"> <img src="s1074361.jpg" width="130"/> </a>
# 数据库
<a href="https://pan.baidu.com/s/1xhYsZUi2fugLf9jxSWA0pQ"> <img src="s2359163.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1aXRWznphuiEc4XRXpM1qLA"> <img src="s4141593.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/182JK19-rvbISYAv4aLk7xg"> <img src="s5968156.jpg" width="130"/> </a>
# Redis
<a href="https://pan.baidu.com/s/1XovYaApdsVsd97pLCwAvpA"> <img src="s27297117.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1bfbiPjoBEaNUs6qLWVEIJw"> <img src="s28296984.jpg" width="130"/> </a>
# Java
<a href="https://pan.baidu.com/s/1iNBkY9ANUcmeSp4VjBGhRQ"> <img src="s27243455.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1zdATX8Qs-RMk6DN7iqECYw"> <img src="s27458236.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1LkPVPrT_3BYFkfxieBkeVw"> <img src="s7663093.jpg" width="130"/> </a>
# C++
<a href="https://pan.baidu.com/s/1VhhqN7oVcrv0KhF32CXRLQ"> <img src="s27023182.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1u3-QrdnkHo5ScUK84v7C5w"> <img src="s2996168.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1K6xm3YlV53trCxyGR0j_gQ"> <img src="s4436543.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/10iFqDOHSveJC3VC7dl1vMw"> <img src="s1613283.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1o-hgLJ4XvXAHeFhWAuuiFQ"> <img src="s4647091.jpg" width="130"/> </a>
# 工具
<a href="https://pan.baidu.com/s/1zYoS3lB1yCCT-So1YeoRuA"> <img src="s4245786.jpg" width="130"/> </a> &nbsp;&nbsp; <a href="https://pan.baidu.com/s/1ybA1qvjx4p844Pd8zDlx7Q"> <img src="s2794811.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>

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)

BIN
other/s10328621.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
other/s1074361.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
other/s1086045.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
other/s1092076.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
other/s1113106.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
other/s11194203.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
other/s1495029.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
other/s1613283.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
other/s1650904.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
other/s1671095.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
other/s2359163.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
other/s26676928.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
other/s2686916.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
other/s27023182.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
other/s27043456.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
other/s27243455.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
other/s27283822.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
other/s27297117.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
other/s27458236.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
other/s2794811.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
other/s28296984.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
other/s28313721.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
other/s28322244.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
other/s28341985.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
other/s28845534.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
other/s29195878.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
other/s2992671.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
other/s2996168.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
other/s3296854.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
other/s3895413.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
other/s4141593.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
other/s4157180.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
other/s4245786.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
other/s4379914.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
other/s4399937.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
other/s4436543.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
other/s4510534.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
other/s4647091.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
other/s4669554.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
other/s4687321.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
other/s5968156.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
other/s7038106.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
other/s7663093.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
other/s8938479.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
other/s9114855.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
pics/10.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
pics/11.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
pics/111521118015898.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
pics/111521118445538.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
pics/111521118483039.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
pics/111521118640738.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

BIN
pics/111521119203347.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

BIN
pics/111521119368714.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
pics/20150928140509757.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Some files were not shown because too many files have changed in this diff Show More