更新upstream

This commit is contained in:
xiongraorao 2018-08-20 10:20:02 +08:00
commit 9e38f733b0
34 changed files with 3394 additions and 3150 deletions

View File

@ -21,12 +21,12 @@
> [剑指 Offer 题解](notes/剑指%20offer%20题解.md) > [剑指 Offer 题解](notes/剑指%20offer%20题解.md)
目录根据原书第二版进行编排,代码和原书有所不同,尽量比原书更简洁。 目录根据原书第二版进行编排,代码和原书有所不同,尽量比原书更简洁。
> [Leetcode 题解](notes/Leetcode%20题解.md) > [Leetcode 题解](notes/Leetcode%20题解.md)
对题目做了一个大致分类,并对每种题型的解题思路做了总结。 对题目做了一个大致分类,并对每种题型的解题思路做了总结。
> [算法](notes/算法.md) > [算法](notes/算法.md)
排序、并查集、栈和队列、红黑树、散列表。 排序、并查集、栈和队列、红黑树、散列表。
@ -35,141 +35,141 @@
> [计算机操作系统](notes/计算机操作系统.md) > [计算机操作系统](notes/计算机操作系统.md)
进程管理、内存管理、设备管理、链接。 进程管理、内存管理、设备管理、链接。
> [Linux](notes/Linux.md) > [Linux](notes/Linux.md)
基本实现原理以及基本操作。 基本实现原理以及基本操作。
## 网络 :cloud: ## 网络 :cloud:
> [计算机网络](notes/计算机网络.md) > [计算机网络](notes/计算机网络.md)
物理层、链路层、网络层、运输层、应用层。 物理层、链路层、网络层、运输层、应用层。
> [HTTP](notes/HTTP.md) > [HTTP](notes/HTTP.md)
方法、状态码、Cookie、缓存、连接管理、HTTPs、HTTP 2.0。 方法、状态码、Cookie、缓存、连接管理、HTTPs、HTTP 2.0。
> [Socket](notes/Socket.md) > [Socket](notes/Socket.md)
I/O 模型、I/O 多路复用。 I/O 模型、I/O 多路复用。
## 面向对象 :couple: ## 面向对象 :couple:
> [设计模式](notes/设计模式.md) > [设计模式](notes/设计模式.md)
实现了 Gof 的 23 种设计模式。 实现了 Gof 的 23 种设计模式。
> [面向对象思想](notes/面向对象思想.md) > [面向对象思想](notes/面向对象思想.md)
三大原则(继承、封装、多态)、类图、设计原则。 三大原则(继承、封装、多态)、类图、设计原则。
## 数据库 :floppy_disk: ## 数据库 :floppy_disk:
> [数据库系统原理](notes/数据库系统原理.md) > [数据库系统原理](notes/数据库系统原理.md)
事务、锁、隔离级别、MVCC、间隙锁、范式。 事务、锁、隔离级别、MVCC、间隙锁、范式。
> [SQL](notes/SQL.md) > [SQL](notes/SQL.md)
SQL 基本语法。 SQL 基本语法。
> [Leetcode-Database 题解](notes/Leetcode-Database%20题解.md) > [Leetcode-Database 题解](notes/Leetcode-Database%20题解.md)
Leetcode 上数据库题目的解题记录。 Leetcode 上数据库题目的解题记录。
> [MySQL](notes/MySQL.md) > [MySQL](notes/MySQL.md)
存储引擎、索引、查询优化、切分、复制。 存储引擎、索引、查询优化、切分、复制。
> [Redis](notes/Redis.md) > [Redis](notes/Redis.md)
五种数据类型、字典和跳跃表数据结构、使用场景、和 Memcache 的比较、淘汰策略、持久化、文件事件的 Reactor 模式、复制。 五种数据类型、字典和跳跃表数据结构、使用场景、和 Memcache 的比较、淘汰策略、持久化、文件事件的 Reactor 模式、复制。
## Java :coffee: ## Java :coffee:
> [Java 基础](notes/Java%20基础.md) > [Java 基础](notes/Java%20基础.md)
不会涉及很多基本语法介绍,主要是一些实现原理以及关键特性。 不会涉及很多基本语法介绍,主要是一些实现原理以及关键特性。
> [Java 容器](/notes/Java%20容器.md) > [Java 容器](/notes/Java%20容器.md)
源码分析ArrayList、Vector、CopyOnWriteArrayList、LinkedList、HashMap、ConcurrentHashMap、LinkedHashMap、WeekHashMap。 源码分析ArrayList、Vector、CopyOnWriteArrayList、LinkedList、HashMap、ConcurrentHashMap、LinkedHashMap、WeekHashMap。
> [Java 并发](notes/Java%20并发.md) > [Java 并发](notes/Java%20并发.md)
线程使用方式、两种互斥同步方法、线程协作、JUC、线程安全、内存模型、锁优化。 线程使用方式、两种互斥同步方法、线程协作、JUC、线程安全、内存模型、锁优化。
> [Java 虚拟机](notes/Java%20虚拟机.md) > [Java 虚拟机](notes/Java%20虚拟机.md)
运行时数据区域、垃圾收集、类加载。 运行时数据区域、垃圾收集、类加载。
> [Java I/O](notes/Java%20IO.md) > [Java I/O](notes/Java%20IO.md)
NIO 的原理以及实例。 NIO 的原理以及实例。
## 系统设计 :bulb: ## 系统设计 :bulb:
> [系统设计基础](notes/系统设计基础.md) > [系统设计基础](notes/系统设计基础.md)
性能、伸缩性、扩展性、可用性、安全性 性能、伸缩性、扩展性、可用性、安全性
> [分布式](notes/分布式.md) > [分布式](notes/分布式.md)
分布式锁、分布式事务、CAP、BASE、Paxos、Raft 分布式锁、分布式事务、CAP、BASE、Paxos、Raft
> [集群](notes/集群.md) > [集群](notes/集群.md)
负载均衡、Session 管理 负载均衡、Session 管理
> [攻击技术](notes/攻击技术.md) > [攻击技术](notes/攻击技术.md)
XSS、CSRF、SQL 注入、DDoS XSS、CSRF、SQL 注入、DDoS
> [缓存](notes/缓存.md) > [缓存](notes/缓存.md)
缓存特征、缓存位置、缓存问题、数据分布、一致性哈希、LRU、CDN 缓存特征、缓存位置、缓存问题、数据分布、一致性哈希、LRU、CDN
> [消息队列](notes/消息队列.md) > [消息队列](notes/消息队列.md)
消息处理模型、使用场景、可靠性 消息处理模型、使用场景、可靠性
## 工具 :hammer: ## 工具 :hammer:
> [Git](notes/Git.md) > [Git](notes/Git.md)
一些 Git 的使用和概念。 一些 Git 的使用和概念。
> [Docker](notes/Docker.md) > [Docker](notes/Docker.md)
Docker 基本原理。 Docker 基本原理。
> [正则表达式](notes/正则表达式.md) > [正则表达式](notes/正则表达式.md)
正则表达式基本语法。 正则表达式基本语法。
> [构建工具](notes/构建工具.md) > [构建工具](notes/构建工具.md)
构建工具的基本概念、主流构建工具介绍。 构建工具的基本概念、主流构建工具介绍。
## 编码实践 :speak_no_evil: ## 编码实践 :speak_no_evil:
> [重构](notes/重构.md) > [重构](notes/重构.md)
参考 重构 改善既有代码的设计。 参考 重构 改善既有代码的设计。
> [代码可读性](notes/代码可读性.md) > [代码可读性](notes/代码可读性.md)
参考 编写可读代码的艺术。 参考 编写可读代码的艺术。
> [代码风格规范](notes/代码风格规范.md) > [代码风格规范](notes/代码风格规范.md)
Google 开源项目的代码风格规范。 Google 开源项目的代码风格规范。
## 后记 :memo: ## 后记 :memo:
**About** ### About
这个仓库是笔者的一个学习笔记,主要总结一些比较重要的知识点,希望对大家有所帮助。 这个仓库是笔者的一个学习笔记,主要总结一些比较重要的知识点,希望对大家有所帮助。
@ -177,7 +177,7 @@ Google 开源项目的代码风格规范。
[BOOKLIST](BOOKLIST.md),这个书单是笔者至今看的一些比较好的技术书籍,虽然没有全都看完,但每本书多多少少都看了一部分。 [BOOKLIST](BOOKLIST.md),这个书单是笔者至今看的一些比较好的技术书籍,虽然没有全都看完,但每本书多多少少都看了一部分。
**How To Contribute** ### How To Contribute
笔记内容是笔者一个字一个字打上去的,难免会有一些笔误,如果发现笔误可直接在相应文档进行编辑修改。 笔记内容是笔者一个字一个字打上去的,难免会有一些笔误,如果发现笔误可直接在相应文档进行编辑修改。
@ -185,13 +185,7 @@ Google 开源项目的代码风格规范。
欢迎在 Issue 中提交对本仓库的改进建议~ 欢迎在 Issue 中提交对本仓库的改进建议~
**Authorization** ### Typesetting
虽然没有加开源协议,但是允许非商业性使用。
转载使用请注明出处,谢谢!
**Typesetting**
笔记内容按照 [中文文案排版指北](http://mazhuang.org/wiki/chinese-copywriting-guidelines/) 进行排版,以保证内容的可读性。 笔记内容按照 [中文文案排版指北](http://mazhuang.org/wiki/chinese-copywriting-guidelines/) 进行排版,以保证内容的可读性。
@ -199,7 +193,7 @@ Google 开源项目的代码风格规范。
笔者将自己实现的文档排版功能提取出来,放在 Github Page 中,无需下载安装即可免费使用:[Text-Typesetting](https://github.com/CyC2018/Markdown-Typesetting)。 笔者将自己实现的文档排版功能提取出来,放在 Github Page 中,无需下载安装即可免费使用:[Text-Typesetting](https://github.com/CyC2018/Markdown-Typesetting)。
**Uploading** ### Uploading
笔者在本地使用为知笔记软件进行书写,为了方便将本地笔记内容上传到 Github 上实现了一整套自动化上传方案包括文本文件的导出、提取图片、Markdown 文档转换、Git 同步。 笔者在本地使用为知笔记软件进行书写,为了方便将本地笔记内容上传到 Github 上实现了一整套自动化上传方案包括文本文件的导出、提取图片、Markdown 文档转换、Git 同步。
@ -207,15 +201,21 @@ Google 开源项目的代码风格规范。
笔者将自己实现文档转换功能提取出来,方便大家在需要将本地 Markdown 上传到 Github或者制作项目 README 文档时生成目录时使用:[GFM-Converter](https://github.com/CyC2018/GFM-Converter)。 笔者将自己实现文档转换功能提取出来,方便大家在需要将本地 Markdown 上传到 Github或者制作项目 README 文档时生成目录时使用:[GFM-Converter](https://github.com/CyC2018/GFM-Converter)。
**Statement** ### License
在对本作品进行演绎时,请署名并以相同方式共享。
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a>
### Statement
本仓库不参与商业行为,不向读者收取任何费用。(This repository is not engaging in business activities, and does not charge readers any fee.) 本仓库不参与商业行为,不向读者收取任何费用。(This repository is not engaging in business activities, and does not charge readers any fee.)
**Logo** ### Logo
Power by [logomakr](https://logomakr.com/). Power by [logomakr](https://logomakr.com/).
**Acknowledgements** ### Acknowledgements
感谢以下人员对本仓库做出的贡献,当然不仅仅只有这些贡献者,这里就不一一列举了。如果你希望被添加到这个名单中,并且提交过 Issue 或者 PR请与笔者联系。 感谢以下人员对本仓库做出的贡献,当然不仅仅只有这些贡献者,这里就不一一列举了。如果你希望被添加到这个名单中,并且提交过 Issue 或者 PR请与笔者联系。

View File

@ -45,7 +45,7 @@
* [二进制分帧层](#二进制分帧层) * [二进制分帧层](#二进制分帧层)
* [服务端推送](#服务端推送) * [服务端推送](#服务端推送)
* [首部压缩](#首部压缩) * [首部压缩](#首部压缩)
* [八、GET 和 POST 的区别](#八get-和-post-的区别) * [八、GET 和 POST 比较](#八get-和-post-比较)
* [作用](#作用) * [作用](#作用)
* [参数](#参数) * [参数](#参数)
* [安全](#安全) * [安全](#安全)
@ -61,13 +61,13 @@
## URL ## URL
- URIUniform Resource Identifier统一资源标识符
- URLUniform Resource Locator统一资源定位符
- URNUniform Resource Name统一资源名称例如 urn:isbn:0-486-27557-4。
URI 包含 URL 和 URN目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。 URI 包含 URL 和 URN目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。
<div align="center"> <img src="../pics//f716427a-94f2-4875-9c86-98793cf5dcc3.jpg" width="400"/> </div><br> - URIUniform Resource Identifier统一资源标识符
- URLUniform Resource Locator统一资源定位符
- URNUniform Resource Name统一资源名称
<div align="center"> <img src="../pics//urlnuri.jpg" width="600"/> </div><br>
## 请求和响应报文 ## 请求和响应报文
@ -197,7 +197,7 @@ CONNECT www.example.com:443 HTTP/1.1
- **204 No Content** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。 - **204 No Content** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。
- **206 Partial Content** :表示客户端进行了范围请求响应报文包含由 Content-Range 指定范围的实体内容。 - **206 Partial Content** :表示客户端进行了范围请求响应报文包含由 Content-Range 指定范围的实体内容。
## 3XX 重定向 ## 3XX 重定向
@ -219,7 +219,7 @@ CONNECT www.example.com:443 HTTP/1.1
- **401 Unauthorized** 该状态码表示发送的请求需要有认证信息BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。 - **401 Unauthorized** 该状态码表示发送的请求需要有认证信息BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。
- **403 Forbidden** :请求被拒绝,服务器端没有必要给出拒绝的详细理由 - **403 Forbidden** :请求被拒绝。
- **404 Not Found** - **404 Not Found**
@ -331,7 +331,7 @@ Set-Cookie: tasty_cookie=strawberry
[page content] [page content]
``` ```
客户端之后对同一个服务器发送请求时,会从浏览器中读出 Cookie 信息通过 Cookie 请求首部字段发送给服务器。 客户端之后对同一个服务器发送请求时,会从浏览器中取出 Cookie 信息并通过 Cookie 请求首部字段发送给服务器。
```html ```html
GET /sample_page.html HTTP/1.1 GET /sample_page.html HTTP/1.1
@ -382,9 +382,9 @@ Path 标识指定了主机下的哪些路径可以接受 Cookie该 URL 路径
除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。 除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。
Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在内存型数据库中,比如 Redis,效率会更高。 Session 可以存储在服务器上的文件、数据库或者内存中。也可以将 Session 存储在 Redis 这种内存型数据库中,效率会更高。
使用 Session 维护用户登录的过程如下: 使用 Session 维护用户登录状态的过程如下:
- 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中; - 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中;
- 服务器验证该用户名和密码; - 服务器验证该用户名和密码;
@ -499,7 +499,7 @@ If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
### 1. 短连接与长连接 ### 1. 短连接与长连接
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要断开一次 TCP 连接,连接建立和断开的开销会很大。 当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。
长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。 长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
@ -688,7 +688,7 @@ HTTPs 并不是新协议,而是让 HTTP 先和 SSLSecure Sockets Layer
### 3. HTTPs 采用的加密方式 ### 3. HTTPs 采用的加密方式
HTTPs 采用混合的加密机制,使用非对称密钥加密用于传输对称密钥来保证安全性,之后使用对称密钥加密进行通信来保证效率。(下图中的 Session Key 就是对称密钥) HTTPs 采用混合的加密机制,使用非对称密钥加密用于传输对称密钥来保证传输过程的安全性,之后使用对称密钥加密进行通信来保证通信过程的效率。(下图中的 Session Key 就是对称密钥)
<div align="center"> <img src="../pics//How-HTTPS-Works.png" width="600"/> </div><br> <div align="center"> <img src="../pics//How-HTTPS-Works.png" width="600"/> </div><br>
@ -717,7 +717,7 @@ HTTPs 的报文摘要功能之所以安全,是因为它结合了加密和认
## HTTPs 的缺点 ## HTTPs 的缺点
- 因为需要进行加密解密等过程,因此速度会更慢; - 因为需要进行加密解密等过程,因此速度会更慢;
- 需要支付证书授权的高费用。 - 需要支付证书授权的高费用。
## 配置 HTTPs ## 配置 HTTPs
@ -727,7 +727,7 @@ HTTPs 的报文摘要功能之所以安全,是因为它结合了加密和认
## HTTP/1.x 缺陷 ## HTTP/1.x 缺陷
HTTP/1.x 实现简单是以牺牲应用性能为代价的: HTTP/1.x 实现简单是以牺牲性能为代价的:
- 客户端需要使用多个连接才能实现并发和缩短延迟; - 客户端需要使用多个连接才能实现并发和缩短延迟;
- 不会压缩请求和响应首部,从而导致不必要的网络流量; - 不会压缩请求和响应首部,从而导致不必要的网络流量;
@ -763,7 +763,7 @@ HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见
<div align="center"> <img src="../pics//_u4E0B_u8F7D.png" width="600"/> </div><br> <div align="center"> <img src="../pics//_u4E0B_u8F7D.png" width="600"/> </div><br>
# 八、GET 和 POST 的区别 # 八、GET 和 POST 比较
## 作用 ## 作用
@ -870,6 +870,7 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404
- [MDN : HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP) - [MDN : HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)
- [HTTP/2 简介](https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn) - [HTTP/2 简介](https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn)
- [htmlspecialchars](http://php.net/manual/zh/function.htmlspecialchars.php) - [htmlspecialchars](http://php.net/manual/zh/function.htmlspecialchars.php)
- [Difference between file URI and URL in java](http://java2db.com/java-io/how-to-get-and-the-difference-between-file-uri-and-url-in-java)
- [How to Fix SQL Injection Using Java PreparedStatement & CallableStatement](https://software-security.sans.org/developer-how-to/fix-sql-injection-in-java-using-prepared-callable-statement) - [How to Fix SQL Injection Using Java PreparedStatement & CallableStatement](https://software-security.sans.org/developer-how-to/fix-sql-injection-in-java-using-prepared-callable-statement)
- [浅谈 HTTP 中 Get 与 Post 的区别](https://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.html) - [浅谈 HTTP 中 Get 与 Post 的区别](https://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.html)
- [Are http:// and www really necessary?](https://www.webdancers.com/are-http-and-www-necesary/) - [Are http:// and www really necessary?](https://www.webdancers.com/are-http-and-www-necesary/)

View File

@ -2,8 +2,17 @@
* [一、概览](#一概览) * [一、概览](#一概览)
* [二、磁盘操作](#二磁盘操作) * [二、磁盘操作](#二磁盘操作)
* [三、字节操作](#三字节操作) * [三、字节操作](#三字节操作)
* [实现文件复制](#实现文件复制)
* [装饰者模式](#装饰者模式)
* [四、字符操作](#四字符操作) * [四、字符操作](#四字符操作)
* [编码与解码](#编码与解码)
* [String](#string)
* [Reader 与 Writer](#reader-与-writer)
* [实现逐行输出文本文件的内容](#实现逐行输出文本文件的内容)
* [五、对象操作](#五对象操作) * [五、对象操作](#五对象操作)
* [序列化](#序列化)
* [Serializable](#serializable)
* [transient](#transient)
* [六、网络操作](#六网络操作) * [六、网络操作](#六网络操作)
* [InetAddress](#inetaddress) * [InetAddress](#inetaddress)
* [URL](#url) * [URL](#url)
@ -37,7 +46,7 @@ Java 的 I/O 大概可以分成以下几类:
File 类可以用于表示文件和目录的信息,但是它不表示文件的内容。 File 类可以用于表示文件和目录的信息,但是它不表示文件的内容。
递归地出一个目录下所有文件: 递归地出一个目录下所有文件:
```java ```java
public static void listAllFiles(File dir) { public static void listAllFiles(File dir) {
@ -56,7 +65,7 @@ public static void listAllFiles(File dir) {
# 三、字节操作 # 三、字节操作
使用字节流操作进行文件复制: ## 实现文件复制
```java ```java
public static void copyFile(String src, String dist) throws IOException { public static void copyFile(String src, String dist) throws IOException {
@ -77,13 +86,15 @@ public static void copyFile(String src, String dist) throws IOException {
} }
``` ```
<div align="center"> <img src="../pics//DP-Decorator-java.io.png" width="500"/> </div><br> ## 装饰者模式
Java I/O 使用了装饰者模式来实现。以 InputStream 为例, Java I/O 使用了装饰者模式来实现。以 InputStream 为例,
- InputStream 是抽象组件; - InputStream 是抽象组件;
- FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作; - FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作;
- FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能,例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。 - FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能。例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。
<div align="center"> <img src="../pics//DP-Decorator-java.io.png" width="500"/> </div><br>
实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。 实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。
@ -96,27 +107,7 @@ DataInputStream 装饰者提供了对更多数据类型进行输入的操作,
# 四、字符操作 # 四、字符操作
不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。 ## 编码与解码
- InputStreamReader 实现从字节流解码成字符流;
- OutputStreamWriter 实现字符流编码成为字节流。
逐行输出文本文件的内容:
```java
public static void readFileContent(String filePath) throws IOException {
FileReader fileReader = new FileReader(filePath);
BufferedReader bufferedReader = new BufferedReader(fileReader);
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
// 装饰者模式使得 BufferedReader 组合了一个 Reader 对象
// 在调用 BufferedReader 的 close() 方法时会去调用 fileReader 的 close() 方法
// 因此只要一个 close() 调用即可
bufferedReader.close();
}
```
编码就是把字符转换为字节,而解码是把字节重新组合成字符。 编码就是把字符转换为字节,而解码是把字节重新组合成字符。
@ -130,6 +121,8 @@ UTF-16be 中的 be 指的是 Big Endian也就是大端。相应地也有 UTF-
Java 使用双字节编码 UTF-16be这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位也就是两个字节Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。 Java 使用双字节编码 UTF-16be这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位也就是两个字节Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。
## String
String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。 String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。
```java ```java
@ -145,13 +138,46 @@ System.out.println(str2);
byte[] bytes = str1.getBytes(); byte[] bytes = str1.getBytes();
``` ```
## Reader 与 Writer
不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。
- InputStreamReader 实现从字节流解码成字符流;
- OutputStreamWriter 实现字符流编码成为字节流。
## 实现逐行输出文本文件的内容
```java
public static void readFileContent(String filePath) throws IOException {
FileReader fileReader = new FileReader(filePath);
BufferedReader bufferedReader = new BufferedReader(fileReader);
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
// 装饰者模式使得 BufferedReader 组合了一个 Reader 对象
// 在调用 BufferedReader 的 close() 方法时会去调用 Reader 的 close() 方法
// 因此只要一个 close() 调用即可
bufferedReader.close();
}
```
# 五、对象操作 # 五、对象操作
## 序列化
序列化就是将一个对象转换成字节序列,方便存储和传输。 序列化就是将一个对象转换成字节序列,方便存储和传输。
- 序列化ObjectOutputStream.writeObject() - 序列化ObjectOutputStream.writeObject()
- 反序列化ObjectInputStream.readObject() - 反序列化ObjectInputStream.readObject()
不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。
## Serializable
序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。 序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。
```java ```java
@ -184,11 +210,11 @@ private static class A implements Serializable {
} }
``` ```
不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。 ## transient
transient 关键字可以使一些属性不会被序列化。 transient 关键字可以使一些属性不会被序列化。
ArrayList 中存储数据的数组是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。 ArrayList 中存储数据的数组 elementData 是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。
```java ```java
private transient Object[] elementData; private transient Object[] elementData;
@ -249,8 +275,8 @@ public static void main(String[] args) throws IOException {
## Datagram ## Datagram
- DatagramPacket数据包类
- DatagramSocket通信类 - DatagramSocket通信类
- DatagramPacket数据包类
# 七、NIO # 七、NIO

View File

@ -62,7 +62,10 @@ int y = x; // 拆箱
## 缓存池 ## 缓存池
new Integer(123) 与 Integer.valueOf(123) 的区别在于new Integer(123) 每次都会新建一个对象,而 Integer.valueOf(123) 可能会使用缓存对象,因此多次使用 Integer.valueOf(123) 会取得同一个对象的引用。 new Integer(123) 与 Integer.valueOf(123) 的区别在于:
- new Integer(123) 每次都会新建一个对象
- Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。
```java ```java
Integer x = new Integer(123); Integer x = new Integer(123);
@ -73,14 +76,6 @@ Integer k = Integer.valueOf(123);
System.out.println(z == k); // true System.out.println(z == k); // true
``` ```
编译器会在自动装箱过程调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同,那么就会引用相同的对象。
```java
Integer m = 123;
Integer n = 123;
System.out.println(m == n); // true
```
valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。 valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容。
```java ```java
@ -125,7 +120,15 @@ static {
} }
``` ```
Java 还将一些其它基本类型的值放在缓冲池中,包含以下这些: 编译器会在自动装箱过程调用 valueOf() 方法,因此多个 Integer 实例使用自动装箱来创建并且值相同,那么就会引用相同的对象。
```java
Integer m = 123;
Integer n = 123;
System.out.println(m == n); // true
```
基本类型对应的缓冲池如下:
- boolean values true and false - boolean values true and false
- all byte values - all byte values
@ -133,7 +136,7 @@ Java 还将一些其它基本类型的值放在缓冲池中,包含以下这些
- int values between -128 and 127 - int values between -128 and 127
- char in the range \u0000 to \u007F - char in the range \u0000 to \u007F
因此在使用这些基本类型对应的包装类型时,就可以直接使用缓冲池中的对象。 在使用这些基本类型对应的包装类型时,就可以直接使用缓冲池中的对象。
[StackOverflow : Differences between new Integer(123), Integer.valueOf(123) and just 123 [StackOverflow : Differences between new Integer(123), Integer.valueOf(123) and just 123
](https://stackoverflow.com/questions/9030817/differences-between-new-integer123-integer-valueof123-and-just-123) ](https://stackoverflow.com/questions/9030817/differences-between-new-integer123-integer-valueof123-and-just-123)
@ -186,15 +189,15 @@ String 不可变性天生具备线程安全,可以在多个线程中安全地
- String 不可变,因此是线程安全的 - String 不可变,因此是线程安全的
- StringBuilder 不是线程安全的 - StringBuilder 不是线程安全的
- StringBuffer 是线程安全的,内部使用 synchronized 同步 - StringBuffer 是线程安全的,内部使用 synchronized 进行同步
[StackOverflow : String, StringBuffer, and StringBuilder](https://stackoverflow.com/questions/2971315/string-stringbuffer-and-stringbuilder) [StackOverflow : String, StringBuffer, and StringBuilder](https://stackoverflow.com/questions/2971315/string-stringbuffer-and-stringbuilder)
## String.intern() ## String.intern()
使用 String.intern() 可以保证相同内容的字符串变量引用同的内存对象。 使用 String.intern() 可以保证相同内容的字符串变量引用同的内存对象。
下面示例中s1 和 s2 采用 new String() 的方式新建了两个不同对象,而 s3 是通过 s1.intern() 方法取得一个对象引用,这个方法首先把 s1 引用的对象放到 String Pool字符串常量池然后返回这个对象引用。因此 s3 和 s1 引用的是同一个字符串常量池的对象。 下面示例中s1 和 s2 采用 new String() 的方式新建了两个不同对象,而 s3 是通过 s1.intern() 方法取得一个对象引用。intern() 首先把 s1 引用的对象放到 String Pool字符串常量池然后返回这个对象引用。因此 s3 和 s1 引用的是同一个字符串常量池的对象。
```java ```java
String s1 = new String("aaa"); String s1 = new String("aaa");
@ -214,7 +217,7 @@ System.out.println(s4 == s5); // true
在 Java 7 之前,字符串常量池被放在运行时常量池中,它属于永久代。而在 Java 7字符串常量池被移到 Native Method 中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。 在 Java 7 之前,字符串常量池被放在运行时常量池中,它属于永久代。而在 Java 7字符串常量池被移到 Native Method 中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。
- [StackOverflow : What is String interning?](https://stackoverflow.com/questions/10578984/what-is-string-interning) - [StackOverflow : What is String interning?](https://stackoverflow.com/questions/10578984/what-is-string-interning)
- [深入解析 String#intern](https://tech.meituan.com/in_depth_understanding_string_intern.html) - [深入解析 String#intern](https://tech.meituan.com/in_depth_understanding_string_intern.html)
# 三、运算 # 三、运算
@ -223,7 +226,7 @@ System.out.println(s4 == s5); // true
Java 的参数是以值传递的形式传入方法中,而不是引用传递。 Java 的参数是以值传递的形式传入方法中,而不是引用传递。
以下代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。但是如果在方法中改变对象的字段值会改变原对象该字段值,因为改变的是同一个地址指向的内容 以下代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。因此在方法中改变指针引用的对象,那么这两个指针此时指向的是完全不同的对象,一方改变其所指向对象的内容对另一方没有影响
```java ```java
public class Dog { public class Dog {
@ -234,7 +237,11 @@ public class Dog {
} }
String getName() { String getName() {
return name; return this.name;
}
void setName(String name) {
this.name = name;
} }
String getObjectAddress() { String getObjectAddress() {
@ -262,6 +269,22 @@ public class PassByValueExample {
} }
``` ```
但是如果在方法中改变对象的字段值会改变原对象该字段值,因为改变的是同一个地址指向的内容。
```java
class PassByValueExample {
public static void main(String[] args) {
Dog dog = new Dog("A");
func(dog);
System.out.println(dog.getName()); // B
}
private static void func(Dog dog) {
dog.setName("B");
}
}
```
[StackOverflow: Is Java “pass-by-reference” or “pass-by-value”?](https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value) [StackOverflow: Is Java “pass-by-reference” or “pass-by-value”?](https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value)
## float 与 double ## float 与 double
@ -317,7 +340,7 @@ switch (s) {
} }
``` ```
switch 不支持 long是因为 switch 的设计初衷是为那些只需要对少数的几个值进行等值判断,如果值过于复杂,那么还是用 if 比较合适。 switch 不支持 long是因为 switch 的设计初衷是对那些只有少数的几个值进行等值判断,如果值过于复杂,那么还是用 if 比较合适。
```java ```java
// long x = 111; // long x = 111;
@ -341,33 +364,37 @@ Java 中有三个访问权限修饰符private、protected 以及 public
可以对类或类中的成员(字段以及方法)加上访问修饰符。 可以对类或类中的成员(字段以及方法)加上访问修饰符。
- 成员可见表示其它类可以用这个类的实例对象访问到该成员;
- 类可见表示其它类可以用这个类创建实例对象。 - 类可见表示其它类可以用这个类创建实例对象。
- 成员可见表示其它类可以用这个类的实例对象访问到该成员;
protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。 protected 用于修饰成员,表示在继承体系中成员对于子类可见,但是这个访问修饰符对于类没有意义。
设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。 设计良好的模块会隐藏所有的实现细节,把它的 API 与它的实现清晰地隔离开来。模块之间只通过它们的 API 进行通信,一个模块不需要知道其他模块的内部工作情况,这个概念被称为信息隐藏或封装。因此访问权限应当尽可能地使每个类或者成员不被外界访问。
如果子类的方法覆盖了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。 如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。
字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。可以使用公有的 getter 和 setter 方法来替换公有字段 字段决不能是公有的,因为这么做的话就失去了对这个字段修改行为的控制,客户端可以对其随意修改。例如下面的例子中AccessExample 拥有 id 共有字段,如果在某个时刻,我们想要使用 int 去存储 id 字段,那么就需要去修改所有的客户端代码
```java ```java
public class AccessExample { public class AccessExample {
public int x; public String id;
} }
``` ```
可以使用公有的 getter 和 setter 方法来替换公有字段,这样的话就可以控制对字段的修改行为。
```java ```java
public class AccessExample { public class AccessExample {
private int x;
public int getX() { private int id;
return x;
public String getId() {
return id + "";
} }
public void setX(int x) { public void setId(String id) {
this.x = x; this.id = Integer.valueOf(id);
} }
} }
``` ```
@ -387,7 +414,7 @@ public class AccessWithInnerClassExample {
} }
public int getValue() { public int getValue() {
return innerClass.x; // 直接访问 return innerClass.x; // 直接访问
} }
} }
``` ```
@ -396,7 +423,7 @@ public class AccessWithInnerClassExample {
**1. 抽象类** **1. 抽象类**
抽象类和抽象方法都使用 abstract 进行声明。抽象类一般会包含抽象方法,抽象方法一定位于抽象类中。 抽象类和抽象方法都使用 abstract 关键字进行声明。抽象类一般会包含抽象方法,抽象方法一定位于抽象类中。
抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。 抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。
@ -415,7 +442,7 @@ public abstract class AbstractClassExample {
``` ```
```java ```java
public class AbstractExtendClassExample extends AbstractClassExample{ public class AbstractExtendClassExample extends AbstractClassExample {
@Override @Override
public void func1() { public void func1() {
System.out.println("func1"); System.out.println("func1");
@ -448,7 +475,7 @@ public interface InterfaceExample {
} }
int x = 123; int x = 123;
// int y; // Variable 'y' might not have been initialized // int y; // Variable 'y' might not have been initialized
public int z = 0; // Modifier 'public' is redundant for interface fields public int z = 0; // Modifier 'public' is redundant for interface fields
// private int k = 0; // Modifier 'private' not allowed here // private int k = 0; // Modifier 'private' not allowed here
// protected int l = 0; // Modifier 'protected' not allowed here // protected int l = 0; // Modifier 'protected' not allowed here
@ -477,21 +504,21 @@ System.out.println(InterfaceExample.x);
- 从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。 - 从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。
- 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。 - 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
- 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。 - 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
- 接口的方法只能是 public 的,而抽象类的方法可以有多种访问权限。 - 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。
**4. 使用选择** **4. 使用选择**
使用抽象类:
- 需要在几个相关的类中共享代码。
- 需要能控制继承来的成员的访问权限,而不是都为 public。
- 需要继承非静态non-static和非常量non-final字段。
使用接口: 使用接口:
- 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法; - 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;
- 需要使用多重继承。 - 需要使用多重继承。
使用抽象类:
- 需要在几个相关的类中共享代码。
- 需要能控制继承来的成员的访问权限,而不是都为 public。
- 需要继承非静态和非常量字段。
在很多情况下,接口优先于抽象类,因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。 在很多情况下,接口优先于抽象类,因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。
- [深入理解 abstract class 和 interface](https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/) - [深入理解 abstract class 和 interface](https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/)
@ -499,8 +526,8 @@ System.out.println(InterfaceExample.x);
## super ## super
- 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而完成一些初始化的工作。 - 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。
- 访问父类的成员:如果子类覆盖了父类的中某个方法的实现,可以通过使用 super 关键字来引用父类的方法实现。 - 访问父类的成员:如果子类重写了父类的中某个方法的实现,可以通过使用 super 关键字来引用父类的方法实现。
```java ```java
public class SuperExample { public class SuperExample {
@ -549,9 +576,22 @@ SuperExtendExample.func()
## 重写与重载 ## 重写与重载
- 重写Override存在于继承体系中指子类实现了一个与父类在方法声明上完全相同的一个方法子类的返回值类型要等于或者小于父类的返回值 **1. 重写Override**
- 重载Overload存在于同一个类中指一个方法与已经存在的方法名称上相同但是参数类型、个数、顺序至少有一个不同。应该注意的是返回值不同其它都相同不算是重载。 存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。
为了满足里式替换原则,重写有有以下两个限制:
- 子类方法的访问权限必须大于等于父类方法;
- 子类方法的返回类型必须是父类方法返回类型或为其子类型。
使用 @Override 注解,可以让编译器帮忙检查是否满足上面的两个限制条件。
**2. 重载Overload**
存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。
应该注意的是,返回值不同,其它都相同不算是重载。
# 五、Object 通用方法 # 五、Object 通用方法
@ -623,7 +663,7 @@ x.equals(null); // false;
**2. equals() 与 ==** **2. equals() 与 ==**
- 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。 - 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
- 对于引用类型,== 判断两个实例是否引用同一个对象,而 equals() 判断引用的对象是否等价,根据引用对象 equals() 方法的具体实现来进行比较 - 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。
```java ```java
Integer x = new Integer(1); Integer x = new Integer(1);
@ -636,7 +676,7 @@ System.out.println(x == y); // false
- 检查是否为同一个对象的引用,如果是直接返回 true - 检查是否为同一个对象的引用,如果是直接返回 true
- 检查是否是同一个类型,如果不是,直接返回 false - 检查是否是同一个类型,如果不是,直接返回 false
- 将 Object 实例进行转型; - 将 Object 对象进行转型;
- 判断每个关键域是否相等。 - 判断每个关键域是否相等。
```java ```java
@ -667,11 +707,11 @@ public class EqualExample {
## hashCode() ## hashCode()
hasCode() 返回散列值,而 equals() 是用来判断两个实例是否等价。等价的两个实例散列值一定要相同,但是散列值相同的两个实例不一定等价。 hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。
在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个实例散列值也相等。 在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。
下面的代码中,新建了两个等价的实例,并将它们添加到 HashSet 中。我们希望将这两个实例当成一样的,只在集合中添加一个实例,但是因为 EqualExample 没有实现 hasCode() 方法,因此这两个实例的散列值是不同的,最终导致集合添加了两个等价的实例 下面的代码中,新建了两个等价的对象,并将它们添加到 HashSet 中。我们希望将这两个对象当成一样的,只在集合中添加一个对象,但是因为 EqualExample 没有实现 hasCode() 方法,因此这两个对象的散列值是不同的,最终导致集合添加了两个等价的对象
```java ```java
EqualExample e1 = new EqualExample(1, 1, 1); EqualExample e1 = new EqualExample(1, 1, 1);
@ -683,7 +723,7 @@ set.add(e2);
System.out.println(set.size()); // 2 System.out.println(set.size()); // 2
``` ```
理想的散列函数应当具有均匀性,即不相等的实例应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来,可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31因为它是一个奇素数如果是偶数的话当出现乘法溢出信息就会丢失因为与 2 相乘相当于向左移一位。 理想的散列函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来,可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31因为它是一个奇素数如果是偶数的话当出现乘法溢出信息就会丢失因为与 2 相乘相当于向左移一位。
一个数与 31 相乘可以转换成移位和减法:`31*x == (x<<5)-x`,编译器会自动进行这个优化。 一个数与 31 相乘可以转换成移位和减法:`31*x == (x<<5)-x`,编译器会自动进行这个优化。
@ -725,7 +765,7 @@ ToStringExample@4554617c
**1. cloneable** **1. cloneable**
clone() 是 Object 的 protect 方法,它不是 public一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。 clone() 是 Object 的 protected 方法,它不是 public一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。
```java ```java
public class CloneExample { public class CloneExample {
@ -768,6 +808,8 @@ java.lang.CloneNotSupportedException: CloneExample
以上抛出了 CloneNotSupportedException这是因为 CloneExample 没有实现 Cloneable 接口。 以上抛出了 CloneNotSupportedException这是因为 CloneExample 没有实现 Cloneable 接口。
应该注意的是clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。
```java ```java
public class CloneExample implements Cloneable { public class CloneExample implements Cloneable {
private int a; private int a;
@ -780,12 +822,9 @@ public class CloneExample implements Cloneable {
} }
``` ```
应该注意的是clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。 **2. 浅拷贝**
**2. 深拷贝与浅拷贝** 拷贝对象和原始对象的引用类型引用同一个对象。
- 浅拷贝:拷贝实例和原始实例的引用类型引用同一个对象;
- 深拷贝:拷贝实例和原始实例的引用类型引用不同对象。
```java ```java
public class ShallowCloneExample implements Cloneable { public class ShallowCloneExample implements Cloneable {
@ -825,6 +864,10 @@ e1.set(2, 222);
System.out.println(e2.get(2)); // 222 System.out.println(e2.get(2)); // 222
``` ```
**3. 深拷贝**
拷贝对象和原始对象的引用类型引用不同对象。
```java ```java
public class DeepCloneExample implements Cloneable { public class DeepCloneExample implements Cloneable {
private int[] arr; private int[] arr;
@ -868,6 +911,8 @@ e1.set(2, 222);
System.out.println(e2.get(2)); // 2 System.out.println(e2.get(2)); // 2
``` ```
**4. clone() 的替代方案**
使用 clone() 方法来拷贝一个对象即复杂又有风险它会抛出异常并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。 使用 clone() 方法来拷贝一个对象即复杂又有风险它会抛出异常并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。
```java ```java
@ -937,7 +982,7 @@ private 方法隐式地被指定为 final如果在子类中定义的方法和
**1. 静态变量** **1. 静态变量**
- 静态变量:类所有的实例都共享静态变量,可以直接通过类名来访问它;静态变量在内存中只存在一份。 - 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它;静态变量在内存中只存在一份。
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。 - 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
```java ```java
@ -956,7 +1001,7 @@ public class A {
**2. 静态方法** **2. 静态方法**
静态方法在类加载的时候就存在了,它不依赖于任何实例所以静态方法必须有实现也就是说它不能是抽象方法abstract 静态方法在类加载的时候就存在了,它不依赖于任何实例所以静态方法必须有实现也就是说它不能是抽象方法abstract
```java ```java
public abstract class A { public abstract class A {
@ -996,7 +1041,6 @@ public class A {
A a2 = new A(); A a2 = new A();
} }
} }
``` ```
```html ```html
@ -1028,12 +1072,12 @@ public class OuterClass {
**5. 静态导包** **5. 静态导包**
在使用静态变量和方法时不用再指明 ClassName从而简化代码但可读性大大降低。
```java ```java
import static com.xxx.ClassName.* import static com.xxx.ClassName.*
``` ```
在使用静态变量和方法时不用再指明 ClassName从而简化代码但可读性大大降低。
**6. 初始化顺序** **6. 初始化顺序**
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。 静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。
@ -1075,11 +1119,12 @@ public InitialOrderTest() {
- 子类(实例变量、普通语句块) - 子类(实例变量、普通语句块)
- 子类(构造函数) - 子类(构造函数)
# 七、反射 # 七、反射
每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。 每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。
类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 Class.forName("com.mysql.jdbc.Driver") 这种方式来控制类的加载,该方法会返回一个 Class 对象。 类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 `Class.forName("com.mysql.jdbc.Driver")` 这种方式来控制类的加载,该方法会返回一个 Class 对象。
反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。 反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。

View File

@ -21,17 +21,17 @@
# 一、概览 # 一、概览
容器主要包括 Collection 和 Map 两种Collection 又包含了 List、Set 以及 Queue 容器主要包括 Collection 和 Map 两种Collection 存储着对象的集合,而 Map 存储着键值对(两个对象)的映射表
## Collection ## Collection
<div align="center"> <img src="../pics//NP4z3i8m38Ntd28NQ4_0KCJ2q044Oez.png"/> </div><br> <div align="center"> <img src="../pics//VP4n3i8m34Ntd28NQ4_0KCJ2q044Oez.png"/> </div><br>
### 1. Set ### 1. Set
- HashSet基于哈希表实现支持快速查找。但不支持有序性操作例如根据一个范围查找元素的操作。并且失去了元素的插入顺序信息也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的 - TreeSet基于红黑树实现支持有序性操作例如根据一个范围查找元素的操作。但是查找效率不如 HashSetHashSet 查找的时间复杂度为 O(1)TreeSet 则为 O(logN)
- TreeSet基于红黑树实现支持有序性操作但是查找效率不如 HashSetHashSet 查找时间复杂度为 O(1)TreeSet 则为 O(logN) - HashSet基于哈希表实现支持快速查找但不支持有序性操作。并且失去了元素的插入顺序信息也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的
- LinkedHashSet具有 HashSet 的查找效率,且内部使用双向链表维护元素的插入顺序。 - LinkedHashSet具有 HashSet 的查找效率,且内部使用双向链表维护元素的插入顺序。
@ -53,13 +53,14 @@
<div align="center"> <img src="../pics//SoWkIImgAStDuUBAp2j9BKfBJ4vLy4q.png"/> </div><br> <div align="center"> <img src="../pics//SoWkIImgAStDuUBAp2j9BKfBJ4vLy4q.png"/> </div><br>
- HashMap基于哈希表实现 - TreeMap基于红黑树实现。
- HashMap基于哈希表实现。
- HashTable和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入 HashTable 并且不会导致数据不一致。它是遗留类,不应该去使用它。现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。 - HashTable和 HashMap 类似,但它是线程安全的,这意味着同一时刻多个线程可以同时写入 HashTable 并且不会导致数据不一致。它是遗留类,不应该去使用它。现在可以使用 ConcurrentHashMap 来支持线程安全,并且 ConcurrentHashMap 的效率会更高,因为 ConcurrentHashMap 引入了分段锁。
- LinkedHashMap使用双向链表来维护元素的顺序顺序为插入顺序或者最近最少使用LRU顺序。 - LinkedHashMap使用双向链表来维护元素的顺序顺序为插入顺序或者最近最少使用LRU顺序。
- TreeMap基于红黑树实现。
# 二、容器中的设计模式 # 二、容器中的设计模式
@ -129,12 +130,67 @@ private static final int DEFAULT_CAPACITY = 10;
ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。 ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。
保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。 保存元素的数组 elementData 使用 transient 修饰,该关键字声明数组默认不会被序列化。
```java ```java
transient Object[] elementData; // non-private to simplify nested class access transient Object[] elementData; // non-private to simplify nested class access
``` ```
ArrayList 实现了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。
```java
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
```
```java
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
```
序列化时需要使用 ObjectOutputStream 的 writeObject() 将对象转换为字节流并输出。而 writeObject() 方法在传入的对象存在 writeObject() 的时候会去反射调用该对象的 writeObject() 来实现序列化。反序列化使用的是 ObjectInputStream 的 readObject() 方法,原理类似。
```java
ArrayList list = new ArrayList();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(list);
```
### 3. 扩容 ### 3. 扩容
添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 `oldCapacity + (oldCapacity >> 1)`,也就是旧容量的 1.5 倍。 添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 `oldCapacity + (oldCapacity >> 1)`,也就是旧容量的 1.5 倍。
@ -241,14 +297,14 @@ public synchronized E get(int index) {
} }
``` ```
### 2. 与 ArrayList 的区别 ### 2. 与 ArrayList 的比较
- Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector因为同步操作完全可以由程序员自己来控制 - Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector因为同步操作完全可以由程序员自己来控制
- Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。 - Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。
### 3. 替代方案 ### 3. 替代方案
为了获得线程安全的 ArrayList可以使用 `Collections.synchronizedList();` 得到一个线程安全的 ArrayList。 可以使用 `Collections.synchronizedList();` 得到一个线程安全的 ArrayList。
```java ```java
List<String> list = new ArrayList<>(); List<String> list = new ArrayList<>();
@ -267,7 +323,7 @@ List<String> list = new CopyOnWriteArrayList<>();
写操作在一个复制的数组上进行,读操作还是在原始数组中进行,读写分离,互不影响。 写操作在一个复制的数组上进行,读操作还是在原始数组中进行,读写分离,互不影响。
写操作需要加锁,防止同时并发写入时导致写入数据丢失。 写操作需要加锁,防止并发写入时导致写入数据丢失。
写操作结束之后需要把原始数组指向新的复制数组。 写操作结束之后需要把原始数组指向新的复制数组。
@ -331,9 +387,9 @@ transient Node<E> first;
transient Node<E> last; transient Node<E> last;
``` ```
<div align="center"> <img src="../pics//49495c95-52e5-4c9a-b27b-92cf235ff5ec.png"/> </div><br> <div align="center"> <img src="../pics//49495c95-52e5-4c9a-b27b-92cf235ff5ec.png" width="500"/> </div><br>
### 2. ArrayList 与 LinkedList ### 2. 与 ArrayList 的比较
- ArrayList 基于动态数组实现LinkedList 基于双向链表实现; - ArrayList 基于动态数组实现LinkedList 基于双向链表实现;
- ArrayList 支持随机访问LinkedList 不支持; - ArrayList 支持随机访问LinkedList 不支持;
@ -351,7 +407,7 @@ transient Node<E> last;
transient Entry[] table; transient Entry[] table;
``` ```
其中Entry 就是存储数据的键值对,它包含了四个字段。从 next 字段我们可以看出 Entry 是一个链表,即数组中的每个位置被当成一个桶,一个桶存放一个链表,链表中存放哈希值相同的 Entry也就是说,HashMap 使用拉链法来解决冲突。 Entry 存储着键值对。它包含了四个字段,从 next 字段我们可以看出 Entry 是一个链表。即数组中的每个位置被当成一个桶一个桶存放一个链表。HashMap 使用拉链法来解决冲突,同一个链表中存放哈希值相同的 Entry
<div align="center"> <img src="../pics//8fe838e3-ef77-4f63-bf45-417b6bc5c6bb.png" width="600"/> </div><br> <div align="center"> <img src="../pics//8fe838e3-ef77-4f63-bf45-417b6bc5c6bb.png" width="600"/> </div><br>
@ -579,8 +635,8 @@ y&(x-1) : 00000010
这个性质和 y 对 x 取模效果是一样的: 这个性质和 y 对 x 取模效果是一样的:
``` ```
x : 00010000
y : 10110010 y : 10110010
x : 00010000
y%x : 00000010 y%x : 00000010
``` ```
@ -638,7 +694,7 @@ void addEntry(int hash, K key, V value, int bucketIndex) {
} }
``` ```
扩容使用 resize() 实现,需要注意的是,扩容操作同样需要把旧 table 的所有键值对重新插入新的 table 中,因此这一步是很费时的。 扩容使用 resize() 实现,需要注意的是,扩容操作同样需要把 oldTable 的所有键值对重新插入 newTable 中,因此这一步是很费时的。
```java ```java
void resize(int newCapacity) { void resize(int newCapacity) {
@ -684,7 +740,10 @@ capacity : 00010000
new capacity : 00100000 new capacity : 00100000
``` ```
对于一个 Key它的哈希值如果在第 6 位上为 0那么取模得到的结果和之前一样如果为 1那么得到的结果为原来的结果 +16。 对于一个 Key
- 它的哈希值如果在第 5 位上为 0那么取模得到的结果和之前一样
- 如果为 1那么得到的结果为原来的结果 +16。
### 7. 扩容-计算数组容量 ### 7. 扩容-计算数组容量
@ -723,7 +782,7 @@ static final int tableSizeFor(int cap) {
从 JDK 1.8 开始,一个桶存储的链表长度大于 8 时会将链表转换为红黑树。 从 JDK 1.8 开始,一个桶存储的链表长度大于 8 时会将链表转换为红黑树。
### 9. HashMap 与 HashTable ### 9. 与 HashTable 的比较
- HashTable 使用 synchronized 来进行同步。 - HashTable 使用 synchronized 来进行同步。
- HashMap 可以插入键为 null 的 Entry。 - HashMap 可以插入键为 null 的 Entry。
@ -884,7 +943,7 @@ transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail; transient LinkedHashMap.Entry<K,V> tail;
``` ```
accessOrder 决定了顺序,默认为 false此时使用的是插入顺序。 accessOrder 决定了顺序,默认为 false此时维护的是插入顺序。
```java ```java
final boolean accessOrder; final boolean accessOrder;
@ -899,7 +958,7 @@ void afterNodeInsertion(boolean evict) { }
### afterNodeAccess() ### afterNodeAccess()
当一个节点被访问时,如果 accessOrder 为 true则会将 该节点移到链表尾部。也就是说指定为 LRU 顺序之后,在每次访问一个节点时,会将这个节点移到链表尾部,保证链表尾部是最近访问的节点,那么链表首部就是最近最久未使用的节点。 当一个节点被访问时,如果 accessOrder 为 true则会将该节点移到链表尾部。也就是说指定为 LRU 顺序之后,在每次访问一个节点时,会将这个节点移到链表尾部,保证链表尾部是最近访问的节点,那么链表首部就是最近最久未使用的节点。
```java ```java
void afterNodeAccess(Node<K,V> e) { // move node to last void afterNodeAccess(Node<K,V> e) { // move node to last
@ -948,8 +1007,8 @@ removeEldestEntry() 默认为 false如果需要让它为 true需要继承
```java ```java
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
return false; return false;
} }
``` ```
### LRU 缓存 ### LRU 缓存
@ -957,7 +1016,7 @@ protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
以下是使用 LinkedHashMap 实现的一个 LRU 缓存: 以下是使用 LinkedHashMap 实现的一个 LRU 缓存:
- 设定最大缓存空间 MAX_ENTRIES 为 3 - 设定最大缓存空间 MAX_ENTRIES 为 3
- 使用 LinkedHashMap 的构造函数将 accessOrder 设置为 true开启 LUR 顺序; - 使用 LinkedHashMap 的构造函数将 accessOrder 设置为 true开启 LRU 顺序;
- 覆盖 removeEldestEntry() 方法实现,在节点多于 MAX_ENTRIES 就会将最近最久未使用的数据移除。 - 覆盖 removeEldestEntry() 方法实现,在节点多于 MAX_ENTRIES 就会将最近最久未使用的数据移除。
```java ```java
@ -1004,14 +1063,14 @@ private static class Entry<K,V> extends WeakReference<Object> implements Map.Ent
### ConcurrentCache ### ConcurrentCache
Tomcat 中的 ConcurrentCache 使用了 WeakHashMap 来实现缓存功能。 Tomcat 中的 ConcurrentCache 使用了 WeakHashMap 来实现缓存功能。
ConcurrentCache 采取的是分代缓存: ConcurrentCache 采取的是分代缓存:
- 经常使用的对象放入 eden 中eden 使用 ConcurrentHashMap 实现,不用担心会被回收(伊甸园); - 经常使用的对象放入 eden 中eden 使用 ConcurrentHashMap 实现,不用担心会被回收(伊甸园);
- 不常用的对象放入 longtermlongterm 使用 WeakHashMap 实现,这些老对象会被垃圾收集器回收。 - 不常用的对象放入 longtermlongterm 使用 WeakHashMap 实现,这些老对象会被垃圾收集器回收。
- 当调用 get() 方法时,会先从 eden 区获取,如果没有找到的话再到 longterm 获取,当从 longterm 获取到就把对象放入 eden 中,保证频繁被访问的节点不容易被回收。 - 当调用 get() 方法时,会先从 eden 区获取,如果没有找到的话再到 longterm 获取,当从 longterm 获取到就把对象放入 eden 中,从而保证经常被访问的节点不容易被回收。
- 当调用 put() 方法时,如果缓存当前容量大小超过了 size那么就将 eden 中的所有对象都放入 longterm 中,利用虚拟机回收掉一部分不经常使用的对象。 - 当调用 put() 方法时,如果 eden 的大小超过了 size那么就将 eden 中的所有对象都放入 longterm 中,利用虚拟机回收掉一部分不经常使用的对象。
```java ```java
public final class ConcurrentCache<K, V> { public final class ConcurrentCache<K, V> {
@ -1080,7 +1139,7 @@ Set <|.. LinkedHashSet
SortSet <|.. TreeSet SortSet <|.. TreeSet
List <|.. ArrayList List <|.. ArrayList
List <|.. Vector List <|.. Vector
List <|.. LinkeList List <|.. LinkedList
Queue <|.. LinkedList Queue <|.. LinkedList
Queue <|.. PriorityQueue Queue <|.. PriorityQueue

View File

@ -168,6 +168,8 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc
同样也是需要实现 run() 方法,因为 Thread 类也实现了 Runable 接口。 同样也是需要实现 run() 方法,因为 Thread 类也实现了 Runable 接口。
当调用 start() 方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 run() 方法。
```java ```java
public class MyThread extends Thread { public class MyThread extends Thread {
public void run() { public void run() {
@ -182,7 +184,10 @@ public static void main(String[] args) {
mt.start(); mt.start();
} }
``` ```
## 三种实现方式的比较
- 实现Runnable接又可以避免Java单继承特性而带来的局限;增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;适合多个相同程序代码的线程区处理同一资源的情况。
- 继承Thread类和实现Runnable方法启动线 程都是使用start方法然后JVM虚拟机将此线程放到就绪队列中如果有处理机可用 则执行run方法。
- 实现Callable接又要实现call方法并且线 程执行完毕后会有返回值。其他的两种都是 重写run方法没有返回值。
## 实现接口 VS 继承 Thread ## 实现接口 VS 继承 Thread
实现接口会更好一些,因为: 实现接口会更好一些,因为:
@ -733,7 +738,6 @@ java.util.concurrentJ.U.C大大提高了并发性能AQS 被认为是 J.
```java ```java
public class CountdownLatchExample { public class CountdownLatchExample {
public static void main(String[] args) throws InterruptedException { public static void main(String[] args) throws InterruptedException {
final int totalThread = 10; final int totalThread = 10;
CountDownLatch countDownLatch = new CountDownLatch(totalThread); CountDownLatch countDownLatch = new CountDownLatch(totalThread);
@ -759,15 +763,30 @@ run..run..run..run..run..run..run..run..run..run..end
用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。 用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。
和 CountdownLatch 相似,都是通过维护计数器来实现的。但是它的计数器是递增的,每次执行 await() 方法之后,计数器会加 1直到计数器的值和设置的值相等等待的所有线程才会继续执行。和 CountdownLatch 的另一个区别是CyclicBarrier 的计数器可以循环使用,所以它才叫做循环屏障 和 CountdownLatch 相似,都是通过维护计数器来实现的。线程执行 await() 方法之后计数器会减 1并进行等待直到计数器为 0所有调用 awati() 方法而在等待的线程才能继续执行
下图应该从下往上看才正确。 CyclicBarrier 和 CountdownLatch 的一个区别是CyclicBarrier 的计数器通过调用 reset() 方法可以循环使用,所以它才叫做循环屏障。
CyclicBarrier 有两个构造函数,其中 parties 指示计数器的初始值barrierAction 在所有线程都到达屏障的时候会执行一次。
```java
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
public CyclicBarrier(int parties) {
this(parties, null);
}
```
<div align="center"> <img src="../pics//CyclicBarrier.png" width=""/> </div><br> <div align="center"> <img src="../pics//CyclicBarrier.png" width=""/> </div><br>
```java ```java
public class CyclicBarrierExample { public class CyclicBarrierExample {
public static void main(String[] args) throws InterruptedException { public static void main(String[] args) {
final int totalThread = 10; final int totalThread = 10;
CyclicBarrier cyclicBarrier = new CyclicBarrier(totalThread); CyclicBarrier cyclicBarrier = new CyclicBarrier(totalThread);
ExecutorService executorService = Executors.newCachedThreadPool(); ExecutorService executorService = Executors.newCachedThreadPool();
@ -776,9 +795,7 @@ public class CyclicBarrierExample {
System.out.print("before.."); System.out.print("before..");
try { try {
cyclicBarrier.await(); cyclicBarrier.await();
} catch (InterruptedException e) { } catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace(); e.printStackTrace();
} }
System.out.print("after.."); System.out.print("after..");
@ -1198,8 +1215,6 @@ volatile 关键字通过添加内存屏障的方式来禁止指令重排,即
上面提到了可以用 volatile 和 synchronized 来保证有序性。除此之外JVM 还规定了先行发生原则,让一个操作无需控制就能先于另一个操作完成。 上面提到了可以用 volatile 和 synchronized 来保证有序性。除此之外JVM 还规定了先行发生原则,让一个操作无需控制就能先于另一个操作完成。
主要有以下这些原则:
### 1. 单一线程原则 ### 1. 单一线程原则
> Single Thread rule > Single Thread rule
@ -1270,14 +1285,16 @@ Thread 对象的结束先行发生于 join() 方法返回。
### 1. 不可变 ### 1. 不可变
不可变Immutable的对象一定是线程安全的无论是对象的方法实现还是方法的调用者都不需要再采取任何的线程安全保障措施只要一个不可变的对象被正确地构建出来那其外部的可见状态永远也不会改变永远也不会看到它在多个线程之中处于不一致的状态。 不可变Immutable的对象一定是线程安全的不需要再采取任何的线程安全保障措施。只要一个不可变的对象被正确地构建出来永远也不会看到它在多个线程之中处于不一致的状态。
多线程环境下,应当尽量使对象成为不可变,来满足线程安全。
不可变的类型: 不可变的类型:
- final 关键字修饰的基本数据类型 - final 关键字修饰的基本数据类型
- String - String
- 枚举类型 - 枚举类型
- Number 部分子类,如 Long 和 Double 等数值包装类型BigInteger 和 BigDecimal 等大数据类型。但同为 Number 的子类型的原子类 AtomicInteger 和 AtomicLong 则并非不可变的。 - Number 部分子类,如 Long 和 Double 等数值包装类型BigInteger 和 BigDecimal 等大数据类型。但同为 Number 的原子类 AtomicInteger 和 AtomicLong 则是可变的。
对于集合类型,可以使用 Collections.unmodifiableXXX() 方法来获取一个不可变的集合。 对于集合类型,可以使用 Collections.unmodifiableXXX() 方法来获取一个不可变的集合。
@ -1305,21 +1322,19 @@ public V put(K key, V value) {
} }
``` ```
多线程环境下,应当尽量使对象成为不可变,来满足线程安全。
### 2. 绝对线程安全 ### 2. 绝对线程安全
不管运行时环境如何,调用者都不需要任何额外的同步措施。 不管运行时环境如何,调用者都不需要任何额外的同步措施。
### 3. 相对线程安全 ### 3. 相对线程安全
相对线程安全需要保证对这个对象单独的操作是线程安全的,在调用的时候不需要做额外的保障措施但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。 相对线程安全需要保证对这个对象单独的操作是线程安全的,在调用的时候不需要做额外的保障措施但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。
在 Java 语言中,大部分的线程安全类都属于这种类型,例如 Vector、HashTable、Collections 的 synchronizedCollection() 方法包装的集合等。 在 Java 语言中,大部分的线程安全类都属于这种类型,例如 Vector、HashTable、Collections 的 synchronizedCollection() 方法包装的集合等。
对于下面的代码,如果删除元素的线程删除了一个元素,而获取元素的线程试图访问一个已经被删除的元素,那么就会抛出 ArrayIndexOutOfBoundsException。 对于下面的代码,如果删除元素的线程删除了 Vector 的一个元素,而获取元素的线程试图访问一个已经被删除的元素,那么就会抛出 ArrayIndexOutOfBoundsException。
```java ```Java
public class VectorUnsafeExample { public class VectorUnsafeExample {
private static Vector<Integer> vector = new Vector<>(); private static Vector<Integer> vector = new Vector<>();
@ -1389,15 +1404,17 @@ synchronized 和 ReentrantLock。
### 2. 非阻塞同步 ### 2. 非阻塞同步
互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。 互斥同步最主要的问题就是线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。
互斥同步属于一种悲观的并发策略,总是认为只要不去做正确的同步措施,那就肯定会出现问题。论共享数据是否真的会出现竞争,它都要进行加锁(这里讨论的是概念模型,实际上虚拟机会优化掉很大一部分不必要的加锁)、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒等操作。 互斥同步属于一种悲观的并发策略,总是认为只要不去做正确的同步措施,那就肯定会出现问题。论共享数据是否真的会出现竞争,它都要进行加锁(这里讨论的是概念模型,实际上虚拟机会优化掉很大一部分不必要的加锁)、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒等操作。
随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略:先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要把线程挂起,因此这种同步操作称为非阻塞同步。 **CAS**
乐观锁需要操作和冲突检测这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,只能靠硬件来完成 随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略:先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步
硬件支持的原子性操作最典型的是比较并交换Compare-and-SwapCAS。CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时只有当 V 的值等于 A才将 V 的值更新为 B。 乐观锁需要操作和冲突检测这两个步骤具备原子性这里就不能再使用互斥同步来保证了只能靠硬件来完成。硬件支持的原子性操作最典型的是比较并交换Compare-and-SwapCAS。CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时只有当 V 的值等于 A才将 V 的值更新为 B。
**AtomicInteger**
J.U.C 包里面的整数原子类 AtomicInteger其中的 compareAndSet() 和 getAndIncrement() 等方法都使用了 Unsafe 类的 CAS 操作。 J.U.C 包里面的整数原子类 AtomicInteger其中的 compareAndSet() 和 getAndIncrement() 等方法都使用了 Unsafe 类的 CAS 操作。
@ -1419,7 +1436,7 @@ public final int incrementAndGet() {
} }
``` ```
以下代码是 getAndAddInt() 源码var1 指示对象内存地址var2 指示该字段相对对象内存地址的偏移var4 指示操作需要加的数值,这里为 1。通过 getIntVolatile(var1, var2) 得到旧的预期值,通过调用 compareAndSwapInt() 来进行 CAS 比较,如果该字段内存地址中的值 ==var5那么就更新内存地址为 var1+var2 的变量为 var5+var4。 以下代码是 getAndAddInt() 源码var1 指示对象内存地址var2 指示该字段相对对象内存地址的偏移var4 指示操作需要加的数值,这里为 1。通过 getIntVolatile(var1, var2) 得到旧的预期值,通过调用 compareAndSwapInt() 来进行 CAS 比较,如果该字段内存地址中的值等于 var5那么就更新内存地址为 var1+var2 的变量为 var5+var4。
可以看到 getAndAddInt() 在一个循环中进行,发生冲突的做法是不断的进行重试。 可以看到 getAndAddInt() 在一个循环中进行,发生冲突的做法是不断的进行重试。
@ -1434,23 +1451,19 @@ public final int getAndAddInt(Object var1, long var2, int var4) {
} }
``` ```
ABA :如果一个变量初次读取的时候是 A 值,它的值被改成了 B后来又被改回为 A那 CAS 操作就会误认为它从来没有被改变过。 **ABA**
如果一个变量初次读取的时候是 A 值,它的值被改成了 B后来又被改回为 A那 CAS 操作就会误认为它从来没有被改变过。
J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题,它可以通过控制变量值的版本来保证 CAS 的正确性。大部分情况下 ABA 问题不会影响程序并发的正确性,如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效。 J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题,它可以通过控制变量值的版本来保证 CAS 的正确性。大部分情况下 ABA 问题不会影响程序并发的正确性,如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效。
### 3. 无同步方案 ### 3. 无同步方案
要保证线程安全,并不是一定就要进行同步,两者没有因果关系同步只是保证共享数据争用时的正确性的手段,如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性,因此会有一些代码天生就是线程安全的 要保证线程安全,并不是一定就要进行同步。如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性。
**(一)可重入代码Reentrant Code** **(一)栈封闭**
这种代码也叫做纯代码Pure Code可以在代码执行的任何时刻中断它转而去执行另外一段代码包括递归调用它本身而在控制权返回后原来的程序不会出现任何错误。 多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的。
可重入代码有一些共同的特征,例如不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数中传入、不调用非可重入的方法等。
**(二)栈封闭**
多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在栈中,属于线程私有的。
```java ```java
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -1482,11 +1495,11 @@ public static void main(String[] args) {
100 100
``` ```
**线程本地存储Thread Local Storage** **线程本地存储Thread Local Storage**
如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行。如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题。 如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行。如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题。
符合这种特点的应用并不少见,大部分使用消费队列的架构模式(如“生产者-消费者”模式)都会将产品的消费过程尽量在一个线程中消费完其中最重要的一个应用实例就是经典 Web 交互模型中的“一个请求对应一个服务器线程”Thread-per-Request的处理方式这种处理方式的广泛应用使得很多 Web 服务端应用都可以使用线程本地存储来解决线程安全问题。 符合这种特点的应用并不少见,大部分使用消费队列的架构模式(如“生产者-消费者”模式)都会将产品的消费过程尽量在一个线程中消费完其中最重要的一个应用实例就是经典 Web 交互模型中的“一个请求对应一个服务器线程”Thread-per-Request的处理方式这种处理方式的广泛应用使得很多 Web 服务端应用都可以使用线程本地存储来解决线程安全问题。
可以使用 java.lang.ThreadLocal 类来实现线程本地存储功能。 可以使用 java.lang.ThreadLocal 类来实现线程本地存储功能。
@ -1584,17 +1597,25 @@ public T get() {
} }
``` ```
ThreadLocal 从理论上讲并不是用来解决多线程并发问题的,因为根本不存在多线程竞争。在一些场景 (尤其是使用线程池) 下,由于 ThreadLocal.ThreadLocalMap 的底层数据结构导致 ThreadLocal 有内存泄漏的情况,尽可能在每次使用 ThreadLocal 后手动调用 remove(),以避免出现 ThreadLocal 经典的内存泄漏甚至是造成自身业务混乱的风险。 ThreadLocal 从理论上讲并不是用来解决多线程并发问题的,因为根本不存在多线程竞争。
在一些场景 (尤其是使用线程池) 下,由于 ThreadLocal.ThreadLocalMap 的底层数据结构导致 ThreadLocal 有内存泄漏的情况,应该尽可能在每次使用 ThreadLocal 后手动调用 remove(),以避免出现 ThreadLocal 经典的内存泄漏甚至是造成自身业务混乱的风险。
**可重入代码Reentrant Code**
这种代码也叫做纯代码Pure Code可以在代码执行的任何时刻中断它转而去执行另外一段代码包括递归调用它本身而在控制权返回后原来的程序不会出现任何错误。
可重入代码有一些共同的特征,例如不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数中传入、不调用非可重入的方法等。
# 十二、锁优化 # 十二、锁优化
这里的锁优化主要是指虚拟机对 synchronized 的优化。 这里的锁优化主要是指 JVM 对 synchronized 的优化。
## 自旋锁 ## 自旋锁
互斥同步的进入阻塞状态的开销都很大,应该尽量避免。在许多应用中,共享数据的锁定状态只会持续很短的一段时间。自旋锁的思想是让一个线程在请求一个共享数据的锁时执行忙循环(自旋)一段时间,如果在这段时间内能获得锁,就可以避免进入阻塞状态。 互斥同步进入阻塞状态的开销都很大,应该尽量避免。在许多应用中,共享数据的锁定状态只会持续很短的一段时间。自旋锁的思想是让一个线程在请求一个共享数据的锁时执行忙循环(自旋)一段时间,如果在这段时间内能获得锁,就可以避免进入阻塞状态。
锁虽然能避免进入阻塞状态从而减少开销,但是它需要进行忙循环操作占用 CPU 时间,它只适用于共享数据的锁定状态很短的场景。 锁虽然能避免进入阻塞状态从而减少开销,但是它需要进行忙循环操作占用 CPU 时间,它只适用于共享数据的锁定状态很短的场景。
在 JDK 1.6 中引入了自适应的自旋锁。自适应意味着自旋的次数不再固定了,而是由前一次在同一个锁上的自旋次数及锁的拥有者的状态来决定。 在 JDK 1.6 中引入了自适应的自旋锁。自适应意味着自旋的次数不再固定了,而是由前一次在同一个锁上的自旋次数及锁的拥有者的状态来决定。
@ -1624,7 +1645,7 @@ public static String concatString(String s1, String s2, String s3) {
} }
``` ```
每个 append() 方法中都有一个同步块。虚拟机观察变量 sb很快就会发现它的动态作用域被限制在 concatString() 方法内部。也就是说sb 的所有引用永远不会逃逸到 concatString() 方法之外,其他线程无法访问到它,因此可以进行消除。 每个 append() 方法中都有一个同步块。虚拟机观察变量 sb很快就会发现它的动态作用域被限制在 concatString() 方法内部。也就是说sb 的所有引用永远不会逃逸到 concatString() 方法之外,其他线程无法访问到它,因此可以进行消除。
## 锁粗化 ## 锁粗化
@ -1636,9 +1657,9 @@ public static String concatString(String s1, String s2, String s3) {
JDK 1.6 引入了偏向锁和轻量级锁从而让锁拥有了四个状态无锁状态unlocked、偏向锁状态biasble、轻量级锁状态lightweight locked和重量级锁状态inflated JDK 1.6 引入了偏向锁和轻量级锁从而让锁拥有了四个状态无锁状态unlocked、偏向锁状态biasble、轻量级锁状态lightweight locked和重量级锁状态inflated
以下是 HotSpot 虚拟机对象头的内存布局,这些数据被称为 mark word。其中 tag bits 对应了五个状态,这些状态在右侧的 state 表格中给出,应该注意的是 state 表格不是存储在对象头中的。除了 marked for gc 状态,其它四个状态已经在前面介绍过了。 以下是 HotSpot 虚拟机对象头的内存布局,这些数据被称为 Mark Word。其中 tag bits 对应了五个状态,这些状态在右侧的 state 表格中给出。除了 marked for gc 状态,其它四个状态已经在前面介绍过了。
<div align="center"> <img src="../pics//bb6a49be-00f2-4f27-a0ce-4ed764bc605c.png" width="600"/> </div><br> <div align="center"> <img src="../pics//bb6a49be-00f2-4f27-a0ce-4ed764bc605c.png" width="500"/> </div><br>
下图左侧是一个线程的虚拟机栈,其中有一部分称为 Lock Record 的区域,这是在轻量级锁运行过程创建的,用于存放锁对象的 Mark Word。而右侧就是一个锁对象包含了 Mark Word 和其它信息。 下图左侧是一个线程的虚拟机栈,其中有一部分称为 Lock Record 的区域,这是在轻量级锁运行过程创建的,用于存放锁对象的 Mark Word。而右侧就是一个锁对象包含了 Mark Word 和其它信息。
@ -1646,9 +1667,9 @@ JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:
轻量级锁是相对于传统的重量级锁而言,它使用 CAS 操作来避免重量级锁使用互斥量的开销。对于绝大部分的锁,在整个同步周期内都是不存在竞争的,因此也就不需要都使用互斥量进行同步,可以先采用 CAS 操作进行同步,如果 CAS 失败了再改用互斥量进行同步。 轻量级锁是相对于传统的重量级锁而言,它使用 CAS 操作来避免重量级锁使用互斥量的开销。对于绝大部分的锁,在整个同步周期内都是不存在竞争的,因此也就不需要都使用互斥量进行同步,可以先采用 CAS 操作进行同步,如果 CAS 失败了再改用互斥量进行同步。
当尝试获取一个锁对象时,如果锁对象标记为 0 01说明锁对象的锁未锁定unlocked状态。此时虚拟机在当前线程栈中创建 Lock Record然后使用 CAS 操作将对象的 Mark Word 更新为 Lock Record 指针。如果 CAS 操作成功了,那么线程就获取了该对象上的锁,并且对象的 Mark Word 的锁标记变为 00表示该对象处于轻量级锁状态。 当尝试获取一个锁对象时,如果锁对象标记为 0 01说明锁对象的锁未锁定unlocked状态。此时虚拟机在当前线程的虚拟机栈中创建 Lock Record然后使用 CAS 操作将对象的 Mark Word 更新为 Lock Record 指针。如果 CAS 操作成功了,那么线程就获取了该对象上的锁,并且对象的 Mark Word 的锁标记变为 00表示该对象处于轻量级锁状态。
<div align="center"> <img src="../pics//baaa681f-7c52-4198-a5ae-303b9386cf47.png" width="500"/> </div><br> <div align="center"> <img src="../pics//baaa681f-7c52-4198-a5ae-303b9386cf47.png" width="400"/> </div><br>
如果 CAS 操作失败了,虚拟机首先会检查对象的 Mark Word 是否指向当前线程的虚拟机栈,如果是的话说明当前线程已经拥有了这个锁对象,那就可以直接进入同步块继续执行,否则说明这个锁对象已经被其他线程线程抢占了。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁。 如果 CAS 操作失败了,虚拟机首先会检查对象的 Mark Word 是否指向当前线程的虚拟机栈,如果是的话说明当前线程已经拥有了这个锁对象,那就可以直接进入同步块继续执行,否则说明这个锁对象已经被其他线程线程抢占了。如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁。
@ -1666,9 +1687,9 @@ JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:
- 给线程起个有意义的名字,这样可以方便找 Bug。 - 给线程起个有意义的名字,这样可以方便找 Bug。
- 缩小同步范围,例如对于 synchronized应该尽量使用同步块而不是同步方法。 - 缩小同步范围,从而减少锁争用。例如对于 synchronized应该尽量使用同步块而不是同步方法。
- 多用同步少用 wait() 和 notify()。首先CountDownLatch, CyclicBarrier, Semaphore 和 Exchanger 这些同步类简化了编码操作,而用 wait() 和 notify() 很难实现复杂控制流;其次,这些同步类是由最好的企业编写和维护,在后续的 JDK 中还会不断优化和完善,使用这些更高等级的同步工具你的程序可以不费吹灰之力获得优化。 - 多用同步工具少用 wait() 和 notify()。首先CountDownLatch, CyclicBarrier, Semaphore 和 Exchanger 这些同步类简化了编码操作,而用 wait() 和 notify() 很难实现复杂控制流;其次,这些同步类是由最好的企业编写和维护,在后续的 JDK 中还会不断优化和完善,使用这些更高等级的同步工具你的程序可以不费吹灰之力获得优化。
- 多用并发集合少用同步集合,例如应该使用 ConcurrentHashMap 而不是 Hashtable。 - 多用并发集合少用同步集合,例如应该使用 ConcurrentHashMap 而不是 Hashtable。

View File

@ -8,15 +8,22 @@
* [运行时常量池](#运行时常量池) * [运行时常量池](#运行时常量池)
* [直接内存](#直接内存) * [直接内存](#直接内存)
* [二、垃圾收集](#二垃圾收集) * [二、垃圾收集](#二垃圾收集)
* [判断一个对象是否存活](#判断一个对象是否存活) * [判断一个对象是否可被回收](#判断一个对象是否可被回收)
* [引用类型](#引用类型)
* [垃圾收集算法](#垃圾收集算法) * [垃圾收集算法](#垃圾收集算法)
* [垃圾收集器](#垃圾收集器) * [垃圾收集器](#垃圾收集器)
* [内存分配与回收策略](#内存分配与回收策略) * [三、内存分配与回收策略](#三内存分配与回收策略)
* [三、类加载机制](#三类加载机制) * [Minor GC 和 Full GC](#minor-gc-和-full-gc)
* [内存分配策略](#内存分配策略)
* [Full GC 的触发条件](#full-gc-的触发条件)
* [四、类加载机制](#四类加载机制)
* [类的生命周期](#类的生命周期) * [类的生命周期](#类的生命周期)
* [类初始化时机](#类初始化时机)
* [类加载过程](#类加载过程) * [类加载过程](#类加载过程)
* [类加载器](#类加载器) * [类初始化时机](#类初始化时机)
* [类与类加载器](#类与类加载器)
* [类加载器分类](#类加载器分类)
* [双亲委派模型](#双亲委派模型)
* [自定义类加载器实现](#自定义类加载器实现)
* [参考资料](#参考资料) * [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
@ -38,7 +45,7 @@
可以通过 -Xss 这个虚拟机参数来指定每个线程的 Java 虚拟机栈内存大小: 可以通过 -Xss 这个虚拟机参数来指定每个线程的 Java 虚拟机栈内存大小:
```java ```java
java -Xss=512M HackTheJava java -Xss512M HackTheJava
``` ```
该区域可能抛出以下异常: 该区域可能抛出以下异常:
@ -48,19 +55,17 @@ java -Xss=512M HackTheJava
## 本地方法栈 ## 本地方法栈
本地方法不是用 Java 实现,对待这些方法需要特别处理。 本地方法一般是用其它语言C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。
与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。 本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
本地方法一般是用其它语言C、C++ 或汇编语言等)编写的, 并且被编译为基于本机硬件和操作系统的程序。
<div align="center"> <img src="../pics//JNI-Java-Native-Interface.jpg" width="350"/> </div><br> <div align="center"> <img src="../pics//JNI-Java-Native-Interface.jpg" width="350"/> </div><br>
## 堆 ## 堆
所有对象实例都在这里分配内存,是垃圾收集的主要区域("GC 堆")。 所有对象都在这里分配内存,是垃圾收集的主要区域("GC 堆")。
现代的垃圾收集器基本都是采用分代收集算法,针对不同的对象采取不同的垃圾回收算法,可以将堆分成两块: 现代的垃圾收集器基本都是采用分代收集算法,针对不同类型的对象采取不同的垃圾回收算法,可以将堆分成两块:
- 新生代Young Generation - 新生代Young Generation
- 老年代Old Generation - 老年代Old Generation
@ -76,16 +81,14 @@ java -Xss=512M HackTheJava
可以通过 -Xms 和 -Xmx 两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。 可以通过 -Xms 和 -Xmx 两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。
```java ```java
java -Xms=1M -Xmx=2M HackTheJava java -Xms1M -Xmx2M HackTheJava
``` ```
## 方法区 ## 方法区
用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
和堆一样不需要连续的内存,并且可以动态扩展。 和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。
动态扩展失败一样会抛出 OutOfMemoryError 异常。
对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。 对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。
@ -107,15 +110,15 @@ Class 文件中的常量池(编译器生成的各种字面量和符号引用
# 二、垃圾收集 # 二、垃圾收集
垃圾收主要是针对堆和方法区进行。 垃圾收主要是针对堆和方法区进行。
程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后也会消失,因此不需要对这三个区域进行垃圾回收。 程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后也会消失,因此不需要对这三个区域进行垃圾回收。
## 判断一个对象是否存活 ## 判断一个对象是否可被回收
### 1. 引用计数算法 ### 1. 引用计数算法
给对象添加一个引用计数器,当对象增加一个引用时计数器加 1引用失效时计数器减 1。引用计数不为 0 的对象仍然存活 给对象添加一个引用计数器,当对象增加一个引用时计数器加 1引用失效时计数器减 1。引用计数为 0 的对象可被回收
两个对象出现循环引用的情况下,此时引用计数器永远不为 0导致无法对它们进行回收。 两个对象出现循环引用的情况下,此时引用计数器永远不为 0导致无法对它们进行回收。
@ -123,6 +126,7 @@ Class 文件中的常量池(编译器生成的各种字面量和符号引用
```java ```java
public class ReferenceCountingGC { public class ReferenceCountingGC {
public Object instance = null; public Object instance = null;
public static void main(String[] args) { public static void main(String[] args) {
@ -147,61 +151,7 @@ Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC
- 方法区中类静态属性引用的对象 - 方法区中类静态属性引用的对象
- 方法区中的常量引用的对象 - 方法区中的常量引用的对象
### 3. 引用类型 ### 3. 方法区的回收
无论是通过引用计算算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判定对象是否可被回收都与引用有关。
Java 具有四种强度不同的引用类型。
**(一)强引用**
被强引用关联的对象不会被回收。
使用 new 一个新对象的方式来创建强引用。
```java
Object obj = new Object();
```
**(二)软引用**
被软引用关联的对象只有在内存不够的情况下才会被回收。
使用 SoftReference 类来创建软引用。
```java
Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null; // 使对象只被软引用关联
```
**(三)弱引用**
被弱引用关联的对象一定会被回收,也就是说它只能存活到下一次垃圾收集。
使用 WeakReference 类来实现弱引用。
```java
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
```
**(四)虚引用**
又称为幽灵引用或者幻影引用。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象实例。
为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收时收到一个系统通知。
使用 PhantomReference 来实现虚引用。
```java
Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj = null;
```
### 4. 方法区的回收
因为方法区主要存放永久代对象,而永久代对象的回收率比新生代低很多,因此在方法区上进行回收性价比不高。 因为方法区主要存放永久代对象,而永久代对象的回收率比新生代低很多,因此在方法区上进行回收性价比不高。
@ -217,12 +167,66 @@ obj = null;
可以通过 -Xnoclassgc 参数来控制是否对类进行卸载。 可以通过 -Xnoclassgc 参数来控制是否对类进行卸载。
### 5. finalize() ### 4. finalize()
finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。但是 try-finally 等方式可以做的更好,并且该方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。 finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。但是 try-finally 等方式可以做的更好,并且该方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。
当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能通过在该方法中让对象重新被引用,从而实现自救。自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会调用 finalize() 方法。 当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能通过在该方法中让对象重新被引用,从而实现自救。自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会调用 finalize() 方法。
## 引用类型
无论是通过引用计算算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判定对象是否可被回收都与引用有关。
Java 具有四种强度不同的引用类型。
### 1. 强引用
被强引用关联的对象不会被回收。
使用 new 一个新对象的方式来创建强引用。
```java
Object obj = new Object();
```
### 2. 软引用
被软引用关联的对象只有在内存不够的情况下才会被回收。
使用 SoftReference 类来创建软引用。
```java
Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null; // 使对象只被软引用关联
```
### 3. 弱引用
被弱引用关联的对象一定会被回收,也就是说它只能存活到下一次垃圾回收发生之前。
使用 WeakReference 类来实现弱引用。
```java
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
```
### 4. 虚引用
又称为幽灵引用或者幻影引用。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象。
为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收时收到一个系统通知。
使用 PhantomReference 来实现虚引用。
```java
Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj = null;
```
## 垃圾收集算法 ## 垃圾收集算法
### 1. 标记 - 清除 ### 1. 标记 - 清除
@ -376,20 +380,21 @@ G1 把堆划分成多个大小相等的独立区域Region新生代和
更详细内容请参考:[Getting Started with the G1 Garbage Collector](http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html) 更详细内容请参考:[Getting Started with the G1 Garbage Collector](http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html)
## 内存分配与回收策略 # 三、内存分配与回收策略
### 1. Minor GC 和 Full GC ## Minor GC 和 Full GC
- Minor GC发生在新生代上因为新生代对象存活时间很短因此 Minor GC 会频繁执行,执行的速度一般也会比较快。 - Minor GC发生在新生代上因为新生代对象存活时间很短因此 Minor GC 会频繁执行,执行的速度一般也会比较快。
- Full GC发生在老年代上老年代对象其存活时间长因此 Full GC 很少执行,执行速度会比 Minor GC 慢很多。 - Full GC发生在老年代上老年代对象其存活时间长因此 Full GC 很少执行,执行速度会比 Minor GC 慢很多。
### 2. 内存分配策略 ## 内存分配策略
(一)对象优先在 Eden 分配 ### 1. 对象优先在 Eden 分配
大多数情况下,对象在新生代 Eden 区分配,当 Eden 区空间不够时,发起 Minor GC。 大多数情况下,对象在新生代 Eden 区分配,当 Eden 区空间不够时,发起 Minor GC。
(二)大对象直接进入老年代 ### 2. 大对象直接进入老年代
大对象是指需要连续内存空间的对象,最典型的大对象是那种很长的字符串以及数组。 大对象是指需要连续内存空间的对象,最典型的大对象是那种很长的字符串以及数组。
@ -397,41 +402,41 @@ G1 把堆划分成多个大小相等的独立区域Region新生代和
-XX:PretenureSizeThreshold大于此值的对象直接在老年代分配避免在 Eden 区和 Survivor 区之间的大量内存复制。 -XX:PretenureSizeThreshold大于此值的对象直接在老年代分配避免在 Eden 区和 Survivor 区之间的大量内存复制。
(三)长期存活的对象进入老年代 ### 3. 长期存活的对象进入老年代
为对象定义年龄计数器,对象在 Eden 出生并经过 Minor GC 依然存活,将移动到 Survivor 中,年龄就增加 1 岁,增加到一定年龄则移动到老年代中。 为对象定义年龄计数器,对象在 Eden 出生并经过 Minor GC 依然存活,将移动到 Survivor 中,年龄就增加 1 岁,增加到一定年龄则移动到老年代中。
-XX:MaxTenuringThreshold 用来定义年龄的阈值。 -XX:MaxTenuringThreshold 用来定义年龄的阈值。
(四)动态对象年龄判定 ### 4. 动态对象年龄判定
虚拟机并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等到 MaxTenuringThreshold 中要求的年龄。 虚拟机并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等到 MaxTenuringThreshold 中要求的年龄。
(五)空间分配担保 ### 5. 空间分配担保
在发生 Minor GC 之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC 可以确认是安全的。 在发生 Minor GC 之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC 可以确认是安全的。
如果不成立的话虚拟机会查看 HandlePromotionFailure 设置值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC如果小于或者 HandlePromotionFailure 设置不允许冒险,那么就要进行一次 Full GC。 如果不成立的话虚拟机会查看 HandlePromotionFailure 设置值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC如果小于或者 HandlePromotionFailure 设置不允许冒险,那么就要进行一次 Full GC。
### 3. Full GC 的触发条件 ## Full GC 的触发条件
对于 Minor GC其触发条件非常简单当 Eden 空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件: 对于 Minor GC其触发条件非常简单当 Eden 空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件:
(一)调用 System.gc() ### 1. 调用 System.gc()
只是建议虚拟机执行 Full GC但是虚拟机不一定真正去执行。不建议使用这种方式而是让虚拟机管理内存。 只是建议虚拟机执行 Full GC但是虚拟机不一定真正去执行。不建议使用这种方式而是让虚拟机管理内存。
(二)老年代空间不足 ### 2. 老年代空间不足
老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等。 老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等。
为了避免以上原因引起的 Full GC应当尽量不要创建过大的对象以及数组。除此之外可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间。 为了避免以上原因引起的 Full GC应当尽量不要创建过大的对象以及数组。除此之外可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间。
(三)空间分配担保失败 ### 3. 空间分配担保失败
使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC。具体内容请参考上面的第五小节。 使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC。具体内容请参考上面的第五小节。
(四)JDK 1.7 及以前的永久代空间不足 ### 4. JDK 1.7 及以前的永久代空间不足
在 JDK 1.7 及以前HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据。 在 JDK 1.7 及以前HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据。
@ -439,13 +444,13 @@ G1 把堆划分成多个大小相等的独立区域Region新生代和
为避免以上原因引起的 Full GC可采用的方法为增大永久代空间或转为使用 CMS GC。 为避免以上原因引起的 Full GC可采用的方法为增大永久代空间或转为使用 CMS GC。
(五)Concurrent Mode Failure ### 5. Concurrent Mode Failure
执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足),便会报 Concurrent Mode Failure 错误,并触发 Full GC。 执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足),便会报 Concurrent Mode Failure 错误,并触发 Full GC。
# 、类加载机制 # 、类加载机制
类是在运行期间动态加载的。 类是在运行期间第一次使用时动态加载的,而不是编译时期一次性加载。因为如果在编译时期一次性加载,那么会占用很多的内存
## 类的生命周期 ## 类的生命周期
@ -461,10 +466,108 @@ G1 把堆划分成多个大小相等的独立区域Region新生代和
- 使用Using - 使用Using
- 卸载Unloading - 卸载Unloading
## 类加载过程
包含了加载、验证、准备、解析和初始化这 5 个阶段。
### 1. 加载
加载是类加载的一个阶段,注意不要混淆。
加载过程完成以下三件事:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时存储结构。
- 在内存中生成一个代表这个类的 Class 对象,作为方法区这个类的各种数据的访问入口。
其中二进制字节流可以从以下方式中获取:
- 从 ZIP 包读取,成为 JAR、EAR、WAR 格式的基础。
- 从网络中获取,最典型的应用是 Applet。
- 运行时计算生成,例如动态代理技术,在 java.lang.reflect.Proxy 使用 ProxyGenerator.generateProxyClass 的代理类的二进制字节流。
- 由其他文件生成,例如由 JSP 文件生成对应的 Class 类。
### 2. 验证
确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
### 3. 准备
类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。
实例变量不会在这阶段分配内存,它将会在对象实例化时随着对象一起分配在堆中。
注意,实例化不是类加载的一个过程,类加载发生在所有实例化操作之前,并且类加载只进行一次,实例化可以进行多次。
初始值一般为 0 值,例如下面的类变量 value 被初始化为 0 而不是 123。
```java
public static int value = 123;
```
如果类变量是常量,那么会按照表达式来进行初始化,而不是赋值为 0。
```java
public static final int value = 123;
```
### 4. 解析
将常量池的符号引用替换为直接引用的过程。
其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。 其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。
### 5. 初始化
初始化阶段才真正开始执行类中定义的 Java 程序代码。初始化阶段即虚拟机执行类构造器 &lt;clinit>() 方法的过程。
在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。
&lt;clinit>() 方法具有以下特点:
- 是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序决定。特别注意的是,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问。例如以下代码:
```java
public class Test {
static {
i = 0; // 给变量赋值可以正常编译通过
System.out.print(i); // 这句编译器会提示“非法向前引用”
}
static int i = 1;
}
```
- 与类的构造函数(或者说实例构造器 &lt;init>())不同,不需要显式的调用父类的构造器。虚拟机会自动保证在子类的 &lt;clinit>() 方法运行之前,父类的 &lt;clinit>() 方法已经执行结束。因此虚拟机中第一个执行 &lt;clinit>() 方法的类肯定为 java.lang.Object。
- 由于父类的 &lt;clinit>() 方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。例如以下代码:
```java
static class Parent {
public static int A = 1;
static {
A = 2;
}
}
static class Sub extends Parent {
public static int B = A;
}
public static void main(String[] args) {
System.out.println(Sub.B); // 2
}
```
- &lt;clinit>() 方法对于类或接口不是必须的,如果一个类中不包含静态语句块,也没有对类变量的赋值操作,编译器可以不为该类生成 &lt;clinit>() 方法。
- 接口中不可以使用静态语句块,但仍然有类变量初始化的赋值操作,因此接口与类一样都会生成 &lt;clinit>() 方法。但接口与类不同的是,执行接口的 &lt;clinit>() 方法不需要先执行父接口的 &lt;clinit>() 方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的 &lt;clinit>() 方法。
- 虚拟机会保证一个类的 &lt;clinit>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 &lt;clinit>() 方法,其它线程都会阻塞等待,直到活动线程执行 &lt;clinit>() 方法完毕。如果在一个类的 &lt;clinit>() 方法中有耗时的操作,就可能造成多个线程阻塞,在实际过程中此种阻塞很隐蔽。
## 类初始化时机 ## 类初始化时机
### 1. 主动引用
虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随之发生): 虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随之发生):
- 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。 - 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。
@ -477,6 +580,8 @@ G1 把堆划分成多个大小相等的独立区域Region新生代和
- 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化; - 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化;
### 2. 被动引用
以上 5 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的常见例子包括: 以上 5 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的常见例子包括:
- 通过子类引用父类的静态字段,不会导致子类初始化。 - 通过子类引用父类的静态字段,不会导致子类初始化。
@ -497,118 +602,13 @@ SuperClass[] sca = new SuperClass[10];
System.out.println(ConstClass.HELLOWORLD); System.out.println(ConstClass.HELLOWORLD);
``` ```
## 类加载过程 ## 类与类加载器
包含了加载、验证、准备、解析和初始化这 5 个阶段。 两个类相等需要类本身相等,并且使用同一个类加载器进行加载。这是因为每一个类加载器都拥有一个独立的类名称空间。
### 1. 加载
加载是类加载的一个阶段,注意不要混淆。
加载过程完成以下三件事:
- 通过一个类的全限定名来获取定义此类的二进制字节流。
- 将这个字节流所代表的静态存储结构转化为方法区的运行时存储结构。
- 在内存中生成一个代表这个类的 Class 对象,作为方法区这个类的各种数据的访问入口。
其中二进制字节流可以从以下方式中获取:
- 从 ZIP 包读取,这很常见,最终成为日后 JAR、EAR、WAR 格式的基础。
- 从网络中获取,这种场景最典型的应用是 Applet。
- 运行时计算生成,这种场景使用得最多得就是动态代理技术,在 java.lang.reflect.Proxy 中,就是用了 ProxyGenerator.generateProxyClass 的代理类的二进制字节流。
- 由其他文件生成,典型场景是 JSP 应用,即由 JSP 文件生成对应的 Class 类。
- 从数据库读取,这种场景相对少见,例如有些中间件服务器(如 SAP Netweaver可以选择把程序安装到数据库中来完成程序代码在集群间的分发。
...
### 2. 验证
确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
- 文件格式验证:验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。
- 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合 Java 语言规范的要求。
- 字节码验证:通过数据流和控制流分析,确保程序语义是合法、符合逻辑的。
- 符号引用验证:发生在虚拟机将符号引用转换为直接引用的时候,对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。
### 3. 准备
类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。
实例变量不会在这阶段分配内存,它将会在对象实例化时随着对象一起分配在 Java 堆中。(实例化不是类加载的一个过程,类加载发生在所有实例化操作之前,并且类加载只进行一次,实例化可以进行多次)
初始值一般为 0 值,例如下面的类变量 value 被初始化为 0 而不是 123。
```java
public static int value = 123;
```
如果类变量是常量,那么会按照表达式来进行初始化,而不是赋值为 0。
```java
public static final int value = 123;
```
### 4. 解析
将常量池的符号引用替换为直接引用的过程。
### 5. 初始化
初始化阶段才真正开始执行类中的定义的 Java 程序代码。初始化阶段即虚拟机执行类构造器 &lt;clinit>() 方法的过程。
在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。
&lt;clinit>() 方法具有以下特点:
- 是由编译器自动收集类中所有类变量的赋值动作和静态语句块static{} 块)中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序决定。特别注意的是,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问。例如以下代码:
```java
public class Test {
static {
i = 0; // 给变量赋值可以正常编译通过
System.out.print(i); // 这句编译器会提示“非法向前引用”
}
static int i = 1;
}
```
- 与类的构造函数(或者说实例构造器 &lt;init>())不同,不需要显式的调用父类的构造器。虚拟机会自动保证在子类的 &lt;clinit>() 方法运行之前,父类的 &lt;clinit>() 方法已经执行结束。因此虚拟机中第一个执行 &lt;clinit>() 方法的类肯定为 java.lang.Object。
- 由于父类的 &lt;clinit>() 方法先执行,也就意味着父类中定义的静态语句块要优于子类的变量赋值操作。例如以下代码:
```java
static class Parent {
public static int A = 1;
static {
A = 2;
}
}
static class Sub extends Parent {
public static int B = A;
}
public static void main(String[] args) {
System.out.println(Sub.B); // 输出结果是父类中的静态变量 A 的值,也就是 2。
}
```
- &lt;clinit>() 方法对于类或接口不是必须的,如果一个类中不包含静态语句块,也没有对类变量的赋值操作,编译器可以不为该类生成 &lt;clinit>() 方法。
- 接口中不可以使用静态语句块,但仍然有类变量初始化的赋值操作,因此接口与类一样都会生成 &lt;clinit>() 方法。但接口与类不同的是,执行接口的 &lt;clinit>() 方法不需要先执行父接口的 &lt;clinit>() 方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的 &lt;clinit>() 方法。
- 虚拟机会保证一个类的 &lt;clinit>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 &lt;clinit>() 方法,其它线程都会阻塞等待,直到活动线程执行 &lt;clinit>() 方法完毕。如果在一个类的 &lt;clinit>() 方法中有耗时的操作,就可能造成多个线程阻塞,在实际过程中此种阻塞很隐蔽。
## 类加载器
在 Java 虚拟机外部实现,以便让应用程序自己决定如何去获取所需要的类。
### 1. 类与类加载器
两个类相等:类本身相等,并且使用同一个类加载器进行加载。这是因为每一个类加载器都拥有一个独立的类名称空间。
这里的相等,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果为 true也包括使用 instanceof 关键字做对象所属关系判定结果为 true。 这里的相等,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果为 true也包括使用 instanceof 关键字做对象所属关系判定结果为 true。
### 2. 类加载器分类 ## 类加载器分类
从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器: 从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器:
@ -624,7 +624,7 @@ public static void main(String[] args) {
- 应用程序类加载器Application ClassLoader这个类加载器是由 AppClassLoadersun.misc.Launcher$AppClassLoader实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值因此一般称为系统类加载器。它负责加载用户类路径ClassPath上所指定的类库开发者可以直接使用这个类加载器如果应用程序中没有自定义过自己的类加载器一般情况下这个就是程序中默认的类加载器。 - 应用程序类加载器Application ClassLoader这个类加载器是由 AppClassLoadersun.misc.Launcher$AppClassLoader实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值因此一般称为系统类加载器。它负责加载用户类路径ClassPath上所指定的类库开发者可以直接使用这个类加载器如果应用程序中没有自定义过自己的类加载器一般情况下这个就是程序中默认的类加载器。
### 3. 双亲委派模型 ## 双亲委派模型
应用程序都是由三种类加载器相互配合进行加载的,如果有必要,还可以加入自己定义的类加载器。 应用程序都是由三种类加载器相互配合进行加载的,如果有必要,还可以加入自己定义的类加载器。
@ -632,17 +632,17 @@ public static void main(String[] args) {
<div align="center"> <img src="../pics//class_loader_hierarchy.png" width="600"/> </div><br> <div align="center"> <img src="../pics//class_loader_hierarchy.png" width="600"/> </div><br>
(一)工作过程 ### 1. 工作过程
一个类加载器首先将类加载请求传送到父类加载器,只有当父类加载器无法完成类加载请求时才尝试加载。 一个类加载器首先将类加载请求传送到父类加载器,只有当父类加载器无法完成类加载请求时才尝试加载。
(二)好处 ### 2. 好处
使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。 使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。
例如 java.lang.Object 存放在 rt.jar 中,如果编写另外一个 java.lang.Object 的类并放到 ClassPath 中,程序可以编译通过。因为双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。正因为 rt.jar 中的 Object 优先级更高,因为程序中所有的 Object 都是这个 Object。 例如 java.lang.Object 存放在 rt.jar 中,如果编写另外一个 java.lang.Object 的类并放到 ClassPath 中,程序可以编译通过。由于双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,这是因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。rt.jar 中的 Object 优先级更高,那么程序中所有的 Object 都是这个 Object。
(三)实现 ### 3. 实现
以下是抽象类 java.lang.ClassLoader 的代码片段,其中的 loadClass() 方法运行过程如下:先检查类是否已经加载过,如果没有则让父类加载器去加载。当父类加载器加载失败时抛出 ClassNotFoundException此时尝试自己去加载。 以下是抽象类 java.lang.ClassLoader 的代码片段,其中的 loadClass() 方法运行过程如下:先检查类是否已经加载过,如果没有则让父类加载器去加载。当父类加载器加载失败时抛出 ClassNotFoundException此时尝试自己去加载。
@ -690,7 +690,7 @@ public abstract class ClassLoader {
} }
``` ```
### 4. 自定义类加载器实现 ## 自定义类加载器实现
FileSystemClassLoader 是自定义类加载器,继承自 java.lang.ClassLoader用于加载文件系统上的类。它首先根据类的全名在文件系统上查找类的字节代码文件.class 文件),然后读取该文件内容,最后通过 defineClass() 方法来把这些字节代码转换成 java.lang.Class 类的实例。 FileSystemClassLoader 是自定义类加载器,继承自 java.lang.ClassLoader用于加载文件系统上的类。它首先根据类的全名在文件系统上查找类的字节代码文件.class 文件),然后读取该文件内容,最后通过 defineClass() 方法来把这些字节代码转换成 java.lang.Class 类的实例。

File diff suppressed because it is too large Load Diff

View File

@ -461,7 +461,7 @@ Employee 表:
+----+-------+--------+-----------+ +----+-------+--------+-----------+
``` ```
查找所有员工,他们的薪资大于其经理薪资。 查找薪资大于其经理薪资的员工信息
## SQL Schema ## SQL Schema
@ -924,27 +924,27 @@ VALUES
```sql ```sql
SELECT SELECT
s1.id - 1 AS id, s1.id - 1 AS id,
s1.student s1.student
FROM FROM
seat s1 seat s1
WHERE WHERE
s1.id MOD 2 = 0 UNION s1.id MOD 2 = 0 UNION
SELECT SELECT
s2.id + 1 AS id, s2.id + 1 AS id,
s2.student s2.student
FROM FROM
seat s2 seat s2
WHERE WHERE
s2.id MOD 2 = 1 s2.id MOD 2 = 1
AND s2.id != ( SELECT max( s3.id ) FROM seat s3 ) UNION AND s2.id != ( SELECT max( s3.id ) FROM seat s3 ) UNION
SELECT SELECT
s4.id AS id, s4.id AS id,
s4.student s4.student
FROM FROM
seat s4 seat s4
WHERE WHERE
s4.id MOD 2 = 1 s4.id MOD 2 = 1
AND s4.id = ( SELECT max( s5.id ) FROM seat s5 ) AND s4.id = ( SELECT max( s5.id ) FROM seat s5 )
ORDER BY ORDER BY
id; id;
``` ```

View File

@ -129,15 +129,16 @@ info 与 man 类似,但是 info 将文档分成一个个页面,每个页面
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
``` ```
env 命令可以获取当前终端的环境变量。
## sudo ## sudo
sudo 允许一般用户使用 root 可执行的命令,不过只有在 /etc/sudoers 配置文件中添加的用户才能使用该指令。 sudo 允许一般用户使用 root 可执行的命令,不过只有在 /etc/sudoers 配置文件中添加的用户才能使用该指令。
## 包管理工具 ## 包管理工具
RPM 和 DPKG 为最常见的两类软件包管理工具。RPM 全称为 Redhat Package Manager最早由 Red Hat 公司制定实施,随后被 GNU 开源操作系统接受并成为很多 Linux 系统 (RHEL) 的既定软件标准。与 RPM 进行竞争的是基于 Debian 操作系统 (UBUNTU) 的 DEB 软件包管理工具 DPKG全称为 Debian Package功能方面与 RPM 相似。 RPM 和 DPKG 为最常见的两类软件包管理工具:
- RPM 全称为 Redhat Package Manager最早由 Red Hat 公司制定实施,随后被 GNU 开源操作系统接受并成为很多 Linux 系统 (RHEL) 的既定软件标准。
- 与 RPM 进行竞争的是基于 Debian 操作系统 (Ubuntu) 的 DEB 软件包管理工具 DPKG全称为 Debian Package功能方面与 RPM 相似。
YUM 基于 RPM具有依赖管理功能并具有软件升级的功能。 YUM 基于 RPM具有依赖管理功能并具有软件升级的功能。
@ -194,13 +195,13 @@ IDEATA全称 Advanced Technology Attachment接口速度最大为 133MB/
### 2. SATA ### 2. SATA
SATA 全称 Serial ATA也就是使用串口的 ATA 接口,抗干扰性强,且对数据线的长度要求比 ATA 低很多,支持热插拔等功能SATA-II 的接口速度为 300MiB/s而新的 SATA-III 标准可达到 600MiB/s 的传输速度。SATA 的数据线也比 ATA 的细得多,有利于机箱内的空气流通,整理线材也比较方便。 SATA 全称 Serial ATA也就是使用串口的 ATA 接口,抗干扰性强,且对数据线的长度要求比 ATA 低很多,支持热插拔等功能SATA-II 的接口速度为 300MiB/s而新的 SATA-III 标准可达到 600MiB/s 的传输速度。SATA 的数据线也比 ATA 的细得多,有利于机箱内的空气流通,整理线材也比较方便。
<div align="center"> <img src="../pics//f9f2a16b-4843-44d1-9759-c745772e9bcf.jpg" width=""/> </div><br> <div align="center"> <img src="../pics//f9f2a16b-4843-44d1-9759-c745772e9bcf.jpg" width=""/> </div><br>
### 3. SCSI ### 3. SCSI
SCSI 全称是 Small Computer System Interface小型机系统接口经历多代的发展从早期的 SCSI-II到目前的 Ultra320 SCSI 以及 Fiber-Channel光纤通道接口型式也多种多样。SCSI 硬盘广为工作站级个人电脑以及服务器所使用,因此会使用较为先进的技术,如碟片转速 15000rpm 的高转速,且资料传输时 CPU 占用率较低,但是单价也比相同容量的 ATA 及 SATA 硬盘更加昂贵。 SCSI 全称是 Small Computer System Interface小型机系统接口经历多代的发展从早期的 SCSI-II 到目前的 Ultra320 SCSI 以及 Fiber-Channel光纤通道接口型式也多种多样。SCSI 硬盘广为工作站级个人电脑以及服务器所使用,因此会使用较为先进的技术,如碟片转速 15000rpm 的高转速,且传输时 CPU 占用率较低,但是单价也比相同容量的 ATA 及 SATA 硬盘更加昂贵。
<div align="center"> <img src="../pics//f0574025-c514-49f5-a591-6d6a71f271f7.jpg" width=""/> </div><br> <div align="center"> <img src="../pics//f0574025-c514-49f5-a591-6d6a71f271f7.jpg" width=""/> </div><br>
@ -229,7 +230,7 @@ Linux 中每个硬件都被当做一个文件,包括磁盘。磁盘以磁盘
MBR 中第一个扇区最重要里面有主要开机记录Master boot record, MBR及分区表partition table其中主要开机记录占 446 bytes分区表占 64 bytes。 MBR 中第一个扇区最重要里面有主要开机记录Master boot record, MBR及分区表partition table其中主要开机记录占 446 bytes分区表占 64 bytes。
分区表只有 64 bytes最多只能存储 4 个分区,这 4 个分区为主分区Primary和扩展分区Extended。其中扩展分区只有一个将其它扇区用来记录分区表,因此通过扩展分区可以分出更多分区,这些分区称为逻辑分区。 分区表只有 64 bytes最多只能存储 4 个分区,这 4 个分区为主分区Primary和扩展分区Extended。其中扩展分区只有一个使用其它扇区用记录额外的分区表,因此通过扩展分区可以分出更多分区,这些分区称为逻辑分区。
Linux 也把分区当成文件,分区文件的命名方式为:磁盘文件名 + 编号,例如 /dev/sda1。注意逻辑分区的编号从 5 开始。 Linux 也把分区当成文件,分区文件的命名方式为:磁盘文件名 + 编号,例如 /dev/sda1。注意逻辑分区的编号从 5 开始。
@ -251,10 +252,10 @@ MBR 不支持 2.2 TB 以上的硬盘GPT 则最多支持到 2<sup>33</sup> TB
BIOSBasic Input/Output System基本输入输出系统它是一个固件嵌入在硬件中的软件BIOS 程序存放在断电后内容不会丢失的只读内存中。 BIOSBasic Input/Output System基本输入输出系统它是一个固件嵌入在硬件中的软件BIOS 程序存放在断电后内容不会丢失的只读内存中。
BIOS 是开机的时候计算机执行的第一个程序这个程序知道可以开机的磁盘并读取磁盘第一个扇区的主要开机记录MBR由主要开机记录MBR执行其中的开机管理程序这个开机管理程序会加载操作系统的核心文件。
<div align="center"> <img src="../pics//50831a6f-2777-46ea-a571-29f23c85cc21.jpg"/> </div><br> <div align="center"> <img src="../pics//50831a6f-2777-46ea-a571-29f23c85cc21.jpg"/> </div><br>
BIOS 是开机的时候计算机执行的第一个程序这个程序知道可以开机的磁盘并读取磁盘第一个扇区的主要开机记录MBR由主要开机记录MBR执行其中的开机管理程序这个开机管理程序会加载操作系统的核心文件。
主要开机记录MBR中的开机管理程序提供以下功能选单、载入核心文件以及转交其它开机管理程序。转交这个功能可以用来实现了多重引导只需要将另一个操作系统的开机管理程序安装在其它分区的启动扇区上在启动开机管理程序时就可以通过选单选择启动当前的操作系统或者转交给其它开机管理程序从而启动另一个操作系统。 主要开机记录MBR中的开机管理程序提供以下功能选单、载入核心文件以及转交其它开机管理程序。转交这个功能可以用来实现了多重引导只需要将另一个操作系统的开机管理程序安装在其它分区的启动扇区上在启动开机管理程序时就可以通过选单选择启动当前的操作系统或者转交给其它开机管理程序从而启动另一个操作系统。
下图中第一扇区的主要开机记录MBR中的开机管理程序提供了两个选单M1、M2M1 指向了 Windows 操作系统,而 M2 指向其它分区的启动扇区,里面包含了另外一个开机管理程序,提供了一个指向 Linux 的选单。 下图中第一扇区的主要开机记录MBR中的开机管理程序提供了两个选单M1、M2M1 指向了 Windows 操作系统,而 M2 指向其它分区的启动扇区,里面包含了另外一个开机管理程序,提供了一个指向 Linux 的选单。
@ -283,11 +284,10 @@ BIOS 不可以读取 GPT 分区表,而 UEFI 可以。
除此之外还包括: 除此之外还包括:
- superblock记录文件系统的整体信息包括 inode 和 block 的总量、使用量、剩余量,以及文件系统的格式与相关信息等; - superblock记录文件系统的整体信息包括 inode 和 block 的总量、使用量、剩余量,以及文件系统的格式与相关信息等;
- block bitmap记录 block 是否被使用的位域 - block bitmap记录 block 是否被使用的位域
<div align="center"> <img src="../pics//BSD_disk.png" width="800"/> </div><br> <div align="center"> <img src="../pics//BSD_disk.png" width="800"/> </div><br>
## 文件读取 ## 文件读取
对于 Ext2 文件系统,当要读取一个文件的内容时,先在 inode 中去查找文件内容所在的所有 block然后把所有 block 的内容读出来。 对于 Ext2 文件系统,当要读取一个文件的内容时,先在 inode 中去查找文件内容所在的所有 block然后把所有 block 的内容读出来。

View File

@ -1,22 +1,23 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [一、存储引擎](#一存储引擎) * [一、索引](#一索引)
* [B+ Tree 原理](#b-tree-原理)
* [MySQL 索引](#mysql-索引)
* [索引优化](#索引优化)
* [索引的优点](#索引的优点)
* [索引的使用场景](#索引的使用场景)
* [二、查询性能优化](#二查询性能优化)
* [使用 Explain 进行分析](#使用-explain-进行分析)
* [优化数据访问](#优化数据访问)
* [重构查询方式](#重构查询方式)
* [三、存储引擎](#三存储引擎)
* [InnoDB](#innodb) * [InnoDB](#innodb)
* [MyISAM](#myisam) * [MyISAM](#myisam)
* [比较](#比较) * [比较](#比较)
* [二、数据类型](#二数据类型) * [四、数据类型](#四数据类型)
* [整型](#整型) * [整型](#整型)
* [浮点数](#浮点数) * [浮点数](#浮点数)
* [字符串](#字符串) * [字符串](#字符串)
* [时间和日期](#时间和日期) * [时间和日期](#时间和日期)
* [三、索引](#三索引)
* [B+ Tree 原理](#b-tree-原理)
* [索引分类](#索引分类)
* [索引的优点](#索引的优点)
* [索引优化](#索引优化)
* [四、查询性能优化](#四查询性能优化)
* [使用 Explain 进行分析](#使用-explain-进行分析)
* [优化数据访问](#优化数据访问)
* [重构查询方式](#重构查询方式)
* [五、切分](#五切分) * [五、切分](#五切分)
* [水平切分](#水平切分) * [水平切分](#水平切分)
* [垂直切分](#垂直切分) * [垂直切分](#垂直切分)
@ -29,107 +30,13 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
# 一、存储引擎 # 一、索引
## InnoDB
InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支持的特性时,才考虑使用其它存储引擎。
实现了四个标准的隔离级别默认级别是可重复读REPEATABLE READ。在可重复读隔离级别下通过多版本并发控制MVCC+ 间隙锁next-key locking防止幻影读。
主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。
内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、能够加速插入操作的插入缓冲区等。
支持真正的在线热备份。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。
## MyISAM
MyISAM 设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用 MyISAM。
MyISAM 提供了大量的特性,包括压缩表、空间数据索引等。
不支持事务。
不支持行级锁只能对整张表加锁读取时会对需要读到的所有表加共享锁写入时则对表加排它锁。但在表有读取操作的同时也可以往表中插入新的记录这被称为并发插入CONCURRENT INSERT
可以手工或者自动执行检查和修复操作,但是和事务恢复以及崩溃恢复不同,可能导致一些数据丢失,而且修复操作是非常慢的。
如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作。
## 比较
- 事务InnoDB 是事务型的,可以使用 Commit 和 Rollback 语句。
- 并发MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。
- 外键InnoDB 支持外键。
- 备份InnoDB 支持在线热备份。
- 崩溃恢复MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。
- 其它特性MyISAM 支持压缩表和空间数据索引。
# 二、数据类型
## 整型
TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT 分别使用 8, 16, 24, 32, 64 位存储空间,一般情况下越小的列越好。
INT(11) 中的数字只是规定了交互工具显示字符的个数,对于存储和计算来说是没有意义的。
## 浮点数
FLOAT 和 DOUBLE 为浮点类型DECIMAL 为高精度小数类型。CPU 原生支持浮点运算,但是不支持 DECIMAl 类型的计算,因此 DECIMAL 的计算比浮点类型需要更高的代价。
FLOAT、DOUBLE 和 DECIMAL 都可以指定列宽,例如 DECIMAL(18, 9) 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分。
## 字符串
主要有 CHAR 和 VARCHAR 两种类型,一种是定长的,一种是变长的。
VARCHAR 这种变长类型能够节省空间,因为只需要存储必要的内容。但是在执行 UPDATE 时可能会使行变得比原来长当超出一个页所能容纳的大小时就要执行额外的操作。MyISAM 会将行拆成不同的片段存储,而 InnoDB 则需要分裂页来使行放进页内。
VARCHAR 会保留字符串末尾的空格,而 CHAR 会删除。
## 时间和日期
MySQL 提供了两种相似的日期时间类型DATETIME 和 TIMESTAMP。
### 1. DATETIME
能够保存从 1001 年到 9999 年的日期和时间,精度为秒,使用 8 字节的存储空间。
它与时区无关。
默认情况下MySQL 以一种可排序的、无歧义的格式显示 DATETIME 值例如“2008-01-16 22:37:08”这是 ANSI 标准定义的日期和时间表示方法。
### 2. TIMESTAMP
和 UNIX 时间戳相同,保存从 1970 年 1 月 1 日午夜(格林威治时间)以来的秒数,使用 4 个字节,只能表示从 1970 年 到 2038 年。
它和时区有关,也就是说一个时间戳在不同的时区所代表的具体时间是不同的。
MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提供了 UNIX_TIMESTAMP() 函数把日期转换为 UNIX 时间戳。
默认情况下,如果插入时没有指定 TIMESTAMP 列的值,会将这个值设置为当前时间。
应该尽量使用 TIMESTAMP因为它比 DATETIME 空间效率更高。
# 三、索引
索引能够轻易将查询性能提升几个数量级。
对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效。对于中到大型的表,索引就非常有效。但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。
索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。
## B+ Tree 原理 ## B+ Tree 原理
### 1. 数据结构 ### 1. 数据结构
B Tree 指的是 Balance Tree也就是平衡树。平衡树一颗查找树,并且所有叶子节点位于同一层。 B Tree 指的是 Balance Tree也就是平衡树。平衡树是一颗查找树并且所有叶子节点位于同一层。
B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具有 B Tree 的平衡性,并且通过顺序访问指针来提高区间查询的性能。 B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具有 B Tree 的平衡性,并且通过顺序访问指针来提高区间查询的性能。
@ -141,37 +48,37 @@ B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具
进行查找操作时,首先在根节点进行二分查找,找到一个 key 所在的指针,然后递归地在指针所指向的节点进行查找。直到查找到叶子节点,然后在叶子节点上进行二分查找,找出 key 所对应的 data。 进行查找操作时,首先在根节点进行二分查找,找到一个 key 所在的指针,然后递归地在指针所指向的节点进行查找。直到查找到叶子节点,然后在叶子节点上进行二分查找,找出 key 所对应的 data。
插入删除操作记录会破坏平衡树的平衡性,因此在插入删除,需要对树进行一个分裂、合并、旋转等操作。 插入删除操作记录会破坏平衡树的平衡性,因此在插入删除操作之后,需要对树进行一个分裂、合并、旋转等操作来维护平衡性
### 3. 与红黑树的比较 ### 3. 与红黑树的比较
红黑树等平衡树也可以用来实现索引,但是文件系统及数据库系统普遍采用 B+ Tree 作为索引结构,主要有以下两个原因: 红黑树等平衡树也可以用来实现索引,但是文件系统及数据库系统普遍采用 B+ Tree 作为索引结构,主要有以下两个原因:
(一)更少的检索次数 (一)更少的查找次数
平衡树检索数据的时间复杂度等于树高 h而树高大致为 O(h)=O(log<sub>d</sub>N),其中 d 为每个节点的出度。 平衡树查找操作的时间复杂度等于树高 h而树高大致为 O(h)=O(log<sub>d</sub>N),其中 d 为每个节点的出度。
红黑树的出度为 2而 B+ Tree 的出度一般都非常大红黑树的树高 h 很明显比 B+ Tree 大非常多,因此检索的次数也就更多。 红黑树的出度为 2而 B+ Tree 的出度一般都非常大,所以红黑树的树高 h 很明显比 B+ Tree 大非常多,检索的次数也就更多。
(二)利用计算机预读特性 (二)利用计算机预读特性
为了减少磁盘 I/O磁盘往往不是严格按需读取而是每次都会预读。这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。预读过程中,磁盘进行顺序读取,顺序读取不需要进行磁盘寻道,并且只需要很短的旋转时间,因此速度会非常快。 为了减少磁盘 I/O磁盘往往不是严格按需读取而是每次都会预读。预读过程中磁盘进行顺序读取顺序读取不需要进行磁盘寻道并且只需要很短的旋转时间因此速度会非常快。
操作系统一般将内存和磁盘分割成固态大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点,并且可以利用预读特性,相邻的节点也能够被预先载入。 操作系统一般将内存和磁盘分割成固态大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点,并且可以利用预读特性,相邻的节点也能够被预先载入。
## 索引分类 ## MySQL 索引
索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。
### 1. B+Tree 索引 ### 1. B+Tree 索引
B+Tree 索引是大多数 MySQL 存储引擎的默认索引类型。 是大多数 MySQL 存储引擎的默认索引类型。
因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。除了用于查找,还可以用于排序和分组。 因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。除了用于查找,还可以用于排序和分组。
可以指定多个列作为索引列,多个索引列共同组成键。 可以指定多个列作为索引列,多个索引列共同组成键。
B+Tree 索引适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。 适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。如果不是按照索引列的顺序进行查找,则无法使用索引。
如果不是按照索引列的顺序进行查找,则无法使用索引。
InnoDB 的 B+Tree 索引分为主索引和辅助索引。 InnoDB 的 B+Tree 索引分为主索引和辅助索引。
@ -185,12 +92,12 @@ InnoDB 的 B+Tree 索引分为主索引和辅助索引。
### 2. 哈希索引 ### 2. 哈希索引
InnoDB 引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。
哈希索引能以 O(1) 时间进行查找,但是失去了有序性,它具有以下限制: 哈希索引能以 O(1) 时间进行查找,但是失去了有序性,它具有以下限制:
- 无法用于排序与分组; - 无法用于排序与分组;
- 只支持精确查找,无法用于部分查找和范围查找; - 只支持精确查找,无法用于部分查找和范围查找。
InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。
### 3. 全文索引 ### 3. 全文索引
@ -200,20 +107,12 @@ MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而
InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。 InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。
### 4. 空间数据索引R-Tree ### 4. 空间数据索引
MyISAM 存储引擎支持空间数据索引,可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。 MyISAM 存储引擎支持空间数据索引R-Tree,可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。
必须使用 GIS 相关的函数来维护数据。 必须使用 GIS 相关的函数来维护数据。
## 索引的优点
- 大大减少了服务器需要扫描的数据行数。
- 帮助服务器避免进行排序和创建临时表B+Tree 索引是有序的,可以用来做 ORDER BY 和 GROUP BY 操作);
- 将随机 I/O 变为顺序 I/OB+Tree 索引是有序的,也就将相邻的数据都存储在一起)。
## 索引优化 ## 索引优化
### 1. 独立的列 ### 1. 独立的列
@ -266,11 +165,25 @@ customer_id_selectivity: 0.0373
具有以下优点: 具有以下优点:
- 因为索引条目通常远小于数据行的大小,所以若只读取索引能大大减少数据访问量。 - 索引通常远小于数据行的大小,只读取索引能大大减少数据访问量。
- 一些存储引擎(例如 MyISAM在内存中只缓存索引而数据依赖于操作系统来缓存。因此只访问索引可以不使用系统调用通常比较费时 - 一些存储引擎(例如 MyISAM在内存中只缓存索引而数据依赖于操作系统来缓存。因此只访问索引可以不使用系统调用通常比较费时
- 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。 - 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。
# 四、查询性能优化 ## 索引的优点
- 大大减少了服务器需要扫描的数据行数。
- 帮助服务器避免进行排序和分组也就不需要创建临时表B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,因为不需要排序和分组,也就不需要创建临时表)。
- 将随机 I/O 变为顺序 I/OB+Tree 索引是有序的,也就将相邻的数据都存储在一起)。
## 索引的使用场景
- 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效。
- 对于中到大型的表,索引就非常有效。
- 但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。
# 二、查询性能优化
## 使用 Explain 进行分析 ## 使用 Explain 进行分析
@ -282,23 +195,13 @@ Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explai
- key : 使用的索引 - key : 使用的索引
- rows : 扫描的行数 - rows : 扫描的行数
更多内容请参考:[MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735)
## 优化数据访问 ## 优化数据访问
### 1. 减少请求的数据量 ### 1. 减少请求的数据量
(一)只返回必要的列 - 只返回必要的列:最好不要使用 SELECT * 语句。
- 只返回必要的行:使用 LIMIT 语句来限制返回的数据。
最好不要使用 SELECT * 语句。 - 缓存重复查询的数据:使用缓存可以避免在数据库中进行查询,特别在要查询的数据经常被重复查询时,缓存带来的查询性能提升将会是非常明显的。
(二)只返回必要的行
使用 WHERE 语句进行查询过滤,有时候也需要使用 LIMIT 语句来限制返回的数据。
(三)缓存重复查询的数据
使用缓存可以避免在数据库中进行查询,特别要查询的数据经常被重复查询,缓存可以带来的查询性能提升将会是非常明显的。
### 2. 减少服务器端扫描的行数 ### 2. 减少服务器端扫描的行数
@ -324,12 +227,12 @@ do {
### 2. 分解大连接查询 ### 2. 分解大连接查询
将一个大连接查询JOIN分解成对每一个表进行一次单表查询,然后将结果在应用程序中进行关联,这样做的好处有: 将一个大连接查询分解成对每一个表进行一次单表查询,然后将结果在应用程序中进行关联,这样做的好处有:
- 让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。 - 让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。
- 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余记录的查询。 - 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余记录的查询。
- 减少锁竞争; - 减少锁竞争;
- 在应用层进行连接,可以更容易对数据库进行拆分,从而更容易做到高性能和可扩展 - 在应用层进行连接,可以更容易对数据库进行拆分,从而更容易做到高性能和可伸缩
- 查询本身效率也可能会有所提升。例如下面的例子中,使用 IN() 代替连接查询,可以让 MySQL 按照 ID 顺序进行查询,这可能比随机的连接要更高效。 - 查询本身效率也可能会有所提升。例如下面的例子中,使用 IN() 代替连接查询,可以让 MySQL 按照 ID 顺序进行查询,这可能比随机的连接要更高效。
```sql ```sql
@ -345,23 +248,111 @@ SELECT * FROM tag_post WHERE tag_id=1234;
SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904); SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904);
``` ```
# 三、存储引擎
## InnoDB
是 MySQL 默认的事务型存储引擎,只有在需要它不支持的特性时,才考虑使用其它存储引擎。
实现了四个标准的隔离级别默认级别是可重复读REPEATABLE READ。在可重复读隔离级别下通过多版本并发控制MVCC+ 间隙锁Next-Key Locking防止幻影读。
主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。
内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、能够加速插入操作的插入缓冲区等。
支持真正的在线热备份。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。
## MyISAM
设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它。
提供了大量的特性,包括压缩表、空间数据索引等。
不支持事务。
不支持行级锁只能对整张表加锁读取时会对需要读到的所有表加共享锁写入时则对表加排它锁。但在表有读取操作的同时也可以往表中插入新的记录这被称为并发插入CONCURRENT INSERT
可以手工或者自动执行检查和修复操作,但是和事务恢复以及崩溃恢复不同,可能导致一些数据丢失,而且修复操作是非常慢的。
如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作。
## 比较
- 事务InnoDB 是事务型的,可以使用 Commit 和 Rollback 语句。
- 并发MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。
- 外键InnoDB 支持外键。
- 备份InnoDB 支持在线热备份。
- 崩溃恢复MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。
- 其它特性MyISAM 支持压缩表和空间数据索引。
# 四、数据类型
## 整型
TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT 分别使用 8, 16, 24, 32, 64 位存储空间,一般情况下越小的列越好。
INT(11) 中的数字只是规定了交互工具显示字符的个数,对于存储和计算来说是没有意义的。
## 浮点数
FLOAT 和 DOUBLE 为浮点类型DECIMAL 为高精度小数类型。CPU 原生支持浮点运算,但是不支持 DECIMAl 类型的计算,因此 DECIMAL 的计算比浮点类型需要更高的代价。
FLOAT、DOUBLE 和 DECIMAL 都可以指定列宽,例如 DECIMAL(18, 9) 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分。
## 字符串
主要有 CHAR 和 VARCHAR 两种类型,一种是定长的,一种是变长的。
VARCHAR 这种变长类型能够节省空间,因为只需要存储必要的内容。但是在执行 UPDATE 时可能会使行变得比原来长当超出一个页所能容纳的大小时就要执行额外的操作。MyISAM 会将行拆成不同的片段存储,而 InnoDB 则需要分裂页来使行放进页内。
VARCHAR 会保留字符串末尾的空格,而 CHAR 会删除。
## 时间和日期
MySQL 提供了两种相似的日期时间类型DATETIME 和 TIMESTAMP。
### 1. DATETIME
能够保存从 1001 年到 9999 年的日期和时间,精度为秒,使用 8 字节的存储空间。
它与时区无关。
默认情况下MySQL 以一种可排序的、无歧义的格式显示 DATETIME 值例如“2008-01-16 22:37:08”这是 ANSI 标准定义的日期和时间表示方法。
### 2. TIMESTAMP
和 UNIX 时间戳相同,保存从 1970 年 1 月 1 日午夜(格林威治时间)以来的秒数,使用 4 个字节,只能表示从 1970 年 到 2038 年。
它和时区有关,也就是说一个时间戳在不同的时区所代表的具体时间是不同的。
MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提供了 UNIX_TIMESTAMP() 函数把日期转换为 UNIX 时间戳。
默认情况下,如果插入时没有指定 TIMESTAMP 列的值,会将这个值设置为当前时间。
应该尽量使用 TIMESTAMP因为它比 DATETIME 空间效率更高。
# 五、切分 # 五、切分
## 水平切分 ## 水平切分
<div align="center"> <img src="../pics//63c2909f-0c5f-496f-9fe5-ee9176b31aba.jpg"/> </div><br>
水平切分又称为 Sharding它是将同一个表中的记录拆分到多个结构相同的表中。 水平切分又称为 Sharding它是将同一个表中的记录拆分到多个结构相同的表中。
当一个表的数据不断增多时Sharding 是必然的选择,它可以将数据分布到集群的不同节点上,从而缓存单个数据库的压力。 当一个表的数据不断增多时Sharding 是必然的选择,它可以将数据分布到集群的不同节点上,从而缓存单个数据库的压力。
<div align="center"> <img src="../pics//63c2909f-0c5f-496f-9fe5-ee9176b31aba.jpg"/> </div><br>
## 垂直切分 ## 垂直切分
<div align="center"> <img src="../pics//e130e5b8-b19a-4f1e-b860-223040525cf6.jpg"/> </div><br> <div align="center"> <img src="../pics//e130e5b8-b19a-4f1e-b860-223040525cf6.jpg"/> </div><br>
垂直切分是将一张表按列切分成多个表,通常是按照列的关系密集程度进行切分,也可以利用垂直切分将经常被使用的列和不经常被使用的列切分到不同的表中。 垂直切分是将一张表按列切分成多个表,通常是按照列的关系密集程度进行切分,也可以利用垂直切分将经常被使用的列和不经常被使用的列切分到不同的表中。
在数据库的层面使用垂直切分将按数据库中表的密集程度部署到不同的库中,例如将原来的电商数据库垂直切分成商品数据库 payDB、用户数据库 userDB 等。 在数据库的层面使用垂直切分将按数据库中表的密集程度部署到不同的库中,例如将原来的电商数据库垂直切分成商品数据库、用户数据库等。
## Sharding 策略 ## Sharding 策略
@ -375,15 +366,15 @@ SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904);
使用分布式事务来解决,比如 XA 接口。 使用分布式事务来解决,比如 XA 接口。
### 2. JOIN ### 2. 链接
可以将原来的 JOIN 查询分解成多个单表查询,然后在用户程序中进行 JOIN。 可以将原来的 JOIN 分解成多个单表查询,然后在用户程序中进行 JOIN。
### 3. ID 唯一性 ### 3. ID 唯一性
- 使用全局唯一 IDGUID - 使用全局唯一 IDGUID
- 为每个分片指定一个 ID 范围 - 为每个分片指定一个 ID 范围
- 分布式 ID 生成器 (如 Twitter 的 Snowflake 算法) - 分布式 ID 生成器 (如 Twitter 的 Snowflake 算法)
更多内容请参考: 更多内容请参考:
@ -396,24 +387,24 @@ SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904);
主要涉及三个线程binlog 线程、I/O 线程和 SQL 线程。 主要涉及三个线程binlog 线程、I/O 线程和 SQL 线程。
- **binlog 线程** :负责将主服务器上的数据更改写入二进制文件binlog中。 - **binlog 线程** :负责将主服务器上的数据更改写入二进制日志中。
- **I/O 线程** :负责从主服务器上读取二进制日志文件,并写入从服务器的中继日志中。 - **I/O 线程** :负责从主服务器上读取二进制日志,并写入从服务器的中继日志中。
- **SQL 线程** :负责读取中继日志并重放其中的 SQL 语句。 - **SQL 线程** :负责读取中继日志并重放其中的 SQL 语句。
<div align="center"> <img src="../pics//master-slave.png"/> </div><br> <div align="center"> <img src="../pics//master-slave.png"/> </div><br>
## 读写分离 ## 读写分离
主服务器用来处理写操作以及实时性要求比较高的读操作,而从服务器用来处理读操作。 主服务器处理写操作以及实时性要求比较高的读操作,而从服务器处理读操作。
读写分离常用代理方式来实现,代理服务器接收应用层传来的读写请求,然后决定转发到哪个服务器。 读写分离能提高性能的原因在于:
MySQL 读写分离能提高性能的原因在于:
- 主从服务器负责各自的读和写,极大程度缓解了锁的争用; - 主从服务器负责各自的读和写,极大程度缓解了锁的争用;
- 从服务器可以配置 MyISAM 引擎,提升查询性能以及节约系统开销; - 从服务器可以使用 MyISAM,提升查询性能以及节约系统开销;
- 增加冗余,提高可用性。 - 增加冗余,提高可用性。
读写分离常用代理方式来实现,代理服务器接收应用层传来的读写请求,然后决定转发到哪个服务器。
<div align="center"> <img src="../pics//master-slave-proxy.png"/> </div><br> <div align="center"> <img src="../pics//master-slave-proxy.png"/> </div><br>
# 参考资料 # 参考资料
@ -425,3 +416,4 @@ MySQL 读写分离能提高性能的原因在于:
- [How to create unique row ID in sharded databases?](https://stackoverflow.com/questions/788829/how-to-create-unique-row-id-in-sharded-databases) - [How to create unique row ID in sharded databases?](https://stackoverflow.com/questions/788829/how-to-create-unique-row-id-in-sharded-databases)
- [SQL Azure Federation Introduction](http://geekswithblogs.net/shaunxu/archive/2012/01/07/sql-azure-federation-ndash-introduction.aspx "Title of this entry.") - [SQL Azure Federation Introduction](http://geekswithblogs.net/shaunxu/archive/2012/01/07/sql-azure-federation-ndash-introduction.aspx "Title of this entry.")
- [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) - [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html)
- [MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735)

View File

@ -49,7 +49,7 @@
Redis 是速度非常快的非关系型NoSQL内存键值数据库可以存储键和五种不同类型的值之间的映射。 Redis 是速度非常快的非关系型NoSQL内存键值数据库可以存储键和五种不同类型的值之间的映射。
键的类型只能为字符串,值支持的五种类型数据类型为:字符串、列表、集合、有序集合、散列表 键的类型只能为字符串,值支持的五种类型数据类型为:字符串、列表、集合、散列表、有序集合。
Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片来扩展写性能。 Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片来扩展写性能。
@ -58,7 +58,7 @@ Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,
| 数据类型 | 可以存储的值 | 操作 | | 数据类型 | 可以存储的值 | 操作 |
| :--: | :--: | :--: | | :--: | :--: | :--: |
| STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作</br> 对整数和浮点数执行自增或者自减操作 | | STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作</br> 对整数和浮点数执行自增或者自减操作 |
| LIST | 列表 | 从两端压入或者弹出元素</br> 读取单个或者多个元素</br> 进行修剪,只保留一个范围内的元素 | | LIST | 列表 | 从两端压入或者弹出元素</br> 单个或者多个元素</br> 进行修剪,只保留一个范围内的元素 |
| SET | 无序集合 | 添加、获取、移除单个元素</br> 检查一个元素是否存在于集合中</br> 计算交集、并集、差集</br> 从集合里面随机获取元素 | | SET | 无序集合 | 添加、获取、移除单个元素</br> 检查一个元素是否存在于集合中</br> 计算交集、并集、差集</br> 从集合里面随机获取元素 |
| HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对</br> 获取所有键值对</br> 检查某个键是否存在| | HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对</br> 获取所有键值对</br> 检查某个键是否存在|
| ZSET | 有序集合 | 添加、获取、删除元素</br> 根据分值范围或者成员来获取元素</br> 计算一个键的排名 | | ZSET | 有序集合 | 添加、获取、删除元素</br> 根据分值范围或者成员来获取元素</br> 计算一个键的排名 |
@ -555,7 +555,7 @@ Sentinel哨兵可以监听主服务器并在主服务器进入下线状
分片是将数据划分为多个部分的方法,可以将数据存储到多台机器里面,这种方法在解决某些问题时可以获得线性级别的性能提升。 分片是将数据划分为多个部分的方法,可以将数据存储到多台机器里面,这种方法在解决某些问题时可以获得线性级别的性能提升。
假设有 4 个 Reids 实例 R0R1R2R3还有很多表示用户的键 user:1user:2... 等等,有不同的方式来选择一个指定的键存储在哪个实例中。 假设有 4 个 Reids 实例 R0R1R2R3还有很多表示用户的键 user:1user:2... ,有不同的方式来选择一个指定的键存储在哪个实例中。
- 最简单的方式是范围分片,例如用户 id 从 0\~1000 的存储到实例 R0 中,用户 id 从 1001\~2000 的存储到实例 R1 中,等等。但是这样需要维护一张映射范围表,维护操作代价很高。 - 最简单的方式是范围分片,例如用户 id 从 0\~1000 的存储到实例 R0 中,用户 id 从 1001\~2000 的存储到实例 R1 中,等等。但是这样需要维护一张映射范围表,维护操作代价很高。
- 还有一种方式是哈希分片,使用 CRC32 哈希函数将键转换为一个数字,再对实例数量求模就能知道应该存储的实例。 - 还有一种方式是哈希分片,使用 CRC32 哈希函数将键转换为一个数字,再对实例数量求模就能知道应该存储的实例。

File diff suppressed because it is too large Load Diff

View File

@ -29,7 +29,7 @@
## 危害 ## 危害
- 窃取用户的 Cookie - 窃取用户的 Cookie
- 伪造虚假的输入表单骗取个人信息 - 伪造虚假的输入表单骗取个人信息
- 显示伪造的文章或者图片 - 显示伪造的文章或者图片
@ -47,7 +47,7 @@
富文本编辑器允许用户输入 HTML 代码,就不能简单地将 `<` 等字符进行过滤了,极大地提高了 XSS 攻击的可能性。 富文本编辑器允许用户输入 HTML 代码,就不能简单地将 `<` 等字符进行过滤了,极大地提高了 XSS 攻击的可能性。
富文本编辑器通常采用 XSS filter 来防范 XSS 攻击,可以定义一些标签白名单或者黑名单,从而不允许有攻击性的 HTML 代码的输入。 富文本编辑器通常采用 XSS filter 来防范 XSS 攻击,通过定义一些标签白名单或者黑名单,从而不允许有攻击性的 HTML 代码的输入。
以下例子中form 和 script 等标签都被转义,而 h 和 p 等标签将会保留。 以下例子中form 和 script 等标签都被转义,而 h 和 p 等标签将会保留。
@ -131,7 +131,7 @@ http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName
<img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman"> <img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">
``` ```
如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 资金 如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 美元
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务器端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。 这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务器端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。
@ -153,8 +153,6 @@ Referer 首部字段位于 HTTP 报文中,用于标识请求来源的地址。
因为 CSRF 攻击是在用户无意识的情况下发生的,所以要求用户输入验证码可以让用户知道自己正在做的操作。 因为 CSRF 攻击是在用户无意识的情况下发生的,所以要求用户输入验证码可以让用户知道自己正在做的操作。
也可以要求用户输入验证码来进行校验。
# 三、SQL 注入攻击 # 三、SQL 注入攻击
## 概念 ## 概念

View File

@ -20,6 +20,7 @@
* [可串行化SERIALIZABLE](#可串行化serializable) * [可串行化SERIALIZABLE](#可串行化serializable)
* [五、多版本并发控制](#五多版本并发控制) * [五、多版本并发控制](#五多版本并发控制)
* [版本号](#版本号) * [版本号](#版本号)
* [隐藏的列](#隐藏的列)
* [Undo 日志](#undo-日志) * [Undo 日志](#undo-日志)
* [实现过程](#实现过程) * [实现过程](#实现过程)
* [快照读与当前读](#快照读与当前读) * [快照读与当前读](#快照读与当前读)
@ -58,9 +59,7 @@
### 2. 一致性Consistency ### 2. 一致性Consistency
数据库在事务执行前后都保持一致性状态。 数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的。
在一致性状态下,所有事务对一个数据的读取结果都是相同的。
### 3. 隔离性Isolation ### 3. 隔离性Isolation
@ -78,10 +77,10 @@
- 只有满足一致性,事务的执行结果才是正确的。 - 只有满足一致性,事务的执行结果才是正确的。
- 在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就一定能满足一致性。 - 在无并发的情况下,事务串行执行,隔离性一定能够满足。此时只要能满足原子性,就一定能满足一致性。
- 在并发的情况下,多个事务并执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。 - 在并发的情况下,多个事务并执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。
- 事务满足持久化是为了能应对数据库崩溃的情况。 - 事务满足持久化是为了能应对数据库崩溃的情况。
<div align="center"> <img src="../pics//a58e294a-615d-4ea0-9fbf-064a6daec4b2.png" width="400"/> </div><br> <div align="center"> <img src="../pics//a58e294a-615d-4ea0-9fbf-064a6daec4b2.png" width="450"/> </div><br>
## AUTOCOMMIT ## AUTOCOMMIT
@ -95,25 +94,25 @@ MySQL 默认采用自动提交模式。也就是说,如果不显式使用`STAR
T<sub>1</sub> 和 T<sub>2</sub> 两个事务都对一个数据进行修改T<sub>1</sub> 先修改T<sub>2</sub> 随后修改T<sub>2</sub> 的修改覆盖了 T<sub>1</sub> 的修改。 T<sub>1</sub> 和 T<sub>2</sub> 两个事务都对一个数据进行修改T<sub>1</sub> 先修改T<sub>2</sub> 随后修改T<sub>2</sub> 的修改覆盖了 T<sub>1</sub> 的修改。
<div align="center"> <img src="../pics//88ff46b3-028a-4dbb-a572-1f062b8b96d3.png" width="300"/> </div><br> <div align="center"> <img src="../pics//88ff46b3-028a-4dbb-a572-1f062b8b96d3.png" width="350"/> </div><br>
## 读脏数据 ## 读脏数据
T<sub>1</sub> 修改一个数据T<sub>2</sub> 随后读取这个数据。如果 T<sub>1</sub> 撤销了这次修改,那么 T<sub>2</sub> 读取的数据是脏数据。 T<sub>1</sub> 修改一个数据T<sub>2</sub> 随后读取这个数据。如果 T<sub>1</sub> 撤销了这次修改,那么 T<sub>2</sub> 读取的数据是脏数据。
<div align="center"> <img src="../pics//dd782132-d830-4c55-9884-cfac0a541b8e.png" width="300"/> </div><br> <div align="center"> <img src="../pics//dd782132-d830-4c55-9884-cfac0a541b8e.png" width="400"/> </div><br>
## 不可重复读 ## 不可重复读
T<sub>2</sub> 读取一个数据T<sub>1</sub> 对该数据做了修改。如果 T<sub>2</sub> 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 T<sub>2</sub> 读取一个数据T<sub>1</sub> 对该数据做了修改。如果 T<sub>2</sub> 再次读取这个数据,此时读取的结果和第一次读取的结果不同。
<div align="center"> <img src="../pics//c8d18ca9-0b09-441a-9a0c-fb063630d708.png" width="300"/> </div><br> <div align="center"> <img src="../pics//c8d18ca9-0b09-441a-9a0c-fb063630d708.png" width="350"/> </div><br>
## 幻影读 ## 幻影读
T<sub>1</sub> 读取某个范围的数据T<sub>2</sub> 在这个范围内插入新的数据T<sub>1</sub> 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。 T<sub>1</sub> 读取某个范围的数据T<sub>2</sub> 在这个范围内插入新的数据T<sub>1</sub> 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。
<div align="center"> <img src="../pics//72fe492e-f1cb-4cfc-92f8-412fb3ae6fec.png" width="300"/> </div><br> <div align="center"> <img src="../pics//72fe492e-f1cb-4cfc-92f8-412fb3ae6fec.png" width="350"/> </div><br>
---- ----
@ -123,7 +122,6 @@ T<sub>1</sub> 读取某个范围的数据T<sub>2</sub> 在这个范围内插
## 封锁粒度 ## 封锁粒度
MySQL 中提供了两种封锁粒度:行级锁以及表级锁。 MySQL 中提供了两种封锁粒度:行级锁以及表级锁。
应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。 应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。
@ -150,8 +148,8 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。
| - | X | S | | - | X | S |
| :--: | :--: | :--: | | :--: | :--: | :--: |
|X|NO|NO| |X|×|×|
|S|NO|YES| |S|×|√|
### 2. 意向锁 ### 2. 意向锁
@ -170,10 +168,10 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。
| - | X | IX | S | IS | | - | X | IX | S | IS |
| :--: | :--: | :--: | :--: | :--: | | :--: | :--: | :--: | :--: | :--: |
|X |NO |NO |NO | NO| |X |× |× |× | ×|
|IX |NO |YES |NO | YES| |IX |× |√ |× | √|
|S |NO |NO |YES | YES| |S |× |× |√ | √|
|IS |NO |YES |YES | YES| |IS |× |√ |√ | √|
解释如下: 解释如下:
@ -298,32 +296,30 @@ SELECT ... FOR UPDATE;
| 隔离级别 | 脏读 | 不可重复读 | 幻影读 | | 隔离级别 | 脏读 | 不可重复读 | 幻影读 |
| :---: | :---: | :---:| :---: | | :---: | :---: | :---:| :---: |
| 未提交读 | YES | YES | YES | | 未提交读 | √ | √ | √ |
| 提交读 | NO | YES | YES | | 提交读 | × | √ | √ |
| 可重复读 | NO | NO | YES | | 可重复读 | × | × | √ |
| 可串行化 | NO | NO | NO | | 可串行化 | × | × | × |
# 五、多版本并发控制 # 五、多版本并发控制
多版本并发控制Multi-Version Concurrency Control, MVCC是 MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。 多版本并发控制Multi-Version Concurrency Control, MVCC是 MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。而未提交读隔离级别总是读取最新的数据行,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁单纯使用 MVCC 无法实现。
而未提交读隔离级别总是读取最新的数据行,无需使用 MVCC。
可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。
## 版本号 ## 版本号
- 系统版本号:是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增。 - 系统版本号:是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增。
- 事务版本号:事务开始时的系统版本号。 - 事务版本号:事务开始时的系统版本号。
InooDB 的 MVCC 在每行记录后面都保存着两个隐藏的列,用来存储两个版本号: ## 隐藏的列
MVCC 在每行记录后面都保存着两个隐藏的列,用来存储两个版本号:
- 创建版本号:指示创建一个数据行的快照时的系统版本号; - 创建版本号:指示创建一个数据行的快照时的系统版本号;
- 删除版本号:如果该快照的删除版本号大于当前事务版本号表示该快照有效,否则表示该快照已经被删除了。 - 删除版本号:如果该快照的删除版本号大于当前事务版本号表示该快照有效,否则表示该快照已经被删除了。
## Undo 日志 ## Undo 日志
InnoDB 的 MVCC 使用到的快照存储在 Undo 日志中该日志通过回滚指针把一个数据行Record的所有快照连接起来。 MVCC 使用到的快照存储在 Undo 日志中该日志通过回滚指针把一个数据行Record的所有快照连接起来。
<div align="center"> <img src="../pics//e41405a8-7c05-4f70-8092-e961e28d3112.jpg" width=""/> </div><br> <div align="center"> <img src="../pics//e41405a8-7c05-4f70-8092-e961e28d3112.jpg" width=""/> </div><br>
@ -331,15 +327,13 @@ InnoDB 的 MVCC 使用到的快照存储在 Undo 日志中,该日志通过回
以下实现过程针对可重复读隔离级别。 以下实现过程针对可重复读隔离级别。
### 1. SELECT
当开始新一个事务时,该事务的版本号肯定会大于当前所有数据行快照的创建版本号,理解这一点很关键。 当开始新一个事务时,该事务的版本号肯定会大于当前所有数据行快照的创建版本号,理解这一点很关键。
### 1. SELECT
多个事务必须读取到同一个数据行的快照,并且这个快照是距离现在最近的一个有效快照。但是也有例外,如果有一个事务正在修改该数据行,那么它可以读取事务本身所做的修改,而不用和其它事务的读取结果一致。 多个事务必须读取到同一个数据行的快照,并且这个快照是距离现在最近的一个有效快照。但是也有例外,如果有一个事务正在修改该数据行,那么它可以读取事务本身所做的修改,而不用和其它事务的读取结果一致。
把没有对一个数据行做修改的事务称为 TT 所要读取的数据行快照的创建版本号必须小于 T 的版本号,因为如果大于或者等于 T 的版本号,那么表示该数据行快照是其它事务的最新修改,因此不能去读取它。 把没有对一个数据行做修改的事务称为 TT 所要读取的数据行快照的创建版本号必须小于 T 的版本号,因为如果大于或者等于 T 的版本号那么表示该数据行快照是其它事务的最新修改因此不能去读取它。除此之外T 所要读取的数据行快照的删除版本号必须大于 T 的版本号,因为如果小于等于 T 的版本号,那么表示该数据行快照是已经被删除的,不应该去读取它。
除了上面的要求T 所要读取的数据行快照的删除版本号必须大于 T 的版本号,因为如果小于等于 T 的版本号,那么表示该数据行快照是已经被删除的,不应该去读取它。
### 2. INSERT ### 2. INSERT
@ -383,13 +377,13 @@ MVCC 不能解决幻读的问题Next-Key Locks 就是为了解决这个问题
## Record Locks ## Record Locks
锁定的对象是记录的索引,而不是记录本身。 锁定一个记录上的索引,而不是记录本身。
如果表没有设置索引InnoDB 会自动在主键上创建隐藏的聚索引,因此 Record Locks 依然可以使用。 如果表没有设置索引InnoDB 会自动在主键上创建隐藏的聚索引,因此 Record Locks 依然可以使用。
## Gap Locks ## Gap Locks
锁定一个范围内的索引,例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15。 锁定索引之间的间隙,但是不包含索引本身。例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15。
```sql ```sql
SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE; SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
@ -397,28 +391,14 @@ SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
## Next-Key Locks ## Next-Key Locks
它是 Record Locks 和 Gap Locks 的结合,不仅锁定一个记录,也锁定范围内的索引。在 user 中有以下记录 它是 Record Locks 和 Gap Locks 的结合,不仅锁定一个记录上的索引也锁定索引之间的间隙。例如一个索引包含以下值10, 11, 13, and 20那么就需要锁定以下区间
```sql ```sql
| id | last_name | first_name | age | (negative infinity, 10]
|------|-------------|--------------|-------| (10, 11]
| 4 | stark | tony | 21 | (11, 13]
| 1 | tom | hiddleston | 30 | (13, 20]
| 3 | morgan | freeman | 40 | (20, positive infinity)
| 5 | jeff | dean | 50 |
| 2 | donald | trump | 80 |
+------|-------------|--------------|-------+
```
那么就需要锁定以下范围:
```sql
(-∞, 21]
(21, 30]
(30, 40]
(40, 50]
(50, 80]
(80, ∞)
``` ```
# 七、关系数据库设计理论 # 七、关系数据库设计理论
@ -446,10 +426,10 @@ SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
不符合范式的关系,会产生很多异常,主要有以下四种异常: 不符合范式的关系,会产生很多异常,主要有以下四种异常:
- 冗余数据:例如 学生-2 出现了两次。 - 冗余数据:例如 `学生-2` 出现了两次。
- 修改异常:修改了一个记录中的信息,但是另一个记录中相同的信息却没有被修改。 - 修改异常:修改了一个记录中的信息,但是另一个记录中相同的信息却没有被修改。
- 删除异常:删除一个信息,那么也会丢失其它信息。例如如果删除了 课程-1需要删除第一行和第三行那么 学生-1 的信息就会丢失。 - 删除异常:删除一个信息,那么也会丢失其它信息。例如删除了 `课程-1` 需要删除第一行和第三行,那么 `学生-1` 的信息就会丢失。
- 插入异常例如想要插入一个学生的信息,如果这个学生还没选课,那么就无法插入。 - 插入异常例如想要插入一个学生的信息,如果这个学生还没选课,那么就无法插入。
## 范式 ## 范式
@ -520,7 +500,11 @@ Sname, Sdept 和 Mname 都部分依赖于键码,当一个学生选修了多门
非主属性不传递函数依赖于键码。 非主属性不传递函数依赖于键码。
上面的 关系-1 中存在以下传递函数依赖Sno -> Sdept -> Mname可以进行以下分解 上面的 关系-1 中存在以下传递函数依赖:
- Sno -> Sdept -> Mname
可以进行以下分解:
关系-11 关系-11
@ -547,13 +531,19 @@ Entity-Relationship有三个组成部分实体、属性、联系。
包含一对一,一对多,多对多三种。 包含一对一,一对多,多对多三种。
如果 A 到 B 是一对多关系,那么画个带箭头的线段指向 B如果是一对一画两个带箭头的线段如果是多对多画两个不带箭头的线段。下图的 Course 和 Student 是一对多的关系。 - 如果 A 到 B 是一对多关系,那么画个带箭头的线段指向 B
- 如果是一对一,画两个带箭头的线段;
- 如果是多对多,画两个不带箭头的线段。
下图的 Course 和 Student 是一对多的关系。
<div align="center"> <img src="../pics//292b4a35-4507-4256-84ff-c218f108ee31.jpg" width=""/> </div><br> <div align="center"> <img src="../pics//292b4a35-4507-4256-84ff-c218f108ee31.jpg" width=""/> </div><br>
## 表示出现多次的关系 ## 表示出现多次的关系
一个实体在联系出现几次,就要用几条线连接。下图表示一个课程的先修关系,先修关系出现两个 Course 实体,第一个是先修课程,后一个是后修课程,因此需要用两条线来表示这种关系。 一个实体在联系出现几次,就要用几条线连接。
下图表示一个课程的先修关系,先修关系出现两个 Course 实体,第一个是先修课程,后一个是后修课程,因此需要用两条线来表示这种关系。
<div align="center"> <img src="../pics//8b798007-e0fb-420c-b981-ead215692417.jpg" width=""/> </div><br> <div align="center"> <img src="../pics//8b798007-e0fb-420c-b981-ead215692417.jpg" width=""/> </div><br>
@ -563,7 +553,7 @@ Entity-Relationship有三个组成部分实体、属性、联系。
<div align="center"> <img src="../pics//423f2a40-bee1-488e-b460-8e76c48ee560.png" width=""/> </div><br> <div align="center"> <img src="../pics//423f2a40-bee1-488e-b460-8e76c48ee560.png" width=""/> </div><br>
一般只使用二元联系,可以把多元关系转换为二元关系。 一般只使用二元联系,可以把多元联系转换为二元联系。
<div align="center"> <img src="../pics//de9b9ea0-1327-4865-93e5-6f805c48bc9e.png" width=""/> </div><br> <div align="center"> <img src="../pics//de9b9ea0-1327-4865-93e5-6f805c48bc9e.png" width=""/> </div><br>

View File

@ -18,7 +18,7 @@
消息生产者向消息队列中发送了一个消息之后,只能被一个消费者消费一次。 消息生产者向消息队列中发送了一个消息之后,只能被一个消费者消费一次。
<div align="center"> <img src="../pics//09b52bcb-88ba-4e36-8244-b375f16ad116.jpg"/> </div><br> <div align="center"> <img src="../pics//685a692f-8f76-4cac-baac-b68e2df9a30f.jpg"/> </div><br>
## 发布/订阅 ## 发布/订阅
@ -72,7 +72,7 @@
## 接收端的可靠性 ## 接收端的可靠性
接收端能够从消息中间件成功消费一次消息。 接收端能够从消息队列成功消费一次消息。
实现方法: 实现方法:

File diff suppressed because it is too large Load Diff

View File

@ -79,7 +79,7 @@
实现可扩展主要有两种方式: 实现可扩展主要有两种方式:
- 使用消息队列进行解耦,应用之间通过消息传递的方式进行通信; - 使用消息队列进行解耦,应用之间通过消息传递的方式进行通信;
- 使用分布式服务将业务和可复用的服务分离开来,业务使用分布式服务框架调用可复用的服务。新增的产品可以过调用可复用的服务来实现业务逻辑,对其它产品没有影响。 - 使用分布式服务将业务和可复用的服务分离开来,业务使用分布式服务框架调用可复用的服务。新增的产品可以过调用可复用的服务来实现业务逻辑,对其它产品没有影响。
# 四、可用性 # 四、可用性

View File

@ -35,10 +35,10 @@
# 二、LRU # 二、LRU
以下是一个基于 双向队列 + HashMap 的 LRU 算法实现,对算法的解释如下: 以下是一个基于 双向链表 + HashMap 的 LRU 算法实现,对算法的解释如下:
- 最基本的思路是当访问某个节点时,将其从原来的位置删除,并重新插入到链表头部,这样就能保证链表尾部存储的就是最近最久未使用的节点,当节点数量大于缓存最大空间时就删除链表尾部的节点。 - 最基本的思路是当访问某个节点时,将其从原来的位置删除,并重新插入到链表头部,这样就能保证链表尾部存储的就是最近最久未使用的节点,当节点数量大于缓存最大空间时就删除链表尾部的节点。
- 为了使删除操作时间复杂度为 O(1)那么就不能采用遍历的方式找到某个节点。HashMap 存储 Key 到节点的映射,通过 Key 就能以 O(1) 的时间得到节点,然后再以 O(1) 的时间将其从双向队列中删除。 - 为了使删除操作时间复杂度为 O(1)那么就不能采用遍历的方式找到某个节点。HashMap 存储 Key 到节点的映射,通过 Key 就能以 O(1) 的时间得到节点,然后再以 O(1) 的时间将其从双向队列中删除。
```java ```java
public class LRU<K, V> implements Iterable<K> { public class LRU<K, V> implements Iterable<K> {
@ -143,6 +143,10 @@ public class LRU<K, V> implements Iterable<K> {
} }
``` ```
源代码:
- [CyC2018/Algorithm](https://github.com/CyC2018/Algorithm/tree/master/Caching)
# 三、缓存位置 # 三、缓存位置
## 浏览器 ## 浏览器
@ -165,9 +169,7 @@ public class LRU<K, V> implements Iterable<K> {
使用 Redis、Memcache 等分布式缓存将数据缓存在分布式缓存系统中。 使用 Redis、Memcache 等分布式缓存将数据缓存在分布式缓存系统中。
相对于本地缓存来说,分布式缓存单独部署,可以根据需求分配硬件资源。 相对于本地缓存来说,分布式缓存单独部署,可以根据需求分配硬件资源。不仅如此,服务器集群都可以访问分布式缓存。而本地缓存需要在服务器集群之间进行同步,实现和性能开销上都非常大。
不仅如此,服务器集群都可以访问分布式缓存。而本地缓存需要在服务器集群之间进行同步,实现和性能开销上都非常大。
## 数据库缓存 ## 数据库缓存
@ -263,7 +265,7 @@ Distributed Hash TableDHT 是一种哈希分布方式,其目的是为了
上面描述的一致性哈希存在数据分布不均匀的问题,节点存储的数据量有可能会存在很大的不同。 上面描述的一致性哈希存在数据分布不均匀的问题,节点存储的数据量有可能会存在很大的不同。
数据不均匀主要是因为节点在哈希环上分布的不均匀,这种情况在节点数量很少的情况下尤其明显。解决方式是通过增加虚拟节点,然后将虚拟节点映射到真实节点上。虚拟节点的数量比真实节点来得大,那么虚拟节点在哈希环上分布的均匀性就会比原来的真节点好,从而使得数据分布也更加均匀。 数据不均匀主要是因为节点在哈希环上分布的不均匀,这种情况在节点数量很少的情况下尤其明显。解决方式是通过增加虚拟节点,然后将虚拟节点映射到真实节点上。虚拟节点的数量比真实节点来得大,那么虚拟节点在哈希环上分布的均匀性就会比原来的真节点好,从而使得数据分布也更加均匀。
参考资料: 参考资料:

View File

@ -62,9 +62,9 @@
(一)懒汉式-线程不安全 (一)懒汉式-线程不安全
以下实现中,私有静态变量 uniqueInstance 被延迟实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance从而节约资源。 以下实现中,私有静态变量 uniqueInstance 被延迟实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance从而节约资源。
这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 `if (uniqueInstance == null)` ,并且此时 uniqueInstance 为 null那么多个线程执行 `uniqueInstance = new Singleton();` 语句,这将导致多次实例化 uniqueInstance。 这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 `if (uniqueInstance == null)` ,并且此时 uniqueInstance 为 null那么会有多个线程执行 `uniqueInstance = new Singleton();` 语句,这将导致多次实例化 uniqueInstance。
```java ```java
public class Singleton { public class Singleton {
@ -83,11 +83,21 @@ public class Singleton {
} }
``` ```
(二)汉式-线程安全 (二)饿汉式-线程安全
只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了对 uniqueInstance 进行多次实例化的问题。 线程不安全问题主要是由于 uniqueInstance 被多次实例化,采取直接实例化 uniqueInstance 的方式就不会产生线程不安全问题。
但是这样有一个问题,就是当一个线程进入该方法之后,其它线程试图进入该方法都必须等待,因此性能上有一定的损耗。 但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。
```java
private static Singleton uniqueInstance = new Singleton();
```
(三)懒汉式-线程安全
只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了多次实例化 uniqueInstance 的问题。
但是当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,因此性能上有一定的损耗。
```java ```java
public static synchronized Singleton getUniqueInstance() { public static synchronized Singleton getUniqueInstance() {
@ -98,17 +108,9 @@ public static synchronized Singleton getUniqueInstance() {
} }
``` ```
(三)饿汉式-线程安全
线程不安全问题主要是由于 uniqueInstance 被实例化了多次,如果 uniqueInstance 采用直接实例化的话,就不会被实例化多次,也就不会产生线程不安全问题。但是直接实例化的方式也丢失了延迟实例化带来的节约资源的优势。
```java
private static Singleton uniqueInstance = new Singleton();
```
(四)双重校验锁-线程安全 (四)双重校验锁-线程安全
uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行。也就是说,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。 uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。
双重校验锁先判断 uniqueInstance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。 双重校验锁先判断 uniqueInstance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。
@ -133,7 +135,7 @@ public class Singleton {
} }
``` ```
考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程同时执行 if 语句,那么两个线程就会同时进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 `uniqueInstance = new Singleton();` 这条语句,只是先后的问题,也就是说会进行两次实例化,从而产生了两个实例。因此必须使用双重校验锁,也就是需要使用两个 if 语句。 考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程同时执行 if 语句,那么两个线程就会同时进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 `uniqueInstance = new Singleton();` 这条语句,只是先后的问题,那么就会进行两次实例化,从而产生了两个实例。因此必须使用双重校验锁,也就是需要使用两个 if 语句。
```java ```java
if (uniqueInstance == null) { if (uniqueInstance == null) {
@ -159,7 +161,7 @@ uniqueInstance 采用 volatile 关键字修饰也是很有必要的。`uniqueIns
这种方式不仅具有延迟初始化的好处,而且由虚拟机提供了对线程安全的支持。 这种方式不仅具有延迟初始化的好处,而且由虚拟机提供了对线程安全的支持。
```source-java ```java
public class Singleton { public class Singleton {
private Singleton() { private Singleton() {
@ -175,7 +177,7 @@ public class Singleton {
} }
``` ```
)枚举实现 )枚举实现
这是单例模式的最佳实践,它实现简单,并且在面对复杂的序列化或者反射攻击的时候,能够防止实例化多次。 这是单例模式的最佳实践,它实现简单,并且在面对复杂的序列化或者反射攻击的时候,能够防止实例化多次。
@ -231,27 +233,9 @@ public class Singleton implements Serializable {
简单工厂不是设计模式,更像是一种编程习惯。它把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。 简单工厂不是设计模式,更像是一种编程习惯。它把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。
<div align="center"> <img src="../pics//c79da808-0f28-4a36-bc04-33ccc5b83c13.png"/> </div><br>
这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。因为客户类往往有多个,如果不使用简单工厂,所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。 这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。因为客户类往往有多个,如果不使用简单工厂,所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。
如果存在下面这种代码,就需要使用简单工厂将对象实例化的部分放到简单工厂中。 <div align="center"> <img src="../pics//c79da808-0f28-4a36-bc04-33ccc5b83c13.png"/> </div><br>
```java
public class Client {
public static void main(String[] args) {
int type = 1;
Product product;
if (type == 1) {
product = new ConcreteProduct1();
} else if (type == 2) {
product = new ConcreteProduct2();
} else {
product = new ConcreteProduct();
}
}
}
```
### 实现 ### 实现
@ -275,6 +259,27 @@ public class ConcreteProduct2 implements Product {
} }
``` ```
以下的 Client 类中包含了实例化的代码,这是一种错误的实现,如果在客户类中存在实例化代码,就需要将代码放到简单工厂中。
```java
public class Client {
public static void main(String[] args) {
int type = 1;
Product product;
if (type == 1) {
product = new ConcreteProduct1();
} else if (type == 2) {
product = new ConcreteProduct2();
} else {
product = new ConcreteProduct();
}
// do something with the product
}
}
```
以下的 SimpleFactory 是简单工厂实现,它被所有需要进行实例化的客户类调用。
```java ```java
public class SimpleFactory { public class SimpleFactory {
public Product createProduct(int type) { public Product createProduct(int type) {
@ -293,6 +298,7 @@ public class Client {
public static void main(String[] args) { public static void main(String[] args) {
SimpleFactory simpleFactory = new SimpleFactory(); SimpleFactory simpleFactory = new SimpleFactory();
Product product = simpleFactory.createProduct(1); Product product = simpleFactory.createProduct(1);
// do something with the product
} }
} }
``` ```
@ -301,7 +307,7 @@ public class Client {
### 意图 ### 意图
定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化推迟到子类。 定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。
### 类图 ### 类图
@ -1853,11 +1859,7 @@ No gumball dispensed
### 与状态模式的比较 ### 与状态模式的比较
状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。 状态模式的类图和策略模式类似,并且都是能够动态改变对象的行为。但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。
但是状态模式是通过状态转移来改变 Context 所组合的 State 对象,而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。
所谓的状态转移,是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变,注意必须要是在运行过程中。
状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法。 状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法。
@ -1971,7 +1973,7 @@ public abstract class CaffeineBeverage {
``` ```
```java ```java
public class Coffee extends CaffeineBeverage{ public class Coffee extends CaffeineBeverage {
@Override @Override
void brew() { void brew() {
System.out.println("Coffee.brew"); System.out.println("Coffee.brew");
@ -1985,7 +1987,7 @@ public class Coffee extends CaffeineBeverage{
``` ```
```java ```java
public class Tea extends CaffeineBeverage{ public class Tea extends CaffeineBeverage {
@Override @Override
void brew() { void brew() {
System.out.println("Tea.brew"); System.out.println("Tea.brew");
@ -2240,7 +2242,7 @@ Number of items: 6
### 意图 ### 意图
使用什么都不做的空对象来代 NULL。 使用什么都不做的空对象来代 NULL。
一个方法返回 NULL意味着方法的调用端需要去检查返回值是否是 NULL这么做会导致非常多的冗余的检查代码。并且如果某一个调用端忘记了做这个检查返回值而直接使用返回的对象那么就有可能抛出空指针异常。 一个方法返回 NULL意味着方法的调用端需要去检查返回值是否是 NULL这么做会导致非常多的冗余的检查代码。并且如果某一个调用端忘记了做这个检查返回值而直接使用返回的对象那么就有可能抛出空指针异常。
@ -2395,7 +2397,7 @@ public abstract class TV {
``` ```
```java ```java
public class Sony extends TV{ public class Sony extends TV {
@Override @Override
public void on() { public void on() {
System.out.println("Sony.on()"); System.out.println("Sony.on()");
@ -2414,7 +2416,7 @@ public class Sony extends TV{
``` ```
```java ```java
public class RCA extends TV{ public class RCA extends TV {
@Override @Override
public void on() { public void on() {
System.out.println("RCA.on()"); System.out.println("RCA.on()");
@ -2553,9 +2555,6 @@ public abstract class Component {
``` ```
```java ```java
import java.util.ArrayList;
import java.util.List;
public class Composite extends Component { public class Composite extends Component {
private List<Component> child; private List<Component> child;
@ -2661,7 +2660,7 @@ Composite:root
### 类图 ### 类图
装饰者Decorator和具体组件ConcreteComponent都继承自组件Component具体组件的方法实现不需要依赖于其它对象而装饰者组合了一个组件这样它可以装饰其它装饰者或者具体组件。所谓装饰就是把这个装饰者套在被装饰上从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的这属于它的功能然后调用被装饰者的方法实现从而也保留了被装饰者的功能。可以看到具体组件应当是装饰层次的最低层因为只有具体组件的方法实现不需要依赖于其它对象。 装饰者Decorator和具体组件ConcreteComponent都继承自组件Component具体组件的方法实现不需要依赖于其它对象而装饰者组合了一个组件这样它可以装饰其它装饰者或者具体组件。所谓装饰就是把这个装饰者套在被装饰者之上,从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的,这属于它的功能,然后调用被装饰者的方法实现,从而也保留了被装饰者的功能。可以看到,具体组件应当是装饰层次的最低层,因为只有具体组件的方法实现不需要依赖于其它对象。
<div align="center"> <img src="../pics//137c593d-0a9e-47b8-a9e6-b71f540b82dd.png"/> </div><br> <div align="center"> <img src="../pics//137c593d-0a9e-47b8-a9e6-b71f540b82dd.png"/> </div><br>
@ -2772,7 +2771,7 @@ public class Client {
### 实现 ### 实现
观看电影需要操作很多电器,使用外观模式可以实现一键看电影功能。 观看电影需要操作很多电器,使用外观模式实现一键看电影功能。
```java ```java
public class SubSystem { public class SubSystem {
@ -2813,7 +2812,7 @@ public class Client {
### 设计原则 ### 设计原则
最少知识原则:只和你的密友谈话。也就是客户对象所需要交互的对象应当尽可能少。 最少知识原则:只和你的密友谈话。也就是客户对象所需要交互的对象应当尽可能少。
## 6. 享元Flyweight ## 6. 享元Flyweight
@ -2824,8 +2823,8 @@ public class Client {
### 类图 ### 类图
- Flyweight享元对象 - Flyweight享元对象
- IntrinsicState内部状态相同的项元对象共享 - IntrinsicState内部状态享元对象共享内部状态
- ExtrinsicState外部状态 - ExtrinsicState外部状态,每个享元对象的外部状态不同
<div align="center"> <img src="../pics//d52270b4-9097-4667-9f18-f405fc661c99.png"/> </div><br> <div align="center"> <img src="../pics//d52270b4-9097-4667-9f18-f405fc661c99.png"/> </div><br>
@ -2856,8 +2855,6 @@ public class ConcreteFlyweight implements Flyweight {
``` ```
```java ```java
import java.util.HashMap;
public class FlyweightFactory { public class FlyweightFactory {
private HashMap<String, Flyweight> flyweights = new HashMap<>(); private HashMap<String, Flyweight> flyweights = new HashMap<>();
@ -2915,7 +2912,7 @@ Java 利用缓存来加速大量小对象的访问时间。
- 远程代理Remote Proxy控制对远程对象不同地址空间的访问它负责将请求及其参数进行编码并向不同地址空间中的对象发送已经编码的请求。 - 远程代理Remote Proxy控制对远程对象不同地址空间的访问它负责将请求及其参数进行编码并向不同地址空间中的对象发送已经编码的请求。
- 虚拟代理Virtual Proxy根据需要创建开销很大的对象它可以缓存实体的附加信息以便延迟对它的访问例如在网站加载一个很大图片时不能马上完成可以用虚拟代理缓存图片的大小信息然后生成一张临时图片代替原始图片。 - 虚拟代理Virtual Proxy根据需要创建开销很大的对象它可以缓存实体的附加信息以便延迟对它的访问例如在网站加载一个很大图片时不能马上完成可以用虚拟代理缓存图片的大小信息然后生成一张临时图片代替原始图片。
- 保护代理Protection Proxy按权限控制对象的访问它负责检查调用者是否具有实现一个请求所必须的访问权限。 - 保护代理Protection Proxy按权限控制对象的访问它负责检查调用者是否具有实现一个请求所必须的访问权限。
- 智能代理Smart Reference取代了简单的指针它在访问对象时执行一些附加操作记录对象的引用次数,比如智能智能;当第一次引用一个持久化对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。 - 智能代理Smart Reference取代了简单的指针它在访问对象时执行一些附加操作记录对象的引用次数当第一次引用一个持久化对象时将它装入内存在访问一个实际对象前检查是否已经锁定了它以确保其它对象不能改变它。
<div align="center"> <img src="../pics//a6c20f60-5eba-427d-9413-352ada4b40fe.png"/> </div><br> <div align="center"> <img src="../pics//a6c20f60-5eba-427d-9413-352ada4b40fe.png"/> </div><br>

View File

@ -1,6 +1,6 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [一、负载均衡](#一负载均衡) * [一、负载均衡](#一负载均衡)
* [算法实现](#算法实现) * [负载均衡算法](#负载均衡算法)
* [转发实现](#转发实现) * [转发实现](#转发实现)
* [二、集群下的 Session 管理](#二集群下的-session-管理) * [二、集群下的 Session 管理](#二集群下的-session-管理)
* [Sticky Session](#sticky-session) * [Sticky Session](#sticky-session)
@ -11,27 +11,27 @@
# 一、负载均衡 # 一、负载均衡
集群中的应用服务器通常被设计成无状态,用户可以请求任何一个节点(应用服务器 集群中的应用服务器(节点)通常被设计成无状态,用户可以请求任何一个应用服务器。
负载均衡器会根据集群中每个节点的负载情况,将用户请求转发到合适的节点上。 负载均衡器会根据集群中每个节点的负载情况,将用户请求转发到合适的节点上。
负载均衡器可以用来实现高可用以及伸缩性: 负载均衡器可以用来实现高可用以及伸缩性:
- 高可用:当某个节点故障时,负载均衡器不会将用户请求转发到该节点上,从而保证所有服务持续可用; - 高可用:当某个节点故障时,负载均衡器会将用户请求转发到另外的节点上,从而保证所有服务持续可用;
- 伸缩性:可以很容易地添加移除节点。 - 伸缩性:可以很容易地添加移除节点。
负载均衡运行过程包含两个部分: 负载均衡运行过程包含两个部分:
1. 根据负载均衡算法得到请求转发的节点; 1. 根据负载均衡算法得到请求转发的节点;
2. 将请求进行转发 2. 将请求进行转发
## 算法实现 ## 负载均衡算法
### 1. 轮询Round Robin ### 1. 轮询Round Robin
轮询算法把每个请求轮流发送到每个服务器上。 轮询算法把每个请求轮流发送到每个服务器上。
下图中,一共有 6 个客户端产生了 6 个请求,这 6 个请求按 (1, 2, 3, 4, 5, 6) 的顺序发送。最后,(1, 3, 5) 的请求会被发送到服务器 1(2, 4, 6) 的请求会被发送到服务器 2。 下图中,一共有 6 个客户端产生了 6 个请求,这 6 个请求按 (1, 2, 3, 4, 5, 6) 的顺序发送。(1, 3, 5) 的请求会被发送到服务器 1(2, 4, 6) 的请求会被发送到服务器 2。
<div align="center"> <img src="../pics//2766d04f-7dad-42e4-99d1-60682c9d5c61.jpg"/> </div><br> <div align="center"> <img src="../pics//2766d04f-7dad-42e4-99d1-60682c9d5c61.jpg"/> </div><br>
@ -51,7 +51,7 @@
由于每个请求的连接时间不一样,使用轮询或者加权轮询算法的话,可能会让一台服务器当前连接数过大,而另一台服务器的连接过小,造成负载不均衡。 由于每个请求的连接时间不一样,使用轮询或者加权轮询算法的话,可能会让一台服务器当前连接数过大,而另一台服务器的连接过小,造成负载不均衡。
例如下图中,(1, 3, 5) 请求会被发送到服务器 1但是 (1, 3) 很快就断开连接,此时只有 (5) 请求连接服务器 1(2, 4, 6) 请求被发送到服务器 2只有 (2) 的连接断开。该系统继续运行时,服务器 2 会承担过大的负载。 例如下图中,(1, 3, 5) 请求会被发送到服务器 1但是 (1, 3) 很快就断开连接,此时只有 (5) 请求连接服务器 1(2, 4, 6) 请求被发送到服务器 2只有 (2) 的连接断开,此时 (6, 4) 请求连接服务器 2。该系统继续运行时,服务器 2 会承担过大的负载。
<div align="center"> <img src="../pics//3b0d1aa8-d0e0-46c2-8fd1-736bf08a11aa.jpg"/> </div><br> <div align="center"> <img src="../pics//3b0d1aa8-d0e0-46c2-8fd1-736bf08a11aa.jpg"/> </div><br>
@ -100,7 +100,7 @@ HTTP 重定向负载均衡服务器使用某种负载均衡算法计算得到服
### 2. DNS 域名解析 ### 2. DNS 域名解析
在 DNS 解析域名的同时使用负载均衡算法计算服务器地址。 在 DNS 解析域名的同时使用负载均衡算法计算服务器 IP 地址。
优点: 优点:
@ -151,7 +151,9 @@ HTTP 重定向负载均衡服务器使用某种负载均衡算法计算得到服
在链路层根据负载均衡算法计算源服务器的 MAC 地址,并修改请求数据包的目的 MAC 地址,并进行转发。 在链路层根据负载均衡算法计算源服务器的 MAC 地址,并修改请求数据包的目的 MAC 地址,并进行转发。
通过配置源服务器的虚拟 IP 地址和负载均衡服务器的 IP 地址一致,从而不需要修改 IP 地址就可以进行转发。也正因为 IP 地址一样,所以源服务器的响应不需要转发回负载均衡服务器,直接转发给客户端,避免了负载均衡服务器的成为瓶颈。这是一种三角传输模式,被称为直接路由,对于提供下载和视频服务的网站来说,直接路由避免了大量的网络传输数据经过负载均衡服务器。 通过配置源服务器的虚拟 IP 地址和负载均衡服务器的 IP 地址一致,从而不需要修改 IP 地址就可以进行转发。也正因为 IP 地址一样,所以源服务器的响应不需要转发回负载均衡服务器,可以直接转发给客户端,避免了负载均衡服务器的成为瓶颈。
这是一种三角传输模式,被称为直接路由,对于提供下载和视频服务的网站来说,直接路由避免了大量的网络传输数据经过负载均衡服务器。
这是目前大型网站使用最广负载均衡转发方式,在 Linux 平台可以使用的负载均衡服务器为 LVSLinux Virtual Server 这是目前大型网站使用最广负载均衡转发方式,在 Linux 平台可以使用的负载均衡服务器为 LVSLinux Virtual Server
@ -188,7 +190,7 @@ HTTP 重定向负载均衡服务器使用某种负载均衡算法计算得到服
## Session Server ## Session Server
使用一个单独的服务器存储 Session 数据,可以使用 MySQL也使用 Redis 或者 Memcached 这种内存型数据库。 使用一个单独的服务器存储 Session 数据,可以使用传统的 MySQL也使用 Redis 或者 Memcached 这种内存型数据库。
优点: 优点:

View File

@ -37,6 +37,7 @@
```java ```java
public class Person { public class Person {
private String name; private String name;
private int gender; private int gender;
private int age; private int age;
@ -63,17 +64,20 @@ public class Person {
继承实现了 **IS-A** 关系,例如 Cat 和 Animal 就是一种 IS-A 关系,因此 Cat 可以继承自 Animal从而获得 Animal 非 private 的属性和方法。 继承实现了 **IS-A** 关系,例如 Cat 和 Animal 就是一种 IS-A 关系,因此 Cat 可以继承自 Animal从而获得 Animal 非 private 的属性和方法。
继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。
Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat 对象。父类引用指向子类对象称为 **向上转型** Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat 对象。父类引用指向子类对象称为 **向上转型**
```java ```java
Animal animal = new Cat(); Animal animal = new Cat();
``` ```
继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。
## 多态 ## 多态
多态分为编译时多态和运行时多态。编译时多态主要指方法的重载,运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定。 多态分为编译时多态和运行时多态:
- 编译时多态主要指方法的重载
- 运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定
运行时多态有三个条件: 运行时多态有三个条件:
@ -116,7 +120,7 @@ public class Music {
# 二、类图 # 二、类图
以下类图使用 [PlantUML](https://www.planttext.com/) 绘制更多语法及使用请参考http://plantuml.com/ 以下类图使用 [PlantUML](https://www.planttext.com/) 绘制更多语法及使用请参考http://plantuml.com/
## 泛化关系 (Generalization) ## 泛化关系 (Generalization)
@ -327,7 +331,7 @@ Vihicle .. N
### 2. 合成复用原则 ### 2. 合成复用原则
尽量使用对象组合,而不是继承来达到复用的目的。 尽量使用对象组合,而不是通过继承来达到复用的目的。
### 3. 共同封闭原则 ### 3. 共同封闭原则
@ -349,3 +353,4 @@ Vihicle .. N
- [看懂 UML 类图和时序图](http://design-patterns.readthedocs.io/zh_CN/latest/read_uml.html#generalization) - [看懂 UML 类图和时序图](http://design-patterns.readthedocs.io/zh_CN/latest/read_uml.html#generalization)
- [UML 系列——时序图顺序图sequence diagram](http://www.cnblogs.com/wolf-sun/p/UML-Sequence-diagram.html) - [UML 系列——时序图顺序图sequence diagram](http://www.cnblogs.com/wolf-sun/p/UML-Sequence-diagram.html)
- [面向对象编程三大特性 ------ 封装、继承、多态](http://blog.csdn.net/jianyuerensheng/article/details/51602015) - [面向对象编程三大特性 ------ 封装、继承、多态](http://blog.csdn.net/jianyuerensheng/article/details/51602015)

View File

@ -1,6 +1,6 @@
创建交流群的主要目的是为了给大家提供一个交流平台,方便大家在学习的过程中互相讨论。 创建交流群的主要目的是为了给大家提供一个交流平台,方便大家在学习的过程中互相讨论。
这个交流群不是一个笔者的问题回答群,我更希望大家能够愿意积极回答,我相信提问和回答的过程都可以帮助大家对知识的掌握程度。 这个交流群不是一个笔者的问题回答群,我更希望大家能够愿意积极回答,我相信提问和回答的过程都可以提高大家对知识的掌握程度。
因为笔者白天要上班,因此不能及时进行回复,大部分时间会处于潜水状态。 因为笔者白天要上班,因此不能及时进行回复,大部分时间会处于潜水状态。
@ -12,4 +12,4 @@
交流群不讨论政治,不讨论有争议性的话题,不发表仇视言论,不传播谣言,不发布广告(招聘信息之类的可以)。 交流群不讨论政治,不讨论有争议性的话题,不发表仇视言论,不传播谣言,不发布广告(招聘信息之类的可以)。
</br> <div align="center"><img src="group.jpg" width="300px"></div> </br> </br> <div align="center"><img src="group.png" width="300px"></div> </br>

BIN
other/group.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
pics/urlnuri.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB