├── .github
├── ISSUE_TEMPLATE
│ ├── ---bug.md
│ └── new.md
└── pull_request_template.md
├── .gitignore
├── 1.md
├── BookList.md
├── LICENSE
├── README.md
├── about
├── _media
│ ├── conghuajidan.jpg
│ └── personal.jpg
└── media
│ └── pic
│ ├── MESI缓存一致性协议.png
│ ├── bplustree.png
│ ├── btree.png
│ ├── btree查询过程.jpg
│ ├── explain-type.png
│ ├── explain详解.png
│ ├── hashmap-1.7-resize-01.png
│ ├── hashmap-1.7-resize-02.png
│ ├── hashmap-1.7-resize-03.png
│ ├── hashmap-1.7-resize-04.png
│ ├── hashmap-1.7-resize-05.png
│ ├── hashmap-1.7-resize-06.png
│ ├── hashmap-1.7-resize-07.png
│ ├── hashmap-1.7-resize-08.png
│ ├── hashmap-1.7-resize-09.png
│ ├── innodb索引原理图.png
│ ├── innodb辅助索引原理图.png
│ ├── java_memory_area.jpeg
│ ├── java_memory_area.jpg
│ ├── java_memory_area_new.jpg
│ ├── java_memory_area_old.jpg
│ ├── myisam索引原理图.png
│ ├── myisam辅助索引原理图.png
│ ├── nat-1.png
│ ├── nat-2.png
│ ├── nat-4.png
│ ├── nat-5.png
│ ├── osi-rm.jpg
│ ├── redisson分布式锁原理.png
│ ├── thread_basic_operation.png
│ ├── volatile无法保证线程操作的原子性.png
│ ├── whereisnginx.jpg
│ ├── zookeeper-node.jpg
│ ├── zookeeper分布式锁原理.png
│ ├── 总线加锁.png
│ ├── 汇编Lock前缀指令.png
│ ├── 索引优化.png
│ ├── 线程基本操作.png
│ └── 线程池原理图.jpg
└── docs
└── notes
├── Java基础
├── Java-关键字
│ ├── README.md
│ └── final 关键字.md
├── Java-容器
│ ├── Collection集合
│ │ ├── image
│ │ │ ├── Stack.png
│ │ │ └── java-collection.gif
│ │ ├── 基础知识
│ │ │ ├── ArrayList.md
│ │ │ ├── LinkedList.md
│ │ │ ├── Stack.md
│ │ │ └── Vector.md
│ │ └── 常见面试题
│ │ │ ├── ArrayList面试题.md
│ │ │ └── Stack面试题.md
│ ├── Map
│ │ ├── Collections.synchronizedMap.md
│ │ ├── HashMap.md
│ │ ├── HashTable.md
│ │ ├── LinkedHashMap.md
│ │ ├── TreeMap.md
│ │ └── img
│ │ │ ├── get.png
│ │ │ ├── hash.png
│ │ │ ├── hashMap-竖着看.png
│ │ │ ├── hashmap-put.png
│ │ │ ├── hashmap-横着看.jpg
│ │ │ ├── putVal.png
│ │ │ └── 扰动函数.png
│ └── README.md
├── Java-序列化机制
│ └── Java 序列化及反序列化.md
├── Java-数据类型
│ └── 引用数据类型
│ │ └── String.md
└── README.md
├── Java多线程
├── 基础篇-线程基础
│ └── 一. 实现多线程的方式&&区别&&原理.md
└── 进阶篇-Java并发理论
│ └── Java内存模型及happens-before原则.md
├── Java虚拟机
├── 1.类加载器子系统.md
├── 2.0 运行时数据区与内存
│ ├── 2.1 程序计数器.md
│ ├── 2.2 虚拟机栈.md
│ ├── 2.3 本地方法接口.md
│ ├── 2.4 本地方法栈.md
│ ├── 2.5 堆.md
│ └── 2.6 方法区.md
├── 3.对象实例化内存布局与访问定位.md
├── 4.直接内存.md
├── 5.执行引擎.md
├── 6.垃圾回收算法.md
└── 7.垃圾回收器.md
├── 分布式
├── ZooKeeper
│ ├── README.md
│ └── ZooKeeper服务注册中心原理.md
├── 分布式事务
│ └── 分布式事务基础概念.md
└── 分布式锁
│ └── 分布式锁的实现.md
├── 基础框架
├── README.md
├── Spring
│ ├── IOC 和 AOP.md
│ ├── SpringBoot常见面试题.md
│ ├── SpringMVC原理.md
│ └── img
│ │ └── springmvc-01.jpg
└── 自定义注解原理及实现.md
├── 工具及组件
├── Git.md
├── JRebel热部署工具.md
├── MarkDown使用手册.md
├── 使用Github.md
└── 日志
│ └── 查看日志常用命令.md
├── 操作系统
├── Linux
│ ├── CentOS.md
│ ├── Linux常用命令.md
│ └── 使用VM逐步搭建CentOS 教程.md
├── README.md
└── 基础知识
│ ├── 1.1操作系统的基本概念.md
│ ├── 1.2.操作系统的发展与分类.md
│ ├── 1.3.操作系统的运行环境.md
│ ├── 1.4.操作系统的体系结构.md
│ ├── 2.1进程概论.md
│ ├── 2.2线程概论.md
│ ├── 2.3处理机调度.md
│ └── 3.1内存管理.md
├── 数据库
├── MySQL
│ ├── MySQL事务.md
│ ├── MySQL存储引擎.md
│ ├── MySQL索引.md
│ └── MySQL锁机制.md
├── README.md
└── SQL练习
│ └── 50道SQL练习.md
├── 算法与数据结构
├── LeetCode 研发类面试专题
│ ├── 1.深度优先搜索.md
│ ├── 2.广度优先搜索.md
│ ├── 3.链表.md
│ ├── 4.二叉树.md
│ └── README.md
├── PAT 甲级题目
│ ├── 1001 A+B Format.md
│ ├── 1002 A+B for Polynomials.md
│ └── PAT 甲级题目汇总.md
└── 剑指Offer 笔记汇总
│ ├── 1.二维数组中的查找.md
│ ├── 11.二进制中1的个数.md
│ ├── 12.数值的整数次方.md
│ ├── 14-链表倒数第K个节点.md
│ ├── 15.反转链表.md
│ ├── 16-合并两个有序链表.md
│ ├── 17. 树的子结构.md
│ ├── 18.二叉树的镜像.md
│ ├── 2.替换空格.md
│ ├── 20.包含min函数的栈.md
│ ├── 28.数组中出现次数超过一半的数字.md
│ ├── 3.从尾到头打印链表.md
│ ├── 30.连续子数组的最大和.md
│ ├── 36-两个链表的第一个公共节点.md
│ ├── 38.二叉树深度.md
│ ├── 39.平衡二叉树.md
│ ├── 4.重建二叉树.md
│ ├── 42.和为S的两个数字.md
│ ├── 5.用两个栈实现一个队列.md
│ ├── 50.数组中重复的数字.md
│ ├── 55-链表中环的位置.md
│ ├── 56-删除链表中重复的节点.md
│ ├── 6.旋转数组的最小数字.md
│ ├── 60.把二叉树打印出多行.md
│ ├── 63.数据流中的中位数.md
│ ├── 7.斐波那契数列.md
│ ├── 8.跳台阶.md
│ ├── 9.变态跳台阶.md
│ └── 剑指 Offer 笔记汇总.md
├── 缓存
└── Redis
│ ├── 0.Redis 面试题.md
│ ├── 1.Redis数据类型
│ ├── 1.1 string.md
│ ├── 1.2 list.md
│ ├── 1.3 hash.md
│ ├── 1.4 set.md
│ └── 1.5 zset.md
│ ├── Redis对象机制.md
│ ├── Redis持久化.md
│ ├── Redis缓存一致性问题.md
│ ├── Redis缓存穿透_击穿_雪崩.md
│ └── Redis过期策略及内存淘汰机制.md
├── 计算机网络
├── 常见面试题
│ └── 1、TCP IP面试题.md
└── 计算机网络-1.OSI网络模型.md
└── 设计模式
└── 单例模式.md
/.github/ISSUE_TEMPLATE/---bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 汇报 bug
3 | about: 指出出现的问题
4 |
5 | ---
6 |
7 | 首先,十分欢迎你来给 NewBie-Plan 开 issue,在提交之前,请花时间阅读一下这个模板的内容,谢谢合作!
8 |
9 | - [ ] 请确认已经读过了 [F.A.Q.](https://github.com/553899811/)(确认过后请将选项打钩 / 填为 `[x]`)
10 |
11 | - 是出现了什么问题?(最好截图)
12 |
13 | - 你是否正在着手修复?
14 |
15 | - 如何复现?
16 |
17 | issue 标题请写为 要汇报的主要内容
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/new.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 添加新内容
3 | about: 申请添加某个 topic
4 |
5 | ---
6 |
7 | 首先,十分欢迎你来给 NewBie-Plan 开 issue,在提交之前,请花时间阅读一下这个模板的内容,谢谢合作!
8 |
9 | - 希望添加的是什么?
10 |
11 |
12 | - 英文叫什么?
13 |
14 |
15 | - 有什么参考资料?
16 |
17 |
18 | issue 标题请写为 'add ' + 要添加的内容
19 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 |
12 |
13 | **审核的同学** 请着重关注以下四方面:
14 |
15 | 1. 注意有没有 typo
16 | 2. 不论你是否熟悉相关知识,都请以初学者的角度把这个 PR 的内容阅读一遍,跟着作者的思路走,然后谈谈你的感受
17 | 3. 如果你熟悉相关知识,请按照自己的理解评估这个 PR 的内容是否合适
18 | 4. 请**尽量**保持跟进直到它被 merge 或 close
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.ear
17 | *.zip
18 | *.tar.gz
19 | *.rar
20 | *.idea
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 | .idea/
25 |
26 |
--------------------------------------------------------------------------------
/1.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/1.md
--------------------------------------------------------------------------------
/BookList.md:
--------------------------------------------------------------------------------
1 | # 学习资源推荐
2 | ## 1.推荐书单
3 | - Java基础
4 | - [B站视频: Java-300集](https://www.bilibili.com/video/av6749471)
5 |
6 | - [牛客网笔试题](https://www.nowcoder.com/intelligentTest)
7 | - Java多线程
8 |
9 | - [Java多线程编程实战指南](https://pan.baidu.com/s/1ACiNLZtaTkwTdz6NHFuSew)
10 |
11 | - [Java并发编程的艺术](https://pan.baidu.com/s/1ACiNLZtaTkwTdz6NHFuSew)
12 |
13 | - [Java多线程编程核心技术](https://pan.baidu.com/s/1ACiNLZtaTkwTdz6NHFuSew)
14 | - JVM虚拟机
15 |
16 | - [深入理解Java虚拟机 JVM高级特性与最佳实践](https://pan.baidu.com/s/1SNI8io12SGVadz039Uzn7g)
17 |
18 | - [实战Java虚拟机 JVM故障诊断与性能优化](https://pan.baidu.com/s/1SNI8io12SGVadz039Uzn7g)
19 |
20 | - [深入理解Java内存模型](https://pan.baidu.com/s/1SNI8io12SGVadz039Uzn7g)
21 |
22 | - 操作系统
23 | - [深入理解计算机系统(原书第三版)](https://pan.baidu.com/s/1LONX7g1rzfzhIvKNp2i-FA)
24 |
25 | - 计算机网络
26 | - [计算机网络-第五版](https://pan.baidu.com/s/1e121Ma3_KJu_4X5wL_sHaQ)
27 | - 数据库
28 |
29 | - [MySQL必知必会](https://pan.baidu.com/s/1tRmtBjXPvcHvZxa5wyaqlw)
30 |
31 | - [高性能MySQL](https://pan.baidu.com/s/1tRmtBjXPvcHvZxa5wyaqlw)
32 |
33 | ## 2. 模块资源
34 | - 2.1 多线程系列
35 | - [Java多线程系列目录(共43篇)](https://blog.csdn.net/mingtianhaiyouwo/article/details/51012392)
36 | - [深入浅出Java Concurrency——线程池](http://wanglizhi.github.io/2016/08/08/Java-Concurrency/)
37 | - [Java并发编程系列文章目录帖及源码](https://blog.csdn.net/u011116672/article/details/51180166)
38 |
39 | - 2.2 JVM系列
40 | - [深入理解Java虚拟机](https://blog.csdn.net/column/details/21960.html)
41 | - 2.3 IO系列
42 | - [http://wangkuiwu.github.io/2100/01/01/index/](http://wangkuiwu.github.io/2100/01/01/index/)
43 |
44 | - 2.4 面试大纲
45 | - [近一个月的面试总结](https://blog.csdn.net/Yan456jie/article/details/52439457)
46 | - [华科硕士-秋招经验](https://blog.csdn.net/a724888/article/details/82532520)
--------------------------------------------------------------------------------
/about/_media/conghuajidan.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/_media/conghuajidan.jpg
--------------------------------------------------------------------------------
/about/_media/personal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/_media/personal.jpg
--------------------------------------------------------------------------------
/about/media/pic/MESI缓存一致性协议.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/MESI缓存一致性协议.png
--------------------------------------------------------------------------------
/about/media/pic/bplustree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/bplustree.png
--------------------------------------------------------------------------------
/about/media/pic/btree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/btree.png
--------------------------------------------------------------------------------
/about/media/pic/btree查询过程.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/btree查询过程.jpg
--------------------------------------------------------------------------------
/about/media/pic/explain-type.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/explain-type.png
--------------------------------------------------------------------------------
/about/media/pic/explain详解.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/explain详解.png
--------------------------------------------------------------------------------
/about/media/pic/hashmap-1.7-resize-01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/hashmap-1.7-resize-01.png
--------------------------------------------------------------------------------
/about/media/pic/hashmap-1.7-resize-02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/hashmap-1.7-resize-02.png
--------------------------------------------------------------------------------
/about/media/pic/hashmap-1.7-resize-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/hashmap-1.7-resize-03.png
--------------------------------------------------------------------------------
/about/media/pic/hashmap-1.7-resize-04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/hashmap-1.7-resize-04.png
--------------------------------------------------------------------------------
/about/media/pic/hashmap-1.7-resize-05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/hashmap-1.7-resize-05.png
--------------------------------------------------------------------------------
/about/media/pic/hashmap-1.7-resize-06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/hashmap-1.7-resize-06.png
--------------------------------------------------------------------------------
/about/media/pic/hashmap-1.7-resize-07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/hashmap-1.7-resize-07.png
--------------------------------------------------------------------------------
/about/media/pic/hashmap-1.7-resize-08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/hashmap-1.7-resize-08.png
--------------------------------------------------------------------------------
/about/media/pic/hashmap-1.7-resize-09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/hashmap-1.7-resize-09.png
--------------------------------------------------------------------------------
/about/media/pic/innodb索引原理图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/innodb索引原理图.png
--------------------------------------------------------------------------------
/about/media/pic/innodb辅助索引原理图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/innodb辅助索引原理图.png
--------------------------------------------------------------------------------
/about/media/pic/java_memory_area.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/java_memory_area.jpeg
--------------------------------------------------------------------------------
/about/media/pic/java_memory_area.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/java_memory_area.jpg
--------------------------------------------------------------------------------
/about/media/pic/java_memory_area_new.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/java_memory_area_new.jpg
--------------------------------------------------------------------------------
/about/media/pic/java_memory_area_old.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/java_memory_area_old.jpg
--------------------------------------------------------------------------------
/about/media/pic/myisam索引原理图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/myisam索引原理图.png
--------------------------------------------------------------------------------
/about/media/pic/myisam辅助索引原理图.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/myisam辅助索引原理图.png
--------------------------------------------------------------------------------
/about/media/pic/nat-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/nat-1.png
--------------------------------------------------------------------------------
/about/media/pic/nat-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/nat-2.png
--------------------------------------------------------------------------------
/about/media/pic/nat-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/nat-4.png
--------------------------------------------------------------------------------
/about/media/pic/nat-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/nat-5.png
--------------------------------------------------------------------------------
/about/media/pic/osi-rm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/osi-rm.jpg
--------------------------------------------------------------------------------
/about/media/pic/redisson分布式锁原理.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/redisson分布式锁原理.png
--------------------------------------------------------------------------------
/about/media/pic/thread_basic_operation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/thread_basic_operation.png
--------------------------------------------------------------------------------
/about/media/pic/volatile无法保证线程操作的原子性.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/volatile无法保证线程操作的原子性.png
--------------------------------------------------------------------------------
/about/media/pic/whereisnginx.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/whereisnginx.jpg
--------------------------------------------------------------------------------
/about/media/pic/zookeeper-node.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/zookeeper-node.jpg
--------------------------------------------------------------------------------
/about/media/pic/zookeeper分布式锁原理.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/zookeeper分布式锁原理.png
--------------------------------------------------------------------------------
/about/media/pic/总线加锁.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/总线加锁.png
--------------------------------------------------------------------------------
/about/media/pic/汇编Lock前缀指令.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/汇编Lock前缀指令.png
--------------------------------------------------------------------------------
/about/media/pic/索引优化.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/索引优化.png
--------------------------------------------------------------------------------
/about/media/pic/线程基本操作.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/线程基本操作.png
--------------------------------------------------------------------------------
/about/media/pic/线程池原理图.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/about/media/pic/线程池原理图.jpg
--------------------------------------------------------------------------------
/docs/notes/Java基础/Java-关键字/README.md:
--------------------------------------------------------------------------------
1 | # Java 关键字
2 |
3 | 在 Java 中目前一共有53个关键字,我们按照使用频率及面试频率依次分析关键字使用场景及衍生的原理。
4 |
5 | # 1. 访问权限修饰符
6 |
7 |
8 |
9 | | public | 公共的 | 可跨包 |
10 | | --- | --- | --- |
11 | | protected | 受保护的 | 当前包内可用 |
12 | | private | 私有的 | 当前类可用 |
13 |
14 |
15 | # 2. 定义类。接口,抽象类及实现接口,继承类的关键字,实例化对象
16 | | class | | |
17 | | --- | --- | --- |
18 | | interface | | |
19 | | abstract | | |
20 | | implemenst | | |
21 | | extends | | |
22 | | new | | |
23 |
24 |
25 | # 3. 修饰方法,类,属性和变量
26 |
27 |
28 |
29 | | **static** | | |
30 | | --- | --- | --- |
31 | | **final** | | [https://www.cnblogs.com/lwbqqyumidi/p/3513047.html](https://www.cnblogs.com/lwbqqyumidi/p/3513047.html) |
32 | | super | | |
33 | | this | | |
34 | | native | | |
35 | | **synchronized** | | 具体内容在多线程章节中 |
36 | | transient | | |
37 | | **volatile** | | 具体内容在多线程章节中 |
38 |
39 |
40 | # 其他
41 | | **enum** | 枚举 | [https://www.cnblogs.com/hyl8218/p/5088287.html](https://www.cnblogs.com/hyl8218/p/5088287.html) ,需重点关注enum 原理分析 |
42 | | --- | --- | --- |
43 | | assert | 断言 | |
44 |
45 |
46 | # 引用
47 | [https://www.cnblogs.com/yan-linxi/p/7977947.html](https://www.cnblogs.com/yan-linxi/p/7977947.html)
48 |
--------------------------------------------------------------------------------
/docs/notes/Java基础/Java-关键字/final 关键字.md:
--------------------------------------------------------------------------------
1 | # final 关键字
2 |
3 | 在Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)
4 |
5 | # 1. final 修饰变量
6 | final**成员变量表示常量,只能被赋值一次,赋值后值不再改变。**
7 |
8 | | 修饰基本数据类型 | 表示该基本数据类型的值一旦在初始化后便不能发生变化 |
9 | | --- | --- |
10 | | 修饰一个引用类型 | 在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的 |
11 |
12 | **final修饰一个成员变量(属性),必须要显示初始化。****这里有两种初始化方式**,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。
13 |

当函数的参数类型声明为final时,说明该参数是只读型的。即你可以读取使用该参数,但是无法改变该参数的值。

14 |
15 | # 2.final 编译期常量
16 | 什么叫对类的依赖性?单从字面上理解就是需不需要类,其实也就是与类的创建有没有关系。
那么类的创建和常量有什么关系吗?或则更具体来说,编译期常量和运行时常量对类的创建有什么不同的影响?
17 |
要解答以上的疑问还得先来看类在什么情况下会创建:
JVM的虚拟机规范严格规定了有且只有5种情况必须立即对类进行“初始化”,其中第一条就是:
遇到new、getstatic或invokestatic这4条字节码指令时,如果类没有初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:
18 |
(1)使用new关键字实例化对象时
(2)读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)时
(3)调用一个类的静态方法时
19 |
20 | ```cpp
21 | /**
22 | *
Description:
23 | * 24 | * @version 1.0.0 25 | * @date 2021/2/25 21:40 26 | */ 27 | public class Final { 28 | 29 | public static void main(String[] args) { 30 | System.out.println("num:" + Test.num); 31 | System.out.println("=== after get num ==="); 32 | System.out.println("length" + Test.a); 33 | } 34 | } 35 | 36 | class Test { 37 | static { 38 | System.out.println("类初始化完成"); 39 | } 40 | 41 | public static final int num = 111; 42 | public static final int a = "Hello Final".length(); 43 | 44 | } 45 | 46 | ``` 47 | ```cpp 48 | num:111 49 | === after get num === 50 | 类初始化完成 51 | length11 52 | ``` 53 | **编译期常量不依赖类,不会引起类的初始化;而运行时常量依赖类,会引起类的初始化。** 54 | 55 | # 引用 56 | [浅谈Java中的final关键字](https://www.cnblogs.com/xiaoxi/p/6392154.html) 57 | 58 | [编译期常量与运行时常量](https://blog.csdn.net/qq_34802416/article/details/83548369) 59 | -------------------------------------------------------------------------------- /docs/notes/Java基础/Java-容器/Collection集合/image/Stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/docs/notes/Java基础/Java-容器/Collection集合/image/Stack.png -------------------------------------------------------------------------------- /docs/notes/Java基础/Java-容器/Collection集合/image/java-collection.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/docs/notes/Java基础/Java-容器/Collection集合/image/java-collection.gif -------------------------------------------------------------------------------- /docs/notes/Java基础/Java-容器/Collection集合/基础知识/LinkedList.md: -------------------------------------------------------------------------------- 1 | 2 | * [LinkedList介绍](#linkedlist介绍) 3 | * [1. LinkedList 简介](#1-linkedlist-简介) 4 | * [1.1 实现细节](#11-实现细节) 5 | * [1.1.1 实现和继承关系](#111-实现和继承关系) 6 | * [1.1.2 底层实现](#112-底层实现) 7 | * [1.1.2.1 主要成员](#1121-主要成员) 8 | * [1.1.2.2 方法分类](#1122-方法分类) 9 | 10 | * [2. LinkedList 高级特性](#2-linkedlist-高级特性) 11 | 12 | 13 | 14 | # LinkedList介绍 15 | ## 1. LinkedList 简介 16 | ``` 17 | LinkedList是双向链表,大致长下面的样子 18 | ``` 19 |  20 | ### 1.1 实现细节 21 | ``` 22 | 基于JDK 1.8版本; 23 | public class LinkedListQueue队列方法 | 90 |LinkedList等效方法 | 91 |作用 | 92 |返回值 | 93 |非空判断 | 94 |
add(e) | 100 |addLast(e) | 101 |队列尾部添加元素 | 102 |void | 103 |false | 104 |
offer(e) | 107 |offerLast(e) | 108 |队列尾部添加元素 | 109 |boolean | 110 |false | 111 |
remove() | 114 |removeFirst() | 115 |移除队列头部元素 | 116 |泛型E | 117 |true | 118 |
poll() | 121 |pollFirst() | 122 |移除队列头部元素 | 123 |泛型E | 124 |false | 125 |
element() | 128 |getFirst() | 129 |获取队列头部元素 | 130 |泛型E | 131 |true | 132 |
peek() | 135 |peekFirst() | 136 |获取队列头部元素 | 137 |泛型E | 138 |false | 139 |
Stack栈方法 | 150 |LinkedList等效方法 | 151 |实质调用 | 152 |作用 | 153 |返回值 | 154 |非空判断 | 155 |
push(e) | 160 |push(e) | 161 |addFirst | 162 |栈中压入元素 | 163 |void | 164 |false | 165 |
pop(e) | 168 |pop(e) | 169 |removeFirst | 170 |栈中弹出元素 | 171 |void | 172 |false | 173 |
peek(e) | 176 |peek(e) | 177 |peek(e) | 178 |获取队列头部元素 | 179 |void | 180 |false | 181 |
函数名称 | 36 |函数描述 | 37 |
HashTable() | 43 |构造函数,初始化容量为11,负载因子: 0.75 | 44 |
clear() | 47 |清空hashTable中所有的元素 | 48 |
clone() | 51 |获取一个hashTable的拷贝 | 52 |
contains() | 55 |检查某个对象是否作为值存在于dictionary中 | 56 |
containsValue() | 59 |同上 | 60 |
containsKey() | 63 |检查某个对象是否作为关键存于dictionary中 | 64 |
entrySet() | 67 |返回hashTable里的所有(key,value) | 68 |
equals() | 71 |判断当前对象和参数是否相等 | 72 |
hashCode() | 75 |获得一个hashTable的哈希码 | 76 |
putAll() | 79 |将一个集合添加到dictionary当中 | 80 |
rehash() | 83 |增长hashTable的内部的空间 | 84 |
Runnable
run object, then that
45 | * Runnable
object's run
method is called;
46 | * otherwise, this method does nothing and returns.
47 | *
48 | * Subclasses of Description: Thread
should override this method.
49 | *
50 | * @see #start()
51 | * @see #stop()
52 | * @see #Thread(ThreadGroup, Runnable, String)
53 | */
54 | @Override
55 | public void run() {
56 | if (target != null) {
57 | target.run();
58 | }
59 | }
60 | ```
61 | 以上ThreadStyle 继承Thread 类, 自定义的 run 方法**完全重写**Thread 内部中的run()方法
62 |
63 | ## 2.2 Runnable方式创建线程
64 | 以上RunnableStyle 实现Runnable 接口,作为Runnable 子类的形式 作为参数传入Thread 构造器中,本地方法注册后在相应平台创建好新线程后 回调 Thread中run()方法, target 为自定义Runnable子类,然后再调用子类实现的 run() 方法;
65 |
66 | # 3.同时使用Thread 和Runnable 会发生什么?
67 | ```java
68 | public static void main(String[] args) {
69 | new Thread(new Runnable() {
70 | @Override
71 | public void run() {
72 | System.out.println("我来自runnable");
73 | }
74 | } ){
75 | @Override
76 | public void run(){
77 | System.out.println("我来自Thread");
78 | }
79 | }.start();
80 | }
81 | ```
82 | ```java
83 | 我来自Thread
84 |
85 | 因为 Thread 已经重写了 run 方法;
86 | ```
87 |
88 | # 自问自答环节
89 |
90 | ## 1. Thread.start() 到底都干了啥,为啥能在不同的平台创建一个新的线程?
91 | 答案: [从JVM层面了解线程的启动和停止](https://segmentfault.com/a/1190000017255007)
92 |
93 |
--------------------------------------------------------------------------------
/docs/notes/Java多线程/进阶篇-Java并发理论/Java内存模型及happens-before原则.md:
--------------------------------------------------------------------------------
1 | # Java内存模型及happens-before原则
2 | ## JMM数据原子操作
3 | - read(读取): 从主内存读取数据
4 | - load(载入): 将主内存读取到的数据写入工作内存
5 | - use(使用): 从工作内存读取数据用于计算
6 | - assign(赋值): 将计算好的值重新赋值到工作内存中
7 | - store(存储): 将工作内存数据写入主内存
8 | - write(写入): 将store过去的变量值赋值给主内存中的变量
9 | - lock(锁定): 将主内存变量加锁,标识为线程独占状态
10 | - unlock(解锁): 将主内存变量解锁,解锁后其他线程可以锁定该变量
11 | ## CPU缓存
12 | ### 缓存一致性-总线加锁
13 | 
14 | ```
15 | 线程2 从主存中read值之后 开始加锁,直到store 线程执行完之后 unlock,其他线程才可以执行。
16 | ```
17 | #### 缺点:
18 | ```
19 | 存在严重的性能问题
20 | ```
21 | ### 缓存一致性-MESI 缓存一致性协议
22 | 
23 | - 对应代码:
24 | ```
25 | public class VolatileVisiblityTest {
26 |
27 | private static volatile boolean initFlag = false;
28 |
29 | public static void main(String[] args) throws InterruptedException {
30 | new Thread(() -> {
31 | System.out.println("waiting data...");
32 | while (!initFlag) {
33 |
34 | }
35 | System.out.println("---------------success");
36 | }).start();
37 | Thread.sleep(2000);
38 | new Thread(() -> prepareData()).start();
39 | }
40 |
41 | public static void prepareData() {
42 | System.out.println("准备数据中....");
43 | initFlag = true;
44 | System.out.println("数据准备完毕");
45 | }
46 |
47 | }
48 |
49 | ```
50 | ```
51 | MESI是保持一致性的协议。它的方法是在CPU缓存中保存一个标记位,这个标记位有四种状态:
52 | ```
53 | - MESI
54 | - M: Modify,修改缓存,当前CPU的缓存已经被修改了,即与内存中数据已经不一致了;
55 | - E: Exclusive,独占缓存,当前CPU的缓存和内存中数据保持一致,而且其他处理器并没有可使用的缓存数据;
56 | - S: Share,共享缓存,和内存保持一致的一份拷贝,多组缓存可以同时拥有针对同一内存地址的共享缓存段;
57 | - I: Invalid,失效缓存,这个说明CPU中的缓存已经不能使用
58 | - CPU的读取遵循下面几点:
59 | - 如果缓存状态是I,那么就从内存中读取,否则就从缓存中直接读取。
60 | - 如果缓存处于M或E的CPU读取到其他CPU有读操作,就把自己的缓存写入到内存中,并将自己的状态设置为S。
61 | - 只有缓存状态是M或E的时候,CPU才可以修改缓存中的数据,修改后,缓存状态变为M。
62 |
63 | - **汇编#Lock前缀指令**
64 | 
65 | ```
66 | volatile关键字修饰的变量 在线程对其赋值操作的时候,会在其汇编语言中加上#Lock前缀,如上图。有2个功能:
67 | [1] 将当前处理器缓存行中的数据立刻刷新到主内存中
68 | [2] 这个写回内存的操作会触发 CPU总线嗅探机制 ,会引起在其他CPU核里缓存了该内存地址的数据无效(MESI缓存一致性协议)。从而 从表面上实现了内存可见性。
69 | ```
70 | - VM参数配置
71 | ```
72 | -server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*VolatileVisiblityTest.prepareData
73 | ```
74 | ## volatile无法保证线程操作的原子性
75 | 
76 | - 代码:
77 | ```
78 | public class VolatileAtomicTest {
79 | static int num = 0;
80 | static void increase() {
81 | num++;
82 | }
83 | public static void main(String[] args) throws InterruptedException {
84 | Thread[] threads = new Thread[10];
85 | for (int i = 0; i < threads.length; i++) {
86 | threads[i] = new Thread(new Runnable() {
87 | @Override
88 | public void run() {
89 | for (int i = 0; i < 1000; i++) {
90 | increase();
91 | }
92 | }
93 | });
94 | threads[i].start();
95 | }
96 | for (Thread thread : threads) {
97 | thread.join();
98 | }
99 | System.out.println(num);
100 | }
101 | }
102 | ```
103 | ```
104 | 结果:num<=10000
105 | ```
106 | - 分析:
107 | ```
108 | 线程1计算完之后 通过主线向主存更新num值,此时MESI缓存一致性及CPU嗅探,使得线程2已经计算完的num丢失。原本两个线程计算完之后应该是2,此时只能是1;
109 | ```
110 |
111 |
--------------------------------------------------------------------------------
/docs/notes/Java虚拟机/2.0 运行时数据区与内存/2.1 程序计数器.md:
--------------------------------------------------------------------------------
1 |
2 | # 1.程序计数器(PC寄存器)
3 |
4 | ## 1.1 PC 寄存器介绍
5 |
6 | - 它是一块很小的内存空间,几乎可以忽略不记。也是运行速度最快的存储区域。
7 | - `在JVM规范中,每个线程都有它自己的程序计数器`,是线程私有的,`生命周期与线程的生命周期保持一致`。
8 | - 任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。程序计数器会存储当前线程正在执行的Java方法的JVM指令地址;或者,`如果是在执行native方法,则是未指定值(undefined)`。
9 | - 它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成
10 | - 字节码解释器工作时就是通过**改变这个计数器的值来选取下一条需要执行的字节码指令**。
11 | - 它是唯一一个在Java虚拟机规范中没有规定任何`OutofMemoryError`情况的区域。
12 |
13 |
14 |
15 |
16 | ## 1.2 PC寄存器的作用
17 | ```cpp
18 | 程序计数器用来存储指向下一条指令的地址,也就是即将要执行的指令代码。由执行引擎读取下一条指令,并执行该指令。
19 | ```
20 |
21 | ## 1.3 代码演示
22 | 
左侧的数字为:指令地址(偏移地址offset) ,即程序计数器中存储的值,然后执行引擎读取该值,并执行该指令。
右侧的为操作指令。
23 |

24 |
25 | ## 1.4 常见面试题
26 |
27 | ### 1.4.1 为什么使用 程序计数器 来记录当前线程的执行地址呢?
28 |
29 | - 因为线程是一个个的顺序执行流,CPU需要不停的切换各个线程,这时候切换回来以后,就得知道接着从哪开始继续执行
30 | - JVM的字节码解释器就需要通过改变PC寄存器的值来明确下一条应该执行什么样的字节码指令
31 |
32 | ### 1.4.2 程序计数器为何被设定为私有的?
33 |
34 | - 我们都知道所谓的多线程在一个特定的时间段内只会执行其中某一个线程的方法,CPU会不停地做任务切换,这样必然导致经常中断或恢复,如何保证分毫无差呢?
35 | - `为了能够准确地记录各个线程正在执行的当前字节码指令地址,最好的办法自然是为每一个线程都分配一个PC寄存器,这样一来各个线程之间便可以进行独立计算,从而不会出现相互干扰的情况`。
36 | - 由于CPU时间片轮限制,众多线程在并发执行过程中,任何一个确定的时刻,一个处理器或者多核处理器中的一个内核,只会执行某个线程中的一条指令。
37 | - 这样必然导致经常中断或恢复,如何保证分毫无差呢?每个线程在创建后,都会产生自己的程序计数器和栈帧,程序计数器在各个线程之间互不影响。
38 |
--------------------------------------------------------------------------------
/docs/notes/Java虚拟机/2.0 运行时数据区与内存/2.2 虚拟机栈.md:
--------------------------------------------------------------------------------
1 |
2 | # 1. 概述
3 | 首先栈是运行时的单位,而堆是存储的单位。
4 |
5 | - 栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。
6 | - 堆解决的是数据存储的问题,即数据怎么放,放哪里。
7 |
8 | 
9 |
10 | # 2. Java虚拟机栈是什么
11 | 是线程私有的
Java虚拟机栈(Java Virtual Machine Stack),早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的Java方法调用。
12 |
13 | ## 2.1 生命周期
14 | 生命周期和线程一致,也就是线程结束了,该虚拟机栈也销毁了 。
15 |
16 | ## 2.2 作用
17 | 主管Java程序的运行,它保存方法的局部变量、部分结果,并参与方法的调用和返回。
18 |
19 | ## 2.3 栈的特点
20 | 先进后出,后进先出。
对于栈来说不存在垃圾回收问题(栈存在溢出的情况)
21 |
22 | ## 2.4 开发中遇到哪些异常?
23 | 栈中可能出现的异常
Java 虚拟机规范允许Java栈的大小是动态的或者是固定不变的。
如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个StackoverflowError 异常。
如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个 outofMemoryError 异常。
24 |
25 | ### 2.4.1 设置栈的大小
26 | ```cpp
27 | -Xss1m
28 | -Xss1k
29 | ```
30 |
31 | # 3. 栈帧
32 | 栈帧的基本单位,栈的存储单元
33 |
34 | ## 3.1 栈中存储什么?
35 | 栈中的数据都是以栈帧(Stack Frame)的格式存在。每个线程都有自己的栈,在这个线程上正在执行的每个方法都各自对应一个栈颜(Stack Frame)。
36 |
37 | 栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。
38 |
39 | 在一条活动线程中,一个时间点上,只会有一个活动的栈帧。即只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧(Current Frame),与当前栈帧相对应的方法就是当前方法(Current Method),定义这个方法的类就是当前类(Current Class)。
40 |
41 | 执行引擎运行的所有字节码指令只针对当前栈帧进行操作。
42 |
43 |
44 | ## 3.2 栈运行原理
45 | 不同线程中所包含的栈帧是不允许存在相互引用的,即不可能在一个栈帧之中引用另外一个线程的栈帧。
46 |
如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着,虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧。
47 |
**Java方法有两种返回函数的方式,一种是正常的函数返回,使用return指令;另外一种是抛出异常。不管使用哪种方式,都会导致栈帧被弹出。**
48 |
49 | ## 3.3 栈帧的内部结构
50 | | 局部变量表(Local Variables) | |
51 | | --- | --- |
52 | | 操作数栈(operand Stack)(或表达式栈) | |
53 | | 动态链接(DynamicLinking)(或指向运行时常量池的方法引用) | |
54 | | 方法返回地址(Return Address)(或方法正常退出或者异常退出的定义) | |
55 |
56 |
57 | ### 3.3.1 局部变量表
58 | 局部变量表:Local Variables,被称之为局部变量数组或本地变量表
59 |
定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量这些数据类型包括各类基本数据类型、对象引用(reference),以及returnAddress类型。
60 |
由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题
61 |
局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。
62 |
方法嵌套调用的次数由栈的大小决定。一般来说,栈越大,方法嵌套调用次数越多。对一个函数而言,它的参数和局部变量越多,使得局部变量表膨胀,它的栈帧就越大,以满足方法调用所需传递的信息增大的需求。进而函数调用就会占用更多的栈空间,导致其嵌套调用次数就会减少。
63 |
局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁。
64 |
65 | #### 3.3.1.1 关于Slot的理解
66 | 参数值的存放总是在局部变量数组的index0开始,到数组长度-1的索引结束。
67 |
68 | 局部变量表,最基本的存储单元是Slot(变量槽)
局部变量表中存放编译期可知的各种基本数据类型(8种),引用类型(reference),returnAddress类型的变量。
69 |
70 | 在局部变量表里,32位以内的类型只占用一个slot(包括returnAddress类型),64位的类型(long和double)占用两个slot。
71 |
72 | | 32位以内 | slot所占个数 |
73 | | --- | --- |
74 | | byte,short,float,int,char | 1 |
75 | | long和double | 2 |
76 |
77 | 
JVM会为局部变量表中的每一个Slot都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的局部变量值。
78 |
79 | 当一个实例方法被调用的时候,它的方法参数和方法体内部定义的局部变量将会按照顺序被复制到局部变量表中的每一个slot上。
80 |
81 | 如果需要访问局部变量表中一个64bit的局部变量值时,只需要使用前一个索引即可。(比如:访问long或double类型变量)
82 |
83 | 如果当前帧是由构造方法或者实例方法创建的,那么该对象引用this将会存放在index为0的slot处,其余的参数按照参数表顺序继续排列。

84 |
85 | #### 3.3.1.2 Slot的重复利用
86 | 栈帧中的局部变量表中的槽位是可以重用的,如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变就很有可能会复用过期局部变量的槽位,从而达到节省资源的目的。
87 |
88 | 
89 |
90 |
91 | #### 3.3.1.3 局部变量与静态变量的对比
92 |
93 | - 按照数据类型区分:基本数据类型,引用数据类型。
94 | - 按照类中声明的位置区分:成员变量(类变量,实例变量),局部变量。
95 | 1. 类变量:链接 过程的 准备 结算,给类变量默认赋值,初始化阶段给类变量显示赋值即静态代码块。
96 | 1. 实例变量:随着对象的创建,会在堆空间中分配实例变量空间,并进行默认赋值。
97 | 1. 局部变量:在使用前必须进行显式赋值,**不然编译不通过**。
98 |
99 | ### 3.3.2 操作数栈
100 | 每一个独立的栈帧除了包含局部变量表以外,还包含一个后进先出(Last - In - First -Out)的 **操作数栈**,也可以称之为 **表达式栈**(Expression Stack)
操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)和 出栈(pop)
101 |
102 | - 某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈。使用它们后再把结果压入栈
103 | - 比如:执行复制、交换、求和等操作。
104 |
105 |
106 |
操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。
107 |
108 | 操作数栈就是**JVM执行引擎**的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的。.
109 |
110 |
111 | #### 3.3.2.1 代码追踪:
112 | ```cpp
113 | public void test(){
114 | byte i = 3;
115 | int j = 2;
116 | int k = i + j;
117 | }
118 | ```
119 | 
此时iconst_3 相当于bipush 3,向操作数栈中push进去一个3。
注意 此时 PC寄存器&&局部变量表&&操作数栈的作用。
(PC寄存器记录 此时线程执行到哪一行,局部变量表存储哪些变量,操作数栈 做临时操作存储计算)
120 |
121 |
122 | #### 3.3.3.2 栈顶缓存技术
123 | 前面提过,基于栈式架构的虚拟机所使用的零地址指令更加紧凑,但完成一项操作的时候必然需要使用更多的入栈和出栈指令,这同时也就意味着将需要更多的指令分派(instruction dispatch)次数和内存读/写次数。
由于操作数是存储在内存中的,因此频繁地执行内存读/写操作必然会影响执行速度。为了解决这个问题,HotSpot JVM的设计者们提出了栈顶缓存(Tos,Top-of-Stack Cashing)技术,将栈顶元素全部缓存在物理CPU的寄存器中,以此降低对内存的读/写次数,提升执行引擎的执行效率。
124 |
125 | ### 3.3.3 动态链接
126 |
127 |
128 |
129 | # 引用
130 | [https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.iconst_i](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.iconst_i)
131 |
132 |
--------------------------------------------------------------------------------
/docs/notes/Java虚拟机/2.0 运行时数据区与内存/2.3 本地方法接口.md:
--------------------------------------------------------------------------------
1 |
2 | ## 什么是本地方法
3 | 简单地讲,一个Native Method 是一个Java调用非Java代码的接囗。一个Native Method是这样一个Java方法:该方法的实现由非Java语言实现,比如C。这个特征并非Java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "c" 告知c++编译器去调用一个c的函数。
"A native method is a Java method whose implementation is provided by non-java code."(本地方法是一个非Java的方法,它的具体实现是非Java代码的实现)
在定义一个native method时,并不提供实现体(有些像定义一个Java interface),因为其实现体是由非java语言在外面实现的。
本地接口的作用是融合不同的编程语言为Java所用,它的初衷是融合C/C++程序。

代码举例说明Native方法是如何编写的
4 | ```java
5 |
6 | public class IhaveNatives {
7 | public native void Native1(int x);
8 | native static public long Native2();
9 | native synchronized private float Native3(Object o);
10 | native void Natives(int[] ary) throws Exception;
11 | }
12 | ```
13 |
14 | - 需要注意的是:标识符native可以与其它java标识符连用,但是abstract除外
15 |
16 | ## 为什么使用Native Method?
17 | Java使用起来非常方便,然而有些层次的任务用Java实现起来不容易,或者我们对程序的效率很在意时,问题就来了。
18 |
19 | ### 与Java环境的交互
20 | 有时Java应用需要与Java外面的环境交互,这是本地方法存在的主要原因。你可以想想Java需要与一些底层系统,如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解Java应用之外的繁琐的细节。
21 |
22 |
23 | ### 与操作系统的交互
24 | JVM支持着Java语言本身和运行时库,它是Java程序赖以生存的平台,它由一个解释器(解释字节码)和一些连接到本地代码的库组成。然而不管怎样,它毕竟不是一个完整的系统,它经常依赖于一底层系统的支持。这些底层系统常常是强大的操作系统。通过使用本地方法,我们得以用Java实现了jre的与底层系统的交互,甚至JVM的一些部分就是用c写的。还有,如果我们要使用一些Java语言本身没有提供封装的操作系统的特性时,我们也需要使用本地方法。
25 |
26 |
27 | ### Sun's Java
28 | Sun的解释器是用C实现的,这使得它能像一些普通的C一样与外部交互。jre大部分是用Java实现的,它也通过一些本地方法与外界交互。例如:类java.lang.Thread的setpriority()方法是用Java实现的,但是它实现调用的是该类里的本地方法setpriorityo()。这个本地方法是用C实现的,并被植入JVM内部,在Windows 95的平台上,这个本地方法最终将调用Win32 setpriority()ApI。这是一个本地方法的具体实现由JVM直接提供,更多的情况是本地方法由外部的动态链接库(external dynamic link library)提供,然后被JVw调用。
29 |
--------------------------------------------------------------------------------
/docs/notes/Java虚拟机/2.0 运行时数据区与内存/2.4 本地方法栈.md:
--------------------------------------------------------------------------------
1 | Java虚拟机栈于管理Java方法的调用,而**本地方法栈用于管理本地方法的调用**。
2 |
3 | 本地方法栈,也是线程私有的。
4 |
5 | 允许被实现成**固定或者是可动态扩展**的内存大小。(在内存溢出方面是相同的);
6 |
7 | - 如果线程请求分配的栈容量超过本地方法栈允许的最大容量,Java虚拟机将会抛出一个stackoverflowError 异常。
8 | - 如果本地方法栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的本地方法栈,那么Java虚拟机将会抛出一个outofMemoryError异常。
9 |
10 |
11 |
本地方法是使用C语言实现的。
它的具体做法是Native Method Stack中登记native方法,在Execution Engine 执行时加载本地方法库。

当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。它和虚拟机拥有同样的权限。
12 |
13 | - 本地方法可以通过本地方法接口来访问虚拟机内部的运行时数据区。
14 | - 它甚至可以直接使用本地处理器中的寄存器
15 | - 直接从本地内存的堆中分配任意数量的内存。
16 |
17 | 并不是所有的JVM都支持本地方法。因为Java虚拟机规范并没有明确要求本地方法栈的使用语言、具体实现方式、数据结构等。如果JVM产品不打算支持native方法,也可以无需实现本地方法栈。
在Hotspot JVM中,直接将本地方法栈和虚拟机栈合二为一。
18 |
--------------------------------------------------------------------------------
/docs/notes/Java虚拟机/2.0 运行时数据区与内存/2.5 堆.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # 1. 堆的核心概念
4 |
5 | - 堆针对一个JVM进程来说是唯一的,也就是一个进程只有一个JVM
6 | - 进程包含多个线程,他们是**共享同一堆空间**的
7 | - 堆的大小可调节 -Xms=1024m
8 | - 《Java虚拟机规范》规定,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。
9 |
10 | # 2. 堆内存细分
11 | Java 8及之后堆内存逻辑上分为三部分:新生区+老年代+元空间(**方法区实现方式**)
堆空间内部结构,JDK1.8之前从永久代 替换成 元空间。
12 |
13 | 
14 |
15 | 

16 |
17 |
18 | ## 2.1 常用参数
19 |
20 | ### 2.1.1 设置堆内存大小与OOM
21 |
22 | - **“-Xms"用于表示堆区的起始内存,等价于-xx:InitialHeapSize**
23 | - **“-Xmx"则用于表示堆区的最大内存,等价于-XX:MaxHeapSize**
24 | - **“-Xmn”则用于表示新生代最大内存大小**
25 |
26 |
27 |
通常会将-Xms和-Xmx两个参数配置相同的值,其目的是**为了能够在ava垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能**。
28 |
29 | 默认情况下
30 |
31 | - 初始内存大小:物理电脑内存大小/64
32 | - 最大内存大小:物理电脑内存大小/4
33 |
34 | ### 2.1.2 配置新生代与老年代在堆结构的占比
35 |
36 |
37 | - 默认-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整个堆的1/3
38 | - 可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整个堆的1/5
39 |
40 |
41 |
42 |
43 | ### 2.1.3 配置Eden空间和Survivor 占比
44 | 在HotSpot中,Eden空间和另外两个survivor空间缺省所占的比例是8:1:1当然开发人员可以通过选项“-xx:SurvivorRatio”调整这个空间比例。比如-xx:SurvivorRatio=8
45 |
46 | ### 2.1.4 新生代过渡到老年代的年龄设置
47 | 可以设置参数:-Xx:MaxTenuringThreshold= N进行设置
48 |
49 |
50 | ## 2.2 GC 过程
51 | | Minor GC | 新生代的GC |
52 | | --- | --- |
53 | | Major GC | 老年代的GC |
54 | | Full GC | 整堆收集,收集整个Java堆和方法区的垃圾收集 |
55 |
56 | 我们都知道,JVM的调优的一个环节,也就是垃圾收集,我们需要尽量的避免垃圾回收,因为在垃圾回收的过程中,容易出现STW的问题
而 Major GC 和 Full GC出现STW的时间,是Minor GC的10倍以上
57 |
JVM在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代。
58 |
59 | 针对Hotspot VM的实现,它里面的GC按照回收区域又分为两大种类型:一种是部分收集(Partial GC),一种是整堆收集(FullGC)。
60 |
61 | **部分收集**:不是完整收集整个Java堆的垃圾收集。其中又分为:
62 |
63 | - 新生代收集(MinorGC/YoungGC):只是新生代的垃圾收集
64 | - 老年代收集(MajorGC/o1dGC):只是老年代的圾收集。
65 | - 目前,只有CMSGC会有单独收集老年代的行为。
66 | - 注意,很多时候Major GC会和Fu11GC混淆使用,需要具体分辨是老年代回收还是整堆回收。
67 | - 混合收集(MixedGC):收集整个新生代以及部分老年代的垃圾收集。
68 | - 目前,只有G1 GC会有这种行为。
69 |
70 | 整堆收集(FullGC):收集整个java堆和方法区的垃圾收集。
71 |
72 |
73 |
74 | ### 2.2.1 Minor GC
75 | | **何时发生Minor GC** | 当年轻代空间不足时,就会触发MinorGC,这里的年轻代满指的是Eden代满,Survivor满不会引发GC。(每次Minor GC会清理年轻代的内存。) |
76 | | --- | --- |
77 | | **Minor GC 特性** | 频率高,回收速度快。因为大部分对象朝生夕死。 |
78 | | **是否会引发STW** | 会,暂停其他用户线程。但是比Major GC引发的STW影响小。 |
79 |
80 | 我们创建的对象,一般都是存放在Eden区的,当我们Eden区满了后,就会触发GC操作,一般被称为 YGC / Minor GC操作.
81 |
82 | 
83 |
84 | - 当我们进行一次垃圾收集后,红色的将会被回收,而绿色的还会被占用着,存放在S0(Survivor From)区。同时我们给每个对象设置了一个年龄计数器,一次回收后就是1。
85 | - 同时Eden区继续存放对象,当Eden区再次存满的时候,又会触发一个MinorGC操作,此时GC将会把 Eden和Survivor From中的对象 进行一次收集,把存活的对象放到 Survivor To区,同时让年龄 + 1
86 |
87 | 
88 |
89 | - 我们继续不断的进行对象生成 和 垃圾回收,当Survivor中的对象的年龄达到15的时候,将会触发一次 Promotion晋升的操作,也就是将年轻代中的对象 晋升到 老年代中。
90 |
91 | 
92 |
93 | #### 2.2.1.1 对象分配的特殊情况
94 | 
95 |
96 | #### 2.2.2.2 动态对象年龄判定
97 | ```cpp
98 | Hotspot 遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,
99 | 当累积的某个年龄大小超过了 survivor 区的一半时,
100 | 取这个年龄和 MaxTenuringThreshold 中更小的一个值,作为新的晋升年龄阈值
101 | ```
102 | **动态年龄计算的代码如下:**
103 | ```cpp
104 | uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
105 | //survivor_capacity是survivor空间的大小
106 | size_t desired_survivor_size = (size_t)((((double)survivor_capacity)*TargetSurvivorRatio)/100);
107 | size_t total = 0;
108 | uint age = 1;
109 | while (age < table_size) {
110 | //sizes数组是每个年龄段对象大小
111 | total += sizes[age];
112 | if (total > desired_survivor_size) {
113 | break;
114 | }
115 | age++;
116 | }
117 | uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
118 | ...
119 | }
120 | ```
121 |
122 | ### 2.2.2 Major GC
123 | | **何时发生Major GC** | 也就是在老年代空间不足时,会先尝试触发MinorGc。如果之后空间还不足,则触发Major GC |
124 | | --- | --- |
125 | | **Major GC 特性** | Major GC的速度一般会比MinorGc慢1e倍以上,STW的时间更长,如果Major GC后,内存还不足,就报OOM了 |
126 | | **是否会引发STW** | 会,后果也更严重,时间更长。 |
127 |
128 |
129 |
130 |
131 | ###
132 |
133 | ### 2.2.3 Full GC
134 | 触发FullGC执行的情况有如下五种:
135 |
136 | - 调用System.gc()时,系统建议执行FullGC,但是不必然执行
137 | - 老年代空间不足
138 | - 方法区空间不足
139 | - 通过Minor GC后进入老年代的平均大小大于老年代的可用内存
140 | - 由Eden区、survivor spacee(From Space)区向survivor spacel(To Space)区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
141 |
142 | 说明:Full GC 是开发或调优中尽量要避免的。这样暂时时间会短一些
143 |
144 | ## 2.3 堆内存常见的分配策略
145 |
146 | ### 2.3.1 对象优先在 eden 区分配
147 | [测试案例](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/jvm/JVM%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6.md#14-%E5%8A%A8%E6%80%81%E5%AF%B9%E8%B1%A1%E5%B9%B4%E9%BE%84%E5%88%A4%E5%AE%9A)
148 |
149 | #### 2.3.1.1 分配担保机制
150 | 只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行Minor GC,否则将进行Full GC。
151 |
152 | 
153 |
154 | ### 2.3.2 大对象直接进入到老年代
155 | 大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。详细原因看2.2 GC过程。
156 |
157 | ### 2.3.3 长期存活的对象将进入老年代
158 | 既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。
如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1.对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。
159 |
160 | ### 2.3.4 动态对象年龄判定
161 | 详情看 2.2.2.2
162 |
163 |
--------------------------------------------------------------------------------
/docs/notes/Java虚拟机/2.0 运行时数据区与内存/2.6 方法区.md:
--------------------------------------------------------------------------------
1 | 
从线程共享与否的角度来看
2 |
3 | 
4 |
5 | # 1.栈,堆,方法区的交互关系
6 | 
7 |
8 | # 2.方法区的理解
9 |
10 | - **含义:**
11 |
12 | 《Java虚拟机规范》中明确说明:“尽管所有的方法区在逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。”
但对于HotSpotJVM而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。
所以,方法区看作是一块独立于Java堆的内存空间。
13 |
14 | - **特性:**
15 | - [x] 方法区(Method Area)与Java堆一样,是各个线程共享的内存区域。
16 | - [x] 方法区在JVM启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的。
17 | - [x] 方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展。
18 | - [x] 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误。
19 |
20 | java.lang.OutOfMemoryError:Metaspace
21 |
22 | 1. 加载大量的第三方的jar包
23 | 1. 大量动态的生成反射类
24 |
25 | # 3.设置方法区大小与OOM
26 |
27 | ## 3.1 参数设置
28 | 元数据区大小可以使用参数 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize指定
29 |
30 | ## 3.2 引起OOM的两大因素
31 | 确认内存中的对象是否是必要的,也就是要先分清楚到底是出现了**内存泄漏**(Memory Leak)还是**内存溢出**(Memory Overflow)
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/docs/notes/Java虚拟机/3.对象实例化内存布局与访问定位.md:
--------------------------------------------------------------------------------
1 |
2 | ## 3.1 创建对象的方式
3 | - new Object(): 最常见的方法
4 | - [x] 单例类中调用getInstance的静态类方法
5 | - Class的newInstance方法
6 | - 使用clone():不调用任何的构造器
7 | - 使用序列化:序列化一般用于Socket的网络传输
8 | - 第三方库 Objenesis
9 |
10 | ## 3.2 创建对象的步骤
11 |
12 | ### 3.2.1 判断对象对应的类是否加载、链接、初始化
13 | 虚拟机遇到一条new指令,首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载,解析和初始化。(即判断类元信息是否存在)。如果没有,那么在双亲委派模式下,使用当前类加载器以ClassLoader + 包名 + 类名为key进行查找对应的 .class文件,如果没有找到文件,则抛出ClassNotFoundException异常,如果找到,则进行类加载,并生成对应的Class对象。
14 |
15 | ### 3.2.2 为对象分配内存
16 | 首先计算对象占用空间的大小,接着在堆中划分一块内存给新对象。如果实例成员变量是引用变量,仅分配引用变量空间即可,即4个字节大小;
17 |
18 | 如果内存规整,采用指针碰撞的方式分配;
如果内存不规整,虚拟机需要维护一个空闲列表分配。
19 |
20 |
21 | #### 3.2.2.1 指针碰撞
22 | 所有用过的内存放在一边,空闲的内存在另一边,中间放着一个指针作为分界点的指示器,分配内存就仅仅是把指针指向空闲挪动一段与对象大小相等的距离。
23 |
24 | #### 3.2.2.2 空闲列表
25 | 如果内存不是规整的,已使用的内存和未使用的内存相互交错,那么虚拟机将采用的是空闲列表来为对象分配内存。意思是虚拟机维护了一个列表,记录上那些内存块是可用的,再分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的内容。这种分配方式成为了 “空闲列表(Free List)”
26 |
27 | 选择哪种分配方式由Java堆是否规整所决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
28 |
29 | ####
30 |
31 | ### 3.2.3 处理并发问题
32 | 对象创建是非常频繁的行为,还需要考虑并发情况下,仅仅修改一个指针所指向的位置也是不安全的,例如正在给对象A分配内存,指针还未修改,对象B又使用原来的指针分配内存。解决问题有两种可选方案:
33 |
34 | #### 3.2.3.1 CAS同步控制
35 | 对分配内存空间的动作进行同步处理。实际上虚拟机采取CAS配上失败重试的方式保证更新操作的原子性。
36 |
37 | #### 3.2.3.2 TLAB 本地线程分配缓冲
38 | 把内存分配的动作按照线程划分到不同的空间中进行,每个线程在Java堆中,预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB),只有本地缓冲区用完了,分配新的缓存区时才需要同步锁定。
39 |
40 | - 虚拟机是否使用TLAB,可以通过-XX: +/-UseTLAB参数来设定。
41 |
42 | ### 3.2.4 初始化分配到的内存空间
43 |
44 |
45 |
46 | ### 3.2.5 设置对象的对象头
47 |
48 |
49 |
50 | ### 3.2.6 执行init方法进行初始化
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/docs/notes/Java虚拟机/4.直接内存.md:
--------------------------------------------------------------------------------
1 |
2 | ## 4.1 概念
3 | 不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。
直接内存是在Java堆外的、直接向系统申请的内存区间。
4 |
5 | ## 4.2 为啥要使用native内存
6 | 通常,访问直接内存的速度会优于Java堆。即读写性能高。
7 |
8 | - 因此出于性能考虑,读写频繁的场合可能会考虑使用直接内存。
9 | - Java的NIO库允许Java程序使用直接内存,用于数据缓冲区
10 |
11 | ## 4.2 操作native内存的方法
12 | ```java
13 | 直接内存大小可以通过MaxDirectMemorySize设置;
14 | 如果不指定,默认与堆的最大值-xmx参数值一致
15 | ```
16 |
17 | ### 4.2.1 BIO
18 |
19 | ### 4.2.2 NIO
20 | 通过存在堆中的DirectByteBuffer操作Native内存
21 |
22 |
23 | ## 4.3 存在的问题
24 |
25 | - 也可能导致outofMemoryError异常
26 |
27 | 由于直接内存在Java堆外,因此它的大小不会直接受限于-xmx指定的最大堆大小,但是系统内存是有限的,Java堆和直接内存的总和依然受限于操作系统能给出的最大内存。 缺点
28 |
29 | - 分配回收成本较高
30 | - 不受JVM内存回收管理
31 |
--------------------------------------------------------------------------------
/docs/notes/Java虚拟机/5.执行引擎.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/docs/notes/Java虚拟机/5.执行引擎.md
--------------------------------------------------------------------------------
/docs/notes/分布式/ZooKeeper/README.md:
--------------------------------------------------------------------------------
1 | # ZooKeeper 专题
2 |
3 | - [ZooKeeper服务注册中心原理](ZooKeeper服务注册中心原理.md)
--------------------------------------------------------------------------------
/docs/notes/分布式/ZooKeeper/ZooKeeper服务注册中心原理.md:
--------------------------------------------------------------------------------
1 | # ZooKeeper实现服务注册的原理
2 | ## 1.ZooKeeper中的节点
3 | ```
4 | ZooKeeper是一个树形结构的目录服务,支持变更推送,因此适合当Dubbo服务的注册中心。
5 | ```
6 | - 节点分类(根据维度来划分):
7 | - 构成集群的机器,我们称之为机器节点。
8 | - 数据模型中的数据单元,称之为数据节点ZNode。
9 | ```
10 | ZooKeeper将所有数据存储在内存中,数据模型是一棵树(ZNode Tree),由斜杠(/)进行分割的路径,就是一个ZNode,例如/foo/path1。每个ZNode上都会保存自己的数据内容,同时还会保存一系列属性信息。
11 | ```
12 | ```
13 | 在ZooKeeper中,数据节点Znode可分为持久节点和临时节点两类,所谓持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNode将一直保存在ZooKeeper上。而临时节点就不一样了,它的生命周期和客户端会话绑定,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。
14 | ```
15 | 
16 | - 上图为 :基于ZooKeeper实现的注册中心节点结构示意
17 | - /dubbo:这是dubbo在ZooKeeper上创建的根节点;
18 | - /dubbo/com.foo.BarService:这是服务节点,代表了Dubbo的一个服务;
19 | - /dubbo/com.foo.BarService/providers:这是服务提供者的根节点,其子节点代表了每一个服务真正的提供者;
20 | - /dubbo/com.foo.BarService/consumers:这是服务消费者的根节点,其子节点代表每一个服务真正的消费者;
21 |
22 | ## 2.注册中心的工作流程
23 | ```
24 | 接下来以上述的BarService为例,说明注册中心的工作流程。
25 | ```
26 | ### 2.1 服务提供方启动
27 |
28 | 服务提供者在启动的时候,会在ZooKeeper上注册服务。所谓注册服务,其实就是在ZooKeeper的/dubbo/com.foo.BarService/providers节点下创建一个子节点,并写入自己的URL地址, 这就代表了com.foo.BarService这个服务的一个提供者。
29 |
30 | ### 2.2 服务消费方启动
31 |
32 | 服务消费者在启动的时候,会向ZooKeeper注册中心订阅自己的服务。其实,就是读取并订阅ZooKeeper上/dubbo/com.foo.BarService/providers节点下的所有子节点,并解析出所有提供者的URL地址来作为该服务地址列表。
33 | 同时,**服务消费者还会在ZooKeeper的/dubbo/com.foo.BarService/consumers节点下创建一个临时节点,并写入自己的URL地址,这就代表了com.foo.BarService这个服务的一个消费者。**
34 |
35 | ### 2.3 消费者远程调用提供者
36 | ```
37 | 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一个提供者进行调用,如果调用失败,再选另一个提供者调用。
38 | ```
39 |
40 | ### 2.4 增加服务提供者
41 | ```
42 | 增加提供者,也就是在providers下面新建子节点。一旦服务提供方有变动,zookeeper就会把最新的服务列表推送给消费者
43 | ```
44 |
45 | ### 2.5 减少服务提供者
46 | **所有提供者在ZooKeeper上创建的节点都是临时节点,利用的是临时节点的生命周期和客户端会话相关的特性,因此一旦提供者所在的机器出现故障导致该提供者无法对外提供服务时,该临时节点就会自动从ZooKeeper上删除,同样,zookeeper会把最新的服务列表推送给消费者。**
47 |
48 | ### 2.6 ZooKeeper宕机以后
49 |
50 | 消费者每次调用服务提供方是不经过ZooKeeper的,消费者只是从zookeeper那里获取服务提供方地址列表。所以当zookeeper宕机之后,不会影响消费者调用服务提供者,影响的是zookeeper宕机之后如果提供者有变动,增加或者减少,无法把最新的服务提供者地址列表推送给消费者,所以消费者感知不到。
51 |
--------------------------------------------------------------------------------
/docs/notes/分布式/分布式事务/分布式事务基础概念.md:
--------------------------------------------------------------------------------
1 |
2 | * [分布式事务介绍](#分布式事务介绍)
3 | * [1. 分布式事务简介](#1-分布式事务简介)
4 | * [2. 应用场景](#2-应用场景)
5 | * [2.1 假设没有分布式事务](#21-假设没有分布式事务)
6 | * [3. 分布式事务的实现](#3-分布式事务的实现)
7 | * [3.1 XA二阶段提交协议](#31-xa二阶段提交协议)
8 | * [3.1.1 魔兽世界组队模式](#311-魔兽世界组队模式)
9 | * [3.1.2 二阶段提交协议](#312-二阶段提交协议)
10 | * [3.1.2.1 二阶段提交协议的算法思想](#3121-二阶段提交协议的算法思想)
11 | * [3.1.2.2 2PC正向流程](#3122-2pc成功流程)
12 | * [3.1.2.3 2PC逆向流程](#3123-2pc失败流程)
13 | * [3.1.2.4 XA两阶段提交的不足](#3124-xa两阶段提交的不足)
14 | * [3.2 XA三阶段提交协议](#32-xa三阶段提交协议)
15 | * [3.2.1 相比2PC的改动](#321-相比2pc的改动)
16 | * [3.2.2.1 CanCommit阶段](#3221-cancommit阶段)
17 | * [3.2.2.2 PreCommit阶段](#3222-precommit阶段)
18 | * [3.2.2.3 DoCommit阶段](#3223-docommit阶段)
19 |
20 |
21 | # 分布式事务介绍
22 | ## 1. 分布式事务简介
23 | ```
24 | 分布式事务用于在分布式系统中保证不同节点之间的数据一致性。
25 | 指事务的操作位于不同的节点上,需要保证事务的 AICD 特性。
26 | ```
27 | - 产生原因
28 | - 数据库分库分表
29 | - SOA 架构,比如一个电商网站将订单业务和库存业务分离出来放到不同的节点上;
30 | ## 2. 应用场景
31 | ### 2.1 假设没有分布式事务
32 | ```
33 | 我们以电商交易业务为例子:
34 | ```
35 | 
36 | ```
37 | 上图中包含了库存和订单两个独立的微服务,每个微服务维护了自己的数据库。
38 | 在交易系统的业务逻辑中,一个商品在下单之前需要先调用库存服务,进行扣除库存,再调用订单服务,创建订单记录。
39 | ```
40 | ```
41 | 正常情况下,两个数据库各自更新成功,两边数据维持着一致性。
42 | ```
43 | 
44 | ```
45 | 但是,在非正常情况下,有可能库存的扣减完成了,随后的订单记录却因为某些原因插入失败。
46 | 这个时候,两边数据就失去了应有的一致性。
47 | ```
48 | 
49 | ```
50 | 单数据源的一致性依靠单机事务来保证,多数据源的一致性就要依靠分布式事务;
51 | ```
52 | ## 3. 分布式事务的实现
53 | ```
54 | 分布式事务的实现有很多种,最具有代表性的是由Oracle Tuxedo系统提出的XA分布式事务协议;
55 | ```
56 | ```
57 | XA协议包含两阶段提交(2PC)和三阶段提交(3PC)两种实现,
58 | 这里我们重点介绍两阶段提交的具体过程;
59 | ```
60 | ### 3.1 XA二阶段提交协议
61 | #### 3.1.1 魔兽世界组队模式
62 | ```
63 | 在魔兽世界这款游戏中,副本组团打BOSS的时候,为了更方便队长与队员们之间的协作,
64 | 队长可以发起一个“就位确认”的操作:
65 | ```
66 | 
67 | ```
68 | 当队员收到就位确认提示后,如果已经就位,就选择“是”,如果还没就位,就选择“否”。
69 | ```
70 | 
71 | ```
72 | 当队长收到了所有人的就位确认,就会向所有队员们发布消息,告诉他们开始打BOSS。
73 | ```
74 | 
75 | ```
76 | 相应的,在队长发起就位确认的时候,有可能某些队员还并没有就位
77 | ```
78 | 
79 |
80 | 
81 |
82 | 
83 | ```
84 | 以上就是魔兽世界当中组团打BOSS的确认流程。这个流程和XA分布式事务协议的两阶段提交非常相似。
85 | 打魔兽,要么一起上,要么都不上!!!
86 | ```
87 | #### 3.1.2 二阶段提交协议
88 | ##### 3.1.2.1 二阶段提交协议的算法思想
89 | ```
90 | 在XA协议中包含着两个角色:事务协调者(部落首领)和事务参与者(部落小兵)。
91 |
92 | 二阶段(Two-phaseCommit,简称2PC)提交协议的算法思想概括:
93 | 参与者将操作成败通知协调者,再有协调者根据所有参与者的反馈情况决定各参与者是否要提交操作还是终止操作;
94 | ```
95 | - **2PC提交协议**:
96 | 主要保证了分布式事务的原子性,即所有节点要么全做,要么全不做;
97 | ##### 3.1.2.2 2PC正向流程
98 | - 第一阶段
99 |
100 | 
101 | ```
102 | 在XA分布式事务的第一阶段,作为事务协调者的节点会首先向所有的参与者节点发送Prepare请求。
103 |
104 | ```
105 | ```
106 | 在接到Prepare请求之后,每一个参与者节点会各自执行与事务有关的数据更新,写入Undo Log和Redo Log。
107 | 如果参与者执行成功,暂时不提交事务,而是向事务协调节点返回“完成”消息。
108 | 当事务协调者接到了所有参与者的返回消息,整个分布式事务将会进入第二阶段。
109 | ```
110 | - 第二阶段
111 |
112 | 
113 | ```
114 | 在XA分布式事务的第二阶段,如果事务协调节点在之前所收到都是正向返回,那么它将会向所有事务参与者发出Commit请求;
115 | ```
116 | ```
117 | 接到Commit请求之后,事务参与者节点会各自进行本地的事务提交,并释放锁资源。
118 | 当本地事务完成提交后,将会向事务协调者返回“完成”消息。
119 | 当事务协调者接收到所有事务参与者的“完成”反馈,整个分布式事务完成。
120 | ```
121 | ```
122 | 以上所描述的是XA两阶段提交的正向流程,接下来我们看一看失败情况的处理流程:
123 | ```
124 | ##### 3.1.2.3 2PC逆向流程
125 | - 第一阶段:
126 |
127 | 
128 |
129 | - 第二阶段:
130 |
131 | 
132 |
133 | ```
134 | 在XA的第一阶段,如果某个事务参与者反馈失败消息,说明该节点的本地事务执行不成功,必须回滚。
135 | ```
136 | ```
137 | 在XA的第一阶段,如果某个事务参与者反馈失败消息,说明该节点的本地事务执行不成功,必须回滚。
138 | 于是在第二阶段,事务协调节点向所有的事务参与者发送Abort请求。接收到Abort请求之后,
139 | 各个事务参与者节点需要在本地进行事务的回滚操作,回滚操作依照Undo Log来进行。
140 | 以上就是XA两阶段提交协议的详细过程。
141 | ```
142 | ##### 3.1.2.4 XA两阶段提交的不足
143 | ```
144 | 2PC提交协议确实可以保证原子性操作,但是存在很多缺点:
145 | ```
146 | - **同步阻塞问题**: 执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
147 | - **单点故障**:
148 | 由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
149 | - **数据不一致**:在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象;
150 | - **事务状态不明确**:
151 | 协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
152 |
153 | ### 3.2 XA三阶段提交协议
154 | #### 3.2.1 相比2PC的改动
155 | - 引入超时机制
156 | ```
157 | 在协调者和参与者中同时引入超时机制;
158 | ```
159 | - 添加PreCommit阶段
160 | ```
161 | 在第一阶段和第二阶段中插入一个准备阶段.保证了在最后提交阶段之前各参与节点的状态是一致的;
162 | ```
163 | #### 3.2.2 3PC阶段分析
164 | ##### 3.2.2.1 CanCommit阶段
165 | ```
166 | 3PC的PreCommit阶段其实和2PC的准备阶段相似.协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应;
167 | ```
168 | - 事务询问
169 | ```
170 | 协调者向参与者发送CanCommit请求(可以理解为是否可以提交的意愿),询问是否可以执行事务提交操作,然后开始等待参与者的响应;
171 | ```
172 | - 响应反馈
173 | ```
174 | 参与者接收到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态,否则反馈No;
175 | ```
176 | ##### 3.2.2.2 PreCommit阶段
177 | ```
178 | 协调者根据参与者的反应情况来决定是否可以记性事务的PreCommit操作.根据响应情况,有以下两种可能。
179 | ```
180 | - 1.假如协调者从所有的参与者获得的反馈都是Yes响应,那么会执行事务的预执行.
181 | ```
182 | [1]发送预提交请求: 协调者向参与者发送PreCommit请求,并进入Prepared阶段;
183 | [2]事务预提交:参与者接收到PreCommit请求之后,会执行事务操作,并将undo和redo信息记录到事务日志中;
184 | [3]响应反馈:如果参与者成功地执行了事务操作,则返回ACK响应,同时开始等待最终命令;
185 | ```
186 | - 2.假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断
187 | ```
188 | [1]发送中断请求:协调者向所有参与者发送abort请求;
189 | [2]中断事务:参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断;
190 | ```
191 | ##### 3.2.2.3 DoCommit阶段
192 | 该阶段进行真正的事务提交,也可以分为以下两种情况。
193 | - 1.**执行提交**
194 | ```
195 | [1].发送提交请求:协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态;
196 | [2]事务提交:参与者接收到doCommit请求之后,执行正式的事务提交.并在完成事务之后释放所有事务资源.
197 | [3]响应反馈:事务提交之后,向协调者发送ACK响应;
198 | [4]完成事务:协调者接收到所有参与者的ACK响应之后,完成事务;
199 | ```
200 | - 2.**中断事务**
201 | 协调者没有接收到参与者发送的ACK请求(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务;
202 | ```
203 | [1]发送中断请求:协调者向所有参与者发送abort请求;
204 |
205 | [2](TODO)事务回滚:参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源;
206 |
207 | [3]反馈结果:参与者完成事务回滚之后,向协调者发送ACK消息;
208 | [4]中断事务:协调者接收到参与者反馈的ACK消息之后,执行消息的中断;
209 | ```
210 | ```
211 | 在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者rebort请求时,会等待超时之后,会继续进行事务的提交。
212 | (其实这个应该是基于概率来决定的,当进入第三阶段时,说明参与者在第二阶段已经收到了PreCommit请求,
213 | 那么协调者产生PreCommit请求的前提条件是他在第二阶段开始之前,收到所有参与者的CanCommit响应都是Yes.
214 | (一旦参与者收到了PreCommit,意味着他知道大家都同意修改了)所以,一句话概括,当进入第三阶段时,由于网络超时等原因,虽然参与者没有收到commit或者abort响应,但是他有理由相信:成功提交的几率很大.)
215 | ```
216 |
217 |
218 |
219 |
--------------------------------------------------------------------------------
/docs/notes/分布式/分布式锁/分布式锁的实现.md:
--------------------------------------------------------------------------------
1 | # 分布式锁的实现
2 | - 为什么需要锁?
3 | - 1.多任务环境(分布式环境)需要
4 | - 2.任务都需要对同一共享资源进行写操作
5 | - 3.对资源的访问是互斥的
6 | ```
7 | 任务通过竞争获取锁才能对该资源进行操作(竞争锁);
8 | 当有一个任务在对资源进行更新时(占有锁),其他任务都不可以对这个资源进行操作(任务阻塞),直到该任务完成更新。
9 | ```
10 | 高效分布式锁所需要的几点特征:
11 | - 1.互斥
12 | - 2.防止死锁
13 | - 3.性能
14 | - 4.可重入
15 | ## 1.基于数据库实现
16 |
17 | ## 2.基于Redisson实现
18 | [Redisson实现分布式锁原理](https://www.cnblogs.com/AnXinliang/p/10019389.html)
19 | [原理2](https://www.cnblogs.com/qdhxhz/p/11046905.html)
20 | [原理3](https://www.jb51.net/article/125931.htm)
21 | 
22 |
23 | ## 3.基于ZooKeeper实现
24 | [ZOOKEEPER分布式锁原理](https://www.bilibili.com/video/av39142828?from=search&seid=468319870292607825)
25 | 
26 |
--------------------------------------------------------------------------------
/docs/notes/基础框架/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/docs/notes/基础框架/README.md
--------------------------------------------------------------------------------
/docs/notes/基础框架/Spring/IOC 和 AOP.md:
--------------------------------------------------------------------------------
1 | # IOC
2 |
3 | # AOP
4 | ## [深入理解JDK动态代理机制](https://www.jianshu.com/p/471c80a7e831)
5 | - JDK静态代理
6 | ```
7 | 静态代理:
8 | 创建一个接口,然后创建被代理的类实现该接口并且实现该接口中的抽象方法。之后再创建一个代理类,同时使其也实现这个接口。
9 | 在代理类中持有一个被代理对象的引用,而后在代理类方法中调用该对象的方法。
10 |
11 | 其实就是代理类为被代理类预处理消息、过滤消息并在此之后将消息转发给被代理类,之后还能进行消息的后置处理。
12 | 代理类和被代理类通常会存在关联关系(即上面提到的持有的被带离对象的引用),代理类本身不实现服务,而是通过调用被代理类中的方法来提供服务。
13 | 缺点:
14 | 由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐;
15 | ```
16 | - JDK动态代理
17 | ```
18 |
19 | ```
20 | ## [深入理解CGLIB动态代理机制](https://www.jianshu.com/p/9a61af393e41)
--------------------------------------------------------------------------------
/docs/notes/基础框架/Spring/SpringBoot常见面试题.md:
--------------------------------------------------------------------------------
1 | # SpringBoot 常见面试题
2 | ## 1.SpringBoot启动原理
3 | [SpringBoot启动原理](http://tengj.top/2017/03/09/springboot3/)
4 | ```
5 | @SpringBootApplication
6 | |
7 | @EnableAutoConfiguration
8 | |
9 | @Import(EnableAutoConfigurationImportSelector.class)
10 | |
11 | SpringFactoriesLoader
12 |
13 | ```
14 |
--------------------------------------------------------------------------------
/docs/notes/基础框架/Spring/SpringMVC原理.md:
--------------------------------------------------------------------------------
1 | # SpringMVC 运行机制
2 | ## 1. SpringMVC原理
3 | 
4 | - SpringMVC请求流程,一般分为12个步骤:
5 | - 1.用户发起请求,由前端控制器DispatcherServlet处理;
6 | - 2.前端控制器通过处理器映射器查找handler,可以根据XML或者注解去找;
7 | - 3.处理器映射器返回执行链;
8 | - 4.前端控制器请求处理器适配器来执行handler;
9 | - 5.处理器适配器来执行handler
10 | - 6.处理业务完成之后,会给处理器适配器返回ModelAndView对象,其中有视图名称,模型数据;
11 | - 7.处理器适配器将视图名称和模型数据返回给前端控制器;
12 | - 8.前端控制器通过视图解析器来对视图进行解析;
13 | - 9.视图解析器返回真正的视图给前端控制器;
14 | - 10.前端控制器通过返回的视图和数据进行渲染;
15 | - 11.返回渲染完成的视图;
16 | - 12.将最终的视图返回给用户,产生响应;
17 |
18 | ## 2. 转发和重定向区别
19 | ```
20 | https://www.cnblogs.com/fifiyong/p/5949689.html
21 | ```
--------------------------------------------------------------------------------
/docs/notes/基础框架/Spring/img/springmvc-01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NewbiesCamp/Java-Programmer-Advancement-Program/0ab8c29079bea40026c0ba55130d0c9bea85ebd8/docs/notes/基础框架/Spring/img/springmvc-01.jpg
--------------------------------------------------------------------------------
/docs/notes/基础框架/自定义注解原理及实现.md:
--------------------------------------------------------------------------------
1 | # 自定义注解原理及实现
2 | ## 元注解
3 | ### 1. @Retention
4 | ```
5 | 英文单词Retention : 保留;
6 | 当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
7 | ```
8 | #### 1.1 @Retention取值
9 | - RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
10 | - RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
11 | - RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。
12 |
13 | ```
14 | 我们可以这样的方式来加深理解,@Retention 去给一张标签解释的时候,它指定了这张标签张贴的时间。@Retention 相当于给一张标签上面盖了一张时间戳,时间戳指明了标签张贴的时间周期。
15 | ```
16 | ### 2.@Target
17 | ```
18 | Target 是目标的意思,@Target 指定了注解运用的地方。
19 | 你可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。
20 | 类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。
21 | ```
22 | #### 2.1 @Target取值
23 | - ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
24 | - ElementType.CONSTRUCTOR 可以给构造方法进行注解
25 | - ElementType.FIELD 可以给属性进行注解
26 | - ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
27 | - ElementType.METHOD 可以给方法进行注解
28 | - ElementType.PACKAGE 可以给一个包进行注解
29 | - ElementType.PARAMETER 可以给一个方法内的参数进行注解
30 | - ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
31 |
32 | ### 3.@Inherited
33 | ```
34 | Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
35 | ```
36 | ####
37 | ### ***借鉴***
38 | [自定义注解](https://blog.csdn.net/briblue/article/details/73824058)
--------------------------------------------------------------------------------
/docs/notes/工具及组件/JRebel热部署工具.md:
--------------------------------------------------------------------------------
1 | # JRebel 热部署工具
2 | [JRebel真正的热部署](https://blog.csdn.net/is_chenzh/article/details/82148157)
--------------------------------------------------------------------------------
/docs/notes/工具及组件/MarkDown使用手册.md:
--------------------------------------------------------------------------------
1 | # 欢迎使用马克飞象
2 |
3 | @(示例笔记本)[马克飞象|帮助|Markdown]
4 |
5 | **马克飞象**是一款专为印象笔记(Evernote)打造的Markdown编辑器,通过精心的设计与技术实现,配合印象笔记强大的存储和同步功能,带来前所未有的书写体验。特点概述:
6 |
7 | - **功能丰富** :支持高亮代码块、*LaTeX* 公式、流程图,本地图片以及附件上传,甚至截图粘贴,工作学习好帮手;
8 | - **得心应手** :简洁高效的编辑器,提供[桌面客户端][1]以及[离线Chrome App][2],支持移动端 Web;
9 | - **深度整合** :支持选择笔记本和添加标签,支持从印象笔记跳转编辑,轻松管理。
10 | -------------------
11 |
12 | [TOC]
13 |
14 | ## Markdown简介
15 |
16 | > Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [维基百科](https://zh.wikipedia.org/wiki/Markdown)
17 |
18 | 正如您在阅读的这份文档,它使用简单的符号标识不同的标题,将某些文字标记为**粗体**或者*斜体*,创建一个[链接](http://www.example.com)或一个脚注[^demo]。下面列举了几个高级功能,更多语法请按`Ctrl + /`查看帮助。
19 |
20 | ### 代码块
21 | ``` python
22 | @requires_authorization
23 | def somefunc(param1='', param2=0):
24 | '''A docstring'''
25 | if param1 > param2: # interesting
26 | print 'Greater'
27 | return (param2 - param1 + 1) or None
28 | class SomeClass:
29 | pass
30 | >>> message = '''interpreter
31 | ... prompt'''
32 | ```
33 | ### LaTeX 公式
34 |
35 | 可以创建行内公式,例如 $\Gamma(n) = (n-1)!\quad\forall n\in\mathbb N$。或者块级公式:
36 |
37 | $$ x = \dfrac{-b \pm \sqrt{b^2 - 4ac}}{2a} $$
38 |
39 | ### 表格
40 | | Item | Value | Qty |
41 | | :-------- | --------:| :--: |
42 | | Computer | 1600 USD | 5 |
43 | | Phone | 12 USD | 12 |
44 | | Pipe | 1 USD | 234 |
45 |
46 | ### 流程图
47 | ```flow
48 | st=>start: Start
49 | e=>end
50 | op=>operation: My Operation
51 | cond=>condition: Yes or No?
52 |
53 | st->op->cond
54 | cond(yes)->e
55 | cond(no)->op
56 | ```
57 |
58 | 以及时序图:
59 |
60 | ```sequence
61 | Alice->Bob: Hello Bob, how are you?
62 | Note right of Bob: Bob thinks
63 | Bob-->Alice: I am good thanks!
64 | ```
65 |
66 | > **提示:**想了解更多,请查看**流程图**[语法][3]以及**时序图**[语法][4]。
67 |
68 | ### 复选框
69 |
70 | 使用 `- [ ]` 和 `- [x]` 语法可以创建复选框,实现 todo-list 等功能。例如:
71 |
72 | - [x] 已完成事项
73 | - [ ] 待办事项1
74 | - [ ] 待办事项2
75 |
76 | > **注意:**目前支持尚不完全,在印象笔记中勾选复选框是无效、不能同步的,所以必须在**马克飞象**中修改 Markdown 原文才可生效。下个版本将会全面支持。
77 |
78 |
79 | ## 印象笔记相关
80 |
81 | ### 笔记本和标签
82 | **马克飞象**增加了`@(笔记本)[标签A|标签B]`语法, 以选择笔记本和添加标签。 **绑定账号后**, 输入`(`自动会出现笔记本列表,请从中选择。
83 |
84 | ### 笔记标题
85 | **马克飞象**会自动使用文档内出现的第一个标题作为笔记标题。例如本文,就是第一行的 `欢迎使用马克飞象`。
86 |
87 | ### 快捷编辑
88 | 保存在印象笔记中的笔记,右上角会有一个红色的编辑按钮,点击后会回到**马克飞象**中打开并编辑该笔记。
89 | >**注意:**目前用户在印象笔记中单方面做的任何修改,马克飞象是无法自动感知和更新的。所以请务必回到马克飞象编辑。
90 |
91 | ### 数据同步
92 | **马克飞象**通过**将Markdown原文以隐藏内容保存在笔记中**的精妙设计,实现了对Markdown的存储和再次编辑。既解决了其他产品只是单向导出HTML的单薄,又规避了服务端存储Markdown带来的隐私安全问题。这样,服务端仅作为对印象笔记 API调用和数据转换之用。
93 |
94 | >**隐私声明:用户所有的笔记数据,均保存在印象笔记中。马克飞象不存储用户的任何笔记数据。**
95 |
96 | ### 离线存储
97 | **马克飞象**使用浏览器离线存储将内容实时保存在本地,不必担心网络断掉或浏览器崩溃。为了节省空间和避免冲突,已同步至印象笔记并且不再修改的笔记将删除部分本地缓存,不过依然可以随时通过`文档管理`打开。
98 |
99 | > **注意:**虽然浏览器存储大部分时候都比较可靠,但印象笔记作为专业云存储,更值得信赖。以防万一,**请务必经常及时同步到印象笔记**。
100 |
101 | ## 编辑器相关
102 | ### 设置
103 | 右侧系统菜单(快捷键`Ctrl + M`)的`设置`中,提供了界面字体、字号、自定义CSS、vim/emacs 键盘模式等高级选项。
104 |
105 | ### 快捷键
106 |
107 | 帮助 `Ctrl + /`
108 | 同步文档 `Ctrl + S`
109 | 创建文档 `Ctrl + Alt + N`
110 | 最大化编辑器 `Ctrl + Enter`
111 | 预览文档 `Ctrl + Alt + Enter`
112 | 文档管理 `Ctrl + O`
113 | 系统菜单 `Ctrl + M`
114 |
115 | 加粗 `Ctrl + B`
116 | 插入图片 `Ctrl + G`
117 | 插入链接 `Ctrl + L`
118 | 提升标题 `Ctrl + H`
119 |
120 | ## 关于收费
121 |
122 | **马克飞象**为新用户提供 10 天的试用期,试用期过后需要[续费](maxiang.info/vip.html)才能继续使用。未购买或者未及时续费,将不能同步新的笔记。之前保存过的笔记依然可以编辑。
123 |
124 |
125 | ## 反馈与建议
126 | - 微博:[@马克飞象](http://weibo.com/u/2788354117),[@GGock](http://weibo.com/ggock "开发者个人账号")
127 | - 邮箱:
操作系统就是伴随着“多道程序技术”而出现的。因此,操作系统和程序并发是一起诞生的。
16 |
17 | 单核CPU同一时刻只能执行一个程序(具体是指线程), 各个程序只能并发执行。
多核CPU同一时刻可以同时执行多个程序,多个程序可以并行地执行。
18 |
19 | ## 2. 共享
20 | 共享即资源共享,是指系统中的资源可供内存中多个并发执行的进程共同使用。
21 |
22 | ### 2.1 共享方式
23 |
24 | #### 2.1.1 互斥共享方式
25 | 系统中的某些资源,虽然可以提供给多个进程使用,但一个时间段内只允许一个进程访问该资源
26 |
27 | #### 2.1.2 同时共享方式
28 | 系统中的某些资源,允许一个时间段内由多个进程“同时”对它们进行访问。
所谓的“同时”往往是宏观上的,而在微观上,这些进程可能是交替地对该资源进行访问地(即分时共享)。
实例 :
29 |
30 | - [x] 互斥共享方式:使用QQ和微信视频,同一时间段内摄像头只能分配给其中一个进程。
31 | - [x] 同时共享方式:使用QQ发送文件A,同时使用微信发送文件B。宏观上看,两边都在同时读取并发送文件,说明2个进程都在访问硬盘资源,从中读取数据。微观上看,2个进程是交替着访问硬盘的。
32 |
33 | ### 2.2 并发和共享的关系
34 |
35 | 1. 并发性 是指 计算机系统中同时存在多个运行着的程序
36 | 1. 共享性 是指系统中的资源可供内存中读个并发执行的进程共同使用
37 |
38 | 并发性和共享性 互为存在条件。
39 |
40 | ## 3. 虚拟
41 | **虚拟** 是指把一个物理上的实体变为若干个逻辑上的对应物。物理实体(前者)是实际存在的,而逻辑上对应物(后者)是用户感受到的。
42 |
43 | ### 3.1 虚拟技术
44 |
45 | #### 3.1.1 空分复用技术(虚拟存储器技术)
46 | 一个电脑一共4GB内存,运行星际2 要4G,QQ 256M,微信 256M,网易云音乐 256M,同时运行需要的内存远大于4G,此时就是虚拟存储器技术的效果。
47 |
48 | #### 3.1.2 时分复用技术(虚拟处理器)
49 | 一个电脑单核,同时可以运行QQ,微信,网易云音乐,实际上只有一个CPU,在用户看来似乎有3个CPU同时为自己服务。此时就是虚拟处理器技术的效果。
** 虚拟技术中的“时分复用技术”,微观上处理机在各个微小的时间段内交替着为各个进程服务。**
50 |
51 | 显然,如果失去了并发性,则一个时间段内系统中只需运行一道程序,那么就是去了实现虚拟性的意义。因此,没有并发性,就谈不上虚拟性。
52 |
53 | ## 4.异步
54 | ** 异步**是指,在多道程序环境下,允许多个程序并发执行,但由于资源有限,进程的执行不是一贯到底的,而是走走停停的,以不可预知的速度向前推进,这就是进程的异步性。
如果失去了并发性,即系统只能串行地运行各个程序,那么每个程序的执行会一贯到底。只有系统拥有并发性,才有可能导致异步性。
55 |
56 | # 总结
57 |
58 | 没有并发和共享,就谈不上虚拟和异步,因此并发和共享是操作系统的两个最基本的特征。
59 |
--------------------------------------------------------------------------------
/docs/notes/操作系统/基础知识/1.2.操作系统的发展与分类.md:
--------------------------------------------------------------------------------
1 | # 2.操作系统的发展与分类
2 |
3 |
4 | # 2.1 手工操作阶段
5 | 
主要缺点: 用户独占全集,人机速度(人输入慢操作系统计算很快)矛盾导致资源利用率极低。
6 |
7 | # 2.2 批处理阶段
8 |
9 | ## 2.2.1 单道批处理系统
10 | 
优点: 缓解了一定程度的人机速度矛盾,资源利用率有所提升。
缺点:内存中仅能有一道程序运行,只有改程序运行结束之后才能调入下一道程序。CPU有大量的时间是在空闲等到I/O完成。资源利用率依然很低。
11 |
12 |
13 | ## 2.2.2 多道批处理系统
14 | 输入设备,CPU计算,及输出设备, 在同一时间可以并行执行。

优点: 多道程序并发执行,共享概念股计算机资源,资源利用率大幅提升,CPU和其他资源更能保持“忙碌”状态,系统吞吐量增大。
缺点:用户响应时间长,没有人机交互功能(用户提交自己的左右以后就只能等待计算机处理完成,中间不能控制自己的作业执行。比如 无法调试程序/无法再程序运行过程中输入一些参数)
15 |
16 | # 2.3 分时操作系统
17 | 计算机以时间片为单位轮流为各个用户/作业服务,各个用户可通过终端与计算机进行交互。
主要优点: 用户请求可以被即时响应,解决了人机交互问题。允许多个用户同时使用一台计算机,并且用户对计算机的操作相互独立,感受不到别人的存在。
主要缺点:不能优先处理一些紧急任务。操作系统对各个用户/作业都是完全公平的,循环地为每一个用户/作业服务一个时间片,不区分任务的紧急性。
18 |
19 | # 2.4 实时操作系统
20 | 优点:能够优先响应一些紧急任务,某些紧急任务不需时间片排队。
在实时操作系统的控制下,计算机系统接收到外部信号后进行处理,并且要在严格的时限内处理完事件。实时操作系统的主要特点是及时性和可靠性。
21 |
22 | ## 2.4.1 实时操作系统分类
23 |
24 | ### 2.4.1.1 硬实时系统
25 | 必须要在绝对严格的规定时间内完成处理,例如 导弹控制系统,自动驾驶系统。
26 |
27 | ### 2.4.1.2 软实时系统
28 | 能接受偶尔违反事件规定。
29 |
30 |
--------------------------------------------------------------------------------
/docs/notes/操作系统/基础知识/1.3.操作系统的运行环境.md:
--------------------------------------------------------------------------------
1 | # 3.操作系统的运行环境
2 |
3 |
4 |
5 | # 3.1 程序的级别
6 |
7 | ## 内核程序
8 | 由很多内核程序组成了“操作系统内核”,内核是操作系统最重要核心的部分,也是最接近硬件的部分
9 |
10 | ## 应用程序
11 | 一条高级语言的代码翻译过来可能会对应多条机器指令,程序运行的过程就是CPU执行一条一条的机器指令的过程。
12 |
13 | # 3.2 指令的级别
14 |
15 | ## 特权指令
16 | 只允许操作系统内核作为“管理者”去使用特权指令,例如内存清零指令。
17 |
18 | ## 非特权指令
19 | 应用程序只能使用“费特权指令”,例如加法指令,减法指令。
在CPU设计和生产的时候就划分了特权指令和非特权指令,因此CPU执行一条指令前就能判断出其类型。
20 |
21 | # 3.3 内核态和用户态
22 | 在讲特权指令和非特权指令时,CPU可以判断出指令类型,但是他怎么去区分正在运行的是内核程序还是应用程序呢?
CPU有2种状态,“内核态”和“用户态”
处在内核态时,说明此时正在运行的是内核程序,此时可以执行特权指令。
处在用户态时,说明此时正在运行的是应用程序,此时可以执行非特权指令。
23 |
24 | CPU中有一个寄存器叫做 程序状态字寄存器(PSW),其中有个二进制位,1表示内核态,0表示用户态。
25 |
--------------------------------------------------------------------------------
/docs/notes/操作系统/基础知识/1.4.操作系统的体系结构.md:
--------------------------------------------------------------------------------
1 | # 4.操作系统的体系结构
2 |
3 | 
4 |
5 | # 4.1 操作系统的内核
6 | 内核是计算机配置上的底层软件,是操作系统最基本,最核心的部分。
实现操作系统内核功能的程序就被称之为 **内核程序**。
7 | **需要注意的是**:有些操作系统不把 “对系统资源进行管理的功能”归为“内核功能”。也就是说,不同的操作系统,对内核功能的划分可能并不一样。
8 |
9 |
10 | # 4.2 操作系统的体系结构:大内核和微内核
11 | 
12 | 类比:操作系统的体系结构问题与企业的管理问题很相似。
内核就相当于企业的管理层,负责一些重要的工作。只有管理层才能执行特权指令,普通员工只能执行非特权指令。用户态,核心态之间的切换相当于普通员工和管理层之间的工作交接,交接工作花费的时间相当于时间片切换消耗。
大内核:企业初创时体量不大,管理层的人会负责大部分的事情。优点:效率高(因为权限高,能直接快速定位并解决问题);缺点:组织结构混乱,难以维护。
微内核:随着企业体量逐渐变大,管理层只负责最核心的一些工作。优点:组织结构清晰,方便维护。缺点:效率低下。
13 |
如下图,可以看出 大内核和微内核的直接区别

14 |
--------------------------------------------------------------------------------
/docs/notes/操作系统/基础知识/2.1进程概论.md:
--------------------------------------------------------------------------------
1 | \# 2.1 进程概论
2 |
3 |
4 | # 进程的概念
5 | 进程是系统进行资源分配和调度的基本单位,线程是CPU调度和分派的基本单位。
6 |
程序:是静态的,就是一个存放在磁盘里的可执行文件,就是一系列的指令集合。
进程:是动态的,是程序的一次执行过程。
7 |
8 | # 进程的组成
9 | 当进程被创建时,操作系统会为该进程分配一个唯一的,不重复的“身份证号”--PID(Process ID,进程ID)
10 |
11 | ## 进程控制块PCB
12 | PCB是进程存在的唯一标志,当进程被创建时,操作系统为其创建PCB,当进程结束时,会回收其PCB。
13 |
14 | PCB是给操作系统的。
数据段和程序块是给进程自己使用的。与进程自身的运行逻辑有关。
15 |
16 |
17 |
18 | ## 知识滚雪球:程序是如何运行的?
19 | 
**重点理解**:进程在内存中的运作。
20 |
21 | # 进程的特征
22 | 
23 | # 进程的状态与转换
24 |
25 | ## 进程状态分类
26 |
27 | ### 创建状态 (New,又称之为:新建态)
28 | 进程正在被创建,操作系统为进程分配资源,初始化PCB。
29 |
30 | ### 就绪状态
31 | 已经具备运行条件,但由于没有空闲CPU,而暂时不能运行。

32 |
33 | ### 运行状态
34 | 占有CPU,并在上搞事情运行。
如果一个进程此时在CPU上运行,那么这个进程处于“运行态”。

35 |
36 | ### 阻塞状态
37 | 等待被调度的状态,因等待某一件事而暂时不能运行。
38 |
在进程运行的过程中,可能会请求等待某个事件的发生(如等待某种系统资源的分配,或者等待其他进程的响应)。在这个事件发生之前,进程无法继续往下执行,此时操作系统会让这个进程暂时释放CPU,并让他进入“阻塞态”。当CPU空闲时,又会选择另一个“继续态”进程上CPU执行。
39 |
40 | ### 终止状态
41 | 对于进程来讲,可以执行exit系统调用,请求操作系统终止该进程。此时该进程会进入“终止态”,操作系统会让该进程下CPU,并回收内存空间等资源,最后还要回收该进程的PCB。
42 |
43 | ## 进程状态间的转换
44 | 
需要注意:
45 |
46 | - 只有就绪态和运行态可以相互转换,其它的都是单向转换。就绪状态的进程通过调度算法从而获得 CPU 时间,转为运行状态;而运行状态的进程,在分配给它的 CPU 时间片用完之后就会转为就绪状态,等待下一次调度。
47 | - 阻塞状态是缺少需要的资源从而由运行状态转换而来,但是该资源不包括 CPU 时间,缺少 CPU 时间会从运行态转换为就绪态。
48 |
49 | # 进程的组织
50 |
51 | # 进程控制
52 |
53 |
54 |
55 | # 进程通信
56 |
57 | ## 进程通信的概念
58 | 进程通信就是指进程之间的信息交换。
进程是分配系统资源的单位(包括内存地址空间),因此各进程拥有的内存地址空间相互独立。
为了安全起见,一个进程不能直接访问另一个进程的地址空间。
59 |
60 | 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为**进程间通信(IPC,InterProcess Communication)**
**
61 |
62 | ## 进程通信的方式
63 |
64 | ### 1. 共享存储
65 | 两个进程对共享空间的访问必须是互斥的(操作系统只负责提供共享空间和同步互斥操作工具,如P,V操作)
66 |
67 | 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读写这一块内存而不需要进行数据的拷贝,从而大大提高效率。
68 |
69 | 不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
70 |
71 |
72 | #### 共享内存的通信原理
73 | 在Linux中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。

当两个进程通过页表将虚拟地址映射到物理地址时,在物理地址中有一块共同的内存区,即共享内存,这块内存可以被两个进程同时看到。这样当一个进程进行写操作,另一个进程读操作就可以实现进程间通信。但是,我们要确保一个进程在写的时候不能被读,因此我们使用信号量来实现同步与互斥。
74 |
对于一个共享内存,实现采用的是引用计数的原理,当进程脱离共享存储区后,计数器减一,挂架成功时,计数器加一,只有当计数器变为零时,才能被删除。当进程终止时,它所附加的共享存储区都会自动脱离。
75 |
76 | ### 2. 管道/匿名管道通信(pipe)
77 | 管道:可以理解为用于连接读写进程的一个共享文件,又名pipe文件。其实就是在内存中开辟一个大小固定的缓冲区。在Linux 中,管道大小等于内存页面的大小,4KB。

78 |
79 | - 管道只能采用[半双工](https://blog.csdn.net/komtao520/article/details/88084984)通信,某一时间段内只能实现单向的传输。如果要实现双向同事通信,则需要设置两个管道。
80 | - 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)
81 | - 各个进程要互斥地访问管道。
82 | - 数据以字符流的形式写入管道,当管道写满时,写进程的write()系统调用将被阻塞,等待读进程将数据取走。当读进程将数据全部取走后,管道变空,此时读进程的read()系统调用将被阻塞。
83 | - 如果没写满,就不允许读。如果没读空,就不允许写。
84 | - 数据一旦读出,就从管道中被抛弃,这就意味着读进程最多只能有一个,否则可能就会有读错数据的情况。
85 | - 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。
86 |
87 | #### 管道的实质
88 |
89 | - 管道的实质是一个内核缓冲区,进程以先进先出的方式从缓冲区存取数据,管道一端的进程顺序的将数据写入缓冲区,另一端的进程则顺序的读出数据。
90 | - 该缓冲区可以看做是一个循环队列,读和写的位置都是自动增长的,不能随意改变,一个数据只能被读一次,读出来以后在缓冲区就不复存在了。
91 | - 当缓冲区读空或者写满时,有一定的规则控制相应的读进程或者写进程进入等待队列,当空的缓冲区有新数据写入或者满的缓冲区有数据读出来时,就唤醒等待队列中的进程继续读写。
92 |
93 | #### 管道的局限
94 | 管道的主要局限性正体现在它的特点上:
95 |
96 | - [x] 只支持单向数据流
97 | - [x] 只能用于具有亲缘关系的进程之间;
98 | - [x] 没有名字
99 | - [x] 管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小);
100 | - [x] 管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等;
101 |
102 | ### 3. 有名管道(FIFO)
103 | 匿名管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道(FIFO)。
提供了一个路径名与之关联,**以有名管道的文件形式存在于文件系统中**,这样,**即使与有名管道的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过有名管道相互通信.**
**
有名管道严格遵循**先进先出(first in first out)**,对匿名管道及有名管道的读总是从开始处返回数据,对它们的写则把数据添加到末尾。
**有名管道的名字存在于文件系统中,内容存放在内存中。**
**
104 |
105 | #### 匿名管道和有名管道总结
106 |
107 | - [x] ** 无名管道阻塞问题:**无名管道无需显示打开,创建时直接返回文件描述符,在读写时需要确定对方的存在,否则将退出。如果当前进程向无名管道的一端写数据,必须确定另一端有某一进程。如果写入无名管道的数据超过其最大值,写操作将阻塞,如果管道中没有数据,读操作将阻塞,如果管道发现另一端断开,将自动退出。
108 | - [x] **有名管道阻塞问题:**有名管道在打开时需要确实对方的存在,否则将阻塞。即以读方式打开某管道,在此之前必须一个进程以写方式打开管道,否则阻塞。此外,可以以读写(O_RDWR)模式打开有名管道,即当前进程读,当前进程写,不会阻塞。
109 |
110 |
111 |
112 |
113 | ### 4.信号
114 |
115 |
116 |
117 | ### 5.消息队列
118 |
119 | ### 6.信号量
120 |
121 | ### 7.套接字
122 |
123 | ## 引用
124 | [进程间通信IPC (InterProcess Communication)](https://www.jianshu.com/p/c1015f5ffa74)
[进程间通信——共享内存(Shared Memory)](https://blog.csdn.net/ypt523/article/details/79958188)
125 |
126 |
--------------------------------------------------------------------------------
/docs/notes/操作系统/基础知识/2.2线程概论.md:
--------------------------------------------------------------------------------
1 | # 2.2 线程概论
2 |
3 | 线程是CPU调度的基本单位。
4 |
5 |
6 | # 线程组成
7 |
8 | - 线程ID:线程标识符
9 | - 当前指令指针
10 | - 寄存器集合:存储单元寄存器的集合
11 | - 堆栈
12 |
13 | 如下图:
14 |
15 | # 线程与进程的关系
16 | 
线程存在于进程之中。
17 |
18 | # 线程的特性和优点
19 | 
20 |
21 | ## 特性
22 | 同一进程的各个线程共享进程拥有的资源。(线程和进程的关联关系)
23 |
24 | ## 优点
25 |
26 |

为什么有了进程还需要线程?
27 |
28 | - 进程间并发,需要保存/恢复进程运行环境,还需要切换内存地址空间,开销成本很大。
29 | - 引入线程机制后,线程并发不需要切换进程运行环境和内存地址空间(因为进程内的线程共享进程的资源),只需要切换线程私有数据,开销成本很小。
30 |
31 | 
引入线程前,进程既是资源分配的基本单位,也是调度的基本单位。
引入线程后,进程是资源分配的基本单位,线程是调度的基本单位。
32 |
33 | # 线程同步
34 | [https://blog.csdn.net/zxx901221/article/details/83006453](https://blog.csdn.net/zxx901221/article/details/83006453)
35 |
36 | ## 1.临界区
37 | 在讲临界区之前,我们提下 临界资源这个概念,
38 |
39 | - 临界资源:各进程采取互斥的方式,实现共享的资源称作临界资源。属于临界资源的硬件有打印机、[磁带机](https://baike.so.com/doc/1934270-2046370.html)等,软件有消息缓冲队列、变量、[数组](https://baike.so.com/doc/5545345-5760453.html)、缓冲区等。 诸进程间应采取互斥方式,实现对这种资源的共享。
40 |
41 |
42 |
43 | ### 概念解释
44 | ```
45 | :通过对多线程的串行化来访问公共资源(同一进程内公共资源)或一段代码,速度快,适合控制数据访问。
46 | ```
47 |
48 | ### 优点
49 | ```
50 | 保证在某一时刻只有一个线程能访问数据的简便办法
51 | ```
52 |
53 | ### 缺点
54 | ```
55 | 虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不能用来同步不同进程中的线程。
56 | ```
57 |
58 | ## 2.互斥量(Mutex)
59 |
60 |
61 |
62 | ### 概念解释
63 | ```
64 | 为协调共同对一个共享资源的单独访问而设计的。
65 | 互斥量跟临界区很相似,比临界区复杂,互斥对象只有一个,只有拥有互斥对象的线程才具有访问资源的权限。
66 | 类似于Java中对象的锁机制;
67 | ```
68 |
69 | ### 优点
70 | ```java
71 | 使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,
72 | 而且可以在不同应用程序的线程之间实现对资源的安全共享。
73 | ```
74 |
75 | ### 缺点
76 | ```
77 | 【1】互斥量是可以命名的,也就是说它可以跨越进程使用,所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。
78 | 因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。
79 | 【2】互斥量只支持同一时刻,保证一个线程可以访问临界资源,但是做不到 同一时刻 ,要求n个线程同时
80 | 访问临界资源的需求(1
[操作系统-信号量及PV操作](https://blog.csdn.net/qq_19782019/article/details/79746627)
[CPU上下文切换](https://blog.51cto.com/12924846/2406421)
111 |
112 |
113 |
--------------------------------------------------------------------------------
/docs/notes/操作系统/基础知识/2.3处理机调度.md:
--------------------------------------------------------------------------------
1 | # 2.3 处理机调度
2 |
3 |
4 | # 1.调度概念
5 | ```java
6 | 任务很多,资源有限,没法做到同时处理,这时候就需要确定某种规则来决定处理任务的顺序,
7 | 这就是处理机调度要研究的问题。
8 |
9 | 在多到程序系统中,进程数大于CPU个数,此时处理机要按照一定的算法选择一个进程并将处理器分配给它 运行。
10 | ```
11 |
12 | # 2.调度算法
13 |
14 | ## 2.1 先到先服务(FCFS,队列实现,非抢占)
15 | 
16 | ## 2.2 短作业优先(SJF,最短作业优先调度算法)
17 |
18 | ### 短作业/进程优先调度算法
19 | ```java
20 | 每次调度时选择当前已到达且运行时间最短的作业/进程。
21 | ```
22 |
23 | ### 
24 | ```java
25 | 当第0秒时,P1到达,就开始运行P1。
26 | 当第7秒时,P1执行完毕,此时P2,P3,P4都已经到达,选择最短作业就是P3,开始执行
27 | 当第8秒时,P3执行完毕,此时P2,P4处于就绪状态,并且他们两个作业时间相同,按照到达顺序进行执行,执行P2
28 | 当第12秒时,P2执行完毕,P4处于就绪状态,开始执行P4
29 | 当第16秒时,P4执行完毕。
30 | ```
31 |
32 | ### 最短剩余时间优先算法
33 | ```java
34 | 最短剩余时间优先算法: 每当有进程加入就绪队列时就要重新调整调度,
35 | 如果新到达的进程剩余时间比当前运行的进程剩余时间更短,则由新进程抢占处理机,
36 | 当前进行进程重新回到就绪队列,此外,当一个进程完成时也需要重新调整调度方案。
37 | ```
38 | 
如上图这个例子,四个进程执行完毕,一共花的时间是 7+4+1+4=16, 但是根据短作业优先,在11时刻时就有三个进程完成作业,对短作业友好,无需因存在长作业而要等待很长时间。
39 | ```java
40 | 第0秒时,P1到达,开始执行
41 | 第2秒时,P2到达,此时P1剩余作业时间5秒,P2剩余时间4秒,开始执行P2,P1暂停,进入到就绪状态。
42 | 第4秒时,P3到达,此时P1剩余作业时间5秒,P3剩余作业时间1秒,P2剩余作业时间2秒,开始执行P3
43 | 第5秒时,P4到达,P3执行完毕,此时P1剩余作业时间5秒,P4剩余作业时间4秒,P2剩余作业时间2秒,开始执行P2
44 | 第7秒时,P2执行完毕,此时P1剩余作业时间5秒,P4剩余作业时间4秒,开始执行P4
45 | 第11秒时,P4执行完毕,开始执行P1
46 | 第16秒时,P1执行完毕。
47 | ```
48 | 
49 | ## 2.3 高响应比优先(HRRN)
50 | 高响应比优先算法
51 | ```java
52 | 非抢占式的调度算法,只有当前运行的进程主动放弃CPU时(正常/异常完成,或者主动阻塞),才需要进行调度,
53 | 调度时计算所有就绪进程的响应比,选响应比最高的进程处理。
54 |
55 | 响应比=(等待时间+要求服务时间)/(要求服务时间)
56 | ```
57 | 
58 | ```java
59 | 第0秒,P1到达就绪队列,P1上处理器开始处理。
60 | 第7秒,P1执行完毕,主动放弃CPU,此时在就绪队列上,
61 | 有P2(等待时间5秒,要求服务时间4秒),P2(响应比=(5+4)/4=2.25)
62 | 有P3(等待时间3秒,要求服务时间1秒),P3(响应比=(3+1)/1=4)
63 | 有P4(等待时间2秒,要求服务时间4秒),P4(响应比=(2+4)/4=1.5)
64 | 优先处理P3
65 | 第8秒,P3执行完毕,主动放弃CPU,此时在就绪队列上,
66 | 有P2(等待时间6秒,要求服务时间4秒),P2(响应比=(6+4)/4=2.5)
67 | 有P4(等待时间3秒,要求服务时间4秒),P3(响应比=(3+4)/4=1.75)
68 | 优先处理P2
69 | 第12秒,P2执行完毕,主动放弃CPU,开始处理P4
70 |
71 | ```
72 | 
73 | ### 优缺点
74 | ```java
75 | 综合考虑了等待时间和运行时间(要求服务时间),等待时间相同时,要求服务时间短的优先(SJF的优点,短作业优先)
76 | 要求服务时间相同时,等待时间长的优先(FCFS的优点,先到先服务)
77 | 对于长作业来说,随着等待时间越来越久,其响应比也会越来越大,从而避免了长作业饥饿的问题。
78 | ```
79 |
80 | ## 2.4 时间片轮转调度算法
81 |
82 |
83 |
84 | # 引用
85 | [非抢占式和抢占式进程调度的区别](https://blog.csdn.net/qq_34173549/article/details/79936219)
86 |
--------------------------------------------------------------------------------
/docs/notes/数据库/MySQL/MySQL存储引擎.md:
--------------------------------------------------------------------------------
1 |
2 | # 1.InnoDB与MyISAM对比
3 | | **特性** | **InnoDB ** | **MyISAM ** |
4 | | --- | --- | --- |
5 | | 事务 | 支持 | 不支持 |
6 | | 锁 | 行锁&&表锁 | 表锁 |
7 | | 缓存 | 不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响 | 只缓存索引,不缓存真实数据 |
8 | | 外键 | 支持 | 不支持 |
9 | | 索引类型 | 主键:聚簇索引 | 非聚簇索引 |
10 | | 备份 | 热备份 | 不支持 |
11 |
12 | - InnoDB 支持崩溃后安全恢复,MyISAM 不支持崩溃后安全恢复;
13 | - **InnoDB 支持行级锁,MyISAM 不支持行级锁,只支持到表锁**;
14 | - InnoDB 支持外键,MyISAM 不支持外键;
15 | - MyISAM 性能比 InnoDB 高;
16 | - InnoDB 主键查询性能高于 MyISAM。
17 |
18 | ## 1.1 MyISAM
19 | 不支持行级锁。
只能对整张表加锁,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT)。
20 |
21 | ###
22 |
23 |
24 | ### 4.如果把一个 InnoDB 表的主键删掉,是不是就没有主键,就没办法进行回表查询了?
25 | 可以回表查询,如果把主键删掉了,那么 InnoDB 会自己生成一个长度为 6 字节的 rowid 作为主键。
26 |
27 |
28 | ### 6.MySQL 中内连接、左连接、右连接有什么区别?
29 |
30 | - 内连(inner join)— 把匹配的关联数据显示出来;
31 | - 左连接(left join)— 把左边的表全部显示出来,右边的表显示出符合条件的数据;
32 | - 右连接(right join)— 把右边的表全部显示出来,左边的表显示出符合条件的数据;
33 |
34 | ###
7.SQL语句执行顺序
35 | 先执行from关键字后面的语句,明确数据的来源,它是从哪张表取来的。
接着执行where关键字后面的语句,对数据进行筛选。
再接着执行group by后面的语句,对数据进行分组分类。
然后执行select后面的语句,也就是对处理好的数据,具体要取哪一部分。
最后执行order by后面的语句,对最终的结果进行排序。
36 |
37 |
38 | ### 8.索引为啥会失效?
39 | |
1. 不要在索引列上进行运算或使用函数
|
40 | | --- |
41 | |
2. 前导模糊查询不会使用索引,例如 like %李
|
42 | |
3. 负向条件索引不会使用索引,建议用in。负向条件有:!=、<>、not in、not exists、not like 等;
|
43 |
44 |
45 | ### 9.为什么主键通常建议使用自增id
46 |
47 | 使用自增 id 可以避免页分裂
**聚簇索引的数据的物理存放顺序与索引顺序是一致的**,即:**只要索引是相邻的,那么对应的数据一定也是相邻地存放在磁盘上的**。如果主键不是自增id,那么可以想 象,它会干些什么,不断地调整数据的物理地址、分页,当然也有其他一些措施来减少这些操作,但却无法彻底避免。但,如果是自增的,那就简单了,它只需要一 页一页地写,索引结构相对紧凑,磁盘碎片少,效率也高。
48 |
49 | ### 10.索引覆盖
50 |
51 | 索引覆盖使用联合索引在特定情况下 ,避免回表查。
创建 联合索引 A,B,C
查询的时候正好查A,B,C 这三个字段,索引就避免回表查其他内容了。直接返回即可。
52 |
53 | ### [
](https://blog.csdn.net/ycf921244819/article/details/90273828)
54 |
55 |
56 | ### 12. 设计表的时候需要注意哪些
57 |
58 | # 常见问题
59 |
60 | ### 1. 在 InnoDB 引擎中 count(*)、count(1)、count(主键)、count(字段) 哪个性能最高?
61 |
62 | - 对于 count(主键 id) 来说,InnoDB 引擎会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。server 层拿到 id 后,判断是不可能为空的,就按行累加。
63 | - 对于 count(1) 来说,InnoDB 引擎遍历整张表,但不取值。server 层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。
64 | - 对于 count(字段) 来说,如果这个“字段”是定义为 not null 的话,一行行地从记录里面读出这个字段,判断不能为 null,按行累加;如果这个“字段”定义允许为 null,那么执行的时候,判断到有可能是 null,还要把值取出来再判断一下,不是 null 才累加。
65 | - 对于 count(*) 来说,并不会把全部字段取出来,而是专门做了优化,不取值,直接按行累加。
66 |
67 | 所以最后得出的结果是:count(字段)
13 |
14 |
15 | # 二叉树
16 |
17 |
18 | ## 二叉树的遍历
19 |
20 | #### [144. 二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/)
21 |
22 |
23 | - 解法一:递归
24 | ```cpp
25 | class Solution {
26 | public:
27 | vector
76 |
77 |
78 | #### [94. 二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/)
79 |
80 | - 解法一
81 | ```cpp
82 | class Solution {
83 | public:
84 | vector
187 |
188 |
189 | #### [面试题34. 二叉树中和为某一值的路径](https://leetcode-cn.com/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/)
190 | ```cpp
191 | /**
192 | * Definition for a binary tree node.
193 | * struct TreeNode {
194 | * int val;
195 | * TreeNode *left;
196 | * TreeNode *right;
197 | * TreeNode() : val(0), left(nullptr), right(nullptr) {}
198 | * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
199 | * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
200 | * };
201 | */
202 | class Solution {
203 | public:
204 | vector
每个专题中的题目都具备关联性,一点一点刷。
每道题目给出在LC的链接,题解看LC提供的,找自己能理解的才是最好的 Solution , 体会下题目的变种与联系。
2 |
3 |
4 | | 深度优先搜索 |
5 | | --- |
6 | | 广度优先搜索 |
7 | | 链表 |
8 | | 二叉树 |
9 | | 排序 |
10 | | 字符串 |
11 | | 双指针 |
12 | | 哈希 |
13 | | 位运算 |
14 | | 数组 |
15 | | DP |
16 | | 栈与队列 |
17 | | 图论 |
18 | | 并查集 |
19 | | 线段树 |
20 |
21 |
22 | # C++ 刷题模板
23 | ```cpp
24 | #include
163 |
164 |
165 |
--------------------------------------------------------------------------------
/docs/notes/算法与数据结构/PAT 甲级题目/1001 A+B Format.md:
--------------------------------------------------------------------------------
1 | # 1001 A+B Format
2 |
3 |
4 | ## [1001 A+B Format](https://pintia.cn/problem-sets/994805342720868352/problems/994805528788582400)
5 | ```cpp
6 | #include
6 | ```cpp
7 | #include
[牛客:二维数组中的查找](https://www.nowcoder.com/practice/abc3fe2ce8e146608e868a70efebf62e?tpId=13&&tqId=11154&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
4 |
5 | ## 题目描述
6 | 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
7 |
8 | ## _代码_
9 | ```java
10 | public class Solution {
11 | public boolean Find(int [][] array,int target) {
12 | if(array.length == 0){
13 | return false;
14 | }
15 | int row=0;
16 | int col=array[0].length-1;
17 | while(row<=array.length-1&&col>=0){
18 | if(target==array[row][col])
19 | return true;
20 | else if(target>array[row][col])
21 | row++;
22 | else
23 | col--;
24 | }
25 | return false;
26 |
27 | }
28 | }
29 | ```
30 |
31 |
32 | ```cpp
33 | bool findNumberIn2DArray(vector
牛客: [https://www.nowcoder.com/practice/1a834e5e3e1a4b7ba251417554e07c00?tpId=13&&tqId=11165&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking](https://www.nowcoder.com/practice/1a834e5e3e1a4b7ba251417554e07c00?tpId=13&&tqId=11165&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
4 |
5 | ```cpp
6 | 浮点型 快速幂
7 | 将底数转化为 分数计算,确保指数是正数;
8 | ```
9 | ```cpp
10 | class Solution {
11 | public:
12 | double myPow(double x, int n) {
13 | if (x == 1 || n == 0) {
14 | return 1;
15 | }
16 | double sum = 1;
17 | long num = n;
18 | if (n < 0) {
19 | x = 1 / x;
20 | num = -num;
21 | }
22 | while (num) {
23 | if (num & 1) {
24 | sum *= x;
25 | }
26 | num >>= 1;
27 | x *= x;
28 | }
29 | return sum;
30 | }
31 | };
32 | ```
33 |
--------------------------------------------------------------------------------
/docs/notes/算法与数据结构/剑指Offer 笔记汇总/14-链表倒数第K个节点.md:
--------------------------------------------------------------------------------
1 | # 14-链表倒数第K个节点
2 |
3 |
4 | ## 题目描述
5 | 输入一个链表,输出该链表中倒数第k个结点。
6 |
分析:
定义2个指针,第一个先跑K个,然后两个一起跑,当第一个跑到头的时候,第二个就是指在倒数第K个节点上。
7 |
8 |
9 | ## 代码
10 | ```java
11 | public class Solution {
12 | public ListNode FindKthToTail(ListNode head, int k) {
13 | /**
14 | * step 1:创建2个节点,都指向头结点;
15 | */
16 | ListNode first = head;
17 | ListNode second = head;
18 | /**
19 | * step 2:让第二个节点指向第K个节点;
20 | */
21 | for (int i = 0; i < k; i++) {
22 | /**
23 | * 如果出现链表长度
14 |
15 | ```cpp
16 | /*
17 | struct TreeNode {
18 | int val;
19 | struct TreeNode *left;
20 | struct TreeNode *right;
21 | TreeNode(int x) :
22 | val(x), left(NULL), right(NULL) {
23 | }
24 | };*/
25 | class Solution {
26 | public:
27 | bool HasSubtree(TreeNode *pRoot1, TreeNode *pRoot2) {
28 | if (pRoot1 == nullptr||pRoot2== nullptr) {
29 | return false;
30 | }
31 | if (pRoot1 == pRoot2) {
32 | return true;
33 | }
34 |
35 |
36 | bool res = false;
37 | if (pRoot1->val == pRoot2->val) {
38 | res = isSubTree(pRoot1, pRoot2);
39 | }
40 | if (res) {
41 | return true;
42 | }
43 | return isSubTree(pRoot1->left, pRoot2) || isSubTree(pRoot1->right, pRoot2);
44 |
45 | };
46 |
47 | bool isSubTree(TreeNode *pRoot1, TreeNode *pRoot2) {
48 | if (pRoot1 == pRoot2) {
49 | return true;
50 | }
51 | if (pRoot2 == nullptr) {
52 | return true;
53 | }
54 | if (pRoot1 == nullptr) {
55 | return false;
56 | }
57 | if (pRoot1->val != pRoot2->val) {
58 | return false;
59 | } else {
60 | return isSubTree(pRoot1->left, pRoot2->left) && isSubTree(pRoot1->right, pRoot2->right);
61 | }
62 | }
63 | };
64 | ```
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/docs/notes/算法与数据结构/剑指Offer 笔记汇总/18.二叉树的镜像.md:
--------------------------------------------------------------------------------
1 | URL:[https://www.nowcoder.com/practice/a9d0ecbacef9410ca97463e4a5c83be7?tpId=13&tqId=11171&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking&tab=answerKey](https://www.nowcoder.com/practice/a9d0ecbacef9410ca97463e4a5c83be7?tpId=13&tqId=11171&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking&tab=answerKey)
2 |
3 | ```java
4 | /**
5 | * struct TreeNode {
6 | * int val;
7 | * struct TreeNode *left;
8 | * struct TreeNode *right;
9 | * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
10 | * };
11 | */
12 | class Solution {
13 | public:
14 | /**
15 | * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
16 | *
17 | *
18 | * @param pRoot TreeNode类
19 | * @return TreeNode类
20 | */
21 | TreeNode *Mirror(TreeNode *pRoot) {
22 | // write code here
23 | if (pRoot == nullptr) {
24 | return nullptr;
25 | }
26 | TreeNode *tmp = pRoot->left;
27 | pRoot->left = pRoot->right;
28 | pRoot->right = tmp;
29 | Mirror(pRoot->right);
30 | Mirror(pRoot->left);
31 | return pRoot;
32 | }
33 | };
34 | ```
35 |
--------------------------------------------------------------------------------
/docs/notes/算法与数据结构/剑指Offer 笔记汇总/2.替换空格.md:
--------------------------------------------------------------------------------
1 | # 2.替换空格
2 |
3 |
4 | ## 题目描述
5 | 请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
6 |
7 |
8 | ## 代码:
9 | ```java
10 | 蠢方法,直接使用StringBuilder 遍历遇到空格,直接append %20 ,遍历结束,即完成
11 | ```
12 | ```java
13 | public class Solution {
14 | public String replaceSpace(StringBuffer str) {
15 | StringBuilder sb = new StringBuilder();
16 |
17 | for (int i = 0; i < str.length(); i++) {
18 | if (str.charAt(i) == ' ') {
19 | sb.append("%20");
20 | } else {
21 | sb.append(str.charAt(i));
22 | }
23 | }
24 | return new String(sb);
25 | }
26 | }
27 | ```
28 |
29 | # 代码
30 | ```java
31 | 先判断出字符串中有多少空格,构造新字符串
32 | ```
33 | ```cpp
34 | string replaceSpace(string s) {
35 | int count = 0, len = s.size();
36 | for (char c : s) {
37 | if (c == ' ') {
38 | count += 1;
39 | }
40 | }
41 | s.resize(len + 2 * count);
42 | for (int i = len - 1, j = s.size() - 1; i < j; i--, j--) {
43 | if (s[i] != ' ') {
44 | s[j] = s[i];
45 | } else {
46 | s[j] = '0';
47 | s[j - 1] = '2';
48 | s[j - 2] = '%';
49 | j -= 2;
50 | }
51 | }
52 | return s;
53 | }
54 |
55 | ```
56 |
--------------------------------------------------------------------------------
/docs/notes/算法与数据结构/剑指Offer 笔记汇总/20.包含min函数的栈.md:
--------------------------------------------------------------------------------
1 | 两个栈,一个存所有数据,一个维护栈顶最小。
2 | ```java
3 | import java.util.Stack;
4 | public class Solution {
5 | // 元素栈;
6 | Stack
45 |
46 | ```cpp
47 |
48 | class Solution {
49 | public:
50 | stack
牛客:[https://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163?tpId=13&&tqId=11181&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking](https://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163?tpId=13&&tqId=11181&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
4 |
方法一:
5 | ```java
6 | class Solution {
7 | public int majorityElement(int[] nums) {
8 | HashMap
翻转链表,然后重新遍历放入到数组中;
9 | ```java
10 | public ArrayList
依次遍历放入栈中,然后遍历栈
29 | ```java
30 | import java.util.ArrayList;
31 | import java.util.List;
32 | import java.util.Stack;
33 | import java.util.ArrayList;
34 | public class Solution {
35 | //
36 | public ArrayList
51 |
52 |
--------------------------------------------------------------------------------
/docs/notes/算法与数据结构/剑指Offer 笔记汇总/30.连续子数组的最大和.md:
--------------------------------------------------------------------------------
1 | # 30.连续子数组的最大和
2 |
3 | LC : [https://leetcode-cn.com/problems/maximum-subarray/](https://leetcode-cn.com/problems/maximum-subarray/)
牛客:[https://www.nowcoder.com/practice/459bd355da1549fa8a49e350bf3df484?tpId=13&&tqId=11183&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking](https://www.nowcoder.com/practice/459bd355da1549fa8a49e350bf3df484?tpId=13&&tqId=11183&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
4 |
**思路和算法:**
**
假设 nums 数组的长度是 n,下标从 0 到 n−1。
5 |
我们用 $$a_{i}$$ 代表 nums[i],用 $$ f(i)$$ 代表以第 ii个数结尾的「连续子数组的最大和」,那么很显然我们要求的答案就是:
6 |
公式 : $$\max \limits_{1
7 |
因此我们只需要求出每个位置的 $$ f(i)$$,然后返回 f 数组中的最大值即可。那么我们如何求$$ f(i)$$呢?我们可以考虑 $$a_{i}$$
单独成为一段还是加入$$ f(i-1)$$对应的那一段,这取决于$$a_{i}$$ 和$$ f(i-1)$$+ $$a_{i}$$ 的大小,我们希望获得一个比较大的,于是可以写出这样的动态规划转移方程:
8 |
$$ f(i)=max \{ f(i-1)+ a_{i},a_{i}\}$$
9 |
10 |
11 | # 代码
12 |
13 |
14 | ```cpp
15 | class Solution {
16 | public:
17 | int maxSubArray(vector
6 |
代码:
7 | ```java
8 | /*
9 | public class ListNode {
10 | int val;
11 | ListNode next = null;
12 |
13 | ListNode(int val) {
14 | this.val = val;
15 | }
16 | }*/
17 | public class Solution {
18 | public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
19 | int len1 = getListNodeLength(pHead1);
20 | int len2 = getListNodeLength(pHead2);
21 | int diff = Math.abs(len1 - len2);
22 | while (diff > 0) {
23 | if (len1 > len2) {
24 | pHead1 = pHead1.next;
25 | } else {
26 | pHead2 = pHead2.next;
27 | }
28 | diff--;
29 | }
30 | while (pHead1 != pHead2
31 | && pHead1 != null
32 | && pHead2 != null) {
33 | pHead1 = pHead1.next;
34 | pHead2 = pHead2.next;
35 | }
36 | return pHead1;
37 | }
38 |
39 | private int getListNodeLength(ListNode head) {
40 | int length = 0;
41 | while (head != null) {
42 | length += 1;
43 | head = head.next;
44 | }
45 | return length;
46 | }
47 | }
48 | ```
49 |
--------------------------------------------------------------------------------
/docs/notes/算法与数据结构/剑指Offer 笔记汇总/38.二叉树深度.md:
--------------------------------------------------------------------------------
1 | # 38.二叉树深度
2 |
3 | 刷题平台:[牛客网](https://www.nowcoder.com/ta/coding-interviews?query=&asc=true&order=&page=1)
4 |
5 |
6 | ## 题目
7 | 输入一棵[二叉树](https://cuijiahua.com/blog/tag/%e4%ba%8c%e5%8f%89%e6%a0%91/),求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
8 |
9 | ### 1.思路
10 | 可以是递归的方法,属于DFS(深度优先搜索);另一种方法是按照层次遍历,属于BFS(广度优先搜索)
11 |
5 TreeDepth(5)
/ \ / \
1 2 6 1 TreeDepath(1)
\
7 TreeDepth(7)
12 |
13 | ### 2.代码
14 |
15 | #### DFS
16 |
17 |
18 | ```java
19 | private class TreeNode {
20 | int val;
21 | TreeNode left, right;
22 |
23 | TreeNode(int val) {
24 | this.val = val;
25 | }
26 | }
27 |
28 | public int TreeDepth(TreeNode root) {
29 | if (root == null) {
30 | return 0;
31 | }
32 | int left = TreeDepth(root.left);
33 | int right = TreeDepth(root.right);
34 | return Math.max(left, right) + 1;
35 | }
36 | ```
37 | ```cpp
38 | class Solution {
39 | public:
40 | int depth(TreeNode *root) {
41 | if (root == nullptr) {
42 | return 0;
43 | }
44 | int left = depth(root->left);
45 | int right = depth(root->right);
46 | return max(left, right) + 1;
47 | }
48 | };
49 | ```
50 |
51 | #### BFS
52 | ```java
53 | import java.util.LinkedList;
54 | import java.util.Queue;
55 |
56 | /**
57 | *
4 |
5 |
6 | ## 题目描述
7 | 输入一棵二叉树,判断该二叉树是否是平衡二叉树。
8 |
在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树
9 |
10 | ## 代码
11 | 左子树和右子树高度差不得超过1 即为平衡二叉树;
12 | ```bash
13 | public class Solution {
14 | public boolean IsBalanced_Solution(TreeNode root) {
15 | if (root == null) {
16 | return true;
17 | }
18 | int left=depth(root.left);
19 | int right=depth(root.right);
20 | int diff=Math.abs(left-right);
21 | if (diff>1){
22 | return false;
23 | }
24 | return IsBalanced_Solution(root.left)&&IsBalanced_Solution(root.right);
25 | }
26 |
27 | private int depth(TreeNode root) {
28 | if (root == null) {
29 | return 0;
30 | }
31 | int left = depth(root.left);
32 | int right = depth(root.right);
33 | return Math.max(left, right) + 1;
34 | }
35 |
36 | }
37 | ```
38 | ```cpp
39 | class Solution {
40 | public:
41 | bool IsBalanced_Solution(TreeNode *pRoot) {
42 | if (pRoot == nullptr) {
43 | return true;
44 | }
45 | int left = depth(pRoot->left);
46 | int right = depth(pRoot->right);
47 | if (abs(left - right) > 1) {
48 | return false;
49 | }
50 | return IsBalanced_Solution(pRoot->left) && IsBalanced_Solution(pRoot->right);
51 | }
52 |
53 | public:
54 | int depth(TreeNode *root) {
55 | if (root == nullptr) {
56 | return 0;
57 | }
58 | int left = depth(root->left);
59 | int right = depth(root->right);
60 | return max(left, right) + 1;
61 | }
62 | };
63 | ```
64 |
--------------------------------------------------------------------------------
/docs/notes/算法与数据结构/剑指Offer 笔记汇总/4.重建二叉树.md:
--------------------------------------------------------------------------------
1 | # 4.重建二叉树
2 |
3 |
4 | ## 题目描述
5 | ```java
6 | 给出一个二叉树的先序和中序遍历,还原这棵二叉树
7 | ```
8 | ```java
9 | /**
10 | * Definition for binary tree
11 | * public class TreeNode {
12 | * int val;
13 | * TreeNode left;
14 | * TreeNode right;
15 | * TreeNode(int x) { val = x; }
16 | * }
17 | */
18 | public class Solution {
19 | /**
20 | * 重建二叉树
21 | *
22 | * @param pre
23 | * @param in
24 | * @return
25 | */
26 | public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
27 | return reConstructBinaryTree(pre, 0, pre.length - 1, in, 0, in.length - 1);
28 | }
29 |
30 | /**
31 | * 5
32 | * / \
33 | * 3 2
34 | * / \ / \
35 | * 1 4 6 8
36 | * pre[根左右]:5 3 1 4 2 6 8
37 | * in[左根右] :1 3 4 5 6 2 8
38 | * out[左右根]:1 4 3 6 8 2 5
39 | * @param pre
40 | * @param preL
41 | * @param preR
42 | * @param in
43 | * @param inL
44 | * @param inR
45 | * @return
46 | */
47 | private TreeNode reConstructBinaryTree(int[] pre, int preL, int preR, int[] in, int inL, int inR) {
48 | if (preL > preR || inL > inR) {
49 | return null;
50 | }
51 | TreeNode root = new TreeNode(pre[preL]);
52 | int i = inL;
53 | while (in[i] != pre[preL] && i <= inR) {
54 | ++i;
55 | }
56 | /**
57 | * * 5
58 | * * / \
59 | * * 3 2
60 | * * / \ / \
61 | * * 1 4 6 8
62 | * * pre[根左右]:5 3 1 4 2 6 8
63 | * * in [左根右]:1 3 4 5 6 2 8
64 | * * out[左右根]:1 4 3 6 8 2 5
65 | */
66 | root.left = reConstructBinaryTree(pre, preL + 1, preL + (i - inL), in, inL, i - 1);
67 | root.right = reConstructBinaryTree(pre, preL + (i - inL) + 1, preR, in, i + 1, inR);
68 | return root;
69 | }
70 | }
71 | ```
72 |
--------------------------------------------------------------------------------
/docs/notes/算法与数据结构/剑指Offer 笔记汇总/42.和为S的两个数字.md:
--------------------------------------------------------------------------------
1 | # 42.和为S的两个数字
2 |
3 | LC : [https://leetcode-cn.com/problems/he-wei-sde-liang-ge-shu-zi-lcof/](https://leetcode-cn.com/problems/he-wei-sde-liang-ge-shu-zi-lcof/)
牛客:[https://www.nowcoder.com/practice/390da4f7a00f44bea7c2f3d19491311b?tpId=13&&tqId=11195&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking](https://www.nowcoder.com/practice/390da4f7a00f44bea7c2f3d19491311b?tpId=13&&tqId=11195&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
4 |
5 | ```cpp
6 | 双指针,一个从左,一个从右遍历,两数之和大于target 的话 right--, 两数之和小于target的话 left++
7 | ```
8 |
9 |
10 | ```cpp
11 | class Solution {
12 | public:
13 | vector
返回描述:
如果数组中有重复的数字,函数返回true,否则返回false。
如果数组中有重复的数字,把重复的数字放到参数duplication[0]中。(ps:duplication已经初始化,可以直接赋值使用。)
4 |
5 | # 代码
6 |
7 | ## 方法一
8 | ```
9 | 如果没有重复数字,那么正常排序后,数字i应该在下标为i的位置,所以思路是重头扫描数组,遇到下标为i的数字如果不是i的话,
10 | (假设为m),那么我们就拿与下标m的数字交换。在交换过程中,如果有重复的数字发生,那么终止返回ture
11 | ```
12 | ```java
13 | //存在BUG
14 | public int findRepeatNumber(int[] nums) {
15 | for (int i = 0; i < nums.length; ++i) {
16 | while (nums[i] != i) {
17 | if (nums[i] == nums[nums[i]]) {
18 | return nums[i];
19 | }
20 | int temp = nums[i];
21 | nums[i] = nums[temp];
22 | nums[temp] = temp;
23 | }
24 | }
25 | return -1;
26 | }
27 | ```
28 |
29 | ## 方法二
30 | ```
31 | class Solution {
32 | public:
33 | // 2, 3, 1, 0, 2, 5
34 | // 1, 3, 2, 0, 2, 5
35 | // 3, 1, 2, 0, 2, 5
36 | // 0, 1, 2, 3, 2, 5
37 | int duplicate(vector
4 |
5 | ```java
6 | /*
7 | public class ListNode {
8 | int val;
9 | ListNode next = null;
10 |
11 | ListNode(int val) {
12 | this.val = val;
13 | }
14 | }
15 | */
16 | public class Solution {
17 |
18 | public ListNode EntryNodeOfLoop(ListNode pHead) {
19 | if (pHead == null) {
20 | return pHead;
21 | }
22 | ListNode fast = pHead;
23 | ListNode slow = pHead;
24 | boolean isExist = false;
25 | while (slow.next != null && fast.next.next != null) {
26 | slow = slow.next;
27 | fast = fast.next.next;
28 | if (slow == fast) {
29 | isExist = true;
30 | break;
31 | }
32 | }
33 | if (isExist) {
34 | slow = pHead;
35 | while (slow != null && fast != null) {
36 | if (slow == fast) {
37 | return slow;
38 | }
39 | slow = slow.next;
40 | fast = fast.next;
41 |
42 | }
43 | }
44 | return null;
45 | }
46 | }
47 | ```
48 |
49 |
50 |
51 |
52 |
53 | # 引用
54 | [https://blog.csdn.net/sinat_35261315/article/details/79205157?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param](https://blog.csdn.net/sinat_35261315/article/details/79205157?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.channel_param)
55 |
56 |
--------------------------------------------------------------------------------
/docs/notes/算法与数据结构/剑指Offer 笔记汇总/56-删除链表中重复的节点.md:
--------------------------------------------------------------------------------
1 | # 56-删除链表中重复的节点
2 |
3 |
4 | ## 题目描述
5 | ```java
6 | 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
7 | ```
8 |
9 | ## 分析
10 | ```java
11 | 注意 : 重复的节点不保留
12 | ```
13 |
14 | ## 代码
15 | ```java
16 | /*
17 | public class ListNode {
18 | int val;
19 | ListNode next = null;
20 |
21 | ListNode(int val) {
22 | this.val = val;
23 | }
24 | }
25 | */
26 | public class Solution {
27 | /**
28 | * @param head
29 | * @return
30 | */
31 | public ListNode deleteDuplication(ListNode head) {
32 | if (head == null) {
33 | return head;
34 | }
35 | ListNode toolNode = new ListNode(-1);
36 | toolNode.next = head;
37 | ListNode pre = toolNode;
38 | ListNode last = toolNode.next;
39 | while (last != null) {
40 | if (last.next != null && last.val == last.next.val) {
41 | while (last.next != null && last.val == last.next.val) {
42 | last = last.next;
43 | }
44 | pre.next = last.next;
45 | last = last.next;
46 | } else {
47 | pre=pre.next;
48 | last=last.next;
49 | }
50 | }
51 | // 1 1 1
52 | // 1 2 3 4
53 | // x 1 1 1 1 2 2 3 4 4 5
54 | // pre last
55 | return toolNode.next;
56 | }
57 | }
58 | ```
59 |
60 | ## 相关例题
61 |
62 |
63 |
--------------------------------------------------------------------------------
/docs/notes/算法与数据结构/剑指Offer 笔记汇总/6.旋转数组的最小数字.md:
--------------------------------------------------------------------------------
1 | # 6.旋转数组的最小数字
2 |
3 |
4 | ## 题目描述
5 | 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组[3,4,5,1,2]为[1,2,3,4,5]的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
6 |
7 | ## 代码
8 | ```java
9 | import java.util.ArrayList;
10 | public class Solution {
11 | public int minNumberInRotateArray(int[] array) {
12 | int low = 0, high = array.length - 1;
13 | while (low < high) {
14 | int mid = low + ((high - low) >> 1);
15 | if (array[mid] > array[high]) {
16 | low = mid + 1;
17 | } else if (array[mid] == array[high]) {
18 | high -= 1;
19 | } else {
20 | high = mid;
21 | }
22 | }
23 | return array[low];
24 | }
25 | }
26 | ```
27 |
--------------------------------------------------------------------------------
/docs/notes/算法与数据结构/剑指Offer 笔记汇总/60.把二叉树打印出多行.md:
--------------------------------------------------------------------------------
1 | [题目](https://www.nowcoder.com/practice/445c44d982d04483b04a54f298796288?tpId=13&tqId=11213&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking&tab=answerKey)
2 |
3 | ```cpp
4 | /*
5 | struct TreeNode {
6 | int val;
7 | struct TreeNode *left;
8 | struct TreeNode *right;
9 | TreeNode(int x) :
10 | val(x), left(NULL), right(NULL) {
11 | }
12 | };
13 | */
14 | class Solution {
15 | public:
16 |
17 | vector
2 |
3 | ```cpp
4 | class Solution {
5 | public:
6 | priority_queue
n<=39
6 |
7 |
8 | ## 代码
9 | ```java
10 | public class Solution {
11 | public int Fibonacci(int n) {
12 | int f1 = 1, f2 = 1, f3 = 0;
13 | if (n == 1 || n == 2) return f1;
14 | else {
15 | for (int i = 3; i <= n; i++) {
16 | f3 = f1 + f2;
17 | f1 = f2;
18 | f2 = f3;
19 | }
20 | return f3;
21 | }
22 | }
23 |
24 | }
25 | ```
26 |
--------------------------------------------------------------------------------
/docs/notes/算法与数据结构/剑指Offer 笔记汇总/8.跳台阶.md:
--------------------------------------------------------------------------------
1 | # 8.跳台阶
2 |
3 |
4 | ## 题目描述
5 | 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
6 |
7 |
8 | ## 代码
9 | ```java
10 | public class Solution {
11 | public int JumpFloor(int target) {
12 | if(target==1)return 1;
13 | else if(target==2) return 2;
14 | else {
15 | return JumpFloor(target-1)+JumpFloor(target-2);
16 | }
17 | }
18 | }
19 | ```
20 |
--------------------------------------------------------------------------------
/docs/notes/算法与数据结构/剑指Offer 笔记汇总/9.变态跳台阶.md:
--------------------------------------------------------------------------------
1 | # 9.变态跳台阶
2 |
3 |
4 | ## 题目描述
5 | 一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
6 | ```java
7 | public class Solution {
8 | public int JumpFloorII(int target) {
9 | if (target < 1) {
10 | return -1;
11 | } else if (target == 1) {
12 | return 1;
13 | } else {
14 | return 2 * JumpFloorII(target - 1);
15 | }
16 | }
17 | }
18 | ```
19 |
--------------------------------------------------------------------------------
/docs/notes/算法与数据结构/剑指Offer 笔记汇总/剑指 Offer 笔记汇总.md:
--------------------------------------------------------------------------------
1 | # 剑指 Offer 笔记汇总
2 |
3 |
4 | ## 链表
5 | | [剑指Offer(3):从头到尾打印链表](https://www.yuque.com/littledream/newbie-plan/ok26fd) | |
6 | | --- | --- |
7 | | 剑指Offer(14):链表倒数第K个节点 | |
8 | | 剑指Offer(16):合并两个有序链表 | |
9 | | 剑指Offer(36):两个链表的第一个公共节点 | |
10 | | 剑指Offer(55):链表中环的位置 | |
11 | | 剑指Offer(56):删除链表中重复的节点 | |
12 |
13 |
14 |
15 |
16 | ## 二叉树
17 | | [剑指Offer(38):二叉树的深度](https://www.yuque.com/littledream/newbie-plan/qmds95) | |
18 | | --- | --- |
19 | | | |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/docs/notes/缓存/Redis/0.Redis 面试题.md:
--------------------------------------------------------------------------------
1 |
2 | #### 1.为什么Redis采用单线程模型会达到每秒万级别的处理能力?
3 | ```cpp
4 | 1. 纯内存访问,内存响应时间为100纳秒。(1毫秒=1000微妙、1微妙=1000纳秒)
5 | 2. 非阻塞I/O,Redis使用epoll作为I/O多路复用技术的实现,不在网络I/O上浪费过多的时间。
6 | 3. 单线程避免了线程切换和竞态产生的消耗。
7 | 另外,单线程对于每个命令执行时间是有要求的,如果某个命令执行时间过长,会造成其他命令的阻塞,这对于Redis这种高性能服务是致命的,
8 | 所以Redis是面向快速执行场景的数据库。
9 | ```
10 |
11 | #### 2.Redis的缓存命中率是什么?如何提高Redis的缓存命中率?
12 | ```cpp
13 | 1. 缓存命中率 是什么?
14 | keyspace_hits:成功从缓存中查询到key则为命中;
15 | keyspace_misses:不能从缓存中查询到key则为未命中;
16 | 命中率即为keyspace_hits/(keyspace_hits+keyspace_misses)的值。
17 |
18 | 2.如何提高Redis的缓存命中率?
19 |
20 | - 将高频读取且时效性不高的的数据缓存到Redis,读取次数越多,命中率越高;
21 | - 设置合适的key过期策略,在相同Key和相同请求的情况下,缓存时间越长,命中率越高;
22 | - 设置合适的缓存更新策略,更新数据缓存值比数据清理后再插入新的值命中率更高;
23 | - 设置合适的缓存粒度大小,粒度越小,命中率会越高,同时也能降低bigkey的风险,但也会增加系统复杂度;
24 |
25 | ```
26 |
--------------------------------------------------------------------------------
/docs/notes/缓存/Redis/1.Redis数据类型/1.4 set.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # 1.内部结构
4 | set类型内部存储结构有2种:
5 |
6 | - insert(整数集合):当集合中的元素都是整数且元素个数小于set-maxintset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。
7 | - hashtable(哈希表):当集合类型无法满足insert的条件时,Redis会使用hashtable (dict.c)作为集合的内部实现;
8 |
9 | ## 1.1 整数集合(inset)
10 | ```cpp
11 | typedef struct intset {
12 | uint32_t encoding;
13 | uint32_t length;
14 | int8_t contents[];
15 | } intset;
16 | ```
17 | 整数集合(intset)是Redis用于保存整数值的集合抽象数据结构,它可以保存类型为int16_t、int32_t或者int64_t的整数值,并且保证集合中不会出现重复元素。
18 |
19 | ### 1.1.1 组成部分
20 |
21 | #### 1.1.1.1 encoding
22 | ```cpp
23 | /* Note that these encodings are ordered, so:
24 | * INTSET_ENC_INT16 < INTSET_ENC_INT32 < INTSET_ENC_INT64. */
25 | #define INTSET_ENC_INT16 (sizeof(int16_t))
26 | #define INTSET_ENC_INT32 (sizeof(int32_t))
27 | #define INTSET_ENC_INT64 (sizeof(int64_t))
28 | ```
29 | contents数组的真正类型取决于encoding属性的值。
encoding属性的值为INTSET_ENC_INT16则数组就是uint16_t类型,数组中的每一个元素都是int16_t类型的整数值(-32768——32767),
encoding属性的值为INTSET_ENC_INT32则数组就是uint32_t类型,数组中的每一个元素都是int16_t类型的整数值(-2147483648——2147483647)。
30 |
31 |
32 | #### 1.1.1.2 length
33 | length属性记录了数组的长度
34 |
35 |
36 | #### 1.1.1.3 contents
37 | contents数组是整数集合的底层实现,整数集合的每个元素都是 contents数组的个数组项(item),各个项在数组中按值的大小从小到大有序地排列,并且数组中不包含任何重复项。
38 |

39 |
40 | 如上图,为一int16_t类型的整数集合,我们可以看到数组中存储了5个int16_t类型的整数,它们按照从小到大的顺序依次排列。这个时候我们思考一个问题。如果这个时候存入一个int32_t类型的整数会怎么样?内存溢出?这个时候就要提到整数集合的升级。
41 |
42 |
43 | ### 1.1.2 整数集合的升级
44 |
45 | #### 1.1.2.1 整数集合升级过程
46 | 正如上面所提到的问题,每当我们要将一个新元素添加到整数集合里面,并且新元素的类型比整数集合现有所有元素的类型都要长时,整数集合需要先进行升级,然后才能将新元素添加到整数集合里面。升级整数集合并添加新元素主要分三步来进行。
47 |
48 | - [x] 根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间。
49 | - [x] 将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素放置到正确的位上,而且在放置元素的过程中,需要继续维持底层数组的有序性质不变。
50 | - [x] 将新元素添加到底层数组里面;
51 |
52 | 
53 |
54 | #### 1.1.2.2 整数集合升级的优点
55 |
56 | - 提升灵活性
57 | ```cpp
58 | 因为C语言是静态类型语言,为了避免类型错误,我们通常不会将两种不同类型的值放在同一个数据结构里面。
59 | 例如,我们一般只使用int16_t类型的数组来保存int16_t类型的值,只使用int32_t类型的数组来保存int32_t类型的值,诸如此类。
60 | 但是,因为整数集合可以通过自动升级底层数组来适应新元素,所以我们可以随意地将int16_t、int32_t或者int64_t类型的整数添加到集合中,
61 | 而不必担心出现类型错误,这种做法非常灵活。
62 | ```
63 |
64 | - 节约内存
65 | ```cpp
66 | 要让一个数组可以同时保存int16_t、int32_t、int64_t三种类型的值,最简单的做法就是直接使用int64t类型的数组作为整数集合的底层实现。
67 | 不过这样一来,即使添加到整数集合里面的都是int16_t类型或者int32_t类型的值,数组都需要使用int64_t类型的空间去保存它们,从而出现浪费内存的情况。
68 |
69 | 而整数集合现在的做法既可以让集合能同时保存三种不同类型的值,又可以确保升级操作只会在有需要的时候进行,这可以尽量节省内存。
70 | 如果我们一直只向整数集合添加int16_t类型的值,那么整数集合的底层实现就会一直是int16_t类型的数组,只有在我们要将int32_t类型或者int64_t类型的值添加到集合时,程序才会对数组进行升级。
71 | ```
72 |
73 | ### 1.1.3 缺点-无法降级
74 | 整数集合不支持降级操作,一旦对数组进行了升级,编码就会一直保持升级后的状态。也就是说一旦我们向一个int16_t的整数集合内添加了一个int32_t的元素后,整数集合将升级到int32_t类型。即使后续的操作中我们删除了这个元素,整数集合还是会保持int32_t类型的状态。可能在某些场景下会造成内存浪费。
75 |
76 | ## 1.2 哈希表 hashtable
77 | 详情查看 1.3 hash 中描述。
78 |
79 |
80 | # 2.常用命令
81 | | 命令 | 说明 | 时间复杂度 |
82 | | --- | --- | --- |
83 | | [SADD key member [member ...]](http://blog.laoyu.site/2020/redis_command/set/sadd/) | 添加一个或者多个元素到集合(set)里 | O(N) |
84 | | [SCARD key](http://blog.laoyu.site/2020/redis_command/set/scard/) | 获取集合里面的元素数量 | O(1) |
85 | | [SDIFF key [key ...]](http://blog.laoyu.site/2020/redis_command/set/sdiff/) | 获得队列不存在的元素 | O(N) |
86 | | [SDIFFSTORE destination key [key ...]](http://blog.laoyu.site/2020/redis_command/set/sdiffstore/) | 获得队列不存在的元素,并存储在一个关键的结果集 | O(N) |
87 | | [SINTER key [key ...]](http://blog.laoyu.site/2020/redis_command/set/sinter/) | 获得两个集合的交集 | O(N*M) |
88 | | [SINTERSTORE destination key [key ...]](http://blog.laoyu.site/2020/redis_command/set/sinterstore/) | 获得两个集合的交集,并存储在一个关键的结果集 | O(N*M) |
89 | | [SISMEMBER key member](http://blog.laoyu.site/2020/redis_command/set/sismember/) | 确定一个给定的值是一个集合的成员 | O(1) |
90 | | [SMEMBERS key](http://blog.laoyu.site/2020/redis_command/set/smembers/) | 获取集合里面的所有元素 | O(N) |
91 | | [SMOVE source destination member](http://blog.laoyu.site/2020/redis_command/set/smove/) | 移动集合里面的一个元素到另一个集合 | O(1) |
92 | | [SPOP key [count]](http://blog.laoyu.site/2020/redis_command/set/spop/) | 删除并获取一个集合里面的元素 | O(1) |
93 | | [SRANDMEMBER key [count]](http://blog.laoyu.site/2020/redis_command/set/srandmember/) | 从集合里面随机获取一个元素 | |
94 | | [SREM key member [member ...]](http://blog.laoyu.site/2020/redis_command/set/srem/) | 从集合里删除一个或多个元素 | O(N) |
95 | | [SUNION key [key ...]](http://blog.laoyu.site/2020/redis_command/set/sunion/) | 添加多个set元素 | O(N) |
96 | | [SUNIONSTORE destination key [key ...]](http://blog.laoyu.site/2020/redis_command/set/sunionstore/) | 合并set元素,并将结果存入新的set里面 | O(N) |
97 | | [SSCAN key cursor [MATCH pattern] [COUNT count]](http://blog.laoyu.site/2020/redis_command/set/sscan/) | 迭代set里面的元素 | O(1) |
98 |
99 |
100 | # 3.使用场景
101 | 通过上文,我们可以知道集合的主要几个特性,无序、不可重复、支持并交差等操作。因此集合类型比较适合用来数据去重和保障数据的唯一性,还可以用来统计多个集合的交集、错集和并集等,当我们存储的数据是无序并且需要去重的情况下,比较适合使用集合类型进行存储
102 |
103 | ## 3.1 标签系统
104 | 集合类型比较典型的使用场景是标签(tag)。
1.给用户添加标签。
105 | ```cpp
106 | sadd user:1:tags tag1 tag2 tag5
107 | sadd user:2:tags tag2 tag3 tag5
108 | ...
109 | sadd user:k:tags tag1 tag2 tag4
110 | ...
111 | ```
112 | 2.给标签添加用户
113 | ```cpp
114 | sadd tag1:users user:1 user:3
115 | sadd tag2:users user:1 user:2 user:3
116 | ...
117 | sadd tagk:users user:1 user:2
118 | ...
119 | ```
120 | 3.使用sinter命令,可以来计算用户共同感兴趣的标签
121 | ```cpp
122 | sinter user:1:tags user:2:tags
123 | ```
124 | 这种标签系统在电商系统、社交系统、视频网站,图书网站,旅游网站等都有着广泛的应用。例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于用户体验以及增强用户黏度比较重要。例如一个社交系统可以根据用户的标签进行好友的推荐,已经用户感兴趣的新闻的推荐等,一个电子商务的网站会对不同标签的用户做不同类型的推荐,比如对数码产品比较感兴趣的人,在各个页面或者通过邮件的形式给他们推荐最新的数码产品,通常会为网站带来更多的利益
125 |
126 |
127 | ## 3.2 抽奖系统
128 | Redis集合的 [SPOP(随机移除并返回集合中一个或多个元素)](https://blog.laoyu.site/2020/redis_command/set/spop/) 和 [SRANDMEMBER(随机返回集合中一个或多个元素)](https://blog.laoyu.site/2020/redis_command/set/srandmember/) 命令可以帮助我们实现一个抽奖系统
如果允许重复中奖,可以使用SRANDMEMBER 命令

129 |
--------------------------------------------------------------------------------
/docs/notes/缓存/Redis/1.Redis数据类型/1.5 zset.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # 1.内部结构
4 | zset是由ziplist和skiplist组成的。
5 |
6 | 当数据比较少时,有序集合使用的是 ziplist 存储的,有序集合使用 ziplist 格式存储必须满足以下两个条件:
7 |
8 | - **有序集合保存的元素个数**要小于 128 个;
9 | - 有序集合保存的**所有元素成员的长度都必须小于** 64 字节。
10 |
11 | ## 1.1 压缩列表(ziplist)
12 | 之前讲过,自取。
13 |
14 |
15 | ## 1.2 跳跃表(skiplist)
16 | 
17 |
18 | ### 1.2.1 什么是跳跃表?
19 |
20 | #### 1.2.1.1 zset
21 | ```cpp
22 | typedef struct zset {
23 | dict *dict;
24 | zskiplist *zsl;
25 | } zset;
26 | ```
27 |
28 |
29 |
30 |
31 | #### 1.2.1.2 zskiplist
32 | ```cpp
33 | typedef struct zskiplist {
34 | struct zskiplistNode *header, *tail;
35 | unsigned long length;
36 | int level;
37 | } zskiplist;
38 | ```
39 |
40 | - header
41 |
42 | 指向跳跃表的表头节点,通过这个指针程序定位表头节点的时间复杂度就为O(1)
43 |
44 | - tail
45 |
46 | 指向跳跃表的表尾节点,通过这个指针程序定位表尾节点的时间复杂度就为O(1)
47 |
48 | - level
49 |
50 | 记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内),通过这个属性可以再O(1)的时间复杂度内获取层高最好的节点的层数。
51 |
52 | - length
53 |
54 | 记录跳跃表的长度,也即是,跳跃表目前包含节点的数量(表头节点不计算在内),通过这个属性,程序可以再O(1)的时间复杂度内返回跳跃表的长度。
55 |
56 |
57 | #### 1.2.1.3 zskiplistNode
58 | 
59 | ```cpp
60 | typedef struct zskiplistNode {
61 | sds ele;
62 | double score;
63 | struct zskiplistNode *backward; //后退指针
64 | struct zskiplistLevel {
65 | struct zskiplistNode *forward; //前进指针
66 | unsigned long span;//跨度
67 | } level[]; //层数
68 | } zskiplistNode;
69 | ```
70 |
71 | - **level 层**
72 |
73 | 节点中用L1、L2、L3等字样标记节点的各个层,L1代表第一层,L代表第二层,以此类推。
74 |
--------------------------------------------------------------------------------
/docs/notes/缓存/Redis/Redis对象机制.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # 1.为什么Redis会设计redisObject对象?
4 | [
](https://www.pdai.tech/md/db/nosql-redis/db-redis-x-redis-object.html)
在redis的命令中,用于对键进行处理的命令占了很大一部分,而对于键所保存的值的类型(键的类型),键能执行的命令又各不相同。如: LPUSH 和 LLEN 只能用于列表键, 而 SADD 和 SRANDMEMBER 只能用于集合键, 等等; 另外一些命令, 比如 DEL、 TTL 和 TYPE, 可以用于任何类型的键;但是要正确实现这些命令, 必须为不同类型的键设置不同的处理方式: 比如说, 删除一个列表键和删除一个字符串键的操作过程就不太一样。
以上的描述说明, **Redis 必须让每个键都带有类型信息, 使得程序可以检查键的类型, 并为它选择合适的处理方式**.
比如说, 集合类型就可以由字典和整数集合两种不同的数据结构实现, 但是, 当用户执行 ZADD 命令时, 他/她应该不必关心集合使用的是什么编码, 只要 Redis 能按照 ZADD 命令的指示, 将新元素添加到集合就可以了。
这说明, **操作数据类型的命令除了要对键的类型进行检查之外, 还需要根据数据类型的不同编码进行多态处理**.
5 |
6 |
7 | # 2.redisObjest数据结构
8 | ```objectivec
9 | /*
10 | * Redis 对象
11 | */
12 | typedef struct redisObject {
13 |
14 | // 类型
15 | unsigned type:4;
16 |
17 | // 编码方式
18 | unsigned encoding:4;
19 |
20 | // LRU - 24位, 记录最末一次访问时间(相对于lru_clock); 或者 LFU(最少使用的数据:8位频率,16位访问时间)
21 | unsigned lru:LRU_BITS; // LRU_BITS: 24
22 |
23 | // 引用计数
24 | int refcount;
25 |
26 | // 指向底层数据结构实例
27 | void *ptr;
28 |
29 | } robj;
30 |
31 | ```
32 | 
33 |
34 | ## 2.1 type
35 | **type记录了对象所保存的值的类型**
36 | ```objectivec
37 | /*
38 | * 对象类型
39 | */
40 | #define OBJ_STRING 0 // 字符串
41 | #define OBJ_LIST 1 // 列表
42 | #define OBJ_SET 2 // 集合
43 | #define OBJ_ZSET 3 // 有序集
44 | #define OBJ_HASH 4 // 哈希表
45 |
46 | ```
47 |
48 | ## 2.2 encoding
49 | **记录了对象所保存的值的编码**
50 | ```objectivec
51 | /*
52 | * 对象编码
53 | */
54 | #define OBJ_ENCODING_RAW 0 /* Raw representation */
55 | #define OBJ_ENCODING_INT 1 /* Encoded as integer */
56 | #define OBJ_ENCODING_HT 2 /* Encoded as hash table */
57 | #define OBJ_ENCODING_ZIPMAP 3 /* 注意:版本2.6后不再使用. */
58 | #define OBJ_ENCODING_LINKEDLIST 4 /* 注意:不再使用了,旧版本2.x中String的底层之一. */
59 | #define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
60 | #define OBJ_ENCODING_INTSET 6 /* Encoded as intset */
61 | #define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
62 | #define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
63 | #define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
64 | #define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
65 |
66 | ```
67 |
68 | ## 2.3 ptr 指针
69 | [
](https://www.pdai.tech/md/db/nosql-redis/db-redis-x-redis-object.html)
**ptr是一个指针,指向实际保存值的数据结构**,这个数据结构由type和encoding属性决定。举个例子, 如果一个redisObject 的type 属性为OBJ_LIST , encoding 属性为OBJ_ENCODING_QUICKLIST ,那么这个对象就是一个Redis 列表(List),它的值保存在一个QuickList的数据结构内,而ptr 指针就指向quicklist的对象;
70 |
--------------------------------------------------------------------------------
/docs/notes/缓存/Redis/Redis持久化.md:
--------------------------------------------------------------------------------
1 |
2 | # 1.RDB
3 | 快照读
1、是什么 在指定的时间间隔内将内存中的数据集快照(snapshot)进磁盘里,恢复的时候是将快照文件读到磁盘里。
4 |
5 | 2、Fork
1)、fork的作用是复制一个与当前进程一样的进程。新进程的所有数据都与原进程一致,并作为原进程的子进程。
2)、Redis 会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,
在用这个临时文件替换上次持久化好的文件;
6 |
7 | 3、RDB 保存的是dump.rdb文件(默认保存位置)
8 |
9 | 4、如何触发:
1)、redis.conf配置文件中,有触发机制(SNAPSHOTTING),共三种,默认900 1,300 10,60 10000
保存900 1 #900 秒内如果超过 1 个 Key 被修改,则启动快照保存
保存300 10 #300 秒内如果超过 10 个 Key 被修改,则启动快照保存
保存60 10000 #60 秒内如果超过 10000 个 key 被修改,则启动快照保存
10 |
11 | 2)、命令触发 :save、bgsave、flushall。
区别:
a:save 只管保存,执行此命令时,停止IO操作,会造成数据阻塞。
b:bgsave redis 在后台异步进行快照操作,快照同时响应客户端请求,通过 lastsave 命令获取最后一次成功执行快照的时间。
c:执行flushALl命令,也会产生dump.rdb文件,但是文件里是空的没有意义。
12 |
13 | 5、恢复备份文件:将备份文件(dump.rdb)移动到redis安装目录并启动服务即可;
14 |
15 | 6、优点:
1)、适合大规模的数据恢复,是一个紧凑压缩的二进制文件,Redis加载RDB恢复数据远远快于AOF的方式。
2)、对数据的完整性和一致性要求不高
总结:
会生成多个数据文件,每个数据文件分别都代表了某一时刻 Redis 里面的数据,这种方式,很适合做冷备,完整的数据运维设置定时任务,定时同步到远端的服务器,比如阿里的云服务,这样一旦线上挂了,你想恢复多少分钟之前的数据,就去远端拷贝一份之前的数据就好了。
RDB 对 Redis 的性能影响非常小,是因为在同步数据的时候他只是 fork 了一个子进程去做持久化的,而且他在数据恢复的时候速度比 AOF 来的快。
16 |
17 | 缺点:
1)、RDB 模式是在一定间隔时间做一次备份,如果 redis 意外 down 掉,会丢失最后一次快照之后修改的所有数据
2)、内存变大,fork 的时候,内存中的数据被克隆了一份,需要考虑2倍的膨胀性。
总结:
RDB 都是快照文件,都是默认五分钟甚至更久的时间才会生成一次,这意味着你这次同步到下次同步这中间五分钟的数据都很可能全部丢失掉。AOF 则最多丢一秒的数据,数据完整性上高下立判。
18 |
19 | 7、停止:redis-cli config set save ""
20 |
21 | # 2.AOF
22 | 当前读
1、是什么 以日志的形式来记录每个操作,将redis执行过的每个 “写” 命令记录下来 (读操作不记录),不可以改写文件,可以追加redis 在启动的时候,会读取该文件重新构建数据库。
23 |
24 | 2、文件信息
#开启AOF持久化存储方式
a、默认文件名:appendonly.aof
b、配置位置:将 redis.config 文件中的 appendonly no 改为 yes.(默认是no)
25 |
26 | 3、AOF持久化存储方式参数说明
1)、每修改同步(appendfsync always):同步持久化、收到写命令后就立即写入磁盘,效率最差,效果最好
2)、每秒同步(appendfsync everysec):异步操作,每秒记录,如果1秒内宕机,有数据丢失 [生产常用此种]
3)、不同步(appendfsync no) 从不同步:完全依赖操作系统,效率最佳,效果没法保证;
27 |
28 | 4、修复文件
redis-check-aof --fix appendonly.aof(注意是 --fix),之后重启redis然后在重新加载。
修复原理:redis-check-aof --fix 此命令会将文件中不符合规则的语法删掉。
29 |
30 | 5、rewrite 重写机制:
1)、是什么
避免出现文件越来越大的情况,新增的重写机制(AOF采用文件追加方式),当AOF文件大小超过设定的阈值,redis启动AOF文件的内容压缩,只保留可以恢复数据的最小指令(bgrewriteof命令)
31 |
32 | 2)、触发机制
redis 会记录上次重写是的 AOF 大小,当 AOF 文件是上次 rewrite 后大小的一倍,且文件大小大于64M时触发。
(redis.conf默认重写大小是64M,一般公司生产配置3G)
33 |
34 | 3)、重写的原理
a:创建新文件
AOF 文件持续增长过大时(文件追加),会 fork 出一个新进程将文件重写(先写一个临时文件,再将此文件rename),遍历新进程内存中的数据,记录每个写操作。重写的 aof 文件没有读取旧文件,而是将内存中的数据重新写进写的 aof 文件中。
b:同步数据
在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF 文件的末尾,使得新旧两个 AOF 文件所保存的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作
35 |
36 | 6、优缺点
优点:
可以实时持久化。
总结:
RDB 五分钟一次生成快照,但是 AOF 是一秒一次去通过一个后台的线程 fsync 操作,那最多丢这一秒的数据。AOF 在对日志文件进行操作的时候是以 append-only 的方式去写的,他只是追加的方式写数据,自然就少了很多磁盘寻址的开销了,写入性能惊人,文件也不容易破损。AOF 的日志是通过一个叫非常可读的方式记录的,这样的特性就适合做灾难性数据误删除的紧急恢复了,比如公司的实习生通过 flushall 清空了所有的数据,只要这个时候后台重写还没发生,你马上拷贝一份AOF 日志文件,把最后一条 flushall 命令删了就完事了。
37 |
38 | 缺点:
a、对于相同数据集的数据来说,AOF 文件要远远大于 RDB 文件,恢复速度比 RDB 慢
b、AOF 的运行速度要慢于 RDB,每秒同步策略较好。不同步效率和 RDB 相同。
39 |
40 | # 引用
41 | [https://www.cnblogs.com/java-zhao/p/5205768.html](https://www.cnblogs.com/java-zhao/p/5205768.html)
42 |
--------------------------------------------------------------------------------
/docs/notes/缓存/Redis/Redis缓存一致性问题.md:
--------------------------------------------------------------------------------
1 |
2 | # 1.数据更新场景
3 | ```cpp
4 | 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中;
5 | 命中:应用程序从cache中取数据,取到后返回;
6 | 更新:先把数据存到数据库中,成功后,再让缓存失效;
7 | ```
8 |
9 | ## 1.1 为啥对缓存的操作是失效 而不是更新 ?
10 | 多线程写缓存容易 造成脏数据,谁知道 那个线程快 哪个慢呢?
11 |
12 | ## 1.2 为啥不同时对DB和cache 操作呢 ?
13 | 事务一旦失败就会造成数据不一致的问题。
14 |
15 | # 2.数据不一致的原因
16 |
17 | ## 2.1 逻辑失败造成的数据不一致
18 | 在并发的情况下,无论是先删除缓存还是更新数据库,还是更新数据库再失效缓存,都会出现数据不一致的情况。
主要是因为 异步读写请求在并发情况下的操作时序导致的数据不一致,称之为“逻辑失败”。
解决这种因为并发导致的问题,核心的解决思路是将异步操作进行串行化。
19 |
20 | ## 2.2 物理失败造成的数据不一致
21 | 在Cache Aside Pattern 中先更新数据库再删除缓存以及异步双闪策略等,如果删除缓存失败时都出现数据不一致的情况。
但是数据库更新以及缓存操作是没办法放到一个事务中,一般来说,使用缓存是分布式缓存如果缓存服务很耗时,那么将更新数据库以及失效缓存放到一个事务中,就会造成大量的数据库链接挂起,严重的降低系统性能,甚至会因为数据库链接数过多,导致系统奔溃。像这种因为缓存操作失效,导致的数据不一致称之为“物理失效”。

22 |
23 | # 3. 缓存策略
24 | | Cache Aside 更新策略 | 先更新数据库,然后再删除缓存; |
25 | | --- | --- |
26 | | Read/Write Through 更新策略 | 先更新缓存,缓存负责同步更新数据库; |
27 | | Write Behind Caching 更新策略 | 先更新缓存,缓存定时异步更新数据库; |
28 |
29 |
30 | ## 3.1 Cache Aside 更新策略
31 | ```sql
32 | 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
33 | 命中:应用程序从cache中取数据,取到后返回;
34 | 更新:先把数据存到数据库中,成功后,再让缓存失效
35 | ```
36 |
37 | ### 3.1.1 先更新数据库,再更新缓存
38 |
39 | #### 原因一:线程安全问题
40 |
41 | 同时有请求A和请求B进行更新操作,那么会出现
(1)线程A更新了数据库
(2)线程B更新了数据库
(3)线程B更新了缓存
(4)线程A更新了缓存
这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。
42 |
43 |
44 | ### 3.1.2 先删除缓存,再更新数据库
45 | 有一个请求A进行更新操作,另一个请求B进行查询操作。(A的写事务 耗费的时间 远大于 B的查询事务)
(1)请求A进行写操作,删除缓存
(2)请求B查询发现缓存不存在
(3)请求B去数据库查询得到旧值
(4)请求B将旧值写入缓存
(5)请求A将新值写入数据库
此时缓存依旧是旧值,还是有脏数据。
**write:**
46 | ```sql
47 | public void write(String key,Object data){
48 | redis.delKey(key);
49 | db.updateData(data);
50 | Thread.sleep(1000);
51 | redis.delKey(key);
52 | }
53 | ```
54 | **read:**
55 | ```sql
56 | public void read(String key){
57 | if(redis.get(key)==null){
58 | return db.get(key);
59 | }else {
60 | return redis.get(key);
61 | }
62 | }
63 | ```
64 |
65 | #### 解决方案:延时双删策略
(1)先淘汰缓存
(2)再写数据库(这两步和原来一样)
(3)休眠1秒,再次淘汰缓存
66 |
67 | 双删在保证数据一致性上代价比较大。
68 |
69 | #### 疑问一:延时时间如何确定?
70 | ```sql
71 | 确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
72 | 几百ms 足矣;
73 | ```
74 |
75 | #### 疑问二:采用同步淘汰策略,吞吐量降低怎么办?
76 | ```sql
77 | 将第二次删除改为异步。起一个线程异步删除,写请求就不用沉睡
78 | ```
79 |
80 | #### 疑问三:第二次删除失败了,怎么办?
81 | 具体解决方案,且看第(3)种更新策略的解析。
82 |
83 |
84 | ### 3.1.3 先更新数据库,再删除缓存
85 |
86 | #### 3.1.3.2 存在的问题
87 | ```sql
88 | 假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生
89 | (1)缓存刚好失效
90 | (2)请求A查询数据库,得一个旧值;
91 | (3)请求B将新值写入数据库
92 | (4)请求B删除缓存
93 | (5)请求A将查到的旧值写入缓存
94 | ok,如果发生上述情况,确实是会发生脏数据。
95 |
96 |
97 | ```
98 | 这种情况 出现的概率很低。因为先天性 读 要比写快很多。 (2)要比(3)先执行完后写入缓存,(3)执行完后将缓存删掉。
99 |
100 | #### 如何解决如此抬杠的问题?
101 | ```sql
102 | 采用3.2 中的延时双删策略。
103 | ```
104 |
105 |
106 | ### 3.1.4 对于第二次删缓存失败了,怎么办 ?
107 | 提供一个保障的重试机制即可,能保证最后把缓存删掉是最后的目的。
这里给出两套方案。
108 |
109 |
110 | #### 3.1.4.1 业务中使用RocketMQ异步删缓存
111 | ESC系统就是这么搞得,一个缓存一个TOPIC,很是麻烦。

流程如下所示
(1)更新数据库数据;
(2)缓存因为种种问题删除失败
(3)将需要删除的key发送至消息队列
(4)自己消费消息,获得需要删除的key
(5)继续重试删除操作,直到成功
112 |
113 | ##### 缺点
114 | ```sql
115 | 对业务代码侵入比较严重
116 | ```
117 |
118 | #### 3.1.4.2 binlog + RocketMQ异步删缓存
119 | 
流程如下图所示:
(1)更新数据库数据
(2)数据库会将操作信息写入binlog日志当中
(3)订阅程序提取出所需要的数据以及key
(4)另起一段非业务代码,获得该信息
(5)尝试删除缓存操作,发现删除失败
(6)将这些信息发送至消息队列
(7)重新从消息队列中获得该数据,重试操作。
120 |
121 |
122 | ##### 优点
123 | ```sql
124 | 没有代码侵入。只需要监听数据库更新,之后使用单独的消息处理流程重试至成功。
125 | ```
126 |
127 | ## 3.2 Read/Write Through 更新策略
128 |
129 | ### 3.2.1 Read Through
130 | 不同点在于程序不需要再去管理从哪去读数据(缓存还是数据库)。相反它会直接从缓存中读数据,该场景下是缓存去决定从哪查询数据。当我们比较两者的时候这是一个优势因为它会让程序代码变得更简洁。

131 |
132 | ### 3.2.2 Write-Through
133 | Write-Through下所有的写操作都经过缓存,每次我们向缓存中写数据的时候,缓存会把数据持久化到对应的数据库中去,且这两个操作都在一个事务中完成。因此,只有两次都写成功了才是最终写成功了。这的确带来了一些写延迟但是它保证了数据一致性。
同时,因为程序只和缓存交互,编码会变得更加简单和整洁,当你需要在多处复用相同逻辑的时候这点变的格外明显。

当使用Write-Through的时候一般都配合使用Read-Through。
Write-Through适用情况有:
134 |
135 | - 需要频繁读取相同数据
136 | - 不能忍受数据丢失(相对Write-Behind而言)和数据不一致
137 |
138 | **Write-Through的潜在使用例子是银行系统。**
139 |
140 | ## 3.3 Write Behind Caching 更新策略
141 | Write-Behind和Write-Through在“程序只和缓存交互且只能通过缓存写数据”这一点上很相似。**不同点在于Write-Through会把数据立即写入数据库中,而Write-Behind会在一段时间之后(或是被其他方式触发)把数据一起写入数据库,这个异步写操作是Write-Behind的最大特点**。
数据库写操作可以用不同的方式完成,其中一个方式就是收集所有的写操作并在某一时间点(比如数据库负载低的时候)批量写入。另一种方式就是合并几个写操作成为一个小批次操作,接着缓存收集写操作(比如5个)一起批量写入。
142 |
143 | 异步写操作极大的降低了请求延迟并减轻了数据库的负担。**同时也放大了数据不一致的。**比如有人此时直接从数据库中查询数据,但是更新的数据还未被写入数据库,此时查询到的数据就不是最新的数据。
144 |
145 |
146 | ### 3.3.1 例子
147 | 使用例子,某一个页面的数据 查询的QPS达到了3000+, 这么高的QPS,如果同时修改 数据库 直接宕机,先操作Redis ,然后使用MQ异步修改 数据库。
148 |
149 | # 引用
150 | [https://www.cnblogs.com/rjzheng/p/9096228.html](https://www.cnblogs.com/rjzheng/p/9096228.html)
[https://blog.csdn.net/cywosp/article/details/23397179/](https://blog.csdn.net/cywosp/article/details/23397179/)
151 |
--------------------------------------------------------------------------------
/docs/notes/缓存/Redis/Redis缓存穿透_击穿_雪崩.md:
--------------------------------------------------------------------------------
1 |
2 | # 1. 缓存雪崩
3 | 
4 |
5 |
6 | # 2.缓存击穿
7 | 
8 |
9 | # 3.缓存穿透
10 | 
11 |
--------------------------------------------------------------------------------
/docs/notes/缓存/Redis/Redis过期策略及内存淘汰机制.md:
--------------------------------------------------------------------------------
1 |
2 | # 1.Redis 过期策略
3 | - [x] **Redis采用 惰性删除 和 定期删除 搭配方案**
4 |
5 | ## 1.1 定时删除(不使用)
6 |
7 | ### 1.1.1 含义
8 | 在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除;
9 |
10 | ### 1.1.2 优点
11 | 保证内存被尽快释放
12 |
13 | ### 1.1.3 缺点
14 |
15 | - 若过期key很多,删除这些key会占用很多的CPU时间,在CPU时间紧张的情况下,CPU不能把所有的时间用来做要紧的事儿,还需要去花时间删除这些key
16 | - 定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器产生),性能影响严重;
17 |
18 |
19 | ## 1.2 惰性删除
20 |
21 | ### 1.2.1 含义
22 | 每次从redis中获取key的时候去检查是否过期,若过期,则删除,返回null。
23 |
24 | ### 1.2.2 优点
25 | 删除操作只发生在从数据库取出key的时候发生,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步(如果此时还不删除的话,我们就会获取到了已经过期的key了)
26 |
27 | ### 1.2.3 缺点
28 | 若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,那么可能发生内存泄露(无用的垃圾占用了大量的内存)
29 |
30 | ## 1.3 定期删除
31 |
32 | ### 1.3.1 含义
33 | 每隔一段时间执行一次删除过期key操作
34 |
35 | ### 1.3.2 优点
36 |
37 | - 通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用--处理"定时删除"的缺点
38 | - 定期删除过期key--处理"惰性删除"的缺点;
39 |
40 | ### 1.3.3 缺点
41 |
42 | - 在内存友好方面,不如“定时删除”。
43 | - 在CPU时间友好方面,不如“惰性删除”。
44 |
45 | ### 1.3.4 难点
46 | 合理设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除)
47 |
48 |
49 |
50 | ## 1.4 Redis持久化对过期Key的处理
51 |
52 |
53 | ### 1.4.1 RDB对过期Key的处理
54 |
55 | 过期key对RDB没有任何影响;
56 |
57 | - 从内存数据库持久化数据到RDB文件
58 | - 持久化key之前,会检查是否过期,过期的key不进入RDB文件
59 | - 从RDB文件恢复数据到内存数据库
60 | - 数据载入数据库之前,会对key先进行过期检查,如果过期,不导入数据库(主库情况)
61 |
62 |
63 | ### 1.4.2 AOF对过期Key的处理
64 |
65 | 过期key对AOF没有任何影响;
66 |
67 | - 从内存数据库持久化数据到AOF文件:
68 | - 当key过期后,还没有被删除,此时进行执行持久化操作(该key是不会进入aof文件的,因为没有发生修改命令)
69 | - 当key过期后,在发生删除操作时,程序会向aof文件追加一条del命令(在将来的以aof文件恢复数据的时候该过期的键就会被删掉)
70 | - AOF重写
71 | - 重写时,会先判断key是否过期,已过期的key不会重写到aof文件
72 |
73 |
74 | # 2.Redis 内存淘汰机制
75 | 采用 惰性删除和 定期删除就没其他问题了吗?很明显,不可能。存在一种这样的情况,如果定期删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用**内存淘汰机制**。
在Redis.conf有一行这样的配置
76 | ```cpp
77 | maxmemory-policy volatile-lru
78 | ```
79 |
80 | ## 2.1 noeviction (驱逐)
81 | 当内存不足以容纳新写入数据时,新写入操作会报错。
82 |
83 | ## 2.2 allkeys-lru
84 | 从数据集中挑选最近最少使用的数据淘汰
当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。
85 |
86 | ## 2.3 allkeys-lfu
87 | 从数据集中挑选使用频率最低的数据淘汰。
88 |
89 | ## 2.4 allkeys-random
90 | 从数据集(server.db[i].dict)中任意选择数据淘汰
当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。(恐怖,这不是乱删吗?)
91 |
92 | ## 2.5 volatile-lru
93 | 从已设置过期时间的数据集中挑选**最近最少使用**的数据淘汰;
当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。
94 |
95 | ## 2.6 volatile-lfu(线上配置)
96 | 从所有配置了过期时间的键中驱逐使用频率最少的键。
97 |
98 | ## 2.7 volatile-random
99 | 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
100 |
101 | ## 2.8 volatile-ttl
102 | 从已设置过期时间的数据集中挑选将要过期的数据淘汰。
103 |
104 |
105 | # 引用
106 | [https://blog.csdn.net/sinat_30333853/article/details/80586161](https://blog.csdn.net/sinat_30333853/article/details/80586161)
107 |
--------------------------------------------------------------------------------
/docs/notes/计算机网络/常见面试题/1、TCP IP面试题.md:
--------------------------------------------------------------------------------
1 |
2 | # 计算机网络
3 | - [一文读懂一个URL请求的过程是怎样的](https://juejin.im/post/5b83b0bfe51d4538c63131a8)
4 | - [TCP的三次握手与四次挥手(详解+动图)](https://blog.csdn.net/qzcsu/article/details/72861891)
5 |
6 | - [计算机网络基础知识总结](https://www.cnblogs.com/maybe2030/p/4781555.html#top)
7 | - [计算机网络面试问题集锦](https://blog.csdn.net/justloveyou_/article/details/78303617)
8 |
--------------------------------------------------------------------------------
/docs/notes/计算机网络/计算机网络-1.OSI网络模型.md:
--------------------------------------------------------------------------------
1 | # 网络层次划分
2 | ## 1.常用网络层次划分
3 | ### 1.1 OSI/RM模型
4 | ```
5 | 该模型将计算机网络体系结构的通信协议划分为7层,自下而上为:
6 | 物理层(Physics Layer)
7 | 数据链路层(Data Link Layer)
8 | 网络层(Network Layer)
9 | 传输层(Transport Layer)
10 | 会话层(Session Layer)
11 | 表现层(Presentation Layer)
12 | 应用层(Application Layer)
13 | ```
14 | ### 1.2 TCP/IP 四层协议 及 TCP/IP 五层协议
15 | ```
16 | 除了标准的OSI七层模型以外,常见的网络层次划分还有TCP/IP四层协议以及TCP/IP五层协议,它们之间的对应关系如下图所示:
17 | ```
18 | 
19 |
20 | ## 2. OSI七层网络模型
21 | ### 2.1 物理层
22 | ### 2.2 数据链路层
23 |
--------------------------------------------------------------------------------
/docs/notes/设计模式/单例模式.md:
--------------------------------------------------------------------------------
1 |
2 | * [单例模式](#单例模式)
3 | * [1.介绍](#1介绍)
4 | * [2.实现方式](#2实现方式)
5 | * [2.1 懒汉式](#21-懒汉式)
6 | * [2.1.1 线程不安全的懒汉式](#211-线程不安全的懒汉式)
7 | * [2.1.2 线程安全的懒汉式](#212-线程安全的懒汉式)
8 | * [2.2 饿汉式](#22-饿汉式)
9 | * [2.3 静态内部类](#23-静态内部类)
10 | * [2.4 双重校验锁(DCL)](#23-双重校验锁dcl)
11 | * [2.4.1 双重锁机制分析](#231-双重锁机制分析)
12 | * [2.4.2 双重锁机制失效分析](#232-双重锁机制失效分析)
13 |
14 |
15 | # 单例模式
16 | ```
17 | 单例模式属于创建型模式,它提供了一种创建对象的最佳方式;
18 | ```
19 | ## 1.介绍
20 | - **目的** :
21 | ```
22 | 单例模式涉及到单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。
23 | 这个类提供了一种访问其唯一对象的方式,可以直接访问,不需要实例化该类的对象;
24 | ```
25 | - **主要解决** :
26 | ```
27 | 一个全局使用的类频繁地创建与销毁
28 | ```
29 | - **如何解决** :
30 | ```
31 | 判断系统是否已经有这个实例化对象,如果有直接返回,如果没有则创建;
32 | ```
33 | - **核心代码** :
34 | ```
35 | 构造函数要求是私有的 private,这样该类就不会被实例化;
36 | ```
37 | - **优点** :
38 | ```
39 | [1]在内存中只有一个实例,减少了内存开销,尤其是频繁地创建和销毁实例
40 | [2]避免对资源的多重占用[TODO 解释];
41 | ```
42 | ## 2.实现方式
43 | ### 2.1 懒汉式
44 | #### 2.1.1 线程不安全的懒汉式
45 | - 实现代码:
46 | ```
47 | public class Singleton {
48 |
49 | private static Singleton instance = null;
50 |
51 | /**
52 | * 私有化构造器,这样该类就不会被实例化;
53 | */
54 | private Singleton() {
55 |
56 | }
57 |
58 | public static Singleton getInstance() {
59 | //判断是否为空,如果是创建实例,如果不是直接返回;
60 | if (instance == null) {
61 | // JVM 重排序导致的空指针问题;
62 | // 1.在堆中开辟内存空间
63 | // 2.在堆内存中实例化Singleton();
64 | // 3.把对象指向堆空间;
65 | // 由于jvm存在乱序执行功能,所以可能在2还没执行时就先执行了3,如果此时再被切换到线程B上,由于执行了3,INSTANCE 已经非空了,// 会被直接拿出来用,这样的话,就会出现异常。这个就是著名的DCL失效问题。
66 | instance = new Singleton();
67 | }
68 | return instance;
69 | }
70 | }
71 | ```
72 | - 特性:
73 | - **延迟加载**
74 | - **非线程安全** :
75 | ```
76 | 多线程访问getInstance()方法都会得到实例,从严格意义上来讲,它不算单例模式;
77 | ```
78 | #### 2.1.2 线程安全的懒汉式
79 | - 实现代码:
80 | ```
81 | public class Singleton {
82 | private static Singleton instance = null;
83 | /**
84 | * 私有化构造器,这样该类就不会被实例化;
85 | * ;
86 | */
87 | private Singleton() {
88 |
89 | }
90 |
91 | public static synchronized Singleton getInstance() {
92 | if (instance == null) {
93 | instance = new Singleton();
94 | }
95 | return instance;
96 | }
97 | }
98 | ```
99 | - 特性:
100 | - **延迟加载**
101 | - **线程安全**
102 | - **效率低下** : 必须加锁 synchronized 才能保证单例,但加锁会影响效率
103 | - **避免内存浪费** :第一次调用才初始化,避免了内存浪费;
104 |
105 | ### 2.2 饿汉式
106 | - 实现代码:
107 | ```
108 | public class Singleton {
109 |
110 | private static Singleton instance = new Singleton();
111 |
112 | /**
113 | * 私有化构造器,这样该类就不会被实例化;
114 | * ;
115 | */
116 | private Singleton() {
117 | }
118 |
119 | public static Singleton getInstance() {
120 | return instance;
121 | }
122 | }
123 | ```
124 | - 特性:
125 | - **线程安全** :
126 | - **存在内存泄漏的风险**
127 | ```
128 | 采用饿汉模式,单实例对象便会在类加载完成之时,常驻堆中,后续访问时本质上是通过该类的Class对象嵌入的intance指针寻址,找到单实例对象的所在。
129 | 这一模式的好处在于:
130 | 1、通过空间换时间,避免了后续访问时由于对象的构造带来的时间上的开销;
131 | 2、(WHY) 无需考虑多线程的并发问题,JVM在类加载过程中,会通过内部加锁机制保证加载类的全局唯一性。
132 | 不好的地方,就是不管你用还是不用,只要完成了类加载,Heap中单实例对象所占的内存空间就被占据了,
133 | 某种程度上,也是内存泄漏的体现。这也是采用『饿汉模式』的由来。
134 | ```
135 | ### 2.3 静态内部类
136 | - 实现代码:
137 | ```
138 | public class Singleton {
139 | private Singleton() {
140 |
141 | }
142 |
143 | private static class SingleTonHolder {
144 | private static Singleton singleton = new Singleton();
145 | }
146 |
147 | private static Singleton getInstance() {
148 | return SingleTonHolder.singleton;
149 | }
150 | }
151 | ```
152 | - 特性:
153 | - **外部类加载时并不需要立即加载内部类 ( 静态内部类和非静态内部类一样,都是在被调用时才会被加载 ),内部类不被加载则不去初始化INSTANCE,故而不占内存**
154 | - URL: https://blog.csdn.net/mnb65482/article/details/80458571
155 |
156 | ### 2.4 双重校验锁(DCL)
157 | - 实现代码:
158 | ```
159 | public class Singleton {
160 |
161 | // volatile 禁止指令重排序
162 | private static volatile Singleton instance;
163 |
164 | /**
165 | * 私有化构造器,这样该类就不会被实例化;
166 | */
167 | private Singleton() {
168 | }
169 |
170 | public static Singleton getInstance() {
171 | if (instance == null) {
172 | synchronized (Singleton.class) {
173 | if (instance == null) {
174 | instance = new Singleton();
175 | return instance;
176 | }
177 | }
178 | }
179 | return instance;
180 | }
181 | }
182 | ```
183 | - 特性
184 | - **延迟加载**
185 | - **线程安全**
186 | - **JDK版本:1.5**
187 |
188 | #### 2.4.1 双重锁机制分析
189 |
190 | - 为什么使用双重锁机制
191 | ```
192 | 我们假设一种情况,就是当两个线程同时到达,即同时调用getInstance()方法.
193 | 此时由于singleTon == null,所以很明显,两个线程都可以通过第一重的检查(instance==null);
194 | 之后,由于锁机制的存在,所以只会有一个线程进入到临界区中,另一个线程只能在外面等待;
195 | 而当第一个线程执行完new Singleton()语句之后,第二个线程便可以进去到临界区中;
196 | 此时,如果没有第二道instance==null的判断,
197 | 那么第二个线程还是可以调用new Singleton()语句去生成新的实例化对象
198 | ,这就违背了单例模式的初衷。
199 |
200 | ```
201 | - 假设将第一个判断去掉,是否会出现异常呢?
202 | ```
203 | 当我们去掉第一个非空判断后,程序在多线程情况下还是可以完好的运行的;
204 | 在不考虑第一个判断的情况下:
205 | 当两个线程同时到达,由于锁机制的存在,第一个线程进入临界区之后去初始化new SingeTon并给instance赋值,第二个线程则等待,当第一个线程退出 lock 语句块时, singleTon 这个静态变量已不为 null 了,所以当第二个线程进入 lock 时,
206 | 还是会被第二重 singleton == null 挡在外面,而无法执行 new Singleton(),
207 | ```
208 | - 既然在没有第一个判断的情况下,单例也可以实施,那为什么需要第一道判断呢?
209 | ```
210 | 这里就涉及一个性能问题了,因为对于单例模式的话,new SingleTon()只需要执行一次就 OK 了,
211 | 而如果没有第一重 singleTon == null 的话,每一次有线程进入 getInstance()时,均会执行锁定操作来实现线程同步,
212 | 这是非常耗费性能的,而如果我加上第一重 singleTon == null 的话,
213 | 那么就只有在第一次,也就是 singleTton ==null成立时的情况下执行一次锁定以实现线程同步,
214 | 而以后的话,便只要直接返回 Singleton 实例就 OK 了而根本无需再进入 lock 语句块了,这样就可以解决由线程同步带来的性能问题了。
215 | ```
216 | #### 2.4.2 双重锁机制失效分析
217 |
218 | - 双重校验锁失效原因分析:
219 | ```
220 | [1]我们先引入一个概念:指令重排序(具体会在多线程的锁机制中详解);
221 | 所谓指令重排序是指在不改变 原语义的情况下,通过调整指令的执行顺序让程序执行地更快;
222 | [2]双重锁的问题在于 由于指令重排序的存在,导致初始化Singleton()的过程和将对象地址赋给instance字段的顺序是不确定的。
223 | 在某个线程创建单例对象的过程中,在构造器被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值.此时,将分配的内存地址复制给instance字段[栈指针指向堆内存的过程],然而该对象还没有初始化。若与此同时,另外一个线程来调用getInstance()方法 ,那么导致的结果就是取到的不是正确的对象。
224 | [3] 在JDK 1.5版本之后引入了 volatile关键字.
225 | 它对于我们来讲 ,实现了内存可见性,确保实例每次都会从主存中读取,以及禁止指令重排序
226 | 也就是保证了instance变量被赋值的时候是确保已经被初始化过的;
227 | ```
228 |
--------------------------------------------------------------------------------