diff --git a/README.md b/README.md
index ed285aa4..7a73df4f 100644
--- a/README.md
+++ b/README.md
@@ -1,318 +1,15 @@
-项目对应 WebSIte:https://duhouan.github.io/Java/#/
+本仓库是对 Java 的一些基础知识、数据库知识、以及框架知识进行收集、整理(持续更新中)。
+仓库对应 WebSite:https://duhouan.github.io/Java/#
+如何流畅访问 Github
-# ✏️ 计算机基础
+
-## 1. 计算机网络
+
-- [第一节 概述](docs/Net/1_概述.md)
-- [第二节 物理层](docs/Net/2_物理层.md)
-- [第三节 数据链路层](docs/Net/3_数据链路层.md)
-- [第四节 网络层](docs/Net/4_网络层.md)
-- [第五节 运输层](docs/Net/5_运输层.md)
-- [第六节 应用层](docs/Net/6_应用层.md)
-
-## 2. 操作系统
-
-- [第一节 操作系统概述](docs/OS/1_操作系统概述.md)
-- [第二节 进程管理](docs/OS/2_进程管理.md)
-- [第三节 死锁](docs/OS/3_死锁.md)
-- [第四节 内存管理](docs/OS/4_内存管理.md)
-- [第五节 设备管理](docs/OS/4_设备管理.md)
-- [第六节 链接](docs/OS/6_链接.md)
-
-## 3. 数据结构和算法
-
-- [第一节 概述](docs/data_structure/1_概述.md)
-- [第二节 线性表](docs/data_structure/2_线性表.md)
-- [第三节 栈和队列](docs/data_structure/3_栈和队列.md)
-- [第四节 树](docs/data_structure/4_树.md)
-- [第五节 图](docs/data_structure/5_图.md)
-- [第六节 集合和映射](docs/data_structure/6_集合和映射.md)
-- [第七节 并查集](docs/data_structure/7_并查集.md)
-- [第八节 优先队列和堆](docs/data_structure/8_优先队列和堆.md)
-- [第九节 哈希表](docs/data_structure/9_哈希表.md)
-- [第十节 排序](docs/data_structure/10_排序.md)
-- [第十一节 线段树](docs/data_structure/11_线段树.md)
-- [第十二节 Trie树](docs/data_structure/12_Trie树.md)
-- [第十三节 AVL](docs/data_structure/13_AVL.md)
-- [第十四节 红黑树](docs/data_structure/14_红黑树.md)
-
-## 4. HTTP
-
-- [第一节 HTTP概述](http/1_HTTP概述.md)
-- [第二节 HTTP状态码](http/2_HTTP状态码.md)
-- [第三节 具体应用](http/3_具体应用.md)
-- [第四节 HTTPS](http/4_HTTPS.md)
-- [第五节 get和post比较](http/5_get和post比较.md)
-
-## 5. Linux
-
-- [第一节 Linux概论](docs/Linux/1_Linux概论.md)
-- [第二节 Linux文件系统](docs/Linux/2_Linux文件系统.md)
-- [第三节 Linux常用命令](docs/Linux/3_Linux常用命令.md)
-- [第四节 Liunx进程管理](docs/Linux/4_Liunx进程管理.md)
-- [第五节 Linux压缩与打包](docs/Linux/5_Linux压缩与打包.md)
-
-# ☕️ Java
-
-## 1. Java 基础
-
-- [第一节 数据类型](docs/JavaBasics/1_数据类型.md)
-- [第二节 String](docs/JavaBasics/2_String.md)
-- [第三节 运算](docs/JavaBasics/3_运算.md)
-- [第四节 Object通用方法](docs/JavaBasics/4_Object通用方法.md)
-- [第五节 关键字](docs/JavaBasics/5_关键字.md)
-- [第六节 反射](docs/JavaBasics/6_反射.md)
-- [第七节 异常](docs/JavaBasics/7_异常.md)
-- [第八节 泛型](docs/JavaBasics/8_泛型.md)
-- [第九节 注解](docs/JavaBasics/9_注解.md)
-- [第十节 Java常见对象](docs/JavaBasics/10_Java常见对象.md)
-- [第十一节 抽象类和接口](docs/JavaBasics/11_抽象类和接口.md)
-- [第十二节 其他](docs/JavaBasics/12_其他.md)
-
-## 2. Java 容器
-
-- [第一节 Java容器概览](docs/JavaContainer/1_Java容器概览.md)
-- [第二节 容器中的设计模式](docs/JavaContainer/2_容器中的设计模式.md)
-- [第三节 容器源码分析 - List](docs/JavaContainer/3_容器源码分析%20-%20List.md)
-- [第四节 容器源码分析 - Map](docs/JavaContainer/4_容器源码分析%20-%20Map.md)
-- [第五节 容器源码分析 - 并发容器](docs/JavaContainer/5_容器源码分析%20-%20并发容器.md)
-
-## 3. Java 虚拟机
-
-- [第一节 运行时数据区域](docs/JVM/1_JVM.md)
-- [第二节 HotSpot 虚拟机对象](docs/JVM/2_JVM.md)
-- [第三节 String 类和常量池](docs/JVM/3_JVM.md)
-- [第四节 8 种基本类型的包装类和常量池](docs/JVM/4_JVM.md)
-- [第五节 垃圾收集](docs/JVM/5_JVM.md)
-- [第六节 内存分配与回收策略](docs/JVM/6_JVM.md)
-- [第七节 类加载机制](docs/JVM/7_JVM.md)
-
-## 4. Java 并发
-
-- [第一节 基础知识](docs/Java_Concurrency/1_基础知识.md)
-- [第二节 并发理论](docs/Java_Concurrency/2_并发理论.md)
-- [第三节 并发关键字](docs/Java_Concurrency/3_并发关键字.md)
-- [第四节 Lock 体系](docs/Java_Concurrency/4_Lock%20体系.md)
-- [第五节 原子操作类](docs/Java_Concurrency/5_原子操作类.md)
-- [第六节 并发容器](docs/Java_Concurrency/6_并发容器.md)
-- [第七节 并发工具](docs/Java_Concurrency/7_并发工具.md)
-- [第八节 线程池](docs/Java_Concurrency/8_线程池.md)
-- [第九节 并发实践](docs/Java_Concurrency/9_并发实践.md)
-
-## 5. JavaIO
-
-- [第一节 概览](docs/JavaIO/1_概览.md)
-- [第二节 磁盘操作](docs/JavaIO/2_磁盘操作.md)
-- [第三节 字节操作](docs/JavaIO/3_字节操作.md)
-- [第四节 字符操作](docs/JavaIO/4_字符操作.md)
-- [第五节 对象操作](docs/JavaIO/5_对象操作.md)
-- [第六节 网络操作](docs/JavaIO/6_网络操作.md)
-- [第七节 NIO](docs/JavaIO/7_NIO.md)
-- [第八节 JavaIO方式](docs/JavaIO/8_JavaIO方式.md)
-
-## 6. 正则表达式
-
-- [第一节 概述](docs/Regex/1_概述.md)
-- [第二节 应用](docs/Regex/2_应用.md)
-
-# 👫 面向对象
-
-## 1. 设计模式
-
-- [第一节 概述](docs/OO/1_概述.md)
-- [第二节 创建型](docs/OO/2_创建型.md)
-- [第三节 行为型](docs/OO/3_行为型.md)
-- [第四节 结构型](docs/OO/4_结构型.md)
-
-## 2. 面向对象思想
-
-- [第一节 面向对象三大特性](docs/OO/5_面向对象三大特性.md)
-- [第二节 关系类图](docs/OO/6_关系类图.md)
-- [第三节 面向对象设计原则](docs/OO/7_面向对象设计原则.md)
-
-# 📝 编程题
-
-## 1. 剑指 Offer 编程题
-
-> **数据结构相关**
-
-- [第一节 数组和矩阵](docs/AimForOffer/数据结构相关/1_数组和矩阵.md)
-- [第二节 字符串](docs/AimForOffer/数据结构相关/2_字符串.md)
-- [第三节 链表](docs/AimForOffer/数据结构相关/3_链表.md)
-- [第四节 树](docs/AimForOffer/数据结构相关/4_树.md)
-- [第五节 栈](docs/AimForOffer/数据结构相关/5_栈.md)
-- [第六节 队列](docs/AimForOffer/数据结构相关/6_队列.md)
-- [第七节 堆](docs/AimForOffer/数据结构相关/7_堆.md)
-- [第八节 哈希.](docs/AimForOffer/数据结构相关/8_哈希.md)
-
-> **算法相关**
-
-- [第一节 查找](docs/AimForOffer/算法思想相关/1_查找.md)
-- [第二节 排序](docs/AimForOffer/算法思想相关/2_排序.md)
-- [第三节 动态规划](docs/AimForOffer/算法思想相关/3_动态规划.md)
-- [第四节 回溯](docs/AimForOffer/算法思想相关/4_回溯.md)
-- [第五节 深度优先](docs/AimForOffer/算法思想相关/5_深度优先.md)
-- [第六节 贪心](docs/AimForOffer/算法思想相关/6_贪心.md)
-- [第七节 数学运算](docs/AimForOffer/算法思想相关/7_数学运算.md)
-- [第八节 其他](docs/AimForOffer/算法思想相关/8_其他.md)
-
-## 2. LeetCode 编程题
-
-> **数据结构相关**
-
-- [第一节 数组问题](docs/LeetCode/数据结构相关/1_数组问题.md)
-- [第二节 链表问题](docs/LeetCode/数据结构相关/2_链表问题.md)
-- [第三节 栈和队列](docs/LeetCode/数据结构相关/3_栈和队列.md)
-- [第四节 二叉树](docs/LeetCode/数据结构相关/4_二叉树.md)
-- [第五节 字符串](docs/LeetCode/数据结构相关/5_字符串.md)
-- [第六节 哈希](docs/LeetCode/数据结构相关/6_哈希.md)
-- [第七节 图](docs/LeetCode/数据结构相关/7_图.md)
-- [第八节 数据结构设计](docs/LeetCode/数据结构相关/8_数据结构设计.md)
-
-> **算法思想相关**
-
-- [第一节 排序](docs/LeetCode/算法思想相关/1_排序.md)
-- [第二节 分治思想](docs/LeetCode/算法思想相关/2_分治思想.md)
-- [第三节 贪心思想](docs/LeetCode/算法思想相关/3_贪心思想.md)
-- [第四节 LRU](docs/LeetCode/算法思想相关/4_LRU.md)
-- [第五节 DFS](docs/LeetCode/算法思想相关/5_DFS.md)
-- [第六节 回溯法](docs/LeetCode/算法思想相关/6_回溯法.md)
-- [第七节 动态规划](docs/LeetCode/算法思想相关/7_动态规划.md)
-- [第八节 数学问题](docs/LeetCode/算法思想相关/8_数学问题.md)
-
-# 💾 数据库
-
-## 1. DataBase
-
-- [第一节 数据库系统原理](docs/DataBase/1_数据库系统原理.md)
-- [第二节 关系数据库设计理论](docs/DataBase/2_关系数据库设计理论.md)
-- [第三节 设计关系型数据库](docs/DataBase/3_设计关系型数据库.md)
-- [第四节 SQL](docs/DataBase/4_SQL.md)
-- [第五节 LeetCode_Database题解](docs/DataBase/5_LeetCode_Database题解.md)
-
-## 2. MySQL
-
-- [第一节 锁机制](docs/MySQL/1_锁机制.md)
-- [第二节 事务隔离级别实现](docs/MySQL/2_事务隔离级别实现.md)
-- [第三节 索引](docs/MySQL/3_索引.md)
-- [第四节 MySQL架构](docs/MySQL/4_MySQL架构.md)
-- [第五节 MySQL优化](docs/MySQL/5_MySQL优化.md)
-- [第六节 补充知识](docs/MySQL/6_补充知识.md)
-
-## *3. Redis
-
-- [第一节 Redis初探](docs/Redis/1_Redis初探.md)
-- [第二节 Redis持久化](docs/Redis/2_Redis持久化.md)
-- [第三节 Redis复制](docs/Redis/3_Redis复制.md)
-- [第四节 处理系统故障](docs/Redis/4_处理系统故障.md)
-- [第五节 Redis事务](docs/Redis/5_Redis事务.md)
-- [第六节 Redis性能方面注意事项](docs/Redis/6_Redis性能方面注意事项.md)
-- [第七节 降低内存占用](docs/Redis/7_降低内存占用.md)
-- [第八节 简单点赞系统](docs/Redis/8_简单点赞系统)
-
-## 4. 海量数据处理
-
-- [第一节 概述](docs/MassDataProcessing/1_概述.md)
-- [第二节 哈希分治](docs/MassDataProcessing/2_哈希分治.md)
-- [第三节 位图](docs/MassDataProcessing/3_位图.md)
-- [第四节 布隆过滤器](docs/MassDataProcessing/4_布隆过滤器.md)
-- [第五节 Trie树](docs/MassDataProcessing/5_Trie树.md)
-- [第六节 数据库](docs/MassDataProcessing/6_数据库.md)
-- [第七节 倒排索引](docs/MassDataProcessing/7_倒排索引.md)
-
-# 🔨 消息中间件
-
-## 1. Kafka
-
-- [第一节 消息队列](docs/Kafka/1_消息队列.md)
-- [第二节 Kafka的架构](docs/Kafka/2_Kafka的架构.md)
-- [第三节 Kafka的高可用原理](docs/Kafka/3_Kafka的高可用原理.md)
-- [第四节 Kafka中一些常见问题](docs/Kafka/4_Kafka中一些常见问题.md)
-- [第五节 Kafka特点](docs/Kafka/5_Kafka特点.md)
-
-## 2. RabbitMQ
-
-- [第一节 主流消息中间件](docs/RabbitMQ/1_主流消息中间件.md)
-- [第二节 RabbitMQ概述](docs/RabbitMQ/2_RabbitMQ概述.md)
-- [第三节 RabbitMQ入门](docs/RabbitMQ/3_RabbitMQ入门.md)
-- [第四节 RabbitMQ高级特性](docs/RabbitMQ/4_RabbitMQ高级特性.md)
-- [第五节 RabbitMQ整合SpringAMQP](docs/RabbitMQ/5_RabbitMQ整合SpringAMQP.md)
-- [第六节 RabbitMQ整合SpringBoot](docs/RabbitMQ/6_RabbitMQ整合SpringBoot.md)
-- [RabbitMQ 官网](https://www.rabbitmq.com/)
-
-# 📖 系统设计
-
-## 1. 常用框架
-
-- [第一节 SpringMVC](docs/SSM/1_SpringMVC.md)
-- [第二节 SpringIOC](docs/SSM/2_SpringIOC.md)
-- [第三节 SpringAOP](docs/SSM/3_SpringAOP.md)
-- [第四节 Spring事务管理](docs/SSM/4_Spring事务管理.md)
-- [第五节 Spring中Bean的作用域](docs/SSM/5_Spring中Bean的作用域.md)
-- [第六节 Spring中Bean的生命周期](docs/SSM/6_Spring中Bean的生命周期.md)
-- [第七节 Spring中常见注解](docs/SSM/7_Spring中常见注解.md)
-- [第八节 Spring中涉及到的设计模式](docs/SSM/8_Spring中涉及到的设计模式.md)
-- [第九节 MyBaits](docs/SSM/9_MyBaits.md)
-
-## 2. Web 安全
-
-- [第一节 常见安全问题](docs/Safety/1_常见安全问题.md)
-- [第二节 跨站脚本攻击](docs/Safety/2_跨站脚本攻击.md)
-- [第三节 跨站请求伪造](docs/Safety/3_跨站请求伪造.md)
-- [第四节 Cookies问题](docs/Safety/4_Cookies问题.md)
-- [第五节 点击劫持问题](docs/Safety/5_点击劫持问题.md)
-- [第六节 传输安全](docs/Safety/6_传输安全.md)
-- [第七节 密码安全](docs/Safety/7_密码安全.md)
-- [第八节 接入层注入问题](docs/Safety/8_接入层注入问题.md)
-- [第九节 接入层上传问题](docs/Safety/9_接入层上传问题.md)
-- [第十节 信息泄露](docs/Safety/10_信息泄露.md)
-- [第十一节 DoS攻击](docs/Safety/11_DoS攻击.md)
-- [第十二节 重放攻击](docs/Safety/12_重放攻击.md)
-
-## 3. 分布式
-
-- [第一节 分布式系统设计理念](docs/distribution/1_分布式系统设计理念.md)
-- [第二节 CAP理论](docs/distribution/2_CAP理论.md)
-- [第三节 BASE理论](docs/distribution/3_BASE理论.md)
-- [第四节 分布式锁](docs/distribution/4_分布式锁.md)
-- [第五节 分布式事务](docs/distribution/5_分布式事务.md)
-- [第六节 分布式缓存的一致性哈希算法](docs/distribution/6_分布式缓存的一致性哈希算法.md)
-
-## *4. 微服务
-
-## 5. 网站架构
-
-- [网站架构](docs/web_architecture/1_网站架构.md)
-- [设计秒杀系统](docs/web_architecture/2_设计秒杀系统.md)
-
-# 💻 工具
-
-## 1. Git
-
-- [git - 简明指南](http://rogerdudler.github.io/git-guide/index.zh.html)
-- [git - 图解](http://marklodato.github.io/visual-git-guide/index-zh-cn.html)
-
-## 2. Maven
-
-- [Maven](docs/Maven/1_Maven.md)
-- [Maven 下载](http://maven.apache.org/download.cgi)
-- [Maven 官网](https://maven.apache.org/)
-
-## *2. Nginx
-
-## *3. Docker
-
-
-
-# 🔧 进阶指南
-
-- [后端面试进阶指南](https://xiaozhuanlan.com/CyC2018)
-- [Java 面试进阶指南](https://xiaozhuanlan.com/javainterview)
-- [编码规范指南](https://github.com/alibaba/p3c)
-
-# 🙊 参考资料
-
-- [参考仓库](docs/reference/参考仓库.md)
-- [参考书籍](docs/reference/参考书籍.md)
-- [慕课网](docs/reference/慕课网.md)
\ No newline at end of file
+| ☕️ Java | 👫 面向对象 | 📝 编程题 | 💾 数据库 | 🎓 系统设计 | ☎️ 常用框架 | 📖 工具 | 📚 参考资料 |
+| :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: | :----------------------------------------------------------: |
+| Java | 面向对象 | 编程题 | 数据库 | 系统设计 | 常用框架 | 工具 | 参考资料 |
diff --git "a/docs/1_345円237円272円347円241円200円.md" "b/docs/1_345円237円272円347円241円200円.md"
new file mode 100644
index 00000000..406b2d57
--- /dev/null
+++ "b/docs/1_345円237円272円347円241円200円.md"
@@ -0,0 +1,99 @@
+# 系统设计基础
+
+## 安全性
+
+要求系统在应对各种攻击手段时能够有可靠的应对措施。
+
+## 性能
+
+### 性能指标
+
+#### 响应时间
+
+指某个请求从发出到接收到响应消耗的时间。
+
+在对响应时间进行测试时,通常采用重复请求的方式,然后计算平均响应时间。
+
+#### 吞吐量
+
+指系统在单位时间内可以处理的请求数量,通常使用每秒的请求数来衡量。
+
+#### 并发量
+
+指系统能同时处理的并发用户请求数量。
+
+在没有并发存在的系统中,请求被顺序执行,此时响应时间为吞吐量的倒数。例如系统支持的吞吐量为 100 req/s,那么平均响应时间应该为 0.01s。
+
+目前的大型系统都支持多线程来处理并发请求,多线程能够提高吞吐量以及缩短响应时间,主要有两个原因:
+
+- 多 CPU
+- IO 等待时间
+
+使用 IO 多路复用等方式,系统在等待一个 IO 操作完成的这段时间内不需要被阻塞,可以去处理其它请求。通过将这个等待时间利用起来,使得 CPU 利用率大大提高。
+
+并发用户数不是越高越好,因为如果并发用户数太高,系统来不及处理这么多的请求,会使得过多的请求需要等待,那么响应时间就会大大提高。
+
+### 性能优化
+
+#### 集群
+
+将多台服务器组成集群,使用负载均衡将请求转发到集群中,避免单一服务器的负载压力过大导致性能降低。
+
+#### 缓存
+
+缓存能够提高性能的原因如下:
+
+- 缓存数据通常位于内存等介质中,这种介质对于读操作特别快;
+- 缓存数据可以位于靠近用户的地理位置上;
+- 可以将计算结果进行缓存,从而避免重复计算。
+
+#### 异步
+
+某些流程可以将操作转换为消息,将消息发送到**消息队列**之后立即返回,之后这个操作会被异步处理。
+
+
+
+## 伸缩性
+
+指不断向集群中添加服务器来缓解不断上升的用户并发访问压力和不断增长的数据存储需求。
+
+### 伸缩性与性能
+
+如果系统存在性能问题,那么单个用户的请求总是很慢的;
+
+如果系统存在伸缩性问题,那么单个用户的请求可能会很快,但是在并发数很高的情况下系统会很慢。
+
+### 实现伸缩性
+
+应用服务器只要不具有状态,那么就可以很容易地通过负载均衡器向集群中添加新的服务器。
+
+关系型数据库的伸缩性通过 Sharding 来实现,将数据按一定的规则分布到不同的节点上,从而解决单台存储服务器的存储空间限制。
+
+对于非关系型数据库,它们天生就是为海量数据而诞生,对伸缩性的支持特别好。
+
+## 扩展性
+
+指的是添加新功能时对现有系统的其它应用无影响,这就要求不同应用具备低耦合的特点。
+
+实现可扩展主要有两种方式:
+
+- 使用消息队列进行解耦,应用之间通过消息传递进行通信;
+- 使用分布式服务将业务和可复用的服务分离开来,业务使用分布式服务框架调用可复用的服务。新增的产品可以通过调用可复用的服务来实现业务逻辑,对其它产品没有影响。
+
+## 可用性
+
+### 冗余
+
+保证高可用的主要手段是使用冗余,当某个服务器故障时就请求其它服务器。
+
+应用服务器的冗余比较容易实现,只要保证应用服务器不具有状态,那么某个应用服务器故障时,负载均衡器将该应用服务器原先的用户请求转发到另一个应用服务器上,不会对用户有任何影响。
+
+存储服务器的冗余需要使用主从复制来实现,当主服务器故障时,需要提升从服务器为主服务器,这个过程称为切换。
+
+### 监控
+
+对 CPU、内存、磁盘、网络等系统负载信息进行监控,当某个信息达到一定阈值时通知运维人员,从而在系统发生故障之前及时发现问题。
+
+### 服务降级
+
+服务降级是系统为了应对大量的请求,主动关闭部分功能,从而保证核心功能可用。
\ No newline at end of file
diff --git "a/docs/Kafka/1_346円266円210円346円201円257円351円230円237円345円210円227円.md" "b/docs/1_346円266円210円346円201円257円351円230円237円345円210円227円.md"
similarity index 100%
rename from "docs/Kafka/1_346円266円210円346円201円257円351円230円237円345円210円227円.md"
rename to "docs/1_346円266円210円346円201円257円351円230円237円345円210円227円.md"
diff --git "a/docs/AimForOffer/346円225円260円346円215円256円347円273円223円346円236円204円347円233円270円345円205円263円/1_346円225円260円347円273円204円345円222円214円347円237円251円351円230円265円.md" "b/docs/AimForOffer/346円225円260円346円215円256円347円273円223円346円236円204円347円233円270円345円205円263円/1_346円225円260円347円273円204円345円222円214円347円237円251円351円230円265円.md"
index 890a88e1..6d56d3ec 100644
--- "a/docs/AimForOffer/346円225円260円346円215円256円347円273円223円346円236円204円347円233円270円345円205円263円/1_346円225円260円347円273円204円345円222円214円347円237円251円351円230円265円.md"
+++ "b/docs/AimForOffer/346円225円260円346円215円256円347円273円223円346円236円204円347円233円270円345円205円263円/1_346円225円260円347円273円204円345円222円214円347円237円251円351円230円265円.md"
@@ -1,4 +1,4 @@
-# 数组
+# 数组和矩阵
## 1、二维数组中的查找
diff --git "a/docs/AimForOffer/346円225円260円346円215円256円347円273円223円346円236円204円347円233円270円345円205円263円/2_345円255円227円347円254円246円344円270円262円.md" "b/docs/AimForOffer/346円225円260円346円215円256円347円273円223円346円236円204円347円233円270円345円205円263円/2_345円255円227円347円254円246円344円270円262円.md"
index 97aef1ca..619b66af 100644
--- "a/docs/AimForOffer/346円225円260円346円215円256円347円273円223円346円236円204円347円233円270円345円205円263円/2_345円255円227円347円254円246円344円270円262円.md"
+++ "b/docs/AimForOffer/346円225円260円346円215円256円347円273円223円346円236円204円347円233円270円345円205円263円/2_345円255円227円347円254円246円344円270円262円.md"
@@ -384,4 +384,111 @@ private void reverse(char[] chs,int start,int end){
}
```
+## *10、实现 strStr()
+
+[实现 strStr()](https://leetcode-cn.com/problems/implement-strstr/)
+
+思路一:朴素模式匹配算法
+
+最朴素的方法就是依次从待匹配串的每一个位置开始,逐一与模版串匹配,
+因为最多检查 (n - m)个位置,所以方法的复杂度为 O(m*(n-1))。
+
+
+
+```java
+//haystack 是待匹配串
+//needle 是模板串
+public int strStr(String haystack, String needle) {
+ if(needle==null || needle.length()==0){
+ return 0;
+ }
+ int i=0,j=0;
+ //k存储的是模板串在待匹配串的位置
+ int k=i;
+ while(i printListFromTailToHead(ListNode listNode) {
-## *2、链表中环的入口结点(9)
+## *2、链表中环的入口结点
[链表中环的入口结点](https://www.nowcoder.com/practice/253d2c59ec3e4bc68da16833f79a38e4?tpId=13&tqId=11208&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -128,7 +128,7 @@ public ListNode removeElements(ListNode head, int val) {
-## *4、删除链表中重复的结点(10)
+## *4、删除链表中重复的结点
[删除链表中重复的结点](https://www.nowcoder.com/practice/fc533c45b73a41b0b44ccba763f866ef?tpId=13&tqId=11209&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -171,7 +171,7 @@ public ListNode deleteDuplication(ListNode pHead) {
-## 5、链表中倒数第 k 个结点(29)
+## 5、链表中倒数第 k 个结点
[链表中倒数第 k 个结点](https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -214,7 +214,7 @@ public ListNode FindKthToTail(ListNode head, int k) {
-## 7、反转链表(30)
+## 7、反转链表
[反转链表](https://www.nowcoder.com/practice/75e878df47f24fdc9dc3e400ec6058ca?tpId=13&tqId=11168&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -262,7 +262,7 @@ public ListNode ReverseList(ListNode head) {
-## 8、合并两个排序的链表(31)
+## 8、合并两个排序的链表
[合并两个排序的链表](https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -343,7 +343,7 @@ public ListNode Merge(ListNode list1, ListNode list2) {
-## 9、二叉搜索树与双向链表(41)
+## 9、二叉搜索树与双向链表
[二叉搜索树与双向链表](https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId=13&tqId=11179&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -386,7 +386,7 @@ private void inOrder(TreeNode node){
-## 10、两个链表的第一个公共结点(51)
+## 10、两个链表的第一个公共结点
[两个链表的第一个公共结点](https://www.nowcoder.com/practice/6ab1d9a29e88450685099d45c9e31e46?tpId=13&tqId=11189&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
diff --git "a/docs/AimForOffer/346円225円260円346円215円256円347円273円223円346円236円204円347円233円270円345円205円263円/4_346円240円221円.md" "b/docs/AimForOffer/346円225円260円346円215円256円347円273円223円346円236円204円347円233円270円345円205円263円/4_346円240円221円.md"
index 148b4fb6..a49dc51d 100644
--- "a/docs/AimForOffer/346円225円260円346円215円256円347円273円223円346円236円204円347円233円270円345円205円263円/4_346円240円221円.md"
+++ "b/docs/AimForOffer/346円225円260円346円215円256円347円273円223円346円236円204円347円233円270円345円205円263円/4_346円240円221円.md"
@@ -424,7 +424,7 @@ private String buildPath(List values){
-## 13、二叉树中和为某一值的路径(39)
+## 13、二叉树中和为某一值的路径
[二叉树中和为某一值的路径](https://www.nowcoder.com/practice/b736e784e3e34731af99065031301bca?tpId=13&tqId=11177&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
diff --git "a/docs/AimForOffer/346円225円260円346円215円256円347円273円223円346円236円204円347円233円270円345円205円263円/5_346円240円210円.md" "b/docs/AimForOffer/346円225円260円346円215円256円347円273円223円346円236円204円347円233円270円345円205円263円/5_346円240円210円.md"
index cbcadc12..1bfbef76 100644
--- "a/docs/AimForOffer/346円225円260円346円215円256円347円273円223円346円236円204円347円233円270円345円205円263円/5_346円240円210円.md"
+++ "b/docs/AimForOffer/346円225円260円346円215円256円347円273円223円346円236円204円347円233円270円345円205円263円/5_346円240円210円.md"
@@ -1,6 +1,6 @@
# 栈
-## 1、用两个栈实现队列(19)
+## 1、用两个栈实现队列
[用两个栈实现队列](https://www.nowcoder.com/practice/54275ddae22f475981afa2244dd448c6?tpId=13&tqId=11158&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -24,7 +24,7 @@ public int pop() {
-## *2、包含 min 函数的栈(35)
+## *2、包含 min 函数的栈
[包含 min 函数的栈](https://www.nowcoder.com/practice/4c776177d2c04c2494f2555c9fcc1e49?tpId=13&tqId=11173&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -58,7 +58,7 @@ public int min() {
-## *3、栈的压入、弹出序列(36)
+## *3、栈的压入、弹出序列
[栈的压入、弹出序列](https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&tqId=11174&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
diff --git "a/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/1_346円237円245円346円211円276円.md" "b/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/1_346円237円245円346円211円276円.md"
index f5657b81..ed9329c7 100644
--- "a/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/1_346円237円245円346円211円276円.md"
+++ "b/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/1_346円237円245円346円211円276円.md"
@@ -1,6 +1,8 @@
# 查找
-## 1、二维数组中的查找(1)
+
+
+## 1、二维数组中的查找
[二维数组中的查找](https://www.nowcoder.com/practice/abc3fe2ce8e146608e868a70efebf62e?tpId=13&tqId=11154&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -30,7 +32,7 @@ public boolean Find(int target, int [][] array) {
-## 2、旋转数组的最小数字(21)
+## 2、旋转数组的最小数字
[旋转数组的最小数字](https://www.nowcoder.com/practice/9f3231a991af4f55b95579b44b7a01ba?tpId=13&tqId=11159&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -86,7 +88,7 @@ public int minNumberInRotateArray(int [] array) {
-## 3、数字在排序数组中出现的次数(52)
+## 3、数字在排序数组中出现的次数
[数字在排序数组中出现的次数](https://www.nowcoder.com/practice/70610bf967994b22bb1c26f9ae901fa2?tpId=13&tqId=11190&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -192,3 +194,4 @@ private int binarySearchLast(int[] array,int k){
return res;
}
```
+
diff --git "a/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/3_345円212円250円346円200円201円350円247円204円345円210円222円.md" "b/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/3_345円212円250円346円200円201円350円247円204円345円210円222円.md"
index eb55b6d6..d60c1f20 100644
--- "a/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/3_345円212円250円346円200円201円350円247円204円345円210円222円.md"
+++ "b/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/3_345円212円250円346円200円201円350円247円204円345円210円222円.md"
@@ -1,6 +1,6 @@
# 动态规划
-## 1、正则表达式匹配(5)
+## 1、正则表达式匹配
[正则表达式匹配](https://www.nowcoder.com/practice/45327ae22b7b413ea21df13ee7d6429c?tpId=13&tqId=11205&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -62,7 +62,7 @@ private boolean match(char[] str,int strIndex,char[] pattern,int patternIndex){
-## 2、斐波那契数列(22)
+## 2、斐波那契数列
[斐波那契数列](https://www.nowcoder.com/practice/c6c7742f5ba7442aada113136ddea0c3?tpId=13&tqId=11160&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -97,7 +97,7 @@ public int Fibonacci(int n) {
-## 3、跳台阶(23)
+## 3、跳台阶
[跳台阶](https://www.nowcoder.com/practice/8c82a5b80378478f9484d87d1c5f12a4?tpId=13&tqId=11161&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -119,7 +119,7 @@ public int JumpFloor(int target) {
-## 4、变态跳台阶(24)
+## 4、变态跳台阶
[变态跳台阶](https://www.nowcoder.com/practice/22243d016f6b47f2a6928b4313c85387?tpId=13&tqId=11162&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -164,7 +164,7 @@ public int JumpFloorII(int target) {
-## 5、矩形覆盖(25)
+## 5、矩形覆盖
[矩形覆盖](https://www.nowcoder.com/practice/72a5a919508a4251859fb2cfb987a0e6?tpId=13&tqId=11163&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -191,7 +191,7 @@ public int RectCover(int target) {
-## 6、连续子数组的最大和(45)
+## 6、连续子数组的最大和
[连续子数组的最大和](https://www.nowcoder.com/practice/459bd355da1549fa8a49e350bf3df484?tpId=13&tqId=11183&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -256,7 +256,7 @@ public int FindGreatestSumOfSubArray(int[] array) {
-## *7、丑数(46)
+## *7、丑数
[丑数](https://www.nowcoder.com/practice/6aa9e04fc3794f68acf8778237ba065b?tpId=13&tqId=11186&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -448,14 +448,14 @@ public int numDecodings(String s) {
int n =s.length();
int[] dp = new int[n+1];
dp[0]=1; // 没有字符则只有一种解码方式
- dp[1]=s.charAt(0)=='0'?0:1; //字符 '0',不能进行解码
+ dp[1]=s.charAt(0)=='0'?0:1; // 字符 '0',不能进行解码
for(int i=2;i<=n;i++){ //解码一位字符 int one = Integer.parseInt(s.substring(i-1,i)); if(one!=0){ dp[i] += dp[i-1]; } - if(s.charAt(i-2)=='0'){ ////字符 '0',不能进行解码 + if(s.charAt(i-2)=='0'){ //字符 '0',不能进行解码 continue; } int two=Integer.parseInt(s.substring(i-2,i)); @@ -522,48 +522,51 @@ public int LCS(String s,String t){ ```java private char[][] x; +private StringBuilder res; //x[i][j]=='c' 表示斜方向 //x[i][j]=='u' 表示向上 //x[i][j]=='l' 表示向左 -private StringBuilder res; -public int LCS(String s,String t){ +public String LCS(String s,String t){ + if(s==null || s==null){ + return ""; + } int m=s.length(); int n=t.length(); - if(m==0 || n==0){ - return 0; - } - - //lcs[i][j] 表示 s[0...i] 和 t[0...j] 之间的最长公共子序列长度 - int[][] lcs=new int[m+1][n+1]; x=new char[m+1][n+1]; res=new StringBuilder(); + // lcs[i][j] 表示 s[0,i-1]、t[0,j-1] 最长公共子序列长度 + // s[i-1]==t[j-1] 则 lcs[i][j]=lcs[i-1][j-1]+1; + // s[i-1]!=t[j-1] 则 lcs[i][j]=max(lcs[i-1][j],lcs[i][j-1]) + int[][] lcs=new int[m+1][n+1]; for(int i=0;i<=m;i++){ for(int j=0;j<=n;j++){ lcs[i][j]=0; } } - for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ if(s.charAt(i-1)==t.charAt(j-1)){ - lcs[i][j]=1+lcs[i-1][j-1]; + lcs[i][j]=lcs[i-1][j-1]+1; x[i][j]='c'; - }else if(lcs[i-1][j]>=lcs[i][j-1]){
- lcs[i][j]=lcs[i-1][j];
- x[i][j]='u';
- }else if(lcs[i-1][j]lcs[i][j-1]){
+ lcs[i][j]=lcs[i-1][j];
+ x[i][j]='u';
+ }
}
}
}
- return lcs[m][n];
+ print(s,m,n);
+ return res.toString();
}
-public void print(String s,int m,int n){
- if(m==0 || n==0){
+private void print(String s,int m,int n){
+ if(s==null || s.length()==0){
return;
}
if(x[m][n]=='c'){
@@ -728,6 +731,26 @@ private int binarySearch(int[] tails,int l,int r,int key){
[完全平方数](https://leetcode-cn.com/problems/perfect-squares/)
```java
-
+// 思路:dp[i] 表示整数 i 的完全平方数的最少数量
+// 显然这些平方数开方后数值的大小范围在 [1,sqrc(n)] 之间
+// 假设 j 在 [1,sqrc(n)] 中取值,我们知道 i 和(i-j^2)之间就差了一个j^2完全平方数
+// 则有 dp[i]=1 + min{dp[i-j^2]}
+public int numSquares(int n) {
+ if(n<=2){ + return n; + } + int N=(int)Math.sqrt(n*1.0); + int[] dp=new int[n+1]; + dp[0]=0; // 0不是正整数,显然不能使用完全平方数去表示 + for(int i=1;i<=n;i++){ + int min=dp[i-1]; + for(int j=1;i-j*j>=0 && j<=n;j++){ + if(dp[i-j*j]> FindPath(TreeNode root, int target) {
- ArrayList> res = new ArrayList();
- ArrayList values = new ArrayList();
- backtrack(root,target,values,res);
- return res;
-}
-
-// values : 记录从根节点到叶子节点的所有路径
-// paths : 存储所有可能的结果
-private void backtrack(TreeNode root, int target,
- ArrayList values,
- ArrayList> paths) {
- if (root == null) {
- return;
- }
- values.add(root.val);
- if(root.left==null && root.right==null && root.val==target){
- paths.add(new ArrayList(values));
- }else{
- backtrack(root.left,target-root.val,values,paths);
- backtrack(root.right,target-root.val,values,paths);
- }
- values.remove(values.size()-1);
-}
-```
-
-
-
-## 2、矩阵中的路径(65)
-
-[矩阵中的路径](https://www.nowcoder.com/practice/c61c6999eecb4b8f88a98f66b273a3cc?tpId=13&tqId=11218&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
-
-```java
-//思路:典型的回溯法思想
-private int m,n; //矩阵的长度、宽度
-private boolean[][] visted; //标记是否访问
-
-private int[][] d={
- {0,1}, //向右
- {-1,0},//向上
- {0,-1},//向左
- {1,0} //向下
-};
-
-private boolean inArea(int x,int y){
- return (x>=0 && x=0 && y=0 && x=0 && y> res;
-
-private boolean[] visited;
-
-public List> permute(int[] nums) {
- res = new ArrayList();
- if(nums==null || nums.length==0){
- return res;
- }
- visited = new boolean[nums.length];
- List p =new ArrayList();
- generatePermutation(nums,0,p);
- return res;
-}
-
-//p中保存一个有 index 的元素的排列
-//向这个排列的末尾添加第 (index+1) 个元素,组成有(index+1) 个元素排列
-private void generatePermutation(int[] nums,int index,List p){
- if(index==nums.length){
- res.add(new ArrayList(p));
- return;
- }
- for(int i=0;i> res;
-
-private boolean[] visited;
-
-public List> permuteUnique(int[] nums) {
- res = new ArrayList();
- if(nums==null || nums.length==0){
- return res;
- }
- visited = new boolean[nums.length];
- Arrays.sort(nums);
- List p = new ArrayList();
- findUniquePermutation(nums,0,p);
- return res;
-}
-
-//p 保存的是有 index 元素的排列
-//向这个排列的末尾添加第 (index+1) 个元素,组成有(index+1) 个元素排列
-private void findUniquePermutation(int[] nums,int index,List p){
- if(index==nums.length){
- res.add(new ArrayList(p));
- return;
- }
- for(int i=0;i0 && nums[i-1]==nums[i] &&
- !visited[i-1]){ //注意:相邻元素相同,并且都未被访问
- continue;
- }
- visited[i]=true;
- p.add(nums[i]);
- findUniquePermutation(nums,index+1,p);
- p.remove(p.size()-1);
- visited[i]=false;
- }
- }
- return;
-}
-```
-
-
-
-## *7、字符串的排列
-
-[字符串的排列](https://www.nowcoder.com/practice/fe6b651b66ae47d7acce78ffdd9a96c7?tpId=13&tqId=11180&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
-
-```java
-private ArrayList res;
-private boolean[] visited;
-
-public ArrayList Permutation(String str) {
- res = new ArrayList();
- if(str==null || str.length()==0){
- return res;
- }
- char[] chs = str.toCharArray();
- Arrays.sort(chs); //方便后面的去重处理
- visited = new boolean[str.length()];
- permute(chs,0,new StringBuilder());
- return res;
-}
-
-//产生排列
-//p中保存一个存在index个元素的排列
-//向这个排列的末尾添加第(index+1)个元素,获得包含(index+1)个元素的排列
-private void permute(char[] chs,int index,StringBuilder p){
- if(index==chs.length){
- res.add(p.toString());
- return;
- }
- for(int i=0;i0 && chs[i-1]==chs[i] && !visited[i-1]){
- continue;
- }
- if(!visited[i]){
- p.append(chs[i]);
- visited[i] = true;
- permute(chs,index+1,p);
- p.deleteCharAt(p.length()-1);
- visited[i] = false;
- }
- }
-}
-```
-
-
-
-## 8、字母大小写全排列
-
-[字母大小写全排列](https://leetcode-cn.com/problems/letter-case-permutation/)
-
-```java
-private List res;
-
-//处理 index 位置的数据
-//如果 index 位置是字母的话,则替换
-private void replaceLetter(int index,StringBuilder p){
- if(index==p.length()){
- res.add(p.toString());
- return;
- }
- char ch=p.charAt(index);
- if(Character.isLetter(ch)){
- p.setCharAt(index,Character.toUpperCase(ch));
- replaceLetter(index+1,p);
- p.setCharAt(index,Character.toLowerCase(ch));
- replaceLetter(index+1,p);
- }else{
- replaceLetter(index+1,p);
- }
- return;
-}
-
-public List letterCasePermutation(String S) {
- res=new ArrayList();
- if(S==null || S.length()==0){
- return res;
- }
- StringBuilder p=new StringBuilder(S);
- replaceLetter(0,p);
- return res;
-}
-```
-
-
-
-## 9、电话号码的字母组合
-
-[电话号码的字母组合](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/)
-
-```java
-private final String[] letterMap={
- " ",//0
- "",//1
- "abc",//2
- "def",//3
- "ghi",//4
- "jkl",//5
- "mno",//6
- "pqrs",//7
- "tuv",//8
- "wxyz"//9
-};
-
-private List res;
-//s 中保留了从 digits[0...index-1] 翻译得到的字符串
-//寻找和 digits[index] 匹配的字母,获取 digits[0...index] 翻译得到的解
-private void findCombination(String digits,int index,String s){
- if(index==digits.length()){
- res.add(s);
- return;
- }
-
- char c = digits.charAt(index);
- String letters = letterMap[c-'0']; // 翻译字符串
- for(int i=0;i letterCombinations(String digits) {
- res = new ArrayList();
- if(digits==null || digits.length()==0){
- return res;
- }
- findCombination(digits,0,"");
- return res;
-}
-```
-
-
-
-## 10、组合
-
-[组合](https://leetcode-cn.com/problems/combinations/)
-
-```java
-//思路一:暴力解法
-
-private List> res;
-
-//c存储已经找到的组合
-//从start开始搜索新的元素
-private void findCombination(int n,int k,int start,List c){
- if(k==c.size()){
- res.add(new ArrayList(c));
- return;
- }
- for(int i=start;i<=n;i++){ - c.add(i); - findCombination(n,k,i+1,c); - c.remove(c.size()-1); - } - return; -} - -public List> combine(int n, int k) {
- res=new ArrayList();
- if(n<=0 || k<=0 || n c=new ArrayList();
- findCombination(n,k,1,c);
- return res;
-}
-```
-
-
-
-```java
-//思路二:优化,进行剪枝
-private List> res;
-
-//c存储已经找到的组合
-//从start开始搜索新的元素
-private void findCombination(int n,int k,int start,List c){
- if(k==c.size()){
- res.add(new ArrayList(c));
- return;
- }
-
- //TODO:优化--剪枝
- //c存储的是已经找到的组合。
- //此时还剩下k-c.size()个空位,
- //则 [i...n]之间的元素最少要有 k-c.size() 个,即 n-i+1>= k-c.size()
- //所以有 i <= n-(k-c.size())+1 - - for(int i=start;i<=n-(k-c.size())+1;i++){ - c.add(i); - findCombination(n,k,i+1,c); - c.remove(c.size()-1); - } - return; -} - -public List> combine(int n, int k) {
- res=new ArrayList();
- if(n<=0 || k<=0 || n c=new ArrayList();
- findCombination(n,k,1,c);
- return res;
-}
-```
-
-
-
-## 11、N皇后
-
-[N皇后](https://leetcode-cn.com/problems/n-queens/)
-
-
-
-
-
-- 思路:
-
-
-
-
-
-判断对角线不合法的情况:
-
-(1)竖向:col[i] 表示第 i 列被占用
-
-(2)对角线1:dai1[i] 表示第 i 条对角线被1占用
-
-
-
-(3)对角线2:dai2[i]表示第i对角线被2占用
-
-
-
-```java
-private List> res;
-
-//用于判断是否在同一竖线上,因为index表示行数,是变化的,所以不用判断是否在相同行
-private boolean[] cols;
-//判断是否在1类对角线上,相应坐标 i+j
-private boolean[] dial1;
-//判断是否在2类对角线上,相应坐标 i-j+n-1
-private boolean[] dial2;
-
-//在 index 行放皇后
-//row 记录在 index 行能够放皇后的位置
-//比如 row.get(0) = 1,就表示在棋盘的 [0,1] 位置放上皇后
-private void putQueen(int n,int index,List row){
- if(index==n){
- res.add(generateBoard(n,row));
- return;
- }
- // [index,j] 放皇后
- for(int j=0;j generateBoard(int n, List row) {
- List res=new ArrayList();
- if(n<=0){ - return res; - } - - char[][] board=new char[n][n]; - for(int i=0;i> solveNQueens(int n) {
- res=new ArrayList();
- if(n==0){
- return res;
- }
- cols=new boolean[n];
- dial1=new boolean[2*n-1];
- dial2=new boolean[2*n-1];
- List row=new ArrayList();
- putQueen(n,0,row);
- return res;
-}
-```
-
-
-
-## 12、N皇后 II
-
-[N皇后 II](https://leetcode-cn.com/problems/n-queens-ii/)
-
-```java
-
-```
-
-
-
-## 13、解数独
-
-[解数独](https://leetcode-cn.com/problems/sudoku-solver/)
-
-```java
-
-```
-
diff --git "a/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/4_346円220円234円347円264円242円.md" "b/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/4_346円220円234円347円264円242円.md"
new file mode 100644
index 00000000..3bbfe622
--- /dev/null
+++ "b/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/4_346円220円234円347円264円242円.md"
@@ -0,0 +1,812 @@
+
+
+
+
+
+
+# 回溯-Backtract
+
+## 1、二叉树中和为某一值的路径
+
+[二叉树中和为某一值的路径](https://www.nowcoder.com/practice/b736e784e3e34731af99065031301bca?tpId=13&tqId=11177&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+public ArrayList> FindPath(TreeNode root, int target) {
+ ArrayList> res = new ArrayList();
+ ArrayList values = new ArrayList();
+ backtrack(root,target,values,res);
+ return res;
+}
+
+// values : 记录从根节点到叶子节点的所有路径
+// paths : 存储所有可能的结果
+private void backtrack(TreeNode root, int target,
+ ArrayList values,
+ ArrayList> paths) {
+ if (root == null) {
+ return;
+ }
+ values.add(root.val);
+ if(root.left==null && root.right==null && root.val==target){
+ paths.add(new ArrayList(values));
+ }else{
+ backtrack(root.left,target-root.val,values,paths);
+ backtrack(root.right,target-root.val,values,paths);
+ }
+ values.remove(values.size()-1);
+}
+```
+
+
+
+## *2、二叉树的所有路径
+
+[二叉树的所有路径](https://leetcode-cn.com/problems/binary-tree-paths/)
+
+```java
+public List binaryTreePaths(TreeNode root) {
+ List paths=new ArrayList();
+ if(root==null){
+ return paths;
+ }
+ List values=new ArrayList();
+ backtrack(root,values,paths);
+ return paths;
+}
+
+//values 记录从根节点到叶子节点
+//paths 记录路径
+private void backtrack(TreeNode root, List values,List paths){
+ if(root==null){
+ return;
+ }
+ values.add(root.val);
+ if(root.left==null && root.right==null){ // 到达叶子节点
+ paths.add(buildPath(values));
+ }else{
+ backtrack(root.left,values,paths);
+ backtrack(root.right,values,paths);
+ }
+ values.remove(values.size()-1);
+}
+
+private String buildPath(List values){
+ StringBuilder res=new StringBuilder();
+ for(int i=0;i");
+ }
+ }
+ return res.toString();
+}
+```
+
+
+
+## *3、矩阵中的路径
+
+[矩阵中的路径](https://www.nowcoder.com/practice/c61c6999eecb4b8f88a98f66b273a3cc?tpId=13&tqId=11218&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+//思路:典型的回溯法思想
+private int m,n; //矩阵的长度、宽度
+private boolean[][] visted; //标记是否访问
+
+private int[][] d={
+ {0,1}, //向右
+ {-1,0},//向上
+ {0,-1},//向左
+ {1,0} //向下
+};
+
+private boolean inArea(int x,int y){
+ return (x>=0 && x=0 && y=0 && x=0 && y> res;
+
+//用于判断是否在同一竖线上,因为index表示行数,是变化的,所以不用判断是否在相同行
+private boolean[] cols;
+//判断是否在1类对角线上,相应坐标 i+j
+private boolean[] dial1;
+//判断是否在2类对角线上,相应坐标 i-j+n-1
+private boolean[] dial2;
+
+//在 index 行放皇后
+//row 记录在 index 行能够放皇后的位置
+//比如 row.get(0) = 1,就表示在棋盘的 [0,1] 位置放上皇后
+private void putQueen(int n,int index,List row){
+ if(index==n){
+ res.add(generateBoard(n,row));
+ return;
+ }
+ // [index,j] 放皇后
+ for(int j=0;j generateBoard(int n, List row) {
+ List res=new ArrayList();
+ if(n<=0){ + return res; + } + + char[][] board=new char[n][n]; + for(int i=0;i> solveNQueens(int n) {
+ res=new ArrayList();
+ if(n==0){
+ return res;
+ }
+ cols=new boolean[n];
+ dial1=new boolean[2*n-1];
+ dial2=new boolean[2*n-1];
+ List row=new ArrayList();
+ putQueen(n,0,row);
+ return res;
+}
+```
+
+
+
+## 6、解数独
+
+[解数独](https://leetcode-cn.com/problems/sudoku-solver/)
+
+```java
+// 思路:回溯法
+// 数独矩阵是 9*9 的方阵,也就是有 81 个位置,
+// 可以看成是一个长度为 81 的数组,使用 pos(从0开始) 记录在数组中位置
+// row[i][m] 表示第 i 行填数字 m 的占用情况,即 row[i][m]=true,表示在第i行填上数字 m 了。
+// col[i][m] 表示第 i 列填数字 m 的占用情况,即 col[i][m]=true,表示在第i列填上数字 m 了。
+// block[i][m] 表示第 i个九宫格填数字 m 的占用情况
+public void solveSudoku(char[][] board) {
+ if(board==null){
+ return;
+ }
+ boolean[][] row=new boolean[9][10];
+ boolean[][] col=new boolean[9][10];
+ boolean[][] block=new boolean[9][10];
+ for(int i=0;i<9;i++){ + for(int j=0;j<9;j++){ + if(board[i][j]!='.'){ // [i,j]位置已填上数字 + int num= board[i][j]-'0'; + row[i][num]=true; + col[j][num]=true; + block[i/3*3+j/3][num]=true; + } + } + } + for(int pos=0;pos<81;pos++){ + int x=pos/9; + int y=pos%9; + if(board[x][y]=='.'){ + if(!putNum(board,pos,row,col,block)){ + continue; + } + } + } +} + +// 向 pos 位置放数字 +private boolean putNum(char[][] board, int pos, + boolean[][] row, boolean[][] col, boolean[][] block){ + if(pos==81){ + return true; + } + int nextPos = pos+1; // pos 位置的下一未填数字的位置 + for(;nextPos<81;nextpos++){ + if(board[nextPos/9][nextPos%9]=='.'){ + break; + } + } + // [x,y] 表示 pos 在表格中位置 + int x=pos/9; + int y=pos%9; + + for(int num=1;num<=9;num++){ // pos 位置可以填 1-9 任意整数 + if(!row[x][num] && !col[y][num] && !block[x/3*3+y/3][num]){ + board[x][y]=(char)(num+'0'); + row[x][num]=true; + col[y][num]=true; + block[x/3*3+y/3][num]=true; + if(putNum(board,nextPos,row,col,block)){ + return true; + } + block[x/3*3+y/3][num]=false; + col[y][num]=false; + row[x][num]=false; + board[x][y]='.'; + } + } + return false; +} +``` + + + +# 广度优先-BFS + +## 1、单词接龙 + +[单词接龙](https://leetcode-cn.com/problems/word-ladder/description/) + +```java +public int ladderLength(String beginWord, String endWord, List wordList) {
+ wordList.add(beginWord);
+ int N = wordList.size();
+ int start = N - 1;
+ int end = 0;
+ while (end < N && !wordList.get(end).equals(endWord)) { + end++; + } + if (end == N) { + return 0; + } + List[] graphic = buildGraphic(wordList);
+ return getShortestPath(graphic, start, end);
+}
+
+private List[] buildGraphic(List wordList) {
+ int N = wordList.size();
+ List[] graphic = new List[N];
+ for (int i = 0; i < N; i++) { + graphic[i] = new ArrayList();
+ for (int j = 0; j < N; j++) { + if (isConnect(wordList.get(i), wordList.get(j))) { + graphic[i].add(j); + } + } + } + return graphic; +} + +private boolean isConnect(String s1, String s2) { + int diffCnt = 0; + for (int i = 0; i < s1.length() && diffCnt <= 1; i++) { + if (s1.charAt(i) != s2.charAt(i)) { + diffCnt++; + } + } + return diffCnt == 1; +} + +private int getShortestPath(List[] graphic, int start, int end) {
+ Queue queue = new LinkedList();
+ boolean[] marked = new boolean[graphic.length];
+ queue.add(start);
+ marked[start] = true;
+ int path = 1;
+ while (!queue.isEmpty()) {
+ int size = queue.size();
+ path++;
+ while (size--> 0) {
+ int cur = queue.poll();
+ for (int next : graphic[cur]) {
+ if (next == end) {
+ return path;
+ }
+ if (marked[next]) {
+ continue;
+ }
+ marked[next] = true;
+ queue.add(next);
+ }
+ }
+ }
+ return 0;
+}
+```
+
+
+
+# 深度优先-DFS
+
+## 1、岛屿数量
+
+[岛屿数量](https://leetcode-cn.com/problems/number-of-islands/)
+
+```java
+// 思路:典型的 flood-fill 算法
+private int m,n;
+
+private boolean[][] visited;
+
+private boolean inArea(int x,int y){
+ return (x>=0 && x=0 && y=0 && x=0 && y=0 && x=0 && y0){
+ sum += num %10;
+ num/=10;
+ }
+ return sum;
+}
+
+private boolean valid(int threshlod ,int x,int y){
+ return (getNum(x)+getNum(y))<=threshlod; +} + +private int walk(int threshold,int startx,int starty){ + visited[startx][starty]=true; + int walks = 0; + if(valid(threshold,startx,starty)){ + walks=1; + } + for(int i=0;i<4;i++){ + int newX = startx+d[i][0]; + int newY = starty+d[i][1]; + if(inArea(newX,newY)){ + if(!visited[newX][newY] && valid(threshold,newX,newY)){ + walks += walk(threshold,newX,newY); + } + } + } + return walks; +} + +public int movingCount(int threshold, int rows, int cols) { + if(threshold<0){ //threshold<0,则机器人就不能走了 + return 0; + } + m = rows; + if(m==0){ + return 0; + } + n= cols; + visited = new boolean[m][n]; + return walk(threshold,0,0); +} +``` + + + +## 4、被围绕的区域 + +[被围绕的区域](https://leetcode-cn.com/problems/surrounded-regions/) + +```java +private int m,n; + +private boolean[][] visited; + +private int[][] d={ + {-1,0}, + {0,1}, + {1,0}, + {0,-1} +}; + +private boolean inArea(int x,int y){ + return (x>=0 && x=0 && y=0 && x=0 && y=matrix[startx][starty]){
+ dfs(matrix,newX,newY,visited);
+ }
+ }
+ }
+}
+
+public List> pacificAtlantic(int[][] matrix) {
+ List> res=new ArrayList();
+ m=matrix.length;
+ if(m==0){
+ return res;
+ }
+ n=matrix[0].length;
+ pacific=new boolean[m][n];
+ atlantic=new boolean[m][n];
+
+ for(int j=0;j> res;
+
+private boolean[] visited;
+
+public List> permute(int[] nums) {
+ res = new ArrayList();
+ if(nums==null || nums.length==0){
+ return res;
+ }
+ visited = new boolean[nums.length];
+ List p =new ArrayList();
+ generatePermutation(nums,0,p);
+ return res;
+}
+
+//p中保存一个有 index 的元素的排列
+//向这个排列的末尾添加第 (index+1) 个元素,组成有(index+1) 个元素排列
+private void generatePermutation(int[] nums,int index,List p){
+ if(index==nums.length){
+ res.add(new ArrayList(p));
+ return;
+ }
+ for(int i=0;i> res;
+
+private boolean[] visited;
+
+public List> permuteUnique(int[] nums) {
+ res = new ArrayList();
+ if(nums==null || nums.length==0){
+ return res;
+ }
+ visited = new boolean[nums.length];
+ Arrays.sort(nums);
+ List p = new ArrayList();
+ findUniquePermutation(nums,0,p);
+ return res;
+}
+
+//p 保存的是有 index 元素的排列
+//向这个排列的末尾添加第 (index+1) 个元素,组成有(index+1) 个元素排列
+private void findUniquePermutation(int[] nums,int index,List p){
+ if(index==nums.length){
+ res.add(new ArrayList(p));
+ return;
+ }
+ for(int i=0;i0 && nums[i-1]==nums[i] &&
+ !visited[i-1]){ //注意:相邻元素相同,并且都未被访问
+ continue;
+ }
+ visited[i]=true;
+ p.add(nums[i]);
+ findUniquePermutation(nums,index+1,p);
+ p.remove(p.size()-1);
+ visited[i]=false;
+ }
+ }
+ return;
+}
+```
+
+
+
+## 3、字符串的排列
+
+[字符串的排列](https://www.nowcoder.com/practice/fe6b651b66ae47d7acce78ffdd9a96c7?tpId=13&tqId=11180&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+private ArrayList res;
+private boolean[] visited;
+
+public ArrayList Permutation(String str) {
+ res = new ArrayList();
+ if(str==null || str.length()==0){
+ return res;
+ }
+ char[] chs = str.toCharArray();
+ Arrays.sort(chs); //方便后面的去重处理
+ visited = new boolean[str.length()];
+ permute(chs,0,new StringBuilder());
+ return res;
+}
+
+//产生排列
+//p中保存一个存在index个元素的排列
+//向这个排列的末尾添加第(index+1)个元素,获得包含(index+1)个元素的排列
+private void permute(char[] chs,int index,StringBuilder p){
+ if(index==chs.length){
+ res.add(p.toString());
+ return;
+ }
+ for(int i=0;i0 && chs[i-1]==chs[i] && !visited[i-1]){
+ continue;
+ }
+ if(!visited[i]){
+ p.append(chs[i]);
+ visited[i] = true;
+ permute(chs,index+1,p);
+ p.deleteCharAt(p.length()-1);
+ visited[i] = false;
+ }
+ }
+}
+```
+
+
+
+## *4、字母大小写全排列
+
+[字母大小写全排列](https://leetcode-cn.com/problems/letter-case-permutation/)
+
+```java
+private List res;
+
+//处理 index 位置的数据
+//如果 index 位置是字母的话,则替换
+private void replaceLetter(int index,StringBuilder p){
+ if(index==p.length()){
+ res.add(p.toString());
+ return;
+ }
+ char ch=p.charAt(index);
+ if(Character.isLetter(ch)){
+ p.setCharAt(index,Character.toUpperCase(ch));
+ replaceLetter(index+1,p);
+ p.setCharAt(index,Character.toLowerCase(ch));
+ replaceLetter(index+1,p);
+ }else{
+ replaceLetter(index+1,p);
+ }
+ return;
+}
+
+public List letterCasePermutation(String S) {
+ res=new ArrayList();
+ if(S==null || S.length()==0){
+ return res;
+ }
+ StringBuilder p=new StringBuilder(S);
+ replaceLetter(0,p);
+ return res;
+}
+```
+
+
+
+## *5、电话号码的字母组合
+
+[电话号码的字母组合](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/)
+
+```java
+private final String[] letterMap={
+ " ",//0
+ "",//1
+ "abc",//2
+ "def",//3
+ "ghi",//4
+ "jkl",//5
+ "mno",//6
+ "pqrs",//7
+ "tuv",//8
+ "wxyz"//9
+};
+
+private List res;
+
+public List letterCombinations(String digits) {
+ res=new ArrayList();
+ if(digits==null || digits.length()==0){
+ return res;
+ }
+ generateLetterCombinations(digits,0,new StringBuilder());
+ return res;
+}
+
+// p 保存 digits[0,index-1] 翻译得到的字符串
+private void generateLetterCombinations(String digits,int index,StringBuilder p){
+ if(index==digits.length()){
+ res.add(p.toString());
+ return;
+ }
+ char[] chs=letterMap[digits.charAt(index)-'0'].toCharArray();
+ for(int i=0;i> res;
+
+//c存储已经找到的组合
+//从start开始搜索新的元素
+private void findCombination(int n,int k,int start,List c){
+ if(k==c.size()){
+ res.add(new ArrayList(c));
+ return;
+ }
+ for(int i=start;i<=n;i++){ + c.add(i); + findCombination(n,k,i+1,c); + c.remove(c.size()-1); + } + return; +} + +public List> combine(int n, int k) {
+ res=new ArrayList();
+ if(n<=0 || k<=0 || n c=new ArrayList();
+ findCombination(n,k,1,c);
+ return res;
+}
+```
+
+```java
+//思路二:剪枝优化
+private List> res;
+
+//c存储已经找到的组合
+//从start开始搜索新的元素
+private void findCombination(int n,int k,int start,List c){
+ if(k==c.size()){
+ res.add(new ArrayList(c));
+ return;
+ }
+
+ //优化--剪枝
+ //c存储的是已经找到的组合。
+ //此时还剩下k-c.size()个空位,
+ //则 [i...n]之间的元素最少要有 k-c.size() 个,即 n-i+1>= k-c.size()
+ //所以有 i <= n-(k-c.size())+1 + + for(int i=start;i<=n-(k-c.size())+1;i++){ + c.add(i); + findCombination(n,k,i+1,c); + c.remove(c.size()-1); + } + return; +} + +public List> combine(int n, int k) {
+ res=new ArrayList();
+ if(n<=0 || k<=0 || n c=new ArrayList();
+ findCombination(n,k,1,c);
+ return res;
+}
+```
+
diff --git "a/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/5_346円267円261円345円272円246円344円274円230円345円205円210円.md" "b/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/5_346円267円261円345円272円246円344円274円230円345円205円210円.md"
deleted file mode 100644
index 60cf3bd7..00000000
--- "a/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/5_346円267円261円345円272円246円344円274円230円345円205円210円.md"
+++ /dev/null
@@ -1,329 +0,0 @@
-# 深度优先
-
-## 1、岛屿数量
-
-[岛屿数量](https://leetcode-cn.com/problems/number-of-islands/)
-
-```java
-// 思路:典型的 flood-fill 算法
-private int m,n;
-
-private boolean[][] visited;
-
-private boolean inArea(int x,int y){
- return (x>=0 && x=0 && y=0 && x=0 && y=0 && x=0 && y0){
- sum += num %10;
- num/=10;
- }
- return sum;
-}
-
-private boolean valid(int threshlod ,int x,int y){
- return (getNum(x)+getNum(y))<=threshlod; -} - -private int walk(int threshold,int startx,int starty){ - visited[startx][starty]=true; - int walks = 0; - if(valid(threshold,startx,starty)){ - walks=1; - } - for(int i=0;i<4;i++){ - int newX = startx+d[i][0]; - int newY = starty+d[i][1]; - if(inArea(newX,newY)){ - if(!visited[newX][newY] && valid(threshold,newX,newY)){ - walks += walk(threshold,newX,newY); - } - } - } - return walks; -} - -public int movingCount(int threshold, int rows, int cols) { - if(threshold<0){ //threshold<0,则机器人就不能走了 - return 0; - } - m = rows; - if(m==0){ - return 0; - } - n= cols; - visited = new boolean[m][n]; - return walk(threshold,0,0); -} -``` - - - -## 4、被围绕的区域 - -[被围绕的区域](https://leetcode-cn.com/problems/surrounded-regions/) - -```java -private int m,n; - -private boolean[][] visited; - -private int[][] d={ - {-1,0}, - {0,1}, - {1,0}, - {0,-1} -}; - -private boolean inArea(int x,int y){ - return (x>=0 && x=0 && y=0 && x=0 && y=matrix[startx][starty]){
- dfs(matrix,newX,newY,visited);
- }
- }
- }
-}
-
-public List> pacificAtlantic(int[][] matrix) {
- List> res=new ArrayList();
- m=matrix.length;
- if(m==0){
- return res;
- }
- n=matrix[0].length;
- pacific=new boolean[m][n];
- atlantic=new boolean[m][n];
-
- for(int j=0;j list=new ArrayList();
- for(int i=0;i() { //按照其实位置进行排序
- @Override
- public int compare(Interval o1, Interval o2) {
- int num=o1.start-o2.start;
- int num2=(num==0)?(o1.end-o2.end):num;
- return num2;
+ Arrays.sort(g);
+ Arrays.sort(s);
+ int i=g.length-1;
+ int j=s.length-1;
+ int num=0;
+ while (i>=0 && j>=0){
+ if(s[j]>=g[i]){
+ num++;
+ j--;
}
- });
-
- ArrayList res=new ArrayList();
- Interval cur=list.get(0);
- for(int i=1;i=list.get(i).start){ //存在区间交叉
- //区间进行合并
- cur.end=Math.max(cur.end,list.get(i).end);
- }else{ //不存在交叉
- res.add(cur);
- cur=list.get(i);
- }
- if(i==m-1){
- res.add(cur);
- }
- }
-
- int n=res.size();
- int[][] matrix=new int[n][2];
- for(int i=0;ilimit){ // 如果只能载一个人,则载最重的
+ res++;
+ heavy--;
+ }else{ // people[light]+people[heavy]<=limit,船可以搭载两个人 + res++; + light++; + heavy--; + } + } + return res; +} ``` -## 5、无重叠区间 +## *5、汇总区间 -[无重叠区间](https://leetcode-cn.com/problems/non-overlapping-intervals/) +[汇总区间](https://leetcode-cn.com/problems/summary-ranges/) ```java +public List summaryRanges(int[] nums) {
+ List res=new ArrayList();
+ if(nums==null || nums.length==0){
+ return res;
+ }
+ if(nums.length==1) {
+ res.add(nums[0] + "");
+ return res;
+ }
+ int start=nums[0];
+ int end=start;
+ for(int i=1;i"+end;
+}
```
-## 6、救生艇
+## 6、无重叠区间
-[救生艇](https://leetcode-cn.com/problems/boats-to-save-people/)
+[无重叠区间](https://leetcode-cn.com/problems/non-overlapping-intervals/)
```java
-
+// 思路:
+// 首先计算最多能组成的不重叠区间个数,然后用区间总个数减去不重叠区间的个数。
+// 其中区间结尾至关重要,选择的区间结尾越小,留给后面的空间就越大,那么后面能够选择的区间个数也就越大。
+public int eraseOverlapIntervals(int[][] intervals) {
+ if(intervals==null || intervals.length==0){
+ return 0;
+ }
+ // 按照区间结尾进行升序排序
+ // 写法一:比较器
+ // 这里使用 p1[1]() {
+ @Override
+ public int compare(int[] p1, int[] p2) {
+ return p1[1] p1[1]p[1]));*/
+ int cnt=1; // 记录最大不重叠区间数
+ int end=intervals[0][1]; // 记录前一个不重叠区间结尾位置
+ for(int i=1;i() {
+ @Override
+ public int compare(int[] o1, int[] o2) {
+ return o1[1](o1[1]o[1]));
+ int res=1;
+ int end=points[0][1];
+ for(int i=1;i() {
+ @Override
+ public int compare(int[] o1, int[] o2) {
+ return (o1[0]
+ o1[0]o[0]));*/
+ List intervalList=new ArrayList(); // 保存结果区间
+ int[] cur=intervals[0]; // 记录当前重叠区间
+ int m=intervals.length;
+ for(int i=1;i=intervals[i][0]){ // cur.end>= next.start
+ cur[1]=Math.max(cur[1],intervals[i][1]);
+ }else{
+ intervalList.add(cur);
+ cur=intervals[i];
+ }
+ if(i==m-1){
+ intervalList.add(cur);
+ }
+ }
+ int[][] res=new int[intervalList.size()][2];
+ for(int i=0;i=0 || j>=0){
+ c+=(i>=0)?num1.charAt(i)-'0':0;
+ c+=(j>=0)?num2.charAt(j)-'0':0;
+ res.append(c%10);
+ c/=10;
+ i--;
+ j--;
}
-
- int l=1;
- int r=x;
- while(l<=r){ - int mid=(r-l)/2+l; - int sqrt=x/mid; - if(sqrt==mid){ - return mid; - }else if(sqrtmid;
- l=mid+1;
- }
+ if(c==1){ // 考虑 num1="90"、num2="90" 的情况
+ res.append("1");
}
- return r;
+ return res.reverse().toString();
}
```
-## 7、求 1+2+3+...+n
+## 2、求 1+2+3+...+n
[求 1+2+3+...+n](https://www.nowcoder.com/practice/7a0da8fc483247ff8800059e12d7caf1?tpId=13&tqId=11200&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -245,11 +216,46 @@ public int Sum_Solution(int n) {
-## *8、从 1 到 n 整数中 1 出现的次数
+## *3、从 1 到 n 整数中 1 出现的次数
[从 1 到 n 整数中 1 出现的次数](https://www.nowcoder.com/practice/bd7f978302044eee894445e244c7eee6?tpId=13&tqId=11184&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
```java
+// 思路一:暴力法
+// 时间复杂度:O(n^2)
+public int NumberOf1Between1AndN_Solution(int n) {
+ if(n<=0){ + return 0; + } + int res=0; + for(int i=1;i<=n;i++){ + res+=getNumberOf1(i); + } + return res; +} + +private int getNumberOf1(int n){ + int cnt=0; + while(n>0){
+ if(n%10==1){
+ cnt++;
+ }
+ n/=10;
+ }
+ return cnt;
+}
+```
+
+```java
+// 思路二:参考 https://blog.csdn.net/yi_Afly/article/details/52012593
+// 总结如下规律:
+// base 为当前位数 base=1,10,100,
+// weight 为当前位值
+// formatter 为 weight 的后一位
+// round 就是 weight 前的所有位
+// 当 weight==0 时,count=round*base
+// 当 weight==1 时,count=round*base+formatter+1
+// 当 weight>1 时,count==round*base+base
public int NumberOf1Between1AndN_Solution(int n) {
if(n<1){ return 0; @@ -276,13 +282,11 @@ public int NumberOf1Between1AndN_Solution(int n) { } ``` -> [Leetcode : 233. Number of Digit One](https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython)
-
> [参考:从 1 到 n 整数中 1 出现的次数](https://blog.csdn.net/yi_Afly/article/details/52012593)
-## *9、数字序列中的某一位数字
+## *4、数字序列中的某一位数字
数字以 0123456789101112131415... 的格式序列化到一个字符串中,求这个字符串的第 index 位。
@@ -340,7 +344,85 @@ private int getDigitAtIndex(int index, int place) {
-## 10、圆圈中最后剩下的数
+# 经典数学问题
+
+## *1、数值的整数次方
+
+[数值的整数次方](https://www.nowcoder.com/practice/1a834e5e3e1a4b7ba251417554e07c00?tpId=13&tqId=11165&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+//思路:分治法
+// a^n 次方
+// 若 a 为奇数 a^n = a*(a*a)^((n-1)/2)
+// 若 a 为偶数 a^n = (a*a)^(n/2)
+
+//注意:
+// n<0 时,则 a^n = 1 /(a ^(-n)) +// 但是当 n == Integer.MIN_VALUE 时,-n = Integer.MAX_VALUE+1 +// 我们可以转化为 1 / (a^(Integer.MAX_VALUE)*a) + +public double Power(double base, int exponent) { + if(exponent==0){ + return 1.0; + } + if(exponent==1){ + return base; + } + if(exponent==Integer.MIN_VALUE){ + return 1/(Power(base,Integer.MAX_VALUE)*base); + } + + if(exponent<0){ + exponent = -exponent; + return 1/Power(base,exponent); + } + + if(exponent%2==1){ + return Power(base*base,(exponent-1)/2)*base; + }else{ + return Power(base*base,exponent/2); + } +} +``` + + + +## 2、x 的平方根 + +[x 的平方根](https://leetcode-cn.com/problems/sqrtx/) + +```java +// 思路:要求是非负整数,则必然 x>=0
+// 当 x<4时,可以证明 sqrt(x)> x/2
+// 当 x=4时,sqrt(4)==4/2=2
+// 当 x>4时,可以证明 sqrt(x) < x/2 +public int mySqrt(int x) { + if(x==0){ + return 0; + } + if(x<4){ // x=1,x=2,x=3,sqrt(x)=1 + return 1; + } + // 考虑 x>=4的情况
+ int l=1;
+ int r=x/2;
+ while(l<=r){ + int mid=(r-l)/2+l; + if(x/midx,采用 x/midmid){
+ l=mid+1;
+ }else{
+ return mid;
+ }
+ }
+ return r; // 只保留整数部分,所以取r
+}
+```
+
+
+
+## *3、圆圈中最后剩下的数
[圆圈中最后剩下的数](https://www.nowcoder.com/practice/f78a359491e64a50bce2d89cff857eb6?tpId=13&tqId=11199&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
@@ -386,100 +468,183 @@ public int LastRemaining_Solution(int n, int m) {
> [参考:约瑟夫环问题](https://blog.csdn.net/u011500062/article/details/72855826)
-## 11、计数质数
+
+
+## 4、计数质数
[计数质数](https://leetcode-cn.com/problems/count-primes/)
```java
-
+// 思路:
+// 定义一个非素数数组 notPrimes,其中 notPrimes[i] 表示元素 i 是否是素数
+// notPrimes[i]=true,说明 i 是素数
+// notPrimes[i]=false,说明 i 不是素数
+public int countPrimes(int n) {
+ int cnt=0;
+ if(n<=1){ + return cnt; + } + boolean[] notPrimes=new boolean[n]; + for(int x=2;x=n 循环停止
+ for(long j=(long)x*x;j
+// 11!=11*(2*5)*9*(4*2)*7*(3*2)*(1*5)*(2*2)*3*(1*2)*1=39916800,有2对<2,5>
+// 也就是说,有多少对<2,5>就有多少0
+// 对于含有2的因子的话是 1*2, 2*2, 3*2, 4*2
+// 对于含有5的因子的话是 1*5, 2*5
+// 含有2的因子每两个出现一次,含有5的因子每5个出现一次,所以2出现的个数远远多于 5,
+// 换言之找到一个5,一定能找到一个2与之配对。
+// 所以我们只需要找有多少个 5。
+// n! = 1 * 2 * 3 * 4 * (1 * 5) * ... * (2 * 5) * ... * (3 * 5) *... * n
+// 可以得出如下规律:每隔5个数会出现一个5,每隔25个数,出现2个5,每隔125个数,出现3个5
+// 所以最终 5的个数:n/5+ n/25 (有1个5在每隔5个数已经出现过一次了) + n/125 (有1个在每隔5个数出现过了,另一个则载每隔25个数出现过了)
+public int trailingZeroes(int n) {
+ if(n<5){ + return 0; + } + int res=0; + while (n>0){
+ res+=n/5;
+ n/=5;
+ }
+ return res;
+}
```
-## 13、字符串相加
+## 6、最少移动次数使数组元素相等 II
-[字符串相加](https://leetcode-cn.com/problems/add-strings/)
+[ 最少移动次数使数组元素相等 II](https://leetcode-cn.com/problems/minimum-moves-to-equal-array-elements-ii/)
```java
-public String addStrings(String num1, String num2) {
- if(num1==null || num1.length()==0){
- return num2;
- }
- if(num2==null || num2.length()==0){
- return num1;
- }
-
- int i=num1.length()-1;
- int j=num2.length()-1;
- int c=0;
-
- StringBuilder res=new StringBuilder();
-
- while(i>=0 || j>=0 || c==1){
- c+=(i>=0)?num1.charAt(i)-'0':0;
- c+=(j>=0)?num2.charAt(j)-'0':0;
- res.append(c%10);
- c/=10;
- i--;
- j--;
+// 思路:
+// 当 x 为这个 N 个数的中位数时,可以使得距离最小。
+// 具体地,若 N 为奇数,则 x 必须为这 N 个数中的唯一中位数;
+// 若 N 为偶数,中间的两个数为 p 和 q,中位数为 (p + q) / 2,
+// 此时 x 只要是区间 [p, q](注意是闭区间) 中的任意一个数即可。
+
+// 写法一:排序求中位数
+public int minMoves2(int[] nums) {
+ Arrays.sort(nums);
+ // 获取数组中位数获取[p,q]中的数值p
+ int mid=nums[nums.length/2];
+ int res=0;
+ for(int num:nums){
+ res+=Math.abs(num-mid);
}
-
- return res.reverse().toString();
+ return res;
}
```
-
-
-## 14、最少移动次数使数组元素相等 II
-
-[ 最少移动次数使数组元素相等 II](https://leetcode-cn.com/problems/minimum-moves-to-equal-array-elements-ii/)
-
```java
+// 写法二:利用快速排序求中位数
+public int minMoves2(int[] nums) {
+ if(nums.length==1){
+ return 0;
+ }
+ int k=nums.length/2;
+ int mid=nums[findKth(nums,k)];
+ int res=0;
+ for(int num:nums){
+ res+=Math.abs(num-mid);
+ }
+ return res;
+}
+private int findKth(int[] nums,int k){
+ int start=0;
+ int end=nums.length-1;
+ while (start<=end){ + int p=partition(nums,start,end); + if(p==k){ + return p; + }else if(pk
+ end=p-1;
+ }
+ }
+ return -1;
+}
+
+private int partition(int[] nums,int start,int end){
+ int pivot=nums[start];
+ while(start=pivot){
+ end--;
+ }
+ nums[start]=nums[end];
+ // 从左向右找第一个大于 pivot 的数
+ while (start n/2,但在本题目 m 即是多数元素
public int majorityElement(int[] nums) {
- int cnt=0; //统计众数
- int majority=-1;
- int n=nums.length;
-
+ int m=nums[0];
+ int cnt=0;
for(int num:nums){
if(cnt==0){
- majority=num;
- cnt++;
+ m=num;
+ cnt=1;
}else{
- if(majority!=num){
- cnt--;
- }else{
+ if(num==m){
cnt++;
+ }else{
+ cnt--;
}
}
}
-
- cnt=0;
- for(int num:nums){
- if(num==majority){
- cnt++;
- }
- }
- return (cnt>n/2)?majority:-1;
+ return m;
}
```
diff --git "a/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/8_345円205円266円344円273円226円.md" "b/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/8_345円205円266円344円273円226円.md"
index a69d6523..cbf78eab 100644
--- "a/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/8_345円205円266円344円273円226円.md"
+++ "b/docs/AimForOffer/347円256円227円346円263円225円346円200円235円346円203円263円347円233円270円345円205円263円/8_345円205円266円344円273円226円.md"
@@ -47,7 +47,7 @@ public class Shuffle {
## 2、抢红包算法
-线段切割法:在一条线段上找 (N-1) 个随机点,就可以将该线段随机且公平地切割成 N 段。
+线段切割法:在一条线段上找 (N-1) 个随机点,就可以将该线段随机且公平地切割成 N 段。算上首端点和尾端点的话,总共有 (N+1) 个点
```java
public class RedPacket {
@@ -57,29 +57,27 @@ public class RedPacket {
* 如果是小数的话先转化为整数,相应的结果除以 100 即可
* @return List 存储 n 个红包的金额
*/
- public List generatePocketByLineCutting(int n,double money){
+ public List generatePocketByLineCutting(int n, double money){
Random random=new Random();
//如果是小数的话先转化为整数
int newMoney=(int)money*100;
- //存储线段的的 (n-1) 个随机点,线段长度为 newMoney
- Set set=new TreeSet();
-
- while(set.size() points=new ArrayList();
+ while (points.size() res=new ArrayList();
-
- int pre=0;
- for(Integer p:set) {
- res.add((p - pre) * 1.0 / 100);
- pre = p;
+ for(int i=1;i=0 个任意字符;
-
-- **\_** 匹配 ==1 个任意字符;
-
-- **[ ]** 可以匹配集合内的字符,例如 [ab] 将匹配字符 a 或者 b。用脱字符 ^ 可以对其进行否定,也就是不匹配集合内的字符。
-
-使用 Like 来进行通配符匹配。
-
-```sql
-SELECT *
-FROM mytable
-WHERE col LIKE '[^AB]%'; -- 不以 A 和 B 开头的任意文本
-```
-
-不要滥用通配符,通配符位于开头处匹配会非常慢。
-
-# 十一、计算字段
-
-在数据库服务器上完成数据的转换和格式化的工作往往比客户端上快得多,并且转换和格式化后的数据量更少的话可以减少网络通信量。
-
-计算字段通常需要使用 **AS** 来取别名,否则输出的时候字段名为计算表达式。
-
-```sql
-SELECT col1 * col2 AS alias
-FROM mytable;
-```
-
-**CONCAT()** 用于连接两个字段。许多数据库会使用空格把一个值填充为列宽,因此连接的结果会出现一些不必要的空格,使用 **TRIM()** 可以去除首尾空格。
-
-```sql
-SELECT CONCAT(TRIM(col1), '(', TRIM(col2), ')') AS concat_col
-FROM mytable;
-```
-
-# 十二、函数
-
-各个 DBMS 的函数都是不相同的,因此不可移植,以下主要是 MySQL 的函数。
-
-## 汇总
-
-|函 数 |说 明|
-| :---: | :---: |
-| AVG() | 返回某列的平均值 |
-| COUNT() | 返回某列的行数 |
-| MAX() | 返回某列的最大值 |
-| MIN() | 返回某列的最小值 |
-| SUM() |返回某列值之和 |
-
-AVG() 会忽略 NULL 行。
-
-使用 DISTINCT 可以让汇总函数值汇总不同的值。
-
-```sql
-SELECT AVG(DISTINCT col1) AS avg_col
-FROM mytable;
-```
-
-## 文本处理
-
-| 函数 | 说明 |
-| :---: | :---: |
-| LEFT() | 左边的字符 |
-| RIGHT() | 右边的字符 |
-| LOWER() | 转换为小写字符 |
-| UPPER() | 转换为大写字符 |
-| LTRIM() | 去除左边的空格 |
-| RTRIM() | 去除右边的空格 |
-| LENGTH() | 长度 |
-| SOUNDEX() | 转换为语音值 |
-
-其中, **SOUNDEX()** 可以将一个字符串转换为描述其语音表示的字母数字模式。
-
-```sql
-SELECT *
-FROM mytable
-WHERE SOUNDEX(col1) = SOUNDEX('apple')
-```
-
-## 日期和时间处理
-
-- 日期格式:YYYY-MM-DD
-- 时间格式:HH:MM:SS
-
-|函 数 | 说 明|
-| :---: | :---: |
-| AddDate() | 增加一个日期(天、周等)|
-| AddTime() | 增加一个时间(时、分等)|
-| CurDate() | 返回当前日期 |
-| CurTime() | 返回当前时间 |
-| Date() |返回日期时间的日期部分|
-| DateDiff() |计算两个日期之差|
-| Date_Add() |高度灵活的日期运算函数|
-| Date_Format() |返回一个格式化的日期或时间串|
-| Day()| 返回一个日期的天数部分|
-| DayOfWeek() |对于一个日期,返回对应的星期几|
-| Hour() |返回一个时间的小时部分|
-| Minute() |返回一个时间的分钟部分|
-| Month() |返回一个日期的月份部分|
-| Now() |返回当前日期和时间|
-| Second() |返回一个时间的秒部分|
-| Time() |返回一个日期时间的时间部分|
-| Year() |返回一个日期的年份部分|
-
-```sql
-mysql> SELECT NOW();
-```
-
-```
-2018年4月14日 20:25:11
-```
-
-## 数值处理
-
-| 函数 | 说明 |
-| :---: | :---: |
-| SIN() | 正弦 |
-| COS() | 余弦 |
-| TAN() | 正切 |
-| ABS() | 绝对值 |
-| SQRT() | 平方根 |
-| MOD() | 余数 |
-| EXP() | 指数 |
-| PI() | 圆周率 |
-| RAND() | 随机数 |
-
-# 十三、分组
-
-分组就是把具有相同的数据值的行放在同一组中。
-
-可以对同一分组数据使用汇总函数进行处理,例如求分组数据的平均值等。
-
-指定的分组字段除了能按该字段进行分组,也会自动按该字段进行排序。
-
-```sql
-SELECT col, COUNT(*) AS num
-FROM mytable
-GROUP BY col;
-```
-
-GROUP BY 自动按分组字段进行排序,ORDER BY 也可以按汇总字段来进行排序。
-
-```sql
-SELECT col, COUNT(*) AS num
-FROM mytable
-GROUP BY col
-ORDER BY num;
-```
-
-WHERE 过滤行,HAVING 过滤分组,行过滤应当先于分组过滤。
-
-```sql
-SELECT col, COUNT(*) AS num
-FROM mytable
-WHERE col> 2
-GROUP BY col
-HAVING num>= 2;
-```
-
-分组规定:
-
-- GROUP BY 子句出现在 WHERE 子句之后,ORDER BY 子句之前;
-- 除了汇总字段外,SELECT 语句中的每一字段都必须在 GROUP BY 子句中给出;
-- NULL 的行会单独分为一组;
-- 大多数 SQL 实现不支持 GROUP BY 列具有可变长度的数据类型。
-
-# 十四、子查询
-
-子查询中只能返回一个字段的数据。
-
-可以将子查询的结果作为 WHRER 语句的过滤条件:
-
-```sql
-SELECT *
-FROM mytable1
-WHERE col1 IN (SELECT col2
- FROM mytable2);
-```
-
-下面的语句可以检索出客户的订单数量,子查询语句会对第一个查询检索出的每个客户执行一次:
-
-```sql
-SELECT cust_name, (SELECT COUNT(*)
- FROM Orders
- WHERE Orders.cust_id = Customers.cust_id)
- AS orders_num
-FROM Customers
-ORDER BY cust_name;
-```
-
-# 十五、连接
-
-连接用于连接多个表,使用 JOIN 关键字,并且条件语句使用 ON 而不是 WHERE。
-
-连接可以替换子查询,并且比子查询的效率一般会更快。
-
-可以用 AS 给列名、计算字段和表名取别名,给表名取别名是为了简化 SQL 语句以及连接相同表。
-
-## 内连接
-
-内连接又称等值连接,使用 INNER JOIN 关键字。
-
-```sql
-SELECT A.value, B.value
-FROM tablea AS A INNER JOIN tableb AS B
-ON A.key = B.key;
-```
-
-可以不明确使用 INNER JOIN,而使用普通查询并在 WHERE 中将两个表中要连接的列用等值方法连接起来。
-
-```sql
-SELECT A.value, B.value
-FROM tablea AS A, tableb AS B
-WHERE A.key = B.key;
-```
-
-在没有条件语句的情况下返回笛卡尔积。
-
-## 自连接
-
-自连接可以看成内连接的一种,只是连接的表是自身而已。
-
-一张员工表,包含员工姓名和员工所属部门,要找出与 Jim 处在同一部门的所有员工姓名。
-
-子查询版本
-
-```sql
-SELECT name
-FROM employee
-WHERE department = (
- SELECT department
- FROM employee
- WHERE name = "Jim");
-```
-
-自连接版本
-
-```sql
-SELECT e1.name
-FROM employee AS e1 INNER JOIN employee AS e2
-ON e1.department = e2.department
- AND e2.name = "Jim";
-```
-
-## 自然连接
-
-自然连接是把同名列通过等值测试连接起来的,同名列可以有多个。
-
-内连接和自然连接的区别:内连接提供连接的列,而自然连接自动连接所有同名列。
-
-```sql
-SELECT A.value, B.value
-FROM tablea AS A NATURAL JOIN tableb AS B;
-```
-
-## 外连接
-
-外连接保留了没有关联的那些行。分为左外连接,右外连接以及全外连接,左外连接就是保留左表没有关联的行。
-
-检索所有顾客的订单信息,包括还没有订单信息的顾客。
-
-```sql
-SELECT Customers.cust_id, Orders.order_num
-FROM Customers LEFT OUTER JOIN Orders
-ON Customers.cust_id = Orders.cust_id;
-```
-
-customers 表:
-
-| cust_id | cust_name |
-| :---: | :---: |
-| 1 | a |
-| 2 | b |
-| 3 | c |
-
-orders 表:
-
-| order_id | cust_id |
-| :---: | :---: |
-|1 | 1 |
-|2 | 1 |
-|3 | 3 |
-|4 | 3 |
-
-结果:
-
-| cust_id | cust_name | order_id |
-| :---: | :---: | :---: |
-| 1 | a | 1 |
-| 1 | a | 2 |
-| 3 | c | 3 |
-| 3 | c | 4 |
-| 2 | b | Null |
-
-# 十六、组合查询
-
-使用 **UNION** 来组合两个查询,如果第一个查询返回 M 行,第二个查询返回 N 行,那么组合查询的结果一般为 M+N 行。
-
-每个查询必须包含相同的列、表达式和聚集函数。
-
-默认会去除相同行,如果需要保留相同行,使用 UNION ALL。
-
-只能包含一个 ORDER BY 子句,并且必须位于语句的最后。
-
-```sql
-SELECT col
-FROM mytable
-WHERE col = 1
-UNION
-SELECT col
-FROM mytable
-WHERE col =2;
-```
-
-# 十七、视图
-
-视图是虚拟的表,本身不包含数据,也就不能对其进行索引操作。
-
-对视图的操作和对普通表的操作一样。
-
-视图具有如下好处:
-
-- 简化复杂的 SQL 操作,比如复杂的连接;
-- 只使用实际表的一部分数据;
-- 通过只给用户访问视图的权限,保证数据的安全性;
-- 更改数据格式和表示。
-
-```sql
-CREATE VIEW myview AS
-SELECT Concat(col1, col2) AS concat_col, col3*col4 AS compute_col
-FROM mytable
-WHERE col5 = val;
-```
-
-# 十八、存储过程
-
-存储过程可以看成是对一系列 SQL 操作的批处理。
-
-使用存储过程的好处:
-
-- 代码封装,保证了一定的安全性;
-- 代码复用;
-- 由于是预先编译,因此具有很高的性能。
-
-命令行中创建存储过程需要自定义分隔符,因为命令行是以 ; 为结束符,而存储过程中也包含了分号,因此会错误把这部分分号当成是结束符,造成语法错误。
-
-包含 in、out 和 inout 三种参数。
-
-给变量赋值都需要用 select into 语句。
-
-每次只能给一个变量赋值,不支持集合的操作。
-
-```sql
-delimiter //
-
-create procedure myprocedure( out ret int )
- begin
- declare y int;
- select sum(col1)
- from mytable
- into y;
- select y*y into ret;
- end //
-
-delimiter ;
-```
-
-```sql
-call myprocedure(@ret);
-select @ret;
-```
-
-# 十九、游标
-
-在存储过程中使用游标可以对一个结果集进行移动遍历。
-
-游标主要用于交互式应用,其中用户需要对数据集中的任意行进行浏览和修改。
-
-使用游标的四个步骤:
-
-1. 声明游标,这个过程没有实际检索出数据;
-2. 打开游标;
-3. 取出数据;
-4. 关闭游标;
-
-```sql
-delimiter //
-create procedure myprocedure(out ret int)
- begin
- declare done boolean default 0;
-
- declare mycursor cursor for
- select col1 from mytable;
- # 定义了一个 continue handler,当 sqlstate '02000' 这个条件出现时,会执行 set done = 1
- declare continue handler for sqlstate '02000' set done = 1;
-
- open mycursor;
-
- repeat
- fetch mycursor into ret;
- select ret;
- until done end repeat;
-
- close mycursor;
- end //
- delimiter ;
-```
-
-# 二十、触发器
-
-触发器会在某个表执行以下语句时而自动执行:DELETE、INSERT、UPDATE。
-
-触发器必须指定在语句执行之前还是之后自动执行,之前执行使用 BEFORE 关键字,之后执行使用 AFTER 关键字。BEFORE 用于数据验证和净化,AFTER 用于审计跟踪,将修改记录到另外一张表中。
-
-INSERT 触发器包含一个名为 NEW 的虚拟表。
-
-```sql
-CREATE TRIGGER mytrigger AFTER INSERT ON mytable
-FOR EACH ROW SELECT NEW.col into @result;
-
-SELECT @result; -- 获取结果
-```
-
-DELETE 触发器包含一个名为 OLD 的虚拟表,并且是只读的。
-
-UPDATE 触发器包含一个名为 NEW 和一个名为 OLD 的虚拟表,其中 NEW 是可以被修改的,而 OLD 是只读的。
-
-MySQL 不允许在触发器中使用 CALL 语句,也就是不能调用存储过程。
-
-# 二十一、事务管理
-
-基本术语:
-
-- 事务(transaction)指一组 SQL 语句;
-- 回退(rollback)指撤销指定 SQL 语句的过程;
-- 提交(commit)指将未存储的 SQL 语句结果写入数据库表;
-- 保留点(savepoint)指事务处理中设置的临时占位符(placeholder),你可以对它发布回退(与回退整个事务处理不同)。
-
-不能回退 SELECT 语句,回退 SELECT 语句也没意义;也不能回退 CREATE 和 DROP 语句。
-
-MySQL 的事务提交默认是隐式提交,每执行一条语句就把这条语句当成一个事务然后进行提交。当出现 START TRANSACTION 语句时,会关闭隐式提交;当 COMMIT 或 ROLLBACK 语句执行后,事务会自动关闭,重新恢复隐式提交。
-
-通过设置 autocommit 为 0 可以取消自动提交;autocommit 标记是针对每个连接而不是针对服务器的。
-
-如果没有设置保留点,ROLLBACK 会回退到 START TRANSACTION 语句处;如果设置了保留点,并且在 ROLLBACK 中指定该保留点,则会回退到该保留点。
-
-```sql
-START TRANSACTION
-// ...
-SAVEPOINT delete1
-// ...
-ROLLBACK TO delete1
-// ...
-COMMIT
-```
-
-# 二十二、字符集
-
-基本术语:
-
-- 字符集为字母和符号的集合;
-- 编码为某个字符集成员的内部表示;
-- 校对字符指定如何比较,主要用于排序和分组。
-
-除了给表指定字符集和校对外,也可以给列指定:
-
-```sql
-CREATE TABLE mytable
-(col VARCHAR(10) CHARACTER SET latin COLLATE latin1_general_ci )
-DEFAULT CHARACTER SET hebrew COLLATE hebrew_general_ci;
-```
-
-可以在排序、分组时指定校对:
-
-```sql
-SELECT *
-FROM mytable
-ORDER BY col COLLATE latin1_general_ci;
-```
-
-# 二十三、权限管理
-
-MySQL 的账户信息保存在 mysql 这个数据库中。
-
-```sql
-USE mysql;
-SELECT user FROM user;
-```
-
-**创建账户**
-
-新创建的账户没有任何权限。
-
-```sql
-CREATE USER myuser IDENTIFIED BY 'mypassword';
-```
-
-**修改账户名**
-
-```sql
-RENAME myuser TO newuser;
-```
-
-**删除账户**
-
-```sql
-DROP USER myuser;
-```
-
-**查看权限**
-
-```sql
-SHOW GRANTS FOR myuser;
-```
-
-**授予权限**
-
-账户用 username@host 的形式定义,username@% 使用的是默认主机名。
-
-```sql
-GRANT SELECT, INSERT ON mydatabase.* TO myuser;
-```
-
-**删除权限**
-
-GRANT 和 REVOKE 可在几个层次上控制访问权限:
-
-- 整个服务器,使用 GRANT ALL 和 REVOKE ALL;
-- 整个数据库,使用 ON database.\*;
-- 特定的表,使用 ON database.table;
-- 特定的列;
-- 特定的存储过程。
-
-```sql
-REVOKE SELECT, INSERT ON mydatabase.* FROM myuser;
-```
-
-**更改密码**
-
-必须使用 Password() 函数
-
-```sql
-SET PASSWROD FOR myuser = Password('new_password');
-```
-
-# 参考资料
-
-- BenForta. SQL 必知必会 [M]. 人民邮电出版社, 2013.
diff --git "a/docs/DataBase/5_LeetCode_Database351円242円230円350円247円243円.md" "b/docs/DataBase/5_LeetCode_Database351円242円230円350円247円243円.md"
index c3d52b50..0f3123f8 100644
--- "a/docs/DataBase/5_LeetCode_Database351円242円230円350円247円243円.md"
+++ "b/docs/DataBase/5_LeetCode_Database351円242円230円350円247円243円.md"
@@ -422,15 +422,15 @@ ON p.PersonId=a.PersonId;
* 内连接:返回两张表的交集部分。
-
+
* 左连接:
-
+
* 右连接:
-
+
# *8、超过经理收入的员工(181)
diff --git a/docs/JVM/1_JVM.md b/docs/JVM/1_JVM.md
index bb466f50..3ec65d58 100644
--- a/docs/JVM/1_JVM.md
+++ b/docs/JVM/1_JVM.md
@@ -164,7 +164,7 @@ Class 文件中的常量池(编译器生成的字面量和符号引用)会
除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()。
-直接内存
+## 直接内存
在 JDK 1.4 中新引入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在堆内存和堆外内存来回拷贝数据。
diff --git "a/docs/JVM/1_Java345円206円205円345円255円230円345円214円272円345円237円237円.md" "b/docs/JVM/1_Java345円206円205円345円255円230円345円214円272円345円237円237円.md"
new file mode 100644
index 00000000..ae76568a
--- /dev/null
+++ "b/docs/JVM/1_Java345円206円205円345円255円230円345円214円272円345円237円237円.md"
@@ -0,0 +1,458 @@
+# Java 运行数数据区域
+
+Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。
+
+JDK 1.8 和之前的版本略有不同:
+
+- JDK 1.7 之前**运行时常量池**逻辑包含**字符串常量池**存放在方法区
+- JDK 1.7 字符串常量池被从方法区中拿到了堆,运行时常量池剩下的内容还在方法区
+- JDK1.8 HotSpot 虚拟机**移除了永久代**,采用**元空间(Metaspace)** 代替方法区,这时候**字符串常量池还在堆**,运行时常量池还在方法区,只不过方法区的实现从永久代变成了元空间。
+
+
+
+## 程序计数器
+
+程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的**行号指示器(逻辑上)**。主要有以下两个作用:
+
+- 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
+- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
+
+此外,程序计数器还有如下特性:
+
+- 通过改变计数器的值来选取下一条需要执行的字节码指令
+- 和线程一对一的关系,即"线程私有"
+- 对 Java 方法计数,如果是 Native 方法则计数器值为 Undefined
+- 只是计数,不会发生内存泄漏,生命周期随着线程的创建而创建,随着线程的结束而死亡。
+
+## Java 虚拟机栈
+
+每个 Java 方法在执行的同时会创建一个栈帧用于存储**局部变量表**、**操作数栈**、动态链接、方法出口信息等。
+
+
+
+从方法调用直至执行完成的过程,就对应着**一个栈帧在 Java 虚拟机栈中入栈和出栈的过程**。Java 方法有两种返回方式:
+
+- return 语句
+- 抛出异常
+
+不管使用哪种返回方式都会导致栈帧被弹出。
+
+可以通过 -Xss 这个虚拟机参数来指定每个线程的 Java 虚拟机栈内存大小:
+
+```java
+java -Xss512M HackTheJava
+```
+
+该区域可能抛出以下异常:
+
+- 当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常;
+- 栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。
+
+注意:**HotSpot 虚拟机的栈容量不可以进行动态扩展的**,所以在 HotSpot 虚拟机是不会由于虚拟机栈无法扩展而导致 OOM 的,但是如果申请时就失败,仍然会出现 OOM 异常。
+
+### 局部变量表
+
+局部变量表主要存放了编译期可知的各种数据类型(boolean、byte、char、short、int、float、long、double)和对象引用。
+
+### 操作数栈
+
+- 局部变量表:包含方法执行过程中的所有变量
+- 操作数栈:入栈、出栈、复制、交换、产生消费变量
+
+通过 javap 命令分析 Java 汇编指令,感受操作数栈和局部变量表的关系。
+
+定义测试类:该类中定义了一个静态方法 add()
+
+```java
+public class JVMTest {
+ public static int add(int a ,int b) {
+ int c = 0;
+ c = a + b;
+ return c;
+ }
+}
+```
+
+使用 javap 命令(javap 分析的是字节码文件)
+
+```html
+javap -verbose JVMTest
+```
+
+得到如下汇编指令:
+
+```html
+ public static int add(int, int);
+ descriptor: (II)I
+ flags: ACC_PUBLIC, ACC_STATIC
+ Code:
+ stack=2, locals=3, args_size=2
+ 0: iconst_0
+ 1: istore_2
+ 2: iload_0
+ 3: iload_1
+ 4: iadd
+ 5: istore_2
+ 6: iload_2
+ 7: ireturn
+ LineNumberTable:
+ line 5: 0
+ line 6: 2
+ line 7: 6
+```
+
+解读上述指令:
+
+- stack = 2 说明栈的深度是 2 ;
+- locals = 3 说明有 3 个本地变量 ;
+- args_size = 2 说明该方法需传入 2 个参数
+- load 指令表示入操作数栈,store 表示出操作数栈
+
+执行 add(1,2),说明局部变量表和操作数栈的关系:
+
+- 首先会将栈帧按照程序计数器指令从大到小依次入栈,栈帧按照程序计数器指令依次出栈。
+- 数据 1、2 是入参,已经存在局部变量表 0、1 位置
+- 首先执行 iconst_0,将数据 0 push 进操作数栈
+- 执行 istore_2,将数据 0 pop出操作数栈并放入局部变量表中 2 位置
+- 执行 iload_0,将 0 位置元素(数值 1) push 进操作数栈
+- 执行 iload_1,将 1 位置元素(数值 2) push 进操作数栈
+- 执行 iadd,将数值1和数值2元素 pop出操作数栈,执行加法运算后,得到结果3,将 3 push 进操作数栈
+- 执行 istore_2,将数据 3 pop出操作数栈并放入局部变量表中 2 位置
+- 执行 iload_2,将 2 位置元素(数值 3)push 进操作数栈
+- 执行 ireturn,返回操作数栈栈顶元素
+
+
+
+
+## 本地方法栈
+
+本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
+
+本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。
+
+本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
+
+方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 。
+
+## 堆
+
+几乎所有的对象实例以及数组都在这里分配内存,是垃圾收集的主要区域,所以也被称作 **GC 堆(Garbage Collected Heap)**。
+
+现代的垃圾收集器基本都是**采用分代收集算法**,其主要的思想是针对不同类型的对象采取不同的垃圾回收算法。可以将堆分成两块:
+
+- 新生代 (Young Generation),新生代可以划分为 Eden 、From Survivor、To Survivor 空间
+- 老年代 (Old Generation)
+
+堆不需要连续内存,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。
+
+可以通过 -Xms 和 -Xmx 这两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。
+
+```java
+java -Xms1M -Xmx2M HackTheJava
+```
+
+JDK 7 版本及 JDK 7 版本之前,Hotspot 虚拟机的堆结构如下:
+
+- 新生代 (Young Generation)
+- 老年代 (Old Generation)
+- 永久代 (Permanent Generation)
+
+
+
+JDK 8 版本之后 HotSpot 虚拟机的永久代被彻底移除了,取而代之是元空间,元空间使用的是直接内存。
+
+
+
+## 堆和栈的关系
+
+Java 内存可以粗糙的区分为堆内存(Heap)和栈内存 (Stack),其中**栈就是现在说的虚拟机栈**,或者说是虚拟机栈中局部变量表部分。引用对象、数组时,栈里定义变量保存堆中目标的首地址。
+
+栈和堆的区别:
+
+- 物理地址
+
+ 堆的物理内存分配是不连续的;
+
+ 栈的物理内存分配是连续的
+
+- 分配内存
+
+ 堆是不连续的,分配的内存是在运行期确定的,大小不固定;
+
+ 栈是连续的,分配的内存在编译器就已经确定,大小固定
+
+- 存放内容
+
+ 堆中存放的是对象和数组,关注的是数据的存储;
+
+ 栈中存放局部变量,关注的是程序方法的执行
+
+- 是否线程私有
+
+ 堆内存中的对象对所有线程可见,可被所有线程访问;
+
+ 栈内存属于某个线程私有的
+
+- 异常
+
+ 栈扩展失败,会抛出 StackOverflowError;
+
+ 堆内存不足,会抛出 OutOfMemoryError
+
+## 方法区
+
+用于存放已被加载的**类信息**、**常量**、**静态变量**、**即时编译器编译后的代码**等数据。
+
+和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。
+
+对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。
+
+HotSpot 虚拟机把它当成永久代来进行垃圾回收。但很难确定永久代的大小,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。
+
+### 方法区与永久代
+
+**方法区只是一个 JVM 规范**,在不同的 JVM 上方法区的实现可能是不同的。
+
+方法区和永久代的关系类似 Java 中接口和类的关系,类实现了接口,**永久代就是 HotSpot 虚拟机对 JVM 规范中方法区的一种实现方式**。
+
+方法区是 JVM 规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久代这一说法。
+
+### 元空间与永久代
+
+**方法区只是一个 JVM 规范,永久代与元空间都是其一种实现方式**。在 JDK 1.8 之后,原来永久代的数据被分到了堆和元空间中:**元空间存储类的元信息,静态变量和常量池等则放入堆中**。
+
+元空间与永久代的最大区别在于:元空间使用本地内存,而永久代使用 JVM 的内存,元空间相比永久代具有如下优势:
+
+- 永久代存在一个 JVM 本身设置的固定大小上限,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小。当元空间溢出时会得到如下错误: `java.lang.OutOfMemoryError: MetaSpace` 可以使用 `-XX:MaxMetaspaceSize` 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。
+- 元空间里面存放的是类的元数据,这样加载多少类的元数据就不由 `MaxPermSize` 控制了, 而由系统的实际可用空间来控制,可以加载更多的类。
+
+
+
+## 运行时常量池
+
+运行时常量池是方法区的一部分。
+
+Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池表(用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中)。
+
+运行时常量池相对于 Class 文件常量池的另外一个重要特征是具备动态性,Java 语言并不要求常量
+一定只有编译期才能产生,运行期间也可以将新的常量放入池中,例如 String 类的 intern()。
+
+
+
+## 字符串常量池
+
+JDK 1.7 之前**运行时常量池**逻辑包含**字符串常量池**存放在方法区。
+
+JDK 1.7 **字符串常量池被单独拿到堆**,运行时常量池剩下的内容还在方法区。
+
+JDK1.8 HotSpot 虚拟机**移除了永久代**,采用**元空间(Metaspace)** 代替方法区,这时候**字符串常量池还在堆**,运行时常量池还在方法区,只不过方法区的实现从永久代变成了元空间。
+
+### String 对象创建方式
+
+创建方式 1:
+
+```java
+String str1 = "abcd";
+```
+
+创建方式 2:
+
+```java
+String str2 = new String("abcd");
+```
+
+这两种不同的创建方法是有差别的:
+
+方式 1 是在**常量池**中获取对象("abcd" 属于字符串字面量,因此编译时期会在常量池中创建一个字符串对象)。
+
+方式 2 会创建两个字符串对象(前提是常量池中还没有 "abcd" 字符串对象):
+
+- "abcd" 属于字符串字面量,因此编译时期会在常量池中创建一个字符串对象,该字符串对象指向这个 "abcd" 字符串字面量;
+- 使用 new 的方式会在堆中创建一个字符串对象。
+
+(**字符串常量"abcd"在编译期**就已经确定放入常量池,而 Java **堆上的"abcd"是在运行期**初始化阶段才确定)。
+
+**str1 指向常量池中的 "abcd" 对象,而 str2 指向堆中的字符串对象。**
+
+### String 的 intern() 方法
+
+String 的 intern() 是一个 **Native 方法**,当调用 intern() 方法时:
+
+- 如果运行时常量池中已经包含一个等于该 String 对象内容的字符串,则返回常量池中该字符串的引用。
+- 如果没有等于该 String 对象的字符串,JDK1.7 之前(不包含 1.7)是在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用,**JDK1.7 以及之后的处理方式是对于存在堆中的对象,在常量池中直接保存对象的引用,而不会重新创建对象。**
+
+```java
+String s3 = new String("1") + new String("1");
+s3.intern();
+String s4 = "11";
+System.out.println(s3 == s4);
+```
+
+JDK 6 输出结果:
+
+```html
+false
+```
+
+JDK 8 输出结果:
+
+```html
+true
+```
+
+补充:[String 的 intern() 方法详解](https://www.cnblogs.com/wxgblogs/p/5635099.html)
+
+### 字符串拼接问题
+
+```java
+String str1 = "hello";
+String str2 = "world";
+
+String str3 = "hello" + "world";//常量池中的对象
+String str4 = str1 + str2; //在堆上创建的新的对象
+String str5 = "helloworld";//常量池中的对象
+
+System.out.println(str3 == str4);
+System.out.println(str3 == str5);
+System.out.println(str4 == str5);
+```
+
+输出结果如下:
+
+```html
+false
+true
+false
+```
+
+str1、str2 是从字符串常量池中获取的对象。
+
+对于 str3,字符串 "hello" 和字符串 "world" 相加有后得到 "helloworld",在字符串常量池中创建 "helloworld" 对象。
+
+对于 str4,str1+str2 则会在堆中创建新的 "helloworld" 对象。
+
+对于 str5,字符串常量池已有 "helloworld" 对象,str5 直接引用该对象。
+
+
+
+所以,尽量避免多个字符串拼接,因为这样会重新创建新的对象。如果需要改变字符串的话,可以使用 StringBuilder 或者 StringBuffer。
+
+## 直接内存
+
+在 JDK 1.4 中新引入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在堆内存和堆外内存来回拷贝数据。
+
+
+
+# Hotpot 虚拟机对象
+
+## 对象的内存布局
+
+在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:
+
+- 对象头
+- 实例数据
+- 对齐填充
+
+
+
+### 对象头
+
+Hotspot 虚拟机的对象头包括两部分信息:
+
+一部分用于存储对象自身的运行时数据(哈希码、GC分代年龄、锁状态标志等等);
+
+另一部分是类型指针,即对象指向它的**类元数据的指针**,虚拟机通过这个指针来**确定这个对象是哪个类的实例**。
+
+### 实例数据
+
+实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。
+
+### 对齐填充
+
+对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起**占位**作用。
+
+因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
+
+
+
+## 对象的创建
+
+Java 对象的创建过程分为以下5步:
+
+- 类加载检查
+- 分配内存
+- 初始化零值
+- 设置对象头
+- 执行 \ 方法
+
+### 1. 类加载检查
+
+虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的**符号引用**,
+并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。
+如果没有,那必须先执行相应的类加载过程。
+
+### 2. 分配内存
+
+在类加载检查通过后,接下来虚拟机将为新生对象分配内存。
+对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 "指针碰撞" 和 "空闲列表" 两种,选择那种分配方式由 Java 堆是否规整决定。
+
+ Java 堆内存是否规整,则取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,"复制算法"内存也是规整的。
+
+#### 两种内存分配方式
+
+##### 指针碰撞
+
+- 原理:用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界值指针,只需要向着没用过的内存方向将指针移动一段与对象大小相等的距离。
+- 适用场景:堆内存规整(即没有内存碎片)的情况
+- GC(Garbage Collection)收集器:Serial、ParNew
+
+##### 空闲列表
+
+- 原理:虚拟机会维护一个列表,在该列表中记录哪些内存块是可用的,在分配的时候,找一块足够大的内存块划分给对象示例,然后更新列表记录
+- 适用场景:堆内存规整
+- GC(Garbage Collection)收集器:CMS
+
+#### 内存分配并发问题
+
+在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,
+作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:
+
+- CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用CAS配上失败重试的方式保证更新操作的原子性。
+- TLAB:每一个线程预先在Java堆中分配一块内存,称为**本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)**。哪个线程要分配内存,就在哪个线程的 TLAB 上分配,只有 TLAB 用完并分配新的 TLAB 时,才采用上述的 CAS 进行内存分配。
+
+### 3. 初始化零值
+
+内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作**保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用**,程序能访问到这些字段的数据类型所对应的零值。
+
+### 4. 设置对象头
+
+初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。
+另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
+
+### 5. 执行 init 方法
+
+在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,**\ 方法还没有执行,所有的字段都还为零**。所以一般来说,执行 new 指令之后会接着执行 \ 方法,把**对象按照程序员的意愿进行初始化**,这样一个真正可用的对象才算完全产生出来。
+
+
+
+## 对象的访问定位
+
+建立对象就是为了使用对象,我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。
+对象的访问方式视虚拟机的实现而定,目前主流的访问方式有两种:使用句柄、直接指针。
+
+### 使用句柄
+
+如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是**对象的句柄地址**,而句柄中包含了对象实例数据与类型数据各自的具体地址信息 。
+
+
+
+### 直接指针
+
+如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而 reference 中存储的直接就是**对象的地址**。
+
+
+
+这两种对象访问方式各有优势:
+
+- 使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 **reference 本身不需要修改**。
+- 使用直接指针访问方式最大的好处就是**速度快**,它节省了一次指针定位的时间开销。
\ No newline at end of file
diff --git a/docs/JavaBasics/2_String.md b/docs/JVM/2_String.md
similarity index 100%
rename from docs/JavaBasics/2_String.md
rename to docs/JVM/2_String.md
diff --git "a/docs/JVM/2_345円236円203円345円234円276円346円224円266円351円233円206円.md" "b/docs/JVM/2_345円236円203円345円234円276円346円224円266円351円233円206円.md"
new file mode 100644
index 00000000..020c1779
--- /dev/null
+++ "b/docs/JVM/2_345円236円203円345円234円276円346円224円266円351円233円206円.md"
@@ -0,0 +1,425 @@
+# 垃圾收集 (Garbage Collection,GC)
+
+垃圾收集主要是针对堆和方法区进行。程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后就会消失,因此不需要对这三个区域进行垃圾回收。
+
+## 判断一个对象是否可被回收
+
+堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。
+
+### 引用计数算法
+
+为对象添加一个引用计数器,当对象增加一个引用时计数器加 1,引用失效时计数器减 1。引用计数为 0 的对象可被回收。
+
+在两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致无法对它们进行回收。正是因为**循环引用**的存在,因此 Java 虚拟机不使用引用计数算法。
+
+```java
+public class Test {
+
+ public Object instance = null;
+
+ public static void main(String[] args) {
+ Test a = new Test();
+ Test b = new Test();
+ a.instance = b;
+ b.instance = a;
+ a = null;
+ b = null;
+ doSomething();
+ }
+}
+```
+
+在上述代码中,a 与 b 引用的对象实例互相持有了对象的引用,因此当我们把对 a 对象与 b 对象的引用去除之后,由于两个对象还存在互相之间的引用,导致两个 Test 对象无法被回收。
+
+- 优点:执行效率高,程序执行受影响较小。
+- 缺点:无法检测出循环引用的情况,引起内存泄漏。
+
+### 可达性分析算法
+
+通过判断对象的引用链是否可达来决定对象是否可以被回收。
+
+以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。
+
+
+
+**Java 虚拟机使用可达性分析算法来判断对象是否可被回收**,GC Roots 一般包含以下几种:
+
+- 虚拟机栈中局部变量表中引用的对象(栈帧中的本地方法变量表)
+- 本地方法栈中 JNI(Native方法) 中引用的对象
+- 方法区中类静态属性引用的对象
+- 方法区中的常量引用的对象
+- 活跃线程的引用对象
+
+
+
+### 方法区的回收
+
+因为方法区主要存放永久代对象,而永久代对象的回收率比新生代低很多,所以在方法区上进行回收性价比不高。
+
+主要是对常量池的回收和对类的卸载。
+
+为了避免内存溢出,在大量使用反射和动态代理的场景都需要虚拟机具备类卸载功能。
+
+类的卸载条件很多,需要满足以下三个条件,并且满足了条件也不一定会被卸载:
+
+- 该类所有的实例都已经被回收,此时堆中不存在该类的任何实例。
+- 加载该类的 ClassLoader 已经被回收。
+- 该类对应的 Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。
+
+
+
+### finalize()
+
+类似 C++ 的析构函数(注意:只是类似,C++ 析构函数调用确定,而 finalize() 方法是不确定的),用于关闭外部资源。但是 try-finally 等方式可以做得更好,并且该方法运行代价很高,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。
+
+当垃圾回收器要宣告一个对象死亡时,至少要经历两次标记过程。
+
+如果对象在进行可达性分析以后,没有与GC Root 直接相连接的引用量,就会被**第一次标记**,并且判断是否执行 finalize() 方法;
+
+如果这个对象覆盖了 finalize() 方法,并且未被引用,就会被放置于 F-Queue 队列,稍后由虚拟机创建的一个低优先级的 finalize() 线程去执行**触发 finalize() 方法**,在该方法中让对象重新被引用,从而实现自救。但是该线程的优先级比较低,执行过程随时可能会被终止。此外,自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会再调用该方法。
+
+
+
+### 引用类型
+
+无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判定对象是否可被回收都与引用有关。
+
+JDK1.2 之前,Java 中引用定义:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用。
+
+JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)。
+
+#### 强引用 (Strong Reference)
+
+被强引用关联的对象不会被回收。
+
+使用 new 一个新对象的方式来创建强引用。
+
+```java
+Object obj = new Object();
+```
+
+当内存空间不足,JVM 抛出 OOM Error 终止程序也不会回收具有强引用的对象,只有通过将对象设置为 null 来弱化引用,才能使其被回收。
+
+#### 软引用 (Soft Reference)
+
+表示对象处在**有用但非必须**的状态。
+
+被软引用关联的对象只有在内存不够的情况下才会被回收。可以用来实现内存敏感的高速缓存。
+
+软引用可以和一个引用队列 ReferenceQueue 联合使用,如果软引用所引用的对象被垃圾回收,JVM 就会把这个软引用加入到与之关联的引用队列中。如果一个弱引用对象本身在引用队列中,就说明该引用对象所指向的对象被回收了。
+
+使用 SoftReference 类来创建软引用。
+
+```java
+Object obj = new Object();
+SoftReference sf = new SoftReference(obj);
+obj = null; // 使对象只被软引用关联
+```
+
+#### 弱引用 (Weak Reference)
+
+表示非必须的对象,比软引用更弱一些。适用于偶尔被使用且不影响垃圾收集的对象。
+
+被弱引用关联的对象一定会被回收,也就是说它只能存活到下一次垃圾回收发生之前。
+
+弱引用可以和一个引用队列 ReferenceQueue 联合使用,如果弱引用所引用的对象被垃圾回收,JVM 就会把这个弱引用加入到与之关联的引用队列中。如果一个弱引用对象本身在引用队列中,就说明该引用对象所指向的对象被回收了。
+
+使用 WeakReference 类来创建弱引用。
+
+```java
+Object obj = new Object();
+WeakReference wf = new WeakReference(obj);
+obj = null;
+```
+
+#### 虚引用 (Phantom Reference)
+
+又称为幽灵引用或者幻影引用,一个对象是否有虚引用的存在,不会对其生存时间造成影响,也无法通过虚引用得到一个对象。
+
+不会决定对象的生命周期,任何时候都可能被垃圾回收器回收。**必须和引用队列 ReferenceQueue 联合使用**。
+
+为一个对象设置虚引用的唯一目的是**能在这个对象被回收时收到一个系统通知,起哨兵作用**。具体来说,就是通过判断引用队列 ReferenceQueue 是否加入虚引用来判断被引用对象是否被 GC(垃圾回收线程) 回收:当 GC 准备回收一个对象时,如果发现它还仅有虚引用指向它,就会在回收该对象之前,把这个虚引用加入到与之关联的引用队列 ReferenceQueue 中。**如果一个虚引用对象本身就在引用队列中,就说明该引用对象所指向的对象被回收了**。
+
+使用 PhantomReference 来创建虚引用。
+
+```java
+Object obj = new Object();
+ReferenceQueue queue = new ReferenceQueue();
+// 虚引用必须和引用队列 ReferenceQueue 联合使用
+PhantomReference pf = new PhantomReference(obj, queue);
+obj = null;
+```
+
+总结:
+
+| 引用类型 | 被垃圾回收的时间 | 用途 | 生存时间 |
+| -------- | ---------------- | -------------- | ----------------- |
+| 强引用 | 从来不会 | 对象的一般状态 | JVM停止运行时终止 |
+| 软引用 | 在内存不足的时候 | 对象缓存 | 内存不足时终止 |
+| 弱引用 | 在垃圾回收的时候 | 对象缓存 | GC运行后终止 |
+| 虚引用 | Unknown | 标记、哨兵 | Unknown |
+
+
+
+## 垃圾收集算法
+
+### "标记-清除" 算法
+
+
+
+在标记阶段,从根集合进行扫描,会检查每个对象是否为活动对象,如果是活动对象,则程序会在对象头部打上标记。
+
+在清除阶段,会进行对象回收并取消标志位,另外,还会判断回收后的分块与前一个空闲分块是否连续,若连续,会合并这两个分块。回收对象就是把对象作为分块,连接到被称为 "空闲链表" 的单向链表,之后进行分配时只需要遍历这个空闲链表,就可以找到分块。
+
+在分配时,程序会搜索空闲链表寻找空间大于等于新对象大小 size 的块 block。如果它找到的块等于 size,会直接返回这个分块;如果找到的块大于 size,会将块分割成大小为 size 与 (block - size) 的两部分,返回大小为 size 的分块,并把大小为 (block - size) 的块返回给空闲链表。
+
+不足:
+
+- 标记和清除过程效率都不高;
+- 会产生大量不连续的内存碎片,导致无法给大对象分配内存。
+
+### "标记-整理" 算法
+
+
+
+标记过程仍然与"标记-清除"算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
+
+优点:不会产生内存碎片
+
+不足:需要移动大量对象,处理效率比较低。
+
+### "复制" 算法
+
+
+
+将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。
+
+主要不足是只使用了内存的一半。
+
+现在的商业虚拟机都采用这种收集算法回收新生代,但是并不是划分为大小相等的两块,而是一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 和其中一块 Survivor。在回收时,将 Eden 和 Survivor 中还存活着的对象全部复制到另一块 Survivor 上,最后清理 Eden 和使用过的那一块 Survivor。
+
+HotSpot 虚拟机的 Eden 和 Survivor 大小比例默认为 8:1,保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 就不够用了,此时需要依赖于老年代进行空间分配担保,也就是借用老年代的空间存储放不下的对象。
+
+### 分代收集
+
+现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。
+
+一般将堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法:
+
+- 新生代:新生代对象存活时间很短,所以可以选择**"复制"算法**,只需要付出少量对象的复制成本就可以完成每次垃圾收集。
+- 老年代:老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择**"标记-清除"或"标记-整理"算法**进行垃圾收集。
+
+## Stop-the-World & SafePoint
+
+### Stop-the-World
+
+所谓 Stop-the-World(简称 STW),指的是 **JVM 由于要执行 GC 而停止了应用程序的执行** :
+
+- 可达性分析算法中 GC Roots 会导致所有 Java 执行线程停顿,原因如下:
+ - 分析工作必须在一一个能确保一致性的快照中进行
+ - 一致性指整个分析期间整个执行系统看起来像被冻结在某个时间点上
+ - 如果出现分析过程中对象引用关系还在不断变化,则分析结果的准确性无法保证
+- 被 STW 中断的应用程序线程会在完成 GC 之后恢复,频繁中断会让用户感觉像是网速不快造成电影卡带一样, 影响用户体验,所以需要减少 STW 的发生。
+
+STW 事件和采用哪款垃圾收集器无关,所有的 GC 都有这个事件。哪怕是 G1 也不能完全避免 STW 情况发生,只能说垃圾回收器越来越优秀,回收效率越来越高,尽可能地缩短了暂停时间。
+
+STW 是 JVM 在后台自动发起和自动完成的。在用户不可见的情况下,把用户正常的工作线程全部停掉。开发中不要用 System.gc() 这样会导致 STW 的发生。
+
+目前,降低系统的停顿时间两种算法:增量收集算法和分区算法。
+
+#### 增量收集算法
+
+基本思想:如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。**垃圾收集线程一次只收集一小片区域的内存空间,接着切换到应用程序线程**。依次反复,直到垃圾收集完成。总的来说,增量收集算法的基础仍是传统的标记-清除和复制算法。增量收集算法通过对线程间冲突的妥善处理,允许垃圾收集线程以分阶段的方式完成标记、清理或复制工作。
+
+不足:由于在垃圾回收过程中,间断性地还执行了应用程序代码,所以能减少系统的停顿时间。但是因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。
+
+#### 分区算法
+
+基本思想:一般来说,在相同条件下,堆空间越大,一次 GC 时所需要的时间就长,有关 GC 产生的停顿也越长。为了更好地控制 GC 产生的停顿时间,**将一块大的内存区域分割成多个小块**,根据目标的停顿时间,每次合理地回收若干个小区间,而不是整个堆空间,从而减少一次 GC 所产生的停顿。
+
+注意分区算法与分代收集算法是不同的:分代收集算法将按照对象的生命周期长短划分成两个部分,而分区算法将整个堆空间划分成**连续的不同小区间**,其中每个小区间都独立使用,独立回收,这样可以控制一次回收多少个小区间。
+
+总结:
+
+- 增量收集算法是将总的收集量一部分一部分的去执行
+- 分区算法是将总的内存空间分为小分区,一次可控的去收集多少个小区间。
+
+### SafePoint
+
+程序执行时并非可以在任何地方都能停顿下来开始 GC,只有在特定的位置才能停顿下来开始 GC,这些位置称为Safepoint 。
+
+SafePoint 的选择很重要,如果太少可能导致 GC 等待的时间太长,如果太频繁可能导致运行时的性能问题。
+
+大部分指令的执行时间都非常短暂,通常会根据是否具有让程序长时间执行的特征为标准。比如选择一些执行时间较长的指令作为 SafePoint,如方法调用、循环跳转和异常跳转等。
+
+
+
+## 垃圾收集器
+
+### 评估 GC 的性能指标
+
+#### 吞吐量
+
+运行用户代码的时间占总运行时间的比例。其中总运行时间 = 程序的运行时间 + 内存回收的时间。
+
+比如:虚拟机总共运行了100分钟, 其中垃圾收集花掉1分钟,那吞吐量就是99%。
+
+#### 暂停时间
+
+执行垃圾收集时,程序的工作线程被暂停的时间。
+
+"高吞吐量" 和 "低暂停时间" 是矛盾的:
+
+- 如果选择以吞吐量优先,那么必然需要降低内存回收的执行频率,但是这样会导致 GC 需要更长的暂停时间来执行内存回收。
+- 如果选择以低延迟优先为原则,那么为了降低每次执行内存回收时的暂停时间,也只能频繁地执行内存回收,但这又引起了年轻代内存的缩减和导致程序吞吐量的下降。
+
+**现在标准:在最大吞吐量优先的情况下,降低停顿时间**。
+
+
+
+
+
+### 经典垃圾收集器
+
+**如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。**
+
+虽然我们对各个收集器进行比较,但并非要挑选出一个最好的收集器。因为直到现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,我们能做的就是**根据具体应用场景选择适合自己的垃圾收集器**。
+
+
+
+以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。 JDK 9 取消了对 Serial+CMS、
+ParNew+Serial Old 这两个组合的支持。
+
+- 单线程与多线程:单线程指的是垃圾收集器只使用一个线程,而多线程使用多个线程;
+- 串行与并行:串行指的是垃圾收集器与用户程序交替执行,这意味着在执行垃圾收集的时候需要停顿用户程序;并行指的是垃圾收集器和用户程序同时执行。除了 CMS 和 G1 之外,其它垃圾收集器都是以串行的方式执行。
+
+#### 1. Serial 收集器
+
+
+
+Serial 翻译为串行,也就是说它以串行的方式执行。
+
+它是单线程的收集器,只会使用一个线程进行垃圾收集工作。
+
+它的优点是简单高效,在单个 CPU 环境下,由于没有线程交互的开销,因此拥有最高的单线程收集效率。
+
+它是 Client 场景下的默认新生代收集器,因为在该场景下内存一般来说不会很大。它收集一两百兆垃圾的停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿时间是可以接受的。
+
+#### 2. ParNew 收集器
+
+
+
+它是 Serial 收集器的多线程版本。
+
+它是 Server 场景下默认的新生代收集器,除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合使用。
+
+#### 3. Parallel Scavenge 收集器
+
+与 ParNew 一样是多线程收集器。
+
+其它收集器目标是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是达到一个可控制的吞吐量,因此**它被称为"吞吐量优先"收集器**。这里的吞吐量指 CPU 用于运行用户程序的时间占总时间的比值。
+
+停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。
+
+缩短停顿时间是以牺牲吞吐量和新生代空间来换取的:新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。
+
+可以通过一个开关参数打开 GC 自适应的调节策略(GC Ergonomics),就不需要手工指定新生代的大小(-Xmn)、Eden 和 Survivor 区的比例、晋升老年代对象年龄等细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。
+
+#### 4. Serial Old 收集器
+
+
+
+是 Serial 收集器的老年代版本,也是给 Client 场景下的虚拟机使用。如果用在 Server 场景下,它有两大用途:
+
+- 在 JDK 1.5 以及之前版本(Parallel Old 诞生以前)中与 Parallel Scavenge 收集器搭配使用。
+- 作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。
+
+#### 5. Parallel Old 收集器
+
+
+
+是 Parallel Scavenge 收集器的老年代版本。
+
+**在注重吞吐量以及 CPU 资源敏感的场合**,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。
+
+#### 6. CMS 收集器
+
+
+
+CMS(Concurrent Mark Sweep),Mark Sweep 指的是标记 - 清除算法。
+
+分为以下六个流程:
+
+- 初始标记:仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿。
+- 并发标记:进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿。
+- 并发预清理:查找执行并发标记阶段从年轻代晋升到老年代的对象
+- **重新标记**:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿。
+- 并发清除:清理垃圾对象,不需要停顿。
+- 并发重置:重置CMS收集器的数据结构,等待下一次垃圾回收。
+
+在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿。
+
+CMS 具有以下缺点:
+
+- 吞吐量低:低停顿时间是以牺牲吞吐量为代价的,导致 CPU 利用率不够高。
+- 无法处理浮动垃圾,可能出现 Concurrent Mode Failure。浮动垃圾是指并发清除阶段由于用户线程继续运行而产生的垃圾,这部分垃圾只能到下一次 GC 时才能进行回收。由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure,这时虚拟机将临时启用 Serial Old 来替代 CMS。
+- 标记 - 清除算法导致的空间碎片,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前触发一次 Full GC。
+
+#### 7. G1 收集器
+
+G1(Garbage-First),它是一款面向服务端应用的垃圾收集器,在多 CPU 和大内存的场景下有很好的性能。HotSpot 开发团队赋予它的使命是未来可以替换掉 CMS 收集器。
+
+堆被分为新生代和老年代,其它收集器进行收集的范围都是整个新生代或者老年代,而 G1 可以直接对新生代和老年代一起回收。
+
+
+
+G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。
+
+通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个小空间可以单独进行垃圾回收。这种划分方法带来了很大的灵活性,使得可预测的停顿时间模型成为可能。通过记录每个 Region 垃圾回收时间以及回收所获得的空间(这两个值是通过过去回收的经验获得),并维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region,保证了 G1 在有限的时间内可以获取尽可能高的收集效率。这种方式的侧重点在于回收垃圾最大量的 Region),G1 (Garbage First) 因此得名。
+
+每个 Region 都有一个 Remembered Set,用来记录该 Region 对象的引用对象所在的 Region。通过使用 Remembered Set,在做可达性分析的时候就可以避免全堆扫描。
+
+
+
+如果不计算维护 Remembered Set 的操作,G1 收集器的运作大致可划分为以下几个步骤:
+
+- 初始标记
+- 并发标记
+- 最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。
+- 筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅度提高收集效率。
+
+与其他垃圾收集器相比,G1 具备如下特点:
+
+- 并行和并发
+
+ 并行性:G1 在回收期间,可以有多个 GC 线程同时工作,有效利用多核计算能力。此时用户线程STW
+
+ 并发性:G1 拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况
+
+- 分代收集
+ 将堆空间分为若干个区域(Region) ,这些区域中包含了逻辑上的年轻代和老年代。
+
+- 空间整合
+
+ G1将内存划分为一个个的 Region,内存的回收以 Region 作为基本单位。从局部(两个 Region 之间)上来看是基于"复制"算法,整体来看是基于"标记 - 整理"算法,这两种算法都可以避免内存碎片。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次 GC。 尤其是当 Java 堆非常大的时候,G1 的优势更加明显。
+
+- 可预测的停顿
+
+ G1 除了追求低停顿外(G1 可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制),还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过 N 毫秒。
+
+相较于CMS,G1还不具备全方位、压倒性优势:
+
+比如在用户程序运行过程中,G1 无论是为了垃圾收集产生的内存占用还是程序运行时的额外执行负载(overload) 都要比 CMS 要高。从使用经验上来说,对于小内存应用,CMS 的表现大概率会优于 G1,而 G1 在大内存应用上则发挥其优势。平衡点在6- 8GB之间。
+
+7 种经典垃圾收集器总结如下:
+
+| 垃圾收集器 | 运行状态 | 作用位置 | 收集算法 | 特点 | 适用场景 |
+| :----------: | :---------: | :------------: | :-----------------------: | :----------: | :--------------------------------: |
+| Serial | 串行 | 新生代 | "复制"算法 | 响应速度优先 | 单 CPU 下的 Client 模式 |
+| ParNew | 并行 | 新生代 | "复制"算法 | 响应速度优先 | 多 CPU 的 Server 模式配合 CMS 使用 |
+| Parallel | 并行 | 新生代 | "复制"算法 | 吞吐量优先 | 后台运算且不需要太多交互的场景 |
+| Serial Old | 串行 | 老年代 | "标记-整理"算法 | 响应速度优先 | 单 CPU 下的 Client 模式 |
+| Parallel Old | 并行 | 老年代 | "标记-整理"算法 | 吞吐量优先 | 后台运算且不需要太多交互的场景 |
+| CMS | 并发 | 老年代 | "标记-清除"算法 | 响应速度优先 | 互联网或 B/S 业务 |
+| G1 | 并发 + 并行 | 新生代+ 老年代 | "标记-整理" + "复制" 算法 | 响应速度优先 | 面向服务端应用 |
+
diff --git a/docs/JVM/6_JVM.md "b/docs/JVM/3_345円206円205円345円255円230円345円210円206円351円205円215円344円270円216円345円233円236円346円224円266円347円255円226円347円225円245円.md"
similarity index 81%
rename from docs/JVM/6_JVM.md
rename to "docs/JVM/3_345円206円205円345円255円230円345円210円206円351円205円215円344円270円216円345円233円236円346円224円266円347円255円226円347円225円245円.md"
index 2eae28e6..e1874468 100644
--- a/docs/JVM/6_JVM.md
+++ "b/docs/JVM/3_345円206円205円345円255円230円345円210円206円351円205円215円344円270円216円345円233円236円346円224円266円347円255円226円347円225円245円.md"
@@ -1,11 +1,18 @@
# 内存分配与回收策略
-[内存回收脑图](http://naotu.baidu.com/file/488e2f0745f7cfff1b03eb1c3d81fe3e?token=df2309819db31dde)
+## HotSpot 虚拟机 GC 分类
-## Minor GC 和 Full GC
+针对 HotSpot 虚拟机的实现,GC 可以分为 2 大类:
-- Minor GC:回收新生代,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速度一般也会比较快。
-- Full GC:回收老年代和新生代,老年代对象其存活时间长,因此 Full GC 很少执行,执行速度会比 Minor GC 慢很多。
+- **部分收集(Partial GC)**
+
+ - 新生代收集(**Minor GC** / Young GC):回收新生代,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速度一般也会比较快。
+ - 老年代收集(**Major GC** / Old GC):只对老年代进行垃圾收集。需要注意的是 Major GC 在有的语境中也用于指代 Full GC。只有 CMS 的并发清除存在这个模式
+ - 混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。只有 G1 存在这个模式
+
+- **整堆收集(Full GC)**
+
+ 收集整个 Java 堆,包括新生代、老年代和永久代(如果存在的话)。老年代对象其存活时间长,因此 Full GC 很少执行,执行速度会比 Minor GC 慢很多。
## 内存分配策略
@@ -66,11 +73,3 @@
### 5. Concurrent Mode Failure
执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足),便会报 Concurrent Mode Failure 错误,并触发 Full GC。
-
-## 常见的调优参数
-
-| 参数 | 解释 |
-| ------------------------ | ---------------------------------------------- |
-| -XX:SurvivorRatio | Eden和Survivor的比值,默认为8:1 |
-| -XX:NewRatio | 老年代和年轻代内存大小的比例 |
-| -XX:MaxTenuringThreshold | 对象从年轻代晋升到老年代经过的GC次数的最大阈值 |
\ No newline at end of file
diff --git "a/docs/JVM/4_JVM350円260円203円344円274円230円.md" "b/docs/JVM/4_JVM350円260円203円344円274円230円.md"
new file mode 100644
index 00000000..6624b20a
--- /dev/null
+++ "b/docs/JVM/4_JVM350円260円203円344円274円230円.md"
@@ -0,0 +1,3 @@
+# JVM 调优
+
+待补充。
\ No newline at end of file
diff --git "a/docs/JVM/5_347円261円273円346円226円207円344円273円266円347円273円223円346円236円204円.md" "b/docs/JVM/5_347円261円273円346円226円207円344円273円266円347円273円223円346円236円204円.md"
new file mode 100644
index 00000000..33dbf928
--- /dev/null
+++ "b/docs/JVM/5_347円261円273円346円226円207円344円273円266円347円273円223円346円236円204円.md"
@@ -0,0 +1,126 @@
+# 类文件结构
+
+## 类文件概述
+
+JVM 可以理解的代码就叫做**字节码**(即扩展名为 .class 的文件,即类文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。
+
+字节码并不针对一种特定的机器,因此 Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
+
+
+
+## 类文件结构
+
+根据 JVM 规范,Class 文件通过 ClassFile 定义:
+
+```c
+ClassFile {
+ u4 magic; // Class 文件的标志
+ u2 minor_version; // Class 的小版本号
+ u2 major_version; // Class 的大版本号
+ u2 constant_pool_count; // 常量池的数量
+ cp_info constant_pool[constant_pool_count-1]; // 常量池
+ u2 access_flags;// Class 的访问标记
+ u2 this_class; // 当前类
+ u2 super_class; // 父类
+ u2 interfaces_count; // 接口
+ u2 interfaces[interfaces_count]; // 一个类可以实现多个接口
+ u2 fields_count; // Class 文件的字段属性
+ field_info fields[fields_count]; // 一个类会可以有多个字段
+ u2 methods_count; // Class 文件的方法数量
+ method_info methods[methods_count]; // 一个类可以有个多个方法
+ u2 attributes_count; // 此类的属性表中的属性数
+ attribute_info attributes[attributes_count]; // 属性表集合
+}
+```
+
+通过分析 ClassFilee,得到 class 文件的组成:
+
+
+
+
+
+
+
+### 魔数
+
+每个 Class 文件的头 4 个字节称为魔数(Magic Number),它的唯一作用是**确定这个文件是否为一个能被虚拟机接收的 Class 文件**。
+
+### 文件版本号
+
+紧接着魔数的四个字节存储的是 Class 文件的版本号:第 5 和第 6 字节位是**次版本号**(Minor Version),第 7 和第 8 字节位是**主版本号**(Major Version)。
+
+每当 Java 发布大版本(比如 Java 8,Java 9)的时候,主版本号都会加 1。
+
+可以使用 `javap -v` 命令来快速查看 Class 文件的版本号信息。
+
+高版本的 Java 虚拟机可以执行低版本编译器生成的 Class 文件,但是低版本的 Java 虚拟机不能执行高版本编译器生成的 Class 文件。在开发时要确保开发的的 JDK 版本和生产环境的 JDK 版本保持一致。
+
+### 常量池
+
+紧接着主次版本号之后的是常量池,常量池的数量是 constant_pool_count-1。
+
+这是因为常量池计数器是从 1 开始计数的,将第 0 项常量空出来是有特殊考虑的,索引值为 0 代表"不引用任何一个常量池项。
+
+常量池主要存放两大常量:**字面量**和**符号引用**。
+
+字面量比较接近于 Java 语言层面的的常量概念,如文本字符串、声明为 final 的常量值等。
+
+符号引用则属于编译原理方面的概念,包括下面三类常量:
+
+- 类和接口的全限定名
+- 字段的名称和描述符
+- 方法的名称和描述符
+
+常量池中每一项常量都是一个表:表开始的第一位是一个 u1(占 1 个字节) 类型的标志位 \- tag 来标识常量的类型,代表当前这个常量属于哪种常量类型。
+
+### 访问标志
+
+紧接着常量池的两个字节代表访问标志(Access Flag),这个标志用于识别一些类或者接口层次的访问信息,包括:
+
+- 这个 Class 是类还是接口
+- 是否为 public 或者 abstract 类型
+- 如果是类的话是否声明为 final 等
+
+### 当前类 & 父类
+
+类索引(This Class)用于确定这个类的全限定名,父类索引(Super Class)用于确定这个类的父类的全限定名,由于 Java 语言的单继承,所以父类索引只有一个,除了 `java.lang.Object` 之外,所有的 Java 类都有父类,因此除了 java.lang.Object 外,所有 Java 类的父类索引都不为 0。
+
+### 接口索引集合
+
+接口索引集合用来描述这个类实现了哪些接口,这些被实现的接口将按 implements (如果这个类本身是接口的话则是extends)后的接口顺序从左到右排列在接口索引集合中。
+
+### 字段表集合
+
+字段表(Fields)用于描述接口或类中声明的变量。
+
+字段包括类级变量以及实例变量,但不包括在方法内部声明的局部变量。
+
+```c
+field_info{
+ u2 access_flags;
+ u2 name_index;
+ u2 descriptor_index;
+ u2 attributes_count;
+ attribute_info attributes[attributes_count];
+}
+```
+
+其中:
+
+- access_flags:字段的作用域(public,protected,private);实例变量还是类变量(static);是否可被序列化(transient);可变性(final);可见性(volatile)
+- name_index:对常量池的引用,表示字段的名称
+- descriptor_index:对常量池的引用,表示字段和方法的描述符
+- attributes_count:一个字段还会拥有一些额外的属性,表示额外属性的个数
+- attributes[attributes_count]:存放属性具体内容。
+
+### 方法表集合
+
+methods_count 表示方法的数量,而 method_info 表示方法表。
+
+Class 文件存储格式中对方法的描述与对字段的描述几乎采用了完全一致的方式。方法表的结构如同字段表一样,依次包括了访问标志、名称索引、描述符索引、属性表集合几项。
+
+因为 volatile 修饰符和 transient 修饰符不可以修饰方法,所以方法表的访问标志中没有这两个对应的标志,但是增加了 synchronized、native、abstract 等关键字修饰方法,所以也就多了这些关键字对应的标志。
+
+### 属性表集合
+
+在 Class 文件,字段表,方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息。与 Class 文件中其它的数据项目要求的顺序、长度和内容不同,属性表集合的限制稍微宽松一些,不再要求各个属性表具有严格的顺序,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写 入自己定义的属性信息,JVM 运行时会忽略掉它不认识的属性。
\ No newline at end of file
diff --git a/docs/JVM/7_JVM.md "b/docs/JVM/6_347円261円273円345円212円240円350円275円275円346円234円272円345円210円266円.md"
similarity index 54%
rename from docs/JVM/7_JVM.md
rename to "docs/JVM/6_347円261円273円345円212円240円350円275円275円346円234円272円345円210円266円.md"
index f1326b6f..210636ca 100644
--- a/docs/JVM/7_JVM.md
+++ "b/docs/JVM/6_347円261円273円345円212円240円350円275円275円346円234円272円345円210円266円.md"
@@ -1,28 +1,18 @@
-# 类加载机制
+# 类的生命周期
-类是在运行期间第一次使用时动态加载的,而不是一次性加载所有类。因为如果一次性加载,那么会占用很多的内存。
-
-## 类的生命周期
-
-
+从类被加载到虚拟机内存中开始,到释放内存总共有 7 个阶段:加载(Loading),验证(Verification),准备(Preparation),解析(Resolution),初始化(Initialization),使用(Using),卸载(Unloading)。
-包括以下 7 个阶段:
-
-- **加载(Loading)**
-- **验证(Verification)**
-- **准备(Preparation)**
-- **解析(Resolution)**
-- **初始化(Initialization)**
-- 使用(Using)
-- 卸载(Unloading)
+其中验证,准备,解析三个部分统称为**连接(Linking)**。
## 类加载过程
-包含了加载、验证、准备、解析和初始化这 5 个阶段。
+类是在运行期间第一次使用时动态加载的,而不是一次性加载所有类。因为如果一次性加载,那么会占用很多的内存。
+
+类加载过程包括加载、连接和初始化这 3 个阶段。其中连接过程又分为 3 步:验证、准备和解析,所以可以说类加载过程包含了加载、验证、准备、解析和初始化这 5 个阶段。
### 1. 加载
-加载是类加载的一个阶段,注意不要混淆。
+**加载是类加载的一个阶段,注意不要混淆**。
加载过程完成以下三件事:
@@ -39,15 +29,20 @@
### 2. 验证
-确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
+确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。包括如下验证:
+
+- 文件格式验证:验证字节流是否符合 Class 文件格式规范
+- 元数据验证:对字节码描述的信息进行语义分析,保证其描述的信息符合 Java 语言规范的要求
+- 字节码验证:通过数据流和控制流分析,确保程序语义是合法的、符合逻辑的。
+- 符号引用验证:确保解析动作能正确执行。
### 3. 准备
-类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。
+类变量是被 static 修饰的变量,**准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存**。注意:在 JDK 7 及之后,HotSpot 已经把原本放在永久代的字符串常量池、**静态变量等移动到堆中**,这个时候类变量则会随着 Class 对象一起存放在 Java 堆中。
实例变量不会在这阶段分配内存,它会在对象实例化时随着对象一起被分配在堆中。应该注意到,实例化不是类加载的一个过程,类加载发生在所有实例化操作之前,并且类加载只进行一次,实例化可以进行多次。
-初始值一般为 0 值,例如下面的类变量 value 被初始化为 0 而不是 123。
+初始值一般为 0 值,例如下面的类变量 value 的初始值是 0 而不是 123(初始化阶段才会赋值)。
```java
public static int value = 123;
@@ -61,16 +56,14 @@ public static final int value = 123;
### 4. 解析
-将常量池的符号引用替换为直接引用的过程。
+解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符 7 类符号引用进行。
-其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。
+其中,符号引用指的是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。在程序实际运行时,只有符号引用是不够的,举个例子:在程序执行方法时,系统需要明确知道这个方法所在的位置。Java 虚拟机为每个类都准备了一张方法表来存放类中所有的方法。当需要调用一个类的方法的时候,只要知道这个方法在方法表中的偏移量就可以直接调用该方法了。通过解析操作符号引用就可以直接转变为目标方法在类中方法表的位置,从而使得方法可以被调用。
-
+**解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,也就是得到类或字段、方法在内存中的指针或者偏移量**。
### 5. 初始化
-
-
初始化阶段才真正开始执行类中定义的 Java 程序代码。初始化阶段是虚拟机执行类构造器 <clinit>() 方法的过程。在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。
<clinit>() 是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序决定。特别注意的是,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问。例如以下代码:
@@ -79,13 +72,13 @@ public static final int value = 123;
public class Test {
static {
i = 0; // 给变量赋值可以正常编译通过
- System.out.print(i); // 这句编译器会提示"非法向前引用"
+ System.out.print(i); // 这句编译器会提示"非法向前引用",因为变量 i 在后面才定义的。
}
static int i = 1;
}
```
-由于父类的 <clinit>() 方法先执行,也就意味着父类中定义的静态语句块的执行要优先于子类。例如以下代码:
+由于父类的 <clinit>() 方法先执行,也就意味着**父类中定义的静态语句块的执行要优先于子类**。例如以下代码:
```java
static class Parent {
@@ -108,47 +101,164 @@ public static void main(String[] args) {
虚拟机会保证一个类的 <clinit>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 <clinit>() 方法,其它线程都会阻塞等待,直到活动线程执行 <clinit>() 方法完毕。如果在一个类的 <clinit>() 方法中有耗时的操作,就可能造成多个线程阻塞,在实际过程中此种阻塞很隐蔽。
-> loadClass 和 forName 的区别?
-- loadClass:得到的是第一个阶段加载的class对象,并没有初始化,例如Spring中Bean的实例的懒加载就是通过这种方式实现的。
-- forName :得到的是第五阶段初始化的class对象,例如JDBC中的数据连接。
## 类初始化时机
### 1. 主动引用
-虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随之发生):
+只有主动去使用类才会初始化类。
+
+虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列 6 种情况必须对类进行初始化(加载、验证、准备都会随之发生):
-- 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。
-- 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
-- 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
-- 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类;
-- 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化;
+- 情况 1:遇到 new、getstatic、putstatic 或 invokestatic 这四条字节码指令时,如果类型没有进行过初始
+ 化,则需要先触发其初始化阶段。能够生成这四条指令的典型 Java 代码场景有:
+ - 使用 new 关键字实例化对象的时候。
+ - 设置一个类型的静态字段(**被 final 修饰、已在编译期把结果放入常量池的静态字段除外**)的时候。
+ - 读取一个类型的静态字段(**被 final 修饰、已在编译期把结果放入常量池的静态字段除外**)的时候。
+ - 调用一个类型的静态方法的时候。( JVM 执行 invokestatic 指令时会初始化类)
+- 情况 2:使用 java.lang.reflect 包的方法对类型进行**反射调用**的时候,如果类型没有进行过初始化,则需
+ 要先触发其初始化。
+- 情况 3:当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
+- 情况 4:当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先
+ 初始化这个主类。
+- 情况 5:当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
+- 情况 6:当一个接口中定义了 JDK8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。
### 2. 被动引用
-以上 5 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的常见例子包括:
+以上 6 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的常见例子包括:
- 通过子类引用父类的静态字段,不会导致子类初始化。
-```java
-System.out.println(SubClass.value); // value 字段在 SuperClass 中定义
-```
+ 举个例子:
+
+ ```java
+ public class SuperClass {
+ static {
+ System.out.println("SuperClass init!");
+ }
+
+ public static int value=123;
+ }
+ ```
+
+ ```java
+ public class SubClass extends SuperClass{
+ static {
+ System.out.println("SubClass init!");
+ }
+ }
+ ```
+
+ ```java
+ public class NotInitialization {
+ public static void main(String[] args) {
+ System.out.println(SubClass.value);
+ }
+ }
+ ```
+
+ 输出结果为:
+
+ ```html
+ SuperClass init!
+ 123
+ ```
+
+ 这是因为对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。
+
+- 通过数组定义来引用类,不会触发此类的初始化。该过程会对数组类进行初始化,**数组类是一个由虚拟机自动生成的、直接继承自 Object 的子类,其中包含了数组的属性和方法**(数组类型不通过类加载器创建,它由 JVM 直接创建)。
+
+ 举个例子:
+
+ ```java
+ public class SuperClass {
+ static {
+ System.out.println("SuperClass init!");
+ }
+
+ public static int value=123;
+ }
+ ```
+
+ ```java
+ public class SubClass extends SuperClass{
+ static {
+ System.out.println("SubClass init!");
+ }
+ }
+ ```
+
+ ```java
+ public class NotInitialization {
+ public static void main(String[] args) {
+ SuperClass[] sca = new SuperClass[10];
+ }
+ }
+ ```
+
+ 输出结果为:
+
+ ```html
+ 没有输出 SuperClass init!
+ ```
-- 通过数组定义来引用类,不会触发此类的初始化。该过程会对数组类进行初始化,数组类是一个由虚拟机自动生成的、直接继承自 Object 的子类,其中包含了数组的属性和方法。
+- 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
-```java
-SuperClass[] sca = new SuperClass[10];
-```
+ 举了例子:
-- 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
+ ```java
+ public class ConstClass {
+ static {
+ System.out.println("ConstClass init!");
+ }
+ public static final String HELLOWORLD = "hello world";
+ }
+ ```
-```java
-System.out.println(ConstClass.HELLOWORLD);
-```
+ ```java
+ public class NotInitialization {
+ public static void main(String[] args) {
+ System.out.println(ConstClass.HELLOWORLD);
+ }
+ }
+ ```
+
+ 输出结果为:
+
+ ```html
+ hello world
+ ```
+
+
+## loadClass 和 forName
+
+loadClass 和 forName 方法都可以获取 Class 对象,但是两者是有区别的:
+
+- loadClass 得到的是加载阶段的 Class 对象,并没有初始化,例如 Spring 中 Bean 的实例的懒加载就是通过这种方式实现的。
+- forName 得到的是初始化阶段的 Class 对象,例如 JDBC 中的数据连接。
+
+## 卸载
+
+卸载类即该类的 Class 对象被 GC。
+
+类的卸载条件很多,需要满足以下三个条件,并且满足了条件也不一定会被卸载:
+
+- 该类所有的实例都已经被回收,此时堆中不存在该类的任何实例。
+- 加载该类的 ClassLoader 已经被回收。
+- 该类对应的 Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。
+
+JDK 自带的 BootstrapClassLoader, ExtClassLoader 和 AppClassLoader 负责加载 JDK 提供的类,所以这些类加载器的实例肯定不会被回收。但是我们自定义的类加载器的实例是可以被回收的,所以使用我们自定义加载器加载的类是可以被卸载掉的。
+
+
+
+# 类加载器
## 类与类加载器
+所有的类都由类加载器加载,加载的作用就是将 .class 文件加载到内存中。
+
两个类相等,需要类本身相等,并且使用同一个类加载器进行加载。这是因为每一个类加载器都拥有一个独立的类名称空间。
这里的相等,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果为 true,也包括使用 instanceof 关键字做对象所属关系判定结果为 true。
@@ -170,21 +280,19 @@ System.out.println(ConstClass.HELLOWORLD);
## 双亲委派模型
-应用程序是由三种类加载器互相配合从而实现类加载,除此之外还可以加入自己定义的类加载器。
+每一个类都有一个对应它的类加载器。系统中的 ClassLoader 在协同工作的时候会默认使用双亲委派模型(Parents Delegation Model)。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。这里的父子关系一般通过组合关系(Composition)来实现,而不是继承关系(Inheritance)。
-下图展示了类加载器之间的层次关系,称为双亲委派模型(Parents Delegation Model)。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。这里的父子关系一般通过组合关系(Composition)来实现,而不是继承关系(Inheritance)。
-
-
+
### 1. 工作过程
-一个类加载器首先将类加载请求转发到父类加载器,只有当父类加载器无法完成时才尝试自己加载。
+在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。加载的时候,首先会把该请求委派给父类加载器的 loadClass() 处理,因此所有的请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader 中。当父类加载器无法处理时,才由自己来处理。当父类加载器为 null 时,会使用启动类加载器 BootstrapClassLoader 作为父类加载器。
### 2. 好处
使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。
-可以避免多份同样的字节码的加载
+可以避免多份同样的字节码的加载。
例如 java.lang.Object 存放在 rt.jar 中,如果编写另外一个 java.lang.Object 并放到 ClassPath 中,程序可以编译通过。由于双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,这是因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。rt.jar 中的 Object 优先级更高,那么程序中所有的 Object 都是这个 Object。
@@ -283,4 +391,8 @@ public class FileSystemClassLoader extends ClassLoader {
+ className.replace('.', File.separatorChar) + ".class";
}
}
-```
\ No newline at end of file
+```
+
+# 补充
+
+- [main() 方法详解](https://www.cnblogs.com/bingyimeiling/p/10409728.html)
\ No newline at end of file
diff --git "a/docs/JVM/7_Java347円250円213円345円272円217円347円274円226円350円257円221円345円222円214円350円277円220円350円241円214円350円277円207円347円250円213円.md" "b/docs/JVM/7_Java347円250円213円345円272円217円347円274円226円350円257円221円345円222円214円350円277円220円350円241円214円350円277円207円347円250円213円.md"
new file mode 100644
index 00000000..6e207c69
--- /dev/null
+++ "b/docs/JVM/7_Java347円250円213円345円272円217円347円274円226円350円257円221円345円222円214円350円277円220円350円241円214円350円277円207円347円250円213円.md"
@@ -0,0 +1,84 @@
+# Java 程序编译和运行过程
+
+Java程序从 \.java 文件创建到程序运行要经过两大过程:
+
+- \.java 文件由编译器编译成 \.class文件
+- 字节码由 JVM 解释运行
+
+## 编译过程
+
+.java 源文件会被 Java编译器进行编译为.class文件:
+
+- Java 编译一个类时,如果这个类所依赖的类还没有被编译,编译器会自动的先编译这个所依赖的类,然后引用。如果 Java 编译器在指定的目录下找不到该类所依赖的类的 \.class文件或者 \.java源文件,则会报
+ "Cant found sysbol" 的异常错误。
+- 编译后的 \.class 文件主要分为两部分:常量池和方法表集合。
+ 常量池记录的是代码出现过的(常量、类名、成员变量等)以及符号引用(类引用、方法引用,成员变量引用等);
+ 方法表集合则记录各个方法的字节码。
+
+
+
+## 运行过程
+
+JVM 并不是在运行时就会把所有使用到的类都加载到内存中,而是用到的时候,才加载进方法区,并且只加载一次。
+Java类运行的过程大概分为两个步骤:
+
+- 类加载
+- 执行类
+
+举例说明 Java 程序运行过程:
+
+```java
+public class Person {
+ private String name;
+
+ public Person(String name){
+ this.name=name;
+ }
+
+ public void sayHello(){
+ System.out.println("Hello! My Name is: " + name);
+ }
+}
+```
+
+```java
+public class JVMTest {
+ public static void main(String[] args) {
+ Person p=new Person("Li Ming");
+ p.sayHello();
+ }
+}
+```
+
+### 1. 类加载
+
+首先编译 JVMTest.java 文件得到 JVMTest.class 文件,系统启动一个 JVM 进程,从 classpath 路径中找到 JVMTest.class 文件,将 JVMTest 的类信息加载到方法区中,这个过程称为 JVMTest 类的加载。
+
+(只有类信息在方法区中,才能创建对象,使用类中的成员变量)
+
+### 2. JVM 找 main() 方法入口
+
+在 main() 方法 入口持有一个指向当前类 (JVMTest) 常量池的指针,常量池中的第一项是一个对 Person 对象的符号引用。
+
+main 方法中 `Person p=new Person("Li Ming"),JVM 需要创建一个 Person 对象,但是此时方法区中是没有 Person 类信息的,所以 JVM 需要加载 Person 类,将 Person 类的信息加载到方法区中。
+
+JVM 以一个直接指向方法区 Person 类的指针替换了常量池中第一项的符号引用。
+
+### 3. 实例化对象
+
+加载完 Person 类的信息以后,JVM 就会在堆中为一个 Person 实例分配内存,然后调用构造方法初始化 Person 实例,并且该实例**持有指向方法区中的 Person 类的类型信息(其中包括方法表)的引用**。
+
+(p 为指向该 Person 实例的引用,会被放到栈中)
+
+### 4. 运行方法
+
+执行 p.sayHello(),JVM 根据栈中 p 的引用找到 Person 对象,然后根据 Person 对象持有的引用定位到方法区中 Person 类类信息的**方法表**,获得 sayHello 方法的字节码地址,然后开始运行方法。
+
+
+
+
+
+
+# 补充
+
+- [main() 方法详解](https://www.cnblogs.com/bingyimeiling/p/10409728.html)
\ No newline at end of file
diff --git "a/docs/JavaBasics/10_Java345円270円270円350円247円201円345円257円271円350円261円241円.md" "b/docs/JavaBasics/10_Java345円270円270円350円247円201円345円257円271円350円261円241円.md"
index 61c891fa..0608c91a 100644
--- "a/docs/JavaBasics/10_Java345円270円270円350円247円201円345円257円271円350円261円241円.md"
+++ "b/docs/JavaBasics/10_Java345円270円270円350円247円201円345円257円271円350円261円241円.md"
@@ -1,908 +1,402 @@
-# Java常见对象
+# Java 常见类 I
-## Arrays
-Arrays:针对数组进行操作的工具类。
+## Object
-- Arrays的常用成员方法:
-```java
-public static String toString(int[] a) //把数组转成字符串
-
-public static void sort(int[] a) //对数组进行排序
+Object类是类层次结构的根类。每个类都使用 Object 作为超类。每个类都直接或者间接的继承自Object类。
-public static int binarySearch(int[] a,int key) //二分查找
-```
+Object 中常用方法有:
-- toString()源码如下:
```java
-public static String toString(int[] a) {
- if (a == null)
- return "null";
- int iMax = a.length - 1;
- if (iMax == -1)
- return "[]";
-
- StringBuilder b = new StringBuilder();
- b.append('[');
- for (int i = 0; ; i++) {
- b.append(a[i]);
- if (i == iMax)
- return b.append(']').toString();
- b.append(", ");
- }
- }
-```
-- binarySearch()调用的是binarySearch0(),源码如下:
-```java
- private static int binarySearch0(int[] a, int fromIndex, int toIndex,int key) {
- int low = fromIndex;
- int high = toIndex - 1;
-
- while (low <= high) { - int mid = (low + high)>>> 1;
- int midVal = a[mid];
-
- if (midVal < key) - low = mid + 1; - else if (midVal> key)
- high = mid - 1;
- else
- return mid; // key found
- }
- return -(low + 1); // key not found.
- }
-```
+public int hashCode() //返回该对象的哈希码值。
+// 注意:哈希值是根据哈希算法计算出来的一个值,这个值和地址值有关,但是不是实际地址值。
-- 使用示例:
-```java
-public class ArraysDemo {
- public static void main(String[] args) {
- // 定义一个数组
- int[] arr = { 24, 69, 80, 57, 13 };
+public final Class getClass() //返回此 Object 的运行时类
- // public static String toString(int[] a) 把数组转成字符串
- System.out.println("排序前:" + Arrays.toString(arr));//排序前:[24, 69, 80, 57, 13]
+public String toString() //返回该对象的字符串表示。
- // public static void sort(int[] a) 对数组进行排序
- Arrays.sort(arr);
- System.out.println("排序后:" + Arrays.toString(arr));//排序后:[13, 24, 57, 69, 80]
+protected Object clone() //创建并返回此对象的一个副本。可重写该方法
- // [13, 24, 57, 69, 80]
- // public static int binarySearch(int[] a,int key) 二分查找
- System.out.println("binarySearch:" + Arrays.binarySearch(arr, 57));//binarySearch:2
- System.out.println("binarySearch:" + Arrays.binarySearch(arr, 577));//binarySearch:-6
- }
-}
+protected void finalize()
+// 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。用于垃圾回收,但是什么时候回收不确定。
```
-## BigDemical
-BigDecimal类:不可变的、任意精度的有符号十进制数,可以解决数据丢失问题。
-看如下程序,写出结果
-```java
-public static void main(String[] args) {
- System.out.println(0.09 + 0.01);
- System.out.println(1.0 - 0.32);
- System.out.println(1.015 * 100);
- System.out.println(1.301 / 100);
- System.out.println(1.0 - 0.12);
-}
-```
-输出结果
-```html
-0.09999999999999999
-0.6799999999999999
-101.49999999999999
-0.013009999999999999
-0.88
-```
-结果和我们想的有一点点不一样,这是因为浮点数类型的数据存储和整数不一样导致的。
-它们大部分的时候,都是带有有效数字位。由于在运算的时候,float类型和double很容易丢失精度,
-所以,为了能精确的表示、计算浮点数,Java提供了BigDecimal。
+### equals() 方法
-- BigDecimal的常用成员方法:
-```java
-public BigDecimal(String val) //构造方法
-
-public BigDecimal add(BigDecimal augend) //加
-
- public BigDecimal subtract(BigDecimal subtrahend)//减
-
- public BigDecimal multiply(BigDecimal multiplicand) //乘
-
- public BigDecimal divide(BigDecimal divisor) //除
-
- public BigDecimal divide(BigDecimal divisor,int scale,int roundingMode)//除法,scale:几位小数,roundingMode:如何舍取
-```
+**1. 等价关系**
+
+I 自反性
-- 使用BigDecimal改进
```java
-public static void main(String[] args) {
- /*System.out.println(0.09 + 0.01);
- System.out.println(1.0 - 0.32);
- System.out.println(1.015 * 100);
- System.out.println(1.301 / 100);
- System.out.println(1.0 - 0.12);*/
-
- BigDecimal bd1 = new BigDecimal("0.09");
- BigDecimal bd2 = new BigDecimal("0.01");
- System.out.println("add:" + bd1.add(bd2));//add:0.10
- System.out.println("-------------------");
-
- BigDecimal bd3 = new BigDecimal("1.0");
- BigDecimal bd4 = new BigDecimal("0.32");
- System.out.println("subtract:" + bd3.subtract(bd4));//subtract:0.68
- System.out.println("-------------------");
-
- BigDecimal bd5 = new BigDecimal("1.015");
- BigDecimal bd6 = new BigDecimal("100");
- System.out.println("multiply:" + bd5.multiply(bd6));//multiply:101.500
- System.out.println("-------------------");
-
- BigDecimal bd7 = new BigDecimal("1.301");
- BigDecimal bd8 = new BigDecimal("100");
- System.out.println("divide:" + bd7.divide(bd8));//divide:0.01301
-
- //四舍五入
- System.out.println("divide:"
- + bd7.divide(bd8, 3, BigDecimal.ROUND_HALF_UP));//保留三位有效数字
- //divide:0.013
-
- System.out.println("divide:"
- + bd7.divide(bd8, 8, BigDecimal.ROUND_HALF_UP));//保留八位有效数字
- //divide:0.01301000
-}
+x.equals(x); // true
```
-## BigInteger
-BigInteger:可以让超过Integer范围内的数据进行运算
+II 对称性
```java
-public static void main(String[] args) {
- Integer num = new Integer("2147483647");
- System.out.println(num);
-
- //Integer num2 = new Integer("2147483648");
- // Exception in thread "main" java.lang.NumberFormatException: For input string: "2147483648"
- //System.out.println(num2);
-
- // 通过 BigIntege来创建对象
- BigInteger num2 = new BigInteger("2147483648");
- System.out.println(num2);
-}
+x.equals(y) == y.equals(x); // true
```
-- BigInteger的常用成员方法:
-```java
-public BigInteger add(BigInteger val) //加
-
-public BigInteger subtract(BigInteger val) //减
+III 传递性
-public BigInteger multiply(BigInteger val) //乘
-
-public BigInteger divide(BigInteger val) //除
-
-public BigInteger[] divideAndRemainder(BigInteger val)//返回商和余数的数组
-```
-- 使用实例:
```java
-public class BigIntegerDemo {
- public static void main(String[] args) {
- Integer num = new Integer("2147483647");
- System.out.println(num);
+if (x.equals(y) && y.equals(z))
+ x.equals(z); // true;
+```
- //Integer num2 = new Integer("2147483648");
- // Exception in thread "main" java.lang.NumberFormatException: For input string: "2147483648"
- //System.out.println(num2);
+IV 一致性
- // 通过 BigIntege来创建对象
- BigInteger num2 = new BigInteger("2147483648");
- System.out.println(num2);
- }
-}
-```
+多次调用 equals() 方法结果不变
```java
-public class BigIntegerDemo2 {
- public static void main(String[] args) {
- BigInteger bi1 = new BigInteger("100");
- BigInteger bi2 = new BigInteger("50");
-
- // public BigInteger add(BigInteger val):加
- System.out.println("add:" + bi1.add(bi2)); //add:150
- // public BigInteger subtract(BigInteger Val):减
- System.out.println("subtract:" + bi1.subtract(bi2));//subtract:50
- // public BigInteger multiply(BigInteger val):乘
- System.out.println("multiply:" + bi1.multiply(bi2));//multiply:5000
- // public BigInteger divide(BigInteger val):除
- System.out.println("divide:" + bi1.divide(bi2));//divide:2
-
- // public BigInteger[] divideAndRemainder(BigInteger val):返回商和余数的数组
- BigInteger[] bis = bi1.divideAndRemainder(bi2);
- System.out.println("divide:" + bis[0]);//divide:2
- System.out.println("remainder:" + bis[1]);//remainder:0
- }
-}
+x.equals(y) == x.equals(y); // true
```
-## Calendar
-Calendar为特定瞬间与一组诸如 YEAR、MONTH、DAY_OF_MONTH、HOUR 等日历字段之间的转换提供了一些方法,
-并为操作日历字段(例如获得下星期的日期)提供了一些方法。
+V 与 null 的比较
-Calendar中常用的方法:
+对任何**不是 null 的对象** x 调用 x.equals(null) 结果都为 false
```java
-public int get(int field) //返回给定日历字段的值。日历类中的每个日历字段都是静态的成员变量,并且是int类型。
-
-public void add(int field,int amount)//根据给定的日历字段和对应的时间,来对当前的日历进行操作。
-
-public final void set(int year,int month,int date)//设置当前日历的年月日
-```
-- 使用示例:
-```java
-public class CalendarDemo {
- public static void main(String[] args) {
- // 其日历字段已由当前日期和时间初始化:
- Calendar rightNow = Calendar.getInstance(); // 子类对象
- int year=rightNow.get(Calendar.YEAR);
- int month=rightNow.get(Calendar.MONTH);//注意月份是从0开始的
- int date=rightNow.get(Calendar.DATE);
- System.out.println(year + "年" + (month + 1) + "月" + date + "日");
- //2018年12月25日
- }
-}
+x.equals(null); // false;
```
-- 使用示例2:
-```java
-public class CalendarDemo2 {
- public static void main(String[] args) {
- // 其日历字段已由当前日期和时间初始化:
- Calendar calendar = Calendar.getInstance(); // 子类对象
- System.out.println(getYearMonthDay(calendar));//2018年12月25日
-
- //三年前的今天
- calendar.add(Calendar.YEAR,-3);
- System.out.println(getYearMonthDay(calendar));//2015年12月25日
-
- //5年后的10天前
- calendar.add(Calendar.YEAR,5);
- calendar.add(Calendar.DATE,-10);
- System.out.println(getYearMonthDay(calendar));//2020年12月15日
-
- //设置 2011年11月11日
- calendar.set(2011,10,11);
- System.out.println(getYearMonthDay(calendar));//2011年11月11日
- }
- //获取年、月、日
- public static String getYearMonthDay(Calendar calendar){
- int year=calendar.get(Calendar.YEAR);
- int month=calendar.get(Calendar.MONTH);
- int date=calendar.get(Calendar.DATE);
- return year + "年" + (month + 1) + "月" + date + "日";
- }
-}
-```
+**2. 等价与相等**
-- 小练习:获取任意一年的二月有多少天
-```java
-/**
- *获取任意一年的二月有多少天
- *分析:
- * A:键盘录入任意的年份
- * B:设置日历对象的年月日
- * 年就是输入的数据
- * 月是2
- * 日是1
- * C:把时间往前推一天,就是2月的最后一天
- * D:获取这一天输出即可
- */
-public class CalendarTest {
- public static void main(String[] args) {
- Scanner sc=new Scanner(System.in);
- int year=sc.nextInt();
- Calendar c= Calendar.getInstance();
- c.set(year,2,1); //得到的就是该年的3月1日
- c.add(Calendar.DATE,-1);//把时间往前推一天,就是2月的最后一天
- //public void add(int field,int amount):根据给定的日历字段和对应的时间,来对当前的日历进行操作。
-
- System.out.println(year+"年,二月有"+c.get(Calendar.DATE)+"天");
- }
-}
-```
+- 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
+- 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。
-## Character
-Character 类在对象中包装一个基本类型 char 的值.此外,该类提供了几种方法,
-以确定字符的类别(小写字母,数字,等等),并将字符从大写转换成小写,反之亦然。
-
-- Character常用方法:
```java
-Character(char value) //构造方法
-
-public static boolean isUpperCase(char ch) //判断给定的字符是否是大写字符
-
-public static boolean isLowerCase(char ch) //判断给定的字符是否是小写字符
+Integer x = new Integer(1);
+Integer y = new Integer(1);
+System.out.println(x.equals(y)); // true
+System.out.println(x == y); // false
+```
-public static boolean isDigit(char ch) //判断给定的字符是否是数字字符
+**3. 实现**
-public static char toUpperCase(char ch) //把给定的字符转换为大写字符
+- 检查是否为同一个对象的引用,如果是直接返回 true;
+- 检查是否是同一个类型,如果不是,直接返回 false;
+- 将 Object 对象进行转型;
+- 判断每个关键域是否相等。
-public static char toLowerCase(char ch) //把给定的字符转换为小写字符
-```
-- 使用示例:
```java
-public class CharacterDemo {
- public static void main(String[] args) {
- // public static boolean isUpperCase(char ch):判断给定的字符是否是大写字符
- System.out.println("isUpperCase:" + Character.isUpperCase('A'));//true
- System.out.println("isUpperCase:" + Character.isUpperCase('a'));//false
- System.out.println("isUpperCase:" + Character.isUpperCase('0'));//false
- System.out.println("-----------------------------------------");
-
- // public static boolean isLowerCase(char ch):判断给定的字符是否是小写字符
- System.out.println("isLowerCase:" + Character.isLowerCase('A'));//false
- System.out.println("isLowerCase:" + Character.isLowerCase('a'));//true
- System.out.println("isLowerCase:" + Character.isLowerCase('0'));//false
- System.out.println("-----------------------------------------");
-
- // public static boolean isDigit(char ch):判断给定的字符是否是数字字符
- System.out.println("isDigit:" + Character.isDigit('A'));//false
- System.out.println("isDigit:" + Character.isDigit('a'));//false
- System.out.println("isDigit:" + Character.isDigit('0'));//true
- System.out.println("-----------------------------------------");
-
- // public static char toUpperCase(char ch):把给定的字符转换为大写字符
- System.out.println("toUpperCase:" + Character.toUpperCase('A'));//A
- System.out.println("toUpperCase:" + Character.toUpperCase('a'));//A
- System.out.println("-----------------------------------------");
-
- // public static char toLowerCase(char ch):把给定的字符转换为小写字符
- System.out.println("toLowerCase:" + Character.toLowerCase('A'));//a
- System.out.println("toLowerCase:" + Character.toLowerCase('a'));//a
- }
-}
-```
+public class EqualExample {
-- 小练习:统计一个字符串中大写字母字符,小写字母字符,数字字符出现的次数。(不考虑其他字符)
+ private int x;
+ private int y;
+ private int z;
-```java
-/**
- * 统计一个字符串中大写字母字符,小写字母字符,数字字符出现的次数。(不考虑其他字符)
- *
- * 分析:
- * A:定义三个统计变量。
- * int bigCont=0;
- * int smalCount=0;
- * int numberCount=0;
- * B:键盘录入一个字符串。
- * C:把字符串转换为字符数组。
- * D:遍历字符数组获取到每一个字符
- * E:判断该字符是
- * 大写 bigCount++;
- * 小写 smalCount++;
- * 数字 numberCount++;
- * F:输出结果即可
- */
-public class CharacterTest {
- public static void main(String[] args) {
- Scanner sc=new Scanner(System.in);
- String str=sc.nextLine();
- printCount(str);
- printCount2(str);
+ public EqualExample(int x, int y, int z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
}
- //原来的写法
- public static void printCount(String str) {
- int numberCount=0;
- int lowercaseCount=0;
- int upercaseCount=0;
-
- for(int index=0;index='0' && ch<='9'){ - numberCount++; - }else if(ch>='A' && ch<='z'){ - upercaseCount++; - }else if(ch>='a' && ch<='z'){ - lowercaseCount++; - } + @Override + public boolean equals(Object o) { + if (this == o) return true; //检查是否为同一个对象的引用,如果是直接返回 true; + if (o == null || getClass() != o.getClass()){ + //检查是否是同一个类型,如果不是,直接返回 false + return false; } - System.out.println("数字有"+numberCount+"个"); - System.out.println("小写字母有"+lowercaseCount+"个"); - System.out.println("大写字母有"+upercaseCount+"个"); - } - //使用包装类来改进 - public static void printCount2(String str) { - int numberCount=0; - int lowercaseCount=0; - int upercaseCount=0; - - for(int index=0;index 当前时间
-
- // Date(long date):根据给定的毫秒值创建日期对象
- //long time = System.currentTimeMillis();
- long time = 1000 * 60 * 60; // 1小时
- Date d2 = new Date(time);
- System.out.println("d2:" + d2);
- //格林威治时间 1970年01月01日00时00分00
- //Thu Jan 01 09:00:00 GMT+08:00 1970 GMT+表示 标准时间加8小时,因为中国是东八区
-
- // 获取时间
- long time2 = d.getTime();
- System.out.println(time2); //1545739438466 毫秒
- System.out.println(System.currentTimeMillis());
-
- // 设置时间
- d.setTime(1000*60*60);
- System.out.println("d:" + d);
- //Thu Jan 01 09:00:00 GMT+08:00 1970
- }
-}
+EqualExample e1 = new EqualExample(1, 1, 1);
+EqualExample e2 = new EqualExample(1, 1, 1);
+System.out.println(e1.equals(e2)); // true
+HashSet set = new HashSet();
+set.add(e1);
+set.add(e2);
+System.out.println(set.size()); // 2
```
+理想的散列函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上。
+这就要求了散列函数要把**所有域的值都考虑进来**。
+可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。
+R 一般取 31,因为它是一个奇素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。
-DateForamt:可以进行日期和字符串的格式化和解析,但是由于是抽象类,所以使用具体子类SimpleDateFormat。
-SimpleDateFormat的构造方法:
+一个数与 31 相乘可以转换成移位和减法:`31*x == (x<<5)-x`,编译器会自动进行这个优化。 ```java -SimpleDateFormat() //默认模式 - -SimpleDateFormat(String pattern) //给定的模式 +@Override +public int hashCode() { + int result = 17; + result = 31 * result + x; + result = 31 * result + y; + result = 31 * result + z; + return result; +} ``` -这个模式字符串该如何写呢? 通过查看API,我们就找到了对应的模式: +> 了解:IDEA中 Alt+Insert 快捷键就可以快速生成 hashCode() 和 equals() 方法。
-| 中文说明 | 模式字符 |
-| :--: | :--: |
-| 年 | y |
-| 月 | M |
-| 日 | d |
-| 时 | H |
-| 分 | m |
-| 秒 | s |
+### toString() 方法
-- Date类型和String类型的相互转换
+默认返回 ToStringExample@4554617c 这种形式,其中 @ 后面的数值为散列码的无符号十六进制表示。
```java
-public class DateFormatDemo {
- public static void main(String[] args) {
- Date date=new Date();
- SimpleDateFormat sdf=new SimpleDateFormat("yyyy年MM月dd日");
- String s=dateToString(date,sdf);
- System.out.println(s); //2018年12月25日
- System.out.println(stringToDate(s,sdf));//Tue Dec 25 00:00:00 GMT+08:00 2018
- }
+public class ToStringExample {
- /**
- * Date -- String(格式化)
- * public final String format(Date date)
- */
- public static String dateToString(Date d, SimpleDateFormat sdf) {
- return sdf.format(d);
- }
+ private int number;
- /**
- * * String -- Date(解析)
- * public Date parse(String source)
- */
- public static Date stringToDate(String s, SimpleDateFormat sdf){
- Date date=null;
- try {
- date=sdf.parse(s);
- } catch (ParseException e) {
- e.printStackTrace();
- }
- return date;
+ public ToStringExample(int number) {
+ this.number = number;
}
}
```
-- 小练习: 算一下你来到这个世界多少天?
```java
-/**
- * *
- * 算一下你来到这个世界多少天?
- *
- * 分析:
- * A:键盘录入你的出生的年月日
- * B:把该字符串转换为一个日期
- * C:通过该日期得到一个毫秒值
- * D:获取当前时间的毫秒值
- * E:用D-C得到一个毫秒值
- * F:把E的毫秒值转换为年
- * /1000/60/60/24
- */
-public class DateTest {
- public static void main(String[] args) throws ParseException {
- // 键盘录入你的出生的年月日
- Scanner sc = new Scanner(System.in);
- System.out.println("请输入你的出生年月日(格式 yyyy-MM-dd):");
- String line = sc.nextLine();
-
- // 把该字符串转换为一个日期
- SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
- Date d = sdf.parse(line);
- long birth=d.getTime(); //出生的时间
- long current=System.currentTimeMillis();//当前时间
-
- long days=(current-birth)/1000/60/60/24;
- System.out.println("你出生了"+days+"天");
- }
-}
+ToStringExample example = new ToStringExample(123);
+System.out.println(example.toString());
```
-## Integer
-Java就针对每一种基本数据类型提供了对应的类类型:
+```html
+ToStringExample@4554617c
+```
-| 基础类型 | 包装类类型 |
-| :--: | :--: |
-| byte | Byte |
-| short | Short |
-| int | Integer |
-| long | Long |
-| float | Float |
-| double | Double |
-| char | Character |
-| boolean | Boolean |
+### clone() 方法
-用于基本数据类型与字符串之间的转换。
+**1. Cloneable**
-- Integer的常用成员方法:
-```java
-public Integer(int value)
+clone() 是 Object 的 **protected 方法**,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。
-public Integer(String s) //注意:这个字符串必须是由数字字符组成
+```java
+public class CloneExample {
+ private int a;
+ private int b;
+}
```
-- int和String的相互转化:
```java
-String.valueOf(number) //int --> String
-
-Integer.parseInt(s) //String --> int
+CloneExample e1 = new CloneExample();
+// CloneExample e2 = e1.clone();
+// 'clone()' has protected access in 'java.lang.Object'
```
+重写 clone() 得到以下实现:
+
```java
-public static void main(String[] args) {
- System.out.println(intToString(100)); //100
- System.out.println(stringToInt("100")); //100
-}
+public class CloneExample {
+ private int a;
+ private int b;
-//int --> String
-public static String intToString(int number) {
- //方式一
- /*String strNumber=""+number;
- return strNumber;*/
- //方式二
- String strNumber=String.valueOf(number);
- return strNumber;
+ // CloneExample 默认继承 Object
+ @Override
+ public CloneExample clone() throws CloneNotSupportedException {
+ return (CloneExample)super.clone();
+ }
}
+```
-//String --> int
-public static int stringToInt(String strNumber) {
- //方式一
- /*Integer number=new Integer(strNumber);
- return number;*/
- //方式二
- Integer number=Integer.parseInt(strNumber);
- return number;
+```java
+CloneExample e1 = new CloneExample();
+try {
+ CloneExample e2 = e1.clone();
+} catch (CloneNotSupportedException e) {
+ e.printStackTrace();
}
```
-- 常用的进制转换:
-```java
-//常用的基本进制转换
-public static String toBinaryString(int i)
+```html
+java.lang.CloneNotSupportedException: CloneExample
+```
-public static String toOctalString(int i)
+以上抛出了 CloneNotSupportedException,这是因为 CloneExample 没有实现 Cloneable 接口。
-public static String toHexString(int i)
-
-//十进制到其他进制
-public static String toString(int i,int radix)//进制的范围:2-36,为什么呢?0,...9,a...z(10个数字+26个字母)
+应该注意的是,**clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法**。
-//其他进制到十进制
-public static int parseInt(String s,int radix)
-```
+**Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException**。
```java
-public class IntegerDemo3 {
- public static void main(String[] args) {
- //test();
- //test2();
- test3();
- }
-
- //常用的基本进制转换
- public static void test(){
- System.out.println(Integer.toBinaryString(100));//1100100
- System.out.println(Integer.toOctalString(100));//144
- System.out.println(Integer.toHexString(100));//64
- System.out.println("-----------------------------");
- }
-
- //十进制到其他进制
- public static void test2(){
- System.out.println(Integer.toString(100, 10));//100
- System.out.println(Integer.toString(100, 2));//1100100
- System.out.println(Integer.toString(100, 8));//144
- System.out.println(Integer.toString(100, 16));//64
- System.out.println(Integer.toString(100, 5));//400
- System.out.println(Integer.toString(100, 7));//202
- //进制的范围在2-36之间,超过这个范围,就作为十进制处理
- System.out.println(Integer.toString(100, -7)); //100
- System.out.println(Integer.toString(100, 70));//100
- System.out.println(Integer.toString(100, 1));//100
- System.out.println(Integer.toString(100, 37));//100
-
- System.out.println(Integer.toString(100, 17));//5f
- System.out.println(Integer.toString(100, 32));//34
- System.out.println(Integer.toString(100, 36));//2s
- System.out.println("-------------------------");
- }
+public class CloneExample implements Cloneable {
+ private int a;
+ private int b;
- //任意进制转换为十进制
- public static void test3(){
- System.out.println(Integer.parseInt("100", 10));//100
- System.out.println(Integer.parseInt("100", 2));//4
- System.out.println(Integer.parseInt("100", 8));//64
- System.out.println(Integer.parseInt("100", 16));//256
- System.out.println(Integer.parseInt("100", 23));//529
- //NumberFormatException,因为二进制是不可能存在 123的
- //System.out.println(Integer.parseInt("123", 2));
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ return super.clone();
}
}
```
-- JDK5的新特性
-
-自动装箱:把基本类型转换为包装类类型
+**2. 浅拷贝**
-自动拆箱:把包装类类型转换为基本类型
+拷贝对象和原始对象的引用类型引用同一个对象。
```java
-public class IntegerDemo4 {
- public static void main(String[] args) {
- Integer num=null;
- if(num!=null){
- num+=100;
- System.out.println(num);
+public class ShallowCloneExample implements Cloneable {
+
+ private int[] arr;
+
+ public ShallowCloneExample() {
+ arr = new int[10];
+ for (int i = 0; i < arr.length; i++) { + arr[i] = i; } - test(); //num:200 - test2(); //num:200 } - //自动装箱,拆箱 - public static void test() { - Integer num=100; - //自动装箱,实际上就是 Integer num=Integer.valueOf(100) - num+=100; - //先拆箱再装箱 num.intValue()+100=200 --> Integer num=Integer.valueOf(num.intValue()+100)
- System.out.println(new StringBuilder("num:").append(num).toString());
+ public void set(int index, int value) {
+ arr[index] = value;
}
-
- //手动拆装箱
- public static void test2() {
- Integer num=Integer.valueOf(100); //手动装箱
- num=Integer.valueOf(num.intValue()+100); //先手动拆箱,进行运算后,再手动装箱
- System.out.println(new StringBuilder("num:").append(num).toString());
+
+ public int get(int index) {
+ return arr[index];
}
-}
-```
-- 小练习:看程序写结果
-```java
-public class IntegerTest {
- public static void main(String[] args) {
- Integer i1 = new Integer(127);
- Integer i2 = new Integer(127);
- System.out.println(i1 == i2);
- System.out.println(i1.equals(i2));
- System.out.println("-----------");
-
- Integer i3 = new Integer(128);
- Integer i4 = new Integer(128);
- System.out.println(i3 == i4);
- System.out.println(i3.equals(i4));
- System.out.println("-----------");
-
- Integer i5 = 128;
- Integer i6 = 128;
- System.out.println(i5 == i6);
- System.out.println(i5.equals(i6));
- System.out.println("-----------");
-
- Integer i7 = 127;
- Integer i8 = 127;
- System.out.println(i7 == i8);
- System.out.println(i7.equals(i8));
+ @Override
+ protected ShallowCloneExample clone() throws CloneNotSupportedException {
+ return (ShallowCloneExample) super.clone();
}
}
```
-输出结果:
-```html
-false
-true
------------
-false
-true
------------
-false
-true
------------
-true
-true
-```
-分析:
-Integer的数据直接赋值,如果在-128到127之间,会直接从缓冲池里获取数据。
-通过查看源码,针对-128到127之间的数据,做了一个**数据缓冲池IntegerCache**,
-如果数据是该范围内的,每次并不创建新的空间。
```java
-public static Integer valueOf(int i) {
- if (i>= IntegerCache.low && i <= IntegerCache.high) - return IntegerCache.cache[i + (-IntegerCache.low)]; - return new Integer(i); - } - - private static class IntegerCache { - static final int low = -128; //low=-128 - static final int high; - static final Integer cache[]; - - static { - // high value may be configured by property - int h = 127; - //... - } - high = h; //high=127 +// 拷贝对象和原始对象的引用类型引用同一个对象。 +ShallowCloneExample e1 = new ShallowCloneExample(); +ShallowCloneExample e2 = null; +try { + e2 = e1.clone(); +} catch (CloneNotSupportedException e) { + e.printStackTrace(); +} +e1.set(2, 222); +System.out.println(e1.get(2)); // 222 +System.out.println(e2.get(2)); // 222 ``` -## Object -Object类是类层次结构的根类。每个类都使用 Object 作为超类。每个类都直接或者间接的继承自Object类。 -- Object类的方法: -```java -public int hashCode() //返回该对象的哈希码值。 -// 注意:哈希值是根据哈希算法计算出来的一个值,这个值和地址值有关,但是不是实际地址值。 +**3. 深拷贝** -public final Class getClass() //返回此 Object 的运行时类 +拷贝对象和原始对象的引用类型引用不同对象。 -public String toString() //返回该对象的字符串表示。 +```java +public class DeepCloneExample implements Cloneable { -protected Object clone() //创建并返回此对象的一个副本。可重写该方法 + private int[] arr; -protected void finalize() -//当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。用于垃圾回收,但是什么时候回收不确定。 -``` + public DeepCloneExample() { + arr = new int[10]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i; + } + } -- [Object的使用 代码示例](https://github.com/DuHouAn/Java/tree/master/JavaBasics/src/code_03_Object) + public void set(int index, int value) { + arr[index] = value; + } -## Scanner -Scanner:用于接收键盘录入数据。 + public int get(int index) { + return arr[index]; + } -- 使用Scanner三部曲: + @Override + protected DeepCloneExample clone() throws CloneNotSupportedException { + DeepCloneExample result = (DeepCloneExample) super.clone(); + // 创建新对象 + result.arr = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + result.arr[i] = arr[i]; + } + return result; + } +} +``` -A:导包 +```java +DeepCloneExample e1 = new DeepCloneExample(); +DeepCloneExample e2 = null; +try { + e2 = e1.clone(); +} catch (CloneNotSupportedException e) { + e.printStackTrace(); +} +e1.set(2, 222); +System.out.println(e1.get(2)); // 222 +System.out.println(e2.get(2)); // 2 +``` -B :创建对象 +**4. clone() 的替代方案** -C :使用相应方法 +使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。 +Effective Java 书上讲到,最好不要去使用 clone(),可以使用**拷贝构造函数或者拷贝工厂来拷贝一个对象**。 -- Scanner常用成员方法 ```java -Scanner(InputStream source) //构造方法 +public class CloneConstructorExample { -public boolean hasNextXxx() //判断是否是某种类型的元素,Xxx表示类型,比如 public boolean hasNextInt() - -public Xxx nextXxx() //获取该元素,Xxx表示类型,比如public int nextInt() -``` -注意:InputMismatchException:表示输入的和你想要的不匹配 + private int[] arr; -- 使用示例1: -```java -public class ScannerDemo { - public static void main(String[] args) { - Scanner sc=new Scanner(System.in); - if(sc.hasNextInt()){ - int x=sc.nextInt(); - System.out.println("x="+x); - }else{ - System.out.println("输入的数据有误"); + public CloneConstructorExample() { //构造函数 + arr = new int[10]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i; } } -} -``` -- 先获取一个数值,换行后,再获取一个字符串,会出现问题。主要原因:就是换行符号的问题。如何解决呢? -```java -public static void main(String[] args) { - Scanner sc=new Scanner(System.in); + public CloneConstructorExample(CloneConstructorExample original) { // 拷贝构造函数 + arr = new int[original.arr.length]; + for (int i = 0; i < original.arr.length; i++) { + arr[i] = original.arr[i]; + } + } - //输入 12 换行后在输出 "sss" - int x=sc.nextInt(); - System.out.println("x:"+x); //x:12 + public void set(int index, int value) { + arr[index] = value; + } - String line=sc.nextLine(); //这里会有问题 因为会将换行符当作字符输输入 - System.out.println("line:"+line); //line: + public int get(int index) { + return arr[index]; + } } ``` -> 解决方案一:先获取一个数值后,再创建一个新的键盘录入对象获取字符串。
-
```java
-public static void method() {
- Scanner sc=new Scanner(System.in);
- int x=sc.nextInt();
- System.out.println("x:"+x);
-
- Scanner sc2=new Scanner(System.in);
- String line=sc2.nextLine();
- System.out.println("line:"+line);
-}
+CloneConstructorExample e1 = new CloneConstructorExample();
+CloneConstructorExample e2 = new CloneConstructorExample(e1);
+e1.set(2, 222);
+System.out.println(e1.get(2)); // 222
+System.out.println(e2.get(2)); // 2
```
-> 解决方案二:把所有的数据都先按照字符串获取,然后要什么,就进行相应的转换。
-```java
-public static void method2() {
- Scanner sc=new Scanner(System.in);
- String xStr=sc.nextLine();
- String line=sc.nextLine();
+## String
- int x=Integer.parseInt(xStr);
-
- System.out.println("x:"+x);
- System.out.println("line:"+line);
-}
+String 被声明为 final,因此它不可被继承。
+
+**内部使用 char 数组存储数据,该数组被声明为 final**,
+这意味着 value 数组初始化之后就不能再引用其它数组。
+并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。
+
+```java
+public final class String
+ implements java.io.Serializable, Comparable, CharSequence {
+ /** The value is used for character storage. */
+ private final char value[];
```
-## String
-字符串:就是由多个字符组成的一串数据。也可以看成是一个字符数组。
-- **String的构造方法方法**:
+### 构造方法
+
```java
-//String的构造方法
public String() //空构造
public String(byte[] bytes) //把字节数组转成字符串
@@ -916,6 +410,8 @@ public String(char[] value,int index,int count) //把字符数组的一部分转
public String(String original) //把字符串常量值转成字符串
```
+使用示例:
+
```java
public class StringDemo {
public static void main(String[] args) {
@@ -961,7 +457,8 @@ public class StringDemo {
}
```
-- 字符串的特点:一旦被赋值,就不能改变
+字符串的特点:一旦被赋值,就不能改变
+
```java
public static void test() {
String s = "hello";
@@ -970,49 +467,29 @@ public static void test() {
}
```
-- 小练习:看程序,写结果:
+小练习:
+
```java
public static void test2(){
String s1 = new String("hello");
String s2 = new String("hello");
- System.out.println(s1 == s2);
- System.out.println(s1.equals(s2));
+ System.out.println(s1 == s2); // false
+ System.out.println(s1.equals(s2)); // true
String s3 = new String("hello");
String s4 = "hello";
- System.out.println(s3 == s4);
- System.out.println(s3.equals(s4));
+ System.out.println(s3 == s4); // false
+ System.out.println(s3.equals(s4)); // true
String s5 = "hello";
String s6 = "hello";
- System.out.println(s5 == s6);
- System.out.println(s5.equals(s6));
+ System.out.println(s5 == s6); // true
+ System.out.println(s5.equals(s6)); // true
}
```
-输出结果:
-```html
-false
-true
-false
-true
-true
-true
-```
-分析:
-> =和qeuals的区别
-
-==:比较引用类型,比较的是地址值是否相同
-
-equals:比较引用类型,默认也是比较地址值是否相同
-
-String类重写了equals()方法,比较的是内容是否相同。
-
-> String s = new String("hello")和String s = "hello"的区别?
+### 判断功能
-前者会创建2个对象,后者创建1个对象。更具体的说,前者会创建2个或者1个对象,后者会创建1个或者0个对象。
-
-- **String类的判断功能**:
```java
boolean equals(Object obj) //比较字符串的内容是否相同,区分大小写
@@ -1026,12 +503,16 @@ boolean endsWith(String str) //判断字符串是否以某个指定的字符串
boolean isEmpty()// 判断字符串是否为空
```
+
注意:字符串内容为空和字符串对象为空。
+
```java
String s = "";//字符串内容为空
String s = null;//字符串对象为空
```
+使用示例:
+
```java
public class StringDemo3 {
public static void main(String[] args) {
@@ -1080,7 +561,8 @@ public class StringDemo3 {
}
```
-- **String类的获取功能**:
+### 获取功能
+
```java
int length() //获取字符串的长度。
@@ -1099,6 +581,8 @@ String substring(int start) //从指定位置开始截取字符串,默认到末
String substring(int start,int end) //从指定位置开始到指定位置结束截取字符串。左闭右开
```
+使用示例:
+
```java
public class StringDemo4 {
public static void main(String[] args) {
@@ -1153,7 +637,8 @@ public class StringDemo4 {
}
```
-- **String的转换功能**
+### 转换功能
+
```java
byte[] getBytes() //字符串转换为字节数组。
@@ -1170,6 +655,8 @@ String toUpperCase() //把字符串转成大写。
String concat(String str) //把字符串拼接。
```
+使用示例:
+
```java
public class StringDemo5 {
public static void main(String[] args) {
@@ -1220,7 +707,8 @@ public class StringDemo5 {
}
```
-- 练习:将一个字符串的首字母转成大写,其余为小写。(只考虑英文大小写字母字符)
+练习:将一个字符串的首字母转成大写,其余为小写。(只考虑英文大小写字母字符)
+
```java
/**
* 需求:把一个字符串的首字母转成大写,其余为小写。(只考虑英文大小写字母字符)
@@ -1245,7 +733,8 @@ public class StringTest {
}
```
-- **String类的其他功能**:
+### 其他功能
+
```java
//替换功能:
String replace(char old,char new)
@@ -1260,7 +749,7 @@ int compareTo(String str)
int compareToIgnoreCase(String str)
```
-> compareTo()方法源码解析
+compareTo() 方法源码解析:
```java
private final char value[]; //字符串会自动转换为一个字符数组。
@@ -1292,7 +781,10 @@ public int compareTo(String anotherString) {
}
```
-- 练习1:把数组中的数据按照指定个格式拼接成一个字符串。举例:int[] arr = {1,2,3};输出结果:[1, 2, 3]
+### 练习
+
+练习1:把数组中的数据按照指定个格式拼接成一个字符串。举例:int[] arr = {1,2,3};输出结果:[1, 2, 3]
+
```java
/**
*
@@ -1334,7 +826,8 @@ public class StringTest2 {
}
```
-- 练习2:统计大串中小串出现的次数
+练习2:统计大串中小串出现的次数
+
```java
/**
* 统计大串中小串出现的次数
@@ -1376,9 +869,12 @@ public class StringTest3 {
}
```
+
+
## StringBuffer
-- **StringBuffer的常用成员方法**:
+### 常用成员方法
+
```java
//StirngBuffer的构造方法
public StringBuffer() //无参构造方法
@@ -1392,6 +888,9 @@ public int capacity() //返回当前容量。 理论值
public int length() //返回长度(字符数)。 实际值
```
+
+使用示例:
+
```java
public static void main(String[] args) {
// public StringBuffer():无参构造方法
@@ -1416,13 +915,18 @@ public static void main(String[] args) {
}
```
-- **StringBuffer的添加功能**:
+### 添加功能
+
```java
-public StringBuffer append(String str) //可以把任意类型数据添加到字符串缓冲区里面,并返回字符串缓冲区本身
+public StringBuffer append(String str)
+//可以把任意类型数据添加到字符串缓冲区里面,并返回字符串缓冲区本身
-public StringBuffer insert(int offset,String str) //在指定位置把任意类型的数据插入到字符串缓冲区里面,并返回字符串缓冲区本身
+public StringBuffer insert(int offset,String str)
+//在指定位置把任意类型的数据插入到字符串缓冲区里面,并返回字符串缓冲区本身
```
+使用示例:
+
```java
public static void main(String[] args) {
// 创建字符串缓冲区对象
@@ -1439,12 +943,18 @@ public static void main(String[] args) {
}
```
-- **StringBuffer的删除功能**
+### 删除功能
+
```java
-public StringBuffer deleteCharAt(int index) //删除指定位置的字符,并返回本身
+public StringBuffer deleteCharAt(int index)
+//删除指定位置的字符,并返回本身
-public StringBuffer delete(int start,int end) //删除从指定位置开始指定位置结束的内容,并返回本身
+public StringBuffer delete(int start,int end)
+//删除从指定位置开始指定位置结束的内容,并返回本身
```
+
+使用示例:
+
```java
public static void main(String[] args) {
// 创建对象
@@ -1465,10 +975,15 @@ public static void main(String[] args) {
}
```
-- **StringBuffer的替换功能**
+### 替换功能
+
```java
-public StringBuffer replace(int start,int end,String str) //从start开始到end用str替换
+public StringBuffer replace(int start,int end,String str)
+//从start开始到end用str替换
```
+
+使用示例:
+
```java
public static void main(String[] args) {
// 创建字符串缓冲区对象
@@ -1487,10 +1002,14 @@ public static void main(String[] args) {
}
```
-- **StringBuffer的反转功能**:
+### 反转功能
+
```java
public StringBuffer reverse()
```
+
+使用示例:
+
```java
public static void main(String[] args) {
// 创建字符串缓冲区对象
@@ -1506,13 +1025,16 @@ public static void main(String[] args) {
}
```
-- **StringBuffer的截取功能**:
+### 截取功能
```java
-public String substring(int start) //TODO:注意截取返回的是String,而不是StringBuffer了
+public String substring(int start) // 注意截取返回的是String,而不是StringBuffer了
public String substring(int start,int end)
```
+
+使用示例:
+
```java
public static void main(String[] args) {
// 创建字符串缓冲区对象
@@ -1535,7 +1057,10 @@ public static void main(String[] args) {
}
```
-- **String和StringBuffer的相互转换**:
+
+
+## String 和 StringBuffer 的相互转换
+
```java
public class StringBufferDemo7 {
public static void main(String[] args) {
@@ -1571,7 +1096,9 @@ public class StringBufferDemo7 {
}
```
-- 练习1:将数组拼接成一个字符串
+### 练习
+
+练习1:将数组拼接成一个字符串
```java
public class StringBufferTest {
@@ -1596,7 +1123,8 @@ public class StringBufferTest {
}
```
-- 练习2:判断一个字符串是否是对称字符串
+练习2:判断一个字符串是否是对称字符串
+
```java
/**
* 判断一个字符串是否是对称字符串
@@ -1637,7 +1165,9 @@ public class StringBufferTest2 {
}
}
```
-- 练习3:看程序写结果
+
+练习3:看程序写结果
+
```java
public class StringBufferTest3 {
public static void main(String[] args) {
@@ -1667,11 +1197,40 @@ public class StringBufferTest3 {
}
}
```
+
输出结果:
+
```html
hello---world
hello---world
hello---world
hello---worldworld
```
-String作为参数传递,效果和基本类型作为参数传递是一样的。
\ No newline at end of file
+
+String作为参数传递,效果和基本类型作为参数传递是一样的。
+
+## String, StringBuffer and StringBuilder
+
+**1. 可变性**
+
+- String 不可变
+- StringBuffer 和 StringBuilder 可变
+
+**2. 线程安全**
+
+- String 不可变,因此是线程安全的
+- StringBuilder 不是线程安全的
+- StringBuffer 是线程安全的,内部使用 synchronized 进行同步
+
+**3. 性能**
+
+- Java中对String对象进行的操作实际上是一个不断创建新的对象并且将旧的对象回收的一个过程,所以执行速度很慢
+- StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用
+- StringBuilder每次都会对StringBuilder对象本身进行操作,而不是生成新的对象并改变对象引用。
+ 相同情况下使用StirngBuilder相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却冒多线程不安全的风险。
+
+> **三者使用的总结**
+
+- 操作少量的数据,使用String
+- 单线程操作字符串缓冲区下操作大量数据,使用StringBuilder
+- 多线程操作字符串缓冲区下操作大量数据,使用StringBuffer
\ No newline at end of file
diff --git "a/docs/JavaBasics/10_Java345円270円270円350円247円201円345円257円271円350円261円241円_2.md" "b/docs/JavaBasics/10_Java345円270円270円350円247円201円345円257円271円350円261円241円_2.md"
new file mode 100644
index 00000000..ba733417
--- /dev/null
+++ "b/docs/JavaBasics/10_Java345円270円270円350円247円201円345円257円271円350円261円241円_2.md"
@@ -0,0 +1,687 @@
+# Java 常见类 II
+
+## Arrays
+Arrays 是针对数组进行操作的工具类。
+
+### 常用成员方法
+
+```java
+public static String toString(int[] a) //把数组转成字符串
+public static void sort(int[] a) //对数组进行排序
+public static int binarySearch(int[] a,int key) //二分查找
+```
+
+其中,toString() 源码如下:
+
+```java
+public static String toString(int[] a) {
+ if (a == null)
+ return "null";
+ int iMax = a.length - 1;
+ if (iMax == -1)
+ return "[]";
+
+ StringBuilder b = new StringBuilder();
+ b.append('[');
+ for (int i = 0; ; i++) {
+ b.append(a[i]);
+ if (i == iMax)
+ return b.append(']').toString();
+ b.append(", ");
+ }
+}
+```
+
+binarySearch() 调用的是 binarySearch0(),binarySearch0() 源码如下:
+
+```java
+private static int binarySearch0(int[] a, int fromIndex, int toIndex,int key) {
+ int low = fromIndex;
+ int high = toIndex - 1;
+
+ while (low <= high) { + int mid = (low + high)>>> 1;
+ int midVal = a[mid];
+
+ if (midVal < key) + low = mid + 1; + else if (midVal> key)
+ high = mid - 1;
+ else
+ return mid; // key found
+ }
+ return -(low + 1); // key not found.
+}
+```
+
+### 使用示例
+
+```java
+public class ArraysDemo {
+ public static void main(String[] args) {
+ // 定义一个数组
+ int[] arr = { 24, 69, 80, 57, 13 };
+
+ // public static String toString(int[] a) 把数组转成字符串
+ System.out.println("排序前:" + Arrays.toString(arr));//排序前:[24, 69, 80, 57, 13]
+
+ // public static void sort(int[] a) 对数组进行排序
+ Arrays.sort(arr);
+ System.out.println("排序后:" + Arrays.toString(arr));//排序后:[13, 24, 57, 69, 80]
+
+ // [13, 24, 57, 69, 80]
+ // public static int binarySearch(int[] a,int key) 二分查找
+ System.out.println("binarySearch:" + Arrays.binarySearch(arr, 57));//binarySearch:2
+ System.out.println("binarySearch:" + Arrays.binarySearch(arr, 577));//binarySearch:-6
+ }
+}
+```
+
+
+
+## BigDemical
+
+BigDecimal类:不可变的、任意精度的有符号十进制数,可以解决数据丢失问题。
+
+看如下程序,写出结果:
+```java
+public static void main(String[] args) {
+ System.out.println(0.09 + 0.01); // 0.09999999999999999
+ System.out.println(1.0 - 0.32); // 0.6799999999999999
+ System.out.println(1.015 * 100); // 101.49999999999999
+ System.out.println(1.301 / 100); // 0.013009999999999999
+ System.out.println(1.0 - 0.12); // 0.88
+}
+```
+结果和我们想的有一点点不一样,这是因为浮点数类型的数据存储和整数不一样导致的。
+它们大部分的时候,都是带有有效数字位。由于在运算的时候,float类型和double很容易丢失精度,
+所以,为了能精确的表示、计算浮点数,Java 提供了 BigDecimal。
+
+### 常用成员方法
+
+```java
+public BigDecimal(String val) //构造方法
+
+public BigDecimal add(BigDecimal augend) //加
+
+public BigDecimal subtract(BigDecimal subtrahend)//减
+
+public BigDecimal multiply(BigDecimal multiplicand) //乘
+
+public BigDecimal divide(BigDecimal divisor) //除
+
+public BigDecimal divide(BigDecimal divisor,int scale,int roundingMode)
+//除法,scale:几位小数,roundingMode:如何舍取
+```
+
+### 使用示例
+
+```java
+public static void main(String[] args) {
+ /*System.out.println(0.09 + 0.01);
+ System.out.println(1.0 - 0.32);
+ System.out.println(1.015 * 100);
+ System.out.println(1.301 / 100);
+ System.out.println(1.0 - 0.12);*/
+
+ BigDecimal bd1 = new BigDecimal("0.09");
+ BigDecimal bd2 = new BigDecimal("0.01");
+ System.out.println("add:" + bd1.add(bd2));//add:0.10
+ System.out.println("-------------------");
+
+ BigDecimal bd3 = new BigDecimal("1.0");
+ BigDecimal bd4 = new BigDecimal("0.32");
+ System.out.println("subtract:" + bd3.subtract(bd4));//subtract:0.68
+ System.out.println("-------------------");
+
+ BigDecimal bd5 = new BigDecimal("1.015");
+ BigDecimal bd6 = new BigDecimal("100");
+ System.out.println("multiply:" + bd5.multiply(bd6));//multiply:101.500
+ System.out.println("-------------------");
+
+ BigDecimal bd7 = new BigDecimal("1.301");
+ BigDecimal bd8 = new BigDecimal("100");
+ System.out.println("divide:" + bd7.divide(bd8));//divide:0.01301
+
+ //四舍五入
+ System.out.println("divide:"
+ + bd7.divide(bd8, 3, BigDecimal.ROUND_HALF_UP));//保留三位有效数字
+ //divide:0.013
+
+ System.out.println("divide:"
+ + bd7.divide(bd8, 8, BigDecimal.ROUND_HALF_UP));//保留八位有效数字
+ //divide:0.01301000
+}
+```
+
+
+
+## BigInteger
+
+BigInteger:可以让超过 Integer 范围内的数据进行运算。
+
+### 常用成员方法
+
+```java
+public BigInteger add(BigInteger val) //加
+
+public BigInteger subtract(BigInteger val) //减
+
+public BigInteger multiply(BigInteger val) //乘
+
+public BigInteger divide(BigInteger val) //除
+
+public BigInteger[] divideAndRemainder(BigInteger val)//返回商和余数的数组
+```
+
+### 使用示例
+
+示例1:
+
+```java
+public class BigIntegerDemo {
+ public static void main(String[] args) {
+ Integer num = new Integer("2147483647");
+ System.out.println(num);
+
+ //Integer num2 = new Integer("2147483648");
+ // Exception in thread "main" java.lang.NumberFormatException: For input string: "2147483648"
+ //System.out.println(num2);
+
+ // 通过 BigIntege来创建对象
+ BigInteger num2 = new BigInteger("2147483648");
+ System.out.println(num2);
+ }
+}
+```
+
+示例2:
+
+```java
+public class BigIntegerDemo2 {
+ public static void main(String[] args) {
+ BigInteger bi1 = new BigInteger("100");
+ BigInteger bi2 = new BigInteger("50");
+
+ // public BigInteger add(BigInteger val):加
+ System.out.println("add:" + bi1.add(bi2)); //add:150
+ // public BigInteger subtract(BigInteger Val):减
+ System.out.println("subtract:" + bi1.subtract(bi2));//subtract:50
+ // public BigInteger multiply(BigInteger val):乘
+ System.out.println("multiply:" + bi1.multiply(bi2));//multiply:5000
+ // public BigInteger divide(BigInteger val):除
+ System.out.println("divide:" + bi1.divide(bi2));//divide:2
+
+ // public BigInteger[] divideAndRemainder(BigInteger val):返回商和余数的数组
+ BigInteger[] bis = bi1.divideAndRemainder(bi2);
+ System.out.println("divide:" + bis[0]);//divide:2
+ System.out.println("remainder:" + bis[1]);//remainder:0
+ }
+}
+```
+
+
+
+
+
+## Character
+
+Character 类在对象中包装一个基本类型 char 的值.此外,该类提供了几种方法,
+以确定字符的类别(小写字母,数字,等等),并将字符从大写转换成小写,反之亦然。
+
+### 常用成员方法
+
+```java
+Character(char value) //构造方法
+
+public static boolean isUpperCase(char ch) //判断给定的字符是否是大写字符
+
+public static boolean isLowerCase(char ch) //判断给定的字符是否是小写字符
+
+public static boolean isDigit(char ch) //判断给定的字符是否是数字字符
+
+public static char toUpperCase(char ch) //把给定的字符转换为大写字符
+
+public static char toLowerCase(char ch) //把给定的字符转换为小写字符
+```
+
+### 使用示例
+
+```java
+public class CharacterDemo {
+ public static void main(String[] args) {
+ // public static boolean isUpperCase(char ch):判断给定的字符是否是大写字符
+ System.out.println("isUpperCase:" + Character.isUpperCase('A'));//true
+ System.out.println("isUpperCase:" + Character.isUpperCase('a'));//false
+ System.out.println("isUpperCase:" + Character.isUpperCase('0'));//false
+ System.out.println("-----------------------------------------");
+
+ // public static boolean isLowerCase(char ch):判断给定的字符是否是小写字符
+ System.out.println("isLowerCase:" + Character.isLowerCase('A'));//false
+ System.out.println("isLowerCase:" + Character.isLowerCase('a'));//true
+ System.out.println("isLowerCase:" + Character.isLowerCase('0'));//false
+ System.out.println("-----------------------------------------");
+
+ // public static boolean isDigit(char ch):判断给定的字符是否是数字字符
+ System.out.println("isDigit:" + Character.isDigit('A'));//false
+ System.out.println("isDigit:" + Character.isDigit('a'));//false
+ System.out.println("isDigit:" + Character.isDigit('0'));//true
+ System.out.println("-----------------------------------------");
+
+ // public static char toUpperCase(char ch):把给定的字符转换为大写字符
+ System.out.println("toUpperCase:" + Character.toUpperCase('A'));//A
+ System.out.println("toUpperCase:" + Character.toUpperCase('a'));//A
+ System.out.println("-----------------------------------------");
+
+ // public static char toLowerCase(char ch):把给定的字符转换为小写字符
+ System.out.println("toLowerCase:" + Character.toLowerCase('A'));//a
+ System.out.println("toLowerCase:" + Character.toLowerCase('a'));//a
+ }
+}
+```
+
+### 练习
+
+统计一个字符串中大写字母字符,小写字母字符,数字字符出现的次数。(不考虑其他字符)
+
+```java
+/**
+ * 统计一个字符串中大写字母字符,小写字母字符,数字字符出现的次数。(不考虑其他字符)
+ *
+ * 分析:
+ * A:定义三个统计变量。
+ * int bigCont=0;
+ * int smalCount=0;
+ * int numberCount=0;
+ * B:键盘录入一个字符串。
+ * C:把字符串转换为字符数组。
+ * D:遍历字符数组获取到每一个字符
+ * E:判断该字符是
+ * 大写 bigCount++;
+ * 小写 smalCount++;
+ * 数字 numberCount++;
+ * F:输出结果即可
+ */
+public class CharacterTest {
+ public static void main(String[] args) {
+ Scanner sc=new Scanner(System.in);
+ String str=sc.nextLine();
+ printCount(str);
+ printCount2(str);
+ }
+
+ //原来的写法
+ public static void printCount(String str) {
+ int numberCount=0;
+ int lowercaseCount=0;
+ int upercaseCount=0;
+
+ for(int index=0;index='0' && ch<='9'){ + numberCount++; + }else if(ch>='A' && ch<='z'){ + upercaseCount++; + }else if(ch>='a' && ch<='z'){ + lowercaseCount++; + } + } + System.out.println("数字有"+numberCount+"个"); + System.out.println("小写字母有"+lowercaseCount+"个"); + System.out.println("大写字母有"+upercaseCount+"个"); + } + + //使用包装类来改进 + public static void printCount2(String str) { + int numberCount=0; + int lowercaseCount=0; + int upercaseCount=0; + + for(int index=0;index 解决方案一:先获取一个数值后,再创建一个新的键盘录入对象获取字符串。
+
+```java
+public static void method() {
+ Scanner sc=new Scanner(System.in);
+ int x=sc.nextInt();
+ System.out.println("x:"+x);
+
+ Scanner sc2=new Scanner(System.in);
+ String line=sc2.nextLine();
+ System.out.println("line:"+line);
+}
+```
+
+> 解决方案二:把所有的数据都先按照字符串获取,然后要什么,就进行相应的转换。
+
+```java
+public static void method2() {
+ Scanner sc=new Scanner(System.in);
+
+ String xStr=sc.nextLine();
+ String line=sc.nextLine();
+
+ int x=Integer.parseInt(xStr);
+
+ System.out.println("x:"+x);
+ System.out.println("line:"+line);
+}
+```
+
+
+
+## Calendar
+
+Calendar为特定瞬间与一组诸如 YEAR、MONTH、DAY_OF_MONTH、HOUR 等日历字段之间的转换提供了一些方法,
+并为操作日历字段(例如获得下星期的日期)提供了一些方法。
+
+### 常用成员方法
+
+```java
+public int get(int field)
+//返回给定日历字段的值。日历类中的每个日历字段都是静态的成员变量,并且是int类型。
+
+public void add(int field,int amount)
+//根据给定的日历字段和对应的时间,来对当前的日历进行操作。
+
+public final void set(int year,int month,int date)
+//设置当前日历的年月日
+```
+
+### 使用示例
+
+示例1:
+
+```java
+public class CalendarDemo {
+ public static void main(String[] args) {
+ // 其日历字段已由当前日期和时间初始化:
+ Calendar rightNow = Calendar.getInstance(); // 子类对象
+ int year=rightNow.get(Calendar.YEAR);
+ int month=rightNow.get(Calendar.MONTH);//注意月份是从0开始的
+ int date=rightNow.get(Calendar.DATE);
+ System.out.println(year + "年" + (month + 1) + "月" + date + "日");
+ //2018年12月25日
+ }
+}
+```
+
+示例2:
+
+```java
+public class CalendarDemo2 {
+ public static void main(String[] args) {
+ // 其日历字段已由当前日期和时间初始化:
+ Calendar calendar = Calendar.getInstance(); // 子类对象
+ System.out.println(getYearMonthDay(calendar));//2018年12月25日
+
+ //三年前的今天
+ calendar.add(Calendar.YEAR,-3);
+ System.out.println(getYearMonthDay(calendar));//2015年12月25日
+
+ //5年后的10天前
+ calendar.add(Calendar.YEAR,5);
+ calendar.add(Calendar.DATE,-10);
+ System.out.println(getYearMonthDay(calendar));//2020年12月15日
+
+ //设置 2011年11月11日
+ calendar.set(2011,10,11);
+ System.out.println(getYearMonthDay(calendar));//2011年11月11日
+ }
+
+ //获取年、月、日
+ public static String getYearMonthDay(Calendar calendar){
+ int year=calendar.get(Calendar.YEAR);
+ int month=calendar.get(Calendar.MONTH);
+ int date=calendar.get(Calendar.DATE);
+ return year + "年" + (month + 1) + "月" + date + "日";
+ }
+}
+```
+
+### 练习
+
+获取任意一年的二月有多少天
+
+```java
+/**
+ *获取任意一年的二月有多少天
+ *分析:
+ * A:键盘录入任意的年份
+ * B:设置日历对象的年月日
+ * 年就是输入的数据
+ * 月是2
+ * 日是1
+ * C:把时间往前推一天,就是2月的最后一天
+ * D:获取这一天输出即可
+ */
+public class CalendarTest {
+ public static void main(String[] args) {
+ Scanner sc=new Scanner(System.in);
+ int year=sc.nextInt();
+ Calendar c= Calendar.getInstance();
+ c.set(year,2,1); //得到的就是该年的3月1日
+ c.add(Calendar.DATE,-1);//把时间往前推一天,就是2月的最后一天
+ //public void add(int field,int amount):根据给定的日历字段和对应的时间,来对当前的日历进行操作。
+
+ System.out.println(year+"年,二月有"+c.get(Calendar.DATE)+"天");
+ }
+}
+```
+
+
+
+## Date
+
+Date: 表示特定的瞬间,精确到毫秒。
+
+### 常用成员方法
+
+```java
+Date() //根据当前的默认毫秒值创建日期对象
+
+Date(long date) //根据给定的毫秒值创建日期对象
+
+public long getTime() //获取时间,以毫秒为单位
+
+public void setTime(long time) //设置时间
+```
+
+### 使用示例
+
+```java
+/**
+ * 把一个毫秒值转换为Date,有两种方式:
+ * (1)构造方法
+ * (2)setTime(long time)
+ */
+public class DateDemo {
+ public static void main(String[] args) {
+ // Date():根据当前的默认毫秒值创建日期对象
+ Date d = new Date();
+ System.out.println("d:" + d);
+ //d:Tue Dec 25 20:01:17 GMT+08:00 2018 --> 当前时间
+
+ // Date(long date):根据给定的毫秒值创建日期对象
+ //long time = System.currentTimeMillis();
+ long time = 1000 * 60 * 60; // 1小时
+ Date d2 = new Date(time);
+ System.out.println("d2:" + d2);
+ //格林威治时间 1970年01月01日00时00分00
+ //Thu Jan 01 09:00:00 GMT+08:00 1970 GMT+表示 标准时间加8小时,因为中国是东八区
+
+ // 获取时间
+ long time2 = d.getTime();
+ System.out.println(time2); //1545739438466 毫秒
+ System.out.println(System.currentTimeMillis());
+
+ // 设置时间
+ d.setTime(1000*60*60);
+ System.out.println("d:" + d);
+ //Thu Jan 01 09:00:00 GMT+08:00 1970
+ }
+}
+```
+
+### Date 和 String 类型的相互转换
+
+```java
+public class DateFormatDemo {
+ public static void main(String[] args) {
+ Date date=new Date();
+ SimpleDateFormat sdf=new SimpleDateFormat("yyyy年MM月dd日");
+ String s=dateToString(date,sdf);
+ System.out.println(s); //2018年12月25日
+ System.out.println(stringToDate(s,sdf));//Tue Dec 25 00:00:00 GMT+08:00 2018
+ }
+
+ /**
+ * Date -- String(格式化)
+ * public final String format(Date date)
+ */
+ public static String dateToString(Date d, SimpleDateFormat sdf) {
+ return sdf.format(d);
+ }
+
+ /**
+ * * String -- Date(解析)
+ * public Date parse(String source)
+ */
+ public static Date stringToDate(String s, SimpleDateFormat sdf){
+ Date date=null;
+ try {
+ date=sdf.parse(s);
+ } catch (ParseException e) {
+ e.printStackTrace();
+ }
+ return date;
+ }
+}
+```
+
+## DateFormat
+
+DateForamt:可以进行日期和字符串的格式化和解析,但是由于是抽象类,所以使用具体子类SimpleDateFormat。
+
+### SimpleDateFormat 构造方法
+
+```java
+SimpleDateFormat() //默认模式
+
+SimpleDateFormat(String pattern) //给定的模式
+```
+
+这个模式字符串该如何写呢? 通过查看 API,我们就找到了对应的模式:
+
+| 中文说明 | 模式字符 |
+| :------: | :------: |
+| 年 | y |
+| 月 | M |
+| 日 | d |
+| 时 | H |
+| 分 | m |
+| 秒 | s |
+
+### 练习
+
+算一下你来到这个世界多少天?
+
+```java
+小练习:/**
+ * *
+ * 算一下你来到这个世界多少天?
+ *
+ * 分析:
+ * A:键盘录入你的出生的年月日
+ * B:把该字符串转换为一个日期
+ * C:通过该日期得到一个毫秒值
+ * D:获取当前时间的毫秒值
+ * E:用D-C得到一个毫秒值
+ * F:把E的毫秒值转换为年
+ * /1000/60/60/24
+ */
+public class DateTest {
+ public static void main(String[] args) throws ParseException {
+ // 键盘录入你的出生的年月日
+ Scanner sc = new Scanner(System.in);
+ System.out.println("请输入你的出生年月日(格式 yyyy-MM-dd):");
+ String line = sc.nextLine();
+
+ // 把该字符串转换为一个日期
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+ Date d = sdf.parse(line);
+ long birth=d.getTime(); //出生的时间
+ long current=System.currentTimeMillis();//当前时间
+
+ long days=(current-birth)/1000/60/60/24;
+ System.out.println("你出生了"+days+"天");
+ }
+}
+```
+
diff --git "a/docs/JavaBasics/12_345円205円266円344円273円226円.md" "b/docs/JavaBasics/12_345円205円266円344円273円226円.md"
deleted file mode 100644
index 54d1f98b..00000000
--- "a/docs/JavaBasics/12_345円205円266円344円273円226円.md"
+++ /dev/null
@@ -1,54 +0,0 @@
-# 其他
-
-## Java 各版本的新特性
-
-**New highlights in Java SE 8**
-
-1. Lambda Expressions
-2. Pipelines and Streams
-3. Date and Time API
-4. Default Methods
-5. Type Annotations
-6. Nashhorn JavaScript Engine
-7. Concurrent Accumulators
-8. Parallel operations
-9. PermGen Error Removed
-
-**New highlights in Java SE 7**
-
-1. Strings in Switch Statement
-2. Type Inference for Generic Instance Creation
-3. Multiple Exception Handling
-4. Support for Dynamic Languages
-5. Try with Resources
-6. Java nio Package
-7. Binary Literals, Underscore in literals
-8. Diamond Syntax
-
-- [Difference between Java 1.8 and Java 1.7?](http://www.selfgrowth.com/articles/difference-between-java-18-and-java-17)
-- [Java 8 特性](http://www.importnew.com/19345.html)
-
-## Java 与 C++ 的区别
-
-- Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 为了兼容 C 即支持面向对象也支持面向过程。
-- Java 通过虚拟机从而实现跨平台特性,但是 C++ 依赖于特定的平台。
-- Java 没有指针,它的引用可以理解为安全指针,而 C++ 具有和 C 一样的指针。
-- Java 支持自动垃圾回收,而 C++ 需要手动回收。
-- Java 不支持多重继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多重继承。
-- Java 不支持操作符重载,虽然可以对两个 String 对象执行加法运算,但是这是语言内置支持的操作,不属于操作符重载,而 C++ 可以。
-- Java 的 goto 是保留字,但是不可用,C++ 可以使用 goto。
-- Java 不支持条件编译,C++ 通过 #ifdef #ifndef 等预处理命令从而实现条件编译。
-
-[What are the main differences between Java and C++?](http://cs-fundamentals.com/tech-interview/java/differences-between-java-and-cpp.php)
-
-## JRE or JDK
-
-- JRE is the JVM program, Java application need to run on JRE.
-- JDK is a superset of JRE, JRE + tools for developing java programs. e.g, it provides the compiler "javac"
-
-## Java基础学习书籍推荐
-- 《Head First Java.第二版》
-
-- 《Java核心技术卷1+卷2》
-
-- 《Java编程思想(第4版)》
diff --git "a/docs/JavaBasics/1_346円225円260円346円215円256円347円261円273円345円236円213円.md" "b/docs/JavaBasics/1_346円225円260円346円215円256円347円261円273円345円236円213円.md"
index 03e7d73e..13d74dc6 100644
--- "a/docs/JavaBasics/1_346円225円260円346円215円256円347円261円273円345円236円213円.md"
+++ "b/docs/JavaBasics/1_346円225円260円346円215円256円347円261円273円345円236円213円.md"
@@ -1,18 +1,20 @@
# 数据类型
## 包装类型
-八个基本类型:
+Java 八个基本类型,基本类型都有对应的包装类型:
-- boolean/1
-- byte/8
-- char/16
-- short/16
-- int/32
-- float/32
-- long/64
-- double/64
+| 基本类型 | 大小(bit) | 最小值 | 最大值 | 默认值 | 包装类 |
+| :------: | :---------: | :-------: | :------------: | :---------------: | :-------: |
+| boolean | 1 | - | - | false | Boolean |
+| char | 16 | Unicode 0 | Unicode 2^16-1 | \u0000(Unicode 0) | Character |
+| byte | 8 | -128 | 127 | (byte)0 | Byte |
+| short | 16 | -2^15 | 2^15-1 | (short)0 | Short |
+| int | 32 | -2^31 | 2^31-1 | 0 | Int |
+| long | 64 | -2^63 | 2^63-1 | 0L | Long |
+| float | 32 | - | - | 0.0F | Float |
+| double | 64 | - | - | 0.0D | Double |
-基本类型都有对应的包装类型,基本类型与其对应的包装类型之间的赋值使用**自动装箱**与**自动拆箱**完成。
+ 基本类型与其对应的包装类型之间的赋值使用**自动装箱**与**自动拆箱**完成。
```java
//自动装箱和自动拆箱
@@ -26,6 +28,14 @@ x=Integer.valueOf(x.intValue()+5);//x.intValue()就是拆箱-->先拆箱,再
## 缓存池
+Java 基本类型的包装类的大部分都实现了缓存池技术,对应的缓冲池如下:
+
+- Boolean 直接返回 true / false
+- Byte/ Short / Integer / Long 创建了数值范围在 [-128,127] 的相应类型的缓存数据
+- Character 创建了数值在 [0,127] 的缓存数据
+
+如果超出对应范围仍然会去创建新的对象。
+
new Integer(123) 与 Integer.valueOf(123) 的区别在于:
- new Integer(123) 每次都会新建一个对象;
@@ -92,12 +102,12 @@ Integer n = 123;
System.out.println(m == n); // true
```
-基本类型对应的缓冲池如下:
+\+ 不适用于 Integer 对象,首先 i,j 进行自动拆箱,然后数值相加,得到数值 80,进行自动装箱后,和 m 引用相同的对象。Integer 对象无法与数值直接进行比较,自动拆箱后转为数值 80,显然 80 == 80,输出 true。
-- boolean values true and false
-- all byte values
-- short values between -128 and 127
-- int values between -128 and 127
-- char in the range \u0000 to \u007F
-
-在使用这些基本类型对应的包装类型时,就可以**直接使用缓冲池中的对象**。
+```java
+Integer i = 40;
+Integer j = 40;
+Integer m = 80;
+System.out.println(m == i+j); // true
+System.out.println(80 == i+j); // true
+```
diff --git "a/docs/JavaBasics/3_350円277円220円347円256円227円.md" "b/docs/JavaBasics/3_350円277円220円347円256円227円.md"
index 2bb2cdb5..35b2fa42 100644
--- "a/docs/JavaBasics/3_350円277円220円347円256円227円.md"
+++ "b/docs/JavaBasics/3_350円277円220円347円256円227円.md"
@@ -1,4 +1,43 @@
-# 运算
+# 基本运算
+
+## == 和 equals()
+
+- == 判断两个对象的地址是不是相等。即判断两个对象是不是同一个对象。
+ - 基本数据类型:== 比较的是值
+ - 引用数据类型:== 比较的是内存地址
+
+- equals() 判断两个对象是否相等。但它一般有两种使用情况:
+ - 情况1:类没有重写 equals() 方法。等价于"=="。
+ - 情况2:类重写了 equals() 方法。一般用来**比较两个对象的内容**,若它们的内容相等,则返回 true (即,认为这两个对象相等)。
+
+注意:
+
+- String 中的 **equals 方法是被重写过**的,因为 object 的 equals 方法是比较的对象的内存地址, 而 String 的 equals 方法比较的是对象的值。
+
+- 当创建 String 类型的对象时,虚拟机会在**常量池**中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。
+ 如果没有就在常量池中重新创建一个 String 对象。
+
+```java
+public class EqualsDemo {
+ public static void main(String[] args) {
+ String a = new String("ab"); // a 为一个引用
+ String b = new String("ab"); // b为另一个引用,对象的内容一样
+ String aa = "ab"; // 放在常量池中
+ String bb = "ab"; // 从常量池中查找
+ if (aa == bb) // true
+ System.out.println("aa==bb");
+ if (a == b) // false,非同一对象
+ System.out.println("a==b");
+ if (a.equals(b)) // true
+ System.out.println("aEQb");
+ if (42 == 42.0) { // true
+ System.out.println("true");
+ }
+ }
+}
+```
+
+
## 参数传递
@@ -6,8 +45,7 @@ Java 的参数是以**值传递**的形式传入方法中,而不是引用传
以下代码中 Dog dog 的 dog 是一个指针,存储的是对象的地址。
在将一个参数传入一个方法时,本质上是**将对象的地址以值的方式传递到形参中**。
-因此在方法中使指针引用其它对象,那么这两个指针此时指向的是完全不同的对象,
-在一方改变其所指向对象的内容时对另一方没有影响。
+因此在方法中使指针引用其它对象,那么这两个指针此时指向的是完全不同的对象,在一方改变其所指向对象的内容时对另一方没有影响。
```java
public class Dog {
@@ -67,47 +105,7 @@ class PassByValueExample {
}
```
-## ==和equals()
-- == 判断两个对象的地址是不是相等。即判断两个对象是不是同一个对象。
-
-
-基本数据类型"=="比较的是值
-
-引用数据类型"=="比较的是内存地址
-
-- equals()判断两个对象是否相等。但它一般有两种使用情况:
-情况1:类没有重写 equals() 方法。等价于"=="。
-
-情况2:类重写了 equals() 方法。一般用来**比较两个对象的内容**;
-若它们的内容相等,则返回 true (即,认为这两个对象相等)。
-
-```java
-public class EqualsDemo {
- public static void main(String[] args) {
- String a = new String("ab"); // a 为一个引用
- String b = new String("ab"); // b为另一个引用,对象的内容一样
- String aa = "ab"; // 放在常量池中
- String bb = "ab"; // 从常量池中查找
- if (aa == bb) // true
- System.out.println("aa==bb");
- if (a == b) // false,非同一对象
- System.out.println("a==b");
- if (a.equals(b)) // true
- System.out.println("aEQb");
- if (42 == 42.0) { // true
- System.out.println("true");
- }
- }
-}
-```
-> **说明**
-
-- String 中的 **equals 方法是被重写过**的,因为 object 的 equals 方法是比较的对象的内存地址,
-而 String 的 equals 方法比较的是对象的值。
-
-- 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。
-如果没有就在常量池中重新创建一个 String 对象。
## float 与 double
@@ -125,6 +123,8 @@ Java 不能隐式执行向下转型,因为这会使得精度降低。
float f = 1.1f;
```
+
+
## 隐式类型转换
因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型下转型为 short 类型。
@@ -147,6 +147,8 @@ s1 += 1;
s1 = (short) (s1 + 1);
```
+
+
## switch
从 Java 7 开始,可以在 switch 条件判断语句中使用 String 对象。
diff --git "a/docs/JavaBasics/4_Object351円200円232円347円224円250円346円226円271円346円263円225円.md" "b/docs/JavaBasics/4_Object351円200円232円347円224円250円346円226円271円346円263円225円.md"
index 26786ca0..08d0ac74 100644
--- "a/docs/JavaBasics/4_Object351円200円232円347円224円250円346円226円271円346円263円225円.md"
+++ "b/docs/JavaBasics/4_Object351円200円232円347円224円250円346円226円271円346円263円225円.md"
@@ -382,4 +382,5 @@ CloneConstructorExample e2 = new CloneConstructorExample(e1);
e1.set(2, 222);
System.out.println(e1.get(2)); // 222
System.out.println(e2.get(2)); // 2
-```
\ No newline at end of file
+```
+
diff --git "a/docs/JavaBasics/5_345円205円263円351円224円256円345円255円227円.md" "b/docs/JavaBasics/5_345円205円263円351円224円256円345円255円227円.md"
index 8548e4b3..627747c6 100644
--- "a/docs/JavaBasics/5_345円205円263円351円224円256円345円255円227円.md"
+++ "b/docs/JavaBasics/5_345円205円263円351円224円256円345円255円227円.md"
@@ -1,6 +1,6 @@
-# 关键字
+# final 和 static 关键字
-## final
+## final 关键字
**1. 数据**
@@ -26,7 +26,7 @@ private 方法隐式地被指定为 final,如果在子类中定义的方法和
**声明类不允许被继承**。
-## static
+## static 关键字
**1. 静态变量**
diff --git "a/docs/JavaBasics/6_345円217円215円345円260円204円.md" "b/docs/JavaBasics/6_345円217円215円345円260円204円.md"
index cf38577a..ba3e78a5 100644
--- "a/docs/JavaBasics/6_345円217円215円345円260円204円.md"
+++ "b/docs/JavaBasics/6_345円217円215円345円260円204円.md"
@@ -1,4 +1,237 @@
-# 反射
+# 异常-Exception
+
+## 异常的概念
+
+Java 异常是一个描述在代码段中**发生异常的对象**,当发生异常情况时,一个代表该异常的对象被创建并且在导致该异常的方法中被抛出,而该方法可以选择自己处理异常或者传递该异常。
+
+## 异常继承体系
+
+
+
+Throwable 可以用来表示任何可以作为异常抛出的类,分为两种: **Error** 和 **Exception**。
+
+- Error:通常是灾难性的致命的错误,是**程序无法控制和处理**的,当出现这些错误时,建议终止程序;
+- Exception:通常情况下是可以被程序处理的,捕获后可能恢复,并且在程序中应该**尽可能地去处理**这些异常。
+
+Java 异常分为两种:
+
+- 受检异常:**除了 RuntimeException 及其子类以外,其他的 Exception 类及其子类都属于这种异常**。
+- 非受检异常:包括 RuntimeException 及其子类和 Error。
+
+
+
+注意:非受检查异常为编译器不要求强制处理的异常,受检异常则是编译器要求必须处置的异常。
+
+Exception 这类异常分为**运行时异常**和**非运行时异常(编译异常)**:
+
+- 运行时异常 :包括 RuntimeException 及其子类。比如 NullPointerException、IndexOutOfBoundsException。属于非受检异常,可以进行捕捉处理,也可以不处理。
+- 非运行时异常(编译异常):RuntimeExcaption 以外的 Exception。IOException、SQLException 已经自定义的异常,必须要进行处理。
+
+
+
+## Java 异常的处理机制
+
+Java 异常处理机制本质上就是**抛出异常**和**捕捉异常**。
+
+**抛出异常**
+
+i.普通问题:指在当前环境下能得到足够的信息,总能处理这个错误。
+
+ii.异常情形:是指**阻止当前方法或作用域继续执行的问题**。对于异常情形,已经程序无法执行继续下去了,
+因为在当前环境下无法获得必要的信息来解决问题,我们所能做的就是从当前环境中跳出,并把问题提交给上一级环境,
+这就是抛出异常时所发生的事情。
+
+iii.抛出异常后,会有几件事随之发生:
+
+第一:像创建普通的java对象一样将使用new在堆上创建一个异常对象
+
+ 第二:当前的执行路径(已经无法继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。
+
+此时,**异常处理机制接管程序,并开始寻找一个恰当的地方继续执行程序**,
+这个恰当的地方就是异常处理程序或者异常处理器,
+它的任务是**将程序从错误状态中恢复**,以使程序要么换一种方式运行,要么继续运行下去。
+
+**捕捉异常**
+
+在方法抛出异常之后,运行时系统将转为寻找合适的**异常处理器**(exception handler)。
+潜在的异常处理器是异常发生时依次存留在**调用栈**中的方法的集合。
+当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。
+运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。
+当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。
+
+注意:
+
+对于运行时异常、错误和受检异常,Java技术所要求的异常处理方式有所不同。
+
+(1)由于**运行时异常及其子类**的不可查性,为了更合理、更容易地实现应用程序,
+Java规定,运行时异常将由Java运行时系统自动抛出,**允许应用程序忽略运行时异常**。
+
+(2)对于方法运行中可能出现的Error,当运行方法不欲捕捉时,Java允许该方法不做任何抛出声明。
+因为,大多数Error异常属于永远不能被允许发生的状况,也属于合理的应用程序不该捕捉的异常。
+
+(3)对于所有的受检异常,
+Java规定:一个方法必须捕捉,或者声明抛出方法之外。
+也就是说,当一个方法选择不捕捉受检异常时,它必须声明将抛出异常。
+
+## Java 异常的处理原则
+
+- **具体明确**:抛出的异常应能通过异常类名和message准确说明异常的类型和产生异常的原因;
+- **提早抛出**:应尽可能早地发现并抛出异常,便于精确定位问题;
+- **延迟捕获**:异常的捕获和处理应尽可能延迟,让掌握更多信息的作用域来处理异常
+
+## Java 常见异常以及错误
+
+| 类型 | 说明 |
+| :--------------------------------------: | :----------------------------------------------------------: |
+| **RuntimeException 子类** | |
+| java.lang.ArrayIndexOutOfBoundsException | 数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出 |
+| java.lang.ArithmeticException | 算术条件异常。譬如:整数除零等 |
+| java.lang.NullPointerException | 空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等 |
+| java.lang.ClassNotFoundException | 找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常 |
+| java.lang.SecurityException | 安全性异常 |
+| java.lang.IllegalArgumentException | 非法参数异常 |
+| **IOException** | |
+| IOException | 操作输入流和输出流时可能出现的异常 |
+| EOFException | 文件已结束异常 |
+| FileNotFoundException | 文件未找到异常 |
+| **其他** | |
+| ClassCastException | 类型转换异常类 |
+| ArrayStoreException | 数组中包含不兼容的值抛出的异常 |
+| SQLException | 操作数据库异常类 |
+| NoSuchFieldException | 字段未找到异常 |
+| NumberFormatException | 字符串转换为数字抛出的异常 |
+| StringIndexOutOfBoundsException | 字符串索引超出范围抛出的异常 |
+| IllegalAccessException | 不允许访问某类异常 |
+| **Error** | |
+| NoClassDefFoundError | 找不到class定义的错误 |
+| StackOverflowError | 深递归导致栈被耗尽而抛出的错误 |
+| OutOfMemoryError | 内存溢出错误 |
+
+## try-catch-finally语句块的执行
+
+
+
+(1) try 块:用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
+
+(2) catch 块:用于处理try捕获到的异常。
+
+(3) finally 块:无论是否捕获或处理异常,finally 块里的语句都会被执行。
+当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在**方法返回之前**被执行。
+
+在以下 4 种特殊情况下,finally 语句块不会被执行:
+
+- 在finally语句块中发生了异常
+- 在前面的代码中用了System.exit()退出程序。
+- 程序所在的线程死亡。
+- 关闭 CPU。
+
+## try catch代码块的性能如何
+
+- 会影响 JVM 的重排序优化;
+- 异常对象实例需要保存栈快照等信息,开销比较大。
+
+## try-with-resources
+
+适用范围:任何实现 java.lang.AutoCloseable 或者 java.io.Closeable 的对象。
+
+ 在 try-with-resources 语句中,任何 catch 或 finally 代码块在声明的资源关闭后运行。
+
+使用 try-catch-finally 关闭资源:
+
+```java
+//读取文本文件的内容
+Scanner scanner = null;
+try {
+ scanner = new Scanner(new File("D://read.txt"));
+ while (scanner.hasNext()) {
+ System.out.println(scanner.nextLine());
+ }
+} catch (FileNotFoundException e) {
+ e.printStackTrace();
+} finally {
+ if (scanner != null) {
+ scanner.close();
+ }
+}
+```
+
+使用 try-with-resources 语句改造上面的代码:
+
+```java
+try (Scanner scanner = new Scanner(new File("test.txt"))) {
+ while (scanner.hasNext()) {
+ System.out.println(scanner.nextLine());
+ }
+} catch (FileNotFoundException e) {
+ e.printStackTrace();
+}
+```
+
+使用 try-with-resources 语句关闭多个资源:
+
+```java
+try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt"))); // 这里采用 ";" 进行分割
+ BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
+ int b;
+ while ((b = bin.read()) != -1) {
+ bout.write(b);
+ }
+}
+catch (IOException e) {
+ e.printStackTrace();
+}
+```
+
+
+
+## final & finally & finalize
+
+final:最终的意思,可以修饰类,修饰成员变量,修饰成员方法
+
+修饰类:类不能被继承
+
+修饰变量:变量是常量
+
+修饰方法:方法不能被重写(Override)
+
+(2)finally:是异常处理的关键字,用于释放资源。一般来说,代码必须执行(特殊情况:在执行到finally JVM就退出了)
+
+(3)finalize:是Object的一个方法,用于垃圾回收。
+
+## 看程序写结果
+
+```java
+public class TestException {
+ public static void main(String[] args) {
+ System.out.println(getInt());
+ }
+
+ public static int getInt(){
+ int a=10;
+ try{
+ System.out.println(a/0);
+ }catch (ArithmeticException e){
+ a=30;
+ return a;
+ }finally {
+ a=40;
+ }
+ System.out.println("a="+a);
+ return a;
+ }
+}
+```
+
+结果为:
+
+```html
+30
+```
+
+
+
+# 反射-Reflection
+
## 反射概念
Java 的反射机制是指在运行状态中:
@@ -43,4 +276,12 @@ Java 的反射机制是指在运行状态中:
## 反射的不足
-性能时一个问题。反射相当于一系列解释操作,通知 JVM 要做什么,性能比直接的 Java 要慢很多。
\ No newline at end of file
+性能是一个问题。反射相当于一系列解释操作,通知 Java 虚拟机要做什么,性能比直接的 Java 要慢很多。
+
+
+
+# 补充
+
+- [Java提高篇——Java异常处理](https://www.cnblogs.com/Qian123/p/5715402.html)
+
+- [深入解析Java反射(1) - 基础](https://www.sczyh30.com/posts/Java/java-reflection-1/)
\ No newline at end of file
diff --git "a/docs/JavaBasics/7_345円274円202円345円270円270円.md" "b/docs/JavaBasics/7_345円274円202円345円270円270円.md"
deleted file mode 100644
index 7d5aa8f3..00000000
--- "a/docs/JavaBasics/7_345円274円202円345円270円270円.md"
+++ /dev/null
@@ -1,184 +0,0 @@
-# 异常
-
-## 异常的概念
-Java 异常是一个描述在代码段中**发生异常的对象**,当发生异常情况时,一个代表该异常的对象被创建并且在导致该异常的方法中被抛出,而该方法可以选择自己处理异常或者传递该异常。
-
-## 异常继承体系
-
-
-
-
-
-Throwable 可以用来表示任何可以作为异常抛出的类,分为两种: **Error** 和 **Exception**。
-
-- Error:通常是灾难性的致命的错误,是**程序无法控制和处理**的,当出现这些错误时,建议终止程序;
-- Exception:通常情况下是可以被程序处理的,捕获后可能恢复,并且在程序中应该**尽可能地去处理**这些异常。
-
-Java 异常分为两种:
-
-
-- 受检异常:**除了 RuntimeException 及其子类以外,其他的 Exception 类及其子类都属于这种异常**。
-- 非受检异常:包括 RuntimeException 及其子类和 Error。
-
-
-
-注意:非受检查异常为编译器不要求强制处理的异常,受检异常则是编译器要求必须处置的异常。
-
-Exception 这类异常分为**运行时异常**和**非运行时异常(编译异常)**:
-
-- 运行时异常 :包括 RuntimeException 及其子类。比如 NullPointerException、IndexOutOfBoundsException。属于非受检异常,可以进行捕捉处理,也可以不处理。
-- 非运行时异常(编译异常):RuntimeExcaption 以外的 Exception。IOException、SQLException 已经自定义的异常,必须要进行处理。
-
-
-
-## Java 异常的处理机制
-
-Java 异常处理机制本质上就是**抛出异常**和**捕捉异常**。
-
-**抛出异常**
-
-i.普通问题:指在当前环境下能得到足够的信息,总能处理这个错误。
-
-ii.异常情形:是指**阻止当前方法或作用域继续执行的问题**。对于异常情形,已经程序无法执行继续下去了,
-因为在当前环境下无法获得必要的信息来解决问题,我们所能做的就是从当前环境中跳出,并把问题提交给上一级环境,
-这就是抛出异常时所发生的事情。
-
-iii.抛出异常后,会有几件事随之发生:
-
-第一:像创建普通的java对象一样将使用new在堆上创建一个异常对象
-
- 第二:当前的执行路径(已经无法继续下去了)被终止,并且从当前环境中弹出对异常对象的引用。
-
-此时,**异常处理机制接管程序,并开始寻找一个恰当的地方继续执行程序**,
-这个恰当的地方就是异常处理程序或者异常处理器,
-它的任务是**将程序从错误状态中恢复**,以使程序要么换一种方式运行,要么继续运行下去。
-
-**捕捉异常**
-
-在方法抛出异常之后,运行时系统将转为寻找合适的**异常处理器**(exception handler)。
-潜在的异常处理器是异常发生时依次存留在**调用栈**中的方法的集合。
-当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。
-运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。
-当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。
-
-注意:
-
-对于运行时异常、错误和受检异常,Java技术所要求的异常处理方式有所不同。
-
-(1)由于**运行时异常及其子类**的不可查性,为了更合理、更容易地实现应用程序,
-Java规定,运行时异常将由Java运行时系统自动抛出,**允许应用程序忽略运行时异常**。
-
-(2)对于方法运行中可能出现的Error,当运行方法不欲捕捉时,Java允许该方法不做任何抛出声明。
-因为,大多数Error异常属于永远不能被允许发生的状况,也属于合理的应用程序不该捕捉的异常。
-
-(3)对于所有的受检异常,
-Java规定:一个方法必须捕捉,或者声明抛出方法之外。
-也就是说,当一个方法选择不捕捉受检异常时,它必须声明将抛出异常。
-
-## Java 异常的处理原则
-
-- **具体明确**:抛出的异常应能通过异常类名和message准确说明异常的类型和产生异常的原因;
-- **提早抛出**:应尽可能早地发现并抛出异常,便于精确定位问题;
-- **延迟捕获**:异常的捕获和处理应尽可能延迟,让掌握更多信息的作用域来处理异常
-
-## Java 常见异常以及错误
-
-| 类型 | 说明 |
-| :--: | :--: |
-| **RuntimeException 子类** | |
-| java.lang.ArrayIndexOutOfBoundsException | 数组索引越界异常。当对数组的索引值为负数或大于等于数组大小时抛出 |
-| java.lang.ArithmeticException | 算术条件异常。譬如:整数除零等 |
-| java.lang.NullPointerException | 空指针异常。当应用试图在要求使用对象的地方使用了null时,抛出该异常。譬如:调用null对象的实例方法、访问null对象的属性、计算null对象的长度、使用throw语句抛出null等等 |
-| java.lang.ClassNotFoundException | 找不到类异常。当应用试图根据字符串形式的类名构造类,而在遍历CLASSPAH之后找不到对应名称的class文件时,抛出该异常 |
-| java.lang.SecurityException | 安全性异常 |
-| java.lang.IllegalArgumentException | 非法参数异常 |
-| **IOException** | |
-| IOException | 操作输入流和输出流时可能出现的异常 |
-| EOFException | 文件已结束异常 |
-| FileNotFoundException | 文件未找到异常 |
-| **其他** | |
-| ClassCastException | 类型转换异常类 |
-| ArrayStoreException | 数组中包含不兼容的值抛出的异常 |
-| SQLException | 操作数据库异常类 |
-| NoSuchFieldException | 字段未找到异常 |
-| NumberFormatException | 字符串转换为数字抛出的异常 |
-| StringIndexOutOfBoundsException | 字符串索引超出范围抛出的异常 |
-| IllegalAccessException | 不允许访问某类异常 |
-| **Error** | |
-| NoClassDefFoundError | 找不到class定义的错误 |
-| StackOverflowError | 深递归导致栈被耗尽而抛出的错误 |
-| OutOfMemoryError | 内存溢出错误 |
-
-## Java异常常见面试题
-
-### try-catch-finally语句块的执行
-
-
-
-(1)try 块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。
-
-(2)catch 块:用于处理try捕获到的异常。
-
-(3)finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。
-当在try块或catch块中遇到return语句时,finally语句块将在**方法返回之前**被执行。
-
-在以下4种特殊情况下,finally块不会被执行:
-
-1)在finally语句块中发生了异常。
-
-2)在前面的代码中用了System.exit()退出程序。
-
-3)程序所在的线程死亡。
-
-4)关闭CPU。
-
-### try catch代码块的性能如何?
-
-1. 会影响JVM的重排序优化;
-2. 异常对象实例需要保存栈快照等信息,开销比较大。
-
-### final和finally和finalize的区别?
-
-(1)final:最终的意思,可以修饰类,修饰成员变量,修饰成员方法
-
-修饰类:类不能被继承
-
-修饰变量:变量是常量
-
-修饰方法:方法不能被重写(Override)
-
-(2)finally:是异常处理的关键字,用于释放资源。一般来说,代码必须执行(特殊情况:在执行到finally JVM就退出了)
-
-(3)finalize:是Object的一个方法,用于垃圾回收。
-
-- 看程序写结果
-
-```java
-public class TestException {
- public static void main(String[] args) {
- System.out.println(getInt());
- }
-
- public static int getInt(){
- int a=10;
- try{
- System.out.println(a/0);
- }catch (ArithmeticException e){
- a=30;
- return a;
- }finally {
- a=40;
- }
- System.out.println("a="+a);
- return a;
- }
-}
-```
-结果为:
-```html
-30
-```
-
-- [Java异常常见面试题及答案1](https://github.com/DuHouAn/Java/blob/master/JavaBasics/01Java%E5%BC%82%E5%B8%B8%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%8A%E7%AD%94%E6%A1%88.txt)
-
-- [Java异常常见面试题及答案2](https://github.com/DuHouAn/Java/blob/master/JavaBasics/02Java%E5%BC%82%E5%B8%B8%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%8A%E7%AD%94%E6%A1%88.txt)
\ No newline at end of file
diff --git "a/docs/JavaBasics/8_346円263円233円345円236円213円.md" "b/docs/JavaBasics/8_346円263円233円345円236円213円.md"
index 158550d8..4c2e8e02 100644
--- "a/docs/JavaBasics/8_346円263円233円345円236円213円.md"
+++ "b/docs/JavaBasics/8_346円263円233円345円236円213円.md"
@@ -1,4 +1,4 @@
-# 泛型
+# 泛型-Generics
## 泛型的概念
在集合中存储对象并在**使用前进行类型转换**是多么的不方便。泛型防止了那种情况的发生。
它提供了**编译期**的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException。
@@ -655,8 +655,267 @@ Java 的泛型是一种**伪泛型,编译为字节码时参数类型会在代
假设参数类型的占位符为T,擦除规则(保留上界)如下:
-(1) 擦除后变为 Object
+(1) \ 擦除后变为 Object
(2) extends A> 擦除后变为 A
-(3) super A> 擦除后变为 Object
\ No newline at end of file
+(3) super A> 擦除后变为 Object
+
+# 注解-Annontation
+
+## 注解概述
+
+Annontation 是 Java5 开始引入的新特征,中文名称叫注解。
+注解是插入到代码中的一种**注释**或者说是一种**元数据**。
+
+这些注解信息可以在编译期使用编译工具进行处理,也可以在运行期使用 Java 反射机制进行处理。
+
+## 注解的用处
+
+- 生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等
+- 跟踪代码依赖性,实现替代**配置文件**功能。如Spring中@Autowired;
+- 在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。
+
+## 注解的原理
+
+注解**本质是一个继承了Annotation的特殊接口**,其具体实现类是Java运行时生成的**动态代理类**。
+我们通过反射获取注解时,返回的是Java运行时生成的**动态代理对象**$Proxy1。
+通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。
+该方法会从memberValues这个Map中索引出对应的值。
+而memberValues的来源是Java常量池。
+
+### 元注解
+
+java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
+
+| 注解 | 说明 |
+| :---------: | :-----------------------: |
+| @Documented | 是否将注解包含在JavaDoc中 |
+| @Retention | 什么时候使用该注解 |
+| @Target | 注解用于什么地方 |
+| @Inherited | 是否允许子类继承该注解 |
+
+- @Documented
+
+一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中。
+
+- @Retention
+
+定义该注解的生命周期。
+
+(1)RetentionPolicy.SOURCE : 在编译阶段丢弃。
+ 这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。
+ @Override, @SuppressWarnings都属于这类注解。
+
+(2)RetentionPolicy.CLASS : 在类加载的时候丢弃。
+在字节码文件的处理中有用。注解默认使用这种方式
+
+(3)RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,
+因此**可以使用反射机制读取该注解的信息**。我们自定义的注解通常使用这种方式。
+
+- @Target
+
+表示该注解用于什么地方。
+默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括:
+
+| 参数 | 说明 |
+| :--------------------------------------------------------: | :----------------------------------: |
+| ElementType.CONSTRUCTOR | 用于描述构造器 |
+| ElementType.FIELD | 成员变量、对象、属性(包括enum实例) |
+| ElementType.LOCAL_VARIABLE | 用于描述局部变量 |
+| ElementType.METHOD | 用于描述方法 |
+| ElementType.PACKAGE | 用于描述包 |
+| ElementType.PARAMETER | 用于描述参数 |
+| ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明 | |
+
+- @Inherited
+
+定义该注释和子类的关系。
+@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。
+如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
+
+### 常见标准的Annotation
+
+- Override (RetentionPolicy.SOURCE : 在编译阶段丢弃。属于@Retention)
+
+ Override是一个标记类型注解,它被用作标注方法。
+ 它说明了被标注的方法重载了父类的方法,起到了断言的作用。
+ 如果我们使用了这种注解在一个没有覆盖父类方法的方法时,**java编译器将以一个编译错误来警示**。
+
+- Deprecated
+
+Deprecated也是一种标记类型注解。
+当一个类型或者类型成员使用@Deprecated修饰的话,编译器将不鼓励使用这个被标注的程序元素。
+所以使用这种修饰具有一定的"延续性":
+ 如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,
+ 虽然继承或者覆盖后的类型或者成员并不是被声明为@Deprecated,但编译器仍然要报警。
+
+- SuppressWarnings
+
+SuppressWarning不是一个标记类型注解。
+它有一个类型为String[]的成员,这个成员的值为**被禁止的警告名**。
+对于javac编译器来讲,对-Xlint选项有效的警告名也同样对@SuppressWarings有效,同时编译器忽略掉无法识别的警告名。
+@SuppressWarnings("unchecked")
+
+### 自定义注解使用的规则
+
+自定义注解类编写的一些规则:
+(1) Annotation型定义为@interface,
+所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口
+
+(2)参数成员只能用public或默认(default)这两个访问权修饰
+
+(3)参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型
+和String、Enum、Class、Annotations等数据类型,以及这一些类型的数组
+
+(4)要获取类方法和字段的注解信息,必须通过Java的反射技术来获取Annotation对象,
+因为除此之外没有别的获取注解对象的方法
+
+(5)注解也可以没有定义成员, 不过这样注解就没啥用了
+
+注意:**自定义注解需要使用到元注解**
+
+> 自定义注解示例
+
+自定义水果颜色注解
+
+```java
+/**
+ * 水果颜色注解
+ */
+@Target(FIELD)
+@Retention(RUNTIME)
+@Documented
+@interface FruitColor {
+ /**
+ * 颜色枚举
+ */
+ public enum Color{绿色,红色,青色};
+
+ /**
+ * 颜色属性 (注意:这里的属性指的就是方法)
+ */
+ Color fruitColor() default Color.绿色;//默认是是绿色的
+}
+```
+
+自定义水果名称注解
+
+```java
+/**
+ * 水果名称注解
+ */
+@Target(FIELD) //ElementType.FIELD:成员变量、对象、属性(包括enum实例)
+@Retention(RUNTIME)// 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。
+@Documented // Deprecated也是一种标记类型注解。
+public @interface FruitName {
+ public String fruitName() default "";
+}
+```
+
+水果供应商注解
+
+```java
+/**
+ * 水果供应者注解
+ */
+@Target(FIELD)
+@Retention(RUNTIME)
+@Documented
+public @interface FruitProvider {
+ /**
+ * 供应者编号
+ */
+ public int id() default -1;
+
+ /**
+ * 供应商名称
+ */
+ public String name() default "";
+
+ /**
+ * 供应商地址
+ */
+ public String address() default "";
+}
+```
+
+通过反射来获取水果信息
+
+```java
+/**
+ * 通过反射获取水果信息
+ */
+public class FruitInfoUtil {
+ public static void getFruitInfo(Class> clazz){
+ String strFruitName=" 水果名称:";
+ String strFruitColor=" 水果颜色:";
+ String strFruitProvider="供应商信息:";
+
+ //获取属性值
+ Field[] fields=clazz.getDeclaredFields();
+ for(Field field:fields){
+ if(field.isAnnotationPresent(FruitName.class)){
+ //判断注解是不是 FruitName
+ FruitName fruitName=field.getAnnotation(FruitName.class);
+ strFruitName=strFruitName+fruitName.fruitName();
+ System.out.println(strFruitName);
+ }else if(field.isAnnotationPresent(FruitColor.class)){
+ FruitColor fruitColor=field.getAnnotation(FruitColor.class);
+ strFruitColor=strFruitColor+fruitColor.fruitColor().toString();
+ System.out.println(strFruitColor);
+ }else if(field.isAnnotationPresent(FruitProvider.class)){
+ FruitProvider fruitProvider=field.getAnnotation(FruitProvider.class);
+ strFruitProvider=strFruitProvider
+ + "[ 供应商编号:"+fruitProvider.id()
+ +" 供应商名称:" +fruitProvider.name()
+ +" 供应商地址:"+fruitProvider.address()+"]";
+ System.out.println(strFruitProvider);
+ }
+ }
+ }
+}
+```
+
+使用注解初始化实例类
+
+```java
+/**
+ * 定义一个实例类
+ * 这里使用注解来初始化
+ */
+public class Apple {
+ @FruitName(fruitName = "苹果")
+ private String appleName;
+
+ @FruitColor(fruitColor = FruitColor.Color.红色)
+ private String appleColor;
+
+ @FruitProvider(id=1,name="红富士",address="陕西省西安市延安路89号红富士大厦")
+ private String appleProvider;
+
+ public String getAppleName() {
+ return appleName;
+ }
+
+ public void setAppleName(String appleName) {
+ this.appleName = appleName;
+ }
+
+ public String getAppleColor() {
+ return appleColor;
+ }
+
+ public void setAppleColor(String appleColor) {
+ this.appleColor = appleColor;
+ }
+
+ public String getAppleProvider() {
+ return appleProvider;
+ }
+
+ public void setAppleProvider(String appleProvider) {
+ this.appleProvider = appleProvider;
+ }
+}
+```
\ No newline at end of file
diff --git "a/docs/JavaBasics/9_346円263円250円350円247円243円.md" "b/docs/JavaBasics/9_346円263円250円350円247円243円.md"
deleted file mode 100644
index fff5cbb8..00000000
--- "a/docs/JavaBasics/9_346円263円250円350円247円243円.md"
+++ /dev/null
@@ -1,243 +0,0 @@
-# 注解
-## 注解概述
-Annontation 是 Java5 开始引入的新特征,中文名称叫注解。
-注解是插入到代码中的一种**注释**或者说是一种**元数据**。
-
-这些注解信息可以在编译期使用编译工具进行处理,也可以在运行期使用 Java 反射机制进行处理。
-
-## 注解的用处
-- 生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等
-- 跟踪代码依赖性,实现替代**配置文件**功能。如Spring中@Autowired;
-- 在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。
-
-## 注解的原理
-注解**本质是一个继承了Annotation的特殊接口**,其具体实现类是Java运行时生成的**动态代理类**。
-我们通过反射获取注解时,返回的是Java运行时生成的**动态代理对象**$Proxy1。
-通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。
-该方法会从memberValues这个Map中索引出对应的值。
-而memberValues的来源是Java常量池。
-
-### 元注解
-java.lang.annotation提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
-
-| 注解 | 说明 |
-| :--: | :--: |
-| @Documented | 是否将注解包含在JavaDoc中 |
-| @Retention | 什么时候使用该注解 |
-| @Target | 注解用于什么地方 |
-| @Inherited | 是否允许子类继承该注解 |
-
-- @Documented
-
-一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中。
-
-- @Retention
-
-定义该注解的生命周期。
-
-(1)RetentionPolicy.SOURCE : 在编译阶段丢弃。
- 这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。
- @Override, @SuppressWarnings都属于这类注解。
-
-(2)RetentionPolicy.CLASS : 在类加载的时候丢弃。
-在字节码文件的处理中有用。注解默认使用这种方式
-
-(3)RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,
-因此**可以使用反射机制读取该注解的信息**。我们自定义的注解通常使用这种方式。
-
-- @Target
-
-表示该注解用于什么地方。
-默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括:
-
-| 参数 | 说明 |
-| :--: | :--: |
-|ElementType.CONSTRUCTOR | 用于描述构造器 |
-| ElementType.FIELD | 成员变量、对象、属性(包括enum实例)|
-| ElementType.LOCAL_VARIABLE | 用于描述局部变量 |
-| ElementType.METHOD | 用于描述方法 |
-| ElementType.PACKAGE | 用于描述包 |
-| ElementType.PARAMETER | 用于描述参数 |
-| ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明 |
-
-- @Inherited
-
-定义该注释和子类的关系。
-@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。
-如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
-
-### 常见标准的Annotation
-- Override (RetentionPolicy.SOURCE : 在编译阶段丢弃。属于@Retention)
-
- Override是一个标记类型注解,它被用作标注方法。
- 它说明了被标注的方法重载了父类的方法,起到了断言的作用。
- 如果我们使用了这种注解在一个没有覆盖父类方法的方法时,**java编译器将以一个编译错误来警示**。
-
-- Deprecated
-
-
-Deprecated也是一种标记类型注解。
-当一个类型或者类型成员使用@Deprecated修饰的话,编译器将不鼓励使用这个被标注的程序元素。
-所以使用这种修饰具有一定的"延续性":
- 如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,
- 虽然继承或者覆盖后的类型或者成员并不是被声明为@Deprecated,但编译器仍然要报警。
-
-- SuppressWarnings
-
-SuppressWarning不是一个标记类型注解。
-它有一个类型为String[]的成员,这个成员的值为**被禁止的警告名**。
-对于javac编译器来讲,对-Xlint选项有效的警告名也同样对@SuppressWarings有效,同时编译器忽略掉无法识别的警告名。
-@SuppressWarnings("unchecked")
-
-### 自定义注解使用的规则
-自定义注解类编写的一些规则:
-(1) Annotation型定义为@interface,
-所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口
-
-(2)参数成员只能用public或默认(default)这两个访问权修饰
-
-(3)参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型
-和String、Enum、Class、Annotations等数据类型,以及这一些类型的数组
-
-(4)要获取类方法和字段的注解信息,必须通过Java的反射技术来获取Annotation对象,
-因为除此之外没有别的获取注解对象的方法
-
-(5)注解也可以没有定义成员, 不过这样注解就没啥用了
-
-注意:**自定义注解需要使用到元注解**
-
-> 自定义注解示例
-
-自定义水果颜色注解
-```java
-/**
- * 水果颜色注解
- */
-@Target(FIELD)
-@Retention(RUNTIME)
-@Documented
-@interface FruitColor {
- /**
- * 颜色枚举
- */
- public enum Color{绿色,红色,青色};
-
- /**
- * 颜色属性 (注意:这里的属性指的就是方法)
- */
- Color fruitColor() default Color.绿色;//默认是是绿色的
-}
-```
-自定义水果名称注解
-```java
-/**
- * 水果名称注解
- */
-@Target(FIELD) //ElementType.FIELD:成员变量、对象、属性(包括enum实例)
-@Retention(RUNTIME)// 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。
-@Documented // Deprecated也是一种标记类型注解。
-public @interface FruitName {
- public String fruitName() default "";
-}
-```
-水果供应商注解
-```java
-/**
- * 水果供应者注解
- */
-@Target(FIELD)
-@Retention(RUNTIME)
-@Documented
-public @interface FruitProvider {
- /**
- * 供应者编号
- */
- public int id() default -1;
-
- /**
- * 供应商名称
- */
- public String name() default "";
-
- /**
- * 供应商地址
- */
- public String address() default "";
-}
-```
-通过反射来获取水果信息
-```java
-/**
- * 通过反射获取水果信息
- */
-public class FruitInfoUtil {
- public static void getFruitInfo(Class> clazz){
- String strFruitName=" 水果名称:";
- String strFruitColor=" 水果颜色:";
- String strFruitProvider="供应商信息:";
-
- //获取属性值
- Field[] fields=clazz.getDeclaredFields();
- for(Field field:fields){
- if(field.isAnnotationPresent(FruitName.class)){
- //判断注解是不是 FruitName
- FruitName fruitName=field.getAnnotation(FruitName.class);
- strFruitName=strFruitName+fruitName.fruitName();
- System.out.println(strFruitName);
- }else if(field.isAnnotationPresent(FruitColor.class)){
- FruitColor fruitColor=field.getAnnotation(FruitColor.class);
- strFruitColor=strFruitColor+fruitColor.fruitColor().toString();
- System.out.println(strFruitColor);
- }else if(field.isAnnotationPresent(FruitProvider.class)){
- FruitProvider fruitProvider=field.getAnnotation(FruitProvider.class);
- strFruitProvider=strFruitProvider
- + "[ 供应商编号:"+fruitProvider.id()
- +" 供应商名称:" +fruitProvider.name()
- +" 供应商地址:"+fruitProvider.address()+"]";
- System.out.println(strFruitProvider);
- }
- }
- }
-}
-```
-使用注解初始化实例类
-```java
-/**
- * 定义一个实例类
- * 这里使用注解来初始化
- */
-public class Apple {
- @FruitName(fruitName = "苹果")
- private String appleName;
-
- @FruitColor(fruitColor = FruitColor.Color.红色)
- private String appleColor;
-
- @FruitProvider(id=1,name="红富士",address="陕西省西安市延安路89号红富士大厦")
- private String appleProvider;
-
- public String getAppleName() {
- return appleName;
- }
-
- public void setAppleName(String appleName) {
- this.appleName = appleName;
- }
-
- public String getAppleColor() {
- return appleColor;
- }
-
- public void setAppleColor(String appleColor) {
- this.appleColor = appleColor;
- }
-
- public String getAppleProvider() {
- return appleProvider;
- }
-
- public void setAppleProvider(String appleProvider) {
- this.appleProvider = appleProvider;
- }
-}
-```
\ No newline at end of file
diff --git "a/docs/JavaBasics/JDK8346円226円260円347円211円271円346円200円247円.md" "b/docs/JavaBasics/JDK8346円226円260円347円211円271円346円200円247円.md"
new file mode 100644
index 00000000..f774bf07
--- /dev/null
+++ "b/docs/JavaBasics/JDK8346円226円260円347円211円271円346円200円247円.md"
@@ -0,0 +1,3 @@
+# JDK 8 新特性
+
+待补充。
\ No newline at end of file
diff --git "a/docs/JavaBasics/346円255円243円345円210円231円350円241円250円350円276円276円345円274円217円.md" "b/docs/JavaBasics/346円255円243円345円210円231円350円241円250円350円276円276円345円274円217円.md"
new file mode 100644
index 00000000..7217feb9
--- /dev/null
+++ "b/docs/JavaBasics/346円255円243円345円210円231円350円241円250円350円276円276円345円274円217円.md"
@@ -0,0 +1,173 @@
+# 正则表达式
+
+## 概述
+
+是指一个用来描述或者匹配一系列符合某个句法规则的字符串的单个字符串。
+
+其实就是**一种规则**。
+
+## 组成规则
+
+规则字符在java.util.regex Pattern类中:[Pattern API](https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html)
+
+## 常见组成规则
+
+### 1. 字符
+
+| 元字符 | 说明 |
+| :----: | :-----------------------: |
+| x | 字符 x |
+| \ | 反斜线字符 |
+| \n | 新行(换行)符 ('\u000A') |
+| \r | 回车符 ('\u000D') |
+
+### 2. 字符类
+
+| 元字符 | 说明 |
+| :------: | :----------------------------------------: |
+| [abc] | a、b 或 c(简单类) |
+| [^abc] | 任何字符,除了 a、b 或 c(否定) |
+| [a-zA-Z] | a到 z 或 A到 Z,两头的字母包括在内(范围) |
+| [0-9] | 0到9的字符都包括 |
+
+### 3. 预定义字符类
+
+| 元字符 | 说明 |
+| :----: | :--------------------------: |
+| . | 任何字符 |
+| \d | 数字。等价于[0-9] |
+| \w | 单词字符。等待雨[a-zA-Z_0-9] |
+
+### 4. 边界匹配器
+
+| 元字符 | 说明 |
+| :----: | :------: |
+| ^ | 行的开头 |
+| $ | 行的结尾 |
+| \b | 单词边界 |
+
+### 5. 数量词
+
+| 元字符 | 说明 |
+| :----: | :---------------------------: |
+| X? | X,零次或一次 |
+| X* | X,零次或多次 |
+| X+ | X,一次或多次 |
+| X{n} | X,恰好 n 次 |
+| X{n,} | X,至少 n 次 |
+| X{n,m} | X,至少 n 次,但是不超过 m 次 |
+
+## 应用
+
+### 判断功能
+
+String 类 matches 方法
+
+```java
+public boolean matches(String regex)
+```
+
+```java
+/**
+* 判断手机号码是否满足要求
+*/
+public static void main(String[] args) {
+ //键盘录入手机号码
+ Scanner sc = new Scanner(System.in);
+ System.out.println("请输入你的手机号码:");
+ String phone = sc.nextLine();
+
+ //定义手机号码的规则
+ String regex = "1[38]\\d{9}";
+
+ //调用功能,判断即可
+ boolean flag = phone.matches(regex);
+
+ //输出结果
+ System.out.println("flag:"+flag);
+}
+```
+
+
+
+### 分割功能
+
+String类的 split 方法
+
+```java
+public String[] split(String regex)
+```
+
+```java
+/**
+* 根据给定正则表达式的匹配拆分此字符串
+*/
+public static void main(String[] args) {
+ //定义一个年龄搜索范围
+ String ages = "18-24";
+
+ //定义规则
+ String regex = "-";
+
+ //调用方法
+ String[] strArray = ages.split(regex);
+ int startAge = Integer.parseInt(strArray[0]);
+ int endAge = Integer.parseInt(strArray[1]);
+}
+```
+
+
+
+### 替换功能
+
+String 类的 replaceAll 方法
+
+```java
+public String replaceAll(String regex,String replacement)
+```
+
+```java
+/**
+* 去除所有的数字
+*/
+public static void main(String[] args) {
+ // 定义一个字符串
+ String s = "helloqq12345worldkh622112345678java";
+
+
+ // 直接把数字干掉
+ String regex = "\\d+";
+ String ss = "";
+
+ String result = s.replaceAll(regex, ss);
+ System.out.println(result);
+}
+```
+
+
+
+### 获取功能
+
+Pattern和Matcher类的使用
+
+```java
+/**
+* 模式和匹配器的基本顺序
+*/
+public static void main(String[] args) {
+ // 模式和匹配器的典型调用顺序
+ // 把正则表达式编译成模式对象
+ Pattern p = Pattern.compile("a*b");
+ // 通过模式对象得到匹配器对象,这个时候需要的是被匹配的字符串
+ Matcher m = p.matcher("aaaaab");
+ // 调用匹配器对象的功能
+ boolean b = m.matches();
+ System.out.println(b);
+
+ //这个是判断功能,但是如果做判断,这样做就有点麻烦了,我们直接用字符串的方法做
+ String s = "aaaaab";
+ String regex = "a*b";
+ boolean bb = s.matches(regex);
+ System.out.println(bb);
+}
+```
\ No newline at end of file
diff --git "a/docs/JavaContainer/1_Java345円256円271円345円231円250円346円246円202円350円247円210円.md" "b/docs/JavaContainer/1_345円256円271円345円231円250円346円246円202円350円247円210円.md"
similarity index 84%
rename from "docs/JavaContainer/1_Java345円256円271円345円231円250円346円246円202円350円247円210円.md"
rename to "docs/JavaContainer/1_345円256円271円345円231円250円346円246円202円350円247円210円.md"
index b30706a7..26515fb0 100644
--- "a/docs/JavaContainer/1_Java345円256円271円345円231円250円346円246円202円350円247円210円.md"
+++ "b/docs/JavaContainer/1_345円256円271円345円231円250円346円246円202円350円247円210円.md"
@@ -4,7 +4,9 @@
## Collection
-
+Collection 集合体系图:
+
+
### 1. Set
@@ -33,7 +35,9 @@
## Map
-
+Map 集合体系图:
+
+
- TreeMap:基于红黑树实现。
diff --git "a/docs/JavaContainer/2_345円256円271円345円231円250円344円270円255円347円232円204円350円256円276円350円256円241円346円250円241円345円274円217円.md" "b/docs/JavaContainer/2_345円256円271円345円231円250円344円270円255円347円232円204円350円256円276円350円256円241円346円250円241円345円274円217円.md"
index e06fb004..f795cada 100644
--- "a/docs/JavaContainer/2_345円256円271円345円231円250円344円270円255円347円232円204円350円256円276円350円256円241円346円250円241円345円274円217円.md"
+++ "b/docs/JavaContainer/2_345円256円271円345円231円250円344円270円255円347円232円204円350円256円276円350円256円241円346円250円241円345円274円217円.md"
@@ -2,9 +2,7 @@
## 迭代器模式
-[参考迭代器模式笔记](https://github.com/DuHouAn/Java/blob/master/Object_Oriented/notes/02%E8%A1%8C%E4%B8%BA%E5%9E%8B.md#4-%E8%BF%AD%E4%BB%A3%E5%99%A8iterator)
-
-
+
Collection 继承了 Iterable 接口,其中的 iterator() 方法能够产生一个 Iterator 对象,通过这个对象就可以迭代遍历 Collection 中的元素。
@@ -21,8 +19,6 @@ for (String item : list) {
## 适配器模式
-[参考适配器模式笔记](https://github.com/DuHouAn/Java/blob/master/Object_Oriented/notes/03%E7%BB%93%E6%9E%84%E5%9E%8B.md#1-%E9%80%82%E9%85%8D%E5%99%A8adapter)
-
java.util.Arrays#asList() 可以把数组类型转换为 List 类型。
```java
diff --git "a/docs/JavaContainer/3_345円256円271円345円231円250円346円272円220円347円240円201円345円210円206円346円236円220円 - List.md" "b/docs/JavaContainer/3_345円256円271円345円231円250円346円272円220円347円240円201円345円210円206円346円236円220円 - List.md"
index fdff5b13..161d764e 100644
--- "a/docs/JavaContainer/3_345円256円271円345円231円250円346円272円220円347円240円201円345円210円206円346円236円220円 - List.md"
+++ "b/docs/JavaContainer/3_345円256円271円345円231円250円346円272円220円347円240円201円345円210円206円346円236円220円 - List.md"
@@ -1,6 +1,6 @@
# 容器源码分析 - List
-如果没有特别说明,以下源码分析基于 JDK 1.8。
+以下源码分析基于 JDK 1.8。
## ArrayList
@@ -176,7 +176,7 @@ private void readObject(java.io.ObjectInputStream s)
}
```
-### 6.System.arraycopy()和Arrays.copyOf()方法
+### 6. System.arraycopy()和Arrays.copyOf()方法
Arrays.copyOf() 的源代码内部调用了 System.arraycopy() 方法。
- System.arraycopy() 方法需要目标数组,将原数组拷贝到目标数组里,而且可以选择**拷贝的起点和长度以及放入新数组中的位置**;
@@ -225,6 +225,8 @@ List synList = Collections.synchronizedList(list);
List list = new CopyOnWriteArrayList();
```
+
+
## LinkedList
### 1. 概览
@@ -246,10 +248,11 @@ transient Node first;
transient Node last;
```
-
+
### 2. 添加元素
-- 将元素添加到链表尾部
+将元素添加到链表尾部:
+
```java
public boolean add(E e) {
linkLast(e);//这里就只调用了这一个方法
@@ -274,7 +277,8 @@ void linkLast(E e) {
}
```
-- 将元素添加到链表头部
+将元素添加到链表头部:
+
```java
public void addFirst(E e) {
linkFirst(e);
diff --git "a/docs/JavaContainer/4_345円256円271円345円231円250円346円272円220円347円240円201円345円210円206円346円236円220円 - Map.md" "b/docs/JavaContainer/4_345円256円271円345円231円250円346円272円220円347円240円201円345円210円206円346円236円220円 - Map.md"
index ea04d9b5..595262cc 100644
--- "a/docs/JavaContainer/4_345円256円271円345円231円250円346円272円220円347円240円201円345円210円206円346円236円220円 - Map.md"
+++ "b/docs/JavaContainer/4_345円256円271円345円231円250円346円272円220円347円240円201円345円210円206円346円236円220円 - Map.md"
@@ -1,6 +1,6 @@
# 容器源码分析 - Map
-如果没有特别说明,以下源码分析基于 JDK 1.8。
+以下源码分析基于 JDK 1.8。
## HashMap
@@ -18,7 +18,7 @@ Entry 存储着键值对。它包含了四个字段,从 next 字段我们可
即数组中的每个位置被当成一个桶,一个桶存放一个链表。HashMap 使用**拉链法**来解决冲突,
同一个链表中存放哈希值相同的 Entry。
-
+
```java
static class Entry implements Map.Entry {
@@ -98,7 +98,7 @@ map.put("K3", "V3");
- 计算键值对所在的桶;
- 在链表上顺序查找,时间复杂度显然和链表的长度成正比。
-
+
### 3. put 操作
diff --git "a/docs/JavaContainer/5_345円256円271円345円231円250円346円272円220円347円240円201円345円210円206円346円236円220円 - 345円271円266円345円217円221円345円256円271円345円231円250円.md" "b/docs/JavaContainer/5_345円256円271円345円231円250円346円272円220円347円240円201円345円210円206円346円236円220円 - 345円271円266円345円217円221円345円256円271円345円231円250円.md"
index 26f41211..98396c27 100644
--- "a/docs/JavaContainer/5_345円256円271円345円231円250円346円272円220円347円240円201円345円210円206円346円236円220円 - 345円271円266円345円217円221円345円256円271円345円231円250円.md"
+++ "b/docs/JavaContainer/5_345円256円271円345円231円250円346円272円220円347円240円201円345円210円206円346円236円220円 - 345円271円266円345円217円221円345円256円271円345円231円250円.md"
@@ -1,6 +1,6 @@
# 容器源码分析 - 并发容器
-如果没有特别说明,以下源码分析基于 JDK 1.8。
+以下源码分析基于 JDK 1.8。
## CopyOnWriteArrayList
@@ -103,7 +103,7 @@ final Segment[] segments;
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
```
-
+
### 2. size 操作
@@ -223,24 +223,16 @@ static final Node tabAt(Node[] tab, int i) {
**底层数据结构:**
-- JDK1.7 的ConcurrentHashMap底层采用**分段的数组+链表**实现,
- JDK1.8 的ConcurrentHashMap底层采用的数据结构与JDK1.8 的HashMap的结构一样,**数组+链表/红黑二叉树**。
-- Hashtable和JDK1.8 之前的HashMap的底层数据结构类似都是采用**数组+链表**的形式,
+- JDK1.7 的 ConcurrentHashMap 底层采用**分段的数组+链表**实现,
+ JDK1.8 的 ConcurrentHashMap 底层采用的数据结构与 JDK1.8 的 HashMap 的结构一样,**数组+链表/红黑二叉树**。
+- Hashtable 和 JDK1.8 之前的HashMap的底层数据结构类似都是采用**数组+链表**的形式,
数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的。
**实现线程安全的方式**
-- JDK1.7的ConcurrentHashMap(分段锁)对整个桶数组进行了分割分段(Segment),
+- JDK1.7 的 ConcurrentHashMap(分段锁)对整个桶数组进行了分割分段(Segment),
每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问度。
- JDK 1.8 采用**数组+链表/红黑二叉树**的数据结构来实现,并发控制使用**synchronized和CAS**来操作。
-- Hashtable:使用 synchronized 来保证线程安全,效率非常低下。
+ JDK 1.8 采用**数组+链表/红黑二叉树**的数据结构来实现,并发控制使用 **synchronized** 和 **CAS**来操作。
+- Hashtable 使用 synchronized 来保证线程安全,效率非常低下。
当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,
如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈。
-
-Hashtable 全表锁
-
-
-
-ConcurrentHashMap 分段锁
-
-
\ No newline at end of file
diff --git "a/docs/JavaIO/1_346円246円202円350円247円210円.md" "b/docs/JavaIO/1_346円246円202円350円247円210円.md"
index 44cbf9ac..5260aca5 100644
--- "a/docs/JavaIO/1_346円246円202円350円247円210円.md"
+++ "b/docs/JavaIO/1_346円246円202円350円247円210円.md"
@@ -1,16 +1,45 @@
-# 概览
+# Java I/O 概览
-## Java 的 I/O 大概可以分成以下几类:
+## I/O 介绍
+
+I/O(**I**nput/**O**utpu) 即**输入/输出** 。
+
+学术的说 I/O 是信息处理系统(计算机)与外界(人或信息处理系统)间的通信。如计算机,即 CPU 访问任何寄存器和 Cache 等封装以外的数据资源都可当成 I/O ,包括且不限于内存,磁盘,显卡。
+
+软件开发中的 I/O 则常指磁盘、网络 IO。
+
+补充:
+
+- [如何完成一次IO](https://llc687.top/126.html)
+- [5 种IO模型](https://mp.weixin.qq.com/s?__biz=Mzg3MjA4MTExMw==&mid=2247484746&idx=1&sn=c0a7f9129d780786cabfcac0a8aa6bb7&source=41&scene=21#wechat_redirect)
+
+## 同步 & 异步
+
+同步、异步是消息通知机制。
+
+- 同步:同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。
+- 异步:异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。
+
+同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。
+
+## 阻塞 & 非阻塞
+
+阻塞、非阻塞是等待通知时的状态。
+
+- 阻塞: 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。
+- 非阻塞:非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。
+
+举个生活中简单的例子:
+
+你妈妈让你烧水,小时候你比较笨啊,在哪里傻等着水开( **同步阻塞** )。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有( **同步非阻塞** )。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情( **异步非阻塞** )。
+
+## Java I/O 分类
+
+Java 的 I/O 大概可以分成以下几类:
- 磁盘操作:File
- 字节操作:InputStream 和 OutputStream
- 字符操作:Reader 和 Writer
- 对象操作:Serializable
- 网络操作:Socket
-- 新的输入/输出:NIO
-
-## IO流的分类:
-
-
-
-注意:一般我们在探讨IO流的时候,如果没有明确说明按哪种分类来说,默认情况下是按照数据类型来分的。
+- 新的输入/输出:NIO & AIO
diff --git "a/docs/JavaIO/3_345円255円227円350円212円202円346円223円215円344円275円234円.md" "b/docs/JavaIO/3_345円255円227円350円212円202円346円223円215円344円275円234円.md"
index 9ca0b8b5..c0c9cc63 100644
--- "a/docs/JavaIO/3_345円255円227円350円212円202円346円223円215円344円275円234円.md"
+++ "b/docs/JavaIO/3_345円255円227円350円212円202円346円223円215円344円275円234円.md"
@@ -309,7 +309,7 @@ Java I/O 使用了装饰者模式来实现。以 InputStream 为例,
- FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作;
- FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能。例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。
-
+
实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。
diff --git "a/docs/JavaIO/5_345円257円271円350円261円241円346円223円215円344円275円234円.md" "b/docs/JavaIO/5_345円257円271円350円261円241円346円223円215円344円275円234円.md"
index b3c8891d..bb15fcd9 100644
--- "a/docs/JavaIO/5_345円257円271円350円261円241円346円223円215円344円275円234円.md"
+++ "b/docs/JavaIO/5_345円257円271円350円261円241円346円223円215円344円275円234円.md"
@@ -1,8 +1,12 @@
# 对象操作
-## 序列化
+## 序列化 & 反序列化
-序列化就是将一个对象转换成字节序列,方便存储和传输。
+如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中,或者在网络传输 Java 对象,这些场景都需要用到序列化。
+
+序列化就是将数据结构或对象转换成二进制字节流的过程。
+
+反序列化将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程。
- 序列化:ObjectOutputStream.writeObject()
- 反序列化:ObjectInputStream.readObject()
diff --git "a/docs/JavaIO/6_347円275円221円347円273円234円346円223円215円344円275円234円.md" "b/docs/JavaIO/6_347円275円221円347円273円234円346円223円215円344円275円234円.md"
index e9edb409..eaa5928e 100644
--- "a/docs/JavaIO/6_347円275円221円347円273円234円346円223円215円344円275円234円.md"
+++ "b/docs/JavaIO/6_347円275円221円347円273円234円346円223円215円344円275円234円.md"
@@ -67,7 +67,7 @@ public static void main(String[] args) throws IOException {
- Socket:客户端类
- 服务器和客户端通过 InputStream 和 OutputStream 进行输入输出。
-
+
## Datagram
diff --git a/docs/JavaIO/7_NIO.md b/docs/JavaIO/7_NIO.md
index 969c8b2b..537fe3a5 100644
--- a/docs/JavaIO/7_NIO.md
+++ b/docs/JavaIO/7_NIO.md
@@ -1,8 +1,8 @@
-# NIO
+# 一、NIO
-新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。
+NIO(New I/O)即新的输入/输出库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。
-NIO核心组件:
+NIO 核心组件:
- 通道(Channels)
@@ -65,23 +65,23 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重
1 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 8。capacity 变量不会改变,下面的讨论会忽略它。
-
+
2 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 为 5,limit 保持不变。
-
+
3 在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0。
-
+
4 从缓冲区中取 4 个字节到输出缓冲中,此时 position 设为 4。
-
+
5 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。
-
+
## 文件 NIO 实例
### FileChannel的使用
@@ -427,7 +427,7 @@ NIO 实现了 IO 多路复用中的 **Reactor 模型**,一个线程 Thread 使
应该注意的是,只有套接字 Channel 才能配置为非阻塞,而 FileChannel 不能,
为 FileChannel 配置非阻塞也没有意义。
-
+
使用Selector的优点:
@@ -660,7 +660,9 @@ public class NIOClient {
## 内存映射文件
-内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。
+内存映射文件((memory-mapped file)) I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。
+
+内存映射文件能让你创建和修改那些大到无法读入内存的文件。有了内存映射文件,可以认为文件已经全部读进了内存,然后把它当成一个非常大的数组来访问了。将文件的一段区域映射到内存中,比传统的文件处理速度要快很多。内存映射文件它虽然最终也是要从磁盘读取数据,但是它并不需要将数据读取到 OS 内核缓冲区,而是直接将进程的用户私有地址空间中的一部分区域与文件对象建立起映射关系,就好像直接从内存中读、写文件一样,速度当然快了。
向内存映射文件写入可能是危险的,只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。
@@ -670,13 +672,7 @@ public class NIOClient {
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
```
-## NIO与IO对比
-
-NIO 与普通 I/O 的区别主要有以下三点:
-- NIO 是非阻塞的;
-- NIO 面向块,I/O 面向流。
-- NIO有选择器,而I/O没有。
## Path
Java7中文件IO发生了很大的变化,专门引入了很多新的类来取代原来的
@@ -927,6 +923,205 @@ public class FilesDemo7 {
paths:[a.txt, test2.txt, test.txt, test3.txt]
```
-# 参考资料
+# 二、AIO
+
+AIO(Asynchronous I/O)即异步输入/输出库是在 JDK 1.7 中引入的。虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。
+
+对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行IO操作,IO操作本身是同步的。但是对AIO来说,则更加进了一步,它不是在 IO 准备好时再通知线程,而是在 IO 操作已经完成后,再给线程发出通知。因此 AIO 是不会阻塞的,此时我们的业务逻辑将变成一个**回调函数**,等待 IO 操作完成后,由系统自动触发。
+
+## AsynchronousServerSocketChannel
+
+在 AIO Socket 编程中,服务端通道是 AsynchronousServerSocketChannel,该类主要有如下方法:
+
+- 提供了一个 open() 静态工厂
+- bind() 方法用于绑定服务端 IP 地址 + 端口号
+- accept() 用于接收用户连接请求
+
+## AsynchronousSocketChannel
+
+在客户端使用的通道是 AsynchronousSocketChannel,这个通道处理除了提供 open 静态工厂方法外,还提供了read() 和 write() 方法。
+
+## CompletionHandler
+
+在AIO编程中,发出一个事件(accept read write等)之后要指定事件处理类(回调函数),AIO 中的事件处理类是 CompletionHandler,这个接口定义了如下两个方法,分别在异步操作成功和失败时被回调:
+
+```java
+void completed(V result,A attachment);
+void failed(Throwable exc,A attachment);
+```
+
+## 示例
+
+### 服务端
+
+```java
+package com.southeast.cn;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousServerSocketChannel;
+import java.nio.channels.AsynchronousSocketChannel;
+import java.nio.channels.CompletionHandler;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class AIOEchoServer {
+
+ public final static int PORT = 8001;
+ public final static String IP = "127.0.0.1";
+
+ private AsynchronousServerSocketChannel server = null;
+
+ public AIOEchoServer(){
+ try {
+ //同样是利用工厂方法产生一个通道,异步通道 AsynchronousServerSocketChannel
+ server = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(IP,PORT));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ //使用这个通道(server)来进行客户端的接收和处理
+ public void start(){
+ System.out.println("Server listen on "+PORT);
+
+ //注册事件和事件完成后的处理器,这个CompletionHandler就是事件完成后的处理器
+ server.accept(null,new CompletionHandler(){
+
+ final ByteBuffer buffer = ByteBuffer.allocate(1024);
+
+ // 回调函数
+ @Override
+ public void completed(AsynchronousSocketChannel result,Object attachment) {
+
+ System.out.println(Thread.currentThread().getName());
+ Future writeResult = null;
+
+ try{
+ buffer.clear();
+ result.read(buffer).get(100,TimeUnit.SECONDS);
+
+ System.out.println("In server: "+ new String(buffer.array()));
+
+ //将数据写回客户端
+ buffer.flip();
+ writeResult = result.write(buffer);
+ }catch(InterruptedException | ExecutionException | TimeoutException e){
+ e.printStackTrace();
+ }finally{
+ server.accept(null,this);
+ try {
+ writeResult.get();
+ result.close();
+ } catch (InterruptedException | ExecutionException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ }
+
+ // 回调函数
+ @Override
+ public void failed(Throwable exc, Object attachment) {
+ System.out.println("failed:"+exc);
+ }
+
+ });
+ }
+
+ public static void main(String[] args) {
+ new AIOEchoServer().start();
+ while(true){
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+}
+```
+
+
+
+### 客户端
+
+```java
+package com.southeast.cn;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousSocketChannel;
+import java.nio.channels.CompletionHandler;
+
+public class AIOClient {
+
+ public static void main(String[] args) throws IOException {
+
+ final AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
+
+ InetSocketAddress serverAddress = new InetSocketAddress("127.0.0.1",8001);
-- https://juejin.im/post/5b4570cce51d451984695a9b
\ No newline at end of file
+ CompletionHandler handler = new CompletionHandler(){
+
+ @Override
+ public void completed(Void result, Object attachment) {
+ client.write(ByteBuffer.wrap("Hello".getBytes()),null,
+ new CompletionHandler(){
+
+ @Override
+ public void completed(Integer result,
+ Object attachment) {
+ final ByteBuffer buffer = ByteBuffer.allocate(1024);
+ client.read(buffer,buffer,new CompletionHandler(){
+
+ @Override
+ public void completed(Integer result,
+ ByteBuffer attachment) {
+ buffer.flip();
+ System.out.println(new String(buffer.array()));
+ try {
+ client.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void failed(Throwable exc,
+ ByteBuffer attachment) {
+ }
+
+ });
+ }
+
+ @Override
+ public void failed(Throwable exc, Object attachment) {
+ }
+
+ });
+ }
+
+ @Override
+ public void failed(Throwable exc, Object attachment) {
+ }
+
+ };
+
+ client.connect(serverAddress, null, handler);
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
+```
\ No newline at end of file
diff --git a/docs/JavaIO/8_AIO.md b/docs/JavaIO/8_AIO.md
new file mode 100644
index 00000000..f8e8666a
--- /dev/null
+++ b/docs/JavaIO/8_AIO.md
@@ -0,0 +1,205 @@
+# AIO
+
+AIO(Asynchronous I/O)即异步输入/输出库是在 JDK 1.7 中引入的。虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。
+
+对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行IO操作,IO操作本身是同步的。但是对AIO来说,则更加进了一步,它不是在 IO 准备好时再通知线程,而是在 IO 操作已经完成后,再给线程发出通知。因此 AIO 是不会阻塞的,此时我们的业务逻辑将变成一个**回调函数**,等待 IO 操作完成后,由系统自动触发。
+
+## AsynchronousServerSocketChannel
+
+在 AIO Socket 编程中,服务端通道是 AsynchronousServerSocketChannel,该类主要有如下方法:
+
+- 提供了一个 open() 静态工厂
+- bind() 方法用于绑定服务端 IP 地址 + 端口号
+- accept() 用于接收用户连接请求
+
+## AsynchronousSocketChannel
+
+在客户端使用的通道是 AsynchronousSocketChannel,这个通道处理除了提供 open 静态工厂方法外,还提供了read() 和 write() 方法。
+
+## CompletionHandler
+
+在AIO编程中,发出一个事件(accept read write等)之后要指定事件处理类(回调函数),AIO 中的事件处理类是 CompletionHandler,这个接口定义了如下两个方法,分别在异步操作成功和失败时被回调:
+
+```java
+void completed(V result,A attachment);
+void failed(Throwable exc,A attachment);
+```
+
+## 示例
+
+### 服务端
+
+```java
+package com.southeast.cn;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousServerSocketChannel;
+import java.nio.channels.AsynchronousSocketChannel;
+import java.nio.channels.CompletionHandler;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public class AIOEchoServer {
+
+ public final static int PORT = 8001;
+ public final static String IP = "127.0.0.1";
+
+ private AsynchronousServerSocketChannel server = null;
+
+ public AIOEchoServer(){
+ try {
+ //同样是利用工厂方法产生一个通道,异步通道 AsynchronousServerSocketChannel
+ server = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(IP,PORT));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ //使用这个通道(server)来进行客户端的接收和处理
+ public void start(){
+ System.out.println("Server listen on "+PORT);
+
+ //注册事件和事件完成后的处理器,这个CompletionHandler就是事件完成后的处理器
+ server.accept(null,new CompletionHandler(){
+
+ final ByteBuffer buffer = ByteBuffer.allocate(1024);
+
+ // 回调函数
+ @Override
+ public void completed(AsynchronousSocketChannel result,Object attachment) {
+
+ System.out.println(Thread.currentThread().getName());
+ Future writeResult = null;
+
+ try{
+ buffer.clear();
+ result.read(buffer).get(100,TimeUnit.SECONDS);
+
+ System.out.println("In server: "+ new String(buffer.array()));
+
+ //将数据写回客户端
+ buffer.flip();
+ writeResult = result.write(buffer);
+ }catch(InterruptedException | ExecutionException | TimeoutException e){
+ e.printStackTrace();
+ }finally{
+ server.accept(null,this);
+ try {
+ writeResult.get();
+ result.close();
+ } catch (InterruptedException | ExecutionException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ }
+
+ // 回调函数
+ @Override
+ public void failed(Throwable exc, Object attachment) {
+ System.out.println("failed:"+exc);
+ }
+
+ });
+ }
+
+ public static void main(String[] args) {
+ new AIOEchoServer().start();
+ while(true){
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+}
+```
+
+
+
+### 客户端
+
+```java
+package com.southeast.cn;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousSocketChannel;
+import java.nio.channels.CompletionHandler;
+
+public class AIOClient {
+
+ public static void main(String[] args) throws IOException {
+
+ final AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
+
+ InetSocketAddress serverAddress = new InetSocketAddress("127.0.0.1",8001);
+
+ CompletionHandler handler = new CompletionHandler(){
+
+ @Override
+ public void completed(Void result, Object attachment) {
+ client.write(ByteBuffer.wrap("Hello".getBytes()),null,
+ new CompletionHandler(){
+
+ @Override
+ public void completed(Integer result,
+ Object attachment) {
+ final ByteBuffer buffer = ByteBuffer.allocate(1024);
+ client.read(buffer,buffer,new CompletionHandler(){
+
+ @Override
+ public void completed(Integer result,
+ ByteBuffer attachment) {
+ buffer.flip();
+ System.out.println(new String(buffer.array()));
+ try {
+ client.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void failed(Throwable exc,
+ ByteBuffer attachment) {
+ }
+
+ });
+ }
+
+ @Override
+ public void failed(Throwable exc, Object attachment) {
+ }
+
+ });
+ }
+
+ @Override
+ public void failed(Throwable exc, Object attachment) {
+ }
+
+ };
+
+ client.connect(serverAddress, null, handler);
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
+```
+
+
+
diff --git "a/docs/JavaIO/8_JavaIO346円226円271円345円274円217円.md" "b/docs/JavaIO/8_JavaIO346円226円271円345円274円217円.md"
index 1524bd76..5ba72d74 100644
--- "a/docs/JavaIO/8_JavaIO346円226円271円345円274円217円.md"
+++ "b/docs/JavaIO/8_JavaIO346円226円271円345円274円217円.md"
@@ -1,34 +1,23 @@
-# JavaIO 方式
+# Java I/O 方式
-## 同步、异步和阻塞、非阻塞
-
-- 同步、异步是消息通知机制
-- 阻塞、非阻塞是**等待通知时的状态**
-
-## Java 3 种 IO 方式
+## BIO & NIO & AIO
Java IO 的方式通常分为阻塞的 BIO(Blocking IO)、同步非阻塞的 NIO(New IO) 和异步非阻塞的 AIO(Asynchronous IO)。
JDK1.4 之前只支持 BIO,JDK1.4 以后开始支持 NIO,JDK1.7 开始支持 AIO。
-
-
### 1. BIO
BIO 是同步阻塞的。服务器的模式为**一个连接一个线程**。
客户端有连接请求时,就需要启动一个线程进行处理。如果这个连接不做任何事情,就会造成不必要的开销,可以通过[线程池机制](https://duhouan.github.io/Java/#/Java_Concurrency/8_%E7%BA%BF%E7%A8%8B%E6%B1%A0)改善。
-
-
### 2. NIO
NIO 是同步非阻塞的。服务器模式为**一个请求一个线程**。
NIO 最重要的地方是当一个连接建立后,不需要对应一个线程,这个连接会被注册到多路复用器上面,所以所有的连接只需要一个线程即可,当这个线程中的多路复用器进行轮询时,发现连接上有请求的话,才开启一个线程进行处理。
-
-
### 3. AIO
AIO 是异步阻塞的。服务器模式为**一个有效请求一个线程**。
diff --git "a/docs/Java_Concurrency/1_345円237円272円347円241円200円347円237円245円350円257円206円.md" "b/docs/Java_Concurrency/1_345円237円272円347円241円200円347円237円245円350円257円206円.md"
index 5a066dab..4850e65d 100644
--- "a/docs/Java_Concurrency/1_345円237円272円347円241円200円347円237円245円350円257円206円.md"
+++ "b/docs/Java_Concurrency/1_345円237円272円347円241円200円347円237円245円350円257円206円.md"
@@ -1,54 +1,52 @@
-# 基础知识
+# 一、进程和线程
-## 一、进程和线程
+## 进程
-### 1. 进程
+进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。在 Java 中,当启动 main 方法时其实就是启动了一个 JVM 的进程,而 main 方法所在的线程就是这个进程中的一个线程,称为**主线程**。
-进程是**资源分配的基本单位**。
+进程是**资源分配的基本单位**。每一个进程都有它自己的内存空间和系统资源。
-进程控制块 (Process Control Block, PCB) 描述进程的基本信息和运行状态,所谓的创建进程和撤销进程,都是指对 PCB 的操作。
+## 线程
-下图显示了 4 个程序创建了 4 个进程,这 4 个进程可以并发地执行。
+线程与进程相似,但线程是一个比进程更小的**执行单位**,是进程中的单个顺序控制流,是一条执行路径。
-
+一个进程如果只有一条执行路径,则称为单线程程序;一个进程如果有多条执行路径,则称为多线程程序。
-### 2. 线程
+一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的**堆**和**方法区**资源,但每个线程有自己的**程序计数器**、**虚拟机栈**和**本地方法栈**,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为**轻量级进程**。
-**线程是 CPU 调度的基本单位**。
+Java 程序是多线程程序,这是因为 JVM 启动至少了主线程和垃圾回收线程。
-一个进程中可以有多个线程,它们共享进程资源。
-
-QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 HTTP 请求线程、事件响应线程、渲染线程等等,线程的并发执行使得在浏览器中点击一个新链接从而发起 HTTP 请求时,浏览器还可以响应用户的其它事件。
-
-
-
-### 3. 进程与线程的区别
+## 进程和线程的区别
- **资源**
- 进程是资源调度的基本单位。
+ 一个进程中可以有多个线程,多个线程共享进程的**堆**和**方法区**资源,但是每个线程有自己的**程序计数器**、**虚拟机栈** 和**本地方法栈**。
- 线程不占有任何资源。
+- **是否独立**
-- **调度**
+ 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。
- 线程是独立调度的基本单位。
+- **开销**
- 同一进程中,线程的切换不会引起进程切换,但一个进程中线程切换到另一个进程中的线程时,就会引起进程切换了。
+ 线程执行开销小,但不利于资源的管理和保护,而进程正相反。
-- **系统开销**
+> 思考 1:程序计数器线程私有?
- 进程的创建和撤销所付出的开销比线程的创建和撤销要大很多。
+程序计数器是主要有以下两个作用:
- 类似的,进程切换的开销也比线程切换的开销要大很多,因为进程切换时,涉及到**当前执行进程的 CPU 环境的保存**和**新调度进程 CPU 环境的设置**,而线程切换时只需要设置和保存少量的寄存器内容,开销很小。
+- 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
+- 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
-- **通信**
+所以,程序计数器私有主要是为了**线程切换后能恢复到正确的执行位置**。
- 线程可以通过**直接读写同一进程中的数据**进行通信。
+> 思考 2 :Java 虚拟机栈、本地方法栈线程私有?
- 进程之间的通信需要借助 IPC (InterProcess Communication)。
+- Java 虚拟机栈中每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
+- 本地方法栈和虚拟机栈所发挥的作用非常相似,它们之间的区别只不过是本地方法栈为本地方法服务。
-### 4. 多进程与多线程
+所以,为了**保证线程中的局部变量不被别的线程访问到**,Java 虚拟机栈和本地方法栈是线程私有的。
+
+## 多进程与多线程
多进程的目的是**提高 CPU 的使用率**。
@@ -56,49 +54,125 @@ QQ 和浏览器是两个进程,浏览器进程里面有很多线程,例如 H
多线程共享同一进程资源(堆内存和方法区),但是栈内存是独立的,JVM 中一个线程对应一个线程栈。多线程抢夺的是 CPU 资源,一个时间点上只有一个线程运行。
-### 5. 并发与并行
+## 上下文切换
+
+线程在执行过程中会有自己的运行条件和状态,即上下文,比如程序计数器,栈信息等。当出现以下几种情况的时,线程会从占用 CPU 状态中退出:
+
+- 主动让出 CPU,比如调用了 `sleep()`, `wait()` 等。
+- 时间片用完,因为操作系统要防止一个线程或者进程长时间占用 CPU 导致其他线程或者进程饿死。
+- 调用了阻塞类型的系统中断,比如请求 IO,线程被阻塞。
+- 被终止或结束运行
-- 并发:一个处理器同时处理多个任务,是**逻辑上的同时发生**。
+其中前三种都会发生线程切换,线程切换意味着需要保存当前线程的上下文,留待线程下次占用 CPU 的时候恢复现场,并加载下一个将要占用 CPU 的线程上下文。这就是**上下文切换**。
-- 并行:多个处理器或者多核处理器同时处理多个不同的任务,是**物理上的同时发生**。
+上下文切换是现代操作系统的基本功能,因其每次需要保存信息恢复信息,这将会占用 CPU,内存等系统资源进行处理,也就意味着效率会有一定损耗,如果频繁切换就会造成整体效率低下。
-来个比喻:并发是一个人同时吃三馒头,而并行就是三个人同时吃三个馒头。
+## 并发与并行
-下图反映了一个包含 8 个操作的任务在一个有两核心的 CPU 中创建四个线程运行的情况。
+并发:一个处理器同时处理多个任务,是**逻辑上的同时发生**。
-假设每个核心有两个线程,那么每个CPU中两个线程会交替并发,两个CPU之间的操作会并行运算。
+并行:多个处理器或者多核处理器同时处理多个不同的任务,是**物理上的同时发生**。
+
+下图反映了一个包含 8 个操作的任务在一个有两核心的 CPU 中创建四个线程运行的情况。 假设每个核心有两个线程,那么每个CPU中两个线程会交替并发,两个CPU之间的操作会并行运算。
单就一个 CPU 而言两个线程可以解决线程阻塞造成的不流畅问题,其本身运行效率并没有提高, 多 CPU 的并行运算才真正解决了运行效率问题,这也正是并发和并行的区别。
-
+
+
+
+
+## 多线程的好处
+
+- **从计算机底层来说:** 线程可以比作是**轻量级的进程**,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程。另外,多核 CPU 时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。
+- **从当代互联网发展趋势来说:** 现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程正是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。
+
-## 二、创建线程的几种方式
-### 1. 三种创建线程的方式
+## 多线程带来的问题
+
+并发编程的目的就是为了能提高程序的执行效率,从而提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:**内存泄漏**、**死锁**、**线程不安全**等等。
+
+
+
+# 二、创建线程
+
+## 三种创建线程的方式
Java 有 3 种创建线程的方式:
- 继承 Thread 类,重写 run() 方法
+- 实现 Runnable 接口,重写 run() 方法
+- 实现 Callable 接口,重写 call() 方法
- ```java
- public class MyThread extends Thread{
- private void attack() {
- System.out.println("Fight");
- System.out.println("Current Thread is : " + Thread.currentThread().getName());
- }
-
- @Override
- public void run() { //重写 run() 方法
- attack();
- }
- }
- ```
+### 继承 Thread 类
-- 实现 Runnable 接口,重写 run() 方法
+需要实现 run() 方法,因为 Thread 类也实现了 Runable 接口。
-- 实现 Callable 接口,重写 call() 方法
+当调用 start() 方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 run() 方法。
-### 2. 选择
+```java
+public class MyThread extends Thread {
+ public void run() {
+ // ...
+ }
+}
+```
+
+```java
+public static void main(String[] args) {
+ MyThread mt = new MyThread();
+ mt.start();
+}
+```
+
+### 实现 Runnable 接口
+
+需要实现接口中的 run() 方法。使用 Runnable 实例再创建一个 Thread 实例,然后调用 Thread 实例的 start() 方法来启动线程。
+
+```java
+public class MyRunnable implements Runnable {
+ @Override
+ public void run() {
+ // ...
+ }
+}
+```
+
+```java
+public static void main(String[] args) {
+ MyRunnable instance = new MyRunnable();
+ Thread thread = new Thread(instance);
+ thread.start();
+}
+```
+
+
+
+### 实现 Callable 接口
+
+需要实现接口中的 call() 方法。 call() 方法有返回值,返回值通过 FutureTask 进行封装。
+
+```java
+public class MyCallable implements Callable {
+ public Integer call() {
+ return 1;
+ }
+}
+```
+
+```java
+public static void main(String[] args) throws ExecutionException, InterruptedException {
+ MyCallable mc = new MyCallable();
+ FutureTask ft = new FutureTask(mc); // 返回值通过 FutureTask 进行封装
+ Thread thread = new Thread(ft);
+ thread.start();
+ System.out.println(ft.get());
+}
+```
+
+
+
+## 选择线程创建方式
实现接口会更好一些:
@@ -118,7 +192,7 @@ Java 有 3 种创建线程的方式:
注意:一个线程只能 start 一次,如果多次 start,会出现 `java.lang.IllegalException`。
-### 3. Runnable 和 Callable 的区别
+## Runnable 和 Callable 的区别
- Callable 的实现方法是 **call()**
@@ -132,9 +206,9 @@ Java 有 3 种创建线程的方式:
Runnable 的 run() 方法不能抛出异常
-### 4. 实现处理线程的返回值
+## 实现处理线程的返回值
-#### 方式一:主线程等待法
+### 方式一:主线程等待法
如果未获取到值,则主线程等待,一直到获取值为止。
@@ -168,7 +242,7 @@ public class CycleWait implements Runnable{
}
```
-#### 方式二:join()
+### 方式二:join()
使用 Thread 的 join() **阻塞当前线程以等待子线程处理完毕**。
@@ -200,7 +274,7 @@ public class CycleWait2 implements Runnable{
}
```
-#### 方式三:Callable + FutureTask
+### 方式三:Callable + FutureTask
实现 Callablee 接口,使用 FutureTask 接收 call() 方法返回的数据。
@@ -233,7 +307,7 @@ public class CycleWait3 implements Callable{
}
```
-#### 方式四:线程池 + Future
+### 方式四:线程池 + Future
线程池执行任务,返回的结果使用 Future 接收。
@@ -269,67 +343,11 @@ public class CycleWait4 implements Callable{
}
```
-## 三、线程的几种状态
-
-
-
-### 新建(New)
-
-创建后尚未启动。
-
-### 可运行(Runnable)
-
-可能正在运行,也可能正在等待 CPU 时间片。
-
-包含了操作系统线程状态中的 Running 和 Ready。
-
-调用 `start()` 方法后开始运行,线程这时候处于 Ready 状态。可运行状态的线程获得了 CPU 时间片后就进入Running 状态。
-
-### 阻塞(Blocked)
-
-等待获取一个**排它锁**,如果其他线程释放了锁就会结束此状态。
-
-### 无限期等待(Waiting)
-
-等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。
-
-| 进入方法 | 退出方法 |
-| ------------------------------------------ | ------------------------------------ |
-| 没有设置 Timeout 参数的 Object.wait() 方法 | Object.notify() / Object.notifyAll() |
-| 没有设置 Timeout 参数的 Thread.join() 方法 | 被调用的线程执行完毕 |
-| LockSupport.park() 方法 | LockSupport.unpark(Thread) |
-
-### *限期等待(Timed Waiting)
-
-无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。
-
-调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用"使一个线程睡眠"进行描述。
-
-调用 Object.wait() 方法使线程进入限期等待或者无限期等待时,常常用"挂起一个线程"进行描述。
-**睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态**。
-| 进入方法 | 退出方法 |
-| ---------------------------------------- | ----------------------------------------------- |
-| Thread.sleep() 方法 | 时间结束 |
-| 设置了 Timeout 参数的 Object.wait() 方法 | 时间结束 / Object.notify() / Object.notifyAll() |
-| 设置了 Timeout 参数的 Thread.join() 方法 | 时间结束 / 被调用的线程执行完毕 |
-| LockSupport.parkNanos() 方法 | LockSupport.unpark(Thread) |
-| LockSupport.parkUntil() 方法 | LockSupport.unpark(Thread) |
+# 三、基础线程机制
-**阻塞和等待的区别**在于:
-
-阻塞是被动的,它是在等待获取一个排它锁。
-
-等待是主动的,通过调用 Thread.sleep() 和 Object.wait() 等方法进入。
-
-### 死亡(Terminated)
-
-可以是线程完成任务后自己结束,或者产生了异常而结束。
-
-## 四、基础线程机制
-
-### Executor
+## Executor
Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。这里的异步是指**多个任务的执行互不干扰,不需要进行同步操作**。
@@ -349,7 +367,7 @@ public static void main(String[] args) {
}
```
-### Daemon
+## Daemon
Java 中有两类线程:
@@ -367,7 +385,7 @@ public static void main(String[] args) {
}
```
-### start() 和 run()
+## start() & run()
- start()
@@ -381,7 +399,7 @@ public static void main(String[] args) {
单独调用 run() 不会启动新线程
-### sleep() 和 wait()
+## sleep() & wait()
sleep() 和 wait() 的区别:
@@ -403,7 +421,7 @@ sleep() 和 wait() 的区别:
sleep() 可在任何地方使用
-### yield() 和 wait()
+## yield() & wait()
Thread.yield() 声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。
@@ -421,7 +439,7 @@ yield() 和 wait() 的区别:
wait() 会释放同步锁,使得其他线程可以使用同步块或者同步方法
-### notify() 和 notifyAll()
+## notify() & notifyAll()
锁池:线程 A 已经拥有某个对象的锁,线程 B、C 想要调用这个对象的同步方法,此时线程 B、C 就会被阻塞,进入一个地方去等待锁的释放,这个地方就是该对象的锁池。
@@ -431,20 +449,98 @@ yield() 和 wait() 的区别:
-## 五、终止线程的几种方式
+# 四、线程生命周期和状态
+
+## 6 种线程状态
-### 1. 退出标志
+### 新建(New)
+
+线程被创建,但是还没有调用 start() 方法
+
+### 可运行(Runnable)
+
+可能正在运行,也可能正在等待 CPU 时间片。
+
+包含了操作系统线程状态中的运行状态(Running)和就绪状态( Ready)。
+
+调用 start() 方法后开始运行,线程这时候处于 Ready 状态。Ready 状态的线程获得了 CPU 时间片后就进入Running 状态。
+
+### 阻塞(Blocked)
+
+等待获取一个**排它锁**,如果其他线程释放了锁就会结束此状态。
+
+### 无限期等待(Waiting)
+
+等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。
+
+| 进入方法 | 退出方法 |
+| ------------------------------------------ | ------------------------------------ |
+| 没有设置 Timeout 参数的 Object.wait() 方法 | Object.notify() / Object.notifyAll() |
+| 没有设置 Timeout 参数的 Thread.join() 方法 | 被调用的线程执行完毕 |
+| LockSupport.park() 方法 | LockSupport.unpark(Thread) |
+
+### 限期等待(Timed Waiting)
+
+无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。
+
+| 进入方法 | 退出方法 |
+| ---------------------------------------- | ----------------------------------------------- |
+| Thread.sleep() 方法 | 时间结束 |
+| 设置了 Timeout 参数的 Object.wait() 方法 | 时间结束 / Object.notify() / Object.notifyAll() |
+| 设置了 Timeout 参数的 Thread.join() 方法 | 时间结束 / 被调用的线程执行完毕 |
+| LockSupport.parkNanos() 方法 | LockSupport.unpark(Thread) |
+| LockSupport.parkUntil() 方法 | LockSupport.unpark(Thread) |
+
+调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用"使一个线程睡眠"进行描述。
+
+调用 Object.wait() 方法使线程进入限期等待或者无限期等待时,常常用"挂起一个线程"进行描述。
+
+**睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态**。
+
+**阻塞和等待的区别**在于:
+
+阻塞是被动的,它是在等待获取一个排它锁。
+
+等待是主动的,通过调用 Thread.sleep() 和 Object.wait() 等方法进入。
+
+### 死亡(Terminated)
+
+可以是线程完成任务后自己结束,或者产生了异常而结束。
+
+
+
+## 线程生命周期
+
+线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换:
+
+
+
+
+
+线程创建之后它将处于NEW 状态,调用 `start()` 方法后开始运行,线程这时候处于 READY 状态。Ready 状态的线程获得了 CPU 时间片(timeslice)后就处于 RUNNING 状态。
+
+当线程执行 `wait()`方法之后,线程进入 WAITING 状态。进入 WAITING 状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 TIME_WAITING 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleep(long millis)`方法或 `wait(long millis)`方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。
+
+当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED 状态。
+
+线程在执行 Runnable 的`run()`方法之后将会进入到 TERMINATE 状态。
+
+
+
+# 五、终止线程的几种方式
+
+## 1. 退出标志
使用退出标志,使线程正常退出,也就是 run 方法完成后线程终止。
需要 while() 循环以某种特定的条件下退出,最直接的方法就是设计一个 boolean 类型的标志,并且通过设置这个标志来控制循环是否退出。
一般需要加上 volatile 来保证标志的可见性。
-### 2. Thread.stop()
+## 2. Thread.stop()
Thread.stop() 强制终止线程(不建议使用)。
-### 3. interrupt() 中断线程
+## 3. interrupt() 中断线程
- 线程处于阻塞状态,使用 interrupt() 则会抛出 InteruptedException 异常。
@@ -517,4 +613,5 @@ t1 (RUNNABLE) loop 2
t1 (TIMED_WAITING) is interrupted.
t1 (RUNNABLE) catch InterruptedException.
t1 (TERMINATED) is interrupted now.
-```
\ No newline at end of file
+```
+
diff --git "a/docs/Java_Concurrency/2_345円271円266円345円217円221円347円220円206円350円256円272円.md" "b/docs/Java_Concurrency/2_345円271円266円345円217円221円347円220円206円350円256円272円.md"
index 65864941..e354e7ad 100644
--- "a/docs/Java_Concurrency/2_345円271円266円345円217円221円347円220円206円350円256円272円.md"
+++ "b/docs/Java_Concurrency/2_345円271円266円345円217円221円347円220円206円350円256円272円.md"
@@ -6,123 +6,57 @@ Java 内存模型(即 Java Memory Model,简称 JMM)试图屏蔽各种硬
本身是一种**抽象的概念,并不真实存在**,它描述的是一组规则或规范。
-### 1. 主内存与工作内存
+### CPU 缓存
-处理器上的寄存器的读写的速度比内存快几个数量级,为了解决这种速度矛盾,在它们之间加入了高速缓存。
+**CPU 缓存的是内存数据用于解决 CPU 处理速度和内存不匹配的问题**。
-加入高速缓存带来了一个新的问题:缓存一致性。如果多个缓存共享同一块主内存区域,那么多个缓存的数据可能会不一致,需要一些协议来解决这个问题。
+通常情况下,当一个 CPU 需要读取主存数据时,它会将主存的数据读到 CPU 缓存中,甚至可能将缓存中的部分内容读到它的内部寄存器中,然后在寄存器中执行操作。
-
+当 CPU 需要将结果写回到主存中去时,它会将内部寄存器的值刷新到缓存中,然后在某个时间点将值刷新回主存。
-JMM 规定所有的变量都存储在主内存中,每个线程还有自己的工作内存,**工作内存存储在高速缓存或者寄存器**中,保存了该线程使用的**变量的主内存副本拷贝**。
+
-**线程只能直接操作工作内存中的变量,不同线程之间的变量值传递需要通过主内存来完成**。
-
-
-
-### 2. JMM 三大特性
-
-> **内存间的交互操作**
-
-Java 内存模型定义了 8 个操作来完成主内存和工作内存的交互操作。
-
-
+加入 CPU 缓存带来了一些新的问题:
-- read:把一个变量的值从主内存传输到工作内存中
-- load:在 read 之后执行,把 read 得到的值放入工作内存的变量副本中
-- use:把工作内存中一个变量的值传递给执行引擎
-- assign:把一个从执行引擎接收到的值赋给工作内存的变量
-- store:把工作内存的一个变量的值传送到主内存中
-- write:在 store 之后执行,把 store 得到的值放入主内存的变量中
-- lock:作用于主内存的变量
-- unlock
+- 缓存一致性问题:当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致的情况,如果真的发生这种情况,需要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操作,这类协议有 MSI、MESI 等。
+- 指令重排序问题: 为了使得处理器内部的运算单元能尽量被充分利用,处理器可能会对输入代码进行**乱序执行(Out-Of-Order Execution)优化**,处理器会在计算之后将乱序执行的结果重组,保证该结果与顺序执行的结果是一致的,但并不保证程序中各个语句计算的先后顺序与输入代码中的顺序一致。
-#### 特性一:原子性
+### 主内存与工作内存
-**Java 内存模型保证了 read、load、use、assign、store、write、lock 和 unlock 操作具有原子性**。
+JMM 规定线程之间的共享变量存放在主内存(主内存就是硬件内存)中,每个线程还有自己的工作内存,存放该线程读/写共享变量的拷贝副本,**工作内存存储在 CPU 高速缓存或者寄存器中**。
-例如对一个 int 类型的变量执行 assign 赋值操作,这个操作就是原子性的。但是 Java 内存模型允许虚拟机将没有被 volatile 修饰的 64 位数据(long,double)的读写操作划分为两次 32 位的操作来进行,即 load、store、read 和 write 操作可以不具备原子性。
-
-有一个错误认识就是,int 等原子性的类型在多线程环境中不会出现线程安全问题。前面的线程不安全示例代码中,cnt 属于 int 类型变量,1000 个线程对它进行自增操作之后,得到的值为 997 而不是 1000。
-
-为了方便讨论,将内存间的交互操作简化为 3 个:load、assign、store。
+**线程只能直接操作工作内存中的变量,不同线程之间的变量值传递需要通过主内存来完成**。
-下图演示了两个线程同时对 cnt 进行操作,load、assign、store 这一系列操作整体上看不具备原子性,那么在 T1 修改 cnt 并且还没有将修改后的值写入主内存,T2 依然可以读入旧值。可以看出,这两个线程虽然执行了两次自增运算,但是主内存中 cnt 的值最后为 1 而不是 2。因此对 int 类型读写操作满足原子性只是说明 load、assign、store 这些单个操作具备原子性。
+
-
+### 主内存和工作内存的交互
-AtomicInteger 能保证多个线程修改的原子性。
+JMM 定义了 8 个操作来完成主内存和工作内存的交互操作。
-
+
-使用 AtomicInteger 重写之前线程不安全的代码之后得到以下线程安全实现:
+- lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
+- unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
+- read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
+- load(载入):作用于工作内存的变量,它把 read 操作从主内存中得到的变量值放入工作内存的变量副本中。
+- use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
+- assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
+- store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的 write 的操作。
+- write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
-```java
-public class AtomicExample {
- private AtomicInteger cnt = new AtomicInteger();
- public void add() {
- cnt.incrementAndGet();
- }
- public int get() {
- return cnt.get();
- }
-}Copy to clipboardErrorCopied
-public static void main(String[] args) throws InterruptedException {
- final int threadSize = 1000;
- AtomicExample example = new AtomicExample(); // 只修改这条语句
- final CountDownLatch countDownLatch = new CountDownLatch(threadSize);
- ExecutorService executorService = Executors.newCachedThreadPool();
- for (int i = 0; i < threadSize; i++) { - executorService.execute(() -> {
- example.add();
- countDownLatch.countDown();
- });
- }
- countDownLatch.await();
- executorService.shutdown();
- System.out.println(example.get());
-}Copy to clipboardErrorCopied
-1000Copy to clipboardErrorCopied
-```
+### 内存模型三大特性
-除了使用原子类之外,也可以使用 synchronized 互斥锁来保证操作的原子性。它对应的内存间交互操作为:lock 和 unlock,在虚拟机实现上对应的字节码指令为 monitorenter 和 monitorexit。
+#### 原子性
-```java
-public class AtomicSynchronizedExample {
- private int cnt = 0;
+原子性指一次的操作或多次操作,要么所有的操作全部都得到执行并且不会受到任何因素的干扰而中断,要么所有的操作都执行,要么都不执行。
- public synchronized void add() {
- cnt++;
- }
+**JMM 保证了 read、load、use、assign、store、write、lock 和 unlock 操作具有原子性**。例如对一个 int 类型的变量执行 assign 赋值操作,这个操作就是原子性的。但是 JMM 允许虚拟机将没有被 volatile 修饰的 64 位数据(long,double)的读写操作划分为两次 32 位的操作来进行,即 load、store、read 和 write 操作可以不具备原子性。
- public synchronized int get() {
- return cnt;
- }
-}Copy to clipboardErrorCopied
-public static void main(String[] args) throws InterruptedException {
- final int threadSize = 1000;
- AtomicSynchronizedExample example = new AtomicSynchronizedExample();
- final CountDownLatch countDownLatch = new CountDownLatch(threadSize);
- ExecutorService executorService = Executors.newCachedThreadPool();
- for (int i = 0; i < threadSize; i++) { - executorService.execute(() -> {
- example.add();
- countDownLatch.countDown();
- });
- }
- countDownLatch.await();
- executorService.shutdown();
- System.out.println(example.get());
-}
-```
+AtomicInteger 等原子操作类能保证多个线程修改的原子性。除了使用原子操作类之外,也可以使用 synchronized 互斥锁来保证操作的原子性。它对应的内存间交互操作为:lock 和 unlock,在虚拟机实现上对应的字节码指令为 monitorenter 和 monitorexit。
-```java
-1000
-```
-
-#### 特性二:可见性
+#### 可见性
可见性指当一个线程修改了**共享变量的值**,其它线程能够立即得知这个修改。
@@ -134,7 +68,7 @@ public static void main(String[] args) throws InterruptedException {
注意: volatile 并不能保证操作的原子性。
-#### 特性三:有序性
+#### 有序性
有序性是指在一个线程内观察,所有的操作都是有序的。在一个线程观察另一个线程,所有操作都是无序的,**无序是因为发生了指令重排序**。
@@ -145,67 +79,26 @@ volatile 和 synchronized 都可保证有序性:
- volatile 关键字通过添加**内存屏障**的方式来禁止指令重排,即重排序时不能把后面的指令放到内存屏障之前。
- synchronized 来保证有序性,它**保证每个时刻只有一个线程执行同步代码**,相当于是让线程顺序执行同步代码。
-### 3. 先行发生原则
-
-#### 规则一:单一线程原则
-
-> Single Thread rule
-
-在一个线程内,在程序前面的操作先行发生于后面的操作。
-
-
-
-#### 规则二:管程锁定规则
-
-一个 unlock 操作先行发生于后面对同一个锁的 lock 操作。
-
-
-
-#### 规则三:volatile 变量规则
-
-> Volatile Variable Rule
-
-对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作。
-
-
-
-#### 规则四:线程启动规则
-
-> Thread Start Rule
-
-Thread 对象的 start() 方法调用先行发生于此线程的每一个动作。
-
-
-
-#### 规则五:线程加入规则
-
-> Thread Join Rule
-
-Thread 对象的结束先行发生于 join() 方法返回。
-
-
-
-#### 规则六:线程中断规则
-
-> Thread Interruption Rule
-
-对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 interrupted() 方法检测到是否有中断发生。
-
-#### 规则七:对象终结规则
+### 先行发生原则
-> Finalizer Rule
+除了使用 volatile 和 synchronized 来保证有序性之外,JVM 还规定了先行发生原则,让一个操作无需控制就能先于另一个操作完成。
-一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始。
+- 单一线程原则(Single Thread Rule):在一个线程内,在程序前面的操作先行发生于后面的操作。
+- 管程锁定规则(Monitor Lock Rule):一个 unlock 操作先行发生于后面对同一个锁的 lock 操作。
+- volatile 变量规则(Volatile Variable Rule):对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作。
+- 线程启动规则(Thread Start Rule):Thread 对象的 start() 方法调用先行发生于此线程的每一个动作。
+- 线程加入规则(Thread Join Rule):Thread 对象的结束先行发生于 join() 方法返回。
+- 线程中断规则(Thread Interruption Rule):对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 interrupted() 方法检测到是否有中断发生。
+- 对象终结规则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始。
+- 传递性(Transitivity):如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那么操作 A 先行发生于操作 C。
-#### 规则八:传递性
+如果两个操作之间的关系不在此列,并且无法从以上规则中推导出来的话,则它们就没有顺序性保障,虚拟机可以对它们随意地进行重排序。
-> Transitivity
-如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那么操作 A 先行发生于操作 C。
-## 乐观锁和悲观锁
+## 乐观锁和悲观锁思想
-### 1. 悲观锁
+### 悲观锁
互斥同步最主要的问题就是线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。
@@ -213,13 +106,13 @@ Thread 对象的结束先行发生于 join() 方法返回。
Java 中 **synchronized** 和 **ReentrantLock** 等独占锁就是悲观锁思想的实现。
-### 2. 乐观锁
+### 乐观锁
乐观锁基于**冲突检测**的乐观并发策略:**先进行操作,如果没有其他线程争用共享数据,操作成功;如果数据存在竞争,就采用补偿措施(常见的有不断重试,直到成功)**。这种乐观的并发策略的许多实现是不需要将线程挂起的,因此这种同步操作称为**非阻塞同步**。
-### 3. CAS
+### CAS
-乐观锁需要**操作**和**冲突检测**这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,只能靠硬件来完成。、
+乐观锁需要**操作**和**冲突检测**这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,只能靠**硬件**来完成。
硬件支持的原子性操作最典型的是:CAS(Compare-and-Swap)。
diff --git "a/docs/Java_Concurrency/3_345円271円266円345円217円221円345円205円263円351円224円256円345円255円227円.md" "b/docs/Java_Concurrency/3_345円271円266円345円217円221円345円205円263円351円224円256円345円255円227円.md"
index e017026c..b5f51931 100644
--- "a/docs/Java_Concurrency/3_345円271円266円345円217円221円345円205円263円351円224円256円345円255円227円.md"
+++ "b/docs/Java_Concurrency/3_345円271円266円345円217円221円345円205円263円351円224円256円345円255円227円.md"
@@ -139,7 +139,7 @@ public class SynchronizedDemo {
}
```
-
+
任意一个对象都拥有自己的 Monitor,当这个对象由同步块或者同步方法调用时, 执行方法的线程必须先获取该对象的 Monitor 才能进入同步块和同步方法, 如果没有获取到 Monitor 的线程将会被阻塞在同步块和同步方法的入口处,进入到 BLOCKED 状态。
@@ -159,12 +159,7 @@ synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令
JDK 1.6 之后对 synchronized 进行优化。
-锁的 4 种状态:
-
-- 无锁
-- 偏向锁
-- 轻量级锁
-- 重量级锁
+锁的 4 种状态:无锁、偏向锁、轻量级锁、重量级锁。
#### 1. 自旋锁
@@ -236,4 +231,7 @@ public static String concatString(String s1, String s2, String s3) {
- volatile 修饰的变量不会被编译器优化
- synchronized 修饰的变量可以被编译器优化
\ No newline at end of file
+ synchronized 修饰的变量可以被编译器优化
+
+
+
diff --git "a/docs/Java_Concurrency/4_Lock 344円275円223円347円263円273円.md" "b/docs/Java_Concurrency/4_Lock 344円275円223円347円263円273円.md"
index aba74653..12de88f0 100644
--- "a/docs/Java_Concurrency/4_Lock 344円275円223円347円263円273円.md"
+++ "b/docs/Java_Concurrency/4_Lock 344円275円223円347円263円273円.md"
@@ -1,5 +1,27 @@
# Lock 体系
+## AQS
+
+AQS(AbtsractQueueSynchronized) 即同步队列器。
+
+AQS 是一个**抽象类**,本身并没有实现任何同步接口的,只是通过提供**同步状态的获取和释放**来供自定义的同步组件使用。
+
+AQS 的实现依赖内部的双向队列(底层是双向链表),称为同步队列。
+
+如果当前线程获取同步状态失败,则会将该线程以及等待状态等信息封装为 Node,将其**加入同步队列的尾部,同时阻塞当前线程**,当同步状态释放时,唤醒队列的头结点。
+
+AQS 使用 CAS 对该同步状态进行原子操作实现对其值的修改。
+
+```java
+private transient volatile Node head; //同步队列的头结点
+private transient volatile Node tail; //同步队列的尾结点
+private volatile int state; //同步状态。
+// state=0,表示同步状态可用
+// state=1,表示同步状态已被占用
+```
+
+补充:[AQS详解](https://www.cnblogs.com/chengxiao/archive/2017/07/24/7141160.html)
+
## Condition 条件对象
条件对象是**线程同步对象中的一种**,主要用来等待某种条件的发生,条件发生后,可以唤醒等待在该条件上的一个线程或者所有线程。
@@ -11,61 +33,51 @@ ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
```
+Condition 中有 awai()、signal()、signalAll() 方法,分别对应 Object 中放入 wait()、notify()、notifyAll() 方法,其实 Condition 也有上述三种方法,改变方法名称是为了避免使用上语义的混淆。
+补充:[Condition 详解](https://blog.csdn.net/qq_38293564/article/details/80554516)
-注意:
-- Condition 中有 await、signal、signalAll ,分别对应 Object 中放入 wait、notify、notifyAll 方法,其实 Condition 也有上述三种方法,改变方法名称是为了避免使用上语义的混淆。
- await 和 signal / signalAll 方法就像一个开关控制着线程 A(等待方)和线程 B(通知方)。
+## 可重入
- 
+某个线程试图获取一个已经由该线程持有的锁,那么这个请求就会成功。"重入"意味着获取的锁的操作的粒度是"线程"而不是"调用"。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。
- 线程 awaitThread 先通过 lock.lock() 方法获取锁成功后调用了 condition.await 方法进入**等待队列**,
+重入的一种实现方法是:为每个锁关联一个**计数器**(方便解锁)和一个**所有者线程**(知道是哪个线程是可重入的),同一个线程每次获取锁,锁的计数器都自增 1,所以要等到锁的计数器下降为 0 时才能释放锁。
- 另一个线程 signalThread 通过 lock.lock() 方法获取锁成功后调用了 **condition.signal / signalAll, 使得线程 awaitThread 能够有机会移入到同步队列**中, 当其他线程释放 Lock 后使得线程 awaitThread 能够有机会获取 Lock, 从而使得线程 awaitThread 能够从 await 方法中退出,然后执行后续操作。 如果 awaitThread 获取 Lock 失败会直接进入到同步队列。
-- 一个 Lock 可以与多个 Condition 对象绑定。
-## AQS
+## 公平锁与非公平锁
-AQS(AbtsractQueueSynchronized) 即同步队列器。
+公平锁是指多个线程在等待同一个锁时,**按照申请锁的顺序来依次获取锁**。
-AQS 是一个抽象类,本身并没有实现任何同步接口的,只是通过提供**同步状态的获取和释放**来供自定义的同步组件使用。
+### 公平锁
-AQS 的实现依赖内部的双向队列(底层是双向链表)。
+- 公平锁每次获取到锁为同步队列中的第一个节点,保证请求资源时间上的绝对顺序。
-如果当前线程获取同步状态失败,则会将该线程以及等待状态等信息封装为 Node,将其**加入同步队列的尾部,同时阻塞当前线程**,当同步状态释放时,唤醒队列的头结点。
+- 公平锁为了保证时间上的绝对顺序,需要频繁的上下文切换,性能较差
-```java
-private transient volatile Node head; //同步队列的头结点
-private transient volatile Node tail; //同步队列的尾结点
-private volatile int state; //同步状态。
-// state=0,表示同步状态可用;state=1,表示同步状态已被占用
-```
+### 非公平锁
-## 可重入
+- 非公平锁有可能刚释放锁的线程下次继续获取该锁,则有可能导致其他线程永远无法获取到锁,造成 "饥饿" 现象。
-某个线程试图获取一个已经有该线程持有的锁,那么这个请求就会成功。"重入"意味着获取的锁的操作的粒度是"线程"而不是"调用"。重入的一种实现方法是,为每个锁关联一个**计数器**(方便解锁)和一个**所有者线程**(知道是哪个线程是可重入的)。
+- 非公平锁会**降低一定的上下文切换**,降低性能开销。**ReentrantLock 默认选择的是非公平锁**。
-## 公平锁与非公平锁
-
-公平锁是指多个线程在等待同一个锁时,**按照申请锁的顺序来依次获取锁**。
-| 公平锁 | 非公平锁 |
-| :----------------------------------------------------------: | :----------------------------------------------------------: |
-| 公平锁每次获取到锁为同步队列中的第一个节点,
保证请求资源时间上的绝对顺序 | 非公平锁有可能刚释放锁的线程下次继续获取该锁,
则有可能导致其他线程永远无法获取到锁,造成"饥饿"现象。 |
-| 公平锁为了保证时间上的绝对顺序,
需要频繁的上下文切换 | 非公平锁会**降低一定的上下文切换**,降低性能开销
因此,ReentrantLock 默认选择的是非公平锁 |
## 独占锁和共享锁
-独占锁模式下,每次只能有一个线程能持有锁,ReentrantLock 就是以独占方式实现的互斥锁。
+### 独占锁
+
+- 独占锁模式下,每次只能有一个线程能持有锁,ReentrantLock 就是以独占方式实现的互斥锁。
+- 独占锁是一种悲观保守的加锁策略,它**避免了读/读冲突**,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。
+
+### 共享锁
-共享锁,则允许多个线程同时获取锁,并发访问共享资源,如:ReadWriteLock。
+- 共享锁允许多个线程同时获取锁,并发访问共享资源,如:ReadWriteLock。
+- 共享锁则是一种乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源。
-很显然,独占锁是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。
-共享锁则是一种乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源。
## ReentrantLock
@@ -85,11 +97,11 @@ static final class NonfairSync extends Sync {
}
```
-- Sync 是一个继承 AQS 的抽象类,并发控制就是通过 Sync 实现的。
+- Sync 是一个继承 AQS 的抽象类,并重写了 tryRelease() 方法,并发控制就是通过 Sync 实现的。
- 重写了 tryRelease() , 有两个子类 FiarSync 和 NonfairSync,即公平锁和非公平锁。
+ 此外 Sync 还有两个子类 FiarSync 和 NonfairSync,即公平锁和非公平锁。
-- 由于 Sync 重写 tryRealese() 方法,并且 FairSync 和 NonfairSync没有再次重写该方法,所以 **公平锁和非公平锁释放锁的操作是一样的**,即**唤醒等待队列中第一个被挂起的线程**。
+- 由于 Sync 重写 tryRealese() 方法,并且 FairSync 和 NonfairSync 没有再次重写该方法,所以**公平锁和非公平锁释放锁的操作是一样的**,即**唤醒等待队列中第一个被挂起的线程**。
- 公平锁和非公平锁获取锁的方式是不同的。
@@ -97,17 +109,19 @@ static final class NonfairSync extends Sync {
非公平锁获取锁的方式是一种**抢占式**的,不考虑线程等待时间,无论是哪个线程获取了锁,则其他线程就进入等待队列。
-```java
-private final Sync sync;
-
-public ReentrantLock() { //默认是非公平锁
- sync = new NonfairSync();
-}
+- ReentrantLock 默认选择的是非公平锁。
-public ReentrantLock(boolean fair) { //可设置为公平锁
- sync = fair ? new FairSync() : new NonfairSync();
-}
-```
+ ```java
+ private final Sync sync;
+
+ public ReentrantLock() { // 默认是非公平锁
+ sync = new NonfairSync();
+ }
+
+ public ReentrantLock(boolean fair) { // 可设置为公平锁
+ sync = fair ? new FairSync() : new NonfairSync();
+ }
+ ```
## ReentrantLock 与 synchronized 的区别
@@ -137,17 +151,15 @@ public ReentrantLock(boolean fair) { //可设置为公平锁
## LockSupport
-LockSupport 位于 java.util.concurrent.locks 包下。 LockSupprot 是线程的**阻塞原语**,用来**阻塞线程**和**唤醒线程**。
-
-每个使用 LockSupport 的线程都会与一个许可关联,
+LockSupport 位于 java.util.concurrent.locks 包下。
-如果该许可可用,并且可在线程中使用,则调用 park() 将会立即返回,否则可能阻塞。
+LockSupprot 是线程的**阻塞原语**,用来**阻塞线程**和**唤醒线程**。
-如果许可尚不可用,则可以调用 unpark 使其可用。
+每个使用 LockSupport 的线程都会与一个许可关联, 如果该许可可用,并且可在线程中使用,则调用 park() 将会立即返回,否则可能阻塞。 如果许可尚不可用,则可以调用 unpark() 方法使其可用。
但是注意**许可不可重入**,也就是说只能调用一次 park() 方法,否则会一直阻塞。
-### LockSupport 中方法
+### 成员方法
| 方法 | 说明 |
| :-------------------------------------------: | :----------------------------------------------------------: |
@@ -163,40 +175,39 @@ LockSupport 位于 java.util.concurrent.locks 包下。 LockSupprot 是线程的
- 调用 park() 方法 dump 线程:
-```
+```html
"main" #1 prio=5 os_prio=0 tid=0x02cdcc00 nid=0x2b48 waiting on condition [0x00d6f000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:304)
at learn.LockSupportDemo.main(LockSupportDemo.java:7)
-Copy to clipboardErrorCopied
```
- 调用 park(Object blocker) 方法 dump 线程:
-```java
+```html
"main" #1 prio=5 os_prio=0 tid=0x0069cc00 nid=0x6c0 waiting on condition [0x00dcf000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x048c2d18> (a java.lang.String)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
- at learn.LockSupportDemo.main(LockSupportDemo.java:7)Copy to clipboardErrorCopied
+ at learn.LockSupportDemo.main(LockSupportDemo.java:7)
```
通过分别调用这两个方法然后 dump 线程信息可以看出, 带 Object 的 park 方法相较于无参的 park 方法会增加
```java
-- parking to wait for <0x048c2d18> (a java.lang.String)Copy to clipboardErrorCopied
+- parking to wait for <0x048c2d18> (a java.lang.String)
```
这种信息就类似于记录"案发现场",有助于工程人员能够迅速发现问题解决问题。
注意:
-- synchronized 使线程阻塞,线程会进入到 BLOCKED 状态
-- 调用 LockSupprt 方法阻塞线程会使线程进入到 WAITING 状态
+- synchronized 使线程阻塞,线程会进入到 Blocked 状态(阻塞状态)
+- 调用 LockSupport 方法阻塞线程会使线程进入到 Waiting 状态(等待状态)
-### LockSupport 使用示例
+### 使用示例
```java
import java.util.concurrent.locks.LockSupport;
diff --git "a/docs/Java_Concurrency/5_345円216円237円345円255円220円346円223円215円344円275円234円347円261円273円.md" "b/docs/Java_Concurrency/5_345円216円237円345円255円220円346円223円215円344円275円234円347円261円273円.md"
index 879713c3..a6a706d3 100644
--- "a/docs/Java_Concurrency/5_345円216円237円345円255円220円346円223円215円344円275円234円347円261円273円.md"
+++ "b/docs/Java_Concurrency/5_345円216円237円345円255円220円346円223円215円344円275円234円347円261円273円.md"
@@ -1,5 +1,7 @@
# 原子操作类
+## AtomicInteger 源码解析
+
`java.util.concurrent.atomic`下的所有原子操作类都实现了 CAS。
AtomicInteger 内部维护一个变量 **Unsafe**:
@@ -49,7 +51,31 @@ public final int getAndAddInt(Object var1, long var2, int var4) {
}
```
-## 1. 原子更新基本类
+除了 CAS 操作外,AtomicInteger 还使用 volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
+
+```java
+
+private static final Unsafe unsafe = Unsafe.getUnsafe();
+private static final long valueOffset;
+
+static {
+ try {
+ valueOffset = unsafe.objectFieldOffset
+ (AtomicInteger.class.getDeclaredField("value"));
+ } catch (Exception ex) { throw new Error(ex); }
+}
+
+private volatile int value;
+```
+
+UnSafe 类的 objectFieldOffset() 方法是一个 native 方法,这个方法是用来拿到 "原来的值" 的内存地址,返回值是 valueOffset。
+
+另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
+
+
+
+## 原子更新基本类
+
atomic 包提高原子更新基本类的工具类,如下:
- AtomicBoolean
@@ -133,7 +159,7 @@ public final boolean compareAndSet(boolean expect, boolean update) {
```
可以看出,compareAndSet 方法的实际上也是先转换成 0,1 的整型变量,然后是通过针对 int 型变量的原子更新方法 compareAndSwapInt 来实现的。
-## 2. 原子更新数组
+## 原子更新数组
atomic 包下提供能原子更新数组中元素的类有:
- AtomicIntegerArray
@@ -170,7 +196,10 @@ public class AtomicIntegerArrayDemo {
2 //注意仍然返回原来的旧值
```
-## 3. 原子更新引用类型
+
+
+## 原子更新引用类型
+
如果需要原子更新引用类型变量的话,为了保证线程安全,atomic 也提供了相关的类:
- AtomicReference
@@ -219,7 +248,10 @@ User{userName='b', age=2}
```
首先将对象 user1 用 AtomicReference 进行封装,然后调用 getAndSet 方法,从结果可以看出,该方法会原子更新引用的 User 对象,变为`User{userName='b', age=2}`,返回的是原来的User 对象`User{userName='a', age=1}`。
-## 4. 原子更新字段类型
+
+
+## 原子更新字段类型
+
如果需要更新对象的某个字段,并在多线程的情况下,能够保证线程安全,atomic同样也提供了相应的原子操作类:
- AtomicIntegeFieldUpdater
diff --git "a/docs/Java_Concurrency/6_345円271円266円345円217円221円345円256円271円345円231円250円.md" "b/docs/Java_Concurrency/6_345円271円266円345円217円221円345円256円271円345円231円250円.md"
index ceab71e6..910c97a6 100644
--- "a/docs/Java_Concurrency/6_345円271円266円345円217円221円345円256円271円345円231円250円.md"
+++ "b/docs/Java_Concurrency/6_345円271円266円345円217円221円345円256円271円345円231円250円.md"
@@ -113,7 +113,7 @@ final Segment[] segments;
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
```
-
+
### 2. size 操作
@@ -244,34 +244,3 @@ static final