auto commit
This commit is contained in:
parent
7120618dee
commit
2fb0c73341
1070
notes/2016 校招真题题解.md
1070
notes/2016 校招真题题解.md
File diff suppressed because it is too large
Load Diff
140
notes/Git.md
140
notes/Git.md
|
@ -1,15 +1,29 @@
|
|||
[TOC]
|
||||
<!-- GFM-TOC -->
|
||||
* [学习资料](#学习资料)
|
||||
* [集中式与分布式](#集中式与分布式)
|
||||
* [Git 的中心服务器](#git-的中心服务器)
|
||||
* [Git 工作流](#git-工作流)
|
||||
* [分支实现](#分支实现)
|
||||
* [冲突](#冲突)
|
||||
* [Fast forward](#fast-forward)
|
||||
* [分支管理策略](#分支管理策略)
|
||||
* [储藏(Stashing)](#储藏stashing)
|
||||
* [SSH 传输设置](#ssh-传输设置)
|
||||
* [.gitignore 文件](#gitignore-文件)
|
||||
* [Git 命令一览](#git-命令一览)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
# 学习资料
|
||||
|
||||
- [Git - 简明指南](http://rogerdudler.github.io/git-guide/index.zh.html)
|
||||
- [图解 Git](http://marklodato.github.io/visual-git-guide/index-zh-cn.html)
|
||||
- [廖雪峰 : Git 教程](https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000)
|
||||
- [Learn Git Branching](https://learngitbranching.js.org/)
|
||||
# 学习资料
|
||||
|
||||
# 集中式与分布式
|
||||
- [Git - 简明指南](http://rogerdudler.github.io/git-guide/index.zh.html)
|
||||
- [图解 Git](http://marklodato.github.io/visual-git-guide/index-zh-cn.html)
|
||||
- [廖雪峰 : Git 教程](https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000)
|
||||
- [Learn Git Branching](https://learngitbranching.js.org/)
|
||||
|
||||
Git 属于分布式版本控制系统,而 SVN 属于集中式。
|
||||
# 集中式与分布式
|
||||
|
||||
Git 属于分布式版本控制系统,而 SVN 属于集中式。
|
||||
|
||||
集中式版本控制只有中心服务器拥有一份代码,而分布式版本控制每个人的电脑上就有一份完整的代码。
|
||||
|
||||
|
@ -19,124 +33,124 @@ Git 属于分布式版本控制系统,而 SVN 属于集中式。
|
|||
|
||||
分布式版本控制新建分支、合并分支操作速度非常快,而集中式版本控制新建一个分支相当于复制一份完整代码。
|
||||
|
||||
# Git 的中心服务器
|
||||
# Git 的中心服务器
|
||||
|
||||
Git 的中心服务器用来交换每个用户的修改。没有中心服务器也能工作,但是中心服务器能够 24 小时保持开机状态,这样就能更方便的交换修改。Github 就是一种 Git 中心服务器。
|
||||
Git 的中心服务器用来交换每个用户的修改。没有中心服务器也能工作,但是中心服务器能够 24 小时保持开机状态,这样就能更方便的交换修改。Github 就是一种 Git 中心服务器。
|
||||
|
||||
# Git 工作流
|
||||
# Git 工作流
|
||||
|
||||
![](index_files/a1198642-9159-4d88-8aec-c3b04e7a2563.jpg)
|
||||
<div align="center"> <img src="../pics//a1198642-9159-4d88-8aec-c3b04e7a2563.jpg"/> </div><br>
|
||||
|
||||
新建一个仓库之后,当前目录就成为了工作区,工作区下有一个隐藏目录 .git,它属于 Git 的版本库。
|
||||
新建一个仓库之后,当前目录就成为了工作区,工作区下有一个隐藏目录 .git,它属于 Git 的版本库。
|
||||
|
||||
Git 版本库有一个称为 stage 的暂存区,还有自动创建的 master 分支以及指向分支的 HEAD 指针。
|
||||
Git 版本库有一个称为 stage 的暂存区,还有自动创建的 master 分支以及指向分支的 HEAD 指针。
|
||||
|
||||
![](index_files/46f66e88-e65a-4ad0-a060-3c63fe22947c.png)
|
||||
<div align="center"> <img src="../pics//46f66e88-e65a-4ad0-a060-3c63fe22947c.png"/> </div><br>
|
||||
|
||||
- git add files 把文件的修改添加到暂存区
|
||||
- git commit 把暂存区的修改提交到当前分支,提交之后暂存区就被清空了
|
||||
- git reset -- files 使用当前分支上的修改覆盖暂缓区,用来撤销最后一次 git add files
|
||||
- git checkout -- files 使用暂存区的修改覆盖工作目录,用来撤销本地修改
|
||||
- git add files 把文件的修改添加到暂存区
|
||||
- git commit 把暂存区的修改提交到当前分支,提交之后暂存区就被清空了
|
||||
- git reset -- files 使用当前分支上的修改覆盖暂缓区,用来撤销最后一次 git add files
|
||||
- git checkout -- files 使用暂存区的修改覆盖工作目录,用来撤销本地修改
|
||||
|
||||
![](index_files/17976404-95f5-480e-9cb4-250e6aa1d55f.png)
|
||||
<div align="center"> <img src="../pics//17976404-95f5-480e-9cb4-250e6aa1d55f.png"/> </div><br>
|
||||
|
||||
可以跳过暂存区域直接从分支中取出修改或者直接提交修改到分支中
|
||||
|
||||
- git commit -a 直接把所有文件的修改添加到暂缓区然后执行提交
|
||||
- git checkout HEAD -- files 取出最后一次修改,可以用来进行回滚操作
|
||||
- git commit -a 直接把所有文件的修改添加到暂缓区然后执行提交
|
||||
- git checkout HEAD -- files 取出最后一次修改,可以用来进行回滚操作
|
||||
|
||||
# 分支实现
|
||||
# 分支实现
|
||||
|
||||
Git 把每次提交都连成一条时间线。分支使用指针来实现,例如 master 分支指针指向时间线的最后一个节点,也就是最后一次提交。HEAD 指针指向的是当前分支。
|
||||
Git 把每次提交都连成一条时间线。分支使用指针来实现,例如 master 分支指针指向时间线的最后一个节点,也就是最后一次提交。HEAD 指针指向的是当前分支。
|
||||
|
||||
![](index_files/fb546e12-e1fb-4b72-a1fb-8a7f5000dce6.jpg)
|
||||
<div align="center"> <img src="../pics//fb546e12-e1fb-4b72-a1fb-8a7f5000dce6.jpg"/> </div><br>
|
||||
|
||||
新建分支是新建一个指针指向时间线的最后一个节点,并让 HEAD 指针指向新分支表示新分支成为当前分支。
|
||||
新建分支是新建一个指针指向时间线的最后一个节点,并让 HEAD 指针指向新分支表示新分支成为当前分支。
|
||||
|
||||
![](index_files/bc775758-89ab-4805-9f9c-78b8739cf780.jpg)
|
||||
<div align="center"> <img src="../pics//bc775758-89ab-4805-9f9c-78b8739cf780.jpg"/> </div><br>
|
||||
|
||||
每次提交只会让当前分支向前移动,而其它分支不会移动。
|
||||
|
||||
![](index_files/5292faa6-0141-4638-bf0f-bb95b081dcba.jpg)
|
||||
<div align="center"> <img src="../pics//5292faa6-0141-4638-bf0f-bb95b081dcba.jpg"/> </div><br>
|
||||
|
||||
合并分支也只需要改变指针即可。
|
||||
|
||||
![](index_files/1164a71f-413d-494a-9cc8-679fb6a2613d.jpg)
|
||||
<div align="center"> <img src="../pics//1164a71f-413d-494a-9cc8-679fb6a2613d.jpg"/> </div><br>
|
||||
|
||||
# 冲突
|
||||
# 冲突
|
||||
|
||||
当两个分支都对同一个文件进行了修改,在分支合并时就会产生冲突。
|
||||
|
||||
![](index_files/58e57a21-6b6b-40b6-af85-956dd4e0f55a.jpg)
|
||||
<div align="center"> <img src="../pics//58e57a21-6b6b-40b6-af85-956dd4e0f55a.jpg"/> </div><br>
|
||||
|
||||
Git 会使用 <<<<<<< ,======= ,>>>>>>> 标记出不同分支的内容,只需要把不同分支中冲突部分修改成一样就能解决冲突。
|
||||
Git 会使用 <<<<<<< ,======= ,>>>>>>> 标记出不同分支的内容,只需要把不同分支中冲突部分修改成一样就能解决冲突。
|
||||
|
||||
```
|
||||
<<<<<<< HEAD
|
||||
Creating a new branch is quick & simple.
|
||||
<<<<<<< HEAD
|
||||
Creating a new branch is quick & simple.
|
||||
=======
|
||||
Creating a new branch is quick AND simple.
|
||||
>>>>>>> feature1
|
||||
Creating a new branch is quick AND simple.
|
||||
>>>>>>> feature1
|
||||
```
|
||||
|
||||
# Fast forward
|
||||
# Fast forward
|
||||
|
||||
"快进式合并"(fast-farward merge),会直接将 master 分支指向合并的分支,这种模式下进行分支合并会丢失分支信息,也就不能在分支历史上看出分支信息。
|
||||
"快进式合并"(fast-farward merge),会直接将 master 分支指向合并的分支,这种模式下进行分支合并会丢失分支信息,也就不能在分支历史上看出分支信息。
|
||||
|
||||
可以在合并时加上 --no-ff 参数来禁用 Fast forward 模式,并且加上 -m 参数让合并时产生一个新的 commit。
|
||||
可以在合并时加上 --no-ff 参数来禁用 Fast forward 模式,并且加上 -m 参数让合并时产生一个新的 commit。
|
||||
|
||||
```
|
||||
$ git merge --no-ff -m "merge with no-ff" dev
|
||||
$ git merge --no-ff -m "merge with no-ff" dev
|
||||
```
|
||||
|
||||
![](index_files/dd78a1fe-1ff3-4bcf-a56f-8c003995beb6.jpg)
|
||||
<div align="center"> <img src="../pics//dd78a1fe-1ff3-4bcf-a56f-8c003995beb6.jpg"/> </div><br>
|
||||
|
||||
# 分支管理策略
|
||||
# 分支管理策略
|
||||
|
||||
master 分支应该是非常稳定的,只用来发布新版本;
|
||||
master 分支应该是非常稳定的,只用来发布新版本;
|
||||
|
||||
日常开发在开发分支 dev 上进行。
|
||||
日常开发在开发分支 dev 上进行。
|
||||
|
||||
![](index_files/245fd2fb-209c-4ad5-bc5e-eb5664966a0e.jpg)
|
||||
<div align="center"> <img src="../pics//245fd2fb-209c-4ad5-bc5e-eb5664966a0e.jpg"/> </div><br>
|
||||
|
||||
# 储藏(Stashing)
|
||||
# 储藏(Stashing)
|
||||
|
||||
在一个分支上操作之后,如果还没有将修改提交到分支上,此时进行切换分支,那么另一个分支上也能看到新的修改。这是因为所有分支都共用一个工作区的缘故。
|
||||
|
||||
可以使用 git stash 将当前分支的修改储藏起来,此时当前工作区的所有修改都会被存到栈上,也就是说当前工作区是干净的,没有任何未提交的修改。此时就可以安全的切换到其它分支上了。
|
||||
可以使用 git stash 将当前分支的修改储藏起来,此时当前工作区的所有修改都会被存到栈上,也就是说当前工作区是干净的,没有任何未提交的修改。此时就可以安全的切换到其它分支上了。
|
||||
|
||||
```
|
||||
$ git stash
|
||||
Saved working directory and index state \ "WIP on master: 049d078 added the index file"
|
||||
HEAD is now at 049d078 added the index file (To restore them type "git stash apply")
|
||||
$ git stash
|
||||
Saved working directory and index state \ "WIP on master: 049d078 added the index file"
|
||||
HEAD is now at 049d078 added the index file (To restore them type "git stash apply")
|
||||
```
|
||||
|
||||
该功能可以用于 bug 分支的实现。如果当前正在 dev 分支上进行开发,但是此时 master 上有个 bug 需要修复,但是 dev 分支上的开发还未完成,不想立即提交。在新建 bug 分支并切换到 bug 分支之前就需要使用 git stash 将 dev 分支的未提交修改储藏起来。
|
||||
该功能可以用于 bug 分支的实现。如果当前正在 dev 分支上进行开发,但是此时 master 上有个 bug 需要修复,但是 dev 分支上的开发还未完成,不想立即提交。在新建 bug 分支并切换到 bug 分支之前就需要使用 git stash 将 dev 分支的未提交修改储藏起来。
|
||||
|
||||
# SSH 传输设置
|
||||
# SSH 传输设置
|
||||
|
||||
Git 仓库和 Github 中心仓库之间是通过 SSH 加密。
|
||||
Git 仓库和 Github 中心仓库之间是通过 SSH 加密。
|
||||
|
||||
如果工作区下没有 .ssh 目录,或者该目录下没有 id_rsa 和 id_rsa.pub 这两个文件,可以通过以下命令来创建 SSH Key:
|
||||
如果工作区下没有 .ssh 目录,或者该目录下没有 id_rsa 和 id_rsa.pub 这两个文件,可以通过以下命令来创建 SSH Key:
|
||||
|
||||
```
|
||||
$ ssh-keygen -t rsa -C "youremail@example.com"
|
||||
$ ssh-keygen -t rsa -C "youremail@example.com"
|
||||
```
|
||||
|
||||
然后把公钥 id_rsa.pub 的内容复制到 Github "Account settings" 的 SSH Keys 中。
|
||||
然后把公钥 id_rsa.pub 的内容复制到 Github "Account settings" 的 SSH Keys 中。
|
||||
|
||||
# .gitignore 文件
|
||||
# .gitignore 文件
|
||||
|
||||
忽略以下文件:
|
||||
|
||||
1. 操作系统自动生成的文件,比如缩略图;
|
||||
2. 编译生成的中间文件,比如 Java 编译产生的 .class 文件;
|
||||
3. 自己的敏感信息,比如存放口令的配置文件。
|
||||
1. 操作系统自动生成的文件,比如缩略图;
|
||||
2. 编译生成的中间文件,比如 Java 编译产生的 .class 文件;
|
||||
3. 自己的敏感信息,比如存放口令的配置文件。
|
||||
|
||||
不需要全部自己编写,可以到 [https://github.com/github/gitignore](https://github.com/github/gitignore) 中进行查询。
|
||||
不需要全部自己编写,可以到 [https://github.com/github/gitignore](https://github.com/github/gitignore) 中进行查询。
|
||||
|
||||
# Git 命令一览
|
||||
# Git 命令一览
|
||||
|
||||
![](index_files/7a29acce-f243-4914-9f00-f2988c528412.jpg)
|
||||
<div align="center"> <img src="../pics//7a29acce-f243-4914-9f00-f2988c528412.jpg"/> </div><br>
|
||||
|
||||
比较详细的地址:http://www.cheat-sheets.org/saved-copy/git-cheat-sheet.pdf
|
||||
|
|
589
notes/HTTP.md
589
notes/HTTP.md
|
@ -1,505 +1,550 @@
|
|||
[TOC]
|
||||
<!-- GFM-TOC -->
|
||||
* [基础概念](#基础概念)
|
||||
* [Web 基础](#web-基础)
|
||||
* [URL](#url)
|
||||
* [请求和响应报文](#请求和响应报文)
|
||||
* [HTTP 方法](#http-方法)
|
||||
* [GET:获取资源](#get获取资源)
|
||||
* [POST:传输实体主体](#post传输实体主体)
|
||||
* [HEAD:获取报文首部](#head获取报文首部)
|
||||
* [PUT:上传文件](#put上传文件)
|
||||
* [PATCH:对资源进行部分修改](#patch对资源进行部分修改)
|
||||
* [DELETE:删除文件](#delete删除文件)
|
||||
* [OPTIONS:查询支持的方法](#options查询支持的方法)
|
||||
* [CONNECT:要求用隧道协议连接代理](#connect要求用隧道协议连接代理)
|
||||
* [TRACE:追踪路径](#trace追踪路径)
|
||||
* [HTTP 状态码](#http-状态码)
|
||||
* [2XX 成功](#2xx-成功)
|
||||
* [3XX 重定向](#3xx-重定向)
|
||||
* [4XX 客户端错误](#4xx-客户端错误)
|
||||
* [5XX 服务器错误](#5xx-服务器错误)
|
||||
* [HTTP 首部](#http-首部)
|
||||
* [通用首部字段](#通用首部字段)
|
||||
* [请求首部字段](#请求首部字段)
|
||||
* [响应首部字段](#响应首部字段)
|
||||
* [实体首部字段](#实体首部字段)
|
||||
* [具体应用](#具体应用)
|
||||
* [Cookie](#cookie)
|
||||
* [缓存](#缓存)
|
||||
* [持久连接](#持久连接)
|
||||
* [编码](#编码)
|
||||
* [分块传输](#分块传输)
|
||||
* [多部分对象集合](#多部分对象集合)
|
||||
* [范围请求](#范围请求)
|
||||
* [内容协商](#内容协商)
|
||||
* [虚拟主机](#虚拟主机)
|
||||
* [通信数据转发](#通信数据转发)
|
||||
* [HTTPs](#https)
|
||||
* [加密](#加密)
|
||||
* [认证](#认证)
|
||||
* [完整性](#完整性)
|
||||
* [各版本比较](#各版本比较)
|
||||
* [HTTP/1.0 与 HTTP/1.1 的区别](#http10-与-http11-的区别)
|
||||
* [HTTP/1.1 与 HTTP/2.0 的区别](#http11-与-http20-的区别)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
# 基础概念
|
||||
|
||||
## Web 基础
|
||||
# 基础概念
|
||||
|
||||
- HTTP(HyperText Transfer Protocol,超文本传输协议)。
|
||||
- WWW(World Wide Web)的三种技术:HTML、HTTP、URL。
|
||||
- RFC(Request for Comments,征求修正意见书),互联网的设计文档。
|
||||
## Web 基础
|
||||
|
||||
## URL
|
||||
- HTTP(HyperText Transfer Protocol,超文本传输协议)。
|
||||
- WWW(World Wide Web)的三种技术:HTML、HTTP、URL。
|
||||
- RFC(Request for Comments,征求修正意见书),互联网的设计文档。
|
||||
|
||||
- URI(Uniform Resource Indentifier,统一资源标识符)
|
||||
- URL(Uniform Resource Locator,统一资源定位符)
|
||||
- URN(Uniform Resource Name,统一资源名称),例如 urn:isbn:0-486-27557-4 。
|
||||
## URL
|
||||
|
||||
URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。
|
||||
- URI(Uniform Resource Indentifier,统一资源标识符)
|
||||
- URL(Uniform Resource Locator,统一资源定位符)
|
||||
- URN(Uniform Resource Name,统一资源名称),例如 urn:isbn:0-486-27557-4 。
|
||||
|
||||
![](index_files/4102b7d0-39b9-48d8-82ae-ac4addb7ebfb.jpg)
|
||||
URI 包含 URL 和 URN,目前 WEB 只有 URL 比较流行,所以见到的基本都是 URL。
|
||||
|
||||
## 请求和响应报文
|
||||
<div align="center"> <img src="../pics//4102b7d0-39b9-48d8-82ae-ac4addb7ebfb.jpg"/> </div><br>
|
||||
|
||||
**请求报文**
|
||||
## 请求和响应报文
|
||||
|
||||
![](index_files/22b39f77-ac47-4978-91ed-84aaf457644c.jpg)
|
||||
**请求报文**
|
||||
|
||||
**响应报文**
|
||||
<div align="center"> <img src="../pics//22b39f77-ac47-4978-91ed-84aaf457644c.jpg"/> </div><br>
|
||||
|
||||
![](index_files/00d8d345-cd4a-48af-919e-209d2788eca7.jpg)
|
||||
**响应报文**
|
||||
|
||||
# HTTP 方法
|
||||
<div align="center"> <img src="../pics//00d8d345-cd4a-48af-919e-209d2788eca7.jpg"/> </div><br>
|
||||
|
||||
客户端发送的 **请求报文** 第一行为请求行,包含了方法字段。
|
||||
# HTTP 方法
|
||||
|
||||
## GET:获取资源
|
||||
客户端发送的 **请求报文** 第一行为请求行,包含了方法字段。
|
||||
|
||||
## POST:传输实体主体
|
||||
## GET:获取资源
|
||||
|
||||
POST 主要目的不是获取资源,而是传输存储在内容实体中的数据。
|
||||
## POST:传输实体主体
|
||||
|
||||
GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在内容实体。
|
||||
POST 主要目的不是获取资源,而是传输存储在内容实体中的数据。
|
||||
|
||||
GET 和 POST 的请求都能使用额外的参数,但是 GET 的参数是以查询字符串出现在 URL 中,而 POST 的参数存储在内容实体。
|
||||
|
||||
```
|
||||
GET /test/demo_form.asp?name1=value1&name2=value2 HTTP/1.1
|
||||
GET /test/demo_form.asp?name1=value1&name2=value2 HTTP/1.1
|
||||
```
|
||||
|
||||
```
|
||||
POST /test/demo_form.asp HTTP/1.1
|
||||
Host: w3schools.com
|
||||
POST /test/demo_form.asp HTTP/1.1
|
||||
Host: w3schools.com
|
||||
name1=value1&name2=value2
|
||||
```
|
||||
|
||||
GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 URL 中是可见的,可能会泄露私密信息。并且 GET 只支持 ASCII 字符,如果参数为中文则可能会出现乱码,而 POST 支持标准字符集。
|
||||
GET 的传参方式相比于 POST 安全性较差,因为 GET 传的参数在 URL 中是可见的,可能会泄露私密信息。并且 GET 只支持 ASCII 字符,如果参数为中文则可能会出现乱码,而 POST 支持标准字符集。
|
||||
|
||||
## HEAD:获取报文首部
|
||||
## HEAD:获取报文首部
|
||||
|
||||
和 GET 方法一样,但是不返回报文实体主体部分。
|
||||
和 GET 方法一样,但是不返回报文实体主体部分。
|
||||
|
||||
主要用于确认 URL 的有效性以及资源更新的日期时间等。
|
||||
主要用于确认 URL 的有效性以及资源更新的日期时间等。
|
||||
|
||||
## PUT:上传文件
|
||||
## PUT:上传文件
|
||||
|
||||
由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般不使用该方法。
|
||||
|
||||
```html
|
||||
PUT /new.html HTTP/1.1
|
||||
Host: example.com
|
||||
Content-type: text/html
|
||||
Content-length: 16
|
||||
PUT /new.html HTTP/1.1
|
||||
Host: example.com
|
||||
Content-type: text/html
|
||||
Content-length: 16
|
||||
|
||||
<p>New File</p>
|
||||
<p>New File</p>
|
||||
```
|
||||
|
||||
## PATCH:对资源进行部分修改
|
||||
## PATCH:对资源进行部分修改
|
||||
|
||||
PUT 也可以用于修改资源,但是只能完全替代原始资源,PATCH 允许部分修改。
|
||||
PUT 也可以用于修改资源,但是只能完全替代原始资源,PATCH 允许部分修改。
|
||||
|
||||
```html
|
||||
PATCH /file.txt HTTP/1.1
|
||||
Host: www.example.com
|
||||
Content-Type: application/example
|
||||
If-Match: "e0023aa4e"
|
||||
Content-Length: 100
|
||||
PATCH /file.txt HTTP/1.1
|
||||
Host: www.example.com
|
||||
Content-Type: application/example
|
||||
If-Match: "e0023aa4e"
|
||||
Content-Length: 100
|
||||
|
||||
[description of changes]
|
||||
[description of changes]
|
||||
```
|
||||
|
||||
## DELETE:删除文件
|
||||
## DELETE:删除文件
|
||||
|
||||
与 PUT 功能相反,并且同样不带验证机制。
|
||||
与 PUT 功能相反,并且同样不带验证机制。
|
||||
|
||||
```html
|
||||
DELETE /file.html HTTP/1.1
|
||||
DELETE /file.html HTTP/1.1
|
||||
```
|
||||
|
||||
## OPTIONS:查询支持的方法
|
||||
## OPTIONS:查询支持的方法
|
||||
|
||||
查询指定的 URL 能够支持的方法。
|
||||
查询指定的 URL 能够支持的方法。
|
||||
|
||||
会返回 Allow: GET, POST, HEAD, OPTIONS 这样的内容。
|
||||
会返回 Allow: GET, POST, HEAD, OPTIONS 这样的内容。
|
||||
|
||||
## CONNECT:要求用隧道协议连接代理
|
||||
## CONNECT:要求用隧道协议连接代理
|
||||
|
||||
要求在于代理服务器通信时建立隧道,使用 SSL(Secure Sokets Layer,安全套接字)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。
|
||||
要求在于代理服务器通信时建立隧道,使用 SSL(Secure Sokets Layer,安全套接字)和 TLS(Transport Layer Security,传输层安全)协议把通信内容加密后经网络隧道传输。
|
||||
|
||||
```html
|
||||
CONNECT www.example.com:443 HTTP/1.1
|
||||
CONNECT www.example.com:443 HTTP/1.1
|
||||
```
|
||||
|
||||
![](index_files/5994928c-3d2d-45bd-abb1-adc4f5f4d775.jpg)
|
||||
<div align="center"> <img src="../pics//5994928c-3d2d-45bd-abb1-adc4f5f4d775.jpg"/> </div><br>
|
||||
|
||||
## TRACE:追踪路径
|
||||
## TRACE:追踪路径
|
||||
|
||||
服务器会将通信路径返回给客户端。
|
||||
|
||||
发送请求时,在 Max-Forwards 首部字段中填入数值,每经过一个服务器就会减 1,当数值为 0 时就停止传输。
|
||||
发送请求时,在 Max-Forwards 首部字段中填入数值,每经过一个服务器就会减 1,当数值为 0 时就停止传输。
|
||||
|
||||
通常不会使用 TRACE,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪),因此更不会去使用它。
|
||||
通常不会使用 TRACE,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪),因此更不会去使用它。
|
||||
|
||||
![](index_files/c8637fd2-3aaa-46c4-b7d9-f24d3fa04781.jpg)
|
||||
<div align="center"> <img src="../pics//c8637fd2-3aaa-46c4-b7d9-f24d3fa04781.jpg"/> </div><br>
|
||||
|
||||
# HTTP 状态码
|
||||
# HTTP 状态码
|
||||
|
||||
服务器返回的 **响应报文** 中第一行为状态行,包含了状态码以及原因短语,用来告知客户端请求的结果。
|
||||
服务器返回的 **响应报文** 中第一行为状态行,包含了状态码以及原因短语,用来告知客户端请求的结果。
|
||||
|
||||
| 状态码 | 类别 | 原因短语 |
|
||||
| --- | --- | --- |
|
||||
| 1XX | Informational(信息性状态码) | 接收的请求正在处理 |
|
||||
| 2XX | Success(成功状态码) | 请求正常处理完毕 |
|
||||
| 3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
|
||||
| 4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
|
||||
| 5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
|
||||
| 状态码 | 类别 | 原因短语 |
|
||||
| --- | --- | --- |
|
||||
| 1XX | Informational(信息性状态码) | 接收的请求正在处理 |
|
||||
| 2XX | Success(成功状态码) | 请求正常处理完毕 |
|
||||
| 3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
|
||||
| 4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
|
||||
| 5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
|
||||
|
||||
## 2XX 成功
|
||||
## 2XX 成功
|
||||
|
||||
- **200 OK**
|
||||
- **200 OK**
|
||||
|
||||
- **204 No Content**:请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。
|
||||
- **204 No Content** :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。
|
||||
|
||||
- **206 Partial Content**:表示客户端进行了范围请求。响应报文包含由 Content-Range 指定范围的实体内容。
|
||||
- **206 Partial Content** :表示客户端进行了范围请求。响应报文包含由 Content-Range 指定范围的实体内容。
|
||||
|
||||
## 3XX 重定向
|
||||
## 3XX 重定向
|
||||
|
||||
- **301 Moved Permanently**:永久性重定向
|
||||
- **301 Moved Permanently** :永久性重定向
|
||||
|
||||
- **302 Found**:临时性重定向
|
||||
- **302 Found** :临时性重定向
|
||||
|
||||
- **303 See Other**:和 302 有着相同的功能,但是 303 明确要求客户端应该采用 GET 方法获取资源。
|
||||
- **303 See Other** :和 302 有着相同的功能,但是 303 明确要求客户端应该采用 GET 方法获取资源。
|
||||
|
||||
- 注:虽然 HTTP 协议规定 301、302 状态下重定向时不允许把 POST 方法改成 GET 方法,但是大多数浏览器都会 在 301、302 和 303 状态下的重定向把 POST 方法改成 GET 方法。
|
||||
- 注:虽然 HTTP 协议规定 301、302 状态下重定向时不允许把 POST 方法改成 GET 方法,但是大多数浏览器都会 在 301、302 和 303 状态下的重定向把 POST 方法改成 GET 方法。
|
||||
|
||||
- **304 Not Modified**:如果请求报文首部包含一些条件,例如:If-Match,If-ModifiedSince,If-None-Match,If-Range,If-Unmodified-Since,但是不满足条件,则服务器会返回 304 状态码。
|
||||
- **304 Not Modified** :如果请求报文首部包含一些条件,例如:If-Match,If-ModifiedSince,If-None-Match,If-Range,If-Unmodified-Since,但是不满足条件,则服务器会返回 304 状态码。
|
||||
|
||||
- **307 Temporary Redirect**:临时重定向,与 302 的含义类似,但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法。
|
||||
- **307 Temporary Redirect** :临时重定向,与 302 的含义类似,但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法。
|
||||
|
||||
## 4XX 客户端错误
|
||||
## 4XX 客户端错误
|
||||
|
||||
- **400 Bad Request**:请求报文中存在语法错误。
|
||||
- **400 Bad Request** :请求报文中存在语法错误。
|
||||
|
||||
- **401 Unauthorized**:该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。
|
||||
- **401 Unauthorized** :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。
|
||||
|
||||
![](index_files/b1b4cf7d-c54a-4ff1-9741-cd2eea331123.jpg)
|
||||
<div align="center"> <img src="../pics//b1b4cf7d-c54a-4ff1-9741-cd2eea331123.jpg"/> </div><br>
|
||||
|
||||
- **403 Forbidden**:请求被拒绝,服务器端没有必要给出拒绝的详细理由。
|
||||
- **403 Forbidden** :请求被拒绝,服务器端没有必要给出拒绝的详细理由。
|
||||
|
||||
- **404 Not Found**
|
||||
- **404 Not Found**
|
||||
|
||||
## 5XX 服务器错误
|
||||
## 5XX 服务器错误
|
||||
|
||||
- **500 Internal Server Error**:服务器正在执行请求时发生错误。
|
||||
- **500 Internal Server Error** :服务器正在执行请求时发生错误。
|
||||
|
||||
- **503 Service Unavilable**:服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。
|
||||
- **503 Service Unavilable** :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。
|
||||
|
||||
# HTTP 首部
|
||||
# HTTP 首部
|
||||
|
||||
有 4 种类型的首部字段:通用首部字段、请求首部字段、响应首部字段和实体首部字段。
|
||||
有 4 种类型的首部字段:通用首部字段、请求首部字段、响应首部字段和实体首部字段。
|
||||
|
||||
各种首部字段及其含义如下(不需要全记,仅供查阅):
|
||||
|
||||
## 通用首部字段
|
||||
## 通用首部字段
|
||||
|
||||
| 首部字段名 | 说明 |
|
||||
| -- | -- |
|
||||
| Cache-Control | 控制缓存的行为 |
|
||||
| Connection | 控制不再转发给代理的首部字段;管理持久连接|
|
||||
| Date | 创建报文的日期时间 |
|
||||
| Pragma | 报文指令 |
|
||||
| Trailer | 报文末端的首部一览 |
|
||||
| Transfer-Encoding | 指定报文主体的传输编码方式 |
|
||||
| Upgrade | 升级为其他协议 |
|
||||
| Via | 代理服务器的相关信息 |
|
||||
| Warning | 错误通知 |
|
||||
| 首部字段名 | 说明 |
|
||||
| -- | -- |
|
||||
| Cache-Control | 控制缓存的行为 |
|
||||
| Connection | 控制不再转发给代理的首部字段;管理持久连接|
|
||||
| Date | 创建报文的日期时间 |
|
||||
| Pragma | 报文指令 |
|
||||
| Trailer | 报文末端的首部一览 |
|
||||
| Transfer-Encoding | 指定报文主体的传输编码方式 |
|
||||
| Upgrade | 升级为其他协议 |
|
||||
| Via | 代理服务器的相关信息 |
|
||||
| Warning | 错误通知 |
|
||||
|
||||
## 请求首部字段
|
||||
## 请求首部字段
|
||||
|
||||
| 首部字段名 | 说明 |
|
||||
| -- | -- |
|
||||
| Accept | 用户代理可处理的媒体类型 |
|
||||
| Accept-Charset | 优先的字符集 |
|
||||
| Accept-Encoding | 优先的内容编码 |
|
||||
| Accept-Language | 优先的语言(自然语言) |
|
||||
| Authorization | Web 认证信息 |
|
||||
| Expect | 期待服务器的特定行为 |
|
||||
| From | 用户的电子邮箱地址 |
|
||||
| Host | 请求资源所在服务器 |
|
||||
| If-Match | 比较实体标记(ETag) |
|
||||
| If-Modified-Since | 比较资源的更新时间 |
|
||||
| If-None-Match | 比较实体标记(与 If-Match 相反) |
|
||||
| If-Range | 资源未更新时发送实体 Byte 的范围请求 |
|
||||
| If-Unmodified-Since | 比较资源的更新时间(与 If-Modified-Since 相反) |
|
||||
| Max-Forwards | 最大传输逐跳数 |
|
||||
| Proxy-Authorization | 代理服务器要求客户端的认证信息 |
|
||||
| Range | 实体的字节范围请求 |
|
||||
| Referer | 对请求中 URI 的原始获取方 |
|
||||
| TE | 传输编码的优先级 |
|
||||
| User-Agent | HTTP 客户端程序的信息 |
|
||||
| 首部字段名 | 说明 |
|
||||
| -- | -- |
|
||||
| Accept | 用户代理可处理的媒体类型 |
|
||||
| Accept-Charset | 优先的字符集 |
|
||||
| Accept-Encoding | 优先的内容编码 |
|
||||
| Accept-Language | 优先的语言(自然语言) |
|
||||
| Authorization | Web 认证信息 |
|
||||
| Expect | 期待服务器的特定行为 |
|
||||
| From | 用户的电子邮箱地址 |
|
||||
| Host | 请求资源所在服务器 |
|
||||
| If-Match | 比较实体标记(ETag) |
|
||||
| If-Modified-Since | 比较资源的更新时间 |
|
||||
| If-None-Match | 比较实体标记(与 If-Match 相反) |
|
||||
| If-Range | 资源未更新时发送实体 Byte 的范围请求 |
|
||||
| If-Unmodified-Since | 比较资源的更新时间(与 If-Modified-Since 相反) |
|
||||
| Max-Forwards | 最大传输逐跳数 |
|
||||
| Proxy-Authorization | 代理服务器要求客户端的认证信息 |
|
||||
| Range | 实体的字节范围请求 |
|
||||
| Referer | 对请求中 URI 的原始获取方 |
|
||||
| TE | 传输编码的优先级 |
|
||||
| User-Agent | HTTP 客户端程序的信息 |
|
||||
|
||||
## 响应首部字段
|
||||
## 响应首部字段
|
||||
|
||||
| 首部字段名 | 说明 |
|
||||
| -- | -- |
|
||||
| Accept-Ranges | 是否接受字节范围请求 |
|
||||
| Age | 推算资源创建经过时间 |
|
||||
| ETag | 资源的匹配信息 |
|
||||
| Location | 令客户端重定向至指定 URI |
|
||||
| Proxy-Authenticate | 代理服务器对客户端的认证信息 |
|
||||
| Retry-After | 对再次发起请求的时机要求 |
|
||||
| Server | HTTP 服务器的安装信息 |
|
||||
| Vary | 代理服务器缓存的管理信息 |
|
||||
| WWW-Authenticate | 服务器对客户端的认证信息 |
|
||||
| 首部字段名 | 说明 |
|
||||
| -- | -- |
|
||||
| Accept-Ranges | 是否接受字节范围请求 |
|
||||
| Age | 推算资源创建经过时间 |
|
||||
| ETag | 资源的匹配信息 |
|
||||
| Location | 令客户端重定向至指定 URI |
|
||||
| Proxy-Authenticate | 代理服务器对客户端的认证信息 |
|
||||
| Retry-After | 对再次发起请求的时机要求 |
|
||||
| Server | HTTP 服务器的安装信息 |
|
||||
| Vary | 代理服务器缓存的管理信息 |
|
||||
| WWW-Authenticate | 服务器对客户端的认证信息 |
|
||||
|
||||
## 实体首部字段
|
||||
## 实体首部字段
|
||||
|
||||
| 首部字段名 | 说明 |
|
||||
| -- | -- |
|
||||
| Allow | 资源可支持的 HTTP 方法 |
|
||||
| Content-Encoding | 实体主体适用的编码方式 |
|
||||
| Content-Language | 实体主体的自然语言 |
|
||||
| Content-Length | 实体主体的大小(单位:字节) |
|
||||
| Content-Location | 替代对应资源的 URI |
|
||||
| Content-MD5 | 实体主体的报文摘要 |
|
||||
| Content-Range | 实体主体的位置范围 |
|
||||
| Content-Type | 实体主体的媒体类型 |
|
||||
| Expires | 实体主体过期的日期时间 |
|
||||
| Last-Modified | 资源的最后修改日期时间 |
|
||||
| 首部字段名 | 说明 |
|
||||
| -- | -- |
|
||||
| Allow | 资源可支持的 HTTP 方法 |
|
||||
| Content-Encoding | 实体主体适用的编码方式 |
|
||||
| Content-Language | 实体主体的自然语言 |
|
||||
| Content-Length | 实体主体的大小(单位:字节) |
|
||||
| Content-Location | 替代对应资源的 URI |
|
||||
| Content-MD5 | 实体主体的报文摘要 |
|
||||
| Content-Range | 实体主体的位置范围 |
|
||||
| Content-Type | 实体主体的媒体类型 |
|
||||
| Expires | 实体主体过期的日期时间 |
|
||||
| Last-Modified | 资源的最后修改日期时间 |
|
||||
|
||||
# 具体应用
|
||||
# 具体应用
|
||||
|
||||
## Cookie
|
||||
## Cookie
|
||||
|
||||
HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。
|
||||
HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单,使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。
|
||||
|
||||
Cookie 是服务器发送给客户端的数据,该数据会被保存在浏览器中,并且在下一次发送请求时包含该数据。通过 Cookie 可以让服务器知道两个请求是否来自于同一个客户端,从而实现保持登录状态等功能。
|
||||
Cookie 是服务器发送给客户端的数据,该数据会被保存在浏览器中,并且在下一次发送请求时包含该数据。通过 Cookie 可以让服务器知道两个请求是否来自于同一个客户端,从而实现保持登录状态等功能。
|
||||
|
||||
**创建过程**
|
||||
**创建过程**
|
||||
|
||||
服务器发送的响应报文包含 Set-Cookie 字段,客户端得到响应报文后把 Cookie 内容保存到浏览器中。
|
||||
服务器发送的响应报文包含 Set-Cookie 字段,客户端得到响应报文后把 Cookie 内容保存到浏览器中。
|
||||
|
||||
```html
|
||||
HTTP/1.0 200 OK
|
||||
Content-type: text/html
|
||||
Set-Cookie: yummy_cookie=choco
|
||||
Set-Cookie: tasty_cookie=strawberry
|
||||
HTTP/1.0 200 OK
|
||||
Content-type: text/html
|
||||
Set-Cookie: yummy_cookie=choco
|
||||
Set-Cookie: tasty_cookie=strawberry
|
||||
|
||||
[page content]
|
||||
[page content]
|
||||
```
|
||||
|
||||
客户端之后发送请求时,会从浏览器中读出 Cookie 值,在请求报文中包含 Cookie 字段。
|
||||
客户端之后发送请求时,会从浏览器中读出 Cookie 值,在请求报文中包含 Cookie 字段。
|
||||
|
||||
```html
|
||||
GET /sample_page.html HTTP/1.1
|
||||
Host: www.example.org
|
||||
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
|
||||
GET /sample_page.html HTTP/1.1
|
||||
Host: www.example.org
|
||||
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
|
||||
```
|
||||
|
||||
**Set-Cookie**
|
||||
**Set-Cookie**
|
||||
|
||||
| 属性 | 说明 |
|
||||
| -- | -- |
|
||||
| NAME=VALUE | 赋予 Cookie 的名称和其值(必需项) |
|
||||
| expires=DATE | Cookie 的有效期(若不明确指定则默认为浏览器关闭前为止) |
|
||||
| path=PATH | 将服务器上的文件目录作为 Cookie 的适用对象(若不指定则默认为文档所在的文件目录) |
|
||||
| domain=域名 | 作为 Cookie 适用对象的域名(若不指定则默认为创建 Cookie 的服务器的域名) |
|
||||
| Secure | 仅在 HTTPS 安全通信时才会发送 Cookie |
|
||||
| HttpOnly | 加以限制,使 Cookie 不能被 JavaScript 脚本访问 |
|
||||
| 属性 | 说明 |
|
||||
| -- | -- |
|
||||
| NAME=VALUE | 赋予 Cookie 的名称和其值(必需项) |
|
||||
| expires=DATE | Cookie 的有效期(若不明确指定则默认为浏览器关闭前为止) |
|
||||
| path=PATH | 将服务器上的文件目录作为 Cookie 的适用对象(若不指定则默认为文档所在的文件目录) |
|
||||
| domain=域名 | 作为 Cookie 适用对象的域名(若不指定则默认为创建 Cookie 的服务器的域名) |
|
||||
| Secure | 仅在 HTTPS 安全通信时才会发送 Cookie |
|
||||
| HttpOnly | 加以限制,使 Cookie 不能被 JavaScript 脚本访问 |
|
||||
|
||||
**Session 和 Cookie 区别**
|
||||
**Session 和 Cookie 区别**
|
||||
|
||||
Session 是服务器用来跟踪用户的一种手段,每个 Session 都有一个唯一标识:Session ID。当服务器创建了一个 Session 时,给客户端发送的响应报文就包含了 Set-Cookie 字段,其中有一个名为 sid 的键值对,这个键值对就是 Session ID。客户端收到后就把 Cookie 保存在浏览器中,并且之后发送的请求报文都包含 Session ID。HTTP 就是通过 Session 和 Cookie 这两种方式一起合作来实现跟踪用户状态的,Session 用于服务器端,Cookie 用于客户端。
|
||||
Session 是服务器用来跟踪用户的一种手段,每个 Session 都有一个唯一标识:Session ID。当服务器创建了一个 Session 时,给客户端发送的响应报文就包含了 Set-Cookie 字段,其中有一个名为 sid 的键值对,这个键值对就是 Session ID。客户端收到后就把 Cookie 保存在浏览器中,并且之后发送的请求报文都包含 Session ID。HTTP 就是通过 Session 和 Cookie 这两种方式一起合作来实现跟踪用户状态的,Session 用于服务器端,Cookie 用于客户端。
|
||||
|
||||
**浏览器禁用 Cookie 的情况**
|
||||
**浏览器禁用 Cookie 的情况**
|
||||
|
||||
会使用 URL 重写技术,在 URL 后面加上 sid=xxx 。
|
||||
会使用 URL 重写技术,在 URL 后面加上 sid=xxx 。
|
||||
|
||||
**使用 Cookie 实现用户名和密码的自动填写**
|
||||
**使用 Cookie 实现用户名和密码的自动填写**
|
||||
|
||||
网站脚本会自动从保存在浏览器中的 Cookie 读取用户名和密码,从而实现自动填写。
|
||||
网站脚本会自动从保存在浏览器中的 Cookie 读取用户名和密码,从而实现自动填写。
|
||||
|
||||
## 缓存
|
||||
## 缓存
|
||||
|
||||
**优点**
|
||||
**优点**
|
||||
|
||||
1. 降低服务器的负担;
|
||||
2. 提高响应速度(缓存资源比服务器上的资源离客户端更近)。
|
||||
1. 降低服务器的负担;
|
||||
2. 提高响应速度(缓存资源比服务器上的资源离客户端更近)。
|
||||
|
||||
**实现方法**
|
||||
**实现方法**
|
||||
|
||||
1. 让代理服务器进行缓存;
|
||||
2. 让客户端浏览器进行缓存。
|
||||
1. 让代理服务器进行缓存;
|
||||
2. 让客户端浏览器进行缓存。
|
||||
|
||||
**Cache-Control 字段**
|
||||
**Cache-Control 字段**
|
||||
|
||||
HTTP 通过 Cache-Control 首部字段来控制缓存。
|
||||
HTTP 通过 Cache-Control 首部字段来控制缓存。
|
||||
|
||||
```html
|
||||
Cache-Control: private, max-age=0, no-cache
|
||||
Cache-Control: private, max-age=0, no-cache
|
||||
```
|
||||
|
||||
**no-cache 指令**
|
||||
**no-cache 指令**
|
||||
|
||||
该指令出现在请求报文的 Cache-Control 字段中,表示缓存服务器需要先向原服务器验证缓存资源是否过期;
|
||||
该指令出现在请求报文的 Cache-Control 字段中,表示缓存服务器需要先向原服务器验证缓存资源是否过期;
|
||||
|
||||
该指令出现在响应报文的 Cache-Control 字段中,表示缓存服务器在进行缓存之前需要先验证缓存资源的有效性。
|
||||
该指令出现在响应报文的 Cache-Control 字段中,表示缓存服务器在进行缓存之前需要先验证缓存资源的有效性。
|
||||
|
||||
**no-store 指令**
|
||||
**no-store 指令**
|
||||
|
||||
该指令表示缓存服务器不能对请求或响应的任何一部分进行缓存。
|
||||
|
||||
no-cache 不表示不缓存,而是缓存之前需要先进行验证,no-store 才是不进行缓存。
|
||||
no-cache 不表示不缓存,而是缓存之前需要先进行验证,no-store 才是不进行缓存。
|
||||
|
||||
**max-age 指令**
|
||||
**max-age 指令**
|
||||
|
||||
该指令出现在请求报文的 Cache-Control 字段中,如果缓存资源的缓存时间小于该指令指定的时间,那么就能接受该缓存。
|
||||
该指令出现在请求报文的 Cache-Control 字段中,如果缓存资源的缓存时间小于该指令指定的时间,那么就能接受该缓存。
|
||||
|
||||
该指令出现在响应报文的 Cache-Control 字段中,表示缓存资源在缓存服务器中保存的时间。
|
||||
该指令出现在响应报文的 Cache-Control 字段中,表示缓存资源在缓存服务器中保存的时间。
|
||||
|
||||
Expires 字段也可以用于告知缓存服务器该资源什么时候会过期。在 HTTP/1.1 中,会优先处理 Cache-Control : max-age 指令;而在 HTTP/1.0 中,Cache-Control : max-age 指令会被忽略掉。
|
||||
Expires 字段也可以用于告知缓存服务器该资源什么时候会过期。在 HTTP/1.1 中,会优先处理 Cache-Control : max-age 指令;而在 HTTP/1.0 中,Cache-Control : max-age 指令会被忽略掉。
|
||||
|
||||
## 持久连接
|
||||
## 持久连接
|
||||
|
||||
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源,如果每进行一次 HTTP 通信就要断开一次 TCP 连接,连接建立和断开的开销会很大。持久连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
|
||||
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源,如果每进行一次 HTTP 通信就要断开一次 TCP 连接,连接建立和断开的开销会很大。持久连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
|
||||
|
||||
![](index_files/c73a0b78-5f46-4d2d-a009-dab2a999b5d8.jpg)
|
||||
<div align="center"> <img src="../pics//c73a0b78-5f46-4d2d-a009-dab2a999b5d8.jpg"/> </div><br>
|
||||
|
||||
持久连接需要使用 Connection 首部字段进行管理。HTTP/1.1 开始 HTTP 默认是持久化连接的,如果要断开 TCP 连接,需要由客户端或者服务器端提出断开,使用 Connection : close;而在 HTTP/1.1 之前默认是非持久化连接的,如果要维持持续连接,需要使用 Connection : Keep-Alive。
|
||||
持久连接需要使用 Connection 首部字段进行管理。HTTP/1.1 开始 HTTP 默认是持久化连接的,如果要断开 TCP 连接,需要由客户端或者服务器端提出断开,使用 Connection : close;而在 HTTP/1.1 之前默认是非持久化连接的,如果要维持持续连接,需要使用 Connection : Keep-Alive。
|
||||
|
||||
**管线化方式** 可以同时发送多个请求和响应,而不需要发送一个请求然后等待响应之后再发下一个请求。
|
||||
**管线化方式** 可以同时发送多个请求和响应,而不需要发送一个请求然后等待响应之后再发下一个请求。
|
||||
|
||||
![](index_files/6943e2af-5a70-4004-8bee-b33d60f39da3.jpg)
|
||||
<div align="center"> <img src="../pics//6943e2af-5a70-4004-8bee-b33d60f39da3.jpg"/> </div><br>
|
||||
|
||||
## 编码
|
||||
## 编码
|
||||
|
||||
编码(Encoding)主要是为了对实体进行压缩。常用的编码有:gzip、compress、deflate、identity,其中 identity 表示不执行压缩的编码格式。
|
||||
编码(Encoding)主要是为了对实体进行压缩。常用的编码有:gzip、compress、deflate、identity,其中 identity 表示不执行压缩的编码格式。
|
||||
|
||||
## 分块传输
|
||||
## 分块传输
|
||||
|
||||
分块传输(Chunked Transfer Coding)可以把数据分割成多块,让浏览器逐步显示页面。
|
||||
分块传输(Chunked Transfer Coding)可以把数据分割成多块,让浏览器逐步显示页面。
|
||||
|
||||
## 多部分对象集合
|
||||
## 多部分对象集合
|
||||
|
||||
一份报文主体内可含有多种类型的实体同时发送,每个部分之间用 boundary 字段定义的分隔符进行分隔,每个部分都可以有首部字段。
|
||||
一份报文主体内可含有多种类型的实体同时发送,每个部分之间用 boundary 字段定义的分隔符进行分隔,每个部分都可以有首部字段。
|
||||
|
||||
例如,上传多个表单时可以使用如下方式:
|
||||
|
||||
```html
|
||||
Content-Type: multipart/form-data; boundary=AaB03x
|
||||
Content-Type: multipart/form-data; boundary=AaB03x
|
||||
|
||||
--AaB03x
|
||||
Content-Disposition: form-data; name="submit-name"
|
||||
Content-Disposition: form-data; name="submit-name"
|
||||
|
||||
Larry
|
||||
--AaB03x
|
||||
Content-Disposition: form-data; name="files"; filename="file1.txt"
|
||||
Content-Type: text/plain
|
||||
Content-Disposition: form-data; name="files"; filename="file1.txt"
|
||||
Content-Type: text/plain
|
||||
|
||||
... contents of file1.txt ...
|
||||
... contents of file1.txt ...
|
||||
--AaB03x--
|
||||
```
|
||||
|
||||
## 范围请求
|
||||
## 范围请求
|
||||
|
||||
如果网络出现中断,服务器只发送了一部分数据,范围请求使得客户端能够只请求未发送的那部分数据,从而避免服务器端重新发送所有数据。
|
||||
|
||||
在请求报文首部中添加 Range 字段,然后指定请求的范围,例如 Range:bytes=5001-10000。请求成功的话服务器发送 206 Partial Content 状态。
|
||||
在请求报文首部中添加 Range 字段,然后指定请求的范围,例如 Range:bytes=5001-10000。请求成功的话服务器发送 206 Partial Content 状态。
|
||||
|
||||
```html
|
||||
GET /z4d4kWk.jpg HTTP/1.1
|
||||
Host: i.imgur.com
|
||||
Range: bytes=0-1023
|
||||
GET /z4d4kWk.jpg HTTP/1.1
|
||||
Host: i.imgur.com
|
||||
Range: bytes=0-1023
|
||||
```
|
||||
|
||||
```html
|
||||
HTTP/1.1 206 Partial Content
|
||||
Content-Range: bytes 0-1023/146515
|
||||
Content-Length: 1024
|
||||
HTTP/1.1 206 Partial Content
|
||||
Content-Range: bytes 0-1023/146515
|
||||
Content-Length: 1024
|
||||
...
|
||||
(binary content)
|
||||
(binary content)
|
||||
```
|
||||
|
||||
## 内容协商
|
||||
## 内容协商
|
||||
|
||||
通过内容协商返回最合适的内容,例如根据浏览器的默认语言选择返回中文界面还是英文界面。
|
||||
|
||||
涉及以下首部字段:Accept、Accept-Charset、Accept-Encoding、Accept-Language、Content-Language。
|
||||
|
||||
![](index_files/39a27cca-c9af-482a-8a87-5522557a309e.jpg)
|
||||
<div align="center"> <img src="../pics//39a27cca-c9af-482a-8a87-5522557a309e.jpg"/> </div><br>
|
||||
|
||||
## 虚拟主机
|
||||
## 虚拟主机
|
||||
|
||||
使用虚拟主机技术,使得一台服务器拥有多个域名,并且在逻辑上可以看成多个服务器。
|
||||
|
||||
## 通信数据转发
|
||||
## 通信数据转发
|
||||
|
||||
**代理**
|
||||
**代理**
|
||||
|
||||
代理服务器接受客户端的请求,并且转发给其它服务器。
|
||||
|
||||
代理服务器一般是透明的,不会改变 URL。
|
||||
代理服务器一般是透明的,不会改变 URL。
|
||||
|
||||
使用代理的主要目的是:缓存、网络访问控制以及访问日志记录。
|
||||
|
||||
![](index_files/c07035c3-a9ba-4508-8e3c-d8ae4c6ee9ee.jpg)
|
||||
<div align="center"> <img src="../pics//c07035c3-a9ba-4508-8e3c-d8ae4c6ee9ee.jpg"/> </div><br>
|
||||
|
||||
**网关**
|
||||
**网关**
|
||||
|
||||
与代理服务器不同的是,网关服务器会将 HTTP 转化为其它协议进行通信,从而请求其它非 HTTP 服务器的服务。
|
||||
与代理服务器不同的是,网关服务器会将 HTTP 转化为其它协议进行通信,从而请求其它非 HTTP 服务器的服务。
|
||||
|
||||
![](index_files/81375888-6be1-476f-9521-42eea3e3154f.jpg)
|
||||
<div align="center"> <img src="../pics//81375888-6be1-476f-9521-42eea3e3154f.jpg"/> </div><br>
|
||||
|
||||
**隧道**
|
||||
**隧道**
|
||||
|
||||
使用 SSL 等加密手段,为客户端和服务器之间建立一条安全的通信线路。
|
||||
使用 SSL 等加密手段,为客户端和服务器之间建立一条安全的通信线路。
|
||||
|
||||
![](index_files/64b95403-d976-421a-8b45-bac89c0b5185.jpg)
|
||||
<div align="center"> <img src="../pics//64b95403-d976-421a-8b45-bac89c0b5185.jpg"/> </div><br>
|
||||
|
||||
# HTTPs
|
||||
# HTTPs
|
||||
|
||||
HTTP 有以下安全性问题:
|
||||
HTTP 有以下安全性问题:
|
||||
|
||||
1. 使用明文进行通信,内容可能会被窃听;
|
||||
2. 不验证通信方的身份,通信方的身份有可能遭遇伪装;
|
||||
3. 无法证明报文的完整性,报文有可能遭篡改。
|
||||
1. 使用明文进行通信,内容可能会被窃听;
|
||||
2. 不验证通信方的身份,通信方的身份有可能遭遇伪装;
|
||||
3. 无法证明报文的完整性,报文有可能遭篡改。
|
||||
|
||||
HTTPs 并不是新协议,而是 HTTP 先和 SSL(Secure Socket Layer)通信,再由 SSL 和 TCP 通信。通过使用 SSL,HTTPs 提供了加密、认证和完整性保护。
|
||||
HTTPs 并不是新协议,而是 HTTP 先和 SSL(Secure Socket Layer)通信,再由 SSL 和 TCP 通信。通过使用 SSL,HTTPs 提供了加密、认证和完整性保护。
|
||||
|
||||
## 加密
|
||||
## 加密
|
||||
|
||||
有两种加密方式:对称密钥加密和公开密钥加密。对称密钥加密的加密和解密使用同一密钥,而公开密钥加密使用一对密钥用于加密和解密,分别为公开密钥和私有密钥。公开密钥所有人都可以获得,通信发送方获得接收方的公开密钥之后,就可以使用公开密钥进行加密,接收方收到通信内容后使用私有密钥解密。
|
||||
|
||||
对称密钥加密的缺点:无法安全传输密钥;公开密钥加密的缺点:相对来说更耗时。
|
||||
|
||||
HTTPs 采用 **混合的加密机制**,使用公开密钥加密用于传输对称密钥,之后使用对称密钥加密进行通信。(下图中,共享密钥即对称密钥)
|
||||
HTTPs 采用 **混合的加密机制** ,使用公开密钥加密用于传输对称密钥,之后使用对称密钥加密进行通信。(下图中,共享密钥即对称密钥)
|
||||
|
||||
![](index_files/110b1a9b-87cd-45c3-a21d-824623715b33.jpg)
|
||||
<div align="center"> <img src="../pics//110b1a9b-87cd-45c3-a21d-824623715b33.jpg"/> </div><br>
|
||||
|
||||
## 认证
|
||||
## 认证
|
||||
|
||||
通过使用 **证书** 来对通信方进行认证。
|
||||
通过使用 **证书** 来对通信方进行认证。
|
||||
|
||||
数字证书认证机构(CA,Certificate Authority)是客户端与服务器双方都可信赖的第三方机构。服务器的运营人员向 CA 提出公开密钥的申请,CA 在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公开密钥证书后绑定在一起。
|
||||
数字证书认证机构(CA,Certificate Authority)是客户端与服务器双方都可信赖的第三方机构。服务器的运营人员向 CA 提出公开密钥的申请,CA 在判明提出申请者的身份之后,会对已申请的公开密钥做数字签名,然后分配这个已签名的公开密钥,并将该公开密钥放入公开密钥证书后绑定在一起。
|
||||
|
||||
进行 HTTPs 通信时,服务器会把证书发送给客户端,客户端取得其中的公开密钥之后,先进行验证,如果验证通过,就可以开始通信。
|
||||
进行 HTTPs 通信时,服务器会把证书发送给客户端,客户端取得其中的公开密钥之后,先进行验证,如果验证通过,就可以开始通信。
|
||||
|
||||
除了上诉提到的服务器端证书之外,还有客户端证书,客户端证书的目的就是让服务器对客户端进行验证。客户端证书需要用户自行安装,只有在业务需要非常高的安全性时才使用客户端证书,例如网上银行。
|
||||
|
||||
使用 OpenSSL 这套开源程序,每个人都可以构建一套属于自己的认证机构,从而自己给自己颁发服务器证书。浏览器在访问该服务器时,会显示“无法确认连接安全性”或“该网站的安全证书存在问题”等警告消息。
|
||||
使用 OpenSSL 这套开源程序,每个人都可以构建一套属于自己的认证机构,从而自己给自己颁发服务器证书。浏览器在访问该服务器时,会显示“无法确认连接安全性”或“该网站的安全证书存在问题”等警告消息。
|
||||
|
||||
## 完整性
|
||||
## 完整性
|
||||
|
||||
SSL 提供摘要功能来验证完整性。
|
||||
SSL 提供摘要功能来验证完整性。
|
||||
|
||||
# 各版本比较
|
||||
# 各版本比较
|
||||
|
||||
## HTTP/1.0 与 HTTP/1.1 的区别
|
||||
## HTTP/1.0 与 HTTP/1.1 的区别
|
||||
|
||||
HTTP/1.1 新增了以下内容:
|
||||
HTTP/1.1 新增了以下内容:
|
||||
|
||||
- 默认为长连接;
|
||||
- 提供了范围请求功能;
|
||||
- 提供了虚拟主机的功能;
|
||||
- 多了一些缓存处理字段;
|
||||
- 多了一些状态码;
|
||||
- 默认为长连接;
|
||||
- 提供了范围请求功能;
|
||||
- 提供了虚拟主机的功能;
|
||||
- 多了一些缓存处理字段;
|
||||
- 多了一些状态码;
|
||||
|
||||
## HTTP/1.1 与 HTTP/2.0 的区别
|
||||
## HTTP/1.1 与 HTTP/2.0 的区别
|
||||
|
||||
**多路复用**
|
||||
**多路复用**
|
||||
|
||||
HTTP/2.0 使用多路复用技术,使用同一个 TCP 连接来处理多个请求。
|
||||
HTTP/2.0 使用多路复用技术,使用同一个 TCP 连接来处理多个请求。
|
||||
|
||||
**首部压缩**
|
||||
**首部压缩**
|
||||
|
||||
HTTP/1.1 的首部带有大量信息,而且每次都要重复发送。HTTP/2.0 要求通讯双方各自缓存一份首部字段表,从而避免了重复传输。
|
||||
HTTP/1.1 的首部带有大量信息,而且每次都要重复发送。HTTP/2.0 要求通讯双方各自缓存一份首部字段表,从而避免了重复传输。
|
||||
|
||||
**服务端推送**
|
||||
**服务端推送**
|
||||
|
||||
在客户端请求一个资源时,会把相关的资源一起发送给客户端,客户端就不需要再次发起请求了。例如客户端请求 index.html 页面,服务端就把 index.js 一起发给客户端。
|
||||
在客户端请求一个资源时,会把相关的资源一起发送给客户端,客户端就不需要再次发起请求了。例如客户端请求 index.html 页面,服务端就把 index.js 一起发给客户端。
|
||||
|
||||
**二进制格式**
|
||||
**二进制格式**
|
||||
|
||||
HTTP/1.1 的解析是基于文本的,而 HTTP/2.0 采用二进制格式。
|
||||
HTTP/1.1 的解析是基于文本的,而 HTTP/2.0 采用二进制格式。
|
||||
|
||||
# 参考资料
|
||||
# 参考资料
|
||||
|
||||
- [图解 HTTP](https://pan.baidu.com/s/1M0AHXqG9sP9Bxne6u0JK8A)
|
||||
- [MDN : HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)
|
||||
- [图解 HTTP](https://pan.baidu.com/s/1M0AHXqG9sP9Bxne6u0JK8A)
|
||||
- [MDN : HTTP](https://developer.mozilla.org/en-US/docs/Web/HTTP)
|
||||
|
|
661
notes/JVM.md
661
notes/JVM.md
|
@ -1,102 +1,165 @@
|
|||
[TOC]
|
||||
<!-- GFM-TOC -->
|
||||
* [内存模型](#内存模型)
|
||||
* [1. 程序计数器](#1-程序计数器)
|
||||
* [2. Java 虚拟机栈](#2-java-虚拟机栈)
|
||||
* [3. 本地方法栈](#3-本地方法栈)
|
||||
* [4. Java 堆](#4-java-堆)
|
||||
* [5. 方法区](#5-方法区)
|
||||
* [6. 运行时常量池](#6-运行时常量池)
|
||||
* [7. 直接内存](#7-直接内存)
|
||||
* [垃圾收集](#垃圾收集)
|
||||
* [1. 判断一个对象是否可回收](#1-判断一个对象是否可回收)
|
||||
* [1.1 引用计数](#11-引用计数)
|
||||
* [1.2 可达性](#12-可达性)
|
||||
* [1.3 引用类型](#13-引用类型)
|
||||
* [1.3.1 强引用](#131-强引用)
|
||||
* [1.3.2 软引用](#132-软引用)
|
||||
* [1.3.3 弱引用](#133-弱引用)
|
||||
* [1.3.4 虚引用](#134-虚引用)
|
||||
* [1.3 方法区的回收](#13-方法区的回收)
|
||||
* [1.4 finalize()](#14-finalize)
|
||||
* [2. 垃圾收集算法](#2-垃圾收集算法)
|
||||
* [2.1 标记-清除算法](#21-标记-清除算法)
|
||||
* [2.2 复制算法](#22-复制算法)
|
||||
* [2.3 标记-整理算法](#23-标记-整理算法)
|
||||
* [2.4 分代收集算法](#24-分代收集算法)
|
||||
* [3. 垃圾收集器](#3-垃圾收集器)
|
||||
* [3.1 Serial 收集器](#31-serial-收集器)
|
||||
* [3.2 ParNew 收集器](#32-parnew-收集器)
|
||||
* [3.3 Parallel Scavenge 收集器](#33-parallel-scavenge-收集器)
|
||||
* [3.4 Serial Old 收集器](#34-serial-old-收集器)
|
||||
* [3.5 Parallel Old 收集器](#35-parallel-old-收集器)
|
||||
* [3.6 CMS 收集器](#36-cms-收集器)
|
||||
* [3.7 G1 收集器](#37-g1-收集器)
|
||||
* [3.8 七种垃圾收集器的比较](#38-七种垃圾收集器的比较)
|
||||
* [4. 内存分配与回收策略](#4-内存分配与回收策略)
|
||||
* [4.1 优先在 Eden 分配](#41-优先在-eden-分配)
|
||||
* [4.2 大对象直接进入老年代](#42-大对象直接进入老年代)
|
||||
* [4.3 长期存活的对象进入老年代](#43-长期存活的对象进入老年代)
|
||||
* [4.4 动态对象年龄判定](#44-动态对象年龄判定)
|
||||
* [4.5 空间分配担保](#45-空间分配担保)
|
||||
* [5. Full GC 的触发条件](#5-full-gc-的触发条件)
|
||||
* [5.1 调用 System.gc()](#51-调用-systemgc)
|
||||
* [5.2 老年代空间不足](#52-老年代空间不足)
|
||||
* [5.3 空间分配担保失败](#53-空间分配担保失败)
|
||||
* [5.4 JDK 1.7 及以前的永久代空间不足](#54-jdk-17-及以前的永久代空间不足)
|
||||
* [5.5 Concurrent Mode Failure](#55-concurrent-mode-failure)
|
||||
* [类加载机制](#类加载机制)
|
||||
* [1 类的生命周期](#1-类的生命周期)
|
||||
* [2. 类初始化时机](#2-类初始化时机)
|
||||
* [3. 类加载过程](#3-类加载过程)
|
||||
* [3.1 加载](#31-加载)
|
||||
* [3.2 验证](#32-验证)
|
||||
* [3.3 准备](#33-准备)
|
||||
* [3.4 解析](#34-解析)
|
||||
* [3.5 初始化](#35-初始化)
|
||||
* [4. 类加载器](#4-类加载器)
|
||||
* [4.1 类与类加载器](#41-类与类加载器)
|
||||
* [4.2 类加载器分类](#42-类加载器分类)
|
||||
* [4.3 双亲委派模型](#43-双亲委派模型)
|
||||
* [JVM 参数](#jvm-参数)
|
||||
* [GC 优化配置](#gc-优化配置)
|
||||
* [GC 类型设置](#gc-类型设置)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
# 内存模型
|
||||
|
||||
![](index_files/dc695f48-4189-4fc7-b950-ed25f6c80f82.jpg)
|
||||
# 内存模型
|
||||
|
||||
<div align="center"> <img src="../pics//dc695f48-4189-4fc7-b950-ed25f6c80f82.jpg"/> </div><br>
|
||||
|
||||
注:白色区域为线程私有的,蓝色区域为线程共享的。
|
||||
|
||||
## 1. 程序计数器
|
||||
## 1. 程序计数器
|
||||
|
||||
记录正在执行的虚拟机字节码指令的地址(如果正在执行的是 Native 方法则为空)。
|
||||
记录正在执行的虚拟机字节码指令的地址(如果正在执行的是 Native 方法则为空)。
|
||||
|
||||
## 2. Java 虚拟机栈
|
||||
## 2. Java 虚拟机栈
|
||||
|
||||
每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
|
||||
每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
|
||||
|
||||
该区域可能抛出以下异常:
|
||||
|
||||
1. 当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常;
|
||||
2. 栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。
|
||||
1. 当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常;
|
||||
2. 栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。
|
||||
|
||||
## 3. 本地方法栈
|
||||
## 3. 本地方法栈
|
||||
|
||||
与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
|
||||
与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
|
||||
|
||||
## 4. Java 堆
|
||||
## 4. Java 堆
|
||||
|
||||
所有对象实例都在这里分配内存。
|
||||
|
||||
这块区域是垃圾收集器管理的主要区域("GC 堆 ")。现在收集器基本都是采用分代收集算法,Java 堆还可以分成:新生代和老年代(新生代还可以分成 Eden 空间、From Survivor 空间、To Survivor 空间等)。
|
||||
这块区域是垃圾收集器管理的主要区域("GC 堆 ")。现在收集器基本都是采用分代收集算法,Java 堆还可以分成:新生代和老年代(新生代还可以分成 Eden 空间、From Survivor 空间、To Survivor 空间等)。
|
||||
|
||||
不需要连续内存,可以通过 -Xmx 和 -Xms 来控制动态扩展内存大小,如果动态扩展失败会抛出 OutOfMemoryError 异常。
|
||||
不需要连续内存,可以通过 -Xmx 和 -Xms 来控制动态扩展内存大小,如果动态扩展失败会抛出 OutOfMemoryError 异常。
|
||||
|
||||
|
||||
## 5. 方法区
|
||||
## 5. 方法区
|
||||
|
||||
用于存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
|
||||
|
||||
和 Java 堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。
|
||||
和 Java 堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。
|
||||
|
||||
对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现,HotSpot 虚拟机把它当成永久代来进行垃圾回收。
|
||||
对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现,HotSpot 虚拟机把它当成永久代来进行垃圾回收。
|
||||
|
||||
## 6. 运行时常量池
|
||||
## 6. 运行时常量池
|
||||
|
||||
运行时常量池是方法区的一部分。
|
||||
|
||||
类加载后,Class 文件中的常量池(用于存放编译期生成的各种字面量和符号引用)就会被放到这个区域。
|
||||
类加载后,Class 文件中的常量池(用于存放编译期生成的各种字面量和符号引用)就会被放到这个区域。
|
||||
|
||||
在运行期间也可以用过 String 类的 intern() 方法将新的常量放入该区域。
|
||||
在运行期间也可以用过 String 类的 intern() 方法将新的常量放入该区域。
|
||||
|
||||
## 7. 直接内存
|
||||
## 7. 直接内存
|
||||
|
||||
在 JDK 1.4 中新加入了 NIO 类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
|
||||
在 JDK 1.4 中新加入了 NIO 类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
|
||||
|
||||
# 垃圾收集
|
||||
# 垃圾收集
|
||||
|
||||
程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后也会消失,因此不需要对这三个区域进行垃圾回收。垃圾回收主要是针对 Java 堆和方法区进行。
|
||||
程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后也会消失,因此不需要对这三个区域进行垃圾回收。垃圾回收主要是针对 Java 堆和方法区进行。
|
||||
|
||||
## 1. 判断一个对象是否可回收
|
||||
## 1. 判断一个对象是否可回收
|
||||
|
||||
### 1.1 引用计数
|
||||
### 1.1 引用计数
|
||||
|
||||
给对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。
|
||||
给对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。
|
||||
|
||||
两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致 GC 收集器无法回收。
|
||||
两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致 GC 收集器无法回收。
|
||||
|
||||
```java
|
||||
objA.instance = objB;
|
||||
objB.instance = objA;
|
||||
objA.instance = objB;
|
||||
objB.instance = objA;
|
||||
```
|
||||
|
||||
### 1.2 可达性
|
||||
### 1.2 可达性
|
||||
|
||||
通过 GC Roots 作为起始点进行搜索,能够到达到的对象都是都是可用的,不可达的对象可被回收。
|
||||
通过 GC Roots 作为起始点进行搜索,能够到达到的对象都是都是可用的,不可达的对象可被回收。
|
||||
|
||||
GC Roots 一般包含以下内容:
|
||||
GC Roots 一般包含以下内容:
|
||||
|
||||
1. 虚拟机栈中引用的对象
|
||||
2. 方法区中类静态属性引用的对象
|
||||
3. 方法区中的常量引用的对象
|
||||
4. 本地方法栈中引用的对象
|
||||
1. 虚拟机栈中引用的对象
|
||||
2. 方法区中类静态属性引用的对象
|
||||
3. 方法区中的常量引用的对象
|
||||
4. 本地方法栈中引用的对象
|
||||
|
||||
### 1.3 引用类型
|
||||
### 1.3 引用类型
|
||||
|
||||
无论是通过引用计算算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。
|
||||
|
||||
Java 对引用的概念进行了扩充,引入四种强度不同的引用类型。
|
||||
Java 对引用的概念进行了扩充,引入四种强度不同的引用类型。
|
||||
|
||||
#### 1.3.1 强引用
|
||||
#### 1.3.1 强引用
|
||||
|
||||
只要强引用存在,垃圾回收器永远不会回收调掉被引用的对象。
|
||||
|
||||
使用 new 一个新对象的方式来创建强引用。
|
||||
使用 new 一个新对象的方式来创建强引用。
|
||||
|
||||
```java
|
||||
Object obj = new Object();
|
||||
Object obj = new Object();
|
||||
```
|
||||
|
||||
#### 1.3.2 软引用
|
||||
#### 1.3.2 软引用
|
||||
|
||||
用来描述一些还有用但是并非必需的对象。
|
||||
|
||||
|
@ -104,36 +167,36 @@ Object obj = new Object();
|
|||
|
||||
软引用主要用来实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源获取数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源获取这些数据。
|
||||
|
||||
使用 SoftReference 类来实现软引用。
|
||||
使用 SoftReference 类来实现软引用。
|
||||
|
||||
```java
|
||||
Object obj = new Object();
|
||||
SoftReference<Object> sf = new SoftReference<Object>(obj);
|
||||
Object obj = new Object();
|
||||
SoftReference<Object> sf = new SoftReference<Object>(obj);
|
||||
```
|
||||
|
||||
#### 1.3.3 弱引用
|
||||
#### 1.3.3 弱引用
|
||||
|
||||
只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会被回收。
|
||||
|
||||
使用 WeakReference 类来实现弱引用。
|
||||
使用 WeakReference 类来实现弱引用。
|
||||
|
||||
```java
|
||||
Object obj = new Object();
|
||||
WeakReference<Object> wf = new WeakReference<Object>(obj);
|
||||
Object obj = new Object();
|
||||
WeakReference<Object> wf = new WeakReference<Object>(obj);
|
||||
```
|
||||
|
||||
#### 1.3.4 虚引用
|
||||
#### 1.3.4 虚引用
|
||||
|
||||
又称为幽灵引用或者幻影引用.一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
|
||||
|
||||
使用 PhantomReference 来实现虚引用。
|
||||
使用 PhantomReference 来实现虚引用。
|
||||
|
||||
```java
|
||||
Object obj = new Object();
|
||||
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
|
||||
Object obj = new Object();
|
||||
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
|
||||
```
|
||||
|
||||
### 1.3 方法区的回收
|
||||
### 1.3 方法区的回收
|
||||
|
||||
因为方法区主要存放永久代对象,而永久代对象的回收率比新生代差很多,因此在方法区上进行回收性价比不高。
|
||||
|
||||
|
@ -143,486 +206,486 @@ PhantomReference<Object> pf = new PhantomReference<Object>(obj);
|
|||
|
||||
类的卸载条件很多,需要满足以下三个条件,并且满足了也不一定会被卸载:
|
||||
|
||||
1. 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
|
||||
2. 加载该类的 ClassLoader 已经被回收。
|
||||
3. 该类对应的 java.lang.Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。
|
||||
1. 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
|
||||
2. 加载该类的 ClassLoader 已经被回收。
|
||||
3. 该类对应的 java.lang.Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。
|
||||
|
||||
可以通过 -Xnoclassgc 参数来控制是否对类进行卸载。
|
||||
可以通过 -Xnoclassgc 参数来控制是否对类进行卸载。
|
||||
|
||||
在大量使用反射、动态代理、CGLib 等 ByteCode 框架、动态生成 JSP 以及 OSGo 这类频繁自定义 ClassLoader 的场景都需要虚拟机具备类卸载功能,以保证不会出现内存溢出。
|
||||
在大量使用反射、动态代理、CGLib 等 ByteCode 框架、动态生成 JSP 以及 OSGo 这类频繁自定义 ClassLoader 的场景都需要虚拟机具备类卸载功能,以保证不会出现内存溢出。
|
||||
|
||||
### 1.4 finalize()
|
||||
### 1.4 finalize()
|
||||
|
||||
finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。但是 try-finally 等方式可以做的更好,并且该方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。
|
||||
finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。但是 try-finally 等方式可以做的更好,并且该方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。
|
||||
|
||||
当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能通过在该方法中让对象重新被引用,从而实现自救。
|
||||
当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能通过在该方法中让对象重新被引用,从而实现自救。
|
||||
|
||||
## 2. 垃圾收集算法
|
||||
## 2. 垃圾收集算法
|
||||
|
||||
### 2.1 标记-清除算法
|
||||
### 2.1 标记-清除算法
|
||||
|
||||
<center> ![](index_files/a4248c4b-6c1d-4fb8-a557-86da92d3a294.jpg) </center>
|
||||
<div align="center"> <img src="../pics//a4248c4b-6c1d-4fb8-a557-86da92d3a294.jpg"/> </div><br>
|
||||
|
||||
将需要回收的对象进行标记,然后清除。
|
||||
|
||||
不足:
|
||||
|
||||
1. 标记和清除过程效率都不高
|
||||
2. 会产生大量碎片
|
||||
1. 标记和清除过程效率都不高
|
||||
2. 会产生大量碎片
|
||||
|
||||
之后的算法都是基于该算法进行改进。
|
||||
|
||||
### 2.2 复制算法
|
||||
### 2.2 复制算法
|
||||
|
||||
<center> ![](index_files/e6b733ad-606d-4028-b3e8-83c3a73a3797.jpg) </center>
|
||||
<div align="center"> <img src="../pics//e6b733ad-606d-4028-b3e8-83c3a73a3797.jpg"/> </div><br>
|
||||
|
||||
将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。
|
||||
|
||||
主要不足是只使用了内存的一半。
|
||||
|
||||
现在的商业虚拟机都采用这种收集算法来回收新生代,但是并不是将内存划分为大小相等的两块,而是分为一块较大的 Eden 空间和两块较小的 Survior 空间,每次使用 Eden 空间和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和 使用过的那一块 Survivor。HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1,保证了内存的利用率达到 90 %。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,此时需要依赖于老年代进行分配担保,也就是借用老年代的空间。
|
||||
现在的商业虚拟机都采用这种收集算法来回收新生代,但是并不是将内存划分为大小相等的两块,而是分为一块较大的 Eden 空间和两块较小的 Survior 空间,每次使用 Eden 空间和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上,最后清理 Eden 和 使用过的那一块 Survivor。HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1,保证了内存的利用率达到 90 %。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 空间就不够用了,此时需要依赖于老年代进行分配担保,也就是借用老年代的空间。
|
||||
|
||||
### 2.3 标记-整理算法
|
||||
### 2.3 标记-整理算法
|
||||
|
||||
<center> ![](index_files/902b83ab-8054-4bd2-898f-9a4a0fe52830.jpg) </center>
|
||||
<div align="center"> <img src="../pics//902b83ab-8054-4bd2-898f-9a4a0fe52830.jpg"/> </div><br>
|
||||
|
||||
让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
|
||||
|
||||
### 2.4 分代收集算法
|
||||
### 2.4 分代收集算法
|
||||
|
||||
现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。
|
||||
|
||||
一般将 Java 堆分为新生代和老年代。
|
||||
一般将 Java 堆分为新生代和老年代。
|
||||
|
||||
1. 新生代使用:复制算法
|
||||
2. 老年代使用:标记-清理 或者 标记-整理 算法。
|
||||
1. 新生代使用:复制算法
|
||||
2. 老年代使用:标记-清理 或者 标记-整理 算法。
|
||||
|
||||
## 3. 垃圾收集器
|
||||
## 3. 垃圾收集器
|
||||
|
||||
<center> ![](index_files/c625baa0-dde6-449e-93df-c3a67f2f430f.jpg) </center>
|
||||
<div align="center"> <img src="../pics//c625baa0-dde6-449e-93df-c3a67f2f430f.jpg"/> </div><br>
|
||||
|
||||
以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。
|
||||
以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。
|
||||
|
||||
### 3.1 Serial 收集器
|
||||
### 3.1 Serial 收集器
|
||||
|
||||
<center> ![](index_files/22fda4ae-4dd5-489d-ab10-9ebfdad22ae0.jpg) </center>
|
||||
<div align="center"> <img src="../pics//22fda4ae-4dd5-489d-ab10-9ebfdad22ae0.jpg"/> </div><br>
|
||||
|
||||
它是单线程的收集器,不仅意味着只会使用一个线程进行垃圾收集工作,更重要的是它在进行垃圾收集时,必须暂停所有其他工作线程,往往造成过长的等待时间。
|
||||
|
||||
它的优点是简单高效,对于单个 CPU 环境来说,由于没有线程交互的开销,因此拥有最高的单线程收集效率。
|
||||
它的优点是简单高效,对于单个 CPU 环境来说,由于没有线程交互的开销,因此拥有最高的单线程收集效率。
|
||||
|
||||
在 Client 应用场景中,分配给虚拟机管理的内存一般来说不会很大,该收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。
|
||||
在 Client 应用场景中,分配给虚拟机管理的内存一般来说不会很大,该收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。
|
||||
|
||||
### 3.2 ParNew 收集器
|
||||
### 3.2 ParNew 收集器
|
||||
|
||||
<center> ![](index_files/81538cd5-1bcf-4e31-86e5-e198df1e013b.jpg) </center>
|
||||
<div align="center"> <img src="../pics//81538cd5-1bcf-4e31-86e5-e198df1e013b.jpg"/> </div><br>
|
||||
|
||||
它是 Serial 收集器的多线程版本。
|
||||
它是 Serial 收集器的多线程版本。
|
||||
|
||||
是 Server 模式下的虚拟机首选新生代收集器,除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合工作。
|
||||
是 Server 模式下的虚拟机首选新生代收集器,除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合工作。
|
||||
|
||||
默认开始的线程数量与 CPU 数量相同,可以使用 -XX:ParallelGCThreads 参数来设置线程数。
|
||||
默认开始的线程数量与 CPU 数量相同,可以使用 -XX:ParallelGCThreads 参数来设置线程数。
|
||||
|
||||
### 3.3 Parallel Scavenge 收集器
|
||||
### 3.3 Parallel Scavenge 收集器
|
||||
|
||||
是并行的多线程收集器。
|
||||
|
||||
其它收集器关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是达到一个可控制的吞吐量,它被称为“吞吐量优先”收集器。这里的吞吐量指 CPU 用于运行用户代码的时间占总时间的比值。
|
||||
其它收集器关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是达到一个可控制的吞吐量,它被称为“吞吐量优先”收集器。这里的吞吐量指 CPU 用于运行用户代码的时间占总时间的比值。
|
||||
|
||||
停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
|
||||
停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
|
||||
|
||||
提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间 -XX:MaxGCPauseMillis 参数以及直接设置吞吐量大小的 -XX:GCTimeRatio 参数(值为大于 0 且小于 100 的整数)。缩短停顿时间是以牺牲吞吐量和新生代空间来换取的:新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。
|
||||
提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间 -XX:MaxGCPauseMillis 参数以及直接设置吞吐量大小的 -XX:GCTimeRatio 参数(值为大于 0 且小于 100 的整数)。缩短停顿时间是以牺牲吞吐量和新生代空间来换取的:新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。
|
||||
|
||||
还提供了一个参数 -XX:+UseAdaptiveSizePolicy,这是一个开关参数,打开参数后,就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种方式称为 GC 自适应的调节策略(GC Ergonomics)。自适应调节策略也是它与 ParNew 收集器的一个重要区别。
|
||||
还提供了一个参数 -XX:+UseAdaptiveSizePolicy,这是一个开关参数,打开参数后,就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种方式称为 GC 自适应的调节策略(GC Ergonomics)。自适应调节策略也是它与 ParNew 收集器的一个重要区别。
|
||||
|
||||
### 3.4 Serial Old 收集器
|
||||
### 3.4 Serial Old 收集器
|
||||
|
||||
<center> ![](index_files/08f32fd3-f736-4a67-81ca-295b2a7972f2.jpg) </center>
|
||||
<div align="center"> <img src="../pics//08f32fd3-f736-4a67-81ca-295b2a7972f2.jpg"/> </div><br>
|
||||
|
||||
Serial Old 是 Serial 收集器的老年代版本,也是给 Client 模式下的虚拟机使用。如果用在 Server 模式下,它有两大用途:
|
||||
Serial Old 是 Serial 收集器的老年代版本,也是给 Client 模式下的虚拟机使用。如果用在 Server 模式下,它有两大用途:
|
||||
|
||||
1. 在 JDK 1.5 以及之前版本(Parallel Old 诞生以前)中与 Parallel Scavenge 收集器搭配使用。
|
||||
2. 作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。
|
||||
1. 在 JDK 1.5 以及之前版本(Parallel Old 诞生以前)中与 Parallel Scavenge 收集器搭配使用。
|
||||
2. 作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。
|
||||
|
||||
### 3.5 Parallel Old 收集器
|
||||
### 3.5 Parallel Old 收集器
|
||||
|
||||
<center> ![](index_files/278fe431-af88-4a95-a895-9c3b80117de3.jpg) </center>
|
||||
<div align="center"> <img src="../pics//278fe431-af88-4a95-a895-9c3b80117de3.jpg"/> </div><br>
|
||||
|
||||
是 Parallel Scavenge 收集器的老年代版本。
|
||||
是 Parallel Scavenge 收集器的老年代版本。
|
||||
|
||||
在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。
|
||||
在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。
|
||||
|
||||
### 3.6 CMS 收集器
|
||||
### 3.6 CMS 收集器
|
||||
|
||||
<center> ![](index_files/62e77997-6957-4b68-8d12-bfd609bb2c68.jpg) </center>
|
||||
<div align="center"> <img src="../pics//62e77997-6957-4b68-8d12-bfd609bb2c68.jpg"/> </div><br>
|
||||
|
||||
CMS(Concurrent Mark Sweep),从 Mark Sweep 可以知道它是基于标记-清除算法实现的。
|
||||
CMS(Concurrent Mark Sweep),从 Mark Sweep 可以知道它是基于标记-清除算法实现的。
|
||||
|
||||
特点:并发收集、低停顿。
|
||||
|
||||
分为以下四个流程:
|
||||
|
||||
1. 初始标记:仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿。
|
||||
2. 并发标记:进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿。
|
||||
3. 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿。
|
||||
4. 并发清除:不需要停顿。
|
||||
1. 初始标记:仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿。
|
||||
2. 并发标记:进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿。
|
||||
3. 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿。
|
||||
4. 并发清除:不需要停顿。
|
||||
|
||||
在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿。
|
||||
|
||||
具有以下缺点:
|
||||
|
||||
1. 对 CPU 资源敏感。CMS 默认启动的回收线程数是 (CPU 数量 + 3) / 4,当 CPU 不足 4 个时,CMS 对用户程序的影响就可能变得很大,如果本来 CPU 负载就比较大,还要分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低了 50%,其实也让人无法接受。并且低停顿时间是以牺牲吞吐量为代价的,导致 CPU 利用率变低。
|
||||
1. 对 CPU 资源敏感。CMS 默认启动的回收线程数是 (CPU 数量 + 3) / 4,当 CPU 不足 4 个时,CMS 对用户程序的影响就可能变得很大,如果本来 CPU 负载就比较大,还要分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低了 50%,其实也让人无法接受。并且低停顿时间是以牺牲吞吐量为代价的,导致 CPU 利用率变低。
|
||||
|
||||
2. 无法处理浮动垃圾。由于并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生。这一部分垃圾出现在标记过程之后,CMS 无法在当次收集中处理掉它们,只好留到下一次 GC 时再清理掉,这一部分垃圾就被称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,那也就还需要预留有足够的内存空间给用户线程使用,因此它不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使用。可以使用 -XX:CMSInitiatingOccupancyFraction 的值来改变触发收集器工作的内存占用百分比,JDK 1.5 默认设置下该值为 68,也就是当老年代使用了 68% 的空间之后会触发收集器工作。如果该值设置的太高,导致浮动垃圾无法保存,那么就会出现 Concurrent Mode Failure,此时虚拟机将启动后备预案:临时启用 Serial Old 收集器来重新进行老年代的垃圾收集。
|
||||
2. 无法处理浮动垃圾。由于并发清理阶段用户线程还在运行着,伴随程序运行自然就还会有新的垃圾不断产生。这一部分垃圾出现在标记过程之后,CMS 无法在当次收集中处理掉它们,只好留到下一次 GC 时再清理掉,这一部分垃圾就被称为“浮动垃圾”。也是由于在垃圾收集阶段用户线程还需要运行,那也就还需要预留有足够的内存空间给用户线程使用,因此它不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发收集时的程序运作使用。可以使用 -XX:CMSInitiatingOccupancyFraction 的值来改变触发收集器工作的内存占用百分比,JDK 1.5 默认设置下该值为 68,也就是当老年代使用了 68% 的空间之后会触发收集器工作。如果该值设置的太高,导致浮动垃圾无法保存,那么就会出现 Concurrent Mode Failure,此时虚拟机将启动后备预案:临时启用 Serial Old 收集器来重新进行老年代的垃圾收集。
|
||||
|
||||
3. 标记-清除算法导致的空间碎片,给大对象分配带来很大麻烦,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前出发一次 Full GC。
|
||||
3. 标记-清除算法导致的空间碎片,给大对象分配带来很大麻烦,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前出发一次 Full GC。
|
||||
|
||||
### 3.7 G1 收集器
|
||||
### 3.7 G1 收集器
|
||||
|
||||
![](index_files/f99ee771-c56f-47fb-9148-c0036695b5fe.jpg)
|
||||
<div align="center"> <img src="../pics//f99ee771-c56f-47fb-9148-c0036695b5fe.jpg"/> </div><br>
|
||||
|
||||
G1(Garbage-First)收集器是当今收集器技术发展最前沿的成果之一,它是一款面向服务端应用的垃圾收集器,HotSpot 开发团队赋予它的使命是(在比较长期的)未来可以替换掉 JDK 1.5 中发布的 CMS 收集器。
|
||||
G1(Garbage-First)收集器是当今收集器技术发展最前沿的成果之一,它是一款面向服务端应用的垃圾收集器,HotSpot 开发团队赋予它的使命是(在比较长期的)未来可以替换掉 JDK 1.5 中发布的 CMS 收集器。
|
||||
|
||||
具备如下特点:
|
||||
|
||||
- 并行与并发:能充分利用多 CPU 环境下的硬件优势,使用多个 CPU 来缩短停顿时间;
|
||||
- 分代收集:分代概念依然得以保留,虽然它不需要其它收集器配合就能独立管理整个 GC 堆,但它能够采用不同方式去处理新创建的对象和已存活一段时间、熬过多次 GC 的旧对象来获取更好的收集效果。
|
||||
- 空间整合:整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。
|
||||
- 可预测的停顿:这是它相对 CMS 的一大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了降低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒,这几乎已经是实时 Java(RTSJ)的垃圾收集器的特征了。
|
||||
- 并行与并发:能充分利用多 CPU 环境下的硬件优势,使用多个 CPU 来缩短停顿时间;
|
||||
- 分代收集:分代概念依然得以保留,虽然它不需要其它收集器配合就能独立管理整个 GC 堆,但它能够采用不同方式去处理新创建的对象和已存活一段时间、熬过多次 GC 的旧对象来获取更好的收集效果。
|
||||
- 空间整合:整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。
|
||||
- 可预测的停顿:这是它相对 CMS 的一大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了降低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒,这几乎已经是实时 Java(RTSJ)的垃圾收集器的特征了。
|
||||
|
||||
在 G1 之前的其他收集器进行收集的范围都是整个新生代或者老生代,而 G1 不再是这样,Java 堆的内存布局与其他收集器有很大区别,将整个 Java 堆划分为多个大小相等的独立区域(Region)。虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,而都是一部分 Region(不需要连续)的集合。
|
||||
在 G1 之前的其他收集器进行收集的范围都是整个新生代或者老生代,而 G1 不再是这样,Java 堆的内存布局与其他收集器有很大区别,将整个 Java 堆划分为多个大小相等的独立区域(Region)。虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,而都是一部分 Region(不需要连续)的集合。
|
||||
|
||||
之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个 Java 堆中进行全区域的垃圾收集。它跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region(这也就是 Garbage-First 名称的来由)。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了它在有限的时间内可以获取尽可能高的收集效率。
|
||||
之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个 Java 堆中进行全区域的垃圾收集。它跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region(这也就是 Garbage-First 名称的来由)。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了它在有限的时间内可以获取尽可能高的收集效率。
|
||||
|
||||
Region 不可能是孤立的,一个对象分配在某个 Region 中,可以与整个 Java 堆任意的对象发生引用关系。在做可达性分析确定对象是否存活的时候,需要扫描整个 Java 堆才能保证准确性,这显然是对 GC 效率的极大伤害。为了避免全堆扫描的发生,每个 Region 都维护了一个与之对应的 Remembered Set。虚拟机发现程序在对 Reference 类型的数据进行写操作时,会产生一个 Write Barrier 暂时中断写操作,检查 Reference 引用的对象是否处于不同的 Region 之中,如果是,便通过 CardTable 把相关引用信息记录到被引用对象所属的 Region 的 Remembered Set 之中。当进行内存回收时,在 GC 根节点的枚举范围中加入 Remembered Set 即可保证不对全堆扫描也不会有遗漏。
|
||||
Region 不可能是孤立的,一个对象分配在某个 Region 中,可以与整个 Java 堆任意的对象发生引用关系。在做可达性分析确定对象是否存活的时候,需要扫描整个 Java 堆才能保证准确性,这显然是对 GC 效率的极大伤害。为了避免全堆扫描的发生,每个 Region 都维护了一个与之对应的 Remembered Set。虚拟机发现程序在对 Reference 类型的数据进行写操作时,会产生一个 Write Barrier 暂时中断写操作,检查 Reference 引用的对象是否处于不同的 Region 之中,如果是,便通过 CardTable 把相关引用信息记录到被引用对象所属的 Region 的 Remembered Set 之中。当进行内存回收时,在 GC 根节点的枚举范围中加入 Remembered Set 即可保证不对全堆扫描也不会有遗漏。
|
||||
|
||||
如果不计算维护 Remembered Set 的操作,G1 收集器的运作大致可划分为以下几个步骤:
|
||||
如果不计算维护 Remembered Set 的操作,G1 收集器的运作大致可划分为以下几个步骤:
|
||||
|
||||
1. 初始标记
|
||||
2. 并发标记
|
||||
3. 最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。
|
||||
4. 筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿是时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。
|
||||
1. 初始标记
|
||||
2. 并发标记
|
||||
3. 最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。
|
||||
4. 筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿是时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。
|
||||
|
||||
### 3.8 七种垃圾收集器的比较
|
||||
### 3.8 七种垃圾收集器的比较
|
||||
|
||||
| 收集器 | 串行、并行 or 并发 | 新生代 / 老年代 | 算法 | 目标 | 适用场景 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **Serial** | 串行 | 新生代 | 复制算法 | 响应速度优先 | 单 CPU 环境下的 Client 模式 |
|
||||
| **Serial Old** | 串行 | 老年代 | 标记-整理 | 响应速度优先 | 单 CPU 环境下的 Client 模式、CMS 的后备预案 |
|
||||
| **ParNew** | 并行 | 新生代 | 复制算法 | 响应速度优先 | 多 CPU 环境时在 Server 模式下与 CMS 配合 |
|
||||
| **Parallel Scavenge** | 并行 | 新生代 | 复制算法 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
|
||||
| **Parallel Old** | 并行 | 老年代 | 标记-整理 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
|
||||
| **CMS** | 并发 | 老年代 | 标记-清除 | 响应速度优先 | 集中在互联网站或 B/S 系统服务端上的 Java 应用 |
|
||||
| **G1** | 并发 | both | 标记-整理 + 复制算法 | 响应速度优先 | 面向服务端应用,将来替换 CMS |
|
||||
| 收集器 | 串行、并行 or 并发 | 新生代 / 老年代 | 算法 | 目标 | 适用场景 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| **Serial** | 串行 | 新生代 | 复制算法 | 响应速度优先 | 单 CPU 环境下的 Client 模式 |
|
||||
| **Serial Old** | 串行 | 老年代 | 标记-整理 | 响应速度优先 | 单 CPU 环境下的 Client 模式、CMS 的后备预案 |
|
||||
| **ParNew** | 并行 | 新生代 | 复制算法 | 响应速度优先 | 多 CPU 环境时在 Server 模式下与 CMS 配合 |
|
||||
| **Parallel Scavenge** | 并行 | 新生代 | 复制算法 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
|
||||
| **Parallel Old** | 并行 | 老年代 | 标记-整理 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
|
||||
| **CMS** | 并发 | 老年代 | 标记-清除 | 响应速度优先 | 集中在互联网站或 B/S 系统服务端上的 Java 应用 |
|
||||
| **G1** | 并发 | both | 标记-整理 + 复制算法 | 响应速度优先 | 面向服务端应用,将来替换 CMS |
|
||||
|
||||
## 4. 内存分配与回收策略
|
||||
## 4. 内存分配与回收策略
|
||||
|
||||
对象的内存分配,也就是在堆上分配。主要分配在新生代的 Eden 区上,少数情况下也可能直接分配在老年代中。
|
||||
对象的内存分配,也就是在堆上分配。主要分配在新生代的 Eden 区上,少数情况下也可能直接分配在老年代中。
|
||||
|
||||
### 4.1 优先在 Eden 分配
|
||||
### 4.1 优先在 Eden 分配
|
||||
|
||||
大多数情况下,对象在新生代 Eden 区分配,当 Eden 区空间不够时,发起 Minor GC;
|
||||
大多数情况下,对象在新生代 Eden 区分配,当 Eden 区空间不够时,发起 Minor GC;
|
||||
|
||||
关于 Minor GC 和 Full GC:
|
||||
关于 Minor GC 和 Full GC:
|
||||
|
||||
- Minor GC:发生在新生代上,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速度一般也会比较快。
|
||||
- Full GC:发生在老年代上,老年代对象和新生代的相反,其存活时间长,因此 Full GC 很少执行,而且执行速度会比 Minor GC 慢很多。
|
||||
- Minor GC:发生在新生代上,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速度一般也会比较快。
|
||||
- Full GC:发生在老年代上,老年代对象和新生代的相反,其存活时间长,因此 Full GC 很少执行,而且执行速度会比 Minor GC 慢很多。
|
||||
|
||||
### 4.2 大对象直接进入老年代
|
||||
### 4.2 大对象直接进入老年代
|
||||
|
||||
大对象是指需要连续内存空间的对象,最典型的大对象是那种很长的字符串以及数组。经常出现大对象会提前触发垃圾收集以获取足够的连续空间分配给大对象。
|
||||
|
||||
提供 -XX:PretenureSizeThreshold 参数,大于此值的对象直接在老年代分配,避免在 Eden 区和 Survivor 区之间的大量内存复制;
|
||||
提供 -XX:PretenureSizeThreshold 参数,大于此值的对象直接在老年代分配,避免在 Eden 区和 Survivor 区之间的大量内存复制;
|
||||
|
||||
### 4.3 长期存活的对象进入老年代
|
||||
### 4.3 长期存活的对象进入老年代
|
||||
|
||||
JVM 为对象定义年龄计数器,经过 Minor GC 依然存活,并且能被 Survivor 区容纳的,移被移到 Survivor 区,年龄就增加 1 岁,增加到一定年龄则移动到老年代中(默认 15 岁,通过 -XX:MaxTenuringThreshold 设置);
|
||||
JVM 为对象定义年龄计数器,经过 Minor GC 依然存活,并且能被 Survivor 区容纳的,移被移到 Survivor 区,年龄就增加 1 岁,增加到一定年龄则移动到老年代中(默认 15 岁,通过 -XX:MaxTenuringThreshold 设置);
|
||||
|
||||
### 4.4 动态对象年龄判定
|
||||
### 4.4 动态对象年龄判定
|
||||
|
||||
JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 区中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无序等待 MaxTenuringThreshold 中要求的年龄。
|
||||
JVM 并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 区中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无序等待 MaxTenuringThreshold 中要求的年龄。
|
||||
|
||||
### 4.5 空间分配担保
|
||||
### 4.5 空间分配担保
|
||||
|
||||
在发生 Minor GC 之前,JVM 先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC 可以确认是安全的;如果不成立的话 JVM 会查看 HandlePromotionFailure 设置值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC,尽管这次 Minor GC 是有风险的;如果小于,或者 HandlePromotionFailure 设置不允许冒险,那这时也要改为进行一次 Full GC。
|
||||
在发生 Minor GC 之前,JVM 先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC 可以确认是安全的;如果不成立的话 JVM 会查看 HandlePromotionFailure 设置值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC,尽管这次 Minor GC 是有风险的;如果小于,或者 HandlePromotionFailure 设置不允许冒险,那这时也要改为进行一次 Full GC。
|
||||
|
||||
## 5. Full GC 的触发条件
|
||||
## 5. Full GC 的触发条件
|
||||
|
||||
对于 Minor GC,其触发条件非常简单,当 Eden 区空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件:
|
||||
对于 Minor GC,其触发条件非常简单,当 Eden 区空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件:
|
||||
|
||||
### 5.1 调用 System.gc()
|
||||
### 5.1 调用 System.gc()
|
||||
|
||||
此方法的调用是建议 JVM 进行 Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加 Full GC 的频率,也即增加了间歇性停顿的次数。因此强烈建议能不使用此方法就不要使用,让虚拟机自己去管理它的内存。可通过 -XX:+ DisableExplicitGC 来禁止 RMI 调用 System.gc()。
|
||||
此方法的调用是建议 JVM 进行 Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加 Full GC 的频率,也即增加了间歇性停顿的次数。因此强烈建议能不使用此方法就不要使用,让虚拟机自己去管理它的内存。可通过 -XX:+ DisableExplicitGC 来禁止 RMI 调用 System.gc()。
|
||||
|
||||
### 5.2 老年代空间不足
|
||||
### 5.2 老年代空间不足
|
||||
|
||||
老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等,当执行 Full GC 后空间仍然不足,则抛出 Java.lang.OutOfMemoryError。为避免以上原因引起的 Full GC,调优时应尽量做到让对象在 Minor GC 阶段被回收、让对象在新生代多存活一段时间以及不要创建过大的对象及数组。
|
||||
老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等,当执行 Full GC 后空间仍然不足,则抛出 Java.lang.OutOfMemoryError。为避免以上原因引起的 Full GC,调优时应尽量做到让对象在 Minor GC 阶段被回收、让对象在新生代多存活一段时间以及不要创建过大的对象及数组。
|
||||
|
||||
### 5.3 空间分配担保失败
|
||||
### 5.3 空间分配担保失败
|
||||
|
||||
使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果出现了 HandlePromotionFailure 担保失败,则会触发 Full GC。
|
||||
使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果出现了 HandlePromotionFailure 担保失败,则会触发 Full GC。
|
||||
|
||||
### 5.4 JDK 1.7 及以前的永久代空间不足
|
||||
### 5.4 JDK 1.7 及以前的永久代空间不足
|
||||
|
||||
在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 class 的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么 JVM 会抛出 java.lang.OutOfMemoryError,为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。
|
||||
在 JDK 1.7 及以前,HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 class 的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么 JVM 会抛出 java.lang.OutOfMemoryError,为避免以上原因引起的 Full GC,可采用的方法为增大永久代空间或转为使用 CMS GC。
|
||||
|
||||
### 5.5 Concurrent Mode Failure
|
||||
### 5.5 Concurrent Mode Failure
|
||||
|
||||
执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(有时候“空间不足”是 CMS GC 时当前的浮动垃圾过多导致暂时性的空间不足触发 Full GC),便会报 Concurrent Mode Failure 错误,并触发 Full GC。
|
||||
执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(有时候“空间不足”是 CMS GC 时当前的浮动垃圾过多导致暂时性的空间不足触发 Full GC),便会报 Concurrent Mode Failure 错误,并触发 Full GC。
|
||||
|
||||
# 类加载机制
|
||||
# 类加载机制
|
||||
|
||||
类是在运行期间动态加载的。
|
||||
|
||||
## 1 类的生命周期
|
||||
## 1 类的生命周期
|
||||
|
||||
![](index_files/32b8374a-e822-4720-af0b-c0f485095ea2.jpg)
|
||||
<div align="center"> <img src="../pics//32b8374a-e822-4720-af0b-c0f485095ea2.jpg"/> </div><br>
|
||||
|
||||
包括以下 7 个阶段:
|
||||
包括以下 7 个阶段:
|
||||
|
||||
- **加载(Loading)**
|
||||
- **验证(Verification)**
|
||||
- **准备(Preparation)**
|
||||
- **解析(Resolution)**
|
||||
- **初始化(Initialization)**
|
||||
- 使用(Using)
|
||||
- 卸载(Unloading)
|
||||
- **加载(Loading)**
|
||||
- **验证(Verification)**
|
||||
- **准备(Preparation)**
|
||||
- **解析(Resolution)**
|
||||
- **初始化(Initialization)**
|
||||
- 使用(Using)
|
||||
- 卸载(Unloading)
|
||||
|
||||
其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。
|
||||
其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。
|
||||
|
||||
## 2. 类初始化时机
|
||||
## 2. 类初始化时机
|
||||
|
||||
虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随着发生):
|
||||
|
||||
1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译器把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。
|
||||
1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译器把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。
|
||||
|
||||
2. 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
|
||||
2. 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
|
||||
|
||||
3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
|
||||
3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
|
||||
|
||||
4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类;
|
||||
4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类;
|
||||
|
||||
5. 当使用 JDK.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化;
|
||||
5. 当使用 JDK.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化;
|
||||
|
||||
以上 5 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的常见例子包括:
|
||||
以上 5 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的常见例子包括:
|
||||
|
||||
- 通过子类引用父类的静态字段,不会导致子类初始化。
|
||||
- 通过子类引用父类的静态字段,不会导致子类初始化。
|
||||
|
||||
```java
|
||||
System.out.println(SubClass.value); // value 字段在 SuperClass 中定义
|
||||
System.out.println(SubClass.value); // value 字段在 SuperClass 中定义
|
||||
```
|
||||
|
||||
- 通过数组定义来引用类,不会触发此类的初始化。该过程会对数组类进行初始化,数组类是一个由虚拟机自动生成的、直接继承自 Object 的子类,其中包含了数组的属性和方法。
|
||||
- 通过数组定义来引用类,不会触发此类的初始化。该过程会对数组类进行初始化,数组类是一个由虚拟机自动生成的、直接继承自 Object 的子类,其中包含了数组的属性和方法。
|
||||
|
||||
```java
|
||||
SuperClass[] sca = new SuperClass[10];
|
||||
SuperClass[] sca = new SuperClass[10];
|
||||
```
|
||||
|
||||
- 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
|
||||
- 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
|
||||
|
||||
```java
|
||||
System.out.println(ConstClass.HELLOWORLD);
|
||||
```
|
||||
|
||||
## 3. 类加载过程
|
||||
## 3. 类加载过程
|
||||
|
||||
包含了加载、验证、准备、解析和初始化这 5 个阶段。
|
||||
包含了加载、验证、准备、解析和初始化这 5 个阶段。
|
||||
|
||||
### 3.1 加载
|
||||
### 3.1 加载
|
||||
|
||||
加载是类加载的一个阶段,注意不要混淆。
|
||||
|
||||
加载过程完成以下三件事:
|
||||
|
||||
1. 通过一个类的全限定名来获取定义此类的二进制字节流。
|
||||
2. 将这个字节流所代表的静态存储结构转化为方法区的运行时存储结构。
|
||||
3. 在内存中生成一个代表这个类的 Class 对象,作为方法区这个类的各种数据的访问入口。
|
||||
1. 通过一个类的全限定名来获取定义此类的二进制字节流。
|
||||
2. 将这个字节流所代表的静态存储结构转化为方法区的运行时存储结构。
|
||||
3. 在内存中生成一个代表这个类的 Class 对象,作为方法区这个类的各种数据的访问入口。
|
||||
|
||||
其中二进制字节流可以从以下方式中获取:
|
||||
|
||||
- 从 ZIP 包读取,这很常见,最终成为日后 JAR、EAR、WAR 格式的基础。
|
||||
- 从网络中获取,这种场景最典型的应用是 Applet。
|
||||
- 运行时计算生成,这种场景使用得最多得就是动态代理技术,在 java.lang.reflect.Proxy 中,就是用了 ProxyGenerator.generateProxyClass 的代理类的二进制字节流。
|
||||
- 由其他文件生成,典型场景是 JSP 应用,即由 JSP 文件生成对应的 Class 类。
|
||||
- 从数据库读取,这种场景相对少见,例如有些中间件服务器(如 SAP Netweaver)可以选择把程序安装到数据库中来完成程序代码在集群间的分发。
|
||||
- 从 ZIP 包读取,这很常见,最终成为日后 JAR、EAR、WAR 格式的基础。
|
||||
- 从网络中获取,这种场景最典型的应用是 Applet。
|
||||
- 运行时计算生成,这种场景使用得最多得就是动态代理技术,在 java.lang.reflect.Proxy 中,就是用了 ProxyGenerator.generateProxyClass 的代理类的二进制字节流。
|
||||
- 由其他文件生成,典型场景是 JSP 应用,即由 JSP 文件生成对应的 Class 类。
|
||||
- 从数据库读取,这种场景相对少见,例如有些中间件服务器(如 SAP Netweaver)可以选择把程序安装到数据库中来完成程序代码在集群间的分发。
|
||||
...
|
||||
|
||||
### 3.2 验证
|
||||
### 3.2 验证
|
||||
|
||||
确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
|
||||
确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
|
||||
|
||||
主要有以下 4 个阶段:
|
||||
主要有以下 4 个阶段:
|
||||
|
||||
**1. 文件格式验证**
|
||||
**1. 文件格式验证**
|
||||
|
||||
验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。
|
||||
验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。
|
||||
|
||||
**2. 元数据验证**
|
||||
**2. 元数据验证**
|
||||
|
||||
对字节码描述的信息进行语义分析,以保证其描述的信息符合 Java 语言规范的要求。
|
||||
对字节码描述的信息进行语义分析,以保证其描述的信息符合 Java 语言规范的要求。
|
||||
|
||||
**3. 字节码验证**
|
||||
**3. 字节码验证**
|
||||
|
||||
通过数据流和控制流分析,确保程序语义是合法、符合逻辑的。
|
||||
|
||||
**4. 符号引用验证**
|
||||
**4. 符号引用验证**
|
||||
|
||||
发生在虚拟机将符号引用转换为直接引用的时候,对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。
|
||||
|
||||
### 3.3 准备
|
||||
### 3.3 准备
|
||||
|
||||
类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。
|
||||
类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。
|
||||
|
||||
实例变量不会在这阶段分配内存,它将会在对象实例化时随着对象一起分配在 Java 堆中。
|
||||
实例变量不会在这阶段分配内存,它将会在对象实例化时随着对象一起分配在 Java 堆中。
|
||||
|
||||
初始值一般为 0 值,例如下面的类变量 value 被初始化为 0 而不是 123。
|
||||
初始值一般为 0 值,例如下面的类变量 value 被初始化为 0 而不是 123。
|
||||
|
||||
```java
|
||||
public static int value = 123;
|
||||
public static int value = 123;
|
||||
```
|
||||
|
||||
如果类变量是常量,那么会按照表达式来进行初始化,而不是赋值为 0。
|
||||
如果类变量是常量,那么会按照表达式来进行初始化,而不是赋值为 0。
|
||||
|
||||
```java
|
||||
public static final int value = 123;
|
||||
public static final int value = 123;
|
||||
```
|
||||
|
||||
### 3.4 解析
|
||||
### 3.4 解析
|
||||
|
||||
将常量池的符号引用替换为直接引用的过程。
|
||||
|
||||
### 3.5 初始化
|
||||
### 3.5 初始化
|
||||
|
||||
初始化阶段才真正开始执行类中的定义的 Java 程序代码。初始化阶段即虚拟机执行类构造器 <clinit>() 方法的过程。
|
||||
初始化阶段才真正开始执行类中的定义的 Java 程序代码。初始化阶段即虚拟机执行类构造器 <clinit>() 方法的过程。
|
||||
|
||||
在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。
|
||||
|
||||
<clinit>() 方法具有以下特点:
|
||||
<clinit>() 方法具有以下特点:
|
||||
|
||||
- 是由编译器自动收集类中所有类变量的赋值动作和静态语句块(static{} 块)中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序决定。特别注意的是,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问。例如以下代码:
|
||||
- 是由编译器自动收集类中所有类变量的赋值动作和静态语句块(static{} 块)中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序决定。特别注意的是,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问。例如以下代码:
|
||||
|
||||
```java
|
||||
public class Test {
|
||||
static {
|
||||
i = 0; // 给变量赋值可以正常编译通过
|
||||
System.out.print(i); // 这句编译器会提示“非法向前引用”
|
||||
}
|
||||
static int i = 1;
|
||||
public class Test {
|
||||
static {
|
||||
i = 0; // 给变量赋值可以正常编译通过
|
||||
System.out.print(i); // 这句编译器会提示“非法向前引用”
|
||||
}
|
||||
static int i = 1;
|
||||
}
|
||||
```
|
||||
|
||||
- 与类的构造函数(或者说实例构造器 <init>())不同,不需要显式的调用父类的构造器。虚拟机会自动保证在子类的 <clinit>() 方法运行之前,父类的 <clinit>() 方法已经执行结束。因此虚拟机中第一个执行 <clinit>() 方法的类肯定为 java.lang.Object。
|
||||
- 与类的构造函数(或者说实例构造器 <init>())不同,不需要显式的调用父类的构造器。虚拟机会自动保证在子类的 <clinit>() 方法运行之前,父类的 <clinit>() 方法已经执行结束。因此虚拟机中第一个执行 <clinit>() 方法的类肯定为 java.lang.Object。
|
||||
|
||||
- 由于父类的 <clinit>() 方法先执行,也就意味着父类中定义的静态语句块要优于子类的变量赋值操作。例如以下代码:
|
||||
- 由于父类的 <clinit>() 方法先执行,也就意味着父类中定义的静态语句块要优于子类的变量赋值操作。例如以下代码:
|
||||
|
||||
```java
|
||||
static class Parent {
|
||||
public static int A = 1;
|
||||
static {
|
||||
A = 2;
|
||||
}
|
||||
static class Parent {
|
||||
public static int A = 1;
|
||||
static {
|
||||
A = 2;
|
||||
}
|
||||
}
|
||||
|
||||
static class Sub extends Parent {
|
||||
public static int B = A;
|
||||
static class Sub extends Parent {
|
||||
public static int B = A;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println(Sub.B); // 输出结果是父类中的静态变量 A 的值 ,也就是 2。
|
||||
public static void main(String[] args) {
|
||||
System.out.println(Sub.B); // 输出结果是父类中的静态变量 A 的值 ,也就是 2。
|
||||
}
|
||||
```
|
||||
|
||||
- <clinit>() 方法对于类或接口不是必须的,如果一个类中不包含静态语句块,也没有对类变量的赋值操作,编译器可以不为该类生成 <clinit>() 方法。
|
||||
- <clinit>() 方法对于类或接口不是必须的,如果一个类中不包含静态语句块,也没有对类变量的赋值操作,编译器可以不为该类生成 <clinit>() 方法。
|
||||
|
||||
- 接口中不可以使用静态语句块,但仍然有类变量初始化的赋值操作,因此接口与类一样都会生成 <clinit>() 方法。但接口与类不同的是,执行接口的 <clinit>() 方法不需要先执行父接口的 <clinit>() 方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的 <clinit>() 方法。
|
||||
- 接口中不可以使用静态语句块,但仍然有类变量初始化的赋值操作,因此接口与类一样都会生成 <clinit>() 方法。但接口与类不同的是,执行接口的 <clinit>() 方法不需要先执行父接口的 <clinit>() 方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的 <clinit>() 方法。
|
||||
|
||||
- 虚拟机会保证一个类的 <clinit>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 <clinit>() 方法,其它线程都会阻塞等待,直到活动线程执行 <clinit>() 方法完毕。如果在一个类的 <clinit>() 方法中有耗时的操作,就可能造成多个进程阻塞,在实际过程中此种阻塞很隐蔽。
|
||||
- 虚拟机会保证一个类的 <clinit>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 <clinit>() 方法,其它线程都会阻塞等待,直到活动线程执行 <clinit>() 方法完毕。如果在一个类的 <clinit>() 方法中有耗时的操作,就可能造成多个进程阻塞,在实际过程中此种阻塞很隐蔽。
|
||||
|
||||
## 4. 类加载器
|
||||
## 4. 类加载器
|
||||
|
||||
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流 ( 即字节码 )”这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。
|
||||
虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流 ( 即字节码 )”这个动作放到 Java 虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。
|
||||
|
||||
### 4.1 类与类加载器
|
||||
### 4.1 类与类加载器
|
||||
|
||||
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。通俗而言:比较两个类是否“相等”(这里所指的“相等”,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果,也包括使用 instanceof() 关键字做对象所属关系判定等情况),只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
|
||||
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在 Java 虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。通俗而言:比较两个类是否“相等”(这里所指的“相等”,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果,也包括使用 instanceof() 关键字做对象所属关系判定等情况),只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
|
||||
|
||||
### 4.2 类加载器分类
|
||||
### 4.2 类加载器分类
|
||||
|
||||
从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器:
|
||||
从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器:
|
||||
|
||||
一种是启动类加载器(Bootstrap ClassLoader),这个类加载器用 C++ 实现,是虚拟机自身的一部分;另一种就是所有其他类的加载器,这些类由 Java 实现,独立于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。
|
||||
一种是启动类加载器(Bootstrap ClassLoader),这个类加载器用 C++ 实现,是虚拟机自身的一部分;另一种就是所有其他类的加载器,这些类由 Java 实现,独立于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。
|
||||
|
||||
从 Java 开发人员的角度看,类加载器可以划分得更细致一些:
|
||||
从 Java 开发人员的角度看,类加载器可以划分得更细致一些:
|
||||
|
||||
- 启动类加载器(Bootstrap ClassLoader) 此类加载器负责将存放在 <JAVA_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。 启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
|
||||
- 启动类加载器(Bootstrap ClassLoader) 此类加载器负责将存放在 <JAVA_HOME>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。 启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
|
||||
|
||||
- 扩展类加载器(Extension ClassLoader) 这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 <Java_Home>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。
|
||||
- 扩展类加载器(Extension ClassLoader) 这个类加载器是由 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将 <Java_Home>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。
|
||||
|
||||
- 应用程序类加载器(Application ClassLoader) 这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
|
||||
- 应用程序类加载器(Application ClassLoader) 这个类加载器是由 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
|
||||
|
||||
### 4.3 双亲委派模型
|
||||
### 4.3 双亲委派模型
|
||||
|
||||
应用程序都是由三种类加载器相互配合进行加载的,如果有必要,还可以加入自己定义的类加载器。下图展示的类加载器之间的层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。该模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器,这里类加载器之间的父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)的关系实现。
|
||||
应用程序都是由三种类加载器相互配合进行加载的,如果有必要,还可以加入自己定义的类加载器。下图展示的类加载器之间的层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。该模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器,这里类加载器之间的父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)的关系实现。
|
||||
|
||||
![](index_files/2cdc3ce2-fa82-4c22-baaa-000c07d10473.jpg)
|
||||
<div align="center"> <img src="../pics//2cdc3ce2-fa82-4c22-baaa-000c07d10473.jpg"/> </div><br>
|
||||
|
||||
**工作过程**
|
||||
**工作过程**
|
||||
|
||||
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载,而是把这个请求委派给父类加载器,每一个层次的加载器都是如此,依次递归。因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成此加载请求(它搜索范围中没有找到所需类)时,子加载器才会尝试自己加载。
|
||||
|
||||
**好处**
|
||||
**好处**
|
||||
|
||||
使用双亲委派模型来组织类加载器之间的关系,使得 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类 java.lang.Object,它存放在 rt.jar 中,无论哪个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此 Object 类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型,由各个类加载器自行加载的话,如果用户编写了一个称为java.lang.Object 的类,并放在程序的 ClassPath 中,那系统中将会出现多个不同的 Object 类,程序将变得一片混乱。如果开发者尝试编写一个与 rt.jar 类库中已有类重名的 Java 类,将会发现可以正常编译,但是永远无法被加载运行。
|
||||
使用双亲委派模型来组织类加载器之间的关系,使得 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类 java.lang.Object,它存放在 rt.jar 中,无论哪个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此 Object 类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型,由各个类加载器自行加载的话,如果用户编写了一个称为java.lang.Object 的类,并放在程序的 ClassPath 中,那系统中将会出现多个不同的 Object 类,程序将变得一片混乱。如果开发者尝试编写一个与 rt.jar 类库中已有类重名的 Java 类,将会发现可以正常编译,但是永远无法被加载运行。
|
||||
|
||||
**实现**
|
||||
**实现**
|
||||
|
||||
```java
|
||||
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
|
||||
//check the class has been loaded or not
|
||||
Class c = findLoadedClass(name);
|
||||
if(c == null) {
|
||||
try{
|
||||
if(parent != null) {
|
||||
c = parent.loadClass(name, false);
|
||||
} else{
|
||||
c = findBootstrapClassOrNull(name);
|
||||
}
|
||||
} catch(ClassNotFoundException e) {
|
||||
//if throws the exception , the father can not complete the load
|
||||
}
|
||||
if(c == null) {
|
||||
c = findClass(name);
|
||||
}
|
||||
}
|
||||
if(resolve) {
|
||||
resolveClass(c);
|
||||
}
|
||||
return c;
|
||||
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
|
||||
//check the class has been loaded or not
|
||||
Class c = findLoadedClass(name);
|
||||
if(c == null) {
|
||||
try{
|
||||
if(parent != null) {
|
||||
c = parent.loadClass(name, false);
|
||||
} else{
|
||||
c = findBootstrapClassOrNull(name);
|
||||
}
|
||||
} catch(ClassNotFoundException e) {
|
||||
//if throws the exception , the father can not complete the load
|
||||
}
|
||||
if(c == null) {
|
||||
c = findClass(name);
|
||||
}
|
||||
}
|
||||
if(resolve) {
|
||||
resolveClass(c);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
```
|
||||
|
||||
# JVM 参数
|
||||
# JVM 参数
|
||||
|
||||
## GC 优化配置
|
||||
## GC 优化配置
|
||||
|
||||
| 配置 | 描述 |
|
||||
| --- | --- |
|
||||
| -Xms | 初始化堆内存大小 |
|
||||
| -Xmx | 堆内存最大值 |
|
||||
| -Xmn | 新生代大小 |
|
||||
| -XX:PermSize | 初始化永久代大小 |
|
||||
| -XX:MaxPermSize | 永久代最大容量 |
|
||||
| 配置 | 描述 |
|
||||
| --- | --- |
|
||||
| -Xms | 初始化堆内存大小 |
|
||||
| -Xmx | 堆内存最大值 |
|
||||
| -Xmn | 新生代大小 |
|
||||
| -XX:PermSize | 初始化永久代大小 |
|
||||
| -XX:MaxPermSize | 永久代最大容量 |
|
||||
|
||||
## GC 类型设置
|
||||
## GC 类型设置
|
||||
|
||||
| 配置 | 描述 |
|
||||
| --- | --- |
|
||||
| -XX:+UseSerialGC | 串行垃圾回收器 |
|
||||
| -XX:+UseParallelGC | 并行垃圾回收器 |
|
||||
| -XX:+UseConcMarkSweepGC | 并发标记扫描垃圾回收器 |
|
||||
| -XX:ParallelCMSThreads= | 并发标记扫描垃圾回收器 = 为使用的线程数量 |
|
||||
| -XX:+UseG1GC | G1 垃圾回收器 |
|
||||
| 配置 | 描述 |
|
||||
| --- | --- |
|
||||
| -XX:+UseSerialGC | 串行垃圾回收器 |
|
||||
| -XX:+UseParallelGC | 并行垃圾回收器 |
|
||||
| -XX:+UseConcMarkSweepGC | 并发标记扫描垃圾回收器 |
|
||||
| -XX:ParallelCMSThreads= | 并发标记扫描垃圾回收器 = 为使用的线程数量 |
|
||||
| -XX:+UseG1GC | G1 垃圾回收器 |
|
||||
|
||||
```java
|
||||
java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar java-application.jar
|
||||
```
|
||||
java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar java-application.jar
|
||||
```
|
||||
|
|
392
notes/Java IO.md
392
notes/Java IO.md
|
@ -1,63 +1,95 @@
|
|||
[TOC]
|
||||
<!-- GFM-TOC -->
|
||||
* [概览](#概览)
|
||||
* [磁盘操作](#磁盘操作)
|
||||
* [字节操作](#字节操作)
|
||||
* [字符操作](#字符操作)
|
||||
* [对象操作](#对象操作)
|
||||
* [网络操作](#网络操作)
|
||||
* [1. InetAddress](#1-inetaddress)
|
||||
* [2. URL](#2-url)
|
||||
* [3. Sockets](#3-sockets)
|
||||
* [4. Datagram](#4-datagram)
|
||||
* [NIO](#nio)
|
||||
* [1. 流与块](#1-流与块)
|
||||
* [2. 通道与缓冲区](#2-通道与缓冲区)
|
||||
* [2.1 通道](#21-通道)
|
||||
* [2.2 缓冲区](#22-缓冲区)
|
||||
* [3. 缓冲区状态变量](#3-缓冲区状态变量)
|
||||
* [4. 文件 NIO 实例](#4-文件-nio-实例)
|
||||
* [5. 阻塞与非阻塞](#5-阻塞与非阻塞)
|
||||
* [5.1 阻塞式 I/O](#51-阻塞式-io)
|
||||
* [5.2 非阻塞式 I/O](#52-非阻塞式-io)
|
||||
* [6. 套接字 NIO 实例](#6-套接字-nio-实例)
|
||||
* [6.1 ServerSocketChannel](#61-serversocketchannel)
|
||||
* [6.2 Selectors](#62-selectors)
|
||||
* [6.3 主循环](#63-主循环)
|
||||
* [6.4 监听新连接](#64-监听新连接)
|
||||
* [6.5 接受新的连接](#65-接受新的连接)
|
||||
* [6.6 删除处理过的 SelectionKey](#66-删除处理过的-selectionkey)
|
||||
* [6.7 传入的 I/O](#67-传入的-io)
|
||||
* [7. 内存映射文件](#7-内存映射文件)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
# 概览
|
||||
|
||||
Java 的 I/O 大概可以分成以下几类
|
||||
# 概览
|
||||
|
||||
1. 磁盘操作:File
|
||||
2. 字节操作:InputStream 和 OutputStream
|
||||
3. 字符操作:Reader 和 Writer
|
||||
4. 对象操作:Serializable
|
||||
5. 网络操作:Socket
|
||||
6. 新的输入/输出:NIO
|
||||
Java 的 I/O 大概可以分成以下几类
|
||||
|
||||
# 磁盘操作
|
||||
1. 磁盘操作:File
|
||||
2. 字节操作:InputStream 和 OutputStream
|
||||
3. 字符操作:Reader 和 Writer
|
||||
4. 对象操作:Serializable
|
||||
5. 网络操作:Socket
|
||||
6. 新的输入/输出:NIO
|
||||
|
||||
File 类可以用于表示文件和目录,但是它只用于表示文件的信息,而不表示文件的内容。
|
||||
# 磁盘操作
|
||||
|
||||
# 字节操作
|
||||
File 类可以用于表示文件和目录,但是它只用于表示文件的信息,而不表示文件的内容。
|
||||
|
||||
![](index_files/8143787f-12eb-46ea-9bc3-c66d22d35285.jpg)
|
||||
# 字节操作
|
||||
|
||||
Java I/O 使用了装饰者模式来实现。以 InputStream 为例,InputStream 是抽象组件,FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作。FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能,例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。
|
||||
<div align="center"> <img src="../pics//8143787f-12eb-46ea-9bc3-c66d22d35285.jpg"/> </div><br>
|
||||
|
||||
实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。
|
||||
Java I/O 使用了装饰者模式来实现。以 InputStream 为例,InputStream 是抽象组件,FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作。FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能,例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。
|
||||
|
||||
实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。
|
||||
|
||||
```java
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
|
||||
```
|
||||
|
||||
DataInputStream 装饰者提供了对更多数据类型进行输入的操作,比如 int、double 等基本类型。
|
||||
DataInputStream 装饰者提供了对更多数据类型进行输入的操作,比如 int、double 等基本类型。
|
||||
|
||||
批量读入文件内容到字节数组:
|
||||
|
||||
```java
|
||||
byte[] buf = new byte[20*1024];
|
||||
int bytes = 0;
|
||||
// 最多读取 buf.length 个字节,返回的是实际读取的个数,返回 -1 的时候表示读到 eof,即文件尾
|
||||
while((bytes = in.read(buf, 0 , buf.length)) != -1) {
|
||||
// ...
|
||||
byte[] buf = new byte[20*1024];
|
||||
int bytes = 0;
|
||||
// 最多读取 buf.length 个字节,返回的是实际读取的个数,返回 -1 的时候表示读到 eof,即文件尾
|
||||
while((bytes = in.read(buf, 0 , buf.length)) != -1) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
# 字符操作
|
||||
# 字符操作
|
||||
|
||||
不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符,所以 I/O 操作的都是字节而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。
|
||||
不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符,所以 I/O 操作的都是字节而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。
|
||||
|
||||
InputStreamReader 实现从文本文件的字节流解码成字符流;OutputStreamWriter 实现字符流编码成为文本文件的字节流。它们继承自 Reader 和 Writer。
|
||||
InputStreamReader 实现从文本文件的字节流解码成字符流;OutputStreamWriter 实现字符流编码成为文本文件的字节流。它们继承自 Reader 和 Writer。
|
||||
|
||||
编码就是把字符转换为字节,而解码是把字节重新组合成字符。
|
||||
|
||||
```java
|
||||
byte[] bytes = str.getBytes(encoding); // 编码
|
||||
String str = new String(bytes, encoding); // 解码
|
||||
byte[] bytes = str.getBytes(encoding); // 编码
|
||||
String str = new String(bytes, encoding); // 解码
|
||||
```
|
||||
|
||||
GBK 编码中,中文占 2 个字节,英文占 1 个字节;UTF-8 编码中,中文占 3 个字节,英文占 1 个字节;Java 使用双字节编码 UTF-16be,中文和英文都占 2 个字节。
|
||||
GBK 编码中,中文占 2 个字节,英文占 1 个字节;UTF-8 编码中,中文占 3 个字节,英文占 1 个字节;Java 使用双字节编码 UTF-16be,中文和英文都占 2 个字节。
|
||||
|
||||
如果编码和解码过程使用不同的编码方式那么就出现了乱码。
|
||||
|
||||
# 对象操作
|
||||
# 对象操作
|
||||
|
||||
序列化就是将一个对象转换成字节序列,方便存储和传输。
|
||||
|
||||
|
@ -65,96 +97,96 @@ GBK 编码中,中文占 2 个字节,英文占 1 个字节;UTF-8 编
|
|||
|
||||
反序列化:ObjectInputStream.readObject()
|
||||
|
||||
序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现。
|
||||
序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现。
|
||||
|
||||
transient 关键字可以使一些属性不会被序列化。
|
||||
transient 关键字可以使一些属性不会被序列化。
|
||||
|
||||
**ArrayList 序列化和反序列化的实现**:ArrayList 中存储数据的数组是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。
|
||||
**ArrayList 序列化和反序列化的实现** :ArrayList 中存储数据的数组是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。
|
||||
|
||||
```
|
||||
private transient Object[] elementData;
|
||||
private transient Object[] elementData;
|
||||
```
|
||||
|
||||
# 网络操作
|
||||
# 网络操作
|
||||
|
||||
Java 中的网络支持:
|
||||
Java 中的网络支持:
|
||||
|
||||
1. InetAddress:用于表示网络上的硬件资源,即 IP 地址;
|
||||
2. URL:统一资源定位符,通过 URL 可以直接读取或者写入网络上的数据;
|
||||
3. Sockets:使用 TCP 协议实现网络通信;
|
||||
4. Datagram:使用 UDP 协议实现网络通信。
|
||||
1. InetAddress:用于表示网络上的硬件资源,即 IP 地址;
|
||||
2. URL:统一资源定位符,通过 URL 可以直接读取或者写入网络上的数据;
|
||||
3. Sockets:使用 TCP 协议实现网络通信;
|
||||
4. Datagram:使用 UDP 协议实现网络通信。
|
||||
|
||||
## 1. InetAddress
|
||||
## 1. InetAddress
|
||||
|
||||
没有公有构造函数,只能通过静态方法来创建实例。
|
||||
|
||||
```java
|
||||
InetAddress.getByName(String host);
|
||||
InetAddress.getByAddress(byte[] addr);
|
||||
InetAddress.getByName(String host);
|
||||
InetAddress.getByAddress(byte[] addr);
|
||||
```
|
||||
|
||||
## 2. URL
|
||||
## 2. URL
|
||||
|
||||
可以直接从 URL 中读取字节流数据
|
||||
可以直接从 URL 中读取字节流数据
|
||||
|
||||
```java
|
||||
URL url = new URL("http://www.baidu.com");
|
||||
InputStream is = url.openStream(); // 字节流
|
||||
InputStreamReader isr = new InputStreamReader(is, "utf-8"); // 字符流
|
||||
BufferedReader br = new BufferedReader(isr);
|
||||
String line = br.readLine();
|
||||
while (line != null) {
|
||||
System.out.println(line);
|
||||
line = br.readLine();
|
||||
URL url = new URL("http://www.baidu.com");
|
||||
InputStream is = url.openStream(); // 字节流
|
||||
InputStreamReader isr = new InputStreamReader(is, "utf-8"); // 字符流
|
||||
BufferedReader br = new BufferedReader(isr);
|
||||
String line = br.readLine();
|
||||
while (line != null) {
|
||||
System.out.println(line);
|
||||
line = br.readLine();
|
||||
}
|
||||
br.close();
|
||||
isr.close();
|
||||
is.close();
|
||||
```
|
||||
|
||||
## 3. Sockets
|
||||
## 3. Sockets
|
||||
|
||||
- ServerSocket:服务器端类
|
||||
- Socket:客户端类
|
||||
- 服务器和客户端通过 InputStream 和 OutputStream 进行输入输出。
|
||||
- ServerSocket:服务器端类
|
||||
- Socket:客户端类
|
||||
- 服务器和客户端通过 InputStream 和 OutputStream 进行输入输出。
|
||||
|
||||
![](index_files/fa4101d7-19ce-4a69-a84f-20bbe64320e5.jpg)
|
||||
<div align="center"> <img src="../pics//fa4101d7-19ce-4a69-a84f-20bbe64320e5.jpg"/> </div><br>
|
||||
|
||||
## 4. Datagram
|
||||
## 4. Datagram
|
||||
|
||||
- DatagramPacket:数据包类
|
||||
- DatagramSocket:通信类
|
||||
- DatagramPacket:数据包类
|
||||
- DatagramSocket:通信类
|
||||
|
||||
# NIO
|
||||
# NIO
|
||||
|
||||
新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的。NIO 弥补了原来的 I/O 的不足,它在标准 Java 代码中提供了高速的、面向块的 I/O。
|
||||
新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的。NIO 弥补了原来的 I/O 的不足,它在标准 Java 代码中提供了高速的、面向块的 I/O。
|
||||
|
||||
## 1. 流与块
|
||||
## 1. 流与块
|
||||
|
||||
I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。
|
||||
I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。
|
||||
|
||||
面向流的 I/O 一次处理一个字节数据,一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也是相对简单的。不利的一面是,面向流的 I/O 通常相当慢。
|
||||
面向流的 I/O 一次处理一个字节数据,一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也是相对简单的。不利的一面是,面向流的 I/O 通常相当慢。
|
||||
|
||||
一个面向块的 I/O 系统以块的形式处理数据,一次处理数据块。按块处理数据比按流处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。
|
||||
一个面向块的 I/O 系统以块的形式处理数据,一次处理数据块。按块处理数据比按流处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。
|
||||
|
||||
I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如, java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。
|
||||
I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如, java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。
|
||||
|
||||
## 2. 通道与缓冲区
|
||||
## 2. 通道与缓冲区
|
||||
|
||||
### 2.1 通道
|
||||
### 2.1 通道
|
||||
|
||||
通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。
|
||||
通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。
|
||||
|
||||
通道与流的不同之处在于,流只能在一个方向上移动,(一个流必须是 InputStream 或者 OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写。
|
||||
通道与流的不同之处在于,流只能在一个方向上移动,(一个流必须是 InputStream 或者 OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写。
|
||||
|
||||
通道包括以下类型:
|
||||
|
||||
- FileChannel:从文件中读写数据;
|
||||
- DatagramChannel:通过 UDP 读写网络中数据;
|
||||
- SocketChannel:通过 TCP 读写网络中数据;
|
||||
- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。
|
||||
- FileChannel:从文件中读写数据;
|
||||
- DatagramChannel:通过 UDP 读写网络中数据;
|
||||
- SocketChannel:通过 TCP 读写网络中数据;
|
||||
- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。
|
||||
|
||||
### 2.2 缓冲区
|
||||
### 2.2 缓冲区
|
||||
|
||||
发送给一个通道的所有对象都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。
|
||||
|
||||
|
@ -162,230 +194,230 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基
|
|||
|
||||
缓冲区包括以下类型:
|
||||
|
||||
- ByteBuffer
|
||||
- CharBuffer
|
||||
- ShortBuffer
|
||||
- IntBuffer
|
||||
- LongBuffer
|
||||
- FloatBuffer
|
||||
- DoubleBuffer
|
||||
- ByteBuffer
|
||||
- CharBuffer
|
||||
- ShortBuffer
|
||||
- IntBuffer
|
||||
- LongBuffer
|
||||
- FloatBuffer
|
||||
- DoubleBuffer
|
||||
|
||||
## 3. 缓冲区状态变量
|
||||
## 3. 缓冲区状态变量
|
||||
|
||||
- capacity:最大容量;
|
||||
- position:当前已经读写的字节数;
|
||||
- limit:还可以读写的字节数。
|
||||
- capacity:最大容量;
|
||||
- position:当前已经读写的字节数;
|
||||
- limit:还可以读写的字节数。
|
||||
|
||||
状态变量的改变过程:
|
||||
|
||||
1. 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 9。capacity 变量不会改变,下面的讨论会忽略它。
|
||||
![](index_files/1bea398f-17a7-4f67-a90b-9e2d243eaa9a.png)
|
||||
1. 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 9。capacity 变量不会改变,下面的讨论会忽略它。
|
||||
<div align="center"> <img src="../pics//1bea398f-17a7-4f67-a90b-9e2d243eaa9a.png"/> </div><br>
|
||||
|
||||
2. 从输入通道中读取 3 个字节数据写入缓冲区中,此时 position 移动设为 3,limit 保持不变。
|
||||
![](index_files/4628274c-25b6-4053-97cf-d1239b44c43d.png)
|
||||
2. 从输入通道中读取 3 个字节数据写入缓冲区中,此时 position 移动设为 3,limit 保持不变。
|
||||
<div align="center"> <img src="../pics//4628274c-25b6-4053-97cf-d1239b44c43d.png"/> </div><br>
|
||||
|
||||
3. 以下图例为已经从输入通道读取了 5 个字节数据写入缓冲区中。在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0。
|
||||
![](index_files/952e06bd-5a65-4cab-82e4-dd1536462f38.png)
|
||||
3. 以下图例为已经从输入通道读取了 5 个字节数据写入缓冲区中。在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0。
|
||||
<div align="center"> <img src="../pics//952e06bd-5a65-4cab-82e4-dd1536462f38.png"/> </div><br>
|
||||
|
||||
4. 从缓冲区中取 4 个字节到输出缓冲中,此时 position 设为 4。
|
||||
![](index_files/b5bdcbe2-b958-4aef-9151-6ad963cb28b4.png)
|
||||
4. 从缓冲区中取 4 个字节到输出缓冲中,此时 position 设为 4。
|
||||
<div align="center"> <img src="../pics//b5bdcbe2-b958-4aef-9151-6ad963cb28b4.png"/> </div><br>
|
||||
|
||||
5. 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。
|
||||
![](index_files/67bf5487-c45d-49b6-b9c0-a058d8c68902.png)
|
||||
5. 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。
|
||||
<div align="center"> <img src="../pics//67bf5487-c45d-49b6-b9c0-a058d8c68902.png"/> </div><br>
|
||||
|
||||
## 4. 文件 NIO 实例
|
||||
## 4. 文件 NIO 实例
|
||||
|
||||
1\. 为要读取的文件创建 FileInputStream,之后通过 FileInputStream 获取输入 FileChannel;
|
||||
1\. 为要读取的文件创建 FileInputStream,之后通过 FileInputStream 获取输入 FileChannel;
|
||||
|
||||
```java
|
||||
FileInputStream fin = new FileInputStream("readandshow.txt");
|
||||
FileChannel fic = fin.getChannel();
|
||||
FileInputStream fin = new FileInputStream("readandshow.txt");
|
||||
FileChannel fic = fin.getChannel();
|
||||
```
|
||||
|
||||
2\. 创建一个容量为 1024 的 Buffer;
|
||||
2\. 创建一个容量为 1024 的 Buffer;
|
||||
|
||||
```java
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1024);
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1024);
|
||||
```
|
||||
|
||||
3\. 将数据从输入 FileChannel 写入到 Buffer 中,如果没有数据的话, read() 方法会返回 -1;
|
||||
3\. 将数据从输入 FileChannel 写入到 Buffer 中,如果没有数据的话, read() 方法会返回 -1;
|
||||
|
||||
```java
|
||||
int r = fcin.read(buffer);
|
||||
if (r == -1) {
|
||||
break;
|
||||
int r = fcin.read(buffer);
|
||||
if (r == -1) {
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
4\. 为要写入的文件创建 FileOutputStream,之后通过 FileOutputStream 获取输出 FileChannel
|
||||
4\. 为要写入的文件创建 FileOutputStream,之后通过 FileOutputStream 获取输出 FileChannel
|
||||
|
||||
```java
|
||||
FileOutputStream fout = new FileOutputStream("writesomebytes.txt");
|
||||
FileChannel foc = fout.getChannel();
|
||||
FileOutputStream fout = new FileOutputStream("writesomebytes.txt");
|
||||
FileChannel foc = fout.getChannel();
|
||||
```
|
||||
|
||||
5\. 调用 flip() 切换读写
|
||||
5\. 调用 flip() 切换读写
|
||||
|
||||
```java
|
||||
buffer.flip();
|
||||
```
|
||||
|
||||
6\. 把 Buffer 中的数据读取到输出 FileChannel 中
|
||||
6\. 把 Buffer 中的数据读取到输出 FileChannel 中
|
||||
|
||||
```java
|
||||
foc.write(buffer);
|
||||
```
|
||||
|
||||
7\. 最后调用 clear() 重置缓冲区
|
||||
7\. 最后调用 clear() 重置缓冲区
|
||||
|
||||
```java
|
||||
buffer.clear();
|
||||
```
|
||||
|
||||
## 5. 阻塞与非阻塞
|
||||
## 5. 阻塞与非阻塞
|
||||
|
||||
应当注意,FileChannel 不能切换到非阻塞模式,套接字 Channel 可以。
|
||||
应当注意,FileChannel 不能切换到非阻塞模式,套接字 Channel 可以。
|
||||
|
||||
### 5.1 阻塞式 I/O
|
||||
### 5.1 阻塞式 I/O
|
||||
|
||||
阻塞式 I/O 在调用 InputStream.read() 方法时会一直等到数据到来时(或超时)才会返回,在调用 ServerSocket.accept() 方法时,也会一直阻塞到有客户端连接才会返回。
|
||||
阻塞式 I/O 在调用 InputStream.read() 方法时会一直等到数据到来时(或超时)才会返回,在调用 ServerSocket.accept() 方法时,也会一直阻塞到有客户端连接才会返回。
|
||||
|
||||
服务端都会为每个连接的客户端创建一个线程来处理读写请求,阻塞式的特点会造成服务器会创建大量线程,并且大部分线程处于阻塞的状态,因此对服务器的性能会有很大的影响。
|
||||
|
||||
![](index_files/edc23f99-c46c-4200-b64e-07516828720d.jpg)
|
||||
<div align="center"> <img src="../pics//edc23f99-c46c-4200-b64e-07516828720d.jpg"/> </div><br>
|
||||
|
||||
### 5.2 非阻塞式 I/O
|
||||
### 5.2 非阻塞式 I/O
|
||||
|
||||
由一个专门的线程来处理所有的 I/O 事件,并负责分发。
|
||||
由一个专门的线程来处理所有的 I/O 事件,并负责分发。
|
||||
|
||||
事件驱动机制:事件到的时候触发,而不是同步地监视事件。
|
||||
|
||||
线程通信:线程之间通过 wait()、notify() 等方式通信,保证每次上下文切换都是有意义的,减少无谓的线程切换。
|
||||
线程通信:线程之间通过 wait()、notify() 等方式通信,保证每次上下文切换都是有意义的,减少无谓的线程切换。
|
||||
|
||||
![](index_files/7fcb2fb0-2cd9-4396-bc2d-282becf963c3.jpg)
|
||||
<div align="center"> <img src="../pics//7fcb2fb0-2cd9-4396-bc2d-282becf963c3.jpg"/> </div><br>
|
||||
|
||||
## 6. 套接字 NIO 实例
|
||||
## 6. 套接字 NIO 实例
|
||||
|
||||
### 6.1 ServerSocketChannel
|
||||
### 6.1 ServerSocketChannel
|
||||
|
||||
每一个监听端口都需要有一个 ServerSocketChannel 用来监听连接。
|
||||
每一个监听端口都需要有一个 ServerSocketChannel 用来监听连接。
|
||||
|
||||
```java
|
||||
ServerSocketChannel ssc = ServerSocketChannel.open();
|
||||
ssc.configureBlocking(false); // 设置为非阻塞
|
||||
ServerSocketChannel ssc = ServerSocketChannel.open();
|
||||
ssc.configureBlocking(false); // 设置为非阻塞
|
||||
|
||||
ServerSocket ss = ssc.socket();
|
||||
InetSocketAddress address = new InetSocketAddress(ports[i]);
|
||||
ss.bind(address); // 绑定端口号
|
||||
ServerSocket ss = ssc.socket();
|
||||
InetSocketAddress address = new InetSocketAddress(ports[i]);
|
||||
ss.bind(address); // 绑定端口号
|
||||
```
|
||||
|
||||
### 6.2 Selectors
|
||||
### 6.2 Selectors
|
||||
|
||||
异步 I/O 通过 Selector 注册对特定 I/O 事件的兴趣 ― 可读的数据的到达、新的套接字连接等等,在发生这样的事件时,系统将会发送通知。
|
||||
异步 I/O 通过 Selector 注册对特定 I/O 事件的兴趣 ― 可读的数据的到达、新的套接字连接等等,在发生这样的事件时,系统将会发送通知。
|
||||
|
||||
创建 Selectors 之后,就可以对不同的通道对象调用 register() 方法。register() 的第一个参数总是这个 Selector。第二个参数是 OP_ACCEPT,这里它指定我们想要监听 ACCEPT 事件,也就是在新的连接建立时所发生的事件。
|
||||
创建 Selectors 之后,就可以对不同的通道对象调用 register() 方法。register() 的第一个参数总是这个 Selector。第二个参数是 OP_ACCEPT,这里它指定我们想要监听 ACCEPT 事件,也就是在新的连接建立时所发生的事件。
|
||||
|
||||
SelectionKey 代表这个通道在此 Selector 上的这个注册。当某个 Selector 通知您某个传入事件时,它是通过提供对应于该事件的 SelectionKey 来进行的。SelectionKey 还可以用于取消通道的注册。
|
||||
SelectionKey 代表这个通道在此 Selector 上的这个注册。当某个 Selector 通知您某个传入事件时,它是通过提供对应于该事件的 SelectionKey 来进行的。SelectionKey 还可以用于取消通道的注册。
|
||||
|
||||
```java
|
||||
Selector selector = Selector.open();
|
||||
SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);
|
||||
Selector selector = Selector.open();
|
||||
SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);
|
||||
```
|
||||
|
||||
### 6.3 主循环
|
||||
### 6.3 主循环
|
||||
|
||||
首先,我们调用 Selector 的 select() 方法。这个方法会阻塞,直到至少有一个已注册的事件发生。当一个或者更多的事件发生时,select() 方法将返回所发生的事件的数量。
|
||||
首先,我们调用 Selector 的 select() 方法。这个方法会阻塞,直到至少有一个已注册的事件发生。当一个或者更多的事件发生时,select() 方法将返回所发生的事件的数量。
|
||||
|
||||
接下来,我们调用 Selector 的 selectedKeys() 方法,它返回发生了事件的 SelectionKey 对象的一个集合。
|
||||
接下来,我们调用 Selector 的 selectedKeys() 方法,它返回发生了事件的 SelectionKey 对象的一个集合。
|
||||
|
||||
我们通过迭代 SelectionKeys 并依次处理每个 SelectionKey 来处理事件。对于每一个 SelectionKey,您必须确定发生的是什么 I/O 事件,以及这个事件影响哪些 I/O 对象。
|
||||
我们通过迭代 SelectionKeys 并依次处理每个 SelectionKey 来处理事件。对于每一个 SelectionKey,您必须确定发生的是什么 I/O 事件,以及这个事件影响哪些 I/O 对象。
|
||||
|
||||
```java
|
||||
int num = selector.select();
|
||||
int num = selector.select();
|
||||
|
||||
Set selectedKeys = selector.selectedKeys();
|
||||
Iterator it = selectedKeys.iterator();
|
||||
Set selectedKeys = selector.selectedKeys();
|
||||
Iterator it = selectedKeys.iterator();
|
||||
|
||||
while (it.hasNext()) {
|
||||
SelectionKey key = (SelectionKey)it.next();
|
||||
// ... deal with I/O event ...
|
||||
while (it.hasNext()) {
|
||||
SelectionKey key = (SelectionKey)it.next();
|
||||
// ... deal with I/O event ...
|
||||
}
|
||||
```
|
||||
|
||||
### 6.4 监听新连接
|
||||
### 6.4 监听新连接
|
||||
|
||||
程序执行到这里,我们仅注册了 ServerSocketChannel,并且仅注册它们“接收”事件。为确认这一点,我们对 SelectionKey 调用 readyOps() 方法,并检查发生了什么类型的事件:
|
||||
程序执行到这里,我们仅注册了 ServerSocketChannel,并且仅注册它们“接收”事件。为确认这一点,我们对 SelectionKey 调用 readyOps() 方法,并检查发生了什么类型的事件:
|
||||
|
||||
```java
|
||||
if ((key.readyOps() & SelectionKey.OP_ACCEPT)
|
||||
== SelectionKey.OP_ACCEPT) {
|
||||
// Accept the new connection
|
||||
// ...
|
||||
if ((key.readyOps() & SelectionKey.OP_ACCEPT)
|
||||
== SelectionKey.OP_ACCEPT) {
|
||||
// Accept the new connection
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
可以肯定地说,readOps() 方法告诉我们该事件是新的连接。
|
||||
可以肯定地说,readOps() 方法告诉我们该事件是新的连接。
|
||||
|
||||
### 6.5 接受新的连接
|
||||
### 6.5 接受新的连接
|
||||
|
||||
因为我们知道这个服务器套接字上有一个传入连接在等待,所以可以安全地接受它;也就是说,不用担心 accept() 操作会阻塞:
|
||||
因为我们知道这个服务器套接字上有一个传入连接在等待,所以可以安全地接受它;也就是说,不用担心 accept() 操作会阻塞:
|
||||
|
||||
```java
|
||||
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
|
||||
SocketChannel sc = ssc.accept();
|
||||
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
|
||||
SocketChannel sc = ssc.accept();
|
||||
```
|
||||
|
||||
下一步是将新连接的 SocketChannel 配置为非阻塞的。而且由于接受这个连接的目的是为了读取来自套接字的数据,所以我们还必须将 SocketChannel 注册到 Selector 上,如下所示:
|
||||
下一步是将新连接的 SocketChannel 配置为非阻塞的。而且由于接受这个连接的目的是为了读取来自套接字的数据,所以我们还必须将 SocketChannel 注册到 Selector 上,如下所示:
|
||||
|
||||
```java
|
||||
sc.configureBlocking(false);
|
||||
SelectionKey newKey = sc.register(selector, SelectionKey.OP_READ);
|
||||
SelectionKey newKey = sc.register(selector, SelectionKey.OP_READ);
|
||||
```
|
||||
|
||||
注意我们使用 register() 的 OP_READ 参数,将 SocketChannel 注册用于读取而不是接受新连接。
|
||||
注意我们使用 register() 的 OP_READ 参数,将 SocketChannel 注册用于读取而不是接受新连接。
|
||||
|
||||
### 6.6 删除处理过的 SelectionKey
|
||||
### 6.6 删除处理过的 SelectionKey
|
||||
|
||||
在处理 SelectionKey 之后,我们几乎可以返回主循环了。但是我们必须首先将处理过的 SelectionKey 从选定的键集合中删除。如果我们没有删除处理过的键,那么它仍然会在主集合中以一个激活的键出现,这会导致我们尝试再次处理它。我们调用迭代器的 remove() 方法来删除处理过的 SelectionKey:
|
||||
在处理 SelectionKey 之后,我们几乎可以返回主循环了。但是我们必须首先将处理过的 SelectionKey 从选定的键集合中删除。如果我们没有删除处理过的键,那么它仍然会在主集合中以一个激活的键出现,这会导致我们尝试再次处理它。我们调用迭代器的 remove() 方法来删除处理过的 SelectionKey:
|
||||
|
||||
```java
|
||||
it.remove();
|
||||
```
|
||||
|
||||
现在我们可以返回主循环并接受从一个套接字中传入的数据 (或者一个传入的 I/O 事件) 了。
|
||||
现在我们可以返回主循环并接受从一个套接字中传入的数据 (或者一个传入的 I/O 事件) 了。
|
||||
|
||||
### 6.7 传入的 I/O
|
||||
### 6.7 传入的 I/O
|
||||
|
||||
当来自一个套接字的数据到达时,它会触发一个 I/O 事件。这会导致在主循环中调用 Selector.select(),并返回一个或者多个 I/O 事件。这一次, SelectionKey 将被标记为 OP_READ 事件,如下所示:
|
||||
当来自一个套接字的数据到达时,它会触发一个 I/O 事件。这会导致在主循环中调用 Selector.select(),并返回一个或者多个 I/O 事件。这一次, SelectionKey 将被标记为 OP_READ 事件,如下所示:
|
||||
|
||||
```java
|
||||
} else if ((key.readyOps() & SelectionKey.OP_READ)
|
||||
== SelectionKey.OP_READ) {
|
||||
// Read the data
|
||||
SocketChannel sc = (SocketChannel)key.channel();
|
||||
// ...
|
||||
} else if ((key.readyOps() & SelectionKey.OP_READ)
|
||||
== SelectionKey.OP_READ) {
|
||||
// Read the data
|
||||
SocketChannel sc = (SocketChannel)key.channel();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## 7. 内存映射文件
|
||||
## 7. 内存映射文件
|
||||
|
||||
内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。
|
||||
内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。
|
||||
|
||||
只有文件中实际读取或者写入的部分才会映射到内存中。
|
||||
|
||||
现代操作系统一般根据需要将文件的部分映射为内存的部分,从而实现文件系统。Java 内存映射机制不过是在底层操作系统中可以采用这种机制时,提供了对该机制的访问。
|
||||
现代操作系统一般根据需要将文件的部分映射为内存的部分,从而实现文件系统。Java 内存映射机制不过是在底层操作系统中可以采用这种机制时,提供了对该机制的访问。
|
||||
|
||||
向内存映射文件写入可能是危险的,仅只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。
|
||||
|
||||
下面代码行将文件的前 1024 个字节映射到内存中,map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,您可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行行映射。
|
||||
下面代码行将文件的前 1024 个字节映射到内存中,map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,您可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行行映射。
|
||||
|
||||
```java
|
||||
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
|
||||
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
|
||||
```
|
||||
|
||||
# 参考资料
|
||||
# 参考资料
|
||||
|
||||
- Eckel B, 埃克尔, 昊鹏, 等. Java 编程思想 [M]. 机械工业出版社, 2002.
|
||||
- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html)
|
||||
- [深入分析 Java I/O 的工作机制](https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html)
|
||||
- [NIO 与传统 IO 的区别](http://blog.csdn.net/shimiso/article/details/24990499)
|
||||
- Eckel B, 埃克尔, 昊鹏, 等. Java 编程思想 [M]. 机械工业出版社, 2002.
|
||||
- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html)
|
||||
- [深入分析 Java I/O 的工作机制](https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html)
|
||||
- [NIO 与传统 IO 的区别](http://blog.csdn.net/shimiso/article/details/24990499)
|
||||
|
|
560
notes/Java 基础.md
560
notes/Java 基础.md
|
@ -1,509 +1,539 @@
|
|||
[TOC]
|
||||
<!-- GFM-TOC -->
|
||||
* [关键字](#关键字)
|
||||
* [1. final](#1-final)
|
||||
* [2. static](#2-static)
|
||||
* [Object 通用方法](#object-通用方法)
|
||||
* [1. 概览](#1-概览)
|
||||
* [2. clone()](#2-clone)
|
||||
* [3. equals()](#3-equals)
|
||||
* [继承](#继承)
|
||||
* [1. 访问权限](#1-访问权限)
|
||||
* [2. 抽象类与接口](#2-抽象类与接口)
|
||||
* [3. super](#3-super)
|
||||
* [4. 重载与重写](#4-重载与重写)
|
||||
* [String](#string)
|
||||
* [1. String, StringBuffer and StringBuilder](#1-string,-stringbuffer-and-stringbuilder)
|
||||
* [2. String 不可变的原因](#2-string-不可变的原因)
|
||||
* [3. String.intern()](#3-stringintern)
|
||||
* [基本类型与运算](#基本类型与运算)
|
||||
* [1. 包装类型](#1-包装类型)
|
||||
* [2. switch](#2-switch)
|
||||
* [反射](#反射)
|
||||
* [异常](#异常)
|
||||
* [泛型](#泛型)
|
||||
* [注解](#注解)
|
||||
* [特性](#特性)
|
||||
* [1. 三大特性](#1-三大特性)
|
||||
* [2. Java 各版本的新特性](#2-java-各版本的新特性)
|
||||
* [3. Java 与 C++ 的区别](#3-java-与-c++-的区别)
|
||||
* [4. JRE or JDK](#4-jre-or-jdk)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
# 关键字
|
||||
|
||||
## 1. final
|
||||
# 关键字
|
||||
|
||||
**数据**
|
||||
## 1. final
|
||||
|
||||
**数据**
|
||||
|
||||
声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。
|
||||
|
||||
- 对于基本类型,final 使数值不变;
|
||||
- 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
|
||||
- 对于基本类型,final 使数值不变;
|
||||
- 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
|
||||
|
||||
**方法**
|
||||
**方法**
|
||||
|
||||
声明方法不能被子类覆盖。
|
||||
|
||||
private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是覆盖基类方法,而是重载了。
|
||||
private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是覆盖基类方法,而是重载了。
|
||||
|
||||
**类**
|
||||
**类**
|
||||
|
||||
声明类不允许被继承。
|
||||
|
||||
## 2. static
|
||||
## 2. static
|
||||
|
||||
**静态变量**
|
||||
**静态变量**
|
||||
|
||||
静态变量在内存中只存在一份,只在类第一次实例化时初始化一次。
|
||||
|
||||
- 静态变量: 类所有的实例都共享静态变量,可以直接通过类名来访问它;
|
||||
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
|
||||
- 静态变量: 类所有的实例都共享静态变量,可以直接通过类名来访问它;
|
||||
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
|
||||
|
||||
**静态方法**
|
||||
**静态方法**
|
||||
|
||||
静态方法在类加载的时候就存在了,它不依赖于任何实例,所以 static 方法必须实现,也就是说它不能是抽象方法(abstract)。
|
||||
静态方法在类加载的时候就存在了,它不依赖于任何实例,所以 static 方法必须实现,也就是说它不能是抽象方法(abstract)。
|
||||
|
||||
**静态语句块**
|
||||
**静态语句块**
|
||||
|
||||
静态语句块和静态变量一样在类第一次实例化时运行一次。
|
||||
|
||||
**初始化顺序**
|
||||
**初始化顺序**
|
||||
|
||||
静态数据优先于其它数据的初始化,静态变量和静态语句块哪个先运行取决于它们在代码中的顺序。
|
||||
|
||||
```java
|
||||
public static String staticField = "静态变量";
|
||||
public static String staticField = "静态变量";
|
||||
```
|
||||
|
||||
```java
|
||||
static {
|
||||
System.out.println("静态语句块");
|
||||
static {
|
||||
System.out.println("静态语句块");
|
||||
}
|
||||
```
|
||||
|
||||
实例变量和普通语句块的初始化在静态变量和静态语句块初始化结束之后。
|
||||
|
||||
```java
|
||||
public String field = "实例变量";
|
||||
public String field = "实例变量";
|
||||
```
|
||||
|
||||
```java
|
||||
{
|
||||
System.out.println("普通语句块");
|
||||
System.out.println("普通语句块");
|
||||
}
|
||||
```
|
||||
|
||||
最后才是构造函数中的数据进行初始化
|
||||
|
||||
```java
|
||||
public InitialOrderTest() {
|
||||
System.out.println("构造函数");
|
||||
public InitialOrderTest() {
|
||||
System.out.println("构造函数");
|
||||
}
|
||||
```
|
||||
|
||||
存在继承的情况下,初始化顺序为:
|
||||
|
||||
1. 父类(静态变量、静态语句块块)
|
||||
2. 子类(静态变量、静态语句块)
|
||||
3. 父类(实例变量、普通语句块)
|
||||
4. 父类(构造函数)
|
||||
5. 子类(实例变量、普通语句块)
|
||||
6. 子类(构造函数)
|
||||
1. 父类(静态变量、静态语句块块)
|
||||
2. 子类(静态变量、静态语句块)
|
||||
3. 父类(实例变量、普通语句块)
|
||||
4. 父类(构造函数)
|
||||
5. 子类(实例变量、普通语句块)
|
||||
6. 子类(构造函数)
|
||||
|
||||
# Object 通用方法
|
||||
# Object 通用方法
|
||||
|
||||
## 1. 概览
|
||||
## 1. 概览
|
||||
|
||||
```java
|
||||
public final native Class<?> getClass()
|
||||
public final native Class<?> getClass()
|
||||
|
||||
public native int hashCode()
|
||||
public native int hashCode()
|
||||
|
||||
public boolean equals(Object obj)
|
||||
public boolean equals(Object obj)
|
||||
|
||||
protected native Object clone() throws CloneNotSupportedException
|
||||
protected native Object clone() throws CloneNotSupportedException
|
||||
|
||||
public String toString()
|
||||
public String toString()
|
||||
|
||||
public final native void notify()
|
||||
public final native void notify()
|
||||
|
||||
public final native void notifyAll()
|
||||
public final native void notifyAll()
|
||||
|
||||
public final native void wait(long timeout) throws InterruptedException
|
||||
public final native void wait(long timeout) throws InterruptedException
|
||||
|
||||
public final void wait(long timeout, int nanos) throws InterruptedException
|
||||
public final void wait(long timeout, int nanos) throws InterruptedException
|
||||
|
||||
public final void wait() throws InterruptedException
|
||||
public final void wait() throws InterruptedException
|
||||
|
||||
protected void finalize() throws Throwable {}
|
||||
protected void finalize() throws Throwable {}
|
||||
```
|
||||
|
||||
## 2. clone()
|
||||
## 2. clone()
|
||||
|
||||
**浅拷贝**
|
||||
**浅拷贝**
|
||||
|
||||
引用类型引用同一个对象。clone() 方法默认就是浅拷贝实现。
|
||||
引用类型引用同一个对象。clone() 方法默认就是浅拷贝实现。
|
||||
|
||||
![](index_files/d990c0e7-64d1-4ba3-8356-111bc91e53c5.png)
|
||||
<div align="center"> <img src="../pics//d990c0e7-64d1-4ba3-8356-111bc91e53c5.png"/> </div><br>
|
||||
|
||||
**深拷贝**
|
||||
**深拷贝**
|
||||
|
||||
可以使用序列化实现。
|
||||
|
||||
![](index_files/2e5620c4-b558-46fe-8f12-00c9dd597a61.png)
|
||||
<div align="center"> <img src="../pics//2e5620c4-b558-46fe-8f12-00c9dd597a61.png"/> </div><br>
|
||||
|
||||
> [How do I copy an object in Java?](https://stackoverflow.com/questions/869033/how-do-i-copy-an-object-in-java)
|
||||
> [How do I copy an object in Java?](https://stackoverflow.com/questions/869033/how-do-i-copy-an-object-in-java)
|
||||
|
||||
## 3. equals()
|
||||
## 3. equals()
|
||||
|
||||
**== 与 equals() 区别**
|
||||
**== 与 equals() 区别**
|
||||
|
||||
- 对于基本类型,== 就是判断两个值是否相等;
|
||||
- 对于基本类型,== 就是判断两个值是否相等;
|
||||
|
||||
- 对于引用类型,== 是判断两个引用是否引用同一个对象,而 equals() 是判断引用的对象是否等价。
|
||||
- 对于引用类型,== 是判断两个引用是否引用同一个对象,而 equals() 是判断引用的对象是否等价。
|
||||
|
||||
**等价性**
|
||||
**等价性**
|
||||
|
||||
[散列](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Java%20%E5%AE%B9%E5%99%A8.md#%E6%95%A3%E5%88%97)
|
||||
|
||||
# 继承
|
||||
# 继承
|
||||
|
||||
## 1. 访问权限
|
||||
## 1. 访问权限
|
||||
|
||||
Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。
|
||||
Java 中有三个访问权限修饰符:private、protected 以及 public,如果不加访问修饰符,表示包级可见。
|
||||
|
||||
可以对类或类中的成员(字段以及方法)加上访问修饰符。成员可见表示其它类可以用成员所在类的对象访问到该成员;类可见表示其它类可以用这个类创建对象。在理解类的可见性时,可以把类当做包中的一个成员,然后包表示一个类,那么就可以类比成员的可见性。
|
||||
|
||||
protected 用于修饰成员,表示在继承体系中成员对于子类可见。但是这个访问修饰符对于类没有意义,因为包没有继承体系。
|
||||
protected 用于修饰成员,表示在继承体系中成员对于子类可见。但是这个访问修饰符对于类没有意义,因为包没有继承体系。
|
||||
|
||||
更详细的内容:[浅析 Java 中的访问权限控制](http://www.importnew.com/18097.html)
|
||||
更详细的内容:[浅析 Java 中的访问权限控制](http://www.importnew.com/18097.html)
|
||||
|
||||
## 2. 抽象类与接口
|
||||
## 2. 抽象类与接口
|
||||
|
||||
抽象类和抽象方法都使用 abstract 进行声明。抽象类一般会包含抽象方法,但是少数情况下可以不包含,例如 HttpServlet 类,但是抽象方法一定位于抽象类中。抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。
|
||||
抽象类和抽象方法都使用 abstract 进行声明。抽象类一般会包含抽象方法,但是少数情况下可以不包含,例如 HttpServlet 类,但是抽象方法一定位于抽象类中。抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。
|
||||
|
||||
```java
|
||||
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
|
||||
// abstract method
|
||||
abstract void service(ServletRequest req, ServletResponse res);
|
||||
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable {
|
||||
// abstract method
|
||||
abstract void service(ServletRequest req, ServletResponse res);
|
||||
|
||||
void init() {
|
||||
// Its implementation
|
||||
}
|
||||
// other method related to Servlet
|
||||
void init() {
|
||||
// Its implementation
|
||||
}
|
||||
// other method related to Servlet
|
||||
}
|
||||
```
|
||||
|
||||
接口定义了一组方法,但是接口都没有方法的实现,可以理解为这些方法都是抽象方法。
|
||||
|
||||
```java
|
||||
public interface Externalizable extends Serializable {
|
||||
public interface Externalizable extends Serializable {
|
||||
|
||||
void writeExternal(ObjectOutput out) throws IOException;
|
||||
void writeExternal(ObjectOutput out) throws IOException;
|
||||
|
||||
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
|
||||
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
|
||||
}
|
||||
```
|
||||
|
||||
| **参数** | **抽象类** | **接口** |
|
||||
| --- | --- | --- |
|
||||
| 默认的方法实现 | 它可以有默认的方法实现 | 接口完全是抽象的。它根本不存在方法的实现 |
|
||||
| 实现 | 子类使用 extends 关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 | 子类使用关键字 implements 来实现接口。它需要提供接口中所有声明的方法的实现 |
|
||||
| 构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
|
||||
| 与正常 Java 类的区别 | 除了你不能实例化抽象类之外,它和普通 Java 类没有任何区别 | 接口是完全不同的类型 |
|
||||
| 访问修饰符 | 抽象方法可以有 public、protected 和 default 这些修饰符 | 接口方法默认修饰符是**public**。你不可以使用其它修饰符。 |
|
||||
| main 方法 | 抽象方法可以有 main 方法并且我们可以运行它 | 接口没有 main 方法,因此我们不能运行它。 |
|
||||
| 多继承 | 抽象方法可以继承一个类和实现多个接口 | 接口只可以继承一个或多个其它接口 |
|
||||
| 速度 | 它比接口速度要快 | 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。 |
|
||||
| 添加新方法 | 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 | 如果你往接口中添加方法,那么你必须改变实现该接口的类。 |
|
||||
| **参数** | **抽象类** | **接口** |
|
||||
| --- | --- | --- |
|
||||
| 默认的方法实现 | 它可以有默认的方法实现 | 接口完全是抽象的。它根本不存在方法的实现 |
|
||||
| 实现 | 子类使用 extends 关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 | 子类使用关键字 implements 来实现接口。它需要提供接口中所有声明的方法的实现 |
|
||||
| 构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
|
||||
| 与正常 Java 类的区别 | 除了你不能实例化抽象类之外,它和普通 Java 类没有任何区别 | 接口是完全不同的类型 |
|
||||
| 访问修饰符 | 抽象方法可以有 public、protected 和 default 这些修饰符 | 接口方法默认修饰符是 **public** 。你不可以使用其它修饰符。 |
|
||||
| main 方法 | 抽象方法可以有 main 方法并且我们可以运行它 | 接口没有 main 方法,因此我们不能运行它。 |
|
||||
| 多继承 | 抽象方法可以继承一个类和实现多个接口 | 接口只可以继承一个或多个其它接口 |
|
||||
| 速度 | 它比接口速度要快 | 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。 |
|
||||
| 添加新方法 | 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 | 如果你往接口中添加方法,那么你必须改变实现该接口的类。 |
|
||||
|
||||
> [Java 抽象类与接口的区别](http://www.importnew.com/12399.html)
|
||||
> [Java 抽象类与接口的区别](http://www.importnew.com/12399.html)
|
||||
|
||||
## 3. super
|
||||
## 3. super
|
||||
|
||||
**访问父类的成员**
|
||||
**访问父类的成员**
|
||||
|
||||
如果子类覆盖了父类的中某个方法的实现,那么就可以通过使用 super 关键字来引用父类的方法实现。
|
||||
如果子类覆盖了父类的中某个方法的实现,那么就可以通过使用 super 关键字来引用父类的方法实现。
|
||||
|
||||
```java
|
||||
public class Superclass {
|
||||
public void printMethod() {
|
||||
System.out.println("Printed in Superclass.");
|
||||
}
|
||||
public class Superclass {
|
||||
public void printMethod() {
|
||||
System.out.println("Printed in Superclass.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
public class Subclass extends Superclass {
|
||||
// Overrides printMethod in Superclass
|
||||
public void printMethod() {
|
||||
super.printMethod();
|
||||
System.out.println("Printed in Subclass");
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
Subclass s = new Subclass();
|
||||
s.printMethod();
|
||||
}
|
||||
public class Subclass extends Superclass {
|
||||
// Overrides printMethod in Superclass
|
||||
public void printMethod() {
|
||||
super.printMethod();
|
||||
System.out.println("Printed in Subclass");
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
Subclass s = new Subclass();
|
||||
s.printMethod();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**访问父类的构造函数**
|
||||
**访问父类的构造函数**
|
||||
|
||||
可以使用 super() 函数访问父类的构造函数,从而完成一些初始化的工作。
|
||||
可以使用 super() 函数访问父类的构造函数,从而完成一些初始化的工作。
|
||||
|
||||
```java
|
||||
public MountainBike(int startHeight, int startCadence, int startSpeed, int startGear) {
|
||||
super(startCadence, startSpeed, startGear);
|
||||
seatHeight = startHeight;
|
||||
public MountainBike(int startHeight, int startCadence, int startSpeed, int startGear) {
|
||||
super(startCadence, startSpeed, startGear);
|
||||
seatHeight = startHeight;
|
||||
}
|
||||
```
|
||||
|
||||
> [Using the Keyword super](https://docs.oracle.com/javase/tutorial/java/IandI/super.html)
|
||||
> [Using the Keyword super](https://docs.oracle.com/javase/tutorial/java/IandI/super.html)
|
||||
|
||||
## 4. 重载与重写
|
||||
## 4. 重载与重写
|
||||
|
||||
- 重写存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法;
|
||||
- 重写存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法;
|
||||
|
||||
- 重载即存在于继承体系中,也存在于同一个类中,指一个方法与已经存在的方法或者父类的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。应该注意的是,返回值不同,其它都相同不算是重载。
|
||||
- 重载即存在于继承体系中,也存在于同一个类中,指一个方法与已经存在的方法或者父类的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。应该注意的是,返回值不同,其它都相同不算是重载。
|
||||
|
||||
# String
|
||||
# String
|
||||
|
||||
## 1. String, StringBuffer and StringBuilder
|
||||
## 1. String, StringBuffer and StringBuilder
|
||||
|
||||
**是否可变**
|
||||
**是否可变**
|
||||
|
||||
String 不可变,StringBuffer 和 StringBuilder 可变。
|
||||
String 不可变,StringBuffer 和 StringBuilder 可变。
|
||||
|
||||
**是否线程安全**
|
||||
**是否线程安全**
|
||||
|
||||
String 不可变,因此是线程安全的。
|
||||
String 不可变,因此是线程安全的。
|
||||
|
||||
StringBuilder 不是线程安全的;StringBuffer 是线程安全的,使用 synchronized 来同步。
|
||||
StringBuilder 不是线程安全的;StringBuffer 是线程安全的,使用 synchronized 来同步。
|
||||
|
||||
> [String, StringBuffer, and StringBuilder](https://stackoverflow.com/questions/2971315/string-stringbuffer-and-stringbuilder)
|
||||
> [String, StringBuffer, and StringBuilder](https://stackoverflow.com/questions/2971315/string-stringbuffer-and-stringbuilder)
|
||||
|
||||
## 2. String 不可变的原因
|
||||
## 2. String 不可变的原因
|
||||
|
||||
**可以缓存 hash 值**
|
||||
**可以缓存 hash 值**
|
||||
|
||||
因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 等。不可变的特性可以使得 hash 值也不可变,因此就只需要进行一次计算。
|
||||
因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 等。不可变的特性可以使得 hash 值也不可变,因此就只需要进行一次计算。
|
||||
|
||||
**String Pool 的需要**
|
||||
**String Pool 的需要**
|
||||
|
||||
如果 String 已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
|
||||
如果 String 已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
|
||||
|
||||
![](index_files/f76067a5-7d5f-4135-9549-8199c77d8f1c.jpg)
|
||||
<div align="center"> <img src="../pics//f76067a5-7d5f-4135-9549-8199c77d8f1c.jpg"/> </div><br>
|
||||
|
||||
**安全性**
|
||||
**安全性**
|
||||
|
||||
String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。
|
||||
String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。
|
||||
|
||||
**线程安全**
|
||||
**线程安全**
|
||||
|
||||
String 不可变性天生具备线程安全,可以在多个线程中使用。
|
||||
String 不可变性天生具备线程安全,可以在多个线程中使用。
|
||||
|
||||
> [Why String is immutable in Java?](https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/)
|
||||
> [Why String is immutable in Java?](https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/)
|
||||
|
||||
## 3. String.intern()
|
||||
## 3. String.intern()
|
||||
|
||||
使用 String.intern() 可以保证所有相同内容的字符串变量引用相同的内存对象。
|
||||
使用 String.intern() 可以保证所有相同内容的字符串变量引用相同的内存对象。
|
||||
|
||||
更详细的内容:[揭开 String.intern() 那神秘的面纱](https://www.jianshu.com/p/95f516cb75ef)
|
||||
更详细的内容:[揭开 String.intern() 那神秘的面纱](https://www.jianshu.com/p/95f516cb75ef)
|
||||
|
||||
# 基本类型与运算
|
||||
# 基本类型与运算
|
||||
|
||||
## 1. 包装类型
|
||||
## 1. 包装类型
|
||||
|
||||
八个基本类型:boolean 1 byte 8 char 16 short 16 int 32 float 32 long 64 double 64
|
||||
八个基本类型:boolean 1 byte 8 char 16 short 16 int 32 float 32 long 64 double 64
|
||||
|
||||
基本类型都有对应的包装类型,它们之间的赋值使用自动装箱与拆箱完成。
|
||||
|
||||
```java
|
||||
Integer x = 2; // 装箱
|
||||
int y = x; // 拆箱
|
||||
Integer x = 2; // 装箱
|
||||
int y = x; // 拆箱
|
||||
```
|
||||
|
||||
new Integer(123) 与 Integer.valueOf(123) 的区别在于,Integer.valueOf(123) 可能会使用缓存对象,因此多次使用 Integer.valueOf(123) 会取得同一个对象的引用。
|
||||
new Integer(123) 与 Integer.valueOf(123) 的区别在于,Integer.valueOf(123) 可能会使用缓存对象,因此多次使用 Integer.valueOf(123) 会取得同一个对象的引用。
|
||||
|
||||
```java
|
||||
public static void main(String[] args) {
|
||||
Integer a = new Integer(1);
|
||||
Integer b = new Integer(1);
|
||||
System.out.println("a==b? " + (a==b));
|
||||
public static void main(String[] args) {
|
||||
Integer a = new Integer(1);
|
||||
Integer b = new Integer(1);
|
||||
System.out.println("a==b? " + (a==b));
|
||||
|
||||
Integer c = Integer.valueOf(1);
|
||||
Integer d = Integer.valueOf(1);
|
||||
System.out.println("c==d? " + (c==d));
|
||||
}
|
||||
Integer c = Integer.valueOf(1);
|
||||
Integer d = Integer.valueOf(1);
|
||||
System.out.println("c==d? " + (c==d));
|
||||
}
|
||||
```
|
||||
|
||||
```html
|
||||
a==b? false
|
||||
c==d? true
|
||||
a==b? false
|
||||
c==d? true
|
||||
```
|
||||
|
||||
valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接使用缓存池的内容。
|
||||
valueOf() 方法的实现比较简单,就是先判断值是否在缓存池中,如果在的话就直接使用缓存池的内容。
|
||||
|
||||
```java
|
||||
public static Integer valueOf(int i) {
|
||||
final int offset = 128;
|
||||
if (i >= -128 && i <= 127) { // must cache
|
||||
return IntegerCache.cache[i + offset];
|
||||
}
|
||||
return new Integer(i);
|
||||
public static Integer valueOf(int i) {
|
||||
final int offset = 128;
|
||||
if (i >= -128 && i <= 127) { // must cache
|
||||
return IntegerCache.cache[i + offset];
|
||||
}
|
||||
return new Integer(i);
|
||||
}
|
||||
```
|
||||
|
||||
The following is the list of primitives stored as immutable objects:
|
||||
The following is the list of primitives stored as immutable objects:
|
||||
|
||||
- boolean values true and false
|
||||
- all byte values
|
||||
- short values between -128 and 127
|
||||
- int values between -128 and 127
|
||||
- char in the range \u0000 to \u007F
|
||||
- boolean values true and false
|
||||
- all byte values
|
||||
- short values between -128 and 127
|
||||
- int values between -128 and 127
|
||||
- char in the range \u0000 to \u007F
|
||||
|
||||
自动装箱过程编译器会调用 valueOf() 方法,因此多个 Integer 对象使用装箱来创建并且值相同,那么就会引用相同的对象,这样做很显然是为了节省内存开销。
|
||||
自动装箱过程编译器会调用 valueOf() 方法,因此多个 Integer 对象使用装箱来创建并且值相同,那么就会引用相同的对象,这样做很显然是为了节省内存开销。
|
||||
|
||||
```java
|
||||
Integer x = 1;
|
||||
Integer y = 1;
|
||||
System.out.println(c==d); // true
|
||||
Integer x = 1;
|
||||
Integer y = 1;
|
||||
System.out.println(c==d); // true
|
||||
```
|
||||
|
||||
> [Differences between new Integer(123), Integer.valueOf(123) and just 123
|
||||
> [Differences between new Integer(123), Integer.valueOf(123) and just 123
|
||||
](https://stackoverflow.com/questions/9030817/differences-between-new-integer123-integer-valueof123-and-just-123)
|
||||
|
||||
## 2. switch
|
||||
## 2. switch
|
||||
|
||||
A switch works with the byte, short, char, and int primitive data types. It also works with enumerated types and a few special classes that "wrap" certain primitive types: Character, Byte, Short, and Integer.
|
||||
A switch works with the byte, short, char, and int primitive data types. It also works with enumerated types and a few special classes that "wrap" certain primitive types: Character, Byte, Short, and Integer.
|
||||
|
||||
In the JDK 7 release, you can use a String object in the expression of a switch statement.
|
||||
In the JDK 7 release, you can use a String object in the expression of a switch statement.
|
||||
|
||||
switch 不支持 long,是因为 swicth 的设计初衷是为那些只需要对少数几个值进行等值判断,如果值过于复杂,那么还是用 if 比较合适。
|
||||
switch 不支持 long,是因为 swicth 的设计初衷是为那些只需要对少数几个值进行等值判断,如果值过于复杂,那么还是用 if 比较合适。
|
||||
|
||||
> [Why can't your switch statement data type be long, Java?](https://stackoverflow.com/questions/2676210/why-cant-your-switch-statement-data-type-be-long-java)
|
||||
> [Why can't your switch statement data type be long, Java?](https://stackoverflow.com/questions/2676210/why-cant-your-switch-statement-data-type-be-long-java)
|
||||
|
||||
switch 使用查找表的方式来实现,JVM 中使用的指令是 lookupswitch。
|
||||
switch 使用查找表的方式来实现,JVM 中使用的指令是 lookupswitch。
|
||||
|
||||
```java
|
||||
public static void main(String... args) {
|
||||
switch (1) {
|
||||
case 1:
|
||||
break;
|
||||
case 2:
|
||||
break;
|
||||
}
|
||||
public static void main(String... args) {
|
||||
switch (1) {
|
||||
case 1:
|
||||
break;
|
||||
case 2:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(java.lang.String[]);
|
||||
Code:
|
||||
Stack=1, Locals=1, Args_size=1
|
||||
0: iconst_1
|
||||
1: lookupswitch{ //2
|
||||
1: 28;
|
||||
2: 31;
|
||||
default: 31 }
|
||||
28: goto 31
|
||||
31: return
|
||||
public static void main(java.lang.String[]);
|
||||
Code:
|
||||
Stack=1, Locals=1, Args_size=1
|
||||
0: iconst_1
|
||||
1: lookupswitch{ //2
|
||||
1: 28;
|
||||
2: 31;
|
||||
default: 31 }
|
||||
28: goto 31
|
||||
31: return
|
||||
```
|
||||
|
||||
> [How does Java's switch work under the hood?](https://stackoverflow.com/questions/12020048/how-does-javas-switch-work-under-the-hood)
|
||||
> [How does Java's switch work under the hood?](https://stackoverflow.com/questions/12020048/how-does-javas-switch-work-under-the-hood)
|
||||
|
||||
# 反射
|
||||
# 反射
|
||||
|
||||
每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。
|
||||
每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。
|
||||
|
||||
类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 Class.forName('com.mysql.jdbc.Driver.class') 这种方式来控制类的加载,该方法会返回一个 Class 对象。
|
||||
类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 Class.forName('com.mysql.jdbc.Driver.class') 这种方式来控制类的加载,该方法会返回一个 Class 对象。
|
||||
|
||||
反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。
|
||||
反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。
|
||||
|
||||
Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库包含了 **Field**、**Method** 以及 **Constructor** 类。可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段,可以使用 invoke() 方法调用与 Method 对象关联的方法,可以用 Constructor 创建新的对象。
|
||||
Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect 类库包含了 **Field** 、**Method** 以及 **Constructor** 类。可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段,可以使用 invoke() 方法调用与 Method 对象关联的方法,可以用 Constructor 创建新的对象。
|
||||
|
||||
IDE 使用反射机制获取类的信息,在使用一个类的对象时,能够把类的字段、方法和构造函数等信息列出来供用户选择。
|
||||
IDE 使用反射机制获取类的信息,在使用一个类的对象时,能够把类的字段、方法和构造函数等信息列出来供用户选择。
|
||||
|
||||
更详细的内容:[深入解析 Java 反射(1)- 基础](http://www.sczyh30.com/posts/Java/java-reflection-1/)
|
||||
更详细的内容:[深入解析 Java 反射(1)- 基础](http://www.sczyh30.com/posts/Java/java-reflection-1/)
|
||||
|
||||
|
||||
**Advantages of Using Reflection:**
|
||||
**Advantages of Using Reflection:**
|
||||
|
||||
- **Extensibility Features** : An application may make use of external, user-defined classes by creating instances of extensibility objects using their fully-qualified names.
|
||||
- **Class Browsers and Visual Development Environments** : A class browser needs to be able to enumerate the members of classes. Visual development environments can benefit from making use of type information available in reflection to aid the developer in writing correct code.
|
||||
- **Debuggers and Test Tools** : Debuggers need to be able to examine private members on classes. Test harnesses can make use of reflection to systematically call a discoverable set APIs defined on a class, to insure a high level of code coverage in a test suite.
|
||||
- **Extensibility Features** : An application may make use of external, user-defined classes by creating instances of extensibility objects using their fully-qualified names.
|
||||
- **Class Browsers and Visual Development Environments** : A class browser needs to be able to enumerate the members of classes. Visual development environments can benefit from making use of type information available in reflection to aid the developer in writing correct code.
|
||||
- **Debuggers and Test Tools** : Debuggers need to be able to examine private members on classes. Test harnesses can make use of reflection to systematically call a discoverable set APIs defined on a class, to insure a high level of code coverage in a test suite.
|
||||
|
||||
**Drawbacks of Reflection**
|
||||
**Drawbacks of Reflection**
|
||||
|
||||
Reflection is powerful, but should not be used indiscriminately. If it is possible to perform an operation without using reflection, then it is preferable to avoid using it. The following concerns should be kept in mind when accessing code via reflection.
|
||||
Reflection is powerful, but should not be used indiscriminately. If it is possible to perform an operation without using reflection, then it is preferable to avoid using it. The following concerns should be kept in mind when accessing code via reflection.
|
||||
|
||||
- **Performance Overhead** : Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
|
||||
- **Security Restrictions** : Reflection requires a runtime permission which may not be present when running under a security manager. This is in an important consideration for code which has to run in a restricted security context, such as in an Applet.
|
||||
- **Exposure of Internals** :Since reflection allows code to perform operations that would be illegal in non-reflective code, such as accessing private fields and methods, the use of reflection can result in unexpected side-effects, which may render code dysfunctional and may destroy portability. Reflective code breaks abstractions and therefore may change behavior with upgrades of the platform.
|
||||
- **Performance Overhead** : Because reflection involves types that are dynamically resolved, certain Java virtual machine optimizations can not be performed. Consequently, reflective operations have slower performance than their non-reflective counterparts, and should be avoided in sections of code which are called frequently in performance-sensitive applications.
|
||||
- **Security Restrictions** : Reflection requires a runtime permission which may not be present when running under a security manager. This is in an important consideration for code which has to run in a restricted security context, such as in an Applet.
|
||||
- **Exposure of Internals** :Since reflection allows code to perform operations that would be illegal in non-reflective code, such as accessing private fields and methods, the use of reflection can result in unexpected side-effects, which may render code dysfunctional and may destroy portability. Reflective code breaks abstractions and therefore may change behavior with upgrades of the platform.
|
||||
|
||||
> [Trail: The Reflection API](https://docs.oracle.com/javase/tutorial/reflect/index.html)
|
||||
> [Trail: The Reflection API](https://docs.oracle.com/javase/tutorial/reflect/index.html)
|
||||
|
||||
|
||||
# 异常
|
||||
# 异常
|
||||
|
||||
Throwable 可以用来表示任何可以作为异常抛出的类,分为两种:**Error** 和 **Exception**,其中 Error 用来表示编译时系统错误。
|
||||
Throwable 可以用来表示任何可以作为异常抛出的类,分为两种: **Error** 和 **Exception**,其中 Error 用来表示编译时系统错误。
|
||||
|
||||
Exception 分为两种:**受检异常** 和 **非受检异常**。受检异常需要用 try...catch... 语句捕获并进行处理,并且可以从异常中恢复;非受检异常是程序运行时错误,例如除 0 会引发 Arithmetic Exception,此时程序奔溃并且无法恢复。
|
||||
Exception 分为两种: **受检异常** 和 **非受检异常**。受检异常需要用 try...catch... 语句捕获并进行处理,并且可以从异常中恢复;非受检异常是程序运行时错误,例如除 0 会引发 Arithmetic Exception,此时程序奔溃并且无法恢复。
|
||||
|
||||
![](index_files/48f8f98e-8dfd-450d-8b5b-df4688f0d377.jpg)
|
||||
<div align="center"> <img src="../pics//48f8f98e-8dfd-450d-8b5b-df4688f0d377.jpg"/> </div><br>
|
||||
|
||||
更详细的内容:
|
||||
- [Java 入门之异常处理](https://www.tianmaying.com/tutorial/Java-Exception)
|
||||
- [Java 异常的面试问题及答案 -Part 1](http://www.importnew.com/7383.html)
|
||||
- [Java 入门之异常处理](https://www.tianmaying.com/tutorial/Java-Exception)
|
||||
- [Java 异常的面试问题及答案 -Part 1](http://www.importnew.com/7383.html)
|
||||
|
||||
# 泛型
|
||||
# 泛型
|
||||
|
||||
```java
|
||||
public class Box<T> {
|
||||
// T stands for "Type"
|
||||
private T t;
|
||||
public void set(T t) { this.t = t; }
|
||||
public T get() { return t; }
|
||||
public class Box<T> {
|
||||
// T stands for "Type"
|
||||
private T t;
|
||||
public void set(T t) { this.t = t; }
|
||||
public T get() { return t; }
|
||||
}
|
||||
```
|
||||
|
||||
更详细的内容:
|
||||
|
||||
- [Java 泛型详解](https://www.ziwenxie.site/2017/03/01/java-generic/)
|
||||
- [10 道 Java 泛型面试题](https://cloud.tencent.com/developer/article/1033693)
|
||||
- [Java 泛型详解](https://www.ziwenxie.site/2017/03/01/java-generic/)
|
||||
- [10 道 Java 泛型面试题](https://cloud.tencent.com/developer/article/1033693)
|
||||
|
||||
# 注解
|
||||
# 注解
|
||||
|
||||
Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。
|
||||
|
||||
更多详细内容:[注解Annotation实现原理与自定义注解例子](https://www.cnblogs.com/acm-bingzi/p/javaAnnotation.html)
|
||||
|
||||
# 特性
|
||||
# 特性
|
||||
|
||||
## 1. 三大特性
|
||||
## 1. 三大特性
|
||||
|
||||
[封装、继承、多态](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E6%80%9D%E6%83%B3.md#%E5%B0%81%E8%A3%85%E7%BB%A7%E6%89%BF%E5%A4%9A%E6%80%81)
|
||||
|
||||
## 2. Java 各版本的新特性
|
||||
## 2. Java 各版本的新特性
|
||||
|
||||
New highlights in Java SE 8
|
||||
New highlights in Java SE 8
|
||||
|
||||
1. Lambda Expressions
|
||||
2. Pipelines and Streams
|
||||
3. Date and Time API
|
||||
4. Default Methods
|
||||
5. Type Annotations
|
||||
6. Nashhorn JavaScript Engine
|
||||
7. Concurrent Accumulators
|
||||
8. Parallel operations
|
||||
9. PermGen Error Removed
|
||||
1. Lambda Expressions
|
||||
2. Pipelines and Streams
|
||||
3. Date and Time API
|
||||
4. Default Methods
|
||||
5. Type Annotations
|
||||
6. Nashhorn JavaScript Engine
|
||||
7. Concurrent Accumulators
|
||||
8. Parallel operations
|
||||
9. PermGen Error Removed
|
||||
|
||||
New highlights in Java SE 7
|
||||
New highlights in Java SE 7
|
||||
|
||||
1. Strings in Switch Statement
|
||||
2. Type Inference for Generic Instance Creation
|
||||
3. Multiple Exception Handling
|
||||
4. Support for Dynamic Languages
|
||||
5. Try with Resources
|
||||
6. Java nio Package
|
||||
7. Binary Literals, Underscore in literals
|
||||
8. Diamond Syntax
|
||||
1. Strings in Switch Statement
|
||||
2. Type Inference for Generic Instance Creation
|
||||
3. Multiple Exception Handling
|
||||
4. Support for Dynamic Languages
|
||||
5. Try with Resources
|
||||
6. Java nio Package
|
||||
7. Binary Literals, Underscore in literals
|
||||
8. Diamond Syntax
|
||||
|
||||
> [Difference between Java 1.8 and Java 1.7?](http://www.selfgrowth.com/articles/difference-between-java-18-and-java-17)
|
||||
> [Difference between Java 1.8 and Java 1.7?](http://www.selfgrowth.com/articles/difference-between-java-18-and-java-17)
|
||||
|
||||
更详细的内容:[Java 8 特性 ](http://www.importnew.com/19345.html)
|
||||
更详细的内容:[Java 8 特性 ](http://www.importnew.com/19345.html)
|
||||
|
||||
## 3. Java 与 C++ 的区别
|
||||
## 3. Java 与 C++ 的区别
|
||||
|
||||
Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 为了兼容 C 即支持面向对象也支持面向过程。
|
||||
Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 为了兼容 C 即支持面向对象也支持面向过程。
|
||||
|
||||
比较详细的内容:
|
||||
|
||||
| Java | C++ |
|
||||
| -- | -- |
|
||||
| Java does not support pointers, templates, unions, operator overloading, structures etc. The Java language promoters initially said "No pointers!", but when many programmers questioned how you can work without pointers, the promoters began saying "Restricted pointers." Java supports what it calls "references". References act a lot like pointers in C++ languages but you cannot perform arithmetic on pointers in Java. References have types, and they're type-safe. These references cannot be interpreted as raw address and unsafe conversion is not allowed. | C++ supports structures, unions, templates, operator overloading, pointers and pointer arithmetic.|
|
||||
| Java support automatic garbage collection. It does not support destructors as C++ does. | C++ support destructors, which is automatically invoked when the object is destroyed. |
|
||||
| Java does not support conditional compilation and inclusion. | Conditional inclusion (#ifdef #ifndef type) is one of the main features of C++. |
|
||||
| Java has built in support for threads. In Java, there is a `Thread` class that you inherit to create a new thread and override the `run()` method. | C++ has no built in support for threads. C++ relies on non-standard third-party libraries for thread support. |
|
||||
| Java does not support default arguments. There is no scope resolution operator (::) in Java. The method definitions must always occur within a class, so there is no need for scope resolution there either. | C++ supports default arguments. C++ has scope resolution operator (::) which is used to to define a method outside a class and to access a global variable within from the scope where a local variable also exists with the same name. |
|
||||
| There is no _goto_ statement in Java. The keywords `const` and `goto` are reserved, even though they are not used. | C++ has _goto_ statement. However, it is not considered good practice to use of _goto_ statement. |
|
||||
| Java doesn't provide multiple inheritance, at least not in the same sense that C++ does. | C++ does support multiple inheritance. The keyword `virtual` is used to resolve ambiguities during multiple inheritance if there is any. |
|
||||
| Exception handling in Java is different because there are no destructors. Also, in Java, try/catch must be defined if the function declares that it may throw an exception. | While in C++, you may not include the try/catch even if the function throws an exception. |
|
||||
| Java has method overloading, but no operator overloading. The `String` class does use the `+` and `+=` operators to concatenate strings and `String`expressions use automatic type conversion, but that's a special built-in case. | C++ supports both method overloading and operator overloading. |
|
||||
| Java has built-in support for documentation comments (`/** ... */`); therefore, Java source files can contain their own documentation, which is read by a separate tool usually `javadoc` and reformatted into HTML. This helps keeping documentation maintained in easy way. | C++ does not support documentation comments. |
|
||||
| Java is interpreted for the most part and hence platform independent. | C++ generates object code and the same code may not run on different platforms. |
|
||||
| Java | C++ |
|
||||
| -- | -- |
|
||||
| Java does not support pointers, templates, unions, operator overloading, structures etc. The Java language promoters initially said "No pointers!", but when many programmers questioned how you can work without pointers, the promoters began saying "Restricted pointers." Java supports what it calls "references". References act a lot like pointers in C++ languages but you cannot perform arithmetic on pointers in Java. References have types, and they're type-safe. These references cannot be interpreted as raw address and unsafe conversion is not allowed. | C++ supports structures, unions, templates, operator overloading, pointers and pointer arithmetic.|
|
||||
| Java support automatic garbage collection. It does not support destructors as C++ does. | C++ support destructors, which is automatically invoked when the object is destroyed. |
|
||||
| Java does not support conditional compilation and inclusion. | Conditional inclusion (#ifdef #ifndef type) is one of the main features of C++. |
|
||||
| Java has built in support for threads. In Java, there is a `Thread` class that you inherit to create a new thread and override the `run()` method. | C++ has no built in support for threads. C++ relies on non-standard third-party libraries for thread support. |
|
||||
| Java does not support default arguments. There is no scope resolution operator (::) in Java. The method definitions must always occur within a class, so there is no need for scope resolution there either. | C++ supports default arguments. C++ has scope resolution operator (::) which is used to to define a method outside a class and to access a global variable within from the scope where a local variable also exists with the same name. |
|
||||
| There is no _goto_ statement in Java. The keywords `const` and `goto` are reserved, even though they are not used. | C++ has _goto_ statement. However, it is not considered good practice to use of _goto_ statement. |
|
||||
| Java doesn't provide multiple inheritance, at least not in the same sense that C++ does. | C++ does support multiple inheritance. The keyword `virtual` is used to resolve ambiguities during multiple inheritance if there is any. |
|
||||
| Exception handling in Java is different because there are no destructors. Also, in Java, try/catch must be defined if the function declares that it may throw an exception. | While in C++, you may not include the try/catch even if the function throws an exception. |
|
||||
| Java has method overloading, but no operator overloading. The `String` class does use the `+` and `+=` operators to concatenate strings and `String`expressions use automatic type conversion, but that's a special built-in case. | C++ supports both method overloading and operator overloading. |
|
||||
| Java has built-in support for documentation comments (`/** ... */`); therefore, Java source files can contain their own documentation, which is read by a separate tool usually `javadoc` and reformatted into HTML. This helps keeping documentation maintained in easy way. | C++ does not support documentation comments. |
|
||||
| Java is interpreted for the most part and hence platform independent. | C++ generates object code and the same code may not run on different platforms. |
|
||||
|
||||
> [What are the main differences between Java and C++?](http://cs-fundamentals.com/tech-interview/java/differences-between-java-and-cpp.php)
|
||||
> [What are the main differences between Java and C++?](http://cs-fundamentals.com/tech-interview/java/differences-between-java-and-cpp.php)
|
||||
|
||||
## 4. JRE or JDK
|
||||
## 4. JRE or JDK
|
||||
|
||||
- JRE is the JVM program, Java application need to run on JRE.
|
||||
- JDK is a superset of JRE, JRE + tools for developing java programs. e.g, it provides the compiler "javac"
|
||||
- JRE is the JVM program, Java application need to run on JRE.
|
||||
- JDK is a superset of JRE, JRE + tools for developing java programs. e.g, it provides the compiler "javac"
|
||||
|
|
484
notes/Java 容器.md
484
notes/Java 容器.md
|
@ -1,399 +1,429 @@
|
|||
[TOC]
|
||||
<!-- GFM-TOC -->
|
||||
* [概览](#概览)
|
||||
* [1. List](#1-list)
|
||||
* [2. Set](#2-set)
|
||||
* [3. Queue](#3-queue)
|
||||
* [4. Map](#4-map)
|
||||
* [5. Java 1.0/1.1 容器](#5-java-1011-容器)
|
||||
* [容器中的设计模式](#容器中的设计模式)
|
||||
* [1. 迭代器模式](#1-迭代器模式)
|
||||
* [2. 适配器模式](#2-适配器模式)
|
||||
* [散列](#散列)
|
||||
* [源码分析](#源码分析)
|
||||
* [1. ArrayList](#1-arraylist)
|
||||
* [概览](#概览)
|
||||
* [Fail-Fast](#fail-fast)
|
||||
* [和 Vector 的区别](#和-vector-的区别)
|
||||
* [和 LinkedList 的区别](#和-linkedlist-的区别)
|
||||
* [2. Vector 与 Stack](#2-vector-与-stack)
|
||||
* [3. LinkedList](#3-linkedlist)
|
||||
* [4. TreeMap](#4-treemap)
|
||||
* [5. HashMap](#5-hashmap)
|
||||
* [基本数据结构](#基本数据结构)
|
||||
* [拉链法的工作原理](#拉链法的工作原理)
|
||||
* [扩容](#扩容)
|
||||
* [null 值](#null-值)
|
||||
* [与 HashTable 的区别](#与-hashtable-的区别)
|
||||
* [6. LinkedHashMap](#6-linkedhashmap)
|
||||
* [7. ConcurrentHashMap](#7-concurrenthashmap)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
# 概览
|
||||
|
||||
![](index_files/ebf03f56-f957-4435-9f8f-0f605661484d.jpg)
|
||||
# 概览
|
||||
|
||||
容器主要包括 Collection 和 Map 两种,Collection 又包含了 List、Set 以及 Queue。
|
||||
<div align="center"> <img src="../pics//ebf03f56-f957-4435-9f8f-0f605661484d.jpg"/> </div><br>
|
||||
|
||||
## 1. List
|
||||
容器主要包括 Collection 和 Map 两种,Collection 又包含了 List、Set 以及 Queue。
|
||||
|
||||
- ArrayList:基于动态数组实现,支持随机访问;
|
||||
## 1. List
|
||||
|
||||
- LinkedList:基于双向循环链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双端队列。
|
||||
- ArrayList:基于动态数组实现,支持随机访问;
|
||||
|
||||
## 2. Set
|
||||
- LinkedList:基于双向循环链表实现,只能顺序访问,但是可以快速地在链表中间插入和删除元素。不仅如此,LinkedList 还可以用作栈、队列和双端队列。
|
||||
|
||||
- HashSet:基于 Hash 实现,支持快速查找,但是失去有序性;
|
||||
## 2. Set
|
||||
|
||||
- TreeSet:基于红黑树实现,保持有序,但是查找效率不如 HashSet;
|
||||
- HashSet:基于 Hash 实现,支持快速查找,但是失去有序性;
|
||||
|
||||
- LinkedHashSet:具有 HashSet 的查找效率,且内部使用链表维护元素的插入顺序,因此具有有序性。
|
||||
- TreeSet:基于红黑树实现,保持有序,但是查找效率不如 HashSet;
|
||||
|
||||
## 3. Queue
|
||||
- LinkedHashSet:具有 HashSet 的查找效率,且内部使用链表维护元素的插入顺序,因此具有有序性。
|
||||
|
||||
只有两个实现:LinkedList 和 PriorityQueue,其中 LinkedList 支持双向队列,PriorityQueue 是基于堆结构实现。
|
||||
## 3. Queue
|
||||
|
||||
## 4. Map
|
||||
只有两个实现:LinkedList 和 PriorityQueue,其中 LinkedList 支持双向队列,PriorityQueue 是基于堆结构实现。
|
||||
|
||||
- HashMap:基于 Hash 实现。
|
||||
## 4. Map
|
||||
|
||||
- LinkedHashMap:使用链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。
|
||||
- HashMap:基于 Hash 实现。
|
||||
|
||||
- TreeMap:基于红黑树实现。
|
||||
- LinkedHashMap:使用链表来维护元素的顺序,顺序为插入顺序或者最近最少使用(LRU)顺序。
|
||||
|
||||
- ConcurrentHashMap:线程安全 Map,不涉及类似于 HashTable 的同步加锁。
|
||||
- TreeMap:基于红黑树实现。
|
||||
|
||||
## 5. Java 1.0/1.1 容器
|
||||
- ConcurrentHashMap:线程安全 Map,不涉及类似于 HashTable 的同步加锁。
|
||||
|
||||
## 5. Java 1.0/1.1 容器
|
||||
|
||||
对于旧的容器,我们决不应该使用它们,只需要对它们进行了解。
|
||||
|
||||
- Vector:和 ArrayList 类似,但它是线程安全的。
|
||||
- Vector:和 ArrayList 类似,但它是线程安全的。
|
||||
|
||||
- HashTable:和 HashMap 类似,但它是线程安全的。
|
||||
- HashTable:和 HashMap 类似,但它是线程安全的。
|
||||
|
||||
# 容器中的设计模式
|
||||
# 容器中的设计模式
|
||||
|
||||
## 1. 迭代器模式
|
||||
## 1. 迭代器模式
|
||||
|
||||
从概览图可以看到,每个集合类都有一个 Iterator 对象,可以通过这个迭代器对象来遍历集合中的元素。
|
||||
从概览图可以看到,每个集合类都有一个 Iterator 对象,可以通过这个迭代器对象来遍历集合中的元素。
|
||||
|
||||
[Java 中的迭代器模式](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md#1-%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F)
|
||||
[Java 中的迭代器模式](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md#1-%E8%BF%AD%E4%BB%A3%E5%99%A8%E6%A8%A1%E5%BC%8F)
|
||||
|
||||
## 2. 适配器模式
|
||||
## 2. 适配器模式
|
||||
|
||||
java.util.Arrays#asList() 可以把数组类型转换为 List 类型。
|
||||
java.util.Arrays#asList() 可以把数组类型转换为 List 类型。
|
||||
|
||||
```java
|
||||
List list = Arrays.asList(1, 2, 3);
|
||||
int[] arr = {1, 2, 3};
|
||||
list = Arrays.asList(arr);
|
||||
List list = Arrays.asList(1, 2, 3);
|
||||
int[] arr = {1, 2, 3};
|
||||
list = Arrays.asList(arr);
|
||||
```
|
||||
|
||||
# 散列
|
||||
# 散列
|
||||
|
||||
使用 hasCode() 来返回散列值,使用的是对象的地址。
|
||||
使用 hasCode() 来返回散列值,使用的是对象的地址。
|
||||
|
||||
而 equals() 是用来判断两个对象是否相等的,相等的两个对象散列值一定要相同,但是散列值相同的两个对象不一定相等。
|
||||
而 equals() 是用来判断两个对象是否相等的,相等的两个对象散列值一定要相同,但是散列值相同的两个对象不一定相等。
|
||||
|
||||
相等必须满足以下五个性质:
|
||||
|
||||
1. 自反性
|
||||
2. 对称性
|
||||
3. 传递性
|
||||
4. 一致性(多次调用 x.equals(y),结果不变)
|
||||
5. 对任何不是 null 的对象 x 调用 x.equals(nul) 结果都为 false
|
||||
1. 自反性
|
||||
2. 对称性
|
||||
3. 传递性
|
||||
4. 一致性(多次调用 x.equals(y),结果不变)
|
||||
5. 对任何不是 null 的对象 x 调用 x.equals(nul) 结果都为 false
|
||||
|
||||
# 源码分析
|
||||
# 源码分析
|
||||
|
||||
建议先阅读 [算法 - 查找](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E7%AE%97%E6%B3%95.md#%E6%9F%A5%E6%89%BE) 部分,对集合类源码的理解有很大帮助。
|
||||
建议先阅读 [算法 - 查找](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E7%AE%97%E6%B3%95.md#%E6%9F%A5%E6%89%BE) 部分,对集合类源码的理解有很大帮助。
|
||||
|
||||
源码下载:[OpenJDK 1.7](http://download.java.net/openjdk/jdk7)
|
||||
源码下载:[OpenJDK 1.7](http://download.java.net/openjdk/jdk7)
|
||||
|
||||
## 1. ArrayList
|
||||
## 1. ArrayList
|
||||
|
||||
[ArraList.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/ArrayList.java)
|
||||
|
||||
### 概览
|
||||
### 概览
|
||||
|
||||
实现了 RandomAccess 接口,因此支持随机访问,这是理所当然的,因为 ArrayList 是基于数组实现的。
|
||||
实现了 RandomAccess 接口,因此支持随机访问,这是理所当然的,因为 ArrayList 是基于数组实现的。
|
||||
|
||||
```java
|
||||
public class ArrayList<E> extends AbstractList<E>
|
||||
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
|
||||
public class ArrayList<E> extends AbstractList<E>
|
||||
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
|
||||
```
|
||||
|
||||
基于数组实现,保存元素的数组使用 transient 修饰,该关键字声明该数组默认不会被序列化。这是因为该数组不是所有位置都占满元素,因此也就没必要全部都进行序列化。ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那么部分内容。
|
||||
基于数组实现,保存元素的数组使用 transient 修饰,该关键字声明该数组默认不会被序列化。这是因为该数组不是所有位置都占满元素,因此也就没必要全部都进行序列化。ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那么部分内容。
|
||||
|
||||
```java
|
||||
private transient Object[] elementData;
|
||||
private transient Object[] elementData;
|
||||
```
|
||||
|
||||
数组的默认大小为 10。
|
||||
数组的默认大小为 10。
|
||||
|
||||
```java
|
||||
public ArrayList(int initialCapacity) {
|
||||
super();
|
||||
if (initialCapacity < 0)
|
||||
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
|
||||
this.elementData = new Object[initialCapacity];
|
||||
public ArrayList(int initialCapacity) {
|
||||
super();
|
||||
if (initialCapacity < 0)
|
||||
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
|
||||
this.elementData = new Object[initialCapacity];
|
||||
}
|
||||
|
||||
public ArrayList() {
|
||||
this(10);
|
||||
public ArrayList() {
|
||||
this(10);
|
||||
}
|
||||
```
|
||||
|
||||
删除元素时调用 System.arraycopy() 对元素进行复制,因此删除操作成本很高。
|
||||
删除元素时调用 System.arraycopy() 对元素进行复制,因此删除操作成本很高。
|
||||
|
||||
```java
|
||||
public E remove(int index) {
|
||||
rangeCheck(index);
|
||||
public E remove(int index) {
|
||||
rangeCheck(index);
|
||||
|
||||
modCount++;
|
||||
E oldValue = elementData(index);
|
||||
modCount++;
|
||||
E oldValue = elementData(index);
|
||||
|
||||
int numMoved = size - index - 1;
|
||||
if (numMoved > 0)
|
||||
System.arraycopy(elementData, index+1, elementData, index, numMoved);
|
||||
elementData[--size] = null; // Let gc do its work
|
||||
int numMoved = size - index - 1;
|
||||
if (numMoved > 0)
|
||||
System.arraycopy(elementData, index+1, elementData, index, numMoved);
|
||||
elementData[--size] = null; // Let gc do its work
|
||||
|
||||
return oldValue;
|
||||
return oldValue;
|
||||
}
|
||||
```
|
||||
|
||||
添加元素时使用 ensureCapacity() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,使得新容量为旧容量的 1.5 倍。扩容操作需要把原数组整个复制到新数组中,因此最好在创建 ArrayList 时就指定大概的容量大小,减少扩容操作的次数。
|
||||
添加元素时使用 ensureCapacity() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,使得新容量为旧容量的 1.5 倍。扩容操作需要把原数组整个复制到新数组中,因此最好在创建 ArrayList 时就指定大概的容量大小,减少扩容操作的次数。
|
||||
|
||||
```java
|
||||
private void ensureCapacityInternal(int minCapacity) {
|
||||
modCount++;
|
||||
// overflow-conscious code
|
||||
if (minCapacity - elementData.length > 0)
|
||||
grow(minCapacity);
|
||||
private void ensureCapacityInternal(int minCapacity) {
|
||||
modCount++;
|
||||
// overflow-conscious code
|
||||
if (minCapacity - elementData.length > 0)
|
||||
grow(minCapacity);
|
||||
}
|
||||
|
||||
private void grow(int minCapacity) {
|
||||
// overflow-conscious code
|
||||
int oldCapacity = elementData.length;
|
||||
int newCapacity = oldCapacity + (oldCapacity >> 1);
|
||||
if (newCapacity - minCapacity < 0)
|
||||
newCapacity = minCapacity;
|
||||
if (newCapacity - MAX_ARRAY_SIZE > 0)
|
||||
newCapacity = hugeCapacity(minCapacity);
|
||||
// minCapacity is usually close to size, so this is a win:
|
||||
elementData = Arrays.copyOf(elementData, newCapacity);
|
||||
private void grow(int minCapacity) {
|
||||
// overflow-conscious code
|
||||
int oldCapacity = elementData.length;
|
||||
int newCapacity = oldCapacity + (oldCapacity >> 1);
|
||||
if (newCapacity - minCapacity < 0)
|
||||
newCapacity = minCapacity;
|
||||
if (newCapacity - MAX_ARRAY_SIZE > 0)
|
||||
newCapacity = hugeCapacity(minCapacity);
|
||||
// minCapacity is usually close to size, so this is a win:
|
||||
elementData = Arrays.copyOf(elementData, newCapacity);
|
||||
}
|
||||
|
||||
private static int hugeCapacity(int minCapacity) {
|
||||
if (minCapacity < 0) // overflow
|
||||
throw new OutOfMemoryError();
|
||||
return (minCapacity > MAX_ARRAY_SIZE) ?
|
||||
Integer.MAX_VALUE :
|
||||
MAX_ARRAY_SIZE;
|
||||
private static int hugeCapacity(int minCapacity) {
|
||||
if (minCapacity < 0) // overflow
|
||||
throw new OutOfMemoryError();
|
||||
return (minCapacity > MAX_ARRAY_SIZE) ?
|
||||
Integer.MAX_VALUE :
|
||||
MAX_ARRAY_SIZE;
|
||||
}
|
||||
```
|
||||
|
||||
### Fail-Fast
|
||||
### Fail-Fast
|
||||
|
||||
modCount 用来记录 ArrayList 结构发生变化的次数,结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。
|
||||
modCount 用来记录 ArrayList 结构发生变化的次数,结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。
|
||||
|
||||
在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。
|
||||
在进行序列化或者迭代等操作时,需要比较操作前后 modCount 是否改变,如果改变了需要抛出 ConcurrentModificationException。
|
||||
|
||||
```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();
|
||||
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
|
||||
// Write out element count, and any hidden stuff
|
||||
int expectedModCount = modCount;
|
||||
s.defaultWriteObject();
|
||||
|
||||
// Write out array length
|
||||
s.writeInt(elementData.length);
|
||||
// Write out array length
|
||||
s.writeInt(elementData.length);
|
||||
|
||||
// Write out all elements in the proper order.
|
||||
for (int i = 0; i < size; i++)
|
||||
s.writeObject(elementData[i]);
|
||||
// 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();
|
||||
}
|
||||
if (modCount != expectedModCount) {
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 和 Vector 的区别
|
||||
### 和 Vector 的区别
|
||||
|
||||
1. Vector 和 ArrayList 几乎是完全相同的,唯一的区别在于 Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制;
|
||||
2. Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。
|
||||
1. Vector 和 ArrayList 几乎是完全相同的,唯一的区别在于 Vector 是同步的,因此开销就比 ArrayList 要大,访问速度更慢。最好使用 ArrayList 而不是 Vector,因为同步操作完全可以由程序员自己来控制;
|
||||
2. Vector 每次扩容请求其大小的 2 倍空间,而 ArrayList 是 1.5 倍。
|
||||
|
||||
为了获得线程安全的 ArrayList,可以调用 Collections.synchronizedList(new ArrayList<>()); 返回一个线程安全的 ArrayList,也可以使用 concurrent 并发包下的 CopyOnWriteArrayList 类;
|
||||
为了获得线程安全的 ArrayList,可以调用 Collections.synchronizedList(new ArrayList<>()); 返回一个线程安全的 ArrayList,也可以使用 concurrent 并发包下的 CopyOnWriteArrayList 类;
|
||||
|
||||
### 和 LinkedList 的区别
|
||||
### 和 LinkedList 的区别
|
||||
|
||||
1. ArrayList 基于动态数组实现,LinkedList 基于双向循环链表实现;
|
||||
2. ArrayList 支持随机访问,LinkedList 不支持;
|
||||
3. LinkedList 在任意位置添加删除元素更快。
|
||||
1. ArrayList 基于动态数组实现,LinkedList 基于双向循环链表实现;
|
||||
2. ArrayList 支持随机访问,LinkedList 不支持;
|
||||
3. LinkedList 在任意位置添加删除元素更快。
|
||||
|
||||
## 2. Vector 与 Stack
|
||||
## 2. Vector 与 Stack
|
||||
|
||||
[Vector.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/Vector.java)
|
||||
|
||||
## 3. LinkedList
|
||||
## 3. LinkedList
|
||||
|
||||
[LinkedList.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/LinkedList.java)
|
||||
|
||||
## 4. TreeMap
|
||||
## 4. TreeMap
|
||||
|
||||
[TreeMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/TreeMap.java)
|
||||
|
||||
## 5. HashMap
|
||||
## 5. HashMap
|
||||
|
||||
[HashMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/HashMap.java)
|
||||
|
||||
### 基本数据结构
|
||||
### 基本数据结构
|
||||
|
||||
使用拉链法来解决冲突,内部包含了一个 Entry 类型的数组 table,数组中的每个位置被当成一个桶。
|
||||
使用拉链法来解决冲突,内部包含了一个 Entry 类型的数组 table,数组中的每个位置被当成一个桶。
|
||||
|
||||
```java
|
||||
transient Entry[] table;
|
||||
transient Entry[] table;
|
||||
```
|
||||
|
||||
其中,Entry 就是存储数据的键值对,它包含了四个字段。从 next 字段我们可以看出 Entry 是一个链表,即每个桶会存放一个链表。
|
||||
其中,Entry 就是存储数据的键值对,它包含了四个字段。从 next 字段我们可以看出 Entry 是一个链表,即每个桶会存放一个链表。
|
||||
|
||||
![](index_files/ce039f03-6588-4f0c-b35b-a494de0eac47.png)
|
||||
<div align="center"> <img src="../pics//ce039f03-6588-4f0c-b35b-a494de0eac47.png"/> </div><br>
|
||||
|
||||
### 拉链法的工作原理
|
||||
### 拉链法的工作原理
|
||||
|
||||
使用默认构造函数新建一个 HashMap,默认大小为 16。Entry 的类型为 <String, Integer>。先后插入三个元素:("sachin", 30), ("vishal", 20) 和 ("vaibhav", 20)。计算 "sachin" 的 hashcode 为 115,使用除留余数法得到 115 % 16 = 3,因此 ("sachin", 30) 键值对放到第 3 个桶上。同样得到 ("vishal", 20) 和 ("vaibhav", 20) 都应该放到第 6 个桶上,因此需要把 ("vaibhav", 20) 链接到 ("vishal", 20) 之后。
|
||||
使用默认构造函数新建一个 HashMap,默认大小为 16。Entry 的类型为 <String, Integer>。先后插入三个元素:("sachin", 30), ("vishal", 20) 和 ("vaibhav", 20)。计算 "sachin" 的 hashcode 为 115,使用除留余数法得到 115 % 16 = 3,因此 ("sachin", 30) 键值对放到第 3 个桶上。同样得到 ("vishal", 20) 和 ("vaibhav", 20) 都应该放到第 6 个桶上,因此需要把 ("vaibhav", 20) 链接到 ("vishal", 20) 之后。
|
||||
|
||||
![](index_files/b9a39d2a-618c-468b-86db-2e851f1a0057.jpg)
|
||||
<div align="center"> <img src="../pics//b9a39d2a-618c-468b-86db-2e851f1a0057.jpg"/> </div><br>
|
||||
|
||||
当进行查找时,需要分成两步进行,第一步是先根据 hashcode 计算出所在的桶,第二步是在链表上顺序查找。由于 table 是数组形式的,具有随机读取的特性,因此这一步的时间复杂度为 O(1),而第二步需要在链表上顺序查找,时间复杂度显然和链表的长度成正比。
|
||||
当进行查找时,需要分成两步进行,第一步是先根据 hashcode 计算出所在的桶,第二步是在链表上顺序查找。由于 table 是数组形式的,具有随机读取的特性,因此这一步的时间复杂度为 O(1),而第二步需要在链表上顺序查找,时间复杂度显然和链表的长度成正比。
|
||||
|
||||
### 扩容
|
||||
### 扩容
|
||||
|
||||
设 HashMap 的 table 长度为 M,需要存储的键值对数量为 N,如果哈希函数满足均匀性的要求,那么每条链表的长度大约为 N/M,因此平均查找次数的数量级为 O(N/M)。
|
||||
设 HashMap 的 table 长度为 M,需要存储的键值对数量为 N,如果哈希函数满足均匀性的要求,那么每条链表的长度大约为 N/M,因此平均查找次数的数量级为 O(N/M)。
|
||||
|
||||
为了让查找的成本降低,应该尽可能使得 N/M 尽可能小,因此需要保证 M 尽可能大,可就是说 table 要尽可能大。HashMap 采用动态扩容来根据当前的 N 值来调整 M 值,使得空间效率和时间效率都能得到保证。
|
||||
为了让查找的成本降低,应该尽可能使得 N/M 尽可能小,因此需要保证 M 尽可能大,可就是说 table 要尽可能大。HashMap 采用动态扩容来根据当前的 N 值来调整 M 值,使得空间效率和时间效率都能得到保证。
|
||||
|
||||
和扩容相关的参数主要有:capacity、size、threshold 和 load_factor。
|
||||
和扩容相关的参数主要有:capacity、size、threshold 和 load_factor。
|
||||
|
||||
capacity 表示 table 的容量大小,默认为 16,需要注意的是容量必须保证为 2 的次方。容量就是 table 数组的长度,size 是数组的实际使用量。
|
||||
capacity 表示 table 的容量大小,默认为 16,需要注意的是容量必须保证为 2 的次方。容量就是 table 数组的长度,size 是数组的实际使用量。
|
||||
|
||||
threshold 规定了一个 size 的临界值,size 必须小于 threshold,如果大于等于,就必须进行扩容操作。
|
||||
threshold 规定了一个 size 的临界值,size 必须小于 threshold,如果大于等于,就必须进行扩容操作。
|
||||
|
||||
threshold = capacity * load_factor,其中 load_factor 为 table 数组能够使用的比例。
|
||||
threshold = capacity * load_factor,其中 load_factor 为 table 数组能够使用的比例。
|
||||
|
||||
```java
|
||||
static final int DEFAULT_INITIAL_CAPACITY = 16;
|
||||
static final int DEFAULT_INITIAL_CAPACITY = 16;
|
||||
|
||||
static final int MAXIMUM_CAPACITY = 1 << 30;
|
||||
static final int MAXIMUM_CAPACITY = 1 << 30;
|
||||
|
||||
static final float DEFAULT_LOAD_FACTOR = 0.75f;
|
||||
static final float DEFAULT_LOAD_FACTOR = 0.75f;
|
||||
|
||||
transient Entry[] table;
|
||||
transient Entry[] table;
|
||||
|
||||
transient int size;
|
||||
transient int size;
|
||||
|
||||
int threshold;
|
||||
int threshold;
|
||||
|
||||
final float loadFactor;
|
||||
final float loadFactor;
|
||||
|
||||
transient int modCount;
|
||||
transient int modCount;
|
||||
```
|
||||
|
||||
从下面的添加元素代码中可以看出,当需要扩容时,令 capacity 为原来的两倍。
|
||||
从下面的添加元素代码中可以看出,当需要扩容时,令 capacity 为原来的两倍。
|
||||
|
||||
```java
|
||||
void addEntry(int hash, K key, V value, int bucketIndex) {
|
||||
Entry<K,V> e = table[bucketIndex];
|
||||
table[bucketIndex] = new Entry<>(hash, key, value, e);
|
||||
if (size++ >= threshold)
|
||||
resize(2 * table.length);
|
||||
void addEntry(int hash, K key, V value, int bucketIndex) {
|
||||
Entry<K,V> e = table[bucketIndex];
|
||||
table[bucketIndex] = new Entry<>(hash, key, value, e);
|
||||
if (size++ >= threshold)
|
||||
resize(2 * table.length);
|
||||
}
|
||||
```
|
||||
|
||||
扩容使用 resize() 实现,需要注意的是,扩容操作同样需要把旧 table 的所有键值对重新插入新的 table 中,因此这一步是很费时的。但是从均摊分析的角度来考虑,HashMap 的查找速度依然在常数级别。
|
||||
扩容使用 resize() 实现,需要注意的是,扩容操作同样需要把旧 table 的所有键值对重新插入新的 table 中,因此这一步是很费时的。但是从均摊分析的角度来考虑,HashMap 的查找速度依然在常数级别。
|
||||
|
||||
```java
|
||||
void resize(int newCapacity) {
|
||||
Entry[] oldTable = table;
|
||||
int oldCapacity = oldTable.length;
|
||||
if (oldCapacity == MAXIMUM_CAPACITY) {
|
||||
threshold = Integer.MAX_VALUE;
|
||||
return;
|
||||
}
|
||||
void resize(int newCapacity) {
|
||||
Entry[] oldTable = table;
|
||||
int oldCapacity = oldTable.length;
|
||||
if (oldCapacity == MAXIMUM_CAPACITY) {
|
||||
threshold = Integer.MAX_VALUE;
|
||||
return;
|
||||
}
|
||||
|
||||
Entry[] newTable = new Entry[newCapacity];
|
||||
transfer(newTable);
|
||||
table = newTable;
|
||||
threshold = (int)(newCapacity * loadFactor);
|
||||
Entry[] newTable = new Entry[newCapacity];
|
||||
transfer(newTable);
|
||||
table = newTable;
|
||||
threshold = (int)(newCapacity * loadFactor);
|
||||
}
|
||||
|
||||
void transfer(Entry[] newTable) {
|
||||
Entry[] src = table;
|
||||
int newCapacity = newTable.length;
|
||||
for (int j = 0; j < src.length; j++) {
|
||||
Entry<K,V> e = src[j];
|
||||
if (e != null) {
|
||||
src[j] = null;
|
||||
do {
|
||||
Entry<K,V> next = e.next;
|
||||
int i = indexFor(e.hash, newCapacity);
|
||||
e.next = newTable[i];
|
||||
newTable[i] = e;
|
||||
e = next;
|
||||
} while (e != null);
|
||||
}
|
||||
}
|
||||
void transfer(Entry[] newTable) {
|
||||
Entry[] src = table;
|
||||
int newCapacity = newTable.length;
|
||||
for (int j = 0; j < src.length; j++) {
|
||||
Entry<K,V> e = src[j];
|
||||
if (e != null) {
|
||||
src[j] = null;
|
||||
do {
|
||||
Entry<K,V> next = e.next;
|
||||
int i = indexFor(e.hash, newCapacity);
|
||||
e.next = newTable[i];
|
||||
newTable[i] = e;
|
||||
e = next;
|
||||
} while (e != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### null 值
|
||||
### null 值
|
||||
|
||||
get() 操作需要分成两种情况,key 为 null 和不为 null,从中可以看出 HashMap 允许插入 null 作为键。
|
||||
get() 操作需要分成两种情况,key 为 null 和不为 null,从中可以看出 HashMap 允许插入 null 作为键。
|
||||
|
||||
```java
|
||||
public V get(Object key) {
|
||||
if (key == null)
|
||||
return getForNullKey();
|
||||
int hash = hash(key.hashCode());
|
||||
for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
|
||||
Object k;
|
||||
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
|
||||
return e.value;
|
||||
}
|
||||
return null;
|
||||
public V get(Object key) {
|
||||
if (key == null)
|
||||
return getForNullKey();
|
||||
int hash = hash(key.hashCode());
|
||||
for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
|
||||
Object k;
|
||||
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
|
||||
return e.value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
put() 操作也需要根据 key 是否为 null 做不同的处理,需要注意的是如果本来没有 key 为 null 的键值对,新插入一个 key 为 null 的键值对时默认是放在数组的 0 位置,这是因为 null 不能计算 hash 值,也就无法知道应该放在哪个链表上。
|
||||
put() 操作也需要根据 key 是否为 null 做不同的处理,需要注意的是如果本来没有 key 为 null 的键值对,新插入一个 key 为 null 的键值对时默认是放在数组的 0 位置,这是因为 null 不能计算 hash 值,也就无法知道应该放在哪个链表上。
|
||||
|
||||
```java
|
||||
public V put(K key, V value) {
|
||||
if (key == null)
|
||||
return putForNullKey(value);
|
||||
int hash = hash(key.hashCode());
|
||||
int i = indexFor(hash, table.length);
|
||||
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
|
||||
Object k;
|
||||
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
|
||||
V oldValue = e.value;
|
||||
e.value = value;
|
||||
e.recordAccess(this);
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
public V put(K key, V value) {
|
||||
if (key == null)
|
||||
return putForNullKey(value);
|
||||
int hash = hash(key.hashCode());
|
||||
int i = indexFor(hash, table.length);
|
||||
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
|
||||
Object k;
|
||||
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
|
||||
V oldValue = e.value;
|
||||
e.value = value;
|
||||
e.recordAccess(this);
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
modCount++;
|
||||
addEntry(hash, key, value, i);
|
||||
return null;
|
||||
modCount++;
|
||||
addEntry(hash, key, value, i);
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
private V putForNullKey(V value) {
|
||||
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
|
||||
if (e.key == null) {
|
||||
V oldValue = e.value;
|
||||
e.value = value;
|
||||
e.recordAccess(this);
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
modCount++;
|
||||
addEntry(0, null, value, 0);
|
||||
return null;
|
||||
private V putForNullKey(V value) {
|
||||
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
|
||||
if (e.key == null) {
|
||||
V oldValue = e.value;
|
||||
e.value = value;
|
||||
e.recordAccess(this);
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
modCount++;
|
||||
addEntry(0, null, value, 0);
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
### 与 HashTable 的区别
|
||||
### 与 HashTable 的区别
|
||||
|
||||
- HashMap 几乎可以等价于 Hashtable,除了 HashMap 是非 synchronized 的,并可以接受 null(HashMap 可以接受为 null 的键值 (key) 和值 (value),而 Hashtable 则不行)。
|
||||
- HashMap 是非 synchronized,而 Hashtable 是 synchronized,这意味着 Hashtable 是线程安全的,多个线程可以共享一个 Hashtable;而如果没有正确的同步的话,多个线程是不能共享 HashMap 的。Java 5 提供了 ConcurrentHashMap,它是 HashTable 的替代,比 HashTable 的扩展性更好。
|
||||
- 另一个区别是 HashMap 的迭代器 (Iterator) 是 fail-fast 迭代器,而 Hashtable 的 enumerator 迭代器不是 fail-fast 的。所以当有其它线程改变了 HashMap 的结构(增加或者移除元素),将会抛出 ConcurrentModificationException,但迭代器本身的 remove() 方法移除元素则不会抛出 ConcurrentModificationException 异常。但这并不是一个一定发生的行为,要看 JVM。这条同样也是 Enumeration 和 Iterator 的区别。
|
||||
- 由于 Hashtable 是线程安全的也是 synchronized,所以在单线程环境下它比 HashMap 要慢。如果你不需要同步,只需要单一线程,那么使用 HashMap 性能要好过 Hashtable。
|
||||
- HashMap 不能保证随着时间的推移 Map 中的元素次序是不变的。
|
||||
- HashMap 几乎可以等价于 Hashtable,除了 HashMap 是非 synchronized 的,并可以接受 null(HashMap 可以接受为 null 的键值 (key) 和值 (value),而 Hashtable 则不行)。
|
||||
- HashMap 是非 synchronized,而 Hashtable 是 synchronized,这意味着 Hashtable 是线程安全的,多个线程可以共享一个 Hashtable;而如果没有正确的同步的话,多个线程是不能共享 HashMap 的。Java 5 提供了 ConcurrentHashMap,它是 HashTable 的替代,比 HashTable 的扩展性更好。
|
||||
- 另一个区别是 HashMap 的迭代器 (Iterator) 是 fail-fast 迭代器,而 Hashtable 的 enumerator 迭代器不是 fail-fast 的。所以当有其它线程改变了 HashMap 的结构(增加或者移除元素),将会抛出 ConcurrentModificationException,但迭代器本身的 remove() 方法移除元素则不会抛出 ConcurrentModificationException 异常。但这并不是一个一定发生的行为,要看 JVM。这条同样也是 Enumeration 和 Iterator 的区别。
|
||||
- 由于 Hashtable 是线程安全的也是 synchronized,所以在单线程环境下它比 HashMap 要慢。如果你不需要同步,只需要单一线程,那么使用 HashMap 性能要好过 Hashtable。
|
||||
- HashMap 不能保证随着时间的推移 Map 中的元素次序是不变的。
|
||||
|
||||
> [What is difference between HashMap and Hashtable in Java?](http://javarevisited.blogspot.hk/2010/10/difference-between-hashmap-and.html)
|
||||
> [What is difference between HashMap and Hashtable in Java?](http://javarevisited.blogspot.hk/2010/10/difference-between-hashmap-and.html)
|
||||
|
||||
## 6. LinkedHashMap
|
||||
## 6. LinkedHashMap
|
||||
|
||||
[LinkedHashMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/HashMap.java)
|
||||
|
||||
## 7. ConcurrentHashMap
|
||||
## 7. ConcurrentHashMap
|
||||
|
||||
[ConcurrentHashMap.java](https://github.com/CyC2018/JDK-Source-Code/tree/master/src/HashMap.java)
|
||||
|
||||
[探索 ConcurrentHashMap 高并发性的实现机制](https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/)
|
||||
[探索 ConcurrentHashMap 高并发性的实现机制](https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/)
|
||||
|
||||
# 参考资料
|
||||
# 参考资料
|
||||
|
||||
- Java 编程思想
|
||||
- Java 编程思想
|
||||
|
|
1035
notes/Java 并发.md
1035
notes/Java 并发.md
File diff suppressed because it is too large
Load Diff
6537
notes/Leetcode 题解.md
6537
notes/Leetcode 题解.md
File diff suppressed because it is too large
Load Diff
1063
notes/Linux.md
1063
notes/Linux.md
File diff suppressed because it is too large
Load Diff
377
notes/MySQL.md
377
notes/MySQL.md
|
@ -1,12 +1,61 @@
|
|||
[TOC]
|
||||
<!-- GFM-TOC -->
|
||||
* [存储引擎](#存储引擎)
|
||||
* [1. InnoDB](#1-innodb)
|
||||
* [2. MyISAM](#2-myisam)
|
||||
* [3. InnoDB 与 MyISAM 的比较](#3-innodb-与-myisam-的比较)
|
||||
* [数据类型](#数据类型)
|
||||
* [1. 整型](#1-整型)
|
||||
* [2. 浮点数](#2-浮点数)
|
||||
* [3. 字符串](#3-字符串)
|
||||
* [4. 时间和日期](#4-时间和日期)
|
||||
* [索引](#索引)
|
||||
* [1. 索引分类](#1-索引分类)
|
||||
* [1.1 B-Tree 索引](#11-b-tree-索引)
|
||||
* [1.2 哈希索引](#12-哈希索引)
|
||||
* [1.3. 空间索引(R-Tree)](#13-空间索引r-tree)
|
||||
* [1.4 全文索引](#14-全文索引)
|
||||
* [2. 索引的优点](#2-索引的优点)
|
||||
* [3. 索引优化](#3-索引优化)
|
||||
* [3.1 独立的列](#31-独立的列)
|
||||
* [3.2 前缀索引](#32-前缀索引)
|
||||
* [3.3 多列索引](#33-多列索引)
|
||||
* [3.4 索引列的顺序](#34-索引列的顺序)
|
||||
* [3.5 聚簇索引](#35-聚簇索引)
|
||||
* [3.6 覆盖索引](#36-覆盖索引)
|
||||
* [4. B-Tree 和 B+Tree 原理](#4-b-tree-和-b+tree-原理)
|
||||
* [4. 1 B-Tree](#4-1-b-tree)
|
||||
* [4.2 B+Tree](#42-b+tree)
|
||||
* [4.3 带有顺序访问指针的 B+Tree](#43-带有顺序访问指针的-b+tree)
|
||||
* [4.4 为什么使用 B-Tree 和 B+Tree](#44-为什么使用-b-tree-和-b+tree)
|
||||
* [查询性能优化](#查询性能优化)
|
||||
* [1. Explain](#1-explain)
|
||||
* [2. 减少返回的列](#2-减少返回的列)
|
||||
* [3. 减少返回的行](#3-减少返回的行)
|
||||
* [4. 拆分大的 DELETE 或 INSERT 语句](#4-拆分大的-delete-或-insert-语句)
|
||||
* [分库与分表](#分库与分表)
|
||||
* [1. 分库与分表的原因](#1-分库与分表的原因)
|
||||
* [2. 实现方式](#2-实现方式)
|
||||
* [2.1 垂直切分](#21-垂直切分)
|
||||
* [2.2 水平切分](#22-水平切分)
|
||||
* [2.3 切分的选择](#23-切分的选择)
|
||||
* [3. Merge 存储引擎](#3-merge-存储引擎)
|
||||
* [4. 分库与分表存在的问题](#4-分库与分表存在的问题)
|
||||
* [4.1 事务问题](#41-事务问题)
|
||||
* [4.2 跨库跨表连接问题](#42-跨库跨表连接问题)
|
||||
* [4.3 额外的数据管理负担和数据运算压力](#43-额外的数据管理负担和数据运算压力)
|
||||
* [5. 分表与分区的不同](#5-分表与分区的不同)
|
||||
* [故障转移和故障恢复](#故障转移和故障恢复)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
# 存储引擎
|
||||
|
||||
## 1. InnoDB
|
||||
# 存储引擎
|
||||
|
||||
InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支持的特性时,才考虑使用其它存储引擎。
|
||||
## 1. InnoDB
|
||||
|
||||
采用 MVCC 来支持高并发,并且实现了四个标准的隔离级别,默认级别是可重复读。
|
||||
InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支持的特性时,才考虑使用其它存储引擎。
|
||||
|
||||
采用 MVCC 来支持高并发,并且实现了四个标准的隔离级别,默认级别是可重复读。
|
||||
|
||||
表是基于聚簇索引建立的,它对主键的查询性能有很高的提升。
|
||||
|
||||
|
@ -14,9 +63,9 @@ InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB
|
|||
|
||||
通过一些机制和工具支持真正的热备份。
|
||||
|
||||
## 2. MyISAM
|
||||
## 2. MyISAM
|
||||
|
||||
MyISAM 提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等。但 MyISAM 不支持事务和行级锁,而且崩溃后无法安全恢复。
|
||||
MyISAM 提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等。但 MyISAM 不支持事务和行级锁,而且崩溃后无法安全恢复。
|
||||
|
||||
只能对整张表加锁,而不是针对行。
|
||||
|
||||
|
@ -24,83 +73,83 @@ MyISAM 提供了大量的特性,包括全文索引、压缩、空间函数(
|
|||
|
||||
可以包含动态或者静态的行。
|
||||
|
||||
如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作。
|
||||
如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作。
|
||||
|
||||
如果表在创建并导入数据以后,不会再进行修改操作,那么这样的表适合采用 MyISAM 压缩表。
|
||||
如果表在创建并导入数据以后,不会再进行修改操作,那么这样的表适合采用 MyISAM 压缩表。
|
||||
|
||||
对于只读数据,或者表比较小、可以容忍修复操作,则依然可以继续使用 MyISAM。
|
||||
对于只读数据,或者表比较小、可以容忍修复操作,则依然可以继续使用 MyISAM。
|
||||
|
||||
MyISAM 设计简单,数据以紧密格式存储,所以在某些场景下性能很好。
|
||||
MyISAM 设计简单,数据以紧密格式存储,所以在某些场景下性能很好。
|
||||
|
||||
## 3. InnoDB 与 MyISAM 的比较
|
||||
## 3. InnoDB 与 MyISAM 的比较
|
||||
|
||||
**事务**
|
||||
**事务**
|
||||
|
||||
InnoDB 是事务型的。
|
||||
InnoDB 是事务型的。
|
||||
|
||||
**备份**
|
||||
**备份**
|
||||
|
||||
InnoDB 支持在线热备份。
|
||||
InnoDB 支持在线热备份。
|
||||
|
||||
**崩溃恢复**
|
||||
**崩溃恢复**
|
||||
|
||||
MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。
|
||||
MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。
|
||||
|
||||
**并发**
|
||||
**并发**
|
||||
|
||||
MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。
|
||||
MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。
|
||||
|
||||
**其它特性**
|
||||
**其它特性**
|
||||
|
||||
MyISAM 支持全文索引,地理空间索引。
|
||||
MyISAM 支持全文索引,地理空间索引。
|
||||
|
||||
# 数据类型
|
||||
# 数据类型
|
||||
|
||||
## 1. 整型
|
||||
## 1. 整型
|
||||
|
||||
TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT 分别使用 8, 16, 24, 32, 64 位存储空间,一般情况下越小的列越好。
|
||||
TINYINT, SMALLINT, MEDIUMINT, INT, BIGINT 分别使用 8, 16, 24, 32, 64 位存储空间,一般情况下越小的列越好。
|
||||
|
||||
INT(11) 中的数字只是规定了交互工具显示字符的个数,对于存储和计算来说是没有意义的。
|
||||
INT(11) 中的数字只是规定了交互工具显示字符的个数,对于存储和计算来说是没有意义的。
|
||||
|
||||
## 2. 浮点数
|
||||
## 2. 浮点数
|
||||
|
||||
FLOAT 和 DOUBLE 为浮点类型,DECIMAL 为高精度小数类型。CPU 原生支持浮点运算,但是不支持 DECIMAl 类型的计算,因此 DECIMAL 的计算比浮点类型需要更高的代价。
|
||||
FLOAT 和 DOUBLE 为浮点类型,DECIMAL 为高精度小数类型。CPU 原生支持浮点运算,但是不支持 DECIMAl 类型的计算,因此 DECIMAL 的计算比浮点类型需要更高的代价。
|
||||
|
||||
FLOAT、DOUBLE 和 DECIMAL 都可以指定列宽,例如 DECIMAL(18, 9) 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分。
|
||||
FLOAT、DOUBLE 和 DECIMAL 都可以指定列宽,例如 DECIMAL(18, 9) 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分。
|
||||
|
||||
## 3. 字符串
|
||||
## 3. 字符串
|
||||
|
||||
主要有 CHAR 和 VARCHAR 两种类型,一种是定长的,一种是变长的。
|
||||
主要有 CHAR 和 VARCHAR 两种类型,一种是定长的,一种是变长的。
|
||||
|
||||
VARCHAR 这种变长类型能够节省空间,因为只需要存储必要的内容。但是在执行 UPDATE 时可能会使行变得比原来长,当超出一个页所能容纳的大小时,就要执行额外的操作。MyISAM 会将行拆成不同的片段存储,而 InnoDB 则需要分裂页来使行放进页内。
|
||||
VARCHAR 这种变长类型能够节省空间,因为只需要存储必要的内容。但是在执行 UPDATE 时可能会使行变得比原来长,当超出一个页所能容纳的大小时,就要执行额外的操作。MyISAM 会将行拆成不同的片段存储,而 InnoDB 则需要分裂页来使行放进页内。
|
||||
|
||||
VARCHAR 会保留字符串末尾的空格,而 CHAR 会删除。
|
||||
VARCHAR 会保留字符串末尾的空格,而 CHAR 会删除。
|
||||
|
||||
## 4. 时间和日期
|
||||
## 4. 时间和日期
|
||||
|
||||
MySQL 提供了两种相似的日期时间类型:DATATIME 和 TIMESTAMP。
|
||||
MySQL 提供了两种相似的日期时间类型:DATATIME 和 TIMESTAMP。
|
||||
|
||||
**DATATIME**
|
||||
**DATATIME**
|
||||
|
||||
能够保存从 1001 年到 9999 年的日期和时间,精度为秒,使用 8 字节的存储空间。
|
||||
能够保存从 1001 年到 9999 年的日期和时间,精度为秒,使用 8 字节的存储空间。
|
||||
|
||||
它与时区无关。
|
||||
|
||||
默认情况下,MySQL 以一种可排序的、无歧义的格式显示 DATATIME 值,例如“2008-01-16 22:37:08”,这是 ANSI 标准定义的日期和时间表示方法。
|
||||
默认情况下,MySQL 以一种可排序的、无歧义的格式显示 DATATIME 值,例如“2008-01-16 22:37:08”,这是 ANSI 标准定义的日期和时间表示方法。
|
||||
|
||||
**TIMESTAMP**
|
||||
**TIMESTAMP**
|
||||
|
||||
和 UNIX 时间戳相同,保存从 1970 年 1 月 1 日午夜(格林威治时间)以来的秒数,使用 4 个字节,只能表示从 1970 年 到 2038 年。
|
||||
和 UNIX 时间戳相同,保存从 1970 年 1 月 1 日午夜(格林威治时间)以来的秒数,使用 4 个字节,只能表示从 1970 年 到 2038 年。
|
||||
|
||||
它和时区有关。
|
||||
|
||||
MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提供了 UNIX_TIMESTAMP() 函数把日期转换为 UNIX 时间戳。
|
||||
MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提供了 UNIX_TIMESTAMP() 函数把日期转换为 UNIX 时间戳。
|
||||
|
||||
默认情况下,如果插入时没有指定 TIMESTAMP 列的值,会将这个值设置为当前时间。
|
||||
默认情况下,如果插入时没有指定 TIMESTAMP 列的值,会将这个值设置为当前时间。
|
||||
|
||||
应该尽量使用 TIMESTAMP,因为它比 DATETIME 空间效率更高。
|
||||
应该尽量使用 TIMESTAMP,因为它比 DATETIME 空间效率更高。
|
||||
|
||||
# 索引
|
||||
# 索引
|
||||
|
||||
索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。
|
||||
|
||||
|
@ -108,283 +157,283 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,
|
|||
|
||||
对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效。对于中到大型的表,索引就非常有效。但是对于特大型的表,建立和使用索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。
|
||||
|
||||
## 1. 索引分类
|
||||
## 1. 索引分类
|
||||
|
||||
### 1.1 B-Tree 索引
|
||||
### 1.1 B-Tree 索引
|
||||
|
||||
B-Tree 索引是大多数 MySQL 存储引擎的默认索引类型。
|
||||
B-Tree 索引是大多数 MySQL 存储引擎的默认索引类型。
|
||||
|
||||
因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。
|
||||
|
||||
可以指定多个列作为索引列,多个索引列共同组成键。B-Tree 索引适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。
|
||||
可以指定多个列作为索引列,多个索引列共同组成键。B-Tree 索引适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。
|
||||
|
||||
除了用于查找,还可以用于排序和分组。
|
||||
|
||||
如果不是按照索引列的顺序进行查找,则无法使用索引。
|
||||
|
||||
### 1.2 哈希索引
|
||||
### 1.2 哈希索引
|
||||
|
||||
基于哈希表实现,优点是查找非常快。
|
||||
|
||||
在 MySQL 中只有 Memory 引擎显式支持哈希索引。
|
||||
在 MySQL 中只有 Memory 引擎显式支持哈希索引。
|
||||
|
||||
InnoDB 引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B-Tree 索引之上再创建一个哈希索引,这样就让 B-Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。
|
||||
InnoDB 引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B-Tree 索引之上再创建一个哈希索引,这样就让 B-Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。
|
||||
|
||||
限制:哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以大部分情况下这一点对性能影响并不明显;无法用于分组与排序;只支持精确查找,无法用于部分查找和范围查找;如果哈希冲突很多,查找速度会变得很慢。
|
||||
|
||||
### 1.3. 空间索引(R-Tree)
|
||||
### 1.3. 空间索引(R-Tree)
|
||||
|
||||
MyISAM 存储引擎支持空间索引,可以用于地理数据存储。
|
||||
MyISAM 存储引擎支持空间索引,可以用于地理数据存储。
|
||||
|
||||
空间索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。
|
||||
|
||||
### 1.4 全文索引
|
||||
### 1.4 全文索引
|
||||
|
||||
MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较索引中的值。
|
||||
MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较索引中的值。
|
||||
|
||||
使用 MATCH AGAINST,而不是普通的 WHERE。
|
||||
使用 MATCH AGAINST,而不是普通的 WHERE。
|
||||
|
||||
## 2. 索引的优点
|
||||
## 2. 索引的优点
|
||||
|
||||
- 大大减少了服务器需要扫描的数据量;
|
||||
- 大大减少了服务器需要扫描的数据量;
|
||||
|
||||
- 帮助服务器避免进行排序和创建临时表;
|
||||
- 帮助服务器避免进行排序和创建临时表;
|
||||
|
||||
- 将随机 I/O 变为顺序 I/O。
|
||||
- 将随机 I/O 变为顺序 I/O。
|
||||
|
||||
## 3. 索引优化
|
||||
## 3. 索引优化
|
||||
|
||||
### 3.1 独立的列
|
||||
### 3.1 独立的列
|
||||
|
||||
在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。
|
||||
|
||||
例如下面的查询不能使用 actor_id 列的索引:
|
||||
例如下面的查询不能使用 actor_id 列的索引:
|
||||
|
||||
```sql
|
||||
SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;
|
||||
SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;
|
||||
```
|
||||
|
||||
### 3.2 前缀索引
|
||||
### 3.2 前缀索引
|
||||
|
||||
对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。
|
||||
对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。
|
||||
|
||||
对于前缀长度的选取需要根据 **索引选择性** 来确定:不重复的索引值和记录总数的比值。选择性越高,查询效率也越高。最大值为 1 ,此时每个记录都有唯一的索引与其对应。
|
||||
对于前缀长度的选取需要根据 **索引选择性** 来确定:不重复的索引值和记录总数的比值。选择性越高,查询效率也越高。最大值为 1 ,此时每个记录都有唯一的索引与其对应。
|
||||
|
||||
### 3.3 多列索引
|
||||
### 3.3 多列索引
|
||||
|
||||
在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 file_id 设置为多列索引。
|
||||
在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 file_id 设置为多列索引。
|
||||
|
||||
```sql
|
||||
SELECT file_id, actor_ id FROM sakila.film_actor
|
||||
WhERE actor_id = 1 OR film_id = 1;
|
||||
SELECT file_id, actor_ id FROM sakila.film_actor
|
||||
WhERE actor_id = 1 OR film_id = 1;
|
||||
```
|
||||
|
||||
### 3.4 索引列的顺序
|
||||
### 3.4 索引列的顺序
|
||||
|
||||
让选择性最强的索引列放在前面,例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。
|
||||
让选择性最强的索引列放在前面,例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。
|
||||
|
||||
```sql
|
||||
SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity,
|
||||
COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity,
|
||||
SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity,
|
||||
COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity,
|
||||
COUNT(*)
|
||||
FROM payment;
|
||||
FROM payment;
|
||||
```
|
||||
|
||||
```html
|
||||
staff_id_selectivity: 0.0001
|
||||
customer_id_selectivity: 0.0373
|
||||
COUNT(*): 16049
|
||||
staff_id_selectivity: 0.0001
|
||||
customer_id_selectivity: 0.0373
|
||||
COUNT(*): 16049
|
||||
```
|
||||
|
||||
### 3.5 聚簇索引
|
||||
### 3.5 聚簇索引
|
||||
|
||||
![](index_files/b9e9ae8c-e216-4c01-b267-a50dbeb98fa4.jpg)
|
||||
<div align="center"> <img src="../pics//b9e9ae8c-e216-4c01-b267-a50dbeb98fa4.jpg"/> </div><br>
|
||||
|
||||
聚簇索引并不是一种索引类型,而是一种数据存储方式。
|
||||
|
||||
术语“聚簇”表示数据行和相邻的键值紧密地存储在一起,InnoDB 的聚簇索引的数据行存放在 B-Tree 的叶子页中。
|
||||
术语“聚簇”表示数据行和相邻的键值紧密地存储在一起,InnoDB 的聚簇索引的数据行存放在 B-Tree 的叶子页中。
|
||||
|
||||
因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
|
||||
|
||||
**优点**
|
||||
**优点**
|
||||
|
||||
1. 可以把相关数据保存在一起,减少 I/O 操作;
|
||||
2. 因为数据保存在 B-Tree 中,因此数据访问更快。
|
||||
1. 可以把相关数据保存在一起,减少 I/O 操作;
|
||||
2. 因为数据保存在 B-Tree 中,因此数据访问更快。
|
||||
|
||||
**缺点**
|
||||
**缺点**
|
||||
|
||||
1. 聚簇索引最大限度提高了 I/O 密集型应用的性能,但是如果数据全部放在内存,就没必要用聚簇索引。
|
||||
2. 插入速度严重依赖于插入顺序,按主键的顺序插入是最快的。
|
||||
3. 更新操作代价很高,因为每个被更新的行都会移动到新的位置。
|
||||
4. 当插入到某个已满的页中,存储引擎会将该页分裂成两个页面来容纳该行,页分裂会导致表占用更多的磁盘空间。
|
||||
5. 如果行比较稀疏,或者由于页分裂导致数据存储不连续时,聚簇索引可能导致全表扫描速度变慢。
|
||||
1. 聚簇索引最大限度提高了 I/O 密集型应用的性能,但是如果数据全部放在内存,就没必要用聚簇索引。
|
||||
2. 插入速度严重依赖于插入顺序,按主键的顺序插入是最快的。
|
||||
3. 更新操作代价很高,因为每个被更新的行都会移动到新的位置。
|
||||
4. 当插入到某个已满的页中,存储引擎会将该页分裂成两个页面来容纳该行,页分裂会导致表占用更多的磁盘空间。
|
||||
5. 如果行比较稀疏,或者由于页分裂导致数据存储不连续时,聚簇索引可能导致全表扫描速度变慢。
|
||||
|
||||
### 3.6 覆盖索引
|
||||
### 3.6 覆盖索引
|
||||
|
||||
索引包含所有需要查询的字段的值。
|
||||
|
||||
**优点**
|
||||
**优点**
|
||||
|
||||
1. 因为索引条目通常远小于数据行的大小,所以若只读取索引,能大大减少数据访问量。
|
||||
2. 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。
|
||||
3. 对于 InnoDB 引擎,若二级索引能够覆盖查询,则无需访问聚簇索引。
|
||||
1. 因为索引条目通常远小于数据行的大小,所以若只读取索引,能大大减少数据访问量。
|
||||
2. 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。
|
||||
3. 对于 InnoDB 引擎,若二级索引能够覆盖查询,则无需访问聚簇索引。
|
||||
|
||||
## 4. B-Tree 和 B+Tree 原理
|
||||
## 4. B-Tree 和 B+Tree 原理
|
||||
|
||||
### 4. 1 B-Tree
|
||||
### 4. 1 B-Tree
|
||||
|
||||
![](index_files/5ed71283-a070-4b21-85ae-f2cbfd6ba6e1.jpg)
|
||||
<div align="center"> <img src="../pics//5ed71283-a070-4b21-85ae-f2cbfd6ba6e1.jpg"/> </div><br>
|
||||
|
||||
为了描述 B-Tree,首先定义一条数据记录为一个二元组 [key, data],key 为记录的键,data 为数据记录除 key 外的数据。
|
||||
为了描述 B-Tree,首先定义一条数据记录为一个二元组 [key, data],key 为记录的键,data 为数据记录除 key 外的数据。
|
||||
|
||||
B-Tree 是满足下列条件的数据结构:
|
||||
B-Tree 是满足下列条件的数据结构:
|
||||
|
||||
- 所有叶节点具有相同的深度,也就是说 B-Tree 是平衡的;
|
||||
- 一个节点中的 key 从左到右非递减排列;
|
||||
- 如果某个指针的左右相邻 key 分别是 key<sub>i</sub> 和 key<sub>i+1</sub>,且不为 null,则该指针指向节点的所有 key 大于 key<sub>i</sub> 且小于 key<sub>i+1</sub>。
|
||||
- 所有叶节点具有相同的深度,也就是说 B-Tree 是平衡的;
|
||||
- 一个节点中的 key 从左到右非递减排列;
|
||||
- 如果某个指针的左右相邻 key 分别是 key<sub>i</sub> 和 key<sub>i+1</sub>,且不为 null,则该指针指向节点的所有 key 大于 key<sub>i</sub> 且小于 key<sub>i+1</sub>。
|
||||
|
||||
在 B-Tree 中按 key 检索数据的算法非常直观:首先从根节点进行二分查找,如果找到则返回对应节点的 data,否则对相应区间的指针指向的节点递归进行查找,直到找到节点或找到 null 指针,前者查找成功,后者查找失败。
|
||||
在 B-Tree 中按 key 检索数据的算法非常直观:首先从根节点进行二分查找,如果找到则返回对应节点的 data,否则对相应区间的指针指向的节点递归进行查找,直到找到节点或找到 null 指针,前者查找成功,后者查找失败。
|
||||
|
||||
由于插入删除新的数据记录会破坏 B-Tree 的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作以保持 B-Tree 性质。
|
||||
由于插入删除新的数据记录会破坏 B-Tree 的性质,因此在插入删除时,需要对树进行一个分裂、合并、转移等操作以保持 B-Tree 性质。
|
||||
|
||||
### 4.2 B+Tree
|
||||
### 4.2 B+Tree
|
||||
|
||||
![](index_files/63cd5b50-d6d8-4df6-8912-ef4a1dd5ba13.jpg)
|
||||
<div align="center"> <img src="../pics//63cd5b50-d6d8-4df6-8912-ef4a1dd5ba13.jpg"/> </div><br>
|
||||
|
||||
与 B-Tree 相比,B+Tree 有以下不同点:
|
||||
与 B-Tree 相比,B+Tree 有以下不同点:
|
||||
|
||||
- 每个节点的指针上限为 2d 而不是 2d+1;
|
||||
- 内节点不存储 data,只存储 key,叶子节点不存储指针。
|
||||
- 每个节点的指针上限为 2d 而不是 2d+1;
|
||||
- 内节点不存储 data,只存储 key,叶子节点不存储指针。
|
||||
|
||||
### 4.3 带有顺序访问指针的 B+Tree
|
||||
### 4.3 带有顺序访问指针的 B+Tree
|
||||
|
||||
![](index_files/1ee5f0a5-b8df-43b9-95ab-c516c54ec797.jpg)
|
||||
<div align="center"> <img src="../pics//1ee5f0a5-b8df-43b9-95ab-c516c54ec797.jpg"/> </div><br>
|
||||
|
||||
一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 基础上进行了优化,在叶子节点增加了顺序访问指针,做这个优化的目的是为了提高区间访问的性能。
|
||||
一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 基础上进行了优化,在叶子节点增加了顺序访问指针,做这个优化的目的是为了提高区间访问的性能。
|
||||
|
||||
### 4.4 为什么使用 B-Tree 和 B+Tree
|
||||
### 4.4 为什么使用 B-Tree 和 B+Tree
|
||||
|
||||
红黑树等数据结构也可以用来实现索引,但是文件系统及数据库系统普遍采用 B-/+Tree 作为索引结构。
|
||||
红黑树等数据结构也可以用来实现索引,但是文件系统及数据库系统普遍采用 B-/+Tree 作为索引结构。
|
||||
|
||||
页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为 4k),主存和磁盘以页为单位交换数据。
|
||||
页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为 4k),主存和磁盘以页为单位交换数据。
|
||||
|
||||
一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。为了减少磁盘 I/O,磁盘往往不是严格按需读取,而是每次都会预读。这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次 I/O 就可以完全载入。B-Tree 中一次检索最多需要 h-1 次 I/O(根节点常驻内存),渐进复杂度为 O(h)=O(logdN)。一般实际应用中,出度 d 是非常大的数字,通常超过 100,因此 h 非常小(通常不超过 3)。而红黑树这种结构,h 明显要深的多。并且于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,效率明显比 B-Tree 差很多。
|
||||
一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。为了减少磁盘 I/O,磁盘往往不是严格按需读取,而是每次都会预读。这样做的理论依据是计算机科学中著名的局部性原理:当一个数据被用到时,其附近的数据也通常会马上被使用。数据库系统的设计者巧妙利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次 I/O 就可以完全载入。B-Tree 中一次检索最多需要 h-1 次 I/O(根节点常驻内存),渐进复杂度为 O(h)=O(logdN)。一般实际应用中,出度 d 是非常大的数字,通常超过 100,因此 h 非常小(通常不超过 3)。而红黑树这种结构,h 明显要深的多。并且于逻辑上很近的节点(父子)物理上可能很远,无法利用局部性,效率明显比 B-Tree 差很多。
|
||||
|
||||
B+Tree 更适合外存索引,原因和内节点出度 d 有关。由于 B+Tree 内节点去掉了 data 域,因此可以拥有更大的出度,拥有更好的性能。
|
||||
B+Tree 更适合外存索引,原因和内节点出度 d 有关。由于 B+Tree 内节点去掉了 data 域,因此可以拥有更大的出度,拥有更好的性能。
|
||||
|
||||
# 查询性能优化
|
||||
# 查询性能优化
|
||||
|
||||
## 1. Explain
|
||||
## 1. Explain
|
||||
|
||||
用来分析 SQL 语句,分析结果中比较重要的字段有:
|
||||
用来分析 SQL 语句,分析结果中比较重要的字段有:
|
||||
|
||||
- select_type : 查询类型,有简单查询、联合查询和子查询
|
||||
- select_type : 查询类型,有简单查询、联合查询和子查询
|
||||
|
||||
- key : 使用的索引
|
||||
- key : 使用的索引
|
||||
|
||||
- rows : 扫描的行数
|
||||
- rows : 扫描的行数
|
||||
|
||||
## 2. 减少返回的列
|
||||
## 2. 减少返回的列
|
||||
|
||||
慢查询主要是因为访问了过多数据,除了访问过多行之外,也包括访问过多列。
|
||||
|
||||
最好不要使用 SELECT * 语句,要根据需要选择查询的列。
|
||||
最好不要使用 SELECT * 语句,要根据需要选择查询的列。
|
||||
|
||||
## 3. 减少返回的行
|
||||
## 3. 减少返回的行
|
||||
|
||||
最好使用 LIMIT 语句来取出想要的那些行。
|
||||
最好使用 LIMIT 语句来取出想要的那些行。
|
||||
|
||||
还可以建立索引来减少条件语句的全表扫描。例如对于下面的语句,不适用索引的情况下需要进行全表扫描,而使用索引只需要扫描几行记录即可,使用 Explain 语句可以通过观察 rows 字段来看出这种差异。
|
||||
还可以建立索引来减少条件语句的全表扫描。例如对于下面的语句,不适用索引的情况下需要进行全表扫描,而使用索引只需要扫描几行记录即可,使用 Explain 语句可以通过观察 rows 字段来看出这种差异。
|
||||
|
||||
```sql
|
||||
SELECT * FROM sakila.film_actor WHERE film_id = 1;
|
||||
SELECT * FROM sakila.film_actor WHERE film_id = 1;
|
||||
```
|
||||
|
||||
## 4. 拆分大的 DELETE 或 INSERT 语句
|
||||
## 4. 拆分大的 DELETE 或 INSERT 语句
|
||||
|
||||
如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。
|
||||
|
||||
```sql
|
||||
DELEFT FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH);
|
||||
DELEFT FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH);
|
||||
```
|
||||
```sql
|
||||
rows_affected = 0
|
||||
do {
|
||||
rows_affected = do_query(
|
||||
"DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH) LIMIT 10000")
|
||||
} while rows_affected > 0
|
||||
rows_affected = 0
|
||||
do {
|
||||
rows_affected = do_query(
|
||||
"DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH) LIMIT 10000")
|
||||
} while rows_affected > 0
|
||||
```
|
||||
|
||||
# 分库与分表
|
||||
# 分库与分表
|
||||
|
||||
## 1. 分库与分表的原因
|
||||
## 1. 分库与分表的原因
|
||||
|
||||
随着时间和业务的发展,数据库中的表会越来越多,并且表中的数据量也会越来越大,那么读写操作的开销也会随着增大。
|
||||
|
||||
## 2. 实现方式
|
||||
## 2. 实现方式
|
||||
|
||||
### 2.1 垂直切分
|
||||
### 2.1 垂直切分
|
||||
|
||||
将表按功能模块、关系密切程度划分出来,部署到不同的库上。例如,我们会建立商品数据库 payDB、用户数据库 userDB 等,分别用来存储项目与商品有关的表和与用户有关的表。
|
||||
将表按功能模块、关系密切程度划分出来,部署到不同的库上。例如,我们会建立商品数据库 payDB、用户数据库 userDB 等,分别用来存储项目与商品有关的表和与用户有关的表。
|
||||
|
||||
### 2.2 水平切分
|
||||
### 2.2 水平切分
|
||||
|
||||
把表中的数据按照某种规则存储到多个结构相同的表中,例如按 id 的散列值、性别等进行划分。
|
||||
把表中的数据按照某种规则存储到多个结构相同的表中,例如按 id 的散列值、性别等进行划分。
|
||||
|
||||
### 2.3 切分的选择
|
||||
### 2.3 切分的选择
|
||||
|
||||
如果数据库中的表太多,并且项目各项业务逻辑清晰,那么垂直切分是首选。
|
||||
|
||||
如果数据库的表不多,但是单表的数据量很大,应该选择水平切分。
|
||||
|
||||
## 3. Merge 存储引擎
|
||||
## 3. Merge 存储引擎
|
||||
|
||||
该存储引擎支持分表。
|
||||
|
||||
## 4. 分库与分表存在的问题
|
||||
## 4. 分库与分表存在的问题
|
||||
|
||||
### 4.1 事务问题
|
||||
### 4.1 事务问题
|
||||
|
||||
在执行分库分表之后,由于数据存储到了不同的库上,数据库事务管理出现了困难。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价;如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。
|
||||
|
||||
### 4.2 跨库跨表连接问题
|
||||
### 4.2 跨库跨表连接问题
|
||||
|
||||
在执行了分库分表之后,难以避免会将原本逻辑关联性很强的数据划分到不同的表、不同的库上。这时,表的连接操作将受到限制,我们无法连接位于不同分库的表,也无法连接分表粒度不同的表,导致原本只需要一次查询就能够完成的业务需要进行多次才能完成。
|
||||
|
||||
### 4.3 额外的数据管理负担和数据运算压力
|
||||
### 4.3 额外的数据管理负担和数据运算压力
|
||||
|
||||
最显而易见的就是数据的定位问题和数据的增删改查的重复执行问题,这些都可以通过应用程序解决,但必然引起额外的逻辑运算。
|
||||
|
||||
## 5. 分表与分区的不同
|
||||
## 5. 分表与分区的不同
|
||||
|
||||
分表,就是讲一张表分成多个小表,这些小表拥有不同的表名;而分区是将一张表的数据分为多个区块,这些区块可以存储在同一个磁盘上,也可以存储在不同的磁盘上,这种方式下表仍然只有一个。
|
||||
|
||||
# 故障转移和故障恢复
|
||||
# 故障转移和故障恢复
|
||||
|
||||
故障转移也叫做切换,当主库出现故障时就切换到备库,使备库成为主库。故障恢复顾名思义就是从故障中恢复过来,并且保证数据的正确性。
|
||||
|
||||
**提升备库或切换角色**
|
||||
**提升备库或切换角色**
|
||||
|
||||
提升一台备库为主库,或者在一个主-主复制结构中调整主动和被动角色。
|
||||
|
||||
**虚拟 IP 地址和 IP 托管**
|
||||
**虚拟 IP 地址和 IP 托管**
|
||||
|
||||
为 MySQL 实例指定一个逻辑 IP 地址,当 MySQL 实例失效时,可以将 IP 地址转移到另一台 MySQL 服务器上。
|
||||
为 MySQL 实例指定一个逻辑 IP 地址,当 MySQL 实例失效时,可以将 IP 地址转移到另一台 MySQL 服务器上。
|
||||
|
||||
**中间件解决方案**
|
||||
**中间件解决方案**
|
||||
|
||||
通过代理,可以路由流量到可以使用的服务器上。
|
||||
|
||||
![](index_files/fabd5fa0-b75e-48d0-9e2c-31471945ceb9.jpg)
|
||||
<div align="center"> <img src="../pics//fabd5fa0-b75e-48d0-9e2c-31471945ceb9.jpg"/> </div><br>
|
||||
|
||||
**在应用中处理故障转移**
|
||||
**在应用中处理故障转移**
|
||||
|
||||
将故障转移整合到应用中可能导致应用变得太过笨拙。
|
||||
|
||||
|
||||
# 参考资料
|
||||
# 参考资料
|
||||
|
||||
- 高性能 MySQL
|
||||
- [MySQL 索引背后的数据结构及算法原理 ](http://blog.codinglabs.org/articles/theory-of-mysql-index.html)
|
||||
- [20+ 条 MySQL 性能优化的最佳经验 ](https://www.jfox.info/20-tiao-mysql-xing-nen-you-hua-de-zui-jia-jing-yan.html)
|
||||
- [数据库为什么分库分表?mysql的分库分表方案](https://www.i3geek.com/archives/1108)
|
||||
- 高性能 MySQL
|
||||
- [MySQL 索引背后的数据结构及算法原理 ](http://blog.codinglabs.org/articles/theory-of-mysql-index.html)
|
||||
- [20+ 条 MySQL 性能优化的最佳经验 ](https://www.jfox.info/20-tiao-mysql-xing-nen-you-hua-de-zui-jia-jing-yan.html)
|
||||
- [数据库为什么分库分表?mysql的分库分表方案](https://www.i3geek.com/archives/1108)
|
||||
|
|
290
notes/Redis.md
Normal file
290
notes/Redis.md
Normal file
|
@ -0,0 +1,290 @@
|
|||
<!-- GFM-TOC -->
|
||||
* [Redis 是什么](#redis-是什么)
|
||||
* [Redis 的五种基本类型](#redis-的五种基本类型)
|
||||
* [键的过期时间](#键的过期时间)
|
||||
* [发布与订阅](#发布与订阅)
|
||||
* [事务](#事务)
|
||||
* [持久化](#持久化)
|
||||
* [1. 快照持久化](#1-快照持久化)
|
||||
* [2. AOF 持久化](#2-aof-持久化)
|
||||
* [复制](#复制)
|
||||
* [处理故障](#处理故障)
|
||||
* [分片](#分片)
|
||||
* [事件](#事件)
|
||||
* [Redis 与 Memcached 的区别](#redis-与-memcached-的区别)
|
||||
* [Redis 适用场景](#redis-适用场景)
|
||||
* [数据淘汰策略](#数据淘汰策略)
|
||||
* [一个简单的论坛系统分析](#一个简单的论坛系统分析)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# Redis 是什么
|
||||
|
||||
Redis 是速度非常快的非关系型(nosql)内存键值数据库,可以存储键和五种不同类型的值之间的映射。
|
||||
|
||||
五种类型数据类型为:字符串、列表、集合、有序集合、散列表。
|
||||
|
||||
Redis 支持很多特性,例如可以将内存中的数据持久化到硬盘中,可以使用复制来扩展读性能,可以使用分片来扩展写性能。
|
||||
|
||||
# Redis 的五种基本类型
|
||||
|
||||
| 数据类型 | 可以存储的值 | 操作 |
|
||||
| -- | -- | -- |
|
||||
| STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作;</br> 对整数和浮点数执行自增或者自减操作 |
|
||||
| LIST | 链表 | 从两端压入或者弹出元素;</br> 读取单个或者多个元素;</br> 进行修剪,只保留一个范围内的元素。 |
|
||||
| SET | 无序集合 | 添加、获取、移除单个元素;</br> 检查一个元素是否存在于集合中;</br> 计算交集、并集、差集;</br> 从集合里面随机获取元素。 |
|
||||
| HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对;</br> 获取所有键值对;</br> 检查某个键是否存在。|
|
||||
| ZSET | 有序集合 <sup>1</sup> | 添加、获取、删除元素个元素;</br> 根据分值范围或者成员来获取元素;</br> 计算一个键的排名。 |
|
||||
|
||||
注 1:有序集合的每个集合元素都对应一个分值,根据这个分值的大小来对集合元素进行排序。有因此有序集合相当于是有序的散列表,键是集合元素,值为元素对应的分值。
|
||||
|
||||
# 键的过期时间
|
||||
|
||||
Redis 可以为每个键设置过期时间,当键过期时,会自动删除该键。
|
||||
|
||||
对于散列表这种容器,只能为整个键设置过期时间(整个散列表),而不能为键里面的单个元素设置过期时间。
|
||||
|
||||
过期时间对于清理缓存数据非常有用。
|
||||
|
||||
# 发布与订阅
|
||||
|
||||
发布与订阅实际上是观察者模式,订阅者订阅了频道之后,发布者向频道发送字符串消息会被所有订阅者接收到。
|
||||
|
||||
发布与订阅有一些问题,很少使用它,而是使用替代的解决方案。问题如下:
|
||||
|
||||
1. 如果订阅者读取消息的速度很慢,会使得消息不断积压在发布者的输出缓存区中,造成内存占用过多;
|
||||
2. 如果订阅者在执行订阅的过程中网络出现问题,那么就会丢失断线期间发送的所有消息。
|
||||
|
||||
# 事务
|
||||
|
||||
Redis 最简单的事务实现方式是使用 MULTI 和 EXEC 命令将事务操作包围起来。
|
||||
|
||||
MULTI 和 EXEC 中的操作将会一次性发送给服务器,而不是一条一条发送,这种方式称为流水线,它可以减少客户端与服务器之间的网络通信次数从而提升性能。
|
||||
|
||||
# 持久化
|
||||
|
||||
Redis 是内存型数据库,为了保证数据在断电后不会丢失,需要将内存中的数据持久化到硬盘上。
|
||||
|
||||
## 1. 快照持久化
|
||||
|
||||
将某个时间点的所有数据都存放到硬盘上。
|
||||
|
||||
可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。
|
||||
|
||||
如果系统发生故障,将会丢失最后一次创建快照之后的数据。并且如果数据量很大,保存快照的时间也会很长。
|
||||
|
||||
## 2. AOF 持久化
|
||||
|
||||
AOF 持久化将写命令添加到 AOF 文件(Append Only File)的末尾。
|
||||
|
||||
对硬盘的文件进行写入时,写入的内容首先会被存储到缓冲区,然后由操作系统决定什么时候将该内容同步到硬盘,用户可以调用 file.flush() 方法请求操作系统尽快将缓冲区存储的数据同步到硬盘。因此将写命令添加到 AOF 文件时,要根据需求来保证何时将添加的数据同步到硬盘上,有以下同步选项:
|
||||
|
||||
| 选项 | 同步频率 |
|
||||
| -- | -- |
|
||||
| always | 每个写命令都同步 |
|
||||
| everysec | 每秒同步一次 |
|
||||
| no | 让操作系统来决定何时同步 |
|
||||
|
||||
always 选项会严重减低服务器的性能;everysec 选项比较合适,可以保证系统奔溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响;no 选项并不能给服务器性能带来多大的提升,而且也会增加系统奔溃时数据丢失的数量。
|
||||
|
||||
随着服务器写请求的增多,AOF 文件会越来越大;Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。
|
||||
|
||||
# 复制
|
||||
|
||||
通过使用 slaveof host port 命令来让一个服务器成为另一个服务器的从服务器。
|
||||
|
||||
一个从服务器只能有一个主服务器,并且不支持主主复制。
|
||||
|
||||
**1. 从服务器连接主服务器的过程**
|
||||
|
||||
(1) 主服务器创建快照文件,发送给从服务器,并在发送期间使用缓冲区记录执行的写命令。快照文件发送完毕之后,开始向从服务器发送存储在缓冲区中的写命令;
|
||||
(2) 从服务器丢弃所有旧数据,载入主服务器发来的快照文件,之后从服务器开始接受主服务器发来的写命令;
|
||||
(3) 主服务器每执行一次写命令,就向从服务器发送相同的写命令。
|
||||
|
||||
**2. 主从链**
|
||||
|
||||
随着负载不断上升,主服务器可能无法很快地更新所有从服务器,或者重新连接和重新同步从服务器而导致系统超载。为了解决这个问题,可以创建一个中间层来分担主服务器的复制工作。中间层的服务器是最上层服务器的从服务器,又是最下层服务器的主服务器。
|
||||
|
||||
<div align="center"> <img src="../pics//b242fafc-5945-42a8-805e-6e3f1f2f89b4.jpg"/> </div><br>
|
||||
|
||||
# 处理故障
|
||||
|
||||
要用到持久化文件来恢复服务器的数据。
|
||||
|
||||
持久化文件可能因为服务器出错也有错误,因此要先对持久化文件进行验证和修复。对 AOF 文件就行验证和修复很容易,修复操作将第一个出错命令和其后的所有命令都删除;但是只能验证快照文件,无法对快照文件进行修复,因为快照文件进行了压缩,出现在快照文件中间的错误可能会导致整个快照文件的剩余部分无法读取。
|
||||
|
||||
当主服务器出现故障时,Redis 常用的做法是新开一台服务器作为主服务器,具体步骤如下:假设 A 为主服务器,B 为从服务器,当 A 出现故障时,让 B 生成一个快照文件,将快照文件发送给 C,并让 C 恢复快照文件的数据。最后,让 B 成为 C 的从服务器。
|
||||
|
||||
# 分片
|
||||
|
||||
Redis 中的分片类似于 MySQL 的分表操作,分片是将数据划分为多个部分的方法,对数据的划分可以基于键包含的 ID、基于键的哈希值,或者基于以上两者的某种组合。通过对数据进行分片,用户可以将数据存储到多台机器里面,也可以从多台机器里面获取数据,这种方法在解决某些问题时可以获得线性级别的性能提升。
|
||||
|
||||
假设有 4 个 Reids 实例 R0,R1,R2,R3,还有很多表示用户的键 user:1,user:2,... 等等,有不同的方式来选择一个指定的键存储在哪个实例中。最简单的方式是范围分片,例如用户 id 从 0\~1000 的存储到实例 R0 中,用户 id 从 1001\~2000 的存储到实例 R1 中,等等。但是这样需要维护一张映射范围表,维护操作代价很高。还有一种方式是哈希分片,使用 CRC32 哈希函数将键转换为一个数字,再对实例数量求模就能知道应该存储的实例。
|
||||
|
||||
**1. 客户端分片**
|
||||
|
||||
客户端使用一致性哈希等算法决定键应当分布到哪个节点。
|
||||
|
||||
**2. 代理分片**
|
||||
|
||||
将客户端请求发送到代理上,由代理转发请求到正确的节点上。
|
||||
|
||||
**3. 服务器分片**
|
||||
|
||||
Redis Cluster。
|
||||
|
||||
# 事件
|
||||
|
||||
**1. 事件类型**
|
||||
|
||||
(1) 文件事件:服务器有许多套接字,事件产生时会对这些套接字进行操作,服务器通过监听套接字来处理事件。常见的文件事件有:客户端的连接事件;客户端的命令请求事件;服务器向客户端返回命令结果的事件;
|
||||
|
||||
(2) 时间事件:又分为两类,定时事件是让一段程序在指定的时间之内执行一次;周期性时间是让一段程序每隔指定时间就执行一次。
|
||||
|
||||
**2. 事件的调度与执行**
|
||||
|
||||
服务器需要不断监听文件事件的套接字才能得到待处理的文件事件,但是不能监听太久,否则时间事件无法在规定的时间内执行,因此监听时间应该根据距离现在最近的时间事件来决定。
|
||||
|
||||
事件调度与执行由 aeProcessEvents 函数负责,伪代码如下:
|
||||
|
||||
```python
|
||||
def aeProcessEvents():
|
||||
|
||||
# 获取到达时间离当前时间最接近的时间事件
|
||||
time_event = aeSearchNearestTimer()
|
||||
|
||||
#计算最接近的时间事件距离到达还有多少毫秒
|
||||
remaind_ms = time_event.when - unix_ts_now()
|
||||
|
||||
# 如果事件已到达,那么 remaind_ms 的值可能为负数,将它设为 0
|
||||
if remaind_ms < 0:
|
||||
remaind_ms = 0
|
||||
|
||||
# 根据 remaind_ms 的值,创建 timeval
|
||||
timeval = create_timeval_with_ms(remaind_ms)
|
||||
|
||||
# 阻塞并等待文件事件产生,最大阻塞时间由传入的 timeval 决定
|
||||
aeApiPoll(timeval)
|
||||
|
||||
# 处理所有已产生的文件事件
|
||||
procesFileEvents()
|
||||
|
||||
# 处理所有已到达的时间事件
|
||||
processTimeEvents()
|
||||
```
|
||||
|
||||
将 aeProcessEvents 函数置于一个循环里面,加上初始化和清理函数,就构成了 Redis 服务器的主函数,伪代码如下:
|
||||
|
||||
```python
|
||||
def main():
|
||||
|
||||
# 初始化服务器
|
||||
init_server()
|
||||
|
||||
# 一直处理事件,直到服务器关闭为止
|
||||
while server_is_not_shutdown():
|
||||
aeProcessEvents()
|
||||
|
||||
# 服务器关闭,执行清理操作
|
||||
clean_server()
|
||||
```
|
||||
|
||||
事件处理的角度下服务器运行流程如下:
|
||||
|
||||
<div align="center"> <img src="../pics//73b73189-9e95-47e5-91d0-9378b8462e15.png"/> </div><br>
|
||||
|
||||
# Redis 与 Memcached 的区别
|
||||
|
||||
两者都是非关系型内存键值数据库。有以下主要不同:
|
||||
|
||||
**1. 数据类型**
|
||||
|
||||
Memcached 仅支持字符串类型,而 Redis 支持五种不同种类的数据类型,使得它可以更灵活地解决问题。
|
||||
|
||||
**2. 数据持久化**
|
||||
|
||||
Redis 支持两种持久化策略:RDB 快照和 AOF 日志,而 Memcached 不支持持久化。
|
||||
|
||||
**3. 分布式**
|
||||
|
||||
Memcached 不支持分布式,只能通过在客户端使用像一致性哈希这样的分布式算法来实现分布式存储,这种方式在存储和查询时都需要先在客户端计算一次数据所在的节点。
|
||||
|
||||
Redis Cluster 实现了分布式的支持。
|
||||
|
||||
**4. 内存管理机制**
|
||||
|
||||
在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘。而 Memcached 的数据则会一直在内存中。
|
||||
|
||||
Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题,但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。
|
||||
|
||||
# Redis 适用场景
|
||||
|
||||
**1. 缓存**
|
||||
|
||||
适用 Redis 作为缓存,将热点数据放到内存中。
|
||||
|
||||
**2. 消息队列**
|
||||
|
||||
Redis 的 list 类型是双向链表,很适合用于消息队列。
|
||||
|
||||
**3. 计数器**
|
||||
|
||||
Redis 这种内存数据库才能支持计数器的频繁读写操作。
|
||||
|
||||
**4. 好友关系**
|
||||
|
||||
使用 set 类型的交集很容易就可以知道两个用户的共同好友。
|
||||
|
||||
# 数据淘汰策略
|
||||
|
||||
可以设置内存最大使用量,当内存使用量超过时施行淘汰策略,具体有 6 种淘汰策略。
|
||||
|
||||
| 策略 | 描述 |
|
||||
| -- | -- |
|
||||
| volatile-lru | 从已设置过期时间的数据集中挑选最近最少使用的数据淘汰 |
|
||||
| volatile-ttl | 从已设置过期时间的数据集中挑选将要过期的数据淘汰 |
|
||||
|volatile-random | 从已设置过期时间的数据集中任意选择数据淘汰 |
|
||||
| allkeys-lru | 从所有数据集中挑选最近最少使用的数据淘汰 |
|
||||
| allkeys-random | 从所有数据集中任意选择数据进行淘汰 |
|
||||
| no-envicition | 禁止驱逐数据 |
|
||||
|
||||
如果使用 Redis 来缓存数据时,要保证所有数据都是热点数据,可以将内存最大使用量设置为热点数据占用的内存量,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰。
|
||||
|
||||
# 一个简单的论坛系统分析
|
||||
|
||||
该论坛系统功能如下:
|
||||
|
||||
1. 可以发布文章;
|
||||
2. 可以对文章进行点赞;
|
||||
3. 在首页可以按文章的发布时间或者文章的点赞数进行排序显示;
|
||||
|
||||
**1. 文章信息**
|
||||
|
||||
文章包括标题、作者、赞数等信息,在关系型数据库中很容易构建一张表来存储这些信息,在 Redis 中可以使用 HASH 来存储每种信息以及其对应的值的映射。
|
||||
|
||||
Redis 没有表的概念将同类型的数据存放在一起,而是使用命名空间的方式来实现这一功能。键名的前面部分存储命名空间,后面部分的内容存储 ID,通常使用 : 来进行分隔。例如下面的 HASH 的键名为 article:92617,其中 article 为命名空间,ID 为 92617。
|
||||
|
||||
<div align="center"> <img src="../pics//2d078e08-3a49-46d0-b784-df780b7e4bc3.jpg"/> </div><br>
|
||||
|
||||
**2. 点赞功能**
|
||||
|
||||
当有用户为一篇文章点赞时,除了要对该文章的 votes 字段进行加 1 操作,还必须记录该用户已经对该文章进行了点赞,防止用户不断点赞。可以建立文章的已投票用户集合来进行记录。
|
||||
|
||||
为了节约内存,规定一篇文章发布满一周之后,就不能再对它进行投票,而文章的已投票集合也会被删除,可以为文章的已投票集合设置一个一周的过期时间就能实现这个规定。
|
||||
|
||||
<div align="center"> <img src="../pics//0e4c8a7f-f84c-4c4e-9544-49cd40167af8.png"/> </div><br>
|
||||
|
||||
**3. 对文章进行排序**
|
||||
|
||||
为了按发布时间和点赞数进行排序,可以建立一个文章发布时间的有序集合和一个文章点赞数的有序集合。(下图中的 score 就是这里所说的点赞数;下面所示的有序集合分值并不直接是时间和点赞数,而是根据它们间接计算出来的)
|
||||
|
||||
<div align="center"> <img src="../pics//ea5e434a-a218-44b5-aa72-4cd08991abcf.jpg"/> </div><br>
|
||||
|
||||
# 参考资料
|
||||
|
||||
1. Redis 实战
|
||||
2. Reids 设计与实现
|
||||
3. [论述 Redis 和 Memcached 的差异](http://www.cnblogs.com/loveincode/p/7411911.html)
|
||||
4. [Redis 3.0 中文版- 分片](http://wiki.jikexueyuan.com/project/redis-guide)
|
||||
5. [Redis 应用场景](http://www.scienjus.com/redis-use-case/)
|
751
notes/SQL 语法.md
751
notes/SQL 语法.md
File diff suppressed because it is too large
Load Diff
331
notes/代码可读性.md
331
notes/代码可读性.md
|
@ -1,6 +1,21 @@
|
|||
[TOC]
|
||||
<!-- GFM-TOC -->
|
||||
* [可读性的重要性](#可读性的重要性)
|
||||
* [用名字表达代码含义](#用名字表达代码含义)
|
||||
* [名字不能带来歧义](#名字不能带来歧义)
|
||||
* [良好的代码风格](#良好的代码风格)
|
||||
* [编写注释](#编写注释)
|
||||
* [如何编写注释](#如何编写注释)
|
||||
* [提高控制流的可读性](#提高控制流的可读性)
|
||||
* [拆分长表达式](#拆分长表达式)
|
||||
* [变量与可读性](#变量与可读性)
|
||||
* [抽取函数](#抽取函数)
|
||||
* [一次只做一件事](#一次只做一件事)
|
||||
* [用自然语言表述代码](#用自然语言表述代码)
|
||||
* [减少代码量](#减少代码量)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
# 可读性的重要性
|
||||
|
||||
# 可读性的重要性
|
||||
|
||||
编程有很大一部分时间是在阅读代码,不仅要阅读自己的代码,而且要阅读别人的代码。因此,可读性良好的代码能够大大提高编程效率。
|
||||
|
||||
|
@ -8,50 +23,50 @@
|
|||
|
||||
只有在核心领域为了效率才可以放弃可读性,否则可读性是第一位。
|
||||
|
||||
# 用名字表达代码含义
|
||||
# 用名字表达代码含义
|
||||
|
||||
一些比较有表达力的单词:
|
||||
|
||||
| 单词 | 可替代单词 |
|
||||
| --- | --- |
|
||||
| send | deliver、dispatch、announce、distribute、route |
|
||||
| find | search、extract、locate、recover |
|
||||
| start| launch、create、begin、open|
|
||||
| make | create、set up、build、generate、compose、add、new |
|
||||
| 单词 | 可替代单词 |
|
||||
| --- | --- |
|
||||
| send | deliver、dispatch、announce、distribute、route |
|
||||
| find | search、extract、locate、recover |
|
||||
| start| launch、create、begin、open|
|
||||
| make | create、set up、build、generate、compose、add、new |
|
||||
|
||||
使用 i、j、k 作为循环迭代器的名字过于简单,user_i、member_i 这种名字会更有表达力。因为循环层次越多,代码越难理解,有表达力的迭代器名字可读性会更高
|
||||
使用 i、j、k 作为循环迭代器的名字过于简单,user_i、member_i 这种名字会更有表达力。因为循环层次越多,代码越难理解,有表达力的迭代器名字可读性会更高
|
||||
|
||||
为名字添加形容词等信息能让名字更具有表达力,但是名字也会变长。名字长短的准则是:作用域越大,名字越长。因此只有在短作用域才能使用一些简单名字。
|
||||
|
||||
# 名字不能带来歧义
|
||||
# 名字不能带来歧义
|
||||
|
||||
起完名字要思考一下别人会对这个名字有何解读,会不会误解了原本想表达的含义。
|
||||
|
||||
用 min、max 表示数量范围;用 first、last 表示访问空间的包含范围,begin、end 表示访问空间的排除范围,即 end 不包含尾部。
|
||||
用 min、max 表示数量范围;用 first、last 表示访问空间的包含范围,begin、end 表示访问空间的排除范围,即 end 不包含尾部。
|
||||
|
||||
![](index_files/05907ab4-42c5-4b5e-9388-6617f6c97bea.jpg)
|
||||
<div align="center"> <img src="../pics//05907ab4-42c5-4b5e-9388-6617f6c97bea.jpg"/> </div><br>
|
||||
|
||||
布尔相关的命名加上 is、can、should、has 等前缀。
|
||||
布尔相关的命名加上 is、can、should、has 等前缀。
|
||||
|
||||
# 良好的代码风格
|
||||
# 良好的代码风格
|
||||
|
||||
适当的空行和缩进。
|
||||
|
||||
排列整齐的注释:
|
||||
|
||||
```
|
||||
int a = 1; // 注释
|
||||
int b = 11; // 注释
|
||||
int c = 111; // 注释
|
||||
int a = 1; // 注释
|
||||
int b = 11; // 注释
|
||||
int c = 111; // 注释
|
||||
```
|
||||
|
||||
语句顺序不能随意,比如与 html 表单相关联的变量的赋值应该和表单在 html 中的顺序一致;
|
||||
语句顺序不能随意,比如与 html 表单相关联的变量的赋值应该和表单在 html 中的顺序一致;
|
||||
|
||||
把相关的代码按块组织起来放在一起。
|
||||
|
||||
# 编写注释
|
||||
# 编写注释
|
||||
|
||||
阅读代码首先会注意到注释,如果注释没太大作用,那么就会浪费代码阅读的时间。那些能直接看出含义的代码不需要写注释,特别是并不需要为每个方法都加上注释,比如那些简单的 getter 和 setter 方法,为这些方法写注释反而让代码可读性更差。
|
||||
阅读代码首先会注意到注释,如果注释没太大作用,那么就会浪费代码阅读的时间。那些能直接看出含义的代码不需要写注释,特别是并不需要为每个方法都加上注释,比如那些简单的 getter 和 setter 方法,为这些方法写注释反而让代码可读性更差。
|
||||
|
||||
不能因为有注释就随便起个名字,而是争取起个好名字而不写注释。
|
||||
|
||||
|
@ -59,209 +74,209 @@ int c = 111; // 注释
|
|||
|
||||
注释用来提醒一些特殊情况。
|
||||
|
||||
用 TODO 等做标记:
|
||||
用 TODO 等做标记:
|
||||
|
||||
| 标记 | 用法 |
|
||||
| 标记 | 用法 |
|
||||
|---|---|
|
||||
|TODO| 待做 |
|
||||
|FIXME| 待修复 |
|
||||
|HACH| 粗糙的解决方案 |
|
||||
|XXX| 危险!这里有重要的问题 |
|
||||
|TODO| 待做 |
|
||||
|FIXME| 待修复 |
|
||||
|HACH| 粗糙的解决方案 |
|
||||
|XXX| 危险!这里有重要的问题 |
|
||||
|
||||
# 如何编写注释
|
||||
# 如何编写注释
|
||||
|
||||
尽量简洁明了:
|
||||
|
||||
```
|
||||
// The first String is student's name
|
||||
// The Second Integer is student's score
|
||||
Map<String, Integer> scoreMap = new HashMap<>();
|
||||
// The first String is student's name
|
||||
// The Second Integer is student's score
|
||||
Map<String, Integer> scoreMap = new HashMap<>();
|
||||
```
|
||||
|
||||
```
|
||||
// Student' name -> Student's score
|
||||
Map<String, Integer> scoreMap = new HashMap<>();
|
||||
// Student' name -> Student's score
|
||||
Map<String, Integer> scoreMap = new HashMap<>();
|
||||
```
|
||||
|
||||
添加测试用例来说明:
|
||||
|
||||
```
|
||||
//...
|
||||
// Example: add(1, 2), return 3
|
||||
int add(int x, int y) {
|
||||
return x + y;
|
||||
// Example: add(1, 2), return 3
|
||||
int add(int x, int y) {
|
||||
return x + y;
|
||||
}
|
||||
```
|
||||
|
||||
在很复杂的函数调用中对每个参数标上名字:
|
||||
|
||||
```
|
||||
int a = 1;
|
||||
int b = 2;
|
||||
int num = add(\* x = *\ a, \* y = *\ b);
|
||||
int a = 1;
|
||||
int b = 2;
|
||||
int num = add(\* x = *\ a, \* y = *\ b);
|
||||
```
|
||||
|
||||
使用专业名词来缩短概念上的解释,比如用设计模式名来说明代码。
|
||||
|
||||
# 提高控制流的可读性
|
||||
# 提高控制流的可读性
|
||||
|
||||
条件表达式中,左侧是变量,右侧是常数。比如下面第一个语句正确:
|
||||
|
||||
```
|
||||
if(len < 10)
|
||||
if(10 > len)
|
||||
if(len < 10)
|
||||
if(10 > len)
|
||||
```
|
||||
|
||||
if / else 条件语句,逻辑的处理顺序为:① 正逻辑;② 关键逻辑;③ 简单逻辑。
|
||||
if / else 条件语句,逻辑的处理顺序为:① 正逻辑;② 关键逻辑;③ 简单逻辑。
|
||||
```
|
||||
if(a == b) {
|
||||
// 正逻辑
|
||||
} else{
|
||||
// 反逻辑
|
||||
if(a == b) {
|
||||
// 正逻辑
|
||||
} else{
|
||||
// 反逻辑
|
||||
}
|
||||
```
|
||||
|
||||
只有在逻辑简单的情况下使用 ? : 三目运算符来使代码更紧凑,否则应该拆分成 if / else;
|
||||
只有在逻辑简单的情况下使用 ? : 三目运算符来使代码更紧凑,否则应该拆分成 if / else;
|
||||
|
||||
do / while 的条件放在后面,不够简单明了,并且会有一些迷惑的地方,最好使用 while 来代替。
|
||||
do / while 的条件放在后面,不够简单明了,并且会有一些迷惑的地方,最好使用 while 来代替。
|
||||
|
||||
如果只有一个 goto 目标,那么 goto 尚且还能接受,但是过于复杂的 goto 会让代码可读性特别差,应该避免使用 goto。
|
||||
如果只有一个 goto 目标,那么 goto 尚且还能接受,但是过于复杂的 goto 会让代码可读性特别差,应该避免使用 goto。
|
||||
|
||||
在嵌套的循环中,用一些 return 语句往往能减少嵌套的层数。
|
||||
在嵌套的循环中,用一些 return 语句往往能减少嵌套的层数。
|
||||
|
||||
# 拆分长表达式
|
||||
# 拆分长表达式
|
||||
|
||||
长表达式的可读性很差,可以引入一些解释变量从而拆分表达式:
|
||||
|
||||
```
|
||||
if line.split(':')[0].strip() == "root":
|
||||
...
|
||||
if line.split(':')[0].strip() == "root":
|
||||
...
|
||||
```
|
||||
```
|
||||
username = line.split(':')[0].strip()
|
||||
if username == "root":
|
||||
...
|
||||
username = line.split(':')[0].strip()
|
||||
if username == "root":
|
||||
...
|
||||
```
|
||||
|
||||
使用摩根定理简化一些逻辑表达式:
|
||||
|
||||
```
|
||||
if(!a && !b) {
|
||||
...
|
||||
if(!a && !b) {
|
||||
...
|
||||
}
|
||||
```
|
||||
```
|
||||
if(a || b) {
|
||||
...
|
||||
if(a || b) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
# 变量与可读性
|
||||
# 变量与可读性
|
||||
|
||||
**去除控制流变量**。在循环中通过使用 break 或者 return 可以减少控制流变量的使用。
|
||||
**去除控制流变量** 。在循环中通过使用 break 或者 return 可以减少控制流变量的使用。
|
||||
|
||||
```
|
||||
boolean done = false;
|
||||
while(/* condition */ && !done) {
|
||||
...
|
||||
if(...) {
|
||||
done = true;
|
||||
continue;
|
||||
}
|
||||
boolean done = false;
|
||||
while(/* condition */ && !done) {
|
||||
...
|
||||
if(...) {
|
||||
done = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
while(/* condition */) {
|
||||
...
|
||||
if(...) {
|
||||
break;
|
||||
}
|
||||
while(/* condition */) {
|
||||
...
|
||||
if(...) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**减小变量作用域**。作用域越小,越容易定位到变量所有使用的地方。
|
||||
**减小变量作用域** 。作用域越小,越容易定位到变量所有使用的地方。
|
||||
|
||||
JavaScript 可以用闭包减小作用域。以下代码中 submit_form 是函数变量,submitted 变量控制函数不会被提交两次。第一个实现中 submitted 是全局变量,第二个实现把 submitted 放到匿名函数中,从而限制了起作用域范围。
|
||||
JavaScript 可以用闭包减小作用域。以下代码中 submit_form 是函数变量,submitted 变量控制函数不会被提交两次。第一个实现中 submitted 是全局变量,第二个实现把 submitted 放到匿名函数中,从而限制了起作用域范围。
|
||||
|
||||
```
|
||||
submitted = false;
|
||||
var submit_form = function(form_name) {
|
||||
if(submitted) {
|
||||
return;
|
||||
}
|
||||
submitted = true;
|
||||
submitted = false;
|
||||
var submit_form = function(form_name) {
|
||||
if(submitted) {
|
||||
return;
|
||||
}
|
||||
submitted = true;
|
||||
};
|
||||
```
|
||||
|
||||
```
|
||||
var submit_form = (function() {
|
||||
var submitted = false;
|
||||
return function(form_name) {
|
||||
if(submitted) {
|
||||
return;
|
||||
}
|
||||
submitted = true;
|
||||
}
|
||||
}()); // () 使得外层匿名函数立即执行
|
||||
var submit_form = (function() {
|
||||
var submitted = false;
|
||||
return function(form_name) {
|
||||
if(submitted) {
|
||||
return;
|
||||
}
|
||||
submitted = true;
|
||||
}
|
||||
}()); // () 使得外层匿名函数立即执行
|
||||
```
|
||||
|
||||
JavaScript 中没有用 var 声明的变量都是全局变量,而全局变量很容易造成迷惑,因此应当总是用 var 来声明变量。
|
||||
JavaScript 中没有用 var 声明的变量都是全局变量,而全局变量很容易造成迷惑,因此应当总是用 var 来声明变量。
|
||||
|
||||
变量定义的位置应当离它使用的位置最近。
|
||||
|
||||
**实例解析**
|
||||
**实例解析**
|
||||
|
||||
在一个网页中有以下文本输入字段:
|
||||
|
||||
```
|
||||
<input type = "text" id = "input1" value = "a">
|
||||
<input type = "text" id = "input2" value = "b">
|
||||
<input type = "text" id = "input3" value = "">
|
||||
<input type = "text" id = "input4" value = "d">
|
||||
<input type = "text" id = "input1" value = "a">
|
||||
<input type = "text" id = "input2" value = "b">
|
||||
<input type = "text" id = "input3" value = "">
|
||||
<input type = "text" id = "input4" value = "d">
|
||||
```
|
||||
|
||||
现在要接受一个字符串并把它放到第一个空的 input 字段中,初始实现如下:
|
||||
现在要接受一个字符串并把它放到第一个空的 input 字段中,初始实现如下:
|
||||
|
||||
```
|
||||
var setFirstEmptyInput = function(new_alue) {
|
||||
var found = false;
|
||||
var i = 1;
|
||||
var elem = document.getElementById('input' + i);
|
||||
while(elem != null) {
|
||||
if(elem.value === '') {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
elem = document.getElementById('input' + i);
|
||||
}
|
||||
if(found) elem.value = new_value;
|
||||
return elem;
|
||||
var setFirstEmptyInput = function(new_alue) {
|
||||
var found = false;
|
||||
var i = 1;
|
||||
var elem = document.getElementById('input' + i);
|
||||
while(elem != null) {
|
||||
if(elem.value === '') {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
elem = document.getElementById('input' + i);
|
||||
}
|
||||
if(found) elem.value = new_value;
|
||||
return elem;
|
||||
}
|
||||
```
|
||||
|
||||
以上实现有以下问题:
|
||||
|
||||
- found 可以去除;
|
||||
- elem 作用域过大;
|
||||
- 可以用 for 循环代替 while 循环;
|
||||
- found 可以去除;
|
||||
- elem 作用域过大;
|
||||
- 可以用 for 循环代替 while 循环;
|
||||
|
||||
```
|
||||
var setFirstEmptyInput = function(new_value) {
|
||||
for(var i = 1; true; i++) {
|
||||
var elem = document.getElementById('input' + i);
|
||||
if(elem === null) {
|
||||
return null;
|
||||
}
|
||||
if(elem.value === '') {
|
||||
elem.value = new_value;
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
var setFirstEmptyInput = function(new_value) {
|
||||
for(var i = 1; true; i++) {
|
||||
var elem = document.getElementById('input' + i);
|
||||
if(elem === null) {
|
||||
return null;
|
||||
}
|
||||
if(elem.value === '') {
|
||||
elem.value = new_value;
|
||||
return elem;
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# 抽取函数
|
||||
# 抽取函数
|
||||
|
||||
工程学就是把大问题拆分成小问题再把这些问题的解决方案放回一起。
|
||||
|
||||
|
@ -270,38 +285,38 @@ var setFirstEmptyInput = function(new_value) {
|
|||
介绍性的代码:
|
||||
|
||||
```
|
||||
int findClostElement(int[] arr) {
|
||||
int clostIdx;
|
||||
int clostDist = Interger.MAX_VALUE;
|
||||
for(int i = 0; i < arr.length; i++) {
|
||||
int x = ...;
|
||||
int y = ...;
|
||||
int z = ...;
|
||||
int value = x * y * z;
|
||||
int dist = Math.sqrt(Math.pow(value, 2), Math.pow(arr[i], 2));
|
||||
if(dist < clostDist) {
|
||||
clostIdx = i;
|
||||
clostDist = value;
|
||||
}
|
||||
}
|
||||
return clostIdx;
|
||||
int findClostElement(int[] arr) {
|
||||
int clostIdx;
|
||||
int clostDist = Interger.MAX_VALUE;
|
||||
for(int i = 0; i < arr.length; i++) {
|
||||
int x = ...;
|
||||
int y = ...;
|
||||
int z = ...;
|
||||
int value = x * y * z;
|
||||
int dist = Math.sqrt(Math.pow(value, 2), Math.pow(arr[i], 2));
|
||||
if(dist < clostDist) {
|
||||
clostIdx = i;
|
||||
clostDist = value;
|
||||
}
|
||||
}
|
||||
return clostIdx;
|
||||
}
|
||||
```
|
||||
|
||||
以上代码中循环部分主要计算距离,这部分不属于代码高层次目标,高层次目标是寻找最小距离的值,因此可以把这部分代替提取到独立的函数中。这样做也带来一个额外的好处有:可以单独进行测试、可以快速找到程序错误并修改。
|
||||
|
||||
```
|
||||
public int findClostElement(int[] arr) {
|
||||
int clostIdx;
|
||||
int clostDist = Interger.MAX_VALUE;
|
||||
for(int i = 0; i < arr.length; i++) {
|
||||
int dist = computDist(arr, i);
|
||||
if(dist < clostDist) {
|
||||
clostIdx = i;
|
||||
clostDist = value;
|
||||
}
|
||||
}
|
||||
return clostIdx;
|
||||
public int findClostElement(int[] arr) {
|
||||
int clostIdx;
|
||||
int clostDist = Interger.MAX_VALUE;
|
||||
for(int i = 0; i < arr.length; i++) {
|
||||
int dist = computDist(arr, i);
|
||||
if(dist < clostDist) {
|
||||
clostIdx = i;
|
||||
clostDist = value;
|
||||
}
|
||||
}
|
||||
return clostIdx;
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -309,18 +324,18 @@ public int findClostElement(int[] arr) {
|
|||
|
||||
函数抽取也用于减小代码的冗余。
|
||||
|
||||
# 一次只做一件事
|
||||
# 一次只做一件事
|
||||
|
||||
只做一件事的代码很容易让人知道其要做的事;
|
||||
|
||||
基本流程:列出代码所做的所有任务;把每个任务拆分到不同的函数,或者不同的段落。
|
||||
|
||||
# 用自然语言表述代码
|
||||
# 用自然语言表述代码
|
||||
|
||||
先用自然语言书写代码逻辑,也就是伪代码,然后再写代码,这样代码逻辑会更清晰。
|
||||
|
||||
# 减少代码量
|
||||
# 减少代码量
|
||||
|
||||
不要过度设计,编码过程会有很多变化,过度设计的内容到最后往往是无用的。
|
||||
|
||||
多用标准库实现。
|
||||
多用标准库实现。
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
# Google Java Style Guide
|
||||
# Google Java Style Guide
|
||||
|
||||
- http://www.hawstein.com/posts/google-java-style.html
|
||||
- http://google.github.io/styleguide/javaguide.html
|
||||
- http://www.hawstein.com/posts/google-java-style.html
|
||||
- http://google.github.io/styleguide/javaguide.html
|
||||
|
||||
# Google C++ Style Guide
|
||||
# Google C++ Style Guide
|
||||
|
||||
- http://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/contents/
|
||||
- http://google.github.io/styleguide/cppguide.html
|
||||
- http://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/contents/
|
||||
- http://google.github.io/styleguide/cppguide.html
|
||||
|
||||
# Google Python Style Guide
|
||||
# Google Python Style Guide
|
||||
|
||||
- http://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/contents/
|
||||
- http://google.github.io/styleguide/pyguide.html
|
||||
- http://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/contents/
|
||||
- http://google.github.io/styleguide/pyguide.html
|
||||
|
|
2985
notes/剑指 offer 题解.md
2985
notes/剑指 offer 题解.md
File diff suppressed because it is too large
Load Diff
286
notes/数据库系统原理.md
286
notes/数据库系统原理.md
|
@ -1,104 +1,154 @@
|
|||
[TOC]
|
||||
<!-- GFM-TOC -->
|
||||
* [事务四大特性](#事务四大特性)
|
||||
* [1. 原子性](#1-原子性)
|
||||
* [2. 一致性](#2-一致性)
|
||||
* [3. 隔离性](#3-隔离性)
|
||||
* [4. 持久性](#4-持久性)
|
||||
* [数据不一致](#数据不一致)
|
||||
* [1. 丢失修改](#1-丢失修改)
|
||||
* [2. 读脏数据](#2-读脏数据)
|
||||
* [3. 不可重复读](#3-不可重复读)
|
||||
* [隔离级别](#隔离级别)
|
||||
* [1. 未提交读(READ UNCOMMITTED)](#1-未提交读read-uncommitted)
|
||||
* [2. 提交读(READ COMMITTED)](#2-提交读read-committed)
|
||||
* [3. 可重复读(REPEATABLE READ)](#3-可重复读repeatable-read)
|
||||
* [4. 可串行化(SERIALIXABLE)](#4-可串行化serialixable)
|
||||
* [可串行化调度](#可串行化调度)
|
||||
* [封锁类型](#封锁类型)
|
||||
* [封锁粒度](#封锁粒度)
|
||||
* [封锁协议](#封锁协议)
|
||||
* [三级封锁协议](#三级封锁协议)
|
||||
* [两段锁协议](#两段锁协议)
|
||||
* [乐观锁和悲观锁](#乐观锁和悲观锁)
|
||||
* [悲观锁](#悲观锁)
|
||||
* [乐观锁](#乐观锁)
|
||||
* [MySQL 隐式和显示锁定](#mysql-隐式和显示锁定)
|
||||
* [范式](#范式)
|
||||
* [第一范式 (1NF)](#第一范式-1nf)
|
||||
* [第二范式 (2NF)](#第二范式-2nf)
|
||||
* [第三范式 (3NF)](#第三范式-3nf)
|
||||
* [BC 范式(BCNF)](#bc-范式bcnf)
|
||||
* [约束](#约束)
|
||||
* [键码](#键码)
|
||||
* [单值约束](#单值约束)
|
||||
* [引用完整性约束](#引用完整性约束)
|
||||
* [域约束](#域约束)
|
||||
* [一般约束](#一般约束)
|
||||
* [数据库的三层模式和两层映像](#数据库的三层模式和两层映像)
|
||||
* [外模式](#外模式)
|
||||
* [模式](#模式)
|
||||
* [内模式](#内模式)
|
||||
* [外模式/模式映像](#外模式模式映像)
|
||||
* [模式/内模式映像](#模式内模式映像)
|
||||
* [ER 图](#er-图)
|
||||
* [实体的三种联系](#实体的三种联系)
|
||||
* [表示出现多次的关系](#表示出现多次的关系)
|
||||
* [联系的多向性](#联系的多向性)
|
||||
* [表示子类](#表示子类)
|
||||
* [一些概念](#一些概念)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
# 事务四大特性
|
||||
|
||||
## 1. 原子性
|
||||
# 事务四大特性
|
||||
|
||||
## 1. 原子性
|
||||
|
||||
事务被视为不可分割的最小单元,要么全部提交成功,要么全部失败回滚。
|
||||
|
||||
## 2. 一致性
|
||||
## 2. 一致性
|
||||
|
||||
事务执行前后都保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的。
|
||||
|
||||
## 3. 隔离性
|
||||
## 3. 隔离性
|
||||
|
||||
一个事务所做的修改在最终提交以前,对其它事务是不可见的。也可以理解为多个事务单独执行,互不影响。
|
||||
|
||||
## 4. 持久性
|
||||
## 4. 持久性
|
||||
|
||||
一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。持久性通过数据库备份和恢复来保证。
|
||||
|
||||
# 数据不一致
|
||||
# 数据不一致
|
||||
|
||||
## 1. 丢失修改
|
||||
## 1. 丢失修改
|
||||
|
||||
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> 的修改。
|
||||
|
||||
## 2. 读脏数据
|
||||
## 2. 读脏数据
|
||||
|
||||
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> 读取的数据是脏数据。
|
||||
|
||||
## 3. 不可重复读
|
||||
## 3. 不可重复读
|
||||
|
||||
T<sub>1</sub> 读入某个数据,T<sub>2</sub> 对该数据做了修改,如果 T<sub>1</sub> 再读这个数据,该数据已经改变,和最开始读入的是不一样的。
|
||||
T<sub>1</sub> 读入某个数据,T<sub>2</sub> 对该数据做了修改,如果 T<sub>1</sub> 再读这个数据,该数据已经改变,和最开始读入的是不一样的。
|
||||
|
||||
# 隔离级别
|
||||
# 隔离级别
|
||||
|
||||
数据库管理系统需要防止出现数据不一致问题,并且有多种级别可以实现,这些级别称为隔离级别。
|
||||
|
||||
## 1. 未提交读(READ UNCOMMITTED)
|
||||
## 1. 未提交读(READ UNCOMMITTED)
|
||||
|
||||
事务中的修改,即使没有提交,对其它事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读。
|
||||
|
||||
## 2. 提交读(READ COMMITTED)
|
||||
## 2. 提交读(READ COMMITTED)
|
||||
|
||||
一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所在的修改在提交之前对其它事务是不可见的。这个级别有时候也叫做不可重复读,因为两次执行同样的查询,可能会得到不一样的结果。
|
||||
|
||||
## 3. 可重复读(REPEATABLE READ)
|
||||
## 3. 可重复读(REPEATABLE READ)
|
||||
|
||||
解决了脏读的问题,保证在同一个事务中多次读取同样的记录结果是一致的。
|
||||
|
||||
但是会出现幻读的问题,所谓幻读,指的是某个事务在读取某个范围内的记录时,另一个事务会在范围内插入数据,当之前的事务再次读取该范围的记录时,会产生幻行。
|
||||
|
||||
## 4. 可串行化(SERIALIXABLE)
|
||||
## 4. 可串行化(SERIALIXABLE)
|
||||
|
||||
强制事务串行执行,避免幻读。
|
||||
|
||||
# 可串行化调度
|
||||
# 可串行化调度
|
||||
|
||||
如果并行的事务的执行结果和某一个串行的方式执行的结果一样,那么可以认为结果是正确的。
|
||||
|
||||
# 封锁类型
|
||||
# 封锁类型
|
||||
|
||||
排它锁 (X 锁)和共享锁 (S 锁),又称写锁和读锁。
|
||||
排它锁 (X 锁)和共享锁 (S 锁),又称写锁和读锁。
|
||||
|
||||
- 一个事务对数据对象 A 加了 X 锁,就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何锁;
|
||||
- 一个事务对数据对象 A 加了 X 锁,就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何锁;
|
||||
|
||||
- 一个事务对数据对象 A 加了 S 锁,可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁,但是不能加 X 锁。
|
||||
- 一个事务对数据对象 A 加了 S 锁,可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁,但是不能加 X 锁。
|
||||
|
||||
# 封锁粒度
|
||||
# 封锁粒度
|
||||
|
||||
应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。
|
||||
|
||||
但是加锁需要消耗资源,锁的各种操作,包括获取锁,检查所是否已经解除、释放锁,都会增加系统开销。因此封锁粒度越小,系统开销就越大。需要在锁开销以及数据安全性之间做一个权衡。
|
||||
|
||||
MySQL 中主要提供了两种封锁粒度:行级锁以及表级锁。
|
||||
MySQL 中主要提供了两种封锁粒度:行级锁以及表级锁。
|
||||
|
||||
# 封锁协议
|
||||
# 封锁协议
|
||||
|
||||
## 三级封锁协议
|
||||
## 三级封锁协议
|
||||
|
||||
![](index_files/785806ed-c46b-4dca-b756-cebe7bf8ac3a.jpg)
|
||||
<div align="center"> <img src="../pics//785806ed-c46b-4dca-b756-cebe7bf8ac3a.jpg"/> </div><br>
|
||||
|
||||
**一级封锁协议**
|
||||
**一级封锁协议**
|
||||
|
||||
事务 T 要修改数据 A 时必须加 X 锁,直到事务结束才释放锁。
|
||||
事务 T 要修改数据 A 时必须加 X 锁,直到事务结束才释放锁。
|
||||
|
||||
可以解决丢失修改问题;
|
||||
|
||||
**二级封锁协议**
|
||||
**二级封锁协议**
|
||||
|
||||
在一级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。
|
||||
在一级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。
|
||||
|
||||
可以解决读脏数据问题,因为如果一个事务在对数据 A 进行修改,根据 1 级封锁协议,会加 X 锁,那么就不能再加 S 锁了,也就是不会读入数据。
|
||||
可以解决读脏数据问题,因为如果一个事务在对数据 A 进行修改,根据 1 级封锁协议,会加 X 锁,那么就不能再加 S 锁了,也就是不会读入数据。
|
||||
|
||||
**三级封锁协议**
|
||||
**三级封锁协议**
|
||||
|
||||
在二级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁。
|
||||
在二级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁。
|
||||
|
||||
可以解决不可重复读的问题,因为读 A 时,其它事务不能对 A 加 X 锁,从而避免了在读的期间数据发生改变。
|
||||
可以解决不可重复读的问题,因为读 A 时,其它事务不能对 A 加 X 锁,从而避免了在读的期间数据发生改变。
|
||||
|
||||
## 两段锁协议
|
||||
## 两段锁协议
|
||||
|
||||
加锁和解锁分为两个阶段进行。两段锁是并行事务可串行化的充分条件,但不是必要条件。
|
||||
|
||||
|
@ -106,210 +156,210 @@ MySQL 中主要提供了两种封锁粒度:行级锁以及表级锁。
|
|||
lock-x(A)...lock-s(B)...lock-s(c)...unlock(A)...unlock(C)...unlock(B)
|
||||
```
|
||||
|
||||
# 乐观锁和悲观锁
|
||||
# 乐观锁和悲观锁
|
||||
|
||||
## 悲观锁
|
||||
## 悲观锁
|
||||
|
||||
假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。
|
||||
|
||||
Java synchronized 就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被阻塞。
|
||||
Java synchronized 就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被阻塞。
|
||||
|
||||
## 乐观锁
|
||||
## 乐观锁
|
||||
|
||||
假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
|
||||
|
||||
Java JUC 中的 Atomic 包就是乐观锁的一种实现,AtomicInteger 通过 CAS(Compare And Set)操作实现线程安全的自增操作。
|
||||
Java JUC 中的 Atomic 包就是乐观锁的一种实现,AtomicInteger 通过 CAS(Compare And Set)操作实现线程安全的自增操作。
|
||||
|
||||
乐观锁有两种实现方式,数据版本和时间戳。它们都需要在数据库表中增加一个字段,使用这个字段来判断数据是否过期。例如,数据版本实现方式中,需要在数据库表中增加一个数字类型的 version 字段,当读取数据时,将 version 字段的值一同读出。随后数据每更新一次,对此 version 值加 1。当提交更新的时候,判断读出的 version 和数据库表中的 version 是否一致,如果一致,则予以更新;否则认为是过期数据。
|
||||
乐观锁有两种实现方式,数据版本和时间戳。它们都需要在数据库表中增加一个字段,使用这个字段来判断数据是否过期。例如,数据版本实现方式中,需要在数据库表中增加一个数字类型的 version 字段,当读取数据时,将 version 字段的值一同读出。随后数据每更新一次,对此 version 值加 1。当提交更新的时候,判断读出的 version 和数据库表中的 version 是否一致,如果一致,则予以更新;否则认为是过期数据。
|
||||
|
||||
## MySQL 隐式和显示锁定
|
||||
## MySQL 隐式和显示锁定
|
||||
|
||||
MySQL InnoDB 采用的是两阶段锁协议。在事务执行过程中,随时都可以执行锁定,锁只有在执行 COMMIT 或者 ROLLBACK 的时候才会释放,并且所有的锁是在同一时刻被释放。前面描述的锁定都是隐式锁定,InnoDB 会根据事务隔离级别在需要的时候自动加锁。
|
||||
MySQL InnoDB 采用的是两阶段锁协议。在事务执行过程中,随时都可以执行锁定,锁只有在执行 COMMIT 或者 ROLLBACK 的时候才会释放,并且所有的锁是在同一时刻被释放。前面描述的锁定都是隐式锁定,InnoDB 会根据事务隔离级别在需要的时候自动加锁。
|
||||
|
||||
另外,InnoDB 也支持通过特定的语句进行显示锁定,这些语句不属于 SQL 规范:
|
||||
另外,InnoDB 也支持通过特定的语句进行显示锁定,这些语句不属于 SQL 规范:
|
||||
|
||||
- SELECT ... LOCK IN SHARE MODE
|
||||
- SELECT ... FOR UPDATE
|
||||
- SELECT ... LOCK IN SHARE MODE
|
||||
- SELECT ... FOR UPDATE
|
||||
|
||||
# 范式
|
||||
# 范式
|
||||
|
||||
记 A->B 表示 A 函数决定 B,也可以说 B 函数依赖于 A。
|
||||
记 A->B 表示 A 函数决定 B,也可以说 B 函数依赖于 A。
|
||||
|
||||
如果 {A1,A2,... ,An} 是关系的一个或多个属性的集合,该集合决定了关系的其它所有属性并且是最小的,那么该集合就称为键码。
|
||||
如果 {A1,A2,... ,An} 是关系的一个或多个属性的集合,该集合决定了关系的其它所有属性并且是最小的,那么该集合就称为键码。
|
||||
|
||||
对于 W->A,如果能找到 W 的真子集 W',使得 W'-> A,那么 W->A 就是部分函数依赖,否则就是完全函数依赖;
|
||||
对于 W->A,如果能找到 W 的真子集 W',使得 W'-> A,那么 W->A 就是部分函数依赖,否则就是完全函数依赖;
|
||||
|
||||
以下关系中,Sno 表示学号,Sname 表示学生姓名,Sdept 表示学院,Cname 表示课程名,Mname 表示院长姓名。函数依赖为 {Sno, Cname} -> {Sname, Sdept, Mname}。
|
||||
以下关系中,Sno 表示学号,Sname 表示学生姓名,Sdept 表示学院,Cname 表示课程名,Mname 表示院长姓名。函数依赖为 {Sno, Cname} -> {Sname, Sdept, Mname}。
|
||||
|
||||
![](index_files/b6a678c0-c875-4038-afba-301846620786.jpg)
|
||||
<div align="center"> <img src="../pics//b6a678c0-c875-4038-afba-301846620786.jpg"/> </div><br>
|
||||
|
||||
不符合范式的关系,会产生很多异常。主要有以下四种异常:
|
||||
|
||||
1. 冗余数据
|
||||
2. 修改异常
|
||||
3. 删除异常
|
||||
4. 插入异常,比如如果新插入一个学生的信息,而这个学生还没选课,那么就无法插入该学生。
|
||||
1. 冗余数据
|
||||
2. 修改异常
|
||||
3. 删除异常
|
||||
4. 插入异常,比如如果新插入一个学生的信息,而这个学生还没选课,那么就无法插入该学生。
|
||||
|
||||
关系数据库的范式理论就是是为了解决这四种异常。
|
||||
|
||||
高级别范式的依赖基于低级别的范式。
|
||||
|
||||
## 第一范式 (1NF)
|
||||
## 第一范式 (1NF)
|
||||
|
||||
属性不可分。
|
||||
|
||||
## 第二范式 (2NF)
|
||||
## 第二范式 (2NF)
|
||||
|
||||
每个非主属性完全函数依赖于键码。
|
||||
|
||||
可以通过分解来满足。
|
||||
|
||||
**分解前**
|
||||
**分解前**
|
||||
|
||||
$$ S(Sno, Cname, Sname, Sdept, Mname) $$
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?S(Sno,Cname,Sname,Sdept,Mname)"/></div> <br>
|
||||
|
||||
$$ Sno, Cname -> Sname, Sdept, Mname $$
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?Sno,Cname->Sname,Sdept,Mname"/></div> <br>
|
||||
|
||||
**分解后**
|
||||
**分解后**
|
||||
|
||||
$$ S1(Sno, Sname, Sdept, Mname)$$
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?S1(Sno,Sname,Sdept,Mname)"/></div> <br>
|
||||
|
||||
$$ Sno -> Sname, Sdept, Mname $$
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?Sno->Sname,Sdept,Mname"/></div> <br>
|
||||
|
||||
$$ Sdept -> Mname $$
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?Sdept->Mname"/></div> <br>
|
||||
|
||||
<center> ![](index_files/8ef22836-8800-4765-b4b8-ade80096b323.jpg) </center>
|
||||
<div align="center"> <img src="../pics//8ef22836-8800-4765-b4b8-ade80096b323.jpg"/> </div><br>
|
||||
|
||||
$$ S2(Sno, Cname, Grade) $$
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?S2(Sno,Cname,Grade)"/></div> <br>
|
||||
|
||||
$$ Sno, Cname -> Grade $$
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?Sno,Cname->Grade"/></div> <br>
|
||||
|
||||
<center> ![](index_files/b0748916-1acd-4138-b24c-69326cb452fe.jpg) </center>
|
||||
<div align="center"> <img src="../pics//b0748916-1acd-4138-b24c-69326cb452fe.jpg"/> </div><br>
|
||||
|
||||
## 第三范式 (3NF)
|
||||
## 第三范式 (3NF)
|
||||
|
||||
非主属性不传递依赖于键码。
|
||||
|
||||
上述 S1 存在传递依赖,Mname 依赖于 Sdept,而 Sdept 又依赖于 Sno,可以继续分解。
|
||||
上述 S1 存在传递依赖,Mname 依赖于 Sdept,而 Sdept 又依赖于 Sno,可以继续分解。
|
||||
|
||||
<center> ![](index_files/923896c1-937e-4a38-b8a6-cec3040b4e2a.jpg) </center>
|
||||
<div align="center"> <img src="../pics//923896c1-937e-4a38-b8a6-cec3040b4e2a.jpg"/> </div><br>
|
||||
|
||||
## BC 范式(BCNF)
|
||||
## BC 范式(BCNF)
|
||||
|
||||
所有属性不传递依赖于键码。
|
||||
|
||||
关系模式 STC(Sname, Tname, Cname, Grade),其中四个属性分别为学生姓名、教师姓名、课程名和成绩。有以下函数依赖:
|
||||
关系模式 STC(Sname, Tname, Cname, Grade),其中四个属性分别为学生姓名、教师姓名、课程名和成绩。有以下函数依赖:
|
||||
|
||||
$$ Sname, Cname -> Tname $$
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?Sname,Cname->Tname"/></div> <br>
|
||||
|
||||
$$ Sname, Cname -> Grade $$
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?Sname,Cname->Grade"/></div> <br>
|
||||
|
||||
$$ Sname, Tname -> Cname $$
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?Sname,Tname->Cname"/></div> <br>
|
||||
|
||||
$$ Sname, Tname -> Grade $$
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?Sname,Tname->Grade"/></div> <br>
|
||||
|
||||
$$ Tname -> Cname $$
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?Tname->Cname"/></div> <br>
|
||||
|
||||
分解成 SC(Sname, Cname, Grade) 和 ST(Sname, Tname),对于 ST,属性之间是多对多关系,无函数依赖。
|
||||
分解成 SC(Sname, Cname, Grade) 和 ST(Sname, Tname),对于 ST,属性之间是多对多关系,无函数依赖。
|
||||
|
||||
# 约束
|
||||
# 约束
|
||||
|
||||
## 键码
|
||||
## 键码
|
||||
|
||||
用于唯一表示一个实体。
|
||||
|
||||
键码可以由多个属性构成,每个构成键码的属性称为码。
|
||||
|
||||
## 单值约束
|
||||
## 单值约束
|
||||
|
||||
某个属性的值是唯一的。
|
||||
|
||||
## 引用完整性约束
|
||||
## 引用完整性约束
|
||||
|
||||
一个实体的属性引用的值在另一个实体的某个属性中存在。
|
||||
|
||||
## 域约束
|
||||
## 域约束
|
||||
|
||||
某个属性的值在特定范围之内。
|
||||
|
||||
## 一般约束
|
||||
## 一般约束
|
||||
|
||||
一般性约束,比如大小约束,数量约束。
|
||||
|
||||
# 数据库的三层模式和两层映像
|
||||
# 数据库的三层模式和两层映像
|
||||
|
||||
- 外模式:局部逻辑结构
|
||||
- 模式:全局逻辑结构
|
||||
- 内模式:物理结构
|
||||
- 外模式:局部逻辑结构
|
||||
- 模式:全局逻辑结构
|
||||
- 内模式:物理结构
|
||||
|
||||
## 外模式
|
||||
## 外模式
|
||||
|
||||
又称用户模式,是用户和数据库系统的接口,特定的用户只能访问数据库系统提供给他的外模式中的数据。例如不同的用户创建了不同数据库,那么一个用户只能访问他有权限访问的数据库。
|
||||
|
||||
一个数据库可以有多个外模式,一个用户只能有一个外模式,但是一个外模式可以给多个用户使用。
|
||||
|
||||
## 模式
|
||||
## 模式
|
||||
|
||||
可以分为概念模式和逻辑模式,概念模式可以用概念-关系来描述;逻辑模式使用特定的数据模式(比如关系模型)来描述数据的逻辑结构,这种逻辑结构包括数据的组成、数据项的名称、类型、取值范围。不仅如此,逻辑模式还要描述数据之间的关系,数据的完整性与安全性要求。
|
||||
|
||||
## 内模式
|
||||
## 内模式
|
||||
|
||||
又称为存储模式,描述记录的存储方式,例如索引的组织方式、数据是否压缩以及是否加密等等。
|
||||
|
||||
## 外模式/模式映像
|
||||
## 外模式/模式映像
|
||||
|
||||
把外模式的局部逻辑结构和模式的全局逻辑结构联系起来。该映像可以保证数据和应用程序的逻辑独立性。
|
||||
|
||||
## 模式/内模式映像
|
||||
## 模式/内模式映像
|
||||
|
||||
把模式的全局逻辑结构和内模式的物理结构联系起来,该映像可以保证数据和应用程序的物理独立性。
|
||||
|
||||
# ER 图
|
||||
# ER 图
|
||||
|
||||
Entity-Relationship,有三个组成部分:实体、属性、联系。
|
||||
|
||||
## 实体的三种联系
|
||||
## 实体的三种联系
|
||||
|
||||
联系包含 1 对 1,1 对多,多对多三种。
|
||||
联系包含 1 对 1,1 对多,多对多三种。
|
||||
|
||||
如果 A 到 B 是 1 对多关系,那么画个带箭头的线段指向 B;如果是 1 对 1,画两个带箭头的线段;如果是多对多,画两个不带箭头的线段。下图的 Course 和 Student 是 1 对多的关系。
|
||||
如果 A 到 B 是 1 对多关系,那么画个带箭头的线段指向 B;如果是 1 对 1,画两个带箭头的线段;如果是多对多,画两个不带箭头的线段。下图的 Course 和 Student 是 1 对多的关系。
|
||||
|
||||
![](index_files/292b4a35-4507-4256-84ff-c218f108ee31.jpg)
|
||||
<div align="center"> <img src="../pics//292b4a35-4507-4256-84ff-c218f108ee31.jpg"/> </div><br>
|
||||
|
||||
## 表示出现多次的关系
|
||||
## 表示出现多次的关系
|
||||
|
||||
一个实体在联系出现几次,就要用几条线连接。下图表示一个课程的先修关系,先修关系出现两个 Course 实体,第一个是先修课程,后一个是后修课程,因此需要用两条线来表示这种关系。
|
||||
一个实体在联系出现几次,就要用几条线连接。下图表示一个课程的先修关系,先修关系出现两个 Course 实体,第一个是先修课程,后一个是后修课程,因此需要用两条线来表示这种关系。
|
||||
|
||||
![](index_files/8b798007-e0fb-420c-b981-ead215692417.jpg)
|
||||
<div align="center"> <img src="../pics//8b798007-e0fb-420c-b981-ead215692417.jpg"/> </div><br>
|
||||
|
||||
## 联系的多向性
|
||||
## 联系的多向性
|
||||
|
||||
虽然老师可以开设多门课,并且可以教授多名学生,但是对于特定的学生和课程,只有一个老师教授,这就构成了一个三元联系。
|
||||
|
||||
![](index_files/423f2a40-bee1-488e-b460-8e76c48ee560.png)
|
||||
<div align="center"> <img src="../pics//423f2a40-bee1-488e-b460-8e76c48ee560.png"/> </div><br>
|
||||
|
||||
一般只使用二元联系,可以把多元关系转换为二元关系。
|
||||
|
||||
![](index_files/de9b9ea0-1327-4865-93e5-6f805c48bc9e.png)
|
||||
<div align="center"> <img src="../pics//de9b9ea0-1327-4865-93e5-6f805c48bc9e.png"/> </div><br>
|
||||
|
||||
## 表示子类
|
||||
## 表示子类
|
||||
|
||||
用 IS-A 联系来表示子类,具体做法是用一个三角形和两条线来连接类和子类。与子类有关的属性和联系都连到子类上,而与父类和子类都有关的连到父类上。
|
||||
用 IS-A 联系来表示子类,具体做法是用一个三角形和两条线来连接类和子类。与子类有关的属性和联系都连到子类上,而与父类和子类都有关的连到父类上。
|
||||
|
||||
![](index_files/7ec9d619-fa60-4a2b-95aa-bf1a62aad408.jpg)
|
||||
<div align="center"> <img src="../pics//7ec9d619-fa60-4a2b-95aa-bf1a62aad408.jpg"/> </div><br>
|
||||
|
||||
# 一些概念
|
||||
# 一些概念
|
||||
|
||||
**数据模型**
|
||||
**数据模型**
|
||||
|
||||
由数据结构、数据操作和完整性三个要素组成。
|
||||
|
||||
**数据库系统**
|
||||
**数据库系统**
|
||||
|
||||
数据库系统包含所有与数据库相关的内容,包括数据库、数据库管理系统、应用程序以及数据库管理员和用户,还包括相关的硬件和软件。
|
||||
|
||||
# 参考资料
|
||||
# 参考资料
|
||||
|
||||
- 史嘉权. 数据库系统概论[M]. 清华大学出版社有限公司, 2006.
|
||||
- 施瓦茨. 高性能MYSQL(第3版)[M]. 电子工业出版社, 2013.
|
||||
- [MySQL 乐观锁与悲观锁 ](https://www.jianshu.com/p/f5ff017db62a)
|
||||
- 史嘉权. 数据库系统概论[M]. 清华大学出版社有限公司, 2006.
|
||||
- 施瓦茨. 高性能MYSQL(第3版)[M]. 电子工业出版社, 2013.
|
||||
- [MySQL 乐观锁与悲观锁 ](https://www.jianshu.com/p/f5ff017db62a)
|
||||
|
|
409
notes/正则表达式.md
Normal file
409
notes/正则表达式.md
Normal file
|
@ -0,0 +1,409 @@
|
|||
<!-- GFM-TOC -->
|
||||
* [概述](#概述)
|
||||
* [匹配单个字符](#匹配单个字符)
|
||||
* [匹配一组字符](#匹配一组字符)
|
||||
* [使用元字符](#使用元字符)
|
||||
* [匹配空白字符](#匹配空白字符)
|
||||
* [匹配特定的字符类别](#匹配特定的字符类别)
|
||||
* [使用 POSIX 字符类](#使用-posix-字符类)
|
||||
* [重复匹配](#重复匹配)
|
||||
* [位置匹配](#位置匹配)
|
||||
* [单词边界](#单词边界)
|
||||
* [字符串边界](#字符串边界)
|
||||
* [使用子表达式](#使用子表达式)
|
||||
* [回溯引用](#回溯引用)
|
||||
* [替换](#替换)
|
||||
* [大小写转换](#大小写转换)
|
||||
* [前后查找](#前后查找)
|
||||
* [嵌入条件](#嵌入条件)
|
||||
* [回溯引用条件](#回溯引用条件)
|
||||
* [前后查找条件](#前后查找条件)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
|
||||
# 概述
|
||||
|
||||
正则表达式用于文本内容的查找和替换。
|
||||
|
||||
正则表达式内置于其它语言或者软件产品中,它本身不是一种语言或者软件。
|
||||
|
||||
一个问题往往可以用多种正则表达式方案来解决。
|
||||
|
||||
[正则表达式在线工具](http://tool.chinaz.com/regex)
|
||||
|
||||
# 匹配单个字符
|
||||
|
||||
正则表达式一般是区分大小写的,但是也有些实现是不区分。
|
||||
|
||||
**.** 可以用来匹配任何的单个字符,但是在绝大多数实现里面,不能匹配换行符;
|
||||
|
||||
**\\** 是元字符,表示它有特殊的含义,而不是字符本身的含义。如果需要匹配 . ,那么要用 \ 进行转义,即在 . 前面加上 \ 。
|
||||
|
||||
**正则表达式**
|
||||
|
||||
```
|
||||
nam.
|
||||
```
|
||||
|
||||
**匹配结果**
|
||||
|
||||
My **name** is Zheng.
|
||||
|
||||
# 匹配一组字符
|
||||
|
||||
**[ ]** 定义一个字符集合;
|
||||
|
||||
0-9、a-z 定义了一个字符区间,区间使用 ASCII 码来确定。字符区间只能用在 [ ] 之间,因此 **-** 元字符只有在 [ ] 之间才是元字符,在 [ ] 之外就是一个普通字符;
|
||||
|
||||
**^** 是取非操作,必须在 [ ] 字符集合中使用;
|
||||
|
||||
**应用**
|
||||
|
||||
匹配以 abc 为开头,并且最后一个字母不为数字的字符串:
|
||||
|
||||
**正则表达式**
|
||||
|
||||
```
|
||||
abc[^0-9]
|
||||
```
|
||||
|
||||
**匹配结果**
|
||||
|
||||
1. **abcd**
|
||||
2. abc1
|
||||
3. abc2
|
||||
|
||||
# 使用元字符
|
||||
|
||||
## 匹配空白字符
|
||||
|
||||
| 元字符 | 说明 |
|
||||
| ------------ | ------------ |
|
||||
| [\b] | 回退(删除)一个字符 |
|
||||
| \f | 换页符 |
|
||||
| \n | 换行符 |
|
||||
| \r | 回车符 |
|
||||
| \t | 制表符 |
|
||||
| \v | 垂直制表符 |
|
||||
|
||||
\r\n 是 Windows 中的文本行结束标签,在 Unix/Linux 则是 \n ;\r\n\r\n 可以匹配 Windows 下的空白行,因为它将匹配两个连续的行尾标签,而这正是两条记录之间的空白行;
|
||||
|
||||
. 是元字符,前提是没有对它们进行转义; f 和 n 也是元字符,但是前提是对他们进行了转义。
|
||||
|
||||
## 匹配特定的字符类别
|
||||
|
||||
**1. 数字元字符**
|
||||
|
||||
| 元字符 | 说明 |
|
||||
| ------------ | ------------ |
|
||||
| \d | 数字字符,等价于 [0-9] |
|
||||
| \D | 非数字字符,等价于 [^0-9] |
|
||||
|
||||
**2. 字母数字元字符**
|
||||
|
||||
| 元字符 | 说明 |
|
||||
| ------------ | ------------ |
|
||||
| \w | 大小写字母,下划线和数字,等价于 [a-zA-Z0-9\_] |
|
||||
| \W | 对 \w 取非 |
|
||||
|
||||
**3. 空白字符元字符**
|
||||
|
||||
| 元字符 | 说明 |
|
||||
| ------------ | ------------ |
|
||||
| \s | 任何一个空白字符,等价于 [\f\n\r\t\v] |
|
||||
| \S | 对 \s 取非 |
|
||||
|
||||
\x 匹配十六进制字符,\0 匹配八进制,例如 \x0A 对应 ASCII 字符 10 ,等价于 \n,也就是它会匹配 \n 。
|
||||
|
||||
## 使用 POSIX 字符类
|
||||
|
||||
| 字符类 | 说明 |
|
||||
| --- | --- |
|
||||
| [:alnum:] | 字母数字字符 |
|
||||
| [:alpha:] | 字母字符 |
|
||||
| [:cntrl:] | 控制字符 |
|
||||
| [:digit:] | 数字字符 |
|
||||
| [:graph:] | 非空白字符 ( 非空格、控制字符等 ) |
|
||||
| [:lower:] | 小写字母 |
|
||||
| [:print:] | 与 [:graph:] 相似,但是包含空格字符 |
|
||||
| [:punct:] | 标点字符 |
|
||||
| [:space:] | 所有的空白字符 ( 换行符、空格、制表符 ) |
|
||||
| [:upper:] | 大写字母 |
|
||||
| [:xdigit:] | 允许十六进制的数字 (0-9a-fA-F) |
|
||||
|
||||
并不是所有正则表达式实现都支持 POSIX 字符类,也不一定使用它。
|
||||
|
||||
使用时需要用两对方括号,例如 [[:alpha:]]。
|
||||
|
||||
# 重复匹配
|
||||
|
||||
**\+** 匹配 1 个或者多个字符, **\*** 匹配 0 个或者多个,**?** 匹配 0 个或者 1 个。
|
||||
|
||||
**应用**
|
||||
|
||||
匹配邮箱地址。
|
||||
|
||||
**正则表达式**
|
||||
|
||||
```
|
||||
[\w.]+@\w+.\w+
|
||||
```
|
||||
|
||||
[\w.] 匹配的是字母数字或者 . ,在其后面加上 + ,表示匹配多次。在字符集合 [ ] 里,. 不是元字符;
|
||||
|
||||
**匹配结果**
|
||||
|
||||
**abc.def<span>@</span>qq.com**
|
||||
|
||||
为了可读性,常常把转义的字符放到字符集合 [ ] 中,但是含义是相同的。
|
||||
|
||||
```
|
||||
\w+@\w+.\w+
|
||||
[\w]+@[\w]+.[\w]+
|
||||
```
|
||||
|
||||
**{n}** 匹配 n 个字符,**{m, n}** 匹配 m\~n 个字符,**{m,}** 至少匹配 m 个字符;
|
||||
|
||||
\* 和 + 都是贪婪型元字符,会匹配最多的内容,在元字符后面加 ? 可以转换为懒惰型元字符,例如 \*?、+? 和 {m, n}? 。
|
||||
|
||||
**正则表达式**
|
||||
|
||||
```
|
||||
a.+c
|
||||
```
|
||||
|
||||
由于 + 是贪婪型的,因此 .+ 会匹配更可能多的内容,所以会把整个 abcabcabc 文本都匹配,而不是只匹配前面的 abc 文本。用懒惰型可以实现匹配前面的。
|
||||
|
||||
**匹配结果**
|
||||
|
||||
**abcabcabc**
|
||||
|
||||
# 位置匹配
|
||||
|
||||
## 单词边界
|
||||
|
||||
**\b** 可以匹配一个单词的边界,边界是指位于 \w 和 \W 之间的位置;**\B** 匹配一个不是单词边界的位置。
|
||||
|
||||
\b 只匹配位置,不匹配字符,因此 \babc\b 匹配出来的结果为 3 个字符。
|
||||
|
||||
## 字符串边界
|
||||
|
||||
**^** 匹配整个字符串的开头,**$** 匹配结尾。
|
||||
|
||||
^ 元字符在字符集合中用作求非,在字符集合外用作匹配字符串的开头。
|
||||
|
||||
使用 (?m) 来打开分行匹配模式,在该模式下,换行被当做字符串的边界。
|
||||
|
||||
**应用**
|
||||
|
||||
匹配代码中以 // 开始的注释行
|
||||
|
||||
**正则表达式**
|
||||
|
||||
```
|
||||
(?m)^\s*//.*$
|
||||
```
|
||||
|
||||
如果没用 (?m),则只会匹配 // 注释 1 以及之后的所有内容,因为 * 是贪婪型的。用了分行匹配模式之后,换行符被当成是字符串分隔符,因此能正确匹配出两个注释内容。
|
||||
|
||||
**匹配结果**
|
||||
|
||||
1. public void fun() {
|
||||
2. **// 注释 1**
|
||||
3. int a = 1;
|
||||
4. int b = 2;
|
||||
5. **// 注释 2**
|
||||
6. int c = a + b;
|
||||
7. }
|
||||
|
||||
# 使用子表达式
|
||||
|
||||
使用 **( )** 定义一个子表达式。子表达式的内容可以当成一个独立元素,即可以将它看成一个字符,并且使用 * 等元字符。
|
||||
|
||||
子表达式可以嵌套,但是嵌套层次过深会变得很难理解。
|
||||
|
||||
**正则表达式**
|
||||
|
||||
```
|
||||
(ab) {2,}
|
||||
```
|
||||
|
||||
**匹配结果**
|
||||
|
||||
**ababab**
|
||||
|
||||
**|** 是或元字符,它把左边和右边所有的部分都看成单独的两个部分,两个部分只要有一个匹配就行。
|
||||
|
||||
```
|
||||
(19|20)\d{2}
|
||||
```
|
||||
|
||||
**匹配结果**
|
||||
|
||||
1. **1900**
|
||||
2. **2010**
|
||||
3. 1020
|
||||
|
||||
**应用**
|
||||
|
||||
匹配 IP 地址。IP 地址中每部分都是 0-255 的数字,用正则表达式匹配时以下情况是合法的:
|
||||
|
||||
1. 一位或者两位的数字
|
||||
2. 1 开头的三位数
|
||||
3. 2 开头,第 2 位是 0-4 的三位数
|
||||
4. 25 开头,第 3 位是 0-5 的三位数
|
||||
|
||||
**正则表达式**
|
||||
|
||||
```
|
||||
(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.) {3}(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5])))
|
||||
```
|
||||
|
||||
**匹配结果**
|
||||
|
||||
1. **192.168.0.1**
|
||||
2. 555.555.555.555
|
||||
|
||||
# 回溯引用
|
||||
|
||||
回溯引用使用 **\n** 来引用某个子表达式,其中 n 代表的是子表达式的序号,从 1 开始。它和子表达式匹配的内容一致,比如子表达式匹配到 abc ,那么回溯引用部分也需要匹配 abc 。
|
||||
|
||||
**应用**
|
||||
|
||||
匹配 HTML 中合法的标题元素。
|
||||
|
||||
**正则表达式**
|
||||
|
||||
\1 将回溯引用子表达式 (h[1-6]) 匹配的内容,也就是说必须和子表达式匹配的内容一致。
|
||||
|
||||
```
|
||||
<(h[1-6])>\w*?</\1>
|
||||
```
|
||||
|
||||
**匹配结果**
|
||||
|
||||
1. **<h1>x</h1>**
|
||||
2. **<h2>x</h2>**
|
||||
3. <h3>x</h1>
|
||||
|
||||
## 替换
|
||||
|
||||
需要用到两个正则表达式。
|
||||
|
||||
**应用**
|
||||
|
||||
修改电话号码格式。
|
||||
|
||||
**文本**
|
||||
|
||||
313-555-1234
|
||||
|
||||
**查找正则表达式**
|
||||
|
||||
```
|
||||
(\d{3})(-)(\d{3})(-)(\d{4})
|
||||
```
|
||||
|
||||
**替换正则表达式**
|
||||
|
||||
在第一个子表达式查找的结果加上 () ,然后加一个空格,在第三个和第五个字表达式查找的结果中间加上 - 进行分隔。
|
||||
|
||||
```
|
||||
($1) $3-$5
|
||||
```
|
||||
|
||||
**结果**
|
||||
|
||||
(313) 555-1234
|
||||
|
||||
## 大小写转换
|
||||
|
||||
| 元字符 | 说明 |
|
||||
| ---| ---|
|
||||
| \l | 把下个字符转换为小写 |
|
||||
| \u| 把下个字符转换为大写 |
|
||||
| \L | 把\L 和\E 之间的字符全部转换为小写 |
|
||||
| \U | 把\U 和\E 之间的字符全部转换为大写 |
|
||||
| \E | 结束\L 或者\U |
|
||||
|
||||
**应用**
|
||||
|
||||
把文本的第二个和第三个字符转换为大写。
|
||||
|
||||
**文本**
|
||||
|
||||
abcd
|
||||
|
||||
**查找**
|
||||
|
||||
```
|
||||
(\w)(\w{2})(\w)
|
||||
```
|
||||
|
||||
**替换**
|
||||
|
||||
```
|
||||
$1\U$2\E$3
|
||||
```
|
||||
|
||||
**结果**
|
||||
|
||||
aBCd
|
||||
|
||||
# 前后查找
|
||||
|
||||
前后查找规定了匹配的内容首尾应该匹配的内容,但是又不包含首尾匹配的内容。向前查找用 **?=** 来定义,它规定了尾部匹配的内容,这个匹配的内容在 ?= 之后定义。所谓向前查找,就是规定了一个匹配的内容,然后以这个内容为尾部向前面查找需要匹配的内容。向后匹配用 ?<= 定义。
|
||||
|
||||
**应用**
|
||||
|
||||
查找出邮件地址 @ 字符前面的部分。
|
||||
|
||||
**正则表达式**
|
||||
|
||||
```
|
||||
\w+(?=@)
|
||||
```
|
||||
|
||||
**结果**
|
||||
|
||||
**abc** @qq.com
|
||||
|
||||
对向前和向后查找取非,只要把 = 替换成 ! 即可,比如 (?=) 替换成 (?!) 。取非操作使得匹配那些首尾不符合要求的内容。
|
||||
|
||||
# 嵌入条件
|
||||
|
||||
## 回溯引用条件
|
||||
|
||||
条件判断为某个子表达式是否匹配,如果匹配则需要继续匹配条件表达式后面的内容。
|
||||
|
||||
**正则表达式**
|
||||
|
||||
子表达式 (\\() 匹配一个左括号,其后的 ? 表示匹配 0 个或者 1 个。 ?(1) 为条件,当子表达式 1 匹配时条件成立,需要执行 \) 匹配,也就是匹配右括号。
|
||||
|
||||
```
|
||||
(\()?abc(?(1)\))
|
||||
```
|
||||
|
||||
**结果**
|
||||
|
||||
1. **(abc)**
|
||||
2. **abc**
|
||||
3. (abc
|
||||
|
||||
## 前后查找条件
|
||||
|
||||
条件为定义的首尾是否匹配,如果匹配,则继续执行后面的匹配。注意,首尾不包含在匹配的内容中。
|
||||
|
||||
**正则表达式**
|
||||
|
||||
?(?=-) 为前向查找条件,只有在以 - 为前向查找的结尾能匹配 \d{5} ,才继续匹配 -\d{4} 。
|
||||
|
||||
```
|
||||
\d{5}(?(?=-)-\d{4})
|
||||
```
|
||||
|
||||
**结果**
|
||||
|
||||
1. **11111**
|
||||
2. 22222-
|
||||
3. **33333-4444**
|
1917
notes/算法.md
1917
notes/算法.md
File diff suppressed because it is too large
Load Diff
729
notes/计算机操作系统.md
729
notes/计算机操作系统.md
File diff suppressed because it is too large
Load Diff
848
notes/计算机网络.md
848
notes/计算机网络.md
File diff suppressed because it is too large
Load Diff
2005
notes/设计模式.md
2005
notes/设计模式.md
File diff suppressed because it is too large
Load Diff
840
notes/重构.md
840
notes/重构.md
File diff suppressed because it is too large
Load Diff
345
notes/面向对象思想.md
345
notes/面向对象思想.md
|
@ -1,336 +1,367 @@
|
|||
[TOC]
|
||||
<!-- GFM-TOC -->
|
||||
* [设计原则](#设计原则)
|
||||
* [1. S.O.L.I.D](#1-solid)
|
||||
* [1.1 单一责任原则](#11-单一责任原则)
|
||||
* [1.2 开放封闭原则](#12-开放封闭原则)
|
||||
* [1.3 里氏替换原则](#13-里氏替换原则)
|
||||
* [1.4 接口分离原则](#14-接口分离原则)
|
||||
* [1.5 依赖倒置原则](#15-依赖倒置原则)
|
||||
* [2. 其他常见原则](#2-其他常见原则)
|
||||
* [2.1 迪米特法则](#21-迪米特法则)
|
||||
* [2.2 合成复用原则](#22-合成复用原则)
|
||||
* [2.3 共同封闭原则](#23-共同封闭原则)
|
||||
* [2.4 稳定抽象原则](#24-稳定抽象原则)
|
||||
* [2.5 稳定依赖原则](#25-稳定依赖原则)
|
||||
* [三大特性](#三大特性)
|
||||
* [1. 封装](#1-封装)
|
||||
* [2. 继承](#2-继承)
|
||||
* [3. 多态](#3-多态)
|
||||
* [UML](#uml)
|
||||
* [1. 类图](#1-类图)
|
||||
* [1.1 继承相关](#11-继承相关)
|
||||
* [1.2 整体和部分](#12-整体和部分)
|
||||
* [1.3 相互联系](#13-相互联系)
|
||||
* [2. 时序图](#2-时序图)
|
||||
* [2.1 定义](#21-定义)
|
||||
* [2.2 赤壁之战时序图](#22-赤壁之战时序图)
|
||||
* [2.3 活动图、时序图之间的关系](#23-活动图时序图之间的关系)
|
||||
* [2.4 类图与时序图的关系](#24-类图与时序图的关系)
|
||||
* [2.5 时序图的组成](#25-时序图的组成)
|
||||
* [参考资料](#参考资料)
|
||||
<!-- GFM-TOC -->
|
||||
|
||||
# 设计原则
|
||||
|
||||
# 设计原则
|
||||
|
||||
设计原则可以帮助我们避免那些糟糕的设计,这些原则被归纳在《敏捷软件开发:原则、模式与实践》这本书中。
|
||||
|
||||
## 1. S.O.L.I.D
|
||||
## 1. S.O.L.I.D
|
||||
|
||||
| 简写 | 全拼 | 中文翻译 |
|
||||
| -- | -- | -- |
|
||||
| SRP | The Single Responsibility Principle | 单一责任原则 |
|
||||
| OCP | The Open Closed Principle | 开放封闭原则 |
|
||||
| LSP | The Liskov Substitution Principle | 里氏替换原则 |
|
||||
| ISP | The Interface Segregation Principle | 接口分离原则 |
|
||||
| DIP | The Dependency Inversion Principle | 依赖倒置原则 |
|
||||
| 简写 | 全拼 | 中文翻译 |
|
||||
| -- | -- | -- |
|
||||
| SRP | The Single Responsibility Principle | 单一责任原则 |
|
||||
| OCP | The Open Closed Principle | 开放封闭原则 |
|
||||
| LSP | The Liskov Substitution Principle | 里氏替换原则 |
|
||||
| ISP | The Interface Segregation Principle | 接口分离原则 |
|
||||
| DIP | The Dependency Inversion Principle | 依赖倒置原则 |
|
||||
|
||||
### 1.1 单一责任原则
|
||||
### 1.1 单一责任原则
|
||||
|
||||
**修改一个类的原因应该只有一个。**
|
||||
**修改一个类的原因应该只有一个。**
|
||||
|
||||
换句话说就是让一个类只负责一件事,当这个类需要做过多事情的时候,就需要分解这个类。
|
||||
|
||||
如果一个类承担的职责过多,就等于把这些职责耦合在了一起,一个职责的变化可能会削弱这个类完成其它职责的能力。
|
||||
|
||||
### 1.2 开放封闭原则
|
||||
### 1.2 开放封闭原则
|
||||
|
||||
**类应该对扩展开放,对修改关闭。**
|
||||
**类应该对扩展开放,对修改关闭。**
|
||||
|
||||
扩展就是添加新功能的意思,因此该原则要求在添加新功能时不需要修改代码。
|
||||
|
||||
符合开闭原则最典型的设计模式是装饰者模式,它可以动态地将责任附加到对象上,而不用去修改类的代码。
|
||||
|
||||
### 1.3 里氏替换原则
|
||||
### 1.3 里氏替换原则
|
||||
|
||||
**子类对象必须能够替换掉所有父类对象。**
|
||||
**子类对象必须能够替换掉所有父类对象。**
|
||||
|
||||
继承是一种 IS-A 关系,子类需要能够当成父类来使用,并且需要比父类更特殊。
|
||||
继承是一种 IS-A 关系,子类需要能够当成父类来使用,并且需要比父类更特殊。
|
||||
|
||||
如果不满足这个原则,那么各个子类的行为上就会有很大差异,增加继承体系的复杂度。
|
||||
|
||||
### 1.4 接口分离原则
|
||||
### 1.4 接口分离原则
|
||||
|
||||
**不应该强迫客户依赖于它们不用的方法。**
|
||||
**不应该强迫客户依赖于它们不用的方法。**
|
||||
|
||||
因此使用多个专门的接口比使用单一的总接口总要好。
|
||||
|
||||
### 1.5 依赖倒置原则
|
||||
### 1.5 依赖倒置原则
|
||||
|
||||
- **高层模块不应该依赖于低层模块,二者都应该依赖于抽象**
|
||||
- **抽象不应该依赖于细节,细节应该依赖于抽象**
|
||||
- **高层模块不应该依赖于低层模块,二者都应该依赖于抽象**
|
||||
- **抽象不应该依赖于细节,细节应该依赖于抽象**
|
||||
|
||||
高层模块包含一个应用程序中重要的策略选择和业务模块,如果高层模块依赖于底层模块,那么底层模块的改动就会直接影响到高层模块,从而迫使高层模块也需要改动。
|
||||
|
||||
依赖于抽象意味着:
|
||||
|
||||
- 任何变量都不应该持有一个指向具体类的指针或者引用;
|
||||
- 任何类都不应该从具体类派生;
|
||||
- 任何方法都不应该覆写它的任何基类中的已经实现的方法。
|
||||
- 任何变量都不应该持有一个指向具体类的指针或者引用;
|
||||
- 任何类都不应该从具体类派生;
|
||||
- 任何方法都不应该覆写它的任何基类中的已经实现的方法。
|
||||
|
||||
## 2. 其他常见原则
|
||||
## 2. 其他常见原则
|
||||
|
||||
除了上述的经典原则,在实际开发中还有下面这些常见的设计原则。
|
||||
|
||||
| 简写 | 全拼 | 中文翻译 |
|
||||
| -- | -- | -- |
|
||||
|LoD| The Law of Demeter | 迪米特法则 |
|
||||
|CRP| The Composite Reuse Principle | 合成复用原则 |
|
||||
|CCP| The Common Closure Principle | 共同封闭原则 |
|
||||
|SAP| The Stable Abstractions Principle | 稳定抽象原则 |
|
||||
|SDP| The Stable Dependencies Principle | 稳定依赖原则 |
|
||||
| 简写 | 全拼 | 中文翻译 |
|
||||
| -- | -- | -- |
|
||||
|LoD| The Law of Demeter | 迪米特法则 |
|
||||
|CRP| The Composite Reuse Principle | 合成复用原则 |
|
||||
|CCP| The Common Closure Principle | 共同封闭原则 |
|
||||
|SAP| The Stable Abstractions Principle | 稳定抽象原则 |
|
||||
|SDP| The Stable Dependencies Principle | 稳定依赖原则 |
|
||||
|
||||
### 2.1 迪米特法则
|
||||
### 2.1 迪米特法则
|
||||
|
||||
迪米特法则又叫作最少知道原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。
|
||||
迪米特法则又叫作最少知道原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。
|
||||
|
||||
### 2.2 合成复用原则
|
||||
### 2.2 合成复用原则
|
||||
|
||||
尽量使用对象组合,而不是继承来达到复用的目的。
|
||||
|
||||
### 2.3 共同封闭原则
|
||||
### 2.3 共同封闭原则
|
||||
|
||||
一起修改的类,应该组合在一起(同一个包里)。如果必须修改应用程序里的代码,我们希望所有的修改都发生在一个包里(修改关闭),而不是遍布在很多包里。
|
||||
|
||||
### 2.4 稳定抽象原则
|
||||
### 2.4 稳定抽象原则
|
||||
|
||||
最稳定的包应该是最抽象的包,不稳定的包应该是具体的包,即包的抽象程度跟它的稳定性成正比。
|
||||
|
||||
### 2.5 稳定依赖原则
|
||||
### 2.5 稳定依赖原则
|
||||
|
||||
包之间的依赖关系都应该是稳定方向依赖的,包要依赖的包要比自己更具有稳定性。
|
||||
|
||||
# 三大特性
|
||||
# 三大特性
|
||||
|
||||
## 1. 封装
|
||||
## 1. 封装
|
||||
|
||||
利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户无需知道对象内部的细节,但可以通过对象对外提供的接口来访问该对象。
|
||||
|
||||
封装有三大好处:
|
||||
|
||||
1. 减少耦合
|
||||
2. 隐藏内部细节,因此内部结构可以自由修改
|
||||
3. 可以对成员进行更精确的控制
|
||||
1. 减少耦合
|
||||
2. 隐藏内部细节,因此内部结构可以自由修改
|
||||
3. 可以对成员进行更精确的控制
|
||||
|
||||
以下 Person 类封装 name、gender、age 等属性,外界只能通过 get() 方法获取一个 Person 对象的 name 属性和 gender 属性,而无法获取 age 属性,但是 age 属性可以供 work() 方法使用。
|
||||
以下 Person 类封装 name、gender、age 等属性,外界只能通过 get() 方法获取一个 Person 对象的 name 属性和 gender 属性,而无法获取 age 属性,但是 age 属性可以供 work() 方法使用。
|
||||
|
||||
注意到 gender 属性使用 int 数据类型进行存储,封装使得用户注意不到这种实现细节。并且在需要修改 gender 属性使用的数据类型时,也可以在不影响客户端代码的情况下进行。
|
||||
注意到 gender 属性使用 int 数据类型进行存储,封装使得用户注意不到这种实现细节。并且在需要修改 gender 属性使用的数据类型时,也可以在不影响客户端代码的情况下进行。
|
||||
|
||||
```java
|
||||
public class Person {
|
||||
private String name;
|
||||
private int gender;
|
||||
private int age;
|
||||
public class Person {
|
||||
private String name;
|
||||
private int gender;
|
||||
private int age;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getGender() {
|
||||
return gender == 0 ? "man" : "woman";
|
||||
}
|
||||
public String getGender() {
|
||||
return gender == 0 ? "man" : "woman";
|
||||
}
|
||||
|
||||
public void work() {
|
||||
if(18 <= age && age <= 50) {
|
||||
System.out.println(name + " is working very hard!");
|
||||
} else {
|
||||
System.out.println(name + " can't work any more!");
|
||||
}
|
||||
}
|
||||
public void work() {
|
||||
if(18 <= age && age <= 50) {
|
||||
System.out.println(name + " is working very hard!");
|
||||
} else {
|
||||
System.out.println(name + " can't work any more!");
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 继承
|
||||
## 2. 继承
|
||||
|
||||
继承实现了 **IS-A** 关系,例如 Cat 和 Animal 就是一种 IS-A 关系,因此 Cat 可以继承自 Animal,从而获得 Animal 非 private 的属性和方法。
|
||||
继承实现了 **IS-A** 关系,例如 Cat 和 Animal 就是一种 IS-A 关系,因此 Cat 可以继承自 Animal,从而获得 Animal 非 private 的属性和方法。
|
||||
|
||||
Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat 对象。父类引用指向子类对象称为 **向上转型**。
|
||||
Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat 对象。父类引用指向子类对象称为 **向上转型** 。
|
||||
|
||||
```java
|
||||
Animal animal = new Cat();
|
||||
Animal animal = new Cat();
|
||||
```
|
||||
|
||||
继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。
|
||||
|
||||
## 3. 多态
|
||||
## 3. 多态
|
||||
|
||||
多态分为编译时多态和运行时多态。编译时多态主要指方法的重载,运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定。
|
||||
|
||||
运行时多态有三个条件:
|
||||
|
||||
1. 继承
|
||||
2. 覆盖
|
||||
3. 向上转型
|
||||
1. 继承
|
||||
2. 覆盖
|
||||
3. 向上转型
|
||||
|
||||
下面的代码中,乐器类(Instrument)有两个子类:Wind 和 Percussion,它们都覆盖了父类的 play() 方法,并且在 main() 方法中使用父类 Instrument 来引用 Wind 和 Percussion 对象。在 Instrument 引用调用 play() 方法时,会执行实际引用对象所在类的 play() 方法,而不是 Instrument 类的方法。
|
||||
下面的代码中,乐器类(Instrument)有两个子类:Wind 和 Percussion,它们都覆盖了父类的 play() 方法,并且在 main() 方法中使用父类 Instrument 来引用 Wind 和 Percussion 对象。在 Instrument 引用调用 play() 方法时,会执行实际引用对象所在类的 play() 方法,而不是 Instrument 类的方法。
|
||||
|
||||
```java
|
||||
public class Instrument {
|
||||
public void play() {
|
||||
System.out.println("Instument is playing...");
|
||||
}
|
||||
public class Instrument {
|
||||
public void play() {
|
||||
System.out.println("Instument is playing...");
|
||||
}
|
||||
}
|
||||
|
||||
public class Wind extends Instrument {
|
||||
public void play() {
|
||||
System.out.println("Wind is playing...");
|
||||
}
|
||||
public class Wind extends Instrument {
|
||||
public void play() {
|
||||
System.out.println("Wind is playing...");
|
||||
}
|
||||
}
|
||||
|
||||
public class Percussion extends Instrument {
|
||||
public void play() {
|
||||
System.out.println("Percussion is playing...");
|
||||
}
|
||||
public class Percussion extends Instrument {
|
||||
public void play() {
|
||||
System.out.println("Percussion is playing...");
|
||||
}
|
||||
}
|
||||
|
||||
public class Music {
|
||||
public static void main(String[] args) {
|
||||
List<Instrument> instruments = new ArrayList<>();
|
||||
instruments.add(new Wind());
|
||||
instruments.add(new Percussion());
|
||||
for(Instrument instrument : instruments) {
|
||||
instrument.play();
|
||||
}
|
||||
}
|
||||
public class Music {
|
||||
public static void main(String[] args) {
|
||||
List<Instrument> instruments = new ArrayList<>();
|
||||
instruments.add(new Wind());
|
||||
instruments.add(new Percussion());
|
||||
for(Instrument instrument : instruments) {
|
||||
instrument.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# UML
|
||||
# UML
|
||||
|
||||
## 1. 类图
|
||||
## 1. 类图
|
||||
|
||||
### 1.1 继承相关
|
||||
### 1.1 继承相关
|
||||
|
||||
继承有两种形式 : 泛化(Generalize)和实现(Realize),表现为 IS-A 关系。
|
||||
继承有两种形式 : 泛化(Generalize)和实现(Realize),表现为 IS-A 关系。
|
||||
|
||||
**泛化关系 (Generalize)**
|
||||
**泛化关系 (Generalize)**
|
||||
|
||||
从具体类中继承。
|
||||
|
||||
![](index_files/29badd92-109f-4f29-abb9-9857f5973928.png)
|
||||
<div align="center"> <img src="../pics//29badd92-109f-4f29-abb9-9857f5973928.png"/> </div><br>
|
||||
|
||||
**实现关系 (Realize)**
|
||||
**实现关系 (Realize)**
|
||||
|
||||
从抽象类或者接口中继承。
|
||||
|
||||
![](index_files/4b16e1d3-3a60-472c-9756-2f31b1c48abe.png)
|
||||
<div align="center"> <img src="../pics//4b16e1d3-3a60-472c-9756-2f31b1c48abe.png"/> </div><br>
|
||||
|
||||
### 1.2 整体和部分
|
||||
### 1.2 整体和部分
|
||||
|
||||
**聚合关系 (Aggregation)**
|
||||
**聚合关系 (Aggregation)**
|
||||
|
||||
表示整体由部分组成,但是整体和部分不是强依赖的,整体不存在了部分还是会存在。以下表示 B 由 A 组成:
|
||||
表示整体由部分组成,但是整体和部分不是强依赖的,整体不存在了部分还是会存在。以下表示 B 由 A 组成:
|
||||
|
||||
![](index_files/34259bb8-ca3a-4872-8771-9e946782d9c3.png)
|
||||
<div align="center"> <img src="../pics//34259bb8-ca3a-4872-8771-9e946782d9c3.png"/> </div><br>
|
||||
|
||||
**组合关系 (Composition)**
|
||||
**组合关系 (Composition)**
|
||||
|
||||
和聚合不同,组合中整体和部分是强依赖的,整体不存在了部分也不存在了。比如公司和部门,公司没了部门就不存在了。但是公司和员工就属于聚合关系了,因为公司没了员工还在。
|
||||
|
||||
![](index_files/7dda050d-ac35-4f47-9f51-18f18ed6fa9a.png)
|
||||
<div align="center"> <img src="../pics//7dda050d-ac35-4f47-9f51-18f18ed6fa9a.png"/> </div><br>
|
||||
|
||||
### 1.3 相互联系
|
||||
### 1.3 相互联系
|
||||
|
||||
**关联关系 (Association)**
|
||||
**关联关系 (Association)**
|
||||
|
||||
表示不同类对象之间有关联,这是一种静态关系,与运行过程的状态无关,在最开始就可以确定。因此也可以用 1 对 1、多对 1、多对多这种关联关系来表示。比如学生和学校就是一种关联关系,一个学校可以有很多学生,但是一个学生只属于一个学校,因此这是一种多对一的关系,在运行开始之前就可以确定。
|
||||
表示不同类对象之间有关联,这是一种静态关系,与运行过程的状态无关,在最开始就可以确定。因此也可以用 1 对 1、多对 1、多对多这种关联关系来表示。比如学生和学校就是一种关联关系,一个学校可以有很多学生,但是一个学生只属于一个学校,因此这是一种多对一的关系,在运行开始之前就可以确定。
|
||||
|
||||
![](index_files/4ccd294c-d6b2-421b-839e-d88336ff5fb7.png)
|
||||
<div align="center"> <img src="../pics//4ccd294c-d6b2-421b-839e-d88336ff5fb7.png"/> </div><br>
|
||||
|
||||
**依赖关系 (Dependency)**
|
||||
**依赖关系 (Dependency)**
|
||||
|
||||
和关联关系不同的是 , 依赖关系是在运行过程中起作用的。一般依赖作为类的构造器或者方法的参数传入。双向依赖时一种不好的设计。
|
||||
和关联关系不同的是 , 依赖关系是在运行过程中起作用的。一般依赖作为类的构造器或者方法的参数传入。双向依赖时一种不好的设计。
|
||||
|
||||
![](index_files/47ca2614-509f-476e-98fc-50ec9f9d43c0.png)
|
||||
<div align="center"> <img src="../pics//47ca2614-509f-476e-98fc-50ec9f9d43c0.png"/> </div><br>
|
||||
|
||||
## 2. 时序图
|
||||
## 2. 时序图
|
||||
|
||||
### 2.1 定义
|
||||
### 2.1 定义
|
||||
|
||||
时序图描述了对象之间传递消息的时间顺序,它用来表示用例的行为顺序。它的主要作用是通过对象间的交互来描述用例(注意是对象),从而寻找类的操作。
|
||||
|
||||
### 2.2 赤壁之战时序图
|
||||
### 2.2 赤壁之战时序图
|
||||
|
||||
从虚线从上往下表示时间的推进。
|
||||
|
||||
![](index_files/80c5aff8-fc46-4810-aeaa-215b5c60a003.png)
|
||||
<div align="center"> <img src="../pics//80c5aff8-fc46-4810-aeaa-215b5c60a003.png"/> </div><br>
|
||||
|
||||
可见,通过时序图可以知道每个类具有以下操作:
|
||||
|
||||
```java
|
||||
publc class 刘备 {
|
||||
public void 应战 ();
|
||||
publc class 刘备 {
|
||||
public void 应战 ();
|
||||
}
|
||||
|
||||
publc class 孔明 {
|
||||
public void 拟定策略 ();
|
||||
public void 联合孙权 ();
|
||||
private void 借东风火攻 ();
|
||||
publc class 孔明 {
|
||||
public void 拟定策略 ();
|
||||
public void 联合孙权 ();
|
||||
private void 借东风火攻 ();
|
||||
}
|
||||
|
||||
public class 关羽 {
|
||||
public void 防守荊州 ();
|
||||
public class 关羽 {
|
||||
public void 防守荊州 ();
|
||||
}
|
||||
|
||||
public class 张飞 {
|
||||
public void 防守荆州前线 ();
|
||||
public class 张飞 {
|
||||
public void 防守荆州前线 ();
|
||||
}
|
||||
|
||||
public class 孙权 {
|
||||
public void 领兵相助 ();
|
||||
public class 孙权 {
|
||||
public void 领兵相助 ();
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 活动图、时序图之间的关系
|
||||
### 2.3 活动图、时序图之间的关系
|
||||
|
||||
活动图示从用户的角度来描述用例;
|
||||
|
||||
时序图是从计算机的角度(对象间的交互)描述用例。
|
||||
|
||||
### 2.4 类图与时序图的关系
|
||||
### 2.4 类图与时序图的关系
|
||||
|
||||
类图描述系统的静态结构,时序图描述系统的动态行为。
|
||||
|
||||
### 2.5 时序图的组成
|
||||
### 2.5 时序图的组成
|
||||
|
||||
**对象**
|
||||
**对象**
|
||||
|
||||
有三种表现形式
|
||||
|
||||
![](index_files/25b8adad-2ef6-4f30-9012-c306b4e49897.png)
|
||||
<div align="center"> <img src="../pics//25b8adad-2ef6-4f30-9012-c306b4e49897.png"/> </div><br>
|
||||
|
||||
在画图时,应该遵循以下原则:
|
||||
|
||||
1. 把交互频繁的对象尽可能地靠拢。
|
||||
1. 把交互频繁的对象尽可能地靠拢。
|
||||
|
||||
2. 把初始化整个交互活动的对象(有时是一个参与者)放置在最左边。
|
||||
2. 把初始化整个交互活动的对象(有时是一个参与者)放置在最左边。
|
||||
|
||||
**生命线**
|
||||
**生命线**
|
||||
|
||||
生命线从对象的创建开始到对象销毁时终止
|
||||
|
||||
![](index_files/b7b0eac6-e7ea-4fb6-8bfb-95fec6f235e2.png)
|
||||
<div align="center"> <img src="../pics//b7b0eac6-e7ea-4fb6-8bfb-95fec6f235e2.png"/> </div><br>
|
||||
|
||||
**消息**
|
||||
**消息**
|
||||
|
||||
对象之间的交互式通过发送消息来实现的。
|
||||
|
||||
消息有 4 种类型:
|
||||
消息有 4 种类型:
|
||||
|
||||
1\. 简单消息,不区分同步异步。
|
||||
1\. 简单消息,不区分同步异步。
|
||||
|
||||
![](index_files/a13b62da-0fa8-4224-a615-4cadacc08871.png)
|
||||
<div align="center"> <img src="../pics//a13b62da-0fa8-4224-a615-4cadacc08871.png"/> </div><br>
|
||||
|
||||
2\. 同步消息,发送消息之后需要暂停活动来等待回应。
|
||||
2\. 同步消息,发送消息之后需要暂停活动来等待回应。
|
||||
|
||||
![](index_files/33821037-dc40-4266-901c-e5b38e618426.png)
|
||||
<div align="center"> <img src="../pics//33821037-dc40-4266-901c-e5b38e618426.png"/> </div><br>
|
||||
|
||||
3\. 异步消息,发送消息之后不需要等待。
|
||||
3\. 异步消息,发送消息之后不需要等待。
|
||||
|
||||
![](index_files/dec6c6cc-1b5f-44ed-b8fd-464fcf849dac.png)
|
||||
<div align="center"> <img src="../pics//dec6c6cc-1b5f-44ed-b8fd-464fcf849dac.png"/> </div><br>
|
||||
|
||||
4\. 返回消息,可选。
|
||||
4\. 返回消息,可选。
|
||||
|
||||
**激活**
|
||||
**激活**
|
||||
|
||||
生命线上的方框表示激活状态,其它时间处于休眠状态。
|
||||
|
||||
![](index_files/6ab5de9b-1c1e-4118-b2c3-fb6c7ed7de6f.png)
|
||||
<div align="center"> <img src="../pics//6ab5de9b-1c1e-4118-b2c3-fb6c7ed7de6f.png"/> </div><br>
|
||||
|
||||
# 参考资料
|
||||
# 参考资料
|
||||
|
||||
- Java 编程思想
|
||||
- 敏捷软件开发:原则、模式与实践
|
||||
- [面向对象设计的 SOLID 原则](http://www.cnblogs.com/shanyou/archive/2009/09/21/1570716.html)
|
||||
- [看懂 UML 类图和时序图](http://design-patterns.readthedocs.io/zh_CN/latest/read_uml.html#generalization)
|
||||
- [UML 系列——时序图(顺序图)sequence diagram](http://www.cnblogs.com/wolf-sun/p/UML-Sequence-diagram.html)
|
||||
- [面向对象编程三大特性 ------ 封装、继承、多态](http://blog.csdn.net/jianyuerensheng/article/details/51602015)
|
||||
- Java 编程思想
|
||||
- 敏捷软件开发:原则、模式与实践
|
||||
- [面向对象设计的 SOLID 原则](http://www.cnblogs.com/shanyou/archive/2009/09/21/1570716.html)
|
||||
- [看懂 UML 类图和时序图](http://design-patterns.readthedocs.io/zh_CN/latest/read_uml.html#generalization)
|
||||
- [UML 系列——时序图(顺序图)sequence diagram](http://www.cnblogs.com/wolf-sun/p/UML-Sequence-diagram.html)
|
||||
- [面向对象编程三大特性 ------ 封装、继承、多态](http://blog.csdn.net/jianyuerensheng/article/details/51602015)
|
||||
|
|
BIN
pics/0e4c8a7f-f84c-4c4e-9544-49cd40167af8.png
Normal file
BIN
pics/0e4c8a7f-f84c-4c4e-9544-49cd40167af8.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
BIN
pics/2d078e08-3a49-46d0-b784-df780b7e4bc3.jpg
Normal file
BIN
pics/2d078e08-3a49-46d0-b784-df780b7e4bc3.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
pics/73b73189-9e95-47e5-91d0-9378b8462e15.png
Normal file
BIN
pics/73b73189-9e95-47e5-91d0-9378b8462e15.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
BIN
pics/b242fafc-5945-42a8-805e-6e3f1f2f89b4.jpg
Normal file
BIN
pics/b242fafc-5945-42a8-805e-6e3f1f2f89b4.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
BIN
pics/ea5e434a-a218-44b5-aa72-4cd08991abcf.jpg
Normal file
BIN
pics/ea5e434a-a218-44b5-aa72-4cd08991abcf.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 59 KiB |
Loading…
Reference in New Issue
Block a user