diff --git a/README.md b/README.md
index a79146c5..ec33d7a5 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,16 @@
-如果你不知道该学习什么的话,请看 [Java 学习线路图是怎样的?]( https://www.zhihu.com/question/56110328/answer/869069586) (原创不易,欢迎点赞),这是 2021 最新最完善的 Java 学习路线!
+👉 如果你不知道该学习什么的话,请看 [Java 学习线路图是怎样的?]( https://zhuanlan.zhihu.com/p/379041500) (原创不易,欢迎点赞),这是 2021 最新最完善的 Java 学习路线!另外,[我的朋友整理了一份消息队列常见面试题,需要的小伙伴可以点击领取!](http://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=100025985&idx=1&sn=681af486050fabbeea27fa1c3bec5d65&chksm=4ea1e94a79d6605c72f280b5268100c6e96c6ab1dc9a0178b33e25a72ff5f4eac3dcb56fa44f#rd)
-👍推荐 [在线阅读](https://snailclimb.gitee.io/javaguide) (Github 访问速度比较慢可能会导致部分图片无法刷新出来)
+👉 推荐 [在线阅读](https://snailclimb.gitee.io/javaguide) (Github 访问速度比较慢可能会导致部分图片无法刷新出来)
-👍推荐[2021最新实战项目源码下载](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=100018862&idx=1&sn=858e00b60c6097e3ba061e79be472280&chksm=4ea1856579d60c73224e4d852af6b0188c3ab905069fc28f4b293963fd1ee55d2069fb229848#rd)
-
-书单已经被移动到[awesome-cs](https://github.com/CodingDocs/awesome-cs) 这个仓库。
+👉 书单已经被移动到 [awesome-cs](https://github.com/CodingDocs/awesome-cs) 这个仓库。
> 1. **介绍**:关于 JavaGuide 的相关介绍请看:[关于 JavaGuide 的一些说明](https://www.yuque.com/snailclimb/dr6cvl/mr44yt) 。
-> 2. **PDF版本** : [《JavaGuide 面试突击版》PDF 版本](#公众号) 。[图解计算机基础 PDF 版](#优质原创PDF资源)。
-> 3. **知识星球** : 简历指导/Java学习/面试指导/面试小册。欢迎加入[我的知识星球](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=100015911&idx=1&sn=2e8a0f5acb749ecbcbb417aa8a4e18cc&chksm=4ea1b0ec79d639fae37df1b86f196e8ce397accfd1dd2004bcadb66b4df5f582d90ae0d62448#rd) 。星球内部更新的[《Java面试进阶指北 打造个人的技术竞争力》](https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7)这个小册的质量很高,专为面试打造。
-> 4. **面试专版** :准备面试的小伙伴可以考虑面试专版:[《Java 面试进阶指南》](https://xiaozhuanlan.com/javainterview?rel=javaguide)
-> 6. **转载须知** :以下所有文章如非文首说明皆为我(Guide哥)的原创,转载在文首注明出处,如发现恶意抄袭/搬运,会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境!⛽️
+> 2. **贡献指南** :欢迎参与 [JavaGuide的维护工作](https://github.com/Snailclimb/JavaGuide/issues/1235),这是一件非常有意义的事情。
+> 3. **PDF版本** : [《JavaGuide 面试突击版》PDF 版本](#公众号) 。
+> 4. **图解计算机基础** :[图解计算机基础 PDF 下载](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=100021725&idx=1&sn=2db9664ca25363139a81691043e9fd8f&chksm=4ea19a1679d61300d8990f7e43bfc7f476577a81b712cf0f9c6f6552a8b219bc081efddb5c54#rd) 。
+> 5. **知识星球** : 简历指导/Java学习/面试指导/面试小册。欢迎加入[我的知识星球](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=100015911&idx=1&sn=2e8a0f5acb749ecbcbb417aa8a4e18cc&chksm=4ea1b0ec79d639fae37df1b86f196e8ce397accfd1dd2004bcadb66b4df5f582d90ae0d62448#rd) 。
+> 6. **面试专版** :准备面试的小伙伴可以考虑面试专版:[《Java面试进阶指北 》](https://www.yuque.com/docs/share/f37fc804-bfe6-4b0d-b373-9c462188fec7) (质量很高,专为面试打造)
+> 7. **转载须知** :以下所有文章如非文首说明皆为我(Guide哥)的原创,转载在文首注明出处,如发现恶意抄袭/搬运,会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境!⛽️
@@ -44,10 +44,10 @@
### 基础
-**知识点/面试题:**(必看:+1: )
+**知识点/面试题** : (必看:+1: )
1. **[Java 基础知识](docs/java/basis/Java基础知识.md)**
-2. **[Java 基础知识疑难点/易错点](docs/java/basis/Java基础知识疑难点.md)**
+2. [Java 基础知识疑难点/易错点](docs/java/basis/Java基础知识疑难点.md)
**重要知识点详解:**
@@ -61,6 +61,7 @@
1. **[Java 容器常见问题总结](docs/java/collection/Java集合框架常见面试题.md)** (必看 :+1:)
2. **源码分析** :[ArrayList 源码+扩容机制分析](docs/java/collection/ArrayList源码+扩容机制分析.md) 、[LinkedList 源码](docs/java/collection/LinkedList源码分析.md) 、[HashMap(JDK1.8)源码+底层数据结构分析]() 、[ConcurrentHashMap 源码+底层数据结构分析](docs/java/collection/ConcurrentHashMap源码+底层数据结构分析.md)
+3. [Java 容器使用注意事项总结](docs/java/collection/Java集合使用注意事项总结.md)
### 并发
@@ -71,14 +72,17 @@
**重要知识点详解:**
-2. **线程池**:[Java 线程池学习总结](./docs/java/multi-thread/java线程池学习总结.md)、[拿来即用的线程池最佳实践](./docs/java/multi-thread/拿来即用的线程池最佳实践.md)
-4. [ ThreadLocal 关键字解析](docs/java/multi-thread/万字详解ThreadLocal关键字.md)
-5. [并发容器总结](docs/java/multi-thread/并发容器总结.md)
-6. [JUC 中的 Atomic 原子类总结](docs/java/multi-thread/Atomic原子类总结.md)
-7. [AQS 原理以及 AQS 同步组件总结](docs/java/multi-thread/AQS原理以及AQS同步组件总结.md)
+1. **线程池**:[Java 线程池学习总结](./docs/java/multi-thread/java线程池学习总结.md)、[拿来即用的线程池最佳实践](./docs/java/multi-thread/拿来即用的线程池最佳实践.md)
+2. [ ThreadLocal 关键字解析](docs/java/multi-thread/万字详解ThreadLocal关键字.md)
+3. [并发容器总结](docs/java/multi-thread/并发容器总结.md)
+4. [JUC 中的 Atomic 原子类总结](docs/java/multi-thread/Atomic原子类总结.md)
+5. [AQS 原理以及 AQS 同步组件总结](docs/java/multi-thread/AQS原理以及AQS同步组件总结.md)
+6. [CompletableFuture入门](docs/java/multi-thread/CompletableFuture入门.md)
### JVM (必看 :+1:)
+JVM 这部分内容主要参考 [JVM 虚拟机规范-Java8 ](https://docs.oracle.com/javase/specs/jvms/se8/html/index.html) 和周志明老师的[《深入理解Java虚拟机(第3版)》](https://book.douban.com/subject/34907497/) (强烈建议阅读多遍!)。
+
1. **[Java 内存区域](docs/java/jvm/Java内存区域.md)**
2. **[JVM 垃圾回收](docs/java/jvm/JVM垃圾回收.md)**
3. [JDK 监控和故障处理工具](docs/java/jvm/JDK监控和故障处理工具总结.md)
@@ -91,27 +95,40 @@
### 新特性
1. **Java 8** :[Java 8 新特性总结](docs/java/new-features/Java8新特性总结.md)、[Java8常用新特性总结](docs/java/new-features/java8-common-new-features.md) 、[Java 8 学习资源推荐](docs/java/new-features/Java8教程推荐.md)、[Java8 forEach 指南](docs/java/new-features/Java8foreach指南.md)
-2. **Java9~Java14** : [一文带你看遍 JDK9~14 的重要新特性!](./docs/java/new-features/一文带你看遍JDK9到14的重要新特性.md)
+2. **Java9~Java15** : [一文带你看遍 JDK9~15 的重要新特性!](./docs/java/new-features/java新特性总结.md)
-## 网络
+### 小技巧
-1. [计算机网络常见面试题](docs/network/计算机网络.md)
-2. [计算机网络基础知识总结](docs/network/计算机网络知识总结.md)
+1. [JAD 反编译](docs/java/tips/JAD反编译tricks.md)
+2. [手把手教你定位常见 Java 性能问题](./docs/java/tips/手把手教你定位常见Java性能问题.md)
-## 操作系统
+## 计算机基础
-1. [操作系统常见问题总结!](docs/operating-system/basis.md)
-2. [后端程序员必备的 Linux 基础知识](docs/operating-system/linux.md)
-3. [Shell 编程入门](docs/operating-system/Shell.md)
+👉 **[图解计算机基础 PDF 下载](https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=100021725&idx=1&sn=2db9664ca25363139a81691043e9fd8f&chksm=4ea19a1679d61300d8990f7e43bfc7f476577a81b712cf0f9c6f6552a8b219bc081efddb5c54#rd)** 。
-## 数据结构与算法
+### 操作系统
+
+1. [操作系统常见问题总结!](docs/cs-basics/operating-system/basis.md)
+2. [后端程序员必备的 Linux 基础知识总结](docs/cs-basics/operating-system/linux.md)
+3. [Shell 编程入门](docs/cs-basics/operating-system/Shell.md)
+
+### 网络
+
+1. [计算机网络常见面试题](docs/cs-basics/network/计算机网络.md)
+2. [计算机网络基础知识总结](docs/cs-basics/network/计算机网络知识总结.md)
### 数据结构
-- **图解数据结构:**
- 1. [线性数据结构 :数组、链表、栈、队列](docs/dataStructures-algorithms/data-structure/线性数据结构.md)
- 2. [图](docs/dataStructures-algorithms/data-structure/图.md)
-- [不了解布隆过滤器?一文给你整的明明白白!](docs/dataStructures-algorithms/data-structure/bloom-filter.md)
+**图解数据结构:**
+
+1. [线性数据结构 :数组、链表、栈、队列](docs/cs-basics/data-structure/线性数据结构.md)
+2. [图](docs/cs-basics/data-structure/图.md)
+3. [堆](docs/cs-basics/data-structure/堆.md)
+4. [树](docs/cs-basics/data-structure/树.md) :重点关注[红黑树](docs/cs-basics/data-structure/红黑树.md)、B-,B+,B*树、LSM树
+
+其他常用数据结构 :
+
+1. [布隆过滤器](docs/cs-basics/data-structure/bloom-filter.md)
### 算法
@@ -120,11 +137,13 @@
- [算法学习书籍+资源推荐](https://www.zhihu.com/question/323359308/answer/1545320858) 。
- [如何刷Leetcode?](https://www.zhihu.com/question/31092580/answer/1534887374)
-**常见算法问题总结:**
+**常见算法问题总结** :
-- [几道常见的字符串算法题总结 ](docs/dataStructures-algorithms/几道常见的子符串算法题.md)
-- [几道常见的链表算法题总结 ](docs/dataStructures-algorithms/几道常见的链表算法题.md)
-- [剑指 offer 部分编程题](docs/dataStructures-algorithms/剑指offer部分编程题.md)
+- [几道常见的字符串算法题总结 ](docs/cs-basics/algorithms/几道常见的字符串算法题.md)
+- [几道常见的链表算法题总结 ](docs/cs-basics/algorithms/几道常见的链表算法题.md)
+- [剑指 offer 部分编程题](docs/cs-basics/algorithms/剑指offer部分编程题.md)
+
+另外,[GeeksforGeeks]( https://www.geeksforgeeks.org/fundamentals-of-algorithms/) 这个网站总结了常见的算法 ,比较全面系统。
## 数据库
@@ -132,23 +151,30 @@
**总结:**
-1. **[MySQL知识点总结](docs/database/MySQL.md)** (必看 :+1:)
-2. [阿里巴巴开发手册数据库部分的一些最佳实践](docs/database/阿里巴巴开发手册数据库部分的一些最佳实践.md)
-3. [一千行 MySQL 学习笔记](docs/database/一千行MySQL命令.md)
-4. [MySQL 高性能优化规范建议](docs/database/MySQL高性能优化规范建议.md)
+1. [数据库基础知识总结](docs/database/数据库基础知识.md)
+2. **[MySQL知识点总结](docs/database/mysql/MySQL总结.md)** (必看 :+1:)
+3. [阿里巴巴开发手册数据库部分的一些最佳实践](docs/database/mysql/阿里巴巴开发手册数据库部分的一些最佳实践.md)
+4. [一千行 MySQL 学习笔记](docs/database/mysql/一千行MySQL学习笔记.md)
+5. [MySQL 高性能优化规范建议](docs/database/mysql/MySQL高性能优化规范建议.md)
**重要知识点:**
-1. [MySQL数据库索引总结](docs/database/MySQL数据库索引.md)
-2. [事务隔离级别(图文详解)]()
-3. [一条 SQL 语句在 MySQL 中如何执行的](docs/database/一条sql语句在mysql中如何执行的.md)
-4. [关于数据库中如何存储时间的一点思考](docs/database/关于数据库存储时间的一点思考.md)
+1. [MySQL数据库索引总结](docs/database/mysql/MySQL数据库索引.md)
+2. [事务隔离级别(图文详解)](docs/database/mysql/事务隔离级别(图文详解).md)
+3. [MySQL三大日志(binlog、redo log和undo log)详解](docs/database/mysql/MySQL三大日志.md)
+4. [InnoDB存储引擎对MVCC的实现](docs/database/mysql/InnoDB对MVCC的实现.md)
+5. [一条 SQL 语句在 MySQL 中如何执行的](docs/database/mysql/一条sql语句在mysql中如何执行的.md)
+6. [关于数据库中如何存储时间的一点思考](docs/database/mysql/关于数据库存储时间的一点思考.md)
### Redis
2. [Redis 常见问题总结](docs/database/Redis/redis-all.md)
3. [面试/工作必备!3种常用的缓存读写策略!](docs/database/Redis/3种常用的缓存读写策略.md)
+## 搜索引擎
+
+用于提高搜索效率,功能和浏览器搜索引擎类似。比较常见的搜索引擎是 Elasticsearch(推荐) 和 Solr。
+
## 系统设计
### 系统设计必备基础
@@ -234,10 +260,6 @@ CAP 也就是 Consistency(一致性)、Availability(可用性)、Partiti
**Paxos 算法**诞生于 1990 年,这是一种解决分布式系统一致性的经典算法 。但是,由于 Paxos 算法非常难以理解和实现,不断有人尝试简化这一算法。到了2013 年才诞生了一个比 Paxos 算法更易理解和实现的分布式一致性算法—**Raft 算法**。
-#### 搜索引擎
-
-用于提高搜索效率,功能和浏览器搜索引擎类似。比较常见的搜索引擎是 Elasticsearch(推荐) 和 Solr。
-
#### RPC
RPC 让调用远程服务调用像调用本地方法那样简单。
@@ -256,7 +278,7 @@ Dubbo 是一款国产的 RPC 框架,由阿里开源。相关阅读:
#### 分布式 id
-在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。比如数据量太大之后,往往需要对进行对数据进行分库分表,分库分表后需要有一个唯一 ID 来标识一条数据或消息,数据库的自增 ID 显然不能满足需求。相关阅读:[为什么要分布式 id ?分布式 id 生成方案有哪些?](docs/system-design/micro-service/分布式id生成方案总结.md)
+在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。比如数据量太大之后,往往需要对数据进行分库分表,分库分表后需要有一个唯一 ID 来标识一条数据或消息,数据库的自增 ID 显然不能满足需求。相关阅读:[为什么要分布式 id ?分布式 id 生成方案有哪些?](docs/system-design/micro-service/分布式id生成方案总结.md)
#### 分布式事务
@@ -339,6 +361,24 @@ Dubbo 是一款国产的 RPC 框架,由阿里开源。相关阅读:
另外,重试的次数一般设为 3 次,再多次的重试没有好处,反而会加重服务器压力(部分场景使用失败重试机制会不太适合)。
+#### 灾备设计
+
+**灾备** = 容灾+备份。
+
+- **备份** : 将系统所产生的的所有重要数据多备份几份。
+- **容灾** : 在异地建立两个完全相同的系统。当某个地方的系统突然挂掉,整个应用系统可以切换到另一个,这样系统就可以正常提供服务了。
+
+#### 异地多活
+
+异地多活描述的是将服务部署在异地并且服务同时对外提供服务。和传统的灾备设计的最主要区别在于“多活”,即所有站点都是同时在对外提供服务的。
+
+异地多活是为了应对突发状况比如火灾、地震等自然或者认为灾害。
+
+相关阅读:
+
+- [四步构建异地多活](https://mp.weixin.qq.com/s/hMD-IS__4JE5_nQhYPYSTg)
+- [《从零开始学架构》— 28 | 业务高可用的保障:异地多活架构](http://gk.link/a/10pKZ)
+
### 大型网站架构
- [8 张图读懂大型网站技术架构](docs/system-design/website-architecture/8%20张图读懂大型网站技术架构.md)
@@ -346,7 +386,6 @@ Dubbo 是一款国产的 RPC 框架,由阿里开源。相关阅读:
## 工具
-1. **Java** :[JAD 反编译](docs/java/JAD反编译tricks.md)、[手把手教你定位常见 Java 性能问题](./docs/java/手把手教你定位常见Java性能问题.md)
2. **Git** :[Git 入门](docs/tools/Git.md)
3. **Github** : [Github小技巧](docs/tools/Github技巧.md)
4. **Docker** : [Docker 基本概念解读](docs/tools/Docker.md) 、[Docker从入门到上手干事](docs/tools/Docker从入门到实战.md)
@@ -370,15 +409,8 @@ Dubbo 是一款国产的 RPC 框架,由阿里开源。相关阅读:
### 待办
-- [ ] 数据结构总结重构
-
-### 优质原创PDF资源
-
-
-
-为了避免恶意传播,微信搜“**Github掘金计划**”后台回复 **“006”** 即可获取。
-
-
+- [ ] 计算机网络知识点完善
+- [ ] 分布式常见理论和算法总结完善
### 捐赠支持
@@ -388,7 +420,11 @@ Dubbo 是一款国产的 RPC 框架,由阿里开源。相关阅读:
### 联系我
-
+
+
+整理了一份各个技术的学习路线,需要的小伙伴加我微信:“**JavaGuide1996**”备注“**Github-学习路线**”即可!
+
+
### 公众号
@@ -397,4 +433,3 @@ Dubbo 是一款国产的 RPC 框架,由阿里开源。相关阅读:
**《Java 面试突击》:** 由本文档衍生的专为面试而生的《Java 面试突击》V4.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可领取!

-
diff --git a/docs/dataStructures-algorithms/几道常见的子符串算法题.md b/docs/cs-basics/algorithms/几道常见的字符串算法题.md
similarity index 100%
rename from docs/dataStructures-algorithms/几道常见的子符串算法题.md
rename to docs/cs-basics/algorithms/几道常见的字符串算法题.md
diff --git a/docs/dataStructures-algorithms/几道常见的链表算法题.md b/docs/cs-basics/algorithms/几道常见的链表算法题.md
similarity index 97%
rename from docs/dataStructures-algorithms/几道常见的链表算法题.md
rename to docs/cs-basics/algorithms/几道常见的链表算法题.md
index 9daa0fc1..7b824368 100644
--- a/docs/dataStructures-algorithms/几道常见的链表算法题.md
+++ b/docs/cs-basics/algorithms/几道常见的链表算法题.md
@@ -50,7 +50,7 @@ Leetcode官方详细解答地址:
我们使用变量来跟踪进位,并从包含最低有效位的表头开始模拟逐
位相加的过程。
-
+
### Solution
@@ -98,7 +98,7 @@ public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
### 题目描述
> 剑指 offer:输入一个链表,反转链表后,输出链表的所有元素。
-
+
### 问题分析
@@ -269,7 +269,7 @@ public class Solution {
我们注意到这个问题可以容易地简化成另一个问题:删除从列表开头数起的第 (L - n + 1)个结点,其中 L是列表的长度。只要我们找到列表的长度 L,这个问题就很容易解决。
-
+
### Solution
diff --git a/docs/dataStructures-algorithms/剑指offer部分编程题.md b/docs/cs-basics/algorithms/剑指offer部分编程题.md
similarity index 83%
rename from docs/dataStructures-algorithms/剑指offer部分编程题.md
rename to docs/cs-basics/algorithms/剑指offer部分编程题.md
index b7b077fb..9b73888b 100644
--- a/docs/dataStructures-algorithms/剑指offer部分编程题.md
+++ b/docs/cs-basics/algorithms/剑指offer部分编程题.md
@@ -14,38 +14,36 @@ n<=39
**采用迭代法:**
```java
- int Fibonacci(int number) {
- if (number <= 0) {
- return 0;
- }
- if (number == 1 || number == 2) {
- return 1;
- }
- int first = 1, second = 1, third = 0;
- for (int i = 3; i <= number; i++) {
- third = first + second;
- first = second;
- second = third;
- }
- return third;
- }
+int Fibonacci(int number) {
+ if (number <= 0) {
+ return 0;
+ }
+ if (number == 1 || number == 2) {
+ return 1;
+ }
+ int first = 1, second = 1, third = 0;
+ for (int i = 3; i <= number; i++) {
+ third = first + second;
+ first = second;
+ second = third;
+ }
+ return third;
+}
```
**采用递归:**
```java
- public int Fibonacci(int n) {
-
- if (n <= 0) {
- return 0;
- }
- if (n == 1||n==2) {
- return 1;
- }
+public int Fibonacci(int n) {
+ if (n <= 0) {
+ return 0;
+ }
+ if (n == 1||n==2) {
+ return 1;
+ }
- return Fibonacci(n - 2) + Fibonacci(n - 1);
-
- }
+ return Fibonacci(n - 2) + Fibonacci(n - 1);
+}
```
### 二 跳台阶问题
@@ -71,24 +69,24 @@ f(1) = 1, f(2) = 2, f(3) = 3, f(4) = 5, 可以总结出f(n) = f(n-1) + f(n-2)
#### **示例代码:**
```java
- int jumpFloor(int number) {
- if (number <= 0) {
- return 0;
- }
- if (number == 1) {
- return 1;
- }
- if (number == 2) {
- return 2;
- }
- int first = 1, second = 2, third = 0;
- for (int i = 3; i <= number; i++) {
- third = first + second;
- first = second;
- second = third;
- }
- return third;
- }
+int jumpFloor(int number) {
+ if (number <= 0) {
+ return 0;
+ }
+ if (number == 1) {
+ return 1;
+ }
+ if (number == 2) {
+ return 2;
+ }
+ int first = 1, second = 2, third = 0;
+ for (int i = 3; i <= number; i++) {
+ third = first + second;
+ first = second;
+ second = third;
+ }
+ return third;
+}
```
### 三 变态跳台阶问题
@@ -113,9 +111,9 @@ f(n)=f(n-1)+f(n-2)+...+f(1)
#### **示例代码:**
```java
- int JumpFloorII(int number) {
- return 1 << --number;//2^(number-1)用位移操作进行,更快
- }
+int JumpFloorII(int number) {
+ return 1 << --number;//2^(number-1)用位移操作进行,更快
+}
```
#### **补充:**
@@ -124,7 +122,7 @@ f(n)=f(n-1)+f(n-2)+...+f(1)
1. “<<” : **左移运算符**,等同于乘2的n次方
2. “>>”: **右移运算符**,等同于除2的n次方
-3. “>>>” **无符号右移运算符**,不管移动前最高位是0还是1,右移后左侧产生的空位部分都以0来填充。与>>类似。
+3. “>>>” : **无符号右移运算符**,不管移动前最高位是0还是1,右移后左侧产生的空位部分都以0来填充。与>>类似。
例:
int a = 16;
int b = a << 2;//左移2,等同于16 * 2的2次方,也就是16 * 4
@@ -147,22 +145,22 @@ f(n)=f(n-1)+f(n-2)+...+f(1)
#### **示例代码:**
```java
- public boolean Find(int target, int [][] array) {
- //基本思路从左下角开始找,这样速度最快
- int row = array.length-1;//行
- int column = 0;//列
- //当行数大于0,当前列数小于总列数时循环条件成立
- while((row >= 0)&& (column< array[0].length)){
- if(array[row][column] > target){
- row--;
- }else if(array[row][column] < target){
- column++;
- }else{
- return true;
- }
+public boolean Find(int target, int [][] array) {
+ //基本思路从左下角开始找,这样速度最快
+ int row = array.length-1;//行
+ int column = 0;//列
+ //当行数大于0,当前列数小于总列数时循环条件成立
+ while((row >= 0)&& (column< array[0].length)){
+ if(array[row][column] > target){
+ row--;
+ }else if(array[row][column] < target){
+ column++;
+ }else{
+ return true;
}
- return false;
}
+ return false;
+}
```
### 五 替换空格
@@ -175,38 +173,37 @@ f(n)=f(n-1)+f(n-2)+...+f(1)
这道题不难,我们可以通过循环判断字符串的字符是否为空格,是的话就利用append()方法添加追加“%20”,否则还是追加原字符。
-或者最简单的方法就是利用: replaceAll(String regex,String replacement)方法了,一行代码就可以解决。
+或者最简单的方法就是利用:replaceAll(String regex,String replacement)方法了,一行代码就可以解决。
#### **示例代码:**
**常规做法:**
```java
- public String replaceSpace(StringBuffer str) {
- StringBuffer out=new StringBuffer();
- for (int i = 0; i < str.toString().length(); i++) {
- char b=str.charAt(i);
- if(String.valueOf(b).equals(" ")){
- out.append("%20");
- }else{
- out.append(b);
- }
+public String replaceSpace(StringBuffer str) {
+ StringBuffer out = new StringBuffer();
+ for (int i = 0; i < str.toString().length(); i++) {
+ char b = str.charAt(i);
+ if(String.valueOf(b).equals(" ")){
+ out.append("%20");
+ }else{
+ out.append(b);
}
- return out.toString();
}
+ return out.toString();
+}
```
**一行代码解决:**
```java
- public String replaceSpace(StringBuffer str) {
- //return str.toString().replaceAll(" ", "%20");
- //public String replaceAll(String regex,String replacement)
- //用给定的替换替换与给定的regular expression匹配的此字符串的每个子字符串。
- //\ 转义字符. 如果你要使用 "\" 本身, 则应该使用 "\\". String类型中的空格用“\s”表示,所以我这里猜测"\\s"就是代表空格的意思
- return str.toString().replaceAll("\\s", "%20");
- }
-
+public String replaceSpace(StringBuffer str) {
+ //return str.toString().replaceAll(" ", "%20");
+ //public String replaceAll(String regex,String replacement)
+ //用给定的替换替换与给定的regular expression匹配的此字符串的每个子字符串。
+ //\ 转义字符. 如果你要使用 "\" 本身, 则应该使用 "\\". String类型中的空格用“\s”表示,所以我这里猜测"\\s"就是代表空格的意思
+ return str.toString().replaceAll("\\s", "%20");
+}
```
### 六 数值的整数次方
@@ -279,17 +276,17 @@ public class Solution {
当然这一题也可以采用笨方法:累乘。不过这种方法的时间复杂度为O(n),这样没有前一种方法效率高。
```java
- // 使用累乘
- public double powerAnother(double base, int exponent) {
- double result = 1.0;
- for (int i = 0; i < Math.abs(exponent); i++) {
- result *= base;
- }
- if (exponent >= 0)
- return result;
- else
- return 1 / result;
+// 使用累乘
+public double powerAnother(double base, int exponent) {
+ double result = 1.0;
+ for (int i = 0; i < Math.abs(exponent); i++) {
+ result *= base;
}
+ if (exponent >= 0)
+ return result;
+ else
+ return 1 / result;
+}
```
### 七 调整数组顺序使奇数位于偶数前面
@@ -434,22 +431,21 @@ public class ListNode {
}
}*/
public class Solution {
-public ListNode ReverseList(ListNode head) {
- ListNode next = null;
- ListNode pre = null;
- while (head != null) {
- //保存要反转到头来的那个节点
- next = head.next;
- //要反转的那个节点指向已经反转的上一个节点
- head.next = pre;
- //上一个已经反转到头部的节点
- pre = head;
- //一直向链表尾走
- head = next;
+ public ListNode ReverseList(ListNode head) {
+ ListNode next = null;
+ ListNode pre = null;
+ while (head != null) {
+ //保存要反转到头来的那个节点
+ next = head.next;
+ //要反转的那个节点指向已经反转的上一个节点
+ head.next = pre;
+ //上一个已经反转到头部的节点
+ pre = head;
+ //一直向链表尾走
+ head = next;
+ }
+ return pre;
}
- return pre;
-}
-
}
```
@@ -538,20 +534,20 @@ public class Solution {
```java
public ListNode Merge(ListNode list1,ListNode list2) {
- if(list1 == null){
- return list2;
- }
- if(list2 == null){
- return list1;
- }
- if(list1.val <= list2.val){
- list1.next = Merge(list1.next, list2);
- return list1;
- }else{
- list2.next = Merge(list1, list2.next);
- return list2;
- }
- }
+ if(list1 == null){
+ return list2;
+ }
+ if(list2 == null){
+ return list1;
+ }
+ if(list1.val <= list2.val){
+ list1.next = Merge(list1.next, list2);
+ return list1;
+ }else{
+ list2.next = Merge(list1, list2.next);
+ return list2;
+ }
+}
```
### 十一 用两个栈实现队列
@@ -566,7 +562,7 @@ public ListNode Merge(ListNode list1,ListNode list2) {
**栈:**后进先出(LIFO)
**队列:** 先进先出
很明显我们需要根据JDK给我们提供的栈的一些基本方法来实现。先来看一下Stack类的一些基本方法:
-
+
既然题目给了我们两个栈,我们可以这样考虑当push的时候将元素push进stack1,pop的时候我们先把stack1的元素pop到stack2,然后再对stack2执行pop操作,这样就可以保证是先进先出的。(负[pop]负[pop]得正[先进先出])
@@ -642,8 +638,6 @@ https://www.nowcoder.com/questionTerminal/d77d11405cc7470d82554cb392585106
….
依次执行,最后辅助栈为空。如果不为空说明弹出序列不是该栈的弹出顺序。
-
-
#### **考察内容:**
栈
diff --git a/docs/dataStructures-algorithms/data-structure/bloom-filter.md b/docs/cs-basics/data-structure/bloom-filter.md
similarity index 62%
rename from docs/dataStructures-algorithms/data-structure/bloom-filter.md
rename to docs/cs-basics/data-structure/bloom-filter.md
index b9d129d2..ca6421e2 100644
--- a/docs/dataStructures-algorithms/data-structure/bloom-filter.md
+++ b/docs/cs-basics/data-structure/bloom-filter.md
@@ -6,18 +6,18 @@
2. 布隆过滤器的原理介绍。
3. 布隆过滤器使用场景。
4. 通过 Java 编程手动实现布隆过滤器。
-5. 利用Google开源的Guava中自带的布隆过滤器。
+5. 利用 Google 开源的 Guava 中自带的布隆过滤器。
6. Redis 中的布隆过滤器。
### 1.什么是布隆过滤器?
首先,我们需要了解布隆过滤器的概念。
-布隆过滤器(Bloom Filter)是一个叫做 Bloom 的老哥于1970年提出的。我们可以把它看作由二进制向量(或者说位数组)和一系列随机映射函数(哈希函数)两部分组成的数据结构。相比于我们平时常用的的 List、Map 、Set 等数据结构,它占用空间更少并且效率更高,但是缺点是其返回的结果是概率性的,而不是非常准确的。理论情况下添加到集合中的元素越多,误报的可能性就越大。并且,存放在布隆过滤器的数据不容易删除。
+布隆过滤器(Bloom Filter)是一个叫做 Bloom 的老哥于 1970 年提出的。我们可以把它看作由二进制向量(或者说位数组)和一系列随机映射函数(哈希函数)两部分组成的数据结构。相比于我们平时常用的的 List、Map 、Set 等数据结构,它占用空间更少并且效率更高,但是缺点是其返回的结果是概率性的,而不是非常准确的。理论情况下添加到集合中的元素越多,误报的可能性就越大。并且,存放在布隆过滤器的数据不容易删除。

-位数组中的每个元素都只占用 1 bit ,并且每个元素只能是 0 或者 1。这样申请一个 100w 个元素的位数组只占用 1000000Bit / 8 = 125000 Byte = 125000/1024 kb ≈ 122kb 的空间。
+位数组中的每个元素都只占用 1 bit ,并且每个元素只能是 0 或者 1。这样申请一个 100w 个元素的位数组只占用 1000000Bit / 8 = 125000 Byte = 125000/1024 kb ≈ 122kb 的空间。
总结:**一个名叫 Bloom 的人提出了一种来检索元素是否在给定大集合中的数据结构,这种数据结构是高效且性能很好的,但缺点是具有一定的错误识别率和删除难度。并且,理论情况下,添加到集合中的元素越多,误报的可能性就越大。**
@@ -35,11 +35,9 @@
举个简单的例子:
-
-

-如图所示,当字符串存储要加入到布隆过滤器中时,该字符串首先由多个哈希函数生成不同的哈希值,然后在对应的位数组的下表的元素设置为 1(当位数组初始化时 ,所有位置均为0)。当第二次存储相同字符串时,因为先前的对应位置已设置为 1,所以很容易知道此值已经存在(去重非常方便)。
+如图所示,当字符串存储要加入到布隆过滤器中时,该字符串首先由多个哈希函数生成不同的哈希值,然后将对应的位数组的下标设置为 1(当位数组初始化时,所有位置均为 0)。当第二次存储相同字符串时,因为先前的对应位置已设置为 1,所以很容易知道此值已经存在(去重非常方便)。
如果我们需要判断某个字符串是否在布隆过滤器中时,只需要对给定字符串再次进行相同的哈希计算,得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。
@@ -49,7 +47,7 @@
### 3.布隆过滤器使用场景
-1. 判断给定数据是否存在:比如判断一个数字是否存在于包含大量数字的数字集中(数字集很大,5亿以上!)、 防止缓存穿透(判断请求的数据是否有效避免直接绕过缓存请求数据库)等等、邮箱的垃圾邮件过滤、黑名单功能等等。
+1. 判断给定数据是否存在:比如判断一个数字是否存在于包含大量数字的数字集中(数字集很大,5 亿以上!)、 防止缓存穿透(判断请求的数据是否有效避免直接绕过缓存请求数据库)等等、邮箱的垃圾邮件过滤、黑名单功能等等。
2. 去重:比如爬给定网址的时候对已经爬取过的 URL 去重。
### 4.通过 Java 编程手动实现布隆过滤器
@@ -147,15 +145,15 @@ public class MyBloomFilter {
测试:
```java
- String value1 = "https://javaguide.cn/";
- String value2 = "https://github.com/Snailclimb";
- MyBloomFilter filter = new MyBloomFilter();
- System.out.println(filter.contains(value1));
- System.out.println(filter.contains(value2));
- filter.add(value1);
- filter.add(value2);
- System.out.println(filter.contains(value1));
- System.out.println(filter.contains(value2));
+String value1 = "https://javaguide.cn/";
+String value2 = "https://github.com/Snailclimb";
+MyBloomFilter filter = new MyBloomFilter();
+System.out.println(filter.contains(value1));
+System.out.println(filter.contains(value2));
+filter.add(value1);
+filter.add(value2);
+System.out.println(filter.contains(value1));
+System.out.println(filter.contains(value2));
```
Output:
@@ -170,15 +168,15 @@ true
测试:
```java
- Integer value1 = 13423;
- Integer value2 = 22131;
- MyBloomFilter filter = new MyBloomFilter();
- System.out.println(filter.contains(value1));
- System.out.println(filter.contains(value2));
- filter.add(value1);
- filter.add(value2);
- System.out.println(filter.contains(value1));
- System.out.println(filter.contains(value2));
+Integer value1 = 13423;
+Integer value2 = 22131;
+MyBloomFilter filter = new MyBloomFilter();
+System.out.println(filter.contains(value1));
+System.out.println(filter.contains(value2));
+filter.add(value1);
+filter.add(value2);
+System.out.println(filter.contains(value1));
+System.out.println(filter.contains(value2));
```
Output:
@@ -190,61 +188,62 @@ true
true
```
-### 5.利用Google开源的 Guava中自带的布隆过滤器
+### 5.利用 Google 开源的 Guava 中自带的布隆过滤器
自己实现的目的主要是为了让自己搞懂布隆过滤器的原理,Guava 中布隆过滤器的实现算是比较权威的,所以实际项目中我们不需要手动实现一个布隆过滤器。
首先我们需要在项目中引入 Guava 的依赖:
```java
-
- com.google.guava
- guava
- 28.0-jre
-
+
+ com.google.guava
+ guava
+ 28.0-jre
+
```
实际使用如下:
-我们创建了一个最多存放 最多 1500个整数的布隆过滤器,并且我们可以容忍误判的概率为百分之(0.01)
+我们创建了一个最多存放 最多 1500 个整数的布隆过滤器,并且我们可以容忍误判的概率为百分之(0.01)
```java
- // 创建布隆过滤器对象
- BloomFilter filter = BloomFilter.create(
- Funnels.integerFunnel(),
- 1500,
- 0.01);
- // 判断指定元素是否存在
- System.out.println(filter.mightContain(1));
- System.out.println(filter.mightContain(2));
- // 将元素添加进布隆过滤器
- filter.put(1);
- filter.put(2);
- System.out.println(filter.mightContain(1));
- System.out.println(filter.mightContain(2));
+// 创建布隆过滤器对象
+BloomFilter filter = BloomFilter.create(
+ Funnels.integerFunnel(),
+ 1500,
+ 0.01);
+// 判断指定元素是否存在
+System.out.println(filter.mightContain(1));
+System.out.println(filter.mightContain(2));
+// 将元素添加进布隆过滤器
+filter.put(1);
+filter.put(2);
+System.out.println(filter.mightContain(1));
+System.out.println(filter.mightContain(2));
```
-在我们的示例中,当`mightContain()` 方法返回*true*时,我们可以99%确定该元素在过滤器中,当过滤器返回*false*时,我们可以100%确定该元素不存在于过滤器中。
+在我们的示例中,当`mightContain()` 方法返回 _true_ 时,我们可以 99%确定该元素在过滤器中,当过滤器返回 _false_ 时,我们可以 100%确定该元素不存在于过滤器中。
**Guava 提供的布隆过滤器的实现还是很不错的(想要详细了解的可以看一下它的源码实现),但是它有一个重大的缺陷就是只能单机使用(另外,容量扩展也不容易),而现在互联网一般都是分布式的场景。为了解决这个问题,我们就需要用到 Redis 中的布隆过滤器了。**
### 6.Redis 中的布隆过滤器
-#### 6.1介绍
+#### 6.1 介绍
Redis v4.0 之后有了 Module(模块/插件) 功能,Redis Modules 让 Redis 可以使用外部模块扩展其功能 。布隆过滤器就是其中的 Module。详情可以查看 Redis 官方对 Redis Modules 的介绍 :https://redis.io/modules
-另外,官网推荐了一个 RedisBloom 作为 Redis 布隆过滤器的 Module,地址:https://github.com/RedisBloom/RedisBloom. 其他还有:
+另外,官网推荐了一个 RedisBloom 作为 Redis 布隆过滤器的 Module,地址:https://github.com/RedisBloom/RedisBloom
+其他还有:
-- redis-lua-scaling-bloom-filter (lua 脚本实现):https://github.com/erikdubbelboer/redis-lua-scaling-bloom-filter
-- pyreBloom(Python中的快速Redis 布隆过滤器) :https://github.com/seomoz/pyreBloom
+- redis-lua-scaling-bloom-filter(lua 脚本实现):https://github.com/erikdubbelboer/redis-lua-scaling-bloom-filter
+- pyreBloom(Python 中的快速 Redis 布隆过滤器) :https://github.com/seomoz/pyreBloom
- ......
RedisBloom 提供了多种语言的客户端支持,包括:Python、Java、JavaScript 和 PHP。
-#### 6.2使用Docker安装
+#### 6.2 使用 Docker 安装
-如果我们需要体验 Redis 中的布隆过滤器非常简单,通过 Docker 就可以了!我们直接在 Google 搜索**docker redis bloomfilter** 然后在排除广告的第一条搜素结果就找到了我们想要的答案(这是我平常解决问题的一种方式,分享一下),具体地址:https://hub.docker.com/r/redislabs/rebloom/ (介绍的很详细 )。
+如果我们需要体验 Redis 中的布隆过滤器非常简单,通过 Docker 就可以了!我们直接在 Google 搜索 **docker redis bloomfilter** 然后在排除广告的第一条搜素结果就找到了我们想要的答案(这是我平常解决问题的一种方式,分享一下),具体地址:https://hub.docker.com/r/redislabs/rebloom/ (介绍的很详细 )。
**具体操作如下:**
@@ -252,35 +251,35 @@ RedisBloom 提供了多种语言的客户端支持,包括:Python、Java、Ja
➜ ~ docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest
➜ ~ docker exec -it redis-redisbloom bash
root@21396d02c252:/data# redis-cli
-127.0.0.1:6379>
+127.0.0.1:6379>
```
-#### 6.3常用命令一览
+#### 6.3 常用命令一览
-> 注意: key:布隆过滤器的名称,item : 添加的元素。
+> 注意: key : 布隆过滤器的名称,item : 添加的元素。
-1. **`BF.ADD `**:将元素添加到布隆过滤器中,如果该过滤器尚不存在,则创建该过滤器。格式:`BF.ADD {key} {item}`。
-2. **`BF.MADD `** : 将一个或多个元素添加到“布隆过滤器”中,并创建一个尚不存在的过滤器。该命令的操作方式`BF.ADD`与之相同,只不过它允许多个输入并返回多个值。格式:`BF.MADD {key} {item} [item ...]` 。
-3. **`BF.EXISTS` ** : 确定元素是否在布隆过滤器中存在。格式:`BF.EXISTS {key} {item}`。
+1. **`BF.ADD`**:将元素添加到布隆过滤器中,如果该过滤器尚不存在,则创建该过滤器。格式:`BF.ADD {key} {item}`。
+2. **`BF.MADD`** : 将一个或多个元素添加到“布隆过滤器”中,并创建一个尚不存在的过滤器。该命令的操作方式`BF.ADD`与之相同,只不过它允许多个输入并返回多个值。格式:`BF.MADD {key} {item} [item ...]` 。
+3. **`BF.EXISTS`** : 确定元素是否在布隆过滤器中存在。格式:`BF.EXISTS {key} {item}`。
4. **`BF.MEXISTS`** : 确定一个或者多个元素是否在布隆过滤器中存在格式:`BF.MEXISTS {key} {item} [item ...]`。
另外,`BF.RESERVE` 命令需要单独介绍一下:
这个命令的格式如下:
-`BF.RESERVE {key} {error_rate} {capacity} [EXPANSION expansion] `。
+`BF.RESERVE {key} {error_rate} {capacity} [EXPANSION expansion]`。
下面简单介绍一下每个参数的具体含义:
1. key:布隆过滤器的名称
-2. error_rate :误报的期望概率。这应该是介于0到1之间的十进制值。例如,对于期望的误报率0.1%(1000中为1),error_rate应该设置为0.001。该数字越接近零,则每个项目的内存消耗越大,并且每个操作的CPU使用率越高。
-3. capacity: 过滤器的容量。当实际存储的元素个数超过这个值之后,性能将开始下降。实际的降级将取决于超出限制的程度。随着过滤器元素数量呈指数增长,性能将线性下降。
+2. error_rate : 期望的误报率。该值必须介于 0 到 1 之间。例如,对于期望的误报率 0.1%(1000 中为 1),error_rate 应该设置为 0.001。该数字越接近零,则每个项目的内存消耗越大,并且每个操作的 CPU 使用率越高。
+3. capacity: 过滤器的容量。当实际存储的元素个数超过这个值之后,性能将开始下降。实际的降级将取决于超出限制的程度。随着过滤器元素数量呈指数增长,性能将线性下降。
可选参数:
-- expansion:如果创建了一个新的子过滤器,则其大小将是当前过滤器的大小乘以`expansion`。默认扩展值为2。这意味着每个后续子过滤器将是前一个子过滤器的两倍。
+- expansion:如果创建了一个新的子过滤器,则其大小将是当前过滤器的大小乘以`expansion`。默认扩展值为 2。这意味着每个后续子过滤器将是前一个子过滤器的两倍。
-#### 6.4实际使用
+#### 6.4 实际使用
```shell
127.0.0.1:6379> BF.ADD myFilter java
@@ -294,4 +293,3 @@ root@21396d02c252:/data# redis-cli
127.0.0.1:6379> BF.EXISTS myFilter github
(integer) 0
```
-
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/图.png b/docs/cs-basics/data-structure/pictures/图/图.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/图.png
rename to docs/cs-basics/data-structure/pictures/图/图.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索1.drawio b/docs/cs-basics/data-structure/pictures/图/广度优先搜索1.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索1.drawio
rename to docs/cs-basics/data-structure/pictures/图/广度优先搜索1.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索1.png b/docs/cs-basics/data-structure/pictures/图/广度优先搜索1.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索1.png
rename to docs/cs-basics/data-structure/pictures/图/广度优先搜索1.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索2.drawio b/docs/cs-basics/data-structure/pictures/图/广度优先搜索2.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索2.drawio
rename to docs/cs-basics/data-structure/pictures/图/广度优先搜索2.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索2.png b/docs/cs-basics/data-structure/pictures/图/广度优先搜索2.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索2.png
rename to docs/cs-basics/data-structure/pictures/图/广度优先搜索2.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索3.drawio b/docs/cs-basics/data-structure/pictures/图/广度优先搜索3.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索3.drawio
rename to docs/cs-basics/data-structure/pictures/图/广度优先搜索3.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索3.png b/docs/cs-basics/data-structure/pictures/图/广度优先搜索3.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索3.png
rename to docs/cs-basics/data-structure/pictures/图/广度优先搜索3.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索4.drawio b/docs/cs-basics/data-structure/pictures/图/广度优先搜索4.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索4.drawio
rename to docs/cs-basics/data-structure/pictures/图/广度优先搜索4.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索4.png b/docs/cs-basics/data-structure/pictures/图/广度优先搜索4.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索4.png
rename to docs/cs-basics/data-structure/pictures/图/广度优先搜索4.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索5.drawio b/docs/cs-basics/data-structure/pictures/图/广度优先搜索5.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索5.drawio
rename to docs/cs-basics/data-structure/pictures/图/广度优先搜索5.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索5.png b/docs/cs-basics/data-structure/pictures/图/广度优先搜索5.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索5.png
rename to docs/cs-basics/data-structure/pictures/图/广度优先搜索5.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索6.drawio b/docs/cs-basics/data-structure/pictures/图/广度优先搜索6.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索6.drawio
rename to docs/cs-basics/data-structure/pictures/图/广度优先搜索6.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索6.png b/docs/cs-basics/data-structure/pictures/图/广度优先搜索6.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索6.png
rename to docs/cs-basics/data-structure/pictures/图/广度优先搜索6.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索图示.drawio b/docs/cs-basics/data-structure/pictures/图/广度优先搜索图示.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索图示.drawio
rename to docs/cs-basics/data-structure/pictures/图/广度优先搜索图示.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索图示.png b/docs/cs-basics/data-structure/pictures/图/广度优先搜索图示.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/广度优先搜索图示.png
rename to docs/cs-basics/data-structure/pictures/图/广度优先搜索图示.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/无向图的邻接矩阵存储.drawio b/docs/cs-basics/data-structure/pictures/图/无向图的邻接矩阵存储.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/无向图的邻接矩阵存储.drawio
rename to docs/cs-basics/data-structure/pictures/图/无向图的邻接矩阵存储.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/无向图的邻接矩阵存储.png b/docs/cs-basics/data-structure/pictures/图/无向图的邻接矩阵存储.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/无向图的邻接矩阵存储.png
rename to docs/cs-basics/data-structure/pictures/图/无向图的邻接矩阵存储.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/无向图的邻接表存储.drawio b/docs/cs-basics/data-structure/pictures/图/无向图的邻接表存储.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/无向图的邻接表存储.drawio
rename to docs/cs-basics/data-structure/pictures/图/无向图的邻接表存储.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/无向图的邻接表存储.png b/docs/cs-basics/data-structure/pictures/图/无向图的邻接表存储.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/无向图的邻接表存储.png
rename to docs/cs-basics/data-structure/pictures/图/无向图的邻接表存储.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/有向图的邻接矩阵存储.drawio b/docs/cs-basics/data-structure/pictures/图/有向图的邻接矩阵存储.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/有向图的邻接矩阵存储.drawio
rename to docs/cs-basics/data-structure/pictures/图/有向图的邻接矩阵存储.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/有向图的邻接矩阵存储.png b/docs/cs-basics/data-structure/pictures/图/有向图的邻接矩阵存储.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/有向图的邻接矩阵存储.png
rename to docs/cs-basics/data-structure/pictures/图/有向图的邻接矩阵存储.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/有向图的邻接矩阵存储的副本.drawio b/docs/cs-basics/data-structure/pictures/图/有向图的邻接矩阵存储的副本.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/有向图的邻接矩阵存储的副本.drawio
rename to docs/cs-basics/data-structure/pictures/图/有向图的邻接矩阵存储的副本.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/有向图的邻接表存储.drawio b/docs/cs-basics/data-structure/pictures/图/有向图的邻接表存储.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/有向图的邻接表存储.drawio
rename to docs/cs-basics/data-structure/pictures/图/有向图的邻接表存储.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/有向图的邻接表存储.png b/docs/cs-basics/data-structure/pictures/图/有向图的邻接表存储.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/有向图的邻接表存储.png
rename to docs/cs-basics/data-structure/pictures/图/有向图的邻接表存储.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索1.drawio b/docs/cs-basics/data-structure/pictures/图/深度优先搜索1.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索1.drawio
rename to docs/cs-basics/data-structure/pictures/图/深度优先搜索1.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索1.png b/docs/cs-basics/data-structure/pictures/图/深度优先搜索1.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索1.png
rename to docs/cs-basics/data-structure/pictures/图/深度优先搜索1.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索2.drawio b/docs/cs-basics/data-structure/pictures/图/深度优先搜索2.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索2.drawio
rename to docs/cs-basics/data-structure/pictures/图/深度优先搜索2.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索2.png b/docs/cs-basics/data-structure/pictures/图/深度优先搜索2.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索2.png
rename to docs/cs-basics/data-structure/pictures/图/深度优先搜索2.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索3.drawio b/docs/cs-basics/data-structure/pictures/图/深度优先搜索3.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索3.drawio
rename to docs/cs-basics/data-structure/pictures/图/深度优先搜索3.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索3.png b/docs/cs-basics/data-structure/pictures/图/深度优先搜索3.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索3.png
rename to docs/cs-basics/data-structure/pictures/图/深度优先搜索3.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索4.drawio b/docs/cs-basics/data-structure/pictures/图/深度优先搜索4.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索4.drawio
rename to docs/cs-basics/data-structure/pictures/图/深度优先搜索4.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索4.png b/docs/cs-basics/data-structure/pictures/图/深度优先搜索4.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索4.png
rename to docs/cs-basics/data-structure/pictures/图/深度优先搜索4.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索5.drawio b/docs/cs-basics/data-structure/pictures/图/深度优先搜索5.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索5.drawio
rename to docs/cs-basics/data-structure/pictures/图/深度优先搜索5.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索5.png b/docs/cs-basics/data-structure/pictures/图/深度优先搜索5.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索5.png
rename to docs/cs-basics/data-structure/pictures/图/深度优先搜索5.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索6.drawio b/docs/cs-basics/data-structure/pictures/图/深度优先搜索6.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索6.drawio
rename to docs/cs-basics/data-structure/pictures/图/深度优先搜索6.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索6.png b/docs/cs-basics/data-structure/pictures/图/深度优先搜索6.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索6.png
rename to docs/cs-basics/data-structure/pictures/图/深度优先搜索6.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索图示.drawio b/docs/cs-basics/data-structure/pictures/图/深度优先搜索图示.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索图示.drawio
rename to docs/cs-basics/data-structure/pictures/图/深度优先搜索图示.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索图示.png b/docs/cs-basics/data-structure/pictures/图/深度优先搜索图示.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/图/深度优先搜索图示.png
rename to docs/cs-basics/data-structure/pictures/图/深度优先搜索图示.png
diff --git a/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素1.png b/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素1.png
new file mode 100644
index 00000000..63381b52
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素1.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素2.png b/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素2.png
new file mode 100644
index 00000000..c23bbfa8
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素2.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素3.png b/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素3.png
new file mode 100644
index 00000000..46fdb57b
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素3.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素4.png b/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素4.png
new file mode 100644
index 00000000..a2e3a937
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素4.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素5.png b/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素5.png
new file mode 100644
index 00000000..1129eeac
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素5.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素6.png b/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素6.png
new file mode 100644
index 00000000..d18889c3
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/删除堆顶元素6.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/堆-插入元素1.png b/docs/cs-basics/data-structure/pictures/堆/堆-插入元素1.png
new file mode 100644
index 00000000..424fe3d9
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/堆-插入元素1.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/堆-插入元素2.png b/docs/cs-basics/data-structure/pictures/堆/堆-插入元素2.png
new file mode 100644
index 00000000..f58aa672
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/堆-插入元素2.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/堆-插入元素3.png b/docs/cs-basics/data-structure/pictures/堆/堆-插入元素3.png
new file mode 100644
index 00000000..24977de8
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/堆-插入元素3.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/堆1.png b/docs/cs-basics/data-structure/pictures/堆/堆1.png
new file mode 100644
index 00000000..fa08e776
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/堆1.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/堆2.png b/docs/cs-basics/data-structure/pictures/堆/堆2.png
new file mode 100644
index 00000000..ec8a89d0
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/堆2.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/堆排序1.png b/docs/cs-basics/data-structure/pictures/堆/堆排序1.png
new file mode 100644
index 00000000..655aa65a
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/堆排序1.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/堆排序2.png b/docs/cs-basics/data-structure/pictures/堆/堆排序2.png
new file mode 100644
index 00000000..fe60da90
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/堆排序2.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/堆排序3.png b/docs/cs-basics/data-structure/pictures/堆/堆排序3.png
new file mode 100644
index 00000000..61633b1d
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/堆排序3.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/堆排序4.png b/docs/cs-basics/data-structure/pictures/堆/堆排序4.png
new file mode 100644
index 00000000..e502b808
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/堆排序4.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/堆排序5.png b/docs/cs-basics/data-structure/pictures/堆/堆排序5.png
new file mode 100644
index 00000000..9d287858
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/堆排序5.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/堆排序6.png b/docs/cs-basics/data-structure/pictures/堆/堆排序6.png
new file mode 100644
index 00000000..85bf1308
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/堆排序6.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/堆的存储.png b/docs/cs-basics/data-structure/pictures/堆/堆的存储.png
new file mode 100644
index 00000000..de77a9af
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/堆的存储.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/建堆1.png b/docs/cs-basics/data-structure/pictures/堆/建堆1.png
new file mode 100644
index 00000000..f69153d0
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/建堆1.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/建堆2.png b/docs/cs-basics/data-structure/pictures/堆/建堆2.png
new file mode 100644
index 00000000..fcbf71ca
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/建堆2.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/建堆3.png b/docs/cs-basics/data-structure/pictures/堆/建堆3.png
new file mode 100644
index 00000000..c4f890b1
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/建堆3.png differ
diff --git a/docs/cs-basics/data-structure/pictures/堆/建堆4.png b/docs/cs-basics/data-structure/pictures/堆/建堆4.png
new file mode 100644
index 00000000..6d6c57fd
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/堆/建堆4.png differ
diff --git a/docs/cs-basics/data-structure/pictures/树/中序遍历.drawio b/docs/cs-basics/data-structure/pictures/树/中序遍历.drawio
new file mode 100644
index 00000000..f8fe81d2
--- /dev/null
+++ b/docs/cs-basics/data-structure/pictures/树/中序遍历.drawio
@@ -0,0 +1 @@
+7Vtbk6I4FP41eZwukgBJHr1g727tVk1VV+30PNKSVmbQuIit7q/fBAISBLVbFHfa8sHkJORyzpcv5xwR4MFs8xj7i+lfIuARQFawAXgIEILQcuWXkmwzCWMkE0ziMNCddoKn8F+uhZaWrsKAL42OiRBREi5M4VjM53ycGDI/jsXa7PYqInPWhT/he4KnsR/tS7+FQTLNpBSRnfw3Hk6m+czQZVnLzM87650sp34g1iUR9gAexEIkWWm2GfBIKS/XS/bcqKG1WFjM58kpD6yenT8ev/39O3VCf/C2ZpuF7X/Ro7z50UpvGCA3kuP1X9SSk63Wg/vPSq2z/yrmyZdlaqWe7ICcxWbXKEsT9d3Lx5CLecmFWgnFiEiuThpRVvrraZjwp4U/Vi1riSMpmyazSNagLPrLRWbZ13DDA7WIMIoGIhJxOhB+feXueCzlyyQWP3mpJSDsxbKKyd94nPBNo/pgYRSJZi5mPIm3sot+gGBtRw1kzHR9XYKFFk1LiMhlvgbipBh5ZytZ0OZ6h+lQjemqKp4HPXUGZG0u5txUq9x3vH0uV76XK0O1b6uobXUtm4MHe6emoke5DrGKx/zABrA+zn484ckxjO7bpaR3p0bvuSzmkZ+Eb+Zy64yhZ/gqQrmRndktYpidWBV7ZtvUT5WPX3Ug5jwgaiMCkU1sN1+JHtYm1GzF5iyZkvZmSYFT6OTjWMKNNLBc+POPM0GJTDLBoJ4aClk23a/BGJSajGF3zRj2tazc/0RWdq0bs7JzLSsPP5GVHWRa2SEdW9m9lpUfP5GVmXtjVibn+nibMHkulb/nPp0s7xw8Vcn9u8IrLCpX9grt/4NX6FaAQuAHvUKKqIk4hq7q+NFzAVYbRFhH4JLD0irBEh6EZQcAc7oEGKneN+yjYYdDD4UdrNOwg52JvhZR4Z6ICtwlKiiCDy7GDrapdEgQNI3pUPjAIGO27AZtNw9c301J2D2AGIdada1XAkxOs5d3e7xP5Pa49MbcHliXljzX7zl8wdT7Pccusi78HqvelldyfMw8FWQmcCB8INAhiBIHOZhWhj/5zrJqh8nvLFq7hGsxEGpkIEU0LTKQfYSBsukaGEiSQWIeCZNB9JEp040W+VE4mcvqWCKYS3lfUUs49qOebpiFQRA1cVssVvNAMdmwLW5ilfSKtc9Ndg3e0cW4qTlX2jIA0B0AilOcyuXUOQCa06gtA8C9AyD1R28NAM0Z1pYBAO8ASC/8CgBo1wBw971R6QI+6arWoqkHESdTMRFzP/pTiIU2yA+eJFv9zoO/SkRDbsV6cDpKxuVvXHwwGXeys3meOeqypBcJCkefKCik1q0Fha3lKt+V2n5/Cr3N80dOPICdpqWgZQZsxIwKXYjrWt8bFbKDmUwJzy7zUnWZzIu4BM7dJZAthNyYS5APfHkAkDsAFBlUg4LOAdD8JmXLAMB3AKhLxb41ADQnBs/zQod9rzcalF/DOOhm/jIWJpXf2N3OLYz3/c1LhH35j+NZ1HdtlzPH8VGXM+vYVdCHarJwngP6Huh5wLOBjNV6EHguYBZgA+AhQNOCycnjInbanToZXzGG8f5BfAyT6epFjUk9wCjwGKAEMDkLVZOq6RxAKWCoiY31KoYFp8en0AK0a+8FORnrA+apQs8GdNjKJr8OR0dXL/crNSA9EbXfEWCuXotUvlIFAT1c0glRBUZSWwxAv5d2HqUFojrkx/rmOCzyX3jU98c/J6m8OrlUjT7D0Nb1UijeTz8theLQZEIb7zNhQXtnUqGs7v6SkoVHuz/2YO8/
\ No newline at end of file
diff --git a/docs/cs-basics/data-structure/pictures/树/中序遍历.png b/docs/cs-basics/data-structure/pictures/树/中序遍历.png
new file mode 100644
index 00000000..3ad5782c
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/树/中序遍历.png differ
diff --git a/docs/cs-basics/data-structure/pictures/树/中序遍历2.drawio b/docs/cs-basics/data-structure/pictures/树/中序遍历2.drawio
new file mode 100644
index 00000000..31e3097a
--- /dev/null
+++ b/docs/cs-basics/data-structure/pictures/树/中序遍历2.drawio
@@ -0,0 +1 @@
+5ZhNc5swEIZ/jY/uYLDBHGPsuIdmpmMfmhxlkEGtYKkQBvLrK1niq46bJm0Zpj6hfSWtpH12NSMmlheXW4bS6AECTCemEZQTaz0xzdnMsMVHKpVSXNdRQshIoAe1wp48Yy0aWs1JgLPeQA5AOUn7og9Jgn3e0xBjUPSHHYH2V01RiC+EvY/opfqFBDxS6tJ0Wv0jJmFUrzyzXdUTo3qwPkkWoQCKjmRtJpbHALhqxaWHqQxeHRc17/5Kb7MxhhP+OxN22UPg7CLH2z06MV1Pt0bxPNVeTojm+sAT06bC3+ogt8wrHQf7ey73uTpCwqfZmdKdGGAu0rLtFK1Qfu9qH2Izh1rUQWg8mmJ3AqIwVkVEON6nyJc9hcgjoUU8psKaiSbKUkX2SEocyE0QSj2gwM6OrOMR274v9Iwz+IY7PYHjHgyjWfyEGcfl1fDNGigimzHEmLNKDNET7DojdSLPa7vopIWWok5G1BrSiRg2nltWoqFxvQGdeRVdlqLk/fQ6CaAE72WcjaaW+z8oO+7IKFtDUV7dEOW5NTLK86Eor2+IsjUfGeXFUJS3N0R56bjjomwPRXlzQ5QX5shq2bmMaCAeDtpMIJGhZZAngQzaWoYBGI8ghATRTwCpDu9XzHmlnz0o59APvogOqx7l/A+L2nzq9q1L7VxZlbbey0ie4deExJEhZz5+/U3BEQsxf23cJXGGKeLk1N/HX+e3HKpK72+oSh1jZFXqDlOlJeGdIhXWU12Vot2WqDT+fYX+QUXpqZ+BCI/ty/enu7e5i2sX6kbQs7p/IN7qSF0ZF47O6JvzvJQNwmx/pKjh7e8oa/MD
\ No newline at end of file
diff --git a/docs/cs-basics/data-structure/pictures/树/中序遍历2.png b/docs/cs-basics/data-structure/pictures/树/中序遍历2.png
new file mode 100644
index 00000000..fe6956b9
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/树/中序遍历2.png differ
diff --git a/docs/cs-basics/data-structure/pictures/树/先序遍历.drawio b/docs/cs-basics/data-structure/pictures/树/先序遍历.drawio
new file mode 100644
index 00000000..b92e5c29
--- /dev/null
+++ b/docs/cs-basics/data-structure/pictures/树/先序遍历.drawio
@@ -0,0 +1 @@
+7Vtbk6I4FP41eZwuIDfyKIq9D7tVU9u1tTOPKGllGo2L2Or8+k0kIEFQ2xvOtOWDyUnI5ZwvJ985IoDdyeo5CWbjv0TIY+BY4QrAHnAc27aI/FKSdSZhjGaCURKFutNW8BL95FpoaekiCvnc6JgKEafRzBQOxXTKh6khC5JELM1uryI2Z50FI74jeBkG8a703yhMx5nUdehW/gePRuN8ZpuwrGUS5J31TubjIBTLkgj6AHYTIdKsNFl1eayUl+sle67f0FosLOHT9JgHEuvvH2+rZPbTI6O56/T+WTrPX/Qo70G80BsGDonleN5ALTldaz2Q/xZqnd6rmKZf5hsrdWQHB89W20ZZGqnvTj6GXMwgF2olFCM6cnXSiLLiLcdRyl9mwVC1LCWOpGycTmJZs2UxmM8yy75GKx6qRURx3BWxSDYDwddXToZDKZ+niXjjpZaQsoFlFZO/8yTlq0b12YVRJJq5mPA0Wcsu+gGCtB01kCHW9WUJFlo0LiEilwUaiKNi5K2tZEGb6wOmc2pMV1XxNOyoMyBrUzHlplrlvpP1t3Lle7nSU/u2itpa17I5eLhzaip6lOsQi2TI92wA6uMcJCOeHsLorl1Kesc1es9lCY+DNHo3l1tnDD3DVxHJjWzNblPD7IRU7JltUz9VPn6VgaiFnxwXOdR2EEUkX4keFkHXbIXmLJmSdmbZAKfQyelYgo1uYD4Lpqd7gpIzyQTdetdQyLLpfg+PQZnpMVDbHgPdysreJ7Iytu/MyvhWVu59IisjaFoZw5atTG5l5edPZGWX3pmV6bkcbxWl30rl7zmnk+UtwVOVnN8VrLCo3JgVol+BFeIKUAg9lRVC10Qcdm5K/NxzAVYbRFgH4JLD0irB0t4LyxYAhtsEGKneN/hEgBHi7gs7cKthBzsTfRdEBTkSFbBNVFBoPxEIMUQulhe4bRoTI/uJ2YwhV7YgYtETXRIiexCDkVXXeiPA5Fz7+rTH/0S0B7M7oz12XVryXN6z/4Kp5z2HLrI2eI9Vb8sbER/XuDBsZl4n1H6iNqaSSGOJIrcy/NF3ll07TD4Jql3CrTyQ0+iBlKO5oAeyD3igbLoGDySdQWoeCdOD6CNTdjdaFMTRaCqrQ4lgLuWeci3RMIg7umEShWHc5NsSsZiGypP1LpV2typpd7Lrm1AN3p2r+abmXOmFAeA8AKCMS0wAoNYB0JxGvTAA8AMAGz56bwBozrBeGADwAQDl8qv0FLUNALLLRiUFfNFVrUVTDyJJx2IkpkH8pxAzbZAfPE3X+p2HYJGKhtyK9YRbSsblb1ycmIw7mmyeZ466LOlVgsL+JwoKqX1vQeHFcpUfSm1/PIV+yfNHjzyAraalmBmvUTMoxBTWtX40KHT3JjIlOttMS9UlMq/CCMiDEcgW4t4ZI8gHvj4A6AMAyhmgewNA84uUFwYAegAAlH4PuxsANOcFzyOhHa/nd/vltzD2sszfxsKk+hN76xaGu3TzGlFf/tt4FvTdmnHmOD7IOLOObcV8Tk0SzsfA80HHBz4CMlTr2MAngFmAdYHvAHdTMH3ysAidtqdOhleMQbh7EJ+jdLwYqDFdHzAX+Ay4FDA5i6smVdNh4LqAOU3eWK+iV/j05Bi3YKPae0FOxjzAfFXoIOD2LrLJr73+wdXL/UoNSCai9tsHjOi1SOUrVVDQgSWdUFVgdGOLLvA6m879TYGqDjmVuzsfFgcDHnvB8G20kVcnl6rRZ9hGul6KxL3N50KReOVdkCIfVvKEhds70xXK6vYfKVl4tP1fD/T/Bw==
\ No newline at end of file
diff --git a/docs/cs-basics/data-structure/pictures/树/先序遍历.png b/docs/cs-basics/data-structure/pictures/树/先序遍历.png
new file mode 100644
index 00000000..5c80cedf
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/树/先序遍历.png differ
diff --git a/docs/cs-basics/data-structure/pictures/树/后序遍历.drawio b/docs/cs-basics/data-structure/pictures/树/后序遍历.drawio
new file mode 100644
index 00000000..c324e801
--- /dev/null
+++ b/docs/cs-basics/data-structure/pictures/树/后序遍历.drawio
@@ -0,0 +1 @@
+7VvLluI2EP0aLacPlqzXEoPpLJKcySHJPHZurAZnDCLGdEN/fSQsGwtsoHmZTHNYIJVkPaqurqoKA1BnvHhMgunoNxmKGMBWuACoCyB0nBZRX1qyzCSc00wwTKLQdFoL+tGbMMKWkc6jUMysjqmUcRpNbeFATiZikFqyIEnkq93tWcb2rNNgKLYE/UEQb0u/RGE6yqQM0rX8FxENR/nMDuFZyzjIO5udzEZBKF9LIuQD1EmkTLPSeNERsVZerpfsuV5Na7GwREzSQx6I2vhvtYG/fv/jz7cvuP+9P38bfTKjvATx3GwYQBKr8bwnveR0afRA/p3rdXrPcpJ+mq2s1FYdIJ4u1o2qNNTf7XwMtZinXGiUUIwI1eqUEVXFex1FqehPg4FueVU4UrJROo5VzVHFYDbNLPscLUSoFxHFcUfGMlkNhJ6fBRkMlHyWJvKHKLWElD+1WsXkLyJJxaJWfU5hFIVmIcciTZaqi3mA5HY0QIbU1F9LsDCiUQkRuSwwQBwWI69tpQrGXO8wHaww3aaKJ2FbnwFVm8iJsNWq9p0sv5Yr38qVrt53q6gtTS2bQ4Rbp2ZDj2odcp4MxI4NIHOcg2Qo0n0Y3bZLSe+4Qu+5LBFxkEYv9nKrjGFm+CwjtZHC7DjnK2N2vGnPbJvmqfLx2xiIMPwAmQupA13qknwlZliEmd2K7FkyJW3NsgJOoZPjsYRqaWA2DSbHM0GJTDJBp5oaClk23c/BGJTajIGaZgz3Wlb2PpCVXX5jVsbXsnL3I1nZsa3s4oatTK5l5ccPZGWGb8zK9FQfbxGlX0vlb7lPp8prB09Xcv+u8AqLypW9Qvd/4RVuAAXzI71C6jAbcRRe1fFjpwKsMoho7YFLDstWCZbOTlg2ADDcJMDI5n1Djw07XLYr7KCNhh38RPSdERXkQFSgJlFBHeeBIISRy7C6wB3bmC5xHrjDuctUi0ta9EhKgmQHYtS4TSImPxaX93v8D+T3YHpjfo9TlZc81fHZfcNUOz77brImHJ9WtS2v5PkQ68ZwuH2fcOeBOphCRjHEiG0MfygF4eph8kmql3AtBoK1DKSJ5owMRPcwUDZdDQMpMkjtI2EziDkyZboxoiCOhhNVHSgECyX3NLVEgyBum4ZxFIZxHbclcj4JNZN1z8VNzOamgnRKeHcr8A4vxk31ydIzAwDdAaCN69oAQI0DoD6PemYAkDsAVg7prQGgPsV6ZgA4dwCAUkp9HYM0DACy7Y0qF7BvqkaLth5kko7kUE6C+Fcpp8Yg/4g0XZqXHoJ5KmuSK60H3FA2Ln/l4shs3MHO5mnmqEqTXiQo7H2goJDwWwsKz5asfFdu+/059HOeP3rgAWw0L8XseI3aQaHLkdXKjkxMsZ2pTAXPRhNTVbnMi/gE7t0n0OxEbswnyAe+PADwHQCaDeCtAaD+XcozAwDeAaDvLnRrAKjPDJ7mhnZ9r/fYKb9Vu9PP/GksTDZ/ZW/cwmjb4bxE3Jf/PJ6Ffdf2OXMc7/U5s45NRX2wIg3nY+D5oO0D3wUqWGs7wCdAxTG8A3wI2Kpgc/KgCJ7Wp04FWJwjtH0QH6N0NH/SYzIfcAZ8DpQzy9UsTE+qp8OAMcBhHRubVXQLTk8OoQXHrbwX1GTcA9zXhbYLWPcsm/zc7e1dvdqv0oBHV/vtAU7MWpTytSooaKOSTqgucLqyRQd47VXn3qpAdYc8Yrw5DouDJxF7weDHcCXfnFypxpxhxzX1UizurT6XicUR3GbCgvZOpEJVXf8pJQuP1n/tQf5/
\ No newline at end of file
diff --git a/docs/cs-basics/data-structure/pictures/树/后序遍历.png b/docs/cs-basics/data-structure/pictures/树/后序遍历.png
new file mode 100644
index 00000000..87bf512e
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/树/后序遍历.png differ
diff --git a/docs/cs-basics/data-structure/pictures/树/完全二叉树.drawio b/docs/cs-basics/data-structure/pictures/树/完全二叉树.drawio
new file mode 100644
index 00000000..107a75dd
--- /dev/null
+++ b/docs/cs-basics/data-structure/pictures/树/完全二叉树.drawio
@@ -0,0 +1 @@
+5VpLc+I4EP41OoaynpaOPJzsZatSNYedOW0ZWwHvGMwYM8D8+m3bsrGMCQwhhAyVQ9QtoUf3p+5PDYgOZ5un1F9M/05CHSPihBtER4gQjB0B/3LNttRIRUrFJI1CM2in+BL90kbpGO0qCvXSGpglSZxFC1sZJPO5DjJL56dpsraHvSSxverCn+g9xZfAj/e1/0RhNjWnIO5O/5eOJtNqZSxU2TPzq8HmJMupHybrhop6iA7TJMnK1mwz1HFuvMou5eceD/TWG0v1PDvlA5GOgn9/JWv68PTfdumwH6Px8sHM8tOPV+bAiIgY5huMoTHJG/Dfny2gMR8vF6VcDHhJYF04VrY1thI/VknV8bAsPNmHAYQvNrvOalanmgY2XM5Ur1apGzsg1kIEDgb+B2GwnkaZ/rLwg7xnDRAE3TSbxSBhaPrLRQmKl2ijw3xvURwPkzhJi4noy4sWQQD6ZZYm33WjJ3TV2HHqxZtmrmym00xvGipj9iedzHSWbmGI6aXCQMDcAVLJ6waijGraAFOl8w2GJ/XMOzdDw3j6N7xOOrzeNvE87OfXB6R5Mte2WcEU6fZrU/jWFEb5uZ1a2hrpgrZfJqs00K+ckJpQ4acTnR3Hvw6tKLDvyYaneIenKl2qYz+Lftqxo8t9ZoXnJCouUQUUZgOF8xYCynObTzXvensi1ZrIaU1UGmZvogJN9bHPBxg9KaxcIIyQOwojTArLqZR/cBhhB728XPjz8526hxXc7c5aVy73Z3iZUOe2vMyvdZfpHd1lzG0vM/rBXhbX8jK/Iy8zzG7Ly+5bid8myr422t8qogftHevLhYr01VSxFm6NKrI/gyqKM6kid6SNUUauShXlWyHZ+RZxjgCsArLTADJ+Fci3CEl+W5Bs57SzXy9S9ohkxMWEuUwQTFuMqLv7SoBVlwLszUZEcSL86E3Bj1PVE5RyyiQH8oGxDUasegorxST0MOG450GTMfsNxtl1H9ZVWfT931zsjt5cFN8YG8ddddm3ErXX81t3WDqWR28xK1bOvJG4RB0rXWFlJzMX91zMXSJdDiFMtqY/OWXSzmmqRVjnFq4Vs8jBmHXht6W4o7clF7cWs7qKvm9n8pcnRjX372GXNx+yPaeWD8RIEJ51GoHBdGp058ez6l58Mp7FlOwpIYjCDgOqJbkNQ+H0uMvyb0g5kVxwcV5A49QmWuzaROtwcfvCQcu9o6DV/lrqo2vb+HBx+8J0Wt4Rna5/8tCuTX2Ym7uq2+e92Xv805SZjhNl1plYTkhd/CMTEJbwlFc113WlXWTnGBK50ygPnZmBsBA9Lg5WoTilPUZ3u7gyp36PSv7VkLkHuONQ5Z8BmZRdBZntcsVe7f+dwSc6wOdxNPBQ30MeQ4NH1MfIEwjSvRoijyBZNGy6FNSI2SVPQJVSlO7n06com67G+ZzSQ0oiTyHpIgWryHzRfDmOpESKHGJTZhejOjOnp2R3zDqzOyymBkh5eaPPkBxd5JDPo8eju4fzggWANObnfURKmL2A8XNTuKhPGzZx84ZyC18M0aBfDH4sGm4+oMq4jagBOT6zQ4V9wU0oaUYDo/LjaDIHMYDrnj+PBjljiAI/7puOWRSG8SEWkyareZhzliLYxP5YxwM/+D4p9O3FwTTmp6KQw0q5EYAGxd+FSoPuccpa851mSCG/T2ZA3P0ctLysux/VUu9/
\ No newline at end of file
diff --git a/docs/cs-basics/data-structure/pictures/树/完全二叉树.png b/docs/cs-basics/data-structure/pictures/树/完全二叉树.png
new file mode 100644
index 00000000..bc0fe0dc
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/树/完全二叉树.png differ
diff --git a/docs/cs-basics/data-structure/pictures/树/平衡二叉树.drawio b/docs/cs-basics/data-structure/pictures/树/平衡二叉树.drawio
new file mode 100644
index 00000000..11133b53
--- /dev/null
+++ b/docs/cs-basics/data-structure/pictures/树/平衡二叉树.drawio
@@ -0,0 +1 @@
+7VnJdpswFP0aL+OjiWmZOEm7SIdzsmjSHQbZcAqIynKM+/UVRmIysR2SYKfuCr0r8TTcq6eHGOFJnH3ibhp8YT6NRgj42QhfjxCCEJjykSPrAnEcqwDmPPRVowq4D/9QBQKFLkOfLhoNBWORCNMm6LEkoZ5oYC7nbNVsNmNRs9fUndMt4N5zo230R+iLoEBtZFX4ZxrOA90zNJ2iJnZ1YzWTReD6bFWD8M0ITzhjoijF2YRG+eLpdSneu32mthwYp4k45IU7eumBOPrJb52v09m3AKzusgvl5cmNlmrCI2RG0t/VVBbmeUE+3TiVhWS6SAt702DGZL9yWmKt1sr8vWS64mKxYfJSNkBGmlWV2ivQbuSAC09lbxqujQA1OkJyYpJ/aVytglDQ+9T18pqVlKDEAhFH0oKy6C7SQhSzMKN+PrYwiiYsYnzjCM9m1PQ8iS8EZ79orca3nCkAZedPlAuaPbvysORTbgTKYir4WjZRL2BTSUDtAaTtVU1RCgpqYtKYqzQ8Lz1XNMuCYvoFrKMO1ttLnPiX+faRVsIS2lxWOW++fqgbj3XjOp83KK21svquPfW3tmhr5eXI2ZJ7dMeUsYodLp9TsW9DbDNZY8roYEpjnEauCJ+aw+2iT/XwnYWbTaSFQppCMYyWAoppqrfqe73tyGk5Ai1HxTpsOdqoqZx2f4Hhg8LKG4QRdEZhhLTUUarlWGGEPMvyInWT/qRuaQV201liRXf/BsslXafCsjHUXsZntJcRaLJM0JFZNodi2Tgjlg10Yixbr038slA81MqPOtGT5Srryw2d9JWpYmmcfKpIPmSqaPZMFQ1gNzVK0KCpov1aSXZ+i4A9AtNCBjUhw51C/hCSNI4pSdI+00hPSRJsj5FNkAURsYiJIG5lRN3VAwnWeRfBnnRENA+UHz6q/Bw8NjE2pDzkuYvKm0etRuCMHeg4xEYQEhNYPbXZErmBh/2y1vei7//RRc7oo6t9Q3f0RA12Xcy+NlPbfcB1Z2r7DtIPcSxC0M3+QKma0TivoNM8zUw4tqBhIdsyZDSxW+4PvvGzO93oTnDnEIaKWWiomGWeUcxqXxSVd7xHi1ldt77/Y1b3ddr+mIVON2YRuDOcvE3MImRnYOwds6RZ/XQtmle/rvHNXw==
\ No newline at end of file
diff --git a/docs/cs-basics/data-structure/pictures/树/平衡二叉树.png b/docs/cs-basics/data-structure/pictures/树/平衡二叉树.png
new file mode 100644
index 00000000..673f3e32
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/树/平衡二叉树.png differ
diff --git a/docs/cs-basics/data-structure/pictures/树/斜树.drawio b/docs/cs-basics/data-structure/pictures/树/斜树.drawio
new file mode 100644
index 00000000..6eeaa610
--- /dev/null
+++ b/docs/cs-basics/data-structure/pictures/树/斜树.drawio
@@ -0,0 +1 @@
+5VhNc9owEP01HMnY+rCtY0LSdibNTKc5NOnN2AJ7KixXiGD667vGsi0bSGlKcTI5sfskrbT73grBCE8WxUcV5smdjLkYIScuRvh6hJDrOh58lMimQhjzK2Cu0thMaoH79Bc3oGPQVRrzZWeillLoNO+CkcwyHukOFiol191pMym6u+bhnO8A91EodtFvaayTCg2Q3+KfeDpP6p1dj1Uji7CebDJZJmEs1xaEb0Z4oqTUlbUoJlyUxavrUq37cGC0OZjimT5mwfT2s7i+U1/V7ePDOLv8TokYj02Up1CsTMIj5AmIdzUFY14a8BkucjCy6TKv/O2EmYR9IS29MbXyfq5kPTBebpm8hAmI5kU7WEd16jBw4CpSs1sNWydAnY0QJAb8g3O1TlLN7/MwKkfWIEHAEr0Q4Llghsu8EsUsLXhcni0VYiKFVNtAeDbjXhQBvtRK/uDWSOyzqeM0mz9xpXlxsPJuwyc0ApcLrtUGppgFxDcSMD2AqPHXlqIMlFhiqrHQaHjeRG5pBsMw/Resoz2s90ucxZdl+4CXyYx3ywp5q82D7TzaznWZt9N4G+O9tPY83mnRXuXh5HKlIv5MytjcHaGac/2nhthl0mKK7mGqxhQXoU6fusfdR5/Z4YtMt01UC4V2hUJxTwFVmmaV3eu9QNTpBiJ9KVV12Am0VVOT9ssFho+6Vk5wjbjv6BqhPXVgPPA1Qg6y/G+k7mgF7afzAPNvm2UP91rXHZhleq5exu+ol323xzIbmGXvXCyT98Qy632f+wOz7A/y8ONFqh8s+9Gy2yWl8yqeiuTIpyJ+VU9FcqqnIjvvUzE4lSQdW5LOkZJ0LUm6b1+SdEhJUoYvPIwpJgGF2991u69Vn10wlzESwAjxHP+FciXBBQoIfIHCz2riod4mqDeKz6plNsj1OqgyvbegTM95VpnBaZRJn1Um/k/KBLf9D7Ga3v4Ti29+Aw==
\ No newline at end of file
diff --git a/docs/cs-basics/data-structure/pictures/树/斜树.png b/docs/cs-basics/data-structure/pictures/树/斜树.png
new file mode 100644
index 00000000..af129158
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/树/斜树.png differ
diff --git a/docs/cs-basics/data-structure/pictures/树/满二叉树.drawio b/docs/cs-basics/data-structure/pictures/树/满二叉树.drawio
new file mode 100644
index 00000000..8379028d
--- /dev/null
+++ b/docs/cs-basics/data-structure/pictures/树/满二叉树.drawio
@@ -0,0 +1 @@
+5VnLcpswFP0aL+PRG1gmTpq2M5nJTBZNuiNGNkwxogLHOF9fYSSMME5c2/WjXqF7JK4e5+jqCnp4MCnupZ+GDyLgcQ+BoOjh2x5CEAKmHiUyrxDPcypgLKNAN1oCT9E71yDQ6DQKeGY1zIWI8yi1waFIEj7MLcyXUszsZiMR272m/pivAE9DP15Ff0RBHlaoi5wl/pVH49D0DJlX1Ux801jPJAv9QMwaEL7r4YEUIq9Kk2LA43LxzLpU731ZU1sPTPIk3+SFZ4cW19/kzzALJmPy8P39HlxdaS9vfjzVE+4hFit/N6+qMC4L6ulPUlVIXrO0shcNRkL1q6aVz/Vasd9TYSqusgWT16oBommxrDRegXGjBlx5qnszcGMEyOoIqYkp/pVxMwujnD+l/rCsmSkJKizMJ7GyoCr6WVqJYhQVPCjHFsXxQMRCLhzh0Yiz4VDhWS7FL96oCRzvFYC68zcuc16sXXlY86k2AhcTnsu5aqJfwExLQO8BZOxZQ1EaChtiMpivNTyuPS9pVgXN9F+wjjpYby9xElyX20dZiUi4vaxq3nL+3DRemsZtOW9QW3Ntbbv2PFjZoq2VVyMXUznkH0wZ69jhyzHPP9sQq0w2mKIdTBlM8tjPozd7uF306R4eRbTYREYoxBYKpS0FVNPUbzX3etuR13IEWo6qdVhxtFBTPe3tBYY3Cit7CCPogsIIcZlFKqZHDiNkLctZ6ifbk7qiFdhNZ41V3f0fLCMMTotleqi9jC9oL0Nqs0zwkVlmh2KZXhDLBJLTYtnZNfErovy5UX4xiZ4qL7O+0jBJX50q1sbJp4rkLFNFtmWqSIFra5Sgg6aK7q6S7LyLgE8EZoQMGkKGHwr5LCRJjyrJ9pm29e3FdfvIJciBiDiEIYhbGVF39YEE6+1LsOcTEdmG8sPHlB/FXp9hTDFxqUo+ILTFCL2+Bz2PuKqGMOBsJ01C7DsYJYe9WJvPov/+zkUu6M6F4Yll47Dru+yuidrH51t3WPrsHD2LUxGCbvYPdCwC67iCnn2YObDvQOog16EqhLkt9xsfmbjTjemEdA7hUDFr/XeiPd8t2QXdLSk7tZjV9Qlh90x+/4lRnfv3oUObF9k+qO01MVIZj1xGasG41Nge4xncNKAdNc8intv3GEMeBESlWi61ZchAnzqk/ENKkUsZZdsFNIrtRIvsLdFS5vKna9V8+esa3/0B
\ No newline at end of file
diff --git a/docs/cs-basics/data-structure/pictures/树/满二叉树.png b/docs/cs-basics/data-structure/pictures/树/满二叉树.png
new file mode 100644
index 00000000..c0f30c04
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/树/满二叉树.png differ
diff --git a/docs/cs-basics/data-structure/pictures/树/链式存储二叉树.drawio b/docs/cs-basics/data-structure/pictures/树/链式存储二叉树.drawio
new file mode 100644
index 00000000..37585459
--- /dev/null
+++ b/docs/cs-basics/data-structure/pictures/树/链式存储二叉树.drawio
@@ -0,0 +1 @@
+7Zxbb9owFMc/DY+bcg95LPQyaUya2kntHl1iiFcTZ8bc+ul3HBxIloXCgMQPFkjEx5fE/h38z3EMPXc4Wz9wlCXfWIxpz7Hidc+97TmObVsBfEjLZmuJonBrmHISq0J7wxN5x8poKeuCxHheKSgYo4JkVeOYpSkei4oNcc5W1WITRqtnzdAU1wxPY0Tr1mcSi2Rr7Tvh3v4Fk2lSnNkOom3ODBWFVU/mCYrZqmRy73rukDMmtkez9RBTOXjFuGzr3Tfk7i6M41QcU2HgfX9xfi98h44e3r/es+dF9PbJUdcmNkWHcQz9V8mUpfAx4GyRxlg2Y0GKcZGwKUsRHTGWgdEG4y8sxEbRQwvBwJSIGVW5cIV88yLrf/aL5E/VXJ64XVdSG5WaC87e8JBRxvOLcy0rCPw+5ExYKkp2fyhfYN92R/ahcZSUac4WfIwPDU1fuRviUywOFfR2NOFrgNkMQx+gIscUCbKsXglS/jjdldsjgwNF7QSCriF4NkE76JKgZwge/modQbDfJUDfADwboOt0STAwBA/PjcdMop0SDA3BswmWbks7IKjEeonoQp3pFglUwwq30Zk8FOhVmsp0VgkR+ClD+WisIB7JRx5xoXh6EgUECgKRFHNVacwoRdmc5K3lsMYJofEIbdhCFOcpUjmuwqlkbUTJNIXjMVCRTQ6WmAsC4cONypiROM6vc0IoLYF27KF145/hGvJEeH3YOeooVQWvCK9UVOb1VXq1j3HsInBJSvFNaF2JftQIGnolCKKPEN2hdHoM8zrSmLPsR+H+0pAxIoHdLWHA5spWJqQmDJHPCzKT4klR95UJwWYqwdXY7BrNB8YfwBuGaijnCR96M4S0vU/DWxbnwDcFBwB/lG1gNBcrPBfX84pT7p0K1zjSM9xreUbhqqWJYSRhXNJf8rUDtPeXs12BAYgJzcP9BGYAnLbMNPo30xLEoFWGdo3hY35aA/F0iOsqwM6YOkawWxPsMNRNsO36wpNR7JYmhqZQSxvJ9oxknw7V1UyzfaPZl6Ooi2gHRrRbE+3+X6Ltel7Xol1fJjOi3dbM0PCQSRvRri/AGdH+EGqomWhHRrQvR1ET0XbqK2BGtK8l2pF2kfbuQY8R7dZFu+mRmC6i7dQX4Yxofwi1YQbobH53jWhfjqIuol1fAzOifS3R9m3dIm2nvinMiHZbM0PDhlxtRLu+CGdE+0OovmaiHRrRvhxFXUTbbEJrT7SDqCrau1/KdCfaZhdad6Kt+Ta0omEj2qdA1Wwfmmv2oV2Qoiai7dbXwIxoX020tds57pqNaJ2JdtOvtrQRbbMR7T+garYRzTUb0S5I8eqiDcn9nxrkeaW/hnDv/gA=
\ No newline at end of file
diff --git a/docs/cs-basics/data-structure/pictures/树/链式存储二叉树.png b/docs/cs-basics/data-structure/pictures/树/链式存储二叉树.png
new file mode 100644
index 00000000..c0ce15b7
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/树/链式存储二叉树.png differ
diff --git a/docs/cs-basics/data-structure/pictures/树/顺序存储.drawio b/docs/cs-basics/data-structure/pictures/树/顺序存储.drawio
new file mode 100644
index 00000000..1ddbb02d
--- /dev/null
+++ b/docs/cs-basics/data-structure/pictures/树/顺序存储.drawio
@@ -0,0 +1 @@
+7V1dc5s4FP01PDYDSAh49FfSh3amu+nMto8yyDYtRi6WE3t//UogMGCwiSHgjZlkJuiCJXHP0dXVMSgKmKz3TyHerL5Sl/iKrrp7BUwVXdc0FfE/wnKILbZtxoZl6LnyoqPh2fuXSKMqrTvPJdvchYxSn3mbvNGhQUAclrPhMKSv+csW1M+3usFLcmJ4drB/av3Hc9kqtlq6ebR/Jt5ylbSsITs+s8bJxfJOtivs0teMCcwUMAkpZfHRej8hvnBe4pf4c48VZ9OOhSRgdT7w9OMPePm6oQxPv88OwRr9+uvwSY9recH+Tt6woiOf1zeeiy6zg/QD+rMT/RwvaMA+bSOURvwC3djsjyf50VL8HSV18M7ME6N0QlqjznvHQeSF8evKY+R5gx1x5pXziNtWbO3zksYP8XYTI7vw9sQVnfB8f0J9GkYVgcWCIMfh9i0L6W+SOeOa9lxV08ZfSMjIvtJ9WgoKZzOha8LCA79EfsBMcJREBglDXzO0kKZVhhGJDUsiLtOaj1jxAwnXG6ADJdAVXRy4IzEGeCmgAcm7ld93ePiRLfzMFqbivtW0dJCluA3inoyagh95P+gudMiZG4ByOONwSdgljp7ikvG7UeL3xBYSHzPvJd/dMjBkC9+ox28khR0l8UrCjvQCnvFtyk9lh1+hItMyHnQL6qamQxOipCcJm2wrfxbkW4mddNJKRJzUJ9dzCVaGge0GB9dHgkwwiQ2T8tCQ2uLmPkbEsMx8xIB9RwyjK5THd4SyYd8YyqgrlKf3hLJWQNnuGWWzK5Sf7ghl27gxlK2mOd7eYz8yxz+TnI4fHxM8UUjyuzQrTAsdZ4XG/yIrLBAFgSuzQkuzchUZmt5p4mc3JVjpIkK9QJeElmqGltpZWvZAMNQnwczCfJPOP29edkDrzLIDar0uO5Kbupp+LdLCrEkL2CctLE17QAAYAFoGn8G1PJqGqj3Ymm1Di5+BSDWvjEk6OkMZQ1V7pYzWVeIzu6PEBxWXqn0nPsk02Grmc36KKc98Lk1lPUxMWgWWHaU+KDdlaHaeOEB7MDXD1C3T0A1gFaqvG4KQXVpN0oha2oWuIlCZ8PouEejzHUUgCAoJj9l3BCrTRK+JQPWT3OuS6RYjEKobgcANRyDrbHBoJwIZ2tkw994RqFrIFYGmxQikXYhAcXMVEYgHA5YfEvkIIodMNtxIE/a9ZcCLDmcw4faxCC2eg/2RPLH2XNevim0h3QWuiGTTtrIjKx+bgH4am2AJ34tfFbUXm6o13pYJoA8EEMMdFtLj3glQLf+2TAAwEEBkGfqtEaBMGX4XAsCBAALcwgLZUPsmQJly+y4EMAYCKBmF/1YIkFT8/gSwBgIop98AGyUSWbcEKFNCC97frvBGHDI89wvL0zKnbfnChclnMIXbHI4s9gLh/egzDvV9vNl6UWXxFSvPd7/gA92xpJmkVNQRXEysRamOgByLzBdtBWqUgwkh4wQmpHYpJCSw1MGJ3yfzsP83cRgOlnUgO4XEDenme7KCF4aNWIeScPbCXbiVtpLxxuhGnvTJIvnsnDJG17IQSm+llUauMsb8lztvoj4YisHvZsLL2rHMf8XlIZvQgEOPvQhIgrfslWxZXdTPDIGzOkQp1vDdsK7WBmsHZbVWUPZ4dNs3CcxNKBc9ao2PlGvMJsrRX/iRkLbiUZ0EzWih16BFl6yofryyZVaoAyPexIh9ng29EaQFQa8eQRoJevdLEK1kvd8tQ1pQ/OoxpJHid8cMsfpmSAuSYD2GNJIE75chKfS9MaQFzbAeQxpphvfLEFCiKXXLkBZExXoMaSQq3jFD+s5UQQuqYz2GoIEh1zCk7JupbhlS/YBmywwxB4ZcxZC+M1VQJoi+C0MafXNxvwwx+s5UQY132wfJvFXJ/JCHtDcFHXSmlcYtDAGiegDejoIOygTSAY4bka9BZ+Jkxc4yw3g9T5De5WvQmTg5HhhyFUN6XxR0Jk5e2oJmYMhtytegM3FyOjDkGob0Ll/DzsTJS+8GDwy5TfkadiZOPg4MuYYhvcvXsDNx8tLGSgNDblO+htXPeJ7A2IwynyvZUGN71ftlSO/yNYQnaIjXs59lUXow/4YCDdmKLmmA/S80cq1A5xdh7CCfx8c7RvPYHTeseDCUfjbrksHy2s26ar8I3gyOzva9vDTpf6QNG8zivpd9bxkDy1TIoo9r7WWmvmU0vX2LvRbHH6y7Z0yvu1bZ+b0UzPyGDQYAZWffumGDfX6jM7vXXatgZ+9rN3ps5sO8rGeifHDq/W1N2Nn72o2eivkwBLD1GyOAUaJNzQxlPFNGM2UGFZ47jDRlhhQ+jdoTZaYrVnSQJ4mTzuVHHvD53rZFSlykxpPHVru5qNOaKbalzGzFMhWbt2KJRkVzhmJZiq1X0UP2ItVD52EdomqwlKi8MXus2DNxMIKKNW3lJr9NHy/2nt8v9wAfGeJ+HxUbyb5w5wtXmMoIZHxiigPbjLCYKONRdPFjdGCKC5IE5uaGjY/nxB9j5/cyshcb566RSwkNynImNRxHP+0MPlTYLgWVDL50pDUcfbx4/Bcq8XR9/Ec0YPYf
\ No newline at end of file
diff --git a/docs/cs-basics/data-structure/pictures/树/顺序存储.png b/docs/cs-basics/data-structure/pictures/树/顺序存储.png
new file mode 100644
index 00000000..33f3c6e3
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/树/顺序存储.png differ
diff --git a/docs/cs-basics/data-structure/pictures/树/顺序存储2.drawio b/docs/cs-basics/data-structure/pictures/树/顺序存储2.drawio
new file mode 100644
index 00000000..8048a1a7
--- /dev/null
+++ b/docs/cs-basics/data-structure/pictures/树/顺序存储2.drawio
@@ -0,0 +1 @@
+7V1dc5s4FP01PDaDAPHxaDtOOp12p7PZ2W32ZUc2ss2WWC6WE3t//UogMGBhE4OBFqaZKbrCCO45ulwdybKiT172jwHarL4QF/uKprp7Rb9XNA0A1WT/ccshsjiOFRmWgeeKk46GJ+8/LIyqsO48F28zJ1JCfOptssY5Wa/xnGZsKAjIW/a0BfGzrW7QEp8YnubIP7X+5bl0FVltzTraP2JvuYpbBqYT1byg+GTxJNsVcslbyqRPFX0SEEKjo5f9BPvcebFfos89FNQmNxbgNS3zgd/G33/s9s92oH3Bn/w9/DH+MvsgrvKK/J14YEUzfXa98YzfMj0IP5g/dvw+xwuyph+2IUojdoIGN/tjJTta8v9H8TXYzcxio3BCckWN3R0DkRXGbyuP4qcNmvOaN8YjZlvRF5+VADtE202E7MLbY5ffhOf7E+KTILyQvlhgcz5n9i0NyHecqnEtZ6aqSeNpX8UPjgOK9ymT8N0jJi+YBgd2iqi1ocBREFmPcX1L0UKYVilGxDYkiLhMrnzEih0IuN4BnSaBLu/itTvifYCV1mSNs25lrggO39KF53Thnj+3mpQOolToyS3ZBXN85n510XtRsMT0MiWxm+mYp7ik/A4lfo9tAfYR9V6z3VkGhmjhK/HYkx1hj3u6gN3Uc3hGzy0+le5+uQs5AN5ptqFZQDMsw4zvRFzWUO1srZ5tJfLaSSshcRKfXM8lvTAMbDdofX0kSAWTyDCRh4bEFjX3a0QMoKrZkGG0HTKMpmAe9whmS+sYyrAplO97hLJpZFGGassom02h/NAjlIFqdwxmq2qWt/fot9Txc5zVseNjiscLcYaX5IVJ4bZ5ofFT5oVWjihJfHh3XmjYWcbFCDeU+tlVCSYdRqgX6BLTUk3REpyl5e0JBjtFMDv/wtGuJJht2ecGHlqrAw+nIvuuZ4VZkhV6p1jhGODO1HWoGzYjhAayYEIA7hzgsJDCagxTta4MSdA8wxgI1DYZEwuDt098HnuU+OQHMdBuOe8BMmWyauJz/g0jT3wuvcmuj0CwbOKjdioEWU7mjQGc7PuEBSgLQIu9dqAGdTt3+dIvLU16mbgRIL2FpiKQ1lQE+tijCHSilrUfgmSq6K8VguKofzkGaT9TDJLX1huDoLy2qRhULOXyUFNjDAIXYlDUXEEMYn2fZvtENoaIPpMOOMKEfG+5ZsU54zRm9jGPJN4c+SNR8eK5rl8U3QKyW7s8lp3vH++Y/QPZ4KTrp8HJkPA9P2irLzgVq7w1E0AbCMBqTCun8rdOgGIBuGYC6AMBFD4o7hoBZNLwTQhgDATgESCfn4K2CSCTbm9CAGcgACdAXvKXzA01SwDnxNk8P38SReHFrB9IQFdkSdbI/0zIRgDyL6b0INbeoR0lilzhV++g0siUULzQr64podKjjWoLshpTJqc90gWcvDLZ9oysVlmZvGqC9f0Tudf3v9KyQLcmRwDIjsmtrCwADV1W+15ZAKhnJ9QYP9ucHonJePukwBySAh5mnI5lhVrxWs6aCWANBAjDAewaA5rTBgdpIExRzI4NDDSZOJjz/naFNvyQopmfS1BkTtuy9xYVQwTutjlDFnlr7v3wM3Pi+2iz9cKLRWesPN/9jA5kR+Nm4lI+k3QRthfSTNKc23i2qAemRLERMFmSjuqojaaSMg2vACf26NRD/u8sJUfrZRnITiFxA7L5I87puGHD0xAcTF+ZV7fCJulvNBwp8kofL+LPzgil5EUUAuGt5KKhq+CY/THnTfjIEbKnmbAyOJbZHz89oBOyZtAjL8QWoy19w1sqRf083y9z4RLW+WV89WFdg1ynlorKHotu+yqBuQrlwm/hoSPlKrOJMFAXfjiUWrGojtfvoIV5DS3MRllRg4ZXjhXqwIh3MWKfZUNrBJGtkLwJQSpN9PaXIEDydZ9GGRJnNbdniDYw5CqGSBYyNcuQ4m9518yQSuPB/jIkgb41htSgGpZjSKW55P4yRJdICs0ypAZZsRxD4MCQqxjSdqaq1yA7lmNIpZmH/jJE9sX0ZhlSw5rFcgypNDXRY4a0nqnWsKixHEPsgSHXMAS2nqkWy6iz6+lRsMBtgD2/9rE12It10gqwgwJRdMA9xr31pLJY/qyCe4HWOeCe/zpwW7gbxaJmFdwLFMwB9/yuJq3hXnVDSjnuBbrkgHt+s5HWcJcJkDXCM6yEOIH+kIW0tYURRmPKYtTCMO479rbuLowwZGriAEdHViUYjSl5BXtJD/2146sSjMaUvPHAkKsY0nqq39iCyEubTg8M6eaqBKOxxZH3A0OuYUjrqxKMEvtH9hiettNEWKzilf5GdLkOXOkb0f1lSOtLAmCx3lczQyptY95jhrSdJsIbK4M/Nzytz8fDGn46plwHrrQdb38Z0vrUPTQGNLozoQ7hgEZ3prmhOaDRnclnWKxI1fwuq7Sxc38Z0uQ0NWtt/s/u9dMfSH3Wta+Pf/+528h+LnMKFTb6HE2VqaGwQcYIKFNTcVTFmShTTbHDg6xoNU+2NzjyRF8sHId/xyJPnUePrnYzfk17qji2MnUU21Ic1orNG+XNQcW2FUdTCuQqcReJxDULyhAZGFIis8acseJM+cHIUOz7Wh7y6/3Dxbtnz8s8MLbC531QHFPcC3M+d4WljPSUTyx+4FghFhNlPApPfggPLH5CHGo6tx2Jj2bYH6P592VozzfOXCM24QCGKKd2yxiH/5IeeNKxJBtoXI7G8aYmkswl2cGk4q4mrHj8HdxoJ6zjrwnr0/8B
\ No newline at end of file
diff --git a/docs/cs-basics/data-structure/pictures/树/顺序存储2.png b/docs/cs-basics/data-structure/pictures/树/顺序存储2.png
new file mode 100644
index 00000000..70c6da26
Binary files /dev/null and b/docs/cs-basics/data-structure/pictures/树/顺序存储2.png differ
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/单链表2.png b/docs/cs-basics/data-structure/pictures/线性数据结构/单链表2.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/单链表2.png
rename to docs/cs-basics/data-structure/pictures/线性数据结构/单链表2.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/双向循环链表.png b/docs/cs-basics/data-structure/pictures/线性数据结构/双向循环链表.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/双向循环链表.png
rename to docs/cs-basics/data-structure/pictures/线性数据结构/双向循环链表.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/双向链表.png b/docs/cs-basics/data-structure/pictures/线性数据结构/双向链表.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/双向链表.png
rename to docs/cs-basics/data-structure/pictures/线性数据结构/双向链表.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/循环队列-堆满.png b/docs/cs-basics/data-structure/pictures/线性数据结构/循环队列-堆满.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/循环队列-堆满.png
rename to docs/cs-basics/data-structure/pictures/线性数据结构/循环队列-堆满.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/数组.png b/docs/cs-basics/data-structure/pictures/线性数据结构/数组.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/数组.png
rename to docs/cs-basics/data-structure/pictures/线性数据结构/数组.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/栈.png b/docs/cs-basics/data-structure/pictures/线性数据结构/栈.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/栈.png
rename to docs/cs-basics/data-structure/pictures/线性数据结构/栈.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/栈实现浏览器倒退和前进.drawio b/docs/cs-basics/data-structure/pictures/线性数据结构/栈实现浏览器倒退和前进.drawio
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/栈实现浏览器倒退和前进.drawio
rename to docs/cs-basics/data-structure/pictures/线性数据结构/栈实现浏览器倒退和前进.drawio
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/栈实现浏览器倒退和前进.png b/docs/cs-basics/data-structure/pictures/线性数据结构/栈实现浏览器倒退和前进.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/栈实现浏览器倒退和前进.png
rename to docs/cs-basics/data-structure/pictures/线性数据结构/栈实现浏览器倒退和前进.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/队列.png b/docs/cs-basics/data-structure/pictures/线性数据结构/队列.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/队列.png
rename to docs/cs-basics/data-structure/pictures/线性数据结构/队列.png
diff --git a/docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/顺序队列假溢出.png b/docs/cs-basics/data-structure/pictures/线性数据结构/顺序队列假溢出.png
similarity index 100%
rename from docs/dataStructures-algorithms/data-structure/pictures/线性数据结构/顺序队列假溢出.png
rename to docs/cs-basics/data-structure/pictures/线性数据结构/顺序队列假溢出.png
diff --git a/docs/dataStructures-algorithms/data-structure/图.md b/docs/cs-basics/data-structure/图.md
similarity index 99%
rename from docs/dataStructures-algorithms/data-structure/图.md
rename to docs/cs-basics/data-structure/图.md
index 71a00d8f..32123a46 100644
--- a/docs/dataStructures-algorithms/data-structure/图.md
+++ b/docs/cs-basics/data-structure/图.md
@@ -11,7 +11,7 @@
- 线性数据结构的元素满足唯一的线性关系,每个元素(除第一个和最后一个外)只有一个直接前趋和一个直接后继。
- 树形数据结构的元素之间有着明显的层次关系。
-但是,树形结构的元素之间的关系是任意的。
+但是,图形结构的元素之间的关系是任意的。
**何为图呢?** 简单来说,图就是由顶点的有穷非空集合和顶点之间的边组成的集合。通常表示为:**G(V,E)**,其中,G表示一个图,V表示顶点的集合,E表示边的集合。
diff --git a/docs/cs-basics/data-structure/堆.md b/docs/cs-basics/data-structure/堆.md
new file mode 100644
index 00000000..cd186132
--- /dev/null
+++ b/docs/cs-basics/data-structure/堆.md
@@ -0,0 +1,192 @@
+# 堆
+
+## 什么是堆
+
+堆是一种满足以下条件的树:
+
+堆中的每一个节点值都大于等于(或小于等于)子树中所有节点的值。或者说,任意一个节点的值都大于等于(或小于等于)所有子节点的值。
+
+> 大家可以把堆(最大堆)理解为一个公司,这个公司很公平,谁能力强谁就当老大,不存在弱的人当老大,老大手底下的人一定不会比他强。这样有助于理解后续堆的操作。
+
+**!!!特别提示:**
+
+- 很多博客说堆是完全二叉树,其实并非如此,**堆不一定是完全二叉树**,只是为了方便存储和索引,我们通常用完全二叉树的形式来表示堆,事实上,广为人知的斐波那契堆和二项堆就不是完全二叉树,它们甚至都不是二叉树。
+- (**二叉**)堆是一个数组,它可以被看成是一个 **近似的完全二叉树**。——《算法导论》第三版
+
+大家可以尝试判断下面给出的图是否是二叉树?
+
+
+
+第1个和第2个是堆。第1个是最大堆,每个节点都比子树中所有节点大。第2个是最小堆,每个节点都比子树中所有节点小。
+
+第3个不是,第三个中,根结点1比2和15小,而15却比3大,19比5大,不满足堆的性质。
+
+## 堆的用途
+当我们只关心所有数据中的最大值或者最小值,存在多次获取最大值或者最小值,多次插入或删除数据时,就可以使用堆。
+
+有小伙伴可能会想到用有序数组,初始化一个有序数组时间复杂度是 `O(nlog(n))`,查找最大值或者最小值时间复杂度都是 `O(1)`,但是,涉及到更新(插入或删除)数据时,时间复杂度为 `O(n)`,即使是使用复杂度为 `O(log(n))` 的二分法找到要插入或者删除的数据,在移动数据时也需要 `O(n)` 的时间复杂度。
+
+**相对于有序数组而言,堆的主要优势在于更新数据效率较高。** 堆的初始化时间复杂度为 `O(nlog(n))`,堆可以做到`O(1)`时间复杂度取出最大值或者最小值,`O(log(n))`时间复杂度插入或者删除数据,具体操作在后续章节详细介绍。
+
+## 堆的分类
+
+堆分为 **最大堆** 和 **最小堆**。二者的区别在于节点的排序方式。
+- **最大堆** :堆中的每一个节点的值都大于等于子树中所有节点的值
+- **最小堆** :堆中的每一个节点的值都小于等于子树中所有节点的值
+
+如下图所示,图1是最大堆,图2是最小堆
+
+
+
+
+## 堆的存储
+之前介绍树的时候说过,由于完全二叉树的优秀性质,利用数组存储二叉树即节省空间,又方便索引(若根结点的序号为1,那么对于树中任意节点i,其左子节点序号为 `2\*i`,右子节点序号为 `2\*i+1`)。
+
+为了方便存储和索引,(二叉)堆可以用完全二叉树的形式进行存储。存储的方式如下图所示:
+
+
+
+## 堆的操作
+堆的更新操作主要包括两种 : **插入元素** 和 **删除堆顶元素**。操作过程需要着重掌握和理解。
+> 在进入正题之前,再重申一遍,堆是一个公平的公司,有能力的人自然会走到与他能力所匹配的位置
+### 插入元素
+> 插入元素,作为一个新入职的员工,初来乍到,这个员工需要从基层做起
+
+**1.将要插入的元素放到最后**
+
+
+
+> 有能力的人会逐渐升职加薪,是金子总会发光的!!!
+
+**2.从底向上,如果父结点比该元素大,则该节点和父结点交换,直到无法交换**
+
+
+
+
+
+### 删除堆顶元素
+
+根据堆的性质可知,最大堆的堆顶元素为所有元素中最大的,最小堆的堆顶元素是所有元素中最小的。当我们需要多次查找最大元素或者最小元素的时候,可以利用堆来实现。
+
+删除堆顶元素后,为了保持堆的性质,需要对堆的结构进行调整,我们将这个过程称之为"**堆化**",堆化的方法分为两种:
+
+- 一种是自底向上的堆化,上述的插入元素所使用的就是自底向上的堆化,元素从最底部向上移动。
+- 另一种是自顶向下堆化,元素由最顶部向下移动。在讲解删除堆顶元素的方法时,我将阐述这两种操作的过程,大家可以体会一下二者的不同。
+
+#### 自底向上堆化
+
+> 在堆这个公司中,会出现老大离职的现象,老大离职之后,他的位置就空出来了
+
+首先删除堆顶元素,使得数组中下标为1的位置空出。
+
+
+
+
+
+
+> 那么他的位置由谁来接替呢,当然是他的直接下属了,谁能力强就让谁上呗
+
+比较根结点的左子节点和右子节点,也就是下标为2,3的数组元素,将较大的元素填充到根结点(下标为1)的位置。
+
+
+
+
+> 这个时候又空出一个位置了,老规矩,谁有能力谁上
+
+一直循环比较空出位置的左右子节点,并将较大者移至空位,直到堆的最底部
+
+
+
+这个时候已经完成了自底向上的堆化,没有元素可以填补空缺了,但是,我们可以看到数组中出现了“气泡”,这会导致存储空间的浪费。接下来我们试试自顶向下堆化。
+
+#### 自顶向下堆化
+自顶向下的堆化用一个词形容就是“石沉大海”,那么第一件事情,就是把石头抬起来,从海面扔下去。这个石头就是堆的最后一个元素,我们将最后一个元素移动到堆顶。
+
+
+
+然后开始将这个石头沉入海底,不停与左右子节点的值进行比较,和较大的子节点交换位置,直到无法交换位置。
+
+
+
+
+
+
+
+### 堆的操作总结
+
+- **插入元素** :先将元素放至数组末尾,再自底向上堆化,将末尾元素上浮
+- **删除堆顶元素** :删除堆顶元素,将末尾元素放至堆顶,再自顶向下堆化,将堆顶元素下沉。也可以自底向上堆化,只是会产生“气泡”,浪费存储空间。最好采用自顶向下堆化的方式。
+
+
+## 堆排序
+
+堆排序的过程分为两步:
+
+- 第一步是建堆,将一个无序的数组建立为一个堆
+- 第二步是排序,将堆顶元素取出,然后对剩下的元素进行堆化,反复迭代,直到所有元素被取出为止。
+
+### 建堆
+
+如果你已经足够了解堆化的过程,那么建堆的过程掌握起来就比较容易了。建堆的过程就是一个对所有非叶节点的自顶向下堆化过程。
+
+首先要了解哪些是非叶节点,最后一个节点的父结点及它之前的元素,都是非叶节点。也就是说,如果节点个数为n,那么我们需要对n/2到1的节点进行自顶向下(沉底)堆化。
+
+具体过程如下图:
+
+
+
+将初始的无序数组抽象为一棵树,图中的节点个数为6,所以4,5,6节点为叶节点,1,2,3节点为非叶节点,所以要对1-3号节点进行自顶向下(沉底)堆化,注意,顺序是从后往前堆化,从3号节点开始,一直到1号节点。
+3号节点堆化结果:
+
+
+
+2号节点堆化结果:
+
+
+
+1号节点堆化结果:
+
+
+
+至此,数组所对应的树已经成为了一个最大堆,建堆完成!
+
+### 排序
+
+由于堆顶元素是所有元素中最大的,所以我们重复取出堆顶元素,将这个最大的堆顶元素放至数组末尾,并对剩下的元素进行堆化即可。
+
+现在思考两个问题:
+
+- 删除堆顶元素后需要执行自顶向下(沉底)堆化还是自底向上(上浮)堆化?
+- 取出的堆顶元素存在哪,新建一个数组存?
+
+先回答第一个问题,我们需要执行自顶向下(沉底)堆化,这个堆化一开始要将末尾元素移动至堆顶,这个时候末尾的位置就空出来了,由于堆中元素已经减小,这个位置不会再被使用,所以我们可以将取出的元素放在末尾。
+
+机智的小伙伴已经发现了,这其实是做了一次交换操作,将堆顶和末尾元素调换位置,从而将取出堆顶元素和堆化的第一步(将末尾元素放至根结点位置)进行合并。
+
+详细过程如下图所示:
+
+取出第一个元素并堆化:
+
+
+
+取出第二个元素并堆化:
+
+
+
+取出第三个元素并堆化:
+
+
+
+取出第四个元素并堆化:
+
+
+
+取出第五个元素并堆化:
+
+
+
+取出第六个元素并堆化:
+
+
+
+堆排序完成!
\ No newline at end of file
diff --git a/docs/cs-basics/data-structure/树.md b/docs/cs-basics/data-structure/树.md
new file mode 100644
index 00000000..010cff99
--- /dev/null
+++ b/docs/cs-basics/data-structure/树.md
@@ -0,0 +1,174 @@
+# 树
+
+树就是一种类似现实生活中的树的数据结构(倒置的树)。任何一颗非空树只有一个根节点。
+
+一棵树具有以下特点:
+
+1. 一棵树中的任意两个结点有且仅有唯一的一条路径连通。
+2. 一棵树如果有 n 个结点,那么它一定恰好有 n-1 条边。
+3. 一棵树不包含回路。
+
+下图就是一颗树,并且是一颗二叉树。
+
+
+
+如上图所示,通过上面这张图说明一下树中的常用概念:
+
+- **节点** :树中的每个元素都可以统称为节点。
+- **根节点** :顶层节点或者说没有父节点的节点。上图中 A 节点就是根节点。
+- **父节点** :若一个节点含有子节点,则这个节点称为其子节点的父节点。上图中的 B 节点是 D 节点、E 节点的父节点。
+- **子节点** :一个节点含有的子树的根节点称为该节点的子节点。上图中 D 节点、E 节点是 B 节点的子节点。
+- **兄弟节点** :具有相同父节点的节点互称为兄弟节点。上图中 D 节点、E 节点的共同父节点是 B 节点,故 D 和 E 为兄弟节点。
+- **叶子节点** :没有子节点的节点。上图中的 D、F、H、I 都是叶子节点。
+- **节点的高度** :该节点到叶子节点的最长路径所包含的边数。
+- **节点的深度** :根节点到该节点的路径所包含的边数
+- **节点的层数** :节点的深度+1。
+- **树的高度** :根节点的高度。
+
+## 二叉树的分类
+
+**二叉树**(Binary tree)是每个节点最多只有两个分支(即不存在分支度大于 2 的节点)的树结构。
+
+**二叉树** 的分支通常被称作“**左子树**”或“**右子树**”。并且,**二叉树** 的分支具有左右次序,不能随意颠倒。
+
+**二叉树** 的第 i 层至多拥有 `2^(i-1)` 个节点,深度为 k 的二叉树至多总共有 `2^k-1` 个节点
+
+### 满二叉树
+
+一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是 **满二叉树**。也就是说,如果一个二叉树的层数为 K,且结点总数是(2^k) -1 ,则它就是 **满二叉树**。如下图所示:
+
+
+
+### 完全二叉树
+
+除最后一层外,若其余层都是满的,并且最后一层或者是满的,或者是在右边缺少连续若干节点,则这个二叉树就是 **完全二叉树** 。
+
+大家可以想象为一棵树从根结点开始扩展,扩展完左子节点才能开始扩展右子节点,每扩展完一层,才能继续扩展下一层。如下图所示:
+
+
+
+完全二叉树有一个很好的性质:**父结点和子节点的序号有着对应关系。**
+
+细心的小伙伴可能发现了,当根节点的值为 1 的情况下,若父结点的序号是 i,那么左子节点的序号就是 2i,右子节点的序号是 2i+1。这个性质使得完全二叉树利用数组存储时可以极大地节省空间,以及利用序号找到某个节点的父结点和子节点,后续二叉树的存储会详细介绍。
+
+### 平衡二叉树
+
+**平衡二叉树** 是一棵二叉排序树,且具有以下性质:
+
+1. 可以是一棵空树
+2. 如果不是空树,它的左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树。
+
+平衡二叉树的常用实现方法有 **红黑树**、**AVL 树**、**替罪羊树**、**加权平衡树**、**伸展树** 等。
+
+在给大家展示平衡二叉树之前,先给大家看一棵树:
+
+
+
+**你管这玩意儿叫树???**
+
+没错,这玩意儿还真叫树,只不过这棵树已经退化为一个链表了,我们管它叫 **斜树**。
+
+**如果这样,那我为啥不直接用链表呢?**
+
+谁说不是呢?
+
+二叉树相比于链表,由于父子节点以及兄弟节点之间往往具有某种特殊的关系,这种关系使得我们在树中对数据进行**搜索**和**修改**时,相对于链表更加快捷便利。
+
+但是,如果二叉树退化为一个链表了,那么那么树所具有的优秀性质就难以表现出来,效率也会大打折,为了避免这样的情况,我们希望每个做 “家长”(父结点) 的,都 **一碗水端平**,分给左儿子和分给右儿子的尽可能一样多,相差最多不超过一层,如下图所示:
+
+
+
+## 二叉树的存储
+
+二叉树的存储主要分为 **链式存储** 和 **顺序存储** 两种:
+
+### 链式存储
+
+和链表类似,二叉树的链式存储依靠指针将各个节点串联起来,不需要连续的存储空间。
+
+每个节点包括三个属性:
+
+- 数据 data。data 不一定是单一的数据,根据不同情况,可以是多个具有不同类型的数据。
+- 左节点指针 left
+- 右节点指针 right。
+
+可是 JAVA 没有指针啊!
+
+那就直接引用对象呗(别问我对象哪里找)
+
+
+
+### 顺序存储
+
+顺序存储就是利用数组进行存储,数组中的每一个位置仅存储节点的 data,不存储左右子节点的指针,子节点的索引通过数组下标完成。根结点的序号为 1,对于每个节点 Node,假设它存储在数组中下标为 i 的位置,那么它的左子节点就存储在 2 _ i 的位置,它的右子节点存储在下标为 2 _ i+1 的位置。
+
+一棵完全二叉树的数组顺序存储如下图所示:
+
+
+
+大家可以试着填写一下存储如下二叉树的数组,比较一下和完全二叉树的顺序存储有何区别:
+
+
+
+可以看到,如果我们要存储的二叉树不是完全二叉树,在数组中就会出现空隙,导致内存利用率降低
+
+## 二叉树的遍历
+
+### 先序遍历
+
+
+
+二叉树的先序遍历,就是先输出根结点,再遍历左子树,最后遍历右子树,遍历左子树和右子树的时候,同样遵循先序遍历的规则,也就是说,我们可以递归实现先序遍历。
+
+代码如下:
+
+```java
+public void preOrder(TreeNode root){
+ if(root == null){
+ return;
+ }
+ system.out.println(root.data);
+ preOrder(root.left);
+ preOrder(root.right);
+}
+```
+
+### 中序遍历
+
+
+
+二叉树的中序遍历,就是先递归中序遍历左子树,再输出根结点的值,再递归中序遍历右子树,大家可以想象成一巴掌把树压扁,父结点被拍到了左子节点和右子节点的中间,如下图所示:
+
+
+
+代码如下:
+
+```java
+public void inOrder(TreeNode root){
+ if(root == null){
+ return;
+ }
+ inOrder(root.left);
+ system.out.println(root.data);
+ inOrder(root.right);
+}
+```
+
+### 后序遍历
+
+
+
+二叉树的后序遍历,就是先递归后序遍历左子树,再递归后序遍历右子树,最后输出根结点的值
+
+代码如下:
+
+```java
+public void postOrder(TreeNode root){
+ if(root == null){
+ return;
+ }
+ postOrder(root.left);
+ postOrder(root.right);
+ system.out.println(root.data);
+}
+```
\ No newline at end of file
diff --git a/docs/cs-basics/data-structure/红黑树.md b/docs/cs-basics/data-structure/红黑树.md
new file mode 100644
index 00000000..1f13744f
--- /dev/null
+++ b/docs/cs-basics/data-structure/红黑树.md
@@ -0,0 +1,14 @@
+**红黑树特点** :
+
+1. 每个节点非红即黑;
+2. 根节点总是黑色的;
+3. 每个叶子节点都是黑色的空节点(NIL节点);
+4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定);
+5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)。
+
+**红黑树的应用** :TreeMap、TreeSet以及JDK1.8的HashMap底层都用到了红黑树。
+
+**为什么要用红黑树?** 简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。详细了解可以查看 [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐)
+
+**相关阅读** :[《红黑树深入剖析及Java实现》](https://zhuanlan.zhihu.com/p/24367771)(美团点评技术团队)
+
diff --git a/docs/dataStructures-algorithms/data-structure/线性数据结构.md b/docs/cs-basics/data-structure/线性数据结构.md
similarity index 99%
rename from docs/dataStructures-algorithms/data-structure/线性数据结构.md
rename to docs/cs-basics/data-structure/线性数据结构.md
index c592a9ea..f00264aa 100644
--- a/docs/dataStructures-algorithms/data-structure/线性数据结构.md
+++ b/docs/cs-basics/data-structure/线性数据结构.md
@@ -78,9 +78,9 @@
### 2.4. 数组 vs 链表
-- 数据支持随机访问,而链表不支持。
+- 数组支持随机访问,而链表不支持。
- 数组使用的是连续内存空间对 CPU 的缓存机制友好,链表则相反。
-- 数据的大小固定,而链表则天然支持动态扩容。如果声明的数组过小,需要另外申请一个更大的内存空间存放数组元素,然后将原数组拷贝进去,这个操作是比较耗时的!
+- 数组的大小固定,而链表则天然支持动态扩容。如果声明的数组过小,需要另外申请一个更大的内存空间存放数组元素,然后将原数组拷贝进去,这个操作是比较耗时的!
## 3. 栈
diff --git a/docs/network/HTTPS中的TLS.md b/docs/cs-basics/network/HTTPS中的TLS.md
similarity index 100%
rename from docs/network/HTTPS中的TLS.md
rename to docs/cs-basics/network/HTTPS中的TLS.md
diff --git a/docs/network/images/Cut-Trough-Switching_0.gif b/docs/cs-basics/network/images/Cut-Trough-Switching_0.gif
similarity index 100%
rename from docs/network/images/Cut-Trough-Switching_0.gif
rename to docs/cs-basics/network/images/Cut-Trough-Switching_0.gif
diff --git a/docs/network/images/isp.png b/docs/cs-basics/network/images/isp.png
similarity index 100%
rename from docs/network/images/isp.png
rename to docs/cs-basics/network/images/isp.png
diff --git a/docs/network/images/七层体系结构图.png b/docs/cs-basics/network/images/七层体系结构图.png
similarity index 100%
rename from docs/network/images/七层体系结构图.png
rename to docs/cs-basics/network/images/七层体系结构图.png
diff --git a/docs/network/images/传输层.png b/docs/cs-basics/network/images/传输层.png
similarity index 100%
rename from docs/network/images/传输层.png
rename to docs/cs-basics/network/images/传输层.png
diff --git a/docs/network/images/应用层.png b/docs/cs-basics/network/images/应用层.png
similarity index 100%
rename from docs/network/images/应用层.png
rename to docs/cs-basics/network/images/应用层.png
diff --git a/docs/network/images/数据链路层.png b/docs/cs-basics/network/images/数据链路层.png
similarity index 100%
rename from docs/network/images/数据链路层.png
rename to docs/cs-basics/network/images/数据链路层.png
diff --git a/docs/network/images/物理层.png b/docs/cs-basics/network/images/物理层.png
similarity index 100%
rename from docs/network/images/物理层.png
rename to docs/cs-basics/network/images/物理层.png
diff --git a/docs/network/images/网络层.png b/docs/cs-basics/network/images/网络层.png
similarity index 100%
rename from docs/network/images/网络层.png
rename to docs/cs-basics/network/images/网络层.png
diff --git a/docs/network/images/计算机网络知识点总结/万维网的大致工作工程.png b/docs/cs-basics/network/images/计算机网络知识点总结/万维网的大致工作工程.png
similarity index 100%
rename from docs/network/images/计算机网络知识点总结/万维网的大致工作工程.png
rename to docs/cs-basics/network/images/计算机网络知识点总结/万维网的大致工作工程.png
diff --git a/docs/cs-basics/network/计算机网络.md b/docs/cs-basics/network/计算机网络.md
new file mode 100644
index 00000000..69a02be4
--- /dev/null
+++ b/docs/cs-basics/network/计算机网络.md
@@ -0,0 +1,300 @@
+## 一 OSI 与 TCP/IP 各层的结构与功能,都有哪些协议?
+
+学习计算机网络时我们一般采用折中的办法,也就是中和 OSI 和 TCP/IP 的优点,采用一种只有五层协议的体系结构,这样既简洁又能将概念阐述清楚。
+
+
+
+结合互联网的情况,自上而下地,非常简要的介绍一下各层的作用。
+
+### 1.1 应用层
+
+**应用层(application-layer)的任务是通过应用进程间的交互来完成特定网络应用。**应用层协议定义的是应用进程(进程:主机中正在运行的程序)间的通信和交互的规则。对于不同的网络应用需要不同的应用层协议。在互联网中应用层协议很多,如**域名系统 DNS**,支持万维网应用的 **HTTP 协议**,支持电子邮件的 **SMTP 协议**等等。我们把应用层交互的数据单元称为报文。
+
+**域名系统**
+
+> 域名系统(Domain Name System 缩写 DNS,Domain Name 被译为域名)是因特网的一项核心服务,它作为可以将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便的访问互联网,而不用去记住能够被机器直接读取的 IP 数串。(百度百科)例如:一个公司的 Web 网站可看作是它在网上的门户,而域名就相当于其门牌地址,通常域名都使用该公司的名称或简称。例如上面提到的微软公司的域名,类似的还有:IBM 公司的域名是 www.ibm.com、Oracle 公司的域名是 www.oracle.com、Cisco 公司的域名是 www.cisco.com 等。
+
+**HTTP 协议**
+
+> 超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的 WWW(万维网) 文件都必须遵守这个标准。设计 HTTP 最初的目的是为了提供一种发布和接收 HTML 页面的方法。(百度百科)
+
+### 1.2 运输层
+
+**运输层(transport layer)的主要任务就是负责向两台主机进程之间的通信提供通用的数据传输服务**。应用进程利用该服务传送应用层报文。“通用的”是指并不针对某一个特定的网络应用,而是多种应用可以使用同一个运输层服务。由于一台主机可同时运行多个线程,因此运输层有复用和分用的功能。所谓复用就是指多个应用层进程可同时使用下面运输层的服务,分用和复用相反,是运输层把收到的信息分别交付上面应用层中的相应进程。
+
+**运输层主要使用以下两种协议:**
+
+1. **传输控制协议 TCP**(Transmission Control Protocol)--提供**面向连接**的,**可靠的**数据传输服务。
+2. **用户数据协议 UDP**(User Datagram Protocol)--提供**无连接**的,尽最大努力的数据传输服务(**不保证数据传输的可靠性**)。
+
+**TCP 与 UDP 的对比见问题三。**
+
+### 1.3 网络层
+
+**在计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换结点, 确保数据及时传送。** 在发送数据时,网络层把运输层产生的报文段或用户数据报封装成分组和包进行传送。在 TCP/IP 体系结构中,由于网络层使用 **IP 协议**,因此分组也叫 **IP 数据报** ,简称 **数据报**。
+
+这里要注意:**不要把运输层的“用户数据报 UDP ”和网络层的“ IP 数据报”弄混**。另外,无论是哪一层的数据单元,都可笼统地用“分组”来表示。
+
+这里强调指出,网络层中的“网络”二字已经不是我们通常谈到的具体网络,而是指计算机网络体系结构模型中第三层的名称.
+
+互联网是由大量的异构(heterogeneous)网络通过路由器(router)相互连接起来的。互联网使用的网络层协议是无连接的网际协议(Internet Protocol)和许多路由选择协议,因此互联网的网络层也叫做**网际层**或**IP 层**。
+
+### 1.4 数据链路层
+
+**数据链路层(data link layer)通常简称为链路层。两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。** 在两个相邻节点之间传送数据时,**数据链路层将网络层交下来的 IP 数据报组装成帧**,在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)。
+
+在接收数据时,控制信息使接收端能够知道一个帧从哪个比特开始和到哪个比特结束。这样,数据链路层在收到一个帧后,就可从中提出数据部分,上交给网络层。
+控制信息还使接收端能够检测到所收到的帧中有无差错。如果发现差错,数据链路层就简单地丢弃这个出了差错的帧,以避免继续在网络中传送下去白白浪费网络资源。如果需要改正数据在链路层传输时出现差错(这就是说,数据链路层不仅要检错,而且还要纠错),那么就要采用可靠性传输协议来纠正出现的差错。这种方法会使链路层的协议复杂些。
+
+### 1.5 物理层
+
+在物理层上所传送的数据单位是比特。
+
+**物理层(physical layer)的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异,** 使其上面的数据链路层不必考虑网络的具体传输介质是什么。“透明传送比特流”表示经实际电路传送后的比特流没有发生变化,对传送的比特流来说,这个电路好像是看不见的。
+
+在互联网使用的各种协议中最重要和最著名的就是 TCP/IP 两个协议。现在人们经常提到的 TCP/IP 并不一定单指 TCP 和 IP 这两个具体的协议,而往往表示互联网所使用的整个 TCP/IP 协议族。
+
+### 1.6 总结一下
+
+上面我们对计算机网络的五层体系结构有了初步的了解,下面附送一张七层体系结构图总结一下(图片来源于网络)。
+
+
+
+## 二 TCP 三次握手和四次挥手(面试常客)
+
+为了准确无误地把数据送达目标处,TCP 协议采用了三次握手策略。
+
+### 2.1 TCP 三次握手漫画图解
+
+如下图所示,下面的两个机器人通过 3 次握手确定了对方能正确接收和发送消息(图片来源:《图解 HTTP》)。
+
+
+**简单示意图:**
+
+
+- 客户端–发送带有 SYN 标志的数据包–一次握手–服务端
+- 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端
+- 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端
+
+**详细示意图(图片来源不详)**
+
+
+
+### 2.2 为什么要三次握手
+
+**三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。**
+
+第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常
+
+第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常
+
+第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常
+
+所以三次握手就能确认双发收发功能都正常,缺一不可。
+
+### 2.3 第 2 次握手传回了 ACK,为什么还要传回 SYN?
+
+接收端传回发送端所发送的 ACK 是为了告诉客户端,我接收到的信息确实就是你所发送的信号了,这表明从客户端到服务端的通信是正常的。而回传 SYN 则是为了建立并确认从服务端到客户端的通信。”
+
+> SYN 同步序列编号(Synchronize Sequence Numbers) 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement)消息响应。这样在客户机和服务器之间才能建立起可靠的 TCP 连接,数据才可以在客户机和服务器之间传递。
+
+### 2.5 为什么要四次挥手
+
+
+
+断开一个 TCP 连接则需要“四次挥手”:
+
+- 客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送
+- 服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加 1 。和 SYN 一样,一个 FIN 将占用一个序号
+- 服务器-关闭与客户端的连接,发送一个 FIN 给客户端
+- 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加 1
+
+任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了 TCP 连接。
+
+举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B 回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。
+
+上面讲的比较概括,推荐一篇讲的比较细致的文章:[https://blog.csdn.net/qzcsu/article/details/72861891](https://blog.csdn.net/qzcsu/article/details/72861891)
+
+## 三 TCP,UDP 协议的区别
+
+
+
+UDP 在传送数据之前不需要先建立连接,远地主机在收到 UDP 报文后,不需要给出任何确认。虽然 UDP 不提供可靠交付,但在某些情况下 UDP 却是一种最有效的工作方式(一般用于即时通信),比如: QQ 语音、 QQ 视频 、直播等等
+
+TCP 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。 TCP 不提供广播或多播服务。由于 TCP 要提供可靠的,面向连接的传输服务(TCP 的可靠体现在 TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源),这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。
+
+## 四 TCP 协议如何保证可靠传输
+
+1. 应用数据被分割成 TCP 认为最适合发送的数据块。
+2. TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
+3. **校验和:** TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
+4. TCP 的接收端会丢弃重复的数据。
+5. **流量控制:** TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制)
+6. **拥塞控制:** 当网络拥塞时,减少数据的发送。
+7. **ARQ 协议:** 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
+8. **超时重传:** 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
+
+### 4.1 ARQ 协议
+
+**自动重传请求**(Automatic Repeat-reQuest,ARQ)是 OSI 模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认帧,它通常会重新发送。ARQ 包括停止等待 ARQ 协议和连续 ARQ 协议。
+
+#### 停止等待 ARQ 协议
+
+停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认(回复 ACK)。如果过了一段时间(超时时间后),还是没有收到 ACK 确认,说明没有发送成功,需要重新发送,直到收到确认后再发下一个分组。
+
+在停止等待协议中,若接收方收到重复分组,就丢弃该分组,但同时还要发送确认。
+
+**优缺点:**
+
+- **优点:** 简单
+- **缺点:** 信道利用率低,等待时间长
+
+**1) 无差错情况:**
+
+发送方发送分组,接收方在规定时间内收到,并且回复确认.发送方再次发送。
+
+**2) 出现差错情况(超时重传):**
+
+停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重传时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为 **自动重传请求 ARQ** 。另外在停止等待协议中若收到重复分组,就丢弃该分组,但同时还要发送确认。**连续 ARQ 协议** 可提高信道利用率。发送维持一个发送窗口,凡位于发送窗口内的分组可连续发送出去,而不需要等待对方确认。接收方一般采用累积确认,对按序到达的最后一个分组发送确认,表明到这个分组位置的所有分组都已经正确收到了。
+
+**3) 确认丢失和确认迟到**
+
+- **确认丢失** :确认消息在传输过程丢失。当 A 发送 M1 消息,B 收到后,B 向 A 发送了一个 M1 确认消息,但却在传输过程中丢失。而 A 并不知道,在超时计时过后,A 重传 M1 消息,B 再次收到该消息后采取以下两点措施:1. 丢弃这个重复的 M1 消息,不向上层交付。 2. 向 A 发送确认消息。(不会认为已经发送过了,就不再发送。A 能重传,就证明 B 的确认消息丢失)。
+- **确认迟到** :确认消息在传输过程中迟到。A 发送 M1 消息,B 收到并发送确认。在超时时间内没有收到确认消息,A 重传 M1 消息,B 仍然收到并继续发送确认消息(B 收到了 2 份 M1)。此时 A 收到了 B 第二次发送的确认消息。接着发送其他数据。过了一会,A 收到了 B 第一次发送的对 M1 的确认消息(A 也收到了 2 份确认消息)。处理如下:1. A 收到重复的确认后,直接丢弃。2. B 收到重复的 M1 后,也直接丢弃重复的 M1。
+
+#### 连续 ARQ 协议
+
+连续 ARQ 协议可提高信道利用率。发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明到这个分组为止的所有分组都已经正确收到了。
+
+**优缺点:**
+
+- **优点:** 信道利用率高,容易实现,即使确认丢失,也不必重传。
+- **缺点:** 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5 条 消息,中间第三条丢失(3 号),这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落,而只好把后三个全部重传一次。这也叫 Go-Back-N(回退 N),表示需要退回来重传已经发送过的 N 个消息。
+
+### 4.2 滑动窗口和流量控制
+
+**TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。** 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。
+
+### 4.3 拥塞控制
+
+在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
+
+为了进行拥塞控制,TCP 发送方要维持一个 **拥塞窗口(cwnd)** 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。
+
+TCP 的拥塞控制采用了四种算法,即 **慢开始** 、 **拥塞避免** 、**快重传** 和 **快恢复**。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。
+
+- **慢开始:** 慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的符合情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd 初始值为 1,每经过一个传播轮次,cwnd 加倍。
+- **拥塞避免:** 拥塞避免算法的思路是让拥塞窗口 cwnd 缓慢增大,即每经过一个往返时间 RTT 就把发送放的 cwnd 加 1.
+- **快重传与快恢复:**
+ 在 TCP/IP 中,快速重传和恢复(fast retransmit and recovery,FRR)是一种拥塞控制算法,它能快速恢复丢失的数据包。没有 FRR,如果数据包丢失了,TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR,如果接收机接收到一个不按顺序的数据段,它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认,它会假定确认件指出的数据段丢失了,并立即重传这些丢失的数据段。有了 FRR,就不会因为重传时要求的暂停被耽误。 当有单独的数据包丢失时,快速重传和恢复(FRR)能最有效地工作。当有多个数据信息包在某一段很短的时间内丢失时,它则不能很有效地工作。
+
+## 五 在浏览器中输入 url 地址 ->> 显示主页的过程(面试常客)
+
+百度好像最喜欢问这个问题。
+
+> 打开一个网页,整个过程会使用哪些协议?
+
+图解(图片来源:《图解 HTTP》):
+
+
+
+> 上图有一个错误,请注意,是 OSPF 不是 OPSF。 OSPF(Open Shortest Path First,ospf)开放最短路径优先协议,是由 Internet 工程任务组开发的路由选择协议
+
+总体来说分为以下几个过程:
+
+1. DNS 解析
+2. TCP 连接
+3. 发送 HTTP 请求
+4. 服务器处理请求并返回 HTTP 报文
+5. 浏览器解析渲染页面
+6. 连接结束
+
+具体可以参考下面这篇文章:
+
+- [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700)
+
+## 六 状态码
+
+
+
+## 七 各种协议与 HTTP 协议之间的关系
+
+一般面试官会通过这样的问题来考察你对计算机网络知识体系的理解。
+
+图片来源:《图解 HTTP》
+
+
+
+## 八 HTTP 长连接,短连接
+
+在 HTTP/1.0 中默认使用短连接。也就是说,客户端和服务器每进行一次 HTTP 操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个 HTML 或其他类型的 Web 页中包含有其他的 Web 资源(如 JavaScript 文件、图像文件、CSS 文件等),每遇到这样一个 Web 资源,浏览器就会重新建立一个 HTTP 会话。
+
+而从 HTTP/1.1 起,默认使用长连接,用以保持连接特性。使用长连接的 HTTP 协议,会在响应头加入这行代码:
+
+```
+Connection:keep-alive
+```
+
+在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输 HTTP 数据的 TCP 连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive 不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如 Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。
+
+**HTTP 协议的长连接和短连接,实质上是 TCP 协议的长连接和短连接。**
+
+—— [《HTTP 长连接、短连接究竟是什么?》](https://www.cnblogs.com/gotodsp/p/6366163.html)
+
+## 九 HTTP 是不保存状态的协议,如何保存用户状态?
+
+HTTP 是一种不保存状态,即无状态(stateless)协议。也就是说 HTTP 协议自身不对请求和响应之间的通信状态进行保存。那么我们保存用户状态呢?Session 机制的存在就是为了解决这个问题,Session 的主要作用就是通过服务端记录用户的状态。典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了(一般情况下,服务器会在一定时间内保存这个 Session,过了时间限制,就会销毁这个 Session)。
+
+在服务端保存 Session 的方法很多,最常用的就是内存和数据库(比如是使用内存数据库 redis 保存)。既然 Session 存放在服务器端,那么我们如何实现 Session 跟踪呢?大部分情况下,我们都是通过在 Cookie 中附加一个 Session ID 来方式来跟踪。
+
+**Cookie 被禁用怎么办?**
+
+最常用的就是利用 URL 重写把 Session ID 直接附加在 URL 路径的后面。
+
+
+
+## 十 Cookie 的作用是什么?和 Session 有什么区别?
+
+Cookie 和 Session 都是用来跟踪浏览器用户身份的会话方式,但是两者的应用场景不太一样。
+
+**Cookie 一般用来保存用户信息** 比如 ① 我们在 Cookie 中保存已经登录过得用户信息,下次访问网站的时候页面可以自动帮你登录的一些基本信息给填了;② 一般的网站都会有保持登录也就是说下次你再访问网站的时候就不需要重新登录了,这是因为用户登录的时候我们可以存放了一个 Token 在 Cookie 中,下次登录的时候只需要根据 Token 值来查找用户即可(为了安全考虑,重新登录一般要将 Token 重写);③ 登录一次网站后访问网站其他页面不需要重新登录。**Session 的主要作用就是通过服务端记录用户的状态。** 典型的场景是购物车,当你要添加商品到购物车的时候,系统不知道是哪个用户操作的,因为 HTTP 协议是无状态的。服务端给特定的用户创建特定的 Session 之后就可以标识这个用户并且跟踪这个用户了。
+
+Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。
+
+Cookie 存储在客户端中,而 Session 存储在服务器上,相对来说 Session 安全性更高。如果要在 Cookie 中存储一些敏感信息,不要直接写入 Cookie 中,最好能将 Cookie 信息加密然后使用到的时候再去服务器端解密。
+
+## 十一 HTTP 1.0 和 HTTP 1.1 的主要区别是什么?
+
+> 这部分回答引用这篇文章 的一些内容。
+
+HTTP1.0 最早在网页中使用是在 1996 年,那个时候只是使用一些较为简单的网页上和网络请求上,而 HTTP1.1 则在 1999 年才开始广泛应用于现在的各大浏览器网络请求中,同时 HTTP1.1 也是当前使用最为广泛的 HTTP 协议。 主要区别主要体现在:
+
+1. **长连接** : **在 HTTP/1.0 中,默认使用的是短连接**,也就是说每次请求都要重新建立一次连接。HTTP 是基于 TCP/IP 协议的,每一次建立或者断开连接都需要三次握手四次挥手的开销,如果每次请求都要这样的话,开销会比较大。因此最好能维持一个长连接,可以用个长连接来发多个请求。**HTTP 1.1 起,默认使用长连接** ,默认开启 Connection: keep-alive。 **HTTP/1.1 的持续连接有非流水线方式和流水线方式** 。流水线方式是客户在收到 HTTP 的响应报文之前就能接着发送新的请求报文。与之相对应的非流水线方式是客户在收到前一个响应后才能发送下一个请求。
+1. **错误状态响应码** :在 HTTP1.1 中新增了 24 个错误状态响应码,如 409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
+1. **缓存处理** :在 HTTP1.0 中主要使用 header 里的 If-Modified-Since,Expires 来做为缓存判断的标准,HTTP1.1 则引入了更多的缓存控制策略例如 Entity tag,If-Unmodified-Since, If-Match, If-None-Match 等更多可供选择的缓存头来控制缓存策略。
+1. **带宽优化及网络连接的使用** :HTTP1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
+
+## 十二 URI 和 URL 的区别是什么?
+
+- URI(Uniform Resource Identifier) 是统一资源标志符,可以唯一标识一个资源。
+- URL(Uniform Resource Locator) 是统一资源定位符,可以提供该资源的路径。它是一种具体的 URI,即 URL 可以用来标识一个资源,而且还指明了如何 locate 这个资源。
+
+URI 的作用像身份证号一样,URL 的作用更像家庭住址一样。URL 是一种具体的 URI,它不仅唯一标识资源,而且还提供了定位该资源的信息。
+
+## 十三 HTTP 和 HTTPS 的区别?
+
+1. **端口** :HTTP 的 URL 由“http://”起始且默认使用端口80,而HTTPS的URL由“https://”起始且默认使用端口443。
+2. **安全性和资源消耗:** HTTP 协议运行在 TCP 之上,所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。HTTPS 是运行在 SSL/TLS 之上的 HTTP 协议,SSL/TLS 运行在 TCP 之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。所以说,HTTP 安全性没有 HTTPS 高,但是 HTTPS 比 HTTP 耗费更多服务器资源。
+ - 对称加密:密钥只有一个,加密解密为同一个密码,且加解密速度快,典型的对称加密算法有 DES、AES 等;
+ - 非对称加密:密钥成对出现(且根据公钥无法推知私钥,根据私钥也无法推知公钥),加密解密使用不同密钥(公钥加密需要私钥解密,私钥加密需要公钥解密),相对对称加密速度较慢,典型的非对称加密算法有 RSA、DSA 等。
+
+## 建议
+
+非常推荐大家看一下 《图解 HTTP》 这本书,这本书页数不多,但是内容很是充实,不管是用来系统的掌握网络方面的一些知识还是说纯粹为了应付面试都有很大帮助。下面的一些文章只是参考。大二学习这门课程的时候,我们使用的教材是 《计算机网络第七版》(谢希仁编著),不推荐大家看这本教材,书非常厚而且知识偏理论,不确定大家能不能心平气和的读完。
+
+## 参考
+
+- [https://blog.csdn.net/qq_16209077/article/details/52718250](https://blog.csdn.net/qq_16209077/article/details/52718250)
+- [https://blog.csdn.net/zixiaomuwu/article/details/60965466](https://blog.csdn.net/zixiaomuwu/article/details/60965466)
+- [https://blog.csdn.net/turn\_\_back/article/details/73743641](https://blog.csdn.net/turn__back/article/details/73743641)
+-
diff --git a/docs/network/计算机网络知识总结.md b/docs/cs-basics/network/计算机网络知识总结.md
similarity index 99%
rename from docs/network/计算机网络知识总结.md
rename to docs/cs-basics/network/计算机网络知识总结.md
index f64a61b1..7d0db60a 100644
--- a/docs/network/计算机网络知识总结.md
+++ b/docs/cs-basics/network/计算机网络知识总结.md
@@ -163,7 +163,7 @@
## 3. 数据链路层(Data Link Layer)
-
+
### 3.1. 基本术语
diff --git a/docs/operating-system/Shell.md b/docs/cs-basics/operating-system/Shell.md
similarity index 85%
rename from docs/operating-system/Shell.md
rename to docs/cs-basics/operating-system/Shell.md
index c88ebdd6..0a7c1508 100644
--- a/docs/operating-system/Shell.md
+++ b/docs/cs-basics/operating-system/Shell.md
@@ -45,20 +45,19 @@
另外,了解 shell 编程也是大部分互联网公司招聘后端开发人员的要求。下图是我截取的一些知名互联网公司对于 Shell 编程的要求。
-
+
### 什么是 Shell?
简单来说“Shell编程就是对一堆Linux命令的逻辑化处理”。
-
W3Cschool 上的一篇文章是这样介绍 Shell的,如下图所示。
-
+
### Shell 编程的 Hello World
-学习任何一门编程语言第一件事就是输出HelloWord了!下面我会从新建文件到shell代码编写来说下Shell 编程如何输出Hello World。
+学习任何一门编程语言第一件事就是输出HelloWorld了!下面我会从新建文件到shell代码编写来说下Shell 编程如何输出Hello World。
(1)新建一个文件 helloworld.sh :`touch helloworld.sh`,扩展名为 sh(sh代表Shell)(扩展名并不影响脚本执行,见名知意就好,如果你用 php 写 shell 脚本,扩展名就用 php 好了)
@@ -80,7 +79,7 @@ shell中 # 符号表示注释。**shell 的第一行比较特殊,一般都会
(4) 运行脚本:`./helloworld.sh` 。(注意,一定要写成 `./helloworld.sh` ,而不是 `helloworld.sh` ,运行其它二进制的程序也一样,直接写 `helloworld.sh` ,linux 系统会去 PATH 里寻找有没有叫 helloworld.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 `helloworld.sh` 是会找不到命令的,要用`./helloworld.sh` 告诉系统说,就在当前目录找。)
-
+
## Shell 变量
@@ -91,18 +90,18 @@ shell中 # 符号表示注释。**shell 的第一行比较特殊,一般都会
**Shell编程中一般分为三种变量:**
1. **我们自己定义的变量(自定义变量):** 仅在当前 Shell 实例中有效,其他 Shell 启动的程序不能访问局部变量。
-2. **Linux已定义的环境变量**(环境变量, 例如:$PATH, $HOME 等..., 这类变量我们可以直接使用),使用 `env` 命令可以查看所有的环境变量,而set命令既可以查看环境变量也可以查看自定义变量。
+2. **Linux已定义的环境变量**(环境变量, 例如:`PATH`, `HOME` 等..., 这类变量我们可以直接使用),使用 `env` 命令可以查看所有的环境变量,而set命令既可以查看环境变量也可以查看自定义变量。
3. **Shell变量** :Shell变量是由 Shell 程序设置的特殊变量。Shell 变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了 Shell 的正常运行
**常用的环境变量:**
-> PATH 决定了shell将到哪些目录中寻找命令或程序
-HOME 当前用户主目录
-HISTSIZE 历史记录数
-LOGNAME 当前用户的登录名
-HOSTNAME 指主机的名称
-SHELL 当前用户Shell类型
-LANGUGE 语言相关的环境变量,多语言可以修改此环境变量
-MAIL 当前用户的邮件存放目录
+> PATH 决定了shell将到哪些目录中寻找命令或程序
+HOME 当前用户主目录
+HISTSIZE 历史记录数
+LOGNAME 当前用户的登录名
+HOSTNAME 指主机的名称
+SHELL 当前用户Shell类型
+LANGUAGE 语言相关的环境变量,多语言可以修改此环境变量
+MAIL 当前用户的邮件存放目录
PS1 基本提示符,对于root用户是#,对于普通用户是$
**使用 Linux 已定义的环境变量:**
@@ -118,7 +117,7 @@ hello="hello world"
echo $hello
echo "helloworld!"
```
-
+
**Shell 编程中的变量名的命名的注意事项:**
@@ -183,7 +182,7 @@ echo $greeting_2 $greeting_3
输出结果:
-
+
**获取字符串长度:**
@@ -234,13 +233,17 @@ echo ${str:0:10} #输出:SnailClimb
#!bin/bash
#author:amau
-var="http://www.runoob.com/linux/linux-shell-variable.html"
-
-s1=${var%%t*}#h
-s2=${var%t*}#http://www.runoob.com/linux/linux-shell-variable.h
-s3=${var%%.*}#http://www
-s4=${var#*/}#/www.runoob.com/linux/linux-shell-variable.html
-s5=${var##*/}#linux-shell-variable.html
+var="https://www.runoob.com/linux/linux-shell-variable.html"
+# %表示删除从后匹配, 最短结果
+# %%表示删除从后匹配, 最长匹配结果
+# #表示删除从头匹配, 最短结果
+# ##表示删除从头匹配, 最长匹配结果
+# 注: *为通配符, 意为匹配任意数量的任意字符
+s1=${var%%t*} #h
+s2=${var%t*} #https://www.runoob.com/linux/linux-shell-variable.h
+s3=${var%%.*} #http://www
+s4=${var#*/} #/www.runoob.com/linux/linux-shell-variable.html
+s5=${var##*/} #linux-shell-variable.html
```
### Shell 数组
@@ -281,7 +284,7 @@ for i in ${array[@]};do echo $i ;done # 遍历数组,数组元素为空,没
### 算数运算符
-
+
我以加法运算符做一个简单的示例(注意:不是单引号,是反引号):
@@ -298,7 +301,7 @@ echo "Total value : $val"
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
-
+
通过一个简单的示例演示关系运算符的使用,下面shell程序的作用是当score=100的时候输出A否则输出B。
@@ -322,7 +325,7 @@ B
### 逻辑运算符
-
+
示例:
@@ -336,18 +339,17 @@ echo $a;
### 布尔运算符
-
+
这里就不做演示了,应该挺简单的。
### 字符串运算符
-
+
简单示例:
```shell
-
#!/bin/bash
a="abc";
b="efg";
@@ -366,7 +368,7 @@ a 不等于 b
### 文件相关运算符
-
+
使用方式很简单,比如我们定义好了一个文件路径`file="/usr/learnshell/test.sh"` 如果我们想判断这个文件是否可读,可以这样`if [ -r $file ]` 如果想判断这个文件是否可写,可以这样`-w $file`,是不是很简单。
@@ -530,8 +532,6 @@ echo "输入的两个数字之和为 $?"
### 带参数的函数
-
-
```shell
#!/bin/bash
funWithParam(){
@@ -544,7 +544,6 @@ funWithParam(){
echo "作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
-
```
输出结果:
@@ -557,5 +556,4 @@ funWithParam 1 2 3 4 5 6 7 8 9 34 73
第十一个参数为 73 !
参数总数有 11 个!
作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 !
-
```
diff --git a/docs/operating-system/basis.md b/docs/cs-basics/operating-system/basis.md
similarity index 96%
rename from docs/operating-system/basis.md
rename to docs/cs-basics/operating-system/basis.md
index b1cb59b5..f35bef6e 100644
--- a/docs/operating-system/basis.md
+++ b/docs/cs-basics/operating-system/basis.md
@@ -8,7 +8,7 @@
开始本文的内容之前,我们先聊聊为什么要学习操作系统。
-- **从对个人能力方面提升来说** :操作系统中的很多思想、很多经典的算法,你都可以在我们日常开发使用的各种工具或者框架中找到它们的影子。比如说我们开发的系统使用的缓存(比如 Redis)和操作系统的高速缓存就很像。CPU 中的高速缓存有很多种,不过大部分都是为了解决 CPU 处理速度和内存处理速度不对等的问题。我们还可以把内存可以看作外存的高速缓存,程序运行的时候我们把外存的数据复制到内存,由于内存的处理速度远远高于外存,这样提高了处理速度。同样地,我们使用的 Redis 缓存就是为了解决程序处理速度和访问常规关系型数据库速度不对等的问题。高速缓存一般会按照局部性原理(2-8 原则)根据相应的淘汰算法保证缓存中的数据是经常会被访问的。我们平常使用的 Redis 缓存很多时候也会按照 2-8 原则去做,很多淘汰算法都和操作系统中的类似。既说了 2-8 原则,那就不得不提命中率了,这是所有缓存概念都通用的。简单来说也就是你要访问的数据有多少能直接在缓存中直接找到。命中率高的话,一般表明你的缓存设计比较合理,系统处理速度也相对较快。
+- **从对个人能力方面提升来说** :操作系统中的很多思想、很多经典的算法,你都可以在我们日常开发使用的各种工具或者框架中找到它们的影子。比如说我们开发的系统使用的缓存(比如 Redis)和操作系统的高速缓存就很像。CPU 中的高速缓存有很多种,不过大部分都是为了解决 CPU 处理速度和内存处理速度不对等的问题。我们还可以把内存看作外存的高速缓存,程序运行的时候我们把外存的数据复制到内存,由于内存的处理速度远远高于外存,这样提高了处理速度。同样地,我们使用的 Redis 缓存就是为了解决程序处理速度和访问常规关系型数据库速度不对等的问题。高速缓存一般会按照局部性原理(2-8 原则)根据相应的淘汰算法保证缓存中的数据是经常会被访问的。我们平常使用的 Redis 缓存很多时候也会按照 2-8 原则去做,很多淘汰算法都和操作系统中的类似。既说了 2-8 原则,那就不得不提命中率了,这是所有缓存概念都通用的。简单来说也就是你要访问的数据有多少能直接在缓存中直接找到。命中率高的话,一般表明你的缓存设计比较合理,系统处理速度也相对较快。
- **从面试角度来说** :尤其是校招,对于操作系统方面知识的考察是非常非常多的。
**简单来说,学习操作系统能够提高自己思考的深度以及对技术的理解力,并且,操作系统方面的知识也是面试必备。**
@@ -233,7 +233,7 @@
于是面试完之后我默默去查阅了相关文档!留下了没有技术的泪水。。。
-> 这部分内容参考了 Microsoft 官网的介绍,地址:
+> 这部分内容参考了 Microsoft 官网的介绍,地址:
现代处理器使用的是一种称为 **虚拟寻址(Virtual Addressing)** 的寻址方式。**使用虚拟寻址,CPU 需要将虚拟地址翻译成物理地址,这样才能访问到真实的物理内存。** 实际上完成虚拟地址转换为物理地址转换的硬件是 CPU 中含有一个被称为 **内存管理单元(Memory Management Unit, MMU)** 的硬件。如下图所示:
@@ -337,7 +337,7 @@
- **OPT 页面置换算法(最佳页面置换算法)** :最佳(Optimal, OPT)置换算法所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。但由于人们目前无法预知进程在内存下的若千页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现。一般作为衡量其他置换算法的方法。
- **FIFO(First In First Out) 页面置换算法(先进先出页面置换算法)** : 总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面进行淘汰。
-- **LRU (Least Currently Used)页面置换算法(最近最久未使用页面置换算法)** :LRU算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 T,当须淘汰一个页面时,选择现有页面中其 T 值最大的,即最近最久未使用的页面予以淘汰。
+- **LRU (Least Recently Used)页面置换算法(最近最久未使用页面置换算法)** :LRU算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 T,当须淘汰一个页面时,选择现有页面中其 T 值最大的,即最近最久未使用的页面予以淘汰。
- **LFU (Least Frequently Used)页面置换算法(最少使用页面置换算法)** : 该置换算法选择在之前时期使用最少的页面作为淘汰页。
## Reference
diff --git a/docs/operating-system/images/Linux-Logo.png b/docs/cs-basics/operating-system/images/Linux-Logo.png
similarity index 100%
rename from docs/operating-system/images/Linux-Logo.png
rename to docs/cs-basics/operating-system/images/Linux-Logo.png
diff --git a/docs/operating-system/images/Linux之父.png b/docs/cs-basics/operating-system/images/Linux之父.png
similarity index 100%
rename from docs/operating-system/images/Linux之父.png
rename to docs/cs-basics/operating-system/images/Linux之父.png
diff --git a/docs/operating-system/images/Linux权限命令.png b/docs/cs-basics/operating-system/images/Linux权限命令.png
similarity index 100%
rename from docs/operating-system/images/Linux权限命令.png
rename to docs/cs-basics/operating-system/images/Linux权限命令.png
diff --git a/docs/operating-system/images/Linux权限解读.png b/docs/cs-basics/operating-system/images/Linux权限解读.png
similarity index 100%
rename from docs/operating-system/images/Linux权限解读.png
rename to docs/cs-basics/operating-system/images/Linux权限解读.png
diff --git a/docs/operating-system/images/Linux目录树.png b/docs/cs-basics/operating-system/images/Linux目录树.png
similarity index 100%
rename from docs/operating-system/images/Linux目录树.png
rename to docs/cs-basics/operating-system/images/Linux目录树.png
diff --git a/docs/operating-system/images/linux.png b/docs/cs-basics/operating-system/images/linux.png
similarity index 100%
rename from docs/operating-system/images/linux.png
rename to docs/cs-basics/operating-system/images/linux.png
diff --git a/docs/operating-system/images/macos.png b/docs/cs-basics/operating-system/images/macos.png
similarity index 100%
rename from docs/operating-system/images/macos.png
rename to docs/cs-basics/operating-system/images/macos.png
diff --git a/docs/operating-system/images/unix.png b/docs/cs-basics/operating-system/images/unix.png
similarity index 100%
rename from docs/operating-system/images/unix.png
rename to docs/cs-basics/operating-system/images/unix.png
diff --git a/docs/operating-system/images/windows.png b/docs/cs-basics/operating-system/images/windows.png
similarity index 100%
rename from docs/operating-system/images/windows.png
rename to docs/cs-basics/operating-system/images/windows.png
diff --git a/docs/operating-system/images/修改文件权限.png b/docs/cs-basics/operating-system/images/修改文件权限.png
similarity index 100%
rename from docs/operating-system/images/修改文件权限.png
rename to docs/cs-basics/operating-system/images/修改文件权限.png
diff --git a/docs/operating-system/images/文件inode信息.png b/docs/cs-basics/operating-system/images/文件inode信息.png
similarity index 100%
rename from docs/operating-system/images/文件inode信息.png
rename to docs/cs-basics/operating-system/images/文件inode信息.png
diff --git a/docs/operating-system/images/用户态与内核态.png b/docs/cs-basics/operating-system/images/用户态与内核态.png
similarity index 100%
rename from docs/operating-system/images/用户态与内核态.png
rename to docs/cs-basics/operating-system/images/用户态与内核态.png
diff --git a/docs/operating-system/linux.md b/docs/cs-basics/operating-system/linux.md
similarity index 98%
rename from docs/operating-system/linux.md
rename to docs/cs-basics/operating-system/linux.md
index 7f318ccc..87d4f937 100644
--- a/docs/operating-system/linux.md
+++ b/docs/cs-basics/operating-system/linux.md
@@ -163,7 +163,7 @@ _玩玩电脑游戏还是必须要有 Windows 的,所以我现在是一台 Win
- **类 Unix 系统** : Linux 是一种自由、开放源码的类似 Unix 的操作系统
- **Linux 本质是指 Linux 内核** : 严格来讲,Linux 这个词本身只表示 Linux 内核,单独的 Linux 内核并不能成为一个可以正常工作的操作系统。所以,就有了各种 Linux 发行版。
-- **Linux 之父** : 一个编程领域的传奇式人物,真大佬!我辈崇拜敬仰之楷模。他是 **Linux 内核** 的最早作者,随后发起了这个开源项目,担任 Linux 内核的首要架构师。他还发起了 Git 这个开源项目,并为主要的开发者。
+- **Linux 之父(林纳斯·本纳第克特·托瓦兹 Linus Benedict Torvalds)** : 一个编程领域的传奇式人物,真大佬!我辈崇拜敬仰之楷模。他是 **Linux 内核** 的最早作者,随后发起了这个开源项目,担任 Linux 内核的首要架构师。他还发起了 Git 这个开源项目,并为主要的开发者。

@@ -224,7 +224,7 @@ Linux 支持很多文件类型,其中非常重要的文件类型有: **普通
- **普通文件(-)** : 用于存储信息和数据, Linux 用户可以根据访问权限对普通文件进行查看、更改和删除。比如:图片、声音、PDF、text、视频、源代码等等。
- **目录文件(d,directory file)** :目录也是文件的一种,用于表示和管理系统中的文件,目录文件中包含一些文件名和子目录名。打开目录事实上就是打开目录文件。
- **符号链接文件(l,symbolic link)** :保留了指向文件的地址而不是文件本身。
-- **字符设备(c,char)** :用来访问字符设备比如硬盘。
+- **字符设备(c,char)** :用来访问字符设备比如键盘。
- **设备文件(b,block)** : 用来访问块设备比如硬盘、软盘。
- **管道文件(p,pipe)** : 一种特殊类型的文件,用于进程之间的通信。
- **套接字(s,socket)** :用于进程间的网络通信,也可以用于本机之间的非网络通信。
@@ -303,7 +303,7 @@ Linux 中的打包文件一般是以.tar 结尾的,压缩的命令一般是以
**2)解压压缩包:**
-命令:`tar [-xvf] 压缩文件``
+命令:`tar [-xvf] 压缩文件`
其中:x:代表解压
diff --git a/docs/dataStructures-algorithms/数据结构.md b/docs/dataStructures-algorithms/数据结构.md
deleted file mode 100644
index 314b2a85..00000000
--- a/docs/dataStructures-algorithms/数据结构.md
+++ /dev/null
@@ -1,179 +0,0 @@
-> 注意!!!这部分内容会进行重构,以下内容仅作为参考。
->
-
-- [Queue](#queue)
- - [什么是队列](#什么是队列)
- - [队列的种类](#队列的种类)
- - [Java 集合框架中的队列 Queue](#java-集合框架中的队列-queue)
- - [推荐文章](#推荐文章)
-- [Set](#set)
- - [什么是 Set](#什么是-set)
- - [补充:有序集合与无序集合说明](#补充:有序集合与无序集合说明)
- - [HashSet 和 TreeSet 底层数据结构](#hashset-和-treeset-底层数据结构)
- - [推荐文章](#推荐文章-1)
-- [List](#list)
- - [什么是List](#什么是list)
- - [List的常见实现类](#list的常见实现类)
- - [ArrayList 和 LinkedList 源码学习](#arraylist-和-linkedlist-源码学习)
- - [推荐阅读](#推荐阅读)
-- [Map](#map)
-- [树](#树)
-
-
-
-
-## Queue
-
-### 什么是队列
-队列是数据结构中比较重要的一种类型,它支持 FIFO,尾部添加、头部删除(先进队列的元素先出队列),跟我们生活中的排队类似。
-
-### 队列的种类
-
-- **单队列**(单队列就是常见的队列, 每次添加元素时,都是添加到队尾,存在“假溢出”的问题也就是明明有位置却不能添加的情况)
-- **循环队列**(避免了“假溢出”的问题)
-
-### Java 集合框架中的队列 Queue
-
-Java 集合中的 Queue 继承自 Collection 接口 ,Deque, LinkedList, PriorityQueue, BlockingQueue 等类都实现了它。
-Queue 用来存放 等待处理元素 的集合,这种场景一般用于缓冲、并发访问。
-除了继承 Collection 接口的一些方法,Queue 还添加了额外的 添加、删除、查询操作。
-
-## Set
-
-### 什么是 Set
-Set 继承于 Collection 接口,是一个不允许出现重复元素,并且无序的集合,主要 HashSet 和 TreeSet 两大实现类。
-
-在判断重复元素的时候,HashSet 集合会调用 hashCode()和 equal()方法来实现;TreeSet 集合会调用compareTo方法来实现。
-
-### 补充:有序集合与无序集合说明
-- 有序集合:集合里的元素可以根据 key 或 index 访问 (List、Map)
-- 无序集合:集合里的元素只能遍历。(Set)
-
-
-### HashSet 和 TreeSet 底层数据结构
-
-**HashSet** 是哈希表结构,主要利用 HashMap 的 key 来存储元素,计算插入元素的 hashCode 来获取元素在集合中的位置;
-
-**TreeSet** 是红黑树结构,每一个元素都是树中的一个节点,插入的元素都会进行排序;
-
-## List
-
-### 什么是List
-
-在 List 中,用户可以精确控制列表中每个元素的插入位置,另外用户可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素。 与 Set 不同,List 通常允许重复的元素。 另外 List 是有序集合而 Set 是无序集合。
-
-### List的常见实现类
-
-**ArrayList** 是一个数组队列,相当于动态数组。它由数组实现,随机访问效率高,随机插入、随机删除效率低。
-
-**LinkedList** 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率高。
-
-**Vector** 是矢量队列,和ArrayList一样,它也是一个动态数组,由数组实现。但是ArrayList是非线程安全的,而Vector是线程安全的。
-
-**Stack** 是栈,它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。
-
-## 树
-
-### 1 二叉树
-
-[二叉树](https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科)
-
-(1)[完全二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)——若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
-
-(2)[满二叉树](https://baike.baidu.com/item/%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91)——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。
-
-(3)[平衡二叉树](https://baike.baidu.com/item/%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91/10421057)——平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
-
-### 2 完全二叉树
-
-[完全二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科)
-
-完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。
-
-### 3 满二叉树
-
-[满二叉树](https://baike.baidu.com/item/%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科,国内外的定义不同)
-
-国内教程定义:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
-
-### 堆
-
-[数据结构之堆的定义](https://blog.csdn.net/qq_33186366/article/details/51876191)
-
-堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
-
-### 4 二叉查找树(BST)
-
-[浅谈算法和数据结构: 七 二叉查找树](https://www.yycoding.xyz/post/2014/3/24/introduce-binary-search-tree)
-
-二叉查找树的特点:
-
-1. 若任意节点的左子树不空,则左子树上所有结点的 值均小于它的根结点的值;
-2. 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
-3. 任意节点的左、右子树也分别为二叉查找树;
-4. 没有键值相等的节点(no duplicate nodes)。
-
-### 5 平衡二叉树(Self-balancing binary search tree)
-
-[ 平衡二叉树](https://baike.baidu.com/item/%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科,平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等)
-
-### 6 红黑树
-
-红黑树特点:
-
-1. 每个节点非红即黑;
-2. 根节点总是黑色的;
-3. 每个叶子节点都是黑色的空节点(NIL节点);
-4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定);
-5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)。
-
-
-红黑树的应用:
-
-TreeMap、TreeSet以及JDK1.8的HashMap底层都用到了红黑树。
-
-**为什么要用红黑树?**
-
-
-简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。详细了解可以查看 [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐)
-
-推荐文章:
-
-- [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐)
-- [寻找红黑树的操作手册](http://dandanlove.com/2018/03/18/red-black-tree/)(文章排版以及思路真的不错)
-- [红黑树深入剖析及Java实现](https://zhuanlan.zhihu.com/p/24367771)(美团点评技术团队)
-
-### 7 B-,B+,B*树
-
-[二叉树学习笔记之B树、B+树、B*树 ](https://yq.aliyun.com/articles/38345)
-
-[《B-树,B+树,B*树详解》](https://blog.csdn.net/aqzwss/article/details/53074186)
-
-[《B-树,B+树与B*树的优缺点比较》](https://blog.csdn.net/bigtree_3721/article/details/73632405)
-
-B-树(或B树)是一种平衡的多路查找(又称排序)树,在文件系统中有所应用。主要用作文件的索引。其中的B就表示平衡(Balance)
-
-1. B+ 树的叶子节点链表结构相比于 B- 树便于扫库,和范围检索。
-2. B+树支持range-query(区间查询)非常方便,而B树不支持。这是数据库选用B+树的最主要原因。
-3. B\*树 是B+树的变体,B\*树分配新结点的概率比B+树要低,空间使用率更高;
-
-### 8 LSM 树
-
-[[HBase] LSM树 VS B+树](https://blog.csdn.net/dbanote/article/details/8897599)
-
-B+树最大的性能问题是会产生大量的随机IO
-
-为了克服B+树的弱点,HBase引入了LSM树的概念,即Log-Structured Merge-Trees。
-
-[LSM树由来、设计思想以及应用到HBase的索引](http://www.cnblogs.com/yanghuahui/p/3483754.html)
-
-
-## 图
-
-
-
-
-## BFS及DFS
-
-- [《使用BFS及DFS遍历树和图的思路及实现》](https://blog.csdn.net/Gene1994/article/details/85097507)
-
diff --git a/docs/database/MySQL Index.md b/docs/database/MySQL Index.md
index 69d606f1..32d88388 100644
--- a/docs/database/MySQL Index.md
+++ b/docs/database/MySQL Index.md
@@ -28,11 +28,9 @@ select username , age from user where username = 'Java' and age = 22
## 选择索引和编写利用这些索引的查询的3个原则
-1. 单行访问是很慢的。特别是在机械硬盘存储中(SSD的随机I/O要快很多,不过这一点仍然成立)。如果服务器从存储中读取一个数据块只是为了获取其中一行,那么就浪费了很多工作。最好读取的块中能包含尽可能多所需要的行。使用索引可以创建位置引,用以提升效率。
+1. 单行访问是很慢的。特别是在机械硬盘存储中(SSD的随机I/O要快很多,不过这一点仍然成立)。如果服务器从存储中读取一个数据块只是为了获取其中一行,那么就浪费了很多工作。最好读取的块中能包含尽可能多所需要的行。使用索引可以创建位置引,用以提升效率。
2. 按顺序访问范围数据是很快的,这有两个原因。第一,顺序 I/O 不需要多次磁盘寻道,所以比随机I/O要快很多(特别是对机械硬盘)。第二,如果服务器能够按需要顺序读取数据,那么就不再需要额外的排序操作,并且GROUPBY查询也无须再做排序和将行按组进行聚合计算了。
-3. 索引覆盖查询是很快的。如果一个索引包含了查询需要的所有列,那么存储引擎就
- 不需要再回表查找行。这避免了大量的单行访问,而上面的第1点已经写明单行访
- 问是很慢的。
+3. 索引覆盖查询是很快的。如果一个索引包含了查询需要的所有列,那么存储引擎就不需要再回表查找行。这避免了大量的单行访问,而上面的第1点已经写明单行访问是很慢的。
## 为什么索引能提高查询速度
@@ -44,9 +42,9 @@ select username , age from user where username = 'Java' and age = 22
MySQL的基本存储结构是页(记录都存在页里边):
-
+
-
+
- **各个数据页可以组成一个双向链表**
- **每个数据页中的记录又可以组成一个单向链表**
@@ -58,18 +56,18 @@ MySQL的基本存储结构是页(记录都存在页里边):
1. **定位到记录所在的页:需要遍历双向链表,找到所在的页**
2. **从所在的页内中查找相应的记录:由于不是根据主键查询,只能遍历所在页的单链表了**
-很明显,在数据量很大的情况下这样查找会很慢!这样的时间复杂度为O(n)。
+很明显,在数据量很大的情况下这样查找会很慢!这样的时间复杂度为O(n)。
### 使用索引之后
索引做了些什么可以让我们查询加快速度呢?其实就是将无序的数据变成有序(相对):
-
+
要找到id为8的记录简要步骤:
-
+
很明显的是:没有用索引我们是需要遍历双向链表来定位对应的页,现在通过 **“目录”** 就可以很快地定位到对应的页上了!(二分查找,时间复杂度近似为O(logn))
@@ -95,7 +93,7 @@ select * from user where city=xx ; // 无法命中索引
### 注意避免冗余索引
-冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如(name,city )和(name )这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。
+冗余索引指的是索引的功能相同,能够命中索引(a, b)就肯定能命中索引(a) ,那么索引(a)就是冗余索引。如(name,city)和(name)这两个索引就是冗余索引,能够命中前者的查询肯定是能够命中后者。在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。
-MySQL 5.7 版本后,可以通过查询 sys 库的 `schema_redundant_indexes` 表来查看冗余索引
+MySQL 5.7 版本后,可以通过查询 sys 库的 `schema_redundant_indexes` 表来查看冗余索引。
diff --git a/docs/database/Redis/Redis持久化.md b/docs/database/Redis/Redis持久化.md
index 0408c276..2da52eec 100644
--- a/docs/database/Redis/Redis持久化.md
+++ b/docs/database/Redis/Redis持久化.md
@@ -7,7 +7,7 @@
**很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后回复数据),或者是为了防止系统故障而将数据备份到一个远程位置。**
-Redis不同于Memcached的很重一点就是,**Redis支持持久化**,而且支持两种不同的持久化操作。Redis的一种持久化方式叫**快照(snapshotting,RDB)**,另一种方式是**只追加文件(append-only file,AOF)**.这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。
+Redis不同于Memcached的很重要一点就是,**Redis支持持久化**,而且支持两种不同的持久化操作。Redis的一种持久化方式叫**快照(snapshotting,RDB)**,另一种方式是**只追加文件(append-only file,AOF)**。这两种方法各有千秋,下面我会详细这两种持久化方法是什么,怎么用,如何选择适合自己的持久化方法。
## 快照(snapshotting)持久化
@@ -16,8 +16,8 @@ Redis可以通过创建快照来获得存储在内存里面的数据在某个时

**快照持久化是Redis默认采用的持久化方式**,在redis.conf配置文件中默认有此下配置:
-```
+```
save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发BGSAVE命令创建快照。
@@ -39,8 +39,6 @@ save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生
如果系统真的发生崩溃,用户将丢失最近一次生成快照之后更改的所有数据。因此,快照持久化只适用于即使丢失一部分数据也不会造成一些大问题的应用程序。不能接受这个缺点的话,可以考虑AOF持久化。
-
-
## **AOF(append-only file)持久化**
与快照持久化相比,AOF持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启:
@@ -55,7 +53,6 @@ appendonly yes
**在Redis的配置文件中存在三种同步方式,它们分别是:**
```
-
appendfsync always #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no #让操作系统决定何时进行同步
@@ -65,7 +62,6 @@ appendfsync no #让操作系统决定何时进行同步
为了兼顾数据和写入性能,用户可以考虑 **appendfsync everysec选项** ,让Redis每秒同步一次AOF文件,Redis性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
-
**appendfsync no** 选项一般不推荐,这种方案会使Redis丢失不定量的数据而且如果用户的硬盘处理写入操作的速度不够的话,那么当缓冲区被等待写入的数据填满时,Redis的写入操作将被阻塞,这会导致Redis的请求速度变慢。
**虽然AOF持久化非常灵活地提供了多种不同的选项来满足不同应用程序对数据安全的不同要求,但AOF持久化也有缺陷——AOF文件的体积太大。**
@@ -100,7 +96,7 @@ auto-aof-rewrite-min-size 64mb
无论是AOF持久化还是快照持久化,将数据持久化到硬盘上都是非常有必要的,但除了进行持久化外,用户还必须对持久化得到的文件进行备份(最好是备份到不同的地方),这样才能尽量避免数据丢失事故发生。如果条件允许的话,最好能将快照文件和重新重写的AOF文件备份到不同的服务器上面。
-随着负载量的上升,或者数据的完整性变得 越来越重要时,用户可能需要使用到复制特性。
+随着负载量的上升,或者数据的完整性变得越来越重要时,用户可能需要使用到复制特性。
## Redis 4.0 对于持久化机制的优化
Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通过配置项 `aof-use-rdb-preamble` 开启)。
@@ -113,4 +109,3 @@ Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通
[深入学习Redis(2):持久化](https://www.cnblogs.com/kismetv/p/9137897.html)
-
diff --git a/docs/database/Redis/redis-all.md b/docs/database/Redis/redis-all.md
index 1da3a9a3..55dc910f 100644
--- a/docs/database/Redis/redis-all.md
+++ b/docs/database/Redis/redis-all.md
@@ -1,62 +1,21 @@
-点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java 面试突击》以及 Java 工程师必备学习资源。
-
-
-
-
-
-- [1. 简单介绍一下 Redis 呗!](#1-简单介绍一下-redis-呗)
-- [2. 分布式缓存常见的技术选型方案有哪些?](#2-分布式缓存常见的技术选型方案有哪些)
-- [3. 说一下 Redis 和 Memcached 的区别和共同点](#3-说一下-redis-和-memcached-的区别和共同点)
-- [4. 缓存数据的处理流程是怎样的?](#4-缓存数据的处理流程是怎样的)
-- [5. 为什么要用 Redis/为什么要用缓存?](#5-为什么要用-redis为什么要用缓存)
-- [6. Redis 常见数据结构以及使用场景分析](#6-redis-常见数据结构以及使用场景分析)
- - [6.1. string](#61-string)
- - [6.2. list](#62-list)
- - [6.3. hash](#63-hash)
- - [6.4. set](#64-set)
- - [6.5. sorted set](#65-sorted-set)
- - [6.6 bitmap](#66-bitmap)
-- [7. Redis 单线程模型详解](#7-redis-单线程模型详解)
-- [8. Redis 没有使用多线程?为什么不使用多线程?](#8-redis-没有使用多线程为什么不使用多线程)
-- [9. Redis6.0 之后为何引入了多线程?](#9-redis60-之后为何引入了多线程)
-- [10. Redis 给缓存数据设置过期时间有啥用?](#10-redis-给缓存数据设置过期时间有啥用)
-- [11. Redis 是如何判断数据是否过期的呢?](#11-redis-是如何判断数据是否过期的呢)
-- [12. 过期的数据的删除策略了解么?](#12-过期的数据的删除策略了解么)
-- [13. Redis 内存淘汰机制了解么?](#13-redis-内存淘汰机制了解么)
-- [14. Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)](#14-redis-持久化机制怎么保证-redis-挂掉之后再重启数据可以进行恢复)
-- [15. Redis 事务](#15-redis-事务)
-- [16. 缓存穿透](#16-缓存穿透)
- - [16.1. 什么是缓存穿透?](#161-什么是缓存穿透)
- - [16.2. 缓存穿透情况的处理流程是怎样的?](#162-缓存穿透情况的处理流程是怎样的)
- - [16.3. 有哪些解决办法?](#163-有哪些解决办法)
-- [17. 缓存雪崩](#17-缓存雪崩)
- - [17.1. 什么是缓存雪崩?](#171-什么是缓存雪崩)
- - [17.2. 有哪些解决办法?](#172-有哪些解决办法)
-- [18. 如何保证缓存和数据库数据的一致性?](#18-如何保证缓存和数据库数据的一致性)
-- [19. 参考](#19-参考)
-- [20. 公众号](#20-公众号)
-
-
-
-
-### 1. 简单介绍一下 Redis 呗!
+### 简单介绍一下 Redis 呗!
简单来说 **Redis 就是一个使用 C 语言开发的数据库**,不过与传统数据库不同的是 **Redis 的数据是存在内存中的** ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。
-另外,**Redis 除了做缓存之外,Redis 也经常用来做分布式锁,甚至是消息队列。**
+另外,**Redis 除了做缓存之外,也经常用来做分布式锁,甚至是消息队列。**
**Redis 提供了多种数据类型来支持不同的业务场景。Redis 还支持事务 、持久化、Lua 脚本、多种集群方案。**
-### 2. 分布式缓存常见的技术选型方案有哪些?
+### 分布式缓存常见的技术选型方案有哪些?
分布式缓存的话,使用的比较多的主要是 **Memcached** 和 **Redis**。不过,现在基本没有看过还有项目使用 **Memcached** 来做缓存,都是直接用 **Redis**。
Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来,随着 Redis 的发展,大家慢慢都转而使用更加强大的 Redis 了。
-分布式缓存主要解决的是单机缓存的容量受服务器限制并且无法保存通用的信息。因为,本地缓存只在当前服务里有效,比如如果你部署了两个相同的服务,他们两者之间的缓存数据是无法共同的。
+分布式缓存主要解决的是单机缓存的容量受服务器限制并且无法保存通用信息的问题。因为,本地缓存只在当前服务里有效,比如如果你部署了两个相同的服务,他们两者之间的缓存数据是无法共同的。
-### 3. 说一下 Redis 和 Memcached 的区别和共同点
+### 说一下 Redis 和 Memcached 的区别和共同点
现在公司一般都是用 Redis 来实现缓存,而且 Redis 自身也越来越强大了!不过,了解 Redis 和 Memcached 的区别和共同点,有助于我们在做相应的技术选型的时候,能够做到有理有据!
@@ -72,14 +31,14 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来
2. **Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memecache 把数据全部存在内存之中。**
3. **Redis 有灾难恢复机制。** 因为可以把缓存中的数据持久化到磁盘上。
4. **Redis 在服务器内存使用完之后,可以将不用的数据放到磁盘上。但是,Memcached 在服务器内存使用完之后,就会直接报异常。**
-5. **Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的.**
+5. **Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的。**
6. **Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的多路 IO 复用模型。** (Redis 6.0 引入了多线程 IO )
7. **Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持。并且,Redis 支持更多的编程语言。**
8. **Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除。**
相信看了上面的对比之后,我们已经没有什么理由可以选择使用 Memcached 来作为自己项目的分布式缓存了。
-### 4. 缓存数据的处理流程是怎样的?
+### 缓存数据的处理流程是怎样的?
作为暖男一号,我给大家画了一个草图。
@@ -92,7 +51,7 @@ Memcached 是分布式缓存最开始兴起的那会,比较常用的。后来
3. 数据库中存在的话就更新缓存中的数据。
4. 数据库中不存在的话就返回空数据。
-### 5. 为什么要用 Redis/为什么要用缓存?
+### 为什么要用 Redis/为什么要用缓存?
_简单,来说使用缓存主要是为了提升用户体验以及应对更多的用户。_
@@ -116,19 +75,27 @@ _简单,来说使用缓存主要是为了提升用户体验以及应对更多
> QPS(Query Per Second):服务器每秒可以执行的查询次数;
-所以,直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。进而,我们也就提高的系统整体的并发。
+由此可见,直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。进而,我们也就提高了系统整体的并发。
-### 6. Redis 常见数据结构以及使用场景分析
+### Redis 除了做缓存,还能做什么?
+
+- **分布式锁** : 通过 Redis 来做分布式锁是一种比较常见的方式。通常情况下,我们都是基于 Redisson 来实现分布式锁。相关阅读:[《分布式锁中的王者方案 - Redisson》](https://mp.weixin.qq.com/s/CbnPRfvq4m1sqo2uKI6qQw)。
+- **限流** :一般是通过 Redis + Lua 脚本的方式来实现限流。相关阅读:[《我司用了 6 年的 Redis 分布式限流器,可以说是非常厉害了!》](https://mp.weixin.qq.com/s/kyFAWH3mVNJvurQDt4vchA)。
+- **消息队列** :Redis 自带的 list 数据结构可以作为一个简单的队列使用。Redis5.0 中增加的 Stream 类型的数据结构更加适合用来做消息队列。它比较类似于 Kafka,有主题和消费组的概念,支持消息持久化以及 ACK 机制。
+- **复杂业务场景** :通过 Redis 以及 Redis 扩展(比如 Redisson)提供的数据结构,我们可以很方便地完成很多复杂的业务场景比如通过 bitmap 统计活跃用户、通过 sorted set 维护排行榜。
+- ......
+
+### Redis 常见数据结构以及使用场景分析
你可以自己本机安装 redis 或者通过 redis 官网提供的[在线 redis 环境](https://try.redis.io/)。

-#### 6.1. string
+#### string
-1. **介绍** :string 数据结构是简单的 key-value 类型。虽然 Redis 是用 C 语言写的,但是 Redis 并没有使用 C 的字符串表示,而是自己构建了一种 **简单动态字符串**(simple dynamic string,**SDS**)。相比于 C 的原生字符串,Redis 的 SDS 不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1)(C 字符串为 O(N)),除此之外,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。
-2. **常用命令:** `set,get,strlen,exists,dect,incr,setex` 等等。
-3. **应用场景** :一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等。
+1. **介绍** :string 数据结构是简单的 key-value 类型。虽然 Redis 是用 C 语言写的,但是 Redis 并没有使用 C 的字符串表示,而是自己构建了一种 **简单动态字符串**(simple dynamic string,**SDS**)。相比于 C 的原生字符串,Redis 的 SDS 不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1)(C 字符串为 O(N)),除此之外,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。
+2. **常用命令:** `set,get,strlen,exists,decr,incr,setex` 等等。
+3. **应用场景:** 一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等。
下面我们简单看看它的使用!
@@ -162,7 +129,6 @@ OK
**计数器(字符串的内容为整数的时候可以使用):**
```bash
-
127.0.0.1:6379> set number 1
OK
127.0.0.1:6379> incr number # 将 key 中储存的数字值增一
@@ -175,7 +141,7 @@ OK
"1"
```
-**过期**:
+**过期(默认为永不过期)**:
```bash
127.0.0.1:6379> expire key 60 # 数据在 60s 后过期
@@ -186,10 +152,10 @@ OK
(integer) 56
```
-#### 6.2. list
+#### list
-1. **介绍** :**list** 即是 **链表**。链表是一种非常常见的数据结构,特点是易于数据元素的插入和删除并且且可以灵活调整链表长度,但是链表的随机访问困难。许多高级编程语言都内置了链表的实现比如 Java 中的 **LinkedList**,但是 C 语言并没有实现链表,所以 Redis 实现了自己的链表数据结构。Redis 的 list 的实现为一个 **双向链表**,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
-2. **常用命令:** `rpush,lpop,lpush,rpop,lrange、llen` 等。
+1. **介绍** :**list** 即是 **链表**。链表是一种非常常见的数据结构,特点是易于数据元素的插入和删除并且可以灵活调整链表长度,但是链表的随机访问困难。许多高级编程语言都内置了链表的实现比如 Java 中的 **LinkedList**,但是 C 语言并没有实现链表,所以 Redis 实现了自己的链表数据结构。Redis 的 list 的实现为一个 **双向链表**,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
+2. **常用命令:** `rpush,lpop,lpush,rpop,lrange,llen` 等。
3. **应用场景:** 发布与订阅或者说消息队列、慢查询。
下面我们简单看看它的使用!
@@ -247,7 +213,7 @@ OK
(integer) 3
```
-#### 6.3. hash
+#### hash
1. **介绍** :hash 类似于 JDK1.8 前的 HashMap,内部实现也差不多(数组 + 链表)。不过,Redis 的 hash 做了更多优化。另外,hash 是一个 string 类型的 field 和 value 的映射表,**特别适合用于存储对象**,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以 hash 数据结构来存储用户信息,商品信息等等。
2. **常用命令:** `hset,hmset,hexists,hget,hgetall,hkeys,hvals` 等。
@@ -284,7 +250,7 @@ OK
"GuideGeGe"
```
-#### 6.4. set
+#### set
1. **介绍 :** set 类似于 Java 中的 `HashSet` 。Redis 中的 set 类型是一种无序集合,集合中的元素没有先后顺序。当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。比如:你可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。
2. **常用命令:** `sadd,spop,smembers,sismember,scard,sinterstore,sunion` 等。
@@ -312,7 +278,7 @@ OK
1) "value2"
```
-#### 6.5. sorted set
+#### sorted set
1. **介绍:** 和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合体。
2. **常用命令:** `zadd,zcard,zscore,zrange,zrevrange,zrem` 等。
@@ -339,11 +305,11 @@ OK
2) "value2"
```
-#### 6.6 bitmap
+#### bitmap
-1. **介绍 :** bitmap 存储的是连续的二进制数字(0 和 1),通过 bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 bitmap 本身会极大的节省储存空间。
+1. **介绍:** bitmap 存储的是连续的二进制数字(0 和 1),通过 bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 bitmap 本身会极大的节省储存空间。
2. **常用命令:** `setbit` 、`getbit` 、`bitcount`、`bitop`
-3. **应用场景:** 适合需要保存状态信息(比如是否签到、是否登录...)并需要进一步对这些信息进行分析的场景。比如用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)。
+3. **应用场景:** 适合需要保存状态信息(比如是否签到、是否登录...)并需要进一步对这些信息进行分析的场景。比如用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)。
```bash
# SETBIT 会返回之前位的值(默认是 0)这里会生成 7 个位
@@ -376,7 +342,7 @@ OK
使用时间作为 key,然后用户 ID 为 offset,如果当日活跃过就设置为 1
-那么我该如果计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只有有一天在线就称为活跃),有请下一个 redis 的命令
+那么我该如何计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只要有一天在线就称为活跃),有请下一个 redis 的命令
```bash
# 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。
@@ -415,27 +381,27 @@ BITOP operation destkey key [key ...]
**使用场景三:用户在线状态**
-对于获取或者统计用户在线状态,使用 bitmap 是一个节约空间效率又高的一种方法。
+对于获取或者统计用户在线状态,使用 bitmap 是一个节约空间且效率又高的一种方法。
只需要一个 key,然后用户 ID 为 offset,如果在线就设置为 1,不在线就设置为 0。
-### 7. Redis 单线程模型详解
+### Redis 单线程模型详解
**Redis 基于 Reactor 模式来设计开发了自己的一套高效的事件处理模型** (Netty 的线程模型也基于 Reactor 模式,Reactor 模式不愧是高性能 IO 的基石),这套事件处理模型对应的是 Redis 中的文件事件处理器(file event handler)。由于文件事件处理器(file event handler)是单线程方式运行的,所以我们一般都说 Redis 是单线程模型。
**既然是单线程,那怎么监听大量的客户端连接呢?**
-Redis 通过**IO 多路复用程序** 来监听来自客户端的大量连接(或者说是监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发生。
+Redis 通过**IO 多路复用程序** 来监听来自客户端的大量连接(或者说是监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发生。
这样的好处非常明显: **I/O 多路复用技术的使用让 Redis 不需要额外创建多余的线程来监听客户端的大量连接,降低了资源的消耗**(和 NIO 中的 `Selector` 组件很像)。
-另外, Redis 服务器是一个事件驱动程序,服务器需要处理两类事件: 1. 文件事件; 2. 时间事件。
+另外, Redis 服务器是一个事件驱动程序,服务器需要处理两类事件:1. 文件事件; 2. 时间事件。
时间事件不需要多花时间了解,我们接触最多的还是 **文件事件**(客户端进行读取写入等操作,涉及一系列网络通信)。
《Redis 设计与实现》有一段话是如是介绍文件事件的,我觉得写得挺不错。
-> Redis 基于 Reactor 模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器(file event handler)。文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字,并根据 套接字目前执行的任务来为套接字关联不同的事件处理器。
+> Redis 基于 Reactor 模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器(file event handler)。文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
>
> 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关 闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。
>
@@ -452,9 +418,9 @@ Redis 通过**IO 多路复用程序** 来监听来自客户端的大量连接(
《Redis设计与实现:12章》
-### 8. Redis 没有使用多线程?为什么不使用多线程?
+### Redis 没有使用多线程?为什么不使用多线程?
-虽然说 Redis 是单线程模型,但是, 实际上,**Redis 在 4.0 之后的版本中就已经加入了对多线程的支持。**
+虽然说 Redis 是单线程模型,但是,实际上,**Redis 在 4.0 之后的版本中就已经加入了对多线程的支持。**

@@ -467,14 +433,14 @@ Redis 通过**IO 多路复用程序** 来监听来自客户端的大量连接(
我觉得主要原因有下面 3 个:
1. 单线程编程容易并且更容易维护;
-2. Redis 的性能瓶颈不再 CPU ,主要在内存和网络;
+2. Redis 的性能瓶颈不在 CPU ,主要在内存和网络;
3. 多线程就会存在死锁、线程上下文切换等问题,甚至会影响性能。
-### 9. Redis6.0 之后为何引入了多线程?
+### Redis6.0 之后为何引入了多线程?
**Redis6.0 引入多线程主要是为了提高网络 IO 读写性能**,因为这个算是 Redis 中的一个性能瓶颈(Redis 的瓶颈主要受限于内存和网络)。
-虽然,Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了, 执行命令仍然是单线程顺序执行。因此,你也不需要担心线程安全问题。
+虽然,Redis6.0 引入了多线程,但是 Redis 的多线程只是在网络数据的读写这类耗时操作上使用了,执行命令仍然是单线程顺序执行。因此,你也不需要担心线程安全问题。
Redis6.0 的多线程默认是禁用的,只使用主线程。如需开启需要修改 redis 配置文件 `redis.conf` :
@@ -493,7 +459,7 @@ io-threads 4 #官网建议4核的机器建议设置为2或3个线程,8核的
1. [Redis 6.0 新特性-多线程连环 13 问!](https://mp.weixin.qq.com/s/FZu3acwK6zrCBZQ_3HoUgw)
2. [为什么 Redis 选择单线程模型](https://draveness.me/whys-the-design-redis-single-thread/)
-### 10. Redis 给缓存数据设置过期时间有啥用?
+### Redis 给缓存数据设置过期时间有啥用?
一般情况下,我们设置保存的缓存数据的时候都会设置一个过期时间。为什么呢?
@@ -502,7 +468,7 @@ io-threads 4 #官网建议4核的机器建议设置为2或3个线程,8核的
Redis 自带了给缓存数据设置过期时间的功能,比如:
```bash
-127.0.0.1:6379> exp key 60 # 数据在 60s 后过期
+127.0.0.1:6379> exp key 60 # 数据在 60s 后过期
(integer) 1
127.0.0.1:6379> setex key 60 value # 数据在 60s 后过期 (setex:[set] + [ex]pire)
OK
@@ -510,7 +476,7 @@ OK
(integer) 56
```
-注意:**Redis 中除了字符串类型有自己独有设置过期时间的命令 `setex` 外,其他方法都需要依靠 `expire` 命令来设置过期时间 。另外, `persist` 命令可以移除一个键的过期时间: **
+注意:**Redis 中除了字符串类型有自己独有设置过期时间的命令 `setex` 外,其他方法都需要依靠 `expire` 命令来设置过期时间 。另外, `persist` 命令可以移除一个键的过期时间。 **
**过期时间除了有助于缓解内存的消耗,还有什么其他用么?**
@@ -518,7 +484,7 @@ OK
如果使用传统的数据库来处理的话,一般都是自己判断过期,这样更麻烦并且性能要差很多。
-### 11. Redis 是如何判断数据是否过期的呢?
+### Redis 是如何判断数据是否过期的呢?
Redis 通过一个叫做过期字典(可以看作是 hash 表)来保存数据过期的时间。过期字典的键指向 Redis 数据库中的某个 key(键),过期字典的值是一个 long long 类型的整数,这个整数保存了 key 所指向的数据库键的过期时间(毫秒精度的 UNIX 时间戳)。
@@ -536,7 +502,7 @@ typedef struct redisDb {
} redisDb;
```
-### 12. 过期的数据的删除策略了解么?
+### 过期的数据的删除策略了解么?
如果假设你设置了一批 key 只能存活 1 分钟,那么 1 分钟后,Redis 是怎么对这批 key 进行删除的呢?
@@ -549,9 +515,9 @@ typedef struct redisDb {
但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里,然后就 Out of memory 了。
-怎么解决这个问题呢?答案就是: **Redis 内存淘汰机制。**
+怎么解决这个问题呢?答案就是:**Redis 内存淘汰机制。**
-### 13. Redis 内存淘汰机制了解么?
+### Redis 内存淘汰机制了解么?
> 相关问题:MySQL 里有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis 中的数据都是热点数据?
@@ -566,10 +532,10 @@ Redis 提供 6 种数据淘汰策略:
4.0 版本后增加以下两种:
-7. **volatile-lfu(least frequently used)**:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
+7. **volatile-lfu(least frequently used)**:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
8. **allkeys-lfu(least frequently used)**:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
-### 14. Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)
+### Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)
很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
@@ -591,13 +557,15 @@ save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生
**AOF(append-only file)持久化**
-与快照持久化相比,AOF 持久化 的实时性更好,因此已成为主流的持久化方案。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化,可以通过 appendonly 参数开启:
+与快照持久化相比,AOF 持久化的实时性更好,因此已成为主流的持久化方案。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化,可以通过 appendonly 参数开启:
```conf
appendonly yes
```
-开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入硬盘中的 AOF 文件。AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 appendonly.aof。
+开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到内存缓存 `server.aof_buf` 中,然后再根据 `appendfsync` 配置来决定何时将其同步到硬盘中的 AOF 文件。
+
+AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 `appendonly.aof`。
在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:
@@ -607,7 +575,7 @@ appendfsync everysec #每秒钟同步一次,显示地将多个写命令同步
appendfsync no #让操作系统决定何时进行同步
```
-为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
+为了兼顾数据和写入性能,用户可以考虑 `appendfsync everysec` 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。
**相关 issue** :[783:Redis 的 AOF 方式](https://github.com/Snailclimb/JavaGuide/issues/783)
@@ -617,15 +585,19 @@ Redis 4.0 开始支持 RDB 和 AOF 的混合持久化(默认关闭,可以通
如果把混合持久化打开,AOF 重写的时候就直接把 RDB 的内容写到 AOF 文件开头。这样做的好处是可以结合 RDB 和 AOF 的优点, 快速加载同时避免丢失过多的数据。当然缺点也是有的, AOF 里面的 RDB 部分是压缩格式不再是 AOF 格式,可读性较差。
+官方文档地址:https://redis.io/topics/persistence
+
+
+
**补充内容:AOF 重写**
AOF 重写可以产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。
AOF 重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有 AOF 文件进行任何读入、分析或者写入操作。
-在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新旧两个 AOF 文件所保存的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作
+在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新旧两个 AOF 文件所保存的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。
-### 15. Redis 事务
+### Redis 事务
Redis 可以通过 **`MULTI`,`EXEC`,`DISCARD` 和 `WATCH`** 等命令来实现事务(transaction)功能。
@@ -641,7 +613,7 @@ QUEUED
2) "Guide哥"
```
-使用 [`MULTI`](https://redis.io/commands/multi)命令后可以输入多个命令。Redis 不会立即执行这些命令,而是将它们放到队列,当调用了[`EXEC`](https://redis.io/commands/exec)命令将执行所有命令。
+使用 [`MULTI`](https://redis.io/commands/multi) 命令后可以输入多个命令。Redis 不会立即执行这些命令,而是将它们放到队列,当调用了 [`EXEC`](https://redis.io/commands/exec) 命令将执行所有命令。
这个过程是这样的:
@@ -700,19 +672,19 @@ Redis 官网也解释了自己为啥不支持回滚。简单来说就是 Redis
- [issue452: 关于 Redis 事务不满足原子性的问题](https://github.com/Snailclimb/JavaGuide/issues/452) 。
- [Issue491:关于 redis 没有事务回滚?](https://github.com/Snailclimb/JavaGuide/issues/491)
-### 16. 缓存穿透
+### 缓存穿透
-#### 16.1. 什么是缓存穿透?
+#### 什么是缓存穿透?
缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。
-#### 16.2. 缓存穿透情况的处理流程是怎样的?
+#### 缓存穿透情况的处理流程是怎样的?
如下图所示,用户的请求最终都要跑到数据库中查询一遍。

-#### 16.3. 有哪些解决办法?
+#### 有哪些解决办法?
最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。
@@ -771,11 +743,11 @@ _为什么会出现误判的情况呢? 我们还要从布隆过滤器的原理
然后,一定会出现这样一种情况:**不同的字符串可能哈希出来的位置相同。** (可以适当增加位数组大小或者调整我们的哈希函数来降低概率)
-更多关于布隆过滤器的内容可以看我的这篇原创:[《不了解布隆过滤器?一文给你整的明明白白!》](https://github.com/Snailclimb/JavaGuide/blob/master/docs/dataStructures-algorithms/data-structure/bloom-filter.md) ,强烈推荐,个人感觉网上应该找不到总结的这么明明白白的文章了。
+更多关于布隆过滤器的内容可以看我的这篇原创:[《不了解布隆过滤器?一文给你整的明明白白!》](https://github.com/Snailclimb/JavaGuide/blob/master/docs/cs-basics/data-structure/bloom-filter.md) ,强烈推荐,个人感觉网上应该找不到总结的这么明明白白的文章了。
-### 17. 缓存雪崩
+### 缓存雪崩
-#### 17.1. 什么是缓存雪崩?
+#### 什么是缓存雪崩?
我发现缓存雪崩这名字起的有点意思,哈哈。
@@ -787,7 +759,7 @@ _为什么会出现误判的情况呢? 我们还要从布隆过滤器的原理
举个例子 :秒杀开始 12 个小时之前,我们统一存放了一批商品到 Redis 中,设置的缓存过期时间也是 12 个小时,那么秒杀开始的时候,这些秒杀的商品的访问直接就失效了。导致的情况就是,相应的请求直接就落到了数据库上,就像雪崩一样可怕。
-#### 17.2. 有哪些解决办法?
+#### 有哪些解决办法?
**针对 Redis 服务不可用的情况:**
@@ -799,7 +771,7 @@ _为什么会出现误判的情况呢? 我们还要从布隆过滤器的原理
1. 设置不同的失效时间比如随机设置缓存的失效时间。
2. 缓存永不失效。
-### 18. 如何保证缓存和数据库数据的一致性?
+### 如何保证缓存和数据库数据的一致性?
细说的话可以扯很多,但是我觉得其实没太大必要(小声 BB:很多解决方案我也没太弄明白)。我个人觉得引入缓存之后,如果为了短时间的不一致性问题,选择让系统设计变得更加复杂的话,完全没必要。
@@ -810,22 +782,12 @@ Cache Aside Pattern 中遇到写请求是这样的:更新 DB,然后直接删
如果更新数据库成功,而删除缓存这一步失败的情况的话,简单说两个解决方案:
1. **缓存失效时间变短(不推荐,治标不治本)** :我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不适用。
-2. **增加 cache 更新重试机制(常用)**: 如果 cache 服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存入队列中,等缓存服务可用之后,再将 缓存中对应的 key 删除即可。
+2. **增加 cache 更新重试机制(常用)**: 如果 cache 服务当前不可用导致缓存删除失败的话,我们就隔一段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存入队列中,等缓存服务可用之后,再将缓存中对应的 key 删除即可。
-### 19. 参考
+### 参考
- 《Redis 开发与运维》
- 《Redis 设计与实现》
- Redis 命令总结:http://Redisdoc.com/string/set.html
- 通俗易懂的 Redis 数据结构基础教程:[https://juejin.im/post/5b53ee7e5188251aaa2d2e16](https://juejin.im/post/5b53ee7e5188251aaa2d2e16)
- WHY Redis choose single thread (vs multi threads): [https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153](https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153)
-
-### 20. 公众号
-
-如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
-
-**《Java 面试突击》:** 由本文档衍生的专为面试而生的《Java 面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"Java 面试突击"** 即可免费领取!
-
-**Java 工程师必备学习资源:** 一些 Java 工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
-
-
diff --git a/docs/database/Redis/redis集群以及应用场景.md b/docs/database/Redis/redis集群以及应用场景.md
index dfa0d40e..cd54f067 100644
--- a/docs/database/Redis/redis集群以及应用场景.md
+++ b/docs/database/Redis/redis集群以及应用场景.md
@@ -77,7 +77,7 @@
- 优化参数不一致:内存不一致.
- 避免全量复制
- 选择小主节点(分片)、低峰期间操作.
- - 如果节点运行 id 不匹配(如主节点重启、运行 id 发送变化),此时要执行全量复制,应该配合哨兵和集群解决.
+ - 如果节点运行 id 不匹配(如主节点重启、运行 id 发生变化),此时要执行全量复制,应该配合哨兵和集群解决.
- 主从复制挤压缓冲区不足产生的问题(网络中断,部分复制无法满足),可增大复制缓冲区( rel_backlog_size 参数).
- 复制风暴
@@ -111,7 +111,7 @@
- 转移流程
1. Sentinel 选出一个合适的 Slave 作为新的 Master(slaveof no one 命令)。
2. 向其余 Slave 发出通知,让它们成为新 Master 的 Slave( parallel-syncs 参数)。
- 3. 等待旧 Master 复活,并使之称为新 Master 的 Slave。
+ 3. 等待旧 Master 复活,并使之成为新 Master 的 Slave。
4. 向客户端通知 Master 变化。
- 从 Slave 中选择新 Master 节点的规则(slave 升级成 master 之后)
1. 选择 slave-priority 最高的节点。
@@ -138,7 +138,7 @@
##### 集中式
-> 将集群元数据(节点信息、故障等等)几种存储在某个节点上。
+> 将集群元数据(节点信息、故障等等)集中存储在某个节点上。
- 优势
1. 元数据的更新读取具有很强的时效性,元数据修改立即更新
- 劣势
diff --git a/docs/database/mysql/InnoDB对MVCC的实现.md b/docs/database/mysql/InnoDB对MVCC的实现.md
new file mode 100644
index 00000000..4e929cd8
--- /dev/null
+++ b/docs/database/mysql/InnoDB对MVCC的实现.md
@@ -0,0 +1,237 @@
+
+
+- [一致性非锁定读和锁定读](#一致性非锁定读和锁定读)
+ - [一致性非锁定读](#一致性非锁定读)
+ - [锁定读](#锁定读)
+- [InnoDB 对 MVCC 的实现](#InnoDB对MVCC的实现)
+ - [隐藏字段](#隐藏字段])
+ - [ReadView](#ReadView)
+ - [undo-log](#undo-log)
+ - [数据可见性算法](#数据可见性算法)
+- [RC、RR 隔离级别下 MVCC 的差异](#RC、RR隔离级别下MVCC的差异)
+- [MVCC 解决不可重复读问题](#MVCC解决不可重复读问题)
+ - [在 RC 下 ReadView 生成情况](#在RC下ReadView生成情况)
+ - [在 RR 下 ReadView 生成情况](#在RR下ReadView生成情况)
+- [MVCC+Next-key-Lock 防止幻读](#MVCC➕Next-key-Lock防止幻读)
+
+
+
+## 一致性非锁定读和锁定读
+
+### 一致性非锁定读
+
+对于 [**一致性非锁定读(Consistent Nonlocking Reads)** ](https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html)的实现,通常做法是加一个版本号或者时间戳字段,在更新数据的同时版本号 + 1 或者更新时间戳。查询时,将当前可见的版本号与对应记录的版本号进行比对,如果记录的版本小于可见版本,则表示该记录可见
+
+在 `InnoDB` 存储引擎中,[多版本控制 (multi versioning)](https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html) 就是对非锁定读的实现。如果读取的行正在执行 `DELETE` 或 `UPDATE` 操作,这时读取操作不会去等待行上锁的释放。相反地,`InnoDB` 存储引擎会去读取行的一个快照数据,对于这种读取历史数据的方式,我们叫它快照读 (snapshot read)
+
+在 `Repeatable Read` 和 `Read Committed` 两个隔离级别下,如果是执行普通的 `select` 语句(不包括 `select ... lock in share mode` ,`select ... for update`)则会使用 `一致性非锁定读(MVCC)`。并且在 `Repeatable Read` 下 `MVCC` 实现了可重复读和防止部分幻读
+
+### 锁定读
+
+如果执行的是下列语句,就是 [**锁定读(Locking Reads)**](https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html)
+
+- `select ... lock in share mode`
+- `select ... for update`
+- `insert`、`update`、`delete` 操作
+
+在锁定读下,读取的是数据的最新版本,这种读也被称为 `当前读(current read)`。锁定读会对读取到的记录加锁:
+
+- `select ... lock in share mode`:对记录加 `S` 锁,其它事务也可以加`S`锁,如果加 `x` 锁则会被阻塞
+
+- `select ... for update`、`insert`、`update`、`delete`:对记录加 `X` 锁,且其它事务不能加任何锁
+
+在一致性非锁定读下,即使读取的记录已被其它事务加上 `X` 锁,这时记录也是可以被读取的,即读取的快照数据。上面说了,在 `Repeatable Read` 下 `MVCC` 防止了部分幻读,这边的 “部分” 是指在 `一致性非锁定读` 情况下,只能读取到第一次查询之前所插入的数据(根据 Read View 判断数据可见性,Read View 在第一次查询时生成)。但是!如果是 `当前读` ,每次读取的都是最新数据,这时如果两次查询中间有其它事务插入数据,就会产生幻读。所以, **`InnoDB` 在实现`Repeatable Read` 时,如果执行的是当前读,则会对读取的记录使用 `Next-key Lock` ,来防止其它事务在间隙间插入数据**
+
+## InnoDB 对 MVCC 的实现
+
+`MVCC` 的实现依赖于:**隐藏字段、Read View、undo log**。在内部实现中,`InnoDB` 通过数据行的 `DB_TRX_ID` 和 `Read View` 来判断数据的可见性,如不可见,则通过数据行的 `DB_ROLL_PTR` 找到 `undo log` 中的历史版本。每个事务读到的数据版本可能是不一样的,在同一个事务中,用户只能看到该事务创建 `Read View` 之前已经提交的修改和该事务本身做的修改
+
+### 隐藏字段
+
+在内部,`InnoDB` 存储引擎为每行数据添加了三个 [隐藏字段](https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.html):
+
+- `DB_TRX_ID(6字节)`:表示最后一次插入或更新该行的事务 id。此外,`delete` 操作在内部被视为更新,只不过会在记录头 `Record header` 中的 `deleted_flag` 字段将其标记为已删除
+- `DB_ROLL_PTR(7字节)` 回滚指针,指向该行的 `undo log` 。如果该行未被更新,则为空
+- `DB_ROW_ID(6字节)`:如果没有设置主键且该表没有唯一非空索引时,`InnoDB` 会使用该 id 来生成聚簇索引
+
+### ReadView
+
+```c
+class ReadView {
+ /* ... */
+private:
+ trx_id_t m_low_limit_id; /* 大于这个 ID 的事务均不可见 */
+
+ trx_id_t m_up_limit_id; /* 小于这个 ID 的事务均可见 */
+
+ trx_id_t m_creator_trx_id; /* 创建该 Read View 的事务ID */
+
+ trx_id_t m_low_limit_no; /* 事务 Number, 小于该 Number 的 Undo Logs 均可以被 Purge */
+
+ ids_t m_ids; /* 创建 Read View 时的活跃事务列表 */
+
+ m_closed; /* 标记 Read View 是否 close */
+}
+```
+
+[`Read View`](https://github.com/facebook/mysql-8.0/blob/8.0/storage/innobase/include/read0types.h#L298) 主要是用来做可见性判断,里面保存了 “当前对本事务不可见的其他活跃事务”
+
+主要有以下字段:
+
+- `m_low_limit_id`:目前出现过的最大的事务 ID+1,即下一个将被分配的事务 ID。大于这个 ID 的数据版本均不可见
+- `m_up_limit_id`:活跃事务列表 `m_ids` 中最小的事务 ID,如果 `m_ids` 为空,则 `m_up_limit_id` 为 `m_low_limit_id`。小于这个 ID 的数据版本均可见
+- `m_ids`:`Read View` 创建时其他未提交的活跃事务 ID 列表。创建 `Read View`时,将当前未提交事务 ID 记录下来,后续即使它们修改了记录行的值,对于当前事务也是不可见的。`m_ids` 不包括当前事务自己和已提交的事务(正在内存中)
+- `m_creator_trx_id`:创建该 `Read View` 的事务 ID
+
+**事务可见性示意图**([图源](https://leviathan.vip/2019/03/20/InnoDB%E7%9A%84%E4%BA%8B%E5%8A%A1%E5%88%86%E6%9E%90-MVCC/#MVCC-1)):
+
+
+
+### undo-log
+
+`undo log` 主要有两个作用:
+
+- 当事务回滚时用于将数据恢复到修改前的样子
+- 另一个作用是 `MVCC` ,当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过 `undo log` 读取之前的版本数据,以此实现非锁定读
+
+**在 `InnoDB` 存储引擎中 `undo log` 分为两种: `insert undo log` 和 `update undo log`:**
+
+1. **`insert undo log`** :指在 `insert` 操作中产生的 `undo log`。因为 `insert` 操作的记录只对事务本身可见,对其他事务不可见,故该 `undo log` 可以在事务提交后直接删除。不需要进行 `purge` 操作
+
+**`insert` 时的数据初始状态:**
+
+
+
+2. **`update undo log`** :`update` 或 `delete` 操作中产生的 `undo log`。该 `undo log`可能需要提供 `MVCC` 机制,因此不能在事务提交时就进行删除。提交时放入 `undo log` 链表,等待 `purge线程` 进行最后的删除
+
+**数据第一次被修改时:**
+
+
+
+**数据第二次被修改时:**
+
+
+
+不同事务或者相同事务的对同一记录行的修改,会使该记录行的 `undo log` 成为一条链表,链首就是最新的记录,链尾就是最早的旧记录。
+
+### 数据可见性算法
+
+在 `InnoDB` 存储引擎中,创建一个新事务后,执行每个 `select` 语句前,都会创建一个快照(Read View),**快照中保存了当前数据库系统中正处于活跃(没有 commit)的事务的 ID 号**。其实简单的说保存的是系统中当前不应该被本事务看到的其他事务 ID 列表(即 m_ids)。当用户在这个事务中要读取某个记录行的时候,`InnoDB` 会将该记录行的 `DB_TRX_ID` 与 `Read View` 中的一些变量及当前事务 ID 进行比较,判断是否满足可见性条件
+
+[具体的比较算法](https://github.com/facebook/mysql-8.0/blob/8.0/storage/innobase/include/read0types.h#L161)如下:[图源](https://leviathan.vip/2019/03/20/InnoDB%E7%9A%84%E4%BA%8B%E5%8A%A1%E5%88%86%E6%9E%90-MVCC/#MVCC-1)
+
+
+
+1. 如果记录 DB_TRX_ID < m_up_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之前就提交了,所以该记录行的值对当前事务是可见的
+
+2. 如果 DB_TRX_ID >= m_low_limit_id,那么表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照之后才修改该行,所以该记录行的值对当前事务不可见。跳到步骤 5
+
+3. m_ids 为空,则表明在当前事务创建快照之前,修改该行的事务就已经提交了,所以该记录行的值对当前事务是可见的
+
+4. 如果 m_low_limit_id <= DB_TRX_ID < m_up_limit_id,表明最新修改该行的事务(DB_TRX_ID)在当前事务创建快照的时候可能处于“活动状态”或者“已提交状态”;所以就要对活跃事务列表 m_ids 进行查找(源码中是用的二分查找,因为是有序的)
+
+ - 如果在活跃事务列表 m_ids 中能找到 DB_TRX_ID,表明:① 在当前事务创建快照前,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了,但没有提交;或者 ② 在当前事务创建快照后,该记录行的值被事务 ID 为 DB_TRX_ID 的事务修改了。这些情况下,这个记录行的值对当前事务都是不可见的。跳到步骤 5
+
+ - 在活跃事务列表中找不到,则表明“id 为 trx_id 的事务”在修改“该记录行的值”后,在“当前事务”创建快照前就已经提交了,所以记录行对当前事务可见
+
+5. 在该记录行的 DB_ROLL_PTR 指针所指向的 `undo log` 取出快照记录,用快照记录的 DB_TRX_ID 跳到步骤 1 重新开始判断,直到找到满足的快照版本或返回空
+
+## RC 和 RR 隔离级别下 MVCC 的差异
+
+在事务隔离级别 `RC` 和 `RR` (InnoDB 存储引擎的默认事务隔离级别)下,`InnoDB` 存储引擎使用 `MVCC`(非锁定一致性读),但它们生成 `Read View` 的时机却不同
+
+- 在 RC 隔离级别下的 **`每次select`** 查询前都生成一个`Read View` (m_ids 列表)
+- 在 RR 隔离级别下只在事务开始后 **`第一次select`** 数据前生成一个`Read View`(m_ids 列表)
+
+## MVCC 解决不可重复读问题
+
+虽然 RC 和 RR 都通过 `MVCC` 来读取快照数据,但由于 **生成 Read View 时机不同**,从而在 RR 级别下实现可重复读
+
+举个例子:
+
+
+
+### 在 RC 下 ReadView 生成情况
+
+1. **`假设时间线来到 T4 ,那么此时数据行 id = 1 的版本链为`:**
+
+ 
+
+由于 RC 级别下每次查询都会生成`Read View` ,并且事务 101、102 并未提交,此时 `103` 事务生成的 `Read View` 中活跃的事务 **`m_ids` 为:[101,102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:101,`m_creator_trx_id` 为:103
+
+- 此时最新记录的 `DB_TRX_ID` 为 101,m_up_limit_id <= 101 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见
+- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是 101,不可见
+- 继续找上一条 `DB_TRX_ID`为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 `name = 菜花`
+
+2. **`时间线来到 T6 ,数据的版本链为`:**
+
+ 
+
+因为在 RC 级别下,重新生成 `Read View`,这时事务 101 已经提交,102 并未提交,所以此时 `Read View` 中活跃的事务 **`m_ids`:[102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:102,`m_creator_trx_id`为:103
+
+- 此时最新记录的 `DB_TRX_ID` 为 102,m_up_limit_id <= 102 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见
+
+- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 为 101,满足 101 < m_up_limit_id,记录可见,所以在 `T6` 时间点查询到数据为 `name = 李四`,与时间 T4 查询到的结果不一致,不可重复读!
+
+3. **`时间线来到 T9 ,数据的版本链为`:**
+
+
+
+重新生成 `Read View`, 这时事务 101 和 102 都已经提交,所以 **m_ids** 为空,则 m_up_limit_id = m_low_limit_id = 104,最新版本事务 ID 为 102,满足 102 < m_low_limit_id,可见,查询结果为 `name = 赵六`
+
+> **总结:** **在 RC 隔离级别下,事务在每次查询开始时都会生成并设置新的 Read View,所以导致不可重复读**
+
+### 在 RR 下 ReadView 生成情况
+
+**在可重复读级别下,只会在事务开始后第一次读取数据时生成一个 Read View(m_ids 列表)**
+
+1. **`在 T4 情况下的版本链为`:**
+
+
+
+在当前执行 `select` 语句时生成一个 `Read View`,此时 **`m_ids`:[101,102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:101,`m_creator_trx_id` 为:103
+
+此时和 RC 级别下一样:
+
+- 最新记录的 `DB_TRX_ID` 为 101,m_up_limit_id <= 101 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见
+- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是 101,不可见
+- 继续找上一条 `DB_TRX_ID`为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 `name = 菜花`
+
+2. **`时间点 T6 情况下`:**
+
+ 
+
+ 在 RR 级别下只会生成一次`Read View`,所以此时依然沿用 **`m_ids` :[101,102]** ,`m_low_limit_id`为:104,`m_up_limit_id`为:101,`m_creator_trx_id` 为:103
+
+- 最新记录的 `DB_TRX_ID` 为 102,m_up_limit_id <= 102 < m_low_limit_id,所以要在 `m_ids` 列表中查找,发现 `DB_TRX_ID` 存在列表中,那么这个记录不可见
+
+- 根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 为 101,不可见
+
+- 继续根据 `DB_ROLL_PTR` 找到 `undo log` 中的上一版本记录,上一条记录的 `DB_TRX_ID` 还是 101,不可见
+
+- 继续找上一条 `DB_TRX_ID`为 1,满足 1 < m_up_limit_id,可见,所以事务 103 查询到数据为 `name = 菜花`
+
+3. **时间点 T9 情况下:**
+
+
+
+此时情况跟 T6 完全一样,由于已经生成了 `Read View`,此时依然沿用 **`m_ids` :[101,102]** ,所以查询结果依然是 `name = 菜花`
+
+## MVCC➕Next-key-Lock 防止幻读
+
+`InnoDB`存储引擎在 RR 级别下通过 `MVCC`和 `Next-key Lock` 来解决幻读问题:
+
+**1、执行普通 `select`,此时会以 `MVCC` 快照读的方式读取数据**
+
+在快照读的情况下,RR 隔离级别只会在事务开启后的第一次查询生成 `Read View` ,并使用至事务提交。所以在生成 `Read View` 之后其它事务所做的更新、插入记录版本对当前事务并不可见,实现了可重复读和防止快照读下的 “幻读”
+
+**2、执行 select...for update/lock in share mode、insert、update、delete 等当前读**
+
+在当前读下,读取的都是最新的数据,如果其它事务有插入新的记录,并且刚好在当前事务查询范围内,就会产生幻读!`InnoDB` 使用 [Next-key Lock](https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html#innodb-next-key-locks) 来防止这种情况。当执行当前读时,会锁定读取到的记录的同时,锁定它们的间隙,防止其它事务在查询范围内插入数据。只要我不让你插入,就不会发生幻读
+
+## 参考
+
+- **《MySQL 技术内幕 InnoDB 存储引擎第 2 版》**
+- [Innodb 中的事务隔离级别和锁的关系](https://tech.meituan.com/2014/08/20/innodb-lock.html)
+- [MySQL 事务与 MVCC 如何实现的隔离级别](https://blog.csdn.net/qq_35190492/article/details/109044141)
+- [InnoDB 事务分析-MVCC](https://leviathan.vip/2019/03/20/InnoDB%E7%9A%84%E4%BA%8B%E5%8A%A1%E5%88%86%E6%9E%90-MVCC/)
diff --git a/docs/database/mysql/MySQL三大日志.md b/docs/database/mysql/MySQL三大日志.md
new file mode 100644
index 00000000..ee621ba0
--- /dev/null
+++ b/docs/database/mysql/MySQL三大日志.md
@@ -0,0 +1,280 @@
+> 本文来自公号程序猿阿星投稿,JavaGuide 对其做了补充完善。
+
+## 前言
+
+`MySQL` 日志 主要包括错误日志、查询日志、慢查询日志、事务日志、二进制日志几大类。其中,比较重要的还要属二进制日志 `binlog`(归档日志)和事务日志 `redo log`(重做日志)和 `undo log`(回滚日志)。
+
+
+
+今天就来聊聊 `redo log`(重做日志)、`binlog`(归档日志)、两阶段提交、`undo log` (回滚日志)。
+
+## redo log
+
+`redo log`(重做日志)是`InnoDB`存储引擎独有的,它让`MySQL`拥有了崩溃恢复能力。
+
+比如 `MySQL` 实例挂了或宕机了,重启时,`InnoDB`存储引擎会使用`redo log`恢复数据,保证数据的持久性与完整性。
+
+
+
+`MySQL` 中数据是以页为单位,你查询一条记录,会从硬盘把一页的数据加载出来,加载出来的数据叫数据页,会放入到 `Buffer Pool` 中。
+
+后续的查询都是先从 `Buffer Pool` 中找,没有命中再去硬盘加载,减少硬盘 `IO` 开销,提升性能。
+
+更新表数据的时候,也是如此,发现 `Buffer Pool` 里存在要更新的数据,就直接在 `Buffer Pool` 里更新。
+
+然后会把“在某个数据页上做了什么修改”记录到重做日志缓存(`redo log buffer`)里,接着刷盘到 `redo log` 文件里。
+
+
+
+理想情况,事务一提交就会进行刷盘操作,但实际上,刷盘的时机是根据策略来进行的。
+
+> 小贴士:每条 redo 记录由“表空间号+数据页号+偏移量+修改数据长度+具体修改的数据”组成
+
+### 刷盘时机
+
+`InnoDB` 存储引擎为 `redo log` 的刷盘策略提供了 `innodb_flush_log_at_trx_commit` 参数,它支持三种策略:
+
+- **0** :设置为 0 的时候,表示每次事务提交时不进行刷盘操作
+- **1** :设置为 1 的时候,表示每次事务提交时都将进行刷盘操作(默认值)
+- **2** :设置为 2 的时候,表示每次事务提交时都只把 redo log buffer 内容写入 page cache
+
+`innodb_flush_log_at_trx_commit` 参数默认为 1 ,也就是说当事务提交时会调用 `fsync` 对 redo log 进行刷盘
+
+另外,`InnoDB` 存储引擎有一个后台线程,每隔`1` 秒,就会把 `redo log buffer` 中的内容写到文件系统缓存(`page cache`),然后调用 `fsync` 刷盘。
+
+
+
+也就是说,一个没有提交事务的 `redo log` 记录,也可能会刷盘。
+
+**为什么呢?**
+
+因为在事务执行过程 `redo log` 记录是会写入`redo log buffer` 中,这些 `redo log` 记录会被后台线程刷盘。
+
+
+
+除了后台线程每秒`1`次的轮询操作,还有一种情况,当 `redo log buffer` 占用的空间即将达到 `innodb_log_buffer_size` 一半的时候,后台线程会主动刷盘。
+
+下面是不同刷盘策略的流程图。
+
+#### innodb_flush_log_at_trx_commit=0
+
+
+
+为`0`时,如果`MySQL`挂了或宕机可能会有`1`秒数据的丢失。
+
+#### innodb_flush_log_at_trx_commit=1
+
+
+
+为`1`时, 只要事务提交成功,`redo log`记录就一定在硬盘里,不会有任何数据丢失。
+
+如果事务执行期间`MySQL`挂了或宕机,这部分日志丢了,但是事务并没有提交,所以日志丢了也不会有损失。
+
+#### innodb_flush_log_at_trx_commit=2
+
+
+
+为`2`时, 只要事务提交成功,`redo log buffer`中的内容只写入文件系统缓存(`page cache`)。
+
+如果仅仅只是`MySQL`挂了不会有任何数据丢失,但是宕机可能会有`1`秒数据的丢失。
+
+### 日志文件组
+
+硬盘上存储的 `redo log` 日志文件不只一个,而是以一个**日志文件组**的形式出现的,每个的`redo`日志文件大小都是一样的。
+
+比如可以配置为一组`4`个文件,每个文件的大小是 `1GB`,整个 `redo log` 日志文件组可以记录`4G`的内容。
+
+它采用的是环形数组形式,从头开始写,写到末尾又回到头循环写,如下图所示。
+
+
+
+在个**日志文件组**中还有两个重要的属性,分别是 `write pos、checkpoint`
+
+- **write pos** 是当前记录的位置,一边写一边后移
+- **checkpoint** 是当前要擦除的位置,也是往后推移
+
+每次刷盘 `redo log` 记录到**日志文件组**中,`write pos` 位置就会后移更新。
+
+每次 `MySQL` 加载**日志文件组**恢复数据时,会清空加载过的 `redo log` 记录,并把 `checkpoint` 后移更新。
+
+`write pos` 和 `checkpoint` 之间的还空着的部分可以用来写入新的 `redo log` 记录。
+
+
+
+如果 `write pos` 追上 `checkpoint` ,表示**日志文件组**满了,这时候不能再写入新的 `redo log` 记录,`MySQL` 得停下来,清空一些记录,把 `checkpoint` 推进一下。
+
+
+
+### redo log 小结
+
+相信大家都知道 `redo log` 的作用和它的刷盘时机、存储形式。
+
+现在我们来思考一个问题: **只要每次把修改后的数据页直接刷盘不就好了,还有 `redo log` 什么事?**
+
+它们不都是刷盘么?差别在哪里?
+
+```java
+1 Byte = 8bit
+1 KB = 1024 Byte
+1 MB = 1024 KB
+1 GB = 1024 MB
+1 TB = 1024 GB
+```
+
+实际上,数据页大小是`16KB`,刷盘比较耗时,可能就修改了数据页里的几 `Byte` 数据,有必要把完整的数据页刷盘吗?
+
+而且数据页刷盘是随机写,因为一个数据页对应的位置可能在硬盘文件的随机位置,所以性能是很差。
+
+如果是写 `redo log`,一行记录可能就占几十 `Byte`,只包含表空间号、数据页号、磁盘文件偏移
+量、更新值,再加上是顺序写,所以刷盘速度很快。
+
+所以用 `redo log` 形式记录修改内容,性能会远远超过刷数据页的方式,这也让数据库的并发能力更强。
+
+> 其实内存的数据页在一定时机也会刷盘,我们把这称为页合并,讲 `Buffer Pool`的时候会对这块细说
+
+## binlog
+
+`redo log` 它是物理日志,记录内容是“在某个数据页上做了什么修改”,属于 `InnoDB` 存储引擎。
+
+而 `binlog` 是逻辑日志,记录内容是语句的原始逻辑,类似于“给 ID=2 这一行的 c 字段加 1”,属于`MySQL Server` 层。
+
+不管用什么存储引擎,只要发生了表数据更新,都会产生 `binlog` 日志。
+
+那 `binlog` 到底是用来干嘛的?
+
+可以说`MySQL`数据库的**数据备份、主备、主主、主从**都离不开`binlog`,需要依靠`binlog`来同步数据,保证数据一致性。
+
+
+
+`binlog`会记录所有涉及更新数据的逻辑操作,并且是顺序写。
+
+### 记录格式
+
+`binlog` 日志有三种格式,可以通过`binlog_format`参数指定。
+
+- **statement**
+- **row**
+- **mixed**
+
+指定`statement`,记录的内容是`SQL`语句原文,比如执行一条`update T set update_time=now() where id=1`,记录的内容如下。
+
+
+
+同步数据时,会执行记录的`SQL`语句,但是有个问题,`update_time=now()`这里会获取当前系统时间,直接执行会导致与原库的数据不一致。
+
+为了解决这种问题,我们需要指定为`row`,记录的内容不再是简单的`SQL`语句了,还包含操作的具体数据,记录内容如下。
+
+
+
+`row`格式记录的内容看不到详细信息,要通过`mysqlbinlog`工具解析出来。
+
+`update_time=now()`变成了具体的时间`update_time=1627112756247`,条件后面的@1、@2、@3 都是该行数据第 1 个~3 个字段的原始值(**假设这张表只有 3 个字段**)。
+
+这样就能保证同步数据的一致性,通常情况下都是指定为`row`,这样可以为数据库的恢复与同步带来更好的可靠性。
+
+但是这种格式,需要更大的容量来记录,比较占用空间,恢复与同步时会更消耗`IO`资源,影响执行速度。
+
+所以就有了一种折中的方案,指定为`mixed`,记录的内容是前两者的混合。
+
+`MySQL`会判断这条`SQL`语句是否可能引起数据不一致,如果是,就用`row`格式,否则就用`statement`格式。
+
+### 写入机制
+
+`binlog`的写入时机也非常简单,事务执行过程中,先把日志写到`binlog cache`,事务提交的时候,再把`binlog cache`写到`binlog`文件中。
+
+因为一个事务的`binlog`不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为`binlog cache`。
+
+我们可以通过`binlog_cache_size`参数控制单个线程 binlog cache 大小,如果存储内容超过了这个参数,就要暂存到磁盘(`Swap`)。
+
+`binlog`日志刷盘流程如下
+
+
+
+- **上图的 write,是指把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,所以速度比较快**
+- **上图的 fsync,才是将数据持久化到磁盘的操作**
+
+`write`和`fsync`的时机,可以由参数`sync_binlog`控制,默认是`0`。
+
+为`0`的时候,表示每次提交事务都只`write`,由系统自行判断什么时候执行`fsync`。
+
+
+
+虽然性能得到提升,但是机器宕机,`page cache`里面的 binglog 会丢失。
+
+为了安全起见,可以设置为`1`,表示每次提交事务都会执行`fsync`,就如同**binlog 日志刷盘流程**一样。
+
+最后还有一种折中方式,可以设置为`N(N>1)`,表示每次提交事务都`write`,但累积`N`个事务后才`fsync`。
+
+
+
+在出现`IO`瓶颈的场景里,将`sync_binlog`设置成一个比较大的值,可以提升性能。
+
+同样的,如果机器宕机,会丢失最近`N`个事务的`binlog`日志。
+
+## 两阶段提交
+
+`redo log`(重做日志)让`InnoDB`存储引擎拥有了崩溃恢复能力。
+
+`binlog`(归档日志)保证了`MySQL`集群架构的数据一致性。
+
+虽然它们都属于持久化的保证,但是侧重点不同。
+
+在执行更新语句过程,会记录`redo log`与`binlog`两块日志,以基本的事务为单位,`redo log`在事务执行过程中可以不断写入,而`binlog`只有在提交事务时才写入,所以`redo log`与`binlog`的写入时机不一样。
+
+
+
+回到正题,`redo log`与`binlog`两份日志之间的逻辑不一致,会出现什么问题?
+
+我们以`update`语句为例,假设`id=2`的记录,字段`c`值是`0`,把字段`c`值更新成`1`,`SQL`语句为`update T set c=1 where id=2`。
+
+假设执行过程中写完`redo log`日志后,`binlog`日志写期间发生了异常,会出现什么情况呢?
+
+
+
+由于`binlog`没写完就异常,这时候`binlog`里面没有对应的修改记录。因此,之后用`binlog`日志恢复数据时,就会少这一次更新,恢复出来的这一行`c`值是`0`,而原库因为`redo log`日志恢复,这一行`c`值是`1`,最终数据不一致。
+
+
+
+为了解决两份日志之间的逻辑一致问题,`InnoDB`存储引擎使用**两阶段提交**方案。
+
+原理很简单,将`redo log`的写入拆成了两个步骤`prepare`和`commit`,这就是**两阶段提交**。
+
+
+
+使用**两阶段提交**后,写入`binlog`时发生异常也不会有影响,因为`MySQL`根据`redo log`日志恢复数据时,发现`redo log`还处于`prepare`阶段,并且没有对应`binlog`日志,就会回滚该事务。
+
+
+
+再看一个场景,`redo log`设置`commit`阶段发生异常,那会不会回滚事务呢?
+
+
+
+并不会回滚事务,它会执行上图框住的逻辑,虽然`redo log`是处于`prepare`阶段,但是能通过事务`id`找到对应的`binlog`日志,所以`MySQL`认为是完整的,就会提交事务恢复数据。
+
+## undo log
+
+> 这部分内容为 JavaGuide 的补充:
+
+我们知道如果想要保证事务的原子性,就需要在异常发生时,对已经执行的操作进行**回滚**,在 MySQL 中,恢复机制是通过 **回滚日志(undo log)** 实现的,所有事务进行的修改都会先记录到这个回滚日志中,然后再执行相关的操作。如果执行过程中遇到异常的话,我们直接利用 **回滚日志** 中的信息将数据回滚到修改之前的样子即可!并且,回滚日志会先于数据持久化到磁盘上。这样就保证了即使遇到数据库突然宕机等情况,当用户再次启动数据库的时候,数据库还能够通过查询回滚日志来回滚将之前未完成的事务。
+
+另外,`MVCC` 的实现依赖于:**隐藏字段、Read View、undo log**。在内部实现中,`InnoDB` 通过数据行的 `DB_TRX_ID` 和 `Read View` 来判断数据的可见性,如不可见,则通过数据行的 `DB_ROLL_PTR` 找到 `undo log` 中的历史版本。每个事务读到的数据版本可能是不一样的,在同一个事务中,用户只能看到该事务创建 `Read View` 之前已经提交的修改和该事务本身做的修改
+
+## 总结
+
+> 这部分内容为 JavaGuide 的补充:
+
+MySQL InnoDB 引擎使用 **redo log(重做日志)** 保证事务的**持久性**,使用 **undo log(回滚日志)** 来保证事务的**原子性**。
+
+`MySQL`数据库的**数据备份、主备、主主、主从**都离不开`binlog`,需要依靠`binlog`来同步数据,保证数据一致性。
+
+## 站在巨人的肩膀上
+
+- 《MySQL 实战 45 讲》
+- 《从零开始带你成为 MySQL 实战优化高手》
+- 《MySQL 是怎样运行的:从根儿上理解 MySQL》
+- 《MySQL 技术 Innodb 存储引擎》
+
+## MySQL 好文推荐
+
+- [CURD 这么多年,你有了解过 MySQL 的架构设计吗?](https://mp.weixin.qq.com/s/R-1km7r0z3oWfwYQV8iiqA)
+- [浅谈 MySQL InnoDB 的内存组件](https://mp.weixin.qq.com/s/7Kab4IQsNcU_bZdbv_MuOg)
diff --git a/docs/database/MySQL.md b/docs/database/mysql/MySQL总结.md
similarity index 91%
rename from docs/database/MySQL.md
rename to docs/database/mysql/MySQL总结.md
index afac2396..37793525 100644
--- a/docs/database/MySQL.md
+++ b/docs/database/mysql/MySQL总结.md
@@ -4,7 +4,7 @@
顾名思义,关系型数据库就是一种建立在关系模型的基础上的数据库。关系模型表明了数据库中所存储的数据之间的联系(一对一、一对多、多对多)。
-关系型数据库中,我们的数据都被存放在了各种表中(比如用户表),表中的每一列就存放着一条数据(比如一个用户的信息)。
+关系型数据库中,我们的数据都被存放在了各种表中(比如用户表),表中的每一行就存放着一条数据(比如一个用户的信息)。

@@ -34,7 +34,7 @@ mysql> show engines;

-从上图我们可以查看出 MySQL 当前默认的存储引擎是 InnoDB,并且在 5.7 版本所有的存储引擎中只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB 支持事务。
+从上图我们可以查看出 MySQL 当前默认的存储引擎是 InnoDB,并且在 5.7 版本所有的存储引擎中只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB 支持事务。
**查看 MySQL 当前默认的存储引擎**
@@ -132,7 +132,7 @@ MVCC 可以看作是行级锁的一个升级,可以有效减少加锁操作,
- Record lock:记录锁,单个行记录上的锁
- Gap lock:间隙锁,锁定一个范围,不包括记录本身
-- Next-key lock:record+gap临键锁,锁定一个范围,包含记录本身
+- Next-key lock:record+gap 临键锁,锁定一个范围,包含记录本身
## 查询缓存
@@ -152,9 +152,9 @@ set global query_cache_type=1;
set global query_cache_size=600000;
```
-如上,**开启查询缓存后在同样的查询条件以及数据情况下,会直接在缓存中返回结果**。这里的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息。因此任何两个查询在任何字符上的不同都会导致缓存不命中。此外,如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL 库中的系统表,其查询结果也不会被缓存。
+如上,**开启查询缓存后在同样的查询条件以及数据情况下,会直接在缓存中返回结果**。这里的查询条件包括查询本身、当前要查询的数据库、客户端协议版本号等一些可能影响结果的信息。(**查询缓存不命中的情况:(1)**)因此任何两个查询在任何字符上的不同都会导致缓存不命中。此外,(**查询缓存不命中的情况:(2)**)如果查询中包含任何用户自定义函数、存储函数、用户变量、临时表、MySQL 库中的系统表,其查询结果也不会被缓存。
-缓存建立之后,MySQL 的查询缓存系统会跟踪查询中涉及的每张表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。
+(**查询缓存不命中的情况:(3)**)**缓存建立之后**,MySQL 的查询缓存系统会跟踪查询中涉及的每张表,如果这些表(数据或结构)发生变化,那么和这张表相关的所有缓存数据都将失效。
**缓存虽然能够提升数据库的查询性能,但是缓存同时也带来了额外的开销,每次查询后都要做一次缓存操作,失效后还要销毁。** 因此,开启查询缓存要谨慎,尤其对于写密集的应用来说更是如此。如果开启,要注意合理控制缓存空间大小,一般来说其大小设置为几十 MB 比较合适。此外,**还可以通过 sql_cache 和 sql_no_cache 来控制某个查询语句是否需要缓存:**
@@ -209,7 +209,7 @@ COMMIT;
1. **原子性**(`Atomicity`) : 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
2. **一致性**(`Consistency`): 执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;
3. **隔离性**(`Isolation`): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
-4. **持久性**(`Durabilily`): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
+4. **持久性**(`Durability`): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
**数据事务的实现原理呢?**
@@ -227,7 +227,7 @@ MySQL InnoDB 引擎通过 **锁机制**、**MVCC** 等手段来保证事务的
- **脏读(Dirty read):** 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
- **丢失修改(Lost to modify):** 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务 1 读取某表中的数据 A=20,事务 2 也读取 A=20,事务 1 修改 A=A-1,事务 2 也修改 A=A-1,最终结果 A=19,事务 1 的修改被丢失。
-- **不可重复读(Unrepeatableread):** 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
+- **不可重复读(Unrepeatable read):** 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
- **幻读(Phantom read):** 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
**不可重复读和幻读区别:**
@@ -269,7 +269,7 @@ mysql> SELECT @@tx_isolation;
🐛 问题更正:**MySQL InnoDB 的 REPEATABLE-READ(可重读)并不保证避免幻读,需要应用使用加锁读来保证。而这个加锁度使用到的机制就是 Next-Key Locks。**
-因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 **READ-COMMITTED(读取提交内容)** ,但是你要知道的是 InnoDB 存储引擎默认使用 **REPEAaTABLE-READ(可重读)** 并不会有任何性能损失。
+因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 **READ-COMMITTED(读取提交内容)** ,但是你要知道的是 InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)** 并不会有任何性能损失。
InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到 **SERIALIZABLE(可串行化)** 隔离级别。
@@ -280,5 +280,4 @@ InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到 **SERIALI
## 参考
- 《高性能 MySQL》
-
- https://www.omnisci.com/technical-glossary/relational-database
diff --git a/docs/database/MySQL数据库索引.md b/docs/database/mysql/MySQL数据库索引.md
similarity index 99%
rename from docs/database/MySQL数据库索引.md
rename to docs/database/mysql/MySQL数据库索引.md
index 31ed9a66..d797144e 100644
--- a/docs/database/MySQL数据库索引.md
+++ b/docs/database/mysql/MySQL数据库索引.md
@@ -8,7 +8,7 @@
**优点** :
-- 使用索引可以大大加快 数据的检索速度(大大减少的检索的数据量), 这也是创建索引的最主要的原因。
+- 使用索引可以大大加快 数据的检索速度(大大减少检索的数据量), 这也是创建索引的最主要的原因。
- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
**缺点** :
diff --git a/docs/database/MySQL高性能优化规范建议.md b/docs/database/mysql/MySQL高性能优化规范建议.md
similarity index 98%
rename from docs/database/MySQL高性能优化规范建议.md
rename to docs/database/mysql/MySQL高性能优化规范建议.md
index 60f17583..240a1dd9 100644
--- a/docs/database/MySQL高性能优化规范建议.md
+++ b/docs/database/mysql/MySQL高性能优化规范建议.md
@@ -168,7 +168,7 @@ MySQL 内存临时表不支持 TEXT、BLOB 这样的大数据类型,如果查
**2、TEXT 或 BLOB 类型只能使用前缀索引**
-因为[MySQL](http://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247487885&idx=1&sn=65b1bf5f7d4505502620179669a9c2df&chksm=ebd62ea1dca1a7b7bf884bcd9d538d78ba064ee03c09436ca8e57873b1d98a55afd6d7884cfc&scene=21#wechat_redirect) 对索引字段长度是有限制的,所以 TEXT 类型只能使用前缀索引,并且 TEXT 列上是不能有默认值的
+因为[MySQL](https://mp.weixin.qq.com/s?__biz=MzI4Njc5NjM1NQ==&mid=2247487885&idx=1&sn=65b1bf5f7d4505502620179669a9c2df&chksm=ebd62ea1dca1a7b7bf884bcd9d538d78ba064ee03c09436ca8e57873b1d98a55afd6d7884cfc&scene=21#wechat_redirect) 对索引字段长度是有限制的,所以 TEXT 类型只能使用前缀索引,并且 TEXT 列上是不能有默认值的
### 3. 避免使用 ENUM 类型
@@ -266,7 +266,7 @@ Innodb 是按照主键索引的顺序来组织表的
### 7. 对于频繁的查询优先考虑使用覆盖索引
-> 覆盖索引:就是包含了所有查询字段 (where,select,ordery by,group by 包含的字段) 的索引
+> 覆盖索引:就是包含了所有查询字段 (where,select,order by,group by 包含的字段) 的索引
**覆盖索引的好处:**
diff --git a/docs/database/一千行MySQL命令.md b/docs/database/mysql/一千行MySQL学习笔记.md
similarity index 99%
rename from docs/database/一千行MySQL命令.md
rename to docs/database/mysql/一千行MySQL学习笔记.md
index 385aa37d..bf8b5e58 100644
--- a/docs/database/一千行MySQL命令.md
+++ b/docs/database/mysql/一千行MySQL学习笔记.md
@@ -609,7 +609,7 @@ CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] VIEW view_name
- 事务开始和结束时,外部数据一致
- 在整个事务过程中,操作是连续的
3. 隔离性(Isolation)
- 多个用户并发访问数据库时,一个用户的事务不能被其它用户的事物所干扰,多个并发事务之间的数据要相互隔离。
+ 多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间的数据要相互隔离。
4. 持久性(Durability)
一个事务一旦被提交,它对数据库中的数据改变就是永久性的。
-- 事务的实现
diff --git a/docs/database/一条sql语句在mysql中如何执行的.md b/docs/database/mysql/一条sql语句在mysql中如何执行的.md
similarity index 83%
rename from docs/database/一条sql语句在mysql中如何执行的.md
rename to docs/database/mysql/一条sql语句在mysql中如何执行的.md
index 261a0c0b..9798df73 100644
--- a/docs/database/一条sql语句在mysql中如何执行的.md
+++ b/docs/database/mysql/一条sql语句在mysql中如何执行的.md
@@ -20,7 +20,7 @@
本篇文章会分析下一个 sql 语句在 MySQL 中的执行流程,包括 sql 的查询在 MySQL 内部会怎么流转,sql 语句的更新是怎么完成的。
-在分析之前我会先带着你看看 MySQL 的基础架构,知道了 MySQL 由那些组件组成已经这些组件的作用是什么,可以帮助我们理解和解决这些问题。
+在分析之前我会先带着你看看 MySQL 的基础架构,知道了 MySQL 由那些组件组成以及这些组件的作用是什么,可以帮助我们理解和解决这些问题。
## 一 MySQL 基础架构分析
@@ -30,11 +30,11 @@
先简单介绍一下下图涉及的一些组件的基本作用帮助大家理解这幅图,在 1.2 节中会详细介绍到这些组件的作用。
-- **连接器:** 身份认证和权限相关(登录 MySQL 的时候)。
-- **查询缓存:** 执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。
-- **分析器:** 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
-- **优化器:** 按照 MySQL 认为最优的方案去执行。
-- **执行器:** 执行语句,然后从存储引擎返回数据。
+- **连接器:**身份认证和权限相关(登录 MySQL 的时候)。
+- **查询缓存:**执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。
+- **分析器:** 没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
+- **优化器:**按照 MySQL 认为最优的方案去执行。
+- **执行器:**执行语句,然后从存储引擎返回数据。

@@ -96,7 +96,7 @@ select * from tb_student A where A.age='18' and A.name=' 张三 ';
结合上面的说明,我们分析下这个语句的执行流程:
* 先检查该语句是否有权限,如果没有权限,直接返回错误信息,如果有权限,在 MySQL8.0 版本以前,会先查询缓存,以这条 sql 语句为 key 在内存中查询是否有结果,如果有直接缓存,如果没有,执行下一步。
-* 通过分析器进行词法分析,提取 sql 语句的关键元素,比如提取上面这个语句是查询 select,提取需要查询的表名为 tb_student,需要查询所有的列,查询条件是这个表的 id='1'。然后判断这个 sql 语句是否有语法错误,比如关键词是否正确等等,如果检查没问题就执行下一步。
+* 通过分析器进行词法分析,提取 sql 语句的关键元素,比如提取上面这个语句是查询 select,提取需要查询的表名为 tb_student,需要查询所有的列,查询条件是这个表的 id='1'。然后判断这个 sql 语句是否有语法错误,比如关键词是否正确等等,如果检查没问题就执行下一步。
* 接下来就是优化器进行确定执行方案,上面的 sql 语句,可以有两种执行方案:
a.先查询学生表中姓名为“张三”的学生,然后判断是否年龄是 18。
@@ -112,7 +112,7 @@ select * from tb_student A where A.age='18' and A.name=' 张三 ';
```
update tb_student A set A.age='19' where A.name=' 张三 ';
```
-我们来给张三修改下年龄,在实际数据库肯定不会设置年龄这个字段的,不然要被技术负责人打的。其实条语句也基本上会沿着上一个查询的流程走,只不过执行更新的时候肯定要记录日志啦,这就会引入日志模块了,MySQL 自带的日志模块式 **binlog(归档日志)** ,所有的存储引擎都可以使用,我们常用的 InnoDB 引擎还自带了一个日志模块 **redo log(重做日志)**,我们就以 InnoDB 模式下来探讨这个语句的执行流程。流程如下:
+我们来给张三修改下年龄,在实际数据库肯定不会设置年龄这个字段的,不然要被技术负责人打的。其实这条语句也基本上会沿着上一个查询的流程走,只不过执行更新的时候肯定要记录日志啦,这就会引入日志模块了,MySQL 自带的日志模块是 **binlog(归档日志)** ,所有的存储引擎都可以使用,我们常用的 InnoDB 引擎还自带了一个日志模块 **redo log(重做日志)**,我们就以 InnoDB 模式下来探讨这个语句的执行流程。流程如下:
* 先查询到张三这一条数据,如果有缓存,也是会用到缓存。
* 然后拿到查询的语句,把 age 改为 19,然后调用引擎 API 接口,写入这一行数据,InnoDB 引擎把数据保存在内存中,同时记录 redo log,此时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,随时可以提交。
@@ -121,7 +121,7 @@ update tb_student A set A.age='19' where A.name=' 张三 ';
**这里肯定有同学会问,为什么要用两个日志模块,用一个日志模块不行吗?**
-这是因为最开始 MySQL 并没与 InnoDB 引擎( InnoDB 引擎是其他公司以插件形式插入 MySQL 的) ,MySQL 自带的引擎是 MyISAM,但是我们知道 redo log 是 InnoDB 引擎特有的,其他存储引擎都没有,这就导致会没有 crash-safe 的能力(crash-safe 的能力即使数据库发生异常重启,之前提交的记录都不会丢失),binlog 日志只能用来归档。
+这是因为最开始 MySQL 并没有 InnoDB 引擎(InnoDB 引擎是其他公司以插件形式插入 MySQL 的),MySQL 自带的引擎是 MyISAM,但是我们知道 redo log 是 InnoDB 引擎特有的,其他存储引擎都没有,这就导致会没有 crash-safe 的能力(crash-safe 的能力即使数据库发生异常重启,之前提交的记录都不会丢失),binlog 日志只能用来归档。
并不是说只用一个日志模块不可以,只是 InnoDB 引擎就是通过 redo log 来支持事务的。那么,又会有同学问,我用两个日志模块,但是不要这么复杂行不行,为什么 redo log 要引入 prepare 预提交状态?这里我们用反证法来说明下为什么要这么做?
@@ -138,10 +138,10 @@ update tb_student A set A.age='19' where A.name=' 张三 ';
## 三 总结
-* MySQL 主要分为 Server 层和引擎层,Server 层主要包括连接器、查询缓存、分析器、优化器、执行器,同时还有一个日志模块(binlog),这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有。
+* MySQL 主要分为 Server 层和引擎层,Server 层主要包括连接器、查询缓存、分析器、优化器、执行器,同时还有一个日志模块(binlog),这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有。
* 引擎层是插件式的,目前主要包括,MyISAM,InnoDB,Memory 等。
-* 查询语句的执行流程如下:权限校验(如果命中缓存)---》查询缓存---》分析器---》优化器---》权限校验---》执行器---》引擎
-* 更新语句执行流程如下:分析器----》权限校验----》执行器---》引擎---redo log(prepare 状态---》binlog---》redo log(commit状态)
+* 查询语句的执行流程如下:权限校验(如果命中缓存)--->查询缓存--->分析器--->优化器--->权限校验--->执行器--->引擎
+* 更新语句执行流程如下:分析器---->权限校验---->执行器--->引擎---redo log(prepare 状态)--->binlog--->redo log(commit状态)
## 四 参考
diff --git a/docs/database/事务隔离级别(图文详解).md b/docs/database/mysql/事务隔离级别(图文详解).md
similarity index 83%
rename from docs/database/事务隔离级别(图文详解).md
rename to docs/database/mysql/事务隔离级别(图文详解).md
index 95da8be9..19cbd71e 100644
--- a/docs/database/事务隔离级别(图文详解).md
+++ b/docs/database/mysql/事务隔离级别(图文详解).md
@@ -69,7 +69,7 @@
| REPEATABLE-READ | × | × | √ |
| SERIALIZABLE | × | × | × |
-MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;`
+MySQL InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)**。我们可以通过`SELECT @@tx_isolation;`命令来查看,MySQL 8.0 该命令改为`SELECT @@transaction_isolation;`
```sql
mysql> SELECT @@tx_isolation;
@@ -80,11 +80,17 @@ mysql> SELECT @@tx_isolation;
+-----------------+
```
-这里需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 **REPEATABLE-READ(可重读)** 事务隔离级别下,允许应用使用 Next-Key Lock 锁算法来避免幻读的产生。这与其他数据库系统(如 SQL Server)是不同的。所以说虽然 InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)** ,但是可以通过应用加锁读(例如 `select * from table for update` 语句)来保证不会产生幻读,而这个加锁度使用到的机制就是 Next-Key Lock 锁算法。从而达到了 SQL 标准的 **SERIALIZABLE(可串行化)** 隔离级别。
+~~这里需要注意的是:与 SQL 标准不同的地方在于 InnoDB 存储引擎在 **REPEATABLE-READ(可重读)** 事务隔离级别下使用的是 Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说 InnoDB 存储引擎的默认支持的隔离级别是 **REPEATABLE-READ(可重读)** 已经可以完全保证事务的隔离性要求,即达到了 SQL 标准的 **SERIALIZABLE(可串行化)** 隔离级别。~~
-因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是**READ-COMMITTED(读取提交内容):**,但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)** 并不会有任何性能损失。
+🐛 问题更正:**MySQL InnoDB 的 REPEATABLE-READ(可重读)并不保证避免幻读,需要应用使用加锁读来保证。而这个加锁度使用到的机制就是 Next-Key Locks。**
-InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到**SERIALIZABLE(可串行化)** 隔离级别。
+因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是 **READ-COMMITTED(读取提交内容)** ,但是你要知道的是 InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)** 并不会有任何性能损失。
+
+InnoDB 存储引擎在 **分布式事务** 的情况下一般会用到 **SERIALIZABLE(可串行化)** 隔离级别。
+
+🌈 拓展一下(以下内容摘自《MySQL 技术内幕:InnoDB 存储引擎(第 2 版)》7.7 章):
+
+> InnoDB 存储引擎提供了对 XA 事务的支持,并通过 XA 事务来支持分布式事务的实现。分布式事务指的是允许多个独立的事务资源(transactional resources)参与到一个全局的事务中。事务资源通常是关系型数据库系统,但也可以是其他类型的资源。全局事务要求在其中的所有参与的事务要么都提交,要么都回滚,这对于事务原有的 ACID 要求又有了提高。另外,在使用分布式事务时,InnoDB 存储引擎的事务隔离级别必须设置为 SERIALIZABLE。
### 实际情况演示
diff --git a/docs/database/关于数据库存储时间的一点思考.md b/docs/database/mysql/关于数据库存储时间的一点思考.md
similarity index 92%
rename from docs/database/关于数据库存储时间的一点思考.md
rename to docs/database/mysql/关于数据库存储时间的一点思考.md
index ab4e62c6..8eccaff2 100644
--- a/docs/database/关于数据库存储时间的一点思考.md
+++ b/docs/database/mysql/关于数据库存储时间的一点思考.md
@@ -1,4 +1,4 @@
-我们平时开发中不可避免的就是要存储时间,比如我们要记录操作表中这条记录的时间、记录转账的交易时间、记录出发时间等等。你会发现这个时间这个东西与我们开发的联系还是非常紧密的,用的好与不好会给我们的业务甚至功能带来很大的影响。所以,我们有必要重新出发,好好认识一下这个东西。
+我们平时开发中不可避免的就是要存储时间,比如我们要记录操作表中这条记录的时间、记录转账的交易时间、记录出发时间等等。你会发现时间这个东西与我们开发的联系还是非常紧密的,用的好与不好会给我们的业务甚至功能带来很大的影响。所以,我们有必要重新出发,好好认识一下这个东西。
这是一篇短小精悍的文章,仔细阅读一定能学到不少东西!
@@ -9,7 +9,7 @@
但是,这是不正确的做法,主要会有下面两个问题:
1. 字符串占用的空间更大!
-2. 字符串存储的日期比较效率比较低(逐个字符进行比对),无法用日期相关的 API 进行计算和比较。
+2. 字符串存储的日期效率比较低(逐个字符进行比对),无法用日期相关的 API 进行计算和比较。
### 2.Datetime 和 Timestamp 之间抉择
@@ -17,7 +17,7 @@ Datetime 和 Timestamp 是 MySQL 提供的两种比较相似的保存时间的
**通常我们都会首选 Timestamp。** 下面说一下为什么这样做!
-#### 2.1 DateTime 类型没有时区信息的
+#### 2.1 DateTime 类型没有时区信息
**DateTime 类型是没有时区信息的(时区无关)** ,DateTime 类型保存的时间都是当前会话所设置的时区对应的时间。这样就会有什么问题呢?当你的时区更换之后,比如你的服务器更换地址或者更换客户端连接时区设置的话,就会导致你从数据库中读出的时间错误。不要小看这个问题,很多系统就是因为这个问题闹出了很多笑话。
@@ -106,7 +106,7 @@ Timestamp 只需要使用 4 个字节的存储空间,但是 DateTime 需要耗

-可以看出 5.6.4 之后的 MySQL 多出了一个需要 0 ~ 3 字节的小数位。Datatime 和 Timestamp 会有几种不同的存储空间占用。
+可以看出 5.6.4 之后的 MySQL 多出了一个需要 0 ~ 3 字节的小数位。DateTime 和 Timestamp 会有几种不同的存储空间占用。
为了方便,本文我们还是默认 Timestamp 只需要使用 4 个字节的存储空间,但是 DateTime 需要耗费 8 个字节的存储空间。
diff --git a/docs/database/阿里巴巴开发手册数据库部分的一些最佳实践.md b/docs/database/mysql/阿里巴巴开发手册数据库部分的一些最佳实践.md
similarity index 100%
rename from docs/database/阿里巴巴开发手册数据库部分的一些最佳实践.md
rename to docs/database/mysql/阿里巴巴开发手册数据库部分的一些最佳实践.md
diff --git a/docs/database/数据库基础知识.md b/docs/database/数据库基础知识.md
new file mode 100644
index 00000000..92f16b97
--- /dev/null
+++ b/docs/database/数据库基础知识.md
@@ -0,0 +1,118 @@
+数据库知识基础,这部分内容一定要理解记忆。虽然这部分内容只是理论知识,但是非常重要,这是后面学习 MySQL 数据库的基础。PS:这部分内容由于涉及太多概念性内容,所以参考了维基百科和百度百科相应的介绍。
+
+## 什么是数据库,数据库管理系统,数据库系统,数据库管理员?
+
+- **数据库** :数据库(DataBase 简称 DB)就是信息的集合或者说数据库是由数据库管理系统管理的数据的集合。
+- **数据库管理系统** : 数据库管理系统(Database Management System 简称 DBMS)是一种操纵和管理数据库的大型软件,通常用于建立、使用和维护数据库。
+- **数据库系统** : 数据库系统(Data Base System,简称 DBS)通常由软件、数据库和数据管理员(DBA)组成。
+- **数据库管理员** : 数据库管理员(Database Administrator,简称 DBA)负责全面管理和控制数据库系统。
+
+数据库系统基本构成如下图所示:
+
+
+
+## 什么是元组,码,候选码,主码,外码,主属性,非主属性?
+
+- **元组** : 元组(tuple)是关系数据库中的基本概念,关系是一张表,表中的每行(即数据库中的每条记录)就是一个元组,每列就是一个属性。 在二维表里,元组也称为行。
+- **码** :码就是能唯一标识实体的属性,对应表中的列。
+- **候选码** : 若关系中的某一属性或属性组的值能唯一的标识一个元组,而其任何、子集都不能再标识,则称该属性组为候选码。例如:在学生实体中,“学号”是能唯一的区分学生实体的,同时又假设“姓名”、“班级”的属性组合足以区分学生实体,那么{学号}和{姓名,班级}都是候选码。
+- **主码** : 主码也叫主键。主码是从候选码中选出来的。 一个实体集中只能有一个主码,但可以有多个候选码。
+- **外码** : 外码也叫外键。如果一个关系中的一个属性是另外一个关系中的主码则这个属性为外码。
+- **主属性** : 候选码中出现过的属性称为主属性。比如关系 工人(工号,身份证号,姓名,性别,部门).显然工号和身份证号都能够唯一标示这个关系,所以都是候选码。工号、身份证号这两个属性就是主属性。如果主码是一个属性组,那么属性组中的属性都是主属性。
+- **非主属性:** 不包含在任何一个候选码中的属性称为非主属性。比如在关系——学生(学号,姓名,年龄,性别,班级)中,主码是“学号”,那么其他的“姓名”、“年龄”、“性别”、“班级”就都可以称为非主属性。
+
+## 主键和外键有什么区别?
+
+- **主键(主码)** :主键用于唯一标识一个元组,不能有重复,不允许为空。一个表只能有一个主键。
+- **外键(外码)** :外键用来和其他表建立联系用,外键是另一表的主键,外键是可以有重复的,可以是空值。一个表可以有多个外键。
+
+## 什么是 ER 图?
+
+> 我们做一个项目的时候一定要试着画 ER 图来捋清数据库设计,这个也是面试官问你项目的时候经常会被问道的。
+
+**E-R 图**也称实体-联系图(Entity Relationship Diagram),提供了表示实体类型、属性和联系的方法,用来描述现实世界的概念模型。 它是描述现实世界关系概念模型的有效方法。 是表示概念关系模型的一种方式。
+
+下图是一个学生选课的 ER 图,每个学生可以选若干门课程,同一门课程也可以被若干人选择,所以它们之间的关系是多对多(M:N)。另外,还有其他两种关系是:1 对 1(1:1)、1 对多(1:N)。
+
+
+
+我们试着将上面的 ER 图转换成数据库实际的关系模型(实际设计中,我们通常会将任课教师也作为一个实体来处理):
+
+
+
+## 数据库范式了解吗?
+
+**1NF(第一范式)**
+
+属性(对应于表中的字段)不能再被分割,也就是这个字段只能是一个值,不能再分为多个其他的字段了。**1NF 是所有关系型数据库的最基本要求** ,也就是说关系型数据库中创建的表一定满足第一范式。
+
+**2NF(第二范式)**
+
+2NF 在 1NF 的基础之上,消除了非主属性对于码的部分函数依赖。如下图所示,展示了第一范式到第二范式的过渡。第二范式在第一范式的基础上增加了一个列,这个列称为主键,非主属性都依赖于主键。
+
+
+
+一些重要的概念:
+
+- **函数依赖(functional dependency)** :若在一张表中,在属性(或属性组)X 的值确定的情况下,必定能确定属性 Y 的值,那么就可以说 Y 函数依赖于 X,写作 X → Y。
+- **部分函数依赖(partial functional dependency)** :如果 X→Y,并且存在 X 的一个真子集 X0,使得 X0→Y,则称 Y 对 X 部分函数依赖。比如学生基本信息表 R 中(学号,身份证号,姓名)当然学号属性取值是唯一的,在 R 关系中,(学号,身份证号)->(姓名),(学号)->(姓名),(身份证号)->(姓名);所以姓名部分函数依赖与(学号,身份证号);
+- **完全函数依赖(Full functional dependency)** :在一个关系中,若某个非主属性数据项依赖于全部关键字称之为完全函数依赖。比如学生基本信息表 R(学号,班级,姓名)假设不同的班级学号有相同的,班级内学号不能相同,在 R 关系中,(学号,班级)->(姓名),但是(学号)->(姓名)不成立,(班级)->(姓名)不成立,所以姓名完全函数依赖与(学号,班级);
+- **传递函数依赖** : 在关系模式 R(U)中,设 X,Y,Z 是 U 的不同的属性子集,如果 X 确定 Y、Y 确定 Z,且有 X 不包含 Y,Y 不确定 X,(X∪Y)∩Z=空集合,则称 Z 传递函数依赖(transitive functional dependency) 于 X。传递函数依赖会导致数据冗余和异常。传递函数依赖的 Y 和 Z 子集往往同属于某一个事物,因此可将其合并放到一个表中。比如在关系 R(学号 ,姓名, 系名,系主任)中,学号 → 系名,系名 → 系主任,所以存在非主属性系主任对于学号的传递函数依赖。。
+
+**3NF(第三范式)**
+
+3NF 在 2NF 的基础之上,消除了非主属性对于码的传递函数依赖 。符合 3NF 要求的数据库设计,**基本**上解决了数据冗余过大,插入异常,修改异常,删除异常的问题。比如在关系 R(学号 ,姓名, 系名,系主任)中,学号 → 系名,系名 → 系主任,所以存在非主属性系主任对于学号的传递函数依赖,所以该表的设计,不符合 3NF 的要求。
+
+**总结**
+
+- 1NF:属性不可再分。
+- 2NF:1NF 的基础之上,消除了非主属性对于码的部分函数依赖。
+- 3NF:3NF 在 2NF 的基础之上,消除了非主属性对于码的传递函数依赖 。
+
+## 什么是存储过程?
+
+我们可以把存储过程看成是一些 SQL 语句的集合,中间加了点逻辑控制语句。存储过程在业务比较复杂的时候是非常实用的,比如很多时候我们完成一个操作可能需要写一大串 SQL 语句,这时候我们就可以写有一个存储过程,这样也方便了我们下一次的调用。存储过程一旦调试完成通过后就能稳定运行,另外,使用存储过程比单纯 SQL 语句执行要快,因为存储过程是预编译过的。
+
+存储过程在互联网公司应用不多,因为存储过程难以调试和扩展,而且没有移植性,还会消耗数据库资源。
+
+阿里巴巴 Java 开发手册里要求禁止使用存储过程。
+
+
+
+## drop、delete 与 truncate 区别?
+
+### 用法不同
+
+- drop(丢弃数据): `drop table 表名` ,直接将表都删除掉,在删除表的时候使用。
+- truncate (清空数据) : `truncate table 表名` ,只删除表中的数据,再插入数据的时候自增长 id 又从 1 开始,在清空表中数据的时候使用。
+- delete(删除数据) : `delete from 表名 where 列名=值`,删除某一列的数据,如果不加 where 子句和`truncate table 表名`作用类似。
+
+truncate 和不带 where 子句的 delete、以及 drop 都会删除表内的数据,但是 **truncate 和 delete 只删除数据不删除表的结构(定义),执行 drop 语句,此表的结构也会删除,也就是执行 drop 之后对应的表不复存在。**
+
+### 属于不同的数据库语言
+
+truncate 和 drop 属于 DDL(数据定义语言)语句,操作立即生效,原数据不放到 rollback segment 中,不能回滚,操作不触发 trigger。而 delete 语句是 DML (数据库操作语言)语句,这个操作会放到 rollback segement 中,事务提交之后才生效。
+
+**DML 语句和 DDL 语句区别:**
+
+- DML 是数据库操作语言(Data Manipulation Language)的缩写,是指对数据库中表记录的操作,主要包括表记录的插入(insert)、更新(update)、删除(delete)和查询(select),是开发人员日常使用最频繁的操作。
+- DDL (Data Definition Language)是数据定义语言的缩写,简单来说,就是对数据库内部的对象进行创建、删除、修改的操作语言。它和 DML 语言的最大区别是 DML 只是对表内部数据的操作,而不涉及到表的定义、结构的修改,更不会涉及到其他对象。DDL 语句更多的被数据库管理员(DBA)所使用,一般的开发人员很少使用。
+
+### 执行速度不同
+
+一般来说:drop>truncate>delete(这个我没有设计测试过)。
+
+## 数据库设计通常分为哪几步?
+
+1. **需求分析** : 分析用户的需求,包括数据、功能和性能需求。
+2. **概念结构设计** : 主要采用 E-R 模型进行设计,包括画 E-R 图。
+3. **逻辑结构设计** : 通过将 E-R 图转换成表,实现从 E-R 模型到关系模型的转换。
+4. **物理结构设计** : 主要是为所设计的数据库选择合适的存储结构和存取路径。
+5. **数据库实施** : 包括编程、测试和试运行
+6. **数据库的运行和维护** : 系统的运行与数据库的日常维护。
+
+## 参考
+
+-
+-
+-
diff --git a/docs/java/basis/BIO,NIO,AIO总结.md b/docs/java/basis/BIO,NIO,AIO总结.md
index 50f6b7fe..33a7ac50 100644
--- a/docs/java/basis/BIO,NIO,AIO总结.md
+++ b/docs/java/basis/BIO,NIO,AIO总结.md
@@ -34,10 +34,10 @@
> When you execute something synchronously, you wait for it to finish before moving on to another task. When you execute something asynchronously, you can move on to another task before it finishes.
>
-> 当你同步执行某项任务时,你需要等待其完成才能继续执行其他任务。当你异步执行某些操作时,你可以在完成另一个任务之前继续进行。
+> 当你同步执行某项任务时,你需要等待其完成才能继续执行其他任务。当您异步执行某项任务时,你可以在它完成之前继续执行其他任务。
- **同步** :两个同步任务相互依赖,并且一个任务必须以依赖于另一任务的某种方式执行。 比如在`A->B`事件模型中,你需要先完成 A 才能执行B。 再换句话说,同步调用中被调用者未处理完请求之前,调用不返回,调用者会一直等待结果的返回。
-- **异步**: 两个异步的任务是完全独立的,一方的执行不需要等待另外一方的执行。再换句话说,异步调用中一调用就返回结果不需要等待结果返回,当结果返回的时候通过回调函数或者其他方式拿着结果再做相关事情,
+- **异步**: 两个异步的任务是完全独立的,一方的执行不需要等待另外一方的执行。再换句话说,异步调用中一调用就返回结果不需要等待结果返回,当结果返回的时候通过回调函数或者其他方式拿着结果再做相关事情。
**阻塞和非阻塞**
@@ -58,7 +58,7 @@ BIO通信(一请求一应答)模型图如下(图源网络,原出处不明)

-采用 **BIO 通信模型** 的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接。我们一般通过在`while(true)` 循环中服务端会调用 `accept()` 方法等待接收客户端的连接的方式监听请求,请求一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成, 不过可以通过多线程来支持多个客户端的连接,如上图所示。
+采用 **BIO 通信模型** 的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接。我们一般通过在`while(true)` 循环中服务端会调用 `accept()` 方法等待接收客户端的连接的方式监听请求,一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成, 不过可以通过多线程来支持多个客户端的连接,如上图所示。
如果要让 **BIO 通信模型** 能够同时处理多个客户端请求,就必须使用多线程(主要原因是`socket.accept()`、`socket.read()`、`socket.write()` 涉及的三个主要函数都是同步阻塞的),也就是说它在接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的 **一请求一应答通信模型** 。我们可以设想一下如果这个连接不做任何事情的话就会造成不必要的线程开销,不过可以通过 **线程池机制** 改善,线程池还可以让线程的创建和回收成本相对较低。使用`FixedThreadPool` 可以有效的控制了线程的最大数量,保证了系统有限的资源的控制,实现了N(客户端请求数量):M(处理客户端请求的线程数量)的伪异步I/O模型(N 可以远远大于 M),下面一节"伪异步 BIO"中会详细介绍到。
@@ -193,7 +193,7 @@ Java IO的各种流是阻塞的。这意味着,当一个线程调用 `read()`
Buffer是一个对象,它包含一些要写入或者要读出的数据。在NIO类库中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中·可以将数据直接写入或者将数据直接读到 Stream 对象中。虽然 Stream 中也有 Buffer 开头的扩展类,但只是流的包装类,还是从流读到缓冲区,而 NIO 却是直接读到 Buffer 中进行操作。
-在NIO厍中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。
+在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。
最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了ByteBuffer,还有其他的一些缓冲区,事实上,每一种Java基本类型(除了Boolean类型)都对应有一种缓冲区。
diff --git a/docs/java/basis/IO模型.md b/docs/java/basis/IO模型.md
index 243fdc00..6091a8fe 100644
--- a/docs/java/basis/IO模型.md
+++ b/docs/java/basis/IO模型.md
@@ -18,7 +18,7 @@ I/O(**I**nput/**O**utpu) 即**输入/输出** 。

-输入设备(比如键盘)和输出设备(比如鼠标)都属于外部设备。网卡、硬盘这种既可以属于输入设备,也可以属于输出设备。
+输入设备(比如键盘)和输出设备(比如显示器)都属于外部设备。网卡、硬盘这种既可以属于输入设备,也可以属于输出设备。
输入设备向计算机输入数据,输出设备接收计算机输出的数据。
@@ -28,7 +28,7 @@ I/O(**I**nput/**O**utpu) 即**输入/输出** 。
根据大学里学到的操作系统相关的知识:为了保证操作系统的稳定性和安全性,一个进程的地址空间划分为 **用户空间(User space)** 和 **内核空间(Kernel space )** 。
-像我们平常运行的应用程序都是运行在用户空间,只有内核空间才能进行系统态级别的资源有关的操作,比如如文件管理、进程通信、内存管理等等。也就是说,我们想要进行 IO 操作,一定是要依赖内核空间的能力。
+像我们平常运行的应用程序都是运行在用户空间,只有内核空间才能进行系统态级别的资源有关的操作,比如文件管理、进程通信、内存管理等等。也就是说,我们想要进行 IO 操作,一定是要依赖内核空间的能力。
并且,用户空间的程序不能直接访问内核空间。
@@ -36,7 +36,7 @@ I/O(**I**nput/**O**utpu) 即**输入/输出** 。
因此,用户进程想要执行 IO 操作的话,必须通过 **系统调用** 来间接访问内核空间
-我们在平常开发过程中接触最多的就是 **磁盘 IO(读写文件)** 和 **网络 IO(网络请求和相应)**。
+我们在平常开发过程中接触最多的就是 **磁盘 IO(读写文件)** 和 **网络 IO(网络请求和响应)**。
**从应用程序的视角来看的话,我们的应用程序对操作系统的内核发起 IO 调用(系统调用),操作系统负责的内核执行具体的 IO 操作。也就是说,我们的应用程序实际上只是发起了 IO 操作的调用而已,具体 IO 的执行是由操作系统的内核来完成的。**
@@ -57,7 +57,7 @@ UNIX 系统下, IO 模型一共有 5 种: **同步阻塞 I/O**、**同步非
**BIO 属于同步阻塞 IO 模型** 。
-同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到在内核把数据拷贝到用户空间。
+同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。

diff --git a/docs/java/basis/Java基础知识.md b/docs/java/basis/Java基础知识.md
index f526cd0d..6b04bbe5 100644
--- a/docs/java/basis/Java基础知识.md
+++ b/docs/java/basis/Java基础知识.md
@@ -117,17 +117,17 @@ JRE 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有
高级编程语言按照程序的执行方式分为编译型和解释型两种。简单来说,编译型语言是指编译器针对特定的操作系统将源代码一次性翻译成可被该平台执行的机器码;解释型语言是指解释器对源程序逐行解释成特定平台的机器码并立即执行。比如,你想阅读一本英文名著,你可以找一个英文翻译人员帮助你阅读,
有两种选择方式,你可以先等翻译人员将全本的英文名著(也就是源码)都翻译成汉语,再去阅读,也可以让翻译人员翻译一段,你在旁边阅读一段,慢慢把书读完。
-Java 语言既具有编译型语言的特征,也具有解释型语言的特征,因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(`\*.class` 文件),这种字节码必须由 Java 解释器来解释执行。因此,我们可以认为 Java 语言编译与解释并存。
+Java 语言既具有编译型语言的特征,也具有解释型语言的特征,因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(`*.class` 文件),这种字节码必须由 Java 解释器来解释执行。因此,我们可以认为 Java 语言编译与解释并存。
### Oracle JDK 和 OpenJDK 的对比
-可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么 Oracle 和 OpenJDK 之间是否存在重大差异?下面我通过收集到的一些资料,为你解答这个被很多人忽视的问题。
+可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么 Oracle JDK 和 OpenJDK 之间是否存在重大差异?下面我通过收集到的一些资料,为你解答这个被很多人忽视的问题。
对于 Java 7,没什么关键的地方。OpenJDK 项目主要基于 Sun 捐赠的 HotSpot 源代码。此外,OpenJDK 被选为 Java 7 的参考实现,由 Oracle 工程师维护。关于 JVM,JDK,JRE 和 OpenJDK 之间的区别,Oracle 博客帖子在 2012 年有一个更详细的答案:
> 问:OpenJDK 存储库中的源代码与用于构建 Oracle JDK 的代码之间有什么区别?
>
-> 答:非常接近 - 我们的 Oracle JDK 版本构建过程基于 OpenJDK 7 构建,只添加了几个部分,例如部署代码,其中包括 Oracle 的 Java 插件和 Java WebStart 的实现,以及一些封闭的源代码派对组件,如图形光栅化器,一些开源的第三方组件,如 Rhino,以及一些零碎的东西,如附加文档或第三方字体。展望未来,我们的目的是开源 Oracle JDK 的所有部分,除了我们考虑商业功能的部分。
+> 答:非常接近 - 我们的 Oracle JDK 版本构建过程基于 OpenJDK 7 构建,只添加了几个部分,例如部署代码,其中包括 Oracle 的 Java 插件和 Java WebStart 的实现,以及一些闭源的第三方组件,如图形光栅化器,一些开源的第三方组件,如 Rhino,以及一些零碎的东西,如附加文档或第三方字体。展望未来,我们的目的是开源 Oracle JDK 的所有部分,除了我们考虑商业功能的部分。
**总结:**
@@ -175,7 +175,7 @@ Java 语言既具有编译型语言的特征,也具有解释型语言的特征
> 字符封装类 `Character` 有一个成员常量 `Character.SIZE` 值为 16,单位是`bits`,该值除以 8(`1byte=8bits`)后就可以得到 2 个字节
> java 编程思想第四版:2.2.2 节
-> 
+> 
### 注释
@@ -216,8 +216,9 @@ Java 中的注释有三种:
### Java 中有哪些常见的关键字?
+| 分类 | 关键字 | | | | | | |
+| :-------------------- | -------- | ---------- | -------- | ------------ | ---------- | --------- | ------ |
| 访问控制 | private | protected | public | | | | |
-| -------------------- | -------- | ---------- | -------- | ------------ | ---------- | --------- | ------ |
| 类,方法和变量修饰符 | abstract | class | extends | final | implements | interface | native |
| | new | static | strictfp | synchronized | transient | volatile | |
| 程序控制 | break | continue | return | do | while | if | else |
@@ -251,7 +252,7 @@ return 用于跳出所在方法,结束该方法的运行。return 一般有两
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
-Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。
+Java 的泛型是伪泛型,这是因为 Java 在运行期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。
```java
List list = new ArrayList<>();
@@ -274,7 +275,7 @@ System.out.println(list);
```java
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
-public class Generic{
+public class Generic {
private T key;
@@ -282,7 +283,7 @@ public class Generic{
this.key = key;
}
- public T getKey(){
+ public T getKey() {
return key;
}
}
@@ -316,7 +317,7 @@ class GeneratorImpl implements Generator{
实现泛型接口,指定类型:
```java
-class GeneratorImpl implements Generator{
+class GeneratorImpl implements Generator{
@Override
public String method() {
return "hello";
@@ -327,13 +328,12 @@ class GeneratorImpl implements Generator{
**3.泛型方法** :
```java
- public static < E > void printArray( E[] inputArray )
- {
- for ( E element : inputArray ){
- System.out.printf( "%s ", element );
- }
- System.out.println();
+public static void printArray(E[] inputArray) {
+ for (E element : inputArray) {
+ System.out.printf("%s ", element);
}
+ System.out.println();
+}
```
使用:
@@ -342,8 +342,8 @@ class GeneratorImpl implements Generator{
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { "Hello", "World" };
-printArray( intArray );
-printArray( stringArray );
+printArray(intArray);
+printArray(stringArray);
```
**常用的通配符为: T,E,K,V,?**
@@ -457,7 +457,7 @@ public native int hashCode();
在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。
-因为 `hashCode()` 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 `hashCode`。
+因为 `hashCode()` 所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 `hashCode` )。
我们刚刚也提到了 `HashSet`,如果 `HashSet` 在对比的时候,同样的 hashcode 有多个对象,它会使用 `equals()` 来判断是否真的相同。也就是说 `hashcode` 只是用来缩小查找成本。
@@ -501,7 +501,7 @@ Java 中有 8 种基本数据类型,分别为:
基本数据类型直接存放在 Java 虚拟机栈中的局部变量表中,而包装类型属于对象类型,我们知道对象实例都存在于堆中。相比于对象类型, 基本数据类型占用的空间非常小。
-> 《深入理解 Java 虚拟机》 :局部变量表主要存放了编译期可知的基本数据类型**(boolean、byte、char、short、int、float、long、double)**、**对象引用**(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
+> 《深入理解 Java 虚拟机》 :局部变量表主要存放了编译期可知的基本数据类型 **(boolean、byte、char、short、int、float、long、double)**、**对象引用**(reference 类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
### 自动装箱与拆箱
@@ -742,15 +742,43 @@ public void f5(int a) {
### 静态方法和实例方法有何不同?
-1. 在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名"的方式。而实例方法只有后面这种方式。也就是说,**调用静态方法可以无需创建对象。**
+**1、调用方式**
-2. 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制。
+在外部调用静态方法时,可以使用 `类名.方法名` 的方式,也可以使用 `对象.方法名` 的方式,而实例方法只有后面这种方式。也就是说,**调用静态方法可以无需创建对象** 。
+
+不过,需要注意的是一般不建议使用 `对象.方法名` 的方式来调用静态方法。这种方式非常容易造成混淆,静态方法不属于类的某个对象而是属于这个类。
+
+因此,一般建议使用 `类名.方法名` 的方式来调用静态方法。
+
+```java
+
+public class Person {
+ public void method() {
+ //......
+ }
+
+ public static void staicMethod(){
+ //......
+ }
+ public static void main(String[] args) {
+ Person person = new Person();
+ // 调用实例方法
+ person.method();
+ // 调用静态方法
+ Person.staicMethod()
+ }
+}
+```
+
+**2、访问类成员是否存在限制**
+
+静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法),而实例方法不存在这个限制。
### 为什么 Java 中只有值传递?
首先,我们回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。
-**按值调用(call by value)** 表示方法接收的是调用者提供的值,**按引用调用(call by reference)** 表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。它用来描述各种程序设计语言(不只是 Java)中方法参数传递方式。
+**按值调用(call by value)** 表示方法接收的是调用者提供的值,**按引用调用(call by reference)** 表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。它用来描述各种程序设计语言(不只是 Java)中方法参数传递方式。
**Java 程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。**
@@ -790,7 +818,7 @@ num2 = 20
**解析:**
-
+
在 swap 方法中,a、b 的值进行交换,并不会影响到 num1、num2。因为,a、b 中的值,只是从 num1、num2 的复制过来的。也就是说,a、b 相当于 num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。
@@ -821,7 +849,7 @@ num2 = 20
**解析:**
-
+
array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的是同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。
@@ -866,11 +894,11 @@ s2:小李
交换之前:
-
+
交换之后:
-
+
通过上面两张图可以很清晰的看出: **方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap 方法的参数 x 和 y 被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝**
@@ -932,7 +960,7 @@ Java 程序设计语言对对象采用的不是引用调用,实际上,对象
- “两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
- “一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
-⭐️ 关于 **重写的返回值类**型 这里需要额外多说明一下,上面的表述不太清晰准确:如果方法的返回类型是 void 和基本数据类型,则返回值重写时不可修改。但是如果方法的返回值是引用类型,重写时是可以返回该引用类型的子类的。
+⭐️ 关于 **重写的返回值类型** 这里需要额外多说明一下,上面的表述不太清晰准确:如果方法的返回类型是 void 和基本数据类型,则返回值重写时不可修改。但是如果方法的返回值是引用类型,重写时是可以返回该引用类型的子类的。
```java
public class Hero {
@@ -1119,7 +1147,6 @@ abstract class AbstractStringBuilder implements Appendable, CharSequence {
Object 类是一个特殊的类,是所有类的父类。它主要提供了以下 11 个方法:
```java
-
public final native Class> getClass()//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。
public native int hashCode() //native方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。
@@ -1140,9 +1167,9 @@ public final void wait(long timeout, int nanos) throws InterruptedException//多
public final void wait() throws InterruptedException//跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
protected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作
-
```
+
## 反射
### 何为反射?
@@ -1231,9 +1258,9 @@ Java 代码在编译过程中 ,我们即使不处理不受检查异常也可
### Throwable 类常用方法
-- **`public string getMessage()`**:返回异常发生时的简要描述
-- **`public string toString()`**:返回异常发生时的详细信息
-- **`public string getLocalizedMessage()`**:返回异常对象的本地化信息。使用 `Throwable` 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 `getMessage()`返回的结果相同
+- **`public String getMessage()`**:返回异常发生时的简要描述
+- **`public String toString()`**:返回异常发生时的详细信息
+- **`public String getLocalizedMessage()`**:返回异常对象的本地化信息。使用 `Throwable` 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 `getMessage()`返回的结果相同
- **`public void printStackTrace()`**:在控制台上打印 `Throwable` 对象封装的异常信息
### try-catch-finally
@@ -1244,9 +1271,9 @@ Java 代码在编译过程中 ,我们即使不处理不受检查异常也可
**在以下 3 种特殊情况下,`finally` 块不会被执行:**
-2. 在 `try` 或 `finally`块中用了 `System.exit(int)`退出程序。但是,如果 `System.exit(int)` 在异常语句之后,`finally` 还是会被执行
-3. 程序所在的线程死亡。
-4. 关闭 CPU。
+1. 在 `try` 或 `finally`块中用了 `System.exit(int)`退出程序。但是,如果 `System.exit(int)` 在异常语句之后,`finally` 还是会被执行
+2. 程序所在的线程死亡。
+3. 关闭 CPU。
下面这部分内容来自 issue:。
@@ -1325,7 +1352,7 @@ try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new F
}
```
-## I\O 流
+## I/O 流
### 什么是序列化?什么是反序列化?
@@ -1334,7 +1361,7 @@ try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new F
简单来说:
- **序列化**: 将数据结构或对象转换成二进制字节流的过程
-- **反序列化**:将在序列化过程中所生成的二进制字节流的过程转换成数据结构或者对象的过程
+- **反序列化**:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class),但是在 C++这种半面向对象的语言中,struct(结构体)定义的是数据结构类型,而 class 对应的是对象类型。
@@ -1350,9 +1377,14 @@ try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new F
### Java 序列化中如果有些字段不想进行序列化,怎么办?
-`对于不想进行序列化的变量,使用`transient`关键字修饰。`
+对于不想进行序列化的变量,使用 `transient` 关键字修饰。
-`transient` 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 `transient` 修饰的变量值不会被持久化和恢复。`transient` 只能修饰变量,不能修饰类和方法。
+`transient` 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 `transient` 修饰的变量值不会被持久化和恢复。
+
+关于 `transient` 还有几点注意:
+- `transient` 只能修饰变量,不能修饰类和方法。
+- `transient` 修饰的变量,在反序列化后变量值将会被置成类型的默认值。例如,如果是修饰 `int` 类型,那么反序列后结果就是 `0`。
+- `static` 变量因为不属于任何对象(Object),所以无论有没有 `transient` 关键字修饰,均不会被序列化。
### 获取用键盘输入常用的两种方法
@@ -1377,7 +1409,7 @@ String s = input.readLine();
- 按照操作单元划分,可以划分为字节流和字符流;
- 按照流的角色划分为节点流和处理流。
-Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
+Java IO 流共涉及 40 多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
@@ -1400,4 +1432,4 @@ Java Io 流共涉及 40 多个类,这些类看上去很杂乱,但实际上
- https://stackoverflow.com/questions/1906445/what-is-the-difference-between-jdk-and-jre
- https://www.educba.com/oracle-vs-openjdk/
-- https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk?answertab=active#tab-top## 基础概念与常识
\ No newline at end of file
+- https://stackoverflow.com/questions/22358071/differences-between-oracle-jdk-and-openjdk 基础概念与常识
diff --git a/docs/java/basis/Java基础知识疑难点.md b/docs/java/basis/Java基础知识疑难点.md
index 3688a0e6..a1d414d4 100644
--- a/docs/java/basis/Java基础知识疑难点.md
+++ b/docs/java/basis/Java基础知识疑难点.md
@@ -1,29 +1,4 @@
-
-
-- [1. 基础](#1-基础)
- - [1.1. 正确使用 equals 方法](#11-正确使用-equals-方法)
- - [1.2. 整型包装类值的比较](#12-整型包装类值的比较)
- - [1.3. BigDecimal](#13-bigdecimal)
- - [1.3.1. BigDecimal 的用处](#131-bigdecimal-的用处)
- - [1.3.2. BigDecimal 的大小比较](#132-bigdecimal-的大小比较)
- - [1.3.3. BigDecimal 保留几位小数](#133-bigdecimal-保留几位小数)
- - [1.3.4. BigDecimal 的使用注意事项](#134-bigdecimal-的使用注意事项)
- - [1.3.5. 总结](#135-总结)
- - [1.4. 基本数据类型与包装数据类型的使用标准](#14-基本数据类型与包装数据类型的使用标准)
-- [2. 集合](#_2-集合)
- - [2.1. Arrays.asList()使用指南](#21-arraysaslist使用指南)
- - [2.1.1. 简介](#211-简介)
- - [2.1.2. 《阿里巴巴Java 开发手册》对其的描述](#212-阿里巴巴java-开发手册对其的描述)
- - [2.1.3. 使用时的注意事项总结](#213-使用时的注意事项总结)
- - [2.1.4. 如何正确的将数组转换为ArrayList?](#214-如何正确的将数组转换为arraylist)
- - [2.2. Collection.toArray()方法使用的坑&如何反转数组](#22-collectiontoarray方法使用的坑如何反转数组)
- - [2.3. 不要在 foreach 循环里进行元素的 remove/add 操作](#23-不要在-foreach-循环里进行元素的-removeadd-操作)
-
-
-
-# 1. 基础
-
-## 1.1. 正确使用 equals 方法
+## 正确使用 equals 方法
Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals。
@@ -52,7 +27,7 @@ Objects.equals(null,"SnailClimb");// false
我们看一下`java.util.Objects#equals`的源码就知道原因了。
```java
public static boolean equals(Object a, Object b) {
- // 可以避免空指针异常。如果a==null的话此时a.equals(b)就不会得到执行,避免出现空指针异常。
+ // 可以避免空指针异常。如果a=null的话此时a.equals(b)就不会得到执行,避免出现空指针异常。
return (a == b) || (a != null && a.equals(b));
}
```
@@ -65,29 +40,29 @@ Reference:[Java中equals方法造成空指针异常的原因及解决方案](htt
- 可以使用 == 或者 != 操作来比较null值,但是不能使用其他算法或者逻辑操作。在Java中`null == null`将返回true。
- 不能使用一个值为null的引用类型变量来调用非静态方法,否则会抛出异常
-## 1.2. 整型包装类值的比较
+## 整型包装类值的比较
所有整型包装类对象值的比较必须使用equals方法。
先看下面这个例子:
```java
-Integer x = 3;
-Integer y = 3;
-System.out.println(x == y);// true
-Integer a = new Integer(3);
-Integer b = new Integer(3);
-System.out.println(a == b);//false
-System.out.println(a.equals(b));//true
+Integer i1 = 40;
+Integer i2 = new Integer(40);
+System.out.println(i1==i2);//false
```
-当使用自动装箱方式创建一个Integer对象时,当数值在-128 ~127时,会将创建的 Integer 对象缓存起来,当下次再出现该数值时,直接从缓存中取出对应的Integer对象。所以上述代码中,x和y引用的是相同的Integer对象。
+`Integer i1=40` 这一行代码会发生装箱,也就是说这行代码等价于 `Integer i1=Integer.valueOf(40)` 。因此,`i1` 直接使用的是常量池中的对象。而`Integer i1 = new Integer(40)` 会直接创建新的对象。因此,输出 false 。
+
+记住:**所有整型包装类对象之间值的比较,全部使用 `equals()` 方法比较**。
+
+
**注意:** 如果你的IDE(IDEA/Eclipse)上安装了阿里巴巴的p3c插件,这个插件如果检测到你用 ==的话会报错提示,推荐安装一个这个插件,很不错。
-## 1.3. BigDecimal
+## BigDecimal
-### 1.3.1. BigDecimal 的用处
+### BigDecimal 的用处
《阿里巴巴Java开发手册》中提到:**浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断。** 具体原理和浮点数的编码方式有关,这里就不多提了,我们下面直接上实例:
@@ -113,7 +88,7 @@ System.out.println(y); /* 0.1 */
System.out.println(Objects.equals(x, y)); /* true */
```
-### 1.3.2. BigDecimal 的大小比较
+### BigDecimal 的大小比较
`a.compareTo(b)` : 返回 -1 表示 `a` 小于 `b`,0 表示 `a` 等于 `b` , 1表示 `a` 大于 `b`。
@@ -122,7 +97,7 @@ BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
System.out.println(a.compareTo(b));// 1
```
-### 1.3.3. BigDecimal 保留几位小数
+### BigDecimal 保留几位小数
通过 `setScale`方法设置保留几位小数以及保留规则。保留规则有挺多种,不需要记,IDEA会提示。
@@ -132,19 +107,19 @@ BigDecimal n = m.setScale(3,BigDecimal.ROUND_HALF_DOWN);
System.out.println(n);// 1.255
```
-### 1.3.4. BigDecimal 的使用注意事项
+### BigDecimal 的使用注意事项
注意:我们在使用BigDecimal时,为了防止精度丢失,推荐使用它的 **BigDecimal(String)** 构造方法来创建对象。《阿里巴巴Java开发手册》对这部分内容也有提到如下图所示。

-### 1.3.5. 总结
+### 总结
BigDecimal 主要用来操作(大)浮点数,BigInteger 主要用来操作大整数(超过 long 类型)。
BigDecimal 的实现利用到了 BigInteger, 所不同的是 BigDecimal 加入了小数位的概念
-## 1.4. 基本数据类型与包装数据类型的使用标准
+## 基本数据类型与包装数据类型的使用标准
Reference:《阿里巴巴Java开发手册》
@@ -160,237 +135,3 @@ Reference:《阿里巴巴Java开发手册》
**反例** : 比如显示成交总额涨跌情况,即正负 x%,x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。
-# 2. 集合
-
-## 2.1. Arrays.asList()使用指南
-
-最近使用`Arrays.asList()`遇到了一些坑,然后在网上看到这篇文章:[Java Array to List Examples](http://javadevnotes.com/java-array-to-list-examples) 感觉挺不错的,但是还不是特别全面。所以,自己对于这块小知识点进行了简单的总结。
-
-### 2.1.1. 简介
-
-`Arrays.asList()`在平时开发中还是比较常见的,我们可以使用它将一个数组转换为一个List集合。
-
-```java
-String[] myArray = {"Apple", "Banana", "Orange"};
-List myList = Arrays.asList(myArray);
-//上面两个语句等价于下面一条语句
-List myList = Arrays.asList("Apple","Banana", "Orange");
-```
-
-JDK 源码对于这个方法的说明:
-
-```java
-/**
- *返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁,
- * 与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。
- */
-public static List asList(T... a) {
- return new ArrayList<>(a);
-}
-```
-
-### 2.1.2. 《阿里巴巴Java 开发手册》对其的描述
-
-`Arrays.asList()`将数组转换为集合后,底层其实还是数组,《阿里巴巴Java 开发手册》对于这个方法有如下描述:
-
-方法.png)
-
-### 2.1.3. 使用时的注意事项总结
-
-**传递的数组必须是对象数组,而不是基本类型。**
-
-`Arrays.asList()`是泛型方法,传入的对象必须是对象数组。
-
-```java
-int[] myArray = {1, 2, 3};
-List myList = Arrays.asList(myArray);
-System.out.println(myList.size());//1
-System.out.println(myList.get(0));//数组地址值
-System.out.println(myList.get(1));//报错:ArrayIndexOutOfBoundsException
-int[] array = (int[]) myList.get(0);
-System.out.println(array[0]);//1
-```
-当传入一个原生数据类型数组时,`Arrays.asList()` 的真正得到的参数就不是数组中的元素,而是数组对象本身!此时List 的唯一元素就是这个数组,这也就解释了上面的代码。
-
-我们使用包装类型数组就可以解决这个问题。
-
-```java
-Integer[] myArray = {1, 2, 3};
-```
-
-**使用集合的修改方法:`add()`、`remove()`、`clear()`会抛出异常。**
-
-```java
-List myList = Arrays.asList(1, 2, 3);
-myList.add(4);//运行时报错:UnsupportedOperationException
-myList.remove(1);//运行时报错:UnsupportedOperationException
-myList.clear();//运行时报错:UnsupportedOperationException
-```
-
-`Arrays.asList()` 方法返回的并不是 `java.util.ArrayList` ,而是 `java.util.Arrays` 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。
-
-```java
-List myList = Arrays.asList(1, 2, 3);
-System.out.println(myList.getClass());//class java.util.Arrays$ArrayList
-```
-
-下图是`java.util.Arrays$ArrayList`的简易源码,我们可以看到这个类重写的方法有哪些。
-
-```java
- private static class ArrayList extends AbstractList
- implements RandomAccess, java.io.Serializable
- {
- ...
-
- @Override
- public E get(int index) {
- ...
- }
-
- @Override
- public E set(int index, E element) {
- ...
- }
-
- @Override
- public int indexOf(Object o) {
- ...
- }
-
- @Override
- public boolean contains(Object o) {
- ...
- }
-
- @Override
- public void forEach(Consumer super E> action) {
- ...
- }
-
- @Override
- public void replaceAll(UnaryOperator operator) {
- ...
- }
-
- @Override
- public void sort(Comparator super E> c) {
- ...
- }
- }
-```
-
-我们再看一下`java.util.AbstractList`的`remove()`方法,这样我们就明白为啥会抛出`UnsupportedOperationException`。
-
-```java
-public E remove(int index) {
- throw new UnsupportedOperationException();
-}
-```
-
-### 2.1.4. 如何正确的将数组转换为ArrayList?
-
-stackoverflow:https://dwz.cn/vcBkTiTW
-
-**1. 自己动手实现(教育目的)**
-
-```java
-//JDK1.5+
-static List arrayToList(final T[] array) {
- final List l = new ArrayList(array.length);
-
- for (final T s : array) {
- l.add(s);
- }
- return l;
-}
-```
-
-```java
-Integer [] myArray = { 1, 2, 3 };
-System.out.println(arrayToList(myArray).getClass());//class java.util.ArrayList
-```
-
-**2. 最简便的方法(推荐)**
-
-```java
-List list = new ArrayList<>(Arrays.asList("a", "b", "c"))
-```
-
-**3. 使用 Java8 的Stream(推荐)**
-
-```java
-Integer [] myArray = { 1, 2, 3 };
-List myList = Arrays.stream(myArray).collect(Collectors.toList());
-//基本类型也可以实现转换(依赖boxed的装箱操作)
-int [] myArray2 = { 1, 2, 3 };
-List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
-```
-
-**4. 使用 Guava(推荐)**
-
-对于不可变集合,你可以使用[`ImmutableList`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java)类及其[`of()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L101)与[`copyOf()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L225)工厂方法:(参数不能为空)
-
-```java
-List il = ImmutableList.of("string", "elements"); // from varargs
-List il = ImmutableList.copyOf(aStringArray); // from array
-```
-对于可变集合,你可以使用[`Lists`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java)类及其[`newArrayList()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java#L87)工厂方法:
-
-```java
-List l1 = Lists.newArrayList(anotherListOrCollection); // from collection
-List l2 = Lists.newArrayList(aStringArray); // from array
-List l3 = Lists.newArrayList("or", "string", "elements"); // from varargs
-```
-
-**5. 使用 Apache Commons Collections**
-
-```java
-List list = new ArrayList();
-CollectionUtils.addAll(list, str);
-```
-
-**6. 使用 Java9 的 `List.of()`方法**
-``` java
-Integer[] array = {1, 2, 3};
-List list = List.of(array);
-System.out.println(list); /* [1, 2, 3] */
-/* 不支持基本数据类型 */
-```
-
-## 2.2. Collection.toArray()方法使用的坑&如何反转数组
-
-该方法是一个泛型方法:` T[] toArray(T[] a);` 如果`toArray`方法中没有传递任何参数的话返回的是`Object`类型数组。
-
-```java
-String [] s= new String[]{
- "dog", "lazy", "a", "over", "jumps", "fox", "brown", "quick", "A"
-};
-List list = Arrays.asList(s);
-Collections.reverse(list);
-s=list.toArray(new String[0]);//没有指定类型的话会报错
-```
-
-由于JVM优化,`new String[0]`作为`Collection.toArray()`方法的参数现在使用更好,`new String[0]`就是起一个模板的作用,指定了返回数组的类型,0是为了节省空间,因为它只是为了说明返回的类型。详见:
-
-## 2.3. 不要在 foreach 循环里进行元素的 remove/add 操作
-
-如果要进行`remove`操作,可以调用迭代器的 `remove `方法而不是集合类的 remove 方法。因为如果列表在任何时间从结构上修改创建迭代器之后,以任何方式除非通过迭代器自身`remove/add`方法,迭代器都将抛出一个`ConcurrentModificationException`,这就是单线程状态下产生的 **fail-fast 机制**。
-
-> **fail-fast 机制** :多个线程对 fail-fast 集合进行修改的时,可能会抛出ConcurrentModificationException,单线程下也会出现这种情况,上面已经提到过。
-
-Java8开始,可以使用`Collection#removeIf()`方法删除满足特定条件的元素,如
-``` java
-List list = new ArrayList<>();
-for (int i = 1; i <= 10; ++i) {
- list.add(i);
-}
-list.removeIf(filter -> filter % 2 == 0); /* 删除list中的所有偶数 */
-System.out.println(list); /* [1, 3, 5, 7, 9] */
-```
-
-`java.util`包下面的所有的集合类都是fail-fast的,而`java.util.concurrent`包下面的所有的类都是fail-safe的。
-
-
-
-
-
diff --git a/docs/java/basis/Java常见关键字总结.md b/docs/java/basis/Java常见关键字总结.md
index 01fcdf2c..5958debc 100644
--- a/docs/java/basis/Java常见关键字总结.md
+++ b/docs/java/basis/Java常见关键字总结.md
@@ -23,63 +23,63 @@
## final 关键字
-**final关键字,意思是最终的、不可修改的,最见不得变化 ,用来修饰类、方法和变量,具有以下特点:**
+**final 关键字,意思是最终的、不可修改的,最见不得变化 ,用来修饰类、方法和变量,具有以下特点:**
-1. **final修饰的类不能被继承,final类中的所有成员方法都会被隐式的指定为final方法;**
+1. **final 修饰的类不能被继承,final 类中的所有成员方法都会被隐式的指定为 final 方法;**
-2. **final修饰的方法不能被重写;**
+2. **final 修饰的方法不能被重写;**
-3. **final修饰的变量是常量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能让其指向另一个对象。**
+3. **final 修饰的变量是常量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能让其指向另一个对象。**
-说明:使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的Java版本已经不需要使用final方法进行这些优化了)。类中所有的private方法都隐式地指定为final。
+说明:使用 final 方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的 Java 实现版本中,会将 final 方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升(现在的 Java 版本已经不需要使用 final 方法进行这些优化了)。类中所有的 private 方法都隐式地指定为 final。
## static 关键字
**static 关键字主要有以下四种使用场景:**
-1. **修饰成员变量和成员方法:** 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:`类名.静态变量名` `类名.静态方法名()`
+1. **修饰成员变量和成员方法:** 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被 static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:`类名.静态变量名` `类名.静态方法名()`
2. **静态代码块:** 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
-3. **静态内部类(static修饰类的话只能修饰内部类):** 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非static成员变量和方法。
-4. **静态导包(用来导入类中的静态资源,1.5之后的新特性):** 格式为:`import static` 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。
+3. **静态内部类(static 修饰类的话只能修饰内部类):** 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非 static 成员变量和方法。
+4. **静态导包(用来导入类中的静态资源,1.5 之后的新特性):** 格式为:`import static` 这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。
## this 关键字
-this关键字用于引用类的当前实例。 例如:
+this 关键字用于引用类的当前实例。 例如:
```java
class Manager {
Employees[] employees;
-
+
void manageEmployees() {
int totalEmp = this.employees.length;
System.out.println("Total employees: " + totalEmp);
this.report();
}
-
+
void report() { }
}
```
-在上面的示例中,this关键字用于两个地方:
+在上面的示例中,this 关键字用于两个地方:
-- this.employees.length:访问类Manager的当前实例的变量。
-- this.report():调用类Manager的当前实例的方法。
+- this.employees.length:访问类 Manager 的当前实例的变量。
+- this.report():调用类 Manager 的当前实例的方法。
此关键字是可选的,这意味着如果上面的示例在不使用此关键字的情况下表现相同。 但是,使用此关键字可能会使代码更易读或易懂。
## super 关键字
-super关键字用于从子类访问父类的变量和方法。 例如:
+super 关键字用于从子类访问父类的变量和方法。 例如:
```java
public class Super {
protected int number;
-
+
protected showNumber() {
System.out.println("number = " + number);
}
}
-
+
public class Sub extends Super {
void bar() {
super.number = 10;
@@ -88,16 +88,16 @@ public class Sub extends Super {
}
```
-在上面的例子中,Sub 类访问父类成员变量 number 并调用其其父类 Super 的 `showNumber()` 方法。
+在上面的例子中,Sub 类访问父类成员变量 number 并调用其父类 Super 的 `showNumber()` 方法。
**使用 this 和 super 要注意的问题:**
- 在构造器中使用 `super()` 调用父类中的其他构造方法时,该语句必须处于构造器的首行,否则编译器会报错。另外,this 调用本类中的其他构造方法时,也要放在首行。
-- this、super不能用在static方法中。
+- this、super 不能用在 static 方法中。
**简单解释一下:**
-被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, **this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西**。
+被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享。而 this 代表对本类对象的引用,指向本类对象;而 super 代表对父类对象的引用,指向父类对象;所以, **this 和 super 是属于对象范畴的东西,而静态方法是属于类范畴的东西**。
## 参考
@@ -111,15 +111,15 @@ public class Sub extends Super {
1. 修饰成员变量和成员方法
2. 静态代码块
3. 修饰类(只能修饰内部类)
-4. 静态导包(用来导入类中的静态资源,1.5之后的新特性)
+4. 静态导包(用来导入类中的静态资源,1.5 之后的新特性)
### 修饰成员变量和成员方法(常用)
-被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。
+被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被 static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。
-方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
+方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
- HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。
+HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。
调用格式:
@@ -170,44 +170,40 @@ public class StaticDemo {
}
```
-
### 静态代码块
静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块 —> 非静态代码块 —> 构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
-静态代码块的格式是
+静态代码块的格式是
```
-static {
-语句体;
+static {
+语句体;
}
```
+一个类中的静态代码块可以有多个,位置可以随便放,它不在任何的方法体内,JVM 加载类时会执行这些静态的代码块,如果静态代码块有多个,JVM 将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。
-一个类中的静态代码块可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果静态代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。
-
-
+
静态代码块对于定义在它之后的静态变量,可以赋值,但是不能访问.
-
### 静态内部类
静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:
1. 它的创建是不需要依赖外围类的创建。
-2. 它不能使用任何外围类的非static成员变量和方法。
-
+2. 它不能使用任何外围类的非 static 成员变量和方法。
Example(静态内部类实现单例模式)
```java
public class Singleton {
-
+
//声明为 private 避免调用默认构造方法创建对象
private Singleton() {
}
-
+
// 声明为 private 表明静态内部该类只能在该 Singleton 类中被访问
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
@@ -219,13 +215,13 @@ public class Singleton {
}
```
-当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 `getUniqueInstance() `方法从而触发 `SingletonHolder.INSTANCE` 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。
+当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 `getUniqueInstance()`方法从而触发 `SingletonHolder.INSTANCE` 时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。
这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。
### 静态导包
-格式为:import static
+格式为:import static
这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法
@@ -234,12 +230,12 @@ public class Singleton {
//将Math中的所有静态资源导入,这时候可以直接使用里面的静态方法,而不用通过类名进行调用
//如果只想导入单一某个静态方法,只需要将*换成对应的方法名即可
-
+
import static java.lang.Math.*;//换成import static java.lang.Math.max;具有一样的效果
-
+
public class Demo {
public static void main(String[] args) {
-
+
int max = max(1,2);
System.out.println(max);
}
@@ -247,8 +243,7 @@ public class Demo {
```
-
-## 补充内容
+## 补充内容
### 静态方法与非静态方法
@@ -259,13 +254,13 @@ Example
```java
class Foo {
int i;
- public Foo(int i) {
+ public Foo(int i) {
this.i = i;
}
public static String method1() {
return "An example string that doesn't depend on i (an instance variable)";
-
+
}
public int method2() {
@@ -274,26 +269,28 @@ class Foo {
}
```
+
你可以像这样调用静态方法:`Foo.method1()`。 如果您尝试使用这种方法调用 method2 将失败。 但这样可行
-``` java
+
+```java
Foo bar = new Foo(1);
bar.method2();
```
总结:
-- 在外部调用静态方法时,可以使用”类名.方法名”的方式,也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
-- 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制
+- 在外部调用静态方法时,可以使用”类名.方法名”的方式,也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
+- 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),而不允许访问实例成员变量和实例方法;实例方法则无此限制
### `static{}`静态代码块与`{}`非静态代码块(构造代码块)
-相同点: 都是在JVM加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些static变量进行赋值。
+相同点: 都是在 JVM 加载类时且在构造方法执行之前执行,在类中都可以定义多个,定义多个时按定义的顺序执行,一般在代码块中对一些 static 变量进行赋值。
-不同点: 静态代码块在非静态代码块之前执行(静态代码块 -> 非静态代码块 -> 构造方法)。静态代码块只在第一次new执行一次,之后不再执行,而非静态代码块在每new一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。
+不同点: 静态代码块在非静态代码块之前执行(静态代码块 -> 非静态代码块 -> 构造方法)。静态代码块只在第一次 new 执行一次,之后不再执行,而非静态代码块在每 new 一次就执行一次。 非静态代码块可在普通方法中定义(不过作用不大);而静态代码块不行。
-> **🐛 修正(参见: [issue #677](https://github.com/Snailclimb/JavaGuide/issues/677))** :静态代码块可能在第一次new的时候执行,但不一定只在第一次new的时候执行。比如通过 `Class.forName("ClassDemo")`创建 Class 对象的时候也会执行。
+> **🐛 修正(参见: [issue #677](https://github.com/Snailclimb/JavaGuide/issues/677))** :静态代码块可能在第一次 new 对象的时候执行,但不一定只在第一次 new 的时候执行。比如通过 `Class.forName("ClassDemo")`创建 Class 对象的时候也会执行,即 new 或者 `Class.forName("ClassDemo")` 都会执行静态代码块。
-一般情况下,如果有些代码比如一些项目最常用的变量或对象必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的。如果我们想要设计不需要创建对象就可以调用类中的方法,例如:Arrays类,Character类,String类等,就需要使用静态方法, 两者的区别是 静态代码块是自动执行的而静态方法是被调用的时候才执行的.
+一般情况下,如果有些代码比如一些项目最常用的变量或对象必须在项目启动的时候就执行的时候,需要使用静态代码块,这种代码是主动执行的。如果我们想要设计不需要创建对象就可以调用类中的方法,例如:`Arrays` 类,`Character` 类,`String` 类等,就需要使用静态方法, 两者的区别是 静态代码块是自动执行的而静态方法是被调用的时候才执行的.
Example:
@@ -346,8 +343,7 @@ public class Test {
静态代码块!--非静态代码块!--默认构造方法!--
```
-
-非静态代码块与构造函数的区别是: 非静态代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。
+非静态代码块与构造函数的区别是: 非静态代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,因为构造函数是可以多个的,运行哪个构造函数就会建立什么样的对象,但无论建立哪个对象,都会先执行相同的构造代码块。也就是说,构造代码块中定义的是不同对象共性的初始化内容。
### 参考
diff --git a/docs/java/basis/代理模式详解.md b/docs/java/basis/代理模式详解.md
index 8323cedc..8106d305 100644
--- a/docs/java/basis/代理模式详解.md
+++ b/docs/java/basis/代理模式详解.md
@@ -120,15 +120,15 @@ after method send()
**从 JVM 角度来说,动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。**
-说到动态代理,Spring AOP、RPC 框架应该是两个不得不的提的,它们的实现都依赖了动态代理。
+说到动态代理,Spring AOP、RPC 框架应该是两个不得不提的,它们的实现都依赖了动态代理。
-**动态代理在我们日常开发中使用的相对较小,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。**
+**动态代理在我们日常开发中使用的相对较少,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。**
就 Java 来说,动态代理的实现方式有很多种,比如 **JDK 动态代理**、**CGLIB 动态代理**等等。
[guide-rpc-framework](https://github.com/Snailclimb/guide-rpc-framework) 使用的是 JDK 动态代理,我们先来看看 JDK 动态代理的使用。
-另外,虽然 [guide-rpc-framework](https://github.com/Snailclimb/guide-rpc-framework) 没有用到 **CGLIB 动态代理 ,我们这里还是简单介绍一下其使用以及和**JDK 动态代理的对比。
+另外,虽然 [guide-rpc-framework](https://github.com/Snailclimb/guide-rpc-framework) 没有用到 **CGLIB 动态代理** ,我们这里还是简单介绍一下其使用以及和**JDK 动态代理**的对比。
### 3.1. JDK 动态代理机制
@@ -154,7 +154,7 @@ after method send()
2. **interfaces** : 被代理类实现的一些接口;
3. **h** : 实现了 `InvocationHandler` 接口的对象;
-要实现动态代理的话,还必须需要实现`InvocationHandler` 来自定义处理逻辑。 当我们的动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现`InvocationHandler` 接口类的 `invoke` 方法来调用。
+要实现动态代理的话,还必须需要实现`InvocationHandler` 来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现`InvocationHandler` 接口类的 `invoke` 方法来调用。
```java
public interface InvocationHandler {
@@ -298,7 +298,7 @@ extends Callback{
1. **obj** :被代理的对象(需要增强的对象)
2. **method** :被拦截的方法(需要增强的方法)
3. **args** :方法入参
-4. **methodProxy** :用于调用原始方法
+4. **proxy** :用于调用原始方法
你可以通过 `Enhancer`类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 `MethodInterceptor` 中的 `intercept` 方法。
@@ -348,7 +348,7 @@ public class DebugMethodInterceptor implements MethodInterceptor {
/**
- * @param o 被代理的对象(需要增强的对象)
+ * @param o 代理对象(增强的对象)
* @param method 被拦截的方法(需要增强的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
@@ -405,7 +405,7 @@ after method send
### 3.3. JDK 动态代理和 CGLIB 动态代理对比
-1. **JDK 动态代理只能只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。** 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
+1. **JDK 动态代理只能代理实现了接口的类或者直接代理接口,而 CGLIB 可以代理未实现任何接口的类。** 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
2. 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
## 4. 静态代理和动态代理的对比
diff --git a/docs/java/basis/反射机制.md b/docs/java/basis/反射机制.md
index 0e2389b5..d6fe4263 100644
--- a/docs/java/basis/反射机制.md
+++ b/docs/java/basis/反射机制.md
@@ -80,7 +80,7 @@ Class alunbarClass2 = o.getClass();
**4.通过类加载器`xxxClassLoader.loadClass()`传入类路径获取:**
```java
-class clazz = ClassLoader.LoadClass("cn.javaguide.TargetObject");
+Class clazz = ClassLoader.loadClass("cn.javaguide.TargetObject");
```
通过类加载器获取 Class 对象不会进行初始化,意味着不进行包括初始化等一些列步骤,静态块和静态对象不会得到执行
@@ -173,4 +173,4 @@ value is JavaGuide
```java
Class> tagetClass = Class.forName("cn.javaguide.TargetObject");
-```
\ No newline at end of file
+```
diff --git a/docs/java/basis/用好Java中的枚举真的没有那么简单.md b/docs/java/basis/用好Java中的枚举真的没有那么简单.md
index 23e47ef6..c17fcba9 100644
--- a/docs/java/basis/用好Java中的枚举真的没有那么简单.md
+++ b/docs/java/basis/用好Java中的枚举真的没有那么简单.md
@@ -12,7 +12,7 @@
enum关键字在 java5 中引入,表示一种特殊类型的类,其总是继承java.lang.Enum类,更多内容可以自行查看其[官方文档](https://docs.oracle.com/javase/6/docs/api/java/lang/Enum.html)。
-枚举在很多时候会和常量拿来对比,可能因为本身我们大量实际使用枚举的地方就是为了替代常量。那么这种方式由什么优势呢?
+枚举在很多时候会和常量拿来对比,可能因为本身我们大量实际使用枚举的地方就是为了替代常量。那么这种方式有什么优势呢?
**以这种方式定义的常量使代码更具可读性,允许进行编译时检查,预先记录可接受值的列表,并避免由于传入无效值而引起的意外行为。**
@@ -219,7 +219,7 @@ public class Pizza {
}
```
- 下面的测试演示了展示了 `EnumSet` 在某些场景下的强大功能:
+ 下面的测试展示了 `EnumSet` 在某些场景下的强大功能:
```java
@Test
@@ -272,7 +272,7 @@ while (iterator.hasNext()) {
}
```
- 下面的测试演示了展示了 `EnumMap` 在某些场景下的强大功能:
+ 下面的测试展示了 `EnumMap` 在某些场景下的强大功能:
```java
@Test
@@ -308,7 +308,7 @@ public void givenPizaOrders_whenGroupByStatusCalled_thenCorrectlyGrouped() {
通常,使用类实现 Singleton 模式并非易事,枚举提供了一种实现单例的简便方法。
-《Effective Java 》和《Java与模式》都非常推荐这种方式,使用这种方式方式实现枚举可以有什么好处呢?
+《Effective Java 》和《Java与模式》都非常推荐这种方式,使用这种方式实现枚举可以有什么好处呢?
《Effective Java》
@@ -401,9 +401,9 @@ public void givenPizaOrder_whenDelivered_thenPizzaGetsDeliveredAndStatusChanges(
## 8. Java 8 与枚举
-Pizza 类可以用Java 8重写,您可以看到方法 lambda 和Stream API如何使 `getAllUndeliveredPizzas()`和`groupPizzaByStatus()`方法变得如此简洁:
+Pizza 类可以用Java 8重写,您可以看到方法 lambda 和Stream API如何使 `getAllUndeliveredPizzas()`和`groupPizzaByStatus()`方法变得如此简洁:
-`getAllUndeliveredPizzas()`:
+`getAllUndeliveredPizzas()`:
```java
public static List getAllUndeliveredPizzas(List input) {
@@ -413,7 +413,7 @@ public static List getAllUndeliveredPizzas(List input) {
}
```
-`groupPizzaByStatus()` :
+`groupPizzaByStatus()` :
```java
public static EnumMap>
@@ -554,4 +554,4 @@ Output:
PinType{code=100001, message='忘记密码使用'}
```
-这样的话,在实际使用起来就会非常灵活方便!
\ No newline at end of file
+这样的话,在实际使用起来就会非常灵活方便!
diff --git a/docs/java/collection/ArrayList源码+扩容机制分析.md b/docs/java/collection/ArrayList源码+扩容机制分析.md
index a30ba17e..79ae722c 100644
--- a/docs/java/collection/ArrayList源码+扩容机制分析.md
+++ b/docs/java/collection/ArrayList源码+扩容机制分析.md
@@ -14,7 +14,7 @@ public class ArrayList extends AbstractList
- `RandomAccess` 是一个标志接口,表明实现这个这个接口的 List 集合是支持**快速随机访问**的。在 `ArrayList` 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
- `ArrayList` 实现了 **`Cloneable` 接口** ,即覆盖了函数`clone()`,能被克隆。
-- `ArrayList` 实现了 `java.io.Serializable `接口,这意味着`ArrayList`支持序列化,能通过序列化去传输。
+- `ArrayList` 实现了 `java.io.Serializable`接口,这意味着`ArrayList`支持序列化,能通过序列化去传输。
### 1.1. Arraylist 和 Vector 的区别?
@@ -594,9 +594,9 @@ public class ArrayList extends AbstractList
```
-细心的同学一定会发现 :**以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。** 下面在我们分析 ArrayList 扩容时会讲到这一点内容!
+细心的同学一定会发现 :**以无参数构造方法创建 `ArrayList` 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。** 下面在我们分析 ArrayList 扩容时会讲到这一点内容!
-> 补充:JDK7 new无参构造的ArrayList对象时,直接创建了长度是10的Object[]数组elementData 。jdk7中的ArrayList的对象的创建**类似于单例的饿汉式**,而jdk8中的ArrayList的对象的创建**类似于单例的懒汉式**。JDK8的内存优化也值得我们在平时开发中学习。
+> 补充:JDK6 new 无参构造的 `ArrayList` 对象时,直接创建了长度是 10 的 `Object[]` 数组 elementData 。
### 3.2. 一步一步分析 ArrayList 扩容机制
@@ -733,6 +733,25 @@ public class ArrayList extends AbstractList
#### 3.3.1. `System.arraycopy()` 方法
+源码:
+
+```java
+ // 我们发现 arraycopy 是一个 native 方法,接下来我们解释一下各个参数的具体意义
+ /**
+ * 复制数组
+ * @param src 源数组
+ * @param srcPos 源数组中的起始位置
+ * @param dest 目标数组
+ * @param destPos 目标数组中的起始位置
+ * @param length 要复制的数组元素的数量
+ */
+ public static native void arraycopy(Object src, int srcPos,
+ Object dest, int destPos,
+ int length);
+```
+
+场景:
+
```java
/**
* 在此列表中的指定位置插入指定的元素。
@@ -781,6 +800,21 @@ public class ArraycopyTest {
#### 3.3.2. `Arrays.copyOf()`方法
+源码:
+
+```java
+ public static int[] copyOf(int[] original, int newLength) {
+ // 申请一个新的数组
+ int[] copy = new int[newLength];
+ // 调用System.arraycopy,将源数组中的数据进行拷贝,并返回新的数组
+ System.arraycopy(original, 0, copy, 0,
+ Math.min(original.length, newLength));
+ return copy;
+ }
+```
+
+场景:
+
```java
/**
以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。
diff --git a/docs/java/collection/ConcurrentHashMap源码+底层数据结构分析.md b/docs/java/collection/ConcurrentHashMap源码+底层数据结构分析.md
index ebabf4a4..89f9d24e 100644
--- a/docs/java/collection/ConcurrentHashMap源码+底层数据结构分析.md
+++ b/docs/java/collection/ConcurrentHashMap源码+底层数据结构分析.md
@@ -6,9 +6,11 @@
### 1. 存储结构
+> 下图存在一个笔误 Segmeng -> Segment
+

-Java 7 中 ConcurrentHashMap 的存储结构如上图,ConcurrnetHashMap 由很多个 Segment 组合,而每一个 Segment 是一个类似于 HashMap 的结构,所以每一个 HashMap 的内部可以进行扩容。但是 Segment 的个数一旦**初始化就不能改变**,默认 Segment 的个数是 16 个,你也可以认为 ConcurrentHashMap 默认支持最多 16 个线程并发。
+Java 7 中 `ConcurrentHashMap` 的存储结构如上图,`ConcurrnetHashMap` 由很多个 `Segment` 组合,而每一个 `Segment` 是一个类似于 HashMap 的结构,所以每一个 `HashMap` 的内部可以进行扩容。但是 `Segment` 的个数一旦**初始化就不能改变**,默认 `Segment` 的个数是 16 个,你也可以认为 `ConcurrentHashMap` 默认支持最多 16 个线程并发。
### 2. 初始化
@@ -91,7 +93,7 @@ public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLe
总结一下在 Java 7 中 ConcurrnetHashMap 的初始化逻辑。
1. 必要参数校验。
-2. 校验并发级别 concurrencyLevel 大小,如果大于最大值,重置为最大值。无惨构造**默认值是 16.**
+2. 校验并发级别 concurrencyLevel 大小,如果大于最大值,重置为最大值。无参构造**默认值是 16.**
3. 寻找并发级别 concurrencyLevel 之上最近的 **2 的幂次方**值,作为初始化容量大小,**默认是 16**。
4. 记录 segmentShift 偏移量,这个值为【容量 = 2 的N次方】中的 N,在后面 Put 时计算位置时会用到。**默认是 32 - sshift = 28**.
5. 记录 segmentMask,默认是 ssize - 1 = 16 -1 = 15.
diff --git a/docs/java/collection/HashMap(JDK1.8)源码+底层数据结构分析.md b/docs/java/collection/HashMap(JDK1.8)源码+底层数据结构分析.md
index 977fdf78..e78ba116 100644
--- a/docs/java/collection/HashMap(JDK1.8)源码+底层数据结构分析.md
+++ b/docs/java/collection/HashMap(JDK1.8)源码+底层数据结构分析.md
@@ -1,4 +1,3 @@
-
@@ -21,14 +20,13 @@
## HashMap 简介
-HashMap 主要用来存放键值对,它基于哈希表的 Map 接口实现,是常用的 Java 集合之一。
+HashMap 主要用来存放键值对,它基于哈希表的 Map 接口实现,是常用的 Java 集合之一,是非线程安全的。
-JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。
+ `HashMap` 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个
-JDK1.8 之后 HashMap 的组成多了红黑树,在满足下面两个条件之后,会执行链表转红黑树操作,以此来加快搜索速度。
+JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。 JDK1.8 以后的 `HashMap` 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。
-- 链表长度大于阈值(默认为 8)
-- HashMap 数组长度超过 64
+`HashMap` 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。并且, `HashMap` 总是使用 2 的幂作为哈希表的大小。
## 底层数据结构分析
@@ -574,5 +572,4 @@ public class HashMapDemo {
}
}
-
```
\ No newline at end of file
diff --git a/docs/java/collection/Java集合使用注意事项总结.md b/docs/java/collection/Java集合使用注意事项总结.md
new file mode 100644
index 00000000..3c04c7af
--- /dev/null
+++ b/docs/java/collection/Java集合使用注意事项总结.md
@@ -0,0 +1,432 @@
+这篇文章我根据《阿里巴巴 Java 开发手册》总结了关于集合使用常见的注意事项以及其具体原理。
+
+强烈建议小伙伴们多多阅读几遍,避免自己写代码的时候出现这些低级的问题。
+
+## 集合判空
+
+《阿里巴巴 Java 开发手册》的描述如下:
+
+> **判断所有集合内部的元素是否为空,使用 `isEmpty()` 方法,而不是 `size()==0` 的方式。**
+
+这是因为 `isEmpty()` 方法的可读性更好,并且时间复杂度为 O(1)。
+
+绝大部分我们使用的集合的 `size()` 方法的时间复杂度也是 O(1),不过,也有很多复杂度不是 O(1) 的,比如 `java.util.concurrent` 包下的某些集合(`ConcurrentLinkedQueue` 、`ConcurrentHashMap`...)。
+
+下面是 `ConcurrentHashMap` 的 `size()` 方法和 `isEmpty()` 方法的源码。
+
+```java
+public int size() {
+ long n = sumCount();
+ return ((n < 0L) ? 0 :
+ (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
+ (int)n);
+}
+final long sumCount() {
+ CounterCell[] as = counterCells; CounterCell a;
+ long sum = baseCount;
+ if (as != null) {
+ for (int i = 0; i < as.length; ++i) {
+ if ((a = as[i]) != null)
+ sum += a.value;
+ }
+ }
+ return sum;
+}
+public boolean isEmpty() {
+ return sumCount() <= 0L; // ignore transient negative values
+}
+```
+
+## 集合转 Map
+
+《阿里巴巴 Java 开发手册》的描述如下:
+
+> **在使用 `java.util.stream.Collectors` 类的 `toMap()` 方法转为 `Map` 集合时,一定要注意当 value 为 null 时会抛 NPE 异常。**
+
+```java
+class Person {
+ private String name;
+ private String phoneNumber;
+ // getters and setters
+}
+
+List bookList = new ArrayList<>();
+bookList.add(new Person("jack","18163138123"));
+bookList.add(new Person("martin",null));
+// 空指针异常
+bookList.stream().collect(Collectors.toMap(Person::getName, Person::getPhoneNumber));
+```
+
+下面我们来解释一下原因。
+
+首先,我们来看 `java.util.stream.Collectors` 类的 `toMap()` 方法 ,可以看到其内部调用了 `Map` 接口的 `merge()` 方法。
+
+```java
+public static >
+Collector toMap(Function super T, ? extends K> keyMapper,
+ Function super T, ? extends U> valueMapper,
+ BinaryOperator mergeFunction,
+ Supplier mapSupplier) {
+ BiConsumer accumulator
+ = (map, element) -> map.merge(keyMapper.apply(element),
+ valueMapper.apply(element), mergeFunction);
+ return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
+}
+```
+
+`Map` 接口的 `merge()` 方法如下,这个方法是接口中的默认实现。
+
+> 如果你还不了解 Java 8 新特性的话,请看这篇文章:[《Java8 新特性总结》](https://mp.weixin.qq.com/s/ojyl7B6PiHaTWADqmUq2rw) 。
+
+```java
+default V merge(K key, V value,
+ BiFunction super V, ? super V, ? extends V> remappingFunction) {
+ Objects.requireNonNull(remappingFunction);
+ Objects.requireNonNull(value);
+ V oldValue = get(key);
+ V newValue = (oldValue == null) ? value :
+ remappingFunction.apply(oldValue, value);
+ if(newValue == null) {
+ remove(key);
+ } else {
+ put(key, newValue);
+ }
+ return newValue;
+}
+```
+
+`merge()` 方法会先调用 `Objects.requireNonNull()` 方法判断 value 是否为空。
+
+```java
+public static T requireNonNull(T obj) {
+ if (obj == null)
+ throw new NullPointerException();
+ return obj;
+}
+```
+
+## 集合遍历
+
+《阿里巴巴 Java 开发手册》的描述如下:
+
+> **不要在 foreach 循环里进行元素的 `remove/add` 操作。remove 元素请使用 `Iterator` 方式,如果并发操作,需要对 `Iterator` 对象加锁。**
+
+通过反编译你会发现 foreach 语法糖底层其实还是依赖 `Iterator` 。不过, `remove/add` 操作直接调用的是集合自己的方法,而不是 `Iterator` 的 `remove/add`方法
+
+这就导致 `Iterator` 莫名其妙地发现自己有元素被 `remove/add` ,然后,它就会抛出一个 `ConcurrentModificationException` 来提示用户发生了并发修改异常。这就是单线程状态下产生的 **fail-fast 机制**。
+
+> **fail-fast 机制** :多个线程对 fail-fast 集合进行修改的时候,可能会抛出`ConcurrentModificationException`。 即使是单线程下也有可能会出现这种情况,上面已经提到过。
+
+Java8 开始,可以使用 `Collection#removeIf()`方法删除满足特定条件的元素,如
+
+```java
+List list = new ArrayList<>();
+for (int i = 1; i <= 10; ++i) {
+ list.add(i);
+}
+list.removeIf(filter -> filter % 2 == 0); /* 删除list中的所有偶数 */
+System.out.println(list); /* [1, 3, 5, 7, 9] */
+```
+
+除了上面介绍的直接使用 `Iterator` 进行遍历操作之外,你还可以:
+
+- 使用普通的 for 循环
+- 使用 fail-safe 的集合类。`java.util`包下面的所有的集合类都是 fail-fast 的,而`java.util.concurrent`包下面的所有的类都是 fail-safe 的。
+- ......
+
+## 集合去重
+
+《阿里巴巴 Java 开发手册》的描述如下:
+
+> **可以利用 `Set` 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 `List` 的 `contains()` 进行遍历去重或者判断包含操作。**
+
+这里我们以 `HashSet` 和 `ArrayList` 为例说明。
+
+```java
+// Set 去重代码示例
+public static Set removeDuplicateBySet(List data) {
+
+ if (CollectionUtils.isEmpty(data)) {
+ return new HashSet<>();
+ }
+ return new HashSet<>(data);
+}
+
+// List 去重代码示例
+public static List removeDuplicateByList(List data) {
+
+ if (CollectionUtils.isEmpty(data)) {
+ return new ArrayList<>();
+
+ }
+ List result = new ArrayList<>(data.size());
+ for (T current : data) {
+ if (!result.contains(current)) {
+ result.add(current);
+ }
+ }
+ return result;
+}
+
+```
+
+两者的核心差别在于 `contains()` 方法的实现。
+
+`HashSet` 的 `contains()` 方法底部依赖的 `HashMap` 的 `containsKey()` 方法,时间复杂度接近于 O(1)(没有出现哈希冲突的时候为 O(1))。
+
+```java
+private transient HashMap map;
+public boolean contains(Object o) {
+ return map.containsKey(o);
+}
+```
+
+我们有 N 个元素插入进 Set 中,那时间复杂度就接近是 O (n)。
+
+`ArrayList` 的 `contains()` 方法是通过遍历所有元素的方法来做的,时间复杂度接近是 O(n)。
+
+```java
+public boolean contains(Object o) {
+ return indexOf(o) >= 0;
+}
+public int indexOf(Object o) {
+ if (o == null) {
+ for (int i = 0; i < size; i++)
+ if (elementData[i]==null)
+ return i;
+ } else {
+ for (int i = 0; i < size; i++)
+ if (o.equals(elementData[i]))
+ return i;
+ }
+ return -1;
+}
+
+```
+
+我们的 `List` 有 N 个元素,那时间复杂度就接近是 O (n^2)。
+
+## 集合转数组
+
+《阿里巴巴 Java 开发手册》的描述如下:
+
+> **使用集合转数组的方法,必须使用集合的 `toArray(T[] array)`,传入的是类型完全一致、长度为 0 的空数组。**
+
+`toArray(T[] array)` 方法的参数是一个泛型数组,如果 `toArray` 方法中没有传递任何参数的话返回的是 `Object`类 型数组。
+
+```java
+String [] s= new String[]{
+ "dog", "lazy", "a", "over", "jumps", "fox", "brown", "quick", "A"
+};
+List list = Arrays.asList(s);
+Collections.reverse(list);
+//没有指定类型的话会报错
+s=list.toArray(new String[0]);
+```
+
+由于 JVM 优化,`new String[0]`作为`Collection.toArray()`方法的参数现在使用更好,`new String[0]`就是起一个模板的作用,指定了返回数组的类型,0 是为了节省空间,因为它只是为了说明返回的类型。详见:
+
+## 数组转集合
+
+《阿里巴巴 Java 开发手册》的描述如下:
+
+> **使用工具类 `Arrays.asList()` 把数组转换成集合时,不能使用其修改集合相关的方法, 它的 `add/remove/clear` 方法会抛出 `UnsupportedOperationException` 异常。**
+
+我在之前的一个项目中就遇到一个类似的坑。
+
+`Arrays.asList()`在平时开发中还是比较常见的,我们可以使用它将一个数组转换为一个 `List` 集合。
+
+```java
+String[] myArray = {"Apple", "Banana", "Orange"};
+List myList = Arrays.asList(myArray);
+//上面两个语句等价于下面一条语句
+List myList = Arrays.asList("Apple","Banana", "Orange");
+```
+
+JDK 源码对于这个方法的说明:
+
+```java
+/**
+ *返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁,
+ * 与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。
+ */
+public static List asList(T... a) {
+ return new ArrayList<>(a);
+}
+```
+
+下面我们来总结一下使用注意事项。
+
+**1、`Arrays.asList()`是泛型方法,传递的数组必须是对象数组,而不是基本类型。**
+
+```java
+int[] myArray = {1, 2, 3};
+List myList = Arrays.asList(myArray);
+System.out.println(myList.size());//1
+System.out.println(myList.get(0));//数组地址值
+System.out.println(myList.get(1));//报错:ArrayIndexOutOfBoundsException
+int[] array = (int[]) myList.get(0);
+System.out.println(array[0]);//1
+```
+
+当传入一个原生数据类型数组时,`Arrays.asList()` 的真正得到的参数就不是数组中的元素,而是数组对象本身!此时 `List` 的唯一元素就是这个数组,这也就解释了上面的代码。
+
+我们使用包装类型数组就可以解决这个问题。
+
+```java
+Integer[] myArray = {1, 2, 3};
+```
+
+**2、使用集合的修改方法: `add()`、`remove()`、`clear()`会抛出异常。**
+
+```java
+List myList = Arrays.asList(1, 2, 3);
+myList.add(4);//运行时报错:UnsupportedOperationException
+myList.remove(1);//运行时报错:UnsupportedOperationException
+myList.clear();//运行时报错:UnsupportedOperationException
+```
+
+`Arrays.asList()` 方法返回的并不是 `java.util.ArrayList` ,而是 `java.util.Arrays` 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。
+
+```java
+List myList = Arrays.asList(1, 2, 3);
+System.out.println(myList.getClass());//class java.util.Arrays$ArrayList
+```
+
+下图是 `java.util.Arrays$ArrayList` 的简易源码,我们可以看到这个类重写的方法有哪些。
+
+```java
+ private static class ArrayList extends AbstractList
+ implements RandomAccess, java.io.Serializable
+ {
+ ...
+
+ @Override
+ public E get(int index) {
+ ...
+ }
+
+ @Override
+ public E set(int index, E element) {
+ ...
+ }
+
+ @Override
+ public int indexOf(Object o) {
+ ...
+ }
+
+ @Override
+ public boolean contains(Object o) {
+ ...
+ }
+
+ @Override
+ public void forEach(Consumer super E> action) {
+ ...
+ }
+
+ @Override
+ public void replaceAll(UnaryOperator operator) {
+ ...
+ }
+
+ @Override
+ public void sort(Comparator super E> c) {
+ ...
+ }
+ }
+```
+
+我们再看一下`java.util.AbstractList`的 `add/remove/clear` 方法就知道为什么会抛出 `UnsupportedOperationException` 了。
+
+```java
+public E remove(int index) {
+ throw new UnsupportedOperationException();
+}
+public boolean add(E e) {
+ add(size(), e);
+ return true;
+}
+public void add(int index, E element) {
+ throw new UnsupportedOperationException();
+}
+
+public void clear() {
+ removeRange(0, size());
+}
+protected void removeRange(int fromIndex, int toIndex) {
+ ListIterator it = listIterator(fromIndex);
+ for (int i=0, n=toIndex-fromIndex; i List arrayToList(final T[] array) {
+ final List l = new ArrayList(array.length);
+
+ for (final T s : array) {
+ l.add(s);
+ }
+ return l;
+}
+
+
+Integer [] myArray = { 1, 2, 3 };
+System.out.println(arrayToList(myArray).getClass());//class java.util.ArrayList
+```
+
+2、最简便的方法
+
+```java
+List list = new ArrayList<>(Arrays.asList("a", "b", "c"))
+```
+
+3、使用 Java8 的 `Stream`(推荐)
+
+```java
+Integer [] myArray = { 1, 2, 3 };
+List myList = Arrays.stream(myArray).collect(Collectors.toList());
+//基本类型也可以实现转换(依赖boxed的装箱操作)
+int [] myArray2 = { 1, 2, 3 };
+List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
+```
+
+4、使用 Guava
+
+对于不可变集合,你可以使用[`ImmutableList`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java)类及其[`of()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L101)与[`copyOf()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/ImmutableList.java#L225)工厂方法:(参数不能为空)
+
+```java
+List il = ImmutableList.of("string", "elements"); // from varargs
+List il = ImmutableList.copyOf(aStringArray); // from array
+```
+
+对于可变集合,你可以使用[`Lists`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java)类及其[`newArrayList()`](https://github.com/google/guava/blob/master/guava/src/com/google/common/collect/Lists.java#L87)工厂方法:
+
+```java
+List l1 = Lists.newArrayList(anotherListOrCollection); // from collection
+List l2 = Lists.newArrayList(aStringArray); // from array
+List l3 = Lists.newArrayList("or", "string", "elements"); // from varargs
+```
+
+5、使用 Apache Commons Collections
+
+```java
+List list = new ArrayList();
+CollectionUtils.addAll(list, str);
+```
+
+6、 使用 Java9 的 `List.of()`方法
+
+```java
+Integer[] array = {1, 2, 3};
+List list = List.of(array);
+```
\ No newline at end of file
diff --git a/docs/java/collection/Java集合框架常见面试题.md b/docs/java/collection/Java集合框架常见面试题.md
index 9cc5b54f..41c72de1 100644
--- a/docs/java/collection/Java集合框架常见面试题.md
+++ b/docs/java/collection/Java集合框架常见面试题.md
@@ -3,11 +3,12 @@
- [1. 剖析面试最常见问题之 Java 集合框架](#1-剖析面试最常见问题之-java-集合框架)
- [1.1. 集合概述](#11-集合概述)
- [1.1.1. Java 集合概览](#111-java-集合概览)
- - [1.1.2. 说说 List,Set,Map 三者的区别?](#112-说说-listsetmap-三者的区别)
+ - [1.1.2. 说说 List, Set, Queue, Map 四者的区别?](#112-说说-list-set-queue-map-四者的区别)
- [1.1.3. 集合框架底层数据结构总结](#113-集合框架底层数据结构总结)
- [1.1.3.1. List](#1131-list)
- [1.1.3.2. Set](#1132-set)
- - [1.1.3.3. Map](#1133-map)
+ - [1.1.3.3 Queue](#1133-queue)
+ - [1.1.3.4. Map](#1134-map)
- [1.1.4. 如何选用集合?](#114-如何选用集合)
- [1.1.5. 为什么要使用集合?](#115-为什么要使用集合)
- [1.2. Collection 子接口之 List](#12-collection-子接口之-list)
@@ -22,25 +23,29 @@
- [1.3.1.2. 重写 compareTo 方法实现按年龄来排序](#1312-重写-compareto-方法实现按年龄来排序)
- [1.3.2. 无序性和不可重复性的含义是什么](#132-无序性和不可重复性的含义是什么)
- [1.3.3. 比较 HashSet、LinkedHashSet 和 TreeSet 三者的异同](#133-比较-hashsetlinkedhashset-和-treeset-三者的异同)
- - [1.4. Map 接口](#14-map-接口)
- - [1.4.1. HashMap 和 Hashtable 的区别](#141-hashmap-和-hashtable-的区别)
- - [1.4.2. HashMap 和 HashSet 区别](#142-hashmap-和-hashset-区别)
- - [1.4.3. HashMap 和 TreeMap 区别](#143-hashmap-和-treemap-区别)
- - [1.4.4. HashSet 如何检查重复](#144-hashset-如何检查重复)
- - [1.4.5. HashMap 的底层实现](#145-hashmap-的底层实现)
- - [1.4.5.1. JDK1.8 之前](#1451-jdk18-之前)
- - [1.4.5.2. JDK1.8 之后](#1452-jdk18-之后)
- - [1.4.6. HashMap 的长度为什么是 2 的幂次方](#146-hashmap-的长度为什么是-2-的幂次方)
- - [1.4.7. HashMap 多线程操作导致死循环问题](#147-hashmap-多线程操作导致死循环问题)
- - [1.4.8. HashMap 有哪几种常见的遍历方式?](#148-hashmap-有哪几种常见的遍历方式)
- - [1.4.9. ConcurrentHashMap 和 Hashtable 的区别](#149-concurrenthashmap-和-hashtable-的区别)
- - [1.4.10. ConcurrentHashMap 线程安全的具体实现方式/底层具体实现](#1410-concurrenthashmap-线程安全的具体实现方式底层具体实现)
- - [1.4.10.1. JDK1.7(上面有示意图)](#14101-jdk17上面有示意图)
- - [1.4.10.2. JDK1.8 (上面有示意图)](#14102-jdk18-上面有示意图)
- - [1.5. Collections 工具类](#15-collections-工具类)
- - [1.5.1. 排序操作](#151-排序操作)
- - [1.5.2. 查找,替换操作](#152-查找替换操作)
- - [1.5.3. 同步控制](#153-同步控制)
+ - [1.4 Collection 子接口之 Queue](#14-collection-子接口之-queue)
+ - [1.4.1 Queue 与 Deque 的区别](#141-queue-与-deque-的区别)
+ - [1.4.2 ArrayDeque 与 LinkedList 的区别](#142-arraydeque-与-linkedlist-的区别)
+ - [1.4.3 说一说 PriorityQueue](#143-说一说-priorityqueue)
+ - [1.5. Map 接口](#15-map-接口)
+ - [1.5.1. HashMap 和 Hashtable 的区别](#151-hashmap-和-hashtable-的区别)
+ - [1.5.2. HashMap 和 HashSet 区别](#152-hashmap-和-hashset-区别)
+ - [1.5.3. HashMap 和 TreeMap 区别](#153-hashmap-和-treemap-区别)
+ - [1.5.4. HashSet 如何检查重复](#154-hashset-如何检查重复)
+ - [1.5.5. HashMap 的底层实现](#155-hashmap-的底层实现)
+ - [1.5.5.1. JDK1.8 之前](#1551-jdk18-之前)
+ - [1.5.5.2. JDK1.8 之后](#1552-jdk18-之后)
+ - [1.5.6. HashMap 的长度为什么是 2 的幂次方](#156-hashmap-的长度为什么是-2-的幂次方)
+ - [1.5.7. HashMap 多线程操作导致死循环问题](#157-hashmap-多线程操作导致死循环问题)
+ - [1.5.8. HashMap 有哪几种常见的遍历方式?](#158-hashmap-有哪几种常见的遍历方式)
+ - [1.5.9. ConcurrentHashMap 和 Hashtable 的区别](#159-concurrenthashmap-和-hashtable-的区别)
+ - [1.5.10. ConcurrentHashMap 线程安全的具体实现方式/底层具体实现](#1510-concurrenthashmap-线程安全的具体实现方式底层具体实现)
+ - [1.5.10.1. JDK1.7(上面有示意图)](#15101-jdk17上面有示意图)
+ - [1.5.10.2. JDK1.8 (上面有示意图)](#15102-jdk18-上面有示意图)
+ - [1.6. Collections 工具类](#16-collections-工具类)
+ - [1.6.1. 排序操作](#161-排序操作)
+ - [1.6.2. 查找,替换操作](#162-查找替换操作)
+ - [1.6.3. 同步控制](#163-同步控制)
@@ -50,19 +55,21 @@
### 1.1.1. Java 集合概览
-从下图可以看出,在 Java 中除了以 `Map` 结尾的类之外, 其他类都实现了 `Collection` 接口。
+Java 集合, 也叫作容器,主要是由两大接口派生而来:一个是 `Collecton`接口,主要用于存放单一元素;另一个是 `Map` 接口,主要用于存放键值对。对于`Collection` 接口,下面又有三个主要的子接口:`List`、`Set` 和 `Queue`。
-并且,以 `Map` 结尾的类都实现了 `Map` 接口。
+Java 集合框架如下图所示:
-
+
-https://www.javatpoint.com/collections-in-java
-### 1.1.2. 说说 List,Set,Map 三者的区别?
+注:图中只列举了主要的继承派生关系,并没有列举所有关系。比方省略了`AbstractList`, `NavigableSet`等抽象类以及其他的一些辅助类,如想深入了解,可自行查看源码。
-- `List`(对付顺序的好帮手): 存储的元素是有序的、可重复的。
+### 1.1.2. 说说 List, Set, Queue, Map 四者的区别?
+
+- `List`(对付顺序的好帮手): 存储的元素是有序的、可重复的。
- `Set`(注重独一无二的性质): 存储的元素是无序的、不可重复的。
-- `Map`(用 Key 来搜索的专家): 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),“x”代表 key,"y"代表 value,Key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。
+- `Queue`(实现排队功能的叫号机): 按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的。
+- `Map`(用 key 来搜索的专家): 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),"x" 代表 key,"y" 代表 value,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。
### 1.1.3. 集合框架底层数据结构总结
@@ -70,23 +77,27 @@
#### 1.1.3.1. List
-- `Arraylist`: `Object[]`数组
-- `Vector`:`Object[]`数组
+- `Arraylist`: `Object[]` 数组
+- `Vector`:`Object[]` 数组
- `LinkedList`: 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)
#### 1.1.3.2. Set
-- `HashSet`(无序,唯一): 基于 `HashMap` 实现的,底层采用 `HashMap` 来保存元素
-- `LinkedHashSet`:`LinkedHashSet` 是 `HashSet` 的子类,并且其内部是通过 `LinkedHashMap` 来实现的。有点类似于我们之前说的 `LinkedHashMap` 其内部是基于 `HashMap` 实现一样,不过还是有一点点区别的
-- `TreeSet`(有序,唯一): 红黑树(自平衡的排序二叉树)
+- `HashSet`(无序,唯一): 基于 `HashMap` 实现的,底层采用 `HashMap` 来保存元素
+- `LinkedHashSet`: `LinkedHashSet` 是 `HashSet` 的子类,并且其内部是通过 `LinkedHashMap` 来实现的。有点类似于我们之前说的 `LinkedHashMap` 其内部是基于 `HashMap` 实现一样,不过还是有一点点区别的
+- `TreeSet`(有序,唯一): 红黑树(自平衡的排序二叉树)
+
+#### 1.1.3.3 Queue
+- `PriorityQueue`: `Object[]` 数组来实现二叉堆
+- `ArrayQueue`: `Object[]` 数组 + 双指针
再来看看 `Map` 接口下面的集合。
-#### 1.1.3.3. Map
+#### 1.1.3.4. Map
- `HashMap`: JDK1.8 之前 `HashMap` 由数组+链表组成的,数组是 `HashMap` 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突)。JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间
- `LinkedHashMap`: `LinkedHashMap` 继承自 `HashMap`,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红黑树组成。另外,`LinkedHashMap` 在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。同时通过对链表进行相应的操作,实现了访问顺序相关逻辑。详细可以查看:[《LinkedHashMap 源码详细分析(JDK1.8)》](https://www.imooc.com/article/22931)
-- `Hashtable`: 数组+链表组成的,数组是 `HashMap` 的主体,链表则是主要为了解决哈希冲突而存在的
+- `Hashtable`: 数组+链表组成的,数组是 `Hashtable` 的主体,链表则是主要为了解决哈希冲突而存在的
- `TreeMap`: 红黑树(自平衡的排序二叉树)
### 1.1.4. 如何选用集合?
@@ -302,15 +313,72 @@ Output:
`LinkedHashSet` 是 `HashSet` 的子类,能够按照添加的顺序遍历;
-`TreeSet` 底层使用红黑树,能够按照添加元素的顺序进行遍历,排序的方式有自然排序和定制排序。
+`TreeSet` 底层使用红黑树,元素是有序的,排序的方式有自然排序和定制排序。
-## 1.4. Map 接口
+## 1.4 Collection 子接口之 Queue
-### 1.4.1. HashMap 和 Hashtable 的区别
+### 1.4.1 Queue 与 Deque 的区别
-1. **线程是否安全:** `HashMap` 是非线程安全的,`HashTable` 是线程安全的,因为 `HashTable` 内部的方法基本都经过`synchronized` 修饰。(如果你要保证线程安全的话就使用 `ConcurrentHashMap` 吧!);
-2. **效率:** 因为线程安全的问题,`HashMap` 要比 `HashTable` 效率高一点。另外,`HashTable` 基本被淘汰,不要在代码中使用它;
-3. **对 Null key 和 Null value 的支持:** `HashMap` 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;HashTable 不允许有 null 键和 null 值,否则会抛出 `NullPointerException`。
+`Queue` 是单端队列,只能从一端插入元素,另一端删除元素,实现上一般遵循 **先进先出(FIFO)** 规则。
+
+`Queue` 扩展了 `Collection` 的接口,根据 **因为容量问题而导致操作失败后处理方式的不同** 可以分为两类方法: 一种在操作失败后会抛出异常,另一种则会返回特殊值。
+
+| `Queue` 接口| 抛出异常 | 返回特殊值 |
+| ------------ | --------- | ---------- |
+| 插入队尾 | add(E e) | offer(E e) |
+| 删除队首 | remove() | poll() |
+| 查询队首元素 | element() | peek() |
+
+`Deque` 是双端队列,在队列的两端均可以插入或删除元素。
+
+`Deque` 扩展了 `Queue` 的接口, 增加了在队首和队尾进行插入和删除的方法,同样根据失败后处理方式的不同分为两类:
+
+| `Deque` 接口 | 抛出异常 | 返回特殊值 |
+| ------------ | ------------- | --------------- |
+| 插入队首 | addFirst(E e) | offerFirst(E e) |
+| 插入队尾 | addLast(E e) | offerLast(E e) |
+| 删除队首 | removeFirst() | pollFirst() |
+| 删除队尾 | removeLast() | pollLast() |
+| 查询队首元素 | getFirst() | peekFirst() |
+| 查询队尾元素 | getLast() | peekLast() |
+
+事实上,`Deque` 还提供有 `push()` 和 `pop()` 等其他方法,可用于模拟栈。
+
+
+### 1.4.2 ArrayDeque 与 LinkedList 的区别
+
+`ArrayDeque` 和 `LinkedList` 都实现了 `Deque` 接口,两者都具有队列的功能,但两者有什么区别呢?
+
+- `ArrayDeque` 是基于可变长的数组和双指针来实现,而 `LinkedList` 则通过链表来实现。
+
+- `ArrayDeque` 不支持存储 `NULL` 数据,但 `LinkedList` 支持。
+
+- `ArrayDeque` 是在 JDK1.6 才被引入的,而`LinkedList` 早在 JDK1.2 时就已经存在。
+
+- `ArrayDeque` 插入时可能存在扩容过程, 不过均摊后的插入操作依然为 O(1)。虽然 `LinkedList` 不需要扩容,但是每次插入数据时均需要申请新的堆空间,均摊性能相比更慢。
+
+从性能的角度上,选用 `ArrayDeque` 来实现队列要比 `LinkedList` 更好。此外,`ArrayDeque` 也可以用于实现栈。
+
+### 1.4.3 说一说 PriorityQueue
+
+`PriorityQueue` 是在 JDK1.5 中被引入的, 其与 `Queue` 的区别在于元素出队顺序是与优先级相关的,即总是优先级最高的元素先出队。
+
+这里列举其相关的一些要点:
+
+- `PriorityQueue` 利用了二叉堆的数据结构来实现的,底层使用可变长的数组来存储数据
+- `PriorityQueue` 通过堆元素的上浮和下沉,实现了在 O(logn) 的时间复杂度内插入元素和删除堆顶元素。
+- `PriorityQueue` 是非线程安全的,且不支持存储 `NULL` 和 `non-comparable` 的对象。
+- `PriorityQueue` 默认是小顶堆,但可以接收一个 `Comparator` 作为构造参数,从而来自定义元素优先级的先后。
+
+`PriorityQueue` 在面试中可能更多的会出现在手撕算法的时候,典型例题包括堆排序、求第K大的数、带权图的遍历等,所以需要会熟练使用才行。
+
+## 1.5. Map 接口
+
+### 1.5.1. HashMap 和 Hashtable 的区别
+
+1. **线程是否安全:** `HashMap` 是非线程安全的,`Hashtable` 是线程安全的,因为 `Hashtable` 内部的方法基本都经过`synchronized` 修饰。(如果你要保证线程安全的话就使用 `ConcurrentHashMap` 吧!);
+2. **效率:** 因为线程安全的问题,`HashMap` 要比 `Hashtable` 效率高一点。另外,`Hashtable` 基本被淘汰,不要在代码中使用它;
+3. **对 Null key 和 Null value 的支持:** `HashMap` 可以存储 null 的 key 和 value,但 null 作为键只能有一个,null 作为值可以有多个;Hashtable 不允许有 null 键和 null 值,否则会抛出 `NullPointerException`。
4. **初始容量大小和每次扩充容量大小的不同 :** ① 创建时如果不指定容量初始值,`Hashtable` 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。`HashMap` 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 `HashMap` 会将其扩充为 2 的幂次方大小(`HashMap` 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 `HashMap` 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。
5. **底层数据结构:** JDK1.8 以后的 `HashMap` 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。
@@ -351,7 +419,7 @@ Output:
}
```
-### 1.4.2. HashMap 和 HashSet 区别
+### 1.5.2. HashMap 和 HashSet 区别
如果你看过 `HashSet` 源码的话就应该知道:`HashSet` 底层就是基于 `HashMap` 实现的。(`HashSet` 的源码非常非常少,因为除了 `clone()`、`writeObject()`、`readObject()`是 `HashSet` 自己不得不实现之外,其他方法都是直接调用 `HashMap` 中的方法。
@@ -362,7 +430,7 @@ Output:
| 调用 `put()`向 map 中添加元素 | 调用 `add()`方法向 `Set` 中添加元素 |
| `HashMap` 使用键(Key)计算 `hashcode` | `HashSet` 使用成员对象来计算 `hashcode` 值,对于两个对象来说 `hashcode` 可能相同,所以`equals()`方法用来判断对象的相等性 |
-### 1.4.3. HashMap 和 TreeMap 区别
+### 1.5.3. HashMap 和 TreeMap 区别
`TreeMap` 和`HashMap` 都继承自`AbstractMap` ,但是需要注意的是`TreeMap`它还实现了`NavigableMap`接口和`SortedMap` 接口。
@@ -370,7 +438,7 @@ Output:
实现 `NavigableMap` 接口让 `TreeMap` 有了对集合内元素的搜索的能力。
-实现`SortMap`接口让 `TreeMap` 有了对集合中的元素根据键排序的能力。默认是按 key 的升序排序,不过我们也可以指定排序的比较器。示例代码如下:
+实现`SortedMap`接口让 `TreeMap` 有了对集合中的元素根据键排序的能力。默认是按 key 的升序排序,不过我们也可以指定排序的比较器。示例代码如下:
```java
/**
@@ -430,12 +498,33 @@ TreeMap treeMap = new TreeMap<>((person1, person2) -> {
**综上,相比于`HashMap`来说 `TreeMap` 主要多了对集合中的元素根据键排序的能力以及对集合内元素的搜索的能力。**
-### 1.4.4. HashSet 如何检查重复
+### 1.5.4. HashSet 如何检查重复
-以下内容摘自我的 Java 启蒙书《Head fist java》第二版:
+以下内容摘自我的 Java 启蒙书《Head first java》第二版:
当你把对象加入`HashSet`时,`HashSet` 会先计算对象的`hashcode`值来判断对象加入的位置,同时也会与其他加入的对象的 `hashcode` 值作比较,如果没有相符的 `hashcode`,`HashSet` 会假设对象没有重复出现。但是如果发现有相同 `hashcode` 值的对象,这时会调用`equals()`方法来检查 `hashcode` 相等的对象是否真的相同。如果两者相同,`HashSet` 就不会让加入操作成功。
+在openjdk8中,`HashSet`的`add()`方法只是简单的调用了`HashMap`的`put()`方法,并且判断了一下返回值以确保是否有重复元素。直接看一下`HashSet`中的源码:
+```java
+// Returns: true if this set did not already contain the specified element
+// 返回值:当set中没有包含add的元素时返回真
+public boolean add(E e) {
+ return map.put(e, PRESENT)==null;
+}
+```
+
+而在`HashMap`的`putVal()`方法中也能看到如下说明:
+```java
+// Returns : previous value, or null if none
+// 返回值:如果插入位置没有元素返回null,否则返回上一个元素
+final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
+ boolean evict) {
+...
+}
+```
+
+也就是说,在openjdk8中,实际上无论`HashSet`中是否已经存在了某元素,`HashSet`都会直接插入,只是会在`add()`方法的返回值处告诉我们插入前是否存在相同元素。
+
**`hashCode()`与 `equals()` 的相关规定:**
1. 如果两个对象相等,则 `hashcode` 一定也是相同的
@@ -452,9 +541,9 @@ TreeMap treeMap = new TreeMap<>((person1, person2) -> {
对于引用类型(包括包装类型)来说,equals 如果没有被重写,对比它们的地址是否相等;如果 equals()方法被重写(例如 String),则比较的是地址里的内容。
-### 1.4.5. HashMap 的底层实现
+### 1.5.5. HashMap 的底层实现
-#### 1.4.5.1. JDK1.8 之前
+#### 1.5.5.1. JDK1.8 之前
JDK1.8 之前 `HashMap` 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 (n - 1) & hash 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。**
@@ -493,7 +582,7 @@ static int hash(int h) {

-#### 1.4.5.2. JDK1.8 之后
+#### 1.5.5.2. JDK1.8 之后
相比于之前的版本, JDK1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。
@@ -501,7 +590,7 @@ static int hash(int h) {
> TreeMap、TreeSet 以及 JDK1.8 之后的 HashMap 底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。
-### 1.4.6. HashMap 的长度为什么是 2 的幂次方
+### 1.5.6. HashMap 的长度为什么是 2 的幂次方
为了能让 HashMap 存取高效,尽量较少碰撞,也就是要尽量把数据分配均匀。我们上面也讲到了过了,Hash 值的范围值-2147483648 到 2147483647,前后加起来大概 40 亿的映射空间,只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。但问题是一个 40 亿长度的数组,内存是放不下的。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来要存放的位置也就是对应的数组下标。这个数组下标的计算方法是“ `(n - 1) & hash`”。(n 代表数组长度)。这也就解释了 HashMap 的长度为什么是 2 的幂次方。
@@ -509,17 +598,17 @@ static int hash(int h) {
我们首先可能会想到采用%取余的操作来实现。但是,重点来了:**“取余(%)操作中如果除数是 2 的幂次则等价于与其除数减一的与(&)操作(也就是说 hash%length==hash&(length-1)的前提是 length 是 2 的 n 次方;)。”** 并且 **采用二进制位操作 &,相对于%能够提高运算效率,这就解释了 HashMap 的长度为什么是 2 的幂次方。**
-### 1.4.7. HashMap 多线程操作导致死循环问题
+### 1.5.7. HashMap 多线程操作导致死循环问题
主要原因在于并发下的 Rehash 会造成元素之间会形成一个循环链表。不过,jdk 1.8 后解决了这个问题,但是还是不建议在多线程下使用 HashMap,因为多线程下使用 HashMap 还是会存在其他问题比如数据丢失。并发环境下推荐使用 ConcurrentHashMap 。
详情请查看:
-### 1.4.8. HashMap 有哪几种常见的遍历方式?
+### 1.5.8. HashMap 有哪几种常见的遍历方式?
[HashMap 的 7 种遍历方式与性能分析!](https://mp.weixin.qq.com/s/zQBN3UvJDhRTKP6SzcZFKw)
-### 1.4.9. ConcurrentHashMap 和 Hashtable 的区别
+### 1.5.9. ConcurrentHashMap 和 Hashtable 的区别
`ConcurrentHashMap` 和 `Hashtable` 的区别主要体现在实现线程安全的方式上不同。
@@ -528,27 +617,27 @@ static int hash(int h) {
**两者的对比图:**
-**HashTable:**
+**Hashtable:**
-
+
-http://www.cnblogs.com/chengxiao/p/6842045.html>
+https://www.cnblogs.com/chengxiao/p/6842045.html>
**JDK1.7 的 ConcurrentHashMap:**

-http://www.cnblogs.com/chengxiao/p/6842045.html>
+https://www.cnblogs.com/chengxiao/p/6842045.html>
**JDK1.8 的 ConcurrentHashMap:**

-JDK1.8 的 `ConcurrentHashMap` 不在是 **Segment 数组 + HashEntry 数组 + 链表**,而是 **Node 数组 + 链表 / 红黑树**。不过,Node 只能用于链表的情况,红黑树的情况需要使用 **`TreeNode`**。当冲突链表达到一定长度时,链表会转换成红黑树。
+JDK1.8 的 `ConcurrentHashMap` 不再是 **Segment 数组 + HashEntry 数组 + 链表**,而是 **Node 数组 + 链表 / 红黑树**。不过,Node 只能用于链表的情况,红黑树的情况需要使用 **`TreeNode`**。当冲突链表达到一定长度时,链表会转换成红黑树。
-### 1.4.10. ConcurrentHashMap 线程安全的具体实现方式/底层具体实现
+### 1.5.10. ConcurrentHashMap 线程安全的具体实现方式/底层具体实现
-#### 1.4.10.1. JDK1.7(上面有示意图)
+#### 1.5.10.1. JDK1.7(上面有示意图)
首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
@@ -563,13 +652,13 @@ static class Segment extends ReentrantLock implements Serializable {
一个 `ConcurrentHashMap` 里包含一个 `Segment` 数组。`Segment` 的结构和 `HashMap` 类似,是一种数组和链表结构,一个 `Segment` 包含一个 `HashEntry` 数组,每个 `HashEntry` 是一个链表结构的元素,每个 `Segment` 守护着一个 `HashEntry` 数组里的元素,当对 `HashEntry` 数组的数据进行修改时,必须首先获得对应的 `Segment` 的锁。
-#### 1.4.10.2. JDK1.8 (上面有示意图)
+#### 1.5.10.2. JDK1.8 (上面有示意图)
`ConcurrentHashMap` 取消了 `Segment` 分段锁,采用 CAS 和 `synchronized` 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。Java 8 在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为 O(N))转换为红黑树(寻址时间复杂度为 O(log(N)))
`synchronized` 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍。
-## 1.5. Collections 工具类
+## 1.6. Collections 工具类
Collections 工具类常用方法:
@@ -577,7 +666,7 @@ Collections 工具类常用方法:
2. 查找,替换操作
3. 同步控制(不推荐,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合)
-### 1.5.1. 排序操作
+### 1.6.1. 排序操作
```java
void reverse(List list)//反转
@@ -588,19 +677,19 @@ void swap(List list, int i , int j)//交换两个索引位置的元素
void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面
```
-### 1.5.2. 查找,替换操作
+### 1.6.2. 查找,替换操作
```java
int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的
int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
-void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素。
+void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素
int frequency(Collection c, Object o)//统计元素出现次数
-int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target).
-boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素
+int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target)
+boolean replaceAll(List list, Object oldVal, Object newVal)//用新元素替换旧元素
```
-### 1.5.3. 同步控制
+### 1.6.3. 同步控制
`Collections` 提供了多个`synchronizedXxx()`方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。
@@ -619,4 +708,4 @@ synchronizedSet(Set s) //返回指定 set 支持的同步(线程安全的
**《Java 面试突击》:** Java 程序员面试必备的《Java 面试突击》V3.0 PDF 版本扫码关注下面的公众号,在后台回复 **"面试突击"** 即可免费领取!
-
\ No newline at end of file
+
diff --git a/docs/java/collection/images/java-collection-hierarchy.png b/docs/java/collection/images/java-collection-hierarchy.png
new file mode 100644
index 00000000..78daf980
Binary files /dev/null and b/docs/java/collection/images/java-collection-hierarchy.png differ
diff --git a/docs/java/jvm/JDK监控和故障处理工具总结.md b/docs/java/jvm/JDK监控和故障处理工具总结.md
index c8263de0..779ab01f 100644
--- a/docs/java/jvm/JDK监控和故障处理工具总结.md
+++ b/docs/java/jvm/JDK监控和故障处理工具总结.md
@@ -24,12 +24,12 @@
这些命令在 JDK 安装目录下的 bin 目录下:
-- **`jps`** (JVM Process Status): 类似 UNIX 的 `ps` 命令。用户查看所有 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息;
-- **`jstat`**( JVM Statistics Monitoring Tool): 用于收集 HotSpot 虚拟机各方面的运行数据;
-- **`jinfo`** (Configuration Info for Java) : Configuration Info forJava,显示虚拟机配置信息;
-- **`jmap`** (Memory Map for Java) :生成堆转储快照;
-- **`jhat`** (JVM Heap Dump Browser ) : 用于分析 heapdump 文件,它会建立一个 HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果;
-- **`jstack`** (Stack Trace for Java):生成虚拟机当前时刻的线程快照,线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合。
+- **`jps`** (JVM Process Status): 类似 UNIX 的 `ps` 命令。用于查看所有 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息;
+- **`jstat`**(JVM Statistics Monitoring Tool): 用于收集 HotSpot 虚拟机各方面的运行数据;
+- **`jinfo`** (Configuration Info for Java) : Configuration Info for Java,显示虚拟机配置信息;
+- **`jmap`** (Memory Map for Java) : 生成堆转储快照;
+- **`jhat`** (JVM Heap Dump Browser) : 用于分析 heapdump 文件,它会建立一个 HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果;
+- **`jstack`** (Stack Trace for Java) : 生成虚拟机当前时刻的线程快照,线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合。
### `jps`:查看所有 Java 进程
@@ -88,7 +88,7 @@ jstat -