Merge pull request #1 from CyC2018/master

更新
This commit is contained in:
NeverOldGeek 2018-10-16 16:45:54 +08:00 committed by GitHub
commit 3ea5b16690
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1627 additions and 1319 deletions

View File

@ -54,7 +54,7 @@
- [STL 源码剖析](https://book.douban.com/subject/1110934/) - [STL 源码剖析](https://book.douban.com/subject/1110934/)
- [深度探索 C++ 对象模型](https://book.douban.com/subject/1091086/) - [深度探索 C++ 对象模型](https://book.douban.com/subject/1091086/)
# 网站架构/分布式 # 系统设计
- [大规模分布式存储系统](https://book.douban.com/subject/25723658/) - [大规模分布式存储系统](https://book.douban.com/subject/25723658/)
- [从 Paxos 到 Zookeeper](https://book.douban.com/subject/26292004/) - [从 Paxos 到 Zookeeper](https://book.douban.com/subject/26292004/)

124
README.md
View File

@ -1,17 +1,16 @@
| | Ⅱ | Ⅲ | Ⅳ | | Ⅵ | Ⅶ | Ⅷ | Ⅸ | | | | Ⅱ | Ⅲ | Ⅳ | | Ⅵ | Ⅶ | Ⅷ | Ⅸ | |
| :--------: | :---------: | :---------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :------:| | :--------: | :---------: | :---------: | :---------: | :---------: | :---------:| :---------: | :-------: | :-------:| :------:|
| 算法[:pencil2:](#算法-pencil2) | 操作系统[:computer:](#操作系统-computer)|网络[:cloud:](#网络-cloud) | 面向对象[:couple:](#面向对象-couple) |数据库[:floppy_disk:](#数据库-floppy_disk)| Java [:coffee:](#java-coffee)| 系统设计[:bulb:](#系统设计-bulb)| 工具[:hammer:](#工具-hammer)| 编码实践[:speak_no_evil:](#编码实践-speak_no_evil)| 后记[:memo:](#后记-memo) | | 算法[:pencil2:](#pencil2-算法) | 操作系统[:computer:](#computer-操作系统)|网络[:cloud:](#cloud-网络) | 面向对象[:couple:](#couple-面向对象) |数据库[:floppy_disk:](#floppy_disk-数据库)| Java [:coffee:](#coffee-java)| 系统设计[:bulb:](#bulb-系统设计)| 工具[:hammer:](#hammer-工具)| 编码实践[:speak_no_evil:](#speak_no_evil-编码实践)| 后记[:memo:](#memo-后记) |
<br> <br>
<div align="center"> <div align="center">
<img src="other/LogoMakr_0zpEzN.png" width="150px"> <img src="other/LogoMakr_0zpEzN.png" width="150px">
<br> <br>
<a href="other/Group.md"> <img src="https://img.shields.io/badge/>-group-4ab8a1.svg"></a> <a href="https://legacy.gitbook.com/book/cyc2018/interview-notebook/details"> <img src="https://img.shields.io/badge/_-gitbook-4ab8a1.svg"></a> <a href="other/Group.md"> <img src="https://img.shields.io/badge/>-group-4ab8a1.svg"></a> <a href="https://legacy.gitbook.com/book/cyc2018/interview-notebook/details"> <img src="https://img.shields.io/badge/_-gitbook-4ab8a1.svg"></a>
</div> </div>
<!-- [![](https://img.shields.io/badge/>-gitter-blue.svg)](https://gitter.im/CyC2018-Interview-Notebook/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link) [![](https://img.shields.io/badge/_-gitbook-4ab8a1.svg)](https://legacy.gitbook.com/book/cyc2018/interview-notebook/details) --> ### :pencil2: 算法
## 算法 :pencil2:
- [剑指 Offer 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/剑指%20offer%20题解.md) - [剑指 Offer 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/剑指%20offer%20题解.md)
@ -20,12 +19,12 @@
- [Leetcode 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Leetcode%20题解.md) - [Leetcode 题解](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Leetcode%20题解.md)
对题目做了一个大致分类,并对每种题型的解题思路做了总结。 对题目做了一个大致分类,并对每种题型的解题思路做了总结。
- [算法](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/算法.md)
- [算法](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/算法.md) 排序、并查集、栈和队列、红黑树、散列表。
排序、并查集、栈和队列、红黑树、散列表。 ### :computer: 操作系统
## 操作系统 :computer:
- [计算机操作系统](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/计算机操作系统.md) - [计算机操作系统](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/计算机操作系统.md)
@ -35,7 +34,7 @@
基本实现原理以及基本操作。 基本实现原理以及基本操作。
## 网络 :cloud: ### :cloud: 网络
- [计算机网络](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/计算机网络.md) - [计算机网络](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/计算机网络.md)
@ -49,7 +48,7 @@
I/O 模型、I/O 多路复用。 I/O 模型、I/O 多路复用。
## 面向对象 :couple: ### :couple: 面向对象
- [设计模式](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/设计模式.md) - [设计模式](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/设计模式.md)
@ -59,7 +58,7 @@
三大原则(继承、封装、多态)、类图、设计原则。 三大原则(继承、封装、多态)、类图、设计原则。
## 数据库 :floppy_disk: ### :floppy_disk: 数据库
- [数据库系统原理](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/数据库系统原理.md) - [数据库系统原理](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/数据库系统原理.md)
@ -81,7 +80,7 @@
五种数据类型、字典和跳跃表数据结构、使用场景、和 Memcache 的比较、淘汰策略、持久化、文件事件的 Reactor 模式、复制。 五种数据类型、字典和跳跃表数据结构、使用场景、和 Memcache 的比较、淘汰策略、持久化、文件事件的 Reactor 模式、复制。
## Java :coffee: ### :coffee: Java
- [Java 基础](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20基础.md) - [Java 基础](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Java%20基础.md)
@ -103,7 +102,7 @@
NIO 的原理以及实例。 NIO 的原理以及实例。
## 系统设计 :bulb: ### :bulb: 系统设计
- [系统设计基础](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/系统设计基础.md) - [系统设计基础](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/系统设计基础.md)
@ -129,7 +128,7 @@
消息处理模型、使用场景、可靠性 消息处理模型、使用场景、可靠性
## 工具 :hammer: ### :hammer: 工具
- [Git](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Git.md) - [Git](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/Git.md)
@ -147,7 +146,7 @@
构建工具的基本概念、主流构建工具介绍。 构建工具的基本概念、主流构建工具介绍。
## 编码实践 :speak_no_evil: ### :speak_no_evil: 编码实践
- [重构](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/重构.md) - [重构](https://github.com/CyC2018/InnterviewNotes/blob/master/notes/重构.md)
@ -161,25 +160,27 @@
Google 开源项目的代码风格规范。 Google 开源项目的代码风格规范。
## 后记 :memo: ### :memo: 后记
### About #### About
这个仓库是笔者的一个学习笔记,主要总结一些比较重要的知识点,希望对大家有所帮助。 本仓库主要是根据计算机经典书籍以及官方技术文档进行总结的学习笔记,希望对大家有所帮助。
笔记不是从网上到处复制粘贴拼凑而来,虽然有少部分内容会直接引入书上原文或者官方技术文档的原文,但是没有直接摘抄其他人的博客文章,只做了参考,参考的文章会在最后给出链接 学习笔记不是从网上到处拼凑而来,除了少部分引用书上和技术文档的原文,其余都是笔者的原创。在您引用本仓库内容或者对内容进行修改演绎时,请遵循文末的开源协议,谢谢
[BOOKLIST](https://github.com/CyC2018/Interview-Notebook/blob/master/BOOKLIST.md),这个书单是笔者至今看的一些比较好的技术书籍,虽然没有全都看完,但每本书多多少少都看了一部分。 #### BookList
### How To Contribute 本仓库参考的书目:[BOOKLIST](https://github.com/CyC2018/Interview-Notebook/blob/master/BOOKLIST.md)。
笔记内容是笔者一个字一个字打上去的,难免会有一些笔误,如果发现笔误可直接在相应文档进行编辑修改。 #### How To Contribute
笔记内容是笔者一个字一个字打上去的,难免会有一些笔误,如果发现笔误可直接对相应文档进行编辑修改。
如果想要提交一个仓库现在还没有的全新内容,可以先将相应的文档放到 other 目录下。 如果想要提交一个仓库现在还没有的全新内容,可以先将相应的文档放到 other 目录下。
欢迎在 Issue 中提交对本仓库的改进建议~ 欢迎在 Issue 中提交对本仓库的改进建议~
### Typesetting #### Typesetting
笔记内容按照 [中文文案排版指北](http://mazhuang.org/wiki/chinese-copywriting-guidelines/) 进行排版,以保证内容的可读性。 笔记内容按照 [中文文案排版指北](http://mazhuang.org/wiki/chinese-copywriting-guidelines/) 进行排版,以保证内容的可读性。
@ -187,7 +188,7 @@
笔者将自己实现的文档排版功能提取出来,放在 Github Page 中,无需下载安装即可免费使用:[Text-Typesetting](https://github.com/CyC2018/Markdown-Typesetting)。 笔者将自己实现的文档排版功能提取出来,放在 Github Page 中,无需下载安装即可免费使用:[Text-Typesetting](https://github.com/CyC2018/Markdown-Typesetting)。
### Uploading #### Uploading
笔者在本地使用为知笔记软件进行书写,为了方便将本地笔记内容上传到 Github 上实现了一整套自动化上传方案包括文本文件的导出、提取图片、Markdown 文档转换、Git 同步。 笔者在本地使用为知笔记软件进行书写,为了方便将本地笔记内容上传到 Github 上实现了一整套自动化上传方案包括文本文件的导出、提取图片、Markdown 文档转换、Git 同步。
@ -195,45 +196,46 @@
笔者将自己实现文档转换功能提取出来,方便大家在需要将本地 Markdown 上传到 Github或者制作项目 README 文档时生成目录时使用:[GFM-Converter](https://github.com/CyC2018/GFM-Converter)。 笔者将自己实现文档转换功能提取出来,方便大家在需要将本地 Markdown 上传到 Github或者制作项目 README 文档时生成目录时使用:[GFM-Converter](https://github.com/CyC2018/GFM-Converter)。
### License #### Logo
Power by [logomakr](https://logomakr.com/).
#### Statement
本仓库不参与商业行为,不向读者收取任何费用。(This repository is not engaging in business activities, and does not charge readers any fee.)
#### Acknowledgements
感谢以下人员对本仓库做出的贡献,当然不仅仅只有这些贡献者,这里就不一一列举了。如果你希望被添加到这个名单中,并且提交过 Issue 或者 PR请与笔者联系。
<a href="https://github.com/linw7">
<img src="https://avatars3.githubusercontent.com/u/21679154?s=400&v=4" width="50px">
</a>
<a href="https://github.com/g10guang">
<img src="https://avatars1.githubusercontent.com/u/18458140?s=400&v=4" width="50px">
</a>
<a href="https://github.com/ResolveWang">
<img src="https://avatars1.githubusercontent.com/u/8018776?s=400&v=4" width="50px">
</a>
<a href="https://github.com/crossoverJie">
<img src="https://avatars1.githubusercontent.com/u/15684156?s=400&v=4" width="50px">
</a>
<a href="https://github.com/jy03078584">
<img src="https://avatars2.githubusercontent.com/u/7719370?s=400&v=4" width="50px">
</a>
<a href="https://github.com/kwongtailau">
<img src="https://avatars0.githubusercontent.com/u/22954582?s=400&v=4" width="50px">
</a>
<a href="https://github.com/xiangflight">
<img src="https://avatars2.githubusercontent.com/u/10072416?s=400&v=4" width="50px">
</a>
<a href="https://github.com/mafulong">
<img src="https://avatars1.githubusercontent.com/u/24795000?s=400&v=4" width="50px">
</a>
#### License
在对本作品进行演绎时,请署名并以相同方式共享。 在对本作品进行演绎时,请署名并以相同方式共享。
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a> <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png" /></a>
### Statement
本仓库不参与商业行为,不向读者收取任何费用。(This repository is not engaging in business activities, and does not charge readers any fee.)
### Logo
Power by [logomakr](https://logomakr.com/).
### Acknowledgements
感谢以下人员对本仓库做出的贡献,当然不仅仅只有这些贡献者,这里就不一一列举了。如果你希望被添加到这个名单中,并且提交过 Issue 或者 PR请与笔者联系。
<a href="https://github.com/linw7">
<img src="other/21679154.png" width="50px">
</a>
<a href="https://github.com/g10guang">
<img src="other/18458140.jpg" width="50px">
</a>
<a href="https://github.com/ResolveWang">
<img src="other/8018776.jpg" width="50px">
</a>
<a href="https://github.com/crossoverJie">
<img src="other/15684156.jpg" width="50px">
</a>
<a href="https://github.com/jy03078584">
<img src="other/7719370.png" width="50px">
</a>
<a href="https://github.com/kwongtailau">
<img src="other/22954582.jpg" width="50px">
</a>
<a href="https://github.com/xiangflight">
<img src="other/10072416.jpg" width="50px">
</a>

View File

@ -27,10 +27,23 @@ This file used to generate gitbook catalogue.
* [Java 虚拟机](/notes/Java 虚拟机.md) * [Java 虚拟机](/notes/Java 虚拟机.md)
* [Java 并发](/notes/Java 并发.md) * [Java 并发](/notes/Java 并发.md)
* [Java 容器](/notes/Java 容器.md) * [Java 容器](/notes/Java 容器.md)
* [Java I/O](/notes/Java I/O.md) * [Java I/O](/notes/Java IO.md)
* 分布式 * 系统设计
* [一致性](/notes/一致性.md) * [系统设计基础](/notes/系统设计基础.md)
* [分布式问题分析](/notes/分布式问题分析.md) * [分布式](/notes/分布式.md)
* [集群](/notes/集群.md)
* [攻击技术](/notes/攻击技术.md)
* [缓存](/notes/缓存.md)
* [消息队列](/notes/消息队列.md)
* 工具
* [Git](/notes/Git.md)
* [Docker](/notes/Docker.md)
* [正则表达式](/notes/正则表达式.md)
* [构建工具](/notes/构建工具.md)
* 编码实践
* [重构](/notes/重构.md)
* [代码可读性](/notes/代码可读性.md)
* [代码风格规范](/notes/代码风格规范.md)
* 参考书目
* [BOOKLIST](/BOOKLIST.md)

View File

@ -0,0 +1,16 @@
<!-- GFM-TOC -->
* [0. 进程内存空间中,堆和栈的区别](#0-进程内存空间中,堆和栈的区别)
<!-- GFM-TOC -->
# 0. 进程内存空间中,堆和栈的区别
> C++
动态、malloc()、new、链式分配、向上生长函数调用、编译器分配回收、向下生长。
https://www.cnblogs.com/sunziying/p/6510030.html
By @CyC
---

View File

@ -4,6 +4,7 @@
* [三、优势](#三优势) * [三、优势](#三优势)
* [四、使用场景](#四使用场景) * [四、使用场景](#四使用场景)
* [五、镜像与容器](#五镜像与容器) * [五、镜像与容器](#五镜像与容器)
* [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
@ -15,11 +16,6 @@
Docker 主要解决环境配置问题,它是一种虚拟化技术,对进程进行隔离,被隔离的进程独立于宿主操作系统和其它隔离的进程。使用 Docker 可以不修改应用程序代码,不需要开发人员学习特定环境下的技术,就能够将现有的应用程序部署在其他机器中。 Docker 主要解决环境配置问题,它是一种虚拟化技术,对进程进行隔离,被隔离的进程独立于宿主操作系统和其它隔离的进程。使用 Docker 可以不修改应用程序代码,不需要开发人员学习特定环境下的技术,就能够将现有的应用程序部署在其他机器中。
参考资料:
- [DOCKER 101: INTRODUCTION TO DOCKER WEBINAR RECAP](https://blog.docker.com/2017/08/docker-101-introduction-docker-webinar-recap/)
- [Docker 入门教程](http://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html)
# 二、与虚拟机的比较 # 二、与虚拟机的比较
虚拟机也是一种虚拟化技术,它与 Docker 最大的区别在于它是通过模拟硬件,并在硬件上安装操作系统来实现。 虚拟机也是一种虚拟化技术,它与 Docker 最大的区别在于它是通过模拟硬件,并在硬件上安装操作系统来实现。
@ -30,19 +26,15 @@ Docker 主要解决环境配置问题,它是一种虚拟化技术,对进程
## 启动速度 ## 启动速度
启动虚拟机需要启动虚拟机的操作系统,再启动相应的应用,这个过程非常慢; 启动虚拟机需要启动虚拟机的操作系统,再启动应用,这个过程非常慢;
而启动 Docker 相当于启动宿主操作系统上的一个进程。 而启动 Docker 相当于启动宿主操作系统上的一个进程。
## 占用资源 ## 占用资源
虚拟机是一个完整的操作系统,需要占用大量的磁盘空间、内存和 CPU一台机器只能开启几十个的虚拟机。 虚拟机是一个完整的操作系统,需要占用大量的磁盘、内存和 CPU一台机器只能开启几十个的虚拟机。
而 Docker 只是一个进程,只需要将应用以及相应的组件打包,在运行时占用很少的资源,一台机器可以开启成千上万个 Docker。 而 Docker 只是一个进程,只需要将应用以及相关的组件打包,在运行时占用很少的资源,一台机器可以开启成千上万个 Docker。
参考资料:
- [Docker container vs Virtual machine](http://www.bogotobogo.com/DevOps/Docker/Docker_Container_vs_Virtual_Machine.php)
# 三、优势 # 三、优势
@ -50,19 +42,15 @@ Docker 主要解决环境配置问题,它是一种虚拟化技术,对进程
## 更容易迁移 ## 更容易迁移
Docker 可以提供一致性的运行环境,可以在不同的机器上进行迁移,而不用担心环境变化导致无法运行。 提供一致性的运行环境,可以在不同的机器上进行迁移,而不用担心环境变化导致无法运行。
## 更容易维护 ## 更容易维护
Docker 使用分层技术和镜像,使得应用可以更容易复用重复部分。复用程度越高,维护工作也越容易。 使用分层技术和镜像,使得应用可以更容易复用重复部分。复用程度越高,维护工作也越容易。
## 更容易扩展 ## 更容易扩展
可以使用基础镜像进一步扩展得到新的镜像,并且官方和开源社区提供了大量的镜像,通过扩展这些镜像得到我们想要的镜像非常容易。 可以使用基础镜像进一步扩展得到新的镜像,并且官方和开源社区提供了大量的镜像,通过扩展这些镜像可以非常容易得到我们想要的镜像。
参考资料:
- [为什么要使用 Docker](https://yeasy.gitbooks.io/docker_practice/introduction/why.html)
# 四、使用场景 # 四、使用场景
@ -80,23 +68,24 @@ Docker 具有轻量级以及隔离性的特点,在将代码集成到一个 Doc
Docker 轻量级的特点使得它很适合用于部署、维护、组合微服务。 Docker 轻量级的特点使得它很适合用于部署、维护、组合微服务。
参考资料:
- [What is Docker](https://www.docker.com/what-docker)
- [持续集成是什么?](http://www.ruanyifeng.com/blog/2015/09/continuous-integration.html)
# 五、镜像与容器 # 五、镜像与容器
镜像是一种静态的结构,可以看成面向对象里面的类,而容器是镜像的一个实例。 镜像是一种静态的结构,可以看成面向对象里面的类,而容器是镜像的一个实例。
镜像包含着容器运行时所需要的代码以及其它组件它是一种分层结构每一层都是只读的read-only layers。构建镜像时会一层一层构建前一层是后一层的基础。镜像的这种分层存储结构很适合镜像的复用以及定制。 镜像包含着容器运行时所需要的代码以及其它组件它是一种分层结构每一层都是只读的read-only layers。构建镜像时会一层一层构建前一层是后一层的基础。镜像的这种分层存储结构很适合镜像的复用以及定制。
构建容器时通过在镜像的基础上添加一个可写层writable layer用来保存着容器运行过程中的修改。 构建容器时通过在镜像的基础上添加一个可写层writable layer用来保存着容器运行过程中的修改。
<div align="center"> <img src="../pics//docker-filesystems-busyboxrw.png"/> </div><br> <div align="center"> <img src="../pics//docker-filesystems-busyboxrw.png"/> </div><br>
参考资料 # 参考资料
- [DOCKER 101: INTRODUCTION TO DOCKER WEBINAR RECAP](https://blog.docker.com/2017/08/docker-101-introduction-docker-webinar-recap/)
- [Docker 入门教程](http://www.ruanyifeng.com/blog/2018/02/docker-tutorial.html)
- [Docker container vs Virtual machine](http://www.bogotobogo.com/DevOps/Docker/Docker_Container_vs_Virtual_Machine.php)
- [How to Create Docker Container using Dockerfile](https://linoxide.com/linux-how-to/dockerfile-create-docker-container/) - [How to Create Docker Container using Dockerfile](https://linoxide.com/linux-how-to/dockerfile-create-docker-container/)
- [理解 Docker2Docker 镜像](http://www.cnblogs.com/sammyliu/p/5877964.html) - [理解 Docker2Docker 镜像](http://www.cnblogs.com/sammyliu/p/5877964.html)
- [为什么要使用 Docker](https://yeasy.gitbooks.io/docker_practice/introduction/why.html)
- [What is Docker](https://www.docker.com/what-docker)
- [持续集成是什么?](http://www.ruanyifeng.com/blog/2015/09/continuous-integration.html)

View File

@ -1,8 +1,7 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [学习资料](#学习资料)
* [集中式与分布式](#集中式与分布式) * [集中式与分布式](#集中式与分布式)
* [Git 的中心服务器](#git-的中心服务器) * [中心服务器](#中心服务器)
* [Git 工作流](#git-工作流) * [工作流](#工作流)
* [分支实现](#分支实现) * [分支实现](#分支实现)
* [冲突](#冲突) * [冲突](#冲突)
* [Fast forward](#fast-forward) * [Fast forward](#fast-forward)
@ -11,16 +10,10 @@
* [SSH 传输设置](#ssh-传输设置) * [SSH 传输设置](#ssh-传输设置)
* [.gitignore 文件](#gitignore-文件) * [.gitignore 文件](#gitignore-文件)
* [Git 命令一览](#git-命令一览) * [Git 命令一览](#git-命令一览)
* [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
# 学习资料
- [Git - 简明指南](http://rogerdudler.github.io/git-guide/index.zh.html)
- [图解 Git](http://marklodato.github.io/visual-git-guide/index-zh-cn.html)
- [廖雪峰 : Git 教程](https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000)
- [Learn Git Branching](https://learngitbranching.js.org/)
# 集中式与分布式 # 集中式与分布式
Git 属于分布式版本控制系统,而 SVN 属于集中式。 Git 属于分布式版本控制系统,而 SVN 属于集中式。
@ -33,11 +26,13 @@ Git 属于分布式版本控制系统,而 SVN 属于集中式。
分布式版本控制新建分支、合并分支操作速度非常快,而集中式版本控制新建一个分支相当于复制一份完整代码。 分布式版本控制新建分支、合并分支操作速度非常快,而集中式版本控制新建一个分支相当于复制一份完整代码。
# Git 的中心服务器 # 中心服务器
Git 的中心服务器用来交换每个用户的修改。没有中心服务器也能工作,但是中心服务器能够 24 小时保持开机状态,这样就能更方便的交换修改。Github 就是一种 Git 中心服务器。 中心服务器用来交换每个用户的修改,没有中心服务器也能工作,但是中心服务器能够 24 小时保持开机状态,这样就能更方便的交换修改。
# Git 工作流 Github 就是一个中心服务器。
# 工作流
<div align="center"> <img src="../pics//a1198642-9159-4d88-8aec-c3b04e7a2563.jpg"/> </div><br> <div align="center"> <img src="../pics//a1198642-9159-4d88-8aec-c3b04e7a2563.jpg"/> </div><br>
@ -54,14 +49,14 @@ Git 版本库有一个称为 stage 的暂存区,还有自动创建的 master
<div align="center"> <img src="../pics//17976404-95f5-480e-9cb4-250e6aa1d55f.png"/> </div><br> <div align="center"> <img src="../pics//17976404-95f5-480e-9cb4-250e6aa1d55f.png"/> </div><br>
可以跳过暂存区域直接从分支中取出修改或者直接提交修改到分支中 可以跳过暂存区域直接从分支中取出修改或者直接提交修改到分支中
- git commit -a 直接把所有文件的修改添加到暂缓区然后执行提交 - git commit -a 直接把所有文件的修改添加到暂缓区然后执行提交
- git checkout HEAD -- files 取出最后一次修改,可以用来进行回滚操作 - git checkout HEAD -- files 取出最后一次修改,可以用来进行回滚操作
# 分支实现 # 分支实现
Git 把每次提交都连成一条时间线。分支使用指针来实现,例如 master 分支指针指向时间线的最后一个节点也就是最后一次提交。HEAD 指针指向的是当前分支 使用指针将每个提交连接成一条时间线HEAD 指针指向当前分支指针
<div align="center"> <img src="../pics//fb546e12-e1fb-4b72-a1fb-8a7f5000dce6.jpg"/> </div><br> <div align="center"> <img src="../pics//fb546e12-e1fb-4b72-a1fb-8a7f5000dce6.jpg"/> </div><br>
@ -69,7 +64,7 @@ Git 把每次提交都连成一条时间线。分支使用指针来实现,例
<div align="center"> <img src="../pics//bc775758-89ab-4805-9f9c-78b8739cf780.jpg"/> </div><br> <div align="center"> <img src="../pics//bc775758-89ab-4805-9f9c-78b8739cf780.jpg"/> </div><br>
每次提交只会让当前分支向前移动,而其它分支不会移动。 每次提交只会让当前分支指针向前移动,而其它分支指针不会移动。
<div align="center"> <img src="../pics//5292faa6-0141-4638-bf0f-bb95b081dcba.jpg"/> </div><br> <div align="center"> <img src="../pics//5292faa6-0141-4638-bf0f-bb95b081dcba.jpg"/> </div><br>
@ -155,4 +150,9 @@ $ ssh-keygen -t rsa -C "youremail@example.com"
比较详细的地址http://www.cheat-sheets.org/saved-copy/git-cheat-sheet.pdf 比较详细的地址http://www.cheat-sheets.org/saved-copy/git-cheat-sheet.pdf
# 参考资料
- [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/)

View File

@ -24,9 +24,9 @@
* [响应首部字段](#响应首部字段) * [响应首部字段](#响应首部字段)
* [实体首部字段](#实体首部字段) * [实体首部字段](#实体首部字段)
* [五、具体应用](#五具体应用) * [五、具体应用](#五具体应用)
* [连接管理](#连接管理)
* [Cookie](#cookie) * [Cookie](#cookie)
* [缓存](#缓存) * [缓存](#缓存)
* [连接管理](#连接管理)
* [内容协商](#内容协商) * [内容协商](#内容协商)
* [内容编码](#内容编码) * [内容编码](#内容编码)
* [范围请求](#范围请求) * [范围请求](#范围请求)
@ -39,20 +39,19 @@
* [认证](#认证) * [认证](#认证)
* [完整性保护](#完整性保护) * [完整性保护](#完整性保护)
* [HTTPs 的缺点](#https-的缺点) * [HTTPs 的缺点](#https-的缺点)
* [配置 HTTPs](#配置-https)
* [七、HTTP/2.0](#七http20) * [七、HTTP/2.0](#七http20)
* [HTTP/1.x 缺陷](#http1x-缺陷) * [HTTP/1.x 缺陷](#http1x-缺陷)
* [二进制分帧层](#二进制分帧层) * [二进制分帧层](#二进制分帧层)
* [服务端推送](#服务端推送) * [服务端推送](#服务端推送)
* [首部压缩](#首部压缩) * [首部压缩](#首部压缩)
* [八、GET 和 POST 比较](#八get-和-post-比较) * [八、HTTP/1.1 新特性](#八http11-新特性)
* [九、GET 和 POST 比较](#九get-和-post-比较)
* [作用](#作用) * [作用](#作用)
* [参数](#参数) * [参数](#参数)
* [安全](#安全) * [安全](#安全)
* [幂等性](#幂等性) * [幂等性](#幂等性)
* [可缓存](#可缓存) * [可缓存](#可缓存)
* [XMLHttpRequest](#xmlhttprequest) * [XMLHttpRequest](#xmlhttprequest)
* [九、HTTP/1.0 与 HTTP/1.1 的区别](#九http10-与-http11-的区别)
* [参考资料](#参考资料) * [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
@ -103,7 +102,7 @@ URI 包含 URL 和 URN目前 WEB 只有 URL 比较流行,所以见到的基
POST 主要用来传输数据,而 GET 主要用来获取资源。 POST 主要用来传输数据,而 GET 主要用来获取资源。
更多 POST 与 GET 的比较请见第章。 更多 POST 与 GET 的比较请见第章。
## PUT ## PUT
@ -304,13 +303,32 @@ CONNECT www.example.com:443 HTTP/1.1
# 五、具体应用 # 五、具体应用
## 连接管理
<div align="center"> <img src="../pics//HTTP1_x_Connections.png" width="800"/> </div><br>
### 1. 短连接与长连接
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。
长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
- 从 HTTP/1.1 开始默认是长连接的,如果要断开连接,需要由客户端或者服务器端提出断开,使用 `Connection : close`
- 在 HTTP/1.1 之前默认是短连接的,如果需要使用长连接,则使用 `Connection : Keep-Alive`
### 2. 流水线
默认情况下HTTP 请求是按顺序发出的,下一个请求只有在当前请求收到响应之后才会被发出。由于会受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。
流水线是在同一条长连接上发出连续的请求,而不用等待响应返回,这样可以避免连接延迟。
## Cookie ## Cookie
HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。 HTTP 协议是无状态的,主要是为了让 HTTP 协议尽可能简单使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。
Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。 Cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器之后向同一服务器再次发起请求时被携带上,用于告知服务端两个请求是否来自同一浏览器。由于之后每次请求都会需要携带 Cookie 数据,因此会带来额外的性能开销(尤其是在移动环境下)。
Cookie 曾一度用于客户端数据的存储因为当时并没有其它合适的存储办法而作为唯一的存储手段但现在随着现代浏览器开始支持各种各样的存储方式Cookie 渐渐被淘汰。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API (本地存储和会话存储)或 IndexedDB。 Cookie 曾一度用于客户端数据的存储因为当时并没有其它合适的存储办法而作为唯一的存储手段但现在随着现代浏览器开始支持各种各样的存储方式Cookie 渐渐被淘汰。新的浏览器 API 已经允许开发者直接将数据存储到本地,如使用 Web storage API本地存储和会话存储或 IndexedDB。
### 1. 用途 ### 1. 用途
@ -348,27 +366,7 @@ Cookie: yummy_cookie=choco; tasty_cookie=strawberry
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
``` ```
### 4. JavaScript 获取 Cookie ### 4. 作用域
通过 `Document.cookie` 属性可创建新的 Cookie也可通过该属性访问非 HttpOnly 标记的 Cookie。
```html
document.cookie = "yummy_cookie=choco";
document.cookie = "tasty_cookie=strawberry";
console.log(document.cookie);
```
### 5. Secure 和 HttpOnly
标记为 Secure 的 Cookie 只能通过被 HTTPS 协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为 Cookie 有其固有的不安全性Secure 标记也无法提供确实的安全保障。
标记为 HttpOnly 的 Cookie 不能被 JavaScript 脚本调用。跨站脚本攻击 (XSS) 常常使用 JavaScript 的 `Document.cookie` API 窃取用户的 Cookie 信息,因此使用 HttpOnly 标记可以在一定程度上避免 XSS 攻击。
```html
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
```
### 6. 作用域
Domain 标识指定了哪些主机可以接受 Cookie。如果不指定默认为当前文档的主机不包含子域名。如果指定了 Domain则一般包含子域名。例如如果设置 Domain=mozilla.org则 Cookie 也包含在子域名中(如 developer.mozilla.org Domain 标识指定了哪些主机可以接受 Cookie。如果不指定默认为当前文档的主机不包含子域名。如果指定了 Domain则一般包含子域名。例如如果设置 Domain=mozilla.org则 Cookie 也包含在子域名中(如 developer.mozilla.org
@ -378,7 +376,29 @@ Path 标识指定了主机下的哪些路径可以接受 Cookie该 URL 路径
- /docs/Web/ - /docs/Web/
- /docs/Web/HTTP - /docs/Web/HTTP
### 7. Session ### 5. JavaScript
通过 `document.cookie` 属性可创建新的 Cookie也可通过该属性访问非 HttpOnly 标记的 Cookie。
```html
document.cookie = "yummy_cookie=choco";
document.cookie = "tasty_cookie=strawberry";
console.log(document.cookie);
```
### 6. HttpOnly
标记为 HttpOnly 的 Cookie 不能被 JavaScript 脚本调用。跨站脚本攻击 (XSS) 常常使用 JavaScript 的 `document.cookie` API 窃取用户的 Cookie 信息,因此使用 HttpOnly 标记可以在一定程度上避免 XSS 攻击。
```html
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
```
### 7. Secure
标记为 Secure 的 Cookie 只能通过被 HTTPS 协议加密过的请求发送给服务端。但即便设置了 Secure 标记,敏感信息也不应该通过 Cookie 传输,因为 Cookie 有其固有的不安全性Secure 标记也无法提供确实的安全保障。
### 8. Session
除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。 除了可以将用户信息通过 Cookie 存储在用户浏览器中,也可以利用 Session 存储在服务器端,存储在服务器端的信息更加安全。
@ -387,18 +407,17 @@ Session 可以存储在服务器上的文件、数据库或者内存中。也可
使用 Session 维护用户登录状态的过程如下: 使用 Session 维护用户登录状态的过程如下:
- 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中; - 用户进行登录时,用户提交包含用户名和密码的表单,放入 HTTP 请求报文中;
- 服务器验证该用户名和密码; - 服务器验证该用户名和密码,如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID
- 如果正确则把用户信息存储到 Redis 中,它在 Redis 中的 Key 称为 Session ID
- 服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID客户端收到响应报文之后将该 Cookie 值存入浏览器中; - 服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID客户端收到响应报文之后将该 Cookie 值存入浏览器中;
- 客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID从 Redis 中取出用户信息,继续之前的业务操作。 - 客户端之后对同一个服务器进行请求时会包含该 Cookie 值,服务器收到之后提取出 Session ID从 Redis 中取出用户信息,继续之前的业务操作。
应该注意 Session ID 的安全性问题,不能让它被恶意攻击者轻易获取,那么就不能产生一个容易被猜到的 Session ID 值。此外,还需要经常重新生成 Session ID。在对安全性要求极高的场景下例如转账等操作除了使用 Session 管理用户状态之外,还需要对用户进行重新验证,比如重新输入密码,或者使用短信验证码等方式。 应该注意 Session ID 的安全性问题,不能让它被恶意攻击者轻易获取,那么就不能产生一个容易被猜到的 Session ID 值。此外,还需要经常重新生成 Session ID。在对安全性要求极高的场景下例如转账等操作除了使用 Session 管理用户状态之外,还需要对用户进行重新验证,比如重新输入密码,或者使用短信验证码等方式。
### 8. 浏览器禁用 Cookie ### 9. 浏览器禁用 Cookie
此时无法使用 Cookie 来保存用户信息,只能使用 Session。除此之外不能再将 Session ID 存放到 Cookie 中,而是使用 URL 重写技术,将 Session ID 作为 URL 的参数进行传递。 此时无法使用 Cookie 来保存用户信息,只能使用 Session。除此之外不能再将 Session ID 存放到 Cookie 中,而是使用 URL 重写技术,将 Session ID 作为 URL 的参数进行传递。
### 9. Cookie 与 Session 选择 ### 10. Cookie 与 Session 选择
- Cookie 只能存储 ASCII 码字符串,而 Session 则可以存取任何类型的数据,因此在考虑数据复杂性时首选 Session - Cookie 只能存储 ASCII 码字符串,而 Session 则可以存取任何类型的数据,因此在考虑数据复杂性时首选 Session
- Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密; - Cookie 存储在浏览器中,容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中,可以将 Cookie 值进行加密,然后在服务器进行解密;
@ -420,7 +439,7 @@ Session 可以存储在服务器上的文件、数据库或者内存中。也可
HTTP/1.1 通过 Cache-Control 首部字段来控制缓存。 HTTP/1.1 通过 Cache-Control 首部字段来控制缓存。
**(一)禁止进行缓存** **3.1 禁止进行缓存**
no-store 指令规定不能对请求或响应的任何一部分进行缓存。 no-store 指令规定不能对请求或响应的任何一部分进行缓存。
@ -428,7 +447,7 @@ no-store 指令规定不能对请求或响应的任何一部分进行缓存。
Cache-Control: no-store Cache-Control: no-store
``` ```
**(二)强制确认缓存** **3.2 强制确认缓存**
no-cache 指令规定缓存服务器需要先向源服务器验证缓存资源的有效性,只有当缓存资源有效才将能使用该缓存对客户端的请求进行响应。 no-cache 指令规定缓存服务器需要先向源服务器验证缓存资源的有效性,只有当缓存资源有效才将能使用该缓存对客户端的请求进行响应。
@ -436,7 +455,7 @@ no-cache 指令规定缓存服务器需要先向源服务器验证缓存资源
Cache-Control: no-cache Cache-Control: no-cache
``` ```
**(三)私有缓存和公共缓存** **3.3 私有缓存和公共缓存**
private 指令规定了将资源作为私有缓存,只能被单独用户所使用,一般存储在用户浏览器中。 private 指令规定了将资源作为私有缓存,只能被单独用户所使用,一般存储在用户浏览器中。
@ -450,7 +469,7 @@ public 指令规定了将资源作为公共缓存,可以被多个用户所使
Cache-Control: public Cache-Control: public
``` ```
**(四)缓存过期机制** **3.4 缓存过期机制**
max-age 指令出现在请求报文中,并且缓存资源的缓存时间小于该指令指定的时间,那么就能接受该缓存。 max-age 指令出现在请求报文中,并且缓存资源的缓存时间小于该指令指定的时间,那么就能接受该缓存。
@ -462,13 +481,13 @@ Cache-Control: max-age=31536000
Expires 首部字段也可以用于告知缓存服务器该资源什么时候会过期。 Expires 首部字段也可以用于告知缓存服务器该资源什么时候会过期。
- 在 HTTP/1.1 中,会优先处理 max-age 指令;
- 在 HTTP/1.0 中max-age 指令会被忽略掉。
```html ```html
Expires: Wed, 04 Jul 2012 08:26:05 GMT Expires: Wed, 04 Jul 2012 08:26:05 GMT
``` ```
- 在 HTTP/1.1 中,会优先处理 max-age 指令;
- 在 HTTP/1.0 中max-age 指令会被忽略掉。
### 4. 缓存验证 ### 4. 缓存验证
需要先了解 ETag 首部字段的含义它是资源的唯一标识。URL 不能唯一表示资源,例如 `http://www.google.com/` 有中文和英文两个资源,只有 ETag 才能对这两个资源进行唯一标识。 需要先了解 ETag 首部字段的含义它是资源的唯一标识。URL 不能唯一表示资源,例如 `http://www.google.com/` 有中文和英文两个资源,只有 ETag 才能对这两个资源进行唯一标识。
@ -493,34 +512,15 @@ Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
``` ```
## 连接管理
<div align="center"> <img src="../pics//HTTP1_x_Connections.png" width="800"/> </div><br>
### 1. 短连接与长连接
当浏览器访问一个包含多张图片的 HTML 页面时,除了请求访问 HTML 页面资源,还会请求图片资源。如果每进行一次 HTTP 通信就要新建一个 TCP 连接,那么开销会很大。
长连接只需要建立一次 TCP 连接就能进行多次 HTTP 通信。
- 从 HTTP/1.1 开始默认是长连接的,如果要断开连接,需要由客户端或者服务器端提出断开,使用 `Connection : close`
- 在 HTTP/1.1 之前默认是短连接的,如果需要使用长连接,则使用 `Connection : Keep-Alive`
### 2. 流水线
默认情况下HTTP 请求是按顺序发出的,下一个请求只有在当前请求收到响应之后才会被发出。由于会受到网络延迟和带宽的限制,在下一个请求被发送到服务器之前,可能需要等待很长时间。
流水线是在同一条长连接上发出连续的请求,而不用等待响应返回,这样可以避免连接延迟。
## 内容协商 ## 内容协商
通过内容协商返回最合适的内容,例如根据浏览器的默认语言选择返回中文界面还是英文界面。 通过内容协商返回最合适的内容,例如根据浏览器的默认语言选择返回中文界面还是英文界面。
### 1. 类型 ### 1. 类型
**(一)服务端驱动型** **1.1 服务端驱动型**
客户端设置特定的 HTTP 首部字段,例如 Accept、Accept-Charset、Accept-Encoding、Accept-Language、Content-Languag,服务器根据这些字段返回特定的资源。 客户端设置特定的 HTTP 首部字段,例如 Accept、Accept-Charset、Accept-Encoding、Accept-Language服务器根据这些字段返回特定的资源。
它存在以下问题: 它存在以下问题:
@ -528,7 +528,7 @@ If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
- 客户端提供的信息相当冗长HTTP/2 协议的首部压缩机制缓解了这个问题并且存在隐私风险HTTP 指纹识别技术); - 客户端提供的信息相当冗长HTTP/2 协议的首部压缩机制缓解了这个问题并且存在隐私风险HTTP 指纹识别技术);
- 给定的资源需要返回不同的展现形式,共享缓存的效率会降低,而服务器端的实现会越来越复杂。 - 给定的资源需要返回不同的展现形式,共享缓存的效率会降低,而服务器端的实现会越来越复杂。
**(二)代理驱动型** **1.2 代理驱动型**
服务器返回 300 Multiple Choices 或者 406 Not Acceptable客户端从中选出最合适的那个资源。 服务器返回 300 Multiple Choices 或者 406 Not Acceptable客户端从中选出最合适的那个资源。
@ -718,16 +718,11 @@ HTTPs 的报文摘要功能之所以安全,是因为它结合了加密和认
- 因为需要进行加密解密等过程,因此速度会更慢; - 因为需要进行加密解密等过程,因此速度会更慢;
- 需要支付证书授权的高额费用。 - 需要支付证书授权的高额费用。
## 配置 HTTPs
[Nginx 配置 HTTPS 服务器](https://aotu.io/notes/2016/08/16/nginx-https/index.html)
# 七、HTTP/2.0 # 七、HTTP/2.0
## HTTP/1.x 缺陷 ## HTTP/1.x 缺陷
HTTP/1.x 实现简单是以牺牲性能为代价的: HTTP/1.x 实现简单是以牺牲性能为代价的:
- 客户端需要使用多个连接才能实现并发和缩短延迟; - 客户端需要使用多个连接才能实现并发和缩短延迟;
- 不会压缩请求和响应首部,从而导致不必要的网络流量; - 不会压缩请求和响应首部,从而导致不必要的网络流量;
@ -741,9 +736,9 @@ HTTP/2.0 将报文分成 HEADERS 帧和 DATA 帧,它们都是二进制格式
在通信过程中,只会有一个 TCP 连接存在它承载了任意数量的双向数据流Stream 在通信过程中,只会有一个 TCP 连接存在它承载了任意数量的双向数据流Stream
- 一个数据流都有一个唯一标识符和可选的优先级信息,用于承载双向信息。 - 一个数据流Stream都有一个唯一标识符和可选的优先级信息,用于承载双向信息。
- 消息Message是与逻辑请求或响应消息对应的完整的一系列帧。 - 消息Message是与逻辑请求或响应对应的完整的一系列帧。
- 帧Fram是最小的通信单位来自不同数据流的帧可以交错发送然后再根据每个帧头的数据流标识符重新组装。 - 帧Frame)是最小的通信单位,来自不同数据流的帧可以交错发送,然后再根据每个帧头的数据流标识符重新组装。
<div align="center"> <img src="../pics//af198da1-2480-4043-b07f-a3b91a88b815.png" width="600"/> </div><br> <div align="center"> <img src="../pics//af198da1-2480-4043-b07f-a3b91a88b815.png" width="600"/> </div><br>
@ -763,7 +758,25 @@ HTTP/2.0 要求客户端和服务器同时维护和更新一个包含之前见
<div align="center"> <img src="../pics//_u4E0B_u8F7D.png" width="600"/> </div><br> <div align="center"> <img src="../pics//_u4E0B_u8F7D.png" width="600"/> </div><br>
# 八、GET 和 POST 比较 # 八、HTTP/1.1 新特性
详细内容请见上文
- 默认是长连接
- 支持流水线
- 支持同时打开多个 TCP 连接
- 支持虚拟主机
- 新增状态码 100
- 支持分块传输编码
- 新增缓存处理指令 max-age
# 九、GET 和 POST 比较
## 作用 ## 作用
@ -845,23 +858,6 @@ DELETE /idX/delete HTTP/1.1 -> Returns 404
- 在使用 XMLHttpRequest 的 POST 方法时,浏览器会先发送 Header 再发送 Data。但并不是所有浏览器会这么做例如火狐就不会。 - 在使用 XMLHttpRequest 的 POST 方法时,浏览器会先发送 Header 再发送 Data。但并不是所有浏览器会这么做例如火狐就不会。
- 而 GET 方法 Header 和 Data 会一起发送。 - 而 GET 方法 Header 和 Data 会一起发送。
# 九、HTTP/1.0 与 HTTP/1.1 的区别
> 详细内容请见上文
- HTTP/1.1 默认是长连接
- HTTP/1.1 支持管线化处理
- HTTP/1.1 支持同时打开多个 TCP 连接
- HTTP/1.1 支持虚拟主机
- HTTP/1.1 新增状态码 100
- HTTP/1.1 支持分块传输编码
- HTTP/1.1 新增缓存处理指令 max-age
# 参考资料 # 参考资料

View File

@ -6,7 +6,7 @@
* [装饰者模式](#装饰者模式) * [装饰者模式](#装饰者模式)
* [四、字符操作](#四字符操作) * [四、字符操作](#四字符操作)
* [编码与解码](#编码与解码) * [编码与解码](#编码与解码)
* [String](#string) * [String 的编码方式](#string-的编码方式)
* [Reader 与 Writer](#reader-与-writer) * [Reader 与 Writer](#reader-与-writer)
* [实现逐行输出文本文件的内容](#实现逐行输出文本文件的内容) * [实现逐行输出文本文件的内容](#实现逐行输出文本文件的内容)
* [五、对象操作](#五对象操作) * [五、对象操作](#五对象操作)
@ -69,16 +69,17 @@ public static void listAllFiles(File dir) {
```java ```java
public static void copyFile(String src, String dist) throws IOException { public static void copyFile(String src, String dist) throws IOException {
FileInputStream in = new FileInputStream(src); FileInputStream in = new FileInputStream(src);
FileOutputStream out = new FileOutputStream(dist); FileOutputStream out = new FileOutputStream(dist);
byte[] buffer = new byte[20 * 1024]; byte[] buffer = new byte[20 * 1024];
int cnt;
// read() 最多读取 buffer.length 个字节 // read() 最多读取 buffer.length 个字节
// 返回的是实际读取的个数 // 返回的是实际读取的个数
// 返回 -1 的时候表示读到 eof即文件尾 // 返回 -1 的时候表示读到 eof即文件尾
while (in.read(buffer, 0, buffer.length) != -1) { while ((cnt = in.read(buffer, 0, buffer.length)) != -1) {
out.write(buffer); out.write(buffer, 0, cnt);
} }
in.close(); in.close();
@ -121,7 +122,7 @@ UTF-16be 中的 be 指的是 Big Endian也就是大端。相应地也有 UTF-
Java 使用双字节编码 UTF-16be这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位也就是两个字节Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。 Java 使用双字节编码 UTF-16be这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位也就是两个字节Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。
## String ## String 的编码方式
String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。 String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。
@ -182,8 +183,10 @@ public static void readFileContent(String filePath) throws IOException {
```java ```java
public static void main(String[] args) throws IOException, ClassNotFoundException { public static void main(String[] args) throws IOException, ClassNotFoundException {
A a1 = new A(123, "abc"); A a1 = new A(123, "abc");
String objectFile = "file/a1"; String objectFile = "file/a1";
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(objectFile)); ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(objectFile));
objectOutputStream.writeObject(a1); objectOutputStream.writeObject(a1);
objectOutputStream.close(); objectOutputStream.close();
@ -195,6 +198,7 @@ public static void main(String[] args) throws IOException, ClassNotFoundExceptio
} }
private static class A implements Serializable { private static class A implements Serializable {
private int x; private int x;
private String y; private String y;
@ -280,10 +284,6 @@ public static void main(String[] args) throws IOException {
# 七、NIO # 七、NIO
- [Java NIO Tutorial](http://tutorials.jenkov.com/java-nio/index.html)
- [Java NIO 浅析](https://tech.meituan.com/nio.html)
- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html)
新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。 新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。
## 流与块 ## 流与块
@ -339,7 +339,7 @@ I/O 包和 NIO 已经很好地集成了java.io.\* 已经以 NIO 为基础重
<div align="center"> <img src="../pics//1bea398f-17a7-4f67-a90b-9e2d243eaa9a.png"/> </div><br> <div align="center"> <img src="../pics//1bea398f-17a7-4f67-a90b-9e2d243eaa9a.png"/> </div><br>
② 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 移动设置为 5limit 保持不变。 ② 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 为 5limit 保持不变。
<div align="center"> <img src="../pics//80804f52-8815-4096-b506-48eef3eed5c6.png"/> </div><br> <div align="center"> <img src="../pics//80804f52-8815-4096-b506-48eef3eed5c6.png"/> </div><br>
@ -371,7 +371,7 @@ public static void fastCopy(String src, String dist) throws IOException {
/* 获取目标文件的输出字节流 */ /* 获取目标文件的输出字节流 */
FileOutputStream fout = new FileOutputStream(dist); FileOutputStream fout = new FileOutputStream(dist);
/* 获取输出字节流的通道 */ /* 获取输出字节流的文件通道 */
FileChannel fcout = fout.getChannel(); FileChannel fcout = fout.getChannel();
/* 为缓冲区分配 1024 个字节 */ /* 为缓冲区分配 1024 个字节 */
@ -392,7 +392,7 @@ public static void fastCopy(String src, String dist) throws IOException {
/* 把缓冲区的内容写入输出文件中 */ /* 把缓冲区的内容写入输出文件中 */
fcout.write(buffer); fcout.write(buffer);
/* 清空缓冲区 */ /* 清空缓冲区 */
buffer.clear(); buffer.clear();
} }
@ -407,7 +407,7 @@ NIO 实现了 IO 多路复用中的 Reactor 模型,一个线程 Thread 使用
通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel找到 IO 事件已经到达的 Channel 执行。 通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel找到 IO 事件已经到达的 Channel 执行。
因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件具有更好的性能。 因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件,对于 IO 密集型的应用具有很好地性能。
应该注意的是,只有套接字 Channel 才能配置为非阻塞,而 FileChannel 不能,为 FileChannel 配置非阻塞也没有意义。 应该注意的是,只有套接字 Channel 才能配置为非阻塞,而 FileChannel 不能,为 FileChannel 配置非阻塞也没有意义。
@ -601,13 +601,15 @@ MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
NIO 与普通 I/O 的区别主要有以下两点: NIO 与普通 I/O 的区别主要有以下两点:
- NIO 是非阻塞的 - NIO 是非阻塞的
- NIO 面向块I/O 面向流 - NIO 面向块I/O 面向流
# 八、参考资料 # 八、参考资料
- Eckel B, 埃克尔, 昊鹏, 等. Java 编程思想 [M]. 机械工业出版社, 2002. - Eckel B, 埃克尔, 昊鹏, 等. Java 编程思想 [M]. 机械工业出版社, 2002.
- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html) - [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html)
- [Java NIO Tutorial](http://tutorials.jenkov.com/java-nio/index.html)
- [Java NIO 浅析](https://tech.meituan.com/nio.html)
- [IBM: 深入分析 Java I/O 的工作机制](https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html) - [IBM: 深入分析 Java I/O 的工作机制](https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html)
- [IBM: 深入分析 Java 中的中文编码问题](https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/index.htm) - [IBM: 深入分析 Java 中的中文编码问题](https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/index.htm)
- [IBM: Java 序列化的高级认识](https://www.ibm.com/developerworks/cn/java/j-lo-serial/index.html) - [IBM: Java 序列化的高级认识](https://www.ibm.com/developerworks/cn/java/j-lo-serial/index.html)

View File

@ -6,7 +6,8 @@
* [概览](#概览) * [概览](#概览)
* [不可变的好处](#不可变的好处) * [不可变的好处](#不可变的好处)
* [String, StringBuffer and StringBuilder](#string,-stringbuffer-and-stringbuilder) * [String, StringBuffer and StringBuilder](#string,-stringbuffer-and-stringbuilder)
* [String.intern()](#stringintern) * [String Pool](#string-pool)
* [new String("abc")](#new-string"abc")
* [三、运算](#三运算) * [三、运算](#三运算)
* [参数传递](#参数传递) * [参数传递](#参数传递)
* [float 与 double](#float-与-double) * [float 与 double](#float-与-double)
@ -64,7 +65,7 @@ int y = x; // 拆箱
new Integer(123) 与 Integer.valueOf(123) 的区别在于: new Integer(123) 与 Integer.valueOf(123) 的区别在于:
- new Integer(123) 每次都会新建一个对象 - new Integer(123) 每次都会新建一个对象
- Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。 - Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。
```java ```java
@ -193,43 +194,101 @@ String 不可变性天生具备线程安全,可以在多个线程中安全地
[StackOverflow : String, StringBuffer, and StringBuilder](https://stackoverflow.com/questions/2971315/string-stringbuffer-and-stringbuilder) [StackOverflow : String, StringBuffer, and StringBuilder](https://stackoverflow.com/questions/2971315/string-stringbuffer-and-stringbuilder)
## String.intern() ## String Pool
使用 String.intern() 可以保证相同内容的字符串变量引用同一的内存对象 字符串常量池String Pool保存着所有字符串字面量literal strings这些字面量在编译时期就确定。不仅如此还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Pool 中
下面示例中s1 和 s2 采用 new String() 的方式新建了两个不同对象,而 s3 是通过 s1.intern() 方法取得一个对象引用。intern() 首先把 s1 引用的对象放到 String Pool字符串常量池然后返回这个对象引用。因此 s3 和 s1 引用的是同一个字符串常量池的对象。 当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。
下面示例中s1 和 s2 采用 new String() 的方式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 方法取得一个字符串引用。intern() 首先把 s1 引用的字符串放到 String Pool 中,然后返回这个字符串引用。因此 s3 和 s4 引用的是同一个字符串。
```java ```java
String s1 = new String("aaa"); String s1 = new String("aaa");
String s2 = new String("aaa"); String s2 = new String("aaa");
System.out.println(s1 == s2); // false System.out.println(s1 == s2); // false
String s3 = s1.intern(); String s3 = s1.intern();
System.out.println(s1.intern() == s3); // true String s4 = s1.intern();
System.out.println(s3 == s4); // true
``` ```
如果是采用 "bbb" 这种使用双引号的形式创建字符串实例,会自动地将新建的对象放入 String Pool 中。 如果是采用 "bbb" 这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。
```java ```java
String s4 = "bbb";
String s5 = "bbb"; String s5 = "bbb";
System.out.println(s4 == s5); // true String s6 = "bbb";
System.out.println(s5 == s6); // true
``` ```
在 Java 7 之前,字符串常量池被放在运行时常量池中,它属于永久代。而在 Java 7字符串常量池被移到 Native Method 中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。 在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。
- [StackOverflow : What is String interning?](https://stackoverflow.com/questions/10578984/what-is-string-interning) - [StackOverflow : What is String interning?](https://stackoverflow.com/questions/10578984/what-is-string-interning)
- [深入解析 String#intern](https://tech.meituan.com/in_depth_understanding_string_intern.html) - [深入解析 String#intern](https://tech.meituan.com/in_depth_understanding_string_intern.html)
## new String("abc")
使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 "abc" 字符串对象)。
- "abc" 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个 "abc" 字符串字面量;
- 而使用 new 的方式会在堆中创建一个字符串对象。
创建一个测试类,其 main 方法中使用这种方式来创建字符串对象。
```java
public class NewStringTest {
public static void main(String[] args) {
String s = new String("abc");
}
}
```
使用 javap -verbose 进行反编译,得到以下内容:
```java
// ...
Constant pool:
// ...
#2 = Class #18 // java/lang/String
#3 = String #19 // abc
// ...
#18 = Utf8 java/lang/String
#19 = Utf8 abc
// ...
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=2, args_size=1
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String abc
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
// ...
```
在 Constant Pool 中,#19 存储这字符串字面量 "abc"#3 是 String Pool 的字符串对象,它指向 #19 这个字符串字面量。在 main 方法中0: 行使用 new #2 在堆中创建一个字符串对象,并且使用 ldc #3 将 String Pool 中的字符串对象作为 String 构造函数的参数。
以下是 String 构造函数的源码,可以看到,在将一个字符串对象作为另一个字符串对象的构造函数参数时,并不会完全复制 value 数组内容,而是都会指向同一个 value 数组。
```java
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
```
# 三、运算 # 三、运算
## 参数传递 ## 参数传递
Java 的参数是以值传递的形式传入方法中,而不是引用传递。 Java 的参数是以值传递的形式传入方法中,而不是引用传递。
以下代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。因此在方法中改变指针引用的对象,那么这两个指针此时指向的是完全不同的对象,一方改变其所指向对象的内容对另一方没有影响。 以下代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中。因此在方法中使指针引用其它对象,那么这两个指针此时指向的是完全不同的对象,一方改变其所指向对象的内容对另一方没有影响。
```java ```java
public class Dog { public class Dog {
String name; String name;
Dog(String name) { Dog(String name) {
@ -269,7 +328,7 @@ public class PassByValueExample {
} }
``` ```
但是如果在方法中改变对象的字段值会改变原对象该字段值,因为改变的是同一个地址指向的内容。 如果在方法中改变对象的字段值会改变原对象该字段值,因为改变的是同一个地址指向的内容。
```java ```java
class PassByValueExample { class PassByValueExample {
@ -289,7 +348,9 @@ class PassByValueExample {
## float 与 double ## float 与 double
1.1 字面量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量因为这是向下转型。Java 不能隐式执行向下转型,因为这会使得精度降低。 Java 不能隐式执行向下转型,因为这会使得精度降低。
1.1 字面量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型。
```java ```java
// float f = 1.1; // float f = 1.1;
@ -310,10 +371,11 @@ short s1 = 1;
// s1 = s1 + 1; // s1 = s1 + 1;
``` ```
但是使用 += 运算符可以执行隐式类型转换。 但是使用 += 或者 ++ 运算符可以执行隐式类型转换。
```java ```java
s1 += 1; s1 += 1;
// s1++;
``` ```
上面的语句相当于将 s1 + 1 的计算结果进行了向下转型: 上面的语句相当于将 s1 + 1 的计算结果进行了向下转型:
@ -373,7 +435,7 @@ protected 用于修饰成员,表示在继承体系中成员对于子类可见
如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。 如果子类的方法重写了父类的方法,那么子类中该方法的访问级别不允许低于父类的访问级别。这是为了确保可以使用父类实例的地方都可以使用子类实例,也就是确保满足里氏替换原则。
字段决不能是公有的因为这么做的话就失去了对这个字段修改行为的控制客户端可以对其随意修改。例如下面的例子中AccessExample 拥有 id 有字段,如果在某个时刻,我们想要使用 int 存储 id 字段,那么就需要修改所有的客户端代码。 字段决不能是公有的因为这么做的话就失去了对这个字段修改行为的控制客户端可以对其随意修改。例如下面的例子中AccessExample 拥有 id 有字段,如果在某个时刻,我们想要使用 int 存储 id 字段,那么就需要修改所有的客户端代码。
```java ```java
public class AccessExample { public class AccessExample {
@ -403,6 +465,7 @@ public class AccessExample {
```java ```java
public class AccessWithInnerClassExample { public class AccessWithInnerClassExample {
private class InnerClass { private class InnerClass {
int x; int x;
} }
@ -468,6 +531,7 @@ ac2.func1();
```java ```java
public interface InterfaceExample { public interface InterfaceExample {
void func1(); void func1();
default void func2(){ default void func2(){
@ -519,7 +583,7 @@ System.out.println(InterfaceExample.x);
- 需要能控制继承来的成员的访问权限,而不是都为 public。 - 需要能控制继承来的成员的访问权限,而不是都为 public。
- 需要继承非静态和非常量字段。 - 需要继承非静态和非常量字段。
在很多情况下,接口优先于抽象类因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。 在很多情况下,接口优先于抽象类因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。
- [深入理解 abstract class 和 interface](https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/) - [深入理解 abstract class 和 interface](https://www.ibm.com/developerworks/cn/java/l-javainterface-abstract/)
- [When to Use Abstract Class and Interface](https://dzone.com/articles/when-to-use-abstract-class-and-intreface) - [When to Use Abstract Class and Interface](https://dzone.com/articles/when-to-use-abstract-class-and-intreface)
@ -527,10 +591,11 @@ System.out.println(InterfaceExample.x);
## super ## super
- 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。 - 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。
- 访问父类的成员:如果子类重写了父类的某个方法的实现,可以通过使用 super 关键字来引用父类的方法实现。 - 访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
```java ```java
public class SuperExample { public class SuperExample {
protected int x; protected int x;
protected int y; protected int y;
@ -547,6 +612,7 @@ public class SuperExample {
```java ```java
public class SuperExtendExample extends SuperExample { public class SuperExtendExample extends SuperExample {
private int z; private int z;
public SuperExtendExample(int x, int y, int z) { public SuperExtendExample(int x, int y, int z) {
@ -598,7 +664,6 @@ SuperExtendExample.func()
## 概览 ## 概览
```java ```java
public final native Class<?> getClass()
public native int hashCode() public native int hashCode()
@ -608,6 +673,10 @@ protected native Object clone() throws CloneNotSupportedException
public String toString() public String toString()
public final native Class<?> getClass()
protected void finalize() throws Throwable {}
public final native void notify() public final native void notify()
public final native void notifyAll() public final native void notifyAll()
@ -617,34 +686,32 @@ 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 {}
``` ```
## equals() ## equals()
**1. 等价关系** **1. 等价关系**
(一)自反性 自反性
```java ```java
x.equals(x); // true x.equals(x); // true
``` ```
(二)对称性 对称性
```java ```java
x.equals(y) == y.equals(x); // true x.equals(y) == y.equals(x); // true
``` ```
(三)传递性 传递性
```java ```java
if (x.equals(y) && y.equals(z)) if (x.equals(y) && y.equals(z))
x.equals(z); // true; x.equals(z); // true;
``` ```
(四)一致性 一致性
多次调用 equals() 方法结果不变 多次调用 equals() 方法结果不变
@ -652,7 +719,7 @@ if (x.equals(y) && y.equals(z))
x.equals(y) == x.equals(y); // true x.equals(y) == x.equals(y); // true
``` ```
(五)与 null 的比较 与 null 的比较
对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false 对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false
@ -660,7 +727,7 @@ x.equals(y) == x.equals(y); // true
x.equals(null); // false; x.equals(null); // false;
``` ```
**2. equals() 与 ==** **2. 等价与相等**
- 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。 - 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
- 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。 - 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。
@ -681,6 +748,7 @@ System.out.println(x == y); // false
```java ```java
public class EqualExample { public class EqualExample {
private int x; private int x;
private int y; private int y;
private int z; private int z;
@ -707,7 +775,7 @@ public class EqualExample {
## hashCode() ## hashCode()
hasCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。 hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。
在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。 在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。
@ -723,7 +791,7 @@ set.add(e2);
System.out.println(set.size()); // 2 System.out.println(set.size()); // 2
``` ```
理想的散列函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31因为它是一个奇素数如果是偶数的话当出现乘法溢出信息就会丢失因为与 2 相乘相当于向左移一位。 理想的散列函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31因为它是一个奇素数如果是偶数的话当出现乘法溢出信息就会丢失因为与 2 相乘相当于向左移一位。
一个数与 31 相乘可以转换成移位和减法:`31*x == (x<<5)-x`,编译器会自动进行这个优化。 一个数与 31 相乘可以转换成移位和减法:`31*x == (x<<5)-x`,编译器会自动进行这个优化。
@ -744,6 +812,7 @@ public int hashCode() {
```java ```java
public class ToStringExample { public class ToStringExample {
private int number; private int number;
public ToStringExample(int number) { public ToStringExample(int number) {
@ -787,7 +856,7 @@ public class CloneExample {
private int b; private int b;
@Override @Override
protected CloneExample clone() throws CloneNotSupportedException { public CloneExample clone() throws CloneNotSupportedException {
return (CloneExample)super.clone(); return (CloneExample)super.clone();
} }
} }
@ -816,7 +885,7 @@ public class CloneExample implements Cloneable {
private int b; private int b;
@Override @Override
protected Object clone() throws CloneNotSupportedException { public Object clone() throws CloneNotSupportedException {
return super.clone(); return super.clone();
} }
} }
@ -828,6 +897,7 @@ public class CloneExample implements Cloneable {
```java ```java
public class ShallowCloneExample implements Cloneable { public class ShallowCloneExample implements Cloneable {
private int[] arr; private int[] arr;
public ShallowCloneExample() { public ShallowCloneExample() {
@ -870,6 +940,7 @@ System.out.println(e2.get(2)); // 222
```java ```java
public class DeepCloneExample implements Cloneable { public class DeepCloneExample implements Cloneable {
private int[] arr; private int[] arr;
public DeepCloneExample() { public DeepCloneExample() {
@ -917,6 +988,7 @@ System.out.println(e2.get(2)); // 2
```java ```java
public class CloneConstructorExample { public class CloneConstructorExample {
private int[] arr; private int[] arr;
public CloneConstructorExample() { public CloneConstructorExample() {
@ -982,11 +1054,12 @@ private 方法隐式地被指定为 final如果在子类中定义的方法和
**1. 静态变量** **1. 静态变量**
- 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它静态变量在内存中只存在一份。 - 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它静态变量在内存中只存在一份。
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。 - 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
```java ```java
public class A { public class A {
private int x; // 实例变量 private int x; // 实例变量
private static int y; // 静态变量 private static int y; // 静态变量
@ -1001,7 +1074,7 @@ public class A {
**2. 静态方法** **2. 静态方法**
静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法abstract 静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。
```java ```java
public abstract class A { public abstract class A {
@ -1015,6 +1088,7 @@ public abstract class A {
```java ```java
public class A { public class A {
private static int x; private static int x;
private int y; private int y;
@ -1053,6 +1127,7 @@ public class A {
```java ```java
public class OuterClass { public class OuterClass {
class InnerClass { class InnerClass {
} }
@ -1124,7 +1199,7 @@ public InitialOrderTest() {
每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。 每个类都有一个 **Class** 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。
类加载相当于 Class 对象的加载。类在第一次使用时才动态加载到 JVM 中,可以使用 `Class.forName("com.mysql.jdbc.Driver")` 这种方式来控制类的加载,该方法会返回一个 Class 对象。 类加载相当于 Class 对象的加载,类在第一次使用时才动态加载到 JVM 中。也可以使用 `Class.forName("com.mysql.jdbc.Driver")` 这种方式来控制类的加载,该方法会返回一个 Class 对象。
反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。 反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class 不存在也可以加载进来。
@ -1221,7 +1296,7 @@ Java 注解是附加在代码中的一些元信息,用于一些工具在编译
- Java 没有指针,它的引用可以理解为安全指针,而 C++ 具有和 C 一样的指针。 - Java 没有指针,它的引用可以理解为安全指针,而 C++ 具有和 C 一样的指针。
- Java 支持自动垃圾回收,而 C++ 需要手动回收。 - Java 支持自动垃圾回收,而 C++ 需要手动回收。
- Java 不支持多重继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多重继承。 - Java 不支持多重继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多重继承。
- Java 不支持操作符重载,虽然可以对两个 String 对象支持加法运算,但是这是语言内置支持的操作,不属于操作符重载,而 C++ 可以。 - Java 不支持操作符重载,虽然可以对两个 String 对象执行加法运算,但是这是语言内置支持的操作,不属于操作符重载,而 C++ 可以。
- Java 的 goto 是保留字但是不可用C++ 可以使用 goto。 - Java 的 goto 是保留字但是不可用C++ 可以使用 goto。
- Java 不支持条件编译C++ 通过 #ifdef #ifndef 等预处理命令从而实现条件编译。 - Java 不支持条件编译C++ 通过 #ifdef #ifndef 等预处理命令从而实现条件编译。

View File

@ -13,7 +13,7 @@
* [HashMap](#hashmap) * [HashMap](#hashmap)
* [ConcurrentHashMap](#concurrenthashmap) * [ConcurrentHashMap](#concurrenthashmap)
* [LinkedHashMap](#linkedhashmap) * [LinkedHashMap](#linkedhashmap)
* [WeekHashMap](#weekhashmap) * [WeakHashMap](#weakhashmap)
* [附录](#附录) * [附录](#附录)
* [参考资料](#参考资料) * [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
@ -25,7 +25,7 @@
## Collection ## Collection
<div align="center"> <img src="../pics//VP4n3i8m34Ntd28NQ4_0KCJ2q044Oez.png"/> </div><br> <div align="center"> <img src="../pics//VP6n3i8W48Ptde8NQ9_0eSR5eOD6uqx.png"/> </div><br>
### 1. Set ### 1. Set
@ -100,7 +100,7 @@ List list = Arrays.asList(arr);
也可以使用以下方式调用 asList() 也可以使用以下方式调用 asList()
```java ```java
List list = Arrays.asList(1,2,3); List list = Arrays.asList(1, 2, 3);
``` ```
# 三、源码分析 # 三、源码分析
@ -126,7 +126,91 @@ public class ArrayList<E> extends AbstractList<E>
private static final int DEFAULT_CAPACITY = 10; private static final int DEFAULT_CAPACITY = 10;
``` ```
### 2. 序列化 ### 2. 扩容
添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 `oldCapacity + (oldCapacity >> 1)`,也就是旧容量的 1.5 倍。
扩容操作需要调用 `Arrays.copyOf()` 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。
```java
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(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);
}
```
### 3. 删除元素
需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上,该操作的时间复杂度为 O(N),可以看出 ArrayList 删除元素的代价是非常高的。
```java
public E remove(int index) {
rangeCheck(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; // clear to let GC do its work
return oldValue;
}
```
### 4. Fail-Fast
modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。
在进行序列化或者迭代等操作时,需要比较操作前后 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();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
```
### 5. 序列化
ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。 ArrayList 基于数组实现,并且具有动态扩容特性,因此保存元素的数组不一定都会被使用,那么就没必要全部进行序列化。
@ -191,90 +275,6 @@ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(list); oos.writeObject(list);
``` ```
### 3. 扩容
添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够时,需要使用 grow() 方法进行扩容,新容量的大小为 `oldCapacity + (oldCapacity >> 1)`,也就是旧容量的 1.5 倍。
扩容操作需要调用 `Arrays.copyOf()` 把原数组整个复制到新数组中,这个操作代价很高,因此最好在创建 ArrayList 对象时就指定大概的容量大小,减少扩容操作的次数。
```java
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(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);
}
```
### 4. 删除元素
需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上,该操作的时间复杂度为 O(N),可以看出 ArrayList 删除元素的代价是非常高的。
```java
public E remove(int index) {
rangeCheck(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; // clear to let GC do its work
return oldValue;
}
```
### 5. Fail-Fast
modCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指添加或者删除至少一个元素的所有操作,或者是调整内部数组的大小,仅仅只是设置元素的值不算结构发生变化。
在进行序列化或者迭代等操作时,需要比较操作前后 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();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
```
## Vector ## Vector
### 1. 同步 ### 1. 同步
@ -461,21 +461,6 @@ static class Entry<K,V> implements Map.Entry<K,V> {
public final String toString() { public final String toString() {
return getKey() + "=" + getValue(); return getKey() + "=" + getValue();
} }
/**
* This method is invoked whenever the value in an entry is
* overwritten by an invocation of put(k,v) for a key k that's already
* in the HashMap.
*/
void recordAccess(HashMap<K,V> m) {
}
/**
* This method is invoked whenever the entry is
* removed from the table.
*/
void recordRemoval(HashMap<K,V> m) {
}
} }
``` ```
@ -590,7 +575,7 @@ int hash = hash(key);
int i = indexFor(hash, table.length); int i = indexFor(hash, table.length);
``` ```
(一)计算 hash 值 **4.1 计算 hash 值**
```java ```java
final int hash(Object k) { final int hash(Object k) {
@ -615,7 +600,7 @@ public final int hashCode() {
} }
``` ```
(二)取模 **4.2 取模**
令 x = 1<<4 x 2 4 次方它具有以下性质 令 x = 1<<4 x 2 4 次方它具有以下性质
@ -745,7 +730,7 @@ new capacity : 00100000
- 它的哈希值如果在第 6 位上为 0那么取模得到的结果和之前一样 - 它的哈希值如果在第 6 位上为 0那么取模得到的结果和之前一样
- 如果为 1那么得到的结果为原来的结果 +16。 - 如果为 1那么得到的结果为原来的结果 +16。
### 7. 扩容-计算数组容量 ### 7. 计算数组容量
HashMap 构造函数允许用户传入的容量不是 2 的 n 次方,因为它可以自动地将传入的容量转换为 2 的 n 次方。 HashMap 构造函数允许用户传入的容量不是 2 的 n 次方,因为它可以自动地将传入的容量转换为 2 的 n 次方。
@ -753,7 +738,7 @@ HashMap 构造函数允许用户传入的容量不是 2 的 n 次方,因为它
``` ```
mask |= mask >> 1 11011000 mask |= mask >> 1 11011000
mask |= mask >> 2 11111100 mask |= mask >> 2 11111110
mask |= mask >> 4 11111111 mask |= mask >> 4 11111111
``` ```
@ -929,7 +914,7 @@ JDK 1.8 使用了 CAS 操作来支持更高的并发度,在 CAS 操作失败
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>
``` ```
维护了一个双向链表,用来维护插入顺序或者 LRU 顺序。 维护了一个双向链表,用来维护插入顺序或者 LRU 顺序。
```java ```java
/** /**
@ -989,7 +974,7 @@ void afterNodeAccess(Node<K,V> e) { // move node to last
### afterNodeInsertion() ### afterNodeInsertion()
在 put 等操作之后执行,当 removeEldestEntry() 方法返回 ture 时会移除最晚的节点,也就是链表首部节点 first。 在 put 等操作之后执行,当 removeEldestEntry() 方法返回 true 时会移除最晚的节点,也就是链表首部节点 first。
evict 只有在构建 Map 的时候才为 false在这里为 true。 evict 只有在构建 Map 的时候才为 false在这里为 true。
@ -1049,7 +1034,7 @@ public static void main(String[] args) {
[3, 1, 4] [3, 1, 4]
``` ```
## WeekHashMap ## WeakHashMap
### 存储结构 ### 存储结构
@ -1197,7 +1182,7 @@ ListIterator <-- List
- Eckel B. Java 编程思想 [M]. 机械工业出版社, 2002. - Eckel B. Java 编程思想 [M]. 机械工业出版社, 2002.
- [Java Collection Framework](https://www.w3resource.com/java-tutorial/java-collections.php) - [Java Collection Framework](https://www.w3resource.com/java-tutorial/java-collections.php)
- [Iterator 模式](https://openhome.cc/Gossip/DesignPattern/IteratorPattern.htm) - [Iterator 模式](https://openhome.cc/Gossip/DesignPattern/IteratorPattern.htm)
- [Java 8 系列之重新认识 HashMap](https://tech.meituan.com/java-hashmap.html) - [Java 8 系列之重新认识 HashMap](https://tech.meituan.com/java_hashmap.html)
- [What is difference between HashMap and Hashtable in Java?](http://javarevisited.blogspot.hk/2010/10/difference-between-hashmap-and.html) - [What is difference between HashMap and Hashtable in Java?](http://javarevisited.blogspot.hk/2010/10/difference-between-hashmap-and.html)
- [Java 集合之 HashMap](http://www.zhangchangle.com/2018/02/07/Java%E9%9B%86%E5%90%88%E4%B9%8BHashMap/) - [Java 集合之 HashMap](http://www.zhangchangle.com/2018/02/07/Java%E9%9B%86%E5%90%88%E4%B9%8BHashMap/)
- [The principle of ConcurrentHashMap analysis](http://www.programering.com/a/MDO3QDNwATM.html) - [The principle of ConcurrentHashMap analysis](http://www.programering.com/a/MDO3QDNwATM.html)

View File

@ -44,9 +44,10 @@
* [内存模型三大特性](#内存模型三大特性) * [内存模型三大特性](#内存模型三大特性)
* [先行发生原则](#先行发生原则) * [先行发生原则](#先行发生原则)
* [十一、线程安全](#十一线程安全) * [十一、线程安全](#十一线程安全)
* [线程安全定义](#线程安全定义) * [不可变](#不可变)
* [线程安全分类](#线程安全分类) * [互斥同步](#互斥同步)
* [线程安全的实现方法](#线程安全的实现方法) * [非阻塞同步](#非阻塞同步)
* [无同步方案](#无同步方案)
* [十二、锁优化](#十二锁优化) * [十二、锁优化](#十二锁优化)
* [自旋锁](#自旋锁) * [自旋锁](#自旋锁)
* [锁消除](#锁消除) * [锁消除](#锁消除)
@ -168,6 +169,8 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc
同样也是需要实现 run() 方法,因为 Thread 类也实现了 Runable 接口。 同样也是需要实现 run() 方法,因为 Thread 类也实现了 Runable 接口。
当调用 start() 方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 run() 方法。
```java ```java
public class MyThread extends Thread { public class MyThread extends Thread {
public void run() { public void run() {
@ -635,6 +638,7 @@ B
```java ```java
public class WaitNotifyExample { public class WaitNotifyExample {
public synchronized void before() { public synchronized void before() {
System.out.println("before"); System.out.println("before");
notifyAll(); notifyAll();
@ -672,12 +676,15 @@ after
## await() signal() signalAll() ## await() signal() signalAll()
java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。相比于 wait() 这种等待方式await() 可以指定等待的条件,因此更加灵活。 java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。
相比于 wait() 这种等待方式await() 可以指定等待的条件,因此更加灵活。
使用 Lock 来获取一个 Condition 对象。 使用 Lock 来获取一个 Condition 对象。
```java ```java
public class AwaitSignalExample { public class AwaitSignalExample {
private Lock lock = new ReentrantLock(); private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition(); private Condition condition = lock.newCondition();
@ -733,6 +740,7 @@ java.util.concurrentJ.U.C大大提高了并发性能AQS 被认为是 J.
```java ```java
public class CountdownLatchExample { public class CountdownLatchExample {
public static void main(String[] args) throws InterruptedException { public static void main(String[] args) throws InterruptedException {
final int totalThread = 10; final int totalThread = 10;
CountDownLatch countDownLatch = new CountDownLatch(totalThread); CountDownLatch countDownLatch = new CountDownLatch(totalThread);
@ -781,6 +789,7 @@ public CyclicBarrier(int parties) {
```java ```java
public class CyclicBarrierExample { public class CyclicBarrierExample {
public static void main(String[] args) { public static void main(String[] args) {
final int totalThread = 10; final int totalThread = 10;
CyclicBarrier cyclicBarrier = new CyclicBarrier(totalThread); CyclicBarrier cyclicBarrier = new CyclicBarrier(totalThread);
@ -807,7 +816,7 @@ before..before..before..before..before..before..before..before..before..before..
## Semaphore ## Semaphore
Semaphore 就是操作系统中的信号量,可以控制对互斥资源的访问线程数。 Semaphore 类似于操作系统中的信号量,可以控制对互斥资源的访问线程数。
<div align="center"> <img src="../pics//Semaphore.png" width=""/> </div><br> <div align="center"> <img src="../pics//Semaphore.png" width=""/> </div><br>
@ -815,6 +824,7 @@ Semaphore 就是操作系统中的信号量,可以控制对互斥资源的访
```java ```java
public class SemaphoreExample { public class SemaphoreExample {
public static void main(String[] args) { public static void main(String[] args) {
final int clientCount = 3; final int clientCount = 3;
final int totalRequestCount = 10; final int totalRequestCount = 10;
@ -859,6 +869,7 @@ FutureTask 可用于异步获取执行结果或取消执行任务的场景。当
```java ```java
public class FutureTaskExample { public class FutureTaskExample {
public static void main(String[] args) throws ExecutionException, InterruptedException { public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() { FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
@Override @Override
@ -964,6 +975,7 @@ produce..produce..consume..consume..produce..consume..produce..consume..produce.
```java ```java
public class ForkJoinExample extends RecursiveTask<Integer> { public class ForkJoinExample extends RecursiveTask<Integer> {
private final int threshold = 5; private final int threshold = 5;
private int first; private int first;
private int last; private int last;
@ -1018,7 +1030,7 @@ ForkJoinPool 实现了工作窃取算法来提高 CPU 的利用率。每个线
如果多个线程对同一个共享数据进行访问而不采取同步操作的话,那么操作的结果是不一致的。 如果多个线程对同一个共享数据进行访问而不采取同步操作的话,那么操作的结果是不一致的。
以下代码演示了 1000 个线程同时对 cnt 执行自增操作,操作结束之后它的值为 997 而不是 1000。 以下代码演示了 1000 个线程同时对 cnt 执行自增操作,操作结束之后它的值有可能小于 1000。
```java ```java
public class ThreadUnsafeExample { public class ThreadUnsafeExample {
@ -1096,11 +1108,11 @@ Java 内存模型定义了 8 个操作来完成主内存和工作内存的交互
Java 内存模型保证了 read、load、use、assign、store、write、lock 和 unlock 操作具有原子性,例如对一个 int 类型的变量执行 assign 赋值操作,这个操作就是原子性的。但是 Java 内存模型允许虚拟机将没有被 volatile 修饰的 64 位数据longdouble的读写操作划分为两次 32 位的操作来进行,即 load、store、read 和 write 操作可以不具备原子性。 Java 内存模型保证了 read、load、use、assign、store、write、lock 和 unlock 操作具有原子性,例如对一个 int 类型的变量执行 assign 赋值操作,这个操作就是原子性的。但是 Java 内存模型允许虚拟机将没有被 volatile 修饰的 64 位数据longdouble的读写操作划分为两次 32 位的操作来进行,即 load、store、read 和 write 操作可以不具备原子性。
有一个错误认识就是int 等原子性的变量在多线程环境中不会出现线程安全问题。前面的线程不安全示例代码中cnt 变量属于 int 类型变量1000 个线程对它进行自增操作之后,得到的值为 997 而不是 1000。 有一个错误认识就是int 等原子性的类型在多线程环境中不会出现线程安全问题。前面的线程不安全示例代码中cnt 属于 int 类型变量1000 个线程对它进行自增操作之后,得到的值为 997 而不是 1000。
为了方便讨论,将内存间的交互操作简化为 3 个load、assign、store。 为了方便讨论,将内存间的交互操作简化为 3 个load、assign、store。
下图演示了两个线程同时对 cnt 变量进行操作load、assign、store 这一系列操作整体上看不具备原子性,那么在 T1 修改 cnt 并且还没有将修改后的值写入主内存T2 依然可以读入该变量的值。可以看出,这两个线程虽然执行了两次自增运算,但是主内存中 cnt 的值最后为 1 而不是 2。因此对 int 类型读写操作满足原子性只是说明 load、assign、store 这些单个操作具备原子性。 下图演示了两个线程同时对 cnt 进行操作load、assign、store 这一系列操作整体上看不具备原子性,那么在 T1 修改 cnt 并且还没有将修改后的值写入主内存T2 依然可以读入值。可以看出,这两个线程虽然执行了两次自增运算,但是主内存中 cnt 的值最后为 1 而不是 2。因此对 int 类型读写操作满足原子性只是说明 load、assign、store 这些单个操作具备原子性。
<div align="center"> <img src="../pics//ef8eab00-1d5e-4d99-a7c2-d6d68ea7fe92.png" width=""/> </div><br> <div align="center"> <img src="../pics//ef8eab00-1d5e-4d99-a7c2-d6d68ea7fe92.png" width=""/> </div><br>
@ -1198,9 +1210,7 @@ public static void main(String[] args) throws InterruptedException {
### 3. 有序性 ### 3. 有序性
有序性是指:在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排序。 有序性是指:在本线程内观察,所有操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,无序是因为发生了指令重排序。在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
volatile 关键字通过添加内存屏障的方式来禁止指令重排,即重排序时不能把后面的指令放到内存屏障之前。 volatile 关键字通过添加内存屏障的方式来禁止指令重排,即重排序时不能把后面的指令放到内存屏障之前。
@ -1270,19 +1280,13 @@ Thread 对象的结束先行发生于 join() 方法返回。
# 十一、线程安全 # 十一、线程安全
## 线程安全定义 多个线程不管以何种方式访问某个类,并且在主调代码中不需要进行同步,都能表现正确的行为。
一个类在可以被多个线程安全调用时就是线程安全的。 线程安全有以下几种实现方式:
## 线程安全分类 ## 不可变
线程安全不是一个非真即假的命题,可以将共享数据按照安全程度的强弱顺序分成以下五类:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立。 不可变Immutable的对象一定是线程安全的不需要再采取任何的线程安全保障措施。只要一个不可变的对象被正确地构建出来永远也不会看到它在多个线程之中处于不一致的状态。多线程环境下应当尽量使对象成为不可变来满足线程安全。
### 1. 不可变
不可变Immutable的对象一定是线程安全的不需要再采取任何的线程安全保障措施。只要一个不可变的对象被正确地构建出来永远也不会看到它在多个线程之中处于不一致的状态。
多线程环境下,应当尽量使对象成为不可变,来满足线程安全。
不可变的类型: 不可变的类型:
@ -1317,101 +1321,25 @@ public V put(K key, V value) {
} }
``` ```
### 2. 绝对线程安全 ## 互斥同步
不管运行时环境如何,调用者都不需要任何额外的同步措施。
### 3. 相对线程安全
相对线程安全需要保证对这个对象单独的操作是线程安全的,在调用的时候不需要做额外的保障措施。但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。
在 Java 语言中,大部分的线程安全类都属于这种类型,例如 Vector、HashTable、Collections 的 synchronizedCollection() 方法包装的集合等。
对于下面的代码,如果删除元素的线程删除了 Vector 的一个元素,而获取元素的线程试图访问一个已经被删除的元素,那么就会抛出 ArrayIndexOutOfBoundsException。
```Java
public class VectorUnsafeExample {
private static Vector<Integer> vector = new Vector<>();
public static void main(String[] args) {
while (true) {
for (int i = 0; i < 100; i++) {
vector.add(i);
}
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
for (int i = 0; i < vector.size(); i++) {
vector.remove(i);
}
});
executorService.execute(() -> {
for (int i = 0; i < vector.size(); i++) {
vector.get(i);
}
});
executorService.shutdown();
}
}
}
```
```html
Exception in thread "Thread-159738" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 3
at java.util.Vector.remove(Vector.java:831)
at VectorUnsafeExample.lambda$main$0(VectorUnsafeExample.java:14)
at VectorUnsafeExample$$Lambda$1/713338599.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
```
如果要保证上面的代码能正确执行下去,就需要对删除元素和获取元素的代码进行同步。
```java
executorService.execute(() -> {
synchronized (vector) {
for (int i = 0; i < vector.size(); i++) {
vector.remove(i);
}
}
});
executorService.execute(() -> {
synchronized (vector) {
for (int i = 0; i < vector.size(); i++) {
vector.get(i);
}
}
});
```
### 4. 线程兼容
线程兼容是指对象本身并不是线程安全的但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用我们平常说一个类不是线程安全的绝大多数时候指的是这一种情况。Java API 中大部分的类都是属于线程兼容的,如与前面的 Vector 和 HashTable 相对应的集合类 ArrayList 和 HashMap 等。
### 5. 线程对立
线程对立是指无论调用端是否采取了同步措施,都无法在多线程环境中并发使用的代码。由于 Java 语言天生就具备多线程特性,线程对立这种排斥多线程的代码是很少出现的,而且通常都是有害的,应当尽量避免。
## 线程安全的实现方法
### 1. 互斥同步
synchronized 和 ReentrantLock。 synchronized 和 ReentrantLock。
### 2. 非阻塞同步 ## 非阻塞同步
互斥同步最主要的问题就是线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。 互斥同步最主要的问题就是线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。
互斥同步属于一种悲观的并发策略,总是认为只要不去做正确的同步措施,那就肯定会出现问题。无论共享数据是否真的会出现竞争,它都要进行加锁(这里讨论的是概念模型,实际上虚拟机会优化掉很大一部分不必要的加锁)、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒等操作。 互斥同步属于一种悲观的并发策略,总是认为只要不去做正确的同步措施,那就肯定会出现问题。无论共享数据是否真的会出现竞争,它都要进行加锁(这里讨论的是概念模型,实际上虚拟机会优化掉很大一部分不必要的加锁)、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒等操作。
**CAS** ### 1. CAS
随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略:先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步。 随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略:先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步。
乐观锁需要操作和冲突检测这两个步骤具备原子性这里就不能再使用互斥同步来保证了只能靠硬件来完成。硬件支持的原子性操作最典型的是比较并交换Compare-and-SwapCAS。CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时只有当 V 的值等于 A才将 V 的值更新为 B。 乐观锁需要操作和冲突检测这两个步骤具备原子性这里就不能再使用互斥同步来保证了只能靠硬件来完成。硬件支持的原子性操作最典型的是比较并交换Compare-and-SwapCAS。CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时只有当 V 的值等于 A才将 V 的值更新为 B。
**AtomicInteger** ### 2. AtomicInteger
J.U.C 包里面的整数原子类 AtomicInteger,其中的 compareAndSet() 和 getAndIncrement() 等方法都使用了 Unsafe 类的 CAS 操作。 J.U.C 包里面的整数原子类 AtomicInteger 的方法调用了 Unsafe 类的 CAS 操作。
以下代码使用了 AtomicInteger 执行了自增的操作。 以下代码使用了 AtomicInteger 执行了自增的操作。
@ -1423,7 +1351,7 @@ public void add() {
} }
``` ```
以下代码是 incrementAndGet() 的源码,它调用了 unsafe 的 getAndAddInt() 。 以下代码是 incrementAndGet() 的源码,它调用了 Unsafe 的 getAndAddInt() 。
```java ```java
public final int incrementAndGet() { public final int incrementAndGet() {
@ -1446,24 +1374,21 @@ public final int getAndAddInt(Object var1, long var2, int var4) {
} }
``` ```
**ABA** ### 3. ABA
如果一个变量初次读取的时候是 A 值,它的值被改成了 B后来又被改回为 A那 CAS 操作就会误认为它从来没有被改变过。 如果一个变量初次读取的时候是 A 值,它的值被改成了 B后来又被改回为 A那 CAS 操作就会误认为它从来没有被改变过。
J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题,它可以通过控制变量值的版本来保证 CAS 的正确性。大部分情况下 ABA 问题不会影响程序并发的正确性,如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效。 J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题,它可以通过控制变量值的版本来保证 CAS 的正确性。大部分情况下 ABA 问题不会影响程序并发的正确性,如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效。
### 3. 无同步方案 ## 无同步方案
要保证线程安全,并不是一定就要进行同步。如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性。 要保证线程安全,并不是一定就要进行同步。如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性。
**(一)栈封闭** ### 1. 栈封闭
多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的。 多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的。
```java ```java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class StackClosedExample { public class StackClosedExample {
public void add100() { public void add100() {
int cnt = 0; int cnt = 0;
@ -1490,7 +1415,7 @@ public static void main(String[] args) {
100 100
``` ```
**线程本地存储Thread Local Storage** ### 2. 线程本地存储Thread Local Storage
如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行。如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题。 如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行。如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题。
@ -1553,7 +1478,7 @@ public class ThreadLocalExample1 {
<div align="center"> <img src="../pics//3646544a-cb57-451d-9e03-d3c4f5e4434a.png" width=""/> </div><br> <div align="center"> <img src="../pics//3646544a-cb57-451d-9e03-d3c4f5e4434a.png" width=""/> </div><br>
每个 Thread 都有一个 ThreadLocal.ThreadLocalMap 对象Thread 类中就定义了 ThreadLocal.ThreadLocalMap 成员 每个 Thread 都有一个 ThreadLocal.ThreadLocalMap 对象。
```java ```java
/* ThreadLocal values pertaining to this thread. This map is maintained /* ThreadLocal values pertaining to this thread. This map is maintained
@ -1596,7 +1521,7 @@ ThreadLocal 从理论上讲并不是用来解决多线程并发问题的,因
在一些场景 (尤其是使用线程池) 下,由于 ThreadLocal.ThreadLocalMap 的底层数据结构导致 ThreadLocal 有内存泄漏的情况,应该尽可能在每次使用 ThreadLocal 后手动调用 remove(),以避免出现 ThreadLocal 经典的内存泄漏甚至是造成自身业务混乱的风险。 在一些场景 (尤其是使用线程池) 下,由于 ThreadLocal.ThreadLocalMap 的底层数据结构导致 ThreadLocal 有内存泄漏的情况,应该尽可能在每次使用 ThreadLocal 后手动调用 remove(),以避免出现 ThreadLocal 经典的内存泄漏甚至是造成自身业务混乱的风险。
**可重入代码Reentrant Code** ### 3. 可重入代码Reentrant Code
这种代码也叫做纯代码Pure Code可以在代码执行的任何时刻中断它转而去执行另外一段代码包括递归调用它本身而在控制权返回后原来的程序不会出现任何错误。 这种代码也叫做纯代码Pure Code可以在代码执行的任何时刻中断它转而去执行另外一段代码包括递归调用它本身而在控制权返回后原来的程序不会出现任何错误。
@ -1684,15 +1609,15 @@ JDK 1.6 引入了偏向锁和轻量级锁,从而让锁拥有了四个状态:
- 缩小同步范围,从而减少锁争用。例如对于 synchronized应该尽量使用同步块而不是同步方法。 - 缩小同步范围,从而减少锁争用。例如对于 synchronized应该尽量使用同步块而不是同步方法。
- 多用同步工具少用 wait() 和 notify()。首先CountDownLatch, CyclicBarrier, Semaphore 和 Exchanger 这些同步类简化了编码操作,而用 wait() 和 notify() 很难实现复杂控制流;其次,这些同步类是由最好的企业编写和维护,在后续的 JDK 中还会不断优化和完善,使用这些更高等级的同步工具你的程序可以不费吹灰之力获得优化。 - 多用同步工具少用 wait() 和 notify()。首先CountDownLatch, CyclicBarrier, Semaphore 和 Exchanger 这些同步类简化了编码操作,而用 wait() 和 notify() 很难实现复杂控制流;其次,这些同步类是由最好的企业编写和维护,在后续的 JDK 中还会不断优化和完善。
- 使用 BlockingQueue 实现生产者消费者问题。
- 多用并发集合少用同步集合,例如应该使用 ConcurrentHashMap 而不是 Hashtable。 - 多用并发集合少用同步集合,例如应该使用 ConcurrentHashMap 而不是 Hashtable。
- 使用本地变量和不可变类来保证线程安全。 - 使用本地变量和不可变类来保证线程安全。
- 使用线程池而不是直接创建 Thread 对象,这是因为创建线程代价很高,线程池可以有效地利用有限的线程来启动任务。 - 使用线程池而不是直接创建线程,这是因为创建线程代价很高,线程池可以有效地利用有限的线程来启动任务。
- 使用 BlockingQueue 实现生产者消费者问题。
# 参考资料 # 参考资料

View File

@ -30,7 +30,7 @@
# 一、运行时数据区域 # 一、运行时数据区域
<div align="center"> <img src="../pics//c9ad2bf4-5580-4018-bce4-1b9a71804d9c.png" width="400"/> </div><br> <div align="center"> <img src="../pics//c9ad2bf4-5580-4018-bce4-1b9a71804d9c.png" width="450"/> </div><br>
## 程序计数器 ## 程序计数器
@ -38,9 +38,9 @@
## Java 虚拟机栈 ## Java 虚拟机栈
每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息,从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。 每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
<div align="center"> <img src="../pics//926c7438-c5e1-4b94-840a-dcb24ff1dafe.png" width="450"/> </div><br> <div align="center"> <img src="../pics//926c7438-c5e1-4b94-840a-dcb24ff1dafe.png" width="500"/> </div><br>
可以通过 -Xss 这个虚拟机参数来指定每个线程的 Java 虚拟机栈内存大小: 可以通过 -Xss 这个虚拟机参数来指定每个线程的 Java 虚拟机栈内存大小:
@ -55,27 +55,21 @@ java -Xss512M HackTheJava
## 本地方法栈 ## 本地方法栈
本地方法一般是用其它语言C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。
本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。 本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
本地方法一般是用其它语言C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。
<div align="center"> <img src="../pics//JNI-Java-Native-Interface.jpg" width="350"/> </div><br> <div align="center"> <img src="../pics//JNI-Java-Native-Interface.jpg" width="350"/> </div><br>
## 堆 ## 堆
所有对象都在这里分配内存,是垃圾收集的主要区域("GC 堆")。 所有对象都在这里分配内存,是垃圾收集的主要区域("GC 堆")。
现代的垃圾收集器基本都是采用分代收集算法,针对不同类型的对象采取不同的垃圾回收算法,可以将堆分成两块: 现代的垃圾收集器基本都是采用分代收集算法,其主要的思想是针对不同类型的对象采取不同的垃圾回收算法,可以将堆分成两块:
- 新生代Young Generation - 新生代Young Generation
- 老年代Old Generation - 老年代Old Generation
新生代可以继续划分成以下三个空间:
- Eden伊甸园
- From Survivor幸存者
- To Survivor
堆不需要连续内存,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。 堆不需要连续内存,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。
可以通过 -Xms 和 -Xmx 两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。 可以通过 -Xms 和 -Xmx 两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。
@ -92,7 +86,7 @@ java -Xms1M -Xmx2M HackTheJava
对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。 对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。
JDK 1.7 之前,HotSpot 虚拟机把它当成永久代来进行垃圾回收。但是从 JDK 1.7 开始,已经把原本放在永久代的字符串常量池移到 Native Method 中。 HotSpot 虚拟机把它当成永久代来进行垃圾回收。但是很难确定永久代的大小,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。
## 运行时常量池 ## 运行时常量池
@ -104,7 +98,7 @@ Class 文件中的常量池(编译器生成的各种字面量和符号引用
## 直接内存 ## 直接内存
在 JDK 1.4 中新加入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。 在 JDK 1.4 中新加入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存Native 堆),然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。
这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。 这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
@ -142,15 +136,15 @@ public class ReferenceCountingGC {
通过 GC Roots 作为起始点进行搜索,能够到达到的对象都是存活的,不可达的对象可被回收。 通过 GC Roots 作为起始点进行搜索,能够到达到的对象都是存活的,不可达的对象可被回收。
<div align="center"> <img src="../pics//0635cbe8.png" width=""/> </div><br>
Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC Roots 一般包含以下内容: Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC Roots 一般包含以下内容:
- 虚拟机栈中引用的对象 - 虚拟机栈中局部变量表中引用的对象
- 本地方法栈中引用的对象 - 本地方法栈中 JNI 中引用的对象
- 方法区中类静态属性引用的对象 - 方法区中类静态属性引用的对象
- 方法区中的常量引用的对象 - 方法区中的常量引用的对象
<div align="center"> <img src="../pics//0635cbe8.png" width=""/> </div><br>
### 3. 方法区的回收 ### 3. 方法区的回收
因为方法区主要存放永久代对象,而永久代对象的回收率比新生代低很多,因此在方法区上进行回收性价比不高。 因为方法区主要存放永久代对象,而永久代对象的回收率比新生代低很多,因此在方法区上进行回收性价比不高。
@ -171,13 +165,13 @@ Java 虚拟机使用该算法来判断对象是否可被回收,在 Java 中 GC
finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。但是 try-finally 等方式可以做的更好,并且该方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。 finalize() 类似 C++ 的析构函数,用来做关闭外部资源等工作。但是 try-finally 等方式可以做的更好,并且该方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。
当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能通过在该方法中让对象重新被引用,从而实现自救。自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会调用 finalize() 方法。 当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能在该方法中让对象重新被引用,从而实现自救。自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会调用 finalize() 方法。
## 引用类型 ## 引用类型
无论是通过引用计算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判定对象是否可被回收都与引用有关。 无论是通过引用计算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判定对象是否可被回收都与引用有关。
Java 具有四种强度不同的引用类型。 Java 提供了四种强度不同的引用类型。
### 1. 强引用 ### 1. 强引用
@ -274,7 +268,7 @@ HotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1保证了
以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。 以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。
- 单线程与多线程:单线程指的是垃圾收集器只使用一个线程进行收集,而多线程使用多个线程; - 单线程与多线程:单线程指的是垃圾收集器只使用一个线程进行收集,而多线程使用多个线程;
- 串行与并行:串行指的是垃圾收集器与用户程序交替执行,这意味着在执行垃圾收集的时候需要停顿用户程序;并指的是垃圾收集器和用户程序同时执行。除了 CMS 和 G1 之外,其它垃圾收集器都是以串行的方式执行。 - 串行与并行:串行指的是垃圾收集器与用户程序交替执行,这意味着在执行垃圾收集的时候需要停顿用户程序;并指的是垃圾收集器和用户程序同时执行。除了 CMS 和 G1 之外,其它垃圾收集器都是以串行的方式执行。
### 1. Serial 收集器 ### 1. Serial 收集器
@ -286,7 +280,7 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
它的优点是简单高效,对于单个 CPU 环境来说,由于没有线程交互的开销,因此拥有最高的单线程收集效率。 它的优点是简单高效,对于单个 CPU 环境来说,由于没有线程交互的开销,因此拥有最高的单线程收集效率。
它是 Client 模式下的默认新生代收集器,因为在用户的桌面应用场景下分配给虚拟机管理的内存一般来说不会很大。Serial 收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。 它是 Client 模式下的默认新生代收集器,因为在应用场景下分配给虚拟机管理的内存一般来说不会很大。Serial 收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿是可以接受的。
### 2. ParNew 收集器 ### 2. ParNew 收集器
@ -304,11 +298,11 @@ Serial 翻译为串行,也就是说它以串行的方式执行。
其它收集器关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是达到一个可控制的吞吐量,它被称为“吞吐量优先”收集器。这里的吞吐量指 CPU 用于运行用户代码的时间占总时间的比值。 其它收集器关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是达到一个可控制的吞吐量,它被称为“吞吐量优先”收集器。这里的吞吐量指 CPU 用于运行用户代码的时间占总时间的比值。
停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。 停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。
缩短停顿时间是以牺牲吞吐量和新生代空间来换取的:新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。 缩短停顿时间是以牺牲吞吐量和新生代空间来换取的:新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。
可以通过一个开关参数打 GC 自适应的调节策略GC Ergonomics就不需要手工指定新生代的大小-Xmn、Eden 和 Survivor 区的比例、晋升老年代对象年龄等细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种方式称为 可以通过一个开关参数打 GC 自适应的调节策略GC Ergonomics就不需要手工指定新生代的大小-Xmn、Eden 和 Survivor 区的比例、晋升老年代对象年龄等细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。
### 4. Serial Old 收集器 ### 4. Serial Old 收集器
@ -378,8 +372,6 @@ G1 把堆划分成多个大小相等的独立区域Region新生代和
- 空间整合:整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。 - 空间整合:整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。
- 可预测的停顿:能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒。 - 可预测的停顿:能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒。
更详细内容请参考:[Getting Started with the G1 Garbage Collector](http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html)
# 三、内存分配与回收策略 # 三、内存分配与回收策略
## Minor GC 和 Full GC ## Minor GC 和 Full GC
@ -539,7 +531,7 @@ public class Test {
- 与类的构造函数(或者说实例构造器 &lt;init>())不同,不需要显式的调用父类的构造器。虚拟机会自动保证在子类的 &lt;clinit>() 方法运行之前,父类的 &lt;clinit>() 方法已经执行结束。因此虚拟机中第一个执行 &lt;clinit>() 方法的类肯定为 java.lang.Object。 - 与类的构造函数(或者说实例构造器 &lt;init>())不同,不需要显式的调用父类的构造器。虚拟机会自动保证在子类的 &lt;clinit>() 方法运行之前,父类的 &lt;clinit>() 方法已经执行结束。因此虚拟机中第一个执行 &lt;clinit>() 方法的类肯定为 java.lang.Object。
- 由于父类的 &lt;clinit>() 方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。例如以下代码: - 由于父类的 &lt;clinit>() 方法先执行,也就意味着父类中定义的静态语句块的执行要优先于子类。例如以下代码:
```java ```java
static class Parent { static class Parent {
@ -744,6 +736,7 @@ public class FileSystemClassLoader extends ClassLoader {
- 周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011. - 周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011.
- [Chapter 2. The Structure of the Java Virtual Machine](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.4) - [Chapter 2. The Structure of the Java Virtual Machine](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.4)
- [Jvm memory](https://www.slideshare.net/benewu/jvm-memory) - [Jvm memory](https://www.slideshare.net/benewu/jvm-memory)
[Getting Started with the G1 Garbage Collector](http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html)
- [JNI Part1: Java Native Interface Introduction and “Hello World” application](http://electrofriends.com/articles/jni/jni-part1-java-native-interface/) - [JNI Part1: Java Native Interface Introduction and “Hello World” application](http://electrofriends.com/articles/jni/jni-part1-java-native-interface/)
- [Memory Architecture Of JVM(Runtime Data Areas)](https://hackthejava.wordpress.com/2015/01/09/memory-architecture-by-jvmruntime-data-areas/) - [Memory Architecture Of JVM(Runtime Data Areas)](https://hackthejava.wordpress.com/2015/01/09/memory-architecture-by-jvmruntime-data-areas/)
- [JVM Run-Time Data Areas](https://www.programcreek.com/2013/04/jvm-run-time-data-areas/) - [JVM Run-Time Data Areas](https://www.programcreek.com/2013/04/jvm-run-time-data-areas/)

View File

@ -103,7 +103,7 @@ Output: True
Explanation: 1 * 1 + 2 * 2 = 5 Explanation: 1 * 1 + 2 * 2 = 5
``` ```
题目描述:判断一个数是否为两个数的平方和,例如 5 = 1<sup>2</sup> + 2<sup>2</sup> 题目描述:判断一个数是否为两个数的平方和。
```java ```java
public boolean judgeSquareSum(int c) { public boolean judgeSquareSum(int c) {
@ -130,7 +130,7 @@ public boolean judgeSquareSum(int c) {
Given s = "leetcode", return "leotcede". Given s = "leetcode", return "leotcede".
``` ```
使用双指针指向待反转的两个元音字符,一个指针从头向尾遍历,一个指针从尾到头遍历。 使用双指针指向待反转的两个元音字符,一个指针从头向尾遍历,一个指针从尾到头遍历。
```java ```java
private final static HashSet<Character> vowels = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U')); private final static HashSet<Character> vowels = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'));
@ -307,6 +307,8 @@ private boolean isValid(String s, String target) {
[215. Kth Largest Element in an Array (Medium)](https://leetcode.com/problems/kth-largest-element-in-an-array/description/) [215. Kth Largest Element in an Array (Medium)](https://leetcode.com/problems/kth-largest-element-in-an-array/description/)
题目描述:找到第 k 大的元素。
**排序** :时间复杂度 O(NlogN),空间复杂度 O(1) **排序** :时间复杂度 O(NlogN),空间复杂度 O(1)
```java ```java
@ -323,7 +325,7 @@ public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<>(); // 小顶堆 PriorityQueue<Integer> pq = new PriorityQueue<>(); // 小顶堆
for (int val : nums) { for (int val : nums) {
pq.add(val); pq.add(val);
if (pq.size() > k) // 维护堆的大小为 K if (pq.size() > k) // 维护堆的大小为 K
pq.poll(); pq.poll();
} }
return pq.peek(); return pq.peek();
@ -555,7 +557,7 @@ Explanation: You don't need to remove any of the intervals since they're already
题目描述:计算让一组区间不重叠所需要移除的区间个数。 题目描述:计算让一组区间不重叠所需要移除的区间个数。
计算最多能组成的不重叠区间个数,然后用区间总个数减去不重叠区间的个数。 计算最多能组成的不重叠区间个数,然后用区间总个数减去不重叠区间的个数。
在每次选择中,区间的结尾最为重要,选择的区间结尾越小,留给后面的区间的空间越大,那么后面能够选择的区间个数也就越大。 在每次选择中,区间的结尾最为重要,选择的区间结尾越小,留给后面的区间的空间越大,那么后面能够选择的区间个数也就越大。
@ -639,7 +641,7 @@ Output:
题目描述:一个学生用两个分量 (h, k) 描述h 表示身高k 表示排在前面的有 k 个学生的身高比他高或者和他一样高。 题目描述:一个学生用两个分量 (h, k) 描述h 表示身高k 表示排在前面的有 k 个学生的身高比他高或者和他一样高。
为了在每次插入操作时不影响后续的操作,身高较高的学生应该先做插入操作,否则身高较小的学生原先正确插入第 k 个位置可能会变成第 k+1 个位置。 为了使插入操作不影响后续的操作,身高较高的学生应该先做插入操作,否则身高较小的学生原先正确插入第 k 个位置可能会变成第 k+1 个位置。
身高降序、k 值升序,然后按排好序的顺序插入队列的第 k 个位置中。 身高降序、k 值升序,然后按排好序的顺序插入队列的第 k 个位置中。
@ -802,6 +804,52 @@ public int maxProfit(int[] prices) {
} }
``` ```
**子数组最大的和**
[53. Maximum Subarray (Easy)](https://leetcode.com/problems/maximum-subarray/description/)
```html
For example, given the array [-2,1,-3,4,-1,2,1,-5,4],
the contiguous subarray [4,-1,2,1] has the largest sum = 6.
```
```java
public int maxSubArray(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int preSum = nums[0];
int maxSum = preSum;
for (int i = 1; i < nums.length; i++) {
preSum = preSum > 0 ? preSum + nums[i] : nums[i];
maxSum = Math.max(maxSum, preSum);
}
return maxSum;
}
```
**买入和售出股票最大的收益**
[121. Best Time to Buy and Sell Stock (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/)
题目描述:只进行一次交易。
只要记录前面的最小价格,将这个最小价格作为买入价格,然后将当前的价格作为售出价格,查看当前收益是不是最大收益。
```java
public int maxProfit(int[] prices) {
int n = prices.length;
if (n == 0) return 0;
int soFarMin = prices[0];
int max = 0;
for (int i = 1; i < n; i++) {
if (soFarMin > prices[i]) soFarMin = prices[i];
else max = Math.max(max, prices[i] - soFarMin);
}
return max;
}
```
## 二分查找 ## 二分查找
**正常实现** **正常实现**
@ -825,7 +873,7 @@ public int binarySearch(int[] nums, int key) {
**时间复杂度** **时间复杂度**
二分查找也称为折半查找,每次都能将查找区间减半,这种折半特性的算法时间复杂度为 O(logN)。 二分查找也称为折半查找,每次都能将查找区间减半,这种折半特性的算法时间复杂度为 O(logN)。
**m 计算** **m 计算**
@ -961,7 +1009,7 @@ public char nextGreatestLetter(char[] letters, char target) {
[540. Single Element in a Sorted Array (Medium)](https://leetcode.com/problems/single-element-in-a-sorted-array/description/) [540. Single Element in a Sorted Array (Medium)](https://leetcode.com/problems/single-element-in-a-sorted-array/description/)
```html ```html
Input: [1,1,2,3,3,4,4,8,8] Input: [1, 1, 2, 3, 3, 4, 4, 8, 8]
Output: 2 Output: 2
``` ```
@ -1132,11 +1180,11 @@ public List<Integer> diffWaysToCompute(String input) {
<div align="center"> <img src="../pics//4ff355cf-9a7f-4468-af43-e5b02038facc.jpg"/> </div><br> <div align="center"> <img src="../pics//4ff355cf-9a7f-4468-af43-e5b02038facc.jpg"/> </div><br>
广度优先搜索的搜索过程有点像一层一层地进行遍历,每层遍历都以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点。需要注意的是,遍历过的节点不能再次被遍历。 广度优先搜索一层一层地进行遍历,每层遍历都以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点。需要注意的是,遍历过的节点不能再次被遍历。
第一层: 第一层:
- 0 -> {6,2,1,5}; - 0 -> {6,2,1,5}
第二层: 第二层:
@ -1150,7 +1198,7 @@ public List<Integer> diffWaysToCompute(String input) {
- 4 -> {} - 4 -> {}
- 3 -> {} - 3 -> {}
可以看到,每一层遍历的节点都与根节点距离相同。设 d<sub>i</sub> 表示第 i 个节点与根节点的距离,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j有 d<sub>i</sub><=d<sub>j</sub>。利用这个结论,可以求解最短路径等 **最优解** 问题:第一次遍历到目的节点,其所经过的路径为最短路径。应该注意的是,使用 BFS 只能求解无权图的最短路径。 每一层遍历的节点都与根节点距离相同。设 d<sub>i</sub> 表示第 i 个节点与根节点的距离,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j有 d<sub>i</sub> <= d<sub>j</sub>。利用这个结论,可以求解最短路径等 **最优解** 问题:第一次遍历到目的节点,其所经过的路径为最短路径。应该注意的是,使用 BFS 只能求解无权图的最短路径。
在程序实现 BFS 时需要考虑以下问题: 在程序实现 BFS 时需要考虑以下问题:
@ -1180,19 +1228,17 @@ public int minPathLength(int[][] grids, int tr, int tc) {
pathLength++; pathLength++;
while (size-- > 0) { while (size-- > 0) {
Pair<Integer, Integer> cur = queue.poll(); Pair<Integer, Integer> cur = queue.poll();
int cr = cur.getKey(), cc = cur.getValue();
grids[cr][cc] = 0; // 标记
for (int[] d : direction) { for (int[] d : direction) {
int nr = cur.getKey() + d[0], nc = cur.getValue() + d[1]; int nr = cr + d[0], nc = cc + d[1];
Pair<Integer, Integer> next = new Pair<>(nr, nc); if (nr < 0 || nr >= m || nc < 0 || nc >= n || grids[nr][nc] == 0) {
if (next.getKey() < 0 || next.getValue() >= m
|| next.getKey() < 0 || next.getValue() >= n) {
continue; continue;
} }
grids[next.getKey()][next.getValue()] = 0; // 标记 if (nr == tr && nc == tc) {
if (next.getKey() == tr && next.getValue() == tc) {
return pathLength; return pathLength;
} }
queue.add(next); queue.add(new Pair<>(nr, nc));
} }
} }
} }
@ -1239,7 +1285,7 @@ public int numSquares(int n) {
continue; continue;
} }
marked[next] = true; marked[next] = true;
queue.add(cur - s); queue.add(next);
} }
} }
} }
@ -1290,7 +1336,7 @@ Output: 0
Explanation: The endWord "cog" is not in wordList, therefore no possible transformation. Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.
``` ```
找出一条从 beginWord 到 endWord 的最短路径,每次移动规定为改变一个字符,并且改变之后的字符串必须在 wordList 中。 题目描述:找出一条从 beginWord 到 endWord 的最短路径,每次移动规定为改变一个字符,并且改变之后的字符串必须在 wordList 中。
```java ```java
public int ladderLength(String beginWord, String endWord, List<String> wordList) { public int ladderLength(String beginWord, String endWord, List<String> wordList) {
@ -1365,7 +1411,7 @@ private int getShortestPath(List<Integer>[] graphic, int start, int end) {
广度优先搜索一层一层遍历,每一层得到的所有新节点,要用队列存储起来以备下一层遍历的时候再遍历。 广度优先搜索一层一层遍历,每一层得到的所有新节点,要用队列存储起来以备下一层遍历的时候再遍历。
而深度优先搜索在得到一个新节点时立对新节点进行遍历:从节点 0 出发开始遍历,得到到新节点 6 时,立马对新节点 6 进行遍历,得到新节点 4如此反复以这种方式遍历新节点直到没有新节点了此时返回。返回到根节点 0 的情况是,继续对根节点 0 进行遍历,得到新节点 2然后继续以上步骤。 而深度优先搜索在得到一个新节点时立对新节点进行遍历:从节点 0 出发开始遍历,得到到新节点 6 时,立马对新节点 6 进行遍历,得到新节点 4如此反复以这种方式遍历新节点直到没有新节点了此时返回。返回到根节点 0 的情况是,继续对根节点 0 进行遍历,得到新节点 2然后继续以上步骤。
从一个节点出发,使用 DFS 对一个图进行遍历时能够遍历到的节点都是从初始节点可达的DFS 常用来求解这种 **可达性** 问题。 从一个节点出发,使用 DFS 对一个图进行遍历时能够遍历到的节点都是从初始节点可达的DFS 常用来求解这种 **可达性** 问题。
@ -1479,12 +1525,14 @@ Input:
[[1,1,0], [[1,1,0],
[1,1,0], [1,1,0],
[0,0,1]] [0,0,1]]
Output: 2 Output: 2
Explanation:The 0th and 1st students are direct friends, so they are in a friend circle. Explanation:The 0th and 1st students are direct friends, so they are in a friend circle.
The 2nd student himself is in a friend circle. So return 2. The 2nd student himself is in a friend circle. So return 2.
``` ```
好友关系可以看成是一个无向图,例如第 0 个人与第 1 个人是好友,那么 M[0][1] 和 M[1][0] 的值都为 1。 题目描述:好友关系可以看成是一个无向图,例如第 0 个人与第 1 个人是好友,那么 M[0][1] 和 M[1][0] 的值都为 1。
```java ```java
private int n; private int n;
@ -1530,7 +1578,7 @@ X X X X
X O X X X O X X
``` ```
使被 'X' 包围的 'O' 转换为 'X'。 题目描述:使被 'X' 包围的 'O' 转换为 'X'。
先填充最外侧,剩下的就是里侧了。 先填充最外侧,剩下的就是里侧了。
@ -1678,7 +1726,6 @@ Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
``` ```
```java ```java
private static final String[] KEYS = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; private static final String[] KEYS = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
public List<String> letterCombinations(String digits) { public List<String> letterCombinations(String digits) {
@ -2316,11 +2363,11 @@ private int cubeNum(int i, int j) {
一行一行地摆放在确定一行中的那个皇后应该摆在哪一列时需要用三个标记数组来确定某一列是否合法这三个标记数组分别为列标记数组、45 度对角线标记数组和 135 度对角线标记数组。 一行一行地摆放在确定一行中的那个皇后应该摆在哪一列时需要用三个标记数组来确定某一列是否合法这三个标记数组分别为列标记数组、45 度对角线标记数组和 135 度对角线标记数组。
45 度对角线标记数组的度为 2 \* n - 1通过下图可以明确 (r, c) 的位置所在的数组下标为 r + c。 45 度对角线标记数组的度为 2 \* n - 1通过下图可以明确 (r, c) 的位置所在的数组下标为 r + c。
<div align="center"> <img src="../pics//85583359-1b45-45f2-9811-4f7bb9a64db7.jpg"/> </div><br> <div align="center"> <img src="../pics//85583359-1b45-45f2-9811-4f7bb9a64db7.jpg"/> </div><br>
135 度对角线标记数组的度也是 2 \* n - 1(r, c) 的位置所在的数组下标为 n - 1 - (r - c)。 135 度对角线标记数组的度也是 2 \* n - 1(r, c) 的位置所在的数组下标为 n - 1 - (r - c)。
<div align="center"> <img src="../pics//9e80f75a-b12b-4344-80c8-1f9ccc2d5246.jpg"/> </div><br> <div align="center"> <img src="../pics//9e80f75a-b12b-4344-80c8-1f9ccc2d5246.jpg"/> </div><br>
@ -2389,8 +2436,6 @@ private void backtracking(int row) {
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=dp[i-1]+dp[i-2]"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=dp[i-1]+dp[i-2]"/></div> <br>
dp[N] 即为所求。
考虑到 dp[i] 只与 dp[i - 1] 和 dp[i - 2] 有关,因此可以只用两个变量来存储 dp[i - 1] 和 dp[i - 2],使得原来的 O(N) 空间复杂度优化为 O(1) 复杂度。 考虑到 dp[i] 只与 dp[i - 1] 和 dp[i - 2] 有关,因此可以只用两个变量来存储 dp[i - 1] 和 dp[i - 2],使得原来的 O(N) 空间复杂度优化为 O(1) 复杂度。
```java ```java
@ -2416,27 +2461,19 @@ public int climbStairs(int n) {
定义 dp 数组用来存储最大的抢劫量,其中 dp[i] 表示抢到第 i 个住户时的最大抢劫量。 定义 dp 数组用来存储最大的抢劫量,其中 dp[i] 表示抢到第 i 个住户时的最大抢劫量。
由于不能抢劫邻近住户,因此如果抢劫了第 i 个住户那么只能抢劫 i - 2 或者 i - 3 的住户,所以 由于不能抢劫邻近住户,如果抢劫了第 i -1 个住户,那么就不能再抢劫第 i 个住户,所以
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=max(dp[i-2],dp[i-3])+nums[i]"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=max(dp[i-2]+nums[i],dp[i-1])"/></div> <br>
```java ```java
public int rob(int[] nums) { public int rob(int[] nums) {
int n = nums.length; int pre2 = 0, pre1 = 0;
if (n == 0) { for (int i = 0; i < nums.length; i++) {
return 0; int cur = Math.max(pre2 + nums[i], pre1);
}
if (n == 1) {
return nums[0];
}
int pre3 = 0, pre2 = 0, pre1 = 0;
for (int i = 0; i < n; i++) {
int cur = Math.max(pre2, pre3) + nums[i];
pre3 = pre2;
pre2 = pre1; pre2 = pre1;
pre1 = cur; pre1 = cur;
} }
return Math.max(pre1, pre2); return pre1;
} }
``` ```
@ -2445,7 +2482,7 @@ public int rob(int[] nums) {
[213. House Robber II (Medium)](https://leetcode.com/problems/house-robber-ii/description/) [213. House Robber II (Medium)](https://leetcode.com/problems/house-robber-ii/description/)
```java ```java
public int rob(int[] nums) { public int rob(int[] nums) {
if (nums == null || nums.length == 0) { if (nums == null || nums.length == 0) {
return 0; return 0;
} }
@ -2456,28 +2493,17 @@ public int rob(int[] nums) {
return Math.max(rob(nums, 0, n - 2), rob(nums, 1, n - 1)); return Math.max(rob(nums, 0, n - 2), rob(nums, 1, n - 1));
} }
private int rob(int[] nums, int first, int last) { private int rob(int[] nums, int first, int last) {
int pre3 = 0, pre2 = 0, pre1 = 0; int pre2 = 0, pre1 = 0;
for (int i = first; i <= last; i++) { for (int i = first; i <= last; i++) {
int cur = Math.max(pre3, pre2) + nums[i]; int cur = Math.max(pre1, pre2 + nums[i]);
pre3 = pre2;
pre2 = pre1; pre2 = pre1;
pre1 = cur; pre1 = cur;
} }
return Math.max(pre2, pre1); return pre1;
} }
``` ```
**母牛生产**
[程序员代码面试指南-P181](#)
题目描述:假设农场中成熟的母牛每年都会生 1 头小母牛,并且永远不会死。第一年有 1 只小母牛,从第二年开始,母牛开始生小母牛。每只小母牛 3 年之后成熟又可以生小母牛。给定整数 N求 N 年后牛的数量。
第 i 年成熟的牛的数量为:
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=dp[i-1]+dp[i-3]"/></div> <br>
**信件错排** **信件错排**
题目描述:有 N 个 信 和 信封,它们被打乱,求错误装信方式的数量。 题目描述:有 N 个 信 和 信封,它们被打乱,求错误装信方式的数量。
@ -2491,7 +2517,15 @@ private int rob(int[] nums, int first, int last) {
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=(i-1)*dp[i-2]+(i-1)*dp[i-1]"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=(i-1)*dp[i-2]+(i-1)*dp[i-1]"/></div> <br>
dp[N] 即为所求。 **母牛生产**
[程序员代码面试指南-P181](#)
题目描述:假设农场中成熟的母牛每年都会生 1 头小母牛,并且永远不会死。第一年有 1 只小母牛,从第二年开始,母牛开始生小母牛。每只小母牛 3 年之后成熟又可以生小母牛。给定整数 N求 N 年后牛的数量。
第 i 年成熟的牛的数量为:
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=dp[i-1]+dp[i-3]"/></div> <br>
### 矩阵路径 ### 矩阵路径
@ -2517,10 +2551,8 @@ public int minPathSum(int[][] grid) {
int[] dp = new int[n]; int[] dp = new int[n];
for (int i = 0; i < m; i++) { for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) { for (int j = 0; j < n; j++) {
if (j == 0) { if (i == 0) {
dp[j] = dp[j]; // 只能从上侧走到该位置 dp[j] = dp[j - 1];
} else if (i == 0) {
dp[j] = dp[j - 1]; // 只能从左侧走到该位置
} else { } else {
dp[j] = Math.min(dp[j - 1], dp[j]); dp[j] = Math.min(dp[j - 1], dp[j]);
} }
@ -2552,7 +2584,7 @@ public int uniquePaths(int m, int n) {
} }
``` ```
也可以直接用数学公式求解,这是一个组合问题。机器人总共移动的次数 S=m+n-2向下移动的次数 D=m-1那么问题可以看成从 S 取出 D 个位置的组合数量,这个问题的解为 C(S, D)。 也可以直接用数学公式求解,这是一个组合问题。机器人总共移动的次数 S=m+n-2向下移动的次数 D=m-1那么问题可以看成从 S 取出 D 个位置的组合数量,这个问题的解为 C(S, D)。
```java ```java
public int uniquePaths(int m, int n) { public int uniquePaths(int m, int n) {
@ -2580,49 +2612,26 @@ sumRange(2, 5) -> -1
sumRange(0, 5) -> -3 sumRange(0, 5) -> -3
``` ```
求区间 i \~ j 的和,可以转换为 sum[j] - sum[i-1],其中 sum[i] 为 0 \~ i 的和。 求区间 i \~ j 的和,可以转换为 sum[j + 1] - sum[i],其中 sum[i] 为 0 \~ i - 1 的和。
```java ```java
class NumArray { class NumArray {
private int[] sums; private int[] sums;
public NumArray(int[] nums) { public NumArray(int[] nums) {
sums = new int[nums.length]; sums = new int[nums.length + 1];
for (int i = 0; i < nums.length; i++) { for (int i = 1; i <= nums.length; i++) {
sums[i] = i == 0 ? nums[0] : sums[i - 1] + nums[i]; sums[i] = sums[i - 1] + nums[i - 1];
} }
} }
public int sumRange(int i, int j) { public int sumRange(int i, int j) {
return i == 0 ? sums[j] : sums[j] - sums[i - 1]; return sums[j + 1] - sums[i];
} }
} }
``` ```
**子数组最大的和**
[53. Maximum Subarray (Easy)](https://leetcode.com/problems/maximum-subarray/description/)
```html
For example, given the array [-2,1,-3,4,-1,2,1,-5,4],
the contiguous subarray [4,-1,2,1] has the largest sum = 6.
```
```java
public int maxSubArray(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int preSum = nums[0];
int maxSum = preSum;
for (int i = 1; i < nums.length; i++) {
preSum = preSum > 0 ? preSum + nums[i] : nums[i];
maxSum = Math.max(maxSum, preSum);
}
return maxSum;
}
```
**数组中等差递增子区间的个数** **数组中等差递增子区间的个数**
[413. Arithmetic Slices (Medium)](https://leetcode.com/problems/arithmetic-slices/description/) [413. Arithmetic Slices (Medium)](https://leetcode.com/problems/arithmetic-slices/description/)
@ -2634,7 +2643,7 @@ return: 3, for 3 arithmetic slices in A: [1, 2, 3], [2, 3, 4] and [1, 2, 3, 4] i
dp[i] 表示以 A[i] 为结尾的等差递增子区间的个数。 dp[i] 表示以 A[i] 为结尾的等差递增子区间的个数。
如果 A[i] - A[i - 1] == A[i - 1] - A[i - 2],表示 [A[i - 2], A[i - 1], A[i]] 是一个等差递增子区间。如果 [A[i - 3], A[i - 2], A[i - 1]] 是一个等差递增子区间,那么 [A[i - 3], A[i - 2], A[i - 1], A[i]] 也是。因此在这个条件下dp[i] = dp[i-1] + 1。 在 A[i] - A[i - 1] == A[i - 1] - A[i - 2] 的条件下,{A[i - 2], A[i - 1], A[i]} 是一个等差递增子区间。如果 {A[i - 3], A[i - 2], A[i - 1]} 是一个等差递增子区间,那么 {A[i - 3], A[i - 2], A[i - 1], A[i]} 也是等差递增子区间dp[i] = dp[i-1] + 1。
```java ```java
public int numberOfArithmeticSlices(int[] A) { public int numberOfArithmeticSlices(int[] A) {
@ -2747,17 +2756,17 @@ public int numDecodings(String s) {
### 最长递增子序列 ### 最长递增子序列
已知一个序列 {S<sub>1</sub>, S<sub>2</sub>,...,S<sub>n</sub>} ,取出若干数组成新的序列 {S<sub>i1</sub>, S<sub>i2</sub>,..., S<sub>im</sub>},其中 i1、i2 ... im 保持递增,即新序列中各个数仍然保持原数列中的先后顺序,称新序列为原序列的一个 **子序列** 已知一个序列 {S<sub>1</sub>, S<sub>2</sub>,...,S<sub>n</sub>},取出若干数组成新的序列 {S<sub>i1</sub>, S<sub>i2</sub>,..., S<sub>im</sub>},其中 i1、i2 ... im 保持递增,即新序列中各个数仍然保持原数列中的先后顺序,称新序列为原序列的一个 **子序列**
如果在子序列中,当下标 ix > iy 时S<sub>ix</sub> > S<sub>iy</sub>,称子序列为原序列的一个 **递增子序列** 如果在子序列中,当下标 ix > iy 时S<sub>ix</sub> > S<sub>iy</sub>,称子序列为原序列的一个 **递增子序列**
定义一个数组 dp 存储最长递增子序列的长度dp[n] 表示以 S<sub>n</sub> 结尾的序列的最长递增子序列长度。对于一个递增子序列 {S<sub>i1</sub>, S<sub>i2</sub>,...,S<sub>im</sub>},如果 im < n 并且 S<sub>im</sub> < S<sub>n</sub> ,此时 {S<sub>i1</sub>, S<sub>i2</sub>,..., S<sub>im</sub>, S<sub>n</sub>} 为一个递增子序列,递增子序列的长度增加 1。满足上述条件的递增子序列中长度最长的那个递增子序列就是要找的在长度最长的递增子序列上加上 S<sub>n</sub> 就构成了以 S<sub>n</sub> 为结尾的最长递增子序列。因此 dp[n] = max{ dp[i]+1 | S<sub>i</sub> < S<sub>n</sub> && i < n} 定义一个数组 dp 存储最长递增子序列的长度dp[n] 表示以 S<sub>n</sub> 结尾的序列的最长递增子序列长度。对于一个递增子序列 {S<sub>i1</sub>, S<sub>i2</sub>,...,S<sub>im</sub>},如果 im < n 并且 S<sub>im</sub> < S<sub>n</sub>,此时 {S<sub>i1</sub>, S<sub>i2</sub>,..., S<sub>im</sub>, S<sub>n</sub>} 为一个递增子序列,递增子序列的长度增加 1。满足上述条件的递增子序列中长度最长的那个递增子序列就是要找的在长度最长的递增子序列上加上 S<sub>n</sub> 就构成了以 S<sub>n</sub> 为结尾的最长递增子序列。因此 dp[n] = max{ dp[i]+1 | S<sub>i</sub> < S<sub>n</sub> && i < n}
因为在求 dp[n] 时可能无法找到一个满足条件的递增子序列,此时 {S<sub>n</sub>} 就构成了递增子序列,需要对前面的求解方程做修改,令 dp[n] 最小为 1 因为在求 dp[n] 时可能无法找到一个满足条件的递增子序列,此时 {S<sub>n</sub>} 就构成了递增子序列,需要对前面的求解方程做修改,令 dp[n] 最小为 1
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[n]=max\{1,dp[i]+1|S_i<S_n\&\&i<n\}"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[n]=max\{1,dp[i]+1|S_i<S_n\&\&i<n\}"/></div> <br>
对于一个长度为 N 的序列,最长递增子序列并不一定会以 S<sub>N</sub> 为结尾,因此 dp[N] 不是序列的最长递增子序列的长度,需要遍历 dp 数组找出最大值才是所要的结果,max{ dp[i] | 1 <= i <= N} 即为所求。 对于一个长度为 N 的序列,最长递增子序列并不一定会以 S<sub>N</sub> 为结尾,因此 dp[N] 不是序列的最长递增子序列的长度,需要遍历 dp 数组找出最大值才是所要的结果max{ dp[i] | 1 <= i <= N} 即为所求。
**最长递增子序列** **最长递增子序列**
@ -2790,12 +2799,12 @@ for (int i = 0; i < n; i++) {
return ret; return ret;
``` ```
以上解法的时间复杂度为 O(N<sup>2</sup>) ,可以使用二分查找将时间复杂度降低为 O(NlogN)。 以上解法的时间复杂度为 O(N<sup>2</sup>),可以使用二分查找将时间复杂度降低为 O(NlogN)。
定义一个 tails 数组,其中 tails[i] 存储长度为 i + 1 的最长递增子序列的最后一个元素。对于一个元素 x 定义一个 tails 数组,其中 tails[i] 存储长度为 i + 1 的最长递增子序列的最后一个元素。对于一个元素 x
- 如果它大于 tails 数组所有的值,那么把它添加到 tails 后面,表示最长递增子序列长度加 1 - 如果它大于 tails 数组所有的值,那么把它添加到 tails 后面,表示最长递增子序列长度加 1
- 如果 tails[i-1] < x <= tails[i]那么更新 tails[i-1] = x - 如果 tails[i-1] < x <= tails[i]那么更新 tails[i] = x
例如对于数组 [4,3,6,5],有: 例如对于数组 [4,3,6,5],有:
@ -2915,19 +2924,19 @@ public int wiggleMaxLength(int[] nums) {
定义一个二维数组 dp 用来存储最长公共子序列的长度,其中 dp[i][j] 表示 S1 的前 i 个字符与 S2 的前 j 个字符最长公共子序列的长度。考虑 S1<sub>i</sub> 与 S2<sub>j</sub> 值是否相等,分为两种情况: 定义一个二维数组 dp 用来存储最长公共子序列的长度,其中 dp[i][j] 表示 S1 的前 i 个字符与 S2 的前 j 个字符最长公共子序列的长度。考虑 S1<sub>i</sub> 与 S2<sub>j</sub> 值是否相等,分为两种情况:
- 当 S1<sub>i</sub>==S2<sub>j</sub> 时,那么就能在 S1 的前 i-1 个字符与 S2 的前 j-1 个字符最长公共子序列的基础上再加上 S1<sub>i</sub> 这个值,最长公共子序列长度加 1 ,即 dp[i][j] = dp[i-1][j-1] + 1。 - 当 S1<sub>i</sub>==S2<sub>j</sub> 时,那么就能在 S1 的前 i-1 个字符与 S2 的前 j-1 个字符最长公共子序列的基础上再加上 S1<sub>i</sub> 这个值,最长公共子序列长度加 1即 dp[i][j] = dp[i-1][j-1] + 1。
- 当 S1<sub>i</sub> != S2<sub>j</sub> 时,此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前 j 个字符最长公共子序列, S1 的前 i 个字符和 S2 的前 j-1 个字符最长公共子序列,它们的最大者,即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。 - 当 S1<sub>i</sub> != S2<sub>j</sub> 时,此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前 j 个字符最长公共子序列,或者 S1 的前 i 个字符和 S2 的前 j-1 个字符最长公共子序列,它们的最大者,即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。
综上,最长公共子序列的状态转移方程为: 综上,最长公共子序列的状态转移方程为:
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i][j]=\left\{\begin{array}{rcl}dp[i-1][j-1]&&{S1_i==S2_j}\\max(dp[i-1][j],dp[i][j-1])&&{S1_i<>S2_j}\end{array}\right."/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i][j]=\left\{\begin{array}{rcl}dp[i-1][j-1]&&{S1_i==S2_j}\\max(dp[i-1][j],dp[i][j-1])&&{S1_i<>S2_j}\end{array}\right."/></div> <br>
对于长度为 N 的序列 S<sub>1</sub> 长度为 M 的序列 S<sub>2</sub>dp[N][M] 就是序列 S<sub>1</sub> 和序列 S<sub>2</sub> 的最长公共子序列长度。 对于长度为 N 的序列 S<sub>1</sub> 和长度为 M 的序列 S<sub>2</sub>dp[N][M] 就是序列 S<sub>1</sub> 和序列 S<sub>2</sub> 的最长公共子序列长度。
与最长递增子序列相比,最长公共子序列有以下不同点: 与最长递增子序列相比,最长公共子序列有以下不同点:
- 针对的是两个序列,求它们的最长公共子序列。 - 针对的是两个序列,求它们的最长公共子序列。
- 在最长递增子序列中dp[i] 表示以 S<sub>i</sub> 为结尾的最长递增子序列长度,子序列必须包含 S<sub>i</sub> 在最长公共子序列中dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度,不一定包含 S1<sub>i</sub> 和 S2<sub>j</sub> - 在最长递增子序列中dp[i] 表示以 S<sub>i</sub> 为结尾的最长递增子序列长度,子序列必须包含 S<sub>i</sub> 在最长公共子序列中dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度,不一定包含 S1<sub>i</sub> 和 S2<sub>j</sub>
- 在求最终解时,最长公共子序列中 dp[N][M] 就是最终解,而最长递增子序列中 dp[N] 不是最终解,因为以 S<sub>N</sub> 为结尾的最长递增子序列不一定是整个序列最长递增子序列,需要遍历一遍 dp 数组找到最大者。 - 在求最终解时,最长公共子序列中 dp[N][M] 就是最终解,而最长递增子序列中 dp[N] 不是最终解,因为以 S<sub>N</sub> 为结尾的最长递增子序列不一定是整个序列最长递增子序列,需要遍历一遍 dp 数组找到最大者。
```java ```java
@ -2956,9 +2965,7 @@ public int lengthOfLCS(int[] nums1, int[] nums2) {
- 第 i 件物品没添加到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值dp[i][j] = dp[i-1][j]。 - 第 i 件物品没添加到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值dp[i][j] = dp[i-1][j]。
- 第 i 件物品添加到背包中dp[i][j] = dp[i-1][j-w] + v。 - 第 i 件物品添加到背包中dp[i][j] = dp[i-1][j-w] + v。
第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。 第 i 件物品可添加也可以不添加取决于哪种情况下最大价值更大。因此0-1 背包的状态转移方程为:
综上0-1 背包的状态转移方程为:
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i][j]=max(dp[i-1][j],dp[i-1][j-w]+v)"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i][j]=max(dp[i-1][j],dp[i-1][j-w]+v)"/></div> <br>
@ -2981,11 +2988,11 @@ public int knapsack(int W, int N, int[] weights, int[] values) {
**空间优化** **空间优化**
在程序实现时可以对 0-1 背包做优化。观察状态转移方程可以知道,前 i 件物品的状态仅前 i-1 件物品的状态有关,因此可以将 dp 定义为一维数组,其中 dp[j] 既可以表示 dp[i-1][j] 也可以表示 dp[i][j]。此时, 在程序实现时可以对 0-1 背包做优化。观察状态转移方程可以知道,前 i 件物品的状态仅前 i-1 件物品的状态有关,因此可以将 dp 定义为一维数组,其中 dp[j] 既可以表示 dp[i-1][j] 也可以表示 dp[i][j]。此时,
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[j]=max(dp[j],dp[j-w]+v)"/></div> <br> <div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[j]=max(dp[j],dp[j-w]+v)"/></div> <br>
因为 dp[j-w] 表示 dp[i-1][j-w],因此不能先求 dp[i][j-w],以防将 dp[i-1][j-w] 覆盖。也就是说要先计算 dp[i][j] 再计算 dp[i][j-w],在程序实现时需要按倒序来循环求解。 因为 dp[j-w] 表示 dp[i-1][j-w],因此不能先求 dp[i][j-w],以防将 dp[i-1][j-w] 覆盖。也就是说要先计算 dp[i][j] 再计算 dp[i][j-w],在程序实现时需要按倒序来循环求解。
```java ```java
public int knapsack(int W, int N, int[] weights, int[] values) { public int knapsack(int W, int N, int[] weights, int[] values) {
@ -3102,7 +3109,6 @@ public int findTargetSumWays(int[] nums, int S) {
int W = (sum + S) / 2; int W = (sum + S) / 2;
int[] dp = new int[W + 1]; int[] dp = new int[W + 1];
dp[0] = 1; dp[0] = 1;
Arrays.sort(nums);
for (int num : nums) { for (int num : nums) {
for (int i = W; i >= num; i--) { for (int i = W; i >= num; i--) {
dp[i] = dp[i] + dp[i - num]; dp[i] = dp[i] + dp[i - num];
@ -3205,7 +3211,7 @@ public int findMaxForm(String[] strs, int m, int n) {
} }
``` ```
**找零钱的方法数** **找零钱的最少硬币数**
[322. Coin Change (Medium)](https://leetcode.com/problems/coin-change/description/) [322. Coin Change (Medium)](https://leetcode.com/problems/coin-change/description/)
@ -3286,60 +3292,6 @@ public int combinationSum4(int[] nums, int target) {
} }
``` ```
**只能进行 k 次的股票交易**
[188. Best Time to Buy and Sell Stock IV (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/description/)
```java
public int maxProfit(int k, int[] prices) {
int n = prices.length;
if (k >= n / 2) { // 这种情况下该问题退化为普通的股票交易问题
int maxProfit = 0;
for (int i = 1; i < n; i++) {
if (prices[i] > prices[i - 1]) {
maxProfit += prices[i] - prices[i - 1];
}
}
return maxProfit;
}
int[][] maxProfit = new int[k + 1][n];
for (int i = 1; i <= k; i++) {
int localMax = maxProfit[i - 1][0] - prices[0];
for (int j = 1; j < n; j++) {
maxProfit[i][j] = Math.max(maxProfit[i][j - 1], prices[j] + localMax);
localMax = Math.max(localMax, maxProfit[i - 1][j] - prices[j]);
}
}
return maxProfit[k][n - 1];
}
```
**只能进行两次的股票交易**
[123. Best Time to Buy and Sell Stock III (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/description/)
```java
public int maxProfit(int[] prices) {
int firstBuy = Integer.MIN_VALUE, firstSell = 0;
int secondBuy = Integer.MIN_VALUE, secondSell = 0;
for (int curPrice : prices) {
if (firstBuy < -curPrice) {
firstBuy = -curPrice;
}
if (firstSell < firstBuy + curPrice) {
firstSell = firstBuy + curPrice;
}
if (secondBuy < firstSell - curPrice) {
secondBuy = firstSell - curPrice;
}
if (secondSell < secondBuy + curPrice) {
secondSell = secondBuy + curPrice;
}
}
return secondSell;
}
```
### 股票交易 ### 股票交易
**需要冷却期的股票交易** **需要冷却期的股票交易**
@ -3410,25 +3362,58 @@ public int maxProfit(int[] prices, int fee) {
} }
``` ```
**买入和售出股票最大的收益**
[121. Best Time to Buy and Sell Stock (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/) **只能进行两次的股票交易**
题目描述:只进行一次交易。 [123. Best Time to Buy and Sell Stock III (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/description/)
只要记录前面的最小价格,将这个最小价格作为买入价格,然后将当前的价格作为售出价格,查看当前收益是不是最大收益。
```java ```java
public int maxProfit(int[] prices) { public int maxProfit(int[] prices) {
int n = prices.length; int firstBuy = Integer.MIN_VALUE, firstSell = 0;
if (n == 0) return 0; int secondBuy = Integer.MIN_VALUE, secondSell = 0;
int soFarMin = prices[0]; for (int curPrice : prices) {
int max = 0; if (firstBuy < -curPrice) {
for (int i = 1; i < n; i++) { firstBuy = -curPrice;
if (soFarMin > prices[i]) soFarMin = prices[i]; }
else max = Math.max(max, prices[i] - soFarMin); if (firstSell < firstBuy + curPrice) {
firstSell = firstBuy + curPrice;
}
if (secondBuy < firstSell - curPrice) {
secondBuy = firstSell - curPrice;
}
if (secondSell < secondBuy + curPrice) {
secondSell = secondBuy + curPrice;
}
} }
return max; return secondSell;
}
```
**只能进行 k 次的股票交易**
[188. Best Time to Buy and Sell Stock IV (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/description/)
```java
public int maxProfit(int k, int[] prices) {
int n = prices.length;
if (k >= n / 2) { // 这种情况下该问题退化为普通的股票交易问题
int maxProfit = 0;
for (int i = 1; i < n; i++) {
if (prices[i] > prices[i - 1]) {
maxProfit += prices[i] - prices[i - 1];
}
}
return maxProfit;
}
int[][] maxProfit = new int[k + 1][n];
for (int i = 1; i <= k; i++) {
int localMax = maxProfit[i - 1][0] - prices[0];
for (int j = 1; j < n; j++) {
maxProfit[i][j] = Math.max(maxProfit[i][j - 1], prices[j] + localMax);
localMax = Math.max(localMax, maxProfit[i - 1][j] - prices[j]);
}
}
return maxProfit[k][n - 1];
} }
``` ```
@ -3450,11 +3435,8 @@ Explanation: You need one step to make "sea" to "ea" and another step to make "e
public int minDistance(String word1, String word2) { public int minDistance(String word1, String word2) {
int m = word1.length(), n = word2.length(); int m = word1.length(), n = word2.length();
int[][] dp = new int[m + 1][n + 1]; int[][] dp = new int[m + 1][n + 1];
for (int i = 0; i <= m; i++) { for (int i = 1; i <= m; i++) {
for (int j = 0; j <= n; j++) { for (int j = 1; j <= n; j++) {
if (i == 0 || j == 0) {
continue;
}
if (word1.charAt(i - 1) == word2.charAt(j - 1)) { if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + 1; dp[i][j] = dp[i - 1][j - 1] + 1;
} else { } else {
@ -3612,7 +3594,7 @@ public int countPrimes(int n) {
```java ```java
int gcd(int a, int b) { int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a% b); return b == 0 ? a : gcd(b, a % b);
} }
``` ```
@ -3683,7 +3665,7 @@ public String convertToBase7(int num) {
} }
``` ```
Java 中 static String toString(int num, int radix) 可以将一个整数转换为 redix 进制表示的字符串。 Java 中 static String toString(int num, int radix) 可以将一个整数转换为 radix 进制表示的字符串。
```java ```java
public String convertToBase7(int num) { public String convertToBase7(int num) {
@ -6413,7 +6395,6 @@ public int maxChunksToSorted(int[] arr) {
} }
``` ```
## 图 ## 图
### 二分图 ### 二分图
@ -6630,6 +6611,7 @@ public int[] findRedundantConnection(int[][] edges) {
} }
private class UF { private class UF {
private int[] id; private int[] id;
UF(int N) { UF(int N) {
@ -6675,7 +6657,7 @@ x ^ x = 0 x & x = x x | x = x
``` ```
- 利用 x ^ 1s = \~x 的特点,可以将位级表示翻转;利用 x ^ x = 0 的特点,可以将三个数中重复的两个数去除,只留下另一个数。 - 利用 x ^ 1s = \~x 的特点,可以将位级表示翻转;利用 x ^ x = 0 的特点,可以将三个数中重复的两个数去除,只留下另一个数。
- 利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。一个数 num 与 mask 00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位。 - 利用 x & 0s = 0 和 x & 1s = x 的特点,可以实现掩码操作。一个数 num 与 mask00111100 进行位与操作,只保留 num 中与 mask 的 1 部分相对应的位。
- 利用 x | 0s = x 和 x | 1s = 1s 的特点,可以实现设值操作。一个数 num 与 mask00111100 进行位或操作,将 num 中与 mask 的 1 部分相对应的位都设置为 1。 - 利用 x | 0s = x 和 x | 1s = 1s 的特点,可以实现设值操作。一个数 num 与 mask00111100 进行位或操作,将 num 中与 mask 的 1 部分相对应的位都设置为 1。
位与运算技巧: 位与运算技巧:
@ -6920,7 +6902,6 @@ public boolean isPowerOfFour(int num) {
} }
``` ```
**判断一个数的位级表示是否不会出现连续的 0 和 1** **判断一个数的位级表示是否不会出现连续的 0 和 1**
[693. Binary Number with Alternating Bits (Easy)](https://leetcode.com/problems/binary-number-with-alternating-bits/description/) [693. Binary Number with Alternating Bits (Easy)](https://leetcode.com/problems/binary-number-with-alternating-bits/description/)
@ -7074,4 +7055,3 @@ public int[] countBits(int num) {
- 何海涛, 软件工程师. 剑指 Offer: 名企面试官精讲典型编程题[M]. 电子工业出版社, 2014. - 何海涛, 软件工程师. 剑指 Offer: 名企面试官精讲典型编程题[M]. 电子工业出版社, 2014.
- 《编程之美》小组. 编程之美[M]. 电子工业出版社, 2008. - 《编程之美》小组. 编程之美[M]. 电子工业出版社, 2008.
- 左程云. 程序员代码面试指南[M]. 电子工业出版社, 2015. - 左程云. 程序员代码面试指南[M]. 电子工业出版社, 2015.

View File

@ -45,7 +45,7 @@
* [变量操作](#变量操作) * [变量操作](#变量操作)
* [指令搜索顺序](#指令搜索顺序) * [指令搜索顺序](#指令搜索顺序)
* [数据流重定向](#数据流重定向) * [数据流重定向](#数据流重定向)
* [八、管线指令](#八管线指令) * [八、管道指令](#八管道指令)
* [提取指令](#提取指令) * [提取指令](#提取指令)
* [排序指令](#排序指令) * [排序指令](#排序指令)
* [双向输出重定向](#双向输出重定向) * [双向输出重定向](#双向输出重定向)
@ -129,15 +129,16 @@ info 与 man 类似,但是 info 将文档分成一个个页面,每个页面
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dmtsai/.local/bin:/home/dmtsai/bin
``` ```
env 命令可以获取当前终端的环境变量。
## sudo ## sudo
sudo 允许一般用户使用 root 可执行的命令,不过只有在 /etc/sudoers 配置文件中添加的用户才能使用该指令。 sudo 允许一般用户使用 root 可执行的命令,不过只有在 /etc/sudoers 配置文件中添加的用户才能使用该指令。
## 包管理工具 ## 包管理工具
RPM 和 DPKG 为最常见的两类软件包管理工具。RPM 全称为 Redhat Package Manager最早由 Red Hat 公司制定实施,随后被 GNU 开源操作系统接受并成为很多 Linux 系统 (RHEL) 的既定软件标准。与 RPM 进行竞争的是基于 Debian 操作系统 (UBUNTU) 的 DEB 软件包管理工具 DPKG全称为 Debian Package功能方面与 RPM 相似。 RPM 和 DPKG 为最常见的两类软件包管理工具:
- RPM 全称为 Redhat Package Manager最早由 Red Hat 公司制定实施,随后被 GNU 开源操作系统接受并成为很多 Linux 系统 (RHEL) 的既定软件标准。
- 与 RPM 进行竞争的是基于 Debian 操作系统 (Ubuntu) 的 DEB 软件包管理工具 DPKG全称为 Debian Package功能方面与 RPM 相似。
YUM 基于 RPM具有依赖管理功能并具有软件升级的功能。 YUM 基于 RPM具有依赖管理功能并具有软件升级的功能。
@ -194,13 +195,13 @@ IDEATA全称 Advanced Technology Attachment接口速度最大为 133MB/
### 2. SATA ### 2. SATA
SATA 全称 Serial ATA也就是使用串口的 ATA 接口,抗干扰性强,且对数据线的长度要求比 ATA 低很多,支持热插拔等功能SATA-II 的接口速度为 300MiB/s而新的 SATA-III 标准可达到 600MiB/s 的传输速度。SATA 的数据线也比 ATA 的细得多,有利于机箱内的空气流通,整理线材也比较方便。 SATA 全称 Serial ATA也就是使用串口的 ATA 接口,抗干扰性强,且对数据线的长度要求比 ATA 低很多,支持热插拔等功能SATA-II 的接口速度为 300MiB/s而新的 SATA-III 标准可达到 600MiB/s 的传输速度。SATA 的数据线也比 ATA 的细得多,有利于机箱内的空气流通,整理线材也比较方便。
<div align="center"> <img src="../pics//f9f2a16b-4843-44d1-9759-c745772e9bcf.jpg" width=""/> </div><br> <div align="center"> <img src="../pics//f9f2a16b-4843-44d1-9759-c745772e9bcf.jpg" width=""/> </div><br>
### 3. SCSI ### 3. SCSI
SCSI 全称是 Small Computer System Interface小型机系统接口经历多代的发展从早期的 SCSI-II到目前的 Ultra320 SCSI 以及 Fiber-Channel光纤通道接口型式也多种多样。SCSI 硬盘广为工作站级个人电脑以及服务器所使用,因此会使用较为先进的技术,如碟片转速 15000rpm 的高转速,且资料传输时 CPU 占用率较低,但是单价也比相同容量的 ATA 及 SATA 硬盘更加昂贵。 SCSI 全称是 Small Computer System Interface小型机系统接口经历多代的发展从早期的 SCSI-II 到目前的 Ultra320 SCSI 以及 Fiber-Channel光纤通道接口型式也多种多样。SCSI 硬盘广为工作站级个人电脑以及服务器所使用,因此会使用较为先进的技术,如碟片转速 15000rpm 的高转速,且传输时 CPU 占用率较低,但是单价也比相同容量的 ATA 及 SATA 硬盘更加昂贵。
<div align="center"> <img src="../pics//f0574025-c514-49f5-a591-6d6a71f271f7.jpg" width=""/> </div><br> <div align="center"> <img src="../pics//f0574025-c514-49f5-a591-6d6a71f271f7.jpg" width=""/> </div><br>
@ -229,7 +230,7 @@ Linux 中每个硬件都被当做一个文件,包括磁盘。磁盘以磁盘
MBR 中第一个扇区最重要里面有主要开机记录Master boot record, MBR及分区表partition table其中主要开机记录占 446 bytes分区表占 64 bytes。 MBR 中第一个扇区最重要里面有主要开机记录Master boot record, MBR及分区表partition table其中主要开机记录占 446 bytes分区表占 64 bytes。
分区表只有 64 bytes最多只能存储 4 个分区,这 4 个分区为主分区Primary和扩展分区Extended。其中扩展分区只有一个将其它扇区用来记录分区表,因此通过扩展分区可以分出更多分区,这些分区称为逻辑分区。 分区表只有 64 bytes最多只能存储 4 个分区,这 4 个分区为主分区Primary和扩展分区Extended。其中扩展分区只有一个使用其它扇区用记录额外的分区表,因此通过扩展分区可以分出更多分区,这些分区称为逻辑分区。
Linux 也把分区当成文件,分区文件的命名方式为:磁盘文件名 + 编号,例如 /dev/sda1。注意逻辑分区的编号从 5 开始。 Linux 也把分区当成文件,分区文件的命名方式为:磁盘文件名 + 编号,例如 /dev/sda1。注意逻辑分区的编号从 5 开始。
@ -251,10 +252,10 @@ MBR 不支持 2.2 TB 以上的硬盘GPT 则最多支持到 2<sup>33</sup> TB
BIOSBasic Input/Output System基本输入输出系统它是一个固件嵌入在硬件中的软件BIOS 程序存放在断电后内容不会丢失的只读内存中。 BIOSBasic Input/Output System基本输入输出系统它是一个固件嵌入在硬件中的软件BIOS 程序存放在断电后内容不会丢失的只读内存中。
BIOS 是开机的时候计算机执行的第一个程序这个程序知道可以开机的磁盘并读取磁盘第一个扇区的主要开机记录MBR由主要开机记录MBR执行其中的开机管理程序这个开机管理程序会加载操作系统的核心文件。
<div align="center"> <img src="../pics//50831a6f-2777-46ea-a571-29f23c85cc21.jpg"/> </div><br> <div align="center"> <img src="../pics//50831a6f-2777-46ea-a571-29f23c85cc21.jpg"/> </div><br>
BIOS 是开机的时候计算机执行的第一个程序这个程序知道可以开机的磁盘并读取磁盘第一个扇区的主要开机记录MBR由主要开机记录MBR执行其中的开机管理程序这个开机管理程序会加载操作系统的核心文件。
主要开机记录MBR中的开机管理程序提供以下功能选单、载入核心文件以及转交其它开机管理程序。转交这个功能可以用来实现了多重引导只需要将另一个操作系统的开机管理程序安装在其它分区的启动扇区上在启动开机管理程序时就可以通过选单选择启动当前的操作系统或者转交给其它开机管理程序从而启动另一个操作系统。 主要开机记录MBR中的开机管理程序提供以下功能选单、载入核心文件以及转交其它开机管理程序。转交这个功能可以用来实现了多重引导只需要将另一个操作系统的开机管理程序安装在其它分区的启动扇区上在启动开机管理程序时就可以通过选单选择启动当前的操作系统或者转交给其它开机管理程序从而启动另一个操作系统。
下图中第一扇区的主要开机记录MBR中的开机管理程序提供了两个选单M1、M2M1 指向了 Windows 操作系统,而 M2 指向其它分区的启动扇区,里面包含了另外一个开机管理程序,提供了一个指向 Linux 的选单。 下图中第一扇区的主要开机记录MBR中的开机管理程序提供了两个选单M1、M2M1 指向了 Windows 操作系统,而 M2 指向其它分区的启动扇区,里面包含了另外一个开机管理程序,提供了一个指向 Linux 的选单。
@ -283,11 +284,10 @@ BIOS 不可以读取 GPT 分区表,而 UEFI 可以。
除此之外还包括: 除此之外还包括:
- superblock记录文件系统的整体信息包括 inode 和 block 的总量、使用量、剩余量,以及文件系统的格式与相关信息等; - superblock记录文件系统的整体信息包括 inode 和 block 的总量、使用量、剩余量,以及文件系统的格式与相关信息等;
- block bitmap记录 block 是否被使用的位域 - block bitmap记录 block 是否被使用的位域
<div align="center"> <img src="../pics//BSD_disk.png" width="800"/> </div><br> <div align="center"> <img src="../pics//BSD_disk.png" width="800"/> </div><br>
## 文件读取 ## 文件读取
对于 Ext2 文件系统,当要读取一个文件的内容时,先在 inode 中去查找文件内容所在的所有 block然后把所有 block 的内容读出来。 对于 Ext2 文件系统,当要读取一个文件的内容时,先在 inode 中去查找文件内容所在的所有 block然后把所有 block 的内容读出来。
@ -644,7 +644,7 @@ locate 使用 /var/lib/mlocate/ 这个数据库来进行搜索,它存储在内
example: find . -name "shadow*" example: find . -name "shadow*"
``` ```
(一)与时间有关的选项 **① 与时间有关的选项**
```html ```html
-mtime n :列出在 n 天前的那一天修改过内容的文件 -mtime n :列出在 n 天前的那一天修改过内容的文件
@ -657,7 +657,7 @@ example: find . -name "shadow*"
<div align="center"> <img src="../pics//658fc5e7-79c0-4247-9445-d69bf194c539.png" width=""/> </div><br> <div align="center"> <img src="../pics//658fc5e7-79c0-4247-9445-d69bf194c539.png" width=""/> </div><br>
(二)与文件拥有者和所属群组有关的选项 **② 与文件拥有者和所属群组有关的选项**
```html ```html
-uid n -uid n
@ -668,7 +668,7 @@ example: find . -name "shadow*"
-nogroup搜索所属群组不存在于 /etc/group 的文件 -nogroup搜索所属群组不存在于 /etc/group 的文件
``` ```
(三)与文件权限和名称有关的选项 **③ 与文件权限和名称有关的选项**
```html ```html
-name filename -name filename
@ -823,7 +823,7 @@ $ echo ${array[1]}
- 以绝对或相对路径来执行指令,例如 /bin/ls 或者 ./ls - 以绝对或相对路径来执行指令,例如 /bin/ls 或者 ./ls
- 由别名找到该指令来执行; - 由别名找到该指令来执行;
- 由 Bash 内的指令来执行; - 由 Bash 内的指令来执行;
- 按 \$PATH 变量指定的搜索路径的顺序找到第一个指令来执行。 - 按 \$PATH 变量指定的搜索路径的顺序找到第一个指令来执行。
## 数据流重定向 ## 数据流重定向
@ -846,11 +846,11 @@ $ echo ${array[1]}
$ find /home -name .bashrc > list 2>&1 $ find /home -name .bashrc > list 2>&1
``` ```
# 八、管线指令 # 八、管指令
线是将一个命令的标准输出作为另一个命令的标准输入,在数据需要经过多个步骤的处理之后才能得到我们想要的内容时就可以使用管线 是将一个命令的标准输出作为另一个命令的标准输入,在数据需要经过多个步骤的处理之后才能得到我们想要的内容时就可以使用管
在命令之间使用 | 分隔各个管线命令。 在命令之间使用 | 分隔各个管命令。
```bash ```bash
$ ls -al /etc | less $ ls -al /etc | less
@ -1038,9 +1038,7 @@ $ grep -n 'go\{2,5\}g' regular_express.txt
## printf ## printf
用于格式化输出。 用于格式化输出。它不属于管道命令,在给 printf 传数据时需要使用 $( ) 形式。
它不属于管道命令,在给 printf 传数据时需要使用 $( ) 形式。
```html ```html
$ printf '%10s %5i %5i %5i %8.2f \n' $(cat printf.txt) $ printf '%10s %5i %5i %5i %8.2f \n' $(cat printf.txt)
@ -1055,7 +1053,7 @@ $ printf '%10s %5i %5i %5i %8.2f \n' $(cat printf.txt)
awk 每次处理一行,处理的最小单位是字段,每个字段的命名方式为:\$nn 为字段号,从 1 开始,\$0 表示一整行。 awk 每次处理一行,处理的最小单位是字段,每个字段的命名方式为:\$nn 为字段号,从 1 开始,\$0 表示一整行。
示例:取出登录用户的用户名和 IP 示例:取出最近五个登录用户的用户名和 IP
```html ```html
$ last -n 5 $ last -n 5
@ -1130,17 +1128,7 @@ dmtsai lines: 5 columns: 9
# ps aux | grep threadx # ps aux | grep threadx
``` ```
### 2. top ### 2. pstree
实时显示进程信息
示例:两秒钟刷新一次
```sh
# top -d 2
```
### 3. pstree
查看进程树 查看进程树
@ -1150,6 +1138,16 @@ dmtsai lines: 5 columns: 9
# pstree -A # pstree -A
``` ```
### 3. top
实时显示进程信息
示例:两秒钟刷新一次
```sh
# top -d 2
```
### 4. netstat ### 4. netstat
查看占用端口的进程 查看占用端口的进程
@ -1169,7 +1167,7 @@ dmtsai lines: 5 columns: 9
| S | interruptible sleep (waiting for an event to complete) | | S | interruptible sleep (waiting for an event to complete) |
| Z | zombie (terminated but not reaped by its parent) | | Z | zombie (terminated but not reaped by its parent) |
| T | stopped (either by a job control signal or because it is being traced) | | T | stopped (either by a job control signal or because it is being traced) |
<br>
<div align="center"> <img src="../pics//76a49594323247f21c9b3a69945445ee.png" width=""/> </div><br> <div align="center"> <img src="../pics//76a49594323247f21c9b3a69945445ee.png" width=""/> </div><br>
## SIGCHLD ## SIGCHLD
@ -1179,12 +1177,12 @@ dmtsai lines: 5 columns: 9
- 得到 SIGCHLD 信号; - 得到 SIGCHLD 信号;
- waitpid() 或者 wait() 调用会返回。 - waitpid() 或者 wait() 调用会返回。
<div align="center"> <img src="../pics//flow.png" width=""/> </div><br> 其中子进程发送的 SIGCHLD 信号包含了子进程的信息,比如进程 ID、进程状态、进程使用 CPU 的时间等。
其中子进程发送的 SIGCHLD 信号包含了子进程的信息,包含了进程 ID、进程状态、进程使用 CPU 的时间等。
在子进程退出时,它的进程描述符不会立即释放,这是为了让父进程得到子进程信息,父进程通过 wait() 和 waitpid() 来获得一个已经退出的子进程的信息。 在子进程退出时,它的进程描述符不会立即释放,这是为了让父进程得到子进程信息,父进程通过 wait() 和 waitpid() 来获得一个已经退出的子进程的信息。
<div align="center"> <img src="../pics//flow.png" width=""/> </div><br>
## wait() ## wait()
```c ```c

View File

@ -4,7 +4,7 @@
* [MySQL 索引](#mysql-索引) * [MySQL 索引](#mysql-索引)
* [索引优化](#索引优化) * [索引优化](#索引优化)
* [索引的优点](#索引的优点) * [索引的优点](#索引的优点)
* [索引的使用场景](#索引的使用场景) * [索引的使用条件](#索引的使用条件)
* [二、查询性能优化](#二查询性能优化) * [二、查询性能优化](#二查询性能优化)
* [使用 Explain 进行分析](#使用-explain-进行分析) * [使用 Explain 进行分析](#使用-explain-进行分析)
* [优化数据访问](#优化数据访问) * [优化数据访问](#优化数据访问)
@ -22,7 +22,7 @@
* [水平切分](#水平切分) * [水平切分](#水平切分)
* [垂直切分](#垂直切分) * [垂直切分](#垂直切分)
* [Sharding 策略](#sharding-策略) * [Sharding 策略](#sharding-策略)
* [Sharding 存在的问题及解决方案](#sharding-存在的问题及解决方案) * [Sharding 存在的问题](#sharding-存在的问题)
* [六、复制](#六复制) * [六、复制](#六复制)
* [主从复制](#主从复制) * [主从复制](#主从复制)
* [读写分离](#读写分离) * [读写分离](#读写分离)
@ -48,7 +48,7 @@ B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具
进行查找操作时,首先在根节点进行二分查找,找到一个 key 所在的指针,然后递归地在指针所指向的节点进行查找。直到查找到叶子节点,然后在叶子节点上进行二分查找,找出 key 所对应的 data。 进行查找操作时,首先在根节点进行二分查找,找到一个 key 所在的指针,然后递归地在指针所指向的节点进行查找。直到查找到叶子节点,然后在叶子节点上进行二分查找,找出 key 所对应的 data。
插入删除操作记录会破坏平衡树的平衡性,因此在插入删除操作之后,需要对树进行一个分裂、合并、旋转等操作来维护平衡性。 插入删除操作会破坏平衡树的平衡性,因此在插入删除操作之后,需要对树进行一个分裂、合并、旋转等操作来维护平衡性。
### 3. 与红黑树的比较 ### 3. 与红黑树的比较
@ -58,13 +58,13 @@ B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具
平衡树查找操作的时间复杂度等于树高 h而树高大致为 O(h)=O(log<sub>d</sub>N),其中 d 为每个节点的出度。 平衡树查找操作的时间复杂度等于树高 h而树高大致为 O(h)=O(log<sub>d</sub>N),其中 d 为每个节点的出度。
红黑树的出度为 2而 B+ Tree 的出度一般都非常大,所以红黑树的树高 h 很明显比 B+ Tree 大非常多,检索的次数也就更多。 红黑树的出度为 2而 B+ Tree 的出度一般都非常大,所以红黑树的树高 h 很明显比 B+ Tree 大非常多,查找的次数也就更多。
(二)利用计算机预读特性 (二)利用磁盘预读特性
为了减少磁盘 I/O磁盘往往不是严格按需读取而是每次都会预读。预读过程中磁盘进行顺序读取顺序读取不需要进行磁盘寻道并且只需要很短的旋转时间因此速度会非常快。 为了减少磁盘 I/O磁盘往往不是严格按需读取而是每次都会预读。预读过程中磁盘进行顺序读取顺序读取不需要进行磁盘寻道并且只需要很短的旋转时间速度会非常快。
操作系统一般将内存和磁盘分割成固态大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点并且可以利用预读特性,相邻的节点也能够被预先载入。 操作系统一般将内存和磁盘分割成固态大小的块,每一块称为一页,内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小,使得一次 I/O 就能完全载入一个节点并且可以利用预读特性,相邻的节点也能够被预先载入。
## MySQL 索引 ## MySQL 索引
@ -74,15 +74,15 @@ B+ Tree 是基于 B Tree 和叶子节点顺序访问指针进行实现,它具
是大多数 MySQL 存储引擎的默认索引类型。 是大多数 MySQL 存储引擎的默认索引类型。
因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。除了用于查找,还可以用于排序和分组。 因为不再需要进行全表扫描,只需要对树进行搜索即可,所以查找速度快很多。
除了用于查找,还可以用于排序和分组。
可以指定多个列作为索引列,多个索引列共同组成键。 可以指定多个列作为索引列,多个索引列共同组成键。
适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。如果不是按照索引列的顺序进行查找,则无法使用索引。 适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。如果不是按照索引列的顺序进行查找,则无法使用索引。
InnoDB 的 B+Tree 索引分为主索引和辅助索引。 InnoDB 的 B+Tree 索引分为主索引和辅助索引。主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
<div align="center"> <img src="../pics//c28c6fbc-2bc1-47d9-9b2e-cf3d4034f877.jpg"/> </div><br> <div align="center"> <img src="../pics//c28c6fbc-2bc1-47d9-9b2e-cf3d4034f877.jpg"/> </div><br>
@ -92,7 +92,7 @@ InnoDB 的 B+Tree 索引分为主索引和辅助索引。
### 2. 哈希索引 ### 2. 哈希索引
哈希索引能以 O(1) 时间进行查找,但是失去了有序性,它具有以下限制 哈希索引能以 O(1) 时间进行查找,但是失去了有序性:
- 无法用于排序与分组; - 无法用于排序与分组;
- 只支持精确查找,无法用于部分查找和范围查找。 - 只支持精确查找,无法用于部分查找和范围查找。
@ -101,9 +101,11 @@ InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当
### 3. 全文索引 ### 3. 全文索引
MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。查找条件使用 MATCH AGAINST而不是普通的 WHERE。 MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。
全文索引一般使用倒排索引实现,它记录着关键词到其所在文档的映射。 查找条件使用 MATCH AGAINST而不是普通的 WHERE。
全文索引使用倒排索引实现,它记录着关键词到其所在文档的映射。
InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。 InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。
@ -136,7 +138,9 @@ WHERE actor_id = 1 AND film_id = 1;
### 3. 索引列的顺序 ### 3. 索引列的顺序
让选择性最强的索引列放在前面,索引的选择性是指:不重复的索引值和记录总数的比值。最大值为 1此时每个记录都有唯一的索引与其对应。选择性越高查询效率也越高。 让选择性最强的索引列放在前面。
索引的选择性是指:不重复的索引值和记录总数的比值。最大值为 1此时每个记录都有唯一的索引与其对应。选择性越高查询效率也越高。
例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。 例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。
@ -173,14 +177,16 @@ customer_id_selectivity: 0.0373
- 大大减少了服务器需要扫描的数据行数。 - 大大减少了服务器需要扫描的数据行数。
- 帮助服务器避免进行排序和分组,也就不需要创建临时表B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,因为不需要排序和分组,也就不需要创建临时表)。 - 帮助服务器避免进行排序和分组,以及避免创建临时表B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,因为不需要排序和分组,也就不需要创建临时表)。
- 将随机 I/O 变为顺序 I/OB+Tree 索引是有序的,也就将相邻的数据都存储在一起)。 - 将随机 I/O 变为顺序 I/OB+Tree 索引是有序的,将相邻的数据都存储在一起)。
## 索引的使用场景 ## 索引的使用条件
- 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效;
- 对于中到大型的表,索引就非常有效;
- 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效。
- 对于中到大型的表,索引就非常有效。
- 但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。 - 但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。
# 二、查询性能优化 # 二、查询性能优化
@ -214,7 +220,7 @@ Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explai
一个大查询如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。 一个大查询如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。
```sql ```sql
DELEFT FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH); DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH);
``` ```
```sql ```sql
@ -227,7 +233,7 @@ do {
### 2. 分解大连接查询 ### 2. 分解大连接查询
将一个大连接查询分解成对每一个表进行一次单表查询,然后将结果在应用程序中进行关联,这样做的好处有: 将一个大连接查询分解成对每一个表进行一次单表查询,然后在应用程序中进行关联,这样做的好处有:
- 让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。 - 让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。
- 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余记录的查询。 - 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余记录的查询。
@ -310,7 +316,7 @@ FLOAT、DOUBLE 和 DECIMAL 都可以指定列宽,例如 DECIMAL(18, 9) 表示
VARCHAR 这种变长类型能够节省空间,因为只需要存储必要的内容。但是在执行 UPDATE 时可能会使行变得比原来长当超出一个页所能容纳的大小时就要执行额外的操作。MyISAM 会将行拆成不同的片段存储,而 InnoDB 则需要分裂页来使行放进页内。 VARCHAR 这种变长类型能够节省空间,因为只需要存储必要的内容。但是在执行 UPDATE 时可能会使行变得比原来长当超出一个页所能容纳的大小时就要执行额外的操作。MyISAM 会将行拆成不同的片段存储,而 InnoDB 则需要分裂页来使行放进页内。
VARCHAR 会保留字符串末尾的空格,而 CHAR 会删除 在进行存储和检索时,会保留 VARCHAR 末尾的空格,而会删除 CHAR 末尾的空格
## 时间和日期 ## 时间和日期
@ -326,7 +332,7 @@ MySQL 提供了两种相似的日期时间类型DATETIME 和 TIMESTAMP。
### 2. TIMESTAMP ### 2. TIMESTAMP
和 UNIX 时间戳相同,保存从 1970 年 1 月 1 日午夜(格林威治时间)以来的秒数,使用 4 个字节,只能表示从 1970 年 到 2038 年。 和 UNIX 时间戳相同,保存从 1970 年 1 月 1 日午夜(格林威治时间)以来的秒数,使用 4 个字节,只能表示从 1970 年到 2038 年。
它和时区有关,也就是说一个时间戳在不同的时区所代表的具体时间是不同的。 它和时区有关,也就是说一个时间戳在不同的时区所代表的具体时间是不同的。
@ -348,48 +354,43 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提
## 垂直切分 ## 垂直切分
<div align="center"> <img src="../pics//e130e5b8-b19a-4f1e-b860-223040525cf6.jpg"/> </div><br>
垂直切分是将一张表按列切分成多个表,通常是按照列的关系密集程度进行切分,也可以利用垂直切分将经常被使用的列和不经常被使用的列切分到不同的表中。 垂直切分是将一张表按列切分成多个表,通常是按照列的关系密集程度进行切分,也可以利用垂直切分将经常被使用的列和不经常被使用的列切分到不同的表中。
在数据库的层面使用垂直切分将按数据库中表的密集程度部署到不同的库中,例如将原来的电商数据库垂直切分成商品数据库、用户数据库等。 在数据库的层面使用垂直切分将按数据库中表的密集程度部署到不同的库中,例如将原来的电商数据库垂直切分成商品数据库、用户数据库等。
<div align="center"> <img src="../pics//e130e5b8-b19a-4f1e-b860-223040525cf6.jpg"/> </div><br>
## Sharding 策略 ## Sharding 策略
- 哈希取模hash(key) % NUM_DB - 哈希取模hash(key) % N
- 范围:可以是 ID 范围也可以是时间范围 - 范围:可以是 ID 范围也可以是时间范围
- 映射表:使用单独的一个数据库来存储映射关系 - 映射表:使用单独的一个数据库来存储映射关系
## Sharding 存在的问题及解决方案 ## Sharding 存在的问题
### 1. 事务问题 ### 1. 事务问题
使用分布式事务来解决,比如 XA 接口。 使用分布式事务来解决,比如 XA 接口。
### 2. ### 2.
可以将原来的 JOIN 分解成多个单表查询,然后在用户程序中进行 JOIN 可以将原来的连接分解成多个单表查询,然后在用户程序中进行连接
### 3. ID 唯一性 ### 3. ID 唯一性
- 使用全局唯一 IDGUID - 使用全局唯一 IDGUID
- 为每个分片指定一个 ID 范围 - 为每个分片指定一个 ID 范围
- 分布式 ID 生成器 (如 Twitter 的 Snowflake 算法) - 分布式 ID 生成器 (如 Twitter 的 Snowflake 算法)
更多内容请参考:
- [How Sharding Works](https://medium.com/@jeeyoungk/how-sharding-works-b4dec46b3f6)
- [大众点评订单系统分库分表实践](https://tech.meituan.com/dianping_order_db_sharding.html)
# 六、复制 # 六、复制
## 主从复制 ## 主从复制
主要涉及三个线程binlog 线程、I/O 线程和 SQL 线程。 主要涉及三个线程binlog 线程、I/O 线程和 SQL 线程。
- **binlog 线程** :负责将主服务器上的数据更改写入二进制日志中。 - **binlog 线程** :负责将主服务器上的数据更改写入二进制日志Binary log中。
- **I/O 线程** :负责从主服务器上读取二进制日志,并写入从服务器的中继日志中。 - **I/O 线程** :负责从主服务器上读取二进制日志,并写入从服务器的重放日志Replay log中。
- **SQL 线程** :负责读取中继日志并重放其中的 SQL 语句。 - **SQL 线程** :负责读取重放日志并重放其中的 SQL 语句。
<div align="center"> <img src="../pics//master-slave.png"/> </div><br> <div align="center"> <img src="../pics//master-slave.png"/> </div><br>
@ -417,3 +418,5 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提
- [SQL Azure Federation Introduction](http://geekswithblogs.net/shaunxu/archive/2012/01/07/sql-azure-federation-ndash-introduction.aspx "Title of this entry.") - [SQL Azure Federation Introduction](http://geekswithblogs.net/shaunxu/archive/2012/01/07/sql-azure-federation-ndash-introduction.aspx "Title of this entry.")
- [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) - [MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html)
- [MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735) - [MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735)
- [How Sharding Works](https://medium.com/@jeeyoungk/how-sharding-works-b4dec46b3f6)
- [大众点评订单系统分库分表实践](https://tech.meituan.com/dianping_order_db_sharding.html)

View File

@ -49,7 +49,7 @@
Redis 是速度非常快的非关系型NoSQL内存键值数据库可以存储键和五种不同类型的值之间的映射。 Redis 是速度非常快的非关系型NoSQL内存键值数据库可以存储键和五种不同类型的值之间的映射。
键的类型只能为字符串,值支持五种类型数据类型:字符串、列表、集合、散列表、有序集合。 键的类型只能为字符串,值支持五种数据类型:字符串、列表、集合、散列表、有序集合。
Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片来扩展写性能。 Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,使用复制来扩展读性能,使用分片来扩展写性能。
@ -58,7 +58,7 @@ Redis 支持很多特性,例如将内存中的数据持久化到硬盘中,
| 数据类型 | 可以存储的值 | 操作 | | 数据类型 | 可以存储的值 | 操作 |
| :--: | :--: | :--: | | :--: | :--: | :--: |
| STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作</br> 对整数和浮点数执行自增或者自减操作 | | STRING | 字符串、整数或者浮点数 | 对整个字符串或者字符串的其中一部分执行操作</br> 对整数和浮点数执行自增或者自减操作 |
| LIST | 列表 | 从两端压入或者弹出元素</br> 对单个或者多个元素</br> 进行修剪,只保留一个范围内的元素 | | LIST | 列表 | 从两端压入或者弹出元素 </br> 对单个或者多个元素</br> 进行修剪,只保留一个范围内的元素 |
| SET | 无序集合 | 添加、获取、移除单个元素</br> 检查一个元素是否存在于集合中</br> 计算交集、并集、差集</br> 从集合里面随机获取元素 | | SET | 无序集合 | 添加、获取、移除单个元素</br> 检查一个元素是否存在于集合中</br> 计算交集、并集、差集</br> 从集合里面随机获取元素 |
| HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对</br> 获取所有键值对</br> 检查某个键是否存在| | HASH | 包含键值对的无序散列表 | 添加、获取、移除单个键值对</br> 获取所有键值对</br> 检查某个键是否存在|
| ZSET | 有序集合 | 添加、获取、删除元素</br> 根据分值范围或者成员来获取元素</br> 计算一个键的排名 | | ZSET | 有序集合 | 添加、获取、删除元素</br> 根据分值范围或者成员来获取元素</br> 计算一个键的排名 |
@ -209,7 +209,7 @@ OK
## 字典 ## 字典
dictht 是一个散列表结构,使用拉链法保存哈希冲突的 dictEntry dictht 是一个散列表结构,使用拉链法保存哈希冲突。
```c ```c
/* This is our hash table structure. Every dictionary has two of this as we /* This is our hash table structure. Every dictionary has two of this as we
@ -253,7 +253,7 @@ rehash 操作不是一次性完成,而是采用渐进方式,这是为了避
在 rehash 期间,每次对字典执行添加、删除、查找或者更新操作时,都会执行一次渐进式 rehash。 在 rehash 期间,每次对字典执行添加、删除、查找或者更新操作时,都会执行一次渐进式 rehash。
采用渐进式 rehash 会导致字典中的数据分散在两个 dictht 上,因此对字典的操作也需要到对应的 dictht 去执行。 采用渐进式 rehash 会导致字典中的数据分散在两个 dictht 上,因此对字典的查找操作也需要到对应的 dictht 去执行。
```c ```c
/* Performs N steps of incremental rehashing. Returns 1 if there are still /* Performs N steps of incremental rehashing. Returns 1 if there are still
@ -355,9 +355,9 @@ List 是一个双向链表,可以通过 lpop 和 lpush 写入和读取消息
## 会话缓存 ## 会话缓存
在分布式场景下具有多个应用服务器,可以使用 Redis 来统一存储这些应用服务器的会话信息。 可以使用 Redis 来统一存储多台应用服务器的会话信息。
当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器。 当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性
## 分布式锁实现 ## 分布式锁实现
@ -393,7 +393,7 @@ Redis Cluster 实现了分布式的支持。
- 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘,而 Memcached 的数据则会一直在内存中。 - 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘,而 Memcached 的数据则会一直在内存中。
- Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。 - Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。
# 六、键的过期时间 # 六、键的过期时间
@ -549,7 +549,7 @@ def main():
# 十二、Sentinel # 十二、Sentinel
Sentinel哨兵可以监听服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。 Sentinel哨兵可以监听集群中的服务器,并在主服务器进入下线状态时,自动从从服务器中选举出新的主服务器。
# 十三、分片 # 十三、分片
@ -605,5 +605,4 @@ Redis 没有关系型数据库中的表这一概念来将同种类型的数据
- [论述 Redis 和 Memcached 的差异](http://www.cnblogs.com/loveincode/p/7411911.html) - [论述 Redis 和 Memcached 的差异](http://www.cnblogs.com/loveincode/p/7411911.html)
- [Redis 3.0 中文版- 分片](http://wiki.jikexueyuan.com/project/redis-guide) - [Redis 3.0 中文版- 分片](http://wiki.jikexueyuan.com/project/redis-guide)
- [Redis 应用场景](http://www.scienjus.com/redis-use-case/) - [Redis 应用场景](http://www.scienjus.com/redis-use-case/)
- [Observer vs Pub-Sub](http://developers-club.com/posts/270339/)
- [Using Redis as an LRU cache](https://redis.io/topics/lru-cache) - [Using Redis as an LRU cache](https://redis.io/topics/lru-cache)

View File

@ -5,7 +5,6 @@
* [I/O 复用](#io-复用) * [I/O 复用](#io-复用)
* [信号驱动 I/O](#信号驱动-io) * [信号驱动 I/O](#信号驱动-io)
* [异步 I/O](#异步-io) * [异步 I/O](#异步-io)
* [同步 I/O 与异步 I/O](#同步-io-与异步-io)
* [五大 I/O 模型比较](#五大-io-模型比较) * [五大 I/O 模型比较](#五大-io-模型比较)
* [二、I/O 复用](#二io-复用) * [二、I/O 复用](#二io-复用)
* [select](#select) * [select](#select)
@ -25,9 +24,9 @@
- 等待数据准备好 - 等待数据准备好
- 从内核向进程复制数据 - 从内核向进程复制数据
对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所等待分组到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。 对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所等待数据到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。
Unix 有五种 I/O 模型: Unix 有五种 I/O 模型:
- 阻塞式 I/O - 阻塞式 I/O
- 非阻塞式 I/O - 非阻塞式 I/O
@ -39,7 +38,7 @@ Unix 下有五种 I/O 模型:
应用进程被阻塞,直到数据复制到应用进程缓冲区中才返回。 应用进程被阻塞,直到数据复制到应用进程缓冲区中才返回。
应该注意到,在阻塞的过程中,其它程序还可以执行,因此阻塞不意味着整个操作系统都被阻塞。因为其他程序还可以执行,因此不消耗 CPU 时间,这种模型的执行效率会比较高。 应该注意到,在阻塞的过程中,其它程序还可以执行,因此阻塞不意味着整个操作系统都被阻塞。因为其他程序还可以执行,所以不消耗 CPU 时间,这种模型的 CPU 利用率效率会比较高。
下图中recvfrom 用于接收 Socket 传来的数据,并复制到应用进程的缓冲区 buf 中。这里把 recvfrom() 当成系统调用。 下图中recvfrom 用于接收 Socket 传来的数据,并复制到应用进程的缓冲区 buf 中。这里把 recvfrom() 当成系统调用。
@ -53,17 +52,17 @@ ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *
应用进程执行系统调用之后,内核返回一个错误码。应用进程可以继续执行,但是需要不断的执行系统调用来获知 I/O 是否完成这种方式称为轮询polling 应用进程执行系统调用之后,内核返回一个错误码。应用进程可以继续执行,但是需要不断的执行系统调用来获知 I/O 是否完成这种方式称为轮询polling
由于 CPU 要处理更多的系统调用,因此这种模型是比较低的。 由于 CPU 要处理更多的系统调用,因此这种模型的 CPU 利用率是比较低的。
<div align="center"> <img src="../pics//1492929000361_5.png"/> </div><br> <div align="center"> <img src="../pics//1492929000361_5.png"/> </div><br>
## I/O 复用 ## I/O 复用
使用 select 或者 poll 等待数据,并且可以等待多个套接字中的任何一个变为可读,这一过程会被阻塞,当某一个套接字可读时返回。之后再使用 recvfrom 把数据从内核复制到进程中。 使用 select 或者 poll 等待数据,并且可以等待多个套接字中的任何一个变为可读。这一过程会被阻塞,当某一个套接字可读时返回,之后再使用 recvfrom 把数据从内核复制到进程中。
它可以让单个进程具有处理多个 I/O 事件的能力。又被称为 Event Driven I/O即事件驱动 I/O。 它可以让单个进程具有处理多个 I/O 事件的能力。又被称为 Event Driven I/O即事件驱动 I/O。
如果一个 Web 服务器没有 I/O 复用,那么每一个 Socket 连接都需要创建一个线程去处理。如果同时有几万个连接,那么就需要创建相同数量的线程。并且相比于多进程和多线程技术I/O 复用不需要进程线程创建和切换的开销,系统开销更小。 如果一个 Web 服务器没有 I/O 复用,那么每一个 Socket 连接都需要创建一个线程去处理。如果同时有几万个连接那么就需要创建相同数量的线程。相比于多进程和多线程技术I/O 复用不需要进程线程创建和切换的开销,系统开销更小。
<div align="center"> <img src="../pics//1492929444818_6.png"/> </div><br> <div align="center"> <img src="../pics//1492929444818_6.png"/> </div><br>
@ -77,22 +76,20 @@ ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *
## 异步 I/O ## 异步 I/O
进行 aio_read 系统调用会立即返回,应用进程继续执行,不会被阻塞,内核会在所有操作完成之后向应用进程发送信号。 应用程执行 aio_read 系统调用会立即返回,应用进程可以继续执行,不会被阻塞,内核会在所有操作完成之后向应用进程发送信号。
异步 I/O 与信号驱动 I/O 的区别在于,异步 I/O 的信号是通知应用进程 I/O 完成,而信号驱动 I/O 的信号是通知应用进程可以开始 I/O。 异步 I/O 与信号驱动 I/O 的区别在于,异步 I/O 的信号是通知应用进程 I/O 完成,而信号驱动 I/O 的信号是通知应用进程可以开始 I/O。
<div align="center"> <img src="../pics//1492930243286_8.png"/> </div><br> <div align="center"> <img src="../pics//1492930243286_8.png"/> </div><br>
## 同步 I/O 与异步 I/O
- 同步 I/O应用进程在调用 recvfrom 操作时会阻塞。
- 异步 I/O不会阻塞。
阻塞式 I/O、非阻塞式 I/O、I/O 复用和信号驱动 I/O 都是同步 I/O虽然非阻塞式 I/O 和信号驱动 I/O 在等待数据阶段不会阻塞,但是在之后的将数据从内核复制到应用进程这个操作会阻塞。
## 五大 I/O 模型比较 ## 五大 I/O 模型比较
前四种 I/O 模型的主要区别在于第一个阶段,而第二个阶段是一样的:将数据从内核复制到应用进程过程中,应用进程会被阻塞。 - 同步 I/O将数据从内核缓冲区复制到应用进程缓冲区的阶段应用进程会阻塞。
- 异步 I/O不会阻塞。
阻塞式 I/O、非阻塞式 I/O、I/O 复用和信号驱动 I/O 都是同步 I/O它们的主要区别在第一个阶段。
非阻塞式 I/O 、信号驱动 I/O 和异步 I/O 在第一阶段不会阻塞。
<div align="center"> <img src="../pics//1492928105791_3.png"/> </div><br> <div align="center"> <img src="../pics//1492928105791_3.png"/> </div><br>
@ -198,7 +195,7 @@ else
select 和 poll 的功能基本相同,不过在一些实现细节上有所不同。 select 和 poll 的功能基本相同,不过在一些实现细节上有所不同。
- select 会修改描述符,而 poll 不会; - select 会修改描述符,而 poll 不会;
- select 的描述符类型使用数组实现FD_SETSIZE 大小默认为 1024因此默认只能监听 1024 个描述符。如果要监听更多描述符的话,需要修改 FD_SETSIZE 之后重新编译;而 poll 的描述符类型使用链表实现,没有描述符数量的限制; - select 的描述符类型使用数组实现FD_SETSIZE 大小默认为 1024因此默认只能监听 1024 个描述符。如果要监听更多描述符的话,需要修改 FD_SETSIZE 之后重新编译;而 poll 的描述符类型使用链表实现,没有描述符数量的限制;
- poll 提供了更多的事件类型,并且对描述符的重复利用上比 select 高。 - poll 提供了更多的事件类型,并且对描述符的重复利用上比 select 高。
- 如果一个线程对某个描述符调用了 select 或者 poll另一个线程关闭了该描述符会导致调用结果不确定。 - 如果一个线程对某个描述符调用了 select 或者 poll另一个线程关闭了该描述符会导致调用结果不确定。
@ -299,7 +296,7 @@ epoll 的描述符事件有两种触发模式LTlevel trigger和 ETed
### 1. select 应用场景 ### 1. select 应用场景
select 的 timeout 参数精度为 1ns而 poll 和 epoll 为 1ms因此 select 更加适用于实时要求更高的场景,比如核反应堆的控制。 select 的 timeout 参数精度为 1ns而 poll 和 epoll 为 1ms因此 select 更加适用于实时性要求比较高的场景,比如核反应堆的控制。
select 可移植性更好,几乎被所有主流平台所支持。 select 可移植性更好,几乎被所有主流平台所支持。
@ -307,13 +304,13 @@ select 可移植性更好,几乎被所有主流平台所支持。
poll 没有最大描述符数量的限制,如果平台支持并且对实时性要求不高,应该使用 poll 而不是 select。 poll 没有最大描述符数量的限制,如果平台支持并且对实时性要求不高,应该使用 poll 而不是 select。
需要同时监控小于 1000 个描述符,就没有必要使用 epoll因为这个应用场景下并不能体现 epoll 的优势。
需要监控的描述符状态变化多,而且都是非常短暂的,也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用频繁系统调用降低效率。并且epoll 的描述符存储在内核,不容易调试。
### 3. epoll 应用场景 ### 3. epoll 应用场景
只需要运行在 Linux 平台上,并且有非常大量的描述符需要同时轮询,而且这些连接最好是长连接。 只需要运行在 Linux 平台上,有大量的描述符需要同时轮询,并且这些连接最好是长连接。
需要同时监控小于 1000 个描述符,就没有必要使用 epoll因为这个应用场景下并不能体现 epoll 的优势。
需要监控的描述符状态变化多,而且都是非常短暂的,也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用,频繁系统调用降低效率。并且 epoll 的描述符存储在内核,不容易调试。
# 参考资料 # 参考资料

View File

@ -19,31 +19,32 @@
* [五、Paxos](#五paxos) * [五、Paxos](#五paxos)
* [执行过程](#执行过程) * [执行过程](#执行过程)
* [约束条件](#约束条件) * [约束条件](#约束条件)
* [五、Raft](#五raft) * [六、Raft](#六raft)
* [单个 Candidate 的竞选](#单个-candidate-的竞选) * [单个 Candidate 的竞选](#单个-candidate-的竞选)
* [多个 Candidate 竞选](#多个-candidate-竞选) * [多个 Candidate 竞选](#多个-candidate-竞选)
* [日志复制](#日志复制) * [数据同步](#数据同步)
* [参考](#参考)
<!-- GFM-TOC --> <!-- GFM-TOC -->
# 一、分布式锁 # 一、分布式锁
在单机场景下,可以使用 Java 提供的内置锁来实现进程同步。但是在分布式场景下,需要同步的进程可能位于不同的节点上,那么就需要使用分布式锁。 在单机场景下,可以使用语言的内置锁来实现进程同步。但是在分布式场景下,需要同步的进程可能位于不同的节点上,那么就需要使用分布式锁。
阻塞锁通常使用互斥量来实现: 阻塞锁通常使用互斥量来实现:
- 互斥量为 1 表示有其它进程在使用锁,此时处于锁定状态; - 互斥量为 0 表示有其它进程在使用锁,此时处于锁定状态;
- 互斥量为 0 表示未锁定状态。 - 互斥量为 1 表示未锁定状态。
1 和 0 可以用一个整型值表示,也可以用某个数据存在或者不存在表示,存在表示互斥量为 1 1 和 0 可以用一个整型值表示,也可以用某个数据是否存在表示
## 数据库的唯一索引 ## 数据库的唯一索引
当想要获得锁时,就向表中插入一条记录,释放锁时删除这条记录。唯一索引可以保证该记录只被插入一次,那么就可以用这个记录是否存在来判断是否存于锁定状态。 获得锁时向表中插入一条记录,释放锁时删除这条记录。唯一索引可以保证该记录只被插入一次,那么就可以用这个记录是否存在来判断是否存于锁定状态。
存在以下几个问题: 存在以下几个问题:
- 锁没有失效时间,解锁失败的话其它进程无法再获得锁。 - 锁没有失效时间,解锁失败的话其它进程无法再获得锁。
- 只能是非阻塞锁,插入失败直接就报错了,无法重试。 - 只能是非阻塞锁,插入失败直接就报错了,无法重试。
- 不可重入,已经获得锁的进程也必须重新获取锁。 - 不可重入,已经获得锁的进程也必须重新获取锁。
@ -59,7 +60,7 @@ EXPIRE 指令可以为一个键值对设置一个过期时间,从而避免了
使用了多个 Redis 实例来实现分布式锁,这是为了保证在发生单点故障时仍然可用。 使用了多个 Redis 实例来实现分布式锁,这是为了保证在发生单点故障时仍然可用。
- 尝试从 N 个相互独立 Redis 实例获取锁,如果一个实例不可用,应该尽快尝试下一个 - 尝试从 N 个相互独立 Redis 实例获取锁;
- 计算获取锁消耗的时间只有当这个时间小于锁的过期时间并且从大多数N / 2 + 1实例上获取了锁那么就认为锁获取成功了 - 计算获取锁消耗的时间只有当这个时间小于锁的过期时间并且从大多数N / 2 + 1实例上获取了锁那么就认为锁获取成功了
- 如果锁获取失败,就到每个实例上释放锁。 - 如果锁获取失败,就到每个实例上释放锁。
@ -67,7 +68,7 @@ EXPIRE 指令可以为一个键值对设置一个过期时间,从而避免了
### 1. Zookeeper 抽象模型 ### 1. Zookeeper 抽象模型
Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示它的父节点为 /app1。 Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点的父节点为 /app1。
<div align="center"> <img src="../pics//31d99967-1171-448e-8531-bccf5c14cffe.jpg" width="400"/> </div><br> <div align="center"> <img src="../pics//31d99967-1171-448e-8531-bccf5c14cffe.jpg" width="400"/> </div><br>
@ -90,27 +91,21 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示
### 5. 会话超时 ### 5. 会话超时
如果一个已经获得锁的会话超时了因为创建的是临时节点所以该会话对应的临时节点会被删除其它会话就可以获得锁了。可以看到Zookeeper 分布式锁不会出现数据库的唯一索引实现分布式锁释放锁失败问题。 如果一个已经获得锁的会话超时了因为创建的是临时节点所以该会话对应的临时节点会被删除其它会话就可以获得锁了。可以看到Zookeeper 分布式锁不会出现数据库的唯一索引实现分布式锁释放锁失败问题。
### 6. 羊群效应 ### 6. 羊群效应
一个节点未获得锁,需要监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知(羊群效应),而我们只希望它的后一个子节点收到通知。 一个节点未获得锁,只需要监听自己的前一个子节点,这是因为如果监听所有的子节点,那么任意一个子节点状态改变,其它所有子节点都会收到通知(羊群效应),而我们只希望它的后一个子节点收到通知。
参考:
- [Distributed locks with Redis](https://redis.io/topics/distlock)
- [浅谈分布式锁](http://www.linkedkeeper.com/detail/blog.action?bid=1023)
- [基于 Zookeeper 的分布式锁](http://www.dengshenyu.com/java/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/2017/10/23/zookeeper-distributed-lock.html)
# 二、分布式事务 # 二、分布式事务
指事务的操作位于不同的节点上,需要保证事务的 AICD 特性。例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。 指事务的操作位于不同的节点上,需要保证事务的 ACID 特性。
例如在下单场景下,库存和订单如果不在同一个节点上,就涉及分布式事务。
## 本地消息表 ## 本地消息表
### 1. 原理 本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性。
本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性。
1. 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。 1. 在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
2. 之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。 2. 之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。
@ -118,23 +113,19 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示
<div align="center"> <img src="../pics//e3bf5de4-ab1e-4a9b-896d-4b0ad7e9220a.jpg"/> </div><br> <div align="center"> <img src="../pics//e3bf5de4-ab1e-4a9b-896d-4b0ad7e9220a.jpg"/> </div><br>
### 2. 分析
本地消息表利用了本地事务来实现分布式事务,并且使用了消息队列来保证最终一致性。
## 2PC ## 2PC
两阶段提交Two-phase Commit2PC通过引入协调者Coordinator来协调参与者的行为并最终决定这些参与者是否要真正执行事务。 两阶段提交Two-phase Commit2PC通过引入协调者Coordinator来协调参与者的行为并最终决定这些参与者是否要真正执行事务。
### 1. 运行过程 ### 1. 运行过程
(一)准备阶段 #### 1.1 准备阶段
协调者询问参与者事务是否执行成功,参与者发回事务执行结果。 协调者询问参与者事务是否执行成功,参与者发回事务执行结果。
<div align="center"> <img src="../pics//04f41228-375d-4b7d-bfef-738c5a7c8f07.jpg"/> </div><br> <div align="center"> <img src="../pics//04f41228-375d-4b7d-bfef-738c5a7c8f07.jpg"/> </div><br>
(二)提交阶段 #### 1.2 提交阶段
如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。 如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
@ -144,28 +135,22 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示
### 2. 存在的问题 ### 2. 存在的问题
(一)同步阻塞 #### 2.1 同步阻塞
所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。 所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。
(二)单点问题 #### 2.2 单点问题
协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。 协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待,无法完成其它操作。
(三)数据不一致 #### 2.3 数据不一致
在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。 在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
(四)太过保守 #### 2.4 太过保守
任意一个节点失败就会导致整个事务失败,没有完善的容错机制。 任意一个节点失败就会导致整个事务失败,没有完善的容错机制。
参考:
- [聊聊分布式事务,再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html)
- [分布式系统的事务处理](https://coolshell.cn/articles/10910.html)
- [深入理解分布式事务](https://juejin.im/entry/577c6f220a2b5800573492be)
# 三、CAP # 三、CAP
分布式系统不可能同时满足一致性CConsistency、可用性AAvailability和分区容忍性PPartition Tolerance最多只能同时满足其中两项。 分布式系统不可能同时满足一致性CConsistency、可用性AAvailability和分区容忍性PPartition Tolerance最多只能同时满足其中两项。
@ -174,9 +159,7 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示
## 一致性 ## 一致性
一致性指的是多个数据副本是否能保持一致的特性。 一致性指的是多个数据副本是否能保持一致的特性,在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态。
在一致性的条件下,系统在执行数据更新操作之后能够从一致性状态转移到另一个一致性状态。
对系统的一个数据更新成功之后,如果所有用户都能够读取到最新的值,该系统就被认为具有强一致性。 对系统的一个数据更新成功之后,如果所有用户都能够读取到最新的值,该系统就被认为具有强一致性。
@ -184,7 +167,7 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示
可用性指分布式系统在面对各种异常时可以提供正常服务的能力可以用系统可用时间占总时间的比值来衡量4 个 9 的可用性表示系统 99.99% 的时间是可用的。 可用性指分布式系统在面对各种异常时可以提供正常服务的能力可以用系统可用时间占总时间的比值来衡量4 个 9 的可用性表示系统 99.99% 的时间是可用的。
在可用性条件下,系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。 在可用性条件下,要求系统提供的服务一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
## 分区容忍性 ## 分区容忍性
@ -194,20 +177,15 @@ Zookeeper 提供了一种树形结构级的命名空间,/app1/p_1 节点表示
## 权衡 ## 权衡
在分布式系统中分区容忍性必不可少因为需要总是假设网络是不可靠的。因此CAP 理论实际是要在可用性和一致性之间做权衡。 在分布式系统中分区容忍性必不可少因为需要总是假设网络是不可靠的。因此CAP 理论实际是要在可用性和一致性之间做权衡。
可用性和一致性往往是冲突的,很难使它们同时满足。在多个节点之间进行数据同步时, 可用性和一致性往往是冲突的,很难使它们同时满足。在多个节点之间进行数据同步时,
* 为了保证一致性CP就需要让所有节点下线成为不可用的状态等待同步完成 - 为了保证一致性CP就需要让所有节点下线成为不可用的状态等待同步完成
* 为了保证可用性AP在同步过程中允许读取所有节点的数据但是数据可能不一致。 - 为了保证可用性AP在同步过程中允许读取所有节点的数据但是数据可能不一致。
<div align="center"> <img src="../pics//0b587744-c0a8-46f2-8d72-e8f070d67b4b.jpg"/> </div><br> <div align="center"> <img src="../pics//0b587744-c0a8-46f2-8d72-e8f070d67b4b.jpg"/> </div><br>
参考:
- 倪超. 从 Paxos 到 ZooKeeper : 分布式一致性原理与实践 [M]. 电子工业出版社, 2015.
- [What is CAP theorem in distributed database system?](http://www.colooshiki.com/index.php/2017/04/20/what-is-cap-theorem-in-distributed-database-system/)
# 四、BASE # 四、BASE
BASE 是基本可用Basically Available、软状态Soft State和最终一致性Eventually Consistent三个短语的缩写。 BASE 是基本可用Basically Available、软状态Soft State和最终一致性Eventually Consistent三个短语的缩写。
@ -224,7 +202,7 @@ BASE 理论是对 CAP 中一致性和可用性权衡的结果,它的核心思
## 软状态 ## 软状态
指允许系统中的数据存在中间状态,并认为该中间状态不会影响系统整体可用性,即允许系统不同节点的数据副本之间进行同步的过程存在时。 指允许系统中的数据存在中间状态,并认为该中间状态不会影响系统整体可用性,即允许系统不同节点的数据副本之间进行同步的过程存在时
## 最终一致性 ## 最终一致性
@ -292,66 +270,69 @@ Acceptor 接收到接受请求时,如果序号大于等于该 Acceptor 承诺
Paxos 协议能够让 Proposer 发送的提议朝着能被大多数 Acceptor 接受的那个提议靠拢,因此能够保证可终止性。 Paxos 协议能够让 Proposer 发送的提议朝着能被大多数 Acceptor 接受的那个提议靠拢,因此能够保证可终止性。
参考: # 六、Raft
- [NEAT ALGORITHMS - PAXOS](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/) Raft 也是分布式一致性协议,主要是用来竞选主节点。
- [Paxos By Example](https://angus.nyc/2012/paxos-by-example/)
# 五、Raft
Raft 和 Paxos 类似,但是更容易理解,也更容易实现。
Raft 主要是用来竞选主节点。
## 单个 Candidate 的竞选 ## 单个 Candidate 的竞选
有三种节点Follower、Candidate 和 Leader。Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间,一般为 150ms\~300ms如果在这个时间内没有收到 Leader 的心跳包,就会变成 Candidate进入竞选阶段。 有三种节点Follower、Candidate 和 Leader。Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了一个随机的竞选超时时间,一般为 150ms\~300ms如果在这个时间内没有收到 Leader 的心跳包,就会变成 Candidate进入竞选阶段。
* 下图表示一个分布式系统的最初阶段,此时只有 Follower没有 Leader。Follower A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。 - 下图展示一个分布式系统的最初阶段,此时只有 Follower 没有 Leader。Node A 等待一个随机的竞选超时时间之后,没收到 Leader 发来的心跳包,因此进入竞选阶段。
<div align="center"> <img src="../pics//111521118015898.gif"/> </div><br> <div align="center"> <img src="../pics//111521118015898.gif"/> </div><br>
* 此时 A 发送投票请求给其它所有节点。 - 此时 Node A 发送投票请求给其它所有节点。
<div align="center"> <img src="../pics//111521118445538.gif"/> </div><br> <div align="center"> <img src="../pics//111521118445538.gif"/> </div><br>
* 其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。 - 其它节点会对请求进行回复,如果超过一半的节点回复了,那么该 Candidate 就会变成 Leader。
<div align="center"> <img src="../pics//111521118483039.gif"/> </div><br> <div align="center"> <img src="../pics//111521118483039.gif"/> </div><br>
* 之后 Leader 会周期性地发送心跳包给 FollowerFollower 接收到心跳包,会重新开始计时。 - 之后 Leader 会周期性地发送心跳包给 FollowerFollower 接收到心跳包,会重新开始计时。
<div align="center"> <img src="../pics//111521118640738.gif"/> </div><br> <div align="center"> <img src="../pics//111521118640738.gif"/> </div><br>
## 多个 Candidate 竞选 ## 多个 Candidate 竞选
* 如果有多个 Follower 成为 Candidate并且所获得票数相同那么就需要重新开始投票,例如下图中 Candidate B 和 Candidate D 都获得两票,因此需要重新开始投票。 - 如果有多个 Follower 成为 Candidate并且所获得票数相同那么就需要重新开始投票。例如下图中 Node B 和 Node D 都获得两票,需要重新开始投票。
<div align="center"> <img src="../pics//111521119203347.gif"/> </div><br> <div align="center"> <img src="../pics//111521119203347.gif"/> </div><br>
* 当重新开始投票时,由于每个节点设置的随机竞选超时时间不同,因此下一次再次出现多个 Candidate 并获得同样票数的概率很低。 - 由于每个节点设置的随机竞选超时时间不同,因此下一次再次出现多个 Candidate 并获得同样票数的概率很低。
<div align="center"> <img src="../pics//111521119368714.gif"/> </div><br> <div align="center"> <img src="../pics//111521119368714.gif"/> </div><br>
## 日志复制 ## 数据同步
* 来自客户端的修改都会被传入 Leader。注意该修改还未被提交只是写入日志中。 - 来自客户端的修改都会被传入 Leader。注意该修改还未被提交只是写入日志中。
<div align="center"> <img src="../pics//7.gif"/> </div><br> <div align="center"> <img src="../pics//7.gif"/> </div><br>
* Leader 会把修改复制到所有 Follower。 - Leader 会把修改复制到所有 Follower。
<div align="center"> <img src="../pics//9.gif"/> </div><br> <div align="center"> <img src="../pics//9.gif"/> </div><br>
* Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。 - Leader 会等待大多数的 Follower 也进行了修改,然后才将修改提交。
<div align="center"> <img src="../pics//10.gif"/> </div><br> <div align="center"> <img src="../pics//10.gif"/> </div><br>
* 此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。 - 此时 Leader 会通知的所有 Follower 让它们也提交修改,此时所有节点的值达成一致。
<div align="center"> <img src="../pics//11.gif"/> </div><br> <div align="center"> <img src="../pics//11.gif"/> </div><br>
参考 # 参考
- 倪超. 从 Paxos 到 ZooKeeper : 分布式一致性原理与实践 [M]. 电子工业出版社, 2015.
- [Distributed locks with Redis](https://redis.io/topics/distlock)
- [浅谈分布式锁](http://www.linkedkeeper.com/detail/blog.action?bid=1023)
- [基于 Zookeeper 的分布式锁](http://www.dengshenyu.com/java/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F/2017/10/23/zookeeper-distributed-lock.html)
- [Raft: Understandable Distributed Consensus](http://thesecretlivesofdata.com/raft) - [Raft: Understandable Distributed Consensus](http://thesecretlivesofdata.com/raft)
- [聊聊分布式事务,再说说解决方案](https://www.cnblogs.com/savorboard/p/distributed-system-transaction-consistency.html)
- [分布式系统的事务处理](https://coolshell.cn/articles/10910.html)
- [深入理解分布式事务](https://juejin.im/entry/577c6f220a2b5800573492be)
- [What is CAP theorem in distributed database system?](http://www.colooshiki.com/index.php/2017/04/20/what-is-cap-theorem-in-distributed-database-system/)
- [NEAT ALGORITHMS - PAXOS](http://harry.me/blog/2014/12/27/neat-algorithms-paxos/)
- [Paxos By Example](https://angus.nyc/2012/paxos-by-example/)

View File

@ -223,14 +223,14 @@ Output:
在字符串尾部填充任意字符,使得字符串的长度等于替换之后的长度。因为一个空格要替换成三个字符(%20因此当遍历到一个空格时需要在尾部填充两个任意字符。 在字符串尾部填充任意字符,使得字符串的长度等于替换之后的长度。因为一个空格要替换成三个字符(%20因此当遍历到一个空格时需要在尾部填充两个任意字符。
令 P1 指向字符串原来的末尾位置P2 指向字符串现在的末尾位置。P1 和 P2从后向前遍历当 P1 遍历到一个空格时,就需要令 P2 指向的位置依次填充 02%(注意是逆序的),否则就填充上 P1 指向字符的值。 令 P1 指向字符串原来的末尾位置P2 指向字符串现在的末尾位置。P1 和 P2 从后向前遍历,当 P1 遍历到一个空格时,就需要令 P2 指向的位置依次填充 02%(注意是逆序的),否则就填充上 P1 指向字符的值。
从后向前遍是为了在改变 P2 所指向的内容时,不会影响到 P1 遍历原来字符串的内容。 从后向前遍是为了在改变 P2 所指向的内容时,不会影响到 P1 遍历原来字符串的内容。
```java ```java
public String replaceSpace(StringBuffer str) { public String replaceSpace(StringBuffer str) {
int P1 = str.length() - 1; int P1 = str.length() - 1;
for (int i = 0; i < str.length(); i++) for (int i = 0; i <= P1; i++)
if (str.charAt(i) == ' ') if (str.charAt(i) == ' ')
str.append(" "); str.append(" ");
@ -385,6 +385,7 @@ private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int inL) {
```java ```java
public class TreeLinkNode { public class TreeLinkNode {
int val; int val;
TreeLinkNode left = null; TreeLinkNode left = null;
TreeLinkNode right = null; TreeLinkNode right = null;
@ -510,6 +511,7 @@ public int Fibonacci(int n) {
```java ```java
public class Solution { public class Solution {
private int[] fib = new int[40]; private int[] fib = new int[40];
public Solution() { public Solution() {
@ -956,7 +958,7 @@ private void printNumber(char[] number) {
```java ```java
public ListNode deleteNode(ListNode head, ListNode tobeDelete) { public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
if (head == null || head.next == null || tobeDelete == null) if (head == null || tobeDelete == null)
return null; return null;
if (tobeDelete.next != null) { if (tobeDelete.next != null) {
// 要删除的节点不是尾节点 // 要删除的节点不是尾节点
@ -1082,7 +1084,7 @@ false
```java ```java
public boolean isNumeric(char[] str) { public boolean isNumeric(char[] str) {
if (str == null) if (str == null || str.length == 0)
return false; return false;
return new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?"); return new String(str).matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?");
} }
@ -1154,11 +1156,11 @@ public ListNode FindKthToTail(ListNode head, int k) {
## 解题思路 ## 解题思路
使用双指针,一个指针 fast 每次移动两个节点,一个指针 slow 每次移动一个节点。因为存在环,所以两个指针必定相遇在环中的某个节点上。假设相遇点在下图的 z1 位置,此时 fast 移动的节点数为 x+2y+zslow 为 x+y由于 fast 速度比 slow 快一倍,因此 x+2y+z=2(x+y),得到 x=z。 使用双指针,一个指针 fast 每次移动两个节点,一个指针 slow 每次移动一个节点。因为存在环,所以两个指针必定相遇在环中的某个节点上。假设相遇点在下图的 y6 位置,此时 fast 移动的节点数为 x+2y+zslow 为 x+y由于 fast 速度比 slow 快一倍,因此 x+2y+z=2(x+y),得到 x=z。
在相遇点slow 要到环的入口点还需要移动 z 个节点,如果让 fast 重新从头开始移动,并且速度变为每次移动一个节点,那么它到环入口点还需要移动 x 个节点。在上面已经推导出 x=z因此 fast 和 slow 将在环入口点相遇。 在相遇点slow 要到环的入口点还需要移动 z 个节点,如果让 fast 重新从头开始移动,并且速度变为每次移动一个节点,那么它到环入口点还需要移动 x 个节点。在上面已经推导出 x=z因此 fast 和 slow 将在环入口点相遇。
<div align="center"> <img src="../pics//2858f8ad-aedb-45a5-a706-e98c96d690fa.jpg" width="600"/> </div><br> <div align="center"> <img src="../pics//70fa1f83-dae7-456d-b94b-ce28963b2ba1.png"/> </div><br>
```java ```java
public ListNode EntryNodeOfLoop(ListNode pHead) { public ListNode EntryNodeOfLoop(ListNode pHead) {
@ -1433,7 +1435,8 @@ public boolean IsPopOrder(int[] pushSequence, int[] popSequence) {
Stack<Integer> stack = new Stack<>(); Stack<Integer> stack = new Stack<>();
for (int pushIndex = 0, popIndex = 0; pushIndex < n; pushIndex++) { for (int pushIndex = 0, popIndex = 0; pushIndex < n; pushIndex++) {
stack.push(pushSequence[pushIndex]); stack.push(pushSequence[pushIndex]);
while (popIndex < n && stack.peek() == popSequence[popIndex]) { while (popIndex < n && !stack.isEmpty()
&& stack.peek() == popSequence[popIndex]) {
stack.pop(); stack.pop();
popIndex++; popIndex++;
} }
@ -1558,7 +1561,7 @@ public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。假设输入的数组的任意两个数字都互不相同。 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。假设输入的数组的任意两个数字都互不相同。
例如,下图是后序遍历序列 3,1,2 所对应的二叉搜索树。 例如,下图是后序遍历序列 1,3,2 所对应的二叉搜索树。
<div align="center"> <img src="../pics//836a4eaf-4798-4e48-b52a-a3dab9435ace.png" width="150"/> </div><br> <div align="center"> <img src="../pics//836a4eaf-4798-4e48-b52a-a3dab9435ace.png" width="150"/> </div><br>
@ -1578,7 +1581,7 @@ private boolean verify(int[] sequence, int first, int last) {
int cutIndex = first; int cutIndex = first;
while (cutIndex < last && sequence[cutIndex] <= rootVal) while (cutIndex < last && sequence[cutIndex] <= rootVal)
cutIndex++; cutIndex++;
for (int i = cutIndex + 1; i < last; i++) for (int i = cutIndex; i < last; i++)
if (sequence[i] < rootVal) if (sequence[i] < rootVal)
return false; return false;
return verify(sequence, first, cutIndex - 1) && verify(sequence, cutIndex, last - 1); return verify(sequence, first, cutIndex - 1) && verify(sequence, cutIndex, last - 1);
@ -2244,7 +2247,7 @@ public int GetUglyNumber_Solution(int N) {
## 题目描述 ## 题目描述
在一个字符串 中找到第一个只出现一次的字符,并返回它的位置。 在一个字符串中找到第一个只出现一次的字符,并返回它的位置。
## 解题思路 ## 解题思路
@ -2687,8 +2690,8 @@ public ArrayList<Integer> maxInWindows(int[] num, int size) {
for (int i = 0; i < size; i++) for (int i = 0; i < size; i++)
heap.add(num[i]); heap.add(num[i]);
ret.add(heap.peek()); ret.add(heap.peek());
for (int i = 1, j = i + size - 1; j < num.length; i++, j++) { /* 维护一个大小为 size 的大顶堆 */ for (int i = 0, j = i + size; j < num.length; i++, j++) { /* 维护一个大小为 size 的大顶堆 */
heap.remove(num[i - 1]); heap.remove(num[i]);
heap.add(num[j]); heap.add(num[j]);
ret.add(heap.peek()); ret.add(heap.peek());
} }
@ -2779,19 +2782,25 @@ public List<Map.Entry<Integer, Double>> dicesSum(int n) {
```java ```java
public boolean isContinuous(int[] nums) { public boolean isContinuous(int[] nums) {
if (nums.length < 5) if (nums.length < 5)
return false; return false;
Arrays.sort(nums); Arrays.sort(nums);
// 统计癞子数量
int cnt = 0; int cnt = 0;
for (int num : nums) /* 统计癞子数量 */ for (int num : nums)
if (num == 0) if (num == 0)
cnt++; cnt++;
// 使用癞子去补全不连续的顺子
for (int i = cnt; i < nums.length - 1; i++) { for (int i = cnt; i < nums.length - 1; i++) {
if (nums[i + 1] == nums[i]) if (nums[i + 1] == nums[i])
return false; return false;
cnt -= nums[i + 1] - nums[i] - 1; /* 使用癞子去补全不连续的顺子 */ cnt -= nums[i + 1] - nums[i] - 1;
} }
return cnt >= 0; return cnt >= 0;
} }
``` ```
@ -2824,7 +2833,7 @@ public int LastRemaining_Solution(int n, int m) {
## 题目描述 ## 题目描述
可以有一次买入和一次卖出,买入必须在前。求最大收益。 可以有一次买入和一次卖出,那么买入必须在前。求最大收益。
## 解题思路 ## 解题思路

View File

@ -13,6 +13,8 @@
跨站脚本攻击Cross-Site Scripting, XSS可以将代码注入到用户浏览的网页上这种代码包括 HTML 和 JavaScript。 跨站脚本攻击Cross-Site Scripting, XSS可以将代码注入到用户浏览的网页上这种代码包括 HTML 和 JavaScript。
## 攻击原理
例如有一个论坛网站,攻击者可以在上面发布以下内容: 例如有一个论坛网站,攻击者可以在上面发布以下内容:
```html ```html
@ -43,40 +45,23 @@
例如将 `<` 转义为 `&lt;`,将 `>` 转义为 `&gt;`,从而避免 HTML 和 Jascript 代码的运行。 例如将 `<` 转义为 `&lt;`,将 `>` 转义为 `&gt;`,从而避免 HTML 和 Jascript 代码的运行。
## 富文本编辑器
富文本编辑器允许用户输入 HTML 代码,就不能简单地将 `<` 等字符进行过滤了,极大地提高了 XSS 攻击的可能性。 富文本编辑器允许用户输入 HTML 代码,就不能简单地将 `<` 等字符进行过滤了,极大地提高了 XSS 攻击的可能性。
富文本编辑器通常采用 XSS filter 来防范 XSS 攻击,通过定义一些标签白名单或者黑名单,从而不允许有攻击性的 HTML 代码的输入。 富文本编辑器通常采用 XSS filter 来防范 XSS 攻击,通过定义一些标签白名单或者黑名单,从而不允许有攻击性的 HTML 代码的输入。
以下例子中form 和 script 等标签都被转义,而 h 和 p 等标签将会保留。 以下例子中form 和 script 等标签都被转义,而 h 和 p 等标签将会保留。
> [XSS 过滤在线测试](http://jsxss.com/zh/try.html)
```html ```html
<h1 id="title">XSS Demo</h1> <h1 id="title">XSS Demo</h1>
<p class="text-center"> <p>123</p>
Sanitize untrusted HTML (to prevent XSS) with a configuration specified by a Whitelist.
</p>
<form> <form>
<input type="text" name="q" value="test"> <input type="text" name="q" value="test">
<button id="submit">Submit</button>
</form> </form>
<pre>hello</pre> <pre>hello</pre>
<p>
<a href="http://jsxss.com">http</a>
</p>
<h3>Features:</h3>
<ul>
<li>Specifies HTML tags and their attributes allowed with whitelist</li>
<li>Handle any tags or attributes using custom function</li>
</ul>
<script type="text/javascript"> <script type="text/javascript">
alert(/xss/); alert(/xss/);
</script> </script>
@ -85,32 +70,21 @@ alert(/xss/);
```html ```html
<h1>XSS Demo</h1> <h1>XSS Demo</h1>
<p> <p>123</p>
Sanitize untrusted HTML (to prevent XSS) with a configuration specified by a Whitelist.
</p>
&lt;form&gt; &lt;form&gt;
&lt;input type="text" name="q" value="test"&gt; &lt;input type="text" name="q" value="test"&gt;
&lt;button id="submit"&gt;Submit&lt;/button&gt;
&lt;/form&gt; &lt;/form&gt;
<pre>hello</pre> <pre>hello</pre>
<p>
<a href="http://jsxss.com">http</a>
</p>
<h3>Features:</h3>
<ul>
<li>Specifies HTML tags and their attributes allowed with whitelist</li>
<li>Handle any tags or attributes using custom function</li>
</ul>
&lt;script type="text/javascript"&gt; &lt;script type="text/javascript"&gt;
alert(/xss/); alert(/xss/);
&lt;/script&gt; &lt;/script&gt;
``` ```
> [XSS 过滤在线测试](http://jsxss.com/zh/try.html)
# 二、跨站请求伪造 # 二、跨站请求伪造
## 概念 ## 概念
@ -119,6 +93,8 @@ alert(/xss/);
XSS 利用的是用户对指定网站的信任CSRF 利用的是网站对用户浏览器的信任。 XSS 利用的是用户对指定网站的信任CSRF 利用的是网站对用户浏览器的信任。
## 攻击原理
假如一家银行用以执行转账操作的 URL 地址如下: 假如一家银行用以执行转账操作的 URL 地址如下:
``` ```
@ -135,7 +111,7 @@ http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务器端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。 这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务器端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险。
过例子能够看出,攻击者并不能通过 CSRF 攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义执行操作。 过例子能够看出,攻击者并不能通过 CSRF 攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义执行操作。
## 防范手段 ## 防范手段
@ -207,7 +183,7 @@ ResultSet rs = stmt.executeQuery();
拒绝服务攻击denial-of-service attackDoS亦称洪水攻击其目的在于使目标电脑的网络或系统资源耗尽使服务暂时中断或停止导致其正常用户无法访问。 拒绝服务攻击denial-of-service attackDoS亦称洪水攻击其目的在于使目标电脑的网络或系统资源耗尽使服务暂时中断或停止导致其正常用户无法访问。
分布式拒绝服务攻击distributed denial-of-service attackDDoS指攻击者使用网络上两个或以上被攻陷的电脑作为“僵尸”向特定的目标发动“拒绝服务”式攻击。 分布式拒绝服务攻击distributed denial-of-service attackDDoS指攻击者使用两个或以上被攻陷的电脑作为“僵尸”向特定的目标发动“拒绝服务”式攻击。
# 参考资料 # 参考资料

View File

@ -55,7 +55,7 @@
事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。 事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。
回滚可以用日志来实现,日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。 回滚可以用回滚日志来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。
### 2. 一致性Consistency ### 2. 一致性Consistency
@ -69,7 +69,7 @@
一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。 一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。
可以通过数据库备份和恢复来实现,在系统发生崩溃时,使用备份的数据库进行数据恢复 使用重做日志来保证持久性
---- ----
@ -80,7 +80,7 @@
- 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。 - 在并发的情况下,多个事务并行执行,事务不仅要满足原子性,还需要满足隔离性,才能满足一致性。
- 事务满足持久化是为了能应对数据库崩溃的情况。 - 事务满足持久化是为了能应对数据库崩溃的情况。
<div align="center"> <img src="../pics//a58e294a-615d-4ea0-9fbf-064a6daec4b2.png" width="450"/> </div><br> <div align="center"> <img src="../pics//6675d713-8b59-4067-ad16-fdd538d4bb43.png" width="500"/> </div><br>
## AUTOCOMMIT ## AUTOCOMMIT
@ -294,12 +294,12 @@ SELECT ... FOR UPDATE;
---- ----
| 隔离级别 | 脏读 | 不可重复读 | 幻影读 | | 隔离级别 | 脏读 | 不可重复读 | 幻影读 | 加锁读 |
| :---: | :---: | :---:| :---: | | :---: | :---: | :---:| :---: | :---: |
| 未提交读 | √ | √ | √ | | 未提交读 | √ | √ | √ | × |
| 提交读 | × | √ | √ | | 提交读 | × | √ | √ | × |
| 可重复读 | × | × | √ | | 可重复读 | × | × | √ | × |
| 可串行化 | × | × | × | | 可串行化 | × | × | × | √ |
# 五、多版本并发控制 # 五、多版本并发控制

View File

@ -1,11 +1,12 @@
<!-- GFM-TOC --> <!-- GFM-TOC -->
* [一、什么是构建工具](#一什么是构建工具) * [一、构建工具的作用](#一构建工具的作用)
* [二、Java 主流构建工具](#二java-主流构建工具) * [二、Java 主流构建工具](#二java-主流构建工具)
* [三、Maven](#三maven) * [三、Maven](#三maven)
* [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
# 一、什么是构建工具 # 一、构建工具的作用
构建工具是用于构建项目的自动化工具,主要包含以下工作: 构建工具是用于构建项目的自动化工具,主要包含以下工作:
@ -15,7 +16,7 @@
## 运行单元测试 ## 运行单元测试
不再需要在项目代码中添加测试代码,从而污染项目代码。 不再需要在项目代码中添加测试代码,从而避免了污染项目代码。
## 将源代码转化为可执行文件 ## 将源代码转化为可执行文件
@ -29,10 +30,6 @@
不再需要通过 FTP 将 Jar 包上传到服务器上。 不再需要通过 FTP 将 Jar 包上传到服务器上。
参考资料:
- [What is a build tool?](https://stackoverflow.com/questions/7249871/what-is-a-build-tool)
# 二、Java 主流构建工具 # 二、Java 主流构建工具
主要包括 Ant、Maven 和 Gradle。 主要包括 Ant、Maven 和 Gradle。
@ -72,12 +69,6 @@ dependencies {
} }
``` ```
参考资料:
- [Java Build Tools Comparisons: Ant vs Maven vs Gradle](https://programmingmitra.blogspot.com/2016/05/java-build-tools-comparisons-ant-vs.html)
- [maven 2 gradle](http://sagioto.github.io/maven2gradle/)
- [新一代构建工具 gradle](https://www.imooc.com/learn/833)
# 三、Maven # 三、Maven
## 概述 ## 概述
@ -114,7 +105,7 @@ POM 代表项目对象模型,它是一个 XML 文件,保存在项目根目
## 依赖原则 ## 依赖原则
### 依赖路径最短优先原则 ### 1. 依赖路径最短优先原则
```html ```html
A -> B -> C -> X(1.0) A -> B -> C -> X(1.0)
@ -122,7 +113,7 @@ A -> D -> X(2.0)
``` ```
由于 X(2.0) 路径最短,所以使用 X(2.0)。 由于 X(2.0) 路径最短,所以使用 X(2.0)。
### 声明顺序优先原则 ### 2. 声明顺序优先原则
```html ```html
A -> B -> X(1.0) A -> B -> X(1.0)
@ -131,7 +122,7 @@ A -> C -> X(2.0)
在 POM 中最先声明的优先,上面的两个依赖如果先声明 B那么最后使用 X(1.0)。 在 POM 中最先声明的优先,上面的两个依赖如果先声明 B那么最后使用 X(1.0)。
### 覆写优先原则 ### 3. 覆写优先原则
子 POM 内声明的依赖优先于父 POM 中声明的依赖。 子 POM 内声明的依赖优先于父 POM 中声明的依赖。
@ -139,9 +130,11 @@ A -> C -> X(2.0)
找到 Maven 加载的 Jar 包版本,使用 `mvn dependency:tree` 查看依赖树,根据依赖原则来调整依赖在 POM 文件的声明顺序。 找到 Maven 加载的 Jar 包版本,使用 `mvn dependency:tree` 查看依赖树,根据依赖原则来调整依赖在 POM 文件的声明顺序。
参考资料 # 参考资料
- [POM Reference](http://maven.apache.org/pom.html#Dependency_Version_Requirement_Specification) - [POM Reference](http://maven.apache.org/pom.html#Dependency_Version_Requirement_Specification)
- [What is a build tool?](https://stackoverflow.com/questions/7249871/what-is-a-build-tool)
- [Java Build Tools Comparisons: Ant vs Maven vs Gradle](https://programmingmitra.blogspot.com/2016/05/java-build-tools-comparisons-ant-vs.html)
- [maven 2 gradle](http://sagioto.github.io/maven2gradle/)
- [新一代构建工具 gradle](https://www.imooc.com/learn/833)

View File

@ -9,6 +9,7 @@
* [三、可靠性](#三可靠性) * [三、可靠性](#三可靠性)
* [发送端的可靠性](#发送端的可靠性) * [发送端的可靠性](#发送端的可靠性)
* [接收端的可靠性](#接收端的可靠性) * [接收端的可靠性](#接收端的可靠性)
* [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
@ -29,22 +30,17 @@
发布与订阅模式和观察者模式有以下不同: 发布与订阅模式和观察者模式有以下不同:
- 观察者模式中,观察者和主题都知道对方的存在;而在发布与订阅模式中,发布者与订阅者不知道对方的存在,它们之间通过频道进行通信。 - 观察者模式中,观察者和主题都知道对方的存在;而在发布与订阅模式中,发布者与订阅者不知道对方的存在,它们之间通过频道进行通信。
- 观察者模式是同步的,当事件触发时,主题会调用观察者的方法,然后等待方法返回;而发布与订阅模式是异步的,发布者向频道发送一个消息之后,就不需要关心订阅者何时去订阅这个消息。 - 观察者模式是同步的,当事件触发时,主题会调用观察者的方法,然后等待方法返回;而发布与订阅模式是异步的,发布者向频道发送一个消息之后,就不需要关心订阅者何时去订阅这个消息,可以立即返回
<div align="center"> <img src="../pics//bee1ff1d-c80f-4b3c-b58c-7073a8896ab2.jpg"/> </div><br> <div align="center"> <img src="../pics//bee1ff1d-c80f-4b3c-b58c-7073a8896ab2.jpg"/> </div><br>
参考:
- [Observer vs Pub-Sub](http://developers-club.com/posts/270339/)
- [消息队列中点对点与发布订阅区别](https://blog.csdn.net/lizhitao/article/details/47723105)
# 二、使用场景 # 二、使用场景
## 异步处理 ## 异步处理
发送者将消息发送给消息队列之后,不需要同步等待消息接收者处理完毕,而是立即返回进行其它操作。消息接收者从消息队列中订阅消息之后异步处理。 发送者将消息发送给消息队列之后,不需要同步等待消息接收者处理完毕,而是立即返回进行其它操作。消息接收者从消息队列中订阅消息之后异步处理。
例如在注册流程中通常需要发送验证邮件来确保注册用户身份合法,可以使用消息队列使发送验证邮件的操作异步处理,用户在填写完注册信息之后就可以完成注册,而将发送验证邮件这一消息发送到消息队列中。 例如在注册流程中通常需要发送验证邮件来确保注册用户身份合法,可以使用消息队列使发送验证邮件的操作异步处理,用户在填写完注册信息之后就可以完成注册,而将发送验证邮件这一消息发送到消息队列中。
只有在业务流程允许异步处理的情况下才能这么做,例如上面的注册流程中,如果要求用户对验证邮件进行点击之后才能完成注册的话,就不能再使用消息队列。 只有在业务流程允许异步处理的情况下才能这么做,例如上面的注册流程中,如果要求用户对验证邮件进行点击之后才能完成注册的话,就不能再使用消息队列。
@ -74,7 +70,12 @@
接收端能够从消息队列成功消费一次消息。 接收端能够从消息队列成功消费一次消息。
实现方法: 两种实现方法:
- 保证接收端处理消息的业务逻辑具有幂等性:只要具有幂等性,那么消费多少次消息,最后处理的结果都是一样的。 - 保证接收端处理消息的业务逻辑具有幂等性:只要具有幂等性,那么消费多少次消息,最后处理的结果都是一样的。
- 保证消息具有唯一编号,并使用一张日志表来记录已经消费的消息编号。 - 保证消息具有唯一编号,并使用一张日志表来记录已经消费的消息编号。
# 参考资料
- [Observer vs Pub-Sub](http://developers-club.com/posts/270339/)
- [消息队列中点对点与发布订阅区别](https://blog.csdn.net/lizhitao/article/details/47723105)

View File

@ -23,7 +23,7 @@
* [五、栈和队列](#五栈和队列) * [五、栈和队列](#五栈和队列)
* [](#栈) * [](#栈)
* [队列](#队列) * [队列](#队列)
* [六、查找](#六查找) * [六、符号表](#六符号表)
* [初级实现](#初级实现) * [初级实现](#初级实现)
* [二叉查找树](#二叉查找树) * [二叉查找树](#二叉查找树)
* [2-3 查找树](#2-3-查找树) * [2-3 查找树](#2-3-查找树)
@ -104,17 +104,21 @@ public class ThreeSumSlow implements ThreeSum {
public int count(int[] nums) { public int count(int[] nums) {
int N = nums.length; int N = nums.length;
int cnt = 0; int cnt = 0;
for (int i = 0; i < N; i++) for (int i = 0; i < N; i++) {
for (int j = i + 1; j < N; j++) for (int j = i + 1; j < N; j++) {
for (int k = j + 1; k < N; k++) for (int k = j + 1; k < N; k++) {
if (nums[i] + nums[j] + nums[k] == 0) if (nums[i] + nums[j] + nums[k] == 0) {
cnt++; cnt++;
}
}
}
}
return cnt; return cnt;
} }
} }
``` ```
### 2. ThreeSumFast ### 2. ThreeSumBinarySearch
通过将数组先排序,对两个元素求和,并用二分查找方法查找是否存在该和的相反数,如果存在,就说明存在三元组的和为 0。 通过将数组先排序,对两个元素求和,并用二分查找方法查找是否存在该和的相反数,如果存在,就说明存在三元组的和为 0。
@ -123,19 +127,23 @@ public class ThreeSumSlow implements ThreeSum {
该方法可以将 ThreeSum 算法增长数量级降低为 O(N<sup>2</sup>logN)。 该方法可以将 ThreeSum 算法增长数量级降低为 O(N<sup>2</sup>logN)。
```java ```java
public class ThreeSumFast { public class ThreeSumBinarySearch implements ThreeSum {
public static int count(int[] nums) {
@Override
public int count(int[] nums) {
Arrays.sort(nums); Arrays.sort(nums);
int N = nums.length; int N = nums.length;
int cnt = 0; int cnt = 0;
for (int i = 0; i < N; i++) for (int i = 0; i < N; i++) {
for (int j = i + 1; j < N; j++) { for (int j = i + 1; j < N; j++) {
int target = -nums[i] - nums[j]; int target = -nums[i] - nums[j];
int index = BinarySearch.search(nums, target); int index = BinarySearch.search(nums, target);
// 应该注意这里的下标必须大于 j否则会重复统计。 // 应该注意这里的下标必须大于 j否则会重复统计。
if (index > j) if (index > j) {
cnt++; cnt++;
}
} }
}
return cnt; return cnt;
} }
} }
@ -143,22 +151,59 @@ public class ThreeSumFast {
```java ```java
public class BinarySearch { public class BinarySearch {
public static int search(int[] nums, int target) { public static int search(int[] nums, int target) {
int l = 0, h = nums.length - 1; int l = 0, h = nums.length - 1;
while (l <= h) { while (l <= h) {
int m = l + (h - l) / 2; int m = l + (h - l) / 2;
if (target == nums[m]) if (target == nums[m]) {
return m; return m;
else if (target > nums[m]) } else if (target > nums[m]) {
l = m + 1; l = m + 1;
else } else {
h = m - 1; h = m - 1;
}
} }
return -1; return -1;
} }
} }
``` ```
### 3. ThreeSumTwoPointer
更有效的方法是先将数组排序,然后使用双指针进行查找,时间复杂度为 O(N<sup>2</sup>)。
```java
public class ThreeSumTwoPointer implements ThreeSum {
@Override
public int count(int[] nums) {
int N = nums.length;
int cnt = 0;
Arrays.sort(nums);
for (int i = 0; i < N - 2; i++) {
int l = i + 1, h = N - 1, target = -nums[i];
if (i > 0 && nums[i] == nums[i - 1]) continue;
while (l < h) {
int sum = nums[l] + nums[h];
if (sum == target) {
cnt++;
while (l < h && nums[l] == nums[l + 1]) l++;
while (l < h && nums[h] == nums[h - 1]) h--;
l++;
h--;
} else if (sum < target) {
l++;
} else {
h--;
}
}
}
return cnt;
}
}
```
## 倍率实验 ## 倍率实验
如果 T(N) \~ aN<sup>b</sup>logN那么 T(2N)/T(N) \~ 2<sup>b</sup> 如果 T(N) \~ aN<sup>b</sup>logN那么 T(2N)/T(N) \~ 2<sup>b</sup>
@ -180,29 +225,20 @@ public class BinarySearch {
public class RatioTest { public class RatioTest {
public static void main(String[] args) { public static void main(String[] args) {
int N = 500; int N = 500;
int loopTimes = 7; int loopTimes = 7;
double preTime = -1; double preTime = -1;
while (loopTimes-- > 0) { while (loopTimes-- > 0) {
int[] nums = new int[N]; int[] nums = new int[N];
StopWatch.start(); StopWatch.start();
ThreeSum threeSum = new ThreeSumSlow(); ThreeSum threeSum = new ThreeSumSlow();
int cnt = threeSum.count(nums); int cnt = threeSum.count(nums);
System.out.println(cnt); System.out.println(cnt);
double elapsedTime = StopWatch.elapsedTime(); double elapsedTime = StopWatch.elapsedTime();
double ratio = preTime == -1 ? 0 : elapsedTime / preTime; double ratio = preTime == -1 ? 0 : elapsedTime / preTime;
System.out.println(N + " " + elapsedTime + " " + ratio); System.out.println(N + " " + elapsedTime + " " + ratio);
preTime = elapsedTime; preTime = elapsedTime;
N *= 2; N *= 2;
} }
} }
} }
@ -265,7 +301,7 @@ public class Selection<T extends Comparable<T>> extends Sort<T> {
@Override @Override
public void sort(T[] nums) { public void sort(T[] nums) {
int N = nums.length; int N = nums.length;
for (int i = 0; i < N; i++) { for (int i = 0; i < N - 1; i++) {
int min = i; int min = i;
for (int j = i + 1; j < N; j++) { for (int j = i + 1; j < N; j++) {
if (less(nums[j], nums[min])) { if (less(nums[j], nums[min])) {
@ -343,7 +379,7 @@ public class Insertion<T extends Comparable<T>> extends Sort<T> {
对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素,每次只能将逆序数量减少 1。 对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素,每次只能将逆序数量减少 1。
希尔排序的出现就是为了改进插入排序的这种局限性,它通过交换不相邻的元素,每次可以将逆序数量减少大于 1。 希尔排序的出现就是为了解决插入排序的这种局限性,它通过交换不相邻的元素,每次可以将逆序数量减少大于 1。
希尔排序使用插入排序对间隔 h 的序列进行排序。通过不断减小 h最后令 h=1就可以使得整个数组是有序的。 希尔排序使用插入排序对间隔 h 的序列进行排序。通过不断减小 h最后令 h=1就可以使得整个数组是有序的。
@ -423,7 +459,7 @@ public abstract class MergeSort<T extends Comparable<T>> extends Sort<T> {
将一个大数组分成两个小数组去求解。 将一个大数组分成两个小数组去求解。
因为每次都将问题对半分成两个子问题,这种对半分的算法复杂度一般为 O(NlogN),因此该归并排序方法的时间复杂度也为 O(NlogN)。 因为每次都将问题对半分成两个子问题,这种对半分的算法复杂度一般为 O(NlogN)。
```java ```java
public class Up2DownMergeSort<T extends Comparable<T>> extends MergeSort<T> { public class Up2DownMergeSort<T extends Comparable<T>> extends MergeSort<T> {
@ -507,7 +543,7 @@ public class QuickSort<T extends Comparable<T>> extends Sort<T> {
取 a[l] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素。不断进行这个过程,就可以保证左指针 i 的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[l] 和 a[j] 交换位置。 取 a[l] 作为切分元素,然后从数组的左端向右扫描直到找到第一个大于等于它的元素,再从数组的右端向左扫描找到第一个小于等于它的元素,交换这两个元素。不断进行这个过程,就可以保证左指针 i 的左侧元素都不大于切分元素,右指针 j 的右侧元素都不小于切分元素。当两个指针相遇时,将切分元素 a[l] 和 a[j] 交换位置。
<div align="center"> <img src="../pics//864bfa7d-1149-420c-a752-f9b3d4e782ec.png" width="400"/> </div><br> <div align="center"> <img src="../pics//766aedd0-1b00-4065-aa2b-7d31138df84f.png" width="400"/> </div><br>
```java ```java
private int partition(T[] nums, int l, int h) { private int partition(T[] nums, int l, int h) {
@ -535,15 +571,15 @@ private int partition(T[] nums, int l, int h) {
### 4. 算法改进 ### 4. 算法改进
(一)切换到插入排序 #### 4.1 切换到插入排序
因为快速排序在小数组中也会递归调用自己,对于小数组,插入排序比快速排序的性能更好,因此在小数组中可以切换到插入排序。 因为快速排序在小数组中也会递归调用自己,对于小数组,插入排序比快速排序的性能更好,因此在小数组中可以切换到插入排序。
(二)三数取中 #### 4.2 三数取中
最好的情况下是每次都能取数组的中位数作为切分元素,但是计算中位数的代价很高。人们发现取 3 个元素并将大小居中的元素作为切分元素的效果最好。 最好的情况下是每次都能取数组的中位数作为切分元素,但是计算中位数的代价很高。人们发现取 3 个元素并将大小居中的元素作为切分元素的效果最好。
(三)三向切分 #### 4.3 三向切分
对于有大量重复元素的数组,可以将数组切分为三部分,分别对应小于、等于和大于切分元素。 对于有大量重复元素的数组,可以将数组切分为三部分,分别对应小于、等于和大于切分元素。
@ -581,7 +617,7 @@ public class ThreeWayQuickSort<T extends Comparable<T>> extends QuickSort<T> {
可以利用这个特性找出数组的第 k 个元素。 可以利用这个特性找出数组的第 k 个元素。
该算法是线性级别的,因为每次能将数组二分,那么比较的总次数为 (N+N/2+N/4+..),直到找到第 k 个元素,这个和显然小于 2N。 该算法是线性级别的,假设每次能将数组二分,那么比较的总次数为 (N+N/2+N/4+..),直到找到第 k 个元素,这个和显然小于 2N。
```java ```java
public T select(T[] nums, int k) { public T select(T[] nums, int k) {
@ -609,7 +645,7 @@ public T select(T[] nums, int k) {
堆的某个节点的值总是大于等于子节点的值,并且堆是一颗完全二叉树。 堆的某个节点的值总是大于等于子节点的值,并且堆是一颗完全二叉树。
堆可以用数组来表示,因为堆是完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2而它的两个子节点的位置分别为 2k 和 2k+1。这里不使用数组索引为 0 的位置,是为了更清晰地描述节点的位置关系。 堆可以用数组来表示,这是因为堆是完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2而它的两个子节点的位置分别为 2k 和 2k+1。这里不使用数组索引为 0 的位置,是为了更清晰地描述节点的位置关系。
<div align="center"> <img src="../pics//f3080f83-6239-459b-8e9c-03b6641f7815.png" width="200"/> </div><br> <div align="center"> <img src="../pics//f3080f83-6239-459b-8e9c-03b6641f7815.png" width="200"/> </div><br>
@ -658,7 +694,7 @@ private void swim(int k) {
} }
``` ```
类似地,当一个节点比子节点来得小,也需要不断地向下进行比较和交换操作,把这种操作称为下沉。一个节点如果有两个子节点,应当与两个子节点中最大那节点进行交换。 类似地,当一个节点比子节点来得小,也需要不断地向下进行比较和交换操作,把这种操作称为下沉。一个节点如果有两个子节点,应当与两个子节点中最大那节点进行交换。
<div align="center"> <img src="../pics//72f0ff69-138d-4e54-b7ac-ebe025d978dc.png" width="400"/> </div><br> <div align="center"> <img src="../pics//72f0ff69-138d-4e54-b7ac-ebe025d978dc.png" width="400"/> </div><br>
@ -703,15 +739,15 @@ public T delMax() {
### 5. 堆排序 ### 5. 堆排序
由于堆可以很容易得到最大的元素并删除它,不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列。因此很容易使用堆来进行排序。并且堆排序是原地排序,不占用额外空间 把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列,这就是堆排序
(一)构建堆 #### 5.1 构建堆
无序数组建立堆最直接的方法是从左到右遍历数组,然后进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。 无序数组建立堆最直接的方法是从左到右遍历数组进行上浮操作。一个更高效的方法是从右至左进行下沉操作,如果一个节点的两个节点都已经是堆有序,那么进行下沉操作可以使得这个节点为根节点的堆有序。叶子节点不需要进行下沉操作,可以忽略叶子节点的元素,因此只需要遍历一半的元素即可。
<div align="center"> <img src="../pics//b84ba6fb-312b-4e69-8c77-fb6eb6fb38d4.png" width="300"/> </div><br> <div align="center"> <img src="../pics//b84ba6fb-312b-4e69-8c77-fb6eb6fb38d4.png" width="300"/> </div><br>
(二)交换堆顶元素与最后一个元素 #### 5.2 交换堆顶元素与最后一个元素
交换之后需要进行下沉操作维持堆的有序状态。 交换之后需要进行下沉操作维持堆的有序状态。
@ -760,7 +796,7 @@ public class HeapSort<T extends Comparable<T>> extends Sort<T> {
对于堆排序,由于要对 N 个节点进行下沉操作,因此复杂度为 NlogN。 对于堆排序,由于要对 N 个节点进行下沉操作,因此复杂度为 NlogN。
堆排序一种原地排序,没有利用额外的空间。 堆排序一种原地排序,没有利用额外的空间。
现代操作系统很少使用堆排序,因为它无法利用局部性原理进行缓存,也就是数组元素很少和相邻的元素进行比较。 现代操作系统很少使用堆排序,因为它无法利用局部性原理进行缓存,也就是数组元素很少和相邻的元素进行比较。
@ -768,9 +804,9 @@ public class HeapSort<T extends Comparable<T>> extends Sort<T> {
### 1. 排序算法的比较 ### 1. 排序算法的比较
| 算法 | 稳定 | 时间复杂度 | 空间复杂度 | 备注 | | 算法 | 稳定 | 时间复杂度 | 空间复杂度 | 备注 |
| :---: | :---: |:---: | :---: | :---: | | :---: | :---: |:---: | :---: | :---: |
| 选择排序 | | N<sup>2</sup> | 1 | | | 选择排序 | × | N<sup>2</sup> | 1 | |
| 冒泡排序 | √ | N<sup>2</sup> | 1 | | | 冒泡排序 | √ | N<sup>2</sup> | 1 | |
| 插入排序 | √ | N \~ N<sup>2</sup> | 1 | 时间复杂度和初始顺序有关 | | 插入排序 | √ | N \~ N<sup>2</sup> | 1 | 时间复杂度和初始顺序有关 |
| 希尔排序 | × | N 的若干倍乘于递增序列的长度 | 1 | | | 希尔排序 | × | N 的若干倍乘于递增序列的长度 | 1 | |
@ -779,7 +815,7 @@ public class HeapSort<T extends Comparable<T>> extends Sort<T> {
| 归并排序 | √ | NlogN | N | | | 归并排序 | √ | NlogN | N | |
| 堆排序 | × | NlogN | 1 | | | | 堆排序 | × | NlogN | 1 | | |
快速排序是最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间近似为 \~cNlogN这里的 c 比其线性对数级别的排序算法都要小。使用三向切分快速排序,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。 快速排序是最快的通用排序算法,它的内循环的指令很少,而且它还能利用缓存,因为它总是顺序地访问数据。它的运行时间近似为 \~cNlogN这里的 c 比其线性对数级别的排序算法都要小。使用三向切分快速排序,实际应用中可能出现的某些分布的输入能够达到线性级别,而其它排序算法仍然需要线性对数时间。
### 2. Java 的排序算法实现 ### 2. Java 的排序算法实现
@ -846,7 +882,6 @@ public class QuickFindUF extends UF {
@Override @Override
public void union(int p, int q) { public void union(int p, int q) {
int pID = find(p); int pID = find(p);
int qID = find(q); int qID = find(q);
@ -881,7 +916,6 @@ public class QuickUnionUF extends UF {
@Override @Override
public int find(int p) { public int find(int p) {
while (p != id[p]) { while (p != id[p]) {
p = id[p]; p = id[p];
} }
@ -891,7 +925,6 @@ public class QuickUnionUF extends UF {
@Override @Override
public void union(int p, int q) { public void union(int p, int q) {
int pRoot = find(p); int pRoot = find(p);
int qRoot = find(q); int qRoot = find(q);
@ -902,7 +935,7 @@ public class QuickUnionUF extends UF {
} }
``` ```
这种方法可以快速进行 union 操作,但是 find 操作和树高成正比,最坏的情况下树的高度为点的数目。 这种方法可以快速进行 union 操作,但是 find 操作和树高成正比,最坏的情况下树的高度为点的数目。
<div align="center"> <img src="../pics//bfbb11e2-d208-4efa-b97b-24cd40467cd8.png" width="150"/> </div><br> <div align="center"> <img src="../pics//bfbb11e2-d208-4efa-b97b-24cd40467cd8.png" width="150"/> </div><br>
@ -1090,7 +1123,7 @@ public class ArrayStack<Item> implements MyStack<Item> {
### 2. 链表实现 ### 2. 链表实现
需要使用链表的头插法来实现,因为头插法中最后压入栈的元素在链表的开头,它的 next 指针指向前一个压入栈的元素,在弹出元素时就可以通过 next 指针遍历到前一个压入栈的元素从而让这个元素为新的栈顶元素。 需要使用链表的头插法来实现,因为头插法中最后压入栈的元素在链表的开头,它的 next 指针指向前一个压入栈的元素,在弹出元素时就可以通过 next 指针遍历到前一个压入栈的元素从而让这个元素为新的栈顶元素。
```java ```java
public class ListStack<Item> implements MyStack<Item> { public class ListStack<Item> implements MyStack<Item> {
@ -1290,7 +1323,7 @@ public class ListQueue<Item> implements MyQueue<Item> {
# 六、查找 # 六、符号表
符号表Symbol Table是一种存储键值对的数据结构可以支持快速查找操作。 符号表Symbol Table是一种存储键值对的数据结构可以支持快速查找操作。
@ -1411,9 +1444,9 @@ public class ListUnorderedST<Key, Value> implements UnorderedST<Key, Value> {
使用一对平行数组,一个存储键一个存储值。 使用一对平行数组,一个存储键一个存储值。
rank() 方法至关重要,当键在表中时,它能够知道该键的位置;当键不在表中时,它也能知道在何处插入新键。 二分查找的 rank() 方法至关重要,当键在表中时,它能够知道该键的位置;当键不在表中时,它也能知道在何处插入新键。
复杂度:二分查找最多需要 logN+1 次比较,使用二分查找实现的符号表的查找操作所需要的时间最多是对数级别的。但是插入操作需要移动数组元素,是线性级别的。 二分查找最多需要 logN+1 次比较,使用二分查找实现的符号表的查找操作所需要的时间最多是对数级别的。但是插入操作需要移动数组元素,是线性级别的。
```java ```java
public class BinarySearchOrderedST<Key extends Comparable<Key>, Value> implements OrderedST<Key, Value> { public class BinarySearchOrderedST<Key extends Comparable<Key>, Value> implements OrderedST<Key, Value> {
@ -1552,7 +1585,7 @@ public class BST<Key extends Comparable<Key>, Value> implements OrderedST<Key, V
} }
``` ```
为了方便绘图,下文中二叉树的空链接不画出来。 为了方便绘图,下文中二叉树的空链接不画出来。
### 1. get() ### 1. get()
@ -1712,7 +1745,7 @@ public Node deleteMin(Node x) {
### 8. delete() ### 8. delete()
- 如果待删除的节点只有一个子树,那么只需要让指向待删除节点的链接指向唯一的子树即可; - 如果待删除的节点只有一个子树, 那么只需要让指向待删除节点的链接指向唯一的子树即可;
- 否则,让右子树的最小节点替换该节点。 - 否则,让右子树的最小节点替换该节点。
<div align="center"> <img src="../pics//fa568fac-ac58-48dd-a9bb-23b3065bf2dc.png" width="400"/> </div><br> <div align="center"> <img src="../pics//fa568fac-ac58-48dd-a9bb-23b3065bf2dc.png" width="400"/> </div><br>
@ -1770,9 +1803,9 @@ private List<Key> keys(Node x, Key l, Key h) {
} }
``` ```
### 10. 性能分析 ### 10. 分析
复杂度:二叉查找树所有操作在最坏的情况下所需要的时间都和树的高度成正比。 二叉查找树所有操作在最坏的情况下所需要的时间都和树的高度成正比。
## 2-3 查找树 ## 2-3 查找树
@ -1802,7 +1835,7 @@ private List<Key> keys(Node x, Key l, Key h) {
## 红黑树 ## 红黑树
2-3 查找树需要用到 2- 节点和 3- 节点,红黑树使用红链接来实现 3- 节点。指向一个节点的链接颜色如果为红色,那么这个节点和上层节点表示的是一个 3- 节点,而黑色则是普通链接。 红黑树是 2-3 查找树,但它不需要分别定义 2- 节点和 3- 节点,而是在普通的二叉查找树之上,为节点添加颜色。指向一个节点的链接颜色如果为红色,那么这个节点和上层节点表示的是一个 3- 节点,而黑色则是普通链接。
<div align="center"> <img src="../pics//4f48e806-f90b-4c09-a55f-ac0cd641c047.png" width="250"/> </div><br> <div align="center"> <img src="../pics//4f48e806-f90b-4c09-a55f-ac0cd641c047.png" width="250"/> </div><br>
@ -1817,6 +1850,7 @@ private List<Key> keys(Node x, Key l, Key h) {
```java ```java
public class RedBlackBST<Key extends Comparable<Key>, Value> extends BST<Key, Value> { public class RedBlackBST<Key extends Comparable<Key>, Value> extends BST<Key, Value> {
private static final boolean RED = true; private static final boolean RED = true;
private static final boolean BLACK = false; private static final boolean BLACK = false;
@ -1972,16 +2006,17 @@ int hash = (((day * R + month) % M) * R + year) % M;
R 通常取 31。 R 通常取 31。
Java 中的 hashCode() 实现了 hash 函数,但是默认使用对象的内存地址值。在使用 hashCode() 函数时,应当结合除留余数法来使用。因为内存地址是 32 位整数,我们只需要 31 位的非负整数,因此应当屏蔽符号位之后再使用除留余数法。 Java 中的 hashCode() 实现了哈希函数,但是默认使用对象的内存地址值。在使用 hashCode() 时,应当结合除留余数法来使用。因为内存地址是 32 位整数,我们只需要 31 位的非负整数,因此应当屏蔽符号位之后再使用除留余数法。
```java ```java
int hash = (x.hashCode() & 0x7fffffff) % M; int hash = (x.hashCode() & 0x7fffffff) % M;
``` ```
使用 Java 自带的 HashMap 等自带的哈希表实现时,只需要去实现 Key 类型的 hashCode() 函数即可。Java 规定 hashCode() 能够将键均匀分布于所有的 32 位整数Java 中的 String、Integer 等对象的 hashCode() 都能实现这一点。以下展示了自定义类型如何实现 hashCode() 使用 Java 的 HashMap 等自带的哈希表实现时,只需要去实现 Key 类型的 hashCode() 函数即可。Java 规定 hashCode() 能够将键均匀分布于所有的 32 位整数Java 中的 String、Integer 等对象的 hashCode() 都能实现这一点。以下展示了自定义类型如何实现 hashCode()
```java ```java
public class Transaction { public class Transaction {
private final String who; private final String who;
private final Date when; private final Date when;
private final double amount; private final double amount;
@ -2003,17 +2038,17 @@ public class Transaction {
} }
``` ```
### 2. 基于拉链法的散列表 ### 2. 拉链法
拉链法使用链表来存储 hash 值相同的键,从而解决冲突。 拉链法使用链表来存储 hash 值相同的键,从而解决冲突。
查找需要分两步,首先查找 Key 所在的链表,然后在链表中顺序查找。 查找需要分两步,首先查找 Key 所在的链表,然后在链表中顺序查找。
对于 N 个键M 条链表 (N>M),如果 hash 函数能够满足均匀性的条件,每条链表的大小趋向于 N/M因此未命中的查找和插入操作所需要的比较次数为 \~N/M。 对于 N 个键M 条链表 (N>M),如果哈希函数能够满足均匀性的条件,每条链表的大小趋向于 N/M因此未命中的查找和插入操作所需要的比较次数为 \~N/M。
<div align="center"> <img src="../pics//b4252c85-6fb0-4995-9a68-a1a5925fbdb1.png" width="300"/> </div><br> <div align="center"> <img src="../pics//b4252c85-6fb0-4995-9a68-a1a5925fbdb1.png" width="300"/> </div><br>
### 3. 基于线性探测法的散列表 ### 3. 线性探测法
线性探测法使用空位来解决冲突,当冲突发生时,向前探测一个空位来存储冲突的键。 线性探测法使用空位来解决冲突,当冲突发生时,向前探测一个空位来存储冲突的键。
@ -2023,6 +2058,7 @@ public class Transaction {
```java ```java
public class LinearProbingHashST<Key, Value> implements UnorderedST<Key, Value> { public class LinearProbingHashST<Key, Value> implements UnorderedST<Key, Value> {
private int N = 0; private int N = 0;
private int M = 16; private int M = 16;
private Key[] keys; private Key[] keys;
@ -2048,7 +2084,7 @@ public class LinearProbingHashST<Key, Value> implements UnorderedST<Key, Value>
} }
``` ```
**(一)查找** #### 3.1 查找
```java ```java
public Value get(Key key) { public Value get(Key key) {
@ -2060,7 +2096,7 @@ public Value get(Key key) {
} }
``` ```
**(二)插入** #### 3.2 插入
```java ```java
public void put(Key key, Value value) { public void put(Key key, Value value) {
@ -2082,7 +2118,7 @@ private void putInternal(Key key, Value value) {
} }
``` ```
**(三)删除** #### 3.3 删除
删除操作应当将右侧所有相邻的键值对重新插入散列表中。 删除操作应当将右侧所有相邻的键值对重新插入散列表中。
@ -2115,7 +2151,7 @@ public void delete(Key key) {
} }
``` ```
**(四)调整数组大小** #### 3.5 调整数组大小
线性探测法的成本取决于连续条目的长度,连续条目也叫聚簇。当聚簇很长时,在查找和插入时也需要进行很多次探测。例如下图中 2\~5 位置就是一个聚簇。 线性探测法的成本取决于连续条目的长度,连续条目也叫聚簇。当聚簇很长时,在查找和插入时也需要进行很多次探测。例如下图中 2\~5 位置就是一个聚簇。
@ -2199,15 +2235,15 @@ public class SparseVector {
这是一个经典的递归问题,分为三步求解: 这是一个经典的递归问题,分为三步求解:
- 将 n-1 个圆盘从 from -> buffer 将 n-1 个圆盘从 from -> buffer
<div align="center"> <img src="../pics//8587132a-021d-4f1f-a8ec-5a9daa7157a7.png" width="300"/> </div><br> <div align="center"> <img src="../pics//8587132a-021d-4f1f-a8ec-5a9daa7157a7.png" width="300"/> </div><br>
- 将 1 个圆盘从 from -> to 将 1 个圆盘从 from -> to
<div align="center"> <img src="../pics//2861e923-4862-4526-881c-15529279d49c.png" width="300"/> </div><br> <div align="center"> <img src="../pics//2861e923-4862-4526-881c-15529279d49c.png" width="300"/> </div><br>
- 将 n-1 个圆盘从 buffer -> to 将 n-1 个圆盘从 buffer -> to
<div align="center"> <img src="../pics//1c4e8185-8153-46b6-bd5a-288b15feeae6.png" width="300"/> </div><br> <div align="center"> <img src="../pics//1c4e8185-8153-46b6-bd5a-288b15feeae6.png" width="300"/> </div><br>
@ -2245,20 +2281,20 @@ from H1 to H3
## 哈夫曼编码 ## 哈夫曼编码
哈夫曼编码根据数据出现的频率对数据进行编码,从而压缩原始数据。 根据数据出现的频率对数据进行编码,从而压缩原始数据。
例如对于文本文件,其中各种字符出现的次数如下: 例如对于一个文本文件,其中各种字符出现的次数如下:
- a : 10 - a : 10
- b : 20 - b : 20
- c : 40 - c : 40
- d : 80 - d : 80
可以将每种字符转换成二进制编码,例如将 a 转换为 00b 转换为 01c 转换为 10d 转换为 11。这是最简单的一种编码方式没有考虑各个字符的权值出现频率。而哈夫曼编码能让出现频率最高的字符的编码最短,从而保证整体的编码长度最短。 可以将每种字符转换成二进制编码,例如将 a 转换为 00b 转换为 01c 转换为 10d 转换为 11。这是最简单的一种编码方式没有考虑各个字符的权值出现频率。而哈夫曼编码采用了贪心策略,使出现频率最高的字符的编码最短,从而保证整体的编码长度最短。
首先生成一颗哈夫曼树,每次生成过程中选取频率最少的两个节点,生成一个新节点作为它们的父节点,并且新节点的频率为两个节点的和。选取频率最少的原因是,生成过程使得先选取的节点在树的最底层,那么需要的编码长度更长,频率更少可以使得总编码长度更少。 首先生成一颗哈夫曼树,每次生成过程中选取频率最少的两个节点,生成一个新节点作为它们的父节点,并且新节点的频率为两个节点的和。选取频率最少的原因是,生成过程使得先选取的节点位于树的更低层,那么需要的编码长度更长,频率更少可以使得总编码长度更少。
生成编码时,从根节点出发,向左遍历则添加二进制位 0向右则添加二进制位 1直到遍历到根节点,根节点代表的字符的编码就是这个路径编码。 生成编码时,从根节点出发,向左遍历则添加二进制位 0向右则添加二进制位 1直到遍历到叶子节点,叶子节点代表的字符的编码就是这个路径编码。
<div align="center"> <img src="../pics//3ff4f00a-2321-48fd-95f4-ce6001332151.png" width="400"/> </div><br> <div align="center"> <img src="../pics//3ff4f00a-2321-48fd-95f4-ce6001332151.png" width="400"/> </div><br>

View File

@ -4,6 +4,7 @@
* [三、扩展性](#三扩展性) * [三、扩展性](#三扩展性)
* [四、可用性](#四可用性) * [四、可用性](#四可用性)
* [五、安全性](#五安全性) * [五、安全性](#五安全性)
* [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
@ -13,7 +14,7 @@
### 1. 响应时间 ### 1. 响应时间
某个请求从发出到接收到响应消耗的时间。 指某个请求从发出到接收到响应消耗的时间。
在对响应时间进行测试时,通常采用重复请求方式,然后计算平均响应时间。 在对响应时间进行测试时,通常采用重复请求方式,然后计算平均响应时间。
@ -68,7 +69,7 @@
应用服务器只要不具有状态,那么就可以很容易地通过负载均衡器向集群中添加新的服务器。 应用服务器只要不具有状态,那么就可以很容易地通过负载均衡器向集群中添加新的服务器。
关系型数据库的伸缩性通过 Sharding 来实现,将数据按一定的规则分布到不同的节点上,从而解决单台存储服务器存储空间限制。 关系型数据库的伸缩性通过 Sharding 来实现,将数据按一定的规则分布到不同的节点上,从而解决单台存储服务器存储空间限制。
对于非关系型数据库,它们天生就是为海量数据而诞生,对伸缩性的支持特别好。 对于非关系型数据库,它们天生就是为海量数据而诞生,对伸缩性的支持特别好。
@ -78,7 +79,7 @@
实现可扩展主要有两种方式: 实现可扩展主要有两种方式:
- 使用消息队列进行解耦,应用之间通过消息传递的方式进行通信; - 使用消息队列进行解耦,应用之间通过消息传递进行通信;
- 使用分布式服务将业务和可复用的服务分离开来,业务使用分布式服务框架调用可复用的服务。新增的产品可以通过调用可复用的服务来实现业务逻辑,对其它产品没有影响。 - 使用分布式服务将业务和可复用的服务分离开来,业务使用分布式服务框架调用可复用的服务。新增的产品可以通过调用可复用的服务来实现业务逻辑,对其它产品没有影响。
# 四、可用性 # 四、可用性
@ -87,7 +88,7 @@
保证高可用的主要手段是使用冗余,当某个服务器故障时就请求其它服务器。 保证高可用的主要手段是使用冗余,当某个服务器故障时就请求其它服务器。
应用服务器的冗余比较容易实现,只要保证应用服务器不具有状态,那么某个应用服务器故障时,负载均衡器将该应用服务器原先的用户请求转发到另一个应用服务器上不会对用户有任何影响。 应用服务器的冗余比较容易实现,只要保证应用服务器不具有状态,那么某个应用服务器故障时,负载均衡器将该应用服务器原先的用户请求转发到另一个应用服务器上不会对用户有任何影响。
存储服务器的冗余需要使用主从复制来实现,当主服务器故障时,需要提升从服务器为主服务器,这个过程称为切换。 存储服务器的冗余需要使用主从复制来实现,当主服务器故障时,需要提升从服务器为主服务器,这个过程称为切换。
@ -97,8 +98,12 @@
## 服务降级 ## 服务降级
服务降级是系统为了应对大量的请求,主动关闭部分功能,从而保证核心功能可用。 服务降级是系统为了应对大量的请求,主动关闭部分功能,从而保证核心功能可用。
# 五、安全性 # 五、安全性
要求系统的应对各种攻击手段时能够有可靠的应对措施。 要求系统在应对各种攻击手段时能够有可靠的应对措施。
# 参考资料
- 大型网站技术架构:核心原理与案例分析

View File

@ -6,6 +6,7 @@
* [五、缓存问题](#五缓存问题) * [五、缓存问题](#五缓存问题)
* [六、数据分布](#六数据分布) * [六、数据分布](#六数据分布)
* [七、一致性哈希](#七一致性哈希) * [七、一致性哈希](#七一致性哈希)
* [参考资料](#参考资料)
<!-- GFM-TOC --> <!-- GFM-TOC -->
@ -25,20 +26,16 @@
## 淘汰策略 ## 淘汰策略
- FIFOFirst In First Out先进先出策略在实时性的场景下需要经常访问最新的数据那么就可以使用 FIFO使最先进入的数据最晚的数据被淘汰。 - FIFOFirst In First Out先进先出策略在实时性的场景下需要经常访问最新的数据那么就可以使用 FIFO使最先进入的数据(最晚的数据)被淘汰。
- LRULeast Recently Used最近最久未使用策略优先淘汰最久未使用的数据也就是上次被访问时间距离现在最远的数据。该策略可以保证内存中的数据都是热点数据也就是经常被访问的数据从而保证缓存命中率。 - LRULeast Recently Used最近最久未使用策略优先淘汰最久未使用的数据也就是上次被访问时间距离现在最久的数据。该策略可以保证内存中的数据都是热点数据也就是经常被访问的数据从而保证缓存命中率。
参考资料:
- [缓存那些事](https://tech.meituan.com/cache_about.html)
# 二、LRU # 二、LRU
以下是一个基于 双向链表 + HashMap 的 LRU 算法实现,对算法的解释如下: 以下是基于 双向链表 + HashMap 的 LRU 算法实现,对算法的解释如下:
- 最基本的思路是当访问某个节点时,将其从原来的位置删除,并重新插入到链表头部这样就能保证链表尾部存储的就是最近最久未使用的节点,当节点数量大于缓存最大空间时就删除链表尾部的节点。 - 访问某个节点时,将其从原来的位置删除,并重新插入到链表头部这样就能保证链表尾部存储的就是最近最久未使用的节点,当节点数量大于缓存最大空间时就淘汰链表尾部的节点。
- 为了使删除操作时间复杂度为 O(1)那么就不能采用遍历的方式找到某个节点。HashMap 存储着 Key 到节点的映射,通过 Key 就能以 O(1) 的时间得到节点,然后再以 O(1) 的时间将其从双向队列中删除。 - 为了使删除操作时间复杂度为 O(1)就不能采用遍历的方式找到某个节点。HashMap 存储着 Key 到节点的映射,通过 Key 就能以 O(1) 的时间得到节点,然后再以 O(1) 的时间将其从双向队列中删除。
```java ```java
public class LRU<K, V> implements Iterable<K> { public class LRU<K, V> implements Iterable<K> {
@ -61,6 +58,7 @@ public class LRU<K, V> implements Iterable<K> {
} }
} }
public LRU(int maxSize) { public LRU(int maxSize) {
this.maxSize = maxSize; this.maxSize = maxSize;
@ -73,6 +71,7 @@ public class LRU<K, V> implements Iterable<K> {
tail.pre = head; tail.pre = head;
} }
public V get(K key) { public V get(K key) {
if (!map.containsKey(key)) { if (!map.containsKey(key)) {
@ -86,6 +85,7 @@ public class LRU<K, V> implements Iterable<K> {
return node.v; return node.v;
} }
public void put(K key, V value) { public void put(K key, V value) {
if (map.containsKey(key)) { if (map.containsKey(key)) {
@ -99,34 +99,40 @@ public class LRU<K, V> implements Iterable<K> {
if (map.size() > maxSize) { if (map.size() > maxSize) {
Node toRemove = removeTail(); Node toRemove = removeTail();
map.remove(toRemove); map.remove(toRemove.k);
} }
} }
private void unlink(Node node) { private void unlink(Node node) {
Node pre = node.pre; Node pre = node.pre;
node.pre = node.next; Node next = node.next;
node.next = pre; pre.next = next;
next.pre = pre;
} }
private void appendHead(Node node) { private void appendHead(Node node) {
node.next = head.next; node.next = head.next;
node.next.pre = node;
node.pre = head;
head.next = node; head.next = node;
} }
private Node removeTail() { private Node removeTail() {
Node node = tail.pre; Node node = tail.pre;
node.pre = tail; tail.pre = node.pre;
node.pre.next = tail;
return node; return node;
} }
@Override @Override
public Iterator<K> iterator() { public Iterator<K> iterator() {
return new Iterator<K>() { return new Iterator<K>() {
private Node cur = head.next; private Node cur = head.next;
@Override @Override
public boolean hasNext() { public boolean hasNext() {
return cur != tail; return cur != tail;
@ -143,10 +149,6 @@ public class LRU<K, V> implements Iterable<K> {
} }
``` ```
源代码:
- [CyC2018/Algorithm](https://github.com/CyC2018/Algorithm/tree/master/Caching)
# 三、缓存位置 # 三、缓存位置
## 浏览器 ## 浏览器
@ -159,7 +161,7 @@ public class LRU<K, V> implements Iterable<K> {
## 反向代理 ## 反向代理
反向代理位于服务器之前,请求与响应都需要经过反向代理。通过将数据缓存在反向代理,在用户请求时就可以直接使用缓存进行响应。 反向代理位于服务器之前,请求与响应都需要经过反向代理。通过将数据缓存在反向代理,在用户请求反向代理时就可以直接使用缓存进行响应。
## 本地缓存 ## 本地缓存
@ -169,15 +171,15 @@ public class LRU<K, V> implements Iterable<K> {
使用 Redis、Memcache 等分布式缓存将数据缓存在分布式缓存系统中。 使用 Redis、Memcache 等分布式缓存将数据缓存在分布式缓存系统中。
相对于本地缓存来说,分布式缓存单独部署,可以根据需求分配硬件资源。不仅如此,服务器集群都可以访问分布式缓存而本地缓存需要在服务器集群之间进行同步,实现和性能开销上都非常大。 相对于本地缓存来说,分布式缓存单独部署,可以根据需求分配硬件资源。不仅如此,服务器集群都可以访问分布式缓存而本地缓存需要在服务器集群之间进行同步,实现难度和性能开销上都非常大。
## 数据库缓存 ## 数据库缓存
MySQL 等数据库管理系统具有自己的查询缓存机制来提高 SQL 查询效率。 MySQL 等数据库管理系统具有自己的查询缓存机制来提高查询效率。
# 四、CDN # 四、CDN
内容分发网络Content distribution networkCDN是一种通过互连的网络系统,利用更靠近用户的服务器更快更可靠地将 HTML、CSS、JavaScript、音乐、图片、视频等静态资源分发给用户。 内容分发网络Content distribution networkCDN是一种互连的网络系统利用更靠近用户的服务器从而更快更可靠地将 HTML、CSS、JavaScript、音乐、图片、视频等静态资源分发给用户。
CDN 主要有以下优点: CDN 主要有以下优点:
@ -187,11 +189,6 @@ CDN 主要有以下优点:
<div align="center"> <img src="../pics//15313ed8-a520-4799-a300-2b6b36be314f.jpg"/> </div><br> <div align="center"> <img src="../pics//15313ed8-a520-4799-a300-2b6b36be314f.jpg"/> </div><br>
参考资料:
- [内容分发网络](https://zh.wikipedia.org/wiki/%E5%85%A7%E5%AE%B9%E5%82%B3%E9%81%9E%E7%B6%B2%E8%B7%AF)
- [How Aspiration CDN helps to improve your website loading speed?](https://www.aspirationhosting.com/aspiration-cdn/)
# 五、缓存问题 # 五、缓存问题
## 缓存穿透 ## 缓存穿透
@ -205,15 +202,15 @@ CDN 主要有以下优点:
## 缓存雪崩 ## 缓存雪崩
指的是由于数据没有被加载到缓存中,或者缓存数据在同一时间大面积失效(过期),又或者缓存服务器宕机,导致大量的请求都到达数据库。 指的是由于数据没有被加载到缓存中,或者缓存数据在同一时间大面积失效(过期),又或者缓存服务器宕机,导致大量的请求都到达数据库。
存在缓存的系统中,系统非常依赖于缓存,缓存分担了很大一部分的数据请求。当发生缓存雪崩时,数据库无法处理这么大的请求,导致数据库崩溃。 缓存的系统中,系统非常依赖于缓存,缓存分担了很大一部分的数据请求。当发生缓存雪崩时,数据库无法处理这么大的请求,导致数据库崩溃。
解决方案: 解决方案:
- 为了防止缓存在同一时间大面积过期导致的缓存雪崩,可以通过观察用户行为,合理设置缓存过期时间来实现; - 为了防止缓存在同一时间大面积过期导致的缓存雪崩,可以通过观察用户行为,合理设置缓存过期时间来实现;
- 为了防止缓存服务器宕机出现的缓存雪崩,可以使用分布式缓存,分布式缓存中每一个节点只缓存部分的数据,当某个节点宕机时可以保证其它节点的缓存仍然可用。 - 为了防止缓存服务器宕机出现的缓存雪崩,可以使用分布式缓存,分布式缓存中每一个节点只缓存部分的数据,当某个节点宕机时可以保证其它节点的缓存仍然可用。
- 也可以进行缓存预热,避免在系统刚启动不久由于还未将大量数据进行缓存而导致缓存雪崩。 - 也可以进行缓存预热,避免在系统刚启动不久由于还未将大量数据进行缓存而导致缓存雪崩。
## 缓存一致性 ## 缓存一致性
@ -243,13 +240,9 @@ CDN 主要有以下优点:
- 能保持数据原有的顺序; - 能保持数据原有的顺序;
- 并且能够准确控制每台服务器存储的数据量,从而使得存储空间的利用率最大。 - 并且能够准确控制每台服务器存储的数据量,从而使得存储空间的利用率最大。
参考资料:
- 大规模分布式存储系统
# 七、一致性哈希 # 七、一致性哈希
Distributed Hash TableDHT 是一种哈希分布方式,其目的是为了克服传统哈希分布在服务器节点数量变化时大量数据失效的问题。 Distributed Hash TableDHT 是一种哈希分布方式,其目的是为了克服传统哈希分布在服务器节点数量变化时大量数据迁移的问题。
## 基本原理 ## 基本原理
@ -265,8 +258,14 @@ Distributed Hash TableDHT 是一种哈希分布方式,其目的是为了
上面描述的一致性哈希存在数据分布不均匀的问题,节点存储的数据量有可能会存在很大的不同。 上面描述的一致性哈希存在数据分布不均匀的问题,节点存储的数据量有可能会存在很大的不同。
数据不均匀主要是因为节点在哈希环上分布的不均匀,这种情况在节点数量很少的情况下尤其明显。解决方式是通过增加虚拟节点,然后将虚拟节点映射到真实节点上。虚拟节点的数量比真实节点来得大,那么虚拟节点在哈希环上分布的均匀性就会比原来的真实节点好,从而使得数据分布也更加均匀。 数据不均匀主要是因为节点在哈希环上分布的不均匀,这种情况在节点数量很少的情况下尤其明显。
参考资料: 解决方式是通过增加虚拟节点,然后将虚拟节点映射到真实节点上。虚拟节点的数量比真实节点来得多,那么虚拟节点在哈希环上分布的均匀性就会比原来的真实节点好,从而使得数据分布也更加均匀。
# 参考资料
- 大规模分布式存储系统
- [缓存那些事](https://tech.meituan.com/cache_about.html)
- [一致性哈希算法](https://my.oschina.net/jayhu/blog/732849) - [一致性哈希算法](https://my.oschina.net/jayhu/blog/732849)
- [内容分发网络](https://zh.wikipedia.org/wiki/%E5%85%A7%E5%AE%B9%E5%82%B3%E9%81%9E%E7%B6%B2%E8%B7%AF)
- [How Aspiration CDN helps to improve your website loading speed?](https://www.aspirationhosting.com/aspiration-cdn/)

View File

@ -13,8 +13,12 @@
* [经典同步问题](#经典同步问题) * [经典同步问题](#经典同步问题)
* [进程通信](#进程通信) * [进程通信](#进程通信)
* [三、死锁](#三死锁) * [三、死锁](#三死锁)
* [死锁的必要条件](#死锁的必要条件) * [必要条件](#必要条件)
* [死锁的处理方法](#死锁的处理方法) * [处理方法](#处理方法)
* [鸵鸟策略](#鸵鸟策略)
* [死锁检测与死锁恢复](#死锁检测与死锁恢复)
* [死锁预防](#死锁预防)
* [死锁避免](#死锁避免)
* [四、内存管理](#四内存管理) * [四、内存管理](#四内存管理)
* [虚拟内存](#虚拟内存) * [虚拟内存](#虚拟内存)
* [分页系统地址映射](#分页系统地址映射) * [分页系统地址映射](#分页系统地址映射)
@ -58,7 +62,11 @@
虚拟技术把一个物理实体转换为多个逻辑实体。 虚拟技术把一个物理实体转换为多个逻辑实体。
主要有两种虚拟技术:时分复用技术和空分复用技术。例如多个进程能在同一个处理器上并发执行使用了时分复用技术,让每个进程轮流占有处理器,每次只执行一小个时间片并快速切换。 主要有两种虚拟技术:时分复用技术和空分复用技术。
多个进程能在同一个处理器上并发执行使用了时分复用技术,让每个进程轮流占有处理器,每次只执行一小个时间片并快速切换。
虚拟内存使用了空分复用技术,它将物理内存抽象为地址空间,每个进程都有各自的地址空间。地址空间和物理内存使用页进行交换,地址空间的页并不需要全部在物理内存中,当使用到一个没有在物理内存的页时,执行页面置换算法,将该页置换到内存中。
### 4. 异步 ### 4. 异步
@ -159,21 +167,21 @@ QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 H
### 3. 区别 ### 3. 区别
(一)拥有资源 拥有资源
进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。 进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。
(二)调度 调度
线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程的线程切换到另一个进程中的线程时,会引起进程切换。 线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程的线程切换到另一个进程中的线程时,会引起进程切换。
(三)系统开销 系统开销
由于创建或撤销进程时系统都要为之分配或回收资源如内存空间、I/O 设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。 由于创建或撤销进程时系统都要为之分配或回收资源如内存空间、I/O 设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。
(四)通信方面 通信方面
进程间通信 (IPC) 需要进程同步和互斥手段的辅助,以保证数据的一致性。而线程间可以通过直接读/写同一进程中的数据段(如全局变量)来进行通信。 线程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助 IPC
## 进程状态的切换 ## 进程状态的切换
@ -235,7 +243,7 @@ QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 H
**2.3 多级反馈队列** **2.3 多级反馈队列**
如果一个进程需要执行 100 个时间片,如果采用时间片轮转调度算法,那么需要交换 100 次。 一个进程需要执行 100 个时间片,如果采用时间片轮转调度算法,那么需要交换 100 次。
多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如 1,2,4,8,..。进程在第一个队列没执行完,就会被移到下一个队列。这种方式下,之前的进程只需要交换 7 次。 多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如 1,2,4,8,..。进程在第一个队列没执行完,就会被移到下一个队列。这种方式下,之前的进程只需要交换 7 次。
@ -330,9 +338,9 @@ void consumer() {
down(&full); down(&full);
down(&mutex); down(&mutex);
int item = remove_item(); int item = remove_item();
consume_item(item);
up(&mutex); up(&mutex);
up(&empty); up(&empty);
consume_item(item);
} }
} }
``` ```
@ -450,6 +458,116 @@ void writer() {
} }
``` ```
以下内容由 [@Bandi Yugandhar](https://github.com/yugandharbandi) 提供。
The first case may result Writer to starve. This case favous Writers i.e no writer, once added to the queue, shall be kept waiting longer than absolutely necessary(only when there are readers that entered the queue before the writer).
```source-c
int readcount, writecount; //(initial value = 0)
semaphore rmutex, wmutex, readLock, resource; //(initial value = 1)
//READER
void reader() {
<ENTRY Section>
down(&readLock); // reader is trying to enter
down(&rmutex); // lock to increase readcount
readcount++;
if (readcount == 1)
down(&resource); //if you are the first reader then lock the resource
up(&rmutex); //release for other readers
up(&readLock); //Done with trying to access the resource
<CRITICAL Section>
//reading is performed
<EXIT Section>
down(&rmutex); //reserve exit section - avoids race condition with readers
readcount--; //indicate you're leaving
if (readcount == 0) //checks if you are last reader leaving
up(&resource); //if last, you must release the locked resource
up(&rmutex); //release exit section for other readers
}
//WRITER
void writer() {
<ENTRY Section>
down(&wmutex); //reserve entry section for writers - avoids race conditions
writecount++; //report yourself as a writer entering
if (writecount == 1) //checks if you're first writer
down(&readLock); //if you're first, then you must lock the readers out. Prevent them from trying to enter CS
up(&wmutex); //release entry section
<CRITICAL Section>
down(&resource); //reserve the resource for yourself - prevents other writers from simultaneously editing the shared resource
//writing is performed
up(&resource); //release file
<EXIT Section>
down(&wmutex); //reserve exit section
writecount--; //indicate you're leaving
if (writecount == 0) //checks if you're the last writer
up(&readLock); //if you're last writer, you must unlock the readers. Allows them to try enter CS for reading
up(&wmutex); //release exit section
}
```
We can observe that every reader is forced to acquire ReadLock. On the otherhand, writers doesnt need to lock individually. Once the first writer locks the ReadLock, it will be released only when there is no writer left in the queue.
From the both cases we observed that either reader or writer has to starve. Below solutionadds the constraint that no thread shall be allowed to starve; that is, the operation of obtaining a lock on the shared data will always terminate in a bounded amount of time.
```source-c
int readCount; // init to 0; number of readers currently accessing resource
// all semaphores initialised to 1
Semaphore resourceAccess; // controls access (read/write) to the resource
Semaphore readCountAccess; // for syncing changes to shared variable readCount
Semaphore serviceQueue; // FAIRNESS: preserves ordering of requests (signaling must be FIFO)
void writer()
{
down(&serviceQueue); // wait in line to be servicexs
// <ENTER>
down(&resourceAccess); // request exclusive access to resource
// </ENTER>
up(&serviceQueue); // let next in line be serviced
// <WRITE>
writeResource(); // writing is performed
// </WRITE>
// <EXIT>
up(&resourceAccess); // release resource access for next reader/writer
// </EXIT>
}
void reader()
{
down(&serviceQueue); // wait in line to be serviced
down(&readCountAccess); // request exclusive access to readCount
// <ENTER>
if (readCount == 0) // if there are no readers already reading:
down(&resourceAccess); // request resource access for readers (writers blocked)
readCount++; // update count of active readers
// </ENTER>
up(&serviceQueue); // let next in line be serviced
up(&readCountAccess); // release access to readCount
// <READ>
readResource(); // reading is performed
// </READ>
down(&readCountAccess); // request exclusive access to readCount
// <EXIT>
readCount--; // update count of active readers
if (readCount == 0) // if there are no readers left:
up(&resourceAccess); // release resource access for all
// </EXIT>
up(&readCountAccess); // release access to readCount
}
```
### 2. 哲学家进餐问题 ### 2. 哲学家进餐问题
<div align="center"> <img src="../pics//a9077f06-7584-4f2b-8c20-3a8e46928820.jpg"/> </div><br> <div align="center"> <img src="../pics//a9077f06-7584-4f2b-8c20-3a8e46928820.jpg"/> </div><br>
@ -588,7 +706,7 @@ FIFO 常用于客户-服务器应用程序中FIFO 用作汇聚点,在客户
# 三、死锁 # 三、死锁
## 死锁的必要条件 ## 必要条件
<div align="center"> <img src="../pics//c037c901-7eae-4e31-a1e4-9d41329e5c3e.png"/> </div><br> <div align="center"> <img src="../pics//c037c901-7eae-4e31-a1e4-9d41329e5c3e.png"/> </div><br>
@ -597,21 +715,30 @@ FIFO 常用于客户-服务器应用程序中FIFO 用作汇聚点,在客户
- 不可抢占:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显式地释放。 - 不可抢占:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显式地释放。
- 环路等待:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。 - 环路等待:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。
## 死锁的处理方法 ## 处理方法
### 1. 鸵鸟策略 主要有以下四种方法:
- 鸵鸟策略
- 死锁检测与死锁恢复
- 死锁预防
- 死锁避免
## 鸵鸟策略
把头埋在沙子里,假装根本没发生问题。 把头埋在沙子里,假装根本没发生问题。
因为解决死锁问题的代价很高,因此鸵鸟策略这种不采取任务措施的方案会获得更高的性能。当发生死锁时不会对用户造成多大影响,或发生死锁的概率很低,可以采用鸵鸟策略。 因为解决死锁问题的代价很高,因此鸵鸟策略这种不采取任务措施的方案会获得更高的性能。
当发生死锁时不会对用户造成多大影响,或发生死锁的概率很低,可以采用鸵鸟策略。
大多数操作系统,包括 UnixLinux 和 Windows处理死锁问题的办法仅仅是忽略它。 大多数操作系统,包括 UnixLinux 和 Windows处理死锁问题的办法仅仅是忽略它。
### 2. 死锁检测与死锁恢复 ## 死锁检测与死锁恢复
不试图阻止死锁,而是当检测到死锁发生时,采取措施进行恢复。 不试图阻止死锁,而是当检测到死锁发生时,采取措施进行恢复。
(一)每种类型一个资源的死锁检测 ### 1. 每种类型一个资源的死锁检测
<div align="center"> <img src="../pics//b1fa0453-a4b0-4eae-a352-48acca8fff74.png"/> </div><br> <div align="center"> <img src="../pics//b1fa0453-a4b0-4eae-a352-48acca8fff74.png"/> </div><br>
@ -621,7 +748,7 @@ FIFO 常用于客户-服务器应用程序中FIFO 用作汇聚点,在客户
每种类型一个资源的死锁检测算法是通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。 每种类型一个资源的死锁检测算法是通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。
(二)每种类型多个资源的死锁检测 ### 2. 每种类型多个资源的死锁检测
<div align="center"> <img src="../pics//e1eda3d5-5ec8-4708-8e25-1a04c5e11f48.png"/> </div><br> <div align="center"> <img src="../pics//e1eda3d5-5ec8-4708-8e25-1a04c5e11f48.png"/> </div><br>
@ -642,35 +769,35 @@ FIFO 常用于客户-服务器应用程序中FIFO 用作汇聚点,在客户
2. 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 1。 2. 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 1。
3. 如果没有这样一个进程,算法终止。 3. 如果没有这样一个进程,算法终止。
(三)死锁恢复 ### 3. 死锁恢复
- 利用抢占恢复 - 利用抢占恢复
- 利用回滚恢复 - 利用回滚恢复
- 通过杀死进程恢复 - 通过杀死进程恢复
### 3. 死锁预防 ## 死锁预防
在程序运行之前预防发生死锁。 在程序运行之前预防发生死锁。
(一)破坏互斥条件 ### 1. 破坏互斥条件
例如假脱机打印机技术允许若干个进程同时输出,唯一真正请求物理打印机的进程是打印机守护进程。 例如假脱机打印机技术允许若干个进程同时输出,唯一真正请求物理打印机的进程是打印机守护进程。
(二)破坏占有和等待条件 ### 2. 破坏占有和等待条件
一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。 一种实现方式是规定所有进程在开始执行前请求所需要的全部资源。
(三)破坏不可抢占条件 ### 3. 破坏不可抢占条件
(四)破坏环路等待 ### 4. 破坏环路等待
给资源统一编号,进程只能按编号顺序来请求资源。 给资源统一编号,进程只能按编号顺序来请求资源。
### 4. 死锁避免 ## 死锁避免
在程序运行时避免发生死锁。 在程序运行时避免发生死锁。
(一)安全状态 ### 1. 安全状态
<div align="center"> <img src="../pics//ed523051-608f-4c3f-b343-383e2d194470.png"/> </div><br> <div align="center"> <img src="../pics//ed523051-608f-4c3f-b343-383e2d194470.png"/> </div><br>
@ -680,7 +807,7 @@ FIFO 常用于客户-服务器应用程序中FIFO 用作汇聚点,在客户
安全状态的检测与死锁的检测类似,因为安全状态必须要求不能发生死锁。下面的银行家算法与死锁检测算法非常类似,可以结合着做参考对比。 安全状态的检测与死锁的检测类似,因为安全状态必须要求不能发生死锁。下面的银行家算法与死锁检测算法非常类似,可以结合着做参考对比。
(二)单个资源的银行家算法 ### 2. 单个资源的银行家算法
一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。 一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。
@ -688,7 +815,7 @@ FIFO 常用于客户-服务器应用程序中FIFO 用作汇聚点,在客户
上图 c 为不安全状态,因此算法会拒绝之前的请求,从而避免进入图 c 中的状态。 上图 c 为不安全状态,因此算法会拒绝之前的请求,从而避免进入图 c 中的状态。
(三)多个资源的银行家算法 ### 3. 多个资源的银行家算法
<div align="center"> <img src="../pics//62e0dd4f-44c3-43ee-bb6e-fedb9e068519.png"/> </div><br> <div align="center"> <img src="../pics//62e0dd4f-44c3-43ee-bb6e-fedb9e068519.png"/> </div><br>
@ -710,7 +837,7 @@ FIFO 常用于客户-服务器应用程序中FIFO 用作汇聚点,在客户
为了更好的管理内存,操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。当程序引用到不在物理内存中的页时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。 为了更好的管理内存,操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间,这个地址空间被分割成多个块,每一块称为一页。这些页被映射到物理内存,但不需要映射到连续的物理内存,也不需要所有页都必须在物理内存中。当程序引用到不在物理内存中的页时,由硬件执行必要的映射,将缺失的部分装入物理内存并重新执行失败的指令。
从上面的描述中可以看出,虚拟内存允许程序不用将地址空间中的每一页都映射到物理内存,也就是说一个程序不需要全部调入内存就可以运行,这使得有限的内存运行大程序为可能。例如有一台计算机可以产生 16 位地址,那么一个程序的地址空间范围是 0\~64K。该计算机只有 32KB 的物理内存,虚拟内存技术允许该计算机运行一个 64K 大小的程序。 从上面的描述中可以看出,虚拟内存允许程序不用将地址空间中的每一页都映射到物理内存,也就是说一个程序不需要全部调入内存就可以运行,这使得有限的内存运行大程序为可能。例如有一台计算机可以产生 16 位地址,那么一个程序的地址空间范围是 0\~64K。该计算机只有 32KB 的物理内存,虚拟内存技术允许该计算机运行一个 64K 大小的程序。
<div align="center"> <img src="../pics//7b281b1e-0595-402b-ae35-8c91084c33c1.png"/> </div><br> <div align="center"> <img src="../pics//7b281b1e-0595-402b-ae35-8c91084c33c1.png"/> </div><br>
@ -718,9 +845,9 @@ FIFO 常用于客户-服务器应用程序中FIFO 用作汇聚点,在客户
内存管理单元MMU管理着地址空间和物理内存的转换其中的页表Page table存储着页程序地址空间和页框物理内存空间的映射表。 内存管理单元MMU管理着地址空间和物理内存的转换其中的页表Page table存储着页程序地址空间和页框物理内存空间的映射表。
下图的页表存放着 16 个页,这 16 个页需要用 4 个比特位来进行索引定位,也就是存储页面号,剩下 12 个比特位存储偏移量。 一个虚拟地址分成两个部分,一部分存储页面号,一部分存储偏移量。
例如对于虚拟地址0010 000000000100前 4 位是存储页面号 2读取表项内容为110 1。该页在内存中,并且页框的地址为 110 000000000100 下图的页表存放着 16 个页,这 16 个页需要用 4 个比特位来进行索引定位。例如对于虚拟地址0010 000000000100前 4 位是存储页面号 2读取表项内容为110 1页表项最后一位表示是否存在于内存中1 表示存在。后 12 位存储偏移量。这个页对应的页框的地址为 110 000000000100
<div align="center"> <img src="../pics//cf4386a1-58c9-4eca-a17f-e12b1e9770eb.png" width="500"/> </div><br> <div align="center"> <img src="../pics//cf4386a1-58c9-4eca-a17f-e12b1e9770eb.png" width="500"/> </div><br>
@ -752,7 +879,7 @@ FIFO 常用于客户-服务器应用程序中FIFO 用作汇聚点,在客户
虽然无法知道将来要使用的页面情况但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出。 虽然无法知道将来要使用的页面情况但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出。
为了实现 LRU需要在内存中维护一个所有页面的链表。当一个页面被访问时将这个页面移到链表表头。这样就能保证链表表尾的页面最近最久未访问的。 为了实现 LRU需要在内存中维护一个所有页面的链表。当一个页面被访问时将这个页面移到链表表头。这样就能保证链表表尾的页面最近最久未访问的。
因为每次访问都需要更新链表,因此这种方式实现的 LRU 代价很高。 因为每次访问都需要更新链表,因此这种方式实现的 LRU 代价很高。
@ -791,12 +918,11 @@ FIFO 算法可能会把经常使用的页面置换出去,为了避免这一问
<div align="center"> <img src="../pics//ecf8ad5d-5403-48b9-b6e7-f2e20ffe8fca.png"/> </div><br> <div align="center"> <img src="../pics//ecf8ad5d-5403-48b9-b6e7-f2e20ffe8fca.png"/> </div><br>
### 6. 时钟 ### 6. 时钟
> Clock > Clock
第二次机会算法需要在链表中移动页面,降低了效率。时钟算法使用环形链表将页面接起来,再使用一个指针指向最老的页面。 第二次机会算法需要在链表中移动页面,降低了效率。时钟算法使用环形链表将页面接起来,再使用一个指针指向最老的页面。
<div align="center"> <img src="../pics//5f5ef0b6-98ea-497c-a007-f6c55288eab1.png"/> </div><br> <div align="center"> <img src="../pics//5f5ef0b6-98ea-497c-a007-f6c55288eab1.png"/> </div><br>
@ -863,7 +989,7 @@ FIFO 算法可能会把经常使用的页面置换出去,为了避免这一问
优先调度与当前磁头所在磁道距离最近的磁道。 优先调度与当前磁头所在磁道距离最近的磁道。
虽然平均寻道时间比较低,但是不够公平。如果新到达的磁道请求总是比一个在等待的磁道请求近,那么在等待的磁道请求会一直等待下去,也就是出现饥饿现象。具体来说,两的磁道请求更容易出现饥饿现象。 虽然平均寻道时间比较低,但是不够公平。如果新到达的磁道请求总是比一个在等待的磁道请求近,那么在等待的磁道请求会一直等待下去,也就是出现饥饿现象。具体来说,两的磁道请求更容易出现饥饿现象。
<div align="center"> <img src="../pics//4e2485e4-34bd-4967-9f02-0c093b797aaa.png"/> </div><br> <div align="center"> <img src="../pics//4e2485e4-34bd-4967-9f02-0c093b797aaa.png"/> </div><br>
@ -912,7 +1038,7 @@ gcc -o hello hello.c
## 静态链接 ## 静态链接
静态接器以一组可重定向目标文件为输入,生成一个完全链接的可执行目标文件作为输出。链接器主要完成以下两个任务: 静态接器以一组可重定向目标文件为输入,生成一个完全链接的可执行目标文件作为输出。链接器主要完成以下两个任务:
- 符号解析:每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的是将每个符号引用与一个符号定义关联起来。 - 符号解析:每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的是将每个符号引用与一个符号定义关联起来。
- 重定位:链接器通过把每个符号定义与一个内存位置关联起来,然后修改所有对这些符号的引用,使得它们指向这个内存位置。 - 重定位:链接器通过把每个符号定义与一个内存位置关联起来,然后修改所有对这些符号的引用,使得它们指向这个内存位置。

View File

@ -5,7 +5,7 @@
* [主机之间的通信方式](#主机之间的通信方式) * [主机之间的通信方式](#主机之间的通信方式)
* [电路交换与分组交换](#电路交换与分组交换) * [电路交换与分组交换](#电路交换与分组交换)
* [时延](#时延) * [时延](#时延)
* [计算机网络体系结构*](#计算机网络体系结构) * [计算机网络体系结构](#计算机网络体系结构)
* [二、物理层](#二物理层) * [二、物理层](#二物理层)
* [通信方式](#通信方式) * [通信方式](#通信方式)
* [带通调制](#带通调制) * [带通调制](#带通调制)
@ -13,14 +13,14 @@
* [基本问题](#基本问题) * [基本问题](#基本问题)
* [信道分类](#信道分类) * [信道分类](#信道分类)
* [信道复用技术](#信道复用技术) * [信道复用技术](#信道复用技术)
* [CSMA/CD 协议*](#csmacd-协议) * [CSMA/CD 协议](#csmacd-协议)
* [PPP 协议](#ppp-协议) * [PPP 协议](#ppp-协议)
* [MAC 地址](#mac-地址) * [MAC 地址](#mac-地址)
* [局域网](#局域网) * [局域网](#局域网)
* [以太网*](#以太网) * [以太网](#以太网)
* [交换机*](#交换机) * [交换机](#交换机)
* [虚拟局域网](#虚拟局域网) * [虚拟局域网](#虚拟局域网)
* [四、网络层*](#四网络层) * [四、网络层](#四网络层)
* [概述](#概述) * [概述](#概述)
* [IP 数据报格式](#ip-数据报格式) * [IP 数据报格式](#ip-数据报格式)
* [IP 地址编址方式](#ip-地址编址方式) * [IP 地址编址方式](#ip-地址编址方式)
@ -31,7 +31,7 @@
* [路由器的结构](#路由器的结构) * [路由器的结构](#路由器的结构)
* [路由器分组转发流程](#路由器分组转发流程) * [路由器分组转发流程](#路由器分组转发流程)
* [路由选择协议](#路由选择协议) * [路由选择协议](#路由选择协议)
* [五、运输层*](#五运输层) * [五、传输层](#五传输层)
* [UDP 和 TCP 的特点](#udp-和-tcp-的特点) * [UDP 和 TCP 的特点](#udp-和-tcp-的特点)
* [UDP 首部格式](#udp-首部格式) * [UDP 首部格式](#udp-首部格式)
* [TCP 首部格式](#tcp-首部格式) * [TCP 首部格式](#tcp-首部格式)
@ -59,7 +59,7 @@
网络把主机连接起来,而互联网是把多种不同的网络连接起来,因此互联网是网络的网络。 网络把主机连接起来,而互联网是把多种不同的网络连接起来,因此互联网是网络的网络。
<div align="center"> <img src="../pics//network-of-networks.gif" width="500"/> </div><br> <div align="center"> <img src="../pics//network-of-networks.gif" width="450"/> </div><br>
## ISP ## ISP
@ -67,9 +67,7 @@
<div align="center"> <img src="../pics//46cec213-3048-4a80-aded-fdd577542801.jpg" width="500"/> </div><br> <div align="center"> <img src="../pics//46cec213-3048-4a80-aded-fdd577542801.jpg" width="500"/> </div><br>
目前的互联网是一种多层次 ISP 结构ISP 根据覆盖面积的大小分为第一层 ISP、区域 ISP 和接入 ISP。 目前的互联网是一种多层次 ISP 结构ISP 根据覆盖面积的大小分为第一层 ISP、区域 ISP 和接入 ISP。互联网交换点 IXP 允许两个 ISP 直接相连而不用经过第三个 ISP。
互联网交换点 IXP 允许两个 ISP 直接相连而不用经过第三个 ISP。
<div align="center"> <img src="../pics//168e893c-e4a0-4ba4-b81f-9d993483abd0.jpg" width="500"/> </div><br> <div align="center"> <img src="../pics//168e893c-e4a0-4ba4-b81f-9d993483abd0.jpg" width="500"/> </div><br>
@ -123,19 +121,19 @@
分组在路由器的输入队列和输出队列中排队等待的时间,取决于网络当前的通信量。 分组在路由器的输入队列和输出队列中排队等待的时间,取决于网络当前的通信量。
## 计算机网络体系结构* ## 计算机网络体系结构
<div align="center"> <img src="../pics//426df589-6f97-4622-b74d-4a81fcb1da8e.png" width="800"/> </div><br> <div align="center"> <img src="../pics//426df589-6f97-4622-b74d-4a81fcb1da8e.png" width="600"/> </div><br>
### 1. 五层协议 ### 1. 五层协议
- **应用层** :为特定应用程序提供数据传输服务,例如 HTTP、DNS 等。数据单位为报文。 - **应用层** :为特定应用程序提供数据传输服务,例如 HTTP、DNS 等。数据单位为报文。
- **运输层** :提供的是进程间的通用数据传输服务。由于应用层协议很多,定义通用的运输层协议就可以支持不断增多的应用层协议。运输层包括两种协议:传输控制协议 TCP提供面向连接、可靠的数据传输服务数据单位为报文段用户数据报协议 UDP提供无连接、尽最大努力的数据传输服务数据单位为用户数据报。TCP 主要提供完整性服务UDP 主要提供及时性服务。 - **传输层** :为进程提供通用数据传输服务。由于应用层协议很多,定义通用的传输层协议就可以支持不断增多的应用层协议。运输层包括两种协议:传输控制协议 TCP提供面向连接、可靠的数据传输服务数据单位为报文段用户数据报协议 UDP提供无连接、尽最大努力的数据传输服务数据单位为用户数据报。TCP 主要提供完整性服务UDP 主要提供及时性服务。
- **网络层** :为主机间提供数据传输服务,而运输层协议是为主机中的进程提供服务。网络层把运输层传递下来的报文段或者用户数据报封装成分组。 - **网络层** :为主机提供数据传输服务。而传输层协议是为主机中的进程提供数据传输服务。网络层把传输层传递下来的报文段或者用户数据报封装成分组。
- **数据链路层** :网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为同一链路的主机提供服务。数据链路层把网络层传下来的分组封装成帧。 - **数据链路层** :网络层针对的还是主机之间的数据传输服务,而主机之间可以有很多链路,链路层协议就是为同一链路的主机提供数据传输服务。数据链路层把网络层传下来的分组封装成帧。
- **物理层** :考虑的是怎样在传输媒体上传输数据比特流,而不是指具体的传输媒体。物理层的作用是尽可能屏蔽传输媒体和通信手段的差异,使数据链路层感觉不到这些差异。 - **物理层** :考虑的是怎样在传输媒体上传输数据比特流,而不是指具体的传输媒体。物理层的作用是尽可能屏蔽传输媒体和通信手段的差异,使数据链路层感觉不到这些差异。
@ -143,7 +141,7 @@
其中表示层和会话层用途如下: 其中表示层和会话层用途如下:
- **表示层** :数据压缩、加密以及数据描述,这使得应用程序不必心在各台主机中数据内部格式不同的问题。 - **表示层** :数据压缩、加密以及数据描述,这使得应用程序不必心在各台主机中数据内部格式不同的问题。
- **会话层** :建立及管理会话。 - **会话层** :建立及管理会话。
@ -157,7 +155,7 @@ TCP/IP 体系结构不严格遵循 OSI 分层概念,应用层可能会直接
<div align="center"> <img src="../pics//45e0e0bf-386d-4280-a341-a0b9496c7674.png" width="400"/> </div><br> <div align="center"> <img src="../pics//45e0e0bf-386d-4280-a341-a0b9496c7674.png" width="400"/> </div><br>
TCP/IP 协议族是一种沙漏形状中间小两边大IP 协议在其中占举足轻重的地位。 TCP/IP 协议族是一种沙漏形状中间小两边大IP 协议在其中占举足轻重的地位。
<div align="center"> <img src="../pics//d4eef1e2-5703-4ca4-82ab-8dda93d6b81f.png" width="500"/> </div><br> <div align="center"> <img src="../pics//d4eef1e2-5703-4ca4-82ab-8dda93d6b81f.png" width="500"/> </div><br>
@ -165,7 +163,7 @@ TCP/IP 协议族是一种沙漏形状中间小两边大IP 协议在其中
在向下的过程中,需要添加下层协议所需要的首部或者尾部,而在向上的过程中不断拆开首部和尾部。 在向下的过程中,需要添加下层协议所需要的首部或者尾部,而在向上的过程中不断拆开首部和尾部。
路由器只有下面三层协议,因为路由器位于网络核心中,不需要为进程或者应用程序提供服务,因此也就不需要输层和应用层。 路由器只有下面三层协议,因为路由器位于网络核心中,不需要为进程或者应用程序提供服务,因此也就不需要输层和应用层。
<div align="center"> <img src="../pics//ac106e7e-489a-4082-abd9-dabebe48394c.jpg" width="800"/> </div><br> <div align="center"> <img src="../pics//ac106e7e-489a-4082-abd9-dabebe48394c.jpg" width="800"/> </div><br>
@ -273,7 +271,7 @@ TCP/IP 协议族是一种沙漏形状中间小两边大IP 协议在其中
<div align="center"> <img src="../pics//92ad9bae-7d02-43ba-8115-a9d6f530ca28.png" width="600"/> </div><br> <div align="center"> <img src="../pics//92ad9bae-7d02-43ba-8115-a9d6f530ca28.png" width="600"/> </div><br>
## CSMA/CD 协议* ## CSMA/CD 协议
CSMA/CD 表示载波监听多点接入 / 碰撞检测。 CSMA/CD 表示载波监听多点接入 / 碰撞检测。
@ -306,7 +304,7 @@ PPP 的帧格式:
MAC 地址是链路层地址,长度为 6 字节48 位),用于唯一标识网络适配器(网卡)。 MAC 地址是链路层地址,长度为 6 字节48 位),用于唯一标识网络适配器(网卡)。
一台主机拥有多少个适配器就有多少个 MAC 地址。例如笔记本电脑普遍存在无线网络适配器和有线网络适配器,因此就有两个 MAC 地址。 一台主机拥有多少个网络适配器就有多少个 MAC 地址。例如笔记本电脑普遍存在无线网络适配器和有线网络适配器,因此就有两个 MAC 地址。
## 局域网 ## 局域网
@ -318,11 +316,11 @@ MAC 地址是链路层地址,长度为 6 字节48 位),用于唯一标
<div align="center"> <img src="../pics//a6026bb4-3daf-439f-b1ec-a5a24e19d2fb.jpg" width="600"/> </div><br> <div align="center"> <img src="../pics//a6026bb4-3daf-439f-b1ec-a5a24e19d2fb.jpg" width="600"/> </div><br>
## 以太网* ## 以太网
以太网是一种星型拓扑结构局域网。 以太网是一种星型拓扑结构局域网。
早期使用集线器进行连接,集线器是一种物理层设备,作用于比特而不是帧,当一个比特到达接口时,集线器重新生成这个比特,并将其能量强度放大,从而扩大网络的传输距离,之后再将这个比特发送到其它所有接口。如果集线器同时收到同时从两个不同接口的帧,那么就发生了碰撞。 早期使用集线器进行连接,集线器是一种物理层设备, 作用于比特而不是帧,当一个比特到达接口时,集线器重新生成这个比特,并将其能量强度放大,从而扩大网络的传输距离,之后再将这个比特发送到其它所有接口。如果集线器同时收到两个不同接口的帧,那么就发生了碰撞。
目前以太网使用交换机替代了集线器,交换机是一种链路层设备,它不会发生碰撞,能根据 MAC 地址进行存储转发。 目前以太网使用交换机替代了集线器,交换机是一种链路层设备,它不会发生碰撞,能根据 MAC 地址进行存储转发。
@ -335,7 +333,7 @@ MAC 地址是链路层地址,长度为 6 字节48 位),用于唯一标
<div align="center"> <img src="../pics//50d38e84-238f-4081-8876-14ef6d7938b5.jpg" width="600"/> </div><br> <div align="center"> <img src="../pics//50d38e84-238f-4081-8876-14ef6d7938b5.jpg" width="600"/> </div><br>
## 交换机* ## 交换机
交换机具有自学习能力,学习的是交换表的内容,交换表中存储着 MAC 地址到接口的映射。 交换机具有自学习能力,学习的是交换表的内容,交换表中存储着 MAC 地址到接口的映射。
@ -355,7 +353,7 @@ MAC 地址是链路层地址,长度为 6 字节48 位),用于唯一标
<div align="center"> <img src="../pics//a74b70ac-323a-4b31-b4d5-90569b8a944b.png" width="500"/> </div><br> <div align="center"> <img src="../pics//a74b70ac-323a-4b31-b4d5-90569b8a944b.png" width="500"/> </div><br>
# 四、网络层* # 四、网络层
## 概述 ## 概述
@ -490,7 +488,7 @@ Traceroute 发送的 IP 数据报封装的是无法交付的 UDP 用户数据报
- 172.16.0.0 \~ 172.31.255.255 - 172.16.0.0 \~ 172.31.255.255
- 192.168.0.0 \~ 192.168.255.255 - 192.168.0.0 \~ 192.168.255.255
VPN 使用公用的互联网作为本机构各专用网之间的通信载体。专用指机构内的主机只与本机构内的其它主机通信;虚拟指好像是,而实际上并不是,它有经过公用的互联网。 VPN 使用公用的互联网作为本机构各专用网之间的通信载体。专用指机构内的主机只与本机构内的其它主机通信;虚拟指好像是,而实际上并不是,它有经过公用的互联网。
下图中,场所 A 和 B 的通信经过互联网,如果场所 A 的主机 X 要和另一个场所 B 的主机 Y 通信IP 数据报的源地址是 10.1.0.1,目的地址是 10.2.0.3。数据报先发送到与互联网相连的路由器 R1R1 对内部数据进行加密,然后重新加上数据报的首部,源地址是路由器 R1 的全球地址 125.1.2.3,目的地址是路由器 R2 的全球地址 194.4.5.6。路由器 R2 收到数据报后将数据部分进行解密,恢复原来的数据报,此时目的地址为 10.2.0.3,就交付给 Y。 下图中,场所 A 和 B 的通信经过互联网,如果场所 A 的主机 X 要和另一个场所 B 的主机 Y 通信IP 数据报的源地址是 10.1.0.1,目的地址是 10.2.0.3。数据报先发送到与互联网相连的路由器 R1R1 对内部数据进行加密,然后重新加上数据报的首部,源地址是路由器 R1 的全球地址 125.1.2.3,目的地址是路由器 R2 的全球地址 194.4.5.6。路由器 R2 收到数据报后将数据部分进行解密,恢复原来的数据报,此时目的地址为 10.2.0.3,就交付给 Y。
@ -500,7 +498,7 @@ VPN 使用公用的互联网作为本机构各专用网之间的通信载体。
专用网内部的主机使用本地 IP 地址又想和互联网上的主机通信时,可以使用 NAT 来将本地 IP 转换为全球 IP。 专用网内部的主机使用本地 IP 地址又想和互联网上的主机通信时,可以使用 NAT 来将本地 IP 转换为全球 IP。
在以前NAT 将本地 IP 和全球 IP 一一对应,这种方式下拥有 n 个全球 IP 地址的专用网内最多只可以同时有 n 台主机接入互联网。为了更有效地利用全球 IP 地址,现在常用的 NAT 转换表把输层的端口号也用上了,使得多个专用网内部的主机共用一个全球 IP 地址。使用端口号的 NAT 也叫做网络地址与端口转换 NAPT。 在以前NAT 将本地 IP 和全球 IP 一一对应,这种方式下拥有 n 个全球 IP 地址的专用网内最多只可以同时有 n 台主机接入互联网。为了更有效地利用全球 IP 地址,现在常用的 NAT 转换表把输层的端口号也用上了,使得多个专用网内部的主机共用一个全球 IP 地址。使用端口号的 NAT 也叫做网络地址与端口转换 NAPT。
<div align="center"> <img src="../pics//2719067e-b299-4639-9065-bed6729dbf0b.png" width=""/> </div><br> <div align="center"> <img src="../pics//2719067e-b299-4639-9065-bed6729dbf0b.png" width=""/> </div><br>
@ -580,9 +578,9 @@ BGP 只能寻找一条比较好的路由,而不是最佳路由。
<div align="center"> <img src="../pics//9cd0ae20-4fb5-4017-a000-f7d3a0eb3529.png" width="600"/> </div><br> <div align="center"> <img src="../pics//9cd0ae20-4fb5-4017-a000-f7d3a0eb3529.png" width="600"/> </div><br>
# 五、运输层* # 五、传输层
网络层只把分组发送到目的主机,但是真正通信的并不是主机而是主机中的进程。运输层提供了进程间的逻辑通信,运输层向高层用户屏蔽了下面网络层的核心细节,使应用程序看起来像是在两个输层实体之间有一条端到端的逻辑通信信道。 网络层只把分组发送到目的主机,但是真正通信的并不是主机而是主机中的进程。传输层提供了进程间的逻辑通信,传输层向高层用户屏蔽了下面网络层的核心细节,使应用程序看起来像是在两个输层实体之间有一条端到端的逻辑通信信道。
## UDP 和 TCP 的特点 ## UDP 和 TCP 的特点
@ -700,7 +698,7 @@ TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文
<div align="center"> <img src="../pics//51e2ed95-65b8-4ae9-8af3-65602d452a25.jpg" width="500"/> </div><br> <div align="center"> <img src="../pics//51e2ed95-65b8-4ae9-8af3-65602d452a25.jpg" width="500"/> </div><br>
TCP 主要通过四算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。 TCP 主要通过四算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。
发送方需要维护一个叫做拥塞窗口cwnd的状态变量注意拥塞窗口与发送方窗口的区别拥塞窗口只是一个状态变量实际决定发送方能发送多少数据的是发送方窗口。 发送方需要维护一个叫做拥塞窗口cwnd的状态变量注意拥塞窗口与发送方窗口的区别拥塞窗口只是一个状态变量实际决定发送方能发送多少数据的是发送方窗口。
@ -713,11 +711,11 @@ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、
### 1. 慢开始与拥塞避免 ### 1. 慢开始与拥塞避免
发送的最初执行慢开始,令 cwnd=1发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍因此之后发送方能够发送的报文段数量为2、4、8 ... 发送的最初执行慢开始,令 cwnd = 1发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍因此之后发送方能够发送的报文段数量为2、4、8 ...
注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能也就更高。设置一个慢开始门限 ssthresh当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。 注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能也就更高。设置一个慢开始门限 ssthresh当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。
如果出现了超时,则令 ssthresh = cwnd/2然后重新执行慢开始。 如果出现了超时,则令 ssthresh = cwnd / 2然后重新执行慢开始。
### 2. 快重传与快恢复 ### 2. 快重传与快恢复
@ -725,7 +723,7 @@ TCP 主要通过四种算法来进行拥塞控制:慢开始、拥塞避免、
在发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。例如收到三个 M<sub>2</sub>,则 M<sub>3</sub> 丢失,立即重传 M<sub>3</sub> 在发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。例如收到三个 M<sub>2</sub>,则 M<sub>3</sub> 丢失,立即重传 M<sub>3</sub>
在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此执行快恢复,令 ssthresh = cwnd/2 cwnd = ssthresh注意到此时直接进入拥塞避免。 在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此执行快恢复,令 ssthresh = cwnd / 2 cwnd = ssthresh注意到此时直接进入拥塞避免。
慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1而快恢复 cwnd 设定为 ssthresh。 慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1而快恢复 cwnd 设定为 ssthresh。
@ -743,8 +741,8 @@ DNS 是一个分布式数据库,提供了主机名和 IP 地址之间相互转
DNS 可以使用 UDP 或者 TCP 进行传输,使用的端口号都为 53。大多数情况下 DNS 使用 UDP 进行传输,这就要求域名解析器和域名服务器都必须自己处理超时和重传来保证可靠性。在两种情况下会使用 TCP 进行传输: DNS 可以使用 UDP 或者 TCP 进行传输,使用的端口号都为 53。大多数情况下 DNS 使用 UDP 进行传输,这就要求域名解析器和域名服务器都必须自己处理超时和重传来保证可靠性。在两种情况下会使用 TCP 进行传输:
- 如果返回的响应超过的 512 字节就改用 TCP 进行传输UDP 最大只支持 512 字节的数据)。 - 如果返回的响应超过的 512 字节UDP 最大只支持 512 字节的数据)。
- 区域传送需要使用 TCP 进行传输(区域传送是主域名服务器向辅助域名服务器传送变化的那部分数据)。 - 区域传送(区域传送是主域名服务器向辅助域名服务器传送变化的那部分数据)。
## 文件传送协议 ## 文件传送协议
@ -806,11 +804,11 @@ POP3 的特点是只要用户从服务器上读取了邮件,就把该邮件删
### 3. IMAP ### 3. IMAP
IMAP 协议中客户端和服务器上的邮件保持同步,如果不手动删除邮件那么服务器上的邮件也不会被删除。IMAP 这种做法可以让用户随时随地去访问服务器上的邮件。 IMAP 协议中客户端和服务器上的邮件保持同步如果不手动删除邮件那么服务器上的邮件也不会被删除。IMAP 这种做法可以让用户随时随地去访问服务器上的邮件。
## 常用端口 ## 常用端口
|应用| 应用层协议 | 端口号 | 输层协议 | 备注 | |应用| 应用层协议 | 端口号 | 输层协议 | 备注 |
| :---: | :--: | :--: | :--: | :--: | :---: | :--: | :--: | :--: | :--:
| 域名解析 | DNS | 53 | UDP/TCP | 长度超过 512 字节时使用 TCP | | 域名解析 | DNS | 53 | UDP/TCP | 长度超过 512 字节时使用 TCP |
| 动态主机配置协议 | DHCP | 67/68 | UDP | | | 动态主机配置协议 | DHCP | 67/68 | UDP | |

View File

@ -44,11 +44,11 @@
## 1. 单例Singleton ## 1. 单例Singleton
### 意图 ### Intent
确保一个类只有一个实例,并提供该实例的全局访问点。 确保一个类只有一个实例,并提供该实例的全局访问点。
### 类图 ### Class Diagram
使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。 使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。
@ -56,13 +56,13 @@
<div align="center"> <img src="../pics//562f2844-d77c-40e0-887a-28a7128abd42.png"/> </div><br> <div align="center"> <img src="../pics//562f2844-d77c-40e0-887a-28a7128abd42.png"/> </div><br>
### 实现 ### Implementation
(一)懒汉式-线程不安全 #### 懒汉式-线程不安全
以下实现中,私有静态变量 uniqueInstance 被延迟实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance从而节约资源。 以下实现中,私有静态变量 uniqueInstance 被延迟实例化,这样做的好处是,如果没有用到该类,那么就不会实例化 uniqueInstance从而节约资源。
这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 `if (uniqueInstance == null)` ,并且此时 uniqueInstance 为 null那么多个线程执行 `uniqueInstance = new Singleton();` 语句,这将导致多次实例化 uniqueInstance。 这个实现在多线程环境下是不安全的,如果多个线程能够同时进入 `if (uniqueInstance == null)` ,并且此时 uniqueInstance 为 null那么会有多个线程执行 `uniqueInstance = new Singleton();` 语句,这将导致实例化多次 uniqueInstance。
```java ```java
public class Singleton { public class Singleton {
@ -81,11 +81,21 @@ public class Singleton {
} }
``` ```
(二)懒汉式-线程安全 #### Ⅱ 饿汉式-线程安全
只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了对 uniqueInstance 进行多次实例化的问题。 线程不安全问题主要是由于 uniqueInstance 被实例化多次,采取直接实例化 uniqueInstance 的方式就不会产生线程不安全问题。
但是这样有一个问题,就是当一个线程进入该方法之后,其它线程试图进入该方法都必须等待,因此性能上有一定的损耗。 但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。
```java
private static Singleton uniqueInstance = new Singleton();
```
#### Ⅲ 懒汉式-线程安全
只需要对 getUniqueInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了实例化多次 uniqueInstance。
但是当一个线程进入该方法之后,其它试图进入该方法的线程都必须等待,即使 uniqueInstance 已经被实例化了。这会让线程阻塞时间过长,因此该方法有性能问题,不推荐使用。
```java ```java
public static synchronized Singleton getUniqueInstance() { public static synchronized Singleton getUniqueInstance() {
@ -96,17 +106,7 @@ public static synchronized Singleton getUniqueInstance() {
} }
``` ```
(三)饿汉式-线程安全 #### Ⅳ 双重校验锁-线程安全
线程不安全问题主要是由于 uniqueInstance 被实例化了多次,如果 uniqueInstance 采用直接实例化的话,就不会被实例化多次,也就不会产生线程不安全问题。
但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。
```java
private static Singleton uniqueInstance = new Singleton();
```
(四)双重校验锁-线程安全
uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。 uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。
@ -133,7 +133,7 @@ public class Singleton {
} }
``` ```
考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程同时执行 if 语句,那么两个线程就会同时进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 `uniqueInstance = new Singleton();` 这条语句,只是先后的问题,那么就会进行两次实例化,从而产生了两个实例。因此必须使用双重校验锁,也就是需要使用两个 if 语句。 考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程都执行了 if 语句,那么两个线程都会进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 `uniqueInstance = new Singleton();` 这条语句,只是先后的问题,那么就会进行两次实例化。因此必须使用双重校验锁,也就是需要使用两个 if 语句。
```java ```java
if (uniqueInstance == null) { if (uniqueInstance == null) {
@ -143,21 +143,21 @@ if (uniqueInstance == null) {
} }
``` ```
uniqueInstance 采用 volatile 关键字修饰也是很有必要的`uniqueInstance = new Singleton();` 这段代码其实是分为三步执行。 uniqueInstance 采用 volatile 关键字修饰也是很有必要的 `uniqueInstance = new Singleton();` 这段代码其实是分为三步执行:
1. 分配内存空间 1. 为 uniqueInstance 分配内存空间
2. 初始化对象 2. 初始化 uniqueInstance
3. 将 uniqueInstance 指向分配的内存地址 3. 将 uniqueInstance 指向分配的内存地址
但是由于 JVM 具有指令重排的特性,有可能执行顺序变为了 1>3>2这在单线程情况下自然是没有问题。但如果是多线程下有可能获得是一个还没有被初始化的实例以致于程序出错 但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出先问题但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如线程 T<sub>1</sub> 执行了 1 和 3此时 T<sub>2</sub> 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回 uniqueInstance但此时 uniqueInstance 还未被初始化
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。 使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
(五)静态内部类实现 #### 静态内部类实现
当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 `getUniqueInstance()` 方法从而触发 `SingletonHolder.INSTANCE` 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例。 当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 `getUniqueInstance()` 方法从而触发 `SingletonHolder.INSTANCE` 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次
这种方式不仅具有延迟初始化的好处,而且由虚拟机提供了对线程安全的支持。 这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。
```java ```java
public class Singleton { public class Singleton {
@ -175,40 +175,62 @@ public class Singleton {
} }
``` ```
(五)枚举实现 #### Ⅵ 枚举实现
这是单例模式的最佳实践,它实现简单,并且在面对复杂的序列化或者反射攻击的时候,能够防止实例化多次。
```java ```java
public enum Singleton { public enum Singleton {
uniqueInstance;
}
```
考虑以下单例模式的实现,该 Singleton 在每次序列化的时候都会创建一个新的实例,为了保证只创建一个实例,必须声明所有字段都是 transient并且提供一个 readResolve() 方法。 INSTANCE;
```java private String objName;
public class Singleton implements Serializable {
private static Singleton uniqueInstance;
private Singleton() { public String getObjName() {
return objName;
} }
public static synchronized Singleton getUniqueInstance() {
if (uniqueInstance == null) { public void setObjName(String objName) {
uniqueInstance = new Singleton(); this.objName = objName;
}
public static void main(String[] args) {
// 单例测试
Singleton firstSingleton = Singleton.INSTANCE;
firstSingleton.setObjName("firstName");
System.out.println(firstSingleton.getObjName());
Singleton secondSingleton = Singleton.INSTANCE;
secondSingleton.setObjName("secondName");
System.out.println(firstSingleton.getObjName());
System.out.println(secondSingleton.getObjName());
// 反射获取实例测试
try {
Singleton[] enumConstants = Singleton.class.getEnumConstants();
for (Singleton enumConstant : enumConstants) {
System.out.println(enumConstant.getObjName());
}
} catch (Exception e) {
e.printStackTrace();
} }
return uniqueInstance;
} }
} }
``` ```
如果不使用枚举来实现单例模式,会出现反射攻击,因为通过 setAccessible() 方法可以将私有构造函数的访问级别设置为 public然后调用构造函数从而实例化对象。如果要防止这种攻击需要在构造函数中添加防止实例化第二个对象的代码。 ```html
firstName
secondName
secondName
secondName
```
从上面的讨论可以看出,解决序列化和反射攻击很麻烦,而枚举实现不会出现这两种问题,所以说枚举实现单例模式是最佳实践。 该实现在多次序列化再进行反序列化之后,不会得到多个实例。而其它实现需要使用 transient 修饰所有字段,并且实现序列化和反序列化的方法
### 使用场景 该实现可以防止反射攻击。在其它实现中,通过 setAccessible() 方法可以将私有构造函数的访问级别设置为 public然后调用构造函数从而实例化对象如果要防止这种攻击需要在构造函数中添加防止多次实例化的代码。该实现是由 JVM 保证只会实例化一次,因此不会出现上述的反射攻击。
### Examples
- Logger Classes - Logger Classes
- Configuration Classes - Configuration Classes
@ -223,37 +245,19 @@ public class Singleton implements Serializable {
## 2. 简单工厂Simple Factory ## 2. 简单工厂Simple Factory
### 意图 ### Intent
在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。 在创建一个对象时不向客户暴露内部细节,并提供一个创建对象的通用接口。
### 类图 ### Class Diagram
简单工厂不是设计模式,更像是一种编程习惯。它把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。 简单工厂把实例化的操作单独放到一个类中,这个类就成为简单工厂类,让简单工厂类来决定应该用哪个具体子类来实例化。
这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。客户类往往有多个,如果不使用简单工厂,那么所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。
<div align="center"> <img src="../pics//c79da808-0f28-4a36-bc04-33ccc5b83c13.png"/> </div><br> <div align="center"> <img src="../pics//c79da808-0f28-4a36-bc04-33ccc5b83c13.png"/> </div><br>
这样做能把客户类和具体子类的实现解耦,客户类不再需要知道有哪些子类以及应当实例化哪个子类。因为客户类往往有多个,如果不使用简单工厂,所有的客户类都要知道所有子类的细节。而且一旦子类发生改变,例如增加子类,那么所有的客户类都要进行修改。 ### Implementation
如果存在下面这种代码,就需要使用简单工厂将对象实例化的部分放到简单工厂中。
```java
public class Client {
public static void main(String[] args) {
int type = 1;
Product product;
if (type == 1) {
product = new ConcreteProduct1();
} else if (type == 2) {
product = new ConcreteProduct2();
} else {
product = new ConcreteProduct();
}
}
}
```
### 实现
```java ```java
public interface Product { public interface Product {
@ -275,8 +279,31 @@ public class ConcreteProduct2 implements Product {
} }
``` ```
以下的 Client 类包含了实例化的代码,这是一种错误的实现。如果在客户类中存在这种实例化代码,就需要考虑将代码放到简单工厂中。
```java
public class Client {
public static void main(String[] args) {
int type = 1;
Product product;
if (type == 1) {
product = new ConcreteProduct1();
} else if (type == 2) {
product = new ConcreteProduct2();
} else {
product = new ConcreteProduct();
}
// do something with the product
}
}
```
以下的 SimpleFactory 是简单工厂实现,它被所有需要进行实例化的客户类调用。
```java ```java
public class SimpleFactory { public class SimpleFactory {
public Product createProduct(int type) { public Product createProduct(int type) {
if (type == 1) { if (type == 1) {
return new ConcreteProduct1(); return new ConcreteProduct1();
@ -290,20 +317,22 @@ public class SimpleFactory {
```java ```java
public class Client { public class Client {
public static void main(String[] args) { public static void main(String[] args) {
SimpleFactory simpleFactory = new SimpleFactory(); SimpleFactory simpleFactory = new SimpleFactory();
Product product = simpleFactory.createProduct(1); Product product = simpleFactory.createProduct(1);
// do something with the product
} }
} }
``` ```
## 3. 工厂方法Factory Method ## 3. 工厂方法Factory Method
### 意图 ### Intent
定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。 定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。
### 类图 ### Class Diagram
在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。 在简单工厂中,创建对象的是另一个类,而在工厂方法中,是由子类来创建对象。
@ -311,7 +340,7 @@ public class Client {
<div align="center"> <img src="../pics//1818e141-8700-4026-99f7-900a545875f5.png"/> </div><br> <div align="center"> <img src="../pics//1818e141-8700-4026-99f7-900a545875f5.png"/> </div><br>
### 实现 ### Implementation
```java ```java
public abstract class Factory { public abstract class Factory {
@ -359,11 +388,11 @@ public class ConcreteFactory2 extends Factory {
## 4. 抽象工厂Abstract Factory ## 4. 抽象工厂Abstract Factory
### 意图 ### Intent
提供一个接口,用于创建 **相关的对象家族** 提供一个接口,用于创建 **相关的对象家族**
### 类图 ### Class Diagram
抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。 抽象工厂模式创建的是对象家族,也就是很多对象而不是一个对象,并且这些对象是相关的,也就是说必须一起创建出来。而工厂方法模式只是用于创建一个对象,这和抽象工厂模式有很大不同。
@ -375,7 +404,7 @@ public class ConcreteFactory2 extends Factory {
<div align="center"> <img src="../pics//8668a3e1-c9c7-4fcb-98b2-a96a5d841579.png"/> </div><br> <div align="center"> <img src="../pics//8668a3e1-c9c7-4fcb-98b2-a96a5d841579.png"/> </div><br>
### 代码实现 ### Implementation
```java ```java
public class AbstractProductA { public class AbstractProductA {
@ -457,15 +486,15 @@ public class Client {
## 5. 生成器Builder ## 5. 生成器Builder
### 意图 ### Intent
封装一个对象的构造过程,并允许按步骤构造。 封装一个对象的构造过程,并允许按步骤构造。
### 类图 ### Class Diagram
<div align="center"> <img src="../pics//13b0940e-d1d7-4b17-af4f-b70cb0a75e08.png"/> </div><br> <div align="center"> <img src="../pics//13b0940e-d1d7-4b17-af4f-b70cb0a75e08.png"/> </div><br>
### 实现 ### Implementation
以下是一个简易的 StringBuilder 实现,参考了 JDK 1.8 源码。 以下是一个简易的 StringBuilder 实现,参考了 JDK 1.8 源码。
@ -547,15 +576,15 @@ abcdefghijklmnopqrstuvwxyz
## 6. 原型模式Prototype ## 6. 原型模式Prototype
### 意图 ### Intent
使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象。 使用原型实例指定要创建对象的类型,通过复制这个原型来创建新对象。
### 类图 ### Class Diagram
<div align="center"> <img src="../pics//a40661e4-1a71-46d2-a158-ff36f7fc3331.png"/> </div><br> <div align="center"> <img src="../pics//a40661e4-1a71-46d2-a158-ff36f7fc3331.png"/> </div><br>
### 实现 ### Implementation
```java ```java
public abstract class Prototype { public abstract class Prototype {
@ -606,39 +635,44 @@ abc
## 1. 责任链Chain Of Responsibility ## 1. 责任链Chain Of Responsibility
### 意图 ### Intent
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止。 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链发送该请求,直到有一个对象处理它为止。
### 类图 ### Class Diagram
- Handler定义处理请求的接口并且实现后继链successor - Handler定义处理请求的接口并且实现后继链successor
<div align="center"> <img src="../pics//691f11eb-31a7-46be-9de1-61f433c4b3c7.png"/> </div><br> <div align="center"> <img src="../pics//691f11eb-31a7-46be-9de1-61f433c4b3c7.png"/> </div><br>
### 实现 ### Implementation
```java ```java
public abstract class Handler { public abstract class Handler {
protected Handler successor; protected Handler successor;
public Handler(Handler successor) { public Handler(Handler successor) {
this.successor = successor; this.successor = successor;
} }
protected abstract void handleRequest(Request request); protected abstract void handleRequest(Request request);
} }
``` ```
```java ```java
public class ConcreteHandler1 extends Handler { public class ConcreteHandler1 extends Handler {
public ConcreteHandler1(Handler successor) { public ConcreteHandler1(Handler successor) {
super(successor); super(successor);
} }
@Override @Override
protected void handleRequest(Request request) { protected void handleRequest(Request request) {
if (request.getType() == RequestType.type1) { if (request.getType() == RequestType.TYPE1) {
System.out.println(request.getName() + " is handle by ConcreteHandler1"); System.out.println(request.getName() + " is handle by ConcreteHandler1");
return; return;
} }
@ -650,14 +684,16 @@ public class ConcreteHandler1 extends Handler {
``` ```
```java ```java
public class ConcreteHandler2 extends Handler{ public class ConcreteHandler2 extends Handler {
public ConcreteHandler2(Handler successor) { public ConcreteHandler2(Handler successor) {
super(successor); super(successor);
} }
@Override @Override
protected void handleRequest(Request request) { protected void handleRequest(Request request) {
if (request.getType() == RequestType.type2) { if (request.getType() == RequestType.TYPE2) {
System.out.println(request.getName() + " is handle by ConcreteHandler2"); System.out.println(request.getName() + " is handle by ConcreteHandler2");
return; return;
} }
@ -670,38 +706,47 @@ public class ConcreteHandler2 extends Handler{
```java ```java
public class Request { public class Request {
private RequestType type; private RequestType type;
private String name; private String name;
public Request(RequestType type, String name) { public Request(RequestType type, String name) {
this.type = type; this.type = type;
this.name = name; this.name = name;
} }
public RequestType getType() { public RequestType getType() {
return type; return type;
} }
public String getName() { public String getName() {
return name; return name;
} }
} }
``` ```
```java ```java
public enum RequestType { public enum RequestType {
type1, type2 TYPE1, TYPE2
} }
``` ```
```java ```java
public class Client { public class Client {
public static void main(String[] args) { public static void main(String[] args) {
Handler handler1 = new ConcreteHandler1(null); Handler handler1 = new ConcreteHandler1(null);
Handler handler2 = new ConcreteHandler2(handler1); Handler handler2 = new ConcreteHandler2(handler1);
Request request1 = new Request(RequestType.type1, "request1");
Request request1 = new Request(RequestType.TYPE1, "request1");
handler2.handleRequest(request1); handler2.handleRequest(request1);
Request request2 = new Request(RequestType.type2, "request2");
Request request2 = new Request(RequestType.TYPE2, "request2");
handler2.handleRequest(request2); handler2.handleRequest(request2);
} }
} }
@ -720,11 +765,16 @@ request2 is handle by ConcreteHandler2
## 2. 命令Command ## 2. 命令Command
### 意图 ### Intent
将命令封装成对象中,以便使用命令来参数化其它对象,或者将命令对象放入队列中进行排队,或者将命令对象的操作记录到日志中,以及支持可撤销的操作。 将命令封装成对象中,具有以下作用:
### 类图 - 使用命令来参数化其它对象
- 将命令放入队列中进行排队
- 将命令的操作记录到日志中
- 支持可撤销的操作
### Class Diagram
- Command命令 - Command命令
- Receiver命令接收者也就是命令真正的执行者 - Receiver命令接收者也就是命令真正的执行者
@ -733,7 +783,7 @@ request2 is handle by ConcreteHandler2
<div align="center"> <img src="../pics//ae1b27b8-bc13-42e7-ac12-a2242e125499.png"/> </div><br> <div align="center"> <img src="../pics//ae1b27b8-bc13-42e7-ac12-a2242e125499.png"/> </div><br>
### 实现 ### Implementation
设计一个遥控器,可以控制电灯开关。 设计一个遥控器,可以控制电灯开关。
@ -843,18 +893,18 @@ public class Client {
## 3. 解释器Interpreter ## 3. 解释器Interpreter
### 意图 ### Intent
为语言创建解释器,通常由语言的语法和语法分析来定义。 为语言创建解释器,通常由语言的语法和语法分析来定义。
### 类图 ### Class Diagram
- TerminalExpression终结符表达式每个终结符都需要一个 TerminalExpression - TerminalExpression终结符表达式每个终结符都需要一个 TerminalExpression
- Context上下文包含解释器之外的一些全局信息 - Context上下文包含解释器之外的一些全局信息
<div align="center"> <img src="../pics//794239e3-4baf-4aad-92df-f02f59b2a6fe.png"/> </div><br> <div align="center"> <img src="../pics//794239e3-4baf-4aad-92df-f02f59b2a6fe.png"/> </div><br>
### 实现 ### Implementation
以下是一个规则检验器实现,具有 and 和 or 规则,通过规则可以构建一颗解析树,用来检验一个文本是否满足解析树定义的规则。 以下是一个规则检验器实现,具有 and 和 or 规则,通过规则可以构建一颗解析树,用来检验一个文本是否满足解析树定义的规则。
@ -967,11 +1017,11 @@ false
## 4. 迭代器Iterator ## 4. 迭代器Iterator
### 意图 ### Intent
提供一种顺序访问聚合对象元素的方法,并且不暴露聚合对象的内部表示。 提供一种顺序访问聚合对象元素的方法,并且不暴露聚合对象的内部表示。
### 类图 ### Class Diagram
- Aggregate 是聚合类,其中 createIterator() 方法可以产生一个 Iterator - Aggregate 是聚合类,其中 createIterator() 方法可以产生一个 Iterator
- Iterator 主要定义了 hasNext() 和 next() 方法。 - Iterator 主要定义了 hasNext() 和 next() 方法。
@ -979,7 +1029,7 @@ false
<div align="center"> <img src="../pics//b0f61ac2-a4b6-4042-9cf0-ccf4238c1ff7.png"/> </div><br> <div align="center"> <img src="../pics//b0f61ac2-a4b6-4042-9cf0-ccf4238c1ff7.png"/> </div><br>
### 实现 ### Implementation
```java ```java
public interface Aggregate { public interface Aggregate {
@ -1008,6 +1058,7 @@ public class ConcreteAggregate implements Aggregate {
```java ```java
public interface Iterator<Item> { public interface Iterator<Item> {
Item next(); Item next();
boolean hasNext(); boolean hasNext();
@ -1038,6 +1089,7 @@ public class ConcreteIterator<Item> implements Iterator {
```java ```java
public class Client { public class Client {
public static void main(String[] args) { public static void main(String[] args) {
Aggregate aggregate = new ConcreteAggregate(); Aggregate aggregate = new ConcreteAggregate();
Iterator<Integer> iterator = aggregate.createIterator(); Iterator<Integer> iterator = aggregate.createIterator();
@ -1055,18 +1107,18 @@ public class Client {
## 5. 中介者Mediator ## 5. 中介者Mediator
### 意图 ### Intent
集中相关对象之间复杂的沟通和控制方式。 集中相关对象之间复杂的沟通和控制方式。
### 类图 ### Class Diagram
- Mediator中介者定义一个接口用于与各同事Colleague对象通信。 - Mediator中介者定义一个接口用于与各同事Colleague对象通信。
- Colleague同事相关对象 - Colleague同事相关对象
<div align="center"> <img src="../pics//d0afdd23-c9a5-4d1c-9b3d-404bff3bd0d1.png"/> </div><br> <div align="center"> <img src="../pics//d0afdd23-c9a5-4d1c-9b3d-404bff3bd0d1.png"/> </div><br>
### 实现 ### Implementation
Alarm闹钟、CoffeePot咖啡壶、Calendar日历、Sprinkler喷头是一组相关的对象在某个对象的事件产生时需要去操作其它对象形成了下面这种依赖结构 Alarm闹钟、CoffeePot咖啡壶、Calendar日历、Sprinkler喷头是一组相关的对象在某个对象的事件产生时需要去操作其它对象形成了下面这种依赖结构
@ -1224,11 +1276,11 @@ doSprinkler()
## 6. 备忘录Memento ## 6. 备忘录Memento
### 意图 ### Intent
在不违反封装的情况下获得对象的内部状态,从而在需要时可以将对象恢复到最初状态。 在不违反封装的情况下获得对象的内部状态,从而在需要时可以将对象恢复到最初状态。
### 类图 ### Class Diagram
- Originator原始对象 - Originator原始对象
- Caretaker负责保存好备忘录 - Caretaker负责保存好备忘录
@ -1236,7 +1288,7 @@ doSprinkler()
<div align="center"> <img src="../pics//867e93eb-3161-4f39-b2d2-c0cd3788e194.png"/> </div><br> <div align="center"> <img src="../pics//867e93eb-3161-4f39-b2d2-c0cd3788e194.png"/> </div><br>
### 实现 ### Implementation
以下实现了一个简单计算器程序,可以输入两个值,然后计算这两个值的和。备忘录模式允许将这两个值存储起来,然后在某个时刻用存储的状态进行恢复。 以下实现了一个简单计算器程序,可以输入两个值,然后计算这两个值的和。备忘录模式允许将这两个值存储起来,然后在某个时刻用存储的状态进行恢复。
@ -1401,7 +1453,7 @@ public class Client {
## 7. 观察者Observer ## 7. 观察者Observer
### 意图 ### Intent
定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知并且自动更新状态。 定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知并且自动更新状态。
@ -1409,7 +1461,7 @@ public class Client {
<div align="center"> <img src="../pics//7a3c6a30-c735-4edb-8115-337288a4f0f2.jpg" width="600"/> </div><br> <div align="center"> <img src="../pics//7a3c6a30-c735-4edb-8115-337288a4f0f2.jpg" width="600"/> </div><br>
### 类图 ### Class Diagram
主题Subject具有注册和移除观察者、并通知所有观察者的功能主题是通过维护一张观察者列表来实现这些操作的。 主题Subject具有注册和移除观察者、并通知所有观察者的功能主题是通过维护一张观察者列表来实现这些操作的。
@ -1417,7 +1469,7 @@ public class Client {
<div align="center"> <img src="../pics//0df5d84c-e7ca-4e3a-a688-bb8e68894467.png"/> </div><br> <div align="center"> <img src="../pics//0df5d84c-e7ca-4e3a-a688-bb8e68894467.png"/> </div><br>
### 实现 ### Implementation
天气数据布告板会在天气信息发生改变时更新其内容,布告板有多个,并且在将来会继续增加。 天气数据布告板会在天气信息发生改变时更新其内容,布告板有多个,并且在将来会继续增加。
@ -1536,15 +1588,15 @@ StatisticsDisplay.update: 1.0 1.0 1.0
## 8. 状态State ## 8. 状态State
### 意图 ### Intent
允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它所属的类。 允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它所属的类。
### 类图 ### Class Diagram
<div align="center"> <img src="../pics//c5085437-54df-4304-b62d-44b961711ba7.png"/> </div><br> <div align="center"> <img src="../pics//c5085437-54df-4304-b62d-44b961711ba7.png"/> </div><br>
### 实现 ### Implementation
糖果销售机有多种状态,每种状态下销售机有不同的行为,状态可以发生转移,使得销售机的行为也发生改变。 糖果销售机有多种状态,每种状态下销售机有不同的行为,状态可以发生转移,使得销售机的行为也发生改变。
@ -1838,16 +1890,16 @@ No gumball dispensed
## 9. 策略Strategy ## 9. 策略Strategy
### 意图 ### Intent
定义一系列算法,封装每个算法,并使它们可以互换。 定义一系列算法,封装每个算法,并使它们可以互换。
策略模式可以让算法独立于使用它的客户端。 策略模式可以让算法独立于使用它的客户端。
### 类图 ### Class Diagram
- Strategy 接口定义了一个算法族,它们都具有 behavior() 方法。 - Strategy 接口定义了一个算法族,它们都实现了 behavior() 方法。
- Context 是使用到该算法族的类,其中的 doSomething() 方法会调用 behavior()setStrategy(in Strategy) 方法可以动态地改变 strategy 对象,也就是说能动态地改变 Context 所使用的算法。 - Context 是使用到该算法族的类,其中的 doSomething() 方法会调用 behavior()setStrategy(Strategy) 方法可以动态地改变 strategy 对象,也就是说能动态地改变 Context 所使用的算法。
<div align="center"> <img src="../pics//1fc969e4-0e7c-441b-b53c-01950d2f2be5.png"/> </div><br> <div align="center"> <img src="../pics//1fc969e4-0e7c-441b-b53c-01950d2f2be5.png"/> </div><br>
@ -1857,7 +1909,7 @@ No gumball dispensed
状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法。 状态模式主要是用来解决状态转移的问题,当状态发生转移了,那么 Context 对象就会改变它的行为;而策略模式主要是用来封装一组可以互相替代的算法族,并且可以根据需要动态地去替换 Context 使用的算法。
### 实现 ### Implementation
设计一个鸭子,它可以动态地改变叫声。这里的算法族是鸭子的叫声行为。 设计一个鸭子,它可以动态地改变叫声。这里的算法族是鸭子的叫声行为。
@ -1887,6 +1939,7 @@ public class Squeak implements QuackBehavior{
```java ```java
public class Duck { public class Duck {
private QuackBehavior quackBehavior; private QuackBehavior quackBehavior;
public void performQuack() { public void performQuack() {
@ -1903,6 +1956,7 @@ public class Duck {
```java ```java
public class Client { public class Client {
public static void main(String[] args) { public static void main(String[] args) {
Duck duck = new Duck(); Duck duck = new Duck();
duck.setQuackBehavior(new Squeak()); duck.setQuackBehavior(new Squeak());
@ -1926,17 +1980,17 @@ quack!
## 10. 模板方法Template Method ## 10. 模板方法Template Method
### 意图 ### Intent
定义算法框架,并将一些步骤的实现延迟到子类。 定义算法框架,并将一些步骤的实现延迟到子类。
通过模板方法,子类可以重新定义算法的某些步骤,而不用改变算法的结构。 通过模板方法,子类可以重新定义算法的某些步骤,而不用改变算法的结构。
### 类图 ### Class Diagram
<div align="center"> <img src="../pics//c3c1c0e8-3a78-4426-961f-b46dd0879dd8.png"/> </div><br> <div align="center"> <img src="../pics//c3c1c0e8-3a78-4426-961f-b46dd0879dd8.png"/> </div><br>
### 实现 ### Implementation
冲咖啡和冲茶都有类似的流程,但是某些步骤会有点不一样,要求复用那些相同步骤的代码。 冲咖啡和冲茶都有类似的流程,但是某些步骤会有点不一样,要求复用那些相同步骤的代码。
@ -2027,11 +2081,11 @@ Tea.addCondiments
## 11. 访问者Visitor ## 11. 访问者Visitor
### 意图 ### Intent
为一个对象结构(比如组合结构)增加新能力。 为一个对象结构(比如组合结构)增加新能力。
### 类图 ### Class Diagram
- Visitor访问者为每一个 ConcreteElement 声明一个 visit 操作 - Visitor访问者为每一个 ConcreteElement 声明一个 visit 操作
- ConcreteVisitor具体访问者存储遍历过程中的累计结果 - ConcreteVisitor具体访问者存储遍历过程中的累计结果
@ -2039,7 +2093,7 @@ Tea.addCondiments
<div align="center"> <img src="../pics//ec923dc7-864c-47b0-a411-1f2c48d084de.png"/> </div><br> <div align="center"> <img src="../pics//ec923dc7-864c-47b0-a411-1f2c48d084de.png"/> </div><br>
### 实现 ### Implementation
```java ```java
public interface Element { public interface Element {
@ -2234,17 +2288,17 @@ Number of items: 6
## 12. 空对象Null ## 12. 空对象Null
### 意图 ### Intent
使用什么都不做的空对象来代替 NULL。 使用什么都不做的空对象来代替 NULL。
一个方法返回 NULL意味着方法的调用端需要去检查返回值是否是 NULL这么做会导致非常多的冗余的检查代码。并且如果某一个调用端忘记了做这个检查返回值而直接使用返回的对象那么就有可能抛出空指针异常。 一个方法返回 NULL意味着方法的调用端需要去检查返回值是否是 NULL这么做会导致非常多的冗余的检查代码。并且如果某一个调用端忘记了做这个检查返回值而直接使用返回的对象那么就有可能抛出空指针异常。
### 类图 ### Class Diagram
<div align="center"> <img src="../pics//dd3b289c-d90e-44a6-a44c-4880517eb1de.png"/> </div><br> <div align="center"> <img src="../pics//dd3b289c-d90e-44a6-a44c-4880517eb1de.png"/> </div><br>
### 实现 ### Implementation
```java ```java
public abstract class AbstractOperation { public abstract class AbstractOperation {
@ -2290,17 +2344,17 @@ public class Client {
## 1. 适配器Adapter ## 1. 适配器Adapter
### 意图 ### Intent
把一个类接口转换成另一个用户需要的接口。 把一个类接口转换成另一个用户需要的接口。
<div align="center"> <img src="../pics//3d5b828e-5c4d-48d8-a440-281e4a8e1c92.png"/> </div><br> <div align="center"> <img src="../pics//3d5b828e-5c4d-48d8-a440-281e4a8e1c92.png"/> </div><br>
### 类图 ### Class Diagram
<div align="center"> <img src="../pics//0f754c1d-b5cb-48cd-90e0-4a86034290a1.png"/> </div><br> <div align="center"> <img src="../pics//0889c0b4-07b4-45fc-873c-e0e16b97f67d.png"/> </div><br>
### 实现 ### Implementation
鸭子Duck和火鸡Turkey拥有不同的叫声Duck 的叫声调用 quack() 方法,而 Turkey 调用 gobble() 方法。 鸭子Duck和火鸡Turkey拥有不同的叫声Duck 的叫声调用 quack() 方法,而 Turkey 调用 gobble() 方法。
@ -2361,18 +2415,18 @@ public class Client {
## 2. 桥接Bridge ## 2. 桥接Bridge
### 意图 ### Intent
将抽象与实现分离开来,使它们可以独立变化。 将抽象与实现分离开来,使它们可以独立变化。
### 类图 ### Class Diagram
- Abstraction定义抽象类的接口 - Abstraction定义抽象类的接口
- Implementor定义实现类接口 - Implementor定义实现类接口
<div align="center"> <img src="../pics//c2cbf5d2-82af-4c78-bd43-495da5adf55f.png"/> </div><br> <div align="center"> <img src="../pics//c2cbf5d2-82af-4c78-bd43-495da5adf55f.png"/> </div><br>
### 实现 ### Implementation
RemoteControl 表示遥控器,指代 Abstraction。 RemoteControl 表示遥控器,指代 Abstraction。
@ -2514,11 +2568,11 @@ public class Client {
## 3. 组合Composite ## 3. 组合Composite
### 意图 ### Intent
将对象组合成树形结构来表示“整体/部分”层次关系,允许用户以相同的方式处理单独对象和组合对象。 将对象组合成树形结构来表示“整体/部分”层次关系,允许用户以相同的方式处理单独对象和组合对象。
### 类图 ### Class Diagram
组件Component类是组合类Composite和叶子类Leaf的父类可以把组合类看成是树的中间节点。 组件Component类是组合类Composite和叶子类Leaf的父类可以把组合类看成是树的中间节点。
@ -2526,7 +2580,7 @@ public class Client {
<div align="center"> <img src="../pics//3fb5b255-b791-45b6-8754-325c8741855a.png"/> </div><br> <div align="center"> <img src="../pics//3fb5b255-b791-45b6-8754-325c8741855a.png"/> </div><br>
### 实现 ### Implementation
```java ```java
public abstract class Component { public abstract class Component {
@ -2648,17 +2702,17 @@ Composite:root
## 4. 装饰Decorator ## 4. 装饰Decorator
### 意图 ### Intent
为对象动态添加功能。 为对象动态添加功能。
### 类图 ### Class Diagram
装饰者Decorator和具体组件ConcreteComponent都继承自组件Component具体组件的方法实现不需要依赖于其它对象而装饰者组合了一个组件这样它可以装饰其它装饰者或者具体组件。所谓装饰就是把这个装饰者套在被装饰者之上从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的这属于它的功能然后调用被装饰者的方法实现从而也保留了被装饰者的功能。可以看到具体组件应当是装饰层次的最低层因为只有具体组件的方法实现不需要依赖于其它对象。 装饰者Decorator和具体组件ConcreteComponent都继承自组件Component具体组件的方法实现不需要依赖于其它对象而装饰者组合了一个组件这样它可以装饰其它装饰者或者具体组件。所谓装饰就是把这个装饰者套在被装饰者之上从而动态扩展被装饰者的功能。装饰者的方法有一部分是自己的这属于它的功能然后调用被装饰者的方法实现从而也保留了被装饰者的功能。可以看到具体组件应当是装饰层次的最低层因为只有具体组件的方法实现不需要依赖于其它对象。
<div align="center"> <img src="../pics//137c593d-0a9e-47b8-a9e6-b71f540b82dd.png"/> </div><br> <div align="center"> <img src="../pics//137c593d-0a9e-47b8-a9e6-b71f540b82dd.png"/> </div><br>
### 实现 ### Implementation
设计不同种类的饮料,饮料可以添加配料,比如可以添加牛奶,并且支持动态添加新配料。每增加一种配料,该饮料的价格就会增加,要求计算一种饮料的价格。 设计不同种类的饮料,饮料可以添加配料,比如可以添加牛奶,并且支持动态添加新配料。每增加一种配料,该饮料的价格就会增加,要求计算一种饮料的价格。
@ -2726,6 +2780,7 @@ public class Mocha extends CondimentDecorator {
```java ```java
public class Client { public class Client {
public static void main(String[] args) { public static void main(String[] args) {
Beverage beverage = new HouseBlend(); Beverage beverage = new HouseBlend();
beverage = new Mocha(beverage); beverage = new Mocha(beverage);
@ -2755,15 +2810,15 @@ public class Client {
## 5. 外观Facade ## 5. 外观Facade
### 意图 ### Intent
提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用。 提供了一个统一的接口,用来访问子系统中的一群接口,从而让子系统更容易使用。
### 类图 ### Class Diagram
<div align="center"> <img src="../pics//f9978fa6-9f49-4a0f-8540-02d269ac448f.png"/> </div><br> <div align="center"> <img src="../pics//f9978fa6-9f49-4a0f-8540-02d269ac448f.png"/> </div><br>
### 实现 ### Implementation
观看电影需要操作很多电器,使用外观模式实现一键看电影功能。 观看电影需要操作很多电器,使用外观模式实现一键看电影功能。
@ -2810,11 +2865,11 @@ public class Client {
## 6. 享元Flyweight ## 6. 享元Flyweight
### 意图 ### Intent
利用共享的方式来支持大量细粒度的对象,这些对象一部分内部状态是相同的。 利用共享的方式来支持大量细粒度的对象,这些对象一部分内部状态是相同的。
### 类图 ### Class Diagram
- Flyweight享元对象 - Flyweight享元对象
- IntrinsicState内部状态享元对象共享内部状态 - IntrinsicState内部状态享元对象共享内部状态
@ -2822,7 +2877,7 @@ public class Client {
<div align="center"> <img src="../pics//d52270b4-9097-4667-9f18-f405fc661c99.png"/> </div><br> <div align="center"> <img src="../pics//d52270b4-9097-4667-9f18-f405fc661c99.png"/> </div><br>
### 实现 ### Implementation
```java ```java
public interface Flyweight { public interface Flyweight {
@ -2865,6 +2920,7 @@ public class FlyweightFactory {
```java ```java
public class Client { public class Client {
public static void main(String[] args) { public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory(); FlyweightFactory factory = new FlyweightFactory();
Flyweight flyweight1 = factory.getFlyweight("aa"); Flyweight flyweight1 = factory.getFlyweight("aa");
@ -2895,22 +2951,22 @@ Java 利用缓存来加速大量小对象的访问时间。
## 7. 代理Proxy ## 7. 代理Proxy
### 意图 ### Intent
控制对其它对象的访问。 控制对其它对象的访问。
### 类图 ### Class Diagram
代理有以下四类: 代理有以下四类:
- 远程代理Remote Proxy控制对远程对象不同地址空间的访问它负责将请求及其参数进行编码并向不同地址空间中的对象发送已经编码的请求。 - 远程代理Remote Proxy控制对远程对象不同地址空间的访问它负责将请求及其参数进行编码并向不同地址空间中的对象发送已经编码的请求。
- 虚拟代理Virtual Proxy根据需要创建开销很大的对象它可以缓存实体的附加信息以便延迟对它的访问例如在网站加载一个很大图片时不能马上完成可以用虚拟代理缓存图片的大小信息然后生成一张临时图片代替原始图片。 - 虚拟代理Virtual Proxy根据需要创建开销很大的对象它可以缓存实体的附加信息以便延迟对它的访问例如在网站加载一个很大图片时不能马上完成可以用虚拟代理缓存图片的大小信息然后生成一张临时图片代替原始图片。
- 保护代理Protection Proxy按权限控制对象的访问它负责检查调用者是否具有实现一个请求所必须的访问权限。 - 保护代理Protection Proxy按权限控制对象的访问它负责检查调用者是否具有实现一个请求所必须的访问权限。
- 智能代理Smart Reference取代了简单的指针它在访问对象时执行一些附加操作记录对象的引用次数,比如智能智能;当第一次引用一个持久化对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。 - 智能代理Smart Reference取代了简单的指针它在访问对象时执行一些附加操作记录对象的引用次数当第一次引用一个对象时将它装入内存在访问一个实际对象前检查是否已经锁定了它以确保其它对象不能改变它。
<div align="center"> <img src="../pics//a6c20f60-5eba-427d-9413-352ada4b40fe.png"/> </div><br> <div align="center"> <img src="../pics//a6c20f60-5eba-427d-9413-352ada4b40fe.png"/> </div><br>
### 实现 ### Implementation
以下是一个虚拟代理的实现,模拟了图片延迟加载的情况下使用与图片大小相等的临时内容去替换原始图片,直到图片加载完成才将图片显示出来。 以下是一个虚拟代理的实现,模拟了图片延迟加载的情况下使用与图片大小相等的临时内容去替换原始图片,直到图片加载完成才将图片显示出来。
@ -2958,6 +3014,7 @@ public class HighResolutionImage implements Image {
```java ```java
public class ImageProxy implements Image { public class ImageProxy implements Image {
private HighResolutionImage highResolutionImage; private HighResolutionImage highResolutionImage;
public ImageProxy(HighResolutionImage highResolutionImage) { public ImageProxy(HighResolutionImage highResolutionImage) {
@ -2981,6 +3038,7 @@ public class ImageProxy implements Image {
```java ```java
public class ImageViewer { public class ImageViewer {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
String image = "http://image.jpg"; String image = "http://image.jpg";
URL url = new URL(image); URL url = new URL(image);

View File

@ -161,6 +161,7 @@ class Customer {
```java ```java
class Rental { class Rental {
private int daysRented; private int daysRented;
private Movie movie; private Movie movie;
@ -199,6 +200,7 @@ class Movie {
```java ```java
public class App { public class App {
public static void main(String[] args) { public static void main(String[] args) {
Customer customer = new Customer(); Customer customer = new Customer();
Rental rental1 = new Rental(1, new Movie(Movie.Type1)); Rental rental1 = new Rental(1, new Movie(Movie.Type1));
@ -236,6 +238,7 @@ public class App {
```java ```java
class Customer { class Customer {
private List<Rental> rentals = new ArrayList<>(); private List<Rental> rentals = new ArrayList<>();
void addRental(Rental rental) { void addRental(Rental rental) {
@ -254,6 +257,7 @@ class Customer {
```java ```java
class Rental { class Rental {
private int daysRented; private int daysRented;
private Movie movie; private Movie movie;
@ -294,8 +298,6 @@ class Price2 implements Price {
``` ```
```java ```java
package imp2;
class Price3 implements Price { class Price3 implements Price {
@Override @Override
public double getCharge() { public double getCharge() {

View File

@ -18,12 +18,12 @@
负载均衡器可以用来实现高可用以及伸缩性: 负载均衡器可以用来实现高可用以及伸缩性:
- 高可用:当某个节点故障时,负载均衡器会将用户请求转发到另外的节点上,从而保证所有服务持续可用; - 高可用:当某个节点故障时,负载均衡器会将用户请求转发到另外的节点上,从而保证所有服务持续可用;
- 伸缩性:可以很容易地添加移除节点。 - 伸缩性:根据系统整体负载情况,可以很容易地添加移除节点。
负载均衡运行过程包含两个部分: 负载均衡运行过程包含两个部分:
1. 根据负载均衡算法得到请求转发的节点; 1. 根据负载均衡算法得到转发的节点;
2. 将请求进行转发。 2. 进行转发。
## 负载均衡算法 ## 负载均衡算法
@ -116,11 +116,6 @@ HTTP 重定向负载均衡服务器使用某种负载均衡算法计算得到服
### 3. 反向代理服务器 ### 3. 反向代理服务器
首先了解一下正向代理与反向代理的区别:
- 正向代理:发生在客户端,是由用户主动发起的。比如翻墙,客户端通过主动访问代理服务器,让代理服务器获得需要的外网数据,然后转发回客户端;
- 反向代理:发生在服务器端,用户不知道代理的存在。
反向代理服务器位于源服务器前面,用户的请求需要先经过反向代理服务器才能到达源服务器。反向代理可以用来进行缓存、日志记录等,同时也可以用来做为负载均衡服务器。 反向代理服务器位于源服务器前面,用户的请求需要先经过反向代理服务器才能到达源服务器。反向代理可以用来进行缓存、日志记录等,同时也可以用来做为负载均衡服务器。
在这种负载均衡转发方式下,客户端不直接请求源服务器,因此源服务器不需要外部 IP 地址,而反向代理需要配置内部和外部两套 IP 地址。 在这种负载均衡转发方式下,客户端不直接请求源服务器,因此源服务器不需要外部 IP 地址,而反向代理需要配置内部和外部两套 IP 地址。
@ -153,7 +148,7 @@ HTTP 重定向负载均衡服务器使用某种负载均衡算法计算得到服
通过配置源服务器的虚拟 IP 地址和负载均衡服务器的 IP 地址一致,从而不需要修改 IP 地址就可以进行转发。也正因为 IP 地址一样,所以源服务器的响应不需要转发回负载均衡服务器,可以直接转发给客户端,避免了负载均衡服务器的成为瓶颈。 通过配置源服务器的虚拟 IP 地址和负载均衡服务器的 IP 地址一致,从而不需要修改 IP 地址就可以进行转发。也正因为 IP 地址一样,所以源服务器的响应不需要转发回负载均衡服务器,可以直接转发给客户端,避免了负载均衡服务器的成为瓶颈。
这是一种三角传输模式,被称为直接路由对于提供下载和视频服务的网站来说,直接路由避免了大量的网络传输数据经过负载均衡服务器。 这是一种三角传输模式,被称为直接路由对于提供下载和视频服务的网站来说,直接路由避免了大量的网络传输数据经过负载均衡服务器。
这是目前大型网站使用最广负载均衡转发方式,在 Linux 平台可以使用的负载均衡服务器为 LVSLinux Virtual Server 这是目前大型网站使用最广负载均衡转发方式,在 Linux 平台可以使用的负载均衡服务器为 LVSLinux Virtual Server
@ -185,7 +180,6 @@ HTTP 重定向负载均衡服务器使用某种负载均衡算法计算得到服
- 占用过多内存; - 占用过多内存;
- 同步过程占用网络带宽以及服务器处理器时间。 - 同步过程占用网络带宽以及服务器处理器时间。
<div align="center"> <img src="../pics//MultiNode-SessionReplication.jpg"/> </div><br> <div align="center"> <img src="../pics//MultiNode-SessionReplication.jpg"/> </div><br>
## Session Server ## Session Server

View File

@ -37,6 +37,7 @@
```java ```java
public class Person { public class Person {
private String name; private String name;
private int gender; private int gender;
private int age; private int age;
@ -63,17 +64,20 @@ public class Person {
继承实现了 **IS-A** 关系,例如 Cat 和 Animal 就是一种 IS-A 关系,因此 Cat 可以继承自 Animal从而获得 Animal 非 private 的属性和方法。 继承实现了 **IS-A** 关系,例如 Cat 和 Animal 就是一种 IS-A 关系,因此 Cat 可以继承自 Animal从而获得 Animal 非 private 的属性和方法。
继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。
Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat 对象。父类引用指向子类对象称为 **向上转型** Cat 可以当做 Animal 来使用,也就是说可以使用 Animal 引用 Cat 对象。父类引用指向子类对象称为 **向上转型**
```java ```java
Animal animal = new Cat(); Animal animal = new Cat();
``` ```
继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象。
## 多态 ## 多态
多态分为编译时多态和运行时多态。编译时多态主要指方法的重载,运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定。 多态分为编译时多态和运行时多态:
- 编译时多态主要指方法的重载
- 运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定
运行时多态有三个条件: 运行时多态有三个条件:
@ -85,24 +89,28 @@ Animal animal = new Cat();
```java ```java
public class Instrument { public class Instrument {
public void play() { public void play() {
System.out.println("Instument is playing..."); System.out.println("Instument is playing...");
} }
} }
public class Wind extends Instrument { public class Wind extends Instrument {
public void play() { public void play() {
System.out.println("Wind is playing..."); System.out.println("Wind is playing...");
} }
} }
public class Percussion extends Instrument { public class Percussion extends Instrument {
public void play() { public void play() {
System.out.println("Percussion is playing..."); System.out.println("Percussion is playing...");
} }
} }
public class Music { public class Music {
public static void main(String[] args) { public static void main(String[] args) {
List<Instrument> instruments = new ArrayList<>(); List<Instrument> instruments = new ArrayList<>();
instruments.add(new Wind()); instruments.add(new Wind());
@ -116,7 +124,7 @@ public class Music {
# 二、类图 # 二、类图
以下类图使用 [PlantUML](https://www.planttext.com/) 绘制更多语法及使用请参考http://plantuml.com/ 以下类图使用 [PlantUML](https://www.planttext.com/) 绘制更多语法及使用请参考http://plantuml.com/
## 泛化关系 (Generalization) ## 泛化关系 (Generalization)
@ -227,9 +235,9 @@ School "1" - "n" Student
和关联关系不同的是依赖关系是在运行过程中起作用的。A 类和 B 类是依赖关系主要有三种形式: 和关联关系不同的是依赖关系是在运行过程中起作用的。A 类和 B 类是依赖关系主要有三种形式:
- A 类是 B 类中的(某中方法的局部变量; - A 类是 B 类方法的局部变量;
- A 类是 B 类方法当中的一个参数; - A 类是 B 类方法当中的一个参数;
- A 类向 B 类发送消息,从而影响 B 类发生变化 - A 类向 B 类发送消息,从而影响 B 类发生变化
<div align="center"> <img src="../pics//LOun2W9134NxVugmbJPp15d4LalxC4O.png"/> </div><br> <div align="center"> <img src="../pics//LOun2W9134NxVugmbJPp15d4LalxC4O.png"/> </div><br>
@ -327,7 +335,7 @@ Vihicle .. N
### 2. 合成复用原则 ### 2. 合成复用原则
尽量使用对象组合,而不是继承来达到复用的目的。 尽量使用对象组合,而不是通过继承来达到复用的目的。
### 3. 共同封闭原则 ### 3. 共同封闭原则

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -2,8 +2,6 @@
这个交流群不是一个笔者的问题回答群,我更希望大家能够愿意积极回答,我相信提问和回答的过程都可以提高大家对知识的掌握程度。 这个交流群不是一个笔者的问题回答群,我更希望大家能够愿意积极回答,我相信提问和回答的过程都可以提高大家对知识的掌握程度。
因为笔者白天要上班,因此不能及时进行回复,大部分时间会处于潜水状态。
至于交流群和 Issue 有什么区别,主要是两方面:一是交流群实时性高一些,二是交流群会更活跃一些。 至于交流群和 Issue 有什么区别,主要是两方面:一是交流群实时性高一些,二是交流群会更活跃一些。
另外Issue 主要是用来发布一些项目中的错误和一些改进建议,当然也可以发布一些可以讨论的问题。 另外Issue 主要是用来发布一些项目中的错误和一些改进建议,当然也可以发布一些可以讨论的问题。

View File

@ -0,0 +1,151 @@
# Algorithm
leetcode/lintcode上的算法题
**关于问题的答案和解体的思路,可以移步 : https://github.com/zhaozhengcoder/Algorithm**
### About
这个仓库最初的想法是把lintcode/lintocde上面的算法题目整理一下因为很多题目太多了显得太乱了就不继续在GitHub上面写了以前写的一部分移到我的博客上面了。
GitHub上面打算整理一些比较典型 或者是 自己思考过的觉得很好的问题。
在博客上面开了两个专栏
1. 数据结构/算法导论 :
https://www.jianshu.com/nb/12397278
2. OJ练习题 :
https://www.jianshu.com/nb/9973135
推荐两篇自己对 递归搜索和动态规划 的理解的blog
1. https://www.jianshu.com/p/5eb4da919efe
2. https://www.jianshu.com/p/6b3a2304f63f
### 题目的索引
GITHUB上面打算整理一些比较典型 或者是 自己思考过的觉得很好的问题。
1.从数据结构的角度索引 :
a. 数组
两数之和
连续最大子数组
乘积最大子数组
买卖股票的最佳时机123
买卖股票的最佳时机1寻找数组里面的最大上升子序列
买卖股票的最佳时机2寻找数组里面所有的上升子序列
买卖股票的最佳时机3寻找数组里面两个不重合的上升子序列并且使他们的和最大 to-do
区间合并(将有交集的区间合并)
寻找缺失的数
1. 一个顺序的数组[1,2,3,5,6],缺少了一个数字,如何找到它?
2. 一个arr的数组只有一个数字出现了一次其他都出现了两次如何找到它
数组的近似划分(将一个数组分成两个,但是差最小)
数组里面第k大的数
跳跃游戏12
跳跃游戏1
给出一个非负整数数组,你最初定位在数组的第一个位置,
数组中的每个元素代表你在那个位置可以跳跃的最大长度,
返回 是否能到达数组的最后一个位置
跳跃游戏2
给出一个非负整数数组,你最初定位在数组的第一个位置,
数组中的每个元素代表你在那个位置可以跳跃的最大长度,   
返回 使用最少的跳跃次数到达数组的最后一个位置
a+. 二维矩阵
顺时针打印二维矩阵
给出一个二维矩阵,找到一个路径(从某个左上角到某个角右下)使这条路径的值最大
b. 链表
c. 字符串
最长公共子序列(并不是连续的)
最长回文子串
d. 二叉树
返回一个平衡二叉树的第k大的节点
二叉树的最低公共祖先
非递归遍历二叉树
e. 图
最短路径
深度/广度优先遍历
2. 从算法的角度建立索引
a. 递归搜索问题
N后问题
全排列
组合问题12
b. 动态规划
背包问题12
数组的近似划分(将一个数组分成两个,但是差最小)
跳跃游戏12
给出一个二维矩阵,找到一个路径(从某个左上角到某个角右下)使这条路径的值最大
3. 常用
a. 排列/组合
b. 深度优先遍历
c. 最短路径
4. 智力题(算法本身很简单,就是想不到的那种)
最多有多少个点在同一条直线上
### Others
1. 类似于系统设计的题目
带最小值的栈/队列
url长链接转短链接
2. 解决特定问题
并查集
布隆过滤器
如果你对机器学习的算法感兴趣,欢迎共同讨论:
https://github.com/zhaozhengcoder/Machine-Learning

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB