[
  {
    "path": ".gitattributes",
    "content": "*.js linguist-language=java\n*.css linguist-language=java\n*.html linguist-language=java\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\ncustom: https://raw.githubusercontent.com/frank-lam/fullstack-tutorial/master/assets/wechat/wx-green.png\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: required\nscript: bash ./assets/deploy/deploy.sh\nbranches:\n  only:\n  - master\nnotifications:\n  email: true"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\"><img src=\"assets/logo-2021.svg\" width=\"80%\"/></div><br/>\n\n|              I              |           II           |           III           |           IV           |            V            |            VI            |        VII        |         VIII         | IX |            X            |            XI            |            XII            |\n| :--------------------------: | :-------------------: | :----------------------: | :---------------------: | :--------------: | :---------------: | :----------------------: | :----------------------: | :----------------------: | :----------------------: | :----------------------: | :----------------------: |\n| 算法<br />[📝](#一数据结构与算法) | Java<br/>[☕️](#二java) | Python<br />[🐍](#三python) | 前端<br />[🔗](#四前端) | 数据库<br/>[💾](#五数据库) | 操作系统<br/>[💻](#六操作系统) | 网络通信<br/>[☁️](#七网络通信) | 分布式<br/>[📃](#八分布式) | 机器学习<br/> [🔍](#九机器学习) |工具<br/>[🔨](#十工具) |Learn<br />[📖](#learn-) |Talking<br />[💡](#talking-bulb) |\n\n<div align=\"center\">  \n    <p>\n        ✨✨✨\n    </p>\n    <p>\n \t\t和 500+ 技术达人在线交流：\n      <a href=\"notes/技术交流群.md\">🤟 快来吧，和大家一起技术互动交流</a>\n    </p>\n  <p>\n    『技术博客』：<a href=\"https://www.frankfeekr.cn\">www.frankfeekr.cn</a>   |  『开源贡献』：<a href=\"notes/开源贡献.md\">⊱ 英雄招募令</a>   |   『微信订阅号』：全栈开发社区\n  </p>\n</div>\n<div align=\"center\"><img src=\"assets/zhishixingqiu.JPG\" width=\"40%\"/></div><br/>\n\n\n\n🔥🔥🔥 \n\n欢迎光临 LinTools 开发者的在线导航： https://tools.frankfeekr.cn\n\n如果你有更好的在线工具，[请点击留言](https://github.com/frank-lam/fullstack-tutorial/issues/65)，持续更新！\n\n\n\n## 前言\n\n- [谈谈技术学习的一些方法论](https://www.frankfeekr.cn/2019/05/09/谈谈技术学习的一些方法论/)\n\n  在学习技术这条路上并不是一帆风顺，也一直在探索一条适合自己的学习方法。从一开始的技术小白，到现在还比较上道的老鸟，在这个过程中走了太多的弯路，想在这里和大家分享一些我的经历和学习方法。\n\n- [如何选择自己的技术栈](https://www.frankfeekr.cn/2019/05/27/如何选择自己的技术栈/)\n\n  在编程的世界里，该如何选择自己的技术栈呢。学前端？学 APP 开发？对于 Java、C++、C#、Python、PHP 又如何选择呢？人工智能现如今这么火，是不是机器学习、深度学习更高级一些呢？那么程序员又如何修炼内功呢？\n\n- [全栈开发神兵利器](notes/全栈开发神兵利器.md)\n\n  工欲善其事，必先利其器。这里我将推荐开发过程中的提效工具、开发利器、协作工具、文档技术等等。\n\n- [XP 极限编程](notes/XP极限编程.md)\n\n  敏捷软件开发中可能是最富有成效的几种方法学之一\n\n\n\n## 技能图谱\n\n- [backend skill](notes/SkillTree/backend-skill.md)\n\n  后台开发技能图谱，从程序员的内功修炼到后台语言，分布式系统架构\n\n\n\n## 一、数据结构与算法\n\n- [数据结构与算法](notes/数据结构与算法.md)\n\n　　排序算法、动态规划、递归、回溯法、贪心算法等\n\n- [海量数据处理](notes/海量数据处理.md)\n\n  数据处理典型案例，逐渐更新\n\n\n\n## 二、Java\n\n- [Java 基础概念](notes/JavaArchitecture/01-Java基础.md)\n\n　　基本概念、面向对象、关键字、基本数据类型与运算、字符串与数组、异常处理、Object 通用方法\n\n- [Java 集合框架](notes/JavaArchitecture/02-Java集合框架.md)\n\n　　数据结构 & 源码分析：ArrayList、Vector、LinkedList、HashMap、ConcurrentHashMap、HashSet、LinkedHashSet and LinkedHashMap\n\n- [Java 并发编程](notes/JavaArchitecture/03-Java并发编程.md)\n\n　　线程状态、线程机制、线程通信、J.U.C 组件、JMM、线程安全、锁优化\n\n- [Java I/O](notes/JavaArchitecture/04-Java-IO.md)\n\n　　磁盘操作、字节操作、字符操作、对象操作、网络操作、NIO\n\n- [Java 虚拟机](notes/JavaArchitecture/05-Java虚拟机.md)\n\n　　运行时数据区域、垃圾收集、内存分配机制、类加载机制、性能调优监控工具\n\n- [Java 设计模式](notes/JavaArchitecture/06-Java设计模式.md)\n\n　　Java 常见的 10 余种设计模式，全 23 种设计模式逐步更新\n\n- [Java Web](notes/JavaArchitecture/07-JavaWeb.md)\n\n　　包含 Servlet & JSP、Spring、SpringMVC、Mybatis、Hibernate、Structs2 核心思想，如 IOC、AOP 等思想。SSM 更详细请转向：[Spring](notes/JavaWeb/Spring.md) | [SpringMVC](https://github.com/frank-lam/SpringMVC_MyBatis_Learning) | [MyBatis](https://github.com/frank-lam/SpringMVC_MyBatis_Learning)\n\n\n\n## 三、Python\n\n- [Python 语言基础](notes/Python/Python简介及基础语法.md)\n\n\n\n## 四、前端\n\n- [前端知识体系](notes/Frontend/前端知识体系.md)\n- [Angular 基础知识](notes/Frontend/Angular.md)\n- [ES6+ 语法全解析](https://notes.frankfeekr.cn/docs/frontend/es6/%E9%A1%B9%E7%9B%AE%E5%87%86%E5%A4%87/%E5%89%8D%E8%A8%80)\n\n\n\n\n<details>\n<summary>TODO LIST</summary>\n\n- HTML5\n\n- CSS3\n\n- CSS 预处理\n\n  - sass(scss)\n  - less\n  - stylus\n\n- CSS 框架\n\n  - BootStarp\n  - LayUI\n\n- JavaScript \n\n  基础语法、进阶、ES6\n\n- JavaScript 框架\n\n  - Vue\n  - React\n  - Angular\n  - jQuery\n\n- Node\n\n  常用 api、对象池、异常处理、进程通信、高并发\n\n- 静态类型检查\n\n  - TypeScript\n  - Flow\n\n- 构建/打包工具\n\n  - webpack\n  - gulp\n  - rollup\n\n- 包管理工具\n\n  - npm\n  - yarn\n  \n- 服务端渲染\n\n  - koa2\n  - express\n  - nuxt\n  - next\n\n</details>\n\n\n\n## 五、数据库\n\n- [MySQL](notes/MySQL.md)\n\n  存储引擎、事务隔离级别、索引、主从复制\n\n- [Redis](notes/Redis.md)\n\n  Redis 核心知识\n\n- [SQL](notes/SQL.md)\n\n  常用 SQL 语句\n\n- [PostgreSQL](notes/PostgreSQL.md)\n\n  一个开源的关系数据库，是从伯克利写的 POSTGRES 软件包发展而来的\n\n- [InfluxDB](https://www.frankfeekr.cn/2019/07/24/influxdb-tutorial-start/)\n\n  玩转时序数据库\n\n\n\n## 六、操作系统\n\n- [操作系统原理](notes/操作系统.md)\n\n　　进程管理、死锁、内存管理、磁盘设备\n\n- [Linux](notes/Linux.md)\n\n　　基础核心概念、常用命令使用\n\n\n\n## 七、网络通信\n\n- [计算机网络](notes/计算机网络.md)\n\n　　传输层、应用层（HTTP）、网络层、网络安全\n\n- [RESTful API](notes/RESTful%20API.md)\n\n  软件架构风格、格设计原则和约束条件\n\n- [Web网络安全](notes/网络安全.md)\n\n  web前后端漏洞分析与防御，XSS 攻击、CSRF 攻击、DDoS 攻击、SQL 注入\n\n\n\n\n## 八、分布式\n\n- [Docker](notes/Docker基础.md)\n\n  容器化引擎服务\n\n- [微服务](notes/微服务.md)\n\n  微服务简介、API 网关、服务注册发现、服务通信\n\n- [Zookeeper](notes/分布式/Zookeeper.md)\n\n  分布式协调服务，服务注册发现\n\n- [Kafka](notes/MicroService/kafka/README.md)\n\n  深入浅出 Kafka，将用最极简的语言带你走进 Kafka 的消息中间件世界\n\n\n\n【说明】**分布式专题** 笔者也在学习中，这里列举了一些技能列表，笔者将局部更新。敬请期待\n\n\n\n\n<details>\n<summary>TODO LIST</summary>\n\n- Kubernetes（k8s）\n\n  容器化部署，管理云平台中多个主机上的容器化的应用\n\n- 云计算\n\n  SaaS（软件即服务） 、PaaS（平台即服务） 、IaaS（基础架构即服务）\n\n- Zookeeper\n\n  分布式协调服务，服务注册发现\n\n- Dubbo、Thrift（RPC 框架）\n\n  分布式服务治理\n\n- 分布式事务解决方案\n\n- ActiveMQ、Kafka、RabbitMQ\n\n  分布式消息通信\n\n- 熔断，限流，降级机制\n\n- Redis\n\n  分布式缓存\n\n- Mycat\n\n  数据库路由\n\n- Nginx\n\n  反向代理\n\n- Tomcat\n\n  Web Server 服务\n\n- DevOps\n\n  自动化运维，持续集成、持续交付、持续部署\n\n\n- 分布式锁\n\n  基于 Redis、MySQL、Zookeeper 的分布式锁实现\n\n- FastDFS\n\n  轻量级分布式文件管理系统\n  \n- Go\n\n  并发的、带垃圾回收的、快速编译的语言\n\n</details>\n\n\n\n## 九、机器学习\n\n- [深度学习初识](notes/DeepLearning/深度学习初识.md)\n\n- 经典机器学习算法\n\n  K 近邻算法、线性回归、梯度下降法、逻辑回归、支持向量机、决策树、集成学习\n\n\n\n\n## 十、工具\n\n- [Git](notes/git-tutorial.md)\n\n  学习指引，将用最极简的语言带你进入 Git 版本控制的世界\n\n- [Git 工作流](notes/Git工作流.md)\n\n  集中式工作流，功能分支工作流， GitFlow 工作流，Forking 工作流，Pull Requests\n\n- [正则表达式](notes/正则表达式.md)\n\n  常见符号含义，速查表\n\n- [手把手教你搭建内网穿透服务](https://github.com/frank-lam/lanproxy-nat)\n\n  基于 lanproxy 穿透服务，为你定了一键启动的服务端和客户端 Docker 镜像\n\n- [基于 SpringBoot & IDEA & JRebel 玩转远程热部署与远程调试](https://www.frankfeekr.cn/2019/07/17/springboot-idea-jrebel-hotswap/)\n\n  手把手带你玩转，远程调试与远程热部署\n\n- [什么是 TDD 及常见的测试方法](notes/软件测试.md)\n\n\n\n## Learn 📖\n\n- [LEARN_LIST](notes/LEARNLIST.md)\n\n　　包含阅读清单，学习课程两部分\n\n- [web应用开发标准流程](notes/web应用开发标准流程.md)\n\n\n\n## Talking :bulb:\n\n本仓库致力于成为一个全栈开发爱好者的学习指南，给初学者一个更明确的学习方向，同时也是对自己技能的强化和巩固。在架构师这条路上，希望和大家一起成长，帮助更多的计算机爱好者能够有一个明确的学习路径。持续不间断的维护本仓库，也欢迎有更多的极客们加入。\n\n都说好记性不如烂笔头，定期的学习和整理必然对学习巩固有所帮助，这里通过索引的方式对全栈开发技术做一个系统分类，方便随时巩固和学习，当然还有面试。在学习这条路上难免会有很多盲点和学不完的知识。有道无术，术尚可求，掌握好思维能力才能应对千变万化的技术。不要把大脑当成硬盘，也不要做高速运转的 CPU，而修行自己的大脑成为一个搜索引擎，学会分析解决问题。\n\nSince 20,May,2018\n\n\n\n## Reference\n\n个人的能力有限，在编写的过程中引用了诸多优秀的 GitHub 仓库。本项目的启发来自 [@CyC2018](https://github.com/CyC2018) 的学习笔记，是一个非常优秀的开源项目，在本仓库中部分内容引用文字和图例；引用了 [@计算所的小鼠标](https://github.com/CarpenterLee) 中对于 JCF 的源码分析和理解；引用了  [阿里面试题总结](https://www.nowcoder.com/discuss/5949) 中全部的面试题，并对面经进行了整理勘误，并进行了知识拓展和修改；引用了 [牛客网](https://www.nowcoder.com) 上的面试经验贴。也引用了知乎上的热门回答和优秀博客的回答。在这里特别鸣谢，我将每篇文章中做外链引用说明。\n\n文中我也推荐了学习的书籍和学习课程，都将附着上最高清、最形象的配图进行讲解。在文中的配图都来自自己绘制的、博客、Github、PDF书籍等等，这里没法一一感谢，谢谢你们。\n\n推荐一些优秀的开源项目，供大家参考，[reference](notes/reference.md)。\n\n\n\n## Contributors\n\nThank you to all the people who already contributed to fullstack-tutorial !\n\nPlease make sure to read the [Contributing Guide/如何给我的仓库贡献](notes/docs/如何给我的仓库贡献.md) before making a pull request. \n\n<a href=\"https://github.com/frank-lam/fullstack-tutorial/graphs/contributors\"><img src=\"https://opencollective.com/fullstack-tutorial/contributors.svg?width=890&button=false\" /></a>\n\n\n\n## Stargazers over time\n\n![Stargazers over time](https://starcharts.herokuapp.com/frank-lam/fullstack-tutorial.svg)\n\n\n\n## License\n\n<a rel=\"license\" href=\"http://creativecommons.org/licenses/by-nc-sa/4.0/\"><img alt=\"知识共享许可协议\" style=\"border-width:0\" src=\"https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png\" /></a>\n\nCopyright (c) 2021-present, Frank Lam\n\n\n\n## 关于作者 :boy:\n\n<div align=\"center\">  \n    <p>\n      『作者简介』：<a href=\"https://www.frankfeekr.cn/author\">https://www.frankfeekr.cn/author</a>\n    </p>\n</div>\n\n\n<div align=\"center\">  \n    <p>\n        在颠覆世界的同时，也要好好关照自己。\n    </p>\n<a target=\"_blank\" href=\"https://frankfeekr.cn\" rel=\"nofollow\"><img src=\"https://img.shields.io/badge/BLOG-frankfeekr.cn-blue.svg\" alt=\"BLOG\" data-canonical-src=\"\" style=\"max-width:100%;\"></a>\n<a target=\"_blank\" href=\"mailto:frank_lin@whu.edu.cn\" rel=\"nofollow\"><img src=\"https://img.shields.io/badge/Email-frank__lin@whu.edu.cn-lightgrey.svg\" alt=\"邮箱\" data-canonical-src=\"\" style=\"max-width:100%;\"></a>\n<a target=\"_blank\" href=\"https://jq.qq.com/?_wv=1027&k=593WvX0\" rel=\"nofollow\" ><img src=\"https://img.shields.io/badge/QQ群-862619503-green.svg\" alt=\"QQ群\" data-canonical-src=\"\" style=\"max-width:100%;\"></a>\n    <br/><br/>\n    <p>\n        from zero to hero.\n    </p>\n</div>\n<div align=\"center\"> <img src=\"assets/wechat/wx-green.png\" width=\"70%\"/></div>\n\n"
  },
  {
    "path": "assets/deploy/deploy.sh",
    "content": "#!/usr/bin/env sh\n\n# 确保脚本抛出遇到的错误\nset -e\n\n\nrm gh-pages -rf; mkdir gh-pages; cp -r notes/* gh-pages/;\ncd gh-pages;\n\n\n# 将子目录的所有图片全部复制到文档根目录中，解决docsify图片索引不到的问题\nrm assets/ -rf\nmkdir assets\n\nrm pics/ -rf\nmkdir pics\n\n\nfor f in $(find ../notes/ -type f -print | grep assets)\n  do\n    cp $f ./assets\n  done\n\nfor f in $(find ../notes/ -type f -print | grep pics)\n  do\n    cp $f ./pics\n  done\n\n# 进入生成的文件夹\n\n#创建.nojekyll 防止Github Pages build错误\ntouch .nojekyll\n\ngit init\ngit add -A\ngit commit -m 'deploy'\n\ngit push -f \"https://${GH_TOKEN}@github.com/frank-lam/fullstack-tutorial.git\" master:gh-pages\n\ncd -"
  },
  {
    "path": "course/01 玩转数据结构.md",
    "content": "## 刘宇波《玩转数据结构》学习记录\n\n| 章节                                        | 记录 |\n| ------------------------------------------- | ---- |\n| **第1章 欢迎学习《玩转数据结构》**          |      |\n| 1-1 欢迎学习《玩转数据结构》                |      |\n| 1-2 学习数据结构（和算法）到底有没有用？    |      |\n| 1-3 关于课程学习的更多注意事项              |      |\n| 1-4  课程编程环境搭建                       |      |\n|                                             |      |\n| **第2章 不要小瞧数组**                      |      |\n| 2-1 使用Java中的数组                        |      |\n| 2-2 二次封装属于我们自己的数组              |      |\n| 2-3 向数组中添加元素                        |      |\n| 2-4 数组中查询元素和修改元素                |      |\n| 2-5 包含，搜索和删除                        |      |\n| 2-6 使用泛型                                |      |\n| 2-7 动态数组                                |      |\n| 2-8 简单的复杂度分析                        |      |\n| 2-9 均摊复杂度和防止复杂度的震荡            |      |\n|                                             |      |\n| **第3章 栈和队列**                          |      |\n| 3-1 栈和栈的应用：撤销操作和系统栈          | 5/21 |\n| 3-2 栈的基本实现                            | 5/21 |\n| 3-3 栈的另一个应用：括号匹配                | 5/21 |\n| 3-4 关于Leetcode的更多说明                  | 5/21 |\n| 3-5 数组队列                                | 5/21 |\n| 3-6 循环队列                                | 5/21 |\n| 3-7 循环队列的实现                          | 5/21 |\n| 3-8 数组队列和循环队列的比较                | 5/21 |\n|                                             |      |\n| **第4章  最基础的动态数据结构：链表**       |      |\n| 4-1 什么是链表                              |      |\n| 4-2 在链表中添加元素                        |      |\n| 4-3 使用链表的虚拟头结点                    |      |\n| 4-4 链表的遍历，查询和修改                  |      |\n| 4-5 从链表中删除元素                        |      |\n| 4-6 使用链表实现栈                          |      |\n| 4-7 带有尾指针的链表：使用链表实现队列      |      |\n|                                             |      |\n| **第5章  链表和递归**                       |      |\n| 5-1 Leetcode中和链表相关的问题              |      |\n| 5-2 测试自己的Leetcode链表代码              |      |\n| 5-3 递归基础与递归的宏观语意                |      |\n| 5-4 链表的天然递归结构性质                  |      |\n| 5-5 递归运行的机制：递归的微观解读          |      |\n| 5-6 递归算法的调试                          |      |\n| 5-7 更多和链表相关的问题                    |      |\n|                                             |      |\n| **第6章 二分搜索树**                        |      |\n| 6-1 为什么要研究树结构                      | 6/29 |\n| 6-2 二分搜索树基础                          | 6/29 |\n| 6-3 向二分搜索树中添加元素                  | 6/29 |\n| 6-4 改进添加操作：深入理解递归终止条件      | 6/30 |\n| 6-5 二分搜索树的查询操作                    | 6/30 |\n| 6-6 二分搜索树的前序遍历                    | 6/30 |\n| 6-7 二分搜索树的中序遍历和后序遍历          | 6/30 |\n| 6-8 深入理解二分搜索树的前中后序遍历        | 6/30 |\n| 6-9 二分搜索树前序遍历的非递归实现——**通过栈实现** | 6/30 |\n| 6-10 二分搜索树的层序遍历——**通过队列实现**      | 6/30 |\n| 6-11 删除二分搜索树的最大元素和最小元素     | 6/30 |\n| 6-12 删除二分搜索树的任意元素               | 6/30 |\n| 6-13 更多二分搜索树相关话题                 | 6/30 |\n|                                             |      |\n| **第7章 集合和映射**                        |      |\n| 7-1 集合基础和基于二分搜索树的集合实现      |      |\n| 7-2 基于链表的集合实现                      |      |\n| 7-3 集合类的复杂度分析                      |      |\n| 7-4 _Leetcode中的集合问题和更多集合相关问题 |      |\n| 7-5 映射基础                                |      |\n| 7-6 基于链表的映射实现                      |      |\n| 7-7 基于二分搜索树的映射实现                |      |\n| 7-8 映射的复杂度分析和更多映射相关问题      |      |\n| 7-9 Leetcode上更多集合和映射的问题          |      |\n|                                             |      |\n| **第8章 优先队列和堆**                      |      |\n| 8-1 什么是优先队列                          |      |\n| 8-2 堆的基础表示                            |      |\n| 8-3 向堆中添加元素和Sift Up                 |      |\n| 8-4 从堆中取出元素和Sift Down               |      |\n| 8-5 Heapify 和 Replace                      |      |\n| 8-6 基于堆的优先队列                        |      |\n| 8-7 Leetcode上优先队列相关问题              |      |\n| 8-8 Java中的PriorityQueue                   |      |\n| 8-9 和堆相关的更多话题和广义队列            |      |\n|                    ||\n| **第9章 线段树** ||\n| 9-1 什么是线段树               ||\n| 9-2 线段树基础表示             ||\n| 9-3 创建线段树                 ||\n| 9-4 线段树中的区间查询         ||\n| 9-5 Leetcode上线段树相关的问题 ||\n| 9-6 线段树中的更新操作         ||\n| 9-7 更多线段树相关的话题       ||\n|                      ||\n| **第10章 Trie** ||\n| 10-1 什么是Trie字典树           ||\n| 10-2 Trie字典树基础             ||\n| 10-3 Trie字典树的查询           ||\n| 10-4 Trie字典树的前缀查询       ||\n| 10-5 Trie字典树和简单的模式匹配 ||\n| 10-6 Trie字典树和字符串映射     ||\n| 10-7 更多和Trie字典树相关的话题 ||\n|  ||\n| **第11章 并查集**               ||\n| 11-1 什么是并查集               ||\n| 11-2 Quick Find                 ||\n| 11-3 Quick Union                ||\n| 11-4 基于size的优化             ||\n| 11-5 基于rank的优化             ||\n| 11-6 路径压缩                   ||\n| 11-7 更多和并查集相关的话题     ||\n|  ||\n| **第12章 AVL**                  ||\n| 12-1 平衡树和AVL                ||\n| 12-2 计算节点的高度和平衡因子   ||\n| 12-3 检查二分搜索树性质和平衡性 ||\n| 12-4 旋转操作的基本原理         ||\n| 12-5 左旋转和右旋转的实现       ||\n| 12-6 LR 和 RL                   ||\n| 12-7 从AVL树中删除元素          ||\n| 12-8 基于AVL树的集合和映射      ||\n|                                                    |      |\n| **第13章 红黑树**                                  |      |\n| 13-1 红黑树与2-3树                                 |      |\n| 13-2 2-3树的绝对平衡性                             |      |\n| 13-3 红黑树与2-3树的等价性                         |      |\n| 13-4 红黑树的基本性质和复杂度分析                  |      |\n| 13-5 保持根节点为黑色和左旋转                      |      |\n| 13-6 颜色翻转和右旋转                              |      |\n| 13-7 红黑树中添加新元素                            |      |\n| 13-8 红黑树的性能测试                              |      |\n| 13-9 更多和红黑树相关的话题                        |      |\n| 13-10 对于红黑树，任何不平衡都会在三次旋转内解决？ |      |\n|                                                    |      |\n| **第14章 哈希表**                                  |      |\n| 14-1 哈希表基础                                    |      |\n| 14-2 哈希函数的设计                                |      |\n| 14-3 Java中的 hashCode 方法                        |      |\n| 14-4 链地址法 Seperate Chaining                    |      |\n| 14-5 实现属于我们自己的哈希表                      |      |\n| 14-6 哈希表的动态空间处理与复杂度分析。            |      |\n| 14-7 哈希表更复杂的动态空间处理方法                |      |\n| 14-8 更多哈希冲突的处理方法                        |      |\n|                                                    |      |\n| **第15章 结尾语**                                  |      |\n| 15-1 更广阔的数据结构的世界，大家加油！            |      |\n\n**2018年7月10日完结**"
  },
  {
    "path": "course/02 玩转算法面试.md",
    "content": "## 刘宇波《玩转算法面试》学习记录\n\n| 章节                                                         | 记录 |\n| ------------------------------------------------------------ | ---- |\n| **第1章 算法面试到底是什么鬼?**                              |      |\n| 1-1 算法面试不仅仅是正确的回答问题                           |      |\n| 1-2 算法面试只是面试的一部分                                 |      |\n| 1-3 如何准备算法面试                                         |      |\n| 1-4 如何回答算法面试问题                                     |      |\n|                                                              |      |\n| **第2章 面试中的复杂度分析**                                 |      |\n| 2-1 究竟什么是大O（Big O）                                   |      |\n| 2-2 对数据规模有一个概念                                     |      |\n| 2-3 简单的复杂度分析                                         |      |\n| 2-4 亲自试验自己算法的时间复杂度                             |      |\n| 2-5 递归算法的复杂度分析                                     |      |\n| 2-6 均摊时间复杂度分析（Amortized Time Analysis）            |      |\n| 2-7 避免复杂度的震荡                                         |      |\n|                                                              |      |\n| **第3章 数组中的问题其实最常见**                             |      |\n| 3-1 从二分查找法看如何写出正确的程序                         |      |\n| 3-2 改变变量定义，依然可以写出正确的算法                     |      |\n| 3-3 在LeetCode上解决第一个问题 Move Zeros                    |      |\n| 3-4 即使简单的问题，也有很多优化的思路                       |      |\n| 3-5 三路快排partition思路的应用 Sort Color                   |      |\n| 3-6 对撞指针 Two Sum II - Input Array is Sorted              |      |\n| 3-7 滑动窗口 Minimum Size Subarray Sum                       |      |\n| 3-8 在滑动窗口中做记录 Longest Substring Without Repeating Characters |      |\n|                                                              |      |\n| **第4章 查找表相关问题**                                     |      |\n| 4-1 set的使用 Intersection of Two Arrays                     |      |\n| 4-2 map的使用 Intersection of Two Arrays II                  |      |\n| 4-3 set和map不同底层实现的区别                               |      |\n| 4-4 使用查找表的经典问题 Two Sum                             |      |\n| 4-5 灵活选择键值 4Sum II                                     |      |\n| 4-6 灵活选择键值 Number of Boomerangs                        |      |\n| 4-7 查找表和滑动窗口 Contain Duplicate II                    |      |\n| 4-8 二分搜索树底层实现的顺序性 Contain Duplicate III         |      |\n|                                                              |      |\n| **第5章 在链表中穿针引线**                                   |      |\n| 5-1 链表，在节点间穿针引线 Reverse Linked List               |      |\n| 5-2 测试你的链表程序                                         |      |\n| 5-3 设立链表的虚拟头结点 Remove Linked List Elements         |      |\n| 5-4 复杂的穿针引线 Swap Nodes in Pairs                       |      |\n| 5-5 不仅仅是穿针引线 Delete Node in a Linked List            |      |\n| 5-6 链表与双指针 Remove Nth Node Form End of List            |      |\n|                                                              |      |\n| **第6章 栈，队列，优先队列**                                 |      |\n| 6-1 栈的基础应用 Valid Parentheses                           |      |\n| 6-2 栈和递归的紧密关系 Binary Tree Preorder, Inorder and Postorder Traversal |      |\n| 6-3 运用栈模拟递归                                           |      |\n| 6-4 队列的典型应用 Binary Tree Level Order Traversal         |      |\n| 6-5 BFS和图的最短路径 Perfect Squares                        |      |\n| 6-6 优先队列                                                 |      |\n| 6-7 优先队列相关的算法问题 Top K Frequent Elements           |      |\n|                                                              |      |\n| **第7章 二叉树和递归**                                       |      |\n| 7-1 二叉树天然的递归结构                                     |      |\n| 7-2 一个简单的二叉树问题引发的血案 Invert Binary Tree        |      |\n| 7-3 注意递归的终止条件 Path Sum                              |      |\n| 7-4 定义递归问题 Binary Tree Path                            |      |\n| 7-5 稍复杂的递归逻辑 Path Sum III                            |      |\n| 7-6 二分搜索树中的问题 Lowest Common Ancestor of a Binary Search Tree |      |\n|                                                              |      |\n| **第8章 递归和回溯法**                                       |      |\n| 8-1 树形问题 Letter Combinations of a Phone Number           | 7/19 |\n| 8-2 什么是回溯                                               |      |\n| 8-3 排列问题 Permutations                                    |      |\n| 8-4 组合问题 Combinations                                    |      |\n| 8-5 回溯法解决组合问题的优化                                 |      |\n| 8-6 二维平面上的回溯法 Word Search                           |      |\n| 8-7 floodfill算法，一类经典问题 Number of Islands-           |      |\n| 8-8 回溯法是经典人工智能的基础 N Queens                      |      |\n|                                                              |      |\n| **第9章 动态规划基础**                                       |      |\n| 9-1 什么是动态规划                                           |      |\n| 9-2 第一个动态规划问题 Climbing Stairs                       |      |\n| 9-3 发现重叠子问题 Integer Break                             |      |\n| 9-4 状态的定义和状态转移 House Robber                        |      |\n| 9-5 0-1背包问题                                              |      |\n| 9-6 0-1背包问题的优化和变种                                  |      |\n| 9-7 面试中的0-1背包问题 Partition Equal Subset Sum           |      |\n| 9-8 LIS问题 Longest Increasing Subsequence                   |      |\n| 9-9 LCS，最短路，求动态规划的具体解以及更多                  |      |\n|                                                              |      |\n| **第10章 贪心算法**                                          |      |\n| 10-1 贪心基础 Assign Cookies                                 |      |\n| 10-2 贪心算法与动态规划的关系 Non-overlapping Intervals      |      |\n| 10-3 贪心选择性质的证明                                      |      |\n|                                                              |      |\n| **第11章 课程结语**                                          |      |\n| 11-1 结语                                                    |      |"
  },
  {
    "path": "course/03 算法与数据结构.md",
    "content": "## 刘宇波《算法与数据结构》学习记录\n\n| 章节                                                         | 记录 |\n| ------------------------------------------------------------ | ---- |\n| **第1章 当我们在讨论算法的时候，我们在讨论什么？** ||\n| 1-1 我们究竟为什么要学习算法                     ||\n| 1-2 课程介绍                                     ||\n|  ||\n| **第2章 排序基础**                           ||\n| 2-1 选择排序法                                   ||\n| 2-2 使用模板（泛型）编写算法                     ||\n| 2-3 随机生成算法测试用例                         ||\n| 2-4 测试算法的性能                               ||\n| 2-5 插入排序法                                   ||\n| 2-6 插入排序法的改进                             ||\n| 2-7 更多关于O（n*2）排序算法的思考               ||\n|  ||\n| **第3章 高级排序问题**                       ||\n| 3-1 归并排序法                                   ||\n| 3-2 归并排序法的实现                             ||\n| 3-3 归并排序法的优化                             ||\n| 3-4 自底向上的归并排序算法                       ||\n| 3-5 快速排序法                                   ||\n| 3-6 随机化快速排序法                             ||\n| 3-7 双路快速排序法                               ||\n| 3-8 三路快速排序法                               ||\n| 3-9 归并排序和快速排序的衍生问题                 ||\n|  ||\n| **第4章 堆和堆排序**                           ||\n| 4-1 为什么使用堆                                 ||\n| 4-2 堆的基本存储                                 ||\n| 4-3 Shift Up                                     ||\n| 4-4 Shift Down                                   ||\n| 4-5 基础堆排序和Heapify                          ||\n| 4-6 优化的堆排序                                 ||\n| 4-7 排序算法总结                                 ||\n| 4-8 索引堆                                       ||\n| 4-9 索引堆的优化                                 ||\n| 4-10 和堆相关的其他问题                          ||\n|  ||\n| **第5章 二分搜索树**                         ||\n| 5-1 二分查找法                                   ||\n| 5-2 二分搜索树基础                               ||\n| 5-3 二分搜索树的节点插入                         ||\n| 5-4 二分搜索书的查找                             ||\n| 5-5 二分搜索树的遍历（深度优先遍历）             ||\n| 5-6 层序遍历（广度优先遍历）                     ||\n| 5-7 删除最大值，最小值                           ||\n| 5-8 二分搜索树的删除                             ||\n| 5-9 二分搜索树的顺序性                           ||\n| 5-10 二分搜索树的局限性                          ||\n| 5-11 树形问题和更多树。                          ||\n|  ||\n| **第6章 并查集**                              ||\n| 6-1 并查集基础                                   ||\n| 6-2 Qucik Find                                   ||\n| 6-3 Quick Union                                  ||\n| 6-4 基于size的优化                               ||\n| 6-5 基于rank的优化                               ||\n| 6-6 路径压缩                                     ||\n|  ||\n| **第7章 图的基础**                            ||\n| 7-1 图论基础                                     ||\n| 7-2 图的表示                                     ||\n| 7-3 相邻点迭代器                                 ||\n| 7-4 图的算法框架                                 ||\n| 7-5 深度优先遍历和联通分量                       ||\n| 7-6 寻路                                         ||\n| 7-7 广度优先遍历和最短路径                       ||\n| 7-8 迷宫生成，ps抠图--更多无权图的应用           ||\n|  ||\n| **第8章 最小生成树**                         ||\n| 8-1 有权图                                       ||\n| 8-2 最小生成树问题和切分定理                     ||\n| 8-3 Prim算法的第一个实现                         ||\n| 8-4 Prim算法的优化                               ||\n| 8-5 优化后的Prim算法的实现                       ||\n| 8-6 Krusk算法                                    ||\n| 8-7 最小生成树算法的思考                         ||\n|  ||\n| **第9章 最短路径**                           ||\n| 9-1 最短路径问题和松弛操作                       ||\n| 9-2 Dijkstra算法的思想                           ||\n| 9-3 实现Dijkstra算法                             ||\n| 9-4 负权边和Bellman-Ford算法                     ||\n| 9-5 实现Bellman-Ford算法                         ||\n| 9-6 更多和最短路径相关的思考                     ||\n|  ||\n| **第10章 结束语**                            ||\n| 10-1 总结，算法思想，大家加油！                  ||\n\n"
  },
  {
    "path": "course/04 Nginx从入门到实战.md",
    "content": "## 《Nginx从入门到实践》学习记录\n\n\n\nFebruary 2018 Web Server Survey | Netcraft\nhttps://news.netcraft.com/archives/2018/02/13/february-2018-web-server-survey.html\n\n| 章节                                                         | 记录 |\n| ------------------------------------------------------------ | ---- |\n| **第1章 课程前言**                                           |      |\n| 1-1 课程介绍                                                 |      |\n| 1-2 学习环境准备                                             |      |\n|                                                              |      |\n| **第2章 基础篇**                                             |      |\n| 2-1 什么是Nginx                                              |      |\n| 2-2 常见的中间件服务                                         |      |\n| 2-3 Nginx特性_实现优点1                                      |      |\n| 2-4 Nginx特性_实现优点2                                      |      |\n| 2-5 Nginx特性_实现优点3                                      |      |\n| 2-6 Nginx特性_实现优点4                                      |      |\n| 2-7 Nginx快速安装                                            |      |\n| 2-8 Nginx的目录和配置语法_Nginx安装目录                      |      |\n| 2-9 Nginx的目录和配置语法_Nginx编译配置参数                  |      |\n| 2-10 Nginx的目录和配置语法_默认配置语法                      |      |\n| 2-11 Nginx的目录和配置语法_默认配置与默认站点启动            |      |\n| 2-12 HTTP请求                                                |      |\n| 2-13 Nginx日志_log_format1                                   |      |\n| 2-14 Nginx日志_log_format2                                   |      |\n| 2-15 Nginx模块讲解_模块介绍                                  |      |\n| 2-16 Nginx模块讲解_sub_status                                |      |\n| 2-17 Nginx模块讲解_random_index                              |      |\n| 2-18 Nginx模块讲解_sub_module                                |      |\n| 2-19 Nginx模块讲解_sub_module配置演示                        |      |\n| 2-20 Nginx的请求限制_配置语法与原理1                         |      |\n| 2-21 Nginx的请求限制_配置语法与原理2                         |      |\n| 2-22 Nginx的请求限制_配置语法与原理3                         |      |\n| 2-23 Nginx的访问控制_介绍实现访问控制的基本方式              |      |\n| 2-24 Nginx的访问控制—access_module配置语法介绍               |      |\n| 2-25 Nginx的访问控制—access_module配置                       |      |\n| 2-26 Nginx的访问控制—access_module局限性                     |      |\n| 2-27 Nginx的访问控制—auth_basic_module配置                   |      |\n| 2-28 Nginx的访问控制—auth_basic_module局限性                 |      |\n|                                                              |      |\n| **第3章 场景实践篇**                                         |      |\n| 3-1 场景实践篇内容介绍                                       |      |\n| 3-2 Nginx作为静态资源web服务_静态资源类型                    |      |\n| 3-3 Nginx作为静态资源web服务_CDN场景                         |      |\n| 3-4 Nginx作为静态资源web服务_配置语法                        |      |\n| 3-5 Nginx作为静态资源web服务_场景演示                        |      |\n| 3-6 Nginx作为静态资源web服务_浏览器缓存原理                  |      |\n| 3-7 Nginx作为静态资源web服务_浏览器缓存场景演示              |      |\n| 3-8 Nginx作为静态资源web服务_跨站访问                        |      |\n| 3-9 Nginx作为静态资源web服务_跨域访问场景配置                |      |\n| 3-10 Nginx作为静态资源web服务_防盗链(1)                      |      |\n| 3-11 Nginx作为静态资源web服务_防盗链(2)                      |      |\n| 3-12 Nginx作为代理服务_代理服务                              |      |\n| 3-13 Nginx作为代理服务_配置语法及反向代理场景                |      |\n| 3-14 Nginx作为代理服务_正向代理配置场景(1)                   |      |\n| 3-15 Nginx作为代理服务_正向代理配置场景(2)                   |      |\n| 3-16 Nginx作为代理服务_代理配置语法补充                      |      |\n| 3-17 Nginx作为代理服务_代理补充配置和规范                    |      |\n| 3-18 Nginx作为负载均衡服务_负载均衡与Nginx                   |      |\n| 3-19 Nginx作为负载均衡服务_配置语法                          |      |\n| 3-20 Nginx作为负载均衡服务_配置场景                          |      |\n| 3-21 Nginx作为负载均衡服务_server参数讲解                    |      |\n| 3-22 Nginx作为负载均衡服务_backup状态演示                    |      |\n| 3-23 Nginx作为负载均衡服务_轮询策略与加权轮询                |      |\n| 3-24 Nginx作为负载均衡服务_负载均衡策略ip_hash方式           |      |\n| 3-25 Nginx作为负载均衡服务_负载均衡策略url_hash策略          |      |\n| 3-26 Nginx作为缓存服务_Nginx作为缓存服务                     |      |\n| 3-27 Nginx作为缓存服务_缓存服务配置语法                      |      |\n| 3-28 Nginx作为缓存服务_场景配置演示                          |      |\n| 3-29 Nginx作为缓存服务_场景配置补充说明                      |      |\n| 3-30 Nginx作为缓存服务_分片请求                              |      |\n|                                                              |      |\n| **第4章 深度学习篇**                                         |      |\n| 4-1 Nginx动静分离_动静分离场景演示                           |      |\n| 4-2 Nginx动静分离_动静分离场景演示(1)                        |      |\n| 4-3 Nginx动静分离_动静分离场景演示(2)                        |      |\n| 4-4 Rewrite规则_rewrite规则作用                              |      |\n| 4-5 Rewrite规则_rewrite配置语法                              |      |\n| 4-6 Rewrite规则_rewrite正则表达式                            |      |\n| 4-7 Rewrite规则_rewrite规则中的flag                          |      |\n| 4-8 Rewrite规则_redirect和permanent区别                      |      |\n| 4-9 Rewrite规则_rewrite规则场景(1)                           |      |\n| 4-10 Rewrite规则_rewrite规则场景(2)                          |      |\n| 4-11 Rewrite规则_rewrite规则书写                             |      |\n| 4-12 Nginx进阶高级模块_secure_link模块作用原理               |      |\n| 4-13 Nginx进阶高级模块_secure_link模块实现请求资源验证       |      |\n| 4-14 Nginx进阶高级模块_Geoip读取地域信息模块介绍             |      |\n| 4-15 Nginx进阶高级模块_Geoip读取地域信息场景展示             |      |\n| 4-16 基于Nginx的HTTPS服务_HTTPS原理和作用1                   |      |\n| 4-17 基于Nginx的HTTPS服务_HTTPS原理和作用2                   |      |\n| 4-18 基于Nginx的HTTPS服务_证书签名生成CA证书                 |      |\n| 4-19 基于Nginx的HTTPS服务_证书签名生成和Nginx的HTTPS服务场景演示1 |      |\n| 4-20 基于Nginx的HTTPS服务_证书签名生成和Nginx的HTTPS服务场景演示2 |      |\n| 4-21 基于Nginx的HTTPS服务_实战场景配置苹果要求的openssl后台HTTPS服务1 |      |\n| 4-22 基于Nginx的HTTPS服务_实战场景配置苹果要求的openssl后台HTTPS服务2 |      |\n| 4-23 基于Nginx的HTTPS服务_实战场景配置苹果要求的openssl后台HTTPS服务3 |      |\n| 4-24 基于Nginx的HTTPS服务_HTTPS服务优化                      |      |\n| 4-25 Nginx与Lua的开发_Nginx与Lua特性与优势                   |      |\n| 4-26 Nginx与Lua的开发_Lua基础开发语法1                       |      |\n| 4-27 Nginx与Lua的开发_Lua基础开发语法2                       |      |\n| 4-28 Nginx与Lua的开发_Nginx与Lua的开发环境                   |      |\n| 4-29 Nginx与Lua的开发_Nginx调用Lua的指令及Nginx的Luaapi接口  |      |\n| 4-30 Nginx与Lua的开发_实战场景灰度发布                       |      |\n| 4-31 Nginx与Lua的开发_实战场景灰度发布场景演示1              |      |\n| 4-32 Nginx与Lua的开发_实战场景灰度发布场景演示2              |      |\n| 4-33 Nginx与Lua的开发_实战场景灰度发布场景演示3              |      |\n| 4-34 Nginx与Lua的开发_实战场景灰度发布场景演示4              |      |\n|                                                              |      |\n| **第5章 Nginx架构篇**                                        |      |\n| 5-1 Nginx常见问题_架构篇介绍                                 |      |\n| 5-2 Nginx常见问题__多个server_name中虚拟主机读取的优先级     |      |\n| 5-3 Nginx常见问题_多个location匹配的优先级1                  |      |\n| 5-4 Nginx常见问题_多个location匹配的优先级2                  |      |\n| 5-5 Nginx常见问题_try_files使用                              |      |\n| 5-6 Nginx常见问题_alias和root的使用区别                      |      |\n| 5-7 Nginx常见问题_如何获取用户真实的ip信息                   |      |\n| 5-8 Nginx常见问题_Nginx中常见错误码                          |      |\n| 5-9 Nginx的性能优化_内容介绍及性能优化考虑                   |      |\n| 5-10 Nginx的性能优化_ab压测工具                              |      |\n| 5-11 Nginx的性能优化_ab压测工具1                             |      |\n| 5-12 Nginx的性能优化_ab压测工具2                             |      |\n| 5-13 Nginx的性能优化_ab压测工具3                             |      |\n| 5-14 Nginx的性能优化_系统与Nginx性能优化                     |      |\n| 5-15 Nginx的性能优化_文件句柄设置                            |      |\n| 5-16 Nginx的性能优化_CPU亲和配置1                            |      |\n| 5-17 Nginx的性能优化_CPU亲和配置2                            |      |\n| 5-18 Nginx的性能优化_Nginx通用配置优化                       |      |\n| 5-19 Nginx安全_基于Nginx的安全章节内容介绍                   |      |\n| 5-20 Nginx安全_恶意行为控制手段                              |      |\n| 5-21 Nginx安全_攻击手段之暴力破解                            |      |\n| 5-22 Nginx安全_文件上传漏洞                                  |      |\n| 5-23 Nginx安全_SQL注入                                       |      |\n| 5-24 Nginx安全_SQL注入场景说明                               |      |\n| 5-25 Nginx安全_场景准备mariadb和lnmp环境                     |      |\n| 5-26 Nginx安全_模拟SQL注入场景                               |      |\n| 5-27 Nginx安全_Nginx+LUA防火墙功能                           |      |\n| 5-28 Nginx安全_Nginx+LUA防火墙防sql注入场景演示              |      |\n| 5-29 Nginx安全_复杂的访问攻击中CC攻击方式                    |      |\n| 5-30 Nginx安全_Nginx版本更新和本身漏洞                       |      |\n| 5-31 Nginx架构总结_静态资源服务的功能设计                    |      |\n| 5-32 Nginx架构总结_Nginx作为代理服务的需求                   |      |\n| 5-33 Nginx架构总结_需求设计评估                              |      |\n| 5-34 完结散花                                                |      |"
  },
  {
    "path": "course/05 Java并发编程与高并发解决方案.md",
    "content": "## 《Java并发编程与高并发解决方案》学习记录\n\n时长：12小时30分钟\n\n| 章节                                       | 记录 |\n| ------------------------------------------ | ---- |\n| **第1章 课程准备**                         |      |\n| 1-1 课程导学                               |      |\n| 1-2 并发编程初体验                         |      |\n| 1-3 并发与高并发基本概念                   |      |\n|                                            |      |\n| **第2章 并发基础**                         |      |\n| 2-1 CPU多级缓存-缓存一致性                 |      |\n| 2-2 CPU多级缓存-乱序执行优化               |      |\n| 2-3 JAVA内存模型                           |      |\n| 2-4 并发的优势与风险                       |      |\n|                                            |      |\n| **第3章 项目准备**                         |      |\n| 3-1 案例环境初始化                         |      |\n| 3-2 案例准备工作                           |      |\n| 3-3 并发模拟-工具                          |      |\n| 3-4 并发模拟-代码                          |      |\n|                                            |      |\n| **第4章 线程安全性**                       |      |\n| 4-1 线程安全性-原子性-atomic-1             |      |\n| 4-2 线程安全性-原子性-atomic-2             |      |\n| 4-3 线程安全性-原子性-synchronized         |      |\n| 4-4 线程安全性-可见性                      |      |\n| 4-5 线程安全性-有序性与总结                |      |\n|                                            |      |\n| **第5章 安全发布对象**                     |      |\n| 5-1 安全发布对象-发布与逸出                |      |\n| 5-2 安全发布对象-四种方法-1                |      |\n| 5-3 安全发布对象-四种方法-2                |      |\n|                                            |      |\n| **第6章 线程安全策略**                     |      |\n| 6-1 不可变对象-1                           |      |\n| 6-2 不可变对象-2                           |      |\n| 6-3 线程封闭-1                             |      |\n| 6-4 线程封闭-2                             |      |\n| 6-5 线程不安全类与写法-1                   |      |\n| 6-6 线程不安全类与写法-2                   |      |\n| 6-7 同步容器-1                             |      |\n| 6-8 同步容器-2                             |      |\n| 6-9 并发容器及安全共享策略总结             |      |\n|                                            |      |\n| **第7章 J.U.C之AQS**                       |      |\n| 7-1 J.U.C之AQS-介绍                        |      |\n| 7-2 J.U.C之AQS-CountDownLatch              |      |\n| 7-3 J.U.C之AQS-Semaphore                   |      |\n| 7-4 J.U.C之AQS-CyclicBarrier               |      |\n| 7-5 J.U.C之AQS-ReentrantLock与锁-1         |      |\n| 7-6 J.U.C之AQS-ReentrantLock与锁-2         |      |\n|                                            |      |\n| **第8章 J.U.C组件拓展**                    |      |\n| 8-1 J.U.C-FutureTask-1                     |      |\n| 8-2 J.U.C-FutureTask-2                     |      |\n| 8-3 J.U.C-ForkJoin                         |      |\n| 8-4 J.U.C-BlockingQueue                    |      |\n|                                            |      |\n| **第9章 线程调度-线程池**                  |      |\n| 9-1 线程池-1                               |      |\n| 9-2 线程池-2                               |      |\n| 9-3 线程池-3                               |      |\n|                                            |      |\n| **第10章 多线程并发拓展**                  |      |\n| 10-1 死锁                                  |      |\n| 10-2 并发最佳实践                          |      |\n| 10-3 Spring与线程安全                      |      |\n| 10-4 HashMap与ConcurrentHashMap解析        |      |\n| 10-5 多线程并发与线程安全总结              |      |\n|                                            |      |\n| **第11章 高并发之扩容思路**                |      |\n| 11-1 高并发之扩容思路                      |      |\n|                                            |      |\n| **第12章 高并发之缓存思路**                |      |\n| 12-1 高并发之缓存-特征、场景及组件介绍-1   |      |\n| 12-2 高并发之缓存-特征、场景及组件介绍-2   |      |\n| 12-3 高并发之缓存-redis的使用              |      |\n| 12-4 高并发之缓存-高并发场景问题及实战讲解 |      |\n|                                            |      |\n| **第13章 高并发之消息队列思路**            |      |\n| 13-1 高并发之消息队列-1                    |      |\n| 13-2 高并发之消息队列-2                    |      |\n| 13-3 高并发之消息队列-3                    |      |\n|                                            |      |\n| **第14章 高并发之应用拆分思路**            |      |\n| 14-1 高并发之应用拆分-1                    |      |\n| 14-2 高并发之应用拆分-2                    |      |\n|                                            |      |\n| **第15章 高并发之应用限流思路**            |      |\n| 15-1 高并发之应用限流-1                    |      |\n| 15-2 高并发之应用限流-2                    |      |\n|                                            |      |\n| **第16章 高并发之服务降级与服务熔断思路**  |      |\n| 16-1 高并发之服务降级与服务熔断思路        |      |\n|                                            |      |\n| **第17章 高并发之数据库切库分库分表思路**  |      |\n| 17-1 高并发之数据库切库分库分表            |      |\n|                                            |      |\n| **第18章 高并发之高可用手段介绍**          |      |\n| 18-1 高并发之高可用一些手段                |      |\n|                                            |      |\n| **第19章 课程总结**                        |      |\n| 19-1 课程总结                              |      |"
  },
  {
    "path": "course/06 HTTP协议原理+实践 Web开发工程师必学.md",
    "content": "## 《HTTP协议原理+实践 Web开发工程师必学》学习记录\n\n| 章节                                      | 记录 |\n| ----------------------------------------- | ---- |\n| **第1章 课程导学**                        |      |\n| 1-1 导学                                  |      |\n| 1-2 内容介绍                              |      |\n|                                           |      |\n| **第2章 HTTP协议基础及发展历史**          |      |\n| 2-1 5层网络模型介绍                       |      |\n| 2-2 HTTP协议的发展历史                    |      |\n| 2-3 HTTP的三次握手                        |      |\n| 2-4 URI-URL和URN                          |      |\n| 2-5 HTTP报文格式                          |      |\n| 2-6 创建一个最简单的web服务               |      |\n|                                           |      |\n| **第3章 HTTP各种特性总览**                |      |\n| 3-1 认识HTTP客户端                        |      |\n| 3-2 CORS跨域请求的限制与解决              |      |\n| 3-3 CORS跨域限制以及预请求验证            |      |\n| 3-4 缓存头Cache-Control的含义和使用       |      |\n| 3-5 缓存验证Last-Modified和Etag的使用     |      |\n| 3-6 cookie和session                       |      |\n| 3-7 HTTP长连接                            |      |\n| 3-8 数据协商                              |      |\n| 3-9 Redirect                              |      |\n| 3-10 CSP                                  |      |\n|                                           |      |\n| **第4章 Nginx代理以及面向未来的HTTP**     |      |\n| 4-1 Nginx安装和基础代理配置               |      |\n| 4-2 Nginx代理配置和代理缓存的用处         |      |\n| 4-3 HTTPS解析                             |      |\n| 4-4 使用Nginx部署HTTPS服务                |      |\n| 4-5 HTTP2的优势和Nginx配置HTTP2的简单使用 |      |\n|                                           |      |\n| **第5章 课程总结**                        |      |\n| 5-1 课程总结                              |      |"
  },
  {
    "path": "course/07 SpringBoot微信点餐.md",
    "content": "## 《Spring Boot企业微信点餐系统 》学习记录\n\n| 章节                                | 记录 |      |\n| ----------------------------------- | ---- | ---- |\n| **01章 课程介绍**                   |      |      |\n| 01-01 课程介绍                      |      |      |\n|                                     |      |      |\n| **02章 项目设计**                   |      |      |\n| 02-01 项目设计                      |      |      |\n| 02-02 架构和基础框架                |      |      |\n| 02-03 数据库设计                    |      |      |\n|                                     |      |      |\n| **03章 项目起步**                   |      |      |\n| 03-01 开发环境搭建                  |      |      |\n| 03-02 日志的使用                    |      |      |\n| 03-03 源码下载及使用                |      |      |\n|                                     |      |      |\n| **04章 买家端类目**                 |      |      |\n| 04-01 买家类目-dao（上）            |      |      |\n| 04-02 买家类目-dao（下）            |      |      |\n| 04-03 买家类目-service              |      |      |\n|                                     |      |      |\n| **05章 买家端商品**                 |      |      |\n| 05-01 买家商品-dao                  | 5/22 |      |\n| 05-02 买家商品-service              | 5/22 |      |\n| 05-03 买家商品-api（上）            | 5/22 |      |\n| 05-04 买家商品-api（下）            | 5/22 |      |\n|                                     |      |      |\n| **06章 买家端订单**                 |      |      |\n| 06-01 买家订单-dao(上)              |      |      |\n| 06-02 买家订单-dao(下)              |      |      |\n| 06-03 买家订单-service创建_A        |      |      |\n| 06-04 买家订单-service创建_B        |      |      |\n| 06-05 买家订单-service创建_C        |      |      |\n| 06-06 买家订单-service创建_D        |      |      |\n| 06-07 买家订单-service查询          |      |      |\n| 06-08 买家订单-service取消          |      |      |\n| 06-09 买家订单-service finish和paid |      |      |\n| 06-10 买家订单-api_A                |      |      |\n| 06-11 买家订单-api_B                |      |      |\n| 06-12 买家订单-api_C                |      |      |\n| 06-13 买家订单-api_D                |      |      |\n|                                     |      |      |\n| **07章 微信授权**                   |      |      |\n| 07-01 设置域名                      |      |      |\n| 07-02 获取code                      |      |      |\n| 07-03 换取access_token              |      |      |\n| 07-04 使用sdk方式（上）             |      |      |\n| 07-05 使用sdk方式（下）             |      |      |\n| 07-06 微信网页授权前端调试          |      |      |\n|                                     |      |      |\n| **08章 微信支付和退款**             |      |      |\n| 08-01 发起微信支付-后端（上）       |      |      |\n| 08-02 发起微信支付-后端（下）       |      |      |\n| 08-03 在网页发起支付                |      |      |\n| 08-04 动态注入参数发起支付          |      |      |\n| 08-05 微信异步通知（上）            |      |      |\n| 08-06 微信异步通知（下）            |      |      |\n| 08-07 微信退款                      |      |      |\n| 08-08 补充：使用测试号实现授权      |      |      |\n|                                     |      |      |\n| **09章 卖家端订单**                 |      |      |\n| 09-01 卖家订单-service              |      |      |\n| 09-02 卖家-订单-controller（上）    |      |      |\n| 09-03 卖家-订单-controller（下）    |      |      |\n| 09-04 卖家订单-controller-翻页      |      |      |\n| 09-05 卖家订单-controller-取消订单  |      |      |\n| 09-06 卖家订单-controller-订单详情  |      |      |\n| 09-07 卖家订单-controller-完结订单  |      |      |\n|                                     |      |      |\n| **10章 卖家端通用功能和上下架**     |      |      |\n| 10-01 关于模版的小技巧              |      |      |\n| 10-02 实现边栏                      |      |      |\n| 10-03 实现商品列表                  |      |      |\n| 10-04 商品上下架-service            |      |      |\n| 10-05 商品上下架-controller         |      |      |\n|                                     |      |      |\n| **11章 卖家端新增商品和类目**       |      |      |\n| 11-01 卖家商品-新增修改页面         |      |      |\n| 11-02 卖家商品-修改表单提交         |      |      |\n| 11-03 卖家商品-新增功能             |      |      |\n| 11-04 卖家类目功能开发              |      |      |\n|                                     |      |      |\n| **12章 买家和卖家端联通**           |      |      |\n| 12-01 分布式session理论(上)         |      |      |\n| 12-02 分布式session理论(下)         |      |      |\n| 12-03 卖家信息表-dao开发            |      |      |\n| 12-04 卖家扫码登录service开发       |      |      |\n| 12-05 卖家扫码登录获取openid        |      |      |\n| 12-06 登录成功                      |      |      |\n| 12-07 登出成功                      |      |      |\n| 12-08 AOP实现身份验证               |      |      |\n| 12-09 微信模版消息推送              |      |      |\n| 12-10 webSocket消息推送             |      |      |\n|                                     |      |      |\n| **13章 项目优化**                   |      |      |\n| 13-01 异常捕获                      |      |      |\n| 13-02 mybatis注解方式使用_A         |      |      |\n| 13-03 mybatis注解方式使用_B         |      |      |\n| 13-04 mybatis xml方式使用           |      |      |\n| 13-05 jpa和mybatis的选择            |      |      |\n| 13-06 ab压测介绍                    |      |      |\n| 13-07 synchronized处理并发          |      |      |\n| 13-08 redis分布式锁                 |      |      |\n| 13-09 redis缓存的使用(上)           |      |      |\n| 13-10 redis缓存的使用(下)           |      |      |\n|                                     |      |      |\n| **14章 项目部署**                   |      |      |\n| 14-01 项目部署                      |      |      |\n|                                     |      |      |\n| **15章 课程总结**                   |      |      |\n| 15-01 13.总结                       |      |      |"
  },
  {
    "path": "course/08 廖雪峰的Java教程.md",
    "content": "## 《廖雪峰的Java教程》学习记录\n\n| 章节                     | 小节                   | 记录 |\n| ------------------------ | ---------------------- | ---- |\n| **1-Java快速入门**       |                        |      |\n| 01-Java入门              |                        |      |\n|                          | 01-Java简介            |      |\n|                          | 02-安装JDK             |      |\n|                          | 03-第一个Java程序      |      |\n|                          | 04-安装并使用Eclipse   |      |\n|                          | 05-安装Eclipse插件     |      |\n| 02-Java程序基础          |                        |      |\n|                          | 01-Java程序基本结构    |      |\n|                          | 02-变量和数据类型      |      |\n|                          | 03-整数运算            |      |\n|                          | 05-浮点数运算          |      |\n|                          | 07-布尔运算            |      |\n|                          | 08-字符和字符串        |      |\n|                          | 09-数组类型            |      |\n| 03-流程控制              |                        |      |\n|                          | 01-输入和输出          |      |\n|                          | 03-if判断              |      |\n|                          | 04-switch多重选择      |      |\n|                          | 05-while循环           |      |\n|                          | 06-do-while循环        |      |\n|                          | 07-for循环             |      |\n|                          | 09-break和continue     |      |\n| 04-数组操作              |                        |      |\n|                          | 1-遍历数组             |      |\n|                          | 2-数组排序             |      |\n|                          | 4-多维数组             |      |\n|                          | 5-命令行参数           |      |\n|                          |                        |      |\n| **2-Java面向对象编程**   |                        |      |\n| 1-面向对象的概念         |                        |      |\n|                          | 面向对象基础           |      |\n| 2-数据封装               |                        |      |\n|                          | 1-方法                 |      |\n|                          | 2-构造方法             |      |\n|                          | 3-方法重载             |      |\n| 3-继承和多态             |                        |      |\n|                          | 1-继承                 |      |\n|                          | 2-多态                 |      |\n| 4-抽象类和接口           |                        |      |\n|                          | 1-抽象类               |      |\n|                          | 2-接口                 |      |\n| 5-包和classpath          |                        |      |\n|                          | 1-静态字段和方法       |      |\n|                          | 2-包                   |      |\n|                          | 3-作用域               |      |\n|                          | 4-classpath和jar       |      |\n| 6-Java核心类             |                        |      |\n|                          | 1-字符串和编码         |      |\n|                          | 2-StringBuilder        |      |\n|                          | 3-包装类型             |      |\n|                          | 4-JavaBean             |      |\n|                          | 5-枚举类               |      |\n|                          | 6-常用工具类           |      |\n|                          |                        |      |\n| **3-Java异常处理**       |                        |      |\n| 1-错误处理               |                        |      |\n|                          | 1-Java的异常           |      |\n|                          | 2-捕获异常             |      |\n|                          | 3-抛出异常             |      |\n|                          | 4-自定义异常           |      |\n| 2-断言和日志             |                        |      |\n|                          | 1-使用断言             |      |\n|                          | 2-使用JDK Logging      |      |\n|                          | 3-使用Commons Logging  |      |\n|                          | 4-使用Log4j            |      |\n|                          |                        |      |\n| **4-Java反射与泛型**     |                        |      |\n| 1-反射                   |                        |      |\n|                          | 1-Class类              |      |\n|                          | 2-访问字段             |      |\n|                          | 3-调用方法             |      |\n|                          | 4-调用构造方法         |      |\n|                          | 5-获取继承关系         |      |\n| 2-注解                   |                        |      |\n|                          | 1-使用注解             |      |\n|                          | 2-定义注解             |      |\n|                          | 3-处理注解             |      |\n| 3-泛型                   |                        |      |\n|                          | 1-什么是泛型           |      |\n|                          | 2-使用泛型             |      |\n|                          | 3-编写泛型             |      |\n|                          | 4-擦拭法               |      |\n|                          | 5-extends通配符        |      |\n|                          | 6-super通配符          |      |\n|                          | 7-泛型和反射           |      |\n|                          |                        |      |\n| **5-Java集合**           |                        |      |\n| 1-Java集合简介           |                        |      |\n|                          | Java集合简介           |      |\n| 2-List                   |                        |      |\n|                          | 1-使用List             |      |\n|                          | 2-编写equals方法       |      |\n| 3-Map                    |                        |      |\n|                          | 1-使用Map              |      |\n|                          | 2-编写equals和hashCode |      |\n|                          | 3-使用Properties       |      |\n| 4-Set                    |                        |      |\n|                          | 使用Set                |      |\n|                          |                        |      |\n| 5-Queue                  |                        |      |\n|                          | 1-使用Queue            |      |\n|                          | 2-使用PriorityQueue    |      |\n|                          | 3-使用Deque            |      |\n| 6-Stack                  |                        |      |\n|                          | 使用Stack              |      |\n|                          |                        |      |\n| 7-最佳实践               |                        |      |\n|                          | 1-使用Iterator         |      |\n|                          | 2-使用Collections      |      |\n|                          |                        |      |\n| **6-Java IO编程**        |                        |      |\n| 1-IO基础                 |                        |      |\n|                          | 1-IO简介               |      |\n|                          | 2-File对象             |      |\n| 2-Input和Output          |                        |      |\n|                          | 1-InputStream          |      |\n|                          | 2-OutputStream         |      |\n|                          | 4-Filter模式           |      |\n|                          | 6-classpath资源        |      |\n|                          | 7-序列化               |      |\n| 3-Reader和Writer         |                        |      |\n|                          | 1-Reader               |      |\n|                          | 2-Writer               |      |\n|                          |                        |      |\n| **7-Java处理日期和时间** |                        |      |\n| 1-概念                   |                        |      |\n|                          | 日期和时间             |      |\n| 2-Date和Calendar         |                        |      |\n|                          | 1-Date                 |      |\n|                          | 2-Calendar             |      |\n| 3-java.time的API         |                        |      |\n|                          | 1-LocalDateTime        |      |\n|                          | 2-ZonedDateTime        |      |\n| 4-最佳实践               |                        |      |\n|                          | 最佳实践               |      |\n|                          |                        |      |\n| **8-JUnit单元测试**      |                        |      |\n| 1-JUnit简介              |                        |      |\n|                          | JUnit测试              |      |\n|                          |                        |      |\n| 2-使用JUnit              |                        |      |\n|                          | 1-使用Before和After    |      |\n|                          | 2-异常测试             |      |\n|                          | 3-参数化测试           |      |\n|                          | 4-超时测试             |      |\n|                          |                        |      |\n| **9-Java正则表达式**     |                        |      |\n| 1-正则表达式入门         |                        |      |\n|                          | 1-正则表达式简介       |      |\n|                          | 2-匹配规则             |      |\n| 2-正则表达式进阶         |                        |      |\n|                          | 1-复杂匹配规则         |      |\n|                          | 3-分组匹配规则         |      |\n|                          | 5-非贪婪匹配           |      |\n|                          | 6-搜索和替换           |      |\n|                          |                        |      |\n| **10-Java加密与安全**    |                        |      |\n| 1-数据安全简介           |                        |      |\n|                          | 加密与安全             |      |\n| 2-编码算法               |                        |      |\n|                          | 1-URL编码              |      |\n|                          | 2-Base64编码           |      |\n| 3-摘要算法               |                        |      |\n|                          | 1-MD5                  |      |\n|                          | 3-SHA1                 |      |\n|                          | 4-BouncyCastle         |      |\n|                          | 5-Hmac                 |      |\n| 4-加密算法               |                        |      |\n|                          | 1-对称加密算法         |      |\n|                          | 2-口令加密算法         |      |\n|                          | 4-密钥交换算法         |      |\n|                          | 5-非对称加密算法       |      |\n| 5-签名算法               |                        |      |\n|                          | 1-RSA签名算法          |      |\n|                          | 2-DSA签名算法          |      |\n| 6-数字证书               |                        |      |\n|                          | 数字证书               |      |\n|                          |                        |      |\n| **11-Java多线程编程**    |                        |      |\n| 1-线程的概念             |                        |      |\n|                          | 1-多线程简介           |      |\n|                          | 2-创建新线程           |      |\n|                          | 3-线程的状态           |      |\n|                          | 5-中断线程             |      |\n|                          | 6-守护线程             |      |\n| 2-线程同步               |                        |      |\n|                          | 1-线程同步             |      |\n|                          | 2-synchronized方法     |      |\n|                          | 3-死锁                 |      |\n|                          | 4-wait和notify         |      |\n| 3-高级concurrent包       |                        |      |\n|                          | 1-ReentrantLock        |      |\n|                          | 2-ReadWriteLock        |      |\n|                          | 3-Condition            |      |\n|                          | 4-Concurrent集合       |      |\n|                          | 5-Atomic               |      |\n|                          | 6-ExecutorService      |      |\n|                          | 7-Future               |      |\n|                          | 8-CompletableFuture    |      |\n|                          | 9-Fork_Join            |      |\n| 4-线程工具类             |                        |      |\n|                          | ThreadLocal            |      |\n|                          |                        |      |\n| **12-Maven基础**         |                        |      |\n| 1-Maven入门              |                        |      |\n|                          | 1-Maven介绍            |      |\n|                          | 2-依赖管理             |      |\n|                          | 3-构建流程             |      |\n| 2-Maven进阶              |                        |      |\n|                          | 1-使用插件             |      |\n|                          | 2-模块管理             |      |\n|                          |                        |      |\n| **13-Java网络编程**      |                        |      |\n| 1-Socket编程             |                        |      |\n|                          | 1-网络编程概念         |      |\n|                          | 2-TCP编程              |      |\n|                          | 3-TCP多线程编程        |      |\n|                          | 5-UDP编程              |      |\n| 2-Email编程              |                        |      |\n|                          | 1-发送Email            |      |\n|                          | 2-接收Email            |      |\n| 3-其他                   |                        |      |\n|                          | 1-HTTP编程             |      |\n|                          | 2-RMI远程调用          |      |\n|                          |                        |      |\n| **14-Java操作XML和JSON** |                        |      |\n| 1-XML                    |                        |      |\n|                          | 1-XML介绍              |      |\n|                          | 2-DOM                  |      |\n|                          | 3-SAX                  |      |\n|                          | 4-第三方XML库          |      |\n| 2-JSON                   |                        |      |\n|                          | 1-JSON介绍             |      |\n|                          | 2-处理JSON             |      |\n|                          |                        |      |\n| **15-Java JDBC编程**     |                        |      |\n| 1-关系数据库基础         |                        |      |\n|                          | 1-关系数据库简介       |      |\n|                          | 2-安装MySQL            |      |\n| 2-SQL入门                |                        |      |\n|                          | 1-SQL介绍              |      |\n|                          | 2-Insert语句           |      |\n|                          | 3-Select语句           |      |\n|                          | 4-Update语句           |      |\n|                          | 5-Delete语句           |      |\n| 3-JDBC接口               |                        |      |\n|                          | 1-JDBC简介             |      |\n|                          | 2-JDBC查询             |      |\n|                          | 3-JDBC更新             |      |\n|                          | 4-JDBC事务             |      |\n|                          | 5-JDBC连接池           |      |\n|                          |                        |      |\n| **16-Java函数式编程**    |                        |      |\n| 1-Lambda表达式           |                        |      |\n|                          | 1-Lambda基础           |      |\n|                          | 3-Method Reference     |      |\n| 2-Stream                 |                        |      |\n|                          | 1-Stream简介           |      |\n|                          | 2-创建Stream           |      |\n|                          | 4-map                  |      |\n|                          | 5-filter               |      |\n|                          | 6-reduce               |      |\n|                          | 7-其他操作             |      |"
  },
  {
    "path": "course/09 Spring学习框架.md",
    "content": "Spring框架2016-黑马程序员_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili\nhttps://www.bilibili.com/video/av27640334?from=search&seid=15898343232092137971\n\n\n\n黑马程序员springmvc_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili\nhttps://www.bilibili.com/video/av18288362?from=search&seid=11826156096511418277\n\n\n\n尚硅谷好评如潮【SpringBoot】视频_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili\nhttps://www.bilibili.com/video/av20965295?from=search&seid=5610749687546975866\n\n\n\n尚硅谷-SpringBoot整合篇_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili\nhttps://www.bilibili.com/video/av23284778/\n\n\n\n\n\n\n\n\n\n最好的JavaWeb教程 - 搜索结果 - 哔哩哔哩弹幕视频网 - ( ゜- ゜)つロ 乾杯~ - bilibili\nhttps://search.bilibili.com/all?keyword=%E6%9C%80%E5%A5%BD%E7%9A%84JavaWeb%E6%95%99%E7%A8%8B\n\n\n\n\n\n\n\n\n\n\n\n| 类型       | 课程                                                         | 时长 |\n| ---------- | ------------------------------------------------------------ | ---- |\n| Spring AOP | [【慕课在线】探秘Spring AOP-慕课网](https://www.imooc.com/learn/869) |      |\n| 基础课程   | [【黑马】Struts2 ](https://www.bilibili.com/video/av13748042) |      |\n| 基础课程   | [【黑马】Spring](https://www.bilibili.com/video/av14839030?from=search&seid=7226271969404706912) |      |\n| 基础课程   | [【黑马】Hibernate](https://www.bilibili.com/video/av14626440) |      |\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"
  },
  {
    "path": "course/10 Redis.md",
    "content": "## 《一站式学习Redis 从入门到高可用分布式实践》 学习记录\n\n- ##### 第1章 Redis初识\n\n  带领听众进入Redis的世界，了解它的前世今生、众多特性、应用场景、安装配置、简单使用，可以让听众对Redis有一个全面的认识。\n\n  - 1-1 导学\n  - 1-2 Redis初识\n  - 1-3 谁在使用Redis\n  - 1-4 redis特性目录\n  - 1-5 特性1-速度快\n  - 1-6 特性2-持久化\n  - 1-7 特性3-数据结构\n  - 1-8 特性4-多语言客户端\n  - 1-9 特性5-功能丰富\n  - 1-10 特性6-简单\n  - 1-11 特性7-复制\n  - 1-12 特性8-高可用分布式\n  - 1-13 redis典型使用场景\n  - 1-14 redis三种启动方式介绍\n  - 1-15 redis常用配置\n  - 1-16 redis安装启动演示\n\n  \n\n- ##### 第2章 API的理解和使用\n\n  全面介绍了Redis提供的5种数据结构字符串（string）、哈希（hash）、列表（list）、集合（set）、有序集合（zset）的数据模型、常用命令、典型应用场景。同时本章还会对Redis的单线程处理机制、键值管理做一个全面介绍，通过对这些原理的理解，听众可以在合适的应用场景选择合适的数据结构。 ...\n\n  - 2-1 -课程目录\n  - 2-2 -通用命令\n  - 2-3 数据结构和内部编码\n  - 2-4 单线程\n  - 2-5 字符串\n  - 2-6 hash (1)\n  - 2-7 hash (2)\n  - 2-8 list(1)\n  - 2-9 list(2)\n  - 2-10 set\n  - 2-11 zset\n\n  \n\n- ##### 第3章 Redis客户端的使用\n\n  本章重点关注Redis客户端的开发，介绍了详细讲解了Java客户端Jedis,简单介绍下Python和Go语言的Redis客户端的选择和使用。\n\n  - 3-1 课程目录\n  - 3-2 Java客户端：Jedis\n  - 3-3 Python客户端：redis-py\n  - 3-4 Go客户端：redigo简介\n  - 3-5 Jedis配置优化（上）\n  - 3-6 Jedis配置优化（下）\n\n  \n\n- ##### 第4章 瑞士军刀Redis其他功能\n\n  除了5种数据结构外，Redis还提供了诸如慢查询、Pipeline、Bitmap、HyperLogLog、发布订阅、GEO等附加功能，在这些功能的帮助下，Redis的应用场景更加丰富。\n\n  - 4-1 课程目录\n  - 4-2 慢查询\n  - 4-3 pipeline\n  - 4-4 发布订阅\n  - 4-5 bitmap\n  - 4-6 hyperloglog\n  - 4-7 geo\n\n  \n\n- ##### 第5章 Redis持久化的取舍和选择\n\n  Redis的持久化功能有效避免因进程退出造成的数据丢失问题，本章将介绍介绍RDB和AOF两种持久化配置和运行流程，以及选择策略\n\n  - 5-1 目录\n  - 5-2 持久化的作用\n  - 5-3 RDB(1)\n  - 5-4 RDB(2)\n  - 5-5 RDB(3)\n  - 5-6 AOF(1)\n  - 5-7 AOF(2)\n  - 5-8 AOF实验\n  - 5-9 RDB和AOF抉择\n\n  \n\n- ##### 第6章 常见的持久化开发运维问题\n\n  本章探讨了常见的持久化问题进行定位和优化，最后结合Redis常见的单机多实例部署场景进行优化\n\n  - 6-1 常见问题目录\n  - 6-2 fork\n  - 6-3 子进程开销和优化\n  - 6-4 AOF阻塞\n\n  \n\n- ##### 第7章 Redis复制的原理与优化\n\n  复制是实现高可用的基石，但复制同样是运维的痛点，本部分详细分析复制的原理，讲解运维过程中可能遇到的问题。\n\n  - 7-1 目录\n  - 7-2 什么是主从复制\n  - 7-3 主从复制配置-介绍\n  - 7-4 主从复制配置-操作\n  - 7-5 runid和复制偏移量\n  - 7-6 全量复制\n  - 7-7 全量复制开销 + 部分复制\n  - 7-8 故障处理\n  - 7-9 主从复制常见问题\n\n  \n\n- ##### 第8章 Redis Sentinel\n\n  本章将一步步解析Redis Sentinel的相关概念、安装部署、配置、客户端路由、原理解析，最后分析了Redis Sentinel运维中的一些问题。\n\n  - 8-1 sentinel-目录\n  - 8-2 主从复制高可用？\n  - 8-3 redis sentinel架构\n  - 8-4 redis sentinel安装与配置\n  - 8-5 redis sentinel安装演示-1\n  - 8-6 redis sentinel安装演示-2\n  - 8-7 java客户端\n  - 8-8 python客户端\n  - 8-9 实现原理-1-故障转移演练\n  - 8-10 实现原理-2.故障转移演练(客户端)\n  - 8-11 实现原理-3.故障演练(日志分析)\n  - 8-12 三个定时任务\n  - 8-13 主观下线和客观下线\n  - 8-14 领导者选举\n  - 8-15 故障转移\n  - 8-16 常见开发运维问题-目录\n  - 8-17 节点运维\n  - 8-18 高可用读写分离\n  - 8-19 本章总结\n\n  \n\n- ##### 第9章 初识Redis Cluster\n\n  Redis Cluster是Redis 3提供的分布式解决方案，有效解决了Redis分布式方面的需求，同时它也是学习分布式存储的绝佳案例。本章将针对Redis Cluster的数据分布，搭建集群进行分析说明。\n\n  - 9-1 本章目录\n  - 9-2 呼唤集群\n  - 9-3 数据分布概论\n  - 9-4 节点取余分区\n  - 9-5 一致性哈希分区\n  - 9-6 虚拟槽哈希分布\n  - 9-7 基本架构\n  - 9-8 原生安装\n  - 9-9 原生安装-1.准备节点\n  - 9-10 原生安装-2.节点握手\n  - 9-11 原生安装-3.分配槽\n  - 9-12 原生安装-4.分配主从\n  - 9-13 ruby环境准备-说明\n  - 9-14 ruby环境准备-操作\n  - 9-15 redis-trib构建集群\n  - 9-16 原生命令和redis-trib.rb对比\n\n  \n\n- ##### 第10章 深入Redis Cluster\n\n  本章将针对Redis Cluster的集群伸缩，请求路由，故障转移等方面进行分析说明。\n\n  - 10-1 集群伸缩目录\n  - 10-2 集群伸缩原理\n  - 10-3 扩展集群-1.加入节点\n  - 10-4 扩展集群-2.加入集群\n  - 10-5 扩展集群-3.迁移槽和数据\n  - 10-6 集群扩容演示-1\n  - 10-7 集群扩容演示-2\n  - 10-8 集群缩容-说明\n  - 10-9 集群缩容-操作\n  - 10-10 客户端路由-目录\n  - 10-11 moved异常说明和操作\n  - 10-12 ask重定向\n  - 10-13 smart客户端实现原理\n  - 10-14 JedisCluster执行源码分析\n  - 10-15 smart客户端JedisCluster-目录\n  - 10-16 JedisCluster基本使用\n  - 10-17 整合spring-1\n  - 10-18 整合spring-2\n  - 10-19 多节点操作命令\n  - 10-20 批量操作优化\n  - 10-21 故障转移-目录\n  - 10-22 故障发现\n  - 10-23 故障恢复\n  - 10-24 故障模拟\n  - 10-25 Redis Cluster常见开发运维问题-目录\n  - 10-26 集群完整性\n  - 10-27 带宽消耗\n  - 10-28 PubSub广播\n  - 10-29 集群倾斜-目录\n  - 10-30 数据倾斜\n  - 10-31 请求倾斜\n  - 10-32 读写分离\n  - 10-33 数据迁移\n  - 10-34 集群vs单机\n  - 10-35 本章总结\n\n  \n\n- ##### 第11章 缓存设计与优化\n\n  讲解将缓存加入应用架构后带来的一些问题，这些问题常常会成为应用的致命点。\n\n  - 11-1 目录\n  - 11-2 缓存的受益和成本\n  - 11-3 缓存的更新策略\n  - 11-4 缓存粒度问题\n  - 11-5 缓存穿透问题\n  - 11-6 缓存雪崩优化\n  - 11-7 无底洞问题\n  - 11-8 热点key的重建优化\n  - 11-9 本章总结\n\n  \n\n- ##### 第12章 Redis云平台CacheCloud\n\n  本章结合前面的知识介绍redis规模化后使用云平台如何进行机器部署、应用接入、用户相关功能维护等问题\n\n  - 12-1 _目录\n  - 12-2 _Redis规模化困扰\n  - 12-3 _快速构建\n  - 12-4 机器部署\n  - 12-5 应用接入\n  - 12-6 用户功能\n  - 12-7 运维功能\n\n  \n\n- ##### 第13章 课程总结\n\n  课程总结\n\n  - 13-1 _课程总结\n\n  \n\n- ##### 第14章 增补：Redis布隆过滤器\n\n  现有50亿电话号码，如何快速判断10w个电话号码是否在其中？利用之前学习的bitmap和redis cluster构建分布式布隆过滤器\n\n  - 14-1 _目录_\n  - 14-2 引出布隆过滤器\n  - 14-3 布隆过滤器基本原理\n  - 14-4 布隆过滤器误差率\n  - 14-5 本地布隆过滤器\n  - 14-6 Redis布隆过器\n  - 14-7 Redis分布式布隆过滤器\n\n  \n\n- ##### 第15章 Redis开发规范\n\n  本章介绍键值的设计、命令的优化、连接池优化、客户端使用规范及客户端常见问题，带领用户全面梳理Redis开发规范。\n\n  - 15-1 key设计\n  - 15-2 value设计\n  - 15-3 发现bigkey的方法\n  - 15-4 bigkey的删除\n  - 15-5 选择合理的数据结构\n  - 15-6 键值生命周期的管理\n  - 15-7 命令优化技巧\n  - 15-8 java客户端优化\n  - 15-9 连接池参数优化1\n  - 15-10 连接池参数优化2\n  - 15-11 连接池参数优化3\n\n  \n\n- ##### 第16章 内存管理\n\n  本章主要讲解Redis内存消耗、组成分析、Redis内存管理设置及内存优化技巧。\n\n  - 16-1 Redis内存优化介绍\n  - 16-2 Redis内存消耗\n  - 16-3 客户端缓冲区\n  - 16-4 缓冲内存\n  - 16-5 对象内存\n  - 16-6 内存设置上限\n  - 16-7 内存回收策略\n  - 16-8 内存优化\n  - 16-9 客户端缓冲区优化\n\n  \n\n- ##### 第17章 开发运维常见坑\n\n  本章介绍Linux针对Redis的内核参数优化、Redis安全七个方法以及Redis热点key的发现方法。\n\n  - 17-1 redis开发运维那些坑\n  - 17-2 overcommit_memory参数讲解和演示\n  - 17-3 其它参数的讲解\n  - 17-4 安全的redis\n  - 17-5 安全七法则\n  - 17-6 热点key"
  },
  {
    "path": "course/11 SpringMVC.md",
    "content": "## 黑马程序员 SpringMVC 学习记录\n\n时长：10小时04分钟\n\n\n| 章节                                                        | 记录 |\n| ----------------------------------------------------------- | ---- |\n| **day01**                                                   |      |\n| 01 spring第一天课程安排                                     |      |\n| 02 springmvc架构-mvc设计模式在bs系统下的应用                |      |\n| 03 springmvc架构-springmvc架构原理分析                      |      |\n| 04 springmvc入门程序-环境搭建                               |      |\n| 05 springmvc入门程序-前端控制器配置                         |      |\n| 06 springmvc入门程序-Handler编写调试                        |      |\n| 07 springmvc入门程序-非注解映射器和适配器                   |      |\n| 08 springmvc入门程序-注解映射器和适配器配置                 |      |\n| 09 springmvc入门程序-注解映射器和适配器                     |      |\n| 10 springmvc入门程序-前端控制器源代码分析                   |      |\n| 11 springmvc入门程序-视图解析器配置前缀和后缀               |      |\n| 12 springmvc和mybaits整合-整合思路                          |      |\n| 13 springmvc和mybaits整合-工程结构                          |      |\n| 14 springmvc和mybaits整合-商品查询mapper                    |      |\n| 15 springmvc和mybaits整合-商品查询service                   |      |\n| 16 springmvc和mybaits整合-商品查询controller                |      |\n| 17 springmvc和mybaits整合-商品查询调试                      |      |\n| 18 springmvc注解开发-商品修改功能分析                       |      |\n| 19 springmvc注解开发-商品修改功能开发service                |      |\n| 20 springmvc注解开发-商品修改功能开发controller             |      |\n| 21 springmvc注解开发-RequestMapping注解                     |      |\n| 22 springmvc注解开发-controller方法返回值                   |      |\n| 23 springmvc注解开发-springmvc参数绑定过程                  |      |\n| 24 springmvc注解开发-springmvc参数绑定-简单类型绑定         |      |\n| 25 springmvc注解开发-springmvc参数绑定-pojo绑定             |      |\n| 26 springmvc注解开发-springmvc参数绑定-自定义参数绑定       |      |\n| 27 springmvc和struts2的区别                                 |      |\n|                                                             |      |\n| **day02**                                                   |      |\n| 01 springmvc第一天课程复习                                  |      |\n| 02 springmvc注解开发-springmvc参数绑定-包装类型pojo参数绑定 |      |\n| 03 springmvc注解开发-springmvc参数绑定-数组                 |      |\n| 04 springmvc注解开发-springmvc参数绑定-list                 |      |\n| 05 springmvc注解开发-validation校验-商品修改校验            |      |\n| 06 springmvc注解开发-validation校验-分组校验                |      |\n| 07 springmvc注解开发-数据回显                               |      |\n| 08 springmvc异常处理-全局异常处理器开发                     |      |\n| 09 springmvc异常处理-抛出异常                               |      |\n| 10 springmvc上传图片                                        |      |\n| 11 springmvc实现json交互-requestBody和responseBody          |      |\n| 12 springmvc实现json交互-准备环境                           |      |\n| 13 springmvc实现json交互-json交互测试                       |      |\n| 14 springmvc对RESTful支持                                   |      |\n| 15 springmvc拦截器-定义和配置                               |      |\n| 16 springmvc拦截器-多拦截器测试                             |      |\n| 17 springmvc拦截器-实现登陆认证                             |      |\n"
  },
  {
    "path": "course/12 Mybatis.md",
    "content": "## 黑马程序员 Mybatis 学习记录\n\n时长：10小时23分钟\n\n| 章节                                              | 记录 |\n| ------------------------------------------------- | ---- |\n| **day-01**                                        |      |\n| 01 mybatis第一天课程安排                          |      |\n| 02 原生态jdbc编程中的问题总结                     |      |\n| 03 mybatis框架原理                                |      |\n| 04 mybatis入门程序-工程结构                       |      |\n| 05 mybatis入门程序-根据id查询用户-映射文件        |      |\n| 06 mybatis入门程序-根据id查询用户-程序代码        |      |\n| 07 mybatis入门程序-根据id查询用户-程序调试        |      |\n| 08 mybatis入门程序-根据名称查询用户               |      |\n| 09 mybatis入门程序-查询用户小结                   |      |\n| 10 mybatis入门程序-添加用户                       |      |\n| 11 mybatis入门程序-添加用户-主键返回              |      |\n| 12 mybatis入门程序-删除用户和更新用户             |      |\n| 13 mybatis入门程序-小结                           |      |\n| 14 mybatis和hibernate的本质区别和应用场景         |      |\n| 15 mybatis开发dao方法-sqlSession应用场合          |      |\n| 16 mybatis开发dao方法-原始dao开发方法             |      |\n| 17 mybatis开发dao方法-原始dao开发方法-问题总结    |      |\n| 18 mybatis开发dao方法-mapper代理开发方法-开发规范 |      |\n| 19 mybatis开发dao方法-mapper代理开发方法-实现     |      |\n| 20 SqlMapConfig-properties定义                    |      |\n| 21 SqlMapConfig-settings                          |      |\n| 22 SqlMapConfig-别名定义                          |      |\n| 23 SqlMapConfig-类型处理器                        |      |\n| 24 SqlMapConfig-mapper加载                        |      |\n| 25 输入映射-pojo包装类型-定义pojo包装类型         |      |\n| 26 输入映射-pojo包装类型-实现                     |      |\n| 27 输出映射-resultType                            |      |\n| 28 输出映射-resultMap                             |      |\n| 29 动态sql-if判断                                 |      |\n| 30 动态sql-sql片段                                |      |\n| 31 动态sql-foreach                                |      |\n| **day-02**                                        |      |\n| 01 第一天课程复习及第二天课程安排                 |      |\n| 02 订单商品数据模型-分析思路                      |      |\n| 03 订单商品数据模型-分析                          |      |\n| 04 高级映射-一对一查询-使用resultType             |      |\n| 05 高级映射-一对一查询-使用resultMap              |      |\n| 06 高级映射-一对多查询                            |      |\n| 07 高级映射-多对多查询                            |      |\n| 08 高级映射-resultMap总结                         |      |\n| 09 高级映射-延迟加载                              |      |\n| 10 查询缓存-一级缓存原理                          |      |\n| 11 查询缓存-一级缓存测试                          |      |\n| 12 查询缓存-一级缓存实际应用                      |      |\n| 13 查询缓存-二级缓存-二级缓存测试                 |      |\n| 14 查询缓存-二级缓存-整合ehcache                  |      |\n| 15 查询缓存-二级缓存-应用场景和局限性             |      |\n| 16 mybatis和spring整合-sqlSessionFactory配置      |      |\n| 17 mybatis和spring整合-原始dao开发                |      |\n| 18 mybatis和spring整合-mapper代理开发             |      |\n| 19 mybatis逆向工程自动生成代码                    |      |"
  },
  {
    "path": "course/13 韩立刚计算机网络.md",
    "content": "## 《韩立刚计算机网络》学习记录\n| 章节                                              | 记录 |\n| ------------------------------------------------- | ---- |\n| **第1章 计算机网络概述**                          |      |\n| 01 课程介绍                                       |      |\n| 02 局域网                                         |      |\n| 03 Intenet和广域网                                |      |\n| 04 规划IP地址介绍MAC地址                          |      |\n| 05 数据包和数据帧                                 |      |\n| 06 访问网站数据传输过程                           |      |\n| 07 OSI参考模型                                    |      |\n| 08 理解OSI参考模型分层思想                        |      |\n| 09 OSI参考模型和网络排错                          |      |\n| 10 OSI参考模型和网络安全                          |      |\n| 11 OSI参考模型和TCP_IP协议                        |      |\n| 12 计算机网络性能指标1                            |      |\n| 13 计算机网络性能指标2                            |      |\n|                                                   |      |\n| **第2章 物理层**                                  |      |\n| 14 物理层定义的标准                       |      |\n| 15 数据通信基础知识                       |      |\n| 16 常用编码                               |      |\n| 17 奈氏准则和香农公式                     |      |\n| 18 数据通信基础知识总结                   |      |\n| 19 物理层下面的传输介质                   |      |\n| 20 信道复用技术                           |      |\n| 21 数字传输系统                           |      |\n| 22 宽带接入技术                           |      |\n|                                                   |      |\n| **第3章 数据链路层**                              |      |\n| 23 数据链路层基本概念                 |      |\n| 24 封装成帧和透明传输                 |      |\n| 25 差错检测                           |      |\n| 26 点到点通信数据链路层协议PPP        |      |\n| 27 配置路由器使用PPP协议              |      |\n| 28 广播信道的数据链路层               |      |\n| 29 CSMA_CD协议技术细节                |      |\n| 30 什么是以太网                       |      |\n| 31 以太网帧格式                       |      |\n| 32 抓包分析数据帧格式                 |      |\n| 33 使用网桥和交换机优化以太网         |      |\n| 34 查看思科交换机MAC地址表            |      |\n| 35 快速以太网和G比特以太网            |      |\n|                                                   |      |\n| **第4章 网络层**                                  |      |\n| 36 网络层提供的服务                               |      |\n| 37 验证网络层功能                                 |      |\n| 38 准备发送数据                                   |      |\n| 39 发送数据的过程                                 |      |\n| 40 ARP协议                                        |      |\n| 41 ARP欺骗                                        |      |\n| 42 网络执法官和ARP防火墙                          |      |\n| 43 ICMP协议和ping命令                             |      |\n| 44 ping和pathping排除网络故障                     |      |\n| 45 IGMP协议和多播组播                             |      |\n| 46 访问多播视频节目                               |      |\n| 47 抓包分析数据包首部                             |      |\n| 48 数据包总长度和数据包分片                       |      |\n| 49 抓包分析分片数据包                             |      |\n| 50 数据包TTL和协议号                              |      |\n| 51 使用抓包工具排除网络故障                       |      |\n| 52 网络畅通的条件                                 |      |\n| 53 静态路由                                       |      |\n| 54 动态路由                                       |      |\n|                                                   |      |\n| **第5章 传输层**                                  |      |\n| 55 TCP和UDP的应用场景                     |      |\n| 56 传输层协议和应用层协议之间关系         |      |\n| 57 服务和应用层协议之间的关系             |      |\n| 58 服务和端口 端口和安全                  |      |\n| 59 传输层功能和端口范围                   |      |\n| 60 UDP首部                                |      |\n| 61 TCP协议概述                            |      |\n| 62 TCP如何实现可靠传输                    |      |\n| 63 TCP首部 端口序号确认号数据偏移         |      |\n| 64 抓包分析TCP首部                        |      |\n| 65 TCP首部标记位                          |      |\n| 66 接收窗口和发送窗口                     |      |\n| 67 TCP滑动窗口技术实现可靠传输            |      |\n| 68 TCP流量控制                            |      |\n| 69 TCP拥塞避免                            |      |\n| 70 TCP传输连接管理                        |      |\n|                                                   |      |\n| **第6章 应用层**                                  |      |\n| 71 什么是域名                             |      |\n| 72 安装和配置DNS服务器                    |      |\n| 73 安装和配置DHCP服务器                   |      |\n| 74 跨网段配置IP地址                       |      |\n| 75 FTP协议主动模式被动模式                |      |\n| 76 安装和配置FTP服务器                    |      |\n| 77 telnet协议                             |      |\n| 78 远程桌面协议RDP                        |      |\n| 79 HTTP协议和网站                         |      |\n| 80 使用Web代理服务访问网站                |      |\n| 81 Internet发送和接收电子邮件的过程       |      |\n| 82 准备邮件服务器实验环境                 |      |\n| 83 安装和配置邮件服务器                   |      |\n|                                                   |      |\n| **第7章 网络安全**                                |      |\n| 84 网络安全介绍                         |      |\n| 85 网络安全面临的威胁                   |      |\n| 86 篡改和伪造                           |      |\n| 87 病毒和木马                           |      |\n| 88 对称加密                             |      |\n| 89 非对称加密和数字签名                 |      |\n| 90 证书颁发机构CA                       |      |\n| 91 安装CA并申请证书和邮箱               |      |\n| 92 发送签名加密电子邮件                 |      |\n| 93 安全套接字SSL功能                    |      |\n| 94 配置网站使用SSL通信                  |      |\n| 95 介绍网络层安全IPSec                  |      |\n| 96 搭建IPSec实验环境                    |      |\n| 97 配置IPSec实现安全通信                |      |\n| 98 数据链路层安全                       |      |\n|                                                   |      |\n| **第8章 Internet上的音频视频**                    |      |\n| 99 互联网音频视频面临的问题 |      |\n| 100 安装流媒体服务          |      |\n| 101 通过网站观看流媒体节目  |      |\n| 102 流媒体实现现场直播      |      |\n| 103 IP电话和QoS             |      |\n|                                                   |      |\n| **第9章 无线网络**                                |      |\n| 104 无线局域网                          |      |\n| 105 创建临时无线网络                    |      |\n\n"
  },
  {
    "path": "course/14 韩立刚计算机网络2.md",
    "content": "## 《韩立刚计算机网络(v2) 》学习记录\n| 章节                                              | 记录 |\n| ------------------------------------------------- | ---- |\n| **第1章 计算机网络概述**                          |      |\n| 001 课程介绍                                      |      |\n| 002 计算机网络在信息时代的作用                    |      |\n| 003 Internet概述                                  |      |\n| 004 Internet组成                                  |      |\n| 005  计算机网络类别                               |      |\n| 006 计算机网络的性能指标                          |      |\n| 007 回顾                                          |      |\n| 008 性能指标2                                     |      |\n| 009 OSI参考模型                                   |      |\n| 010 理解OSI参考模型                               |      |\n| 011 OSI和网络排错                                 |      |\n| 012 OSI参考模型与网络安全                         |      |\n| 013 计算机网络的体系结构                          |      |\n| 014 虚拟机的网络设置                              |      |\n|                                                   |      |\n| **第2章 物理层**                                  |      |\n| 015 物理层基本概念                                |      |\n| 016 数据通信基础知识                              |      |\n| 017 奈氏准则和香农公式                            |      |\n| 018 物理层下面的物理媒体                          |      |\n| 019 回顾                                          |      |\n| 020 频分复用技术                                  |      |\n| 021 时分复用技术                                  |      |\n| 022 码分复用技术                                  |      |\n| 023 数字传输系统                                  |      |\n| 024 宽带接入技术                                  |      |\n|                                                   |      |\n| **第3章 数据链路层**                              |      |\n| 025 数据链路层几个基本概念                        |      |\n| 026 三个基本问题                                  |      |\n| 027 CRC差错检测技术                               |      |\n| 028 PPP协议介绍                                   |      |\n| 029 PPP协议实现透明传输的2种方法                  |      |\n| 030 PPP协议的工作状态                             |      |\n| 031 配置路由器接口使用PPP协议封装                 |      |\n| 032 回顾                                          |      |\n| 033 以太网特点                                    |      |\n| 034 CSMA CD                                       |      |\n| 035 以太网冲突检测和避让机制                      |      |\n| 036 以太网                                        |      |\n| 037 集线器                                        |      |\n| 038 无冲突时以太网信道最大利用率                  |      |\n| 039 以太网MAC地址                                 |      |\n| 040 回顾                                          |      |\n| 041 更改和查看MAC地址                             |      |\n| 042 以太网帧格式                                  |      |\n| 043 抓包工具排除网络故障                          |      |\n| 044 以太网帧格式2                                 |      |\n| 045 网桥和交换机优化以太网                        |      |\n| 046 查看交换机ＭＡＣ地址表                        |      |\n| 047 生成树过程                                    |      |\n| 048 更改交换机生成树的根 优先级                   |      |\n| 049 远程重启服务器                                |      |\n| 050 验证VLAN                                      |      |\n| 051 VLAN干道链路和访问链路                        |      |\n| 052 扩展以太网                                    |      |\n| 053 交换机上实现的接入安全                        |      |\n|                                                   |      |\n| **第4章 网络层**                                  |      |\n| 054 网络的两种服务 虚电路和数据报服务             |      |\n| 055 虚电路和数据报服务的对比                      |      |\n| 056 虚拟互联网                                    |      |\n| 057 IP地址 网络部分和主机部分                     |      |\n| 058 IP地址 ABCDE类IP地址                          |      |\n| 059 IP地址 十进制和二进制关系                     |      |\n| 060 IP地址 ABCD类网络和默认子网掩码               |      |\n| 061 IP地址 保留的地址                             |      |\n| 062 子网掩码的作用                                |      |\n| 063 IP地址 将一个C类网络等分成两个子网            |      |\n| 064 IP地址 将一个C类网络等分成四个子网            |      |\n| 065 IP地址 将一个C类网络等分成八个子网            |      |\n| 066 IP地址 点到点网络的子网掩码最好是252          |      |\n| 067 IP地址 划分子网的规律                         |      |\n| 068 IP地址 变长子网划分                           |      |\n| 069 IP地址 变长子网练习                           |      |\n| 070 IP地址 子网划分回顾                           |      |\n| 071 IP地址 B类网络子网划分                        |      |\n| 072 IP地址 利用超网合并网段                       |      |\n| 073 数据包转发过程 MAC地址和IP地址                |      |\n| 074 基于MAC地址控制代理服务器只能控制本网段计算机 |      |\n| 075 ARP协议工作机制                               |      |\n| 076 arp欺骗的应用                                 |      |\n| 077 如何断定ARP欺骗                               |      |\n| 078 逆向ARP 就是计算机请求IP地址的过程            |      |\n| 079 数据包首部                                    |      |\n| 080 数据包首部生存时间TTL                         |      |\n| 081 数据包首部 首部校验和                         |      |\n| 082 通过抓包工具分析数据包首部                    |      |\n| 083 网络畅通的条件和静态路由                      |      |\n| 084 添加静态路由                                  |      |\n| 085 Windows网关就是默认路由                       |      |\n| 086 网络负载均衡                                  |      |\n| 087 ICMP协议ping和pathping                        |      |\n| 088 RIP协议工作原理                               |      |\n| 089 配置动态路由RIP协议                           |      |\n| 090 回顾                                          |      |\n| 091 配置OSPF协议                                  |      |\n| 092 OSPF协议特点                                  |      |\n| 093 BGP协议实现自制系统之间最佳路径选择           |      |\n| 094 VPN的功能                                     |      |\n| 095 验证VPN拨号                                   |      |\n| 096 创建VPN拨号连接                               |      |\n| 097 站点间VPN                                     |      |\n| 098 NAT和PAT                                      |      |\n| 099 NAT的端口映射                                 |      |\n| 100 回顾                                          |      |\n| 101 虚拟机的网络设置                              |      |\n|                                                   |      |\n| **第5章 传输层**                                  |      |\n| 102 TCPIP协议分层                                 |      |\n| 103 TCP和UDP的应用场景                            |      |\n| 104 传输层协议和应用层协议之间的关系              |      |\n| 105 服务和应用层协议之间的关系                    |      |\n| 106 安装telnet客户端                              |      |\n| 107 更改服务器的默认端口                          |      |\n| 108 TCPIP筛选实现服务器安全                       |      |\n| 109 Windows防火墙的作用                           |      |\n| 110 Windows防火墙不能防控灰鸽子木马程序           |      |\n| 111 IPSec严格控制网络流量                         |      |\n| 112 UDP协议                                       |      |\n| 113 TCP协议概述                                   |      |\n| 114 TCP如何实现可靠传输                           |      |\n| 115 TCP首部                                       |      |\n| 116 抓包分析TCP首部                               |      |\n| 117 TCP首部标记位                                 |      |\n| 118 接收窗口确定发送窗口                          |      |\n| 119 TCP滑动窗口技术实现可靠传输                   |      |\n| 120 避免拥塞                                      |      |\n| 121 TCP连接管理                                   |      |\n|                                                   |      |\n| **第6章 应用层**                                  |      |\n| 122 域名解析过程                                  |      |\n| 123 安装和配置DNS服务器                           |      |\n| 124 DHCP协议                                      |      |\n| 125 跨网段分配IP地址                              |      |\n| 126 FTP协议主动模式和被动模式                     |      |\n| 127 安装和配置FTP服务器                           |      |\n| 128 Telnet协议                                    |      |\n| 129 远程桌面协议RDP                               |      |\n| 130 HTTP协议                                      |      |\n| 131 Web代理服务器                                 |      |\n| 132 Internet发送和接收电子邮件的过程              |      |\n| 133 准备邮件服务器实验环境                        |      |\n| 134 安装和配置邮件服务器                          |      |\n|                                                   |      |\n| **第7章 网络安全**                                |      |\n| 135 网络安全介绍                                  |      |\n| 136 网络安全面临的4种威胁                         |      |\n| 137 篡改和伪造攻击                                |      |\n| 138 病毒和木马                                    |      |\n| 139 对称加密                                      |      |\n| 140 非对称加密和数字签名                          |      |\n| 141 证书颁发机构CA                                |      |\n| 142 安装CA并申请证书和邮箱                        |      |\n| 143 发送签名和加密电子邮件                        |      |\n| 144 安全套接字SSL功能                             |      |\n| 145 配置网站使用SSL通信                           |      |\n| 146 网络层安全IPSec介绍                           |      |\n| 147 搭建IPSec实验环境                             |      |\n| 148 配置IPSec实现网络安全                         |      |\n| 149 数据链路层安全                                |      |\n|                                                   |      |\n| **第8章 Internet上的音频视频**                    |      |\n| 150 互联网音频视频面临的问题                      |      |\n| 151 安装流媒体服务                                |      |\n| 152 通过网站观看流媒体节目                        |      |\n| 153 流媒体实现现场直播                            |      |\n| 154 IP电话                                        |      |\n|                                                   |      |\n| **第9章 无线网络**                                |      |\n| 155 无线局域网                                    |      |\n| 156 创建临时无线网络                              |      |"
  },
  {
    "path": "course/15 咕泡学院-公开课.md",
    "content": "## 咕泡学院 公开课\n\n| 章节                                                         | 记录 |\n| ------------------------------------------------------------ | ---- |\n| 2018_03_05如何从源码copy经验并武装自己                       |      |\n| 2018_03_07面试Mysql底层要准备这些                            |      |\n| 2018_04_11_深入浅出JVM                                       |      |\n| 2018_04_18_轻松搞定面试中各种并发问题                        |      |\n| 2018_04_20_魔鬼式分析NIO通信架构原理                         |      |\n| 2018_04_23_垂直打击之JVM剖析                                 |      |\n| 2018_05_08(下午)_一张图完美还原HashMap(JDK1.8)源码           |      |\n| 2018_05_08_434_BAT面试必问的JVM到底怎么学                    |      |\n| 2018_05_11_阿里面试必备之分析IO及NIO的底层原理               |      |\n| 2018_05_13_千万级流量的优化策略实战                          |      |\n| 2018_05_14_漫谈Mysql索引                                     |      |\n| 2018_05_16_synchronized的底层原理分析                        |      |\n| 2018_05_23面试必问之JVM                                      |      |\n| 2018_05_28_一小时搞定阿里核心面试题                          |      |\n| Jack_20180427_今晚谁再说看不懂HashMap源码算我输              |      |\n| Jack_20180430_后来的我们学会了这样看源码                     |      |\n| Jack_20180501_Tomcat8源码邀你乘坐五一假期末班车              |      |\n| Jack_20180504_Professional Analysis of NIO                   |      |\n| Jack_20180511_阿里面试必备之分析IO及NIO的底层原理            |      |\n| James_0430高效程序员如何优雅落地需求                         |      |\n| James_0506程序员的职业生涯及面试指南                         |      |\n| James_0523面试必问之JVM                                      |      |\n| James_0719Mysql底层索引剖析                                  |      |\n| Mic_20180428_分布式服务Dubbo的前世今生                       |      |\n| Mic_20180502_深入分析https的底层原理                         |      |\n| Mic_20180718_分布式集群及动态负载均衡实战                    |      |\n| Tom_20180429_以ElasticSearch为例，论学习方法的重要性         |      |\n| Tom_20180503_巧用工具，解放双手，架构师都是这样思考问题的    |      |\n| Tom_20180720_一口气写完Spring核心代码，不信你就来            |      |\n| 咕泡学院 - Java 9 异步并发编程                               |      |\n| 咕泡学院 - Java 9 新特性之核心库（上）                       |      |\n| 咕泡学院 - Java 9 新特性之核心库（下）                       |      |\n| 咕泡学院 - Reactor Streams 并发编程之 Reactor                |      |\n| 咕泡学院 - 微服务实践之路                                    |      |\n| 咕泡学院 - 深入浅出 Spring Boot 日志                         |      |\n| 咕泡学院 - 深入浅出 Spring Boot 管控                         |      |\n| 大神带你剖析Mysql索引底层数据结构                            |      |\n|                                                              |      |\n| 20180803_理解 Spring 技术栈或许没有那么难                    |      |\n| Mic_20180802_并发编程的原理                                  |      |\n| Tom_20180801_不修改代码、不新增配置和，1小时快速搭建可视化监控系统 |      |\n| Tom_20180805_10W条记录，只需0.1秒快速搜索到附近的人          |      |\n"
  },
  {
    "path": "course/16 咕泡学院-JAVA开发中的热点技术剖析.md",
    "content": "## 咕泡学院 JAVA开发中的热点技术剖析\n\n| 章节                                            | 记录 |\n| ----------------------------------------------- | ---- |\n| **PART1**                                       |      |\n| Bad smell 内存泄露和内存溢出                    |      |\n| JVM难吗？听听一本正经的James老师怎么说          |      |\n| mybatis源码分析及手写实现                       |      |\n| 爱一个人需要一万个理由，学习Nginx只需要一个     |      |\n|                                                 |      |\n| **PART2**                                       |      |\n| 分布式环境下session跨域共享原理                 |      |\n| 带你装B带你飞，手把手教你学docker               |      |\n| 纯手写系列之Dubbo RPC实现原理                   |      |\n|                                                 |      |\n| **PART3**                                       |      |\n| 分布式全局ID生成器介绍                          |      |\n| 深入底层解读网络通信                            |      |\n| 高阶程序员必须要知道的多线程并发编程            |      |\n|                                                 |      |\n| **PART4**                                       |      |\n| 深入分析ThreadLocal的底层实现原理               |      |\n| 深入分析动态代理的实现原理                      |      |\n| 深入分析热部署底层原理                          |      |\n|                                                 |      |\n| **PART5**                                       |      |\n| 一道面试题引发的思考                            |      |\n| 做一个合格的程序员你必须掌握的linux性能工具集锦 |      |\n| 系统设计方法论之监控系统设计                    |      |\n| 系统设计方法论初探之Redis实现监控系统设计       |      |"
  },
  {
    "path": "course/17 咕泡学院-BAT面试课程.md",
    "content": "## 咕泡学院 BAT面试课程\n\n| 章节                                                     | 记录 |\n| -------------------------------------------------------- | ---- |\n| 7.1 如何控制多线程执行顺序                               |      |\n| 7.2 深入底层研究volatile和synchronized的区别             |      |\n| 7.3 深入底层lock接口和synchronized的区别和优势           |      |\n| 7.4 多线程并发编程的总结和梳理                           |      |\n| 7.5 分布式锁的实现原理                                   |      |\n| 7.6 剖析一道不简单的面试题                               |      |\n| 7.7 图解阻塞io和非阻塞io及多路复用机制                   |      |\n| 7.8 mysql中binlog的底层原理分析                          |      |\n| 7.9 cookie和session的联系和区别                          |      |\n| 7.10 tom_面试题_aop的底层实现原理，动态代理的动如何体现  |      |\n| 7.11 tom_面试题_javaweb开发中解决跨域问题的n种方案       |      |\n| 7.12 tom_面试题_从数据库底层来分析事务的提交与回滚全过程 |      |\n| 7.13 james_vip_要不要跳槽，怎么成长                      |      |\n| 7.14 小马哥-技术人成长之路（上）                         |      |\n| 7.15 小马哥-技术人成长之路（下）                         |      |"
  },
  {
    "path": "course/18 咕泡学院-性能优化.md",
    "content": "## 咕泡学院 性能优化\n\n| 章节                              | 记录 |\n| --------------------------------- | ---- |\n| 1.1 性能优化专题-什么是性能优化一 |      |\n| 1.1 性能优化专题-什么是性能优化二 |      |\n| 2.1 性能测试与优化（一）          |      |\n| 2.1 性能测试与优化（二）          |      |\n| 3.1 jvm介绍 一                    |      |\n| 3.1 jvm介绍 二                    |      |\n| 3.2 jvm垃圾回收一                 |      |\n| 3.2 jvm垃圾回收二                 |      |\n| 3.3 jvm案例分析一                 |      |\n| 3.3 jvm案例分析二                 |      |\n| 4.1 tomcat 架构（上）             |      |\n| 4.1 tomcat 架构（下）             |      |\n| 4.2 嵌入式 tomcat（下）           |      |\n| 4.2 嵌入式 tomcat（上）           |      |\n| 4.3 tomcat 性能优化（下）         |      |\n| 4.3 tomcat 性能优化（中）         |      |\n| 4.3 tomcat 性能优化（上）         |      |\n| 5.1 性能优化之mysql介绍一         |      |\n| 5.1 性能优化之mysql介绍二         |      |\n| 5.2 性能优化之mysql优化一         |      |\n| 5.2 性能优化之mysql优化二         |      |"
  },
  {
    "path": "course/19 并发编程原理.md",
    "content": "## 《Java并发编程原理与实战》学习记录\n官网：[【龙果学院】Java并发编程原理与实战](https://www.roncoo.com/course/view/b6f89747a8284f44838b2c4da6c8677b)\n\n| 课程大纲                                                   | 记录 |\n| ---------------------------------------------------------- | ---- |\n| 1 你真的了解并发吗？                                       |      |\n| 2 理解多线程与并发的之间的联系与区别                       |      |\n| 3 解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 |      |\n| 4 学习并发的四个阶段并推荐学习并发的资料                   |      |\n| 5 线程的状态以及各状态之间的转换详解                       |      |\n| 6 线程的初始化，中断以及其源码讲解                         |      |\n| 7 多种创建线程的方式案例演示（一）带返回值的方式           |      |\n| 8 多种创建线程的方式案例演示（二）使用线程池               |      |\n| 9 Spring对并发的支持：Spring的异步任务                     |      |\n| 10 使用jdk8提供的lambda进行并行计算                        |      |\n| 11 了解多线程所带来的安全风险                              |      |\n| 12 从线程的优先级看饥饿问题                                |      |\n| 13 从Java字节码的角度看线程安全性问题                      |      |\n| 14 synchronized保证线程安全的原理（理论层面）              |      |\n| 15 synchronized保证线程安全的原理（jvm层面）               |      |\n| 16 单例问题与线程安全性深入解析                            |      |\n| 17 理解自旋锁，死锁与重入锁                                |      |\n| 18 深入理解volatile原理与使用                              |      |\n| 19 JDK5提供的原子类的操作以及实现原理                      |      |\n| 20 Lock接口认识与使用                                      |      |\n| 21 手动实现一个可重入锁                                    |      |\n| 22 AbstractQueuedSynchronizer(AQS)详解                     |      |\n| 23 使用AQS重写自己的锁                                     |      |\n| 24 重入锁原理与演示                                        |      |\n| 25 读写锁认识与原理                                        |      |\n| 26 细读ReentrantReadWriteLock源码                          |      |\n| 27 ReentrantReadWriteLock锁降级详解                        |      |\n| 28 线程安全性问题简单总结                                  |      |\n| 29 线程之间的通信之wait/notify                             |      |\n| 30 通过生产者消费者模型理解等待唤醒机制                    |      |\n| 31 Condition的使用及原理解析                               |      |\n| 32 使用Condition重写wait/notify案例并实现一个有界队列      |      |\n| 33 深入解析Condition源码                                   |      |\n| 34 实战：简易数据连接池                                    |      |\n| 35 线程之间通信之join应用与实现原理剖析                    |      |\n| 36 ThreadLocal 使用及实现原理                              |      |\n| 37 并发工具类CountDownLatch详解                            |      |\n| 38 并发工具类CyclicBarrier 详解                            |      |\n| 39 并发工具类Semaphore详解                                 |      |\n| 40 并发工具类Exchanger详解                                 |      |\n| 41 CountDownLatch,CyclicBarrier,Semaphore源码解析          |      |\n| 42 提前完成任务之FutureTask使用                            |      |\n| 43 Future设计模式实现（实现类似于JDK提供的Future）         |      |\n| 44 Future源码解读                                          |      |\n| 45 Fork/Join框架详解                                       |      |\n| 46 同步容器与并发容器                                      |      |\n| 47 并发容器CopyOnWriteArrayList原理与使用                  |      |\n| 48 并发容器ConcurrentLinkedQueue原理与使用                 |      |\n| 49 Java中的阻塞队列原理与使用                              |      |\n| 50 实战：简单实现消息队列                                  |      |\n| 51 并发容器ConcurrentHashMap原理与使用                     |      |\n| 52 线程池的原理与使用                                      |      |\n| 53 Executor框架详解                                        |      |\n| 54 实战：简易web服务器（一）                               |      |\n| 55 实战：简易web服务器（二）                               |      |\n| 56 JDK8的新增原子操作类LongAddr原理与使用                  |      |\n| 57 JDK8新增锁StampedLock详解                               |      |\n| 58 重排序问题                                              |      |\n| 59 happens-before简单概述                                  |      |\n| 60 锁的内存语义                                            |      |\n| 61 volatile内存语义                                        |      |\n| 62 final域的内存语义                                       |      |\n| 63 实战：问题定位                                          |      |"
  },
  {
    "path": "course/20 MySQL性能管理及架构设计.md",
    "content": "## 《MySQL性能管理及架构设计》学习记录\n\n| 章节                                                         | 记录 |\n| ------------------------------------------------------------ | ---- |\n| **第1章 实例和故事**                                         |      |\n| *决定电商11大促成败的各个关键因素。*                         |      |\n|                                                              |      |\n| 1-1 什么决定了电商双11大促的成败                             |      |\n| 1-2 在双11大促中的数据库服务器                               |      |\n| 1-3 在大促中什么影响了数据库性能                             |      |\n| 1-4 大表带来的问题                                           |      |\n| 1-5 大事务带来的问题                                         |      |\n|                                                              |      |\n| **第2章 什么影响了MySQL性能**                                |      |\n| *详细介绍影响性能各个因素，包括硬件、操作系统等等。*         |      |\n|                                                              |      |\n| 2-1 影响性能的几个方面                                       |      |\n| 2-2 CPU资源和可用内存大小                                    |      |\n| 2-3 磁盘的配置和选择                                         |      |\n| 2-4 使用RAID增加传统机器硬盘的性能                           |      |\n| 2-5 使用固态存储SSD或PCIe卡                                  |      |\n| 2-6 使用网络存储SAN和NAS                                     |      |\n| 2-7 总结：服务器硬件对性能的影响                             |      |\n| 2-8 操作系统对性能的影响-MySQL适合的操作系统                 |      |\n| 2-9 CentOS系统参数优化                                       |      |\n| 2-10 文件系统对性能的影响                                    |      |\n| 2-11 MySQL体系结构                                           |      |\n| 2-12 MySQL常用存储引擎之MyISAM                               |      |\n| 2-13 MySQL常用存储引擎之Innodb                               |      |\n| 2-14 Innodb存储引擎的特性(1)                                 |      |\n| 2-15 Innodb存储引擎的特性(2)                                 |      |\n| 2-16 MySQL常用存储引擎之CSV                                  |      |\n| 2-17 MySQL常用存储引擎之Archive                              |      |\n| 2-18 MySQL常用存储引擎之Memory                               |      |\n| 2-19 MySQL常用存储引擎之Federated                            |      |\n| 2-20 如何选择存储引擎                                        |      |\n| 2-21 MySQL服务器参数介绍                                     |      |\n| 2-22 内存配置相关参数                                        |      |\n| 2-23 IO相关配置参数                                          |      |\n| 2-24 安全相关配置参数                                        |      |\n| 2-25 其它常用配置参数                                        |      |\n| 2-26 数据库设计对性能的影响                                  |      |\n| 2-27 总结                                                    |      |\n|                                                              |      |\n| **第3章 MySQL基准测试**                                      |      |\n| *了解基准测试,MySQL基准测试工具介绍及实例演示。*             |      |\n|                                                              |      |\n| 3-1 什么是基准测试                                           |      |\n| 3-2 如何进行基准测试                                         |      |\n| 3-3 基准测试演示实例                                         |      |\n| 3-4 Mysql基准测试工具之mysqlslap                             |      |\n| 3-5 Mysql基准测试工具之sysbench                              |      |\n| 3-6 sysbench基准测试演示实例                                 |      |\n|                                                              |      |\n| **第4章 MySQL数据库结构优化**                                |      |\n| *详细介绍数据库结构设计、范式和反范式设计、物理设计等等。*   |      |\n|                                                              |      |\n| 4-1 数据库结构优化介绍                                       |      |\n| 4-2 数据库结构设计                                           |      |\n| 4-3 需求分析及逻辑设计                                       |      |\n| 4-4 需求分析及逻辑设计-反范式化设计                          |      |\n| 4-5 范式化设计和反范式化设计优缺点                           |      |\n| 4-6 物理设计介绍                                             |      |\n| 4-7 物理设计-数据类型的选择                                  |      |\n| 4-8 物理设计-如何存储日期类型                                |      |\n| 4-9 物理设计-总结                                            |      |\n|                                                              |      |\n| **第5章 MySQL高可用架构设计**                                |      |\n| *详细介绍二进制日志及其对复制的影响、GTID的复制、MMM、MHA等等。* |      |\n|                                                              |      |\n| 5-1 mysql复制功能介绍                                        |      |\n| 5-2 mysql二进制日志                                          |      |\n| 5-3 mysql二进制日志格式对复制的影响                          |      |\n| 5-4 mysql复制工作方式                                        |      |\n| 5-5 基于日志点的复制                                         |      |\n| 5-6 基于GTID的复制                                           |      |\n| 5-7 MySQL复制拓扑                                            |      |\n| 5-8 MySQL复制性能优化                                        |      |\n| 5-9 MySQL复制常见问题处理                                    |      |\n| 5-10 什么是高可用架构                                        |      |\n| 5-11 MMM架构介绍                                             |      |\n| 5-12 MMM架构实例演示（上）                                   |      |\n| 5-13 MMM架构实例演示（下）                                   |      |\n| 5-14 MMM架构的优缺点                                         |      |\n| 5-15 MHA架构介绍                                             |      |\n| 5-16 MHA架构实例演示(1)                                      |      |\n| 5-17 MHA架构实例演示(2)                                      |      |\n| 5-18 MHA架构优缺点                                           |      |\n| 5-19 读写分离和负载均衡介绍                                  |      |\n| 5-20 MaxScale实例演示                                        |      |\n|                                                              |      |\n| **第6章 数据库索引优化**                                     |      |\n| *介绍BTree索引和Hash索引，详细介绍索引的优化策略等等。*      |      |\n|                                                              |      |\n| 6-1 Btree索引和Hash索引                                      |      |\n| 6-2 安装演示数据库                                           |      |\n| 6-3 索引优化策略（上）                                       |      |\n| 6-4 索引优化策略（中）                                       |      |\n| 6-5 索引优化策略（下）                                       |      |\n|                                                              |      |\n| **第7章 SQL查询优化**                                        |      |\n| *详细介绍慢查询日志及示例演示,MySQL查询优化器介绍及特定SQL的查询优化等。* |      |\n|                                                              |      |\n| 7-1 获取有性能问题SQL的三种方法                              |      |\n| 7-2 慢查询日志介绍                                           |      |\n| 7-3 慢查询日志实例                                           |      |\n| 7-4 实时获取性能问题SQL                                      |      |\n| 7-5 SQL的解析预处理及生成执行计划                            |      |\n| 7-6 如何确定查询处理各个阶段所消耗的时间                     |      |\n| 7-7 特定SQL的查询优化                                        |      |\n|                                                              |      |\n| **第8章 数据库的分库分表**                                   |      |\n| *详细介绍数据库分库分表的实现原理及演示案例等。*             |      |\n|                                                              |      |\n| 8-1 数据库分库分表的几种方式                                 |      |\n| 8-2 数据库分片前的准备                                       |      |\n| 8-3 数据库分片演示(上)                                       |      |\n| 8-4 数据库分片演示(下)                                       |      |\n|                                                              |      |\n| **第9章 数据库监控**                                         |      |\n| *介绍数据库可用性监控、性能监控、MySQL主从复制监控等*        |      |\n|                                                              |      |\n| 9-1 数据库监控介绍                                           |      |\n| 9-2 数据库可用性监控                                         |      |\n| 9-3 数据库性能监控                                           |      |\n| 9-4 MySQL主从复制监控                                        |      |"
  },
  {
    "path": "course/21 JVM.md",
    "content": "## 《深入理解Java虚拟机》学习记录\n\n[深入理解Java虚拟机（jvm性能调优+内存模型+虚拟机原理）](https://www.roncoo.com/course/view/0ec92a81e8764b838e86c4febe7a5b17)\n\n\n| 章节                                                 | 记录 |\n| ---------------------------------------------------- | ---- |\n| 1 说在前面的话                                       |      |\n| 2 整个部分要讲的内容说明                             |      |\n| 3 环境搭建以及jdk,jre,jvm的关系                      |      |\n| 4 jvm初体验-内存溢出问题的分析与解决                 |      |\n| 5 jvm再体验-jvm可视化监控工具                        |      |\n| 6 杂谈                                               |      |\n| 7 Java的发展历史                                     |      |\n| 8 Java的发展历史续                                   |      |\n| 9 Java技术体系                                       |      |\n| 10 jdk8的新特性                                      |      |\n| 11 lanmbda表达式简介                                 |      |\n| 12 Java虚拟机-classic vm                             |      |\n| 13 Java虚拟机-ExactVM                                |      |\n| 14 Java虚拟机-HotSpotVM                              |      |\n| 15 Java虚拟机-kvm                                    |      |\n| 16 Java虚拟机-JRockit                                |      |\n| 17 Java虚拟机-j9                                     |      |\n| 18 Java虚拟机-dalvik                                 |      |\n| 19 Java虚拟机-MicrosoftJVM                           |      |\n| 20 Java虚拟机-高性能Java虚拟机                       |      |\n| 21 Java虚拟机-TaobaoVM                               |      |\n| 22 Java内存区域-简介                                 |      |\n| 23 Java内存区域-Java虚拟机栈                         |      |\n| 24 Java内存区域-程序计数器                           |      |\n| 25 Java内存区域-本地方法栈                           |      |\n| 26 Java内存区域-堆内存                               |      |\n| 27 Java内存区域-方法区                               |      |\n| 28 Java内存区域-直接内存和运行时常量池               |      |\n| 29 对象在内存中的布局-对象的创建                     |      |\n| 30 探究对象的结构                                    |      |\n| 31 深入理解对象的访问定位                            |      |\n| 32 垃圾回收-概述                                     |      |\n| 33 垃圾回收-判断对象是否存活算法-引用计数法详解      |      |\n| 34 垃圾回收-判断对象是否存活算法-可达性分析法详解    |      |\n| 35 垃圾回收算法-标记清除算法                         |      |\n| 36 垃圾回收算法-复制算法                             |      |\n| 37 垃圾回收算法-标记整理算法和分代收集算法           |      |\n| 38 垃圾收集器-serial收集器详解                       |      |\n| 39 垃圾收集器-parnew收集器详解                       |      |\n| 40 垃圾收集器-parallel收集器详解                     |      |\n| 41 垃圾收集器-cms收集器详解                          |      |\n| 42 最牛的垃圾收集器-g1收集器详解                     |      |\n| 43 内存分配-概述                                     |      |\n| 44 内存分配-Eden区域                                 |      |\n| 45 内存分配-大对象直接进老年代                       |      |\n| 46 内存分配-长期存活的对象进入老年代                 |      |\n| 47 内存分配-空间分配担保                             |      |\n| 48 内存分配-逃逸分析与栈上分配                       |      |\n| 49 虚拟机工具介绍                                    |      |\n| 50 虚拟机工具-jps详解                                |      |\n| 51 虚拟机工具-jstat详解                              |      |\n| 52 虚拟机工具-jinfo详解                              |      |\n| 53 虚拟机工具-jmap详解                               |      |\n| 54 虚拟机工具-jhat详解                               |      |\n| 55 虚拟机工具-jstack详解                             |      |\n| 56 可视化虚拟机工具-Jconsole内存监控                 |      |\n| 57 可视化虚拟机工具-Jconsole线程监控                 |      |\n| 58 死锁原理以及可视化虚拟机工具-Jconsole线程死锁监控 |      |\n| 59 VisualVM使用详解                                  |      |\n| 60 性能调优概述                                      |      |\n| 61 性能调优-案例1                                    |      |\n| 62 性能调优-案例2                                    |      |\n| 63 性能调优-案例3                                    |      |\n| 64 前半部分内容整体回顾                              |      |\n| 65 Class文件简介和发展历史                           |      |\n| 66 Class文件结构概述                                 |      |\n| 67 Class文件设计理念以及意义                         |      |\n| 68 文件结构-魔数                                     |      |\n| 69 文件结构-常量池                                   |      |\n| 70 文件结构-访问标志                                 |      |\n| 71 文件结构-类索引                                   |      |\n| 72 文件结构-字段表集合                               |      |\n| 73 文件结构-方法表集合                               |      |\n| 74 文件结构-属性表集合                               |      |\n| 75 字节码指令简介                                    |      |\n| 76 字节码与数据类型                                  |      |\n| 77 加载指令                                          |      |\n| 78 运算指令                                          |      |\n| 79 类型转换指令                                      |      |\n| 80 对象创建与访问指令                                |      |\n| 81 操作树栈指令                                      |      |\n| 82 控制转移指令                                      |      |\n| 83 方法调用和返回指令                                |      |\n| 84 异常处理指令                                      |      |\n| 85 同步指令                                          |      |\n| 86 类加载机制概述                                    |      |\n| 87 类加载时机                                        |      |\n| 88 类加载的过程-加载                                 |      |\n| 89 类加载的过程-验证                                 |      |\n| 90 类加载的过程-准备                                 |      |\n| 91 类加载的过程-解析                                 |      |\n| 92 类加载的过程-初始化                               |      |\n| 93 类加载器                                          |      |\n| 94 双亲委派模型                                      |      |\n| 95 运行时栈帧结构                                    |      |\n| 96 局部变量表                                        |      |\n| 97 操作数栈                                          |      |\n| 98 动态连接                                          |      |\n| 99 方法返回地址和附加信息                            |      |\n| 100 方法调用-解析调用                                |      |\n| 101 方法调用-静态分派调用                            |      |\n| 102 方法调用-动态分派调用                            |      |\n| 103 动态类型语言支持                                 |      |\n| 104 字节码执行引擎小结                               |      |\n| 105 总结与回顾                                       |      |\n| 106 happens-before简单概述                           |      |\n| 107 重排序问题                                       |      |\n| 108 锁的内存语义                                     |      |\n| 109 volatile的内存语义                               |      |\n| 110 final域内存语义                                  |      |"
  },
  {
    "path": "course/22 白鹤翔_JVM虚拟机优化.md",
    "content": "## 尚学堂-白鹤翔_jvm虚拟机优化 学习记录\n| 章节 | 记录 |\n| ------------------------------- | ------------------------------- |\n| 1 jvm_虚拟机概念1              |               |\n| 2 jvm_虚拟机概念2               |                |\n| 3 jvm_虚拟机组成部分概述        |         |\n| 4 jvm_堆栈永久区详细讲解        |         |\n| 5 jvm_虚拟机参数讲解（一）1    |     |\n| 6 jvm_虚拟机参数讲解（一）2    |     |\n| 7 jvm_虚拟机参数讲解（二）1    |     |\n| 8 jvm_虚拟机参数讲解（二）2    |     |\n| 9 jvm_虚拟机参数讲解（三）1    |     |\n| 10 jvm_虚拟机参数讲解（三）2   |    |\n| 11 jvm_虚拟机参数讲解（三）3   |    |\n| 12 jvm_垃圾收集算法讲解（一）1 |  |\n| 13 jvm_垃圾收集算法讲解（一）2 |  |\n| 14 jvm_垃圾收集算法讲解（二）   |    |\n| 15 jvm_关于一些编辑器工具使用   |    |\n| 16 虚拟机介绍                   |                    |\n| 17 垃圾回收                     |                      |"
  },
  {
    "path": "course/23 MySQL大型分布式集群.md",
    "content": "## 《MySQL大型分布式集群》 学习记录\n\n官网：[【龙果学院】MySQL大型分布式集群](https://www.roncoo.com/course/view/658088f6e77541f5835b61800314083e)\n\n### 课程介绍\n\n本套课程将通过分布式集群和分库分表两部分内容进行讲解\n\n1. 主要解决针对大型网站架构中持久化部分中，大量数据存储以及高并发访问所带来是数据读写问题。分布式是将一个业务拆分为多个子业务，部署在不同的服务器上。集群是同一个业务，部署在多个服务器上。\n\n2. 着重对数据切分做了细致丰富的讲解，从数据切分的原理出发，一步一步深入理解数据的切分，通过深入理解各种切分策略来设计和优化我们的系统。这部分中我们还用到了数据库中间件和客户端组件来进行数据的切分，让广大网友能够对数据的切分从理论到实战都会有一个质的飞跃。\n\n学完本套课程以后能够达到的效果：\n\n　　期望通过本课程能帮助大家学习到如何通过分布式+集群的方式来提高io的吞吐量，以及数据库的主从复制，主主复制，负载均衡，高可用，分库分表以及数据库中间件的使用。希望能够帮助大家更加清楚了解架构的工作模式，从而写出更高质量的代码。对于企业的架构人员可以优化企业架构。对于兴趣爱好者，可以作为一个很好的入门。\n\n　　课程讲解过程中尽可能用简单的语言描述其中的原理，通过实例来帮助初学者快速上手。案例中代码全部手写，实例全部现场真实环境演示。\n\n　　教程样例项目中用到的技术及相应的环境\n\n　　MySQL5.7  CentOS6.9  Vmware  Spring3.x以上  JDK8  Maven XShell Xftp\n\n　　教程中所有的与编程相关均使用Java来进行演示，但与编程语言无关，可使用任何编程语言进行测试。  \n\n\n\n| 章节                                                 | 记录 |\n| ---------------------------------------------------- | ---- |\n| 1 课程概述                                           |      |\n| 2 课程背景                                           |      |\n| 3 纵观大型网站架构发展，总结持久化部分需要应对的问题 |      |\n| 4 操作系统安装以及配置                               |      |\n| 5 在CentOS上通过yum安装mysql5.7                      |      |\n| 6 mysql初次见面-mysql5.7的用户以及安全策略           |      |\n| 7 mysql初次见面续-mysql基本操作                      |      |\n| 8 认识主从复制                                       |      |\n| 9 主从复制的准备工作01-mysql用户以及权限             |      |\n| 10 主从复制的准备工作02-binlog日志详解               |      |\n| 11 主从实战01-准备环境                               |      |\n| 12 主从实战02-主节点配置                             |      |\n| 13 主从实战03-从节点配置                             |      |\n| 14 java操作主从01                                    |      |\n| 15 java操作主从02                                    |      |\n| 16 主主复制                                          |      |\n| 17 负载均衡概述以及环境准备                          |      |\n| 18 搭建负载均衡-01                                   |      |\n| 19 搭建负载均衡-02                                   |      |\n| 20 启动haproxy的监控功能                             |      |\n| 21 高可用以及环境准备                                |      |\n| 22 搭建keepalived                                    |      |\n| 23 Keepalived配置简介                                |      |\n| 24 Keepalived配置邮件                                |      |\n| 25 Keepalived其他配置                                |      |\n| 26 分库分表概述                                      |      |\n| 27 逻辑分表01-水平分表                               |      |\n| 28 逻辑分表02-水平分表续及垂直分表                   |      |\n| 29 表分区                                            |      |\n| 30 数据库中间件01-认识mycat                          |      |\n| 31 数据库中间件02-mycat安装                          |      |\n| 32 数据库中间件03-mycat的helloworld                  |      |\n| 33 数据库中间件04-mycat的初识                        |      |\n| 34 数据库中间件05-mycat的数据切分                    |      |\n| 35 数据库中间件06-mycat的读写分离-01                 |      |\n| 36 数据库中间件06-mycat的读写分离-02                 |      |\n| 37 数据库中间件06-mycat的读写分离03-读写分离补充     |      |\n| 38 数据库中间件07-mycat的高可用-01                   |      |\n| 39 数据库中间件08-mycat的高可用-02                   |      |\n| 40 数据库中间件09-mycat集群                          |      |\n| 41 mysql查询缓存                                     |      |\n| 42 数据库切分概述                                    |      |\n| 43 java环境配置                                      |      |\n| 44 水平切分原理及单表切分后的操作                    |      |\n| 45 水平切分原理及单表切分后的操作-2                  |      |\n| 46 水平切分多表关联操作                              |      |\n| 47 垂直切分原理及操作                                |      |\n| 48 全局序列号                                        |      |\n| 49 数据库切分策略-分片枚举                           |      |\n| 50 数据库切分策略-hash                               |      |\n| 51 数据库切分策略-范围约定                           |      |\n| 52 数据库切分策略-取模                               |      |\n| 53 数据库切分策略-按日期分片                         |      |\n| 54 全局表                                            |      |\n| 55 认识MyCat                                         |      |\n| 56 部署MyCat                                         |      |\n| 57 使用MyCat完成简单的数据库分片                     |      |\n| 58 MyCat分片策略                                     |      |\n| 59 MyCat全局表配置                                   |      |\n| 60 MyCatER表配置                                     |      |\n| 61 另外一种切分方式-使用客户端组件的方式实现数据库分 |      |\n| 62 课程总结                                          |      |\n\n"
  },
  {
    "path": "course/24 操作系统（清华大学）.md",
    "content": "## 操作系统_清华大学(向勇、陈渝) \n\n| 章节                                       | 记录 |\n| ------------------------------------------ | ---- |\n| 1.1 课程概述                               |      |\n| 1.2 什么是操作系统                         |      |\n| 1.3 为什么学习操作系统                     |      |\n| 1.4 如何学习操作系统                       |      |\n| 1.5 操作系统实例                           |      |\n| 1.6 操作系统的历史                         |      |\n| 1.7 操作系统结构                           |      |\n| 1.8 小结                                   |      |\n|                                            |      |\n| 2.1 操作系统的启动                         |      |\n| 2.2 操作系统的终端、异常、和系统调用       |      |\n|                                            |      |\n| 3.1 计算机体系结构及内存分层体系           |      |\n| 3.2 地址空间与地址生成                     |      |\n| 3.3 连续内存分配：内存碎片与分区的动态分配 |      |\n| 3.4 连续内存分配：压缩式与交换式碎片整理   |      |\n|                                            |      |\n| 4.1 非连续内存分配：分段                   |      |\n| 4.2 非连续内存分配：分页                   |      |\n| 4.3 非连续内存分配：页表－概述、TLB        |      |\n| 4.4 非连续内存分配：页表－二级，多级页表   |      |\n| 4.5 非连续内存分配：页表－反向页表         |      |\n|                                            |      |\n| 5.1 虚拟内存的起因                         |      |\n| 5.2 覆盖技术                               |      |\n| 5.3 交换技术                               |      |\n| 5.4 虚存技术（上）                         |      |\n| 5.5 虚存技术（下）                         |      |\n|                                            |      |\n| 6.1 最优页面置换算法                       |      |\n| 6.2 先进先出算法                           |      |\n| 6.3 最近最久未使用算法                     |      |\n| 6.4 时钟页面置换算法                       |      |\n| 6.5 二次机会法                             |      |\n| 6.6 最不常用法                             |      |\n| 6.7 Belady现象、LRU、FIFO、Clock的比较     |      |\n| 6.8 局部页替换算法的问题、工作集模型       |      |\n| 6.9 两个全局置换算法                       |      |\n| 6.10 抖动问题                              |      |\n|                                            |      |\n| 7.1 进程的定义                             |      |\n| 7.2 进程的组成                             |      |\n| 7.3 进程的特点                             |      |\n| 7.4 进程控制结构                           |      |\n| 7.5 进程的生命期原理                       |      |\n| 7.6 进程状态变化模型                       |      |\n| 7.7 进程挂起                               |      |\n| 7.8 为什么使用线程                         |      |\n| 7.9 什么是线程                             |      |\n| 7.10 线程的实现                            |      |\n| 7.11 上下文切换                            |      |\n| 7.12 进程控制——创建进程                    |      |\n| 7.13 进程控制——加载和执行进程              |      |\n| 7.14 进程控制——等待和终止进程              |      |\n|                                            |      |\n| 8.1 背景                                   |      |\n| 8.2 调度原则                               |      |\n| 8.3 调度算法1                              |      |\n| 8.4 调度算法2                              |      |\n| 8.5 实时调度                               |      |\n| 8.6 多处理器调度与优先级反转               |      |\n|                                            |      |\n| 9.1 背景知识                               |      |\n| 9.2 一些概念part1                          |      |\n| 9.3 一些概念part2                          |      |\n| 9.4 一些概念part3                          |      |\n| 9.5 临界区                                 |      |\n| 9.6 方法1：禁用硬件中断                    |      |\n| 9.7 方法2：基于软件的解决方案              |      |\n| 9.8 方法3：更高级的抽象                    |      |\n|                                            |      |\n| 10.1 背景                                  |      |\n| 10.2 信号量                                |      |\n| 10.3 信号量的使用                          |      |\n| 10.4 信号量的实现                          |      |\n| 10.5 管程                                  |      |\n| 10.6 经典同步问题-1                        |      |\n| 10.7 经典同步问题-2                        |      |\n| 10.8 经典同步问题-3                        |      |\n| 10.9 经典同步问题-4                        |      |\n| 10.10 经典同步问题-5                       |      |\n| 10.11 景点同步问题-6                       |      |\n|                                            |      |\n| 11.1 死锁问题                              |      |\n| 11.2 系统模型                              |      |\n| 11.3 死锁特征                              |      |\n| 11.4 死锁处理办法                          |      |\n| 11.5 死锁预防和死锁避免                    |      |\n| 11.6 银行家算法                            |      |\n| 11.7 死锁检测和死锁恢复                    |      |\n| 11.8 IPC概述                               |      |\n| 11.9 信号、管道、消息队列和共享内存        |      |\n|                                            |      |\n| 12.1 文件系统：总体介绍                    |      |\n| 12.2 基本概念                              |      |\n| 12.3 基本概念——文件系统和文件              |      |\n| 12.4 基本概念——文件系统的功能              |      |\n| 12.5 基本概念——文件和块                    |      |\n| 12.6 基本概念——文件描述符                  |      |\n| 12.7 基本概念——目录                        |      |\n| 12.8 基本概念——文件别名                    |      |\n| 12.9 基本概念——文件系统种类                |      |\n| 12.10 虚拟文件系统                         |      |\n| 12.11 数据缓存                             |      |\n| 12.12 打开文件的数据结构                   |      |\n| 12.13 文件分配                             |      |\n| 12.14 空闲空间列表                         |      |\n| 12.15 多磁盘管理-RAID                      |      |\n| 12.16 磁盘调度                             |      |\n\n"
  },
  {
    "path": "course/25 Linux达人养成计划 I.md",
    "content": "## 《Linux达人养成计划 I》学习记录\n\n时长：8小时58分钟\n\n| 章节                                       | 记录 |\n| ------------------------------------------ | ---- |\n| 1-1 Linux简介                              |      |\n| 1-2 开源软件简介                           |      |\n| 1-3 Linux应用领域                          |      |\n| 1-4 Linux学习方法                          |      |\n| 1-5 Linux与Windows的不同                   |      |\n| 1-6 字符界面的优势                         |      |\n|                                            |      |\n| 2-1 虚拟机的安装                           |      |\n| 2-2 虚拟机使用                             |      |\n| 2-3 系统分区之分区与格式化                 |      |\n| 2-4 分区之分区设备文件名与挂载             |      |\n| 2-5 Linux系统安装                          |      |\n| 2-6 XShell的安装和使用                     |      |\n|                                            |      |\n| 3-1 命令格式（命令基本格式及文件处理命令） |      |\n| 3-2 目录处理命令（上）                     |      |\n| 3-3 目录处理命令（下）                     |      |\n| 3-4 常见目录作用                           |      |\n| 3-5 链接命令                               |      |\n|                                            |      |\n| 4-1 文件搜索命令locate                     |      |\n| 4-2 命令搜索命令                           |      |\n| 4-3 find命令                               |      |\n| 4-4 grep命令                               |      |\n|                                            |      |\n| 5-1 帮助命令                               |      |\n| 5-2 其他帮助命令                           |      |\n|                                            |      |\n| 6-1 压缩命令1                              |      |\n| 6-2 压缩命令2                              |      |\n|                                            |      |\n| 7-1 关机与重启命令                         |      |\n|                                            |      |\n| 8-1 挂载命令                               |      |\n| 8-2 用户登录查看命令                       |      |\n|                                            |      |\n| 9-1 shell概述                              |      |\n| 9-2 脚本执行方式                           |      |\n| 9-3 别名与快捷键                           |      |\n| 9-4 历史命令                               |      |\n| 9-5 输出重定向                             |      |\n| 9-6 管道符                                 |      |\n| 9-7 通配符                                 |      |"
  },
  {
    "path": "course/26 Linux 达人养成计划 II.md",
    "content": "## 《Linux 达人养成计划 II》学习记录\n\n时长：2小时30分 \n\n| 章节                              | 记录 |\n| --------------------------------- | ---- |\n| 第1章 VIM文本编辑器               |      |\n| 1-1 VIM编辑器概述                 |      |\n| 1-2 VIM编辑器的操作模式           |      |\n| 1-3 VIM编辑器的命令模式           |      |\n| 1-4 底行模式和命令模式常用指令    |      |\n| 1-5 练习题                        |      |\n|                                   |      |\n| 第2章 磁盘管理                    |      |\n| 2-1 Linux 磁盘管理基本命令        |      |\n| 2-2 Linux 硬盘分区和格式化概述    |      |\n| 2-3 Linux 在VM虚拟机中添加硬盘    |      |\n| 2-4 Linux中MBR分区                |      |\n| 2-5 Linux中GPT分区                |      |\n| 2-6 Linux中分区的格式化           |      |\n| 2-7 Linux中挂载分区               |      |\n| 2-8 Linux中swap分区               |      |\n| 2-9 练习题                        |      |\n|                                   |      |\n| 第3章 用户管理                    |      |\n| 3-1 Linux中用户和用户组的概念     |      |\n| 3-2 Linux中用户和用户组的基本命令 |      |\n| 3-3 Linux中用户和用户组进阶命令   |      |\n| 3-4 Linux中用户管理其他命令       |      |"
  },
  {
    "path": "course/27 快速上手Linux 玩转典型应用.md",
    "content": "## 快速上手 Linux 玩转典型应用\n\n时长：10小时\n\n| 章节                                       | 记录 |\n| ------------------------------------------ | ---- |\n| 第1章 课程介绍                             |      |\n| 1-1 导学                                   |      |\n|                                            |      |\n| 第2章 Linux简介                            |      |\n| 2-1 什么是 Linux                           |      |\n| 2-2 Linux能够做什么事情                    |      |\n| 2-3 Linux的学习方法                        |      |\n| 2-4 忘掉 window的所有东西                  |      |\n|                                            |      |\n| 第3章 CentOs 的安装                        |      |\n| 3-1 虚拟机是什么                           |      |\n| 3-2 在虚拟机中安装CentOs                   |      |\n| 3-3 云服务器介绍                           |      |\n|                                            |      |\n| 第4章 准备工作                             |      |\n| 4-1 课前准备工作                           |      |\n|                                            |      |\n| 第5章 远程连接SSH专题                      |      |\n| 5-1 认识SSH                                |      |\n| 5-2 服务器安装SSH服务                      |      |\n| 5-3 客户端安装SSH工具                      |      |\n| 5-4 客户端链接SSH服务                      |      |\n| 5-5 SSH config 命令讲解                    |      |\n| 5-6 SSH免密登录                            |      |\n|                                            |      |\n| 第6章 Linux常用命令讲解                    |      |\n| 6-1 Linux常用命令                          |      |\n| 6-2 服务器硬件资源信息                     |      |\n| 6-3 文件操作命令                           |      |\n| 6-4 Linux文本编辑神器--VIM                 |      |\n| 6-5 系统用户操作命令                       |      |\n| 6-6 防火墙的设置                           |      |\n| 6-7 提权和文件上传下载的操作               |      |\n|                                            |      |\n| 第7章 WebServer安装和配置讲解              |      |\n| 7-1 Apache的安装                           |      |\n| 7-2 Apache的虚拟主机配置及伪静态操作       |      |\n| 7-3 Nginx的基本操作                        |      |\n| 7-4 Nginx伪静态的实现                      |      |\n| 7-5 实例演示反向代理和负载均衡             |      |\n|                                            |      |\n| 第8章 数据库服务                           |      |\n| 8-1 Mysql安装及链接                        |      |\n| 8-2 远程链接                               |      |\n| 8-3 开启genelog                            |      |\n|                                            |      |\n| 第9章 缓存服务                             |      |\n| 9-1 Redis、Memcache介绍及安装              |      |\n| 9-2 Redis的基本操作                        |      |\n|                                            |      |\n| 第10章 Git安装和使用                       |      |\n| 10-1 git版本管理工具(上)                   |      |\n| 10-2 git版本管理工具（下）                 |      |\n|                                            |      |\n| 第11章 Php框架TP5，Lavaral Yii2.0 环境配置 |      |\n| 11-1 PHP基础运行环境搭建                   |      |\n| 11-2 Laravel运行环境配置(上)               |      |\n| 11-3 Laravel运行环境配置(下)               |      |\n| 11-4 yii2.0 TP 5.0 框架配置                |      |\n| 11-5 PhpMyadmin的配置安装                  |      |\n| 11-6 PhpRedisAdmin的配置安装               |      |\n|                                            |      |\n| 第12章 Java运行环境配置                    |      |\n| 12-1 Java运行环境配置                      |      |\n| 12-2 maven高级使用                         |      |\n|                                            |      |\n| 第13章 Python运行环境                      |      |\n| 13-1 Python 运行环境配置                   |      |\n| 13-2 Python flask 高级应用                 |      |\n|                                            |      |\n| 第14章 服务管理                            |      |\n| 14-1 Linux常见服务-Crontba、Ntpdate        |      |\n| 14-2 Linux常见服务--Logrotate.Supervisor   |      |\n|                                            |      |\n| 第15章 监控神器Zabbix                      |      |\n| 15-1 监控系统Zabbix （上）                 |      |\n| 15-2 监控系统Zabbix (下)                   |      |\n|                                            |      |\n| 第16章 课程总结                            |      |\n| 16-1 课程总结                              |      |"
  },
  {
    "path": "course/28 系统学习Docker 践行DevOps理念.md",
    "content": "## 系统学习Docker 践行DevOps理念\n\n时长：15小时\n\n| 章节                                                | 记录 |\n| --------------------------------------------------- | ---- |\n| 第1章 容器技术和Docker简介                          |      |\n| 1-1 Docker导学                                      |      |\n| 1-2 容器技术概述                                    |      |\n| 1-3 Docker魅力初体验                                |      |\n| 1-4 课程源码获取和注意事项                          |      |\n|                                                     |      |\n| 第2章 Docker环境的各种搭建方法                      |      |\n| 2-1 Docker安装简介                                  |      |\n| 2-2 在MAC系统上安装Docker                           |      |\n| 2-3 在Windows系统上安装Docker                       |      |\n| 2-4 Vagrant&VirtualBox for Mac                      |      |\n| 2-5 Vagrant&VirtualBox for Windows                  |      |\n| 2-6 在CentOS上安装Docker                            |      |\n| 2-7 Docker Machine的本地使用                        |      |\n| 2-8 Docker Machine在阿里云上的使用                  |      |\n| 2-9 Docker Machine在亚马逊AWS云上的使用             |      |\n| 2-10 Docker Playground                              |      |\n| 2-11 本章总结                                       |      |\n|                                                     |      |\n| 第3章 Docker的镜像和容器                            |      |\n| 3-1 Docker架构和底层技术简介                        |      |\n| 3-2 Docker Image概述                                |      |\n| 3-3 DIY一个Base Image                               |      |\n| 3-4 初识Container                                   |      |\n| 3-5 构建自己的Docker镜像                            |      |\n| 3-6 Dockerfile语法梳理及最佳实践                    |      |\n| 3-7 RUN vs CMD vs Entrypoint                        |      |\n| 3-8 镜像的发布                                      |      |\n| 3-9 Dockerfile实战                                  |      |\n| 3-10 容器的操作                                     |      |\n| 3-11 Dockerfile实战(2)                              |      |\n| 3-12 容器的资源限制                                 |      |\n|                                                     |      |\n| 第4章 Docker的网络                                  |      |\n| 4-1 本章概述和实验环境介绍                          |      |\n| 4-2 网络基础回顾                                    |      |\n| 4-3 Linux网络命名空间                               |      |\n| 4-4 Docker bridge0详解                              |      |\n| 4-5 容器之间的link                                  |      |\n| 4-6 容器的端口映射                                  |      |\n| 4-7 容器网络之host和none                            |      |\n| 4-8 多容器复杂应用的部署演示                        |      |\n| 4-9 Overlay和Underlay的通俗解释                     |      |\n| 4-10 Docker Overlay网络和etcd实现多机容器通信       |      |\n|                                                     |      |\n| 第5章 Docker的持久化存储和数据共享                  |      |\n| 5-1 本章介绍                                        |      |\n| 5-2 本章实验环境介绍                                |      |\n| 5-3 数据持久化之Data Volume                         |      |\n| 5-4 数据持久化之Bind Mouting                        |      |\n| 5-5 开发者利器-Docker+Bind Mout                     |      |\n|                                                     |      |\n| 第6章 Docker Compose多容器部署                      |      |\n| 6-1 根据前面所学部署一个wordpress                   |      |\n| 6-2 Docker Compose到底是什么                        |      |\n| 6-3 Docker Compose的安装和基本使用                  |      |\n| 6-4 水平扩展和负载均衡                              |      |\n| 6-5 部署一个复杂的投票应用                          |      |\n|                                                     |      |\n| 第7章 容器编排Docker Swarm                          |      |\n| 7-1 容器编排Swarm介绍                               |      |\n| 7-2 创建一个三节点的swarm集群                       |      |\n| 7-3 Service的创建维护和水平扩展                     |      |\n| 7-4 在swarm集群里通过service部署wordpress           |      |\n| 7-5 集群服务间通信之Routing Mesh                    |      |\n| 7-6 Routing Mesh之Ingress负载均衡                   |      |\n| 7-7 Docker Stack部署Wordpress                       |      |\n| 7-8 作业解答之部署投票应用                          |      |\n| 7-9 Docker Secret管理和使用                         |      |\n| 7-10 Docker Secret在Stack中的使用                   |      |\n| 7-11 Service更新                                    |      |\n|                                                     |      |\n| 第8章 DevOps初体验——Docker Cloud和Docker企业版      |      |\n| 8-1 谈钱不伤感情-Docker的收费模式                   |      |\n| 8-2 Docker Cloud简介                                |      |\n| 8-3 Docker Cloud之自动build Docker image            |      |\n| 8-4 Docker Cloud之持续集成和持续部署                |      |\n| 8-5 Docker企业版的在线免费体验                      |      |\n| 8-6 Docker企业版本地安装之UCP                       |      |\n| 8-7 Docker企业版本地安装之DTR                       |      |\n| 8-8 Docker企业版UCP的基本使用演示                   |      |\n| 8-9 体验阿里云的容器服务                            |      |\n| 8-10 在阿里云上安装Docker企业版.mp4                 |      |\n| 8-11 Docker企业版DTR的基本使用演示                  |      |\n|                                                     |      |\n| 第9章 容器编排Kubernetes                            |      |\n| 9-1 Kubenetes简介                                   |      |\n| 9-2 Minikube快速搭建K8S单节点环境                   |      |\n| 9-3 K8S最小调度单位Pod                              |      |\n| 9-4 ReplicaSet和ReplicationController               |      |\n| 9-5 Deployment_.mp4                                 |      |\n| 9-6 使用Tectonic在本地搭建多节点K8S集群             |      |\n| 9-7 k8s基础网络Cluster Network                      |      |\n| 9-8 Service简介和演示                               |      |\n| 9-9 NodePort类型Service以及Label的简单实用_音频.mp4 |      |\n| 9-10 准备工作——使用kops在亚马逊AWS上搭建k8s集群     |      |\n| 9-11 使用kops在亚马逊AWS上搭建k8s集群.mp4           |      |\n| 9-12 LoadBlancer类型Service以及AWS的DNS服务配置     |      |\n| 9-13 在亚马逊k8s集群上部署wordpress                 |      |\n|                                                     |      |\n| 第10章 容器的的运维和监控                           |      |\n| 10-1 容器的基本监控                                 |      |\n| 10-2 k8s集群运行资源监控——Heapster+Grafana+InfluxDB |      |\n| 10-3 根据资源占用自动横向伸缩                       |      |\n| 10-4 k8s集群Log的采集和展示——ELK+Fluentd            |      |\n| 10-5 k8s集群监控方案Prometheus                      |      |\n|                                                     |      |\n| 第11章 Docker+DevOps实战——过程和工具                |      |\n| 11-1 本章简介                                       |      |\n| 11-2 搭建GitLab服务器                               |      |\n| 11-3 搭建GitLab CI服务器和Pipeline演示              |      |\n| 11-4 基于真实Python项目的CI演示                     |      |\n| 11-5 简单Java项目的CI演示                           |      |\n| 11-6 使用Python项目演示的CICD流程                   |      |\n| 11-7 CI实现版本自动发布                             |      |\n| 11-8 本章总结和如何继续学习                         |      |\n|                                                     |      |\n| 第12章 课程总结                                     |      |\n| 12-1 不是总结的总结                                 |      |"
  },
  {
    "path": "course/30 Python Flask构建可扩展的RESTful API.md",
    "content": "## Python Flask构建可扩展的RESTful API\n\n时长：6小时25分钟\n\n| 章节                                                         | 记录 |\n| ------------------------------------------------------------ | ---- |\n| **第1章 随便聊聊**                                           |      |\n| 聊聊Flask与Django，聊聊代码的创造性                          |      |\n|                                                              |      |\n| 1-1 Flask VS Django                                          |      |\n| 1-2 课程更新维护说明                                         |      |\n|                                                              |      |\n| **第2章 起步与红图**                                         |      |\n| 本章我们初始化项目，探讨与研究Flask的默认层级结构。当我们遇到层级结构不合理时，我们将模仿蓝图自己定义一个“红图”来扩展Flask层级体系 |      |\n|                                                              |      |\n| 2-1 环境、开发工具与flask1.0                                 |      |\n| 2-2 初始化项目                                               |      |\n| 2-3 新建入口文件                                             |      |\n| 2-4 蓝图分离视图函数的缺陷                                   |      |\n| 2-5 打开思维，创建自己的Redprint——红图                       |      |\n| 2-6 实现Redprint                                             |      |\n| 2-7 优化Redprint                                             |      |\n|                                                              |      |\n| **第3章 REST基本特征**                                       |      |\n| 本章我们将探讨REST的基本特征，并结合实际情况给出REST的适用范围与优劣势 |      |\n|                                                              |      |\n| 3-1 REST的最基本特征（可选观看）                             |      |\n| 3-2 为什么标准REST不适合内部开发（可选观看）                 |      |\n|                                                              |      |\n| **第4章 自定义异常对象**                                     |      |\n| 异常处理其实是一个非常严肃而又麻烦的事情，这直接涉及到前端如何对用户做出响应。本章我们将重写HTTPException并建立全局异常处理机制，统一处理框架内的异常，向前端返回统一而标准的异常信息，简化前端的开发流程 |      |\n|                                                              |      |\n| 4-1 关于“用户”的思考                                         |      |\n| 4-2 构建Client验证器                                         |      |\n| 4-3 处理不同客户端注册的方案                                 |      |\n| 4-4 创建User模型                                             |      |\n| 4-5 完成客户端注册                                           |      |\n| 4-6 生成用户数据                                             |      |\n| 4-7 自定义异常对象                                           |      |\n| 4-8 浅谈异常返回的标准与重要性                               |      |\n| 4-9 自定义APIException                                       |      |\n|                                                              |      |\n| **第5章 理解WTForms并灵活改造她**                            |      |\n| WTForms其实是非常强大的验证插件。但很多同学对WTForms的理解仅仅停留在“验证表单”上。那WTForms可以用来做API的参数验证码？完全可以，但这需要你灵活的使用它，对它做出一些“改变” |      |\n|                                                              |      |\n| 5-1 重写WTForms 一                                           |      |\n| 5-2 重写WTForms 二                                           |      |\n| 5-3 可以接受定义的复杂，但不能接受调用的复杂                 |      |\n| 5-4 已知异常与未知异常                                       |      |\n| 5-5 全局异常处理                                             |      |\n|                                                              |      |\n| **第6章 Token与HTTPBasic验证 —— 用令牌来管理用户**           |      |\n| 在我的TP5课程里，我们使用令牌的方式是服务器缓存的方式。那么在Python Flask中我们换一种令牌的发放方式。我们将用户的信息加密后作为令牌返回到客户端，客户端在访问服务器API时必须以HTTP Basic的方式携带令牌，我们再读取令牌信息后，将用户信息存入到g变量中，共业务代码全局使用... |      |\n|                                                              |      |\n| 6-1 Token概述                                                |      |\n| 6-2 获取Token令牌                                            |      |\n| 6-3 Token的用处                                              |      |\n| 6-4 @auth拦截器执行流程                                      |      |\n| 6-5 HTTPBasicAuth基本原理                                    |      |\n| 6-6 以BasicAuth的方式发送Token                               |      |\n| 6-7 验证Token                                                |      |\n| 6-8 重写first_or_404与get_or_404                             |      |\n|                                                              |      |\n| **第7章 模型对象的序列化**                                   |      |\n| 最适合Python JSON序列化的是dict字典类型，每一种语言都有其对应的数据结构用来对应JSON对象，比如在PHP中是它的数组数据结构。而Python是用字典来对应JSON的。如果我们想直接序列化一个对象或者模型对象，那么最笨的办法是把对象的属性读取出来，然后组装成一个字典再序列化。这实在是太麻烦了。本章节我们将深入了解JSO... |      |\n|                                                              |      |\n| 7-1 鸡汤？                                                   |      |\n| 7-2 理解序列化时的default函数                                |      |\n| 7-3 不完美的对象转字典                                       |      |\n| 7-4 深入理解dict的机制                                       |      |\n| 7-5 一个元素的元组要特别注意                                 |      |\n| 7-6 序列化SQLAlchemy模型                                     |      |\n| 7-7 完善序列化                                               |      |\n| 7-8 ViewModel对于API有意义吗                                 |      |\n|                                                              |      |\n| **第8章 权限控制**                                           |      |\n| 我看过太多同学编写的API在互联网上疯狂的裸奔了。殊不知这太危险了。API必须提供分层保护机制，根据不同用户的种类来限制其可以访问的API，从而保护接口。比如管理员可以访问哪些接口，普通用户可以访问哪些接口，小程序可以访问哪些，APP又能够访问哪些？灵活而强大的可配置Scope，可以帮助你事半功倍... |      |\n|                                                              |      |\n| 8-1 删除模型注意事项                                         |      |\n| 8-2 g变量中读取uid防止超权                                   |      |\n| 8-3 生成超级管理员账号                                       |      |\n| 8-4 不太好的权限管理方案                                     |      |\n| 8-5 比较好的权限管理方案                                     |      |\n| 8-6 实现Scope权限管理 一                                     |      |\n| 8-7 globals()实现“反射”                                      |      |\n| 8-8 实现Scope权限管理 二                                     |      |\n| 8-9 Scope优化一 支持权限相加                                 |      |\n| 8-10 Scope优化 二 支持权限链式相加                           |      |\n| 8-11 Scope优化 三 所有子类支持相加                           |      |\n| 8-12 Scope优化 四 运算符重载                                 |      |\n| 8-13 Scope 优化 探讨模块级别的Scope                          |      |\n| 8-14 Scope优化 实现模块级别的Scope                           |      |\n| 8-15 Scope优化 七 支持排除                                   |      |\n|                                                              |      |\n| **第9章 实现部分鱼书小程序功能**                             |      |\n| 理论必须结合实践，我们提供一个简单的鱼书小程序，编写他的业务接口，并用小程序来进行API的检验 |      |\n|                                                              |      |\n| 9-1 小程序演示API调用效果                                    |      |\n| 9-2 模糊搜索书籍                                             |      |\n| 9-3 再谈严格型REST的缺陷                                     |      |\n| 9-4 实现hide方法                                             |      |\n| 9-5 @orm.reconstructor 解决模型对象实例化问题                |      |\n| 9-6 重构hide与append                                         |      |\n| 9-7 赠送礼物接口                                             |      |\n| 9-8 实现获取令牌信息接口                                     |      |"
  },
  {
    "path": "course/31 学习Scala 进击大数据Spark生态圈.md",
    "content": "学习Scala_进击大数据Spark生态圈-课程章节\nhttps://coding.imooc.com/class/chapter/215.html#Anchor\n\n\n\n"
  },
  {
    "path": "course/32 10小时入门大数据.md",
    "content": ""
  },
  {
    "path": "course/Java 架构师成长之路/01 Java 单体应用.md",
    "content": "## Java 单体应用\n\n| **章节**                                                  | **记录** |\n| --------------------------------------------------------- | -------- |\n| **开篇**                                                  |          |\n| 1 开篇-程序员的玄学与佛学-1                               |          |\n| 2 开篇-程序员的玄学与佛学-2                               |          |\n| 3 开篇-程序员的玄学与佛学-3                               |          |\n|                                                           |          |\n| **第01章**                                                |          |\n| 4 第01章-使用 Intellij IDEA-IDEA 简介                     |          |\n| 5 第01章-使用 Intellij IDEA-第一个 IDEA 应用程            |          |\n| 6 小知识-使用 Markdown 记笔记                             |          |\n|                                                           |          |\n| **第02章**                                                |          |\n| 7 第02章-使用 Maven 构建应用-Maven 简介                   |          |\n| 8 第02章-使用 Maven 构建应用-Maven 安装配置               |          |\n| 9 第02章-使用 Maven 构建应用-Maven 本地仓库               |          |\n| 10 第02章-使用 Maven 构建应用-Maven 中央仓库              |          |\n| 11 第02章-使用 Maven 构建应用-Maven 依赖机制              |          |\n| 12 第02章-使用 Maven 构建应用-Maven POM                   |          |\n| 13 第02章-使用 Maven 构建应用-Maven 快照                  |          |\n| 14 第02章-使用 Maven 构建应用-第一个 Maven 应用程序       |          |\n|                                                           |          |\n| **第03章**                                                |          |\n| 15 第03章-三层架构+MVC-什么是三层架构                     |          |\n| 16 第03章-三层架构+MVC-什么是 MVC 模式                    |          |\n| 17 小知识-高内聚，低耦合                                  |          |\n| 18 课后练习-三层架构-1                                    | 12/27    |\n| 19 课后练习-三层架构-2                                    |          |\n| 20 小知识-关于如何自学的一些心得                          |          |\n|                                                           |          |\n| **第04章**                                                |          |\n| 21 第04章-使用 Bootstrap-Bootstrap 简介                   |          |\n| 22 第04章-使用 Bootstrap-Bootstrap 环境安装               |          |\n| 23 第04章-使用 Bootstrap-Bootstrap 网格布局               |          |\n| 24 第04章-使用 Bootstrap-媒体查询的用法                   |          |\n| 25 第04章-使用 Bootstrap-Bootstrap 表格                   |          |\n| 26 第04章-使用 Bootstrap-Bootstrap 字体图标               |          |\n|                                                           |          |\n| **第05章**                                                |          |\n| 27 第05章-基础框架入门-Spring-Spring 简介                 |          |\n| 28 第05章-基础框架入门-Spring-Spring 体系结构             |          |\n| 29 第05章-基础框架入门-Spring-Spring 的特点               |          |\n| 30 第05章-基础框架入门-Spring-Spring 与 IoC               |          |\n| 31 第05章-基础框架入门-Spring-第一个 Spring 应用程        |          |\n| 32 小知识-什么是 TDD 及常见的测试方式                     |          |\n| 33 第05章-基础框架入门-JUnit-JUnit 简介                   |          |\n| 34 第05章-基础框架入门-JUnit-第一个 JUnit 单元测试        |          |\n| 35 第05章-基础框架入门-JUnit-JUnit 断言                   |          |\n| 36 第05章-基础框架入门-Log4j-Log4j 简介                   |          |\n| 37 第05章-基础框架入门-Log4j-Log4j 日志级别               |          |\n| 38 第05章-基础框架入门-Log4j-Log4j 日志输出控制文件       |          |\n| 39 第05章-基础框架入门-Log4j-第一个 Log4j 日志文件        |          |\n| 40 综合复习-本周知识总结                                  |          |\n| 41 综合复习-创建项目                                      |          |\n| 42 综合复习-Bootstrap 管理模板-AdminLTE                   |          |\n| 43 综合复习-Bootstrap 管理模板-创建登录页                 |          |\n| 44 综合复习-实现登录功能-完成后台编码                     |          |\n| 45 综合复习-实现登录功能-增强用户体验                     |          |\n|                                                           |          |\n| **第06章**                                                |          |\n| 46 第06章-Spring Web 与 Bean 装配-Spring                  |          |\n| 47 第06章-Spring Web 与 Bean 装配-容器中 Bea              |          |\n| 48 第06章-Spring Web 与 Bean 装配-基于注解的装配          |          |\n| 49 课后作业-记住我                                        |          |\n|                                                           |          |\n| **第07章**                                                |          |\n| 50 第07章-Spring MVC 与 Maven 模块化开发-Spr              |          |\n| 51 第07章-Spring MVC 与 Maven 模块化开发-Spr              |          |\n| 52 第07章-Spring MVC 与 Maven 模块化开发-第一个           |          |\n| 53 第07章-Spring MVC 与 Maven 模块化开发-Spr              |          |\n| 54 第07章-Spring MVC 与 Maven 模块化开发-Mav              |          |\n| 55 课后练习-重新完善功能代码                              |          |\n|                                                           |          |\n| **第08章**                                                |          |\n| 56 第08章-MyBatis 数据持久化-MyBatis 简介                 |          |\n| 57 第08章-MyBatis 数据持久化-Druid 简介                   |          |\n| 58 第08章-MyBatis 数据持久化-Spring 整合 Drui             |          |\n| 59 第08章-MyBatis 数据持久化-Spring 整合 MyBa             |          |\n| 60 小知识-utf8 与 utf8mb4 字符集                          |          |\n| 61 第08章-MyBatis 数据持久化-第一个 MyBatis 对象          |          |\n|                                                           |          |\n| **第09章**                                                |          |\n| 62 第09章-MyBatis 表操作-单表 CRUD 操作                   |          |\n| 63 项目实战-MyShop-实现用户登录功能                       |          |\n| 64 项目实战-MyShop-实现首页布局                           |          |\n| 65 项目实战-MyShop-用户管理功能-用户列表展示              |          |\n| 66 项目实战-MyShop-用户管理功能-实现新增用户功能          |          |\n| 67 项目实战-MyShop-用户管理功能-使用 Spring MVC           |          |\n| 68 项目实战-MyShop-用户管理功能-使用 jQuery Vali          |          |\n| 69 项目实战-MyShop-用户管理功能-使用动态 SQL 实现搜索功   |          |\n| 70 项目实战-MyShop-用户管理功能-优化搜索功能              |          |\n| 71 项目实战-MyShop-用户管理功能-使用 jQuery iChe          |          |\n| 72 项目实战-MyShop-用户管理功能-实现批量删除功能1         |          |\n| 73 项目实战-MyShop-用户管理功能-实现批量删除功能2         |          |\n| 74 项目实战-MyShop-用户管理功能-使用 DataTables           |          |\n| 75 项目实战-MyShop-用户管理功能-使用 DataTables           |          |\n| 76 项目实战-MyShop-用户管理功能-实现编辑和查看功能        |          |\n| 77 项目实战-MyShop-用户管理功能-重新实现搜索功能          |          |\n| 78 项目实战-MyShop-用户管理功能-最后的收尾工作            |          |\n| 79 项目实战-MyShop-内容管理功能-内容分类功能准备          |          |\n| 80 项目实战-MyShop-内容管理功能-使用 TreeTable 展         |          |\n| 81 项目实战-MyShop-内容管理功能-内容列表功能准备          |          |\n| 82 项目实战-MyShop-内容管理功能-使用 zTree 展示内容分     |          |\n| 83 项目实战-MyShop-使用 Lombok 插件简化 Bean 代           |          |\n| 84 项目实战-MyShop-使用 Spring Validation                 |          |\n| 85 项目实战-MyShop-内容管理功能-Spring MVC + D            |          |\n| 86 项目实战-MyShop-内容管理功能-封装 Dropzone 插件        |          |\n| 87 项目实战-MyShop-内容管理功能-使用 wangEditor           |          |\n| 88 项目实战-MyShop-内容管理功能-实现 wangEditor           |          |\n| 89 项目实战-MyShop-内容管理功能-实现关联关系查询          |          |\n| 90 项目实战-MyShop-重构代码-提取统一的数据访问和业务接口  |          |\n| 91 项目实战-MyShop-内容管理功能-实现内容分类的新增功能    |          |\n| 92 项目实战-MyShop-重构代码-深度封装业务逻辑层1           |          |\n| 93 项目实战-MyShop-重构代码-深度封装业务逻辑层2           |          |\n| 94 项目实战-MyShop-重构代码-封装控制器层                  |          |\n|                                                           |          |\n| **第11章**                                                |          |\n| 95 第11章-Spring 的事务管理-Spring 事务管理简介           |          |\n| 96 第11章-Spring 的事务管理-使用 AspectJ 的 AO            |          |\n| 97 项目实战-MyShop-使用 Spring 注解管理事务               |          |\n|                                                           |          |\n| **第12章**                                                |          |\n| 98 第12章-解决模块间的通信问题-项目改动说明               |          |\n| 99 第12章-解决模块间的通信问题-后台管理与门户数据展示说明 |          |\n| 100 第12章-解决模块间的通信问题-为什么存在通信问题        |          |\n| 101 第12章-解决模块间的通信问题-Apache HttpClien          |          |\n| 102 第12章-解决模块间的通信问题-Apache HttpClien          |          |\n| 103 第12章-解决模块间的通信问题-使用 Jackson 处理 JS      |          |\n| 104 第12章-解决模块间的通信问题-创建 API 接口模块         |          |\n| 105 小知识-POJO、VO、DTO、Entity、Domain 的区             |          |\n| 106 第12章-解决模块间的通信问题-RESTful 风格的 API        |          |\n| 107 第12章-解决模块间的通信问题-使用 Spirng MVC 实现      |          |\n|                                                           |          |\n| 108 项目实战-MyShop-创建 Web UI 门户模块                  |          |\n| 109 项目实战-MyShop-实现 UI 模块与 API 模块的通信         |          |\n| 110 项目实战-MyShop-实现门户首页的幻灯片展示              |          |\n| 111 项目实战-MyShop-实现门户登录-实现 API                 |          |\n| 112 项目实战-MyShop-实现门户登录-对接 API                 |          |\n| 113 项目实战-MyShop-实现门户登录-使用拦截器禁止重复登录   |          |\n| 114 小知识-验证码的作用与如何打码                         |          |\n| 115 项目实战-MyShop-解决 Maven 无法下载依赖的问题         |          |\n| 116 项目实战-MyShop-实现门户登录-增加 Kaptcha 验证        |          |\n| 117 项目实战-MyShop-使用 Apache Commons Em                |          |\n| 118 课程完结-阶段总结，撒花，撒花，可喜可贺，可喜可贺     |          |\n\n"
  },
  {
    "path": "course/Java 架构师成长之路/02 Java 微服务架构.md",
    "content": "## Java 微服务架构\n\n| 章节                                                  | 记录 |\n| ----------------------------------------------------- | ---- |\n| 1 关于博客改版的一些说明                              |      |\n| 2 第01章-微服务简介-构建单体应用模型                  |      |\n| 3 第01章-微服务简介-走向单体地狱                      |      |\n| 4 第01章-微服务简介-微服务解决复杂问题                |      |\n| 5 第01章-微服务简介-微服务的优点                      |      |\n| 6 第01章-微服务简介-微服务的缺点                      |      |\n|                                                       |      |\n| 7 第02章-基础设施即服务-Linux-简介                    |      |\n| 8 第02章-基础设施即服务-Linux-关于操作系统的选型      |      |\n| 9 第02章-基础设施即服务-Linux-安装 Ubuntu Serve       |      |\n| 10 第02章-基础设施即服务-Linux-服务器远程控制         |      |\n| 11 第02章-基础设施即服务-Linux-目录结构说明           |      |\n| 12 第02章-基础设施即服务-Linux-操作文件目录命令       |      |\n| 13 第02章-基础设施即服务-Linux-系统管理命令           |      |\n| 14 第02章-基础设施即服务-Linux-重启与压缩命令         |      |\n| 15 第02章-基础设施即服务-Linux-编辑器的使用           |      |\n| 16 第02章-基础设施即服务-Linux-软件的安装与卸载       |      |\n| 17 第02章-基础设施即服务-Linux-用户和组管理           |      |\n| 18 第02章-基础设施即服务-Linux-文件权限管理           |      |\n| 19 第02章-基础设施即服务-Linux-安装 Java              |      |\n| 20 第02章-基础设施即服务-Linux-安装 Tomcat            |      |\n| 21 第02章-基础设施即服务-Linux-安装 MySQL             |      |\n| 22 第02章-基础设施即服务-Linux-部署应用到生成环境     |      |\n| 23 第02章-基础设施即服务-Docker-什么是 Docker         |      |\n| 24 第02章-基础设施即服务-Docker-为什么要用 Docker     |      |\n| 25 第02章-基础设施即服务-Docker-引擎                  |      |\n| 26 第02章-基础设施即服务-Docker-架构                  |      |\n| 27 第02章-基础设施即服务-Docker-镜像与容器            |      |\n| 28 第02章-基础设施即服务-Docker-仓库                  |      |\n| 29 第02章-基础设施即服务-Docker-安装                  |      |\n| 30 第02章-基础设施即服务-Docker-加速器                |      |\n| 31 第02章-基础设施即服务-Docker-镜像-获取镜像         |      |\n| 32 第02章-基础设施即服务-Docker-镜像-列出镜像         |      |\n| 33 第02章-基础设施即服务-Docker-镜像-删除镜像         |      |\n| 34 第02章-基础设施即服务-Docker-镜像-Dockerfile       |      |\n| 35 第02章-基础设施即服务-Docker-镜像-Dockerfile       |      |\n| 36 第02章-基础设施即服务-Docker-镜像-Dockerfile       |      |\n| 37 第02章-基础设施即服务-Docker-镜像-Dockerfile       |      |\n| 38 第02章-基础设施即服务-Docker-镜像-Dockerfile       |      |\n| 39 第02章-基础设施即服务-Docker-守护态运行容器        |      |\n| 40 第02章-基础设施即服务-Docker-容器数据持久化        |      |\n| 41 第02章-基础设施即服务-Docker-部署数据库            |      |\n| 42 第02章-基础设施即服务-Docker-部署项目到容器        |      |\n| 43 第02章-基础设施即服务-Docker Compose-简介与安装    |      |\n| 44 第02章-基础设施即服务-Docker Compose-基本使用      |      |\n| 45 第02章-基础设施即服务-Docker Compose-部署项目到    |      |\n| 46 第02章-基础设施即服务-Docker Compose-YAML          |      |\n|                                                       |      |\n| 47 第03章-平台即服务-GitLab-使用 Git 托管代码         |      |\n| 48 第03章-平台即服务-GitLab-简介与安装                |      |\n| 49 第03章-平台即服务-GitLab-基本设置                  |      |\n| 50 第03章-平台即服务-GitLab-第一个托管项目            |      |\n| 51 第03章-平台即服务-GitLab-使用 SSH 免密登录         |      |\n| 52 第03章-平台即服务-Nexus-简介与安装                 |      |\n| 53 第03章-平台即服务-Nexus-在项目中使用 Maven 私服    |      |\n| 54 第03章-平台即服务-Registry-简介与安装              |      |\n| 55 第03章-平台即服务-Registry-配置 WebUI 与客户端     |      |\n| 56 Docker-真正实现：一次构建，到处运行                |      |\n|                                                       |      |\n| 57 第04章-再谈微服务-传统架构与微服务架构的区别       |      |\n| 58 第04章-再谈微服务-微服务的特征                     |      |\n| 59 第04章-再谈微服务-微服务的实践1                    |      |\n| 60 第04章-再谈微服务-微服务的实践2                    |      |\n| 61 小知识-单点故障与分布式锁                          |      |\n| 62 第04章-再谈微服务-微服务架构设计模式               |      |\n|                                                       |      |\n| 63 第05章-微服务框架-SpringBoot-简史                  |      |\n| 64 第05章-微服务框架-SpringBoot-简介                  |      |\n| 65 第05章-微服务框架-SpringBoot-优缺点                |      |\n| 66 第05章-微服务框架-SpringBoot-第一个应用程序        |      |\n| 67 第05章-微服务框架-SpringBoot-单元测试              |      |\n| 68 第05章-微服务框架-SpringBoot-常用配置              |      |\n| 69 第05章-微服务框架-SpringBoot-Thymeleaf-简          |      |\n| 70 第05章-微服务框架-SpringBoot-Thymeleaf-为          |      |\n| 71 第05章-微服务框架-SpringBoot-Thymeleaf-第          |      |\n| 72 第05章-微服务框架-SpringBoot-MyBatis-整合          |      |\n| 73 第05章-微服务框架-SpringBoot-MyBatis-测试          |      |\n| 74 第05章-微服务框架-SpringCloud-简介                 |      |\n| 75 第05章-微服务框架-SpringCloud-创建统一的依赖管理   |      |\n| 76 第05章-微服务框架-SpringCloud-服务注册与发现       |      |\n| 77 第05章-微服务框架-SpringCloud-创建服务提供者       |      |\n| 78 第05章-微服务框架-SpringCloud-创建服务消费者（Ri   |      |\n| 79 第05章-微服务框架-SpringCloud-创建服务消费者（Fe   |      |\n| 80 第05章-微服务框架-SpringCloud-使用熔断器防止服务雪 |      |\n| 81 第05章-微服务框架-SpringCloud-使用熔断器仪表盘监控 |      |\n| 82 第05章-微服务框架-SpringCloud-使用路由网关统一访问 |      |\n| 83 第05章-微服务框架-SpringCloud-使用路由网关的服务过 |      |\n| 84 第05章-微服务框架-SpringCloud-分布式配置中心-服务  |      |\n| 85 第05章-微服务框架-SpringCloud-分布式配置中心-客户  |      |\n| 86 第05章-微服务框架-SpringCloud-服务链路追踪         |      |\n| 87 第05章-微服务框架-SpringCloud-服务监控-服务端      |      |\n| 88 第05章-微服务框架-SpringCloud-服务监控-客户端      |      |"
  },
  {
    "path": "course/Java 架构师成长之路/03 Java 微服务实战.md",
    "content": "## Java 微服务实战\n| 章节                                        | 记录 |\n| ------------------------------------------- | ---- |\n| 1 开篇-倾力打造互联网 Java 全栈工程师       |      |\n| 2 白皮书                                    |      |\n| 3 项目简介                                  |      |\n| 4 开发前的准备-了解敏捷开发                 |      |\n| 5 开发前的准备-了解 XP 极限编程             |      |\n| 6 开发前的准备-创建 GitLab 项目组           |      |\n| 7 开发前的准备-完善各服务配置               |      |\n| 8 开发前的准备-部署服务到容器1              |      |\n| 9 开发前的准备-部署服务到容器2              |      |\n| 10 部署持续集成-持续集成的基本概念          |      |\n| 11 部署持续集成-持续集成的操作流程          |      |\n| 12 部署持续集成-使用 GitLab 持续集成        |      |\n| 13 部署持续集成-使用 GitLab Runn            |      |\n| 14 部署持续集成-使用 GitLab Runn            |      |\n| 15 部署持续集成-第一个 GitLab Run           |      |\n| 16 部署持续集成-实战分布式配置中心          |      |\n| 17 部署持续集成-实战服务注册与发现          |      |\n| 18 管理员服务-提供者1                       |      |\n| 19 管理员服务-提供者2                       |      |\n| 20 管理员服务-提供者3                       |      |\n| 21 管理员服务-消费者                        |      |\n| 22 反向代理负载均衡-Nginx 简介              |      |\n| 23 反向代理负载均衡-Nginx 虚拟主机          |      |\n| 24 小知识-Nginx 惊群问题                    |      |\n| 25 反向代理负载均衡-Nginx 反向代理          |      |\n| 26 反向代理负载均衡-Nginx 负载均衡          |      |\n| 27 反向代理负载均衡-Nginx 搭建伪 CD         |      |\n| 28 数据缓存服务-Redis 简介                  |      |\n| 29 数据缓存服务-Redis 高可用方案            |      |\n| 30 数据缓存服务-Redis Sentinel              |      |\n| 31 数据缓存服务-提供者                      |      |\n| 32 单点登录服务-单点登录系统机制            |      |\n| 33 单点登录服务-多系统登录的复杂性          |      |\n| 34 单点登录服务-单点登录系统流程            |      |\n| 35 单点登录服务-实战单点登录1               |      |\n| 36 单点登录服务-实战单点登录2               |      |\n| 37 单点登录服务-实战单点登录3               |      |\n| 38 单点登录服务-实战单点登录4               |      |\n| 39 单点登录服务-实战单点登录5               |      |\n| 40 单点登录服务-使用 Nginx 反向代理解       |      |\n| 41 单点登录服务-实战单点登录完结篇          |      |\n| 42 进入下半场的准备工作                     |      |\n| 43 番外篇-重构改善既有代码的设计-数据库     |      |\n| 44 番外篇-重构改善既有代码的设计-领域模型   |      |\n| 45 番外篇-重构改善既有代码的设计-业务逻辑层 |      |\n| 46 番外篇-重构改善既有代码的设计-管理员服务 |      |\n| 47 番外篇-重构改善既有代码的设计-管理员分页 |      |\n| 48 文章服务-提供者                          |      |\n| 49 文章服务-配置 MyBatis Redis              |      |\n| 50 文章服务-配置 Swagger2 接口文档          |      |\n| 51 文章服务-消费者                          |      |\n| 52 文章服务-消费者-分页功能                 |      |\n| 53 文章服务-消费者-提取 Thymeleaf           |      |\n| 54 文章服务-消费者-保存功能                 |      |\n| 55 文件上传服务-FastDFS                     |      |\n| 56 文件上传服务-提供者                      |      |\n| 57 文章服务-消费者-文件上传功能             |      |\n| 58 后台服务聚合-页面拆分1                   |      |\n| 59 后台服务聚合-页面拆分2                   |      |\n| 60 后台服务聚合-页面聚合1                   |      |\n| 61 后台服务聚合-页面聚合2                   |      |\n| 62 数字货币服务-前言                        |      |\n| 63 消息队列-消息队列的流派                  |      |\n| 64 消息队列-Actor 模型                      |      |\n| 65 消息队列-RabbitMQ 简介                   |      |\n| 66 消息队列-RabbitMQ 安装                   |      |\n| 67 消息队列-RabbitMQ 使用                   |      |\n| 68 任务调度-Quartz                          |      |"
  },
  {
    "path": "course/Java 架构师成长之路/04 Java 微服务架构 Dubbo 篇.md",
    "content": "## Java 微服务架构 Dubbo 篇\n\n| 章节                                                | 记录 |\n| --------------------------------------------------- | ---- |\n| 01 实现微服务架构-课程回顾-微服务架构需要解决的问题 |      |\n| 02 实现微服务架构-Zookeeper-什么是分布式协调服务    |      |\n| 03 实现微服务架构-Zookeeper-什么是分布式锁1         |      |\n| 04 实现微服务架构-Zookeeper-什么是分布式锁2         |      |\n| 05 实现微服务架构-Zookeeper-什么是分布式锁3         |      |\n| 06 实现微服务架构-Zookeeper-服务注册与发现          |      |\n| 07 实现微服务架构-Zookeeper-集群崩溃恢复1           |      |\n| 08 实现微服务架构-Zookeeper-集群崩溃恢复2           |      |\n| 09 实现微服务架构-Zookeeper-集群数据同步            |      |\n| 10 实现微服务架构-Zookeeper-如何实现分布式锁        |      |\n| 11 实现微服务架构-Zookeeper-基于 Docker             |      |\n| 12 实现微服务架构-Zookeeper-配置说明                |      |\n|                                                     |      |\n| 13 实现微服务架构-Dubbo-简介1                       |      |\n| 14 实现微服务架构-Dubbo-简介2                       |      |\n| 15 实现微服务架构-Dubbo-服务治理                    |      |\n| 16 实现微服务架构-Dubbo-核心功能与组件角色          |      |\n| 17 实现微服务架构-Dubbo-管理控制台                  |      |\n| 18 实现微服务架构-Dubbo-服务提供者                  |      |\n| 19 实现微服务架构-Dubbo-服务消费者                  |      |\n| 20 实现微服务架构-Dubbo-负载均衡                    |      |\n| 21 实现微服务架构-Dubbo-使用 Kryo 实现高速序        |      |\n| 22 实现微服务架构-Dubbo-使用 Hystrix 实现           |      |\n| 23 实现微服务架构-Dubbo-使用 Hystrix 熔断           |      |\n| 24 实现微服务架构-Dubbo-系统架构的演进              |      |\n|                                                     |      |\n| 25 实现微服务架构-项目实战-搭建通用模块项目         |      |\n| 26 实现微服务架构-项目实战-搭建用户管理服务         |      |\n|                                                     |      |\n| 27 实现微服务架构-持续集成-GitLab Runner            |      |\n| 28 实现微服务架构-持续集成-实战服务提供者           |      |\n| 29 实现微服务架构-持续集成-实战服务消费者           |      |\n|                                                     |      |\n| 30 实现微服务架构-持续交付-基于 Docker 安装 J       |      |\n| 31 实现微服务架构-持续交付-配置 Jenkins             |      |\n| 32 实现微服务架构-持续交付-创建第一个任务           |      |\n| 33 实现微服务架构-持续交付-实战服务提供者           |      |\n|                                                     |      |\n| 34 实现微服务架构-用户管理服务-Metronic 模板介      |      |\n| 35 实现微服务架构-用户管理服务-实现基本布局         |      |\n| 36 实现微服务架构-用户管理服务-PageHelper分页       |      |\n| 37 实现微服务架构-用户管理服务-PageHelper分页       |      |\n|                                                     |      |\n| 38 实现微服务架构-内容管理服务-实现基本功能         |      |\n|                                                     |      |\n| 39 实现微服务架构-服务聚合-API 网关1                |      |\n| 40 实现微服务架构-服务聚合-API 网关2                |      |\n|                                                     |      |\n| 41 实现微服务架构-分布式文件系统-什么是 FastDFS     |      |\n| 42 实现微服务架构-分布式文件系统-基于 Docker 安     |      |\n| 43 实现微服务架构-分布式文件系统-FastDFS Jav        |      |\n|                                                     |      |\n| 44 实现微服务架构-使用 Nginx 解决跨域问题-跨域现    |      |\n| 45 实现微服务架构-使用 Nginx 解决跨域问题-Ngi       |      |\n| 46 实现微服务架构-使用 Nginx 解决跨域问题-Ngi       |      |\n| 47 实现微服务架构-使用 Nginx 解决跨域问题-Ngi       |      |\n| 48 实现微服务架构-使用 Nginx 解决跨域问题-Ngi       |      |\n| 49 实现微服务架构-使用 Nginx 解决跨域问题-COR       |      |\n| 50 实现微服务架构-使用 Nginx 解决跨域问题-假请求    |      |\n|                                                     |      |\n| 51 实现微服务架构-使用 Redis 实现数据缓存-创建缓    |      |\n| 52 实现微服务架构-使用 Redis 实现数据缓存-MyB       |      |\n|                                                     |      |\n| 53 实现微服务架构-使用 Solr 实现全文检索-Solr       |      |\n| 54 实现微服务架构-使用 Solr 实现全文检索-搜索引擎   |      |\n| 55 实现微服务架构-使用 Solr 实现全文检索-基于 D     |      |\n| 56 实现微服务架构-使用 Solr 实现全文检索-Solr       |      |\n| 57 实现微服务架构-使用 Solr 实现全文检索-Spri       |      |"
  },
  {
    "path": "course/LEARN.md",
    "content": "## PART2：学习记录\n\n\n\n★2018年6月-7月学习计划\n\n【慕课网】玩转数据结构 从入门到进阶\n\n【慕课网】程序员的内功修炼，学好算法与数据结构\n\n【慕课网】玩转算法面试 leetcode题库分门别类详细解析  \n\n【慕课网】HTTP协议原理+实践 Web开发工程师必学\n\n【慕课网】Java并发编程与高并发解决方案\n\n\n\n\n\n### 学习课程（精简版）\n\n以下清单是需要学习的课程\n\n| 专题      | 类型               | 课程                                                         |      进展       |\n| --------- | ------------------ | ------------------------------------------------------------ | :-------------: |\n| **专题1** | **数据结构与算法** |                                                              |                 |\n|           |                    | :gem:【慕课网】程序员的内功修炼，学好算法与数据结构          |                 |\n|           |                    | :gem:【慕课网】玩转算法面试 leetcode题库分门别类详细解析     |                 |\n|           |                    | :gem:【慕课网】玩转数据结构 从入门到进阶                     |                 |\n|           |                    | 【牛客网】算法高频题目精讲                                   |                 |\n|           |                    | 【牛客网】直通BAT — 求职算法精品课                           |                 |\n| **专题2** | **后端技术栈**     |                                                              |                 |\n|           | 00 Web基础         |                                                              |                 |\n|           |                    | 【表严肃】讲正则表达式                                       |      完成       |\n|           | 01 Java            |                                                              |                 |\n|           |                    | 【翁凯】Java基础                                             |      完成       |\n|           |                    | 【慕课网】Java零基础入门                                     |                 |\n|           |                    | :gem:【极客学院】极客学院23种设计模式                        |                 |\n|           |                    | :gem:【炼数成金】深入JVM内核—原理、诊断与优化                |                 |\n|           |                    | :gem:【炼数成金】实战Java高并发程序设计                      |                 |\n|           |                    | 【慕课网】Java并发编程与高并发解决方案                       |      **🚴**      |\n|           |                    | 【慕课网】Google面试官亲授 升级Java面试                      |                 |\n|           |                    | 【慕课网】Java读源码之Netty深入剖析                          | 有时间回来学习  |\n|           |                    | 【慕课在线】Netty入门之WebSocket初体验 \\| [慕课网](https://www.imooc.com/learn/941) |                 |\n|           | 02 Java web        |                                                              |                 |\n|           |                    | [【慕课网在线】Spring Boot热部署](https://www.imooc.com/learn/915) \\| [参考文档](https://www.cnblogs.com/magicalSam/p/7196355.html) |     进行中      |\n|           |                    | 【慕课网】ZooKeeper分布式专题与Dubbo微服务入门               |                 |\n|           |                    | :gem:【慕课网】Spring Cloud微服务实战_廖师兄                 |                 |\n|           |                    | :gem:【慕课网】Spring Boot企业微信点餐系统                   |      完成       |\n|           |                    | 【神马小风】Spring MVC4入门                                  |      完成       |\n|           |                    | ~~【极客学院】JavaWeb工程师(全套)~~ \\| 部分可以学习          |                 |\n|           |                    | 神码小风：SpringMVC4从入门到实战视频教程 \\| [腾讯课堂](https://ke.qq.com/course/263024) |      完成       |\n|           | 03 Web Server      |                                                              |                 |\n|           |                    | :gem:【慕课网】Nginx从入门到实践                             |                 |\n|           | 04 Linux           |                                                              |                 |\n|           |                    | 【慕课网】大数据的Linux基础                                  |                 |\n|           |                    | 【慕课网】快速上手Linux 玩转典型应用                         |                 |\n|           |                    | 【莫烦】Linux 简易教学                                       |                 |\n|           |                    | 【慕课网在线】[Crontab不知疲倦的时间表](https://www.imooc.com/learn/1009) |                 |\n|           | 05 网络            |                                                              |                 |\n|           |                    | **:gem:【慕课网】HTTP协议原理+实践 Web开发工程师必学**       | **:bicyclist:** |\n|           | 06 数据库          |                                                              |                 |\n|           |                    | 【慕课网】MySQL性能管理及架构设计                            |                 |\n|           |                    | 【慕课网】Redis从入门到高可用，分布式实践                    |                 |\n|           | 07 Git             |                                                              |                 |\n|           |                    | 【表严肃】讲Git                                              |      完成       |\n|           |                    | 【莫烦】01 Git 版本管理                                      |                 |\n|           | 08 docker          |                                                              |                 |\n|           |                    | :gem:【慕课网】系统学习Docker 践行DevOps理念                 |                 |\n|           |                    | 【慕课网】Docker环境下的前后端分离项目部署与运维             |                 |\n| **专题3** | **机器学习**       |                                                              |                 |\n|           |                    | :gem:【慕课网—刘宇波】Python3入门机器学习 经典算法与应用     |                 |\n|           |                    | ~~【慕课网】机器学习入门 Scikit-learn实现经典小案例~~        |                 |\n|           |                    | ~~【慕课网】基于Python玩转人工智能最火框架 TensorFlow应用实践~~ |                 |\n| **专题4** | **Python**         |                                                              |                 |\n|           |                    | ~~【慕课网】Python3入门与进阶~~                              |                 |\n|           |                    | ~~【莫烦】Python+机器学习系列~~                              |                 |\n|           |                    | 【慕课网】Python Flask构建可扩展的RESTful API                |                 |\n|           |                    |                                                              |                 |\n| **专题5** | **PHP**            |                                                              |                 |\n|           |                    | 【慕课网】360大牛全面解读PHP面试                             |      完成       |\n|           |                    | 【网易云课堂】thinkphp5开发restful-api接口                   |      完成       |\n| **专题6** | 工具篇             |                                                              |                 |\n|           |                    | [【慕课网在线】IntelliJ IDEA神器使用技巧](https://www.imooc.com/learn/924) |      完成       |\n|           |                    |                                                              |                 |\n|           |                    |                                                              |                 |\n|           |                    |                                                              |                 |\n|           |                    |                                                              |                 |\n|           |                    |                                                              |                 |\n|           |                    |                                                              |                 |\n\n\n\n\n\n### 学习图书（精简版）\n\n以下是需要学习的清单\n\n| 编号 | 类型              | 图书                                | 进展 |\n| ---- | ----------------- | ----------------------------------- | ---- |\n| 1    | 面试宝典          | **:gem:《Java程序员面试笔试宝典》** |      |\n| 2    | 面试宝典          | 《程序员代码面试指南》结合Leetcode  |      |\n| 3    | Java              | **《Java并发编程实战》**            |      |\n| 4    | Java              | 《深入理解Java虚拟机》              |      |\n| 5    | Java（设计模式）  | **《设计模式之禅》**                |      |\n| 6    | Java Web          | 《Spring实战》                      |      |\n| 7    | Java Web          | 《深入分析Java Web技术内幕》        |      |\n| 8    | 数据库            | 《高性能MySQL》                     |      |\n| 9    | 计算机网络        | **《图解HTTP》**                    |      |\n| 10   | 数据结构与算法    | 《计算机考研——机试指南》            |      |\n| 11   | 数据结构与算法    | 《数据结构高分笔记》                |      |\n| 12   | 操作系统（Linux） | 《快乐的Linux命令行（书）》         |      |\n\n深入理解计算机系统\n\n自顶向下计算机网络\n\n\n\nNginx阿里巴巴文档\n\n\n\n### 课程与图书总览\n\n以下是所有待学习图书，仅作为全文预览检索\n\n详情转向 [course/LEARN_FULL.md](LEARN_FULL.md)\n\n"
  },
  {
    "path": "course/LEARNLIST.md",
    "content": "# 阅读清单与学习课程\n\n在这部分，我将对阅读书籍和学习课程进程推荐，在课程中我将附带官网链接，图书则附京东的链接。\n\n正在整理，未完待续\n\n\n\n## 一、数据结构与算法\n\n包含数据结构与算法两部分。\n\n### :books:阅读清单\n\n- [《算法4》](https://item.jd.com/11098789.html)\n- [《剑指Offer》](https://item.jd.com/12163054.html)\n- [《程序员面试指南》](https://item.jd.com/11770838.html)\n\n### :tv:学习课程\n\n- [【慕课网】刘宇波：玩转数据结构，从入门到进阶](https://coding.imooc.com/class/207.html)\n- [【慕课网】刘宇波：程序员的内功修炼，学好算法与数据结构](https://coding.imooc.com/class/71.html)\n- [【慕课网】刘宇波：玩转算法面试 leetcode题库分门别类详细解析](https://coding.imooc.com/class/82.html)\n\n\n\n\n\n## 二、Java\n\n包含 Java 核心知识和 Java Web 框架。\n\n### :books:阅读清单\n\n- [《Java并发编程实战》](https://item.jd.com/10922250.html) \n- \n- [《深入分析Java Web技术内幕》](https://item.jd.com/11520670.html)\n- 《阿里巴巴Java开发手册》\n- 《Java程序员面试笔试宝典》\n- 《Java网络编程》\n- 《Spring实战》\n- [《Spring MVC+MyBatis开发从入门到项目实战》](https://item.jd.com/12308496.html)\n- 《深入理解Java虚拟机》\n- 《Java核心技术 卷Ⅰ / Ⅱ》\n\n### :tv:学习课程\n\n- 【廖雪峰】Java教程\n- 【龙果学院】Java并发编程原理与实战\n- 【龙果学院】深入理解Java虚拟机（jvm性能调优+内存模型+虚拟机原理）\n- 【慕课网】Java零基础入门\n- 【尚学堂】白鹤翔_jvm虚拟机优化\n- 【网易云课堂】Java开发工程师（Web方向）翁凯\n- 【慕课网】Spring Cloud微服务实战_廖师兄\n- 【黑马程序员】SSH框架_王泽\n- 【黑马程序员】SpringMVC+Mybatis\n- 【牛客网】初中高Python+Java项目实战_叶神\n\n\n\n## 三、Web Server\n\n- 【网易云课堂】thinkphp5开发restful-api接口\n\n\n\n## 四、面向对象\n\n- [《设计模式之禅》](https://item.jd.com/11414555.html)\n\n- 【极客学院】极客学院23种设计模式\n\n\n\n## 五、数据库\n\n### :books:阅读清单\n\n- 《高性能MySQL》\n- 《Redis实战》\n\n### :tv:学习课程\n\n- 【慕课网】MySQL性能管理及架构设计\n- 【慕课网】Redis从入门到高可用，分布式实践\n\n\n\n## 六、操作系统\n\n### :books:阅读清单\n\n- 《Linux+C程序设计大全》\n- 《快乐的Linux命令行》\n- 《深入理解计算机系统》\n\n### :tv:学习课程\n\n- [【慕课网】快速上手Linux 玩转典型应用](https://coding.imooc.com/class/154.html)\n- [【慕课在线】Linux达人养成计划 I-Linux的入门级课程！](https://www.imooc.com/learn/175)\n- [【慕课在线】Linux 达人养成计划 II VIM+磁盘管理+用户权限！](https://www.imooc.com/learn/111)\n- 【小甲鱼】零基础入门学习汇编语言\n- 操作系统_清华大学(向勇、陈渝)\n\n\n\n## 七、计算机网络\n\n### :books:阅读清单\n\n- 《图解HTTP》\n- 《计算机网络原理创新教程》韩立刚主编\n\n### :tv:学习课程\n\n- 【51CTO】韩老师-计算机网络原理-156讲\n- 【慕课网】HTTP协议原理+实践 Web开发工程师必学\n\n\n\n\n\n## 八、系统架构\n\n### :books:阅读清单\n\n### :tv:学习课程\n\n- 【慕课网】Nginx从入门到实践\n- 【咕泡学院】架构师系列课程\n\n\n\n## 九、面试\n\n- 【慕课网】360大牛全面解读PHP面试\n\n- 【慕课网】Google面试官亲授 升级Java面试\n\n\n\n## 十、机器学习\n\n### :books:阅读清单\n\n- 《机器学习实战》\n\n### :tv:学习课程\n\n- 【慕课网】Python3入门机器学习 经典算法与应用\n- 【莫烦Python】机器学习系列\n\n\n\n## 十一、工具\n\n### :tv:学习课程\n\n- 【表严肃】讲正则表达式\n- 【表严肃】讲Git\n- 【慕课网】系统学习Docker 践行DevOps理念\n- [【慕课在线】IntelliJ IDEA神器使用技巧](https://www.imooc.com/learn/924)\n\n"
  },
  {
    "path": "course/LEARN_FULL.md",
    "content": "## PART2：学习总览\n\n\n\n### 学习课程总览\n\n| 专题      | 类型               | 课程                                                         | 进展 |\n| --------- | :----------------- | ------------------------------------------------------------ | ---- |\n| **专题1** | **数据结构与算法** |                                                              |      |\n|           |                    | 【慕课网】程序员的内功修炼，学好算法与数据结构               |      |\n|           |                    | 【慕课网】玩转算法面试 leetcode题库分门别类详细解析          |      |\n|           |                    | 【慕课网】玩转数据结构                                       |      |\n|           |                    | 【牛客网】算法高频题目精讲                                   |      |\n|           |                    | 【牛客网】直通BAT — 求职算法精品课                           |      |\n|           |                    | 【小甲鱼】数据结构与算法                                     |      |\n|           |                    | 【慕课网】看得见的算法 7个经典应用诠释算法精髓               |      |\n| **专题2** | **后端技术栈**     |                                                              |      |\n|           | 00 Web基础         |                                                              |      |\n|           |                    | 【表严肃】讲正则表达式                                       |      |\n|           | 01 Java            |                                                              |      |\n|           |                    | 【翁凯】Java基础                                             |      |\n|           |                    | 【慕课网】Java零基础入门                                     |      |\n|           |                    | 【极客学院】极客学院23种设计模式                             |      |\n|           |                    | 【炼数成金】深入JVM内核—原理、诊断与优化                     |      |\n|           |                    | 【炼数成金】实战Java高并发程序设计                           |      |\n|           |                    | 【龙果学院】深入理解Java虚拟机                               |      |\n|           |                    | 【慕课网】Google面试官亲授 升级Java面试                      |      |\n|           | 02 Java web        |                                                              |      |\n|           |                    | 【慕课网】ZooKeeper分布式专题与Dubbo微服务入门               |      |\n|           |                    | 【慕课网】Spring Cloud微服务实战_廖师兄                      |      |\n|           |                    | 【慕课网】Spring Boot企业微信点餐系统                        |      |\n|           |                    | 【慕课网】Java秒杀系统方案优化 高性能高并发实战              |      |\n|           |                    | 【慕课网】IT段子手详解MyBatis遇到Spring 秒学Java SSM开发大众点评 |      |\n|           |                    | 【慕课网】Docker+Kubernetes(k8s)微服务容器化实践             |      |\n|           |                    | 【极客学院】JavaWeb工程师(全套)                              |      |\n|           |                    | ~~【传智播客】Springmvc+Mybatis由浅入深全套视频教程~~        |      |\n|           |                    | ~~【传智播客】孔浩Java课程~~                                 |      |\n|           |                    | ~~【慕课网】Java Spring带前后端开发完整电商平台~~            |      |\n|           |                    | ~~【慕课网】Java从零到企业级电商项目实战~~                   |      |\n|           |                    | ~~【慕课网】Spring Boot企业级博客系统实战视频教程~~          |      |\n|           | 03 Web Server      |                                                              |      |\n|           |                    | 【慕课网】Nginx从入门到实践                                  |      |\n|           | 04 Linux           |                                                              |      |\n|           |                    | 【慕课网】大数据的Linux基础                                  |      |\n|           |                    | 【慕课网】快速上手Linux 玩转典型应用                         |      |\n|           |                    | 【莫烦】Linux 简易教学                                       |      |\n|           | 05 网络            |                                                              |      |\n|           |                    | 【慕课网】HTTP协议原理+实践 Web开发工程师必学                |      |\n|           | 06 数据库          |                                                              |      |\n|           |                    | 【慕课网】MySQL性能管理及架构设计                            |      |\n|           |                    | 【慕课网】Redis从入门到高可用，分布式实践                    |      |\n|           | 07 Git             |                                                              |      |\n|           |                    | 【表严肃】讲Git                                              |      |\n|           |                    | 【莫烦】01 Git 版本管理                                      |      |\n|           | 08 docker          |                                                              |      |\n|           |                    | 【极客学院】docker                                           |      |\n|           |                    | 【慕课网】系统学习Docker 践行DevOps理念                      |      |\n| **专题3** | **机器学习**       |                                                              |      |\n|           |                    | 【麦子学院】机器学习系列 × 3                                 |      |\n|           |                    | 【慕课网】机器学习入门 Scikit-learn实现经典小案例            |      |\n|           |                    | 【慕课网】基于Python玩转人工智能最火框架 TensorFlow应用实践  |      |\n|           |                    | 【慕课网—刘宇波】Python3入门机器学习 经典算法与应用          |      |\n|           |                    | 【斯坦福】Andrew Ng机器学习                                  |      |\n| **专题4** | **Python**         |                                                              |      |\n|           |                    | 【慕课网】Python3入门与进阶                                  |      |\n|           |                    | 【慕课网】2017 Vue+Django API前后端分离开发电商新技术跨域项目实战 |      |\n|           |                    | 【慕课网】Python高级编程和异步IO并发编程                     |      |\n|           |                    | 【慕课网】Python升级3.6 强力Django+杀手级Xadmin打造在线教育平台 |      |\n|           |                    | 【慕课网】Vue+Django REST framework 打造生鲜电商项目         |      |\n|           |                    | 【莫烦】Python+机器学习系列                                  |      |\n| **专题5** | **PHP**            |                                                              |      |\n|           |                    | 【慕课网】360大牛全面解读PHP面试                             |      |\n|           |                    | 【慕课网】PHP接口开发                                        |      |\n|           |                    | 【网易云课堂】thinkphp5开发restful-api接口                   |      |\n\n\n\n\n\n### 学习书籍总览\n\n以下是所有待学习图书，仅作为全文预览检索\n\n| 类型              | 图书                                         | 进展 |\n| ----------------- | -------------------------------------------- | ---- |\n| 面试宝典          | 《Java程序员面试笔试宝典》                   |      |\n| 面试宝典          | 《程序员代码面试指南》                       |      |\n| 面试宝典          | ~~《剑指Offer》~~                            |      |\n| 面试宝典          | ~~《技术之瞳》~~                             |      |\n| 面试宝典          | 《Java程序员面试面试笔试真题与解析》         |      |\n| Java              | 《Java并发编程实战》                         |      |\n| Java              | 《深入理解Java虚拟机》                       |      |\n| Java              | ~~《Java核心技术 卷Ⅰ / Ⅱ》~~                 |      |\n| Java（设计模式）  | 《设计模式之禅》                             |      |\n| Java Web          | 《Spring实战》                               |      |\n| Java Web          | 《深入分析Java Web技术内幕》                 |      |\n| 数据库            | 《高性能MySQL》                              |      |\n| 计算机网络        | 《图解HTTP》                                 |      |\n| 数据结构与算法    | 《计算机考研——机试指南》                     |      |\n| 数据结构与算法    | 《数据结构高分笔记》                         |      |\n| 操作系统（Linux） | 《快乐的Linux命令行（书）》                  |      |\n| 操作系统          | ~~《操作系统高分笔记》~~                     |      |\n| 机器学习          | ~~《机器学习实战》~~                         |      |\n| 机器学习          | ~~《终极算法》~~                             |      |\n| 机器学习          | ~~《机器学习在线——解析阿里云机器学习平台》~~ |      |\n| 计算机视觉        | ~~《OpenCV3编程入门》~~                      |      |\n| 阿里丛书          | ~~《尽在双十一》~~                           |      |\n\n\n\n"
  },
  {
    "path": "course/LEARN_RECORD.md",
    "content": "## PART2：学习记录\n\n### 已完成的课程\n\n| 课程                                                         | 进展            |\n| ------------------------------------------------------------ | --------------- |\n| [SpringBoot+MyBatis搭建迷你小程序教程](https://www.imooc.com/learn/945) | 2018/05/06 完成 |\n| [IntelliJ IDEA神器使用技巧](https://www.imooc.com/learn/924) | 2018/05/14 完成 |\n| [表严肃讲正则表达式](http://biaoyansu.com/28.x)              | 2018/06/05 完成 |\n| [表严肃讲Git](http://biaoyansu.com/27.x)                     | 2018/06/05 完成 |\n| [第一个docker化的java应用](https://www.imooc.com/learn/824)  | 2018/06/06 完成 |\n\n\n\n### 学习课程\n\n| 课程                                                         | 进展                         |\n| ------------------------------------------------------------ | ---------------------------- |\n| ★★★刘宇波：玩转数据结构 \\| [代码仓库](https://github.com/liuyubobobo/Play-with-Algorithms) \\| [学习笔记](course\\01 玩转数据结构.md ) | 2018/05/06 学习至 第三章<br> |\n| ★刘宇波：玩转算法面试 \\| [学习笔记](course/02 玩转算法面试.md) | 计划学习                     |\n| ★刘宇波：程序员的内功修炼                                    | 计划学习                     |\n| ★★★廖师兄：[Spring Boot企业微信点餐系统](https://coding.imooc.com/class/117.html) \\| [学习笔记](course\\03 SpringBoot微信点餐.md ) | 2018/05/06 学习至 第五章     |\n| [微信授权登录](https://www.imooc.com/learn/713)              |                              |\n| [PHP第三方登录—OAuth2.0协议](https://www.imooc.com/learn/557) |                              |\n|                                                              |                              |\n| Redis从入门到高可用，分布式实践 \\| [慕课网](https://coding.imooc.com/class/151.html) | 计划学习                     |\n| ZooKeeper分布式专题与Dubbo微服务入门 \\| [慕课网](https://coding.imooc.com/class/201.html) | 计划学习                     |\n|                                                              |                              |\n| [Redis入门](https://www.imooc.com/learn/839)                 | 计划学习                     |\n|                                                              |                              |\n| HTTP协议原理+实践 Web开发工程师必学                          | 正在学习                     |\n| ★Nginx入门到实践 \\| [慕课网](https://coding.imooc.com/class/evaluation/121.html#Anchor) \\| [学习笔记](course\\04 Nginx从入门到实战.md) | 正在学习                     |\n| 系统学习Docker 践行DevOps理念                                | 正在学习                     |\n\n"
  },
  {
    "path": "notes/DeepLearning/README.md",
    "content": "深度学习板块\n\n"
  },
  {
    "path": "notes/DeepLearning/assets/README.md",
    "content": "深度学习板块-图片文件夹"
  },
  {
    "path": "notes/DeepLearning/深度学习入门课程.md",
    "content": "## 入门课程\n\n> 简介：本课程首先介绍机器学习与深度学习，包括机器学习的应用、岗位职责，深度学习的等。然后通过讲解神经元及其衍生模型逻辑斯底回归、目标函数、梯度下降等深度学习基础知识。最后通过Tensorflow来实现课程讲解的模型。\n\n深度学习之神经网络入门最佳路径\nhttps://www.imooc.com/learn/1063\n\n\n\n深度学习-初识 - Corwien - SegmentFault 思否\nhttps://segmentfault.com/a/1190000016068053\n\n\n\n## 从入门到实战\n\n实战课已经上线：https://coding.imooc.com/class/259.html"
  },
  {
    "path": "notes/DeepLearning/深度学习初识.md",
    "content": "<!-- TOC -->\n\n- [深度学习初识](#深度学习初识)\n    - [一、入门基本概念](#一入门基本概念)\n        - [机器学习简介](#机器学习简介)\n            - [机器学习应用举例](#机器学习应用举例)\n            - [机器学习应用流程](#机器学习应用流程)\n            - [机器学习岗位职责](#机器学习岗位职责)\n        - [深度学习简介](#深度学习简介)\n            - [深度学习与机器学习关系](#深度学习与机器学习关系)\n            - [深度学习算法集合](#深度学习算法集合)\n            - [深度学习进展](#深度学习进展)\n    - [二、神经网络](#二神经网络)\n        - [人体神经元模型](#人体神经元模型)\n        - [人工神经网络](#人工神经网络)\n        - [1. 神经元](#1-神经元)\n        - [2. 逻辑回归模型](#2-逻辑回归模型)\n        - [目标函数](#目标函数)\n        - [梯度下降](#梯度下降)\n    - [三、Tensorflow基础](#三tensorflow基础)\n        - [Tensorflow简介](#tensorflow简介)\n        - [计算图模型](#计算图模型)\n    - [TensorFlow 安装](#tensorflow-安装)\n    - [参考资料](#参考资料)\n\n<!-- /TOC -->\n\n# 深度学习初识\n\n## 一、入门基本概念\n\n### 机器学习简介\n\n**机器学习**：无序数据转化为价值的方法\n**机器学习价值**：从数据中抽取规律，并预测未来\n\n\n\n#### 机器学习应用举例\n\n- 分类问题：图像识别、垃圾邮件识别\n- 回归问题：股价预测、房价预测\n- 排序问题：点击率预估、推荐\n- 生成问题：图像生成、图像风格转换、图像文字描述生成\n\n\n\n#### 机器学习应用流程\n\n![image-20190405103241674](assets/image-20190405103241674.png)\n\n\n\n#### 机器学习岗位职责\n\n- 数据处理（采集+去噪）\n- 模型训练（特征+模型）\n- 模型评估与优化（MSE、F1-score、AUC+调参）\n- 模型应用（A/B测试）\n\n\n\n### 深度学习简介\n\n![img](assets/b248f2b9d659c18808a7b75d7537dea09a886564.jpg)\n\n人工智能、机器学习、深度学习之间的关系\n\n```\n人工智能（AI）> 机器学习（Machine Learning）> 深度学习（Deep learning）\n```\n\n\n\n#### 深度学习与机器学习关系\n\n- 机器学习是实现人工智能的方法\n- 深度学习是实现机器学习算法的技术\n\n\n\n#### 深度学习算法集合\n\n- **卷积神经网络**(Convolutional Neural Network, CNN)\n  - CV 领域使用较多\n- **循环神经网络**(Recurrent Neural Networks，RNNs)\n  - NLP 领域使用较多\n  - 处理不定长数据\n- 自动编码器\n- 稀疏编码\n- 深度信念网络\n- 深度学习 + 强化学习 = **深度强化学习**\n  - AlphaGo\n  - AlphaZero\n\n\n\n#### 深度学习进展\n\n1. 图像分类\n\nImageNet: http://image-net.org/\n\n![v2-718f95df083b2d715ee29b018d9eb5c2_1200x500](assets/v2-718f95df083b2d715ee29b018d9eb5c2_1200x500.jpg)\n\n\n\n2. 机器翻译\n\n![机器翻译](assets/640.jpeg)\n\n3. 图像生成\n\n![image_1_0_0](assets/image_1_0_0.jpg)\n\n![v2-4bcf97eff1afd63aab37c95d751f270f_r](assets/v2-4bcf97eff1afd63aab37c95d751f270f_r.jpg)\n\n![river-starrynight-combined](assets/river-starrynight-combined.png)\n\n- [Implementing Neural Artistic Style Transfer | L2Program](http://l2program.co.uk/945/implementing-neural-artistic-style-transfer)\n\n4. 字体生成\n\n5. AlphaGo\n\n\n\n## 二、神经网络\n\n### 人体神经元模型\n\n神经网络模型是模仿人类的大脑神经构造而构造的。先来看人体神经元模型。\n\n![img](assets/neural-network-3.png)\n\n神经元的可以分为四个区域：\n\n- 接收区（receptive zone）：树突接收到输入信息。\n- 触发区（trigger zone）：位于轴突和细胞体交接的地方，决定是否产生神经冲动。\n- 传导区（conducting zone）：由轴突进行神经冲动的传递。\n- 输出区（output zone）：神经冲动的目的就是要让神经末梢，突触的神经递质或电力释出，才能影响下一个接受的细胞（神经元、肌肉细胞或是腺体细胞），此称为突触传递。\n\n\n\n### 人工神经网络\n\n人工神经网络（ANN：Artificial Neural Network），简称神经网络（NN：Neural Network）。迄今为止，人工神经网络尚无统一定义， 其实一种模拟了人体神经元构成的数学模型，依靠系统的复杂程度，通过调整内部大量节点之间相互连接的关系，从而达到处理信息的目的。\n\n![img](assets/1*OvTgvr5SNAp579n9kyBzAw.png)\n\n上图显示了人工神经网络是一个分层模型，逻辑上可以分为三层：\n\n- 输入层：输入层接收特征向量 x 。\n- 输出层：输出层产出最终的预测 h 。\n- 隐含层：隐含层介于输入层与输出层之间，之所以称之为隐含层，是因为当中产生的值并不像输入层使用的样本矩阵 X 或者输出层用到的标签矩阵 y 那样直接可见。\n\n\n\n\n\n### 1. 神经元\n\n![img](assets/2112343587-5b78f2b533cd1.png)\n\n![img](assets/3893839953-5b78f3ea9066e.png)\n\n\n\n![image-20190405111251357](assets/image-20190405111251357.png)\n\n\n\n### 2. 逻辑回归模型\n\n```\n神经元 -> 激活函数sigmoid -> 二元类逻辑斯蒂回归模型\n```\n\n![img](assets/1388465484-5b78f514b3a47.png)\n\n\n\n神经元 -> 多输出\n\n- W从向量扩展为矩阵\n- 输出W*x则变成向量\n\n![img](https://image-static.segmentfault.com/342/024/3420244370-5b78f5943f6bc)\n\n\n\n多输出神经元 -> softmax -> 多分类逻辑斯蒂回归模型\n\n![image-20190405111638911](assets/image-20190405111638911.png)\n\n![image-20190405111848924](assets/image-20190405111848924.png)\n\n![image-20190405111934921](assets/image-20190405111934921.png)\n\n### 目标函数\n\n也称为损失函数，衡量对数据的拟合程度\n\n\n\n### 梯度下降\n\n- 下山算法\n  - 找方向\n  - 走一步\n\n- 梯度下降算法\n\n![img](assets/gradient.png)\n\n## 三、Tensorflow基础\n\n### Tensorflow简介\n\nGoogle Brain 第二代机器学习框架\n\n\n\n### 计算图模型\n\n- 命令式变成\n- 声明式变成\n\n![img](assets/1867956752-5b78fbe0b8f6b.png)\n\n\n\n## TensorFlow 安装\n\n- [TensorFlow 官方文档](https://tensorflow.google.cn/)\n\n- [TensorFlow 安装方法](https://tensorflow.google.cn/install)\n\n\n\n1. 基于 VirtualEnv 的安装\n2. 原生 pip\n3. Docker\n4. 从源代码安装\n\n\n\n## 参考资料\n\n- [机器学习入门(五) -- 神经网络](https://waltyou.github.io/Neural-Network/#%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E5%AE%9A%E4%B9%89)\n\n- [深度学习-初识 - Corwien - SegmentFault 思否](https://segmentfault.com/a/1190000016068053)"
  },
  {
    "path": "notes/DistributedSystem/03 分布式通信-序列化.md",
    "content": "# java序列化机制Serialize接口\n\n## java本身的序列化机制存在的问题\n\n1. 序列化数据结果比较大、传输效率比较低\n\n2. 不能跨语言对接\n\n以至于在后来的很长一段时间，基于XML格式编码的对象序列化机制成为了主流，一方面解决了多语言兼容问题，另一方面比二进制的序列化方式更容易理解。以至于基于XML的SOAP协议及对应的WebService框架在很长一段时间内成为各个主流开发语言的必备的技术。\n\n \n\n再到后来，基于JSON的简单文本格式编码的HTTP REST接口又基本上取代了复杂的Web Service接口，成为分布式架构中远程通信的首要选择。但是JSON序列化存储占用的空间大、性能低等问题，同时移动客户端应用需要更高效的传输数据来提升用户体验。在这种情况下与语言无关并且搞笑的二进制编码协议就成为了大家追求的热点技术之一。首先诞生的一个开源的二进制序列化框架-MessagePack。它比google的Protocol Buffers出现得还要早\n\n \n\n恰当的序列化协议不仅可以提高系统的通用性、强壮型、安全性、优化性能。同时还能让系统更加易于调试和扩展\n\n \n\n## 序列化和反序列化的概念\n\n把对象转化为字节序列的过程称之为对象的序列化\n\n反之，称之为反序列化\n\n# 怎么去实现一个序列化操作\n\n1. 实现Serializable接口\n\n2. ObjectInputStream  : 表示读取指定的字节数据转换成对象\n\n3. ObjectOutputStream ：\n\n \n\n# 科普小知识\n\n## serialVersionUID的作用\n\n文件流中的class和classpath中的class，也就是修改过后的class，不兼容了，处于安全机制考虑，程序抛出了错误，并且拒绝载入。从错误结果来看，如果没有为指定的class配置serialVersionUID，那么java编译器会自动给这个class进行一个摘要算法，类似于指纹算法，只要这个文件有任何改动，得到的UID就会截然不同的，可以保证在这么多类中，这个编号是唯一的。所以，由于没有显指定 serialVersionUID，编译器又为我们生成了一个UID，当然和前面保存在文件中的那个不会一样了，于是就出现了2个序列化版本号不一致的错误。因此，只要我们自己指定了serialVersionUID，就可以在序列化后，去添加一个字段，或者方法，而不会影响到后期的还原，还原后的对象照样可以使用，而且还多了方法或者属性可以用。\n\n## 静态变量的序列化\n\n序列化并不保存静态变量的状态（例如序列号之前修改了静态变量，则会被序列化）。\n\n## Transient关键字\n\ntransient关键字表示指定属性不参与序列化\n\n## 父子类问题\n\n如果父类没有实现序列化，而子类实现列序列化。那么父类中的成员没办法做序列化操作\n\n## 序列化的存储规则\n\n对同一个对象进行多次写入，打印出的第一次存储结果和第二次存储结果，只多了5个字节的引用关系。\n\n并不会导致文件累加\n\n \n\n# 序列化实现深度克隆\n\n**浅拷贝（浅复制、浅克隆）**：被复制对象的所有变量都含有与原来的对象相同的值，而所有的对其他对象的引用仍然指向原来的对象。换言之，浅拷贝仅仅复制所拷贝的对象，而不复制它所引用的对象。\n\n**深拷贝（深复制、深克隆）**：被复制对象的所有变量都含有与原来的对象相同的值，除去那些引用其他对象的变量。\n\n　　那些引用其他对象的变量将指向被复制过的新对象，而不再是原有的那些被引用的对象。\n\n　　换言之，深拷贝把要复制的对象所引用的对象都复制了一遍\n\n \n\n# 总结\n\n1. 在java中，只要一个类实现了java.io.Serializable接口，那么它就可以被序列化\n\n2. 通过ObjectOutputStream和ObjectInputStream对对象进行序列化合反序列化操作\n\n3. 对象是否允许被反序列化，不仅仅是取决于对象的代码是否一致，同时还有一个重要的因素（UID）\n\n4. 序列化不保存静态变量\n\n5. 要想父类对象也参与序列化操作，那么必须要让父类也实现Serializable接口\n\n6. Transient关键字，主要是控制变量是否能够被序列化。如果没有被序列化的成员变量反序列化后，会被设置成初始值，比如String -> null\n\n7. 通过序列化操作实现深度克隆\n\n \n\n# 主流的序列化技术有哪些\n\nJSON/Hessian(2) /xml/protobuf/kryo/MsgPack/FST/thrift/protostuff/Avro\n\n \n\n \n\n"
  },
  {
    "path": "notes/DistributedSystem/04 分布式通信协议-http.md",
    "content": "# HTTP协议的概述\n\n1.  客户端和服务器端\n\n![img](assets/wps306F.tmp.jpg) \n\n \n\n2. 资源\n\n   html/文本、word、avi电影、其他资源\n\n3. 媒体类型\n\n   MIME类型（*MIME* (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准。 *MIME*消息能包含文本、图像、音频、视频以及其他应用程序专用的数据。 ）\n\n   text/html\n\n   image/jpeg \n\n4. URI和URL\n\n   URI：web服务器资源的名字，例如： index.html\n\n   URL：`http://www.gupaoedu.com:80/java/index.html[?query-string] #location`\n\n   schema: http/https/ftp.\n\n   host: web服务器的ip地址或者域名\n\n   port: 服务端端口， http默认访问的端口是80\n\n   path: 资源访问路径\n\n   query-string: 查询参数\n\n5. 方法\n\n   GET/PUT/DELETE/POST/HEAD\n\n HEAD：主要用于确认 URL 的有效性以及资源更新的日期时间等。\n\n### \n\n# 报文\n\nrequest参数、 response响应参数\n\nrequest消息结构包含三部分： （起始行、首部字段、主体） \n\n \n\nMETHOD /path / http/version-number\n\nHeader-Name:value\n\n空行\n\n主体 optional request body\n\n \n\n![img](assets/wps3080.tmp.jpg) \n\n \n\n \n\nresponse\n\nhttp/version-number   status code message\nheader-name:value\n\n \n\nbody\n\n \n\n![img](assets/wps3081.tmp.jpg) \n\n \n\n# 状态码\n\nhttp/1.1版本的协议里面定义了五种类型的状态码\n\n1XX    提示信息\n\n2XX    成功\n\n3XX    重定向\n\n4XX    客户端错误\n\n5XX    服务器端的错误 \n\n \n\n# 缓存\n\nHTTP协议的特点\n\n1. 无状态\n\n   cookie+session\n\n2. 多次请求\n\n3. 基于TCP协议\n\n \n\n \n\n# HTTPS\n\nSSL/TLS\n\n目前最新：SSL 3.0 \n\nISOC  在 SSL 的基础上发布了升级版本 TLS1.2（最新）\n\n \n\n## HTTPS的工作原理\n\n![img](assets/wps3082.tmp.jpg) \n\n \n\n### 第一步， 使用对称加解密\n\n \n\n![img](assets/wps3083.tmp.jpg) \n\n \n\n### 第二步，密钥是公开的，所有的客户端都可以拿到\n\n![img](assets/wps3093.tmp.jpg) \n\n \n\n### 第三步 针对不同的客户端使用不同的密钥\n\n![img](assets/wps3094.tmp.jpg) \n\n \n\n问题：协商过程是没有加密的，所以还会出现被截断的问题\n\n \n\n### 第四步：使用非对称加密\n\n非对称：公钥和私钥的概念 \n\n![img](assets/wps3095.tmp.jpg) \n\n \n\n问题： 客户端如何拿到公钥\n\n1. 服务器端把公钥发送给每一个客户端\n\n2. 服务器端把公钥放到远程服务器，客户端可以请求到\n\n3. 让浏览器保存所有的公钥（不现实）\n\n \n\n### 第五步 公钥被调包的问题按照上面的方案，永远存在。\n\n![img](assets/wps3096.tmp.jpg) \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![img](assets/wps30A7.tmp.jpg) \n\n \n\n \n\n# RESTful\n\nREST表述性状态转移\n\n使用WEB标准来做一些准则和约束。\n\n \n\nRESTful的基本概念\n\n1. 在REST中，一切的内容都被认为是一种资源\n\n2. 每个资源都由URI唯一标识\n\n3. 使用统一的接口处理资源请求（POST/GET/PUT/DELETE/HEAD）\n\n4. 无状态（每次请求之前是无关联，没有session）\n\n \n\n## 资源和URI\n\n1. [/]表示资源的层级关系\n\n2. ？过滤资源\n\n3. 使用_或者-让URI的可读性更好\n\n \n\n## 统一接口\n\n GET  获取某个资源。 幂等（取多少次结果都没有变化）\n\n POST 创建一个新的资源\n\n PUT 替换某个已有的资源（更新操作） ， 幂等（更新多次只保存一个结果）\n\n DELETE 删除某个资源\n\n\n\n PATCH/HEAD （用的比较少）\n\n \n\n## 资源表述\n\n \n\nMIME 类型（）\n\naccept: text/xml   html文件\n\nContent-Type告诉客户端资源的表述形式\n\n \n\n \n\n## 资源链接\n\n 超媒体即应用状态引擎（可以做多层链接）\n\n https://api.github.com/repos/github\n\n```\n{\n  \"message\": \"Not Found\",\n  \"documentation_url\": \"https://developer.github.com/v3\"\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## RESTful的最佳设计\n\n \n\n1. 域名\n\nhttp://api.gupaoedu.com \n\n<http://api/gupaoedu.com/api>\n\n \n\n2. 版本\n\nhttp://api.gupaoedu.com/v1/user/1\n\nheader里面维护版本\n\n\n\n3. 路径\n\nhttp://api.gupaoedu.com/v1/users_list  //获取用户列表\n\n \n\nhttp://api.gupaoedu.com/v1/goods-list  //商品列表\n\n \n\nhttp://api.gupaoedu.com/v1/users/{id}\n\n\n\n4. 过滤信息\n\n<https://api.github.com/user/repos?page=2&per_page=100>\n\n<https://developer.github.com/v3/#rate-limiting> \n\n\n\n5.  状态码\n\n- 业务状态码\n\n- http状态码"
  },
  {
    "path": "notes/Docker.md",
    "content": "<!-- TOC -->\n\n- [前言](#前言)\n- [1. Docker 简介](#1-docker-简介)\n    - [1.1 什么是 Docker](#11-什么是-docker)\n    - [1.2 为什么要使用 Docker](#12-为什么要使用-docker)\n- [2. Docker 基本概念](#2-docker-基本概念)\n    - [2.1 Docker 引擎](#21-docker-引擎)\n    - [2.2 Docker 架构](#22-docker-架构)\n    - [2.3 Docker 镜像](#23-docker-镜像)\n    - [2.4 Docker 容器](#24-docker-容器)\n    - [2.5 Docker 仓库](#25-docker-仓库)\n- [3. 安装 Docker](#3-安装-docker)\n    - [3.1 Ubuntu 安装 Docker](#31-ubuntu-安装-docker)\n        - [准备工作](#准备工作)\n            - [系统要求](#系统要求)\n            - [卸载旧版本](#卸载旧版本)\n            - [Ubuntu 14.04 可选内核模块](#ubuntu-1404-可选内核模块)\n            - [Ubuntu 16.04 +](#ubuntu-1604-)\n        - [使用 APT 安装](#使用-apt-安装)\n            - [安装必要的一些系统工具](#安装必要的一些系统工具)\n            - [安装 GPG 证书](#安装-gpg-证书)\n            - [写入软件源信息](#写入软件源信息)\n            - [更新并安装 Docker CE](#更新并安装-docker-ce)\n        - [使用脚本自动安装](#使用脚本自动安装)\n            - [特别说明](#特别说明)\n        - [启动 Docker CE](#启动-docker-ce)\n        - [建立 docker 用户组](#建立-docker-用户组)\n        - [测试 Docker 是否安装正确](#测试-docker-是否安装正确)\n        - [镜像加速](#镜像加速)\n        - [参考文档](#参考文档)\n    - [3.2 CentOS 安装 Docker](#32-centos-安装-docker)\n        - [准备工作](#准备工作-1)\n            - [系统要求](#系统要求-1)\n            - [卸载旧版本](#卸载旧版本-1)\n        - [使用 yum 安装](#使用-yum-安装)\n            - [安装 Docker CE](#安装-docker-ce)\n        - [使用脚本自动安装](#使用脚本自动安装-1)\n        - [启动 Docker CE](#启动-docker-ce-1)\n        - [建立 docker 用户组](#建立-docker-用户组-1)\n        - [测试 Docker 是否安装正确](#测试-docker-是否安装正确-1)\n        - [镜像加速](#镜像加速-1)\n        - [添加内核参数](#添加内核参数)\n        - [参考文档](#参考文档-1)\n    - [3.3 树莓派卡片电脑安装 Docker](#33-树莓派卡片电脑安装-docker)\n        - [系统要求](#系统要求-2)\n        - [使用 APT 安装](#使用-apt-安装-1)\n            - [安装 Docker CE](#安装-docker-ce-1)\n        - [使用脚本自动安装](#使用脚本自动安装-2)\n        - [启动 Docker CE](#启动-docker-ce-2)\n        - [建立 docker 用户组](#建立-docker-用户组-2)\n        - [测试 Docker 是否安装正确](#测试-docker-是否安装正确-2)\n        - [镜像加速](#镜像加速-2)\n    - [3.4 macOS 安装 Docker](#34-macos-安装-docker)\n        - [系统要求](#系统要求-3)\n        - [安装](#安装)\n            - [使用 Homebrew 安装](#使用-homebrew-安装)\n            - [手动下载安装](#手动下载安装)\n        - [运行](#运行)\n        - [镜像加速](#镜像加速-3)\n    - [3.5 Windows 安装 Docker](#35-windows-安装-docker)\n        - [系统要求](#系统要求-4)\n        - [安装](#安装-1)\n        - [运行](#运行-1)\n        - [镜像加速](#镜像加速-4)\n    - [3.6 Docker 镜像加速器](#36-docker-镜像加速器)\n        - [Ubuntu 14.04、Debian 7 Wheezy](#ubuntu-1404debian-7-wheezy)\n        - [Ubuntu 16.04+、Debian 8+、CentOS 7](#ubuntu-1604debian-8centos-7)\n        - [Windows 10](#windows-10)\n        - [macOS](#macos)\n        - [检查加速器是否生效](#检查加速器是否生效)\n\n<!-- /TOC -->\n# 前言\n\nDocker 容器化引擎，更详细请转向：[Docker_Tutorial: Docker 容器化引擎入门手册](https://github.com/frank-lam/Docker_Tutorial)\n\n> 参考资料：\n>\n> - http://www.funtl.com/\n> - [Docker —— 从入门到实践](https://yeasy.gitbooks.io/docker_practice/content/)\n>\n> 基于以上部分内容做一定的修改\n\n# 1. Docker 简介\n\n## 1.1 什么是 Docker\n\nDocker 最初是 dotCloud 公司创始人 Solomon Hykes 在法国期间发起的一个公司内部项目，它是基于 dotCloud 公司多年云服务技术的一次革新，并于 [2013 年 3 月以 Apache 2.0 授权协议开源](https://en.wikipedia.org/wiki/Docker_(software))，主要项目代码在 [GitHub](https://github.com/moby/moby) 上进行维护。Docker 项目后来还加入了 Linux 基金会，并成立推动 [开放容器联盟（OCI）](https://www.opencontainers.org/)。\n\nDocker 自开源后受到广泛的关注和讨论，至今其 GitHub 项目已经超过 4 万 6 千个星标和一万多个 fork。甚至由于 Docker 项目的火爆，在 2013 年底，[dotCloud 公司决定改名为 Docker](https://blog.docker.com/2013/10/dotcloud-is-becoming-docker-inc/)。Docker 最初是在 Ubuntu 12.04 上开发实现的；Red Hat 则从 RHEL 6.5 开始对 Docker 进行支持；Google 也在其 PaaS 产品中广泛应用 Docker。\n\nDocker 使用 Google 公司推出的 [Go 语言](https://golang.org/) 进行开发实现，基于 Linux 内核的 [cgroup](https://zh.wikipedia.org/wiki/Cgroups)，[namespace](https://en.wikipedia.org/wiki/Linux_namespaces)，以及 [AUFS](https://en.wikipedia.org/wiki/Aufs) 类的 [Union FS](https://en.wikipedia.org/wiki/Union_mount) 等技术，对进程进行封装隔离，属于 [操作系统层面的虚拟化技术](https://en.wikipedia.org/wiki/Operating-system-level_virtualization)。由于隔离的进程独立于宿主和其它的隔离的进程，因此也称其为容器。最初实现是基于 [LXC](https://linuxcontainers.org/lxc/introduction/)，从 0.7 版本以后开始去除 LXC，转而使用自行开发的 [libcontainer](https://github.com/docker/libcontainer)，从 1.11 开始，则进一步演进为使用 [runC](https://github.com/opencontainers/runc) 和 [containerd](https://github.com/containerd/containerd)。\n\nDocker 在容器的基础上，进行了进一步的封装，从文件系统、网络互联到进程隔离等等，极大的简化了容器的创建和维护。使得 Docker 技术比虚拟机技术更为轻便、快捷。\n\n下面的图片比较了 Docker 和传统虚拟化方式的不同之处。传统虚拟机技术是虚拟出一套硬件后，在其上运行一个完整操作系统，在该系统上再运行所需应用进程；而容器内的应用进程直接运行于宿主的内核，容器内没有自己的内核，而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。\n\n![img](assets/virtualization.png)\n\n![img](assets/docker.png)\n\n\n\n## 1.2 为什么要使用 Docker\n\n作为一种新兴的虚拟化方式，Docker 跟传统的虚拟化方式相比具有众多的优势。\n\n**更高效的利用系统资源**\n\n由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销，Docker 对系统资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度，都要比传统虚拟机技术更高效。因此，相比虚拟机技术，一个相同配置的主机，往往可以运行更多数量的应用。\n\n**更快速的启动时间**\n\n传统的虚拟机技术启动应用服务往往需要数分钟，而 Docker 容器应用，由于直接运行于宿主内核，无需启动完整的操作系统，因此可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。\n\n**一致的运行环境**\n\n开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致，导致有些 bug 并未在开发过程中被发现。而 Docker 的镜像提供了除内核外完整的运行时环境，确保了应用运行环境一致性，从而不会再出现 *「这段代码在我机器上没问题啊」* 这类问题。\n\n**持续交付和部署**\n\n对开发和运维（[DevOps](https://zh.wikipedia.org/wiki/DevOps)）人员来说，最希望的就是一次创建或配置，可以在任意地方正常运行。\n\n使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员可以通过 `Dockerfile` 来进行镜像构建，并结合 [持续集成(Continuous Integration)](https://en.wikipedia.org/wiki/Continuous_integration) 系统进行集成测试，而运维人员则可以直接在生产环境中快速部署该镜像，甚至结合 [持续部署(Continuous Delivery/Deployment)](https://en.wikipedia.org/wiki/Continuous_delivery) 系统进行自动部署。\n\n而且使用 `Dockerfile` 使镜像构建透明化，不仅仅开发团队可以理解应用运行环境，也方便运维团队理解应用运行所需条件，帮助更好的生产环境中部署该镜像。\n\n**更轻松的迁移**\n\n由于 Docker 确保了执行环境的一致性，使得应用的迁移更加容易。Docker 可以在很多平台上运行，无论是物理机、虚拟机、公有云、私有云，甚至是笔记本，其运行结果是一致的。因此用户可以很轻易的将在一个平台上运行的应用，迁移到另一个平台上，而不用担心运行环境的变化导致应用无法正常运行的情况。\n\n**更轻松的维护和扩展**\n\nDocker 使用的分层存储以及镜像的技术，使得应用重复部分的复用更为容易，也使得应用的维护更新更加简单，基于基础镜像进一步扩展镜像也变得非常简单。此外，Docker 团队同各个开源项目团队一起维护了一大批高质量的 [官方镜像](https://store.docker.com/search?q=&source=verified&type=image)，既可以直接在生产环境使用，又可以作为基础进一步定制，大大的降低了应用服务的镜像制作成本。\n\n**对比传统虚拟机总结**\n\n| 特性       | 容器               | 虚拟机      |\n| ---------- | ------------------ | ----------- |\n| 启动       | 秒级               | 分钟级      |\n| 硬盘使用   | 一般为 `MB`        | 一般为 `GB` |\n| 性能       | 接近原生           | 弱于        |\n| 系统支持量 | 单机支持上千个容器 | 一般几十个  |\n\n# 2. Docker 基本概念\n\nDocker 包括三个基本概念\n\n- 镜像（`Image`）\n- 容器（`Container`）\n- 仓库（`Repository`）\n\n理解了这三个概念，就理解了 Docker 的整个生命周期。\n\n## 2.1 Docker 引擎\n\nDocker 引擎是一个包含以下主要组件的客户端服务器应用程序。\n\n- 一种服务器，它是一种称为守护进程并且长时间运行的程序。\n- REST API 用于指定程序可以用来与守护进程通信的接口，并指示它做什么。\n- 一个有命令行界面 (CLI) 工具的客户端。\n\nDocker 引擎组件的流程如下图所示：\n\n![img](assets/620140640_31678.png)\n\n\n\n## 2.2 Docker 架构\n\nDocker 使用客户端-服务器 (C/S) 架构模式，使用远程 API 来管理和创建 Docker 容器。\n\nDocker 容器通过 Docker 镜像来创建。\n\n**容器与镜像**的关系类似于**面向对象**编程中的**对象与类**。\n\n| Docker | 面向对象 |\n| ------ | -------- |\n| 容器   | 对象     |\n| 镜像   | 类       |\n\n![img](assets/262150629_86976.png)\n\n| 标题                | 说明                                                         |\n| ------------------- | ------------------------------------------------------------ |\n| **镜像**(Images)    | Docker 镜像是用于创建 Docker 容器的模板。                    |\n| **容器**(Container) | 容器是独立运行的一个或一组应用。                             |\n| **客户端**(Client)  | Docker 客户端通过命令行或者其他工具使用 Docker API (<https://docs.docker.com/reference/api/docker_remote_api>) 与 Docker 的守护进程通信。 |\n| **主机**(Host)      | 一个物理或者虚拟的机器用于执行 Docker 守护进程和容器。       |\n| **仓库**(Registry)  | Docker 仓库用来保存镜像，可以理解为代码控制中的代码仓库。Docker Hub([https://hub.docker.com](https://hub.docker.com/)) 提供了庞大的镜像集合供使用。 |\n| **Docker Machine**  | Docker Machine是一个简化Docker安装的命令行工具，通过一个简单的命令行即可在相应的平台上安装Docker，比如VirtualBox、 Digital Ocean、Microsoft Azure。 |\n\n## 2.3 Docker 镜像\n\n我们都知道，操作系统分为内核和用户空间。对于 Linux 而言，内核启动后，会挂载 `root` 文件系统为其提供用户空间支持。而 Docker 镜像（Image），就相当于是一个 `root` 文件系统。比如官方镜像 `ubuntu:16.04` 就包含了完整的一套 Ubuntu 16.04 最小系统的 `root` 文件系统。\n\nDocker 镜像是一个特殊的文件系统，除了提供容器运行时所需的程序、库、资源、配置等文件外，还包含了一些为运行时准备的一些配置参数（如匿名卷、环境变量、用户等）。镜像不包含任何动态数据，其内容在构建之后也不会被改变。\n\n**分层存储**\n\n因为镜像包含操作系统完整的 `root` 文件系统，其体积往往是庞大的，因此在 Docker 设计时，就充分利用 [Union FS](https://en.wikipedia.org/wiki/Union_mount) 的技术，将其设计为分层存储的架构。所以严格来说，镜像并非是像一个 ISO 那样的打包文件，镜像只是一个虚拟的概念，其实际体现并非由一个文件组成，而是由一组文件系统组成，或者说，由多层文件系统联合组成。\n\n镜像构建时，会一层层构建，前一层是后一层的基础。每一层构建完就不会再发生改变，后一层上的任何改变只发生在自己这一层。比如，删除前一层文件的操作，实际不是真的删除前一层的文件，而是仅在当前层标记为该文件已删除。在最终容器运行的时候，虽然不会看到这个文件，但是实际上该文件会一直跟随镜像。因此，在构建镜像的时候，需要额外小心，每一层尽量只包含该层需要添加的东西，任何额外的东西应该在该层构建结束前清理掉。\n\n分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层，然后进一步添加新的层，以定制自己所需的内容，构建新的镜像。\n\n关于镜像构建，将会在后续相关章节中做进一步的讲解。\n\n## 2.4 Docker 容器\n\n镜像（`Image`）和容器（`Container`）的关系，就像是面向对象程序设计中的 `类` 和 `实例` 一样，镜像是静态的定义，容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。\n\n容器的实质是进程，但与直接在宿主执行的进程不同，容器进程运行于属于自己的独立的 [命名空间](https://en.wikipedia.org/wiki/Linux_namespaces)。因此容器可以拥有自己的 `root` 文件系统、自己的网络配置、自己的进程空间，甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里，使用起来，就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。也因为这种隔离的特性，很多人初学 Docker 时常常会混淆容器和虚拟机。\n\n前面讲过镜像使用的是分层存储，容器也是如此。每一个容器运行时，是以镜像为基础层，在其上创建一个当前容器的存储层，我们可以称这个为容器运行时读写而准备的存储层为**容器存储层**。\n\n容器存储层的生存周期和容器一样，容器消亡时，容器存储层也随之消亡。因此，任何保存于容器存储层的信息都会随容器删除而丢失。\n\n按照 Docker 最佳实践的要求，容器不应该向其存储层内写入任何数据，容器存储层要保持无状态化。所有的文件写入操作，都应该使用 `数据卷（Volume）`、或者绑定宿主目录，在这些位置的读写会跳过容器存储层，直接对宿主（或网络存储）发生读写，其性能和稳定性更高。\n\n数据卷的生存周期独立于容器，容器消亡，数据卷不会消亡。因此，使用数据卷后，容器删除或者重新运行之后，数据却不会丢失。\n\n## 2.5 Docker 仓库\n\n镜像构建完成后，可以很容易的在当前宿主机上运行，但是，如果需要在其它服务器上使用这个镜像，我们就需要一个集中的存储、分发镜像的服务，`Docker Registry` 就是这样的服务。\n\n一个 **Docker Registry** 中可以包含多个**仓库**（`Repository`）；每个仓库可以包含多个**标签**（`Tag`）；每个标签对应一个镜像。\n\n通常，一个仓库会包含同一个软件不同版本的镜像，而标签就常用于对应该软件的各个版本。我们可以通过 `<仓库名>:<标签>` 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签，将以 `latest` 作为默认标签。\n\n以 [Ubuntu 镜像](https://store.docker.com/images/ubuntu) 为例，`ubuntu` 是仓库的名字，其内包含有不同的版本标签，如，`14.04`, `16.04`。我们可以通过 `ubuntu:14.04`，或者 `ubuntu:16.04` 来具体指定所需哪个版本的镜像。如果忽略了标签，比如 `ubuntu`，那将视为 `ubuntu:latest`。\n\n仓库名经常以 *两段式路径* 形式出现，比如 `jwilder/nginx-proxy`，前者往往意味着 Docker Registry 多用户环境下的用户名，后者则往往是对应的软件名。但这并非绝对，取决于所使用的具体 Docker Registry 的软件或服务。\n\n**公有 Docker Registry**\n\nDocker Registry 公开服务是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像，并可能提供收费服务供用户管理私有镜像。\n\n最常使用的 Registry 公开服务是官方的 [Docker Hub](https://hub.docker.com/)，这也是默认的 Registry，并拥有大量的高质量的官方镜像。除此以外，还有 [CoreOS](https://coreos.com/) 的 [Quay.io](https://quay.io/repository/)，CoreOS 相关的镜像存储在这里；Google 的 [Google Container Registry](https://cloud.google.com/container-registry/)，[Kubernetes](http://kubernetes.io/) 的镜像使用的就是这个服务。\n\n由于某些原因，在国内访问这些服务可能会比较慢。国内的一些云服务商提供了针对 Docker Hub 的镜像服务（`Registry Mirror`），这些镜像服务被称为**加速器**。常见的有 [阿里云加速器](https://cr.console.aliyun.com/#/accelerator)、[DaoCloud 加速器](https://www.daocloud.io/mirror#accelerator-doc) 等。使用加速器会直接从国内的地址下载 Docker Hub 的镜像，比直接从 Docker Hub 下载速度会提高很多。\n\n国内也有一些云服务商提供类似于 Docker Hub 的公开服务。比如 [时速云镜像仓库](https://hub.tenxcloud.com/)、[网易云镜像服务](https://c.163.com/hub#/m/library/)、[DaoCloud 镜像市场](https://hub.daocloud.io/)、[阿里云镜像库](https://cr.console.aliyun.com/) 等。\n\n**私有 Docker Registry**\n\n除了使用公开服务外，用户还可以在本地搭建私有 Docker Registry。Docker 官方提供了 [Docker Registry](https://store.docker.com/images/registry/) 镜像，可以直接使用做为私有 Registry 服务。\n\n开源的 Docker Registry 镜像只提供了 [Docker Registry API](https://docs.docker.com/registry/spec/api/) 的服务端实现，足以支持 `docker` 命令，不影响使用。但不包含图形界面，以及镜像维护、用户管理、访问控制等高级功能。在官方的商业化版本 [Docker Trusted Registry](https://docs.docker.com/datacenter/dtr/2.0/) 中，提供了这些高级功能。\n\n除了官方的 Docker Registry 外，还有第三方软件实现了 Docker Registry API，甚至提供了用户界面以及一些高级功能。比如，[VMWare Harbor](https://github.com/vmware/harbor) 和 [Sonatype Nexus](https://www.sonatype.com/docker)。\n\n# 3. 安装 Docker\n\nDocker 在 1.13 版本之后，从 2017 年的 3 月 1 日开始，版本命名规则变为如下：\n\n| 项目        | 说明         |\n| ----------- | ------------ |\n| 版本格式    | YY.MM        |\n| Stable 版本 | 每个季度发行 |\n| Edge 版本   | 每个月发行   |\n\n同时 Docker 划分为 CE 和 EE。CE 即社区版（免费，支持周期三个月），EE 即企业版，强调安全，付费使用。\n\nDocker CE 每月发布一个 Edge 版本 (17.03, 17.04, 17.05…)，每三个月发布一个 Stable 版本 (17.03, 17.06, 17.09…)，Docker EE 和 Stable 版本号保持一致，但每个版本提供一年维护。\n\n官方网站上有各种环境下的 [安装指南](https://docs.docker.com/engine/installation/)，这里主要介绍 Docker CE 在 Linux 、Windows 10 (PC) 和 macOS 上的安装。\n\n## 3.1 Ubuntu 安装 Docker\n\n> 警告：切勿在没有配置 Docker APT 源的情况下直接使用 apt 命令安装 Docker.\n\n### 准备工作\n\n#### 系统要求\n\nDocker CE 支持以下版本的 [Ubuntu](https://www.ubuntu.com/server) 操作系统：\n\n- Artful 17.10 (Docker CE 17.11 Edge +)\n- Xenial 16.04 (LTS)\n- Trusty 14.04 (LTS)\n\nDocker CE 可以安装在 64 位的 x86 平台或 ARM 平台上。Ubuntu 发行版中，LTS（Long-Term-Support）长期支持版本，会获得 5 年的升级维护支持，这样的版本会更稳定，因此在生产环境中推荐使用 LTS 版本,当前最新的 LTS 版本为 Ubuntu 16.04。\n\n#### 卸载旧版本\n\n旧版本的 Docker 称为 `docker` 或者 `docker-engine`，使用以下命令卸载旧版本：\n\n```\n$ sudo apt-get remove docker \\\n               docker-engine \\\n               docker.io\n```\n\n#### Ubuntu 14.04 可选内核模块\n\n从 Ubuntu 14.04 开始，一部分内核模块移到了可选内核模块包 (`linux-image-extra-*`) ，以减少内核软件包的体积。正常安装的系统应该会包含可选内核模块包，而一些裁剪后的系统可能会将其精简掉。`AUFS` 内核驱动属于可选内核模块的一部分，作为推荐的 Docker 存储层驱动，一般建议安装可选内核模块包以使用 `AUFS`。\n\n如果系统没有安装可选内核模块的话，可以执行下面的命令来安装可选内核模块包：\n\n```\n$ sudo apt-get update\n\n$ sudo apt-get install \\\n    linux-image-extra-$(uname -r) \\\n    linux-image-extra-virtual\n```\n\n#### Ubuntu 16.04 +\n\nUbuntu 16.04 + 上的 Docker CE 默认使用 `overlay2` 存储层驱动,无需手动配置。\n\n### 使用 APT 安装\n\n#### 安装必要的一些系统工具\n\n```\nsudo apt-get update\nsudo apt-get -y install apt-transport-https ca-certificates curl software-properties-common\n```\n\n#### 安装 GPG 证书\n\n```\ncurl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -\n```\n\n#### 写入软件源信息\n\n```\nsudo add-apt-repository \"deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable\"\n```\n\n#### 更新并安装 Docker CE\n\n```\nsudo apt-get -y update\nsudo apt-get -y install docker-ce\n```\n\n> 以上命令会添加稳定版本的 Docker CE APT 镜像源，如果需要最新或者测试版本的 Docker CE 请将 stable 改为 edge 或者 test。从 Docker 17.06 开始，edge test 版本的 APT 镜像源也会包含稳定版本的 Docker。\n\n### 使用脚本自动安装\n\n在测试或开发环境中 Docker 官方为了简化安装流程，提供了一套便捷的安装脚本，Ubuntu 系统上可以使用这套脚本安装：\n\n```\n$ curl -fsSL get.docker.com -o get-docker.sh\n# 可能会出现 404 错误，请移步下面的特别说明\n$ sudo sh get-docker.sh --mirror Aliyun\n```\n\n执行这个命令后，脚本就会自动的将一切准备工作做好，并且把 Docker CE 的 Edge 版本安装在系统中。\n\n#### 特别说明\n\n2018 年 7 月 21 日，貌似阿里云这边在做调整，故导致 Docker 的 Aliyun 安装脚本不可用，是永久性还是临时性的尚不清除，如果你已经按照之前的操作安装 Docker，请按以下步骤进行修复并重新安装\n\n- 如果已经使用了 Aliyun 脚本安装并成功的\n  - 请先卸载 Docker，命令为：`apt-get autoremove docker-ce`\n  - 删除 `/etc/apt/sources.list.d` 目录下的 `docker.list` 文件\n- 使用 `AzureChinaCloud` 镜像脚本重新安装，命令为：`sudo sh get-docker.sh --mirror AzureChinaCloud`\n\n### 启动 Docker CE\n\n```\n$ sudo systemctl enable docker\n$ sudo systemctl start docker\n```\n\nUbuntu 14.04 请使用以下命令启动：\n\n```\n$ sudo service docker start\n```\n\n### 建立 docker 用户组\n\n默认情况下，`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑，一般 Linux 系统上不会直接使用 `root` 用户。因此，更好地做法是将需要使用 `docker` 的用户加入 `docker` 用户组。\n\n建立 `docker` 组：\n\n```\n$ sudo groupadd docker\n```\n\n将当前用户加入 `docker` 组：\n\n```\n$ sudo usermod -aG docker $USER\n```\n\n退出当前终端并重新登录，进行如下测试。\n\n### 测试 Docker 是否安装正确\n\n```\n$ docker run hello-world\n\nUnable to find image 'hello-world:latest' locally\nlatest: Pulling from library/hello-world\nca4f61b1923c: Pull complete\nDigest: sha256:be0cd392e45be79ffeffa6b05338b98ebb16c87b255f48e297ec7f98e123905c\nStatus: Downloaded newer image for hello-world:latest\n\nHello from Docker!\nThis message shows that your installation appears to be working correctly.\n\nTo generate this message, Docker took the following steps:\n 1. The Docker client contacted the Docker daemon.\n 2. The Docker daemon pulled the \"hello-world\" image from the Docker Hub.\n    (amd64)\n 3. The Docker daemon created a new container from that image which runs the\n    executable that produces the output you are currently reading.\n 4. The Docker daemon streamed that output to the Docker client, which sent it\n    to your terminal.\n\nTo try something more ambitious, you can run an Ubuntu container with:\n $ docker run -it ubuntu bash\n\nShare images, automate workflows, and more with a free Docker ID:\n https://cloud.docker.com/\n\nFor more examples and ideas, visit:\n https://docs.docker.com/engine/userguide/\n```\n\n若能正常输出以上信息，则说明安装成功。\n\n### 镜像加速\n\n鉴于国内网络问题，后续拉取 Docker 镜像十分缓慢，强烈建议安装 Docker 之后配置 `国内镜像加速`。\n\n### 参考文档\n\n- [Docker 官方 Ubuntu 安装文档](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/)\n\n## 3.2 CentOS 安装 Docker\n\n> 警告：切勿在没有配置 Docker YUM 源的情况下直接使用 yum 命令安装 Docker.\n\n### 准备工作\n\n#### 系统要求\n\nDocker CE 支持 64 位版本 CentOS 7，并且要求内核版本不低于 3.10。 CentOS 7 满足最低内核的要求，但由于内核版本比较低，部分功能（如 `overlay2` 存储层驱动）无法使用，并且部分功能可能不太稳定。\n\n#### 卸载旧版本\n\n旧版本的 Docker 称为 `docker` 或者 `docker-engine`，使用以下命令卸载旧版本：\n\n```\n$ sudo yum remove docker \\\n                  docker-client \\\n                  docker-client-latest \\\n                  docker-common \\\n                  docker-latest \\\n                  docker-latest-logrotate \\\n                  docker-logrotate \\\n                  docker-selinux \\\n                  docker-engine-selinux \\\n                  docker-engine\n```\n\n### 使用 yum 安装\n\n执行以下命令安装依赖包：\n\n```\n$ sudo yum install -y yum-utils \\\n           device-mapper-persistent-data \\\n           lvm2\n```\n\n鉴于国内网络问题，强烈建议使用国内源，官方源请在注释中查看。\n\n执行下面的命令添加 `yum` 软件源：\n\n```\n$ sudo yum-config-manager \\\n    --add-repo \\\n    https://mirrors.ustc.edu.cn/docker-ce/linux/centos/docker-ce.repo\n\n\n# 官方源\n# $ sudo yum-config-manager \\\n#     --add-repo \\\n#     https://download.docker.com/linux/centos/docker-ce.repo    \n```\n\n如果需要最新版本的 Docker CE 请使用以下命令：\n\n```\n$ sudo yum-config-manager --enable docker-ce-edge\n```\n\n如果需要测试版本的 Docker CE 请使用以下命令：\n\n```\n$ sudo yum-config-manager --enable docker-ce-test\n```\n\n#### 安装 Docker CE\n\n更新 `yum` 软件源缓存，并安装 `docker-ce`。\n\n```\n$ sudo yum makecache fast\n$ sudo yum install docker-ce\n```\n\n### 使用脚本自动安装\n\n在测试或开发环境中 Docker 官方为了简化安装流程，提供了一套便捷的安装脚本，CentOS 系统上可以使用这套脚本安装：\n\n```\n$ curl -fsSL get.docker.com -o get-docker.sh\n$ sudo sh get-docker.sh --mirror Aliyun\n```\n\n执行这个命令后，脚本就会自动的将一切准备工作做好，并且把 Docker CE 的 Edge 版本安装在系统中。\n\n### 启动 Docker CE\n\n```\n$ sudo systemctl enable docker\n$ sudo systemctl start docker\n```\n\n### 建立 docker 用户组\n\n默认情况下，`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑，一般 Linux 系统上不会直接使用 `root` 用户。因此，更好地做法是将需要使用 `docker` 的用户加入 `docker` 用户组。\n\n建立 `docker` 组：\n\n```\n$ sudo groupadd docker\n```\n\n将当前用户加入 `docker` 组：\n\n```\n$ sudo usermod -aG docker $USER\n```\n\n退出当前终端并重新登录，进行如下测试。\n\n### 测试 Docker 是否安装正确\n\n```\n$ docker run hello-world\n\nUnable to find image 'hello-world:latest' locally\nlatest: Pulling from library/hello-world\nca4f61b1923c: Pull complete\nDigest: sha256:be0cd392e45be79ffeffa6b05338b98ebb16c87b255f48e297ec7f98e123905c\nStatus: Downloaded newer image for hello-world:latest\n\nHello from Docker!\nThis message shows that your installation appears to be working correctly.\n\nTo generate this message, Docker took the following steps:\n 1. The Docker client contacted the Docker daemon.\n 2. The Docker daemon pulled the \"hello-world\" image from the Docker Hub.\n    (amd64)\n 3. The Docker daemon created a new container from that image which runs the\n    executable that produces the output you are currently reading.\n 4. The Docker daemon streamed that output to the Docker client, which sent it\n    to your terminal.\n\nTo try something more ambitious, you can run an Ubuntu container with:\n $ docker run -it ubuntu bash\n\nShare images, automate workflows, and more with a free Docker ID:\n https://cloud.docker.com/\n\nFor more examples and ideas, visit:\n https://docs.docker.com/engine/userguide/\n```\n\n若能正常输出以上信息，则说明安装成功。\n\n### 镜像加速\n\n鉴于国内网络问题，后续拉取 Docker 镜像十分缓慢，强烈建议安装 Docker 之后配置 `国内镜像加速`。\n\n### 添加内核参数\n\n默认配置下，如果在 CentOS 使用 Docker CE 看到下面的这些警告信息：\n\n```\nWARNING: bridge-nf-call-iptables is disabled\nWARNING: bridge-nf-call-ip6tables is disabled\n```\n\n请添加内核配置参数以启用这些功能。\n\n```\n$ sudo tee -a /etc/sysctl.conf <<-EOF\nnet.bridge.bridge-nf-call-ip6tables = 1\nnet.bridge.bridge-nf-call-iptables = 1\nEOF\n```\n\n然后重新加载 `sysctl.conf` 即可\n\n```\n$ sudo sysctl -p\n```\n\n### 参考文档\n\n- [Docker 官方 CentOS 安装文档](https://docs.docker.com/engine/installation/linux/docker-ce/centos/)。\n\n## 3.3 树莓派卡片电脑安装 Docker\n\n> 警告：切勿在没有配置 Docker APT 源的情况下直接使用 apt 命令安装 Docker.\n\n### 系统要求\n\nDocker CE 不仅支持 `x86_64` 架构的计算机，同时也支持 `ARM` 架构的计算机，本小节内容以树莓派单片电脑为例讲解 `ARM` 架构安装 Docker CE。\n\nDocker CE 支持以下版本的 [Raspbian](https://www.raspberrypi.org/downloads/raspbian/) 操作系统：\n\n- Raspbian Stretch\n- Raspbian Jessie\n\n*注：* `Raspbian` 是树莓派的开发与维护机构 [树莓派基金会](http://www.raspberrypi.org/) 推荐用于树莓派的首选系统，其基于 `Debian`。\n\n### 使用 APT 安装\n\n由于 apt 源使用 HTTPS 以确保软件下载过程中不被篡改。因此，我们首先需要添加使用 HTTPS 传输的软件包以及 CA 证书。\n\n```\n$ sudo apt-get update\n\n$ sudo apt-get install \\\n     apt-transport-https \\\n     ca-certificates \\\n     curl \\\n     gnupg2 \\\n     lsb-release \\\n     software-properties-common\n```\n\n鉴于国内网络问题，强烈建议使用国内源，官方源请在注释中查看。\n\n为了确认所下载软件包的合法性，需要添加软件源的 GPG 密钥。\n\n```\n$ curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/raspbian/gpg | sudo apt-key add -\n\n# 官方源\n# $ curl -fsSL https://download.docker.com/linux/raspbian/gpg | sudo apt-key add -\n```\n\n然后，我们需要向 `source.list` 中添加 Docker CE 软件源：\n\n```\n$ sudo add-apt-repository \\\n    \"deb [arch=armhf] https://mirrors.ustc.edu.cn/docker-ce/linux/raspbian \\\n    $(lsb_release -cs) \\\n    stable\"\n\n\n# 官方源\n# $ sudo add-apt-repository \\\n#    \"deb [arch=armhf] https://download.docker.com/linux/raspbian \\\n#    $(lsb_release -cs) \\\n#    stable\"    \n```\n\n> 以上命令会添加稳定版本的 Docker CE APT 源，如果需要最新版本的 Docker CE 请将 stable 改为 edge 或者 test。从 Docker 17.06 开始，edge test 版本的 APT 源也会包含稳定版本的 Docker CE。\n\n#### 安装 Docker CE\n\n更新 apt 软件包缓存，并安装 `docker-ce`。\n\n```\n$ sudo apt-get update\n\n$ sudo apt-get install docker-ce\n```\n\n### 使用脚本自动安装\n\n在测试或开发环境中 Docker 官方为了简化安装流程，提供了一套便捷的安装脚本，Raspbian 系统上可以使用这套脚本安装：\n\n```\n$ curl -fsSL get.docker.com -o get-docker.sh\n$ sudo sh get-docker.sh --mirror Aliyun\n```\n\n执行这个命令后，脚本就会自动的将一切准备工作做好，并且把 Docker CE 的 Edge 版本安装在系统中。\n\n### 启动 Docker CE\n\n```\n$ sudo systemctl enable docker\n$ sudo systemctl start docker\n```\n\n### 建立 docker 用户组\n\n默认情况下，`docker` 命令会使用 [Unix socket](https://en.wikipedia.org/wiki/Unix_domain_socket) 与 Docker 引擎通讯。而只有 `root` 用户和 `docker` 组的用户才可以访问 Docker 引擎的 Unix socket。出于安全考虑，一般 Linux 系统上不会直接使用 `root` 用户。因此，更好地做法是将需要使用 `docker` 的用户加入 `docker` 用户组。\n\n建立 `docker` 组：\n\n```\n$ sudo groupadd docker\n```\n\n将当前用户加入 `docker` 组：\n\n```\n$ sudo usermod -aG docker $USER\n```\n\n退出当前终端并重新登录，进行如下测试。\n\n### 测试 Docker 是否安装正确\n\n```\n$ docker run arm32v7/hello-world\n\nUnable to find image 'hello-world:latest' locally\nlatest: Pulling from library/hello-world\nca4f61b1923c: Pull complete\nDigest: sha256:be0cd392e45be79ffeffa6b05338b98ebb16c87b255f48e297ec7f98e123905c\nStatus: Downloaded newer image for hello-world:latest\n\nHello from Docker!\nThis message shows that your installation appears to be working correctly.\n\nTo generate this message, Docker took the following steps:\n 1. The Docker client contacted the Docker daemon.\n 2. The Docker daemon pulled the \"hello-world\" image from the Docker Hub.\n    (amd64)\n 3. The Docker daemon created a new container from that image which runs the\n    executable that produces the output you are currently reading.\n 4. The Docker daemon streamed that output to the Docker client, which sent it\n    to your terminal.\n\nTo try something more ambitious, you can run an Ubuntu container with:\n $ docker run -it ubuntu bash\n\nShare images, automate workflows, and more with a free Docker ID:\n https://cloud.docker.com/\n\nFor more examples and ideas, visit:\n https://docs.docker.com/engine/userguide/\n```\n\n若能正常输出以上信息，则说明安装成功。\n\n*注意：* ARM 平台不能使用 `x86` 镜像，查看 Raspbian 可使用镜像请访问 [arm32v7](https://hub.docker.com/u/arm32v7/)。\n\n### 镜像加速\n\n鉴于国内网络问题，后续拉取 Docker 镜像十分缓慢，强烈建议安装 Docker 之后配置 `国内镜像加速`。\n\n## 3.4 macOS 安装 Docker\n\n### 系统要求\n\n[Docker for Mac](https://docs.docker.com/docker-for-mac/) 要求系统最低为 macOS 10.10.3 Yosemite。如果系统不满足需求，可以安装 [Docker Toolbox](https://docs.docker.com/toolbox/overview/)。\n\n### 安装\n\n#### 使用 Homebrew 安装\n\n[Homebrew](http://brew.sh/) 的 [Cask](https://caskroom.github.io/) 已经支持 Docker for Mac，因此可以很方便的使用 Homebrew Cask 来进行安装：\n\n```\n$ brew cask install docker\n```\n\n#### 手动下载安装\n\n如果需要手动下载，请点击以下链接下载 [Stable](https://download.docker.com/mac/stable/Docker.dmg) 或 [Edge](https://download.docker.com/mac/edge/Docker.dmg) 版本的 Docker for Mac。\n\n如同 macOS 其它软件一样，安装也非常简单，双击下载的 `.dmg` 文件，然后将那只叫 [Moby](https://blog.docker.com/2013/10/call-me-moby-dock/) 的鲸鱼图标拖拽到 `Application` 文件夹即可（其间需要输入用户密码）。\n\n![img](assets/install-mac-dmg.png)\n\n### 运行\n\n从应用中找到 Docker 图标并点击运行。\n\n![img](assets/install-mac-apps.png)\n\n运行之后，会在右上角菜单栏看到多了一个鲸鱼图标，这个图标表明了 Docker 的运行状态。\n\n![img](assets/install-mac-menubar.png)\n\n第一次点击图标，可能会看到这个安装成功的界面，点击 “Got it!” 可以关闭这个窗口。\n\n![img](assets/install-mac-success.png)\n\n以后每次点击鲸鱼图标会弹出操作菜单。\n\n![img](assets/install-mac-menu.png)\n\n启动终端后，通过命令可以检查安装后的 Docker 版本。\n\n```\n$ docker --version\nDocker version 17.10.0-ce, build f4ffd25\n$ docker-compose --version\ndocker-compose version 1.17.0-rc1, build a0f95af\n$ docker-machine --version\ndocker-machine version 0.13.0, build 9ba6da9\n```\n\n如果 `docker version`、`docker info` 都正常的话，可以尝试运行一个 [Nginx 服务器](https://store.docker.com/images/nginx/)：\n\n```\n$ docker run -d -p 80:80 --name webserver nginx\n```\n\n服务运行后，可以访问 [http://localhost](http://localhost/)，如果看到了 “Welcome to nginx!”，就说明 Docker for Mac 安装成功了。\n\n![img](assets/install-mac-example-nginx.png)\n\n要停止 Nginx 服务器并删除执行下面的命令：\n\n```\n$ docker stop webserver\n$ docker rm webserver\n```\n\n### 镜像加速\n\n鉴于国内网络问题，后续拉取 Docker 镜像十分缓慢，强烈建议安装 Docker 之后配置 `国内镜像加速`。\n\n\n\n## 3.5 Windows 安装 Docker\n\n### 系统要求\n\n[Docker for Windows](https://docs.docker.com/docker-for-windows/install/) 支持 64 位版本的 Windows 10 Pro，且必须开启 Hyper-V。\n\n### 安装\n\n点击以下链接下载 [Stable](https://download.docker.com/win/stable/Docker%20for%20Windows%20Installer.exe) 或 [Edge](https://download.docker.com/win/edge/Docker%20for%20Windows%20Installer.exe) 版本的 Docker for Windows。\n\n下载好之后双击 Docker for Windows Installer.exe 开始安装。\n\n### 运行\n\n在 Windows 搜索栏输入 Docker 点击 Docker for Windows 开始运行。\n\n![img](assets/install-win-docker-app-search.png)\n\nDocker CE 启动之后会在 Windows 任务栏出现鲸鱼图标。\n\n![img](assets/install-win-taskbar-circle.png)\n\n等待片刻，点击 Got it 开始使用 Docker CE。\n\n![img](assets/install-win-success-popup-cloud.png)\n\n### 镜像加速\n\n鉴于国内网络问题，后续拉取 Docker 镜像十分缓慢，强烈建议安装 Docker 之后配置 `国内镜像加速`。\n\n## 3.6 Docker 镜像加速器\n\n国内从 Docker Hub 拉取镜像有时会遇到困难，此时可以配置镜像加速器。Docker 官方和国内很多云服务商都提供了国内加速器服务，例如：\n\n- [Docker 官方提供的中国 registry mirror](https://docs.docker.com/registry/recipes/mirror/#use-case-the-china-registry-mirror)\n- [阿里云加速器](https://cr.console.aliyun.com/#/accelerator)\n- [DaoCloud 加速器](https://www.daocloud.io/mirror#accelerator-doc)\n\n我们以 Docker 官方加速器为例进行介绍。\n\n### Ubuntu 14.04、Debian 7 Wheezy\n\n对于使用 [upstart](http://upstart.ubuntu.com/) 的系统而言，编辑 `/etc/default/docker` 文件，在其中的 `DOCKER_OPTS` 中配置加速器地址：\n\n```\nDOCKER_OPTS=\"--registry-mirror=https://registry.docker-cn.com\"\n```\n\n重新启动服务。\n\n```\n$ sudo service docker restart\n```\n\n### Ubuntu 16.04+、Debian 8+、CentOS 7\n\n对于使用 [systemd](https://www.freedesktop.org/wiki/Software/systemd/) 的系统，请在 `/etc/docker/daemon.json` 中写入如下内容（如果文件不存在请新建该文件）\n\n```json\n{\n  \"registry-mirrors\": [\n    \"https://registry.docker-cn.com\"\n  ]\n}\n```\n\n> 注意，一定要保证该文件符合 json 规范，否则 Docker 将不能启动。\n\n之后重新启动服务。\n\n```shell\n$ sudo systemctl daemon-reload\n$ sudo systemctl restart docker\n```\n\n> 注意：如果您之前查看旧教程，修改了 `docker.service` 文件内容，请去掉您添加的内容（`--registry-mirror=https://registry.docker-cn.com`），这里不再赘述。\n\n### Windows 10\n\n对于使用 Windows 10 的系统，在系统右下角托盘 Docker 图标内右键菜单选择 `Settings`，打开配置窗口后左侧导航菜单选择 `Daemon`。在 `Registry mirrors`一栏中填写加速器地址 `https://registry.docker-cn.com`，之后点击 `Apply` 保存后 Docker 就会重启并应用配置的镜像地址了。\n\n### macOS\n\n对于使用 macOS 的用户，在任务栏点击 Docker for mac 应用图标 -> Perferences… -> Daemon -> Registry mirrors。在列表中填写加速器地址 `https://registry.docker-cn.com`。修改完成之后，点击 `Apply & Restart` 按钮，Docker 就会重启并应用配置的镜像地址了。\n\n### 检查加速器是否生效\n\n配置加速器之后，如果拉取镜像仍然十分缓慢，请手动检查加速器配置是否生效，在命令行执行 `docker info`，如果从结果中看到了如下内容，说明配置成功。\n\n```\nRegistry Mirrors:\n https://registry.docker-cn.com/\n```"
  },
  {
    "path": "notes/Docker基础.md",
    "content": "<!-- TOC -->\n\n- [一、Docker 快速入门](#一docker-快速入门)\n    - [初探 Docker](#初探-docker)\n        - [什么是 Docker](#什么是-docker)\n        - [为什么要使用 Docker](#为什么要使用-docker)\n    - [CentOS 7 安装 Docker](#centos-7-安装-docker)\n    - [Docker 镜像加速器](#docker-镜像加速器)\n    - [Docker 常用命令](#docker-常用命令)\n        - [1. 启动、停止、重启服务](#1-启动停止重启服务)\n        - [2. 拉取一个镜像，启动容器](#2-拉取一个镜像启动容器)\n        - [3. 启动的容器列表](#3-启动的容器列表)\n        - [4. 查看所有的容器](#4-查看所有的容器)\n        - [5. 启动、停止、重启某个容器](#5-启动停止重启某个容器)\n        - [6. 查看指定容器的日志记录](#6-查看指定容器的日志记录)\n        - [7. 删除某个容器，若正在运行，需要先停止](#7-删除某个容器若正在运行需要先停止)\n        - [8. 删除容器](#8-删除容器)\n        - [9. 删除镜像](#9-删除镜像)\n        - [10. 删除虚悬镜像](#10-删除虚悬镜像)\n        - [11. 镜像导入与导出](#11-镜像导入与导出)\n- [二、Docker File 镜像构建](#二docker-file-镜像构建)\n- [三、Docker Compose](#三docker-compose)\n    - [docker-compose 命令安装](#docker-compose-命令安装)\n        - [1. 安装 python-pip](#1-安装-python-pip)\n        - [2. 安装 Docker-Compose](#2-安装-docker-compose)\n    - [docker-compose.yml 规范](#docker-composeyml-规范)\n- [四、Docker 实战](#四docker-实战)\n    - [实战1：快速搭建 MySQL](#实战1快速搭建-mysql)\n    - [实战2：快速搭建 phpMyAdmin](#实战2快速搭建-phpmyadmin)\n    - [实战3：快速搭建 GitLab](#实战3快速搭建-gitlab)\n- [参考资料](#参考资料)\n\n<!-- /TOC -->\n# 一、Docker 快速入门\n\n本文部分内容引用来自：【千锋-李卫民老师博客，推荐大家学习】http://www.funtl.com/\n\nA simple, interactive and fun playground to learn Docker：[Play with Docker](https://labs.play-with-docker.com/)\n\n\n## 初探 Docker\n\n### 什么是 Docker\n\n　　Docker 最初是 dotCloud 公司创始人 Solomon Hykes 在法国期间发起的一个公司内部项目，它是基于 dotCloud 公司多年云服务技术的一次革新，并于 [2013 年 3 月以 Apache 2.0 授权协议开源](https://en.wikipedia.org/wiki/Docker_(software))，主要项目代码在 [GitHub](https://github.com/moby/moby) 上进行维护。Docker 项目后来还加入了 Linux 基金会，并成立推动 [开放容器联盟（OCI）](https://www.opencontainers.org/)。\n\n　　Docker 自开源后受到广泛的关注和讨论，至今其 GitHub 项目已经超过 4 万 6 千个星标和一万多个 fork。甚至由于 Docker 项目的火爆，在 2013 年底，[dotCloud 公司决定改名为 Docker](https://blog.docker.com/2013/10/dotcloud-is-becoming-docker-inc/)。Docker 最初是在 Ubuntu 12.04 上开发实现的；Red Hat 则从 RHEL 6.5 开始对 Docker 进行支持；Google 也在其 PaaS 产品中广泛应用 Docker。\n\n　　Docker 使用 Google 公司推出的 [Go 语言](https://golang.org/) 进行开发实现，基于 Linux 内核的 [cgroup](https://zh.wikipedia.org/wiki/Cgroups)，[namespace](https://en.wikipedia.org/wiki/Linux_namespaces)，以及 [AUFS](https://en.wikipedia.org/wiki/Aufs) 类的 [Union FS](https://en.wikipedia.org/wiki/Union_mount) 等技术，对进程进行封装隔离，属于 [操作系统层面的虚拟化技术](https://en.wikipedia.org/wiki/Operating-system-level_virtualization)。由于隔离的进程独立于宿主和其它的隔离的进程，因此也称其为容器。最初实现是基于 [LXC](https://linuxcontainers.org/lxc/introduction/)，从 0.7 版本以后开始去除 LXC，转而使用自行开发的 [libcontainer](https://github.com/docker/libcontainer)，从 1.11 开始，则进一步演进为使用 [runC](https://github.com/opencontainers/runc) 和 [containerd](https://github.com/containerd/containerd)。\n\n　　Docker 在容器的基础上，进行了进一步的封装，从文件系统、网络互联到进程隔离等等，极大的简化了容器的创建和维护。使得 Docker 技术比虚拟机技术更为轻便、快捷。\n\n　　下面的图片比较了 Docker 和传统虚拟化方式的不同之处。传统虚拟机技术是虚拟出一套硬件后，在其上运行一个完整操作系统，在该系统上再运行所需应用进程；而容器内的应用进程直接运行于宿主的内核，容器内没有自己的内核，而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。\n\n![img](assets/VMs-and-Containers.jpg)\n\n\n\n### 为什么要使用 Docker\n\n![Docker architecture](assets/docker-architecture.png)\n\n　　作为一种新兴的虚拟化方式，Docker 跟传统的虚拟化方式相比具有众多的优势。\n\n**更高效的利用系统资源**\n\n　　由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销，Docker 对系统资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度，都要比传统虚拟机技术更高效。因此，相比虚拟机技术，一个相同配置的主机，往往可以运行更多数量的应用。\n\n**更快速的启动时间**\n\n　　传统的虚拟机技术启动应用服务往往需要数分钟，而 Docker 容器应用，由于直接运行于宿主内核，无需启动完整的操作系统，因此可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。\n\n**一致的运行环境**\n\n　　开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致，导致有些 bug 并未在开发过程中被发现。而 Docker 的镜像提供了除内核外完整的运行时环境，确保了应用运行环境一致性，从而不会再出现 *「这段代码在我机器上没问题啊」* 这类问题。\n\n**持续交付和部署**\n\n　　对开发和运维（[DevOps](https://zh.wikipedia.org/wiki/DevOps)）人员来说，最希望的就是一次创建或配置，可以在任意地方正常运行。\n\n　　使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员可以通过 `Dockerfile` 来进行镜像构建，并结合 [持续集成(Continuous Integration)](https://en.wikipedia.org/wiki/Continuous_integration) 系统进行集成测试，而运维人员则可以直接在生产环境中快速部署该镜像，甚至结合 [持续部署(Continuous Delivery/Deployment)](https://en.wikipedia.org/wiki/Continuous_delivery) 系统进行自动部署。\n\n　　而且使用 `Dockerfile` 使镜像构建透明化，不仅仅开发团队可以理解应用运行环境，也方便运维团队理解应用运行所需条件，帮助更好的生产环境中部署该镜像。\n\n**更轻松的迁移**\n\n　　由于 Docker 确保了执行环境的一致性，使得应用的迁移更加容易。Docker 可以在很多平台上运行，无论是物理机、虚拟机、公有云、私有云，甚至是笔记本，其运行结果是一致的。因此用户可以很轻易的将在一个平台上运行的应用，迁移到另一个平台上，而不用担心运行环境的变化导致应用无法正常运行的情况。\n\n**更轻松的维护和扩展**\n\n　　Docker 使用的分层存储以及镜像的技术，使得应用重复部分的复用更为容易，也使得应用的维护更新更加简单，基于基础镜像进一步扩展镜像也变得非常简单。此外，Docker 团队同各个开源项目团队一起维护了一大批高质量的 [官方镜像](https://store.docker.com/search?q=&source=verified&type=image)，既可以直接在生产环境使用，又可以作为基础进一步定制，大大的降低了应用服务的镜像制作成本。\n\n**对比传统虚拟机总结**\n\n| 特性       | 容器               | 虚拟机      |\n| ---------- | ------------------ | ----------- |\n| 启动       | 秒级               | 分钟级      |\n| 硬盘使用   | 一般为 `MB`        | 一般为 `GB` |\n| 性能       | 接近原生           | 弱于        |\n| 系统支持量 | 单机支持上千个容器 | 一般几十个  |\n\n\n\n## CentOS 7 安装 Docker\n\n1. 先更新 yum 软件管理器，然后再安装 Docker\n\n```shell\n[root@localhost /] yum -y update\n[root@localhost /] yum install -y docker\n```\n\n　　说明：上述 `-y` 代表选择程序安装中的 yes 选项\n\n　　或是，直接安装 \n\n```shell\nyum install docker\n```\n\n2. 验证安装，查看 Docker 版本信息\n\n```shell\n[root@localhost /] docker -v\nDocker version 1.13.1, build 8633870/1.13.1\nYou have new mail in /var/spool/mail/root\n```\n\n3. 启动 / 重启 / 关闭 Docker \n\n```shell\n[root@localhost /] docker start\n[root@localhost /] docker restart\n[root@localhost /] docker stop\n```\n\n\n\n【意外情况】service docker start 无法启动问题\n\n- centos 安装 docker 显示 No package docker available，原因是 yum 没有找到 docker 的包，需要 epel 第三方软件库，运行下面的命令\n\n  `sudo yum install epel-release`\n\n- [【亲测有效】Centos安装完成docker后启动docker报错docker](http://www.cnblogs.com/ECJTUACM-873284962/p/9362840.html)\n\n\n\n1. 配置yum源\n\n   vim /etc/yum.repos.d/docker.repo\n\n```ini\n[dockerrepo]\nname=Docker Repository\nbaseurl=https://yum.dockerproject.org/repo/main/centos/$releasever/\nenabled=1\ngpgcheck=1\ngpgkey=https://yum.dockerproject.org/gpg\n```\n\n2. 通过yum安装\n\n```\nyum install docker-engine\nservice docker start\nservice docker status\n```\n\n3. 日志\n\n```\nvim /var/log/docker\n```\n\n\n\n在 Ubuntu 16.04 LTS 上 离线安装 Docker / Docker-compose - TonyZhang24 - 博客园\nhttps://www.cnblogs.com/atuotuo/p/9272368.html\n\n\n\n## Docker 镜像加速器\n\n1. 加速器服务\n\n   [DaoCloud 加速器，一行命令，镜像万千](https://www.daocloud.io/mirror)\n\n   [阿里云 - 开发者平台 - 容器 hub](https://dev.aliyun.com/search.html)\n\n2. 配置 Docker 加速器\n\n　　以 Linux 为例\n\n```shell\ncurl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://f1361db2.m.daocloud.io\n```\n\n　　该脚本可以将 --registry-mirror 加入到你的 Docker 配置文件 /etc/docker/daemon.json 中。适用于 Ubuntu14.04、Debian、CentOS6 、CentOS7、Fedora、Arch Linux、openSUSE Leap 42.1，其他版本可能有细微不同。更多详情请访问文档。\n\n　　删除 /etc/docker/daemon.json 中最后一个逗号，重启 Docker 服务即可\n\n\n\n## Docker 常用命令\n\n![docker-cmd](assets/docker-cmd.png)\n\n### 1. 启动、停止、重启服务\n\n```shell\n[root@localhost ~]# service docker restart\nRedirecting to /bin/systemctl restart docker.service\n[root@localhost ~]# service docker stop\nRedirecting to /bin/systemctl stop docker.service\n[root@localhost ~]# service docker start\nRedirecting to /bin/systemctl start docker.service\n```\n\n### 2. 拉取一个镜像，启动容器\n\n```shell\n[root@localhost ~]# docker pull centos\n[root@localhost ~]# docker run -it -v /centos_dir:/docker_dir --name biodwhu-1 centos\n```\n\n- -i：允许我们对容器内的 (STDIN) 进行交互\n- -t：在新容器内指定一个伪终端或终端\n- -v：是挂在宿机目录， /centos_dir 是宿机目录，/docker_dir 是当前 Docker 容器的目录，宿机目录必须是绝对的。\n- -p：端口映射\n- --name：是给容器起一个名字，可省略，省略的话 docker 会随机产生一个名字\n\n- --restart：always\n\n### 3. 启动的容器列表\n\n```shell\n[root@localhost ~]# docker ps\n```\n\n### 4. 查看所有的容器\n\n```shell\n[root@localhost ~]# docker ps -a\n```\n\n### 5. 启动、停止、重启某个容器\n\n```shell\n[root@localhost ~]# docker start biodwhu-1\nbiodwhu-1\n[root@localhost ~]# docker stop biodwhu-2\nbiodwhu-2\n[root@localhost ~]# docker restart biodwhu-3\nbiodwhu-3\n```\n\n### 6. 查看指定容器的日志记录\n\n```shell\n[root@localhost ~]# docker logs -f biodwhu-1\n```\n\n### 7. 删除某个容器，若正在运行，需要先停止\n\n```shell\n[root@localhost ~]# docker rm biodwhu-1\nError response from daemon: You cannot remove a running container 2d48fc5b7c17b01e6247cbc012013306faf1e54f24651d5e16d6db4e15f92d33. Stop the container before attempting removal or use -f\n[root@localhost ~]# docker stop biodwhu-1\nbiodwhu-1\n[root@localhost ~]# docker rm biodwhu-1\nbiodwhu-1\n```\n\n### 8. 删除容器\n\n```shell\n# 删除某个容器\n[root@localhost ~]# docker rm f3b346204a39\n\n# 删除所有容器\n[root@localhost ~]# docker stop $(docker ps -a -q)\n[root@localhost ~]# docker rm $(docker ps -a -q)\n```\n\n### 9. 删除镜像\n\n```shell\n# 删除某个镜像\n[root@localhost ~]# docker rmi docker.io/mysql:5.6\n\n# 删除所有镜像\n[root@localhost ~]# docker rmi $(docker images -q)\n\n# 强制删除所有镜像\n[root@localhost ~]# docker rmi -f $(docker images -q)\n```\n\n### 10. 删除虚悬镜像\n\n我们在 build 镜像的过程中，可能会产生一些临时的不具有名称也没有作用的镜像他们的名称一般都是 `<none>` ,我们可以执行下面的命令将其清除掉：\n\n```shell\n[root@localhost ~]# docker rmi $(docker images -f \"dangling=true\" -q)\n# 或者\n[root@localhost ~]# docker image prune -a -f\n```\n\n### 11. 镜像导入与导出\n\n保存镜像\n\n```shell\n[root@localhost ~]# docker save a46c2a2722b9 > /var/docker/images_save/mysql.tar.gz\n```\n\n加载镜像\n\n```shell\n[root@localhost ~]# docker load -i /var/docker/images_save/mysql.tar.gz\n```\n\n\n\n\n\n# 二、Docker File 镜像构建\n\n# 三、Docker Compose\n\n## docker-compose 命令安装\n\nDocker-Compose 是一个部署多个容器的简单但是非常必要的工具.\n\n安装 Docker-Compose 之前，请先安装 python-pip\n\n### 1. 安装 python-pip\n\n1. 首先检查 Linux 有没有安装 python-pip 包，终端执行 pip -v\n\n```shell\n[root@localhost ~]# pip -V\n-bash: pip: command not found\n```\n\n2. 没有 python-pip 包就执行命令\n\n```shell\n[root@localhost ~]# yum -y install epel-release\n```\n\n3. 执行成功之后，再次执行\n\n```shell\n[root@localhost ~]# yum -y install python-pip\n```\n\n4. 对安装好的 pip 进行升级\n\n```shell\n[root@localhost ~]# pip install --upgrade pip\n```\n\n### 2. 安装 Docker-Compose\n\n1. 终端执行\n\n```shell\n[root@localhost ~]# pip install docker-compose\n```\n\n2. 检查 docker-compose 安装\n\n```shell\n[root@localhost ~]# docker-compose -version\n```\n\n参考资料：\n\n- [CentOS7下安装Docker-Compose - YatHo - 博客园](https://www.cnblogs.com/YatHo/p/7815400.html)\n\n## docker-compose.yml 规范\n\n\n\n\n\n\n\n# 四、Docker 实战\n\n## 实战1：快速搭建 MySQL\n\n- 官方镜像仓库\n\n  [https://hub.docker.com/_/mysql/](https://hub.docker.com/_/mysql/)\n\n- docker-compose.yml\n\n```yml\nversion: '3.1'\nservices:\n  mysql:\n    restart: always\n    image: mysql:5.6\n    container_name: mysql\n    ports:\n      - 3306:3306\n    environment:\n      TZ: Asia/Shanghai\n      MYSQL_ROOT_PASSWORD: 123456\n    command:\n      --character-set-server=utf8mb4\n      --collation-server=utf8mb4_general_ci\n      --explicit_defaults_for_timestamp=true\n      --lower_case_table_names=1\n      --max_allowed_packet=128M\n      --sql-mode=\"STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO\"\n    volumes:\n      - /usr/local/docker/mysql/mysql-data:/var/lib/mysql\n```\n\n\n\n## 实战2：快速搭建 phpMyAdmin\n\n- 官方镜像仓库\n\n  [phpmyadmin/phpmyadmin](phpmyadmin/phpmyadmin)\n\n- docker-compose.yml\n\n```yml\nversion: '3.1'\nservices:\n  phpmyadmin:\n    image: phpmyadmin/phpmyadmin\n    container_name: phpmyadmin\n    environment:\n     - PMA_ARBITRARY=1\n     - PMA_HOST=120.92.17.12\n    # - PMA_PORT=3306\n    # - PMA_USER=xxx\n    # - PMA_PASSWORD=xxx\n    restart: always\n    ports:\n     - 6060:80\n    volumes:\n     - /sessions\n```\n\n\n\n\n\n## 实战3：快速搭建 GitLab\n\nGitLab 使用163邮箱发送邮件 - 刘锐群的笔记 - CSDN博客\nhttps://blog.csdn.net/liuruiqun/article/details/50000213\n\n\n\ngitlab服务器邮箱配置 - weifengCorp - 博客园\nhttps://www.cnblogs.com/weifeng1463/p/8489563.html\n\n\n\ntwang2218/gitlab-ce-zh - Docker Hub\nhttps://hub.docker.com/r/twang2218/gitlab-ce-zh/\n\n```yml\nversion: '3'\nservices:\n    web:\n      image: 'twang2218/gitlab-ce-zh:10.5'\n      restart: always\n      hostname: '120.131.11.187'\n      environment:\n        TZ: 'Asia/Shanghai'\n        GITLAB_OMNIBUS_CONFIG: |\n          external_url 'http://120.131.11.187:3000'\n          gitlab_rails['gitlab_shell_ssh_port'] = 2222\n          unicorn['port'] = 8888\n          nginx['listen_port'] = 3000\n      ports:\n        - '3000:3000'\n        - '8443:443'\n        - '2222:22'\n      volumes:\n        - /usr/local/docker/gitlab/config:/etc/gitlab\n        - /usr/local/docker/gitlab/data:/var/opt/gitlab\n        - /usr/local/docker/gitlab/logs:/var/log/gitlab\n```\n\n\n\n\n\n\n\n\n\n\n\n# 参考资料\n\n- [docker前后分离笔记 - 小翼的前端天地](https://my-fe.pub/post/docker-front-and-backend-separation.html#toc-619)\n"
  },
  {
    "path": "notes/Docker实战.md",
    "content": "# Docker实战系列\n\n## Docker搭建自己的Gitlab CI Runner\n\n> 这里主要将采用 docker 镜像方式安装\n>\n\n1. 拉取gitlab-runner镜像\n\n```\ndocker pull gitlab/gitlab-runner:latest\n```\n\n2. 添加 gitlab-runner container\n\n```\nsudo docker run -d --name gitlab-runner --restart always \\\n  -v /srv/gitlab-runner/config:/etc/gitlab-runner \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  gitlab/gitlab-runner:latest\n  \n  sudo docker run -d --restart always \\\n  -v /srv/gitlab-runner/config:/etc/gitlab-runner \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  gitlab/gitlab-runner:latest\n```\n\n3. 注册runner\n\n```\nsudo docker exec -it gitlab-runner gitlab-ci-multi-runner register\n```\n\n4. 注册\n\n```shell\nsudo docker exec -it gitlab-runner gitlab-ci-multi-runner register\n```\n\n\n\n\n\n\n\nsudo docker run -d --name gitlab-runner --restart always \\\n  -v /srv/gitlab-runner/config:/etc/gitlab-runner \\\n  -v /var/run/docker.sock:/var/run/docker.sock \\\n  gitlab/gitlab-runner:latest\n\n\n\n\n\n\n\n\n\n\n\n\n\n[Docker搭建自己的Gitlab CI Runner - 哎_小羊的博客 - CSDN博客](https://blog.csdn.net/aixiaoyang168/article/details/72168834)\n"
  },
  {
    "path": "notes/EalsticSearch/Untitled.md",
    "content": "elasticsearch-head的使用 - 仅此而已-远方 - 博客园\nhttps://www.cnblogs.com/xuwenjin/p/8792919.html\n\n\n\n\n\ntyrival/gitbook: Logstash + Elasticsearch 6.7 用户指南中文版\nhttps://github.com/tyrival/gitbook\n\n\n\n\n\nMac下ElasticSearch安装 - 简书\nhttps://www.jianshu.com/p/df4af12a420a\n\n\n\n\n\n## 安装\n\nLogstash在Linux上安装部署 - haw2106 - 博客园\nhttps://www.cnblogs.com/haw2106/p/10410916.html\n\nElasticSearch在linux上安装部署 - socket强 - 博客园\nhttps://www.cnblogs.com/socketqiang/p/11363024.html\n\n详解Docker下使用Elasticsearch可视化Kibana_docker_脚本之家\nhttps://www.jb51.net/article/138582.htm\n\n\n\n\n\ndocker下载镜像报net/http: TLS handshake timeout-西风未眠-51CTO博客\nhttps://blog.51cto.com/10950710/2122702\n\n\n\nDocker安装部署ELK教程 (Elasticsearch+Kibana+Logstash+Filebeat) - 万能付博 - 博客园\nhttps://www.cnblogs.com/fbtop/p/11005469.html\n\n\n\n\n\n1.拉取镜像\n\n```\ndocker pull elasticsearch:6.5.4\ndocker pull kibana:6.5.4\n```\n\n \n\n2.启动容器\n\n```\ndocker run  -d --name es1  -p 9200:9200 -p 9300:9300 -e \"discovery.type=single-node\" elasticsearch:6.5.4\ndocker run -d  -p 5601:5601 --name kibana --link es1:elasticsearch  kibana:6.5.4\n```"
  },
  {
    "path": "notes/FastDFS.md",
    "content": "# FastDFS\n\n手把手教你搭建分布式文件存储系统\n\n\n\n用FastDFS一步步搭建文件管理系统 - bojiangzhou - 博客园\nhttps://www.cnblogs.com/chiangchou/p/fastdfs.html\n\n"
  },
  {
    "path": "notes/Frontend/Angular.md",
    "content": "# Angular\n\n## 1. 框架背景\n\nAngular 是一个由 Google维护的开源JavaScript框架，用于在HTML和JavaScript中构建Web应用程序，是三大框架之首。\n\n不管是1还是2，Angular最显著的特征就是其**整合性**。涵盖了M、V、C/VM等各个层面，不需要组合、评估其它技术就能完成大部分前端开发任务。这样可以有效降低决策成本，提高决策速度，对需要快速起步的团队是非常有帮助的。\n\n由于它是从一个用来做原型的框架演化而来的，加上诞生时间很早（2009年，作为对比，jQuery诞生于2006年），当时生态不完善，连模块化机制都得自己实现。\n\n但Angular 2就不同了，发布于2016年9月份，它是基于ES6来开发的，它的起点更高，整合了现代前端的各种先进理念，在框架、文档、工具等各个层面提供了全方位的支持\n\n在Angular 中最具特色的就是**依赖注入**系统了，它把后端开发中用来解决复杂问题、实现高弹性设计的技术引入了前端\n\n\n\n## 2. Angular CLI\n\n###   \t2.1 安装\n\n​\tAngular CLI用于简单，快速构建Angular2项目，只要掌握几行命令就能扣减前端架构。依赖于NodeJs和npm。\n\n```jsx\n//安装脚手架\nnpm install -g angular-cli\n//创建项目\nng new project_name(项目名称)\n//启动项目\ncd project_name\nng serve --open \n```\n\n \n\n### \t2.2 主要特性\n\n1. Angular CLI 可以快速搭建框架，创建module，service，class，directive等；\n2. 具有webpack的功能，代码分割，按需加载；\n3. 代码打包压缩；\n4. 模块测试；\n5. 热部署，有改动立即重新编译，不用刷新浏览器；而且速度很快\n6.  有开发环境，测试环境，生产环境的配置，不用自己操心； \n7.  sass，less的预编译Angular CLI都会自动识别后缀来编译；\n8. typescript的配置，Angular CLI在创建应用时都可以自己配置；\n9.  在创建好的工程也可以做一些个性化的配置，webpack的具体配置还不支持，未来可能会增加；\n10.  Angular CLI创建的工程结构是最佳实践，生产可用；\n\n\n\n### \t2.3 创建module，component，service，class\n\n\n\n​    \n\n## 3. 架构\n\n### \t3.1 模块\n\n​\t\t模块组件的特征在于可以用于执行单个任务的代码块。 您可以从代码(类)中导出值。 Angular应用程序被称为模块，并使用许多模块构建您的应用程序。 Angular 的基本构建块是可以从模块导出的**组件**类。\n\n```js\nexport class AppComponent {\n  title = '朝夕教育';\n}\n\n```\n\n### \t3.2 组件\n\n组件是拥有模板的控制器类，主要处理页面上的应用程序和逻辑的视图。 组件可以拥有独立的样式。\n注册组件，使用 *@Component* 注释，可以将应用程序拆分为更小的部分。\n\n#### 3.2.1 创建组件\n\n使用ng命令`ng generate component <component-name>`创建的组件会自动生成在`app.module`中的引用，推荐使用ng命令生成组件\n\n```jsx\n//快速创建\nng g c xxx\n```\n\n```jsx\n@Component({\n  selector: 'app-root',\n  templateUrl: './app.component.html',\n  styleUrls: ['./app.component.scss']\n})\n```\n\n@Component最常用的几个选项:\n\n**selector**：这个 CSS 选择器用于在模板中标记出该指令，并触发该指令的实例化。\n\n**template**：组件的内联模板\n\n**templateUrl**：组件模板文件的 URL\n\n**styleUrls**：组件样式文件\n\n**styles**：组件内联样式\n\n\n\n#### 3.2.2 组件生命周期\n\n​\tAngular 会按以下顺序执行钩子方法。你可以用它来执行以下类型的操作。\n\n| 钩子方法                  | 用途                                                         | 时机                                                         |\n| :------------------------ | :----------------------------------------------------------- | :----------------------------------------------------------- |\n| `ngOnChanges()`           | 当 Angular 设置或重新设置数据绑定的输入属性时响应。 该方法接受当前和上一属性值的 `SimpleChanges` 对象注意，这发生的非常频繁，所以你在这里执行的任何操作都会显著影响性能。 | 在 `ngOnInit()` 之前以及所绑定的一个或多个输入属性的值发生变化时都会调用。注意，如果你的组件没有输入，或者你使用它时没有提供任何输入，那么框架就不会调用 `ngOnChanges()`。 |\n| `ngOnInit()`              | 在 Angular 第一次显示数据绑定和设置指令/组件的输入属性之后，初始化指令/组件。 | 在第一轮 `ngOnChanges()` 完成之后调用，只调用**一次**。      |\n| `ngDoCheck()`             | 检测，并在发生 Angular 无法或不愿意自己检测的变化时作出反应。 | 紧跟在每次执行变更检测时的 `ngOnChanges()` 和 首次执行变更检测时的 `ngOnInit()` 后调用。 |\n| `ngAfterContentInit()`    | 当 Angular 把外部内容投影进组件视图或指令所在的视图之后调用。 | 第一次 `ngDoCheck()` 之后调用，只调用一次。                  |\n| `ngAfterContentChecked()` | 每当 Angular 检查完被投影到组件或指令中的内容之后调用        | `ngAfterContentInit()` 和每次 `ngDoCheck()` 之后调用         |\n| `ngAfterViewInit()`       | 当 Angular 初始化完组件视图及其子视图或包含该指令的视图之后调用 | 第一次 `ngAfterContentChecked()` 之后调用，只调用一次。      |\n| `ngAfterViewChecked()`    | 每当 Angular 做完组件视图和子视图或包含该指令的视图的变更检测之后调用。 | `ngAfterViewInit()` 和每次 `ngAfterContentChecked()` 之后调用。 |\n| `ngOnDestroy()`           | 每当 Angular 每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器，以防内存泄漏 | 在 Angular 销毁指令或组件之前立即调用。                      |\n\n#### 3.2.3 组件交互\n\n1. ​\t**@Input**\n\n       父组件通过`@Input`给子组件绑定属性设置输入类数据\n\n    ```jsx\n    //父组件\n    <app-hello  [name]=\"'tina'\"></app-hello>\n    \n    //子组件\n    import { Component, Input } from '@angular/core';\n    @Input()\n      name!: string;\n     ngOnInit(): void {\n        console.log(this.name)\n      }\n    ```\n\n2. ​\t**@Output**\n\n    ​\t父组件给子组件传递一个事件，子组件通过`@Output`弹射触发事件\n\n    ```jsx\n    //父组件\n     <app-hello  (addList)=\"addListFun($event)\"  [name]=\"'tina'\"></app-hello>\n     \n    list:number[] = [1,2,3,4]\n      addListFun(num:number){\n        this.list.push(num)\n      }\n    \n    //子组件\n    import { Component, Output,EventEmitter } from '@angular/core';\n    @Output() addList = new EventEmitter()\n      pushList(v:string){\n        console.log(this.inpValue)\n          this.addList.emit(v)\n      }\n    ```\n\n3. ​    **@ViewChild()**\n\n       通过`ViewChild`获取子组件实例，获取子组件数据\n\n    ```jsx\n    <app-hello #myChild  [name]=\"'tina'\"></app-hello>\n    <button (click)=\"getView($event)\">获取</button>\n    \n    \n    @ViewChild('myChild') child: any;\n      constructor() { }\n    \n      ngOnInit(): void {\n      }\n      getView(e:any){\n        console.log(this.child)\n        this.child.setInpValue('我是一段数据')\n      }\n    ```\n\n    \n\n### \t3.3 模板\n\n在 Angular 中，**模板**就是一块 HTML。在模板中，你可以通过一种特殊语法来使用 Angular 的许多功能\n\n#### \t3.3.1 插值语法\n\n​\t\t所谓 \"插值\" 是指将表达式嵌入到标记文本中。 默认情况下，插值会用双花括号 `{{` 和 `}}` 作为分隔符\t\t\n\n```js\n<h3>hello {{ name }}</h3>\n```\n\n​\t\t花括号之间的文本通常是组件属性的名字。Angular 会把这个名字替换为响应组件属性的字符串值\n\n​\t\t括号间的素材是一个**模板表达式**我们可以在`{{}}`内编写js运算\n\n```jsx\n<h3>hello {{ 1+1 }}</h3>\n```\n\n#### \t3.3.2  属性绑定\n\n1.  **Attribute绑定**\n\n    ```jsx\n    <h3 [id]=\"'h3-dom'\">hello {{ 1+1 }}</h3>\n    ```\n\n2.  **类绑定**\n\n    ```jsx\n    //单一类绑定\n    <h3 [class.h3-dom]=\"true\">hello {{ 1+1 }}</h3>\n    \n    //多重类绑定\n    <h3 [class]=\"'h3-dom title-dom min-title'\">hello {{ 1+1 }}</h3>\n    <h3 [class]=\"{'h3-dom':true,'title-dom':false}\">hello {{ 1+1 }}</h3>\n    <h3 [class]=\"['h3-dom','title-dom']\">hello {{ 1+1 }}</h3>\n    \n    //ngClass\n    export class AppComponent {\n        isActive = true;\n    }\n    \n    <h3 [ngClass]=\"{'active': isActive}\">hello {{ 1+1 }}</h3>\n    \n    ```\n\n3.  **样式绑定**\n\n    ```jsx\n    //单一样式绑定\n    <h3 [style.width]=\"'300px'\">hello {{ 1+1 }}</h3>\n    \n    //带单位的单一样式绑定\n    <h3 [style.width.px]=\"'300'\">hello {{ 1+1 }}</h3>\n    \n    //多重样式绑定\n    <h3 [style]=\"'background:red;color:#fff'\">hello {{ 1+1 }}</h3>\n    <h3 [style]=\"{'background':'red','color':'#fff'}\">hello {{ 1+1 }}</h3>\n    \n    //ngStyle\n    export class AppComponent {\n        isMax = false;\n    }\n    <h3 [ngStyle]=\"{'color': 'red'}\">hello {{ 1+1 }}</h3>\n    <h3 [ngStyle]=\"{'font-size': isMax ? '24px' : '12px'}\">hello {{ 1+1 }}</h3>\n    ```\n\n#### \t3.3.3  条件判断\n\n​\t\t\t ***ngIf**是直接影响元素是否被渲染，而非控制元素的显示和隐藏\n\n```jsx\nexport class AppComponent {\n    isMax = false;\n    isMin = false;\n}\n<div *ngIf=\"isMax\">Max title</div>\n<div *ngIf=\"isMin\">Min title</div>\n\n//解析完\n<ng-template [ngIf]=\"isMax\">\n  <div>Max title</div>\n</ng-template>\n\n<ng-container *ngIf=\"isMax; else elseTemplate\">\n       isMax为true\n</ng-container>\n<ng-template #elseTemplate>\n    isMax为false\n</ng-template>\n```\n\n####     3.3.4 循环语句\n\n​\t解析器会把 `let color`、`let i` 和 `let odd` 翻译成命名变量 `color`、`i` 和 `odd`\n\n微语法解析器接收of，会将它的首字母大写(Of)，然后加上属性的指令名(ngFor)前缀，它最终生成的名字是 ngFor 的输入属性(colors)\n\n`NgFor` 指令在列表上循环，每个循环中都会设置和重置它自己的上下文对象上的属性。 这些属性包括 `index` 和 `odd` 以及一个特殊的属性名 `$implicit`(隐式变量)\n\nAngular 将 let-color 设置为此上下文中 $implicit 属性的值， 它是由 NgFor 用当前迭代中的 colors 初始化的\n\n```jsx\nexport class AppComponent {\n      colors:Array<string> = [ 'red', 'blue', 'yellow', 'green' ];\n}\n\n<div *ngFor=\"let color of colors let i=index let odd=odd\">\n  {{odd}}\n  {{i}}\n  {{color}}\n</div>\n\n//解析完\n<ng-template ngFor let-color [ngForOf]=\"colors\" let-i=\"index\" let-odd=\"odd\">\n  <div>{{odd}} {{i}} {{color}}</div>\n</ng-template>\n```\n\n```jsx\nexport class AppComponent {\n    status = 1;\n}\n\n  <ul [ngSwitch]=\"status\">\n    <li *ngSwitchCase=\"1\">已支付</li>\n    <li *ngSwitchCase=\"2\">订单已经确认</li> \n    <li *ngSwitchCase=\"3\">已发货</li>\n    <li *ngSwitchDefault>无效</li>\n  </ul>\n```\n\n#### \t3.3.5 事件绑定\n\n​\t\t Angular 的事件绑定语法由等号左侧括号内的目标事件名和右侧引号内的模板语句组成。目标事件名是 `click` ，模板语句是 `onSave()` \n\n事件对象通过`$event`传递\n\n```jsx\nexport class AppComponent {\n   onSave(){\n       console.log('点击了按钮')\n   }\n}\n\n<button 2(click)=\"onSave($event)\">Save</button>\n```\n\n#### \t3.3.6 双向绑定\n\n双向绑定是应用中的组件共享数据的一种方式。使用双向绑定绑定来侦听事件并在父组件和子组件之间同步更新值\n\nngModel指令**只对表单元素有效**，所以在使用之前需要导入`FormsModule`板块\n\n```jsx\nimport { FormsModule } from '@angular/forms';\n\n@NgModule({\n  // 申明组件内用到的视图\n  declarations: [\n    AppComponent,\n    HelloComponent,\n  ],\n  //引入模块需要的类\n  imports: [\n    BrowserModule,\n    AppRoutingModule,\n    FormsModule\n  ],\n  //全局服务\n  providers: [],\n  //根组件\n  bootstrap: [AppComponent]\n})\n```\n\n```jsx\nexport class AppComponent {\n  userName='';\n}\n<div>\n    输入: <input [(ngModel)]=\"userName\">\n\t<h1>你输入了: {{userName}}</h1>\n</div>\n```\n\n#### \t3.3.7 模板引用变量\n\n模板变量可以帮助你在模板的另一部分使用这个部分的数据。使用模板变量，你可以执行某些任务，比如响应用户输入或微调应用的表单\n\n在模板中，要使用井号 `#` 来声明一个模板变量。下列模板变量 `#userName` 语法在 `<input>` 元素上声明了一个名为 `userName` 的变量\n\n```html\n<input #userName placeholder=\"请输入用户名\" />\n```\n\n可以在组件模板中的任何地方引用某个模板变量\n\n```jsx\n<button (click)=\"callUserName(userName.value)\">Call</button>\n\n\nexport class AppComponent {\n   callUserName(v){\n       console.log(v)\n   }\n}\n```\n\nAngular 根据你所声明的变量的位置给模板变量赋值：\n\n- 如果在组件上声明变量，该变量就会引用该组件实例。\n- 如果在标准的 HTML 标记上声明变量，该变量就会引用该元素。\n- 如果你在 `<ng-template>` 元素上声明变量，该变量就会引用一个 `TemplateRef` 实例来代表此模板。\n\n#### 3.3.8 表单控件\n\n使用表单控件有三个步骤。\n\n1. 在你的应用中注册响应式表单模块。该模块声明了一些你要用在响应式表单中的指令。\n2. 生成一个新的 `FormControl` 实例，并把它保存在组件中。\n3. 在模板中注册这个 `FormControl`。\n\n**注册响应式表单模块**\n\n要使用响应式表单控件，就要从 `@angular/forms` 包中导入 `ReactiveFormsModule`，并把它添加到你的 NgModule 的 `imports` 数组中。\n\n```js\nimport { ReactiveFormsModule } from '@angular/forms';\n\n@NgModule({\n  imports: [\n    // other imports ...\n    ReactiveFormsModule\n  ],\n})\nexport class AppModule { }\n```\n\n要注册一个表单控件，就要导入 `FormControl` 类并创建一个 `FormControl` 的新实例，将其保存为类的属性。\n\n```js\nimport { Component } from '@angular/core';\nimport { FormControl } from '@angular/forms';\n\n@Component({\n  selector: 'app-name-editor',\n  templateUrl: './name-editor.component.html',\n  styleUrls: ['./name-editor.component.css']\n})\nexport class NameEditorComponent {\n  name = new FormControl('');\n}\n\n//使用这种模板绑定语法，把该表单控件注册给了模板中名为 name 的输入元素。这样，表单控件和 DOM 元素就可以互相通讯了：视图会反映模型的变化，模型也会反映视图中的变化\n\n<label>\n  Name:\n  <input type=\"text\" [formControl]=\"name\">\n</label>\n<p>\n  Value: {{ name.value }}\n</p>\n```\n\n修改name值可以通过`FormControl` 提供的 `setValue()` 方法\n\n```js\nupdateName() {\n  this.name.setValue('Tina');\n}\n```\n\n#### 3.3.9 表单控件分组\n\n表单中通常会包含几个相互关联的控件。响应式表单提供了两种把多个相关控件分组到同一个输入表单中的方法\n\n要将表单组添加到此组件中，请执行以下步骤。\n\n1. 创建一个 `FormGroup` 实例。\n2. 把这个 `FormGroup` 模型关联到视图。\n3. 保存表单数据。\n\n**创建一个 FormGroup 实例**\n\n在组件类中创建一个名叫 `loginForm` 的属性，并设置为 `FormGroup` 的一个新实例。要初始化这个 `FormGroup`，请为构造函数提供一个由控件组成的对象，对象中的每个名字都要和表单控件的名字一一对应\n\n```js\nimport { Component } from '@angular/core';\nimport { FormGroup, FormControl } from '@angular/forms';\n\n@Component({\n  selector: 'app-profile-editor',\n  templateUrl: './profile-editor.component.html',\n  styleUrls: ['./profile-editor.component.css']\n})\nexport class ProfileEditorComponent {\n  loginForm = new FormGroup({\n    userName: new FormControl(''),\n    password: new FormControl(''),\n  });\n}\n\n//模板渲染\n<form [formGroup]=\"loginForm\">\n  \n  <label>\n    账号:\n    <input type=\"text\" formControlName=\"userName\">\n  </label>\n\n  <label>\n    密码:\n    <input type=\"text\" formControlName=\"password\">\n  </label>\n\n</form>\n```\n\n\n\n#### \t3.3.10 表单验证 \n\n​\t表单元素添加`required`关键字表示必填，通过绑定`ngModel`的引用可以拿到到当前组件的信息，通过引用获取到验证的信息\n\n```jsx\nexport class AppComponent {\n    fromData={\n       name:'',\n       password:''\n    };\n\n    subBtnFUn(obj){\n      console.log(obj)\n    }\n}\n\n<form  action=\"\">\n    账号：<input required #nameInp=\"ngModel\" type=\"text\" [(ngModel)]=\"fromData.name\" name=\"userName\">\n    <br>\n    <span>{{nameInp.valid }}</span>\n    <hr>\n    密码：<input required  #pasInp=\"ngModel\" type=\"text\" [(ngModel)]=\"fromData.password\" name=\"password\">\n    <br>\n    <span>{{pasInp.valid }}</span>\n    <hr>\n    <button (click)=\"subBtnFUn(nameInp)\">提交</button>\n</form>\n```\n\n我们还可以通过 **ngModel** 跟踪修改状态与有效性验证，它使用了三个 CSS 类来更新控件，以便反映当前状态。\n\n| 状态             | 为 true 时的类 | 为 false 时的类 |\n| :--------------- | :------------- | :-------------- |\n| 控件已经被访问过 | `ng-touched`   | `ng-untouched`  |\n| 控件值已经变化   | `ng-dirty`     | `ng-pristine`   |\n| 控件值是有效的   | `ng-valid`     | `ng-invalid`    |\n\n#### \t3.3.11 自定义表单验证\n\n​\t先引入表单的一些内置依赖\n\n```js\nimport { FormGroup, FormBuilder,Validators } from '@angular/forms';\n\n//构造函数里注入FormBuilder\nconstructor(private fb:FormBuilder) { }\n\n//错误提醒数据\nformErrors = {\n  'title': '',\n  'content': ''\n};\n\n\n//在组件类的初始化函数里对表单中的元素的校验进行定义，并调用表单的valueChanges方法，检测表单的输入的变化\nngOnInit():void {\n  this.taskInfo.isComplete = 1;\n  this.tasksForm = this.fb.group({\n     userName: ['', [Validators.required,\n    \t\t\t\t Validators.maxLength(18),\n                     Validators.minLength(6) ] ],\n    password: ['', [this.passWordVal]],\n    phone: ['', [Validators.required,this.phoneVal],]\n  });\n\n  phoneVal(phone: FormControl): object {\n    const value = phone.value || '';\n    if(!value) return  {desc:'请输入手机号'}\n    const valid =  /[0-9]{11}/.test(value);\n    return valid ? {} :{desc:'联系电话必须是11位数字'}\n  }\n  passWordVal(password:FormControl):object{\n    const value = password.value || '';\n    const valid = value.match(/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$/);\n    return valid ? {} : {passwordValidator: {desc:'密码至少包含 数字和英文，长度6-20'}}\n  }\n}\n```\n\n#### 3.3.12 管道\n\n​\t管道的作用就是传输。不同的管道具有不同的作用。(其实就是处理数据)\n\n`angular`中自带的`pipe`函数\n\n| 管道          | 功能                                                         |\n| ------------- | ------------------------------------------------------------ |\n| DatePipe      | 日期管道，格式化日期                                         |\n| JsonPipe      | 将输入数据对象经过JSON.stringify()方法转换后输出对象的字符串 |\n| UpperCasePipe | 将文本所有小写字母转换成大写字母                             |\n| LowerCasePipe | 将文本所有大写字母转换成小写字母                             |\n| DecimalPipe   | 将数值按照特定的格式显示文本                                 |\n| CurrentcyPipe | 将数值进行货币格式化处理                                     |\n| SlicePipe     | 将数组或者字符串裁剪成新子集                                 |\n| PercentPipe   | 将数值转百分比格式                                           |\n\n`pipe`用法\n\n- {{ 输入数据 | 管道 : 管道参数}} (其中‘|’是管道操作符)\n\n- 链式管道 {{ 输入数据 | date | uppercase}}\n\n- 管道流通方向自左向右，逐层执行\n\n    使用脚手架命令：**ng g p test**\n\n    ```js\n    import { Pipe, PipeTransform } from '@angular/core';\n    \n    @Pipe({\n      name: 'testTitle'\n    })\n    export class TestPipe implements PipeTransform {\n    \n      transform(value: unknown, ...args: unknown[]): unknown {\n        console.log(value)\n        return 'title';\n      }\n    }\n    \n    \n    <p>{{ 'Angular' | testTitle }}</p>\n    \n    ```\n\n    \n\n### \t3.4 服务\n\n   angular中，把从组件内抽离出来的代码叫服务，服务的本质就是函数\n\n 官方认为组件不应该直接获取或保存数据， 它们应该聚焦于展示数据，而把数据访问的职责委托给某个服务。而服务就充当着数据访问，逻辑处理的功能。把组件和服务区分开，以提高模块性和复用性。通过把组件中和视图有关的功能与其他类型的处理分离开，可以让组件类更加精简、高效。\n\n使用命令ng g s xxx创建一个服务，通过**@Injectable()**装饰器标识服务。\n\n```js\n//导入Injectable装饰器\nimport { Injectable } from '@angular/core';\n//使用Injectable装饰器声明服务\n@Injectable({\n  //作用域设定，'root'表示默认注入，注入到AppModule里\n  providedIn: 'root',\n})\nexport class TestService {\n}\n\n```\n\n组件中如何使用服务呢，必须将服务依赖注入系统、组件或者模块，才能够使用服务。我们可以用**注册提供商**和**根注入器**实现**。**\n\n   该服务本身是 CLI 创建的一个类，并且加上了 `@Injectable()` 装饰器。默认情况下，该装饰器是用 `providedIn` 属性进行配置的，它会为该服务创建一个提供商。\n\n\n\n### 3.5 依赖注入\n\n​\t在这个例子中，`providedIn: 'root'` 指定 Angular 应该在根注入器中提供该服务,从而实现**根注入器**将服务注入，它就在整个应用程序中可用了**。**\n\n**providedIn**：\n\n​\t'root' ：注入到AppModule，提供该服务，所有子组件都可以使用（推荐）\n\n​\tnull ： 不设定服务作用域（不推荐）\n\n​\t组件名：只作用于该组件（懒加载模式）\n\n```js\n\nimport { Injectable } from '@angular/core';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class TestService {\n}\n\n\nimport { Component, OnInit } from '@angular/core';\nimport {HeroService} from '../hero.service'\n\n@Component({\n  selector: 'app-test',\n  templateUrl: './test.component.html',\n  styleUrls: ['./test.component.scss'],\n})\nexport class TestComponent implements OnInit {\n\n  constructor(private heroService:HeroService) { }\n\n  ngOnInit(): void {\n    console.log(this.heroService.getHeroList())\n  }\n}\n```\n\n​\t也可以使用 `@Component` 或 `@Directive` 内部的 `providers: []`，为特定的组件子树提供服务，这也将导致创建多个服务实例(每个组件使用一个服务实例)\n\n```js\n\nimport { Injectable } from '@angular/core';\n\n@Injectable({\n  providedIn: 'root',\n})\nexport class TestService {\n}\n\n\nimport { Component, OnInit } from '@angular/core';\nimport {HeroService} from '../hero.service'\n\n@Component({\n  selector: 'app-test',\n  templateUrl: './test.component.html',\n  styleUrls: ['./test.component.scss'],\n  providers: [HeroService]\n})\nexport class TestComponent implements OnInit {\n\n  constructor(private heroService:HeroService) { }\n\n  ngOnInit(): void {\n    console.log(this.heroService.getHeroList())\n  }\n}\n```\n\n\n\n### 3.6 路由\n\n​\t路由就是连接组件的筋络,它也是树形结构的.有了它,就可以在angular中实现路径的导航模式\n\n可以把路由看成是一组规则,它决定了url的变化对应着哪一种状态,具体表现就是不同视图的切换\n\n在angular中,路由是非常重要的组成部分, 组件的实例化与销毁,模块的加载,组件的某些生命周期钩子的发起,都是与它有关\n\n#### 3.6.1 路由基本使用\n\n**路由器**是一个调度中心,它是一套规则的列表,能够查询当前URL对应的规则,并呈现出相应的视图.\n\n**路由**是列表里面的一个规则,即路由定义,它有很多功能字段:\n\n- **path**字段,表示该路由中的URL路径部分\n- **Component**字段,表示与该路由相关联的组件\n\n每个带路由的Angular应用都有一个路由器服务的单例对象,通过路由定义的列表进行配置后使用。\n\n```js\nimport { NgModule } from '@angular/core';\nimport { RouterModule, Routes } from '@angular/router';\nimport {HomeComponent} from './home/home.component'\n\nconst routes: Routes = [\n {path:'home',component:HomeComponent}\n];\n\n@NgModule({\n  imports: [RouterModule.forRoot(routes)],\n  exports: [RouterModule]\n})\nexport class AppRoutingModule { }\n\n```\n\n```jsx\n//路由导航 \n<a [routerLink]=\"['/home']\">home</a>\n <a [routerLink]=\"['/hello']\">hello</a>\n\n//组件渲染输出\n<router-outlet></router-outlet>\n```\n\n上述具体的工作流程,可以举例简单描述为：\n\n- 当浏览器地址栏的URL变化时，路径部分`/home`满足了列表中path为\"**home**\"的这个路由定义,激活对应**HomeComponent**的实例,显示它的视图\n- 当应用程序请求导航到路径`/hello`时,符合了另外的规则,激活对应视图且展示内容,并将该路径更新到浏览器地址栏和历史\n\n####   3.6.2 路由嵌套\n\n​\t\t父子路由嵌套配置：\n\n```js\nconst routes: Routes = [\n    {path:'home',\n      component:HomeComponent,\n      children:[\n        {path:'hello',component:HelloComponent}\n      ]\n    },\n];\n```\n\n​\t在**home Component**内这是`router-outlet`路由出口，即可在**home** 路由内渲染子级路由\n\n```jsx\n//home template\n<h2>home Component</h2>\n<a [routerLink]=\"['/home/hello']\">hello</a>\n<router-outlet></router-outlet>\n```\n\n​\t在非`home Component`内跳转到`/home/hello`路由需要写全路径\n\n```jsx\n//app template\n<h2>app Component</h2>\n<a [routerLink]=\"['/home/hello']\">hello</a>\n<router-outlet></router-outlet>\n```\n\n​\t\t\n\n####    3.6.3 路由传参\n\n- **query**\n\n     在a标签上添加一个参数queryParams，并通过`this.routerinfo.snapshot.queryParams`获取参数\n\n    ```jsx\n    <a [routerLink]=\"['/hello']\" [queryParams]=\"{id:3}\" >hello</a>\n    ```\n\n    ```js\n    import {ActivatedRoute} from '@angular/router';\n    constructor(private routerinfo:ActivatedRoute) { }\n    \n     ngOnInit() {\n      //id为参数名字\n        this.id=this.routerinfo.snapshot.queryParams[\"id\"]\n      }\n    ```\n\n    \n\n- **params**\n\n    修改路由配置文件`path`,路由导航`a`标签`routerLink`后面数组的第二个参数为传递的值\n\n    并且通过`subscribe`请阅的方式获取`name`参数\n\n    ```js\n     {\n        path: 'hello/:name',\n        component:HelloComponent,\n      },\n    ```\n\n    ```jsx\n    //我们在后面添加/:name此时name即为传递的参数名字\n    //a标签设置如下\n    <a [routerLink]=\"['/hello','我是url传递参数']\" [queryParams]=\"{id:3}\" >hello</a>\n    ```\n\n    ```js\n    ngOnInit() {\n        this.routerinfo.params.subscribe((params:Params)=>{\n          this.name=params['name']\n        })\n      }\n    ```\n\n"
  },
  {
    "path": "notes/Frontend/README.md",
    "content": "这里将是前端相关的项目目录"
  },
  {
    "path": "notes/Frontend/assets/README.md",
    "content": "这里是图片目录"
  },
  {
    "path": "notes/Frontend/前端知识体系.md",
    "content": "<!-- TOC -->\n\n- [前端知识体系](#前端知识体系)\n  - [前端三要素](#前端三要素)\n    - [结构层（HTML）](#结构层html)\n    - [表现层（CSS）](#表现层css)\n      - [什么是 CSS 预处理器](#什么是-css-预处理器)\n      - [常用的 CSS 预处理器有哪些](#常用的-css-预处理器有哪些)\n    - [行为层（JavaScript）](#行为层javascript)\n      - [Native 原生 JS 开发](#native-原生-js-开发)\n      - [TypeScript 微软的标准](#typescript-微软的标准)\n      - [JavaScript 框架](#javascript-框架)\n      - [UI 框架](#ui-框架)\n      - [JavaScript 构建工具](#javascript-构建工具)\n  - [三端统一](#三端统一)\n    - [混合开发（Hybrid App）](#混合开发hybrid-app)\n    - [微信小程序](#微信小程序)\n  - [后端技术](#后端技术)\n  - [附：当前主流前端框架](#附当前主流前端框架)\n    - [Angular](#angular)\n      - [Angular Material](#angular-material)\n    - [Vue.js](#vuejs)\n      - [iView](#iview)\n      - [ElementUI](#elementui)\n      - [ICE](#ice)\n      - [VantUI](#vantui)\n      - [AtUI](#atui)\n      - [CubeUI](#cubeui)\n    - [混合开发](#混合开发)\n      - [Flutter](#flutter)\n      - [Ionic](#ionic)\n    - [微信小程序](#微信小程序-1)\n      - [mpvue](#mpvue)\n      - [WeUI](#weui)\n\n<!-- /TOC -->\n\n# 前端知识体系\n\n## 前端三要素\n\n- HTML（结构）：超文本标记语言（Hyper Text Markup Language），决定网页的结构和内容\n- CSS（表现）：层叠样式表（Cascading Style Sheets），设定网页的表现样式\n- JavaScript（行为）：是一种弱类型脚本语言，其源代码不需经过编译，而是由浏览器解释运行，用于控制网页的行为\n\n### 结构层（HTML）\n\n略\n\n### 表现层（CSS）\n\nCSS 层叠样式表是一门标记语言，并不是编程语言，因此不可以自定义变量，不可以引用等，换句话说就是不具备任何语法支持，它主要缺陷如下：\n\n- 语法不够强大，比如无法嵌套书写，导致模块化开发中需要书写很多重复的选择器；\n- 没有变量和合理的样式复用机制，使得逻辑上相关的属性值必须以字面量的形式重复输出，导致难以维护；\n\n这就导致了我们在工作中无端增加了许多工作量。为了解决这个问题，前端开发人员会使用一种称之为 **【CSS 预处理器】** 的工具，提供 CSS 缺失的样式层复用机制、减少冗余代码，提高样式代码的可维护性。大大提高了前端在样式上的开发效率。\n\n#### 什么是 CSS 预处理器\n\nCSS 预处理器定义了一种新的语言，其基本思想是，用一种专门的编程语言，为 CSS 增加了一些编程的特性，将 CSS 作为目标生成文件，然后开发者就只要使用这种语言进行 CSS 的编码工作。转化成通俗易懂的话来说就是“**用一种专门的编程语言，进行 Web 页面样式设计，再通过编译器转化为正常的 CSS 文件，以供项目使用**”。\n\n#### 常用的 CSS 预处理器有哪些\n\n- SASS：基于 Ruby，通过服务端处理，功能强大。解析效率高。需要学习 Ruby 语言，上手难度高于 LESS。\n- LESS：基于 NodeJS，通过客户端处理，使用简单。功能比 SASS 简单，解析效率也低于 SASS，但在实际开发中足够了，所以我们后台人员如果需要的话，建议使用 LESS。\n\n### 行为层（JavaScript）\n\nJavaScript 一门弱类型脚本语言，其源代码在发往客户端运行之前不需经过编译，而是将文本格式的字符代码发送给浏览器由浏览器解释运行。\n\n#### Native 原生 JS 开发\n\n原生 JS 开发，也就是让我们按照 **【ECMAScript】** 标准的开发方式，简称是 ES，特点是所有浏览器都支持。截止到当前博客发布时间（2018 年 12 月 04 日），ES 标准已发布如下版本：\n\n- ES3\n- ES4（内部，未正式发布）\n- ES5（全浏览器支持）\n- ES6（常用，当前主流版本）\n- ES7\n- ES8\n- ES9（草案阶段）\n\n区别就是逐步增加新特性。\n\n#### TypeScript 微软的标准\n\nTypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集，而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。由安德斯·海尔斯伯格（C#、Delphi、TypeScript 之父；.NET 创立者）主导。\n\n该语言的特点就是除了具备 ES 的特性之外还纳入了许多不在标准范围内的新特性，所以会导致很多浏览器不能直接支持 TypeScript 语法，需要编译后（编译成 JS）才能被浏览器正确执行。\n\n#### JavaScript 框架\n\n- jQuery：大家熟知的 JavaScript 框架，优点是简化了 DOM 操作，缺点是 DOM 操作太频繁，影响前端性能；在前端眼里使用它仅仅是为了兼容 IE6、7、8；\n- Angular：Google 收购的前端框架，由一群 Java 程序员开发，其特点是将后台的 MVC 模式搬到了前端并增加了模块化开发的理念，与微软合作，采用 TypeScript 语法开发；对后台程序员友好，对前端程序员不太友好；最大的缺点是版本迭代不合理（如：1 代 -> 2 代，除了名字，基本就是两个东西；截止发表博客时已推出了 Angular8）。Angular 作为 Google 推出的一个“重型”的框架，内置包括路由、HTTP 等常用组件，同时深度集成 TypeScript，WebPack，RxJS，angular-cli 等工具，可以方便地进行大型前端项目的开发以及调试。但是由于 Angular 引入了许多概念，增加了新手上手的复杂性。特别是组件、模块、服务、指令、依赖注入的引入，对于新手而言，厘清其中各个概念的作用域是就不是一件容易的事情。同时 RxJS 的引入，也进一步使得学习曲线变得陡峭。\n- React：Facebook 出品，一款高性能的 JS 前端框架；特点是提出了新概念 **【虚拟 DOM】** 用于减少真实 DOM 操作，在内存中模拟 DOM 操作，有效的提升了前端渲染效率；缺点是使用复杂，因为需要额外学习一门 **【JSX】** 语言；\n- `Vue`：一款渐进式 JavaScript 框架，所谓渐进式就是逐步实现新特性的意思，如实现模块化开发、路由、状态管理等新特性。其特点是综合了 Angular（模块化） 和 React（虚拟 DOM） 的优点；\n- `Axios`：前端通信框架；因为 `Vue` 的边界很明确，就是为了处理 DOM，所以并不具备通信能力，此时就需要额外使用一个通信框架与服务器交互；当然也可以直接选择使用 jQuery 提供的 AJAX 通信功能；\n\n#### UI 框架\n\n- Ant-Design：阿里巴巴出品，基于 React 的 UI 框架\n- ElementUI：饿了么出品，基于 Vue 的 UI 框架\n- Bootstrap：Twitter 推出的一个用于前端开发的开源工具包\n- AmazeUI：又叫“妹子 UI”，一款 HTML5 跨屏前端框架\n\n#### JavaScript 构建工具\n\n- Babel：JS 编译工具，主要用于浏览器不支持的 ES 新特性，比如用于编译 TypeScript\n- WebPack：模块打包器，主要作用是打包、压缩、合并及按序加载\n\n**注：以上知识点已将 WebApp 开发所需技能全部梳理完毕**\n\n## 三端统一\n\n### 混合开发（Hybrid App）\n\n主要目的是实现一套代码三端统一（PC、Android、iOS）并能够调用到设备底层硬件（如：传感器、GPS、摄像头等），打包方式主要有以下两种：\n\n- 云打包：HBuild -> HBuildX，DCloud 出品；API Cloud\n- 本地打包： Cordova（前身是 PhoneGap）\n\n### 微信小程序\n\n详见微信官网，这里就是介绍一个方便微信小程序 UI 开发的框架：WeUI\n\n## 后端技术\n\n前端人员为了方便开发也需要掌握一定的后端技术，但我们 Java 后台人员知道后台知识体系极其庞大复杂，所以为了方便前端人员开发后台应用，就出现了 NodeJS 这样的技术。\n\nNodeJS 的作者已经声称放弃 NodeJS（说是架构做的不好再加上笨重的 node_modules，可能让作者不爽了吧），开始开发全新架构的 Deno\n\n既然是后台技术，那肯定也需要框架和项目管理工具，NodeJS 框架及项目管理工具如下：\n\n- Express：NodeJS 框架\n- Koa：Express 简化版\n- NPM：项目综合管理工具，类似于 Maven\n- YARN：NPM 的替代方案，类似于 Maven 和 Gradle 的关系\n\n## 附：当前主流前端框架\n\n### Angular\n\n#### Angular Material\n\nAngular Material 是 Angular 官方团队维护的一个遵从 Material Design 的 Angular UI 库。它提供了丰富的组件库、CDK、以及内置主题，开箱即用，并且与 Angular 版本同步。\n\n- [官网地址](https://material.angular.io/)\n- [Github](https://github.com/angular/components)\n\n#### ngx-admin\n\nngx-admin 是一款开源的 Angualr UI 库，目前仍处于维护和活跃状态。它也提供了丰富的开箱即用的组件库、内置主题，并且与 Angular 版本同步。代码风格遵循 Angular 最佳实践，也是一个不错的学习资源。\n\n- [Github](https://github.com/akveo/ngx-admin)\n\n### Vue.js\n\n#### iView\n\niview 是一个强大的基于 Vue 的 UI 库，有很多实用的基础组件比 elementui 的组件更丰富，主要服务于 PC 界面的中后台产品。使用单文件的 Vue 组件化开发模式 基于 npm + webpack + babel 开发，支持 ES2015 高质量、功能丰富 友好的 API ，自由灵活地使用空间。\n\n- [官网地址](https://www.iviewui.com/)\n- [Github](https://github.com/TalkingData/iview-weapp)\n- [iview-admin](https://github.com/iview/iview-admin)\n\n**备注：属于前端主流框架，选型时可考虑使用，主要特点是移动端支持较多**\n\n#### ElementUI\n\nElement 是饿了么前端开源维护的 Vue UI 组件库，组件齐全，基本涵盖后台所需的所有组件，文档讲解详细，例子也很丰富。主要用于开发 PC 端的页面，是一个质量比较高的 Vue UI 组件库。\n\n- [官网地址](http://element-cn.eleme.io/#/zh-CN)\n- [Github](https://github.com/ElementUI/element-starter)\n- [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)\n\n**备注：属于前端主流框架，选型时可考虑使用，主要特点是桌面端支持较多**\n\n#### ICE\n\n飞冰是阿里巴巴团队基于 React/Angular/Vue 的中后台应用解决方案，在阿里巴巴内部，已经有 270 多个来自几乎所有 BU 的项目在使用。飞冰包含了一条从设计端到开发端的完整链路，帮助用户快速搭建属于自己的中后台应用。\n\n- [官网地址](https://alibaba.github.io/ice)\n- [Github](https://github.com/alibaba/ice)\n\n**备注：主要组件还是以 React 为主，截止 2019 年 02 月 17 日更新博客前对 Vue 的支持还不太完善，目前尚处于观望阶段**\n\n#### VantUI\n\nVant UI 是有赞前端团队基于有赞统一的规范实现的 Vue 组件库，提供了一整套 UI 基础组件和业务组件。通过 Vant，可以快速搭建出风格统一的页面，提升开发效率。\n\n- [官网地址](https://youzan.github.io/vant/#/zh-CN/intro)\n- [Github](https://github.com/youzan/vant)\n\n#### AtUI\n\nat-ui 是一款基于 Vue 2.x 的前端 UI 组件库，主要用于快速开发 PC 网站产品。 它提供了一套 npm + webpack + babel 前端开发工作流程，CSS 样式独立，即使采用不同的框架实现都能保持统一的 UI 风格。\n\n- [官网地址](https://at-ui.github.io/at-ui/#/zh)\n- [Github](https://github.com/at-ui/at-ui)\n\n#### CubeUI\n\ncube-ui 是滴滴团队开发的基于 Vue.js 实现的精致移动端组件库。支持按需引入和后编译，轻量灵活；扩展性强，可以方便地基于现有组件实现二次开发。\n\n- [官网地址](https://didi.github.io/cube-ui/#/zh-CN)\n- [Github](https://github.com/didi/cube-ui/)\n\n### 混合开发\n\n#### Flutter\n\nFlutter 是谷歌的移动端 UI 框架，可在极短的时间内构建 Android 和 iOS 上高质量的原生级应用。Flutter 可与现有代码一起工作, 它被世界各地的开发者和组织使用, 并且 Flutter 是免费和开源的。\n\n- [官网地址](http://doc.flutter-dev.cn/)\n- [Github](https://github.com/flutter/flutter)\n\n**备注：Google 出品，主要特点是快速构建原生 APP 应用程序，如做混合应用该框架为必选框架**\n\n#### Ionic\n\nIonic 既是一个 CSS 框架也是一个 Javascript UI 库，Ionic 是目前最有潜力的一款 HTML5 手机应用开发框架。通过 SASS 构建应用程序，它提供了很多 UI 组件来帮助开发者开发强大的应用。它使用 JavaScript MVVM 框架和 AngularJS/Vue 来增强应用。提供数据的双向绑定，使用它成为 Web 和移动开发者的共同选择。\n\n- [官网地址](https://ionicframework.com/)\n- [官网文档](https://ionicframework.com/docs/)\n- [Github](https://github.com/ionic-team/ionic)\n\n### 微信小程序\n\n#### mpvue\n\nmpvue 是美团开发的一个使用 `Vue.js` 开发小程序的前端框架，目前支持 **微信小程序**、**百度智能小程序**，**头条小程序** 和 **支付宝小程序**。 框架基于 `Vue.js`，修改了的运行时框架 `runtime` 和代码编译器 `compiler` 实现，使其可运行在小程序环境中，从而为小程序开发引入了 `Vue.js` 开发体验。\n\n- [官网地址](http://mpvue.com/)\n- [Github](https://github.com/Meituan-Dianping/mpvue)\n\n**备注：完备的 Vue 开发体验，并且支持多平台的小程序开发，推荐使用**\n\n#### WeUI\n\nWeUI 是一套同微信原生视觉体验一致的基础样式库，由微信官方设计团队为微信内网页和微信小程序量身设计，令用户的使用感知更加统一。包含 button、cell、dialog、toast、article、icon 等各式元素。\n\n- [官网地址](https://weui.io/)\n- [Github](https://github.com/weui/weui.git)\n"
  },
  {
    "path": "notes/Git.md",
    "content": "<!-- TOC -->\n\n- [前言](#前言)\n- [一、Git 简介](#一git-简介)\n    - [什么是版本控制系统](#什么是版本控制系统)\n        - [为什么需要版本 控制](#为什么需要版本-控制)\n        - [什么是版本控制系统](#什么是版本控制系统-1)\n        - [版本控制系统的发展史](#版本控制系统的发展史)\n    - [什么是 Git](#什么是-git)\n    - [如何安装 Git](#如何安装-git)\n- [二、Git 常用命令速查](#二git-常用命令速查)\n    - [配置](#配置)\n    - [基础操作](#基础操作)\n    - [比对 diff](#比对-diff)\n    - [历史 log](#历史-log)\n    - [分支 branch](#分支-branch)\n    - [远程](#远程)\n- [三、Git 常用场景](#三git-常用场景)\n    - [1. 删除本地文件后，想从远程仓库中重新Pull最新版文件](#1-删除本地文件后想从远程仓库中重新pull最新版文件)\n    - [2. 储藏与清理](#2-储藏与清理)\n\n<!-- /TOC -->\n\n# 前言\n\n这是一篇入门级使用指南，更多详细的请参考 [Git 官网电子书](https://git-scm.com/book/zh/v2)。\n\n在这里将列举一些常用命令和场景解决方案，欢迎大家补充学习。\n\n\n\n# 一、Git 简介\n\n## 什么是版本控制系统\n\n### 为什么需要版本 控制\n\n在软件开发过程，每天都会产生新的代码，代码合并的过程中可能会出现如下问题：\n\n- 代码被覆盖或丢失\n- 代码写的不理想希望还原之前的版本\n- 希望知道与之前版本的差别\n- 是谁修改了代码以及为什么修改\n- 发版时希望分成不同的版本(测试版、发行版等)\n\n因此，我们希望有一种机制，能够帮助我们：\n\n- 可以随时回滚到之前的版本\n- 协同开发时不会覆盖别人的代码\n- 留下修改记录，以便随时查看\n- 发版时可以方便的管理不同的版本\n\n### 什么是版本控制系统\n\n一个标准的版本控制系统 Version Control System (VCS)，通常需要有以下功能：\n\n- 能够创建 Repository (仓库)，用来保存代码\n- 协同开发时方便将代码分发给团队成员\n- 记录每次修改代码的内容、时间、原因等信息\n- 能够创建 Branch (分支)，可以根据不同的场景进行开发\n- 能够创建 Tag (标签)，建立项目里程碑\n\n### 版本控制系统的发展史\n\n版本控制系统发展至今有几种不同的模式：\n\n**Local VCS**\n\n本地使用 `复制/粘贴` 的方式进行管理，缺点是无法协同开发\n\n**Centralized VCS (Lock，悲观锁)**\n\n中央集中式版本控制系统团队共用仓库，当某人需要编辑文件时，进行锁定，以免其他人同时编辑时造成冲突。缺点是虽然避免了冲突，但不是很方便。其他人需要排队才能编辑文件，如果有人编辑了很久或是忘记解锁就会造成其他人长时间等待的情况。\n\n**Centralized VCS (Merge，乐观锁)**\n\n中央集中式版本控制系统团队共用仓库，不采用悲观锁方式来避免冲突，而是事后发现如果别人也修改相同文件(冲突)，再进行手动修改解决。有很多 VCS 属于这种类型，如：CVS，Subversion，Perforce 等\n\n中央集中式版本控制系统的共同问题是，做任何操作都需要和服务器同步，如果服务器宕机则会造成无法继续工作的窘迫。\n\n**Distributed VCS**\n\n分布式版本控制系统，本地也拥有完整的代码仓库，就不会出现上述集中式管理的问题，即使没有网络，依然可以 `commit` 和看 `log`，也无需担心服务器同步问题。如：Git，Mercurial，Bazaar 等就属于分布式版本控制系统。缺点是功能比较复杂，上手需要一定的学习时间。\n\n\n\n## 什么是 Git\n\n- Git 是一个开源的分布式版本控制系统，用于敏捷高效地处理任何或小或大的项目。\n- Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。\n- Git 与常用的版本控制工具 CVS, Subversion 等不同，它采用了分布式版本库的方式，不必服务器端软件支持。\n\n## 如何安装 Git\n\n- Git 官网下载地址：[Git 版本管理工具](https://git-scm.com/downloads)\n\n- 客户端推荐：[SourceTree](https://www.sourcetreeapp.com/)，软件安装跳过注册，[请参考这里](https://www.cnblogs.com/lucio110/p/8192792.html?tdsourcetag=s_pcqq_aiomsg)\n\n\n\n\n\n# 二、Git 常用命令速查\n\n一图胜千言\n\n![img](assets/TQDj8Uo1pj3YkMSoeSitYC1QB4a019V68N6GZFBE.png)\n\n\n\n推荐一个不错的可视化工具：[Git Cheat Sheet](http://ndpsoftware.com/git-cheatsheet.html)\n\n## 配置\n\n设置提交者姓名\n\n```shell\n$ git config --global user.name \"John Doe\"\n```\n\n设置提交者邮箱\n\n```shell\n$ git config --global user.email johndoe@example.com\n```\n\n查看配置列表\n\n```shell\n$ git config --list\n```\n\n\n\n## 基础操作\n\n在指定目录创建仓库，如果没有指定目录名将在当前目录创建仓库\n\n```shell\n$ git init [目录名]\n\n# 当前文件夹初始化\n$ git init .\n\n# 指定目录\n$ git init frank\n```\n\n从指定地址克隆仓库，若不指定`目录名`将默认创建与远程同名目录\n\n```shell\n$ git clone <远程仓库地址> [目录名]\n\n# 不想创建目录，目录名为 . ，直接加在内容到当前目录下\n$ git clone https://github.com/frank-lam/2019_campus_apply.git .\n```\n\n将文件或目录中已修改的代码添加追暂存区\n\n```shell\n$ git add <目录名|文件名>\n```\n\n提交暂存区内容\n\n```shell\n$ git commit -m \"<注释>\"\n```\n\n查看仓库状态\n\n```shell\n$ git status\n```\n\n\n\n## 比对 diff\n\n比对当前内容和暂存区内容\n\n```shell\n$ git diff\n```\n\n比对当前内容和最近一次提交\n\n```shell\n$ git diff HEAD\n```\n\n比对当前内容和倒数第二次提交\n\n```shell\n$ git diff HEAD^\n```\n\n比对最近两次提交\n\n```shell\n$ git diff HEAD^ HEAD\n```\n\n\n\n## 历史 log\n\n查看提交历史\n\n```shell\n$ git log --oneline\n```\n\n打印为单行log\n\n```shell\n$ git log --oneline\n```\n\n打印所有记录（忽略HEAD的位置）\n\n```shell\n$ git log --all\n```\n\n打印示意图（忽略HEAD的位置）\n\n```shell\n$ git log --graph\n```\n\n\n\n## 分支 branch\n\n查看所有分支\n\n```shell\n$ git branch\n```\n\n有分支：创建分支，无分支：列出所有分支\n\n```shell\n$ git branch [分支]\n```\n\n切换至分支\n\n```shell\n$ git checkout <分支>\n```\n\n创建并切换至分支分支\n\n```shell\n$ git checkout -b <分支>\n```\n\n将分支与当前分支合并\n\n```shell\n$ git merge <分支>\n```\n\n\n\n## 远程\n\n拉取远程仓库\n\n```shell\n$ git pull\n```\n\n推送至远程仓库\n\n```shell\n$ git push <远程仓库> <分支>\n```\n\n新增远程仓库 origin\n\n```shell\n$ git remote add origin https://xxx.git\n```\n\n修改远程仓库 origin\n\n```shell\n$ git remote set-url origin https://xxx.git\n```\n\n\n\n\n\n# 三、Git 常用场景\n\n## 1. 删除本地文件后，想从远程仓库中重新 Pull 最新版文件\n\nGit提示：up-to-date，但未得到删除的文件\n\n原因：当前本地库处于另一个分支中，需将本分支发 Head 重置至 master\n\n```bash\n$ git checkout master \n$ git reset --hard\n```\n\ngit 强行 pull 并覆盖本地文件\n\n```shell\n$ git fetch --all  \n$ git reset --hard origin/master \n$ git pull\n```\n\n## 2. 储藏与清理\n\n应用场景：\n\n1. 当正在 dev 分支上开发某个项目，这时项目中出现一个 bug，需要紧急修复，但是正在开发的内容只是完成一半，还不想提交，这时可以用 git stash 命令将修改的内容保存至堆栈区，然后顺利切换到 hotfix 分支进行 bug 修复，修复完成后，再次切回到 dev 分支，从堆栈中恢复刚刚保存的内容。 \n2. 由于疏忽，本应该在 dev 分支开发的内容，却在 master 上进行了开发，需要重新切回到 dev 分支上进行开发，可以用 git stash 将内容保存至堆栈中，切回到 dev 分支后，再次恢复内容即可。 \n\n总的来说，git stash 命令的作用就是将目前还不想提交的但是已经修改的内容进行保存至堆栈中，后续可以在某个分支上恢复出堆栈中的内容。这也就是说，stash 中的内容不仅仅可以恢复到原先开发的分支，也可以恢复到其他任意指定的分支上。git stash 作用的范围包括工作区和暂存区中的内容，也就是说没有提交的内容都会保存至堆栈中。\n\n\n\n- [Git - 储藏与清理](https://git-scm.com/book/zh/v2/Git-%E5%B7%A5%E5%85%B7-%E5%82%A8%E8%97%8F%E4%B8%8E%E6%B8%85%E7%90%86)\n- [git stash详解 - stone_yw的博客 - CSDN博客](https://blog.csdn.net/stone_yw/article/details/80795669)\n\n\n\n## 3. SSH 连接配置\n\n### 1. 生成密钥对\n\n大多数 Git 服务器都会选择使用 SSH 公钥来进行授权。系统中的每个用户都必须提供一个公钥用于授权，没有的话就要生成一个。生成公钥的过程在所有操作系统上都差不多。首先你要确认一下本机是否已经有一个公钥。\n\nSSH 公钥默认储存在账户的主目录下的 ~/.ssh 目录。进去看看：\n\n```shell\n$ cd ~/.ssh\n$ ls\nauthorized_keys2  id_dsa       known_hosts config            id_dsa.pub\n```\n\n看一下有没有id_rsa和id_rsa.pub(或者是id_dsa和id_dsa.pub之类成对的文件)，有 .pub 后缀的文件就是公钥，另一个文件则是密钥。\n\n假如没有这些文件，甚至连 .ssh 目录都没有，可以用 ssh-keygen 来创建。该程序在 Linux/Mac 系统上由 SSH 包提供，而在 Windows 上则包含在 MSysGit 包里：\n\n```shell\n$ ssh-keygen -t rsa -C \"your_email@youremail.com\"\n\nCreates a new ssh key using the provided email # Generating public/private rsa key pair.\n\nEnter file in which to save the key (/home/you/.ssh/id_rsa):\n```\n\n直接按Enter就行。然后，会提示你输入密码，如下(建议输一个，安全一点，当然不输也行，应该不会有人闲的无聊冒充你去修改你的代码)：\n\n```shell\nEnter same passphrase again: [Type passphrase again]\n```\n\n完了之后，大概是这样：\n\n```shell\nYour public key has been saved in /home/you/.ssh/id_rsa.pub.\nThe key fingerprint is: # 01:0f:f4:3b:ca:85:d6:17:a1:7d:f0:68:9d:f0:a2:db your_email@youremail.com\n```\n\n到此为止，你本地的密钥对就生成了。\n\n\n(9+条消息)Mac OS 配置多个ssh-key - maoxinwen1的博客 - CSDN博客\nhttps://blog.csdn.net/maoxinwen1/article/details/80269299\n\n\n### 2. 添加公钥到你的远程仓库（github）\n\n1. 查看你生成的公钥：\n\n```\n$ cat ~/.ssh/id_rsa.pub\n\nssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0X6L1zLL4VHuvGb8aJH3ippTozmReSUzgntvk434aJ/v7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8vR3c8E7CjZN733f5AL8uEYJA+YZevY5UCvEg+umT7PHghKYaJwaCxV7sjYP7Z6V79OMCEAGDNXC26IBMdMgOluQjp6o6j2KAdtRBdCDS/QIU5THQDxJ9lBXjk1fiq9tITo/aXBvjZeD+gH/Apkh/0GbO8VQLiYYmNfqqAHHeXdltORn8N7C9lOa/UW3KM7QdXo6J0GFlBVQeTE/IGqhMS5PMln3 admin@admin-PC\n```\n\n2. 登陆你的 GitHub 帐户。点击你的头像，然后 `Settings -> 左栏点击 SSH and GPG keys -> 点击 New SSH key`\n\n3. 然后你复制上面的公钥内容，粘贴进“Key”文本域内。 title域，自己随便起个名字。\n\n4. 点击 Add key。\n\n完成以后，验证下这个key是不是正常工作：\n\n```shell\n$ ssh -T git@github.com\n\nAttempts to ssh to github\n```\n\n如果，看到：\n\n```\nHi xxx! You've successfully authenticated, but GitHub does not # provide shell access.\n```\n\n恭喜你，你的设置已经成功了。\n\n### 3. 修改git的remote url\n\n 使用命令 git remote -v 查看你当前的 remote url\n\n```shell\n$ git remote -v\norigin https://github.com/someaccount/someproject.git (fetch)\norigin https://github.com/someaccount/someproject.git (push)\n```\n\n如果是以上的结果那么说明此项目是使用https协议进行访问的（如果地址是git开头则表示是git协议）\n\n你可以登陆你的github，就像本文开头的图例，你在上面可以看到你的ssh协议相应的url，类似：\n\n ![img](assets/1160195-20170512120555144-795931549.png)\n\n复制此ssh链接，然后使用命令 git remote set-url 来调整你的url。\n\n```shell\n$ git remote set-url origin git@github.com:someaccount/someproject.git\n```\n\n然后你可以再用命令 git remote -v 查看一下，url是否已经变成了ssh地址。\n\n然后你就可以愉快的使用 git fetch, git pull , git push，再也不用输入烦人的密码了\n\n\n\n\n\n### 4. 常见问题\n\n- [Git 提交大文件提示 fatal: The remote end hung up unexpectedly - WNFK - 博客园](https://www.cnblogs.com/hanxianlong/p/3464224.html)\n\n\n\n## 4. Git 记住密码\n\n- [Git Pull 避免用户名和密码方法 - 王信平 - 博客园](https://www.cnblogs.com/wangshuo1/p/5531200.html)\n\n\n\n## 5. Git FTP 使用\n\n利用Git版本管理将只修改过的文件上传到FTP服务器 支持SFTP协议 - 吕滔博客\nhttps://lvtao.net/tool/gitftp.html\n\n\n\n## 6. Git 删除文件如何提交\n\n\n\n## 7. Git 中的 origin 是什么意思\n\ngit学习：关于origin和master - mashiqi - 博客园\nhttps://www.cnblogs.com/mashiqi/p/6002671.html\n\n\n\nGit 里面的 origin 到底代表啥意思? - Not Only DBA. - CSDN博客\nhttps://blog.csdn.net/u011478909/article/details/77683754\n\n\n\n## 8. Git 回退到制定版本\n\n**回滚到指定的版本**\n\n```shell\ngit reset --hard e377f60e28c8b84158\n```\n\n**强制提交**\n\n```shell\ngit push -f origin master\n```\n\n"
  },
  {
    "path": "notes/Git命令速查.md",
    "content": "# Git 命令速查\n\n> 本文参考极客时间苏玲老师的《玩转Git三剑客》\n\n\n\n## 添加配置\n```bash\ngit config [--local | --global | --system] user.name 'Your name'\ngit config [--local | --global | --system] user.email 'Your email'\n```\n\n\n\n\n\n## 查看配置\n\n```bash\ngit config --list [--local | --global | --system]\n```\n### 区别\n```bash\nlocal：区域为本仓库\nglobal: 当前用户的所有仓库\nsystem: 本系统的所有用户\n```\n\n\n## `git add .` 和 `git add -u`区别\n\n```bash\ngit add . ：将工作空间新增和被修改的文件添加的暂存区\ngit add -u :将工作空间被修改和被删除的文件添加到暂存区(不包含没有纳入Git管理的新增文件)\n```\n\n\n\n## 创建仓库\n\n```bash\ngit init [project folder name]  初始化 git 仓库\ngit add [fileName]  把文件从工作目录添加到暂存区\ngit commit -m'some information'  用于提交暂存区的文件\ngit commit -am'Some information' 用于提交跟踪过的文件\ngit log  查看历史\ngit status  查看状态\n```\n\n**额外**    \ngit add -u 可以添加所有已经被 git 控制的文件到暂存区\n以前删除文件夹只会用 「-rf」，今天学到了 「-r」，并得知它们两个区别：「-r」 有时候会提示是否确认删除。    \n\n\n\n## 给文件重命名的简便方法\n\n```bash\ngit  mv  [old file name]  [new file name]\ngit commit -m 'some information'\n```\n\n\n\n## 通过`git log`查看版本演变历史\n\n```bash\ngit log --all 查看所有分支的历史\ngit log --all --graph 查看图形化的 log 地址\ngit log --oneline 查看单行的简洁历史。\ngit log --oneline -n4 查看最近的4条简洁历史。\ngit log --oneline --all -n4 --graph 查看所有分支最近4条单行的图形化历史。\ngit help --web log 跳转到git log 的帮助文档网页\n```\n\n```bash\ngit branch -v 查看本地有多少分支\n```\n\n\n\n## 通过图形界面工具来查看版本历史\n\n```bash\ngitk\n```\n\n\n\n## 探密`.git`目录\n\n查看`.git`文件夹下的内容：    \n```bash\nls .git/ -al\n```\n如下:   \n```shell\ndrwxr-xr-x 1 Andy 197609   0 12月 17 22:38 ./\ndrwxr-xr-x 1 Andy 197609   0 12月 17 21:50 ../\n-rw-r--r-- 1 Andy 197609   7 12月 17 22:38 COMMIT_EDITMSG\n-rw-r--r-- 1 Andy 197609 301 12月 12 22:55 config\n-rw-r--r-- 1 Andy 197609  73 12月 12 22:55 description\n-rw-r--r-- 1 Andy 197609  96 12月 19 00:00 FETCH_HEAD\n-rw-r--r-- 1 Andy 197609  23 12月 12 22:55 HEAD\ndrwxr-xr-x 1 Andy 197609   0 12月 12 22:55 hooks/\n-rw-r--r-- 1 Andy 197609 249 12月 17 22:38 index\ndrwxr-xr-x 1 Andy 197609   0 12月 12 22:55 info/\ndrwxr-xr-x 1 Andy 197609   0 12月 12 22:55 logs/\ndrwxr-xr-x 1 Andy 197609   0 12月 17 22:38 objects/\n-rw-r--r-- 1 Andy 197609 114 12月 12 22:55 packed-refs\ndrwxr-xr-x 1 Andy 197609   0 12月 12 22:55 refs/\n```\n\n```bash\ncat命令主要用来查看文件内容，创建文件，文件合并，追加文件内容等功能。\ncat HEAD 查看HEAD文件的内容\ngit cat-file 命令 显示版本库对象的内容、类型及大小信息。\ngit cat-file -t b44dd71d62a5a8ed3 显示版本库对象的类型\ngit cat-file -s b44dd71d62a5a8ed3 显示版本库对象的大小\ngit cat-file -p b44dd71d62a5a8ed3 显示版本库对象的内容\n```\n\n`.git`里几个常用的如下：    \n```bash\nHEAD：指向当前的工作路径\nconfig：存放本地仓库（local）相关的配置信息。\nrefs/heads: 存放分支\nrefs/heads/master/: 指向master分支最后一次commit\nrefs/tags: 存放tag，又叫里程牌 （当这次commit是具有里程碑意义的 比如项目1.0的时候 就可以打tag）\nobjects：核心文件，存储文件\n```\n.git/objects/ 存放所有的 git 对象，对象哈希值前 2 位作为文件夹名称，后 38 位作为对象文件名, 可通过 git cat-file -p 命令，拼接文件夹名称+文件名查看。    \n\n## `commit`、`tree`和`blob`三个对象之间的关系\n![tree](./images/img1.jpg)     \n\n```bash\ncommit: 提交时的镜像\ntree: 文件夹\nblob: 文件\n```\n\n**【同学问题】** 每次commit，git 都会将当前项目的所有文件夹及文件快照保存到objects目录，如果项目文件比较大，不断迭代，commit无数次后，objects目录中文件大小是不是会变得无限大？    \n**【老师解答】** Git对于内容相同的文件只会存一个blob，不同的commit的区别是commit、tree和有差异的blob，多数未变更的文件对应的blob都是相同的，这么设计对于版本管理系统来说可以省很多存储空间。其次，Git还有增量存储的机制，我估计是对于差异很小的blob设计的吧。    \n\n\n\n## `分离头指针`情况下的注意事项\n\ndetached HEAD   \n\n\n\n## 进一步理解`HEAD`和`branch`\n\n```bash\ngit checkout -b new_branch [具体分支 或 commit] 创建新分支并切换到新分支\ngit diff HEAD HEAD~1 比较最近两次提交\ngit diff HEAD HEAD~2 比较最近和倒数第三次提交\ngit diff HEAD HEAD^  比较最近两次提交\ngit diff HEAD HEAD^^ 比较最近和倒数第三次提交\n```\n\n\n\n## 怎么删除不需要的分支？\n\n查看分支：   \n```bash\ngit branch -av\n```\n删除分支命令：    \n```bash\ngit branch -d [branch name]  删除\ngit branch -D [branch name]  强制删除\n```\n\n\n\n## 怎么修改最新 commit 的 message？\n\n```bash\ngit commit --amend  对最近一次的commit信息进行修改\n```\n\n\n\n## 怎么修改老旧 commit 的 message？\n\n```bash\ngit rebase -i [要更改的commit的上一级commit]\n```\n接下来就是一个交互过程...    \n这期间会产生一个detached HEAD，然后将改好的commit指向该detached HEAD，如下图所示：    \n![rebase](./images/img2.jpg)    \n\n**git rebase工作的过程中，就是用了分离头指针。rebase意味着基于新base的commit来变更部分commits。它处理的时候，把HEAD指向base的commit，此时如果该commit没有对应branch，就处于分离头指针的状态，然后重新一个一个生成新的commit，当rebase创建完最后一个commit后，结束分离头状态，Git让变完基的分支名指向HEAD。**    \n\n\n\n## 怎样把连续的多个commit整理成1个？\n\n```bash\ngit rebase -i [要更改的commit的上一级commit]\n```\n```bash\n$ git log --graph\n* commit 7d3386842a2168ae630b65f687364243139c893c (HEAD -> master, origin/master, origin/HEAD)\n| Author: aimuch <liuvay@gmail.com>\n| Date:   Thu Dec 20 23:34:18 2018 +0800\n|\n|     update\n|\n* commit 9eb3188bbc63cae1bfed5f9dfc1593019e360a6a\n| Author: aimuch <liuvay@gmail.com>\n| Date:   Wed Dec 19 20:30:14 2018 +0800\n|\n|     update\n|\n* commit bbe6d53e2b477f2d2aa402af7f315ecdfc63459e\n| Author: aimuch <liuvay@gmail.com>\n| Date:   Wed Dec 19 20:12:29 2018 +0800\n|\n|     update\n|\n* commit 7735d66ded7f98adeca93d96fb7be12ffb67c76a\n| Author: aimuch <liuvay@gmail.com>\n| Date:   Wed Dec 19 00:27:00 2018 +0800\n|\n|     update\n|\n* commit d9f9d115fab425b5654f8ccfec6a996aef35b76b\n| Author: aimuch <liuvay@gmail.com>\n| Date:   Wed Dec 19 00:23:36 2018 +0800\n|\n|     update\n\n```\n```bash\npick   7735d66 update #合并到该commit上\nsquash bbe6d53 update\nsquash 9eb3188 update\nsquash 7d33868 update\n# Rebase d9f9d11..7d33868 onto d9f9d11 (4 commands)\n#\n# Commands:\n# p, pick <commit> = use commit\n# r, reword <commit> = use commit, but edit the commit message\n# e, edit <commit> = use commit, but stop for amending\n# s, squash <commit> = use commit, but meld into previous commit\n# f, fixup <commit> = like \"squash\", but discard this commit's log message\n# x, exec <command> = run command (the rest of the line) using shell\n# b, break = stop here (continue rebase later with 'git rebase --continue')\n# d, drop <commit> = remove commit\n# l, label <label> = label current HEAD with a name\n# t, reset <label> = reset HEAD to a label\n# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n# .       create a merge commit using the original merge commit's\n# .       message (or the oneline, if no original merge commit was\n# .       specified). Use -c <commit> to reword the commit message.\n```\n\n```bash\n# This is a combination of 4 commits.\n# This is the 1st commit message:\n\nupdate\n\n# This is the commit message #2:\n\nupdate\n\n# This is the commit message #3:\n\nupdate\n\n# This is the commit message #4:\n\nupdate\n\n```\n## git 修改.gitignore后生效\n```bash\ngit rm -r --cached .    #清除缓存\ngit add .               #重新trace file\ngit commit -m \"update .gitignore\" #提交和注释\ngit push origin master  #可选，如果需要同步到remote上的话\n```\n\n\n\n## 怎么比较暂存区和HEAD所含文件的差异？\n\n```bash\ngit diff --cached\n```\n或者\n```bash\ngit diff --staged\n```\n\n\n\n## 怎么比较工作区和暂存区所含文件的差异？\n\n```bash\ngit diff\n```\n```bash\ngit diff -- [filename/pathname] #比较具体的文件或者路径\n```\n\n\n\n## 如何让暂存区恢复成和HEAD的一样？\n\n```bash\ngit reset HEAD\n```\n```bash\ngit reset 有三个参数\n--soft 这个只是把 HEAD 指向的 commit 恢复到你指定的 commit，暂存区 工作区不变\n--hard 这个是 把 HEAD， 暂存区， 工作区 都修改为 你指定的 commit 的时候的文件状态\n--mixed 这个是不加时候的默认参数，把 HEAD，暂存区 修改为 你指定的 commit 的时候的文件状态，工作区保持不变\n```\n\n\n\n##  如何让工作区的文件恢复为和暂存区一样？\n\n```bash\ngit checkout -- <file>...\n```\n**恢复工作区用checkout，恢复暂存区用reset。**   \n\n\n\n## 怎样取消暂存区部分文件的更改？\n\n```bash\ngit reset HEAD -- <file>...\n```\n\n\n\n## 看看不同提交的指定文件的差异\n\n```bash\ngit diff commit-id1 commit-id2 -- <file>...\n```\n\n\n\n## 正确删除文件的方法\n\n```bash\ngit rm <file>\n```\n\n\n\n## 开发中临时加塞了紧急任务怎么处理？\n\n```bash\ngit stash list #查看stash中存放的信息\ngit stash #将当前工作区内容存放到\"堆栈\"中\n```\n```bash\ngit stash apply #把\"堆栈\"里面的内容弹出到工作区中，同时\"堆栈\"中信息还在\n```\n```bash\ngit stash pop #把\"堆栈\"里面的内容弹出到工作区中，同时丢弃\"堆栈\"中最新的信息\n```\n\n\n\n## 如何指定不需要Git管理的文件？\n\n```bash\n.gitignore\n```\n**【同学提问】** 如果提交commit后，想再忽略一些已经提交的文件，怎么处理。    \n**【老师回答】** The problem is that .gitignore ignores just files that weren't tracked before (by git add). Run git reset name_of_file to unstage the file and keep it. In case you want to also remove given file from the repository (after pushing), use git rm --cached name_of_file.    \n把想忽略的文件添加到 .gitignore ；然后通过 git rm -- cached name_of_file 的方式删除掉git仓库里面无需跟踪的文件。    \n\n\n\n##  添加远程仓库\n```bash\ngit remote add [shortname] [url]\n```\n\n\n\n## 配置公私钥\n\n1、 检查是否已存在相应的`ssh key`:    \n打开终端, 输入:   \n\n```bash\nls -al ~/.ssh\n```\n核对列出来的ssh key是否有已存在的，假如你没有看到列出的公私钥对，或是不想再用之前的公私钥对，你可以选择下面的步骤生成新的公私钥对.    \n\n2、生成新的`ssh key`,并添加至`ssh-agent`:    \n打开终端, 使用`ssh key`生成命令：\n```bash\nssh-keygen -t rsa -b 4096 -C \"your_email@example.com\"\n```\n**注意** ：后面的邮箱对应相应账号的邮箱，假如是github的账号，且注册账号的邮箱为xxxx@qq.com，则命令行为：`ssh-keygen -t rsa -b 4096 -C \"xxxx@qq.com`。    \n\n3、接下来会提示你保存的`ssh key`的名称以及路径。默认路径是(`/Users/you/.ssh/id_rsa`) (`you`为用户个人目录)。这一步很重要，如果你使用默认的，且下一个账号也是使用默认的路径和文件名，那么之前的`ssh key`就会被后来生成的`ssh key`重写，从而导致之前的账号不可用。因此，正确的做法是给它命名，最后以应用名进行命名，因为更容易区分。以下是我个人配的：\n```bash\n/Users/andy/.ssh/github_rsa \n```\n\n4、接下来会提示设置ssh安全密码。这一步可以使用默认的（即不设置密码），直接按回车即可。倘若想了解更多关于ssh key密码设置的细节，可访问： “Working with SSH key passphrases” 。    \n\n5、 `ssh key`生成后，接下来需要为`ssh key`添加代理，这是为了让请求自动对应相应的账号。网上很多文章写到需要另外配置`config`文件，经本人亲测，其实是不需要的，在生成了`ssh key`后，通过为生成的`ssh key`添加代理即可，为`ssh key`添加代理命令：`ssh-add ~/.ssh/xxx_rsa,xxx_rsa`是你生成的`ssh key`的私钥名。    \n\n6、连接测试    \n接下来我们测试是否配置成功，打开终端，输入:    \n```bash\nssh -T git@github.com\n```\n\n\n\n## 怎么快速淘到感兴趣的开源项目？\n\n**UI界面高级搜索**： https://github.com/search/advanced     \n\n**命令高级搜索**：   \n```bash\ngit 最好 学习 资料 in:readme stars:>1000 language:c\n```\n上述命令的意思是搜索reademe中包含`git、最好、学习、资料`”且`star大于1000`的，用`C语言编写`的仓库。    "
  },
  {
    "path": "notes/Git工作流.md",
    "content": "<!-- TOC -->\n\n- [前言](#前言)\n- [一、Git 工作流简介](#一git-工作流简介)\n    - [集中式工作流](#集中式工作流)\n    - [功能分支工作流](#功能分支工作流)\n    - [GitFlow 工作流](#gitflow-工作流)\n    - [Forking 工作流](#forking-工作流)\n    - [Pull Requests](#pull-requests)\n- [二、工作流](#二工作流)\n    - [1. 集中式工作流](#1-集中式工作流)\n        - [工作方式](#工作方式)\n        - [解决冲突](#解决冲突)\n        - [示例](#示例)\n            - [有人先初始化好中央仓库](#有人先初始化好中央仓库)\n            - [所有人克隆中央仓库](#所有人克隆中央仓库)\n            - [小明开发功能](#小明开发功能)\n            - [小红开发功能](#小红开发功能)\n            - [小明发布功能](#小明发布功能)\n            - [小红试着发布功能](#小红试着发布功能)\n            - [小红在小明的提交之上 rebase](#小红在小明的提交之上-rebase)\n            - [小红解决合并冲突](#小红解决合并冲突)\n            - [小红成功发布功能](#小红成功发布功能)\n        - [总结](#总结)\n    - [2. 功能分支工作流](#2-功能分支工作流)\n        - [工作方式](#工作方式-1)\n        - [Pull Requests](#pull-requests-1)\n        - [示例](#示例-1)\n            - [小红开始开发一个新功能](#小红开始开发一个新功能)\n            - [小红要去吃个午饭](#小红要去吃个午饭)\n            - [小红完成功能开发](#小红完成功能开发)\n            - [小黑收到 Pull Request](#小黑收到-pull-request)\n            - [小红再做修改](#小红再做修改)\n            - [小红发布她的功能](#小红发布她的功能)\n            - [与此同时，小明在做和小红一样的事](#与此同时小明在做和小红一样的事)\n        - [总结](#总结-1)\n    - [3. GitFlow工作流](#3-gitflow工作流)\n        - [工作方式](#工作方式-2)\n            - [历史分支](#历史分支)\n            - [功能分支](#功能分支)\n            - [发布分支](#发布分支)\n            - [维护分支](#维护分支)\n        - [示例](#示例-2)\n            - [创建开发分支](#创建开发分支)\n            - [小红和小明开始开发新功能](#小红和小明开始开发新功能)\n            - [小红完成功能开发](#小红完成功能开发-1)\n            - [小红开始准备发布](#小红开始准备发布)\n            - [小红完成发布](#小红完成发布)\n            - [最终用户发现 Bug](#最终用户发现-bug)\n        - [总结](#总结-2)\n    - [4. Forking 工作流](#4-forking-工作流)\n        - [工作方式](#工作方式-3)\n            - [正式仓库](#正式仓库)\n            - [Forking 工作流的分支使用方式](#forking-工作流的分支使用方式)\n        - [示例](#示例-3)\n            - [项目维护者初始化正式仓库](#项目维护者初始化正式仓库)\n            - [开发者 fork 正式仓库](#开发者-fork-正式仓库)\n            - [开发者克隆自己 fork 出来的仓库](#开发者克隆自己-fork-出来的仓库)\n            - [开发者开发自己的功能](#开发者开发自己的功能)\n            - [开发者发布自己的功能](#开发者发布自己的功能)\n            - [项目维护者集成开发者的功能](#项目维护者集成开发者的功能)\n            - [开发者和正式仓库做同步](#开发者和正式仓库做同步)\n        - [总结](#总结-3)\n    - [5. Pull Requests](#5-pull-requests)\n        - [解析 Pull Request](#解析-pull-request)\n        - [工作方式](#工作方式-4)\n            - [在功能分支工作流中使用 Pull Request](#在功能分支工作流中使用-pull-request)\n            - [在 GitFlow 工作流中使用 Pull Request](#在-gitflow-工作流中使用-pull-request)\n            - [在 Forking 工作流中使用 Pull Request](#在-forking-工作流中使用-pull-request)\n        - [示例](#示例-4)\n            - [小红 fork 正式项目](#小红-fork-正式项目)\n            - [小红克隆她的 Bitbucket 仓库](#小红克隆她的-bitbucket-仓库)\n            - [小红开发新功能](#小红开发新功能)\n        - [编辑代码](#编辑代码)\n            - [小红 push 功能到她的 Bitbucket 仓库中](#小红-push-功能到她的-bitbucket-仓库中)\n            - [小红发起 Pull Request](#小红发起-pull-request)\n            - [小明 review Pull Request](#小明-review-pull-request)\n            - [小红补加提交](#小红补加提交)\n            - [小明接受 Pull Request](#小明接受-pull-request)\n        - [总结](#总结-4)\n\n<!-- /TOC -->\n\n# 前言\n\n　　本文将介绍 Git 工作流相关知识，包含集中式工作流，功能分支工作流， GitFlow 工作流，Forking 工作流，Pull Requests 等等基本概念。\n\n# 一、Git 工作流简介\n\n工作流有各式各样的用法，但也正因此使得在实际工作中如何上手使用增加了难度。这篇指南通过总览公司团队中最常用的几种 Git 工作流让大家可以上手使用。\n\n在阅读的过程中请记住，本文中的几种工作流是作为方案指导而不是条例规定。在展示了各种工作流可能的用法后，你可以从不同的工作流中挑选或揉合出一个满足你自己需求的工作流。\n\n## 集中式工作流\n\n如果你的开发团队成员已经很熟悉 Subversion，集中式工作流让你无需去适应一个全新流程就可以体验 Git 带来的收益。这个工作流也可以作为向更 Git 风格工作流迁移的友好过渡。\n\n<div align=\"center\"> <img src=\"assets/git-workflow-svn.png\" height=\"200px\"/></div>\n\n\n\n## 功能分支工作流\n\n功能分支工作流以集中式工作流为基础，不同的是为各个新功能分配一个专门的分支来开发。这样可以在把新功能集成到正式项目前，用 `Pull Requests` 的方式讨论变更。\n\n<div align=\"center\"> <img src=\"assets/git-workflow-feature-branch-1.png\" width=\"400px\" /></div>\n\n\n\n## GitFlow 工作流\n\nGitFlow 工作流通过为功能开发、发布准备和维护分配独立的分支，让发布迭代过程更流畅。严格的分支模型也为大型项目提供了一些非常必要的结构。\n\n<div align=\"center\"> <img src=\"assets/git-workflows-gitflow.png\" /></div>\n\n## Forking 工作流\n\nForking 工作流是分布式工作流，充分利用了 Git 在分支和克隆上的优势。可以安全可靠地管理大团队的开发者（developer），并能接受不信任贡献者（contributor）的提交。x\n\n\n\n\n\n<div align=\"center\"> <img src=\"assets/git-workflow-forking.png\" /></div>\n\n## Pull Requests\n\nPull requests 让开发者更方便地进行协作的功能，提供了友好的 Web 界面可以在提议的修改合并到正式项目之前对修改进行讨论。\n\n<div align=\"center\"> <img src=\"assets/pull-request.png\" /></div>\n\n# 二、工作流\n\n## 1. 集中式工作流\n\n转到分布式版本控制系统看起来像个令人生畏的任务，但不改变已用的工作流你也可以用上 Git 带来的收益。团队可以用和 Subversion 完全不变的方式来开发项目。\n\n但使用 Git 加强开发的工作流，Git 比 SVN 有几个优势。首先，每个开发可以有属于自己的整个工程的本地拷贝。隔离的环境让各个开发者的工作和项目的其他部分（修改）独立开来 —— 即自由地提交到自己的本地仓库，先完全忽略上游的开发，直到方便的时候再把修改反馈上去。\n\n其次，Git 提供了强壮的分支和合并模型。不像 SVN，Git 的分支设计成可以做为一种用来在仓库之间集成代码和分享修改的『失败安全』的机制。\n\n### 工作方式\n\n像 Subversion 一样，集中式工作流以中央仓库作为项目所有修改的单点实体。相比 SVN 缺省的开发分支 trunk，Git 叫做 master，所有修改提交到这个分支上。该工作流只用到 master 这一个分支。\n\n开发者开始先克隆中央仓库。在自己的项目拷贝中，像 SVN 一样的编辑文件和提交修改；但修改是存在本地的，和中央仓库是完全隔离的。开发者可以把和上游的同步延后到一个方便时间点。\n\n要发布修改到正式项目中，开发者要把本地 master 分支的修改『推（`push`）』到中央仓库中。这相当于 svn commit 操作，但 `push` 操作会把所有还不在中央仓库的本地提交都推上去。\n\n<div align=\"center\"> <img src=\"assets/git-workflow-svn-push-local.png\" width=\"\"/></div>\n\n### 解决冲突\n\n中央仓库代表了正式项目，所以提交历史应该被尊重且是稳定不变的。如果开发者本地的提交历史和中央仓库有分歧，Git 会拒绝 `push` 提交否则会覆盖已经在中央库的正式提交。\n\n<div align=\"center\"> <img src=\"assets/git-workflow-svn-managingconflicts.png\" width=\"\"/></div>\n\n在开发者提交自己功能修改到中央库前，需要先 `fetch` 在中央库的新增提交，`rebase` 自己提交到中央库提交历史之上。这样做的意思是在说，『我要把自己的修改加到别人已经完成的修改上。』最终的结果是一个完美的线性历史，就像以前的 `SVN` 的工作流中一样。\n\n如果本地修改和上游提交有冲突，Git 会暂停 `rebase` 过程，给你手动解决冲突的机会。Git 解决合并冲突，用和生成提交一样的 `git status` 和 `git add` 命令，很一致方便。还有一点，如果解决冲突时遇到麻烦，Git 可以很简单中止整个 `rebase` 操作，重来一次（或者让别人来帮助解决）。\n\n### 示例\n\n让我们一起逐步分解来看看一个常见的小团队如何用这个工作流来协作的。有两个开发者小明和小红，看他们是如何开发自己的功能并提交到中央仓库上的。\n\n#### 有人先初始化好中央仓库\n\n<div align=\"center\"> <img src=\"assets/git-workflow-svn-initialize.png\" width=\"\"/></div>\n\n第一步，有人在服务器上创建好中央仓库。如果是新项目，你可以初始化一个空仓库；否则你要导入已有的 Git 或 SVN 仓库。\n\n中央仓库应该是个裸仓库（bare repository），即没有工作目录（working directory）的仓库。\n\n#### 所有人克隆中央仓库\n\n<div align=\"center\"> <img src=\"assets/git-workflow-svn-clone.png\" width=\"\"/></div>\n\n下一步，各个开发者创建整个项目的本地拷贝。通过 `git clone` 命令完成：\n\n```\ngit clone https://github.com/path/to/repo.git\n```\n\n基于你后续会持续和克隆的仓库做交互的假设，克隆仓库时 Git 会自动添加远程别名 origin 指回『父』仓库。\n\n#### 小明开发功能\n\n<div align=\"center\"> <img src=\"assets/git-workflow-svn-1.png\" width=\"\"/></div>\n\n在小明的本地仓库中，他使用标准的 Git 过程开发功能：编辑、暂存（Stage）和提交。如果你不熟悉暂存区（Staging Area），这里说明一下：暂存区的用来准备一个提交，但可以不用把工作目录中所有的修改内容都包含进来。这样你可以创建一个高度聚焦的提交，尽管你本地修改很多内容。\n\n```\ngit status # 查看本地仓库的修改状态\ngit add # 暂存文件\ngit commit # 提交文件\n```\n\n请记住，因为这些命令生成的是本地提交，小明可以按自己需求反复操作多次，而不用担心中央仓库上有了什么操作。对需要多个更简单更原子分块的大功能，这个做法是很有用的。\n\n#### 小红开发功能\n\n<div align=\"center\"> <img src=\"assets/git-workflow-svn-2.png\" width=\"\"/></div>\n\n与此同时，小红在自己的本地仓库中用相同的编辑、暂存和提交过程开发功能。和小明一样，她也不关心中央仓库有没有新提交；当然更不关心小明在他的本地仓库中的操作，因为所有本地仓库都是私有的。\n\n#### 小明发布功能\n\n<div align=\"center\"> <img src=\"assets/git-workflow-svn-3.png\" width=\"\"/></div>\n\n一旦小明完成了他的功能开发，会发布他的本地提交到中央仓库中，这样其它团队成员可以看到他的修改。他可以用下面的 `git push` 命令：\n\n```\ngit push origin master\n```\n\n注意，origin 是在小明克隆仓库时 Git 创建的远程中央仓库别名。master 参数告诉 Git 推送的分支。由于中央仓库自从小明克隆以来还没有被更新过，所以 push 操作不会有冲突，成功完成。\n\n#### 小红试着发布功能\n\n<div align=\"center\"> <img src=\"assets/git-workflow-svn-4.png\" width=\"\"/></div>\n\n一起来看看在小明发布修改后，小红 push 修改会怎么样？她使用完全一样的 push 命令：\n\n```\ngit push origin master\n```\n\n但她的本地历史已经和中央仓库有分岐了，Git 拒绝操作并给出下面很长的出错消息：\n\n```\nerror: failed to push some refs to '/path/to/repo.git'\nhint: Updates were rejected because the tip of your current branch is behind\nhint: its remote counterpart. Merge the remote changes (e.g. 'git pull')\nhint: before pushing again.\nhint: See the 'Note about fast-forwards' in 'git push --help' for details.\n```\n\n这避免了小红覆写正式的提交。她要先 pull 小明的更新到她的本地仓库合并上她的本地修改后，再重试。\n\n#### 小红在小明的提交之上 rebase\n\n<div align=\"center\"> <img src=\"assets/git-workflow-svn-5.png\" width=\"\"/></div>\n\n小红用 `git pull` 合并上游的修改到自己的仓库中。这条命令类似 `svn update` ——拉取所有上游提交命令到小红的本地仓库，并尝试和她的本地修改合并：\n\n```\ngit pull --rebase origin master\n```\n\n`--rebase` 选项告诉 Git 把小红的提交移到同步了中央仓库修改后的 master 分支的顶部，如下图所示：\n\n<div align=\"center\"> <img src=\"assets/git-workflow-svn-6.png\" width=\"\"/></div>\n\n如果你忘加了这个选项，pull 操作仍然可以完成，但每次 pull 操作要同步中央仓库中别人修改时，提交历史会以一个多余的『合并提交』结尾。对于集中式工作流，最好是使用 rebase 而不是生成一个合并提交。\n\n#### 小红解决合并冲突\n\n<div align=\"center\"> <img src=\"assets/git-workflow-svn-7.png\" width=\"\"/></div>\n\nrebase 操作过程是把本地提交一次一个地迁移到更新了的中央仓库 master 分支之上。这意味着可能要解决在迁移某个提交时出现的合并冲突，而不是解决包含了所有提交的大型合并时所出现的冲突。这样的方式让你尽可能保持每个提交的聚焦和项目历史的整洁。反过来，简化了哪里引入 Bug 的分析，如果有必要，回滚修改也可以做到对项目影响最小。\n\n如果小红和小明的功能是相关的，不大可能在 rebase 过程中有冲突。如果有，Git 在合并有冲突的提交处暂停 rebase 过程，输出下面的信息并带上相关的指令：\n\n```\nCONFLICT (content): Merge conflict in\n```\n\n<div align=\"center\"> <img src=\"assets/git-workflow-svn-8.png\" width=\"\"/></div>\n\nGit 很赞的一点是，任何人可以解决他自己的冲突。在这个例子中，小红可以简单的运行 `git status` 命令来查看哪里有问题。冲突文件列在 Unmerged paths（未合并路径）一节中：\n\n```\n# Unmerged paths:\n# (use \"git reset HEAD <some-file>...\" to unstage)\n# (use \"git add/rm <some-file>...\" as appropriate to mark resolution)\n#\n# both modified: <some-file>\n```\n\n接着小红编辑这些文件。修改完成后，用老套路暂存这些文件，并让 `git rebase` 完成剩下的事：\n\n```\ngit add\ngit rebase --continue\n```\n\n要做的就这些了。Git 会继续一个一个地合并后面的提交，如其它的提交有冲突就重复这个过程。\n\n如果你碰到了冲突，但发现搞不定，不要惊慌。只要执行下面这条命令，就可以回到你执行 `git pull --rebase` 命令前的样子：\n\n```\ngit rebase --abort\n```\n\n#### 小红成功发布功能\n\n<div align=\"center\"> <img src=\"assets/git-workflow-svn-9.png\" width=\"\"/></div>\n\n小红完成和中央仓库的同步后，就能成功发布她的修改了：\n\n```\ngit push origin master\n```\n\n### 总结\n\n如你所见，仅使用几个 Git 命令我们就可以模拟出传统 Subversion 开发环境。对于要从 SVN 迁移过来的团队来说这太好了，但没有发挥出 Git 分布式本质的优势。\n\n如果你的团队适应了集中式工作流，但想要更流畅的协作效果，绝对值得探索一下功能分支工作流的收益。通过为一个功能分配一个专门的分支，能够做到一个新增功能集成到正式项目之前对新功能进行深入讨论。\n\n\n\n## 2. 功能分支工作流\n\n<div align=\"center\"> <img src=\"assets/git-workflow-feature-branch-1-1542442210570.png\" width=\"\"/></div>\n\n一旦你玩转了集中式工作流，在开发过程中可以很简单地加上功能分支，用来鼓励开发者之间协作和简化交流。\n\n功能分支工作流背后的核心思路是所有的功能开发应该在一个专门的分支，而不是在 master 分支上。这个隔离可以方便多个开发者在各自的功能上开发而不会弄乱主干代码。另外，也保证了 master 分支的代码一定不会是有问题的，极大有利于集成环境。\n\n功能开发隔离也让 pull requests 工作流成功可能，pull requests 工作流能为每个分支发起一个讨论，在分支合入正式项目之前，给其它开发者有表示赞同的机会。另外，如果你在功能开发中有问题卡住了，可以开一个 pull requests 来向同学们征求建议。这些做法的重点就是，pull requests 让团队成员之间互相评论工作变成非常方便！\n\n### 工作方式\n\n功能分支工作流仍然用中央仓库，并且 master 分支还是代表了正式项目的历史。但不是直接提交本地历史到各自的本地 master 分支，开发者每次在开始新功能前先创建一个新分支。功能分支应该有个有描述性的名字，比如 animated-menu-items 或 issue-#1061，这样可以让分支有个清楚且高聚焦的用途。\n\n在 master 分支和功能分支之间，Git 是没有技术上的区别，所以开发者可以用和集中式工作流中完全一样的方式编辑、暂存和提交修改到功能分支上。\n\n另外，功能分支也可以（且应该）push 到中央仓库中。这样不修改正式代码就可以和其它开发者分享提交的功能。由于 master 仅有的一个『特殊』分支，在中央仓库上存多个功能分支不会有任何问题。当然，这样做也可以很方便地备份各自的本地提交。\n\n### Pull Requests\n\n功能分支除了可以隔离功能的开发，也使得通过 Pull Requests 讨论变更成为可能。一旦某个开发完成一个功能，不是立即合并到 master，而是 push 到中央仓库的功能分支上并发起一个 Pull Request 请求去合并修改到 master。在修改成为主干代码前，这让其它的开发者有机会先去 Review 变更。\n\nCode Review 是 Pull Requests 的一个重要的收益，但 Pull Requests 目的是讨论代码一个通用方式。你可以把 Pull Requests 作为专门给某个分支的讨论。这意味着可以在更早的开发过程中就可以进行 Code Review。比如，一个开发者开发功能需要帮助时，要做的就是发起一个 Pull Request，相关的人就会自动收到通知，在相关的提交旁边能看到需要帮助解决的问题。\n\n一旦 Pull Request 被接受了，发布功能要做的就和集中式工作流就很像了。首先，确定本地的 master 分支和上游的 master 分支是同步的。然后合并功能分支到本地 master 分支并 push 已经更新的本地 master 分支到中央仓库。\n\n### 示例\n\n下面的示例演示了如何把 Pull Requests 作为 Code Review 的方式，但注意 Pull Requests 可以用于很多其它的目的。\n\n#### 小红开始开发一个新功能\n\n<div align=\"center\"> <img src=\"assets/git-workflow-feature-branch-2.png\" width=\"\"/></div>\n\n在开始开发功能前，小红需要一个独立的分支。使用下面的命令新建一个分支：\n\n```\ngit checkout -b marys-feature master\n```\n\n这个命令检出一个基于 master 名为 marys-feature 的分支，Git 的 `-b` 选项表示如果分支还不存在则新建分支。这个新分支上，小红按老套路编辑、暂存和提交修改，按需要提交以实现功能：\n\n```\ngit status\ngit add\ngit commit\n```\n\n#### 小红要去吃个午饭\n\n<div align=\"center\"> <img src=\"assets/git-workflow-feature-branch-3.png\" width=\"\"/></div>\n\n早上小红为新功能添加一些提交。去吃午饭前，push 功能分支到中央仓库是很好的做法，这样可以方便地备份，如果和其它开发协作，也让他们可以看到小红的提交。\n\n```\ngit push -u origin marys-feature\n```\n\n这条命令 `push marys-feature` 分支到中央仓库（origin），`-u` 选项设置本地分支去跟踪远程对应的分支。设置好跟踪的分支后，小红就可以使用 `git push` 命令省去指定推送分支的参数。\n\n#### 小红完成功能开发\n\n<div align=\"center\"> <img src=\"assets/git-workflow-feature-branch-4.png\" width=\"\"/></div>\n\n小红吃完午饭回来，完成整个功能的开发。在合并到 master 之前，她发起一个 Pull Request 让团队的其它人知道功能已经完成。但首先，她要确认中央仓库中已经有她最近的提交：\n\n```\ngit push\n```\n\n然后，在她的 Git GUI 客户端中发起 Pull Request，请求合并 marys-feature 到 master，团队成员会自动收到通知。Pull Request 很酷的是可以在相关的提交旁边显示评注，所以你可以很对某个变更集提问。\n\n#### 小黑收到 Pull Request\n\n<div align=\"center\"> <img src=\"assets/git-workflow-feature-branch-5.png\" width=\"\"/></div>\n\n小黑收到了 Pull Request 后会查看 marys-feature 的修改。决定在合并到正式项目前是否要做些修改，且通过 Pull Request 和小红来回地讨论。\n\n#### 小红再做修改\n\n<div align=\"center\"> <img src=\"assets/git-workflow-feature-branch-6.png\" width=\"\"/></div>\n\n要再做修改，小红用和功能第一个迭代完全一样的过程。编辑、暂存、提交并push更新到中央仓库。小红这些活动都会显示在 Pull Request 上，小黑可以断续做评注。\n\n如果小黑有需要，也可以把 marys-feature 分支拉到本地，自己来修改，他加的提交也会一样显示在 Pull Request 上。\n\n#### 小红发布她的功能\n\n<div align=\"center\"> <img src=\"assets/git-workflow-feature-branch-7.png\" width=\"\"/></div>\n\n一旦小黑可以的接受 Pull Request，就可以合并功能到稳定项目代码中（可以由小黑或是小红来做这个操作）：\n\n```\ngit checkout master\ngit pull\ngit pull origin marys-feature\ngit push\n```\n\n无论谁来做合并，首先要检出 master 分支并确认是它是最新的。然后执行 `git pull origin marys-feature` 合并 marys-feature 分支到和已经和远程一致的本地 master 分支。你可以使用简单 `git merge marys-feature` 命令，但前面的命令可以保证总是最新的新功能分支。最后更新的 master 分支要重新 push 回到 origin。\n\n这个过程常常会生成一个合并提交。有些开发者喜欢有合并提交，因为它像一个新功能和原来代码基线的连通符。但如果你偏爱线性的提交历史，可以在执行合并时 rebase 新功能到 master 分支的顶部，这样生成一个快进（fast-forward）的合并。\n\n一些 GUI 客户端可以只要点一下『接受』按钮执行好上面的命令来自动化 Pull Request 接受过程。如果你的不能这样，至少在功能合并到 master 分支后能自动关闭 Pull Request。\n\n#### 与此同时，小明在做和小红一样的事\n\n当小红和小黑在 marys-feature 上工作并讨论她的 Pull Request 的时候，小明在自己的功能分支上做完全一样的事。\n\n通过隔离功能到独立的分支上，每个人都可以自主的工作，当然必要的时候在开发者之间分享变更还是比较繁琐的。\n\n### 总结\n\n到了这里，但愿你发现了功能分支可以很直接地在集中式工作流的仅有的 master 分支上完成多功能的开发。另外，功能分支还使用了 Pull Request，使得可以在你的版本控制 GUI 客户端中讨论某个提交。\n\n功能分支工作流是开发项目异常灵活的方式。问题是，有时候太灵活了。对于大型团队，常常需要给不同分支分配一个更具体的角色。GitFlow 工作流是管理功能开发、发布准备和维护的常用模式。\n\n\n\n## 3. GitFlow工作流\n\n<div align=\"center\"> <img src=\"assets/git-workflows-gitflow-1542442241823.png\" width=\"\"/></div>\n\nGitFlow 工作流定义了一个围绕项目发布的严格分支模型。虽然比功能分支工作流复杂几分，但提供了用于一个健壮的用于管理大型项目的框架。\n\nGitFlow 工作流没有用超出功能分支工作流的概念和命令，而是为不同的分支分配一个很明确的角色，并定义分支之间如何和什么时候进行交互。除了使用功能分支，在做准备、维护和记录发布也使用各自的分支。当然你可以用上功能分支工作流所有的好处：Pull Requests、隔离实验性开发和更高效的协作。\n\n### 工作方式\n\nGitFlow 工作流仍然用中央仓库作为所有开发者的交互中心。和其它的工作流一样，开发者在本地工作并 push 分支到中央仓库中。\n\n#### 历史分支\n\n相对使用仅有的一个 master 分支，GitFlow 工作流使用2个分支来记录项目的历史。master 分支存储了正式发布的历史，而 develop 分支作为功能的集成分支。这样也方便 master 分支上的所有提交分配一个版本号。\n\n<div align=\"center\"> <img src=\"assets/git-workflow-release-cycle-1historical.png\" width=\"\"/></div>\n\n剩下要说明的问题围绕着这2个分支的区别展开。\n\n#### 功能分支\n\n每个新功能位于一个自己的分支，这样可以 push 到中央仓库以备份和协作。但功能分支不是从 master 分支上拉出新分支，而是使用 develop 分支作为父分支。当新功能完成时，合并回 develop 分支。新功能提交应该从不直接与 master 分支交互。\n\n<div align=\"center\"> <img src=\"assets/git-workflow-release-cycle-2feature.png\" width=\"\"/></div>\n\n注意，从各种含义和目的上来看，功能分支加上 develop 分支就是功能分支工作流的用法。但 GitFlow 工作流没有在这里止步。\n\n#### 发布分支\n\n<div align=\"center\"> <img src=\"assets/git-workflow-release-cycle-3release.png\" width=\"\"/></div>\n\n一旦 develop 分支上有了做一次发布（或者说快到了既定的发布日）的足够功能，就从 develop 分支上 fork 一个发布分支。新建的分支用于开始发布循环，所以从这个时间点开始之后新的功能不能再加到这个分支上 —— 这个分支只应该做 Bug 修复、文档生成和其它面向发布任务。一旦对外发布的工作都完成了，发布分支合并到 master 分支并分配一个版本号打好 Tag。另外，这些从新建发布分支以来的做的修改要合并回 develop 分支。\n\n使用一个用于发布准备的专门分支，使得一个团队可以在完善当前的发布版本的同时，另一个团队可以继续开发下个版本的功能。这也打造定义良好的开发阶段（比如，可以很轻松地说，『这周我们要做准备发布版本 4.0』，并且在仓库的目录结构中可以实际看到）。\n\n常用的分支约定：\n\n- 用于新建发布分支的分支: develop\n- 用于合并的分支: master\n- 分支命名: release- *或 release/*\n\n#### 维护分支\n\n<div align=\"center\"> <img src=\"assets/git-workflow-release-cycle-4maintenance.png\" width=\"\"/></div>\n\n维护分支或说是热修复（hotfix）分支用于生成快速给产品发布版本（production releases）打补丁，这是唯一可以直接从 master 分支 fork 出来的分支。修复完成，修改应该马上合并回 master 分支和 develop 分支（当前的发布分支），master 分支应该用新的版本号打好 Tag。\n\n为 Bug 修复使用专门分支，让团队可以处理掉问题而不用打断其它工作或是等待下一个发布循环。你可以把维护分支想成是一个直接在 master 分支上处理的临时发布。\n\n### 示例\n\n下面的示例演示本工作流如何用于管理单个发布循环。假设你已经创建了一个中央仓库。\n\n#### 创建开发分支\n\n<div align=\"center\"> <img src=\"assets/git-workflow-release-cycle-5createdev.png\" width=\"\"/></div>\n\n第一步为 master 分支配套一个 develop 分支。简单来做可以本地创建一个空的 develop 分支，push 到服务器上：\n\n```\ngit branch develop\ngit push -u origin develop\n```\n\n以后这个分支将会包含了项目的全部历史，而 master 分支将只包含了部分历史。其它开发者这时应该克隆中央仓库，建好 develop 分支的跟踪分支：\n\n```\ngit clone ssh://user@host/path/to/repo.git\ngit checkout -b develop origin/develop\n```\n\n现在每个开发都有了这些历史分支的本地拷贝。\n\n#### 小红和小明开始开发新功能\n\n<div align=\"center\"> <img src=\"assets/git-workflow-release-cycle-6maryjohnbeginnew.png\" width=\"\"/></div>\n\n这个示例中，小红和小明开始各自的功能开发。他们需要为各自的功能创建相应的分支。新分支不是基于 master 分支，而是应该基于 develop 分支：\n\n```\ngit checkout -b some-feature develop\n```\n\n他们用老套路添加提交到各自功能分支上：编辑、暂存、提交：\n\n```\ngit status\ngit add\ngit commit\n```\n\n#### 小红完成功能开发\n\n<div align=\"center\"> <img src=\"assets/git-workflow-release-cycle-7maryfinishes.png\" width=\"\"/></div>\n\n添加了提交后，小红觉得她的功能 OK 了。如果团队使用 Pull Requests，这时候可以发起一个用于合并到 develop 分支。否则她可以直接合并到她本地的 develop 分支后 push 到中央仓库：\n\n```\ngit pull origin develop\ngit checkout develop\ngit merge some-feature\ngit push\ngit branch -d some-feature\n```\n\n第一条命令在合并功能前确保 develop 分支是最新的。注意，功能决不应该直接合并到 master 分支。冲突解决方法和集中式工作流一样。\n\n#### 小红开始准备发布\n\n<div align=\"center\"> <img src=\"assets/git-workflow-release-cycle-8maryprepsrelease.png\" width=\"\"/></div>\n\n这个时候小明正在实现他的功能，小红开始准备她的第一个项目正式发布。像功能开发一样，她用一个新的分支来做发布准备。这一步也确定了发布的版本号：\n\n```\ngit checkout -b release-0.1 develop\n```\n\n这个分支是清理发布、执行所有测试、更新文档和其它为下个发布做准备操作的地方，像是一个专门用于改善发布的功能分支。\n\n只要小红创建这个分支并 push 到中央仓库，这个发布就是功能冻结的。任何不在 develop 分支中的新功能都推到下个发布循环中。\n\n#### 小红完成发布\n\n<div align=\"center\"> <img src=\"assets/git-workflow-release-cycle-9maryfinishes.png\" width=\"\"/></div>\n\n一旦准备好了对外发布，小红合并修改到 master 分支和 develop 分支上，删除发布分支。合并回 develop 分支很重要，因为在发布分支中已经提交的更新需要在后面的新功能中也要是可用的。另外，如果小红的团队要求 Code Review，这是一个发起 Pull Request 的理想时机。\n\n```\ngit checkout master\ngit merge release-0.1\ngit push\ngit checkout develop\ngit merge release-0.1\ngit push\ngit branch -d release-0.1\n```\n\n发布分支是作为功能开发（develop 分支）和对外发布（master 分支）间的缓冲。只要有合并到 master 分支，就应该打好 Tag 以方便跟踪。\n\n```\ngit tag -a 0.1 -m \"Initial public release\" master\ngit push --tags\n```\n\nGit 有提供各种勾子（hook），即仓库有事件发生时触发执行的脚本。可以配置一个勾子，在你 push 中央仓库的 master 分支时，自动构建好对外发布。\n\n#### 最终用户发现 Bug\n\n<div align=\"center\"> <img src=\"assets/git-workflow-gitflow-enduserbug.png\" width=\"\"/></div>\n\n对外发布后，小红回去和小明一起做下个发布的新功能开发，直到有最终用户开了一个 Ticket 抱怨当前版本的一个 Bug。为了处理 Bug，小红（或小明）从 master 分支上拉出了一个维护分支，提交修改以解决问题，然后直接合并回 master 分支：\n\n```\ngit checkout -b issue-#001 master\n# Fix the bug\ngit checkout master\ngit merge issue-#001\ngit push\n```\n\n就像发布分支，维护分支中新加这些重要修改需要包含到 develop 分支中，所以小红要执行一个合并操作。然后就可以安全地删除这个分支了：\n\n```\ngit checkout develop\ngit merge issue-#001\ngit push\ngit branch -d issue-#001\n```\n\n### 总结\n\n到了这里，但愿你对集中式工作流、功能分支工作流和 GitFlow 工作流已经感觉很舒适了。你应该也牢固的掌握了本地仓库的潜能，push/pull 模式和 Git 健壮的分支和合并模型。\n\n记住，这里演示的工作流只是可能用法的例子，而不是在实际工作中使用 Git 不可违逆的条例。所以不要畏惧按自己需要对工作流的用法做取舍。不变的目标就是让 Git 为你所用。\n\n\n\n## 4. Forking 工作流\n\nForking 工作流和前面讨论的几种工作流有根本的不同。这种工作流不是使用单个服务端仓库作为『中央』代码基线，而让各个开发者都有一个服务端仓库。这意味着各个代码贡献者有 2 个 Git 仓库而不是 1 个：一个本地私有的，另一个服务端公开的。\n\n<div align=\"center\"> <img src=\"assets/git-workflows-forking.png\" width=\"\"/></div>\n\nForking 工作流的一个主要优势是，贡献的代码可以被集成，而不需要所有人都能 push 代码到仅有的中央仓库中。开发者 push 到自己的服务端仓库，而只有项目维护者才能 push 到正式仓库。这样项目维护者可以接受任何开发者的提交，但无需给他正式代码库的写权限。\n\n效果就是一个分布式的工作流，能为大型、自发性的团队（包括了不受信的第三方）提供灵活的方式来安全的协作。也让这个工作流成为开源项目的理想工作流。\n\n### 工作方式\n\n和其它的 Git 工作流一样，Forking 工作流要先有一个公开的正式仓库存储在服务器上。但一个新的开发者想要在项目上工作时，不是直接从正式仓库克隆，而是 fork 正式项目在服务器上创建一个拷贝。\n\n这个仓库拷贝作为他个人公开仓库 —— 其它开发者不允许 push 到这个仓库，但可以 pull 到修改（后面我们很快就会看这点很重要）。在创建了自己服务端拷贝之后，和之前的工作流一样，开发者执行 git clone 命令克隆仓库到本地机器上，作为私有的开发环境。\n\n要提交本地修改时，push 提交到自己公开仓库中 —— 而不是正式仓库中。然后，给正式仓库发起一个 pull request，让项目维护者知道有更新已经准备好可以集成了。对于贡献的代码，pull request 也可以很方便地作为一个讨论的地方。\n\n为了集成功能到正式代码库，维护者 pull 贡献者的变更到自己的本地仓库中，检查变更以确保不会让项目出错，合并变更到自己本地的 master 分支，然后 push master 分支到服务器的正式仓库中。到此，贡献的提交成为了项目的一部分，其它的开发者应该执行 pull 操作与正式仓库同步自己本地仓库。\n\n#### 正式仓库\n\n在 Forking 工作流中，『官方』仓库的叫法只是一个约定，理解这点很重要。从技术上来看，各个开发者仓库和正式仓库在Git看来没有任何区别。事实上，让正式仓库之所以正式的唯一原因是它是项目维护者的公开仓库。\n\n#### Forking 工作流的分支使用方式\n\n所有的个人公开仓库实际上只是为了方便和其它的开发者共享分支。各个开发者应该用分支隔离各个功能，就像在功能分支工作流和 GitFlow 工作流一样。唯一的区别是这些分支被共享了。在 Forking 工作流中这些分支会被 pull 到另一个开发者的本地仓库中，而在功能分支工作流和 GitFlow 工作流中是直接被 push 到正式仓库中。\n\n### 示例\n\n#### 项目维护者初始化正式仓库\n\n<div align=\"center\"> <img src=\"assets/git-workflows-forking-1.png\" width=\"\"/></div>\n\n和任何使用 Git 项目一样，第一步是创建在服务器上一个正式仓库，让所有团队成员都可以访问到。通常这个仓库也会作为项目维护者的公开仓库。\n\n公开仓库应该是裸仓库，不管是不是正式代码库。所以项目维护者会运行像下面的命令来搭建正式仓库：\n\n```\nssh user@host\ngit init --bare /path/to/repo.git\n```\n\nBitbucket 和 Stash 提供了一个方便的 GUI 客户端以完成上面命令行做的事。这个搭建中央仓库的过程和前面提到的工作流完全一样。如果有现存的代码库，维护者也要 push 到这个仓库中。\n\n#### 开发者 fork 正式仓库\n\n<div align=\"center\"> <img src=\"assets/git-workflows-forking-2.png\" width=\"\"/></div>\n\n其它所有的开发需要 fork 正式仓库。可以用 git clone 命令用 SSH 协议连通到服务器，拷贝仓库到服务器另一个位置 —— 是的，fork 操作基本上就只是一个服务端的克隆。Bitbucket 和 Stash 上可以点一下按钮就让开发者完成仓库的 fork 操作。\n\n这一步完成后，每个开发都在服务端有一个自己的仓库。和正式仓库一样，这些仓库应该是裸仓库。\n\n#### 开发者克隆自己 fork 出来的仓库\n\n<div align=\"center\"> <img src=\"assets/git-workflows-forking-3.png\" width=\"\"/></div>\n\n下一步，各个开发者要克隆自己的公开仓库，用熟悉的 git clone 命令。\n\n在这个示例中，假定用 Bitbucket 托管了仓库。记住，如果这样的话各个开发者需要有各自的 Bitbucket 账号，使用下面命令克隆服务端自己的仓库：\n\n```\ngit clone https://user@bitbucket.org/user/repo.git\n```\n\n相比前面介绍的工作流只用了一个 origin 远程别名指向中央仓库，Forking 工作流需要 2 个远程别名 —— 一个指向正式仓库，另一个指向开发者自己的服务端仓库。别名的名字可以任意命名，常见的约定是使用 origin 作为远程克隆的仓库的别名（这个别名会在运行 git clone 自动创建），upstream（上游）作为正式仓库的别名。\n\n```\ngit remote add upstream https://bitbucket.org/maintainer/repo\n```\n\n需要自己用上面的命令创建 upstream 别名。这样可以简单地保持本地仓库和正式仓库的同步更新。注意，如果上游仓库需要认证（比如不是开源的），你需要提供用户：\n\n```\ngit remote add upstream https://user@bitbucket.org/maintainer/repo.git\n```\n\n这时在克隆和 pull 正式仓库时，需要提供用户的密码。\n\n#### 开发者开发自己的功能\n\n<div align=\"center\"> <img src=\"assets/git-workflows-forking-4.png\" width=\"\"/></div>\n\n在刚克隆的本地仓库中，开发者可以像其它工作流一样的编辑代码、提交修改和新建分支：\n\n```\ngit checkout -b some-feature\n// Edit some code\ngit commit -a -m \"Add first draft of some feature\"\n```\n\n所有的修改都是私有的直到 push 到自己公开仓库中。如果正式项目已经往前走了，可以用 git pull 命令获得新的提交：\n\n```\ngit pull upstream master\n```\n\n由于开发者应该都在专门的功能分支上工作，pull 操作结果会都是快进合并。\n\n#### 开发者发布自己的功能\n\n<div align=\"center\"> <img src=\"assets/git-workflows-forking-5.png\" width=\"\"/></div>\n\n一旦开发者准备好了分享新功能，需要做二件事。首先，通过push他的贡献代码到自己的公开仓库中，让其它的开发者都可以访问到。他的 origin 远程别名应该已经有了，所以要做的就是：\n\n```\ngit push origin feature-branch\n```\n\n这里和之前的工作流的差异是，origin 远程别名指向开发者自己的服务端仓库，而不是正式仓库。\n\n第二件事，开发者要通知项目维护者，想要合并他的新功能到正式库中。Bitbucket 和 Stash 提供了 Pull Request 按钮，弹出表单让你指定哪个分支要合并到正式仓库。一般你会想集成你的功能分支到上游远程仓库的 master 分支中。\n\n#### 项目维护者集成开发者的功能\n\n<div align=\"center\"> <img src=\"assets/git-workflows-forking-6.png\" width=\"\"/></div>\n\n当项目维护者收到 pull request，他要做的是决定是否集成它到正式代码库中。有二种方式来做：\n\n- 直接在 pull request 中查看代码\n- pull 代码到他自己的本地仓库，再手动合并\n\n第一种做法更简单，维护者可以在 GUI 中查看变更的差异，做评注和执行合并。但如果出现了合并冲突，需要第二种做法来解决。这种情况下，维护者需要从开发者的服务端仓库中 fetch 功能分支，合并到他本地的 master 分支，解决冲突：\n\n```\ngit fetch https://bitbucket.org/user/repo feature-branch\n// 查看变更\ngit checkout master\ngit merge FETCH_HEAD\n```\n\n变更集成到本地的 master 分支后，维护者要 push 变更到服务器上的正式仓库，这样其它的开发者都能访问到：\n\n```\ngit push origin master\n```\n\n注意，维护者的 origin 是指向他自己公开仓库的，即是项目的正式代码库。到此，开发者的贡献完全集成到了项目中\n\n#### 开发者和正式仓库做同步\n\n<div align=\"center\"> <img src=\"assets/git-workflows-forking-7.png\" width=\"\"/></div>\n\n由于正式代码库往前走了，其它的开发需要和正式仓库做同步：\n\n```\ngit pull upstream master\n```\n\n### 总结\n\n如果你之前是使用 SVN，Forking 工作流可能看起来像是一个激进的范式切换（paradigm shift）。但不要害怕，这个工作流实际上就是在功能分支工作流之上引入另一个抽象层。不是直接通过单个中央仓库来分享分支，而是把贡献代码发布到开发者自己的服务端仓库中。\n\n示例中解释了，一个贡献如何从一个开发者流到正式的 master 分支中，但同样的方法可以把贡献集成到任一个仓库中。比如，如果团队的几个人协作实现一个功能，可以在开发之间用相同的方法分享变更，完全不涉及正式仓库。\n\n这使得 Forking 工作流对于松散组织的团队来说是个非常强大的工具。任一开发者可以方便地和另一开发者分享变更，任何分支都能有效地合并到正式代码库中。\n\n\n\n\n\n## 5. Pull Requests\n\nPull Requests 是 Bitbucket 上方便开发者之间协作的功能。提供了一个用户友好的 Web 界面，在集成提交的变更到正式项目前可以对变更进行讨论。\n\n<div align=\"center\"> <img src=\"assets/pull-request-bitbucket.png\" width=\"\"/></div>\n\n开发者向团队成员通知功能开发已经完成，Pull Requests 是最简单的用法。开发者完成功能开发后，通过 Bitbucket 账号发起一个 Pull Request。这样让涉及这个功能的所有人知道，要去做 Code Review 和合并到 master 分支。\n\n但是，Pull Request 远不止一个简单的通知，而是为讨论提交的功能的一个专门论坛。如果变更有任何问题，团队成员反馈在 Pull Request 中，甚至 push 新的提交微调功能。所有的这些活动都直接跟踪在 Pull Request 中。\n\n<div align=\"center\"> <img src=\"assets/pull-request-overview.png\" width=\"\"/></div>\n\n相比其它的协作模型，这种分享提交的形式有助于打造一个更流畅的工作流。SVN 和 Git 都能通过一个简单的脚本收到通知邮件；但是，讨论变更时，开发者通常只能去回复邮件。这样做会变得杂乱，尤其还要涉及后面的几个提交时。Pull Requests 把所有相关功能整合到一个和 Bitbucket 仓库界面集成的用户友好 Web 界面中。\n\n### 解析 Pull Request\n\n当要发起一个 Pull Request，你所要做的就是请求（Request）另一个开发者（比如项目的维护者），来 pull 你仓库中一个分支到他的仓库中。这意味着你要提供 4 个信息（源仓库、源分支、目的仓库、目的分支），以发起 Pull Request。\n\n<div align=\"center\"> <img src=\"assets/pull-request-anatomy.png\" width=\"\"/></div>\n\n### 工作方式\n\nPull Request 可以和功能分支工作流、GitFlow 工作流或 Forking 工作流一起使用。但 Pull Request 要求要么分支不同，要么仓库不同，所以不能用于集中式工作流。在不同的工作流中使用 Pull Request 会有一些不同，但基本的过程是这样的：\n\n- 开发者在本地仓库中新建一个专门的分支开发功能。\n- 开发者 push 分支修改到公开的 Bitbucket 仓库中。\n- 开发者通过 Bitbucket 发起一个 Pull Request。\n- 团队的其它成员 review code，讨论并修改。\n- 项目维护者合并功能到官方仓库中并关闭 Pull Request。\n\n#### 在功能分支工作流中使用 Pull Request\n\n功能分支工作流用一个共享的 Bitbucket 仓库来管理协作，开发者在专门的分支上开发功能。但不是立即合并到 master 分支上，而是在合并到主代码库之前开发者应该开一个 Pull Request 发起功能的讨论。\n\n<div align=\"center\"> <img src=\"assets/pull-request-feature-branch.png\" width=\"\"/></div>\n\n功能分支工作流只有一个公开的仓库，所以 Pull Request 的目的仓库和源仓库总是同一个。通常开发者会指定他的功能分支作为源分支，master 分支作为目的分支。\n\n收到 Pull Request 后，项目维护者要决定如何做。如果功能没问题，就简单地合并到 master 分支，关闭 Pull Request。但如果提交的变更有问题，他可以在 Pull Request 中反馈。之后新加的提交也会评论之后接着显示出来\n\n在功能还没有完全开发完的时候，也可能发起一个 Pull Request。比如开发者在实现某个需求时碰到了麻烦，他可以发一个包含正在进行中工作的 Pull Request。其它的开发者可以在 Pull Request 提供建议，或者甚至直接添加提交来解决问题。\n\n#### 在 GitFlow 工作流中使用 Pull Request\n\nGitFlow 工作流和功能分支工作流类似，但围绕项目发布定义一个严格的分支模型。在 GitFlow 工作流中使用 Pull Request 让开发者在发布分支或是维护分支上工作时，可以有个方便的地方对关于发布分支或是维护分支的问题进行交流。\n\n<div align=\"center\"> <img src=\"assets/gitflow-workflow-pull-request.png\" width=\"\"/></div>\n\nGitFlow 工作流中 Pull Request 的使用过程和上一节中完全一致：当一个功能、发布或是热修复分支需要 Review 时，开发者简单发起一个 Pull Request，团队的其它成员会通过 Bitbucket 收到通知。\n\n新功能一般合并到 develop 分支，而发布和热修复则要同时合并到 develop 分支和 master 分支上。Pull Request 可能用做所有合并的正式管理。\n\n#### 在 Forking 工作流中使用 Pull Request\n\n在 Forking 工作流中，开发者 push 完成的功能到他自己的仓库中，而不是共享仓库。然后，他发起一个 Pull Request，让项目维护者知道他的功能已经可以 Review 了。\n\n在这个工作流，Pull Request 的通知功能非常有用，因为项目维护者不可能知道其它开发者在他们自己的仓库添加了提交\n\n<div align=\"center\"> <img src=\"assets/pull-request-forking-workflow-1.png\" width=\"\"/></div>\n\n由于各个开发有自己的公开仓库，Pull Request 的源仓库和目标仓库不是同一个。源仓库是开发者的公开仓库，源分支是包含了修改的分支。如果开发者要合并修改到正式代码库中，那么目标仓库是正式仓库，目标分支是 master 分支。\n\nPull Request 也可以用于正式项目之外的其它开发者之间的协作。比如，如果一个开发者和一个团队成员一起开发一个功能，他们可以发起一个 Pull Request，用团队成员的 Bitbucket 仓库作为目标，而不是正式项目的仓库。然后使用相同的功能分支作为源和目标分支。\n\n<div align=\"center\"> <img src=\"assets/pull-request-forking-workflow-2.png\" width=\"\"/></div>\n\n2 个开发者之间可以在 Pull Request 中讨论和开发功能。完成开发后，他们可以发起另一个 Pull Request，请求合并功能到正式的 master 分支。在 Forking 工作流中，这样的灵活性让 Pull Request 成为一个强有力的协作工具。\n\n### 示例\n\n下面的示例演示了 Pull Request 如何在在 Forking 工作流中使用。也同样适用于小团队的开发协作和第三方开发者向开源项目的贡献。\n\n在示例中，小红是个开发，小明是项目维护者。他们各自有一个公开的 Bitbucket 仓库，而小明的仓库包含了正式工程。\n\n#### 小红 fork 正式项目\n\n<div align=\"center\"> <img src=\"assets/pull-request-1.png\" width=\"\"/></div>\n\n小红先要 fork 小明的 Bitbucket 仓库，开始项目的开发。她登陆 Bitbucket，浏览到小明的仓库页面，点 Fork 按钮。\n\n<div align=\"center\"> <img src=\"assets/pull-request-2.png\" width=\"\"/></div>\n\n然后为 fork 出来的仓库填写名字和描述，这样小红就有了服务端的项目拷贝了。\n\n#### 小红克隆她的 Bitbucket 仓库\n\n<div align=\"center\"> <img src=\"assets/pull-request-3.png\" width=\"\"/></div>\n\n下一步，小红克隆自己刚才 fork 出来的 Bitbucket 仓库，以在本机上准备出工作拷贝。命令如下：\n\n```\ngit clone https://user@bitbucket.org/user/repo.git\n```\n\n请记住，`git clone` 会自动创建 origin 远程别名，是指向小红 fork 出来的仓库。\n\n#### 小红开发新功能\n\n在开始改代码前，小红要为新功能先新建一个新分支。她会用这个分支作为 Pull Request 的源分支。\n\n```\ngit checkout -b some-feature\n```\n\n### 编辑代码\n\n```\ngit commit -a -m \"Add first draft of some feature\"\n```\n\n在新功能分支上，小红按需要添加提交。甚至如果小红觉得功能分支上的提交历史太乱了，她可以用交互式 rebase 来删除或压制提交。对于大型项目，整理功能分支的历史可以让项目维护者更容易看出在 Pull Request 中做了什么内容。\n\n#### 小红 push 功能到她的 Bitbucket 仓库中\n\n<div align=\"center\"> <img src=\"assets/pull-request-5.png\" width=\"\"/></div>\n\n小红完成了功能后，push 功能到她自己的 Bitbucket 仓库中（不是正式仓库），用下面简单的命令：\n\n```\ngit push origin some-branch\n```\n\n这时她的变更可以让项目维护者看到了（或者任何想要看的协作者）。\n\n#### 小红发起 Pull Request\n\n<div align=\"center\"> <img src=\"assets/example-6.png\" width=\"\"/></div>\n\nBitbucket 上有了她的功能分支后，小红可以用她的 Bitbucket 账号浏览到她的 fork 出来的仓库页面，点右上角的【Pull Request】按钮，发起一个 Pull Request。弹出的表单自动设置小红的仓库为源仓库，询问小红以指定源分支、目标仓库和目标分支。\n\n小红想要合并功能到正式仓库，所以源分支是她的功能分支，目标仓库是小明的公开仓库，而目标分支是 master 分支。另外，小红需要提供 Pull Request 的标题和描述信息。如果需要小明以外的人审核批准代码，她可以把这些人填在【Reviewers】文本框中。\n\n<div align=\"center\"> <img src=\"assets/pull-request-7.png\" width=\"\"/></div>\n\n创建好了 Pull Request，通知会通过Bitbucket系统消息或邮件（可选）发给小明。\n\n#### 小明 review Pull Request\n\n<div align=\"center\"> <img src=\"assets/pull-request-8.png\" width=\"\"/></div>\n\n在小明的 Bitbucket 仓库页面的 【Pull Request】Tab 可以看到所有人发起的 Pull Request。点击小红的 Pull Request 会显示出 Pull Request 的描述、功能的提交历史和每个变更的差异（diff）。\n\n如果小明想要合并到项目中，只要点一下【Merge】按钮，就可以同意 Pull Request 并合并到 master 分支。\n\n但如果像这个示例中一样小明发现了在小红的代码中的一个小 Bug，要小红在合并前修复。小明可以在整个 Pull Request 上加上评注，或是选择历史中的某个提交加上评注。\n\n<div align=\"center\"> <img src=\"assets/pull-request-9.png\" width=\"\"/></div>\n\n#### 小红补加提交\n\n如果小红对反馈有任何疑问，可以在 Pull Request 中响应，把 Pull Request 当作是她功能讨论的论坛。\n\n小红在她的功能分支新加提交以解决代码问题，并 push 到她的 Bitbucket 仓库中，就像前一轮中的做法一样。这些提交会进入的 Pull Request，小明在原来的评注旁边可以再次 review 变更。\n\n#### 小明接受 Pull Request\n\n最终，小明接受变更，合并功能分支到 master 分支，并关闭 Pull Request。至此，功能集成到项目中，其它的项目开发者可以用标准的 git pull 命令 pull 这些变更到自己的本地仓库中。\n\n### 总结\n\n到了这里，你应该有了所有需要的工具来集成 Pull Request 到你自己的工作流。请记住，Pull Request 并不是为了替代任何基于 Git 的协作工作流，而是它们的一个便利的补充，让团队成员间的协作更轻松方便。"
  },
  {
    "path": "notes/JavaArchitecture/01-Java基础.md",
    "content": "# 前言\n\n本文主要包含 Java 核心基础知识，主要根据以下部分进行节选，选择了个人认为在面试中最为核心的部分。\n\n- 《Java程序员面试笔试宝典》何昊，薛鹏，叶向阳 著\n- [《阿里面经OneNote》](https://blog.csdn.net/sinat_22797429/article/details/76293284)\n\n 主要内容：基本概念、面向对象、关键字、基本数据类型与运算、字符串与数组、异常处理、Object 通用方法\n\n\n\n# 一、基本概念\n\n## 1. Java程序初始化的顺序是怎么样的\n\n　　在 Java 语言中，当实例化对象时，对象所在类的所有成员变量首先要进行初始化，只有当所有类成员完成初始化后，才会调用对象所在类的构造函数创建象。\n\n**初始化一般遵循3个原则：**\n\n- 静态对象（变量）优先于非静态对象（变量）初始化，静态对象（变量）只初始化一次，而非静态对象（变量）可能会初始化多次；\n- 父类优先于子类进行初始化；\n- 按照成员变量的定义顺序进行初始化。 即使变量定义散布于方法定义之中，它们依然在任何方法（包括构造函数）被调用之前先初始化；\n\n**加载顺序**\n\n- 父类（静态变量、静态语句块）\n- 子类（静态变量、静态语句块）\n- 父类（实例变量、普通语句块）\n- 父类（构造函数）\n- 子类（实例变量、普通语句块）\n- 子类（构造函数）\n\n\n\n**实例** \n\n```java\nclass Base {\n    // 1.父类静态代码块\n    static {\n        System.out.println(\"Base static block!\");\n    }\n    // 3.父类非静态代码块\n    {\n        System.out.println(\"Base block\");\n    }\n    // 4.父类构造器\n    public Base() {\n        System.out.println(\"Base constructor!\");\n    }\n}\n\npublic class Derived extends Base {\n    // 2.子类静态代码块\n    static{\n        System.out.println(\"Derived static block!\");\n    }\n    // 5.子类非静态代码块\n    {\n        System.out.println(\"Derived block!\");\n    }\n    // 6.子类构造器\n    public Derived() {\n        System.out.println(\"Derived constructor!\");\n    }\n    public static void main(String[] args) {\n        new Derived();\n    }\n}\n```\n\n结果是：\n\n```\nBase static block!\nDerived static block!\nBase block\nBase constructor!\nDerived block!\nDerived constructor!\n```\n\n\n\n## 2. Java和C++的区别\n\n- Java 是**纯粹的面向对象语言**，所有的对象都继承自 java.lang.Object，**C++ 为了兼容 C 即支持面向对象也支持面向过程**。\n- Java 通过虚拟机从而实现**跨平台特性**，但是 C++ 依赖于**特定的平台**。\n- Java 没有指针，它的引用可以理解为安全指针，而 C++ 具有和 C 一样的指针。\n- Java 支持**自动垃圾回收**，而 C++ 需要**手动回收**。（C++11 中引入智能指针，使用引用计数法垃圾回收）\n- **Java 不支持多重继承**，只能通过实现多个接口来达到相同目的，而 **C++ 支持多重继承**。\n- Java 不支持操作符重载，虽然可以对两个 String 对象支持加法运算，但是这是语言内置支持的操作，不属于操作符重载，而 C++ 可以。\n- Java 内置了线程的支持，而 C++ 需要依靠第三方库。\n- Java 的 **goto 是保留字**，但是不可用，C++ 可以使用 goto。\n- Java **不支持条件编译**，C++ 通过 #ifdef #ifndef 等预处理命令从而实现**条件编译**。\n\n\n\n参考资料：\n- [C++ 工程实践(8)：值语义 - 陈硕 - 博客园](http://www.cnblogs.com/Solstice/archive/2011/08/16/2141515.html)\n- [c++11改进我们的程序之垃圾回收](https://www.cnblogs.com/qicosmos/p/3282779.html)\n\n\n\n## 3. 反射\n\n### 先看一个知乎回答\n\n　　首先看一个在知乎上的优秀回答吧：\n\n　　反射是什么呢？当我们的程序在运行时，需要动态的加载一些类这些类可能之前用不到所以不用加载到 JVM，而是在运行时根据需要才加载，这样的好处对于服务器来说不言而喻。\n\n　　举个例子我们的项目底层有时是用 mysql，有时用 oracle，需要动态地根据实际情况加载驱动类，这个时候反射就有用了，假设 com.java.dbtest.myqlConnection，com.java.dbtest.oracleConnection 这两个类我们要用，这时候我们的程序就写得比较动态化，通过 Class tc = Class.forName(\"com.java.dbtest.TestConnection\"); 通过类的全类名让 JVM 在服务器中找到并加载这个类，而如果是 Oracle 则传入的参数就变成另一个了。这时候就可以看到反射的好处了，这个动态性就体现出 Java 的特性了！\n\n　　举多个例子，大家如果接触过 spring，会发现当你配置各种各样的 bean 时，是以配置文件的形式配置的，你需要用到哪些 bean 就配哪些，spring 容器就会根据你的需求去动态加载，你的程序就能健壮地运行。\n\n\n\n### 什么是反射\n\n　　反射 (Reflection) 是 Java 程序开发语言的特征之一，它允许运行中的 Java 程序获取自身的信息，并且可以操作类或对象的内部属性。通过 Class 获取 class 信息称之为反射（Reflection）\n\n　　简而言之，通过反射，我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。\n\n　　程序中一般的对象的类型都是在编译期就确定下来的，而 Java 反射机制可以动态地创建对象并调用其属性，这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象，即使这个对象的类型在编译期是未知的。\n\n　　反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性，它不需要事先（写代码的时候或编译期）知道运行对象是谁。\n\n　　Java 反射框架主要提供以下功能：\n\n　　1. 在运行时判断任意一个对象所属的类\n\n　　2. 在运行时构造任意一个类的对象\n\n　　3. 在运行时判断任意一个类所具有的成员变量和方法（通过反射甚至可以调用 private 方法）\n\n　　4. 在运行时调用任意一个对象的方法\n\n　　**重点**：是运行时而不是编译时\n\n\n\n### 主要用途\n\n　　很多人都认为反射在实际的 Java 开发应用中并不广泛，其实不然。\n\n当我们在使用 IDE （如Eclipse，IDEA）时，当我们输入一个对象或类并想调用它的属性或方法时，一按点号，编译器就会自动列出它的属性或方法，这里就会用到反射。\n\n　　**反射最重要的用途就是开发各种通用框架**\n\n　　很多框架（比如 Spring ）都是配置化的（比如通过 XML 文件配置 JavaBean,Action 之类的），为了保证框架的通用性，它们可能需要根据配置文件加载不同的对象或类，调用不同的方法，这个时候就必须用到反射——运行时动态加载需要加载的对象。\n\n　　对与框架开发人员来说，反射虽小但作用非常大，它是各种容器实现的核心。而对于一般的开发者来说，不深入框架开发则用反射用的就会少一点，不过了解一下框架的底层机制有助于丰富自己的编程思想，也是很有益的。\n\n### 获得Class对象\n\n1. 调用运行时类本身的 `.class` 属性\n\n```java\nClass clazz1 = Person.class;\nSystem.out.println(clazz1.getName());\n```\n\n2. 通过运行时类的对象获取 `getClass();`\n\n```java\nPerson p = new Person();\nClass clazz3 = p.getClass();\nSystem.out.println(clazz3.getName());\n```\n3. 使用 Class 类的 `forName` 静态方法\n\n```java\npublic static Class<?> forName(String className)\n// 在JDBC开发中常用此方法加载数据库驱动:\nClass.forName(driver);\n```\n4. （了解）通过类的加载器 ClassLoader\n\n```java\nClassLoader classLoader = this.getClass().getClassLoader();\nClass clazz5 = classLoader.loadClass(className);\nSystem.out.println(clazz5.getName());\n```\n\n\n\n参考资料：\n\n- [深入解析Java反射（1） - 基础 | 「浮生若梦」 - sczyh30's blog](https://www.sczyh30.com/posts/Java/java-reflection-1/#%E4%B8%80%E3%80%81%E5%9B%9E%E9%A1%BE%EF%BC%9A%E4%BB%80%E4%B9%88%E6%98%AF%E5%8F%8D%E5%B0%84%EF%BC%9F)\n- [学习java应该如何理解反射？ - 知乎](https://www.zhihu.com/question/24304289/answer/147529485)\n\n\n\n## 4. 注解\n\n### 什么是注解\n\n　　Annontation 是 Java5 开始引入的新特征，中文名称叫注解。它提供了一种安全的类似注释的机制，用来**将任何的信息或元数据（metadata）与程序元素（类、方法、成员变量等）进行关联**。为程序的元素（类、方法、成员变量）加上更直观更明了的说明，这些说明信息是与程序的业务逻辑无关，并且供指定的工具或框架使用。Annontation 像一种修饰符一样，应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。\n\n　　Java 注解是附加在代码中的一些元信息，用于一些工具在编译、运行时进行解析和使用，起到说明、配置的功能。注解不会也不能影响代码的实际逻辑，仅仅起到辅助性的作用。包含在 ` java.lang.annotation` 包中。\n\n　　简单来说：注解其实就是**代码中的特殊标记**，这些标记可以**在编译、类加载、运行时被读取，并执行相对应的处理**。 \n\n\n\n### 为什么要用注解\n\n传统的方式，我们是通过配置文件 `.xml` 来告诉类是如何运行的。\n\n有了注解技术以后，我们就可以通过注解告诉类如何运行\n\n例如：我们以前编写 Servlet 的时候，需要在 web.xml 文件配置具体的信息。我们使用了注解以后，可以直接在 Servlet 源代码上，增加注解...Servlet 就被配置到 Tomcat 上了。也就是说，注解可以给类、方法上注入信息。\n\n明显地可以看出，这样是非常直观的，并且 Servlet 规范是推崇这种配置方式的。 \n\n\n\n### 基本Annotation\n\n在 java.lang 包下存在着5个基本的 Annotation，重点掌握前三个。\n\n1. @Override 重写注解\n   - 如果我们使用IDE重写父类的方法，我们就可以看见它了。\n   - @Override是告诉编译器要检查该方法是实现父类的，可以帮我们避免一些低级的错误。\n   - 比如，我们在实现 equals() 方法的时候，把 euqals() 打错了，那么编译器就会发现该方法并不是实现父类的，与注解 @Override 冲突，于是就会给予错误。\n\n2. @Deprecated 过时注解\n   - 该注解也非常常见，Java 在设计的时候，可能觉得某些方法设计得不好，为了兼容以前的程序，是不能直接把它抛弃的，于是就设置它为过时。\n   - Date对象中的 toLocalString() 就被设置成过时了\n   - 当我们在程序中调用它的时候，在 IDE 上会出现一条横杠，说明该方法是过时的。\n\n```java\n@Deprecated\npublic String toLocaleString() {\n    DateFormat formatter = DateFormat.getDateTimeInstance();\n    return formatter.format(this);\n}\n```\n\n3. @SuppressWarnings 抑制编译器警告注解\n   - 该注解在我们写程序的时候并不是很常见，我们可以用它来让编译器不给予我们警告\n   - 当我们在使用集合的时候，如果没有指定泛型，那么会提示安全检查的警告\n   - 如果我们在类上添加了@SuppressWarnings这个注解，那么编译器就不会给予我们警告了 \n\n4. @SafeVarargs Java 7“堆污染”警告\n   - 什么是堆污染呢？？当把一个不是泛型的集合赋值给一个带泛型的集合的时候，这种情况就很容易发生堆污染。\n   - 这个注解也是用来抑制编译器警告的注解，用的地方并不多。\n\n5. @FunctionalInterface 用来指定该接口是函数式接口\n   - 用该注解显式指定该接口是一个函数式接口。\n\n\n\n### 自定义注解类编写规则\n\n1. Annotation 型定义为 @interface, 所有的 Annotation 会自动继承 java.lang.Annotation 这一接口，并且不能再去继承别的类或是接口.\n2. 参数成员只能用 public 或默认(default)这两个访问权修饰\n3. 参数成员只能用基本类型 byte,short,char,int,long,float,double,boolean 八种基本数据类型和 String、Enum、Class、annotations 等数据类型，以及这一些类型的数组\n4. 要获取类方法和字段的注解信息，必须通过 Java 的反射技术来获取 Annotation 对象，因为你除此之外没有别的获取注解对象的方法\n5. 注解也可以没有定义成员, 不过这样注解就没啥用了\n    PS：自定义注解需要使用到元注解\n\n\n\n### 自定义注解实例\n\n```java\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.Target;\nimport static java.lang.annotation.ElementType.FIELD;\nimport static java.lang.annotation.RetentionPolicy.RUNTIME;\n\n/**\n * 水果名称注解\n */\n@Target(FIELD)\n@Retention(RUNTIME)\n@Documented\npublic @interface FruitName {\n    String value() default \"\";\n}\n```\n\n 参考资料：[注解Annotation实现原理与自定义注解例子](https://www.cnblogs.com/acm-bingzi/p/javaAnnotation.html)\n\n\n\n## 5. 泛型\n\n### 通俗解释\n\n　　通俗的讲，泛型就是操作类型的 占位符，即：假设占位符为 T，那么此次声明的数据结构操作的数据类型为T类型。\n\n　　假定我们有这样一个需求：写一个排序方法，能够对整型数组、字符串数组甚至其他任何类型的数组进行排序，该如何实现？答案是可以使用 **Java 泛型**。\n\n　　使用 Java 泛型的概念，我们可以写一个泛型方法来对一个对象数组排序。然后，调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序。\n\n### 泛型方法\n\n　　你可以写一个泛型方法，该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型，编译器适当地处理每一个方法调用。\n\n下面是定义泛型方法的规则：\n\n- 所有泛型方法声明都有一个类型参数声明部分（由尖括号分隔），该类型参数声明部分在方法返回类型之前（在下面例子中的 \\<E>）。\n- 每一个类型参数声明部分包含一个或多个类型参数，参数间用逗号隔开。一个泛型参数，也被称为一个类型变量，是用于指定一个泛型类型名称的标识符。\n- 类型参数能被用来声明返回值类型，并且能作为泛型方法得到的实际参数类型的占位符。\n- 泛型方法体的声明和其他方法一样。注意类型参数 **只能代表引用型类型，不能是原始类型** （像 int,double,char 的等）。\n\n```java\npublic class GenericMethodTest\n{\n   // 泛型方法 printArray                         \n   public static < E > void printArray( E[] inputArray )\n   {\n      // 输出数组元素            \n         for ( E element : inputArray ){        \n            System.out.printf( \"%s \", element );\n         }\n         System.out.println();\n    }\n \n    public static void main( String args[] )\n    {\n        // 创建不同类型数组： Integer, Double 和 Character\n        Integer[] intArray = { 1, 2, 3, 4, 5 };\n        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };\n        Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };\n \n        System.out.println( \"整型数组元素为:\" );\n        printArray( intArray  ); // 传递一个整型数组\n \n        System.out.println( \"\\n双精度型数组元素为:\" );\n        printArray( doubleArray ); // 传递一个双精度型数组\n \n        System.out.println( \"\\n字符型数组元素为:\" );\n        printArray( charArray ); // 传递一个字符型数组\n    } \n}\n```\n\n\n\n### 泛型类\n\n　　泛型类的声明和非泛型类的声明类似，除了在类名后面添加了类型参数声明部分。\n\n　　和泛型方法一样，泛型类的类型参数声明部分也包含一个或多个类型参数，参数间用逗号隔开。一个泛型参数，也被称为一个类型变量，是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数，这些类被称为参数化的类或参数化的类型。\n\n```java\npublic class Box<T> {\n\tprivate T t;\n\tpublic void add(T t) {\n\t    this.t = t;\n\t}\n\n\tpublic T get() {\n\t    return t;\n\t}\n\n\tpublic static void main(String[] args) {\n\t    Box<Integer> integerBox = new Box<Integer>();\n\t    Box<String> stringBox = new Box<String>();\n\n\t    integerBox.add(new Integer(10));\n\t    stringBox.add(new String(\"菜鸟教程\"));\n\n\t    System.out.printf(\"整型值为 :%d\\n\\n\", integerBox.get());\n\t    System.out.printf(\"字符串为 :%s\\n\", stringBox.get());\n\t}\n}\n```\n\n### 类型通配符\n\n1. 类型通配符一般是使用 `?` 代替具体的类型参数。例如  `List<?>` 在逻辑上是 `List<String>`，`List<Integer>` 等所有 **List<具体类型实参>** 的父类。  \n\n2. 类型通配符上限通过形如 List 来定义，如此定义就是通配符泛型值接受 Number 及其下层子类类型。  \n\n3. 类型通配符下限通过形如 List<? super Number> 来定义，表示类型只能接受 Number 及其三层父类类型，如 Objec 类型的实例。  \n\n\n\n参考资料：\n\n- [Java 泛型，了解这些就够用了。 - 逃离沙漠 - 博客园](https://www.cnblogs.com/demingblog/p/5495610.html)\n- [Java 泛型 | 菜鸟教程](http://www.runoob.com/java/java-generics.html)\n- [【Java心得总结四】Java泛型下——万恶的擦除 - xlturing - 博客园](https://www.cnblogs.com/xltcjylove/p/3671943.html)\n  \n\n\n\n## 6. 字节与字符的区别\n\n理解编码的关键，是要把字符的概念和字节的概念理解准确。这两个概念容易混淆，我们在此做一下区分：\n\n| 类型           | **概念描述**                                                 | **举例**                      |\n| -------------- | ------------------------------------------------------------ | ----------------------------- |\n| 字符           | 人们使用的记号，抽象意义上的一个符号。                       | '1', '中', 'a', '$', '￥', …… |\n| 字节           | 计算机中存储数据的单元，一个 8 位的二进制数，是一个很具体的存储空间。 | 0x01, 0x45, 0xFA, ……          |\n| ANSI 字符串    | 在内存中，如果“字符”是以 **ANSI 编码**形式存在的，一个字符可能使用一个字节或多个字节来表示，那么我们称这种字符串为 **ANSI 字符串**或者**多字节字符串**。 | \"中文123\" （占7字节）         |\n| UNICODE 字符串 | 在内存中，如果“字符”是以在 UNICODE 中的序号存在的，那么我们称这种字符串为 **UNICODE 字符串**或者**宽字节字符串**。 | L\"中文123\" （占10字节）       |\n\n\n\n**字节与字符区别**\n\n它们完全不是一个位面的概念，所以两者之间没有“区别”这个说法。不同编码里，字符和字节的对应关系不同：\n\n| 类型    | **概念描述**                                                 |\n| ------- | ------------------------------------------------------------ |\n| ASCII   | 一个英文字母（不分大小写）占一个字节的空间，一个中文汉字占两个字节的空间。一个二进制数字序列，在计算机中作为一个数字单元，一般为 8 位二进制数，换算为十进制。最小值 0，最大值 255。 |\n| UTF-8   | 一个英文字符等于一个字节，一个中文（含繁体）等于三个字节     |\n| Unicode | 一个英文等于两个字节，一个中文（含繁体）等于两个字节。符号：英文标点占一个字节，中文标点占两个字节。举例：英文句号“.”占 1 个字节的大小，中文句号“。”占 2 个字节的大小。 |\n| UTF-16  | 一个英文字母字符或一个汉字字符存储都需要 2 个字节（Unicode扩展区的一些汉字存储需要4个字节） |\n| UTF-32  | 世界上任何字符的存储都需要 4 个字节                          |\n\n\n\n参考资料：\n\n- [字符，字节和编码 - Characters, Bytes And Encoding](http://www.regexlab.com/zh/encoding.htm)\n\n\n\n## 7. 有哪些访问修饰符\n\nJava 面向对象的基本思想之一是封装细节并且公开接口。Java 语言采用访问控制修饰符来控制类及类的方法和变量的访问权限，从而向使用者暴露接口，但隐藏实现细节。访问控制分为四种级别：\n\n| 修饰符    | 当前类 | 同 包 | 子 类 | 其他包 |\n| --------- | ------ | ----- | ----- | ------ |\n| public    | √      | √     | √     | √      |\n| protected | √      | √     | √     | ×      |\n| default   | √      | √     | ×     | ×      |\n| private   | √      | ×     | ×     | ×      |\n\n- 类的成员不写访问修饰时默认为 default。默认对于同一个包中的其他类相当于公开（public），对于不是同一个包中的其他类相当于私有（private）。\n- 受保护（protected）对子类相当于公开，对不是同一包中的没有父子关系的类相当于私有。\n- Java 中，外部类的修饰符只能是 public 或默认，类的成员（包括内部类）的修饰符可以是以上四种。 \n\n\n\n## 8. 深拷贝与浅拷贝\n\n- **浅拷贝**：被复制对象的所有变量都含有与原来的对象相同的值，而所有的对其他对象的引用仍然指向原来的对象。换言之，浅拷贝仅仅复制所拷贝的对象，而不复制它所引用的对象。\n\n<div align=\"center\"> <img src=\"assets/shadow_copy2.jpg\" width=\"550\"/></div>\n\n\n\n- **深拷贝**：对基本数据类型进行值传递，对引用数据类型，创建一个新的对象，并复制其内容，此为深拷贝。\n\n<div align=\"center\"> <img src=\"assets/deep_copy2.jpg\" width=\"550\"/></div>\n\n\n\n参考资料：\n\n- [细说 Java 的深拷贝和浅拷贝 - 承香墨影 - SegmentFault 思否](https://segmentfault.com/a/1190000010648514)\n- [（基础系列）object clone 的用法、原理和用途 - 掘金](https://juejin.im/post/59bfc707f265da0646188bca)\n\n## 9. Lamda表达式\n\n\nLambda 表达式，也可称为闭包，它是推动 Java 8 发布的最重要新特性。\n\nLambda 允许把函数作为一个方法的参数（函数作为参数传递进方法中）。\n\n使用 Lambda 表达式可以使代码变的更加简洁紧凑。\n\n### 语法\n\nlambda 表达式的语法格式如下：\n\n```java\n(parameters) -> expression\n或\n(parameters) -> { statements; }\n```\n\n以下是 lambda 表达式的重要特征:\n\n- **可选类型声明：**不需要声明参数类型，编译器可以统一识别参数值。\n- **可选的参数圆括号：**一个参数无需定义圆括号，但多个参数需要定义圆括号。\n- **可选的大括号：**如果主体包含了一个语句，就不需要使用大括号。\n- **可选的返回关键字：**如果主体只有一个表达式返回值则编译器会自动返回值，大括号需要指定明表达式返回了一个数值。\n\n### Lambda 表达式实例\n\nLambda 表达式的简单例子:\n\n```java\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在 Java8Tester.java 文件输入以下代码：\n\n```java\npublic class Java8Tester {\n   public static void main(String args[]){\n      Java8Tester tester = new Java8Tester();\n        \n      // 类型声明\n      MathOperation addition = (int a, int b) -> a + b;\n        \n      // 不用类型声明\n      MathOperation subtraction = (a, b) -> a - b;\n        \n      // 大括号中的返回语句\n      MathOperation multiplication = (int a, int b) -> { return a * b; };\n        \n      // 没有大括号及返回语句\n      MathOperation division = (int a, int b) -> a / b;\n        \n      System.out.println(\"10 + 5 = \" + tester.operate(10, 5, addition));\n      System.out.println(\"10 - 5 = \" + tester.operate(10, 5, subtraction));\n      System.out.println(\"10 x 5 = \" + tester.operate(10, 5, multiplication));\n      System.out.println(\"10 / 5 = \" + tester.operate(10, 5, division));\n        \n      // 不用括号\n      GreetingService greetService1 = message ->\n      System.out.println(\"Hello \" + message);\n        \n      // 用括号\n      GreetingService greetService2 = (message) ->\n      System.out.println(\"Hello \" + message);\n        \n      greetService1.sayMessage(\"Runoob\");\n      greetService2.sayMessage(\"Google\");\n   }\n    \n   interface MathOperation {\n      int operation(int a, int b);\n   }\n    \n   interface GreetingService {\n      void sayMessage(String message);\n   }\n    \n   private int operate(int a, int b, MathOperation mathOperation){\n      return mathOperation.operation(a, b);\n   }\n}\n```\n\n执行以上脚本，输出结果为：\n\n```shell\n$ javac Java8Tester.java \n$ java Java8Tester\n10 + 5 = 15\n10 - 5 = 5\n10 x 5 = 50\n10 / 5 = 2\nHello Runoob\nHello Google\n```\n使用 Lambda 表达式需要注意以下两点：\n\n- Lambda 表达式主要用来定义行内执行的方法类型接口，例如，一个简单方法接口。在上面例子中，我们使用各种类型的 Lambda 表达式来定义 MathOperation 接口的方法。然后我们定义了 sayMessage 的执行。\n- Lambda 表达式免去了使用匿名方法的麻烦，并且给予 Java 简单但是强大的函数化的编程能力。\n\n### 变量作用域\n\nlambda 表达式只能引用标记了 final 的外层局部变量，这就是说不能在 lambda 内部修改定义在域外的局部变量，否则会编译错误。\n\n在 Java8Tester.java 文件输入以下代码：\n\n```java\npublic class Java8Tester {\n \n   final static String salutation = \"Hello! \";\n   \n   public static void main(String args[]){\n      GreetingService greetService1 = message -> \n      System.out.println(salutation + message);\n      greetService1.sayMessage(\"Runoob\");\n   }\n    \n   interface GreetingService {\n      void sayMessage(String message);\n   }\n}\n```\n\n执行以上脚本，输出结果为：\n\n```shell\n$ javac Java8Tester.java \n$ java Java8Tester\nHello! Runoob\n```\n\n我们也可以直接在 lambda 表达式中访问外层的局部变量：\n\n```java\npublic class Java8Tester {\n    public static void main(String args[]) {\n        final int num = 1;\n        Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));\n        s.convert(2);  // 输出结果为 3\n    }\n \n    public interface Converter<T1, T2> {\n        void convert(int i);\n    }\n}\n```\n\nlambda 表达式的局部变量可以不用声明为 final，但是必须不可被后面的代码修改（即隐性的具有 final 的语义）\n\n```java\nint num = 1;  \nConverter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));\ns.convert(2);\nnum = 5;  \n//报错信息：Local variable num defined in an enclosing scope must be final or effectively \n final\n```\n\n在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。\n\n```java\nString first = \"\"; \n\n//编译会出错\nComparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length()); \n```\n\n\n\n## 10. 字符串常量池\n\n　　Java 中字符串对象创建有两种形式，一种为字面量形式，如 `String str = \"abc\";`，另一种就是使用 new 这种标准的构造对象的方法，如 `String str = new String(\"abc\");`，这两种方式我们在代码编写时都经常使用，尤其是字面量的方式。然而**这两种实现其实存在着一些性能和内存占用的差别**。这一切都是源于 JVM 为了减少字符串对象的重复创建，其维护了一个特殊的内存，这段内存被成为**字符串常量池**或者**字符串字面量池**。\n\n**工作原理**\n\n　　当代码中出现字面量形式创建字符串对象时，JVM首先会对这个字面量进行检查，如果字符串常量池中存在相同内容的字符串对象的引用，则将这个引用返回，否则新的字符串对象被创建，然后将这个引用放入字符串常量池，并返回该引用。\n\n```java\npublic class Test {\n    public static void main(String[] args) {\n\n        String s1 = \"abc\";\n        String s2 = \"abc\";\n\n        // 以上两个局部变量都存在了常量池中\n        System.out.println(s1 == s2); // true\n\n\n        // new出来的对象不会放到常量池中,内存地址是不同的\n        String s3 = new String();\n        String s4 = new String();\n\n        /**\n     \t* 字符串的比较不可以使用双等号,这样会比较内存地址\n     \t* 字符串比较应当用equals,可见String重写了equals\n     \t*/\n        System.out.println(s3 == s4); // false\n        System.out.println(s3.equals(s4)); // true\n    }\n}\n```\n\n\n\n## 11. 解释型语言与编译型语言的区别\n\n　　我们使用工具编写的字母加符号的代码，是我们能看懂的高级语言，计算机无法直接理解，计算机需要先对我们编写的代码翻译成计算机语言，才能执行我们编写的程序。\n\n　　将高级语言翻译成计算机语言有编译，解释两种方式。两种方式只是翻译的时间不同。\n\n**1.  编译型语言**\n\n　　编译型语言写得程序在执行之前，需要借助一个程序，将高级语言编写的程序翻译成计算机能懂的机器语言，然后，这个机器语言就能直接执行了，也就是我们常见的（exe文件）。\n\n**2.  解释型语言**\n\n　　解释型语言的程序不需要编译，节省了一道工序，不过解释型的语言在运行的时候需要翻译，每个语句都是执行的时候才翻译，对比编译型语言，效率比较低。通俗来讲，就是借助一个程序，且这个程序能试图理解编写的代码，然后按照编写的代码中的要求执行。\n\n**3.  脚本语言**\n\n　　脚本语言也是一种解释型语言，又被称为扩建的语言，或者动态语言不需要编译，可以直接使用，由解释器来负责解释。\n\n脚本语言一般都是以文本形式存在，类似于一种命令。\n\n**4.  通俗理解编译型语言和解释型语言**\n\n　　同行讨论编译型语言和解释型语言的时候，这么说过，编译型语言相当于做一桌子菜再吃，解释型语言就是吃火锅。解释型的语言执行效率低，类似火锅需要一边煮一边吃。\n\n\n\n\n\n# 二、面向对象\n\n## 1. Java的四个基本特性，对多态的理解，在项目中哪些地方用到多态\n\n- **Java的四个基本特性**\n  - **抽象**：抽象是将一类对象的共同特征总结出来构造类的过程，包括<u>数据抽象</u>和<u>行为抽象</u>两方面。抽象只关注对象有哪些属性和行为，并不关注这些行为的细节是什么。  \n  - **封装**：通常认为封装是把数据和操作数据的方法绑定起来，对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装；我们编写一个类就是对数据和数据操作的封装。可以说，封装就是隐藏一切可隐藏的东西，只向外界提供最简单的编程接口。 \n  - **继承**：继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类（超类、基类）；得到继承信息的类被称为子类（派生类）。继承让变化中的软件系统有了一定的延续性，同时继承也是封装程序中可变因素的重要手段。\n  - **多态**：多态性是指允许不同子类型的对象对同一消息作出不同的响应。 \n- **多态的理解(多态的实现方式)** \n  - **方法重载**（overload）：实现的是**编译时的多态性**（也称为前绑定）。 \n  - **方法重写**（override）：实现的是**运行时的多态性**（也称为后绑定）。运行时的多态是面向对象最精髓的东西。 \n  - 要实现多态需要做两件事：\n    - 1)  **方法重写**（子类继承父类并重写父类中已有的或抽象的方法）；\n    - 2)  **对象造型**（用父类型引用引用子类型对象，这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为）。 \n- **项目中对多态的应用** \n  - 举一个简单的例子，在物流信息管理系统中，有两种用户：订购客户和卖房客户，两个客户都可以登录系统，他们有相同的方法 Login，但登陆之后他们会进入到不同的页面，也就是在登录的时候会有不同的操作，两种客户都继承父类的 Login 方法，但对于不同的对象，拥有不同的操作。 \n- **面相对象开发方式优点（B65）**\n  - 较高的**开发效率**：可以把事物进行抽象，映射为开发的对象。\n  - 保证软件的**鲁棒性**：高重用性，可以重用已有的而且在相关领域经过长期测试的代码。\n  - 保证软件的**高可维护性**：代码的可读性非常好，设计模式也使得代码结构清晰，拓展性好。\n\n\n\n## 2. 什么是重载和重写\n\n- **重载**：重载发生在同一个类中，同名的方法如果有不同的参数列表（参数类型不同、参数个数不同或者二者都不同）则视为重载。 \n- **重写**：重写发生在子类与父类之间，重写要求子类被重写方法与父类被重写方法有相同的返回类型，比父类被重写方法更好访问，不能比父类被重写方法声明更多的异常（里氏代换原则）。根据不同的子类对象确定调用的那个方法。 \n\n <div align=\"center\"> <img src=\"assets/overloading-vs-overriding.png\" width=\"700\"/></div>\n\n\n\n\n## 3. 面向对象和面向过程的区别？用面向过程可以实现面向对象吗？\n\n- 面向对象和面向过程的区别 \n  - **面向过程**就像是一个细心的管家，事无具细的都要考虑到。而**面向对象**就像是个家用电器，你只需要知道他的功能，不需要知道它的工作原理。 \n  - **面向过程**是一种是“事件”为中心的编程思想。就是分析出解决问题所需的步骤，然后用函数把这些步骤实现，并按顺序调用。**面向对象**是以“对象”为中心的编程思想。 \n  - 简单的举个例子：汽车发动、汽车到站 \n    - 这对于 **面向过程** 来说，是两个事件，汽车启动是一个事件，汽车到站是另一个事件，**面向过程**编程的过程中我们关心的是事件，而不是汽车本身。针对上述两个事件，形成两个函数，之 后依次调用。（事件驱动，动词为主）\n    - 然而这对于**面向对象**来说，我们关心的是汽车这类对象，两个事件只是这类对象所具有的行为。而且对于这两个行为的顺序没有强制要求。（对象驱动，名词为主，将问题抽象出具体的对象，而这个对象有自己的属性和方法，在解决问题的时候是将不同的对象组合在一起使用）\n- 用面向过程可以实现面向对象吗 ？\n  - 如果是 C 语言来展现出面向对象的思想，C 语言中是不是有个叫结构体的东西，这个里面有自己定义的变量 可以通过函数指针就可以实现对象\n\n\n\n## 4. 面向对象开发的六个基本原则，在项目中用过哪些原则\n\n- **六个基本原则**（参考《设计模式之禅》）\n\n  - **单一职责**（Single Responsibility Principle 简称 SRP）：**一个类应该仅有一个引起它变化的原因**。在面向对象中，如果只让一个类完成它该做的事，而不涉及与它无关的领域就是践行了高内聚的原则，这个类就只有单一职责。 \n\n  - **里氏替换**（Liskov Substitution Principle 简称 LSP）：**任何时候子类型能够替换掉它们的父类型**。子类一定是增加父类的能力而不是减少父类的能力，因为子类比父类的能力更多，把能力多的对象当成能力少的对象来用当然没有任何问题。 \n\n  - **依赖倒置**（Dependence Inversion Principle 简称 DIP）：**要依赖于抽象，不要依赖于具体类**。要做到依赖倒置，应该做到：①高层模块不应该依赖底层模块，二者都应该依赖于抽象；②抽象不应该依赖于具体实现，具体实现应该依赖于抽象。\n\n  - **接口隔离**（Interface Segregation Principle 简称 ISP）：**不应该强迫客户依赖于他们不用的方法** 。接口要小而专，绝不能大而全。臃肿的接口是对接口的污染，既然接口表示能力，那么一个接口只应该描述一种能力，接口也应该是高度内聚的。 \n\n  - **最少知识原则**（Least Knowledge Principle 简称 LKP）：**只和你的朋友谈话**。迪米特法则又叫最少知识原则，一个对象应当对其他对象有尽可能少的了解。 \n\n  - **开闭原则**（Open Closed Principle 简称 OCP）：**软件实体应当对扩展开放，对修改关闭**。要做到开闭有两个要点：①抽象是关键，一个系统中如果没有抽象类或接口系统就没有扩展点；②封装可变性，将系统中的各种可变因素封装到一个继承结构中，如果多个可变因素混杂在一起，系统将变得复杂而换乱。 \n\n- 其他原则\n\n  - 合成聚和复用：优先使用聚合或合成关系复用代码\n  - 面向接口编程\n  - 优先使用组合，而非继承\n  - 一个类需要的数据应该隐藏在类的内部\n  - 类之间应该零耦合，或者只有传导耦合，换句话说，类之间要么没关系，要么只使用另一个类的接口提供的操作\n  - 在水平方向上尽可能统一地分布系统功能\n\n\n\n- 项目中用到的原则 \n  - 单一职责、开放封闭、合成聚合复用(最简单的例子就是String类)、接口隔离\n\n\n\n## 5. 内部类有哪些\n\n可以将一个类的定义放在另一个类的定义内部，这就是内部类。\n\n在 Java 中内部类主要分为成员内部类、局部内部类、匿名内部类、静态内部类\n\n### （一）成员内部类\n\n成员内部类也是最普通的内部类，它是外围类的一个成员，所以他是可以**无限制的访问外围类的所有成员属性和方法，尽管是private的**，但是外围类要访问内部类的成员属性和方法则需要通过内部类实例来访问。\n\n```java\npublic class OuterClass {\n    private String str;\n   \n    public void outerDisplay(){\n        System.out.println(\"outerClass...\");\n    }\n    \n    public class InnerClass{\n        public void innerDisplay(){\n            str = \"chenssy...\"; //使用外围内的属性\n            System.out.println(str);\n            outerDisplay();  //使用外围内的方法\n        }\n    }\n    \n    // 推荐使用getxxx()来获取成员内部类，尤其是该内部类的构造函数无参数时\n    public InnerClass getInnerClass(){\n        return new InnerClass();\n    }\n    \n    public static void main(String[] args) {\n        OuterClass outer = new OuterClass();\n        OuterClass.InnerClass inner = outer.getInnerClass();\n        inner.innerDisplay();\n    }\n}\n--------------------\nchenssy...\nouterClass...\n```\n\n在成员内部类中要注意两点：\n\n- 成员内部类中不能存在`static`方法, 但是可以存在`static`域, 前提是需要使用`final`关键字进行修饰.\n\n- 成员内部类是依附于外围类的，所以只有先创建了外围类才能够创建内部类。   \n\n\n\n### （二）局部内部类\n\n有这样一种内部类，它是嵌套在方法和作用于内的，对于这个类的使用主要是应用与解决比较复杂的问题，想创建一个类来辅助我们的解决方案，到那时又不希望这个类是公共可用的，所以就产生了局部内部类，局部内部类和成员内部类一样被编译，只是它的作用域发生了改变，它只能在该方法和属性中被使用，出了该方法和属性就会失效。 \n\n```java\n//定义在方法里：\npublic class Parcel5 {\n    public Destionation destionation(String str){\n        class PDestionation implements Destionation{\n            private String label;\n            private PDestionation(String whereTo){\n                label = whereTo;\n            }\n            public String readLabel(){\n                return label;\n            }\n        }\n        return new PDestionation(str);\n    }\n    \n    public static void main(String[] args) {\n        Parcel5 parcel5 = new Parcel5();\n        Destionation d = parcel5.destionation(\"chenssy\");\n    }\n}\n\n//定义在作用域内:\npublic class Parcel6 {\n    private void internalTracking(boolean b){\n        if(b){\n            class TrackingSlip{\n                private String id;\n                TrackingSlip(String s) {\n                    id = s;\n                }\n                String getSlip(){\n                    return id;\n                }\n            }\n            TrackingSlip ts = new TrackingSlip(\"chenssy\");\n            String string = ts.getSlip();\n        }\n    }\n    \n    public void track(){\n        internalTracking(true);\n    }\n    \n    public static void main(String[] args) {\n        Parcel6 parcel6 = new Parcel6();\n        parcel6.track();\n    }\n}\n```\n\n\n\n### （三）匿名内部类\n\n匿名内部类也就是没有名字的内部类。正因为没有名字，所以匿名内部类只能使用一次，它通常用来简化代码编写。但使用匿名内部类还有个前提条件：必须继承一个父类或实现一个接口\n\n\n\n**实例1：不使用匿名内部类来实现抽象方法**\n\n```java\nabstract class Person {\n    public abstract void eat();\n}\n \nclass Child extends Person {\n    public void eat() {\n        System.out.println(\"eat something\");\n    }\n}\n \npublic class Demo {\n    public static void main(String[] args) {\n        Person p = new Child();\n        p.eat();\n    }\n}\n```\n\n**运行结果**：eat something\n\n\n\n可以看到，我们用 Child 继承了 Person 类，然后实现了 Child 的一个实例，将其向上转型为 Person 类的引用\n\n但是，如果此处的 Child 类只使用一次，那么将其编写为独立的一个类岂不是很麻烦？\n\n这个时候就引入了匿名内部类\n\n \n\n**实例2：匿名内部类的基本实现**\n\n```java\nabstract class Person {\n    public abstract void eat();\n}\n \npublic class Demo {\n    public static void main(String[] args) {\n        Person p = new Person() {\n            public void eat() {\n                System.out.println(\"eat something\");\n            }\n        };\n        p.eat();\n    }\n}\n```\n\n**运行结果**：eat something\n\n\n\n可以看到，我们直接将抽象类 Person 中的方法在大括号中实现了，这样便可以省略一个类的书写，并且，匿名内部类还能用于接口上。\n\n\n\n**实例3：在接口上使用匿名内部类**\n\n```java\ninterface Person {\n    public void eat();\n}\n \npublic class Demo {\n    public static void main(String[] args) {\n        Person p = new Person() {\n            public void eat() {\n                System.out.println(\"eat something\");\n            }\n        };\n        p.eat();\n    }\n}\n```\n\n**运行结果**：eat something\n\n \n\n由上面的例子可以看出，只要一个类是抽象的或是一个接口，那么其子类中的方法都可以使用匿名内部类来实现\n\n最常用的情况就是在多线程的实现上，因为要实现多线程必须继承 Thread 类或是继承 Runnable 接口\n\n \n\n**实例4：Thread类的匿名内部类实现**\n\n```java\n\npublic class Demo {\n    public static void main(String[] args) {\n        Thread t = new Thread() {\n            public void run() {\n                for (int i = 1; i <= 5; i++) {\n                    System.out.print(i + \" \");\n                }\n            }\n        };\n        t.start();\n    }\n}\n```\n\n**运行结果**：1 2 3 4 5\n\n \n\n**实例5：Runnable接口的匿名内部类实现**\n\n```java\npublic class Demo {\n    public static void main(String[] args) {\n        Runnable r = new Runnable() {\n            public void run() {\n                for (int i = 1; i <= 5; i++) {\n                    System.out.print(i + \" \");\n                }\n            }\n        };\n        Thread t = new Thread(r);\n        t.start();\n    }\n}\n```\n\n**运行结果**：1 2 3 4 5\n\n\n\n### （四）静态内部类\n\n关键字 static 中提到 static 可以修饰成员变量、方法、代码块，其他它还可以修饰内部类，使用 static 修饰的内部类我们称之为静态内部类，不过我们更喜欢称之为嵌套内部类。静态内部类与非静态内部类之间存在一个最大的区别，我们知道非静态内部类在编译完成之后会隐含地保存着一个引用，该引用是指向创建它的外围内，但是静态内部类却没有。\n\n1. 它的创建是不需要依赖于外围类的。\n\n2. 它不能使用任何外围类的非 static 成员变量和方法。\n\n```java\npublic class OuterClass {\n    private String sex;\n    public static String name = \"chenssy\";\n    \n    // 静态内部类 \n    static class InnerClass1{\n        // 在静态内部类中可以存在静态成员\n        public static String _name1 = \"chenssy_static\";\n        \n        public void display(){ \n            // 静态内部类只能访问外围类的静态成员变量和方法\n\t\t   // 不能访问外围类的非静态成员变量和方法\n            System.out.println(\"OutClass name :\" + name);\n        }\n    }\n    \n\n    // 非静态内部类\n    class InnerClass2{\n        // 非静态内部类中不能存在静态成员\n        public String _name2 = \"chenssy_inner\";\n        // 非静态内部类中可以调用外围类的任何成员,不管是静态的还是非静态的\n        public void display(){\n            System.out.println(\"OuterClass name：\" + name);\n        }\n    }\n    \n    // 外围类方法\n    public void display(){\n        // 外围类访问静态内部类：内部类\n        System.out.println(InnerClass1._name1);\n        // 静态内部类 可以直接创建实例不需要依赖于外围类\n        new InnerClass1().display();\n        \n        // 非静态内部的创建需要依赖于外围类\n        OuterClass.InnerClass2 inner2 = new OuterClass().new InnerClass2();\n        // 方位非静态内部类的成员需要使用非静态内部类的实例\n        System.out.println(inner2._name2);\n        inner2.display();\n    }\n    \n    public static void main(String[] args) {\n        OuterClass outer = new OuterClass();\n        outer.display();\n    }\n}\n----------------\nOutput:\nchenssy_static\nOutClass name :chenssy\nchenssy_inner\nOuterClass name：chenssy\n```\n\n\n\n## 6. 组合、继承和代理的区别\n\n### 定义\n\n- 组合：在新类中 new 另外一个类的对象，以添加该对象的特性。\n- 继承：从基类继承得到子类，获得父类的特性。\n- 代理：在代理类中创建某功能的类，调用类的一些方法以获得该类的部分特性。\n\n### 使用场合\n\n- 组合：各部件之间没什么关系，只需要组合即可。例如组装电脑，需要 new CPU(),new RAM(),new Disk()\n\n  ```java\n  public class Computer {\n      public Computer() {\n          CPU cpu=new CPU();\n          RAM ram=new RAM();\n          Disk disk=new Disk();\n      }\n  }\n  class CPU{    }\n  class RAM{    }\n  class Disk{    }\n  ```\n\n- 继承：子类需要具有父类的功能，各子类之间有所差异。例如 Shape 类作为父类，子类有 Rectangle，CirCle，Triangle……代码不写了，大家都经常用。\n\n- 代理：飞机控制类，我不想暴露太多飞机控制的功能，只需部分前进左右转的控制（而不需要暴露发射导弹功能）。通过在代理类中 new 一个飞机控制对象，然后在方法中添加飞机控制类的各个需要暴露的功能。\n\n  ```java\n  public class PlaneDelegation{    \n      private PlaneControl planeControl;    //private外部不可访问\n  \t\n      // 飞行员权限代理类，普通飞行员不可以开火\n      PlaneDelegation(){\n          planeControl = new PlaneControl();\n      }\n      public void speed(){\n          planeControl.speed();\n      }\n      public void left(){\n          planeControl.left();\n      }\n      public void right(){\n          planeControl.right();\n      }\n  }\n  \n  final class PlaneControl {// final表示不可继承，控制器都能继承那还得了\n      protected void speed() {}\n      protected void fire() {}\n      protected void left() {}\n      protected void right() {}\n  }\n  ```\n\n**说明：**\n\n- 继承：代码复用，引用不灵活；\n- 组合：代码复用，\n- 接口：引用灵活； \n- 推荐组合+接口使用，看 IO 中包装流 FilterInputStream 中的策略模式\n\n\n\n## 7. 什么是构造函数\n\n构造函数是函数的一种特殊形式。特殊在哪里？构造函数中不需要定义返回类型（void 是无需返回值的意思，请注意区分两者），且构造函数的名称与所在的类名完全一致，其余的与函数的特性相同，可以带有参数列表，可以存在函数的重载现象。 \n\n一般用来初始化一些成员变量，当要生成一个类的对象（实例）的时候就会调用类的构造函数。如果不显示声明类的构造方法，会自动生成一个默认的不带参数的空的构造函数。\n\n```java\npublic class Demo{\n    private int num=0;\n\n    //无参构造函数\n    Demo()\n    {\n        System.out.println(\"constractor_run\");\n    }\n\n    //有参构造函数\n    Demo(int num)\n    {\n        System.out.println(\"constractor_args_run\");\n    }\n\n    //普通成员函数\n    public void demoFunction()\n    {\n        System.out.println(\"function_run\");\n    }\n}\n```\n\n在这里要说明一点，如果在类中我们不声明构造函数，JVM 会帮我们默认生成一个空参数的构造函数；如果在类中我们声明了带参数列表的构造函数，JVM 就不会帮我们默认生成一个空参数的构造函数，我们想要使用空参数的构造函数就必须自己去显式的声明一个空参的构造函数。 \n\n\n\n**构造函数的作用**\n\n　　通过开头的介绍，构造函数的轮廓已经渐渐清晰，那么为什么会有构造函数呢？构造函数有什么作用？构造函数是面向对象编程思想所需求的，它的主要作用有以下两个：\n\n- **创建对象**。任何一个对象创建时，都需要初始化才能使用，所以任何类想要创建实例对象就必须具有构造函数。\n- **对象初始化**。构造函数可以对对象进行初始化，并且是给与之格式（参数列表）相符合的对象初始化，是具有一定针对性的初始化函数。\n\n\n\n\n\n## 8. 向上造型和向下造型\n\n父类引用能指向子类对象，子类引用不能指向父类对象；\n\n**向上造型**\n\n父类引用指向子类对象，例如：\n\n```java\nFather f1 = new Son();\n```\n\n**向下造型**\n\n把指向子类对象的父类引用赋给子类引用，需要强制转换，例如：\n\n```java\nFather f1 = new Son();\nSon s1 = (Son)f1;\n```\n\n但有运行出错的情况：\n\n```java\nFather f2 = new Father();\nSon s2 = (Son)f2; //编译无错但运行会出现错误\n```\n\n在不确定父类引用是否指向子类对象时，可以用 instanceof 来判断：\n\n```java\nif(f3 instanceof Son){\n     Son s3 = (Son)f3;\n}\n```\n\n\n\n# 三、关键字\n\n## 1. final与static的区别\n\n### final\n\n- **1. 数据**\n\n  - 声明数据为常量，可以是编译时常量，也可以是在运行时被初始化后不能被改变的常量。\n\n  - 对于基本类型，final 使数值不变；\n  - 对于引用类型，final 使引用不变，也就不能引用其它对象，但是被引用的对象本身是可以修改的。\n\n```java\nfinal int x = 1;\n// x = 2;  // cannot assign value to final variable 'x'\nfinal A y = new A();\ny.a = 1;\n```\n\n- **2. 方法**\n\n  - 声明方法不能被子类覆盖。\n    - private 方法隐式地被指定为 final，如果在子类中定义的方法和基类中的一个 private 方法签名相同，此时子类的方法不是覆盖基类方法，而是在子类中定义了一个新的方法。\n\n- **3. 类**\n  - 声明类不允许被继承。\n\n### static\n\n- **1. 静态变量**\n\n     静态变量在内存中只存在一份，只在类初始化时赋值一次。\n\n       - 静态变量：类所有的实例都共享静态变量，可以直接通过类名来访问它；\n       - 实例变量：每创建一个实例就会产生一个实例变量，它与该实例同生共死。\n\n```java\npublic class A {\n    private int x;        // 实例变量\n    public static int y;  // 静态变量\n}\n```\n\n　　注意：不能再成员函数内部定义static变量。\n\n- **2. 静态方法**\n\n  静态方法在类加载的时候就存在了，它不依赖于任何实例，所以静态方法必须有实现，也就是说它不能是抽象方法（abstract）。\n\n- **3. 静态语句块**\n\n  静态语句块在类初始化时运行一次。\n\n- **4. 静态内部类**\n\n  内部类的一种，静态内部类不依赖外部类，且不能访问外部类的非静态的变量和方法。\n\n- **5. 静态导包** \n\n```java\nimport static com.xxx.ClassName.*\n```\n\n　　在使用静态变量和方法时不用再指明 ClassName，从而简化代码，但可读性大大降低。\n\n- **6. 变量赋值顺序** \n\n  静态变量的赋值和静态语句块的运行优先于实例变量的赋值和普通语句块的运行，静态变量的赋值和静态语句块的运行哪个先执行取决于它们在代码中的顺序。\n\n```java\npublic static String staticField = \"静态变量\";\n```\n\n```java\nstatic {\n    System.out.println(\"静态语句块\");\n}\n```\n\n```java\npublic String field = \"实例变量\";\n```\n\n```java\n{\n    System.out.println(\"普通语句块\");\n}\n```\n\n最后才运行构造函数\n\n```java\npublic InitialOrderTest() {\n    System.out.println(\"构造函数\");\n}\n```\n\n存在继承的情况下，初始化顺序为：\n\n- 父类（静态变量、静态语句块）\n- 子类（静态变量、静态语句块）\n- 父类（实例变量、普通语句块）\n- 父类（构造函数）\n- 子类（实例变量、普通语句块）\n- 子类（构造函数）\n\n\n\n## 2. break、continue、return\n\n### break\n\n跳出当前循环；但是如果是嵌套循环，则只能跳出当前的这一层循环，只有逐层 break 才能跳出所有循环。\n\n```java\nfor (int i = 0; i < 10; i++) {\n    // 在执行i==6时强制终止循环，i==6不会被执行\n    if (i == 6)\n        break;\n    System.out.println(i);  \n}  \n\n输出结果为0 1 2 3 4 5 ；6以后的都不会输出\n```\n\n### continue\n\n终止当前循环，但是不跳出循环（在循环中 continue 后面的语句是不会执行了），继续往下根据循环条件执行循环。 \n\n```java\nfor (int i = 0; i < 10; i++) {  \n    // i==6不会被执行，而是被中断了    \n    if (i == 6)\n        continue;\n    System.out.println(i);  \n}\n\n输出结果为0 1 2 3 4 5 7 8 9； 只有6没有输出\n```\n\n### return\n\n- return 从当前的方法中退出，返回到该调用的方法的语句处，继续执行。 \n- return 返回一个值给调用该方法的语句，返回值的数据类型必须与方法的声明中的返回值的类型一致。 \n- return 后面也可以不带参数，不带参数就是返回空，其实主要目的就是用于想中断函数执行，返回调用函数处。\n\n特别注意：返回值为 void 的方法，从某个判断中跳出，必须用 return。\n\n\n\n\n\n## 3. final、finally和finalize区别\n\n### final\n\nfinal 用于声明属性、方法和类，分别表示属性不可变、方法不可覆盖和类不可被继承。\n\n- final 属性：被final修饰的变量不可变（引用不可变）\n- final 方法：不允许任何子类重写这个方法，但子类仍然可以使用这个方法\n- final 参数：用来表示这个参数在这个函数内部不允许被修改\n- final 类：此类不能被继承，所有方法都不能被重写\n\n\n\n### finally\n\n　　在异常处理的时候，提供 finally 块来执行任何的清除操作。如果抛出一个异常，那么相匹配的 catch 字句就会执行，然后控制就会进入 finally 块，前提是有 finally 块。例如：数据库连接关闭操作上\n\n　　finally 作为异常处理的一部分，它只能用在 try/catch 语句中，并且附带一个语句块，表示这段语句最终一定会被执行（不管有没有抛出异常），经常被用在需要释放资源的情况下。（×）（**这句话其实存在一定的问题，还没有深入了解，欢迎大家在 issue 中提出自己的见解）** \n\n- 异常情况说明：\n  - 在执行 try 语句块之前已经返回或抛出异常，所以 try 对应的 finally 语句并没有执行。 \n  - 我们在 try 语句块中执行了 System.exit (0) 语句，终止了 Java 虚拟机的运行。那有人说了，在一般的 Java 应用中基本上是不会调用这个 System.exit(0) 方法的 \n  - 当一个线程在执行 try 语句块或者 catch 语句块时被打断（interrupted）或者被终止（killed），与其相对应的 finally 语句块可能不会执行 \n  - 还有更极端的情况，就是在线程运行 try 语句块或者 catch 语句块时，突然死机或者断电，finally 语句块肯定不会执行了。可能有人认为死机、断电这些理由有些强词夺理，没有关系，我们只是为了说明这个问题。 \n\n\n\n### finalize\n\n　　finalize() 是 Object 中的方法，当垃圾回收器将要回收对象所占内存之前被调用，即当一个对象被虚拟机宣告死亡时会先调用它 finalize() 方法，让此对象处理它生前的最后事情（这个对象可以趁这个时机挣脱死亡的命运）。要明白这个问题，先看一下虚拟机是如何判断一个对象该死的。\n\n　　可以覆盖此方法来实现对其他资源的回收，例如关闭文件。\n\n\n\n#### 判定死亡\n\n　　Java 采用可达性分析算法来判定一个对象是否死期已到。Java中以一系列 \"GC  Roots\" 对象作为起点，如果一个对象的引用链可以最终追溯到 \"GC  Roots\" 对象，那就天下太平。\n\n　　否则如果只是A对象引用B，B对象又引用A，A B引用链均未能达到 \"GC  Roots\" 的话，那它俩将会被虚拟机宣判符合死亡条件，具有被垃圾回收器回收的资格。\n\n\n\n#### 最后的救赎\n\n上面提到了判断死亡的依据，但被判断死亡后，还有生还的机会。\n\n如何自我救赎：\n\n1. 对象覆写了 finalize() 方法（这样在被判死后才会调用此方法，才有机会做最后的救赎）；\n\n2. 在 finalize() 方法中重新引用到 \"GC  Roots\" 链上（如把当前对象的引用 this 赋值给某对象的类变量/成员变量，重新建立可达的引用）.\n\n需要注意：\n\n　　finalize() 只会在对象内存回收前被调用一次 (The finalize method is never invoked more than once by a Java virtual machine for any given object. )\n\n　　finalize() 的调用具有不确定性，只保证方法会调用，但不保证方法里的任务会被执行完（比如一个对象手脚不够利索，磨磨叽叽，还在自救的过程中，被杀死回收了）。\n\n\n\n#### finalize()的作用 \n\n　　虽然以上以对象救赎举例，但 finalize() 的作用往往被认为是用来做最后的资源回收。\n　　基于在自我救赎中的表现来看，此方法有很大的不确定性（不保证方法中的任务执行完）而且运行代价较高。所以用来回收资源也不会有什么好的表现。\n\n　　综上：finalize() 方法并没有什么鸟用。\n\n　　至于为什么会存在一个鸡肋的方法：书中说 “它不是 C/C++ 中的析构函数，而是 Java 刚诞生时为了使 C/C++ 程序员更容易接受它所做出的一个妥协”。\n\n\n\n参考资料：\n\n- [关于finalize() - CSDN博客](https://blog.csdn.net/L_wwbs/article/details/70770447?locationNum=1&fps=1)\n\n\n\n## 4. assert有什么作用\n\n　　断言（assert）作为一种软件调试的方法，提供了一种在代码中进行正确性检查的机制，目前很多开发语言都支持这种机制。\n\n　　在实现中，assertion 就是在程序中的一条语句，它对一个 boolean 表达式进行检查，一个正确程序必须保证这个 boolean 表达式的值为 true；如果该值为 false，说明程序已经处于不正确的状态下，系统将给出警告并且退出。一般来说，assertion 用于保证程序最基本、关键的正确性。**assertion 检查通常在开发和测试时开启**。为了提高性能，**在软件发布后，assertion 检查通常是关闭的**。下面简单介绍一下 Java 中 assertion 的实现。\n\n　　在语法上，为了支持 assertion，Java 增加了一个关键字 assert。它包括两种表达式，分别如下：\n\n　　**assert <boolean表达式>**\n\n　　如果 <boolean表达式> 为 true，则程序继续执行。\n\n　　如果为 false，则程序抛出 AssertionError，并终止执行。\n\n \n\n　　**assert <boolean表达式> : <错误信息表达式>**\n\n　　如果 <boolean表达式> 为 true，则程序继续执行。\n\n　　如果为 false，则程序抛出 java.lang.AssertionError，并输入<错误信息表达式>。\n\n```java\npublic static void main(String[] args) {\n    System.out.println(\"123\");\n\n    int a = 0;\n    int b = 1;\n    assert a == b; //需显示开启，默认为不开启状态 \n    assert a == b : \"执行失败！\";\n\n    System.out.println(\"1234\");\n}\n```\n\nassert 的应用范围很多，主要包括：\n\n- 检查控制流\n- 检查输入参数是否有效\n- 检查函数结果是否有效\n- 检查程序不变\n\n\n\n### 什么是断言\n\n> 断言是编程术语，表示为一些布尔表达式，程序员相信在程序中的某个特定点该表达式值为真，可以在任何时候启用和禁用断言验证，因此可以在测试时启用断言而在部署时禁用断言。同样，程序投入运行后，最终用户在遇到问题时可以重新启用断言。\n\n使用断言可以创建更稳定、品质更好且 不易于出错的代码。当需要在一个值为 `false` 时中断当前操作的话，可以使用断言。单元测试必须使用断言（Junit/JunitX）。\n\n### 常用断言方法\n\n| 断言                                                         | 描述                                                         |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n| void assertEquals([String message], expected value, actual value) | 断言两个值相等。值可能是类型有 int, short, long, byte, char or java.lang.Object. 第一个参数是一个可选的字符串消息 |\n| void assertTrue([String message], boolean condition)         | 断言一个条件为真                                             |\n| void assertFalse([String message],boolean condition)         | 断言一个条件为假                                             |\n| void assertNotNull([String message], java.lang.Object object) | 断言一个对象不为空(null)                                     |\n| void assertNull([String message], java.lang.Object object)   | 断言一个对象为空(null)                                       |\n| void assertSame([String message], java.lang.Object expected, java.lang.Object actual) | 断言，两个对象引用相同的对象                                 |\n| void assertNotSame([String message], java.lang.Object unexpected, java.lang.Object actual) | 断言，两个对象不是引用同一个对象                             |\n| void assertArrayEquals([String message], expectedArray, resultArray) | 断言预期数组和结果数组相等。数组的类型可能是 int, long, short, char, byte or java.lang.Object. |\n\n\n\n## 5. volatile\n\n> 每次都读错，美式发音：volatile /'vɑlətl/ adj. [化学] 挥发性的；不稳定的；爆炸性的；反复无常的 \n\n　　volatile 是一个**类型修饰符**（type specifier），它是被设计用来修饰被不同线程访问和修改的变量。在使用 volatile 修饰成员变量后，所有线程在任何时间所看到变量的值都是相同的。此外，使用 volatile 会组织编译器对代码的优化，因此会降低程序的执行效率。所以，除非迫不得已，否则，能不使用 volatile 就尽量不要使用 volatile。\n\n- 每次访问变量时，总是获取主内存的最新值\n- 每次修改变量后，立刻写回到主内存中\n\n <div align=\"center\"> <img src=\"assets/java-volatile.png\" width=\"400\"/></div>\n\n参考资料：\n\n- [理解java Volatile 关键字 - 个人文章 - SegmentFault 思否](https://segmentfault.com/a/1190000015087945)\n  \n\n\n\n## 6. instanceof\n\ninstanceof 是 Java 的一个二元操作符，类似于 ==，>，< 等操作符。\n\ninstanceof 是 Java 的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例，返回 boolean 的数据类型。\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        Object testObject = new ArrayList();\n        displayObjectClass(testObject);\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    }\n}\n```\n\n\n\n## 7. strictfp\n\nstrictfp，即 **strict float point** (精确浮点)。 \n\nstrictfp 关键字可应用于类、接口或方法。使用 strictfp 关键字声明一个方法时，该方法中所有的 float 和 double 表达式都严格遵守 FP-strict 的限制,符合 IEEE-754 规范。当对一个类或接口使用 strictfp 关键字时，该类中的所有代码，包括嵌套类型中的初始设定值和代码，都将严格地进行计算。严格约束意味着所有表达式的结果都必须是 IEEE 754 算法对操作数预期的结果，以单精度和双精度格式表示。\n\n如果你想让你的浮点运算更加精确，而且不会因为不同的硬件平台所执行的结果不一致的话，可以用关键字strictfp.\n\n\n\n## 8. transient\n\n> transient 英 /'trænzɪənt/   adj. 短暂的；路过的  n. 瞬变现象；过往旅客；候鸟\n\n我们都知道一个对象只要实现了 Serilizable 接口，这个对象就可以被序列化，Java 的这种序列化模式为开发者提供了很多便利，我们可以不必关系具体序列化的过程，只要这个类实现了 Serilizable 接口，这个类的所有属性和方法都会自动序列化。\n\n然而在实际开发过程中，我们常常会遇到这样的问题，这个类的有些属性需要序列化，而其他属性不需要被序列化，打个比方，如果一个用户有一些敏感信息（如密码，银行卡号等），为了安全起见，不希望在网络操作（主要涉及到序列化操作，本地序列化缓存也适用）中被传输，这些信息对应的变量就可以加上 transient 关键字。换句话说，这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。\n\n**总之，Java 的 transient 关键字为我们提供了便利，你只需要实现 Serilizable 接口，将不需要序列化的属性前添加关键字transient，序列化对象的时候，这个属性就不会序列化到指定的目的地中。**\n\n\n\n参考资料：\n\n- [Java transient关键字使用小记 - Alexia(minmin) - 博客园](https://www.cnblogs.com/lanxuezaipiao/p/3369962.html)\n\n\n\n## 9. native\n\nnative（即 JNI，Java Native Interface），凡是一种语言，都希望是纯。比如解决某一个方案都喜欢就单单这个语言来写即可。Java 平台有个用户和本地 C 代码进行互操作的 API，称为 Java Native Interface (Java本地接口)。 \n\n<div align=\"center\"> <img src=\"assets/java-native-interface.png\" width=\"500\"/></div><br/>\n\n\n\n参考资料：\n\n- [java中native的用法 - 不止吧 - 博客园](https://www.cnblogs.com/b3051/p/7484501.html)\n\n\n\n# 四、基本数据类型与运算\n\n## 1. Java的基本数据类型和引用类型，自动装箱和拆箱\n\n- 4 类 8 种基本数据类型。4 整数型，2 浮点型，1 布尔型，1 字符型\n\n| 类型       | 存储 | 取值范围                                                     | 默认值          | 包装类    |\n| ---------- | ---- | ------------------------------------------------------------ | --------------- | --------- |\n| **整数型** |      |                                                              |                 |           |\n| byte       | 8    | 最大存储数据量是 255，最小 -2<sup>7</sup>，最大 2<sup>7</sup>-1，<br />[-128~127] | (byte) 0        | Byte      |\n| short      | 16   | 最大数据存储量是 65536，[-2<sup>15</sup>,2<sup>15</sup>-1]，<br />[-32768,32767]，±3万 | (short) 0       | Short     |\n| int        | 32   | 最大数据存储容量是 2<sup>31</sup>-1，<br />[-2<sup>31</sup>,2<sup>31</sup>-1]，±21亿，[ -2147483648, 2147483647] | 0               | Integer   |\n| long       | 64   | 最大数据存储容量是 2<sup>64</sup>-1，<br />[-2<sup>63</sup>,2<sup>63</sup>-1]， ±922亿亿（±（922+16个零）） | 0L              | Long      |\n| **浮点型** |      |                                                              |                 |           |\n| float      | 32   | 数据范围在 3.4e-45~1.4e38，直接赋值时必须在数字后加上 f 或 F | 0.0f            | Float     |\n| double     | 64   | 数据范围在 4.9e-324~1.8e308，赋值时可以加 d 或 D 也可以不加  | 0.0d            | Double    |\n| **布尔型** |      |                                                              |                 |           |\n| boolean    | 1    | true / flase                                                 | false           | Boolean   |\n| **字符型** |      |                                                              |                 |           |\n| char       | 16   | 存储 Unicode 码，用单引号赋值                                | '\\u0000' (null) | Character |\n\n- 引用数据类型\n  - 类（class）、接口（interface）、数组\n- 自动装箱和拆箱\n  - 基本数据类型和它对应的封装类型之间可以相互转换。自动拆装箱是 `jdk5.0` 提供的新特特性，它可以自动实现类型的转换\n  - **装箱**：从**基本数据类型**到**封装类型**叫做装箱\n  - **拆箱**：从**封装类型**到**基本数据类型**叫拆箱\n\n```java\n// jdk 1.5\npublic class TestDemo {\n    public static void main(String[] args) {\n        Integer m =10;\n        int i = m;\n    }\n}\n```\n\n　　上面的代码在 jdk1.4 以后的版本都不会报错，它实现了自动拆装箱的功能，如果是 jdk1.4，就得这样写了\n\n```java\n// jdk 1.4\npublic class TestDemo {\n    public static void main(String[] args) {\n        Integer b = new Integer(210);\n        int c = b.intValue();\n    }\n}\n```\n\n\n\n## 2. ValueOf缓存池\n\n　　new Integer(123)  与 Integer.valueOf(123)  的区别在于，new Integer(123)  每次都会新建一个对象，而 Integer.valueOf(123)  可能会使用缓存对象，因此多次使用 Integer.valueOf(123)  会取得同一个对象的引用。\n\n```java\nInteger x = new Integer(123);\nInteger y = new Integer(123);\nSystem.out.println(x == y);    // false\nInteger z = Integer.valueOf(123);\nInteger k = Integer.valueOf(123);\nSystem.out.println(z == k);   // true\n```\n\n　　编译器会在自动装箱过程调用 valueOf() 方法，因此多个 Integer 实例使用自动装箱来创建并且值相同，那么就会引用相同的对象。\n\n```java\nInteger m = 123;\nInteger n = 123;\nSystem.out.println(m == n); // true\n```\n\nvalueOf() 方法的实现比较简单，就是先判断值是否在缓存池中，如果在的话就直接使用缓存池的内容。\n\n```java\n// valueOf 源码实现\npublic 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在 Java 8 中，Integer 缓存池的大小默认为 -128\\~127。\n\n```java\nstatic final int low = -128;\nstatic final int high;\nstatic final Integer cache[];\n\nstatic {\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\nJava 还将一些其它基本类型的值放在缓冲池中，包含以下这些：\n\n- boolean values true and false\n- all byte values\n- short values between -128 and 127\n- int values between -128 and 127\n- char in the range \\u0000 to \\u007F\n\n因此在使用这些基本类型对应的包装类型时，就可以直接使用缓冲池中的对象。\n\n\n\n参考资料：\n\n- [StackOverflow : Differences between new Integer(123), Integer.valueOf(123) and just 123](https://stackoverflow.com/questions/9030817/differences-between-new-integer123-integer-valueof123-and-just-123)\n\n\n\n## 3. i++和++i有什么区别\n\n### i++\n\ni++ 是在程序执行完毕后进行自增，而 ++i 是在程序开始执行前进行自增。\n\n**i++ 的操作分三步**\n\n1. 栈中取出 i\n2. i 自增 1\n3. 将 i 存到栈\n\n三个阶段：内存到寄存器，寄存器自增，写回内存（这三个阶段中间都可以被中断分离开）\n\n所以 i++ 不是原子操作，上面的三个步骤中任何一个步骤同时操作，都可能导致 i 的值不正确自增\n\n\n\n### ++i\n\n在多核的机器上，CPU 在读取内存 i 时也会可能发生同时读取到同一值，这就导致两次自增，实际只增加了一次。\n\ni++ 和 ++i 都不是原子操作\n\n\n\n**原子性**：指的是一个操作是不可中断的。即使是在多个线程一起执行的时候，一个操作一旦开始，就不会被其他线程打断。\n\n\n\nJMM 三大特性：原子性，可见性，有序性。详情请阅读 Github 仓库：[Java 并发编程](03%20Java%20并发编程.md) 一文。\n\n\n\n## 4. 位运算符\n\nJava 定义了位运算符，应用于整数类型 (int)，长整型 (long)，短整型 (short)，字符型 (char)，和字节型 (byte)等类型。\n\n下表列出了位运算符的基本运算，假设整数变量A的值为60和变量B的值为13\n\nA（60）：0011 1100\n\nB（13）：0000 1101\n\n| 操作符 | 名称       | 描述                                                         | 例子                          |\n| ------ | ---------- | ------------------------------------------------------------ | ----------------------------- |\n| ＆     | 与         | 如果相对应位都是 1，则结果为 1，否则为 0                     | （A＆B）得到 12，即 0000 1100 |\n| \\|     | 或         | 如果相对应位都是 0，则结果为 0，否则为 1                     | （A\\|B）得到 61，即 0011 1101 |\n| ^      | 异或       | 如果相对应位值相同，则结果为 0，否则为 1                     | （A^B）得到49，即 0011 0001   |\n| 〜     | 非         | 按位取反运算符翻转操作数的每一位，即 0 变成 1，1 变成 0      | （〜A）得到-61，即1100 0011   |\n| <<     | 左移       | （左移一位乘2）按位左移运算符。左操作数按位左移右操作数指定的位数。左移 n 位表示原来的值乘 2<sup>n</sup> | A << 2得到240，即 1111 0000   |\n| >>     |            | （右移一位除2）有符号右移，按位右移运算符。左操作数按位右移右操作数指定的位数 | A >> 2得到15即 1111           |\n| >>>    | 无符号右移 | 无符号右移，按位右移补零操作符。左操作数的值按右操作数指定的位数右移，移动得到的空位以零填充 | A>>>2得到15即0000 1111        |\n\n\n\n## 5. 原码、补码、反码是什么\n\n### 机器数\n\n　　一个数在计算机中的二进制表示形式，叫做这个数的机器数。机器数是带符号的，在计算机用一个数的最高位存放符号，正数为 0，负数为 1。\n\n　　比如，十进制中的数 +3 ，计算机字长为 8 位，转换成二进制就是 00000011。如果是 -3 ，就是 10000011 。那么，这里的 00000011 和 10000011 就是机器数。\n\n\n\n### 真值\n\n　　因为第一位是符号位，所以机器数的形式值就不等于真正的数值。例如上面的有符号数 10000011，其最高位 1 代表负，其真正数值是 -3 而不是形式值 131（10000011 转换成十进制等于 131）。所以，为区别起见，将带符号位的机器数对应的真正数值称为机器数的真值。\n\n例：0000 0001 的真值 = +000 0001 = +1，1000 0001 的真值 = –000 0001 = –1\n\n\n\n### 原码\n\n　　原码就是符号位加上真值的绝对值，即用第一位表示符号，其余位表示值。比如如果是 8 位二进制:\n\n　　[+1]<sub>原</sub> = 0000 0001\n\n　　[-1]<sub>原</sub> = 1000 0001\n\n　　第一位是符号位。因为第一位是符号位，所以 8 位二进制数的取值范围就是：[1111 1111 , 0111 1111]，即：[-127 , 127]\n\n　　原码是人脑最容易理解和计算的表示方式\n\n\n\n### 反码\n\n反码的表示方法是：\n\n- **正数**的反码是其本身；\n- **负数**的反码是在其原码的基础上，**符号位不变，其余各个位取反**。\n\n[+1] = [00000001]<sub>原</sub> = [00000001]<sub>反</sub>\n\n[-1] = [10000001]<sub>原</sub>= [11111110]<sub>反</sub>\n\n可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算。\n\n\n\n### 补码\n\n补码的表示方法是：\n\n- **正数**的补码就是其本身；\n- **负数**的补码是在其原码的基础上，符号位不变，其余各位取反, 最后+1。(**反码的基础上 +1**)\n\n[+1] = [0000 0001]<sub>原</sub> = [0000 0001]<sub>反</sub> = [0000 0001]<sub>补</sub>\n\n[-1]  = [1000 0001]<sub>原</sub> = [1111 1110]<sub>反</sub> = [1111 1111]<sub>补</sub>\n\n对于负数，补码表示方式也是人脑无法直观看出其数值的。 通常也需要转换成原码在计算其数值。\n\n\n\n参考资料：\n\n- [原码, 反码, 补码 详解 - ziqiu.zhang - 博客园](http://www.cnblogs.com/zhangziqiu/archive/2011/03/30/ComputerCode.html)\n\n\n\n## 6. 不用额外变量交换两个整数的值\n\n如果给定整数 a 和 b，用以下三行代码即可交换 a 和b 的值\n\n```java\na = a ^ b;\nb = a ^ b;\na = a ^ b;\n```\n\n- 假设 a 异或 b 的结果记为 c，**c 就是 a 整数位信息和 b 整数位信息的所有不同信息**。\n  - 比如：a = 4 = 100，b = 3 = 011，a^b = c = 111\n- a 异或 c 的结果就是 b，比如：a = 4 = 100，c = 111，a^c = 011 = 3 = b\n- b 异或c 的结果就是 a，比如：b = 3 = 011，c = 111，b^c = 100 = 4 = a\n\n说明：位运算的题目基本上都带有靠经验积累才会做的特征，也就是准备阶段需要做足够多的题，面试时才会有良好的感觉。\n\n\n\n## 7. 不使用运算符进行a+b操作\n\n- a^b;  得到不含进位之和\n- (a & b)<<1;  进位\n- 只要进位不为零，则迭代；否则返回\n\n```java\n#include <stdio.h>\n\nint add(int a, int b)\n{\n    int c = a & b;\n    int r = a ^ b;\n    if(c == 0){\n        return r;\n    }\n    else{\n        return add(r, c << 1);\n    }\n}\n\nint main(int argn, char *argv[])\n{\n    printf(\"sum = %d\\n\", add(-10000, 56789));\n    return 0;\n}\n```\n\n\n\n## 8. &和&& 、|和||的区别\n\n（1）&& 和 & 都是表示与，区别是 && 只要第一个条件不满足，后面条件就不再判断。而 & 要对所有的条件都进行判断。\n\n```java\n// 例如：\npublic static void main(String[] args) {  \n    if((23!=23) && (100/0==0)){  \n        System.out.println(\"运算没有问题。\");  \n    }else{  \n        System.out.println(\"没有报错\");  \n    }  \n}  \n// 输出的是“没有报错”。而将 && 改为 & 就会如下错误：\n// Exception in thread \"main\" java.lang.ArithmeticException: / by zero\n```\n\n- 原因：\n  - &&时判断第一个条件为 false，后面的 100/0==0 这个条件就没有进行判断。\n\n  - & 时要对所有的条件进行判断，所以会对后面的条件进行判断，所以会报错。\n\n\n  （2）|| 和 | 都是表示 “或”，区别是 || 只要满足第一个条件，后面的条件就不再判断，而 | 要对所有的条件进行判断。 看下面的程序： \n\n```java\npublic static void main(String[] args) {  \n    if((23==23)||(100/0==0)){  \n    \tSystem.out.println(\"运算没有问题。\");  \n    }else{  \n    \tSystem.out.println(\"没有报错\");  \n    }  \n}\n// 此时输出“运算没有问题”。若将||改为|则会报错。\n```\n\n - 原因\n    - || 判断第一个条件为 true，后面的条件就没有进行判断就执行了括号中的代码\n    - 而 | 要对所有的条件进行判断，所以会报错\n\n\n\n\n\n# 五、字符串与数组\n\n## 1. String,StringBuffer,StringBuilder，以及对String不变性的理解\n\n- String、StringBuffer、StringBuilder \n  - 都是 final 类，都不允许被继承\n  - String 长度是不可变的，StringBuffer、StringBuilder 长度是可变的\n  - StringBuffer 是线程安全的，StringBuilder 不是线程安全的，但它们两个中的所有方法都是相同的，StringBuffer 在 StringBuilder 的方法之上添加了 synchronized 修饰，保证线程安全\n  - StringBuilder 比 StringBuffer 拥有更好的性能\n  - 如果一个 String 类型的字符串，在编译时就可以确定是一个字符串常量，则编译完成之后，字符串会自动拼接成一个常量。此时 String 的速度比 StringBuffer 和 StringBuilder 的性能好的多\n\n\n\n- String 不变性的理解 \n  - String 类是被 final 进行修饰的，不能被继承 \n  - 在用 + 号链接字符串的时候会创建新的字符串\n  - String s = new String(\"Hello world\"); 可能创建两个对象也可能创建一个对象。如果静态区中有 “Hello world” 字符串常量对象的话，则仅仅在堆中创建一个对象。如果静态区中没有 “Hello world” 对象，则堆上和静态区中都需要创建对象。 \n  - 在 Java 中, 通过使用 \"+\" 符号来串联字符串的时候,，实际上底层会转成通过 StringBuilder 实例的 append() 方法来实现。\n\n \n\n## 2. String有重写Object的hashcode和toString吗？如果重写equals不重写hashcode会出现什么问题？\n\n- String 有重写 Object 的 hashcode 和 toString吗？ \n\n  - String 重写了 Object 类的 hashcode 和 toString 方法。 \n\n- 当 equals 方法被重写时，通常有必要重写 hashCode 方法，以维护 hashCode 方法的常规协定，该协定声明相对等的两个对象必须有相同的 hashCode \n\n  - object1.equals(object2) 时为 true， object1.hashCode() ==  object2.hashCode() 为 true \n  - object1.hashCode() ==  object2.hashCode() 为 false 时，object1.equals(object2) 必定为 false \n  - object1.hashCode() ==  object2.hashCode() 为 true时，但 object1.equals(object2) 不一定定为 true \n\n- 重写 equals 不重写 hashcode 会出现什么问题 \n\n  - 在存储散列集合时(如 Set 类)，如果原对象.equals(新对象)，但没有对 hashCode 重写，即两个对象拥有不同的 hashCode，则在集合中将会存储两个值相同的对象，从而导致混淆。因此在重写 equals 方法时，必须重写 hashCode 方法。 \n\n\n\n## 3. 如果你定义一个类，包括学号，姓名，分数，如何把这个对象作为key？要重写equals和hashcode吗\n\n- 需要重写 equals 方法和 hashcode，必须保证对象的属性改变时，其 hashcode 不能改变。 \n\n\n\n## 4. 字面量\n\n在编程语言中，字面量（literal）指的是在源代码中直接表示的一个固定的值。 \n\n八进制是用在整数字面量之前添加 “0” 来表示的。 \n\n十六进制用在整数字面量之前添加 “0x” 或者 “0X” 来表示的\n\n Java 7 中新增了二进制：用在整数字面量之前添加 “0b” 或者 “0B” 来表示的。  \n\n**在数值字面量中使用下划线**\n\n在 Java7 中，数值字面量，不管是整数还是浮点数都允许在数字之间插入任意多个下划线。并且不会对数值产生影响，目的是方便阅读，规则只能在数字之间使用。\n\n```java\npublic class BinaryIntegralLiteral {\n    public static void main(String[] args) {\n        System.out.println(0b010101);\n        System.out.println(0B010101);\n        System.out.println(0x15A);\n        System.out.println(0X15A);\n        System.out.println(077);\n        System.out.println(5_000);\n        /**\n         * 输出结果\n         * 21 \n         * 21 \n         * 346 \n         * 346 \n         * 63\n         * 5000\n         */\n    }\n}\n```\n\n\n\n# 六、异常处理\n\n## 1. 常见异常分为那两种(Exception，Error)，常见异常的基类以及常见的异常\n\n- Throwable 是 Java 语言中所有错误和异常的超类（万物即可抛）。它有两个子类：Error、Exception。 \n- 异常种类 \n\n  - **Error**：Error 为错误，是程序无法处理的，如 OutOfMemoryError、ThreadDeath 等，出现这种情况你唯一能做的就是听之任之，交由 JVM 来处理，不过 JVM 在大多数情况下会选择终止线程。 \n  - **Exception**：Exception 是程序可以处理的异常。它又分为两种 CheckedException（受捡异常），一种是 UncheckedException（不受检异常）。 \n    - **受检异常**（CheckException）：发生在编译阶段，必须要使用 try…catch（或者throws）否则编译不通过。 \n    - **非受检异常** （UncheckedException）：是程序运行时错误，例如除 0 会引发 Arithmetic Exception，此时程序奔溃并且无法恢复。 （发生在运行期，具有不确定性，主要是由于程序的逻辑问题所引起的，难以排查，我们一般都需要纵观全局才能够发现这类的异常错误，所以在程序设计中我们需要认真考虑，好好写代码，尽量处理异常，即使产生了异常，也能尽量保证程序朝着有利方向发展。 ）\n- 常见异常的基类（Exception）\n\n  - IOException \n  - RuntimeException \n- 常见的异常\n\n<div align=\"center\"><img src=\"assets/exception_and_error.png\" width=\"650\"/></div>\n\n\n\n# 七、Object 通用方法\n\n以下为 Object 中的通用方法\n\n```java\npublic final native Class<?> getClass()\n\npublic native int hashCode()\n\npublic boolean equals(Object obj)\n\nprotected native Object clone() throws CloneNotSupportedException\n\npublic String toString()\n\npublic final native void notify()\n\npublic final native void notifyAll()\n\npublic final native void wait(long timeout) throws InterruptedException\n\npublic final void wait(long timeout, int nanos) throws InterruptedException\n\npublic final void wait() throws InterruptedException\n\nprotected void finalize() throws Throwable {} // JVM内存回收之finalize()方法\n```\n\n## equals()\n\n**1. equals() 与 == 的区别**\n\n- 对于基本类型，==  判断两个值是否相等，基本类型没有 equals() 方法。\n- 对于引用类型，==  判断两个实例是否引用同一个对象，而 equals() 判断引用的对象是否等价。\n\n```java\nInteger x = new Integer(1);\nInteger y = new Integer(1);\nSystem.out.println(x.equals(y)); // true\nSystem.out.println(x == y);      // false\n```\n\n**2. 等价关系** \n\n（一）自反性\n\n```java\nx.equals(x); // true\n```\n\n（二）对称性\n\n```java\nx.equals(y) == y.equals(x); // true\n```\n\n（三）传递性\n\n```java\nif (x.equals(y) && y.equals(z))\n    x.equals(z); // true;\n```\n\n（四）一致性\n\n多次调用 equals() 方法结果不变\n\n```java\nx.equals(y) == x.equals(y); // true\n```\n\n（五）与 null 的比较\n\n对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false\n\n```java\nx.euqals(null); // false;\n```\n\n**3. 实现** \n\n- 检查是否为同一个对象的引用，如果是直接返回 true；\n- 检查是否是同一个类型，如果不是，直接返回 false；\n- 将 Object 实例进行转型；\n- 判断每个关键域是否相等。\n\n```java\npublic class EqualExample {\n    private int x;\n    private int y;\n    private int z;\n\n    public EqualExample(int x, int y, int z) {\n        this.x = x;\n        this.y = y;\n        this.z = z;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n\n        EqualExample that = (EqualExample) o;\n\n        if (x != that.x) return false;\n        if (y != that.y) return false;\n        return z == that.z;\n    }\n}\n```\n\n## hashCode()\n\n　　hasCode() 返回散列值，而 equals() 是用来判断两个实例是否等价。**等价的两个实例散列值一定要相同，但是散列值相同的两个实例不一定等价。**\n\n　　在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法，保证等价的两个实例散列值也相等。\n\n　　下面的代码中，新建了两个等价的实例，并将它们添加到 HashSet 中。我们希望将这两个实例当成一样的，只在集合中添加一个实例，但是因为 EqualExample 没有实现 hasCode() 方法，因此这两个实例的散列值是不同的，最终导致集合添加了两个等价的实例。\n\n```java\nEqualExample e1 = new EqualExample(1, 1, 1);\nEqualExample e2 = new EqualExample(1, 1, 1);\nSystem.out.println(e1.equals(e2)); // true\nHashSet<EqualExample> set = new HashSet<>();\nset.add(e1);\nset.add(e2);\nSystem.out.println(set.size());   // 2\n```\n\n　　理想的散列函数应当具有均匀性，即不相等的实例应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来，可以将每个域都当成 R 进制的某一位，然后组成一个 R 进制的整数。R 一般取 31，因为它是一个奇素数，如果是偶数的话，当出现乘法溢出，信息就会丢失，因为与 2 相乘相当于向左移一位。\n\n　　一个数与 31 相乘可以转换成移位和减法：`31\\*x == (x<<5)-x`，编译器会自动进行这个优化。\n\n```java\n@Override\npublic int hashCode() {\n    int result = 17;\n    result = 31 * result + x;\n    result = 31 * result + y;\n    result = 31 * result + z;\n    return result;\n}\n```\n\n## toString()\n\n默认返回 ToStringExample@4554617c 这种形式，其中 @ 后面的数值为**散列码的无符号十六进制**表示。\n\n```java\npublic class ToStringExample {\n    private int number;\n\n    public ToStringExample(int number) {\n        this.number = number;\n    }\n}\n```\n\n```java\nToStringExample example = new ToStringExample(123);\nSystem.out.println(example.toString());\n```\n\n```html\nToStringExample@4554617c\n```\n\n## clone()\n\n**1. cloneable** \n\nclone() 是 Object 的 protect 方法，它不是 public，一个类不显式去重写 clone()，其它类就不能直接去调用该类实例的 clone() 方法。\n\n```java\npublic class CloneExample {\n    private int a;\n    private int b;\n}\n```\n\n```java\nCloneExample e1 = new CloneExample();\n// CloneExample e2 = e1.clone(); // 'clone()' has protected access in 'java.lang.Object'\n```\n\n重写 clone() 得到以下实现：\n\n```java\npublic class CloneExample {\n    private int a;\n    private int b;\n\n    @Override\n    protected CloneExample clone() throws CloneNotSupportedException {\n        return (CloneExample)super.clone();\n    }\n}\n```\n\n```java\nCloneExample e1 = new CloneExample();\ntry {\n    CloneExample e2 = e1.clone();\n} catch (CloneNotSupportedException e) {\n    e.printStackTrace();\n}\n```\n\n```html\njava.lang.CloneNotSupportedException: CloneTest\n```\n\n以上抛出了 CloneNotSupportedException，这是因为 CloneTest 没有实现 Cloneable 接口。\n\n```java\npublic class CloneExample implements Cloneable {\n    private int a;\n    private int b;\n\n    @Override\n    protected Object clone() throws CloneNotSupportedException {\n        return super.clone();\n    }\n}\n```\n\n应该注意的是，clone() 方法并不是 Cloneable 接口的方法，而是 Object 的一个 protected 方法。Cloneable 接口只是规定，如果一个类没有实现 Cloneable 接口又调用了 clone() 方法，就会抛出 CloneNotSupportedException。\n\n\n\n参考资料：\n\n- [【必读】搞懂 Java equals 和 hashCode 方法 - 掘金](https://juejin.im/post/5ac4d8abf265da23a4050ae3)\n\n\n\n# 更新日志\n\n- 2018/7/11 v1.0 第一版\n- 2018/7/31 v2.0 基础版\n- 2018/8/30-31 v3.0 初版\n"
  },
  {
    "path": "notes/JavaArchitecture/02-Java集合框架.md",
    "content": "<!-- TOC -->\n\n- [前言](#前言)\n- [一、概述](#一概述)\n    - [集合框架图](#集合框架图)\n    - [Collection](#Collection)\n    - [Map](#Map)\n    - [工具类](#工具类)\n    - [通用实现](#通用实现)\n- [二、深入源码分析](#二深入源码分析)\n    - [ArrayList](#ArrayList)\n        - [1. 概览](#1-概览)\n        - [2. 序列化](#2-序列化)\n        - [3. 扩容](#3-扩容)\n        - [4. 删除元素](#4-删除元素)\n        - [5. Fail-Fast](#5-Fail-Fast)\n    - [Vector](#Vector)\n        - [1. 同步](#1-同步)\n        - [2. ArrayList 与 Vector](#2-ArrayList-与-Vector)\n        - [3. Vector 替代方案](#3-Vector-替代方案)\n            - [synchronizedList](#synchronizedList)\n            - [CopyOnWriteArrayList](#CopyOnWriteArrayList)\n    - [LinkedList](#LinkedList)\n        - [1. 概览](#1-概览-1)\n        - [2. add()](#2-add)\n        - [3. remove()](#3-remove)\n        - [4. get()](#4-get)\n        - [5. 总结](#5-总结)\n        - [6. ArrayList 与 LinkedList](#6-ArrayList-与-LinkedList)\n    - [HashMap](#HashMap)\n        - [1. 存储结构](#1-存储结构)\n            - [JDK1.7 的存储结构](#jdk17-的存储结构)\n            - [JDK1.8 的存储结构](#jdk18-的存储结构)\n        - [2. 重要参数](#2-重要参数)\n        - [3. 确定哈希桶数组索引位置](#3-确定哈希桶数组索引位置)\n        - [4. 分析HashMap的put方法](#4-分析HashMap的put方法)\n        - [5. 扩容机制](#5-扩容机制)\n        - [6. 线程安全性](#6-线程安全性)\n        - [7. JDK1.8与JDK1.7的性能对比](#7-jdk18与jdk17的性能对比)\n        - [8. Hash较均匀的情况](#8-hash较均匀的情况)\n        - [9. Hash极不均匀的情况](#9-hash极不均匀的情况)\n        - [10. HashMap与Hashtable](#10-HashMap与Hashtable)\n        - [11. 小结](#11-小结)\n    - [ConcurrentHashMap](#ConcurrentHashMap)\n        - [1. 概述](#1-概述)\n        - [2. 存储结构](#2-存储结构)\n        - [2. size 操作](#2-size-操作)\n        - [3. 同步方式](#3-同步方式)\n        - [4. JDK 1.8 的改动](#4-jdk-18-的改动)\n    - [HashSet](#hashset)\n        - [1. 成员变量](#1-成员变量)\n        - [2. 构造函数](#2-构造函数)\n        - [3. add()](#3-add)\n        - [4. 总结](#4-总结)\n    - [LinkedHashSet and LinkedHashMap](#LinkedHashSet-and-LinkedHashMap)\n        - [1. 概览](#1-概览-2)\n        - [2. get()](#2-get)\n        - [3. put()](#3-put)\n        - [4. remove()](#4-remove)\n        - [5. LinkedHashSet](#5-LinkedHashSet)\n        - [6. LinkedHashMap经典用法](#6-LinkedHashMap经典用法)\n- [三、容器中的设计模式](#三容器中的设计模式)\n    - [迭代器模式](#迭代器模式)\n    - [适配器模式](#适配器模式)\n- [四、面试指南](#四面试指南)\n    - [1. ArrayList和LinkedList区别](#1-ArrayList和LinkedList区别)\n    - [2. HashMap和Hashtable区别，HashMap的key类型](#2-HashMap和Hashtable区别HashMap的key类型)\n    - [3. HashMap和ConcurrentHashMap](#3-HashMap和ConcurrentHashMap)\n    - [4. Hashtable的原理](#4-Hashtable的原理)\n    - [5. Hash冲突的解决办法](#5-hash冲突的解决办法)\n    - [6. 什么是迭代器](#6-什么是迭代器)\n    - [7. 构造相同hash的字符串进行攻击，这种情况应该怎么处理？JDK7如何处理](#7-构造相同hash的字符串进行攻击这种情况应该怎么处理jdk7如何处理)\n    - [8. HashMap为什么大小是2的幂次](#8-HashMap为什么大小是2的幂次)\n- [更新日志](#更新日志)\n\n<!-- /TOC -->\n# 前言\n\n　　Java集合框架 (Java Collections Framework, JCF) 也称容器，这里可以类比 C++ 中的 STL，在市面上似乎还没能找到一本详细介绍的书籍。在这里主要对如下部分进行源码分析，及在面试中常见的问题。\n\n　　例如，在阿里面试常问到的 HashMap 和 ConcurrentHashMap 原理等等。深入源码分析是面试中必备的技能，通过本文的阅读会对集合框架有更深一步的了解。\n\n\n\n本文参考：\n\n- [CarpenterLee/JCFInternals: 深入理解Java集合框架](https://github.com/CarpenterLee/JCFInternals)\n- [crossoverJie/Java-Interview: 👨‍🎓 Java related : basic, concurrent, algorithm](https://github.com/crossoverJie/Java-Interview)\n- [Interview-Notebook/Java 容器.md at master · CyC2018/Interview-Notebook](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Java%20%E5%AE%B9%E5%99%A8.md)\n\n\n\n# 一、概述\n\n　　Java集合框架提供了数据持有对象的方式，提供了对数据集合的操作。Java 集合框架位于 ` java.util` 包下，主要有三个大类：**Collection(接口)**、**Map(接口)**、**集合工具类**。\n\n\n\n## 集合框架图\n\n<div align=\"center\"> <img src=\"assets/1535785576589.png\" width=\"\"/></div>\n\n\n\n## Collection\n\n- `ArrayList`：**线程不同步**。默认初始容量为 10，当数组大小不足时容量扩大为 1.5 倍。为追求效率，ArrayList 没有实现同步（synchronized），如果需要多个线程并发访问，用户可以手动同步，也可使用 Vector 替代。 \n- `LinkedList`：**线程不同步**。**双向链接实现**。LinkedList 同时实现了 List 接口和 Deque 接口，也就是说它既可以看作一个顺序容器，又可以看作一个队列（Queue），同时又可以看作一个栈（Stack）。这样看来，LinkedList 简直就是个全能冠军。当你需要使用栈或者队列时，可以考虑使用 LinkedList，一方面是因为 Java 官方已经声明不建议使用 Stack 类，更遗憾的是，Java 里根本没有一个叫做 Queue 的类（它是个接口名字）。关于栈或队列，现在的首选是 ArrayDeque，它有着比 LinkedList（当作栈或队列使用时）有着更好的性能。 \n- `Stack and Queue`：Java 里有一个叫做 Stack 的类，却没有叫做 Queue 的类（它是个接口名字）。当需要使用栈时，Java 已不推荐使用 Stack，而是推荐使用更高效的 ArrayDeque；既然 Queue 只是一个接口，当需要使用队列时也就首选 ArrayDeque 了（次选是 LinkedList ）。 \n- `Vector`：**线程同步**。默认初始容量为 10，当数组大小不足时容量扩大为 2 倍。它的同步是通过 `Iterator` 方法加 `synchronized` 实现的。\n- `Stack`：**线程同步**。继承自 Vector，添加了几个方法来完成栈的功能。现在已经不推荐使用 Stack，在栈和队列中有限使用 ArrayDeque，其次是 LinkedList。\n- `TreeSet`：**线程不同步**，内部使用 `NavigableMap` 操作。默认元素 “自然顺序” 排列，可以通过 `Comparator` 改变排序。TreeSet 里面有一个 TreeMap（适配器模式）\n- `HashSet`：**线程不同步**，内部使用 HashMap 进行数据存储，提供的方法基本都是调用 HashMap 的方法，所以两者本质是一样的。集合元素可以为 NULL。\n- `Set`：Set 是一种不包含重复元素的 Collection，Set 最多只有一个 null 元素。Set 集合通常可以通过 Map 集合通过适配器模式得到。\n- `PriorityQueue`：Java 中 PriorityQueue 实现了 Queue 接口，不允许放入 null 元素；其通过堆实现，具体说是通过完全二叉树（complete binary tree）实现的**小顶堆**（任意一个非叶子节点的权值，都不大于其左右子节点的权值），也就意味着可以通过数组来作为 PriorityQueue 的底层实现。 \n  - **优先队列的作用是能保证每次取出的元素都是队列中权值最小的**（Java 的优先队列每次取最小元素，C++ 的优先队列每次取最大元素）。这里牵涉到了大小关系，**元素大小的评判可以通过元素本身的自然顺序（natural ordering），也可以通过构造时传入的比较器**（*Comparator*，类似于 C++ 的仿函数）。 \n- `NavigableSet`：添加了搜索功能，可以对给定元素进行搜索：小于、小于等于、大于、大于等于，放回一个符合条件的最接近给定元素的 key。\n- `EnumSet`：线程不同步。内部使用 Enum 数组实现，速度比 `HashSet` 快。**只能存储在构造函数传入的枚举类的枚举值**。\n\n\n\n　　注释：更多设计模式，请转向 [Java 设计模式](06%20设计模式.md)\n\n\n\n## Map\n\n- `TreeMap`：线程不同步，基于 **红黑树** （Red-Black tree）的 NavigableMap 实现，能够把它保存的记录根据键排序，默认是按键值的升序排序，也可以指定排序的比较器，当用 Iterator 遍历 TreeMap 时，得到的记录是排过序的。\n  - **TreeMap 底层通过红黑树（Red-Black tree）实现**，也就意味着 `containsKey()`, `get()`, `put()`, `remove()` 都有着 `log(n)` 的时间复杂度。其具体算法实现参照了《算法导论》。\n- `Hashtable`：**线程安全**，HashMap 的迭代器 \\(Iterator\\) 是 `fail-fast` 迭代器。**Hashtable 不能存储 NULL 的 key 和 value。**\n- `HashMap`：线程不同步。根据 `key` 的 `hashcode` 进行存储，内部使用静态内部类 `Node` 的数组进行存储，默认初始大小为 16，每次扩大一倍。当发生 Hash 冲突时，采用拉链法（链表）。JDK 1.8中：**当单个桶中元素个数大于等于8时，链表实现改为红黑树实现；当元素个数小于6时，变回链表实现。由此来防止hashCode攻击。**\n  - Java HashMap 采用的是冲突链表方式。  \n  - HashMap 是 Hashtable 的轻量级实现，可以接受为 null 的键值 (key\\) 和值 \\(value\\)，而 Hashtable 不允许。\n- `LinkedHashMap`：**保存了记录的插入顺序**，在用 Iterator 遍历 LinkedHashMap 时，先得到的记录肯定是先插入的。也可以在构造时用带参数，按照应用次数排序。在遍历的时候会比 HashMap 慢，不过有种情况例外，当 HashMap 容量很大，实际数据较少时，遍历起来可能会比 LinkedHashMap 慢，因为 LinkedHashMap 的遍历速度只和实际数据有关，和容量无关，而 HashMap 的遍历速度和他的容量有关。\n- `WeakHashMap`：从名字可以看出它是某种 Map。它的特殊之处在于 WeakHashMap 里的 entry 可能会被 GC 自动删除，即使程序员没有调用 `remove()` 或者 `clear()` 方法。 WeakHashMap 的存储结构类似于HashMap\n  - 既然有 WeekHashMap，是否有 WeekHashSet 呢？答案是没有！不过 Java Collections 工具类给出了解决方案，`Collections.newSetFromMap(Map<E,Boolean> map)` 方法可以将任何 Map包装成一个Set。\n\n\n\n\n## 工具类\n\n- `Collections`、`Arrays`：集合类的一个工具类帮助类，其中提供了一系列静态方法，用于对集合中元素进行排序、搜索以及线程安全等各种操作。\n\n- `Comparable`、`Comparator`：一般是用于对象的比较来实现排序，两者略有区别。\n\n  > - 类设计者没有考虑到比较问题而没有实现 Comparable 接口。这是我们就可以通过使用 Comparator，这种情况下，我们是不需要改变对象的。\n  > - 一个集合中，我们可能需要有多重的排序标准，这时候如果使用 Comparable 就有些捉襟见肘了，可以自己继承 Comparator 提供多种标准的比较器进行排序。\n\n\n\n**说明**：线程不同步的时候可以通过，Collections.synchronizedList() 方法来包装一个线程同步方法\n\n\n\n## 通用实现\n\n<table align=\"center\"><tr><td colspan=\"2\" rowspan=\"2\" align=\"center\" border=\"0\"></td><th colspan=\"5\" align=\"center\">Implementations</th></tr><tr><th>Hash Table</th><th>Resizable Array</th><th>Balanced Tree</th><th>Linked List</th><th>Hash Table + Linked List</th></tr><tr><th rowspan=\"4\">Interfaces</th><th>Set</th><td><tt>HashSet</tt></td><td></td><td><tt>TreeSet</tt></td><td></td><td><tt>LinkedHashSet</tt></td></tr><tr><th>List</th><td></td><td><tt>ArrayList</tt></td><td></td><td><tt>LinkedList</tt></td><td></td></tr><tr><th>Deque</th><td></td><td><tt>ArrayDeque</tt></td><td></td><td><tt>LinkedList</tt></td><td></td></tr><tr><th>Map</th><td><tt>HashMap</tt></td><td></td><td><tt>TreeMap</tt></td><td></td><td><tt>LinkedHashMap</tt></td></tr></table>\n\n\n\n**参考资料：**\n\n- [CarpenterLee/JCFInternals:深入理解Java集合框架](https://github.com/CarpenterLee/JCFInternals)\n- [Java基础-集合框架 - 掘金](https://juejin.im/post/5af86ac8f265da0ba063410e)\n\n\n\n# 二、深入源码分析\n\n源码分析基于 JDK 1.8 / JDK 1.7，在 IDEA 中 double shift 调出 Search EveryWhere，查找源码文件，找到之后就可以阅读源码。\n\n## ArrayList\n\n### 1. 概览\n\n实现了 RandomAccess 接口，因此支持随机访问，这是理所当然的，因为 ArrayList 是基于数组实现的。\n\n```java\npublic class ArrayList<E> extends AbstractList<E>\n        implements List<E>, RandomAccess, Cloneable, java.io.Serializable\n```\n\n数组的默认大小为 10。\n\n```java\nprivate static final int DEFAULT_CAPACITY = 10;\n```\n\n<div align=\"center\"> <img src=\"assets/ArrayList_base.png\" width=\"600\"/></div>\n\n\n\n### 2. 序列化\n\n基于数组实现，保存元素的数组使用 transient 修饰，该关键字声明数组默认不会被序列化。ArrayList 具有动态扩容特性，因此保存元素的数组不一定都会被使用，那么就没必要全部进行序列化。ArrayList 重写了 writeObject() 和 readObject() 来控制只序列化数组中有元素填充那部分内容。\n\n```java\ntransient Object[] elementData; // non-private to simplify nested class access\n```\n\n\n\n### 3. 扩容\n\n添加元素时使用 ensureCapacityInternal() 方法来保证容量足够，如果不够时，需要使用 grow() 方法进行扩容，新容量的大小为 `oldCapacity + (oldCapacity >> 1)`，也就是旧容量的 1.5 倍。\n\n扩容操作需要调用 `Arrays.copyOf()` 把原数组整个复制到新数组中，这个操作代价很高，因此最好在创建 ArrayList 对象时就指定大概的容量大小，减少扩容操作的次数。\n\n```java\n// JDK 1.8\n    \npublic boolean add(E e) {\n    ensureCapacityInternal(size + 1);  // Increments modCount!!\n    elementData[size++] = e;\n    return true;\n}\n\n// 判断数组是否越界\nprivate void ensureCapacityInternal(int minCapacity) {\n    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {\n        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);\n    }\n    ensureExplicitCapacity(minCapacity);\n}\n\nprivate void ensureExplicitCapacity(int minCapacity) {\n    modCount++;\n    // overflow-conscious code\n    if (minCapacity - elementData.length > 0)\n        grow(minCapacity);\n}\n\n// 扩容\nprivate void grow(int minCapacity) {\n    // overflow-conscious code\n    int oldCapacity = elementData.length;\n    int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍\n    if (newCapacity - minCapacity < 0)\n        newCapacity = minCapacity;\n    if (newCapacity - MAX_ARRAY_SIZE > 0)\n        newCapacity = hugeCapacity(minCapacity);\n    // minCapacity is usually close to size, so this is a win:\n    elementData = Arrays.copyOf(elementData, newCapacity);\n}\n\nprivate static int hugeCapacity(int minCapacity) {\n    if (minCapacity < 0) // overflow\n        throw new OutOfMemoryError();\n    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;\n}\n```\n\n\n\n### 4. 删除元素\n\n需要调用 System.arraycopy() 将 index+1 后面的元素都复制到 index 位置上。\n\n```java\npublic E remove(int index) {\n    rangeCheck(index);\n    modCount++;\n    E oldValue = elementData(index);\n    int numMoved = size - index - 1;\n    if (numMoved > 0)\n        System.arraycopy(elementData, index+1, elementData, index, numMoved);\n    elementData[--size] = null; // clear to let GC do its work\n    return oldValue;\n}\n```\n\n\n\n### 5. Fail-Fast\n\n**开始之前我们想讲讲，什么是 fail-fast 机制?**\n\nfail-fast 机制在遍历一个集合时，当集合结构被修改，会抛出 Concurrent Modification Exception。\n\nfail-fast 会在以下两种情况下抛出 Concurrent Modification Exception\n\n（1）单线程环境\n\n- 集合被创建后，在遍历它的过程中修改了结构。\n\n- 注意 remove() 方法会让 expectModcount 和 modcount 相等，所以是不会抛出这个异常。\n\n（2）多线程环境\n\n- 当一个线程在遍历这个集合，而另一个线程对这个集合的结构进行了修改。\n\n\n\nmodCount 用来记录 ArrayList 结构发生变化的次数。结构发生变化是指**添加**或者**删除**至少一个元素的所有操作，或者是调整内部数组的大小，仅仅只是设置元素的值不算结构发生变化。\n\n在进行序列化或者迭代等操作时，需要比较操作前后 modCount 是否改变，如果改变了需要抛出 Concurrent Modification Exception。\n\n```java\nprivate void writeObject(java.io.ObjectOutputStream s) 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\n\n## Vector\n\n### 1. 同步\n\n它的实现与 ArrayList 类似，但是使用了 synchronized 进行同步。\n\n```java\npublic synchronized boolean add(E e) {\n    modCount++;\n    ensureCapacityHelper(elementCount + 1);\n    elementData[elementCount++] = e;\n    return true;\n}\n\npublic synchronized E get(int index) {\n    if (index >= elementCount)\n        throw new ArrayIndexOutOfBoundsException(index);\n    return elementData(index);\n}\n```\n\n\n\n### 2. ArrayList 与 Vector\n\n- Vector 是同步的，因此开销就比 ArrayList 要大，访问速度更慢。最好使用 ArrayList 而不是 Vector，因为同步操作完全可以由程序员自己来控制；\n- Vector 每次扩容请求其大小的 2 倍空间，而 ArrayList 是 1.5 倍。\n\n\n\n### 3. Vector 替代方案\n\n#### synchronizedList\n\n为了获得线程安全的 ArrayList，可以使用  `Collections.synchronizedList();`  得到一个线程安全的  ArrayList。\n\n```java\nList<String> list = new ArrayList<>();\nList<String> synList = Collections.synchronizedList(list);\n```\n\n\n\n#### CopyOnWriteArrayList\n\n<div align=\"center\"> <img src=\"assets/cow.png\" width=\"\"/></div><br/>\n\n\n\n也可以使用 concurrent 并发包下的 CopyOnWriteArrayList 类。\n\n```java\nList<String> list = new CopyOnWriteArrayList<>();\n```\n\nCopyOnWrite 容器即写时复制的容器。通俗的理解是**当我们往一个容器添加元素的时候，不直接往当前容器添加，而是先将当前容器进行 Copy，复制出一个新的容器，然后新的容器里添加元素，添加完元素之后，再将原容器的引用指向新的容器**。这样做的好处是我们可以对 CopyOnWrite 容器进行并发的读，而不需要加锁，因为当前容器不会添加任何元素。所以 CopyOnWrite 容器也是一种**读写分离**的思想，读和写不同的容器。 \n\n```java\npublic boolean add(T e) {\n    final ReentrantLock lock = this.lock;\n    lock.lock();\n    try {\n        Object[] elements = getArray();\n        int len = elements.length;\n        // 复制出新数组\n        Object[] newElements = Arrays.copyOf(elements, len + 1);\n        // 把新元素添加到新数组里\n        newElements[len] = e;\n        // 把原数组引用指向新数组\n        setArray(newElements);\n        return true;\n    } finally {\n        lock.unlock();\n    }\n}\n\nfinal void setArray(Object[] a) {\n    array = a;\n}\n```\n\n读的时候不需要加锁，如果读的时候有多个线程正在向 ArrayList 添加数据，读还是会读到旧的数据，因为写的时候不会锁住旧的 ArrayList。  \n\n```java\npublic E get(int index) {\n    return get(getArray(), index);\n}\n```\n\n**CopyOnWrite的缺点**\n\n- CopyOnWrite 容器有很多优点，但是同时也存在两个问题，即内存占用问题和数据一致性问题。所以在开发的时候需要注意一下。\n\n**内存占用问题**。\n\n- 因为 CopyOnWrite 的写时复制机制，所以在进行写操作的时候，内存里会同时驻扎两个对象的内存，旧的对象和新写入的对象（注意：在复制的时候只是复制容器里的引用，只是在写的时候会创建新对象添加到新容器里，而旧容器的对象还在使用，所以有两份对象内存）。如果这些对象占用的内存比较大，比如说 200M 左右，那么再写入 100M 数据进去，内存就会占用 300M，那么这个时候很有可能造成频繁的 Yong GC 和 Full GC。之前我们系统中使用了一个服务由于每晚使用 CopyOnWrite 机制更新大对象，造成了每晚 15 秒的 Full GC，应用响应时间也随之变长。\n\n- 针对内存占用问题，可以通过压缩容器中的元素的方法来减少大对象的内存消耗，比如，如果元素全是 10 进制的数字，可以考虑把它压缩成 36 进制或 64 进制。或者不使用 CopyOnWrite 容器，而使用其他的并发容器，如  ConcurrentHashMap 。\n\n**数据一致性问题**。\n\n- CopyOnWrite 容器只能保证数据的最终一致性，不能保证数据的实时一致性。所以如果你希望写入的的数据，马上能读到，请不要使用 CopyOnWrite 容器。\n\n- 关于 C++ 的 STL 中，曾经也有过 Copy-On-Write 的玩法，参见陈皓的《[C++ STL String类中的Copy-On-Write](http://blog.csdn.net/haoel/article/details/24058)》，后来，因为有很多线程安全上的事，就被去掉了。\n\n\n\n参考资料：\n\n- [聊聊并发-Java中的Copy-On-Write容器 | 并发编程网 – ifeve.com](http://ifeve.com/java-copy-on-write/)\n\n\n\n## LinkedList\n\n<div align=\"center\"> <img src=\"assets/LinkedList_base.png\" width=\"600\"/></div>\n\n### 1. 概览\n\n**LinkedList 底层是基于双向链表实现的**，也是实现了 List 接口，所以也拥有 List 的一些特点 (JDK1.7/8 之后取消了循环，修改为双向链表) 。 \n\nLinkedList 同时实现了 List 接口和 Deque 接口，也就是说它既可以看作一个顺序容器，又可以看作一个队列（Queue），同时又可以看作一个栈（Stack）。这样看来， LinkedList 简直就是个全能冠军。当你需要使用栈或者队列时，可以考虑使用 LinkedList ，一方面是因为 Java 官方已经声明不建议使用 Stack 类，更遗憾的是，Java里根本没有一个叫做 Queue 的类（它是个接口名字）。\n\n关于栈或队列，现在的首选是 ArrayDeque，它有着比 LinkedList （当作栈或队列使用时）有着更好的性能。 \n\n基于双向链表实现，内部使用 Node 来存储链表节点信息。\n\n```java\nprivate static class Node<E> {\n    E item;\n    Node<E> next;\n    Node<E> prev;\n}\n```\n\n每个链表存储了 Head 和 Tail 指针：\n\n```java\ntransient Node<E> first;\ntransient Node<E> last;\n```\n\nLinkedList 的实现方式决定了所有跟下标相关的操作都是线性时间，而在首段或者末尾删除元素只需要常数时间。为追求效率*LinkedList*没有实现同步（synchronized），如果需要多个线程并发访问，可以先采用 `Collections.synchronizedList()` 方法对其进行包装。 \n\n\n\n### 2. add()\n\n<div align=\"center\"> <img src=\"assets/LinkedList_add.png\" width=\"600\"/></div>\n\nadd() 方法有两个版本，一个是 `add(E e)`，该方法在 LinkedList 的末尾插入元素，因为有 last 指向链表末尾，在末尾插入元素的花费是常数时间。只需要简单修改几个相关引用即可；另一个是 `add(int index, E element)`，该方法是在指定下表处插入元素，需要先通过线性查找找到具体位置，然后修改相关引用完成插入操作。\n\n```java\n// JDK 1.8\npublic boolean add(E e) {\n    linkLast(e);\n    return true;\n}\n\n/**\n* Links e as last element.\n*/\nvoid linkLast(E e) {\n    final Node<E> l = last;\n    final Node<E> newNode = new Node<>(l, e, null);\n    last = newNode;\n    if (l == null)\n        first = newNode;\n    else\n        l.next = newNode;\n    size++;\n    modCount++;\n}\n\n```\n`add(int index, E element)` 的逻辑稍显复杂，可以分成两部分\n\n1. 先根据 index 找到要插入的位置；\n\n2. 修改引用，完成插入操作。\n\n\n```java\npublic void add(int index, E element) {\n    checkPositionIndex(index);\n    if (index == size)\n        linkLast(element);\n    else\n        linkBefore(element, node(index));\n}\n\nprivate void checkPositionIndex(int index) {\n    if (!isPositionIndex(index))\n        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));\n}\n```\n\n上面代码中的 `node(int index)` 函数有一点小小的 trick，因为链表双向的，可以从开始往后找，也可以从结尾往前找，具体朝那个方向找取决于条件 `index < (size >> 1)`，也即是 index 是靠近前端还是后端。\n\n\n\n### 3. remove()\n\nremove() 方法也有两个版本，一个是删除跟指定元素相等的第一个元素 `remove(Object o)`，另一个是删除指定下标处的元素 `remove(int index)`。\n\n<div align=\"center\"> <img src=\"assets/LinkedList_remove.png\" width=\"600\"/></div>\n\n两个删除操作都要：\n\n1. 先找到要删除元素的引用；\n2. 修改相关引用，完成删除操作。\n\n在寻找被删元素引用的时候 `remove(Object o)` 调用的是元素的 equals 方法，而 `remove(int index)` 使用的是下标计数，两种方式都是线性时间复杂度。在步骤 2 中，两个 `revome()` 方法都是通过 `unlink(Node<E> x)` 方法完成的。这里需要考虑删除元素是第一个或者最后一个时的边界情况。  \n\n\n\n### 4. get()\n\n```java\npublic E get(int index) {\n\tcheckElementIndex(index);\n\treturn node(index).item;\n}\n    \nNode<E> node(int index) {\n\t// assert isElementIndex(index);\n    if (index < (size >> 1)) {\n        Node<E> x = first;\n        for (int i = 0; i < index; i++)\n            x = x.next;\n        return x;\n\t} else {\n        Node<E> x = last;\n        for (int i = size - 1; i > index; i--)\n            x = x.prev;\n        return x;\n\t}\n}\n```\n\n由此可以看出是使用二分查找来看 `index` 离 size 中间距离来判断是从头结点正序查还是从尾节点倒序查。\n\n- node() 会以 `O(n/2)` 的性能去获取一个结点\n  - 如果索引值大于链表大小的一半，那么将从尾结点开始遍历\n\n这样的效率是非常低的，特别是当 index 越接近 size 的中间值时。\n\n\n\n### 5. 总结\n\n- LinkedList 插入，删除都是移动指针效率很高。\n- 查找需要进行遍历查询，效率较低。\n\n\n\n### 6. ArrayList 与 LinkedList\n\n- ArrayList 基于动态数组实现，LinkedList 基于双向链表实现；\n- ArrayList 支持随机访问，LinkedList 不支持；\n- LinkedList 在任意位置添加删除元素更快。\n\n\n\n## HashMap\n\n我们这篇文章就来试着分析下 HashMap 的源码，由于 HashMap 底层涉及到太多方面，一篇文章总是不能面面俱到，所以我们可以带着面试官常问的几个问题去看源码：\n\n1. 了解底层如何存储数据的\n2. HashMap 的几个主要方法\n3. HashMap 是如何确定元素存储位置的以及如何处理哈希冲突的\n4. HashMap 扩容机制是怎样的\n5. JDK 1.8 在扩容和解决哈希冲突上对 HashMap 源码做了哪些改动？有什么好处?\n\n\n\nHashMap 的内部功能实现很多，本文主要从根据 key 获取哈希桶数组索引位置、put 方法的详细执行、扩容过程三个具有代表性的点深入展开讲解。\n\n### 1. 存储结构\n\n#### JDK1.7 的存储结构\n\n在 1.7 之前 JDK 采用「拉链法」来存储数据，即数组和链表结合的方式：\n\n <div align=\"center\"> <img src=\"assets/hashmap-link.jpg\" width=\"550\"/></div>\n\n 「拉链法」用专业点的名词来说叫做**链地址法**。简单来说，就是数组加链表的结合。在每个数组元素上存储的都是一个链表。\n\n我们之前说到不同的 key 可能经过 hash 运算可能会得到相同的地址，但是一个数组单位上只能存放一个元素，采用链地址法以后，如果遇到相同的 hash 值的 key 的时候，我们可以将它放到作为数组元素的链表上。待我们去取元素的时候通过 hash 运算的结果找到这个链表，再在链表中找到与 key 相同的节点，就能找到 key 相应的值了。\n\nJDK1.7 中新添加进来的元素总是放在数组相应的角标位置，而原来处于该角标的位置的节点作为 next 节点放到新节点的后边。稍后通过源码分析我们也能看到这一点。\n\n \n\n####  JDK1.8 的存储结构\n\n对于 JDK1.8 之后的 `HashMap` 底层在解决哈希冲突的时候，就不单单是使用数组加上单链表的组合了，因为当处理如果 hash 值冲突较多的情况下，链表的长度就会越来越长，此时通过单链表来寻找对应 Key 对应的 Value 的时候就会使得时间复杂度达到 O(n)，因此在 JDK1.8 之后，在链表新增节点导致链表长度超过 `TREEIFY_THRESHOLD = 8`  的时候，就会在添加元素的同时将原来的单链表转化为红黑树。\n\n对数据结构很在行的读者应该，知道红黑树是一种易于增删改查的二叉树，他对与数据的查询的时间复杂度是 `O(logn)` 级别，所以利用红黑树的特点就可以更高效的对 `HashMap` 中的元素进行操作。\n\n<div align=\"center\"> <img src=\"assets/hashmap-rb-link.jpg\" width=\"550\"/></div><br/>\n\n \n\n从结构实现来讲，HashMap 是数组+链表+红黑树（JDK1.8增加了红黑树部分）实现的，如下如所示。 \n\n<div align=\"center\"> <img src=\"assets/hashMap-datastruct.png\" width=\"400\"/></div>\n\n\n\n**这里需要讲明白两个问题：数据底层具体存储的是什么？这样的存储方式有什么优点呢？** \n\n（1）从源码可知，HashMap 类中有一个非常重要的字段，就是 Node[] table，即哈希桶数组，明显它是一个 Node 的数组。我们来看 Node（ JDK1.8 中） 是何物。\n\n```java\nstatic class Node<K,V> implements Map.Entry<K,V> {\n    final int hash;    //用来定位数组索引位置\n    final K key;\n    V value;\n    Node<K,V> next;   //链表的下一个node\n\n    Node(int hash, K key, V value, Node<K,V> next) { ... }\n    public final K getKey(){ ... }\n    public final V getValue() { ... }\n    public final String toString() { ... }\n    public final int hashCode() { ... }\n    public final V setValue(V newValue) { ... }\n    public final boolean equals(Object o) { ... }\n}\n```\n\nNode 是 HashMap 的一个内部类，实现了 Map.Entry 接口，本质是就是一个映射（键值对）。上图中的每个黑色圆点就是一个Node对象。\n\n（2）HashMap 就是使用哈希表来存储的。哈希表为解决冲突，可以采用**开放地址法**和**链地址法**等来解决问题， Java 中 HashMap 采用了链地址法。链地址法，简单来说，就是数组加链表的结合。在每个数组元素上都一个链表结构，当数据被 Hash 后，得到数组下标，把数据放在对应下标元素的链表上。例如程序执行下面代码：\n\n```java\nmap.put(\"美团\",\"小美\");\n```\n\n系统将调用 \"美团\" 这个 key 的 hashCode() 方法得到其 hashCode 值（该方法适用于每个 Java 对象），然后再通过 Hash 算法的后两步运算（高位运算和取模运算，下文有介绍）来定位该键值对的存储位置，有时两个 key 会定位到相同的位置，表示发生了 Hash 碰撞。当然 Hash 算法计算结果越分散均匀，Hash 碰撞的概率就越小，map 的存取效率就会越高。\n\n如果哈希桶数组很大，即使较差的 Hash 算法也会比较分散，如果哈希桶数组数组很小，即使好的 Hash 算法也会出现较多碰撞，所以就需要在空间成本和时间成本之间权衡，其实就是在根据实际情况确定哈希桶数组的大小，并在此基础上设计好的 hash 算法减少 Hash 碰撞。\n\n**那么通过什么方式来控制 map 使得 Hash 碰撞的概率又小，哈希桶数组（Node[] table）占用空间又少呢？**\n\n答案就是好的 Hash 算法和扩容机制。\n\n在理解 Hash 和扩容流程之前，我们得先了解下 HashMap 的几个字段。从 HashMap 的默认构造函数源码可知，构造函数就是对下面几个字段进行初始化，源码如下：\n\n```java\nint threshold;             // 所能容纳的key-value对极限 \nfinal float loadFactor;    // 负载因子\nint modCount;  \nint size;\n```\n\n首先，**Node[] table的初始化长度 length (默认值是16)**，**Load factor 为负载因子(默认值是0.75)**，threshold 是 HashMap 所能容纳的最大数据量的 Node (键值对)个数。**threshold = length * Load factor**。也就是说，在数组定义好长度之后，负载因子越大，所能容纳的键值对个数越多。\n\n结合负载因子的定义公式可知，threshold 就是在此 Load factor 和 length (数组长度)对应下允许的最大元素数目，超过这个数目就重新 resize(扩容)，扩容后的 HashMap 容量是之前容量的两倍。默认的负载因子 0.75 是对空间和时间效率的一个平衡选择，建议大家不要修改，除非在时间和空间比较特殊的情况下，如果内存空间很多而又对时间效率要求很高，可以降低负载因子 Load factor 的值；相反，如果内存空间紧张而对时间效率要求不高，可以增加负载因子 loadFactor 的值，这个值可以大于1。\n\nsize 这个字段其实很好理解，就是 HashMap 中实际存在的键值对数量。注意和 table 的长度 length、容纳最大键值对数量 threshold 的区别。而 modCount 字段主要用来记录 HashMap 内部结构发生变化的次数，主要用于迭代的快速失败。强调一点，内部结构发生变化指的是结构发生变化，例如 put 新键值对，但是某个 key 对应的 value 值被覆盖不属于结构变化。\n\n在 HashMap 中，哈希桶数组 table 的长度 length 大小必须为 2<sup>n</sup>（一定是合数），这是一种非常规的设计，常规的设计是把桶的大小设计为素数。相对来说素数导致冲突的概率要小于合数，具体证明可以参考 [为什么一般hashtable的桶数会取一个素数？](https://blog.csdn.net/liuqiyao_01/article/details/14475159) ，**Hashtable 初始化桶大小为 11，就是桶大小设计为素数的应用（Hashtable 扩容后不能保证还是素数）**。HashMap 采用这种非常规设计，**主要是为了在取模和扩容时做优化，同时为了减少冲突，HashMap 定位哈希桶索引位置时，也加入了高位参与运算的过程**。\n\n这里存在一个问题，即使负载因子和 Hash 算法设计的再合理，也免不了会出现拉链过长的情况，一旦出现拉链过长，则会严重影响 HashMap 的性能。于是，在 JDK1.8 版本中，对数据结构做了进一步的优化，引入了红黑树。而当链表长度太长（默认超过8）时，链表就转换为红黑树，利用红黑树快速增删改查的特点提高 HashMap 的性能，其中会用到红黑树的插入、删除、查找等算法。本文不再对红黑树展开讨论，想了解更多红黑树数据结构的工作原理可以参考：[教你初步了解红黑树](https://blog.csdn.net/v_july_v/article/details/6105630)。\n\n\n\n### 2. 重要参数\n\n| 参数                | 说明                                                         |\n| ------------------- | ------------------------------------------------------------ |\n| buckets             | 在 HashMap 的注释里使用哈希桶来形象的表示数组中每个地址位置。注意这里并不是数组本身，数组是装哈希桶的，他可以被称为**哈希表**。 |\n| capacity            | table 的容量大小，默认为 16。需要注意的是 capacity 必须保证为 2 的 n 次方。 |\n| size                | table 的实际使用量。                                         |\n| threshold           | size 的临界值，size 必须小于 threshold，如果大于等于，就必须进行扩容操作。 |\n| loadFactor          | 装载因子，table 能够使用的比例，threshold = capacity * loadFactor。 |\n| TREEIFY_THRESHOLD   | 树化阀值，哈希桶中的节点个数大于该值（默认为8）的时候将会被转为红黑树行存储结构。 |\n| UNTREEIFY_THRESHOLD | 非树化阀值，小于该值（默认为 6）的时候将再次改为单链表的格式存储 |\n\n\n\n### 3. 确定哈希桶数组索引位置\n\n很多操作都需要先确定一个键值对所在的桶下标。\n\n```java\nint hash = hash(key);\nint i = indexFor(hash, table.length);\n```\n\n（一）计算 hash 值\n\n```java\nfinal 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\n    // This function ensures that hashCodes that differ only by\n    // constant multiples at each bit position have a bounded\n    // number of collisions (approximately 8 at default load factor).\n    h ^= (h >>> 20) ^ (h >>> 12);\n    return h ^ (h >>> 7) ^ (h >>> 4);\n}\npublic final int hashCode() {\n    return Objects.hashCode(key) ^ Objects.hashCode(value);\n}\n```\n\n（二）取模\n\n令 x = 1<<4，即 x 为 2 的 4 次方，它具有以下性质：\n\n```\nx   : 00010000\nx-1 : 00001111\n```\n\n令一个数 y 与 x-1 做与运算，可以去除 y 位级表示的第 4 位以上数：\n\n```\ny       : 10110010\nx-1     : 00001111\ny&(x-1) : 00000010\n```\n\n这个性质和 y 对 x 取模效果是一样的：\n\n```\ny   : 10110010\nx   : 00010000\ny%x : 00000010\n```\n\n我们知道，位运算的代价比求模运算小的多，因此在进行这种计算时用位运算的话能带来更高的性能。\n\n确定桶下标的最后一步是将 key 的 hash 值对桶个数取模：hash%capacity，如果能保证 capacity 为 2 的 n 次方，那么就可以将这个操作转换为位运算。\n\n```java\nstatic int indexFor(int h, int length) {\n    return h & (length-1);\n}\n```\n\n\n\n### 4. 分析HashMap的put方法\n\n\n\n　　HashMap 的 put 方法执行过程可以通过下图来理解，自己有兴趣可以去对比源码更清楚地研究学习。\n\n<div align=\"center\"> <img src=\"assets/hashmap-put.png\" width=\"\"/></div><br/>\n\n①.判断键值对数组 table[i] 是否为空或为 null，否则执行 resize() 进行扩容；\n\n②.根据键值 key 计算 hash 值得到插入的数组索引i，如果 table[i]==null，直接新建节点添加，转向 ⑥，如果table[i] 不为空，转向 ③；\n\n③.判断 table[i] 的首个元素是否和 key 一样，如果相同直接覆盖 value，否则转向 ④，这里的相同指的是 hashCode 以及 equals；\n\n④.判断table[i] 是否为 treeNode，即 table[i] 是否是红黑树，如果是红黑树，则直接在树中插入键值对，否则转向 ⑤；\n\n⑤.遍历 table[i]，判断链表长度是否大于 8，大于 8 的话把链表转换为红黑树，在红黑树中执行插入操作，否则进行链表的插入操作；遍历过程中若发现 key 已经存在直接覆盖 value 即可；\n\n⑥.插入成功后，判断实际存在的键值对数量 size 是否超多了最大容量 threshold，如果超过，进行扩容。\n\nJDK1.8 HashMap 的 put 方法源码如下:\n\n```java\npublic V put(K key, V value) {\n    // 对key的hashCode()做hash\n    return putVal(hash(key), key, value, false, true);\n}\n\nfinal V putVal(int hash, K key, V value, boolean onlyIfAbsent,\n               boolean evict) {\n    Node<K,V>[] tab; Node<K,V> p; int n, i;\n    // 步骤①：tab为空则创建\n    if ((tab = table) == null || (n = tab.length) == 0)\n        n = (tab = resize()).length;\n    // 步骤②：计算index，并对null做处理 \n    if ((p = tab[i = (n - 1) & hash]) == null) \n        tab[i] = newNode(hash, key, value, null);\n    else {\n        Node<K,V> e; K k;\n        // 步骤③：节点key存在，直接覆盖value\n        if (p.hash == hash &&\n            ((k = p.key) == key || (key != null && key.equals(k))))\n            e = p;\n        // 步骤④：判断该链为红黑树\n        else if (p instanceof TreeNode)\n            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);\n        // 步骤⑤：该链为链表\n        else {\n            for (int binCount = 0; ; ++binCount) {\n                if ((e = p.next) == null) {\n                    p.next = newNode(hash, key,value,null);\n                     //链表长度大于8转换为红黑树进行处理\n                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st  \n                        treeifyBin(tab, hash);\n                    break;\n                }\n                 // key已经存在直接覆盖value\n                if (e.hash == hash &&\n                    ((k = e.key) == key || (key != null && key.equals(k)))) \n                           break;\n                p = e;\n            }\n        }\n        \n        if (e != null) { // existing mapping for key\n            V oldValue = e.value;\n            if (!onlyIfAbsent || oldValue == null)\n                e.value = value;\n            afterNodeAccess(e);\n            return oldValue;\n        }\n    }\n    ++modCount;\n    // 步骤⑥：超过最大容量 就扩容\n    if (++size > threshold)\n        resize();\n    afterNodeInsertion(evict);\n    return null;\n}\n```\n\n### 5. 扩容机制\n\n扩容 (resize) 就是重新计算容量，向 HashMap 对象里不停的添加元素，而 HashMap 对象内部的数组无法装载更多的元素时，对象就需要扩大数组的长度，以便能装入更多的元素。当然 Java 里的数组是无法自动扩容的，方法是使用一个新的数组代替已有的容量小的数组，就像我们用一个小桶装水，如果想装更多的水，就得换大水桶。\n\n我们分析下 resize 的源码，鉴于 JDK1.8 融入了红黑树，较复杂，为了便于理解我们仍然使用 JDK1.7 的代码，好理解一些，本质上区别不大，具体区别后文再说。\n\n```JAVA\nvoid resize(int newCapacity) {   //传入新的容量\n    Entry[] oldTable = table;    //引用扩容前的Entry数组\n    int oldCapacity = oldTable.length;         \n    if (oldCapacity == MAXIMUM_CAPACITY) {  //扩容前的数组大小如果已经达到最大(2^30)了\n        threshold = Integer.MAX_VALUE; //修改阈值为int的最大值(2^31-1)，这样以后就不会扩容了\n        return;\n    }\n \n    Entry[] newTable = new Entry[newCapacity];  //初始化一个新的Entry数组\n    transfer(newTable);                         //！！将数据转移到新的Entry数组里\n    table = newTable;                           //HashMap的table属性引用新的Entry数组\n    threshold = (int)(newCapacity * loadFactor);//修改阈值\n}\n```\n\n这里就是使用一个容量更大的数组来代替已有的容量小的数组，transfer() 方法将原有 Entry 数组的元素拷贝到新的 Entry 数组里。\n\n```java\nvoid transfer(Entry[] newTable) {\n    Entry[] src = table;                   //src引用了旧的Entry数组\n    int newCapacity = newTable.length;\n    for (int j = 0; j < src.length; j++) { //遍历旧的Entry数组\n        Entry<K,V> e = src[j];             //取得旧Entry数组的每个元素\n        if (e != null) {\n            src[j] = null;//释放旧Entry数组的对象引用（for循环后，旧的Entry数组不再引用任何对象）\n            do {\n                Entry<K,V> next = e.next;\n                int i = indexFor(e.hash, newCapacity); //！！重新计算每个元素在数组中的位置\n                e.next = newTable[i]; //标记[1]\n                newTable[i] = e;      //将元素放在数组上\n                e = next;             //访问下一个Entry链上的元素\n            } while (e != null);\n        }\n    }\n}\n```\n\nnewTable[i] 的引用赋给了 e.next，也就是使用了单链表的头插入方式，同一位置上新元素总会被放在链表的头部位置；这样先放在一个索引上的元素终会被放到 Entry 链的尾部(如果发生了 hash 冲突的话），这一点和 Jdk1.8 有区别，下文详解。在旧数组中同一条 Entry 链上的元素，通过重新计算索引位置后，有可能被放到了新数组的不同位置上。\n\n下面举个例子说明下扩容过程。假设了我们的 hash 算法就是简单的用 key mod 一下表的大小（也就是数组的长度）。其中的哈希桶数组 table 的 size=2， 所以 key = 3、7、5，put 顺序依次为 5、7、3。在 mod 2 以后都冲突在 table[1] 这里了。这里假设负载因子 loadFactor=1，即当键值对的实际大小 size 大于 table 的实际大小时进行扩容。接下来的三个步骤是哈希桶数组 resize 成 4，然后所有的 Node 重新 rehash 的过程。\n\n<div align=\"center\"> <img src=\"assets/jdk1.7-resize.png\" width=\"600\"/></div>\n\n下面我们讲解下 JDK1.8 做了哪些优化。经过观测可以发现，我们使用的是 2 次幂的扩展 (指长度扩为原来 2 倍)，所以，元素的位置要么是在原位置，要么是在原位置再移动 2 次幂的位置。看下图可以明白这句话的意思，n 为 table 的长度，图（a）表示扩容前的 key1 和 key2 两种 key 确定索引位置的示例，图（b）表示扩容后 key1 和 key2 两种 key 确定索引位置的示例，其中 hash1 是 key1 对应的哈希与高位运算结果。\n\n<div align=\"center\"> <img src=\"assets/hashMap-1.8-hash1.png\" width=\"700\"/></div><br/>\n\n元素在重新计算 hash 之后，因为 n 变为 2 倍，那么 n-1 的 mask 范围在高位多 1bit (红色)，因此新的 index 就会发生这样的变化：\n\n<div align=\"center\"> <img src=\"assets/hashMap-1.8-hash2.png\" width=\"600\"/></div>\n\n因此，我们在扩充 HashMap 的时候，不需要像 JDK1.7 的实现那样重新计算 hash，只需要看看原来的 hash 值新增的那个 bit 是 1 还是 0 就好了，是 0 的话索引没变，是 1 的话索引变成“原索引+oldCap”，可以看看下图为 16 扩充为 32 的 resize 示意图：\n\n<div align=\"center\"> <img src=\"assets/jdk1.8-resize.png\" width=\"700\"/></div><br/>\n\n这个设计确实非常的巧妙，既省去了重新计算 hash 值的时间，而且同时，由于新增的 1bit 是 0 还是 1 可以认为是随机的，因此 resize 的过程，均匀的把之前的冲突的节点分散到新的 bucket 了。这一块就是 JDK1.8 新增的优化点。有一点注意区别，JDK1.7 中 rehash 的时候，旧链表迁移新链表的时候，如果在新表的数组索引位置相同，则链表元素会倒置，但是从上图可以看出，JDK1.8 不会倒置。有兴趣的同学可以研究下 JDK1.8 的 resize源 码，写的很赞，如下:\n\n```java\nfinal Node<K,V>[] resize() {\n    Node<K,V>[] oldTab = table;\n    int oldCap = (oldTab == null) ? 0 : oldTab.length;\n    int oldThr = threshold;\n    int newCap, newThr = 0;\n    if (oldCap > 0) {\n        // 超过最大值就不再扩充了，就只好随你碰撞去吧\n        if (oldCap >= MAXIMUM_CAPACITY) {\n            threshold = Integer.MAX_VALUE;\n            return oldTab;\n        }\n        // 没超过最大值，就扩充为原来的2倍\n        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&\n                 oldCap >= DEFAULT_INITIAL_CAPACITY)\n            newThr = oldThr << 1; // double threshold\n    }\n    else if (oldThr > 0) // initial capacity was placed in threshold\n        newCap = oldThr;\n    else {               // zero initial threshold signifies using defaults\n        newCap = DEFAULT_INITIAL_CAPACITY;\n        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);\n    }\n    // 计算新的resize上限\n    if (newThr == 0) {\n\n        float ft = (float)newCap * loadFactor;\n        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?\n                  (int)ft : Integer.MAX_VALUE);\n    }\n    threshold = newThr;\n    @SuppressWarnings({\"rawtypes\"，\"unchecked\"})\n        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];\n    table = newTab;\n    if (oldTab != null) {\n        // 把每个bucket都移动到新的buckets中\n        for (int j = 0; j < oldCap; ++j) {\n            Node<K,V> e;\n            if ((e = oldTab[j]) != null) {\n                oldTab[j] = null;\n                if (e.next == null)\n                    newTab[e.hash & (newCap - 1)] = e;\n                else if (e instanceof TreeNode)\n                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);\n                else { // 链表优化重hash的代码块\n                    Node<K,V> loHead = null, loTail = null;\n                    Node<K,V> hiHead = null, hiTail = null;\n                    Node<K,V> next;\n                    do {\n                        next = e.next;\n                        // 原索引\n                        if ((e.hash & oldCap) == 0) {\n                            if (loTail == null)\n                                loHead = e;\n                            else\n                                loTail.next = e;\n                            loTail = e;\n                        }\n                        // 原索引+oldCap\n                        else {\n                            if (hiTail == null)\n                                hiHead = e;\n                            else\n                                hiTail.next = e;\n                            hiTail = e;\n                        }\n                    } while ((e = next) != null);\n                    // 原索引放到bucket里\n                    if (loTail != null) {\n                        loTail.next = null;\n                        newTab[j] = loHead;\n                    }\n                    // 原索引+oldCap放到bucket里\n                    if (hiTail != null) {\n                        hiTail.next = null;\n                        newTab[j + oldCap] = hiHead;\n                    }\n                }\n            }\n        }\n    }\n    return newTab;\n}\n```\n\n### 6. 线程安全性\n\n在多线程使用场景中，应该尽量避免使用线程不安全的 HashMap，而使用线程安全的 ConcurrentHashMap。那么为什么说 HashMap 是线程不安全的，下面举例子说明在并发的多线程使用场景中使用 HashMap 可能造成死循环。代码例子如下(便于理解，仍然使用 JDK1.7 的环境)：\n\n```java\npublic class HashMapInfiniteLoop {  \n\n    private static HashMap<Integer,String> map = new HashMap<Integer,String>(2，0.75f);  \n    public static void main(String[] args) {  \n        map.put(5， \"C\");  \n\n        new Thread(\"Thread1\") {  \n            public void run() {  \n                map.put(7, \"B\");  \n                System.out.println(map);  \n            };  \n        }.start();  \n        new Thread(\"Thread2\") {  \n            public void run() {  \n                map.put(3, \"A);  \n                System.out.println(map);  \n            };  \n        }.start();        \n    }  \n}\n```\n\n其中，map初始化为一个长度为2的数组，loadFactor=0.75，threshold=2*0.75=1，也就是说当put第二个key的时候，map就需要进行resize。\n\n通过设置断点让线程1和线程2同时debug到transfer方法(3.3小节代码块)的首行。注意此时两个线程已经成功添加数据。放开thread1的断点至transfer方法的“Entry next = e.next;” 这一行；然后放开线程2的的断点，让线程2进行resize。结果如下图。\n\n<div align=\"center\"> <img src=\"assets/jdk1.7-drop-dead-1.png\" width=\"600\"/></div>\n\n注意，Thread1的 e 指向了key(3)，而next指向了key(7)，其在线程二rehash后，指向了线程二重组后的链表。\n\n线程一被调度回来执行，先是执行 newTalbe[i] = e， 然后是e = next，导致了e指向了key(7)，而下一次循环的next = e.next导致了next指向了key(3)。\n\n<div align=\"center\"> <img src=\"assets/jdk1.7-drop-dead-2.png\" width=\"600\"/></div>\n\n\n\n<div align=\"center\"> <img src=\"assets/jdk1.7-drop-dead-3.png\" width=\"600\"/></div>\n\ne.next = newTable[i] 导致 key(3).next 指向了 key(7)。注意：此时的key(7).next 已经指向了key(3)， 环形链表就这样出现了。\n\n\n\n<div align=\"center\"> <img src=\"assets/jdk1.7-drop-dead-5.png\" width=\"600\"/></div>\n\n于是，当我们用线程一调用map.get(11)时，悲剧就出现了——Infinite Loop。\n\n### 7. JDK1.8与JDK1.7的性能对比\n\nHashMap中，如果key经过hash算法得出的数组索引位置全部不相同，即Hash算法非常好，那样的话，getKey方法的时间复杂度就是O(1)，如果Hash算法技术的结果碰撞非常多，假如Hash算极其差，所有的Hash算法结果得出的索引位置一样，那样所有的键值对都集中到一个桶中，或者在一个链表中，或者在一个红黑树中，时间复杂度分别为O(n)和O(lgn)。 鉴于JDK1.8做了多方面的优化，总体性能优于JDK1.7，下面我们从两个方面用例子证明这一点。\n\n### 8. Hash较均匀的情况\n\n为了便于测试，我们先写一个类Key，如下：\n\n```java\nclass Key implements Comparable<Key> {\n\n    private final int value;\n\n    Key(int value) {\n        this.value = value;\n    }\n\n    @Override\n    public int compareTo(Key o) {\n        return Integer.compare(this.value, o.value);\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass())\n            return false;\n        Key key = (Key) o;\n        return value == key.value;\n    }\n\n    @Override\n    public int hashCode() {\n        return value;\n    }\n}\n```\n\n这个类复写了equals方法，并且提供了相当好的hashCode函数，任何一个值的hashCode都不会相同，因为直接使用value当做hashcode。为了避免频繁的GC，我将不变的Key实例缓存了起来，而不是一遍一遍的创建它们。代码如下：\n\n```java\npublic class Keys {\n\n    public static final int MAX_KEY = 10_000_000;\n    private static final Key[] KEYS_CACHE = new Key[MAX_KEY];\n\n    static {\n        for (int i = 0; i < MAX_KEY; ++i) {\n            KEYS_CACHE[i] = new Key(i);\n        }\n    }\n\n    public static Key of(int value) {\n        return KEYS_CACHE[value];\n    }\n}\n```\n\n现在开始我们的试验，测试需要做的仅仅是，创建不同size的HashMap（1、10、100、......10000000），屏蔽了扩容的情况，代码如下：\n\n```java\nstatic void test(int mapSize) {\n\n    HashMap<Key, Integer> map = new HashMap<Key,Integer>(mapSize);\n    for (int i = 0; i < mapSize; ++i) {\n        map.put(Keys.of(i), i);\n    }\n\n    long beginTime = System.nanoTime(); //获取纳秒\n    for (int i = 0; i < mapSize; i++) {\n        map.get(Keys.of(i));\n    }\n    long endTime = System.nanoTime();\n    System.out.println(endTime - beginTime);\n}\n\npublic static void main(String[] args) {\n    for(int i=10;i<= 1000 0000;i*= 10){\n        test(i);\n    }\n}\n```\n\n在测试中会查找不同的值，然后度量花费的时间，为了计算getKey的平均时间，我们遍历所有的get方法，计算总的时间，除以key的数量，计算一个平均值，主要用来比较，绝对值可能会受很多环境因素的影响。结果如下：\n\n<div align=\"center\"> <img src=\"assets/hashmap-compare1.png\" width=\"\"/></div><br/>\n\n通过观测测试结果可知，JDK1.8的性能要高于JDK1.7 15%以上，在某些size的区域上，甚至高于100%。由于Hash算法较均匀，JDK1.8引入的红黑树效果不明显，下面我们看看Hash不均匀的的情况。\n\n### 9. Hash极不均匀的情况\n\n假设我们又一个非常差的Key，它们所有的实例都返回相同的hashCode值。这是使用HashMap最坏的情况。代码修改如下：\n\n```java\nclass Key implements Comparable<Key> {\n\n    //...\n\n    @Override\n    public int hashCode() {\n        return 1;\n    }\n}\n```\n\n仍然执行main方法，得出的结果如下表所示：\n\n<div align=\"center\"> <img src=\"assets/hashmap-compare2.png\" width=\"\"/></div><br/>\n\n从表中结果中可知，随着size的变大，JDK1.7的花费时间是增长的趋势，而JDK1.8是明显的降低趋势，并且呈现对数增长稳定。当一个链表太长的时候，HashMap会动态的将它替换成一个红黑树，这话的话会将时间复杂度从O(n)降为O(logn)。hash算法均匀和不均匀所花费的时间明显也不相同，这两种情况的相对比较，可以说明一个好的hash算法的重要性。\n\n测试环境：处理器为2.2 GHz Intel Core i7，内存为16 GB 1600 MHz DDR3，SSD硬盘，使用默认的JVM参数，运行在64位的OS X 10.10.1上。\n\n\n\n### 10. HashMap与Hashtable\n\n1. Hashtable 使用 synchronized 来进行同步。\n2. HashMap 可以插入键为 null 的 Entry。\n3. HashMap 的迭代器是 fail-fast 迭代器。\n4. HashMap 不能保证随着时间的推移 Map 中的元素次序是不变的。\n\n\n\n### 11. 小结\n\n1. 扩容是一个特别耗性能的操作，所以当程序员在使用 HashMap 的时候，估算 map 的大小，初始化的时候给一个大致的数值，避免 map 进行频繁的扩容。\n2. 负载因子是可以修改的，也可以大于1，但是建议不要轻易修改，除非情况非常特殊。\n3. HashMap 是线程不安全的，不要在并发的环境中同时操作 HashMap，建议使用 ConcurrentHashMap。\n4. JDK1.8 引入红黑树大程度优化了 HashMap 的性能。\n\n\n\n参考资料：\n\n- [Java 8系列之重新认识HashMap——美团技术](https://tech.meituan.com/java_hashmap.html)\n- [搞懂 Java HashMap 源码 - 掘金](https://juejin.im/post/5ac83fa35188255c5668afd0)\n- [搞懂 Java equals 和 hashCode 方法 - 掘金](https://juejin.im/post/5ac4d8abf265da23a4050ae3)\n\n\n\n\n\n## ConcurrentHashMap\n\n### 1. 概述　　\n\n　　众所周知，哈希表是中非常高效，复杂度为 O(1) 的数据结构，在 Java 开发中，我们最常见到最频繁使用的就是 HashMap 和 Hashtable，但是在线程竞争激烈的并发场景中使用都不够合理。\n\n　　**HashMap** ：先说 HashMap，HashMap 是**线程不安全**的，在并发环境下，可能会形成**环状链表**（扩容时可能造成），导致 get 操作时，cpu 空转，所以，在并发环境中使 用HashMap 是非常危险的。\n\n　　**Hashtable** ： Hashtable 和 HashMap的实现原理几乎一样，差别无非是：（1）Hashtable不允许key和value为null；（2）Hashtable是线程安全的。\n\n　　但是 Hashtable 线程安全的策略实现代价却太大了，简单粗暴，get/put 所有相关操作都是 synchronized 的，这相当于给整个哈希表加了一把大锁，多线程访问时候，只要有一个线程访问或操作该对象，那其他线程只能阻塞，相当于将所有的操作串行化，在竞争激烈的并发场景中性能就会非常差。\n\n<div align=\"center\"> <img src=\"assets/hashtable-ds.png\" width=\"600\"/></div>\n\n　　Hashtable 性能差主要是由于所有操作需要竞争同一把锁，而如果容器中有多把锁，每一把锁锁一段数据，这样在多线程访问时不同段的数据时，就不会存在锁竞争了，这样便可以有效地提高并发效率。这就是ConcurrentHashMap 所采用的 \"**分段锁**\" 思想。\n\n<div align=\"center\"> <img src=\"assets/hashmap-ds.png\" width=\"600\"/></div><br/>\n\n### 2. 存储结构\n\nConcurrentHashMap 采用了非常精妙的\"分段锁\"策略，ConcurrentHashMap 的主干是个 Segment 数组。\n\n```java\n final Segment<K,V>[] segments;\n```\n\n　　Segment 继承了 ReentrantLock，所以它就是一种可重入锁（ReentrantLock)。在 ConcurrentHashMap，一个 Segment 就是一个子哈希表，Segment 里维护了一个 HashEntry 数组，并发环境下，对于不同 Segment 的数据进行操作是不用考虑锁竞争的。（就按默认的 ConcurrentLeve 为16来讲，理论上就允许 16 个线程并发执行，有木有很酷）\n\n　　**所以，对于同一个 Segment 的操作才需考虑线程同步，不同的 Segment 则无需考虑。**\n\nSegment 类似于 HashMap，一个 Segment 维护着一个 HashEntry 数组\n\n```java\ntransient volatile HashEntry<K,V>[] table;\n```\n\nHashEntry 是目前我们提到的最小的逻辑处理单元了。一个 ConcurrentHashMap 维护一个 Segment 数组，一个 Segment 维护一个 HashEntry 数组。\n\n```java\nstatic final class HashEntry<K,V> {\n    final int hash;\n    final K key;\n    volatile V value;\n    volatile HashEntry<K,V> next;\n}\n```\n\n　　ConcurrentHashMap 和 HashMap 实现上类似，最主要的差别是 ConcurrentHashMap 采用了分段锁（Segment），每个分段锁维护着几个桶（HashEntry），多个线程可以同时访问不同分段锁上的桶，从而使其并发度更高（并发度就是 Segment 的个数）。\n\nSegment 继承自 **ReentrantLock**。\n\n```java\nstatic final class Segment<K,V> extends ReentrantLock implements Serializable {\n\n    private static final long serialVersionUID = 2249069246763182397L;\n\n    static final int MAX_SCAN_RETRIES =\n        Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;\n\n    transient volatile HashEntry<K,V>[] table;\n\n    transient int count;\n\n    transient int modCount;\n\n    transient int threshold;\n\n    final float loadFactor;\n}\n```\n\n```java\nfinal Segment<K,V>[] segments;\n```\n\n默认的并发级别为 16，也就是说默认创建 16 个 Segment。\n\n```java\nstatic final int DEFAULT_CONCURRENCY_LEVEL = 16;\n```\n\n\n\n### 2. size 操作\n\n每个 Segment 维护了一个 count 变量来统计该 Segment 中的键值对个数。\n\n```java\n/**\n * The number of elements. Accessed only either within locks\n * or among other volatile reads that maintain visibility.\n */\ntransient int count;\n```\n\n在执行 size 操作时，需要遍历所有 Segment 然后把 count 累计起来。\n\nConcurrentHashMap 在执行 size 操作时先尝试不加锁，如果连续两次不加锁操作得到的结果一致，那么可以认为这个结果是正确的。\n\n尝试次数使用 RETRIES_BEFORE_LOCK 定义，该值为 2，retries 初始值为 -1，因此尝试次数为 3。\n\n如果尝试的次数超过 3 次，就需要对每个 Segment 加锁。\n\n```java\n/**\n * Number of unsynchronized retries in size and containsValue\n * methods before resorting to locking. This is used to avoid\n * unbounded retries if tables undergo continuous modification\n * which would make it impossible to obtain an accurate result.\n */\nstatic final int RETRIES_BEFORE_LOCK = 2;\n\npublic int size() {\n    // Try a few times to get accurate count. On failure due to\n    // continuous async changes in table, resort to locking.\n    final Segment<K,V>[] segments = this.segments;\n    int size;\n    boolean overflow; // true if size overflows 32 bits\n    long sum;         // sum of modCounts\n    long last = 0L;   // previous sum\n    int retries = -1; // first iteration isn't retry\n    try {\n        for (;;) {\n            // 超过尝试次数，则对每个 Segment 加锁\n            if (retries++ == RETRIES_BEFORE_LOCK) {\n                for (int j = 0; j < segments.length; ++j)\n                    ensureSegment(j).lock(); // force creation\n            }\n            sum = 0L;\n            size = 0;\n            overflow = false;\n            for (int j = 0; j < segments.length; ++j) {\n                Segment<K,V> seg = segmentAt(segments, j);\n                if (seg != null) {\n                    sum += seg.modCount;\n                    int c = seg.count;\n                    if (c < 0 || (size += c) < 0)\n                        overflow = true;\n                }\n            }\n            // 连续两次得到的结果一致，则认为这个结果是正确的\n            if (sum == last)\n                break;\n            last = sum;\n        }\n    } finally {\n        if (retries > RETRIES_BEFORE_LOCK) {\n            for (int j = 0; j < segments.length; ++j)\n                segmentAt(segments, j).unlock();\n        }\n    }\n    return overflow ? Integer.MAX_VALUE : size;\n}\n```\n\n### 3. 同步方式\n\nSegment 继承自 ReentrantLock，所以我们可以很方便的对每一个 Segment 上锁。\n\n对于读操作，获取 Key 所在的 Segment 时，需要保证可见性。具体实现上可以使用 volatile 关键字，也可使用锁。但使用锁开销太大，而使用 volatile 时每次写操作都会让所有 CPU 内缓存无效，也有一定开销。ConcurrentHashMap 使用如下方法保证可见性，取得最新的 Segment。\n\n```java\nSegment<K,V> s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)\n```\n\n获取 Segment 中的 HashEntry 时也使用了类似方法\n\n```java\nHashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile\n  (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE)\n```\n\n对于写操作，并不要求同时获取所有 Segment 的锁，因为那样相当于锁住了整个 Map。它会先获取该 Key-Value 对所在的 Segment 的锁，获取成功后就可以像操作一个普通的 HashMap 一样操作该 Segment，并保证该Segment 的安全性。\n同时由于其它 Segment 的锁并未被获取，因此理论上可支持 concurrencyLevel（等于 Segment 的个数）个线程安全的并发读写。\n\n获取锁时，并不直接使用 lock 来获取，因为该方法获取锁失败时会挂起。事实上，它使用了自旋锁，如果 tryLock 获取锁失败，说明锁被其它线程占用，此时通过循环再次以 tryLock 的方式申请锁。如果在循环过程中该 Key 所对应的链表头被修改，则重置 retry 次数。如果 retry 次数超过一定值，则使用 lock 方法申请锁。\n\n这里使用自旋锁是因为自旋锁的效率比较高，但是它消耗 CPU 资源比较多，因此在自旋次数超过阈值时切换为互斥锁。\n\n\n\n### 4. JDK 1.8 的改动\n\n- JDK 1.7 使用分段锁机制来实现并发更新操作，核心类为 Segment，它继承自重入锁 ReentrantLock，并发程度与 Segment 数量相等。\n\n- JDK 1.8 使用了 CAS 操作来支持更高的并发度，在 CAS 操作失败时使用内置锁 synchronized。\n\n- 并且 JDK 1.8 的实现也在链表过长时会转换为红黑树。\n\n\n\n参考资料：\n\n- [ConcurrentHashMap演进从Java7到Java8](http://www.jasongj.com/java/concurrenthashmap/)\n\n- [ConcurrentHashMap实现原理及源码分析 - dreamcatcher-cx - 博客园](https://www.cnblogs.com/chengxiao/p/6842045.html)\n\n\n\n\n## HashSet\n\n　　前面已经说过 HashSet 是对 HashMap 的简单包装，对 HashSet 的函数调用都会转换成合适的 HashMap 方法，因此 HashSet 的实现非常简单，只有不到 300 行代码（适配器模式）。这里不再赘述。\n\n```java\n//HashSet是对HashMap的简单包装\npublic class HashSet<E>\n{\n\t......\n\tprivate transient HashMap<E,Object> map;//HashSet里面有一个HashMap\n    // Dummy value to associate with an Object in the backing Map\n    private static final Object PRESENT = new Object();\n    public HashSet() {\n        map = new HashMap<>();\n    }\n    ......\n    public boolean add(E e) {//简单的方法转换\n        return map.put(e, PRESENT)==null;\n    }\n    ......\n}\n```\n\n\n\n### 1. 成员变量\n\n首先了解下 `HashSet` 的成员变量:\n\n```java\n    private transient HashMap<E,Object> map;\n\n    // Dummy value to associate with an Object in the backing Map\n    private static final Object PRESENT = new Object();\n```\n\n发现主要就两个变量:\n\n- `map` ：用于存放最终数据的。\n- `PRESENT` ：是所有写入 map 的 `value` 值。\n\n\n\n### 2. 构造函数\n\n```java\n    public HashSet() {\n        map = new HashMap<>();\n    }\n    \n    public HashSet(int initialCapacity, float loadFactor) {\n        map = new HashMap<>(initialCapacity, loadFactor);\n    }    \n```\n\n构造函数很简单，利用了 `HashMap` 初始化了 `map` 。\n\n\n\n### 3. add()\n\n```java\n    public boolean add(E e) {\n        return map.put(e, PRESENT)==null;\n    }\n```\n\n比较关键的就是这个 `add()` 方法。 可以看出它是将存放的对象当做了 `HashMap` 的健，`value` 都是相同的 `PRESENT` 。由于 `HashMap` 的 `key` 是不能重复的，所以每当有重复的值写入到 `HashSet` 时，`value` 会被覆盖，但 `key` 不会收到影响，这样就保证了 `HashSet` 中只能存放不重复的元素。\n\n\n\n### 4. 总结\n\n`HashSet` 的原理比较简单，几乎全部借助于 `HashMap` 来实现的。\n\n所以 `HashMap` 会出现的问题 `HashSet` 依然不能避免。\n\n\n\n## LinkedHashSet and LinkedHashMap\n\n### 1. 概览\n\n　　如果你已看过前面关于 HashSet 和 HashMap，的讲解，一定能够想到本文将要讲解的 LinkedHashSet 和 LinkedHashMap 其实也是一回事。 LinkedHashSet 和 LinkedHashMap 在 Java 里也有着相同的实现，前者仅仅是对后者做了一层包装，也就是说 LinkedHashSet 里面有一个 LinkedHashMap（**适配器模式**）。因此本文将重点分析 LinkedHashMap。\n\n　　LinkedHashMap 实现了 Map 接口，即允许放入 key 为 null 的元素，也允许插入 value 为 null 的元素。从名字上可以看出该容器是 LinkedList 和 HashMap 的混合体，也就是说它同时满足 HashMap 和 LinkedList 的某些特性。**可将 LinkedHashMap 看作采用 LinkedList 增强的 HashMap。**\n\n<div align=\"center\"> <img src=\"assets/LinkedHashMap_base.png\" width=\"650\"/></div><br/>\n\n\n\n事实上 LinkedHashMap 是 HashMap 的直接子类，**二者唯一的区别是 LinkedHashMap 在 HashMap 的基础上，采用双向链表（doubly-linked list）的形式将所有 entry 连接起来，这样是为保证元素的迭代顺序跟插入顺序相同**。上图给出了 LinkedHashMap 的结构图，主体部分跟 HashMap 完全一样，多了 `header` 指向双向链表的头部（是一个哑元），**该双向链表的迭代顺序就是 entry 的插入顺序**。\n\n除了可以保迭代历顺序，这种结构还有一个好处：**迭代 LinkedHashMap 时不需要像 HashMap 那样遍历整个table，而只需要直接遍历 header 指向的双向链表即可**，也就是说 LinkedHashMap 的迭代时间就只跟`entry`的个数相关，而跟`table`的大小无关。\n\n有两个参数可以影响 LinkedHashMap 的性能：**初始容量**（inital capacity）和**负载系数**（load factor）。初始容量指定了初始`table`的大小，负载系数用来指定自动扩容的临界值。当`entry`的数量超过`capacity*load_factor`时，容器将自动扩容并重新哈希。对于插入元素较多的场景，将初始容量设大可以减少重新哈希的次数。\n\n将对象放入到 LinkedHashMap 或 LinkedHashSet 中时，有两个方法需要特别关心：`hashCode()` 和 `equals()`。**hashCode() 方法决定了对象会被放到哪个 bucket 里，当多个对象的哈希值冲突时，equals() 方法决定了这些对象是否是“同一个对象”**。所以，如果要将自定义的对象放入到 `LinkedHashMap` 或 `LinkedHashSet` 中，需要 *@Override*`hashCode()` 和 `equals()` 方法。\n\n通过如下方式可以得到一个跟源 Map 迭代顺序 一样的 LinkedHashMap：\n\n```java\nvoid foo(Map m) {\n    Map copy = new LinkedHashMap(m);\n    ...\n}\n```\n\n出于性能原因，LinkedHashMap 是非同步的（not synchronized），如果需要在多线程环境使用，需要程序员手动同步；或者通过如下方式将 LinkedHashMap 包装成（wrapped）同步的：\n\n`Map m = Collections.synchronizedMap(new LinkedHashMap(...));`\n\n\n\n### 2. get()\n\n`get(Object key)` 方法根据指定的 `key` 值返回对应的 `value`。该方法跟`HashMap.get()`方法的流程几乎完全一样，读者可自行[参考前文](https://github.com/CarpenterLee/JCFInternals/blob/master/markdown/6-HashSet%20and%20HashMap.md#get)，这里不再赘述。\n\n\n\n### 3. put()\n\n`put(K key, V value)` 方法是将指定的 `key, value` 对添加到 `map` 里。该方法首先会对 `map` 做一次查找，看是否包含该元组，如果已经包含则直接返回，查找过程类似于`get()`方法；如果没有找到，则会通过 `addEntry(int hash, K key, V value, int bucketIndex)` 方法插入新的 `entry`。\n\n注意，这里的**插入有两重含义**：\n\n> 1. 从 table 的角度看，新的 entry 需要插入到对应的 bucket 里，当有哈希冲突时，采用头插法将新的 entry 插入到冲突链表的头部。\n> 2. 从 header 的角度看，新的 entry 需要插入到双向链表的尾部。\n\n<div align=\"center\"> <img src=\"assets/LinkedHashMap_addEntry.png\" width=\"650\"/></div>\n\n`addEntry()`代码如下：\n\n```java\n// LinkedHashMap.addEntry()\nvoid 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 = hash & (table.length-1);// hash%table.length\n    }\n    // 1.在冲突链表头部插入新的entry\n    HashMap.Entry<K,V> old = table[bucketIndex];\n    Entry<K,V> e = new Entry<>(hash, key, value, old);\n    table[bucketIndex] = e;\n    // 2.在双向链表的尾部插入新的entry\n    e.addBefore(header);\n    size++;\n}\n```\n\n上述代码中用到了 `addBefore()`方 法将新 `entry e` 插入到双向链表头引用 `header` 的前面，这样 `e` 就成为双向链表中的最后一个元素。`addBefore()` 的代码如下：\n\n```java\n// LinkedHashMap.Entry.addBefor()，将this插入到existingEntry的前面\nprivate void addBefore(Entry<K,V> existingEntry) {\n    after  = existingEntry;\n    before = existingEntry.before;\n    before.after = this;\n    after.before = this;\n}\n```\n\n上述代码只是简单修改相关 `entry` 的引用而已。\n\n\n\n### 4. remove()\n\n`remove(Object key)`的作用是删除`key`值对应的`entry`，该方法的具体逻辑是在`removeEntryForKey(Object key)`里实现的。`removeEntryForKey()`方法会首先找到`key`值对应的`entry`，然后删除该`entry`（修改链表的相应引用）。查找过程跟`get()`方法类似。\n\n注意，这里的**删除也有两重含义**：\n\n> 1. 从`table`的角度看，需要将该`entry`从对应的`bucket`里删除，如果对应的冲突链表不空，需要修改冲突链表的相应引用。\n> 2. 从`header`的角度来看，需要将该`entry`从双向链表中删除，同时修改链表中前面以及后面元素的相应引用。\n\n\n\n<div align=\"center\"> <img src=\"assets/LinkedList_remove.png\" width=\"700\"/></div><br/>\n\n`removeEntryForKey()` 对应的代码如下：\n\n```javascript\n// LinkedHashMap.removeEntryForKey()，删除key值对应的entry\nfinal Entry<K,V> removeEntryForKey(Object key) {\n\t......\n\tint hash = (key == null) ? 0 : hash(key);\n    int i = indexFor(hash, table.length);// hash&(table.length-1)\n    Entry<K,V> prev = table[i];// 得到冲突链表\n    Entry<K,V> e = prev;\n    while (e != null) {// 遍历冲突链表\n        Entry<K,V> next = e.next;\n        Object k;\n        if (e.hash == hash &&\n            ((k = e.key) == key || (key != null && key.equals(k)))) {// 找到要删除的entry\n            modCount++; size--;\n            // 1. 将e从对应bucket的冲突链表中删除\n            if (prev == e) table[i] = next;\n            else prev.next = next;\n            // 2. 将e从双向链表中删除\n            e.before.after = e.after;\n            e.after.before = e.before;\n            return e;\n        }\n        prev = e; e = next;\n    }\n    return e;\n}\n```\n\n\n\n### 5. LinkedHashSet\n\n前面已经说过*LinkedHashSet*是对*LinkedHashMap*的简单包装，对*LinkedHashSet*的函数调用都会转换成合适的*LinkedHashMap*方法，因此*LinkedHashSet*的实现非常简单，这里不再赘述。\n\n```java\npublic class LinkedHashSet<E>\n    extends HashSet<E>\n    implements Set<E>, Cloneable, java.io.Serializable {\n    ......\n    // LinkedHashSet里面有一个LinkedHashMap\n    public LinkedHashSet(int initialCapacity, float loadFactor) {\n        map = new LinkedHashMap<>(initialCapacity, loadFactor);\n    }\n\t......\n    public boolean add(E e) {//简单的方法转换\n        return map.put(e, PRESENT)==null;\n    }\n    ......\n}\n```\n\n\n\n### 6. LinkedHashMap经典用法\n\nLinkedHashMap 除了可以保证迭代顺序外，还有一个非常有用的用法：可以轻松实现一个采用了FIFO替换策略的缓存。具体说来，LinkedHashMap 有一个子类方法 `protected boolean removeEldestEntry(Map.Entry<K,V> eldest)`，该方法的作用是告诉 Map 是否要删除“最老”的 Entry，所谓最老就是当前 Map 中最早插入的 Entry，如果该方法返回 true，最老的那个元素就会被删除。在每次插入新元素的之后 LinkedHashMap 会自动询问 removeEldestEntry() 是否要删除最老的元素。这样只需要在子类中重载该方法，当元素个数超过一定数量时让 removeEldestEntry() 返回 true，就能够实现一个固定大小的 FIFO 策略的缓存。示例代码如下：\n\n```java\n/** 一个固定大小的FIFO替换策略的缓存 */\nclass FIFOCache<K, V> extends LinkedHashMap<K, V>{\n    private final int cacheSize;\n    public FIFOCache(int cacheSize){\n        this.cacheSize = cacheSize;\n    }\n\n    // 当Entry个数超过cacheSize时，删除最老的Entry\n    @Override\n    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {\n       return size() > cacheSize;\n    }\n}\n```\n\n\n\n\n\n# 三、容器中的设计模式\n\n## 迭代器模式\n\n<div align=\"center\"> <img src=\"assets/Iterator-1.jpg\" width=\"\"/></div><br/>\n\nCollection 实现了 Iterable 接口，其中的 iterator() 方法能够产生一个 Iterator 对象，通过这个对象就可以迭代遍历 Collection 中的元素。\n\n从 JDK 1.5 之后可以使用 foreach 方法来遍历实现了 Iterable 接口的聚合对象。\n\n```java\nList<String> list = new ArrayList<>();\nlist.add(\"a\");\nlist.add(\"b\");\nfor (String item : list) {\n    System.out.println(item);\n}\n```\n\n\n\n## 适配器模式\n\njava.util.Arrays.asList() 可以把数组类型转换为 List 类型。\n\n```java\n@SafeVarargs\npublic static <T> List<T> asList(T... a)\n```\n\n如果要将数组类型转换为 List 类型，应该注意的是 asList() 的参数为泛型的变长参数，因此不能使用基本类型数组作为参数，只能使用相应的包装类型数组。\n\n```java\nInteger[] arr = {1, 2, 3};\nList list = Arrays.asList(arr);\n```\n\n也可以使用以下方式生成 List。\n\n```java\nList list = Arrays.asList(1,2,3);\n```\n\n\n\n# 四、面试指南\n\n## 1. ArrayList和LinkedList区别\n\n- ArrayList 和 LinkedList 可想从名字分析，它们一个是 Array (动态数组) 的数据结构，一个是 Link (链表) 的数据结构，此外，它们两个都是对 List 接口的实现。前者是数组队列，相当于动态数组；后者为双向链表结构，也可当作堆栈、队列、双端队列；\n- **当随机访问 List 时**（get和set操作），ArrayList 比 LinkedList的效率更高，因为 LinkedList 是线性的数据存储方式，所以需要移动指针从前往后依次查找；\n- **当对数据进行增加和删除的操作时**（add 和 remove 操作），LinkedList 比 ArrayList 的效率更高，因为 ArrayList 是数组，所以在其中进行增删操作时，会对操作点之后所有数据的下标索引造成影响，需要进行数据的移动；\n- **从利用效率来看**，ArrayList 自由性较低，因为它需要手动的设置固定大小的容量，但是它的使用比较方便，只需要创建，然后添加数据，通过调用下标进行使用；而 LinkedList 自由性较高，能够动态的随数据量的变化而变化，但是它不便于使用；\n- ArrayList 主要空间开销在于需要在 List 列表预留一定空间；而 LinkList 主要控件开销在于需要存储结点信息以及结点指针信息。\n\n\n\n- **ArrayList、LinkedList 和 Vector如何选择？**\n  - 当对数据的主要操作为索引或只在集合的末端增加、删除元素时，使用 ArrayList 或 Vector 效率比较高；\n  - 当对数据的操作主要为指定位置的插入或删除操作时，使用 LinkedList 效率比较高；\n  - 当在多线程中使用容器时（即多个线程会同时访问该容器），选用 Vector 较为安全；\n\n\n\n## 2. HashMap和Hashtable区别，HashMap的key类型\n\n- **HashMap和Hashtable的区别**  \n\n  - Hashtable 的方法是同步的，HashMap 非同步，所以在多线程场合要手动同步。\n  - Hashtable 不允许 null 值 (key 和 value 都不可以)，HashMap 允许 null 值( key 和 value 都可以)。 \n  - 两者的遍历方式大同小异，Hashtable 仅仅比 HashMap 多一个 elements 方法。 \n\n  - Hashtable 和 HashMap 都能通过 values() 方法返回一个 Collection ，然后进行遍历处理。 \n\n  - 两者也都可以通过 entrySet() 方法返回一个 Set ， 然后进行遍历处理。 \n\n  - Hashtable 使用 Enumeration，HashMap 使用 Iterator。 \n  - 哈希值的使用不同，Hashtable 直接使用对象的 hashCode。而 HashMap 重新计算hash值，而且用于代替求模。 \n  - Hashtable 中 hash 数组默认大小是11，增加的方式是 old*2+1。HashMap 中 hash 数组的默认大小是16，而且一定是 2 的指数。 \n  - Hashtable 基于 Dictionary 类，而 HashMap 基于 AbstractMap 类 \n\n- **HashMap中的key可以是任何对象或数据类型吗** \n\n  - 可以为null，但不能是可变对象，如果是可变对象的话，对象中的属性改变，则对象 HashCode 也进行相应的改变，导致下次无法查找到已存在Map中的数据。 \n  - 如果可变对象在 HashMap 中被用作键，那就要小心在改变对象状态的时候，不要改变它的哈希值了。我们只需要保证成员变量的改变能保证该对象的哈希值不变即可。 \n\n- **Hashtable是线程安全的么** \n\n  - Hashtable 是线程安全的，其实现是在对应的方法上添加了 synchronized 关键字进行修饰，由于在执行此方法的时候需要获得对象锁，则执行起来比较慢。所以现在如果为了保证线程安全的话，使用 CurrentHashMap。 \n\n\n\n## 3. HashMap和ConcurrentHashMap\n\n- **HashMap和Concurrent HashMap区别？** \n  - HashMap 是非线程安全的，CurrentHashMap 是线程安全的。 \n  - ConcurrentHashMap 将整个 Hash 桶进行了分段 segment，也就是将这个大的数组分成了几个小的片段segment，而且每个小的片段 segment 上面都有锁存在，那么在插入元素的时候就需要先找到应该插入到哪一个片段 segment，然后再在这个片段上面进行插入，而且这里还需要获取 segment 锁。 \n  - ConcurrentHashMap 让锁的粒度更精细一些，并发性能更好。 \n- **ConcurrentHashMap 线程安全吗， ConcurrentHashMap如何保证 线程安全？** \n  - Hashtable 容器在竞争激烈的并发环境下表现出效率低下的原因是所有访问 Hashtable 的线程都必须竞争同一把锁，那假如容器里有多把锁，每一把锁用于锁容器其中一部分数据，那么当多线程访问容器里不同数据段的数据时，线程间就不会存在锁竞争，从而可以有效的提高并发访问效率，这就是 ConcurrentHashMap 所使用的**分段锁**，首先将数据分成一段一段的存储，然后给每一段数据配一把锁，当一个线程占用锁访问其中一个段数据的时候，其他段的数据也能被其他线程访问。 \n  - get 操作的高效之处在于整个 get 过程不需要加锁，除非读到的值是空的才会加锁重读。**get 方法里将要使用的共享变量都定义成 volatile**，如用于统计当前 Segement 大小的 count 字段和用于存储值的 HashEntry 的 value。定义成 volatile 的变量，能够在线程之间保持可见性，能够被多线程同时读，并且保证不会读到过期的值，但是只能被单线程写（有一种情况可以被多线程写，就是写入的值不依赖于原值），在 get 操作里只需要读不需要写共享变量 count 和 value，所以可以不用加锁。 \n  - put 方法首先定位到 Segment，然后在 Segment 里进行插入操作。\n    - 插入操作需要经历两个步骤：（1）判断是否需要对 Segment 里的 HashEntry 数组进行扩容；（2）定位添加元素的位置然后放在HashEntry数组里。 \n\n\n\n## 4. Hashtable的原理\n\n**Hashtable 使用链地址法进行元素存储，通过一个实际的例子来演示一下插入元素的过程：**\n\n假设我们现在 Hashtable 的容量为 5，已经存在了 (8,8)，(10,10)，(13,13)，(16,16)，(17,17)，(21,21) 这 6 个键值对，目前他们在 Hashtable 中的位置如下：\n\n<div align=\"center\"> <img src=\"assets/hashtable1.png\" width=\"\"/></div><br/>\n\n\n\n现在，我们插入一个新的键值对，put(16,22)，假设 key=16 的索引为 1.但现在索引 1 的位置有两个 Entry 了，所以程序会对链表进行迭代。迭代的过程中，发现其中有一个 Entry 的 key 和我们要插入的键值对的 key 相同，所以现在会做的工作就是将 newValue=22 替换 oldValue=16，然后返回 oldValue = 16. \n\n\n\n<div align=\"center\"> <img src=\"assets/hashtable2.png\" width=\"\"/></div><br/>\n\n\n\n然后我们现在再插入一个，put(33,33)，key=33 的索引为 3，并且在链表中也不存在 key=33 的 Entry，所以将该节点插入链表的第一个位置。 \n\n<div align=\"center\"> <img src=\"assets/hashtable3.png\" width=\"\"/></div><br/>\n\n\n\n**Hashtable 与 HashMap 的简单比较**\n\n1. Hashtable 基于 Dictionary 类，而 HashMap 是基于 AbstractMap。Dictionary 是任何可将键映射到相应值的类的抽象父类，而 AbstractMap 是基于 Map 接口的实现，它以最大限度地减少实现此接口所需的工作。\n2. HashMap 的 key 和 value 都允许为 null，而 Hashtable 的 key 和 value 都不允许为 null。HashMap 遇到 key 为 null 的时候，调用 putForNullKey 方法进行处理，而对 value 没有处理；Hashtable遇到 null，直接返回 NullPointerException。\n3. **Hashtable 方法是同步，而HashMap则不是**。我们可以看一下源码，Hashtable 中的几乎所有的 public 的方法都是 synchronized 的，而有些方法也是在内部通过 synchronized 代码块来实现。所以有人一般都建议如果是涉及到多线程同步时采用 Hashtable，没有涉及就采用 HashMap，但是在 Collections 类中存在一个静态方法：**synchronizedMap()**，该方法创建了一个线程安全的 Map 对象，并把它作为一个封装的对象来返回。\n\n\n\n**参考资料：**\n\n- [Hashtable 的实现原理 - Java 集合学习指南 - 极客学院Wiki](http://wiki.jikexueyuan.com/project/java-collection/hashtable.html)\n\n\n\n## 5. Hash冲突的解决办法\n\n- 链地址法\n- 开放地址法（向后一位）\n  - 线性探测\n  - 平方探测\n  - 二次哈希\n- 再哈希法\n\n\n\n## 6. 什么是迭代器\n\n　　Java 集合框架的集合类，我们有时候称之为容器。容器的种类有很多种，比如 ArrayList、LinkedList、HashSet...，每种容器都有自己的特点，ArrayList 底层维护的是一个数组；LinkedList 是链表结构的；HashSet 依赖的是哈希表，每种容器都有自己特有的数据结构。\n\n　　因为容器的内部结构不同，很多时候可能不知道该怎样去遍历一个容器中的元素。所以为了使对容器内元素的操作更为简单，Java 引入了迭代器模式！ \n\n　　把访问逻辑从不同类型的集合类中抽取出来，从而避免向外部暴露集合的内部结构。\n\n　　**迭代器模式**：就是提供一种方法对一个容器对象中的各个元素进行访问，而又不暴露该对象容器的内部细节。\n\n```java\npublic static void main(String[] args) {\n    // 使用迭代器遍历ArrayList集合\n    Iterator<String> listIt = list.iterator();\n    while(listIt.hasNext()){\n        System.out.println(listIt.hasNext());\n    }\n    // 使用迭代器遍历Set集合\n    Iterator<String> setIt = set.iterator();\n    while(setIt.hasNext()){\n        System.out.println(listIt.hasNext());\n    }\n    // 使用迭代器遍历LinkedList集合\n    Iterator<String> linkIt = linkList.iterator();\n    while(linkIt.hasNext()){\n        System.out.println(listIt.hasNext());\n    }\n}\n```\n\n参考资料：\n\n- [深入理解Java中的迭代器 - Mr·Dragon - 博客园](https://www.cnblogs.com/zyuze/p/7726582.html)\n  \n\n\n\n## 7. 构造相同hash的字符串进行攻击，这种情况应该怎么处理？JDK7如何处理\n\n**攻击原理：**\n\n　　当客户端发送一个请求到服务器，如果该请求中带有参数，服务器端会将 参数名-参数值 作为 key-value 保存在 HashMap 中。如果有人恶意构造请求，在请求中加入大量相同 hash 值的 String 参数名（key），那么在服务器端用于存储这些 key-value 对的 HashMap 会被强行退化成链表，如图：\n\n<div align=\"center\"> <img src=\"assets/hash-to-badlink.png\" width=\"\"/></div>\n\n如果数据量足够大，那么在查找，插入时会占用大量 CPU，达到拒绝服务攻击的目的。\n\n \n\n**怎么处理**\n\n1. 限制 POST 和 GET 请求的参数个数\n2. 限制 POST 请求的请求体大小\n3. Web Application FireWall（WAF）\n\n\n\n**JDK7如何处理**\n\nHashMap 会动态的使用一个专门 TreeMap 实现来替换掉它。\n\n\n\n\n\n## 8. HashMap为什么大小是2的幂次\n\n首先来看一下 HashMap 的 put 方法的源码\n\n```java\npublic V put(K key, V value) {\n    if (key == null)                \n        return putForNullKey(value);  //将空key的Entry加入到table[0]中\n    int hash = hash(key.hashCode());  //计算key.hashcode()的hash值，hash函数由HashMap自己实现\n    int i = indexFor(hash, table.length);  //获取将要存放的数组下标\n    /*\n     * for中的代码用于：当hash值相同且key相同的情况下，使用新值覆盖旧值（其实就是修改功能）\n     */\n    //注意：for循环在第一次执行时就会先判断条件\n    for (Entry<K, V> e = table[i]; e != null; e = e.next) {\n        Object k;\n        //hash值相同且key相同的情况下，使用新值覆盖旧值\n        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {\n            V oldValue = e.value;\n            e.value = value;\n            //e.recordAccess(this);\n            return oldValue;//返回旧值\n        }\n    }\n    modCount++;\n    addEntry(hash, key, value, i);//增加一个新的Entry到table[i]\n    return null;//如果没有与传入的key相等的Entry，就返回null\n}\n```\n\n```java\n/**\n * \"按位与\"来获取数组下标\n */\nstatic int indexFor(int h, int length) {\n    return h & (length - 1);\n}\n\n```\n\n**HashMap 始终将自己的桶保持在2<sup>n</sup>，这是为什么？indexFor这个方法解释了这个问题**\n\n大家都知道计算机里面位运算是基本运算，位运算的效率是远远高于取余 % 运算的\n\n举个例子：2<sup>n</sup> 转换成二进制就是 1+n 个 0，减 1 之后就是 0+n个1，如16 -> 10000，15 -> 01111\n\n那么根据 & 位运算的规则，都为 1 (真)时，才为 1，那 0≤运算后的结果≤15，假设 h <= 15，那么运算后的结果就是 h 本身，h >15，运算后的结果就是最后四位二进制做 & 运算后的值，最终，就是 % 运算后的余数。\n\n当容量一定是 2<sup>n</sup> 时，h & (length - 1) == h % length\n\n\n\n\n\n# 更新日志\n\n- 2018/8/3 v2.5 基础版\n- 2018/9/1 v3.0 初稿版\n"
  },
  {
    "path": "notes/JavaArchitecture/03-Java并发编程.md",
    "content": "<!-- TOC -->\n\n- [前言](#前言)\n- [第一部分：并发编程](#第一部分并发编程)\n    - [1. 线程状态转换](#1-线程状态转换)\n        - [新建（New）](#新建new)\n        - [可运行（Runnable）](#可运行runnable)\n        - [阻塞（Blocking）](#阻塞blocking)\n        - [无限期等待（Waiting）](#无限期等待waiting)\n        - [限期等待（Timed Waiting）](#限期等待timed-waiting)\n        - [死亡（Terminated）](#死亡terminated)\n    - [2. Java实现多线程的方式及三种方式的区别](#2-java实现多线程的方式及三种方式的区别)\n        - [实现 Runnable 接口](#实现-runnable-接口)\n        - [实现 Callable 接口](#实现-callable-接口)\n        - [继承 Thread 类](#继承-thread-类)\n        - [实现接口 VS 继承 Thread](#实现接口-vs-继承-thread)\n        - [三种方式的区别](#三种方式的区别)\n    - [3. 基础线程机制](#3-基础线程机制)\n        - [Executor](#executor)\n        - [Daemon（守护线程）](#daemon守护线程)\n        - [sleep()](#sleep)\n        - [yield()](#yield)\n        - [线程阻塞](#线程阻塞)\n    - [4. 中断](#4-中断)\n        - [InterruptedException](#interruptedexception)\n        - [interrupted()](#interrupted)\n        - [Executor 的中断操作](#executor-的中断操作)\n    - [5. 互斥同步](#5-互斥同步)\n        - [synchronized](#synchronized)\n        - [ReentrantLock](#reentrantlock)\n        - [synchronized 和 ReentrantLock 比较](#synchronized-和-reentrantlock-比较)\n        - [synchronized与lock的区别，使用场景。看过synchronized的源码没？](#synchronized与lock的区别使用场景看过synchronized的源码没)\n        - [什么是CAS](#什么是cas)\n            - [入门例子](#入门例子)\n            - [Compare And Swap](#compare-and-swap)\n        - [什么是乐观锁和悲观锁](#什么是乐观锁和悲观锁)\n        - [Synchronized（对象锁）和Static Synchronized（类锁）区别](#synchronized对象锁和static-synchronized类锁区别)\n    - [6. 线程之间的协作](#6-线程之间的协作)\n        - [join()](#join)\n        - [wait() notify() notifyAll()](#wait-notify-notifyall)\n        - [await() signal() signalAll()](#await-signal-signalall)\n        - [sleep和wait有什么区别](#sleep和wait有什么区别)\n    - [7. J.U.C - AQS](#7-juc---aqs)\n        - [CountdownLatch](#countdownlatch)\n        - [CyclicBarrier](#cyclicbarrier)\n        - [Semaphore](#semaphore)\n        - [总结](#总结)\n    - [8. J.U.C - 其它组件](#8-juc---其它组件)\n        - [FutureTask](#futuretask)\n        - [BlockingQueue](#blockingqueue)\n        - [ForkJoin](#forkjoin)\n    - [9. 线程不安全示例](#9-线程不安全示例)\n    - [10. Java 内存模型（JMM）](#10-java-内存模型jmm)\n        - [主内存与工作内存](#主内存与工作内存)\n        - [内存间交互操作](#内存间交互操作)\n        - [内存模型三大特性](#内存模型三大特性)\n            - [1. 原子性](#1-原子性)\n            - [2. 可见性](#2-可见性)\n            - [3. 有序性](#3-有序性)\n        - [指令重排序](#指令重排序)\n            - [数据依赖性](#数据依赖性)\n            - [as-if-serial语义](#as-if-serial语义)\n            - [程序顺序规则](#程序顺序规则)\n            - [重排序对多线程的影响](#重排序对多线程的影响)\n        - [先行发生原则（happens-before）](#先行发生原则happens-before)\n            - [1. 单一线程原则](#1-单一线程原则)\n            - [2. 管程锁定规则](#2-管程锁定规则)\n            - [3. volatile 变量规则](#3-volatile-变量规则)\n            - [4. 线程启动规则](#4-线程启动规则)\n            - [5. 线程加入规则](#5-线程加入规则)\n            - [6. 线程中断规则](#6-线程中断规则)\n            - [7. 对象终结规则](#7-对象终结规则)\n            - [8. 传递性](#8-传递性)\n    - [11. 线程安全](#11-线程安全)\n        - [线程安全定义](#线程安全定义)\n        - [线程安全分类](#线程安全分类)\n            - [1. 不可变](#1-不可变)\n            - [2. 绝对线程安全](#2-绝对线程安全)\n            - [3. 相对线程安全](#3-相对线程安全)\n            - [4. 线程兼容](#4-线程兼容)\n            - [5. 线程对立](#5-线程对立)\n        - [线程安全的实现方法](#线程安全的实现方法)\n            - [1. 阻塞同步（互斥同步）](#1-阻塞同步互斥同步)\n            - [2. 非阻塞同步](#2-非阻塞同步)\n            - [3. 无同步方案](#3-无同步方案)\n                - [（一）可重入代码（Reentrant Code）](#一可重入代码reentrant-code)\n                - [（二）栈封闭](#二栈封闭)\n                - [（三）线程本地存储（Thread Local Storage）](#三线程本地存储thread-local-storage)\n    - [12. 锁优化](#12-锁优化)\n        - [自旋锁](#自旋锁)\n        - [锁消除](#锁消除)\n        - [锁粗化](#锁粗化)\n        - [轻量级锁](#轻量级锁)\n        - [偏向锁](#偏向锁)\n    - [13. 多线程开发良好的实践](#13-多线程开发良好的实践)\n    - [14. 线程池实现原理](#14-线程池实现原理)\n        - [并发队列](#并发队列)\n        - [线程池概念](#线程池概念)\n        - [Executor类图](#executor类图)\n        - [线程池工作原理](#线程池工作原理)\n        - [初始化线程池](#初始化线程池)\n            - [初始化方法](#初始化方法)\n        - [常用方法](#常用方法)\n            - [execute与submit的区别](#execute与submit的区别)\n            - [shutDown与shutDownNow的区别](#shutdown与shutdownnow的区别)\n        - [内部实现](#内部实现)\n        - [线程池的状态](#线程池的状态)\n        - [线程池其他常用方法](#线程池其他常用方法)\n        - [如何合理设置线程池的大小](#如何合理设置线程池的大小)\n- [第二部分：面试指南](#第二部分面试指南)\n    - [1. volatile 与 synchronized 的区别](#1-volatile-与-synchronized-的区别)\n    - [2. 什么是线程池？如果让你设计一个动态大小的线程池，如何设计，应该有哪些方法？线程池创建的方式？](#2-什么是线程池如果让你设计一个动态大小的线程池如何设计应该有哪些方法线程池创建的方式)\n    - [3. 什么是并发和并行](#3-什么是并发和并行)\n        - [并发](#并发)\n        - [并行](#并行)\n    - [4. 什么是线程安全](#4-什么是线程安全)\n        - [非线程安全!=不安全？](#非线程安全不安全)\n        - [线程安全十万个为什么？](#线程安全十万个为什么)\n    - [5. volatile 关键字的如何保证内存可见性](#5-volatile-关键字的如何保证内存可见性)\n    - [5. 什么是线程？线程和进程有什么区别？为什么要使用多线程](#5-什么是线程线程和进程有什么区别为什么要使用多线程)\n    - [6. 多线程共用一个数据变量需要注意什么？](#6-多线程共用一个数据变量需要注意什么)\n    - [7. 内存泄漏与内存溢出](#7-内存泄漏与内存溢出)\n        - [Java内存回收机制](#java内存回收机制)\n        - [Java内存泄露引起原因](#java内存泄露引起原因)\n            - [静态集合类](#静态集合类)\n            - [监听器](#监听器)\n            - [各种连接](#各种连接)\n            - [内部类和外部模块等的引用](#内部类和外部模块等的引用)\n            - [单例模式](#单例模式)\n    - [8. 如何减少线程上下文切换](#8-如何减少线程上下文切换)\n    - [9. 线程间通信和进程间通信](#9-线程间通信和进程间通信)\n        - [线程间通信](#线程间通信)\n        - [进程间通信](#进程间通信)\n    - [10. 什么是同步和异步，阻塞和非阻塞？](#10-什么是同步和异步阻塞和非阻塞)\n        - [同步](#同步)\n        - [异步](#异步)\n        - [阻塞](#阻塞)\n        - [非阻塞](#非阻塞)\n    - [11. Java中的锁](#11-java中的锁)\n        - [一个简单的锁](#一个简单的锁)\n        - [锁的可重入性](#锁的可重入性)\n        - [锁的公平性](#锁的公平性)\n        - [在 finally 语句中调用 unlock()](#在-finally-语句中调用-unlock)\n    - [12. 并发包(J.U.C)下面，都用过什么](#12-并发包juc下面都用过什么)\n    - [13. 从volatile说到,i++原子操作,线程安全问题](#13-从volatile说到i原子操作线程安全问题)\n- [参考资料](#参考资料)\n- [更新日志](#更新日志)\n\n<!-- /TOC -->\n\n# 前言\n\n在本文将总结多线程并发编程中的常见面试题，主要核心线程生命周期、线程通信、并发包部分。主要分成 “并发编程” 和 “面试指南” 两 部分，在面试指南中将讨论并发相关面经。\n\n\n\n# 第一部分：并发编程\n\n## 1. 线程状态转换\n\n<div align=\"center\"> <img src=\"assets/ace830df-9919-48ca-91b5-60b193f593d2.png\" width=\"\"/></div>\n\n这里笔者也绘制了一张中文版的图，[点击查看](https://raw.githubusercontent.com/frank-lam/2019_campus_apply/master/notes/JavaArchitecture/assets/1536767960941.png)\n\n### 新建（New）\n\n创建后尚未启动。\n\n### 可运行（Runnable）\n\n可能正在运行，也可能正在等待 CPU 时间片。\n\n包含了操作系统线程状态中的 运行（Running ） 和 就绪（Ready）。\n\n### 阻塞（Blocking）\n\n这个状态下，是在多个线程有同步操作的场景，比如正在等待另一个线程的 synchronized 块的执行释放，或者可重入的 synchronized 块里别人调用 wait() 方法，也就是线程在等待进入临界区。 \n\n阻塞可以分为：等待阻塞，同步阻塞，其他阻塞\n\n### 无限期等待（Waiting）\n\n等待其它线程显式地唤醒，否则不会被分配 CPU 时间片。\n\n| 进入方法                                   | 退出方法                             |\n| ------------------------------------------ | ------------------------------------ |\n| 没有设置 Timeout 参数的 Object.wait() 方法 | Object.notify() / Object.notifyAll() |\n| 没有设置 Timeout 参数的 Thread.join() 方法 | 被调用的线程执行完毕                 |\n| LockSupport.park() 方法                    | -                                    |\n\n### 限期等待（Timed Waiting）\n\n无需等待其它线程显式地唤醒，在一定时间之后会被系统自动唤醒。\n\n调用 Thread.sleep() 方法使线程进入限期等待状态时，常常用 “**使一个线程睡眠**” 进行描述。\n\n调用 Object.wait() 方法使线程进入限期等待或者无限期等待时，常常用 “**挂起一个线程**” 进行描述。\n\n**睡眠和挂起**是用来描述**行为**，而**阻塞**和等待用来描述**状态**。\n\n阻塞和等待的区别在于，阻塞是被动的，它是在等待获取一个排它锁。而等待是主动的，通过调用 Thread.sleep() 和 Object.wait() 等方法进入。\n\n| 进入方法                                 | 退出方法                                        |\n| ---------------------------------------- | ----------------------------------------------- |\n| Thread.sleep() 方法                      | 时间结束                                        |\n| 设置了 Timeout 参数的 Object.wait() 方法 | 时间结束 / Object.notify() / Object.notifyAll() |\n| 设置了 Timeout 参数的 Thread.join() 方法 | 时间结束 / 被调用的线程执行完毕                 |\n| LockSupport.parkNanos() 方法             | -                                               |\n| LockSupport.parkUntil() 方法             | -                                               |\n\n### 死亡（Terminated）\n\n- 线程因为 run 方法正常退出而自然死亡\n- 因为一个没有捕获的异常终止了 run 方法而意外死亡\n\n\n\n\n\n## 2. Java实现多线程的方式及三种方式的区别\n\n有三种使用线程的方法：\n\n- 实现 Runnable 接口；\n- 实现 Callable 接口；\n- 继承 Thread 类。\n\n实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务，不是真正意义上的线程，因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。\n\n\n\n### 实现 Runnable 接口\n\n需要实现 run() 方法。\n\n通过 Thread 调用 start() 方法来启动线程。\n\n```java\npublic class MyRunnable implements Runnable {\n    public void run() {\n        // ...\n    }\n}\n```\n\n```java\npublic static void main(String[] args) {\n    MyRunnable instance = new MyRunnable();\n    Thread thread = new Thread(instance);\n    thread.start();\n}\n```\n\n\n\n### 实现 Callable 接口\n\n与 Runnable 相比，Callable 可以有返回值，返回值通过 FutureTask 进行封装。\n\n```java\npublic class MyCallable implements Callable<Integer> {\n    public Integer call() {\n        return 123;\n    }\n}\n```\n\n```java\npublic static void main(String[] args) throws ExecutionException, InterruptedException {\n    MyCallable mc = new MyCallable();\n    FutureTask<Integer> ft = new FutureTask<>(mc);\n    Thread thread = new Thread(ft);\n    thread.start();\n    System.out.println(ft.get());\n}\n```\n\n\n\n### 继承 Thread 类\n\n同样也是需要实现 run() 方法，因为 Thread 类也实现了 Runable 接口。\n\n```java\npublic class MyThread extends Thread {\n    public void run() {\n        // ...\n    }\n}\n```\n\n```java\npublic static void main(String[] args) {\n    MyThread mt = new MyThread();\n    mt.start();\n}\n```\n\n\n\n### 实现接口 VS 继承 Thread\n\n实现接口会更好一些，因为：\n\n- Java 不支持多重继承，因此继承了 Thread 类就无法继承其它类，但是可以实现多个接口；\n- 类可能只要求可执行就行，继承整个 Thread 类开销过大。\n\n\n\n### 三种方式的区别 \n\n- 实现 Runnable 接口可以避免 Java 单继承特性而带来的局限；增强程序的健壮性，代码能够被多个线程共享，代码与数据是独立的；适合多个相同程序代码的线程区处理同一资源的情况。 \n- 继承 Thread 类和实现 Runnable 方法启动线程都是使用 start() 方法，然后 JVM 虚拟机将此线程放到就绪队列中，如果有处理机可用，则执行 run() 方法。 \n- 实现 Callable 接口要实现 call() 方法，并且线程执行完毕后会有返回值。其他的两种都是重写 run() 方法，没有返回值。 \n\n\n\n## 3. 基础线程机制\n\n### Executor\n\nExecutor 管理多个异步任务的执行，而无需程序员显式地管理线程的生命周期。这里的异步是指多个任务的执行互不干扰，不需要进行同步操作。\n\n主要有三种 Executor：\n\n- CachedThreadPool：一个任务创建一个线程；\n- FixedThreadPool：所有任务只能使用固定大小的线程；\n- SingleThreadExecutor：相当于大小为 1 的 FixedThreadPool。\n\n```java\npublic static void main(String[] args) {\n    ExecutorService executorService = Executors.newCachedThreadPool();\n    for (int i = 0; i < 5; i++) {\n        executorService.execute(new MyRunnable());\n    }\n    executorService.shutdown();\n}\n```\n\n\n\n**为什么引入Executor线程池框架？**\n\nnew Thread() 的缺点\n\n- 每次 new Thread() 耗费性能 \n- 调用 new Thread() 创建的线程缺乏管理，被称为野线程，而且可以无限制创建，之间相互竞争，会导致过多占用系统资源导致系统瘫痪。 \n- 不利于扩展，比如如定时执行、定期执行、线程中断\n\n采用线程池的优点\n\n- 重用存在的线程，减少对象创建、消亡的开销，性能佳 \n- 可有效控制最大并发线程数，提高系统资源的使用率，同时避免过多资源竞争，避免堵塞 \n- 提供定时执行、定期执行、单线程、并发数控制等功能\n\n\n\n### Daemon（守护线程）\n\nJava 中有两类线程：User Thread (用户线程)、Daemon Thread (守护线程)\n\n用户线程即运行在前台的线程，而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供便利服务，而且仅在普通、非守护线程仍然运行时才需要，比如垃圾回收线程就是一个守护线程。当 JVM 检测仅剩一个守护线程，而用户线程都已经退出运行时，JVM 就会退出，因为没有如果没有了被守护这，也就没有继续运行程序的必要了。如果有非守护线程仍然存活，JVM 就不会退出。\n\n守护线程并非只有虚拟机内部提供，用户在编写程序时也可以自己设置守护线程。用户可以用 Thread 的 setDaemon(true) 方法设置当前线程为守护线程。\n\n虽然守护线程可能非常有用，但必须小心确保其他所有非守护线程消亡时，不会由于它的终止而产生任何危害。因为你不可能知道在所有的用户线程退出运行前，守护线程是否已经完成了预期的服务任务。一旦所有的用户线程退出了，虚拟机也就退出运行了。 因此，不要在守护线程中执行业务逻辑操作（比如对数据的读写等）。\n\n **另外有几点需要注意：**\n\n- setDaemon(true) 必须在调用线程的 start() 方法之前设置，否则会跑出 IllegalThreadStateException 异常。\n- 在守护线程中产生的新线程也是守护线程。\n- 不要认为所有的应用都可以分配给守护线程来进行服务，比如读写操作或者计算逻辑。\n\n\n\n守护线程是程序运行时在后台提供服务的线程，不属于程序中不可或缺的部分。\n\n当所有非守护线程结束时，程序也就终止，同时会杀死所有守护线程。\n\nmain() 属于非守护线程。\n\n使用 setDaemon() 方法将一个线程设置为守护线程。\n\n```java\npublic static void main(String[] args) {\n    Thread thread = new Thread(new MyRunnable());\n    thread.setDaemon(true);\n}\n```\n\n\n\n### sleep()\n\nThread.sleep(millisec) 方法会休眠当前正在执行的线程，millisec 单位为毫秒。\n\nsleep() 可能会抛出 InterruptedException，因为异常不能跨线程传播回 main() 中，因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。\n\n```java\npublic void run() {\n    try {\n        Thread.sleep(3000);\n    } catch (InterruptedException e) {\n        e.printStackTrace();\n    }\n}\n```\n\n\n\n### yield()\n\n对静态方法 Thread.yield() 的调用声明了**当前线程已经完成了生命周期中最重要的部分**，可以切换给其它线程来执行。该方法只是对线程调度器的一个建议，而且也只是建议具有相同优先级的其它线程可以运行。\n\n```java\npublic void run() {\n    Thread.yield();\n}\n```\n\n\n\n\n\n### 线程阻塞\n\n线程可以阻塞于四种状态：\n\n- 当线程执行 Thread.sleep() 时，它一直阻塞到指定的毫秒时间之后，或者阻塞被另一个线程打断；\n- 当线程碰到一条 wait() 语句时，它会一直阻塞到接到通知 notify()、被中断或经过了指定毫秒时间为止（若制定了超时值的话）\n- 线程阻塞与不同 I/O 的方式有多种。常见的一种方式是 InputStream 的 read() 方法，该方法一直阻塞到从流中读取一个字节的数据为止，它可以无限阻塞，因此不能指定超时时间；\n- 线程也可以阻塞等待获取某个对象锁的排他性访问权限（即等待获得 synchronized 语句必须的锁时阻塞）。\n\n> 注意，并非所有的阻塞状态都是可中断的，以上阻塞状态的前两种可以被中断，后两种不会对中断做出反应\n\n\n\n\n\n## 4. 中断\n\n一个线程执行完毕之后会自动结束，如果在运行过程中发生异常也会提前结束。\n\n### InterruptedException\n\n通过调用一个线程的 interrupt() 来中断该线程，如果该线程处于**阻塞、限期等待或者无限期等待**状态，那么就会抛出 InterruptedException，从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞。\n\n对于以下代码，在 main() 中启动一个线程之后再中断它，由于线程中调用了 Thread.sleep() 方法，因此会抛出一个 InterruptedException，从而提前结束线程，不执行之后的语句。\n\n```java\npublic class InterruptExample {\n\n    private static class MyThread1 extends Thread {\n        @Override\n        public void run() {\n            try {\n                Thread.sleep(2000);\n                System.out.println(\"Thread run\");\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n```\n\n```java\npublic static void main(String[] args) throws InterruptedException {\n    Thread thread1 = new MyThread1();\n    thread1.start();\n    thread1.interrupt();\n    System.out.println(\"Main run\");\n}\n```\n\n```java\nMain run\njava.lang.InterruptedException: sleep interrupted\n    at java.lang.Thread.sleep(Native Method)\n    at InterruptExample.lambda$main$0(InterruptExample.java:5)\n    at InterruptExample$$Lambda$1/713338599.run(Unknown Source)\n    at java.lang.Thread.run(Thread.java:745)\n```\n\n\n\n### interrupted()\n\n如果一个线程的 run() 方法执行一个无限循环，并且没有执行 sleep() 等会抛出 InterruptedException 的操作，那么调用线程的 interrupt() 方法就无法使线程提前结束。\n\n但是调用 interrupt() 方法会设置线程的中断标记，此时调用 interrupted() 方法会返回 true。因此可以在循环体中使用 interrupted() 方法来判断线程是否处于中断状态，从而提前结束线程。\n\n```java\npublic class InterruptExample {\n\n    private static class MyThread2 extends Thread {\n        @Override\n        public void run() {\n            while (!interrupted()) {\n                // ..\n            }\n            System.out.println(\"Thread end\");\n        }\n    }\n}\n```\n\n```javascript\npublic static void main(String[] args) throws InterruptedException {\n    Thread thread2 = new MyThread2();\n    thread2.start();\n    thread2.interrupt();\n}\n```\n\n```\nThread end\n```\n\n\n\n### Executor 的中断操作\n\n调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭，但是如果调用的是 shutdownNow() 方法，则相当于调用每个线程的 interrupt() 方法。\n\n以下使用 Lambda 创建线程，相当于创建了一个匿名内部线程。\n\n```java\npublic static void main(String[] args) {\n    ExecutorService executorService = Executors.newCachedThreadPool();\n    executorService.execute(() -> {\n        try {\n            Thread.sleep(2000);\n            System.out.println(\"Thread run\");\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    });\n    executorService.shutdownNow();\n    System.out.println(\"Main run\");\n}\n```\n\n```java\nMain run\njava.lang.InterruptedException: sleep interrupted\n    at java.lang.Thread.sleep(Native Method)\n    at ExecutorInterruptExample.lambda$main$0(ExecutorInterruptExample.java:9)\n    at ExecutorInterruptExample$$Lambda$1/1160460865.run(Unknown Source)\n    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)\n    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)\n    at java.lang.Thread.run(Thread.java:745)\n```\n\n如果只想中断 Executor 中的一个线程，可以通过使用 submit() 方法来提交一个线程，它会返回一个 Future<?> 对象，通过调用该对象的 cancel(true) 方法就可以中断线程。\n\n```java\nFuture<?> future = executorService.submit(() -> {\n    // ..\n});\nfuture.cancel(true);\n```\n\n\n\n## 5. 互斥同步\n\nJava 提供了两种锁机制来控制多个线程对共享资源的互斥访问，第一个是 JVM 实现的 synchronized，而另一个是 JDK 实现的 ReentrantLock。\n\n### synchronized\n\n**1. 同步一个代码块**\n\n```java\npublic void func() {\n    synchronized (this) {\n        // ...\n    }\n}\n```\n\n它只作用于同一个对象，如果调用两个对象上的同步代码块，就不会进行同步。\n\n对于以下代码，使用 ExecutorService 执行了两个线程，由于调用的是同一个对象的同步代码块，因此这两个线程会进行同步，当一个线程进入同步语句块时，另一个线程就必须等待。\n\n```java\npublic class SynchronizedExample {\n    public void func1() {\n        synchronized (this) {\n            for (int i = 0; i < 10; i++) {\n                System.out.print(i + \" \");\n            }\n        }\n    }\n}\n```\n\n```java\npublic static void main(String[] args) {\n    SynchronizedExample e1 = new SynchronizedExample();\n    ExecutorService executorService = Executors.newCachedThreadPool();\n    executorService.execute(() -> e1.func1());\n    executorService.execute(() -> e1.func1());\n}\n```\n\n```java\n0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9\n```\n\n对于以下代码，两个线程调用了不同对象的同步代码块，因此这两个线程就不需要同步。从输出结果可以看出，两个线程交叉执行。\n\n```java\npublic static void main(String[] args) {\n    SynchronizedExample e1 = new SynchronizedExample();\n    SynchronizedExample e2 = new SynchronizedExample();\n    ExecutorService executorService = Executors.newCachedThreadPool();\n    executorService.execute(() -> e1.func1());\n    executorService.execute(() -> e2.func1());\n}\n```\n\n```\n0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9\n```\n\n\n\n**2. 同步一个方法**\n\n```java\npublic synchronized void func () {\n    // ...\n}\n```\n\n它和同步代码块一样，作用于同一个对象。\n\n\n\n**3. 同步一个类**\n\n```java\npublic void func() {\n    synchronized (SynchronizedExample.class) {\n        // ...\n    }\n}\n```\n\n作用于整个类，也就是说两个线程调用同一个类的不同对象上的这种同步语句，也会进行同步。\n\n```java\npublic class SynchronizedExample {\n    public void func2() {\n        synchronized (SynchronizedExample.class) {\n            for (int i = 0; i < 10; i++) {\n                System.out.print(i + \" \");\n            }\n        }\n    }\n}\n```\n\n```java\npublic static void main(String[] args) {\n    SynchronizedExample e1 = new SynchronizedExample();\n    SynchronizedExample e2 = new SynchronizedExample();\n    ExecutorService executorService = Executors.newCachedThreadPool();\n    executorService.execute(() -> e1.func2());\n    executorService.execute(() -> e2.func2());\n}\n```\n\n```\n0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9\n```\n\n\n\n**4. 同步一个静态方法**\n\n- 非静态同步函数的锁是：this\n- 静态的同步函数的锁是：字节码对象\n\n```java\npublic synchronized static void fun() {\n    // ...\n}\n```\n\n作用于整个类。\n\n\n\n### ReentrantLock\n\n重入锁（ReentrantLock）是一种递归无阻塞的同步机制。\n\n```java\npublic class LockExample {\n    private Lock lock = new ReentrantLock();\n\n    public void func() {\n        lock.lock();\n        try {\n            for (int i = 0; i < 10; i++) {\n                System.out.print(i + \" \");\n            }\n        } finally {\n            lock.unlock(); // 确保释放锁，从而避免发生死锁。\n        }\n    }\n}\n```\n\n```java\npublic static void main(String[] args) {\n    LockExample lockExample = new LockExample();\n    ExecutorService executorService = Executors.newCachedThreadPool();\n    executorService.execute(() -> lockExample.func());\n    executorService.execute(() -> lockExample.func());\n}\n```\n\n```\n0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9\n```\n\nReentrantLock 是 java.util.concurrent（J.U.C）包中的锁，相比于 synchronized，它多了以下高级功能：\n\n**1. 等待可中断**\n\n当持有锁的线程长期不释放锁的时候，正在等待的线程可以选择放弃等待，改为处理其他事情。\n\n**2. 可实现公平锁**\n\n公平锁是指多个线程在等待同一个锁时，必须按照申请锁的时间顺序来依次获得锁。\n\nsynchronized 中的锁是非公平的，ReentrantLock 默认情况下也是非公平的，但可以通过带布尔值的构造函数要求使用公平锁。\n\n**3. 锁绑定多个条件**\n\n一个 ReentrantLock 对象可以同时绑定多个 Condition 对象。\n\n\n\n### synchronized 和 ReentrantLock 比较\n\n**1. 锁的实现**\n\nsynchronized 是 JVM 实现的，而 ReentrantLock 是 JDK 实现的。\n\n**2. 性能**\n\n新版本 Java 对 synchronized 进行了很多优化，例如自旋锁等。目前来看它和 ReentrantLock 的性能基本持平了，因此性能因素不再是选择 ReentrantLock 的理由。synchronized 有更大的性能优化空间，应该优先考虑 synchronized。\n\n**3. 功能**\n\nReentrantLock 多了一些高级功能。\n\n**4. 使用选择**\n\n除非需要使用 ReentrantLock 的高级功能，否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制，JVM 原生地支持它，而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题，因为 JVM 会确保锁的释放。\n\n\n\n### synchronized与lock的区别，使用场景。看过synchronized的源码没？\n\n- （用法）synchronized（隐式锁）：在需要同步的对象中加入此控制，synchronized 可以加在方法上，也可以加在特定代码块中，括号中表示需要锁的对象。 \n- （用法）lock（显示锁）：需要显示指定起始位置和终止位置。一般使用 ReentrantLock 类做为锁，多个线程中必须要使用一个 ReentrantLock 类做为对象才能保证锁的生效。且在加锁和解锁处需要通过 lock() 和 unlock() 显示指出。所以一般会在 finally 块中写 unlock() 以防死锁。 \n- （性能）synchronized 是托管给 JVM 执行的，而 lock 是 Java 写的控制锁的代码。在 Java1.5 中，synchronize 是性能低效的。因为这是一个重量级操作，需要调用操作接口，导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用 Java 提供的 Lock 对象，性能更高一些。但是到了 Java1.6 ，发生了变化。synchronize 在语义上很清晰，可以进行很多优化，有<u>适应自旋，锁消除，锁粗化，轻量级锁，偏向锁</u>等等。导致 在 Java1.6 上 synchronize 的性能并不比 Lock 差。 \n- （机制）**synchronized 原始采用的是 CPU 悲观锁机制，即线程获得的是独占锁。Lock 也属于悲观锁**。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。相对而言乐观锁每次不加锁，而是假设没有冲突而去完成某项操作，如果因为冲突失败就重试，直到成功为止。乐观锁实现的机制就是 CAS 操作（Compare and Swap）。 \n\n \n\n### 什么是CAS\n\n> 蘑菇街面试，这里简单论述一下\n\n#### 入门例子\n\n在 Java 并发包中有这样一个包，java.util.concurrent.atomic，该包是对 Java 部分数据类型的原子封装，在原有数据类型的基础上，提供了原子性的操作方法，保证了线程安全。下面以 AtomicInteger 为例，来看一下是如何实现的。\n\n```java\npublic final int incrementAndGet() {\n    for (;;) {\n        int current = get();\n        int next = current + 1;\n        if (compareAndSet(current, next))\n            return next;\n    }\n}\n\npublic final int decrementAndGet() {\n    for (;;) {\n        int current = get();\n        int next = current - 1;\n        if (compareAndSet(current, next))\n            return next;\n    }\n}\n```\n\n以这两个方法为例，incrementAndGet 方法相当于原子性的 ++i，decrementAndGet 方法相当于原子性的 --i，这两个方法中都没有使用阻塞式的方式来保证原子性（如 Synchronized ），那它们是如何保证原子性的呢，下面引出 CAS。\n\n#### Compare And Swap\n\nCAS 指的是现代 CPU 广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。这个指令会对内存中的共享数据做原子的读写操作。\n\n简单介绍一下这个指令的操作过程：\n\n- 首先，CPU 会将内存中将要被更改的数据与期望的值做比较。\n- 然后，当这两个值相等时，CPU 才会将内存中的数值替换为新的值。否则便不做操作。\n- 最后，CPU 会将旧的数值返回。\n\n这一系列的操作是原子的。它们虽然看似复杂，但却是 Java 5 并发机制优于原有锁机制的根本。简单来说，CAS 的含义是：我认为原有的值应该是什么，如果是，则将原有的值更新为新值，否则不做修改，并告诉我原来的值是多少。\n​       简单的来说，CAS 有 3 个操作数，内存值 V，旧的预期值 A，要修改的新值 B。当且仅当预期值 A 和内存值 V 相同时，将内存值 V 修改为 B，否则返回 V。这是一种乐观锁的思路，它相信在它修改之前，没有其它线程去修改它；而 Synchronized 是一种悲观锁，它认为在它修改之前，一定会有其它线程去修改它，悲观锁效率很低。\n\n\n\n### 什么是乐观锁和悲观锁\n\n- 为什么需要锁（并发控制）\n  - 在多用户环境中，在同一时间可能会有多个用户更新相同的记录，这会产生冲突。这就是著名的并发性问题。\n  - 典型的冲突有：\n    - 丢失更新：一个事务的更新覆盖了其它事务的更新结果，就是所谓的更新丢失。例如：用户 A 把值从 6 改为 2，用户 B 把值从 2 改为 6，则用户 A 丢失了他的更新。\n    - 脏读：当一个事务读取其它完成一半事务的记录时，就会发生脏读取。例如：用户 A,B 看到的值都是6，用户 B 把值改为 2，用户 A 读到的值仍为 6。\n  - 为了解决这些并发带来的问题。 我们需要引入并发控制机制。\n- 并发控制机制\n  - **悲观锁：假定会发生并发冲突**，独占锁，屏蔽一切可能违反数据完整性的操作。\n  - **乐观锁：假设不会发生并发冲突**，只在提交操作时检查是否违反数据完整性。乐观锁不能解决脏读的问题。\n\n参考资料：\n\n- [乐观锁与悲观锁——解决并发问题 - WhyWin - 博客园](https://www.cnblogs.com/0201zcr/p/4782283.html)\n\n\n\n### Synchronized（对象锁）和Static Synchronized（类锁）区别\n\n- 一个是实例锁（锁在某一个实例对象上，如果该类是单例，那么该锁也具有全局锁的概念），一个是全局锁（该锁针对的是类，无论实例多少个对象，那么线程都共享该锁）。\n\n  实例锁对应的就是 synchronized关 键字，而类锁（全局锁）对应的就是 static synchronized（或者是锁在该类的 class 或者 classloader 对象上）。\n\n```java\n/**\n * static synchronized 和synchronized的区别！\n * 关键是区别第四种情况！\n */\npublic class StaticSynchronized {\n\n    /**\n     * synchronized方法\n     */\n    public synchronized void isSynA(){\n        System.out.println(\"isSynA\");\n    }\n    public synchronized void isSynB(){\n        System.out.println(\"isSynB\");\n    }\n\n    /**\n     * static synchronized方法\n     */\n    public static synchronized void cSynA(){\n        System.out.println(\"cSynA\");\n    }\n    public static synchronized void cSynB(){\n        System.out.println(\"cSynB\");\n    }\n\n    public static void main(String[] args) {\n        StaticSynchronized x = new StaticSynchronized();\n        StaticSynchronized y = new StaticSynchronized();\n        /**\n         *  x.isSynA()与x.isSynB(); 不能同时访问(同一个对象访问synchronized方法)\n         *  x.isSynA()与y.isSynB(); 能同时访问(不同对象访问synchronized方法)\n         *  x.cSynA()与y.cSynB(); 不能同时访问(不同对象也不能访问static synchronized方法)\n         *  x.isSynA()与y.cSynA(); 能同时访问(static synchronized方法占用的是类锁，\n         *                        而访问synchronized方法占用的是对象锁，不存在互斥现象)\n         */\n    }\n}\n```\n\n\n\n## 6. 线程之间的协作\n\n当多个线程可以一起工作去解决某个问题时，如果某些部分必须在其它部分之前完成，那么就需要对线程进行协调。\n\n### join()\n\n在线程中调用另一个线程的 join() 方法，会将当前线程挂起，而不是忙等待，直到目标线程结束。\n\n对于以下代码，虽然 b 线程先启动，但是因为在 b 线程中调用了 a 线程的 join() 方法，b 线程会等待 a 线程结束才继续执行，因此最后能够保证 a 线程的输出先于 b 线程的输出。\n\n```java\npublic class JoinExample {\n    private class A extends Thread {\n        @Override\n        public void run() {\n            System.out.println(\"A\");\n        }\n    }\n\n    private class B extends Thread {\n        private A a;\n        B(A a) {\n            this.a = a;\n        }\n\n        @Override\n        public void run() {\n            try {\n                a.join();\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n            System.out.println(\"B\");\n        }\n    }\n\n    public void test() {\n        A a = new A();\n        B b = new B(a);\n        b.start();\n        a.start();\n    }\n}\n```\n\n```java\npublic static void main(String[] args) {\n    JoinExample example = new JoinExample();\n    example.test();\n}\n```\n\n```\nA\nB\n```\n\n\n\n### wait() notify() notifyAll()\n\n调用 wait() 使得线程等待某个条件满足，线程在等待时会被挂起，当其他线程的运行使得这个条件满足时，其它线程会调用 notify()（随机叫醒一个） 或者 notifyAll() （叫醒所有 wait 线程，争夺时间片的线程只有一个）来唤醒挂起的线程。\n\n它们都属于 Object 的一部分，而不属于 Thread。\n\n只能用在**同步方法**或者**同步控制块**中使用！否则会在运行时抛出 IllegalMonitorStateExeception。\n\n使用 wait() 挂起期间，线程会释放锁。这是因为，如果没有释放锁，那么其它线程就无法进入对象的同步方法或者同步控制块中，那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程，造成死锁。\n\n```java\npublic class WaitNotifyExample {\n    public synchronized void before() {\n        System.out.println(\"before\");\n        notifyAll();\n    }\n\n    public synchronized void after() {\n        try {\n            wait();\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n        System.out.println(\"after\");\n    }\n}\n```\n\n```java\npublic static void main(String[] args) {\n    ExecutorService executorService = Executors.newCachedThreadPool();\n    WaitNotifyExample example = new WaitNotifyExample();\n    executorService.execute(() -> example.after());\n    executorService.execute(() -> example.before());\n}\n```\n\n```\nbefore\nafter\n```\n\n\n\n### await() signal() signalAll()\n\njava.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调，可以在 Condition 上调用 await() 方法使线程等待，其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。相比于 wait() 这种等待方式，await() 可以指定等待的条件，因此更加灵活。\n\n使用 Lock 来获取一个 Condition 对象。\n\n```java\npublic class AwaitSignalExample {\n    private Lock lock = new ReentrantLock();\n    private Condition condition = lock.newCondition();\n\n    public void before() {\n        lock.lock();\n        try {\n            System.out.println(\"before\");\n            condition.signalAll();\n        } finally {\n            lock.unlock();\n        }\n    }\n\n    public void after() {\n        lock.lock();\n        try {\n            condition.await();\n            System.out.println(\"after\");\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        } finally {\n            lock.unlock();\n        }\n    }\n}\n```\n\n```java\npublic static void main(String[] args) {\n    ExecutorService executorService = Executors.newCachedThreadPool();\n    AwaitSignalExample example = new AwaitSignalExample();\n    executorService.execute(() -> example.after());\n    executorService.execute(() -> example.before());\n}\n```\n\n```\nbefore\nafter\n```\n\n\n\n### sleep和wait有什么区别\n\n- sleep 和 wait\n  - wait() 是 Object 的方法，而 sleep() 是 Thread 的静态方法；\n  - wait() 会释放锁，sleep() 不会。\n- 有什么区别\n  - sleep() 方法（休眠）是线程类（Thread）的静态方法，调用此方法会让当前线程暂停执行指定的时间，将执行机会（CPU）让给其他线程，但是对象的锁依然保持，因此休眠时间结束后会自动恢复（线程回到就绪状态）。 \n  - wait() 是 Object 类的方法，调用对象的 wait() 方法导致当前线程放弃对象的锁（线程暂停执行），进入对象的等待池（wait pool），只有调用对象的 notify() 方法（或 notifyAll() 方法）时才能唤醒等待池中的线程进入等锁池（lock pool），如果线程重新获得对象的锁就可以进入就绪状态。 \n\n\n\n## 7. J.U.C - AQS\n\nAQS 是  AbstractQueuedSynchronizer 的简称，java.util.concurrent（J.U.C）大大提高了并发性能，AQS (AbstractQueuedSynchronizer) 被认为是 J.U.C 的核心。它提供了一个基于 FIFO 队列，这个队列可以用来构建锁或者其他相关的同步装置的基础框架。下图是 AQS 底层的数据结构：\n\n\n\n<div align=\"center\"><img src=\"assets/616953-20160403170136176-573839888.png\" width=\"600\"/></div>\n\n它底层使用的是双向列表，是队列的一种实现 , 因此也可以将它当成一种队列。\n\n- Sync queue 是同步列表，它是双向列表 , 包括 head，tail 节点。其中 head 节点主要用来后续的调度 ;\n- Condition queue 是单向链表 , 不是必须的 , 只有当程序中需要 Condition 的时候，才会存在这个单向链表 , 并且可能会有多个 Condition queue。\n\n\n\n简单的来说：\n\n- AQS其实就是一个可以给我们实现锁的**框架**\n\n- 内部实现的关键是：**先进先出的队列、state 状态**\n\n- 定义了内部类 ConditionObject\n\n- 拥有两种线程模式\n\n- - 独占模式\n  - 共享模式\n\n- 在 LOCK 包中的相关锁（常用的有 ReentrantLock、 ReadWriteLock ）都是基于 AQS 来构建\n- 一般我们叫 AQS 为同步器。\n\n\n\n### CountdownLatch\n\nCountDownLatch 类位于 java.util.concurrent 包下，利用它可以实现类似计数器的功能。比如有一个任务 A，它要等待其他 4 个任务执行完毕之后才能执行，此时就可以利用 CountDownLatch 来实现这种功能了。\n\n维护了一个计数器 cnt，每次调用 countDown() 方法会让计数器的值减 1，减到 0 的时候，那些因为调用 await() 方法而在等待的线程就会被唤醒。\n\n<div align=\"center\"> <img src=\"assets/CountdownLatch.png\" width=\"\"/></div>\n\n\n\nCountDownLatch 类只提供了一个构造器：\n\n```java\npublic CountDownLatch(int count) {  };  // 参数count为计数值\n```\n\n然后下面这 3 个方法是 CountDownLatch 类中最重要的方法：\n\n```java\n//调用await()方法的线程会被挂起，它会等待直到count值为0才继续执行\npublic void await() throws InterruptedException { };\n//和await()类似，只不过等待一定的时间后count值还没变为0的话就会继续执行\npublic boolean await(long timeout, TimeUnit unit) throws InterruptedException { };\n//将count值减1\npublic void countDown() { };\n```\n\n下面看一个例子大家就清楚 CountDownLatch 的用法了：\n\n```java\npublic class Test {\n    public static void main(String[] args) {\n        final CountDownLatch latch = new CountDownLatch(2);\n        new Thread() {\n            public void run() {\n                try {\n                    System.out.println(\"子线程\" + Thread.currentThread().getName() + \"正在执行\");\n                    Thread.sleep(3000);\n                    System.out.println(\"子线程\" + Thread.currentThread().getName() + \"执行完毕\");\n                    latch.countDown();\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n            }\n\n            ;\n        }.start();\n        new Thread() {\n            public void run() {\n                try {\n                    System.out.println(\"子线程\" + Thread.currentThread().getName() + \"正在执行\");\n                    Thread.sleep(3000);\n                    System.out.println(\"子线程\" + Thread.currentThread().getName() + \"执行完毕\");\n                    latch.countDown();\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                }\n            }\n\n            ;\n        }.start();\n        try {\n            System.out.println(\"等待2个子线程执行完毕...\");\n            latch.await();\n            System.out.println(\"2个子线程已经执行完毕\");\n            System.out.println(\"继续执行主线程\");\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n}\n```\n\n执行结果：\n\n```\n线程Thread-0正在执行\n线程Thread-1正在执行\n等待2个子线程执行完毕...\n线程Thread-0执行完毕\n线程Thread-1执行完毕\n2个子线程已经执行完毕\n继续执行主线程\n```\n\n\n\n### CyclicBarrier\n\n用来控制多个线程互相等待，只有当多个线程都到达时，这些线程才会继续执行。\n\n和 CountdownLatch 相似，都是通过维护计数器来实现的。但是它的计数器是递增的，每次执行 await() 方法之后，计数器会加 1，直到计数器的值和设置的值相等，等待的所有线程才会继续执行。和 CountdownLatch 的另一个区别是，CyclicBarrier 的计数器可以循环使用，所以它才叫做循环屏障。\n\n下图应该从下往上看才正确。\n\n<div align=\"center\"><img src=\"assets/CyclicBarrier.png\" width=\"\"/></div>\n\n\n```java\npublic class CyclicBarrierExample {\n    public static void main(String[] args) throws InterruptedException {\n        final int totalThread = 10;\n        CyclicBarrier cyclicBarrier = new CyclicBarrier(totalThread);\n        ExecutorService executorService = Executors.newCachedThreadPool();\n        for (int i = 0; i < totalThread; i++) {\n            executorService.execute(() -> {\n                System.out.print(\"before..\");\n                try {\n                    cyclicBarrier.await();\n                } catch (InterruptedException e) {\n                    e.printStackTrace();\n                } catch (BrokenBarrierException e) {\n                    e.printStackTrace();\n                }\n                System.out.print(\"after..\");\n            });\n        }\n        executorService.shutdown();\n    }\n}\n```\n\n```\nbefore..before..before..before..before..before..before..before..before..before..after..after..after..after..after..after..after..after..after..after..\n```\n\n\n\n### Semaphore\n\nSemaphore 就是操作系统中的信号量，可以控制对互斥资源的访问线程数。Semaphore 可以控同时访问的线程个数，通过 acquire() 获取一个许可，如果没有就等待，而 release() 释放一个许可。\n\n<div align=\"center\"><img src=\"assets/Semaphore.png\" width=\"\"/></div><br/>\n\nSemaphore 类位于 java.util.concurrent 包下，它提供了2个构造器：\n\n```java\npublic Semaphore(int permits) {          \n    //参数permits表示许可数目，即同时可以允许多少线程进行访问\n    sync = new NonfairSync(permits);\n}\n\npublic Semaphore(int permits, boolean fair) {    \n    //这个多了一个参数fair表示是否是公平的，即等待时间越久的越先获取许可    \n    sync = (fair) ? new FairSync(permits) : new NonfairSync(permits);\n}\n```\n\n下面说一下 Semaphore 类中比较重要的几个方法，首先是 acquire()、release() 方法：\n\n```java\n//获取一个许可\npublic void acquire() throws InterruptedException {  }\n//获取permits个许可\npublic void acquire(int permits) throws InterruptedException { }\n//释放一个许可\npublic void release() { }\n//释放permits个许可\npublic void release(int permits) { }\n```\n\n　　acquire() 用来获取一个许可，若无许可能够获得，则会一直等待，直到获得许可。\n\n　　release() 用来释放许可。注意，在释放许可之前，必须先获获得许可。\n\n这 4 个方法都会被阻塞，如果想立即得到执行结果，可以使用下面几个方法：\n\n```java\n//尝试获取一个许可，若获取成功，则立即返回true，若获取失败，则立即返回false\npublic boolean tryAcquire() { };    \n//尝试获取一个许可，若在指定的时间内获取成功，则立即返回true，否则则立即返回false\npublic boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { }; \n//尝试获取permits个许可，若获取成功，则立即返回true，若获取失败，则立即返回false\npublic boolean tryAcquire(int permits) { }; \n//尝试获取permits个许可，若在指定的时间内获取成功，则立即返回true，否则则立即返回false\npublic boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { }; \n```\n\n　　另外还可以通过 availablePermits() 方法得到可用的许可数目。\n\n　　下面通过一个例子来看一下 Semaphore 的具体使用：\n\n　　假若一个工厂有 5 台机器，但是有 8 个工人，一台机器同时只能被一个工人使用，只有使用完了，其他工人才能继续使用。那么我们就可以通过 Semaphore 来实现：\n\n```java\npublic class Test {\n    public static void main(String[] args) {\n        int N = 8;            //工人数        \n        Semaphore semaphore = new Semaphore(5); //机器数目       \n        for (int i = 0; i < N; i++) new Worker(i, semaphore).start();\n    }\n\n    static class Worker extends Thread {\n        private int num;\n        private Semaphore semaphore;\n\n        public Worker(int num, Semaphore semaphore) {\n            this.num = num;\n            this.semaphore = semaphore;\n        }\n        \n        @Override\n        public void run() {\n            try {\n                semaphore.acquire();\n                System.out.println(\"工人\" + this.num + \"占用一个机器在生产...\");\n                Thread.sleep(2000);\n                System.out.println(\"工人\" + this.num + \"释放出机器\");\n                semaphore.release();\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n        }\n    }\n}\n```\n\n执行结果：\n\n```\n工人0占用一个机器在生产...\n工人1占用一个机器在生产...\n工人2占用一个机器在生产...\n工人4占用一个机器在生产...\n工人5占用一个机器在生产...\n工人0释放出机器\n工人2释放出机器\n工人3占用一个机器在生产...\n工人7占用一个机器在生产...\n工人4释放出机器\n工人5释放出机器\n工人1释放出机器\n工人6占用一个机器在生产...\n工人3释放出机器\n工人7释放出机器\n工人6释放出机器\n```\n\n\n\n### 总结\n\n下面对上面说的三个辅助类进行一个总结：\n\n- CountDownLatch 和 CyclicBarrier 都能够实现线程之间的等待，只不过它们侧重点不同：\n  - CountDownLatch 一般用于某个线程A等待若干个其他线程执行完任务之后，它才执行；\n  - CyclicBarrier 一般用于一组线程互相等待至某个状态，然后这一组线程再同时执行；\n  - 另外，CountDownLatch 是不能够重用的，而 CyclicBarrier 是可以重用的。\n- Semaphore 其实和锁有点类似，它一般用于控制对某组资源的访问权限。\n\n\n\n## 8. J.U.C - 其它组件\n\n### FutureTask\n\n在介绍 Callable 时我们知道它可以有返回值，返回值通过 Future 进行封装。FutureTask 实现了 RunnableFuture 接口，该接口继承自 Runnable 和 Future 接口，这使得 FutureTask 既可以当做一个任务执行，也可以有返回值。\n\n```java\npublic class FutureTask<V> implements RunnableFuture<V>\n```\n\n```java\npublic interface RunnableFuture<V> extends Runnable, Future<V>\n```\n\nFutureTask 可用于异步获取执行结果或取消执行任务的场景。当一个计算任务需要执行很长时间，那么就可以用 FutureTask 来封装这个任务，主线程在完成自己的任务之后再去获取结果。\n\n```java\npublic class FutureTaskExample {\n    public static void main(String[] args) throws ExecutionException, InterruptedException {\n        FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {\n            @Override\n            public Integer call() throws Exception {\n                int result = 0;\n                for (int i = 0; i < 100; i++) {\n                    Thread.sleep(10);\n                    result += i;\n                }\n                return result;\n            }\n        });\n\n        Thread computeThread = new Thread(futureTask);\n        computeThread.start();\n\n        Thread otherThread = new Thread(() -> {\n            System.out.println(\"other task is running...\");\n            try {\n                Thread.sleep(1000);\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n        });\n        otherThread.start();\n        System.out.println(futureTask.get());\n    }\n}\n```\n\n```java\nother task is running...\n4950\n```\n\n\n\n### BlockingQueue\n\njava.util.concurrent.BlockingQueue 接口有以下阻塞队列的实现：\n\n- **FIFO 队列** ：LinkedBlockingQueue、ArrayBlockingQueue（固定长度）\n- **优先级队列** ：PriorityBlockingQueue\n\n提供了阻塞的 take() 和 put() 方法：如果队列为空 take() 将阻塞，直到队列中有内容；如果队列为满 put() 将阻塞，直到队列有空闲位置。\n\n**使用 BlockingQueue 实现生产者消费者问题**\n\n```java\npublic class ProducerConsumer {\n\n    private static BlockingQueue<String> queue = new ArrayBlockingQueue<>(5);\n\n    private static class Producer extends Thread {\n        @Override\n        public void run() {\n            try {\n                queue.put(\"product\");\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n            System.out.print(\"produce..\");\n        }\n    }\n\n    private static class Consumer extends Thread {\n\n        @Override\n        public void run() {\n            try {\n                String product = queue.take();\n            } catch (InterruptedException e) {\n                e.printStackTrace();\n            }\n            System.out.print(\"consume..\");\n        }\n    }\n}\n```\n\n```java\npublic static void main(String[] args) {\n    for (int i = 0; i < 2; i++) {\n        Producer producer = new Producer();\n        producer.start();\n    }\n    for (int i = 0; i < 5; i++) {\n        Consumer consumer = new Consumer();\n        consumer.start();\n    }\n    for (int i = 0; i < 3; i++) {\n        Producer producer = new Producer();\n        producer.start();\n    }\n}\n```\n\n```\nproduce..produce..consume..consume..produce..consume..produce..consume..produce..consume..\n```\n\n\n\n### ForkJoin\n\n主要用于并行计算中，和 MapReduce 原理类似，都是把大的计算任务拆分成多个小任务并行计算。\n\n```java\npublic class ForkJoinExample extends RecursiveTask<Integer> {\n    private final int threshold = 5;\n    private int first;\n    private int last;\n\n    public ForkJoinExample(int first, int last) {\n        this.first = first;\n        this.last = last;\n    }\n\n    @Override\n    protected Integer compute() {\n        int result = 0;\n        if (last - first <= threshold) {\n            // 任务足够小则直接计算\n            for (int i = first; i <= last; i++) {\n                result += i;\n            }\n        } else {\n            // 拆分成小任务\n            int middle = first + (last - first) / 2;\n            ForkJoinExample leftTask = new ForkJoinExample(first, middle);\n            ForkJoinExample rightTask = new ForkJoinExample(middle + 1, last);\n            leftTask.fork();\n            rightTask.fork();\n            result = leftTask.join() + rightTask.join();\n        }\n        return result;\n    }\n}\n```\n\n```javascript\npublic static void main(String[] args) throws ExecutionException, InterruptedException {\n    ForkJoinExample example = new ForkJoinExample(1, 10000);\n    ForkJoinPool forkJoinPool = new ForkJoinPool();\n    Future result = forkJoinPool.submit(example);\n    System.out.println(result.get());\n}\n```\n\nForkJoin 使用 ForkJoinPool 来启动，它是一个特殊的线程池，线程数量取决于 CPU 核数。\n\n```java\npublic class ForkJoinPool extends AbstractExecutorService\n```\n\nForkJoinPool 实现了工作窃取算法来提高 CPU 的利用率。每个线程都维护了一个双端队列，用来存储需要执行的任务。工作窃取算法允许空闲的线程从其它线程的双端队列中窃取一个任务来执行。窃取的任务必须是最晚的任务，避免和队列所属线程发生竞争。例如下图中，Thread2 从 Thread1 的队列中拿出最晚的 Task1 任务，Thread1 会拿出 Task2 来执行，这样就避免发生竞争。但是如果队列中只有一个任务时还是会发生竞争。\n\n<div align=\"center\"><img src=\"assets/fork-and-join.jpg\" width=\"600\"/></div><br/>\n\n \n\n## 9. 线程不安全示例\n\n如果多个线程对同一个共享数据进行访问而不采取同步操作的话，那么操作的结果是不一致的。\n\n以下代码演示了 1000 个线程同时对 cnt 执行自增操作，操作结束之后它的值为 997 而不是 1000。\n\n```java\npublic class ThreadUnsafeExample {\n    private int cnt = 0;\n    public void add() {\n        cnt++;\n    }\n    public int get() {\n        return cnt;\n    }\n}\n\npublic static void main(String[] args) throws InterruptedException {\n    final int threadSize = 1000;\n    ThreadUnsafeExample example = new ThreadUnsafeExample();\n    final CountDownLatch countDownLatch = new CountDownLatch(threadSize);\n    ExecutorService executorService = Executors.newCachedThreadPool();\n    for (int i = 0; i < threadSize; i++) {\n        executorService.execute(() -> {\n            example.add();\n            countDownLatch.countDown();\n        });\n    }\n    countDownLatch.await();\n    executorService.shutdown();\n    System.out.println(example.get());\n}\n```\n\n```\n997\n```\n\n\n\n## 10. Java 内存模型（JMM）\n\nJava 内存模型试图屏蔽各种硬件和操作系统的内存访问差异，以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。\n\n### 主内存与工作内存\n\n处理器上的寄存器的读写的速度比内存快几个数量级，为了解决这种速度矛盾，在它们之间加入了高速缓存。\n\n加入高速缓存带来了一个新的问题：缓存一致性。如果多个缓存共享同一块主内存区域，那么多个缓存的数据可能会不一致，需要一些协议来解决这个问题。\n\n<div align=\"center\"> <img src=\"assets/1195582-20180508173147029-1341787720.png\" width=\"700\"/></div>\n\n所有的变量都存储在**主内存**中，每个线程还有自己的**工作内存**，工作内存存储在高速缓存或者寄存器中，保存了该线程使用的变量的主内存副本拷贝。\n\n线程只能直接操作工作内存中的变量，不同线程之间的变量值传递需要通过主内存来完成。\n\n\n\n**Java内存模型和硬件关系图**\n\n<div align=\"center\"> <img src=\"assets/v2-4015322359279c5568263aeb7f41c36d.jpg\" width=\"600\"/></div>\n\n**Java内存模型抽象结构图**\n\n<div align=\"center\"><img src=\"assets/1135283-20170403195814660-1521573510.png\" width=\"400\"/></div>\n\n\n\n### 内存间交互操作\n\nJava 内存模型定义了 8 个操作来完成主内存和工作内存的交互操作。\n\n<div align=\"center\"> <img src=\"assets/536c6dfd-305a-4b95-b12c-28ca5e8aa043.png\" width=\"650\"/></div>\n\n- read：把一个变量的值从主内存传输到工作内存中\n- load：在 read 之后执行，把 read 得到的值放入工作内存的变量副本中\n- use：把工作内存中一个变量的值传递给执行引擎\n- assign：把一个从执行引擎接收到的值赋给工作内存的变量\n- store：把工作内存的一个变量的值传送到主内存中\n- write：在 store 之后执行，把 store 得到的值放入主内存的变量中\n- lock：作用于主内存的变量，把一个变量标识为一条线程独占状态\n- unlock：作用于主内存变量，把一个处于锁定状态的变量释放出来，释放后的变量才可以被其他线程锁定\n\n\n\n如果要把一个变量从主内存中复制到工作内存，就需要按顺寻地执行 read 和 load 操作，如果把变量从工作内存中同步回主内存中，就要按顺序地执行 store 和 write 操作。Java内存模型只要求上述操作必须按顺序执行，而没有保证必须是连续执行。也就是 read 和 load 之间，store 和 write 之间是可以插入其他指令的，如对主内存中的变量a、b进行访问时，可能的顺序是read a，read b，load b， load a。\n\n\n\n**Java内存模型还规定了在执行上述8种基本操作时，必须满足如下规则：**\n\n- 不允许 read 和 load、store 和 write 操作之一单独出现\n- 不允许一个线程丢弃它的最近 assign 的操作，即变量在工作内存中改变了之后必须同步到主内存中\n- 不允许一个线程无原因地（没有发生过任何assign操作）把数据从工作内存同步回主内存中\n- 一个新的变量只能在主内存中诞生，不允许在工作内存中直接使用一个未被初始化（load 或 assign）的变量。即就是对一个变量实施 use 和 store 操作之前，必须先执行过了 assign 和 load 操作。\n- 一个变量在同一时刻只允许一条线程对其进行lock操作，lock 和 unlock必须成对出现\n- 如果对一个变量执行 lock 操作，将会清空工作内存中此变量的值，在执行引擎使用这个变量前需要重新执行 load 或 assign 操作初始化变量的值\n- 如果一个变量事先没有被 lock 操作锁定，则不允许对它执行 unlock 操作；也不允许去 unlock 一个被其他线程锁定的变量。\n- 对一个变量执行 unlock 操作之前，必须先把此变量同步到主内存中（执行 store 和 write 操作）。\n\n\n\n参考资料：\n\n[volatile关键字与Java内存模型(JMM) - yzwall - 博客园](https://www.cnblogs.com/yzwall/p/6661528.html)\n\n\n\n\n### 内存模型三大特性\n\n#### 1. 原子性\n\n- 概念\n  - 事物有原子性，这个概念大概都清楚，即一个操作或多个操作要么执行的过程中不被任何因素打断，要么不执行。\n- 如何实现原子性？\n  - 通过同步代码块 synchronized 或者 local 锁来确保原子性\n\n\n\nJava 内存模型保证了 read、load、use、assign、store、write、lock 和 unlock 操作具有原子性，例如对一个 int 类型的变量执行 assign 赋值操作，这个操作就是原子性的。但是 Java 内存模型允许虚拟机将没有被 volatile 修饰的 64 位数据（long，double）的读写操作划分为两次 32 位的操作来进行，即 load、store、read 和 write 操作可以不具备原子性。\n\n有一个错误认识就是，int 等原子性的变量在多线程环境中不会出现线程安全问题。前面的线程不安全示例代码中，cnt 变量属于 int 类型变量，1000 个线程对它进行自增操作之后，得到的值为 997 而不是 1000。\n\n为了方便讨论，将内存间的交互操作简化为 3 个：load、assign、store。\n\n下图演示了两个线程同时对 cnt 变量进行操作，load、assign、store 这一系列操作整体上看不具备原子性，那么在 T1 修改 cnt 并且还没有将修改后的值写入主内存，T2 依然可以读入该变量的值。可以看出，这两个线程虽然执行了两次自增运算，但是主内存中 cnt 的值最后为 1 而不是 2。因此对 int 类型读写操作满足原子性只是说明 load、assign、store 这些单个操作具备原子性。\n\n<div align=\"center\"><img src=\"assets/ef8eab00-1d5e-4d99-a7c2-d6d68ea7fe92-1534148019548.png\" width=\"400\"/></div><br/>\n\nAtomicInteger 能保证多个线程修改的原子性。\n\n<div align=\"center\"><img src=\"assets/952afa9a-458b-44ce-bba9-463e60162945-1534148027104.png\" width=\"400\"/></div><br/>\n\n\n\n使用 AtomicInteger 重写之前线程不安全的代码之后得到以下线程安全实现：\n\n```java\npublic class AtomicExample {\n    private AtomicInteger cnt = new AtomicInteger();\n\n    public void add() {\n        cnt.incrementAndGet();\n    }\n\n    public int get() {\n        return cnt.get();\n    }\n}\n```\n\n```java\npublic static void main(String[] args) throws InterruptedException {\n    final int threadSize = 1000;\n    AtomicExample example = new AtomicExample(); // 只修改这条语句\n    final CountDownLatch countDownLatch = new CountDownLatch(threadSize);\n    ExecutorService executorService = Executors.newCachedThreadPool();\n    for (int i = 0; i < threadSize; i++) {\n        executorService.execute(() -> {\n            example.add();\n            countDownLatch.countDown();\n        });\n    }\n    countDownLatch.await();\n    executorService.shutdown();\n    System.out.println(example.get());\n}\n```\n\n```\n1000\n```\n\n除了使用原子类之外，也可以使用 synchronized 互斥锁来保证操作的原子性。它对应的内存间交互操作为：lock 和 unlock，在虚拟机实现上对应的字节码指令为 monitorenter 和 monitorexit。\n\n```java\npublic class AtomicSynchronizedExample {\n    private int cnt = 0;\n\n    public synchronized void add() {\n        cnt++;\n    }\n\n    public synchronized int get() {\n        return cnt;\n    }\n}\npublic static void main(String[] args) throws InterruptedException {\n    final int threadSize = 1000;\n    AtomicSynchronizedExample example = new AtomicSynchronizedExample();\n    final CountDownLatch countDownLatch = new CountDownLatch(threadSize);\n    ExecutorService executorService = Executors.newCachedThreadPool();\n    for (int i = 0; i < threadSize; i++) {\n        executorService.execute(() -> {\n            example.add();\n            countDownLatch.countDown();\n        });\n    }\n    countDownLatch.await();\n    executorService.shutdown();\n    System.out.println(example.get());\n}\n```\n\n```\n1000\n```\n\n\n\n#### 2. 可见性\n\n可见性指当一个线程修改了共享变量的值，其它线程能够立即得知这个修改。Java 内存模型是通过在变量修改后将新值同步回主内存，在变量读取前从主内存刷新变量值来实现可见性的。\n\n主要有有三种实现可见性的方式：\n\n- volatile\n- synchronized，对一个变量执行 unlock 操作之前，必须把变量值同步回主内存。\n- final，被 final 关键字修饰的字段在构造器中一旦初始化完成，并且没有发生 this 逃逸（其它线程通过 this 引用访问到初始化了一半的对象），那么其它线程就能看见 final 字段的值。\n\n对前面的线程不安全示例中的 cnt 变量使用 volatile 修饰，不能解决线程不安全问题，因为 volatile 并不能保证操作的原子性。\n\n\n\n#### 3. 有序性\n\n有序性是指：在本线程内观察，所有操作都是有序的。在一个线程观察另一个线程，所有操作都是无序的，无序是因为发生了指令重排序。\n\n在 Java 内存模型中，允许编译器和处理器对指令进行重排序，重排序过程不会影响到单线程程序的执行，却会影响到多线程并发执行的正确性。\n\nvolatile 关键字通过添加内存屏障的方式来禁止指令重排，即重排序时不能把后面的指令放到内存屏障之前。\n\n也可以通过 synchronized 来保证有序性，它保证每个时刻只有一个线程执行同步代码，相当于是让线程顺序执行同步代码。\n\n\n\n### 指令重排序\n\n在执行程序时为了提高性能，编译器和处理器常常会对指令做重排序。\n\n指令重排序包括：**编译器重排序**和**处理器重排序**\n\n\n\n重排序分三种类型：\n\n1. **编译器优化的重排序**。编译器在不改变单线程程序语义的前提下，可以重新安排语句的执行顺序。\n2. **指令级并行的重排序**。现代处理器采用了指令级并行技术（Instruction-Level Parallelism， ILP）来将多条指令重叠执行。如果不存在数据依赖性，处理器可以改变语句对应机器指令的执行顺序。\n3. **内存系统的重排序**。由于处理器使用缓存和读/写缓冲区，这使得加载和存储操作看上去可能是在乱序执行。\n\n从 Java 源代码到最终实际执行的指令序列，会分别经历下面三种重排序：\n\n<div align=\"center\"><img src=\"assets/33-1534150864535.png\" width=\"\"/></div>\n\n**上述的 1 属于编译器重排序，2 和 3 属于处理器重排序**。这些重排序都可能会导致多线程程序出现内存可见性问题。对于编译器，JMM 的编译器重排序规则会禁止特定类型的编译器重排序（不是所有的编译器重排序都要禁止）。对于处理器重排序，JMM 的处理器重排序规则会要求 Java 编译器在生成指令序列时，插入特定类型的内存屏障（memory barriers，intel 称之为 memory fence）指令，通过内存屏障指令来禁止特定类型的处理器重排序（不是所有的处理器重排序都要禁止）。\n\nJMM 属于语言级的内存模型，它确保在不同的编译器和不同的处理器平台之上，通过禁止特定类型的编译器重排序和处理器重排序，为程序员提供一致的内存可见性保证。\n\n#### 数据依赖性\n\n如果两个操作访问同一个变量，且这两个操作中有一个为写操作，此时这两个操作之间就存在数据依赖性。数据依赖分下列三种类型：\n\n| 名称   | 代码示例     | 说明                           |\n| ------ | ------------ | ------------------------------ |\n| 写后读 | a = 1;b = a; | 写一个变量之后，再读这个位置。 |\n| 写后写 | a = 1;a = 2; | 写一个变量之后，再写这个变量。 |\n| 读后写 | a = b;b = 1; | 读一个变量之后，再写这个变量。 |\n\n上面三种情况，只要重排序两个操作的执行顺序，程序的执行结果将会被改变。\n\n前面提到过，编译器和处理器可能会对操作做重排序。编译器和处理器在重排序时，会遵守数据依赖性，编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。\n\n注意，这里所说的数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作，不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑。\n\n#### as-if-serial语义\n\nas-if-serial 语义的意思指：不管怎么重排序（编译器和处理器为了提高并行度），（单线程）程序的执行结果不能被改变。编译器，runtime 和 处理器 都必须遵守 as-if-serial 语义。\n\n为了遵守 as-if-serial 语义，编译器和处理器不会对存在数据依赖关系的操作做重排序，因为这种重排序会改变执行结果。但是，如果操作之间不存在数据依赖关系，这些操作可能被编译器和处理器重排序。为了具体说明，请看下面计算圆面积的代码示例：\n\n```java\ndouble pi  = 3.14;    //A\ndouble r   = 1.0;     //B\ndouble area = pi * r * r; //C\n```\n\n上面三个操作的数据依赖关系如下图所示：\n\n<div align=\"center\"><img src=\"assets/11.png\" width=\"\"/></div>\n\n如上图所示，A 和 C 之间存在数据依赖关系，同时 B 和 C 之间也存在数据依赖关系。因此在最终执行的指令序列中，C 不能被重排序到 A 和 B 的前面（C 排到 A 和 B 的前面，程序的结果将会被改变）。但 A 和 B 之间没有数据依赖关系，编译器和处理器可以重排序 A 和 B 之间的执行顺序。下图是该程序的两种执行顺序：\n\n<div align=\"center\"><img src=\"assets/22.png\" width=\"\"/></div>\n\nas-if-serial 语义把单线程程序保护了起来，遵守 as-if-serial 语义的编译器，runtime 和处理器共同为编写单线程程序的程序员创建了一个幻觉：单线程程序是按程序的顺序来执行的。as-if-serial 语义使单线程程序员无需担心重排序会干扰他们，也无需担心内存可见性问题。\n\n#### 程序顺序规则\n\n根据 happens- before 的程序顺序规则，上面计算圆的面积的示例代码存在三个 happens- before 关系：\n\n1. A happens- before B；\n2. B happens- before C；\n3. A happens- before C；\n\n这里的第 3 个 happens- before 关系，是根据 happens- before 的传递性推导出来的。\n\n这里 A happens- before B，但实际执行时 B 却可以排在 A 之前执行（看上面的重排序后的执行顺序）。如果A happens- before B，JMM 并不要求 A 一定要在 B 之前执行。JMM 仅仅要求前一个操作（执行的结果）对后一个操作可见，且前一个操作按顺序排在第二个操作之前。这里操作 A 的执行结果不需要对操作 B 可见；而且重排序操作 A 和操作 B 后的执行结果，与操作 A 和操作 B 按 happens- before 顺序执行的结果一致。在这种情况下， JMM 会认为这种重排序并不非法（not illegal），JMM 允许这种重排序。\n\n在计算机中，软件技术和硬件技术有一个共同的目标：在不改变程序执行结果的前提下，尽可能的开发并行度。编译器和处理器遵从这一目标，从 happens- before 的定义我们可以看出，JMM 同样遵从这一目标。\n\n#### 重排序对多线程的影响\n\n现在让我们来看看，重排序是否会改变多线程程序的执行结果。请看下面的示例代码：\n\n```java\nclass ReorderExample {\n    int a = 0;\n    boolean flag = false;\n\n    public void writer() {\n        a = 1;                   // 1\n        flag = true;             // 2\n    }\n\n    Public void reader() {\n        if (flag) {                // 3\n            int i =  a * a;        // 4\n            ……\n        }\n    }\n}\n```\n\nflag 变量是个标记，用来标识变量 a 是否已被写入。这里假设有两个线程 A 和 B，A首先执行 writer() 方法，随后 B 线程接着执行 reader() 方法。线程 B 在执行操作 4 时，能否看到线程 A 在操作 1 对共享变量 a 的写入？\n\n答案是：不一定能看到。\n\n由于操作 1 和操作 2 没有数据依赖关系，编译器和处理器可以对这两个操作重排序；同样，操作 3 和操作 4 没有数据依赖关系，编译器和处理器也可以对这两个操作重排序。让我们先来看看，当操作 1 和操作 2 重排序时，可能会产生什么效果？请看下面的程序执行时序图：\n\n<div align=\"center\"><img src=\"assets/33.png\" width=\"\"/></div>\n\n如上图所示，操作 1 和操作 2 做了重排序。程序执行时，线程 A 首先写标记变量 flag，随后线程 B 读这个变量。由于条件判断为真，线程 B 将读取变量 a。此时，变量 a 还根本没有被线程 A 写入，在这里多线程程序的语义被重排序破坏了！\n\n※注：本文统一用红色的虚箭线表示错误的读操作，用绿色的虚箭线表示正确的读操作。\n\n下面再让我们看看，当操作 3 和操作 4 重排序时会产生什么效果（借助这个重排序，可以顺便说明控制依赖性）。下面是操作 3 和操作 4 重排序后，程序的执行时序图：\n\n<div align=\"center\"><img src=\"assets/44.png\" width=\"\"/></div>\n\n在程序中，操作 3 和操作 4 存在控制依赖关系。当代码中存在控制依赖性时，会影响指令序列执行的并行度。为此，编译器和处理器会采用猜测（Speculation）执行来克服控制相关性对并行度的影响。以处理器的猜测执行为例，执行线程 B 的处理器可以提前读取并计算 a*a，然后把计算结果临时保存到一个名为重排序缓冲（reorder buffer ROB）的硬件缓存中。当接下来操作3的条件判断为真时，就把该计算结果写入变量 i 中。\n\n从图中我们可以看出，猜测执行实质上对操作 3 和 4 做了重排序。重排序在这里破坏了多线程程序的语义！\n\n在单线程程序中，对存在控制依赖的操作重排序，不会改变执行结果（这也是 as-if-serial 语义允许对存在控制依赖的操作做重排序的原因）；但在多线程程序中，对存在控制依赖的操作重排序，可能会改变程序的执行结果。\n\n\n\n参考资料：\n\n- [深入理解Java内存模型（一）——基础](http://www.infoq.com/cn/articles/java-memory-model-1)\n\n- [深入理解Java内存模型（二）——重排序](http://www.infoq.com/cn/articles/java-memory-model-2)\n\n\n\n### 先行发生原则（happens-before）\n\nHappens-before 是用来指定两个操作之间的执行顺序。提供跨线程的内存可见性。\n\n在 Java 内存模型中，如果一个操作执行的结果需要对另一个操作可见，那么这两个操作之间必然存在 happens-before 关系。\n\n上面提到了可以用 volatile 和 synchronized 来保证有序性。除此之外，JVM 还规定了先行发生原则，让一个操作无需控制就能先于另一个操作完成。\n\n\n\n主要有以下这些原则：\n\n#### 1. 单一线程原则\n\n> Single Thread rule\n\n在一个线程内，在程序前面的操作先行发生于后面的操作。\n\n<div align=\"center\"><img src=\"assets/single-thread-rule-1534148720379.png\" width=\"\"/></div>\n\n\n\n#### 2. 管程锁定规则\n\n> Monitor Lock Rule\n\n对一个锁的解锁（unlock ），总是 happens-before 于随后对这个锁的加锁（lock）\n\n<div align=\"center\"><img src=\"assets/monitor-lock-rule-1534148737603.png\" width=\"\"/></div>\n\n\n\n#### 3. volatile 变量规则\n\n> Volatile Variable Rule\n\n对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作。\n\n<div align=\"center\"><img src=\"assets/volatile-variable-rule-1534148747964.png\" width=\"600\"/></div>\n\n\n\n#### 4. 线程启动规则\n\n> Thread Start Rule\n\nThread 对象的 start() 方法调用先行发生于此线程的每一个动作。\n\n<div align=\"center\"><img src=\"assets/thread-start-rule-1534148760654.png\" width=\"600\"/></div>\n\n\n\n#### 5. 线程加入规则\n\n> Thread Join Rule\n\nThread 对象的结束先行发生于 join() 方法返回。\n\n<div align=\"center\"><img src=\"assets/thread-join-rule-1534148774041.png\" width=\"\"/></div>\n\n\n\n#### 6. 线程中断规则\n\n> Thread Interruption Rule\n\n对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生，可以通过 interrupted() 方法检测到是否有中断发生。\n\n\n\n#### 7. 对象终结规则\n\n> Finalizer Rule\n\n一个对象的初始化完成（构造函数执行结束）先行发生于它的 finalize() 方法的开始。\n\n\n\n#### 8. 传递性\n\n> Transitivity\n\n如果操作 A 先行发生于操作 B，操作 B 先行发生于操作 C，那么操作 A 先行发生于操作 C。\n\n\n\n## 11. 线程安全\n\n### 线程安全定义\n\n一个类在可以被多个线程安全调用时就是线程安全的。\n\n### 线程安全分类\n\n线程安全不是一个非真即假的命题，可以将共享数据按照安全程度的强弱顺序分成以下五类：不可变、绝对线程安全、相对线程安全、线程兼容和线程对立。\n\n#### 1. 不可变\n\n**不可变（Immutable）的对象一定是线程安全的**，无论是对象的方法实现还是方法的调用者，都不需要再采取任何的线程安全保障措施，只要一个不可变的对象被正确地构建出来，那其外部的可见状态永远也不会改变，永远也不会看到它在多个线程之中处于不一致的状态。\n\n不可变的类型：\n\n- final 关键字修饰的基本数据类型；\n- String\n- 枚举类型\n- Number 部分子类，如 Long 和 Double 等数值包装类型，BigInteger 和 BigDecimal 等大数据类型。但同为 Number 的子类型的原子类 AtomicInteger 和 AtomicLong 则并非不可变的。\n\n对于集合类型，可以使用 Collections.unmodifiableXXX() 方法来获取一个不可变的集合。\n\n```java\npublic class ImmutableExample {\n    public static void main(String[] args) {\n        Map<String, Integer> map = new HashMap<>();\n        Map<String, Integer> unmodifiableMap = Collections.unmodifiableMap(map);\n        unmodifiableMap.put(\"a\", 1);\n    }\n}\n```\n\n```java\nException in thread \"main\" java.lang.UnsupportedOperationException\n    at java.util.Collections$UnmodifiableMap.put(Collections.java:1457)\n    at ImmutableExample.main(ImmutableExample.java:9)\n```\n\nCollections.unmodifiableXXX() 先对原始的集合进行拷贝，需要对集合进行修改的方法都直接抛出异常。\n\n```java\npublic V put(K key, V value) {\n    throw new UnsupportedOperationException();\n}\n```\n\n多线程环境下，应当尽量使对象成为不可变，来满足线程安全。\n\n\n\n#### 2. 绝对线程安全\n\n不管运行时环境如何，调用者都不需要任何额外的同步措施。\n\n\n\n#### 3. 相对线程安全\n\n相对的线程安全需要保证对这个对象单独的操作是线程安全的，在调用的时候不需要做额外的保障措施，但是对于一些特定顺序的连续调用，就可能需要在调用端使用额外的同步手段来保证调用的正确性。\n\n在 Java 语言中，大部分的线程安全类都属于这种类型，例如 Vector、HashTable、Collections 的 synchronizedCollection() 方法包装的集合等。\n\n对于下面的代码，如果删除元素的线程删除了一个元素，而获取元素的线程试图访问一个已经被删除的元素，那么就会抛出 ArrayIndexOutOfBoundsException。\n\n```java\npublic class VectorUnsafeExample {\n    private static Vector<Integer> vector = new Vector<>();\n\n    public static void main(String[] args) {\n        while (true) {\n            for (int i = 0; i < 100; i++) {\n                vector.add(i);\n            }\n            ExecutorService executorService = Executors.newCachedThreadPool();\n            executorService.execute(() -> {\n                for (int i = 0; i < vector.size(); i++) {\n                    vector.remove(i);\n                }\n            });\n            executorService.execute(() -> {\n                for (int i = 0; i < vector.size(); i++) {\n                    vector.get(i);\n                }\n            });\n            executorService.shutdown();\n        }\n    }\n}\n```\n\n```java\nException in thread \"Thread-159738\" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 3\n    at java.util.Vector.remove(Vector.java:831)\n    at VectorUnsafeExample.lambda$main$0(VectorUnsafeExample.java:14)\n    at VectorUnsafeExample$$Lambda$1/713338599.run(Unknown Source)\n    at java.lang.Thread.run(Thread.java:745)\n```\n\n如果要保证上面的代码能正确执行下去，就需要对删除元素和获取元素的代码进行同步。\n\n```java\nexecutorService.execute(() -> {\n    synchronized (vector) {\n        for (int i = 0; i < vector.size(); i++) {\n            vector.remove(i);\n        }\n    }\n});\nexecutorService.execute(() -> {\n    synchronized (vector) {\n        for (int i = 0; i < vector.size(); i++) {\n            vector.get(i);\n        }\n    }\n});\n```\n\n\n\n#### 4. 线程兼容\n\n线程兼容是指对象本身并不是线程安全的，但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用，我们平常说一个类不是线程安全的，绝大多数时候指的是这一种情况。Java API 中大部分的类都是属于线程兼容的，如与前面的 Vector 和 HashTable 相对应的集合类 ArrayList 和 HashMap 等。\n\n\n\n#### 5. 线程对立\n\n线程对立是指无论调用端是否采取了同步措施，都无法在多线程环境中并发使用的代码。由于 Java 语言天生就具备多线程特性，线程对立这种排斥多线程的代码是很少出现的，而且通常都是有害的，应当尽量避免。\n\n\n\n### 线程安全的实现方法\n\n#### 1. 阻塞同步（互斥同步）\n\nsynchronized 和 ReentrantLock。\n\n互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题，因此这种同步也称为阻塞同步。\n\n互斥同步属于一种**悲观的并发策略**，总是认为只要不去做正确的同步措施，那就肯定会出现问题。无论共享数据是否真的会出现竞争，它都要进行加锁（这里讨论的是概念模型，实际上虚拟机会优化掉很大一部分不必要的加锁）、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒等操作。\n\n\n\n#### 2. 非阻塞同步\n\n随着硬件指令集的发展，我们可以使用基于冲突检测的**乐观并发策略**：先进行操作，如果没有其它线程争用共享数据，那操作就成功了，否则采取补偿措施（不断地重试，直到成功为止）。这种乐观的并发策略的许多实现都不需要把线程挂起，因此这种同步操作称为非阻塞同步。\n\n乐观锁需要操作和冲突检测这两个步骤具备原子性，这里就不能再使用互斥同步来保证了，只能靠硬件来完成。\n\n硬件支持的原子性操作最典型的是：**比较并交换**（Compare-and-Swap，CAS）。CAS 指令需要有 3 个操作数，分别是**内存地址 V、旧的预期值 A 和新值 B**。当执行操作时，只有当 V 的值等于 A，才将 V 的值更新为 B。\n\nJ.U.C 包里面的整数原子类 AtomicInteger，其中的 compareAndSet() 和 getAndIncrement() 等方法都使用了 Unsafe 类的 CAS 操作。\n\n以下代码使用了 AtomicInteger 执行了自增的操作。\n\n```java\nprivate AtomicInteger cnt = new AtomicInteger();\n\npublic void add() {\n    cnt.incrementAndGet();\n}\n```\n\n以下代码是 incrementAndGet() 的源码，它调用了 unsafe 的 getAndAddInt() 。\n\n```java\npublic final int incrementAndGet() {\n    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;\n}\n```\n\n以下代码是 getAndAddInt() 源码，var1 指示对象内存地址，var2 指示该字段相对对象内存地址的偏移，var4 指示操作需要加的数值，这里为 1。通过 getIntVolatile(var1, var2) 得到旧的预期值，通过调用 compareAndSwapInt() 来进行 CAS 比较，如果该字段内存地址中的值 ==var5，那么就更新内存地址为 var1+var2 的变量为 var5+var4。\n\n可以看到 getAndAddInt() 在一个循环中进行，发生冲突的做法是不断的进行重试。\n\n```java\npublic final int getAndAddInt(Object var1, long var2, int var4) {\n    int var5;\n    do {\n        var5 = this.getIntVolatile(var1, var2);\n    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));\n\n    return var5;\n}\n```\n\nABA ：如果一个变量初次读取的时候是 A 值，它的值被改成了 B，后来又被改回为 A，那 CAS 操作就会误认为它从来没有被改变过。\n\nJ.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题，它可以通过控制变量值的版本来保证 CAS 的正确性。大部分情况下 ABA 问题不会影响程序并发的正确性，如果需要解决 ABA 问题，改用传统的互斥同步可能会比原子类更高效。\n\n\n\n#### 3. 无同步方案\n\n要保证线程安全，并不是一定就要进行同步，两者没有因果关系。同步只是保证共享数据争用时的正确性的手段，如果一个方法本来就不涉及共享数据，那它自然就无须任何同步措施去保证正确性，因此会有一些代码天生就是线程安全的。\n\n##### （一）可重入代码（Reentrant Code）\n\n这种代码也叫做纯代码（Pure Code），可以在代码执行的任何时刻中断它，转而去执行另外一段代码（包括递归调用它本身），而在控制权返回后，原来的程序不会出现任何错误。\n\n可重入代码有一些共同的特征，例如不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数中传入、不调用非可重入的方法等。\n\n##### （二）栈封闭\n\n多个线程访问同一个方法的局部变量时，不会出现线程安全问题，因为局部变量存储在栈中，属于线程私有的。\n\n```java\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\npublic class StackClosedExample {\n    public void add100() {\n        int cnt = 0;\n        for (int i = 0; i < 100; i++) {\n            cnt++;\n        }\n        System.out.println(cnt);\n    }\n}\n```\n\n```java\npublic static void main(String[] args) {\n    StackClosedExample example = new StackClosedExample();\n    ExecutorService executorService = Executors.newCachedThreadPool();\n    executorService.execute(() -> example.add100());\n    executorService.execute(() -> example.add100());\n    executorService.shutdown();\n}\n```\n\n```java\n100\n100\n```\n\n\n\n##### （三）线程本地存储（Thread Local Storage）\n\n如果一段代码中所需要的数据必须与其他代码共享，那就看看这些共享数据的代码是否能保证在同一个线程中执行。如果能保证，我们就可以把共享数据的可见范围限制在同一个线程之内，这样，无须同步也能保证线程之间不出现数据争用的问题。\n\n符合这种特点的应用并不少见，大部分使用消费队列的架构模式（如“生产者-消费者”模式）都会将产品的消费过程尽量在一个线程中消费完，其中最重要的一个应用实例就是经典 Web 交互模型中的 “**一个请求对应一个服务器线程**”（Thread-per-Request）的处理方式，这种处理方式的广泛应用使得很多 Web 服务端应用都可以使用线程本地存储来解决线程安全问题。\n\n可以使用 java.lang.ThreadLocal 类来实现线程本地存储功能。\n\n\n\n这是一个非常好的例题，请参考整理：\n\n[关于ThreadLocal类以下说法正确的是?_迅雷笔试题_牛客网](https://www.nowcoder.com/questionTerminal/b82e4a85a66e4dc488a5ab49094976e9?orderByHotValue=0&pos=73&mutiTagIds=171)\n\n\n\n\n\n\n**示例用法**\n\n先通过下面这个实例来理解 ThreadLocal 的用法。先声明一个 ThreadLocal 对象，存储布尔类型的数值。然后分别在main线程、Thread1、Thread2中为 ThreadLocal 对象设置不同的数值：\n\n```java\npublic class ThreadLocalDemo {\n    public static void main(String[] args) {\n\n      \t// 声明 ThreadLocal对象\n        ThreadLocal<Boolean> mThreadLocal = new ThreadLocal<Boolean>();\n\n        // 在主线程、子线程1、子线程2中去设置访问它的值\n        mThreadLocal.set(true);\n\n        System.out.println(\"Main \" + mThreadLocal.get());\n\n        new Thread(\"Thread#1\"){\n            @Override\n            public void run() {\n                mThreadLocal.set(false);\n                System.out.println(\"Thread#1 \" + mThreadLocal.get());\n            }\n        }.start();\n\n        new Thread(\"Thread#2\"){\n            @Override\n            public void run() {\n                System.out.println(\"Thread#2 \" + mThreadLocal.get());\n            }\n        }.start();\n    }\n}\n```\n\n打印的结果输出如下所示：\n\n```\nMainThread true\nThread#1 false\nThread#2 null\n```\n\n可以看见，在不同线程对同一个 ThreadLocal对象设置数值，在不同的线程中取出来的值不一样。接下来就分析一下源码，看看其内部结构。\n\n\n\n**结构概览**\n\n<div align=\"center\"><img src=\"assets/006dXScfgy1fj7s01fjqpj30ng0jbabn.jpg\" width=\"500\"/></div><br/>\n\n清晰的看到一个线程 Thread 中存在一个 ThreadLocalMap，ThreadLocalMap 中的 key 对应 ThreadLocal，在此处可见 Map 可以存储多个 key 即 (ThreadLocal)。另外 Value 就对应着在 ThreadLocal 中存储的 Value。\n\n因此总结出：每个 Thread 中都具备一个 ThreadLocalMap，而 ThreadLocalMap 可以存储以 ThreadLocal 为key的键值对。这里解释了为什么每个线程访问同一个 ThreadLocal，得到的确是不同的数值。如果此处你觉得有点突兀，接下来看源码分析！\n\n\n\n**源码分析**\n\n**1. ThreadLocal#set**\n\n```java\npublic void set(T value) {\n    // 获取当前线程对象\n    Thread t = Thread.currentThread();\n    // 根据当前线程的对象获取其内部Map\n    ThreadLocalMap map = getMap(t);\n    \n    // 注释1\n    if (map != null)\n    \tmap.set(this, value);\n    else\n    \tcreateMap(t, value);\n}\n```\n\n如上所示，大部分解释已经在代码中做出，注意`注释1`处，得到 map 对象之后，用的 `this` 作为  key，this 在这里代表的是当前线程的 ThreadLocal 对象。 另外就是第二句根据 getMap 获取一个 ThreadLocalMap，其中getMap 中传入了参数 t (当前线程对象)，这样就能够获取每个线程的 `ThreadLocal` 了。 \n\n继续跟进到 ThreadLocalMap 中查看 set 方法：\n\n\n\n**2. ThreadLocalMap**\n\nThreadLocalMap 是 ThreadLocal 的一个内部类，在分析其 set 方法之前，查看一下其类结构和成员变量。\n\n```java\n static class ThreadLocalMap {\n     // Entry类继承了WeakReference<ThreadLocal<?>>\n     // 即每个Entry对象都有一个ThreadLocal的弱引用（作为key），这是为了防止内存泄露。\n     // 一旦线程结束，key变为一个不可达的对象，这个Entry就可以被GC了。\n     static class Entry extends WeakReference<ThreadLocal<?>> {\n         /** The value associated with this ThreadLocal. */\n         Object value;\n         Entry(ThreadLocal<?> k, Object v) {\n             super(k);\n             value = v;\n         }\n     }\n     // ThreadLocalMap 的初始容量，必须为2的倍数\n     private static final int INITIAL_CAPACITY = 16;\n\n     // resized时候需要的table\n     private Entry[] table;\n\n     // table中的entry个数\n     private int size = 0;\n\n     // 扩容数值\n     private int threshold; // Default to 0\n }\n```\n\n一起看一下其常用的构造函数：\n\n```java\nThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {\n    table = new Entry[INITIAL_CAPACITY];\n    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);\n    table[i] = new Entry(firstKey, firstValue);\n    size = 1;\n    setThreshold(INITIAL_CAPACITY);\n}\n```\n\n构造函数的第一个参数就是本 ThreadLocal 实例 (this)，第二个参数就是要保存的线程本地变量。构造函数首先创建一个长度为16的 Entry 数组，然后计算出 firstKey 对应的哈希值，然后存储到 table 中，并设置 size 和 threshold。\n\n注意一个细节，计算 hash 的时候里面采用了 hashCode & (size - 1) 的算法，这相当于取模运算 hashCode % size 的一个更高效的实现（和HashMap中的思路相同）。正是因为这种算法，我们要求 size必须是 2 的指数，因为这可以使得 hash 发生冲突的次数减小。\n\n\n\n**3. ThreadLocalMap#set**\n\nThreadLocal 中 put 函数最终调用了 ThreadLocalMap 中的 set 函数，跟进去看一看：\n\n```java\nprivate void set(ThreadLocal<?> key, Object value) {\n    Entry[] tab = table;\n    int len = tab.length;\n    int i = key.threadLocalHashCode & (len-1);\n\n    for (Entry e = tab[i];\n         e != null;\n         // 冲突了\n         e = tab[i = nextIndex(i, len)]) {\n        ThreadLocal<?> k = e.get();\n\n        if (k == key) {\n            e.value = value;\n            return;\n        }\n\n        if (k == null) {\n            replaceStaleEntry(key, value, i);\n            return;\n        }\n    }\n\n    tab[i] = new Entry(key, value);\n    int sz = ++size;\n    if (!cleanSomeSlots(i, sz) && sz >= threshold)\n        rehash();\n}\n```\n\n在上述代码中如果 Entry 在存放过程中冲突了，调用 nextIndex 来处理，如下所示。是否还记得 hashmap 中对待冲突的处理？这里好像是另一种套路：只要 i 的数值小于 len，就加1取值，官方术语称为：线性探测法。\n\n```java\n private static int nextIndex(int i, int len) {\n     return ((i + 1 < len) ? i + 1 : 0);\n }\n```\n\n以上步骤ok了之后，再次关注一下源码中的 cleanSomeSlots，该函数主要的作用就是清理无用的 entry，避免出现内存泄露：\n\n```java\nprivate boolean cleanSomeSlots(int i, int n) {\n    boolean removed = false;\n    Entry[] tab = table;\n    int len = tab.length;\n    do {\n        i = nextIndex(i, len);\n        Entry e = tab[i];\n        if (e != null && e.get() == null) {\n            n = len;\n            removed = true;\n            i = expungeStaleEntry(i);\n        }\n    } while ( (n >>>= 1) != 0);\n    return removed;\n}\n```\n\n\n\n**4. ThreadLocal#get**\n\n看完了 set 函数，肯定是要关注 get 的，源码如下所示：\n\n```java\npublic T get() {\n    // 获取Thread对象t\n    Thread t = Thread.currentThread();\n    // 获取t中的map\n    ThreadLocalMap map = getMap(t);\n    if (map != null) {\n        ThreadLocalMap.Entry e = map.getEntry(this);\n        if (e != null) {\n            @SuppressWarnings(\"unchecked\")\n            T result = (T)e.value;\n            return result;\n        }\n    }\n    // 如果t中的map为空\n    return setInitialValue();\n}\n```\n\n如果 map 为 null，就返回 setInitialValue() 这个方法，跟进这个方法看一下：\n\n```java\n private T setInitialValue() {\n     T value = initialValue();\n     Thread t = Thread.currentThread();\n     ThreadLocalMap map = getMap(t);\n     if (map != null)\n         map.set(this, value);\n     else\n         createMap(t, value);\n     return value;\n }\n```\n\n最后返回的是 value，而 value 来自 `initialValue() `，进入这个源码中查看：\n\n```java\nprotected T initialValue() {\n    return null;\n}\n```\n\n原来如此，如果不设置 ThreadLocal 的数值，默认就是 null，来自于此。\n\nThreadLocal 从理论上讲并不是用来解决多线程并发问题的，因为根本不存在多线程竞争。在一些场景 (尤其是使用线程池) 下，由于 ThreadLocal.ThreadLocalMap 的底层数据结构导致 ThreadLocal 有内存泄漏的情况，尽可能在每次使用 ThreadLocal 后手动调用 remove()，以避免出现 ThreadLocal 经典的内存泄漏甚至是造成自身业务混乱的风险。\n\n\n\n参考资料：\n\n- [深入理解 Java 之 ThreadLocal 工作原理](https://allenwu.itscoder.com/threadlocal-source)\n  \n\n\n\n## 12. 锁优化\n\n这里的锁优化主要是指虚拟机对 synchronized 的优化。\n\n### 自旋锁\n\n互斥同步的进入阻塞状态的开销都很大，应该尽量避免。在许多应用中，共享数据的锁定状态只会持续很短的一段时间。自旋锁的思想是让一个线程在请求一个共享数据的锁时执行忙循环（自旋）一段时间，如果在这段时间内能获得锁，就可以避免进入阻塞状态。\n\n自旋锁虽然能避免进入阻塞状态从而减少开销，但是它需要进行忙循环操作占用 CPU 时间，它只适用于共享数据的锁定状态很短的场景。\n\n在 JDK 1.6 中引入了自适应的自旋锁。自适应意味着自旋的次数不再固定了，而是由前一次在同一个锁上的自旋次数及锁的拥有者的状态来决定。\n\n### 锁消除\n\n锁消除是指对于被检测出不可能存在竞争的共享数据的锁进行消除。\n\n锁消除主要是通过**逃逸分析**来支持，如果堆上的共享数据不可能逃逸出去被其它线程访问到，那么就可以把它们当成私有数据对待，也就可以将它们的锁进行消除。\n\n对于一些看起来没有加锁的代码，其实隐式的加了很多锁。例如下面的字符串拼接代码就隐式加了锁：\n\n```java\npublic static String concatString(String s1, String s2, String s3) {\n    return s1 + s2 + s3;\n}\n```\n\nString 是一个不可变的类，编译器会对 String 的拼接自动优化。在 JDK 1.5 之前，会转化为 StringBuffer 对象的连续 append() 操作：\n\n```java\npublic static String concatString(String s1, String s2, String s3) {\n    StringBuffer sb = new StringBuffer();\n    sb.append(s1);\n    sb.append(s2);\n    sb.append(s3);\n    return sb.toString();\n}\n```\n\n每个 append() 方法中都有一个同步块。虚拟机观察变量 sb，很快就会发现它的动态作用域被限制在 concatString() 方法内部。也就是说，sb 的所有引用永远不会“逃逸”到 concatString() 方法之外，其他线程无法访问到它，因此可以进行消除。\n\n\n\n### 锁粗化\n\n如果一系列的连续操作都对同一个对象反复加锁和解锁，频繁的加锁操作就会导致性能损耗。\n\n上一节的示例代码中连续的 append() 方法就属于这类情况。如果虚拟机探测到由这样的一串零碎的操作都对同一个对象加锁，将会把加锁的范围扩展（粗化）到整个操作序列的外部。对于上一节的示例代码就是扩展到第一个 append() 操作之前直至最后一个 append() 操作之后，这样只需要加锁一次就可以了。\n\n\n\n### 轻量级锁\n\nJDK 1.6 引入了偏向锁和轻量级锁，从而让锁拥有了四个状态：无锁状态（unlocked）、偏向锁状态（biasble）、轻量级锁状态（lightweight locked）和重量级锁状态（inflated）。\n\n以下是 HotSpot 虚拟机对象头的内存布局，这些数据被称为 mark word。其中 tag bits 对应了五个状态，这些状态在右侧的 state 表格中给出，应该注意的是 state 表格不是存储在对象头中的。除了 marked for gc 状态，其它四个状态已经在前面介绍过了。\n\n<div align=\"center\"><img src=\"assets/bb6a49be-00f2-4f27-a0ce-4ed764bc605c-1534158631668.png\" width=\"600\"/></div><br/>\n\n下图左侧是一个线程的虚拟机栈，其中有一部分称为 Lock Record 的区域，这是在轻量级锁运行过程创建的，用于存放锁对象的 Mark Word。而右侧就是一个锁对象，包含了 Mark Word 和其它信息。\n\n<div align=\"center\"><img src=\"assets/051e436c-0e46-4c59-8f67-52d89d656182-1534158643175.png\" width=\"500\"/></div>\n\n轻量级锁是相对于传统的重量级锁而言，它使用 CAS 操作来避免重量级锁使用互斥量的开销。对于绝大部分的锁，在整个同步周期内都是不存在竞争的，因此也就不需要都使用互斥量进行同步，可以先采用 CAS 操作进行同步，如果 CAS 失败了再改用互斥量进行同步。\n\n当尝试获取一个锁对象时，如果锁对象标记为 0 01，说明锁对象的锁未锁定（unlocked）状态。此时虚拟机在当前线程栈中创建 Lock Record，然后使用 CAS 操作将对象的 Mark Word 更新为 Lock Record 指针。如果 CAS 操作成功了，那么线程就获取了该对象上的锁，并且对象的 Mark Word 的锁标记变为 00，表示该对象处于轻量级锁状态。\n\n<div align=\"center\"><img src=\"assets/baaa681f-7c52-4198-a5ae-303b9386cf47-1534158703049.png\" width=\"500\"/></div>\n\n如果 CAS 操作失败了，虚拟机首先会检查对象的 Mark Word 是否指向当前线程的虚拟机栈，如果是的话说明当前线程已经拥有了这个锁对象，那就可以直接进入同步块继续执行，否则说明这个锁对象已经被其他线程线程抢占了。如果有两条以上的线程争用同一个锁，那轻量级锁就不再有效，要膨胀为重量级锁。\n\n\n\n### 偏向锁\n\n偏向锁的思想是偏向于让第一个获取锁对象的线程，这个线程在之后获取该锁就不再需要进行同步操作，甚至连 CAS 操作也不再需要。\n\n当锁对象第一次被线程获得的时候，进入偏向状态，标记为 1 01。同时使用 CAS 操作将线程 ID 记录到 Mark Word 中，如果 CAS 操作成功，这个线程以后每次进入这个锁相关的同步块就不需要再进行任何同步操作。\n\n当有另外一个线程去尝试获取这个锁对象时，偏向状态就宣告结束，此时撤销偏向（Revoke Bias）后恢复到未锁定状态或者轻量级锁状态。\n\n<div align=\"center\"><img src=\"assets/390c913b-5f31-444f-bbdb-2b88b688e7ce-1534158712253.jpg\" width=\"600\"/></div>\n\n\n\n## 13. 多线程开发良好的实践\n\n- 给线程起个有意义的名字，这样可以方便找 Bug。\n- 缩小同步范围，例如对于 synchronized，应该尽量使用同步块而不是同步方法。\n- 多用同步类少用 wait() 和 notify()。首先，CountDownLatch, CyclicBarrier, Semaphore 和 Exchanger 这些同步类简化了编码操作，而用 wait() 和 notify() 很难实现对复杂的控制流；其次，这些同步类是由最好的企业编写和维护，在后续的 JDK 中还会不断优化和完善，使用这些更高等级的同步工具你的程序可以不费吹灰之力获得优化。\n- 多用并发集合少用同步集合，例如应该使用 ConcurrentHashMap 而不是 Hashtable。\n- 使用本地变量和不可变类来保证线程安全。\n- 使用线程池而不是直接创建 Thread 对象，这是因为创建线程代价很高，线程池可以有效地利用有限的线程来启动任务。\n- 使用 BlockingQueue 实现生产者消费者问题。\n\n\n\n## 14. 线程池实现原理\n\n> 蘑菇街面试，设计一个线程池\n\n![ThrealpoolExecutor_framework](assets/ThrealpoolExecutor_framework.jpg)\n\n### 并发队列\n\n**入队**\n\n非阻塞队列：当队列中满了时候，放入数据，数据丢失\n\n阻塞队列：当队列满了的时候，进行等待，什么时候队列中有出队的数据，那么第11个再放进去\n\n**出队**\n\n非阻塞队列：如果现在队列中没有元素，取元素，得到的是null\n\n阻塞队列：等待，什么时候放进去，再取出来\n\n\n\n线程池使用的是阻塞队列\n\n\n\n### 线程池概念\n\n线程是稀缺资源，如果被无限制的创建，不仅会消耗系统资源，还会降低系统的稳定性，合理的使用线程池对线程进行统一分配、调优和监控，有以下好处：\n\n1. 降低资源消耗；\n2. 提高响应速度；\n3. 提高线程的可管理性。\n\nJava1.5 中引入的 Executor 框架把任务的提交和执行进行解耦，只需要定义好任务，然后提交给线程池，而不用关心该任务是如何执行、被哪个线程执行，以及什么时候执行。\n\n### Executor类图\n\n<div align=\"center\"><img src=\"assets/820628cf179f4952812da4e8ca5de672.png\" width=\"\"/></div>\n\n### 线程池工作原理\n\n线程池中的核心线程数，当提交一个任务时，线程池创建一个新线程执行任务，直到当前线程数等于corePoolSize；如果当前线程数为 corePoolSize，继续提交的任务被保存到阻塞队列中，等待被执行；如果阻塞队列满了，那就创建新的线程执行当前任务；直到线程池中的线程数达到 maxPoolSize，这时再有任务来，只能执行 reject() 处理该任务。\n\n### 初始化线程池\n\n- **newFixedThreadPool()**\n  说明：**初始化一个指定线程数的线程池**，其中 corePoolSize == maxiPoolSize，使用 LinkedBlockingQuene 作为阻塞队列\n  特点：即使当线程池没有可执行任务时，也不会释放线程。\n- **newCachedThreadPool()**\n  说明：**初始化一个可以缓存线程的线程池**，默认缓存60s，线程池的线程数可达到 Integer.MAX_VALUE，即 2147483647，内部使用 SynchronousQueue 作为阻塞队列；\n  特点：在没有任务执行时，当线程的空闲时间超过 keepAliveTime，会自动释放线程资源；当提交新任务时，如果没有空闲线程，则创建新线程执行任务，会导致一定的系统开销；\n  因此，使用时要注意控制并发的任务数，防止因创建大量的线程导致而降低性能。\n- **newSingleThreadExecutor()**\n  说明：**初始化只有一个线程的线程池**，内部使用 LinkedBlockingQueue 作为阻塞队列。\n  特点：如果该线程异常结束，会重新创建一个新的线程继续执行任务，唯一的线程可以保证所提交任务的顺序执行\n- **newScheduledThreadPool()**\n  特点：初始化的线程池可以在指定的时间内周期性的执行所提交的任务，在实际的业务场景中可以使用该线程池定期的同步数据。\n\n#### 初始化方法\n\n```java\n// 使用Executors静态方法进行初始化\nExecutorService service = Executors.newSingleThreadExecutor();\n// 常用方法\nservice.execute(new Thread());\nservice.submit(new Thread());\nservice.shutDown();\nservice.shutDownNow();\n```\n\n### 常用方法\n\n#### execute与submit的区别\n\n1. 接收的参数不一样\n2. submit有返回值，而execute没有\n\n用到返回值的例子，比如说我有很多个做 validation 的 task，我希望所有的 task 执行完，然后每个 task 告诉我它的执行结果，是成功还是失败，如果是失败，原因是什么。然后我就可以把所有失败的原因综合起来发给调用者。\n\n3. submit方便Exception处理\n\n如果你在你的 task 里会抛出 checked 或者 unchecked exception，而你又希望外面的调用者能够感知这些 exception 并做出及时的处理，那么就需要用到 submit，通过捕获 Future.get 抛出的异常。\n\n#### shutDown与shutDownNow的区别\n\n当线程池调用该方法时,线程池的状态则立刻变成 SHUTDOWN 状态。此时，则不能再往线程池中添加任何任务，否则将会抛出 RejectedExecutionException 异常。但是，此时线程池不会立刻退出，直到添加到线程池中的任务都已经处理完成，才会退出。\n\n### 内部实现\n\n```java\npublic ThreadPoolExecutor(\n\tint corePoolSize,     // 核心线程数\n\tint maximumPoolSize,  // 最大线程数\n\tlong keepAliveTime,   // 线程存活时间（在 corePore<*<maxPoolSize 情况下有用）\n\tTimeUnit unit,        // 存活时间的时间单位\n\tBlockingQueue<Runnable> workQueue    // 阻塞队列（用来保存等待被执行的任务）\n\tThreadFactory threadFactory,    // 线程工厂，主要用来创建线程；\n\tRejectedExecutionHandler handler // 当拒绝处理任务时的策略\n){\n    \nthis(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,\n         Executors.defaultThreadFactory(), defaultHandler);\n}\n```\n\n关于 workQueue 参数，有四种队列可供选择：\n\n- ArrayBlockingQueue：基于数组结构的有界阻塞队列，按 FIFO 排序任务；\n- LinkedBlockingQuene：基于链表结构的阻塞队列，按 FIFO 排序任务；\n- SynchronousQuene：一个不存储元素的阻塞队列，每个插入操作必须等到另一个线程调用移除操作，否则插入操作一直处于阻塞状态，吞吐量通常要高于 ArrayBlockingQuene；\n- PriorityBlockingQuene：具有优先级的无界阻塞队列；\n\n关于 handler 参数，线程池的饱和策略，当阻塞队列满了，且没有空闲的工作线程，如果继续提交任务，必须采取一种策略处理该任务，线程池提供了 4 种策略：\n\n- ThreadPoolExecutor.AbortPolicy：丢弃任务并抛出RejectedExecutionException异常。\n- ThreadPoolExecutor.DiscardPolicy：丢弃任务，但是不抛出异常。\n- ThreadPoolExecutor.DiscardOldestPolicy：丢弃队列最前面的任务，然后重新尝试执行任务（重复此过程）\n- ThreadPoolExecutor.CallerRunsPolicy：由调用线程处理该任务\n\n当然也可以根据应用场景实现 RejectedExecutionHandler 接口，自定义饱和策略，如记录日志或持久化存储不能处理的任务。\n\n### 线程池的状态\n\n```java\nprivate final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));\n```\n\n其中 AtomicInteger 变量 ctl 的功能非常强大：利用低 29 位表示线程池中线程数，通过高 3 位表示线程池的运行状态：\n\n- **RUNNING**：-1 << COUNT_BITS，即高 3 位为 111，该状态的线程池会接收新任务，并处理阻塞队列中的任务；\n- **SHUTDOWN**： 0 << COUNT_BITS，即高 3 位为 000，该状态的线程池不会接收新任务，但会处理阻塞队列中的任务；\n- **STOP** ： 1 << COUNT_BITS，即高 3 位为 001，该状态的线程不会接收新任务，也不会处理阻塞队列中的任务，而且会中断正在运行的任务；\n- **TIDYING** ： 2 << COUNT_BITS，即高 3 位为 010，该状态表示线程池对线程进行整理优化；\n- **TERMINATED**： 3 << COUNT_BITS，即高 3 位为 011，该状态表示线程池停止工作；\n\n### 线程池其他常用方法\n\n如果执行了线程池的 prestartAllCoreThreads() 方法，线程池会提前创建并启动所有核心线程。\nThreadPoolExecutor 提供了动态调整线程池容量大小的方法：setCorePoolSize() 和 setMaximumPoolSize()。\n\n### 如何合理设置线程池的大小\n\n一般需要根据任务的类型来配置线程池大小：\n如果是 CPU 密集型任务，就需要尽量压榨 CPU，参考值可以设为 NCPU+1\n如果是 IO 密集型任务，参考值可以设置为 2*NCPU\n\n\n\n- 参考资料\n  - [Java线程池的使用及原理](http://tangxiaolin.com/learn/show?id=402881d2651d1bdf01651de142df0000)\n  - [深入分析java线程池的实现原理 - 简书](https://www.jianshu.com/p/87bff5cc8d8c)\n  - [尚学堂线程池并发队列ThreadPoolExecutor_Callable原理解析哔哩哔哩 (゜-゜)つロ 干杯~-bilibili](https://www.bilibili.com/video/av26354412?p=2)\n\n\n\n# 第二部分：面试指南\n\n在这里将总结面试中和并发编程相关的常见知识点，如在第一部分中出现的这里将不进行详细阐述。面试指南中，我将用最简洁的语言描述，更多是以一种大纲的形式列出问答点，根据自己掌握的情况回答。\n\n\n\n参考资料：\n\n- [Java线程面试题 Top 50 - ImportNew](http://www.importnew.com/12773.html)\n\n\n\n## 1. volatile 与 synchronized 的区别\n\n**（1）仅靠volatile不能保证线程的安全性。（原子性）**\n\n- ① volatile 轻量级，只能修饰变量。synchronized重量级，还可修饰方法\n- ② volatile 只能保证数据的可见性，不能用来同步，因为多个线程并发访问 volatile 修饰的变量不会阻塞。\n\nsynchronized 不仅保证可见性，而且还保证原子性，因为，只有获得了锁的线程才能进入临界区，从而保证临界区中的所有语句都全部执行。多个线程争抢 synchronized 锁对象时，会出现阻塞。\n\n\n\n**（2）线程安全性**\n\n线程安全性包括两个方面，①可见性。②原子性。\n\n从上面自增的例子中可以看出：仅仅使用 volatile 并不能保证线程安全性。而 synchronized 则可实现线程的安全性。\n\n\n\n## 2. 什么是线程池？如果让你设计一个动态大小的线程池，如何设计，应该有哪些方法？线程池创建的方式？\n\n- 什么是线程池 \n\n  - 线程池顾名思义就是事先创建若干个可执行的线程放入一个池（容器）中，需要的时候从池中获取线程不用自行创建，使用完毕不需要销毁线程而是放回池中，从而减少创建和销毁线程对象的开销。 \n\n- 设计一个动态大小的线程池，如何设计，应该有哪些方法 \n\n  - 一个线程池包括以下四个基本组成部分： \n    - 线程管理器 (ThreadPool)：用于创建并管理线程池，包括创建线程，销毁线程池，添加新任务； \n    - 工作线程 (PoolWorker)：线程池中线程，在没有任务时处于等待状态，可以循环的执行任务； \n    - 任务接口 (Task)：每个任务必须实现的接口，以供工作线程调度任务的执行，它主要规定了任务的入口，任务执行完后的收尾工作，任务的执行状态等； \n    - 任务队列 (TaskQueue)：用于存放没有处理的任务。提供一种缓冲机制； \n  - 所包含的方法 \n    - private ThreadPool()  创建线程池 \n    - public static ThreadPool getThreadPool()  获得一个默认线程个数的线程池  \n    - public void execute(Runnable task)  执行任务,其实只是把任务加入任务队列，什么时候执行有线程池管理器决定 \n    - public void execute(Runnable[] task)  批量执行任务,其实只是把任务加入任务队列，什么时候执行有线程池管理器决定 \n    - public void destroy()  销毁线程池,该方法保证在所有任务都完成的情况下才销毁所有线程，否则等待任务完成才销毁 \n    - public int getWorkThreadNumber() 返回工作线程的个数  \n    - public int getFinishedTasknumber() 返回已完成任务的个数,这里的已完成是只出了任务队列的任务个数，可能该任务并没有实际执行完成 \n    - public void addThread() 在保证线程池中所有线程正在执行，并且要执行线程的个数大于某一值时。增加线程池中线程的个数 \n    - public void reduceThread() 在保证线程池中有很大一部分线程处于空闲状态，并且空闲状态的线程在小于某一值时，减少线程池中线程的个数  \n\n- 线程池四种创建方式\n\n  Java 通过 Executors 提供四种线程池，分别为：\n\n  - new CachedThreadPool 创建一个可缓存线程池，如果线程池长度超过处理需要，可灵活回收空闲线程，若无可回收，则新建线程。\n  - new FixedThreadPool 创建一个定长线程池，可控制线程最大并发数，超出的线程会在队列中等待。\n  - new ScheduledThreadPool 创建一个定长线程池，支持定时及周期性任务执行。\n  - new SingleThreadExecutor 创建一个单线程化的线程池，它只会用唯一的工作线程来执行任务，保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。\n\n\n\n## 3. 什么是并发和并行\n\n<div align=\"center\"> <img src=\"assets/concurrent_and_parallel.png\" width=\"\"/></div><br/>\n\n### 并发\n\n- 并发是指两个任务都请求运行，而处理器只能按受一个任务，就把这两个任务安排轮流进行，由于时间间隔较短，使人感觉两个任务都在运行。\n- 如果用一台电脑我先给甲发个消息，然后立刻再给乙发消息，然后再跟甲聊，再跟乙聊。这就叫并发。\n- 多个线程操作相同的资源，保证线程安全，合理使用资源\n\n\n\n### 并行\n\n- 并行就是两个任务同时运行，就是甲任务进行的同时，乙任务也在进行。(需要多核CPU)\n\n- 比如我跟两个网友聊天，左手操作一个电脑跟甲聊，同时右手用另一台电脑跟乙聊天，这就叫并行。\n\n- 服务能同时处理很多请求，提高程序性能\n\n\n参考资料：\n\n- [Cplusplus-Concurrency-In-Practice/1.1 What is concurrency.md at master · forhappy/Cplusplus-Concurrency-In-Practice](https://github.com/forhappy/Cplusplus-Concurrency-In-Practice/blob/master/zh/chapter1-Introduction/1.1%20What%20is%20concurrency.md)\n\n\n\n## 4. 什么是线程安全\n\n当多个线程访问同一个对象时，如果不用考虑这些线程在运行时环境下的调度和交替运行，也不需要进行额外的同步，或者在调用方进行任何其他的协调操作，调用这个对象的行为都可以获取正确的结果，那这个对象是线程安全的。——来自《深入理解Java虚拟机》\n\n- 定义 \n\n  - 某个类的行为与其规范一致。 \n  - 不管多个线程是怎样的执行顺序和优先级,或是 wait , sleep , join 等控制方式，如果一个类在多线程访问下运转一切正常，并且访问类不需要进行额外的同步处理或者协调，那么我们就认为它是线程安全的。  \n\n- 如何保证线程安全？（更加详细的请转向第一部分 `11. 线程安全`）\n\n  - 对变量使用 volitate \n  - 对程序段进行加锁 (synchronized , lock) \n\n- 注意 \n\n  - 非线程安全的集合在多线程环境下可以使用，但并不能作为多个线程共享的属性，可以作为某个线程独享的属性。 \n  - 例如 Vector 是线程安全的，ArrayList 不是线程安全的。如果每一个线程中 new 一个 ArrayList，而这个ArrayList 只是在这一个线程中使用，肯定没问题。 \n\n\n\n### 非线程安全!=不安全？\n\n有人在使用过程中有一个不正确的观点：我的程序是多线程的，不能使用 ArrayList 要使用 Vector，这样才安全。\n\n**非线程安全并不是多线程环境下就不能使用**。注意我上面有说到：**多线程操作同一个对象**。注意是**同一个对象**。比如最上面那个模拟，就是在主线程中 new 的一个 ArrayList 然后多个线程操作同一个 ArrayList 对象。\n\n如果是每个线程中 new 一个 ArrayList，而这个 ArrayList 只在这一个线程中使用，那么肯定是没问题的。\n\n\n\n### 线程安全十万个为什么？\n\n**问：平时项目中使用锁和 synchronized 比较多，而很少使用 volatile，难道就没有保证可见性？**\n答：锁和 synchronized 即可以保证原子性，也可以保证可见性。都是通过保证同一时间只有一个线程执行目标代码段来实现的。\n\n\n\n**问：锁和 synchronized 为何能保证可见性？**\n答：根据 [JDK 7的Java doc](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/package-summary.html#MemoryVisibility) 中对 `concurrent` 包的说明，一个线程的写结果保证对另外线程的读操作可见，只要该写操作可以由 `happen-before` 原则推断出在读操作之前发生。\n\n> The results of a write by one thread are guaranteed to be **visible** to a read by another thread only if the write operation happens-before the read operation. The synchronized and volatile constructs, as well as the Thread.start() and Thread.join() methods, can form happens-before relationships.\n\n\n\n**问：既然锁和 synchronized 即可保证原子性也可保证可见性，为何还需要 volatile？**\n答：synchronized和锁需要通过操作系统来仲裁谁获得锁，开销比较高，而 volatile 开销小很多。因此在只需要保证可见性的条件下，使用 volatile 的性能要比使用锁和 synchronized 高得多。\n\n\n\n**问：既然锁和 synchronized 可以保证原子性，为什么还需要 AtomicInteger 这种的类来保证原子操作？**\n答：锁和 synchronized 需要通过操作系统来仲裁谁获得锁，开销比较高，而 AtomicInteger 是通过CPU级的CAS操作来保证原子性，开销比较小。所以使用 AtomicInteger 的目的还是为了提高性能。\n\n\n\n**问：还有没有别的办法保证线程安全**\n答：有。尽可能避免引起非线程安全的条件——共享变量。如果能从设计上避免共享变量的使用，即可避免非线程安全的发生，也就无须通过锁或者 synchronized 以及 volatile 解决原子性、可见性和顺序性的问题。\n\n\n\n**问：synchronized 即可修饰非静态方式，也可修饰静态方法，还可修饰代码块，有何区别**\n答：synchronized 修饰非静态同步方法时，锁住的是当前实例；synchronized 修饰静态同步方法时，锁住的是该类的 Class 对象；synchronized 修饰静态代码块时，锁住的是 synchronized 关键字后面括号内的对象。\n\n\n\n参考资料：\n\n- [Java进阶（二）当我们说线程安全时，到底在说什么](http://www.jasongj.com/java/thread_safe/)\n\n\n\n## 5. volatile 关键字的如何保证内存可见性\n\n- volatile 关键字的作用 \n\n  - 保证内存的可见性 \n  - 防止指令重排 \n  - 注意：volatile 并不保证原子性 \n\n- 内存可见性 \n\n  - volatile 保证可见性的原理是在每次访问变量时都会进行一次刷新，因此每次访问都是主内存中最新的版本。所以 volatile 关键字的作用之一就是保证变量修改的实时可见性。 \n\n- 当且仅当满足以下所有条件时，才应该使用 volatile 变量 \n\n  - 对变量的写入操作不依赖变量的当前值，或者你能确保只有单个线程更新变量的值。 \n  - 该变量没有包含在具有其他变量的不变式中。 \n\n- volatile 使用建议 \n\n  - 在两个或者更多的线程需要访问的成员变量上使用 volatile。当要访问的变量已在 synchronized 代码块中，或者为常量时，没必要使用volatile。 \n  - 由于使用 volatile 屏蔽掉了 JVM 中必要的代码优化，所以在效率上比较低，因此一定在必要时才使用此关键字。 \n\n- volatile 和 synchronized区别 \n\n  - volatile 不会进行加锁操作： \n\n  　　volatile 变量是一种稍弱的同步机制在访问 volatile 变量时不会执行加锁操作，因此也就不会使执行线程阻塞，因此 volatile 变量是一种比 synchronized 关键字更轻量级的同步机制。 \n\n  - volatile 变量作用类似于同步变量读写操作： \n\n  　　从内存可见性的角度看，写入 volatile 变量相当于退出同步代码块，而读取 volatile 变量相当于进入同步代码块。 \n\n  - volatile 不如 synchronized安全： \n\n  　　在代码中如果过度依赖 volatile 变量来控制状态的可见性，通常会比使用锁的代码更脆弱，也更难以理解。仅当 volatile 变量能简化代码的实现以及对同步策略的验证时，才应该使用它。一般来说，用同步机制会更安全些。 \n\n  - volatile 无法同时保证内存可见性和原子性： \n\n  　　加锁机制（即同步机制）既可以确保可见性又可以确保原子性，而 volatile 变量只能确保可见性，原因是声明为volatile的简单变量如果当前值与该变量以前的值相关，那么 volatile 关键字不起作用，也就是说如下的表达式都不是原子操作：“count++”、“count = count+1”。 \n\n   \n\n## 5. 什么是线程？线程和进程有什么区别？为什么要使用多线程\n\n（1）线程和进程\n\n- 进程是操作系统**分配资源**的最小单位 \n- 线程是**CPU调度**的最小单位  \n\n\n\n（2）使用线程的原因\n\n- 使用多线程可以减少程序的响应时间；\n- 与进程相比，线程的创建和切换开销更小；\n- 多核电脑上，可以同时执行多个线程，提高资源利用率；\n- 简化程序的结构，使程序便于理解和维护；\n\n\n\n## 6. 多线程共用一个数据变量需要注意什么？\n\n- 当我们在线程对象（Runnable）中定义了全局变量，run方法会修改该变量时，如果有多个线程同时使用该线程对象，那么就会造成全局变量的值被同时修改，造成错误. \n- ThreadLocal 是JDK引入的一种机制，它用于解决线程间共享变量，使用 ThreadLocal 声明的变量，即使在线程中属于全局变量，针对每个线程来讲，这个变量也是独立的。 \n- volatile 变量每次被线程访问时，都强迫线程从主内存中重读该变量的最新值，而当该变量发生修改变化时，也会强迫线程将最新的值刷新回主内存中。这样一来，不同的线程都能及时的看到该变量的最新值。 \n\n\n\n## 7. 内存泄漏与内存溢出\n\n### Java内存回收机制\n\n　　不论哪种语言的内存分配方式，都需要返回所分配内存的真实地址，也就是返回一个指针到内存块的首地址。Java中对象是采用 new、反射、clone、反序列化等方法创建的， 这些对象的创建都是在堆（Heap）中分配的，所有对象的回收都是由Java虚拟机通过垃圾回收机制完成的。GC 为了能够正确释放对象，会监控每个对象的运行状况，对他们的申请、引用、被引用、赋值等状况进行监控，Java 会使用有向图的方法进行管理内存，实时监控对象是否可以达到，如果不可到达，则就将其回收，这样也可以消除引用循环的问题。\n\n　　在 Java 语言中，判断一个内存空间是否符合垃圾收集标准有两个：一个是给对象赋予了空值 null，以下再没有调用过，另一个是给对象赋予了新值，这样重新分配了内存空间。 \n\n### Java内存泄露引起原因\n\n　　首先，什么是内存泄露？经常听人谈起内存泄露，但要问什么是内存泄露，没几个说得清楚。\n\n　　**内存泄露**：是指无用对象（不再使用的对象）持续占有内存或无用对象的内存得不到及时释放，从而造成的内存空间的浪费称为内存泄露。内存泄露有时不严重且不易察觉，这样开发者就不知道存在内存泄露，但有时也会很严重，会提示 `Out of memory`。\n\n　　**内存溢出**：指程序运行过程中**无法申请到足够的内存**而导致的一种错误。**内存泄露是内存溢出的一种诱因**，不是唯一因素\n　　那么，Java 内存泄露根本原因是什么呢？长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露，尽管短生命周期对象已经不再需要，但是因为长生命周期对象持有它的引用而导致不能被回收，这就是 Java 中内存泄露的发生场景。具体主要有如下几大类\n\n#### 静态集合类\n\n　　静态集合类，使用Set、Vector、HashMap等集合类的时候需要特别注意。当这些类被定义成静态的时候，由于他们的生命周期跟应用程序一样长，这时候就有可能发生内存泄漏。 \n\n```java\n// 例子 \nclass StaticTest \n{ \n    private static Vector v = new Vector(10); \n    public void init() \n    { \n        for (int i = 1; i < 100; i++) \n        { \n            Object object = new Object(); \n            v.add(object); \n            object = null; \n        } \n    } \n} \n```\n\n　　在上面的代码中，循环申请object对象，并添加到Vector中，然后设置object=null（就是清除栈中引用变量object）,但是这些对象被vector引用着，必然不能被GC回收，造成内存泄露。因此要释放这些对象，还需要将它们从vector中删除，最简单的方法就是将vector=null,清空集合类中的引用。 \n\n \n\n#### 监听器\n\n　　在 Java 编程中，我们都需要和监听器打交道，通常一个应用中会用到很多监听器，我们会调用一个控件，诸如 `addXXXListener()` 等方法来增加监听器，但往往在释放的时候却没有去删除这些监听器，从而增加了内存泄漏的机会。 \n\n\n\n#### 各种连接\n\n　　比如数据库连接（dataSourse.getConnection()），网络连接 (socket) 和 IO 连接，除非其显式的调用了其close() 方 法将其连接关闭，否则是不会自动被 GC 回收的。对于 Resultset 和 Statement 对象可以不进行显式回收，但 Connection 一定要显式回收，因为 Connection 在任何时候都无法自动回收，而 Connection一旦回收，Resultset 和 Statement 对象就会立即为 NULL。但是如果使用连接池，情况就不一样了，除了要显式地关闭连接，还必须显式地关闭 Resultset Statement 对象（关闭其中一个，另外一个也会关闭），否则就会造成大量的 Statement 对象无法释放，从而引起内存泄漏。这种情况下一般都会在 try 里面去的连接，在 finally 里面释放连接。 \n\n\n\n#### 内部类和外部模块等的引用\n\n　　内部类的引用是比较容易遗忘的一种，而且一旦没释放可能导致一系列的后继类对象没有释放。在调用外部模块的时候，也应该注意防止内存泄漏，如果模块A调用了外部模块B的一个方法，如： `public void register(Object o) ` 这个方法有可能就使得A模块持有传入对象的引用，这时候需要查看B模块是否提供了出去引用的方法，这种情况容易忽略，而且发生内存泄漏的话，还比较难察觉。 \n\n\n\n#### 单例模式\n\n　　因为单利对象初始化后将在 JVM 的整个生命周期内存在，如果它持有一个外部对象的（生命周期比较短）引用，那么这个外部对象就不能被回收，从而导致内存泄漏。如果这个外部对象还持有其他对象的引用，那么内存泄漏更严重。 \n\n\n\n## 8. 如何减少线程上下文切换\n\n使用多线程时，**不是多线程能提升程序的执行速度**，使用多线程是为了**更好地利用 CPU 资源**！\n\n程序在执行时，多线程是 CPU 通过给每个线程**分配 CPU 时间片来实现**的，时间片是CPU分配给每个线程执行的时间，因时间片非常短，所以**CPU 通过不停地切换线程执行**。\n\n线程**不是越多就越好**的，因为线程上下文切换是有**性能损耗**的，在使用多线程的同时需要考虑如何减少上下文切换\n\n一般来说有以下几条经验\n\n- **无锁并发编程**。多线程竞争时，会引起上下文切换，所以多线程处理数据时，可以用一些办法来避免使用锁，如将数据的 ID 按照Hash取模分段，不同的线程处理不同段的数据\n- **CAS算法**。Java 的 Atomic 包使用 CAS 算法来更新数据，**而不需要加锁**。\n- **控制线程数量**。避免创建不需要的线程，比如任务很少，但是创建了很多线程来处理，这样会造成大量线程都处于等待状态\n- **协程**。在单线程里实现多任务的调度，并在单线程里维持多个任务间的切换\n- 协程可以看成是用户态**自管理的“线程”**。**不会参与**CPU时间调度，没有均衡分配到时间。**非抢占式**的\n\n还可以考虑我们的应用是**IO密集型的还是CPU密集型**的。\n\n- 如果是IO密集型的话，线程可以多一些。\n- 如果是CPU密集型的话，线程不宜太多。\n\n\n\n## 9. 线程间通信和进程间通信\n\n### 线程间通信\n\n- **synchronized 同步**\n\n  - 这种方式，本质上就是 “共享内存” 式的通信。多个线程需要访问同一个共享变量，谁拿到了锁（获得了访问权限），谁就可以执行。\n\n- **while 轮询的方式**\n\n  - 在这种方式下，线程A不断地改变条件，线程 ThreadB 不停地通过 while 语句检测这个条件`(list.size()==5)` 是否成立 ，从而实现了线程间的通信。但是这种方式会浪费 CPU 资源。之所以说它浪费资源，是因为 JVM 调度器将 CPU 交给线程B执行时，它没做啥“有用”的工作，只是在不断地测试某个条件是否成立。就类似于现实生活中，某个人一直看着手机屏幕是否有电话来了，而不是： 在干别的事情，当有电话来时，响铃通知TA电话来了。\n\n- **wait/notify 机制**\n\n  - 当条件未满足时，线程A调用 wait() 放弃CPU，并进入阻塞状态。（不像 while 轮询那样占用 CPU）\n\n    当条件满足时，线程B调用 notify() 通知线程A，所谓通知线程A，就是唤醒线程A，并让它进入可运行状态。\n\n- **管道通信**\n\n  - java.io.PipedInputStream 和 java.io.PipedOutputStream 进行通信\n\n\n\n### 进程间通信\n\n- **管道（Pipe）** ：管道可用于具有亲缘关系进程间的通信，允许一个进程和另一个与它有共同祖先的进程之间进行通信。\n- **命名管道（named pipe）** ：命名管道克服了管道没有名字的限制，因此，除具有管道所具有的功能外，它还允许无亲缘关 系 进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。\n- **信号（Signal）** ：信号是比较复杂的通信方式，用于通知接受进程有某种事件发生，除了用于进程间通信外，进程还可以发送 信号给进程本身；Linux除了支持Unix早期信号语义函数sigal外，还支持语义符合Posix.1标准的信号函数sigaction（实际上，该函数是基于BSD的，BSD为了实现可靠信号机制，又能够统一对外接口，用sigaction函数重新实现了signal函数）。\n- **消息（Message）队列** ：消息队列是消息的链接表，包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息，被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少，管道只能承载无格式字节流以及缓冲区大小受限等缺\n- **共享内存** ：使得多个进程可以访问同一块内存空间，是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制，如信号量结合使用，来达到进程间的同步及互斥。\n- **内存映射（mapped memory）** ：内存映射允许任何多个进程间通信，每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。\n- **信号量（semaphore）** ：主要作为进程间以及同一进程不同线程之间的同步手段。\n- **套接口（Socket）** ：更为一般的进程间通信机制，可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的，但现在一般可以移植到其它类Unix系统上：linux和System V的变种都支持套接字。\n\n\n\n参考资料：\n\n- [Java线程与线程、进程与进程之间通信方式 | 理想村 | 屠杭镝的博客](https://www.tuhd.top/2017/08/04/2017-08-04-threadandprocess/)\n- [JAVA多线程之线程间的通信方式 - hapjin - 博客园](https://www.cnblogs.com/hapjin/p/5492619.html)\n\n\n\n## 10. 什么是同步和异步，阻塞和非阻塞？\n\n> 同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)\n\n### 同步\n\n- 在发出一个同步调用时，在没有得到结果之前，该调用就不返回。\n- 例如：按下电饭锅的煮饭按钮，然后等待饭煮好，把饭盛出来，然后再去炒菜。\n\n### 异步\n\n- 在发出一个异步调用后，调用者不会立刻得到结果，该调用就返回了。\n- 例如：按下电钮锅的煮饭按钮，直接去炒菜或者做别的事情，当电饭锅“滴滴滴”响的时候，再回去把饭盛出来。显然，异步式编程要比同步式编程高效得多。\n\n\n\n> 阻塞和非阻塞关注的是程序在等待调用结果（消息，返回值）时的状态.\n\n### 阻塞\n\n- 调用结果返回之前，当前线程会被挂起。调用线程只有在得到结果之后才会返回。\n- 例子：你打电话问书店老板有没有《分布式系统》这本书，你如果是阻塞式调用，你会一直把自己“挂起”，直到得到这本书有没有的结果 \n\n### 非阻塞\n\n- 在不能立刻得到结果之前，该调用不会阻塞当前线程。\n- 例子：你打电话问书店老板有没有《分布式系统》这本书，你不管老板有没有告诉你，你自己先一边去玩了， 当然你也要偶尔过几分钟check一下老板有没有返回结果。 \n\n\n\n参考资料：\n\n- [深入理解并发 / 并行，阻塞 / 非阻塞，同步 / 异步 - 后端 - 掘金](https://juejin.im/entry/58ae4636b123db0052b1caf8)\n\n\n\n## 11. Java中的锁\n\n本小结参考：[Java 中的锁 - Java 并发性和多线程 - 极客学院Wiki](http://wiki.jikexueyuan.com/project/java-concurrent/locks-in-java.html)\n\n　　锁像 synchronized 同步块一样，是一种线程同步机制，但比 Java 中的 synchronized 同步块更复杂。因为锁（以及其它更高级的线程同步机制）是由 synchronized 同步块的方式实现的，所以我们还不能完全摆脱 synchronized 关键字（译者注：这说的是 Java 5 之前的情况）。\n\n　　自 Java 5 开始，java.util.concurrent.locks 包中包含了一些锁的实现，因此你不用去实现自己的锁了。但是你仍然需要去了解怎样使用这些锁，且了解这些实现背后的理论也是很有用处的。可以参考我对 java.util.concurrent.locks.Lock 的介绍，以了解更多关于锁的信息。\n\n### 一个简单的锁\n\n让我们从 java 中的一个同步块开始：\n\n```java\npublic class Counter{\n    private int count = 0;\n\n    public int inc(){\n        synchronized(this){\n            return ++count;\n        }\n    }\n}\n```\n\n可以看到在 inc()方法中有一个 synchronized(this)代码块。该代码块可以保证在同一时间只有一个线程可以执行 return ++count。虽然在 synchronized 的同步块中的代码可以更加复杂，但是++count 这种简单的操作已经足以表达出线程同步的意思。\n\n以下的 Counter 类用 Lock 代替 synchronized 达到了同样的目的：\n\n```java\npublic class Counter{\n    private Lock lock = new Lock();\n    private int count = 0;\n\n    public int inc(){\n        lock.lock();\n        int newCount = ++count;\n        lock.unlock();\n        return newCount;\n    }\n}\n```\n\nlock()方法会对 Lock 实例对象进行加锁，因此所有对该对象调用 lock()方法的线程都会被阻塞，直到该 Lock 对象的 unlock()方法被调用。\n\n这里有一个 Lock 类的简单实现：\n\n```java\npublic class Counter{\npublic class Lock{\n    private boolean isLocked = false;\n\n    public synchronized void lock()\n        throws InterruptedException{\n        while(isLocked){\n            wait();\n        }\n        isLocked = true;\n    }\n\n    public synchronized void unlock(){\n        isLocked = false;\n        notify();\n    }\n}\n```\n\n注意其中的 while(isLocked) 循环，它又被叫做 “**自旋锁**”。自旋锁以及 wait() 和 notify() 方法在[线程通信](http://wiki.jikexueyuan.com/project/java-concurrent/thread-communication.html)这篇文章中有更加详细的介绍。当 isLocked 为 true 时，调用 lock() 的线程在 wait() 调用上阻塞等待。为防止该线程没有收到 notify() 调用也从 wait() 中返回（也称作[虚假唤醒](http://wiki.jikexueyuan.com/project/java-concurrent/race-conditions-and-critical-sections.html)），这个线程会重新去检查 isLocked 条件以决定当前是否可以安全地继续执行还是需要重新保持等待，而不是认为线程被唤醒了就可以安全地继续执行了。如果 isLocked 为 false，当前线程会退出 while(isLocked) 循环，并将 isLocked 设回 true，让其它正在调用 lock() 方法的线程能够在 Lock 实例上加锁。\n\n当线程完成了[临界区](http://wiki.jikexueyuan.com/project/java-concurrent/race-conditions-and-critical-sections.html)（位于 lock()和 unlock()之间）中的代码，就会调用 unlock()。执行 unlock()会重新将 isLocked 设置为 false，并且通知（唤醒）其中一个（若有的话）在 lock()方法中调用了 wait()函数而处于等待状态的线程。\n\n### 锁的可重入性\n\nJava 中的 synchronized 同步块是可重入的。这意味着如果一个 Java 线程进入了代码中的 synchronized 同步块，并因此获得了该同步块使用的同步对象对应的管程上的锁，那么这个线程可以进入由同一个管程对象所同步的另一个 java 代码块。下面是一个例子：\n\n```java\npublic class Reentrant{\n    public synchronized outer(){\n        inner();\n    }\n\n    public synchronized inner(){\n        //do something\n    }\n}\n```\n\n注意 outer()和 inner()都被声明为 synchronized，这在 Java 中和 synchronized(this) 块等效。如果一个线程调用了 outer()，在 outer()里调用 inner()就没有什么问题，因为这两个方法（代码块）都由同一个管程对象（”this”) 所同步。如果一个线程已经拥有了一个管程对象上的锁，那么它就有权访问被这个管程对象同步的所有代码块。这就是可重入。线程可以进入任何一个它已经拥有的锁所同步着的代码块。\n\n前面给出的锁实现不是可重入的。如果我们像下面这样重写 Reentrant 类，当线程调用 outer() 时，会在 inner()方法的 lock.lock() 处阻塞住。\n\n```java\npublic class Reentrant2{\n    Lock lock = new Lock();\n\n    public outer(){\n        lock.lock();\n        inner();\n        lock.unlock();\n    }\n\n    public synchronized inner(){\n        lock.lock();\n        //do something\n        lock.unlock();\n    }\n}\n```\n\n调用 outer() 的线程首先会锁住 Lock 实例，然后继续调用 inner()。inner()方法中该线程将再一次尝试锁住 Lock 实例，结果该动作会失败（也就是说该线程会被阻塞），因为这个 Lock 实例已经在 outer()方法中被锁住了。\n\n两次 lock()之间没有调用 unlock()，第二次调用 lock 就会阻塞，看过 lock() 实现后，会发现原因很明显：\n\n```java\npublic class Lock{\n    boolean isLocked = false;\n\n    public synchronized void lock()\n        throws InterruptedException{\n        while(isLocked){\n            wait();\n        }\n        isLocked = true;\n    }\n\n    ...\n}\n```\n\n一个线程是否被允许退出 lock()方法是由 while 循环（自旋锁）中的条件决定的。当前的判断条件是只有当 isLocked 为 false 时 lock 操作才被允许，而没有考虑是哪个线程锁住了它。\n\n为了让这个 Lock 类具有可重入性，我们需要对它做一点小的改动：\n\n```java\npublic class Lock{\n    boolean isLocked = false;\n    Thread  lockedBy = null;\n    int lockedCount = 0;\n\n    public synchronized void lock()\n        throws InterruptedException{\n        Thread callingThread =\n            Thread.currentThread();\n        while(isLocked && lockedBy != callingThread){\n            wait();\n        }\n        isLocked = true;\n        lockedCount++;\n        lockedBy = callingThread;\n  }\n\n    public synchronized void unlock(){\n        if(Thread.curentThread() ==\n            this.lockedBy){\n            lockedCount--;\n\n            if(lockedCount == 0){\n                isLocked = false;\n                notify();\n            }\n        }\n    }\n\n    ...\n}\n```\n\n注意到现在的 while 循环（自旋锁）也考虑到了已锁住该 Lock 实例的线程。如果当前的锁对象没有被加锁(isLocked = false)，或者当前调用线程已经对该 Lock 实例加了锁，那么 while 循环就不会被执行，调用 lock()的线程就可以退出该方法（译者注：“被允许退出该方法”在当前语义下就是指不会调用 wait()而导致阻塞）。\n\n除此之外，我们需要记录同一个线程重复对一个锁对象加锁的次数。否则，一次 unblock()调用就会解除整个锁，即使当前锁已经被加锁过多次。在 unlock()调用没有达到对应 lock()调用的次数之前，我们不希望锁被解除。\n\n现在这个 Lock 类就是可重入的了。\n\n### 锁的公平性\n\nJava 的 synchronized 块并不保证尝试进入它们的线程的顺序。因此，如果多个线程不断竞争访问相同的 synchronized 同步块，就存在一种风险，其中一个或多个线程永远也得不到访问权 —— 也就是说访问权总是分配给了其它线程。这种情况被称作线程饥饿。为了避免这种问题，锁需要实现公平性。本文所展现的锁在内部是用 synchronized 同步块实现的，因此它们也不保证公平性。[饥饿和公平](http://wiki.jikexueyuan.com/project/java-concurrent/starvation-and-fairness.html)中有更多关于该内容的讨论。\n\n\n\n### 在 finally 语句中调用 unlock()\n\n如果用 Lock 来保护临界区，并且临界区有可能会抛出异常，那么在 finally 语句中调用 unlock()就显得非常重要了。这样可以保证这个锁对象可以被解锁以便其它线程能继续对其加锁。以下是一个示例：\n\n```java\nlock.lock();\ntry{\n    //do critical section code,\n    //which may throw exception\n} finally {\n    lock.unlock();\n}\n```\n\n这个简单的结构可以保证当临界区抛出异常时 Lock 对象可以被解锁。如果不是在 finally 语句中调用的 unlock()，当临界区抛出异常时，Lock 对象将永远停留在被锁住的状态，这会导致其它所有在该 Lock 对象上调用 lock()的线程一直阻塞。\n\n\n\n## 12. 并发包(J.U.C)下面，都用过什么\n\n- concurrent下面的包 \n  - Executor  用来创建线程池，在实现Callable接口时，添加线程。 \n  - FeatureTask 此 FutureTask 的 get 方法所返回的结果类型。 \n  - TimeUnit \n  - Semaphore  \n  - LinkedBlockingQueue  \n- 所用过的类 \n  - Executor   \n\n\n\n\n## 13. 从volatile说到,i++原子操作,线程安全问题\n\n从 volatile 说到，i++原子操作，线程安全问题 - CSDN博客\nhttps://blog.csdn.net/zbw18297786698/article/details/53420780\n\n\n\n\n\n# 参考资料\n\n- [Interview-Notebook/Java 并发.md at master · CyC2018/Interview-Notebook](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/Java%20%E5%B9%B6%E5%8F%91.md)\n\n- [Java 并发编程-极客学院Wiki](http://wiki.jikexueyuan.com/project/java-concurrency/)\n\n\n\n\n# 更新日志\n\n- 2018/9/2 v3.0\n"
  },
  {
    "path": "notes/JavaArchitecture/04-Java-IO.md",
    "content": "<!-- TOC -->\n\n- [Java IO](#java-io)\n    - [1、磁盘操作（File）](#1磁盘操作file)\n    - [2、字节操作（*Stream）](#2字节操作stream)\n    - [3、字符操作（*Reader | *Writer）](#3字符操作reader--writer)\n    - [4、Java序列化，如何实现序列化和反序列化，常见的序列化协议有哪些？](#4java序列化如何实现序列化和反序列化常见的序列化协议有哪些)\n        - [Java序列化定义](#java序列化定义)\n        - [如何实现序列化和反序列化，底层怎么实现](#如何实现序列化和反序列化底层怎么实现)\n        - [相关注意事项](#相关注意事项)\n        - [常见的序列化协议有哪些](#常见的序列化协议有哪些)\n    - [5、同步和异步](#5同步和异步)\n    - [6、Java中的NIO，BIO，AIO分别是什么](#6java中的niobioaio分别是什么)\n        - [BIO](#bio)\n        - [NIO](#nio)\n        - [AIO (NIO.2)](#aio-nio2)\n        - [总结](#总结)\n    - [7、BIO，NIO，AIO区别](#7bionioaio区别)\n    - [8、Stock通信的伪代码实现流程](#8stock通信的伪代码实现流程)\n    - [9、网络操作](#9网络操作)\n        - [InetAddress](#inetaddress)\n        - [URL](#url)\n        - [Sockets](#sockets)\n        - [Datagram](#datagram)\n        - [什么是Socket？](#什么是socket)\n\n<!-- /TOC -->\n\n# Java IO\n\nJava 的 I/O 大概可以分成以下几类：\n\n- 磁盘操作：File\n- 字节操作：InputStream 和 OutputStream\n- 字符操作：Reader 和 Writer\n- 对象操作：Serializable\n- 网络操作：Socket\n- 新的输入/输出：NIO\n\n\n\n## 1、磁盘操作（File）\n\nFile 类可以用于表示文件和目录的信息，但是它不表示文件的内容。\n\n递归地输出一个目录下所有文件：\n\n```java\npublic static void listAllFiles(File dir)\n{\n    if (dir == null || !dir.exists()) {\n        return;\n    }\n    if (dir.isFile()) {\n        System.out.println(dir.getName());\n        return;\n    }\n    for (File file : dir.listFiles()) {\n        listAllFiles(file);\n    }\n}\n```\n\n\n\n## 2、字节操作（*Stream）\n\n使用字节流操作进行文件复制：\n\n```java\npublic static void copyFile(String src, String dist) throws IOException\n{\n    FileInputStream in = new FileInputStream(src);\n    FileOutputStream out = new FileOutputStream(dist);\n    byte[] buffer = new byte[20 * 1024];\n    // read() 最多读取 buffer.length 个字节\n    // 返回的是实际读取的个数\n    // 返回 -1 的时候表示读到 eof，即文件尾\n    while (in.read(buffer, 0, buffer.length) != -1) {\n        out.write(buffer);\n    }\n    in.close();\n    out.close();\n}\n```\n\n\n\n<div align=\"center\"> <img src=\"assets/DP-Decorator-java.io.png\" width=\"600\"/> </div><br>\n\n\n\nJava I/O 使用了**装饰者模式**来实现。以 InputStream 为例，InputStream 是抽象组件，FileInputStream 是 InputStream 的子类，属于具体组件，提供了字节流的输入操作。FilterInputStream 属于抽象装饰者，装饰者用于装饰组件，为组件提供额外的功能，例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。\n\n实例化一个具有缓存功能的字节流对象时，只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。\n\n```java\nFileInputStream fileInputStream = new FileInputStream(filePath);\nBufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);\n```\n\nDataInputStream 装饰者提供了对更多数据类型进行输入的操作，比如 int、double 等基本类型。\n\n\n\n## 3、字符操作（*Reader | *Writer）\n\n不管是磁盘还是网络传输，最小的存储单元都是字节，而不是字符。**但是在程序中操作的通常是字符形式的数据，因此需要提供对字符进行操作的方法。**\n\n- InputStreamReader 实现从**字节流**解码成**字符流**；\n- OutputStreamWriter 实现**字符流**编码成为**字节流**。\n\n逐行输出文本文件的内容：\n\n```java\npublic static void readFileContent(String filePath) throws IOException\n{\n    FileReader fileReader = new FileReader(filePath);\n    BufferedReader bufferedReader = new BufferedReader(fileReader);\n    String line;\n    while ((line = bufferedReader.readLine()) != null) {\n        System.out.println(line);\n    }\n    // 装饰者模式使得 BufferedReader 组合了一个 Reader 对象\n    // 在调用 BufferedReader 的 close() 方法时会去调用 fileReader 的 close() 方法\n    // 因此只要一个 close() 调用即可\n    bufferedReader.close();\n}\n```\n\n**编码**就是把字符转换为字节，而**解码**是把字节重新组合成字符。\n\n如果编码和解码过程使用不同的编码方式那么就出现了乱码。\n\n- GBK 编码中，中文字符占 2 个字节，英文字符占 1 个字节；\n- UTF-8 编码中，中文字符占 3 个字节，英文字符占 1 个字节；\n- UTF-16be 编码中，中文字符和英文字符都占 2 个字节。\n\nUTF-16be 中的 be 指的是 Big Endian，也就是大端。相应地也有 UTF-16le，le 指的是 Little Endian，也就是小端。\n\nJava 使用双字节编码 UTF-16be，这不是指 Java 只支持这一种编码方式，而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位，也就是两个字节，Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。\n\nString 可以看成一个字符序列，可以指定一个编码方式将它转换为字节序列，也可以指定一个编码方式将一个字节序列转换为 String。\n\n```java\nString str1 = \"中文\";\nbyte[] bytes = str1.getBytes(\"UTF-8\");\nString str2 = new String(bytes, \"UTF-8\");\nSystem.out.println(str2);\n```\n\n在调用无参数 getBytes() 方法时，默认的编码方式不是 UTF-16be。双字节编码的好处是可以使用一个 char 存储中文和英文，而将 String 转为 bytes[] 字节数组就不再需要这个好处，因此也就不再需要双字节编码。getBytes() 的默认编码方式与平台有关，一般为 UTF-8。\n\n```java\nbyte[] bytes = str1.getBytes();\n```\n\n\n\n\n\n\n\n## 4、Java序列化，如何实现序列化和反序列化，常见的序列化协议有哪些？ \n\n### Java序列化定义 \n\n（1）Java序列化是指把Java对象转换为字节序列的过程，而Java反序列化是指把字节序列恢复为Java对象的过程；\n\n（2）**序列化**：对象序列化的最主要的用处就是在传递和保存对象的时候，保证对象的完整性和可传递性。序列化是把对象转换成有序字节流，以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了Java对象的状态以及相关的描述信息。序列化机制的核心作用就是**对象状态的保存与重建**。\n\n（3）**反序列化**：客户端从文件中或网络上获得序列化后的对象字节流后，根据字节流中所保存的对象状态及描述信息，通过反序列化重建对象。\n\n（4）本质上讲，序列化就是把实体对象状态按照一定的格式写入到有序字节流，反序列化就是从有序字节流重建对象，恢复对象状态。\n\n### 如何实现序列化和反序列化，底层怎么实现\n\n**1、JDK类库中序列化和反序列化API**\n\n（1）java.io.ObjectOutputStream：表示对象输出流；\n\n它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化，把得到的字节序列写到一个目标输出流中；\n\n（2）java.io.ObjectInputStream：表示对象输入流；\n\n它的readObject()方法源输入流中读取字节序列，再把它们反序列化成为一个对象，并将其返回；\n\n\n\n**2、实现序列化的要求**\n\n只有实现了 Serializable 或 Externalizable 接口的类的对象才能被序列化，否则抛出异常！\n\n\n\n**3、实现Java对象序列化与反序列化的方法**\n\n　　假定一个User类，它的对象需要序列化，可以有如下三种方法：\n\n- 若 User 类仅仅实现了 Serializable 接口，则可以按照以下方式进行序列化和反序列化\n  - ObjectOutputStream 采用默认的序列化方式，对 User 对象的非 transient 的实例变量进行序列化。\n  - ObjcetInputStream 采用默认的反序列化方式，对对 User 对象的非 transient 的实例变量进行反序列化。\n- 若User类仅仅实现了Serializable接口，并且还定义了 `readObject(ObjectInputStream in)` 和`writeObject(ObjectOutputSteam out)`，则采用以下方式进行序列化与反序列化。\n  - ObjectOutputStream 调用 User 对象的 writeObject(ObjectOutputStream out) 的方法进行序列化。 \n  - ObjectInputStream 会调用 User 对象的 readObject(ObjectInputStream in) 的方法进行反序列化。\n- 若User类实现了 Externalnalizable 接口，且 User 类必须实现 `readExternal(ObjectInput in)` 和 `writeExternal(ObjectOutput out)` 方法，则按照以下方式进行序列化与反序列化。\n  - ObjectOutputStream 调用 User 对象的 writeExternal(ObjectOutput out)) 的方法进行序列化。 \n  - ObjectInputStream 会调用User对象的 readExternal(ObjectInput in) 的方法进行反序列化。\n\n\n\n**4、JDK类库中序列化的步骤**\n\n步骤一：创建一个对象输出流，它可以包装一个其它类型的目标输出流，如文件输出流：\n\n```java\nObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(\"D:\\\\object.out\"));\n```\n\n步骤二：通过对象输出流的writeObject()方法写对象：\n\n```java\noos.writeObject(new User(\"xuliugen\", \"123456\", \"male\"));\n```\n\n\n\n**5、JDK类库中反序列化的步骤**\n\n步骤一：创建一个对象输入流，它可以包装一个其它类型输入流，如文件输入流：\n\n```java\nObjectInputStream ois= new ObjectInputStream(new FileInputStream(\"object.out\"));\n```\n\n步骤二：通过对象输出流的readObject()方法读取对象：\n\n```java\nUser user = (User) ois.readObject();\n```\n\n说明：为了正确读取数据，完成反序列化，必须保证向对象输出流写对象的顺序与从对象输入流中读对象的顺序一致。\n\n\n\n**6、序列化和反序列化的示例**\n\n为了更好地理解Java序列化与反序列化，举一个简单的示例如下：\n\n```java\npublic class SerialDemo {\n\n    public static void main(String[] args) throws IOException, ClassNotFoundException {\n        //序列化\n        FileOutputStream fos = new FileOutputStream(\"object.out\");\n        ObjectOutputStream oos = new ObjectOutputStream(fos);\n        User user1 = new User(\"xuliugen\", \"123456\", \"male\");\n        oos.writeObject(user1);\n        oos.flush();\n        oos.close();\n        \n        //反序列化\n        FileInputStream fis = new FileInputStream(\"object.out\");\n        ObjectInputStream ois = new ObjectInputStream(fis);\n        User user2 = (User) ois.readObject();\n        System.out.println(user2.getUserName()+ \" \" + \n            user2.getPassword() + \" \" + user2.getSex());\n        //反序列化的输出结果为：xuliugen 123456 male\n    }\n}\n\npublic class User implements Serializable {\n    private String userName;\n    private String password;\n    private String sex;\n    //全参构造方法、get和set方法省略\n}\n```\n\n\n\n### 相关注意事项\n\n1、序列化时，只对对象的状态进行保存，而不管对象的方法；\n\n2、当一个父类实现序列化，子类自动实现序列化，不需要显式实现 Serializable 接口；\n\n3、当一个对象的实例变量引用其他对象，序列化该对象时也把引用对象进行序列化；\n\n4、并非所有的对象都可以序列化，至于为什么不可以，有很多原因了，比如：\n\n- 安全方面的原因，比如一个对象拥有private，public等field，对于一个要传输的对象，比如写到文件，或者进行RMI传输等等，在序列化进行传输的过程中，这个对象的private等域是不受保护的；\n- 资源分配方面的原因，比如socket，thread类，如果可以序列化，进行传输或者保存，也无法对他们进行重新的资源分配，而且，也是没有必要这样实现；\n\n5、声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态，transient代表对象的临时数据。\n\n6、序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联，该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。为它赋予明确的值。显式地定义serialVersionUID有两种用途：\n\n- 在某些场合，希望类的不同版本对序列化兼容，因此需要确保类的不同版本具有相同的serialVersionUID；\n- 在某些场合，不希望类的不同版本对序列化兼容，因此需要确保类的不同版本具有不同的serialVersionUID。\n\n7、Java有很多基础类已经实现了serializable接口，比如String , Vector等。但是也有一些没有实现serializable接口的；\n\n8、如果一个对象的成员变量是一个对象，那么这个对象的数据成员也会被保存！这是能用序列化解决深拷贝的重要原因；\n\n\n\n**ArrayList 序列化和反序列化的实现** ：ArrayList 中存储数据的数组是用 transient 修饰的，因为这个数组是动态扩展的，并不是所有的空间都被使用，因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法，使得可以只序列化数组中有内容的那部分数据。\n\n```java\nprivate transient Object[] elementData;\n```\n\n\n\n参考资料：\n\n- [序列化和反序列化的底层实现原理是什么？ - CSDN博客](https://blog.csdn.net/xlgen157387/article/details/79840134)\n\n\n\n\n\n### 常见的序列化协议有哪些 \n\n- COM主要用于Windows平台，并没有真正实现跨平台，另外COM的序列化的原理利用了编译器中虚表，使得其学习成本巨大。 \n\n- CORBA是早期比较好的实现了跨平台，跨语言的序列化协议。COBRA的主要问题是参与方过多带来的版本过多，版本之间兼容性较差，以及使用复杂晦涩。 \n\n- XML & SOAP \n\n  - XML是一种常用的序列化和反序列化协议，具有跨机器，跨语言等优点。 \n  - SOAP（Simple Object Access protocol） 是一种被广泛应用的，基于XML为序列化和反序列化协议的结构化消息传递协议。SOAP具有安全、可扩展、跨语言、跨平台并支持多种传输层协议。 \n\n- JSON（JavaScript Object Notation） \n\n  - 这种Associative array格式非常符合工程师对对象的理解。 \n  - 它保持了XML的人眼可读（Human-readable）的优点。 \n  - 相对于XML而言，序列化后的数据更加简洁。  \n  - 它具备javascript的先天性支持，所以被广泛应用于Web browser的应用常景中，是Ajax的事实标准协议。 \n  - 与XML相比，其协议比较简单，解析速度比较快。 \n  - 松散的Associative array使得其具有良好的可扩展性和兼容性。 \n\n- Thrift是Facebook开源提供的一个高性能，轻量级RPC服务框架，其产生正是为了满足当前大数据量、分布式、跨语言、跨平台数据通讯的需求。Thrift在空间开销和解析性能上有了比较大的提升，对于对性能要求比较高的分布式系统，它是一个优秀的RPC解决方案；但是由于Thrift的序列化被嵌入到Thrift框架里面，Thrift框架本身并没有透出序列化和反序列化接口，这导致其很难和其他传输层协议共同使用 \n\n- Protobuf具备了优秀的序列化协议的所需的众多典型特征 \n\n  - 标准的IDL和IDL编译器，这使得其对工程师非常友好。 \n  - 序列化数据非常简洁，紧凑，与XML相比，其序列化之后的数据量约为1/3到1/10。 \n  - 解析速度非常快，比对应的XML快约20-100倍。 \n  - 提供了非常友好的动态库，使用非常简介，反序列化只需要一行代码。由于其解析性能高，序列化后数据量相对少，非常适合应用层对象的持久化场景 \n\n- Avro的产生解决了JSON的冗长和没有IDL的问题，Avro属于Apache Hadoop的一个子项目。 Avro提供两种序列化格式：JSON格式或者Binary格式。Binary格式在空间开销和解析性能方面可以和Protobuf媲美，JSON格式方便测试阶段的调试。适合于高性能的序列化服务。 \n\n- **几种协议的对比** \n\n  - XML序列化（Xstream）无论在性能和简洁性上比较差； \n  - Thrift与Protobuf相比在时空开销方面都有一定的劣势； \n  - Protobuf和Avro在两方面表现都非常优越。 \n\n\n\n## 5、同步和异步\n\n同步IO：\n\n- 读写IO时代码等数据返回后才继续执行后续代码\n- 代码编写简单，CPU执行效率低\n- JDK提供的java.io是同步IO\n\n异步IO：\n\n- 读写IO时仅发出请求，然后立即执行后续代码\n- 代码编写复杂，CPU执行效率高\n- JDK提供的java.nio是异步IO\n\n\n\n\n\n## 6、Java中的NIO，BIO，AIO分别是什么\n\n- **同步阻塞IO（BIO）**：用户进程发起一个IO操作以后，必须等待IO操作的真正完成后，才能继续运行；\n- **同步非阻塞IO（NIO）**：用户进程发起一个IO操作以后，可做其它事情，但用户进程需要经常询问IO操作是否完成，这样造成不必要的CPU资源浪费；\n- **异步非阻塞IO（AIO）**：用户进程发起一个IO操作然后，立即返回，等IO操作真正的完成以后，应用程序会得到IO操作完成的通知。类比Future模式。\n\n\n\n- 先来个例子理解一下概念，以银行取款为例：\n  - **同步 ：** 自己亲自出马持银行卡到银行取钱（使用同步IO时，Java自己处理IO读写）。\n  - **异步 ：** 委托一小弟拿银行卡到银行取钱，然后给你（使用异步IO时，Java将IO读写委托给OS处理，需要将数据缓冲区地址和大小传给OS(银行卡和密码)，OS需要支持异步IO操作API）。\n  - **阻塞 ：** ATM排队取款，你只能等待（使用阻塞IO时，Java调用会一直阻塞到读写完成才返回）。\n  - **非阻塞 ：** 柜台取款，取个号，然后坐在椅子上做其它事，等号广播会通知你办理，没到号你就不能去，你可以不断问大堂经理排到了没有，大堂经理如果说还没到你就不能去（使用非阻塞IO时，如果不能读写Java调用会马上返回，当IO事件分发器会通知可读写时再继续进行读写，不断循环直到读写完成）。\n\n<div align=\"center\"> <img src=\"assets/java-io.png\" width=\"\"/></div><br/>\n\n\n\n### BIO \n\n**定义：**BIO 全称Block-IO 是一种**阻塞同步**的通信模式。我们常说的Stock IO 一般指的是BIO。是一个比较传统的通信方式，**模式简单**，**使用方便**。但**并发处理能力低**，**通信耗时**，**依赖网速**。\n\n**BIO 设计原理：**\n\n服务器通过一个 Acceptor 线程负责监听客户端请求和为每个客户端创建一个新的线程进行链路处理。典型的一请求一应答模式。若客户端数量增多，频繁地创建和销毁线程会给服务器打开很大的压力。后改良为用线程池的方式代替新增线程，被称为伪异步IO。\n\n服务器提供IP地址和监听的端口，客户端通过TCP的三次握手与服务器连接，连接成功后，双放才能通过套接字(Stock)通信。\n\n**小结：**\n\nBIO模型中通过 **Socket** 和 **ServerSocket** 完成套接字通道的实现。阻塞，同步，建立连接耗时。\n\n\n\n<div align=\"center\"> <img src=\"assets/java-bio2.png\" width=\"\"/></div><br/>\n\n\n\n为了改进这种一连接一线程的模型，我们可以使用线程池来管理这些线程（需要了解更多请参考前面提供的文章），实现1个或多个线程处理N个客户端的模型（但是底层还是使用的同步阻塞I/O），通常被称为“**伪异步I/O模型**“。\n\n<div align=\"center\"> <img src=\"assets/java-bio-threadpool.png\" width=\"\"/></div><br/>\n\n实现很简单，我们只需要将新建线程的地方，交给线程池管理即可。\n\n我们知道，如果使用 CachedThreadPool 线程池（不限制线程数量，如果不清楚请参考文首提供的文章），其实除了能自动帮我们管理线程（复用），看起来也就像是1:1的客户端：线程数模型，而使用 FixedThreadPool 我们就有效的控制了线程的最大数量，保证了系统有限的资源的控制，实现了N:M的伪异步 I/O 模型。\n\n但是，正因为限制了线程数量，如果发生大量并发请求，超过最大数量的线程就只能等待，直到线程池中的有空闲的线程可以被复用。而对 Socket 的输入流就行读取时，会一直阻塞，直到发生：\n\n- 有数据可读\n- 可用数据以及读取完毕\n- 发生空指针或 I/O 异常\n\n所以在读取数据较慢时（比如数据量大、网络传输慢等），大量并发的情况下，其他接入的消息，只能一直等待，这就是最大的弊端。\n\n*而后面即将介绍的NIO，就能解决这个难题。*\n\n\n\n### NIO\n\nNIO（官方：New IO），也叫Non-Block IO 是一种**同步非阻塞**的通信模式。\n\n**NIO 设计原理：**\n\nNIO相对于BIO来说一大进步。客户端和服务器之间通过Channel通信。NIO可以在Channel进行读写操作。这些Channel都会被注册在Selector多路复用器上。Selector通过一个线程不停的轮询这些Channel。找出已经准备就绪的Channel执行IO操作。\nNIO 通过一个线程轮询，实现千万个客户端的请求，这就是非阻塞NIO的特点。\n\n1）**缓冲区Buffer**：它是NIO与BIO的一个重要区别。BIO是将数据直接写入或读取到Stream对象中。而NIO的数据操作都是在缓冲区中进行的。缓冲区实际上是一个数组。Buffer最常见的类型是ByteBuffer，另外还有CharBuffer，ShortBuffer，IntBuffer，LongBuffer，FloatBuffer，DoubleBuffer。\n\n2）**通道Channel**：和流不同，通道是双向的。NIO可以通过Channel进行数据的读，写和同时读写操作。通道分为两大类：一类是网络读写（SelectableChannel），一类是用于文件操作（FileChannel），我们使用的SocketChannel和ServerSocketChannel都是SelectableChannel的子类。\n\n3）**多路复用器Selector**：NIO编程的基础。多路复用器提供选择已经就绪的任务的能力。就是Selector会不断地轮询注册在其上的通道（Channel），如果某个通道处于就绪状态，会被Selector轮询出来，然后通过SelectionKey可以取得就绪的Channel集合，从而进行后续的IO操作。服务器端只要提供一个线程负责Selector的轮询，就可以接入成千上万个客户端，这就是JDK NIO库的巨大进步。\n\n小结：**NIO模型中通过SocketChannel和ServerSocketChannel完成套接字通道的实现。非阻塞/阻塞，同步，避免TCP建立连接使用三次握手带来的开销。**\n\n![](assets/java-nio.png)\n\n\n\n### AIO (NIO.2)\n\n- 异步非阻塞，服务器实现模式为一个有效请求一个线程，客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理. \n- AIO方式使用于连接数目多且连接比较长（重操作）的架构，比如**相册服务器**，充分调用OS参与并发操作，编程比较复杂，JDK7开始支持。 \n\nAIO 并没有采用NIO的多路复用器，而是使用异步通道的概念。其read，write方法的返回类型都是Future对象。而Future模型是异步的，其核心思想是：去主函数等待时间。\n\n小结：**AIO模型中通过AsynchronousSocketChannel和AsynchronousServerSocketChannel完成套接字通道的实现。非阻塞，异步**。\n\n\n\n### 总结\n\n1. BIO模型中通过**Socket**和**ServerSocket**完成套接字通道实现。阻塞，同步，连接耗时。\n2. NIO模型中通过**SocketChannel**和**ServerSocketChannel**完成套接字通道实现。非阻塞/阻塞，同步，避免TCP建立连接使用三次握手带来的开销。\n3. AIO模型中通过**AsynchronousSocketChannel**和**AsynchronousServerSocketChannel**完成套接字通道实现。非阻塞，异步。\n\n<div align=\"center\"> <img src=\"assets/java-io-compare.png\" width=\"\"/></div><br/>\n\n\n\n**另外，**I/O属于底层操作，需要操作系统支持，并发也需要操作系统的支持，所以性能方面不同操作系统差异会比较明显。 \n\n\n\n参考：\n\n- [Java BIO、NIO、AIO 学习-力量来源于赤诚的爱！-51CTO博客](http://blog.51cto.com/stevex/1284437)\n- [Netty序章之BIO NIO AIO演变 - JavaEE教程 - SegmentFault 思否](https://segmentfault.com/a/1190000012976683)\n- [Java 网络IO编程总结（BIO、NIO、AIO均含完整实例代码） - CSDN博客](https://blog.csdn.net/anxpp/article/details/51512200#t3)\n- [Java IO Tutorial](http://tutorials.jenkov.com/java-io/index.html)\n\n\n\n## 7、BIO，NIO，AIO区别\n\n- **BIO（同步阻塞）**：客户端和服务器连接需要三次握手，使用简单，但吞吐量小\n- **NIO（同步非阻塞）**：客户端与服务器通过Channel连接，采用多路复用器轮询注册的Channel。提高吞吐量和可靠性。\n- **AIO（异步非阻塞）**：NIO的升级版，采用异步通道实现异步通信，其read和write方法均是异步方法。\n\n\n\n\n\n## 8、Stock通信的伪代码实现流程\n\n1. 服务器绑定端口：server = new ServerSocket(PORT)\n2. 服务器阻塞监听：socket = server.accept()\n3. 服务器开启线程：new Thread(Handle handle)\n4. 服务器读写数据：BufferedReader PrintWriter \n5. 客户端绑定IP和PORT：new Socket(IP_ADDRESS, PORT)\n6. 客户端传输接收数据：BufferedReader PrintWriter\n\n\n\n## 9、网络操作\n\nJava 中的网络支持：\n\n- InetAddress：用于表示网络上的硬件资源，即 IP 地址；\n- URL：统一资源定位符；\n- Sockets：使用 TCP 协议实现网络通信；\n- Datagram：使用 UDP 协议实现网络通信。\n\n### InetAddress\n\n没有公有构造函数，只能通过静态方法来创建实例。\n\n```java\nInetAddress.getByName(String host);\nInetAddress.getByAddress(byte[] address);\n```\n\n\n\n### URL\n\n可以直接从 URL 中读取字节流数据。\n\n```java\npublic static void main(String[] args) throws IOException\n{\n    URL url = new URL(\"http://www.baidu.com\");\n    // 字节流\n    InputStream is = url.openStream();\n    // 字符流\n    InputStreamReader isr = new InputStreamReader(is, \"utf-8\");\n    BufferedReader br = new BufferedReader(isr);\n    String line = br.readLine();\n    while (line != null) {\n        System.out.println(line);\n        line = br.readLine();\n    }\n    br.close();\n}\n```\n\n\n\n### Sockets\n\n- ServerSocket：服务器端类\n- Socket：客户端类\n- 服务器和客户端通过 InputStream 和 OutputStream 进行输入输出。\n\n\n\n<div align=\"center\"> <img src=\"https://raw.githubusercontent.com/CyC2018/Interview-Notebook/033676724523021872edb86176e92a87b87acd46/pics/ClienteServidorSockets1521731145260.jpg\" width=\"\"/></div><br/>\n\n\n\n\n\n参考资料：\n\n- [使用TCP/IP的套接字（Socket）进行通信 - alps_01 - 博客园](https://www.cnblogs.com/alps/p/5672757.html)\n\n\n\n### Datagram\n\n- DatagramPacket：数据包类\n- DatagramSocket：通信类\n\n\n\n\n\n### 什么是Socket？\n\n> TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点，这种端点就叫做套接字（socket）或插口。 \n>\n> 套接字用（IP地址：端口号）表示。\n\nSocket是进程通讯的一种方式，即调用这个网络库的一些API函数实现分布在不同主机的相关进程之间的数据交换。 \n\nsocket是网络编程的基础，本文用打电话来类比socket通信中建立TCP连接的过程。\n\n**socket函数**：表示你买了或者借了一部手机。\n**bind函数**：告诉别人你的手机号码，让他们给你打电话。\n**listen函数**：打开手机的铃声，而不是静音，这样有电话时可以立马反应。listen函数的第二个参数，最大连接数，表示最多有几个人可以同时拨打你的号码。不过我们的手机，最多只能有一个人打进来，要不然就提示占线。\n**connect函数**：你的朋友知道了你的号码，通过这个号码来联系你。在他等待你回应的时候，不能做其他事情，所以connect函数是阻塞的。\n**accept函数**：你听到了电话铃声，接电话，accept it！然后“喂”一声，你的朋友听到你的回应，知道电话已经打进去了。至此，一个TCP连接建立了。\n**read/write函数**：连接建立后，TCP的两端可以互相收发消息，这时候的连接是全双工的。对应打电话中的电话煲。\n**close函数**：通话完毕，一方说“我挂了”，另一方回应\"你挂吧\"，然后将连接终止。实际的close(sockfd)有些不同，它不止是终止连接，还把手机也归还，不在占有这部手机，就当是公用电话吧。\n\n注意到，上述连接是阻塞的，你一次只能响应一个用户的连接请求，但在实际网络编程中，一个服务器服务于多个客户，上述方案也就行不通了，怎么办？想一想10086，移动的声讯服务台，也是只有一个号码，它怎么能同时服务那么多人呢？可以这样理解，在你打电话到10086时，总服务台会让一个接线员来为你服务，而它自己却继续监听有没有新的电话接入。在网络编程中，这个过程类似于fork一个子进程，建立实际的通信连接，而主进程继续监听。10086的接线员是有限的，所以当连接的人数达到上线时，它会放首歌给你听，忙等待，直到有新的空闲接线员为止。\n实际网络编程中，处理并发的方式还有select/poll/epoll等。\n\n下面是一个实际的socket通信过程：\n\n<div align=\"center\"> <img src=\"assets/tcpsocket.png\" width=\"\"/></div><br/>\n\n\n\n**Socket的特点**\n\n1. Socket基于TCP链接，数据传输有保障\n2. Socket适用于建立长时间链接\n3. Socket编程通常应用于即时通讯\n\n\n\n## 资料引用\n\n- 部分参考自：[CyC2018/CS-Notes](CyC2018/CS-Notes)"
  },
  {
    "path": "notes/JavaArchitecture/05-Java虚拟机.md",
    "content": "<!-- TOC -->\n\n- [前言](#前言)\n- [核心知识](#核心知识)\n    - [JVM体系结构](#jvm体系结构)\n        - [JVM各个模块简介](#jvm各个模块简介)\n        - [JVM是如何工作的？](#jvm是如何工作的)\n    - [1. 运行时数据区域](#1-运行时数据区域)\n        - [1. 程序计数器（线程私有）](#1-程序计数器线程私有)\n        - [2. 虚拟机栈（线程私有）](#2-虚拟机栈线程私有)\n        - [3. 本地方法栈（线程私有）](#3-本地方法栈线程私有)\n        - [4. 堆](#4-堆)\n            - [新生代 （Young Generation）](#新生代-young-generation)\n            - [老年代 （Old Generation）](#老年代-old-generation)\n            - [永久代 （Permanent Generation）](#永久代-permanent-generation)\n        - [5. 方法区](#5-方法区)\n        - [6. 运行时常量池](#6-运行时常量池)\n        - [7. 直接内存](#7-直接内存)\n    - [2. 判断一个对象是否可被回收](#2-判断一个对象是否可被回收)\n        - [1. 引用计数算法](#1-引用计数算法)\n        - [2. 可达性分析算法](#2-可达性分析算法)\n            - [★ GC用的引用可达性分析算法中，哪些对象可作为GC Roots对象？](#★-gc用的引用可达性分析算法中哪些对象可作为gc-roots对象)\n        - [3. 引用类型](#3-引用类型)\n            - [1. 强引用（Strong Reference）](#1-强引用strong-reference)\n            - [2. 软引用（Soft Reference）](#2-软引用soft-reference)\n            - [3. 弱引用（Weak Reference）](#3-弱引用weak-reference)\n            - [4. 虚引用（Phantom Reference）](#4-虚引用phantom-reference)\n        - [4. 方法区的回收](#4-方法区的回收)\n        - [5. finalize()](#5-finalize)\n    - [3. 垃圾收集算法（垃圾处理方法）](#3-垃圾收集算法垃圾处理方法)\n        - [1. 标记 - 清除](#1-标记---清除)\n        - [2. 标记 - 整理](#2-标记---整理)\n        - [3. 复制回收](#3-复制回收)\n        - [★ 分代收集](#★-分代收集)\n    - [4. 垃圾收集器](#4-垃圾收集器)\n        - [1. Serial](#1-serial)\n        - [2. ParNew](#2-parnew)\n        - [3. Parallel Scavenge](#3-parallel-scavenge)\n        - [4. Serial Old](#4-serial-old)\n        - [5. Parallel Old](#5-parallel-old)\n        - [6. CMS](#6-cms)\n        - [7. G1](#7-g1)\n        - [8. 比较](#8-比较)\n    - [5. 内存分配与回收策略](#5-内存分配与回收策略)\n        - [1. 什么时候进行Minor GC，Full GC](#1-什么时候进行minor-gcfull-gc)\n        - [2. 内存分配策略](#2-内存分配策略)\n            - [1. 对象优先在 Eden 分配](#1-对象优先在-eden-分配)\n            - [2. 大对象直接进入老年代](#2-大对象直接进入老年代)\n            - [3. 长期存活的对象进入老年代](#3-长期存活的对象进入老年代)\n            - [4. 动态对象年龄判定](#4-动态对象年龄判定)\n            - [5. 空间分配担保](#5-空间分配担保)\n        - [3. Full GC 的触发条件](#3-full-gc-的触发条件)\n            - [1. 调用 System.gc()](#1-调用-systemgc)\n            - [2. 老年代空间不足](#2-老年代空间不足)\n            - [3. 空间分配担保失败](#3-空间分配担保失败)\n            - [4. JDK 1.7 及以前的永久代空间不足](#4-jdk-17-及以前的永久代空间不足)\n            - [5. Concurrent Mode Failure](#5-concurrent-mode-failure)\n    - [6. 类加载机制](#6-类加载机制)\n        - [类的生命周期](#类的生命周期)\n        - [类初始化时机](#类初始化时机)\n            - [1. 主动引用](#1-主动引用)\n            - [2. 被动引用](#2-被动引用)\n        - [类加载过程](#类加载过程)\n            - [1. 加载](#1-加载)\n            - [2. 验证](#2-验证)\n            - [3. 准备](#3-准备)\n            - [4. 解析](#4-解析)\n            - [5. 初始化](#5-初始化)\n        - [类加载器](#类加载器)\n            - [1. 类与类加载器](#1-类与类加载器)\n            - [2. 类加载器分类](#2-类加载器分类)\n            - [3. 双亲委派模型](#3-双亲委派模型)\n    - [7. Student s = new Student(); 在内存中做了哪些事情](#7-student-s--new-student-在内存中做了哪些事情)\n    - [8. Java虚拟机工具](#8-java虚拟机工具)\n        - [（1）jps](#1jps)\n        - [（2）jstat](#2jstat)\n        - [（3）jinfo](#3jinfo)\n        - [（4）jmap](#4jmap)\n        - [（5）jhat](#5jhat)\n        - [（6）jstack](#6jstack)\n        - [（7）jconsole](#7jconsole)\n        - [（8）jvisualvm](#8jvisualvm)\n    - [9. 了解过JVM调优没，基本思路是什么](#9-了解过jvm调优没基本思路是什么)\n    - [10. JVM线程死锁，你该如何判断是因为什么？如果用VisualVM，dump线程信息出来，会有哪些信息](#10-jvm线程死锁你该如何判断是因为什么如果用visualvmdump线程信息出来会有哪些信息)\n    - [11. 什么是内存泄露？用什么工具可以查出内存泄漏](#11-什么是内存泄露用什么工具可以查出内存泄漏)\n    - [* 虚拟机参数](#-虚拟机参数)\n- [附录：参考资料](#附录参考资料)\n- [更新说明](#更新说明)\n\n<!-- /TOC -->\n# 前言\n\n在本文将深入讨论 Java 虚拟机相关核心知识\n\n\n\n参考书籍：\n\n- 《深入理解 Java 虚拟机》周志明，机械工业出版社\n\n\n\n学习课程：\n\n- 【炼数成金】深入 JVM 内核—原理、诊断与优化\n- 【龙果学院】深入理解 Java 虚拟机（ JVM 性能调优+内存模型+虚拟机原理）\n- 【尚学堂】白鹤翔 JVM 虚拟机优化\n\n\n\n# 核心知识\n\n- JVM 基本结构\n  - 类加载器\n  - 执行引擎\n  - 运行时数据区\n  - 本地接口\n\nClass Files -> ClassLoader -> 运行时数据区 -> 执行引擎，本地库接口 -> 本地方法库\n\n\n\n\n\n## JVM体系结构\n\n虚拟机是物理机器的软件实现。Java 的开发遵循 write once run anywhere（“一次编写到处乱跑”）理念，它运行在 VM（虚拟机）上。编译器将 Java 文件编译成 Java.class 文件，之后，将 .class 文件输入到 JVM 中，加载并执行该类文件。下图是 JVM 的体系结构\n\n![img](assets/435918-20180701233830808-854564995.png)\n\n\n\n### JVM各个模块简介\n\n1. 运行时数据区：经过编译生成的字节码文件（class文件），由 class loader（类加载子系统）加载后交给执行引擎执行。在执行引擎执行的过程中产生的数据会存储在一块内存区域。这块内存区域就是运行时区域\n\n2. 程序计数器：用于记录当前线程的正在执行的字节码指令位置。由于虚拟机的多线程是切换线程并分配 cpu 执行时间的方式实现的，不同线程的执行位置都需要记录下来，因此程序计数器是线程私有的\n\n3. 虚拟机栈：虚拟机栈是 Java 方法执行的内存结构，虚拟机会在每个 Java 方法执行时创建一个“栈桢”，用于存储局部变量表，操作数栈，动态链接，方法出口等信息。当方法执行完毕时，该栈桢会从虚拟机栈中出栈。其中局部变量表包含基本数据类型和对象引用\n   - 在 Java 虚拟机规范中，对这个区域规定了两种异常状态：如果线程请求的栈的深度大于虚拟机允许的深度，将抛出 StackOverFlowError 异常（栈溢出），如果虚拟机栈可以动态扩展（现在大部分 Java 虚拟机都可以动态扩展，只不过 Java 虚拟机规范中也允许固定长度的 Java 虚拟机栈），如果扩展时无法申请到足够的内存空间，就会抛出 OutOfmMemoryError 异常（没有足够的内存）\n\n4. 本地方法栈：类似 Java 方法的执行有虚拟机栈，本地方法的执行则对应有本地方法栈\n\n5. 方法区：用于存储已被虚拟机加载的类信息，常量，静态变量，即时编译器编译后的代码等数据。线程共享（看存储的数据就知道了）\n\n6. Java 堆（Heap）：堆的主要作用是存放程序运行过程中创建的对象实例，因为要存放的对象实例有可能会极多，因此也是虚拟机内存管理中最大的一块。并且由于硬件条件有限，所以需要不断回收已“无用”的实例对象来腾出空间给新生成的实例对象；因此 Java 的垃圾回收主要是针对堆进行回收的（还有方法区的常量池），Java 堆很多时候也被称为GC堆（Garbage Collected Heap）。\n\n7. 类加载机制（Class Loader）：类加载子系统是根据一个类的全限定名来加载该类的二进制流到内存中，在JVM 中将形成一份描述 Class 结构的元信息对象（方法区），通过该元信息对象可以获知 Class 的结构信息：如构造函数，属性和方法等，Java 允许用户借由这个 Class 相关的元信息对象间接调用 Class 对象的功能。\n\n\n\n\n\n### JVM是如何工作的？\n\n如上面的体系结构图所示，JVM 分为三个主要的子系统：\n\n1. 类加载器子系统\n2. 运行时数据区\n3. 执行引擎\n\n\n\n**1、类加载器子系统**\n\nJava的动态类加载功能是由类加载器子系统处理的。它负责加载、链接，并且在**运行时**首次引用类的时候初始化类，而不是在编译期间。\n\n**1.1、加载**\n\n这个组件负责加载类。BootStrap类加载器、Extension类加载器和Application类加载器是实现这个功能的三大类加载器。\n\n1. **BootStrap类加载器** —— 负责从classpath加载类，如果没有类存在，将只加载**rt.jar**。这个加载器的优先级最高。\n2. **Extension类加载器** —— 负责加载**扩展文件夹（jre\\lib）**中的类。\n3. **Application类加载器** —— 负责加载**应用级classpath**和环境变量指向的路径下的类。\n\n上述**类加载器**在加载类文件时遵循**委托层次结构算法**。\n\n**1.2、链接**\n\n1. **校验** —— 字节码验证器将校验生成的字节码是否正确，如果校验失败，我们将获得**校验错误信息**。\n2. **准备** —— 对于所有的静态变量，内存将被申请并分配默认值。\n3. **解析** —— 所有**标记的内存引用**从**方法区域**被替换成的**原始引用**。\n\n**1.3、初始化**\n\n这是类加载的最后阶段，所有的静态变量都将被分配原值，静态代码块将被执行。\n\n\n\n运行时数据区\n\n运行时数据区被划分为五个主要部分：\n\n- **方法区** —— 所有**类级数据**都将存储在这里，包括**静态变量**。每一个JVM只有一个方法区，并且它是一个共享资源。\n- **堆区** —— 所有**对象**及其对应的**实例变量**和**数组**等存储在此，每个JVM同样只有一个堆区。由于**方法区**和**堆区**是多线程内存共享，因此存储的数据是非线程安全的。\n- **栈区** —— 每个线程都会创建一个单独的**运行时栈**。在每一次**方法调用**，都会在栈内存中创建一个**栈帧（Stack Frame）**。所有**局部变量**将在栈内存中创建。栈区是线程安全的，因为它不是一个共享资源。栈帧可以被划分为三个实体：\n\n> **局部变量数组** —— 与方法中有多少局部变量有关，相应的值将存储在此处。\n> **操作数栈** —— 如果任何的中间操作需要被执行，**操作数栈**将作为运行时工作区来执行操作。\n> **帧数据** —— 与方法相对应的所有符号存储在此。在任何异常情况下，catch块的信息被保留在帧数据中。\n\n- **PC寄存器** —— 每一个线程都有单独的**PC寄存器**，一旦执行指令，PC寄存器将被下一条指令**更新**，保存当前**执行指令**的地址。\n- **本地方法栈** —— 本地方法栈保存本地方法信息，每一个线程都会创建一个单独的本地方栈。\n\n\n\n**3、执行引擎**\n\n分配到运行时数据区的字节码将被执行引擎执行。执行引擎读取字节码并逐一执行。\n\n- **解释器** —— 解释器能更加快速地解释字节码，但是执行缓慢。解释器的缺点是当多次调用一个方法时，每次都要重新解释。\n- **JIT编译器** —— JIT编译器弥补了解释器的不足。执行引擎使用解释器来转换字节码，当它发现重复的代码时，它将使用JIT编译器来编译整个字节码并转换为本地代码。本地代码将直接被重复的方法所调用，从而提高系统性能。\n- **中间代码生成器** —— 生成中间代码。\n- **代码优化器** —— 负责优化上述生成的中间代码。\n- **目标代码生成器** —— 负责生成机器码或者本地代码。\n- **分析器** —— 一个特殊的组件，负责查找热点代码，比如一个方法是否被调用多次。\n- **垃圾回收器** —— 回收并删除未引用的对象。可以通过调用**System.gc()**来触发垃圾回收，但不能保证它执行。JVM的垃圾回收是回收被创建的对象。\n\n**Java本地接口（JNI）**：**JNI**与**本地方法库**交互，并为执行引擎提供**本地方法库**。\n\n**本地方法库（Native Method Libraries）**：它是执行引擎所需的本地库集合。\n\n\n\n\n\n## 1. 运行时数据区域\n\n<div align=\"center\"> <img src=\"assets/540631a4-6018-40a5-aed7-081e2eeeaeea.png\" width=\"500\"/> </div><br>\n\n\n\n### 1. 程序计数器（线程私有）\n\n记录正在执行的虚拟机字节码指令的地址（如果正在执行的是本地方法则为空）。\n\n- 多个线程竞争时被挂起，程序计数器记录执行到哪里\n- 唯一一个在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域\n\n\n\n### 2. 虚拟机栈（线程私有）\n\n每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息，从调用直至执行完成的过程，就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。 \n\n\n<div align=\"center\"> <img src=\"assets/926c7438-c5e1-4b94-840a-dcb24ff1dafe.png\" width=\"500\"/> </div><br>\n\n* 补充：栈帧中还存在动态链接、出口（返回地址）等。\n\n\n\n可以通过 -Xss 这个虚拟机参数来指定一个程序的 Java 虚拟机栈内存大小：\n\n```\njava -Xss=512M HackTheJava\n```\n\n该区域可能抛出以下异常：\n\n- 当线程请求的栈深度超过最大值，会抛出 StackOverflowError 异常；\n- 栈进行动态扩展时如果无法申请到足够内存，会抛出 OutOfMemoryError 异常。\n\n\n\n栈帧详解：[虚拟机中的运行时栈帧 - kosamino - 博客园](https://www.cnblogs.com/jing99/p/6076102.html)\n\n\n\n\n### 3. 本地方法栈（线程私有）\n\n本地方法一般是用其它语言（C、C++ 或汇编语言等）编写的，并且被编译为基于本机硬件和操作系统的程序，对待这些方法需要特别处理。\n\n本地方法栈与 Java 虚拟机栈类似，它们之间的区别只不过是本地方法栈为本地方法服务。\n\n<div align=\"center\"><img src=\"assets/JNI-Java-Native-Interface.jpg\" width=\"350\"/></div><br/>\n\n\n\n### 4. 堆\n\n所有对象实例都在这里分配内存。\n\n是垃圾收集的主要区域（\"GC 堆\"）。现代的垃圾收集器基本都是采用分代收集算法（因为对象的生命周期不一样），主要思想是针对不同的对象采取不同的垃圾回收算法。虚拟机把 Java 堆分成以下三块：\n\n#### 新生代 （Young Generation）\n\n- 在方法中去 new 一个对象，那这方法调用完毕后，对象就会被回收，这就是一个典型的新生代对象。 \n\n#### 老年代 （Old Generation）\n\n- 在新生代中经历了 N 次垃圾回收后仍然存活的对象就会被放到老年代中。而且大对象直接进入老年代 \n- 当 Survivor 空间不够用时，需要依赖于老年代进行分配担保，所以大对象直接进入老年代 \n\n#### 永久代 （Permanent Generation）\n\n- 即方法区。 \n\n\n\n当一个对象被创建时，它首先进入新生代，之后有可能被转移到老年代中。\n\n新生代存放着大量的生命很短的对象，因此新生代在三个区域中垃圾回收的频率最高。为了更高效地进行垃圾回收，把新生代继续划分成以下三个空间：\n\n- Eden（伊甸园）\n- From Survivor（幸存者）\n- To Survivor\n\n\n\n\n<div align=\"center\"> <img src=\"assets/ppt_img.gif\" width=\"\"/> </div><br>\n\nJava 堆不需要连续内存，并且可以动态增加其内存，增加失败会抛出 OutOfMemoryError 异常。\n\n可以通过 -Xms 和 -Xmx 两个虚拟机参数来指定一个程序的 Java 堆内存大小，第一个参数设置初始值，第二个参数设置最大值。\n\n```java\njava -Xms=1M -Xmx=2M HackTheJava\n```\n\n- 思考：为什么是 8:1:1\n\n\n\n### 5. 方法区\n\n用于存放已被加载的类信息（包含：类版本、字段、方法、接口）、常量 (final)、静态变量 (static)、即时编译器 (JIT) 编译后的代码等数据。因为都是共享的数据，所有要放在方法区。\n\n和 Java 堆一样不需要连续的内存，并且可以动态扩展，动态扩展失败一样会抛出 OutOfMemoryError 异常。\n\n对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载，但是一般比较难实现。\n\nJDK 1.7 之前，HotSpot 虚拟机把它当成永久代来进行垃圾回收，JDK 1.8 之后，取消了永久代，用 metaspace（元数据）区替代。\n\n\n\n### 6. 运行时常量池\n\n运行时常量池是方法区的一部分。\n\nClass 文件中的常量池（编译器生成的各种字面量和符号引用）会在类加载后被放入这个区域。\n\n除了在编译期生成的常量，还允许动态生成，例如 String 类的 intern()。\n\n```java\n在TLAB空间中存在\n\n// 字节码常量\nString s1 = \"123\";\nString s2 = \"123\";\nSystem.out.println(s1 == s1);  //  true\n```\n\n\n\n### 7. 直接内存\n\n在 JDK 1.4 中新加入了 NIO 类，它可以使用 Native 函数库直接分配堆外内存，然后通过一个存储在 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能，因为避免了在 Java 堆和 Native 堆中来回复制数据。\n\n\n\n<div align=\"center\"><img src=\"assets/running-jvm.png\" width=\"\"/></div><br/>\n\n\n\n## 2. 判断一个对象是否可被回收\n\n**程序计数器、虚拟机栈和本地方法栈**这三个区域属于线程私有的，只存在于线程的生命周期内，线程结束之后也会消失，因此不需要对这三个区域进行垃圾回收。垃圾回收主要是针对 **Java 堆和方法区**进行。\n\n### 1. 引用计数算法\n\n**描述**：给对象中添加一个引用计数器每当有一个地方引用它时，计数器就加1；当引用失效时，计数器值就减1；任何时刻计数器为0的对象就是不可能在被使用的。\n\n**缺陷**：很难解决对象间相互循环引用的问题\n\n\n\n### 2. 可达性分析算法\n\n通过 GC Roots 作为起始点进行搜索，能够到达到的对象都是存活的，不可达的对象可被回收。\n\n<div align=\"center\"> <img src=\"assets/root-tracing.png\" width=\"650\"/> </div><br>\n\n\n\n####  ★ GC用的引用可达性分析算法中，哪些对象可作为GC Roots对象？\n\n- 虚拟机栈（栈帧中的本地变量表）中引用的对象。\n- 方法区中静态属性引用的对象。\n- 方法区中常量引用的对象。\n- 本地方法栈中 JNI （即一般说的 Native 方法）引用的对象。\n\n\n\n### 3. 引用类型\n\n无论是通过引用计算算法判断对象的引用数量，还是通过可达性分析算法判断对象是否可达，判定对象是否可被回收都与引用有关。\n\n在 JDK 1.2 之后，Java 对引用的概念进行了扩充，将引用分为 强引用（Strong Reference）、软引用（Soft Reference）、弱引用（Weak Reference）、虚引用（Phantom Reference）4种，这4种引用强度依次逐渐减弱。 \n\n#### 1. 强引用（Strong Reference）\n\n被强引用关联的对象不会被回收。\n\n使用 new 一个新对象的方式来创建强引用。\n\n```java\nObject obj = new Object();\n```\n\n#### 2. 软引用（Soft Reference）\n\n被软引用关联的对象只有在内存不够的情况下才会被回收。\n\n使用 SoftReference 类来创建软引用。\n\n```java\nObject obj = new Object();\nSoftReference<Object> sf = new SoftReference<Object>(obj);\nobj = null;  // 使对象只被软引用关联\n```\n\n#### 3. 弱引用（Weak Reference）\n\n被弱引用关联的对象一定会被回收，也就是说它只能存活到下一次垃圾回收发生之前。\n\n使用 WeakReference 类来实现弱引用。\n\n```java\nObject obj = new Object();\nWeakReference<Object> wf = new WeakReference<Object>(obj);\nobj = null;\n```\n\n#### 4. 虚引用（Phantom Reference）\n\n又称为幽灵引用或者幻影引用。一个对象是否有虚引用的存在，完全不会对其生存时间构成影响，也无法通过虚引用取得一个对象。\n\n为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收时收到一个系统通知。\n\n使用 PhantomReference 来实现虚引用。\n\n```java\nObject obj = new Object();\nPhantomReference<Object> pf = new PhantomReference<Object>(obj);\nobj = null;\n```\n\n\n\n### 4. 方法区的回收\n\nJava虚拟机规范中确实说过可以**不要求虚拟机在方法区实现垃圾收集**，而且在方法区中进行垃圾收集的 “性价比” 一般比较低：在堆中，尤其在新生代中，常规的应用一次垃圾收集一般可以回收 70% ~ 95%的空间，而永久代的垃圾收集效率远低于此。\n\n永久代的垃圾收集主要回收两部分：**废弃常量** 和 **无用的类**。\n\n- 回收废弃常量与回收 Java 堆中的对象非常类似。\n- 要判定一个类是否是 “无用的类” 的条件相对苛刻许多。类需要同时满足下面3个条件才能算 “无用的类”\n  - 该类的所有实例都已经被回收。\n  - 加载该类的 `ClassLoader` 已经被回收。\n  - 该类对应的 `java.lang.Class` 对象没有在任何地方被引用，无法在任何地方通过反射访问该类的方法。\n\n在大量使用反射、动态代理、GGLib 等 ByteCode 框架、动态生成 Jsp 以及 OSGI 这类频繁自定义 ClassLoader 的场景都需要虚拟机具备类卸载的功能，以保证永久代不会溢出。     \n\n\n\n### 5. finalize()\n\nfinalize() 类似 C++ 的析构函数，用来做关闭外部资源等工作。但是 try-finally 等方式可以做的更好，并且该方法运行代价高昂，不确定性大，无法保证各个对象的调用顺序，因此最好不要使用。\n\n当一个对象可被回收时，如果需要执行该对象的 finalize() 方法，那么就有可能通过在该方法中让对象重新被引用，从而实现自救。自救只能进行一次，如果回收的对象之前调用了 finalize() 方法自救，后面回收时不会调用 finalize() 方法。\n\n\n\n## 3. 垃圾收集算法（垃圾处理方法）\n\n### 1. 标记 - 清除\n\n<div align=\"center\"> <img src=\"assets/a4248c4b-6c1d-4fb8-a557-86da92d3a294.jpg\" width=\"\"/> </div><br> \n\n\n首先标记出所有需要回收的对象，在标记完成后统一回收所有标记的对象。\n\n **不足：**\n\n- **效率问题**：标记和清除的效率都不高\n- **空间问题**：标记清除之后会产生大量不连续的内存碎片，导致以后需要分配较大对象时，无法找到足够的连续内存而不得不提前触发另外一次垃圾收集。\n\n\n\n### 2. 标记 - 整理\n\n<div align=\"center\"> <img src=\"assets/902b83ab-8054-4bd2-898f-9a4a0fe52830.jpg\" width=\"\"/> </div><br>\n\n\n标记过程仍然与\"标记-清除\"算法一样，但后续步骤不是直接对可回收对象进行清理，而是让所有存活的对象都向一端移动，然后直接清除掉端边界以外的内存。\n\n\n\n### 3. 复制回收\n\n\n<div align=\"center\"> <img src=\"assets/e6b733ad-606d-4028-b3e8-83c3a73a3797.jpg\" width=\"\"/> </div><br>\n\n将内存划分为大小相等的两块，每次只使用其中一块，当这一块内存用完了就将还存活的对象复制到另一块上面，然后再把使用过的内存空间进行一次清理。\n\n主要不足是只使用了内存的一半。\n\n现在的商业虚拟机都**采用这种收集算法来回收新生代**，但是并不是将新生代划分为大小相等的两块，而是分为一块较大的 Eden 空间和两块较小的 Survivor 空间，每次使用 Eden 空间和其中一块 Survivor。在回收时，将 Eden 和 Survivor 中还存活着的对象一次性复制到另一块 Survivor 空间上，最后清理 Eden 和使用过的那一块 Survivor。\n\nHotSpot 虚拟机的 Eden 和 Survivor 的大小比例默认为 8:1，保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活，那么一块 Survivor 空间就不够用了，此时需要依赖于老年代进行分配担保，也就是借用老年代的空间存储放不下的对象。\n\n\n\n### ★ 分代收集\n\n现在的商业虚拟机采用分代收集算法，它根据对象存活周期将内存划分为几块，不同块采用适当的收集算法。\n\n一般将堆分为新生代和老年代。\n\n- 新生代使用：**复制回收** 算法\n- 老年代使用：**标记 - 清除** 或者 **标记 - 整理** 算法\n\n\n\n## 4. 垃圾收集器\n\n<div align=\"center\"> <img src=\"assets/c625baa0-dde6-449e-93df-c3a67f2f430f.jpg\" width=\"\"/> </div><br>\n\n\n\n以上是 HotSpot 虚拟机中的 7 个垃圾收集器，连线表示垃圾收集器可以配合使用。\n\n- 单线程与多线程：单线程指的是垃圾收集器只使用一个线程进行收集，而多线程使用多个线程；\n- 串行与并行：串行指的是垃圾收集器与用户程序交替执行，这意味着在执行垃圾收集的时候需要停顿用户程序；并形指的是垃圾收集器和用户程序同时执行。除了 CMS 和 G1 之外，其它垃圾收集器都是以串行的方式执行。\n\n\n\n### 1. Serial\n\n![](assets/serial.png)\n\nSerial 翻译为串行，也就是说它以串行的方式执行。\n\n它是单线程的收集器，只会使用一个线程进行垃圾收集工作。\n\n它的优点是简单高效，对于单个 CPU 环境来说，由于没有线程交互的开销，因此拥有最高的单线程收集效率。\n\n它是 Client 模式下的默认新生代收集器，因为在用户的桌面应用场景下，分配给虚拟机管理的内存一般来说不会很大。Serial 收集器收集几十兆甚至一两百兆的新生代停顿时间可以控制在一百多毫秒以内，只要不是太频繁，这点停顿是可以接受的。\n\n\n\n### 2. ParNew\n\n![](assets/parNew.png)\n\n它是 Serial 收集器的多线程版本。\n\n是 Server 模式下的虚拟机首选新生代收集器，除了性能原因外，主要是因为除了 Serial 收集器，只有它能与 CMS 收集器配合工作。\n\n在JDK1.5 时期，HotSpot 推出了 CMS 收集器（Concurrent Mark Sweep），它是 HotSpot 虚拟机中第一款真正意义上的**并发收集器**。不幸的是，CMS 作为老年代的收集器，却无法与 JDK1.4.0 中已经存在的新生代收集器 Parallel Scavenge 配合工作，**所以在 JDK1.5中使用 CMS 来收集老年代的时候，新生代只能选择 ParNew 或者 Serial 收集器中的一个**。 \n\n默认开启的线程数量与 CPU 数量相同，可以使用 -XX:ParallelGCThreads 参数来设置线程数。\n\n\n\n> *Parallel Scavenge 收集器以及后面提到的 G1 收集器都没有使用传统的 GC 收集器代码框架，而另外独立实现，其余集中收集器则共用了部分的框架代码。* \n\n\n\n### 3. Parallel Scavenge\n\n![](assets/parallel-scavenge.png)\n\n\n\nParallel Scavenge 收集器是一个新生代收集器，它也是使用复制算法的收集器，又是并行的多线程收集器。\n\n **与 ParNew 的不同之处：**\n\n其它收集器关注点是尽可能缩短垃圾收集时用户线程的停顿时间（响应时间），而它的目标是达到一个可控制的吞吐量，它被称为 **吞吐量优先收集器**。\n\n> 吞吐量指 CPU 用于运行用户代码的时间占总时间的比值\n>\n> 吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)\n\n停顿时间越短就越适合需要与用户交互的程序，良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间，尽快完成程序的运算任务，主要适合在后台运算而不需要太多交互的任务。\n\n缩短停顿时间是以牺牲吞吐量和新生代空间来换取的：新生代空间变小，垃圾回收变得频繁，导致吞吐量下降。\n\n可以通过一个开关参数打卡 GC 自适应的调节策略（GC Ergonomics），就不需要手工指定新生代的大小（-Xmn）、Eden 和 Survivor 区的比例、晋升老年代对象年龄等细节参数了。虚拟机会根据当前系统运行情况收集性能监控信息，动态调整这些参数，以提供最合适的停顿时间或最大的吞吐量，这种调节方式称为**GC自适应的调节策略（GC Ergonomiscs）** 。\n\n\n\nParallel Scavenge 收集器气提供了两个参数用于**精确控制吞吐量**\n\n- **最大垃圾收集停顿时间：** -XX:MaxGCPauseMills\n- **吞吐量大小：**-XX:GCTimeRatio\n\n**MaxGCPauseMills** 参数允许的值是一个大于0的毫秒数，收集器将尽可能地保证内存回收所花费的时间不超过设定值。但 GC 的停顿时间缩短是以牺牲吞吐量和新生代空间来换取的。停顿时间下降，但吞吐量也降下来了。\n\n**GCTimeRatio** 参数的值是一个大于0且小于100的整数，也就是垃圾收集时间占总时间的比例，相当于吞吐量的倒数。区间 1/(1+99) ~ 1/(1+1)，即 1% ~ 50%。\n\n由于与吞吐量关系密切，Parallel Scavenge 收集器也经常称为 “吞吐量优先“ 收集器。\n\n**-XX:+UserAdaptiveSizePolicy**： GC 自适应调节策略（GC Ergonomics），打开参数后，就不需要手工指定新生代的大小（-Xmn）、Eden 与 Survivor 区的比例（-XX:SurvivorRatio）、晋升老年代对象的年龄（-XX:PretenureSizeThreshold）等细节参数了。\n\n\n\n\n\n### 4. Serial Old\n\n![](assets/serial-old.png)\n\nSerial Old 是 Serial 收集器的老年代版本，它同样是一个单线程收集器，使用 ”标记-整理“ 算法。\n\n这个收集器的主要意义也是在于给 Client 模式下的虚拟机使用。如果在 Server 模式下，那么它主要还有两大用途：\n\n- 在 JDK 1.5 以及之前版本（Parallel Old 诞生以前）中与 Parallel Scavenge 收集器搭配使用。\n\n- 作为 CMS 收集器的后备预案，在并发收集发生 Concurrent Mode Failure 时使用。\n\n\n\n\n### 5. Parallel Old\n\n![](assets/parallel-old.png)\n\nParallel Old 是 Parallel Scavenge 收集器的老年代版本，使用多线程和 ”标记-整理“ 算法。\n\n在注重吞吐量以及 CPU 资源敏感的场合，都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。\n\n\n\n### 6. CMS\n\n![](assets/cms.png)\n\nCMS（Concurrent Mark Sweep），Mark Sweep 指的是 **标记 - 清除** 算法。CMS 是一款优秀的收集器，主要优点：并发收集、低停顿，Sun公司也称之为**并发低停顿收集器**（Concurrent Low Pause Collection）。\n\n特点：并发收集、低停顿。\n\n分为以下四个流程：\n\n- 初始标记：仅仅只是标记一下 GC Roots 能直接关联到的对象，速度很快，需要停顿。\n- **并发标记**：进行 GC Roots Tracing 的过程，它在整个回收过程中耗时最长，不需要停顿。\n- 重新标记：为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录，需要停顿。\n- **并发清除**：不需要停顿。\n\n在整个过程中耗时最长的**并发标记**和**并发清除**过程中，收集器线程都可以与用户线程一起工作，不需要进行停顿。\n\n具有以下缺点：\n\n- 吞吐量低：低停顿时间是以牺牲吞吐量为代价的，导致 CPU 利用率不够高。\n- 无法处理浮动垃圾，可能出现 Concurrent Mode Failure。浮动垃圾是指并发清除阶段由于用户线程继续运行而产生的垃圾，这部分垃圾只能到下一次 GC 时才能进行回收。由于浮动垃圾的存在，因此需要预留出一部分内存，意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。如果预留的内存不够存放浮动垃圾，就会出现 Concurrent Mode Failure，这时虚拟机将临时启用 Serial Old 来替代 CMS。\n- 标记 - 清除算法导致的空间碎片，往往出现老年代空间剩余，但无法找到足够大连续空间来分配当前对象，不得不提前触发一次 Full GC。\n\n  - CMS 提供了一个开关参数 **-XX:+UseCMSCompactAtFullCollection**（默认开启），用于在 CMS 收集器顶不住要进行 Full GC 时开启内存碎片的合并整理过程，内存整理的过程是无法并发的。\n  - 参数 **-XX:CMSFullGCsBeforeCompaction** 用于设置执行多少次不压缩的 Full GC后，跟着来以此带压缩的，（默认值为0）\n\n\n\n### 7. G1\n\nG1的第一篇paper（附录1）发表于 2004 年，在 2012 年才在 jdk1.7u4 中可用。oracle 官方计划在 jdk9 中将 G1 变成默认的垃圾收集器，以替代 CMS。\n\n- 为何 oracle 要极力推荐 G1 呢，G1 有哪些优点？ \n  - 首先，G1的设计原则就是简单可行的性能调优\n  - 其次，G1将新生代，老年代的物理空间划分取消了**。**\n\nG1（Garbage-First），它是一款面向服务端应用的垃圾收集器，在多 CPU 和大内存的场景下有很好的性能。HotSpot 开发团队赋予它的使命是未来可以替换掉 CMS 收集器。\n\n堆被分为新生代和老年代，其它收集器进行收集的范围都是整个新生代或者老年代，而 G1 可以直接对新生代和老年代一起回收。\n\n\n<div align=\"center\"> <img src=\"assets/3d37bfb7e94c81bd23c7fda6b7d87d59-1534559067339.png\" width=\"400\"/> </div><br>\n\nG1 把堆划分成多个大小相等的独立区域（Region），新生代和老年代不再物理隔离。\n\n\n\n<div align=\"center\"> <img src=\"assets/02d155395a44f40be1b7e9f634939cb0-1534559721068.png\" width=\"600\"/> </div><br>\n\n通过引入 Region 的概念，从而将原来的一整块内存空间划分成多个的小空间，使得每个小空间可以单独进行垃圾回收。这种划分方法带来了很大的灵活性，使得可预测的停顿时间模型成为可能。通过记录每个 Region 垃圾回收时间以及回收所获得的空间（这两个值是通过过去回收的经验获得），并维护一个优先列表，每次根据允许的收集时间，优先回收价值最大的 Region。\n\n每个 Region 都有一个 Remembered Set，用来记录该 Region 对象的引用对象所在的 Region。通过使用 Remembered Set，在做可达性分析的时候就可以避免全堆扫描。\n\n<div align=\"center\"> <img src=\"assets/f99ee771-c56f-47fb-9148-c0036695b5fe.jpg\" width=\"\"/> </div><br>\n\n \n\n如果不计算维护 Remembered Set 的操作，G1 收集器的运作大致可划分为以下几个步骤：\n\n- 初始标记\n- 并发标记\n- 最终标记：为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录，虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面，最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程，但是可并行执行。\n- 筛选回收：首先对各个 Region 中的回收价值和成本进行排序，根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行，但是因为只回收一部分 Region，时间是用户可控制的，而且停顿用户线程将大幅度提高收集效率。\n\n具备如下特点：\n\n- 空间整合：整体来看是基于“标记 - 整理”算法实现的收集器，从局部（两个 Region 之间）上来看是基于“复制”算法实现的，这意味着运行期间不会产生内存空间碎片。\n- 可预测的停顿：能让使用者明确指定在一个长度为 M 毫秒的时间片段内，消耗在 GC 上的时间不得超过 N 毫秒。\n\n\n\n详情请参考：\n\n- [【必读】深入理解 Java G1 垃圾收集器 - 文章 - 伯乐在线](http://blog.jobbole.com/109170/)\n- [Java Hotspot G1 GC的一些关键技术 - 美团技术](https://tech.meituan.com/g1.html)\n  \n\n### 8. 比较\n\n| 收集器                | 单线程/并行 | 串行/并发 | 新生代/老年代   | 收集算法             | 目标         | 适用场景                                      |\n| --------------------- | ----------- | --------- | --------------- | -------------------- | ------------ | --------------------------------------------- |\n| **Serial**            | 单线程      | 串行      | 新生代          | 复制                 | 响应速度优先 | 单 CPU 环境下的 Client 模式                   |\n| **ParNew**            | 并行        | 串行      | 新生代          | 复制算法             | 响应速度优先 | 多 CPU 环境时在 Server 模式下与 CMS 配合      |\n| **Parallel Scavenge** | 并行        | 串行      | 新生代          | 复制算法             | 吞吐量优先   | 在后台运算而不需要太多交互的任务              |\n| **Serial Old**        | 单线程      | 串行      | 老年代          | 标记-整理            | 响应速度优先 | 单 CPU 环境下的 Client 模式、CMS 的后备预案   |\n| **Parallel Old**      | 并行        | 串行      | 老年代          | 标记-整理            | 吞吐量优先   | 在后台运算而不需要太多交互的任务              |\n| **CMS**               | 并行        | 并发      | 老年代          | 标记-清除            | 响应速度优先 | 集中在互联网站或 B/S 系统服务端上的 Java 应用 |\n| **G1**                | 并行        | 并发      | 新生代 + 老年代 | 标记-整理 + 复制算法 | 响应速度优先 | 面向服务端应用，将来替换 CMS                  |\n\n\n\n参考：[Java GC | Pandora](https://hellojz.me/2017/10/10/jvm/gc/)\n\n\n\n\n## 5. 内存分配与回收策略\n\n### 1. 什么时候进行Minor GC，Full GC\n\n- Minor GC：发生在新生代上，因为新生代对象存活时间很短，因此 Minor GC 会频繁执行，执行的速度一般也会比较快。\n  - 新生代中的垃圾收集动作，采用的是复制算法\n  - 对于较大的对象，在 Minor GC 的时候可以直接进入老年代 \n- Full GC：发生在老年代上，老年代对象其存活时间长，因此 Full GC 很少执行，执行速度会比 Minor GC 慢很多。\n  - Full GC 是发生在老年代的垃圾收集动作，采用的是 标记-清除/整理 算法。 \n  - 由于老年代的对象几乎都是在 Survivor 区熬过来的，不会那么容易死掉。因此 Full GC 发生的次数不会有 Minor GC 那么频繁，并且 Time(Full GC)>Time(Minor GC) \n\n\n\n### 2. 内存分配策略\n\n#### 1. 对象优先在 Eden 分配\n\n大多数情况下，对象在新生代 Eden 区分配，当 Eden 区空间不够时，发起 Minor GC。\n\n#### 2. 大对象直接进入老年代\n\n大对象是指需要连续内存空间的对象，最典型的大对象是那种很长的字符串以及数组。\n\n经常出现大对象会提前触发垃圾收集以获取足够的连续空间分配给大对象。\n\n-XX:PretenureSizeThreshold，大于此值的对象直接在老年代分配，避免在 Eden 区和 Survivor 区之间的大量内存复制。\n\n#### 3. 长期存活的对象进入老年代\n\n为对象定义年龄计数器，对象在 Eden 出生并经过 Minor GC 依然存活，将移动到 Survivor 中，年龄就增加 1 岁，增加到一定年龄则移动到老年代中。\n\n-XX:MaxTenuringThreshold 用来定义年龄的阈值。\n\n#### 4. 动态对象年龄判定\n\n虚拟机并不是永远地要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代，如果在 Survivor 中相同年龄所有对象大小的总和大于 Survivor 空间的一半，则年龄大于或等于该年龄的对象可以直接进入老年代，无需等到 MaxTenuringThreshold 中要求的年龄。\n\n#### 5. 空间分配担保\n\n在发生 Minor GC 之前，虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间，如果条件成立的话，那么 Minor GC 可以确认是安全的。\n\n如果不成立的话虚拟机会查看 HandlePromotionFailure 设置值是否允许担保失败，如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小，如果大于，将尝试着进行一次 Minor GC；如果小于，或者 HandlePromotionFailure 设置不允许冒险，那么就要进行一次 Full GC。\n\n\n\n### 3. Full GC 的触发条件\n\n对于 Minor GC，其触发条件非常简单，当 Eden 空间满时，就将触发一次 Minor GC。而 Full GC 则相对复杂，有以下条件：\n\n#### 1. 调用 System.gc()\n\n只是建议虚拟机执行 Full GC，但是虚拟机不一定真正去执行。不建议使用这种方式，而是让虚拟机管理内存。\n\n#### 2. 老年代空间不足\n\n老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等。\n\n为了避免以上原因引起的 Full GC，应当尽量不要创建过大的对象以及数组。除此之外，可以通过 -Xmn 虚拟机参数调大新生代的大小，让对象尽量在新生代被回收掉，不进入老年代。还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄，让对象在新生代多存活一段时间。\n\n#### 3. 空间分配担保失败\n\n使用复制算法的 Minor GC 需要老年代的内存空间作担保，如果担保失败会执行一次 Full GC。具体内容请参考上面的第五小节。\n\n#### 4. JDK 1.7 及以前的永久代空间不足\n\n在 JDK 1.7 及以前，HotSpot 虚拟机中的方法区是用永久代实现的，永久代中存放的为一些 Class 的信息、常量、静态变量等数据。\n\n当系统中要加载的类、反射的类和调用的方法较多时，永久代可能会被占满，在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了，那么虚拟机会抛出 java.lang.OutOfMemoryError。\n\n为避免以上原因引起的 Full GC，可采用的方法为增大永久代空间或转为使用 CMS GC。\n\n#### 5. Concurrent Mode Failure\n\n执行 CMS GC 的过程中同时有对象要放入老年代，而此时老年代空间不足（可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足），便会报 Concurrent Mode Failure 错误，并触发 Full GC。\n\n\n\n## 6. 类加载机制\n\n> 58同城面试，这里好好的再把类加载机制深入一下吧\n\n虚拟机把描述类的数据从 Class 文件加载到内存，并对数据进行校验、转换解析和初始化，最终形成可以被虚拟机直接使用的 Java 类型，这就是虚拟机类加载机制。（类是在运行期间动态加载的）\n\n懒加载：要用的时候再去加载\n\n### 类的生命周期\n\n\n<div align=\"center\"> <img src=\"assets/1534576247083.png\" width=\"650\"/> </div><br>\n\n\n\n包括以下 7 个阶段：\n\n- **加载（Loading）**\n- **验证（Verification）**\n- **准备（Preparation）**\n- **解析（Resolution）**\n- **初始化（Initialization）**\n- 使用（Using）\n- 卸载（Unloading）\n\n其中解析过程在某些情况下可以在初始化阶段之后再开始，这是为了支持 Java 的动态绑定。\n\n这 7 个阶段中的：加载、验证、准备、初始化、卸载的顺序是固定的。但它们并不一定是严格同步串行执行，它们之间可能会有交叉，但总是以 “开始” 的顺序总是按部就班的。至于解析则有可能在初始化之后才开始，这是为了支持 Java 语言的运行时绑定（也称为动态绑定或晚期绑定）。 \n\n### 类初始化时机\n\n#### 1. 主动引用\n\n虚拟机规范中并没有强制约束何时进行加载，但是规范严格规定了有且只有下列五种情况必须对类进行初始化（加载、验证、准备都会随之发生）：\n\n- 遇到 new、getstatic、putstatic、invokestatic  这四条字节码指令时，如果类没有进行过初始化，则必须先触发其初始化。最常见的生成这 4 条指令的场景是：使用 new  关键字实例化对象的时候；读取或设置一个类的静态字段（被 final  修饰、已在编译期把结果放入常量池的静态字段除外）的时候；以及调用一个类的静态方法的时候。\n- 使用 java.lang.reflect 包的方法对类进行反射调用的时候，如果类没有进行初始化，则需要先触发其初始化。\n- 当初始化一个类的时候，如果发现其父类还没有进行过初始化，则需要先触发其父类的初始化。\n- 当虚拟机启动时，用户需要指定一个要执行的主类（包含 main() 方法的那个类），虚拟机会先初始化这个主类；\n- 当使用 JDK 1.7 的动态语言支持时，如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为  REF_getStatic, REF_putStatic, REF_invokeStatic  的方法句柄，并且这个方法句柄所对应的类没有进行过初始化，则需要先触发其初始化；\n\n#### 2. 被动引用\n\n以上 5 种场景中的行为称为对一个类进行主动引用。除此之外，所有引用类的方式都不会触发初始化，称为被动引用。被动引用的常见例子包括：\n\n- 通过子类引用父类的静态字段，不会导致子类初始化。\n\n```java\nSystem.out.println(SubClass.value);  // value 字段在 SuperClass 中定义\n```\n\n- 通过数组定义来引用类，不会触发此类的初始化。该过程会对数组类进行初始化，数组类是一个由虚拟机自动生成的、直接继承自 Object 的子类，其中包含了数组的属性和方法。\n\n```java\nSuperClass[] sca = new SuperClass[10];\n```\n\n- 常量在编译阶段会存入调用类的常量池中，本质上并没有直接引用到定义常量的类，因此不会触发定义常量的类的初始化。\n\n```java\nSystem.out.println(ConstClass.HELLOWORLD);\n```\n\n\n\n### 类加载过程\n\n包含了加载、验证、准备、解析和初始化这 5 个阶段。\n\n#### 1. 加载\n\n加载是类加载的一个阶段，注意不要混淆。\n\n加载过程完成以下三件事：\n\n- 通过一个类的全限定名来获取定义此类的二进制字节流\n- 将这个字节流所代表的静态存储结构转化为方法区的运行时存储结构\n- 在内存中生成一个代表这个类的 Class 对象，作为方法区这个类的各种数据的访问入口\n\n\n\n加载源（其中二进制字节流可以从以下方式中获取）：\n\n- **文件**：从 ZIP 包读取，这很常见，最终成为日后 JAR、EAR、WAR 格式的基础。\n- **网络**：从网络中获取，这种场景最典型的应用是 Applet。\n- **计算生成一个二进制流**：运行时计算生成，这种场景使用得最多得就是动态代理技术，在 java.lang.reflect.Proxy 中，就是用了 ProxyGenerator.generateProxyClass 的代理类的二进制字节流。\n- **由其他文件生成**：由其他文件生成，典型场景是 JSP 应用，即由 JSP 文件生成对应的 Class 类。\n- **数据库**：从数据库读取，这种场景相对少见，例如有些中间件服务器（如 SAP Netweaver）可以选择把程序安装到数据库中来完成程序代码在集群间的分发。 \n\n\n\n#### 2. 验证\n\n目的：确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求，并且不会危害虚拟机自身的安全。\n\n- 文件格式验证：验证字节流是否符合 Class 文件格式的规范，并且能被当前版本的虚拟机处理。\n  - 是否以 0xCAFEBABE 开头，前四个字节为魔数\n  - 版本号是否合理，如：JDK1.8（52.0）、JDK1.7（51.0）\n\n- 元数据验证：对字节码描述的信息进行语义分析，以保证其描述的信息符合 Java 语言规范的要求。\n  - 是否有父类-继\n  - 继承了 final 类？\n  - 非抽象类实现了所有的抽象方法\n- 字节码验证（很复杂）：通过数据流和控制流分析，确保程序语义是合法、符合逻辑的。\n  - 运行检查\n  - 栈数据类型和操作码数据参数吻合\n  - 跳转指令指定到合理的位置\n- 符号引用验证：发生在虚拟机将符号引用转换为直接引用的时候，对类自身以外（常量池中的各种符号引用）的信息进行匹配性校验。\n  - 常量池中描述类是否存在\n  - 访问的方法或字段是否存在且有足够的权限\n\n\n\n#### 3. 准备\n\n准备阶段正式为类变量分配内存并设置变量的初始值。这些变量使用的内存都将在方法区中进行分配。类变量是被 static 修饰的变量，准备阶段为类变量分配内存并设置初始值，使用的是方法区的内存。\n\n实例变量不会在这阶段分配内存，它将会在对象实例化时随着对象一起分配在堆中。注意，实例化不是类加载的一个过程，类加载发生在所有实例化操作之前，并且类加载只进行一次，实例化可以进行多次。\n\n初始值一般为 0 值，例如下面的类变量 value 被初始化为 0 而不是 123，在初始化的 \\<clinit> 中才会被设置为1。\n\n- 默认值：int 0, boolean false, float 0.0, char '0', 抽象数据类型 null\n\n```java\npublic static int value = 123;\n```\n\n- 对于 static final 类型，在准备阶段就会被赋上正确的值\n\n```java\npublic static final int value = 123;\n```\n\n\n\n\n\n#### 4. 解析\n\n解析阶段是虚拟机将常量池的**符号引用替换为直接引用**的过程\n\n- 类或接口的解析\n- 字段解析\n- 类方法解析\n- 接口方法解析\n\n\n\n**什么是符号引用和直接引用？**\n\n- **符号引用**：符号引用是一组符号来描述所引用的目标对象，符号可以是任何形式的字面量，只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关，引用的目标对象并不一定已经加载到内存中。\n- **直接引用**：直接引用可以是直接指向目标对象的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机内存布局实现相关的，同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同，如果有了直接引用，那引用的目标必定已经在内存中存在。\n\n\n\n符号引用就是字符串，这个字符串包含足够的信息，以供实际使用时可以找到相应的位置。你比如说某个方法的符号引用，如：“java/io/PrintStream.println:(Ljava/lang/String;)V”。里面有类的信息，方法名，方法参数等信息。\n\n当第一次运行时，要根据字符串的内容，到该类的方法表中搜索这个方法。运行一次之后，符号引用会被替换为直接引用，下次就不用搜索了。直接引用就是偏移量，通过偏移量虚拟机可以直接在该类的内存区域中找到方法字节码的起始位置。\n\n\n\n\n\n参考资料：\n\n- [【必读】JVM 类加载机制 - 掘金](https://juejin.im/post/5a1d644551882534af25b381)\n\n\n\n#### 5. 初始化\n\n初始化阶段才真正开始执行类中定义的 Java 程序代码。初始化阶段即虚拟机执行类构造器 \\<clinit>() 方法的过程。\n\n\n\n\n\n在准备阶段，类变量已经赋过一次系统要求的初始值，而在初始化阶段，根据程序员通过程序制定的主观计划去初始化类变量和其它资源。\n\n\\<clinit>() 方法具有以下特点：\n\n- 是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的，编译器收集的顺序由语句在源文件中出现的顺序决定。特别注意的是，静态语句块只能访问到定义在它之前的类变量，定义在它之后的类变量只能赋值，不能访问。例如以下代码：\n\n```java\npublic class Test {\n    static {\n        i = 0;                // 给变量赋值可以正常编译通过\n        System.out.print(i);  // 这句编译器会提示“非法向前引用”\n    }\n    static int i = 1;\n}\n```\n\n- 与类的构造函数（或者说实例构造器 \\<init>()）不同，不需要显式的调用父类的构造器。虚拟机会自动保证在子类的  \\<clinit>() 方法运行之前，父类的 \\<clinit>() 方法已经执行结束。因此虚拟机中第一个执行  \\<clinit>() 方法的类肯定为 java.lang.Object。\n- 由于父类的 \\<clinit>() 方法先执行，也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。例如以下代码：\n\n```java\nstatic class Parent {\n    public static int A = 1;\n    static {\n        A = 2;\n    }\n}\n\nstatic class Sub extends Parent {\n    public static int B = A;\n}\n\npublic static void main(String[] args) {\n     System.out.println(Sub.B);  // 2\n}\n```\n\n- \\<clinit>() 方法对于类或接口不是必须的，如果一个类中不包含静态语句块，也没有对类变量的赋值操作，编译器可以不为该类生成 \\<clinit>() 方法。\n- 接口中不可以使用静态语句块，但仍然有类变量初始化的赋值操作，因此接口与类一样都会生成 \\<clinit>()  方法。但接口与类不同的是，执行接口的 \\<clinit>() 方法不需要先执行父接口的 \\<clinit>()  方法。只有当父接口中定义的变量使用时，父接口才会初始化。另外，接口的实现类在初始化时也一样不会执行接口的 \\<clinit>()  方法。\n- 虚拟机会保证一个类的 \\<clinit>()  方法在多线程环境下被正确的加锁和同步，如果多个线程同时初始化一个类，只会有一个线程执行这个类的 \\<clinit>()  方法，其它线程都会阻塞等待，直到活动线程执行 \\<clinit>() 方法完毕。如果在一个类的 \\<clinit>()  方法中有耗时的操作，就可能造成多个线程阻塞，在实际过程中此种阻塞很隐蔽。\n\n\n\n\n\n### 类加载器\n\n虚拟机设计团队把类加载阶段中的 “通过一个类的全限定名来获取描述此类的二进制字节流（即字节码）” 这个动作放到 Java 虚拟机外部去实现，以便让应用程序自己决定如何去获取所需要的类（通过一个类的全限之名获取描述此类的二进制字节流）。实现这个动作的代码模块称为 **“类加载器”**。\n\n \n\n#### 1. 类与类加载器\n\n两个类相等：只有被同一个类加载器加载的类才可能会相等。相同的字节码被不同的类加载器加载的类不相等。\n\n这里的相等，包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果为 true，也包括使用 instanceof 关键字做对象所属关系判定结果为 true。\n\n\n\n#### 2. 类加载器分类\n\n从 Java 虚拟机的角度来讲，只存在以下两种不同的类加载器：\n\n- **启动类加载器**（Bootstrap ClassLoader），这个类加载器用 C++ 实现，是虚拟机自身的一部分；\n- **所有其他类的加载器**，这些类由 Java 实现，独立于虚拟机外部，并且全都继承自抽象类 java.lang.ClassLoader。\n\n\n\n从 Java 开发人员的角度看，类加载器可以划分得更细致一些：\n\n- **启动类加载器**（Bootstrap ClassLoader）\n  - 此类加载器负责将存放在 <JAVA_HOME>\\lib 目录中的，或者被 -Xbootclasspath 参数所指定的路径中的，并且是虚拟机识别的（仅按照文件名识别，如 rt.jar，名字不符合的类库即使放在 lib 目录中也不会被加载）类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接引用，用户在编写自定义类加载器时，如果需要把加载请求委派给启动类加载器，直接使用 null 代替即可。\n- **扩展类加载器**（Extension ClassLoader）\n  - 这个类加载器是由 ExtClassLoader（sun.misc.Launcher$ExtClassLoader）实现的。它负责将 <JAVA_HOME>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中，开发者可以直接使用扩展类加载器。\n- **应用程序类加载器**（Application ClassLoader）\n  - 这个类加载器是由 AppClassLoader（sun.misc.Launcher$AppClassLoader）实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值，因此一般称为系统类加载器。它负责加载用户类路径（ClassPath）上所指定的类库，开发者可以直接使用这个类加载器，如果应用程序中没有自定义过自己的类加载器，一般情况下这个就是程序中默认的类加载器。\n- **自定义类加载器**\n  - 载器步骤：   \n    - 定义一个类，继承 ClassLoader\n    - 重写 loadClass 方法\n    - 实例化 Class 对象\n  - 自定义类加载器的优势\n    - 类加载器是 Java 语言的一项创新，也是 Java  语言流行的重要原因之一，它最初的设计是为了满足 java applet 的需求而开发出来的\n    - 高度的灵活性\n    - 通过自定义类加载器可以实现热部署\n    - 代码加密\n\n\n\n参考资料：\n\n- [深入理解Java虚拟机 - JY的博客 | JY Blog](https://jybeyonding.github.io/2018/03/16/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA-%E7%AC%AC7%E7%AB%A0-%E8%99%9A%E6%8B%9F%E6%9C%BA%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6/)\n\n\n\n#### 3. 双亲委派模型\n\n- 为什么要使用双亲委派模型？\n  - 主要是为了避免重复加载的问题\n\nJVM 如何加载一个类的过程，双亲委派模型中有哪些方法有没有可能父类加载器和子类加载器，加载同一个类？如果加载同一个类，该使用哪一个类？\n\n- **双亲委派机制图**\n\n<div align=\"center\"> <img src=\"assets/d330251551f6de988239494ce2773095.png\" width=\"450\"/> </div><br>\n\n- **双亲委派概念** \n  - 如果一个类加载器收到了类加载的请求，它首先不会自己去尝试加载这个类，而是把这个请求委派给父类加载器去完成，每一个层次的加载器都是如此，因此所有的类加载请求都会传给顶层的启动类加载器，只有当父加载器反馈自己无法完成该加载请求（该加载器的搜索范围中没有找到对应的类）时，子加载器才会尝试自己去加载。 \n- **加载器** \n  - 启动（Bootstrap）类加载器：是用本地代码实现的类装入器，它负责将 <Java_Runtime_Home>/lib下面的类库加载到内存中（比如rt.jar）。由于引导类加载器涉及到虚拟机本地实现细节，开发者无法直接获取到启动类加载器的引用，所以不允许直接通过引用进行操作。 \n  - 标准扩展（Extension）类加载器：是由 Sun 的 ExtClassLoader（sun.misc.Launcher$ExtClassLoader）实现的。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。\n  - 系统（System）类加载器：由 Sun 的  AppClassLoader（sun.misc.Launcher$AppClassLoader）实现的。它负责将系统类路径（CLASSPATH）中指定的类库加载到内存中。开发者可以直接使用系统类加载器。除了以上列举的三种类加载器，还有一种比较特殊的类型 — 线程上下文类加载器。 \n- 如果加载同一个类，该使用哪一个类？ \n\n  - 父类的\n\n\n\n## 7. Student s = new Student(); 在内存中做了哪些事情\n\n1. 加载 Student.class 文件进内存 \n2. 在栈内存为 s 开辟空间 \n3. 在堆内存为 Student 对象开辟空间 \n4. 对 Student 对象的成员变量进行默认初始化 \n5. 对 Student 对象的成员变量进行显示初始化 \n6. 通过构造方法对 Student 对象的成员变量赋值 \n7. Student 对象初始化完毕，把对象地址赋值给 s 变量 \n\n\n\n## 8. Java虚拟机工具\n\nJDK 本身提供了很多方便的 JVM 性能调优监控工具，除了 jps、jstat、jinfo、jmap、jhat、jstack 等小巧的工具，还有集成式的 jvisualvm 和 jconsole。\n\n\n\n### （1）jps\n\n**jps（JVM Process Status Tool，虚拟机进程监控工具）**，这个命令可以列出正在运行的虚拟机进程，并显示虚拟机执行主类名称，以及这些进程的本地虚拟机唯一 ID。这个 ID 被称为本地虚拟机唯一 ID（local virtual Machine Identifier，简写为LVMID）。如果你在 linux 的一台服务器上使用 jps 得到的 LVMID 其实就是和 ps 命令得到的 PID 是一样的。\n\n**语法格式如下：**\n\n```shell\njps [options] [hostid]\n```\n\n如果不指定hostid就默认为当前主机或服务器。\n\n**options参数选项说明如下：**\n\n```shell\n-q 不输出类名、Jar名和传入main方法的参数\n-m 输出传入main方法的参数\n-l 输出main类或Jar的全限名\n-v 输出传入JVM的参数\n```\n\n**使用（查看所有java进程）**\n\n```shell\njps -lv\n```\n\n\n\n示例：\n\n```shell\n[root@chengchi ~]# jps\n24804 Jps\n1862 mango.jar\n\n[root@chengchi ~]# jps -lv\n24787 sun.tools.jps.Jps -Dapplication.home=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.171-8.b10.el7_5.x86_64 -Xms8m\n1862 /home/www/api.chengchijinfu.com/mango_server/target/mango.jar -Dserver.port=8080 -Dspring.profiles.active=prod\n```\n\n\n\n### （2）jstat\n\n**jstat（JVM Statistics Monitoring Tool，虚拟机统计信息监视工具）**，这个命令用于监视虚拟机各种运行状态信息。它可以显示本地或者远程虚拟机进程中的**类装载、内存、垃圾收集、JIT编译等运行数据**，虽然没有GUI图形界面，只是提供了纯文本控制台环境的服务器上，但它是运行期间定位虚拟机性能问题的首选工具。\n\n 语法格式如下：\n\n```shell\njstat [option vmid [interval [s | ms] [count ] ] ]\n```\n\n\n\n例如：需要每 1000 毫秒查询一次进程 16418 垃圾收集状况，一共查询 10 次，那命令如下：\n\n![](assets/tools_stat.png)\n\n参考：[jstat命令详解 - CSDN博客](https://blog.csdn.net/zhaozheng7758/article/details/8623549)\n\n\n\n\n### （3）jinfo\n\n**jinfo （Configuration Info for Java，配置信息工具）** 这个命令可以实时地查看和调整虚拟机各项参数。 \n\n查看2788的MaxPerm大小可以用 \n\n```shell\n[root@Bill-8 bin]# jinfo -flag MaxPermSize 2788\n-XX:MaxPermSize=134217728\n```\n\n\n\n### （4）jmap\n\n**jmap（Memory Map for Java，内存映像工具）**，用于生成堆转存的快照，一般是 heapdump 或者 dump 文件。如果不适用 jmap 命令，可以使用 -XX:+HeapDumpOnOutOfMemoryError 参数，当虚拟机发生内存溢出的时候可以产生快照。或者使用kill -3 pid也可以产生。jmap 的作用并不仅仅是为了获取 dump 文件，它可以查询 finalize 执行队列，java 堆和永久代的详细信息，如空间使用率，当前用的哪种收集器。\n\n**jmap的命令格式：**\n\n```shell\njmap [option] vmid\n```\n\n\n\n```shell\njmap -J-d64 -heap 16418\n```\n\n\n\n### （5）jhat\n\n**jhat（虚拟机堆转储快照分析工具）**，这个工具是用来分析 jmap dump 出来的文件。\n 由于这个工具功能比较简陋，运行起来也比较耗时，所以这个工具不推荐使用，推荐使用MAT。\n\n例如分析dump 出来的 test.bin，命令如下：\n\n```shell\njhat test.bin \n```\n\n它会在本地启动一个web服务，端口是7000，这样直接访问 127.0.0.1:7000就能看到分析结果了。\n\n\n\n### （6）jstack\n\n> 阿里实习面试\n\n**jstack（Java Stack Trace，Java堆栈跟踪工具）**，这个命令用于查看虚拟机当前时刻的线程快照（一般是threaddump 或者 javacore文件）。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合。**生成线程快照的主要目的是：**定位线程出现长时间停顿的原因，入线程间死锁、死循环、请求外部资源导致的长时间等待都是导致线程长时间停顿的常见原因。线程出现停顿的时候通过jstack来查看各个线程的调用堆栈，就可以知道没有响应的线程到底在后台做些什么事情。\n\n命令格式：\n\n```shell\njstack [option] vmid\n```\n\n使用：查看进程2849 的堆栈信息\n\n```shell\n[root@Bill-8 yrd_soft]# jstack 2849\n```\n\n \n\n\n\n### （7）jconsole\n\n> 阿里面经\n\nJConsole 中，您将能够监视 JVM 内存的使用情况、线程堆栈跟踪、已装入的类和 VM 信息以及 CE MBean。\n\njconsole：一个 java GUI 监视工具，可以以图表化的形式显示各种数据。并可通过远程连接监视远程的服务器VM。用 Java 写的 GUI 程序，用来监控 VM，并可监控远程的 VM，非常易用，而且功能非常强。命令行里打 jconsole，选则进程就可以了。\n\n \n\n### （8）jvisualvm\n\njvisualvm 同 jconsole 都是一个基于图形化界面的、可以查看本地及远程的 JAVA GUI 监控工具，Jvisualvm 同 jconsole 的使用方式一样，直接在命令行打入 jvisualvm 即可启动，jvisualvm 界面更美观一些，数据更实时：\n\n\n\n参考资料：\n\n- [JVM性能监控工具](https://www.jianshu.com/p/25e94a1399a0)\n\n\n\n\n\n## 9. 了解过JVM调优没，基本思路是什么\n\n详情转向：[美团技术：从实际案例聊聊Java应用的GC优化](https://tech.meituan.com/jvm_optimize.html)\n\n\n\n\n## 10. JVM线程死锁，你该如何判断是因为什么？如果用VisualVM，dump线程信息出来，会有哪些信息\n\n- 常常需要在隔两分钟后再次收集一次thread dump，如果得到的输出相同，仍然是大量thread都在等待给同一个地址上锁，那么肯定是死锁了。 \n\n\n\n\n\n## 11. 什么是内存泄露？用什么工具可以查出内存泄漏\n\n在 Java 中，内存泄漏就是存在一些被分配的对象，这些对象有下面两个特点\n\n- 这些对象是可达的，即在有向图中，存在通路可以与其相连；\n- 这些对象是无用的，即程序以后不会再使用这些对象。\n\n如果对象满足这两个条件，这些对象就可以判定为 Java 中的内存泄漏，这些对象不会被 GC 所回收，然而它却占用内存。\n\n\n\n在 C++ 中，内存泄漏的范围更大一些。有些对象被分配了内存空间，然后却不可达，由于 C++ 中没有 GC，这些内存将永远收不回来。在 Java 中，这些不可达的对象都由 GC 负责回收，因此程序员不需要考虑这部分的内存泄露。\n\n通过分析，我们得知，对于C++，程序员需要自己管理边和顶点，而对于 Java 程序员只需要管理边就可以了(不需要管理顶点的释放)。通过这种方式，Java 提高了编程的效率。\n\n<div align=\"center\"> <img src=\"assets/memory-leak.gif\" width=\"600\"/> </div><br>\n\n\n\n同样给出一个 Java 内存泄漏的典型例子，\n\n```java\nVector v = new Vector(10);\nfor (int i = 1; i < 100; i++) {\n    Object o = new Object();\n    v.add(o);\n    o = null;   \n}\n```\n\n在这个例子中，我们循环申请Object对象，并将所申请的对象放入一个 Vector 中，如果我们仅仅释放引用本身，那么 Vector 仍然引用该对象，所以这个对象对 GC 来说是不可回收的。因此，如果对象加入到Vector 后，还必须从 Vector 中删除，最简单的方法就是将 Vector 对象设置为 null。 \n\n\n\n**内存泄露查询工具**\n\n- **MemoryAnalyzer**：一个功能丰富的 JAVA 堆转储文件分析工具，可以帮助你发现内存漏洞和减少内存消耗 \n- **EclipseMAT**：是一款开源的JAVA内存分析软件，查找内存泄漏，能容易找到大块内存并验证谁在一直占用它，它是基于Eclipse RCP(Rich Client Platform)，可以下载RCP的独立版本或者Eclipse的插件 \n- **JProbe**：分析Java的内存泄漏。 \n\n\n\n参考资料：[Java中关于内存泄漏出现的原因以及如何避免内存泄漏（超详细版汇总上）](https://blog.csdn.net/wtt945482445/article/details/52483944) \n\n\n\n\n\n## * 虚拟机参数\n\n大多数的配置都是为堆服务的\n\n（1）-XX 对于系统级别的（JVM）的配置\n\n- 比如配置日志信息或者配置JVM使用什么样的垃圾回收器\n\n（2）非-XX 配置基本都是对应用层面上的配置\n\n`+ 表示启动`，`- 表示禁用`\n\n\n\n\n\n# 附录：参考资料\n\n- [面试JVM 听这堂课就够了_面试jvm 听这堂课就够了_腾讯视频](https://v.qq.com/x/cover/bcmtqgpddsbj75k/g1423t1uwp5.html)\n- [咕泡学院-James老师_腾讯课堂](https://ke.qq.com/teacher/2904270631)\n- [Java虚拟机概述和基本概念](https://centmeng.github.io/2017/03/30/Java%E6%9E%B6%E6%9E%84%E5%B8%88-JVM/)\n- [JVM性能监控工具 - 简书](https://www.jianshu.com/p/25e94a1399a0)\n\n- [探索JVM底层奥秘ClassLoader源码分析与案例讲解_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili](https://www.bilibili.com/video/av17748750?from=search&seid=7485294466500539956)\n\n- [一个“Hello World”理解JVM运行时数据区 - 西楼有酒 - 博客园](https://www.cnblogs.com/JunFengChan/p/9250585.html)\n\n- [JVM体系结构讲解](https://zhuanlan.zhihu.com/p/28347393)\n\n\n\n\n# 更新说明\n\nv1.0 2018/7/21 初版完成\n\nv2.4 2018/8/18 基础初版\n\nv2.5 2018/8/18 修改格式，补充符号链接与直接链接"
  },
  {
    "path": "notes/JavaArchitecture/06-Java设计模式.md",
    "content": "<!-- TOC -->\n\n- [一、概述](#一概述)\n    ​    - [设计模式怎么分类，每一类都有哪些？【蚂蚁金服内推】](#设计模式怎么分类每一类都有哪些蚂蚁金服内推)\n    ​    - [设计模式怎么用到项目中？【阿里面经】](#设计模式怎么用到项目中阿里面经)\n- [二、设计模式](#二设计模式)\n    - [单例模式](#单例模式)\n    - [工厂模式](#工厂模式)\n    - [观察者模式](#观察者模式)\n    - [适配器模式（Adapter）](#适配器模式adapter)\n        - [意图](#意图)\n        - [类型](#类型)\n        - [类图](#类图)\n        - [实现](#实现)\n        - [JDK](#jdk)\n    - [模仿方法模式](#模仿方法模式)\n    - [策略模式（Strategy）](#策略模式strategy)\n        - [意图](#意图-1)\n        - [类图](#类图-1)\n        - [与状态模式的比较](#与状态模式的比较)\n        - [实现](#实现-1)\n        - [JDK](#jdk-1)\n            - [](#)\n    - [责任链模式](#责任链模式)\n    - [装饰者模式](#装饰者模式)\n    - [迭代器模式（Iterator）](#迭代器模式iterator)\n        ​    ​    - [所了解的设计模式，单例模式的注意事项，jdk源码哪些用到了你说的设计模式](#所了解的设计模式单例模式的注意事项jdk源码哪些用到了你说的设计模式)\n- [三、设计模式常见问题](#三设计模式常见问题)\n- [附录：参考资料](#附录参考资料)\n\n<!-- /TOC -->\n[Interview-Notebook/设计模式](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.md)\n\n\n# 一、概述\n\n1、设计模式是人们在面对同类型软件工程设计问题所总结出的一些有用经验。模式不是代码，而是某类问题的通用设计解决方案\n\n2、4人组Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides总结写了《设计模式》\n\n3、设计模式的优点和用途\n\n4、学习设计模式最好的方式：在你的设计和以往的工程里寻找何处可以使用它们\n\n5、设计模式的本质目的是使软件工程在维护性、扩展性、变化性、复杂度方面成O(N)\n\n6、OO（Object Oriented）是原则，设计模式是具体方法、工具\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\n\n\n\n\n## 适配器模式（Adapter）\n\n2018/7/15\n\n### 意图\n\n把一个类接口转换成另一个用户需要的接口。适配器模式让那些接口不兼容的类可以一起工作 \n\n\n\n[![img](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/3d5b828e-5c4d-48d8-a440-281e4a8e1c92.png)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/3d5b828e-5c4d-48d8-a440-281e4a8e1c92.png)\n\n\n\n### 类型\n\n适配器模式的别名为包装器(Wrapper)模式，它既可以作为**类结构型模式**，也可以作为**对象结构型模式**。在适配器模式定义中所提及的接口是指广义的接口，它可以表示一个方法或者方法的集合。 \n\n- 对象适配器：（传入对象）组合方式，但是更灵活推荐使用这种方式\n- 类适配器：（多重继承）继承方式，效率更高\n\n\n\n### 类图\n\n[![img](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/0f754c1d-b5cb-48cd-90e0-4a86034290a1.png)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/0f754c1d-b5cb-48cd-90e0-4a86034290a1.png)\n\n \n\n### 实现\n\n鸭子（Duck）和火鸡（Turkey）拥有不同的叫声，Duck 的叫声调用 quack() 方法，而 Turkey 调用 gobble() 方法。\n\n要求将 Turkey 的 gobble() 方法适配成 Duck 的 quack() 方法，从而让火鸡冒充鸭子！\n\n```java\npublic interface Duck {\n    void quack();\n}\n```\n\n```java\npublic interface Turkey {\n    void gobble();\n}\n```\n\n```java\npublic class WildTurkey implements Turkey {\n    @Override\n    public void gobble() {\n        System.out.println(\"gobble!\");\n    }\n}\n```\n\n```java\npublic class TurkeyAdapter implements Duck {\n    Turkey turkey;\n\n    public TurkeyAdapter(Turkey turkey) {\n        this.turkey = turkey;\n    }\n\n    @Override\n    public void quack() {\n        turkey.gobble();\n    }\n}\n```\n\n```java\npublic class Client {\n    public static void main(String[] args) {\n        Turkey turkey = new WildTurkey();\n        Duck duck = new TurkeyAdapter(turkey);\n        duck.quack();\n    }\n}\n```\n\n### JDK\n\n- [java.util.Arrays#asList()](http://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#asList%28T...%29)\n- [java.util.Collections#list()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#list-java.util.Enumeration-)\n- [java.util.Collections#enumeration()](https://docs.oracle.com/javase/8/docs/api/java/util/Collections.html#enumeration-java.util.Collection-)\n- [javax.xml.bind.annotation.adapters.XMLAdapter](http://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/adapters/XmlAdapter.html#marshal-BoundType-)\n\n\n\n\n\n## 模仿方法模式\n\n\n\n\n\n\n\n## 策略模式（Strategy）\n\n2018/7/11\n\n### 意图\n\n- 定义一系列算法，封装每个算法，并使它们可以互换。\n- 策略模式可以让算法独立于使用它的客户端。\n\n### 类图\n\n- Strategy 接口定义了一个算法族，它们都具有 behavior() 方法。\n- Context 是使用到该算法族的类，其中的 doSomething() 方法会调用 behavior()，setStrategy(in Strategy) 方法可以动态地改变 strategy 对象，也就是说能动态地改变 Context 所使用的算法。\n\n[![img](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/1fc969e4-0e7c-441b-b53c-01950d2f2be5.png)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/1fc969e4-0e7c-441b-b53c-01950d2f2be5.png)\n\n### 与状态模式的比较\n\n状态模式的类图和策略模式类似，并且都是能够动态改变对象的行为。\n\n但是状态模式是通过状态转移来改变 Context 所组合的 State 对象，而策略模式是通过 Context 本身的决策来改变组合的 Strategy 对象。\n\n所谓的状态转移，是指 Context 在运行过程中由于一些条件发生改变而使得 State 对象发生改变，注意必须要是在运行过程中。\n\n状态模式主要是用来解决状态转移的问题，当状态发生转移了，那么 Context 对象就会改变它的行为；而策略模式主要是用来封装一组可以互相替代的算法族，并且可以根据需要动态地去替换 Context 使用的算法。\n\n### 实现\n\n设计一个鸭子，它可以动态地改变叫声。这里的算法族是鸭子的叫声行为。\n\n```java\npublic interface QuackBehavior {\n    void quack();\n}\n```\n\n```java\npublic class Quack implements QuackBehavior {\n    @Override\n    public void quack() {\n        System.out.println(\"quack!\");\n    }\n}\n```\n\n```java\npublic class Squeak implements QuackBehavior{\n    @Override\n    public void quack() {\n        System.out.println(\"squeak!\");\n    }\n}\n```\n\n```java\npublic class Duck {\n    private QuackBehavior quackBehavior;\n\n    public void performQuack() {\n        if (quackBehavior != null) {\n            quackBehavior.quack();\n        }\n    }\n\n    public void setQuackBehavior(QuackBehavior quackBehavior) {\n        this.quackBehavior = quackBehavior;\n    }\n}\n```\n\n```java\npublic class Client {\n    public static void main(String[] args) {\n        Duck duck = new Duck();\n        duck.setQuackBehavior(new Squeak());\n        duck.performQuack();\n        duck.setQuackBehavior(new Quack());\n        duck.performQuack();\n    }\n}\n```\n\n```\nsqueak!\nquack!\n```\n\n### JDK\n\n- java.util.Comparator#compare()\n- javax.servlet.http.HttpServlet\n- javax.servlet.Filter#doFilter()\n\n\n\n#### \n\n\n\n## 责任链模式\n\n\n\n\n\n\n\n## 装饰者模式\n\n\n\n\n\n## 迭代器模式（Iterator）\n\n2018/7/16\n\n\n\n\n\n\n\n- 反应器模式\n\n \n\n1. 常用的八种掌握就行，原理，使用\n2. 单例、工厂、观察者重点\n\n\n\n\n\n##### 所了解的设计模式，单例模式的注意事项，jdk源码哪些用到了你说的设计模式 \n\n- 所了解的设计模式 \n  - 工厂模式：定义一个用于创建对象的接口，让子类决定实例化哪一个类， Factory Method 使一个类的实例化延迟到了子类。 \n  - 单例模式：保证一个类只有一个实例，并提供一个访问它的全局访问点； \n  - 适配器模式：将一类的接口转换成客户希望的另外一个接口，Adapter 模式使得原本由于接口不兼容而不能一起工作那些类可以一起工作。 \n  - 装饰者模式：动态地给一个对象增加一些额外的职责，就增加的功能来说， Decorator 模式相比生成子类更加灵活。 \n  - 代理：为其他对象提供一种代理以控制对这个对象的访问 \n  - 迭代器模式：提供一个方法顺序访问一个聚合对象的各个元素，而又不需要暴露该对象的内部表示。 \n- 单例模式的注意事项 \n  - 尽量使用懒加载 \n  - 双重检索实现线程安全 \n  - 构造方法为private \n  - 定义静态的Singleton instance对象和getInstance()方法 \n- jdk源码中用到的设计模式 \n  - 装饰器模式：IO流中 \n  - 迭代器模式：Iterator \n  - 单利模式： java.lang.Runtime \n  - 代理模式：RMI \n\n\n\n# 三、设计模式常见问题\n\n\n\n\n\n1.什么是高内聚，低耦合？\n\n\n\n\n\n一个类只做一件事\n\n一个方法只做一件事\n\n写仅只写一次\n\n\n\n\n\n# 附录：参考资料\n\n\n\n卡奴达摩的专栏 - CSDN博客\nhttps://blog.csdn.net/zhengzhb/article/category/926691/1\n\n\n\n单例模式 - 23种设计模式 - 极客学院Wiki\nhttp://wiki.jikexueyuan.com/project/java-design-pattern/singleton-pattern.html\n\n\n\n\n\nhttps://www.bilibili.com/video/av18569541/\n\n\n\nhexter 录制的课程 - 极客学院【23种设计模式】\nhttp://my.jikexueyuan.com/hexter/record/\n\n\n\n设计模式之禅\n\n"
  },
  {
    "path": "notes/JavaArchitecture/07-JavaWeb.md",
    "content": "<!-- TOC -->\n\n- [前言](#前言)\n- [一、Servlet / JSP / Web](#一servlet--jsp--web)\n    - [1. 什么是Servlet](#1-什么是servlet)\n    - [2. Tomcat容器等级](#2-tomcat容器等级)\n    - [3. Servlet执行流程](#3-servlet执行流程)\n        - [浏览器请求](#浏览器请求)\n        - [服务器创建对象](#服务器创建对象)\n        - [调用init方法](#调用init方法)\n        - [调用service方法](#调用service方法)\n        - [向浏览器响应](#向浏览器响应)\n    - [4. Servlet生命周期](#4-servlet生命周期)\n    - [5. Tomcat装载Servlet的三种情况](#5-tomcat装载servlet的三种情况)\n    - [6. forward和redirect](#6-forward和redirect)\n    - [7. Jsp和Servlet的区别](#7-jsp和servlet的区别)\n    - [8. tomcat和Servlet的联系](#8-tomcat和servlet的联系)\n    - [9. cookie和session的区别](#9-cookie和session的区别)\n    - [10. JavaEE中的三层结构和MVC](#10-javaee中的三层结构和mvc)\n    - [11. RESTful 架构](#11-restful-架构)\n        - [什么是REST](#什么是rest)\n        - [什么是RESTful API](#什么是restful-api)\n        - [RESTful 风格](#restful-风格)\n- [二、Spring](#二spring)\n    - [1. Spring IOC、AOP的理解、实现的原理，以及优点](#1-spring-iocaop的理解实现的原理以及优点)\n        - [IOC](#ioc)\n        - [AOP](#aop)\n    - [2. 什么是依赖注入，注入的方式有哪些](#2-什么是依赖注入注入的方式有哪些)\n    - [3. Spring IOC初始化过程](#3-spring-ioc初始化过程)\n    - [4. 项目中Spring AOP用在什么地方，为什么这么用，切点，织入，通知用自己的话描述一下](#4-项目中spring-aop用在什么地方为什么这么用切点织入通知用自己的话描述一下)\n    - [5. AOP动态代理2种实现原理，他们的区别是什么？](#5-aop动态代理2种实现原理他们的区别是什么)\n    - [6. Struts拦截器和Spring AOP区别](#6-struts拦截器和spring-aop区别)\n    - [7. Spring 是如何管理事务的，事务管理机制](#7-spring-是如何管理事务的事务管理机制)\n        - [如何管理的](#如何管理的)\n    - [8. Spring中bean加载机制，生命周期](#8-spring中bean加载机制生命周期)\n        - [加载机制](#加载机制)\n        - [生命周期](#生命周期)\n    - [9. Bean实例化的三种方式](#9-bean实例化的三种方式)\n    - [10. BeanFactory 和 FactoryBean的区别](#10-beanfactory-和-factorybean的区别)\n    - [11. BeanFactory和ApplicationContext的区别](#11-beanfactory和applicationcontext的区别)\n        - [BeanFactory](#beanfactory)\n        - [两者装载bean的区别](#两者装载bean的区别)\n        - [我们该用BeanFactory还是ApplicationContent](#我们该用beanfactory还是applicationcontent)\n        - [ApplicationContext其他特点](#applicationcontext其他特点)\n        - [spring的AOP（常用的是拦截器）](#spring的aop常用的是拦截器)\n        - [spring载入多个上下文](#spring载入多个上下文)\n    - [12. ApplicationContext 上下文的生命周期](#12-applicationcontext-上下文的生命周期)\n    - [13. Spring中autowire和resourse关键字的区别](#13-spring中autowire和resourse关键字的区别)\n    - [14. Spring的注解讲一下，介绍Spring中的熟悉的注解](#14-spring的注解讲一下介绍spring中的熟悉的注解)\n        - [一： 组件类注解](#一-组件类注解)\n        - [二：装配bean时常用的注解](#二装配bean时常用的注解)\n    - [15. Spring 中用到了那些设计模式？](#15-spring-中用到了那些设计模式)\n        - [工厂模式（Factory Method）](#工厂模式factory-method)\n        - [单态模式【单例模式】（Singleton）](#单态模式单例模式singleton)\n        - [适配器（Adapter）](#适配器adapter)\n        - [代理（Proxy）](#代理proxy)\n        - [观察者（Observer）](#观察者observer)\n    - [16. Spring 的优点有哪些](#16-spring-的优点有哪些)\n    - [17. IOC和AOP用到的设计模式](#17-ioc和aop用到的设计模式)\n- [二、SpringMVC](#二springmvc)\n    - [1. Spring MVC的工作原理](#1-spring-mvc的工作原理)\n    - [2. Spring MVC注解的优点](#2-spring-mvc注解的优点)\n- [三、Hibernate](#三hibernate)\n    - [1. 简述Hibernate常见优化策略。](#1-简述hibernate常见优化策略)\n    - [2. Hibernate一级缓存与二级缓存之间的区别](#2-hibernate一级缓存与二级缓存之间的区别)\n    - [3. Hibernate的理解](#3-hibernate的理解)\n- [四、MyBatis](#四mybatis)\n    - [1. Mybatis原理](#1-mybatis原理)\n    - [2. Hibernate了解吗，Mybatis和Hibernate的区别](#2-hibernate了解吗mybatis和hibernate的区别)\n- [五、Tomcat](#五tomcat)\n    - [1. tomcat加载基本流程，涉及到的参数](#1-tomcat加载基本流程涉及到的参数)\n- [附录：参考资料](#附录参考资料)\n\n<!-- /TOC -->\n# 前言\n\n在本文中将总结 Java Web 开发技术和相关框架的核心知识。因框架知识体系比较庞大，具体每个框架的使用我将放在 `../JavaWeb` 这个目录下，包含 Spring、Strust2、Hibernate、Spring Boot 等框架。\n\n\n\n- Spring\n- Strust2\n- Hibernate\n- Mybatis\n- Spring MVC\n- Spring Boot\n- Dubbo\n\n\n\n> 在面试指南中将列举面试中常见的考点，包含Servlet、JSP、Spring、中间件等常考Java Web框架知识\n>\n\n\n\n\n\n参考资料：\n\n- [JSP-Servlet的工作流程 - wwfy - 博客园](https://www.cnblogs.com/w-wfy/p/5833103.html)\n\n\n\n\n\n# 一、Servlet / JSP / Web\n\n## 1. 什么是Servlet\n\nServlet 是在服务器上运行的小程序。一个 servlet 就是一个 Java 类，并且可以通过 “请求—响应” 编程模式来访问的这个驻留在服务器内存里的 servlet 程序。  \n\n类的继承关系如下：\n\n<div align=\"center\"><img src=\"assets/1535532891036.png\" width=\"550\"/></div>\n\nServlet三种实现方式：\n\n- 实现javax.servlet.Servlet接口\n\n- 继承javax.servlet.GenericServlet类\n- 继承javax.servlet.http.HttpServlet类\n\n　　通常会去继承HttpServlet类来完成Servlet。\n\n\n\n## 2. Tomcat容器等级\n\nTomcat的容器分为4个等级，Servlet的容器管理Context容器，一个Context对应一个Web工程。\n\n<div align=\"center\"><img src=\"assets/4685968-b27b8782600dd0af.png\" width=\"600\"/></div>\n\n\n\n\n## 3. Servlet执行流程\n\n主要描述了从浏览器到服务器，再从服务器到浏览器的整个执行过程\n\n### 浏览器请求\n\n<div align=\"center\"><img src=\"assets/20180521175251513.png\" width=\"600\"/></div>\n\n浏览器向服务器请求时，服务器不会直接执行我们的类，而是到 web.xml 里寻找路径名 \n① 浏览器输入访问路径后，携带了请求行，头，体 \n② 根据访问路径找到已注册的 servlet 名称\n③ 根据映射找到对应的 servlet 名 \n④ 根据根据 servlet 名找到我们全限定类名，既我们自己写的类\n\n### 服务器创建对象\n\n<div align=\"center\"><img src=\"assets/20180521182037787.png\" width=\"600\"/></div>\n\n① 服务器找到全限定类名后，通过反射创建对象，同时也创建了 servletConfig，里面存放了一些初始化信息（注意服务器只会创建一次 servlet 对象，所以 servletConfig 也只有一个）\n\n\n\n### 调用init方法\n\n<div align=\"center\"><img src=\"assets/20180521183945631.png\" width=\"600\"/></div>\n\n① 对象创建好之后，首先要执行 init 方法，但是我们发现我们自定义类下没有 init 方法，所以程序会到其父类 HttpServlet 里找 \n② 我们发现 HttpServlet 里也没有 init 方法，所以继续向上找，既向其父类 GenericServlet 中继续寻找,在 GenericServlet 中我们发现了 init 方法，则执行 init 方法（对接口 Servlet 中的 init 方法进行了重写） \n\n注意： 在 GenericServlet 中执行 public void init(ServletConfig config) 方法的时候，又调用了自己无参无方法体的 init() 方法，其目的是为了方便开发者，如果开发者在初始化的过程中需要实现一些功能，可以重写此方法。\n\n\n\n### 调用service方法\n\n<div align=\"center\"><img src=\"assets/20180521212619975.png\" width=\"700\"/></div>\n\n接着，服务器会先创建两个对象：ServletRequest 请求对象和 ServletResponse 响应对象，用来封装浏览器的请求数据和封装向浏览器的响应数据 \n① 接着服务器会默认在我们写的类里寻找 service(ServletRequest req, ServletResponse res) 方法，但是 DemoServlet 中不存在，那么会到其父类中寻找 \n② 到父类 HttpServlet 中发现有此方法，则直接调用此方法，并将之前创建好的两个对象传入 \n③ 然后将传入的两个参数强转，并调用 HttpServlet 下的另外个 service 方法 \n④ 接着执行 `service(HttpServletRequest req, HttpServletResponse resp) `方法，在此方法内部进行了判断请求方式，并执行doGet和doPost，但是doGet和doPost方法已经被我们自己重写了，所以会执行我们重写的方法 \n看到这里，你或许有疑问：为什么我们不直接重写service方法？ \n因为如果重写service方法的话，我们需要将强转，以及一系列的安全保护判断重新写一遍，会存在安全隐患\n\n### 向浏览器响应\n<div align=\"center\"><img src=\"assets/20180521214423142.png\" width=\"600\"/></div>\n\n\n\n## 4. Servlet生命周期\n\n- `void init(ServletConfig servletConfig) `：Servlet对象创建之后马上执行的初始化方法，只执行一次；\n- `void service(ServletRequest servletRequest, ServletResponse servletResponse) `：每次处理请求都是在调用这个方法，它会被调用多次；\n- `void destroy() `：在Servlet被销毁之前调用，负责释放 Servlet 对象占用的资源的方法；\n\n特性：\n\n- 线程不安全的，所以它的效率高。\n- 单例，一个类只有一个对象，当然可能存在多个 Servlet 类\n\nServlet 类由自己编写，但对象由服务器来创建，并由服务器来调用相应的方法　\n\n\n\n服务器启动时 ( web.xml中配置`load-on-startup=1`，默认为0 ) 或者第一次请求该 servlet 时，就会初始化一个 Servlet 对象，也就是会执行初始化方法 init(ServletConfig conf)\n\n该 servlet 对象去处理所有客户端请求，在 `service(ServletRequest req，ServletResponse res)` 方法中执行\n\n最后服务器关闭时，才会销毁这个 servlet 对象，执行 destroy() 方法。\n\n<div align=\"center\"> <img src=\"assets/1535535812505.png\" width=\"650\"/></div><br/>\n\n**总结（面试会问）：**　　　\n\n1）Servlet何时创建\n\n\t默认第一次访问servlet时创建该对象（调用init()方法）\n\n2）Servlet何时销毁\n\n\t服务器关闭servlet就销毁了(调用destroy()方法)\n\n3）每次访问必须执行的方法\n\n\tpublic void service(ServletRequest arg0, ServletResponse arg1)\n\n\n\n## 5. Tomcat装载Servlet的三种情况\n\n1. Servlet容器启动时自动装载某些Servlet，实现它只需要在web.xml文件中的 `<servlet></servlet>` 之间添加以下代码：\n\n```xml\n<load-on-startup>1</load-on-startup>\n```\n\n　　其中，数字越小表示优先级越高。\n\n　　例如：我们在 web.xml 中设置 TestServlet2 的优先级为 1，而 TestServlet1 的优先级为 2，启动和关闭Tomcat：优先级高的先启动也先关闭。　　\n\n2. 客户端首次向某个Servlet发送请求\n\n3. Servlet 类被修改后，Tomcat 容器会重新装载 Servlet。\n\n\n\n## 6. forward和redirect\n\n*本节参考：《Java程序员面试笔试宝典》P172*\n\n　　在设计 Web 应用程序时，经常需要把一个系统进行结构化设计，即按照模块进行划分，让不同的 Servlet 来实现不同的功能，例如可以让其中一个 Servlet 接收用户的请求，另外一个 Servlet 来处理用户的请求。为了实现这种程序的模块化，就需要保证在不同的 Servlet 之间可以相互跳转，而 Servlet 中主要有两种实现跳转的方式：forward 与 redirect 方式。\n\n　　forward 是服务器内部的重定向，服务器直接访问目标地址的 URL，把那个 URL 的响应内容读取过来，而客户端并不知道，因此在客户端浏览器的地址栏中不会显示转向后的地址，还是原来的地址。由于在整个定向的过程中用的是同一个 Request，因此 forward 会将 Request 的信息带到被定向的 JSP 或 Servlet 中使用。\n\n　　redirect 则是客户端的重定向，是完全的跳转，即客户端浏览器会获取到跳转后的地址，然后重新发送请求，因此浏览器中会显示跳转后的地址。同事，由于这种方式比 forward 方式多了一次网络请求，因此其效率要低于 forward 方式。需要注意的是，客户端的重定向可以通过设置特定的 HTTP 头或改写 JavaScript 脚本实现。\n\n　　下图可以更好的说明二者的区别：\n\n<div align=\"center\"><img src=\"assets/1535537913258.png\" width=\"\"/></div>\n\n　　鉴于以上的区别，一般当 forward 方式可以满足需求时，尽可能地使用 forward 方式。但在有些情况下，例如，需要跳转到下一个其他服务器上的资源，则必须使用 redirect 方式。\n\n引申：filter的作用是什么？主要实现什么方法？\n\nfilter 使用户可以改变一个 request 并且修改一个 response。filter 不是一个 Servlet，它不能产生一个 response，但它能够在一个 request 到达 Servlet 之前预处理 request，也可以在离开 Servlet 时处理 response。filter 其实是一个 “Servlet Chaining” (Servler 链)。\n\n一个 filter 的作用包括以下几个方面：\n\n1）在 Servlet 被调用之前截获\n\n2）在 Servlet 被调用之前检查 Servlet Request\n\n3）根据需要修改 Request 头和 Request 数据\n\n4）根据需要修改 Response 头和 Response 数据\n\n5）在 Servlet 被调用之后截获\n\n\n\n## 7. Jsp和Servlet的区别\n\n**1、不同之处在哪？**\n\n- Servlet 在 Java 代码中通过 HttpServletResponse 对象动态输出 HTML 内容\n- JSP 在静态 HTML 内容中嵌入 Java 代码，Java 代码被动态执行后生成 HTML 内容\n\n**2、各自的特点**\n\n- Servlet 能够很好地组织业务逻辑代码，但是在 Java 源文件中通过字符串拼接的方式生成动态 HTML 内容会导致代码维护困难、可读性差\n- JSP 虽然规避了 Servlet 在生成 HTML 内容方面的劣势，但是在 HTML 中混入大量、复杂的业务逻辑同样也是不可取的\n\n**3、通过MVC双剑合璧**\n\n既然 JSP 和 Servlet 都有自身的适用环境，那么能否扬长避短，让它们发挥各自的优势呢？答案是肯定的——MVC(Model-View-Controller)模式非常适合解决这一问题。\n\nMVC模式（Model-View-Controller）是软件工程中的一种软件架构模式，把软件系统分为三个基本部分：模型（Model）、视图（View）和控制器（Controller）：\n\n- Controller——负责转发请求，对请求进行处理\n- View——负责界面显示\n- Model——业务功能编写（例如算法实现）、数据库设计以及数据存取操作实现\n\n\n\n在 JSP/Servlet 开发的软件系统中，这三个部分的描述如下所示：\n\n<div align=\"center\"><img src=\"assets/229cf9ff5b1729eaf408fac56238eeb3.png\" width=\"600\"/></div><br/>\n\n1. Web 浏览器发送 HTTP 请求到服务端，被 Controller(Servlet) 获取并进行处理（例如参数解析、请求转发）\n2. Controller(Servlet) 调用核心业务逻辑——Model部分，获得结果\n3. Controller(Servlet) 将逻辑处理结果交给 View（JSP），动态输出 HTML 内容\n4. 动态生成的 HTML 内容返回到浏览器显示\n\nMVC 模式在 Web 开发中的好处是非常明显，它规避了 JSP 与 Servlet 各自的短板，Servlet 只负责业务逻辑而不会通过 out.append() 动态生成 HTML 代码；JSP 中也不会充斥着大量的业务代码。这大大提高了代码的可读性和可维护性。\n\n\n\n## 8. tomcat和Servlet的联系\n\n　　Tomcat是Web应用服务器，是一个Servlet/JSP容器。Tomcat 作为 Servlet 容器，负责处理客户请求，把请求传送给Servlet，并将Servlet的响应传送回给客户。而 Servlet 是一种运行在支持 Java 语言的服务器上的组件。Servlet最常见的用途是扩展 Java Web 服务器功能，提供非常安全的，可移植的，易于使用的CGI替代品。\n\n　　从 http 协议中的请求和响应可以得知，浏览器发出的请求是一个请求文本，而浏览器接收到的也应该是一个响应文本。但是在上面这个图中，并不知道是如何转变的，只知道浏览器发送过来的请求也就是 request，我们响应回去的就用 response。忽略了其中的细节，现在就来探究一下。\n\n<div align=\"center\"><img src=\"assets/servlet-tomcat.png\" width=\"600\"/></div>\n\n① Tomcat 将 http 请求文本接收并解析，然后封装成 HttpServletRequest 类型的 request 对象，所有的 HTTP 头数据读可以通过 request 对象调用对应的方法查询到。\n\n② Tomcat 同时会要响应的信息封装为 HttpServletResponse 类型的 response 对象，通过设置 response 属性就可以控制要输出到浏览器的内容，然后将 response 交给 tomcat，tomcat 就会将其变成响应文本的格式发送给浏览器\n\nJava Servlet API 是 Servlet 容器(tomcat) 和 servlet 之间的接口，它定义了 serlvet 的各种方法，还定义了 Servlet 容器传送给 Servlet 的对象类，其中最重要的就是 ServletRequest 和 ServletResponse。所以说我们在编写 servlet 时，需要实现 Servlet 接口，按照其规范进行操作。\n\n \n\n## 9. cookie和session的区别\n\n类似这种面试题，实际上都属于“开放性”问题，你扯到哪里都可以。不过如果我是面试官的话，我还是希望对方能做到一点——不要混淆 session 和 session 实现。\n\n本来 session 是一个抽象概念，开发者为了实现中断和继续等操作，将 user agent 和 server 之间一对一的交互，抽象为“会话”，进而衍生出“会话状态”，也就是 session 的概念。\n\n而 cookie 是一个实际存在的东西，http 协议中定义在 header 中的字段。可以认为是 session 的一种后端无状态实现。\n\n而我们今天常说的 “session”，是为了绕开 cookie 的各种限制，通常借助 cookie 本身和后端存储实现的，一种更高级的会话状态实现。\n\n所以 cookie 和 session，你可以认为是同一层次的概念，也可以认为是不同层次的概念。具体到实现，session 因为 session id 的存在，通常要借助 cookie 实现，但这并非必要，只能说是通用性较好的一种实现方案。\n\n\n\n**引申**\n\n1. 由于 HTTP 协议是无状态的协议，所以服务端需要记录用户的状态时，就需要用某种机制来识具体的用户，这个机制就是 Session。典型的场景比如购物车，当你点击下单按钮时，由于 HTTP 协议无状态，所以并不知道是哪个用户操作的，所以服务端要为特定的用户创建了特定的 Session，用用于标识这个用户，并且跟踪用户，这样才知道购物车里面有几本书。这个 Session 是保存在服务端的，有一个唯一标识。在服务端保存Session 的方法很多，内存、数据库、文件都有。集群的时候也要考虑 Session 的转移，在大型的网站，一般会有专门的 Session 服务器集群，用来保存用户会话，这个时候 Session 信息都是放在内存的，使用一些缓存服务比如 Memcached 之类的来放 Session。\n\n2. 思考一下服务端如何识别特定的客户？\n\n   这个时候 Cookie 就登场了。每次 HTTP 请求的时候，客户端都会发送相应的 Cookie 信息到服务端。实际上大多数的应用都是用 Cookie 来实现 Session 跟踪的，第一次创建 Session 的时候，服务端会在 HTTP 协议中告诉客户端，需要在 Cookie 里面记录一个Session ID，以后每次请求把这个会话 ID 发送到服务器，我就知道你是谁了。有人问，如果客户端的浏览器禁用了 Cookie 怎么办？一般这种情况下，会使用一种叫做URL重写的技术来进行会话跟踪，即每次 HTTP 交互，URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数，服务端据此来识别用户。\n\n3. Cookie 其实还可以用在一些方便用户的场景下，设想你某次登陆过一个网站，下次登录的时候不想再次输入账号了，怎么办？这个信息可以写到 Cookie 里面，访问网站的时候，网站页面的脚本可以读取这个信息，就自动帮你把用户名给填了，能够方便一下用户。这也是 Cookie 名称的由来，给用户的一点甜头。\n\n\n\n所以，总结一下：\n\n- Session 是在服务端保存的一个数据结构，用来跟踪用户的状态，这个数据可以保存在集群、数据库、文件中；\n- Cookie 是客户端保存用户信息的一种机制，用来记录用户的一些信息，也是实现 Session 的一种方式。\n\n\n\n## 10. JavaEE中的三层结构和MVC\n\n做企业应用开发时，经常采用三层架构分层：表示层、业务层、持久层。表示层负责接收用户请求、转发请求、显示数据等；业务层负责组织业务逻辑；持久层负责持久化业务对象。\n\n这三个分层，每一层都有不同的模式，就是架构模式。**表示层**最常用的架构模式就是MVC。\n\n因此，MVC 是三层架构中表示层最常用的架构模式。\n\n\n\nMVC 是**客户端**的一种设计模式，所以他天然就不考虑数据如何存储的问题。作为客户端，只需要解决用户界面、交互和业务逻辑就好了。在 MVC 模式中，View 负责的是用户界面，Controller 负责交互，Model 负责业务逻辑。至于数据如何存储和读取，当然是由 Model 调用服务端的接口来完成。\n\n在三层架构中，并没有客户端/服务端的概念，所以表示层、业务层的任务其实和 MVC 没什么区别，而持久层在 MVC 里面是没有的。\n\n\n\n各层次的关系：表现层的控制->服务层->数据持久化层。\n\n<div align=\"center\"><img src=\"assets/jee-3-ties.bmp\" width=\"\"/></div>\n\n\n\n\n\n参考资料：\n\n- [JavaEE中的三层结构和MVC - cuiyi's blog（崔毅 crazycy） - BlogJava](http://www.blogjava.net/crazycy/archive/2006/07/03/56387.html)\n- [三层构架和 MVC 不同吗？ - 知乎](https://www.zhihu.com/question/24291079)\n\n\n\n## 11. RESTful 架构\n\n### 什么是REST\n\n可以总结为一句话：REST 是所有 Web 应用都应该遵守的架构设计指导原则。 \nRepresentational State Transfer，翻译是”表现层状态转化”。 \n面向资源是 REST 最明显的特征，对于同一个资源的一组不同的操作。资源是服务器上一个可命名的抽象概念，资源是以名词为核心来组织的，首先关注的是名词。REST要求，必须通过统一的接口来对资源执行各种操作。对于每个资源只能执行一组有限的操作。（7个HTTP方法：GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS）\n\n### 什么是RESTful API\n\n符合REST架构设计的API。\n\n### RESTful 风格\n\n以豆瓣网为例\n\n1. 应该尽量将 API 部署在专用域名之下 \n   `http://api.douban.com`/v2/user/1000001?apikey=XXX\n\n2. 应该将 API 的版本号放入URL \n   `http://api.douban.com/v2`/user/1000001?apikey=XXX\n\n3. 在 RESTful 架构中，每个网址代表一种资源（resource），所以网址中`不能有动词，只能有名词`，而且所用的`名词往往与数据库的表格名对应`。一般来说，数据库中的表都是同种记录的”集合”（collection），所以 API 中的名词也应该使用复数。 \n   http://api.douban.com/v2/`book`/:id (获取图书信息) \n   http://api.douban.com/v2/`movie`/subject/:id (电影条目信息) \n   http://api.douban.com/v2/`music`/:id (获取音乐信息) \n   http://api.douban.com/v2/`event`/:id (获取同城活动)\n\n4. 对于资源的具体操作类型，由HTTP动词表示。常用的HTTP动词有下面四个(对应`增/删/改/查`)。 \n   **GET**（`select`）：从服务器取出资源（一项或多项）。 \n   eg. 获取图书信息 `GET` <http://api.douban.com/v2/book/:id>\\\n\n   **POST**（`create`）：在服务器新建一个资源。 \n   eg. 用户收藏某本图书 `POST` <http://api.douban.com/v2/book/:id/collection>\n\n   **PUT**（`update`）：在服务器更新资源（客户端提供改变后的完整资源）。 \n   eg. 用户修改对某本图书的收藏 `PUT` <http://api.douban.com/v2/book/:id/collection>\n\n   **DELETE**（`delete`）：从服务器删除资源。 \n   eg. 用户删除某篇笔记 `DELETE` <http://api.douban.com/v2/book/annotation/:id>\n\n5. 如果记录数量很多，服务器不可能都将它们返回给用户。API应该提供参数，过滤返回结果\n\n   ?limit=10：指定返回记录的数量\n   eg. 获取图书信息 `GET` <http://api.douban.com/v2/book/:id>`?limit=10`\n\n6. 服务器向用户返回的状态码和提示信息 \n   每个状态码代表不同意思, 就像代号一样\n\n   2系 代表正常返回\n\n   4系 代表数据异常 \n\n   5系 代表服务器异常\n\n\n\n# 二、Spring\n\n## 1. Spring IOC、AOP的理解、实现的原理，以及优点 \n\nSpring的IoC容器是Spring的核心，Spring AOP是spring框架的重要组成部分\n\n### IOC\n\n- **我的理解**\n  - 正常的情况下，比如有一个类，在类里面有方法（不是静态的方法），调用类里面的方法，创建类的对象，使用对象调用方法，创建类对象的过程，需要new出来对象\n  - 通过控制反转，把对象的创建不是通过new方式实现，而是交给Spring配置创建类对象\n  - IOC的意思是控件反转也就是由容器控制程序之间的关系，这也是spring的优点所在，把控件权交给了外部容器，之前的写法，由程序代码直接操控，而现在控制权由应用代码中转到了外部容器，控制权的转移是所谓反转。换句话说之前用new的方式获取对象，现在由spring给你至于怎么给你就是di了。\n\n\n\n- **Spring IOC实现原理** \n  - 创建xml配置文件，配置要创建的对象类\n  - 通过反射创建实例； \n  - 获取需要注入的接口实现类并将其赋值给该接口。 \n\n- **优点**\n\n  - 解耦合，开发更方便组织分工\n  - 高层不依赖于底层（依赖倒置）\n  - 是应用更容易测试\n  - 因为把对象生成放在了XML里定义，所以当我们需要换一个实现子类将会变成很简单（一般这样的对象都是现实于某种接口的），只要修改XML就可以了，这样我们甚至可以实现对象的热插拨\n\n\n\n### AOP\n\n- **我的理解**\n\n  - AOP（Aspect Oriented Programming ）称为面向切面编程，扩展功能不是修改源代码实现，在程序开发中主要用来解决一些系统层面上的问题，比如日志，事务，权限等待，Struts2的拦截器设计就是基于AOP的思想，是个比较经典的例子。\n  - 面向切面编程（aop）是对面向对象编程（oop）的补充 \n  - 面向切面编程提供声明式事务管理\n  - AOP就是典型的代理模式的体现\n\n- **Spring AOP实现原理** \n\n  - 动态代理（利用**反射和动态编译**将代理模式变成动态的） \n\n  - JDK的动态代理\n\n    - JDK内置的Proxy动态代理可以在运行时动态生成字节码，而没必要针对每个类编写代理类\n    - JDKProxy返回动态代理类，是目标类所实现接口的另一个实现版本，它实现了对目标类的代理（如同UserDAOProxy与UserDAOImp的关系） \n\n  - cglib动态代理 \n\n    - CGLibProxy返回的动态代理类，则是目标代理类的一个子类（代理类扩展了UserDaoImpl类） \n    - cglib继承被代理的类，重写方法，织入通知，动态生成字节码并运行\n\n- **优点**\n\n  - 各个步骤之间的良好隔离性 \n  - 源代码无关性\n  - 松耦合\n  - 易扩展\n  - 代码复用\n\n\n\n## 2. 什么是依赖注入，注入的方式有哪些\n\n- DI（依赖注入）\n\n  - 所谓依赖注入，就是把底层类作为参数传入上层类，实现上层类对下层类的控制。DI依赖注入，向类里面属性注入值 ，依赖注入不能单独存在，需要在IOC基础上完成操作。\n\n- - 使用set方法注入 \n  - 使用有参构造注入 \n  - 使用接口注入\n  - 注解注入(@Autowire) \n\n\n## 3. Spring IOC初始化过程\n\n <div align=\"center\"> <img src=\"assets/bean-init2.png\" width=\"\"/></div><br/>\n\n IOC容器的初始化分为三个过程实现：\n\n- 第一个过程是Resource资源定位。这个Resouce指的是BeanDefinition的资源定位。这个过程就是容器找数据的过程，就像水桶装水需要先找到水一样。\n- 第二个过程是BeanDefinition的载入过程。这个载入过程是把用户定义好的Bean表示成Ioc容器内部的数据结构，而这个容器内部的数据结构就是BeanDefition。\n- 第三个过程是向IOC容器注册这些BeanDefinition的过程，这个过程就是将前面的BeanDefition保存到HashMap中的过程。\n\n\n\n更详细说明请阅读：[2 IOC容器初始化过程 - CSDN博客](https://blog.csdn.net/u010723709/article/details/47046211)\n\n\n\n 参考资料：\n\n- [Spring IOC容器的初始化过程 - 掘金](https://juejin.im/post/5af8f8066fb9a07ac85a853e)\n- [Spring IOC核心源码学习 | Yikun](https://yikun.github.io/2015/05/29/Spring-IOC%E6%A0%B8%E5%BF%83%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0/)\n\n\n\n## 4. 项目中Spring AOP用在什么地方，为什么这么用，切点，织入，通知用自己的话描述一下\n\n- **Joinpoint（连接点）（重要）**\n  - 类里面可以被增强的方法，这些方法称为连接点\n- **Pointcut（切入点）（重要）**\n  - 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义\n- **Advice（通知/增强）（重要）**\n  - 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知，后置通知，异常通知，最终通知，环绕通知（切面要完成的功能）\n- **Aspect（切面）**：\n  - 是切入点和通知（引介）的结合\n- **Introduction（引介）**\n  - 引介是一种特殊的通知在不修改类代码的前提下， Introduction可以在运行期为类动态地添加一些方法或Field.\n- **Target（目标对象）**\n  - 代理的目标对象（要增强的类）\n- **Weaving（织入）**\n  - 是把增强应用到目标的过程，把advice 应用到 target的过程\n- **Proxy（代理）**\n  - 一个类被AOP织入增强后，就产生一个结果代理类\n\nAOP（Aspect Oriented Programming ）称为面向切面编程，扩展功能不是修改源代码实现，在程序开发中主要用来解决一些系统层面上的问题，比如日志，事务，权限等待，Struts2的拦截器设计就是基于AOP的思想，是个比较经典的例子。\n\n\n\n## 5. AOP动态代理2种实现原理，他们的区别是什么？\n\n- 动态代理与cglib实现的区别\n  - JDK动态代理只能对实现了接口的类生成代理，而不能针对类. \n  - cglib是针对类实现代理，主要是对指定的类生成一个子类，覆盖其中的方法因为是继承，所以该类或方法最好不要声明成final。 \n  - JDK代理是不需要以来第三方的库，只要JDK环境就可以进行代理 \n  - cglib必须依赖于cglib的类库，但是它需要类来实现任何接口代理的是指定的类生成一个子类，覆盖其中的方法，是一种继承 \n\n \n\n\n\n## 6. Struts拦截器和Spring AOP区别\n\n\n\nStruts2拦截器浅析-慕课网\nhttps://www.imooc.com/learn/450\n\n\n\n\n\n\n\n## 7. Spring 是如何管理事务的，事务管理机制\n\n事务管理可以帮助我们保证数据的一致性，对应企业的实际应用很重要。\n\nSpring的事务机制包括声明式事务和编程式事务。\n\n- **编程式事务管理**：Spring推荐使用TransactionTemplate，实际开发中使用声明式事务较多。\n- **声明式事务管理**：将我们从复杂的事务处理中解脱出来，获取连接，关闭连接、事务提交、回滚、异常处理等这些操作都不用我们处理了，Spring都会帮我们处理。\n\n**声明式事务管理使用了AOP面向切面编程实现的，本质就是在目标方法执行前后进行拦截。在目标方法执行前加入或创建一个事务，在执行方法执行后，根据实际情况选择提交或是回滚事务**。\n\n### 如何管理的\n\nSpring事务管理主要包括3个接口，Spring的事务主要是由它们(**PlatformTransactionManager，TransactionDefinition，TransactionStatus**)三个共同完成的。\n\n**1. PlatformTransactionManager**：事务管理器–主要用于平台相关事务的管理\n\n主要有三个方法：\n\n- commit 事务提交；\n- rollback 事务回滚；\n- getTransaction 获取事务状态。\n\n**2. TransactionDefinition**：事务定义信息–用来定义事务相关的属性，给事务管理器PlatformTransactionManager使用\n\n这个接口有下面四个主要方法：\n\n- getIsolationLevel：获取隔离级别；\n- getPropagationBehavior：获取传播行为；\n- getTimeout：获取超时时间；\n- isReadOnly：是否只读（保存、更新、删除时属性变为false–可读写，查询时为true–只读）\n\n事务管理器能够根据这个返回值进行优化，这些事务的配置信息，都可以通过配置文件进行配置。\n\n**3. TransactionStatus**：事务具体运行状态–事务管理过程中，每个时间点事务的状态信息。\n\n例如它的几个方法：\n\n- hasSavepoint()：返回这个事务内部是否包含一个保存点，\n- isCompleted()：返回该事务是否已完成，也就是说，是否已经提交或回滚\n- isNewTransaction()：判断当前事务是否是一个新事务\n\n**声明式事务的优缺点**：\n\n- **优点**：不需要在业务逻辑代码中编写事务相关代码，只需要在配置文件配置或使用注解（@Transaction），这种方式没有侵入性。\n- **缺点**：声明式事务的最细粒度作用于方法上，如果像代码块也有事务需求，只能变通下，将代码块变为方法。\n\n<http://blog.csdn.net/jie_liang/article/details/77600742>\n\n\n\n\n\n\n\n## 8. Spring中bean加载机制，生命周期\n\n### 加载机制\n\n【Spring】详解Spring中Bean的加载 - weknow619 - 博客园\nhttps://www.cnblogs.com/weknow619/p/6673667.html\n\n### 生命周期\n\n在传统的Java应用中，bean的生命周期很简单。使用Java关键字new进行bean实例化，然后该bean就可以使用了。一旦该bean不再被使用，则由Java自动进行垃圾回收。\n\n相比之下，Spring容器中的bean的生命周期就显得相对复杂多了。正确理解Spring bean的生命周期非常重要，因为你或许要利用Spring提供的扩展点来自定义bean的创建过程。下图展示了bean装载到Spring应用上下文中的一个典型的生命周期过程。 \n\n<div align=\"center\"> <img src=\"assets/bean-life.png\" width=\"\"/></div><br/>\n\n上图bean在Spring容器中从创建到销毁经历了若干阶段，每一阶段都可以针对Spring如何管理bean进行个性化定制\n\n**正如你所见，在bean准备就绪之前，bean工厂执行了若干启动步骤。我们对上图进行详细描述：**\n\n1. Spring 对 Bean 进行实例化；\n   - 相当于程序中的new Xx()\n2. Spring 将值和 Bean 的引用注入进 Bean 对应的属性中；\n3. **如果Bean实现了 BeanNameAware 接口**，Spring 将 Bean 的 ID 传递给setBeanName()方法\n   - 实现BeanNameAware清主要是为了通过Bean的引用来获得Bean的ID，一般业务中是很少有在Bean的ID的\n\n1. **如果Bean实现了BeanFactoryAware接口**，Spring将调用setBeanDactory(BeanFactory bf)方法并把BeanFactory容器实例作为参数传入。\n   - 实现BeanFactoryAware 主要目的是为了获取Spring容器，如Bean通过Spring容器发布事件等\n\n1. **如果Bean实现了ApplicationContextAwaer接口**，Spring容器将调用setApplicationContext(ApplicationContext ctx)方法，将bean所在的应用上下文的引用传入进来\n   - 作用与BeanFactory类似都是为了获取Spring容器，不同的是Spring容器在调用setApplicationContext方法时会把它自己作为setApplicationContext 的参数传入，而Spring容器在调用setBeanDactory前需要程序员自己指定（注入）setBeanDactory里的参数BeanFactory\n\n1. **如果Bean实现了BeanPostProcess接口**，Spring将调用它们的postProcessBeforeInitialization（预初始化）方法\n   - 作用是在Bean实例创建成功后对进行增强处理，如对Bean进行修改，增加某个功能\n2. **如果Bean实现了InitializingBean接口**，Spring将调用它们的afterPropertiesSet方法，作用与在配置文件中对Bean使用init-method声明初始化的作用一样，都是在Bean的全部属性设置成功后执行的初始化方法。\n3. **如果Bean实现了BeanPostProcess接口**，Spring将调用它们的postProcessAfterInitialization（后初始化）方法\n   - 作用与6的一样，只不过6是在Bean初始化前执行的，而这个是在Bean初始化后执行的，时机不同\n\n1. 经过以上的工作后，Bean将一直驻留在应用上下文中给应用使用，直到应用上下文被销毁\n2. **如果Bean实现了DispostbleBean接口**，Spring将调用它的destory方法，作用与在配置文件中对Bean使用destory-method属性的作用一样，都是在Bean实例销毁前执行的方法。\n\n\n\n## 9. Bean实例化的三种方式\n\n- 使用类的无参构造创建（此种方式用的最多）\n- 使用静态工厂创建对象\n- 使用实例工厂创建对象\n\n\n\n## 10. BeanFactory 和 FactoryBean的区别\n\n- **BeanFactory**是个Factory，也就是IOC容器或对象工厂，在Spring中，所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的，提供了实例化对象和拿对象的功能。\n- **FactoryBean**是个Bean，这个Bean不是简单的Bean，而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似。\n\n\n\n\n\n## 11. BeanFactory和ApplicationContext的区别\n\n### BeanFactory\n\n是Spring里面最低层的接口，提供了最简单的容器的功能，只提供了实例化对象和拿对象的功能。\n\n### 两者装载bean的区别\n\n- **BeanFactory**：在启动的时候不会去实例化Bean，中有从容器中拿Bean的时候才会去实例化；\n- **ApplicationContext**：在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化；\n\n### 我们该用BeanFactory还是ApplicationContent\n\n**BeanFactory** 延迟实例化的优点：\n\n应用启动的时候占用资源很少，对资源要求较高的应用，比较有优势；\n\n缺点：速度会相对来说慢一些。而且有可能会出现空指针异常的错误，而且通过bean工厂创建的bean生命周期会简单一些\n\n**ApplicationContext** 不延迟实例化的优点：\n\n- 所有的Bean在启动的时候都加载，系统运行的速度快；\n- 在启动的时候所有的Bean都加载了，我们就能在系统启动的时候，尽早的发现系统中的配置问题\n- 建议web应用，在启动的时候就把所有的Bean都加载了。\n\n缺点：把费时的操作放到系统启动中完成，所有的对象都可以预加载，缺点就是消耗服务器的内存\n\n### ApplicationContext其他特点\n\n除了提供BeanFactory所支持的所有功能外，ApplicationContext还有额外的功能\n\n- 默认初始化所有的Singleton，也可以通过配置取消预初始化。\n- 继承MessageSource，因此支持国际化。\n- 资源访问，比如访问URL和文件（ResourceLoader）；\n- 事件机制，（有继承关系）上下文 ，使得每一个上下文都专注于一个特定的层次，比如应用的web层；\n- 同时加载多个配置文件。\n- 消息发送、响应机制（ApplicationEventPublisher）；\n- 以声明式方式启动并创建Spring容器。\n\n由于ApplicationContext会预先初始化所有的Singleton Bean，于是在系统创建前期会有较大的系统开销，但一旦ApplicationContext初始化完成，程序后面获取Singleton Bean实例时候将有较好的性能。\n\n也可以为bean设置lazy-init属性为true，即Spring容器将不会预先初始化该bean。\n\n### spring的AOP（常用的是拦截器）\n\n一般拦截器都是实现HandlerInterceptor，其中有三个方法preHandle、postHandle、afterCompletion\n\n1. preHandle：执行controller之前执行\n2. postHandle：执行完controller，return modelAndView之前执行，主要操作modelAndView的值\n3. afterCompletion：controller返回后执行\n\n### spring载入多个上下文\n\n不同项目使用不同分模块策略，spring配置文件分为\n\n- applicationContext.xml(主文件，包括JDBC配置，hibernate.cfg.xml，与所有的Service与DAO基类)\n- applicationContext-cache.xml(cache策略，包括hibernate的配置)\n- applicationContext-jmx.xml(JMX，调试hibernate的cache性能)\n- applicationContext-security.xml(acegi安全)\n- applicationContext-transaction.xml(事务)\n- moduleName-Service.xml\n- moduleName-dao.xml\n\n\n\n## 12. ApplicationContext 上下文的生命周期\n\nPS：可以借鉴Servlet的生命周期，实例化、初始init、接收请求service、销毁destroy;\n\nSpring上下文中的Bean也类似，【Spring上下文的生命周期】\n\n1. 实例化一个Bean，也就是我们通常说的new；\n2. 按照Spring上下文对实例化的Bean进行配置，也就是IOC注入\n3. 如果这个Bean实现了BeanNameAware接口，会调用它实现的setBeanName(String beanId)方法，此处传递的是Spring配置文件中Bean的ID；\n4. 如果这个Bean实现了BeanFactoryAware接口，会调用它实现的setBeanFactory()，传递的是Spring工厂本身（可以用这个方法获取到其他Bean）；\n5. 如果这个Bean实现了ApplicationContextAware接口，会调用setApplicationContext(ApplicationContext)方法，传入Spring上下文，该方式同样可以实现步骤4，但比4更好，以为ApplicationContext是BeanFactory的子接口，有更多的实现方法；\n6. 如果这个Bean关联了BeanPostProcessor接口，将会调用postProcessBeforeInitialization(Object obj, String s)方法，BeanPostProcessor经常被用作是Bean内容的更改，并且由于这个是在Bean初始化结束时调用After方法，也可用于内存或缓存技术；\n7. 如果这个Bean在Spring配置文件中配置了init-method属性会自动调用其配置的初始化方法；\n8. 如果这个Bean关联了BeanPostProcessor接口，将会调用postAfterInitialization(Object obj, String s)方法；\n\n注意：以上工作完成以后就可以用这个Bean了，那这个Bean是一个single的，所以一般情况下我们调用同一个ID的Bean会是在内容地址相同的实例\n\n1. 当Bean不再需要时，会经过清理阶段，如果Bean实现了DisposableBean接口，会调用其实现的destroy方法\n2. 最后，如果这个Bean的Spring配置中配置了destroy-method属性，会自动调用其配置的销毁方法\n\n以上10步骤可以作为面试或者笔试的模板，另外这里描述的是应用Spring上下文Bean的生命周期，如果应用Spring的工厂也就是BeanFactory的话去掉第5步就Ok了；\n\n\n\n\n\n## 13. Spring中autowire和resourse关键字的区别\n\n@Resource和@Autowired都是做bean的注入时使用，其实@Resource并不是Spring的注解，它的包是javax.annotation.Resource，需要导入，但是Spring支持该注解的注入。 \n\n1、共同点\n\n两者都可以写在字段和setter方法上。两者如果都写在字段上，那么就不需要再写setter方法。\n\n2、不同点\n\n**（1）@Autowired**\n\n@Autowired为Spring提供的注解，需要导入包org.springframework.beans.factory.annotation.Autowired;只按照byType注入。\n\n```java\npublic class TestServiceImpl {\n    // 下面两种@Autowired只要使用一种即可\n    @Autowired\n    private UserDao userDao; // 用于字段上\n    \n    @Autowired\n    public void setUserDao(UserDao userDao) { // 用于属性的方法上\n        this.userDao = userDao;\n    }\n}\n```\n\n@Autowired注解是按照类型（byType）装配依赖对象，默认情况下它要求依赖对象必须存在，如果允许null值，可以设置它的required属性为false。如果我们想使用按照名称（byName）来装配，可以结合@Qualifier注解一起使用。如下：\n\n```java\npublic class TestServiceImpl {\n    @Autowired\n    @Qualifier(\"userDao\")\n    private UserDao userDao; \n}\n```\n\n\n\n**（2）@Resource**\n\n@Resource默认按照ByName自动注入，由J2EE提供，需要导入包javax.annotation.Resource。@Resource有两个重要的属性：name和type，而Spring将@Resource注解的name属性解析为bean的名字，而type属性则解析为bean的类型。所以，如果使用name属性，则使用byName的自动注入策略，而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性，这时将通过反射机制使用byName自动注入策略。\n\n```javascript\npublic class TestServiceImpl {\n    // 下面两种@Resource只要使用一种即可\n    @Resource(name=\"userDao\")\n    private UserDao userDao; // 用于字段上\n    \n    @Resource(name=\"userDao\")\n    public void setUserDao(UserDao userDao) { // 用于属性的setter方法上\n        this.userDao = userDao;\n    }\n}\n```\n\n注：最好是将@Resource放在setter方法上，因为这样更符合面向对象的思想，通过set、get去操作属性，而不是直接去操作属性。\n\n@Resource装配顺序：\n\n1. 如果同时指定了name和type，则从Spring上下文中找到唯一匹配的bean进行装配，找不到则抛出异常。\n\n2. 如果指定了name，则从上下文中查找名称（id）匹配的bean进行装配，找不到则抛出异常。\n\n3. 如果指定了type，则从上下文中找到类似匹配的唯一bean进行装配，找不到或是找到多个，都会抛出异常。\n4. 如果既没有指定name，又没有指定type，则自动按照byName方式进行装配；如果没有匹配，则回退为一个原始类型进行匹配，如果匹配则自动装配。\n\n@Resource的作用相当于@Autowired，只不过@Autowired按照byType自动注入。\n\n\n\n\n\n## 14. Spring的注解讲一下，介绍Spring中的熟悉的注解\n\n> 思考：spring怎么知道应该哪些Java类当初bean类处理？ \n>\n> 答案：使用配置文件或者注解的方式进行标识需要处理的java类! \n\n### 一： 组件类注解\n\n@Component ：标准一个普通的spring Bean类。 \n@Repository：标注一个DAO组件类。 \n@Service：标注一个业务逻辑组件类。 \n@Controller：标注一个控制器组件类。 \n\n这些都是注解在平时的开发过程中出镜率极高，@Component、@Repository、@Service、@Controller实质上属于同一类注解，用法相同，功能相同，区别在于标识组件的类型。@Component可以代替@Repository、@Service、@Controller，因为这三个注解是被@Component标注的。如下代码\n\n```java\n@Target({ElementType.TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Component\npublic @interface Controller {\n    String value() default \"\";\n}\n```\n\n举例：\n\n（1）当一个组件代表数据访问层（DAO）的时候，我们使用@Repository进行注解，如下\n\n```java\n@Repository\npublic class HappyDaoImpl implements HappyDao{\nprivate final static Logger LOGGER = LoggerFactory.getLogger(HappyDaoImpl .class);\npublic void  club(){\n        //do something ,like drinking and singing\n    }\n}1234567\n```\n\n（2）当一个组件代表业务层时，我们使用@Service进行注解，如下\n\n```java\n@Service(value=\"goodClubService\")\n//使用@Service注解不加value ,默认名称是clubService\npublic class ClubServiceImpl implements ClubService {\n    @Autowired\n    private ClubDao clubDao;\n\n    public void doHappy(){\n        //do some Happy\n    }\n }12345678910\n```\n\n（3）当一个组件作为前端交互的控制层，使用@Controller进行注解，如下\n\n```java\n@Controller\npublic class HappyController {\n    @Autowired //下面进行讲解\n    private ClubService clubService;\n\n    // Control the people entering the Club\n    // do something\n}\n/*Controller相关的注解下面进行详细讲解，这里简单引入@Controller*/\n```\n\n**3、总结注意点**\n\n1. 被注解的java类当做Bean实例，Bean实例的名称默认是Bean类的首字母小写，其他部分不变。@Service也可以自定义Bean名称，但是必须是唯一的！ \n2. 尽量使用对应组件注解的类替换@Component注解，在spring未来的版本中，@Controller，@Service，@Repository会携带更多语义。并且便于开发和维护！ \n3. 指定了某些类可作为Spring Bean类使用后，最好还需要让spring搜索指定路径，在Spring配置文件加入如下配置： \n\n```xml\n<!-- 自动扫描指定包及其子包下的所有Bean类 -->\n<context:component-scan base-package=\"org.springframework.*\"/>\n```\n\n\n\n### 二：装配bean时常用的注解\n\n@Autowired：属于Spring 的org.springframework.beans.factory.annotation包下,可用于为类的属性、构造器、方法进行注值 \n@Resource：不属于spring的注解，而是来自于JSR-250位于java.annotation包下，使用该annotation为目标bean指定协作者Bean。 \n\n...\n\n\n\n更详细请转向：[Spring常用注解介绍【经典总结】 - CSDN博客](https://blog.csdn.net/u010648555/article/details/76299467)\n\n\n\n\n\n\n\n## 15. Spring 中用到了那些设计模式？\n\nSpring框架中使用到了大量的设计模式，下面列举了比较有代表性的：\n\n- 代理模式—在AOP和remoting中被用的比较多。\n- 单例模式—在spring配置文件中定义的bean默认为单例模式。\n- 模板方法—用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。\n- 工厂模式—BeanFactory用来创建对象的实例。\n- 适配器–spring aop\n- 装饰器–spring data hashmapper\n- 观察者– spring 时间驱动模型\n- 回调–Spring ResourceLoaderAware回调接口\n\n### 工厂模式（Factory Method）\n\nSpring容器就是实例化和管理Bean的工厂\n\n工厂模式隐藏了创建类的细节，返回值必定是接口或者抽象类,而不是具体的某个对象，工厂类根据条件生成不同的子类实例。当得到子类的实例后，就可以调用基类中的方法，不必考虑返回的是哪一个子类的实例。\n\n这个很明显，在各种BeanFactory以及ApplicationContext创建中都用到了；\n\n**Spring通过配置文件，就可以管理所有的bean，而这些bean就是Spring工厂能产生的实例，因此，首先我们在Spring配置文件中对两个实例进行配置**。\n\n### 单态模式【单例模式】（Singleton）\n\n**Spring默认将所有的Bean设置成 单例模式，即对所有的相同id的Bean的请求，都将返回同一个共享的Bean实例。这样就可以大大降低Java创建对象和销毁时的系统开销**。\n\n使用Spring将Bean设置称为单例行为，则无需自己完成单例模式。\n\n| 可以通过singleton=“true | false” 或者 scope=“？”来指定 |\n\n\n### 适配器（Adapter）\n\n**在Spring的Aop中，使用的Advice（通知）来增强被代理类的功能。Spring实现这一AOP功能的原理就使用代理模式（1、JDK动态代理。2、CGLib字节码生成技术代理。）对类进行方法级别的切面增强，即，生成被代理类的代理类， 并在代理类的方法前，设置拦截器，通过执行拦截器重的内容增强了代理方法的功能，实现的面向切面编程**。\n\n### 代理（Proxy）\n\nSpring实现了一种能够通过额外的方法调用完成任务的设计模式 - 代理设计模式,比如JdkDynamicAopProxy和Cglib2AopProxy。\n\n代理设计模式的一个很好的例子是**org.springframework.aop.framework.ProxyFactoryBean**。**该工厂根据Spring bean构建AOP代理。该类实现了定义getObject()方法的FactoryBean接口。此方法用于将需求Bean的实例返回给bean factory**。在这种情况下，它不是返回的实例，而是AOP代理。在执行代理对象的方法之前，可以通过调用补充方法来进一步“修饰”代理对象(其实所谓的静态代理不过是在装饰模式上加了个要不要你来干动作行为而已，而不是装饰模式什么也不做就加了件衣服，其他还得由你来全权完成)。\n\n### 观察者（Observer）\n\n定义对象间的一种一对多的依赖关系，当一个对象的状态发生改变时，所有依赖于它的对象都得到通知并被自动更新。**spring中Observer模式常用的地方是listener的实现。如ApplicationListener**。\n\n\n\n补充面试题：Spring里面的工厂模式和代理模式，IO中的装饰者模式，挑几个最熟的能讲讲思路和伪代码实现？\n\n\n\n\n\n## 16. Spring 的优点有哪些\n\n1. 降低了组件之间的耦合性 ，实现了软件各层之间的解耦 \n2. 可以使用容易提供的众多服务，如事务管理，消息服务等 \n3. 容器提供单例模式支持 \n4. 容器提供了AOP技术，利用它很容易实现如权限拦截，运行期监控等功能 \n5. 容器提供了众多的辅助类，能加快应用的开发 \n6. spring对于主流的应用框架提供了集成支持，如hibernate，JPA，Struts等 \n7. spring属于低侵入式设计，代码的污染极低 \n8. 独立于各种应用服务器 \n9. spring的DI机制降低了业务对象替换的复杂性 \n10. Spring的高度开放性，并不强制应用完全依赖于Spring，开发者可以自由选择spring的部分或全部 \n\n\n\n## 17. IOC和AOP用到的设计模式\n\n用过spring的朋友都知道spring的强大和高深，都觉得深不可测，其实当你真正花些时间读一读源码就知道它的一些技术实现其实是建立在一些最基本的技术之上而已；例如AOP(面向方面编程)的实现是建立在CGLib提供的类代理和jdk提供的接口代理，IOC(控制反转)的实现建立在工厂模式、Java反射机制和jdk的操作XML的DOM解析方式.\n\n\n\n\n\n# 二、SpringMVC\n\n## 1. Spring MVC的工作原理\n\nSpring MVC 的工作原理如下图：  \n\n![img](assets/Spring-mvc-framework-1536053968817.png)\n\n  \n\n- ① 客户端的所有请求都交给前端控制器DispatcherServlet来处理，它会负责调用系统的其他模块来真正处理用户的请求。  \n- ② DispatcherServlet收到请求后，将根据请求的信息（包括URL、HTTP协议方法、请求头、请求参数、Cookie等）以及HandlerMapping的配置找到处理该请求的Handler（任何一个对象都可以作为请求的Handler）。  \n- ③ 在这个地方Spring会通过HandlerAdapter对该处理进行封装。  \n- ④ HandlerAdapter是一个适配器，它用统一的接口对各种Handler中的方法进行调用。  \n- ⑤ Handler完成对用户请求的处理后，会返回一个ModelAndView对象给DispatcherServlet，ModelAndView顾名思义，包含了数据模型以及相应的视图的信息。  \n- ⑥ ModelAndView的视图是逻辑视图，DispatcherServlet还要借助ViewResolver完成从逻辑视图到真实视图对象的解析工作。  \n- ⑦ 当得到真正的视图对象后，DispatcherServlet会利用视图对象对模型数据进行渲染。  \n- ⑧ 客户端得到响应，可能是一个普通的HTML页面，也可以是XML或JSON字符串，还可以是一张图片或者一个PDF文件。 \n\n\n\n**组件及其作用**\n\n1. 前端控制器 (DispatcherServlet)\n\n   接收请求，响应结果，相当于转发器，中央处理器。负责调用系统的其他模块来真正处理用户的请求。 \n\n   有了DispatcherServlet减少了其他组件之间的耦合度\n\n2. 处理器映射器 (HandlerMapping)\n\n   作用：根据请求的 url 查找 Handler\n\n3. **处理器 (Handler)**\n\n   注意：编写 Handler 时按照 HandlerAdapter 的要求去做，这样适配器才可以去正确执行 Handler\n\n4. 处理器适配器 (HandlerAdapter)\n\n   作用：按照特定规则（HandlerAdapter要求的规则）执行Handler。\n\n5. 视图解析器 (ViewResolver)\n\n   作用：进行视图解析，根据逻辑视图解析成真正的视图 (View)\n\n6. **视图 (View)**\n\n   View 是一个接口实现类支持不同的 View 类型（jsp,pdf等等）\n\n注意：只需要程序员开发，处理器和视图。\n\n\n\n## 2. Spring MVC注解的优点\n\n\n\n\n\n# 三、Hibernate\n\n## 1. 简述Hibernate常见优化策略。\n\n- 制定合理的缓存策略（二级缓存、查询缓存）。  \n- 采用合理的Session管理机制。  \n- 尽量使用延迟加载特性。  \n- 设定合理的批处理参数。  \n- 如果可以，选用UUID作为主键生成器。  \n- 如果可以，选用基于版本号的乐观锁替代悲观锁。  \n- 在开发过程中, 开启hibernate.show_sql选项查看生成的SQL，从而了解底层的状况；开发完成后关闭此选项。  \n- 考虑数据库本身的优化，合理的索引、恰当的数据分区策略等都会对持久层的性能带来可观的提升，但这些需要专业的DBA（数据库管理员）提供支持。 \n\n\n\n## 2. Hibernate一级缓存与二级缓存之间的区别\n\n- Hibernate的Session提供了一级缓存的功能，默认总是有效的，当应用程序保存持久化实体、修改持久化实体时，Session并不会立即把这种改变提交到数据库，而是缓存在当前的Session中，除非显示调用了Session的flush()方法或通过close()方法关闭Session。通过一级缓存，可以减少程序与数据库的交互，从而提高数据库访问性能。  \n- SessionFactory级别的二级缓存是全局性的，所有的Session可以共享这个二级缓存。不过二级缓存默认是关闭的，需要显示开启并指定需要使用哪种二级缓存实现类（可以使用第三方提供的实现）。一旦开启了二级缓存并设置了需要使用二级缓存的实体类，SessionFactory就会缓存访问过的该实体类的每个对象，除非缓存的数据超出了指定的缓存空间。  \n- 一级缓存和二级缓存都是对整个实体进行缓存，不会缓存普通属性，如果希望对普通属性进行缓存，可以使用查询缓存。查询缓存是将HQL或SQL语句以及它们的查询结果作为键值对进行缓存，对于同样的查询可以直接从缓存中获取数据。查询缓存默认也是关闭的，需要显示开启。 \n\n\n\n## 3. Hibernate的理解\n\n\n\n# 四、MyBatis\n\n## 1. Mybatis原理\n\n## 2. Hibernate了解吗，Mybatis和Hibernate的区别\n\n\n\n# 五、Tomcat\n\n## 1. tomcat加载基本流程，涉及到的参数\n\n\n\n\n\n\n\n# 附录：参考资料\n\n\n\n参考资料：\n\n- [Java-Guide/Spring学习与面试.md at master · Snailclimb/Java-Guide](https://github.com/Snailclimb/Java-Guide/blob/master/%E4%B8%BB%E6%B5%81%E6%A1%86%E6%9E%B6/Spring%E5%AD%A6%E4%B9%A0%E4%B8%8E%E9%9D%A2%E8%AF%95.md)\n- [biezhi/java-bible: 我的技术摘要](https://github.com/biezhi/java-bible)\n- [Spring 常见的一些面试题整理 搜云库](https://www.souyunku.com/2018/03/21/spring/)\n\n参考面经：\n\n- [阿里Java研发工程师实习面经_笔经面经_牛客网](https://www.nowcoder.com/discuss/72899?type=2&order=3&pos=509&page=1)\n\n- [网易面经（Java开发岗） - Andya - 博客园](https://www.cnblogs.com/Andya/p/7456511.html )\n\n\n\n"
  },
  {
    "path": "notes/JavaArchitecture/08-系统架构.md",
    "content": "## tomcat集群怎么保证同步 \n\n\n\n\n\n## 布隆过滤器\n\n\n\n\n\n（其他）\n\n\n\n1. 常用的hash算法有哪些？ \n\n   -  加法Hash；把输入元素一个一个的加起来构成最后的结果 \n   -  位运算Hash；这类型Hash函数通过利用各种位运算（常见的是移位和异或）来充分的混合输入元素 \n   -  乘法Hash；这种类型的Hash函数利用了乘法的不相关性（乘法的这种性质，最有名的莫过于平方取头尾的随机数生成算法，虽然这种算法效果并不好）；jdk5.0里面的String类的hashCode()方法也使用乘法Hash；32位FNV算法 \n   -  除法Hash；除法和乘法一样，同样具有表面上看起来的不相关性。不过，因为除法太慢，这种方式几乎找不到真正的应用 \n   -  查表Hash；查表Hash最有名的例子莫过于CRC系列算法。虽然CRC系列算法本身并不是查表，但是，查表是它的一种最快的实现方式。查表Hash中有名的例子有：Universal Hashing和Zobrist Hashing。他们的表格都是随机生成的。 \n   -  混合Hash；混合Hash算法利用了以上各种方式。各种常见的Hash算法，比如MD5、Tiger都属于这个范围。它们一般很少在面向查找的Hash函数里面使用 \n\n    \n\n2. 如何设计存储海量数据的存储系统 \n\n3. 缓存的实现原理，设计缓存要注意什么 \n\n4. 什么是一致性哈希？用来解决什么问题？ \n\n在设计分布式cache系统的时候，我们需要让key的分布均衡，并且在增加cache server后，cache的迁移做到最少。 \n\n \n\n1. 现在有一个进程挂起了，如何用工具查出原因？  \n\n   - 通过 Javacore 了解线程运行状况 \n\n   javacore，也可以称为“threaddump”或是“javadump”，它是 Java 提供的一种诊断特性，能够提供一份可读的当前运行的 JVM 中线程使用情况的快照。即在某个特定时刻，JVM 中有哪些线程在运行，每个线程执行到哪一个类，哪一个方法。 \n\n   应用程序如果出现不可恢复的错误或是内存泄露，就会自动触发 Javacore 的生成。而为了性能问题诊断的需要，我们也会主动触发生成 Javacore。在 AIX、Linux、Solaris 环境中，我们通常使用 kill -3 <PID> 产生该进程的 Javacore。IBM Java6 中产生 Javacore 的详细方法可以参考文章 [1]。 \n\n    \n\n2. 你知道的开源协议有哪些？  \n\n   - Mozilla Public License： MPL License，允许免费重发布、免费修改，但要求修改后的代码版权归软件的发起者。这种授权维护了商业软件的利益，它要求基于这种软件得修改无偿贡献版权给该软件。这样，围绕该软件得所有代码得版权都集中在发起开发人得手中。但MPL是允许修改，无偿使用得。MPL软件对链接没有要求。 \n   - BSD开源协议：给于使用者很大自由的协议。可以自由的使用，修改源代码，也可以将修改后的代码作为开源或者专有软件再发布。 \n   - Apache Licence 2.0 ：Apache Licence是著名的非盈利开源组织Apache采用的协议。该协议和BSD类似，同样鼓励代码共享和尊重原作者的著作权，同样允许代码修改，再发布(作为开源或商业软件)。 \n   - GPL：GPL许可证是自由软件的应用最广泛的软件许可证，人们可以修改程式的一个或几个副本或程式的任何部分，以此形成基於这些程式的衍生作品。必须在修改过的档案中附有明显的说明：您修改了此一档案及任何修改的日期。 您必须让您发布或出版的作品，包括本程式的全部或一部分，或内含本程式的全部或部分所衍生的作品，允许第三方在此许可证条款下使用，并且不得因为此项授权行为而收费。 \n   - LGPL：LGPL是GPL的一个为主要为类库使用设计的开源协议。和GPL要求任何使用/修改/衍生之GPL类库的的软件必须采用GPL协议不同。LGPL允许商业软件通过类库引用(link)方式使用LGPL类库而不需要开源商业软件的代码。这使得采用LGPL协议的开源代码可以被商业软件作为类库引用并发布和销售。 \n   - Public Domain：公共域授权。将软件授权为公共域，这些软件包没有授权协议，任何人都可以随意使用它 \n\n3. 你知道的开源软件有哪些？  \n\n   - JDK \n   - Eclipse  \n   - Tomcat \n   - Spring \n   - Hibernate \n   - MySQL "
  },
  {
    "path": "notes/JavaArchitecture/Overview.md",
    "content": "\n\n## 概述\n\n为了更加系统的学习，参考各类书籍和课程，以下将通过下列8方面展开，对Java技术栈进行总结。\n\n\n\n##### 目录结构\n\n1. ##### Java 基础\n\n   - 基本概念\n   - 基本类型与运算\n   - 字符串与数组\n   - 异常处理\n\n2. ##### Java 集合框架\n\n3. ##### Java 并发与线程\n\n   - 多线程\n   - 并发包\n\n4. ##### Java IO流\n\n   - NIO\n   - AIO\n   - BIO\n\n5. ##### Java 虚拟机\n\n6. ##### 设计模式（常见8种）\n\n   - 单例模式\n   - 工厂模式\n   - 观察者模式\n   - 适配器模式\n   - 模仿方法模式\n   - 策略模式\n   - 责任链模式\n   - 装饰者模式\n\n7. ##### Web框架\n\n8. ##### 系统架构\n\n   - Nginx\n   - Docker\n   - 负载均衡\n\n\n\n\n\n\n\n蒋蒋蒋蒋大仙的个人空间 - 哔哩哔哩 ( ゜- ゜)つロ 乾杯~ Bilibili\nhttps://space.bilibili.com/231379623/#/"
  },
  {
    "path": "notes/JavaWeb/Hibernate.md",
    "content": "> hibernate 美 /'haɪbɚnet/ vi. 过冬；（动物）冬眠；（人等）避寒\n\n\n\n\n\n"
  },
  {
    "path": "notes/JavaWeb/Mybatis.md",
    "content": "# 前言\n\n\n\n\n\n参考仓库：\n\n- [springmvc-mybatis-learning](https://github.com/brianway/springmvc-mybatis-learning)\n\n1. Spring、SpringMVC原理、流程\n\n1. Mybatis原理\n2. Hibernate了解吗，Mybatis和Hibernate的区别"
  },
  {
    "path": "notes/JavaWeb/Spring.md",
    "content": "\n\n[TOC]\n\n<!-- TOC -->\n\n- [前言](#前言)\n- [一、基础概念](#一基础概念)\n    - [1. JavaBean](#1-javabean)\n    - [2. Bean](#2-bean)\n    - [3. 传统Javabean与Spring中的bean的区别](#3-传统javabean与spring中的bean的区别)\n    - [4. POJO](#4-pojo)\n- [二、Spring核心技术](#二spring核心技术)\n    - [1. IOC（控制反转）](#1-ioc控制反转)\n        - [1.1 什么是IOC](#11-什么是ioc)\n        - [1.2 IoC能做什么](#12-ioc能做什么)\n        - [1.3 IoC和DI](#13-ioc和di)\n        - [1.4 IOC底层原理 (降低类之间的耦合度)](#14-ioc底层原理-降低类之间的耦合度)\n        - [1.5 Spring中怎么用](#15-spring中怎么用)\n            - [（1）配置文件方式](#1配置文件方式)\n            - [（2）注解方式](#2注解方式)\n    - [2. DI（依赖注入）](#2-di依赖注入)\n        - [2.1 什么是依赖注入](#21-什么是依赖注入)\n        - [2.2 为什么使用依赖注入](#22-为什么使用依赖注入)\n        - [2.3 为什么要实现松耦合](#23-为什么要实现松耦合)\n        - [2.4 IOC和DI区别](#24-ioc和di区别)\n        - [2.5 依赖注入方式](#25-依赖注入方式)\n            - [（1）使用set方法注入](#1使用set方法注入)\n            - [（2）使用有参构造注入](#2使用有参构造注入)\n            - [（3）注入对象类型属性](#3注入对象类型属性)\n            - [（4）p名称空间注入](#4p名称空间注入)\n            - [（5）注入复杂类型属性](#5注入复杂类型属性)\n    - [3. AOP（面向切面编程）](#3-aop面向切面编程)\n        - [3.1 什么是AOP](#31-什么是aop)\n        - [3.2 底层原理](#32-底层原理)\n            - [第一种 JDK 自带的动态代理技术](#第一种-jdk-自带的动态代理技术)\n            - [第二种 CGLIB(CodeGenerationLibrary)是一个开源项目](#第二种-cglibcodegenerationlibrary是一个开源项目)\n        - [3.3 AOP操作术语](#33-aop操作术语)\n        - [3.4 Spring的AOP操作](#34-spring的aop操作)\n            - [（1）AOP准备操作](#1aop准备操作)\n            - [（2）使用表达式配置切入点](#2使用表达式配置切入点)\n        - [3.5 使用xml实现AOP](#35-使用xml实现aop)\n        - [3.6 使用注解实现AOP](#36-使用注解实现aop)\n        - [3.7 为什么需要代理模式？](#37-为什么需要代理模式)\n        - [3.8 静态代理](#38-静态代理)\n        - [3.9 动态代理，使用JDK内置的Proxy实现](#39-动态代理使用jdk内置的proxy实现)\n        - [3.10 动态代理，使用cglib实现](#310-动态代理使用cglib实现)\n    - [4. Spring Ioc容器](#4-spring-ioc容器)\n        - [4.1 Bean作用域](#41-bean作用域)\n        - [4.2 Bean 的生命周期](#42-bean-的生命周期)\n        - [4.3 BeanFactory 和ApplicationContext（Bean工厂和应用上下文）](#43-beanfactory-和applicationcontextbean工厂和应用上下文)\n\n<!-- /TOC -->\n\n# 前言\n\n为了更好的深入学习Spring核心技术，在这里整理了Spring相关的常见核心知识，和面试知识点，本文将通过浅显易懂的语言和代码实现，相信可以在最短的时间内进行巩固学习。\n\n\n\n本文参考：\n\n- [理解并实现一个IOC容器](https://github.com/biezhi/java-bible/blob/master/ioc/index.md)\n- [【源码实现】Java-Guide/Spring学习与面试.md at master · Snailclimb/Java-Guide](https://github.com/Snailclimb/Java-Guide/blob/master/%E4%B8%BB%E6%B5%81%E6%A1%86%E6%9E%B6/Spring%E5%AD%A6%E4%B9%A0%E4%B8%8E%E9%9D%A2%E8%AF%95.md)\n- [一起来谈谈 Spring AOP！ - 掘金](https://juejin.im/post/5aa7818af265da23844040c6)\n- [黑马程序员Spring2016学习笔记](https://github.com/Only-lezi/spring-learning/tree/master/spring-learning-article)\n- [Spring学习总结（二）——静态代理、JDK与CGLIB动态代理、AOP+IoC - 张果 - 博客园](http://www.cnblogs.com/best/p/5679656.html)\n- [Spring IoC有什么好处呢？ - 知乎](https://www.zhihu.com/question/23277575/answer/169698662)\n- [极客学院Spring Wiki](http://wiki.jikexueyuan.com/project/spring/transaction-management.html) \n- [【必读】Spring W3Cschool教程](https://www.w3cschool.cn/wkspring/f6pk1ic8.html) \n- [【必读】Java新手如何学习Spring、Struts、Hibernate三大框架？ - 知乎，深度好文](https://www.zhihu.com/question/21142149)\n  \n\n\n\n个人的使用体会\n\n- **spring**：核心提供依赖注入\n- **spring mvc**： mvc框架\n- **spring boot**：说不太出什么感觉，跟mvc比就感觉配置少了好多\n- **spring cloud**：围绕微服务的一套东西 \n\n\n\nfrom 2018/7/27\n\n\n\n\n\n# 一、基础概念\n\n## 1. JavaBean\n\n**JavaBean是一种组件技术**，就好像你做了一个扳子，而这个扳子会在很多地方被拿去用，这个扳子也提供多种功能(你可以拿这个扳子扳、锤、撬等等)，而这个扳子就是一个组件。\n\n　**JavaBean是一个遵循特定写法的Java类**，它通常具有如下特点：\n\n- 这个Java类必须具有一个无参的构造函数\n- 属性必须私有化。\n- 私有化的属性必须通过public类型的方法暴露给其它程序，并且方法的命名也必须遵守一定的命名规范。\n- 这个类应是可序列化的。（比如可以实现Serializable 接口，用于实现bean的持久性）\n\n许多开发者把JavaBean看作遵从特定命名约定的POJO。 简而言之，当一个POJO可序列化，有一个无参的构造函数，使用getter和setter方法来访问属性时，他就是一个JavaBean。  \n\n```java\npackage gacl.javabean.study;\n\n/**\n * @author gacl\n * Person类就是一个最简单的JavaBean\n */\npublic class Person {\n\n    //Person类封装的私有属性\n    // 姓名 String类型\n    private String name;\n    // 性别 String类型\n    private String sex;\n    // 年龄 int类型\n    private int age;\n  \n   \n    /**\n     * 无参数构造方法\n     */\n    public Person() {\n        \n    }\n    \n    //Person类对外提供的用于访问私有属性的public方法\n    public String getName() {\n        return name;\n    }\n    public void setName(String name) {\n        this.name = name;\n    }\n    public String getSex() {\n        return sex;\n    }\n    public void setSex(String sex) {\n        this.sex = sex;\n    }\n    public int getAge() {\n        return age;\n    }\n    public void setAge(int age) {\n        this.age = age;\n    }\n}\n```\n\nJavaBean在J2EE开发中，通常用于封装数据，对于遵循以上写法的JavaBean组件，其它程序可以通过反射技术实例化JavaBean对象，并且通过反射那些遵守命名规范的方法，从而获知JavaBean的属性，进而调用其属性保存数据。 \n\n\n\n## 2. Bean\n\n- **Bean的中文含义是“豆子”，Bean的含义是可重复使用的Java组件**。所谓组件就是一个由可以自行进行内部管理的一个或几个类所组成、外界不了解其内部信息和运行方式的群体。使用它的对象只能通过接口来操作。\n- Bean并不需要继承特别的基类(BaseClass)或实现特定的接口(Interface)。Bean的编写规范使Bean的容器(Container)能够分析一个Java类文件，并将其方法(Methods)翻译成属性(Properties)，即把Java类作为一个Bean类使用。Bean的编写规范包括Bean类的构造方法、定义属性和访问方法编写规则。\n- Java Bean是基于Java的组件模型，由**属性、方法和事件**3部分组成。在该模型中，JavaBean可以被修改或与其他组件结合以生成新组件或完整的程序。它是一种Java类，通过封装成为具有某种功能或者处理某个业务的对象。因此，也可以通过嵌在JSP页面内的Java代码访问Bean及其属性。\n\n\n\n## 3. 传统Javabean与Spring中的bean的区别\n\nJavabean已经没人用了\n\nspringbean可以说是javabean的发展, 但已经完全不是一回事儿了\n\n \n\n**用处不同：**传统javabean更多地作为值传递参数，而spring中的bean用处几乎无处不在，任何组件都可以被称为bean。\n\n**写法不同：**传统javabean作为值对象，要求每个属性都提供getter和setter方法；但spring中的bean只需为接受设值注入的属性提供setter方法。\n\n**生命周期不同：**传统javabean作为值对象传递，不接受任何容器管理其生命周期；spring中的bean有spring管理其生命周期行为。\n\n所有可以被spring容器实例化并管理的java类都可以称为bean。\n\n原来服务器处理页面返回的值都是直接使用request对象，后来增加了javabean来管理对象，所有页面值只要是和javabean对应，就可以用类.GET属性方法来获取值。javabean不只可以传参数，也可以处理数据，相当与把一个服务器执行的类放到了页面上，使对象管理相对不那么乱（对比asp的时候所有内容都在页面上完成）。\n\nspring中的bean，是通过配置文件、javaconfig等的设置，有spring自动实例化，用完后自动销毁的对象。让我们只需要在用的时候使用对象就可以，不用考虑如果创建类对象（这就是spring的注入）。一般是用在服务器端代码的执行上。\n\n\n\n## 4. POJO\n\nPOJO 和JavaBean是我们常见的两个关键字，一般容易混淆，POJO全称是Plain Ordinary Java Object / Pure Old Java Object，中文可以翻译成：普通Java类，**具有一部分getter/setter方法的那种类就可以称作POJO**，但是JavaBean则比 POJO复杂很多， Java Bean 是可复用的组件，对 Java Bean 并没有严格的规范，理论上讲，任何一个 Java 类都可以是一个 Bean 。但通常情况下，由于 Java Bean 是被容器所创建（如 Tomcat) 的，所以 Java Bean 应具有一个无参的构造器，另外，通常 Java Bean 还要实现 Serializable 接口用于实现 Bean 的持久性。 Java Bean 是不能被跨进程访问的  \n\n\n\n一般在web应用程序中建立一个数据库的映射对象时，我们只能称它为POJO。\nPOJO(Plain Old Java Object)这个名字用来强调它是一个普通java对象，而不是一个特殊的对象。\n2005年11月时，“POJO”主要用来指代那些没用遵从特定的Java对象模型，约定或框架如EJB的Java对象.\n理想地讲，一个POJO是一个不受任何限制的Java对象（除了Java语言规范）。例如一个POJO不应该是\n\n      1. 扩展预定的类，如  public class Foo extends javax.servlet.http.HttpServlet { ...\n    \n      2. 实现预定的接口，如  public class Bar implements javax.ejb.EntityBean { ...\n    \n      3. 包含预定的标注，如  @javax.ejb.Entity public class Baz{ ...\n  然后，因为技术上的困难及其他原因，许多兼容POJO风格的软件产品或框架事实上仍然要求使用预定的标注，譬如用于更方便的持久化。\n\n\n\n\n\n# 二、Spring核心技术\n\n## 1. IOC（控制反转）\n\n### 1.1 什么是IOC\n\nIoC(Inversion of Control)，意为控制反转，不是什么技术，而是一种设计思想。Ioc意味着**将你设计好的对象交给容器控制，而不是传统的在你的对象内部直接控制**。\n\n如何理解好Ioc呢？理解好Ioc的关键是要明确“谁控制谁，控制什么，为何是反转（有反转就应该有正转了），哪些方面反转了”，那我们来深入分析一下：\n\n- **谁控制谁，控制什么**：传统Java SE程序设计，我们直接在对象内部通过new进行创建对象，是程序主动去创建依赖对象；而IoC是有专门一个容器来创建这些对象，即由Ioc容器来控制对 象的创建；谁控制谁？当然是IoC 容器控制了对象；控制什么？那就是主要控制了外部资源获取（不只是对象包括比如文件等）。\n- **为何是反转，哪些方面反转了**：有反转就有正转，传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象，也就是正转；而反转则是由容器来帮忙创建及注入依赖对象；为何是反转？因为由容器帮我们查找及注入依赖对象，对象只是被动的接受依赖对象，所以是反转；哪些方面反转了？依赖对象的获取被反转了。\n\n**简单来说**\n\n> 正转：比如有一个类，在类里面有方法（不是静态的方法），调用类里面的方法，创建类的对象，使用对象调用方法，创建类对象的过程，需要new出来对象\n>\n> 反转：把对象的创建不是通过new方式实现，而是交给Spring配置创建类对象\n\n\n\n【必读】IOC深度理解，请转向：[深入浅出IOC](../JavaWeb/深入浅出IOC.md)\n\n\n\n\n### 1.2 IoC能做什么\n\nIoC 不是一种技术，只是一种思想，一个重要的面向对象编程的法则，它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象，从而导致类与类之间高耦合，难于测试；有了IoC容器后，把创建和查找依赖对象的控制权交给了容器，由容器进行注入组合对象，所以对象与对象之间是松散耦合，这样也方便测试，利于功能复用，更重要的是使得程序的整个体系结构变得非常灵活。\n\n其实IoC对编程带来的最大改变不是从代码上，而是从思想上，发生了“主从换位”的变化。应用程序原本是老大，要获取什么资源都是主动出击，但是在IoC/DI思想中，应用程序就变成被动的了，被动的等待IoC容器来创建并注入它所需要的资源了。\n\nIoC很好的体现了面向对象设计法则之一—— 好莱坞法则：“别找我们，我们找你”；即由IoC容器帮对象找相应的依赖对象并注入，而不是由对象主动去找。\n\n### 1.3 IoC和DI\n\n**DI—Dependency Injection，即“依赖注入”**：组件之间依赖关系由容器在运行期决定，形象的说，即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能，而是为了提升组件重用的频率，并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制，我们只需要通过简单的配置，而无需任何代码就可指定目标需要的资源，完成自身的业务逻辑，而不需要关心具体的资源来自何处，由谁实现。\n\n理解DI的关键是：“**谁依赖谁，为什么需要依赖，谁注入谁，注入了什么**”，那我们来深入分析一下：\n\n- **谁依赖于谁：** 当然是应用程序依赖于IoC容器；\n- **为什么需要依赖：** 应用程序需要IoC容器来提供对象需要的外部资源；\n- **谁注入谁：** 很明显是IoC容器注入应用程序某个对象，应用程序依赖的对象；\n- **注入了什么：** 就是注入某个对象所需要的外部资源（包括对象、资源、常量数据）。\n\nIoC和DI由什么关系呢？其实它们是同一个概念的不同角度描述，由于控制反转概念比较含糊（可能只是理解为容器控制对象这一个层面，很难让人想到谁来维护对象关系），所以2004年大师级人物Martin Fowler又给出了一个新的名字：“依赖注入”，相对IoC 而言，**“依赖注入”**明确描述了**“被注入对象依赖IoC容器配置依赖对象”**。\n\n对于Spring Ioc这个核心概念，我相信每一个学习Spring的人都会有自己的理解。这种概念上的理解没有绝对的标准答案，仁者见仁智者见智。 理解了IoC和DI的概念后，一切都将变得简单明了，剩下的工作只是在框架中堆积木而已，下一节来看看Spring是怎么用的\n\n\n\n### 1.4 IOC底层原理 (降低类之间的耦合度)\n\n- 底层原理使用技术\n  - xml配置文件\n  - dom4j解决xml\n  - 工厂设计模式\n  - 反射\n- 原理\n\n```java\n//伪代码\n//需要实例化的类\npublic class UserService{\n}\n\npublic class UserServlet{\n    //得到UserService的对象\n    //原始的做法：new 对象();  来创建\n    \n    //经过spring后\n    UserFactory.getService();   //(下面两步的代码调用的)\n}\n```\n\n**第一步：创建xml配置文件，配置要创建的对象类**\n\n```xml\n<bean id=\"userService\" class=\"cn.blinkit.UserService\"/>\n```\n\n**第二步：创建工厂类，使用dom4j解析配置文件+反射**\n\n```java\npublic class Factory {\n    //返回UserService对象的方法\n    public static UserService getService() {\n        //1.使用dom4j来解析xml文件  \n        //根据id值userService，得到id值对应的class属性值\n        String classValue = \"class属性值\";\n        //2.使用反射来创建类对象\n        Class clazz = Class.forName(classValue)；\n        //创建类的对象\n        UserService service = clazz.newInstance();\n        return service;\n    }\n}\n```\n\n \n\n超详细原理讲解：[java-bible/4.principle.md at master · biezhi/java-bible](https://github.com/biezhi/java-bible/blob/master/ioc/4.principle.md)\n\n\n<div align=\"center\"> <img src=\"../pics/spring-ioc.png\" width=\"\"/></div><br/>\n\n\n\n### 1.5 Spring中怎么用\n\n#### （1）配置文件方式\n\n我们在Spring中是这样获取对象的：\n\n```java\npublic static void main(String[] args) {   \n\tApplicationContext context = new FileSystemXmlApplicationContext(\"applicationContext.xml\");   \n\tLol lol = (Lol) context.getBean(\"lol\");   \n\tlol.gank(); \n}\n```\n\n一起看看Spring如何让它生效呢，在 `applicationContext.xml` 配置文件中是酱紫的：\n\n```xml\n<bean id=\"lol\" class=\"com.biezhi.test.Lol\">\n\t<property name=\"name\" value=\"剑圣\" />   \n</bean>  \n```\n\n`Lol` 类是这样的：\n\n```java\npublic class Lol {\n\n\tprivate String name;\n\t\n\tpublic Lol() {\n\t}\n\t\n\tpublic void gank(){\n\t\tSystem.out.println(this.name + \"在gank!!\");\n\t}\n\t\n\tpublic String getName() {\n\t\treturn name;\n\t}\n\n\tpublic void setName(String name) {\n\t\tthis.name = name;\n\t}\n}\n```\n\n上面的代码运行结果自然是 `剑圣在gank!!`。\n\n#### （2）注解方式\n\nSpring更高级的用法，在3.0版本之后有了基于Annotation的注入实现，为毛每次都要配置 `Xml` 看到都蛋疼。。\n\n首先还是要在 `xml` 中配置启用注解方式\n\n```xml\n<context:annotation-config/>  \n```\n\n这样就能使用注解驱动依赖注入了，下面是一个使用场景\n\n```java\npublic class Lol {\n\n\t@Autowired\n\tprivate DuangService duangService ;\n\t\n\tpublic void buyDuang(String name, int money) {\n\t\tduangService.buy(name, money);\n\t}\n}\n```\n\n```java\n@Service(\"duangService\")\npublic class DuangService {\n\t\n\tpublic void buy(String name, int money){\n\t\tif(money > 0){\n\t\t\tSystem.out.println(name + \"买了\" + money + \"毛钱的特效，装逼成功！\");\n\t\t} else{\n\t\t\tSystem.out.println(name + \"没钱还想装逼，真是匪夷所思\");\n\t\t}\n\t}\n}\n```\n\n这只是一个简单的例子，剑圣打野的时候想要买5毛钱的三杀特效，嗯。。虽然不符合逻辑\n\n此时 `DuangService` 已经注入到 `Lol` 对象中，运行代码的结果（这里是例子，代码不能运行的）就是：\n\n```java\n德玛买了5毛钱的特效，装逼成功！\n```\n\n\n\n## 2. DI（依赖注入）\n\n### 2.1 什么是依赖注入\n\n在依赖注入的模式下，创建被调用者得工作不再由调用者来完成，创建被调用者实例的工作通常由Spring容器完成，然后注入调用者。**创建对象时，向类里的属性设置值**\n\n\n\n### 2.2 为什么使用依赖注入\n\n为了实现代码/模块之间松耦合。\n\n\n\n### 2.3 为什么要实现松耦合\n\n上层调用下层，上层依赖于下层，当下层剧烈变动时上层也要跟着变动，这就会导致模块的复用性降低而且大大提高了开发的成本。\n\n一般情况下抽象的变化概率很小，让用户程序依赖于抽象，实现的细节也依赖于抽象。即使实现细节不断变动，只要抽象不变，客户程序就不需要变化。这大大降低了客户程序与实现细节的耦合度。\n\n\n\n### 2.4 IOC和DI区别\n\n1. IOC控制反转，把对象创建交给Spring配置 \n2. DI依赖注入，向类里面属性注入值 \n3. 关系，依赖注入不能单独存在，需要在IOC基础上完成操作\n\n\n\n### 2.5 依赖注入方式\n\n1. 使用set方法注入 \n\n2. 使用有参构造注入 \n\n3. 使用接口注入\n\n说明：Spring框架中支持前两种方式\n\n\n\n#### （1）使用set方法注入\n\n```xml\n<bean id=\"person\" class=\"cn.wang.property.Person\">\n<!--set方法注入属性\n    name属性值：类中定义的属性名称\n    value属性值：设置具体的值\n-->\n        <property name=\"pname\" value=\"zs\"></property>\n</bean>1234567\n```\n\n#### （2）使用有参构造注入\n\n```java\npublic class Person {\n    private String pname;\n\n    public void setPname(String pname) {\n        this.pname = pname;\n    }\n}\n```\n\n```xml\n<bean id=\"user\" class=\"cn.wang.ioc.User\">\n        <!--构造方法注入属性-->\n        <constructor-arg name=\"pname\" value=\"Tony\"></constructor-arg>\n</bean>\n```\n\n#### （3）注入对象类型属性 \n\n- 创建service和dao类，在service中得到dao \n\n具体实现过程 \n\n- 在service中把dao作为属性，生成dao的set方法\n\n```java\npublic class UserService {\n    // 1.定义UserDao类型属性\n    private UserDao userDao;\n\n    // 2.生成set方法\n    public void setUserDao(UserDao userDao) {\n        this.userDao = userDao;\n    }\n}\n```\n\n1. 配置文件注入关系\n\n```java\n<bean id=\"userDao\" class=\"cn.wang.property.UserDao\">\n        <property name=\"name\" value=\"Tom\"></property>\n    </bean>\n    <bean id=\"userService\" class=\"cn.wang.property.UserService\">\n        <!--name属性值：UserService类里的属性名称-->\n        <!--ref属性:UserDao类配置bean标签中的id值-->\n        <property name=\"userDao\" ref=\"userDao\"></property>\n    </bean>12345678\n```\n\n#### （4）p名称空间注入 \n\n![p名称空间注入](../pics/ioc-p1.png)\n\n![p名称空间注入](../pics/ioc-p2.png)\n\n#### （5）注入复杂类型属性\n\n```java\n<!-- 注入复杂类型属性值 -->\n    <!-- \n    String pname;\n    String[] arrs;\n    List<String> list;\n    Map<String, String> map;\n    Properties props; \n    -->\n    <bean id=\"person\" class=\"cn.wang.property.Person\">\n        <property name=\"pname\" value=\"zs\"></property>\n        <property name=\"arrs\">\n            <list>\n                <value>aaa</value>\n                <value>bbb</value>\n                <value>ccc</value>\n            </list>\n        </property>\n        <property name=\"list\">\n            <list>\n                <value>qqq</value>\n                <value>www</value>\n                <value>eee</value>\n            </list>\n        </property>\n        <property name=\"map\">\n            <map>\n                <entry key=\"001\" value=\"Tom\"></entry>\n                <entry key=\"002\" value=\"Amy\"></entry>\n                <entry key=\"003\" value=\"Jim\"></entry>\n            </map>\n        </property>\n        <property name=\"props\">\n            <props>\n                <prop key=\"username\">admin</prop>\n                <prop key=\"passwprd\">admin</prop>\n            </props>\n        </property>\n    </bean>\n```\n\n\n\n## 3. AOP（面向切面编程）\n\n\n\n<div align=\"center\"> <img src=\"../pics/spring-aop.png\" width=\"650\"/></div><br/>\n\n\n\n### 3.1 什么是AOP\n\nAOP（Aspect Oriented Programming ）称为面向切面编程，扩展功能不是修改源代码实现，在程序开发中主要用来解决一些系统层面上的问题，比如日志，事务，权限等待，Struts2的拦截器设计就是基于AOP的思想，是个比较经典的例子。\n\n- AOP：面向切面编程，扩展功能不修改源代码实现\n- AOP采取**横向抽取机制**，取代了传统**纵向继承**体系重复性代码（性能监视、事务管理、安全检查、缓存）\n\n\n\n**spring的底层采用两种方式进行增强**\n\n         第一：Spring传统AOP 纯java实现，在运行期，对目标对象进行代理，织入增强代码\n    \n         第二：AspectJ第三方开源技术，Spring已经整合AspectJ，提供对AspectJ注解的支持，开发AOP程序 更加容易（企业主流）\n\n### 3.2 底层原理\n\n<div align=\"center\"> <img src=\"../pics/../pics/aop2.png\" width=\"650\"/></div><br/>\n\n\n\n#### 第一种 JDK 自带的动态代理技术\n\nJDK动态代理必须基于接口进行代理\n\n作用：使用代理可以对目标对象进行性能监控（计算运行时间）、安全检查（是否具有权限）、 记录日志等。\n\n注意：必须要有接口才能进行代理，代理后对象必须转换为接口类型\n\n\n\n#### 第二种 CGLIB(CodeGenerationLibrary)是一个开源项目\n\nSpring使用CGlib 进行AOP代理， hibernate 也是支持CGlib（默认使用 javassist ）需要下载cglib 的jar包（Spring 最新版本3.2 内部已经集成了cglib ，**无需下载cglib的jar** ）\n\n**作用：可以为目标类，动态创建子类，对目标类方法进行代理（无需接口）**\n\n原理：Spring AOP 底层，会判断用户是根据接口代理还是目标类代理，如果针对接口代理就使用JDK代理，如果针对目标类代理就使用Cglib代理。\n\n<div align=\"center\"> <img src=\"../pics/../pics/aop1.png\" width=\"650\"/></div><br/>\n\n\n\n\n\n### 3.3 AOP操作术语\n\n以下面代码为例：\n\n```java\npublic class User {\n    public void add() {...}\n    public void delete() {...}\n    public void update() {...}\n    public void query() {...}\n}\n```\n\n- **Joinpoint（连接点）（重要）**\n  - 类里面可以被增强的方法，这些方法称为连接点\n\n- **Pointcut（切入点）（重要）**\n  - 所谓切入点是指我们要对哪些 Joinpoint  进行拦截的定义\n\n- **Advice（通知/增强）（重要）**\n\n  - 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。通知分为**前置通知，后置通知，异常通知，最终通知，环绕通知**（方法之前和方法之后）\n\n- **Aspect（切面）**：\n  - 把增强应用到具体方法上面，过程成为切面。\n\n- **Introduction（引介）**\n  - 引介是一种特殊的通知在不修改类代码的前提下， Introduction 可以在运行期为类动态地添加一些方法或 Field。\n\n- **Target（目标对象）**\n  - 代理的目标对象（要增强的类）\n\n- **Weaving（织入）**\n  - 是把增强应用到目标的过程，把 advice 应用到 target的过程\n\n- **Proxy（代理）**\n  - 一个类被 AOP 织入增强后，就产生一个结果代理类\n\n\n\n### 3.4 Spring的AOP操作\n\n- 在Spring里面进行Aop操作，使用aspectj实现\n  - aspectj不是Spring的一部分，和Spring一起使用进行Aop操作 \n  - Spring2.0以后新增了对aspectj的支持\n\n- 使用aspectj实现aop有两种方式\n  - 基于aspectj的xml配置\n  - 基于aspectj的注解方式\n\n#### （1）AOP准备操作\n\n1、除了导入基本的jar包之外，还需要导入aop相关的jar包：\n\n```\naopalliance-1.0.jar\naspectjweaver-1.8.7.jar\nspring-aspects-5.0.4.RELEASE.jar\nspring-aop-5.0.4.RELEASE.jar\n```\n\n2、创建Spring核心配置文件 \n除了引入了约束spring-beans之外还需要引入新约束spring-aop\n\n```xml\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<beans xmlns=\"http://www.springframework.org/schema/beans\"\n       xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n       xsi:schemaLocation=\"\n        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd\n        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-aop.xsd\">\n\n</beans>12345678\n```\n\n#### （2）使用表达式配置切入点\n\n1. 切入点：实际增强的方法\n2. 常用的表达式\n   execution(<访问修饰符>? <返回类型> <方法名>(<参数>)<异常>)\n   （1）对包内的add方法进行增强\n   `execution(* cn.blinkit.aop.Book.add(..))`\n   （2）* 是对类里面的所有方法进行增强\n   `execution(* cn.blinkit.aop.Book.*(..))`\n   （3）*.* 是所有的类中的方法进行增强\n   `execution(* *.*(..))`\n   （4）匹配所有save开头的方法\n   `execution(* save*(..))`\n\n\n\n### 3.5 使用xml实现AOP\n\n**aop配置代码：** Book\n\n```java\npublic class Book {\n    public void add() {\n        System.out.println(\"add......\");\n    }\n}\n```\n\nMyBook\n\n```java\npublic class MyBook {\n    public void before1() {\n        System.out.println(\"前置增强......\");\n    }\n\n    public void after1() {\n        System.out.println(\"后置增强......\");\n    }\n\n    //环绕通知\n    public void around1(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {\n        //方法之前\n        System.out.println(\"方法之前.....\");\n\n        //执行被增强的方法\n        proceedingJoinPoint.proceed();\n\n        //方法之后\n        System.out.println(\"方法之后......\");\n\n    }\n}\n```\n\nxml配置\n\n```xml\n    <!--1. 配置对象-->\n    <bean id=\"book\" class=\"cn.blinkit.aop.Book\"></bean>\n    <bean id=\"myBook\" class=\"cn.blinkit.aop.MyBook\"></bean>\n\n    <!--2. 配置aop操作-->\n    <aop:config>\n        <!--2.1 配置切入点-->\n        <aop:pointcut id=\"pointcut1\" expression=\"execution(* cn.blinkit.aop.Book.*(..))\"></aop:pointcut>\n\n        <!--2.2 配置切面\n                把增强用到方法上面\n        -->\n        <aop:aspect ref=\"myBook\">\n            <!--\n                aop:before   :前置通知\n                aop:after    :后置通知\n                aop:around   :环绕通知\n                配置增强类型\n                method : 增强类里面使用哪个方法作为前置\n            -->\n            <aop:before method=\"before1\" pointcut-ref=\"pointcut1\"></aop:before>\n            <aop:after method=\"after1\" pointcut-ref=\"pointcut1\"></aop:after>\n            <aop:around method=\"around1\" pointcut-ref=\"pointcut1\"></aop:around>\n        </aop:aspect>\n    </aop:config>\n```\n\n测试代码\n\n```java\npublic class AOPTest {\n    @Test\n    public void testBook() {\n        ApplicationContext context = new ClassPathXmlApplicationContext(\"cn/blinkit/aop/spring-aop.xml\");\n        Book book = (Book) context.getBean(\"book\");\n        book.add();\n\n    }\n}\n```\n\n\n\n### 3.6 使用注解实现AOP\n\n1. 创建对象\n   (1)创建Book和MyBook **（增强类）** 对象\n\n2. 在spring核心配置文件中，开启aop操作\n   具体操作见xml配置文件代码：\n\n   ```xml\n   <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n   <beans xmlns=\"http://www.springframework.org/schema/beans\"\n          xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n          xmlns:aop=\"http://www.springframework.org/schema/aop\"\n          xsi:schemaLocation=\"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd\n           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd\">\n   \n       <!-- 1.开启aop操作 -->\n       <aop:aspectj-autoproxy></aop:aspectj-autoproxy>\n   \n       <!-- 2.配置对象 -->\n       <bean id=\"book\" class=\"com.jxs.aspectj.Book\"></bean>\n       <bean id=\"nbBook\" class=\"com.jxs.aspectj.NBBook\"></bean>\n   \n   </beans>\n   ```\n\n3. 在增强类上面使用注解完成aop操作\n   （1）类上面加上`@Aspect`\n   （2）方法上面加上\n   `@Before(value = \"execution(* cn.blinkit.aop.anno.Book.*(..))\")`\n   `@After(value = \"表达式\")`\n   `@Around(value = \"表达式\")`等...\n\n4. \n\n**Book**\n\n```java\npublic class Book {\n    public void add() {\n        System.out.println(\"add...注解版本...\");\n    }\n}\n```\n\n**MyBook增强类**\n\n```java\n@Aspect\npublic class MyBook {\n    //在方法上面使用注解完成增强配置\n    @Before(value = \"execution(* cn.blinkit.aop.anno.Book.*(..))\")\n    public void before1() {\n        System.out.println(\"前置增强...注解版本...\");\n    }\n\n    @After(value = \"execution(* cn.blinkit.aop.anno.Book.*(..))\")\n    public void after1() {\n        System.out.println(\"后置增强...注解版本...\");\n    }\n\n    //环绕通知\n    @Around(value = \"execution(* cn.blinkit.aop.anno.Book.*(..))\")\n    public void around1(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {\n        //方法之前\n        System.out.println(\"方法之前...注解版本...\");\n\n        //执行被增强的方法\n        proceedingJoinPoint.proceed();\n\n        //方法之后\n        System.out.println(\"方法之后...注解版本...\");\n\n    }\n}\n```\n\n**xml配置**\n\n```xml\n<!--开启aop操作-->\n    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>\n\n    <!--创建对象-->\n    <bean id=\"book\" class=\"cn.blinkit.aop.anno.Book\"></bean>\n    <bean id=\"myBook\" class=\"cn.blinkit.aop.anno.MyBook\"></bean>\n```\n\n\n\n### 3.7 为什么需要代理模式？\n\n假设需实现一个计算的类Math、完成加、减、乘、除功能，如下所示：  \n\n```java\npackage com.zhangguo.Spring041.aop01;\n\npublic class Math {\n    //加\n    public int add(int n1,int n2){\n        int result=n1+n2;\n        System.out.println(n1+\"+\"+n2+\"=\"+result);\n        return result;\n    }\n    \n    \n    //减\n    public int sub(int n1,int n2){\n        int result=n1-n2;\n        System.out.println(n1+\"-\"+n2+\"=\"+result);\n        return result;\n    }\n    \n    //乘\n    public int mut(int n1,int n2){\n        int result=n1*n2;\n        System.out.println(n1+\"X\"+n2+\"=\"+result);\n        return result;\n    }\n    \n    //除\n    public int div(int n1,int n2){\n        int result=n1/n2;\n        System.out.println(n1+\"/\"+n2+\"=\"+result);\n        return result;\n    }\n}\n```\n\n现在需求发生了变化，要求项目中所有的类在执行方法时输出执行耗时。最直接的办法是修改源代码，如下所示：  \n\n```java\npackage com.zhangguo.Spring041.aop01;\n\nimport java.util.Random;\n\npublic class Math {\n    //加\n    public int add(int n1,int n2){\n        //开始时间\n        long start=System.currentTimeMillis();\n        lazy();\n        int result=n1+n2;\n        System.out.println(n1+\"+\"+n2+\"=\"+result);\n        Long span= System.currentTimeMillis()-start;\n        System.out.println(\"共用时：\"+span);\n        return result;\n    }\n    \n    //减\n    public int sub(int n1,int n2){\n        //开始时间\n        long start=System.currentTimeMillis();\n        lazy();\n        int result=n1-n2;\n        System.out.println(n1+\"-\"+n2+\"=\"+result);\n        Long span= System.currentTimeMillis()-start;\n        System.out.println(\"共用时：\"+span);\n        return result;\n    }\n    \n    //乘\n    public int mut(int n1,int n2){\n        //开始时间\n        long start=System.currentTimeMillis();\n        lazy();\n        int result=n1*n2;\n        System.out.println(n1+\"X\"+n2+\"=\"+result);\n        Long span= System.currentTimeMillis()-start;\n        System.out.println(\"共用时：\"+span);\n        return result;\n    }\n    \n    //除\n    public int div(int n1,int n2){\n        //开始时间\n        long start=System.currentTimeMillis();\n        lazy();\n        int result=n1/n2;\n        System.out.println(n1+\"/\"+n2+\"=\"+result);\n        Long span= System.currentTimeMillis()-start;\n        System.out.println(\"共用时：\"+span);\n        return result;\n    }\n    \n    //模拟延时\n    public void lazy()\n    {\n        try {\n            int n=(int)new Random().nextInt(500);\n            Thread.sleep(n);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n}\n```\n\n\n\n缺点：\n\n1. 工作量特别大，如果项目中有多个类，多个方法，则要修改多次。\n2. 违背了设计原则：开闭原则（OCP），对扩展开放，对修改关闭，而为了增加功能把每个方法都修改了，也不便于维护。\n3. 违背了设计原则：单一职责（SRP），每个方法除了要完成自己本身的功能，还要计算耗时、延时；每一个方法引起它变化的原因就有多种。\n4. 违背了设计原则：依赖倒转（DIP），抽象不应该依赖细节，两者都应该依赖抽象。而在Test类中，Test与Math都是细节。\n\n解决：\n\n- 使用静态代理可以解决部分问题（请往下看...）\n\n\n\n\n\n\n\n### 3.8 静态代理\n\n 1、定义抽象主题接口\n\n```java\npackage com.zhangguo.Spring041.aop02;\n\n/**\n * 接口\n * 抽象主题\n */\npublic interface IMath {\n    //加\n    int add(int n1, int n2);\n\n    //减\n    int sub(int n1, int n2);\n\n    //乘\n    int mut(int n1, int n2);\n\n    //除\n    int div(int n1, int n2);\n\n}\n```\n\n2、主题类，算术类，实现抽象接口\n\n```java\npackage com.zhangguo.Spring041.aop02;\n\n/**\n * 被代理的目标对象\n *真实主题\n */\npublic class Math implements IMath {\n    //加\n    public int add(int n1,int n2){\n        int result=n1+n2;\n        System.out.println(n1+\"+\"+n2+\"=\"+result);\n        return result;\n    }\n    \n    //减\n    public int sub(int n1,int n2){\n        int result=n1-n2;\n        System.out.println(n1+\"-\"+n2+\"=\"+result);\n        return result;\n    }\n    \n    //乘\n    public int mut(int n1,int n2){\n        int result=n1*n2;\n        System.out.println(n1+\"X\"+n2+\"=\"+result);\n        return result;\n    }\n    \n    //除\n    public int div(int n1,int n2){\n        int result=n1/n2;\n        System.out.println(n1+\"/\"+n2+\"=\"+result);\n        return result;\n    }\n}\n```\n\n3、代理类 \n\n```java\npackage com.zhangguo.Spring041.aop02;\n\nimport java.util.Random;\n\n/**\n * 静态代理类\n */\npublic class MathProxy implements IMath {\n\n    //被代理的对象\n    IMath math=new Math();\n    \n    //加\n    public int add(int n1, int n2) {\n        //开始时间\n        long start=System.currentTimeMillis();\n        lazy();\n        int result=math.add(n1, n2);\n        Long span= System.currentTimeMillis()-start;\n        System.out.println(\"共用时：\"+span);\n        return result;\n    }\n\n    //减法\n    public int sub(int n1, int n2) {\n        //开始时间\n        long start=System.currentTimeMillis();\n        lazy();\n        int result=math.sub(n1, n2);\n        Long span= System.currentTimeMillis()-start;\n        System.out.println(\"共用时：\"+span);\n        return result;\n    }\n\n    //乘\n    public int mut(int n1, int n2) {\n        //开始时间\n        long start=System.currentTimeMillis();\n        lazy();\n        int result=math.mut(n1, n2);\n        Long span= System.currentTimeMillis()-start;\n        System.out.println(\"共用时：\"+span);\n        return result;\n    }\n    \n    //除\n    public int div(int n1, int n2) {\n        //开始时间\n        long start=System.currentTimeMillis();\n        lazy();\n        int result=math.div(n1, n2);\n        Long span= System.currentTimeMillis()-start;\n        System.out.println(\"共用时：\"+span);\n        return result;\n    }\n\n    //模拟延时\n    public void lazy()\n    {\n        try {\n            int n=(int)new Random().nextInt(500);\n            Thread.sleep(n);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n}\n```\n\n4、测试运行 \n\n```java\npackage com.zhangguo.Spring041.aop02;\n\npublic class Test {\n    \n    IMath math=new MathProxy();\n    @org.junit.Test\n    public void test01()\n    {\n        int n1=100,n2=5;\n        math.add(n1, n2);\n        math.sub(n1, n2);\n        math.mut(n1, n2);\n        math.div(n1, n2);\n    }\n}\n```\n\n5、小结 \n\n通过静态代理，是否完全解决了上述的4个问题：\n\n**已解决：**\n\n- 解决了“开闭原则（OCP）”的问题，因为并没有修改Math类，而扩展出了MathProxy类。\n\n- 解决了“依赖倒转（DIP）”的问题，通过引入接口。\n\n- 解决了“单一职责（SRP）”的问题，Math类不再需要去计算耗时与延时操作，但从某些方面讲MathProxy还是存在该问题。\n\n**未解决：**\n\n- 如果项目中有多个类，则需要编写多个代理类，工作量大，不好修改，不好维护，不能应对变化。\n\n如果要解决上面的问题，可以使用动态代理。\n\n\n\n\n\n### 3.9 动态代理，使用JDK内置的Proxy实现\n\n只需要一个代理类，而不是针对每个类编写代理类。\n\n在上一个示例中修改代理类MathProxy如下：\n\n```java\npackage com.zhangguo.Spring041.aop03;\n\nimport java.lang.reflect.InvocationHandler;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Proxy;\nimport java.util.Random;\n\n/**\n * 动态代理类\n */\npublic class DynamicProxy implements InvocationHandler {\n\n    //被代理的对象\n    Object targetObject;\n    \n    /**\n     * 获得被代理后的对象\n     * @param object 被代理的对象\n     * @return 代理后的对象\n     */\n    public Object getProxyObject(Object object){\n        this.targetObject=object;\n        return Proxy.newProxyInstance(\n                targetObject.getClass().getClassLoader(), //类加载器\n                targetObject.getClass().getInterfaces(),  //获得被代理对象的所有接口\n                this);  //InvocationHandler对象\n        //loader:一个ClassLoader对象，定义了由哪个ClassLoader对象来生成代理对象进行加载\n        //interfaces:一个Interface对象的数组，表示的是我将要给我需要代理的对象提供一组什么接口，如果我提供了一组接口给它，那么这个代理对象就宣称实现了该接口(多态)，这样我就能调用这组接口中的方法了\n        //h:一个InvocationHandler对象，表示的是当我这个动态代理对象在调用方法的时候，会关联到哪一个InvocationHandler对象上，间接通过invoke来执行\n    }\n    \n    \n    /**\n     * 当用户调用对象中的每个方法时都通过下面的方法执行，方法必须在接口\n     * proxy 被代理后的对象\n     * method 将要被执行的方法信息（反射）\n     * args 执行方法时需要的参数\n     */\n    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {\n        //被织入的内容，开始时间\n        long start=System.currentTimeMillis();\n        lazy();\n        \n        //使用反射在目标对象上调用方法并传入参数\n        Object result=method.invoke(targetObject, args);\n        \n        //被织入的内容，结束时间\n        Long span= System.currentTimeMillis()-start;\n        System.out.println(\"共用时：\"+span);\n        \n        return result;\n    }\n    \n    //模拟延时\n    public void lazy()\n    {\n        try {\n            int n=(int)new Random().nextInt(500);\n            Thread.sleep(n);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n\n}\n```\n\n测试运行：  \n\n```java\npackage com.zhangguo.Spring041.aop03;\n\npublic class Test {\n    \n    //实例化一个MathProxy代理对象\n    //通过getProxyObject方法获得被代理后的对象\n    IMath math=(IMath)new DynamicProxy().getProxyObject(new Math());\n    @org.junit.Test\n    public void test01()\n    {\n        int n1=100,n2=5;\n        math.add(n1, n2);\n        math.sub(n1, n2);\n        math.mut(n1, n2);\n        math.div(n1, n2);\n    }\n    \n    IMessage message=(IMessage) new DynamicProxy().getProxyObject(new Message());\n    @org.junit.Test\n    public void test02()\n    {\n        message.message();\n    }\n}\n```\n\n小结：\n\n JDK内置的Proxy动态代理可以在运行时动态生成字节码，而没必要针对每个类编写代理类。中间主要使用到了一个接口InvocationHandler与Proxy.newProxyInstance静态方法，参数说明如下：\n\n使用内置的Proxy实现动态代理有一个问题：**被代理的类必须实现接口，未实现接口则没办法完成动态代理。**\n\n如果项目中有些类没有实现接口，则不应该为了实现动态代理而刻意去抽出一些没有实例意义的接口，通过cglib可以解决该问题。\n\n\n\n### 3.10 动态代理，使用cglib实现\n\nCGLIB(Code Generation Library)是一个开源项目,是一个强大的，高性能，高质量的Code生成类库，它可以在运行期扩展Java类与实现Java接口，通俗说cglib可以在运行时动态生成字节码。\n\n1、引用cglib，通过maven\n\n2、使用cglib完成动态代理，大概的原理是：cglib继承被代理的类，重写方法，织入通知，动态生成字节码并运行，因为是继承所以final类是没有办法动态代理的。具体实现如下：  \n\n```java\npackage com.zhangguo.Spring041.aop04;\n\nimport java.lang.reflect.Method;\nimport java.util.Random;\n\nimport net.sf.cglib.proxy.Enhancer;\nimport net.sf.cglib.proxy.MethodInterceptor;\nimport net.sf.cglib.proxy.MethodProxy;\n\n/*\n * 动态代理类\n * 实现了一个方法拦截器接口\n */\npublic class DynamicProxy implements MethodInterceptor {\n\n    // 被代理对象\n    Object targetObject;\n\n    //Generate a new class if necessary and uses the specified callbacks (if any) to create a new object instance. \n    //Uses the no-arg constructor of the superclass.\n    //动态生成一个新的类，使用父类的无参构造方法创建一个指定了特定回调的代理实例\n    public Object getProxyObject(Object object) {\n        this.targetObject = object;\n        //增强器，动态代码生成器\n        Enhancer enhancer=new Enhancer();\n        //回调方法\n        enhancer.setCallback(this);\n        //设置生成类的父类类型\n        enhancer.setSuperclass(targetObject.getClass());\n        //动态生成字节码并返回代理对象\n        return enhancer.create();\n    }\n\n    // 拦截方法\n    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {\n        // 被织入的横切内容，开始时间 before\n        long start = System.currentTimeMillis();\n        lazy();\n\n        // 调用方法\n        Object result = methodProxy.invoke(targetObject, args);\n\n        // 被织入的横切内容，结束时间\n        Long span = System.currentTimeMillis() - start;\n        System.out.println(\"共用时：\" + span);\n        \n        return result;\n    }\n\n    // 模拟延时\n    public void lazy() {\n        try {\n            int n = (int) new Random().nextInt(500);\n            Thread.sleep(n);\n        } catch (InterruptedException e) {\n            e.printStackTrace();\n        }\n    }\n\n}\n```\n\n测试运行：  \n\n```java\npackage com.zhangguo.Spring041.aop04;\n\npublic class Test {\n    //实例化一个DynamicProxy代理对象\n    //通过getProxyObject方法获得被代理后的对象\n    Math math=(Math)new DynamicProxy().getProxyObject(new Math());\n    @org.junit.Test\n    public void test01()\n    {\n        int n1=100,n2=5;\n        math.add(n1, n2);\n        math.sub(n1, n2);\n        math.mut(n1, n2);\n        math.div(n1, n2);\n    }\n    //另一个被代理的对象,不再需要重新编辑代理代码\n    Message message=(Message) new DynamicProxy().getProxyObject(new Message());\n    @org.junit.Test\n    public void test02()\n    {\n        message.message();\n    }\n}\n```\n\n**小结**\n\n使用cglib可以实现动态代理，即使被代理的类没有实现接口，但被代理的类必须不是final类。\n\n\n\n\n\n\n\n## 4. Spring Ioc容器\n\n### 4.1 Bean作用域\n\nSpring 框架支持以下五个作用域，如果你使用 web-aware ApplicationContext 时，其中三个是可用的。\n\n| 作用域         | 描述                                                         |\n| -------------- | ------------------------------------------------------------ |\n| singleton      | 在spring IoC容器仅存在一个Bean实例，Bean以单例方式存在，默认值 |\n| prototype      | 每次从容器中调用Bean时，都返回一个新的实例，即每次调用getBean()时，相当于执行newXxxBean() |\n| request        | 每次HTTP请求都会创建一个新的Bean，该作用域仅适用于WebApplicationContext环境 |\n| session        | 同一个HTTP Session共享一个Bean，不同Session使用不同的Bean，仅适用于WebApplicationContext环境 |\n| global-session | 一般用于Portlet应用环境，改作用于仅适用于WebApplicationContext环境 |\n\n\n\n### 4.2 Bean 的生命周期\n\n在传统的Java应用中，bean的生命周期很简单。使用Java关键字new进行bean实例化，然后该bean就可以使用了。一旦该bean不再被使用，则由Java自动进行垃圾回收。\n\n相比之下，Spring容器中的bean的生命周期就显得相对复杂多了。正确理解Spring bean的生命周期非常重要，因为你或许要利用Spring提供的扩展点来自定义bean的创建过程。下图展示了bean装载到Spring应用上下文中的一个典型的生命周期过程。 \n\n<div align=\"center\"> <img src=\"../pics/bean-life.png\" width=\"\"/></div><br/>\n\n上图bean在Spring容器中从创建到销毁经历了若干阶段，每一阶段都可以针对Spring如何管理bean进行个性化定制\n\n**正如你所见，在bean准备就绪之前，bean工厂执行了若干启动步骤。我们对上图进行详细描述：**\n\n1. Spring 对 Bean 进行实例化；\n   - 相当于程序中的new Xx()\n2. Spring 将值和 Bean 的引用注入进 Bean 对应的属性中；\n3. **如果Bean实现了 BeanNameAware 接口**，Spring 将 Bean 的 ID 传递给setBeanName()方法\n   - 实现BeanNameAware清主要是为了通过Bean的引用来获得Bean的ID，一般业务中是很少有在Bean的ID的\n\n4. **如果Bean实现了BeanFactoryAware接口**，Spring将调用setBeanDactory(BeanFactory bf)方法并把BeanFactory容器实例作为参数传入。\n   - 实现BeanFactoryAware 主要目的是为了获取Spring容器，如Bean通过Spring容器发布事件等\n\n5. **如果Bean实现了ApplicationContextAwaer接口**，Spring容器将调用setApplicationContext(ApplicationContext ctx)方法，将bean所在的应用上下文的引用传入进来\n   - 作用与BeanFactory类似都是为了获取Spring容器，不同的是Spring容器在调用setApplicationContext方法时会把它自己作为setApplicationContext 的参数传入，而Spring容器在调用setBeanDactory前需要程序员自己指定（注入）setBeanDactory里的参数BeanFactory\n\n6. **如果Bean实现了BeanPostProcess接口**，Spring将调用它们的postProcessBeforeInitialization（预初始化）方法\n   - 作用是在Bean实例创建成功后对进行增强处理，如对Bean进行修改，增加某个功能\n7. **如果Bean实现了InitializingBean接口**，Spring将调用它们的afterPropertiesSet方法，作用与在配置文件中对Bean使用init-method声明初始化的作用一样，都是在Bean的全部属性设置成功后执行的初始化方法。\n8. **如果Bean实现了BeanPostProcess接口**，Spring将调用它们的postProcessAfterInitialization（后初始化）方法\n   - 作用与6的一样，只不过6是在Bean初始化前执行的，而这个是在Bean初始化后执行的，时机不同\n\n9. 经过以上的工作后，Bean将一直驻留在应用上下文中给应用使用，直到应用上下文被销毁\n10. **如果Bean实现了DispostbleBean接口**，Spring将调用它的destory方法，作用与在配置文件中对Bean使用destory-method属性的作用一样，都是在Bean实例销毁前执行的方法。\n\n\n\n### 4.3 BeanFactory 和ApplicationContext（Bean工厂和应用上下文）\n\nBean 工厂（com.springframework.beans.factory.BeanFactory）是Spring 框架最核心的接口，它提供了高级IoC 的配置机制。\n\n应用上下文（com.springframework.context.ApplicationContext）建立在BeanFactory 基础之上。\n\n几乎所有的应用场合我们都直接使用ApplicationContext 而非底层的BeanFactory。\n\n<div align=\"center\"> <img src=\"../pics/beanfactory.jpg\" width=\"\"/></div><br/>\n\nApplicationContext 的初始化和BeanFactory有一个重大的区别：\n\n- BeanFactory在初始化容器时，并未实例化Bean，直到第一次访问某个Bean 时才实例目标Bean；\n- 而ApplicationContext 则在初始化应用上下文时就实例化所有单实例的Bean 。 \n\n```xml\nApplicationContext ctx = new ClassPathXmlApplicationContext(\"com/baobaotao/context/beans.xml\");\nApplicationContext ctx = new FileSystemXmlApplicationContext(\"com/baobaotao/context/beans.xml\");\nApplicationContext ctx = new ClassPathXmlApplicationContext(new String[]{\"conf/beans1.xml\",\"conf/beans2.xml\"});\n```\n\n参考资料：\n\n- [BeanFactory 和ApplicationContext（Bean工厂和应用上下文） - 品互网络](http://www.pinhuba.com/spring/101250.htm)\n\n\n\n\n\n\n\n"
  },
  {
    "path": "notes/JavaWeb/深入浅出IOC.md",
    "content": "<!-- TOC -->\n\n- [深入浅出IOC](#深入浅出ioc)\n    - [什么是依赖倒置原则](#什么是依赖倒置原则)\n    - [控制反转和依赖注入](#控制反转和依赖注入)\n    - [控制反转的好处](#控制反转的好处)\n    - [总结](#总结)\n\n<!-- /TOC -->\n\n\n\n# 深入浅出IOC\n\n要了解**控制反转( Inversion of Control )**, 我觉得有必要先了解软件设计的一个重要思想：**依赖倒置原则（Dependency Inversion Principle ）**。\n\n\n\n## 什么是依赖倒置原则\n\n假设我们设计一辆汽车：先设计轮子，然后根据轮子大小设计底盘，接着根据底盘设计车身，最后根据车身设计好整个汽车。这里就出现了一个“依赖”关系：汽车依赖车身，车身依赖底盘，底盘依赖轮子。\n\n<div align=\"center\"> <img src=\"../pics/what-is-ioc/ioc1.jpg\" width=\"\"/></div><br/>\n\n这样的设计看起来没问题，但是可维护性却很低。假设设计完工之后，上司却突然说根据市场需求的变动，要我们把车子的轮子设计都改大一码。这下我们就蛋疼了：因为我们是根据轮子的尺寸设计的底盘，轮子的尺寸一改，底盘的设计就得修改；同样因为我们是根据底盘设计的车身，那么车身也得改，同理汽车设计也得改——整个设计几乎都得改！\n\n我们现在换一种思路。我们先设计汽车的大概样子，然后根据汽车的样子来设计车身，根据车身来设计底盘，最后根据底盘来设计轮子。这时候，依赖关系就倒置过来了：轮子依赖底盘， 底盘依赖车身， 车身依赖汽车。\n\n<div align=\"center\"> <img src=\"../pics/what-is-ioc/ioc2.jpg\" width=\"\"/></div><br/>\n\n这时候，上司再说要改动轮子的设计，我们就只需要改动轮子的设计，而不需要动底盘，车身，汽车的设计了。\n\n这就是依赖倒置原则——把原本的高层建筑依赖底层建筑“倒置”过来，变成底层建筑依赖高层建筑。高层建筑决定需要什么，底层去实现这样的需求，但是高层并不用管底层是怎么实现的。这样就不会出现前面的“牵一发动全身”的情况。\n\n\n\n## 控制反转和依赖注入\n\n**控制反转（Inversion of Control）** 就是依赖倒置原则的一种代码设计的思路。具体采用的方法就是所谓的**依赖注入（Dependency Injection）**。其实这些概念初次接触都会感到云里雾里的。说穿了，这几种概念的关系大概如下：\n\n<div align=\"center\"> <img src=\"../pics/what-is-ioc/ioc3.jpg\" width=\"\"/></div><br/>\n\n为了理解这几个概念，我们还是用上面汽车的例子。只不过这次换成代码。我们先定义四个Class，**车，车身，底盘，轮胎**。然后初始化这辆车，最后跑这辆车。代码结构如下：\n\n<div align=\"center\"> <img src=\"../pics/what-is-ioc/ioc4.jpg\" width=\"\"/></div><br/>\n\n这样，就相当于上面第一个例子，上层建筑依赖下层建筑——每一个类的构造函数都直接调用了底层代码的构造函数。假设我们需要改动一下轮胎（Tire）类，把它的尺寸变成动态的，而不是一直都是30。我们需要这样改：\n\n<div align=\"center\"> <img src=\"../pics/what-is-ioc/ioc5.jpg\" width=\"\"/></div><br/>\n\n\n\n由于我们修改了轮胎的定义，为了让整个程序正常运行，我们需要做以下改动：\n\n<div align=\"center\"> <img src=\"../pics/what-is-ioc/ioc6.jpg\" width=\"\"/></div><br/>\n\n\n\n由此我们可以看到，仅仅是为了修改轮胎的构造函数，这种设计却需要**修改整个上层所有类的构造函数**！在软件工程中，**这样的设计几乎是不可维护的**——在实际工程项目中，有的类可能会是几千个类的底层，如果每次修改这个类，我们都要修改所有以它作为依赖的类，那软件的维护成本就太高了。\n\n所以我们需要进行控制反转（IoC），及上层控制下层，而不是下层控制着上层。我们用依赖注入（Dependency Injection）这种方式来实现控制反转。**所谓依赖注入，就是把底层类作为参数传入上层类，实现上层类对下层类的“控制**”。这里我们用**构造方法传递的依赖注入方式**重新写车类的定义：\n\n<div align=\"center\"> <img src=\"../pics/what-is-ioc/ioc7.jpg\" width=\"\"/></div><br/>\n\n这里我们再把轮胎尺寸变成动态的，同样为了让整个系统顺利运行，我们需要做如下修改：\n\n<div align=\"center\"> <img src=\"../pics/what-is-ioc/ioc8.jpg\" width=\"\"/></div><br/>\n\n看到没？这里 **我只需要修改轮胎类就行了，不用修改其他任何上层类。** 这显然是更容易维护的代码。不仅如此，在实际的工程中，这种设计模式还有利于**不同组的协同合作和单元测试：**比如开发这四个类的分别是四个不同的组，那么只要定义好了接口，四个不同的组可以同时进行开发而不相互受限制；而对于单元测试，如果我们要写Car类的单元测试，就只需要Mock（ 模拟）一下Framework类传入Car就行了，而不用把Framework, Bottom, Tire全部new一遍再来构造Car。\n\n这里我们是采用的**构造函数传入**的方式进行的依赖注入。其实还有另外两种方法：**Setter传递**和**接口传递**。这里就不多讲了，核心思路都是一样的，都是为了实现**控制反转**。\n\n<div align=\"center\"> <img src=\"../pics/what-is-ioc/ioc9.jpg\" width=\"\"/></div><br/>\n\n\n\n## 控制反转的好处\n\n看到这里你应该能理解什么控制反转和依赖注入了。那什么是 **控制反转容器(IoC Container)** 呢？其实上面的例子中，对车类进行初始化的那段代码发生的地方，就是控制反转容器。\n\n<div align=\"center\"> <img src=\"../pics/what-is-ioc/ioc10.jpg\" width=\"\"/></div><br/>\n\n显然你也应该观察到了，因为采用了依赖注入，在初始化的过程中就不可避免的会写大量的new。这里IoC容器就解决了这个问题。**这个容器可以自动对你的代码进行初始化，你只需要维护一个Configuration（可以是xml可以是一段代码），而不用每次初始化一辆车都要亲手去写那一大段初始化的代码**。这是引入IoC Container的第一个好处。\n\nIoC Container的第二个好处是：**我们在创建实例的时候不需要了解其中的细节。**在上面的例子中，我们自己手动创建一个车instance时候，是从底层往上层new的：\n\n<div align=\"center\"> <img src=\"../pics/what-is-ioc/ioc11.jpg\" width=\"\"/></div><br/>\n\n这个过程中，我们需要了解整个Car/Framework/Bottom/Tire类构造函数是怎么定义的，才能一步一步new/注入。\n\n而IoC Container在进行这个工作的时候是反过来的，它先从最上层开始往下找依赖关系，到达最底层之后再往上一步一步new（有点像深度优先遍历）：\n\n<div align=\"center\"> <img src=\"../pics/what-is-ioc/ioc12.jpg\" width=\"\"/></div><br/>\n\n这里IoC Container可以直接隐藏具体的创建实例的细节，在我们来看它就像一个工厂：\n\n<div align=\"center\"> <img src=\"../pics/what-is-ioc/ioc13.png\" width=\"\"/></div><br/>\n\n我们就像是工厂的客户。我们只需要向工厂请求一个Car实例，然后它就给我们按照Config创建了一个Car实例。我们完全不用管这个Car实例是怎么一步一步被创建出来。\n\n\n\n## 总结\n\n实际项目中，有的Service Class可能是十年前写的，有几百个类作为它的底层。假设我们新写的一个API需要实例化这个Service，我们总不可能回头去搞清楚这几百个类的构造函数吧？IoC Container的这个特性就很完美的解决了这类问题——**因为这个架构要求你在写class的时候需要写相应的Config文件，所以你要初始化很久以前的Service类的时候，前人都已经写好了Config文件，你直接在需要用的地方注入这个Service就可以了**。这大大增加了项目的可维护性且降低了开发难度。\n\n\n\n这里只是很粗略的讲了一下我自己对IoC和DI的理解。主要的目的是在于**最大限度避免晦涩难懂的专业词汇，用尽量简洁，通俗，直观的例子**来解释这些概念。如果让大家能有一个类似“哦！原来就是这么个玩意嘛！”的印象，我觉得就OK了。想要深入了解的话，可以上网查阅一些更权威的资料。这里推荐一下 [Dependency injection ](https://link.zhihu.com/?target=https%3A//en.wikipedia.org/wiki/Dependency_injection) 和 [Inversion of Control Containers and the Dependency Injection pattern](https://link.zhihu.com/?target=https%3A//martinfowler.com/articles/injection.html) 这两篇文章，讲的很好很详细。"
  },
  {
    "path": "notes/LEARNLIST.md",
    "content": "# 阅读清单与学习课程\n\n在这部分，我将对阅读书籍和学习课程进程推荐，在课程中我将附带官网链接，图书则附京东和豆瓣链接，PDF 下载我会陆续更新。\n\n如果你有更好的学习资料，可以在 [issue#26](https://github.com/frank-lam/fullstack-tutorial/issues/26) 中留言\n\n\n\n程序员的武林秘籍\n\n- 《微服务：从设计到部署》中文版\n- 《重构_改善既有代码的设计》\n- 《研磨设计模式》\n\n\n\n## 一、数据结构与算法\n\n包含数据结构与算法两部分。\n\n### 📖阅读清单\n\n| 书名               | 推荐                               |                  京东                   |                        豆瓣                         |\n| ------------------ | ---------------------------------- | :-------------------------------------: | :-------------------------------------------------: |\n| 《算法4》          | 算法圣经，比《算法导论》更浅显易懂 | [JD](https://item.jd.com/11098789.html) | [douban](https://book.douban.com/subject/19952400/) |\n| 《剑指Offer》      | 校招，面试算法必刷，配合牛客网刷题 | [JD](https://item.jd.com/12163054.html) | [douban](https://book.douban.com/subject/25910559/) |\n| 《程序员面试指南》 | 左神，视频课一般，书是还是可以的   | [JD](https://item.jd.com/11770838.html) | [douban](https://book.douban.com/subject/26638586/) |\n\n\n\n### 📺学习课程\n\n| 课程                                                         | 推荐                           |\n| ------------------------------------------------------------ | ------------------------------ |\n| [【慕课网】刘宇波：玩转数据结构，从入门到进阶](https://coding.imooc.com/class/207.html) | 数据结构从底层到实现，浅显易懂 |\n| [【慕课网】刘宇波：程序员的内功修炼，学好算法与数据结构](https://coding.imooc.com/class/71.html) | 程序员的内功修炼，强烈推荐     |\n| [【慕课网】刘宇波：玩转算法面试 leetcode题库分门别类详细解析](https://coding.imooc.com/class/82.html) | Leetcode 刷题入门，强烈推荐    |\n| [【极客时间】覃超：算法面试通关40讲](https://time.geekbang.org/course/intro/130) | 市面上比较新的课程，推荐       |\n\n\n\n## 二、Java\n\n包含 Java 核心知识和 Java Web 框架。\n\n### 📖阅读清单\n\n| 书名                                                         | 推荐 | 京东 |\n| ------------------------------------------------------------ | ---- | :--: |\n| 《Java 核心技术 卷Ⅰ / Ⅱ》 | Java 爱好者经典必读，需要的时候翻一番 | [JD](https://item.jd.com/12037418.html) |\n| 《Java 编程思想》 | Java 爱好者经典必读，需要的时候翻一番 | [JD](https://item.jd.com/10058164.html) |\n| 《Java 并发编程实战》 | 并发编程经典读物 | [JD](https://item.jd.com/10922250.html) |\n| 《阿里巴巴 Java 开发手册》 | Java 程序员人手一本的读物 | [JD](https://item.jd.com/12284606.html) |\n| 《Java 程序员面试笔试宝典》 | 很好的面试宝典，但是有点老了，本仓库的很多内容也参考了本书。建议直接阅读本仓库 | [JD](https://item.jd.com/11551720.html) |\n| 《深入理解 Java 虚拟机》周志明 | 值得通读几遍的 JVM 刊物 | [JD](https://item.jd.com/11252778.html) |\n| 《Java 网络编程》              | 买了好久，我也还没开始翻 | [JD](https://item.jd.com/11544991.html) |\n\n\n\n### 📺学习课程\n\n| 课程                                                         | 推荐                                                         |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n| [【网易云课堂】Java 开发工程师（Web方向）翁凯](https://mooc.study.163.com/smartSpec/detail/85002.htm?share=1&shareId=1036020973) | 浙大老师，幽默风趣不失内涵，Java 语言启蒙导师。建议只看翁恺部分 |\n| [【廖雪峰】Java 教程](https://www.liaoxuefeng.com/webpage/java) | 廖雪峰就是质量的保证                                         |\n| [【慕课网】Java 零基础入门](https://class.imooc.com/sc/?plan_id=18) | 理论视频讲解 + 编码实战，还行吧                              |\n| [【龙果学院】Java 并发编程原理与实战](https://www.roncoo.com/view/78) | 说不出来什么，但是还是不错的                                 |\n| [【龙果学院】深入理解 Java 虚拟机（jvm性能调优+内存模型+虚拟机原理）](https://www.roncoo.com/view/67) | 在虚拟机课程中看过最好得了！                                 |\n| [【尚学堂】白鹤翔_jvm虚拟机优化](https://www.bilibili.com/video/av39445571?from=search&seid=6036059502839011544) | JVM 浅显入门，可以在 B 站上看                                |\n\n\n\n## 三、Java web\n\n### 📖阅读清单\n\n| 书名               | 推荐 | 京东 |\n| ------------------ | ---- | :--: |\n| 《Spring 实战》 | 深入 Spring 必读 | [JD](https://item.jd.com/11899370.html) |\n| 《深入分析Java Web技术内幕》 | 阿里开源的 Java web 技术丛书 | [JD](https://item.jd.com/11520670.html) |\n| 《Spring MVC+MyBatis开发从入门到项目实战》 | / | [JD](https://item.jd.com/12308496.html) |\n\n\n\n### 📺学习课程\n\n| 课程                                                         | 推荐                            |\n| ------------------------------------------------------------ | ------------------------------- |\n| [【网易云课堂】thinkphp5开发restful-api接口](https://study.163.com/course/courseMain.htm?courseId=1004171002) | 了解 RESTful 设计最短的入门课程 |\n| [【黑马程序员】SSH框架_王泽](https://www.bilibili.com/video/av20967368?from=search&seid=15534785324761577356) | SSH 入门必学，全网最好得课程    |\n| [【黑马程序员】SpringMVC+Mybatis](https://www.bilibili.com/video/av27573870?from=search&seid=14767778023112934631) | SSM 入门必学，全网最好得课程    |\n| [【牛客网】初中高Python+Java项目实战_叶神](https://www.nowcoder.com/courses) | /                               |\n| [【慕课网】Spring Cloud微服务实战_廖师兄](https://coding.imooc.com/class/187.html) | /                               |\n\n\n\n## 四、面向对象与设计模式\n\n### 📖阅读清单\n\n| 书名                        | 推荐                                       |                  京东                   |\n| --------------------------- | ------------------------------------------ | :-------------------------------------: |\n| 《设计模式之禅》            | 极具趣味，容易理解，但讲解又极为严谨和透彻 | [JD](https://item.jd.com/11414555.html) |\n| 《研磨设计模式》            | 程序员的武林秘籍，必读刊物                 |  [JD](https://e.jd.com/30112056.html)   |\n| 《重构_改善既有代码的设计》 | 程序员的武林秘籍，必读刊物，Java 语言编写  | [JD](https://item.jd.com/11728740.html) |\n\n\n\n### 📺学习课程\n\n| 课程                                                         | 推荐                                   |\n| ------------------------------------------------------------ | -------------------------------------- |\n| [【极客学院】极客学院 23 种设计模式](https://www.bilibili.com/video/av29988660?from=search&seid=15497789228752740663) | 大概是目前看到的最好得设计模式入门课程 |\n| [【慕课网】Java 设计模式精讲 Debug 方式+内存分析](https://coding.imooc.com/class/270.html) | /                                      |\n\n\n\n\n\n## 五、数据库\n\n### 📖阅读清单\n\n| 书名            | 推荐 | 京东 |\n| --------------- | ---- | :--: |\n| 《高性能 MySQL》 | MySQL 工具书 | [JD](https://item.jd.com/11220393.html) |\n| 《Redis 实战》  | Redis 入门与实战最佳读物 | [JD](https://item.jd.com/11791607.html) |\n\n\n\n### 📺学习课程\n\n| 课程                                                         | 推荐 |\n| ------------------------------------------------------------ | ---- |\n| [【慕课网】MySQL 性能管理及架构设计](https://coding.imooc.com/class/79.html) | /    |\n| [【慕课网】Redis 从入门到高可用，分布式实践](https://coding.imooc.com/class/151.html) | /    |\n| [【慕课网】MySQL 面试指南](https://coding.imooc.com/class/chapter/296.html#Anchor) | /    |\n\n\n\n## 六、操作系统\n\n### 📖阅读清单\n\n| 书名                       | 推荐 | 京东 | 在线 |\n| -------------------------- | ---- | :--: | :--: |\n| 《Linux+C 程序设计大全》   | 快绝版的书了吧，非常经典，现在好多市面上的书都是这里抄来的 | [当当](http://e.dangdang.com/products/1900376798.html) | / |\n| 《快乐的 Linux 命令行》    | 最好得 Linux Tutorial，适合通读的书籍，并不是鸟哥这种类型的工具书，建议读一遍 | [JD](https://item.jd.com/11196146.html) | [📖](https://github.com/billie66/TLCL) |\n| 《深入理解计算机系统》     | 程序员的内功修炼，必读必读必读！ | [JD](https://item.jd.com/12006637.html) | / |\n| 《UNIX环境高级编程》       | 还没开始读... | [JD](https://item.jd.com/11469694.html) | / |\n| 《Linux+高性能服务器编程》 | 已经绝版的书啦，按需印刷 | [JD](https://item.jd.com/27343565806.html) | / |\n\n\n\n### 📺学习课程\n\n| 课程                                                         | 推荐                                                   |\n| ------------------------------------------------------------ | ------------------------------------------------------ |\n| [【慕课网】快速上手Linux 玩转典型应用](https://coding.imooc.com/class/154.html) | /                                                      |\n| [【慕课在线】Linux达人养成计划 I-Linux的入门级课程！](https://www.imooc.com/learn/175) | 入门最佳指南                                           |\n| [【慕课在线】Linux 达人养成计划 II VIM+磁盘管理+用户权限！](https://www.imooc.com/learn/111) | 入门最佳指南                                           |\n| [【小甲鱼】零基础入门学习汇编语言](https://www.bilibili.com/video/av28132657?from=search&seid=15313556840461592969) | 幽默不失内涵，非常值得学习                             |\n| [【哔哩哔哩】操作系统_清华大学(向勇、陈渝)](https://www.bilibili.com/video/av6538245?from=search&seid=9425433231081071738) | 操作系统的书本看的乏味，不妨可以看看清华大学的这个课程 |\n\n\n\n## 七、计算机网络\n\n### 📖阅读清单\n\n| 书名                                 | 推荐                                   |                  京东                   |\n| ------------------------------------ | -------------------------------------- | :-------------------------------------: |\n| 《计算机网络原理创新教程》韩立刚主编 | 全网最通俗易懂的网络课程，还有配套课程 | [JD](https://item.jd.com/12047649.html) |\n| 《图解HTTP》                         | 后台开发工程师，必须要会的 HTTP 协议   | [JD](https://item.jd.com/11449491.html) |\n\n\n\n### 📺学习课程\n\n| 课程                                                         | 推荐                                                         |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n| [【51CTO】韩老师-计算机网络原理-156讲](http://edu.51cto.com/course/7313.html) | 教科书级别，比谢希仁版的计算机网络更浅显易懂，有配套视频理论和实战，老师的讲课风格非常棒，是全网入门计算机网络最佳书籍与课程<br />适合面试和考研！ |\n| [【慕课网】HTTP协议原理+实践 Web开发工程师必学](https://coding.imooc.com/class/225.html) | 如果看不进书，可以推荐看这个，不算是非常优秀，但是适合快速入门 |\n\n\n\n## 八、架构师之路——分布式系统篇\n\n### 📖阅读清单\n\n| 书名                              | 推荐                                                         |                  京东                   |                             在线                             |\n| --------------------------------- | ------------------------------------------------------------ | :-------------------------------------: | :----------------------------------------------------------: |\n| 《微服务：从设计到部署》中文版    | 翻译国外的书籍，暂未初版，推荐在 GitHub 上直接阅读，或是下载 PDF |                   JD                    |        [📖](https://github.com/DocsHome/microservices)        |\n| 《Docker技术入门与实战（第3版）》 | 浅显易懂，理论与实战，推荐在 Gitbook 阅读                    | [JD](https://item.jd.com/12453318.html) | [📖](https://yeasy.gitbooks.io/docker_practice/content/introduction/) |\n| 《深入理解 Nginx》陶辉            | 教科书级 Nginx 指南，可配合极客时间视频课程                  | [JD](https://item.jd.com/11877268.html) |                              /                               |\n\n\n\n### 📺学习课程\n\n| 课程                                                         | 推荐                                                         |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n| [【咕泡学院】架构师系列课程](https://www.gupaoedu.com/)      | 部分课程值得学习，特别是 James 的讲课风格超级棒，带你深入底层 |\n| [【慕课网】系统学习 Docker 践行 DevOps 理念](https://coding.imooc.com/class/189.html) | /                                                            |\n| [【慕课网】Nginx 从入门到实践](https://coding.imooc.com/class/121.html) | /                                                            |\n| [【极客时间】Nginx 核心知识100讲](https://time.geekbang.org/course/intro/138) | 适合配套课本一起学习                                         |\n\n\n\n## 九、面试\n\n### 📺学习课程\n\n| 课程                                                         | 推荐 |\n| ------------------------------------------------------------ | ---- |\n| [【慕课网】360 大牛全面解读 PHP 面试](https://coding.imooc.com/class/133.html) | /    |\n| [【慕课网】Google 面试官亲授 升级 Java 面试](https://coding.imooc.com/class/132.html) | /    |\n\n\n\n## 十、机器学习与深度学习\n\n### 📖阅读清单\n\n| 书名                       | 推荐                                                         |                  京东                   |\n| -------------------------- | ------------------------------------------------------------ | :-------------------------------------: |\n| 《机器学习实战》图灵出版社 | 机器学习入门必读刊物                                         | [JD](https://item.jd.com/11242112.html) |\n| 《百面机器学习》           | 算法工程师少有的面试读物，还是彩色的，推荐给大家。包含机器学习与深度学习的核心知识 | [JD](https://item.jd.com/12401859.html) |\n\n\n\n### 📺学习课程\n\n| 课程                                                         | 推荐                                                         |\n| ------------------------------------------------------------ | ------------------------------------------------------------ |\n| [【慕课网】刘宇波：Python3入门机器学习 经典算法与应用](https://coding.imooc.com/class/169.html) | 看过的最好的机器学习入门视频，比吴恩达的实战更多，更浅显易懂 |\n| [【网易云课堂】吴恩达：机器学习](https://study.163.com/course/courseMain.htm?courseId=1004570029&_trace_c_p_k2_=25b4fdcb17dc4332825b750d83df2462) | 全球机器学习爱好者都在看的学习视频                           |\n| [【慕课网】刘宇波：专为程序员设计的线性代数课程](https://coding.imooc.com/class/260.html) | 程序员的内功修炼                                             |\n| [【莫烦Python】机器学习系列](https://morvanzhou.github.io/)  | 非常棒的机器学习入门，我的入门也来自于这里                   |\n\n\n\n## 十一、工具\n\n### 📺学习课程\n\n| 课程                                                         | 推荐                                         |\n| ------------------------------------------------------------ | -------------------------------------------- |\n| [【表严肃】讲正则表达式](https://biaoyansu.com/28.x)         | 表严肃的风格幽默风趣，轻松就把技术学到啦     |\n| [【表严肃】讲 Git](https://biaoyansu.com/27.x)               | 表严肃的风格幽默风趣，轻松就把技术学到啦     |\n| [【极客时间】Git 三剑客，携程代码平台-苏玲](https://time.geekbang.org/course/intro/145) | 目前看过的最好得 Git 学习课程                |\n| [【慕课在线】IntelliJ IDEA神器使用技巧](https://www.imooc.com/learn/924) | 学习 Java 前，先把工具用熟练，磨刀不误砍材工 |\n"
  },
  {
    "path": "notes/Leetcode题解.md",
    "content": "- <!-- GFM-TOC -->\n\n  * [算法思想](#算法思想)\n      * [贪心思想](#贪心思想)\n      * [双指针](#双指针)\n      * [排序](#排序)\n          * [快速选择](#快速选择)\n          * [堆排序](#堆排序)\n          * [桶排序](#桶排序)\n          * [荷兰国旗问题](#荷兰国旗问题)\n      * [二分查找](#二分查找)\n      * [搜索](#搜索)\n          * [BFS](#bfs)\n          * [DFS](#dfs)\n          * [Backtracking](#backtracking)\n      * [分治](#分治)\n      * [动态规划](#动态规划)\n          * [斐波那契数列](#斐波那契数列)\n          * [矩阵路径](#矩阵路径)\n          * [数组区间](#数组区间)\n          * [分割整数](#分割整数)\n          * [最长递增子序列](#最长递增子序列)\n          * [最长公共子序列](#最长公共子序列)\n          * [0-1 背包](#0-1-背包)\n          * [股票交易](#股票交易)\n          * [字符串编辑](#字符串编辑)\n      * [数学](#数学)\n          * [素数](#素数)\n          * [最大公约数](#最大公约数)\n          * [进制转换](#进制转换)\n          * [阶乘](#阶乘)\n          * [字符串加法减法](#字符串加法减法)\n          * [相遇问题](#相遇问题)\n          * [多数投票问题](#多数投票问题)\n          * [其它](#其它)\n  * [数据结构相关](#数据结构相关)\n      * [栈和队列](#栈和队列)\n      * [哈希表](#哈希表)\n      * [字符串](#字符串)\n      * [数组与矩阵](#数组与矩阵)\n      * [链表](#链表)\n      * [树](#树)\n          * [递归](#递归)\n          * [层次遍历](#层次遍历)\n          * [前中后序遍历](#前中后序遍历)\n          * [BST](#bst)\n          * [Trie](#trie)\n      * [图](#图)\n          * [二分图](#二分图)\n          * [拓扑排序](#拓扑排序)\n          * [并查集](#并查集)\n      * [位运算](#位运算)\n  * [参考资料](#参考资料)\n    <!-- GFM-TOC -->\n\n\n  # 算法思想\n\n  ## 贪心思想\n\n  贪心思想保证每次操作都是局部最优的，并且最后得到的结果是全局最优的。\n\n  **分配饼干** \n\n  [455. Assign Cookies (Easy)](https://leetcode.com/problems/assign-cookies/description/)\n\n  ```html\n  Input: [1,2], [1,2,3]\n  Output: 2\n  \n  Explanation: You have 2 children and 3 cookies. The greed factors of 2 children are 1, 2.\n  You have 3 cookies and their sizes are big enough to gratify all of the children,\n  You need to output 2.\n  ```\n\n  题目描述：每个孩子都有一个满足度，每个饼干都有一个大小，只有饼干的大小大于等于一个孩子的满足度，该孩子才会获得满足。求解最多可以获得满足的孩子数量。\n\n  因为最小的孩子最容易得到满足，因此先满足最小孩子。给一个孩子的饼干应当尽量小又能满足该孩子，这样大饼干就能拿来给满足度比较大的孩子。因此贪心策略\n\n  证明：假设在某次选择中，贪心策略选择给当前满足度最小的孩子分配第 m 个饼干，第 m 个饼干为可以满足该孩子的最小饼干。假设存在一种最优策略，给该孩子分配第 n 个饼干，并且 m < n。我们可以发现，经过这一轮分配，贪心策略分配后剩下的饼干一定有一个比最优策略来得大。因此在后续的分配中，贪心策略一定能满足更多的孩子。也就是说不存在比贪心策略更优的策略，即贪心策略就是最优策略。\n\n  ```java\n  public int findContentChildren(int[] g, int[] s) {\n      Arrays.sort(g);\n      Arrays.sort(s);\n      int gi = 0, si = 0;\n      while (gi < g.length && si < s.length) {\n          if (g[gi] <= s[si]) {\n              gi++;\n          }\n          si++;\n      }\n      return gi;\n  }\n  ```\n\n  **不重叠的区间个数** \n\n  [435. Non-overlapping Intervals (Medium)](https://leetcode.com/problems/non-overlapping-intervals/description/)\n\n  ```html\n  Input: [ [1,2], [1,2], [1,2] ]\n  \n  Output: 2\n  \n  Explanation: You need to remove two [1,2] to make the rest of intervals non-overlapping.\n  ```\n\n  ```html\n  Input: [ [1,2], [2,3] ]\n  \n  Output: 0\n  \n  Explanation: You don't need to remove any of the intervals since they're already non-overlapping.\n  ```\n\n  题目描述：计算让一组区间不重叠所需要移除的区间个数。\n\n  计算最多能组成的不重叠区间个数，然后用区间总个数减去不重叠区间的个数。\n\n  在每次选择中，区间的结尾最为重要，选择的区间结尾越小，留给后面的区间的空间越大，那么后面能够选择的区间个数也就越大。\n\n  按区间的结尾进行排序，每次选择结尾最小，并且和前一个区间不重叠的区间。\n\n  ```java\n  public int eraseOverlapIntervals(Interval[] intervals) {\n      if (intervals.length == 0) {\n          return 0;\n      }\n      Arrays.sort(intervals, Comparator.comparingInt(o -> o.end));\n      int cnt = 1;\n      int end = intervals[0].end;\n      for (int i = 1; i < intervals.length; i++) {\n          if (intervals[i].start < end) {\n              continue;\n          }\n          end = intervals[i].end;\n          cnt++;\n      }\n      return intervals.length - cnt;\n  }\n  ```\n\n  使用 lambda 表示式创建 Comparator 会导致算法运行时间过长，如果注重运行时间，可以修改为普通创建 Comparator 语句：\n\n  ```java\n  Arrays.sort(intervals, new Comparator<Interval>() {\n      @Override\n      public int compare(Interval o1, Interval o2) {\n          return o1.end - o2.end;\n      }\n  });\n  ```\n\n  **投飞镖刺破气球** \n\n  [452. Minimum Number of Arrows to Burst Balloons (Medium)](https://leetcode.com/problems/minimum-number-of-arrows-to-burst-balloons/description/)\n\n  ```\n  Input:\n  [[10,16], [2,8], [1,6], [7,12]]\n  \n  Output:\n  2\n  ```\n\n  题目描述：气球在一个水平数轴上摆放，可以重叠，飞镖垂直投向坐标轴，使得路径上的气球都会刺破。求解最小的投飞镖次数使所有气球都被刺破。\n\n  也是计算不重叠的区间个数，不过和 Non-overlapping Intervals 的区别在于，[1, 2] 和 [2, 3] 在本题中算是重叠区间。\n\n  ```java\n  public int findMinArrowShots(int[][] points) {\n      if (points.length == 0) {\n          return 0;\n      }\n      Arrays.sort(points, Comparator.comparingInt(o -> o[1]));\n      int cnt = 1, end = points[0][1];\n      for (int i = 1; i < points.length; i++) {\n          if (points[i][0] <= end) {\n              continue;\n          }\n          cnt++;\n          end = points[i][1];\n      }\n      return cnt;\n  }\n  ```\n\n  **根据身高和序号重组队列** \n\n  [406. Queue Reconstruction by Height(Medium)](https://leetcode.com/problems/queue-reconstruction-by-height/description/)\n\n  ```html\n  Input:\n  [[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]\n  \n  Output:\n  [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]\n  ```\n\n  题目描述：一个学生用两个分量 (h, k) 描述，h 表示身高，k 表示排在前面的有 k 个学生的身高比他高或者和他一样高。\n\n  为了在每次插入操作时不影响后续的操作，身高较高的学生应该先做插入操作，否则身高较小的学生原先正确插入第 k 个位置可能会变成第 k+1 个位置。\n\n  身高降序、k 值升序，然后按排好序的顺序插入队列的第 k 个位置中。\n\n  ```java\n  public int[][] reconstructQueue(int[][] people) {\n      if (people == null || people.length == 0 || people[0].length == 0) {\n          return new int[0][0];\n      }\n      Arrays.sort(people, (a, b) -> (a[0] == b[0] ? a[1] - b[1] : b[0] - a[0]));\n      List<int[]> queue = new ArrayList<>();\n      for (int[] p : people) {\n          queue.add(p[1], p);\n      }\n      return queue.toArray(new int[queue.size()][]);\n  }\n  ```\n\n  **分隔字符串使同种字符出现在一起** \n\n  [763. Partition Labels (Medium)](https://leetcode.com/problems/partition-labels/description/)\n\n  ```html\n  Input: S = \"ababcbacadefegdehijhklij\"\n  Output: [9,7,8]\n  Explanation:\n  The partition is \"ababcbaca\", \"defegde\", \"hijhklij\".\n  This is a partition so that each letter appears in at most one part.\n  A partition like \"ababcbacadefegde\", \"hijhklij\" is incorrect, because it splits S into less parts.\n  ```\n\n  ```java\n  public List<Integer> partitionLabels(String S) {\n      int[] lastIndexsOfChar = new int[26];\n      for (int i = 0; i < S.length(); i++) {\n          lastIndexsOfChar[char2Index(S.charAt(i))] = i;\n      }\n      List<Integer> partitions = new ArrayList<>();\n      int firstIndex = 0;\n      while (firstIndex < S.length()) {\n          int lastIndex = firstIndex;\n          for (int i = firstIndex; i < S.length() && i <= lastIndex; i++) {\n              int index = lastIndexsOfChar[char2Index(S.charAt(i))];\n              if (index > lastIndex) {\n                  lastIndex = index;\n              }\n          }\n          partitions.add(lastIndex - firstIndex + 1);\n          firstIndex = lastIndex + 1;\n      }\n      return partitions;\n  }\n  \n  private int char2Index(char c) {\n      return c - 'a';\n  }\n  ```\n\n\n  **种植花朵** \n\n  [605. Can Place Flowers (Easy)](https://leetcode.com/problems/can-place-flowers/description/)\n\n  ```html\n  Input: flowerbed = [1,0,0,0,1], n = 1\n  Output: True\n  ```\n\n  题目描述：花朵之间至少需要一个单位的间隔，求解是否能种下 n 朵花。\n\n  ```java\n  public boolean canPlaceFlowers(int[] flowerbed, int n) {\n      int len = flowerbed.length;\n      int cnt = 0;\n      for (int i = 0; i < len && cnt < n; i++) {\n          if (flowerbed[i] == 1) {\n              continue;\n          }\n          int pre = i == 0 ? 0 : flowerbed[i - 1];\n          int next = i == len - 1 ? 0 : flowerbed[i + 1];\n          if (pre == 0 && next == 0) {\n              cnt++;\n              flowerbed[i] = 1;\n          }\n      }\n      return cnt >= n;\n  }\n  ```\n\n  **判断是否为子序列** \n\n  [392. Is Subsequence (Medium)](https://leetcode.com/problems/is-subsequence/description/)\n\n  ```html\n  s = \"abc\", t = \"ahbgdc\"\n  Return true.\n  ```\n\n  ```java\n  public boolean isSubsequence(String s, String t) {\n      int index = -1;\n      for (char c : s.toCharArray()) {\n          index = t.indexOf(c, index + 1);\n          if (index == -1) {\n              return false;\n          }\n      }\n      return true;\n  }\n  ```\n\n  **修改一个数成为非递减数组** \n\n  [665. Non-decreasing Array (Easy)](https://leetcode.com/problems/non-decreasing-array/description/)\n\n  ```html\n  Input: [4,2,3]\n  Output: True\n  Explanation: You could modify the first 4 to 1 to get a non-decreasing array.\n  ```\n\n  题目描述：判断一个数组能不能只修改一个数就成为非递减数组。\n\n  在出现 nums[i] < nums[i - 1] 时，需要考虑的是应该修改数组的哪个数，使得本次修改能使 i 之前的数组成为非递减数组，并且  **不影响后续的操作** 。优先考虑令 nums[i - 1] = nums[i]，因为如果修改 nums[i] = nums[i - 1] 的话，那么 nums[i] 这个数会变大，就有可能比 nums[i + 1] 大，从而影响了后续操作。还有一个比较特别的情况就是 nums[i] < nums[i - 2]，只修改 nums[i - 1] = nums[i] 不能使数组成为非递减数组，只能修改 nums[i] = nums[i - 1]。\n\n  ```java\n  public boolean checkPossibility(int[] nums) {\n      int cnt = 0;\n      for (int i = 1; i < nums.length && cnt < 2; i++) {\n          if (nums[i] >= nums[i - 1]) {\n              continue;\n          }\n          cnt++;\n          if (i - 2 >= 0 && nums[i - 2] > nums[i]) {\n              nums[i] = nums[i - 1];\n          } else {\n              nums[i - 1] = nums[i];\n          }\n      }\n      return cnt <= 1;\n  }\n  ```\n\n  **股票的最大收益** \n\n  [122. Best Time to Buy and Sell Stock II (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/description/)\n\n  题目描述：一次股票交易包含买入和卖出，多个交易之间不能交叉进行。\n\n  对于 [a, b, c, d]，如果有 a <= b <= c <= d ，那么最大收益为 d - a。而 d - a = (d - c) + (c - b) + (b - a) ，因此当访问到一个 prices[i] 且 prices[i] - prices[i-1] > 0，那么就把 prices[i] - prices[i-1] 添加到收益中，从而在局部最优的情况下也保证全局最优。\n\n  ```java\n  public int maxProfit(int[] prices) {\n      int profit = 0;\n      for (int i = 1; i < prices.length; i++) {\n          if (prices[i] > prices[i - 1]) {\n              profit += (prices[i] - prices[i - 1]);\n          }\n      }\n      return profit;\n  }\n  ```\n\n  ## 双指针\n\n  双指针主要用于遍历数组，两个指针指向不同的元素，从而协同完成任务。\n\n  **有序数组的 Two Sum** \n\n  [Leetcode ：167. Two Sum II - Input array is sorted (Easy)](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/)\n\n  ```html\n  Input: numbers={2, 7, 11, 15}, target=9\n  Output: index1=1, index2=2\n  ```\n\n  题目描述：在有序数组中找出两个数，使它们的和为 target。\n\n  使用双指针，一个指针指向值较小的元素，一个指针指向值较大的元素。指向较小元素的指针从头向尾遍历，指向较大元素的指针从尾向头遍历。\n\n  如果两个指针指向元素的和 sum == target，那么得到要求的结果；如果 sum > target，移动较大的元素，使 sum 变小一些；如果 sum < target，移动较小的元素，使 sum 变大一些。\n\n  ```java\n  public int[] twoSum(int[] numbers, int target) {\n      int i = 0, j = numbers.length - 1;\n      while (i < j) {\n          int sum = numbers[i] + numbers[j];\n          if (sum == target) {\n              return new int[]{i + 1, j + 1};\n          } else if (sum < target) {\n              i++;\n          } else {\n              j--;\n          }\n      }\n      return null;\n  }\n  ```\n\n  **两数平方和** \n\n  [633. Sum of Square Numbers (Easy)](https://leetcode.com/problems/sum-of-square-numbers/description/)\n\n  ```html\n  Input: 5\n  Output: True\n  Explanation: 1 * 1 + 2 * 2 = 5\n  ```\n\n  题目描述：判断一个数是否为两个数的平方和，例如 5 = 1<sup>2</sup> + 2<sup>2</sup>。\n\n  ```java\n  public boolean judgeSquareSum(int c) {\n      int i = 0, j = (int) Math.sqrt(c);\n      while (i <= j) {\n          int powSum = i * i + j * j;\n          if (powSum == c) {\n              return true;\n          } else if (powSum > c) {\n              j--;\n          } else {\n              i++;\n          }\n      }\n      return false;\n  }\n  ```\n\n  **反转字符串中的元音字符** \n\n  [345. Reverse Vowels of a String (Easy)](https://leetcode.com/problems/reverse-vowels-of-a-string/description/)\n\n  ```html\n  Given s = \"leetcode\", return \"leotcede\".\n  ```\n\n  使用双指针，指向待反转的两个元音字符，一个指针从头向尾遍历，一个指针从尾到头遍历。\n\n  ```java\n  private final static HashSet<Character> vowels = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'));\n  \n  public String reverseVowels(String s) {\n      int i = 0, j = s.length() - 1;\n      char[] result = new char[s.length()];\n      while (i <= j) {\n          char ci = s.charAt(i);\n          char cj = s.charAt(j);\n          if (!vowels.contains(ci)) {\n              result[i++] = ci;\n          } else if (!vowels.contains(cj)) {\n              result[j--] = cj;\n          } else {\n              result[i++] = cj;\n              result[j--] = ci;\n          }\n      }\n      return new String(result);\n  }\n  ```\n\n  **回文字符串** \n\n  [680. Valid Palindrome II (Easy)](https://leetcode.com/problems/valid-palindrome-ii/description/)\n\n  ```html\n  Input: \"abca\"\n  Output: True\n  Explanation: You could delete the character 'c'.\n  ```\n\n  题目描述：可以删除一个字符，判断是否能构成回文字符串。\n\n  ```java\n  public boolean validPalindrome(String s) {\n      int i = -1, j = s.length();\n      while (++i < --j) {\n          if (s.charAt(i) != s.charAt(j)) {\n              return isPalindrome(s, i, j - 1) || isPalindrome(s, i + 1, j);\n          }\n      }\n      return true;\n  }\n  \n  private boolean isPalindrome(String s, int i, int j) {\n      while (i < j) {\n          if (s.charAt(i++) != s.charAt(j--)) {\n              return false;\n          }\n      }\n      return true;\n  }\n  ```\n\n  **归并两个有序数组** \n\n  [88. Merge Sorted Array (Easy)](https://leetcode.com/problems/merge-sorted-array/description/)\n\n  ```html\n  Input:\n  nums1 = [1,2,3,0,0,0], m = 3\n  nums2 = [2,5,6],       n = 3\n  \n  Output: [1,2,2,3,5,6]\n  ```\n\n  题目描述：把归并结果存到第一个数组上。\n\n  需要从尾开始遍历，否则在 nums1 上归并得到的值会覆盖还未进行归并比较的值\n\n  ```java\n  public void merge(int[] nums1, int m, int[] nums2, int n) {\n      int index1 = m - 1, index2 = n - 1;\n      int indexMerge = m + n - 1;\n      while (index1 >= 0 || index2 >= 0) {\n          if (index1 < 0) {\n              nums1[indexMerge--] = nums2[index2--];\n          } else if (index2 < 0) {\n              nums1[indexMerge--] = nums1[index1--];\n          } else if (nums1[index1] > nums2[index2]) {\n              nums1[indexMerge--] = nums1[index1--];\n          } else {\n              nums1[indexMerge--] = nums2[index2--];\n          }\n      }\n  }\n  ```\n\n  **判断链表是否存在环** \n\n  [141. Linked List Cycle (Easy)](https://leetcode.com/problems/linked-list-cycle/description/)\n\n  使用双指针，一个指针每次移动一个节点，一个指针每次移动两个节点，如果存在环，那么这两个指针一定会相遇。\n\n  ```java\n  public boolean hasCycle(ListNode head) {\n      if (head == null) {\n          return false;\n      }\n      ListNode l1 = head, l2 = head.next;\n      while (l1 != null && l2 != null && l2.next != null) {\n          if (l1 == l2) {\n              return true;\n          }\n          l1 = l1.next;\n          l2 = l2.next.next;\n      }\n      return false;\n  }\n  ```\n\n  **最长子序列** \n\n  [524. Longest Word in Dictionary through Deleting (Medium)](https://leetcode.com/problems/longest-word-in-dictionary-through-deleting/description/)\n\n  ```\n  Input:\n  s = \"abpcplea\", d = [\"ale\",\"apple\",\"monkey\",\"plea\"]\n  \n  Output:\n  \"apple\"\n  ```\n\n  题目描述：删除 s 中的一些字符，使得它构成字符串列表 d 中的一个字符串，找出能构成的最长字符串。如果有多个相同长度的结果，返回按字典序排序的最大字符串。\n\n  ```java\n  public String findLongestWord(String s, List<String> d) {\n      String longestWord = \"\";\n      for (String target : d) {\n          int l1 = longestWord.length(), l2 = target.length();\n          if (l1 > l2 || (l1 == l2 && longestWord.compareTo(target) < 0)) {\n              continue;\n          }\n          if (isValid(s, target)) {\n              longestWord = target;\n          }\n      }\n      return longestWord;\n  }\n  \n  private boolean isValid(String s, String target) {\n      int i = 0, j = 0;\n      while (i < s.length() && j < target.length()) {\n          if (s.charAt(i) == target.charAt(j)) {\n              j++;\n          }\n          i++;\n      }\n      return j == target.length();\n  }\n  ```\n\n  ## 排序\n\n  ### 快速选择\n\n  一般用于求解  **Kth Element**  问题，可以在 O(N) 时间复杂度，O(1) 空间复杂度完成求解工作。\n\n  与快速排序一样，快速选择一般需要先打乱数组，否则最坏情况下时间复杂度为 O(N<sup>2</sup>)。\n\n  ### 堆排序\n\n  堆排序用于求解  **TopK Elements**  问题，通过维护一个大小为 K 的堆，堆中的元素就是 TopK Elements。当然它也可以用于求解 Kth Element 问题，堆顶元素就是 Kth Element。快速选择也可以求解 TopK Elements 问题，因为找到 Kth Element 之后，再遍历一次数组，所有小于等于 Kth Element 的元素都是 TopK Elements。可以看到，快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题。\n\n  **Kth Element** \n\n  [215. Kth Largest Element in an Array (Medium)](https://leetcode.com/problems/kth-largest-element-in-an-array/description/)\n\n  **排序** ：时间复杂度 O(NlogN)，空间复杂度 O(1)\n\n  ```java\n  public int findKthLargest(int[] nums, int k) {\n      Arrays.sort(nums);\n      return nums[nums.length - k];\n  }\n  ```\n\n  **堆排序** ：时间复杂度 O(NlogK)，空间复杂度 O(K)。\n\n  ```java\n  public int findKthLargest(int[] nums, int k) {\n      PriorityQueue<Integer> pq = new PriorityQueue<>(); // 小顶堆\n      for (int val : nums) {\n          pq.add(val);\n          if (pq.size() > k) // 维护堆的大小为 K\n              pq.poll();\n      }\n      return pq.peek();\n  }\n  ```\n\n  **快速选择** ：时间复杂度 O(N)，空间复杂度 O(1)\n\n  ```java\n  public int findKthLargest(int[] nums, int k) {\n      k = nums.length - k;\n      int l = 0, h = nums.length - 1;\n      while (l < h) {\n          int j = partition(nums, l, h);\n          if (j == k) {\n              break;\n          } else if (j < k) {\n              l = j + 1;\n          } else {\n              h = j - 1;\n          }\n      }\n      return nums[k];\n  }\n  \n  private int partition(int[] a, int l, int h) {\n      int i = l, j = h + 1;\n      while (true) {\n          while (a[++i] < a[l] && i < h) ;\n          while (a[--j] > a[l] && j > l) ;\n          if (i >= j) {\n              break;\n          }\n          swap(a, i, j);\n      }\n      swap(a, l, j);\n      return j;\n  }\n  \n  private void swap(int[] a, int i, int j) {\n      int t = a[i];\n      a[i] = a[j];\n      a[j] = t;\n  }\n  ```\n\n  ### 桶排序\n\n  **出现频率最多的 k 个数** \n\n  [347. Top K Frequent Elements (Medium)](https://leetcode.com/problems/top-k-frequent-elements/description/)\n\n  ```html\n  Given [1,1,1,2,2,3] and k = 2, return [1,2].\n  ```\n\n  设置若干个桶，每个桶存储出现频率相同的数，并且桶的下标代表桶中数出现的频率，即第 i 个桶中存储的数出现的频率为 i。把数都放到桶之后，从后向前遍历桶，最先得到的 k 个数就是出现频率最多的的 k 个数。\n\n  ```java\n  public List<Integer> topKFrequent(int[] nums, int k) {\n      Map<Integer, Integer> frequencyForNum = new HashMap<>();\n      for (int num : nums) {\n          frequencyForNum.put(num, frequencyForNum.getOrDefault(num, 0) + 1);\n      }\n      List<Integer>[] buckets = new ArrayList[nums.length + 1];\n      for (int key : frequencyForNum.keySet()) {\n          int frequency = frequencyForNum.get(key);\n          if (buckets[frequency] == null) {\n              buckets[frequency] = new ArrayList<>();\n          }\n          buckets[frequency].add(key);\n      }\n      List<Integer> topK = new ArrayList<>();\n      for (int i = buckets.length - 1; i >= 0 && topK.size() < k; i--) {\n          if (buckets[i] != null) {\n              topK.addAll(buckets[i]);\n          }\n      }\n      return topK;\n  }\n  ```\n\n  **按照字符出现次数对字符串排序** \n\n  [451. Sort Characters By Frequency (Medium)](https://leetcode.com/problems/sort-characters-by-frequency/description/)\n\n  ```html\n  Input:\n  \"tree\"\n  \n  Output:\n  \"eert\"\n  \n  Explanation:\n  'e' appears twice while 'r' and 't' both appear once.\n  So 'e' must appear before both 'r' and 't'. Therefore \"eetr\" is also a valid answer.\n  ```\n\n  ```java\n  public String frequencySort(String s) {\n      Map<Character, Integer> frequencyForNum = new HashMap<>();\n      for (char c : s.toCharArray())\n          frequencyForNum.put(c, frequencyForNum.getOrDefault(c, 0) + 1);\n  \n      List<Character>[] frequencyBucket = new ArrayList[s.length() + 1];\n      for (char c : frequencyForNum.keySet()) {\n          int f = frequencyForNum.get(c);\n          if (frequencyBucket[f] == null) {\n              frequencyBucket[f] = new ArrayList<>();\n          }\n          frequencyBucket[f].add(c);\n      }\n      StringBuilder str = new StringBuilder();\n      for (int i = frequencyBucket.length - 1; i >= 0; i--) {\n          if (frequencyBucket[i] == null) {\n              continue;\n          }\n          for (char c : frequencyBucket[i]) {\n              for (int j = 0; j < i; j++) {\n                  str.append(c);\n              }\n          }\n      }\n      return str.toString();\n  }\n  ```\n\n  ### 荷兰国旗问题\n\n  荷兰国旗包含三种颜色：红、白、蓝。有这三种颜色的球，算法的目标是将这三种球按颜色顺序正确地排列。\n\n  它其实是三向切分快速排序的一种变种，在三向切分快速排序中，每次切分都将数组分成三个区间：小于切分元素、等于切分元素、大于切分元素，而该算法是将数组分成三个区间：等于红色、等于白色、等于蓝色。\n\n  <div align=\"center\"> <img src=\"../pics//3b49dd67-2c40-4b81-8ad2-7bbb1fe2fcbd.png\"/> </div><br>\n\n  **按颜色进行排序** \n\n  [75. Sort Colors (Medium)](https://leetcode.com/problems/sort-colors/description/)\n\n  ```html\n  Input: [2,0,2,1,1,0]\n  Output: [0,0,1,1,2,2]\n  ```\n\n  题目描述：只有 0/1/2 三种颜色。\n\n  ```java\n  public void sortColors(int[] nums) {\n      int zero = -1, one = 0, two = nums.length;\n      while (one < two) {\n          if (nums[one] == 0) {\n              swap(nums, ++zero, one++);\n          } else if (nums[one] == 2) {\n              swap(nums, --two, one);\n          } else {\n              ++one;\n          }\n      }\n  }\n  \n  private void swap(int[] nums, int i, int j) {\n      int t = nums[i];\n      nums[i] = nums[j];\n      nums[j] = t;\n  }\n  ```\n\n  ## 二分查找\n\n  **正常实现** \n\n  ```java\n  public int binarySearch(int[] nums, int key) {\n      int l = 0, h = nums.length - 1;\n      while (l <= h) {\n          int m = l + (h - l) / 2;\n          if (nums[m] == key) {\n              return m;\n          } else if (nums[m] > key) {\n              h = m - 1;\n          } else {\n              l = m + 1;\n          }\n      }\n      return -1;\n  }\n  ```\n\n  **时间复杂度** \n\n  二分查找也称为折半查找，每次都能将查找区间减半，这种折半特性的算法时间复杂度都为 O(logN)。\n\n  **m 计算** \n\n  有两种计算中值 m 的方式：\n\n  - m = (l + h) / 2\n  - m = l + (h - l) / 2\n\n  l + h 可能出现加法溢出，最好使用第二种方式。\n\n  **返回值** \n\n  循环退出时如果仍然没有查找到 key，那么表示查找失败。可以有两种返回值：\n\n  - -1：以一个错误码表示没有查找到 key\n  - l：将 key 插入到 nums 中的正确位置\n\n  **变种** \n\n  二分查找可以有很多变种，变种实现要注意边界值的判断。例如在一个有重复元素的数组中查找 key 的最左位置的实现如下：\n\n  ```java\n  public int binarySearch(int[] nums, int key) {\n      int l = 0, h = nums.length - 1;\n      while (l < h) {\n          int m = l + (h - l) / 2;\n          if (nums[m] >= key) {\n              h = m;\n          } else {\n              l = m + 1;\n          }\n      }\n      return l;\n  }\n  ```\n\n  该实现和正常实现有以下不同：\n\n  - 循环条件为 l < h\n  - h 的赋值表达式为 h = m\n  - 最后返回 l 而不是 -1\n\n  在 nums[m] >= key 的情况下，可以推导出最左 key 位于 [l, m] 区间中，这是一个闭区间。h 的赋值表达式为 h = m，因为 m 位置也可能是解。\n\n  在 h 的赋值表达式为 h = mid 的情况下，如果循环条件为 l <= h，那么会出现循环无法退出的情况，因此循环条件只能是 l < h。以下演示了循环条件为 l <= h 时循环无法退出的情况：\n\n  ```text\n  nums = {0, 1, 2}, key = 1\n  l   m   h\n  0   1   2  nums[m] >= key\n  0   0   1  nums[m] < key\n  1   1   1  nums[m] >= key\n  1   1   1  nums[m] >= key\n  ...\n  ```\n\n  当循环体退出时，不表示没有查找到 key，因此最后返回的结果不应该为 -1。为了验证有没有查找到，需要在调用端判断一下返回位置上的值和 key 是否相等。\n\n  **求开方** \n\n  [69. Sqrt(x) (Easy)](https://leetcode.com/problems/sqrtx/description/)\n\n  ```html\n  Input: 4\n  Output: 2\n  \n  Input: 8\n  Output: 2\n  Explanation: The square root of 8 is 2.82842..., and since we want to return an integer, the decimal part will be truncated.\n  ```\n\n  一个数 x 的开方 sqrt 一定在 0 \\~ x 之间，并且满足 sqrt == x / sqrt。可以利用二分查找在 0 \\~ x 之间查找 sqrt。\n\n  对于 x = 8，它的开方是 2.82842...，最后应该返回 2 而不是 3。在循环条件为 l <= h 并且循环退出时，h 总是比 l 小 1，也就是说 h = 2，l = 3，因此最后的返回值应该为 h 而不是 l。\n\n  ```java\n  public int mySqrt(int x) {\n      if (x <= 1) {\n          return x;\n      }\n      int l = 1, h = x;\n      while (l <= h) {\n          int mid = l + (h - l) / 2;\n          int sqrt = x / mid;\n          if (sqrt == mid) {\n              return mid;\n          } else if (mid > sqrt) {\n              h = mid - 1;\n          } else {\n              l = mid + 1;\n          }\n      }\n      return h;\n  }\n  ```\n\n  **大于给定元素的最小元素** \n\n  [744. Find Smallest Letter Greater Than Target (Easy)](https://leetcode.com/problems/find-smallest-letter-greater-than-target/description/)\n\n  ```html\n  Input:\n  letters = [\"c\", \"f\", \"j\"]\n  target = \"d\"\n  Output: \"f\"\n  \n  Input:\n  letters = [\"c\", \"f\", \"j\"]\n  target = \"k\"\n  Output: \"c\"\n  ```\n\n  题目描述：给定一个有序的字符数组 letters 和一个字符 target，要求找出 letters 中大于 target 的最小字符，如果找不到就返回第 1 个字符。\n\n  ```java\n  public char nextGreatestLetter(char[] letters, char target) {\n      int n = letters.length;\n      int l = 0, h = n - 1;\n      while (l <= h) {\n          int m = l + (h - l) / 2;\n          if (letters[m] <= target) {\n              l = m + 1;\n          } else {\n              h = m - 1;\n          }\n      }\n      return l < n ? letters[l] : letters[0];\n  }\n  ```\n\n  **有序数组的 Single Element** \n\n  [540. Single Element in a Sorted Array (Medium)](https://leetcode.com/problems/single-element-in-a-sorted-array/description/)\n\n  ```html\n  Input: [1,1,2,3,3,4,4,8,8]\n  Output: 2\n  ```\n\n  题目描述：一个有序数组只有一个数不出现两次，找出这个数。要求以 O(logN) 时间复杂度进行求解。\n\n  令 index 为 Single Element 在数组中的位置。如果 m 为偶数，并且 m + 1 < index，那么 nums[m] == nums[m + 1]；m + 1 >= index，那么 nums[m] != nums[m + 1]。\n\n  从上面的规律可以知道，如果 nums[m] == nums[m + 1]，那么 index 所在的数组位置为 [m + 2, h]，此时令 l = m + 2；如果 nums[m] != nums[m + 1]，那么 index 所在的数组位置为 [l, m]，此时令 h = m。\n\n  因为 h 的赋值表达式为 h = m，那么循环条件也就只能使用 l < h 这种形式。\n\n  ```java\n  public int singleNonDuplicate(int[] nums) {\n      int l = 0, h = nums.length - 1;\n      while (l < h) {\n          int m = l + (h - l) / 2;\n          if (m % 2 == 1) {\n              m--;   // 保证 l/h/m 都在偶数位，使得查找区间大小一直都是奇数\n          }\n          if (nums[m] == nums[m + 1]) {\n              l = m + 2;\n          } else {\n              h = m;\n          }\n      }\n      return nums[l];\n  }\n  ```\n\n  **第一个错误的版本** \n\n  [278. First Bad Version (Easy)](https://leetcode.com/problems/first-bad-version/description/)\n\n  题目描述：给定一个元素 n 代表有 [1, 2, ..., n] 版本，可以调用 isBadVersion(int x) 知道某个版本是否错误，要求找到第一个错误的版本。\n\n  如果第 m 个版本出错，则表示第一个错误的版本在 [l, m] 之间，令 h = m；否则第一个错误的版本在 [m + 1, h] 之间，令 l = m + 1。\n\n  因为 h 的赋值表达式为 h = m，因此循环条件为 l < h。\n\n  ```java\n  public int firstBadVersion(int n) {\n      int l = 1, h = n;\n      while (l < h) {\n          int mid = l + (h - l) / 2;\n          if (isBadVersion(mid)) {\n              h = mid;\n          } else {\n              l = mid + 1;\n          }\n      }\n      return l;\n  }\n  ```\n\n  **旋转数组的最小数字** \n\n  [153. Find Minimum in Rotated Sorted Array (Medium)](https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/description/)\n\n  ```html\n  Input: [3,4,5,1,2],\n  Output: 1\n  ```\n\n  ```java\n  public int findMin(int[] nums) {\n      int l = 0, h = nums.length - 1;\n      while (l < h) {\n          int m = l + (h - l) / 2;\n          if (nums[m] <= nums[h]) {\n              h = m;\n          } else {\n              l = m + 1;\n          }\n      }\n      return nums[l];\n  }\n  ```\n\n  **查找区间** \n\n  [34. Search for a Range (Medium)](https://leetcode.com/problems/search-for-a-range/description/)\n\n  ```html\n  Input: nums = [5,7,7,8,8,10], target = 8\n  Output: [3,4]\n  \n  Input: nums = [5,7,7,8,8,10], target = 6\n  Output: [-1,-1]\n  ```\n\n  ```java\n  public int[] searchRange(int[] nums, int target) {\n      int first = binarySearch(nums, target);\n      int last = binarySearch(nums, target + 1) - 1;\n      if (first == nums.length || nums[first] != target) {\n          return new int[]{-1, -1};\n      } else {\n          return new int[]{first, Math.max(first, last)};\n      }\n  }\n  \n  private int binarySearch(int[] nums, int target) {\n      int l = 0, h = nums.length; // 注意 h 的初始值\n      while (l < h) {\n          int m = l + (h - l) / 2;\n          if (nums[m] >= target) {\n              h = m;\n          } else {\n              l = m + 1;\n          }\n      }\n      return l;\n  }\n  ```\n\n  ## 搜索\n\n  深度优先搜索和广度优先搜索广泛运用于树和图中，但是它们的应用远远不止如此。\n\n  ### BFS\n\n  <div align=\"center\"> <img src=\"../pics//4ff355cf-9a7f-4468-af43-e5b02038facc.jpg\"/> </div><br>\n\n  广度优先搜索的搜索过程有点像一层一层地进行遍历，每层遍历都以上一层遍历的结果作为起点，遍历一个距离能访问到的所有节点。需要注意的是，遍历过的节点不能再次被遍历。\n\n  第一层：\n\n  - 0 -> {6,2,1,5};\n\n  第二层：\n\n  - 6 -> {4}\n  - 2 -> {}\n  - 1 -> {}\n  - 5 -> {3}\n\n  第三层：\n\n  - 4 -> {}\n  - 3 -> {}\n\n  可以看到，每一层遍历的节点都与根节点距离相同。设 d<sub>i</sub> 表示第 i 个节点与根节点的距离，推导出一个结论：对于先遍历的节点 i 与后遍历的节点 j，有 d<sub>i</sub><=d<sub>j</sub>。利用这个结论，可以求解最短路径等  **最优解**  问题：第一次遍历到目的节点，其所经过的路径为最短路径。应该注意的是，使用 BFS 只能求解无权图的最短路径。\n\n  在程序实现 BFS 时需要考虑以下问题：\n\n  - 队列：用来存储每一轮遍历得到的节点；\n  - 标记：对于遍历过的节点，应该将它标记，防止重复遍历。\n\n  **计算在网格中从原点到特定点的最短路径长度** \n\n  ```html\n  [[1,1,0,1],\n   [1,0,1,0],\n   [1,1,1,1],\n   [1,0,1,1]]\n  ```\n\n  1 表示可以经过某个位置，求解从 (0, 0) 位置到 (tr, tc) 位置的最短路径长度。\n\n  ```java\n  public int minPathLength(int[][] grids, int tr, int tc) {\n      final int[][] direction = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};\n      final int m = grids.length, n = grids[0].length;\n      Queue<Pair<Integer, Integer>> queue = new LinkedList<>();\n      queue.add(new Pair<>(0, 0));\n      int pathLength = 0;\n      while (!queue.isEmpty()) {\n          int size = queue.size();\n          pathLength++;\n          while (size-- > 0) {\n              Pair<Integer, Integer> cur = queue.poll();\n              for (int[] d : direction) {\n                  int nr = cur.getKey() + d[0], nc = cur.getValue() + d[1];\n                  Pair<Integer, Integer> next = new Pair<>(nr, nc);\n                  if (next.getKey() < 0 || next.getValue() >= m\n                          || next.getKey() < 0 || next.getValue() >= n) {\n  \n                      continue;\n                  }\n                  grids[next.getKey()][next.getValue()] = 0; // 标记\n                  if (next.getKey() == tr && next.getValue() == tc) {\n                      return pathLength;\n                  }\n                  queue.add(next);\n              }\n          }\n      }\n      return -1;\n  }\n  ```\n\n  **组成整数的最小平方数数量** \n\n  [279. Perfect Squares (Medium)](https://leetcode.com/problems/perfect-squares/description/)\n\n  ```html\n  For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9.\n  ```\n\n  可以将每个整数看成图中的一个节点，如果两个整数之差为一个平方数，那么这两个整数所在的节点就有一条边。\n\n  要求解最小的平方数数量，就是求解从节点 n 到节点 0 的最短路径。\n\n  本题也可以用动态规划求解，在之后动态规划部分中会再次出现。\n\n  ```java\n  public int numSquares(int n) {\n      List<Integer> squares = generateSquares(n);\n      Queue<Integer> queue = new LinkedList<>();\n      boolean[] marked = new boolean[n + 1];\n      queue.add(n);\n      marked[n] = true;\n      int level = 0;\n      while (!queue.isEmpty()) {\n          int size = queue.size();\n          level++;\n          while (size-- > 0) {\n              int cur = queue.poll();\n              for (int s : squares) {\n                  int next = cur - s;\n                  if (next < 0) {\n                      break;\n                  }\n                  if (next == 0) {\n                      return level;\n                  }\n                  if (marked[next]) {\n                      continue;\n                  }\n                  marked[next] = true;\n                  queue.add(cur - s);\n              }\n          }\n      }\n      return n;\n  }\n  \n  /**\n   * 生成小于 n 的平方数序列\n   * @return 1,4,9,...\n   */\n  private List<Integer> generateSquares(int n) {\n      List<Integer> squares = new ArrayList<>();\n      int square = 1;\n      int diff = 3;\n      while (square <= n) {\n          squares.add(square);\n          square += diff;\n          diff += 2;\n      }\n      return squares;\n  }\n  ```\n\n  **最短单词路径** \n\n  [127. Word Ladder (Medium)](https://leetcode.com/problems/word-ladder/description/)\n\n  ```html\n  Input:\n  beginWord = \"hit\",\n  endWord = \"cog\",\n  wordList = [\"hot\",\"dot\",\"dog\",\"lot\",\"log\",\"cog\"]\n  \n  Output: 5\n  \n  Explanation: As one shortest transformation is \"hit\" -> \"hot\" -> \"dot\" -> \"dog\" -> \"cog\",\n  return its length 5.\n  ```\n\n  ```html\n  Input:\n  beginWord = \"hit\"\n  endWord = \"cog\"\n  wordList = [\"hot\",\"dot\",\"dog\",\"lot\",\"log\"]\n  \n  Output: 0\n  \n  Explanation: The endWord \"cog\" is not in wordList, therefore no possible transformation.\n  ```\n\n  找出一条从 beginWord 到 endWord 的最短路径，每次移动规定为改变一个字符，并且改变之后的字符串必须在 wordList 中。\n\n  ```java\n  public int ladderLength(String beginWord, String endWord, List<String> wordList) {\n      wordList.add(beginWord);\n      int N = wordList.size();\n      int start = N - 1;\n      int end = 0;\n      while (end < N && !wordList.get(end).equals(endWord)) {\n          end++;\n      }\n      if (end == N) {\n          return 0;\n      }\n      List<Integer>[] graphic = buildGraphic(wordList);\n      return getShortestPath(graphic, start, end);\n  }\n  \n  private List<Integer>[] buildGraphic(List<String> wordList) {\n      int N = wordList.size();\n      List<Integer>[] graphic = new List[N];\n      for (int i = 0; i < N; i++) {\n          graphic[i] = new ArrayList<>();\n          for (int j = 0; j < N; j++) {\n              if (isConnect(wordList.get(i), wordList.get(j))) {\n                  graphic[i].add(j);\n              }\n          }\n      }\n      return graphic;\n  }\n  \n  private boolean isConnect(String s1, String s2) {\n      int diffCnt = 0;\n      for (int i = 0; i < s1.length() && diffCnt <= 1; i++) {\n          if (s1.charAt(i) != s2.charAt(i)) {\n              diffCnt++;\n          }\n      }\n      return diffCnt == 1;\n  }\n  \n  private int getShortestPath(List<Integer>[] graphic, int start, int end) {\n      Queue<Integer> queue = new LinkedList<>();\n      boolean[] marked = new boolean[graphic.length];\n      queue.add(start);\n      marked[start] = true;\n      int path = 1;\n      while (!queue.isEmpty()) {\n          int size = queue.size();\n          path++;\n          while (size-- > 0) {\n              int cur = queue.poll();\n              for (int next : graphic[cur]) {\n                  if (next == end) {\n                      return path;\n                  }\n                  if (marked[next]) {\n                      continue;\n                  }\n                  marked[next] = true;\n                  queue.add(next);\n              }\n          }\n      }\n      return 0;\n  }\n  ```\n\n  ### DFS\n\n  <div align=\"center\"> <img src=\"../pics//f7f7e3e5-7dd4-4173-9999-576b9e2ac0a2.png\"/> </div><br>\n\n  广度优先搜索一层一层遍历，每一层得到的所有新节点，要用队列存储起来以备下一层遍历的时候再遍历。\n\n  而深度优先搜索在得到一个新节点时立马对新节点进行遍历：从节点 0 出发开始遍历，得到到新节点 6 时，立马对新节点 6 进行遍历，得到新节点 4；如此反复以这种方式遍历新节点，直到没有新节点了，此时返回。返回到根节点 0 的情况是，继续对根节点 0 进行遍历，得到新节点 2，然后继续以上步骤。\n\n  从一个节点出发，使用 DFS 对一个图进行遍历时，能够遍历到的节点都是从初始节点可达的，DFS 常用来求解这种  **可达性**  问题。\n\n  在程序实现 DFS 时需要考虑以下问题：\n\n  - 栈：用栈来保存当前节点信息，当遍历新节点返回时能够继续遍历当前节点。可以使用递归栈。\n  - 标记：和 BFS 一样同样需要对已经遍历过的节点进行标记。\n\n  **查找最大的连通面积** \n\n  [695. Max Area of Island (Easy)](https://leetcode.com/problems/max-area-of-island/description/)\n\n  ```html\n  [[0,0,1,0,0,0,0,1,0,0,0,0,0],\n   [0,0,0,0,0,0,0,1,1,1,0,0,0],\n   [0,1,1,0,1,0,0,0,0,0,0,0,0],\n   [0,1,0,0,1,1,0,0,1,0,1,0,0],\n   [0,1,0,0,1,1,0,0,1,1,1,0,0],\n   [0,0,0,0,0,0,0,0,0,0,1,0,0],\n   [0,0,0,0,0,0,0,1,1,1,0,0,0],\n   [0,0,0,0,0,0,0,1,1,0,0,0,0]]\n  ```\n\n  ```java\n  private int m, n;\n  private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};\n  \n  public int maxAreaOfIsland(int[][] grid) {\n      if (grid == null || grid.length == 0) {\n          return 0;\n      }\n      m = grid.length;\n      n = grid[0].length;\n      int maxArea = 0;\n      for (int i = 0; i < m; i++) {\n          for (int j = 0; j < n; j++) {\n              maxArea = Math.max(maxArea, dfs(grid, i, j));\n          }\n      }\n      return maxArea;\n  }\n  \n  private int dfs(int[][] grid, int r, int c) {\n      if (r < 0 || r >= m || c < 0 || c >= n || grid[r][c] == 0) {\n          return 0;\n      }\n      grid[r][c] = 0;\n      int area = 1;\n      for (int[] d : direction) {\n          area += dfs(grid, r + d[0], c + d[1]);\n      }\n      return area;\n  }\n  ```\n\n  **矩阵中的连通分量数目** \n\n  [200. Number of Islands (Medium)](https://leetcode.com/problems/number-of-islands/description/)\n\n  ```html\n  Input:\n  11000\n  11000\n  00100\n  00011\n  \n  Output: 3\n  ```\n\n  可以将矩阵表示看成一张有向图。\n\n  ```java\n  private int m, n;\n  private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};\n  \n  public int numIslands(char[][] grid) {\n      if (grid == null || grid.length == 0) {\n          return 0;\n      }\n      m = grid.length;\n      n = grid[0].length;\n      int islandsNum = 0;\n      for (int i = 0; i < m; i++) {\n          for (int j = 0; j < n; j++) {\n              if (grid[i][j] != '0') {\n                  dfs(grid, i, j);\n                  islandsNum++;\n              }\n          }\n      }\n      return islandsNum;\n  }\n  \n  private void dfs(char[][] grid, int i, int j) {\n      if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0') {\n          return;\n      }\n      grid[i][j] = '0';\n      for (int[] d : direction) {\n          dfs(grid, i + d[0], j + d[1]);\n      }\n  }\n  ```\n\n  **好友关系的连通分量数目** \n\n  [547. Friend Circles (Medium)](https://leetcode.com/problems/friend-circles/description/)\n\n  ```html\n  Input:\n  [[1,1,0],\n   [1,1,0],\n   [0,0,1]]\n  Output: 2\n  Explanation:The 0th and 1st students are direct friends, so they are in a friend circle.\n  The 2nd student himself is in a friend circle. So return 2.\n  ```\n\n  好友关系可以看成是一个无向图，例如第 0 个人与第 1 个人是好友，那么 M[0][1] 和 M[1][0] 的值都为 1。\n\n  ```java\n  private int n;\n  \n  public int findCircleNum(int[][] M) {\n      n = M.length;\n      int circleNum = 0;\n      boolean[] hasVisited = new boolean[n];\n      for (int i = 0; i < n; i++) {\n          if (!hasVisited[i]) {\n              dfs(M, i, hasVisited);\n              circleNum++;\n          }\n      }\n      return circleNum;\n  }\n  \n  private void dfs(int[][] M, int i, boolean[] hasVisited) {\n      hasVisited[i] = true;\n      for (int k = 0; k < n; k++) {\n          if (M[i][k] == 1 && !hasVisited[k]) {\n              dfs(M, k, hasVisited);\n          }\n      }\n  }\n  ```\n\n  **填充封闭区域** \n\n  [130. Surrounded Regions (Medium)](https://leetcode.com/problems/surrounded-regions/description/)\n\n  ```html\n  For example,\n  X X X X\n  X O O X\n  X X O X\n  X O X X\n  \n  After running your function, the board should be:\n  X X X X\n  X X X X\n  X X X X\n  X O X X\n  ```\n\n  使被 'X' 包围的 'O' 转换为 'X'。\n\n  先填充最外侧，剩下的就是里侧了。\n\n  ```java\n  private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};\n  private int m, n;\n  \n  public void solve(char[][] board) {\n      if (board == null || board.length == 0) {\n          return;\n      }\n  \n      m = board.length;\n      n = board[0].length;\n  \n      for (int i = 0; i < m; i++) {\n          dfs(board, i, 0);\n          dfs(board, i, n - 1);\n      }\n      for (int i = 0; i < n; i++) {\n          dfs(board, 0, i);\n          dfs(board, m - 1, i);\n      }\n  \n      for (int i = 0; i < m; i++) {\n          for (int j = 0; j < n; j++) {\n              if (board[i][j] == 'T') {\n                  board[i][j] = 'O';\n              } else if (board[i][j] == 'O') {\n                  board[i][j] = 'X';\n              }\n          }\n      }\n  }\n  \n  private void dfs(char[][] board, int r, int c) {\n      if (r < 0 || r >= m || c < 0 || c >= n || board[r][c] != 'O') {\n          return;\n      }\n      board[r][c] = 'T';\n      for (int[] d : direction) {\n          dfs(board, r + d[0], c + d[1]);\n      }\n  }\n  ```\n\n  **能到达的太平洋和大西洋的区域** \n\n  [417. Pacific Atlantic Water Flow (Medium)](https://leetcode.com/problems/pacific-atlantic-water-flow/description/)\n\n  ```html\n  Given the following 5x5 matrix:\n  \n    Pacific ~   ~   ~   ~   ~\n         ~  1   2   2   3  (5) *\n         ~  3   2   3  (4) (4) *\n         ~  2   4  (5)  3   1  *\n         ~ (6) (7)  1   4   5  *\n         ~ (5)  1   1   2   4  *\n            *   *   *   *   * Atlantic\n  \n  Return:\n  [[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (positions with parentheses in above matrix).\n  ```\n\n  左边和上边是太平洋，右边和下边是大西洋，内部的数字代表海拔，海拔高的地方的水能够流到低的地方，求解水能够流到太平洋和大西洋的所有位置。\n\n  ```java\n  \n  private int m, n;\n  private int[][] matrix;\n  private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};\n  \n  public List<int[]> pacificAtlantic(int[][] matrix) {\n      List<int[]> ret = new ArrayList<>();\n      if (matrix == null || matrix.length == 0) {\n          return ret;\n      }\n  \n      m = matrix.length;\n      n = matrix[0].length;\n      this.matrix = matrix;\n      boolean[][] canReachP = new boolean[m][n];\n      boolean[][] canReachA = new boolean[m][n];\n  \n      for (int i = 0; i < m; i++) {\n          dfs(i, 0, canReachP);\n          dfs(i, n - 1, canReachA);\n      }\n      for (int i = 0; i < n; i++) {\n          dfs(0, i, canReachP);\n          dfs(m - 1, i, canReachA);\n      }\n  \n      for (int i = 0; i < m; i++) {\n          for (int j = 0; j < n; j++) {\n              if (canReachP[i][j] && canReachA[i][j]) {\n                  ret.add(new int[]{i, j});\n              }\n          }\n      }\n  \n      return ret;\n  }\n  \n  private void dfs(int r, int c, boolean[][] canReach) {\n      if (canReach[r][c]) {\n          return;\n      }\n      canReach[r][c] = true;\n      for (int[] d : direction) {\n          int nextR = d[0] + r;\n          int nextC = d[1] + c;\n          if (nextR < 0 || nextR >= m || nextC < 0 || nextC >= n\n                  || matrix[r][c] > matrix[nextR][nextC]) {\n  \n              continue;\n          }\n          dfs(nextR, nextC, canReach);\n      }\n  }\n  ```\n\n  ### Backtracking\n\n  Backtracking（回溯）属于 DFS。\n\n  - 普通 DFS 主要用在  **可达性问题** ，这种问题只需要执行到特点的位置然后返回即可。\n  - 而 Backtracking 主要用于求解  **排列组合**  问题，例如有 { 'a','b','c' } 三个字符，求解所有由这三个字符排列得到的字符串，这种问题在执行到特定的位置返回之后还会继续执行求解过程。\n\n  因为 Backtracking 不是立即就返回，而要继续求解，因此在程序实现时，需要注意对元素的标记问题：\n\n  - 在访问一个新元素进入新的递归调用时，需要将新元素标记为已经访问，这样才能在继续递归调用时不用重复访问该元素；\n  - 但是在递归返回时，需要将元素标记为未访问，因为只需要保证在一个递归链中不同时访问一个元素，可以访问已经访问过但是不在当前递归链中的元素。\n\n  **数字键盘组合** \n\n  [17. Letter Combinations of a Phone Number (Medium)](https://leetcode.com/problems/letter-combinations-of-a-phone-number/description/)\n\n  <div align=\"center\"> <img src=\"../pics//a3f34241-bb80-4879-8ec9-dff2d81b514e.jpg\"/> </div><br>\n\n  ```html\n  Input:Digit string \"23\"\n  Output: [\"ad\", \"ae\", \"af\", \"bd\", \"be\", \"bf\", \"cd\", \"ce\", \"cf\"].\n  ```\n\n  ```java\n  \n  private static final String[] KEYS = {\"\", \"\", \"abc\", \"def\", \"ghi\", \"jkl\", \"mno\", \"pqrs\", \"tuv\", \"wxyz\"};\n  \n  public List<String> letterCombinations(String digits) {\n      List<String> combinations = new ArrayList<>();\n      if (digits == null || digits.length() == 0) {\n          return combinations;\n      }\n      doCombination(new StringBuilder(), combinations, digits);\n      return combinations;\n  }\n  \n  private void doCombination(StringBuilder prefix, List<String> combinations, final String digits) {\n      if (prefix.length() == digits.length()) {\n          combinations.add(prefix.toString());\n          return;\n      }\n      int curDigits = digits.charAt(prefix.length()) - '0';\n      String letters = KEYS[curDigits];\n      for (char c : letters.toCharArray()) {\n          prefix.append(c);                         // 添加\n          doCombination(prefix, combinations, digits);\n          prefix.deleteCharAt(prefix.length() - 1); // 删除\n      }\n  }\n  ```\n\n  **IP 地址划分** \n\n  [93. Restore IP Addresses(Medium)](https://leetcode.com/problems/restore-ip-addresses/description/)\n\n  ```html\n  Given \"25525511135\",\n  return [\"255.255.11.135\", \"255.255.111.35\"].\n  ```\n\n  ```java\n  public List<String> restoreIpAddresses(String s) {\n      List<String> addresses = new ArrayList<>();\n      StringBuilder tempAddress = new StringBuilder();\n      doRestore(0, tempAddress, addresses, s);\n      return addresses;\n  }\n  \n  private void doRestore(int k, StringBuilder tempAddress, List<String> addresses, String s) {\n      if (k == 4 || s.length() == 0) {\n          if (k == 4 && s.length() == 0) {\n              addresses.add(tempAddress.toString());\n          }\n          return;\n      }\n      for (int i = 0; i < s.length() && i <= 2; i++) {\n          if (i != 0 && s.charAt(0) == '0') {\n              break;\n          }\n          String part = s.substring(0, i + 1);\n          if (Integer.valueOf(part) <= 255) {\n              if (tempAddress.length() != 0) {\n                  part = \".\" + part;\n              }\n              tempAddress.append(part);\n              doRestore(k + 1, tempAddress, addresses, s.substring(i + 1));\n              tempAddress.delete(tempAddress.length() - part.length(), tempAddress.length());\n          }\n      }\n  }\n  ```\n\n  **在矩阵中寻找字符串** \n\n  [79. Word Search (Medium)](https://leetcode.com/problems/word-search/description/)\n\n  ```html\n  For example,\n  Given board =\n  [\n    ['A','B','C','E'],\n    ['S','F','C','S'],\n    ['A','D','E','E']\n  ]\n  word = \"ABCCED\", -> returns true,\n  word = \"SEE\", -> returns true,\n  word = \"ABCB\", -> returns false.\n  ```\n\n  ```java\n  private final static int[][] direction = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};\n  private int m;\n  private int n;\n  \n  public boolean exist(char[][] board, String word) {\n      if (word == null || word.length() == 0) {\n          return true;\n      }\n      if (board == null || board.length == 0 || board[0].length == 0) {\n          return false;\n      }\n  \n      m = board.length;\n      n = board[0].length;\n      boolean[][] hasVisited = new boolean[m][n];\n  \n      for (int r = 0; r < m; r++) {\n          for (int c = 0; c < n; c++) {\n              if (backtracking(0, r, c, hasVisited, board, word)) {\n                  return true;\n              }\n          }\n      }\n  \n      return false;\n  }\n  \n  private boolean backtracking(int curLen, int r, int c, boolean[][] visited, final char[][] board, final String word) {\n      if (curLen == word.length()) {\n          return true;\n      }\n      if (r < 0 || r >= m || c < 0 || c >= n\n              || board[r][c] != word.charAt(curLen) || visited[r][c]) {\n  \n          return false;\n      }\n  \n      visited[r][c] = true;\n  \n      for (int[] d : direction) {\n          if (backtracking(curLen + 1, r + d[0], c + d[1], visited, board, word)) {\n              return true;\n          }\n      }\n  \n      visited[r][c] = false;\n  \n      return false;\n  }\n  ```\n\n  **输出二叉树中所有从根到叶子的路径** \n\n  [257. Binary Tree Paths (Easy)](https://leetcode.com/problems/binary-tree-paths/description/)\n\n  ```html\n    1\n   /  \\\n  2    3\n   \\\n    5\n  ```\n\n  ```html\n  [\"1->2->5\", \"1->3\"]\n  ```\n\n  ```java\n  \n  public List<String> binaryTreePaths(TreeNode root) {\n      List<String> paths = new ArrayList<>();\n      if (root == null) {\n          return paths;\n      }\n      List<Integer> values = new ArrayList<>();\n      backtracking(root, values, paths);\n      return paths;\n  }\n  \n  private void backtracking(TreeNode node, List<Integer> values, List<String> paths) {\n      if (node == null) {\n          return;\n      }\n      values.add(node.val);\n      if (isLeaf(node)) {\n          paths.add(buildPath(values));\n      } else {\n          backtracking(node.left, values, paths);\n          backtracking(node.right, values, paths);\n      }\n      values.remove(values.size() - 1);\n  }\n  \n  private boolean isLeaf(TreeNode node) {\n      return node.left == null && node.right == null;\n  }\n  \n  private String buildPath(List<Integer> values) {\n      StringBuilder str = new StringBuilder();\n      for (int i = 0; i < values.size(); i++) {\n          str.append(values.get(i));\n          if (i != values.size() - 1) {\n              str.append(\"->\");\n          }\n      }\n      return str.toString();\n  }\n  ```\n\n  **排列** \n\n  [46. Permutations (Medium)](https://leetcode.com/problems/permutations/description/)\n\n  ```html\n  [1,2,3] have the following permutations:\n  [\n    [1,2,3],\n    [1,3,2],\n    [2,1,3],\n    [2,3,1],\n    [3,1,2],\n    [3,2,1]\n  ]\n  ```\n\n  ```java\n  public List<List<Integer>> permute(int[] nums) {\n      List<List<Integer>> permutes = new ArrayList<>();\n      List<Integer> permuteList = new ArrayList<>();\n      boolean[] hasVisited = new boolean[nums.length];\n      backtracking(permuteList, permutes, hasVisited, nums);\n      return permutes;\n  }\n  \n  private void backtracking(List<Integer> permuteList, List<List<Integer>> permutes, boolean[] visited, final int[] nums) {\n      if (permuteList.size() == nums.length) {\n          permutes.add(new ArrayList<>(permuteList)); // 重新构造一个 List\n          return;\n      }\n      for (int i = 0; i < visited.length; i++) {\n          if (visited[i]) {\n              continue;\n          }\n          visited[i] = true;\n          permuteList.add(nums[i]);\n          backtracking(permuteList, permutes, visited, nums);\n          permuteList.remove(permuteList.size() - 1);\n          visited[i] = false;\n      }\n  }\n  ```\n\n  **含有相同元素求排列** \n\n  [47. Permutations II (Medium)](https://leetcode.com/problems/permutations-ii/description/)\n\n  ```html\n  [1,1,2] have the following unique permutations:\n  [[1,1,2], [1,2,1], [2,1,1]]\n  ```\n\n  数组元素可能含有相同的元素，进行排列时就有可能出现重复的排列，要求重复的排列只返回一个。\n\n  在实现上，和 Permutations 不同的是要先排序，然后在添加一个元素时，判断这个元素是否等于前一个元素，如果等于，并且前一个元素还未访问，那么就跳过这个元素。\n\n  ```java\n  public List<List<Integer>> permuteUnique(int[] nums) {\n      List<List<Integer>> permutes = new ArrayList<>();\n      List<Integer> permuteList = new ArrayList<>();\n      Arrays.sort(nums);  // 排序\n      boolean[] hasVisited = new boolean[nums.length];\n      backtracking(permuteList, permutes, hasVisited, nums);\n      return permutes;\n  }\n  \n  private void backtracking(List<Integer> permuteList, List<List<Integer>> permutes, boolean[] visited, final int[] nums) {\n      if (permuteList.size() == nums.length) {\n          permutes.add(new ArrayList<>(permuteList));\n          return;\n      }\n  \n      for (int i = 0; i < visited.length; i++) {\n          if (i != 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {\n              continue;  // 防止重复\n          }\n          if (visited[i]){\n              continue;\n          }\n          visited[i] = true;\n          permuteList.add(nums[i]);\n          backtracking(permuteList, permutes, visited, nums);\n          permuteList.remove(permuteList.size() - 1);\n          visited[i] = false;\n      }\n  }\n  ```\n\n  **组合** \n\n  [77. Combinations (Medium)](https://leetcode.com/problems/combinations/description/)\n\n  ```html\n  If n = 4 and k = 2, a solution is:\n  [\n    [2,4],\n    [3,4],\n    [2,3],\n    [1,2],\n    [1,3],\n    [1,4],\n  ]\n  ```\n\n  ```java\n  public List<List<Integer>> combine(int n, int k) {\n      List<List<Integer>> combinations = new ArrayList<>();\n      List<Integer> combineList = new ArrayList<>();\n      backtracking(combineList, combinations, 1, k, n);\n      return combinations;\n  }\n  \n  private void backtracking(List<Integer> combineList, List<List<Integer>> combinations, int start, int k, final int n) {\n      if (k == 0) {\n          combinations.add(new ArrayList<>(combineList));\n          return;\n      }\n      for (int i = start; i <= n - k + 1; i++) {  // 剪枝\n          combineList.add(i);\n          backtracking(combineList, combinations, i + 1, k - 1, n);\n          combineList.remove(combineList.size() - 1);\n      }\n  }\n  ```\n\n  **组合求和** \n\n  [39. Combination Sum (Medium)](https://leetcode.com/problems/combination-sum/description/)\n\n  ```html\n  given candidate set [2, 3, 6, 7] and target 7,\n  A solution set is:\n  [[7],[2, 2, 3]]\n  ```\n\n  ```java\n  public List<List<Integer>> combinationSum(int[] candidates, int target) {\n      List<List<Integer>> combinations = new ArrayList<>();\n      backtracking(new ArrayList<>(), combinations, 0, target, candidates);\n      return combinations;\n  }\n  \n  private void backtracking(List<Integer> tempCombination, List<List<Integer>> combinations,\n                            int start, int target, final int[] candidates) {\n  \n      if (target == 0) {\n          combinations.add(new ArrayList<>(tempCombination));\n          return;\n      }\n      for (int i = start; i < candidates.length; i++) {\n          if (candidates[i] <= target) {\n              tempCombination.add(candidates[i]);\n              backtracking(tempCombination, combinations, i, target - candidates[i], candidates);\n              tempCombination.remove(tempCombination.size() - 1);\n          }\n      }\n  }\n  ```\n\n  **含有相同元素的求组合求和** \n\n  [40. Combination Sum II (Medium)](https://leetcode.com/problems/combination-sum-ii/description/)\n\n  ```html\n  For example, given candidate set [10, 1, 2, 7, 6, 1, 5] and target 8,\n  A solution set is:\n  [\n    [1, 7],\n    [1, 2, 5],\n    [2, 6],\n    [1, 1, 6]\n  ]\n  ```\n\n  ```java\n  public List<List<Integer>> combinationSum2(int[] candidates, int target) {\n      List<List<Integer>> combinations = new ArrayList<>();\n      Arrays.sort(candidates);\n      backtracking(new ArrayList<>(), combinations, new boolean[candidates.length], 0, target, candidates);\n      return combinations;\n  }\n  \n  private void backtracking(List<Integer> tempCombination, List<List<Integer>> combinations,\n                            boolean[] hasVisited, int start, int target, final int[] candidates) {\n  \n      if (target == 0) {\n          combinations.add(new ArrayList<>(tempCombination));\n          return;\n      }\n      for (int i = start; i < candidates.length; i++) {\n          if (i != 0 && candidates[i] == candidates[i - 1] && !hasVisited[i - 1]) {\n              continue;\n          }\n          if (candidates[i] <= target) {\n              tempCombination.add(candidates[i]);\n              hasVisited[i] = true;\n              backtracking(tempCombination, combinations, hasVisited, i + 1, target - candidates[i], candidates);\n              hasVisited[i] = false;\n              tempCombination.remove(tempCombination.size() - 1);\n          }\n      }\n  }\n  ```\n\n  **1-9 数字的组合求和** \n\n  [216. Combination Sum III (Medium)](https://leetcode.com/problems/combination-sum-iii/description/)\n\n  ```html\n  Input: k = 3, n = 9\n  \n  Output:\n  \n  [[1,2,6], [1,3,5], [2,3,4]]\n  ```\n\n  从 1-9 数字中选出 k 个数不重复的数，使得它们的和为 n。\n\n  ```java\n  public List<List<Integer>> combinationSum3(int k, int n) {\n      List<List<Integer>> combinations = new ArrayList<>();\n      List<Integer> path = new ArrayList<>();\n      backtracking(k, n, 1, path, combinations);\n      return combinations;\n  }\n  \n  private void backtracking(int k, int n, int start,\n                            List<Integer> tempCombination, List<List<Integer>> combinations) {\n  \n      if (k == 0 && n == 0) {\n          combinations.add(new ArrayList<>(tempCombination));\n          return;\n      }\n      if (k == 0 || n == 0) {\n          return;\n      }\n      for (int i = start; i <= 9; i++) {\n          tempCombination.add(i);\n          backtracking(k - 1, n - i, i + 1, tempCombination, combinations);\n          tempCombination.remove(tempCombination.size() - 1);\n      }\n  }\n  ```\n\n  **子集** \n\n  [78. Subsets (Medium)](https://leetcode.com/problems/subsets/description/)\n\n  找出集合的所有子集，子集不能重复，[1, 2] 和 [2, 1] 这种子集算重复\n\n  ```java\n  public List<List<Integer>> subsets(int[] nums) {\n      List<List<Integer>> subsets = new ArrayList<>();\n      List<Integer> tempSubset = new ArrayList<>();\n      for (int size = 0; size <= nums.length; size++) {\n          backtracking(0, tempSubset, subsets, size, nums); // 不同的子集大小\n      }\n      return subsets;\n  }\n  \n  private void backtracking(int start, List<Integer> tempSubset, List<List<Integer>> subsets,\n                            final int size, final int[] nums) {\n  \n      if (tempSubset.size() == size) {\n          subsets.add(new ArrayList<>(tempSubset));\n          return;\n      }\n      for (int i = start; i < nums.length; i++) {\n          tempSubset.add(nums[i]);\n          backtracking(i + 1, tempSubset, subsets, size, nums);\n          tempSubset.remove(tempSubset.size() - 1);\n      }\n  }\n  ```\n\n  **含有相同元素求子集** \n\n  [90. Subsets II (Medium)](https://leetcode.com/problems/subsets-ii/description/)\n\n  ```html\n  For example,\n  If nums = [1,2,2], a solution is:\n  \n  [\n    [2],\n    [1],\n    [1,2,2],\n    [2,2],\n    [1,2],\n    []\n  ]\n  ```\n\n  ```java\n  public List<List<Integer>> subsetsWithDup(int[] nums) {\n      Arrays.sort(nums);\n      List<List<Integer>> subsets = new ArrayList<>();\n      List<Integer> tempSubset = new ArrayList<>();\n      boolean[] hasVisited = new boolean[nums.length];\n      for (int size = 0; size <= nums.length; size++) {\n          backtracking(0, tempSubset, subsets, hasVisited, size, nums); // 不同的子集大小\n      }\n      return subsets;\n  }\n  \n  private void backtracking(int start, List<Integer> tempSubset, List<List<Integer>> subsets, boolean[] hasVisited,\n                            final int size, final int[] nums) {\n  \n      if (tempSubset.size() == size) {\n          subsets.add(new ArrayList<>(tempSubset));\n          return;\n      }\n      for (int i = start; i < nums.length; i++) {\n          if (i != 0 && nums[i] == nums[i - 1] && !hasVisited[i - 1]) {\n              continue;\n          }\n          tempSubset.add(nums[i]);\n          hasVisited[i] = true;\n          backtracking(i + 1, tempSubset, subsets, hasVisited, size, nums);\n          hasVisited[i] = false;\n          tempSubset.remove(tempSubset.size() - 1);\n      }\n  }\n  ```\n\n  **分割字符串使得每个部分都是回文数** \n\n  [131. Palindrome Partitioning (Medium)](https://leetcode.com/problems/palindrome-partitioning/description/)\n\n  ```html\n  For example, given s = \"aab\",\n  Return\n  \n  [\n    [\"aa\",\"b\"],\n    [\"a\",\"a\",\"b\"]\n  ]\n  ```\n\n  ```java\n  public List<List<String>> partition(String s) {\n      List<List<String>> partitions = new ArrayList<>();\n      List<String> tempPartition = new ArrayList<>();\n      doPartition(s, partitions, tempPartition);\n      return partitions;\n  }\n  \n  private void doPartition(String s, List<List<String>> partitions, List<String> tempPartition) {\n      if (s.length() == 0) {\n          partitions.add(new ArrayList<>(tempPartition));\n          return;\n      }\n      for (int i = 0; i < s.length(); i++) {\n          if (isPalindrome(s, 0, i)) {\n              tempPartition.add(s.substring(0, i + 1));\n              doPartition(s.substring(i + 1), partitions, tempPartition);\n              tempPartition.remove(tempPartition.size() - 1);\n          }\n      }\n  }\n  \n  private boolean isPalindrome(String s, int begin, int end) {\n      while (begin < end) {\n          if (s.charAt(begin++) != s.charAt(end--)) {\n              return false;\n          }\n      }\n      return true;\n  }\n  ```\n\n  **数独** \n\n  [37. Sudoku Solver (Hard)](https://leetcode.com/problems/sudoku-solver/description/)\n\n  <div align=\"center\"> <img src=\"../pics//1ca52246-c443-48ae-b1f8-1cafc09ec75c.png\"/> </div><br>\n\n  ```java\n  private boolean[][] rowsUsed = new boolean[9][10];\n  private boolean[][] colsUsed = new boolean[9][10];\n  private boolean[][] cubesUsed = new boolean[9][10];\n  private char[][] board;\n  \n  public void solveSudoku(char[][] board) {\n      this.board = board;\n      for (int i = 0; i < 9; i++)\n          for (int j = 0; j < 9; j++) {\n              if (board[i][j] == '.') {\n                  continue;\n              }\n              int num = board[i][j] - '0';\n              rowsUsed[i][num] = true;\n              colsUsed[j][num] = true;\n              cubesUsed[cubeNum(i, j)][num] = true;\n          }\n  \n      for (int i = 0; i < 9; i++) {\n          for (int j = 0; j < 9; j++) {\n              backtracking(i, j);\n          }\n      }\n  }\n  \n  private boolean backtracking(int row, int col) {\n      while (row < 9 && board[row][col] != '.') {\n          row = col == 8 ? row + 1 : row;\n          col = col == 8 ? 0 : col + 1;\n      }\n      if (row == 9) {\n          return true;\n      }\n      for (int num = 1; num <= 9; num++) {\n          if (rowsUsed[row][num] || colsUsed[col][num] || cubesUsed[cubeNum(row, col)][num]) {\n              continue;\n          }\n          rowsUsed[row][num] = colsUsed[col][num] = cubesUsed[cubeNum(row, col)][num] = true;\n          board[row][col] = (char) (num + '0');\n          if (backtracking(row, col)) {\n              return true;\n          }\n          board[row][col] = '.';\n          rowsUsed[row][num] = colsUsed[col][num] = cubesUsed[cubeNum(row, col)][num] = false;\n      }\n      return false;\n  }\n  \n  private int cubeNum(int i, int j) {\n      int r = i / 3;\n      int c = j / 3;\n      return r * 3 + c;\n  }\n  ```\n\n  **N 皇后** \n\n  [51. N-Queens (Hard)](https://leetcode.com/problems/n-queens/description/)\n\n  <div align=\"center\"> <img src=\"../pics//1f080e53-4758-406c-bb5f-dbedf89b63ce.jpg\"/> </div><br>\n\n  在 n\\*n 的矩阵中摆放 n 个皇后，并且每个皇后不能在同一行，同一列，同一对角线上，求所有的 n 皇后的解。\n\n  一行一行地摆放，在确定一行中的那个皇后应该摆在哪一列时，需要用三个标记数组来确定某一列是否合法，这三个标记数组分别为：列标记数组、45 度对角线标记数组和 135 度对角线标记数组。\n\n  45 度对角线标记数组的维度为 2 \\* n - 1，通过下图可以明确 (r, c) 的位置所在的数组下标为 r + c。\n\n  <div align=\"center\"> <img src=\"../pics//85583359-1b45-45f2-9811-4f7bb9a64db7.jpg\"/> </div><br>\n\n  135 度对角线标记数组的维度也是 2 \\* n - 1，(r, c) 的位置所在的数组下标为 n - 1 - (r - c)。\n\n  <div align=\"center\"> <img src=\"../pics//9e80f75a-b12b-4344-80c8-1f9ccc2d5246.jpg\"/> </div><br>\n\n  ```java\n  private List<List<String>> solutions;\n  private char[][] nQueens;\n  private boolean[] colUsed;\n  private boolean[] diagonals45Used;\n  private boolean[] diagonals135Used;\n  private int n;\n  \n  public List<List<String>> solveNQueens(int n) {\n      solutions = new ArrayList<>();\n      nQueens = new char[n][n];\n      for (int i = 0; i < n; i++) {\n          Arrays.fill(nQueens[i], '.');\n      }\n      colUsed = new boolean[n];\n      diagonals45Used = new boolean[2 * n - 1];\n      diagonals135Used = new boolean[2 * n - 1];\n      this.n = n;\n      backtracking(0);\n      return solutions;\n  }\n  \n  private void backtracking(int row) {\n      if (row == n) {\n          List<String> list = new ArrayList<>();\n          for (char[] chars : nQueens) {\n              list.add(new String(chars));\n          }\n          solutions.add(list);\n          return;\n      }\n  \n      for (int col = 0; col < n; col++) {\n          int diagonals45Idx = row + col;\n          int diagonals135Idx = n - 1 - (row - col);\n          if (colUsed[col] || diagonals45Used[diagonals45Idx] || diagonals135Used[diagonals135Idx]) {\n              continue;\n          }\n          nQueens[row][col] = 'Q';\n          colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = true;\n          backtracking(row + 1);\n          colUsed[col] = diagonals45Used[diagonals45Idx] = diagonals135Used[diagonals135Idx] = false;\n          nQueens[row][col] = '.';\n      }\n  }\n  ```\n\n  ## 分治\n\n  **给表达式加括号** \n\n  [241. Different Ways to Add Parentheses (Medium)](https://leetcode.com/problems/different-ways-to-add-parentheses/description/)\n\n  ```html\n  Input: \"2-1-1\".\n  \n  ((2-1)-1) = 0\n  (2-(1-1)) = 2\n  \n  Output : [0, 2]\n  ```\n\n  ```java\n  public List<Integer> diffWaysToCompute(String input) {\n      List<Integer> ways = new ArrayList<>();\n      for (int i = 0; i < input.length(); i++) {\n          char c = input.charAt(i);\n          if (c == '+' || c == '-' || c == '*') {\n              List<Integer> left = diffWaysToCompute(input.substring(0, i));\n              List<Integer> right = diffWaysToCompute(input.substring(i + 1));\n              for (int l : left) {\n                  for (int r : right) {\n                      switch (c) {\n                          case '+':\n                              ways.add(l + r);\n                              break;\n                          case '-':\n                              ways.add(l - r);\n                              break;\n                          case '*':\n                              ways.add(l * r);\n                              break;\n                      }\n                  }\n              }\n          }\n      }\n      if (ways.size() == 0) {\n          ways.add(Integer.valueOf(input));\n      }\n      return ways;\n  }\n  ```\n\n  ## 动态规划\n\n  递归和动态规划都是将原问题拆成多个子问题然后求解，他们之间最本质的区别是，动态规划保存了子问题的解，避免重复计算。\n\n  ### 斐波那契数列\n\n  **爬楼梯** \n\n  [70. Climbing Stairs (Easy)](https://leetcode.com/problems/climbing-stairs/description/)\n\n  题目描述：有 N 阶楼梯，每次可以上一阶或者两阶，求有多少种上楼梯的方法。\n\n  定义一个数组 dp 存储上楼梯的方法数（为了方便讨论，数组下标从 1 开始），dp[i] 表示走到第 i 个楼梯的方法数目。第 i 个楼梯可以从第 i-1 和 i-2 个楼梯再走一步到达，走到第 i 个楼梯的方法数为走到第 i-1 和第 i-2 个楼梯的方法数之和。\n\n  <div align=\"center\"><img src=\"https://latex.codecogs.com/gif.latex?dp[i]=dp[i-1]+dp[i-2]\"/></div> <br>\n\n  dp[N] 即为所求。\n\n  考虑到 dp[i] 只与 dp[i - 1] 和 dp[i - 2] 有关，因此可以只用两个变量来存储 dp[i - 1] 和 dp[i - 2]，使得原来的 O(N) 空间复杂度优化为 O(1) 复杂度。\n\n  ```java\n  public int climbStairs(int n) {\n      if (n <= 2) {\n          return n;\n      }\n      int pre2 = 1, pre1 = 2;\n      for (int i = 2; i < n; i++) {\n          int cur = pre1 + pre2;\n          pre2 = pre1;\n          pre1 = cur;\n      }\n      return pre1;\n  }\n  ```\n\n  **强盗抢劫** \n\n  [198. House Robber (Easy)](https://leetcode.com/problems/house-robber/description/)\n\n  题目描述：抢劫一排住户，但是不能抢邻近的住户，求最大抢劫量。\n\n  定义 dp 数组用来存储最大的抢劫量，其中 dp[i] 表示抢到第 i 个住户时的最大抢劫量。由于不能抢劫邻近住户，因此如果抢劫了第 i 个住户那么只能抢劫 i - 2 或者 i - 3 的住户，所以\n\n  <div align=\"center\"><img src=\"https://latex.codecogs.com/gif.latex?dp[i]=max(dp[i-2],dp[i-3])+nums[i]\"/></div> <br>\n\n  ```java\n  public int rob(int[] nums) {\n      int n = nums.length;\n      if (n == 0) {\n          return 0;\n      }\n      if (n == 1) {\n          return nums[0];\n      }\n      int pre3 = 0, pre2 = 0, pre1 = 0;\n      for (int i = 0; i < n; i++) {\n          int cur = Math.max(pre2, pre3) + nums[i];\n          pre3 = pre2;\n          pre2 = pre1;\n          pre1 = cur;\n      }\n      return Math.max(pre1, pre2);\n  }\n  ```\n\n  **强盗在环形街区抢劫** \n\n  [213. House Robber II (Medium)](https://leetcode.com/problems/house-robber-ii/description/)\n\n  ```java\n  public int rob(int[] nums) {\n      if (nums == null || nums.length == 0) {\n          return 0;\n      }\n      int n = nums.length;\n      if (n == 1) {\n          return nums[0];\n      }\n      return Math.max(rob(nums, 0, n - 2), rob(nums, 1, n - 1));\n  }\n  \n  private int rob(int[] nums, int first, int last) {\n      int pre3 = 0, pre2 = 0, pre1 = 0;\n      for (int i = first; i <= last; i++) {\n          int cur = Math.max(pre3, pre2) + nums[i];\n          pre3 = pre2;\n          pre2 = pre1;\n          pre1 = cur;\n      }\n      return Math.max(pre2, pre1);\n  }\n  ```\n\n  **母牛生产** \n\n  [程序员代码面试指南-P181](#)\n\n  题目描述：假设农场中成熟的母牛每年都会生 1 头小母牛，并且永远不会死。第一年有 1 只小母牛，从第二年开始，母牛开始生小母牛。每只小母牛 3 年之后成熟又可以生小母牛。给定整数 N，求 N 年后牛的数量。\n\n  第 i 年成熟的牛的数量为：\n\n  <div align=\"center\"><img src=\"https://latex.codecogs.com/gif.latex?dp[i]=dp[i-1]+dp[i-3]\"/></div> <br>\n\n  **信件错排** \n\n  题目描述：有 N 个 信 和 信封，它们被打乱，求错误装信方式的数量。\n\n  定义一个数组 dp 存储错误方式数量，dp[i] 表示前 i 个信和信封的错误方式数量。假设第 i 个信装到第 j 个信封里面，而第 j 个信装到第 k 个信封里面。根据 i 和 k 是否相等，有两种情况：\n\n  - i==k，交换 i 和 k 的信后，它们的信和信封在正确的位置，但是其余 i-2 封信有 dp[i-2] 种错误装信的方式。由于 j 有 i-1 种取值，因此共有 (i-1)\\*dp[i-2] 种错误装信方式。\n  - i != k，交换 i 和 j 的信后，第 i 个信和信封在正确的位置，其余 i-1 封信有 dp[i-1] 种错误装信方式。由于 j 有 i-1 种取值，因此共有 (i-1)\\*dp[i-1] 种错误装信方式。\n\n  综上所述，错误装信数量方式数量为：\n\n  <div align=\"center\"><img src=\"https://latex.codecogs.com/gif.latex?dp[i]=(i-1)*dp[i-2]+(i-1)*dp[i-1]\"/></div> <br>\n\n  dp[N] 即为所求。\n\n  ### 矩阵路径\n\n  **矩阵的最小路径和** \n\n  [64. Minimum Path Sum (Medium)](https://leetcode.com/problems/minimum-path-sum/description/)\n\n  ```html\n  [[1,3,1],\n   [1,5,1],\n   [4,2,1]]\n  Given the above grid map, return 7. Because the path 1→3→1→1→1 minimizes the sum.\n  ```\n\n  题目描述：求从矩阵的左上角到右下角的最小路径和，每次只能向右和向下移动。\n\n  ```java\n  public int minPathSum(int[][] grid) {\n      if (grid.length == 0 || grid[0].length == 0) {\n          return 0;\n      }\n      int m = grid.length, n = grid[0].length;\n      int[] dp = new int[n];\n      for (int i = 0; i < m; i++) {\n          for (int j = 0; j < n; j++) {\n              if (j == 0) {\n                  dp[j] = dp[j];        // 只能从上侧走到该位置\n              } else if (i == 0) {\n                  dp[j] = dp[j - 1];    // 只能从左侧走到该位置\n              } else {\n                  dp[j] = Math.min(dp[j - 1], dp[j]);\n              }\n              dp[j] += grid[i][j];\n          }\n      }\n      return dp[n - 1];\n  }\n  ```\n\n  **矩阵的总路径数** \n\n  [62. Unique Paths (Medium)](https://leetcode.com/problems/unique-paths/description/)\n\n  题目描述：统计从矩阵左上角到右下角的路径总数，每次只能向右或者向下移动。\n\n  <div align=\"center\"> <img src=\"../pics//7c98e1b6-c446-4cde-8513-5c11b9f52aea.jpg\"/> </div><br>\n\n  ```java\n  public int uniquePaths(int m, int n) {\n      int[] dp = new int[n];\n      Arrays.fill(dp, 1);\n      for (int i = 1; i < m; i++) {\n          for (int j = 1; j < n; j++) {\n              dp[j] = dp[j] + dp[j - 1];\n          }\n      }\n      return dp[n - 1];\n  }\n  ```\n\n  也可以直接用数学公式求解，这是一个组合问题。机器人总共移动的次数 S=m+n-2，向下移动的次数 D=m-1，那么问题可以看成从 S 从取出 D 个位置的组合数量，这个问题的解为 C(S, D)。\n\n  ```java\n  public int uniquePaths(int m, int n) {\n      int S = m + n - 2;  // 总共的移动次数\n      int D = m - 1;      // 向下的移动次数\n      long ret = 1;\n      for (int i = 1; i <= D; i++) {\n          ret = ret * (S - D + i) / i;\n      }\n      return (int) ret;\n  }\n  ```\n\n  ### 数组区间\n\n  **数组区间和** \n\n  [303. Range Sum Query - Immutable (Easy)](https://leetcode.com/problems/range-sum-query-immutable/description/)\n\n  ```html\n  Given nums = [-2, 0, 3, -5, 2, -1]\n  \n  sumRange(0, 2) -> 1\n  sumRange(2, 5) -> -1\n  sumRange(0, 5) -> -3\n  ```\n\n  求区间 i \\~ j 的和，可以转换为 sum[j] - sum[i-1]，其中 sum[i] 为 0 \\~ i 的和。\n\n  ```java\n  class NumArray {\n      private int[] sums;\n  \n      public NumArray(int[] nums) {\n          sums = new int[nums.length];\n          for (int i = 0; i < nums.length; i++) {\n              sums[i] = i == 0 ? nums[0] : sums[i - 1] + nums[i];\n          }\n      }\n  \n      public int sumRange(int i, int j) {\n          return i == 0 ? sums[j] : sums[j] - sums[i - 1];\n      }\n  }\n  ```\n\n  **子数组最大的和** \n\n  [53. Maximum Subarray (Easy)](https://leetcode.com/problems/maximum-subarray/description/)\n\n  ```html\n  For example, given the array [-2,1,-3,4,-1,2,1,-5,4],\n  the contiguous subarray [4,-1,2,1] has the largest sum = 6.\n  ```\n\n  ```java\n  public int maxSubArray(int[] nums) {\n      if (nums == null || nums.length == 0) {\n          return 0;\n      }\n      int preSum = nums[0];\n      int maxSum = preSum;\n      for (int i = 1; i < nums.length; i++) {\n          preSum = preSum > 0 ? preSum + nums[i] : nums[i];\n          maxSum = Math.max(maxSum, preSum);\n      }\n      return maxSum;\n  }\n  ```\n\n  **数组中等差递增子区间的个数** \n\n  [413. Arithmetic Slices (Medium)](https://leetcode.com/problems/arithmetic-slices/description/)\n\n  ```html\n  A = [1, 2, 3, 4]\n  return: 3, for 3 arithmetic slices in A: [1, 2, 3], [2, 3, 4] and [1, 2, 3, 4] itself.\n  ```\n\n  dp[i] 表示以 A[i] 为结尾的等差递增子区间的个数。\n\n  如果 A[i] - A[i - 1] == A[i - 1] - A[i - 2]，表示 [A[i - 2], A[i - 1], A[i]] 是一个等差递增子区间。如果 [A[i - 3], A[i - 2], A[i - 1]] 是一个等差递增子区间，那么 [A[i - 3], A[i - 2], A[i - 1], A[i]] 也是。因此在这个条件下，dp[i] = dp[i-1] + 1。\n\n  ```java\n  public int numberOfArithmeticSlices(int[] A) {\n      if (A == null || A.length == 0) {\n          return 0;\n      }\n      int n = A.length;\n      int[] dp = new int[n];\n      for (int i = 2; i < n; i++) {\n          if (A[i] - A[i - 1] == A[i - 1] - A[i - 2]) {\n              dp[i] = dp[i - 1] + 1;\n          }\n      }\n      int total = 0;\n      for (int cnt : dp) {\n          total += cnt;\n      }\n      return total;\n  }\n  ```\n\n  ### 分割整数\n\n  **分割整数的最大乘积** \n\n  [343. Integer Break (Medim)](https://leetcode.com/problems/integer-break/description/)\n\n  题目描述：For example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4).\n\n  ```java\n  public int integerBreak(int n) {\n      int[] dp = new int[n + 1];\n      dp[1] = 1;\n      for (int i = 2; i <= n; i++) {\n          for (int j = 1; j <= i - 1; j++) {\n              dp[i] = Math.max(dp[i], Math.max(j * dp[i - j], j * (i - j)));\n          }\n      }\n      return dp[n];\n  }\n  ```\n\n  **按平方数来分割整数** \n\n  [279. Perfect Squares(Medium)](https://leetcode.com/problems/perfect-squares/description/)\n\n  题目描述：For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9.\n\n  ```java\n  public int numSquares(int n) {\n      List<Integer> squareList = generateSquareList(n);\n      int[] dp = new int[n + 1];\n      for (int i = 1; i <= n; i++) {\n          int min = Integer.MAX_VALUE;\n          for (int square : squareList) {\n              if (square > i) {\n                  break;\n              }\n              min = Math.min(min, dp[i - square] + 1);\n          }\n          dp[i] = min;\n      }\n      return dp[n];\n  }\n  \n  private List<Integer> generateSquareList(int n) {\n      List<Integer> squareList = new ArrayList<>();\n      int diff = 3;\n      int square = 1;\n      while (square <= n) {\n          squareList.add(square);\n          square += diff;\n          diff += 2;\n      }\n      return squareList;\n  }\n  ```\n\n  **分割整数构成字母字符串** \n\n  [91. Decode Ways (Medium)](https://leetcode.com/problems/decode-ways/description/)\n\n  题目描述：Given encoded message \"12\", it could be decoded as \"AB\" (1 2) or \"L\" (12).\n\n  ```java\n  public int numDecodings(String s) {\n      if (s == null || s.length() == 0) {\n          return 0;\n      }\n      int n = s.length();\n      int[] dp = new int[n + 1];\n      dp[0] = 1;\n      dp[1] = s.charAt(0) == '0' ? 0 : 1;\n      for (int i = 2; i <= n; i++) {\n          int one = Integer.valueOf(s.substring(i - 1, i));\n          if (one != 0) {\n              dp[i] += dp[i - 1];\n          }\n          if (s.charAt(i - 2) == '0') {\n              continue;\n          }\n          int two = Integer.valueOf(s.substring(i - 2, i));\n          if (two <= 26) {\n              dp[i] += dp[i - 2];\n          }\n      }\n      return dp[n];\n  }\n  ```\n\n  ### 最长递增子序列\n\n  已知一个序列 {S<sub>1</sub>, S<sub>2</sub>,...,S<sub>n</sub>} ，取出若干数组成新的序列 {S<sub>i1</sub>, S<sub>i2</sub>,..., S<sub>im</sub>}，其中 i1、i2 ... im 保持递增，即新序列中各个数仍然保持原数列中的先后顺序，称新序列为原序列的一个 **子序列** 。\n\n  如果在子序列中，当下标 ix > iy 时，S<sub>ix</sub> > S<sub>iy</sub>，称子序列为原序列的一个 **递增子序列** 。\n\n  定义一个数组 dp 存储最长递增子序列的长度，dp[n] 表示以 S<sub>n</sub> 结尾的序列的最长递增子序列长度。对于一个递增子序列 {S<sub>i1</sub>, S<sub>i2</sub>,...,S<sub>im</sub>}，如果 im < n 并且 S<sub>im</sub> < S<sub>n</sub> ，此时 {S<sub>i1</sub>, S<sub>i2</sub>,..., S<sub>im</sub>, S<sub>n</sub>} 为一个递增子序列，递增子序列的长度增加 1。满足上述条件的递增子序列中，长度最长的那个递增子序列就是要找的，在长度最长的递增子序列上加上 S<sub>n</sub> 就构成了以 S<sub>n</sub> 为结尾的最长递增子序列。因此 dp[n] = max{ dp[i]+1 | S<sub>i</sub> < S<sub>n</sub> && i < n} 。\n\n  因为在求 dp[n] 时可能无法找到一个满足条件的递增子序列，此时 {S<sub>n</sub>} 就构成了递增子序列，需要对前面的求解方程做修改，令 dp[n] 最小为 1，即：\n\n  <div align=\"center\"><img src=\"https://latex.codecogs.com/gif.latex?dp[n]=max\\{1,dp[i]+1|S_i<S_n\\&\\&i<n\\}\"/></div> <br>\n\n  对于一个长度为 N 的序列，最长递增子序列并不一定会以 S<sub>N</sub> 为结尾，因此 dp[N] 不是序列的最长递增子序列的长度，需要遍历 dp 数组找出最大值才是所要的结果，即 max{ dp[i] | 1 <= i <= N} 即为所求。\n\n  **最长递增子序列** \n\n  [300. Longest Increasing Subsequence (Medium)](https://leetcode.com/problems/longest-increasing-subsequence/description/)\n\n  ```java\n  public int lengthOfLIS(int[] nums) {\n      int n = nums.length;\n      int[] dp = new int[n];\n      for (int i = 0; i < n; i++) {\n          int max = 1;\n          for (int j = 0; j < i; j++) {\n              if (nums[i] > nums[j]) {\n                  max = Math.max(max, dp[j] + 1);\n              }\n          }\n          dp[i] = max;\n      }\n      return Arrays.stream(dp).max().orElse(0);\n  }\n  ```\n\n  使用 Stream 求最大值会导致运行时间过长，可以改成以下形式：\n\n  ```java\n  int ret = 0;\n  for (int i = 0; i < n; i++) {\n      ret = Math.max(ret, dp[i]);\n  }\n  return ret;\n  ```\n\n  以上解法的时间复杂度为 O(N<sup>2</sup>) ，可以使用二分查找将时间复杂度降低为 O(NlogN)。\n\n  定义一个 tails 数组，其中 tails[i] 存储长度为 i + 1 的最长递增子序列的最后一个元素。对于一个元素 x，\n\n  - 如果它大于 tails 数组所有的值，那么把它添加到 tails 后面，表示最长递增子序列长度加 1；\n  - 如果 tails[i-1] < x <= tails[i]，那么更新 tails[i-1] = x。\n\n  例如对于数组 [4,3,6,5]，有：\n\n  ```html\n  tails      len      num\n  []         0        4\n  [4]        1        3\n  [3]        1        6\n  [3,6]      2        5\n  [3,5]      2        null\n  ```\n\n  可以看出 tails 数组保持有序，因此在查找 S<sub>i</sub> 位于 tails 数组的位置时就可以使用二分查找。\n\n  ```java\n  public int lengthOfLIS(int[] nums) {\n      int n = nums.length;\n      int[] tails = new int[n];\n      int len = 0;\n      for (int num : nums) {\n          int index = binarySearch(tails, len, num);\n          tails[index] = num;\n          if (index == len) {\n              len++;\n          }\n      }\n      return len;\n  }\n  \n  private int binarySearch(int[] tails, int len, int key) {\n      int l = 0, h = len;\n      while (l < h) {\n          int mid = l + (h - l) / 2;\n          if (tails[mid] == key) {\n              return mid;\n          } else if (tails[mid] > key) {\n              h = mid;\n          } else {\n              l = mid + 1;\n          }\n      }\n      return l;\n  }\n  ```\n\n  **一组整数对能够构成的最长链** \n\n  [646. Maximum Length of Pair Chain (Medium)](https://leetcode.com/problems/maximum-length-of-pair-chain/description/)\n\n  ```html\n  Input: [[1,2], [2,3], [3,4]]\n  Output: 2\n  Explanation: The longest chain is [1,2] -> [3,4]\n  ```\n\n  题目描述：对于 (a, b) 和 (c, d) ，如果 b < c，则它们可以构成一条链。\n\n  ```java\n  public int findLongestChain(int[][] pairs) {\n      if (pairs == null || pairs.length == 0) {\n          return 0;\n      }\n      Arrays.sort(pairs, (a, b) -> (a[0] - b[0]));\n      int n = pairs.length;\n      int[] dp = new int[n];\n      Arrays.fill(dp, 1);\n      for (int i = 1; i < n; i++) {\n          for (int j = 0; j < i; j++) {\n              if (pairs[j][1] < pairs[i][0]) {\n                  dp[i] = Math.max(dp[i], dp[j] + 1);\n              }\n          }\n      }\n      return Arrays.stream(dp).max().orElse(0);\n  }\n  ```\n\n  **最长摆动子序列** \n\n  [376. Wiggle Subsequence (Medium)](https://leetcode.com/problems/wiggle-subsequence/description/)\n\n  ```html\n  Input: [1,7,4,9,2,5]\n  Output: 6\n  The entire sequence is a wiggle sequence.\n  \n  Input: [1,17,5,10,13,15,10,5,16,8]\n  Output: 7\n  There are several subsequences that achieve this length. One is [1,17,10,13,10,16,8].\n  \n  Input: [1,2,3,4,5,6,7,8,9]\n  Output: 2\n  ```\n\n  要求：使用 O(N) 时间复杂度求解。\n\n  ```java\n  public int wiggleMaxLength(int[] nums) {\n      if (nums == null || nums.length == 0) {\n          return 0;\n      }\n      int up = 1, down = 1;\n      for (int i = 1; i < nums.length; i++) {\n          if (nums[i] > nums[i - 1]) {\n              up = down + 1;\n          } else if (nums[i] < nums[i - 1]) {\n              down = up + 1;\n          }\n      }\n      return Math.max(up, down);\n  }\n  ```\n\n  ### 最长公共子序列\n\n  对于两个子序列 S1 和 S2，找出它们最长的公共子序列。\n\n  定义一个二维数组 dp 用来存储最长公共子序列的长度，其中 dp[i][j] 表示 S1 的前 i 个字符与 S2 的前 j 个字符最长公共子序列的长度。考虑 S1<sub>i</sub> 与 S2<sub>j</sub> 值是否相等，分为两种情况：\n\n  - 当 S1<sub>i</sub>==S2<sub>j</sub> 时，那么就能在 S1 的前 i-1 个字符与 S2 的前 j-1 个字符最长公共子序列的基础上再加上 S1<sub>i</sub> 这个值，最长公共子序列长度加 1 ，即 dp[i][j] = dp[i-1][j-1] + 1。\n  - 当 S1<sub>i</sub> != S2<sub>j</sub> 时，此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前 j 个字符最长公共子序列，与 S1 的前 i 个字符和 S2 的前 j-1 个字符最长公共子序列，它们的最大者，即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。\n\n  综上，最长公共子序列的状态转移方程为：\n\n  <div align=\"center\"><img src=\"https://latex.codecogs.com/gif.latex?dp[i][j]=\\left\\{\\begin{array}{rcl}dp[i-1][j-1]&&{S1_i==S2_j}\\\\max(dp[i-1][j],dp[i][j-1])&&{S1_i<>S2_j}\\end{array}\\right.\"/></div> <br>\n\n  对于长度为 N 的序列 S<sub>1</sub> 和 长度为 M 的序列 S<sub>2</sub>，dp[N][M] 就是序列 S<sub>1</sub> 和序列 S<sub>2</sub> 的最长公共子序列长度。\n\n  与最长递增子序列相比，最长公共子序列有以下不同点：\n\n  - 针对的是两个序列，求它们的最长公共子序列。\n  - 在最长递增子序列中，dp[i] 表示以 S<sub>i</sub> 为结尾的最长递增子序列长度，子序列必须包含 S<sub>i</sub> ；在最长公共子序列中，dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度，不一定包含 S1<sub>i</sub> 和 S2<sub>j</sub> 。\n  - 在求最终解时，最长公共子序列中 dp[N][M] 就是最终解，而最长递增子序列中 dp[N] 不是最终解，因为以 S<sub>N</sub> 为结尾的最长递增子序列不一定是整个序列最长递增子序列，需要遍历一遍 dp 数组找到最大者。\n\n  ```java\n  public int lengthOfLCS(int[] nums1, int[] nums2) {\n      int n1 = nums1.length, n2 = nums2.length;\n      int[][] dp = new int[n1 + 1][n2 + 1];\n      for (int i = 1; i <= n1; i++) {\n          for (int j = 1; j <= n2; j++) {\n              if (nums1[i - 1] == nums2[j - 1]) {\n                  dp[i][j] = dp[i - 1][j - 1] + 1;\n              } else {\n                  dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);\n              }\n          }\n      }\n      return dp[n1][n2];\n  }\n  ```\n\n  ### 0-1 背包\n\n  有一个容量为 N 的背包，要用这个背包装下物品的价值最大，这些物品有两个属性：体积 w 和价值 v。\n\n  定义一个二维数组 dp 存储最大价值，其中 dp[i][j] 表示前 i 件物品体积不超过 j 的情况下能达到的最大价值。设第 i 件物品体积为 w，价值为 v，根据第 i 件物品是否添加到背包中，可以分两种情况讨论：\n\n  - 第 i 件物品没添加到背包，总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值，dp[i][j] = dp[i-1][j]。\n  - 第 i 件物品添加到背包中，dp[i][j] = dp[i-1][j-w] + v。\n\n  第 i 件物品可添加也可以不添加，取决于哪种情况下最大价值更大。\n\n  综上，0-1 背包的状态转移方程为：\n\n  <div align=\"center\"><img src=\"https://latex.codecogs.com/gif.latex?dp[i][j]=max(dp[i-1][j],dp[i-1][j-w]+v)\"/></div> <br>\n\n  ```java\n  public int knapsack(int W, int N, int[] weights, int[] values) {\n      int[][] dp = new int[N + 1][W + 1];\n      for (int i = 1; i <= N; i++) {\n          int w = weights[i - 1], v = values[i - 1];\n          for (int j = 1; j <= W; j++) {\n              if (j >= w) {\n                  dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w] + v);\n              } else {\n                  dp[i][j] = dp[i - 1][j];\n              }\n          }\n      }\n      return dp[N][W];\n  }\n  ```\n\n  **空间优化** \n\n  在程序实现时可以对 0-1 背包做优化。观察状态转移方程可以知道，前 i 件物品的状态仅由前 i-1 件物品的状态有关，因此可以将 dp 定义为一维数组，其中 dp[j] 既可以表示 dp[i-1][j] 也可以表示 dp[i][j]。此时，\n\n  <div align=\"center\"><img src=\"https://latex.codecogs.com/gif.latex?dp[j]=max(dp[j],dp[j-w]+v)\"/></div> <br>\n\n  因为 dp[j-w] 表示 dp[i-1][j-w]，因此不能先求 dp[i][j-w]，以防止将 dp[i-1][j-w] 覆盖。也就是说要先计算 dp[i][j] 再计算 dp[i][j-w]，在程序实现时需要按倒序来循环求解。\n\n  ```java\n  public int knapsack(int W, int N, int[] weights, int[] values) {\n      int[] dp = new int[W + 1];\n      for (int i = 1; i <= N; i++) {\n          int w = weights[i - 1], v = values[i - 1];\n          for (int j = W; j >= 1; j--) {\n              if (j >= w) {\n                  dp[j] = Math.max(dp[j], dp[j - w] + v);\n              }\n          }\n      }\n      return dp[W];\n  }\n  ```\n\n  **无法使用贪心算法的解释** \n\n  0-1 背包问题无法使用贪心算法来求解，也就是说不能按照先添加性价比最高的物品来达到最优，这是因为这种方式可能造成背包空间的浪费，从而无法达到最优。考虑下面的物品和一个容量为 5 的背包，如果先添加物品 0 再添加物品 1，那么只能存放的价值为 16，浪费了大小为 2 的空间。最优的方式是存放物品 1 和物品 2，价值为 22.\n\n| id   | w    | v    | v/w  |\n| ---- | ---- | ---- | ---- |\n| 0    | 1    | 6    | 6    |\n| 1    | 2    | 10   | 5    |\n| 2    | 3    | 12   | 4    |\n\n  **变种** \n\n  - 完全背包：物品数量为无限个\n\n  - 多重背包：物品数量有限制\n\n  - 多维费用背包：物品不仅有重量，还有体积，同时考虑这两种限制\n\n  - 其它：物品之间相互约束或者依赖\n\n  **划分数组为和相等的两部分** \n\n  [416. Partition Equal Subset Sum (Medium)](https://leetcode.com/problems/partition-equal-subset-sum/description/)\n\n  ```html\n  Input: [1, 5, 11, 5]\n  \n  Output: true\n  \n  Explanation: The array can be partitioned as [1, 5, 5] and [11].\n  ```\n\n  可以看成一个背包大小为 sum/2 的 0-1 背包问题。\n\n  ```java\n  public boolean canPartition(int[] nums) {\n      int sum = computeArraySum(nums);\n      if (sum % 2 != 0) {\n          return false;\n      }\n      int W = sum / 2;\n      boolean[] dp = new boolean[W + 1];\n      dp[0] = true;\n      Arrays.sort(nums);\n      for (int num : nums) {                 // 0-1 背包一个物品只能用一次\n          for (int i = W; i >= num; i--) {   // 从后往前，先计算 dp[i] 再计算 dp[i-num]\n              dp[i] = dp[i] || dp[i - num];\n          }\n      }\n      return dp[W];\n  }\n  \n  private int computeArraySum(int[] nums) {\n      int sum = 0;\n      for (int num : nums) {\n          sum += num;\n      }\n      return sum;\n  }\n  ```\n\n  **改变一组数的正负号使得它们的和为一给定数** \n\n  [494. Target Sum (Medium)](https://leetcode.com/problems/target-sum/description/)\n\n  ```html\n  Input: nums is [1, 1, 1, 1, 1], S is 3.\n  Output: 5\n  Explanation:\n  \n  -1+1+1+1+1 = 3\n  +1-1+1+1+1 = 3\n  +1+1-1+1+1 = 3\n  +1+1+1-1+1 = 3\n  +1+1+1+1-1 = 3\n  \n  There are 5 ways to assign symbols to make the sum of nums be target 3.\n  ```\n\n  该问题可以转换为 Subset Sum 问题，从而使用 0-1 背包的方法来求解。\n\n  可以将这组数看成两部分，P 和 N，其中 P 使用正号，N 使用负号，有以下推导：\n\n  ```html\n                    sum(P) - sum(N) = target\n  sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) + sum(N)\n                         2 * sum(P) = target + sum(nums)\n  ```\n\n  因此只要找到一个子集，令它们都取正号，并且和等于 (target + sum(nums))/2，就证明存在解。\n\n  ```java\n  public int findTargetSumWays(int[] nums, int S) {\n      int sum = computeArraySum(nums);\n      if (sum < S || (sum + S) % 2 == 1) {\n          return 0;\n      }\n      int W = (sum + S) / 2;\n      int[] dp = new int[W + 1];\n      dp[0] = 1;\n      Arrays.sort(nums);\n      for (int num : nums) {\n          for (int i = W; i >= num; i--) {\n              dp[i] = dp[i] + dp[i - num];\n          }\n      }\n      return dp[W];\n  }\n  \n  private int computeArraySum(int[] nums) {\n      int sum = 0;\n      for (int num : nums) {\n          sum += num;\n      }\n      return sum;\n  }\n  ```\n\n  DFS 解法：\n\n  ```java\n  public int findTargetSumWays(int[] nums, int S) {\n      return findTargetSumWays(nums, 0, S);\n  }\n  \n  private int findTargetSumWays(int[] nums, int start, int S) {\n      if (start == nums.length) {\n          return S == 0 ? 1 : 0;\n      }\n      return findTargetSumWays(nums, start + 1, S + nums[start])\n              + findTargetSumWays(nums, start + 1, S - nums[start]);\n  }\n  ```\n\n  **字符串按单词列表分割** \n\n  [139. Word Break (Medium)](https://leetcode.com/problems/word-break/description/)\n\n  ```html\n  s = \"leetcode\",\n  dict = [\"leet\", \"code\"].\n  Return true because \"leetcode\" can be segmented as \"leet code\".\n  ```\n\n  dict 中的单词没有使用次数的限制，因此这是一个完全背包问题。\n\n  0-1 背包和完全背包在实现上的不同之处是，0-1 背包对物品的迭代是在最外层，而完全背包对物品的迭代是在最里层。\n\n  ```java\n  public boolean wordBreak(String s, List<String> wordDict) {\n      int n = s.length();\n      boolean[] dp = new boolean[n + 1];\n      dp[0] = true;\n      for (int i = 1; i <= n; i++) {\n          for (String word : wordDict) {   // 完全一个物品可以使用多次\n              int len = word.length();\n              if (len <= i && word.equals(s.substring(i - len, i))) {\n                  dp[i] = dp[i] || dp[i - len];\n              }\n          }\n      }\n      return dp[n];\n  }\n  ```\n\n  **01 字符构成最多的字符串** \n\n  [474. Ones and Zeroes (Medium)](https://leetcode.com/problems/ones-and-zeroes/description/)\n\n  ```html\n  Input: Array = {\"10\", \"0001\", \"111001\", \"1\", \"0\"}, m = 5, n = 3\n  Output: 4\n  \n  Explanation: There are totally 4 strings can be formed by the using of 5 0s and 3 1s, which are \"10\",\"0001\",\"1\",\"0\"\n  ```\n\n  这是一个多维费用的 0-1 背包问题，有两个背包大小，0 的数量和 1 的数量。\n\n  ```java\n  public int findMaxForm(String[] strs, int m, int n) {\n      if (strs == null || strs.length == 0) {\n          return 0;\n      }\n      int[][] dp = new int[m + 1][n + 1];\n      for (String s : strs) {    // 每个字符串只能用一次\n          int ones = 0, zeros = 0;\n          for (char c : s.toCharArray()) {\n              if (c == '0') {\n                  zeros++;\n              } else {\n                  ones++;\n              }\n          }\n          for (int i = m; i >= zeros; i--) {\n              for (int j = n; j >= ones; j--) {\n                  dp[i][j] = Math.max(dp[i][j], dp[i - zeros][j - ones] + 1);\n              }\n          }\n      }\n      return dp[m][n];\n  }\n  ```\n\n  **找零钱的方法数** \n\n  [322. Coin Change (Medium)](https://leetcode.com/problems/coin-change/description/)\n\n  ```html\n  Example 1:\n  coins = [1, 2, 5], amount = 11\n  return 3 (11 = 5 + 5 + 1)\n  \n  Example 2:\n  coins = [2], amount = 3\n  return -1.\n  ```\n\n  题目描述：给一些面额的硬币，要求用这些硬币来组成给定面额的钱数，并且使得硬币数量最少。硬币可以重复使用。\n\n  - 物品：硬币\n  - 物品大小：面额\n  - 物品价值：数量\n\n  因为硬币可以重复使用，因此这是一个完全背包问题。\n\n  ```java\n  public int coinChange(int[] coins, int amount) {\n      if (coins == null || coins.length == 0) {\n          return 0;\n      }\n      int[] minimum = new int[amount + 1];\n      Arrays.fill(minimum, amount + 1);\n      minimum[0] = 0;\n      Arrays.sort(coins);\n      for (int i = 1; i <= amount; i++) {\n          for (int j = 0; j < coins.length && coins[j] <= i; j++) {\n              minimum[i] = Math.min(minimum[i], minimum[i - coins[j]] + 1);\n          }\n      }\n      return minimum[amount] > amount ? -1 : minimum[amount];\n  }\n  ```\n\n  **组合总和** \n\n  [377. Combination Sum IV (Medium)](https://leetcode.com/problems/combination-sum-iv/description/)\n\n  ```html\n  nums = [1, 2, 3]\n  target = 4\n  \n  The possible combination ways are:\n  (1, 1, 1, 1)\n  (1, 1, 2)\n  (1, 2, 1)\n  (1, 3)\n  (2, 1, 1)\n  (2, 2)\n  (3, 1)\n  \n  Note that different sequences are counted as different combinations.\n  \n  Therefore the output is 7.\n  ```\n\n  完全背包。\n\n  ```java\n  public int combinationSum4(int[] nums, int target) {\n      if (nums == null || nums.length == 0) {\n          return 0;\n      }\n      int[] maximum = new int[target + 1];\n      maximum[0] = 1;\n      Arrays.sort(nums);\n      for (int i = 1; i <= target; i++) {\n          for (int j = 0; j < nums.length && nums[j] <= i; j++) {\n              maximum[i] += maximum[i - nums[j]];\n          }\n      }\n      return maximum[target];\n  }\n  ```\n\n  **只能进行 k 次的股票交易** \n\n  [188. Best Time to Buy and Sell Stock IV (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/description/)\n\n  ```java\n  public int maxProfit(int k, int[] prices) {\n      int n = prices.length;\n      if (k >= n / 2) {   // 这种情况下该问题退化为普通的股票交易问题\n          int maxProfit = 0;\n          for (int i = 1; i < n; i++) {\n              if (prices[i] > prices[i - 1]) {\n                  maxProfit += prices[i] - prices[i - 1];\n              }\n          }\n          return maxProfit;\n      }\n      int[][] maxProfit = new int[k + 1][n];\n      for (int i = 1; i <= k; i++) {\n          int localMax = maxProfit[i - 1][0] - prices[0];\n          for (int j = 1; j < n; j++) {\n              maxProfit[i][j] = Math.max(maxProfit[i][j - 1], prices[j] + localMax);\n              localMax = Math.max(localMax, maxProfit[i - 1][j] - prices[j]);\n          }\n      }\n      return maxProfit[k][n - 1];\n  }\n  ```\n\n  **只能进行两次的股票交易** \n\n  [123. Best Time to Buy and Sell Stock III (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/description/)\n\n  ```java\n  public int maxProfit(int[] prices) {\n      int firstBuy = Integer.MIN_VALUE, firstSell = 0;\n      int secondBuy = Integer.MIN_VALUE, secondSell = 0;\n      for (int curPrice : prices) {\n          if (firstBuy < -curPrice) {\n              firstBuy = -curPrice;\n          }\n          if (firstSell < firstBuy + curPrice) {\n              firstSell = firstBuy + curPrice;\n          }\n          if (secondBuy < firstSell - curPrice) {\n              secondBuy = firstSell - curPrice;\n          }\n          if (secondSell < secondBuy + curPrice) {\n              secondSell = secondBuy + curPrice;\n          }\n      }\n      return secondSell;\n  }\n  ```\n\n  ### 股票交易\n\n  **需要冷却期的股票交易** \n\n  [309. Best Time to Buy and Sell Stock with Cooldown(Medium)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/description/)\n\n  题目描述：交易之后需要有一天的冷却时间。\n\n  <div align=\"center\"> <img src=\"../pics//a3da4342-078b-43e2-b748-7e71bec50dc4.png\"/> </div><br>\n\n  ```java\n  public int maxProfit(int[] prices) {\n      if (prices == null || prices.length == 0) {\n          return 0;\n      }\n      int N = prices.length;\n      int[] buy = new int[N];\n      int[] s1 = new int[N];\n      int[] sell = new int[N];\n      int[] s2 = new int[N];\n      s1[0] = buy[0] = -prices[0];\n      sell[0] = s2[0] = 0;\n      for (int i = 1; i < N; i++) {\n          buy[i] = s2[i - 1] - prices[i];\n          s1[i] = Math.max(buy[i - 1], s1[i - 1]);\n          sell[i] = Math.max(buy[i - 1], s1[i - 1]) + prices[i];\n          s2[i] = Math.max(s2[i - 1], sell[i - 1]);\n      }\n      return Math.max(sell[N - 1], s2[N - 1]);\n  }\n  ```\n\n  **需要交易费用的股票交易** \n\n  [714. Best Time to Buy and Sell Stock with Transaction Fee (Medium)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/description/)\n\n  ```html\n  Input: prices = [1, 3, 2, 8, 4, 9], fee = 2\n  Output: 8\n  Explanation: The maximum profit can be achieved by:\n  Buying at prices[0] = 1\n  Selling at prices[3] = 8\n  Buying at prices[4] = 4\n  Selling at prices[5] = 9\n  The total profit is ((8 - 1) - 2) + ((9 - 4) - 2) = 8.\n  ```\n\n  题目描述：每交易一次，都要支付一定的费用。\n\n  <div align=\"center\"> <img src=\"../pics//61942711-45a0-4e11-bbc9-434e31436f33.png\"/> </div><br>\n\n  ```java\n  public int maxProfit(int[] prices, int fee) {\n      int N = prices.length;\n      int[] buy = new int[N];\n      int[] s1 = new int[N];\n      int[] sell = new int[N];\n      int[] s2 = new int[N];\n      s1[0] = buy[0] = -prices[0];\n      sell[0] = s2[0] = 0;\n      for (int i = 1; i < N; i++) {\n          buy[i] = Math.max(sell[i - 1], s2[i - 1]) - prices[i];\n          s1[i] = Math.max(buy[i - 1], s1[i - 1]);\n          sell[i] = Math.max(buy[i - 1], s1[i - 1]) - fee + prices[i];\n          s2[i] = Math.max(s2[i - 1], sell[i - 1]);\n      }\n      return Math.max(sell[N - 1], s2[N - 1]);\n  }\n  ```\n\n  **买入和售出股票最大的收益** \n\n  [121. Best Time to Buy and Sell Stock (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/)\n\n  题目描述：只进行一次交易。\n\n  只要记录前面的最小价格，将这个最小价格作为买入价格，然后将当前的价格作为售出价格，查看当前收益是不是最大收益。\n\n  ```java\n  public int maxProfit(int[] prices) {\n      int n = prices.length;\n      if (n == 0) return 0;\n      int soFarMin = prices[0];\n      int max = 0;\n      for (int i = 1; i < n; i++) {\n          if (soFarMin > prices[i]) soFarMin = prices[i];\n          else max = Math.max(max, prices[i] - soFarMin);\n      }\n      return max;\n  }\n  ```\n\n  ### 字符串编辑\n\n  **删除两个字符串的字符使它们相等** \n\n  [583. Delete Operation for Two Strings (Medium)](https://leetcode.com/problems/delete-operation-for-two-strings/description/)\n\n  ```html\n  Input: \"sea\", \"eat\"\n  Output: 2\n  Explanation: You need one step to make \"sea\" to \"ea\" and another step to make \"eat\" to \"ea\".\n  ```\n\n  可以转换为求两个字符串的最长公共子序列问题。\n\n  ```java\n  public int minDistance(String word1, String word2) {\n      int m = word1.length(), n = word2.length();\n      int[][] dp = new int[m + 1][n + 1];\n      for (int i = 0; i <= m; i++) {\n          for (int j = 0; j <= n; j++) {\n              if (i == 0 || j == 0) {\n                  continue;\n              }\n              if (word1.charAt(i - 1) == word2.charAt(j - 1)) {\n                  dp[i][j] = dp[i - 1][j - 1] + 1;\n              } else {\n                  dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]);\n              }\n          }\n      }\n      return m + n - 2 * dp[m][n];\n  }\n  ```\n\n  **编辑距离** \n\n  [72. Edit Distance (Hard)](https://leetcode.com/problems/edit-distance/description/)\n\n  ```html\n  Example 1:\n  \n  Input: word1 = \"horse\", word2 = \"ros\"\n  Output: 3\n  Explanation:\n  horse -> rorse (replace 'h' with 'r')\n  rorse -> rose (remove 'r')\n  rose -> ros (remove 'e')\n  Example 2:\n  \n  Input: word1 = \"intention\", word2 = \"execution\"\n  Output: 5\n  Explanation:\n  intention -> inention (remove 't')\n  inention -> enention (replace 'i' with 'e')\n  enention -> exention (replace 'n' with 'x')\n  exention -> exection (replace 'n' with 'c')\n  exection -> execution (insert 'u')\n  ```\n\n  题目描述：修改一个字符串成为另一个字符串，使得修改次数最少。一次修改操作包括：插入一个字符、删除一个字符、替换一个字符。\n\n  ```java\n  public int minDistance(String word1, String word2) {\n      if (word1 == null || word2 == null) {\n          return 0;\n      }\n      int m = word1.length(), n = word2.length();\n      int[][] dp = new int[m + 1][n + 1];\n      for (int i = 1; i <= m; i++) {\n          dp[i][0] = i;\n      }\n      for (int i = 1; i <= n; i++) {\n          dp[0][i] = i;\n      }\n      for (int i = 1; i <= m; i++) {\n          for (int j = 1; j <= n; j++) {\n              if (word1.charAt(i - 1) == word2.charAt(j - 1)) {\n                  dp[i][j] = dp[i - 1][j - 1];\n              } else {\n                  dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i][j - 1], dp[i - 1][j])) + 1;\n              }\n          }\n      }\n      return dp[m][n];\n  }\n  ```\n\n  **复制粘贴字符** \n\n  [650. 2 Keys Keyboard (Medium)](https://leetcode.com/problems/2-keys-keyboard/description/)\n\n  题目描述：最开始只有一个字符 A，问需要多少次操作能够得到 n 个字符 A，每次操作可以复制当前所有的字符，或者粘贴。\n\n  ```\n  Input: 3\n  Output: 3\n  Explanation:\n  Intitally, we have one character 'A'.\n  In step 1, we use Copy All operation.\n  In step 2, we use Paste operation to get 'AA'.\n  In step 3, we use Paste operation to get 'AAA'.\n  ```\n\n  ```java\n  public int minSteps(int n) {\n      if (n == 1) return 0;\n      for (int i = 2; i <= Math.sqrt(n); i++) {\n          if (n % i == 0) return i + minSteps(n / i);\n      }\n      return n;\n  }\n  ```\n\n  ```java\n  public int minSteps(int n) {\n      int[] dp = new int[n + 1];\n      int h = (int) Math.sqrt(n);\n      for (int i = 2; i <= n; i++) {\n          dp[i] = i;\n          for (int j = 2; j <= h; j++) {\n              if (i % j == 0) {\n                  dp[i] = dp[j] + dp[i / j];\n                  break;\n              }\n          }\n      }\n      return dp[n];\n  }\n  ```\n\n  ## 数学\n\n  ### 素数\n\n  **素数分解** \n\n  每一个数都可以分解成素数的乘积，例如 84 = 2<sup>2</sup> \\* 3<sup>1</sup> \\* 5<sup>0</sup> \\* 7<sup>1</sup> \\* 11<sup>0</sup> \\* 13<sup>0</sup> \\* 17<sup>0</sup> \\* …\n\n  **整除** \n\n  令 x = 2<sup>m0</sup> \\* 3<sup>m1</sup> \\* 5<sup>m2</sup> \\* 7<sup>m3</sup> \\* 11<sup>m4</sup> \\* …\n\n  令 y = 2<sup>n0</sup> \\* 3<sup>n1</sup> \\* 5<sup>n2</sup> \\* 7<sup>n3</sup> \\* 11<sup>n4</sup> \\* …\n\n  如果 x 整除 y（y mod x == 0），则对于所有 i，mi <= ni。\n\n  **最大公约数最小公倍数** \n\n  x 和 y 的最大公约数为：gcd(x,y) =  2<sup>min(m0,n0)</sup> \\* 3<sup>min(m1,n1)</sup> \\* 5<sup>min(m2,n2)</sup> \\* ...\n\n  x 和 y 的最小公倍数为：lcm(x,y) =  2<sup>max(m0,n0)</sup> \\* 3<sup>max(m1,n1)</sup> \\* 5<sup>max(m2,n2)</sup> \\* ...\n\n  **生成素数序列** \n\n  [204. Count Primes (Easy)](https://leetcode.com/problems/count-primes/description/)\n\n  埃拉托斯特尼筛法在每次找到一个素数时，将能被素数整除的数排除掉。\n\n  ```java\n  public int countPrimes(int n) {\n      boolean[] notPrimes = new boolean[n + 1];\n      int count = 0;\n      for (int i = 2; i < n; i++) {\n          if (notPrimes[i]) {\n              continue;\n          }\n          count++;\n          // 从 i * i 开始，因为如果 k < i，那么 k * i 在之前就已经被去除过了\n          for (long j = (long) (i) * i; j < n; j += i) {\n              notPrimes[(int) j] = true;\n          }\n      }\n      return count;\n  }\n  ```\n\n  ### 最大公约数\n\n  ```java\n  int gcd(int a, int b) {\n      return b == 0 ? a : gcd(b, a% b);\n  }\n  ```\n\n  最小公倍数为两数的乘积除以最大公约数。\n\n  ```java\n  int lcm(int a, int b) {\n      return a * b / gcd(a, b);\n  }\n  ```\n\n  **使用位操作和减法求解最大公约数** \n\n  [编程之美：2.7](#)\n\n  对于 a 和 b 的最大公约数 f(a, b)，有：\n\n  - 如果 a 和 b 均为偶数，f(a, b) = 2\\*f(a/2, b/2);\n  - 如果 a 是偶数 b 是奇数，f(a, b) = f(a/2, b);\n  - 如果 b 是偶数 a 是奇数，f(a, b) = f(a, b/2);\n  - 如果 a 和 b 均为奇数，f(a, b) = f(b, a-b);\n\n  乘 2 和除 2 都可以转换为移位操作。\n\n  ```java\n  public int gcd(int a, int b) {\n      if (a < b) {\n          return gcd(b, a);\n      }\n      if (b == 0) {\n          return a;\n      }\n      boolean isAEven = isEven(a), isBEven = isEven(b);\n      if (isAEven && isBEven) {\n          return 2 * gcd(a >> 1, b >> 1);\n      } else if (isAEven && !isBEven) {\n          return gcd(a >> 1, b);\n      } else if (!isAEven && isBEven) {\n          return gcd(a, b >> 1);\n      } else {\n          return gcd(b, a - b);\n      }\n  }\n  ```\n\n  ### 进制转换\n\n  **7 进制** \n\n  [504. Base 7 (Easy)](https://leetcode.com/problems/base-7/description/)\n\n  ```java\n  public String convertToBase7(int num) {\n      if (num == 0) {\n          return \"0\";\n      }\n      StringBuilder sb = new StringBuilder();\n      boolean isNegative = num < 0;\n      if (isNegative) {\n          num = -num;\n      }\n      while (num > 0) {\n          sb.append(num % 7);\n          num /= 7;\n      }\n      String ret = sb.reverse().toString();\n      return isNegative ? \"-\" + ret : ret;\n  }\n  ```\n\n  Java 中 static String toString(int num, int radix) 可以将一个整数转换为 redix 进制表示的字符串。\n\n  ```java\n  public String convertToBase7(int num) {\n      return Integer.toString(num, 7);\n  }\n  ```\n\n  **16 进制** \n\n  [405. Convert a Number to Hexadecimal (Easy)](https://leetcode.com/problems/convert-a-number-to-hexadecimal/description/)\n\n  ```html\n  Input:\n  26\n  \n  Output:\n  \"1a\"\n  \n  Input:\n  -1\n  \n  Output:\n  \"ffffffff\"\n  ```\n\n  负数要用它的补码形式。\n\n  ```java\n  public String toHex(int num) {\n      char[] map = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};\n      if (num == 0) return \"0\";\n      StringBuilder sb = new StringBuilder();\n      while (num != 0) {\n          sb.append(map[num & 0b1111]);\n          num >>>= 4; // 因为考虑的是补码形式，因此符号位就不能有特殊的意义，需要使用无符号右移，左边填 0\n      }\n      return sb.reverse().toString();\n  }\n  ```\n\n  **26 进制** \n\n  [168. Excel Sheet Column Title (Easy)](https://leetcode.com/problems/excel-sheet-column-title/description/)\n\n  ```html\n  1 -> A\n  2 -> B\n  3 -> C\n  ...\n  26 -> Z\n  27 -> AA\n  28 -> AB\n  ```\n\n  因为是从 1 开始计算的，而不是从 0 开始，因此需要对 n 执行 -1 操作。\n\n  ```java\n  public String convertToTitle(int n) {\n      if (n == 0) {\n          return \"\";\n      }\n      n--;\n      return convertToTitle(n / 26) + (char) (n % 26 + 'A');\n  }\n  ```\n\n  ### 阶乘\n\n  **统计阶乘尾部有多少个 0** \n\n  [172. Factorial Trailing Zeroes (Easy)](https://leetcode.com/problems/factorial-trailing-zeroes/description/)\n\n  尾部的 0 由 2 * 5 得来，2 的数量明显多于 5 的数量，因此只要统计有多少个 5 即可。\n\n  对于一个数 N，它所包含 5 的个数为：N/5 + N/5<sup>2</sup> + N/5<sup>3</sup> + ...，其中 N/5 表示不大于 N 的数中 5 的倍数贡献一个 5，N/5<sup>2</sup> 表示不大于 N 的数中 5<sup>2</sup> 的倍数再贡献一个 5 ...。\n\n  ```java\n  public int trailingZeroes(int n) {\n      return n == 0 ? 0 : n / 5 + trailingZeroes(n / 5);\n  }\n  ```\n\n  如果统计的是 N! 的二进制表示中最低位 1 的位置，只要统计有多少个 2 即可，该题目出自 [编程之美：2.2](#) 。和求解有多少个 5 一样，2 的个数为 N/2 + N/2<sup>2</sup> + N/2<sup>3</sup> + ...\n\n  ### 字符串加法减法\n\n  **二进制加法** \n\n  [67. Add Binary (Easy)](https://leetcode.com/problems/add-binary/description/)\n\n  ```html\n  a = \"11\"\n  b = \"1\"\n  Return \"100\".\n  ```\n\n  ```java\n  public String addBinary(String a, String b) {\n      int i = a.length() - 1, j = b.length() - 1, carry = 0;\n      StringBuilder str = new StringBuilder();\n      while (carry == 1 || i >= 0 || j >= 0) {\n          if (i >= 0 && a.charAt(i--) == '1') {\n              carry++;\n          }\n          if (j >= 0 && b.charAt(j--) == '1') {\n              carry++;\n          }\n          str.append(carry % 2);\n          carry /= 2;\n      }\n      return str.reverse().toString();\n  }\n  ```\n\n  **字符串加法** \n\n  [415. Add Strings (Easy)](https://leetcode.com/problems/add-strings/description/)\n\n  字符串的值为非负整数。\n\n  ```java\n  public String addStrings(String num1, String num2) {\n      StringBuilder str = new StringBuilder();\n      int carry = 0, i = num1.length() - 1, j = num2.length() - 1;\n      while (carry == 1 || i >= 0 || j >= 0) {\n          int x = i < 0 ? 0 : num1.charAt(i--) - '0';\n          int y = j < 0 ? 0 : num2.charAt(j--) - '0';\n          str.append((x + y + carry) % 10);\n          carry = (x + y + carry) / 10;\n      }\n      return str.reverse().toString();\n  }\n  ```\n\n  ### 相遇问题\n\n  **改变数组元素使所有的数组元素都相等** \n\n  [462. Minimum Moves to Equal Array Elements II (Medium)](https://leetcode.com/problems/minimum-moves-to-equal-array-elements-ii/description/)\n\n  ```html\n  Input:\n  [1,2,3]\n  \n  Output:\n  2\n  \n  Explanation:\n  Only two moves are needed (remember each move increments or decrements one element):\n  \n  [1,2,3]  =>  [2,2,3]  =>  [2,2,2]\n  ```\n\n  每次可以对一个数组元素加一或者减一，求最小的改变次数。\n\n  这是个典型的相遇问题，移动距离最小的方式是所有元素都移动到中位数。理由如下：\n\n  设 m 为中位数。a 和 b 是 m 两边的两个元素，且 b > a。要使 a 和 b 相等，它们总共移动的次数为 b - a，这个值等于 (b - m) + (m - a)，也就是把这两个数移动到中位数的移动次数。\n\n  设数组长度为 N，则可以找到 N/2 对 a 和 b 的组合，使它们都移动到 m 的位置。\n\n  **解法 1** \n\n  先排序，时间复杂度：O(NlogN)\n\n  ```java\n  public int minMoves2(int[] nums) {\n      Arrays.sort(nums);\n      int move = 0;\n      int l = 0, h = nums.length - 1;\n      while (l <= h) {\n          move += nums[h] - nums[l];\n          l++;\n          h--;\n      }\n      return move;\n  }\n  ```\n\n  **解法 2** \n\n  使用快速选择找到中位数，时间复杂度 O(N)\n\n  ```java\n  public int minMoves2(int[] nums) {\n      int move = 0;\n      int median = findKthSmallest(nums, nums.length / 2);\n      for (int num : nums) {\n          move += Math.abs(num - median);\n      }\n      return move;\n  }\n  \n  private int findKthSmallest(int[] nums, int k) {\n      int l = 0, h = nums.length - 1;\n      while (l < h) {\n          int j = partition(nums, l, h);\n          if (j == k) {\n              break;\n          }\n          if (j < k) {\n              l = j + 1;\n          } else {\n              h = j - 1;\n          }\n      }\n      return nums[k];\n  }\n  \n  private int partition(int[] nums, int l, int h) {\n      int i = l, j = h + 1;\n      while (true) {\n          while (nums[++i] < nums[l] && i < h) ;\n          while (nums[--j] > nums[l] && j > l) ;\n          if (i >= j) {\n              break;\n          }\n          swap(nums, i, j);\n      }\n      swap(nums, l, j);\n      return j;\n  }\n  \n  private void swap(int[] nums, int i, int j) {\n      int tmp = nums[i];\n      nums[i] = nums[j];\n      nums[j] = tmp;\n  }\n  ```\n\n  ### 多数投票问题\n\n  **数组中出现次数多于 n / 2 的元素** \n\n  [169. Majority Element (Easy)](https://leetcode.com/problems/majority-element/description/)\n\n  先对数组排序，最中间那个数出现次数一定多于 n / 2。\n\n  ```java\n  public int majorityElement(int[] nums) {\n      Arrays.sort(nums);\n      return nums[nums.length / 2];\n  }\n  ```\n\n  可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题，使得时间复杂度为 O(N)。可以这么理解该算法：使用 cnt 来统计一个元素出现的次数，当遍历到的元素和统计元素不相等时，令 cnt--。如果前面查找了 i 个元素，且 cnt == 0，说明前 i 个元素没有 majority，或者有 majority，但是出现的次数少于 i / 2，因为如果多于 i / 2 的话 cnt 就一定不会为 0。此时剩下的 n - i 个元素中，majority 的数目依然多于 (n - i) / 2，因此继续查找就能找出 majority。\n\n  ```java\n  public int majorityElement(int[] nums) {\n      int cnt = 0, majority = nums[0];\n      for (int num : nums) {\n          majority = (cnt == 0) ? num : majority;\n          cnt = (majority == num) ? cnt + 1 : cnt - 1;\n      }\n      return majority;\n  }\n  ```\n\n  ### 其它\n\n  **平方数** \n\n  [367. Valid Perfect Square (Easy)](https://leetcode.com/problems/valid-perfect-square/description/)\n\n  ```html\n  Input: 16\n  Returns: True\n  ```\n\n  平方序列：1,4,9,16,..\n\n  间隔：3,5,7,...\n\n  间隔为等差数列，使用这个特性可以得到从 1 开始的平方序列。\n\n  ```java\n  public boolean isPerfectSquare(int num) {\n      int subNum = 1;\n      while (num > 0) {\n          num -= subNum;\n          subNum += 2;\n      }\n      return num == 0;\n  }\n  ```\n\n  **3 的 n 次方** \n\n  [326. Power of Three (Easy)](https://leetcode.com/problems/power-of-three/description/)\n\n  ```java\n  public boolean isPowerOfThree(int n) {\n      return n > 0 && (1162261467 % n == 0);\n  }\n  ```\n\n  **乘积数组** \n\n  [238. Product of Array Except Self (Medium)](https://leetcode.com/problems/product-of-array-except-self/description/)\n\n  ```html\n  For example, given [1,2,3,4], return [24,12,8,6].\n  ```\n\n  给定一个数组，创建一个新数组，新数组的每个元素为原始数组中除了该位置上的元素之外所有元素的乘积。\n\n  要求时间复杂度为 O(N)，并且不能使用除法。\n\n  ```java\n  public int[] productExceptSelf(int[] nums) {\n      int n = nums.length;\n      int[] products = new int[n];\n      Arrays.fill(products, 1);\n      int left = 1;\n      for (int i = 1; i < n; i++) {\n          left *= nums[i - 1];\n          products[i] *= left;\n      }\n      int right = 1;\n      for (int i = n - 2; i >= 0; i--) {\n          right *= nums[i + 1];\n          products[i] *= right;\n      }\n      return products;\n  }\n  ```\n\n  **找出数组中的乘积最大的三个数** \n\n  [628. Maximum Product of Three Numbers (Easy)](https://leetcode.com/problems/maximum-product-of-three-numbers/description/)\n\n  ```html\n  Input: [1,2,3,4]\n  Output: 24\n  ```\n\n  ```java\n  public int maximumProduct(int[] nums) {\n      int max1 = Integer.MIN_VALUE, max2 = Integer.MIN_VALUE, max3 = Integer.MIN_VALUE, min1 = Integer.MAX_VALUE, min2 = Integer.MAX_VALUE;\n      for (int n : nums) {\n          if (n > max1) {\n              max3 = max2;\n              max2 = max1;\n              max1 = n;\n          } else if (n > max2) {\n              max3 = max2;\n              max2 = n;\n          } else if (n > max3) {\n              max3 = n;\n          }\n  \n          if (n < min1) {\n              min2 = min1;\n              min1 = n;\n          } else if (n < min2) {\n              min2 = n;\n          }\n      }\n      return Math.max(max1*max2*max3, max1*min1*min2);\n  }\n  ```\n\n  # 数据结构相关\n\n  ## 栈和队列\n\n  **用栈实现队列** \n\n  [232. Implement Queue using Stacks (Easy)](https://leetcode.com/problems/implement-queue-using-stacks/description/)\n\n  ```java\n  class MyQueue {\n  \n      private Stack<Integer> in = new Stack<>();\n      private Stack<Integer> out = new Stack<>();\n  \n      public void push(int x) {\n          in.push(x);\n      }\n  \n      public int pop() {\n          in2out();\n          return out.pop();\n      }\n  \n      public int peek() {\n          in2out();\n          return out.peek();\n      }\n  \n      private void in2out() {\n          if (out.isEmpty()) {\n              while (!in.isEmpty()) {\n                  out.push(in.pop());\n              }\n          }\n      }\n  \n      public boolean empty() {\n          return in.isEmpty() && out.isEmpty();\n      }\n  }\n  ```\n\n  **用队列实现栈** \n\n  [225. Implement Stack using Queues (Easy)](https://leetcode.com/problems/implement-stack-using-queues/description/)\n\n  在将一个元素 x 插入队列时，需要让除了 x 之外的所有元素出队列，再入队列。此时 x 在队首，第一个出队列，实现了后进先出顺序。\n\n  ```java\n  class MyStack {\n  \n      private Queue<Integer> queue;\n  \n      public MyStack() {\n          queue = new LinkedList<>();\n      }\n  \n      public void push(int x) {\n          queue.add(x);\n          int cnt = queue.size();\n          while (cnt-- > 1) {\n              queue.add(queue.poll());\n          }\n      }\n  \n      public int pop() {\n          return queue.remove();\n      }\n  \n      public int top() {\n          return queue.peek();\n      }\n  \n      public boolean empty() {\n          return queue.isEmpty();\n      }\n  }\n  ```\n\n  **最小值栈** \n\n  [155. Min Stack (Easy)](https://leetcode.com/problems/min-stack/description/)\n\n  ```java\n  class MinStack {\n  \n      private Stack<Integer> dataStack;\n      private Stack<Integer> minStack;\n      private int min;\n  \n      public MinStack() {\n          dataStack = new Stack<>();\n          minStack = new Stack<>();\n          min = Integer.MAX_VALUE;\n      }\n  \n      public void push(int x) {\n          dataStack.add(x);\n          min = Math.min(min, x);\n          minStack.add(min);\n      }\n  \n      public void pop() {\n          dataStack.pop();\n          minStack.pop();\n          min = minStack.isEmpty() ? Integer.MAX_VALUE : minStack.peek();\n      }\n  \n      public int top() {\n          return dataStack.peek();\n      }\n  \n      public int getMin() {\n          return minStack.peek();\n      }\n  }\n  ```\n\n  对于实现最小值队列问题，可以先将队列使用栈来实现，然后就将问题转换为最小值栈，这个问题出现在 编程之美：3.7。\n\n  **用栈实现括号匹配** \n\n  [20. Valid Parentheses (Easy)](https://leetcode.com/problems/valid-parentheses/description/)\n\n  ```html\n  \"()[]{}\"\n  \n  Output : true\n  ```\n\n  ```java\n  public boolean isValid(String s) {\n      Stack<Character> stack = new Stack<>();\n      for (char c : s.toCharArray()) {\n          if (c == '(' || c == '{' || c == '[') {\n              stack.push(c);\n          } else {\n              if (stack.isEmpty()) {\n                  return false;\n              }\n              char cStack = stack.pop();\n              boolean b1 = c == ')' && cStack != '(';\n              boolean b2 = c == ']' && cStack != '[';\n              boolean b3 = c == '}' && cStack != '{';\n              if (b1 || b2 || b3) {\n                  return false;\n              }\n          }\n      }\n      return stack.isEmpty();\n  }\n  ```\n\n  **数组中元素与下一个比它大的元素之间的距离** \n\n  [739. Daily Temperatures (Medium)](https://leetcode.com/problems/daily-temperatures/description/)\n\n  ```html\n  Input: [73, 74, 75, 71, 69, 72, 76, 73]\n  Output: [1, 1, 4, 2, 1, 1, 0, 0]\n  ```\n\n  在遍历数组时用 Stack 把数组中的数存起来，如果当前遍历的数比栈顶元素来的大，说明栈顶元素的下一个比它大的数就是当前元素。\n\n  ```java\n  public int[] dailyTemperatures(int[] temperatures) {\n      int n = temperatures.length;\n      int[] dist = new int[n];\n      Stack<Integer> indexs = new Stack<>();\n      for (int curIndex = 0; curIndex < n; curIndex++) {\n          while (!indexs.isEmpty() && temperatures[curIndex] > temperatures[indexs.peek()]) {\n              int preIndex = indexs.pop();\n              dist[preIndex] = curIndex - preIndex;\n          }\n          indexs.add(curIndex);\n      }\n      return dist;\n  }\n  ```\n\n  **循环数组中比当前元素大的下一个元素** \n\n  [503. Next Greater Element II (Medium)](https://leetcode.com/problems/next-greater-element-ii/description/)\n\n  ```text\n  Input: [1,2,1]\n  Output: [2,-1,2]\n  Explanation: The first 1's next greater number is 2; \n  The number 2 can't find next greater number; \n  The second 1's next greater number needs to search circularly, which is also 2.\n  ```\n\n  与 739. Daily Temperatures (Medium) 不同的是，数组是循环数组，并且最后要求的不是距离而是下一个元素。\n\n  ```java\n  public int[] nextGreaterElements(int[] nums) {\n      int n = nums.length;\n      int[] next = new int[n];\n      Arrays.fill(next, -1);\n      Stack<Integer> pre = new Stack<>();\n      for (int i = 0; i < n * 2; i++) {\n          int num = nums[i % n];\n          while (!pre.isEmpty() && nums[pre.peek()] < num) {\n              next[pre.pop()] = num;\n          }\n          if (i < n){\n              pre.push(i);\n          }\n      }\n      return next;\n  }\n  ```\n\n  ## 哈希表\n\n  哈希表使用 O(N) 空间复杂度存储数据，从而能够以 O(1) 时间复杂度求解问题。\n\n  Java 中的  **HashSet**  用于存储一个集合，可以查找元素是否在集合中。\n\n  如果元素有穷，并且范围不大，那么可以用一个布尔数组来存储一个元素是否存在。例如对于只有小写字符的元素，就可以用一个长度为 26 的布尔数组来存储一个字符集合，使得空间复杂度降低为 O(1)。\n\n  Java 中的  **HashMap**  主要用于映射关系，从而把两个元素联系起来。\n\n  在对一个内容进行压缩或者其它转换时，利用 HashMap 可以把原始内容和转换后的内容联系起来。例如在一个简化 url 的系统中[Leetcdoe : 535. Encode and Decode TinyURL (Medium)](https://leetcode.com/problems/encode-and-decode-tinyurl/description/)，利用 HashMap 就可以存储精简后的 url 到原始 url 的映射，使得不仅可以显示简化的 url，也可以根据简化的 url 得到原始 url 从而定位到正确的资源。\n\n  HashMap 也可以用来对元素进行计数统计，此时键为元素，值为计数。和 HashSet 类似，如果元素有穷并且范围不大，可以用整型数组来进行统计。\n\n  **数组中的两个数和为给定值** \n\n  [1. Two Sum (Easy)](https://leetcode.com/problems/two-sum/description/)\n\n  可以先对数组进行排序，然后使用双指针方法或者二分查找方法。这样做的时间复杂度为 O(NlogN)，空间复杂度为 O(1)。\n\n  用 HashMap 存储数组元素和索引的映射，在访问到 nums[i] 时，判断 HashMap 中是否存在 target - nums[i]，如果存在说明 target - nums[i] 所在的索引和 i 就是要找的两个数。该方法的时间复杂度为 O(N)，空间复杂度为 O(N)，使用空间来换取时间。\n\n  ```java\n  public int[] twoSum(int[] nums, int target) {\n      HashMap<Integer, Integer> indexForNum = new HashMap<>();\n      for (int i = 0; i < nums.length; i++) {\n          if (indexForNum.containsKey(target - nums[i])) {\n              return new int[]{indexForNum.get(target - nums[i]), i};\n          } else {\n              indexForNum.put(nums[i], i);\n          }\n      }\n      return null;\n  }\n  ```\n\n  **判断数组是否含有重复元素** \n\n  [217. Contains Duplicate (Easy)](https://leetcode.com/problems/contains-duplicate/description/)\n\n  ```java\n  public boolean containsDuplicate(int[] nums) {\n      Set<Integer> set = new HashSet<>();\n      for (int num : nums) {\n          set.add(num);\n      }\n      return set.size() < nums.length;\n  }\n  ```\n\n  **最长和谐序列** \n\n  [594. Longest Harmonious Subsequence (Easy)](https://leetcode.com/problems/longest-harmonious-subsequence/description/)\n\n  ```html\n  Input: [1,3,2,2,5,2,3,7]\n  Output: 5\n  Explanation: The longest harmonious subsequence is [3,2,2,2,3].\n  ```\n\n  和谐序列中最大数和最小数只差正好为 1，应该注意的是序列的元素不一定是数组的连续元素。\n\n  ```java\n  public int findLHS(int[] nums) {\n      Map<Integer, Integer> countForNum = new HashMap<>();\n      for (int num : nums) {\n          countForNum.put(num, countForNum.getOrDefault(num, 0) + 1);\n      }\n      int longest = 0;\n      for (int num : countForNum.keySet()) {\n          if (countForNum.containsKey(num + 1)) {\n              longest = Math.max(longest, countForNum.get(num + 1) + countForNum.get(num));\n          }\n      }\n      return longest;\n  }\n  ```\n\n  **最长连续序列** \n\n  [128. Longest Consecutive Sequence (Hard)](https://leetcode.com/problems/longest-consecutive-sequence/description/)\n\n  ```html\n  Given [100, 4, 200, 1, 3, 2],\n  The longest consecutive elements sequence is [1, 2, 3, 4]. Return its length: 4.\n  ```\n\n  要求以 O(N) 的时间复杂度求解。\n\n  ```java\n  public int longestConsecutive(int[] nums) {\n      Map<Integer, Integer> countForNum = new HashMap<>();\n      for (int num : nums) {\n          countForNum.put(num, 1);\n      }\n      for (int num : nums) {\n          forward(countForNum, num);\n      }\n      return maxCount(countForNum);\n  }\n  \n  private int forward(Map<Integer, Integer> countForNum, int num) {\n      if (!countForNum.containsKey(num)) {\n          return 0;\n      }\n      int cnt = countForNum.get(num);\n      if (cnt > 1) {\n          return cnt;\n      }\n      cnt = forward(countForNum, num + 1) + 1;\n      countForNum.put(num, cnt);\n      return cnt;\n  }\n  \n  private int maxCount(Map<Integer, Integer> countForNum) {\n      int max = 0;\n      for (int num : countForNum.keySet()) {\n          max = Math.max(max, countForNum.get(num));\n      }\n      return max;\n  }\n  ```\n\n  ## 字符串\n\n  **两个字符串包含的字符是否完全相同** \n\n  [242. Valid Anagram (Easy)](https://leetcode.com/problems/valid-anagram/description/)\n\n  ```html\n  s = \"anagram\", t = \"nagaram\", return true.\n  s = \"rat\", t = \"car\", return false.\n  ```\n\n  字符串只包含小写字符，总共有 26 个小写字符。可以用 HashMap 来映射字符与出现次数。因为键的范围很小，因此可以使用长度为 26 的整型数组对字符串出现的字符进行统计，然后比较两个字符串出现的字符数量是否相同。\n\n  ```java\n  public boolean isAnagram(String s, String t) {\n      int[] cnts = new int[26];\n      for (char c : s.toCharArray()) {\n          cnts[c - 'a']++;\n      }\n      for (char c : t.toCharArray()) {\n          cnts[c - 'a']--;\n      }\n      for (int cnt : cnts) {\n          if (cnt != 0) {\n              return false;\n          }\n      }\n      return true;\n  }\n  ```\n\n  **计算一组字符集合可以组成的回文字符串的最大长度** \n\n  [409. Longest Palindrome (Easy)](https://leetcode.com/problems/longest-palindrome/description/)\n\n  ```html\n  Input : \"abccccdd\"\n  Output : 7\n  Explanation : One longest palindrome that can be built is \"dccaccd\", whose length is 7.\n  ```\n\n  使用长度为 256 的整型数组来统计每个字符出现的个数，每个字符有偶数个可以用来构成回文字符串。\n\n  因为回文字符串最中间的那个字符可以单独出现，所以如果有单独的字符就把它放到最中间。\n\n  ```java\n  public int longestPalindrome(String s) {\n      int[] cnts = new int[256];\n      for (char c : s.toCharArray()) {\n          cnts[c]++;\n      }\n      int palindrome = 0;\n      for (int cnt : cnts) {\n          palindrome += (cnt / 2) * 2;\n      }\n      if (palindrome < s.length()) {\n          palindrome++;   // 这个条件下 s 中一定有单个未使用的字符存在，可以把这个字符放到回文的最中间\n      }\n      return palindrome;\n  }\n  ```\n\n  **字符串同构** \n\n  [205. Isomorphic Strings (Easy)](https://leetcode.com/problems/isomorphic-strings/description/)\n\n  ```html\n  Given \"egg\", \"add\", return true.\n  Given \"foo\", \"bar\", return false.\n  Given \"paper\", \"title\", return true.\n  ```\n\n  记录一个字符上次出现的位置，如果两个字符串中的字符上次出现的位置一样，那么就属于同构。\n\n  ```java\n  public boolean isIsomorphic(String s, String t) {\n      int[] preIndexOfS = new int[256];\n      int[] preIndexOfT = new int[256];\n      for (int i = 0; i < s.length(); i++) {\n          char sc = s.charAt(i), tc = t.charAt(i);\n          if (preIndexOfS[sc] != preIndexOfT[tc]) {\n              return false;\n          }\n          preIndexOfS[sc] = i + 1;\n          preIndexOfT[tc] = i + 1;\n      }\n      return true;\n  }\n  ```\n\n  **回文子字符串** \n\n  [647. Palindromic Substrings (Medium)](https://leetcode.com/problems/palindromic-substrings/description/)\n\n  ```html\n  Input: \"aaa\"\n  Output: 6\n  Explanation: Six palindromic strings: \"a\", \"a\", \"a\", \"aa\", \"aa\", \"aaa\".\n  ```\n\n  从字符串的某一位开始，尝试着去扩展子字符串。\n\n  ```java\n  private int cnt = 0;\n  \n  public int countSubstrings(String s) {\n      for (int i = 0; i < s.length(); i++) {\n          extendSubstrings(s, i, i);     // 奇数长度\n          extendSubstrings(s, i, i + 1); // 偶数长度\n      }\n      return cnt;\n  }\n  \n  private void extendSubstrings(String s, int start, int end) {\n      while (start >= 0 && end < s.length() && s.charAt(start) == s.charAt(end)) {\n          start--;\n          end++;\n          cnt++;\n      }\n  }\n  ```\n\n  **判断一个整数是否是回文数** \n\n  [9. Palindrome Number (Easy)](https://leetcode.com/problems/palindrome-number/description/)\n\n  要求不能使用额外空间，也就不能将整数转换为字符串进行判断。\n\n  将整数分成左右两部分，右边那部分需要转置，然后判断这两部分是否相等。\n\n  ```java\n  public boolean isPalindrome(int x) {\n      if (x == 0) {\n          return true;\n      }\n      if (x < 0 || x % 10 == 0) {\n          return false;\n      }\n      int right = 0;\n      while (x > right) {\n          right = right * 10 + x % 10;\n          x /= 10;\n      }\n      return x == right || x == right / 10;\n  }\n  ```\n\n  **统计二进制字符串中连续 1 和连续 0 数量相同的子字符串个数** \n\n  [696. Count Binary Substrings (Easy)](https://leetcode.com/problems/count-binary-substrings/description/)\n\n  ```html\n  Input: \"00110011\"\n  Output: 6\n  Explanation: There are 6 substrings that have equal number of consecutive 1's and 0's: \"0011\", \"01\", \"1100\", \"10\", \"0011\", and \"01\".\n  ```\n\n  ```java\n  public int countBinarySubstrings(String s) {\n      int preLen = 0, curLen = 1, count = 0;\n      for (int i = 1; i < s.length(); i++) {\n          if (s.charAt(i) == s.charAt(i - 1)) {\n              curLen++;\n          } else {\n              preLen = curLen;\n              curLen = 1;\n          }\n  \n          if (preLen >= curLen) {\n              count++;\n          }\n      }\n      return count;\n  }\n  ```\n\n  **字符串循环移位包含** \n\n  [编程之美：3.1](#)\n\n  ```html\n  s1 = AABCD, s2 = CDAA\n  Return : true\n  ```\n\n  给定两个字符串 s1 和 s2，要求判定 s2 是否能够被 s1 做循环移位得到的字符串包含。\n\n  s1 进行循环移位的结果是 s1s1 的子字符串，因此只要判断 s2 是否是 s1s1 的子字符串即可。\n\n  **字符串循环移位** \n\n  [编程之美：2.17](#)\n\n  ```html\n  s = \"abcd123\" k = 3\n  Return \"123abcd\"\n  ```\n\n  将字符串向右循环移动 k 位。\n\n  将 abcd123 中的 abcd 和 123 单独逆序，得到 dcba321，然后对整个字符串进行逆序，得到 123abcd。\n\n  **字符串中单词的翻转** \n\n  [程序员代码面试指南](#)\n\n  ```html\n  s = \"I am a student\"\n  return \"student a am I\"\n  ```\n\n  将每个单词逆序，然后将整个字符串逆序。\n\n  ## 数组与矩阵\n\n  **把数组中的 0 移到末尾** \n\n  [283. Move Zeroes (Easy)](https://leetcode.com/problems/move-zeroes/description/)\n\n  ```html\n  For example, given nums = [0, 1, 0, 3, 12], after calling your function, nums should be [1, 3, 12, 0, 0].\n  ```\n\n  ```java\n  public void moveZeroes(int[] nums) {\n      int idx = 0;\n      for (int num : nums) {\n          if (num != 0) {\n              nums[idx++] = num;\n          }\n      }\n      while (idx < nums.length) {\n          nums[idx++] = 0;\n      }\n  }\n  ```\n\n  **改变矩阵维度** \n\n  [566. Reshape the Matrix (Easy)](https://leetcode.com/problems/reshape-the-matrix/description/)\n\n  ```html\n  Input:\n  nums =\n  [[1,2],\n   [3,4]]\n  r = 1, c = 4\n  \n  Output:\n  [[1,2,3,4]]\n  \n  Explanation:\n  The row-traversing of nums is [1,2,3,4]. The new reshaped matrix is a 1 * 4 matrix, fill it row by row by using the previous list.\n  ```\n\n  ```java\n  public int[][] matrixReshape(int[][] nums, int r, int c) {\n      int m = nums.length, n = nums[0].length;\n      if (m * n != r * c) {\n          return nums;\n      }\n      int[][] reshapedNums = new int[r][c];\n      int index = 0;\n      for (int i = 0; i < r; i++) {\n          for (int j = 0; j < c; j++) {\n              reshapedNums[i][j] = nums[index / n][index % n];\n              index++;\n          }\n      }\n      return reshapedNums;\n  }\n  ```\n\n  **找出数组中最长的连续 1** \n\n  [485. Max Consecutive Ones (Easy)](https://leetcode.com/problems/max-consecutive-ones/description/)\n\n  ```java\n  public int findMaxConsecutiveOnes(int[] nums) {\n      int max = 0, cur = 0;\n      for (int x : nums) {\n          cur = x == 0 ? 0 : cur + 1;\n          max = Math.max(max, cur);\n      }\n      return max;\n  }\n  ```\n\n  **一个数组元素在 [1, n] 之间，其中一个数被替换为另一个数，找出重复的数和丢失的数** \n\n  [645. Set Mismatch (Easy)](https://leetcode.com/problems/set-mismatch/description/)\n\n  ```html\n  Input: nums = [1,2,2,4]\n  Output: [2,3]\n  ```\n\n  ```html\n  Input: nums = [1,2,2,4]\n  Output: [2,3]\n  ```\n\n  最直接的方法是先对数组进行排序，这种方法时间复杂度为 O(NlogN)。本题可以以 O(N) 的时间复杂度、O(1) 空间复杂度来求解。\n\n  主要思想是通过交换数组元素，使得数组上的元素在正确的位置上。遍历数组，如果第 i 位上的元素不是 i + 1，那么一直交换第 i 位和 nums[i] - 1 位置上的元素。\n\n  ```java\n  public int[] findErrorNums(int[] nums) {\n      for (int i = 0; i < nums.length; i++) {\n          while (nums[i] != i + 1 && nums[nums[i] - 1] != nums[i]) {\n              swap(nums, i, nums[i] - 1);\n          }\n      }\n      for (int i = 0; i < nums.length; i++) {\n          if (nums[i] != i + 1) {\n              return new int[]{nums[i], i + 1};\n          }\n      }\n      return null;\n  }\n  \n  private void swap(int[] nums, int i, int j) {\n      int tmp = nums[i];\n      nums[i] = nums[j];\n      nums[j] = tmp;\n  }\n  ```\n\n  类似题目：\n\n  - [448. Find All Numbers Disappeared in an Array (Easy)](https://leetcode.com/problems/find-all-numbers-disappeared-in-an-array/description/)，寻找所有丢失的元素\n  - [442. Find All Duplicates in an Array (Medium)](https://leetcode.com/problems/find-all-duplicates-in-an-array/description/)，寻找所有重复的元素。\n\n  **找出数组中重复的数，数组值在 [1, n] 之间** \n\n  [287. Find the Duplicate Number (Medium)](https://leetcode.com/problems/find-the-duplicate-number/description/)\n\n  要求不能修改数组，也不能使用额外的空间。\n\n  二分查找解法：\n\n  ```java\n  public int findDuplicate(int[] nums) {\n       int l = 1, h = nums.length - 1;\n       while (l <= h) {\n           int mid = l + (h - l) / 2;\n           int cnt = 0;\n           for (int i = 0; i < nums.length; i++) {\n               if (nums[i] <= mid) cnt++;\n           }\n           if (cnt > mid) h = mid - 1;\n           else l = mid + 1;\n       }\n       return l;\n  }\n  ```\n\n  双指针解法，类似于有环链表中找出环的入口：\n\n  ```java\n  public int findDuplicate(int[] nums) {\n      int slow = nums[0], fast = nums[nums[0]];\n      while (slow != fast) {\n          slow = nums[slow];\n          fast = nums[nums[fast]];\n      }\n      fast = 0;\n      while (slow != fast) {\n          slow = nums[slow];\n          fast = nums[fast];\n      }\n      return slow;\n  }\n  ```\n\n  **有序矩阵查找** \n\n  [240. Search a 2D Matrix II (Medium)](https://leetcode.com/problems/search-a-2d-matrix-ii/description/)\n\n  ```html\n  [\n     [ 1,  5,  9],\n     [10, 11, 13],\n     [12, 13, 15]\n  ]\n  ```\n\n  ```java\n  public boolean searchMatrix(int[][] matrix, int target) {\n      if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return false;\n      int m = matrix.length, n = matrix[0].length;\n      int row = 0, col = n - 1;\n      while (row < m && col >= 0) {\n          if (target == matrix[row][col]) return true;\n          else if (target < matrix[row][col]) col--;\n          else row++;\n      }\n      return false;\n  }\n  ```\n\n  **有序矩阵的 Kth Element** \n\n  [378. Kth Smallest Element in a Sorted Matrix ((Medium))](https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/description/)\n\n  ```html\n  matrix = [\n    [ 1,  5,  9],\n    [10, 11, 13],\n    [12, 13, 15]\n  ],\n  k = 8,\n  \n  return 13.\n  ```\n\n  解题参考：[Share my thoughts and Clean Java Code](https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/discuss/85173)\n\n  二分查找解法：\n\n  ```java\n  public int kthSmallest(int[][] matrix, int k) {\n      int m = matrix.length, n = matrix[0].length;\n      int lo = matrix[0][0], hi = matrix[m - 1][n - 1];\n      while(lo <= hi) {\n          int mid = lo + (hi - lo) / 2;\n          int cnt = 0;\n          for(int i = 0; i < m; i++) {\n              for(int j = 0; j < n && matrix[i][j] <= mid; j++) {\n                  cnt++;\n              }\n          }\n          if(cnt < k) lo = mid + 1;\n          else hi = mid - 1;\n      }\n      return lo;\n  }\n  ```\n\n  堆解法：\n\n  ```java\n  public int kthSmallest(int[][] matrix, int k) {\n      int m = matrix.length, n = matrix[0].length;\n      PriorityQueue<Tuple> pq = new PriorityQueue<Tuple>();\n      for(int j = 0; j < n; j++) pq.offer(new Tuple(0, j, matrix[0][j]));\n      for(int i = 0; i < k - 1; i++) { // 小根堆，去掉 k - 1 个堆顶元素，此时堆顶元素就是第 k 的数\n          Tuple t = pq.poll();\n          if(t.x == m - 1) continue;\n          pq.offer(new Tuple(t.x + 1, t.y, matrix[t.x + 1][t.y]));\n      }\n      return pq.poll().val;\n  }\n  \n  class Tuple implements Comparable<Tuple> {\n      int x, y, val;\n      public Tuple(int x, int y, int val) {\n          this.x = x; this.y = y; this.val = val;\n      }\n  \n      @Override\n      public int compareTo(Tuple that) {\n          return this.val - that.val;\n      }\n  }\n  ```\n\n  **数组相邻差值的个数** \n\n  [667. Beautiful Arrangement II (Medium)](https://leetcode.com/problems/beautiful-arrangement-ii/description/)\n\n  ```html\n  Input: n = 3, k = 2\n  Output: [1, 3, 2]\n  Explanation: The [1, 3, 2] has three different positive integers ranging from 1 to 3, and the [2, 1] has exactly 2 distinct integers: 1 and 2.\n  ```\n\n  题目描述：数组元素为 1\\~n 的整数，要求构建数组，使得相邻元素的差值不相同的个数为 k。\n\n  让前 k+1 个元素构建出 k 个不相同的差值，序列为：1 k+1 2 k 3 k-1 ... k/2 k/2+1.\n\n  ```java\n  public int[] constructArray(int n, int k) {\n      int[] ret = new int[n];\n      ret[0] = 1;\n      for (int i = 1, interval = k; i <= k; i++, interval--) {\n          ret[i] = i % 2 == 1 ? ret[i - 1] + interval : ret[i - 1] - interval;\n      }\n      for (int i = k + 1; i < n; i++) {\n          ret[i] = i + 1;\n      }\n      return ret;\n  }\n  ```\n\n  **数组的度** \n\n  [697. Degree of an Array (Easy)](https://leetcode.com/problems/degree-of-an-array/description/)\n\n  ```html\n  Input: [1,2,2,3,1,4,2]\n  Output: 6\n  ```\n\n  题目描述：数组的度定义为元素出现的最高频率，例如上面的数组度为 3。要求找到一个最小的子数组，这个子数组的度和原数组一样。\n\n  ```java\n  public int findShortestSubArray(int[] nums) {\n      Map<Integer, Integer> numsCnt = new HashMap<>();\n      Map<Integer, Integer> numsLastIndex = new HashMap<>();\n      Map<Integer, Integer> numsFirstIndex = new HashMap<>();\n      for (int i = 0; i < nums.length; i++) {\n          int num = nums[i];\n          numsCnt.put(num, numsCnt.getOrDefault(num, 0) + 1);\n          numsLastIndex.put(num, i);\n          if (!numsFirstIndex.containsKey(num)) {\n              numsFirstIndex.put(num, i);\n          }\n      }\n      int maxCnt = 0;\n      for (int num : nums) {\n          maxCnt = Math.max(maxCnt, numsCnt.get(num));\n      }\n      int ret = nums.length;\n      for (int i = 0; i < nums.length; i++) {\n          int num = nums[i];\n          int cnt = numsCnt.get(num);\n          if (cnt != maxCnt) continue;\n          ret = Math.min(ret, numsLastIndex.get(num) - numsFirstIndex.get(num) + 1);\n      }\n      return ret;\n  }\n  ```\n\n  **对角元素相等的矩阵** \n\n  [766. Toeplitz Matrix (Easy)](https://leetcode.com/problems/toeplitz-matrix/description/)\n\n  ```html\n  1234\n  5123\n  9512\n  \n  In the above grid, the diagonals are \"[9]\", \"[5, 5]\", \"[1, 1, 1]\", \"[2, 2, 2]\", \"[3, 3]\", \"[4]\", and in each diagonal all elements are the same, so the answer is True.\n  ```\n\n  ```java\n  public boolean isToeplitzMatrix(int[][] matrix) {\n      for (int i = 0; i < matrix[0].length; i++) {\n          if (!check(matrix, matrix[0][i], 0, i)) {\n              return false;\n          }\n      }\n      for (int i = 0; i < matrix.length; i++) {\n          if (!check(matrix, matrix[i][0], i, 0)) {\n              return false;\n          }\n      }\n      return true;\n  }\n  \n  private boolean check(int[][] matrix, int expectValue, int row, int col) {\n      if (row >= matrix.length || col >= matrix[0].length) {\n          return true;\n      }\n      if (matrix[row][col] != expectValue) {\n          return false;\n      }\n      return check(matrix, expectValue, row + 1, col + 1);\n  }\n  ```\n\n  **嵌套数组** \n\n  [565. Array Nesting (Medium)](https://leetcode.com/problems/array-nesting/description/)\n\n  ```html\n  Input: A = [5,4,0,3,1,6,2]\n  Output: 4\n  Explanation:\n  A[0] = 5, A[1] = 4, A[2] = 0, A[3] = 3, A[4] = 1, A[5] = 6, A[6] = 2.\n  \n  One of the longest S[K]:\n  S[0] = {A[0], A[5], A[6], A[2]} = {5, 6, 2, 0}\n  ```\n\n  题目描述：S[i] 表示一个集合，集合的第一个元素是 A[i]，第二个元素是 A[A[i]]，如此嵌套下去。求最大的 S[i]。\n\n  ```java\n  public int arrayNesting(int[] nums) {\n      int max = 0;\n      for (int i = 0; i < nums.length; i++) {\n          int cnt = 0;\n          for (int j = i; nums[j] != -1; ) {\n              cnt++;\n              int t = nums[j];\n              nums[j] = -1; // 标记该位置已经被访问\n              j = t;\n  \n          }\n          max = Math.max(max, cnt);\n      }\n      return max;\n  }\n  ```\n\n  **分隔数组** \n\n  [769. Max Chunks To Make Sorted (Medium)](https://leetcode.com/problems/max-chunks-to-make-sorted/description/)\n\n  ```html\n  Input: arr = [1,0,2,3,4]\n  Output: 4\n  Explanation:\n  We can split into two chunks, such as [1, 0], [2, 3, 4].\n  However, splitting into [1, 0], [2], [3], [4] is the highest number of chunks possible.\n  ```\n\n  题目描述：分隔数组，使得对每部分排序后数组就为有序。\n\n  ```java\n  public int maxChunksToSorted(int[] arr) {\n      if (arr == null) return 0;\n      int ret = 0;\n      int right = arr[0];\n      for (int i = 0; i < arr.length; i++) {\n          right = Math.max(right, arr[i]);\n          if (right == i) ret++;\n      }\n      return ret;\n  }\n  ```\n\n  ## 链表\n\n  链表是空节点，或者有一个值和一个指向下一个链表的指针，因此很多链表问题可以用递归来处理。\n\n  **找出两个链表的交点** \n\n  [160. Intersection of Two Linked Lists (Easy)](https://leetcode.com/problems/intersection-of-two-linked-lists/description/)\n\n  ```html\n  A:          a1 → a2\n                      ↘\n                        c1 → c2 → c3\n                      ↗\n  B:    b1 → b2 → b3\n  ```\n\n  要求：时间复杂度为 O(N)，空间复杂度为 O(1)\n\n  设 A 的长度为 a + c，B 的长度为 b + c，其中 c 为尾部公共部分长度，可知 a + c + b = b + c + a。\n\n  当访问 A 链表的指针访问到链表尾部时，令它从链表 B 的头部开始访问链表 B；同样地，当访问 B 链表的指针访问到链表尾部时，令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。\n\n  ```java\n  public ListNode getIntersectionNode(ListNode headA, ListNode headB) {\n      ListNode l1 = headA, l2 = headB;\n      while (l1 != l2) {\n          l1 = (l1 == null) ? headB : l1.next;\n          l2 = (l2 == null) ? headA : l2.next;\n      }\n      return l1;\n  }\n  ```\n\n  如果只是判断是否存在交点，那么就是另一个问题，即 [编程之美：3.6]() 的问题。有两种解法：把第一个链表的结尾连接到第二个链表的开头，看第二个链表是否存在环；或者直接比较两个链表的最后一个节点是否相同。\n\n  **链表反转** \n\n  [206. Reverse Linked List (Easy)](https://leetcode.com/problems/reverse-linked-list/description/)\n\n  递归\n\n  ```java\n  public ListNode reverseList(ListNode head) {\n      if (head == null || head.next == null) {\n          return head;\n      }\n      ListNode next = head.next;\n      ListNode newHead = reverseList(next);\n      next.next = head;\n      head.next = null;\n      return newHead;\n  }\n  ```\n\n  头插法\n\n  ```java\n  public ListNode reverseList(ListNode head) {\n      ListNode newHead = new ListNode(-1);\n      while (head != null) {\n          ListNode next = head.next;\n          head.next = newHead.next;\n          newHead.next = head;\n          head = next;\n      }\n      return newHead.next;\n  }\n  ```\n\n  **归并两个有序的链表** \n\n  [21. Merge Two Sorted Lists (Easy)](https://leetcode.com/problems/merge-two-sorted-lists/description/)\n\n  ```java\n  public ListNode mergeTwoLists(ListNode l1, ListNode l2) {\n      if (l1 == null) return l2;\n      if (l2 == null) return l1;\n      if (l1.val < l2.val) {\n          l1.next = mergeTwoLists(l1.next, l2);\n          return l1;\n      } else {\n          l2.next = mergeTwoLists(l1, l2.next);\n          return l2;\n      }\n  }\n  ```\n\n  **从有序链表中删除重复节点** \n\n  [83. Remove Duplicates from Sorted List (Easy)](https://leetcode.com/problems/remove-duplicates-from-sorted-list/description/)\n\n  ```html\n  Given 1->1->2, return 1->2.\n  Given 1->1->2->3->3, return 1->2->3.\n  ```\n\n  ```java\n  public ListNode deleteDuplicates(ListNode head) {\n      if(head == null || head.next == null) return head;\n      head.next = deleteDuplicates(head.next);\n      return head.val == head.next.val ? head.next : head;\n  }\n  ```\n\n  **删除链表的倒数第 n 个节点** \n\n  [19. Remove Nth Node From End of List (Medium)](https://leetcode.com/problems/remove-nth-node-from-end-of-list/description/)\n\n  ```html\n  Given linked list: 1->2->3->4->5, and n = 2.\n  After removing the second node from the end, the linked list becomes 1->2->3->5.\n  ```\n\n  ```java\n  public ListNode removeNthFromEnd(ListNode head, int n) {\n      ListNode fast = head;\n      while (n-- > 0) {\n          fast = fast.next;\n      }\n      if (fast == null) return head.next;\n      ListNode slow = head;\n      while (fast.next != null) {\n          fast = fast.next;\n          slow = slow.next;\n      }\n      slow.next = slow.next.next;\n      return head;\n  }\n  ```\n\n  **交换链表中的相邻结点** \n\n  [24. Swap Nodes in Pairs (Medium)](https://leetcode.com/problems/swap-nodes-in-pairs/description/)\n\n  ```html\n  Given 1->2->3->4, you should return the list as 2->1->4->3.\n  ```\n\n  题目要求：不能修改结点的 val 值，O(1) 空间复杂度。\n\n  ```java\n  public ListNode swapPairs(ListNode head) {\n      ListNode node = new ListNode(-1);\n      node.next = head;\n      ListNode pre = node;\n      while (pre.next != null && pre.next.next != null) {\n          ListNode l1 = pre.next, l2 = pre.next.next;\n          ListNode next = l2.next;\n          l1.next = next;\n          l2.next = l1;\n          pre.next = l2;\n  \n          pre = l1;\n      }\n      return node.next;\n  }\n  ```\n\n  **链表求和** \n\n  [445. Add Two Numbers II (Medium)](https://leetcode.com/problems/add-two-numbers-ii/description/)\n\n  ```html\n  Input: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)\n  Output: 7 -> 8 -> 0 -> 7\n  ```\n\n  题目要求：不能修改原始链表。\n\n  ```java\n  public ListNode addTwoNumbers(ListNode l1, ListNode l2) {\n      Stack<Integer> l1Stack = buildStack(l1);\n      Stack<Integer> l2Stack = buildStack(l2);\n      ListNode head = new ListNode(-1);\n      int carry = 0;\n      while (!l1Stack.isEmpty() || !l2Stack.isEmpty() || carry != 0) {\n          int x = l1Stack.isEmpty() ? 0 : l1Stack.pop();\n          int y = l2Stack.isEmpty() ? 0 : l2Stack.pop();\n          int sum = x + y + carry;\n          ListNode node = new ListNode(sum % 10);\n          node.next = head.next;\n          head.next = node;\n          carry = sum / 10;\n      }\n      return head.next;\n  }\n  \n  private Stack<Integer> buildStack(ListNode l) {\n      Stack<Integer> stack = new Stack<>();\n      while (l != null) {\n          stack.push(l.val);\n          l = l.next;\n      }\n      return stack;\n  }\n  ```\n\n  **回文链表** \n\n  [234. Palindrome Linked List (Easy)](https://leetcode.com/problems/palindrome-linked-list/description/)\n\n  题目要求：以 O(1) 的空间复杂度来求解。\n\n  切成两半，把后半段反转，然后比较两半是否相等。\n\n  ```java\n  public boolean isPalindrome(ListNode head) {\n      if (head == null || head.next == null) return true;\n      ListNode slow = head, fast = head.next;\n      while (fast != null && fast.next != null) {\n          slow = slow.next;\n          fast = fast.next.next;\n      }\n      if (fast != null) slow = slow.next;  // 偶数节点，让 slow 指向下一个节点\n      cut(head, slow);                     // 切成两个链表\n      return isEqual(head, reverse(slow));\n  }\n  \n  private void cut(ListNode head, ListNode cutNode) {\n      while (head.next != cutNode) {\n          head = head.next;\n      }\n      head.next = null;\n  }\n  \n  private ListNode reverse(ListNode head) {\n      ListNode newHead = null;\n      while (head != null) {\n          ListNode nextNode = head.next;\n          head.next = newHead;\n          newHead = head;\n          head = nextNode;\n      }\n      return newHead;\n  }\n  \n  private boolean isEqual(ListNode l1, ListNode l2) {\n      while (l1 != null && l2 != null) {\n          if (l1.val != l2.val) return false;\n          l1 = l1.next;\n          l2 = l2.next;\n      }\n      return true;\n  }\n  ```\n\n  **分隔链表** \n\n  [725. Split Linked List in Parts(Medium)](https://leetcode.com/problems/split-linked-list-in-parts/description/)\n\n  ```html\n  Input:\n  root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3\n  Output: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]\n  Explanation:\n  The input has been split into consecutive parts with size difference at most 1, and earlier parts are a larger size than the later parts.\n  ```\n\n  题目描述：把链表分隔成 k 部分，每部分的长度都应该尽可能相同，排在前面的长度应该大于等于后面的。\n\n  ```java\n  public ListNode[] splitListToParts(ListNode root, int k) {\n      int N = 0;\n      ListNode cur = root;\n      while (cur != null) {\n          N++;\n          cur = cur.next;\n      }\n      int mod = N % k;\n      int size = N / k;\n      ListNode[] ret = new ListNode[k];\n      cur = root;\n      for (int i = 0; cur != null && i < k; i++) {\n          ret[i] = cur;\n          int curSize = size + (mod-- > 0 ? 1 : 0);\n          for (int j = 0; j < curSize - 1; j++) {\n              cur = cur.next;\n          }\n          ListNode next = cur.next;\n          cur.next = null;\n          cur = next;\n      }\n      return ret;\n  }\n  ```\n\n  **链表元素按奇偶聚集** \n\n  [328. Odd Even Linked List (Medium)](https://leetcode.com/problems/odd-even-linked-list/description/)\n\n  ```html\n  Example:\n  Given 1->2->3->4->5->NULL,\n  return 1->3->5->2->4->NULL.\n  ```\n\n  ```java\n  public ListNode oddEvenList(ListNode head) {\n      if (head == null) {\n          return head;\n      }\n      ListNode odd = head, even = head.next, evenHead = even;\n      while (even != null && even.next != null) {\n          odd.next = odd.next.next;\n          odd = odd.next;\n          even.next = even.next.next;\n          even = even.next;\n      }\n      odd.next = evenHead;\n      return head;\n  }\n  ```\n\n  ## 树\n\n  ### 递归\n\n  一棵树要么是空树，要么有两个指针，每个指针指向一棵树。树是一种递归结构，很多树的问题可以使用递归来处理。\n\n  **树的高度** \n\n  [104. Maximum Depth of Binary Tree (Easy)](https://leetcode.com/problems/maximum-depth-of-binary-tree/description/)\n\n  ```java\n  public int maxDepth(TreeNode root) {\n      if (root == null) return 0;\n      return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;\n  }\n  ```\n\n  **平衡树** \n\n  [110. Balanced Binary Tree (Easy)](https://leetcode.com/problems/balanced-binary-tree/description/)\n\n  ```html\n      3\n     / \\\n    9  20\n      /  \\\n     15   7\n  ```\n\n  平衡树左右子树高度差都小于等于 1\n\n  ```java\n  private boolean result = true;\n  \n  public boolean isBalanced(TreeNode root) {\n      maxDepth(root);\n      return result;\n  }\n  \n  public int maxDepth(TreeNode root) {\n      if (root == null) return 0;\n      int l = maxDepth(root.left);\n      int r = maxDepth(root.right);\n      if (Math.abs(l - r) > 1) result = false;\n      return 1 + Math.max(l, r);\n  }\n  ```\n\n  **两节点的最长路径** \n\n  [543. Diameter of Binary Tree (Easy)](https://leetcode.com/problems/diameter-of-binary-tree/description/)\n\n  ```html\n  Input:\n  \n           1\n          / \\\n         2  3\n        / \\\n       4   5\n  \n  Return 3, which is the length of the path [4,2,1,3] or [5,2,1,3].\n  ```\n\n  ```java\n  private int max = 0;\n  \n  public int diameterOfBinaryTree(TreeNode root) {\n      depth(root);\n      return max;\n  }\n  \n  private int depth(TreeNode root) {\n      if (root == null) return 0;\n      int leftDepth = depth(root.left);\n      int rightDepth = depth(root.right);\n      max = Math.max(max, leftDepth + rightDepth);\n      return Math.max(leftDepth, rightDepth) + 1;\n  }\n  ```\n\n  **翻转树** \n\n  [226. Invert Binary Tree (Easy)](https://leetcode.com/problems/invert-binary-tree/description/)\n\n  ```java\n  public TreeNode invertTree(TreeNode root) {\n      if (root == null) return null;\n      TreeNode left = root.left;  // 后面的操作会改变 left 指针，因此先保存下来\n      root.left = invertTree(root.right);\n      root.right = invertTree(left);\n      return root;\n  }\n  ```\n\n  **归并两棵树** \n\n  [617. Merge Two Binary Trees (Easy)](https://leetcode.com/problems/merge-two-binary-trees/description/)\n\n  ```html\n  Input:\n         Tree 1                     Tree 2\n            1                         2\n           / \\                       / \\\n          3   2                     1   3\n         /                           \\   \\\n        5                             4   7\n  Output:\n  Merged tree:\n           3\n          / \\\n         4   5\n        / \\   \\\n       5   4   7\n  ```\n\n  ```java\n  public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {\n      if (t1 == null && t2 == null) return null;\n      if (t1 == null) return t2;\n      if (t2 == null) return t1;\n      TreeNode root = new TreeNode(t1.val + t2.val);\n      root.left = mergeTrees(t1.left, t2.left);\n      root.right = mergeTrees(t1.right, t2.right);\n      return root;\n  }\n  ```\n\n  **判断路径和是否等于一个数** \n\n  [Leetcdoe : 112. Path Sum (Easy)](https://leetcode.com/problems/path-sum/description/)\n\n  ```html\n  Given the below binary tree and sum = 22,\n                5\n               / \\\n              4   8\n             /   / \\\n            11  13  4\n           /  \\      \\\n          7    2      1\n  return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22.\n  ```\n\n  路径和定义为从 root 到 leaf 的所有节点的和\n\n  ```java\n  public boolean hasPathSum(TreeNode root, int sum) {\n      if (root == null) return false;\n      if (root.left == null && root.right == null && root.val == sum) return true;\n      return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);\n  }\n  ```\n\n  **统计路径和等于一个数的路径数量** \n\n  [437. Path Sum III (Easy)](https://leetcode.com/problems/path-sum-iii/description/)\n\n  ```html\n  root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8\n  \n        10\n       /  \\\n      5   -3\n     / \\    \\\n    3   2   11\n   / \\   \\\n  3  -2   1\n  \n  Return 3. The paths that sum to 8 are:\n  \n  1.  5 -> 3\n  2.  5 -> 2 -> 1\n  3. -3 -> 11\n  ```\n\n  路径不一定以 root 开头，也不一定以 leaf 结尾，但是必须连续。\n\n  ```java\n  public int pathSum(TreeNode root, int sum) {\n      if (root == null) return 0;\n      int ret = pathSumStartWithRoot(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum);\n      return ret;\n  }\n  \n  private int pathSumStartWithRoot(TreeNode root, int sum) {\n      if (root == null) return 0;\n      int ret = 0;\n      if (root.val == sum) ret++;\n      ret += pathSumStartWithRoot(root.left, sum - root.val) + pathSumStartWithRoot(root.right, sum - root.val);\n      return ret;\n  }\n  ```\n\n  **子树** \n\n  [572. Subtree of Another Tree (Easy)](https://leetcode.com/problems/subtree-of-another-tree/description/)\n\n  ```html\n  Given tree s:\n       3\n      / \\\n     4   5\n    / \\\n   1   2\n  \n  Given tree t:\n     4\n    / \\\n   1   2\n  \n  Return true, because t has the same structure and node values with a subtree of s.\n  \n  Given tree s:\n  \n       3\n      / \\\n     4   5\n    / \\\n   1   2\n      /\n     0\n  \n  Given tree t:\n     4\n    / \\\n   1   2\n  \n  Return false.\n  ```\n\n  ```java\n  public boolean isSubtree(TreeNode s, TreeNode t) {\n      if (s == null) return false;\n      return isSubtreeWithRoot(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t);\n  }\n  \n  private boolean isSubtreeWithRoot(TreeNode s, TreeNode t) {\n      if (t == null && s == null) return true;\n      if (t == null || s == null) return false;\n      if (t.val != s.val) return false;\n      return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right);\n  }\n  ```\n\n  **树的对称** \n\n  [101. Symmetric Tree (Easy)](https://leetcode.com/problems/symmetric-tree/description/)\n\n  ```html\n      1\n     / \\\n    2   2\n   / \\ / \\\n  3  4 4  3\n  ```\n\n  ```java\n  public boolean isSymmetric(TreeNode root) {\n      if (root == null) return true;\n      return isSymmetric(root.left, root.right);\n  }\n  \n  private boolean isSymmetric(TreeNode t1, TreeNode t2) {\n      if (t1 == null && t2 == null) return true;\n      if (t1 == null || t2 == null) return false;\n      if (t1.val != t2.val) return false;\n      return isSymmetric(t1.left, t2.right) && isSymmetric(t1.right, t2.left);\n  }\n  ```\n\n  **最小路径** \n\n  [111. Minimum Depth of Binary Tree (Easy)](https://leetcode.com/problems/minimum-depth-of-binary-tree/description/)\n\n  树的根节点到叶子节点的最小路径长度\n\n  ```java\n  public int minDepth(TreeNode root) {\n      if (root == null) return 0;\n      int left = minDepth(root.left);\n      int right = minDepth(root.right);\n      if (left == 0 || right == 0) return left + right + 1;\n      return Math.min(left, right) + 1;\n  }\n  ```\n\n  **统计左叶子节点的和** \n\n  [404. Sum of Left Leaves (Easy)](https://leetcode.com/problems/sum-of-left-leaves/description/)\n\n  ```html\n      3\n     / \\\n    9  20\n      /  \\\n     15   7\n  \n  There are two left leaves in the binary tree, with values 9 and 15 respectively. Return 24.\n  ```\n\n  ```java\n  public int sumOfLeftLeaves(TreeNode root) {\n      if (root == null) return 0;\n      if (isLeaf(root.left)) return root.left.val + sumOfLeftLeaves(root.right);\n      return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right);\n  }\n  \n  private boolean isLeaf(TreeNode node){\n      if (node == null) return false;\n      return node.left == null && node.right == null;\n  }\n  ```\n\n  **相同节点值的最大路径长度** \n\n  [687. Longest Univalue Path (Easy)](https://leetcode.com/problems/longest-univalue-path/)\n\n  ```html\n               1\n              / \\\n             4   5\n            / \\   \\\n           4   4   5\n  \n  Output : 2\n  ```\n\n  ```java\n  private int path = 0;\n  \n  public int longestUnivaluePath(TreeNode root) {\n      dfs(root);\n      return path;\n  }\n  \n  private int dfs(TreeNode root){\n      if (root == null) return 0;\n      int left = dfs(root.left);\n      int right = dfs(root.right);\n      int leftPath = root.left != null && root.left.val == root.val ? left + 1 : 0;\n      int rightPath = root.right != null && root.right.val == root.val ? right + 1 : 0;\n      path = Math.max(path, leftPath + rightPath);\n      return Math.max(leftPath, rightPath);\n  }\n  ```\n\n  **间隔遍历** \n\n  [337. House Robber III (Medium)](https://leetcode.com/problems/house-robber-iii/description/)\n\n  ```html\n       3\n      / \\\n     2   3\n      \\   \\\n       3   1\n  Maximum amount of money the thief can rob = 3 + 3 + 1 = 7.\n  ```\n\n  ```java\n  public int rob(TreeNode root) {\n      if (root == null) return 0;\n      int val1 = root.val;\n      if (root.left != null) val1 += rob(root.left.left) + rob(root.left.right);\n      if (root.right != null) val1 += rob(root.right.left) + rob(root.right.right);\n      int val2 = rob(root.left) + rob(root.right);\n      return Math.max(val1, val2);\n  }\n  ```\n\n  **找出二叉树中第二小的节点** \n\n  [671. Second Minimum Node In a Binary Tree (Easy)](https://leetcode.com/problems/second-minimum-node-in-a-binary-tree/description/)\n\n  ```html\n  Input:\n     2\n    / \\\n   2   5\n      / \\\n      5  7\n  \n  Output: 5\n  ```\n\n  一个节点要么具有 0 个或 2 个子节点，如果有子节点，那么根节点是最小的节点。\n\n  ```java\n  public int findSecondMinimumValue(TreeNode root) {\n      if (root == null) return -1;\n      if (root.left == null && root.right == null) return -1;\n      int leftVal = root.left.val;\n      int rightVal = root.right.val;\n      if (leftVal == root.val) leftVal = findSecondMinimumValue(root.left);\n      if (rightVal == root.val) rightVal = findSecondMinimumValue(root.right);\n      if (leftVal != -1 && rightVal != -1) return Math.min(leftVal, rightVal);\n      if (leftVal != -1) return leftVal;\n      return rightVal;\n  }\n  ```\n\n  ### 层次遍历\n\n  使用 BFS 进行层次遍历。不需要使用两个队列来分别存储当前层的节点和下一层的节点，因为在开始遍历一层的节点时，当前队列中的节点数就是当前层的节点数，只要控制遍历这么多节点数，就能保证这次遍历的都是当前层的节点。\n\n  **一棵树每层节点的平均数** \n\n  [637. Average of Levels in Binary Tree (Easy)](https://leetcode.com/problems/average-of-levels-in-binary-tree/description/)\n\n  ```java\n  public List<Double> averageOfLevels(TreeNode root) {\n      List<Double> ret = new ArrayList<>();\n      if (root == null) return ret;\n      Queue<TreeNode> queue = new LinkedList<>();\n      queue.add(root);\n      while (!queue.isEmpty()) {\n          int cnt = queue.size();\n          double sum = 0;\n          for (int i = 0; i < cnt; i++) {\n              TreeNode node = queue.poll();\n              sum += node.val;\n              if (node.left != null) queue.add(node.left);\n              if (node.right != null) queue.add(node.right);\n          }\n          ret.add(sum / cnt);\n      }\n      return ret;\n  }\n  ```\n\n  **得到左下角的节点** \n\n  [513. Find Bottom Left Tree Value (Easy)](https://leetcode.com/problems/find-bottom-left-tree-value/description/)\n\n  ```html\n  Input:\n  \n          1\n         / \\\n        2   3\n       /   / \\\n      4   5   6\n         /\n        7\n  \n  Output:\n  7\n  ```\n\n  ```java\n  public int findBottomLeftValue(TreeNode root) {\n      Queue<TreeNode> queue = new LinkedList<>();\n      queue.add(root);\n      while (!queue.isEmpty()) {\n          root = queue.poll();\n          if (root.right != null) queue.add(root.right);\n          if (root.left != null) queue.add(root.left);\n      }\n      return root.val;\n  }\n  ```\n\n  ### 前中后序遍历\n\n  ```html\n      1\n     / \\\n    2   3\n   / \\   \\\n  4   5   6\n  ```\n\n  - 层次遍历顺序：[1 2 3 4 5 6]\n  - 前序遍历顺序：[1 2 4 5 3 6]\n  - 中序遍历顺序：[4 2 5 1 3 6]\n  - 后序遍历顺序：[4 5 2 6 3 1]\n\n  层次遍历使用 BFS 实现，利用的就是 BFS 一层一层遍历的特性；而前序、中序、后序遍历利用了 DFS 实现。\n\n  前序、中序、后序遍只是在对节点访问的顺序有一点不同，其它都相同。\n\n  ① 前序\n\n  ```java\n  void dfs(TreeNode root) {\n      visit(root);\n      dfs(root.left);\n      dfs(root.right);\n  }\n  ```\n\n  ② 中序\n\n  ```java\n  void dfs(TreeNode root) {\n      dfs(root.left);\n      visit(root);\n      dfs(root.right);\n  }\n  ```\n\n  ③ 后序\n\n  ```java\n  void dfs(TreeNode root) {\n      dfs(root.left);\n      dfs(root.right);\n      visit(root);\n  }\n  ```\n\n  **非递归实现二叉树的前序遍历** \n\n  [144. Binary Tree Preorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-preorder-traversal/description/)\n\n  ```java\n  public List<Integer> preorderTraversal(TreeNode root) {\n      List<Integer> ret = new ArrayList<>();\n      Stack<TreeNode> stack = new Stack<>();\n      stack.push(root);\n      while (!stack.isEmpty()) {\n          TreeNode node = stack.pop();\n          if (node == null) continue;\n          ret.add(node.val);\n          stack.push(node.right);  // 先右后左，保证左子树先遍历\n          stack.push(node.left);\n      }\n      return ret;\n  }\n  ```\n\n  **非递归实现二叉树的后序遍历** \n\n  [145. Binary Tree Postorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-postorder-traversal/description/)\n\n  前序遍历为 root -> left -> right，后序遍历为 left -> right -> root，可以修改前序遍历成为 root -> right -> left，那么这个顺序就和后序遍历正好相反。\n\n  ```java\n  public List<Integer> postorderTraversal(TreeNode root) {\n      List<Integer> ret = new ArrayList<>();\n      Stack<TreeNode> stack = new Stack<>();\n      stack.push(root);\n      while (!stack.isEmpty()) {\n          TreeNode node = stack.pop();\n          if (node == null) continue;\n          ret.add(node.val);\n          stack.push(node.left);\n          stack.push(node.right);\n      }\n      Collections.reverse(ret);\n      return ret;\n  }\n  ```\n\n  **非递归实现二叉树的中序遍历** \n\n  [94. Binary Tree Inorder Traversal (Medium)](https://leetcode.com/problems/binary-tree-inorder-traversal/description/)\n\n  ```java\n  public List<Integer> inorderTraversal(TreeNode root) {\n      List<Integer> ret = new ArrayList<>();\n      if (root == null) return ret;\n      Stack<TreeNode> stack = new Stack<>();\n      TreeNode cur = root;\n      while (cur != null || !stack.isEmpty()) {\n          while (cur != null) {\n              stack.push(cur);\n              cur = cur.left;\n          }\n          TreeNode node = stack.pop();\n          ret.add(node.val);\n          cur = node.right;\n      }\n      return ret;\n  }\n  ```\n\n  ### BST\n\n  二叉查找树（BST）：根节点大于等于左子树所有节点，小于等于右子树所有节点。\n\n  二叉查找树中序遍历有序。\n\n  **修剪二叉查找树** \n\n  [669. Trim a Binary Search Tree (Easy)](https://leetcode.com/problems/trim-a-binary-search-tree/description/)\n\n  ```html\n  Input:\n  \n      3\n     / \\\n    0   4\n     \\\n      2\n     /\n    1\n  \n    L = 1\n    R = 3\n  \n  Output:\n  \n        3\n       /\n     2\n    /\n   1\n  ```\n\n  题目描述：只保留值在 L \\~ R 之间的节点\n\n  ```java\n  public TreeNode trimBST(TreeNode root, int L, int R) {\n      if (root == null) return null;\n      if (root.val > R) return trimBST(root.left, L, R);\n      if (root.val < L) return trimBST(root.right, L, R);\n      root.left = trimBST(root.left, L, R);\n      root.right = trimBST(root.right, L, R);\n      return root;\n  }\n  ```\n\n  **寻找二叉查找树的第 k 个元素** \n\n  [230. Kth Smallest Element in a BST (Medium)](https://leetcode.com/problems/kth-smallest-element-in-a-bst/description/)\n\n\n  中序遍历解法：\n\n  ```java\n  private int cnt = 0;\n  private int val;\n  \n  public int kthSmallest(TreeNode root, int k) {\n      inOrder(root, k);\n      return val;\n  }\n  \n  private void inOrder(TreeNode node, int k) {\n      if (node == null) return;\n      inOrder(node.left, k);\n      cnt++;\n      if (cnt == k) {\n          val = node.val;\n          return;\n      }\n      inOrder(node.right, k);\n  }\n  ```\n\n  递归解法：\n\n  ```java\n  public int kthSmallest(TreeNode root, int k) {\n      int leftCnt = count(root.left);\n      if (leftCnt == k - 1) return root.val;\n      if (leftCnt > k - 1) return kthSmallest(root.left, k);\n      return kthSmallest(root.right, k - leftCnt - 1);\n  }\n  \n  private int count(TreeNode node) {\n      if (node == null) return 0;\n      return 1 + count(node.left) + count(node.right);\n  }\n  ```\n\n  **把二叉查找树每个节点的值都加上比它大的节点的值** \n\n  [Convert BST to Greater Tree (Easy)](https://leetcode.com/problems/convert-bst-to-greater-tree/description/)\n\n  ```html\n  Input: The root of a Binary Search Tree like this:\n  \n                5\n              /   \\\n             2     13\n  \n  Output: The root of a Greater Tree like this:\n  \n               18\n              /   \\\n            20     13\n  ```\n\n  先遍历右子树。\n\n  ```java\n  private int sum = 0;\n  \n  public TreeNode convertBST(TreeNode root) {\n      traver(root);\n      return root;\n  }\n  \n  private void traver(TreeNode node) {\n      if (node == null) return;\n      traver(node.right);\n      sum += node.val;\n      node.val = sum;\n      traver(node.left);\n  }\n  ```\n\n  **二叉查找树的最近公共祖先** \n\n  [235. Lowest Common Ancestor of a Binary Search Tree (Easy)](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/description/)\n\n  ```html\n          _______6______\n        /                \\\n    ___2__             ___8__\n   /      \\           /      \\\n  0        4         7        9\n          /  \\\n         3   5\n  \n  For example, the lowest common ancestor (LCA) of nodes 2 and 8 is 6. Another example is LCA of nodes 2 and 4 is 2, since a node can be a descendant of itself according to the LCA definition.\n  ```\n\n  ```java\n  public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {\n      if (root.val > p.val && root.val > q.val) return lowestCommonAncestor(root.left, p, q);\n      if (root.val < p.val && root.val < q.val) return lowestCommonAncestor(root.right, p, q);\n      return root;\n  }\n  ```\n\n  **二叉树的最近公共祖先** \n\n  [236. Lowest Common Ancestor of a Binary Tree (Medium) ](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/)\n\n  ```html\n         _______3______\n        /              \\\n    ___5__           ___1__\n   /      \\         /      \\\n  6        2       0        8\n          /  \\\n         7    4\n  \n  For example, the lowest common ancestor (LCA) of nodes 5 and 1 is 3. Another example is LCA of nodes 5 and 4 is 5, since a node can be a descendant of itself according to the LCA definition.\n  ```\n\n  ```java\n  public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {\n      if (root == null || root == p || root == q) return root;\n      TreeNode left = lowestCommonAncestor(root.left, p, q);\n      TreeNode right = lowestCommonAncestor(root.right, p, q);\n      return left == null ? right : right == null ? left : root;\n  }\n  ```\n\n  **从有序数组中构造二叉查找树** \n\n  [108. Convert Sorted Array to Binary Search Tree (Easy)](https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/description/)\n\n  ```java\n  public TreeNode sortedArrayToBST(int[] nums) {\n      return toBST(nums, 0, nums.length - 1);\n  }\n  \n  private TreeNode toBST(int[] nums, int sIdx, int eIdx){\n      if (sIdx > eIdx) return null;\n      int mIdx = (sIdx + eIdx) / 2;\n      TreeNode root = new TreeNode(nums[mIdx]);\n      root.left =  toBST(nums, sIdx, mIdx - 1);\n      root.right = toBST(nums, mIdx + 1, eIdx);\n      return root;\n  }\n  ```\n\n  **根据有序链表构造平衡的二叉查找树** \n\n  [109. Convert Sorted List to Binary Search Tree (Medium)](https://leetcode.com/problems/convert-sorted-list-to-binary-search-tree/description/)\n\n  ```html\n  Given the sorted linked list: [-10,-3,0,5,9],\n  \n  One possible answer is: [0,-3,9,-10,null,5], which represents the following height balanced BST:\n  \n        0\n       / \\\n     -3   9\n     /   /\n   -10  5\n  ```\n\n  ```java\n  public TreeNode sortedListToBST(ListNode head) {\n      if (head == null) return null;\n      if (head.next == null) return new TreeNode(head.val);\n      ListNode preMid = preMid(head);\n      ListNode mid = preMid.next; \n      preMid.next = null;  // 断开链表\n      TreeNode t = new TreeNode(mid.val);\n      t.left = sortedListToBST(head);\n      t.right = sortedListToBST(mid.next);\n      return t;\n  }\n  \n  private ListNode preMid(ListNode head) {\n      ListNode slow = head, fast = head.next;\n      ListNode pre = head;\n      while (fast != null && fast.next != null) {\n          pre = slow;\n          slow = slow.next;\n          fast = fast.next.next;\n      }\n      return pre;\n  }\n  ```\n\n  **在二叉查找树中寻找两个节点，使它们的和为一个给定值** \n\n  [653. Two Sum IV - Input is a BST (Easy)](https://leetcode.com/problems/two-sum-iv-input-is-a-bst/description/)\n\n  ```html\n  Input:\n  \n      5\n     / \\\n    3   6\n   / \\   \\\n  2   4   7\n  \n  Target = 9\n  \n  Output: True\n  ```\n\n  使用中序遍历得到有序数组之后，再利用双指针对数组进行查找。\n\n  应该注意到，这一题不能用分别在左右子树两部分来处理这种思想，因为两个待求的节点可能分别在左右子树中。\n\n  ```java\n  public boolean findTarget(TreeNode root, int k) {\n      List<Integer> nums = new ArrayList<>();\n      inOrder(root, nums);\n      int i = 0, j = nums.size() - 1;\n      while (i < j) {\n          int sum = nums.get(i) + nums.get(j);\n          if (sum == k) return true;\n          if (sum < k) i++;\n          else j--;\n      }\n      return false;\n  }\n  \n  private void inOrder(TreeNode root, List<Integer> nums) {\n      if (root == null) return;\n      inOrder(root.left, nums);\n      nums.add(root.val);\n      inOrder(root.right, nums);\n  }\n  ```\n\n  **在二叉查找树中查找两个节点之差的最小绝对值** \n\n  [530. Minimum Absolute Difference in BST (Easy)](https://leetcode.com/problems/minimum-absolute-difference-in-bst/description/)\n\n  ```html\n  Input:\n  \n     1\n      \\\n       3\n      /\n     2\n  \n  Output:\n  \n  1\n  ```\n\n  利用二叉查找树的中序遍历为有序的性质，计算中序遍历中临近的两个节点之差的绝对值，取最小值。\n\n  ```java\n  private int minDiff = Integer.MAX_VALUE;\n  private TreeNode preNode = null;\n  \n  public int getMinimumDifference(TreeNode root) {\n      inOrder(root);\n      return minDiff;\n  }\n  \n  private void inOrder(TreeNode node) {\n      if (node == null) return;\n      inOrder(node.left);\n      if (preNode != null) minDiff = Math.min(minDiff, node.val - preNode.val);\n      preNode = node;\n      inOrder(node.right);\n  }\n  ```\n\n  **寻找二叉查找树中出现次数最多的值** \n\n  [501. Find Mode in Binary Search Tree (Easy)](https://leetcode.com/problems/find-mode-in-binary-search-tree/description/)\n\n  ```html\n     1\n      \\\n       2\n      /\n     2\n  \n  return [2].\n  ```\n\n  答案可能不止一个，也就是有多个值出现的次数一样多，并且是最大的。\n\n  ```java\n  private int curCnt = 1;\n  private int maxCnt = 1;\n  private TreeNode preNode = null;\n  \n  public int[] findMode(TreeNode root) {\n      List<Integer> maxCntNums = new ArrayList<>();\n      inOrder(root, maxCntNums);\n      int[] ret = new int[maxCntNums.size()];\n      int idx = 0;\n      for (int num : maxCntNums) {\n          ret[idx++] = num;\n      }\n      return ret;\n  }\n  \n  private void inOrder(TreeNode node, List<Integer> nums) {\n      if (node == null) return;\n      inOrder(node.left, nums);\n      if (preNode != null) {\n          if (preNode.val == node.val) curCnt++;\n          else curCnt = 1;\n      }\n      if (curCnt > maxCnt) {\n          maxCnt = curCnt;\n          nums.clear();\n          nums.add(node.val);\n      } else if (curCnt == maxCnt) {\n          nums.add(node.val);\n      }\n      preNode = node;\n      inOrder(node.right, nums);\n  }\n  ```\n\n  ### Trie\n\n  <div align=\"center\"> <img src=\"../pics//5c638d59-d4ae-4ba4-ad44-80bdc30f38dd.jpg\"/> </div><br>\n\n  Trie，又称前缀树或字典树，用于判断字符串是否存在或者是否具有某种字符串前缀。\n\n  **实现一个 Trie** \n\n  [208. Implement Trie (Prefix Tree) (Medium)](https://leetcode.com/problems/implement-trie-prefix-tree/description/)\n\n  ```java\n  class Trie {\n  \n      private class Node {\n          Node[] childs = new Node[26];\n          boolean isLeaf;\n      }\n  \n      private Node root = new Node();\n  \n      public Trie() {\n      }\n  \n      public void insert(String word) {\n          insert(word, root);\n      }\n  \n      private void insert(String word, Node node) {\n          if (node == null) return;\n          if (word.length() == 0) {\n              node.isLeaf = true;\n              return;\n          }\n          int index = indexForChar(word.charAt(0));\n          if (node.childs[index] == null) {\n              node.childs[index] = new Node();\n          }\n          insert(word.substring(1), node.childs[index]);\n      }\n  \n      public boolean search(String word) {\n          return search(word, root);\n      }\n  \n      private boolean search(String word, Node node) {\n          if (node == null) return false;\n          if (word.length() == 0) return node.isLeaf;\n          int index = indexForChar(word.charAt(0));\n          return search(word.substring(1), node.childs[index]);\n      }\n  \n      public boolean startsWith(String prefix) {\n          return startWith(prefix, root);\n      }\n  \n      private boolean startWith(String prefix, Node node) {\n          if (node == null) return false;\n          if (prefix.length() == 0) return true;\n          int index = indexForChar(prefix.charAt(0));\n          return startWith(prefix.substring(1), node.childs[index]);\n      }\n  \n      private int indexForChar(char c) {\n          return c - 'a';\n      }\n  }\n  ```\n\n  **实现一个 Trie，用来求前缀和** \n\n  [677. Map Sum Pairs (Medium)](https://leetcode.com/problems/map-sum-pairs/description/)\n\n  ```html\n  Input: insert(\"apple\", 3), Output: Null\n  Input: sum(\"ap\"), Output: 3\n  Input: insert(\"app\", 2), Output: Null\n  Input: sum(\"ap\"), Output: 5\n  ```\n\n  ```java\n  class MapSum {\n  \n      private class Node {\n          Node[] child = new Node[26];\n          int value;\n      }\n  \n      private Node root = new Node();\n  \n      public MapSum() {\n  \n      }\n  \n      public void insert(String key, int val) {\n          insert(key, root, val);\n      }\n  \n      private void insert(String key, Node node, int val) {\n          if (node == null) return;\n          if (key.length() == 0) {\n              node.value = val;\n              return;\n          }\n          int index = indexForChar(key.charAt(0));\n          if (node.child[index] == null) {\n              node.child[index] = new Node();\n          }\n          insert(key.substring(1), node.child[index], val);\n      }\n  \n      public int sum(String prefix) {\n          return sum(prefix, root);\n      }\n  \n      private int sum(String prefix, Node node) {\n          if (node == null) return 0;\n          if (prefix.length() != 0) {\n              int index = indexForChar(prefix.charAt(0));\n              return sum(prefix.substring(1), node.child[index]);\n          }\n          int sum = node.value;\n          for (Node child : node.child) {\n              sum += sum(prefix, child);\n          }\n          return sum;\n      }\n  \n      private int indexForChar(char c) {\n          return c - 'a';\n      }\n  }\n  ```\n\n  ## 图\n\n  ### 二分图\n\n  如果可以用两种颜色对图中的节点进行着色，并且保证相邻的节点颜色不同，那么这个图就是二分图。\n\n  **判断是否为二分图** \n\n  [785. Is Graph Bipartite? (Medium)](https://leetcode.com/problems/is-graph-bipartite/description/)\n\n  ```html\n  Input: [[1,3], [0,2], [1,3], [0,2]]\n  Output: true\n  Explanation:\n  The graph looks like this:\n  0----1\n  |    |\n  |    |\n  3----2\n  We can divide the vertices into two groups: {0, 2} and {1, 3}.\n  ```\n\n  ```html\n  Example 2:\n  Input: [[1,2,3], [0,2], [0,1,3], [0,2]]\n  Output: false\n  Explanation:\n  The graph looks like this:\n  0----1\n  | \\  |\n  |  \\ |\n  3----2\n  We cannot find a way to divide the set of nodes into two independent subsets.\n  ```\n\n  ```java\n  public boolean isBipartite(int[][] graph) {\n      int[] colors = new int[graph.length];\n      Arrays.fill(colors, -1);\n      for (int i = 0; i < graph.length; i++) {  // 处理图不是连通的情况\n          if (colors[i] == -1 && !isBipartite(i, 0, colors, graph)) {\n              return false;\n          }\n      }\n      return true;\n  }\n  \n  private boolean isBipartite(int curNode, int curColor, int[] colors, int[][] graph) {\n      if (colors[curNode] != -1) {\n          return colors[curNode] == curColor;\n      }\n      colors[curNode] = curColor;\n      for (int nextNode : graph[curNode]) {\n          if (!isBipartite(nextNode, 1 - curColor, colors, graph)) {\n              return false;\n          }\n      }\n      return true;\n  }\n  ```\n\n  ### 拓扑排序\n\n  常用于在具有先序关系的任务规划中。\n\n  **课程安排的合法性** \n\n  [207. Course Schedule (Medium)](https://leetcode.com/problems/course-schedule/description/)\n\n  ```html\n  2, [[1,0]]\n  return true\n  ```\n\n  ```html\n  2, [[1,0],[0,1]]\n  return false\n  ```\n\n  题目描述：一个课程可能会先修课程，判断给定的先修课程规定是否合法。\n\n  本题不需要使用拓扑排序，只需要检测有向图是否存在环即可。\n\n  ```java\n  public boolean canFinish(int numCourses, int[][] prerequisites) {\n      List<Integer>[] graphic = new List[numCourses];\n      for (int i = 0; i < numCourses; i++) {\n          graphic[i] = new ArrayList<>();\n      }\n      for (int[] pre : prerequisites) {\n          graphic[pre[0]].add(pre[1]);\n      }\n      boolean[] globalMarked = new boolean[numCourses];\n      boolean[] localMarked = new boolean[numCourses];\n      for (int i = 0; i < numCourses; i++) {\n          if (hasCycle(globalMarked, localMarked, graphic, i)) {\n              return false;\n          }\n      }\n      return true;\n  }\n  \n  private boolean hasCycle(boolean[] globalMarked, boolean[] localMarked,\n                           List<Integer>[] graphic, int curNode) {\n  \n      if (localMarked[curNode]) {\n          return true;\n      }\n      if (globalMarked[curNode]) {\n          return false;\n      }\n      globalMarked[curNode] = true;\n      localMarked[curNode] = true;\n      for (int nextNode : graphic[curNode]) {\n          if (hasCycle(globalMarked, localMarked, graphic, nextNode)) {\n              return true;\n          }\n      }\n      localMarked[curNode] = false;\n      return false;\n  }\n  ```\n\n  **课程安排的顺序** \n\n  [210. Course Schedule II (Medium)](https://leetcode.com/problems/course-schedule-ii/description/)\n\n  ```html\n  4, [[1,0],[2,0],[3,1],[3,2]]\n  There are a total of 4 courses to take. To take course 3 you should have finished both courses 1 and 2. Both courses 1 and 2 should be taken after you finished course 0. So one correct course order is [0,1,2,3]. Another correct ordering is[0,2,1,3].\n  ```\n\n  使用 DFS 来实现拓扑排序，使用一个栈存储后序遍历结果，这个栈的逆序结果就是拓扑排序结果。\n\n  证明：对于任何先序关系：v->w，后序遍历结果可以保证 w 先进入栈中，因此栈的逆序结果中 v 会在 w 之前。\n\n  ```java\n  public int[] findOrder(int numCourses, int[][] prerequisites) {\n      List<Integer>[] graphic = new List[numCourses];\n      for (int i = 0; i < numCourses; i++) {\n          graphic[i] = new ArrayList<>();\n      }\n      for (int[] pre : prerequisites) {\n          graphic[pre[0]].add(pre[1]);\n      }\n      Stack<Integer> postOrder = new Stack<>();\n      boolean[] globalMarked = new boolean[numCourses];\n      boolean[] localMarked = new boolean[numCourses];\n      for (int i = 0; i < numCourses; i++) {\n          if (hasCycle(globalMarked, localMarked, graphic, i, postOrder)) {\n              return new int[0];\n          }\n      }\n      int[] orders = new int[numCourses];\n      for (int i = numCourses - 1; i >= 0; i--) {\n          orders[i] = postOrder.pop();\n      }\n      return orders;\n  }\n  \n  private boolean hasCycle(boolean[] globalMarked, boolean[] localMarked, List<Integer>[] graphic,\n                           int curNode, Stack<Integer> postOrder) {\n  \n      if (localMarked[curNode]) {\n          return true;\n      }\n      if (globalMarked[curNode]) {\n          return false;\n      }\n      globalMarked[curNode] = true;\n      localMarked[curNode] = true;\n      for (int nextNode : graphic[curNode]) {\n          if (hasCycle(globalMarked, localMarked, graphic, nextNode, postOrder)) {\n              return true;\n          }\n      }\n      localMarked[curNode] = false;\n      postOrder.push(curNode);\n      return false;\n  }\n  ```\n\n  ### 并查集\n\n  并查集可以动态地连通两个点，并且可以非常快速地判断两个点是否连通。\n\n  **冗余连接** \n\n  [684. Redundant Connection (Medium)](https://leetcode.com/problems/redundant-connection/description/)\n\n  ```html\n  Input: [[1,2], [1,3], [2,3]]\n  Output: [2,3]\n  Explanation: The given undirected graph will be like this:\n    1\n   / \\\n  2 - 3\n  ```\n\n  题目描述：有一系列的边连成的图，找出一条边，移除它之后该图能够成为一棵树。\n\n  ```java\n  public int[] findRedundantConnection(int[][] edges) {\n      int N = edges.length;\n      UF uf = new UF(N);\n      for (int[] e : edges) {\n          int u = e[0], v = e[1];\n          if (uf.connect(u, v)) {\n              return e;\n          }\n          uf.union(u, v);\n      }\n      return new int[]{-1, -1};\n  }\n  \n  private class UF {\n      private int[] id;\n  \n      UF(int N) {\n          id = new int[N + 1];\n          for (int i = 0; i < id.length; i++) {\n              id[i] = i;\n          }\n      }\n  \n      void union(int u, int v) {\n          int uID = find(u);\n          int vID = find(v);\n          if (uID == vID) {\n              return;\n          }\n          for (int i = 0; i < id.length; i++) {\n              if (id[i] == uID) {\n                  id[i] = vID;\n              }\n          }\n      }\n  \n      int find(int p) {\n          return id[p];\n      }\n  \n      boolean connect(int u, int v) {\n          return find(u) == find(v);\n      }\n  }\n  ```\n\n  ## 位运算\n\n  [【一起玩算法05】位运算的应用_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili](https://www.bilibili.com/video/av3878878?from=search&seid=13087598393682658830)\n\n\n  **1. 基本原理** \n\n  0s 表示一串 0，1s 表示一串 1。\n\n  ```\n  x ^ 0s = x      x & 0s = 0      x | 0s = x\n  x ^ 1s = ~x     x & 1s = x      x | 1s = 1s\n  x ^ x = 0       x & x = x       x | x = x\n  ```\n\n  - 利用 x ^ 1s = \\~x 的特点，可以将位级表示翻转；利用 x ^ x = 0 的特点，可以将三个数中重复的两个数去除，只留下另一个数。\n  - 利用 x & 0s = 0 和 x & 1s = x 的特点，可以实现掩码操作。一个数 num 与 mask ：00111100 进行位与操作，只保留 num 中与 mask 的 1 部分相对应的位。\n  - 利用 x | 0s = x 和 x | 1s = 1s 的特点，可以实现设值操作。一个数 num 与 mask：00111100 进行位或操作，将 num 中与 mask 的 1 部分相对应的位都设置为 1。\n\n  位与运算技巧：\n\n  - n&(n-1) 去除 n 的位级表示中最低的那一位。例如对于二进制表示 10110 **100** ，减去 1 得到 10110**011**，这两个数相与得到 10110**000**。\n  - n&(-n) 得到 n 的位级表示中最低的那一位。-n 得到 n 的反码加 1，对于二进制表示 10110 **100** ，-n 得到 01001**100**，相与得到 00000**100**。\n  - n-n&(\\~n+1) 去除 n 的位级表示中最高的那一位。\n\n  移位运算：\n\n  - \\>\\> n 为算术右移，相当于除以 2<sup>n</sup>；\n  - \\>\\>\\> n 为无符号右移，左边会补上 0。\n  - &lt;&lt; n 为算术左移，相当于乘以 2<sup>n</sup>。\n\n  **2. mask 计算** \n\n  要获取 111111111，将 0 取反即可，\\~0。\n\n  要得到只有第 i 位为 1 的 mask，将 1 向左移动 i-1 位即可，1&lt;&lt;(i-1) 。例如 1&lt;&lt;4 得到只有第 5 位为 1 的 mask ：00010000。\n\n  要得到 1 到 i 位为 1 的 mask，1&lt;&lt;(i+1)-1 即可，例如将 1&lt;&lt;(4+1)-1 = 00010000-1 = 00001111。\n\n  要得到 1 到 i 位为 0 的 mask，只需将 1 到 i 位为 1 的 mask 取反，即 \\~(1&lt;&lt;(i+1)-1)。\n\n  **3. Java 中的位操作** \n\n  ```html\n  static int Integer.bitCount();           // 统计 1 的数量\n  static int Integer.highestOneBit();      // 获得最高位\n  static String toBinaryString(int i);     // 转换为二进制表示的字符串\n  ```\n\n  **统计两个数的二进制表示有多少位不同** \n\n  [461. Hamming Distance (Easy)](https://leetcode.com/problems/hamming-distance/)\n\n  ```html\n  Input: x = 1, y = 4\n  \n  Output: 2\n  \n  Explanation:\n  1   (0 0 0 1)\n  4   (0 1 0 0)\n         ↑   ↑\n  \n  The above arrows point to positions where the corresponding bits are different.\n  ```\n\n  对两个数进行异或操作，位级表示不同的那一位为 1，统计有多少个 1 即可。\n\n  ```java\n  public int hammingDistance(int x, int y) {\n      int z = x ^ y;\n      int cnt = 0;\n      while(z != 0) {\n          if ((z & 1) == 1) cnt++;\n          z = z >> 1;\n      }\n      return cnt;\n  }\n  ```\n\n  使用 z&(z-1) 去除 z 位级表示最低的那一位。\n\n  ```java\n  public int hammingDistance(int x, int y) {\n      int z = x ^ y;\n      int cnt = 0;\n      while (z != 0) {\n          z &= (z - 1);\n          cnt++;\n      }\n      return cnt;\n  }\n  ```\n\n  可以使用 Integer.bitcount() 来统计 1 个的个数。\n\n  ```java\n  public int hammingDistance(int x, int y) {\n      return Integer.bitCount(x ^ y);\n  }\n  ```\n\n  **数组中唯一一个不重复的元素** \n\n  [136. Single Number (Easy)](https://leetcode.com/problems/single-number/description/)\n\n  ```html\n  Input: [4,1,2,1,2]\n  Output: 4\n  ```\n\n  两个相同的数异或的结果为 0，对所有数进行异或操作，最后的结果就是单独出现的那个数。\n\n  ```java\n  public int singleNumber(int[] nums) {\n      int ret = 0;\n      for (int n : nums) ret = ret ^ n;\n      return ret;\n  }\n  ```\n\n  **找出数组中缺失的那个数** \n\n  [268. Missing Number (Easy)](https://leetcode.com/problems/missing-number/description/)\n\n  ```html\n  Input: [3,0,1]\n  Output: 2\n  ```\n\n  题目描述：数组元素在 0-n 之间，但是有一个数是缺失的，要求找到这个缺失的数。\n\n  ```java\n  public int missingNumber(int[] nums) {\n      int ret = 0;\n      for (int i = 0; i < nums.length; i++) {\n          ret = ret ^ i ^ nums[i];\n      }\n      return ret ^ nums.length;\n  }\n  ```\n\n  **数组中不重复的两个元素** \n\n  [260. Single Number III (Medium)](https://leetcode.com/problems/single-number-iii/description/)\n\n  两个不相等的元素在位级表示上必定会有一位存在不同。\n\n  将数组的所有元素异或得到的结果为不存在重复的两个元素异或的结果。\n\n  diff &= -diff 得到出 diff 最右侧不为 0 的位，也就是不存在重复的两个元素在位级表示上最右侧不同的那一位，利用这一位就可以将两个元素区分开来。\n\n  ```java\n  public int[] singleNumber(int[] nums) {\n      int diff = 0;\n      for (int num : nums) diff ^= num;\n      diff &= -diff;  // 得到最右一位\n      int[] ret = new int[2];\n      for (int num : nums) {\n          if ((num & diff) == 0) ret[0] ^= num;\n          else ret[1] ^= num;\n      }\n      return ret;\n  }\n  ```\n\n  **翻转一个数的比特位** \n\n  [190. Reverse Bits (Easy)](https://leetcode.com/problems/reverse-bits/description/)\n\n  ```java\n  public int reverseBits(int n) {\n      int ret = 0;\n      for (int i = 0; i < 32; i++) {\n          ret <<= 1;\n          ret |= (n & 1);\n          n >>>= 1;\n      }\n      return ret;\n  }\n  ```\n\n  如果该函数需要被调用很多次，可以将 int 拆成 4 个 byte，然后缓存 byte 对应的比特位翻转，最后再拼接起来。\n\n  ```java\n  private static Map<Byte, Integer> cache = new HashMap<>();\n  \n  public int reverseBits(int n) {\n      int ret = 0;\n      for (int i = 0; i < 4; i++) {\n          ret <<= 8;\n          ret |= reverseByte((byte) (n & 0b11111111));\n          n >>= 8;\n      }\n      return ret;\n  }\n  \n  private int reverseByte(byte b) {\n      if (cache.containsKey(b)) return cache.get(b);\n      int ret = 0;\n      byte t = b;\n      for (int i = 0; i < 8; i++) {\n          ret <<= 1;\n          ret |= t & 1;\n          t >>= 1;\n      }\n      cache.put(b, ret);\n      return ret;\n  }\n  ```\n\n  **不用额外变量交换两个整数** \n\n  [程序员代码面试指南 ：P317](#)\n\n  ```java\n  a = a ^ b;\n  b = a ^ b;\n  a = a ^ b;\n  ```\n\n  **判断一个数是不是 2 的 n 次方** \n\n  [231. Power of Two (Easy)](https://leetcode.com/problems/power-of-two/description/)\n\n  二进制表示只有一个 1 存在。\n\n  ```java\n  public boolean isPowerOfTwo(int n) {\n      return n > 0 && Integer.bitCount(n) == 1;\n  }\n  ```\n\n  利用 1000 & 0111 == 0 这种性质，得到以下解法：\n\n  ```java\n  public boolean isPowerOfTwo(int n) {\n      return n > 0 && (n & (n - 1)) == 0;\n  }\n  ```\n\n  **判断一个数是不是 4 的 n 次方** \n\n  [342. Power of Four (Easy)](https://leetcode.com/problems/power-of-four/)\n\n  这种数在二进制表示中有且只有一个奇数位为 1，例如 16（10000）。\n\n  ```java\n  public boolean isPowerOfFour(int num) {\n      return num > 0 && (num & (num - 1)) == 0 && (num & 0b01010101010101010101010101010101) != 0;\n  }\n  ```\n\n  也可以使用正则表达式进行匹配。\n\n  ```java\n  public boolean isPowerOfFour(int num) {\n      return Integer.toString(num, 4).matches(\"10*\");\n  }\n  ```\n\n\n  **判断一个数的位级表示是否不会出现连续的 0 和 1** \n\n  [693. Binary Number with Alternating Bits (Easy)](https://leetcode.com/problems/binary-number-with-alternating-bits/description/)\n\n  ```html\n  Input: 10\n  Output: True\n  Explanation:\n  The binary representation of 10 is: 1010.\n  \n  Input: 11\n  Output: False\n  Explanation:\n  The binary representation of 11 is: 1011.\n  ```\n\n  对于 1010 这种位级表示的数，把它向右移动 1 位得到 101，这两个数每个位都不同，因此异或得到的结果为 1111。\n\n  ```java\n  public boolean hasAlternatingBits(int n) {\n      int a = (n ^ (n >> 1));\n      return (a & (a + 1)) == 0;\n  }\n  ```\n\n  **求一个数的补码** \n\n  [476. Number Complement (Easy)](https://leetcode.com/problems/number-complement/description/)\n\n  ```html\n  Input: 5\n  Output: 2\n  Explanation: The binary representation of 5 is 101 (no leading zero bits), and its complement is 010. So you need to output 2.\n  ```\n\n  题目描述：不考虑二进制表示中的首 0 部分。\n\n  对于 00000101，要求补码可以将它与 00000111 进行异或操作。那么问题就转换为求掩码 00000111。\n\n  ```java\n  public int findComplement(int num) {\n      if (num == 0) return 1;\n      int mask = 1 << 30;\n      while ((num & mask) == 0) mask >>= 1;\n      mask = (mask << 1) - 1;\n      return num ^ mask;\n  }\n  ```\n\n  可以利用 Java 的 Integer.highestOneBit() 方法来获得含有首 1 的数。\n\n  ```java\n  public int findComplement(int num) {\n      if (num == 0) return 1;\n      int mask = Integer.highestOneBit(num);\n      mask = (mask << 1) - 1;\n      return num ^ mask;\n  }\n  ```\n\n  对于 10000000 这样的数要扩展成 11111111，可以利用以下方法：\n\n  ```html\n  mask |= mask >> 1    11000000\n  mask |= mask >> 2    11110000\n  mask |= mask >> 4    11111111\n  ```\n\n  ```java\n  public int findComplement(int num) {\n      int mask = num;\n      mask |= mask >> 1;\n      mask |= mask >> 2;\n      mask |= mask >> 4;\n      mask |= mask >> 8;\n      mask |= mask >> 16;\n      return (mask ^ num);\n  }\n  ```\n\n  **实现整数的加法** \n\n  [371. Sum of Two Integers (Easy)](https://leetcode.com/problems/sum-of-two-integers/description/)\n\n  a ^ b 表示没有考虑进位的情况下两数的和，(a & b) << 1 就是进位。\n\n  递归会终止的原因是 (a & b) << 1 最右边会多一个 0，那么继续递归，进位最右边的 0 会慢慢增多，最后进位会变为 0，递归终止。\n\n  ```java\n  public int getSum(int a, int b) {\n      return b == 0 ? a : getSum((a ^ b), (a & b) << 1);\n  }\n  ```\n\n  **字符串数组最大乘积** \n\n  [318. Maximum Product of Word Lengths (Medium)](https://leetcode.com/problems/maximum-product-of-word-lengths/description/)\n\n  ```html\n  Given [\"abcw\", \"baz\", \"foo\", \"bar\", \"xtfn\", \"abcdef\"]\n  Return 16\n  The two words can be \"abcw\", \"xtfn\".\n  ```\n\n  题目描述：字符串数组的字符串只含有小写字符。求解字符串数组中两个字符串长度的最大乘积，要求这两个字符串不能含有相同字符。\n\n  本题主要问题是判断两个字符串是否含相同字符，由于字符串只含有小写字符，总共 26 位，因此可以用一个 32 位的整数来存储每个字符是否出现过。\n\n  ```java\n  public int maxProduct(String[] words) {\n      int n = words.length;\n      int[] val = new int[n];\n      for (int i = 0; i < n; i++) {\n          for (char c : words[i].toCharArray()) {\n              val[i] |= 1 << (c - 'a');\n          }\n      }\n      int ret = 0;\n      for (int i = 0; i < n; i++) {\n          for (int j = i + 1; j < n; j++) {\n              if ((val[i] & val[j]) == 0) {\n                  ret = Math.max(ret, words[i].length() * words[j].length());\n              }\n          }\n      }\n      return ret;\n  }\n  ```\n\n  **统计从 0 \\~ n 每个数的二进制表示中 1 的个数** \n\n  [338. Counting Bits (Medium)](https://leetcode.com/problems/counting-bits/description/)\n\n  对于数字 6(110)，它可以看成是 4(100) 再加一个 2(10)，因此 dp[i] = dp[i&(i-1)] + 1;\n\n  ```java\n  public int[] countBits(int num) {\n      int[] ret = new int[num + 1];\n      for(int i = 1; i <= num; i++){\n          ret[i] = ret[i&(i-1)] + 1;\n      }\n      return ret;\n  }\n  ```\n\n  # 参考资料\n\n  - [Leetcode](https://leetcode.com/problemset/algorithms/?status=Todo)\n  - Weiss M A, 冯舜玺. 数据结构与算法分析——C 语言描述[J]. 2004.\n  - Sedgewick R. Algorithms[M]. Pearson Education India, 1988.\n  - 何海涛, 软件工程师. 剑指 Offer: 名企面试官精讲典型编程题[M]. 电子工业出版社, 2014.\n  - 《编程之美》小组. 编程之美[M]. 电子工业出版社, 2008.\n  - 左程云. 程序员代码面试指南[M]. 电子工业出版社, 2015."
  },
  {
    "path": "notes/Linux.md",
    "content": "<!-- TOC -->\n\n- [前言](#前言)\n- [Linux](#linux)\n    - [1. 顶层目录结构](#1-顶层目录结构)\n    - [2. 深入理解 inode](#2-深入理解-inode)\n        - [inode是什么](#inode是什么)\n        - [inode的内容](#inode的内容)\n        - [inode的大小](#inode的大小)\n        - [inode号码](#inode号码)\n        - [目录文件](#目录文件)\n        - [inode的特殊作用](#inode的特殊作用)\n    - [3. 什么是硬链接与软链接](#3-什么是硬链接与软链接)\n        - [硬链接](#硬链接)\n        - [软链接](#软链接)\n    - [4. Linux查看CPU、内存占用的命令](#4-linux查看cpu内存占用的命令)\n        - [top](#top)\n        - [cat /proc/meminfo](#cat-procmeminfo)\n        - [free](#free)\n    - [5. 定时任务 crontab](#5-定时任务-crontab)\n    - [6. 文件权限](#6-文件权限)\n    - [7. chmod 修改权限](#7-chmod-修改权限)\n    - [8. 文件与目录的基本操作](#8-文件与目录的基本操作)\n        - [1. ls](#1-ls)\n        - [2. cd](#2-cd)\n        - [3. mkdir](#3-mkdir)\n        - [4. rmdir](#4-rmdir)\n        - [5. touch](#5-touch)\n        - [6. cp](#6-cp)\n        - [7. rm](#7-rm)\n        - [8. mv](#8-mv)\n    - [9. 获取文件内容](#9-获取文件内容)\n        - [1. cat](#1-cat)\n        - [2. tac](#2-tac)\n        - [3. more](#3-more)\n        - [4. less](#4-less)\n        - [5. head](#5-head)\n        - [6. tail](#6-tail)\n        - [7. od](#7-od)\n        - [问：Linux查看日志文件的方式](#问linux查看日志文件的方式)\n    - [10. 指令与文件搜索](#10-指令与文件搜索)\n        - [1. which](#1-which)\n        - [2. whereis](#2-whereis)\n        - [3. locate](#3-locate)\n        - [4. find](#4-find)\n        - [*. grep的使用，一定要掌握，每次都会问在文件中查找（包含匹配）](#-grep的使用一定要掌握每次都会问在文件中查找包含匹配)\n        - [*. 管道](#-管道)\n    - [11. 压缩与解压缩命令](#11-压缩与解压缩命令)\n        - [.zip](#zip)\n        - [.gz](#gz)\n        - [.bz2](#bz2)\n        - [tar](#tar)\n        - [.tar.gz](#targz)\n        - [.tar.bz2](#tarbz2)\n    - [12. Bash](#12-bash)\n        - [特性](#特性)\n        - [变量操作](#变量操作)\n        - [指令搜索顺序](#指令搜索顺序)\n        - [输出重定向](#输出重定向)\n        - [输入重定向](#输入重定向)\n    - [13. 正则表达式](#13-正则表达式)\n        - [cut](#cut)\n        - [grep](#grep)\n        - [printf](#printf)\n        - [awk](#awk)\n        - [sed](#sed)\n    - [14. 进程管理](#14-进程管理)\n        - [查看进程](#查看进程)\n            - [1. ps](#1-ps)\n            - [2. top](#2-top)\n            - [3. pstree](#3-pstree)\n            - [4. netstat](#4-netstat)\n        - [进程状态](#进程状态)\n            - [SIGCHLD](#sigchld)\n            - [wait()](#wait)\n            - [waitpid()](#waitpid)\n            - [孤儿进程](#孤儿进程)\n            - [僵尸进程](#僵尸进程)\n    - [15. 进程和线程的区别](#15-进程和线程的区别)\n    - [16. kill用法，某个进程杀不掉的原因（进入内核态，忽略kill信号）](#16-kill用法某个进程杀不掉的原因进入内核态忽略kill信号)\n    - [17. 包管理工具](#17-包管理工具)\n        - [软件类型](#软件类型)\n        - [发行版](#发行版)\n    - [18. 网络配置和网络诊断命令](#18-网络配置和网络诊断命令)\n    - [19. 磁盘管理](#19-磁盘管理)\n    - [20. VIM 三个模式](#20-vim-三个模式)\n    - [21. 用户管理](#21-用户管理)\n        - [创建用户](#创建用户)\n        - [删除用户](#删除用户)\n        - [查看所有用户](#查看所有用户)\n        - [普通用户改为高级用户](#普通用户改为高级用户)\n        - [创建的用户 SSH 生效](#创建的用户-ssh-生效)\n    - [22. lspci](#22-lspci)\n    - [23. Screen命令](#23-screen命令)\n        - [screen命令是什么](#screen命令是什么)\n        - [安装](#安装)\n        - [使用方法](#使用方法)\n        - [远程演示](#远程演示)\n        - [常用快捷键](#常用快捷键)\n    - [24. Linux 下如何查看系统版本](#24-linux-下如何查看系统版本)\n    - [25. 常用快捷方式](#25-常用快捷方式)\n    - [26. 高并发网络编程之epoll详解](#26-高并发网络编程之epoll详解)\n- [参考资料](#参考资料)\n- [更新日志](#更新日志)\n\n<!-- /TOC -->\n\n# 前言\n\n在本文将讲解常用的 Linux 核心知识，但并不保证知识的系统性，只列举最常见的命令和知识点。\n\n\n\n参考资料：\n\n- 《快乐的 Linux 命令行》\n\n\n\n# Linux\n\n## 1. 顶层目录结构 \n\n<div align=\"center\"><img src=\"pics/linux-filesystem.png\" width=\"\"/></div>\n\n| 目录            | 说明                                                         |\n| --------------- | ------------------------------------------------------------ |\n| **/root**       | 根目录，万物起源                                             |\n| **/boot**       | 包含 Linux 内核，最初的 RMA 磁盘映像（系统启动时，由驱动程序所需），和启动加载程序<br/>**有趣的文件**：<br />/boot/grub/grub.conf or menu.lst，被用来配置启动<br/>加载程序<br />/boot/vmlinuz， Linux 内核 |\n| **/bin**        | 包含系统启动和运行所必须的二进制程序                         |\n| **/sbin**       | s就是Super User的意思，这里存放的是系统管理员使用的系统管理程序 |\n| **/usr**        | 这是一个非常重要的目录，用户的很多应用程序和文件都放在这个目录下，类似于 windows 下的 program files 目录。usr (unix software resource) |\n| **/usr/bin**    | 系统用户使用的应用程序                                       |\n| **/usr/sbin**   | 超级用户使用的比较高级的管理程序和系统守护程序               |\n| /usr/src        | 内核源代码默认的放置目录                                     |\n| **/proc**       | 系统内存的映射目录，提供内核与进程信息                       |\n| **/lost+found** | 一般情况下是空的，当系统非法关机后，这里就存放了一些文件，文件系统恢复时的恢复文件 |\n| **/var**        | 这个目录中存放着在不断扩充着的东西，我们习惯将那些经常被修改的目录放在这个目录下。存放邮件、系统日志等变化文件，存放系统或程序运行过程中的数据文件(variable) |\n| **/tmp**        | 这个目录是用来存放一些临时文件的                             |\n| **/etc**        | 存放系统配置文件。它也包含一系列的 shell 脚本，在系统启动时，这些脚本会运行每个系统服务。这个目录中的任何文件应该是可读的文本文件。<br /><br />`/etc/crontab`，定义自动运行的任务。<br />`/etc/fstab`，包含存储设备的列表，以及与他们相关的<br/>挂载点。<br />`/etc/passwd`，包含用户帐号列表。 |\n| **/home**       | 用户主目录，在通常的配置环境下，系统会在 /home 下，给每个用户分配一个目录。普通只能在他们自己的目录下创建文件。这个限制保护系统免受错误的用户活动破坏。 |\n| **/dev**        | 这是一个包含设备结点的特殊目录。“一切都是文件”，也使用于设备。在这个目录里，内核维护着它支持的设备 |\n| **/lib**        | 动态连接共享库，`.so` 文件，类似于 Windows 里的 dll 文件。   |\n| **/mnt**        | 系统提供该目录是为了让用户临时挂载别的文件系统的，我们可以将光驱挂载在 /mnt/ 上，然后进入该目录就可以查看光驱里的内容了。 |\n| **/media**      | 系统会自动识别一些设备，例如 U 盘、光驱等等，把识别的设备挂载到这个目录下。 |\n| /sys            | sys 虚拟文件系统挂载点                                       |\n| /srv            | 存放服务相关数据                                             |\n| /opt            | 这是给主机额外安装软件所摆放的目录。比如你安装一个 ORACLE 数据库则就可以放到这个目录下。默认是空的。 |\n| /srv            | 该目录存放一些服务启动之后需要提取的数据。                   |\n| /selinux        | 这个目录是 Redhat/CentOS 所特有的目录，Selinux 是一个安全机制，类似于windows 的防火墙，但是这套机制比较复杂，这个目录就是存放selinux 相关的文件的。 |\n\n**问：proc下都放什么文件？**\n\n- 此目录的所有数据都在内存里，如 系统核心，外部设备，网络状态。由于所有数据都储存在内存里，所以不占用磁盘空间； 这个目录是一个虚拟的目录，它是系统内存的映射，我们可以通过直接访问这个目录来获取系统信息。\n\n```shell\nmore /proc/meminfo\n```\n\n- 这个目录的内容不在硬盘上而是在内存里，我们也可以直接修改里面的某些文件，比如可以通过下面的命令来屏蔽主机的 ping 命令，使别人无法 ping 你的机器：\n\n```shell\necho 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all\n```\n\n\n\n**注意事项**\n\n在 Linux 系统中，有几个目录是比较重要的，平时需要注意不要误删除或者随意更改内部文件。\n\n- **/etc：** 上边也提到了，这个是系统中的配置文件，如果你更改了该目录下的某个文件可能会导致系统不能启动。\n\n- **/bin, /sbin, /usr/bin, /usr/sbin：** 这是系统预设的执行文件的放置目录，比如 ls 就是在 /bin/ls 目录下的。\n  - 值得提出的是，**/bin, /usr/bin** 是给系统用户使用的指令（除 root 外的通用户）\n  - 而**/sbin, /usr/sbin** 则是给 root 使用的指令。\n\n- **/var：** 这是一个非常重要的目录，系统上跑了很多程序，那么每个程序都会有相应的日志产生，而这些日志就被记录到这个目录下，具体在 /var/log 目录下，另外 mail 的预设放置也是在这里。\n\n\n\n总而言之，在Linux中一切皆文件！\n\n\n\n## 2. 深入理解 inode\n\n### inode是什么\n\n理解 inode，要从文件储存说起。\n\n文件储存在硬盘上，硬盘的最小存储单位叫做 \"扇区\"（Sector）。每个扇区储存 512 字节（相当于 0.5KB）。\n\n操作系统读取硬盘的时候，不会一个个扇区地读取，这样效率太低，而是一次性连续读取多个扇区，即一次性读取一个\"块\"（block）。这种由多个扇区组成的\"块\"，是文件存取的最小单位。\"块\"的大小，最常见的是 4KB，即连续八个 sector 组成一个 block。\n\n文件数据都储存在 \"块\" 中，那么很显然，我们还必须找到一个地方储存文件的元信息，比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做 inode，中文译名为 \"索引节点\"。\n\n每一个文件都有对应的 inode，里面包含了与该文件有关的一些信息。\n\n### inode的内容\n\nnode 包含文件的元信息，具体来说有以下内容：\n\n```\n* 文件的字节数\n* 文件拥有者的 User ID\n* 文件的 Group ID\n* 文件的读、写、执行权限\n* 文件的时间戳，共有三个：ctime：指inode上一次变动的时间，mtime：指文件内容上一次变动的时间，atime：指文件上一次打开的时间。\n* 链接数，即有多少文件名指向这个inode\n* 文件数据block的位置\n```\n\n可以用 stat 命令，查看某个文件的 inode 信息：\n\n```\n$ stat abby.txt\n  File: ‘abby.txt’\n  Size: 22              Blocks: 8          IO Block: 4096   regular file\nDevice: fd01h/64769d    Inode: 2106782     Links: 2\nAccess: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)\nAccess: 2018-07-22 16:37:18.640787898 +0800\nModify: 2018-07-22 16:37:10.678607855 +0800\nChange: 2018-07-22 16:37:10.833611360 +0800\n Birth: -\n```\n\n总之，除了文件名以外的所有文件信息，都存在inode之中。至于为什么没有文件名，下文会有详细解释。\n\n### inode的大小\n\n<div align=\"center\"><img src=\"assets/0417_WTD_Linux_F1.gif\" width=\"450\"/></div>\n\ninode 也会消耗硬盘空间，所以硬盘格式化的时候，操作系统自动将硬盘分成两个区域。一个是**数据区**，存放文件数据；另一个是 **inode 区**（inode table），存放 inode 所包含的信息。\n\n每个 inode 节点的大小，一般是 128 字节或 256 字节。inode 节点的总数，在格式化时就给定，一般是每 1KB 或每 2KB 就设置一个 inode。假定在一块 1GB 的硬盘中，每个 inode 节点的大小为 128 字节，每 1KB 就设置一个 inode，那么 inode table 的大小就会达到 128 MB，占整块硬盘的 12.8%。\n\n查看每个硬盘分区的 inode 总数和已经使用的数量，可以使用df 命令。\n\n```shell\n$ df -i\nFilesystem      Inodes  IUsed   IFree IUse% Mounted on\n/dev/vda1      3932160 189976 3742184    5% /\ndevtmpfs        998993    339  998654    1% /dev\ntmpfs          1001336      1 1001335    1% /dev/shm\ntmpfs          1001336    397 1000939    1% /run\ntmpfs          1001336     16 1001320    1% /sys/fs/cgroup\ntmpfs          1001336      1 1001335    1% /run/user/0\n```\n\n查看每个 inode 节点的大小，可以用如下命令：\n\n```shell\n$ sudo dumpe2fs -h /dev/vda1 | grep \"Inode size\"\ndumpe2fs 1.42.9 (28-Dec-2013)\nInode size:               256\n```\n\n由于每个文件都必须有一个inode，因此有可能发生inode已经用光，但是硬盘还未存满的情况。这时，就无法在硬盘上创建新文件。\n\n\n\n### inode号码\n\n每个 inode 都有一个号码，操作系统用 inode 号码来识别不同的文件。\n\n这里值得重复一遍，Unix/Linux 系统内部不使用文件名，而使用 inode 号码来识别文件。对于系统来说，文件名只是 inode 号码便于识别的别称或者绰号。\n\n表面上，用户通过文件名，打开文件。实际上，系统内部这个过程分成三步：首先，系统找到这个文件名对应的 inode 号码；其次，通过 inode 号码，获取 inode 信息；最后，根据 inode 信息，找到文件数据所在的 block，读出数据。\n\n使用 `ls -i` 命令，可以看到文件名对应的 inode 号码：\n\n```shell\n$ ls -i test.txt\n1712426 test.txt\n```\n\n\n\n### 目录文件\n\nUnix/Linux 系统中，目录（directory）也是一种文件。打开目录，实际上就是打开目录文件。\n\n目录文件的结构非常简单，就是一系列目录项（dirent）的列表。每个目录项，由两部分组成：所包含文件的文件名，以及该文件名对应的 inode 号码。\n\nls 命令只列出目录文件中的所有文件名：\n\n```shell\nls /etc\n```\n\nls -i 命令列出整个目录文件，即文件名和 inode 号码：\n\n```shell\nls -i /etc\n```\n\n如果要查看文件的详细信息，就必须根据 inode 号码，访问 inode 节点，读取信息。`ls -l` 命令列出文件的详细信息。\n\n```shell\nls -l /etc\n```\n\n理解了上面这些知识，就能理解目录的权限。目录文件的读权限（r）和写权限（w），都是针对目录文件本身。由于目录文件内只有文件名和 inode 号码，所以如果只有读权限，只能获取文件名，无法获取其他信息，因为其他信息都储存在 inode 节点中，而读取 inode 节点内的信息需要目录文件的执行权限（x）。\n\n\n\n### inode的特殊作用\n\n由于 inode 号码与文件名分离，这种机制导致了一些 Unix/Linux 系统特有的现象。\n\n1. 有时，文件名包含特殊字符，无法正常删除。这时，直接删除 inode 节点，就能起到删除文件的作用。\n\n2. 移动文件或重命名文件，只是改变文件名，不影响 inode 号码。\n\n3. 打开一个文件以后，系统就以 inode 号码来识别这个文件，不再考虑文件名。因此，通常来说，系统无法从 inode 号码得知文件名。\n\n第 3 点使得软件更新变得简单，可以在不关闭软件的情况下进行更新，不需要重启。因为系统通过inode号码，识别运行中的文件，不通过文件名。更新的时候，新版文件以同样的文件名，生成一个新的inode，不会影响到运行中的文件。等到下一次运行这个软件的时候，文件名就自动指向新版文件，旧版文件的 inode 则被回收。\n\n\n\n## 3. 什么是硬链接与软链接\n\n<div align=\"center\"><img src=\"assets/393890-20151128142803015-292063645.png\" width=\"400\"/></div>\n\n### 硬链接\n\n一般情况下，文件名和 inode 号码是 \"一一对应\" 关系，每个 inode 号码对应一个文件名。但是，Unix/Linux 系统允许，多个文件名指向同一个 inode 号码。\n\n这意味着，可以用不同的文件名访问同样的内容；对文件内容进行修改，会影响到所有文件名；但是，删除一个文件名，不影响另一个文件名的访问。这种情况就被称为 \"硬链接\"（hard link）。\n\n运行上面这条命令以后，源文件与目标文件的 inode 号码相同，都指向同一个 inode。inode 信息中有一项叫做 \"链接数\"，记录指向该 inode 的文件名总数，这时就会增加 1。\n\n反过来，删除一个文件名，就会使得 inode 节点中的 \"链接数\" 减1。当这个值减到 0，表明没有文件名指向这个 inode，系统就会回收这个 inode 号码，以及其所对应 block 区域。\n\n这里顺便说一下目录文件的 \"链接数\"。创建目录时，默认会生成两个目录项：\".\"和\"..\"。前者的 inode 号码就是当前目录的 inode 号码，等同于当前目录的 \"硬链接\"；后者的 inode 号码就是当前目录的父目录的inode号码，等同于父目录的 \"硬链接\"。所以，任何一个目录的 \"硬链接\" 总数，总是等于 2 加上它的子目录总数（含隐藏目录）。\n\n\n\n几个硬连接＝几个名字的同一个房子\n\n**硬链接（Hard Link）**：硬连接不能跨越不同的文件系统，硬连接记录的是目标的 inode；只能指向文件。硬连接与原始文件都删除才意味着文件被删除。\n\n- 特征\n  - 拥有相同的 i 节点和存储 block 块，可以看做是同一个文件\n  - 可通过 i 节点识别\n  - 不能跨分区\n  - 不能针对目录使用\n\n\n\n### 软链接\n\n除了硬链接以外，还有一种特殊情况。\n\n文件 A 和文件 B 的 inode 号码虽然不一样，但是文件 A 的内容是文件 B 的路径。读取文件 A 时，系统会自动将访问者导向文件 B。因此，无论打开哪一个文件，最终读取的都是文件 B。这时，文件 A 就称为文件 B 的\"软链接\"（soft link）或者\"符号链接（symbolic link）。\n\n这意味着，文件 A 依赖于文件 B 而存在，如果删除了文件 B，打开文件 A 就会报错：\"No such file or directory\"。这是软链接与硬链接最大的不同：文件 A 指向文件 B 的文件名，而不是文件 B 的 inode 号码，文件 B 的 inode \"链接数\"不会因此发生变化。\n\n\n\n几个软链接=几个指向源文件的路标\n\n**软链接（Symbolic Link，又称符号链接）**：软链接能跨越不同的文件系统，软链接记录的是目标的 path。源文件删除后，则软链接无效。**相当于Windows系统中的“快捷方式”**\n\n- 特征：\n  - 类似 windows 的快捷方式\n  - 软链接拥有自己的 i 节点和 block 块，但是数据块中只保存原文件的文件名和 i  节点号，并没有实际的文件数据\n  - 修改任意一个文件，另一个都会改变\n  - 删除源文件，则软链接无法使用\n  - 软链接的文件权限都为 rwxrwxrwx (文件权限以原文件为准)\n  - 若要创建软链接，则创建的源文件必须使用绝对路径，否则在使用软链接时会报错\n\n\n\n\n\n注意：复制是建造一个一模一样的房子，inode是不同的。\n\n命令\n\n```\n 硬链接：ln 源文件 链接名\n 软链接：ln -s 源文件 链接名\n```\n\n区别： 若将源文件删除，硬链接依旧有效，而软链接会无效，即找不到源文件\n\n\n\n参考资料：\n\n- [ln命令_Linux ln 命令用法详解：用来为文件创件连接](http://man.linuxde.net/ln)\n- [linux 硬链接与软连接简单的对比试验_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili](https://www.bilibili.com/video/av9769329?from=search&seid=5663546178393951142)\n- [【 linux从入门到放弃】10：文件和文件夹进阶inode_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili](https://www.bilibili.com/video/av6068432?from=search&seid=12178075587929134931)\n\n\n\n## 4. Linux查看CPU、内存占用的命令\n\n### top\n\n**top命令**可以实时动态地查看系统的整体运行情况，是一个综合了多方信息监测系统性能和运行信息的实用工具。通过top命令所提供的互动式界面，用热键可以管理。\n\n \n\n### cat /proc/meminfo\n\n查看RAM使用情况最简单的方法是通过 `/proc/meminfo`。这个动态更新的虚拟文件实际上是许多其他内存相关工具(如：free / ps / top)等的组合显示。`/proc/meminfo` 列出了所有你想了解的内存的使用情况。 \n\n \n\n### free\n\nfree 命令是一个快速查看内存使用情况的方法，它是对 `/proc/meminfo` 收集到的信息的一个概述。\n\n这个命令用于显示系统当前内存的使用情况，包括已用内存、可用内存和交换内存的情况\n\n默认情况下 free 会以字节为单位输出内存的使用量\n\n```shell\n$ free\n             total       used       free     shared    buffers     cached\nMem:       3566408    1580220    1986188          0     203988     902960\n-/+ buffers/cache:     473272    3093136\nSwap:      4000176          0    4000176\n```\n\n如果你想以其他单位输出内存的使用量，需要加一个选项，`-g` 为GB，`-m` 为MB，`-k` 为KB，`-b` 为字节\n\n```shell\n$ free -g\n             total       used       free     shared    buffers     cached\nMem:             3          1          1          0          0          0\n-/+ buffers/cache:          0          2\nSwap:            3          0          3\n```\n\n如果你想查看所有内存的汇总，请使用 -t 选项，使用这个选项会在输出中加一个汇总行\n\n```shell\n$ free -t\n             total       used       free     shared    buffers     cached\nMem:       3566408    1592148    1974260          0     204260     912556\n-/+ buffers/cache:     475332    3091076\nSwap:      4000176          0    4000176\nTotal:     7566584    1592148    5974436\n```\n\n\n\n\n\n## 5. 定时任务 crontab\n\n> 该词来源于希腊语chronos（χρόνος），原意是时间。\n\ncrontab 命令常见于 Unix和类 Unix 的操作系统之中，用于设置周期性被执行的指令。该命令从标准输入设备读取指令，并将其存放于 `crontab` 文件中，以供之后读取和执行。\n\n通常，crontab 储存的指令被守护进程激活，crond 常常在后台运行，每一分钟检查是否有预定的作业需要执行。这类作业一般称为 cron jobs。\n\n**crontab文件**\n\n\ncrontab 文件包含送交 cron 守护进程的一系列作业和指令。每个用户可以拥有自己的 crontab 文件；同时，操作系统保存一个针对整个系统的 crontab 文件，该文件通常存放于/etc或者/etc之下的子目录中，而这个文件只能由系统管理员来修改。\n\ncrontab 文件的每一行均遵守特定的格式，由空格或 tab 分隔为数个领域，每个领域可以放置单一或多个数值。\n\n```shell\n# cron服务是Linux的内置服务，但它不会开机自动启动。\n# 可以用以下命令启动和停止服务：\n\n/sbin/service crond start\n/sbin/service crond stop\n/sbin/service crond restart\n/sbin/service crond reload\n```\n\n要把 cron 设为在开机的时候自动启动，在 `/etc/rc.d/rc.local` 脚本中加入 `/sbin/service crond start` 即可\n\n查看当前用户的 crontab，输入 crontab -l；\n\n编辑 crontab，输入 crontab -e；\n\n删除 crontab，输入 crontab -r\n\n**操作符号**\n\n在一个区域里填写多个数值的方法：\n\n逗号（','）分开的值，例如：“1,3,4,7,8”\n连词符（'-'）指定值的范围，例如：“1-6”，意思等同于“1,2,3,4,5,6”\n星号（'*'）代表任何可能的值。例如，在“小时域”里的星号等于是“每一个小时”，等等\n某些cron程序的扩展版本也支持斜线（'/'）操作符，用于表示跳过某些给定的数。例如，“/3”在小时域中等于“0,3,6,9,12,15,18,21”等被3整除的数；\n\n<div align=\"center\"><img src=\"pics/crontab_cheatsheet.png\" width=\"500\"/></div>\n\n**时间设置**\n\n```linux\n# 文件格式说明\n#  ——分钟（0 - 59）\n# |  ——小时（0 - 23）\n# | |  ——日（1 - 31）\n# | | |  ——月（1 - 12）\n# | | | |  ——星期（0 - 7，星期日=0或7）\n# | | | | |\n# * * * * * 被执行的命令\n```\n\n注：\n\n1. 在“星期域”（第五个域），0和7都被视为星期日。\n2. 不很直观的用法：如果日期和星期同时被设定，那么其中的一个条件被满足时，指令便会被执行。请参考下例。\n3. 前5个域称之**分时日月周**，可方便个人记忆。\n\n从第六个域起，指明要执行的命令。\n\n\n\n**实例**\n\n 每1分钟执行一次command\n\n```shell\n* * * * * command\n```\n\n每小时的第3和第15分钟执行\n\n```shell\n3,15 * * * * command\n```\n\n在上午8点到11点的第3和第15分钟执行\n\n```shell\n3,15 8-11 * * * command\n```\n\n每隔两天的上午8点到11点的第3和第15分钟执行\n\n```shell\n3,15 8-11 */2 * * command\n```\n\n\n\n\n\n### 应用场景：定时备份MySQL数据库\n\n\n\n\n\n\n\n\n\n**参考资料**：\n\n- [crontab命令_Linux crontab 命令用法详解：提交和管理用户的需要周期性执行的任务](http://man.linuxde.net/crontab)\n\n\n\n## 6. 文件权限\n\n<div align=\"center\"><img src=\"pics/linux-permissions.png\" width=\"500\"/></div><br/>\n\n- `Type`: 很多种 (最常见的是 `-` 为文件, `d` 为文件夹， 其他的还有`l`, `n` … 这种东西, 真正自己遇到了, 网上再搜就好, 一次性说太多记不住的).\n  - d：目录\n  - -：文件\n  - l：链接文件\n- `User`: 后面跟着的三个空是使用 User 的身份能对这个做什么处理 (`r` 可读; `w` 可写; `x可`执行; `-` 不能完成某个操作).\n- `Group`: 一个 Group 里可能有一个或多个 user, 这些权限的样式和 User 一样.\n- `Others`: 除了 User 和 Group 以外人的权限.\n- `.`：代表 ACL 权限\n\n如果有朋友对 User, group, others 这几个没什么概念的话, 我这里补充一下. User 一般就是指你, 这个正在使用电脑的人. Group 是一个 User 的集合, 最开始创建新 User 的时候, 他也为这个 User 创建了一个和 User 一样名字的 Group, 这个新 Group 里只有这个 User. 一般来说, 像一个企业部门的电脑, 都可以放在一个 Group 里, 分享了一些共享文件和权限. Others 就是除了上面提到的 User 和 Group 以外的人.\n\n \n\n**文件时间有以下三种：**\n\n- modification time (mtime)：文件的内容更新就会更新；\n- status time (ctime)：文件的状态（权限、属性）更新就会更新；\n- access time (atime)：读取文件时就会更新。\n\n\n\n## 7. chmod 修改权限\n\n可以将一组权限用数字来表示，此时一组权限的 3 个位当做二进制数字的位，从左到右每个位的权值为 4. 2. 1，即每个权限对应的数字权值为 `r:4，w:2，x:1`。\n\n```shell\n# chmod [-R] xyz dirname/filename\n```\n\n示例：将 .bashrc 文件的权限修改为 -rwxr-xr--。\n\n```shell\n# chmod 754 .bashrc\n```\n\n也可以使用符号来设定权限。\n\n```shell\n# chmod [ugoa]  [+-=] [rwx] dirname/filename\n- u：拥有者\n- g：所属群组\n- o：其他人\n- a：所有人\n- +：添加权限\n- -：移除权限\n- =：设定权限\n```\n\n示例：为 .bashrc 文件的所有用户添加写权限。\n\n```shell\n# chmod a+w .bashrc\n```\n\n\n\n参考资料：\n\n- [Linux 文件权限 - Linux 简易教学 | 莫烦Python](https://morvanzhou.github.io/tutorials/others/linux-basic/3-01-file-permissions/)\n- [带你玩转Linux命令行（基础入门篇） - CSDN博客](https://blog.csdn.net/u012104219/article/details/79125771)\n\n\n\n\n\n## 8. 文件与目录的基本操作\n\n### 1. ls\n\n列出文件或者目录的信息，目录的信息就是其中包含的文件。\n\n```\n# ls [-aAdfFhilnrRSt] file|dir\n-a ：列出全部的文件\n-d ：仅列出目录本身\n-l ：以长数据串行列出，包含文件的属性与权限等等数据\n```\n\n### 2. cd\n\n更换当前目录。\n\n```\ncd [相对路径或绝对路径]\n```\n\n### 3. mkdir\n\n创建目录。\n\n```\n# mkdir [-mp] 目录名称\n-m ：配置目录权限\n-p ：递归创建目录（这个很常用）\n```\n\n### 4. rmdir\n\n删除目录，目录必须为空。\n\n```\nrmdir [-p] 目录名称\n-p ：递归删除目录\n```\n\n### 5. touch\n\n更新文件时间或者建立新文件。\n\n```\n# touch [-acdmt] filename\n-a ： 更新 atime\n-c ： 更新 ctime，若该文件不存在则不建立新文件\n-m ： 更新 mtime\n-d ： 后面可以接更新日期而不使用当前日期，也可以使用 --date=\"日期或时间\"\n-t ： 后面可以接更新时间而不使用当前时间，格式为[YYYYMMDDhhmm]\n```\n\n### 6. cp\n\n复制文件。\n\n如果源文件有两个以上，则目的文件一定要是目录才行。\n\n```\ncp [-adfilprsu] source destination\n-r 复制目录\n-p 连带文件属性复制\n-d 若源文件是链接文件，则复制链接属性\n-a 相当于 -pdr，包括文件的时间信息等.\n```\n\n### 7. rm\n\n删除文件。\n\n```\n# rm [-fir] 文件或目录\n-r ：递归删除\n```\n\n### 8. mv\n\n移动文件。\n\n```\n# mv [-fiu] source destination\n# mv [options] source1 source2 source3 .... directory\n-f ： force 强制的意思，如果目标文件已经存在，不会询问而直接覆盖\n```\n\n\n\n\n\n## 9. 获取文件内容\n\n### 1. cat\n\n取得文件内容。\n\n```\n# cat [-AbEnTv] filename\n-n ：打印出行号，连同空白行也会有行号，-b 不会\n```\n\n### 2. tac\n\n是 cat 的反向操作，从最后一行开始打印。\n\n### 3. more\n\n和 cat 不同的是它可以一页一页查看文件内容，比较适合大文件的查看。\n\n- 按 Space 键：显示文本的下一屏内容。\n- 按 Enter 键：只显示文本的下一行内容。\n- 按斜线符 `/` ：接着输入一个模式，可以在文本中寻找下一个相匹配的模式。\n- 按 H 键：显示帮助屏，该屏上有相关的帮助信息。\n- 按 B 键：显示上一屏内容。\n- 按 Q 键：退出 more 命令。\n\n### 4. less\n\n和 more 类似，但是多了一个向前翻页的功能。\n\n### 5. head\n\n取得文件前几行。\n\n```\n# head [-n number] filename\n-n ：后面接数字，代表显示几行的意思\n```\n\n### 6. tail\n\n是 head 的反向操作，只是取得是后几行。\n\n### 7. od\n\n以字符或者十六进制的形式显示二进制文件。\n\n\n\n### 问：Linux查看日志文件的方式\n\n- /var/log/message 系统启动后的信息和错误日志，是Red Hat Linux中最常用的日志之一 \n- /var/log/secure 与安全相关的日志信息 \n- /var/log/maillog 与邮件相关的日志信息 \n- /var/log/cron 与定时任务相关的日志信息 \n- /var/log/spooler 与UUCP和news设备相关的日志信息 \n- /var/log/boot.log 守护进程启动和停止相关的日志消息 \n- /var/log/wtmp 该日志文件永久记录每个用户登录、注销及系统的启动、停机的事件\n\n\n\n## 10. 指令与文件搜索\n\n### 1. which\n\n指令搜索。\n\n- 搜索系统命令所在路径及别名\n\n- PATH环境变量：定义的是系统搜索命令的路径\n\n```\necho $PATH \n```\n\n```\n# which [-a] command\n-a ：将所有指令列出，而不是只列第一个\n```\n\n### 2. whereis\n\n文件搜索。搜索系统命令所在路径及帮助文档所在位置。速度比较快，因为它只搜索几个特定的目录。\n\n```\n# whereis [-bmsu] dirname/filename\n```\n\n选项：\n\n- -b 只查找可执行文件\n- -m 只查找帮助文件\n\n### 3. locate\n\n文件搜索。可以用关键字或者正则表达式进行搜索。\n\n**注意：** locate 是从数据库中读取数据（`/var/lib/mlocate`），而不是从文件系统中读取。从数据库中读取时是读取 updatedb 命令返回的结果，而 updatedb 命令默认是一天（24小时）才自动运行一次，这就意味着如果是最新创建的文件，使用 locate 命令可能查找不到。\n\n**解决方法：** \n在使用 locate命令前，先手动运行updatedb命令（需要 root 权限）：`sudo updatedb`\n\n<div align=\"center\"><img src=\"pics/locate-op.jpg\" width=\"500\"/></div>\n\n```shell\n# locate安装\nsudo yum install mlocate\nupdatedb\n```\n\n\n\n**实例**\n\n搜索 etc 目录下所有以 sh 开头的文件：\n\n```\nlocate /etc/sh\n```\n\n搜索用户主目录下，所有以m开头的文件：\n\n```\nlocate ~/m\n```\n\n搜索用户主目录下，所有以m开头的文件，并且忽略大小写：\n\n```\nlocate -i ~/m\n```\n\n### 4. find\n\n- find 搜索范围\n  搜索文件\n- `find / -name install.log`\n  避免大范围搜索，会非常耗费系统资源\n  find 是在系统当中搜索符合条件的文件名。如果需要匹配，使用通配符匹配，通配符是完全匹配。\n- Linux 通配符\n\n```\n*    匹配任意内容\n?    匹配任意一个字符\n[]   匹配任意一个中括号内的字符\n```\n\n- `find /root -iname install.log`\n  不区分大小写\n- `find /root -user root`\n  按照所有者搜索\n- `find /root -nouser`\n  查找没有所有者的文件\n- `find /var/log/ -mtime +10`\n  查找 10 天前修改的文件\n\n```\n-10    10天内修改的文件\n10\t   10天当天修改的文件\n+10    10天前修改的文件\n\natime  文件访问时间\nctime  改变文件属性\nmtime  修改文件内容  \n```\n\n- `find . -size 25k`\n  查找文件大小是 25kb 的文件\n\n```\n-25k\t小于25kb的文件\n25k\t    等于25kb的文件\n+25k\t大于25kb的文件\n```\n\n- `find . -inum 262422`\n  查找 i 节点是 262422 的文件\n- `find /etc/ -size +20k -a -size -50k`\n  查找 /etc/ 目录下，大于 20k 并且小于 50k 的文件\n  - -a and 逻辑与，两个条件都满足\n  - -o or 逻辑或，两个条件满足一个即可\n- `find /etc/ -size +20k -a -size -50k -exec ls -lh {} ;`\n  查找/etc/目录下，大于20k并且小于50k的文件，并显示详细信息\n  -exec/ 命令 {} ;　 对搜索结果执行操作\n\n### *. grep的使用，一定要掌握，每次都会问在文件中查找（包含匹配）\n\n**grep**（global search regular expression(RE) and print out the line，全面搜索正则表达式并把行打印出来）是一种强大的文本搜索工具，它能使用正则表达式搜索文本，并把匹配的行打印出来。\n\n \n\n例子：在目录~/test下递归查找包含字符串 \"hello\" 的所有文件，并显示匹配行的行号\n\n```shell\n$ grep -Rn \"hello\" ~/test \n/home/slot/test/aa:1:hello world!\n/home/slot/test/cc:1:hello world!\n/home/slot/test/bb:1:hello world!\n```\n\n### *. 管道\n\n管线是将一个命令的标准输出作为另一个命令的标准输入，在数据需要经过多个步骤的处理之后才能得到我们想要的内容时就可以使用管线。\n\n在命令之间使用 | 分隔各个管线命令。\n\n```\n$ ls -al /etc | less\n```\n\n查看已经建立TCP请求的个数\n\n```\nnetstat -an | grep ESTABLISHED| wc -l\n```\n\n<div align=\"center\"> <img src=\"pics/Pipeline.svg\" width=\"400\"/></div><br/>\n\n- 子进程从父进程继承文件描述符\n  - file descriptor 0 stdin, 1 stdout, 2stderr\n- 进程不知道（或不关心！）从键盘，文件，程序读取或写入到终端，文件，程序。\n- shell（ # ls | more）\n  - 创建管道\n  - 为 ls 创建一个进程，设置 stdout 为管道写端\n  - 为 more 创建一个进程，设置 stdin 为管道读端\n\n\n\n 参考资料\n\n- [Linux查找命令 - 简书](https://www.jianshu.com/p/72c579528337)\n\n\n\n## 11. 压缩与解压缩命令\n\n常用压缩格式如下：\n\n| 扩展名    | 压缩程序                              |\n| --------- | ------------------------------------- |\n| *.zip     | zip                                   |\n| *.gz      | gzip                                  |\n| *.bz2     | bzip2                                 |\n| *.tar     | tar 程序打包的数据，没有经过压缩      |\n| *.tar.gz  | tar 程序打包的文件，经过 gzip 的压缩  |\n| *.tar.bz2 | tar 程序打包的文件，经过 bzip2 的压缩 |\n\n### .zip\n\n（一）压缩\n\n- zip 压缩文件名 源文件\n  压缩文件\n- zip -r 压缩文件名 源目录\n  压缩目录\n\n（二）解压缩\n\n- unzip 压缩文件\n  解压.zip文件\n\n### .gz\n\n（一）压缩\n\n- gzip 源文件\n  压缩为.gz格式的压缩文件，源文件会消失\n- gzip -c 源文件 > 压缩文件\n  压缩为.gz格式的压缩文件，源文件保留\n\n> 注：-c是将压缩的格式不写入新文件，打印到屏幕上，利用输出重定向造成一个既压缩.gz格式源文件也不消失的现象。但是gzip本身是不支持保留源文件压缩的。\n\n- gzip -r 目录\n  压缩目录下所有的子文件，但是不能压缩目录\n\n（二）解压缩\n\n- gzip -d 压缩文件\n  解压缩文件\n- gunzip 压缩文件\n  解压缩文件\n\n### .bz2\n\n（一）压缩\n\n- bzip2 源文件\n  压缩为.bz2格式，不能保留源文件\n- bzip2 -k 源文件\n  压缩之后保留源文件\n- 注意：bzip2命令不能压缩目录\n\n（二）解压缩\n\n- bzip2 -d 压缩文件\n  解压缩，-k保留压缩文件\n- bunzip2 压缩文件\n  解压缩，-k保留压缩文件\n\n### tar\n\n将一个目录打包成文件.tar格式，这样 `.gz` 和 `.bz2` 可压缩，解压缩目录 \n\n- tar -cvf 打包文件名 源文件\n- 选项：\n\n```\n-c   打包   \n-x   解打包\n-v   显示过程   \n-f   指定打包后的文件名  \n```\n\n解打包命令\n\n- tar -xvf 打包文件名\n\n### .tar.gz\n\n- 其实.tar.gz格式是先打包为.tar格式，再压缩为.gz格式\n- tar -zcvf 压缩包名.tar.gz 源文件\n- 选项：\n  -z： 压缩为.tar.gz格式\n- tar -zxvf 压缩包名.tar.gz\n- 选项：\n  -x： 解压缩.tar.gz格式\n\n### .tar.bz2\n\n其实 `.tar.gz` 格式是先打包成 `.tar` 格式，再压缩为 `.gz` 格式\n\n- tar -jcvf 压缩包名 .tar.bz2 源文件\n\n- 选项：\n  -z：压缩为.tar.gz格式\n\n  -j：支持bzip2解压文件\n\n- tar -jxvf 压缩包名.tar.bz2\n\n- 选项：\n  -x： 解压缩.tar.gz格式\n\n\n\ntar命令参考选项\n\n```shell\n-A或--catenate：新增文件到以存在的备份文件；\n-B：设置区块大小；\n-c或--create：建立新的备份文件；\n-C <目录>：这个选项用在解压缩，若要在特定目录解压缩，可以使用这个选项。\n-d：记录文件的差别；\n-x或--extract或--get：从备份文件中还原文件；\n-t或--list：列出备份文件的内容；\n-z或--gzip或--ungzip：通过gzip指令处理备份文件；\n-Z或--compress或--uncompress：通过compress指令处理备份文件；\n-f<备份文件>或--file=<备份文件>：指定备份文件；\n-v或--verbose：显示指令执行过程；\n-r：添加文件到已经压缩的文件；\n-u：添加改变了和现有的文件到已经存在的压缩文件；\n-j：支持bzip2解压文件；\n-v：显示操作过程；\n-l：文件系统边界设置；\n-k：保留原有文件不覆盖；\n-m：保留文件不被覆盖；\n-w：确认压缩文件的正确性；\n-p或--same-permissions：用原来的文件权限还原文件；\n-P或--absolute-names：文件名使用绝对名称，不移除文件名称前的“/”号；\n-N <日期格式> 或 --newer=<日期时间>：只将较指定日期更新的文件保存到备份文件里；\n--exclude=<范本样式>：排除符合范本样式的文件。\n```\n\n\n\n参考资料：\n\n- [Linux达人养成记课程笔记](https://github.com/wangworld/hexo-blog/blob/8e95371d51f18a06b2800caf45b14c4505a6a8db/source/_posts/Linux%E5%91%BD%E4%BB%A4%E5%AD%A6%E4%B9%A0.md)\n\n\n\n## 12. Bash\n\nShell 是一个用 C 语言编写的程序，它是用户使用 Linux 的桥梁。Shell 既是一种命令语言，又是一种程序设计语言。可以通过 Shell 请求内核提供服务，Bash 正是 Shell 的一种。\n\nShell 是指一种应用程序，这个应用程序提供了一个界面，用户通过这个界面访问操作系统内核的服务。\n\nKen Thompson 的 sh 是第一种 Unix Shell，Windows Explorer 是一个典型的图形界面 Shell。\n\n\n\nShell 编程跟 Java、php 编程一样，只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。\n\nLinux 的 Shell 种类众多，常见的有：\n\n- Bourne Shell（/usr/bin/sh或/bin/sh）\n- **Bourne Again Shell（/bin/bash）**\n- C Shell（/usr/bin/csh）\n- K Shell（/usr/bin/ksh）\n- Shell for Root（/sbin/sh）\n- ……\n\n本教程关注的是 Bash，也就是 Bourne Again Shell，由于易用和免费，Bash 在日常工作中被广泛使用。同时，Bash 也是大多数Linux 系统默认的 Shell。\n\n在一般情况下，人们并不区分 Bourne Shell 和 Bourne Again Shell，所以，像 **#!/bin/sh**，它同样也可以改为 **#!/bin/bash**。\n\n\\#! 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序。\n\n\n\n参考资料：\n\n- [Linux脚本开头#!/bin/bash和#!/bin/sh是什么意思以及区别 - CSDN博客](https://blog.csdn.net/y_hanxiao/article/details/78638479)\n\n\n\n### 特性\n\n- 命令历史：记录使用过的命令\n- 命令与文件补全：快捷键：tab\n- 命名别名：例如 lm 是 ls -al 的别名\n- shell scripts\n- 通配符：例如 ls -l /usr/bin/X* 列出 /usr/bin 下面所有以 X 开头的文件\n\n\n\n### 变量操作\n\n对一个变量赋值直接使用 =。\n\n对变量取用需要在变量前加上 $ ，也可以用 ${} 的形式；\n\n输出变量使用 echo 命令。\n\n```\n$ x=abc\n$ echo $x\n$ echo ${x}\n```\n\n变量内容如果有空格，必须使用双引号或者单引号。\n\n- **双引号内的特殊字符可以保留原本特性**，例如 x=\"lang is $LANG\"，则 x 的值为 lang is zh_TW.UTF-8；\n- **单引号内的特殊字符就是特殊字符本身**，例如 x='lang is \\$LANG'，则 x 的值为 lang is $LANG。\n\n可以使用 `指令` 或者 (指令) 的方式将指令的执行结果赋值给变量。例如 version=(uname -r)，则 version 的值为 4.15.0-22-generic。\n\n可以使用 export 命令将自定义变量转成环境变量，环境变量可以在子程序中使用，所谓子程序就是由当前 Bash 而产生的子 Bash。\n\nBash 的变量可以声明为数组和整数数字。注意数字类型没有浮点数。如果不进行声明，默认是字符串类型。变量的声明使用 declare 命令：\n\n```\n$ declare [-aixr] variable\n-a ： 定义为数组类型\n-i ： 定义为整数类型\n-x ： 定义为环境变量\n-r ： 定义为 readonly 类型\n```\n\n使用 [ ] 来对数组进行索引操作：\n\n```\n$ array[1]=a\n$ array[2]=b\n$ echo ${array[1]}\n```\n\n### 指令搜索顺序\n\n- 以绝对或相对路径来执行指令，例如 /bin/ls 或者 ./ls ；\n- 由别名找到该指令来执行；\n- 由 Bash 内建的指令来执行；\n- 按 $PATH 变量指定的搜索路径的顺序找到第一个指令来执行。\n\n### 输出重定向\n\n重定向指的是使用文件代替标准输入、标准输出和标准错误输出。\n\n| 设备   | 类型         | 设备文件名  | 文件描述符 | 运算符    |\n| ------ | ------------ | ----------- | ---------- | --------- |\n| 键盘   | 标准输入     | /dev/stdin  | 0          | < 或 <<   |\n| 显示器 | 标准输出     | /dev/stdout | 1          | > 或 >>   |\n| 显示器 | 标准错误输出 | /dev/stderr | 2          | 2> 或 2>> |\n\n其中，**有一个箭头（>或者<）的表示以覆盖的方式重定向**，**而有两个箭头（>>或者<<）的表示以追加的方式重定向**。\n\n可以将不需要的标准输出以及标准错误输出重定向到 `/dev/null`（黑洞），相当于扔进垃圾箱。\n\n**注意：**在错误输出的时候 `2>` 或 `2>>` 后加文件名不能出现空格\n\n```\n$ llc 2>index.txt\n$ cat index.txt\n程序“llc”尚未安装。 您可以使用以下命令安装：\nsudo apt install llvm\n```\n\n在实际的应用中，上面的写法有一定问题，因为我们编写的时候并不能确定我们写的是正确的还是错误的，也就无法确定写入正确的文件还是错误的文件，因此这里仅作了解，用处不大。\n\n**正确输出和错误输出同时保存:**\n\n| 命令                     | 例子                               | 说明                                                     |\n| ------------------------ | ---------------------------------- | -------------------------------------------------------- |\n| 命令 > 文件 2>&1         | ll > abby.txt 2>&1                 | 以覆盖的方式，把正确输出和错误输出都保存到同一个文件当中 |\n| **命令 >> 文件 2>&1**    | ll >> abby.txt 2>&1                | 以追加的方式，把正确输出和错误输出都保存到同一个文件当中 |\n| 命令 &>文件              | llc &> abby.txt                    | 以覆盖的方式，把正确输出和错误输出都保存到同一个文件当中 |\n| **命令&>>文件**          | llc &>> abby.txt                   | 以追加的方式，把正确输出和错误输出都保存到同一个文件当中 |\n| **命令>>文件1 2>>文件2** | netstat >>success.txt 2>>error.txt | 把正确的输出追加到文件1中，把错误的输出追加到文件2中     |\n\n如果需要将标准输出以及标准错误输出同时重定向到一个文件，需要将某个输出转换为另一个输出，例如 `2>&1` 表示将标准错误输出转换为标准输出。\n\n```\n$ find /home -name .bashrc > list 2>&1\n```\n\n### 输入重定向\n\n**wc命令** 用来计算数字。利用 wc 指令我们可以计算文件的 Byte 数、字数或是列数，若不指定文件名称，或是所给予的文件名为 “-”，则 wc 指令会从标准输入设备读取数据。\n\n 语法 \n\n```\nwc(选项)(参数)\n```\n\n选项 \n\n```\n-c或--bytes或——chars：只显示Bytes数；\n-l或——lines：只显示列数；\n-w或——words：只显示字数。\n```\n\n参数 \n\n文件：需要统计的文件列表。\n\n\n\n参考资料：\n\n- [Shell 教程 | 菜鸟教程](http://www.runoob.com/linux/linux-shell.html)\n\n\n\n## 13. 正则表达式\n\n### cut\n\ncut 对数据进行切分，取出想要的部分。切分过程一行一行地进行。\n\n```shell\n$ cut\n-d ：分隔符\n-f ：经过 -d 分隔后，使用 -f n 取出第 n 个区间\n-c ：以字符为单位取出区间\n```\n\n\n\n例如有一个学生报表信息，包含No、Name、Mark、Percent：\n\n```shell\n$ cat test.txt \nNo Name Mark Percent\n01 tom 69 91\n02 jack 71 87\n03 alex 68 98\n```\n\n使用 `-f` 选项提取指定字段：\n\n```shell\n$ cut -f 1 test.txt \nNo\n01\n02\n03\n```\n\n```shell\n$ cut -f2,3 test.txt \nName Mark\ntom 69\njack 71\nalex 68\n```\n\n`--complement` 选项提取指定字段之外的列（打印除了第二列之外的列）：\n\n```shell\n$ cut -f2 --complement test.txt \nNo Mark Percent\n01 69 91\n02 71 87\n03 68 98\n```\n\n使用 `-d` 选项指定字段分隔符：\n\n```shell\n$ cat test2.txt \nNo;Name;Mark;Percent\n01;tom;69;91\n02;jack;71;87\n03;alex;68;98\n```\n\n```shell\n$ cut -f2 -d\";\" test2.txt \nName\ntom\njack\nalex\n```\n\n\n\n### grep\n\ng/re/p（globally search a regular expression and print)，使用正则表示式进行全局查找并打印。\n\n```shell\n$ grep [-acinv] [--color=auto] 搜寻字符串 filename\n-c ： 计算找到个数\n-i ： 忽略大小写\n-n ： 输出行号\n-v ： 反向选择，亦即显示出没有 搜寻字符串 内容的那一行\n--color=auto ：找到的关键字加颜色显示\n```\n\n示例：把含有 the 字符串的行提取出来（注意默认会有 --color=auto 选项，因此以下内容在 Linux 中有颜色显示 the 字符串）\n\n```shell\n$ grep -n 'the' regular_express.txt\n8:I can't finish the test.\n12:the symbol '*' is represented as start.\n15:You are the best is mean you are the no. 1.\n16:The world Happy is the same with \"glad\".\n18:google is the best tools for search keyword\n```\n\n因为 { 和 } 在 shell 是有特殊意义的，因此必须要使用转义字符进行转义。\n\n```shell\n$ grep -n 'go\\{2,5\\}g' regular_express.txt\n```\n\n\n\n### printf\n\n用于格式化输出。\n\n它不属于管道命令，在给 printf 传数据时需要使用 $( ) 形式。\n\n```shell\n$ printf '%10s %5i %5i %5i %8.2f \\n' $(cat printf.txt)\n    DmTsai    80    60    92    77.33\n     VBird    75    55    80    70.00\n       Ken    60    90    70    73.33\n```\n\n\n\n### awk\n\n是由 Alfred Aho，Peter Weinberger, 和 Brian Kernighan 创造，awk 这个名字就是这三个创始人名字的首字母。\n\nawk 每次处理一行，处理的最小单位是字段，每个字段的命名方式为：\\$n，n 为字段号，从 1 开始，$0 表示一整行。\n\n示例 1：取出登录用户的用户名和 ip\n\n```shell\n$ last -n 5\ndmtsai pts/0 192.168.1.100 Tue Jul 14 17:32 still logged in\ndmtsai pts/0 192.168.1.100 Thu Jul 9 23:36 - 02:58 (03:22)\ndmtsai pts/0 192.168.1.100 Thu Jul 9 17:23 - 23:36 (06:12)\ndmtsai pts/0 192.168.1.100 Thu Jul 9 08:02 - 08:17 (00:14)\ndmtsai tty1 Fri May 29 11:55 - 12:11 (00:15)\n\n$ last -n 5 | awk '{print $1 \"\\t\" $3}\n```\n\n可以根据字段的某些条件进行匹配，例如匹配字段小于某个值的那一行数据。\n\n```shell\n$ awk '条件类型 1 {动作 1} 条件类型 2 {动作 2} ...' filename\n```\n\n示例 2：/etc/passwd 文件第三个字段为 UID，对 UID 小于 10 的数据进行处理。\n\n```shell\n$ cat /etc/passwd | awk 'BEGIN {FS=\":\"} $3 < 10 {print $1 \"\\t \" $3}'\nroot 0\nbin 1\ndaemon 2\n```\n\nawk 变量：\n\n| 变量名称 | 代表意义                     |\n| -------- | ---------------------------- |\n| NF       | 每一行拥有的字段总数         |\n| NR       | 目前所处理的是第几行数据     |\n| FS       | 目前的分隔字符，默认是空格键 |\n\n示例 3：输出正在处理的行号，并显示每一行有多少字段\n\n```shell\n$ last -n 5 | awk '{print $1 \"\\t lines: \" NR \"\\t columns: \" NF}'\ndmtsai lines: 1 columns: 10\ndmtsai lines: 2 columns: 10\ndmtsai lines: 3 columns: 10\ndmtsai lines: 4 columns: 10\ndmtsai lines: 5 columns: 9\n```\n\n\n\n### sed\n\nLinux sed命令是利用script来处理文本文件。使得文本替换脚本化，操作很类似于vim\n\nsed可依照script的指令，来处理、编辑文本文件。\n\nSed主要用来自动编辑一个或多个文件；简化对文件的反复操作；编写转换程序等。\n\n\n\n**语法**\n\n```\nsed [-hnV][-e<script>][-f<script文件>][文本文件]\\\n```\n\n**动作说明**：\n\n- a ：新增， a 的后面可以接字串，而这些字串会在新的一行出现(目前的下一行)～\n- c ：取代， c 的后面可以接字串，这些字串可以取代 n1,n2 之间的行！\n- d ：删除，因为是删除啊，所以 d 后面通常不接任何咚咚；\n- i ：插入， i 的后面可以接字串，而这些字串会在新的一行出现(目前的上一行)；\n- p ：打印，亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行～\n- s ：取代，可以直接进行取代的工作哩！通常这个 s 的动作可以搭配正规表示法！例如 1,20s/old/new/g 就是啦！\n\n\n\n**示例**（这里就不一一展开示例了，更多请参考下面的链接）\n\n```\n# 替换文件中的所有匹配项\nsed -i 's/原字符串/替换字符串/g' filename\n```\n\n\n\n参考文档：\n\n- [Linux sed命令 | 菜鸟教程](http://www.runoob.com/linux/linux-comm-sed.html)\n- [sed 字符串替换 - Amei1314 - 博客园](https://www.cnblogs.com/linux-wangkun/p/5745584.html)\n  \n\n\n\n## 14. 进程管理\n\n### 查看进程\n\n#### 1. ps\n\n查看某个时间点的进程信息\n\n示例一：查看自己的进程\n\n```\n# ps -l\n```\n\n示例二：查看系统所有进程\n\n```\n# ps aux\n```\n\n示例三：查看特定的进程\n\n```\n# ps aux | grep threadx\n```\n\n```\n-a：显示所有终端机下执行的程序，除了阶段作业领导者之外。\n-u<用户识别码>：此选项的效果和指定\"-U\"选项相同。\nx：显示所有程序，不以终端机来区分。\n```\n\n#### 2. top\n\n实时显示进程信息\n\n示例：两秒钟刷新一次\n\n```\n# top -d 2\n```\n\n#### 3. pstree\n\n查看进程树\n\n示例：查看所有进程树\n\n```\n# pstree -A\n```\n\n#### 4. netstat\n\n查看占用端口的进程\n\n示例：查看特定端口的进程\n\n```\n# netstat -anp | grep port\n```\n\n\n\n参考资料：\n\n- [Linux基础13 进程管理_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili](https://www.bilibili.com/video/av9539203?from=search&seid=12568422774751055363)\n\n\n\n### 进程状态\n\n<div align=\"center\"><img src=\"assets/76a49594323247f21c9b3a69945445ee.png\" width=\"\"/></div>\n\n| 状态 | 说明                                                         |\n| ---- | ------------------------------------------------------------ |\n| R    | running or runnable (on run queue)                           |\n| D    | uninterruptible sleep (usually I/O)                          |\n| S    | interruptible sleep (waiting for an event to complete)       |\n| Z    | zombie (terminated but not reaped by its parent) 僵尸进程    |\n| T    | stopped (either by a job control signal or because it is being traced) |\n\n\n\n#### SIGCHLD\n\n当一个子进程改变了它的状态时：停止运行，继续运行或者退出，有两件事会发生在父进程中：\n\n- 得到 SIGCHLD 信号；\n- waitpid() 或者 wait() 调用会返回。\n\n<div align=\"center\"><img src=\"assets/flow.png\" width=\"\"/></div>\n\n其中子进程发送的 SIGCHLD 信号包含了子进程的信息，包含了进程 ID、进程状态、进程使用 CPU 的时间等。\n\n在子进程退出时，它的进程描述符不会立即释放，这是为了让父进程得到子进程信息。父进程通过 wait() 和 waitpid() 来获得一个已经退出的子进程的信息。\n\n\n\n#### wait()\n\n```c\npid_t wait(int *status)\n```\n\n父进程调用 wait() 会一直阻塞，直到收到一个子进程退出的 SIGCHLD 信号，之后 wait() 函数会销毁子进程并返回。\n\n如果成功，返回被收集的子进程的进程 ID；如果调用进程没有子进程，调用就会失败，此时返回 -1，同时 errno 被置为 ECHILD。\n\n参数 status 用来保存被收集的子进程退出时的一些状态，如果我们对这个子进程是如何死掉的毫不在意，只想把这个子进程消灭掉，可以设置这个参数为 NULL：\n\n```\npid = wait(NULL);\n```\n\n\n\n#### waitpid()\n\n```c\npid_t waitpid(pid_t pid, int *status, int options)\n```\n\n作用和 wait() 完全相同，但是多了两个可由用户控制的参数 pid 和 options。\n\npid 参数指示一个子进程的 ID，表示只关心这个子进程的退出 SIGCHLD 信号。如果 pid=-1 时，那么和 wait() 作用相同，都是关心所有子进程退出的 SIGCHLD 信号。\n\noptions 参数主要有 WNOHANG 和 WUNTRACED 两个选项，WNOHANG 可以使 waitpid() 调用变成非阻塞的，也就是说它会立即返回，父进程可以继续执行其它任务。\n\n\n\n#### 孤儿进程\n\n一个父进程退出，而它的一个或多个子进程还在运行，那么这些子进程将成为孤儿进程。\n\n孤儿进程将被 init 进程（进程号为 1）所收养，并由 init 进程对它们完成状态收集工作。\n\n由于孤儿进程会被 init 进程收养，所以孤儿进程不会对系统造成危害。\n\n\n\n#### 僵尸进程\n\n一个子进程的进程描述符在子进程退出时不会释放，只有当父进程通过 wait() 或 waitpid() 获取了子进程信息后才会释放。如果子进程退出，而父进程并没有调用 wait() 或 waitpid()，那么子进程的进程描述符仍然保存在系统中，这种进程称之为僵尸进程。\n\n僵尸进程通过 ps 命令显示出来的状态为 Z（zombie）。\n\n系统所能使用的进程号是有限的，如果大量的产生僵尸进程，将因为没有可用的进程号而导致系统不能产生新的进程。\n\n要消灭系统中大量的僵尸进程，只需要将其父进程杀死，此时所有的僵尸进程就会变成孤儿进程，从而被 init 所收养，这样 init 就会释放所有的僵死进程所占有的资源，从而结束僵尸进程。\n\n\n\n参考资料：\n\n- [孤儿进程与僵尸进程[总结] - Anker's Blog - 博客园](https://www.cnblogs.com/Anker/p/3271773.html)\n- [《深入理解计算机系统》异常控制流——读书笔记 - CSDN博客](https://blog.csdn.net/zhanghaodx082/article/details/12280689)\n- [Linux系统学习笔记：异常控制流 - CSDN博客](https://blog.csdn.net/yangxuefeng09/article/details/10066357)\n- [Linux 之守护进程、僵死进程与孤儿进程 | LiuYongbin](http://liubigbin.github.io/2016/03/11/Linux-%E4%B9%8B%E5%AE%88%E6%8A%A4%E8%BF%9B%E7%A8%8B%E3%80%81%E5%83%B5%E6%AD%BB%E8%BF%9B%E7%A8%8B%E4%B8%8E%E5%AD%A4%E5%84%BF%E8%BF%9B%E7%A8%8B/)\n- [CSAPP笔记第八章异常控制流 呕心沥血千行笔记- DDUPzy - 博客园](https://www.cnblogs.com/zy691357966/p/5480537.html)\n\n\n## 15. 进程和线程的区别\n\n**进程**：CPU资源分配的最小单位\n\n**线程**：CPU调度的最小单位\n\n例子：\n\n开个QQ，开了一个进程；开了迅雷，开了一个进程。 在QQ的这个进程里，传输文字开一个线程、传输语音开了一个线程、弹出对话框又开了一个线程。\n\n所以运行某个软件，相当于开了一个进程。在这个软件运行的过程里（在这个进程里），多个工作支撑的完成QQ的运行，那么这“多个工作”分别有一个线程。\n\n所以一个进程管着多个线程。\n\n通俗的讲：“进程是爹妈，管着众多的线程儿子”...\n\n\n\n参考资料：\n\n- [进程与线程的一个简单解释 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html)\n\n\n\n## 16. kill用法，某个进程杀不掉的原因（进入内核态，忽略kill信号） \n\n1. 该进程是僵尸进程（STAT z），此时进程已经释放所有的资源，但是没有被父进程释放。僵尸进程要等到父进程结束，或者重启系统才可以被释放。\n2. 进程处于“核心态”，并且在等待不可获得的资源，处于“核心态 ”的资源默认忽略所有信号。只能重启系统。\n\n\n\n参考资料：\n\n- [linux kill -9 杀不掉的进程 - CSDN博客](https://blog.csdn.net/lemontree1945/article/details/79169178)\n\n\n\n### kill\n\nkill命令用来删除执行中的程序或工作。kill可将指定的信息送至程序。预设的信息为SIGTERM(15),可将指定程序终止。若仍无法终止该程序，可使用SIGKILL(9)信息尝试强制删除程序。程序或工作的编号可利用[ps](http://man.linuxde.net/ps)指令或job指令查看。\n\n**语法** \n\n```\nkill(选项)(参数)\n```\n\n**选项** \n\n```\n-a：当处理当前进程时，不限制命令名和进程号的对应关系；\n-l <信息编号>：若不加<信息编号>选项，则-l参数会列出全部的信息名称；\n-p：指定kill 命令只打印相关进程的进程号，而不发送任何信号；\n-s <信息名称或编号>：指定要送出的信息；\n-u：指定用户。\n```\n\n**参数** \n\n进程或作业识别号：指定要删除的进程或作业。\n\n**实例** \n\n列出所有信号名称：\n\n```\n kill -l\n 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL\n 5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE\n 9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2\n13) SIGPIPE     14) SIGALRM     15) SIGTERM     16) SIGSTKFLT\n17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP\n21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU\n25) SIGXFSZ     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH\n29) SIGIO       30) SIGPWR      31) SIGSYS      34) SIGRTMIN\n35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3  38) SIGRTMIN+4\n39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8\n43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12\n47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14\n51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10\n55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6\n59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2\n63) SIGRTMAX-1  64) SIGRTMAX\n```\n\n只有第9种信号(SIGKILL)才可以无条件终止进程，其他信号进程都有权利忽略，**下面是常用的信号：**\n\n```\nHUP     1    终端断线\nINT     2    中断（同 Ctrl + C）\nQUIT    3    退出（同 Ctrl + \\）\nTERM   15    终止\nKILL    9    强制终止\nCONT   18    继续（与STOP相反， fg/bg命令）\nSTOP   19    暂停（同 Ctrl + Z）\n```\n\n先用ps查找进程，然后用kill杀掉：\n\n```\nps -ef | grep vim\nroot      3268  2884  0 16:21 pts/1    00:00:00 vim install.log\nroot      3370  2822  0 16:21 pts/0    00:00:00 grep vim\n\nkill 3268\nkill 3268\n-bash: kill: (3268) - 没有那个进程\n```\n\n\n\n### killall\n\nkillall命令使用进程的名称来杀死进程，使用此指令可以杀死一组同名进程。我们可以使用[kill](http://man.linuxde.net/kill)命令杀死指定进程PID的进程，如果要找到我们需要杀死的进程，我们还需要在之前使用[ps](http://man.linuxde.net/ps)等命令再配合[grep](http://man.linuxde.net/grep)来查找进程，而killall把这两个过程合二为一，是一个很好用的命令。\n\n**语法** \n\n```\nkillall(选项)(参数)\n```\n\n选项 \n\n```\n-e：对长名称进行精确匹配；\n-l：忽略大小写的不同；\n-p：杀死进程所属的进程组；\n-i：交互式杀死进程，杀死进程前需要进行确认；\n-l：打印所有已知信号列表；\n-q：如果没有进程被杀死。则不输出任何信息；\n-r：使用正规表达式匹配要杀死的进程名称；\n-s：用指定的进程号代替默认信号“SIGTERM”；\n-u：杀死指定用户的进程。\n```\n\n**参数** \n\n进程名称：指定要杀死的进程名称。\n\n**实例** \n\n杀死所有同名进程\n\n```\nkillall vi\n```\n\n\n\n## 17. 包管理工具\n\nRPM 和 DPKG 为最常见的两类软件包管理工具。RPM 全称为 Redhat Package Manager，最早由 Red Hat 公司制定实施，随后被 GNU 开源操作系统接受并成为很多 Linux 系统 (RHEL) 的既定软件标准。与 RPM 进行竞争的是基于 Debian 操作系统 (UBUNTU) 的 DEB 软件包管理工具 DPKG，全称为 Debian Package，功能方面与 RPM 相似。\n\nYUM 基于 RPM，具有依赖管理功能，并具有软件升级的功能。\n\n\n\n### 软件类型\n\n1. 源码包    需要编译   *.tar.gz\n2. 二进制包    已编译   *.rpm\n\n\n\n### 发行版\n\nLinux 发行版是 Linux 内核及各种应用软件的集成版本。\n\n| 基于的包管理工具<br />（包类型） | 商业发行版 | 社区发行版      | 工具         | 在线安装<br />（自动解决依赖关系） |\n| -------------------------------- | ---------- | --------------- | ------------ | ---------------------------------- |\n| RPM                              | Red Hat    | Fedora / CentOS | rpm/rpmbuild | yum                                |\n| DPKG                             | Ubuntu     | Debian          | dpkg         | apt                                |\n\n注意：不管是源码包，还是二进制包，安装时都可能会有依赖关系！\n\n\n\n## 18. 网络配置和网络诊断命令\n\n- 第一个命令ifconfig，这个命令可以查看当前主机的ip地址和网卡信息。（附加网卡的启用ifup eth0与停用stop eth0命令）\n\n- 第二个命令ping，ping命令可以用来测试网络的连通性，使用非常广泛，不论是一般用户还是黑客都喜欢钟爱这个命令。 \n\n- 第三个命令netstat，netstat(网络统计)命令显示连接信息,路由表信息等，通常配合使用参数，这里只演示一个参数。 \n\n- 第四个命令traceroute，traceroute是路由跟踪命令，可以查看到你从源到目的的所经过的路由。  \n\n- 第五、六个命令dig与nslookup，为什么放在一起讲呢?原因是它们既有联系又有区别，dig查询DNS相关信息记录,CNAME,MX记录等等。这个命令主要用于解决相关DNS查询；nslookup也具备查询DNS的功能，还可以显示一个ip地址的记录！\n\n- 第七、八个命令host和hostname，host命令可以用来查找到IP的名称或IP的名字在IPv4和IPv6 DNS记录和查询，hostname命令查看主机名，或者你可以到/etc/sysconfig/network中修改主机名。\n\n- 第九个命令route，可以用来查看路由表，也可以用来增加和删除路由条目。 \n\n- 第十个命令arp，ARP为地址解析协议，可以看到默认的表使用。\n- 第十一个命令ethtool，ethtool查看到网络模式和网络速度等信息。有关配置可以到/etc/sysconfig/network-scripts/ifcfg-eth0下进行修改。\n- GUI管理命令system-config-network，图形化的管理界面调用命令。配置网络设置也可以使用配置IP地址、网关、DNS等。\n\n\n\n参考资料：\n\n- [Linux网络配置和网络诊断命令介绍_百度经验](https://jingyan.baidu.com/article/c1465413b694d90bfcfc4c87.html)\n\n## 19. 磁盘管理\n\n**df 命令**用于显示磁盘分区上的可使用的磁盘空间。默认显示单位为 KB。可以利用该命令来获取硬盘被占用了多少空间，目前还剩下多少空间等信息。 \n\n查看系统磁盘设备，默认是 KB 为单位：\n\n```shell\n[root@LinServ-1 ~]# df\n文件系统               1K-块        已用     可用 已用% 挂载点\n/dev/sda2            146294492  28244432 110498708  21% /\n/dev/sda1              1019208     62360    904240   7% /boot\ntmpfs                  1032204         0   1032204   0% /dev/shm\n/dev/sdb1            2884284108 218826068 2518944764   8% /data1\n```\n\n使用 `-h` 选项以 KB 以上的单位来显示，可读性高：\n\n```shell\n[root@LinServ-1 ~]# df -h\n文件系统              容量  已用 可用 已用% 挂载点\n/dev/sda2             140G   27G  106G  21% /\n/dev/sda1             996M   61M  884M   7% /boot\ntmpfs                1009M     0 1009M   0% /dev/shm\n/dev/sdb1             2.7T  209G  2.4T   8% /data1\n```\n\n\n\nMBR\n\nGPT\n\nMOUNT\n\nparted\n\nmkfs\n\n\n\n【待补充】\n\n\n\n## 20. VIM 三个模式\n\n- 一般指令模式（Command mode）：VIM 的默认模式，可以用于移动游标查看内容；\n- 编辑模式（Insert mode）：按下 \"i\" 等按键之后进入，可以对文本进行编辑；\n- 指令列模式（Bottom-line mode）：按下 \":\" 按键之后进入，用于保存退出等操作。\n\n<div align=\"center\"><img src=\"assets/5942debd-fc00-477a-b390-7c5692cc8070.jpg\" width=\"400\"/></div>\n\n\n\n在指令列模式下，有以下命令用于离开或者保存文件。\n\n| 命令 | 作用                                                         |\n| ---- | ------------------------------------------------------------ |\n| :w   | 写入磁盘                                                     |\n| :w!  | 当文件为只读时，强制写入磁盘。到底能不能写入，与用户对该文件的权限有关 |\n| :q   | 离开                                                         |\n| :q!  | 强制离开不保存                                               |\n| :wq  | 写入磁盘后离开                                               |\n| :wq! | 强制写入磁盘后离开                                           |\n\n\n\n## 21. 用户管理\n\n### 创建用户\n\n```\nuseradd username -p password\n```\n\n### 删除用户\n\n```\nuserdel username\n```\n\n### 查看所有用户\n\n```\nvim /etc/passwd\n```\n\n### 普通用户改为高级用户\n\n当我们在创建用户成功之后 `vim /etc/passwd` 在最下边可以看到刚刚创建的用户。那么如果我想将刚才创建的用户改为高级管理员怎么办，要将他具有 root 一样的权限。\n查看 /etc/passwd 文件中 root 的 uid 和 gid\n\n```\nroot:x:0:0:root:/root:/bin/bash\n用户名:密码:Uid:Gid:描述:家目录:登录使用shell\n```\n\n我们可以看到 root 的 uid 和 gid 是 0  0，那么我们可以修改刚才新建的用户将 uid 和 gid 改为 0 0，那么就具有 root 的权限。\n\n### 创建的用户 SSH 生效\n\n用上面的命令新建用户和密码后打开 ssh 登录 发现无论如何也登陆不了，那么还需要执行如下一句命令\n\n```\necho password | passwd --stdin username\n```\n\n\n\n## 22. lspci\n\nlspci 是一个用来显示系统中所有PCI总线设备或连接到该总线上的所有设备的工具。\n\n**语法** \n\n```\nlspci(选项)\n```\n\n**选项** \n\n```\n-n：以数字方式显示PCI厂商和设备代码；\n-t：以树状结构显示PCI设备的层次关系，包括所有的总线、桥、设备以及它们之间的联接；\n-b：以总线为中心的视图；\n-d：仅显示给定厂商和设备的信息；\n-s：仅显示指定总线、插槽上的设备和设备上的功能块信息；\n-i：指定PCI编号列表文件，而不使用默认的文件；\n-m：以机器可读方式显示PCI设备信息。\n```\n\n**例子**\n\n查看显卡信息\n\n```shell\n$ lspci | grep VGA\n```\n\n\n\n## 23. Screen命令\n\n> 经常我们通过SecureCRT、Puty这样的工具连上服务器进行命令操作，但是安装的过程中很可能会出现断网或者是不小心关闭窗口，造成安装中断，为了防止这种现象，接下来介绍screen命令的使用。\n\n\n\n### screen命令是什么\n\n- Screen 是一个可以在多个进程之间多路复用一个物理终端的全屏窗口管理器。Screen 中有会话的概念，用户可以在一个 screen 会话中创建多个 screen 窗口，在每一个 screen 窗口中就像操作一个真实的 telnet/SSH 连接窗口那样。\n\n### 安装\n\n- CentOS系统可以执行：`yum install screen`\n- Debian/Ubuntu系统执行：`apt-get install screen`\n\n### 使用方法\n\n1、常用的使用方法\n\n- 用来解决文章开始我们遇到的问题，比如在安装lnmp时。\n\n  1.1 创建 screen 会话\n\n  - 可以先执行：`screen -S lnmp` ，screen就会创建一个名字为 lnmp 的会话。\n\n  1.2 暂时离开，保留screen会话中的任务或程序\n\n  - 当需要临时离开时（会话中的程序不会关闭，仍在运行）可以用快捷键 Ctrl+a+d (即按住Ctrl，依次再按a,d)\n\n  1.3 恢复screen会话\n\n  - 当回来时可以再执行执行：`screen -r lnmp`即可恢复到离开前创建的 lnmp 会话的工作界面。\n  - 如果忘记了，或者当时没有指定会话名，可以执行：`screen -ls` screen会列出当前存在的会话列表，如下图：\n\n```shell\n$ screen -ls\nThere are screens on:\n\t\t11791.lnmp     (Attached)\n        27620.frank     (Attached)\n        27545.pts-0.chengchi    (Attached)\n3 Sockets in /var/run/screen/S-root.\n```\n\n- `11791.lnmp` 即为刚才的 screen 创建的 lnmp 会话，目前已经暂时退出了 lnmp 会话，所以状态为Detached，当使用 `screen -r lnmp` 后状态就会变为 Attached，11791 是这个 screen 的会话的进程 ID，恢复会话时也可以使用：`screen -r 11791`\n\n  1.4 关闭screen的会话\n\n  - 执行：exit ，会提示：[screen is terminating]，表示已经成功退出screen会话。\n\n### 远程演示\n\n- 首先演示者先在服务器上执行 `screen -S test`\n- 创建一个screen会话 \n  观众可以链接到远程服务器上执行 `screen -x test` 观众屏幕上就会出现和演示者同步。\n\n### 常用快捷键\n\n- `Ctrl+a c` ：在当前screen会话中创建窗口\n- `Ctrl+a w` ：窗口列表\n- `Ctrl+a n` ：下一个窗口\n- `Ctrl+a p` ：上一个窗口\n- `Ctrl+a 0-9` ：在第0个窗口和第9个窗口之间切换\n\n\n\n## 24. Linux 下如何查看系统版本\n\nLinu x下如何查看版本信息， 包括位数、版本信息以及 CPU 内核信息、CPU 具体型号等等，整个 CPU 信息一目了然。\n\n \n\n1. 查看版本当前操作系统内核信息\n\n```shell\n$ uname －a \n\nLinux vm10-0-0-21.ksc.com 3.10.0-693.21.1.el7.x86_64 #1 SMP Wed Mar 7 19:03:37 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux\n```\n\n2. 查看当前操作系统版本信息\n\n```shell\n$ cat /proc/version\n\nLinux version 3.10.0-693.21.1.el7.x86_64 (builder@kbuilder.dev.centos.org) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-16) (GCC) ) #1 SMP Wed Mar 7 19:03:37 UTC 2018\n```\n\n3. 查看版本当前操作系统发行版信息\n\n```shell\n$ cat /etc/issue  或cat /etc/redhat-release\n\nRed Hat Linux release 9 (Shrike)\n```\n\n4. 查看 CPU 相关信息，包括型号、主频、内核信息等\n\n```shell\n$ cat /proc/cpuinfo\n```\n\n5. 查看版本说明当前 CPU 运行在32bit模式下， 但不代表 CPU 不支持64bit\n\n```shell\n$ getconf LONG_BIT \n```\n\n6. 系统详细信息\n\n```shell\n$ lsb_release -a\n```\n\n\n\n## 25. 常用快捷方式\n\n- ctrl + c：强制终止当前命令\n\n- ctrl + l：清屏\n\n- ctrl + a：光标移动到命令行首\n\n- ctrl + e：光标移动到命令行尾\n\n- ctrl + u：从光标所在位置删除到行首\n\n- ctrl + z：把命令放入后台（配合fg使用）\n\n- ctrl + r：在历史命令中搜索\n\n\n\n## 26. 高并发网络编程之epoll详解\n\n详情请转向：[高并发网络编程之epoll详解 - CSDN博客](https://blog.csdn.net/shenya1314/article/details/73691088)\n\n\n\n\n\n## 27. Linux下限制IP访问\n\nLinux下限制IP访问 - sinat_24928447的博客 - CSDN博客\nhttps://blog.csdn.net/sinat_24928447/article/details/78042290\n\n\n\n\n\n## 28. ssh命令连接服务器\n\n【随笔】ssh登录时如何直接在参数中加入登录密码 - linxiong - 博客园\nhttps://www.cnblogs.com/linxiong945/p/4226211.html\n\n\n\nsshpass的安装使用 - 蓝凌的博客 - CSDN博客\nhttps://blog.csdn.net/qq_30553235/article/details/78711491\n\n\n\n\n\n## 29. 防火墙\n\nCentos中iptables和firewall防火墙开启、关闭、查看状态、基本设置等 - 菲宇运维 - CSDN博客\nhttps://blog.csdn.net/bbwangj/article/details/74502967\n\n\n\nlinux如何查看端口被哪个进程占用？ - 晓容晓枫 - 博客园\nhttps://www.cnblogs.com/CEO-H/p/7794306.html\n\n\n\n\n\n1、lsof -i:端口号\n\n2、netstat -tunlp|grep 端口号\n\n都可以查看指定端口被哪个进程占用的情况\n\n\n\n\n\n\n\n# 参考资料\n\n- [【Linux】初踏足Linux的大门 - CSDN博客](https://blog.csdn.net/qq_41035588/article/details/80947383)\n\n- [如何学习（记住）linux命令（常用选项）？ - 知乎](https://www.zhihu.com/question/21690166/answer/66721478)\n\n\n\n# 更新日志\n\n- 2018/7/23 v1.0 版本\n\n- 2018/8/23 v2.0 基础版1\n\n- 2018/8/26 - 27 v2.5 基础版2\n"
  },
  {
    "path": "notes/Linux实战.md",
    "content": "Linux命令之ll命令显示内容日期格式 - 师成 - 关注云计算,关注大数据 - CSDN博客\nhttps://blog.csdn.net/ShrCheng/article/details/80833124\n\n\n\n永久修改时间格式使用以下命令:\n\n```bash\nsudo echo \"export TIME_STYLE='+%Y-%m-%d %H:%M:%S'\" >> /etc/profile && source /etc/profile\n```\n\n\n\nCentOS设置DNS\n\n通过编辑/etc/resolv.conf文件，往里边添加内容：\n\n> nameserver 8.8.8.8\n>\n> nameserver 8.8.4.4\n\n然后保存退出。这两个IP地址是谷歌公开的DNS。\n\n\n\n\n\n\n\nCentOS修改时区、日期、时间 - kaynet - 博客园\nhttps://www.cnblogs.com/kaynet/p/6409274.html\n\n\n\n解决配置vim中文乱码的问题 - weixin_36250487的博客 - CSDN博客\nhttps://blog.csdn.net/weixin_36250487/article/details/79888103\n\n解决linux下vim乱码的情况：(修改vimrc的内容）\n\n全局的情况下：即所有用户都能用这个配置\n\n文件地址：/etc/vimrc\n\n在文件中添加：\n\n```\nset fileencodings=utf-8,ucs-bom,gb18030,gbk,gb2312,cp936\nset termencoding=utf-8\nset encoding=utf-8\n```\n\n如果只修改个人的vim配置情况：\n\n需要把/etc/vimrc复制到你自己的根目录下面：复制为.vimrc(前面有个点，作为隐藏文件)\n\n然后把上面三句话加入到你的文件中,如下图，保存退出就ok了。\n\n\n\nwarning: push.default is unset的解决方案 - 笨笨个人笔记 - CSDN博客\nhttps://blog.csdn.net/jrainbow/article/details/19338525"
  },
  {
    "path": "notes/MachineLearning/README.md",
    "content": "机器学习板块"
  },
  {
    "path": "notes/MachineLearning/assets/README.md",
    "content": "机器学习板块-图片文件夹"
  },
  {
    "path": "notes/Memcached.md",
    "content": "Memcached集群_布尔教育_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili\nhttps://www.bilibili.com/video/av17984532?p=2\n\n\n\n\n\n"
  },
  {
    "path": "notes/MicroService/kafka/README.md",
    "content": "<div align=\"left\"><img src=\"assets/logo.png\" width=\"250px\"/></div>\r\n\r\n# Kafka Tutorial\r\n\r\n深入浅出 Kafka，将用最极简的语言带你走进 Kafka 的消息中间件世界。\r\n\r\n| 部分 | 章节                                                         | 概要                                            | 进度 |\r\n| :--- | :----------------------------------------------------------- | :---------------------------------------------- | :--- |\r\n| Ⅰ    | [深入浅出 Kafka（一）初识](kafka-tutorial-1_%E5%88%9D%E8%AF%86.md) | 背景、核心概念、架构设计                        | √    |\r\n| Ⅱ    | [深入浅出 Kafka（二）单节点部署](kafka-tutorial-2_%E5%8D%95%E8%8A%82%E7%82%B9%E9%83%A8%E7%BD%B2.md) | 单节点下，宿主机和容器的两种部署方式            | √    |\r\n| Ⅲ    | 深入浅出 Kafka（三）集群化部署                               | 集群下的，宿主机和容器的两种部署方式            |      |\r\n| Ⅳ    | [深入浅出 Kafka（四）架构深入](kafka-tutorial-4_%E6%9E%B6%E6%9E%84%E6%B7%B1%E5%85%A5.md) | 深入理解 Kafka 的架构细节                       | √    |\r\n| Ⅴ    | [深入浅出 Kafka（五）Kafka API](kafka-tutorial-5_kafka-api.md) | Kafka API 接口使用                              | √    |\r\n| Ⅵ    | [深入浅出 Kafka（六）Spring Kafka API](kafka-tutorial-6_spring-kafka-api.md) | Kafka 与 SpringBoot 框架整合，常见 API 接口使用 | √    |\r\n| Ⅶ    | 深入浅出 Kafka（七）监控                                     | Kafka Monitor，Kafka Manager                    |      |\r\n\r\n\r\n\r\n## 参考资料\r\n\r\n- 官网：[Apache Kafka](https://kafka.apache.org/)\r\n- 中文社区：[Kafka 中文文档 - ApacheCN](http://kafka.apachecn.org/)\r\n- 快速上手：[尚硅谷大数据课程之Kafka（2019新版）](https://www.bilibili.com/video/av65544753?from=search&seid=14596778029771113163)\r\n\r\n"
  },
  {
    "path": "notes/MicroService/kafka/kafka-tutorial-1_初识.md",
    "content": "\r\n<!-- TOC -->\r\n\r\n- [深入浅出 Kafka（一）初识](#深入浅出-kafka一初识)\r\n    - [一、定义](#一定义)\r\n    - [二、消息队列（Message Queue）](#二消息队列message-queue)\r\n        - [传统消息队列的应用场景](#传统消息队列的应用场景)\r\n        - [使用消息队列的好处](#使用消息队列的好处)\r\n        - [消息队列的两种模式](#消息队列的两种模式)\r\n            - [1. 点对点（Queue，不可重复消费）](#1-点对点queue不可重复消费)\r\n            - [2. 发布/订阅（Topic，可以重复消费）](#2-发布订阅topic可以重复消费)\r\n    - [三、Kafka基础架构](#三kafka基础架构)\r\n    - [参考资料](#参考资料)\r\n\r\n<!-- /TOC -->\r\n# 深入浅出 Kafka（一）初识\r\n\r\n> 开始前，可以阅读 Kafka 官方介绍：[Kafka 中文文档 - ApacheCN](http://kafka.apachecn.org/intro.html)\r\n\r\n\r\n\r\n## 一、定义 \r\n\r\nKafka 是一个分布式的基于发布/订阅模式的消息队列（Message Queue），主要应用于大数据实时处理领域。 \r\n\r\n\r\n\r\n## 二、消息队列（Message Queue）\r\n\r\n### 传统消息队列的应用场景\r\n\r\n![nAc6zQ.png](assets/nAc6zQ.png)\r\n\r\n\r\n\r\n\r\n### 使用消息队列的好处 \r\n\r\n- 解耦 \r\n  - 允许你独立的扩展或修改两边的处理过程，只要确保它们遵守同样的接口约束。 \r\n\r\n- 可恢复性 \r\n  - 系统的一部分组件失效时，不会影响到整个系统。消息队列降低了进程间的耦合度，所以即使一个处理消息的进程挂掉，加入队列中的消息仍然可以在系统恢复后被处理。 \r\n\r\n- 缓冲\r\n  - 有助于控制和优化数据流经过系统的速度，解决生产消息和消费消息的处理速度不一致的情况。 \r\n\r\n- 灵活性 & 峰值处理能力\r\n  - 在访问量剧增的情况下，应用仍然需要继续发挥作用，但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力，而不会因为突发的超负荷的请求而完全崩溃。 \r\n\r\n- 异步通信 \r\n  - 很多时候，用户不想也不需要立即处理消息。消息队列提供了异步处理机制，允许用户把一个消息放入队列，但并不立即处理它。想向队列中放入多少消息就放多少，然后在需要的时候再去处理它们。\r\n\r\n\r\n\r\n\r\n\r\n### 消息队列的两种模式\r\n\r\nJava消息服务（Java Message Service，JMS）规范目前支持两种消息模型：点对点（point to point， queue）和发布/订阅（publish/subscribe，topic）。\r\n\r\n#### 1. 点对点（Queue，不可重复消费）\r\n\r\n消息生产者生产消息发送到queue中，然后消息消费者从queue中取出并且消费消息。\r\n消息被消费以后，queue中不再有存储，所以消息消费者不可能消费到已经被消费的消息。Queue支持存在多个消费者，但是对一个消息而言，只会有一个消费者可以消费。\r\n\r\n![kafka-mq1](assets/kafka-mq1.png)\r\n\r\n\r\n\r\n#### 2. 发布/订阅（Topic，可以重复消费）\r\n\r\nPub/Sub发布订阅（广播）：使用topic作为通信载体\r\n\r\n消息生产者（发布）将消息发布到topic中，同时有多个消息消费者（订阅）消费该消息。和点对点方式不同，发布到topic的消息会被所有订阅者消费。\r\n\r\ntopic实现了发布和订阅，当你发布一个消息，所有订阅这个topic的服务都能得到这个消息，所以从1到N个订阅者都能得到一个消息的拷贝。\r\n\r\n![kafka-mq2](assets/kafka-mq2.png)\r\n\r\n\r\n\r\n## 三、Kafka基础架构\r\n\r\n![nAcIiT.png](assets/nAcIiT.png)\r\n\r\n- **Producer**：消息生产者，就是向kafka broker发消息的客户端；\r\n\r\n- **Consumer**：消息消费者，向kafka broker取消息的客户端；\r\n\r\n- **Consumer Group(CG)**：消费者组，由多个consumer组成。****消费者组内每个消费者负责消费不同分区的数据，一个分区只能由一个消费者消费；消费者组之间互不影响。所有的消费者都属于某个消费者组，即消费者组是逻辑上的一个订阅者。\r\n\r\n- **Broker** 一台kafka服务器就是一个broker。一个集群由多个broker组成。一个broker可以容纳多个topic。\r\n\r\n- **Topic** 可以理解为一个队列，**生产者和消费者面向的都是一个topic**；\r\n\r\n- **Partidion** 为了实现扩展性，一个非常大的topic可以分布到多个broker（即服务器）上，**一个topic可以分为多个partition**，每个partition是一个有序的队列；（分区主要使用来实现负载均衡）\r\n\r\n- **Replica** 副本，为保证集群中的某个节点发生故障时，该节点上的partition数据不丢失，且kafka仍然能够继续工作，kafka提供了副本机制，一个topic的每个分区都有若干个副本，一个**leader**和若干个**follower**。\r\n\r\n- **Leader** 每个分区多个副本的“主”，生产者发送数据的对象，以及消费者消费数据的对象都是leader。\r\n\r\n- **Follower** 每个分区多个副本中的“从”，实时从leader中同步数据，保持和leader数据的同步。leader发生故障时，某个follower会成为新的follower。\r\n\r\n\r\n\r\n## 参考资料\r\n\r\n- [Note/Kafka.md at master · Tiankx1003/Note](https://github.com/Tiankx1003/Note/blob/master/Markdown/HadoopEcosys/Kafka.md)\r\n"
  },
  {
    "path": "notes/MicroService/kafka/kafka-tutorial-2_单节点部署.md",
    "content": "<!-- TOC -->\r\n\r\n- [深入浅出 Kafka（二）单节点部署](#深入浅出-kafka二单节点部署)\r\n    - [系统环境](#系统环境)\r\n    - [一、宿主机部署](#一宿主机部署)\r\n        - [安装 Zookeeper（可选择，自带或是独立的 zk 服务）](#安装-zookeeper可选择自带或是独立的-zk-服务)\r\n        - [下载 Kafka](#下载-kafka)\r\n        - [启动 Zookeeper 服务（可选择，自带或是独立的 zk 服务）](#启动-zookeeper-服务可选择自带或是独立的-zk-服务)\r\n        - [启动 Kafka 服务](#启动-kafka-服务)\r\n        - [创建 Topic](#创建-topic)\r\n        - [查看 Topic](#查看-topic)\r\n        - [产生消息](#产生消息)\r\n        - [消费消息](#消费消息)\r\n        - [删除 Topic](#删除-topic)\r\n        - [查看描述 Topic 信息](#查看描述-topic-信息)\r\n    - [二、容器化部署](#二容器化部署)\r\n        - [1 Zookeeper + 1 Kafka](#1-zookeeper--1-kafka)\r\n        - [与容器内的开发环境交互](#与容器内的开发环境交互)\r\n    - [三、Kafka 配置说明](#三kafka-配置说明)\r\n    - [参考资料](#参考资料)\r\n\r\n<!-- /TOC -->\r\n\r\n\r\n# 深入浅出 Kafka（二）单节点部署\r\n\r\n> 单节点部署环境，主要用于学习与调试。集群化部署方案，请访问「深入浅出 Kafka（三）集群化部署」；若部署完单节点想进一步学习，请转向「深入浅出 Kafka（四）架构深入」。\r\n\r\n\r\n\r\n## 系统环境\r\n\r\n- CentOS 7.4\r\n- Kafka 2.11\r\n\r\n\r\n\r\n## 一、宿主机部署\r\n\r\n### 安装 Zookeeper（可选择，自带或是独立的 zk 服务）\r\n\r\n下载\r\n\r\n```shell\r\nwget https://mirrors.huaweicloud.com/apache/zookeeper/zookeeper-3.4.10/zookeeper-3.4.10.tar.gz\r\n```\r\n\r\n启动 \r\n\r\n```SHELL\r\nsh zkServer.sh start\r\n```\r\n\r\n\r\n\r\n### 下载 Kafka\r\n\r\n从[官网下载](https://kafka.apache.org/downloads)Kafka 安装包，解压安装，或直接使用命令下载。\r\n\r\n```shell\r\nwget https://mirrors.huaweicloud.com/apache/kafka/1.1.0/kafka_2.12-1.1.0.tgz\r\n```\r\n\r\n解压安装\r\n\r\n```shell\r\ntar -zvxf kafka_2.11-1.0.0.tgz -C /usr/local/\r\ncd /usr/local/kafka_2.11-1.0.0/\r\n```\r\n\r\n修改配置文件\r\n\r\n```\r\nvim config/server.properties\r\n```\r\n\r\n修改其中\r\n\r\n```\r\nlog.dirs=data/kafka-logs\r\nlisteners=PLAINTEXT://192.168.72.133:9092\r\n```\r\n\r\n> 另外 advertised.listeners，是暴露给外部的 listeners，如果没有设置，会用 listeners\r\n\r\n\r\n\r\n### 启动 Zookeeper 服务（可选择，自带或是独立的 zk 服务）\r\n\r\n使用安装包中的脚本启动单节点 Zookeeper 实例：\r\n\r\n```\r\nbin/zookeeper-server-start.sh -daemon config/zookeeper.properties\r\n```\r\n\r\n\r\n\r\n### 启动 Kafka 服务\r\n\r\n使用 kafka-server-start.sh 启动 kafka 服务\r\n\r\n前台启动\r\n\r\n```\r\nbin/kafka-server-start.sh config/server.properties\r\n```\r\n\r\n后台启动\r\n\r\n```\r\nbin/kafka-server-start.sh -daemon config/server.properties\r\n```\r\n\r\n\r\n\r\n### 创建 Topic\r\n\r\n使用 kafka-topics.sh 创建但分区单副本的 topic test\r\n\r\n```\r\nbin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test\r\n```\r\n\r\n\r\n\r\n### 查看 Topic\r\n\r\n```\r\nbin/kafka-topics.sh --list --zookeeper localhost:2181\r\n```\r\n\r\n\r\n\r\n### 产生消息\r\n\r\n使用 kafka-console-producer.sh 发送消息\r\n\r\n```\r\nbin/kafka-console-producer.sh --broker-list localhost:9092 --topic test\r\n```\r\n\r\n\r\n\r\n### 消费消息\r\n\r\n使用 kafka-console-consumer.sh 接收消息并在终端打印\r\n\r\n```\r\nbin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic test --from-beginning\r\n```\r\n\r\n\r\n\r\n### 删除 Topic\r\n\r\n```\r\nbin/kafka-topics.sh --delete --zookeeper localhost:2181 --topic test\r\n```\r\n\r\n\r\n\r\n### 查看描述 Topic 信息\r\n\r\n```shell\r\n[root@localhost kafka_2.11-1.0.0]# bin/kafka-topics.sh --describe --zookeeper \r\nTopic:test      PartitionCount:1        ReplicationFactor:1     Configs:\r\n        Topic: test     Partition: 0    Leader: 1       Replicas: 1     Isr: 1\r\n```\r\n\r\n第一行给出了所有分区的摘要，每个附加行给出了关于一个分区的信息。 由于我们只有一个分区，所以只有一行。\r\n\r\n- Leader\r\n  - 是负责给定分区的所有读取和写入的节点。 每个节点将成为分区随机选择部分的领导者。\r\n\r\n- Replicas\r\n  - 是复制此分区日志的节点列表，无论它们是否是领导者，或者即使他们当前处于活动状态。\r\n\r\n- Isr\r\n  - 是一组 “同步” 副本。这是复制品列表的子集，当前活着并被引导到领导者。\r\n\r\n\r\n\r\n## 二、容器化部署\r\n\r\n　　在上述的篇幅中，实现了宿主机上部署单节点环境（1 Zookeeper + 1 Kafka）。但是在不同环境配置上具有差异性，初学者入门需要进行复杂的配置，可能会造成配置失败。\r\n\r\n　　使用 Docker 容器化部署可以实现开箱即用，免去了很多安装配置的时间。\r\n\r\n### 1 Zookeeper + 1 Kafka\r\n\r\n　　以 [wurstmeister/kafka - Docker Hub](https://hub.docker.com/r/wurstmeister/kafka/) 为例，使用 docker-compose 运行一个只有一个 ZooKeeper node 和一个 Kafka broker 的开发环境：\r\n\r\n```yaml\r\nversion: '2'\r\nservices:\r\n  zoo1:\r\n    image: wurstmeister/zookeeper\r\n    restart: unless-stopped\r\n    hostname: zoo1\r\n    ports:\r\n      - \"2181:2181\"\r\n    container_name: zookeeper\r\n\r\n  # kafka version: 1.1.0\r\n  # scala version: 2.12\r\n  kafka1:\r\n    image: wurstmeister/kafka\r\n    ports:\r\n      - \"9092:9092\"\r\n    environment:\r\n      KAFKA_ADVERTISED_HOST_NAME: localhost\r\n      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://192.168.72.133:9092\r\n      KAFKA_ZOOKEEPER_CONNECT: \"zoo1:2181\"\r\n      KAFKA_BROKER_ID: 1\r\n      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1\r\n      KAFKA_CREATE_TOPICS: \"stream-in:1:1,stream-out:1:1\"\r\n    depends_on:\r\n      - zoo1\r\n    container_name: kafka\r\n```\r\n\r\n　　这里利用了 wurstmeister/kafka 提供的环境参数 `KAFKA_CREATE_TOPICS` 使Kafka运行后自动创建 topics。\r\n\r\n\r\n\r\n### 与容器内的开发环境交互\r\n\r\n　　可以使用 `docker exec` 命令直接调用 kafka 容器内的脚本来进行创建/删除 topic，启动 console producer 等等操作。\r\n\r\n　　如果本地存有与容器内相同的 Kafka 版本文件，也可以直接使用本地脚本文件。如上述 docker-compose.yml 文件所示，kafka1 的 hostname 即是 kafka1，端口为 9092，通过 kafka1:9092 就可以连接到容器内的 Kafka 服务。\r\n\r\n\r\n\r\n**列出所有 topics** (在本地 kafka 路径下)\r\n\r\n```shell\r\n$ bin/kafka-topics.sh --zookeeper localhost:2181 --list\r\n```\r\n\r\n**列出所有 Kafka brokers**\r\n\r\n```shell\r\n$ docker exec zookeeper bin/zkCli.sh ls /brokers/ids\r\n```\r\n\r\n\r\n\r\n## 三、Kafka 配置说明\r\n\r\n详细：[server.properties - Kafka 中文文档 - ApacheCN](http://kafka.apachecn.org/documentation.html#configuration)\r\n\r\n```properties\r\n# Licensed to the Apache Software Foundation (ASF) under one or more\r\n# contributor license agreements.  See the NOTICE file distributed with\r\n# this work for additional information regarding copyright ownership.\r\n# The ASF licenses this file to You under the Apache License, Version 2.0\r\n# (the \"License\"); you may not use this file except in compliance with\r\n# the License.  You may obtain a copy of the License at\r\n#\r\n#    http://www.apache.org/licenses/LICENSE-2.0\r\n#\r\n# Unless required by applicable law or agreed to in writing, software\r\n# distributed under the License is distributed on an \"AS IS\" BASIS,\r\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n# See the License for the specific language governing permissions and\r\n# limitations under the License.\r\n\r\n# see kafka.server.KafkaConfig for additional details and defaults\r\n\r\n############################# Server Basics #############################\r\n\r\n# The id of the broker. This must be set to a unique integer for each broker.\r\nbroker.id=0\r\n\r\n############################# Socket Server Settings #############################\r\n\r\n# The address the socket server listens on. It will get the value returned from \r\n# java.net.InetAddress.getCanonicalHostName() if not configured.\r\n#   FORMAT:\r\n#     listeners = listener_name://host_name:port\r\n#   EXAMPLE:\r\n#     listeners = PLAINTEXT://your.host.name:9092\r\nlisteners=PLAINTEXT://192.168.72.133:9092\r\n\r\n# Hostname and port the broker will advertise to producers and consumers. If not set, \r\n# it uses the value for \"listeners\" if configured.  Otherwise, it will use the value\r\n# returned from java.net.InetAddress.getCanonicalHostName().\r\n#advertised.listeners=PLAINTEXT://your.host.name:9092\r\n\r\n# Maps listener names to security protocols, the default is for them to be the same. See the config documentation for more details\r\n#listener.security.protocol.map=PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL\r\n\r\n# The number of threads that the server uses for receiving requests from the network and sending responses to the network\r\nnum.network.threads=3\r\n\r\n# The number of threads that the server uses for processing requests, which may include disk I/O\r\nnum.io.threads=8\r\n\r\n# The send buffer (SO_SNDBUF) used by the socket server\r\nsocket.send.buffer.bytes=102400\r\n\r\n# The receive buffer (SO_RCVBUF) used by the socket server\r\nsocket.receive.buffer.bytes=102400\r\n\r\n# The maximum size of a request that the socket server will accept (protection against OOM)\r\nsocket.request.max.bytes=104857600\r\n\r\n\r\n############################# Log Basics #############################\r\n\r\n# A comma separated list of directories under which to store log files\r\nlog.dirs=/tmp/kafka-logs\r\n\r\n# The default number of log partitions per topic. More partitions allow greater\r\n# parallelism for consumption, but this will also result in more files across\r\n# the brokers.\r\nnum.partitions=1\r\n\r\n# The number of threads per data directory to be used for log recovery at startup and flushing at shutdown.\r\n# This value is recommended to be increased for installations with data dirs located in RAID array.\r\nnum.recovery.threads.per.data.dir=1\r\n\r\n############################# Internal Topic Settings  #############################\r\n# The replication factor for the group metadata internal topics \"__consumer_offsets\" and \"__transaction_state\"\r\n# For anything other than development testing, a value greater than 1 is recommended for to ensure availability such as 3.\r\noffsets.topic.replication.factor=1\r\ntransaction.state.log.replication.factor=1\r\ntransaction.state.log.min.isr=1\r\n\r\n############################# Log Flush Policy #############################\r\n\r\n# Messages are immediately written to the filesystem but by default we only fsync() to sync\r\n# the OS cache lazily. The following configurations control the flush of data to disk.\r\n# There are a few important trade-offs here:\r\n#    1. Durability: Unflushed data may be lost if you are not using replication.\r\n#    2. Latency: Very large flush intervals may lead to latency spikes when the flush does occur as there will be a lot of data to flush.\r\n#    3. Throughput: The flush is generally the most expensive operation, and a small flush interval may lead to excessive seeks.\r\n# The settings below allow one to configure the flush policy to flush data after a period of time or\r\n# every N messages (or both). This can be done globally and overridden on a per-topic basis.\r\n\r\n# The number of messages to accept before forcing a flush of data to disk\r\n#log.flush.interval.messages=10000\r\n\r\n# The maximum amount of time a message can sit in a log before we force a flush\r\n#log.flush.interval.ms=1000\r\n\r\n############################# Log Retention Policy #############################\r\n\r\n# The following configurations control the disposal of log segments. The policy can\r\n# be set to delete segments after a period of time, or after a given size has accumulated.\r\n# A segment will be deleted whenever *either* of these criteria are met. Deletion always happens\r\n# from the end of the log.\r\n\r\n# The minimum age of a log file to be eligible for deletion due to age\r\nlog.retention.hours=168\r\n\r\n# A size-based retention policy for logs. Segments are pruned from the log unless the remaining\r\n# segments drop below log.retention.bytes. Functions independently of log.retention.hours.\r\n#log.retention.bytes=1073741824\r\n\r\n# The maximum size of a log segment file. When this size is reached a new log segment will be created.\r\nlog.segment.bytes=1073741824\r\n\r\n# The interval at which log segments are checked to see if they can be deleted according\r\n# to the retention policies\r\nlog.retention.check.interval.ms=300000\r\n\r\n############################# Zookeeper #############################\r\n\r\n# Zookeeper connection string (see zookeeper docs for details).\r\n# This is a comma separated host:port pairs, each corresponding to a zk\r\n# server. e.g. \"127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002\".\r\n# You can also append an optional chroot string to the urls to specify the\r\n# root directory for all kafka znodes.\r\nzookeeper.connect=localhost:2181\r\n\r\n# Timeout in ms for connecting to zookeeper\r\nzookeeper.connection.timeout.ms=6000\r\n\r\n\r\n############################# Group Coordinator Settings #############################\r\n\r\n# The following configuration specifies the time, in milliseconds, that the GroupCoordinator will delay the initial consumer rebalance.\r\n# The rebalance will be further delayed by the value of group.initial.rebalance.delay.ms as new members join the group, up to a maximum of max.poll.interval.ms.\r\n# The default value for this is 3 seconds.\r\n# We override this to 0 here as it makes for a better out-of-the-box experience for development and testing.\r\n# However, in production environments the default value of 3 seconds is more suitable as this will help to avoid unnecessary, and potentially expensive, rebalances during application startup.\r\ngroup.initial.rebalance.delay.ms=0\r\n```\r\n\r\n- advertised.listeners and listeners 两个配置文件的区别：[kafka - advertised.listeners and listeners - fxjwind - 博客园](https://www.cnblogs.com/fxjwind/p/6225909.html?utm_source=tuicool&utm_medium=referral)\r\n\r\n\r\n\r\n## 参考资料\r\n\r\n- [CentOS7 下 Kafka 的安装介绍 - 个人文章 - SegmentFault 思否](https://segmentfault.com/a/1190000012990954)\r\n\r\n- 非常重要：[kafka 踩坑之消费者收不到消息 - kris - CSDN 博客](https://blog.csdn.net/qq_25868207/article/details/81516024)\r\n\r\n- [kafka 安装搭建(整合 springBoot 使用) - u010391342 的博客 - CSDN 博客](https://blog.csdn.net/u010391342/article/details/81430402)\r\n\r\n- [Zookeeper+Kafka 的单节点配置 - 紫轩弦月 - 博客园](https://www.cnblogs.com/ALittleMoreLove/archive/2018/07/31/9396745.html)\r\n\r\n- [@KafkaListener 注解解密 - laomei - CSDN 博客](https://blog.csdn.net/sweatOtt/article/details/86714272)\r\n\r\n- [使用Docker快速搭建Kafka开发环境 - 简书](https://www.jianshu.com/p/ac03f126980e)\r\n\r\n  \r\n"
  },
  {
    "path": "notes/MicroService/kafka/kafka-tutorial-3_集群化部署.md",
    "content": "# 深入浅出 Kafka（三）集群化部署\r\n\r\n"
  },
  {
    "path": "notes/MicroService/kafka/kafka-tutorial-4_架构深入.md",
    "content": "<!-- TOC -->\r\n\r\n- [深入浅出 Kafka（四）架构深入](#深入浅出-kafka四架构深入)\r\n    - [一、Kafka 工作流程及文件存储机制](#一kafka-工作流程及文件存储机制)\r\n    - [二、Kafka 生产者](#二kafka-生产者)\r\n        - [1. 分区策略](#1-分区策略)\r\n            - [（1）分区的原因](#1分区的原因)\r\n            - [（2）分区的原则](#2分区的原则)\r\n        - [2. 数据可靠性保证](#2-数据可靠性保证)\r\n            - [（1）副本数据同步策略](#1副本数据同步策略)\r\n            - [（2）ISR](#2isr)\r\n            - [（3）ack 应答机制](#3ack-应答机制)\r\n            - [（4）ack 参数设置(asks)](#4ack-参数设置asks)\r\n            - [（4）数据一致性问题（故障处理）](#4数据一致性问题故障处理)\r\n        - [3. Exactly Once 语义](#3-exactly-once-语义)\r\n    - [三、Kafka 消费者](#三kafka-消费者)\r\n        - [1. 消费方式](#1-消费方式)\r\n        - [2. 分区分配策略](#2-分区分配策略)\r\n        - [3. offset 的维护](#3-offset-的维护)\r\n    - [四、Kafka 高效读写数据](#四kafka-高效读写数据)\r\n        - [1. 顺序写磁盘](#1-顺序写磁盘)\r\n        - [2. 零拷贝技术](#2-零拷贝技术)\r\n    - [五、Zookeeper 在 Kafka 中的作用](#五zookeeper-在-kafka-中的作用)\r\n    - [六、Kafka 事务](#六kafka-事务)\r\n        - [1. Producer事务事务](#1-producer事务事务)\r\n        - [2. Consumer **事务**](#2-consumer-事务)\r\n\r\n<!-- /TOC -->\r\n\r\n# 深入浅出 Kafka（四）架构深入\r\n\r\n## 一、Kafka 工作流程及文件存储机制\r\n\r\n![](assets/kafka-work.png)\r\n\r\n\r\n\r\n　　Kafka 中消息是以 topic 进行分类的，生产者生产消息，消费者消费消息，都是面向 topic 的。\r\n\r\n　　topic 是逻辑上的概念，而 partition 是物理上的概念，每个 partition 对应于一个 log 文件，该 log 文件中存储的就是 producer 生产的数据。Producer 生产的数据会被不断追加到该 log 文件末端，且每条数据都有自己的 offset。消费者组中的每个消费者，都会实时记录自己消费到了哪个 offset，以便出错恢复时，从上次的位置继续消费。\r\n\r\n![](assets/kafka-index.png)   \r\n\r\n　　由于生产者生产的消息会不断追加到 log 文件末尾，为防止 log 文件过大导致数据定位效率低下，Kafka 采取了**分片**和**索引**机制，将每个 partition 分为多个 segment。每个 segment 对应两个文件——“.index”文件和 “.log” 文件。这些文件位于一个文件夹下，该文件夹的命名规则为：topic 名称 + 分区序号。例如，first 这个 topic 有三个分区，则其对应的文件夹为 first-0,first-1,first-2。\r\n\r\n```\r\n00000000000000000000.index\r\n00000000000000000000.log\r\n00000000000000170410.index\r\n00000000000000170410.log\r\n00000000000000239430.index\r\n00000000000000239430.log\r\n```\r\n\r\n　　index 和 log 文件以当前 segment 的第一条消息的 offset 命名。\r\n\r\n![](assets/kafka-index2.png)\r\n\r\n　　**“.index”文件存储大量的索引信息，“.log”文件存储大量的数据**，**索引文件中的元数据指向对应数据文件中**message 的物理偏移地址。\r\n\r\n\r\n\r\n## 二、Kafka 生产者\r\n\r\n### 1. 分区策略\r\n\r\n#### （1）分区的原因\r\n\r\n- **方便在集群中扩展**，每个 Partition 可以通过调整以适应他所在的机器，而一个 topic 可以有多个 Partition 组成，因此这个集群就可以适应任意大小的数据了；\r\n- **可以提高并发**，因为可以以 Partition 为单位读写了。\r\n\r\n\r\n\r\n#### （2）分区的原则\r\n\r\n- 我们将 producer 发送的数据封装成一个 ProducerRecord 对象。\r\n\r\n![](assets/kafka-partition.png)\r\n\r\n1. 指明 partition 的情况下，直接将指明的值直接作为 partition 值；\r\n2. 没有指明 partition 值但有 key 的情况下，将 key 的 hash 值与 topic 的 partition 数进行取余得到 partition 值；\r\n3. 既没有 partition 值有没有 key 值的情况下，第一次调用时随机生成一个整数(后面调用在这个整数上自增)，将这个值的 topic 可用的 partition 总数取余得到 partition 值，也就是常说的 Round Robin（轮询调度）算法。\r\n\r\n\r\n\r\n### 2. 数据可靠性保证\r\n\r\n　　为保证 producer 发送的数据，能可靠的发送到指定的 topic，topic 的每个 partition 收到 producer 发送的数据后，都需要向 producer 发送 ack（acknowledgement 确认收到），如果 producer 收到 ack，就会进行下一轮的发送，否则重新发送数据。\r\n\r\n![](assets/kafka-ack.png)\r\n\r\n#### （1）副本数据同步策略\r\n\r\n|**方案**                       |**优点**                                          |**缺点**                                           |\r\n| ------------------------------- | -------------------------------------------------- | --------------------------------------------------- |\r\n|**半数以上完成同步，就发送 ack**| 延迟低                                             | 选举新的 leader 时，容忍 n 台节点的故障，需要 2n+1 个副本 |\r\n|**全部完成同步，才发送 ack**    | 选举新的 leader 时，容忍 n 台节点的故障，需要 n+1 个副本 | 延迟高                                              |\r\n\r\nKafka 选择了第二种方案，原因如下：\r\n1. 同样为了容忍 n 台节点的故障，第一种方案需要 2n+1 个副本，而第二种方案只需要 n+1 个副本，而 Kafka 的每个分区都有大量的数据，第一种方案会造成大量数据的冗余。\r\n2. 虽然第二种方案的网络延迟会比较高，但网络延迟对 Kafka 的影响较小（同一网络环境下的传输）。\r\n\r\n\r\n\r\n#### （2）ISR\r\n\r\n　　采用第二种方案之后，设想以下情景：leader 收到数据，所有 follower 都开始同步数据，但有一个 follower，因为某种故障，迟迟不能与 leader 进行同步，那 leader 就要一直等下去，直到它完成同步，才能发送 ack。这个问题怎么解决呢？\r\n\r\n　　Leader 维护了一个动态的 **in-sync replica set** (ISR)，意为和 leader 保持同步的 follower 集合。当 ISR 中的 follower 完成数据的同步之后，leader 就会给 producer 发送 ack。如果 follower 长时间未向 leader 同步数据，则该 follower 将被踢出 ISR，该时间阈值由 **replica.lag.time.max.ms** 参数设定。Leader 发生故障之后，就会从 ISR 中选举新的 leader。\r\n\r\n\r\n\r\n#### （3）ack 应答机制\r\n\r\n　　对于某些不太重要的数据，对数据的可靠性要求不是很高，能够容忍数据的少量丢失，所以没必要等 ISR 中的 follower 全部接收成功。\r\n\r\n　　所以 Kafka 为用户提供了三种可靠性级别，用户根据对可靠性和延迟的要求进行权衡，选择以下的配置。\r\n\r\n\r\n\r\n#### （4）ack 参数设置(asks)\r\n\r\n- 0：producer 不等待 broker 的 ack，这一操作提供了一个最低的延迟，broker 一接收到还没有写入磁盘就已经返回，当 broker 故障时有可能**丢失数据**。\r\n- 1：producer 等待 broker 的 ack，partition 的 leader 落盘成功后返回 ack，如果在 follower 同步成功之前 leader 故障，那么就会**丢失数据**。\r\n\r\n![](assets/kafka-acks1.png)\r\n\r\n- -1(all)：producer 等待 broker 的 ack，partition 的 leader 和 follower（是 ISR 中的） 全部落盘成功后才返回 ack，但是如果 follower 同步完成后，broker 发送 ack 之前，leader 发生故障，producer 重新发送消息给新 leader 那么会造成**数据重复**。\r\n\r\n![](assets/kafka-acks-1.png)\r\n\r\n#### （4）数据一致性问题（故障处理）\r\n\r\n![](assets/kafka-failover.png)\r\n\r\n- **follower 故障**\r\n  follower 发生故障后会被临时踢出 ISR，待该 follower 恢复后，follower 会读取本地磁盘记录的上次的 HW，并将 log 文件高于 HW 的部分截取掉，从 HW 开始向 leader 进行同步。等该 **follower 的 LEO 大于等于该 Partition 的 HW**，即 follower 追上 leader 之后，就可以重新加入 ISR 了。\r\n\r\n- **leader 故障**\r\n  leader 发生故障之后，会从 ISR 中选出一个新的 leader，之后，为保证多个副本之间的数据一致性，其余的 follower 会先将各自的 log 文件高于 HW 的部分截掉，然后从新的 leader 同步数据。\r\n\r\n**== 注意：这只能保证副本之间的数据一致性，并不能保证数据不丢失或者不重复。==**\r\n\r\n**注意**：这只能保证副本之间的数据一致性，并不能保证数据不丢失或者不重复。\r\n\r\n\r\n\r\n### 3. Exactly Once 语义\r\n\r\n　　将服务器的 ACK 级别设置为 -1，可以保证 Producer 到 Server 之间不会丢失数据，即 At Least Once 语义。相对的，将服务器 ACK 级别设置为 0，可以保证生产者每条消息只会被 发送一次，即 At Most Once 语义。 \r\n\r\n　　**At Least Once 可以保证数据不丢失，但是不能保证数据不重复**；相对的，At Least Once 可以保证数据不重复，但是不能保证数据不丢失。但是，对于一些非常重要的信息，比如说交易数据，下游数据消费者要求数据既不重复也不丢失，即 Exactly Once 语义。在 0.11 版本以前的 Kafka，对此是无能为力的，只能保证数据不丢失，再在下游消费者对数据做全局去重。对于多个下游应用的情况，每个都需要单独做全局去重，这就对性能造成了很大影响。 \r\n\r\n　　0.11 版本的 Kafka，引入了一项重大特性：幂等性。所谓的幂等性就是指 Producer 不论向 Server 发送多少次重复数据，Server 端都只会持久化一条。幂等性结合 At Least Once 语义，就构成了 Kafka 的 Exactly Once 语义。即： \r\n\r\n<center>At Least Once + 幂等性 = Exactly Once</center>\r\n　　要启用幂等性，只需要将 Producer 的参数中 enable.idompotence 设置为 true 即可。Kafka 的幂等性实现其实就是将原来下游需要做的去重放在了数据上游。开启幂等性的 Producer 在初始化的时候会被分配一个 PID，发往同一 Partition 的消息会附带 Sequence Number。而 Broker 端会对 <PID, Partition, SeqNumber> 做缓存，当具有相同主键的消息提交时，Broker 只会持久化一条。 \r\n\r\n　　但是 PID 重启就会变化，同时不同的 Partition 也具有不同主键，**所以幂等性无法保证跨分区跨会话**的 Exactly Once。\r\n\r\n\r\n\r\n## 三、Kafka 消费者\r\n\r\n### 1. 消费方式\r\n\r\n　　**consumer 采用 pull（拉）模式从 broker 中读取数据。**\r\n\r\n　　**push（推）模式很难适应消费速率不同的消费者，因为消息发送速率是由 broker 决定的。**它的目标是尽可能以最快速度传递消息，但是这样很容易造成 consumer 来不及处理消息，典型的表现就是拒绝服务以及网络拥塞。而 pull 模式则可以根据 consumer 的消费能力以适当的速率消费消息。\r\n\r\n　　**pull 模式不足之处是，如果 kafka 没有数据，消费者可能会陷入循环中，一直返回空数据。**针对这一点，Kafka 的消费者在消费数据时会传入一个时长参数 timeout，如果当前没有数据可供消费，consumer 会等待一段时间之后再返回，这段时长即为 timeout。\r\n\r\n\r\n\r\n### 2. 分区分配策略\r\n\r\n　　一个 consumer group 中有多个 consumer，一个 topic 有多个 partition，所以必然会涉及到 partition 的分配问题，即确定那个 partition 由哪个 consumer 来消费。\r\n\r\n　　Kafka 有两种分配策略，一是 **RoundRobin**，一是 **range**。\r\n\r\n![1567511408405](assets/1567511408405.png)\r\n\r\n\r\n\r\n　　**roundrobin**根据 partition 号对 consumer 个数取模后轮循分配\r\n\r\n![1567511461051](assets/1567511461051.png)\r\n\r\n \r\n\r\n　　**range**提前按照均匀分配的原则计算个数后直接分配。\r\n\r\n![1567511487508](assets/1567511487508.png)\r\n\r\n　　在订阅多个 partition 时 range 会有**不均匀**问题，kafka 默认为 range，因为不考虑多 partition 订阅时，range 效率更高。\r\n\r\n\r\n\r\n### 3. offset 的维护\r\n\r\n　　由于 consumer 在消费过程中可能会出现断电宕机等故障，consumer 恢复后，需要从故障前的位置的继续消费，所以 consumer 需要实时记录自己消费到了哪个 offset，以便故障恢复后继续消费。\r\n\r\n　　group + topic + partition（GTP） 才能确定一个 offset！\r\n\r\n![1567511669385](assets/1567511669385.png)\r\n\r\n　　Kafka 0.9 版本之前，consumer 默认将 offset 保存在 Zookeeper 中，从 0.9 版本开始，consumer 默认将 offset 保存在 Kafka 一个内置的 topic 中，该 topic 为 `__consumer_offsets`（此时消费者对于 offset 相当于生产者）。\r\n\r\n1）修改配置文件 consumer.properties\r\n\r\n```properties\r\nexclude.internal.topics=false\r\n```\r\n\r\n2）读取offset\r\n\r\n- 0.11.0.0 之前版本:\r\n\r\n```shell\r\nbin/kafkabin/kafka--consoleconsole--consumer.sh consumer.sh ----topic __consumer_offsets topic __consumer_offsets ----zookeeper zookeeper hadoophadoop102102:2181 :2181 ----formatter formatter\r\n\r\n\"kafka.coordinator.GroupMetadataManager\"kafka.coordinator.GroupMetadataManager\\\\$OffsetsMessageFormatter\" $OffsetsMessageFormatter\" ----consumer.config config/consumer.properties consumer.config config/consumer.properties ----fromfrom--beginningbeginning\r\n```\r\n\r\n- 0.11.0.0 之后版本(含): \r\n\r\n```shell\r\nbin/kafkabin/kafka--consoleconsole--consumer.sh consumer.sh ----topic __consumer_offsets topic __consumer_offsets ----zookeeper zookeeper hadoophadoop102102:2181 :2181 ----formatter formatter\r\n\r\n\"kafka.coordinator.group.GroupMetadataManager\"kafka.coordinator.group.GroupMetadataManager\\\\$OffsetsMessageForm$OffsetsMessageFormatter\" atter\" ----consumer.config config/consumer.propertiesconsumer.config config/consumer.properties ----fromfrom--beginningbeginning\r\n```\r\n\r\n　　同一个消费者组中的消费者， 同一时刻只能有一个消费者消费。\r\n\r\n\r\n\r\n## 四、Kafka 高效读写数据\r\n\r\n### 1. 顺序写磁盘\r\n\r\n　　Kafka 的 producer 生产数据，要写入到 log 文件中，写的过程是一直追加到文件末端，为顺序写。官网有数据表明，同样的磁盘，顺序写能到到 600M/s，而随机写只有 100k/s。这与磁盘的机械机构有关，顺序写之所以快，是因为其省去了大量磁头寻址的时间。\r\n\r\n\r\n\r\n### 2. 零拷贝技术\r\n\r\n　　零拷贝主要的任务就是避免 CPU 将数据从一块存储拷贝到另外一块存储，主要就是利用各种零拷贝技术，避免让 CPU 做大量的数据拷贝任务，减少不必要的拷贝，或者让别的组件来做这一类简单的数据传输任务，让 CPU 解脱出来专注于别的任务。这样就可以让系统资源的利用更加有效。\r\n\r\n　　更详细，请参考：[浅析Linux中的零拷贝技术 - 简书](https://www.jianshu.com/p/fad3339e3448)\r\n\r\n![1567513386020](assets/1567513386020.png)\r\n\r\n![1567513364566](assets/1567513364566.png)\r\n\r\n\r\n\r\n## 五、Zookeeper 在 Kafka 中的作用\r\n\r\n　　Kafka 集群中有一个 broker 会被选举为 Controller，负责管理集群 broker 的上下线，所有 topic 的分区副本分配和 leader 选举等工作。\r\n\r\n　　Controller 的管理工作都是依赖于 Zookeeper 的。\r\n\r\n　　以下为 partition 的 leader 选举过程：\r\n\r\n![](assets/kafka-zk.png)\r\n\r\n\r\n\r\n## 六、Kafka 事务\r\n\r\n　　Kafka 从0.11 版本开始引入了事务支持。事务可以保证 Kafka 在 Exactly Once 语义的基础上，生产和消费可以跨分区和会话，要么全部成功，要么全部失败。\r\n\r\n　　**注意**：这里的事务主要谈的是生产者（Producer）的事务\r\n\r\n\r\n\r\n### 1. Producer事务事务\r\n\r\n　　为了实现跨分区跨会话的事务，需要引入一个全局唯一的 Transaction ID（**一定是客户端给的**），并将 Producer 获得的 PID 和 Transaction ID 绑定。这样当 Producer 重启后就可以通过正在进行的 Transaction ID 获得原来的 PID。 \r\n\r\n　　为了管理 Transaction，Kafka 引入了一个新的组件 Transaction Coordinator。Producer 就是通过和 Transaction Coordinator 交互获得 Transaction ID 对应的任务状态。Transaction Coordinator 还负责将事务所有写入 Kafka 的一个内部 Topic，这样即使整个服务重启，由于事务状态得到保存，进行中的事务状态可以得到恢复，从而继续进行。\r\n\r\n\r\n\r\n### 2. Consumer **事务** \r\n\r\n　　上述事务机制主要是从 Producer 方面考虑，对于 Consumer 而言，事务的保证就会相对较弱，尤其时无法保证 Commit 的信息被精确消费。这是由于 Consumer 可以通过 offset 访问任意信息，而且不同的 Segment File 生命周期不同，同一事务的消息可能会出现重启后被删除的情况。\r\n"
  },
  {
    "path": "notes/MicroService/kafka/kafka-tutorial-5_kafka-api.md",
    "content": "<!-- TOC -->\r\n\r\n- [深入浅出 Kafka（五）Kafka API](#深入浅出-kafka五kafka-api)\r\n    - [一、Producer API](#一producer-api)\r\n        - [1. 消息发送流程](#1-消息发送流程)\r\n        - [2. 异步发送 API](#2-异步发送-api)\r\n            - [（1）不带回调函数的异步（AsyncProducer）](#1不带回调函数的异步asyncproducer)\r\n            - [（2）带回调函数的异步（CallbackProducer）](#2带回调函数的异步callbackproducer)\r\n        - [3. 同步发送 API](#3-同步发送-api)\r\n            - [（1）同步发送（SyncProducer）](#1同步发送syncproducer)\r\n    - [二、Consumer API](#二consumer-api)\r\n        - [1. 自动提交 offset](#1-自动提交-offset)\r\n        - [2. 手动提交 offset](#2-手动提交-offset)\r\n            - [（1）同步提交 commitSync offset](#1同步提交-commitsync-offset)\r\n            - [（2）异步提交 commitAsync offset](#2异步提交-commitasync-offset)\r\n            - [（3）数据漏消费和重复消费分析](#3数据漏消费和重复消费分析)\r\n        - [3. 自定义存储 offset](#3-自定义存储-offset)\r\n    - [三、自定义 Interceptor](#三自定义-interceptor)\r\n        - [1. 拦截器原理](#1-拦截器原理)\r\n        - [2. 拦截器案例](#2-拦截器案例)\r\n\r\n<!-- /TOC -->\r\n\r\n# 深入浅出 Kafka（五）Kafka API\r\n\r\n## 一、Producer API\r\n\r\n### 1. 消息发送流程\r\n\r\n　　Kafka 的 Producer 发送消息采用的是**异步发送**的方式。\r\n\r\n　　在消息发送的过程中，涉及到了两个线程 —— main 线程和 Sender 线程，以及一个线程共享变量——RecordAccumulator（接收器）。\r\n\r\n　　main 线程将消息发送给 RecordAccumulator，Sender 线程不断从 RecordAccumulator 中拉取消息发送到 Kafka broker。\r\n\r\n![](assets/kafka-produce.png)\r\n\r\n相关参数：\r\n- **batch.size**：只有数据积累到 batch.size 之后，sender 才会发送数据。\r\n- **linger.ms**：如果数据迟迟未达到 batch.size，sender 等待 linger.time 之后就会发送数据。\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n### 2. 异步发送 API\r\n\r\n- **KafkaProducer** 需要创建一个生产者对象，用来发送数据\r\n- **ProducerConfig** 获取所需的一系列配置参数\r\n- **ProducerRecord** 每条数据都要封装成一个 ProducerRecord 对象\r\n\r\n```xml\r\n<dependency>\r\n    <groupId>org.apache.kafka</groupId>\r\n    <artifactId>kafka-clients</artifactId>\r\n    <version>0.11.0.0</version>\r\n</dependency>\r\n```\r\n\r\n#### （1）不带回调函数的异步（AsyncProducer）\r\n\r\n```java\r\npackage com.tian.kafka.producer;\r\n\r\nimport org.apache.kafka.clients.producer.KafkaProducer;\r\nimport org.apache.kafka.clients.producer.ProducerConfig;\r\nimport org.apache.kafka.clients.producer.ProducerRecord;\r\nimport org.apache.kafka.common.serialization.StringSerializer;\r\n\r\nimport java.util.Properties;\r\n\r\n/**\r\n * 不带回调函数的异步 Producer API\r\n */\r\npublic class AsyncProducer {\r\n    public static void main(String[] args) {\r\n        Properties props = new Properties();\r\n        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,\r\n                \"hadoop101:9092,hadoop102:9092,hadoop103:9092\");\r\n        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,\r\n                StringSerializer.class.getName());\r\n        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,\r\n                StringSerializer.class.getName());\r\n        props.put(ProducerConfig.ACKS_CONFIG, \"all\");\r\n        props.put(ProducerConfig.RETRIES_CONFIG, 1);\r\n        props.put(ProducerConfig.LINGER_MS_CONFIG, 1);\r\n        // 配置拦截器\r\n\r\n        // 通过配置创建 KafkaProducer 对象\r\n        KafkaProducer<String, String> producer = new KafkaProducer<>(props);\r\n        for (int i = 0; i < 1000; i++) {\r\n            ProducerRecord<String, String> record = new ProducerRecord<>(\"first\", \"message\" + i);\r\n            producer.send(record);\r\n        }\r\n        producer.close();\r\n    }\r\n}\r\n```\r\n\r\n#### （2）带回调函数的异步（CallbackProducer）\r\n\r\n```java\r\npackage com.tian.kafka.producer;\r\n\r\nimport org.apache.kafka.clients.producer.*;\r\nimport org.apache.kafka.common.serialization.StringSerializer;\r\n\r\nimport java.util.Properties;\r\n\r\n/**\r\n * 带回调函数的异步Producer API\r\n */\r\npublic class CallbackProducer {\r\n    public static void main(String[] args) {\r\n        Properties props = new Properties();\r\n        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,\r\n                \"192.168.72.133:9092\");\r\n        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,\r\n                StringSerializer.class.getName());\r\n        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,\r\n                StringSerializer.class.getName());\r\n        props.put(ProducerConfig.ACKS_CONFIG, \"all\");\r\n        props.put(ProducerConfig.RETRIES_CONFIG, 1);\r\n        KafkaProducer<String, String> producer = new KafkaProducer<>(props);\r\n        for (int i = 0; i < 1000; i++) {\r\n            ProducerRecord<String, String> record = new ProducerRecord<>(\"first\", \"message\" + i);\r\n            producer.send(record, new Callback() {\r\n                @Override\r\n                public void onCompletion(RecordMetadata recordMetadata, Exception e) {\r\n                    if (e == null)\r\n                        System.out.println(\"success:\" + recordMetadata.topic() +\r\n                                \"-\" + recordMetadata.partition() +\r\n                                \"-\" + recordMetadata.offset());\r\n                    else e.printStackTrace();\r\n                }\r\n            });\r\n\r\n        }\r\n        producer.close();\r\n    }\r\n}\r\n```\r\n\r\n\r\n\r\n### 3. 同步发送 API\r\n\r\n#### （1）同步发送（SyncProducer）\r\n\r\n```java\r\npackage com.tian.kafka.producer;\r\n\r\nimport org.apache.kafka.clients.producer.KafkaProducer;\r\nimport org.apache.kafka.clients.producer.ProducerConfig;\r\nimport org.apache.kafka.clients.producer.ProducerRecord;\r\nimport org.apache.kafka.clients.producer.RecordMetadata;\r\nimport org.apache.kafka.common.serialization.StringSerializer;\r\n\r\nimport java.util.Properties;\r\nimport java.util.concurrent.ExecutionException;\r\n\r\n/**\r\n * 同步 Producer API\r\n */\r\npublic class SyncProducer {\r\n    public static void main(String[] args) throws ExecutionException, InterruptedException {\r\n        // 创建 properties 对象用于存放配置\r\n        Properties props = new Properties();\r\n        // 添加配置\r\n        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, \"hadoop101:9092,hadoop102:9092,hadoop103:9092\");\r\n        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());\r\n        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());\r\n        props.put(ProducerConfig.ACKS_CONFIG, \"all\");\r\n        props.put(ProducerConfig.RETRIES_CONFIG, 1); // 重试次数\r\n        props.put(ProducerConfig.LINGER_MS_CONFIG, 500);\r\n        // 通过已有配置创建 kafkaProducer 对象\r\n        KafkaProducer<String, String> producer = new KafkaProducer<>(props);\r\n        // 循环调用 send 方法不断发送数据\r\n        for (int i = 0; i < 100; i++) {\r\n            ProducerRecord<String, String> record = new ProducerRecord<>(\"first\", \"message\" + i);\r\n            RecordMetadata metadata = producer.send(record).get();// 通过 get()方法实现同步效果\r\n            if (metadata != null)\r\n                System.out.println(\"success:\" + metadata.topic() + \"-\" +\r\n                        metadata.partition() + \"-\" + metadata.offset());\r\n        }\r\n        producer.close(); // 关闭生产者对象\r\n    }\r\n}\r\n```\r\n\r\n\r\n\r\n## 二、Consumer API\r\n\r\n　　Consumer 消费数据时的可靠性是很容易保证的，因为数据在 Kafka 中是持久化的，故不用担心数据丢失问题。\r\n　　由于 consumer 在消费过程中可能会出现断电宕机等故障，consumer 恢复后，需要从故障前的位置的继续消费，所以 consumer 需要实时记录自己消费到了哪个 offset，以便故障恢复后继续消费。\r\n　　所以 offset 的维护是 Consumer 消费数据是必须考虑的问题。\r\n\r\n### 1. 自动提交 offset\r\n\r\n```xml\r\n<dependency>\r\n    <groupId>org.apache.kafka</groupId>\r\n    <artifactId>kafka-clients</artifactId>\r\n    <version>0.11.0.0</version>\r\n</dependency>\r\n```\r\n\r\n- **KafkaConsumer**：需要创建一个消费者对象，用来消费数据\r\n- **ConsumerConfig**：获取所需的一系列配置参数\r\n- **ConsuemrRecord**：每条数据都要封装成一个 ConsumerRecord 对象\r\n\r\n为了使我们能够专注于自己的业务逻辑，Kafka 提供了自动提交 offset 的功能。\r\n\r\n自动提交 offset 的相关参数：\r\n\r\n- **enable.auto.commit**：是否开启自动提交 offset 功能\r\n- **auto.commit.interval.ms**：自动提交 offset 的时间间隔\r\n\r\n```java\r\npackage com.tian.kafka.consumer;\r\n\r\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\r\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\r\nimport org.apache.kafka.clients.consumer.ConsumerRecords;\r\nimport org.apache.kafka.clients.consumer.KafkaConsumer;\r\nimport org.apache.kafka.common.serialization.StringDeserializer;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.Properties;\r\n\r\n/**\r\n * 自动提交 offset\r\n */\r\npublic class AutoCommitOffset {\r\n    public static void main(String[] args) {\r\n        Properties props = new Properties();\r\n        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,\r\n                \"hadoop101:9092,hadoop102:9092\");\r\n        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,\r\n                StringDeserializer.class.getName());\r\n        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,\r\n                StringDeserializer.class.getName());\r\n        props.put(ConsumerConfig.GROUP_ID_CONFIG,\"tian\"); // groupid\r\n        props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,\"earliest\");\r\n        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,true); // 自动提交\r\n        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);\r\n        consumer.subscribe(Arrays.asList(\"first\")); // 添加需要消费的 topic\r\n        try {\r\n            while(true){\r\n                ConsumerRecords<String, String> records = consumer.poll(100);\r\n                for (ConsumerRecord<String, String> record : records) {\r\n                    System.out.println(record.value());\r\n                }\r\n            }\r\n        } finally {\r\n            consumer.close();// 在死循环中无法调用 close 方法，所以需要使用 finally\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n\r\n\r\n### 2. 手动提交 offset\r\n\r\n　　虽然自动提交 offset 十分简介便利，但由于其是基于时间提交的，开发人员难以把握 offset 提交的时机。因此 Kafka 还提供了手动提交 offset 的 API。\r\n　　手动提交 offset 的方法有两种: 分别是 **commitSync**（**同步提交**）和 **commitAsync**（**异步提交**）。两者的相同点是，都会将本次 poll 的一批数据最高的偏移量提交; 不同点是，commitSync 阻塞当前线程，一直到提交成功，并且会自动失败充实（由不可控因素导致，也会出现提交失败）; 而 commitAsync 则没有失败重试机制，故有可能提交失败。\r\n\r\n#### （1）同步提交 commitSync offset\r\n\r\n　　由于同步提交 offset 有失败重试机制，故更加可靠，以下为同步提交 offset 的示例\r\n\r\n```java\r\npackage com.tian.kafka.consumer;\r\n\r\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\r\nimport org.apache.kafka.clients.consumer.ConsumerRecords;\r\nimport org.apache.kafka.clients.consumer.KafkaConsumer;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.Properties;\r\n\r\n/**\r\n * 同步手动提交 offset\r\n */\r\npublic class CustomComsumer {\r\n\r\n    public static void main(String[] args) {\r\n\r\n        Properties props = new Properties();\r\n        props.put(\"bootstrap.servers\", \"hadoop102:9092\");//Kafka 集群\r\n        props.put(\"group.id\", \"test\");// 消费者组，只要 group.id 相同，就属于同一个消费者组\r\n        props.put(\"enable.auto.commit\", \"false\");// 关闭自动提交 offset\r\n        props.put(\"key.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\r\n        props.put(\"value.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\r\n\r\n        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);\r\n        consumer.subscribe(Arrays.asList(\"first\"));// 消费者订阅主题\r\n\r\n        while (true) {\r\n            ConsumerRecords<String, String> records = consumer.poll(100);// 消费者拉取数据\r\n            for (ConsumerRecord<String, String> record : records) {\r\n                System.out.printf(\"offset = %d, key = %s, value = %s%n\", record.offset(), record.key(), record.value());\r\n            }\r\n            consumer.commitSync();// 同步提交，当前线程会阻塞直到 offset 提交成功\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n\r\n\r\n#### （2）异步提交 commitAsync offset\r\n\r\n　　虽然同步提交 offset 更可靠一些，但是由于其会阻塞当前线程，直到提交成功。因此吞吐量会收到很大的影响，因此更多的情况下，会选用异步提交 offset 的方式。\r\n\r\n```java\r\npackage com.tian.kafka.consumer;\r\n\r\nimport org.apache.kafka.clients.consumer.*;\r\nimport org.apache.kafka.common.TopicPartition;\r\nimport org.apache.kafka.common.serialization.StringDeserializer;\r\n\r\nimport java.util.Arrays;\r\nimport java.util.Map;\r\nimport java.util.Properties;\r\n\r\n/**\r\n * 异步手动提交 offset\r\n */\r\npublic class AsyncManualCommitOffset {\r\n    public static void main(String[] args) {\r\n        Properties props = new Properties();\r\n        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, \"hadoop101:9092\");\r\n        props.put(ConsumerConfig.GROUP_ID_CONFIG, \"tian\");\r\n        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);\r\n        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG,\r\n                StringDeserializer.class.getName());\r\n        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,\r\n                StringDeserializer.class.getName());\r\n        KafkaConsumer<String, String> consumer = new KafkaConsumer<String,String>(props);\r\n        consumer.subscribe(Arrays.asList(\"first\"));\r\n        while (true) {\r\n            ConsumerRecords<String, String> records = consumer.poll(100);\r\n            for (ConsumerRecord<String, String> record : records) {\r\n                System.out.println(\"offset:\" + record.offset() +\r\n                        \"key:\" + record.key() + \"value\" + record.value());\r\n            }\r\n            consumer.commitAsync(new OffsetCommitCallback() {\r\n                public void onComplete(Map<TopicPartition, OffsetAndMetadata> map, Exception e) {\r\n                    if (e != null)\r\n                        System.out.println(\"commit failed for\" + map);\r\n                }\r\n            });// 异步提交\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n\r\n\r\n#### （3）数据漏消费和重复消费分析 \r\n\r\n　　无论是同步提交还是异步提交 offset，都有可能会造成数据的漏消费或者重复消费。先提交 offset 后消费，有可能造成数据的漏消费；而先消费后提交 offset，有可能会造成数据的重复消费。\r\n\r\n![](assets/kafka-offset.png)\r\n\r\n\r\n\r\n### 3. 自定义存储 offset\r\n\r\n　　Kafka 0.9 版本之前，offset 存储在 zookeeper，0.9 版本之后，默认将 offset 存储在 Kafka 的一个内置的 topic 中。除此之外，Kafka 还可以选择自定义存储 offset。\r\n　　offset 的维护是相当繁琐的，因为需要考虑到消费者的 Rebalance。\r\n　　当有新的消费者加入消费者组、已有的消费者推出消费者组或者所订阅的主题的分区发生变化，就会触发到分区的重新分配，重新分配的过程叫做 Rebalance。\r\n　　消费者发生 Rebalance 之后，每个消费者消费的分区就会发生变化。因此消费者要首先获取到自己被重新分配到的分区，并且定位到每个分区最近提交的 offset 位置继续消费。\r\n　　要实现自定义存储 offset，需要借助 ConsumerRebalanceListener，以下为示例代码，其中提交和获取 offset 的方法，需要根据所选的 offset 存储系统自行实现。\r\n\r\n```java\r\npackage com.tian.kafka.consumer;\r\n\r\nimport org.apache.kafka.clients.consumer.*;\r\nimport org.apache.kafka.common.TopicPartition;\r\n\r\nimport java.util.*;\r\n\r\n/**\r\n * 自定义存储 offset\r\n */\r\npublic class CustomConsumer {\r\n\r\n    private static Map<TopicPartition, Long> currentOffset = new HashMap<>();\r\n\r\n    public static void main(String[] args) {\r\n        Properties props = new Properties();\r\n        props.put(\"bootstrap.servers\", \"hadoop102:9092\");//Kafka 集群\r\n        props.put(\"group.id\", \"test\");// 消费者组，只要 group.id 相同，就属于同一个消费者组\r\n        props.put(\"enable.auto.commit\", \"false\");// 关闭自动提交 offset\r\n        props.put(\"key.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\r\n        props.put(\"value.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\r\n\r\n        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);\r\n        // 消费者订阅主题\r\n        consumer.subscribe(Arrays.asList(\"first\"), new ConsumerRebalanceListener() {\r\n            \r\n            // 该方法会在 Rebalance 之前调用\r\n            @Override\r\n            public void onPartitionsRevoked(Collection<TopicPartition> partitions) {\r\n                commitOffset(currentOffset);\r\n            }\r\n\r\n            // 该方法会在 Rebalance 之后调用\r\n            @Override\r\n            public void onPartitionsAssigned(Collection<TopicPartition> partitions) {\r\n                currentOffset.clear();\r\n                for (TopicPartition partition : partitions) {\r\n                    consumer.seek(partition, getOffset(partition));// 定位到最近提交的 offset 位置继续消费\r\n                }\r\n            }\r\n        });\r\n\r\n        while (true) {\r\n            ConsumerRecords<String, String> records = consumer.poll(100);// 消费者拉取数据\r\n            for (ConsumerRecord<String, String> record : records) {\r\n                System.out.printf(\"offset = %d, key = %s, value = %s%n\", record.offset(), record.key(), record.value());\r\n                currentOffset.put(new TopicPartition(record.topic(), record.partition()), record.offset());\r\n            }\r\n            commitOffset(currentOffset); \r\n        }\r\n    }\r\n\r\n    // 获取某分区的最新 offset\r\n    private static long getOffset(TopicPartition partition) {\r\n        return 0;\r\n    }\r\n\r\n    // 提交该消费者所有分区的 offset\r\n    private static void commitOffset(Map<TopicPartition, Long> currentOffset) {\r\n\r\n    }\r\n}\r\n```\r\n\r\n\r\n\r\n## 三、自定义 Interceptor\r\n\r\n### 1. 拦截器原理\r\n\r\n　　Producer 拦截器 (interceptor) 是在 Kafka 0.10 版本被引入的，主要用于实现 clients 端的定制化控制逻辑。\r\n对于 producer 而言，interceptor 使得用户在消息发送前以及 producer 回调逻辑前有机会对消息做一些定制化需求，比如修改消息等。同时，producer 允许用户指定多个 interceptor 按序作用于同一条消息从而形成一个拦截链(interceptor chain)。Intercetpor 的实现接口是 `org.apache.kafka.clients.producer.ProducerInterceptor`，\r\n\r\n其定义的方法包括：\r\n\r\n1. configure (configs)\r\n   获取配置信息和初始化数据时调用。\r\n2. onSend (ProducerRecord)：\r\n   该方法封装进 KafkaProducer.send 方法中，即它运行在用户主线程中。Producer 确保在消息被序列化以及计算分区前调用该方法。用户可以在该方法中对消息做任何操作，但最好保证不要修改消息所属的 topic 和分区，否则会影响目标分区的计算。\r\n3. onAcknowledgement (RecordMetadata, Exception)：\r\n   该方法会在消息从 RecordAccumulator 成功发送到 Kafka Broker 之后，或者在发送过程中失败时调用。并且通常都是在 producer 回调逻辑触发之前。onAcknowledgement 运行在 producer 的 IO 线程中，因此不要在该方法中放入很重的逻辑，否则会拖慢 producer 的消息发送效率。\r\n4. close：\r\n   关闭 interceptor，主要用于执行一些资源清理工作\r\n   如前所述，interceptor 可能被运行在多个线程中，因此在具体实现时用户需要自行确保线程安全。另外倘若指定了多个 interceptor，则 producer 将按照指定顺序调用它们，并仅仅是捕获每个 interceptor 可能抛出的异常记录到错误日志中而非在向上传递。这在使用过程中要特别留意。\r\n\r\n\r\n\r\n### 2. 拦截器案例\r\n\r\n- **需求 **\r\n  实现一个简单的双 interceptor 组成的拦截链。第一个 interceptor 会在消息发送前将时间戳信息加到消息 value 的最前部；第二个 interceptor 会在消息发送后更新成功发送消息数或失败发送消息数。\r\n\r\n![](assets/kafka-interceptor.png)\r\n\r\n```java\r\npackage com.tian.kafka.interceptor;\r\n\r\nimport org.apache.kafka.clients.producer.ProducerInterceptor;\r\nimport org.apache.kafka.clients.producer.ProducerRecord;\r\nimport org.apache.kafka.clients.producer.RecordMetadata;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * 增加时间戳\r\n */\r\npublic class TimeInterceptor implements ProducerInterceptor<String,String> {\r\n    /**\r\n     * 返回一个新的 recorder，把时间戳写入消息体的最前部\r\n     * @param record\r\n     * @return\r\n     */\r\n    @Override\r\n    public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {\r\n        return new ProducerRecord(record.topic(),record.partition(),record.timestamp(),\r\n                record.key(),System.currentTimeMillis() + \",\" + record.value().toString());\r\n    }\r\n\r\n    @Override\r\n    public void onAcknowledgement(RecordMetadata recordMetadata, Exception e) {\r\n\r\n    }\r\n\r\n    @Override\r\n    public void close() {\r\n\r\n    }\r\n\r\n    @Override\r\n    public void configure(Map<String, ?> map) {\r\n\r\n    }\r\n}\r\n```\r\n\r\n```java\r\npackage com.tian.kafka.interceptor;\r\n\r\nimport org.apache.kafka.clients.producer.ProducerInterceptor;\r\nimport org.apache.kafka.clients.producer.ProducerRecord;\r\nimport org.apache.kafka.clients.producer.RecordMetadata;\r\n\r\nimport java.util.Map;\r\n\r\n/**\r\n * 统计发送消息成功和发送消息失败数，\r\n * 并在 producer 关闭时打印这连个计时器\r\n */\r\npublic class CountInterceptor implements ProducerInterceptor<String,String> {\r\n    private int errorCounter = 0;\r\n    private int successCounter = 0;\r\n\r\n    /**\r\n     * 直接返回传入的参量\r\n     * @param producerRecord\r\n     * @return producerRecord\r\n     */\r\n    @Override\r\n    public ProducerRecord<String, String> onSend(ProducerRecord<String, String> producerRecord) {\r\n        return producerRecord;\r\n    }\r\n\r\n    /**\r\n     * 统计成功和失败的次数\r\n     * @param recordMetadata\r\n     * @param e\r\n     */\r\n    @Override\r\n    public void onAcknowledgement(RecordMetadata recordMetadata, Exception e) {\r\n        if(e == null)\r\n            successCounter++;\r\n        else\r\n            errorCounter++;\r\n    }\r\n\r\n    /**\r\n     * 打印结果\r\n     */\r\n    @Override\r\n    public void close() {\r\n        System.out.println(\"Successful sent:\" + successCounter);\r\n        System.out.println(\"Failed sent:\" + errorCounter);\r\n    }\r\n\r\n    @Override\r\n    public void configure(Map<String, ?> map) {\r\n\r\n    }\r\n}\r\n```\r\n\r\n```java\r\npackage com.tian.kafka.interceptor;\r\n\r\nimport org.apache.kafka.clients.producer.KafkaProducer;\r\nimport org.apache.kafka.clients.producer.ProducerConfig;\r\nimport org.apache.kafka.clients.producer.ProducerRecord;\r\nimport org.codehaus.jackson.map.ser.std.StringSerializer;\r\n\r\nimport java.util.ArrayList;\r\nimport java.util.List;\r\nimport java.util.Properties;\r\n\r\n/**\r\n * 主程序\r\n */\r\npublic class InterceptorProducer {\r\n    public static void main(String[] args) {\r\n        // 配置信息\r\n        Properties props = new Properties();\r\n        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,\"hadoop101:9092\");\r\n        props.put(ProducerConfig.ACKS_CONFIG,\"all\");\r\n        props.put(ProducerConfig.RETRIES_CONFIG,0);\r\n        props.put(ProducerConfig.BATCH_SIZE_CONFIG,16384);\r\n        props.put(ProducerConfig.LINGER_MS_CONFIG,1);\r\n        props.put(ProducerConfig.BUFFER_MEMORY_CONFIG,33664432);\r\n        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,\r\n                StringSerializer.class.getName());\r\n        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,\r\n                StringSerializer.class.getName());\r\n        // 构建拦截链\r\n        List<String> interceptors = new ArrayList<String>();\r\n        interceptors.add(\"com.tian.kafka.interceptor.TimeInterceptor\");\r\n        interceptors.add(\"com.tian.kafka.interceptor.CountInterceptor\");\r\n        props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, interceptors);\r\n        String topic = \"first\";\r\n        KafkaProducer<String, String> producer = new KafkaProducer<>(props);\r\n        // 发送消息\r\n        for (int i = 0; i < 100; i++) {\r\n            ProducerRecord<String, String> record = new ProducerRecord<>(topic, \"message\" + 1);\r\n            producer.send(record);\r\n        }\r\n        // 关闭 producer\r\n        producer.close();\r\n    }\r\n}\r\n```\r\n\r\n```bash\r\n# 测试\r\n# 在 kafka 上启动消费者，然后运行客户端 java 程序。\r\nkafka-console-consumer.sh --bootstrap-server hadoop102:9092 --from-beginning --topic first\r\n# 1501904047034,message0\r\n# 1501904047225,message1\r\n# 1501904047230,message2\r\n# 1501904047234,message3\r\n# 1501904047236,message4\r\n# 1501904047240,message5\r\n# 1501904047243,message6\r\n# 1501904047246,message7\r\n# 1501904047249,message8\r\n# 1501904047252,message9\r\n```\r\n"
  },
  {
    "path": "notes/MicroService/kafka/kafka-tutorial-6_spring-kafka-api.md",
    "content": "<!-- TOC -->\r\n\r\n- [深入浅出 Kafka（六）Spring Kafka API](#深入浅出-kafka六spring-kafka-api)\r\n    - [一、Producer](#一producer)\r\n        - [1. KafkaProducer](#1-kafkaproducer)\r\n        - [2. KafkaProducerConfig](#2-kafkaproducerconfig)\r\n    - [二、Consumer](#二consumer)\r\n        - [3. KafkaConsumer](#3-kafkaconsumer)\r\n        - [4. KafkaConsumerConfig](#4-kafkaconsumerconfig)\r\n    - [三、配置文件](#三配置文件)\r\n    - [四、pom 依赖](#四pom-依赖)\r\n    - [参考资料](#参考资料)\r\n\r\n<!-- /TOC -->\r\n\r\n# 深入浅出 Kafka（六）Spring Kafka API\r\n\r\n　　在上一节中学习了如何通过 Kafka API 的方式进行生产者和消费者及其配置，但是主要是通过手动编写 Java 代码的方式实现。在项目开发中，我们主要会使用到 SpringBoot 框架，这里就将带你 SpringBoot 与 Kafka 整合，通过注解和配置的方式轻松集成。\r\n\r\n　　这里将列举，最常见的生产者和消费者使用方式，更完整的 API 文档，请转向 [Overview (Spring Kafka 2.2.8.RELEASE API)](https://docs.spring.io/spring-kafka/api/) 学习。\r\n\r\n　　送上本节 DEMO 项目代码，[点击预览](https://github.com/frank-lam/open-code-lab/tree/master/springboot/springboot-kafka-sample)。\r\n\r\n\r\n\r\n## 一、Producer\r\n\r\n### 1. KafkaProducer\r\n\r\n```java\r\npackage cn.frankfeekr.springbootkafkasample.client;\r\n\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.springframework.beans.factory.annotation.Autowired;\r\nimport org.springframework.beans.factory.annotation.Value;\r\nimport org.springframework.kafka.core.KafkaTemplate;\r\nimport org.springframework.web.bind.annotation.GetMapping;\r\nimport org.springframework.web.bind.annotation.RequestParam;\r\nimport org.springframework.web.bind.annotation.RestController;\r\n\r\n\r\n@RestController\r\npublic class KafkaProducer {\r\n\r\n    private static final Logger LOG = LoggerFactory.getLogger(KafkaProducer.class);\r\n\r\n    @Autowired\r\n    private KafkaTemplate<String, String> kafkaTemplate;\r\n\r\n    @Value(\"${app.topic.foo}\")\r\n    private String topic;\r\n\r\n\r\n    @GetMapping(\"demo\")\r\n    public String send(@RequestParam String msg){\r\n//        LOG.info(\"sending message='{}' to topic='{}'\", message, topic);\r\n        kafkaTemplate.send(topic,\"key\", msg);\r\n        return \"send success\";\r\n    }\r\n}\r\n```\r\n\r\n### 2. KafkaProducerConfig\r\n\r\n```java\r\npackage cn.frankfeekr.springbootkafkasample.client;\r\n\r\nimport org.apache.kafka.clients.producer.ProducerConfig;\r\nimport org.apache.kafka.common.serialization.StringSerializer;\r\nimport org.springframework.beans.factory.annotation.Value;\r\nimport org.springframework.context.annotation.Bean;\r\nimport org.springframework.context.annotation.Configuration;\r\nimport org.springframework.kafka.core.DefaultKafkaProducerFactory;\r\nimport org.springframework.kafka.core.KafkaTemplate;\r\nimport org.springframework.kafka.core.ProducerFactory;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\n@Configuration\r\npublic class KafkaSenderConfig {\r\n\r\n    @Value(\"${spring.kafka.bootstrap-servers}\")\r\n    private String bootstrapServers;\r\n\r\n    @Bean\r\n    public Map<String, Object> producerConfigs() {\r\n        Map<String, Object> props = new HashMap<>();\r\n        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);\r\n        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);\r\n        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);\r\n        return props;\r\n    }\r\n\r\n    @Bean\r\n    public ProducerFactory<String, String> producerFactory() {\r\n        return new DefaultKafkaProducerFactory<>(producerConfigs());\r\n    }\r\n\r\n    @Bean\r\n    public KafkaTemplate<String, String> kafkaTemplate() {\r\n        return new KafkaTemplate<>(producerFactory());\r\n    }\r\n\r\n}\r\n```\r\n\r\n\r\n\r\n## 二、Consumer\r\n\r\n### 3. KafkaConsumer\r\n\r\n```java\r\npackage cn.frankfeekr.springbootkafkasample.client;\r\n\r\nimport org.apache.kafka.clients.consumer.Consumer;\r\nimport org.apache.kafka.clients.consumer.ConsumerRecord;\r\nimport org.slf4j.Logger;\r\nimport org.slf4j.LoggerFactory;\r\nimport org.springframework.kafka.annotation.KafkaListener;\r\nimport org.springframework.kafka.support.Acknowledgment;\r\nimport org.springframework.stereotype.Service;\r\n\r\n@Service\r\npublic class KafkaConsumer {\r\n\r\n    private static final Logger LOG = LoggerFactory.getLogger(KafkaConsumer.class);\r\n\r\n    @KafkaListener(topics = \"${app.topic.foo}\")\r\n    public void listen(ConsumerRecord<String, String> record, Acknowledgment ack, Consumer<?, ?> consumer) {\r\n        LOG.warn(\"topic:{},key: {},partition:{}, value: {}, record: {}\",record.topic(), record.key(),record.partition(), record.value(), record);\r\n        if (record.topic().equalsIgnoreCase(\"test\")){\r\n            throw new RuntimeException();\r\n        }\r\n        System.out.println(\"提交 offset \");\r\n        consumer.commitAsync();\r\n    }\r\n}\r\n```\r\n\r\n\r\n\r\n### 4. KafkaConsumerConfig\r\n\r\n```java\r\npackage cn.frankfeekr.springbootkafkasample.client;\r\n\r\nimport org.apache.kafka.clients.consumer.ConsumerConfig;\r\nimport org.apache.kafka.common.serialization.StringDeserializer;\r\nimport org.springframework.beans.factory.annotation.Value;\r\nimport org.springframework.context.annotation.Bean;\r\nimport org.springframework.context.annotation.Configuration;\r\nimport org.springframework.kafka.annotation.EnableKafka;\r\nimport org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;\r\nimport org.springframework.kafka.config.KafkaListenerContainerFactory;\r\nimport org.springframework.kafka.core.ConsumerFactory;\r\nimport org.springframework.kafka.core.DefaultKafkaConsumerFactory;\r\nimport org.springframework.kafka.listener.ConcurrentMessageListenerContainer;\r\nimport org.springframework.kafka.listener.ContainerProperties;\r\n\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\n@EnableKafka\r\n@Configuration\r\npublic class KafkaConsumerConfig {\r\n\r\n    @Value(\"${spring.kafka.bootstrap-servers}\")\r\n    private String bootstrapServers;\r\n\r\n    @Bean\r\n    public Map<String, Object> consumerConfigs() {\r\n        Map<String, Object> props = new HashMap<>();\r\n        props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);\r\n        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);\r\n        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);\r\n        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);\r\n        props.put(ConsumerConfig.GROUP_ID_CONFIG, \"foo\");\r\n        props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, \"earliest\");\r\n\r\n        return props;\r\n    }\r\n\r\n\r\n    @Bean\r\n    public ConsumerFactory<String, String> consumerFactory() {\r\n        return new DefaultKafkaConsumerFactory<>(consumerConfigs());\r\n    }\r\n\r\n    @Bean\r\n    public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, String>> kafkaListenerContainerFactory() {\r\n        ConcurrentKafkaListenerContainerFactory<String, String> factory = new ConcurrentKafkaListenerContainerFactory<>();\r\n        factory.setConcurrency(3);\r\n        factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL);\r\n        factory.setConsumerFactory(consumerFactory());\r\n        return factory;\r\n    }\r\n\r\n}\r\n```\r\n\r\n\r\n\r\n## 三、配置文件\r\n\r\n```yaml\r\nserver:\r\n  port: 9090\r\nspring:\r\n  kafka:\r\n    bootstrap-servers: 127.0.0.1:9092\r\napp:\r\n  topic:\r\n    foo: frankfeekr\r\n```\r\n\r\n\r\n\r\n## 四、pom 依赖\r\n\r\n```xml\r\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd\">\r\n    <modelVersion>4.0.0</modelVersion>\r\n    <parent>\r\n        <groupId>org.springframework.boot</groupId>\r\n        <artifactId>spring-boot-starter-parent</artifactId>\r\n        <version>2.1.7.RELEASE</version>\r\n        <relativePath/> <!-- lookup parent from repository -->\r\n    </parent>\r\n    <groupId>cn.frankfeekr</groupId>\r\n    <artifactId>springboot-kafka-sample</artifactId>\r\n    <version>0.0.1-SNAPSHOT</version>\r\n    <name>springboot-kafka-sample</name>\r\n    <description>Demo project for Spring Boot</description>\r\n\r\n    <properties>\r\n        <java.version>1.8</java.version>\r\n    </properties>\r\n\r\n    <dependencies>\r\n        <dependency>\r\n            <groupId>org.springframework.boot</groupId>\r\n            <artifactId>spring-boot-starter-web</artifactId>\r\n        </dependency>\r\n\r\n        <dependency>\r\n            <groupId>org.springframework.boot</groupId>\r\n            <artifactId>spring-boot-starter-test</artifactId>\r\n            <scope>test</scope>\r\n        </dependency>\r\n\r\n        <dependency>\r\n            <groupId>org.springframework.kafka</groupId>\r\n            <artifactId>spring-kafka</artifactId>\r\n            <version>2.2.7.RELEASE</version>\r\n        </dependency>\r\n    </dependencies>\r\n\r\n    <build>\r\n        <plugins>\r\n            <plugin>\r\n                <groupId>org.springframework.boot</groupId>\r\n                <artifactId>spring-boot-maven-plugin</artifactId>\r\n            </plugin>\r\n        </plugins>\r\n    </build>\r\n\r\n</project>\r\n```\r\n\r\n\r\n\r\n## 参考资料\r\n\r\n- [Spring for Apache Kafka](https://docs.spring.io/spring-kafka/reference/html)\r\n- [Overview (Spring Kafka 2.2.8.RELEASE API)](https://docs.spring.io/spring-kafka/docs/2.2.8.RELEASE/api/)\r\n- [SpringBoot整合Kafka实现发布订阅 - 简书](https://www.jianshu.com/p/7a284bf4efc9)\r\n- [SpringBoot 集成 Spring For Kafka 操作 Kafka 详解 · 小豆丁个人博客](http://www.mydlq.club/article/34/)\r\n"
  },
  {
    "path": "notes/MicroService/kafka/kafka-tutorial-7_监控.md",
    "content": "# 深入浅出 Kafka（七）监控\r\n\r\nKafka Monitor，Kafka Manager\r\n\r\n"
  },
  {
    "path": "notes/MySQL.md",
    "content": "<!-- TOC -->\n\n- [前言](#前言)\n- [第一部分：MySQL基础](#第一部分mysql基础)\n    - [MySQL的多存储引擎架构](#mysql的多存储引擎架构)\n    - [1. 什么是事务](#1-什么是事务)\n        - [AUTOCOMMIT](#autocommit)\n    - [2. 数据库ACID](#2-数据库acid)\n        - [1. 原子性（Atomicity）](#1-原子性atomicity)\n        - [2. 一致性（Consistency）](#2-一致性consistency)\n        - [3. 隔离性（Isolation）](#3-隔离性isolation)\n        - [4. 持久性（Durability）](#4-持久性durability)\n    - [3. 数据库中的范式](#3-数据库中的范式)\n        - [1. 第一范式 (1NF)](#1-第一范式-1nf)\n        - [2. 第二范式 (2NF)](#2-第二范式-2nf)\n        - [3. 第三范式 (3NF)](#3-第三范式-3nf)\n    - [4. 并发一致性问题](#4-并发一致性问题)\n        - [1. 丢失修改](#1-丢失修改)\n        - [2. 脏读](#2-脏读)\n        - [3. 不可重复读](#3-不可重复读)\n        - [4. 幻读](#4-幻读)\n    - [5. 事务隔离级别](#5-事务隔离级别)\n        - [1. 串行化 (Serializable)](#1-串行化-serializable)\n        - [2. 可重复读 (Repeated Read)](#2-可重复读-repeated-read)\n        - [3. 读已提交 (Read Committed)](#3-读已提交-read-committed)\n        - [4. 读未提交 (Read Uncommitted)](#4-读未提交-read-uncommitted)\n    - [6. 存储引擎](#6-存储引擎)\n        - [简介](#简介)\n        - [1. MyISAM](#1-myisam)\n        - [2. InnoDB](#2-innodb)\n        - [3. CSV](#3-csv)\n        - [4. Archive](#4-archive)\n        - [5. Memory](#5-memory)\n        - [6. Federated](#6-federated)\n        - [问：独立表空间和系统表空间应该如何抉择](#问独立表空间和系统表空间应该如何抉择)\n        - [问：如何选择存储引擎](#问如何选择存储引擎)\n        - [问：MyISAM和InnoDB引擎的区别](#问myisam和innodb引擎的区别)\n        - [问：为什么不建议 InnoDB 使用亿级大表](#问为什么不建议-innodb-使用亿级大表)\n    - [7. MySQL数据类型](#7-mysql数据类型)\n        - [1. 整型](#1-整型)\n        - [2. 浮点数](#2-浮点数)\n        - [3. 字符串](#3-字符串)\n        - [4. 时间和日期](#4-时间和日期)\n            - [DATETIME](#datetime)\n            - [TIMESTAMP](#timestamp)\n    - [8. 索引](#8-索引)\n        - [1. 索引使用的场景](#1-索引使用的场景)\n        - [2. B Tree 原理](#2-b-tree-原理)\n            - [B-Tree](#b-tree)\n            - [B+Tree](#btree)\n            - [顺序访问指针](#顺序访问指针)\n            - [优势](#优势)\n        - [3. 索引分类](#3-索引分类)\n            - [B+Tree 索引](#btree-索引)\n            - [哈希索引](#哈希索引)\n            - [全文索引](#全文索引)\n            - [空间数据索引（R-Tree）](#空间数据索引r-tree)\n        - [4. 索引的特点](#4-索引的特点)\n        - [5. 索引的优点](#5-索引的优点)\n        - [6. 索引的缺点](#6-索引的缺点)\n        - [7. 索引失效](#7-索引失效)\n        - [8. 在什么情况下适合建立索引](#8-在什么情况下适合建立索引)\n    - [9. 为什么用B+树做索引而不用B-树或红黑树](#9-为什么用b树做索引而不用b-树或红黑树)\n    - [10. 联合索引](#10-联合索引)\n        - [1. 什么是联合索引](#1-什么是联合索引)\n        - [2. 命名规则](#2-命名规则)\n        - [3. 创建索引](#3-创建索引)\n        - [4. 索引类型](#4-索引类型)\n        - [5. 删除索引](#5-删除索引)\n        - [6. 什么情况下使用索引](#6-什么情况下使用索引)\n    - [11. 主键、外键和索引的区别](#11-主键外键和索引的区别)\n    - [12. 聚集索引与非聚集索引](#12-聚集索引与非聚集索引)\n    - [13. 数据库中的分页查询语句怎么写，如何优化](#13-数据库中的分页查询语句怎么写如何优化)\n    - [14. 常用的数据库有哪些？Redis用过吗？](#14-常用的数据库有哪些redis用过吗)\n    - [15. Redis的数据结构](#15-redis的数据结构)\n    - [16. 分库分表](#16-分库分表)\n        - [1. 垂直切分](#1-垂直切分)\n            - [垂直切分的优点](#垂直切分的优点)\n            - [垂直切分的缺点](#垂直切分的缺点)\n        - [2. 水平切分](#2-水平切分)\n            - [水平切分的优点](#水平切分的优点)\n            - [水平切分的缺点](#水平切分的缺点)\n            - [垂直切分和水平切分的共同点](#垂直切分和水平切分的共同点)\n        - [3. Sharding 策略](#3-sharding-策略)\n        - [4. Sharding 存在的问题及解决方案](#4-sharding-存在的问题及解决方案)\n            - [事务问题](#事务问题)\n            - [JOIN](#join)\n            - [ID 唯一性](#id-唯一性)\n    - [17. 主从复制与读写分离](#17-主从复制与读写分离)\n        - [主从复制](#主从复制)\n        - [读写分离](#读写分离)\n    - [18. 查询性能优化](#18-查询性能优化)\n        - [1. 使用 Explain 进行分析](#1-使用-explain-进行分析)\n        - [2. 优化数据访问](#2-优化数据访问)\n            - [1. 减少请求的数据量](#1-减少请求的数据量)\n            - [2. 减少服务器端扫描的行数](#2-减少服务器端扫描的行数)\n        - [3. 重构查询方式](#3-重构查询方式)\n            - [1. 切分大查询](#1-切分大查询)\n            - [2. 分解大连接查询](#2-分解大连接查询)\n    - [19. 锁类型](#19-锁类型)\n        - [1. 乐观锁](#1-乐观锁)\n        - [2. 悲观锁](#2-悲观锁)\n        - [3. 共享锁](#3-共享锁)\n        - [4. 排它锁](#4-排它锁)\n        - [5. 行锁](#5-行锁)\n        - [6. 表锁](#6-表锁)\n        - [7. 死锁](#7-死锁)\n- [第二部分：高性能MySQL实践](#第二部分高性能mysql实践)\n    - [1. 如何解决秒杀的性能问题和超卖的讨论](#1-如何解决秒杀的性能问题和超卖的讨论)\n        - [解决方案1](#解决方案1)\n        - [解决方案2](#解决方案2)\n        - [解决方案3](#解决方案3)\n    - [2. 数据库主从不一致，怎么解](#2-数据库主从不一致怎么解)\n- [附录：参考资料](#附录参考资料)\n\n<!-- /TOC -->\n\n# 前言\n\n在本文将讨论数据库原理和 MySQL 核心知识，MySQL 性能优化等，包含 **MySQL基础** 和 **高性能MySQL实践** 两部分。\n\n\n\n参考资料：\n\n- 《高性能MySQL》第三版\n- 部分参考：[CyC2018/Interview-Notebook](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/MySQL.md)，特别鸣谢作者 @CyC2018\n\n\n\n学习资料：\n\n- 【慕课网】MySQL性能管理及架构设计\n- 【龙果学院】MySQL大型分布式集群\n- 【咕泡学院】性能分析—MySQL部分\n\n\n\n# 第一部分：MySQL基础\n\n## MySQL的多存储引擎架构\n\n<div align=\"center\"><img src=\"assets/MySQL.png\" width=\"\"/></div><br/>\n\n这里先有个整体的MySQL Server的整体概念，详情转向：[MySQL的多存储引擎架构](http://zhaox.github.io/2016/06/24/mysql-architecture)\n\n\n\n## 1. 什么是事务\n\n<div align=\"center\"><img src=\"assets/185b9c49-4c13-4241-a848-fbff85c03a64.png\" width=\"400\"/></div><br/>\n\n\n\n事务指的是满足 ACID 特性的一组操作，可以通过 Commit 提交一个事务，也可以使用 Rollback 进行回滚。\n\n### AUTOCOMMIT\n\nMySQL 默认 自动提交模式。也就是说，如果不显式使用 `START TRANSACTION` 语句来开始一个事务，那么每个查询都会被当做一个事务自动提交\n\n\n\n## 2. 数据库ACID\n\n### 1. 原子性（Atomicity）\n\n　　原子性是指事务是一个不可分割的工作单位，事务中的操作要么全部成功，要么全部失败。比如在同一个事务中的SQL语句，要么全部执行成功，要么全部执行失败。\n\n　　回滚可以用日志来实现，日志记录着事务所执行的修改操作，在回滚时反向执行这些修改操作即可。\n\n\n\n### 2. 一致性（Consistency）\n\n　　事务必须使数据库从一个一致性状态变换到另外一个一致性状态。以转账为例子，A向B转账，假设转账之前这两个用户的钱加起来总共是2000，那么A向B转账之后，不管这两个账户怎么转，A用户的钱和B用户的钱加起来的总额还是2000，这个就是事务的一致性。 \n\n\n\n### 3. 隔离性（Isolation）\n\n　　隔离性是当多个用户并发访问数据库时，比如操作同一张表时，数据库为每一个用户开启的事务，不能被其他事务的操作所干扰，多个并发事务之间要相互隔离。\n\n　　即要达到这么一种效果：对于任意两个并发的事务 T1 和 T2，在事务 T1 看来，T2 要么在 T1 开始之前就已经结束，要么在 T1 结束之后才开始，这样每个事务都感觉不到有其他事务在并发地执行。\n\n\n\n### 4. 持久性（Durability）\n\n　　一旦事务提交，则其所做的修改将会永远保存到数据库中。即使系统发生崩溃，事务执行的结果也不能丢失。\n\n　　可以通过数据库备份和恢复来实现，在系统发生奔溃时，使用备份的数据库进行数据恢复。\n\n\n\n事务的 ACID 特性概念简单，但不是很好理解，主要是因为这几个特性不是一种平级关系：\n\n- 只有满足一致性，事务的执行结果才是正确的。\n- 在无并发的情况下，事务串行执行，隔离性一定能够满足。此时要只要能满足原子性，就一定能满足一致性。\n- 在并发的情况下，多个事务并发执行，事务不仅要满足原子性，还需要满足隔离性，才能满足一致性。\n- 事务满足持久化是为了能应对数据库奔溃的情况。\n\n\n\n<div align=\"center\"><img src=\"assets/a58e294a-615d-4ea0-9fbf-064a6daec4b2-1534474592177.png\" width=\"500\"/></div><br/>\n\n\n\n## 3. 数据库中的范式\n\n　　满足最低要求的范式是第一范式（1NF）。在第一范式的基础上进一步满足更多规范要求的称为第二范式（2NF），其余范式以次类推。一般说来，数据库只需满足第三范式 (3NF）就行了。 \n\n　　范式的包含关系。一个数据库设计如果符合第二范式，一定也符合第一范式。如果符合第三范式，一定也符合第二范式… \n\n- 1NF：属性不可分 \n- 2NF：属性完全依赖于主键 [消除部分子函数依赖] \n- 3NF：属性不依赖于其它非主属性 [消除传递依赖] \n- BCNF（巴斯-科德范式）：在1NF基础上，任何非主属性不能对主键子集依赖[在3NF基础上消除对主码子集的依赖] \n- 4NF：要求把同一表内的多对多关系删除。 \n- 5NF（完美范式）：从最终结构重新建立原始结构。 \n\n\n\n范式理论是为了解决以上提到四种异常。\n\n高级别范式的依赖于低级别的范式，1NF 是最低级别的范式。\n\n<div align=\"center\"><img src=\"assets/c2d343f7-604c-4856-9a3c-c71d6f67fecc.png\" width=\"400\"/></div><br/>\n\n\n\n### 1. 第一范式 (1NF)\n\n属性不可分。\n\n### 2. 第二范式 (2NF)\n\n每个非主属性完全函数依赖于键码。\n\n可以通过分解来满足。\n\n**分解前** \n\n| Sno  | Sname  | Sdept  | Mname  | Cname  | Grade |\n| ---- | ------ | ------ | ------ | ------ | ----- |\n| 1    | 学生-1 | 学院-1 | 院长-1 | 课程-1 | 90    |\n| 2    | 学生-2 | 学院-2 | 院长-2 | 课程-2 | 80    |\n| 2    | 学生-2 | 学院-2 | 院长-2 | 课程-1 | 100   |\n| 3    | 学生-3 | 学院-2 | 院长-2 | 课程-2 | 95    |\n\n以上学生课程关系中，{Sno, Cname} 为键码，有如下函数依赖：\n\n- Sno -> Sname, Sdept\n- Sdept -> Mname\n- Sno, Cname-> Grade\n\nGrade 完全函数依赖于键码，它没有任何冗余数据，每个学生的每门课都有特定的成绩。\n\nSname, Sdept 和 Mname 都部分依赖于键码，当一个学生选修了多门课时，这些数据就会出现多次，造成大量冗余数据。\n\n**分解后** \n\n关系-1\n\n| Sno  | Sname  | Sdept  | Mname  |\n| ---- | ------ | ------ | ------ |\n| 1    | 学生-1 | 学院-1 | 院长-1 |\n| 2    | 学生-2 | 学院-2 | 院长-2 |\n| 3    | 学生-3 | 学院-2 | 院长-2 |\n\n有以下函数依赖：\n\n- Sno -> Sname, Sdept\n- Sdept -> Mname\n\n关系-2\n\n| Sno  | Cname  | Grade |\n| ---- | ------ | ----- |\n| 1    | 课程-1 | 90    |\n| 2    | 课程-2 | 80    |\n| 2    | 课程-1 | 100   |\n| 3    | 课程-2 | 95    |\n\n有以下函数依赖：\n\n- Sno, Cname -> Grade\n\n### 3. 第三范式 (3NF)\n\n非主属性不传递函数依赖于键码。\n\n上面的 关系-1 中存在以下传递函数依赖：\n\n- Sno -> Sdept -> Mname\n\n可以进行以下分解：\n\n关系-11\n\n| Sno  | Sname  | Sdept  |\n| ---- | ------ | ------ |\n| 1    | 学生-1 | 学院-1 |\n| 2    | 学生-2 | 学院-2 |\n| 3    | 学生-3 | 学院-2 |\n\n关系-12\n\n| Sdept  | Mname  |\n| ------ | ------ |\n| 学院-1 | 院长-1 |\n| 学院-2 | 院长-2 |\n\n\n\n## 4. 并发一致性问题 \n\n### 1. 丢失修改\n\n　　 T1 和 T2 两个事务都对一个数据进行修改，T1 先修改，T2 随后修改，T2 的修改覆盖了 T1 的修改。\n\n<div align=\"center\"><img src=\"assets/88ff46b3-028a-4dbb-a572-1f062b8b96d3.png\" width=\"400\"/></div><br/>\n\n\n\n### 2. 脏读\n\n　　 （针对未提交数据）如果一个事务中对数据进行了更新，但**事务还没有提交**，另一个事务可以 “看到” 该事务没有提交的更新结果，这样造成的问题就是，如果第一个事务回滚，那么，第二个事务在此之前所 “看到” 的数据就是一笔脏数据。 **（脏读又称无效数据读出。一个事务读取另外一个事务还没有提交的数据叫脏读。 ）**\n\n**例子：**\n\n　　Mary 的原工资为 1000, 财务人员将 Mary 的工资改为了 8000 (但未提交事务)\n\n　　Mary 读取自己的工资，发现自己的工资变为了 8000，欢天喜地！\n\n　　而财务发现操作有误，回滚了事务，Mary 的工资又变为了1000\n\n　　像这样，Mary记取的工资数8000是一个脏数据。\n\n**解决办法**：\n\n　　把数据库的事务隔离级别调整到 READ_COMMITTED\n\n\n\n**图解：**\n\n　　T1 修改一个数据，T2 随后读取这个数据。如果 T1 撤销了这次修改，那么 T2 读取的数据是脏数据。\n\n<div align=\"center\"><img src=\"assets/dd782132-d830-4c55-9884-cfac0a541b8e.png\" width=\"450\"/></div><br/>\n\n\n\n### 3. 不可重复读\n\n　　是指在一个事务内，多次读同一数据。在这个事务还没有结束时，另外一个事务也访问该同一数据。那么，在第一个事务中的两次读数据之间，由于第二个事务的修改，那么第一个事务两次读到的的数据可能是不一样的。这样在一个事务内两次读到的数据是不一样的，因此称为是不可重复读。**（同时操作，事务1分别读取事务2操作时和提交后的数据，读取的记录内容不一致。不可重复读是指在同一个事务内，两个相同的查询返回了不同的结果。 ）**\n\n**例子：**\n\n（1）在事务1中，Mary 读取了自己的工资为1000，操作并没有完成\n\n```sql\ncon1 = getConnection();  \nselect salary from employee empId =\"Mary\"; \n```\n\n（2）在事务2中，这时财务人员修改了 Mary 的工资为 2000，并提交了事务. \n\n```sql\ncon2 = getConnection();  \nupdate employee set salary = 2000;  \ncon2.commit();  \n```\n\n（3）在事务1中，Mary 再次读取自己的工资时，工资变为了2000 \n\n```sql\n//con1  \nselect salary from employee empId =\"Mary\";  \n```\n\n在一个事务中前后两次读取的结果并不致，导致了不可重复读。\n\n**解决办法**：\n\n　　如果只有在修改事务完全提交之后才可以读取数据，则可以避免该问题。把数据库的事务隔离级别调整到REPEATABLE_READ\n\n\n\n**图解：**\n\n　　T2 读取一个数据，T1 对该数据做了修改。如果 T2 再次读取这个数据，此时读取的结果和第一次读取的结果不同。\n\n<div align=\"center\"><img src=\"assets/c8d18ca9-0b09-441a-9a0c-fb063630d708-1534474726485.png\" width=\"400\"/></div><br/>\n\n\n\n### 4. 幻读\n\n　　事务 T1 读取一条指定的 Where 子句所返回的结果集，然后 T2 事务新插入一行记录，这行记录恰好可以满足T1 所使用的查询条件。然后 T1 再次对表进行检索，但又看到了 T2 插入的数据。 **（和可重复读类似，但是事务 T2 的数据操作仅仅是插入和删除，不是修改数据，读取的记录数量前后不一致）** \n\n\n\n幻读的重点在于新增或者删除 (数据条数变化)\n\n同样的条件，第1次和第2次读出来的记录数不一样\n\n**例子：**\n\n目前工资为1000的员工有10人。 \n（1）事务1，读取所有工资为 1000 的员工（共读取 10 条记录 ）\n\n```sql\ncon1 = getConnection();  \nSelect * from employee where salary =1000;  \n```\n\n（2）这时另一个事务向 employee 表插入了一条员工记录，工资也为 1000  \n\n```sql\ncon2 = getConnection();  \nInsert into employee(empId,salary) values(\"Lili\",1000);  \ncon2.commit();  \n```\n\n事务1再次读取所有工资为 1000的 员工（共读取到了 11 条记录，这就像产生了幻读）\n\n```sql\n//con1  \nselect * from employee where salary =1000;  \n```\n\n**解决办法：**\n\n　　如果在操作事务完成数据处理之前，任何其他事务都不可以添加新数据，则可避免该问题。把数据库的事务隔离级别调整到 SERIALIZABLE_READ\n\n\n\n**图解：**\n\n　　T1 读取某个范围的数据，T2 在这个范围内插入新的数据，T1 再次读取这个范围的数据，此时读取的结果和和第一次读取的结果不同。\n\n<div align=\"center\"><img src=\"assets/72fe492e-f1cb-4cfc-92f8-412fb3ae6fec.png\" width=\"400\"/></div><br/>\n\n\n\n\n\n## 5. 事务隔离级别\n\n### 1. 串行化 (Serializable)\n\n　　所有事务一个接着一个的执行，这样可以避免幻读 (phantom read)，对于基于锁来实现并发控制的数据库来说，串行化要求在执行范围查询的时候，需要获取范围锁，如果不是基于锁实现并发控制的数据库，则检查到有违反串行操作的事务时，需回滚该事务。 \n\n\n\n### 2. 可重复读 (Repeated Read)\n\n　　所有被 Select 获取的数据都不能被修改，这样就可以避免一个事务前后读取数据不一致的情况。但是却没有办法控制幻读，因为这个时候其他事务不能更改所选的数据，但是可以增加数据，即前一个事务有读锁但是没有范围锁，为什么叫做可重复读等级呢？那是因为该等级解决了下面的不可重复读问题。\n\n　　引申：现在主流数据库都使用 MVCC 并发控制，使用之后RR（可重复读）隔离级别下是不会出现幻读的现象。\n\n\n\n### 3. 读已提交 (Read Committed)\n\n　　被读取的数据可以被其他事务修改，这样可能导致不可重复读。也就是说，事务读取的时候获取读锁，但是在读完之后立即释放(不需要等事务结束)，而写锁则是事务提交之后才释放，释放读锁之后，就可能被其他事务修改数据。该等级也是 SQL Server 默认的隔离等级。\n\n\n\n### 4. 读未提交 (Read Uncommitted)\n\n　　最低的隔离等级，允许其他事务看到没有提交的数据，会导致脏读。\n\n\n\n**总结**\n\n- 四个级别逐渐增强，每个级别解决一个问题，每个级别解决一个问题，事务级别遇到，性能越差，大多数环境(Read committed 就可以用了) \n\n| 隔离级别 | 脏读 | 不可重复读 | 幻影读 |\n| -------- | ---- | ---------- | ------ |\n| 未提交读 | √    | √          | √      |\n| 提交读   | ×    | √          | √      |\n| 可重复读 | ×    | ×          | √      |\n| 可串行化 | ×    | ×          | ×      |\n\n\n\n## 6. 存储引擎\n\n　　对于初学者来说我们通常不关注存储引擎，但是 MySQL 提供了多个存储引擎，包括处理事务安全表的引擎和处理非事务安全表的引擎。在 MySQL 中，不需要在整个服务器中使用同一种存储引擎，针对具体的要求，可以对每一个表使用不同的存储引擎。 \n\n### 简介\n\n　　MySQL 中的数据用各种不同的技术存储在文件（或者内存）中。这些技术中的每一种技术都使用不同的存储机制、索引技巧、锁定水平并且最终提供广泛的不同的功能和能力。通过选择不同的技术，你能够获得额外的速度或者功能，从而改善你的应用的整体功能。存储引擎说白了就是如何存储数据、如何为存储的数据建立索引和如何更新、查询数据等技术的实现方法。\n\n　　例如，如果你在研究大量的临时数据，你也许需要使用内存存储引擎。内存存储引擎能够在内存中存储所有的表格数据。又或者，你也许需要一个支持事务处理的数据库（以确保事务处理不成功时数据的回退能力）。\n\n\n\n> 在MySQL中有很多存储引擎，每种存储引擎大相径庭，那么又改如何选择呢？\n\n\n　　`MySQL 5.5` 以前的默认存储引擎是 `MyISAM`, `MySQL 5.5` 之后的默认存储引擎是 `InnoDB`\n\n　　不同存储引起都有各自的特点，为适应不同的需求，需要选择不同的存储引擎，所以首先考虑这些存储引擎各自的功能和兼容。\n\n\n\n\n### 1. MyISAM\nMySQL 5.5 版本之前的默认存储引擎，在 `5.0` 以前最大表存储空间最大 `4G`，`5.0` 以后最大 `256TB`。\n\n\nMyisam 存储引擎由 `.myd`（数据）和 `.myi`（索引文件）组成，`.frm`文件存储表结构（所以存储引擎都有）\n\n**特性**\n\n- 并发性和锁级别 （对于读写混合的操作不好，为表级锁，写入和读互斥）\n- 表损坏修复\n- Myisam 表支持的索引类型（全文索引）\n- Myisam 支持表压缩（压缩后，此表为只读，不可以写入。使用 myisampack 压缩）\n\n**应用场景**\n\n- 没有事务\n- 只读类应用（插入不频繁，查询非常频繁）\n- 空间类应用（唯一支持空间函数的引擎）\n- 做很多 count 的计算\n\n\n\n\n### 2. InnoDB\nMySQL 5.5 及之后版本的默认存储引擎\n\n**特性**\n\n- InnoDB为事务性存储引擎\n- 完全支持事物的 ACID 特性\n- Redo log （实现事务的持久性） 和 Undo log（为了实现事务的原子性，存储未完成事务log，用于回滚）\n- InnoDB支持行级锁\n- 行级锁可以最大程度的支持并发\n- 行级锁是由存储引擎层实现的\n\n**应用场景**\n\n- 可靠性要求比较高，或者要求事务\n- 表更新和查询都相当的频繁，并且行锁定的机会比较大的情况。\n\n\n\n\n### 3. CSV\n\n**文件系统存储特点**\n\n- 数据以文本方式存储在文件中\n- `.csv`文件存储表内容\n- `.csm`文件存储表的元数据，如表状态和数据量\n- `.frm`存储表的结构\n\n\n\n**CSV存储引擎特点**\n\n- 以 CSV 格式进行数据存储\n- 所有列必须都是不能为 NULL\n- 不支持索引\n- 可以对数据文件直接编辑（其他引擎是二进制存储，不可编辑）\n\n\n\n**引用场景**\n\n- 作为数据交换的中间表\n\n\n### 4. Archive\n\n**特性**\n\n- 以 zlib 对表数据进行压缩，磁盘 I/O 更少\n- 数据存储在ARZ为后缀的文件中（表文件为 `a.arz`，`a.frm`）\n- 只支持 insert 和 select 操作（不可以 delete 和 update，会提示没有这个功能）\n- 只允许在自增ID列上加索引\n\n**应用场景**\n\n- 日志和数据采集类应用\n\n\n\n\n### 5. Memory\n\n特性\n\n- 也称为 HEAP 存储引擎，所以数据保存在内存中（数据库重启后会导致数据丢失）\n\n- 支持 HASH 索引（等值查找应选择 HASH）和 BTree 索引（范围查找应选择）\n- 所有字段都为固定长度，varchar(10) == char(10)\n- 不支持 BLOG 和 TEXT 等大字段\n- Memory 存储使用表级锁（性能可能不如 innodb） \n- 最大大小由 `max_heap_table_size` 参数决定\n- Memory存储引擎默认表大小只有 `16M`，可以通过调整 `max_heap_table_size` 参数\n\n应用场景\n\n- 用于查找或是映射表，例如右边和地区的对应表\n- 用于保存数据分析中产生的中间表\n- 用于缓存周期性聚合数据的结果表\n\n\n**注意：** Memory 数据易丢失，所以要求数据可再生\n\n\n\n\n### 6. Federated\n**特性**\n\n- 提供了访问远程 MySQL 服务器上表的方法\n- 本地不存储数据，数据全部放在远程服务器上\n\n**使用 Federated**\n\n默认是禁止的。如果需要启用，需要在启动时增加Federated参数\n\n\n\n### 问：独立表空间和系统表空间应该如何抉择\n\n**两者比较**\n\n- 系统表空间：无法简单的收缩大小（这很恐怖，会导致 ibdata1 一直增大，即使删除了数据也不会变小）\n- 独立表空间：可以通过 optimize table 命令收缩系统文件\n- 系统表空间：会产生I/O瓶颈（因为只有一个文件）\n- 独立表空间：可以向多个文件刷新数据\n\n**总结**\n强烈建议：对Innodb引擎使用独立表空间（mysql5.6版本以后默认是独立表空间）\n\n**系统表转移为独立表的步骤（非常繁琐）**\n\n- 使用 mysqldump 导出所有数据库表数据\n- 停止 mysql 服务，修改参数，并且删除Innodb相关文件\n- 重启 mysql 服务，重建mysql系统表空间\n- 重新导入数据\n\n\n\n### 问：如何选择存储引擎\n\n**参考条件：**  \n\n- 是否需要事务\n- 是否可以热备份\n- 崩溃恢复\n- 存储引擎的特有特性  \n\n\n**重要一点：** 不要混合使用存储引擎\n**强烈推荐：** Innodb\n\n\n\n### 问：MyISAM和InnoDB引擎的区别\n\n**区别：**\n\n- MyISAM 不支持外键，而 InnoDB 支持\n- MyISAM 是非事务安全型的，而 InnoDB 是事务安全型的。\n- MyISAM 锁的粒度是表级，而 InnoDB 支持行级锁定。\n- MyISAM 支持全文类型索引，而 InnoDB 不支持全文索引。\n- MyISAM 相对简单，所以在效率上要优于 InnoDB，小型应用可以考虑使用 MyISAM。\n- MyISAM 表是保存成文件的形式，在跨平台的数据转移中使用 MyISAM 存储会省去不少的麻烦。\n- InnoDB 表比 MyISAM 表更安全，可以在保证数据不会丢失的情况下，切换非事务表到事务表（alter table tablename type=innodb）。\n\n**应用场景：**\n\n- MyISAM 管理非事务表。它提供高速存储和检索，以及全文搜索能力。如果应用中需要执行大量的 SELECT 查询，那么 MyISAM 是更好的选择。\n- InnoDB 用于事务处理应用程序，具有众多特性，包括 ACID 事务支持。如果应用中需要执行大量的 INSERT\n   或 UPDATE 操作，则应该使用 InnoDB，这样可以提高多用户并发操作的性能。\n\n\n\n\n### 问：为什么不建议 InnoDB 使用亿级大表\n仅作拓展延伸，详情请转向：[为什么不建议innodb使用亿级大表 | 峰云就她了](http://xiaorui.cc/2016/12/08/%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E5%BB%BA%E8%AE%AEinnodb%E4%BD%BF%E7%94%A8%E4%BA%BF%E7%BA%A7%E5%A4%A7%E8%A1%A8/)\n\n\n\n\n\n## 7. MySQL数据类型\n\n### 1. 整型\n\n| 类型      | 存储 | 存储 | 最小值                        | 最大值                        |\n| --------- | ---- | ---- | ----------------------------- | ----------------------------- |\n|           | byte | bit  | signed                        | signed                        |\n| TINYINT   | 1    | 8    | -2<sup>7</sup> = -128         | 2<sup>7</sup>-1 = 127         |\n| SMALLINT  | 2    | 16   |                               |                               |\n| MEDIUMINT | 3    | 24   |                               |                               |\n| INT       | 4    | 32   | -2<sup>31</sup> = -2147483648 | 2<sup>31</sup>-1 = 2147483647 |\n| BIGINT    | 8    | 64   |                               |                               |\n\n\n\nTINYINT, SMALLINT, MEDIUMINT, INT, BIGINT 分别使用 8, 16, 24, 32, 64 位存储空间，一般情况下越小的列越好。\n\nINT(11) 中的数字只是规定了交互工具显示字符的个数，对于存储和计算来说是没有意义的。\n\n\n\n### 2. 浮点数\n\nFLOAT 和 DOUBLE 为浮点类型，DECIMAL 为高精度小数类型。CPU 原生支持浮点运算，但是不支持 DECIMAl 类型的计算，因此 DECIMAL 的计算比浮点类型需要更高的代价。\n\nFLOAT、DOUBLE 和 DECIMAL 都可以指定列宽，例如 DECIMAL(18, 9) 表示总共 18 位，取 9 位存储小数部分，剩下 9 位存储整数部分。\n\n\n\n### 3. 字符串\n\n主要有 CHAR 和 VARCHAR 两种类型，一种是定长的，一种是变长的。\n\nVARCHAR 这种变长类型能够节省空间，因为只需要存储必要的内容。但是在执行 UPDATE 时可能会使行变得比原来长，当超出一个页所能容纳的大小时，就要执行额外的操作。MyISAM 会将行拆成不同的片段存储，而 InnoDB 则需要分裂页来使行放进页内。\n\nVARCHAR 会保留字符串末尾的空格，而 CHAR 会删除。\n\n\n\n### 4. 时间和日期\n\nMySQL 提供了两种相似的日期时间类型：DATETIME 和 TIMESTAMP。\n\n#### DATETIME\n\n能够保存从 1001 年到 9999 年的日期和时间，精度为秒，使用 8 字节的存储空间。\n\n它与时区无关。\n\n默认情况下，MySQL 以一种可排序的、无歧义的格式显示 DATATIME 值，例如“2008-01-16 22:37:08”，这是 ANSI 标准定义的日期和时间表示方法。\n\n\n\n#### TIMESTAMP\n\n和 UNIX 时间戳相同，保存从 1970 年 1 月 1 日午夜（格林威治时间）以来的秒数，使用 4 个字节，只能表示从 1970 年 到 2038 年。\n\n它和时区有关，也就是说一个时间戳在不同的时区所代表的具体时间是不同的。\n\nMySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期，并提供了 UNIX_TIMESTAMP() 函数把日期转换为 UNIX 时间戳。\n\n默认情况下，如果插入时没有指定 TIMESTAMP 列的值，会将这个值设置为当前时间。\n\n应该尽量使用 TIMESTAMP，因为它比 DATETIME 空间效率更高。\n\n\n\n## 8. 索引\n\n### 1. 索引使用的场景\n\n索引能够轻易将查询性能提升几个数量级。\n\n1. 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效。\n2. 对于中到大型的表，索引就非常有效。\n3. 但是对于特大型的表，建立和维护索引的代价将会随之增长。这种情况下，需要用到一种技术可以直接区分出需要查询的一组数据，而不是一条记录一条记录地匹配，例如可以使用分区技术。\n\n索引是在存储引擎层实现的，而不是在服务器层实现的，所以不同存储引擎具有不同的索引类型和实现。\n\n\n\n### 2. B Tree 原理\n\n#### B-Tree\n\n<div align=\"center\"><img src=\"assets/06976908-98ab-46e9-a632-f0c2760ec46c.png\" width=\"\"/></div><br/>\n\n\n定义一条数据记录为一个二元组 [key, data]，B-Tree 是满足下列条件的数据结构：\n\n- 所有叶节点具有相同的深度，也就是说 B-Tree 是平衡的；\n- 一个节点中的 key 从左到右非递减排列；\n- 如果某个指针的左右相邻 key 分别是 keyi 和 keyi+1，且不为 null，则该指针指向节点的（所有 key ≥ keyi） 且（key ≤ keyi+1）。\n\n查找算法：首先在根节点进行二分查找，如果找到则返回对应节点的 data，否则在相应区间的指针指向的节点递归进行查找。\n\n由于插入删除新的数据记录会破坏 B-Tree 的性质，因此在插入删除时，需要对树进行一个分裂、合并、旋转等操作以保持 B-Tree 性质。\n\n#### B+Tree\n\n\n<div align=\"center\"><img src=\"assets/7299afd2-9114-44e6-9d5e-4025d0b2a541.png\" width=\"\"/></div><br/>\n\n\n与 B-Tree 相比，B+Tree 有以下不同点：\n\n- 每个节点的指针上限为 2d 而不是 2d+1（d 为节点的出度）；\n- 内节点不存储 data，只存储 key；\n- 叶子节点不存储指针。\n\n#### 顺序访问指针\n\n\n<div align=\"center\"><img src=\"assets/061c88c1-572f-424f-b580-9cbce903a3fe.png\" width=\"\"/></div><br/>\n\n\n\n\n一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 基础上进行了优化，在叶子节点增加了顺序访问指针，做这个优化的目的是为了提高区间访问的性能。\n\n\n\n#### 优势\n\n红黑树等平衡树也可以用来实现索引，但是文件系统及数据库系统普遍采用 B Tree 作为索引结构，主要有以下两个原因：\n\n**（一）更少的检索次数**\n\n平衡树检索数据的时间复杂度等于树高 h，而树高大致为 O(h)=O(logdN)，其中 d 为每个节点的出度。\n\n红黑树的出度为 2，而 B Tree 的出度一般都非常大。红黑树的树高 h 很明显比 B Tree 大非常多，因此检索的次数也就更多。\n\nB+Tree 相比于 B-Tree 更适合外存索引，因为 B+Tree 内节点去掉了 data 域，因此可以拥有更大的出度，检索效率会更高。\n\n**（二）利用计算机预读特性**\n\n为了减少磁盘 I/O，磁盘往往不是严格按需读取，而是每次都会预读。这样做的理论依据是计算机科学中著名的局部性原理：当一个数据被用到时，其附近的数据也通常会马上被使用。预读过程中，磁盘进行顺序读取，顺序读取不需要进行磁盘寻道，并且只需要很短的旋转时间，因此速度会非常快。\n\n操作系统一般将内存和磁盘分割成固态大小的块，每一块称为一页，内存与磁盘以页为单位交换数据。数据库系统将索引的一个节点的大小设置为页的大小，使得一次 I/O 就能完全载入一个节点，并且可以利用预读特性，相邻的节点也能够被预先载入。\n\n更多内容请参考：[MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html)\n\n\n\n### 3. 索引分类\n\n| 特性                         | 说明                                                 | InnoDB | MyISAM | MEMORY |\n| ---------------------------- | ---------------------------------------------------- | ------ | ------ | ------ |\n| B树索引 (B-tree indexes)     | 自增ID物理连续性更高，<br />二叉树，红黑树高度不可控 | √      | √      | √      |\n| R树索引 (R-tree indexes)     | 空间索引                                             |        | √      |        |\n| 哈希索引 (Hash indexes)      | 无法做范围查询                                       | √      |        | √      |\n| 全文索引 (Full-text indexes) |                                                      | √      | √      |        |\n\n#### B+Tree 索引\n\nB+Tree 索引是大多数 MySQL 存储引擎的默认索引类型。\n\n因为不再需要进行全表扫描，只需要对树进行搜索即可，因此查找速度快很多。除了用于查找，还可以用于排序和分组。\n\n可以指定多个列作为索引列，多个索引列共同组成键。\n\nB+Tree 索引适用于全键值、键值范围和键前缀查找，其中键前缀查找只适用于最左前缀查找。\n\n如果不是按照索引列的顺序进行查找，则无法使用索引。\n\nInnoDB 的 B+Tree 索引分为**主索引**和**辅助索引**。\n\n主索引的叶子节点 data 域记录着完整的数据记录，这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方，所以一个表只能有一个聚簇索引。\n\n<div align=\"center\"><img src=\"assets/c28c6fbc-2bc1-47d9-9b2e-cf3d4034f877.jpg\" width=\"\"/></div><br/>\n\n\n\n辅助索引的叶子节点的 data 域记录着主键的值，因此在使用辅助索引进行查找时，需要先查找到主键值，然后再到主索引中进行查找。\n\n<div align=\"center\"><img src=\"assets/7ab8ca28-2a41-4adf-9502-cc0a21e63b51.jpg\" width=\"\"/></div><br/>\n\n\n\n\n\n#### 哈希索引\n\nInnoDB 引擎有一个特殊的功能叫 “自适应哈希索引”，当某个索引值被使用的非常频繁时，会在 B+Tree 索引之上再创建一个哈希索引，这样就让 B+Tree 索引具有哈希索引的一些优点，比如快速的哈希查找。\n\n哈希索引能以 O(1) 时间进行查找，但是失去了有序性，它具有以下限制：\n\n- 无法用于排序与分组；\n- 只支持精确查找，无法用于部分查找和范围查找；\n\n\n\n#### 全文索引\n\nMyISAM 存储引擎支持全文索引，用于查找文本中的关键词，而不是直接比较是否相等。查找条件使用 MATCH AGAINST，而不是普通的 WHERE。\n\n全文索引一般使用倒排索引实现，它记录着关键词到其所在文档的映射。\n\nInnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。\n\n\n\n#### 空间数据索引（R-Tree）\n\nMyISAM 存储引擎支持空间数据索引，可以用于地理数据存储。空间数据索引会从所有维度来索引数据，可以有效地使用任意维度来进行组合查询。\n\n必须使用 GIS 相关的函数来维护数据。\n\n\n\n### 4. 索引的特点 \n\n- 可以加快数据库的检索速度 \n- 降低数据库插入、修改、删除等维护的速度 \n- 只能创建在表上，不能创建到视图上 \n- 既可以直接创建又可以间接创建 \n- 可以在优化隐藏中使用索引 \n- 使用查询处理器执行SQL语句，在一个表上，一次只能使用一个索引 \n\n\n\n### 5. 索引的优点 \n\n- 创建唯一性索引，保证数据库表中每一行数据的唯一性 \n- 大大加快数据的检索速度，这是创建索引的最主要的原因 \n- 加速数据库表之间的连接，特别是在实现数据的参考完整性方面特别有意义 \n- 在使用分组和排序子句进行数据检索时，同样可以显著减少查询中分组和排序的时间 \n- 通过使用索引，可以在查询中使用优化隐藏器，提高系统的性能 \n\n\n\n### 6. 索引的缺点 \n\n- 创建索引和维护索引要耗费时间，这种时间随着数据量的增加而增加 \n- 索引需要占用物理空间，除了数据表占用数据空间之外，每一个索引还要占一定的物理空间，如果建立聚簇索引，那么需要的空间就会更大 \n- 当对表中的数据进行增加、删除和修改的时候，索引也需要维护，降低数据维护的速度 \n\n\n\n### 7. 索引失效 \n\n> 美团面经：哪些情况下不会使用索引？\n\n- 如果MySQL估计使用**全表扫秒比使用索引快**，则不适用索引。\n\n  例如，如果列key均匀分布在1和100之间，下面的查询使用索引就不是很好：select * from table_name where key>1 and key<90;\n\n- 如果**条件中有or**，即使其中有条件带索引也不会使用\n\n  例如：select * from table_name where key1='a' or key2='b';如果在key1上有索引而在key2上没有索引，则该查询也不会走索引\n\n- 复合索引，如果索引列**不是复合索引的第一部分**，则不使用索引（即不符合最左前缀）\n\n  例如，复合索引为(key1,key2),则查询select * from table_name where key2='b';将不会使用索引\n\n- 如果**like是以 % 开始的**，则该列上的索引不会被使用。\n\n  例如select * from table_name where key1 like '%a'；该查询即使key1上存在索引，也不会被使用如果列类型是字符串，那一定要在条件中使用引号引起来，否则不会使用索引\n\n- 如果列为字符串，则where条件中必须将字符常量值加引号，否则即使该列上存在索引，也不会被使用。\n\n  例如,select * from table_name where key1=1;如果key1列保存的是字符串，即使key1上有索引，也不会被使用。\n\n- 如果使用MEMORY/HEAP表，并且where条件中不使用“=”进行索引列，那么不会用到索引，head表只有在“=”的条件下才会使用索引\n\n\n\n### 8. 在什么情况下适合建立索引\n\n- 为经常出现在关键字order by、group by、distinct后面的字段，建立索引。 \n- 在union等集合操作的结果集字段上，建立索引。其建立索引的目的同上。 \n- 为经常用作查询选择 where 后的字段，建立索引。 \n- 在经常用作表连接 join 的属性上，建立索引。 \n- 考虑使用索引覆盖。对数据很少被更新的表，如果用户经常只查询其中的几个字段，可以考虑在这几个字段上建立索引，从而将表的扫描改变为索引的扫描。 \n\n\n\n更多资料：[MySQL索引背后的数据结构及算法原理](https://www.kancloud.cn/kancloud/theory-of-mysql-index/41846)\n\n\n\n## 9. 为什么用B+树做索引而不用B-树或红黑树\n\nB+ 树只有叶节点存放数据，其余节点用来索引，而 B- 树是每个索引节点都会有 Data 域。所以从 InooDB 的角度来看，B+ 树是用来充当索引的，一般来说索引非常大，尤其是关系性数据库这种数据量大的索引能达到亿级别，所以为了减少内存的占用，索引也会被存储在磁盘上。\n\n- 那么 MySQL如何衡量查询效率呢？答：磁盘 IO 次数\n  - B- 树 / B+ 树 的特点就是每层节点数目非常多，层数很少，目的就是为了就少磁盘 IO 次数，但是 B- 树的每个节点都有 data 域（指针），这无疑增大了节点大小，说白了增加了磁盘 IO 次数（磁盘 IO 一次读出的数据量大小是固定的，单个数据变大，每次读出的就少，IO 次数增多，一次 IO 多耗时），而 B+ 树除了叶子节点其它节点并不存储数据，节点小，磁盘 IO 次数就少。\n  - B+ 树所有的 Data 域在叶子节点，一般来说都会进行一个优化，就是**将所有的叶子节点用指针串起来**。这样遍历叶子节点就能获得全部数据，这样就能进行区间访问啦。在数据库中基于范围的查询是非常频繁的，而 B 树不支持这样的遍历操作。\n\n- B 树相对于红黑树的区别\n  - **AVL 树和红黑树基本都是存储在内存中才会使用的数据结构**。在大规模数据存储的时候，红黑树往往出现由于**树的深度过大**而造成磁盘 IO 读写过于频繁，进而导致效率低下的情况。为什么会出现这样的情况，我们知道要获取磁盘上数据，必须先通过磁盘移动臂移动到数据所在的柱面，然后找到指定盘面，接着旋转盘面找到数据所在的磁道，最后对数据进行读写。磁盘IO代价主要花费在查找所需的柱面上，树的深度过大会造成磁盘IO频繁读写。根据**磁盘查找存取的次数往往由树的高度所决定**，所以，只要我们通过某种较好的树结构减少树的结构尽量减少树的高度，B树可以有多个子女，从几十到上千，可以降低树的高度。\n  - **数据库系统的设计者巧妙利用了磁盘预读原理**，将一个节点的大小设为等于一个页，这样每个节点只需要一次 I/O 就可以完全载入。为了达到这个目的，在实际实现 B-Tree 还需要使用如下技巧：每次新建节点时，直接申请一个页的空间，这样就保证**一个节点物理上也存储在一个页里**，加之计算机存储分配都是按页对齐的，就实现了一个 node 只需一次 I/O。\n\n\n\n## 10. 联合索引\n\n### 1. 什么是联合索引\n\n两个或更多个列上的索引被称作联合索引，联合索引又叫复合索引。对于复合索引：Mysql 从左到右的使用索引中的字段，一个查询可以只使用索引中的一部份，但只能是最左侧部分。\n\n例如索引是key index (a,b,c)，可以支持[a]、[a,b]、[a,b,c] 3种组合进行查找，但不支 [b,c] 进行查找。当最左侧字段是常量引用时，索引就十分有效。\n\n### 2. 命名规则\n\n1. 需要加索引的字段，要在 where 条件中\n2. 数据量少的字段不需要加索引\n3. 如果 where 条件中是OR关系，加索引不起作用\n4.  符合最左原则\n\n### 3. 创建索引\n\n在执行 CREATE TABLE 语句时可以创建索引，也可以单独用 CREATE INDEX 或 ALTER TABLE 来为表增加索引。\n\n**ALTER TABLE**\n\nALTER TABLE 用来创建普通索引、UNIQUE 索引或 PRIMARY KEY 索引。\n\n例如：\n\n```mysql\n ALTER TABLE table_name ADD INDEX index_name (column_list)\n ALTER TABLE table_name ADD UNIQUE (column_list)\n ALTER TABLE table_name ADD PRIMARY KEY (column_list)\n```\n\n其中 table_name 是要增加索引的表名，column_list 指出对哪些列进行索引，多列时各列之间用逗号分隔。索引名 index_name 可选，缺省时，MySQL将根据第一个索引列赋一个名称。另外，ALTER TABLE 允许在单个语句中更改多个表，因此可以在同时创建多个索引。\n\n\n\n**CREATE INDEX**\n\nCREATE INDEX 可对表增加普通索引或 UNIQUE 索引。\n\n例如：\n\n```mysql\n CREATE INDEX index_name ON table_name (column_list)\n CREATE UNIQUE INDEX index_name ON table_name (column_list)\n```\n\ntable_name、index_name 和 column_list 具有与 ALTER TABLE 语句中相同的含义，索引名不可选。另外，不能用 CREATE INDEX 语句创建 PRIMARY KEY 索引。\n\n### 4. 索引类型\n\n在创建索引时，可以规定索引能否包含重复值。如果不包含，则索引应该创建为 PRIMARY KEY 或 UNIQUE 索引。对于单列惟一性索引，这保证单列不包含重复的值。对于多列惟一性索引，保证多个值的组合不重复。\nPRIMARY KEY 索引和 UNIQUE 索引非常类似。\n\n事实上，PRIMARY KEY 索引仅是一个具有名称 PRIMARY 的 UNIQUE 索引。这表示一个表只能包含一个 PRIMARY KEY，因为一个表中不可能具有两个同名的索引。\n   下面的SQL语句对 students 表在 sid 上添加 PRIMARY KEY 索引。\n​     ALTER TABLE students ADD PRIMARY KEY (sid)\n\n### 5. 删除索引\n\n可利用 ALTER TABLE 或 DROP INDEX 语句来删除索引。类似于 CREATE INDEX 语句，DROP INDEX 可以在 ALTER TABLE 内部作为一条语句处理，语法如下。\n\n```mysql\n DROP INDEX index_name ON talbe_name\n ALTER TABLE table_name DROP INDEX index_name\n ALTER TABLE table_name DROP PRIMARY KEY\n```\n\n其中，前两条语句是等价的，删除掉 table_name 中的索引 index_name。\n\n第3条语句只在删除 PRIMARY KEY 索引时使用，因为一个表只可能有一个 PRIMARY KEY 索引，因此不需要指定索引名。如果没有创建 PRIMARY KEY 索引，但表具有一个或多个 UNIQUE 索引，则 MySQL 将删除第一个 UNIQUE 索引。\n\n如果从表中删除了某列，则索引会受到影响。对于多列组合的索引，如果删除其中的某列，则该列也会从索引中删除。如果删除组成索引的所有列，则整个索引将被删除。\n\n\n\n### 6. 什么情况下使用索引\n\n1. 为了快速查找匹配WHERE条件的行。\n2. 为了从考虑的条件中消除行。\n3. 如果表有一个multiple-column索引，任何一个索引的最左前缀可以通过使用优化器来查找行。\n4. 查询中与其它表关联的字，字段常常建立了外键关系\n5. 查询中统计或分组统计的字段\n   - select max(hbs_bh) from zl_yhjbqk\n   - select qc_bh,count(*) from zl_yhjbqk group by qc_bh\n\n\n\n 更多请转向：[MySQL-联合索引 - 简书](https://www.jianshu.com/p/f65be52d5e2b)\n\n\n\n## 11. 主键、外键和索引的区别\n\n|          | 定义                                                 | 作用                     | 个数                     |\n| -------- | ---------------------------------------------------- | ------------------------ | ------------------------ |\n| **主键** | 唯一标识一条记录，不能有重复的，不允许为空           | 用来保证数据完整性       | 主键只能有一个           |\n| **外键** | 表的外键是另一表的主键，外键可以有重复的，可以是空值 | 用来和其他表建立联系用的 | 一个表可以有多个外键     |\n| **索引** | 该字段没有重复值，但可以有一个空值                   | 是提高查询排序的速度     | 一个表可以有多个惟一索引 |\n\n\n\n## 12. 聚集索引与非聚集索引\n\nhttps://www.cnblogs.com/s-b-b/p/8334593.html\n\n聚集索引一定是唯一索引。但唯一索引不一定是聚集索引。  \n\n聚集索引，在索引页里直接存放数据，而非聚集索引在索引页里存放的是索引，这些索引指向专门的数据页的数据。\n\n\n\n## 13. 数据库中的分页查询语句怎么写，如何优化\n\n- Mysql 的 limit 用法 \n  - SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset \n  - LIMIT 接受一个或两个数字参数。参数必须是一个整数常量。如果给定两个参数，第一个参数指定第一个返回记录行的偏移量，第二个参数指定返回记录行的最大数目。初始记录行的偏移量是 0(而不是 1) \n- 最基本的分页方式：SELECT ... FROM ... WHERE ... ORDER BY ... LIMIT ...  \n\n\n\n## 14. 常用的数据库有哪些？Redis用过吗？\n\n- 常用的数据库有哪些？Redis用过吗？ \n  - 常用的数据库 \n    - MySQL\n    - SQLServer \n  - Redis\n    - Redis 是一个速度非常快的非关系型数据库，他可以存储键(key)与5种不同类型的值（value）之间的映射，可以将存储在内存中的键值对数据持久化到硬盘中。 \n    - 与 Memcached 相比 \n      - 两者都可用于存储键值映射，彼此性能也相差无几 \n      - Redis 能够自动以两种不同的方式将数据写入硬盘 \n      - Redis 除了能存储普通的字符串键之外，还可以存储其他4种数据结构，memcached 只能存储字符串键 \n      - Redis 既能用作主数据库，由可以作为其他存储系统的辅助数据库 \n  - Redis应用场景\n    - 缓存、任务队列、应用排行榜、网站访问统计、数据过期处理、分布式集群架构中的session分离\n  - Redis特点\n    - 高并发读写\n    - 海量数据的高效存储和访问\n    - 高可扩展性和高可用性\n\n\n\n## 15. Redis的数据结构 \n\n- STRING：可以是字符串、整数或者浮点数 \n- LIST：一个链表，链表上的每个节点都包含了一个字符串 \n- SET：包含字符串的无序收集器（unordered collection），并且被包含的每个字符串都是独一无二、各不相同的 \n- HAST：包含键值对的无序散列表 \n- ZSET：字符串成员（member）与浮点数分值（score）之间的有序映射，元素的排列顺序由分值的大小决定 \n\n<div align=\"center\"><img src=\"pics/redis-data-structure-types.jpeg\" width=\"650\"/></div><br/>\n\n注：更多 Redis 相关内容将在 [Redis](redis.md) 中进行展开，请转向。\n\n\n\n## 16. 分库分表\n\n简单来说，数据的切分就是通过某种特定的条件，将我们存放在同一个数据库中的数据分散存放到多个数据库（主机）中，以达到分散单台设备负载的效果，即分库分表。\n\n数据的切分根据其切分规则的类型，可以分为如下两种切分模式。\n\n- 垂直（纵向）切分：把单一的表拆分成多个表，并分散到不同的数据库（主机）上。\n- 水平（横向）切分：根据表中数据的逻辑关系，将同一个表中的数据按照某种条件拆分到多台数据库（主机）上。\n\n### 1. 垂直切分\n\n<div align=\"center\"><img src=\"assets/e130e5b8-b19a-4f1e-b860-223040525cf6.jpg\" width=\"\"/></div><br/>\n\n垂直切分是将一张表按列切分成多个表，通常是按照列的关系密集程度进行切分，也可以利用垂直切分将经常被使用的列和不经常被使用的列切分到不同的表中。\n\n在数据库的层面使用垂直切分将按数据库中表的密集程度部署到不同的库中，例如将原来的电商数据库垂直切分成商品数据库 payDB、用户数据库 userBD 等。\n\n\n\n#### 垂直切分的优点\n\n- 拆分后业务清晰，拆分规则明确\n\n- 系统之间进行整合或扩展很容易\n\n- 按照成本、应用的等级、应用的类型等将表放到不同的机器上，便于管理\n\n- 便于实现**动静分离**、**冷热分离**的数据库表的设计模式\n\n- 数据维护简单\n\n\n#### 垂直切分的缺点\n\n- 部分业务表无法关联（Join），只能通过接口方式解决，提高了系统的复杂度\n- 受每种业务的不同限制，存在单库性能瓶颈，不易进行数据扩展和提升性能\n- 事务处理复杂\n\n\n\n### 2. 水平切分\n\n<div align=\"center\"><img src=\"assets/63c2909f-0c5f-496f-9fe5-ee9176b31aba.jpg\" width=\"\"/></div><br/>\n\n\n\n水平切分又称为 Sharding，它是将同一个表中的记录拆分到多个结构相同的表中。\n\n当一个表的数据不断增多时，Sharding 是必然的选择，它可以将数据分布到集群的不同节点上，从而缓存单个数据库的压力。\n\n#### 水平切分的优点\n\n- 单库单表的数据保持在一定的量级，有助于性能的提高\n- 切分的表的结构相同，应用层改造较少，只需要增加路由规则即可\n- 提高了系统的稳定性和负载能力\n\n\n\n#### 水平切分的缺点\n\n- 切分后，数据是分散的，很难利用数据库的Join操作，跨库Join性能较差\n- 拆分规则难以抽象\n- 分片事务的一致性难以解决\n- 数据扩容的难度和维护量极大\n\n\n\n#### 垂直切分和水平切分的共同点\n\n- 存在分布式事务的问题\n- 存在跨节点Join的问题\n- 存在跨节点合并排序、分页的问题\n- 存在多数据源管理的问题\n\n\n\n### 3. Sharding 策略\n\n- 哈希取模：hash(key) % NUM_DB\n\n  - 比如按照 userId mod 64.将数据分布在64个服务器上\n\n- 范围：可以是 ID 范围也可以是时间范围\n\n  - 比如每台服务器计划存放一个亿的数据,先将数据写入服务器A.一旦服务器A写满,则将数据写入服务器B,以此类推. 这种方式的好处是扩展方便.数据在各个服务器上分布均匀.  \n\n- 映射表：使用单独的一个数据库来存储映射关系\n\n\n### 4. Sharding 存在的问题及解决方案\n\n#### 事务问题\n\n使用分布式事务来解决，比如 XA 接口。\n\n#### JOIN\n\n可以将原来的 JOIN 查询分解成多个单表查询，然后在用户程序中进行 JOIN。\n\n#### ID 唯一性\n\n- 使用全局唯一 ID：GUID。\n- 为每个分片指定一个 ID 范围。\n- 分布式 ID 生成器 (如 Twitter 的 Snowflake 算法)。\n\n\n\n\n\n## 17. 主从复制与读写分离\n\n### 主从复制\n\n主要涉及三个线程：binlog 线程、I/O 线程和 SQL 线程。\n\n- **binlog 线程** ：负责将主服务器上的数据更改写入二进制文件（binlog）中。\n- **I/O 线程** ：负责从主服务器上读取二进制日志文件，并写入从服务器的中继日志中。\n- **SQL 线程** ：负责读取中继日志并重放其中的 SQL 语句。\n\n<div align=\"center\"><img src=\"assets/master-slave.png\" width=\"\"/></div><br/>\n\n\n\n\n\n### 读写分离\n\n主服务器用来处理写操作以及实时性要求比较高的读操作，而从服务器用来处理读操作。\n\n读写分离常用代理方式来实现，代理服务器接收应用层传来的读写请求，然后决定转发到哪个服务器。\n\nMySQL 读写分离能提高性能的原因在于：\n\n- 主从服务器负责各自的读和写，极大程度缓解了锁的争用；\n- 从服务器可以配置 MyISAM 引擎，提升查询性能以及节约系统开销；\n- 增加冗余，提高可用性。\n\n<div align=\"center\"><img src=\"assets/master-slave-proxy.png\" width=\"\"/></div><br/>\n\n\n\n## 18. 查询性能优化\n\n### 1. 使用 Explain 进行分析\n\nExplain 用来分析 SELECT 查询语句，开发人员可以通过分析 Explain 结果来优化查询语句。\n\n比较重要的字段有：\n\n- **select_type** : 查询类型，有简单查询、联合查询、子查询等\n- **key** : 使用的索引\n- **rows** : 扫描的行数\n\n```sql\nmysql> explain select * from user_info where id = 2\\G\n*************************** 1. row ***************************\n           id: 1\n  select_type: SIMPLE\n        table: user_info\n   partitions: NULL\n         type: const\npossible_keys: PRIMARY\n          key: PRIMARY\n      key_len: 8\n          ref: const\n         rows: 1\n     filtered: 100.00\n        Extra: NULL\n1 row in set, 1 warning (0.00 sec)\n```\n\n更多内容请参考：[MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735)\n\n\n\n### 2. 优化数据访问\n\n#### 1. 减少请求的数据量\n\n（一）只返回必要的列\n\n最好不要使用 SELECT * 语句。\n\n（二）只返回必要的行\n\n使用 WHERE 语句进行查询过滤，有时候也需要使用 LIMIT 语句来限制返回的数据。\n\n（三）缓存重复查询的数据\n\n使用缓存可以避免在数据库中进行查询，特别要查询的数据经常被重复查询，缓存可以带来的查询性能提升将会是非常明显的。\n\n#### 2. 减少服务器端扫描的行数\n\n最有效的方式是使用索引来覆盖查询。\n\n\n\n### 3. 重构查询方式\n\n#### 1. 切分大查询\n\n一个大查询如果一次性执行的话，可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。\n\n```sql\nDELEFT FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH);\n```\n\n```sql\nrows_affected = 0\ndo {\n    rows_affected = do_query(\n    \"DELETE FROM messages WHERE create  < DATE_SUB(NOW(), INTERVAL 3 MONTH) LIMIT 10000\")\n} while rows_affected > 0\n```\n\n\n\n#### 2. 分解大连接查询\n\n将一个大连接查询（JOIN）分解成对每一个表进行一次单表查询，然后将结果在应用程序中进行关联，这样做的好处有：\n\n- 让缓存更高效。对于连接查询，如果其中一个表发生变化，那么整个查询缓存就无法使用。而分解后的多个查询，即使其中一个表发生变化，对其它表的查询缓存依然可以使用。\n- 分解成多个单表查询，这些单表查询的缓存结果更可能被其它查询使用到，从而减少冗余记录的查询。\n- 减少锁竞争；\n- 在应用层进行连接，可以更容易对数据库进行拆分，从而更容易做到高性能和可扩展。\n- 查询本身效率也可能会有所提升。例如下面的例子中，使用 IN() 代替连接查询，可以让 MySQL 按照 ID 顺序进行查询，这可能比随机的连接要更高效。\n\n```sql\nSELECT * FROM tab\nJOIN tag_post ON tag_post.tag_id=tag.id\nJOIN post ON tag_post.post_id=post.id\nWHERE tag.tag='mysql';\nSELECT * FROM tag WHERE tag='mysql';\nSELECT * FROM tag_post WHERE tag_id=1234;\nSELECT * FROM post WHERE post.id IN (123,456,567,9098,8904);\n```\n\n\n\n## 19. 锁类型\n\nMySQL/InnoDB 的加锁，一直是一个面试中常问的话题。例如，数据库如果有高并发请求，如何保证数据完整性？产生死锁问题如何排查并解决？在工作过程中，也会经常用到，乐观锁，排它锁等。\n\n注：MySQL 是一个支持插件式存储引擎的数据库系统。下面的所有介绍，都是基于 InnoDB 存储引擎，其他引擎的表现，会有较大的区别。\n\n**版本查看**\n\n```mysql\nselect version();\n```\n\n**存储引擎查看**\n\nMySQL 给开发者提供了查询存储引擎的功能，我这里使用的是 MySQL5.6.4，可以使用：\n\n```mysql\nSHOW ENGINES\n```\n\n\n\n### 1. 乐观锁\n\n用数据版本（Version）记录机制实现，这是乐观锁最常用的一种实现方式。何谓数据版本？即为数据增加一个版本标识，一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时，将version字段的值一同读出，数据每更新一次，对此version值加1。当我们提交更新的时候，判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对，如果数据库表当前版本号与第一次取出来的version值相等，则予以更新，否则认为是过期数据。\n\n**举例**\n\n1、数据库表设计\n\n三个字段，分别是 id,value,version\n\n```mysql\nselect id,value,version from TABLE where id=#{id}\n```\n\n2、每次更新表中的value字段时，为了防止发生冲突，需要这样操作\n\n```mysql\nupdate TABLE\nset value=2,version=version+1\nwhere id=#{id} and version=#{version};\n```\n\n\n\n### 2. 悲观锁\n\n与乐观锁相对应的就是悲观锁了。悲观锁就是在操作数据时，认为此操作会出现数据冲突，所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作，这点跟 Java 中的 synchronized 很相似，所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的，悲观锁是由数据库自己实现了的，要用的时候，我们直接调用数据库的相关语句就可以了。\n\n说到这里，由悲观锁涉及到的另外两个锁概念就出来了，它们就是**共享锁**与**排它锁**。**共享锁和排它锁是悲观锁的不同的实现**，它俩都属于悲观锁的范畴。\n\n\n\n以排它锁为例：\n\n要使用悲观锁，我们必须关闭 mysql 数据库的自动提交属性，因为 MySQL 默认使用 autocommit 模式，也就是说，当你执行一个更新操作后，MySQL 会立刻将结果进行提交。\n\n我们可以使用命令设置 MySQL 为非 autocommit 模式：\n\n```mysql\nset autocommit=0;\n# 设置完autocommit后，我们就可以执行我们的正常业务了。具体如下：\n\n# 1. 开始事务 (三者选一就可以)\nbegin; / begin work; / start transaction;\n\n# 2. 查询表信息\nselect status from TABLE where id=1 for update;\n\n# 3. 插入一条数据\ninsert into TABLE (id,value) values (2,2);\n\n# 4. 修改数据为\nupdate TABLE set value=2 where id=1;\n\n# 5. 提交事务\ncommit;/commit work;\n```\n\n\n\n### 3. 共享锁\n\n共享锁又称**读锁**（read lock），是读取操作创建的锁。其他用户可以并发读取数据，但任何事务都不能对数据进行修改（获取数据上的排他锁），直到已释放所有共享锁。\n\n如果事务 T 对数据 A 加上共享锁后，则其他事务只能对 A 再加共享锁，不能加排他锁。获得共享锁的事务只能读数据，不能修改数据\n\n打开第一个查询窗口\n\n```mysql\n#三者选一就可以\nbegin; / begin work; / start transaction;\n\nSELECT * from TABLE where id = 1  lock in share mode;\n```\n\n然后在另一个查询窗口中，对 id 为 1 的数据进行更新\n\n```mysql\nupdate TABLE set name=\"www.souyunku.com\" where id =1;\n```\n\n此时，操作界面进入了卡顿状态，过了超时间，提示错误信息\n\n如果在超时前，执行 `commit`，此更新语句就会成功。\n\n```mysql\n[SQL]update  test_one set name=\"www.souyunku.com\" where id =1;\n[Err] 1205 - Lock wait timeout exceeded; try restarting transaction\n```\n\n加上共享锁后，也提示错误信息\n\n```mysql\nupdate test_one set name=\"www.souyunku.com\" where id =1 lock in share mode;\n[SQL]update  test_one set name=\"www.souyunku.com\" where id =1 lock in share mode;\n[Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'lock in share mode' at line 1\n```\n\n在查询语句后面增加 `lock in share mode`，MySQL 会对查询结果中的每行都加共享锁，当没有其他线程对查询结果集中的任何一行使用排他锁时，可以成功申请共享锁，否则会被阻塞。其他线程也可以读取使用了共享锁的表，而且这些线程读取的是同一个版本的数据。\n\n加上共享锁后，对于 `update,insert,delete` 语句会自动加排它锁。\n\n### 4. 排它锁\n\n排他锁 exclusive lock（也叫 writer lock）又称**写锁**。\n\n**排它锁是悲观锁的一种实现，在上面悲观锁也介绍过**。\n\n若事务 1 对数据对象 A 加上 X 锁，事务 1 可以读 A 也可以修改 A，其他事务不能再对 A 加任何锁，直到事物 1 释放 A 上的锁。这保证了其他事务在事物 1 释放 A 上的锁之前不能再读取和修改 A。排它锁会阻塞所有的排它锁和共享锁\n\n读取为什么要加读锁呢：防止数据在被读取的时候被别的线程加上写锁\n\n使用方式：在需要执行的语句后面加上 `for update` 就可以了\n\n\n\n### 5. 行锁\n\n行锁又分**共享锁**和**排他锁**,由字面意思理解，就是给某一行加上锁，也就是一条记录加上锁。\n\n**注意**：行级锁都是基于索引的，如果一条SQL语句用不到索引是不会使用行级锁的，会使用表级锁。\n\n**共享锁：**\n\n名词解释：共享锁又叫做读锁，所有的事务只能对其进行读操作不能写操作，加上共享锁后在事务结束之前其他事务只能再加共享锁，除此之外其他任何类型的锁都不能再加了。\n\n```mysql\n#结果集的数据都会加共享锁\nSELECT * from TABLE where id = \"1\"  lock in share mode;\n```\n\n**排他锁：**\n\n名词解释：若某个事物对某一行加上了排他锁，只能这个事务对其进行读写，在此事务结束之前，其他事务不能对其进行加任何锁，其他进程可以读取，不能进行写操作，需等待其释放。\n\n```mysql\nselect status from TABLE where id=1 for update;\n```\n\n可以参考之前演示的共享锁，排它锁语句\n\n由于对于表中 id 字段为主键，就也相当于索引。执行加锁时，会将 id 这个索引为 1 的记录加上锁，那么这个锁就是行锁。\n\n\n\n### 6. 表锁\n\n如何加表锁\n\ninnodb 的行锁是在有索引的情况下,没有索引的表是锁定全表的.\n\n**Innodb中的行锁与表锁**\n\n前面提到过，在 Innodb 引擎中既支持行锁也支持表锁，那么什么时候会锁住整张表，什么时候或只锁住一行呢？\n只有通过索引条件检索数据，InnoDB 才使用行级锁，否则，InnoDB 将使用表锁！\n\n在实际应用中，要特别注意 InnoDB 行锁的这一特性，不然的话，可能导致大量的锁冲突，从而影响并发性能。\n\n行级锁都是基于索引的，如果一条 SQL 语句用不到索引是不会使用行级锁的，会使用表级锁。行级锁的缺点是：由于需要请求大量的锁资源，所以速度慢，内存消耗大。\n\n\n\n### 7. 死锁\n\n死锁（Deadlock） \n所谓死锁：是指两个或两个以上的进程在执行过程中，因争夺资源而造成的一种互相等待的现象，若无外力作用，它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁，这些永远在互相等待的进程称为死锁进程。由于资源占用是互斥的，当某个进程提出申请资源后，使得有关进程在无外力协助下，永远分配不到必需的资源而无法继续运行，这就产生了一种特殊现象死锁。\n\n解除正在死锁的状态有两种方法：\n\n**第一种**：\n\n1. 查询是否锁表\n\n```mysql\nshow OPEN TABLES where In_use > 0;\n```\n\n2. 查询进程（如果您有SUPER权限，您可以看到所有线程。否则，您只能看到您自己的线程）\n\n```mysql\nshow processlist\n```\n\n3. 杀死进程id（就是上面命令的id列）\n\n```mysql\nkill id\n```\n\n**第二种**：\n\n1. 查看当前的事务\n\n```mysql\nSELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;\n```\n\n2. 查看当前锁定的事务\n\n```mysql\nSELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;\n```\n\n3. 查看当前等锁的事务\n\n```mysql\nSELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;\n```\n\n**杀死进程**\n\n```mysql\nkill 进程ID\n```\n\n如果系统资源充足，进程的资源请求都能够得到满足，死锁出现的可能性就很低，否则就会因争夺有限的资源而陷入死锁。其次，进程运行推进顺序与速度不同，也可能产生死锁。\n产生死锁的四个必要条件：\n\n1. 互斥条件：一个资源每次只能被一个进程使用。\n\n2. 请求与保持条件：一个进程因请求资源而阻塞时，对已获得的资源保持不放。\n\n3. 不剥夺条件：进程已获得的资源，在末使用完之前，不能强行剥夺。\n\n4. 循环等待条件：若干进程之间形成一种头尾相接的循环等待资源关系。\n\n虽然不能完全避免死锁，但可以使死锁的数量减至最少。将死锁减至最少可以增加事务的吞吐量并减少系统开销，因为只有很少的事务回滚，而回滚会取消事务执行的所有工作。由于死锁时回滚而由应用程序重新提交。\n\n**下列方法有助于最大限度地降低死锁：**\n\n1. 按同一顺序访问对象\n\n2. 避免事务中的用户交互\n\n3. 保持事务简短并在一个批处理中\n\n4. 使用低隔离级别\n\n5. 使用绑定连接\n\n\n\n说明：间隙锁相关锁知识待补充\n\n\n\n参考资料：\n\n- [Mysql锁机制简单了解一下 - Java面试通关手册 - SegmentFault 思否](https://segmentfault.com/a/1190000015219003#articleHeader0)\n\n- [锁概念的理解 - 搜云库 - SegmentFault 思否](https://segmentfault.com/a/1190000015815061#articleHeader7)\n\n\n\n\n# 第二部分：高性能MySQL实践\n\n## 1. 如何解决秒杀的性能问题和超卖的讨论\n\n抢订单环节一般会带来2个问题：\n\n　　1、高并发\n\n　　比较火热的秒杀在线人数都是10w起的，如此之高的在线人数对于网站架构从前到后都是一种考验。\n\n　　2、超卖\n\n　　任何商品都会有数量上限，如何避免成功下订单买到商品的人数不超过商品数量的上限，这是每个抢购活动都要面临的难题。\n\n\n\n### 解决方案1\n\n　　将存库MySQL前移到Redis中，所有的写操作放到内存中，由于Redis中不存在锁故不会出现互相等待，并且由于Redis的写性能和读性能都远高于MySQL，这就解决了高并发下的性能问题。然后通过队列等异步手段，将变化的数据异步写入到DB中。\n\n　　优点：解决性能问题\n\n　　缺点：没有解决超卖问题，同时由于异步写入DB，存在某一时刻DB和Redis中数据不一致的风险。\n\n\n\n### 解决方案2\n\n　　**引入队列，然后将所有写DB操作在单队列中排队，完全串行处理。当达到库存阀值的时候就不在消费队列，并关闭购买功能。这就解决了超卖问题。**\n\n　　优点：解决超卖问题，略微提升性能。\n\n　　缺点：性能受限于队列处理机处理性能和DB的写入性能中最短的那个，另外多商品同时抢购的时候需要准备多条队列。\n\n\n\n### 解决方案3\n\n　　**将提交操作变成两段式，先申请后确认。然后利用Redis的原子自增操作（相比较MySQL的自增来说没有空洞），同时利用Redis的事务特性来发号，保证拿到小于等于库存阀值的号的人都可以成功提交订单。**然后数据异步更新到DB中。\n\n　　优点：解决超卖问题，库存读写都在内存中，故同时解决性能问题。\n\n　　缺点：由于异步写入DB，可能存在数据不一致。另可能存在少买，也就是如果拿到号的人不真正下订单，可能库存减为0，但是订单数并没有达到库存阀值。\n\n\n\n参考资料：\n\n- [库存扣多了，到底怎么整 | 架构师之路](https://mp.weixin.qq.com/s/waGRvyhab2z8b-BIw9bJeA)\n- [如何解决秒杀的性能问题和超卖的讨论 - CSDN博客](https://blog.csdn.net/zhoudaxia/article/details/38067003)\n\n\n\n## 2. 数据库主从不一致，怎么解\n\n数据库主库和从库不一致，常见有这么几种优化方案：\n\n（1）业务可以接受，系统不优化\n\n（2）强制读主，高可用主库，用缓存提高读性能\n\n（3）在cache里记录哪些记录发生过写请求，来路由读主还是读从\n\n\n\n参考资料：\n\n- [数据库主从不一致，怎么解？](https://mp.weixin.qq.com/s/5JYtta9aMGcic7o_ejna-A)\n\n\n\n# 附录：参考资料\n\n- [视频：MySQL 事务的隔离级别与锁-极客学院](http://www.jikexueyuan.com/course/1524.html)\n- [mysql-tutorial/3.5.md at master · jaywcjlove/mysql-tutorial](https://github.com/jaywcjlove/mysql-tutorial/blob/master/chapter3/3.5.md)\n- [大神带你剖析Mysql索引底层数据结构_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili](https://www.bilibili.com/video/av17252271?from=search&seid=3701018912873961528)\n- [大众点评订单系统分库分表实践](https://tech.meituan.com/dianping_order_db_sharding.html)\n- [库存扣减还有这么多方案？ | 架构师之路](https://mp.weixin.qq.com/s/Lfy7ek-vArVBTaUYfl64Bg)\n- [关于分库分表，这有一套大而全的轻量级架构设计思路（蚂蚁金服技术专家）](https://www.toutiao.com/a6545626478447428103/?tt_from=weixin&article_category=stock&timestamp=1524029012&app=news_article&iid=26214166927&wxshare_count=1)\n- [Java大型互联网架构-快速搞定大型互联网网站分库分表方案_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili](https://www.bilibili.com/video/av20966672?from=search&seid=6900253656657206494)\n- [MySQL分库分表_ITPUB博客](http://blog.itpub.net/29254281/viewspace-1819422/)\n- \n"
  },
  {
    "path": "notes/Nginx.md",
    "content": "# Nginx\n\n### \n\n\n\n\n\n## 负载均衡\n\n\n\n\n\n## 反向代理\n\n\n\n## 配置文件\n\n\n\n"
  },
  {
    "path": "notes/PostgreSQL.md",
    "content": "<!-- TOC -->\n\n- [PostgreSQL](#postgresql)\n    - [1. 什么是PostgreSql？](#1-什么是postgresql)\n        - [知识点](#知识点)\n        - [数据库排名](#数据库排名)\n        - [官方网站](#官方网站)\n        - [技术准备](#技术准备)\n        - [使用环境](#使用环境)\n        - [安装](#安装)\n    - [2. 初来乍到数据库](#2-初来乍到数据库)\n        - [知识点](#知识点-1)\n        - [实战演习](#实战演习)\n    - [3. 操作数据表](#3-操作数据表)\n        - [知识点](#知识点-2)\n        - [实战演习](#实战演习-1)\n    - [4. 字段类型](#4-字段类型)\n        - [PostgreSql的基础数据类型](#postgresql的基础数据类型)\n    - [5. 添加表约束](#5-添加表约束)\n        - [知识点](#知识点-3)\n        - [实战演习](#实战演习-2)\n            - [db.sql](#dbsql)\n    - [6. INSERT语句](#6-insert语句)\n        - [知识点](#知识点-4)\n        - [实战演习](#实战演习-3)\n            - [SQL部分](#sql部分)\n    - [7. SELECT语句](#7-select语句)\n        - [知识点](#知识点-5)\n        - [实战演习](#实战演习-4)\n            - [init.sql](#initsql)\n            - [SQL实战](#sql实战)\n    - [8. WHERE语句](#8-where语句)\n        - [知识点](#知识点-6)\n        - [实战演习](#实战演习-5)\n    - [9. 数据抽出选项](#9-数据抽出选项)\n        - [知识点](#知识点-7)\n        - [实战演习](#实战演习-6)\n    - [10. 统计抽出数据](#10-统计抽出数据)\n        - [知识点](#知识点-8)\n        - [实战演习](#实战演习-7)\n    - [11. 方便的函数](#11-方便的函数)\n        - [知识点](#知识点-9)\n        - [实战演习](#实战演习-8)\n    - [12. 更新和删除](#12-更新和删除)\n        - [知识点](#知识点-10)\n        - [实战演习](#实战演习-9)\n    - [13. 变更表结构](#13-变更表结构)\n        - [知识点](#知识点-11)\n        - [实战演习](#实战演习-10)\n    - [14. 操作多个表](#14-操作多个表)\n        - [知识点](#知识点-12)\n        - [实战演习](#实战演习-11)\n            - [renew.sql](#renewsql)\n            - [SQL实行](#sql实行)\n    - [15. 使用视图](#15-使用视图)\n        - [视图概念](#视图概念)\n        - [简单解释](#简单解释)\n        - [知识点](#知识点-13)\n        - [实战演习](#实战演习-12)\n        - [实战建议](#实战建议)\n    - [16. 使用事务](#16-使用事务)\n        - [知识点](#知识点-14)\n        - [实战演习](#实战演习-13)\n    - [参考资料](#参考资料)\n\n<!-- /TOC -->\n\n# PostgreSQL\n\n> 本节参考：[PostgreSql入门](http://komavideo.com/postgresql/index.html)，特别感谢小马视频的学习课程，深入浅出了 PostgreSQL。在此基础上针对自己的学习过程，修改了部分内容。\n\n\n\n本文为 PostgreSQL 入门指南，通过极简的语言，带你走进 PostgreSQL 世界的大门。\n\n## 1. 什么是PostgreSql？\n\nPostgreSQL 是一个自由的对象-关系数据库服务器(数据库管理系统)，是从伯克利写的 POSTGRES 软件包发展而来的。经过十几年的发展， PostgreSQL 是世界上可以获得的最先进的开放源码的数据库系统， 它提供了多版本并发控制，支持几乎所有SQL语句（包括子查询，事务和用户定义类型和函数），并且可以获得非常广阔范围的（开发）语言绑定 （包括C,C++,Java,perl,python,php,nodejs,ruby）。\n\n### 知识点\n\n* 面向关系的数据库\n  + Oracle\n  + MySql\n  + SQLServer\n  + PostgreSql\n* NoSql\n  + MongoDB\n  + Redis\n\n### 数据库排名\n\nhttps://db-engines.com/en/ranking\n\n### 官方网站\n\nhttps://www.postgresql.org/\n\n### 技术准备\n\n* SQL语言基础\n\n### 使用环境\n\n* Ubuntu Server 16 LTS\n* PostgreSql 9.5.x\n\n### 安装\n\n~~~bash\n$ sudo apt-get install postgresql\n$ psql --version\n~~~\n\n\n\n## 2. 初来乍到数据库\n\n### 知识点\n\n* psql的基础\n* 数据库简单操作\n* 写个SQL\n\n### 实战演习\n\n~~~bash\n$ sudo su postgres\n$ psql --version\n$ psql -l\n$ createdb komablog\n$ psql -l\n$ psql komablog\n> help\n> \\h\n> \\?\n> \\l\n> \\q\n$ psql komablog\n> select now();\n> select version();\n> \\q\n$ dropdb komablog\n$ psql -l\n~~~\n\n\n\n## 3. 操作数据表\n\n### 知识点\n\n* create table\n* drop table\n* psql使用\n\n### 实战演习\n\n~~~bash\n$ sudo su postgres\n$ createdb komablog\n$ psql -l\n$ psql komablog\n> create table posts (title varchar(255), content text);\n> \\dt\n> \\d posts\n> alter table posts rename to komaposts;\n> \\dt\n> drop table komaposts;\n> \\dt\n> \\q\n$ nano db.sql\n...\ncreate table posts (title varchar(255), content text);\n...\n$ psql komablog\n> \\i db.sql\n> \\dt\n~~~\n\n\n\n## 4. 字段类型\n\n ### 知识点\n\n* PostgreSql的基础数据类型\n\n### PostgreSql的基础数据类型\n\n* 数值型：\n  + integer(int)\n  + real\n  + serial\n* 文字型：\n  + char\n  + varchar\n  + text\n* 布尔型：\n  + boolean\n* 日期型：\n  + date\n  + time\n  + timestamp\n* 特色类型：\n  + Array\n  + \b网络地址型(inet)\n  + JSON型\n  + XML型\n\n参考网站：\n\nhttps://www.postgresql.org/docs/9.5/static/datatype.html\n\n\n\n## 5. 添加表约束\n\n### 知识点\n\n- 表子段的约束条件\n\n### 实战演习\n\n#### db.sql\n\n```sql\ncreate table posts (\n    id serial primary key,\n    title varchar(255) not null,\n    content text check(length(content) > 8),\n    is_draft boolean default TRUE,\n    is_del boolean default FALSE,\n    created_date timestamp default 'now'\n);\n\n-- 说明\n/*\n约束条件：\n\nnot null:不能为空\nunique:在所有数据中值必须唯一\ncheck:字段设置条件\ndefault:字段默认值\nprimary key(not null, unique):主键，不能为空，且不能重复\n*/\n```\n\n\n\n## 6. INSERT语句\n\n### 知识点\n\n- insert into [tablename] (field, ...) values (value, ...)\n\n### 实战演习\n\n```sql\n$ psql komablog\n> \\dt\n> \\d posts\n```\n\n#### SQL部分\n\n```sql\n> insert into posts (title, content) values ('', '');\n> insert into posts (title, content) values (NULL, '');\n> insert into posts (title, content) values ('title1', 'content11');\n> select * from posts;\n> insert into posts (title, content) values ('title2', 'content22');\n> insert into posts (title, content) values ('title3', 'content33');\n> select * from posts;\n```\n\n\n\n## 7. SELECT语句\n\n### 知识点\n\n- select 基本使用\n\n### 实战演习\n\n#### init.sql\n\n```sql\ncreate table users (\n    id serial primary key,\n    player varchar(255) not null,\n    score real,\n    team varchar(255)\n);\n\ninsert into users (player, score, team) values\n('库里', 28.3, '勇士'),\n('哈登', 30.2, '火箭'),\n('阿杜', 25.6, '勇士'),\n('阿詹', 27.8, '骑士'),\n('神龟', 31.3, '雷霆'),\n('白边', 19.8, '热火');\n```\n\n#### SQL实战\n\n```bash\n$ psql komablog\n> \\i init.sql\n> \\dt\n> \\d users\n> select * from users;\n> \\x\n> select * from users;\n> \\x\n> select * from users;\n> select player, score from users;\n```\n\n\n\n## 8. WHERE语句\n\n### 知识点\n\n- where语句的使用\n\n使用where语句来设定select,update,delete语句数据抽出的条件。\n\n### 实战演习\n\n```sql\n> select * from users;\n> select * from users where score > 20;\n> select * from users where score < 30;\n> select * from users where score > 20 and score < 30;\n> select * from users where team = '勇士';\n> select * from users where team != '勇士';\n> select * from users where player like '阿%';\n> select * from users where player like '阿_';\n```\n\n\n\n## 9. 数据抽出选项\n\n### 知识点\n\nselect语句在抽出数据时，可以对语句设置更多的选项，已得到想要的数据。\n\n- order by\n- limit\n- offset\n\n### 实战演习\n\n```sql\n> select * from users order by score asc;\n> select * from users order by score desc;\n> select * from users order by team;\n> select * from users order by team, score;\n> select * from users order by team, score desc;\n> select * from users order by team desc, score desc;\n> select * from users order by score desc limit 3;\n> select * from users order by score desc limit 3 offset 1;\n> select * from users order by score desc limit 3 offset 2;\n> select * from users order by score desc limit 3 offset 3;\n```\n\n\n\n## 10. 统计抽出数据\n\n### 知识点\n\n- distinct\n- sum\n- max/min\n- group by/having\n\n### 实战演习\n\n```sql\n> select distinct team from users;\n> select sum(score) from users;\n> select max(score) from users;\n> select min(score) from users;\n> select * from users where score = (select max(score) from users);\n> select * from users where score = (select min(score) from users);\n> select team, max(score) from users group by team;\n> select team, max(score) from users group by team having max(score) >= 25;\n> select team, max(score) from users group by team having max(score) >= 25 order by max(score);\n```\n\n\n\n## 11. 方便的函数\n\n### 知识点\n\n- length\n- concat\n- alias\n- substring\n- random\n\n参考网站：\n\nhttps://www.postgresql.org/docs/9.5/static/functions.html\n\n### 实战演习\n\n```sql\n> select player, length(player) from users;\n> select player, concat(player, '/', team) from users;\n> select player, concat(player, '/', team) as \"球员信息\" from users;\n> select substring(team, 1, 1) as \"球队首文字\" from users;\n> select concat('我', substring(team, 1, 1)) as \"球队首文字\" from users;\n> select random();\n> select * from users order by random();\n> select * from users order by random() limit 1;\n```\n\n\n\n## 12. 更新和删除\n\n### 知识点\n\n- update [table] set [field=newvalue,...] where ...\n- delete from [table] where ...\n\n### 实战演习\n\n```sql\n> update users set score = 29.1 where player = '阿詹';\n> update users set score = score + 1 where team = '勇士';\n> update users set score = score + 100 where team IN ('勇士', '骑士');\n> delete from users where score > 30;\n```\n\n\n\n## 13. 变更表结构\n\n### 知识点\n\n- alter table [tablename] ...\n- create index ...\n- drop index ...\n\n### 实战演习\n\n```sql\n> \\d users;\n> alter table users add fullname varchar(255);\n> \\d users;\n> alter table users drop fullname;\n> \\d users;\n> alter table users rename player to nba_player;\n> \\d users;\n> alter table users alter nba_player type varchar(100);\n> \\d users;\n> create index nba_player_index on users(nba_player);\n> \\d users;\n> drop index nba_player_index;\n> \\d users;\n```\n\n\n\n## 14. 操作多个表\n\n### 知识点\n\n- 表结合查询的基础知识\n\n### 实战演习\n\n#### renew.sql\n\n```sql\ncreate table users (\n    id serial primary key,\n    player varchar(255) not null,\n    score real,\n    team varchar(255)\n);\ninsert into users (player, score, team) values\n('库里', 28.3, '勇士'),\n('哈登', 30.2, '火箭'),\n('阿杜', 25.6, '勇士'),\n('阿詹', 27.8, '骑士'),\n('神龟', 31.3, '雷霆'),\n('白边', 19.8, '热火');\n\ncreate table twitters (\n    id serial primary key,\n    user_id integer,\n    content varchar(255) not null\n);\ninsert into twitters (user_id, content) values\n(1, '今天又是大胜,克莱打的真好!'),\n(2, '今晚我得了60分,哈哈!'),\n(3, '获胜咱不怕,缺谁谁尴尬.'),\n(4, '明年我也可能转会西部'),\n(5, '我都双20+了，怎么球队就是不胜呢?'),\n(1, '明年听说有条大鱼要来,谁呀?');\n```\n\n#### SQL实行\n\n```sql\n$ dropdb komablog;\n$ createdb komablog;\n$ psql komablog;\n> \\i renew.sql\n> select * from users;\n> select * from twitters;\n> select users.player, twitters.content from users, twitters where users.id = twitters.user_id;\n> select u.player, t.content from users as u, twitters as t where u.id = t.user_id;\n> select u.player, t.content from users as u, twitters as t where u.id = t.user_id and u.id = 1;\n```\n\n\n\n## 15. 使用视图\n\n### 视图概念\n\n视图（View）是从一个或多个表导出的对象。视图与表不同，视图是一个虚表，即视图所对应的数据不进行实际存储，数据库中只存储视图的定义，在对视图的数据进行操作时，系统根据视图的定义去操作与视图相关联的基本表。\n\n### 简单解释\n\n视图就是一个SELECT语句，把业务系统中常用的SELECT语句简化成一个类似于表的对象，便于简单读取和开发。\n\n### 知识点\n\n- 使用数据库视图(view)\n  - create view ...\n  - drop view ...\n\n### 实战演习\n\n```sql\n> select u.player, t.content from users as u, twitters as t where u.id = t.user_id and u.id = 1;\n> create view curry_twitters as select u.player, t.content from users as u, twitters as t where u.id = t.user_id and u.id = 1;\n> \\dv\n> \\d curry_twitters\n> select * from curry_twitters;\n> drop view curry_twitters;\n> \\dv\n```\n\n### 实战建议\n\n在自己项目中，为了提高数据查询速度，可在表中加入索引index。同时对于经常需要查询的语句，可以提前建立视图view，方便于编码和管理。\n\n\n\n## 16. 使用事务\n\n数据库事务(Database Transaction) ，是指作为单个逻辑工作单元执行的一系列操作，要么完全地执行，要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成，否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元，可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务，必须满足所谓的ACID（原子性、一致性、隔离性和持久性）属性。事务是数据库运行中的逻辑工作单位，由DBMS中的事务管理子系统负责事务的处理。\n\n### 知识点\n\n- PostgreSql数据库事务使用\n  - begin\n  - commit\n  - rollback\n\n### 实战演习\n\n```sql\n> select * from users;\n> begin;\n> update users set score = 50 where player = '库里';\n> update users set score = 60 where player = '哈登';\n> commit;\n> select * from users;\n> begin;\n> update users set score = 0 where player = '库里';\n> update users set score = 0 where player = '哈登';\n> rollback;\n> select * from users;\n```\n\n\n\n\n\n## 参考资料\n\n- [【小马技术】PostgreSql 关系型数据库入门_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili](https://www.bilibili.com/video/av24590479?from=search&seid=9519508000355338670)\n\n\n\n"
  },
  {
    "path": "notes/Python/Python安装.md",
    "content": "#### Windows 安装\r\n\r\n---\r\n\r\n> [Python 官网](https://www.python.org/)\r\n\r\n- 打开官网下载安装包\r\n\r\n  ![官网界面1](assets/win安装.png)\r\n\r\n- 安装包介绍\r\n\r\n  ```css\r\n  Python 3.6.8 --> Python版本\r\n  Windows X86 web-based installer -->32位 web在线安装，下载包很小\r\n  Windows X86 executable installer -->32位 常见以 .exe 结尾可执行安装包 # 推荐这个\r\n  Windows X86 embeddable zip file -->32位 Zip的源码压缩包\r\n  # 请根据系统 64/32 选择 下载\r\n  X86 --> 32位系统\r\n  X86-64 --> 64位系统\r\n  ```\r\n\r\n![安装包介绍](assets/安装包介绍.png)\r\n\r\n- 安装\r\n\r\n  > # 勾选 Add Python 3.6 to PATH ---> 自动配置环境变量\r\n\r\n  - Install Now `默认设置`\r\n\r\n  ![安装界面](assets/安装.png)\r\n\r\n  - Customize installation `自定义安装选项`\r\n\r\n  ![Customize_installation](assets/Customize_installation.png)\r\n\r\n  > 勾选 pip\r\n  >\r\n  > 其他可根据情况勾选\r\n\r\n  ![Advanced](assets/Advanced.png)\r\n\r\n  > 根据自身情况可选择勾选与修改，一般默认即可\r\n  >\r\n  > 继续 Install 即可\r\n\r\n  ![安装完成](assets/安装完成.png)\r\n\r\n##### 验证安装\r\n\r\n- 组合键(Win + R) 打开运行，输入`cmd`\r\n\r\n  ![win键](assets/win键.png)\r\n\r\n  ![cmd](assets/cmd.png)\r\n\r\n  ```python\r\n  #命令行界面输入\r\n  pip -V\r\n  python # 进入后 exit()可退出\r\n  # 输出如图示\r\n  ```\r\n\r\n  ![cmd验证](assets/cmd验证.png)\r\n\r\n- 安装结束\r\n\r\n#### Liunx 安装\r\n\r\n---\r\n\r\n##### 前言\r\n\r\n> Liunx 发行版自带 Python2 版本\r\n>\r\n> 命令行 python2 | python 即可进入交互式界面\r\n\r\n##### 下载安装包\r\n\r\n- 安装依赖包\r\n\r\n  ```python\r\n  sudo yum install -y openssl-devel bzip2-devel expat-devel gdbm-devel readline-devel sqlite-devel\r\n  ```\r\n\r\n- 下载安装包\r\n\r\n  ```python\r\n  wget https://www.python.org/ftp/python/3.6.5/Python-3.6.5.tgz\r\n  # 其他版本可在官网下载，以3.65为例\r\n  ```\r\n\r\n  > [Python 官网](https://www.python.org/ftp/python)\r\n\r\n  ![liunx_下载](assets/liunx_下载.png)\r\n\r\n##### 安装\r\n\r\n- 解压安装\r\n\r\n  ```python\r\n  tar -zxvf Python-3.6.5.tgz # 解压安装包\r\n  ```\r\n\r\n- 编译安装\r\n\r\n  ```python\r\n  # 进入解压后文件夹\r\n  cd Python-3.6.5\r\n  # 创建安装目录\r\n  mkdir /usr/local/python3\r\n  # 指明安装路径\r\n  ./configure -prefix=/usr/local/python3\r\n  # 编译安装\r\n  sudo make && make install\r\n  ```\r\n\r\n  ![liunx安装成功](assets/liunx安装成功.png)\r\n\r\n- 建立软连接\r\n\r\n  ```python\r\n  #为python3创建软连接\r\n  sudo ln -s /usr/local/python3/bin/python3 /usr/bin/python3\r\n  #为pip3创建软连接\r\n  sudo ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3\r\n  ```\r\n\r\n##### 验证\r\n\r\n```python\r\n# shell界面 输入\r\npython3\r\n# shell输入\r\npip3 -V  # V大写\r\n```\r\n\r\n![linux_python_验证.png](assets/linux_python_验证.png)\r\n\r\n![liunx_pip 验证](assets/liunx_pip_验证.png)\r\n\r\n##### Python2\r\n\r\n- 前面提到 Liunx 自带 Python2 版本，但是没有 pip 包管理工具\r\n\r\n- 安装 Python2 pip 包管理工具\r\n\r\n  ```python\r\n  # 安装依赖\r\n  sudo yum -y install epel-release\r\n  # 安装pip\r\n  sudo yum install python-pip\r\n  # 验证\r\n  pip -V\r\n  ```\r\n\r\n  ![python2_pip验证](assets/python2_pip验证.png)\r\n\r\n---\r\n\r\n#### Mac 下安装 Python\r\n\r\n> [Python 官网](https://www.python.org)\r\n\r\n##### 源码安装\r\n\r\n> Mac 也是类 unix 系统,也可参考 Liunx 下安装 Python 方式一致\r\n\r\n##### 安装包安装\r\n\r\n- 下载 Mac 安装包\r\n\r\n  ![Mac安装包](assets/Mac包下载.png)\r\n\r\n  ***\r\n\r\n  > 根据系统位数,下载相应版本 pkg 安装即可\r\n\r\n  ![Mac下载1](assets/Mac下载1.png)\r\n\r\n  - 双击安装即可\r\n\r\n    ![Mac安装](assets/Mac安装.png)\r\n\r\n##### 验证\r\n\r\n```shell\r\n# shell 下\r\npip -V #v 大写\r\npython3\r\n```\r\n\r\n![mac验证](assets/Mac验证.png)\r\n"
  },
  {
    "path": "notes/Python/Python简介及基础语法.md",
    "content": "#### Python诞生发展\n\n---\n\n- Python的创始人为吉多·范罗苏姆（Guido van Rossum,江湖人称“龟叔“）。1989年的圣诞节期间，吉多·范罗苏姆为了在阿姆斯特丹打发时间，决心开发一个新的脚本解释程序，作为ABC语言的一种继承。之所以选中Python作为程序的名字，是因为他是BBC电视剧——蒙提·派森的飞行马戏团的爱好者。\n- Python 2.0于2000年10月16日发布，增加了实现完整的垃圾回收，并且支持Unicode。同时，整个开发过程更加透明，社群对开发进度的影响逐渐扩大。\n- Python 3.0于2008年12月3日发布，此版不完全兼容之前的Python源代码。\n\n#### Python理念\n\n---\n\n- Python 特性\n  - Python是完全面向对象的语言,所以**一切皆对象**\n  - Python拥有丰富的第三库\n  - Python有相对较少的关键字，结构简单，和一个明确定义的语法，学习起来更加简单\n  - Python的设计哲学是“优雅”、“明确”、“简单”\n  - 等等特性\n\n-  Python不足\n  - 饱受诟病的运行速度\n\n    > 个人认为动态语言都有这个小瑕疵\n\n  - 代码无法加密\n\n    > 无法像C JAVA 编译成二进制机器码\n\n- Python应用领域\n  - Web程序\n  - GUI开发\n  - 科学计算\n  - 机器学习\n\n- Python小彩蛋\n  -  人生苦短,我用Python\n\n    > 等价于“PHP是世界上最好的编程语言”\n\n  - Python之禅\n\n    > Python设计者认为如何使用Python的建议\n\n    ```python\n    # Python交互式命令行\n    >>> imoport this\n    ```\n\n    ![Python之禅](assets/Python之禅.png)\n\n#### Python基础语法\n\n------\n\n##### 注释\n\n- 采用`#`行注释\n\n  > 不同于Js，GO等语言采用  // 注释 \n\n  ```python\n  # 这是行注释\n  print(\"python\")\n  ```\n\n- 块注释采用成对`'''`或者`\"\"\"`\n\n  ```python\n  #!/usr/bin/python3 \n  '''\n  这是多行注释，用三个单引号\n  这是多行注释，用三个单引号 \n  这是多行注释，用三个单引号\n  '''\n  \n  \"\"\"\n  这是多行注释，用三个双引号\n  这是多行注释，用三个双引号 \n  这是多行注释，用三个双引号\n  \"\"\"\n  ```\n\n##### 编码\n\n- **Python2** 时采用`ASCII编码 `，**Python3** 采用utf-8为默认编码，支持中文\n\n  ```python\n  # -*- coding: utf-8 -*-\n  # Python2 中使用中文需要在开头指定编码方式,否则会抛出编码错误异常\n  ```\n\n\n##### 行与缩进\n\n- Python最具特色的就是使用缩进来表示代码块，不需要使用大括号 {}  \n\n  - 个人建议统一使用4个空格缩进\n  - **每个代码块内缩进应该一致**\n\n  ```python\n  if True:\n      print('这是一个代码块'）\n  else:\n      print('这是又一个代码块'）\n  ```\n\n  ------\n\n  ```python\n  if True: \n       print(\"这是一个代码块\") \n     print(\"这是错误的！！\")  # 缩进不一致，会导致运行错误\n                                      \n  File \"<tokenize>\", line 3\n      print(\"这是错误的！！\")\n      ^\n  IndentationError: unindent does not match any outer indentation level \n  ```\n\n- 多行语句\n\n  - 当一行代码太长是，采用`\\`来实现多行语句\n\n  - [], {}, 或 () 中的多行语句，不需要使用反斜杠(\\) \n\n    ```python\n    t = \"我\\\n         太\\\n         长\\\n         了\"\n    t = \"我\" +\\\n         \"太\"+\\\n         \"长\" +\\\n         \"了\"\n    ```\n\n    ------\n\n    ```python\n    lis = [\n        1,\n        2,\n        3,\n    ]\n    ```\n\n- 同行显示多局代码\n\n  - Python可以在同一行中使用多条语句，语句之间使用分`;`号分割\n\n    ```python\n    # 条件语句 : 后空格 执行语句\n    if True: print('这是一个代码块'）\n    # 其他语句 ;分割\n    print(\"第一句\");print(\"第二句\") \n    ```\n\n     "
  },
  {
    "path": "notes/Python/流畅的Python/README.md",
    "content": "# python 学习笔记\n\n\n\n## <i class=\"icon-list\"></i> 目录\n|  章节  |                               标题                               |   进度   |\n| :----: | :--------------------------------------------------------------: | :------: |\n| 第1章  |                        [序幕](./序幕.md)                         |  `完成`  |\n| 第2章  |              [序列构成的数组](./序列构成的数组.md)               |  `完成`  |\n| 第3章  |                  [字典和集合](./字典和集合.md)                   |  `完成`  |\n| 第4章  |              [文本和字节序列](./文本和字节序列.md)               |  `完成`  |\n| 第5章  |                        [函数](./函数.md)                         |  `完成`  |\n| 第6章  |    [使用一等函数实现设计模式](./使用一等函数实现设计模式.md)     |  `完成`  |\n| 第7章  |            [函数装饰器和闭包](./函数装饰器和闭包.md)             |  `完成`  |\n| 第7章  |                    [扩展内容](./扩展内容.md)                     |  `完成`  |\n| 第8章  |   [对象引用、可变性和垃圾回收](./对象引用-可变性和垃圾回收.md)   |  `完成`  |\n| 第9章  |        [符合Python风格的对象](./符合Python风格的对象.md)         |  `完成`  |\n| 第10章 |       [序列的修改、散列和切片](./序列的修改-散列和切片.md)       |  `完成`  |\n| 第11章 |                      [抽象类](./抽象类.md)                       |  `完成`  |\n| 第12章 |                      [类继承](./类继承.md)                       |  `完成`  |\n| 第13章 |              [正确重载运算符](./正确重载运算符.md)               |  `完成`  |\n| 第14章 | [可迭代的对象、迭代器和生成器](./可迭代的对象-迭代器和生成器.md) |  `完成`  |\n| 第15章 |        [上下文管理器和else块](./上下文管理器和else块.md)         |  `完成`  |\n| 第16章 |                        [协程](./协程.md)                         |  `完成`  |\n| 第16章 |               [协程-补充内容](./协程-补充内容.md)                |  `完成`  |\n| 第17章 |          [使用future处理并发](./使用future处理并发.md)           |  `完成`  |\n| 第18章 |       [使用asyncio包处理并发](./使用asyncio包处理并发.md)        |  `完成`  |\n| 第19章 |              [动态属性和特性](./动态属性和特性.md)               |  `完成`  |\n| 第20章 |                  [属性描述符](./属性描述符.md)                   | `待完善` |\n| 第21章 |                    [类元编程](./类元编程.md)                     |  `完成`  |\n| 第22章 |       [python字节码-补充内容](./python字节码-补充内容.md)        |  `完成`  |\n| 第23章 |              [python时间模块](./python时间模块.md)               |  `完成`  |\n| 第24章 |              [python命名空间](./python命名空间.md)               |  `完成`  |\n| 第25章 |                [常见问题答疑](./常见问题答疑.md)                 | `待补充` |\n\n\n##  <i class=\"icon-desktop\"></i> 参考\n\n- [x] 《流畅的Python》（[巴西] Luciano Ramalho著）\n"
  },
  {
    "path": "notes/Python/流畅的Python/python命名空间.md",
    "content": "# 第24章 python命名空间\n\n<!-- TOC -->\n\n- [第24章 python命名空间](#%e7%ac%ac24%e7%ab%a0-python%e5%91%bd%e5%90%8d%e7%a9%ba%e9%97%b4)\n  - [内置命令空间](#%e5%86%85%e7%bd%ae%e5%91%bd%e4%bb%a4%e7%a9%ba%e9%97%b4)\n  - [全局命名空间](#%e5%85%a8%e5%b1%80%e5%91%bd%e5%90%8d%e7%a9%ba%e9%97%b4)\n  - [局部命名空间](#%e5%b1%80%e9%83%a8%e5%91%bd%e5%90%8d%e7%a9%ba%e9%97%b4)\n  - [区别](#%e5%8c%ba%e5%88%ab)\n  - [func函数内存地址](#func%e5%87%bd%e6%95%b0%e5%86%85%e5%ad%98%e5%9c%b0%e5%9d%80)\n  - [NotImplementedError](#notimplementederror)\n\n<!-- /TOC -->\n\n## 内置命令空间\n\n就是python解释器 ，启动就可以使用的名字存储在内置命名空间中\n内置的名字在启动解释器的时候被加载进内存里\n\n\n## 全局命名空间\n\n自己写的代码，但不是函数中的代码\n\n是在程序从上到下被执行的过程中依次加载进内存的\n放置了我们设置的所有变量名和函数名\n\n\n## 局部命名空间\n\n就是函数内部定义的名字\n当调用函数的时候，才会产生这个名称空间，随着函数执行的结束，这个命名空间就消失了\n\n\n## 区别\n\n1、在局部： 可以使用全局，内置命名空间中的名字\n\n2、在全局：可以使用内置命名空间中的名字，但是不能使用局部中变量使用\n\n例子：\n```python\ndef func():\n    a = 1\n\n\nfunc()\nprint(a)\n```\n\n运行结果:\n\n```\nNameError: name 'a' is not defined\n```\n\n3、在内置： 不能使用局部和全局的名字的\n\n顺序是这样的：\n**`内置>全局>局部`**\n\n例子:\n```python\ndef max():\n    print(\"in max func\")\n\n\nmax()\n```\n\n运行结果：\n```python\nin max func\n```\n\n在正常情况下，直接使用内置的名字\n当我们在全局定义了和内置名字空间中同名的名字时，就会使用全局的名字\n一级一级找\n\n\n## func函数内存地址\n\n```python\n# 函数名() 函数的调用\n# 加入id 就是函数的内存地址\n\n\ndef max():\n    print(\"in max func\")\n\n\nprint(max)\nprint(id(max))\n```\n运行结果：\n```\n<function max at 0x0000025D7091D9D8>\n2600343820760\n\n```\n\n\n\n##  NotImplementedError\n\nPython编程中raise可以实现报出错误的功能，而报错的条件可以由程序员自己去定制。\n\n在面向对象编程中，可以先预留一个方法接口不实现，在其子类中实现。\n\n如果要求其子类一定要实现，不实现的时候会导致问题，那么采用raise的方式就很好。\n\n而此时产生的问题分类是NotImplementedError\n\n\n例子：\n```python\nclass ClassDemo(object):\n    def run(self):\n        raise NotImplementedError\n\n\nclass ChildClass(ClassDemo):\n    def run(self):\n        print(\"Hello world\")\n\n\nChildClass().run()\n```\n<br>\n\n例子:\n```python\nclass ClassDemo(object):\n    def run(self):\n        raise NotImplementedError\n\n    def wrong(self):\n        # Will raise a TypeError\n        NotImplemented = \"don't do this\"\n        return NotImplemented\n\n\nclass ChildClass(ClassDemo):\n    def run(self):\n        print(\"Hello world\")\n\n    def wrong(self):\n        print(\"wrong\")\n\n\nChildClass().run()  # Hello world\n\nwrong = ClassDemo().wrong()\nprint(wrong)  # don't do this\n\n```\n\n\n这里区分下 NotImplemented && NotImplementedError\n\n```\n type(NotImplemented)\n<class 'NotImplementedType'>\n type(NotImplementedError)\n<class 'type'>\nissubclass(NotImplementedError,Exception)\nTrue\n```\n\n\n\nNotImplemented 是 Python 内建命名空间内仅有的 6 个常量（Python 中没有真正的常量）之一，\n其它几个分别是 False、True、None、Ellipsis 和` __debug__`。\n\n和 Ellipsis 一样，NotImplemented 也可以被重新赋值：\nNotImplemented = \"don't do this\"\n\n两者是什么关系呢？答案是**“没啥关系”**。\n\nPython 中 NotImplemented 广泛应用于二元魔术方法中，比如 `__eq__()、__lt__() `等等，表示该类型无法和其它类型进行对应的二元运算\n\n\n例子：\n```python\nclass A(object):\n    def __init__(self, value):\n        self.value = value\n\n    def __eq__(self, other):\n        if isinstance(other, A):\n            print('Comparing an A with an A')\n            return other.value == self.value\n        if isinstance(other, B):\n            print('Comparing an A with a B')\n            return other.value == self.value\n            print('Could not compare A with the other class')\n            return NotImplemented\n\n\nclass B(object):\n    def __init__(self, value):\n        self.value = value\n\n    def __eq__(self, other):\n        # raise NotImplementedError\n        if isinstance(other, B):\n            print('Comparing a B with another B')\n            return other.value == self.value\n            print('Could not compare B with the other class')\n            return NotImplemented\n\n\na, b = A(1), B(1)\naa, bb = A(1), B(1)\na == aa  # True\nb == bb  # True\na == b  # True\nb == a  # True\n\n```\n\n运行结果：\n```\nComparing an A with an A\nComparing a B with another B\nComparing an A with a B\n```\n\n\n\n说明 == 运算符执行时会先寻找 B 的 `__eq__() `方法，\n遇到 NotImplemented 返回值则反过来去寻找 A 的 `__eq__()` 方法。\n\n\n什么时候该使用 NotImplementedError？\n\n>NotImplementedError 是 RuntimeError 的子类：\n>issubclass(NotImplementedError, RuntimeError) # True\n\n>官网 的建议是当你需要一个方法必须覆盖才能使用时，其效果类似于 Java 中的接口，用于定义一个未实现的抽象方法。"
  },
  {
    "path": "notes/Python/流畅的Python/python字节码-补充内容.md",
    "content": "# 第22章 python字节码-补充内容\n\n<!-- TOC -->\n\n- [第22章 python字节码-补充内容](#%e7%ac%ac22%e7%ab%a0-python%e5%ad%97%e8%8a%82%e7%a0%81-%e8%a1%a5%e5%85%85%e5%86%85%e5%ae%b9)\n  - [python编译过程](#python%e7%bc%96%e8%af%91%e8%bf%87%e7%a8%8b)\n    - [py文件](#py%e6%96%87%e4%bb%b6)\n      - [__pycache__](#pycache)\n      - [pyc文件](#pyc%e6%96%87%e4%bb%b6)\n      - [pyo文件](#pyo%e6%96%87%e4%bb%b6)\n  - [python程序执行原理](#python%e7%a8%8b%e5%ba%8f%e6%89%a7%e8%a1%8c%e5%8e%9f%e7%90%86)\n\n<!-- /TOC -->\n\n\n\n## python编译过程\n\n\n在日常生活中，Python代码一般是不编译的，几个py文件复制来就能用。再加上脚本语言的名头，有些不太了解Python的朋友就以为Python没有编译这个过程。\n\n其实，虽然Python是脚本语言，但它与Java和C#一样，**`只能执行字节码`**。\n\n只是Python将编译过程隐藏起来，不大明显而已。\n\n\n这一章就详细记述一下Python的编译过程以及一些技巧。\n\n`这里使用的python版本是3.6.6`\n\n\n### py文件\n\n**py文件 就是python代码文件**\n\n这里准备两个py文件为下面分析使用\n\nmymodule.py\n```python\nclass MyModule(object):\n    def say(self, name):\n        print(\"Say...\", name)\n```\n\ndemo.py\n```python\nfrom mymodule import MyModule\n\ntest = MyModule()\n\ntest.say(\"Hello\")\n```\n\n<br>\n运行demo.py方法在当前目录会产生一个`__pycache__`目录\n\n进入这个目录我们看到里面有个文件mymodule.cpython-36.pyc\n\n<br>\n这里就有两个问题了\n\n`__pycache__`目录是什么？\n\npyc文件又是什么？\n\n\n#### `__pycache__`\n\n`__pycache__`是包含编译并准备执行Python 3字节码的目录\n\n当第一次运行 python 脚本时,解释器会将 *.py 脚本进行编译并保存到 `__pycache__` 目录\n\n**这里有个问题？** \n\n我就写个一个文件，里面就打印一句话，会不会产生pycache目录呢？\n\n```\nprint(\"hello world\")\n```\n\n实验表明：\n> 并不会产生这个pycache目录，这个目录只有当**`import xxx`**才会生成\n\n\n**`这个目录能不能删掉呢？删掉有什么影响呢?`**\n\n下次执行脚本时,若解释器发现你的 *.py 脚本没有变更,便会跳过编译一步,直接运行保存在 `__pycache__ `目录下的 *.pyc 文件\n\n\n**执行python脚本会导致字节代码在内存中生成并保持到程序关闭。**\n\n**如果导入模块，为了更快的可重用性，Python将创建一个缓存.pyc(PYC是'Python''Compiled')文件，其中导入的模块的字节代码被缓存。**\n\n想法是通过避免在重新导入时重新编译(编译一次，运行多次策略)来加速python模块的加载。\n\n文件名与模块名称相同。\n初始点后面的部分表示创建缓存的Python实现(可能是CPython)，后跟其版本号。\n\n如前面说的mymodule.cpython-36.pyc文件\n\n#### pyc文件\n\npython2 代码 运行是直接在本地产生pyc文件\npython3 代码运行是把pyc文件放在pycache目录\n\n\n.pyc文件是由.py文件经过编译后生成的字节码文件，其加载速度相对于之前的.py文件有所提高，而且还可以实现源码隐藏，以及一定程度上的反编译\n\n\npyc文件，是python编译后的字节码（bytecode）文件。\n只要你运行了py文件，python编译器就会自动生成一个对应的pyc字节码文件。\n这个pyc字节码文件，经过python解释器，会生成机器码运行\n\n下次调用直接调用pyc，而不调用py文件。直到你这个py文件有改变。\n\npython解释器会检查pyc文件中的生成时间，对比py文件的修改时间，如果py更新，那么就生成新的pyc。\n\n\n#### pyo文件\n\npyo文件也是优化（注意这两个字，便于后续的理解）编译后的程序（相比于.pyc文件更小），也可以提高加载速度。\n\n但对于嵌入式系统，它可将所需模块编译成.pyo文件以减少容量。  但总的来说，作用上是几乎与原来的.py脚本没有区别的，\n\n\n在所有的Python选项中：\n\n>-O，表示优化生成.pyo字节码\n\n>-OO，表示进一步移除-O选项生成的字节码文件中的文档字符串\n\n>-m，表示导入并运行指定的模块\n\n\n执行下面的命令查看是否生成pyo文件\n\n```python\n#py_compile是Python的自带模块\npython -O -m py_compile demo.py\npython -OO -m py_compile demo.py\n```\n\n到pycache目录看到生成了cpython-36.opt-1.pyc文件\n\n![Alt text](https://raw.githubusercontent.com/Syncma/Figurebed/master/img/1578027718663.png)\n\n我们发现下面的结果：\n\n1.`咦，说好的生成pyo文件的呢？怎么又生成了pyc文件？`\n\n2.两个pyc文件\n-O 选择生成的是xxxx-1.pyc\n-OO 选项生成的是xxxx-2.pyc\n\n`这两个pyc又有啥区别呢？`\n\n\n>查资料，发现从python3.5+开始就去除了pyo文件后缀名，\n[消除pyo文件原文链接](https://www.python.org/dev/peps/pep-0488/)\n[PYC目录链接](https://www.python.org/dev/peps/pep-3147/)\n\n为啥官方要去除pyo呢？ \n\n我读完这两篇文章，简单总结下：\n\n由于pyc 文件在 Python 主要版本之间并不兼容\n所以引入了一种更灵活的替代机制 -pyc\n\n格式包含实现名称和版本号\n\n*.pyc 文件可以表示优化和未优化的字节码。\n\n优化级别信息可以包含在 *.pyc 文件的名字中，\n\n优化级别：\n```\n0: .pyc\n1 (-O): .pyo\n2 (-OO): .pyo\n```\n\n\n\n## python程序执行原理\n\n![Alt text](https://raw.githubusercontent.com/Syncma/Figurebed/master/img/1578032890559.png)\n"
  },
  {
    "path": "notes/Python/流畅的Python/python时间模块.md",
    "content": "# 第23章 python时间模块\n\n\n<!-- TOC -->\n\n- [第23章 python时间模块](#%e7%ac%ac23%e7%ab%a0-python%e6%97%b6%e9%97%b4%e6%a8%a1%e5%9d%97)\n  - [time 模块](#time-%e6%a8%a1%e5%9d%97)\n  - [datetime模块](#datetime%e6%a8%a1%e5%9d%97)\n  - [字符串转换成datetime类型](#%e5%ad%97%e7%ac%a6%e4%b8%b2%e8%bd%ac%e6%8d%a2%e6%88%90datetime%e7%b1%bb%e5%9e%8b)\n  - [datetime转换成字符串类型](#datetime%e8%bd%ac%e6%8d%a2%e6%88%90%e5%ad%97%e7%ac%a6%e4%b8%b2%e7%b1%bb%e5%9e%8b)\n  - [时间戳转string](#%e6%97%b6%e9%97%b4%e6%88%b3%e8%bd%acstring)\n\n<!-- /TOC -->\n\n\n## time 模块\n\n\ntime模块提供各种操作时间的函数\n\n一般有两种表示时间的方式:\n\n第一种: 是时间戳的方式(相对于1970.1.1 00:00:00以秒计算的偏移量),时间戳是惟一的\n\n第二种: 以数组的形式表示即(struct_time),共有九个元素，分别表示，同一个时间戳的struct_time会因为时区不同而不同\n\n\n例子：\n```python\nimport time\n\nprint(time.time())\n# 1578030257.4879787\n\nprint(time.localtime())\n# time.struct_time(tm_year=2020, tm_mon=1, tm_mday=3, tm_hour=13, tm_min=44, tm_sec=17, tm_wday=4, tm_yday=3, tm_isdst=0)\n\n```\n\n\n\n## datetime模块\n\nPython提供了多个内置模块用于操作日期时间，像calendar，time，datetime。time模块。\n相比于time模块，datetime模块的接口则更直观、更容易调用。\n\ndatetime模块定义了下面这几个类：\n\ndatetime.date：表示日期的类。常用的属性有year, month, day, today；\n\ndatetime.time：表示时间的类。常用的属性有hour, minute, second, microsecond；\n\ndatetime.datetime：表示日期时间。\n\ndatetime.timedelta：表示时间间隔，即两个时间点之间的长度。\n\ndatetime.tzinfo：与时区有关的相关信息。\n\ndatetime中，表示日期时间的是一个datetime对象\n\ndatetime中提供了strftime方法，可以将一个datetime型日期转换成字符串\n\n\n举一个例子：\n```python\nimport datetime\n\n\ndef datetostr(date):\n    return str(date)[0:10]\n\ndef getDaysByNum(num):\n    today = datetime.date.today()\n    oneday = datetime.timedelta(days=1)\n    li = []\n    for i in range(0, num):\n        today = today - oneday\n        li.append(datetostr(today))\n    return li\n\n\nprint(getDaysByNum(3))\n```\n\n\n\n## 字符串转换成datetime类型\n\n```python\nimport datetime\ns = \"2016-01-12\"\nprint(datetime.datetime.strptime(s, '%Y-%m-%d')\n```\n\n\n## datetime转换成字符串类型\n```python\nimport datetime\ns = \"2016-01-12\"\nprint(datetime.datetime.strptime(s, '%Y-%m-%d').strftime('%Y-%m-%d'))\n\n```\n\n## 时间戳转string\n```python\nimport time\n\ntime1 = 1353254400\n\ntime_str = time.strftime('%Y-%m-%d', time.localtime(time1))\nprint(time_str)\n```\n\n##几个例子\n\n获取小时：\n```python\nimport datetime as dt\nhours = [dt.time(i).strftime('%H:%M') for i in range(24)]\n\nprint(hours)\n```\n\n\n时间相减：\n```python\nimport time\nimport datetime\n\n\ndef String2Time(s):\n    return time.strptime(s, \"%Y-%m-%d %H:%M:%S\")\n\n\ndef dateMinDate(d1, d2):\n    d1 = String2Time(d1)\n    d2 = String2Time(d2)\n    # 这里的*号表示将time.struct_time类型转换成datetime.datetime\n    delta = datetime.datetime(*d1[:6]) - datetime.datetime(*d2[:6])\n    print(delta)\n\n\nif __name__ == \"__main__\":\n    dateMinDate(\"2012-06-26 15:20:00\", \"2012-06-26 15:10:00\")\n\n```\n\n\n时间间隔：\n```python\nimport datetime\ndate1 = '2011-05-03'\ndate2 = '2011-05-10'\ndatelist = []\n\nstart = datetime.datetime.strptime(date1, '%Y-%m-%d')\nend = datetime.datetime.strptime(date2, '%Y-%m-%d')\nstep = datetime.timedelta(days=1)\n\nwhile start <= end:\n    days = start.strftime(\"%Y-%m-%d\")  # 此处要加入这一句\n    datelist.append(days)\n    start += step\n\nprint(datelist)\n```\n\n\n计算多久之前：\n```python\nimport datetime\nimport sys\nif sys.version_info[0] >= 3:\n    unicode = str\n\n\ndef timebefore(d):\n    chunks = (\n        (60 * 60 * 24 * 365, u'年'),\n        (60 * 60 * 24 * 30, u'月'),\n        (60 * 60 * 24 * 7, u'周'),\n        (60 * 60 * 24, u'天'),\n        (60 * 60, u'小时'),\n        (60, u'分钟'),\n    )\n    # 如果不是datetime类型转换后与datetime比较\n    if not isinstance(d, datetime.datetime):\n        d = datetime.datetime(d.year, d.month, d.day)\n    now = datetime.datetime.now()\n    delta = now - d\n    # 忽略毫秒\n    before = delta.days * 24 * 60 * 60 + delta.seconds  # python2.7直接调用 delta.total_seconds()\n    # 刚刚过去的1分钟\n    if before <= 60:\n        return u'刚刚'\n    for seconds, unit in chunks:\n        count = before // seconds\n        if count != 0:\n            break\n    return unicode(count) + unit + u\"前\"\n\n\nd = datetime.datetime.fromtimestamp(1571500800)\nprint(timebefore(d))\n\n```\n"
  },
  {
    "path": "notes/Python/流畅的Python/上下文管理器和else块.md",
    "content": "# 第15章 上下文管理器和else块\n\n<!-- TOC -->\n\n- [第15章 上下文管理器和else块](#%e7%ac%ac15%e7%ab%a0-%e4%b8%8a%e4%b8%8b%e6%96%87%e7%ae%a1%e7%90%86%e5%99%a8%e5%92%8celse%e5%9d%97)\n  - [try..except..finally](#tryexceptfinally)\n  - [with语句](#with%e8%af%ad%e5%8f%a5)\n  - [上下文管理器](#%e4%b8%8a%e4%b8%8b%e6%96%87%e7%ae%a1%e7%90%86%e5%99%a8)\n  - [@contextmanager 装饰器](#contextmanager-%e8%a3%85%e9%a5%b0%e5%99%a8)\n\n<!-- /TOC -->\n\n## try..except..finally\n\n看一个例子：\n```python\ndef get_result():\n    a = 3\n    b = 0\n    try:\n        b = 3 / 0\n    except Exception as e:\n        print(\"ERROR=\", e)\n    finally:\n        print(\"a=\", a)\n\n\nif __name__ == \"__main__\":\n    get_result()\n   \n```\n\n返回结果是：\n```python\nERROR= division by zero\na= 3\n```\n\n<br>\n总结：\n>try..except..else没有捕获到异常，执行else语句\ntry..except..finally不管是否捕获到异常，都执行finally语句\n\n\n\n## with语句\n\n\npython文本文件读写的3种方法\n\n```python\n# 第一种方法：\nfile1 = open(\"test.txt\")\nfile2 = open(\"output.txt\", \"w\")\nwhile True:\n    line = file1.readline()\n    # 这里可以进行逻辑处理\n    file2.write(line)\n    if not line:\n        break\n\n# 记住文件处理完，关闭是个好习惯\nfile1.close()\nfile2.close()\n\n# 读文件有3种方法：\n# - read()将文本文件所有行读到一个字符串中。\n# - readline()是一行一行的读\n# - readlines()是将文本文件中所有行读到一个list中，文本文件每一行是list的一个元素。\n\n# 优点：readline()可以在读行过程中跳过特定行。\n\n# 第二种方法：文件迭代器，用for循环的方法\nfile2 = open(\"output.txt\", \"w\")\nfor line in open(\"test.txt\"):\n    # 这里可以进行逻辑处理\n    file2.write(line)\n\n# 第三种方法： 推荐使用这个方法文件上下文管理器\nwith open('somefile.txt', 'r') as f:\n    data = f.read()\n\nwith open('somefile.txt', 'r') as f:\n    for line in f:\n        print(line)\n\nwith open('somefile.txt', 'w') as f:\n    f.write(\"hello\")\n```\n\n\n\n**这里重点说说第三种方法**\n\n打开文件在进行读写的时候可能会出现一些异常状况，如果按照常规的f.open\n写法，我们需要try,except,finally，做异常判断，并且文件最终不管遇到什么情况，都要执行finally f.close()关闭文件，with方法帮我们实现了finally中f.close\n\n\n## 上下文管理器\n\n上下文管理器协议包含`__enter__ 和 __exit__` 两个方法。\n\nwith 语句开始运行时，会在上下文管理器对象上调用 `__enter__ `方法。\nwith 语句运行结束后，会在上下文管理器对象上调用` __exit__ `方法，以此扮演 finally 子句的角色。\n\n最常见的例子是确保关闭文件对象\n```python\nclass T(object):\n    def __enter__(self):\n        print('T.__enter__')\n        return '我是__enter__的返回值'\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        print('T.__exit__')\n\n\nwith T() as t:\n    print(t)\n```\n\n返回结果：\n```\nT.__enter__\n我是__enter__的返回值\nT.__exit__\n```\n\n\nwith之于上下文管理器，就像for之于迭代器一样。with就是为了方便上下文管理器的使用。\n\n\n## @contextmanager 装饰器\n\n\n下文管理器就不得不提一下@contextmanager 装饰器，它能减少创建上下文管理器的样板代码量，\n\n因为不用编写一个完整的类，定义` __enter__和 __exit__ `方法，而只需实现有一个 yield 语句的生成器，生成想让` __enter__ `方法返回的值。\n\n\n@contextmanager 装饰器优雅且实用，把三个不同的 Python 特性结合到了一起：**函数装饰器、生成器和 with 语句。**\n\n在使用 @contextmanager 装饰的生成器中，yield 语句的作用是把函数的定义体分成两部分：\n\n\t1. yield 语句前面的所有代码在 with 块开始时（即解释器调用` __enter__` 方法时）执行\n\t2. yield 语句后面的代码在with 块结束时（即调用 `__exit__` 方法时）执行。\n\n\n例子：\n```python\nimport sys\nimport contextlib\n\n\n@contextlib.contextmanager\ndef WoHa(n):\n    original_write = sys.stdout.write\n\n    def reverse_write(text):\n        original_write(text[::-1])\n\n    sys.stdout.write = reverse_write\n    yield n\n    sys.stdout.write = original_write\n    return True\n\n\nobj1 = WoHa('你手机拿反了')\nwith obj1 as content:\n    print('哈哈镜花缘')\n    print(content)\n\nprint('#### with 执行完毕后，在输出content: ####')\nprint(content)\n\n```\n\n\n返回结果：\n```\n缘花镜哈哈\n了反拿机手你\n#### with 执行完毕后，在输出content: ####\n你手机拿反了\n```\n\n\n这里我们需要注意的是：\n\n代码执行到yield时，会产出一个值，这个值会绑定到 with 语句中 as 子句的变量上。\n执行 with 块中的代码时，这个函数会在yield这里暂停。\n"
  },
  {
    "path": "notes/Python/流畅的Python/使用asyncio包处理并发.md",
    "content": "# 第18章 使用asyncio包处理并发\n\n<!-- TOC -->\n\n- [第18章 使用asyncio包处理并发](#第18章-使用asyncio包处理并发)\n    - [主线程与子线程](#主线程与子线程)\n    - [守护线程](#守护线程)\n    - [线程同步](#线程同步)\n    - [asyncio介绍](#asyncio介绍)\n        - [关键组件说明](#关键组件说明)\n        - [回调](#回调)\n    - [asyncio与gevent关系](#asyncio与gevent关系)\n    - [asyncio与Flask](#asyncio与flask)\n        - [aiohttp](#aiohttp)\n        - [与单进程、多进程对比](#与单进程多进程对比)\n\n<!-- /TOC -->\n\n\n## 主线程与子线程\n\n\n当一个进程启动之后，会默认产生一个主线程，因为线程是程序执行流的最小单元。\n\n当设置多线程时，主线程会创建多个子线程，在python中，默认情况下（其实就是`setDaemon(False)`），主线程执行完自己的任务以后，就退出了。\n\n此时子线程会继续执行自己的任务，直到自己的任务结束。\n\n\n例子：\n```python\nimport time\nimport threading\n\n\ndef run():\n    time.sleep(2)\n    print('当前线程名称是:%s' % threading.currentThread().name)\n    time.sleep(2)\n\n\nif __name__ == \"__main__\":\n\n    start_time = time.time()\n    print('这是主线程:%s' % threading.current_thread().name)\n\n    thread_list = []\n\n    for i in range(5):\n        t = threading.Thread(target=run)\n        thread_list.append(t)\n\n    for t in thread_list:\n        t.start()\n\n    print('主线程结束:%s' % threading.current_thread().name)\n\nprint('一共用时:%f' % float(time.time() - start_time))\n```\n\n运行结果：\n```\n这是主线程:MainThread\n主线程结束:MainThread\n一共用时:0.002953\n当前线程名称是:Thread-2\n当前线程名称是:Thread-1\n当前线程名称是:Thread-5\n当前线程名称是:Thread-3\n当前线程名称是:Thread-4\n```\n\n\n##守护线程\n当我们使用`setDaemon(True)`方法，设置子线程为守护线程时，主线程一旦执行结束，则全部线程全部被终止执行\n\n可能出现的情况就是，子线程的任务还没有完全执行结束，就被迫停止。\n\n例子：\n```python\nimport time\nimport threading\n\n\ndef run():\n    time.sleep(2)\n    print('当前线程名称是:%s' % threading.currentThread().name)\n    time.sleep(2)\n\n\nif __name__ == \"__main__\":\n\n    start_time = time.time()\n    print('这是主线程:%s' % threading.current_thread().name)\n\n    thread_list = []\n\n    for i in range(5):\n        t = threading.Thread(target=run)\n        thread_list.append(t)\n\n    for t in thread_list:\n        t.setDaemon(True)\n        t.start()\n\n    print('主线程结束:%s' % threading.current_thread().name)\n\nprint('一共用时:%f' % float(time.time() - start_time))\n```\n\n运行结果：\n```\n这是主线程:MainThread\n主线程结束:MainThread\n一共用时:0.002954\n```\n\n\n## 线程同步\n\njoin所完成的工作就是线程同步，即主线程任务结束之后，进入阻塞状态，一直等待其他的子线程执行结束之后，主线程在终止\n\n例子：\n```python\nimport time\nimport threading\n\n\ndef run():\n    time.sleep(2)\n    print('当前线程名称是:%s' % threading.currentThread().name)\n    time.sleep(2)\n\n\nif __name__ == \"__main__\":\n\n    start_time = time.time()\n    print('这是主线程:%s' % threading.current_thread().name)\n\n    thread_list = []\n\n    for i in range(5):\n        t = threading.Thread(target=run)\n        thread_list.append(t)\n\n    for t in thread_list:\n        t.start()\n        t.join()\n\n    print('主线程结束:%s' % threading.current_thread().name)\n\nprint('一共用时:%f' % float(time.time() - start_time))\n```\n\n运行结果：\n```\n这是主线程:MainThread\n当前线程名称是:Thread-1\n当前线程名称是:Thread-2\n当前线程名称是:Thread-3\n当前线程名称是:Thread-4\n当前线程名称是:Thread-5\n主线程结束:MainThread\n一共用时:20.015406\n```\n\n\n\n\n##asyncio介绍\n\nasyncio的编程模型就是一个`消息循环`, 异步非阻塞的协程\n\n\n### 关键组件说明\n\nasyncio使用了与以往python用法完全不同的构造：`事件循环、协程和futures`\n\n\n关于asyncio的一些关键字的说明：\n\n>* event_loop 事件循环：程序开启一个无限循环，把一些函数注册到事件循环上，当满足事件发生的时候，调用相应的协程函数\n\n>* coroutine 协程：协程对象，指一个使用async关键字定义的函数，它的调用不会立即执行函数，而是会返回一个协程对象。协程对象需要注册到事件循环，由事件循环调用。\n\n>* task 任务：一个协程对象就是一个原生可以挂起的函数，任务则是对协程进一步封装，其中包含了任务的各种状态\n\n>* future: 代表将来执行或没有执行的任务的结果。它和task上没有本质上的区别\n\t\n>* async/await 关键字：python3.5用于定义协程的关键字，async定义一个协程，await用于挂起阻塞的异步调用接口。\n\n\n例子：\n\n\n\n```python\nimport asyncio\nimport datetime\n\n\n# 写法1\n# @asyncio.coroutine\n# def hello():\n\n#     print('hello world')\n#     r = yield from asyncio.sleep(1)\n\n# print('hello again')\n\n# 写法2\nasync def hello():\n    print('hello world')\n    await asyncio.sleep(1)\n    print('hello again')\n\n\nif __name__ == \"__main__\":\n\n    loop = asyncio.get_event_loop()\n    task = loop.create_task(hello())\n\n    print(datetime.datetime.now())\n\n    # print(task)\n    loop.run_until_complete(task)\n\n    # print(task)\n    print(datetime.datetime.now())\n    loop.close()\n```\n\n\n\n协程对象不能直接运行，在注册事件循环的时候，其实是run_until_complete方法将协程包装成为了一个任务（task）对象。\n\n所谓task对象是Future类的子类。保存了协程运行后的状态，用于未来获取协程的结果。\n\n在通过loop.create_task(hello())的时候，任务其实是处于pending状态。\n\n在hello中通过asyncio.sleep(1)耗时一秒最后任务执行完后状态变为done.\n\nasyncio.ensure_future(coroutine) 和 loop.create_task(coroutine)都可以创建一个task，run_until_complete的参数是一个futrue对象。\n\n当传入一个协程，其内部会自动封装成task，task是Future的子类\n\n\n\n再来看一个例子：\n```python\nimport asyncio\nimport random\n\n\nasync def MyCoroutine(id):\n    process_time = random.randint(1, 5)\n    # 使用asyncio.sleep模拟一些耗时的操作\n    await asyncio.sleep(process_time)\n    print(\"协程: {}, 执行完毕。用时： {} 秒\".format(id, process_time))\n\n\nasync def main():\n    # ensure_future方法 接收协程或者future作为参数，作用是排定他们的执行时间\n    tasks = [asyncio.ensure_future(MyCoroutine(i)) for i in range(10)]\n\n    # 返回结果\n    await asyncio.gather(*tasks)\n\n\n# 事件循环\nloop = asyncio.get_event_loop()\ntry:\n    loop.run_until_complete(main())\nfinally:\n    loop.close()\n```\n\n运行结果：\n```\n协程: 7, 执行完毕。用时： 1 秒\n协程: 9, 执行完毕。用时： 1 秒\n协程: 5, 执行完毕。用时： 2 秒\n协程: 0, 执行完毕。用时： 4 秒\n协程: 6, 执行完毕。用时： 4 秒\n协程: 2, 执行完毕。用时： 4 秒\n协程: 3, 执行完毕。用时： 4 秒\n协程: 8, 执行完毕。用时： 4 秒\n协程: 4, 执行完毕。用时： 4 秒\n协程: 1, 执行完毕。用时： 4 秒\n```\n\n\n从输出结果可以看出两点：\n1.协程并没有按照顺序返回结果；\n2.批量运行的任务所用的时间和所有任务中用时最长的相同。\n\n\n\n\n### 回调\n\n```python\nimport asyncio\nimport requests\n\nasync def request():\n    url = 'https://www.baidu.com'\n    status = requests.get(url)\n    return status\n\ndef callback(task):\n    print('Status:', task.result())\n\ncoroutine = request()\ntask = asyncio.ensure_future(coroutine)\ntask.add_done_callback(callback)\nprint('Task:', task)\n\nloop = asyncio.get_event_loop()\nloop.run_until_complete(task)\nprint('Task:', task)\n```\n\n也可以不用回调:\n```python\nimport asyncio\nimport requests\n\n\nasync def request():\n    url = 'https://www.baidu.com'\n    status = requests.get(url)\n    return status\n\n\ncoroutine = request()\ntask = asyncio.ensure_future(coroutine)\nprint('Task:', task)\n\nloop = asyncio.get_event_loop()\nloop.run_until_complete(task)\nprint('Task:', task)\nprint('Task Result:', task.result())\n\n```\n\n## asyncio与gevent关系\n\n\ngevent是第三方库，通过greenlet实现协程\n\n其基本思路是：\n当一个greenlet遇到IO操作时，就自动切换到其他的greenlet，等到IO操作完成，再在适当的时候切换回来继续执行。\n\n\nasyncio是Python 3.4版本引入的标准库，直接内置了对异步IO的支持，不需要第三方的支持，\n\nasyncio的编程模型就是一个消息循环。\n\n我们从asyncio模块中直接获取一个EventLoop的引用，然后把需要执行的协程扔到EventLoop中执行，就实现了异步IO。\n\n很多异步io操作这两个库都可以用，只是他们在不同场景下的效率和易用性可能有区别，当然这个得进行深入的测试和研究，单就现在普通的场景来说 区别并不大\n\n\n\n## asyncio与Flask\n\n首先使用flask编写一个web服务器\n```python\nfrom flask import Flask\nimport time\n\napp = Flask(__name__)\n\n@app.route('/')\ndef index():\n    time.sleep(3)\n    return 'Hello!'\n\nif __name__ == '__main__':\n    app.run(threaded=True)\n```\n\n这里run() 方法加了一个参数 threaded，这表明 Flask 启动了多线程模式，不然默认是只有一个线程的。\n\n如果不开启多线程模式，同一时刻遇到多个请求的时候，只能顺次处理，这样即使我们使用协程异步请求了这个服务，也只能一个一个排队等待，瓶颈就会出现在服务端。\n\n所以，多线程模式是有必要打开的。\n\n\n程序运行后会开启一个web, 端口默认5000\n\n使用asyncio模块进行测试\n```python\nimport asyncio\nimport requests\nimport time\n\nstart = time.time()\n\nasync def request():\n    url = 'http://127.0.0.1:5000'\n    print('Waiting for', url)\n    response = requests.get(url)\n    print('Get response from', url, 'Result:', response.text)\n\ntasks = [asyncio.ensure_future(request()) for _ in range(5)]\nloop = asyncio.get_event_loop()\nloop.run_until_complete(asyncio.wait(tasks))\n\nend = time.time()\nprint('Cost time:', end - start)\n```\n\n\n运行结果：\n```\nWaiting for http://127.0.0.1:5000\nGet response from http://127.0.0.1:5000 Result: Hello!\nWaiting for http://127.0.0.1:5000\nGet response from http://127.0.0.1:5000 Result: Hello!\nWaiting for http://127.0.0.1:5000\nGet response from http://127.0.0.1:5000 Result: Hello!\nWaiting for http://127.0.0.1:5000\nGet response from http://127.0.0.1:5000 Result: Hello!\nWaiting for http://127.0.0.1:5000\nGet response from http://127.0.0.1:5000 Result: Hello!\nCost time: 15.0814049243927\n```\n\n可以发现和正常的请求并没有什么两样，依然还是顺次执行的，耗时 15 秒，平均一个请求耗时 3 秒，说好的异步处理呢？\n\n其实，要实现异步处理，我们得先要有挂起的操作，当一个任务需要等待 IO 结果的时候，可以挂起当前任务，转而去执行其他任务，这样我们才能充分利用好资源，上面方法都是一本正经的串行走下来，连个挂起都没有，怎么可能实现异步？\n\n要实现异步，接下来我们再了解一下 await 的用法，使用 await 可以将耗时等待的操作挂起，让出控制权。\n\n当协程执行的时候遇到 await，时间循环就会将本协程挂起，转而去执行别的协程，直到其他的协程挂起或执行完毕。\n\n改进后代码：\n```python\nimport asyncio\nimport requests\nimport time\n\nstart = time.time()\n\n\n# 这里增加异步get\n# 否则会报错：TypeError: object Response can't be used in 'await' expression\nasync def get(url):\n    return requests.get(url)\n\n\nasync def request():\n    url = 'http://127.0.0.1:5000'\n    print('Waiting for', url)\n    response = await get(url)  # 修改这里增加await\n    print('Get response from', url, 'Result:', response.text)\n\n\ntasks = [asyncio.ensure_future(request()) for _ in range(5)]\nloop = asyncio.get_event_loop()\nloop.run_until_complete(asyncio.wait(tasks))\n\nend = time.time()\nprint('Cost time:', end - start)\n```\n\n运行结果：\n```\nWaiting for http://127.0.0.1:5000\nGet response from http://127.0.0.1:5000 Result: Hello!\nWaiting for http://127.0.0.1:5000\nGet response from http://127.0.0.1:5000 Result: Hello!\nWaiting for http://127.0.0.1:5000\nGet response from http://127.0.0.1:5000 Result: Hello!\nWaiting for http://127.0.0.1:5000\nGet response from http://127.0.0.1:5000 Result: Hello!\nWaiting for http://127.0.0.1:5000\nGet response from http://127.0.0.1:5000 Result: Hello!\nCost time: 15.083374977111816\n```\n\n还是不行，它还不是异步执行，也就是说我们仅仅将涉及 IO 操作的代码封装到 async 修饰的方法里面是不可行的！\n\n我们必须要使用支持**异步操作的请求方式**才可以实现真正的异步，所以这里就需要 aiohttp 派上用场了.\n\n\n### aiohttp\n\n首先执行下面命令进行安装这个模块：\n\n```python\npip install aiohttp\n```\n\n\n例子：\n```python\nimport asyncio\nimport aiohttp\nimport time\n\nstart = time.time()\n\nasync def get(url):\n    session = aiohttp.ClientSession()\n    response = await session.get(url)\n    result = await response.text()\n    session.close()\n    return result\n\nasync def request():\n    url = 'http://127.0.0.1:5000'\n    print('Waiting for', url)\n    result = await get(url)\n    print('Get response from', url, 'Result:', result)\n\ntasks = [asyncio.ensure_future(request()) for _ in range(5)]\nloop = asyncio.get_event_loop()\nloop.run_until_complete(asyncio.wait(tasks))\n\nend = time.time()\nprint('Cost time:', end - start)\n```\n\n运行结果：\n```\nxx\nCost time: 3.056924819946289\n```\n\n\n我们发现这次请求的耗时由 15 秒变成了 3 秒，耗时直接变成了原来的 1/5\n\n这就是异步操作的便捷之处，当遇到阻塞式操作时，任务被挂起，程序接着去执行其他的任务，而不是傻傻地等着，这样可以充分利用 CPU 时间，而不必把时间浪费在等待 IO 上\n\n\n这里将 task 数量设置成 100 ,发现结果也是3秒\n```\nCost time: 3.4409260749816895\n```\n\n### 与单进程、多进程对比\n\n\n单进程代码：\n```python\nimport requests\nimport time\n\nstart = time.time()\n\n\ndef request():\n    url = 'http://127.0.0.1:5000'\n    print('Waiting for', url)\n    result = requests.get(url).text\n    print('Get response from', url, 'Result:', result)\n\n\nfor _ in range(100):\n    request()\n\nend = time.time()\nprint('Cost time:', end - start)\n```\n\n运行结果：\n```\nCost time: 301.17162680625916\n```\n\n\n\n\n多进程版本：\n\n\n```python\nimport requests\nimport time\nimport multiprocessing\n\nstart = time.time()\n\n\ndef request(_):\n    url = 'http://127.0.0.1:5000'\n    print('Waiting for', url)\n    result = requests.get(url).text\n    print('Get response from', url, 'Result:', result)\n\n\nif __name__ == \"__main__\":  # 这一行在windows下执行一定要添加，否则会报错\n\n    cpu_count = multiprocessing.cpu_count()\n    print('Cpu count:', cpu_count)\n    pool = multiprocessing.Pool(cpu_count)\n\n    pool.map(request, range(100))\n\n    end = time.time()\n    print('Cost time:', end - start)\n```\n\n运行结果：\n```\nCost time: 48.85933017730713\n```\n\n\n这里想查看cpu个数也可以使用：\n```python\n import psutil\n psutil.cpu_count()\n```"
  },
  {
    "path": "notes/Python/流畅的Python/使用future处理并发.md",
    "content": "# 第17章 使用future处理并发\n\n<!-- TOC -->\n\n- [第17章 使用future处理并发](#%e7%ac%ac17%e7%ab%a0-%e4%bd%bf%e7%94%a8future%e5%a4%84%e7%90%86%e5%b9%b6%e5%8f%91)\n  - [futures模块](#futures%e6%a8%a1%e5%9d%97)\n    - [多线程模式ThreadPoolExecutor](#%e5%a4%9a%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%bc%8fthreadpoolexecutor)\n    - [多进程模式ProcessPoolExecutor](#%e5%a4%9a%e8%bf%9b%e7%a8%8b%e6%a8%a1%e5%bc%8fprocesspoolexecutor)\n    - [深入原理](#%e6%b7%b1%e5%85%a5%e5%8e%9f%e7%90%86)\n\n<!-- /TOC -->\n## futures模块\n\nPython3引入的concurrent.futures模块。concurrent.futures 是python3新增加的一个库，用于并发处理，提供了多线程和多进程的并发功能 类似于其他语言里的线程池（也有一个进程池），他属于上层的封装，对于用户来说，不用在考虑那么多东西了\n\n\nconcurrent提供了两种并发模型，一个是多线程ThreadPoolExecutor，一个是多进程ProcessPoolExecutor。对于IO密集型任务宜使用多线程模型。对于计算密集型任务应该使用多进程模型。\n\n\n### 多线程模式ThreadPoolExecutor\n多线程模式适合IO密集型运算，这里我要使用sleep来模拟一下慢速的IO任务\n这里使用Google fire开源库来简化命令行参数处理\n\n\n```python\nimport time\nimport fire\nimport threading\nfrom concurrent.futures import ThreadPoolExecutor, wait\n\n\n# 分割子任务\ndef each_task(index):\n    time.sleep(1)  # 睡1s，模拟IO\n    print(\"thread %d square %d\" % (threading.current_thread().ident, index))\n    return index * index  # 返回结果\n\n\ndef run(thread_num, task_num):\n    # 实例化线程池，thread_num个线程\n    executor = ThreadPoolExecutor(thread_num)\n    start = time.time()\n\n    fs = []  # future列表\n    for i in range(task_num):\n        fs.append(executor.submit(each_task, i))  # 提交任务\n\n    wait(fs)  # 等待计算结束\n    end = time.time()\n    duration = end - start\n\n    s = sum([f.result() for f in fs])  # 求和\n    print(\"total result=%d cost: %s\" % (s, duration))\n    executor.shutdown()  # 销毁线程池\n\n\nif __name__ == '__main__':\n    fire.Fire(run)\n```\n\n执行返回结果：\n```\npython 1.py 2 10 # 也就是2个线程跑10个任务\nthread 5216 square 0\nthread 5856 square 1\nthread 5216 square 2\nthread 5856 square 3\nthread 5216 square 4\nthread 5856 square 5\nthread 5216 square 6\nthread 5856 square 7\nthread 5856 square 9\nthread 5216 square 8\ntotal result=285 cost: 5.06045389175415\n```\n\n为什么输出乱了，这是因为print操作不是原子的，它是两个连续的write操作合成的，第一个write输出内容，第二个write输出换行符，write操作本身是原子的，但是在多线程环境下，这两个write操作会交错执行，所以输出就不整齐了。如果将代码稍作修改，将print改成单个write操作，输出就整齐了  ---`发现改了还是不行`\n\n\n\n调大参数看看效果：\n```\npython 1.py 10 10\nthread 4792 square 9\nthread 10380 square 6\nthread 14420 square 7\nthread 5720 square 3\nthread 13808 square 4\nthread 17344 square 1\nthread 9172 square 5\nthread 10684 square 8\nthread 11696 square 0\nthread 6300 square 2\ntotal result=285 cost: 1.05\n```\n\n可以看到1s中就完成了所有的任务。这就是多线程的魅力，可以将多个IO操作并行化，减少整体处理时间。\n\n\n\n### 多进程模式ProcessPoolExecutor\n\n相比多线程适合处理IO密集型任务，多进程适合计算密集型。\n\n```python\nimport os\nimport sys\nimport math\nimport time\nimport fire\nfrom concurrent.futures import ProcessPoolExecutor, wait\n\n\n# 分割子任务\ndef each_task(n):\n    # 按公式计算圆周率\n    s = 0.0\n    for i in range(n):\n        s += 1.0 / (i + 1) / (i + 1)\n    pi = math.sqrt(6 * s)\n    # os.getpid可以获得子进程号\n    sys.stdout.write(\"process %s n=%d pi=%s\\n\" % (os.getpid(), n, pi))\n    return pi\n\n\ndef run(process_num, *ns):  # 输入多个n值，分成多个子任务来计算结果\n    # 实例化进程池，process_num个进程\n    executor = ProcessPoolExecutor(process_num)\n    start = time.time()\n    fs = []  # future列表\n    for n in ns:\n        fs.append(executor.submit(each_task, int(n)))  # 提交任务\n    wait(fs)  # 等待计算结束\n    end = time.time()\n    duration = end - start\n    print(\"total cost: %.2f\" % duration)\n    executor.shutdown()  # 销毁进程池\n\n\nif __name__ == '__main__':\n    fire.Fire(run)\n```\n\n执行返回结果：\n```\npython 1.py  1 5000000 5001000 5002000 5003000\nprocess 10024 n=5000000 pi=3.141592462603821\nprocess 10024 n=5001000 pi=3.141592462641988\nprocess 10024 n=5002000 pi=3.1415924626801544\nprocess 10024 n=5003000 pi=3.141592462718321\ntotal cost: 4.62\n```\n\n增大一个进程看看效果：\n```\npython 1.py 2 5000000 5001000 5002000 5003000\nprocess 5860 n=5000000 pi=3.141592462603821\nprocess 14948 n=5001000 pi=3.141592462641988\nprocess 14948 n=5003000 pi=3.141592462718321\nprocess 5860 n=5002000 pi=3.1415924626801544\ntotal cost: 2.78\n```\n\n从耗时上看缩短了接近1半，说明多进程确实起到了计算并行化的效果\n\n\n### 深入原理\n\n\n[Python最广为使用的并发库futures使用入门与内部原理](https://mp.weixin.qq.com/s/NBBDou4rIMo9KibVb4fjeg)"
  },
  {
    "path": "notes/Python/流畅的Python/使用一等函数实现设计模式.md",
    "content": "# 第6章 使用一等函数实现设计模式\n\n<!-- TOC -->\n\n- [第6章 使用一等函数实现设计模式](#第6章-使用一等函数实现设计模式)\n    - [单例模式](#单例模式)\n        - [为什么](#为什么)\n        - [是什么](#是什么)\n        - [怎么用](#怎么用)\n\n<!-- /TOC -->\n设计模式\n每个设计模式都是围绕如下三个问题：\n\n>1.为什么？即为什么要使用这个设计模式，在使用这个模式之前存在什么样的问题？\n\n>2.是什么？通过Python语言来去实现这个设计模式，用于解决为什么中提到的问题。\n\n>3.怎么用？理解了为什么我们也就基本了解了什么情况下使用这个模式，不过在这里还是会细化使用场景，阐述模式的局限和优缺点\n\n\n## 单例模式\n这一篇我们先来看看单例模式。单例模式是设计模式中逻辑最简单，最容易理解的一个模式，简单到只需要一句话就可以理解，即**`保证只有一个对象实例的模式`**。\n\n问题的关键在于实现起来并没有想象的那么简单。\n\n不过我们还是先来讨论下为什么需要这个模式吧。\n\n### 为什么\n\n我们首先来看看单例模式的使用场景，然后再来分析为什么需要单例模式。\n*  **Python的logger就是一个单例模式，用以日志记录**\n* **Windows的资源管理器是一个单例模式**\n* **线程池，数据库连接池等资源池一般也用单例模式**\n* **网站计数器**\n\n<br>\n\n从这些使用场景我们可以总结下什么情况下需要单例模式：\n1.当每个实例都会占用资源，而且实例初始化会影响性能，这个时候就可以考虑使用单例模式，**`它给我们带来的好处是只有一个实例占用资源，并且只需初始化一次；`**\n\n2.当有同步需要的时候，可以通过一个实例来进行同步控制，比如对某个共享文件（如日志文件）的控制，对计数器的同步控制等，这种情况下由于只有一个实例，所以不用担心同步问题。\n\n<br>\n当然所有使用单例模式的前提是我们的确用一个实例就可以搞定要解决的问题，而不需要多个实例，如果每个实例都需要维护自己的状态，这种情况下单例模式肯定是不适用的。\n\n\n接下来看看如何使用Python来实现一个单例模式。\n\n### 是什么\n\n最开始的想法很简单，实现如下：\n\n```python\nclass Singleton(object):\n    __instance = None\n\n    def __init__(self):\n        pass\n\n    def __new__(cls, *args, **kwd):\n        if Singleton.__instance is None:\n            Singleton.__instance = object.__new__(cls, *args, **kwd)\n        return Singleton.__instance\n\n\ns1 = Singleton()\ns2 = Singleton()\nprint(s1)\nprint(s2)\n\nif s1 == s2:\n    print(\"True\")\nelse:\n    print(\"False\")\n\n```\n\n运行结果是：\n```\n<__main__.Singleton object at 0x000002176FE884A8>\n<__main__.Singleton object at 0x000002176FE884A8>\nTrue\n\n```\n\n### 怎么用\n\n在 Python 中，我们可以用多种方法来实现单例模式：\n\n> 使用模块\n使用 `__new__`\n使用装饰器（decorator）\n使用元类（metaclass）\n\n\n1.使用模块\n其实，Python 的模块就是天然的单例模式，因为模块在第一次导入时，会生成 .pyc 文件，当第二次导入时，就会直接加载 .pyc 文件，而不会再次执行模块代码。\n\n因此，我们只需把相关的函数和数据定义在一个模块中，就可以获得一个单例对象了。\n如果我们真的想要一个单例类，可以考虑这样做：\n\n```python\n# mysingleton.py\nclass My_Singleton(object):\n    def foo(self):\n        pass\n\n\nmy_singleton = My_Singleton()\n\n# 将上面的代码保存在文件 mysingleton.py 中，然后这样使用：\n\nfrom mysingleton import my_singleton\nmy_singleton.foo()\n\n```\n<br>\n\n2.使用`__new__`方法\n为了使类只能出现一个实例，我们可以使用 __new__ 来控制实例的创建过程，代码如下：\n\n```python\nclass Singleton(object):\n    _instance = None\n\n    def __new__(cls, *args, **kw):\n        if not cls._instance:\n            cls._instance = super(Singleton, cls).__new__(cls, *args, **kw)\n        return cls._instance\n\n\nclass MyClass(Singleton):\n    a = 1\n\n\none = MyClass()\ntwo = MyClass()\n\nprint(one == two)\nprint(one is two)\nprint(id(one), id(two))\n```\n\n运行结果：\n```\nTrue\nTrue\n2483856443152 2483856443152\n```\n<br>\n3.使用装饰器\n我们知道，装饰器（decorator）可以动态地修改一个类或函数的功能。这里，我们也可以使用装饰器来装饰某个类，使其只能生成一个实例，代码如下：\n\n```python\nfrom functools import wraps\n\n\ndef singleton(cls):\n    instances = {}\n\n    @wraps(cls)\n    def getinstance(*args, **kw):\n        if cls not in instances:\n            instances[cls] = cls(*args, **kw)\n        return instances[cls]\n\n    return getinstance\n\n\n@singleton\nclass MyClass(object):\n    a = 1\n\n\none = MyClass()\ntwo = MyClass()\n\nprint(one == two)\n\n```\n\n在上面，我们定义了一个装饰器 singleton，它返回了一个内部函数 getinstance，该函数会判断某个类是否在字典 instances 中，\n\n如果不存在，则会将 cls 作为 key，cls(*args, **kw) 作为 value 存到 instances 中，否则，直接返回 instances[cls]。\n\n<br>\n4.使用元类\n\n元类（metaclass）可以控制类的创建过程，它主要做三件事：\n\n拦截类的创建\n修改类的定义\n返回修改后的类\n使用元类实现单例模式的代码如下：\n\n```python\nclass Singleton(type):\n    _instances = {}\n\n    def __call__(cls, *args, **kwargs):\n        if cls not in cls._instances:\n            cls._instances[cls] = super(Singleton,\n                                        cls).__call__(*args, **kwargs)\n        return cls._instances[cls]\n\n\n# # Python2\n# class MyClass(object):\n#     __metaclass__ = Singleton\n\n\n#Python3\nclass MyClass(metaclass=Singleton):\n    pass\n\n\none = MyClass()\ntwo = MyClass()\n\nprint(one == two)\n\n```\n"
  },
  {
    "path": "notes/Python/流畅的Python/函数.md",
    "content": "# 第5章 函数\n\n<!-- TOC -->\n\n- [第5章 函数](#第5章-函数)\n    - [函数](#函数)\n    - [高阶函数](#高阶函数)\n        - [map函数](#map函数)\n        - [filter函数](#filter函数)\n        - [reduce函数](#reduce函数)\n        - [lambda 函数](#lambda-函数)\n        - [sorted函数](#sorted函数)\n    - [`__call__`方法](#__call__方法)\n    - [函数注解](#函数注解)\n    - [冻结参数的函数](#冻结参数的函数)\n\n<!-- /TOC -->\n\n\n## 函数\n\n在python中一切都可以视作为对象，包括函数\n\n```python\ndef function_try():\n    '''it is funciton try doc'''\n\n    print('function_try doc')\n\n\nif __name__ == \"__main__\":\n\n    # __doc__属性用于生成对象的帮助文本\n    print(function_try.__doc__)\n    # __name__则是输出了具体的函数名\n    print(function_try.__name__)\n```\n<br>\n我们还可以将函数名赋值给变量，通过变量的形式来访问。\n\n```python\ndef function_try():\n    '''it is funciton try doc'''\n\n    print('function_try doc')\n\n\nif __name__ == \"__main__\":\n\n    fun = function_try\n\n    # __doc__属性用于生成对象的帮助文本\n    print(fun.__doc__)\n    # __name__则是输出了具体的函数名\n    print(fun.__name__)\n```\n<br>\n函数也可以通过参数被传递给另外一个函数。\n\n```python\ndef function_try():\n    '''it is funciton try doc'''\n\n    print('function_try doc')\n\n\ndef function_try1(func):\n    print(func.__doc__)\n    print(func.__name__)\n    func()\n\n\nif __name__ == \"__main__\":\n\n    fun = function_try\n    function_try1(fun)\n\n```\n\n## 高阶函数\n\n>**接受函数为参数，或者把函数作为结果返回的函数是高阶函数**\n\n###  map函数\n```python\ndef cube(x):\n    return x * x\n\n\ndata = map(cube, range(1, 11))\nprint(list(data)) # 此处要加list\n\n\ndef add_num(n):\n    return n + 1\n\n\nprint(list(map(add_num, range(1, 6))))\n```\n\n再看几个例子：\n```python\nresult = ['1', '2', '3']\n\nprint(list(map(int, result)))  # [1, 2, 3]\n\n# 也可以用列表推导式\nprint([int(i) for i in result])\n\n```\n\n```python\nt = ((1, 'a'), (2, 'b'))\nprint(type(t))\nprint(dict(t))\nprint(dict((y, x) for x, y in t))\n\n# map方法\n# print(dict(map(None, t)))  # 这个python3已废弃\nprint(dict(map(reversed, t)))\n\n```\n\n\n### filter函数\nFilter的作用在于根据第一个参数为true还是false来进行过滤。\n下面例子中将大于3的数值过滤出来。\n```python\ns = [1, 2, 3, 4, 5]\n\nprint(list(filter(lambda x: x > 3, s)))\n# [4, 5]\n```\n\n### reduce函数\n就是将参数累加的应用于函数中。就好比我们要对1到100进行求值。\n代码如下。效果等同于sum(range(100))\n```python\n# python3 已经将reduce移到functools模块中\nfrom functools import reduce\nfrom operator import add\n\nprint(reduce(add, range(100)))\n\n```\n\n### lambda 函数\n这是Python支持一种有趣的语法，`它允许你快速定义单行的最小函数`\n例子：\n```python\ng = lambda x: x * 2\nprint(g(3))\n\n# 相等于下面的表达式\nprint((lambda x: x * 2)(3))\n\nD = {'jack': 23, 'rose': 21, 'flank': 22}\n\nvalue_sort = sorted(D.items(), key=lambda d: d[1])  # 值(value)排序\nprint(value_sort)\n# [('rose', 21), ('flank', 22), ('jack', 23)]\n\nkey_sort = sorted(D.items(), key=lambda d: d[0])  # 按键(key)排序按\nprint(key_sort)\n# [('flank', 22), ('jack', 23), ('rose', 21)]\n\nkey_reverse = sorted(D.items(), key=lambda d: d[0], reverse=True)  # 按键(key)降序\nprint(key_reverse)\n# [('rose', 21), ('jack', 23), ('flank', 22)]\n\n# 值(value)降序\nvalue_reverse = sorted(D.items(), key=lambda d: d[1], reverse=True)\nprint(value_reverse)\n# [('jack', 23), ('flank', 22), ('rose', 21)]\n\n```\npython lambda是在python中使用lambda来创建匿名函数，而用def创建的方法是有名称的，除了从表面上的方法名不一样外，python lambda还有哪些和def不一样呢？\n\n>1 **python lambda会创建一个函数对象，但不会把这个函数对象赋给一个标识符，而def则会把函数对象赋值给一个变量**。\n\n>2 **python lambda它只是一个表达式，而def则是一个语句**。\n\n下面是python lambda的格式，看起来好精简阿。\n```python\nlambda x: print x\n```\n\nlambda表达式在“：”后只能有一个表达式。\n也就是说，在 def中，用return可以返回的也可以放在lambda后面，不能用return返回的也不能定义在python lambda后面。\n\n因此，像if或for或print这种语句就不能用于lambda中，lambda一般只用来定义简单的函数。\n\n下面举几个python lambda的例子吧\n1. 单个参数的：\n```python\ng = lambda x:x*2\nprint(g(3))\n# 结果是6\n```\n2.多个参数的：\n```python \nm = lambda x,y,z: (x-y)*z\nprint(m(3,1,2))\n\n#结果是4\n``` \n\n\n### sorted函数\n\n**`sorted是另外返回一个排序后的列表，原列表fruits并没有发生改变`**\n\n```python\nfruits = ['strawberry', 'fig', 'apple', 'cherry', 'rasberry', 'banana']\n\nret = sorted(fruits, key=len)\n\nprint('before sorted %s' % fruits)\n# before sorted ['strawberry', 'fig', 'apple', 'cherry', 'rasberry', 'banana']\n\nprint('after sorted %s' % ret)\n# after sorted ['fig', 'apple', 'cherry', 'banana', 'rasberry', 'strawberry']\n\n```\n\n与它很相像的`sort函数`，看下面的例子：\n```python\na = [5, 4, 3, 2, 1]\n\nsorted(a)  # 将a从大到小排序,不影响a本身结构\nprint(\"a=\", a)  # a= [5, 4, 3, 2, 1]\n\na.sort()  # 将a从小到大排序,影响a本身结构\nprint(a)  # [1, 2, 3, 4, 5]\n\nb = a.sort()\nprint(\"b=\", b)  # b= None\n\nc = ['aa', 'bb', 'BB', 'CC', 'zz']\nprint(sorted(c))\n# 按列表中元素每个字母的ascii码从小到大排序\n# 如果要从大到小,请用sorted(b,reverse=True)\n# ['BB', 'CC', 'aa', 'bb', 'zz']\n```\n\n## `__call__`方法\n\n前面介绍到可以把函数当做对象。那么我们可以像调用函数那样调用类么。\n答案是肯定的。\n\n只需要我们重写类中的`__call__`就可以了\n\n```python\nclass function_try(object):\n    def __init__(self, value):\n        self.data = value\n\n    def __call__(self, *args, **kwargs):\n        print('function try was called')\n\n        for a in args:\n            print(a)\n\n\nif __name__ == \"__main__\":\n\n    f = function_try(3)\n    f('hello', 'world')\n```\n\n\n除了__doc__, __name__这些函数还有很多属性。可以用**dir(func)**的方法进行查看\n\n## 函数注解\n\nPython 3提供了一种句法，用于为函数声明中的参数和返回值附加元数据\nPython不做检查、不做强制、不做验证，什么操作都不做\n\n```python\n# def test(text, max_len=5):\n# 下面是python3 新写法\ndef test(text: str, max_len: 'int>0' = 5) -> str:\n    if len(text) > max_len:\n        return \"OK\"\n\n\ntext = \"hello world\"\n\n# print(len(text))\na = test(text)\nprint(a, type(a))\n\nprint(test.__annotations__)\n#{'text': <class 'str'>, 'max_len': 'int>0', 'return': <class 'str'>}\n\n```\n\n## 冻结参数的函数\n最后介绍一个可以冻结参数的功能。**Functions.partial.** \n\n在某些场景下，我们期望可以冻结一个参数，并将这个参数作用于其他参数。\n\n```python\nfrom functools import partial\nfrom operator import mul\n\ntriple = partial(mul, 3)  # 将第一个参数固定为3\n\nprint(triple(7))  # 7*3=21\n\nprint(list(map(triple, range(1, 10))))  # 1,10分别和3相乘\n\n```"
  },
  {
    "path": "notes/Python/流畅的Python/函数装饰器和闭包.md",
    "content": "# 第7章 函数装饰器和闭包\n\n<!-- TOC -->\n\n- [第7章 函数装饰器和闭包](#第7章-函数装饰器和闭包)\n    - [装饰器](#装饰器)\n        - [基本概念](#基本概念)\n        - [优点](#优点)\n        - [使用](#使用)\n        - [装饰器分类](#装饰器分类)\n            - [无参数装饰器 – 包装无参数函数](#无参数装饰器--包装无参数函数)\n            - [无参数装饰器 – 包装带参数函数](#无参数装饰器--包装带参数函数)\n            - [带参数装饰器 – 包装无参数函数](#带参数装饰器--包装无参数函数)\n            - [带参数装饰器 – 包装带参数函数](#带参数装饰器--包装带参数函数)\n        - [装饰器总结](#装饰器总结)\n        - [几个内置的装饰器](#几个内置的装饰器)\n            - [`@staticmethod`](#staticmethod)\n            - [`@classmethod`](#classmethod)\n            - [普通方法、类方法和静态方法的比较](#普通方法类方法和静态方法的比较)\n            - [`@property`](#property)\n    - [变量作用域](#变量作用域)\n        - [Local作用域](#local作用域)\n        - [Enclosing作用域](#enclosing作用域)\n            - [关键字nolocal](#关键字nolocal)\n        - [Global作用域](#global作用域)\n        - [build-in作用域](#build-in作用域)\n        - [python命名空间规则](#python命名空间规则)\n    - [可变数据类型和不可变数据类型](#可变数据类型和不可变数据类型)\n        - [可变数据类型：列表list和字典dict](#可变数据类型列表list和字典dict)\n        - [不可变数据类型：数值型、字符串型string和元组tuple](#不可变数据类型数值型字符串型string和元组tuple)\n        - [字符串不可变的原因](#字符串不可变的原因)\n    - [闭包](#闭包)\n        - [什么是闭包](#什么是闭包)\n        - [为什么要使用闭包](#为什么要使用闭包)\n        - [怎么使用闭包](#怎么使用闭包)\n\n<!-- /TOC -->\n## 装饰器\n\n### 基本概念\n装饰器是一个很著名的设计模式，经常被用于有切面需求的场景，较为经典的有插入日志、性能测试、事务处理, Web权限校验, Cache等。\n\n很有名的例子，就是咖啡，加糖的咖啡，加牛奶的咖啡。 本质上，还是咖啡，只是在原有的东西上，做了“装饰”，使之附加一些功能或特性。\n\n例如记录日志，需要对某些函数进行记录\n\n笨的办法，每个函数加入代码，如果代码变了，就悲催了。\n\n装饰器的办法，定义一个专门日志记录的装饰器，对需要的函数进行装饰\n\n### 优点\n抽离出大量函数中与函数功能本身无关的雷同代码并继续重用\n\n即，可以将函数“修饰”为完全不同的行为，可以有效的将业务逻辑正交分解，如用于将权限和身份验证从业务中独立出来\n\n概括的讲，**`装饰器的作用就是为已经存在的对象添加额外的功能`**\n\n### 使用\n在Python中，装饰器实现是十分方便的\n原因是：函数可以被扔来扔去。\n\n函数作为一个对象：\n\nA.可以被赋值给其他变量，可以作为返回值\n\nB.可以被定义在另外一个函数内\n\ndef:\n装饰器是一个函数,一个用来包装函数的函数，装饰器在函数申明完成的时候被调用，调用之后返回一个修改之后的函数对象，将其重新赋值原来的标识符，并永久丧失对原始函数对象的访问(申明的函数被换成一个被装饰器装饰过后的函数)\n\n当我们对某个方法应用了装饰方法后， 其实就改变了被装饰函数名称所引用的函数代码块入口点，使其重新指向了由装饰方法所返回的函数入口点。\n\n由此我们可以用decorator改变某个原有函数的功能，添加各种操作，或者完全改变原有实现。\n\n### 装饰器分类\n装饰器分为无参数decorator，有参数decorator\n\n>* 无参数decorator  生成一个新的装饰器函数\n\n>* 有参decorator     有参装饰，装饰函数先处理参数，再生成一个新的装饰器函数，然后对函数进行装饰\n\n**`装饰器有参/无参，函数有参/无参，组合共4种`**\n\n#### 无参数装饰器 – 包装无参数函数\n\n```python\ndef decorator(func):\n    print(\"hello\")\n    return func\n\n@decorator\ndef foo():\n    print(\"this is foo\")\n    return \"111\"\n\nprint(foo())\n\n# 等价于\n# foo = decorator(foo)\n# foo()\n```\n\n\n\n#### 无参数装饰器 – 包装带参数函数\n\n```python\ndef decorator_func_args(func):\n    def inner(*args, **kwargs):  # 处理传入函数的参数\n        print(\"begin\")\n        ret = func(*args, **kwargs)  # 函数调用\n        print(\"end\")\n        return ret\n    return inner\n\n@decorator_func_args\ndef foo2(a, b=2):\n    print(a, b)\n    return \"111\"\n\nprint(foo2(1))\n```\n\n#### 带参数装饰器 – 包装无参数函数\n\n```python\ndef decorator_with_params(arg_of_decorator):  # 这里是装饰器的参数\n    print(arg_of_decorator)\n    # 最终被返回的函数\n    def newDecorator(func):\n        print(func)\n        return func\n    return newDecorator\n\n@decorator_with_params(\"deco_args\")\ndef foo3():\n    print(\"Hello\")\n    return \"111\"\n\nprint(foo3())\n\n```\n\n#### 带参数装饰器 – 包装带参数函数\n```python\ndef decorator_whith_params_and_func_args(arg_of_decorator):\n    def handle_func(func):\n        def handle_args(*args, **kwargs):\n            print(\"begin\")\n            func(*args, **kwargs)\n            print(\"end\")\n            print(arg_of_decorator, func, args, kwargs)\n            return func\n        return handle_args\n    return handle_func\n\n@decorator_whith_params_and_func_args(\"123\")\ndef foo4(a, b=2):\n    print(\"Content\")\n    return \"111\"\n\nprint(foo4(1, b=3))\n\n```\n\n### 装饰器总结\n1.装饰器是用来装饰函数\n\n2.返回的是一个函数对象\n\n3.被修饰函数标识符 指向返回的函数对象\n\n4.语法糖 @deco\n\n### 几个内置的装饰器\n#### `@staticmethod`\nstaticmethod，静态方法在调用时，对类及实例一无所知\n\n仅仅是获取传递过来的参数，没有隐含的第一个参数，在Python里基本上用处不大，你完全可以用一个模块函数替换它\n\n```python\nclass Test(object):\n    # 定义类Test的属性\n    name = 'python'\n    content = '人生苦短，我用python!'\n\n    def instance_method(self):\n        print(\"是类{}的实例方法，只能被实例对象调用\".format(Test))\n\n    @staticmethod  # 静态方法，无法访问Test类的属性\n    def static_method():  # 默认没有参数？能不能给个参数？\n        print(\"Call static method ()\\n\")\n        # print(self.name, self.content) # 错误调用\n\n\n# 静态方法调用方法, 可以通过类调用，类的实例调用\nTest.static_method()\nTest().static_method()\n\n# 类的实例方法只能实例调用\nTest().instance_method()\n\n```\n\n`@staticmethod`装饰（静态方法）：**经常有一些跟类有关系的功能但在运行时又不需要实例和类参与的情况下需要用到静态方法。**\n\n **`比如更改环境变量或者修改其他类的属性等能用到静态方法`**\n \n例子：\n```python\nclass Test:\n    def __init__(self, num1, num2):\n        self.num1 = num1\n        self.num2 = num2\n\n    def plus(self):\n        result = self.num1 + self.num2\n        return result\n\n    def multiply(self):\n        result = self.num1 * self.num2\n        return result\n\n    @staticmethod\n    def sum(num1, num2):\n        s = num1 * num1 + num2 * num2\n        print(s)\n\n\nTest.sum(3, 4)\n\n```\n\n\n```python\nIND = 'ON'\n\n\nclass Kls(object):\n    def __init__(self, data):\n        self.data = data\n\n    @staticmethod\n    def checkind():\n        return IND == 'ON'\n\n    def do_reset(self):\n        if self.checkind():\n            print('Reset done for: %s' % self.data)\n\n    def set_db(self):\n        if self.checkind():\n            print('DB connection made for: %s' % self.data)\n\n\nik1 = Kls(24)\nik1.do_reset()\nik1.set_db()\n```\n\n```python\nclass Foo(object):\n    X = 1\n    Y = 2\n\n    @staticmethod\n    def averag(*mixes):\n        return sum(mixes) / len(mixes)\n\n    @staticmethod\n    def static_method():\n        return Foo.averag(Foo.X, Foo.Y)\n\n\nfoo = Foo()\n# 子类的实例继承了父类的static_method静态方法，\n# 调用该方法，还是调用的父类的方法和类属性。\nprint(foo.static_method())\n```\n\n\n总结：\n\n静态方法：staticmethod修饰，无需参数，类与实例共享。\n\n无法访问类属性、实例属性，相当于一个相对独立的方法，\n\n跟类其实没什么关系，换个角度来讲，其实就是放在一个类的作用域里的函数而已。\n\n**静态方法中引用类属性的话，必须通过类对象来引用。**\n\n\n#### `@classmethod`\n\n`@classmethod` 是一个函数修饰符，它表示接下来的是一个类方法，**类方法的第一个参数cls**\n\n```python\nclass Test(object):\n    # 定义类Test的属性\n    name = 'python'\n    content = '人生苦短，我用python!'\n\n    @classmethod  # 类方法访问Test类的属性\n    def class_method(cls):\n        # 参数cls必须要有\n        print(cls.name, cls.content)\n\n\nclass Son(Test):\n    content = \"你好\"\n    name = \"世界\"\n\n\n# 调用方法\nTest.class_method()\nTest().class_method()\n\n# 类方法有类变量cls传入，从而可以用cls做一些相关的处理。\n# 并且有子类继承时，调用该类方法时，传入的类变量cls是子类，而非父类。\n# 调用的是子类的方法和子类的类属性\nprint(\"*\" * 10)\nSon.class_method()\nSon().class_method()\n```\n\n\n#### 普通方法、类方法和静态方法的比较\n\n> 普通方法：**由对象调用。至少一个self参数**，执行普通方法时，\n自动将调用该方法的对象赋值给self\n\n>类方法：**由类调用； 至少一个cls参数**；执行类方法时，自动将调用该方法的类复制给cls\n\n>静态方法：**由类调用；无默认参数**\n\n```python\nclass Foo(object):\n    # def __init__(self, name):\n    #     self.name = name\n\n    def ord_func(self):\n        \"\"\" 定义普通方法，至少有一个self参数 \"\"\"\n\n        # print self.name\n        print('普通方法')\n\n    @classmethod\n    def class_func(cls):\n        \"\"\" 定义类方法，至少有一个cls参数 \"\"\"\n\n        print('类方法')\n\n    @staticmethod\n    def static_func():\n        \"\"\" 定义静态方法 ，无默认参数\"\"\"\n\n        print('静态方法')\n\n\n# 调用普通方法\nf = Foo()\nf.ord_func()\n\n# 调用类方法\nFoo.class_func()\nf.class_func()\n\n# 调用静态方法\nFoo.static_func()\nf.static_func()\n```\n\n\n相同点：对于所有的方法而言，均属于类（非对象）中，所以，在内存中也只保存一份。\n不同点：方法调用者不同、调用方法时自动传入的参数不同。\n\n\n使用普通方法、类方法和静态方法都可以通过对象（t）进行调用，但是静态方法和类方法无法访问对象的属性，所以**`更改对象（t）的属性仅仅只是对普通方法起作用`**\n\n```python\nclass Test(object):\n    # 定义类Test的属性\n    name = 'python'\n    content = '人生苦短，我用python!'\n\n    def normal_method(self):\n        print(self.content)\n\n    @classmethod\n    def class_method(cls):\n        print(cls.content)\n\n    @staticmethod\n    def static_method():\n        print('content')\n\n\nt = Test()\nt.content = '人生苦短，及时行乐'  # 设置对象t的属性\n\nt.normal_method()  # 人生苦短，及时行乐\nt.class_method()  # 人生苦短，我用python!\nt.static_method()  # content\n\n```\n\n```python\nclass P(object):\n    tip = 'Persion tips'  # 类属性，类及类成员共享\n\n    @classmethod\n    def clsFunc(cls):\n        \"\"\"类方法共享\"\"\"\n        return 'Welcome:' + cls.tip\n\n    def insFunc(self):\n        \"\"\"实例方法\"\"\"\n        return self.__class__\n\n    @staticmethod\n    def staFunc():\n        \"\"\"静态方法\"\"\"\n        return P.tip\n\n\np = P()\nprint(P.tip)\nprint(p.tip)\nprint(p.insFunc())\n\np.tip = \"Hello\"\nprint(p.clsFunc())\nprint(p.staFunc())\n\nprint(P.clsFunc())\nprint(P.staFunc())\n```\n<br>\n总结:\n\n>三种方法都可以通过对象进行调用，但类方法和静态方法无法访问对象属性，类方法通过对象调用获取的仍是类属性（并非对象属性）；\n\n>普通方法无法通过类名调用，类方法和静态方法可以，但静态方法不能进行访问，仅仅只是通过传值得方式（与函数调用相同)\n\n\n\n\n#### `@property`\n\n访问类或实例的属性时是直接通过obj.XXX的形式访问，但property是一个特性的属性\n\n访问添加了装饰器@property的方法时无须额外添加()来调用，而是以**属性的形式来访问**。\n\n所以，利用这个特性可以虚拟实现类属性的只读设置。\n\n```python\nclass Student(object):\n    #  上面的birth是可读写属性，而age就是一个只读属性\n    #   因为age可以根据birth和当前时间计算出来。\n    @property  # 相当于property.getter(birth) 或者property(birth)\n    def birth(self):\n        return self._birth\n\n    @birth.setter  # 相当于birth = property.setter(birth)\n    def birth(self, value):\n        self._birth = value\n\n    @property\n    def age(self):\n        return 2019 - self._birth\n\n\nstudent = Student()\n\nstudent.birth = 1987\nprint(student.birth)\nprint(student.age)\n\n```\n\n这里有个问题,**`为什么property属性只能类的实例调用?直接使用类调用可以不可以?`**\n\n\n```python\nclass Test(object):\n    @property\n    def p(self):\n        return \"Hello World\"\n\n\n# 类调用\nprint(Test.p)  # <property object at 0x0000026EBE999AE8>\n\n# 实例调用\ntest = Test()\nprint(test.p)  # Hello World\nprint(Test.p.__get__(test))  # Hello World\n\nprint(test.p())  # TypeError: 'str' object is not callable\n\n```\n\n这里要看下什么是[python描述符](https://docs.python.org/3.6/howto/descriptor.html)\n\n>Test.p 返回的是一个property,它的属性是描述符.\n它是一个类,用很多魔法方法,比如`__get__,__set__...`\n\n>当property被类的实例调用时候,就会触发其中的`__get__`方法.\n所以**可以通过类调用property,也可以使用类的实例调用property**\n\n\n\n最后在重复一下@property的含义：\n>它是一个装饰器，作用是把类中的方法变成属性来进行调用。\n\n\n\n\n## 变量作用域\n\n主要有下面几种作用域\n\n**`L: Local 函数内部作用域 -局部名字空间中的名字`**\n\n**`E: Enclosing 封闭作用域-函数内部与内嵌函数之间`**\n\n**`G: Global全局作用域  -内置和全局命名空间中的名字都属于全局作用域`**\n\n**`B: build-in 内置作用域`**\n\n顺序:  L>E>G>B\n\n下面分别介绍这几个作用域\n\n### Local作用域\n```python\n# 这里所有的变量都在 local 作用域\ndef foo(a, b):\n    c = 1\n    d = a + b + c\n    return d\n\n```\n\n\n### Enclosing作用域\n```python\ni = 0\n\n\ndef a():\n    i = 1\n\n    # 对函数b来说就是Enclosing 封闭作用域\n    def b():\n        i = 2\n\n    b()\n    print(i)\n\n\na()  # 1\n\n```\n\n#### 关键字nolocal\n修改Enclosing变量的问题，就需要使用nonlocal关键字。\n还是刚才的例子,增加一个nonlocal:\n\n```python\ni = 0\n\n\ndef a():\n    i = 1\n\n    # 对函数b来说就是Enclosing 封闭作用域\n    def b():\n        nonlocal i\n        i = 2\n\n    b()\n    print(i)\n\n\na()  # 2\n\n```\n<br>\n注意两点:\n\n>nonlocal不能代替global。 如果在上述代码的函数a中，就只能使用global。\n 因为，外层就是Global作用域了。\n\n> **在多重嵌套中，nonlocal只会上溯一层； 而如果上一层没有，则会继续上溯**。\n\n举个例子来说明:\n\n```python\ndef a():\n    i = 1\n\n    def b():\n        # i = 2\n        def c():\n            nonlocal i\n            i = 3\n\n        c()\n        print('b:', i)\n\n    b()\n    print('a:', i)\n\n\na()\n```\n\n返回结果:\n```\nb: 3\na: 3\n```\n\n\n把上面的注释打开,对比这个:\n```python\ndef a():\n    i = 1\n\n    def b():\n        i = 2\n\n        def c():\n            nonlocal i\n            i = 3\n\n        c()\n        print('b:', i)\n\n    b()\n    print('a:', i)\n\n\na()\n```\n返回结果:\n```\nb: 3\na: 1\n\n```\n\n\n\n### Global作用域\n\n看一个例子:\n```python\ni = 0\n\n\ndef a():\n    i = 1\n    print('local:', i)\n\n\na()\nprint('global:', i)\n```\n\n返回结果:\n```\nlocal: 1\nglobal: 0\n```\n<br>\n\n**如果在一个局部（函数）内声明一个global变量,**\n\n**那么这个变量在局部的所有操作将对全局的变量有效**\n\n```python\ni = 0\n\n\ndef a():\n    global i\n    i = 1\n    print('local:', i)\n\n\na()\nprint('global:', i)\n```\n\n返回结果:\n```\nlocal: 1\nglobal: 1\n\n```\n\n\n```python\ni = 0\n\n\ndef a():\n    i = 1\n    print('local:', i)\n    # 可以查看局部作用域\n    print(locals())\n    # 查看全局作用域\n    print(globals())\n\n\na()\nprint('global:', i)\nprint(\"*\" * 10)\nprint(locals())\nprint(globals())\n```\n\n<br>\n\n**记住两点:**\n\n**`global永远打印全局的名字`**\n\n**`locals 输出什么 根据locals所在位置`**\n\n\n### build-in作用域\n\nPython内置模块的名字空间\n\n```python\ndef list_length(a):\n    return len(a)\n\n\na = [1, 2, 3]\nprint(list_length(a))  # 3\n\n# python3 写法\nimport builtins\nprint(builtins.len)  # <built-in function len>\n\n```\n\n### python命名空间规则\n\npython使用命名空间记录变量\n\npython中的命名空间就像是一个dict，key是变量的名字，value是变量的值。\n\npython中，每个函数都有一个自己的命名空间，叫做local namespace，它记录了函数的变量。\n\npython中，每个module有一个自己的命名空间，叫做global namespace，它记录了module的变量，包括 functions, classes 和其它imported modules，还有 module级别的 变量和常量。\n\n还有一个build-in 命名空间，可以被任意模块访问，这个build-in命名空间中包含了build-in function 和 exceptions。\n\n当python中的某段代码要访问一个变量x时，python会在所有的命名空间中寻找这个变量，查找的顺序为:\n>local namespace - 指的是当前函数或者当前类方法。\n>如果在当前函数中找到了变量，停止搜索\n\n>global namespace - 指的是当前的模块。如果在当前模块中找到了变量，停止搜索\n\n>build-in namespace - 如果在之前两个namespace中都找不到变量x，python会假设x是build-in的函数或者变量。\n>如果x不是内置函数或者变量，python会报错NameError。\n\n\n对于闭包来说，这里有一点区别，如果在local namespace中找不到变量的话，还会去父函数的local namespace中找变量\n\n\n直接看例子:\n```python\nlen = len([])\nprint(\"befoe global len=\", len)\n\n\ndef a():\n    len = 1\n\n    def b():\n        len = 2\n\n        def c():\n            len = 3\n            print('locals len:', len)\n\n        c()\n        print('enclosing function locals len:', len)\n\n    b()\n    print(\"after global len=\", len)\n\n\na()\n```\n\n\n运行结果:\n```\nbefoe global len= 0\nlocals len: 3\nenclosing function locals len: 2\nafter global len= 1\n\n```\n\n* from module import 和 import module\n\n使用import module时，module本身被引入，但是保存它原有的命名空间，所以我们需要使用module.name这种方式访问它的 函数和变量。\n\nfrom module import这种方式，是将其它模块的函数或者变量引到当前的命名空间中\n\n所以就不需要使用module.name这种方式访问其它的模块的方法了。\n\n* if `__name__ `trick\n\npython中的module也是对象，所有的modules都有一个内置的属性`__name__`，\n\n模块的`__name__`属性的值取决于如何使用这个模块，\n\n如果import module，那么`__name__`属性的值是模块的名字\n\n如果直接执行这个模块的话，那么`__name__`属性的值就是默认值`__main_`_。\n\n\n\n## 可变数据类型和不可变数据类型\n\n\n### 可变数据类型：列表list和字典dict\n\n允许变量的值发生变化，即如果对变量进行append、+=等这种操作后，只是改变了变量的值，而不会新建一个对象，变量引用的对象的地址也不会变化\n\n不过对于相同的值的不同对象，在内存中则会存在不同的对象，即每个对象都有自己的地址，相当于内存中对于同值的对象保存了多份，这里不存在引用计数，是实实在在的对象。\n\n```python\na = [1, 2]\nb = [1, 2]\n\nprint(\"ID a=\", id(a))  # ID a= 1236557349896\nprint(\"ID b=\", id(b))  # ID b= 1236556806216\n\nprint(a == b)  # 判断值是否相等 True\n\nprint(a is b)  # 判断地址是否一样 False\n\n```\n\n<br>\n\n### 不可变数据类型：数值型、字符串型string和元组tuple\n\n不允许变量的值发生变化，如果改变了变量的值，相当于是新建了一个对象，而对于相同的值的对象，在内存中则只有一个对象（一个地址）\n\n```python\na = 3\nb = 3\n\nprint(\"ID a=\", id(a))  # ID a= 1570991200\nprint(\"ID b=\", id(b))  # ID b= 1570991200\n\nprint(a == b)  # 判断值是否相等 True\n\nprint(a is b)  # 判断地址是否一样 True\n\n```\n\n<br>\n再看一个例子:\n\n```python\n# 列表是可变的\na = [1, 2, 3, 4]\na[0] = 100\na.insert(3, 300)\n\nprint(a)\n\n# 字符串是不可变的\ns = \"Hello world\"\n# s[0] = \"z\"\n# print(s)  # TypeError: 'str' object does not support item assignment\n\n# 字符串replace方法只是返回一个新字符串，并不改变原来的值\nprint(s.replace('world', 'Mars'))\nprint(s)\n\n# 如果想改变字符串的值，可以用重新赋值的方法\ns = s.replace('world', 'Mars')\nprint(s)\n\n# 或者使用bytearray代替字符串\nc = bytearray('abcde', 'utf-8')\nc[1:3] = b'12'\nprint(c)  # bytearray(b'a12de')\n\n```\n\n<br>\n\n* 总结:\n\n可变数据类型:\n>**list, dictionary, set**, numpy array, user defined objects\n\n\n不可变数据类型 \n> **integer, float,** long, complex, **string, tuple**, frozenset\n\n<br>\n\n### 字符串不可变的原因\n\n1.`列表可以通过以下的方法改变，而字符串不支持这样的变化`\n\n```python\na = [1, 2, 3, 4]\n\n# 此时a 和 b 指向同一块区域,改变 b 的值,a 也会同时改变\nb = a\n\nprint(\"ID a=\", id(a))\nprint(\"ID b=\", id(b))\n\nb[0] = 100\nprint(a)  # [100, 2, 3, 4]\nprint(b)  # [100, 2, 3, 4]\n\n```\n\n2.`字符串与整数浮点数一样被认为是基本类型，而基本类型在Python中是不可变的。\n\n\n\n\n## 闭包\n\n### 什么是闭包\n**一个闭包就是你调用了一个函数A，这个函数A返回了一个函数B给你。**\n\n这个返回的函数B就叫做闭包。你在调用函数A的时候传递的参数就是**自由变量**。\n\n也就是说闭包是嵌套函数，内部函数调用外部函数的变量\n\n例子:\n```python\ndef outer():\n    a = 1\n\n    def inner():\n        print(a)\n        print(inner.__closure__)\n\n    return inner\n\n\ninn = outer()\ninn()\n```\n\n\n返回结果:\n```\n1\n(<cell at 0x000001E6396FD078: int object at 0x000000005DA36C20>, <cell at 0x000001E6396FD0A8: function object at 0x000001E6396FBC80>)\n```\n\n<br>\n这里的返回值 两个cell的意思是:\n\n**`内部函数引用了外部函数的变量, int和func类型`**\n\n\n这里inner函数就是一个闭包,并且拥有该闭包持有的自由变量a\n\n总结创建一个闭包必须满足以下几点:\n\n1.必须有一个内嵌函数\n\n2.内嵌函数必须引用外部函数中的变量\n\n3.外部函数的返回值必须是内嵌函数\n\n\n### 为什么要使用闭包\n\n两个特点:\n* **封装** \n* **代码复用**\n\n\n```python\ndef set_passline(passline):\n    def cmp(val):\n        if val >= passline:\n            print(\"pass\")\n        else:\n            print(\"failed\")\n\n    return cmp\n\n\nf_100 = set_passline(60)\nf_100(89)\nf_100(59)\n```\n\n### 怎么使用闭包\n\n\n* 在python中很重要也很常见的一个使用场景就是装饰器\n`装饰器是特殊的闭包形式`\n\n```python\ndef decorator_func(func):\n    def wrapper(*args, **kwargs):\n        return func(*args, **kwargs)\n\n    return wrapper\n\n\n@decorator_func\ndef func(name):\n    print('my name is', name)\n\n\n# 等价于\n# decorator_func(func)\n\nfunc(\"tony\")\n```\n\n<br>\n\n* 惰性求值\n惰性求值属性可以在对象被使用的时候才进行计算,这样可以减少一些资源消耗,提高程序效率\n\n```python\ndef out_func(a):\n    def inner_func():\n        return a\n\n    return inner_func\n\n\nfunc = out_func(1)\nprint(func())  # 1\n```\n\n<br>\n\n* 需要对某个函数的参数提前赋值的情况\n\n当然在Python中已经有了很好的解决访问 functools.parial,但是用闭包也能实现\n\n```python\ndef partial(**outer_kwargs):\n    def wrapper(func):\n        def inner(*args, **kwargs):\n            for k, v in outer_kwargs.items():\n                kwargs[k] = v\n                return func(*args, **kwargs)\n\n        return inner\n\n    return wrapper\n\n\n@partial(age=15)\ndef say(name=None, age=None):\n    print(name, age)\n\n\nsay(name=\"tony\")\n\n# 当然用functools比这个简单多了\n# import functools\n# functools.partial(say, age=15)(name='tony')\n```"
  },
  {
    "path": "notes/Python/流畅的Python/动态属性和特性.md",
    "content": "# 第19章 动态属性和特性\n\n\n这里主要讲:\n\n`_getattr__方法`\n\n`__new__方法`\n\n`@property`\n\n\n可以查看以前章节内容"
  },
  {
    "path": "notes/Python/流畅的Python/协程-补充内容.md",
    "content": "# 第16章 协程-补充内容\n\n<!-- TOC -->\n\n- [第16章 协程-补充内容](#第16章-协程-补充内容)\n    - [多进程VS多线程](#多进程vs多线程)\n        - [多进程](#多进程)\n            - [multiprocessing 模块](#multiprocessing-模块)\n            - [多进程通讯共享数据](#多进程通讯共享数据)\n                - [队列](#队列)\n                - [管道](#管道)\n                - [Managers](#managers)\n            - [进程池](#进程池)\n        - [多线程 GIL](#多线程-gil)\n            - [GIL是什么](#gil是什么)\n            - [GIL例子](#gil例子)\n            - [GIL释放](#gil释放)\n            - [为什么这样设计](#为什么这样设计)\n            - [能不能去掉GIL](#能不能去掉gil)\n            - [threading模块](#threading模块)\n            - [multiprocessing.dummy 模块](#multiprocessingdummy-模块)\n        - [两者关系](#两者关系)\n        - [多进程分析详解](#多进程分析详解)\n\n<!-- /TOC -->\n## 多进程VS多线程\n\n\n### 多进程\n\n**`多进程就是利用 CPU 的多核优势`**，在同一时间并行地执行多个任务，可以大大提高执行效率。\n\n\n#### multiprocessing 模块\n\n例子：\n```python\nfrom multiprocessing import Process\n\n\ndef fun1(name):\n    print('测试%s多进程' % name)\n\n\nif __name__ == '__main__':\n\n    process_list = []\n    for i in range(5):  # 开启5个子进程执行fun1函数\n        p = Process(target=fun1, args=('Python', ))  # 实例化进程对象\n        p.start()\n        process_list.append(p)\n\n    for i in process_list:\n        p.join()\n\n    print('结束测试')\n```\n\n返回结果：\n```\n测试Python多进程\n测试Python多进程\n测试Python多进程\n测试Python多进程\n测试Python多进程\n结束测试\n```\n\n<br>\n\n类继承方式：\n```python\nfrom multiprocessing import Process\n\n\nclass MyProcess(Process):  # 继承Process类\n    def __init__(self, name):\n        super(MyProcess, self).__init__()\n        self.name = name\n\n    def run(self):\n        print('测试%s多进程' % self.name)\n\n\nif __name__ == '__main__':\n    process_list = []\n    for i in range(5):  # 开启5个子进程执行fun1函数\n        p = MyProcess('Python')  # 实例化进程对象\n        p.start()\n        process_list.append(p)\n\n    for i in process_list:\n        p.join()\n\n    print('结束测试')\n```\n\n#### 多进程通讯共享数据\n\n多进程之间可以通过管道，队列，Managers来实现通讯和共享数据\n\n\n##### 队列\n\n```python\nfrom multiprocessing import Process, Queue\n\n\ndef fun1(q, i):\n    print('子进程%s 开始put数据' % i)\n    q.put('我是%s 通过Queue通信' % i)\n\n\nif __name__ == '__main__':\n    q = Queue()\n\n    process_list = []\n    for i in range(3):\n\t     # 注意args里面要把q对象传给我们要执行的方法，这样子进程才能和主进程用Queue来通信\n        p = Process(target=fun1, args=(\n            q,\n            i,\n        ))\n        p.start()\n        process_list.append(p)\n\n    for i in process_list:\n        p.join()\n\n    print('主进程获取Queue数据')\n    print(q.get())\n    print(q.get())\n    print(q.get())\n    print('结束测试')\n```\n\n运行结果：\n```\n子进程0 开始put数据\n子进程1 开始put数据\n子进程2 开始put数据\n主进程获取Queue数据\n我是0 通过Queue通信\n我是1 通过Queue通信\n我是2 通过Queue通信\n结束测试\n```\n\n\n\n\n##### 管道\n```python\nfrom multiprocessing import Process, Pipe\n\n\ndef fun1(conn):\n    print('子进程发送消息：')\n    conn.send('你好主进程')\n    print('子进程接受消息：')\n    print(conn.recv())\n    conn.close()\n\n\nif __name__ == '__main__':\n    conn1, conn2 = Pipe()  # 关键点，pipe实例化生成一个双向管\n    p = Process(target=fun1, args=(conn2, ))  # conn2传给子进程\n    p.start()\n    print('主进程接受消息：')\n    print(conn1.recv())\n    print('主进程发送消息：')\n    conn1.send(\"你好子进程\")\n    p.join()\n    print('结束测试')\n```\n\n返回结果：\n```\n主进程接受消息：\n子进程发送消息：\n子进程接受消息：\n你好主进程\n主进程发送消息：\n你好子进程\n结束测试\n```\n\n\n##### Managers\nQueue和Pipe只是实现了数据交互，并没实现数据共享，即一个进程去更改另一个进程的数据。那么要用到Managers\n\n```python\nfrom multiprocessing import Process, Manager\n\n\ndef fun1(dic, lis, index):\n\n    dic[index] = 'a'\n    dic['2'] = 'b'\n    lis.append(index)  # [0,1,2,3,4,0,1,2,3,4,5,6,7,8,9]\n    #print(l)\n\n\nif __name__ == '__main__':\n    with Manager() as manager:\n        dic = manager.dict()  # 注意字典的声明方式，不能直接通过{}来定义\n        l = manager.list(range(5))  # [0,1,2,3,4]\n\n        process_list = []\n        for i in range(10):\n            p = Process(target=fun1, args=(dic, l, i))\n            p.start()\n            process_list.append(p)\n\n        for res in process_list:\n            res.join()\n        print(dic)\n        print(l)\n```\n\n返回结果：\n```\n{0: 'a', '2': 'b', 2: 'a', 1: 'a', 3: 'a', 5: 'a', 4: 'a', 9: 'a', 6: 'a', 8: 'a', 7: 'a'}\n[0, 1, 2, 3, 4, 0, 2, 1, 3, 5, 4, 9, 6, 8, 7]\n```\n\n可以看到主进程定义了一个字典和一个列表，在子进程中，可以添加和修改字典的内容，在列表中插入新的数据，实现进程间的数据共享，即可以共同修改同一份数据\n\n\n#### 进程池\n\n进程池内部维护一个进程序列，当使用时，则去进程池中获取一个进程，如果进程池序列中没有可供使用的进进程，那么程序就会等待，直到进程池中有可用进程为止。就是固定有几个进程可以使用。\n\n进程池中有两个方法：\napply：同步，一般不使用\napply_async：异步\n\n异步：\n```python\nfrom multiprocessing import Process, Pool\nimport os, time, random\n\n\ndef fun1(name):\n    print('Run task %s (%s)...' % (name, os.getpid()))\n    start = time.time()\n    time.sleep(random.random() * 3)\n    end = time.time()\n    print('Task %s runs %0.2f seconds.' % (name, (end - start)))\n\n\nif __name__ == '__main__':\n    pool = Pool(5)  # 创建一个5个进程的进程池\n\n    for i in range(10):\n        pool.apply_async(func=fun1, args=(i, ))\n\n    pool.close()\n    pool.join()\n    print('结束测试')\n```\n\n运行结果是：\n```\n\nRun task 0 (13892)...\nRun task 1 (15796)...\nRun task 2 (6648)...\nRun task 3 (9260)...\nRun task 4 (1872)...\nTask 3 runs 0.79 seconds.\nRun task 5 (9260)...\nTask 5 runs 0.10 seconds.\nRun task 6 (9260)...\nTask 2 runs 1.43 seconds.\nRun task 7 (6648)...\nTask 4 runs 2.44 seconds.\nTask 1 runs 2.44 seconds.\nRun task 8 (1872)...\nRun task 9 (15796)...\nTask 0 runs 2.58 seconds.\nTask 9 runs 0.14 seconds.\nTask 8 runs 0.88 seconds.\nTask 6 runs 2.55 seconds.\nTask 7 runs 2.07 seconds.\n结束测试\n```\n\n对Pool对象调用join()方法会等待所有子进程执行完毕，调用join()之前必须先调用close()，调用close()之后就不能继续添加新的Process了。\n\n\n\n另外一个例子：\n```python\nfrom multiprocessing import Manager, Pool\nimport os, time\n\n\ndef reader(q):\n    print(\"reader 启动(%s),父进程为（%s)\" % (os.getpid(), os.getpid()))\n    for i in range(q.qsize()):\n        print(\"reader 从Queue获取到消息:%s\" % q.get(True))\n\n\ndef writer(q):\n    print(\"writer 启动（%s),父进程为(%s)\" % (os.getpid(), os.getpid()))\n    for i in \"itcast\":\n        q.put(i)\n\n\nif __name__ == \"__main__\":\n    print(\"(%s)start\" % os.getpid())\n\n    q = Manager().Queue()  # 使用Manager中的Queue\n    po = Pool()\n\n    po.apply_async(writer, (q, ))\n    time.sleep(1)\n    po.apply_async(reader, (q, ))\n\n    po.close()\n    po.join()\n\n    print(\"(%s)End\" % os.getpid())\n```\n\n运行结果是：\n```\n(9516)start\nwriter 启动（10852),父进程为(10852)\nreader 启动(13392),父进程为（13392)\nreader 从Queue获取到消息:i\nreader 从Queue获取到消息:t\nreader 从Queue获取到消息:c\nreader 从Queue获取到消息:a\nreader 从Queue获取到消息:s\nreader 从Queue获取到消息:t\n(9516)End\n```\n\n同步：\n```python\nfrom multiprocessing import Process, Pool\nimport os, time, random\n\n\ndef fun1(name):\n    print('Run task %s (%s)...' % (name, os.getpid()))\n    start = time.time()\n    time.sleep(random.random() * 3)\n    end = time.time()\n    print('Task %s runs %0.2f seconds.' % (name, (end - start)))\n\n\nif __name__ == '__main__':\n    pool = Pool(5)  # 创建一个5个进程的进程池\n\n    for i in range(10):\n        pool.apply(func=fun1, args=(i, ))\n\n    pool.close()\n    pool.join()\n    print('结束测试')\n```\n\n运行结果：\n```\nRun task 0 (12996)...\nTask 0 runs 0.96 seconds.\nRun task 1 (5704)...\nTask 1 runs 2.68 seconds.\nRun task 2 (6808)...\nTask 2 runs 1.31 seconds.\nRun task 3 (3020)...\nTask 3 runs 0.85 seconds.\nRun task 4 (16980)...\nTask 4 runs 2.35 seconds.\nRun task 5 (12996)...\nTask 5 runs 1.25 seconds.\nRun task 6 (5704)...\nTask 6 runs 2.43 seconds.\nRun task 7 (6808)...\nTask 7 runs 1.79 seconds.\nRun task 8 (3020)...\nTask 8 runs 0.72 seconds.\nRun task 9 (16980)...\nTask 9 runs 0.77 seconds.\n结束测试\n```\n\n\n### 多线程 GIL\n\n\n#### GIL是什么\n\nGIL 是python的全局解释器锁，同一进程中假如有多个线程运行，一个线程在运行python程序的时候会霸占python解释器（加了一把锁即GIL），使该进程内的其他线程无法运行，等该线程运行完后其他线程才能运行。如果线程运行过程中遇到耗时操作，则解释器锁解开，使其他线程运行。所以在多线程中，线程的运行仍是有先后顺序的，并不是同时进行。\n\n多进程中因为每个进程都能被系统分配资源，相当于每个进程有了一个python解释器，所以多进程可以实现多个进程的同时运行，缺点是进程系统资源开销大\n\n#### GIL例子\n\n\n```python\nimport time\n\n\ndef decrement(n):\n    while n > 0:\n        n -= 1\n\n\nstart = time.time()\ndecrement(100000000)\ncost = time.time() - start\nprint(cost)  # 4.968633651733398\n\n```\n\n使用threading多线程模块：\n```python\nimport time\nimport threading\n\n\ndef decrement(n):\n    while n > 0:\n        n -= 1\n\n\nstart = time.time()\n\nt1 = threading.Thread(target=decrement, args=[50000000])\nt2 = threading.Thread(target=decrement, args=[50000000])\n\nt1.start()  # 启动线程，执行任务\nt2.start()  # 同上\n\nt1.join()  # 主线程阻塞，直到t1执行完成，主线程继续往后执行\nt2.join()  # 同上\n\ncost = time.time() - start\nprint(cost)  # 4.755946159362793\n```\n\n\n按理来说，两个线程同时并行地运行在两个 CPU 之上，时间应该减半才对，现在不减反增。\n\n是什么原因导致多线程不快反慢的呢？\n\n**原因就在于 GIL ，在` Cpython 解释器`（Python语言的主流解释器）中，有一把全局解释锁（Global Interpreter Lock）**，\n\n在解释器解释执行 Python 代码时，先要得到这把锁，意味着，任何时候只可能有一个线程在执行代码，其它线程要想获得 CPU 执行代码指令，\n\n就必须先获得这把锁，如果锁被其它线程占用了，那么该线程就只能等待，直到占有该锁的线程释放锁才有执行代码指令的可能。\n\n因此，这也就是为什么两个线程一起执行反而更加慢的原因，因为同一时刻，只有一个线程在运行，其它线程只能等待，即使是多核CPU，\n\n也没办法让多个线程「并行」地同时执行代码，只能是交替执行，因为多线程涉及到上线文切换、锁机制处理（获取锁，释放锁等），\n\n所以，多线程执行不快反慢。\n\n\n#### GIL释放\n什么时候 GIL 被释放呢？\n\n当一个线程遇到 I/O 任务时，将释放GIL。\n\n计算密集型（CPU-bound）线程执行 100 次解释器的计步（ticks）时（计步可粗略看作 Python 虚拟机的指令），也会释放 GIL。\n\n可以通过 sys.setcheckinterval()设置计步长度，sys.getcheckinterval() 查看计步长度。相比单线程，这些多是多线程带来的额外开销\n\n#### 为什么这样设计\nCPython 解释器为什么要这样设计？\n\n多线程是为了适应现代计算机硬件高速发展充分利用多核处理器的产物，通过多线程使得 CPU 资源可以被高效利用起来，\n\n但是多线程有个问题，怎么解决共享数据的同步、一致性问题，因为，对于多个线程访问共享数据时，可能有两个线程同时修改一个数据情况，\n\n如果没有合适的机制保证数据的一致性，那么程序最终导致异常，\n\n所以，Python之父就搞了个全局的线程锁，不管你数据有没有同步问题，反正一刀切，上个全局锁，保证数据安全。\n\n这也就是多线程鸡肋的原因，因为它没有细粒度的控制数据的安全，而是用一种简单粗暴的方式来解决。\n\n\n#### 能不能去掉GIL\n那么把 GIL 去掉可行吗？\n\n去掉GIL的 Python 在单线程条件下执行效率将近慢了2倍。\n\n\n\n#### threading模块\n\n```python\nimport threading\nfrom time import sleep\n\n\nclass Mythead(threading.Thread):\n    def run(self):\n        for i in range(3):\n            sleep(1)  # 挂起1s\n            # self.name是该线程的名字，默认会分配一个形如“Thread-N”的名字，其中 N 是一个十进制数\n            print(u'线程:%s,索引%s' % (self.name, i))\n\n\nif __name__ == '__main__':\n    for i in range(3):\n        t = Mythead()\n        t.start()\n```\n\n运行结果：\n```\n线程:Thread-2,索引0\n线程:Thread-3,索引0\n线程:Thread-4,索引0\n线程:Thread-2,索引1\n线程:Thread-3,索引1\n线程:Thread-4,索引1\n线程:Thread-2,索引2\n线程:Thread-3,索引2\n线程:Thread-4,索引2\n```\n\n从上面的运行结果来看，多线程程序的执行顺序是确定的。\n\n\n```python\nimport threading\nimport logging\nfrom time import sleep\n\n#配置logging，以“模式名/线程名字(10个字符)/信息”方式输出。\nlogging.basicConfig(level=logging.DEBUG,\n                    format='[%(levelname)s](%(threadName)-10s) %(message)s')\n\n\ndef foo():\n    logging.debug('Starting...')\n    sleep(2)\n    logging.debug('Exiting...')\n\n\nthreading.Thread(name='mythread', target=foo).start()\nthreading.Thread(target=foo).start()\n\n\n```\n返回结果是:\n```\n[DEBUG](mythread  ) Starting...\n[DEBUG](Thread-2  ) Starting...\n[DEBUG](mythread  ) Exiting...\n[DEBUG](Thread-2  ) Exiting...\n```\n\n\n比较有趣的是, multiprocessing中提供了一个dummy module, 以multiprocessing API的方式提供了对threading模块的封装,\n\n这就意味着使用如下代码时:\n\n#### multiprocessing.dummy 模块\n\n\nmultiprocessing.dummy 模块与 multiprocessing 模块的区别：\ndummy 模块是多线程，而 multiprocessing 是多进程， api 都是通用的。\n\nfrom multiprocessing.dummy import Pool, Process\nPool和Process的底层其实都是使用threading的实现(即ThreadPool和Thread),\n\n```python\n# from multiprocessing import Pool\nfrom multiprocessing.dummy import Pool as ThreadPool\nimport time\nfrom urllib.request import urlopen\n\nurls = [\n    'http://www.baidu.com',\n    'http://home.baidu.com/',\n    'http://tieba.baidu.com/',\n]\nstart = time.time()\nresults = map(urlopen, urls)\nprint('Normal:', time.time() - start)\n\nstart2 = time.time()  # 开8个 worker，没有参数时默认是 cpu 的核心数\npool = ThreadPool(processes=8)\nresults2 = pool.map(urlopen, urls)\npool.close()\npool.join()\n\nprint('Thread Pool:', time.time() - start2)\n```\n\n\n### 两者关系\n\n**多进程适合在CPU密集操作**（cpu操作指令比较多，如位多的的浮点运算）。\n**多线程适合在IO密性型操作**（读写数据操作比多的的，比如爬虫）\n\n\n### 多进程分析详解\n\n\n[深入Python多进程编程基础](https://mp.weixin.qq.com/s/7cM0CmtklFQs0tlnw8mFGg)\n[深入Python多进程通信原理与实战](https://mp.weixin.qq.com/s/qKU6z1PvBENTy8QxdZxCzg)"
  },
  {
    "path": "notes/Python/流畅的Python/协程.md",
    "content": "# 第16章 协程\n\n\n<!-- TOC -->\n\n- [第16章 协程](#第16章-协程)\n    - [进程 VS 线程](#进程-vs-线程)\n        - [进程](#进程)\n            - [堆栈](#堆栈)\n            - [数据结构的堆栈](#数据结构的堆栈)\n            - [内存分配中的堆和栈](#内存分配中的堆和栈)\n        - [线程](#线程)\n        - [线程安全](#线程安全)\n        - [互斥锁](#互斥锁)\n    - [阻塞VS非阻塞](#阻塞vs非阻塞)\n        - [阻塞](#阻塞)\n        - [非阻塞](#非阻塞)\n    - [同步VS异步](#同步vs异步)\n        - [同步](#同步)\n        - [异步](#异步)\n    - [区别](#区别)\n    - [并发 VS并行](#并发-vs并行)\n    - [协程](#协程)\n        - [greenlet](#greenlet)\n        - [gevent](#gevent)\n    - [协程VS子程序](#协程vs子程序)\n\n<!-- /TOC -->\n\n## 进程 VS 线程\n\n\n进程和线程说明\n\n* 是程序在操作系统中的一次执行过程，是系统进行资源分配和调度的基本单位\n\n* 线程是进程的一个执行实例，是程序执行的最小单位，它是比进程更小的能独立运行的基本单位\n\n* 一个进程可以创建和销毁多个线程，同一个进程中的多个线程可以并发执行\n* 一个程序至少有一个进程，一个进程至少有一个线程\n\n可以联想\n打开迅雷.exe  就是打开进程\n多个任务 就是多个线程**\n\n### 进程\n进程：一个运行的程序（代码）就是一个进程，没有运行的代码叫程序，进程是系统资源分配的最小单位，进程拥有自己独立的内存空间，所有进程间数据不共享，开销大。\n\n\n进程：\n1、操作系统进行资源分配和调度的基本单位，多个进程之间相互独立\n2、稳定性好，如果一个进程崩溃，不影响其他进程，但是进程消耗资源大，开启的进程数量有限制\n\n\n**进程拥有自己独立的堆和栈，既不共享堆，亦不共享栈，进程由操作系统调度。**\n\n`怎么得出来这结论的？`\n\n#### 堆栈\n\n什么是堆栈？在计算机中堆栈的概念分为：**数据结构的堆栈和内存分配中堆栈**。\n\n<br>\n\n#### 数据结构的堆栈\n\n堆：堆可以被看成是一棵树，如：**堆排序**。在队列中，调度程序反复提取队列中第一个作业并运行，因为实际情况中某些时间较短的任务将等待很长时间才能结束，或者某些不短小，但具有重要性的作业，同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。\n\n栈：一种先进后出的数据结构。\n\n\n\n####内存分配中的堆和栈\n内存分配中的堆和栈\n栈（操作系统）：由操作系统自动分配释放 ，存放函数的参数值，局部变量的值等。其操作方式类似于数据结构中的栈。\n\n堆（操作系统）： 一般由程序员分配释放， 若程序员不释放，程序结束时可能由OS回收，分配方式倒是类似于链表。\n\n堆栈缓存方式\n栈使用的是一级缓存， 他们通常都是被调用时处于存储空间中，调用完毕立即释放。\n堆则是存放在二级缓存中，生命周期由虚拟机的垃圾回收算法来决定（并不是一旦成为孤儿对象就能被回收）。所以调用这些对象的速度要相对来得低一些。\n\n### 线程\n线程: cpu调度执行的最小单位，也叫执行路径，不能独立存在，依赖进程存在，一个进程至少有一个线程，叫主线程，而多个线程共享内存（数据共享，共享全局变量),从而极大地提高了程序的运行效率。\n\n\n线程：\n1、CPU进行资源分配和调度的基本单位，线程是进程的一部分，是比进程更小的能独立运行的基本单位，一个进程下的多个线程可以共享该进程的所有资源\n2、如果IO操作密集，则可以多线程运行效率高，缺点是如果一个线程崩溃，都会造成进程的崩溃\n\n\n线程拥有自己独立的栈和共享的堆，共享堆，不共享栈，线程亦由操作系统调度(标准线程是的)。\n\n\n### 线程安全\n\n因为线程间共享进程中的全局变量，所以当其他线程改变了共享的变量时，可能会对本线程产生影响。\n\n所谓线程安全的约束是指一个函数被多个并发线程反复调用时，要一直产生正确的结果。要保证线程安全，主要是通过加锁的方式保证共享变量的正确访问。\n\n\n换句话说, **线程安全 是在多线程的环境下, 线程安全能够保证多个线程同时执行时程序依旧运行正确, 而且要保证对于共享的数据,可以由多个线程存取,但是同一时刻只能有一个线程进行存取**.\n\n既然,多线程环境下必须存在资源的竞争,那么如何才能保证同一时刻只有一个线程对共享资源进行存取?\n\n`加锁可以保证存取操作的唯一性, 从而保证同一时刻只有一个线程对共享数据存取`.\n\n通常加锁也有2种不同的粒度的锁:\n\n1. fine-grained(所谓的**细粒度**), 那么程序员需要自行地加,解锁来保证线程安全\n   \n2. coarse-grained(所谓的**粗粒度**), 那么语言层面本身维护着一个全局的锁机制,用来保证线程安全\n\n\n前一种方式比较典型的是 java, Jython 等, 后一种方式比较典型的是 CPython (即Python).\n至于Python中的全局锁机制,也即 `GIL (Global Interpreter Lock)`\n\n\n\n### 互斥锁\n每个对象都对应于一个可称为’互斥锁‘的标记，这个标记用来保证在任一时刻，只能有一个线程访问该对象。\n\n同一进程中的多线程之间是共享系统资源的，\n\n多个线程同时对一个对象进行操作，一个线程操作尚未结束，另一线程已经对其进行操作，导致最终结果出现错误，此时需要对被操作对象添加互斥锁，保证每个线程对该对象的操作都得到正确的结果。\n\n\n\n## 阻塞VS非阻塞\n\n### 阻塞\n阻塞状态指程序未得到所需计算资源时被挂起的状态。程序在等待某个操作完成期间，自身无法继续干别的事情，则称该程序在该操作上是阻塞的。\n\n\n常见的阻塞形式有：网络 I/O 阻塞、磁盘 I/O 阻塞、用户输入阻塞等。\n\n阻塞是无处不在的，包括 CPU 切换上下文时，所有的进程都无法真正干事情，它们也会被阻塞。\n如果是多核 CPU 则正在执行上下文切换操作的核不可被利用。\n\n\n### 非阻塞\n\n程序在等待某操作过程中，自身不被阻塞，可以继续运行干别的事情，则称该程序在该操作上是非阻塞的。\n\n非阻塞并不是在任何程序级别、任何情况下都可以存在的。\n\n仅当程序封装的级别可以囊括独立的子程序单元时，它才可能存在非阻塞状态。\n\n非阻塞的存在是因为阻塞存在，正因为某个操作阻塞导致的耗时与效率低下，我们才要把它变成非阻塞的。\n\n\n\n\n## 同步VS异步\n\n### 同步\n\n多个任务之间有先后顺序执行，一个执行完下个才能执行\n\n\n### 异步\n\n多个任务之间没有先后顺序，可以同时执行，有时候一个任务可能要在必要的时候获取另一个同时执行的任务的结果，这个就叫回调\n\n\n\n\n## 区别\n\n同步异步相对于多任务而言，阻塞非阻塞相对于代码执行而言。\n\n\n\n\n## 并发 VS并行\n\n1、多线程程序在单核上运行，就是并发  \n多线程，看起来一起执行，GIL在同一时刻限制了多个线程只能有一个线程被CPU执行\n\n2、多线程程序在多核上运行，就是并行 \n多进程，多个进程在同一时刻可以占用多个CPU\n\n<br>\n举个例子：\n1、并发： 因为是在一个cpu上，比如有十个线程，每个线程进行10毫秒（进行轮询操作）\n从人的角度看，好像这10个线程都在运行，但是从微观上看，在某个时间点看，\n其实只有一个线程在执行，这就是并发\n\n2.并行：因为在多个CPU上(比如10个CPU)，比如有10个线程，每个线程执行10毫秒（各自在不同CPU上执行），\n从人的角度看，好像这10个线程都在运行，但是从微观上看，在某个时间点看，\n也同时有10个线程在执行，这就是并行\n\n\n\n\n\n## 协程\n协程: 是一种用户态的轻量级线程，协程的调度完全由用户控制。\n\n协程和线程一样共享堆，不共享栈，协程由程序员在协程的代码里显示调度。\n\n\n协程拥有自己的寄存器上下文和栈。\n\n协程调度时，将寄存器上下文和栈保存到其他地方，在切回来的时候，恢复先前保存的寄存器上下文和栈，直接操中栈则基本没有内核切换的开销，可以不加锁的访问全局变量，所以**上下文的切换非常快。**\n\n简单点说协程是进程和线程的升级版,进程和线程都面临着内核态和用户态的切换问题而耗费许多切换时间,而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态.\n\n\n能够在一个线程中实现并发的概念\n能够规避在一些任务中的IO操作\n在任务的执行过程中，检测到IO就切换到其他任务\n协程在一个线程上 提高CPU的利用率\n协程相比与多线程优势  切换的效率更快\n\n应用场景：\n爬虫的例子，请求过程中的IO等待\n\n```python\nfrom gevent import monkey\nmonkey.patch_all()\nimport gevent\nfrom urllib.request import urlopen\n\n\ndef get_url(url):\n    response = urlopen(url)\n    content = response.read().decode('utf-8')\n    return len(content)\n\n\ng1 = gevent.spawn(get_url, 'http://www.baidu.com')\ng2 = gevent.spawn(get_url, 'http://www.taobao.com')\ng3 = gevent.spawn(get_url, 'http://www.hao123.com')\ngevent.joinall([g1, g2, g3])\nprint(g1.value)\nprint(g2.value)\nprint(g3.value)\n```\n\n运行结果：\n```\n156535\n133524\n298483\n```\n可以看到结果是同时显示出来的\n\n<br>\n\n\n**Python里最常见的yield就是协程的思想**\n\n\n举个例子：\n```python\nimport threading\n\n\nclass Thread(threading.Thread):\n    def __init__(self, name):\n        threading.Thread.__init__(self)\n        self.name = name\n\n    def run(self):\n        for i in range(10):\n            print(self.name)\n\n\nthreadA = Thread(\"A\")\nthreadB = Thread(\"B\")\n\nthreadA.start()\nthreadB.start()\n\n```\n\n\n返回结果是：\n```\nA\nA\nA\nB\nB\nB\nB\nB\nB\nB\nB\nB\nBA\nA\nA\nA\nA\n\nA\nA\n```\n那么总共发生了 20 次切换：主线程 -> A -> B -> A -> B …\n\n使用协程方式：\n\n```python\nimport greenlet\n\n\ndef run(name, nextGreenlets):\n    for i in range(10):\n        print(name)\n        if nextGreenlets:\n            nextGreenlets.pop(0).switch(chr(ord(name) + 1), nextGreenlets)\n\n\ngreenletA = greenlet.greenlet(run)\ngreenletB = greenlet.greenlet(run)\n\ngreenletA.switch('A', [greenletB])\n\n```\n\n\n返回结果：\n```\nA\nB\nB\nB\nB\nB\nB\nB\nB\nB\nB\n\n```\n此时发生了 2 次切换：主协程 -> A -> B\n\n\nyield例子：\n```python\ndef consumer():\n    print(123)\n    while True:\n        x = yield\n        print(\"处理了数据:\", x)\n\n\ndef producer():\n    c = consumer()\n    next(c)\n    for i in range(10):\n        print(\"生产了数据:\", i)\n        c.send(i)\n\n\nproducer()\n```\n\n### greenlet\ngreenlet 是一个轻量级的协程实现，使用的方法简单而清晰。创建 greenlet 实例执行方法，在方法内部可通过 **`greenlet.switch()`** 切换至其他 greenlet 实例进行执行\n\n例子：\n```python\nfrom greenlet import greenlet\n\n\ndef eat():\n    print(\"eating start\")\n    g2.switch()\n    print(\"eating end\")\n    g2.switch()\n\n\ndef play():\n    print(\"playing start\")\n    g1.switch()\n    print(\"playing end\")\n\n\ng1 = greenlet(eat)\ng2 = greenlet(play)\ng1.switch()\n\n```\n\n运行结果：\n```\neating start\nplaying start\neating end\nplaying end\n\n```\n\n\n### gevent\n\ngevent 基于 greenlet 库进行了封装，基于 libev 和 libuv 提供了高效的同步API。\n\n对 greenlet 在业务开发中的不便之处，提供了很好的解决方案：\n\n对于 greenlet 实例的管理，不使用树形关系进行组织，隐藏不必要的复杂性\n\n因为协程还是运行在一个OS进程中，所以协程不能跑阻塞任务，否则就要将整个OS进程阻塞住了。\n\n采用 monkey patching 与第三方库协作，将阻塞任务变成非阻塞，也不需要手工通过greenlet.switch() 切换；\n\n\n例子：\n```python\nimport gevent\n\n\ndef foo():\n    print('Running in foo')\n    gevent.sleep(0)\n    print('Explicit context switch to foo again')\n\n\ndef bar():\n    print('Explicit context to bar')\n    gevent.sleep(0)\n    print('Implicit context switch back to bar')\n\n\ngevent.joinall([\n    gevent.spawn(foo),\n    gevent.spawn(bar),\n])\n\n```\n\n运行结果：\n```\nRunning in foo\nExplicit context to bar\nExplicit context switch to foo again\nImplicit context switch back to bar\n```\n\n通过这个例子可以看到 两个上下文通过调用 gevent.sleep()来互相切换。\n\n\n再看一个例子：\n```python\nimport gevent\nimport random\n\n\ndef task(pid):\n    \"\"\"\n    Some non-deterministic task\n    \"\"\"\n    gevent.sleep(random.randint(0, 2) * 0.001)\n    print('Task', pid, 'done')\n\n\ndef synchronous():\n    for i in range(1, 10):\n        task(i)\n\n\ndef asynchronous():\n    threads = [gevent.spawn(task, i) for i in range(10)]\n    gevent.joinall(threads)\n\n\nprint('Synchronous:')\nsynchronous()\n\nprint('Asynchronous:')\nasynchronous()\n\n```\n\n运行结果是：\n```\nSynchronous:\nTask 1 done\nTask 2 done\nTask 3 done\nTask 4 done\nTask 5 done\nTask 6 done\nTask 7 done\nTask 8 done\nTask 9 done\nAsynchronous:\nTask 0 done\nTask 4 done\nTask 5 done\nTask 1 done\nTask 3 done\nTask 7 done\nTask 2 done\nTask 6 done\nTask 8 done\nTask 9 done\n```\n\n\n在同步的情况下，任务是按顺序执行的，在执行各个任务的时候会阻塞主线程。\n\n而gevent.spawn 的重要功能就是封装了greenlet里面的函数。\n初始化的greenlet放在了threads这个list里面，被传递给了 gevent.joinall 这个函数，它会阻塞当前的程序来执行所有的greenlet。\n\n在异步执行的情况下，所有任务的执行顺序是完全随机的。\n每一个greenlet的都不会阻塞其他greenlet的执行。\n\n\n\n\n\n\n\n## 协程VS子程序\n\n`协程是为了实现单线程的i/o密集型的任务并发（通过event loop）\n如果子程序的话，永远是串行的`\n\n什么是子程序？\n\n协程比子程序的优势在哪里？\n\n"
  },
  {
    "path": "notes/Python/流畅的Python/可迭代的对象-迭代器和生成器.md",
    "content": "# 第14章 可迭代的对象、迭代器和生成器\n<!-- TOC -->\n\n- [第14章 可迭代的对象、迭代器和生成器](#第14章-可迭代的对象迭代器和生成器)\n    - [可迭代对象](#可迭代对象)\n    - [迭代器](#迭代器)\n        - [迭代器是什么](#迭代器是什么)\n        - [迭代器内存消耗检测](#迭代器内存消耗检测)\n        - [迭代器使用](#迭代器使用)\n    - [生成器](#生成器)\n        - [yield生成器](#yield生成器)\n        - [生成器执行过程](#生成器执行过程)\n        - [生成器创建](#生成器创建)\n        - [生成器判断](#生成器判断)\n        - [yield与协程](#yield与协程)\n\n<!-- /TOC -->\n\n## 可迭代对象\n\n举个例子：\n\n```python\nimport re\n\nre_word = re.compile(r'\\w+')\n\n\nclass Sentence(object):\n    def __init__(self, text):\n        self.text = text\n        self.word = re_word.findall(text)\n\n    def __getitem__(self, item):\n        return self.word[item]\n\n    def __len__(self):\n        return len(self.word)\n\n    def __str__(self):\n        return 'Sentence(%s)' % self.word\n\n\nif __name__ == \"__main__\":\n\n    s = Sentence(\"Today is Tuesday\")\n    print(s)\n\n    for word in s:\n        print(word)\n\n```\n\n返回结果：\n```\nSentence(['Today', 'is', 'Tuesday'])\nToday\nis\nTuesday\n```\n\n我们知道一个对象可以迭代是因为实现了`__iter__`方法，\n但是在Sentence中并没有实现`__iter__`方法。那为什么可以迭代呢。\n\n原因在于在python中实现了iter和getitem的都是可迭代的。首先会检查是否实现了iter方法，如果实现了则调用，如果没有但是实现了`__getitem__`方法。\n\nPython就会创建一个迭代器。\n\n尝试按照顺序获取元素。如果尝试失败则会抛出typeerror异常，提示object is not iterable.\n\n因此：\n\n> 如果对象实现了能返回迭代器的`__iter__`方法，那么对象就是可迭代的。\n> \n>如果实现了`__getitem__`方法，而且其参数是从零开始的索引。\n\n这种对象也可以迭代。\n\n\n我们用`__iter__`方法来改造之前的Sentence。\n在`__iter__`中返回一个可迭代对象iter(self.word)。\n当执行for word in s的时候就会调用`__iter__`方法\n\n```python\nimport re\n\nre_word = re.compile(r'\\w+')\n\n\nclass Sentence(object):\n    def __init__(self, text):\n        self.text = text\n        self.word = re_word.findall(text)\n\n    def __iter__(self):\n        return iter(self.word)\n\n    def __len__(self):\n        return len(self.word)\n\n    def __str__(self):\n        return 'Sentence(%s)' % self.word\n\n\nif __name__ == \"__main__\":\n\n    s = Sentence(\"Today is Tuesday\")\n    print(s)\n\n    for word in s:\n        print(word)\n\n```\n\n\n\n再来看下next, next的作用是返回下一个元素，如果没有元素了，抛出stopIteration异常。\n\n我们来看下next的使用方法。如果要遍历一个字符串，最简单的方法如下：\n\n```python\ns = 'abc'\n\nfor char in s:\n    print(char)\n\n```\n\n如果不用for方法，代码需要修改如下：\n```python\ns = 'abc'\n\nit = iter(s)\n\nwhile True:\n    try:\n        print(next(it))\n    except StopIteration:\n        del it\n        break\n```\n\n首先将s变成一个iter对象，然后不断调用next获取下一个字符，如果没有字符了，则会抛出StopIteration异常释放对it的引用\n\n```python\ns = 'abc'\n\nit = iter(s)\n\nprint(next(it))\nprint(next(it))\nprint(next(it))\nprint(next(it)) # StopIteration\n\n\n```\n\n因为只有3个字符，但是调用了4次it.next()导致已经找不到字符因此抛出异常。\n<br>\n\n总结：\n> 可迭代对象：实现了`__iter__`方法，就是可迭代的，可以返回自身作为迭代器。\n> 也可以返回其他一个可迭代对象\n\n\n## 迭代器\n\n### 迭代器是什么\n\n迭代器：在Python2中实现了next方法，在python3中实现了`__next__`方法。\n\n首先要让x通过iter变成一个可迭代对象，然后使用迭代器来调用元素\n\n使用迭代器好处就是：**每次只从对象中读取一条数据，不会造成内存的过大开销。**\n\n\n![Alt text](https://raw.githubusercontent.com/Syncma/Figurebed/master/img/迭代器.png)\n\n\n\n### 迭代器内存消耗检测\n\n可以看看它的内存占用：\n\n使用python2.7.15+:\n\n```python\nimport sys\ni = iter(range(1000000))\nprint sys.getsizeof(i)\n\n\nr = range(1000000)\nprint sys.getsizeof(r)\n\n\ny = xrange(1000000)    # 注意 xrange跟range的区别： xrange是range的迭代器的表达方式\nprint sys.getsizeof(y)\n```\n\n结果返回:\n```\n56\n8000064\n32\n```\n\n\n使用python 3.6+\n```python\nimport sys\ni = iter(range(1000000))\nprint (sys.getsizeof(i))\n\n\nr = range(1000000)\nprint (sys.getsizeof(r))\n\n```\n\n返回结果：\n```\n32\n48\n```\n\n\n这里有个问题：为什么在python2跟Python3的运行结果相差这么大呢？\n**这是因为python3内部机制已经将range转换成了一个迭代器了。**\n\n这里可以看的出来适合大的数据，比如好几个G的数据， 使用了迭代器 内存使用大幅度减少，这是迭代器最大的优点。\n\n\n总结下：\n我们简单说迭代器就是访问集合元素，迭代器就是有一个next()方法的对象，而不是通过索引来计数的。\n\n### 迭代器使用\n\n那么我们怎么能访问迭代器里面的元素呢？\n\n迭代器有两个方法 ，分别是**iter()和next()**方法\n\n这两个方法是迭代器最基本的方法\n一个用来获得迭代器对象，一个用来获取容器中的下一个元素。\n\n**itertools是python提供的非常高效创建与使用迭代器的模块**\n\n```python\nfrom itertools import chain\ntest = chain.from_iterable('ABCDEFG')\n# print(test)\n# print(dir(test)) # 查看类具体方法\n\nprint(test.__next__())  # 'A'\nprint(test.__next__())  # 'B'\nprint(test.__next__())  # 'C'\n\ntest2 = chain('AB', 'CDE', 'F')\nprint(list(test2))  # ['A', 'B', 'C', 'D', 'E', 'F']\n```\n\n\n我们知道迭代器是不支持索引的，原因就是索引需实现明元素确占用的内存地址，而迭代器是用到元素的时候才会创建。如下：\n\n```python\ni = iter(range(3))  # 创建迭代器\n# i.index(2)  # 获取元素为2的索引\n# AttributeError: 'range_iterator' object has no attribute 'index'\n\n# 列表\nl = range(3)\nprint(l.index(2))  # 获取索引2\n\n```\n\n这个时候可以使用内建函数enumerate(),这个函数很重要。\n\n```python\nfor i, j in enumerate(iter(['A', 'B', 'C'])):\n    # for i, j in enumerate(['A', 'B', 'C']):\n    print(i, j)\n\n```\n\n运行结果返回：\n```\n0 A\n1 B\n2 C\n```\n\n<br>\n\n可以看下这个函数的源码是怎么写的\n\n```python\nclass enumerate(Iterator[Tuple[int, _T]], Generic[_T]):\n    def __init__(self, iterable: Iterable[_T], start: int = ...) -> None: ...\n    def __iter__(self) -> Iterator[Tuple[int, _T]]: ...\n    if sys.version_info >= (3,):\n        def __next__(self) -> Tuple[int, _T]: ...\n    else:\n        def next(self) -> Tuple[int, _T]: ..\n```\n\n## 生成器\n生成器是迭代器，但你只能遍历它一次(iterate over them once) \n\n因为生成器并没有将所有值放入内存中，而是实时地生成这些值\n\n```python\nmygenerator = (x * x for x in range(3))  # 生成器\n# mygenerator = [x * x for x in range(3)] # 列表\nfor i in mygenerator:\n    print(\"i=\", i)\n\nfor i in mygenerator:\n    print(\"New=\", i)\n```\n\n运行结果：\n```\ni= 0\ni= 1\ni= 4\n```\n\n注意，你不能执行for i in mygenerator第二次，因为每个生成器只能被使用一次\n\n\n### yield生成器\n\n在Python中，使用生成器可以很方便的支持迭代器协议。\n\n生成器通过生成器函数产生，生成器函数可以通过常规的def语句来定义，但是不用return返回，\n而是用yield一次返回一个结果，在每个结果之间挂起和继续它们的状态，来自动实现迭代协议。\n\n也就是说，yield是一个语法糖，内部实现支持了迭代器协议\n\n同时yield内部是一个状态机，维护着挂起和继续的状态。\n\n\n下面看看生成器的使用：\n```python\ndef Zrange(n):\n    i = 0\n    while i < n:\n        yield i\n        i += 1\n\n\nzrange = Zrange(3)\nprint(zrange)  # <generator object Zrange at 0x000002997DE75468>\nprint([i for i in zrange])  # [0, 1, 2]\n```\n\n在这个例子中，定义了一个生成器函数，函数返回一个生成器对象，然后就可以通过for语句进行迭代访问了。\n\n其实，生成器函数返回生成器的迭代器。 “生成器的迭代器”这个术语通常被称作”生成器”。\n要注意的是生成器就是一类特殊的迭代器。作为一个迭代器，生成器必须要定义一些方法，其中一个就是next()。\n\n如同迭代器一样，我们可以使用next()函数来获取下一个值。\n\n\n### 生成器执行过程\n\n例子：\n```python\ndef Zrange(n):\n    print(\"begin of Zrange\")\n    i = 0\n    while i < n:\n        print(\"before yield:\", i)\n        yield i\n        i += 1\n        print(\"after yield:\", i)\n\n    print(\"begin of Zrange\")\n\n\nzrange = Zrange(3)\n# print(zrange)\n\nprint(zrange.__next__())\nprint(\"-\" * 10)\nprint(zrange.__next__())\nprint(\"-\" * 10)\nprint(zrange.__next__())\n# print(zrange.__next__()) # StopIteration\n```\n\n运行结果：\n```\nbegin of Zrange\nbefore yield: 0\n0\n----------\nafter yield: 1\nbefore yield: 1\n1\n----------\nafter yield: 2\nbefore yield: 2\n2\n```\n\n\n通过结果可以看到：\n* 当调用生成器函数的时候，函数只是返回了一个生成器对象，并没有 执行。\n*  当next()方法第一次被调用的时候，生成器函数才开始执行，执行到yield语句处停止\n* next()方法的返回值就是yield语句处的参数（yielded value）\n* 当继续调用next()方法的时候，函数将接着上一次停止的yield语句处继续执行，并到下一个yield处停止；如果后面没有yield就抛出StopIteration异常\n\n\n\n总结：生成器是迭代器的一种，但功能方法比迭代器多\n\n\n\n\n### 生成器创建\n\n 生成器的创建可以使用yield关键字， 也可以使用生成器表达式\n\n```python\n(x*2 for i in range(10))\n```\n\n\n### 生成器判断\n\n判断是否为生成器函数可用isgeneratorfunction, 判断是否为生成器对象可用isgenerator\n\n```python\nfrom inspect import isgeneratorfunction, isgenerator\n\ng = (i for i in range(3))\nprint(isgenerator(g))  # True\n\n\ndef demo():\n    yield 1\n\n\nprint(isgenerator(demo()))  # True\nprint(isgeneratorfunction(demo()))  # False\nprint(isgeneratorfunction(demo))  # True\n```\n\n\n### yield与协程\n\n```python\ndef coroutine():\n    print(\"coroutine start...\")\n    result = None\n    while True:\n        s = yield result\n        result = 'result:{}'.format(s)\n\n\nc = coroutine()\nc.send(None)  # coroutine start...\n\nprint(c.send(\"first\"))  # result:first\nprint(c.send(\"second\"))  # result:second\n\nc.close()\n\n# c.send(\"hello\")  # StopIteration\n```\n\n"
  },
  {
    "path": "notes/Python/流畅的Python/字典和集合.md",
    "content": "# 第3章 字典和集合\n\n<!-- TOC -->\n\n- [第3章 字典和集合](#第3章-字典和集合)\n    - [字典推导式](#字典推导式)\n    - [setdefault用法](#setdefault用法)\n    - [defaultdict用法](#defaultdict用法)\n    - [字典的变种](#字典的变种)\n    - [collections.Counter 用法](#collectionscounter-用法)\n    - [集合](#集合)\n    - [关于字典，列表，集合运行效率的对比](#关于字典列表集合运行效率的对比)\n\n<!-- /TOC -->\n\n## 字典推导式\n\n可能你见过列表推导时,却没有见过字典推导式,在2.7中才加入的:\n\nd = {key: value for (key, value) in iterable}\n\n举个例子说明：\n```python\niterable = [(1, 'hello'), (2, 'world'), (3, 'happy')]\nd = {key: value for (key, value) in iterable}\n\nprint(d)  # {1: 'hello', 2: 'world', 3: 'happy'}\n\n```\n<br>\n\n## setdefault用法\n\n使用setdefault处理找不到的键\n\n简单例子：\n```python\nd = {'a': 1, 'b': 2}\nd['c'] = 3\n\n# 若原来没有，设置，否则原值不变\nd.setdefault('c', 4)\n\nprint(d)  # {'a': 1, 'b': 2, 'c': 3}\n\n```\n\n字典也有get方法，如下所示：\n```python\nd = {'a': 1, 'b': 2}\n# d['c'] = 3\n\n\n# 使用get 方法设置的默认值不会改变原字典\na = d.get('c', 4)\nprint(a) # 4\n\nprint(d)  # {'a': 1, 'b': 2}\n\n```\n\n`可以看到两者的区别：`\n> get 方法设置的默认值不会改变原字典， 而setdefault设置的默认值会改变原字典的值。\n\n\n再看一个例子：\n\n```python\nsome_dict = {'abc': 'a', 'cdf': 'b', 'gh': 'a', 'fh': 'g', 'hfz': 'g'}\nnew_dict = {}\n\nfor k, v in some_dict.items():\n    new_dict.setdefault(v, []).append(k)\n\nprint(new_dict)  # {'a': ['abc', 'gh'], 'b': ['cdf'], 'g': ['fh', 'hfz']}\n\n```\n\n\n<br>\n\n##  defaultdict用法\n\n所有的映射类型在处理找不到的键的时候，都会牵扯到`__missing__`方法。这也是这个方法称作“missing”的原因。虽然基类dict并没有定义这个方法，但是dict是知道有这么个东西存在的.\n\n举个例子来说：\n```python\nclass Dict(dict):\n    def __missing__(self, key):\n        self[key] = []\n        return self[key]\n\n\ndct = Dict()\nprint(dct[\"foo\"])  # []\n\ndct[\"foo\"].append(1)\ndct[\"foo\"].append(2)\nprint(dct[\"foo\"])  # [1,2]\n\n```\ndefaultdict是属于collections 模块下的一个工厂函数，用于构建字典对象，\n接收一个函数（可调用）对象为作为参数。\n参数返回的类型是什么，key对应value就是什么类型\n\n```python\n\nfrom collections import defaultdict\n\n# 参数为 list，它就会构建一个默认value为list的字典，\n# 例如result['a']的值默认就是list对象。\ndct = defaultdict(list)\n\nprint(dct)  # defaultdict(<class 'list'>, {})\n\nprint(dct[\"a\"])  # []\ndct[\"a\"].append(\"hello\")\nprint(dct)  # defaultdict(<class 'list'>, {'a': ['hello']})\n\n```\n\n看一个例子：\n```python\nfrom collections import defaultdict\n\nresult = defaultdict(list)\n\ndata = [(\"p\", 1), (\"p\", 2), (\"p\", 3), (\"h\", 1), (\"h\", 2), (\"h\", 3)]\n\nfor (key, value) in data:\n    result[key].append(value)\n\nprint(result)  # defaultdict(<class 'list'>, {'p': [1, 2, 3], 'h': [1, 2, 3]})\nprint(dict(result))  # {'p': [1, 2, 3], 'h': [1, 2, 3]}\n\n```\n\n再看一个例子：\n```python\n# 字典的统计\nnames = ['leo', 'sam', 'jack', 'peter', 'joe', 'susan']\n# 要变成这样：{3: ['leo', 'sam', 'joe'], 4: ['jack'], 5: ['peter', 'susan']}\n\n# bad\nnums = [len(n) for n in names]\ncontain = {}\nfor k1, k2 in zip(nums, names):\n    print(k1, k2)\n    if k1 not in contain:\n        contain[k1] = [k2]\n    else:\n        contain[k1].append(k2)\nprint(contain)\n\nprint(\"#\" * 10)\n\n# better\nfrom collections import defaultdict\nd = defaultdict(list)\nfor name in names:\n    key = len(name)\n    d[key].append(name)\n\nprint(dict(d))\n```\n<br>\n\ndefault性能效率检测\n\n```python\nimport timeit\nfrom collections import defaultdict\n\n\ndef way1():\n    d = {}\n    chars = ['a', 'b', 'c', 'c', 'a', 'a'] * 10000\n    for c in chars:\n        if c in d:\n            d[c] += 1\n        else:\n            d[c] = 1\n    return d\n\n\ndef way2():\n    d = defaultdict(int)\n    chars = ['a', 'b', 'c', 'c', 'a', 'a'] * 10000\n    for c in chars:\n        d[c] += 1\n\n    return d\n\n\nif __name__ == \"__main__\":\n    t1 = timeit.timeit(\"way1\", setup=\"from __main__ import way1\", number=10)\n    print(t1)  # 6e-07(表示6x10^(-7),也就是6x0.0000001,如果写成6e07表示6x10^7)\n\n    t2 = timeit.timeit(\"way2\", setup=\"from __main__ import way2\", number=10)\n    print(t2)  # 4.000000000000097e-07\n\n```\n\n\n> 对于字典的使用，我们要学会用**defaultdict**来代替，\n> 一来是因为有缺省值非常安全，如果访问不存在的key，不会报错；\n> 二来是Pyhon性能会大幅提高。\n仅仅换了字典数据结构，性能就大幅的提高了很多。\n\n<br>\n\n##  字典的变种\n\n\npython字典 dict 默认是无序的，要有序请用collection中的orderdict\n\npython 2 代码：\n```python\nimport collections\n\nd = {}\nd['name'] = 'zhf'\nd['age'] = 33\nd['city'] = 'chengdu'\n\n\n\nfor k, v in d.items():\n    print k, v\n\na = collections.OrderedDict()\na['name'] = 'zhf'\na['age'] = 33\na['city'] = 'chengdu'\n\n\nprint \"*\" * 10\nfor k, v in a.items():\n    print k,v\n\n```\n返回结果：\n```\ncity chengdu\nage 33\nname zhf\n**********\nname zhf\nage 33\ncity chengdu\n\n```\n\npython3.6.7 代码：\n```python\nimport collections\n\nd = {}\nd['name'] = 'zhf'\nd['age'] = 33\nd['city'] = 'chengdu'\n\nfor k, v in d.items():\n    print(k, v)\n\na = collections.OrderedDict()\na['name'] = 'zhf'\na['age'] = 33\na['city'] = 'chengdu'\n\nprint(\"*\" * 10)\nfor k, v in a.items():\n    print(k, v)\n\n```\n\n运行结果：\n```\nname zhf\nage 33\ncity chengdu\n**********\nname zhf\nage 33\ncity chengdu\n\n```\n\n> `可以看到3.6版本的Python已经使得dict变得紧凑以及关键字变得有序`\n\n\n<br>\n\n##  collections.Counter 用法\n\n简单例子：\n```python\nimport collections\n\nstring = \"aaabbbc\"\n\nct = collections.Counter(string)\nprint(ct)  # Counter({'a': 3, 'b': 3, 'c': 1})\nprint(dict(ct))  # {'a': 3, 'b': 3, 'c': 1}\n\nct.update('abcdef')\nprint(dict(ct))  # {'a': 4, 'b': 4, 'c': 2, 'd': 1, 'e': 1, 'f': 1}\n\n```\n<br>\n\n##  集合\n\n集合的本质是许多唯一对象的聚集。因此集合可以去掉重复的元素\n\n\n```python\nd = ['abc', 'def', 'abc', 'def']\n\ns = set(d)\n\nprint(s)  # {'def', 'abc'}\n\n```\n\n假设有2个集合a和b，需要统计集合a的哪些元素在集合b中也出现。\n如果不使用集合，代码只能写成下面的形式:\n```python\na = ['abc', 'def', 'aaa']\nb = ['abc', 'bbb', 'ccc', 'def']\n\nfor n in a:\n    if n in b:\n        print(n)\n```\n但是如果使用集合，则不用这么麻烦。\n在集合中。a|b返回的是合集，a&b返回的是交集。\na-b返回的是差集。**-差集是指属于A但不属于B的结合**\n\n```python\na = ['abc', 'def', 'aaa']\nb = ['abc', 'bbb', 'ccc', 'def']\n\nprint(set(a) & set(b))\nprint(set(a) | set(b))\nprint(set(a) - set(b)) # 相等于 print(set(a).difference(b))\n```\n\n结果返回：\n```\n{'def', 'abc'}\n{'aaa', 'def', 'ccc', 'abc', 'bbb'}\n{'aaa'}\n```\n<br>\n\n## 关于字典，列表，集合运行效率的对比\n\n列表交集代码：\n\n```python\nfrom time import time\n\nt = time()\nlista = [1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 34, 53, 42, 44]\nlistb = [2, 4, 6, 9, 23]\nintersection = []\n\nfor i in range(1000000):\n    for a in lista:\n        for b in listb:\n            if a == b:\n                intersection.append(a)\n\nprint(\"total run time:%s\" % (time() - t))\n# total run time:5.015027046203613\n```\n\n集合交集代码：\n```python\nfrom time import time\nt = time()\n\nlista = [1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 34, 53, 42, 44]\nlistb = [2, 4, 6, 9, 23]\nintersection = []\n\nfor i in range(1000000):\n    list(set(lista) & set(listb))\n\nprint(\"total run time:%s\" % (time() - t))\n# total run time:1.0969467163085938\n```\n\n\n字典代码：\n```python\nfrom time import time\n\nt = time()\nlist = [\n    'a', 'b', 'is', 'python', 'jason', 'hello', 'hill', 'with', 'phone',\n    'test', 'dfdf', 'apple', 'pddf', 'ind', 'basic', 'none', 'baecr', 'var',\n    'bana', 'dd', 'wrd'\n]\n# list = dict.fromkeys(list, True) # total run time:0.9107456207275391\nprint(list)\nfilter = []\nfor i in range(1000000):\n    for find in ['is', 'hat', 'new', 'list', 'old', '.']:\n        if find not in list:\n            filter.append(find)\nprint(\"total run time:%s\" % (time() - t))\n# total run time:2.0197031497955322\n```\n\n\nPython 字典中使用了 hash table，因此查找操作的复杂度为 O(1)，而 list 实际是个数组，在 list 中，查找需要遍历整个 list，其复杂度为 O(n)，因此对成员的查找访问等操作字典要比 list 更快。\n\n因此在需要多数据成员进行频繁的查找或者访问的时候，`使用 dict 而不是 list 是一个较好的选择`\n\n> 一个良好的算法能够对性能起到关键作用，因此性能改进的首要点是对算法的改进。\n> 在算法的时间复杂度排序上依次是：\n**O(1) -> O(lg n) -> O(n lg n) -> O(n^2) -> O(n^3) -> O(n^k) -> O(k^n) -> O(n!)**"
  },
  {
    "path": "notes/Python/流畅的Python/对象引用-可变性和垃圾回收.md",
    "content": "# 第8章-对象引用、可变性和垃圾回收\n\n\n<!-- TOC -->\n\n- [第8章-对象引用、可变性和垃圾回收](#%e7%ac%ac8%e7%ab%a0-%e5%af%b9%e8%b1%a1%e5%bc%95%e7%94%a8%e5%8f%af%e5%8f%98%e6%80%a7%e5%92%8c%e5%9e%83%e5%9c%be%e5%9b%9e%e6%94%b6)\n  - [浅拷贝](#%e6%b5%85%e6%8b%b7%e8%b4%9d)\n  - [深拷贝](#%e6%b7%b1%e6%8b%b7%e8%b4%9d)\n  - [两者区别](#%e4%b8%a4%e8%80%85%e5%8c%ba%e5%88%ab)\n\n<!-- /TOC -->\n\n## 浅拷贝\n\n**`copy 浅复制，不会拷贝其子对象，修改子对象，将受影响`**\n\n```python\nimport copy\na = [1, 2, 3]\nb = copy.copy(a)\n\nprint(\"id a=\", id(a))  # id a= 1669348705224\nprint(\"id b=\", id(b))  # id b= 1669348161608\n\nprint(\"id a 0=\", id(a[0]))  # id a 0= 1586588704\nprint(\"id b 0=\", id(b[0]))  # id b 0= 1586588704\n\na[0] = \"hello\"\nprint(\"new a=\", a)  # new a= ['hello', 2, 3]\nprint(\"new b=\", b)  # new b= [1, 2, 3]\n\nb[0] = \"world\"\nprint(\"new 2 a=\", a)  # new a= ['hello', 2, 3]\nprint(\"new 2 b=\", b)  # new 2 b= ['world', 2, 3]\n\n\n```\n\n<br>\n\n## 深拷贝\n**`deepcopy 深复制，将拷贝其子对象，修改子对象，将不受影响`**\n\n```python\nimport copy\na = [1, 2, 3]\nb = copy.deepcopy(a)\n\nprint(\"id a=\", id(a))  # id a= 2760920581064\nprint(\"id b=\", id(b))  # id b= 2760920037448\n\nprint(\"id a 0=\", id(a[0]))  # id a 0= 1586588704\nprint(\"id b 0=\", id(b[0]))  # id b 0= 1586588704\n\na[0] = \"hello\"\nprint(\"new a=\", a)  # new a= ['hello', 2, 3]\nprint(\"new b=\", b)  # new b= [1, 2, 3]\n\nb[0] = \"world\"\nprint(\"new 2 a=\", a)  # new a= ['hello', 2, 3]\nprint(\"new 2 b=\", b)  # new 2 b= ['world', 2, 3]\n```\n\n## 两者区别\n\n发现**对于不可变对象**，比如整数、字符串、元组、还有由这些不可变对象组成的集合对象，**浅拷贝和深拷贝没有区别，都是拷贝一个新对象**\n\n\n**`两者的区别在于拷贝组合对象`**，比如列表中还有列表，字典中还有字典或者列表的情况时，浅拷贝只拷贝了外面的壳子，里面的元素并没有拷贝，而深拷贝则是把壳子和里面的元素都拷贝了一份新的。\n\n看一个例子：\n\n```python\nimport copy\n\na = [0, [1, 2], 3]\nb = copy.copy(a)\nc = copy.deepcopy(a)\n\nc[1][0] = \"hello\"\nprint(\"a=\", a)  # a= [0, [1, 2], 3]\nprint(\"c=\", c)  # c= [0, ['hello', 2], 3]\n\nb[1][0] = \"world\"\n\nprint(\"new a=\", a)  # new a= [0, ['world', 2], 3]\nprint(\"new b=\", b)  # new b= [0, ['world', 2], 3]\n```\n\n<br>\n\n**小提示：**\n\n> 可以利用[python在线调试](http://www.pythontutor.com/visualize.html#mode=display)进行相关调试分析\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "notes/Python/流畅的Python/属性描述符.md",
    "content": "# 第20章 属性描述符\n\n"
  },
  {
    "path": "notes/Python/流畅的Python/常见问题答疑.md",
    "content": "# 常见问题答疑\n<!-- TOC -->\n\n- [常见问题答疑](#%e5%b8%b8%e8%a7%81%e9%97%ae%e9%a2%98%e7%ad%94%e7%96%91)\n  - [1.Python 类self方法](#1python-%e7%b1%bbself%e6%96%b9%e6%b3%95)\n\n<!-- /TOC -->\n\n## 1.Python 类self方法\n\nPython的类的方法和普通的函数有一个很明显的区别，在类的方法必须有个额外的第一个参数(self)，但在调用这个方法的时候不必为这个参数赋值\n\nPython的类的方法的这个特别的参数指代的是对象本身，而按照Python的惯例，它用self来表示。（当然我们也可以用其他任何名称来代替，只是规范和标准在那建议我们一致使用self）\n\n**为何Python给self赋值而你不必给self赋值？**\n\n例子说明：\n\n创建了一个类MyClass，实例化MyClass得到了MyObject这个对象，然后调用这个对象的方法MyObject.method(arg1,arg2)，\n\n这个过程中，Python会自动转为Myclass.mehod(MyObject,arg1,arg2)\n这就是Python的self的原理了。\n\n即使你的类的方法不需要任何参数，但还是得给这个方法定义一个self参数，虽然我们在实例化调用的时候不用理会这个参数不用给它赋值。\n\n\n实例：\n\n```python\nclass Demo(object):\n    def hello(self):\n        print('Hello')\n\n\np = Demo()\np.hello()  # hello\nDemo.hello(p)  # hello\n\n```\n\n如果把self去掉的话，这样就报错了\n```\nTypeError: hello() takes no arguments (1 given)\n```\n\n\n**self在Python里不是关键字**\n\n**self代表当前对象的地址**\n\n```python\nclass Demo(object):\n    def hello(self):\n        print(\"self=\", self) \n        print('Hello')\n\n\np = Demo()\np.hello()  \n# self= <__main__.Demo object at 0x000001CD84029EF0>\n# hello\n```\n\n"
  },
  {
    "path": "notes/Python/流畅的Python/序列构成的数组.md",
    "content": "# 第2章 序列构成的数组\n\n<!-- TOC -->\n\n- [第2章 序列构成的数组](#第2章-序列构成的数组)\n    - [列表推导](#列表推导)\n    - [数组与列表效率对比](#数组与列表效率对比)\n    - [使用memoryview模块](#使用memoryview模块)\n    - [bisect模块用法](#bisect模块用法)\n    - [pickle模块用法](#pickle模块用法)\n    - [双向队列](#双向队列)\n\n<!-- /TOC -->\n\n第二章开始介绍了列表这种数据结构，这个在python是经常用到的结构\n\n## 列表推导\n列表的推导，将一个字符串编程一个列表，有下面的2种方法。\n其中第二种方法更简洁。可读性也比第一种要好。\n\n\n```python\nstr = 'abc'\nstring = []\n\n# 第一种方法\nfor s in str:\n    print(string.append(s))\n\n# 第二种方法\nret = [s for s in str]\nprint(ret)\n```\n\n> 用这种for…in的方法来推导列表，有个好处就是不会有变量泄露也就是越界的问题\n\n列表的推导还有一种方式，称为生成器表达式。\n表达式都差不多，不过是方括号编程了圆括号而已\n\n`生成器的好处是什么呢?`\n\n> 列表推导是首先生成一个组合的列表，这会占用到内存。\n而生成式则是在每一个for循环运行时才生成一个组合，这样就不会预先占用内存\n生成器表达式不会一次将整个列表加载到内存之中，而是生成一个生成器对象(Generator objector)，所以一次只加载一个列表元素\n\n举个例子来说明：\n\n有20000个数组的列表。分别用列表推导法和生成式表达法来进行遍历。\n并用`memory_profiler`\t来监控代码占用的内存\n\n列表推导法例子：\n```python\n# 如果系统没有这个模块就执行pip install memory_profiler进行安装\nfrom memory_profiler import profile\n\n\n@profile\ndef fun_try():\n    test = []\n\n    for i in range(20000):\n        test.append(i)\n\n    for num in [t for t in test]:\n        print(num)\n\n\nfun_try()\n```\n\n这个代码运行结果是：\n\n```\n\tLine #    Mem usage    Increment   Line Contents\n================================================\n     5     39.9 MiB     39.9 MiB   @profile\n     6                             def fun_try():\n     7     39.9 MiB      0.0 MiB       test = []\n     8\n     9     40.8 MiB      0.0 MiB       for i in range(20000):\n    10     40.8 MiB      0.1 MiB           test.append(i)\n    11\n    12     41.2 MiB      0.2 MiB       for num in [t for t in test]:\n    13     41.2 MiB      0.0 MiB           print(num)\n\n```\n\n<br>\n生成式例子：\n\n```python\n# 如果系统没有这个模块就执行pip install memory_profiler进行安装\nfrom memory_profiler import profile\n\n\n@profile\ndef fun_try():\n    test = []\n\n    for i in range(20000):\n        test.append(i)\n\n    for num in (t for t in test):\n        print(num)\n\n\nfun_try()\n\n```\n\n这个代码运行结果是:\n```\nLine #    Mem usage    Increment   Line Contents\n================================================\n     5     40.1 MiB     40.1 MiB   @profile\n     6                             def fun_try():\n     7     40.1 MiB      0.0 MiB       test = []\n     8\n     9     41.1 MiB      0.0 MiB       for i in range(20000):\n    10     41.1 MiB      0.1 MiB           test.append(i)\n    11\n    12     41.1 MiB      0.0 MiB       for num in (t for t in test):\n    13     41.1 MiB      0.0 MiB           print(num)\n\n```\n<br>\n\n结论：\n\n `通过这两个结果可以看到列表推导法增加了0.2MB的内存\n 除非特殊的原因，应该经常在代码中使用生成器表达式。\n 但除非是面对非常大的列表，否则是不会看出明显区别的。\n`\n\n<br>\n下面介绍下元组。说到元组，第一个反应就应该是不可变列表。\n但作者同时还介绍了元组的很多其他特性。\n首先来看下元组的拆包。\n\n```python\n#元组拆包\nt = (20, 8)\na, b = t\nprint(a, b)\n```\n\n如果在进行拆包的同时，并不是对所有的元组数据都感兴趣。\n**_占位符**就能帮助处理这种情况\n\n```python\nimport os\n _, filename = os.path.split(\"/home/jian/prj/demo.txt\")\nprint(_, filename)  #/home/jian/prj demo.txt\n```\n上面元组拆包的时候是对可迭代对象进行遍历，然后一一赋值到变量。\n但是如果想通过给每个元组元素的命名来访问，则需用到命名元组namdtuple\n\n```python\n# collections.namedtuple构建一个带有字段名的元组和一个有名字的类\nfrom collections import namedtuple, OrderedDict\n\nCity = namedtuple('City', 'name country population')\ntokyo = City('Tokyo', 'JP', '123')\ntokyo_data = ('Tokyo', 'JP', '123')\n\nprint(tokyo)  # City(name='Tokyo', country='JP', population='123')\nprint(tokyo.name, tokyo.country, tokyo.population)  # Tokyo JP 123\nprint(City._fields)  # ('name', 'country', 'population')\n\ntokyo = City._make(tokyo_data)\nprint(tokyo)  # City(name='Tokyo', country='JP', population='123')\n\nOrderedDict([('name', 'Tokyo'), ('country', 'JP'), ('population', '123')])\nprint(tokyo._asdict())\n# OrderedDict([('name', 'Tokyo'), ('country', 'JP'), ('population', '123')])\n\n```\n<br>\n\n**符号用来处理剩下的元素**\n\n```python\na, b, *rest = range(5)\nprint(a, b, rest)  # 0 1 [2, 3, 4]\n\na, *b, rest = range(5)\nprint(a, b, rest)  # 0 [1,2,3] 4\n\n```\n\n\n* 序列的操作：\n增量赋值：\n增量运算符+，*等其实是调用`__iadd__/__add__/__mul__`方法。\n**对于可变对象来说，+调用的是`__iadd__`，对于不可变对象来说对象调用的是`__add__`。**\n\n**两者有什么区别呢。**\n\n先看下面的例子\n```python\nstr = [1, 2, 3]\nstr1 = (1, 2, 3)\n\nprint(\"str:%d\" % id(str))\nprint(\"str1:%d\" % id(str1))\n\nstr += str\nstr1 += str1\n\nprint(\"str:%d\" % id(str))\nprint(\"str1:%d\" % id(str1))\n```\n\n得到的结果如下：\n\n```\nstr:2256630301640\nstr1:2256630239736\nstr:2256630301640\nstr1:2256630606152\n\n```\n\nstr是列表,str1是元组，列表是可变对象，元组是不可变对象。\n\n在进行加法运算后，str的id没有改变，因此还是之前的对象，但是str1的id却发生了改变，不是之前的对象了。\n\n这种的差别在于`__iadd__`的方法类似调用a.extend(b)的方式，是在原有的对象上进行扩展操作。\n\n但是`__add__`的方式类似于a=a+b。\n首先a+b得到一个新的的对象，然后复制给a，因此变量和之前的对象没有任何联系。\n而是被关联到一个新的对象。同样的乘法`__imul__/__mul__`也是类似的道理\n\n列表组成的列表：\n```python\nboard = [['_'] * 3 for i in range(3)]\nprint(board)\n\nboard[1][2] = 'x'\nprint(board)\n\n```\n运行结果：\n```\n[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]\n[['_', '_', '_'], ['_', '_', 'x'], ['_', '_', '_']]\n\n```\n\n输出三个列表的ID\n```python\nboard = [['_'] * 3 for i in range(3)]\nprint(board)\n\nboard[1][2] = 'x'\nprint(board)\n\n# 输出3个列表的ID\nprint(id(board[0]))\nprint(id(board[1]))\nprint(id(board[2]))\n\n```\n\n结果是分别属于不同的ID：\n```\n3221578302664\n3221578302536\n3221578302600\n```\n\n我们再来看下另外一种用法。下面的代码对一个包含3个列表的列表进行*3的操作。\n```python\nboard = [['_'] * 3] * 3\nprint(board)\n\nboard[1][2] = 'x'\nprint(board)\nprint(id(board[0]))\nprint(id(board[1]))\nprint(id(board[2]))\n```\n\n运行结果是：\n发现id都一样。说明了全部指向的是同一个对象。\n```\n[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']]\n[['_', '_', 'x'], ['_', '_', 'x'], ['_', '_', 'x']]\n1195278432328\n1195278432328\n1195278432328\n```\n\n如果对不可变对象中的可变对象进行赋值会产生什么后果，比如下面的这段代码\n```python\nt = (1, 2, [30, 40])\nt[2] += [50, 60]\n\nprint(t)\n```\n\n运行结果直接报错：\n```\nTypeError: 'tuple' object does not support item assignment\n```\n\n我们可以把代码放在[python在线调式网站](http://pythontutor.com/visualize.html#mode=edit)进行调试\n\n把代码稍微修改下然后再看看情况\n```python\nt = (1, 2, [30, 40])\nt[2].append([50, 60])\n\nprint(t)\n```\n\n`为什么这两种实现会带来不同的结果呢?`\n\n原因在于t是一个元组属于不可变对象。但用t[2]+=[50,60]的时候是对一个元组进行赋值。\n所以报错误。\n\n但是同时t[2]又属于一个列表可变对象。因此数据也更新成功了\n\n但是如果用t[2].append([50,60])的操作则是对一个列表进行操作，而并没有对一个元组进行赋值。因此能够更新成功且不会报错误。\n\n这是一个很有趣的例子。\n\n对于理解可变对象和不可变对象的操作很有帮助。\n<br>\n\n`查看背后的字节码情况：`\n```python\nimport dis\na = (1, 2, 3)\n\nbyte_info = dis.dis('a[2]+=1')\nprint(byte_info)\n```\n\n打印出反编译的结果(反编译后的代码与汇编语言接近)是：\n```\n  1           0 LOAD_NAME                0 (a)\n              2 LOAD_CONST               0 (2)\n              4 DUP_TOP_TWO\n              6 BINARY_SUBSCR\n              8 LOAD_CONST               1 (1)\n             10 INPLACE_ADD\n             12 ROT_THREE\n             14 STORE_SUBSCR\n             16 LOAD_CONST               2 (None)\n             18 RETURN_VALUE\n\n```\n\n>插播一个有趣的例子\n\npython之交换变量值\n\n```python\nimport dis     \n\ndef swap1():\n    x = 5\n    y = 6\n    x, y = y,x\n\ndef swap2():\n    x = 5\n    y = 6\n    tmp = x\n    x = y\n    y = tmp\n\nif __name__ == \"__main__\":\n    print (\"***SWAP1***\")\n    print (dis.dis(swap1))\n    print (\"***SWAP2***\")\n    print (dis.dis(swap2))\n```\n\n查看结果：\n```\n*** SWAP1***\n  5           0 LOAD_CONST               1 (5)\n              3 STORE_FAST               0 (x)\n  6           6 LOAD_CONST               2 (6)\n              9 STORE_FAST               1 (y)\n  7          12 LOAD_FAST                1 (y)\n             15 LOAD_FAST                0 (x)\n             18 ROT_TWO             \n             19 STORE_FAST               0 (x)\n             22 STORE_FAST               1 (y)\n             25 LOAD_CONST               0 (None)\n             28 RETURN_VALUE      \n             \n***SWAP2***\n10           0 LOAD_CONST               1 (5)\n              3 STORE_FAST               0 (x)\n11           6 LOAD_CONST               2 (6)\n              9 STORE_FAST               1 (y)\n12          12 LOAD_FAST                0 (x)\n             15 STORE_FAST               2 (tmp)\n13          18 LOAD_FAST                1 (y)\n             21 STORE_FAST               0 (x)\n14          24 LOAD_FAST                2 (tmp)\n             27 STORE_FAST               1 (y)\n             30 LOAD_CONST               0 (None)\n             33 RETURN_VALUE        \n```\n\n得到结论\n>通过字节码可以看到，swap1和swap2最大的区别在于，swap1中通过ROT_TWO交换栈顶的两个元素实现x和y值的互换，swap2中引入了tmp变量，多了一次LOAD_FAST, STORE_FAST的操作。\n>\n>执行一个ROT_TWO指令比执行一个LOAD_FAST+STORE_FAST的指令快，\n>\n>**这也是为什么swap1比swap2性能更好的原因**。\n\n<br>\n\n* 数组：\n\n在列表和元组中，存放的是具体的对象，如整数对象，字符对象。\n如下面的整数b。占据28个字节。因为存放的是整数对象，而非整数本身。\n\n```python\nimport sys\nb = 1\n\nprint(sys.getsizeof(b)) # 28\n```\n\n对于存放大量数据来说。我们选择用数组的形式要好很多。\n**因为数组存储的不是对象，而是数字的机器翻译。也就是字节表述。**\n\n![Alt text](https://raw.githubusercontent.com/Syncma/Figurebed/master/img/1183125-20170617154733759-2053769655.png)\n\n\n在array中需要规定各个字符的类型，如上表中的Type code。\n定义好类型后则数组内的元素必须全是这个类型，否则会报错。\n\n就像下面的代码。类型规定的是b也就是单字节的整数。\n但是在插入的时候却是c字符，则报错：`TypeError: an integer is required`\n\n```python\nimport array\n\nnum = array.array('b')\nnum.append('c')\n\nprint(num)\n```\n\n<br>\n\n**在上表中，每个类型都有字节大小的限制。如果超出了字节大小的限制也是会报错的**\n\n还是b的这个类型，是有符号的单字节整数，那么范围是-128到127.\n如果我们插入128.则报错：`signed char is greater than maximum` 提示超过了最大\n\n```python\nimport array\n\nnum = array.array('b')\nnum.append(128)\n\nprint(num)\n```\n\n对于数组这种结构体来说，由于占用的内存小，因此在读取和写入文件的时候的速度更快，相比于列表来说的话。\n\n## 数组与列表效率对比\n下面来对比下：\n首先是用列表生成并写入txt文档的用法\n\n```python\nimport time\nimport struct\n\n# struct 例子\n# a = 20\n# 'i'表示一个int\n# # struct.pack把python值转化成字节流\n# data = struct.pack('i', a)\n# print(len(data))\n# print(repr(data))\n# print(data)\n\n# 拆包\n# a1 = struct.unpack('i', data)\n# print(\"a1=\", a1)\n\n\ndef arry_try_list():\n\n    floats = [float for float in range(10**7)]\n\n    fp = open('list.bin', 'wb')\n    start = time.clock()\n\n    for f in floats:\n\n        strdata = struct.pack('i', f)\n        fp.write(strdata)\n\n    fp.close()\n\n    end = time.clock()\n\n    print(end - start)\n\n\narry_try_list()\n\n```\n\n执行结果：5.8789385\n\n\n再看数组的形式：\n```python\nfrom array import array\nfrom random import random\nimport time\n\n\ndef array_try():\n\n    floats = array('d', (random() for i in range(10**7)))\n    start = time.clock()\n\n    fp = open('floats.bin', 'wb')\n    floats.tofile(fp)\n\n    fp.close()\n\n    end = time.clock()\n    print(end - start)\n\n\narray_try()\n```\n执行结果： 0.045192899999999994\n\n可以看到速度明显提升了很多\n\n再来对比读文件的速度\n```python\nfrom array import array\nimport time\n\n\ndef array_try():\n\n    floats = array('d')\n    start = time.clock()\n\n    fp = open('floats.bin', 'rb')\n    # 比直接从文本文件里面读快，后者使用内置的float方法把每一行文字变成浮点数\n    floats.fromfile(fp, 10**7)\n    fp.close()\n\n    end = time.clock()\n    print(end - start)\n\n\narray_try()\n```\n\n执行结果是：0.1172238\n\n***\n## 使用memoryview模块\n精准地修改了一个数组的某个字节\n```python\n#memoryview内存视图\n#在不复制内容的情况下操作同一个数组的不同切片\nfrom array import array\n\n#5个短整型有符号整数的数组，类型码是h\nnumbers = array('h', [-2, -1, 0, 1, 2])\n\nmemv = memoryview(numbers)\nprint(len(memv))\n\n#转成B类型，无符号字符\nmemv_oct = memv.cast('B')\ntolist = memv_oct.tolist()\nprint(tolist)\n\nmemv_oct[5] = 4\nprint(numbers)\n```\n\n***\n## bisect模块用法\n\n```python\n\nimport bisect\n\n# 以空格作为分隔打印S中所有元素再换行.\ndef print_all(S):\n    for x in S:\n        print(x, end = \" \")\n    print(\"\")\n\n# 有序向量SV.\nSV = [1, 3, 6, 6, 8, 9]\n\n#查找元素.\nkey = int(input())\nprint(bisect.bisect_left(SV, key))\nprint(bisect.bisect_right(SV, key))\n\n# 插入新元素.\nkey = 0\nbisect.insort_right(SV, key)\n\n# 删除重复元素的最后一个. 思考: 如何删除第一个?\nkey = 6\ni = bisect.bisect_right(SV, key)\ni -= 1\n# 注意此时i < len(SV)必然成立. 如果确实有key这个元素则删除.\nif (i > 0 and SV[i] == key): del(SV[i])\nprint_all(SV)\n\n# 删除重复key所在区间: [bisect.bisect_left(SV, key):bisect.bisect_right(SV, key)).\ndel SV[bisect.bisect_left(SV, key):bisect.bisect_right(SV, key)]\nprint_all(SV)\n\n# 无序向量USV.\nUSV = [9, 6, 1, 3, 8, 6]\n\n# 插入新元素\nkey = 0\nUSV.append(key)\n\n# 删除重复元素的最后一个.\nkey = 6\n# 逆向遍历, 如果存在则删除.\ni = len(USV) - 1\nwhile i > 0:\n    if USV[i] == key:\n        USV[i] = USV[-1]\n        USV.pop()\n        break\n    i -= 1\nprint_all(USV)\n\n# 删除重复元素的第一个.\ntry:\n    i = USV.index(key)\nexcept:\n    pass\nelse:\n    USV[i] = USV[-1]\n    USV.pop()\nprint_all(USV)\n```\n***\n##pickle模块用法\npickle模块是将python值转成成byte\n```python\ntry:\n    import cPickle as pickle\nexcept:\n    import pickle\n\ndata = [{'a': 'A', 'b': 2, 'c': 3.0}]\n\n# 编码\ndata_string = pickle.dumps(data)\n\nprint(\"DATA:\", data)\nprint(\"PICKLE:\", data_string)\nprint(type(data_string))\n\n# 解码\ndata_from_string = pickle.loads(data_string)\nprint(data_from_string)\n\n```\n\n***\n## 双向队列\n双向队列deque\n\n```python\nimport timeit\nfrom collections import deque\n\n\ndef way1():\n    mylist = list(range(100000))\n    for i in range(100000):\n        mylist.pop()\n\n\ndef way2():\n    mydeque = deque(range(100000))\n    for i in range(100000):\n        mydeque.pop()\n\n\nif __name__ == \"__main__\":\n    t1 = timeit.timeit(\"way1\", setup=\"from __main__ import way1\", number=10)\n    print(t1)\n\n    t2 = timeit.timeit(\"way2\", setup=\"from __main__ import way2\", number=10)\n    print(t2)\n\n```\n\n结果：\n```\n6e-07\n1.0000000000001327e-06\n```\n\n\n\n> **deque是双向队列，如果你的业务逻辑里面需要大量的从队列的头或者尾部删除，添加，用deque的性能会大幅提高！**\n\n> 如果只是小队列，并且对元素需要随机访问操作,那么list会快一些。\n\n\n```python\n# 双向队列用法\n\nfrom collections import deque\n\n# 创建一个队列\nq = deque([1])\nprint(q)\n\n# 往队列中添加一个元素\nq.append(2)\nprint(q)\n\n# 往队列最左边添加一个元素\nq.appendleft(3)\nprint(q)\n\n# 同时入队多个元素\nq.extend([4, 5, 6])\nprint(q)\n\n# 在最左边同时入队多个元素\nq.extendleft([7, 8, 9])\nprint(q)\n\n# 剔除队列中最后一个\nq.pop()\nprint(q)\n\n# 删除队列最左边的一个元素\nq.popleft()\nprint(q)\n\n# 清空队列\n# q.clear()\n\n# 获取队列长度\n# length = q.maxlen\n# print(length)\n\n```"
  },
  {
    "path": "notes/Python/流畅的Python/序列的修改-散列和切片.md",
    "content": "# 第10章-序列的修改、散列和切片\n\n<!-- TOC -->\n\n- [第10章-序列的修改、散列和切片](#%e7%ac%ac10%e7%ab%a0-%e5%ba%8f%e5%88%97%e7%9a%84%e4%bf%ae%e6%94%b9%e6%95%a3%e5%88%97%e5%92%8c%e5%88%87%e7%89%87)\n  - [反射](#%e5%8f%8d%e5%b0%84)\n    - [作用](#%e4%bd%9c%e7%94%a8)\n    - [Python反射方法](#python%e5%8f%8d%e5%b0%84%e6%96%b9%e6%b3%95)\n\n<!-- /TOC -->\n\n\n## 反射\n\n**反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力**，在python中一切皆对象（类，实例，模块等等都是对象），那么我们就可以通过反射的形式操作对象相关的属性。\n\n用字符串数据类型的变量名或者函数名来调用对应的属性\nA.b  getattr(A, 'b')\n\n\n### 作用\n\n1.实现可插拔机制\n\n**可以事先定义好接口，接口只有在被完成后才会真正执行，这实现了即插即用**，这其实是一种‘后期绑定’，什么意思？即你可以事先把主要的逻辑写好（只定义接口），然后后期再去实现接口的功能\n\n\n举个例子：\n\ndemo.py\n```python\nclass FanShe(object):\n    x = 1\n\n    def test1(self):\n        print('this is test1')\n\n    def test(self):\n        print(\"this is test\")\n\n```\n\n\n其他文件调用：\n```python\nimport demo as tf\n\nc = tf.FanShe\n\n# 判断c对象中的test是否存在，存在即调用执行，不存在就执行其他逻辑代码\nif hasattr(c, 'test'):\n    func = getattr(c, 'test')\n    print(func(\"hello\"))\n\nelse:\n    print('不存在此方法')\n    print('处理其他的逻辑')\n```\n\n\n<br>\n\n2.动态导入模块\n\ndemo/`__init__.py`\ndemo/work.py\n\nwork.py\n\n```python\ndef test():\n    print('this is test')\n```\n\n在外层想要调用这个work.py里面的test方法可以使用下面的方法：\n\n```python\nM = __import__('demo.work')\nprint(M)\nM.work.test()\n\n# 可以写成这样\nimport importlib\n\nM = importlib.import_module('demo.work')\nprint(M)\nM.test()\n\n```\n\n### Python反射方法\n\nPython中的反射主要有下面几个方法：\n\n1.hasattr(object,name) \n判断对象中有没有一个name字符串对应的方法或属性\n\n2.getattr(object, name, default=None)\n获取对象name字符串属性的值，如果不存在返回default的值\n\n3.setattr(object, key, value) \n设置对象的key属性为value值，等同于object.key = value\n\n4.delattr(object, name) \n删除对象的name字符串属性\n\n\n```python\nclass Cat(object):\n    class_level = '贵族'\n\n    def __init__(self, name, type, speed, age):\n        self.name = name\n        self.type = type\n        self.speed = speed\n        self.age = age\n\n    def run(self):\n        print('%s岁的%s%s正在以%s的速度奔跑' %\n              (self.age, self.type, self.name, self.speed))\n\n\nxiaohua = Cat('小花', '波斯猫', '10m/s', 10)\nxiaohua.run()  # 10岁的波斯猫小花正在以10m/s的速度奔跑\n\n# hasattr(object, name)  判断对象中有没有一个name字符串对应的方法或属性\nprint(hasattr(xiaohua, 'name'))  # 判断xiaohua有没有name的属性，返回True,说明小花有name的属性，\n# 注意'name'一定要是字符串类型\n\nprint(hasattr(xiaohua, 'size'))  # 返回False说明小花没有size的属性\n\n# 2.getattr(object, name, default=None) 获取对象name字符串属性的值，\n# 如果不存在返回default的值\nprint(getattr(xiaohua, 'speed', '20m/s'))  # 10m/s\n\n# 获取xiaohua的speed的属性，speed的属性存在\nprint(xiaohua.__dict__)\n# {'name': '小花', 'type': '波斯猫', 'speed': '10m/s', 'age': 10}\n\nprint(getattr(xiaohua, 'weight', '5kg'))\n# 5kg, 获取xiaohua的weight属性，此属性不存在，返回default的值\n\n# print(getattr(xiaohua, 'weight'))  # 不设置default时，属性不存在\n# 会报错 AttributeError: 'Cat' object has no attribute 'weigh'\n\n# 3.setattr(object, key, value)# 设置对象的key属性为value值\n# 等同于object.key = value\n\nsetattr(xiaohua, 'weight', '5kg')\n# 给xiaohua增加weight的属性\nprint(xiaohua.__dict__)\n# {'name': '小花', 'type': '波斯猫', 'speed': '10m/s', 'age': 10, 'weight': '5kg'}\n\n# 4.delattr(object, name) 删除对象的name字符串属性\n\ndelattr(xiaohua, 'weight')\nprint(xiaohua.__dict__)\n# {'name': '小花', 'type': '波斯猫', 'speed': '10m/s', 'age': 10}\n```"
  },
  {
    "path": "notes/Python/流畅的Python/序幕.md",
    "content": "#  第1章 序幕\n<!-- TOC -->\n\n- [第1章 序幕](#%e7%ac%ac1%e7%ab%a0-%e5%ba%8f%e5%b9%95)\n  - [tuple和nametuple的区别](#tuple%e5%92%8cnametuple%e7%9a%84%e5%8c%ba%e5%88%ab)\n  - [__repr__和__str__的区别](#repr%e5%92%8cstr%e7%9a%84%e5%8c%ba%e5%88%ab)\n\n<!-- /TOC -->\n\n这一章中作者简要的介绍了python数据模型，主要是python的一些特殊方法。比如`__len__`,`__getitem__`. 并用一个纸牌的程序来讲解了这些方法\n\n## tuple和nametuple的区别\n>**Nametuple是类似于元组的数据类型。**\n\n>**除了能够用索引来访问数据，还支持用方便的属性名来访问数据。**  \n\n传统的元组访问如下。\n```python\ntup1 = ('abc', 'def', 'ghi')\nprint(tup1[1])\n```\n\n对每个元素的访问都必须通过索引来找到。这种找法很不直观\n\n使用nametuple来构造：\n```python\nimport collections\ntup2 = collections.namedtuple('tuple2', ['name', 'age', 'height'])\nt1 = tup2('zhf', '33', '175')\nprint(t1)\nprint(t1.age)\nprint(t1.height)\nprint(t1.name)\n```  \n\n得到结果如下，namedtupel中tuple2是类型名，name,age,height是属性名字从上面的访问可以看到，直接用t1.age的方法访问更加直观。\n\n当然也可以用索引比如t1[0]的方法来访问\n\n程序执行返回结果如下：\n```python\ntuple2(name='zhf', age='33', height='175')\n33\n175\nzhf\n```\n\n\nnamedtupe1也支持迭代访问：\n```python\nfor t in t1:\n    print(t)\n```\n\n<br>\n\n**`和元组一样，namedtupel中的元素也是不可变更的`**\n\n如果执行t1.age+=1。将会提示无法设置元素\n\n```python\n t1.age += 1\nAttributeError: can't set attribute\n```   \n\n下面来看下书中的纸牌例子，代码如下\n```python\nfrom collections import namedtuple\nCard = namedtuple('Card', ['rank', 'suit'])\nclass FrenchDeck(object):\n    ranks = [str(n) for n in range(2, 11)] + list('JQKA')\n    suits = 'spades diamonds clubs hearts'.split()\n    def __init__(self):\n        self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks]\n    def __len__(self):\n        return len(self._cards)\n    def __getitem__(self, position):\n        return self._cards[position]\nif __name__ == '__main__':\n    deck = FrenchDeck()\n    # 如果类中有一个__len__方法， len函数首先就会调用类里面的方法\n    print(len(deck))\n    print(deck[1])\n```\n<br>\n首先定义了的纸牌元组Card， rank代表纸牌数字，suit代表纸牌花色。\n然后在FrenchDeck首先定义了ranks和suit的具体值。\n在`__init__`中对self._cards进行初始化。\n`__len__`反馈self._cards的长度。`__getitem__`反馈具体的纸牌值。\n\n结果如下:\n纸牌的长度为52，其中deck[1]为Card(rank=’3’,suit=’spades’)\n\n可以看到len(deck)其实调用的是`__len__`方法。\ndeck[1]调用的是`__getitem__`由于有了`__getitem__`方法，\n\n还可以进行迭代访问,如下：\n```python\nfor d in deck:\n    print (d)\n```\n<br>\n既然是可迭代的，那么我们可以模拟随机发牌的机制。\n\n```python\nfrom random import choice\n\tprint (choice(deck))\n```\n>随机抽取，使用random.choice标准库\n`要看看choice底层实现原理，搞明白机制`\n\n<br>\n接下来看另外一个例子，关于向量运算的。\n比如有向量1 vector1(1,2),向量2 vector2(3,4)。\n那么vector1+vector2的结果应该是（4,6）。\n\nVector1和vector2都是向量，如何实现运算呢。`方法是__add__,__mul__`\n\n代码如下：\n\n```python\nfrom math import hypot\nclass vector(object):\n    def __init__(self, x=0, y=0):\n        self.x = x\n        self.y = y\n    def __repr__(self):\n        return 'Vector(%r,%r)' % (self.x, self.y)\n    def __abs__(self):\n        return hypot(self.x, self.y)\n    def __bool__(self):\n        return bool(abs(self))\n    def __add__(self, other):\n        x = self.x + other.x\n        y = self.y + other.y\n        return vector(x, y)\n    def __mul__(self, scalar):\n        return vector(self.x * scalar, self.y * scalar)\nif __name__ == '__main__':\n    v1 = vector(1, 2)\n    v2 = vector(2, 3)\n    print(v1 + v2)\n    print(abs(v1))\n    print(v1 * 3)\n\n```\n返回结果：\n```\nVector(3,5)\n2.23606797749979\nVector(3,6)\n```\n\n\n\n在这里`__add__,_ _mul_ __,__abs__`分别实现了向量加法，乘法，以及求模的运算。\n\n值得一提的是`__repr__`的方法。\n\n这个方法是在需要打印对象的时候调用。\n\n例如print vector(1,2)的时候得到vector(1,2). \n\n否则就是表示对象的字符串：<Vector object at 0x0000>.\n\n这个__repr__和__str__的作用是类似的\n\n这里要知道`__repr__和__str__`的区别在哪里 [^1]\n\n\n<br>\n\n## `__repr__和__str__`的区别\n\n主要区别如下：\n\n*  **`区别1`**\n>  如果_`_str__ 存在会首先调用__str__  `\n>  如果`__str__ 不存在会调用__repr__`\n\n测试代码如下：\n``` python\n\nclass DemoClass(object):\n    def __repr__(self):\n        return 'DemoClass repr'\n\n    def __str__(self):\n        return 'DemoClass str'\n\n\ndemo = DemoClass()\nprint(demo)\t#返回是:DemoClass str\n```\n\n```python\nclass DemoClass(object):\n    def __repr__(self):\n        return 'DemoClass repr'\n\n\ndemo = DemoClass()\nprint(demo)  # DemoClass repr\n\n```\n<br>\n\n* **`区别2`**\n```python\nclass DemoClass(object):\n    def __init__(self, name):\n        self.name = name\n\n    def __repr__(self):\n        # %r 字符串 (采用repr()的显示)\n        return \"repr My name is %r\" % (self.name)\n\n    def __str__(self):\n        return \"str My name is %s\" % (self.name)\n\n\ndemo = DemoClass(\"tony\")\nprint(demo)  # 返回\"str My name is tony\"\n\n# 使用repr(obj)的时候，会自动调用__repr__函数\nprint(repr(demo))  # 返回\"repr My name is tony\"\n\ndemo1 = DemoClass(\"'tony1\")\nprint(demo1)  # str My name is 'tony1\nprint(repr(demo1))  # repr My name is \"'tony1\"\n\n```\n   \n   可以看到有一些区别的\n> `__repr__`所返回的字符串应该准确、无歧义，并且尽可能表达出如何用代码创建出这个被打印的对象。\n\n> `__repr__和__str__`的区别在于，后者是在str（）函数被使用，或是在用print函数打印一个对象的时候才被调用的，并且它返回的字符串对终端用户更友好。\n\n<br>\n再比如下面一个例子：\n\n``` python\nclass DemoClass(object):\n    def __init__(self, name):\n        self.name = name\n\n    def say(self):\n        print(self.name)\n\n    def __repr__(self):\n        return \"DemoClass('jacky')\"\n\n    def __str__(self):\n        return \"str My name is %s\" % (self.name)\n\n\ndemo = DemoClass(\"tony\")\ndemo.say()  # tony\n\ndemo_repr = type(repr(demo))\nprint(demo_repr)  # <class 'str'>\n\ndemo_eval = type(eval(repr(demo)))\nprint(demo_eval)  # <class '__main__.DemoClass'>\n\n# 用于重建对象，如果eval(repr(obj))会得到一个对象的拷贝。\nstudy = eval(repr(demo))\nstudy.say()  # jacky\n```\n\n\n<br>\n\n* **`区别3`**\n> **这里额外比较repr和str函数的区别**\n\n在 Python 中要将某一类型的变量或者常量转换为字符串对象通常有两种方法，即str() 或者 repr() \n```python\n>>> a = 10\n>>> type(str(a))\n<class 'str'>\n>>> type(repr(a))\n<class 'str'>\n```\n\n但是这二者之间有什么区别呢？\n因为提供两个功能完全相同的内建函数是没有意义的。\n\n先看一个例子。\n\n```python\n>>> print(str('123'))\n123\n>>> print(str(123))\n123\n>>> print(repr('123'))\n'123'\n>>> print(repr(123))\n123\n```\n从例子中不难发现，当我们把一个字符串传给 str() 函数再打印到终端的时候，输出的字符不带引号。\n\n而将一个字符串传给 repr() 函数再打印到终端的时候，输出的字符带有引号。\n\n造成这两种输出形式不同的原因在于：\n\nprint 语句结合 str() 函数实际上是调用了对象的` __str__ `方法来输出结果。\n而 print 结合 repr() 实际上是调用对象的` __repr__` 方法输出结果。\n\n\n下例中我们用 str 对象直接调用这两个方法，输出结果的形式与前一个例子保持一致。\n\n```python\n>>> print('123'.__repr__())\n'123'\n>>>> print('123'.__str__())\n123\n```\n<br>\n不过这个例子可能还是无法很好表达到底 str() 与 repr() 各有什么意义\n我们再来看一个例子。\n\n```python\n>>> from datetime import datetime\n>>> now = datetime.now()\n>>> print(str(now))\n2017-04-22 15:41:33.012917\n>>> print(repr(now))\ndatetime.datetime(2017, 4, 22, 15, 41, 33, 12917)\n```\n通过 str() 的输出结果我们能很好地知道 now 实例的内容，\n但是却丢失了 now 实例的数据类型信息。\n\n而通过 repr() 的输出结果我们不仅能获得 now 实例的内容，\n还能知道 now 是 datetime.datetime 对象的实例。\n\n\n再比如:\n```python\n\nstring = \"Hello\\tWill\\n\"\nprint(str(string))\nprint(repr(string))\n```\n\n结果：\n\n```python\nHello    Will\n'Hello\\tWill\\n'\n```\n<br>\n\n再比如：\n```python\n\na = \"哈哈\"\n\nprint(str(a))\nprint(repr(a))\n\n# 返回结果\n\"\"\"\n哈哈\n'哈哈'\n\"\"\"\n```\n\n\n<br>\n\n因此 str() 与 repr() 的不同在于：\n* **str() 的输出追求可读性，输出格式要便于理解，适合用于输出内容到用户终端。**\n* **repr() 的输出追求明确性，除了对象内容，还需要展示出对象的数据类型信息，适合开发和调试阶段使用。**\n\n<br>\n\n另外如果想要自定义类的实例能够被 str() 和 repr() 所调用，\n那么就需要在自定义类中重载` __str__ 和 __repr__` 方法。 \n\n\n\n[^1]:  `__repr__和__str__`的区别\n"
  },
  {
    "path": "notes/Python/流畅的Python/扩展内容.md",
    "content": "# 第7章-扩展内容\n\n<!-- TOC -->\n\n- [第7章-扩展内容](#第7章-扩展内容)\n    - [python面向对象](#python面向对象)\n        - [基础篇](#基础篇)\n            - [三种编程方式](#三种编程方式)\n            - [创建类和对象](#创建类和对象)\n            - [面向对象三大特性](#面向对象三大特性)\n                - [封装](#封装)\n                - [继承](#继承)\n                    - [新式类与经典类](#新式类与经典类)\n                - [多态](#多态)\n        - [进阶篇](#进阶篇)\n            - [类的成员](#类的成员)\n                - [字段](#字段)\n                - [方法](#方法)\n                - [属性](#属性)\n                    - [属性的两种定义方式](#属性的两种定义方式)\n                - [公有成员和私有成员](#公有成员和私有成员)\n            - [特殊的类的成员](#特殊的类的成员)\n                - [`__module__ 和 __class__`](#__module__-和-__class__)\n                - [`__init__`](#__init__)\n                - [`__del__`](#__del__)\n                - [`__call__`](#__call__)\n                - [`__dict__`](#__dict__)\n                - [` __getitem__&__setitem__&__delitem__`](#-__getitem____setitem____delitem__)\n                - [` __getslice__&__setslice__&__delslice__`](#-__getslice____setslice____delslice__)\n                - [` __iter__`](#-__iter__)\n                - [` __new__`](#-__new__)\n\n<!-- /TOC -->\n## python面向对象\n\n### 基础篇\n\n#### 三种编程方式\n\n>面向过程：根据业务逻辑从上到下写垒代码\n函数式：将某功能代码封装到函数中，日后便无需重复编写，仅调用函数即可\n面向对象：对函数进行分类和封装，让开发“更快更好更强…”\n\n\n面向过程编程最易被初学者接受，其往往用一长段代码来实现指定功能，开发过程中最常见的操作就是粘贴复制，即：将之前实现的代码块复制到现需功能处。\n\n```python\nwhile True:\n    if cpu利用率 > 90%:\n        # 发送邮件提醒\n        连接邮箱服务器\n        发送邮件\n        关闭连接\n\n    if 硬盘使用空间 > 90%:\n        # 发送邮件提醒\n        连接邮箱服务器\n        发送邮件\n        关闭连接\n\n    if 内存占用 > 80%:\n        # 发送邮件提醒\n        连接邮箱服务器\n        发送邮件\n        关闭连接\n```\n\n随着时间的推移，开始使用了函数式编程，增强代码的重用性和可读性，就变成了这样：\n```python\ndef 发送邮件(内容)\n    #发送邮件提醒\n    连接邮箱服务器\n    发送邮件\n    关闭连接\n\nwhile True：\n\n    if cpu利用率 > 90%:\n        发送邮件('CPU报警')\n\n    if 硬盘使用空间 > 90%:\n        发送邮件('硬盘报警')\n\n    if 内存占用 > 80%:\n        发送邮件('内存报警')\n```\n\n今天我们来学习一种新的编程方式：面向对象编程（Object Oriented Programming，OOP，面向对象程序设计）。\n\n#### 创建类和对象\n面向对象编程是一种编程方式，此编程方式的落地需要使用 “类” 和 “对象” 来实现，\n所以面向对象编程其实就是对 “类” 和 “对象” 的使用。\n\n* 类就是一个模板，模板里可以包含多个函数，函数里实现一些功能\n* 对象则是根据模板创建的实例，通过实例对象可以执行类中的函数\n\n\n```python\n# 创建Test类,python3 初始类都继承object类\nclass Test(object):\n    # 类中定义的函数也叫方法\n    def demo(self):  # 类中的函数第一个参数必须是self\n        print(\"self=\", self)\n        print(\"Method=\", dir(self))\n        print(\"Hello\")\n\n\n# 根据类Test创建对象obj\nobj = Test()  # 类名加括号\nobj.demo()  # 执行demo方法\n\n# 获取一个实例的类名\nprint(obj.__class__.__name__)\n\n# 判断一个对象是否拥有某个属性\nif hasattr(obj, 'demo'):\n    print(\"it has demo method\")\nelse:\n    print(\"have no method\")\n```\n\n返回结果：\n```\nself= <__main__.Test object at 0x00000224F3539A58>\nMethod= ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'demo']\nHello\n```\n<br>\n\n结论：\n\n>方法的第一个参数是调用这个方法的实例对象本身.\n>从上面结果可以看出来self对于代码不是特殊的，只是另一个对象.\n\n\n\n\n#### 面向对象三大特性\n\n##### 封装\n\n封装，顾名思义就是将内容封装到某个地方，以后再去调用被封装在某处的内容。\n所以，在使用面向对象的封装特性时，需要：\n\n* 将内容封装到某处\n* 从某处调用被封装的内容\n\n```python\n# 1. 将内容封装到某处\nclass Test(object):\n    # __init__方法成为构造方法，根据类创建对象时自动执行\n    def __init__(self, name):\n        self.name = name\n\n    def detail(self):\n        print(self.name)\n\n\n# 自动执行__init__方法\n# 将tony封装到obj self的name属性中\n# 当执行obj,self等于obj\nobj = Test('tony')\n\n# 2.从某处调用封装的内容\n# 通过对象直接调用, 格式:对象.属性名\nprint(obj.name)\n\n# 通过self调用\n# python默认会将obj传给self参数，即obj.detail(obj)\n# 此时方法内部的self=obj,所以 self.name='tony'\nprint(obj.detail())\n\n```\n\n<br>\n综上所述，对于面向对象的封装来说，其实就是使用构造方法将内容封装到 对象 中，然后通过对象直接或者self间接获取被封装的内容。\n\n\n##### 继承\n继承，面向对象中的继承和现实生活中的继承相同，即：**子可以继承父的内容**。\n\n例如：\n猫可以：喵喵叫、吃、喝、拉、撒\n狗可以：汪汪叫、吃、喝、拉、撒\n\n如果我们要分别为猫和狗创建一个类，那么就需要为 猫 和 狗 实现他们所有的功能\n\n```python\nclass 猫：     \n    def 喵喵叫(self):        \n        print '喵喵叫'     \n    def 吃(self):       \n         # do something     \n    def 喝(self):       \n     # do something     \n    def 拉(self):       \n     # do something     \n    def 撒(self):       \n     # do something \n\n\nclass 狗：     \n    def 汪汪叫(self):       \n        print '喵喵叫'     \n    def 吃(self):        \n        # do something    \n    def 喝(self):        \n        # do something     \n    def 拉(self):        \n        # do something     \n    def 撒(self):       \n        # do something\n```\n\n上述代码不难看出，吃、喝、拉、撒是猫和狗都具有的功能，而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想，如下实现：\n\n动物：吃、喝、拉、撒\n猫：喵喵叫（猫继承动物的功能）\n狗：汪汪叫（狗继承动物的功能）\n\n```python\nclass 动物:\n \n    def 吃(self):\n        # do something\n \n    def 喝(self):\n        # do something\n \n    def 拉(self):\n        # do something\n \n    def 撒(self):\n        # do something\n \n# 在类后面括号中写入另外一个类名，表示当前类继承另外一个类\nclass 猫(动物)：\n \n    def 喵喵叫(self):\n        print '喵喵叫'\n        \n# 在类后面括号中写入另外一个类名，表示当前类继承另外一个类\nclass 狗(动物)：\n \n    def 汪汪叫(self):\n        print '喵喵叫'\n```\n\n所以，对于面向对象的继承来说，**其实就是将多个类共有的方法提取到父类中，子类仅需继承父类而不必一一实现每个方法。**\n\n```python\nclass Animal(object):\n    def eat(self):\n        print(\"%s 吃 \" % self.name)\n\n    def drink(self):\n        print(\"%s 喝 \" % self.name)\n\n    def shit(self):\n        print(\"%s 拉 \" % self.name)\n\n    def pee(self):\n        print(\"%s 撒 \" % self.name)\n\n\nclass Cat(Animal):\n    def __init__(self, name):\n        self.name = name\n        self.breed = '猫'\n\n    def cry(self):\n        print('喵喵叫')\n\n\nclass Dog(Animal):\n    def __init__(self, name):\n        self.name = name\n        self.breed = '狗'\n\n    def cry(self):\n        print('汪汪叫')\n\n\nc1 = Cat('小白家的小黑猫')\nc1.eat()\n\nc2 = Cat('小黑的小白猫')\nc2.drink()\n\nd1 = Dog('胖子家的小瘦狗')\nd1.eat()\n```\n\n那么问题又来了，多继承呢？\n* 是否可以继承多个类\n* 如果继承的多个类每个类中都定了相同的函数，那么那一个会被使用呢？\n\n\n1、Python的类可以继承多个类，Java和C#中则只能继承一个类\n2、Python的类如果继承了多个类，那么其寻找方法的方式有两种，分别是：深度优先和广度优先\n\n###### 新式类与经典类\n注意：\n\n当类是经典类时，多继承情况下，会按照**深度优先**方式查找 -`找到第一个`\n \n当类是新式类时，多继承情况下，会按照**广度优先**方式查找 -`找到最后一个`\n\n\n经典类和新式类，从字面上可以看出一个老一个新，新的必然包含了跟多的功能，也是之后推荐的写法\n\n从写法上区分的话，如果 当前类或者父类继承了object类，那么该类便是新式类，否则便是经典类。\n\n经典类 -python2.7测试\n```python\nclass D:\n    def bar(self):\n        print 'D.bar'\n\n\nclass C(D):\n    def bar(self):\n        print 'C.bar'\n\n\nclass B(D):\n    def bar(self):\n        print 'hhhhh'\n        print 'B.bar'\n\n\nclass A(B, C):\n    pass\n    # def bar(self):\n    #     print 'A.bar'        \n\n\na = A()\na.bar()\n\nprint A.__mro__ # 报错 ,__mro__只对新式类有效\n\n```\n\n执行bar方法时\n首先去A类中查找，如果A类中没有，则继续去B类中找，如果B类中么有，则继续去D类中找，如果D类中么有，则继续去C类中找，如果还是未找到，则报错\n\n所以，查找顺序：**A --> B --> D --> C**\n\n在上述查找bar方法的过程中，一旦找到，则寻找过程立即中断，便不会再继续找了\n\n\n新式类-python3.6+\n```python\n class D(object):\n    def bar(self):\n        print('D.bar')\n\n\nclass C(D):\n    def bar(self):\n        print('C.bar')\n\n\nclass B(D):\n    def bar(self):\n        print('B.bar')\n\n\nclass A(B, C):\n    pass\n    # print('A.bar')\n\n\na = A()\na.bar()\n\n# 方法解析顺序Method Resolution Order - MRO\nprint(A.__mro__)\n# (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)\n\n\n\n```\n\n\n执行bar方法时\n首先去A类中查找，如果A类中没有，则继续去B类中找，如果B类中么有，则继续去C类中找，如果C类中么有，则继续去D类中找，如果还是未找到，则报错\n\n所以，查找顺序：**A --> B --> C --> D**\n\n 在上述查找bar方法的过程中，一旦找到，则寻找过程立即中断，便不会再继续找了\n\n\n再看一个例子：\n```python\nclass D:\n    def __init__(self):\n        self.a = 1\n\n\nclass B(D):\n    pass\n\n\nclass C(B):\n    def __init__(self):\n        self.a = 2\n\n\nclass A(B,C):\n    pass\n\n\na = A()\nprint (a.a)  # 1\n```\n\n```python\nclass D(object):\n    def __init__(self):\n        self.a = 1\n\n\nclass B(D):\n    pass\n\n\nclass C(D):\n    def __init__(self):\n        self.a = 2\n\n\nclass A(B,C):\n    pass\n\n\na = A()\nprint (a.a) # 2\n```\n\n##### 多态\n\nPyhon不支持Java和C#这一类强类型语言中多态的写法，但是原生多态，其Python崇尚“鸭子类型”。\n\n\n```python\nclass Animal(object):\n    def run(self):\n        print('Animal is running...')\n\n\nclass Dog(Animal):\n    pass\n\n\nclass Cat(Animal):\n    pass\n\n\nc = Dog()\nb = Animal()\n\nisok = isinstance(c, Animal)\nprint(isok)\n\nisok = isinstance(b, Dog)\nprint(isok)\n\n\ndef run_twice(animal):\n    animal.run()\n    animal.run()\n\n\nclass Tortoise(Animal):\n    def run(self):\n        print('Tortoise is running slowly...')\n\n\nclass people(object):\n    def run(self):\n        print(\"People run...\")\n\n\nisok = run_twice(Tortoise())\nprint(isok)\n\n# people这个类却和animal没有任何关系，但是其中却有run这个方法\n# 鸭子类型\nisok = run_twice(people())\nprint(isok)\n```\n\n\n### 进阶篇\n\n\n#### 类的成员\n类的成员可以分为三大类：**`字段、方法和属性`**\n\n##### 字段\n\n字段包括：普通字段和静态字段，他们在定义和使用中有所区别，而最本质的区别是内存中保存的位置不同，\n\n* **`普通字段属于对象`**\n* **`静态字段属于类`**\n\n```python\nclass Province:\n    # 静态字段\n    country = '中国'\n\n    def __init__(self, name):\n        # 普通字段\n        self.name = name\n\n\n# 直接访问普通字段\nobj = Province('河北省')\nprint(obj.name)\n\n# 直接访问静态字段\nprint(Province.country)\n\n```\n\n\n##### 方法\n\n\n##### 属性\n\n如果你已经了解Python类中的方法，那么属性就非常简单了，因为Python中的属性其实是普通方法的变种。\n对于属性，有以下三个知识点：\n\n* 属性的基本使用\n* 属性的两种定义方式\n\n\n```python\nclass Goods(object):\n    @property\n    def price(self):\n        print(\"100\")\n\n\nobj = Goods()\nobj.price\n```\n由属性的定义和调用要注意一下几点：\n* 定义时，在普通方法的基础上添加 @property 装饰器；\n* 定义时，属性仅有一个self参数\n\n* 调用时，无需括号\n方法：foo_obj.func()\n属性：foo_obj.prop\n\n\n注意：属性存在意义是：访问属性时可以制造出和访问字段完全相同的假象\n\n属性由方法变种而来，如果Python中没有属性，方法完全可以代替其功能\n\n\n\n1.特殊的类属性\n\n`class.__class__`    类的类型\n`class.__bases__`   父类名称\n`class.__dict__`      类所有属性\n`class.__module__`    类所在的模块\n\n\n\n2.特殊的实例属性\n`__class__` 实例所对应的类\n`__dict__`  实例的属性\n\n\n\n<br>\n\n###### 属性的两种定义方式\n\n属性的定义有两种方式：\n\n* 装饰器 即：在方法上应用装饰器\n* 静态字段 即：在类中定义值为property对象的静态字段\n\n装饰器方式：在类的普通方法上应用@property装饰器\n\n静态字段方式，创建值为property对象的静态字段\n当使用静态字段的方式创建属性时，经典类和新式类无区别\n\n```python\nclass Foo(object):\n    def get_bar(self):\n        return 'hello'\n\n    BAR = property(get_bar)\n\n\nobj = Foo()\nreuslt = obj.BAR  # 自动调用get_bar方法，并获取方法的返回值\nprint(reuslt)\n\n```\n\n\nproperty的构造方法中有个四个参数\n>* 第一个参数是方法名，调用 对象.属性 时自动触发执行方法\n>* 第二个参数是方法名，调用 对象.属性 ＝ XXX 时自动触发执行方法\n>* 第三个参数是方法名，调用 del 对象.属性 时自动触发执行方法\n>* 第四个参数是字符串，调用 对象.属性.`__doc__` ，此参数是该属性的描述信息\n\n\n```python\nclass Foo(object):\n    def get_bar(self):\n        return 'hello'\n\n    # 必须两个参数\n    def set_bar(self, value):\n        return 'set value' + value\n\n    def del_bar(self):\n        return 'hello'\n\n    BAR = property(get_bar, set_bar, del_bar, 'description...')\n\n\nobj = Foo()\n\nprint(obj.BAR)  # 自动调用第一个参数中定义的方法：get_bar\nobj.BAR = \"alex\"  # 自动调用第二个参数中定义的方法：set_bar方法，并将“alex”当作参数传入\nprint(obj.BAR)\n\nprint(obj.BAR.__doc__)  # 自动获取第四个参数中设置的值：description...\ndel Foo.BAR  # 自动调用第三个参数中定义的方法：del_bar方法\n\n```\n<br>\n实例属性和类属性关系\n\n记住：\n>**实例属性不能左右类属性**\n\n>**但是类属性可以左右实例属性**\n\n```python\nclass A(object):\n    x = 7\n\n\nfoo = A()\nfoo.x += 7\nprint(A.x)  # 7\nprint(foo.x)  # 14\n\nprint(\"####\")\nA.x = A.x + 1\nprint(A.x)  # 8\nprint(foo.x)  # 14\n\n\n```\n\n`也有特例`：\n```python\nclass B(object):\n    y = [1, 2, 3]\n\n\nbar = B()\nbar.y.append(4)\n\nprint(bar.y)  # [1, 2, 3, 4]\nprint(B.y)  # [1, 2, 3, 4]\n\n```\n\n<br>\n\n##### 公有成员和私有成员\n\n\n>* `公有成员，在任何地方都能访问`\n>* `私有成员，只有在类的内部才能方法`\n\n\n私有成员和公有成员的定义不同：**私有成员命名时，前两个字符是下划线**。\n（特殊成员除外，例如：`__init__、__call__、__dict__`等）\n\n\n```python\nclass C(object):\n    def __init__(self):\n        self.name = '公有字段'\n        self.__foo = \"私有字段\"\n```\n\n<br>\n\n1. 静态字段\n\n>*  `公有静态字段：类可以访问；类内部可以访问；派生类中可以访问`\n\n>* `私有静态字段：仅类内部可以访问`\n\n```python\nclass C(object):\n\n    name = \"公有静态字段\"\n\n    def func(self):\n        print(C.name)\n\n\nclass D(C):\n    def show(self):\n        print(C.name)\n\n\nC.name  # 类访问\n\nobj = C()\nobj.func()  # 类内部可以访问\n\nobj_son = D()\nobj_son.show()  # 派生类中可以访问\n\n```\n\n\n```python\nclass C(object):\n\n    __name = \"公有静态字段\"\n\n    def func(self):\n        print(C.__name)\n\n\nclass D(C):\n    def show(self):\n        print(C.__name)\n\n\nC.__name  # 类访问            ==> 错误\n\nobj = C()\nobj.func()  # 类内部可以访问     ==> 正确\n\nobj_son = D()\nobj_son.show()  # 派生类中可以访问   ==> 错误\n\n```\n<br>\n\n2.普通字段\n\n>* **`公有普通字段：对象可以访问；类内部可以访问；派生类中可以访问`**\n>* **`私有普通字段：仅类内部可以访问`**\n\n\n```python\nclass C(object):\n    def __init__(self):\n        self.foo = \"公有字段\"\n\n    def func(self):\n        print(self.foo)  # 类内部访问\n\n\nclass D(C):\n    def show(self):\n        print(self.foo)  # 派生类中访问\n\n\nobj = C()\n\nobj.foo  # 通过对象访问\nobj.func()  # 类内部访问\n\nobj_son = D()\nobj_son.show()  # 派生类中访问\n\n```\n\n```python\nclass C:\n    def __init__(self):\n        self.__foo = \"私有字段\"\n\n    def func(self):\n        print(self.foo)  # 类内部访问\n\n    def __doSomething(self):\n        print(\"do something\")\n\n\nclass D(C):\n    def show(self):\n        print(self.foo)  # 派生类中访问\n\n\nobj = C()\n\nobj.__foo  # 通过对象访问    ==> 错误\nobj.func()  # 类内部访问     ==> 正确\n\nobj_son = D()\nobj_son.show()  # 派生类中访问  ==> 错误\n\n# 强制访问私有字段-不建议这样使用\nprint(obj._C__foo)\n\n# 访问私有方法\nprint(obj._C__doSomething())\n\n```\n\n<br>\n\n总结：\n\n> 在python里，标识符有字母、数字、下划线组成。\n\n>在python中，所有标识符可以包括英文、数字以及下划线（_），但不能以数字开头。\n>python中的标识符是**区分大小写**的。\n\n>以下划线开头的标识符是有特殊意义的。以**单下划线开头（`_foo`）**的代表不能直接访问的类属性，需通过类提供的接口进行访问，不能用\"from xxx import *\"而导入；\n\n>以双下划线开头的（`__foo`）代表类的私有成员\n\n>以双下划线开头和结尾的（`__foo__`）代表python里特殊方法专用的标识，如`__init__（）`代表类的构造函数。\n\n\n#### 特殊的类的成员\n\n\n##### `__module__ 和 __class__`\n\n\n>`__module__` 表示当前操作的对象在那个模块\n>`__class__` 表示当前操作的对象的类是什么\n\n\ndemo.py 单独访问：\n可以看到`__module__`表示的main模块，`__class__`表示main模块中的Test类\n\n```python\nclass Test(object):\n    def __init__(self):\n        self.name = \"tony\"\n\n\nobj = Test()\nprint(obj.__module__)  # __main__\nprint(obj.__class__)  # <class '__main__.Test'>\n\n```\n<br>\n被其他文件引用：\n可以看到`__module__`表示的demo模块，`__class__`表示demo模块中的Test类\n\n```python\nfrom demo import Test\n\nobj = Test()\nprint(obj.__module__)  # demo\n\nprint(obj.__class__)  # <class 'demo.Test'>\n```\n\n\n##### `__init__`\n\n构造方法，通过类创建对象时，自动触发执行。\n\n```python\nclass Test(object):\n    def __init__(self, name):\n        self.name = name\n\n\ntest = Test(\"tony\") # 自动执行类中的__init__方法\n```\n\n<br>这里要`注意`：\n```python\nclass Test(object):\n    def __init__(self, name):\n        self.name = name\n        # return \"111\"  # 问题：能不能给它加个返回值？\n        # 默认的返回值\n        # <__main__.Test object at 0x000001E27BA18518>\n\n\ntest = Test(\"tony\")  # 自动执行类中的__init__方法\nprint(test)\n# TypeError: __init__() should return None, not 'str'\n\n```\n\n<br>\n\n##### `__del__`\n\n析构方法，当对象在内存中被释放时，自动触发执行。\n\n>此方法一般无须定义，因为Python是一门高级语言，程序员在使用时无需关心内存的分配和释放，因为此工作都是交给Python解释器来执行\n\n>所以，**析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。**\n\n```python\nclass Test(object):\n    def __del__(self):\n        print(\"I am deleted...\")\n\n\ntest = Test()\ndel test  # I am deleted...\n\n```\n\n<br>\n\n##### `__call__`\n\n\n对象后面加括号，触发执行。\n\n>构造方法的执行是由创建对象触发的，即：对象 = 类名() \n而对于` __call__ `方法的执行是由对象后加括号触发的，即：对象() 或者 类()()\n\n```python\nclass Foo(object):\n    def __init__(self):\n        print('init')\n\n    def __call__(self):\n        print('call')\n\n\nobj = Foo()\nprint(obj)  # 执行__init__方法\n# init\n# <__main__.Foo object at 0x000001EC6ACC8470>\n\nprint(obj())  # 执行__call__方法\n\n```\n\n<br>\n\n##### `__dict__`\n\n类或对象中的所有成员\n\n```python\nclass Province:\n\n    country = 'China'\n\n    def __init__(self, name, count):\n        self.name = name\n        self.count = count\n\n    def func(self, *args, **kwargs):\n        print('func')\n\n\n# 获取类的成员，即：静态字段、方法、\nprint(Province.__dict__)\n\nobj1 = Province('HeBei', 10000)\nprint(obj1.__dict__)\n# 获取 对象obj1 的成员\n# 输出：{'count': 10000, 'name': 'HeBei'}\n\nobj2 = Province('HeNan', 3888)\nprint(obj2.__dict__)\n# 获取 对象obj2 的成员\n# 输出：{'count': 3888, 'name': 'HeNan'}\n```\n\n<br>\n\n##### ` __getitem__&__setitem__&__delitem__`\n\n用于索引操作，如字典。以上分别表示获取、设置、删除数据\n\n\n```python\nclass Foo(object):\n    def __getitem__(self, key):\n        print('__getitem__', key)\n\n    def __setitem__(self, key, value):\n        print('__setitem__', key, value)\n\n    def __delitem__(self, key):\n        print('__delitem__', key)\n\n\nobj = Foo()\n\nresult = obj['k1']  # 自动触发执行 __getitem__\nobj['k1'] = 'tony'  # 自动触发执行 __setitem__\ndel obj['k1']  # 自动触发执行 __delitem__\n\nprint(obj['k1'])  # 自动触发执行 __getitem__\n\n```\n\n<br>\n\n##### ` __getslice__&__setslice__&__delslice__`\n\n该三个方法用于分片操作，如：列表\n\n **`仅限于python2.6+`**\n~~python3已废弃这些方法~~\n\npython3 已经变成`__getitem__`&`__setitem__`&`__delitem__`\n\n<br>\n\n##### ` __iter__`\n\n用于迭代器，之所以列表、字典、元组可以进行for循环，是因为类型内部定义了` __iter__`\n\n```python\nclass Foo(object):\n    pass\n\n\n# obj = Foo()\n\n# for i in obj:\n# print(i)  # TypeError: 'Foo' object is not iterable\n\n\nclass FooX(object):\n    def __iter__(self):\n        pass\n\n\nobjx = FooX()\n\n# for i in objx:\n# print(i)  # TypeError: iter() returned non-iterator of type 'NoneType'\n\n\nclass FooY(object):\n    def __init__(self, sq):\n        self.sq = sq\n\n    def __iter__(self):\n        return iter(self.sq)\n\n\nobjy = FooY([11, 22, 33, 44])\n\nfor i in objy:\n    print(i)\n\n# 11\n# 22\n# 33\n# 44\n\n# 也可也写成\nbj = iter([11, 22, 33, 44])\n\nfor i in bj:\n    print(i)\n\n```\n\n<br>\n\n##### ` __new__`\n\n`__new__()`:  构造方法\n\n\n**1.如果有`__new__`和`__init__`都存在， 优先找`__new__`**\n\n\n这两个方法是用来创建object的子类对象，静态方法`__new__()`用来**创建类的实例**，\n\n然后再调用`__init__()`来**初始化实例**。\n\n```python\nclass A(object):\n    def __init__(self, *args, **kwargs):\n        print(\"init %s\" % self.__class__)\n\n    def __new__(cls, *args, **kwargs):\n        print(\"new %s\" % cls)\n        return object.__new__(cls, *args, **kwargs)\n\n\na = A()\na\n\n# new <class '__main__.A'>\n# init <class '__main__.A'>\n\n```\n\n<br>\n\n**`2.其他区别`**\n\n`__new__`方法默认返回实例对象供`__init__`方法、实例方法使用。\n\n```python\nclass Foo(object):\n    price = 50\n\n    def __new__(cls, *agrs, **kwds):\n        inst = object.__new__(cls, *agrs, **kwds)\n        return inst\n\n    def how_much_of_book(self, n):\n        print(self)\n        return self.price * n\n\n\nfoo = Foo()\nprint(foo.how_much_of_book(8))\n# Foo类中重载了__new__方法，它的返回值为Foo类的实例对象\n# <__main__.Foo object at 0x0000017BF8678630>\n# 400\n```\n\n<br>\n`__init__ `方法为初始化方法，为类的实例提供一些属性或完成一些动作\n\n```python\nclass Foo(object):\n    def __new__(cls, *agrs, **kwds):\n        inst = object.__new__(cls, *agrs, **kwds)\n        return inst\n\n    def __init__(self, price=50):\n        self.price = price\n\n    def how_much_of_book(self, n):\n        print(self)\n        return self.price * n\n\n\nfoo = Foo()\nprint(foo.how_much_of_book(8))\n```\n\n<br>\n\n**3.几点注意事项**\n\n`__new__ `方法创建实例对象供`__init__ `方法使用，`__init__`方法定制实例对象。\n\n`__new__` 方法必须返回值，`__init__`方法不需要返回值。(如果返回非None值就报错)  \n  \n <br>\n**4. 一般用不上`__new__`方法**\n\n`__new__`方法可以用在下面二种情况。\n\n1> 继承不可变数据类型时需要用到`__new__`方法(like int, str, or tuple） 。\n\n```python\nclass Inch(float):\n    def __new__(cls, arg=0.0):\n        return float.__new__(cls, arg * 0.0254)\n\n\nprint(Inch(12))\n```\n\n\n2>用在元类，定制创建类对象\n\n元类：\n\n当我们定义了类以后，就可以根据这个类创建出实例，所以：先定义类，然后创建实例。\n\n但是如果我们想创建出类呢？那就必须根据metaclass创建出类，所以：先定义metaclass，然后创建类。\n\n**连接起来就是：先定义metaclass，就可以创建类，最后创建实例。**\n\n```python\n# metaclass是类的模板，所以必须从`type`类型派生：\nclass MetaClass(type):\n    \"\"\"\n    参数必须是这四个\n    cls：当前准备创建的类\n    name：类的名字\n    bases：类的父类集合\n    attrs：类的属性和方法，是一个字典\n    \"\"\"\n    def __new__(cls, name, bases, attrs):\n        print(\"Allocating memory for class\", name)\n        print(\"new cls=\", cls)\n        print(\"new name=\", name)\n        print(\"new bases=\", bases)\n        print(\"new attrs=\", attrs)\n        return super(MetaClass, cls).__new__(cls, name, bases, attrs)\n\n    def __init__(cls, name, bases, attrs):\n        print(\"Initializing class\", name)\n        print(\"int cls=\", cls)\n        print(\"int name=\", name)\n        print(\"int bases=\", bases)\n        print(\"int attrs=\", attrs)\n        super(MetaClass, cls).__init__(name, bases, attrs)\n\n\nclass Myclass(metaclass=MetaClass):\n    def foo(self, param):\n        print(param)\n\n\np = Myclass()\np.foo(\"hello\")\n\n```"
  },
  {
    "path": "notes/Python/流畅的Python/抽象类.md",
    "content": "# 第11章-抽象类\n\n\n<!-- TOC -->\n\n- [第11章-抽象类](#%e7%ac%ac11%e7%ab%a0-%e6%8a%bd%e8%b1%a1%e7%b1%bb)\n  - [ABC类](#abc%e7%b1%bb)\n\n<!-- /TOC -->\n## ABC类\n\npython中并没有提供抽象类与抽象方法，\n但是提供了内置模块**abc(abstract base class)**来模拟实现抽象类。\n\n可以通过abc将基类声明为抽象类的方式，然后注册具体类作为这个基类的实现。\n\n\n假设我们在写一个关于动物的代码。涉及到的动物有鸟，狗，牛。\n\n首先鸟，狗，牛都是属于动物的。既然是动物那么肯定需要吃饭，发出声音。\n\n但是具体到鸟，狗，牛来说吃饭和声音肯定是不同的。\n\n需要具体去实现鸟，狗，牛吃饭和声音的代码。\n\n概括一下抽象基类的作用：定义一些共同事物的规则和行为\n\n\n例子：\n\n```python\nclass Animal(object):\n    def eat(self):\n        raise NotImplementedError\n\n    def voice(self):\n        raise NotImplementedError\n\n\nclass Dog(Animal):\n    def voice(self):\n        print('wow....')\n\n\nclass Bird(Animal):\n    def voice(self):\n        print('jiji....')\n\n\nclass Cow(Animal):\n    def voice(self):\n        print('Oh.....')\n\n\nif __name__ == \"__main__\":\n\n    d = Dog()\n    d.voice()\n    d.eat()  # raise NotImplementedError\n\n```\n\n这样实现有个缺点，就是只有子类调用eat方法的时候才会报错。\n\n子类是可以正常实例化的。\n\n但是你能够想象鸟，狗，牛不会吃饭么？ 如果不会吃饭那肯定不算是动物了。\n\n所以正常的实现应该是如果没有实现eat方法，实例化就应该是失败的。\n\n那么这里就要用到抽象基类的一般使用方法.\n\n\n```python\nimport abc\n\n\nclass Animal(object):\n\n    __metaclass__ = abc.ABCMeta\n\n    @abc.abstractmethod\n    def eat(self):\n        return\n\n    @abc.abstractmethod\n    def voice(self):\n        return\n\n\nclass Dog(Animal):\n    def voice(self):\n        print('wow....')\n\n    def eat(self):\n        print(\"Dog eat...\")\n\n\nclass Bird(Animal):\n    def voice(self):\n        print('jiji....')\n\n    def eat(self):\n        print(\"Bird eat...\")\n\n\nclass Cow(Animal):\n    def voice(self):\n        print('Oh.....')\n\n    def eat(self):\n        print(\"Cow eat...\")\n\n\nif __name__ == \"__main__\":\n\n    d = Dog()\n    b = Bird()\n    c = Cow()\n\n    d.voice()\n    d.eat()\n\n    b.voice()\n    b.eat()\n\n    c.voice()\n    c.eat()\n\n```\n\n返回结果：\n```\nwow....\nDog eat...\njiji....\nBird eat...\nOh.....\nCow eat...\n```\n\n\n使用注册的方式:\n\n```python\nfrom abc import ABCMeta, abstractmethod\n\n\nclass Animal(metaclass=ABCMeta):\n    @abstractmethod\n    def eat(self):\n        return\n\n    @abstractmethod\n    def voice(self):\n        return\n\n\nclass Dog(object):\n    def voice(self):\n        print('wow....')\n\n    def eat(self):\n        print(\"Dog eat...\")\n\n\n# 使用注册方式,必须两个方法都要写，否则会报错\nAnimal.register(Dog)\n\nif __name__ == \"__main__\":\n\n    d = Dog()\n\n    d.voice()\n    d.eat()\n\n```\n\n<br>\n\n**`继承和注册这两种方法有什么区别呢?`**\n\n\nanimals.py\n```python\nfrom abc import ABCMeta, abstractmethod\n\n\nclass Animal(metaclass=ABCMeta):\n    @abstractmethod\n    def eat(self):\n        return\n\n    @abstractmethod\n    def voice(self):\n        return\n\n```\n\nsub_class.py\n```python\nfrom animals import Animal\n\n\nclass Dog(Animal):\n    def voice(self):\n        print('wow....')\n\n    def eat(self):\n        print(\"Dog eat...\")\n\n\nprint('subclass=', issubclass(Dog, Animal))\nprint('Instance=', isinstance(Dog(), Animal))\n\n```\n\nabc_register.py\n```python\nfrom animals import Animal\n\n\nclass Dog(object):\n    def voice(self):\n        print('wow....')\n\n    def eat(self):\n        print(\"Dog eat...\")\n\n\nAnimal.register(Dog)\n\nprint('subclass=', issubclass(Dog, Animal))\nprint('Instance=', isinstance(Dog(), Animal))\n```\n\n\n\n\n\n"
  },
  {
    "path": "notes/Python/流畅的Python/文本和字节序列.md",
    "content": "# 第4章 文本和字节序列\n\n<!-- TOC -->\n\n- [第4章 文本和字节序列](#%e7%ac%ac4%e7%ab%a0-%e6%96%87%e6%9c%ac%e5%92%8c%e5%ad%97%e8%8a%82%e5%ba%8f%e5%88%97)\n  - [编码和解码](#%e7%bc%96%e7%a0%81%e5%92%8c%e8%a7%a3%e7%a0%81)\n  - [chardet模块](#chardet%e6%a8%a1%e5%9d%97)\n  - [json与字典区别](#json%e4%b8%8e%e5%ad%97%e5%85%b8%e5%8c%ba%e5%88%ab)\n  - [json模块和simplejson模块](#json%e6%a8%a1%e5%9d%97%e5%92%8csimplejson%e6%a8%a1%e5%9d%97)\n\n<!-- /TOC -->\n\n## 编码和解码  \n\n> markdom可以插入emoji表情包\n其中&#作为前缀，x1f54为对应表情的unicode编码\n&#x1F97A \n[emoji](https://unicode.org/Public/emoji/12.1/emoji-data.txt)\n\n\n把码位转换成字节序列的过程是编码；把字节序列转换成码位的过程是解码\n\n> decode的作用是将其他编码的字符串转换成unicode编码\n> 如str1.decode('gb2312')，表示将gb2312编码的字符串转换成unicode编码。\n> \n> encode的作用是将unicode编码转换成其他编码的字符串\n> 如str2.encode('gb2312')，表示将unicode编码的字符串转换成gb2312编码。\n\n\n**`Python 3默认使用UTF-8编码源码`，Python 2（从2.5开始）则默认使用ASCII。**\n如果加载的.py模块中包含UTF-8之外的数据，而且没有声明编码，会报错\n\n```sequence\ntitle: python 2 编解码过程\n编码前其他编码字符串->unicode: 编码 decode\nunicode->>编码后其他编码字符串:解码 encode\n```\n\n>**在新版本的python3中，取消了unicode类型，代替它的是使用unicode字符的字符串类型(str),字符串类型（str）成为基础类型**\n\n如下所示，而编码后的变为了字节类型(bytes)\n\n```sequence\ntitle: python 3 编解码过程\n\n编码前其他编码字符串->str(unicode): 编码 decode\nstr(unicode)->>编码后其他编码字符串:解码 encode\n```\n\n\n```python\n# 指定字符串类型对象u\nu = \"中文\"\nprint(u, type(u))\n\n# 以gb2312编码对u进行编码，获得bytes类型对象str\nstr = u.encode('gb2312')\nprint(str, type(str))\n\n# 以gb2312编码对字符串str进行解码，获得字符串类型对象u1\nu1 = str.decode('gb2312')\nprint(u1, type(u1))\n```\n\n返回结果:\n```\n中文 <class 'str'>\nb'\\xd6\\xd0\\xce\\xc4' <class 'bytes'>\n中文 <class 'str'>\n\n```\n<br>\n\n##  chardet模块\n\n> **chardet模块检测读取出来的str是什么编码格式的**\n\n```python\nfrom urllib.request import urlopen\nimport chardet\n\nrawdata = urlopen('http://baidu.com/').read()\nchr = chardet.detect(rawdata)\nprint(chr) # {'encoding': 'ascii', 'confidence': 1.0, 'language': ''}\n```\n<br>\njson.dumps在默认情况下，对于非ascii字符生成的是相对应的字符编码，而非原始字符.\n\n>首先明确，chardet这个是**`判断编码`**而不是判断数据类型的，\n\n>其次，加encode只是将其变成了字节\n\n**chardet只能够接受字节而不能接收字符类型的**\n\n```python\nimport json\nimport chardet\n\ndict1 = {\"haha\": \"哈哈\"}\n\n# json.dumps 默认ascii编码\nprint(json.dumps(dict1))  # {\"haha\": \"\\u54c8\\u54c8\"}\n\n# 禁止ascii编码后默认utf-8\nprint(json.dumps(dict1, ensure_ascii=False))  # {\"haha\": \"哈哈\"}\n\n# ascii\nss = chardet.detect(json.dumps(dict1).encode())\nprint(ss)\n\n# utf-8\nss = chardet.detect(json.dumps(dict1, ensure_ascii=False).encode())\nprint(ss)\n\n```\n\n<br>\n\n## json与字典区别\n\n在python中，字典的输出内容跟json格式内容一样，但是字典的格式是字典，json的格式是字符串，所以在传输的时候（特别是网页）要转换使用。\n\n`重要函数`\n>编码：**把一个Python对象编码转换成Json字符串   json.dumps()**\n\n>解码：**把Json格式字符串解码转换成Python对象   json.loads()**\n\n举个例子:\n```python\nimport json\ndic = {'str': 'this is a string', 'list': [1, 2, 'a', 'b']}\nprint(type(dic))  # <class 'dict'>\nprint(dic)\n\njson_obj = json.dumps(dic)\nprint(type(json_obj))  # <class 'str'>\nprint(json_obj)\n\ndic1 = json.loads(json_obj)\nprint(type(dic1))  # <class 'dict'>\nprint(dic1)\n\n```\n<br>\n\n`几个主要函数`\n>dump,dumps,load,loads\n\n>**带s跟不带s的区别是 带s的是对 字符串的处理，而不带 s的是对文件对像的处理。**\n\n\n```python\nimport json\nfrom io import StringIO\n\n# 创建文件流对象\nio = StringIO()\n\n# 把 json编码数据导向到此文件对象\njson.dump(['streaming API'], io)\n\n# 取得文件流对象的内容\nprint(io.getvalue())  # [\"streaming API\"]\n```\n\n<br>\n\n**参数ensure_ascii**\n>默认为True，所有的非ascii字符在输出时都会被转义为\\uxxxx的序列，\n返回的对象是一个只由ascii字符组成的str类型，为False时不会进行转义输出，反回的对象是个unicode。（**这个参数对包含中文的json输出时非常有用**）\n\n\n<br>\n举个例子:\n\n```python\nimport json\n\ndata = {u'我': u'是', u'美': u'女'}\n\nprint(json.dumps(data))  # {\"\\u6211\": \"\\u662f\", \"\\u7f8e\": \"\\u5973\"}\n\nprint(json.dumps(data, ensure_ascii=False))  # {\"我\": \"是\", \"美\": \"女\"}\n\n```\n<br>\n处理中文json时，要想不每次都给一堆重复的参数，可以用partial\n\n```python\nimport json\nfrom functools import partial\n\njson_dumps = partial(json.dumps, ensure_ascii=False, sort_keys=True)\n\ndata = {u'我': u'是', u'美': u'女'}\n\nprint(json_dumps(data))  # {\"我\": \"是\", \"美\": \"女\"}\n\n```\n<br>\n\n看一个例子:\n```python\nimport json\n\n# 使用双引号、单引号都可以的，建议使用双引号\nh = '{\"foo\":\"bar\", \"foo2\":\"bar2\"}'\nd = \"{'muffin' : 'lolz', 'foo' : 'kitty'}\"\n\njson_obj1 = json.dumps(h)\njson_obj2 = json.dumps(d)\n\ndic1 = json.loads(json_obj1)\ndic2 = json.loads(json_obj2)\n\nprint(\"json_obj1=\", json_obj1)\nprint(\"json_obj2=\", json_obj2)\nprint(\"dic1=\", dic1)\nprint(\"dic1 type=\", type(dic1))\nprint(\"dic2=\", dic2)\nprint(\"dic2 type=\", type(dic2))\n```\n<br>\n\n总结:\n>**Python 的字典是一种数据结构，JSON 是一种数据格式。**\n>**Python的字典key可以是任意可hash对象，json只能是字符串**\n\n\n<br>\n\n## json模块和simplejson模块\n\n<br>\n\n环境: \n**python 3.6+**\n**json: 2.0.9**\n**simplejson: 3.16.0**\n\n<br>\n\n* 区别1\n\n```python\nimport json\nimport simplejson\n\njson_str = b'hello'\njson_str = json_str.decode() # 使用json模块必须要先加入这一行否则会报错\nprint(json.dumps(json_str))\nprint(simplejson.dumps(json_str))\n```\n\n比较性能方面的差异:\n\n[压测数据包链接](http://abral.altervista.org/jsonpickle-bench.zip)\n\n压测脚本：\n```python\nimport timeit\nimport simplejson\nimport pickle\nimport json\n\ntimes = 100\nprint(\"Times are for %i iterations\" % times)\n\nf = open('words.pickle', \"rb\")\nwords = pickle.load(f)\nf.close()\n\n\ndef bench(cmd, imprt):\n    t = timeit.Timer(cmd, imprt)\n    s = t.timeit(number=times)\n    print(\"%s total=%02f avg=%02f\" % (cmd, s, (s / times)))\n    return s\n\n\ndef simplejson_loads():\n    simplejson.loads(simplejson.dumps(words))\n\n\ndef simplejson_dumps():\n    simplejson.dumps(words)\n\n\nb1 = bench('simplejson_loads()', 'from __main__ import simplejson_loads')\nb2 = bench('simplejson_dumps()', 'from __main__ import simplejson_dumps')\n\n\ndef json_dumps():\n    json.dumps(words)\n\n\ndef json_loads():\n    json.loads(json.dumps(words))\n\n\nb3 = bench('json_loads()', 'from __main__ import json_loads')\nb4 = bench('json_dumps()', 'from __main__ import json_dumps')\n\n```\n\n\n返回结果是：\n```\nTimes are for 100 iterations\nsimplejson_loads() total=1.760785 avg=0.017608\nsimplejson_dumps() total=0.921571 avg=0.009216\njson_loads() total=1.351725 avg=0.013517\njson_dumps() total=0.716219 avg=0.007162\n```\n\n> `从上面结果可以看到json 在loads操作和dumps操作都比simplejson好`\n~~网上说的simplejson比json快，感觉说的不对~~\n"
  },
  {
    "path": "notes/Python/流畅的Python/正确重载运算符.md",
    "content": "# 第13章 正确重载运算符\n<!-- TOC -->\n\n- [第13章 正确重载运算符](#%e7%ac%ac13%e7%ab%a0-%e6%ad%a3%e7%a1%ae%e9%87%8d%e8%bd%bd%e8%bf%90%e7%ae%97%e7%ac%a6)\n  - [几个不常用的运算符](#%e5%87%a0%e4%b8%aa%e4%b8%8d%e5%b8%b8%e7%94%a8%e7%9a%84%e8%bf%90%e7%ae%97%e7%ac%a6)\n\n<!-- /TOC -->\n\n## 几个不常用的运算符\n\n这里我们看几个之前没讲过的运算符`__neg__,__pos__,__invert__`\n\n`__neg__`是在-v的时候调用\n\n`__pos__`是在+v的时候调用\n\n`__invert__`是在~v的时候调用\n\n\n看下面的例子：\n\n```python\nclass Vector(object):\n    def __init__(self, x):\n        self.x = x\n\n    def __neg__(self):\n        return \"Vector(%d)\" % (-self.x)\n\n    def __str__(self):\n        return \"Vector(%s)\" % (str(self.data))\n\n    def __iter__(self):\n        return iter(self.data)\n\n    def __pos__(self):\n        return \"Vector(%d)\" % (self.x + 1)\n\n    def __invert__(self):\n        return \"Vector(%d)\" % (~self.x)\n\n\nif __name__ == \"__main__\":\n\n    v = Vector(1)\n\n    print(-v)  # Vector(-1)\n    print(+v)  # Vector(2)\n    print(~v)  # Vector(-2)\n\n```\n\n返回结果：\n```\nVector(-1)\nVector(2)\nVector(-2)\n\n```\n"
  },
  {
    "path": "notes/Python/流畅的Python/符合Python风格的对象.md",
    "content": "# 第9章-符合Python风格的对象\n\n<!-- TOC -->\n\n- [第9章-符合Python风格的对象](#%e7%ac%ac9%e7%ab%a0-%e7%ac%a6%e5%90%88python%e9%a3%8e%e6%a0%bc%e7%9a%84%e5%af%b9%e8%b1%a1)\n  - [format表示法](#format%e8%a1%a8%e7%a4%ba%e6%b3%95)\n  - [__slots__方法](#slots%e6%96%b9%e6%b3%95)\n\n<!-- /TOC -->\n\n## format表示法\n\n```python\n\n>>> print \"Hello %(name)s !\" % {'name': 'James'}\nHello James !\n\n>>> print \"I am years %(age)i years old\" % {'age': 18}\nI am years 18 years old\n\n#format的写法:\n\n>>> print \"Hello {name} !\".format(name=\"James\")\nHello James !\n```\n\n\n## `__slots__`方法\n\n\nPython内置属性很多，其中`__slots__`属性比较少会被用到，基本不会被当作是必选项。但如果您对内存使用有颇高的要求，`__slots__`会给你很大帮助。\n\n`__slots__`的目的又是什么呢？\n\n答案是：优化内存使用。限制实例属性的自由添加只是副作用而已。\n\n那么`__slots__`属性究竟如何神奇？\n这要先从`__dict_`_属性说起。\n\n\n` __dict__`属性的用途在于记录实例属性：\n\n`__dict__`属性用来记录实例中的属性信息，如果对实例中的属性信息进行修改或者添加新的属性，`__dict__`都会对其进行记录。\n\n```python\nclass Person(object):\n    def __init__(self, name, age):\n        self.name = name\n        self.age = age\n\n\nperson = Person(\"tony\", 20)  # 对Person类实例化\nprint(person.__dict__)  # 记录实例所有的属性 {'name': 'tony', 'age': 20}\n\nperson.age = 'jacky'\nperson.gender = 'male'\n\nprint(person.__dict__)  # {'name': 'tony', 'age': 'jacky', 'gender': 'male'}\n\n```\n\n\n\n那么`__slots__`跟`__dict__`又有什么关系呢？\n\n简单点理解:\n\n `__slots_`_存在的价值在于删除`__dict__`属性，从而来**优化类实例对内存的需求**。\n \n而这带来的副作用就是：由于缺少了`__dict__`，类实例木有办法再自由添加属性了！\n\n\n```python\nclass Person(object):\n    __slots__ = (\"name\", \"age\")\n\n    def __init__(self, name, age):\n        self.name = name\n        self.age = age\n\n\nperson = Person(\"tony\", 20)  # 对Person类实例化\n\nprint(\"__dict__\" in dir(person))  # False, 没有了__dict__属性\n\nperson.age = 'jacky'\nperson.gender = 'male'  # AttributeError: 'Person' object has no attribute 'gender'\n# 这就是__slots__的副作用，不能自由添加实例属性了\n\n```\n\n\n总结：\n\n默认情况下，自定义的对象都使用dict来存储属性（通过obj.`__dict__`查看），而python中的dict大小一般比实际存储的元素个数要大（以此降低hash冲突概率），因此会浪费一定的空间。\n\n在新式类中使用`__slots__`，就是告诉Python虚拟机，这种类型的对象只会用到这些属性，\n因此虚拟机预留足够的空间就行了\n\n如果声明了`__slots__`，那么对象就不会再有`__dict__`属性。\n\n\n到底能省多少，取决于类自身有多少属性、属性的类型，以及同时存在多少个类的实例\n\n\n可以查看这个[链接](http://tech.oyster.com/save-ram-with-python-slots/)\n\n\n\n我们也可以自己测试下结果：\n\n```python\n# 使用 profile 进行性能分析\nimport profile\n\n\nclass A(object):\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\n\ndef profile_result():\n    f = [A(1, 2) for i in range(100000)]\n\n\nif __name__ == \"__main__\":\n    profile.run(\"profile_result()\")\n```\n\n\n返回结果：\n```\n         100006 function calls in 0.297 seconds\n\n   Ordered by: standard name\n\n   ncalls  tottime  percall  cumtime  percall filename:lineno(function)\n        1    0.000    0.000    0.297    0.297 :0(exec)\n        1    0.000    0.000    0.000    0.000 :0(setprofile)\n        1    0.016    0.016    0.297    0.297 <string>:1(<module>)\n        1    0.000    0.000    0.281    0.281 demo.py:11(profile_result)\n        1    0.141    0.141    0.281    0.281 demo.py:12(<listcomp>)\n   100000    0.141    0.000    0.141    0.000 demo.py:6(__init__)\n        1    0.000    0.000    0.297    0.297 profile:0(profile_result())\n        0    0.000             0.000          profile:0(profiler)\n\n```\n\n\n这里返回值参数意思：\n```\n其中输出每列的具体解释如下：\n\nncalls：表示函数调用的次数；\n\ntottime：表示指定函数的总的运行时间，除掉函数中调用子函数的运行时间；\n\npercall：（第一个 percall）等于 tottime/ncalls；\n\ncumtime：表示该函数及其所有子函数的调用运行的时间，即函数开始调用到返回的时间；\n\npercall：（第二个 percall）即函数运行一次的平均时间，等于 cumtime/ncalls；\n\nfilename:lineno(function)：每个函数调用的具体信息；\n```\n\n\n查看内存占用情况：\n\n```python\n# 使用 profile 进行性能分析\nfrom memory_profiler import profile\n\n\nclass A(object):\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\n\n@profile\ndef profile_result():\n    f = [A(1, 2) for i in range(100000)]\n\n\nif __name__ == \"__main__\":\n    profile_result()\n\n```\n\n返回结果：\n```python\nFilename: d:\\学习\\python\\demo\\demo.py\nLine #    Mem usage    Increment   Line Contents\n================================================\n    11     50.8 MiB     50.8 MiB   @profile\n    12                             def profile_result():\n    13     68.8 MiB      0.6 MiB       f = [A(1, 2) for i in range(100000)]\n\n\n```\n\n<br>\n改用`__slots__`的方式：\n\n```python\n# 使用 profile 进行性能分析\nfrom memory_profiler import profile\n\n\nclass A(object):\n    __slots__ = ('x', 'y')\n\n    def __init__(self, x, y):\n        self.x = x\n        self.y = y\n\n\n@profile\ndef profile_result():\n    f = [A(1, 2) for i in range(100000)]\n\n\nif __name__ == \"__main__\":\n    profile_result()\n\n```\n\n\n返回结果：\n\n```\nFilename: d:\\学习\\python\\demo\\demo.py\nLine #    Mem usage    Increment   Line Contents\n================================================\n    13     50.6 MiB     50.6 MiB   @profile\n    14                             def profile_result():\n    15     57.9 MiB      0.7 MiB       f = [A(1, 2) for i in range(100000)]\n\n```\n<br>\n\n**可以看到内存使用由原先的68.8MB->57.9MB**"
  },
  {
    "path": "notes/Python/流畅的Python/类元编程.md",
    "content": "# 第21章 类元编程\n\n<!-- TOC -->\n\n- [第21章 类元编程](#%e7%ac%ac21%e7%ab%a0-%e7%b1%bb%e5%85%83%e7%bc%96%e7%a8%8b)\n  - [类工厂函数 collections.namedtuple](#%e7%b1%bb%e5%b7%a5%e5%8e%82%e5%87%bd%e6%95%b0-collectionsnamedtuple)\n  - [ABC元类](#abc%e5%85%83%e7%b1%bb)\n\n<!-- /TOC -->\n## 类工厂函数 collections.namedtuple\n\n\n## ABC元类\n\n这些内容可以查看第11章"
  },
  {
    "path": "notes/Python/流畅的Python/类继承.md",
    "content": "# 第12章-类继承\n\n<!-- TOC -->\n\n- [第12章-类继承](#%e7%ac%ac12%e7%ab%a0-%e7%b1%bb%e7%bb%a7%e6%89%bf)\n  - [super函数](#super%e5%87%bd%e6%95%b0)\n  - [问题](#%e9%97%ae%e9%a2%98)\n\n<!-- /TOC -->\n\n\n## super函数\n\n\nPy 2.x 和 Py 3.x 中有一个很大的区别就是类，无论是类的定义还是类的继承。\n**Py 3.x 中类的继承可以直接使用 super() 关键字代替原来的 super(Class, self)。**\n\n那么 super() 到底是依据什么来继承的呢？\n\nsuper()函数根据传进去的两个参数具体作用如下：\n\n>通过第一参数传进去的类名确定当前在MRO中的哪个位置。MRO(Method Resolution Order)\n>通过第二个参数传进去的self，确定当前的MRO列表。\n\n```python\nclass A(object):\n    def name(self):\n        print('name is xiaoming')\n        # super(A, self).name()\n\n\nclass B(object):\n    def name(self):\n        print('name is cat')\n\n\nclass C(A, B):\n    def name(self):\n        print('name is wang')\n        super(C, self).name()\n\n\nif __name__ == '__main__':\n\n    c = C()\n    print(c.__class__.__mro__)\n    c.name()\n```\n\n返回结果是：\n```\n(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)\nname is wang\nname is xiaoming\n```\n\n如果把注释去掉的结果是：\n```python\n(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)\nname is wang\nname is xiaoming\nname is cat\n\n```\n\n<br>\n再看一个例子：\n\n```python\nclass Base(object):\n    def func_1(self):\n        print('this is base')\n\n\nclass A(Base):\n    def func_1(self):\n        super().func_1()\n        print('this is A')\n\n\nclass B(Base):\n    def func_1(self):\n        super().func_1()\n        print('this is B')\n\n\nclass C(A, B):\n    def func_1(self):\n        super().func_1()\n        print('this is c')\n\n\nprint(C.mro())\nC().func_1()\n\nprint(help(C))\n```\n\n\n返回结果：\n```\nHelp on class C in module __main__:\n\nclass C(A, B)\n| Method resolution order:\n| C\n| A\n| B\n```\n\n“从左到到右”的意思，C继承关系最近的是A, 然后是B，最后是上层BASE。\n调用方法时的入栈顺序是 C A B Base,\n出栈顺序是Base B A C,\n\n```python\nclass Base(object):\n    def __init__(self):\n        print(\"Base created\")\n\n\nclass ChildA(Base):\n    def __init__(self):\n        Base.__init__(self)\n\n\nclass ChildB(Base):\n    def __init__(self):\n        # super(ChildB, self).__init__()\n        # 或者更简单的写法\n        # super().__init__()\n\n        # 如果不用super写法就要这样写\n        print(\"self=\", self)  # <__main__.ChildB object at 0x000001EA8CD085F8>\n        mro = type(self).mro()\n        print(mro)\n        # [<class '__main__.ChildB'>, <class '__main__.Base'>, <class 'object'>]\n\n        for next_class in mro[mro.index(ChildB) + 1:]:\n            print(next_class)  # <class '__main__.Base'>\n            if hasattr(next_class, '__init__'):\n                next_class.__init__(self)\n                break\n\n\nprint(ChildA())\nprint(\"#\" * 10)\nprint(ChildB())\n\n```\n\n\n## 问题\n\n看下面的代码：\n\n```python\nclass Base(object):\n    def __init__(self):\n        print(\"Base created\")\n\n\nclass ChildA(Base):\n    def __init__(self):\n        print(\"ChildA init...\")\n        Base.__init__(self)\n\n\nclass ChildB(Base):\n    def __init__(self):\n        print(\"ChildB init..\")\n        # super(ChildB, self).__init__()\n        # 或者更简单的写法\n        super().__init__()\n\n\nif __name__ == \"__main__\":\n    print(ChildA())\n    print(\"#\" * 10)\n    print(ChildB())\n\n```\n\n返回值：\n```\nChildA init...\nBase created\n<__main__.ChildA object at 0x000001D089FE85F8>\n##########\nChildB init..\nBase created\n<__main__.ChildB object at 0x000001D089FE85F8>\n\n```\n\n<br>\n可以看到结果是一样的，那么   `Base.__init__(self)` 和  `super().__init__()`\n有什么区别呢？\n\n请看这个例子：\n\n```python\nclass Base(object):\n    def __init__(self):\n        print(\"Base created\")\n\n\nclass UserDependency(Base):\n    def __init__(self):\n        print(\"UserDependency init...\")\n        super().__init__()\n\n\nclass ChildA(Base):\n    def __init__(self):\n        print(\"ChildA init...\")\n        Base.__init__(self)\n\n\nclass ChildB(Base):\n    def __init__(self):\n        print(\"ChildB init..\")\n        # super(ChildB, self).__init__()\n        # 或者更简单的写法\n        super().__init__()\n\n\nclass UserA(ChildA, UserDependency):\n    def __init__(self):\n        print(\"UserA init...\")\n        super(UserA, self).__init__()\n\n\nclass UserB(ChildB, UserDependency):\n    def __init__(self):\n        print(\"UserB init...\")\n        super(UserB, self).__init__()\n\n\nif __name__ == \"__main__\":\n    print(UserA())\n    print(\"#\" * 10)\n    print(UserB())\n```\n\n返回结果：\n```\nUserA init...\nChildA init...\nBase created\n<__main__.UserA object at 0x0000019295A38B00>\n##########\nUserB init...\nChildB init..\nUserDependency init...\nBase created\n<__main__.UserB object at 0x0000019295A38B00>\n\n```\n\n<br>\n\n**`这里我们看到了区别，在多重继承中，没有使用super方法的类，没有调用UserDenpendency方法`**"
  },
  {
    "path": "notes/RESTful API.md",
    "content": "- [RESTful 架构详解](#restful-架构详解)\n    - [1. 什么是REST](#1-什么是rest)\n    - [2. RESTful的基本概念](#2-restful的基本概念)\n    - [3. 理解RESTful](#3-理解restful)\n        - [3.1 资源和URI](#31-资源和uri)\n        - [3.2 统一资源接口](#32-统一资源接口)\n        - [3.3 资源表述](#33-资源表述)\n        - [3.4 资源的链接](#34-资源的链接)\n        - [3.5 状态转移](#35-状态转移)\n    - [4. RESTful的最佳设计](#4-restful的最佳设计)\n- [参考资料](#参考资料)\n\n# RESTful 架构详解\n\n## 1. 什么是REST\n\nREST (Representational State Transfer)，中文意思是：表述性状态转移。\n\n一组架构约束条件和原则\n\n如果一个架构符合 REST 的约束条件和原则，我们就称它为 RESTful 架构\n\n\n\n## 2. RESTful的基本概念\n\n1. 在 REST 中，一切的内容都被认为是一种资源\n2. 每个资源都由 URI 唯一标识\n3. 使用统一的接口处理资源请求（POST/GET/PUT/DELETE/HEAD）\n4. 无状态（每次请求之前是无关联，没有 session ）\n\n \n\n## 3. 理解RESTful\n\n要理解RESTful架构，需要理解Representational State Transfer这个词组到底是什么意思，它的每一个词都有些什么涵义。\n\n下面我们结合REST原则，围绕资源展开讨论，从资源的定义、获取、表述、关联、状态变迁等角度，列举一些关键概念并加以解释。\n\n- 资源与URI\n- 统一资源接口\n- 资源的表述\n- 资源的链接\n- 状态的转移\n\n\n\n### 3.1 资源和URI\n\n1. 使用 `/` 来表示资源的层级关系\n2. 使用 `?` 用来过滤资源\n3. 使用 `_` 或者 `-` 让URI的可读性更好\n4. `,` 或 `;` 可以用来表示同级资源的关系\n\n\n\n### 3.2 统一资源接口\n\n| 请求方法 | 描述                                                         |\n| -------- | ------------------------------------------------------------ |\n| GET      | 获取某个资源。 幂等（取多少次结果都没有变化）                |\n| POST     | 创建一个新的资源                                             |\n| PUT      | 替换某个已有的资源（更新操作） ， 幂等（更新多次只保存一个结果） |\n| DELETE   | 删除某个资源                                                 |\n| HEAD     | 主要用于确认 URL 的有效性以及资源更新的日期时间等            |\n| PATCH    | 新引入的，对PUT方法的补充，用来对已知资源进行局部更新        |\n\n\n\n### 3.3 资源表述\n\n上面提到，客户端通过 HTTP 方法可以获取资源，是吧? 不，确切的说，客户端获取的只是资源的表述而已。资源在外界的具体呈现，可以有多种表述(或成为表现、表示)形式，在客户端和服务端之间传送的也是资源的表述，而不是资源本身。 例如文本资源可以采用 html、xml、json 等格式，图片可以使用 PNG 或 JPG 展现出来。\n\n资源的表述包括数据和描述数据的元数据，例如，HTTP 头 \"Content-Type\" 就是这样一个元数据属性。\n\n那么客户端如何知道服务端提供哪种表述形式呢？\n\n答案是可以通过 HTTP 内容协商，客户端可以通过 Accept 头请求一种特定格式的表述，服务端则通过 Content-Type 告诉客户端资源的表述形式。\n\n\n\nMIME 类型\n\naccept: text/xml   html文件\n\nContent-Type告诉客户端资源的表述形式\n\n \n\n### 3.4 资源的链接\n\n 超媒体即应用状态引擎（可以做多层链接）\n\n https://api.github.com/repos/github\n\n```\n{\n  \"message\": \"Not Found\",\n  \"documentation_url\": \"https://developer.github.com/v3\"\n}\n```\n\n\n\n### 3.5 状态转移\n\n服务器端不应该保存客户端状态。\n\n应用状态 -> 服务器端不保存应用状态\n\n \n\n访问订单   根据接口去查询\n\n访问商品   查询\n\n\n\n## 4. RESTful的最佳设计\n\n以豆瓣网为例\n\n1. 应该尽量将 API 部署在专用域名之下 \n   `http://api.douban.com`/v2/user/1000001?apikey=XXX\n\n2. 应该将 API 的版本号放入 URL \n   `http://api.douban.com/v2`/user/1000001?apikey=XXX\n\n3. 在 RESTful 架构中，每个网址代表一种资源（resource），所以网址中不能有动词，只能有名词，而且所用的名词往往与数据库的表格名对应。一般来说，数据库中的表都是同种记录的 ”集合”（collection），所以 API 中的名词也应该使用复数。\n\n\n   http://api.douban.com/v2/`book`/:id (获取图书信息) \n\n   http://api.douban.com/v2/`movie`/subject/:id (电影条目信息) \n\n   http://api.douban.com/v2/`music`/:id (获取音乐信息) \n\n   http://api.douban.com/v2/`event`/:id (获取同城活动)\n\n4. 对于资源的具体操作类型，由 HTTP 动词表示。常用的 HTTP 动词有下面四个(对应 **增/删/改/查** )。 \n\n   **GET**（select）：从服务器取出资源（一项或多项）。 \n   eg. 获取图书信息  【GET】 <http://api.douban.com/v2/book/:id>\n\n   **POST**（create）：在服务器新建一个资源。 \n   eg. 用户收藏某本图书  【POST】 <http://api.douban.com/v2/book/:id/collection>\n\n   **PUT**（update）：在服务器更新资源（客户端提供改变后的完整资源）。 \n   eg. 用户修改对某本图书的收藏  【PUT】 <http://api.douban.com/v2/book/:id/collection>\n\n   **DELETE**（delete）：从服务器删除资源。 \n   eg. 用户删除某篇笔记  【DELETE】 <http://api.douban.com/v2/book/annotation/:id>\n\n5. 如果记录数量很多，服务器不可能都将它们返回给用户。API应该提供参数，过滤返回结果\n\n   `?limit=10`：指定返回记录的数量\n   eg. 获取图书信息  【GET】 <http://api.douban.com/v2/book/:id>`?limit=10`\n\n6. 服务器向用户返回的状态码和提示信息 \n   每个状态码代表不同意思, 就像代号一样\n\n   HTTP状态码\n\n   - 1系 信息\n\n   - 2系 正常返回\n\n   - 3系 重定向\n\n   - 4系 数据异常\n\n   - 5系 服务器异常\n\n   业务状态码\n\n \n\n\n\n# 参考资料\n\n- [RESTful 架构详解 | 菜鸟教程](https://www.runoob.com/w3cnote/restful-architecture.html)\n- [RESTful API 设计指南 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2014/05/restful_api.html)\n"
  },
  {
    "path": "notes/Redis.md",
    "content": "# 前言\n\n这里将持续整理一些 Redis 核心笔记\n\n中文社区：[redis.cn](http://redis.cn/)\n\n\n# 一、Redis\n\n## 1. 简介\n\n\n\n单线程为什么这么快？\n\n1. 纯内存\n2. 非阻塞IO\n3. 避免线程切换和竞争消耗\n\n\n\n单线程Redis注意事项\n\n1. 一次只运行一条命令\n\n2. 拒绝长（慢）命令，例如：keys、flushall、flushdb、slow lua script、mutil/exec、operate big value(collection)\n\n3. Redis其实不是单线程，fysnc file descriptor进行持久化\n\n\n\n特性\n\n1. 速度快\n2. 持久化\n3. 多钟数据结构\n4. 支持多种编程语言\n5. 功能丰富\n6. 简单\n7. 主从复制\n8. 高可用，分布式\n\n\n\n## 2. 应用场景\n\n缓存系统\n\n排行榜\n\n计数器\n\n社交网络\n\n消息队列系统\n\n实时系统\n\n\n## 3. 数据类型\n\n<div align=\"center\"><img src=\"pics/redis-data-structure-types.jpeg\" width=\"650\"/></div><br/>\n\n| 结构类型 | 结构存储的值                                                 | 结构的读写能力                                               |\n| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ |\n| STRING   | 可以是字符串、整数或者浮点数                                 | 对整个字符串或者字符串的其中一部分执行操作<br/>对整数和浮点数执行自增或自减操作 |\n| LIST     | 一个链表，链表上的每个节点都包含了一个字符串                 | 从两端压入或者弹出元素 <br/>对单个或者多个元素<br/>进行修剪，只保留一个范围内的元素 |\n| SET      | 包含字符串的无序收集器（unordered collection），并且被包含的每个字符串都是独一无二、各不相同的 | 添加、获取、移除单个元素<br/>检查一个元素是否存在于集合中<br/>计算交集、并集、差集<br/>从集合里面随机获取元素 |\n| HAST     | 包含键值对的无序散列表                                       | 添加、获取、移除单个键值对<br/>获取所有键值对<br/>检查某个键是否存在 |\n| ZSET     | 字符串成员（member）与浮点数分值（score）之间的有序映射，元素的排列顺序由分值的大小决定 | 添加、获取、删除元素<br/>根据分值范围或者成员来获取元素<br/>计算一个键的排名 |\n\n\n\n### STRING\n\n<div align=\"center\"><img src=\"assets/redis-string.png\" width=\"450\"/></div>\n\n**设置语法**\n\n```\nset key value [EX seconds] [PX ms] [nx|xx]\n```\n\n- key: 键名\n- value: 键值\n- ex seconds: 键秒级过期时间\n- ex ms: 键毫秒及过期时间\n- nx: 键不存在才能设置，setnx和nx选项作用一样，用于添加，分布式锁的实现\n- xx: 键存在才能设置，setxx和xx选项作用一样，用于更新\n\n**常用命令**\n\n```redis\n> set hello world\nOK\n> get hello\n\"world\"\n> del hello\n(integer) 1\n> get hello\n(nil)\n```\n\n书中提到一个有趣的概念，批量操作mget可以提供效率节省时间\n\n逐条 get/se t的时间消耗公式：\n\n```\nn次get/set时间 = n次网络时间 + n次命令时间\n```\n\n批量get/set的时间消耗公式： `n次get/set时间 = 1次网络时间 + n次命令时间`\n\n合理的使用批量操作可以提高Redis性能，但是注意不要量太大，**如果过量的话可能会导致Redis阻塞**\n\n\n\n**时间复杂度**\n\n- set: O(1)\n- get: O(1)\n- del: O(k)，k为键的个数\n- mget: O(k)，k为键的个数\n- mset: O(k)，k为键的个数\n- append: O(1)\n- str: O(1)\n- getrange: O(n), n为字符串的长度\n\n\n\n**内部编码**\n\n- int: 8字节长整型\n- embstr: 小于39字节值\n- raw: 大于39字节的值\n\n\n\n**典型场景**\n\n- 缓存\n- 计算器\n- 分布式锁\n\n\n\n**场景**\n\n- 缓存\n- 计算器\n- 分布式锁\n\n\n\n### LIST\n\n<div align=\"center\"><img src=\"assets/1536026733016.png\" width=\"450\"/></div>\n\n```shell\n> rpush list-key item\n(integer) 1\n> rpush list-key item2\n(integer) 2\n> rpush list-key item\n(integer) 3\n> lrange list-key 0 -1\n1) \"item\"\n2) \"item2\"\n3) \"item\"\n> lindex list-key 1\n\"item2\"\n> lpop list-key\n\"item\"\n> lrange list-key 0 -1\n1) \"item2\"\n2) \"item\"\n```\n\n\n\n### SET\n\n\n\n\n\n<div align=\"center\"><img src=\"assets/1536026799672.png\" width=\"450\"/></div>\n\n```shell\n> sadd set-key item\n(integer) 1\n> sadd set-key item2\n(integer) 1\n> sadd set-key item3\n(integer) 1\n> sadd set-key item\n(integer) 0\n> smembers set-key\n1) \"item\"\n2) \"item2\"\n3) \"item3\"\n> sismember set-key item4\n(integer) 0\n> sismember set-key item\n(integer) 1\n> srem set-key item2\n(integer) 1\n> srem set-key item2\n(integer) 0\n> smembers set-key\n1) \"item\"\n2) \"item3\"\n```\n\n\n\n### HASH\n\n<div align=\"center\"><img src=\"assets/1536026823413.png\" width=\"450\"/></div>\n\n**创建哈希类型的键值**\n\n```shell\n127.0.0.1:6379> hset user name LotusChing \n(integer) 1\n127.0.0.1:6379> hset user age 21\n(integer) 1\n127.0.0.1:6379> hset user gender \"Male\"\n(integer) 1\n```\n\nHSET 不支持创建一次性创建多field\n\n```shell\n127.0.0.1:6379> hset user name \"LotusChing\" age 21\n(error) ERR wrong number of arguments for 'hset' command\n```\n\n**获取哈希键中的field值**\n\n```shell\n127.0.0.1:6379> hget user name\n\"LotusChing\"\n127.0.0.1:6379> hget user age\n\"21\"\n127.0.0.1:6379> hget user gender\n\"Male\"\n```\n\nHGET 不支持一次获取多个field\n\n**获取哈希键中的fields**\n\n```shell\n127.0.0.1:6379> hekys user\n1) \"name\"\n2) \"age\"\n```\n\n**获取哈希键中的所有field的value**\n\n```shell\n127.0.0.1:6379> hvals user\n1) \"LotusChing\"\n2) \"21\"\n```\n\n**删除哈希键中某个field**\n\n```shell\n127.0.0.1:6379> hdel user age\n(integer) 1\n127.0.0.1:6379> hkeys user\n1) \"name\"\n```\n\n**统计哈希中field的个数**\n\n```shell\n127.0.0.1:6379> hkeys user\n1) \"name\"\n2) \"age\"\n3) \"gender\"\n127.0.0.1:6379> hlen user\n(integer) 3\n```\n\n**批量设置哈希键的field**\n\n```shell\n127.0.0.1:6379> hmset user name \"LotusChing\" age 21 gender \"Male\"\nOK\n127.0.0.1:6379> hkeys user\n1) \"name\"\n2) \"age\"\n3) \"gender\"\n127.0.0.1:6379> hvals user\n1) \"LotusChing\"\n2) \"21\"\n3) \"Male\"\n```\n\n**批量获取哈希键中field的value**\n\n```shell\n127.0.0.1:6379> hmget user name age gender\n1) \"LotusChing\"\n2) \"21\"\n3) \"Male\"\n```\n\n**判断哈希键中field是否存在**\n\n```shell\n127.0.0.1:6379> hexists user name\n(integer) 1\n127.0.0.1:6379> hexists user hobbies\n(integer) 0\n```\n\n**一次性获取哈希键中所有的fields和values**\n\n注意：尽量避免使用`hgetall`，因为如果哈希键field过多的话，可能会导致Redis阻塞，建议使用`hmget`获取所需哈希键中的field值，或者采用`hscan`\n\n```shell\n127.0.0.1:6379> hgetall user\n1) \"name\"\n2) \"LotusChing\"\n3) \"age\"\n4) \"21\"\n5) \"gender\"\n6) \"Male\"\n```\n\n\n\n\n\n### ZSET\n\n<div align=\"center\"><img src=\"assets/1536026839475.png\" width=\"450\"/></div>\n\n\n\n```shell\n> zadd zset-key 728 member1\n(integer) 1\n> zadd zset-key 982 member0\n(integer) 1\n> zadd zset-key 982 member0\n(integer) 0\n> zrange zset-key 0 -1 withscores\n1) \"member1\"\n2) \"728\"\n3) \"member0\"\n4) \"982\"\n> zrangebyscore zset-key 0 800 withscores\n1) \"member1\"\n2) \"728\"\n> zrem zset-key member1\n(integer) 1\n> zrem zset-key member1\n(integer) 0\n> zrange zset-key 0 -1 withscores\n1) \"member0\"\n2) \"982\"\n```\n\n\n\n参考资料：\n\n- [Chapter 1: Getting to know Redis | Redis Labs](https://redislabs.com/ebook/part-1-getting-started/chapter-1-getting-to-know-redis/)\n\n\n\n\n## 4. 通用命令\n\n- keys\n\n- dbsize\n\n- del\n\n- exists\n\n- expire key seconds\n\n- ttl\n\n- persist\n\n- type\n\n\n\n**过期时间**\n\n```shell\nredis> SET cache_page \"www.google.com\"\nOK\n\nredis> EXPIRE cache_page 30  # 设置过期时间为 30 秒\n(integer) 1\n\nredis> TTL cache_page    # 查看剩余生存时间\n(integer) 23\n\nredis> EXPIRE cache_page 30000   # 更新过期时间\n(integer) 1\n\nredis> TTL cache_page\n(integer) 29996\n```\n\n\n\n\n\n\n\n\n## 6. Redis持久化\n\nRedis 持久化之RDB和AOF - ITDragon龙 - 博客园\nhttps://www.cnblogs.com/itdragon/p/7906481.html\n\n\n\n# 参考资料\n\n- [ops_book/listlie-886829.md at master · LotusChing/ops_book](ops_book/listlie-886829.md at master · LotusChing/ops_book)\n\n\n# 更新日志\n\n- 2018/9/4 init1.0"
  },
  {
    "path": "notes/SQL.md",
    "content": "# 前言\n\n在这部分将整理 SQL 语句的使用\n\n\n\n# SQL基础\n\n## 1. INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL JOIN\n\nSQL 中的连接查询有四种方式，它们之间其实并没有太大区别，仅仅是查询出来的结果有所不同。\n\n- inner join（内连接）\n- left join（左连接）\n- right join（右连接）\n- full join（全连接）\n\n例如我们有两张表： \n\n![20150603222647340](assets/20150603222647340-2.png)\n\n**Orders** 表通过外键 **Id_P** 和 **Persons** 表进行关联。\n\n### 1. inner join，在两张表进行连接查询时，只保留两张表中完全匹配的结果集。\n\n我们使用 inner join 对两张表进行连接查询，sql如下：\n\n```mysql\nSELECT Persons.LastName, Persons.FirstName, Orders.OrderNo\nFROM Persons\nINNER JOIN Orders\nON Persons.Id_P=Orders.Id_P\nORDER BY Persons.LastName\n```\n\n查询结果集： \n\n![20150603222827804](assets/20150603222827804.png)\n\n此种连接方式 Orders 表中 Id_P 字段在 Persons 表中找不到匹配的，则不会列出来。\n\n\n\n### 2. left join，在两张表进行连接查询时，会返回左表所有的行，即使在右表中没有匹配的记录\n\n我们使用 left join 对两张表进行连接查询，sql如下：\n\n```mysql\nSELECT Persons.LastName, Persons.FirstName, Orders.OrderNo\nFROM Persons\nLEFT JOIN Orders\nON Persons.Id_P=Orders.Id_P\nORDER BY Persons.LastName\n```\n查询结果如下： \n\n![20150603223638605](assets/20150603223638605.png)\n\n可以看到，左表（Persons表）中 LastName 为 Bush 的行的 Id_P 字段在右表（Orders表）中没有匹配，但查询结果仍然保留该行。\n\n\n\n### 3. right join，在两张表进行连接查询时，会返回右表所有的行，即使在左表中没有匹配的记录\n\n我们使用right join对两张表进行连接查询，sql如下：\n\n```mysql\nSELECT Persons.LastName, Persons.FirstName, Orders.OrderNo\nFROM Persons\nRIGHT JOIN Orders\nON Persons.Id_P=Orders.Id_P\nORDER BY Persons.LastName\n```\n\n 查询结果如下：\n\n![20150603224352995](assets/20150603224352995.png)\n\nOrders 表中最后一条记录 Id_P 字段值为 65，在左表中没有记录与之匹配，但依然保留。\n\n### 4. full join，在两张表进行连接查询时，返回左表和右表中所有没有匹配的行\n\n我们使用 full join 对两张表进行连接查询，sql如下：\n\n```mysql\nSELECT Persons.LastName, Persons.FirstName, Orders.OrderNo\nFROM Persons\nFULL JOIN Orders\nON Persons.Id_P=Orders.Id_P\nORDER BY Persons.LastName\n```\n\n查询结果如下： \n\n![20150603224604636](assets/20150603224604636.png)\n\n查询结果是 left join 和 right join 的并集。\n\n这些连接查询的区别也仅此而已。\n\n\n\n参考来源：\n\n- [SQL中INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL JOIN区别 - 杨浪 - 博客园](https://www.cnblogs.com/yanglang/p/8780722.html)\n\n\n\n\n\n"
  },
  {
    "path": "notes/SkillTree/backend-skill.md",
    "content": "# 后台开发 技能图谱\n\n> 欢迎在 [issue#25](https://github.com/frank-lam/fullstack-tutorial/issues/25) 中留言，持续更新\n\n![backend-skill](assets/backend-skill.svg)"
  },
  {
    "path": "notes/Socket.md",
    "content": "高并发网络编程之epoll详解 - CSDN博客\nhttps://blog.csdn.net/shenya1314/article/details/73691088\n\n\n\n\n\n## RMI\n\n在RMI中通过代理模式实现\n\n\n\n## RPC\n\n"
  },
  {
    "path": "notes/SpringBoot/README.md",
    "content": "## Spring Boot 学习指南\n\n"
  },
  {
    "path": "notes/XP极限编程.md",
    "content": "<!-- TOC -->\n\n- [了解 XP 极限编程](#了解-xp-极限编程)\n    - [解析极限编程](#解析极限编程)\n    - [四大价值观](#四大价值观)\n        - [沟通](#沟通)\n        - [简单](#简单)\n        - [反馈](#反馈)\n        - [勇气](#勇气)\n        - [四大价值观之外](#四大价值观之外)\n    - [5 个原则](#5-个原则)\n        - [快速反馈](#快速反馈)\n        - [简单性假设](#简单性假设)\n        - [逐步修改](#逐步修改)\n        - [提倡更改](#提倡更改)\n        - [优质工作](#优质工作)\n    - [13 个最佳实践](#13-个最佳实践)\n        - [计划游戏](#计划游戏)\n        - [小型发布](#小型发布)\n        - [隐喻](#隐喻)\n        - [简单设计](#简单设计)\n        - [测试先行／测试驱动开发](#测试先行／测试驱动开发)\n        - [重构](#重构)\n        - [结对编程](#结对编程)\n        - [集体代码所有制](#集体代码所有制)\n        - [持续集成](#持续集成)\n        - [每周工作 40 小时／可持续的速度](#每周工作-40-小时／可持续的速度)\n        - [现场客户](#现场客户)\n        - [编码标准](#编码标准)\n        - [配合是关键](#配合是关键)\n    - [原文链接](#原文链接)\n\n<!-- /TOC -->\n# 了解 XP 极限编程\n\n敏捷方法论有一个共同的特点，那就是都将矛头指向了“文档”，它们认为传统的软件工程方法文档量太“重”了，称为“重量级”方法，而相应的敏捷方法则是“轻量级”方法。正是因为“轻量级”感觉没有什么力量，不但不能够有效体现灵活性，反而显得是不解决问题的方法论似的。因此，就有了一次划时代的会议，创建了敏捷联盟。\n\n在敏捷方法论领域中，比较知名的、有影响力的，是拥有与 Microsoft 的操作系统相同缩写语——XP的极限编程（eXtreme Programming）。极限编程方法论可以说是敏捷联盟中最鲜艳的一面旗帜，也是被研究、尝试、应用、赞扬、批判最多的一种方法论，也是相对来说最成熟的一种。\n\n这一被誉为“黑客文化”的方法论的雏形最初形成于1996—1999年间，Kent Beck、Ward Cunninggham、Ron Jeffrey 在开发 C3 项目（Chrysler Comprehensive Compensation）的实践中总结出了 XP 的基本元素。在此之后，Kent Beck 和他的一些好朋友们一起在实践中完善提高，终于形成了极限编程方法论。\n\n## 解析极限编程\n\n那么什么是 XP 呢？XP 是一种轻量（敏捷）、高效、低风险、柔性、可预测、科学而且充满乐趣的软件开发方式。与其他方法论相比，其最大的不同在于：\n\n- 在更短的周期内，更早地提供具体、持续的反馈信息。\n- 在迭代的进行计划编制，首先在最开始迅速生成一个总体计划，然后在整个项目开发过程中不断的发展它。\n- 依赖于自动测试程序来监控开发进度，并及早地捕获缺陷。\n- 依赖于口头交流、测试和源程序进行沟通。\n- 倡导持续的演化式设计。\n- 依赖于开发团队内部的紧密协作。\n- 尽可能达到程序员短期利益和项目长期利益的平衡。\n\nKent Beck 曾经说过“开车”就是一个 XP 的范例，即使看上去进行得很顺利，也不要把视线从公路上移开，因为路况的变化，将使得你必须随时做出一些这样那样的调整。而在软件项目中，客户就是司机，他们也没有办法确切地知道软件应该做什么，因此程序员就需要向客户提供方向盘，并且告知我们现在的位置。\n\nXP 包括写什么呢？如图，`XP 由价值观、原则、实践和行为四个部分组成，它们彼此相互依赖、关联， 并通过行为贯穿于整个生命期。`\n\n\n\n<div align=\"center\"><img src=\"assets/20150924172800834\" width=\"\"/></div>\n\n## 四大价值观\n\n\nXP 的核心是其总结的沟通（Communication）、简单（Simplicity）、反馈（Feedback）、勇气（Courage）四大价值观，它们是XP的基础，也是XP的灵魂。\n\n\n此外还扩展了第五个价值观：谦逊（Modesty）。 XP用“沟通、简单、反馈、勇气和谦逊”来减轻开发压力和包袱；XP 精神可以启发我们如何学习和对待快速变化、多样的开发技术。成功学习 XP 的关键，是用“沟通、简单、反馈、勇气和谦逊”的态度来对待 XP；轻松愉快地来感受 XP 的实践思想；自己认真实践后，通过对真实反馈的分析，来决定 XP 对自己的价值；有勇气接受它，或改进它。\n\n### 沟通\n\n通常程序员给人留下的印象就是“内向、不善言谈”，然后项目中的许多问题就出在这些缺乏沟通的开发人员身上。经常由于某个程序员做出了一个设计决定，但是却不能及时地通知大家，结果使得大家在协作与配合上出现了很多的麻烦，而在传统的方法论中，并不在意这种口头沟通不畅的问题，而是希望借助于完善的流程和面面俱到的文档、报表、计划来替代，但是这同时又引入了效率不高的新问题。\n\nXP 方法论认为，如果小组成员之间无法做到持续的、无间断的交流，那么协作就无从谈起，从这个角度能够发现，通过文档、报表等人工制品进行交流面临巨大的局限性。因此，XP 组合了诸如对编程这样的最佳实践，鼓励大家进行口头交流，通过交流解决问题，提高效率。\n\n### 简单\n\nXP 方法论提倡在工作中秉承“够用就好”的思路，也就是尽量地简单化，只要今天够用就行，不考虑明天会发现的新问题。这一点看上去十分容易，但是要真正做到保持简单的工作其实很难的。因为在传统的开发方法中，都要求大家对未来做一些预先规划，以便对今后可能发生的变化预留一些扩展的空间。\n\n正如对传统开发方法的认识一样，许多开发人员也会质疑 XP，保持系统的扩展性很重要，如果都保持简单，那么如何使得系统能够有良好的扩展性呢？其实不然，保持简单的理由有两个：\n\n- 开发小组在开发时所做的规划，并无法保证其符合客户需要的，因此做的大部分工作都将落空，使得开发过程中重复的、没有必要的工作增加，导致整体效率降低。\n- 另外，在 XP 中提倡时刻对代码进行重构，一直保持其良好的结构与可扩展性。也就是说，可扩展性和为明天设计并不是同一个概念，XP 是反对为明天考虑而工作，并不是说代码要失去扩展性\n\n而且简单和沟通之间还有一种相对微妙的相互支持关系。当一个团队之间，沟通的越多，那么就越容易明白哪些工作需要做，哪些工作不需要做。另一方面，系统越简单，需要沟通的内容也就越少，沟通也将更加全面。\n\n### 反馈\n\n是什么原因使得我们的客户、管理层这么不理解开发团队？为什么客户、管理层总是喜欢给我们一个死亡之旅？究其症结，就是开发的过程中缺乏必要的反馈。在许许多多项目中，当开发团队经历过了需求分析阶段之后，在相当长的一段时间内，是没有任何反馈信息的。整个开发过程对于客户和管理层而言就像一个黑盒子，进度完全是不可见的。\n\n而且在项目的过程中，这样的现象不仅出现在开发团队与客户、管理层之间，还包括在开发团队内部。这一切问题都需要我们更加注重反馈。，反馈对于任何软件项目的成功都是至关重要的，而在 XP 方法论中则更进一步，通过持续、明确的反馈来暴露软件状态的问题。具体而言就是：\n\n- 在开发团队内部，通过提前编写单元测试代码，时时反馈代码的问题与进展。\n- 在开发过程中，还应该加强集成工作，做到持续集成，使得每一次增量都是一个可执行的工作版本，也就是逐渐是软件长大，整个过程中，应该通过向客户和管理层演示这些可运行的版本，以便及早地反馈，及早地发现问题。\n\n同时，我们也会发现反馈与沟通也有着良好的配合，及时和良好的反馈有助于沟通。而简单的系统更有利于测试和反馈。\n\n### 勇气\n\n在应用 XP 方法论时，我们每时每刻都在应对变化：由于沟通良好，因此会有更多需求变更的机会；由于时刻保持系统的简单，因此新的变化会带来一些重新开发的需要；由于反馈及时，因此会有更多中间打断你的思路的新需求。\n\n总之这一切，使得你立刻处于变化之中，因此这时就需要你有勇气来面对快速开发，面对可能的重新开发。也许你会觉得，为什么要让我们的开发变得如此零乱，但是其实这些变化若你不让它早暴露，那么它就会迟一些出现，并不会因此消亡，因此，XP 方法论让它们早出现、早解决，是实现“小步快走”开发节奏的好办法。\n\n也就是 XP 方法论要求开发人员穿上强大、自动测试的盔甲，勇往直前，在重构、编码规范的支持下，有目的地快速开发。\n\n勇气可以来源于沟通，因为它使得高风险、高回报的试验成为可能；勇气可以来源于简单，因为面对简单的系统，更容易鼓起勇气；勇气可以来源于反馈，因为你可以及时获得每一步前进的状态（自动测试），会使得你更勇于重构代码。\n\n### 四大价值观之外\n\n在这四大价值观之下，隐藏着一个更深刻的东西，那就是尊重。因为这一切都建立在团队成员之间的相互关心、相互理解的基础之上。\n\n## 5 个原则\n\n### 快速反馈\n\n及时地、快速地获取反馈，并将所学到的知识尽快地投入到系统中去。也就是指开发人员应该通过较短的反馈循环迅速地了解现在的产品是否满足了客户的需求。这也是对反馈这一价值观的进一步补充。\n\n### 简单性假设\n\n类似地，简单性假设原则是对简单这一价值观的进一步补充。这一原则要求开发人员将每个问题都看得十分容易解决，也就是说只为本次迭代考虑，不去想未来可能需要什么，相信具有将来必要时增加系统复杂性的能力，也就是号召大家出色地完成今天的任务。\n\n### 逐步修改\n\n就像开车打方向盘一样，不要一次做出很大的改变，那样将会使得可控性变差，更适合的方法是进行微调。而在软件开发中，这样的道理同样适用，任何问题都应该通过一系列能够带来差异的微小改动来解决。\n\n### 提倡更改\n\n在软件开发过程中，最好的办法是在解决最重要问题时，保留最多选项的那个。也就是说，尽量为下一次修改做好准备。\n\n### 优质工作\n\n在实践中，经常看到许多开发人员喜欢将一些细小的问题留待后面解决。例如，界面的按钮有一些不平整，由于不影响使用就先不管；某一两个成员函数暂时没用就不先写等。这就是一种工作拖泥带水的现象，这样的坏习惯一旦养成，必然使得代码质量大打折扣。\n\n而在 XP 方法论中，贯彻的是“小步快走”的开发原则，因此工作质量决不可打折扣，通常采用测试先行的编码方式来提供支持。\n\n## 13 个最佳实践\n\n在 XP 中，集成了 13 个最佳实践，有趣的是，它们没有一个是创新的概念，大多数概念和编程一样老。其主要创新点在于提供一种良好的思路，将这些最佳实践结合在一起，并且确保尽可能彻底地执行它们，使得它们能够在最大程度上相互支持，紧接下来，我们就对每一种最佳实践进行一番了解。\n\n### 计划游戏\n\n计划游戏的主要思想就是先快速地制定一份概要的计划，然后随着项目细节的不断清晰，再逐步完善这份计划。计划游戏产生的结果是一套用户故事及后续的一两次迭代的概要计划。\n\n“客户负责业务决策，开发团队负责技术决策”是计划游戏获得成功的前提条件。也就是说，系统的范围、下一次迭代的发布时间、用户故事的优先级应该由客户决定；而每个用户故事所需的开发时间、不同技术的成本、如何组建团队、每个用户故事的风险，以及具体的开发顺序应该由开发团队决定。\n\n好了，明白这些就可以进行计划游戏了。首先客户和开发人员坐在同一间屋子里，每个人都准备一支笔、一些用于记录用户故事的纸片，最好再准备一个白板，就可以开始了。\n\n- 客户编写故事：由客户谈论系统应该完成什么功能，然后用通俗的自然语言，使用自己的语汇，将其写在卡片上，这也就是用户故事。\n- 开发人员进行估算：首先客户按优先级将用户故事分成必须要有、希望有、如果有更好三类，然后开发人员对每个用户故事进行估算，先从高优先级开始估算。如果在估算的时候，感到有一些故事太大，不容易进行估算，或者是估算的结果超过 2人/周，那么就应该对其进行分解，拆成 2 个或者多个小故事。\n- 确定迭代的周期：接下来就是确定本次迭代的时间周期，这可以根据实际的情况进行确定，不过最佳的迭代周期是 2~3 周。有了迭代的时间之后，再结合参与的开发人数，算出可以完成的工作量总数。然后根据估算的结果，与客户协商，挑出时间上够、优先级合适的用户故事组合，形成计划。\n\n### 小型发布\n\nXP 方法论秉承的是“持续集成，小步快走”的哲学，也就是说每一次发布的版本应该尽可能的小，当然前提条件是每个版本有足够的商业价值，值得发布。\n\n由于小型发布可以使得集成更频繁，客户获得的中间结果也越频繁，反馈也就越频繁，客户就能够实时地了解项目的进展情况，从而提出更多的意见，以便在下一次迭代中计划进去。以实现更高的客户满意度。\n\n### 隐喻\n\n相对而言，隐喻这一个最佳实践是最令人费解的。什么是隐喻呢？根据词典中的解释是：“一种语言的表达手段，它用来暗示字面意义不相似的事物之间的相似之处”。那么这在软件开发中又有什么用呢？总结而言，常常用于四个方面。\n\n- 寻求共识：也就是鼓励开发人员在寻求问题共识时，可以借用一些沟通双方都比较熟悉的事物来做类比，从而帮助大家更好地理解解决方案的关键结构，也就是更好地理解系统是什么、能做什么。\n- 发明共享词汇：通过隐喻，有助于提出一个用来表示对象、对象间的关系通用名称。例如，策略模式（用来表示可以实现多种不同策略的设计模式）、工厂模式（用来表示可以按需“生产”出所需类得设计模式）等。\n- 创新的武器：有的时候，可以借助其他东西来找到解决问题的新途径。例如：“我们可以将工作流看做一个生产线”。\n- 描述体系结构：体系结构是比较抽象的，引入隐喻能够大大减轻理解的复杂度。例如管道体系结构就是指两个构件之间通过一条传递消息的“管道”进行通信。\n\n当然，如果能够找到合适的隐喻是十分快乐的，但并不是每种情况都可以找到恰当的隐喻，你也没有必要强求\n\n### 简单设计\n\n强调简单设计的价值观，引出了简单性假设原则，落到实处就是“简单设计”实践。这个实践看上去似乎很容易理解，但却又经常被误解，许多批评者就指责 XP 忽略设计是不正确的。其实，XP 的简单设计实践并不是要忽略设计，而且认为设计不应该在编码之前一次性完成，因为那样只能建立在“情况不会发生变化”或者“我们可以预见所有的变化”之类的谎言的基础上的。\n\nKent Beck 概念中简单设计是这样的：\n\n- 能够通过所有的测试程序。\n- 没有包括任何重复的代码。\n- 清楚地表现了程序员赋予的所有意图。\n- 包括尽可能少的类和方法\n- 他认为要想保持设计简单的系统，需要具备简单思考的能力，拥有理解代码和修改的勇气，以及为了消除代码的“坏味道”而定期重构的习惯。\n- 那么如何开始进行简单的设计呢？XP 实践者们也总结也一些具体的、可操作的思考方法。\n- 首先写测试代码：具体将在后面详细描述。\n- 保持每个类只负责一件事：SRP（单一职责原则）是面向对象设计的基础原则之一。\n- 使用 Demeter（迪米特）法则：迪米特法则，也称为 LoD 法则、最少知识原则。也就是指一个对象应当对其他对象尽可能少地了解。用隐喻的方法来解释的话就是“只与你直接的朋友通信”、“不要和陌生人说话”。\n- 使用 CRC 卡片进行探索。\n\n### 测试先行／测试驱动开发\n\n当我第一次看到“测试先行”这个概念的时候，我的第一感觉就是不解，陷入了“程序都还没有写出来，测试什么呀？”的迷思。我开始天马行空地寻求相关的隐喻，终于找到了能够启发我的工匠，首先，我们来看看两个不同的工匠是如何工作的吧。\n\n- 工匠一：先拉上一根水平线，砌每一块砖时，都与这跟水平线进行比较，使得每一块砖都保持水平。\n- 工匠二：先将一排砖都砌完，然后再拉上一根水平线，看看哪些砖有问题，对有问题的砖进行适当的调整。\n\n你会选择哪种工作方法呢？你一定会骂工匠二笨吧！这样多浪费时间呀！然而你自己想想，你平时在编写程序的时候又是怎么做的呢？我们就是按工匠二的方法在工作呀！甚至有时候比工匠二还笨，是整面墙都砌完了，直接进行“集成测试”，经常让整面的墙倒塌。看到这里，你还会觉得自己的方法高明吗？这个连工匠都明白的道理，自己却画地为牢呀。\n\n不仅我们没有采用工匠一的工作方法，甚至有的时候程序员会以“开发工作太紧张”为理由，而忽略测试工作。但这样却导致了一个恶性循环，越是没有空编写测试程序，代码的效率与质量越差，花在找 Bug、解决 Bug 的时间也越来越多，实际产能大打降低。由于产能降低了，因此时间更紧张，压力更大。你想想，为什么不拉上一根水平线呢？难道，我们不能够将后面浪费的时间花在单元测试上，使得我们的程序一开始就更健壮，更加易于修改吗？不过，编写测试程序当然要比拉一条水平线难道多，所以我们需要引入“自动化测试工具”，免费的 xUnit 测试框架就是你最佳的选择。\n\n为了鼓励程序员原意甚至喜欢在编写程序之前编写测试代码，XP 方法论还提供了许多有说服力的理由。\n\n- 如果你已经保持了简单的设计，那么编写测试代码根本不难。\n- 如果你在结对编程，那么如果你想出一个好的测试代码，那么你的伙伴一定行。\n- 当所有的测试都通过的时候，你再也不会担心所写的代码今后会“暗箭伤人”，那种感觉是相当棒的。\n- 当你的客户看到所有的测试都通过的时候，会对程序充满前所未有的信心。\n- 当你需要进行重构时，测试代码会给你带来更大的勇气，因为你要测试是否重构成功只需要一个按钮。\n\n测试先行是 XP 方法论中一个十分重要的最佳实践，并且其中所蕴含的知识与方法也十分丰富。\n\n### 重构\n\n重构时一种对代码进行改进而不影响功能实现的技术，XP 需要开发人员在闻到代码的坏味道时，有重构代码的勇气。重构的目的是降低变化引发的风险，使得代码优化更加容易。通常重构发生在两种情况之下。\n\n- 实现某个特性之前：尝试改变现有的代码结构，以使得实现新的特性更加容易。\n- 实现某个特性之后：检查刚刚写完的代码后，认真检查一下，看是否能够进行简化。\n\n在《重构》一书中，作者 Martin Fowler 提示我们：在考虑重构时，应该要养成编写并经常运行测试代码的习惯；要先编写代码，再进行重构；把每一次增加功能都当做一次重构的好时机；将每一个纠正错误当做一次重构的重要时机。同时，该书中也列出大量需要重构的情况和重构方法。\n\n最后类似地，给还没有足够勇气进行重构的程序员打几剂强心针：\n\n- XP 提倡集体代码所有制，因此你可以大胆地在任何需要修改的地方做改动。\n- 由于在 XP 项目组中有完整的编码标准，因此在重构前无须重新定义格式。\n- 在重构中遇到困难，和你结对编程的伙伴能够为你提供有效的帮助。\n- 简单的设计，会给重构带来很大的帮助。\n- 测试先行让你拥有了一个有效的检验器，随时运行一下就知道你重构的工作是否带来了影响。\n- 由于 XP 在持续集成，因此你重构所带来的破坏很快就能够暴露，并且得以解决。\n\n重构技术是对简单性设计的一个良好的补充，也是 XP 中重视“优质工作”的体现，这也是优秀的程序员必备的一项技能。\n\n### 结对编程\n\n“什么！两个人坐在一起写程序？那岂不是对人力的巨大浪费吗？而且我在工作时可不喜欢有一个人坐在边上当检察官。”是的，正如这里列举出来的问题一样，结对编程技术还是被很多人质疑的。\n\n不过，自从 20 世纪 60 年代，就有类似的实践在进行，长期以来的研究结果却给出了另外一番景象，那就是结对编程的效率反而比单独编程更高。一开始虽然会牺牲一些速度，但慢慢的，开发速度会逐渐加快，究其原因，主要是结对编程大打降低了沟通的成本，提供了工作的质量，具体表现在：\n\n- 所有的设计决策确保不是由一个人做出的。\n- 系统的任何一个部分都肯定至少有 2 个人以上熟悉。\n- 几乎不可能有 2 个人都忽略的测试项或者其他任务\n- 结对组合的动态性，是一个企业知识管理的好途径。\n- 代码总是能够保证被评审过。\n- 而且 XP 方法论集成的其他最佳实践也能够使得结对编程更加容易进行：\n- 编码标准可以消除一些无谓的分歧。\n- 隐喻可以帮助结对伙伴更好地沟通。\n- 简单设计可以使得结对伙伴更了解他们所从事的工作。\n\n结对编程技术被誉为 XP 保持工作质量、强调人文主义的一个典型的实践，应用得当还能够使得开发团队之前的协作更加流畅、知识交流与共享更加频繁，团队的稳定性也会更加稳固。\n\n### 集体代码所有制\n\n由于 XP 方法论鼓励团队进行结对编程，而且认为结对编程的组合应该动态地搭配，根据任务的不同、专业技能的不同进行最优组合。由于每个人都肯定会遇到不同的代码，所以代码的所有制就不再适合于私有，因为那样会给修改工作带来巨大的不便。\n\n也就是说，团队中的每个成员都拥有对代码进行改进的权利，每个人都拥有全部代码，也都需要对全部代码负责。同时，XP 强调代码是谁破坏的（也就是修改后发生问题），就应该由谁来修复。\n\n由于在 XP 中，有一些与之匹配的最佳实践，因此你并无须担心采用集体代码所有制会让你的代码变得越来越乱：\n\n- 由于在 XP 项目中，集成工作是一件经常性得工作，因此当有人修改代码而带来了集成的问题，会在很快的时间内被发现。\n- 由于每一个类都会有一个测试代码，因此不论谁修改了代码，都需要运行这个测试代码，这样偶然性的破坏发生的概率将很小。\n- 由于每一个代码的修改就是通过了结对的两个程序员共同思考，因此通常做出的修改都是对系统有益的。\n- 由于大家都坚持了相同的编码标准，因此代码的可读性、可修改性都会比较好，而且还能够避免由于命名法、缩进等小问题引发经常性得代码修改。\n\n集成代码所有制是 XP 与其他敏捷方法的一个较大不同，也是从另一个侧面体现了 XP 中蕴含的很深厚的编码情节。\n\n### 持续集成\n\n在前面谈到小型发布、重构、结对编程、集体代码所有制等最佳实践的时候，我们多次看到“持续集成”的身影，可以说持续集成是对这些最佳实践的基本支撑条件。\n\n可能大家会对持续集成与小型发布代表的意思混淆不清，其实小型发布是指在开发周期经常发布中间版本，而持续集成的含义则是要求 XP 团队每天尽可能多次地做代码集成，每次都在确保系统运行的单元测试通过之后进行。\n\n这样，就可以及早地暴露、消除由于重构、集体代码所有制所引入的错误，从而减少解决问题的痛苦\n\n要在开发过程中做到持续集成并不容易，首先需要养成这个习惯。而且集成工作往往是十分枯燥、烦琐的，因此适当地引入每日集成工具是十分必要的。XP 建议大家首先使用配置管理服务器将代码管理起来，然后使用 Ant 或 Nant 等 XP 工具，编写集成脚本，调用 xUint 等测试框架，这样就可以实现每当程序员将代码 Check in 到配置服务器上时，Ant 就会自动完成编译和集成，并调用测试代码完成相应的测试工作。\n\n### 每周工作 40 小时／可持续的速度\n\n这是最让开发人员开心的、管理者反对的一个最佳实践了，加班、再加班早已成为开发人员的家常便饭，也是管理者最常使用的一种策略，而 XP 方法论认为，加班最终会扼杀团队的积极性，最终导致项目失败，这也充分体现了 XP 方法关注人的因素比关注过程的因素更多一些。\n\nKent Beck 认为开发人员即使能够工作更长的时间，他们也不该这样做，因为这样做会使他们更容易厌倦编程工作，从而产生一些影响他们效能的其他问题。因此，每周工作 40 小时是一种顺势行为，是一种规律。其实对于开发人员和管理者来说，违反这种规律是不值得的。\n\n- 开发人员：如果不懂得休息，那么就无法将自己的节奏调整到最佳状态，那么就会带来很大的负面影响。而且在精神不集中的状态下，开发质量也得不到保证。\n- 管理者：也许这可以称得上“第二种人月神话”，那就是你不得不通过延长每天的工作时间来获得更多的人月。这是因为，每个开发人员的工作精力是有限的，不可能无限增长，在精力不足的时候，不仅写出来的代码质量没有保障，而且还可能为项目带来退步的效果。因此采用加班的方式并不是一个理性的方式，是得不偿失的。\n\n不过有一点是需要解释的，“每周工作 40 小时”中的 40 不是一个绝对数，它所代表的意思是团队应该保证按照“正常的时间”进行工作。那么如何做到这一点呢？\n\n- 首先，定义符合你团队情况的“正常工作时间”。\n- 其次，逐步将工作时间调整到“正常工作时间”。\n- 再次，除非你的时间计划一团糟，否则不应该在时间妥协。\n- 最后，鼓起勇气，制定一个合情合理的时间表。\n\n正如米卢说过的“享受足球”一样，同样地，每一个开发人员应该做到“享受编程”，那么“每周工作 40 小时”就是你的起点。\n\n团队只有持久才有获胜的希望。他们以能够长期维持的速度努力工作，他们保存精力，他们把项目看作是马拉松长跑，而不是全速短跑。\n\n### 现场客户\n\n为了保证开发出来的结果与客户的预想接近，XP 方法论认为最重要的需要将客户请到开发现场。就像计划游戏中提到过的，在 XP 项目中，应该时刻保证客户负责业务决策，开发团队负责技术决策。因此，在项目中有客户在现场明确用户故事，并做出相应的业务决策，对于 XP 项目而言有着十分重要的意义。\n\n也许有人会问，客户提交了用户故事之后不就完成工作了吗？其实很多尝试过用户故事的团队都会发现其太过简单，包含的信息量极少，XP方法论不会不了解，因此，不会把用户故事当做开发人员交付代码的唯一指示。用户故事只是一个起点，后面的细节还需要开发人员与客户之间建立起来的良好沟通来补充。\n\n作为一名有经验的开发人员，绝对不会对现场客户的价值产生任何怀疑，但是都会觉得想要实现现场客户十分困难。要实现这一点，需要对客户进行沟通，让其明白，想对于开发团队，项目成功对于客户而言更为重要。而现场客户则是保障项目成功的一个重要措施，想想在你装修房子的时候，你是不是常常在充当现场客户的角色呢？其实这隐喻就是让客户理解现场客户重要性最好的突破口。\n\n其实现场客户在具体实施时，也不是一定需要客户一直和开发团队在一起，而是在开发团队应该和客户能够随时沟通，可以是面谈，可以是在线聊天，可以是电话，当然面谈是必不可少的。其中的关键是当开发人员需要客户做出业务决策是，需要进一步了解业务细节时能够随时找到相应的客户。\n\n不过，也有一些项目是可以不要现场客户参与的：\n\n- 当开发组织中已经有相关的领域专家时。\n- 当做一些探索性工作，而且客户也不知道他想要什么时（例如新产品、新解决方案的研究与开发）。\n\n去尝试吧，现场客户不仅可以争取得到，而且还能使得团队焕然一新，与客户建立起良好的合作与信任。\n\n### 编码标准\n\n编码标准是一个“雅俗共享”的最佳实践，不管是代表重型方法论的 RUP，PSP，还是代表敏捷方法论的 XP，都认为开发团队应该拥有一个编码标准。XP 方法论认为拥有编码标准可以避免团队在一些与开发进度无关的细节问题上发生争论，而且会给重构、结对编程带来很大麻烦。试想如果有人将你上次写的代码的变量命名法做了修改，下次你需要再改这部分代码时，会是一种什么感觉呢？\n\n不过，XP 方法论的编码标准的目的不是创建一个事无巨细的规则表，而是只要能够提供一个确保代码清晰，便于交流的指导方针。\n\n如果你的团队已经拥有编码标准，就可以直接使用它，并在过程中进行完善。如果还没有，那么大家可以先进行编码，然后在过程中逐步总结出编码规则，边做边形成。当然除了这种文字规范以外，还可以采用一些如自动格式化代码工具之类的方法进行代码规范。，事实上，你只需要很好地贯彻执行其他的实践并且进行沟通，编码标准会很容易地浮现出来。\n\n### 配合是关键\n\n有句经典名言“1+1 > 2 ”最适合表达 XP 的观点，Kent Beck 认为 XP 方法论的最大价值在于在项目中融会贯通地运用12个最佳实践，而非单独地使用。你当然可以使用其中的一些实践，但这并不意味着你就运用了 XP 方法论。XP 方法论真正能够发挥其效能，就必须完整地运用12个实践。\n\n\n\n<div align=\"center\"><img src=\"assets/20150924173757246\" width=\"450\"/></div>\n\n\n\n## 原文链接\n\n<https://blog.csdn.net/fw0124/article/details/48713959>"
  },
  {
    "path": "notes/archives/我的秋招之路.md",
    "content": "# 我的秋招之路\n\n## 秋招研磨\n\n　　经过了大半年的学习和努力。2018 年 10 月 17 日，我的秋招终于落下帷幕。\n\n　　讲讲我的秋招之路吧。大概是 2018 年 2 月底，春节都还没结束，学校的就业群里也开始了阿里内推，2018 年暑期实习生招聘的序幕拉开了。\n\n　　由于是专硕，两年的读研时间显的好短暂，才短暂接触了几个月的机器学习算法，都才刚刚入门了 Python 和会使用 Tensorflow 和 Scikit-learn 框架进行一些简单的机器学习分类工作，马上又要着手于校招，让我措手不及。在机器学习算法等相关学科，对于数学功底要求比较高，对于快速就业让我十分的无助。由于之前也写过两年的 PHP 后台开发相关的工作，大概在很长的一段时间内一直在纠结究竟是找机器学习岗位或是后台开发方向。\n\n　　思考了很久，我对自己未来的规划是希望成为全栈工程师，在技术达到一定高度，并有很好的想法的时候会有创业做产品的打算。于是我准备往后台开发方向，但是问题又来了！\n\n　　究竟是找 Python web、PHP web、Java web，哪个方向呢？Python 和 PHP 算是自己擅长的方向，想找一份工作，固话一下相关的基础知识准没问题。当做了一段时间算法后，也对各个岗位的就业分析了一下，主要还是 Java 占据了半壁江山，其次是 C++，然后是 Python、PHP、GO。在小型公司，快速的项目迭代更多的使用了 PHP 或是 Python 进行快速开发，但当项目成长到一定的体量都被 Java 给一统江湖了。其中，特别是拼多多和小红书这两家企业，从 PHP 和 Python 替换到了 Java 后台架构。PHP/Python：do fast，Java：think big。\n\n　　出于这样一个打算，我选择了 Java ，也期待未来从事更多的分布式系统架构相关工作。在后台开发的技术栈上，尤其是 Java 和 C++ 这样的语言入门和学习的门槛太高。还没有一个特别好的学习指南能够帮助我好好的学习 Java 这样一门语言，其实在本科也学过一年多 Java，但是更多的是停留在应用层面。\n\n　　大概三月初吧，开始准备全心转向 Java 后台开发方向。得益于之前对于 PHP 的后台开发语言的学习，我的 Java 学习之路还是比较顺利和深入的。查看了很多知乎大神对于学习路径的指导，我也根据自己以往的学习经验摸索出了对于整个技术栈的认知。在学习过程中，你会看书、看视频教程、看博客等等，一开始也用 OneNote 做一些笔记，但是发现很不方便。在 Github 上看到了很多优秀的开源笔记项目，后来用到了 Typora 这 Markdown 编辑器，开始爱上了记录，于是乎创建了 [frank-lam/2019_campus_apply](https://github.com/frank-lam/2019_campus_apply) 这个仓库。此后开始了我正式的，艰苦的学习之路。（具体的技术栈我不一一展开，感兴趣的同学欢迎关注我的仓库，目前已有 800+ star 和 200+ fork）\n\n　　下面给大家列一下，后台开发工程师需要达到的一个高度吧。\n\n- 在线笔试基础\n  - Leetcode 必刷\n  - 剑指 Offer 必刷\n\n- 内功修炼\n  - 数据结构与算法\n  - 海量数据处理方法\n  - Linux 基础与命令\n  - 计算机网络（应用层，传输层，网络层等相关协议）\n  - Web 网络和 HTTP/HTTPS 协议\n  - 数据库（MySQL，Redis，SQLServer）\n  - 操作系统原理\n  - Git 版本管理工具使用\n  - 正则表达式\n\n- Java 核心技术\n\n  - 语法与基础概念\n  - 面向对象与 23 种设计模式\n  - Java 容器源码（数据结构 & 源码分析：ArrayList、Vector、LinkedList、HashMap、ConcurrentHashMap、HashSet、LinkedHashSet and LinkedHashMap）\n  - Java 并发编程（线程状态、线程机制、线程通信、J.U.C 组件、JMM、线程安全、锁优化）\n  - Java IO（磁盘操作、字节操作、字符操作、对象操作、网络操作、NIO）\n  - Java 虚拟机（运行时数据区域、垃圾收集、内存分配机制、类加载机制、性能调优监控工具）\n  - Java Web（学习 Spring + SpringMVC + MyBatis 框架和设计模式思想，学习 Servlet 和 JSP）\n\n- 高级部分（加分项，想达到 sp 水平，必须了解）\n  - Zookeeper（分布式协调服务）\n\n  - Dubbo（分布式服务治理）\n  - 分布式事务解决方案\n  - ActiveMQ、Kafka、RabbitMQ（分布式消息通信）\n  - Redis（分布式缓存与集群搭建）\n  - mycat（数据库路由）\n\n  - Nginx（反向代理）\n  - Docker（容器技术）\n\n  - Tomcat\n\n　　或许很多人看到我罗列的这些东西已经开始焦虑，并且感到不适了。是的，要知道一个道理：面试造火箭，工作拧螺丝。如果想通往大厂那么请好好静下心来，至少需要一年的时间学习来学习我所列的知识清单。也感谢校招的这段时间，逼着推动我，让我对技术的认识有了一个全新的认识。\n\n　　再回到秋招这件事，暑期实习生招聘的几个月中，参加了无数的校园宣讲会也面试了几家公司增长了一些经验，但是很不幸最后都挂了。虽然没有能成功实习，对自己的能力有了一个很好的评估，深知到自己基础知识的薄弱，于是静下心来开始了漫长夯基的日子。这里推荐很多即使不能出去实习的同学，也好好的把握好实习招聘，做一些经验上的积累。像很多公司也推出了 mini 短期实习的一些政策，或许是一个很好的机会。\n\n　　都说金九银十，是的九月、十月是秋招最重要的两个月。但是学霸们六七月份已经开始了他们的提前批内推（也称学霸批），如果你想加入阿里（7.15开始）、大疆（6.30截止）等等大厂需要早早准备了。并且内推免笔试呢，增加面试经验。\n\n　　老实说那段日子真的很难熬，印象最深刻大概还停留在六、七、八月份紧张的学习，那段时间如同考研冲刺般的激励自己，无数次在心中暗示自己我一定行的。那几个月大概对于技术的学习超过了以往任何一段时间，比以往任何一段时间更明确自己的方向，我认为超过了过去两年学习的水平。翻阅了无数本计算机相关的图书，为了快速入门看了五十多门的视频课程，其中有无数优秀的书籍和课程，这里我将在后期一起整理分享。大概七八月份两个月没有休息过，每天八点到实验室开始看书，晚上大概十一点左右打卡回去。那段时间虽然艰苦，但是对基础的学习和后来的面试起到了关键性的作用，否则我想将无法支撑我的面试。\n\n　　很快的九月来了，秋招正式的打响了。大概九月份每天都在频繁的笔试、面试、宣讲会，印象最深刻的是同一天 4 场笔试（从早晨 9 点一直笔试持续到晚上 11点才结束），记录最高的一天跑了五场面试，跑遍了华科和武大周边的五星级酒店。\n\n　　秋招还是喜出望外，拿到一些 Offer，海康威视、华为、贝贝网、苏宁易购，还有等待 Offer 的腾讯和 58 同城。虽然一心想去阿里和网易都没有很顺利，都在两轮面试后挂了（面试的太早了，都没有让我好好准备），但是最后还是拿到了华为 SP。\n\n　　转眼回头再看，每一个无眠的深夜，每一个纠结的过往，从此都云淡风轻。\n\n\n\n## 如何准备秋招\n\n- 夯实基础，把我在上文列举的知识清单掌握\n- 写一份优秀的简历\n- 包装一个你最熟悉的项目\n- 掌握好面试话术\n- 多看面经，每次面试后写下面经\n\n　　简历即是你的名片，把你会的东西都写到简历上，谨慎使用（熟悉，良好，了解）等表述性词汇，一定要对自己的简历上每个字负责。提到的技术栈，一定要有深入研究，否则切记写上。详细描述一个你最熟悉的项目，包括（1）项目背景和你的工作；（2）项目核心技术点和难点；（3）项目优化改进。\n\n　　最后在面试过程中要衣着整洁，语言吐字清晰，切记说不会，一定要思考后再回答。面试是一个交互性的交流，不要觉得低人一等，更不要被面试官牵着走，所以这里再次强调简历的重要性，大部分面试都会根据你的简历来提问。甚至我在面试中单独附上了我项目的整体架构图，有了架构图更好的对项目进行深化，也是牵引面试官的一个好办法。如果你的简历没有一个好的项目，那么请做好疯狂被怼基础知识的可能性吧！\n\n\n\n## 调整好心态\n\n　　在秋招的过程中面试了 20 家企业，也一路挂过来，一定要有一颗平和的心。广撒简历，重点培养。明确自己的岗位，工作地进行海投、精准面试。基本上面到 HR 面的企业都拿到了口头 Offer 或是意向书。\n\n　　在面试过程中，选择往往大于努力，一定要认准自己的方向。\n\n　　Offer = 40%运气 + 40%技术 + 20%表达能力\n\n\n\n## 后记\n\n　　秋招之路不易，写下一些记忆，希望能够帮助更多的人。\n\n　　更多内容请关注我的 Github：[frank-lam/2019_campus_apply: Computer Science Notes/Full Stack Developer Tutorial，后台技术栈/全栈开发/架构师之路，秋招/春招/校招/面试。 from zero to hero.](https://github.com/frank-lam/2019_campus_apply)\n"
  },
  {
    "path": "notes/archives/手把手教你，搭建内网穿透服务.md",
    "content": "## 为了更方便的使用，已创建新的仓库，请移步使用教程！\n\nfrank-lam/lanproxy-nat: 🌍 手把手教你玩转内网穿透，基于 lanproxy 穿透服务，为你定制了一键启动的服务端和客户端镜像。\n\nhttps://github.com/frank-lam/lanproxy-nat"
  },
  {
    "path": "notes/data-structures-and-algorithms/LeetCode.md",
    "content": "# LeetCode"
  },
  {
    "path": "notes/data-structures-and-algorithms/README.md",
    "content": "# 数据结构与算法\n\n> 欢迎来到数据结构与算法的世界，带你从基础数据结构层层深入。本章以 Java 代码为例，结合 LeetCode 题解示例进行数据结构学习。\n\n\n\n## 学习目录\n\n| 部分 | 章节                         | 概要                                                 |\n| :--- | :--------------------------- | :--------------------------------------------------- |\n| Ⅰ    | [数据结构](数据结构.md)      | 线性表、栈和队列、树和二叉树等经典数据结构           |\n| Ⅱ    | [算法思想](算法思想.md)      | 排序算法、动态规划、递归、回溯法、贪心算法等经典算法 |\n| Ⅲ    | [LeetCode 题解](LeetCode.md) | LeetCode 热题 HOT 100                                |\n\n\n\n## 在线刷题\n\n- 题库 - 力扣 (LeetCode) 全球极客挚爱的技术成长平台\n\n  https://leetcode-cn.com/problemset/all\n\n- 牛客网 - 找工作神器|笔试题库|面试经验|实习招聘内推，求职就业一站解决\n\n  https://www.nowcoder.com/\n\n\n\n## 在线工具\n\n- VisuAlgo - visualising data structures and algorithms through animation\n\n  https://visualgo.net/en\n\n- Data Structure Visualization\n\n  https://www.cs.usfca.edu/~galles/visualization/Algorithms.html\n\n\n\n## 学习课程\n\n| 课程                                                   | 链接                                                    | 代码                                                         | 推荐                                      |\n| ------------------------------------------------------ | ------------------------------------------------------- | ------------------------------------------------------------ | ----------------------------------------- |\n| 【慕课网】玩转数据结构 从入门到进阶（Java）            | [官网](https://coding.imooc.com/class/chapter/207.html) | [代码](https://github.com/liuyubobobo/Play‑with‑Data‑Structures) | 数据结构从底层到实现，浅显易懂            |\n| 【慕课网】算法与数据结构体系课（Java）<br/>            | [官网](https://class.imooc.com/datastructure#Anchor)    | [代码](https://github.com/frank‑lam/Algorithm)               | 《玩转数据结构 从入门到进阶》 - 升级版    |\n| 【慕课网】程序员的内功修炼，学好算法与数据结构（C++）  | [官网](https://coding.imooc.com/class/chapter/71.html)  | [代码](https://github.com/liuyubobobo/Play‑with‑Algorithms)  | 程序员的内功修炼，强烈推荐                |\n| 【慕课网】玩转算法面试 LeetCode 题库（C++）            | [官网](https://coding.imooc.com/class/chapter/82.html)  | [代码](https://github.com/liuyubobobo/Play‑with‑Algorithm‑Interview) | LeetCode 刷题入门，强烈推荐               |\n| 【极客时间】覃超：算法面试通关40讲                     | [官网](https://time.geekbang.org/course/intro/130)      | /                                                            | 市面上比较新的课程，推荐                  |\n| 【慕课网】刘宇波：看得见的算法 7个经典应用诠释算法精髓 | [官网](https://coding.imooc.com/class/chapter/138.html) | /                                                            | 通过7款经典好玩游戏，真正将算法用于实际开 |\n\n"
  },
  {
    "path": "notes/data-structures-and-algorithms/leetcode/Leetcode 307. 区域和检索 - 数组可修改.md",
    "content": "https://leetcode-cn.com/problems/range-sum-query-mutable\n\n## 307. 区域和检索 - 数组可修改\n\n给你一个数组 `nums` ，请你完成两类查询，其中一类查询要求更新数组下标对应的值，另一类查询要求返回数组中某个范围内元素的总和。\n\n实现 `NumArray` 类：\n\n- `NumArray(int[] nums)` 用整数数组 `nums` 初始化对象\n- `void update(int index, int val)` 将 `nums[index]` 的值更新为 `val`\n- `int sumRange(int left, int right)` 返回子数组 `nums[left, right]` 的总和（即，`nums[left] + nums[left + 1], ..., nums[right]`）\n\n\n\n**示例：**\n\n```\n输入：\n[\"NumArray\", \"sumRange\", \"update\", \"sumRange\"]\n[[[1, 3, 5]], [0, 2], [1, 2], [0, 2]]\n输出：\n[null, 9, null, 8]\n\n解释：\nNumArray numArray = new NumArray([1, 3, 5]);\nnumArray.sumRange(0, 2); // 返回 9 ，sum([1,3,5]) = 9\nnumArray.update(1, 2);   // nums = [1,2,5]\nnumArray.sumRange(0, 2); // 返回 8 ，sum([1,2,5]) = 8\n```\n\n\n\n**提示：**\n\n- `1 <= nums.length <= 3 * 104`\n- `-100 <= nums[i] <= 100`\n- `0 <= index < nums.length`\n- `-100 <= val <= 100`\n- `0 <= left <= right < nums.length`\n- 最多调用 `3 * 104` 次 `update` 和 `sumRange` 方法\n\n\n\n**提交代码：**\n\n```java\nclass NumArray {\n    public class SegmentTree<E> {\n\n        private E[] tree;\n        private E[] data;\n        private Merger<E> merger;\n\n        public SegmentTree(E[] arr, Merger<E> merger){\n\n            this.merger = merger;\n\n            data = (E[])new Object[arr.length];\n            for(int i = 0 ; i < arr.length ; i ++)\n                data[i] = arr[i];\n\n            tree = (E[])new Object[4 * arr.length];\n            buildSegmentTree(0, 0, arr.length - 1);\n        }\n\n        // 在treeIndex的位置创建表示区间[l...r]的线段树\n        private void buildSegmentTree(int treeIndex, int l, int r){\n\n            if(l == r){\n                tree[treeIndex] = data[l];\n                return;\n            }\n\n            int leftTreeIndex = leftChild(treeIndex);\n            int rightTreeIndex = rightChild(treeIndex);\n\n            // int mid = (l + r) / 2;\n            int mid = l + (r - l) / 2;\n            buildSegmentTree(leftTreeIndex, l, mid);\n            buildSegmentTree(rightTreeIndex, mid + 1, r);\n\n            tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);\n        }\n\n        public int getSize(){\n            return data.length;\n        }\n\n        public E get(int index){\n            if(index < 0 || index >= data.length)\n                throw new IllegalArgumentException(\"Index is illegal.\");\n            return data[index];\n        }\n\n        // 返回完全二叉树的数组表示中，一个索引所表示的元素的左孩子节点的索引\n        private int leftChild(int index){\n            return 2*index + 1;\n        }\n\n        // 返回完全二叉树的数组表示中，一个索引所表示的元素的右孩子节点的索引\n        private int rightChild(int index){\n            return 2*index + 2;\n        }\n\n        // 返回区间[queryL, queryR]的值\n        public E query(int queryL, int queryR){\n\n            if(queryL < 0 || queryL >= data.length ||\n                    queryR < 0 || queryR >= data.length || queryL > queryR)\n                throw new IllegalArgumentException(\"Index is illegal.\");\n\n            return query(0, 0, data.length - 1, queryL, queryR);\n        }\n\n        // 在以treeIndex为根的线段树中[l...r]的范围里，搜索区间[queryL...queryR]的值\n        private E query(int treeIndex, int l, int r, int queryL, int queryR){\n\n            if(l == queryL && r == queryR)\n                return tree[treeIndex];\n\n            int mid = l + (r - l) / 2;\n            // treeIndex的节点分为[l...mid]和[mid+1...r]两部分\n\n            int leftTreeIndex = leftChild(treeIndex);\n            int rightTreeIndex = rightChild(treeIndex);\n            if(queryL >= mid + 1)\n                return query(rightTreeIndex, mid + 1, r, queryL, queryR);\n            else if(queryR <= mid)\n                return query(leftTreeIndex, l, mid, queryL, queryR);\n\n            E leftResult = query(leftTreeIndex, l, mid, queryL, mid);\n            E rightResult = query(rightTreeIndex, mid + 1, r, mid + 1, queryR);\n            return merger.merge(leftResult, rightResult);\n        }\n\n        // 将index位置的值，更新为e\n        public void set(int index, E e){\n\n            if(index < 0 || index >= data.length)\n                throw new IllegalArgumentException(\"Index is illegal\");\n\n            data[index] = e;\n            set(0, 0, data.length - 1, index, e);\n        }\n\n        // 在以treeIndex为根的线段树中更新index的值为e\n        private void set(int treeIndex, int l, int r, int index, E e){\n\n            if(l == r){\n                tree[treeIndex] = e;\n                return;\n            }\n\n            int mid = l + (r - l) / 2;\n            // treeIndex的节点分为[l...mid]和[mid+1...r]两部分\n\n            int leftTreeIndex = leftChild(treeIndex);\n            int rightTreeIndex = rightChild(treeIndex);\n            if(index >= mid + 1)\n                set(rightTreeIndex, mid + 1, r, index, e);\n            else // index <= mid\n                set(leftTreeIndex, l, mid, index, e);\n\n            tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);\n        }\n\n        @Override\n        public String toString(){\n            StringBuilder res = new StringBuilder();\n            res.append('[');\n            for(int i = 0 ; i < tree.length ; i ++){\n                if(tree[i] != null)\n                    res.append(tree[i]);\n                else\n                    res.append(\"null\");\n\n                if(i != tree.length - 1)\n                    res.append(\", \");\n            }\n            res.append(']');\n            return res.toString();\n        }\n    }\n\n    public interface Merger<E> {\n        E merge(E a, E b);\n    }\n\n    private SegmentTree<Integer> segTree;\n\n    public NumArray(int[] nums) {\n\n        if(nums.length != 0){\n            Integer[] data = new Integer[nums.length];\n            for(int i = 0 ; i < nums.length ; i ++)\n                data[i] = nums[i];\n            segTree = new SegmentTree<>(data, (a, b) -> a + b);\n        }\n    }\n\n    public void update(int i, int val) {\n        if(segTree == null)\n            throw new IllegalArgumentException(\"Error\");\n        segTree.set(i, val);\n    }\n\n    public int sumRange(int i, int j) {\n        if(segTree == null)\n            throw new IllegalArgumentException(\"Error\");\n        return segTree.query(i, j);\n    }\n}\n```"
  },
  {
    "path": "notes/data-structures-and-algorithms/数据结构.md",
    "content": "# 数据结构\n\n## 一、基础数据结构\n\n### 字符串\n\n- 标准库，解析，匹配等\n\n### 线性表\n\n- 数组\t\n\n- 动态数组\n\n### 栈和队列\n\n### 链表\n\n### 哈希表\n\n\n\n## 二、高级数据结构\n\n### 1. 线段树\n\n线段树（segment tree），顾名思义， 是用来存放给定区间（segment, or interval）内对应信息的一种数据结构。与树状数组（binary indexed tree）相似，线段树也用来处理数组相应的区间查询（range query）和元素更新（update）操作。与树状数组不同的是，线段树不止可以适用于区间求和的查询，也可以进行区间最大值，区间最小值（Range Minimum/Maximum Query problem）或者区间异或值的查询。\n\n对应于树状数组，线段树进行更新（update）的操作为 O(logn)，进行区间查询（range query）的操作也为 O(logn)。\n\n![image-20210821164803357](assets/image-20210821164803357.png)\n\n在线可视化：https://visualgo.net/en/segmenttree\n\n\n\n#### 303. 区域和检索 - 数组不可变 （LeetCode）\n\n\nhttps://leetcode-cn.com/problems/range-sum-query-immutable\n\n```java\nclass NumArray {\n    private int[] data;\n\n    public NumArray(int[] nums) {\n        this.data = new int[nums.length];\n        for (int i = 0; i < nums.length; i++) {\n            this.data[i] = nums[i];\n        }\n    }\n\n    public void update(int index, int val) {\n        this.data[index] = val;\n    }\n\n    public int sumRange(int left, int right) {\n        int sum = 0;\n        for (int i = left; i <= right; i++) {\n            sum += this.data[i];\n        }\n        return sum;\n    }\n}\n```\n\n\n\n#### 307. 区域和检索 - 数组可修改 （LeetCode）\n\nhttps://leetcode-cn.com/problems/range-sum-query-mutable\n\n```java\n\nclass NumArray {\n    public class SegmentTree<E> {\n        private E[] tree;\n        private E[] data;\n        private Merger<E> merger;\n\n        public SegmentTree(E[] arr, Merger<E> merger){\n\n            this.merger = merger;\n\n            data = (E[])new Object[arr.length];\n            for(int i = 0 ; i < arr.length ; i ++)\n                data[i] = arr[i];\n\n            tree = (E[])new Object[4 * arr.length];\n            buildSegmentTree(0, 0, arr.length - 1);\n        }\n\n        // 在treeIndex的位置创建表示区间[l...r]的线段树\n        private void buildSegmentTree(int treeIndex, int l, int r){\n\n            if(l == r){\n                tree[treeIndex] = data[l];\n                return;\n            }\n\n            int leftTreeIndex = leftChild(treeIndex);\n            int rightTreeIndex = rightChild(treeIndex);\n\n            // int mid = (l + r) / 2;\n            int mid = l + (r - l) / 2;\n            buildSegmentTree(leftTreeIndex, l, mid);\n            buildSegmentTree(rightTreeIndex, mid + 1, r);\n\n            tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);\n        }\n\n        public int getSize(){\n            return data.length;\n        }\n\n        public E get(int index){\n            if(index < 0 || index >= data.length)\n                throw new IllegalArgumentException(\"Index is illegal.\");\n            return data[index];\n        }\n\n        // 返回完全二叉树的数组表示中，一个索引所表示的元素的左孩子节点的索引\n        private int leftChild(int index){\n            return 2*index + 1;\n        }\n\n        // 返回完全二叉树的数组表示中，一个索引所表示的元素的右孩子节点的索引\n        private int rightChild(int index){\n            return 2*index + 2;\n        }\n\n        // 返回区间[queryL, queryR]的值\n        public E query(int queryL, int queryR){\n\n            if(queryL < 0 || queryL >= data.length ||\n                    queryR < 0 || queryR >= data.length || queryL > queryR)\n                throw new IllegalArgumentException(\"Index is illegal.\");\n\n            return query(0, 0, data.length - 1, queryL, queryR);\n        }\n\n        // 在以treeIndex为根的线段树中[l...r]的范围里，搜索区间[queryL...queryR]的值\n        private E query(int treeIndex, int l, int r, int queryL, int queryR){\n\n            if(l == queryL && r == queryR)\n                return tree[treeIndex];\n\n            int mid = l + (r - l) / 2;\n            // treeIndex的节点分为[l...mid]和[mid+1...r]两部分\n\n            int leftTreeIndex = leftChild(treeIndex);\n            int rightTreeIndex = rightChild(treeIndex);\n            if(queryL >= mid + 1)\n                return query(rightTreeIndex, mid + 1, r, queryL, queryR);\n            else if(queryR <= mid)\n                return query(leftTreeIndex, l, mid, queryL, queryR);\n\n            E leftResult = query(leftTreeIndex, l, mid, queryL, mid);\n            E rightResult = query(rightTreeIndex, mid + 1, r, mid + 1, queryR);\n            return merger.merge(leftResult, rightResult);\n        }\n\n        // 将index位置的值，更新为e\n        public void set(int index, E e){\n\n            if(index < 0 || index >= data.length)\n                throw new IllegalArgumentException(\"Index is illegal\");\n\n            data[index] = e;\n            set(0, 0, data.length - 1, index, e);\n        }\n\n        // 在以treeIndex为根的线段树中更新index的值为e\n        private void set(int treeIndex, int l, int r, int index, E e){\n\n            if(l == r){\n                tree[treeIndex] = e;\n                return;\n            }\n\n            int mid = l + (r - l) / 2;\n            // treeIndex的节点分为[l...mid]和[mid+1...r]两部分\n\n            int leftTreeIndex = leftChild(treeIndex);\n            int rightTreeIndex = rightChild(treeIndex);\n            if(index >= mid + 1)\n                set(rightTreeIndex, mid + 1, r, index, e);\n            else // index <= mid\n                set(leftTreeIndex, l, mid, index, e);\n\n            tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);\n        }\n\n        @Override\n        public String toString(){\n            StringBuilder res = new StringBuilder();\n            res.append('[');\n            for(int i = 0 ; i < tree.length ; i ++){\n                if(tree[i] != null)\n                    res.append(tree[i]);\n                else\n                    res.append(\"null\");\n\n                if(i != tree.length - 1)\n                    res.append(\", \");\n            }\n            res.append(']');\n            return res.toString();\n        }\n    }\n\n    public interface Merger<E> {\n        E merge(E a, E b);\n    }\n\n    private SegmentTree<Integer> segTree;\n\n    public NumArray(int[] nums) {\n\n        if(nums.length != 0){\n            Integer[] data = new Integer[nums.length];\n            for(int i = 0 ; i < nums.length ; i ++)\n                data[i] = nums[i];\n            segTree = new SegmentTree<>(data, (a, b) -> a + b);\n        }\n    }\n\n    public void update(int i, int val) {\n        if(segTree == null)\n            throw new IllegalArgumentException(\"Error\");\n        segTree.set(i, val);\n    }\n\n    public int sumRange(int i, int j) {\n        if(segTree == null)\n            throw new IllegalArgumentException(\"Error\");\n        return segTree.query(i, j);\n    }\n}\n```\n\n\n\n### 2. Trie\n\nhttps://blog.csdn.net/jt102605/article/details/84258314\n\nhttps://blog.csdn.net/longgeqiaojie304/article/details/106316103\n\n发生在微软的一个真实案例：\n\n在一个古老的手持设备中实现一个通讯录功能，但是当时的手持设备的芯片运算能力是非常低的，所以他们发现当通讯录中记录的条目非常多的时候，搜索通讯录中的内容是非常慢的。但是这个问题是被微软的一个实习生解决了。其实他解决的方式非常简单，他就是使用了这种Trie数据结构来解决的。\n\n\n![image-20210821172111765](assets/image-20210821172111765.png)\n\n### 树和二叉树\n\n- 二叉树\n\n- 二分搜索树\n\n- AVL 树\n\n- B 和 B+\n\n- 二分查找法\n  - Leetcode 704.\n\n- 红黑树\n\n\n\n### 集合和映射\n\n\n\n### 优先队列和堆\n\n\n\n\n\n### Trie\n\n### 并查集\n"
  },
  {
    "path": "notes/data-structures-and-algorithms/算法思想.md",
    "content": "# 算法思想\n\n## 一、排序算法\n\n### 1. 选择排序（Selection Sort）\n\n选择出数组中的最小元素，将它与数组的第一个元素交换位置。再从剩下的元素中选择出最小的元素，将它与数组的第二个元素交换位置。不断进行这样的操作，直到将整个数组排序。 \n\n![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015224719590-1433219824.gif)\n\n**代码实现**\n\n```java\npublic static void sort(int[] arr) {\n    for (int i = 0; i < arr.length; i++) {\n        // 寻找[i, n)区间里的最小值的索引\n        int minIndex = i;\n        for (int j = i + 1; j < arr.length; j++) {\n            if(arr[minIndex] > arr[j]){\n                minIndex = j;\n            }\n        }\n        swap( arr , i , minIndex);\n    }\n}\n\nprivate static void swap(int[] arr, int i, int j) {\n    int t = arr[i];\n    arr[i] = arr[j];\n    arr[j] = t;\n}\n```\n\n**算法分析**\n\n表现最稳定的排序算法之一，因为无论什么数据进去都是O(n2)的时间复杂度，所以用到它的时候，数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。理论上讲，选择排序可能也是平时排序一般人想到的最多的排序方法了吧。\n\n \n\n### 2. 插入排序（Insertion Sort）\n\n插入排序从左到右进行，每次都将当前元素插入到左侧已经排序的数组中，使得插入之后左部数组依然有序。\n\n第 j 元素是通过不断向左比较并交换来实现插入过程：当第 j 元素小于第 j - 1 元素，就将它们的位置交换，然后令 j 指针向左移动一个位置，不断进行以上操作。\n\n![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015225645277-1151100000.gif)\n\n**代码实现**\n\n```java\npublic static void sort(int[] arr) {\n    for (int i = 0; i < arr.length; i++) {\n        // 选择 arr[i...n) 中的最小值\n        int minIndex = i;\n        for (int j = i + 1; j < arr.length; j++) {\n            if (arr[minIndex] > arr[j]) {\n                minIndex = j;\n            }\n        }\n        swap(arr, i, minIndex);\n    }\n}\n\n// 改进版插入排序（减少了数组元素的操作次数）\npublic static void better_sort(int[] arr) {\n    for (int i = 0; i < arr.length; i++) {\n        int e = arr[i];\n        int j = i;\n        for (; j > 0; j--) {\n            if (e < arr[j - 1])\n                arr[j] = arr[j - 1];\n            else\n                break;\n        }\n        arr[j] = e;\n    }\n}\n\nprivate static void swap(int[] arr, int i, int j) {\n    int t = arr[i];\n    arr[i] = arr[j];\n    arr[j] = t;\n}\n```\n\n**算法分析**\n\n插入排序在实现上，通常采用 in-place 排序（即只需用到 O(1) 的额外空间的排序），因而在从后向前扫描过程中，需要反复把已排序元素逐步向后挪位，为最新元素提供插入空间。\n\n \n\n### 3. 冒泡排序（Bubble Sort）\n\n通过从左到右不断交换相邻逆序的相邻元素，在一轮的交换之后，可以让未排序的元素上浮到右侧。\n\n在一轮循环中，如果没有发生交换，就说明数组已经是有序的，此时可以直接退出。\n\n![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015223238449-2146169197.gif)\n\n**代码实现**\n\n```java\nprivate static void sort(int[] arr) {\n    for (int i = arr.length - 1; i > 0; i--) { // 从最后一位开始确定\n        boolean swapped = false;\n        for (int j = 0; j < i; j++) {\n            if(arr[j] > arr[j+1]){\n                swapped = true;\n                swap(arr,j,j+1);\n            }\n        }\n        if(!swapped)\n            return;\n    }\n}\n\nprivate static void swap(int[] arr, int i, int j) {\n    int t = arr[i];\n    arr[i] = arr[j];\n    arr[j] = t;\n}\n```\n\n\n\n### 4. 希尔排序（Shell Sort）\n\n1959年Shell发明，第一个突破O(n2)的排序算法，是简单插入排序的改进版。它与插入排序的不同之处在于，它会优先比较距离较远的元素。希尔排序又叫**缩小增量排序**。\n\n\n\n**算法描述**\n\n先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序，具体算法描述：\n\n- 选择一个增量序列t1，t2，…，tk，其中ti>tj，tk=1；\n- 按增量序列个数k，对序列进行k 趟排序；\n- 每趟排序，根据对应的增量ti，将待排序列分割成若干长度为m 的子序列，分别对各子表进行直接插入排序。仅增量因子为1 时，整个序列作为一个表来处理，表长度即为整个序列的长度。\n\n![](https://images2018.cnblogs.com/blog/849589/201803/849589-20180331170017421-364506073.gif)\n\n**代码实现**\n\n```java\n// 希尔排序\npublic static void sort(int[] arr) {\n    int n = arr.length;\n    for (int h = n / 2; h > 0; h = h / 2) {\n        // 内部是一个插入排序\n        for (int i = 0; i < n; i = i + h) {\n\n            int e = arr[i];\n            int j = i;\n            for (; j > 0; j = j - h) {\n                if (e < arr[j - h])\n                    arr[j] = arr[j - h];\n                else\n                    break;\n            }\n            arr[j] = e;\n        }\n    }\n}\n\n\n// 希尔排序2\npublic static void sort2(int[] arr) {\n    int n = arr.length;\n    // 计算 increment sequence: 1, 4, 13, 40, 121, 364, 1093...\n    int h = 1;\n    while (h < n / 3) h = 3 * h + 1;\n\n    System.out.println(h);\n\n    while (h >= 1) {\n        // h-sort the array\n        for (int i = h; i < n; i++) {\n            \n            // 对 arr[i], arr[i-h], arr[i-2*h], arr[i-3*h]... 使用插入排序\n            int e = arr[i];\n            int j = i;\n            for (; j >= h && e < arr[j - h]; j -= h)\n                arr[j] = arr[j - h];\n            arr[j] = e;\n        }\n\n        h /= 3;\n    }\n}\n```\n\n**算法分析**\n\n对于大规模的数组，插入排序很慢，因为它只能交换相邻的元素，每次只能将逆序数量减少 1。\n\n希尔排序的出现就是为了改进插入排序的这种局限性，它通过交换不相邻的元素，每次可以将逆序数量减少大于 1。\n\n希尔排序使用插入排序对间隔 h 的序列进行排序。通过不断减小 h，最后令 h=1，就可以使得整个数组是有序的。\n\n\n\n### 5. 归并排序（Merge Sort）\n\n归并排序的思想是将数组分成两部分，分别进行排序，然后归并起来。把长度为n的输入序列分成两个长度为n/2的子序列；对这两个子序列分别采用归并排序；将两个排序好的子序列合并成一个最终的排序序列。\n\n![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015230557043-37375010.gif)\n\n**代码实现**\n\n> 1.归并方法\n>\n> 归并方法将数组中两个已经排序的部分归并成一个。\n\n```java\nprivate static void sort(int[] arr) {\n    __MergeSort(arr, 0, arr.length - 1);\n}\n\nprivate static void __MergeSort(int[] arr, int l, int r) {\n    if (l >= r)\n        return;\n    int mid = (l + r) / 2;\n    __MergeSort(arr, l, mid);\n    __MergeSort(arr, mid + 1, r);\n    merge(arr, l, mid, r);\n}\n\n// 将arr[l...mid]和arr[mid+1...r]两部分进行归并\nprivate static void merge(int[] arr, int l, int mid, int r) {\n    int[] aux = Arrays.copyOfRange(arr, l, r + 1);\n\n    // 初始化，i指向左半部分的起始索引位置l；j指向右半部分起始索引位置mid+1\n    int i = l, j = mid + 1;\n    for (int k = l; k <= r; k++) {\n        if (i > mid) {  // 如果左半部分元素已经全部处理完毕\n            arr[k] = aux[j - l];\n            j++;\n        } else if (j > r) {   // 如果右半部分元素已经全部处理完毕\n            arr[k] = aux[i - l];\n            i++;\n        } else if (aux[i - l] < aux[j - l]) {  // 左半部分所指元素 < 右半部分所指元素\n            arr[k] = aux[i - l];\n            i++;\n        } else {  // 左半部分所指元素 >= 右半部分所指元素\n            arr[k] = aux[j - l];\n            j++;\n        }\n    }\n}\n```\n\n> 2.自底向上归并排序\n\n```java\nprivate static void sort(int[] arr) {\n    int N = arr.length;\n    int[] aux = new int[N];\n    for (int sz = 1; sz < N; sz += sz)\n        for (int i = 0; i + sz < N; i += sz + sz)\n            merge(arr, i, i + sz - 1, Math.min(i + sz + sz - 1, N - 1));\n}\n```\n\n\n\n### 6. 快速排序（Quick Sort）\n\n快速排序可以说是20世纪最伟大的算法之一了。相信都有所耳闻，它的速度也正如它的名字那样，是一个非常快的算法了。当然它也后期经过了不断的改进和优化，才被公认为是一个值得信任的非常优秀的算法。\n\n![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015230936371-1413523412.gif)\n\n**代码实现**\n\n#### 1. 普通快速排序\n\n```java\n// 递归使用快速排序,对arr[l...r]的范围进行排序\npublic static void QuickSort(int[] arr,int l,int r){\n    if(l>=r)\n        return;\n    int p = partition(arr,l,r);\n    QuickSort(arr,l,p-1);\n    QuickSort(arr,p+1,r);\n}\n\n// 将数组通过p分割成两部分\n// 对arr[l...r]部分进行partition操作\n// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]\npublic static int partition(int[] arr, int l, int r) {\n    swap(arr, l, (int) (Math.random() % (r - l + 1)) + l);  // 加入这一行变成随机快速排序\n\n    int v = arr[l];\n    int j = l;\n    for(int i = j +1;i<=r;i++){\n        if(arr[i] < v){\n            j++;\n            swap(arr,i,j);\n        }\n    }\n    swap(arr,l,j);\n    return j;\n}\n\npublic static void swap(int[] arr,int i,int j) {\n    int temp = arr[i];\n    arr[i] = arr[j];\n    arr[j] = temp;\n}\n```\n\n快速排序是原地排序，不需要辅助数组，但是递归调用需要辅助栈。\n\n快速排序最好的情况下是每次都正好能将数组对半分，这样递归调用次数才是最少的。这种情况下比较次数为 CN=2CN/2+N，复杂度为 O(NlogN)。\n\n最坏的情况下，第一次从最小的元素切分，第二次从第二小的元素切分，如此这般。因此最坏的情况下需要比较 N2/2。为了防止数组最开始就是有序的，在进行快速排序时需要随机打乱数组。\n\n\n\n#### 2. 双路快速排序\n\n若果数组中含有大量重复的元素，则partition很可能把数组划分成两个及其不平衡的两部分，时间复杂度退化成O(n²)。这时候应该把小于v和大于v放在数组两端。\n\n![](../pics/partition2.jpg)\n\n```java\n// 双路快速排序的partition\n// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]\nprivate static int partition(int[] arr, int l, int r) {\n\n    // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot\n    // swap(arr, l, (int) (Math.random() % (r - l + 1)) + l);\n\n    int v = arr[l];\n\n    // arr[l+1...i) <= v; arr(j...r] >= v\n    int i = l + 1, j = r;\n    while (true) {\n        // 注意这里的边界, arr[i].compareTo(v) < 0, 不能是arr[i].compareTo(v) <= 0\n        // 思考一下为什么?\n        while (i <= r && arr[i] < v)\n            i++;\n\n        // 注意这里的边界, arr[j].compareTo(v) > 0, 不能是arr[j].compareTo(v) >= 0\n        // 思考一下为什么?\n        while (j >= l + 1 && arr[j] > v)\n            j--;\n\n        // 对于上面的两个边界的设定, 有的同学在课程的问答区有很好的回答:)\n        // 大家可以参考: http://coding.imooc.com/learn/questiondetail/4920.html\n        if (i > j)\n            break;\n        \n        swap(arr, i, j);\n        i++;\n        j--;\n    }\n\n    swap(arr, l, j);\n\n    return j;\n}\n\n// 递归使用快速排序,对arr[l...r]的范围进行排序\nprivate static void QuickSort2Ways(int[] arr, int l, int r) {\n    // 对于小规模数组, 使用插入排序\n    if (l >= r) return;\n    int p = partition(arr, l, r);\n    QuickSort2Ways(arr, l, p - 1);\n    QuickSort2Ways(arr, p + 1, r);\n}\n```\n\n#### 3. 三路快速排序\n\n数组分成三个部分，大于v 等于v 小于v\n\n在具有大量重复键值对的情况下使用三路快排\n\n![](../pics/partition3.jpg)\n\n\n\n```java\n// 递归使用快速排序,对arr[l...r]的范围进行排序\nprivate static void QuickSort3Ways(int[] arr, int l, int r){\n\n    // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot\n    swap( arr, l, (int)(Math.random()*(r-l+1)) + l );\n\n    int v = arr[l];\n\n    int lt = l;     // arr[l+1...lt] < v\n    int gt = r + 1; // arr[gt...r] > v\n    int i = l+1;    // arr[lt+1...i) == v\n    while( i < gt ){\n        if( arr[i] < v){\n            swap( arr, i, lt+1);\n            i ++;\n            lt ++;\n        }\n        else if( arr[i] > v ){\n            swap( arr, i, gt-1);\n            gt --;\n        }\n        else{ // arr[i] == v\n            i ++;\n        }\n    }\n    swap( arr, l, lt );\n\n    QuickSort3Ways(arr, l, lt-1);\n    QuickSort3Ways(arr, gt, r);\n}\n```\n\n\n\n### 7. 堆排序（Heap Sort）\n\n#### 1. 堆\n\n堆的某个节点的值总是大于等于子节点的值，并且堆是一颗完全二叉树。\n\n堆可以用数组来表示，因为堆是完全二叉树，而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2，而它的两个子节点的位置分别为 2k 和 2k+1。这里不使用数组索引为 0 的位置，是为了更清晰地描述节点的位置关系。\n\n![](../pics/heap.png)\n\n#### 2. 上浮和下沉\n\n在堆中，当一个节点比父节点大，那么需要交换这个两个节点。交换后还可能比它新的父节点大，因此需要不断地进行比较和交换操作，把这种操作称为**上浮（ShiftUp）**。\n\n![](../pics/shiftup_heap.png) \n\n```java\nprivate void shiftUp(int k){\n    while( k > 1 && data[k/2] < data[k])){\n        swap(k, k/2);\n        k /= 2;\n    }\n}\n```\n\n\n\n类似地，当一个节点比子节点来得小，也需要不断地向下进行比较和交换操作，把这种操作称为**下沉（Shift Down）**。一个节点如果有两个子节点，应当与两个子节点中最大那么节点进行交换。\n\n![](../pics/shiftdown_heap.png) \n\n```java\nprivate void shiftDown(int k){\n    while( 2*k <= count ){ // 当前结点有左孩子\n        int j = 2*k; // 在此轮循环中,data[k]和data[j]交换位置\n        if( j+1 <= count && data[j+1] > data[j] )\n            j ++;\n        // data[j] 是 data[2*k]和data[2*k+1]中的最大值\n\n        if( data[k] >= data[j] ) \n            break;\n        swap(k, j);\n        k = j;\n    }\n}\n```\n\n#### 3.插入元素\n\n将新元素放到数组末尾，然后上浮到合适的位置。  \n\n```java\n// 向最大堆中插入一个新的元素 item\npublic void insert(Item item){\n    assert count + 1 <= capacity;\n    data[count+1] = item;\n    count ++;\n    shiftUp(count);\n}\n```\n\n#### 4. 删除最大元素\n\n```java\n// 从最大堆中取出堆顶元素, 即堆中所存储的最大数据\npublic Item extractMax(){\n    assert count > 0;\n    Item ret = data[1];\n    \n    swap( 1 , count );\n    count --;\n    shiftDown(1);\n    return ret;\n}\n```\n\n#### 5. 堆排序\n\n由于堆可以很容易得到最大的元素并删除它，不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置，并且不删除它，那么就可以得到一个从尾到头的递减序列，从正向来看就是一个递增序列。因此很容易使用堆来进行排序。并且堆排序是原地排序，不占用额外空间。\n\n```java\n// 不使用一个额外的最大堆, 直接在原数组上进行原地的堆排序\npublic class HeapSort {\n\n    // 对整个arr数组使用HeapSort1排序\n    // HeapSort1, 将所有的元素依次添加到堆中, 在将所有元素从堆中依次取出来, 即完成了排序\n    // 无论是创建堆的过程, 还是从堆中依次取出元素的过程, 时间复杂度均为O(nlogn)\n    // 整个堆排序的整体时间复杂度为O(nlogn)\n    public static void sort1(Comparable[] arr){\n\n        int n = arr.length;\n        MaxHeap<Comparable> maxHeap = new MaxHeap<Comparable>(n);\n        for( int i = 0 ; i < n ; i ++ )\n            maxHeap.insert(arr[i]);\n\n        for( int i = n-1 ; i >= 0 ; i -- )\n            arr[i] = maxHeap.extractMax();\n    }\n\n\n    // 只通过shiftDown操作进行排序\n    public static void sort2(Comparable[] arr){\n        int n = arr.length;\n\n        // 注意，此时我们的堆是从0开始索引的\n        // 从(最后一个元素的索引-1)/2开始\n        // 最后一个元素的索引 = n-1\n        for( int i = (n-1-1)/2 ; i >= 0 ; i -- )\n            shiftDown2(arr, n, i);\n\n        for( int i = n-1; i > 0 ; i-- ){ // 这个的目的是让序列从小到大排序\n            swap( arr, 0, i);\n            shiftDown2(arr, i, 0);\n        }\n    }\n\n    // 交换堆中索引为i和j的两个元素\n    private static void swap(Object[] arr, int i, int j){\n        Object t = arr[i];\n        arr[i] = arr[j];\n        arr[j] = t;\n    }\n\n    // 原始的shiftDown过程\n    private static void shiftDown(Comparable[] arr, int n, int k){\n        while( 2*k+1 < n ){\n            int j = 2*k+1;\n            if( j+1 < n && arr[j+1].compareTo(arr[j]) > 0 )\n                j += 1;\n\n            if( arr[k].compareTo(arr[j]) >= 0 )break;\n\n            swap( arr, k, j);\n            k = j;\n        }\n    }\n\n    // 优化的shiftDown过程, 使用赋值的方式取代不断的swap,\n    // 该优化思想和我们之前对插入排序进行优化的思路是一致的\n    private static void shiftDown2(Comparable[] arr, int n, int k){\n\n        Comparable e = arr[k];\n        while( 2*k+1 < n ){\n            int j = 2*k+1;\n            if( j+1 < n && arr[j+1].compareTo(arr[j]) > 0 )\n                j += 1;\n\n            if( e.compareTo(arr[j]) >= 0 )\n                break;\n\n            arr[k] = arr[j];\n            k = j;\n        }\n\n        arr[k] = e;\n    }\n\n    // 测试 HeapSort\n    public static void main(String[] args) {\n        Integer[] arr = {10, 91, 8, 7, 6, 5, 4, 3, 2, 1};\n        HeapSort.sort2(arr);\n        PrintHelper.printArray(arr);\n    }\n}\n```\n\n#### 6. 堆排序的应用——Top K问题\n\n例如，有1亿个浮点数，如何找出其中最大的10000个？（B326）\n\n\n\n### 8. 计数排序\n\nhttps://www.cnblogs.com/freedom314/p/5847092.html\n\n\n\n### 9. 排序算法总结\n\n|          | 平均时间复杂度 | 原地排序 | 额外空间 | 稳定排序 |\n| :------: | :------------: | :------: | :------: | :------: |\n| 插入排序 |     O(n^2)     |    √     |   O(1)   |    √     |\n| 归并排序 |    O(nlogn)    |    ×     |   O(n)   |    √     |\n| 快速排序 |    O(nlogn)    |    √     | O(logn)  |    ×     |\n|  堆排序  |    O(nlogn)    |    √     |   O(1)   |    ×     |\n\n稳定排序：对于相等的元素，在排序后，原来靠前的元素依然靠前。相等元素的相对位置没有发生变化。\n\n```java\n// 可以通过⾃自定义⽐比较函数，让排序算法不不存在稳定性的问题。\nbool operator<(const Student& otherStudent){\n    return score != otherStudent.score ?\n    score > otherStudent.score :\n    name < otherStudent.name;\n}\n```\n\n\n\n\n\n\n\n![](../pics/sort_algorithm_analyze.png) \n\n\n\n## 二、二分查找法\n\nLeetcode 704.\n\n\n\n\n\n## 三、递归和回溯法\n\n- Leetcode 70.\n\n- Leetcode 17.\n\n\n\n## 四、分治的思想\n\n\n\n\n\n## 五、动态规划\n\n- 9-1 什么是动态规划 (20:27)\n\n- 9-2 第一个动态规划问题 Climbing Stairs (14:02)\n\n  - [70. 爬楼梯](https://leetcode-cn.com/problems/climbing-stairs/)\n\n  - 【作业】[120. 三角形最小路径和](https://leetcode-cn.com/problems/triangle/)\n\n  - 【作业】[64. 最小路径和](https://leetcode-cn.com/problems/minimum-path-sum/)\n\n- 9-3 发现重叠子问题 Integer Break (25:10)\n\n  - [343. 整数拆分](https://leetcode-cn.com/problems/integer-break/)\n\n  - 【作业】279\n\n  - 【作业】91\n\n  - 【作业】62\n\n  - 【作业】63\n\n- 9-4 状态的定义和状态转移 House Robber (27:19)\n\n  - [198. 打家劫舍](https://leetcode-cn.com/problems/house-robber/)\n\n  - 【作业】213\n\n  - 【作业】337\n\n  - 【作业】309\n\n- 9-5 0-1背包问题 (32:37)\n\n- 9-6 0-1背包问题的优化和变种 (20:18)\n\n- 9-7 面试中的0-1背包问题 Partition Equal Subset Sum (27:34)\n\n- 9-8 LIS问题 Longest Increasing Subsequence (25:12)\n\n  - [300. 最长递增子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence/)\n\n  - 【作业】[376. 摆动序列](https://leetcode-cn.com/problems/wiggle-subsequence/)\n\n- 9-9 LCS，最短路，求动态规划的具体解以及更多 (21:00)\n  - [1143. 最长公共子序列](https://leetcode-cn.com/problems/longest-common-subsequence/)\n\n- 9-10 动态规划的经典问题\n\n- https://www.cxyzjd.com/article/qq_42874319/115740445\n\n- \n\n## 六、贪心算法 08/10\n\n- 10-1 贪心基础 Assign Cookies (12:12)\n\n  - [455. 分发饼干](https://leetcode-cn.com/problems/assign-cookies/)\n\n  - 【作业】[392. 判断子序列](https://leetcode-cn.com/problems/is-subsequence/)\n\n- 10-2 贪心算法与动态规划的关系 Non-overlapping Intervals (17:58)\n  - [435. 无重叠区间](https://leetcode-cn.com/problems/non-overlapping-intervals/)\n\n- 10-3 贪心选择性质的证明 (15:19)\n\n\n\n## 七、模式匹配\n\n- KMP \n\n- RK \n\n\n\n## 八、搜索算法（深搜，广搜等）\n\n\n\n## 九、并查集\n"
  },
  {
    "path": "notes/docs/如何给我的仓库贡献.md",
    "content": "▲ [GO HOME](https://github.com/frank-lam/2019_campus_apply)\n\n\n\n# 前言\n\n本开源项目还在萌芽起步阶段，在编写的过程中难免遇到错误和不足，非常欢迎大家能够给出自己的意见，成为这个开源项目的贡献者。\n\n这里有两种方式可以对项目进行指正和贡献。\n\n- **issue**：如果内容上有错误和不足，可以直接在 issue 中提出来（关于 issue 可在网页上直接操作，这里就不说明）\n\n- **contribution**：当然如果你有更好的想法，可以直接贡献你的 contribution（下文将教你如何进行开源贡献）\n\n\n\n# 开始你第一个开源贡献\n\n## 1. Fork 一个仓库\n\n通过点击页面右侧的 Fork 按钮来 Fork 一个仓库. 这将会复制一个仓库到你的账号中去。\n\n<div align=\"center\"><img src=\"assets/t011055b630bbc39de1.png\" width=\"550\"/></div>\n\n\n## 2. 克隆仓库\n\n现在我们克隆这个项目到本地. 点击克隆按钮然后点击 *copy to clipboard* 图标.\n\n<div align=\"center\"><img src=\"assets/t01e7b7ddbc94f7e570.png\" width=\"550\"/></div>\n\n\n\n```shell\ngit clone \"url you just copied\"\n```\n\n\n\n这里的 “url you just copied” (命令行中不需要冒号) 是仓库地址 . 填入在上一步获得到的地址。\n\n例如:\n\n```shell\ngit clone https://github.com/this-is-you/first-contributions.git\n```\n\n这里的 `this-is-you` 是你的GitHub账户. 复制你first-contributions 这个项目的内容到你的电脑。\n\n\n\n## 3. 创建一个分支\n\n在你的电脑中改变一个仓库文件夹 (如果你还没到这一步):\n\n```shell\ncd first-contributions\n```\n\n创建一个分支使用 `git checkout` 命令:\n\n```shell\ngit checkout -b <add-your-name>\n```\n\n例如:\n\n```shell\ngit checkout -b add-alonzo-church\n```\n\n(分支的名称不需要 *add* 这个词, 但有一种情况可以加上，就是把你的名字加上去。)\n\n\n\n## 4. 做一些改动然后递交\n\n用编辑器打开 `Contributors.md` 这个文件, 把你的名字加进去然后保存.在这个目录下执行 `git status`, 你就会发现已经有变化了. 把改变的文件加入你的分支只需要执行 `git add`命令:\n\n```shell\ngit add Contributors.md\n```\n\n递交刚才的文件执行 `git commit` 命令:\n\n```shell\ngit commit -m \"Add <your-name> to Contributors list\"\n```\n\n替换 ``成你的名字\n\n5. 推送变动内容到GitHub\n\n推送变动执行 `git push`:\n\n```shell\ngit push origin <add-your-name>\n```\n\n替换成你之前创建的分支名称\n\n\n\n## 6. 查看你提交的变动\n\n进入GitHub项目主页, 你会看到一个 `Compare & pull request` 按钮，点击这个按钮.\n\n<div align=\"center\"><img src=\"assets/t019f9c483eb548ed76.png\" width=\"\"/></div>\n\n现在提交你的递交请求.\n\n<div align=\"center\"><img src=\"assets/t014ed3595e40678bc9.png\" width=\"\"/></div>\n\n很快我就会把所有的变动合并到这个项目的主分支，一旦变动合并，你将会受到一个通知邮件。 你项目的主分支将不会有变动，是为了和我的项目保持同步\n\n\n\n## 7. 保持你的fork和仓库同步\n\n首先，切换主分支\n\n```shell\ngit checkout master\n```\n\n然后加上仓库的地址 `upstream remote url`:\n\n```shell\ngit remote add upstream https://github.com/Roshanjossey/first-contributions\n```\n\nThis is a way of telling git that another version of this project exists in the specified url and we’re calling it `upstream`. Once the changes are merged, fetch the new version of my repository:\n\n```shell\ngit fetch upstream\n```\n\nHere we’re fetching all the changes in my fork (upstream remote). Now, you need to merge the new revision of my repository into your master branch.\n\n```shell\ngit rebase upstream/master\n```\n\nHere you’re applying all the changes you fetched to master branch. If you push the master branch now, your fork will also have the changes:\n\n```shell\ngit push origin master\n```\n\nNotice here you’re pushing to the remote named origin.\n\nAt this point I have merged your branch ``into my master branch, and you have merged my master branch into your own master branch. Your branch is now no longer needed, so you may delete it:\n\n```shell\ngit branch -d <add-your-name>\n```\n\n同样你可以删除你远程仓库\n\n```shell\ngit push origin --delete <add-your-name>\n```\n\n这不是必须的, but the name of this branch shows its rather special purpose. Its life can be made correspondingly short.\n\n\n\n\n引用：\n\n- [一步一步教你完成Github的第一个Contribution](https://www.zcfy.cc/article/a-step-by-step-guide-to-making-your-first-github-contribution-4142.html?t=new)\n- [git如何与原始仓库同步 - CSDN博客](https://blog.csdn.net/libing403/article/details/51729744)"
  },
  {
    "path": "notes/docsify/about/author.md",
    "content": "# 联系作者\n\n<div align=\"center\">  \n    <p>\n        在颠覆世界的同时，也要好好关照自己。\n    </p>\n<a  target=\"_blank\" href=\"https://zhuanlan.zhihu.com/frankfeekr\" rel=\"nofollow\"><img src=\"https://img.shields.io/badge/知乎专栏-frankfeekr-blue.svg\" alt=\"QQ群\" data-canonical-src=\"\" style=\"max-width:100%;\"></a>\n<a target=\"_blank\" href=\"http://blog.csdn.net/u012104219\" rel=\"nofollow\"><img src=\"https://img.shields.io/badge/CSDN-东风牧野-red.svg\" alt=\"CSDN\" data-canonical-src=\"\" style=\"max-width:100%;\"></a>\n<a target=\"_blank\" href=\"mailto:frank_lin@whu.edu.cn\" rel=\"nofollow\"><img src=\"https://img.shields.io/badge/Email-frank__lin@whu.edu.cn-lightgrey.svg\" alt=\"邮箱\" data-canonical-src=\"\" style=\"max-width:100%;\"></a>\n<a target=\"_blank\" href=\"https://jq.qq.com/?_wv=1027&k=593WvX0\" rel=\"nofollow\" ><img src=\"https://img.shields.io/badge/QQ群-862619503-green.svg\" alt=\"QQ群\" data-canonical-src=\"\" style=\"max-width:100%;\"></a>\n    <br/><br/>\n    <p>\n        from zero to hero.\n    </p>\n</div>\n<div align=\"center\"><img src=\"https://raw.githubusercontent.com/frank-lam/fullstack-tutorial/master/assets/wechat-fullstack.png\" width=\"620\"/></div><br/>"
  },
  {
    "path": "notes/docsify/about/donate.md",
    "content": "# 打赏一下\n\n<div align=\"center\">\n<p>\n如果你觉得这个项目帮助到了你，你可以帮作者买一杯果汁🍹表示鼓励\n</p>\n</div><br/>\n\n![donate](https://raw.githubusercontent.com/frank-lam/fullstack-tutorial/master/assets/tipping.jpg)\n"
  },
  {
    "path": "notes/docsify/coverpage.md",
    "content": "# fullstack tutorial\n\n> 全栈开发指南，架构师成长之路\n\n<!-- - 后台技术栈/全栈开发/架构师之路，秋招/春招/校招/面试 -->\n- 操作系统、计算机网络、数据库与算法\n- Java技术栈、微服务、分布式系统架构\n- Docker、Git工作流、正则表达式\n- from zero to hero\n\n<span id=\"la_19815069\"></span>\n\n[Get Started](introduction)\n[GitHub](https://github.com/frank-lam/fullstack-tutorial)\n"
  },
  {
    "path": "notes/docsify/css/main.css",
    "content": ".markdown-section iframe[src*=\"buttons.github.io\"] {\n    margin: 0;\n}\n\nfigure.thumbnails img {\n    margin: 0.75em 0;\n    border-radius: 3px;\n    box-shadow: 0 2px 6px rgba(0,0,0,0.1), 0 4px 12px rgba(0,0,0,0.15);\n}\n\n@media (min-width: 30em) {\n    figure.thumbnails:after {\n        content: \"\";\n        display: table;\n        clear: both;\n    }\n\n    figure.thumbnails img {\n        float: left;\n        width: calc(50% - 0.75em);\n    }\n\n    figure.thumbnails img:nth-child(even) {\n        margin-left: 1.5em;\n    }\n\n    @supports (display: flex) {\n        figure.thumbnails {\n            display: flex;\n            align-items: center;\n        }\n\n        figure.thumbnails img {\n            flex-grow: 1;\n            width: 0;\n        }\n\n        figure.thumbnails img + img {\n            margin: 0 0 0 1.5em;\n        }\n    }\n}\n"
  },
  {
    "path": "notes/docsify/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, minimum-scale=1.0, shrink-to-fit=no, viewport-fit=cover\">\n    <meta name=\"description\" content=\"A delightfully simple theme system for docsify.js. Features multiple themes with rich customization options, an improved desktop and mobile experience, and legacy browser support (IE10+).\">\n    <meta name=\"google-site-verification\" content=\"_EWJaM8G6xwMtEqemO4yN8WqSU2CaKVi3jTj0gLg48E\">\n    <title>fullstack tutorial - 全栈开发指南</title>\n\n    <!-- Stylesheets -->\n    <link rel=\"stylesheet\" href=\"https://unpkg.com/docsify-themeable/dist/css/theme-simple.css\" title=\"Simple\">\n    <link rel=\"stylesheet\" href=\"docsify/css/main.css\">\n\n    <!-- Alternate Stylesheets -->\n    <link rel=\"stylesheet alternate\" href=\"https://unpkg.com/docsify-themeable/dist/css/theme-defaults.css\" title=\"Defaults\">\n    <link rel=\"stylesheet alternate\" href=\"https://unpkg.com/docsify-themeable/dist/css/theme-simple-dark.css\" title=\"Simple Dark\">\n\n    <!-- Test Stylesheets -->\n    <!-- <link rel=\"stylesheet\" href=\"//unpkg.com/docsify/themes/vue.css\"> -->\n    <!-- <link rel=\"stylesheet\" href=\"//unpkg.com/docsify/themes/buble.css\"> -->\n    <!-- <link rel=\"stylesheet\" href=\"//unpkg.com/docsify/themes/dark.css\"> -->\n    <!-- <link rel=\"stylesheet\" href=\"//unpkg.com/docsify/themes/pure.css\"> -->\n    <style>\n        .sidebar-nav a,\n        .sidebar nav a {\n            white-space: unset !important;\n        }\n\n        .sidebar-nav ul li ul {\n            font-size: 14px;\n        }\n\n        .sidebar-nav ul li strong {\n            font-size: 17px;\n        }\n\n        h1#fullstack-tutorial span {\n            color: hsl(204, 90%, 45%);\n        }\n\n        article#main a {\n            color: hsl(204, 90%, 45%);\n        }\n        /* 隐藏 Github TOC 目录 */\n        .markdown-section :first-child li{\n            display: none;\n        }\n    </style>\n</head>\n\n<body>\n    <div id=\"app\"></div>\n\n    <!-- JavaScript -->\n    <!-- <script>\n        window.$docsify = {\n            loadNavbar: true,\n            loadNavbar: 'docsify/navbar.md',\n            // basePath: '/docs/notes',\n            // GENERAL\n            // -----------------------------------------------------------------\n            name: 'fullstack tutorial',\n            repo: 'https://github.com/frank-lam/fullstack-tutorial',\n            coverpage: 'docsify/coverpage.md',\n            homepage: 'introduction.md',\n            loadSidebar: 'docsify/sidebar.md',\n\n            // NAVIGATION\n            // -----------------------------------------------------------------\n            alias: {\n                '.*?/changelog': 'https://unpkg.com/docsify-themeable/CHANGELOG.md',\n            },\n            auto2top: true,\n            maxLevel: 3,\n            subMaxLevel: 2,\n\n            // PLUGINS\n            // -----------------------------------------------------------------\n            executeScript: true,\n            ga: 'UA-100100910-3',\n            search: {\n                depth: 3,\n                noData: 'No results!',\n                placeholder: 'Search...'\n            }\n        };\n    </script> -->\n    <p class=\"page_view\">\n        <script type=\"text/javascript\" src=\"//quote.51.la/q?id=19815069&mb=4\"></script>\n    </p>\n    \n    <div id=\"app\"></div>\n\n    <script src=\"docsify/js/main.js\"></script>\n    <!-- <script src=\"//unpkg.com/docsify/lib/plugins/emoji.js\"></script> -->\n    <script src=\"https://unpkg.com/docsify\"></script>\n    <script src=\"https://unpkg.com/docsify-themeable\"></script>\n    <script src=\"https://unpkg.com/docsify-tabs\"></script>\n    <script src=\"https://unpkg.com/docsify-copy-code\"></script>\n    <script src=\"https://unpkg.com/docsify-pagination/dist/docsify-pagination.min.js\"></script>\n    <script src=\"https://unpkg.com/docsify/lib/plugins/external-script.min.js\"></script>\n    <script src=\"https://unpkg.com/docsify/lib/plugins/ga.min.js\"></script>\n    <script src=\"https://unpkg.com/docsify/lib/plugins/search.js\"></script>\n    <script src=\"https://unpkg.com/docsify/lib/plugins/zoom-image.min.js\"></script>\n    <script src=\"https://unpkg.com/prismjs/components/prism-bash.min.js\"></script>\n\n    <link rel=\"stylesheet\" href=\"//unpkg.com/gitalk/dist/gitalk.css\">\n\n    <script src=\"//unpkg.com/docsify/lib/plugins/gitalk.min.js\"></script>\n    <script src=\"//unpkg.com/gitalk/dist/gitalk.min.js\"></script>\n    <script>\n        const gitalk = new Gitalk({\n            clientID: '67ba8c5b2ac4f27e47e1',\n            clientSecret: 'bea349f1e21ab64348ddb365901b3f6daf9f0697',\n            repo: 'fullstack-tutorial-gitalk',\n            owner: 'frank-lam',\n            admin: ['frank-lam'],\n            id: location.pathname,\n            // distractionFreeMode: false\n        })\n    </script>\n\n</body>\n</html>"
  },
  {
    "path": "notes/docsify/js/.eslintrc.js",
    "content": "module.exports = {\n    \"parserOptions\": {\n        \"ecmaVersion\": 5,\n        \"sourceType\": \"script\"\n    },\n    \"env\": {\n        \"commonjs\": false,\n        \"es6\"     : false,\n        \"node\"    : false\n    },\n    \"rules\": {\n        \"no-var\"      : \"off\",\n        \"prefer-const\": \"off\"\n    }\n}"
  },
  {
    "path": "notes/docsify/js/main.js",
    "content": "(function () {\n    // Functions\n    // =========================================================================\n    /**\n     * Adds event listeners to change active stylesheet and restore previously\n     * activated stylesheet on reload.\n     *\n     * @example\n     *\n     * This link:\n     *   <a href=\"#\" data-link-title=\"Foo\">Foo</a>\n     * Will active this existing link:\n     *   <link rel=\"stylesheet alternate\" title=\"Foo\" href=\"...\" >\n     *\n     * @example\n     *\n     * This link:\n     *   <a href=\"#\" data-link-href=\"path/to/file.css\">Bar</a>\n     * Will activate this existing link:\n     *   <link rel=\"stylesheet alternate\" title=\"[someID]\" href=\"path/to/file.css\" >\n     * Or generate this active link:\n     *   <link rel=\"stylesheet\" title=\"Bar\" href=\"path/to/file.css\" >\n     */\n\n    function imgAdd2() {\n        var imgs = document.querySelectorAll('.content img');\n        // var imgs = document.getElementsByTagName('medium-zoom-image');\n        for (let j = 0; j < imgs.length; j++) {\n            // console.log('zheshi')\n            let img = imgs[j];\n            // console.log(img);\n            if (img.src) {\n                console.log(img.src);\n                if (!img.getAttribute('hadassets')) {\n                    let hasharr = window.location.hash.split('/');\n                    // console.log(hasharr);\n                    let origin = window.location.origin;\n                    img.setAttribute('hadassets', 1);\n                    var hasharrPath = \"\";\n                    for(var i = 1; i < hasharr.length - 1 ; i++){\n                        hasharrPath += hasharr[i] + \"/\";\n                    }\n                    img.src = origin + '/' + hasharrPath  + img.src.substring(origin.length + 1);\n                    // img.src = origin + '/' + hasharrPath  + img.src;\n                }\n            }\n        }\n    }\n\n    function imgAdd() {\n        var imgs = document.querySelectorAll('.markdown-section img');\n        // var imgs = document.getElementsByTagName('medium-zoom-image');\n\n        for (let j = 0; j < imgs.length; j++) {\n            // console.log('zheshi')\n            let img = imgs[j];\n            // console.log(img);\n            if (img.src) {\n                console.log(img.src);\n                if (!img.getAttribute('hadassets')) {\n                    let hasharr = window.location.hash.split('/');\n                    // console.log(hasharr);\n                    let origin = window.location.origin;\n                    img.setAttribute('hadassets', 1);\n                    var hasharrPath = \"\";\n                    for(var i = 1; i < hasharr.length - 1 ; i++){\n                        hasharrPath += hasharr[i] + \"/\";\n                    }\n                    \n                    // img.src = origin + '/' + hasharrPath  + img.src.substring(origin.length + 1);\n                    var imgSrc = img.getAttribute(\"src\");\n                    if(!imgSrc.includes(hasharrPath)){\n                        img.setAttribute(\"src\", hasharrPath  + imgSrc);\n                    }\n                    // img.src = origin + '/' + hasharrPath  + img.getAttribute(\"src\");\n                }\n            }\n        }\n    }\n    function initStyleSwitcher() {\n        var isInitialzed = false;\n        var sessionStorageKey = 'activeStylesheetHref';\n\n        function handleSwitch(activeHref, activeTitle) {\n            var activeElm = document.querySelector('link[href*=\"' + activeHref + '\"],link[title=\"' + activeTitle + '\"]');\n\n            if (!activeElm && activeHref) {\n                activeElm = document.createElement('link');\n                activeElm.setAttribute('href', activeHref);\n                activeElm.setAttribute('rel', 'stylesheet');\n                activeElm.setAttribute('title', activeTitle);\n\n                document.head.appendChild(activeElm);\n\n                activeElm.addEventListener('load', function linkOnLoad() {\n                    activeElm.removeEventListener('load', linkOnLoad);\n                    setActiveLink(activeElm);\n                });\n            }\n            else if (activeElm) {\n                setActiveLink(activeElm);\n            }\n        }\n\n        function setActiveLink(activeElm) {\n            var activeHref = activeElm.getAttribute('href');\n            var activeTitle = activeElm.getAttribute('title');\n            var inactiveElms = document.querySelectorAll('link[title]:not([href*=\"' + activeHref + '\"]):not([title=\"' + activeTitle + '\"])');\n\n            // Remove \"alternate\" keyword\n            activeElm.setAttribute('rel', (activeElm.rel || '').replace(/\\s*alternate/g, '').trim());\n\n            // Force enable stylesheet (required for some browsers)\n            activeElm.disabled = true;\n            activeElm.disabled = false;\n\n            // Store active style sheet\n            sessionStorage.setItem(sessionStorageKey, activeHref);\n\n            // Disable other elms\n            for (var i = 0; i < inactiveElms.length; i++) {\n                var elm = inactiveElms[i];\n\n                elm.disabled = true;\n\n                // Fix for browsersync and alternate stylesheet updates. Will\n                // cause FOUC when switching stylesheets during development, but\n                // required to properly apply style updates when alternate\n                // stylesheets are enabled.\n                if (window.browsersyncObserver) {\n                    var linkRel = elm.getAttribute('rel') || '';\n                    var linkRelAlt = linkRel.indexOf('alternate') > -1 ? linkRel : (linkRel + ' alternate').trim();\n\n                    elm.setAttribute('rel', linkRelAlt);\n                }\n            }\n\n            // CSS custom property ponyfil\n            if ((window.$docsify || {}).themeable) {\n                window.$docsify.themeable.util.cssVars();\n            }\n        }\n\n        // Event listeners\n        if (!isInitialzed) {\n            isInitialzed = true;\n            // Restore active stylesheet\n            document.addEventListener('DOMContentLoaded', function () {\n                // setTimeout(function () {\n                //     imgAdd();\n                // }, 300)\n                var activeHref = sessionStorage.getItem(sessionStorageKey);\n\n                if (activeHref) {\n                    handleSwitch(activeHref);\n                }\n\n                // 方法一：会出现时而可以加载，时而不能加载问题\n                // var getJs = true;\n                // while(getJs){\n                //     setTimeout(function () {\n                //         $('#la_19815069').append('<script type=\"text/javascript\" src=\"//quote.51.la/q?id=19815069&mb=4\"></script>');\n                //     }, 150)\n                //     if($('#la_19815069 a')){\n                //        getJs = false;\n                //     }\n                // }\n\n                // 方法二：\n                for(var i = 1; i < 20; i++){\n                    if($('#la_19815069 a').length != 0){\n                        break;\n                    }\n                    else{\n                        setTimeout(function(){\n                            $('#la_19815069').append('<script type=\"text/javascript\" src=\"//quote.51.la/q?id=19815069&mb=4\"></script>');\n                        }, 200);\n                    }\n                }\n                \n            });\n\n            // Update active stylesheet\n            document.addEventListener('click', function (evt) {\n                var dataHref = evt.target.getAttribute('data-link-href');\n                var dataTitle = evt.target.getAttribute('data-link-title')\n\n                if (dataHref || dataTitle) {\n                    dataTitle = dataTitle\n                        || evt.target.textContent\n                        || '_' + Math.random().toString(36).substr(2, 9); // UID\n\n                    handleSwitch(dataHref, dataTitle);\n                    evt.preventDefault();\n                }\n                // setTimeout(function () {\n                //     imgAdd();\n                // }, 300)\n            });\n        }\n    }\n\n\n\n    // Main\n    // =========================================================================\n    initStyleSwitcher();\n})();\n"
  },
  {
    "path": "notes/docsify/navbar.md",
    "content": "* Themes\n  - <a href=\"#\" data-link-title=\"Simple\">默认</a>\n  - <a href=\"#\" data-link-title=\"Defaults\">简约</a> \n  - <a href=\"#\" data-link-title=\"Simple Dark\">深色</a>\n\n* About\n  * [联系作者](docsify/about/author)\n  * [打赏一下](docsify/about/donate)\n\n\n"
  },
  {
    "path": "notes/docsify/sidebar.md",
    "content": "- [Quick Start](introduction)\n- **Java 核心概念**\n- [Java 基础](JavaArchitecture/01-Java基础)\n- [Java 集合框架](JavaArchitecture/02-Java集合框架)\n- [Java 并发编程](JavaArchitecture/03-Java并发编程)\n- [Java IO](JavaArchitecture/04-Java-IO)\n- [Java 虚拟机](JavaArchitecture/05-Java虚拟机)\n- [Java 设计模式](JavaArchitecture/06-Java设计模式)\n- [Java Web](JavaArchitecture/07-JavaWeb)\n- **分布式/微服务**\n- [Docker](Docker)\n- **数据库**\n- [MySQL](MySQL.md)\n- [SQL](SQL.md)\n- [Redis](Redis.md)\n- **操作系统**\n- [操作系统原理](操作系统)\n- [Linux](Linux.md)\n- **网络通信**\n- [计算机网络](计算机网络)\n- [网络安全](网络安全)\n- [RESTful API](RESTful%20API)\n\n\n<!-- - **网络通信**\n- [Java 基础](JavaArchitecture/01-Java基础)\n- [Java 集合框架](JavaArchitecture/02-Java集合框架)\n- [Java 集合框架](JavaArchitecture/02-Java集合框架)\n-  -->\n\n<!-- \n- **Links**\n- [Linux](Docker)\n- [Quick Start](quick-start)\n- [Themes](themes)\n- [Customization](customization)\n- [Options](options)\n- [Markdown](markdown)\n- [Changelog](changelog)\n\n -->\n\n\n- **Links**\n<!-- - [![Code](https://icongr.am/feather/code.svg?size=16&color=808080)GitHub](https://codesandbox.io/s/xv36w4695o) -->\n<!-- - [![Github](https://icongram.jgog.in/simple/github.svg?size=16)Github](https://github.com/frank-lam/fullstack-tutorial) -->\n- <a  target=\"_blank\" href=\"https://zhuanlan.zhihu.com/frankfeekr\" rel=\"nofollow\"><img src=\"https://img.shields.io/badge/知乎专栏-frankfeekr-blue.svg\" alt=\"QQ群\" data-canonical-src=\"\" style=\"max-width:100%;\"></a>\n- <a target=\"_blank\" href=\"http://blog.csdn.net/u012104219\" rel=\"nofollow\"><img src=\"https://img.shields.io/badge/CSDN-东风牧野-red.svg\" alt=\"CSDN\" data-canonical-src=\"\" style=\"max-width:100%;\"></a>\n- <a target=\"_blank\" href=\"mailto:frank_lin@whu.edu.cn\" rel=\"nofollow\"><img src=\"https://img.shields.io/badge/Email-frank__lin@whu.edu.cn-lightgrey.svg\" alt=\"邮箱\" data-canonical-src=\"\" style=\"max-width:100%;\"></a>\n- <a target=\"_blank\" href=\"https://jq.qq.com/?_wv=1027&k=593WvX0\" rel=\"nofollow\" ><img src=\"https://img.shields.io/badge/QQ技术交流群-862619503-green.svg\" alt=\"QQ群\" data-canonical-src=\"\" style=\"max-width:100%;\"></a>\n\n\n- **全栈开发社区**\n- <a  target=\"_blank\" href=\"https://zhuanlan.zhihu.com/frankfeekr\" rel=\"nofollow\"><img src=\"https://raw.githubusercontent.com/frank-lam/fullstack-tutorial/master/assets/wechat-fullstack3.png\" alt=\"QQ群\" data-canonical-src=\"\" style=\"max-width:100%;\"></a>"
  },
  {
    "path": "notes/docsify/unpkg/docsify/lib/plugins/emoji.js",
    "content": "(function () {\nvar AllGithubEmoji = [\n  '+1',\n  '100',\n  '1234',\n  '8ball',\n  'a',\n  'ab',\n  'abc',\n  'abcd',\n  'accept',\n  'aerial_tramway',\n  'airplane',\n  'alarm_clock',\n  'alien',\n  'ambulance',\n  'anchor',\n  'angel',\n  'anger',\n  'angry',\n  'anguished',\n  'ant',\n  'apple',\n  'aquarius',\n  'aries',\n  'arrow_backward',\n  'arrow_double_down',\n  'arrow_double_up',\n  'arrow_down',\n  'arrow_down_small',\n  'arrow_forward',\n  'arrow_heading_down',\n  'arrow_heading_up',\n  'arrow_left',\n  'arrow_lower_left',\n  'arrow_lower_right',\n  'arrow_right',\n  'arrow_right_hook',\n  'arrow_up',\n  'arrow_up_down',\n  'arrow_up_small',\n  'arrow_upper_left',\n  'arrow_upper_right',\n  'arrows_clockwise',\n  'arrows_counterclockwise',\n  'art',\n  'articulated_lorry',\n  'astonished',\n  'athletic_shoe',\n  'atm',\n  'b',\n  'baby',\n  'baby_bottle',\n  'baby_chick',\n  'baby_symbol',\n  'back',\n  'baggage_claim',\n  'balloon',\n  'ballot_box_with_check',\n  'bamboo',\n  'banana',\n  'bangbang',\n  'bank',\n  'bar_chart',\n  'barber',\n  'baseball',\n  'basketball',\n  'bath',\n  'bathtub',\n  'battery',\n  'bear',\n  'bee',\n  'beer',\n  'beers',\n  'beetle',\n  'beginner',\n  'bell',\n  'bento',\n  'bicyclist',\n  'bike',\n  'bikini',\n  'bird',\n  'birthday',\n  'black_circle',\n  'black_joker',\n  'black_large_square',\n  'black_medium_small_square',\n  'black_medium_square',\n  'black_nib',\n  'black_small_square',\n  'black_square_button',\n  'blossom',\n  'blowfish',\n  'blue_book',\n  'blue_car',\n  'blue_heart',\n  'blush',\n  'boar',\n  'boat',\n  'bomb',\n  'book',\n  'bookmark',\n  'bookmark_tabs',\n  'books',\n  'boom',\n  'boot',\n  'bouquet',\n  'bow',\n  'bowling',\n  'bowtie',\n  'boy',\n  'bread',\n  'bride_with_veil',\n  'bridge_at_night',\n  'briefcase',\n  'broken_heart',\n  'bug',\n  'bulb',\n  'bullettrain_front',\n  'bullettrain_side',\n  'bus',\n  'busstop',\n  'bust_in_silhouette',\n  'busts_in_silhouette',\n  'cactus',\n  'cake',\n  'calendar',\n  'calling',\n  'camel',\n  'camera',\n  'cancer',\n  'candy',\n  'capital_abcd',\n  'capricorn',\n  'car',\n  'card_index',\n  'carousel_horse',\n  'cat',\n  'cat2',\n  'cd',\n  'chart',\n  'chart_with_downwards_trend',\n  'chart_with_upwards_trend',\n  'checkered_flag',\n  'cherries',\n  'cherry_blossom',\n  'chestnut',\n  'chicken',\n  'children_crossing',\n  'chocolate_bar',\n  'christmas_tree',\n  'church',\n  'cinema',\n  'circus_tent',\n  'city_sunrise',\n  'city_sunset',\n  'cl',\n  'clap',\n  'clapper',\n  'clipboard',\n  'clock1',\n  'clock10',\n  'clock1030',\n  'clock11',\n  'clock1130',\n  'clock12',\n  'clock1230',\n  'clock130',\n  'clock2',\n  'clock230',\n  'clock3',\n  'clock330',\n  'clock4',\n  'clock430',\n  'clock5',\n  'clock530',\n  'clock6',\n  'clock630',\n  'clock7',\n  'clock730',\n  'clock8',\n  'clock830',\n  'clock9',\n  'clock930',\n  'closed_book',\n  'closed_lock_with_key',\n  'closed_umbrella',\n  'cloud',\n  'clubs',\n  'cn',\n  'cocktail',\n  'coffee',\n  'cold_sweat',\n  'collision',\n  'computer',\n  'confetti_ball',\n  'confounded',\n  'confused',\n  'congratulations',\n  'construction',\n  'construction_worker',\n  'convenience_store',\n  'cookie',\n  'cool',\n  'cop',\n  'copyright',\n  'corn',\n  'couple',\n  'couple_with_heart',\n  'couplekiss',\n  'cow',\n  'cow2',\n  'credit_card',\n  'crescent_moon',\n  'crocodile',\n  'crossed_flags',\n  'crown',\n  'cry',\n  'crying_cat_face',\n  'crystal_ball',\n  'cupid',\n  'curly_loop',\n  'currency_exchange',\n  'curry',\n  'custard',\n  'customs',\n  'cyclone',\n  'dancer',\n  'dancers',\n  'dango',\n  'dart',\n  'dash',\n  'date',\n  'de',\n  'deciduous_tree',\n  'department_store',\n  'diamond_shape_with_a_dot_inside',\n  'diamonds',\n  'disappointed',\n  'disappointed_relieved',\n  'dizzy',\n  'dizzy_face',\n  'do_not_litter',\n  'dog',\n  'dog2',\n  'dollar',\n  'dolls',\n  'dolphin',\n  'door',\n  'doughnut',\n  'dragon',\n  'dragon_face',\n  'dress',\n  'dromedary_camel',\n  'droplet',\n  'dvd',\n  'e-mail',\n  'ear',\n  'ear_of_rice',\n  'earth_africa',\n  'earth_americas',\n  'earth_asia',\n  'egg',\n  'eggplant',\n  'eight',\n  'eight_pointed_black_star',\n  'eight_spoked_asterisk',\n  'electric_plug',\n  'elephant',\n  'email',\n  'end',\n  'envelope',\n  'envelope_with_arrow',\n  'es',\n  'euro',\n  'european_castle',\n  'european_post_office',\n  'evergreen_tree',\n  'exclamation',\n  'expressionless',\n  'eyeglasses',\n  'eyes',\n  'facepunch',\n  'factory',\n  'fallen_leaf',\n  'family',\n  'fast_forward',\n  'fax',\n  'fearful',\n  'feelsgood',\n  'feet',\n  'ferris_wheel',\n  'file_folder',\n  'finnadie',\n  'fire',\n  'fire_engine',\n  'fireworks',\n  'first_quarter_moon',\n  'first_quarter_moon_with_face',\n  'fish',\n  'fish_cake',\n  'fishing_pole_and_fish',\n  'fist',\n  'five',\n  'flags',\n  'flashlight',\n  'flipper',\n  'floppy_disk',\n  'flower_playing_cards',\n  'flushed',\n  'foggy',\n  'football',\n  'footprints',\n  'fork_and_knife',\n  'fountain',\n  'four',\n  'four_leaf_clover',\n  'fr',\n  'free',\n  'fried_shrimp',\n  'fries',\n  'frog',\n  'frowning',\n  'fu',\n  'fuelpump',\n  'full_moon',\n  'full_moon_with_face',\n  'game_die',\n  'gb',\n  'gem',\n  'gemini',\n  'ghost',\n  'gift',\n  'gift_heart',\n  'girl',\n  'globe_with_meridians',\n  'goat',\n  'goberserk',\n  'godmode',\n  'golf',\n  'grapes',\n  'green_apple',\n  'green_book',\n  'green_heart',\n  'grey_exclamation',\n  'grey_question',\n  'grimacing',\n  'grin',\n  'grinning',\n  'guardsman',\n  'guitar',\n  'gun',\n  'haircut',\n  'hamburger',\n  'hammer',\n  'hamster',\n  'hand',\n  'handbag',\n  'hankey',\n  'hash',\n  'hatched_chick',\n  'hatching_chick',\n  'headphones',\n  'hear_no_evil',\n  'heart',\n  'heart_decoration',\n  'heart_eyes',\n  'heart_eyes_cat',\n  'heartbeat',\n  'heartpulse',\n  'hearts',\n  'heavy_check_mark',\n  'heavy_division_sign',\n  'heavy_dollar_sign',\n  'heavy_exclamation_mark',\n  'heavy_minus_sign',\n  'heavy_multiplication_x',\n  'heavy_plus_sign',\n  'helicopter',\n  'herb',\n  'hibiscus',\n  'high_brightness',\n  'high_heel',\n  'hocho',\n  'honey_pot',\n  'honeybee',\n  'horse',\n  'horse_racing',\n  'hospital',\n  'hotel',\n  'hotsprings',\n  'hourglass',\n  'hourglass_flowing_sand',\n  'house',\n  'house_with_garden',\n  'hurtrealbad',\n  'hushed',\n  'ice_cream',\n  'icecream',\n  'id',\n  'ideograph_advantage',\n  'imp',\n  'inbox_tray',\n  'incoming_envelope',\n  'information_desk_person',\n  'information_source',\n  'innocent',\n  'interrobang',\n  'iphone',\n  'it',\n  'izakaya_lantern',\n  'jack_o_lantern',\n  'japan',\n  'japanese_castle',\n  'japanese_goblin',\n  'japanese_ogre',\n  'jeans',\n  'joy',\n  'joy_cat',\n  'jp',\n  'key',\n  'keycap_ten',\n  'kimono',\n  'kiss',\n  'kissing',\n  'kissing_cat',\n  'kissing_closed_eyes',\n  'kissing_heart',\n  'kissing_smiling_eyes',\n  'koala',\n  'koko',\n  'kr',\n  'lantern',\n  'large_blue_circle',\n  'large_blue_diamond',\n  'large_orange_diamond',\n  'last_quarter_moon',\n  'last_quarter_moon_with_face',\n  'laughing',\n  'leaves',\n  'ledger',\n  'left_luggage',\n  'left_right_arrow',\n  'leftwards_arrow_with_hook',\n  'lemon',\n  'leo',\n  'leopard',\n  'libra',\n  'light_rail',\n  'link',\n  'lips',\n  'lipstick',\n  'lock',\n  'lock_with_ink_pen',\n  'lollipop',\n  'loop',\n  'loud_sound',\n  'loudspeaker',\n  'love_hotel',\n  'love_letter',\n  'low_brightness',\n  'm',\n  'mag',\n  'mag_right',\n  'mahjong',\n  'mailbox',\n  'mailbox_closed',\n  'mailbox_with_mail',\n  'mailbox_with_no_mail',\n  'man',\n  'man_with_gua_pi_mao',\n  'man_with_turban',\n  'mans_shoe',\n  'maple_leaf',\n  'mask',\n  'massage',\n  'meat_on_bone',\n  'mega',\n  'melon',\n  'memo',\n  'mens',\n  'metal',\n  'metro',\n  'microphone',\n  'microscope',\n  'milky_way',\n  'minibus',\n  'minidisc',\n  'mobile_phone_off',\n  'money_with_wings',\n  'moneybag',\n  'monkey',\n  'monkey_face',\n  'monorail',\n  'moon',\n  'mortar_board',\n  'mount_fuji',\n  'mountain_bicyclist',\n  'mountain_cableway',\n  'mountain_railway',\n  'mouse',\n  'mouse2',\n  'movie_camera',\n  'moyai',\n  'muscle',\n  'mushroom',\n  'musical_keyboard',\n  'musical_note',\n  'musical_score',\n  'mute',\n  'nail_care',\n  'name_badge',\n  'neckbeard',\n  'necktie',\n  'negative_squared_cross_mark',\n  'neutral_face',\n  'new',\n  'new_moon',\n  'new_moon_with_face',\n  'newspaper',\n  'ng',\n  'night_with_stars',\n  'nine',\n  'no_bell',\n  'no_bicycles',\n  'no_entry',\n  'no_entry_sign',\n  'no_good',\n  'no_mobile_phones',\n  'no_mouth',\n  'no_pedestrians',\n  'no_smoking',\n  'non-potable_water',\n  'nose',\n  'notebook',\n  'notebook_with_decorative_cover',\n  'notes',\n  'nut_and_bolt',\n  'o',\n  'o2',\n  'ocean',\n  'octocat',\n  'octopus',\n  'oden',\n  'office',\n  'ok',\n  'ok_hand',\n  'ok_woman',\n  'older_man',\n  'older_woman',\n  'on',\n  'oncoming_automobile',\n  'oncoming_bus',\n  'oncoming_police_car',\n  'oncoming_taxi',\n  'one',\n  'open_book',\n  'open_file_folder',\n  'open_hands',\n  'open_mouth',\n  'ophiuchus',\n  'orange_book',\n  'outbox_tray',\n  'ox',\n  'package',\n  'page_facing_up',\n  'page_with_curl',\n  'pager',\n  'palm_tree',\n  'panda_face',\n  'paperclip',\n  'parking',\n  'part_alternation_mark',\n  'partly_sunny',\n  'passport_control',\n  'paw_prints',\n  'peach',\n  'pear',\n  'pencil',\n  'pencil2',\n  'penguin',\n  'pensive',\n  'performing_arts',\n  'persevere',\n  'person_frowning',\n  'person_with_blond_hair',\n  'person_with_pouting_face',\n  'phone',\n  'pig',\n  'pig2',\n  'pig_nose',\n  'pill',\n  'pineapple',\n  'pisces',\n  'pizza',\n  'point_down',\n  'point_left',\n  'point_right',\n  'point_up',\n  'point_up_2',\n  'police_car',\n  'poodle',\n  'poop',\n  'post_office',\n  'postal_horn',\n  'postbox',\n  'potable_water',\n  'pouch',\n  'poultry_leg',\n  'pound',\n  'pouting_cat',\n  'pray',\n  'princess',\n  'punch',\n  'purple_heart',\n  'purse',\n  'pushpin',\n  'put_litter_in_its_place',\n  'question',\n  'rabbit',\n  'rabbit2',\n  'racehorse',\n  'radio',\n  'radio_button',\n  'rage',\n  'rage1',\n  'rage2',\n  'rage3',\n  'rage4',\n  'railway_car',\n  'rainbow',\n  'raised_hand',\n  'raised_hands',\n  'raising_hand',\n  'ram',\n  'ramen',\n  'rat',\n  'recycle',\n  'red_car',\n  'red_circle',\n  'registered',\n  'relaxed',\n  'relieved',\n  'repeat',\n  'repeat_one',\n  'restroom',\n  'revolving_hearts',\n  'rewind',\n  'ribbon',\n  'rice',\n  'rice_ball',\n  'rice_cracker',\n  'rice_scene',\n  'ring',\n  'rocket',\n  'roller_coaster',\n  'rooster',\n  'rose',\n  'rotating_light',\n  'round_pushpin',\n  'rowboat',\n  'ru',\n  'rugby_football',\n  'runner',\n  'running',\n  'running_shirt_with_sash',\n  'sa',\n  'sagittarius',\n  'sailboat',\n  'sake',\n  'sandal',\n  'santa',\n  'satellite',\n  'satisfied',\n  'saxophone',\n  'school',\n  'school_satchel',\n  'scissors',\n  'scorpius',\n  'scream',\n  'scream_cat',\n  'scroll',\n  'seat',\n  'secret',\n  'see_no_evil',\n  'seedling',\n  'seven',\n  'shaved_ice',\n  'sheep',\n  'shell',\n  'ship',\n  'shipit',\n  'shirt',\n  'shit',\n  'shoe',\n  'shower',\n  'signal_strength',\n  'six',\n  'six_pointed_star',\n  'ski',\n  'skull',\n  'sleeping',\n  'sleepy',\n  'slot_machine',\n  'small_blue_diamond',\n  'small_orange_diamond',\n  'small_red_triangle',\n  'small_red_triangle_down',\n  'smile',\n  'smile_cat',\n  'smiley',\n  'smiley_cat',\n  'smiling_imp',\n  'smirk',\n  'smirk_cat',\n  'smoking',\n  'snail',\n  'snake',\n  'snowboarder',\n  'snowflake',\n  'snowman',\n  'sob',\n  'soccer',\n  'soon',\n  'sos',\n  'sound',\n  'space_invader',\n  'spades',\n  'spaghetti',\n  'sparkle',\n  'sparkler',\n  'sparkles',\n  'sparkling_heart',\n  'speak_no_evil',\n  'speaker',\n  'speech_balloon',\n  'speedboat',\n  'squirrel',\n  'star',\n  'star2',\n  'stars',\n  'station',\n  'statue_of_liberty',\n  'steam_locomotive',\n  'stew',\n  'straight_ruler',\n  'strawberry',\n  'stuck_out_tongue',\n  'stuck_out_tongue_closed_eyes',\n  'stuck_out_tongue_winking_eye',\n  'sun_with_face',\n  'sunflower',\n  'sunglasses',\n  'sunny',\n  'sunrise',\n  'sunrise_over_mountains',\n  'surfer',\n  'sushi',\n  'suspect',\n  'suspension_railway',\n  'sweat',\n  'sweat_drops',\n  'sweat_smile',\n  'sweet_potato',\n  'swimmer',\n  'symbols',\n  'syringe',\n  'tada',\n  'tanabata_tree',\n  'tangerine',\n  'taurus',\n  'taxi',\n  'tea',\n  'telephone',\n  'telephone_receiver',\n  'telescope',\n  'tennis',\n  'tent',\n  'thought_balloon',\n  'three',\n  'thumbsdown',\n  'thumbsup',\n  'ticket',\n  'tiger',\n  'tiger2',\n  'tired_face',\n  'tm',\n  'toilet',\n  'tokyo_tower',\n  'tomato',\n  'tongue',\n  'top',\n  'tophat',\n  'tractor',\n  'traffic_light',\n  'train',\n  'train2',\n  'tram',\n  'triangular_flag_on_post',\n  'triangular_ruler',\n  'trident',\n  'triumph',\n  'trolleybus',\n  'trollface',\n  'trophy',\n  'tropical_drink',\n  'tropical_fish',\n  'truck',\n  'trumpet',\n  'tshirt',\n  'tulip',\n  'turtle',\n  'tv',\n  'twisted_rightwards_arrows',\n  'two',\n  'two_hearts',\n  'two_men_holding_hands',\n  'two_women_holding_hands',\n  'u5272',\n  'u5408',\n  'u55b6',\n  'u6307',\n  'u6708',\n  'u6709',\n  'u6e80',\n  'u7121',\n  'u7533',\n  'u7981',\n  'u7a7a',\n  'uk',\n  'umbrella',\n  'unamused',\n  'underage',\n  'unlock',\n  'up',\n  'us',\n  'v',\n  'vertical_traffic_light',\n  'vhs',\n  'vibration_mode',\n  'video_camera',\n  'video_game',\n  'violin',\n  'virgo',\n  'volcano',\n  'vs',\n  'walking',\n  'waning_crescent_moon',\n  'waning_gibbous_moon',\n  'warning',\n  'watch',\n  'water_buffalo',\n  'watermelon',\n  'wave',\n  'wavy_dash',\n  'waxing_crescent_moon',\n  'waxing_gibbous_moon',\n  'wc',\n  'weary',\n  'wedding',\n  'whale',\n  'whale2',\n  'wheelchair',\n  'white_check_mark',\n  'white_circle',\n  'white_flower',\n  'white_large_square',\n  'white_medium_small_square',\n  'white_medium_square',\n  'white_small_square',\n  'white_square_button',\n  'wind_chime',\n  'wine_glass',\n  'wink',\n  'wolf',\n  'woman',\n  'womans_clothes',\n  'womans_hat',\n  'womens',\n  'worried',\n  'wrench',\n  'x',\n  'yellow_heart',\n  'yen',\n  'yum',\n  'zap',\n  'zero',\n  'zzz'\n];\n\n// Emoji from All-Github-Emoji-Icons\n// https://github.com/scotch-io/All-Github-Emoji-Icons\nwindow.emojify = function (match, $1) {\n  return AllGithubEmoji.indexOf($1) === -1 ?\n    match :\n    '<img class=\"emoji\" src=\"https://assets-cdn.github.com/images/icons/emoji/' +\n      $1 +\n      '.png\" alt=\"' +\n      $1 +\n      '\" />'\n};\n\n}());\n"
  },
  {
    "path": "notes/docsify/unpkg/docsify/lib/plugins/search.js",
    "content": "(function () {\nvar INDEXS = {};\n\nfunction escapeHtml(string) {\n  var entityMap = {\n    '&': '&amp;',\n    '<': '&lt;',\n    '>': '&gt;',\n    '\"': '&quot;',\n    '\\'': '&#39;',\n    '/': '&#x2F;'\n  };\n\n  return String(string).replace(/[&<>\"'/]/g, function (s) { return entityMap[s]; })\n}\n\nfunction getAllPaths(router) {\n  var paths = [];\n\n  Docsify.dom.findAll('.sidebar-nav a:not(.section-link):not([data-nosearch])').forEach(function (node) {\n    var href = node.href;\n    var originHref = node.getAttribute('href');\n    var path = router.parse(href).path;\n\n    if (\n      path &&\n      paths.indexOf(path) === -1 &&\n      !Docsify.util.isAbsolutePath(originHref)\n    ) {\n      paths.push(path);\n    }\n  });\n\n  return paths\n}\n\nfunction saveData(maxAge) {\n  localStorage.setItem('docsify.search.expires', Date.now() + maxAge);\n  localStorage.setItem('docsify.search.index', JSON.stringify(INDEXS));\n}\n\nfunction genIndex(path, content, router, depth) {\n  if ( content === void 0 ) content = '';\n\n  var tokens = window.marked.lexer(content);\n  var slugify = window.Docsify.slugify;\n  var index = {};\n  var slug;\n\n  tokens.forEach(function (token) {\n    if (token.type === 'heading' && token.depth <= depth) {\n      slug = router.toURL(path, {id: slugify(token.text)});\n      index[slug] = {slug: slug, title: token.text, body: ''};\n    } else {\n      if (!slug) {\n        return\n      }\n      if (!index[slug]) {\n        index[slug] = {slug: slug, title: '', body: ''};\n      } else if (index[slug].body) {\n        index[slug].body += '\\n' + (token.text || '');\n      } else {\n        index[slug].body = token.text;\n      }\n    }\n  });\n  slugify.clear();\n  return index\n}\n\n/**\n * @param {String} query\n * @returns {Array}\n */\nfunction search(query) {\n  var matchingResults = [];\n  var data = [];\n  Object.keys(INDEXS).forEach(function (key) {\n    data = data.concat(Object.keys(INDEXS[key]).map(function (page) { return INDEXS[key][page]; }));\n  });\n\n  query = query.trim();\n  var keywords = query.split(/[\\s\\-，\\\\/]+/);\n  if (keywords.length !== 1) {\n    keywords = [].concat(query, keywords);\n  }\n\n  var loop = function ( i ) {\n    var post = data[i];\n    var isMatch = false;\n    var resultStr = '';\n    var postTitle = post.title && post.title.trim();\n    var postContent = post.body && post.body.trim();\n    var postUrl = post.slug || '';\n\n    if (postTitle && postContent) {\n      keywords.forEach(function (keyword) {\n        // From https://github.com/sindresorhus/escape-string-regexp\n        var regEx = new RegExp(\n          keyword.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&'),\n          'gi'\n        );\n        var indexTitle = -1;\n        var indexContent = -1;\n\n        indexTitle = postTitle && postTitle.search(regEx);\n        indexContent = postContent && postContent.search(regEx);\n\n        if (indexTitle < 0 && indexContent < 0) {\n          isMatch = false;\n        } else {\n          isMatch = true;\n          if (indexContent < 0) {\n            indexContent = 0;\n          }\n\n          var start = 0;\n          var end = 0;\n\n          start = indexContent < 11 ? 0 : indexContent - 10;\n          end = start === 0 ? 70 : indexContent + keyword.length + 60;\n\n          if (end > postContent.length) {\n            end = postContent.length;\n          }\n\n          var matchContent =\n            '...' +\n            escapeHtml(postContent)\n              .substring(start, end)\n              .replace(regEx, (\"<em class=\\\"search-keyword\\\">\" + keyword + \"</em>\")) +\n            '...';\n\n          resultStr += matchContent;\n        }\n      });\n\n      if (isMatch) {\n        var matchingPost = {\n          title: escapeHtml(postTitle),\n          content: resultStr,\n          url: postUrl\n        };\n\n        matchingResults.push(matchingPost);\n      }\n    }\n  };\n\n  for (var i = 0; i < data.length; i++) loop( i );\n\n  return matchingResults\n}\n\nfunction init$1(config, vm) {\n  var isAuto = config.paths === 'auto';\n  var isExpired = localStorage.getItem('docsify.search.expires') < Date.now();\n\n  INDEXS = JSON.parse(localStorage.getItem('docsify.search.index'));\n\n  if (isExpired) {\n    INDEXS = {};\n  } else if (!isAuto) {\n    return\n  }\n\n  var paths = isAuto ? getAllPaths(vm.router) : config.paths;\n  var len = paths.length;\n  var count = 0;\n\n  paths.forEach(function (path) {\n    if (INDEXS[path]) {\n      return count++\n    }\n\n    Docsify\n      .get(vm.router.getFile(path), false, vm.config.requestHeaders)\n      .then(function (result) {\n        INDEXS[path] = genIndex(path, result, vm.router, config.depth);\n        len === ++count && saveData(config.maxAge);\n      });\n  });\n}\n\nvar NO_DATA_TEXT = '';\nvar options;\n\nfunction style() {\n  var code = \"\\n.sidebar {\\n  padding-top: 0;\\n}\\n\\n.search {\\n  margin-bottom: 20px;\\n  padding: 6px;\\n  border-bottom: 1px solid #eee;\\n}\\n\\n.search .input-wrap {\\n  display: flex;\\n  align-items: center;\\n}\\n\\n.search .results-panel {\\n  display: none;\\n}\\n\\n.search .results-panel.show {\\n  display: block;\\n}\\n\\n.search input {\\n  outline: none;\\n  border: none;\\n  width: 100%;\\n  padding: 0 7px;\\n  line-height: 36px;\\n  font-size: 14px;\\n}\\n\\n.search input::-webkit-search-decoration,\\n.search input::-webkit-search-cancel-button,\\n.search input {\\n  -webkit-appearance: none;\\n  -moz-appearance: none;\\n  appearance: none;\\n}\\n.search .clear-button {\\n  width: 36px;\\n  text-align: right;\\n  display: none;\\n}\\n\\n.search .clear-button.show {\\n  display: block;\\n}\\n\\n.search .clear-button svg {\\n  transform: scale(.5);\\n}\\n\\n.search h2 {\\n  font-size: 17px;\\n  margin: 10px 0;\\n}\\n\\n.search a {\\n  text-decoration: none;\\n  color: inherit;\\n}\\n\\n.search .matching-post {\\n  border-bottom: 1px solid #eee;\\n}\\n\\n.search .matching-post:last-child {\\n  border-bottom: 0;\\n}\\n\\n.search p {\\n  font-size: 14px;\\n  overflow: hidden;\\n  text-overflow: ellipsis;\\n  display: -webkit-box;\\n  -webkit-line-clamp: 2;\\n  -webkit-box-orient: vertical;\\n}\\n\\n.search p.empty {\\n  text-align: center;\\n}\\n\\n.app-name.hide, .sidebar-nav.hide {\\n  display: none;\\n}\";\n\n  Docsify.dom.style(code);\n}\n\nfunction tpl(defaultValue) {\n  if ( defaultValue === void 0 ) defaultValue = '';\n\n  var html =\n    \"<div class=\\\"input-wrap\\\">\\n      <input type=\\\"search\\\" value=\\\"\" + defaultValue + \"\\\" />\\n      <div class=\\\"clear-button\\\">\\n        <svg width=\\\"26\\\" height=\\\"24\\\">\\n          <circle cx=\\\"12\\\" cy=\\\"12\\\" r=\\\"11\\\" fill=\\\"#ccc\\\" />\\n          <path stroke=\\\"white\\\" stroke-width=\\\"2\\\" d=\\\"M8.25,8.25,15.75,15.75\\\" />\\n          <path stroke=\\\"white\\\" stroke-width=\\\"2\\\"d=\\\"M8.25,15.75,15.75,8.25\\\" />\\n        </svg>\\n      </div>\\n    </div>\\n    <div class=\\\"results-panel\\\"></div>\\n    </div>\";\n  var el = Docsify.dom.create('div', html);\n  var aside = Docsify.dom.find('aside');\n\n  Docsify.dom.toggleClass(el, 'search');\n  Docsify.dom.before(aside, el);\n}\n\nfunction doSearch(value) {\n  var $search = Docsify.dom.find('div.search');\n  var $panel = Docsify.dom.find($search, '.results-panel');\n  var $clearBtn = Docsify.dom.find($search, '.clear-button');\n  var $sidebarNav = Docsify.dom.find('.sidebar-nav');\n  var $appName = Docsify.dom.find('.app-name');\n\n  if (!value) {\n    $panel.classList.remove('show');\n    $clearBtn.classList.remove('show');\n    $panel.innerHTML = '';\n\n    if (options.hideOtherSidebarContent) {\n      $sidebarNav.classList.remove('hide');\n      $appName.classList.remove('hide');\n    }\n    return\n  }\n  var matchs = search(value);\n\n  var html = '';\n  matchs.forEach(function (post) {\n    html += \"<div class=\\\"matching-post\\\">\\n<a href=\\\"\" + (post.url) + \"\\\">\\n<h2>\" + (post.title) + \"</h2>\\n<p>\" + (post.content) + \"</p>\\n</a>\\n</div>\";\n  });\n\n  $panel.classList.add('show');\n  $clearBtn.classList.add('show');\n  $panel.innerHTML = html || (\"<p class=\\\"empty\\\">\" + NO_DATA_TEXT + \"</p>\");\n  if (options.hideOtherSidebarContent) {\n    $sidebarNav.classList.add('hide');\n    $appName.classList.add('hide');\n  }\n}\n\nfunction bindEvents() {\n  var $search = Docsify.dom.find('div.search');\n  var $input = Docsify.dom.find($search, 'input');\n  var $inputWrap = Docsify.dom.find($search, '.input-wrap');\n\n  var timeId;\n  // Prevent to Fold sidebar\n  Docsify.dom.on(\n    $search,\n    'click',\n    function (e) { return e.target.tagName !== 'A' && e.stopPropagation(); }\n  );\n  Docsify.dom.on($input, 'input', function (e) {\n    clearTimeout(timeId);\n    timeId = setTimeout(function (_) { return doSearch(e.target.value.trim()); }, 100);\n  });\n  Docsify.dom.on($inputWrap, 'click', function (e) {\n    // Click input outside\n    if (e.target.tagName !== 'INPUT') {\n      $input.value = '';\n      doSearch();\n    }\n  });\n}\n\nfunction updatePlaceholder(text, path) {\n  var $input = Docsify.dom.getNode('.search input[type=\"search\"]');\n\n  if (!$input) {\n    return\n  }\n  if (typeof text === 'string') {\n    $input.placeholder = text;\n  } else {\n    var match = Object.keys(text).filter(function (key) { return path.indexOf(key) > -1; })[0];\n    $input.placeholder = text[match];\n  }\n}\n\nfunction updateNoData(text, path) {\n  if (typeof text === 'string') {\n    NO_DATA_TEXT = text;\n  } else {\n    var match = Object.keys(text).filter(function (key) { return path.indexOf(key) > -1; })[0];\n    NO_DATA_TEXT = text[match];\n  }\n}\n\nfunction updateOptions(opts) {\n  options = opts;\n}\n\nfunction init(opts, vm) {\n  var keywords = vm.router.parse().query.s;\n\n  updateOptions(opts);\n  style();\n  tpl(keywords);\n  bindEvents();\n  keywords && setTimeout(function (_) { return doSearch(keywords); }, 500);\n}\n\nfunction update(opts, vm) {\n  updateOptions(opts);\n  updatePlaceholder(opts.placeholder, vm.route.path);\n  updateNoData(opts.noData, vm.route.path);\n}\n\nvar CONFIG = {\n  placeholder: 'Type to search',\n  noData: 'No Results!',\n  paths: 'auto',\n  depth: 2,\n  maxAge: 86400000, // 1 day\n  hideOtherSidebarContent: false\n};\n\nvar install = function (hook, vm) {\n  var util = Docsify.util;\n  var opts = vm.config.search || CONFIG;\n\n  if (Array.isArray(opts)) {\n    CONFIG.paths = opts;\n  } else if (typeof opts === 'object') {\n    CONFIG.paths = Array.isArray(opts.paths) ? opts.paths : 'auto';\n    CONFIG.maxAge = util.isPrimitive(opts.maxAge) ? opts.maxAge : CONFIG.maxAge;\n    CONFIG.placeholder = opts.placeholder || CONFIG.placeholder;\n    CONFIG.noData = opts.noData || CONFIG.noData;\n    CONFIG.depth = opts.depth || CONFIG.depth;\n    CONFIG.hideOtherSidebarContent = opts.hideOtherSidebarContent || CONFIG.hideOtherSidebarContent;\n  }\n\n  var isAuto = CONFIG.paths === 'auto';\n\n  hook.mounted(function (_) {\n    init(CONFIG, vm);\n    !isAuto && init$1(CONFIG, vm);\n  });\n  hook.doneEach(function (_) {\n    update(CONFIG, vm);\n    isAuto && init$1(CONFIG, vm);\n  });\n};\n\n$docsify.plugins = [].concat(install, $docsify.plugins);\n\n}());\n"
  },
  {
    "path": "notes/docsify/unpkg/docsify-themeable/dist/css/theme-defaults.css",
    "content": "﻿.github-corner{position:absolute;z-index:40;top:0;right:0;border-bottom:0;text-decoration:none}.github-corner svg{height:70px;width:70px;fill:var(--theme-color);color:var(--base-background-color)}.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}.progress{position:fixed;z-index:60;top:0;left:0;right:0;height:3px;width:0;background-color:var(--theme-color);transition:width var(--duration-fast),opacity calc(var(--duration-fast) * 2)}body.ready-transition:after,body.ready-transition>*:not(.progress){opacity:0;transition:opacity var(--spinner-transition-duration)}body.ready-transition:after{content:'';position:absolute;z-index:1000;top:calc(50% - (var(--spinner-size) / 2));left:calc(50% - (var(--spinner-size) / 2));height:var(--spinner-size);width:var(--spinner-size);border:var(--spinner-track-width, 0) solid var(--spinner-track-color);border-left-color:var(--theme-color);border-left-color:var(--theme-color);border-radius:50%;animation:spinner var(--duration-slow) infinite linear}body.ready-transition.ready-spinner:after{opacity:1}body.ready-transition.ready-fix:after{opacity:0}body.ready-transition.ready-fix>*:not(.progress){opacity:1;transition-delay:var(--spinner-transition-duration)}@keyframes spinner{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}*,*:before,*:after{box-sizing:inherit;font-size:inherit;-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:none;-webkit-touch-callout:none}html{box-sizing:border-box;background-color:var(--base-background-color);font-family:var(--base-font-family);font-size:var(--base-font-size);font-weight:var(--base-font-weight);letter-spacing:var(--base-letter-spacing);line-height:var(--base-line-height);color:var(--base-color);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased}::selection{background:var(--selection-color)}a{text-decoration:none;text-decoration-skip:ink;text-decoration-skip-ink:auto}body{margin:0}hr{height:0;margin:2em 0;border:none;border-bottom:var(--hr-border, 0)}img{border:0}main{display:block}main.hidden{display:none}mark{background:var(--mark-background);color:var(--mark-color)}pre{font-family:var(--pre-font-family);font-size:var(--pre-font-size);font-weight:var(--pre-font-weight);line-height:var(--pre-line-height)}small{display:inline-block;font-size:var(--small-font-size)}strong{font-weight:var(--strong-font-weight);color:var(--strong-color, currentColor)}sub,sup{font-size:var(--subsup-font-size);line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}.emoji{height:var(--emoji-size);vertical-align:middle}.task-list-item{list-style:none}.task-list-item input{margin-right:0.5em;margin-left:0;vertical-align:0.075em}.markdown-section code[class*=\"lang-\"],.markdown-section pre[data-lang]{font-family:var(--code-font-family);font-size:var(--code-font-size);font-weight:var(--code-font-weight);letter-spacing:normal;line-height:var(--code-block-line-height);tab-size:var(--code-tab-size);text-align:left;white-space:pre;word-spacing:normal;word-wrap:normal;word-break:normal;-ms-hyphens:none;hyphens:none}.markdown-section pre[data-lang]{position:relative;overflow:hidden;margin:var(--code-block-margin);padding:0;border-radius:var(--code-block-border-radius)}.markdown-section pre[data-lang]::after{content:attr(data-lang);position:absolute;top:0.75em;right:0.75em;opacity:0.6;color:inherit;font-size:var(--font-size-s);line-height:1}.markdown-section pre[data-lang] code{display:block;overflow:auto;padding:var(--code-block-padding)}code[class*=\"lang-\"],pre[data-lang]{color:var(--code-theme-text)}pre[data-lang]::selection,pre[data-lang] ::selection,code[class*=\"lang-\"]::selection,code[class*=\"lang-\"] ::selection{background:var(--code-theme-selection, var(--selection-color))}:not(pre)>code[class*=\"lang-\"],pre[data-lang]{background:var(--code-theme-background)}.namespace{opacity:0.7}.token.comment,.token.prolog,.token.doctype,.token.cdata{color:var(--code-theme-comment)}.token.punctuation{color:var(--code-theme-punctuation)}.token.property,.token.tag,.token.boolean,.token.number,.token.constant,.token.symbol,.token.deleted{color:var(--code-theme-tag)}.token.selector,.token.attr-name,.token.string,.token.char,.token.builtin,.token.inserted{color:var(--code-theme-selector)}.token.operator,.token.entity,.token.url,.language-css .token.string,.style .token.string{color:var(--code-theme-operator)}.token.atrule,.token.attr-value,.token.keyword{color:var(--code-theme-keyword)}.token.function{color:var(--code-theme-function)}.token.regex,.token.important,.token.variable{color:var(--code-theme-variable)}.token.important,.token.bold{font-weight:bold}.token.italic{font-style:italic}.token.entity{cursor:help}.markdown-section{position:relative;max-width:var(--content-max-width);margin:0 auto;padding:2rem 45px}.app-nav:not(:empty) ~ main .markdown-section{padding-top:3.5rem}.markdown-section figure,.markdown-section p,.markdown-section ol,.markdown-section ul{margin:1em 0}.markdown-section ol,.markdown-section ul{padding-left:1.5rem}.markdown-section ol ol,.markdown-section ol ul,.markdown-section ul ol,.markdown-section ul ul{margin-top:0.15rem;margin-bottom:0.15rem}.markdown-section a{border-bottom:var(--link-border-bottom);color:var(--link-color);text-decoration:var(--link-text-decoration);text-decoration-color:var(--link-text-decoration-color)}.markdown-section a:hover{border-bottom:var(--link-border-bottom--hover, var(--link-border-bottom, 0));color:var(--link-color--hover, var(--link-color));text-decoration:var(--link-text-decoration--hover, var(--link-text-decoration));text-decoration-color:var(--link-text-decoration-color--hover, var(--link-text-decoration-color))}.markdown-section a.anchor{border-bottom:0;color:inherit;text-decoration:none}.markdown-section a.anchor:hover{text-decoration:underline}.markdown-section blockquote{overflow:visible;margin:2em 0;padding:1.5em;border-width:var(--blockquote-border-width, 0);border-style:var(--blockquote-border-style);border-color:var(--blockquote-border-color);border-radius:var(--blockquote-border-radius);background:var(--blockquote-background);color:var(--blockquote-color);font-family:var(--blockquote-font-family);font-size:var(--blockquote-font-size);font-style:var(--blockquote-font-style);font-weight:var(--blockquote-font-weight);quotes:\"“\" \"”\" \"‘\" \"’\"}.markdown-section blockquote em{font-family:var(--blockquote-em-font-family);font-size:var(--blockquote-em-font-size);font-style:var(--blockquote-em-font-style);font-weight:var(--blockquote-em-font-weight)}.markdown-section blockquote p:first-child{margin-top:0}.markdown-section blockquote p:first-child:before,.markdown-section blockquote p:first-child:after{color:var(--blockquote-quotes-color);font-family:var(--blockquote-quotes-font-family);font-size:var(--blockquote-quotes-font-size);line-height:0}.markdown-section blockquote p:first-child:before{content:var(--blockquote-quotes-open);margin-right:0.15em;vertical-align:-0.45em}.markdown-section blockquote p:first-child:after{content:var(--blockquote-quotes-close);margin-left:0.15em;vertical-align:-0.55em}.markdown-section blockquote p:last-child{margin-bottom:0}.markdown-section code{font-family:var(--code-font-family);font-size:var(--code-font-size);font-weight:var(--code-font-weight);line-height:inherit}.markdown-section code:not([class*=\"lang-\"]):not([class*=\"language-\"]){margin:var(--code-inline-margin);padding:var(--code-inline-padding);border-radius:var(--code-inline-border-radius);background:var(--code-inline-background);color:var(--code-inline-color, currentColor);white-space:nowrap}.markdown-section h1:first-child,.markdown-section h2:first-child,.markdown-section h3:first-child,.markdown-section h4:first-child,.markdown-section h5:first-child,.markdown-section h6:first-child{margin-top:0}.markdown-section h1+h2,.markdown-section h1+h3,.markdown-section h1+h4,.markdown-section h1+h5,.markdown-section h1+h6,.markdown-section h2+h3,.markdown-section h2+h4,.markdown-section h2+h5,.markdown-section h2+h6,.markdown-section h3+h4,.markdown-section h3+h5,.markdown-section h3+h6,.markdown-section h4+h5,.markdown-section h4+h6,.markdown-section h5+h6{margin-top:1rem}.markdown-section h1{margin:var(--heading-h1-margin, var(--heading-margin));padding:var(--heading-h1-padding, var(--heading-padding));border-width:var(--heading-h1-border-width, 0);border-style:var(--heading-h1-border-style);border-color:var(--heading-h1-border-color);font-family:var(--heading-h1-font-family, var(--heading-font-family));font-size:var(--heading-h1-font-size);font-weight:var(--heading-h1-font-weight, var(--heading-font-weight));line-height:var(--base-line-height);color:var(--heading-h1-color, var(--heading-color))}.markdown-section h2{margin:var(--heading-h2-margin, var(--heading-margin));padding:var(--heading-h2-padding, var(--heading-padding));border-width:var(--heading-h2-border-width, 0);border-style:var(--heading-h2-border-style);border-color:var(--heading-h2-border-color);font-family:var(--heading-h2-font-family, var(--heading-font-family));font-size:var(--heading-h2-font-size);font-weight:var(--heading-h2-font-weight, var(--heading-font-weight));line-height:var(--base-line-height);color:var(--heading-h2-color, var(--heading-color))}.markdown-section h3{margin:var(--heading-h3-margin, var(--heading-margin));padding:var(--heading-h3-padding, var(--heading-padding));border-width:var(--heading-h3-border-width, 0);border-style:var(--heading-h3-border-style);border-color:var(--heading-h3-border-color);font-family:var(--heading-h3-font-family, var(--heading-font-family));font-size:var(--heading-h3-font-size);font-weight:var(--heading-h3-font-weight, var(--heading-font-weight));color:var(--heading-h3-color, var(--heading-color))}.markdown-section h4{margin:var(--heading-h4-margin, var(--heading-margin));padding:var(--heading-h4-padding, var(--heading-padding));border-width:var(--heading-h4-border-width, 0);border-style:var(--heading-h4-border-style);border-color:var(--heading-h4-border-color);font-family:var(--heading-h4-font-family, var(--heading-font-family));font-size:var(--heading-h4-font-size);font-weight:var(--heading-h4-font-weight, var(--heading-font-weight));color:var(--heading-h4-color, var(--heading-color))}.markdown-section h5{margin:var(--heading-h5-margin, var(--heading-margin));padding:var(--heading-h5-padding, var(--heading-padding));border-width:var(--heading-h5-border-width, 0);border-style:var(--heading-h5-border-style);border-color:var(--heading-h5-border-color);font-family:var(--heading-h5-font-family, var(--heading-font-family));font-weight:var(--heading-h5-font-weight, var(--heading-font-weight));color:var(--heading-h5-color, var(--heading-color))}.markdown-section h5,.markdown-section h5+p,.markdown-section h5+p+p{font-size:var(--heading-h5-font-size)}.markdown-section h6{margin:var(--heading-h6-margin, var(--heading-margin));padding:var(--heading-h6-padding, var(--heading-padding));border-width:var(--heading-h6-border-width, 0);border-style:var(--heading-h6-border-style);border-color:var(--heading-h6-border-color);font-family:var(--heading-h6-font-family, var(--heading-font-family));font-weight:var(--heading-h6-font-weight, var(--heading-font-weight))}.markdown-section h6,.markdown-section h6+p,.markdown-section h6+p+p{font-size:var(--heading-h6-font-size);color:var(--heading-h6-color, var(--heading-color))}.markdown-section iframe{margin:1em 0}.markdown-section img{max-width:100%}.markdown-section kbd{display:inline-block;min-width:var(--kbd-min-width);margin:var(--kbd-margin);padding:var(--kbd-padding);border:var(--kbd-border);border-radius:var(--kbd-border-radius);background:var(--kbd-background);font-family:inherit;font-size:var(--kbd-font-size);text-align:center;letter-spacing:0;line-height:1;color:var(--kbd-color)}.markdown-section kbd+kbd{margin-left:-0.15em}.markdown-section table{display:block;overflow:auto;margin:1rem 0;border-spacing:0;border-collapse:collapse}.markdown-section th,.markdown-section td{padding:var(--table-cell-padding)}.markdown-section th:not([align]){text-align:left}.markdown-section thead{border-color:var(--table-head-border-color);border-style:solid;border-width:var(--table-head-border-width, 0);background:var(--table-head-background)}.markdown-section th{font-weight:var(--table-head-font-weight);color:var(--strong-color)}.markdown-section td{border-color:var(--table-cell-border-color);border-style:solid;border-width:var(--table-cell-border-width, 0)}.markdown-section tbody{border-color:var(--table-body-border-color);border-style:solid;border-width:var(--table-body-border-width, 0)}.markdown-section tbody tr:nth-child(odd){background:var(--table-row-odd-background)}.markdown-section tbody tr:nth-child(even){background:var(--table-row-even-background)}.markdown-section>ul .task-list-item{margin-left:-1.25em}.markdown-section>ul .task-list-item .task-list-item{margin-left:0}.markdown-section .table-wrapper table{display:table;width:100%}.markdown-section .table-wrapper td::before{display:none}@media (max-width: 30em){.markdown-section .table-wrapper tbody,.markdown-section .table-wrapper tr,.markdown-section .table-wrapper td{display:block}.markdown-section .table-wrapper th,.markdown-section .table-wrapper td{border:none}.markdown-section .table-wrapper thead{display:none}.markdown-section .table-wrapper tr{border-color:var(--table-cell-border-color);border-style:solid;border-width:var(--table-cell-border-width, 0);padding:var(--table-cell-padding)}.markdown-section .table-wrapper tr:not(:last-child){border-bottom:0}.markdown-section .table-wrapper td{display:-ms-flexbox;display:flex;padding:0.15em 0}.markdown-section .table-wrapper td::before{display:block;min-width:8em;max-width:8em;font-weight:bold;text-align:left}}.markdown-section .tip,.markdown-section .warn{position:relative;margin:2em 0;padding:var(--notice-padding);border-width:var(--notice-border-width, 0);border-style:var(--notice-border-style);border-color:var(--notice-border-color);border-radius:var(--notice-border-radius);background:var(--notice-background);font-family:var(--notice-font-family);font-weight:var(--notice-font-weight);color:var(--notice-color)}.markdown-section .tip:before,.markdown-section .warn:before{display:inline-block;position:var(--notice-before-position, relative);top:var(--notice-before-top);left:var(--notice-before-left);height:var(--notice-before-height);width:var(--notice-before-width);margin:var(--notice-before-margin);padding:var(--notice-before-padding);border-radius:var(--notice-before-border-radius);line-height:var(--notice-before-line-height);font-family:var(--notice-before-font-family);font-size:var(--notice-before-font-size);font-weight:var(--notice-before-font-weight);text-align:center}.markdown-section .tip{border-width:var(--notice-important-border-width, var(--notice-border-width, 0));border-style:var(--notice-important-border-style, var(--notice-border-style));border-color:var(--notice-important-border-color, var(--notice-border-color));background:var(--notice-important-background, var(--notice-background));color:var(--notice-important-color, var(--notice-color))}.markdown-section .tip:before{content:var(--notice-important-before-content, var(--notice-before-content));background:var(--notice-important-before-background, var(--notice-before-background));color:var(--notice-important-before-color, var(--notice-before-color))}.markdown-section .warn{border-width:var(--notice-tip-border-width, var(--notice-border-width, 0));border-style:var(--notice-tip-border-style, var(--notice-border-style));border-color:var(--notice-tip-border-color, var(--notice-border-color));background:var(--notice-tip-background, var(--notice-background));color:var(--notice-tip-color, var(--notice-color))}.markdown-section .warn:before{content:var(--notice-tip-before-content, var(--notice-before-content));background:var(--notice-tip-before-background, var(--notice-before-background));color:var(--notice-tip-before-color, var(--notice-before-color))}.cover{display:none;position:relative;z-index:20;min-height:100vh;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;padding:calc(var(--cover-border-inset, 0px) + var(--cover-border-width, 0px));color:var(--cover-color);text-align:var(--cover-text-align)}@media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none){.cover{height:100vh}}.cover:before,.cover:after{content:'';position:absolute}.cover:before{top:0;bottom:0;left:0;right:0;background-blend-mode:var(--cover-background-blend-mode);background-color:var(--cover-background-color);background-image:var(--cover-background-image);background-position:var(--cover-background-position);background-repeat:var(--cover-background-repeat);background-size:var(--cover-background-size)}.cover:after{top:var(--cover-border-inset, 0);bottom:var(--cover-border-inset, 0);left:var(--cover-border-inset, 0);right:var(--cover-border-inset, 0);border-width:var(--cover-border-width, 0);border-style:solid;border-color:var(--cover-border-color)}.cover a{border-bottom:var(--cover-link-border-bottom);color:var(--cover-link-color);text-decoration:var(--cover-link-text-decoration);text-decoration-color:var(--cover-link-text-decoration-color)}.cover a:hover{border-bottom:var(--cover-link-border-bottom--hover, var(--cover-link-border-bottom));color:var(--cover-link-color--hover, var(--cover-link-color));text-decoration:var(--cover-link-text-decoration--hover, var(--cover-link-text-decoration));text-decoration-color:var(--cover-link-text-decoration-color--hover, var(--cover-link-text-decoration-color))}.cover h1{color:var(--cover-heading-color);position:relative;margin:0;font-size:var(--cover-heading-font-size);font-weight:var(--cover-heading-font-weight);line-height:1.2}.cover h1 a,.cover h1 a:hover{display:block;border-bottom:none;color:inherit;text-decoration:none}.cover h1 small{position:absolute;bottom:0;margin-left:0.5em}.cover h1 span{font-size:calc(var(--cover-heading-font-size-min) * 1px)}@media (min-width: 26em){.cover h1 span{font-size:calc((var(--cover-heading-font-size-min) * 1px) + (var(--cover-heading-font-size-max) - var(--cover-heading-font-size-min)) * ((100vw - 420px) / (1024 - 420)))}}@media (min-width: 64em){.cover h1 span{font-size:calc(var(--cover-heading-font-size-max) * 1px)}}.cover blockquote{margin:0;color:var(--cover-blockquote-color);font-size:var(--cover-blockquote-font-size)}.cover blockquote a{color:inherit}.cover ul{padding:0;list-style-type:none}.cover .cover-main{position:relative;z-index:1;max-width:var(--cover-max-width);margin:var(--cover-margin);padding:0 45px}.cover .cover-main>p:last-child{margin:1.25em -.25em}.cover .cover-main>p:last-child a{display:block;margin:.375em .25em;padding:var(--cover-button-padding);border:var(--cover-button-border);border-radius:var(--cover-button-border-radius);box-shadow:var(--cover-button-box-shadow);background:var(--cover-button-background);text-align:center;text-decoration:var(--cover-button-text-decoration);text-decoration-color:var(--cover-button-text-decoration-color);color:var(--cover-button-color);white-space:nowrap;transition:var(--cover-button-transition)}.cover .cover-main>p:last-child a:hover{border:var(--cover-button-border--hover, var(--cover-button-border));box-shadow:var(--cover-button-box-shadow--hover, var(--cover-button-box-shadow));background:var(--cover-button-background--hover, var(--cover-button-background));text-decoration:var(--cover-button-text-decoration--hover, var(--cover-button-text-decoration));text-decoration-color:var(--cover-button-text-decoration-color--hover, var(--cover-button-text-decoration-color));color:var(--cover-button-color--hover, var(--cover-button-color))}.cover .cover-main>p:last-child a:first-child{border:var(--cover-button-primary-border, var(--cover-button-border));box-shadow:var(--cover-button-primary-box-shadow, var(--cover-button-box-shadow));background:var(--cover-button-primary-background, var(--cover-button-background));text-decoration:var(--cover-button-primary-text-decoration, var(--cover-button-text-decoration));text-decoration-color:var(--cover-button-primary-text-decoration-color, var(--cover-button-text-decoration-color));color:var(--cover-button-primary-color, var(--cover-button-color))}.cover .cover-main>p:last-child a:first-child:hover{border:var(--cover-button-primary-border--hover, var(--cover-button-border--hover, var(--cover-button-primary-border, var(--cover-button-border))));box-shadow:var(--cover-button-primary-box-shadow--hover, var(--cover-button-box-shadow--hover, var(--cover-button-primary-box-shadow, var(--cover-button-box-shadow))));background:var(--cover-button-primary-background--hover, var(--cover-button-background--hover, var(--cover-button-primary-background, var(--cover-button-background))));text-decoration:var(--cover-button-primary-text-decoration--hover, var(--cover-button-text-decoration--hover, var(--cover-button-primary-text-decoration, var(--cover-button-text-decoration))));text-decoration-color:var(--cover-button-primary-text-decoration-color--hover, var(--cover-button-text-decoration-color--hover, var(--cover-button-primary-text-decoration-color, var(--cover-button-text-decoration-color))));color:var(--cover-button-primary-color--hover, var(--cover-button-color--hover, var(--cover-button-primary-color, var(--cover-button-color))))}@media (min-width: 30.01em){.cover .cover-main>p:last-child a{display:inline-block}}.cover .mask{visibility:var(--cover-background-mask-visibility, hidden);position:absolute;top:0;bottom:0;left:0;right:0;background-color:var(--cover-background-mask-color);opacity:var(--cover-background-mask-opacity)}.cover.has-mask .mask{visibility:visible}.cover.show{display:-ms-flexbox;display:flex}.app-nav{position:absolute;z-index:30;top:calc(35px - (0.5em * var(--base-line-height)));left:45px;right:80px;text-align:right}.app-nav.no-badge{right:45px}.app-nav li>img,.app-nav li>a>img{margin-top:-0.25em;vertical-align:middle}.app-nav li>img:first-child,.app-nav li>a>img:first-child{margin-right:0.5em}.app-nav a{display:block;line-height:1;transition:var(--navbar-root-transition)}.app-nav ul,.app-nav li{margin:0;padding:0;list-style:none}.app-nav li{position:relative}.app-nav>ul>li{display:inline-block;margin:var(--navbar-root-margin)}.app-nav>ul>li:first-child{margin-left:0}.app-nav>ul>li:last-child{margin-right:0}.app-nav>ul>li>a,.app-nav>ul>li>span{padding:var(--navbar-root-padding);border-width:var(--navbar-root-border-width, 0);border-style:var(--navbar-root-border-style);border-color:var(--navbar-root-border-color);border-radius:var(--navbar-root-border-radius);background:var(--navbar-root-background);color:var(--navbar-root-color);text-decoration:var(--navbar-root-text-decoration);text-decoration-color:var(--navbar-root-text-decoration-color)}.app-nav>ul>li>a:hover,.app-nav>ul>li>span:hover{background:var(--navbar-root-background--hover, var(--navbar-root-background));border-style:var(--navbar-root-border-style--hover, var(--navbar-root-border-style));border-color:var(--navbar-root-border-color--hover, var(--navbar-root-border-color));color:var(--navbar-root-color--hover, var(--navbar-root-color));text-decoration:var(--navbar-root-text-decoration--hover, var(--navbar-root-text-decoration));text-decoration-color:var(--navbar-root-text-decoration-color--hover, var(--navbar-root-text-decoration-color))}.app-nav>ul>li>a:not(:last-child),.app-nav>ul>li>span:not(:last-child){padding:var(--navbar-menu-root-padding, var(--navbar-root-padding));background:var(--navbar-menu-root-background, var(--navbar-root-background))}.app-nav>ul>li>a:not(:last-child):hover,.app-nav>ul>li>span:not(:last-child):hover{background:var(--navbar-menu-root-background--hover, var(--navbar-menu-root-background, var(--navbar-root-background--hover, var(--navbar-root-background))))}.app-nav>ul>li>a.active{background:var(--navbar-root-background--active, var(--navbar-root-background));border-style:var(--navbar-root-border-style--active, var(--navbar-root-border-style));border-color:var(--navbar-root-border-color--active, var(--navbar-root-border-color));color:var(--navbar-root-color--active, var(--navbar-root-color));text-decoration:var(--navbar-root-text-decoration--active, var(--navbar-root-text-decoration));text-decoration-color:var(--navbar-root-text-decoration-color--active, var(--navbar-root-text-decoration-color))}.app-nav>ul>li>a.active:not(:last-child):hover{background:var(--navbar-menu-root-background--active, var(--navbar-menu-root-background, var(--navbar-root-background--active, var(--navbar-root-background))))}.app-nav>ul>li ul{visibility:hidden;position:absolute;top:100%;right:50%;overflow-y:auto;box-sizing:border-box;max-height:calc(50vh);padding:var(--navbar-menu-padding);border-width:var(--navbar-menu-border-width, 0);border-style:solid;border-color:var(--navbar-menu-border-color);border-radius:var(--navbar-menu-border-radius);background:var(--navbar-menu-background);box-shadow:var(--navbar-menu-box-shadow);text-align:left;white-space:nowrap;opacity:0;transform:translate(50%, -0.35em);transition:var(--navbar-menu-transition)}.app-nav>ul>li ul li{white-space:nowrap}.app-nav>ul>li ul a{margin:var(--navbar-menu-link-margin);padding:var(--navbar-menu-link-padding);border-width:var(--navbar-menu-link-border-width, 0);border-style:var(--navbar-menu-link-border-style);border-color:var(--navbar-menu-link-border-color);border-radius:var(--navbar-menu-link-border-radius);background:var(--navbar-menu-link-background);color:var(--navbar-menu-link-color);text-decoration:var(--navbar-menu-link-text-decoration);text-decoration-color:var(--navbar-menu-link-text-decoration-color)}.app-nav>ul>li ul a:hover{background:var(--navbar-menu-link-background--hover, var(--navbar-menu-link-background));border-style:var(--navbar-menu-link-border-style--hover, var(--navbar-menu-link-border-style));border-color:var(--navbar-menu-link-border-color--hover, var(--navbar-menu-link-border-color));color:var(--navbar-menu-link-color--hover, var(--navbar-menu-link-color));text-decoration:var(--navbar-menu-link-text-decoration--hover, var(--navbar-menu-link-text-decoration));text-decoration-color:var(--navbar-menu-link-text-decoration-color--hover, var(--navbar-menu-link-text-decoration-color))}.app-nav>ul>li ul a.active{background:var(--navbar-menu-link-background--active, var(--navbar-menu-link-background));border-style:var(--navbar-menu-link-border-style--active, var(--navbar-menu-link-border-style));border-color:var(--navbar-menu-link-border-color--active, var(--navbar-menu-link-border-color));color:var(--navbar-menu-link-color--active, var(--navbar-menu-link-color));text-decoration:var(--navbar-menu-link-text-decoration--active, var(--navbar-menu-link-text-decoration));text-decoration-color:var(--navbar-menu-link-text-decoration-color--active, var(--navbar-menu-link-text-decoration-color))}.app-nav>ul>li:hover ul,.app-nav>ul>li:focus ul,.app-nav>ul>li.focus-within ul{visibility:visible;opacity:1;transform:translate(50%, 0)}.sidebar,.sidebar-toggle,main>.content{transition:all var(--sidebar-transition-duration) ease-out}@media (min-width: 48em){nav.app-nav{margin-left:var(--sidebar-width)}}main{position:relative;overflow-x:hidden;min-height:100vh}@media (min-width: 48em){main>.content{margin-left:var(--sidebar-width)}}.sidebar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;position:fixed;z-index:10;top:0;right:100%;overflow-y:auto;height:100vh;width:var(--sidebar-width);padding:var(--sidebar-padding);border-width:var(--sidebar-border-width);border-style:solid;border-color:var(--sidebar-border-color);background:var(--sidebar-background)}.sidebar>h1{margin:0;margin:var(--sidebar-name-margin);padding:var(--sidebar-name-padding);background:var(--sidebar-name-background);color:var(--sidebar-name-color);font-family:var(--sidebar-name-font-family);font-size:var(--sidebar-name-font-size);font-weight:var(--sidebar-name-font-weight);text-align:var(--sidebar-name-text-align)}.sidebar>h1 img{max-width:100%}.sidebar>h1 .app-name-link{color:var(--sidebar-name-color)}@media (min-width: 48em){.sidebar{position:absolute;transform:translateX(var(--sidebar-width))}}@media print{.sidebar{display:none}}.sidebar-nav,.sidebar nav{-ms-flex-order:1;order:1;margin:var(--sidebar-nav-margin);padding:var(--sidebar-nav-padding);background:var(--sidebar-nav-background)}.sidebar-nav ul,.sidebar nav ul{margin:0;padding:0;list-style:none}.sidebar-nav ul ul,.sidebar nav ul ul{margin-left:var(--sidebar-nav-indent)}.sidebar-nav a,.sidebar nav a{display:block;overflow:hidden;margin:var(--sidebar-nav-link-margin);padding:var(--sidebar-nav-link-padding);border-width:var(--sidebar-nav-link-border-width, 0);border-style:var(--sidebar-nav-link-border-style);border-color:var(--sidebar-nav-link-border-color);border-radius:var(--sidebar-nav-link-border-radius);background-color:var(--sidebar-nav-link-background-color);background-image:var(--sidebar-nav-link-background-image);background-position:var(--sidebar-nav-link-background-position);background-repeat:var(--sidebar-nav-link-background-repeat);background-size:var(--sidebar-nav-link-background-size);color:var(--sidebar-nav-link-color);font-weight:var(--sidebar-nav-link-font-weight);white-space:nowrap;text-decoration:var(--sidebar-nav-link-text-decoration);text-decoration-color:var(--sidebar-nav-link-text-decoration-color);text-overflow:ellipsis;transition:var(--sidebar-nav-link-transition)}.sidebar-nav a img,.sidebar nav a img{margin-top:-0.25em;vertical-align:middle}.sidebar-nav a img:first-child,.sidebar nav a img:first-child{margin-right:0.5em}.sidebar-nav a:hover,.sidebar nav a:hover{border-width:var(--sidebar-nav-link-border-width--hover, var(--sidebar-nav-link-border-width, 0));border-style:var(--sidebar-nav-link-border-style--hover, var(--sidebar-nav-link-border-style));border-color:var(--sidebar-nav-link-border-color--hover, var(--sidebar-nav-link-border-color));background-color:var(--sidebar-nav-link-background-color--hover, var(--sidebar-nav-link-background-color));background-image:var(--sidebar-nav-link-background-image--hover, var(--sidebar-nav-link-background-image));background-position:var(--sidebar-nav-link-background-position--hover, var(--sidebar-nav-link-background-position));background-size:var(--sidebar-nav-link-background-size--hover, var(--sidebar-nav-link-background-size));color:var(--sidebar-nav-link-color--hover, var(--sidebar-nav-link-color));font-weight:var(--sidebar-nav-link-font-weight--hover, var(--sidebar-nav-link-font-weight));text-decoration:var(--sidebar-nav-link-text-decoration--hover, var(--sidebar-nav-link-text-decoration));text-decoration-color:var(--sidebar-nav-link-text-decoration-color)}.sidebar-nav ul>li>span,.sidebar-nav ul>li>strong,.sidebar nav ul>li>span,.sidebar nav ul>li>strong{display:block;margin:var(--sidebar-nav-strong-margin);padding:var(--sidebar-nav-strong-padding);border-width:var(--sidebar-nav-strong-border-width, 0);border-style:solid;border-color:var(--sidebar-nav-strong-border-color);color:var(--sidebar-nav-strong-color);font-size:var(--sidebar-nav-strong-font-size);font-weight:var(--sidebar-nav-strong-font-weight);text-transform:var(--sidebar-nav-strong-text-transform)}.sidebar-nav ul>li>span+ul,.sidebar-nav ul>li>strong+ul,.sidebar nav ul>li>span+ul,.sidebar nav ul>li>strong+ul{margin-left:0}.sidebar-nav ul>li:first-child>span,.sidebar-nav ul>li:first-child>strong,.sidebar nav ul>li:first-child>span,.sidebar nav ul>li:first-child>strong{margin-top:0}.sidebar-nav::-webkit-scrollbar,.sidebar nav::-webkit-scrollbar{width:0}@supports (width: env(safe-area-inset)){@media only screen and (orientation: landscape){.sidebar-nav,.sidebar nav{margin-left:calc(env(safe-area-inset-left) / 2)}}}.sidebar-nav li>a:before,.sidebar-nav li>strong:before{display:inline-block}.sidebar-nav li>a{background-repeat:var(--sidebar-nav-pagelink-background-repeat);background-size:var(--sidebar-nav-pagelink-background-size)}.sidebar-nav li>a[href^=\"#/\"]:not([href*=\"?id=\"]){transition:var(--sidebar-nav-pagelink-transition)}.sidebar-nav li>a[href^=\"#/\"]:not([href*=\"?id=\"]),.sidebar-nav li>a[href^=\"#/\"]:not([href*=\"?id=\"]) ~ ul a{padding:var(--sidebar-nav-pagelink-padding, var(--sidebar-nav-link-padding))}.sidebar-nav li>a[href^=\"#/\"]:not([href*=\"?id=\"]):only-child{background-image:var(--sidebar-nav-pagelink-background-image);background-position:var(--sidebar-nav-pagelink-background-position)}.sidebar-nav li>a[href^=\"#/\"]:not([href*=\"?id=\"]):not(:only-child){background-image:var(--sidebar-nav-pagelink-background-image--loaded, var(--sidebar-nav-pagelink-background-image));background-position:var(--sidebar-nav-pagelink-background-position--loaded, var(--sidebar-nav-pagelink-background-image))}.sidebar-nav li.active>a,.sidebar-nav li.collapse>a{border-width:var(--sidebar-nav-link-border-width--active, var(--sidebar-nav-link-border-width));border-style:var(--sidebar-nav-link-border-style--active, var(--sidebar-nav-link-border-style));border-color:var(--sidebar-nav-link-border-color--active, var(--sidebar-nav-link-border-color));background-color:var(--sidebar-nav-link-background-color--active, var(--sidebar-nav-link-background-color));background-image:var(--sidebar-nav-link-background-image--active, var(--sidebar-nav-link-background-image));background-position:var(--sidebar-nav-link-background-position--active, var(--sidebar-nav-link-background-position));background-size:var(--sidebar-nav-link-background-size--active, var(--sidebar-nav-link-background-size));color:var(--sidebar-nav-link-color--active, var(--sidebar-nav-link-color));font-weight:var(--sidebar-nav-link-font-weight--active, var(--sidebar-nav-link-font-weight));text-decoration:var(--sidebar-nav-link-text-decoration--active, var(--sidebar-nav-link-text-decoration));text-decoration-color:var(--sidebar-nav-link-text-decoration-color)}.sidebar-nav li.active>a[href^=\"#/\"]:not([href*=\"?id=\"]):not(:only-child){background-image:var(--sidebar-nav-pagelink-background-image--active, var(--sidebar-nav-pagelink-background-image--loaded, var(--sidebar-nav-pagelink-background-image)));background-position:var(--sidebar-nav-pagelink-background-position--active, var(--sidebar-nav-pagelink-background-position--loaded, var(--sidebar-nav-pagelink-background-image)))}.sidebar-nav li.collapse>a[href^=\"#/\"]:not([href*=\"?id=\"]):not(:only-child){background-image:var(--sidebar-nav-pagelink-background-image--collapse, var(--sidebar-nav-pagelink-background-image--loaded, var(--sidebar-nav-pagelink-background-image)));background-position:var(--sidebar-nav-pagelink-background-position--collapse, var(--sidebar-nav-pagelink-background-position--loaded, var(--sidebar-nav-pagelink-background-image)))}.sidebar-nav li.collapse .app-sub-sidebar{display:none}.sidebar-nav>ul>li>a:before{content:var(--sidebar-nav-link-before-content-l1, var(--sidebar-nav-link-before-content));margin:var(--sidebar-nav-link-before-margin-l1, var(--sidebar-nav-link-before-margin));color:var(--sidebar-nav-link-before-color-l1, var(--sidebar-nav-link-before-color))}.sidebar-nav>ul>li.active>a:before{content:var(--sidebar-nav-link-before-content-l1--active, var(--sidebar-nav-link-before-content--active, var(--sidebar-nav-link-before-content-l1, var(--sidebar-nav-link-before-content))));color:var(--sidebar-nav-link-before-color-l1--active, var(--sidebar-nav-link-before-color--active, var(--sidebar-nav-link-before-color-l1, var(--sidebar-nav-link-before-color))))}.sidebar-nav>ul>li>ul>li>a:before{content:var(--sidebar-nav-link-before-content-l2, var(--sidebar-nav-link-before-content));margin:var(--sidebar-nav-link-before-margin-l2, var(--sidebar-nav-link-before-margin));color:var(--sidebar-nav-link-before-color-l2, var(--sidebar-nav-link-before-color))}.sidebar-nav>ul>li>ul>li.active>a:before{content:var(--sidebar-nav-link-before-content-l2--active, var(--sidebar-nav-link-before-content--active, var(--sidebar-nav-link-before-content-l2, var(--sidebar-nav-link-before-content))));color:var(--sidebar-nav-link-before-color-l2--active, var(--sidebar-nav-link-before-color--active, var(--sidebar-nav-link-before-color-l2, var(--sidebar-nav-link-before-color))))}.sidebar-nav>ul>li>ul>li>ul>li>a:before{content:var(--sidebar-nav-link-before-content-l3, var(--sidebar-nav-link-before-content));margin:var(--sidebar-nav-link-before-margin-l3, var(--sidebar-nav-link-before-margin));color:var(--sidebar-nav-link-before-color-l3, var(--sidebar-nav-link-before-color))}.sidebar-nav>ul>li>ul>li>ul>li.active>a:before{content:var(--sidebar-nav-link-before-content-l3--active, var(--sidebar-nav-link-before-content--active, var(--sidebar-nav-link-before-content-l3, var(--sidebar-nav-link-before-content))));color:var(--sidebar-nav-link-before-color-l3--active, var(--sidebar-nav-link-before-color--active, var(--sidebar-nav-link-before-color-l3, var(--sidebar-nav-link-before-color))))}.sidebar-nav>ul>li>ul>li>ul>li>ul>li>a:before{content:var(--sidebar-nav-link-before-content-l4, var(--sidebar-nav-link-before-content));margin:var(--sidebar-nav-link-before-margin-l4, var(--sidebar-nav-link-before-margin));color:var(--sidebar-nav-link-before-color-l4, var(--sidebar-nav-link-before-color))}.sidebar-nav>ul>li>ul>li>ul>li>ul>li.active>a:before{content:var(--sidebar-nav-link-before-content-l4--active, var(--sidebar-nav-link-before-content--active, var(--sidebar-nav-link-before-content-l4, var(--sidebar-nav-link-before-content))));color:var(--sidebar-nav-link-before-color-l4--active, var(--sidebar-nav-link-before-color--active, var(--sidebar-nav-link-before-color-l4, var(--sidebar-nav-link-before-color))))}.sidebar-nav>:last-child{margin-bottom:2rem}.sidebar-toggle,.sidebar-toggle-button{outline:none}.sidebar-toggle{position:fixed;z-index:11;top:0;bottom:0;left:0;width:40px;margin:0;padding:0;border:0;background:transparent;appearance:none;cursor:pointer}.sidebar-toggle .sidebar-toggle-button{position:absolute;top:var(--sidebar-toggle-offset-top);left:var(--sidebar-toggle-offset-left);height:var(--sidebar-toggle-height);width:var(--sidebar-toggle-width);border-radius:var(--sidebar-toggle-border-radius);border-width:var(--sidebar-toggle-border-width);border-style:var(--sidebar-toggle-border-style);border-color:var(--sidebar-toggle-border-color);background:var(--sidebar-toggle-background, transparent);color:var(--sidebar-toggle-icon-color)}.sidebar-toggle span{position:absolute;top:calc(50% - (var(--sidebar-toggle-icon-stroke-width) / 2));left:calc(50% - (var(--sidebar-toggle-icon-width) / 2));height:var(--sidebar-toggle-icon-stroke-width);width:var(--sidebar-toggle-icon-width);background-color:currentColor}.sidebar-toggle span:nth-child(1){margin-top:calc(0px - (var(--sidebar-toggle-icon-height) / 2))}.sidebar-toggle span:nth-child(3){margin-top:calc((var(--sidebar-toggle-icon-height) / 2))}@media (min-width: 48em){.sidebar-toggle{position:absolute;overflow:visible;left:0;transform:translateX(var(--sidebar-width))}}@media print{.sidebar-toggle{display:none}}@media (max-width: 47.99em){body.close .sidebar,body.close .sidebar-toggle,body.close main>.content{transform:translateX(var(--sidebar-width))}}@media (min-width: 48em){body.close main>.content{transform:translateX(0)}}@media (max-width: 47.99em){body.close nav.app-nav,body.close .github-corner{display:none}}@media (min-width: 48em){body.close .sidebar,body.close .sidebar-toggle{transform:translateX(0)}}@media (min-width: 48em){body.close nav.app-nav{margin-left:0}}@media (max-width: 47.99em){body.close .sidebar-toggle{width:100%}body.close .sidebar-toggle span{margin-top:0}body.close .sidebar-toggle span:nth-child(1){transform:rotate(45deg)}body.close .sidebar-toggle span:nth-child(2){display:none}body.close .sidebar-toggle span:nth-child(3){transform:rotate(-45deg)}}@media (min-width: 48em){body.close main>.content{margin-left:0}}@media (min-width: 48em){body.sticky .sidebar,body.sticky .sidebar-toggle{position:fixed}}body .docsify-copy-code-button,body .docsify-copy-code-button::after{background:var(--copycode-background);color:var(--copycode-color)}body .docsify-pagination-container{border-top:var(--pagination-border-top);color:var(--pagination-color)}body .pagination-item-label{font-size:var(--pagination-label-font-size)}body .pagination-item-label svg{color:var(--pagination-label-color);height:var(--pagination-chevron-height);stroke:var(--pagination-chevron-stroke);stroke-linecap:var(--pagination-chevron-stroke-linecap);stroke-linejoin:var(--pagination-chevron-stroke-linecap);stroke-width:var(--pagination-chevron-stroke-width)}body .pagination-item-title{color:var(--pagination-title-color);font-size:var(--pagination-title-font-size)}body .app-name.hide{display:block}body .sidebar{padding:var(--sidebar-padding)}.sidebar .search{margin:0;padding:0;border:0}.sidebar .search input{padding:0;line-height:1;font-size:inherit}.sidebar .search .clear-button{width:auto}.sidebar .search .clear-button svg{transform:scale(1)}.sidebar .search .matching-post{border:none}.sidebar .search p{font-size:inherit}.sidebar .search{-ms-flex-order:var(--search-flex-order);order:var(--search-flex-order);margin:var(--search-margin);padding:var(--search-padding);background:var(--search-background)}.sidebar .search a{color:inherit}.sidebar .search h2{margin:var(--search-result-heading-margin);font-size:var(--search-result-heading-font-size);font-weight:var(--search-result-heading-font-weight);color:var(--search-result-heading-color)}.sidebar .search .input-wrap{margin:var(--search-input-margin);background-color:var(--search-input-background-color);border-width:var(--search-input-border-width, 0);border-style:solid;border-color:var(--search-input-border-color);border-radius:var(--search-input-border-radius)}.sidebar .search input[type=\"search\"]{min-width:0;padding:var(--search-input-padding);border:none;background-color:transparent;background-image:var(--search-input-background-image);background-position:var(--search-input-background-position);background-repeat:var(--search-input-background-repeat);background-size:var(--search-input-background-size);font-size:var(--search-input-font-size);color:var(--search-input-color);transition:var(--search-input-transition)}.sidebar .search input[type=\"search\"]::-ms-clear{display:none}.sidebar .search input[type=\"search\"]:-ms-input-placeholder{color:var(--search-input-placeholder-color, gray)}.sidebar .search input[type=\"search\"]::placeholder{color:var(--search-input-placeholder-color, gray)}.sidebar .search input[type=\"search\"]::-webkit-input-placeholder{line-height:normal}.sidebar .search input[type=\"search\"]:focus{background-color:var(--search-input-background-color--focus, var(--search-input-background-color));background-image:var(--search-input-background-image--focus, var(--search-input-background-image));background-position:var(--search-input-background-position--focus, var(--search-input-background-position));background-size:var(--search-input-background-size--focus, var(--search-input-background-size))}@supports (width: env(safe-area-inset)){@media only screen and (orientation: landscape){.sidebar .search input[type=\"search\"]{margin-left:calc(env(safe-area-inset-left) / 2)}}}.sidebar .search p{overflow:hidden;text-overflow:ellipsis;-webkit-line-clamp:2}.sidebar .search p:empty{text-align:center}.sidebar .search .clear-button{margin:0 15px 0 0;padding:0;border:none;line-height:1;background:transparent;cursor:pointer}.sidebar .search .clear-button svg circle{fill:var(--search-clear-icon-color1, gray)}.sidebar .search .clear-button svg path{stroke:var(--search-clear-icon-color2, #fff)}.sidebar .search.show ~ *:not(h1){display:none}.sidebar .search .results-panel{display:none;font-size:var(--search-result-item-font-size)}.sidebar .search .results-panel.show{display:block}.sidebar .search .matching-post{margin:var(--search-result-item-margin);padding:var(--search-result-item-padding)}.sidebar .search .matching-post,.sidebar .search .matching-post:last-child{border-width:var(--search-result-item-border-width, 0) !important;border-style:var(--search-result-item-border-style);border-color:var(--search-result-item-border-color)}.sidebar .search .matching-post p{margin:0}.sidebar .search .search-keyword{margin:var(--search-result-keyword-margin);padding:var(--search-result-keyword-padding);border-radius:var(--search-result-keyword-border-radius);background-color:var(--search-result-keyword-background);color:var(--search-result-keyword-color, currentColor);font-style:normal;font-weight:var(--search-result-keyword-font-weight)}.medium-zoom-overlay,.medium-zoom-image--open{z-index:50 !important}.medium-zoom-overlay{background:var(--zoomimage-overlay-background) !important}:root{--mono-hue: 113;--mono-saturation: 0%;--mono-shade3: hsl(var(--mono-hue), var(--mono-saturation), 20%);--mono-shade2: hsl(var(--mono-hue), var(--mono-saturation), 30%);--mono-shade1: hsl(var(--mono-hue), var(--mono-saturation), 40%);--mono-base: hsl(var(--mono-hue), var(--mono-saturation), 50%);--mono-tint1: hsl(var(--mono-hue), var(--mono-saturation), 70%);--mono-tint2: hsl(var(--mono-hue), var(--mono-saturation), 89%);--mono-tint3: hsl(var(--mono-hue), var(--mono-saturation), 97%);--theme-hue: 204;--theme-saturation: 90%;--theme-lightness: 45%;--theme-color: hsl(var(--theme-hue), var(--theme-saturation), var(--theme-lightness));--modular-scale: 1.333;--modular-scale--2: calc(var(--modular-scale-2) / var(--modular-scale));--modular-scale--1: calc(var(--modular-scale-1) / var(--modular-scale));--modular-scale-1: 1rem;--modular-scale-2: calc(var(--modular-scale-1) * var(--modular-scale));--modular-scale-3: calc(var(--modular-scale-2) * var(--modular-scale));--modular-scale-4: calc(var(--modular-scale-3) * var(--modular-scale));--modular-scale-5: calc(var(--modular-scale-4) * var(--modular-scale));--font-size-xxxl: var(--modular-scale-5);--font-size-xxl: var(--modular-scale-4);--font-size-xl: var(--modular-scale-3);--font-size-l: var(--modular-scale-2);--font-size-m: var(--modular-scale-1);--font-size-s: var(--modular-scale--1);--font-size-xs: var(--modular-scale--2);--duration-slow: 1s;--duration-medium: 0.5s;--duration-fast: 0.25s;--spinner-size: 60px;--spinner-track-width: 4px;--spinner-track-color: rgba(0, 0, 0, 0.15);--spinner-transition-duration: var(--duration-medium)}:root{--base-background-color: #fff;--base-color: var(--mono-shade2);--base-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";--base-font-size: 16px;--base-line-height: 1.7;--emoji-size: calc(var(--base-line-height) * 1em);--hr-border: 1px solid var(--mono-tint2);--mark-background: #ffecb3;--pre-font-family: var(--code-font-family);--pre-font-size: var(--code-font-size);--selection-color: #b4d5fe;--small-font-size: var(--font-size-s);--strong-color: var(--heading-color);--strong-font-weight: 600;--subsup-font-size: var(--font-size-s)}:root{--content-max-width: 55em;--blockquote-background: var(--mono-tint3);--blockquote-border-style: solid;--blockquote-border-radius: var(--border-radius-m);--code-font-family: Inconsolata, Consolas, Menlo, Monaco, \"Andale Mono WT\", \"Andale Mono\", \"Lucida Console\", \"DejaVu Sans Mono\", \"Bitstream Vera Sans Mono\", \"Courier New\", Courier, monospace;--code-font-size: calc(var(--font-size-m) * 0.95);--code-tab-size: 4;--code-block-border-radius: var(--border-radius-m);--code-block-line-height: var(--base-line-height);--code-block-margin: 1em 0;--code-block-padding: 1.75em 1.5em 1.5em 1.5em;--code-inline-background: var(--code-theme-background);--code-inline-border-radius: var(--border-radius-s);--code-inline-color: var(--code-theme-text);--code-inline-margin: 0 0.15em;--code-inline-padding: 0.125em 0.4em;--code-theme-background: var(--mono-tint3);--heading-color: var(--mono-shade3);--heading-margin: 2.5rem 0 0;--heading-h1-border-style: solid;--heading-h1-font-size: var(--font-size-xxl);--heading-h2-border-style: solid;--heading-h2-font-size: var(--font-size-xl);--heading-h3-border-style: solid;--heading-h3-font-size: var(--font-size-l);--heading-h4-border-style: solid;--heading-h4-font-size: var(--font-size-m);--heading-h5-border-style: solid;--heading-h5-font-size: var(--font-size-s);--heading-h6-border-style: solid;--heading-h6-color: var(--mono-base);--heading-h6-font-size: var(--font-size-s);--kbd-background: var(--mono-tint3);--kbd-border-radius: var(--border-radius-m);--kbd-margin: 0 0.3em;--kbd-min-width: 2.5em;--kbd-padding: 0.65em 0.5em;--link-text-decoration: underline;--notice-background: var(--mono-tint3);--notice-border-radius: var(--border-radius-m);--notice-border-style: solid;--notice-padding: 1em 1.5em;--table-cell-padding: 0.75em 0.5em;--table-head-border-color: var(--table-cell-border-color);--table-head-font-weight: var(--strong-font-weight);--table-row-odd-background: var(--mono-tint3)}:root{--cover-margin: 0 auto;--cover-max-width: 40em;--cover-text-align: center;--cover-background-color: var(--base-background-color);--cover-background-mask-color: var(--base-background-color);--cover-background-mask-opacity: 0.8;--cover-background-position: center center;--cover-background-repeat: no-repeat;--cover-background-size: cover;--cover-blockquote-font-size: var(--font-size-l);--cover-border-color: var(--theme-color);--cover-button-border: 1px solid var(--theme-color);--cover-button-border-radius: var(--border-radius-m);--cover-button-color: var(--theme-color);--cover-button-padding: 0.5em 2rem;--cover-button-text-decoration: none;--cover-button-transition: all var(--duration-fast) ease-in-out;--cover-button-primary-background: var(--theme-color);--cover-button-primary-border: 1px solid var(--theme-color);--cover-button-primary-color: #fff;--cover-heading-color: var(--theme-color);--cover-heading-font-size: var(--font-size-xxl);--cover-link-text-decoration: underline }:root{--navbar-root-border-style: solid;--navbar-root-margin: 0 0 0 1.5em;--navbar-root-transition: all var(--duration-fast);--navbar-menu-background: var(--base-background-color);--navbar-menu-border-radius: var(--border-radius-m);--navbar-menu-box-shadow: rgba(45,45,45,0.05) 0px 0px 1px, rgba(49,49,49,0.05) 0px 1px 2px, rgba(42,42,42,0.05) 0px 2px 4px, rgba(32,32,32,0.05) 0px 4px 8px, rgba(49,49,49,0.05) 0px 8px 16px, rgba(35,35,35,0.05) 0px 16px 32px;--navbar-menu-padding: 0.5em;--navbar-menu-transition: all var(--duration-fast);--navbar-menu-link-border-style: solid;--navbar-menu-link-margin: 0.75em 0.5em;--navbar-menu-link-padding: 0.2em 0 }:root{--copycode-background: #808080;--copycode-color: #fff}:root{--docsifytabs-border-color: var(--mono-tint2);--docsifytabs-border-radius-px: var(--border-radius-s);--docsifytabs-tab-background: var(--mono-tint3);--docsifytabs-tab-color: var(--mono-tint1)}:root{--pagination-border-top: 1px solid var(--mono-tint2);--pagination-chevron-height: 0.8em;--pagination-chevron-stroke: currentColor;--pagination-chevron-stroke-linecap: round;--pagination-chevron-stroke-width: 1px;--pagination-label-font-size: var(--font-size-s);--pagination-title-font-size: var(--font-size-l)}:root{--search-margin: 1.5rem 0 0;--search-input-background-repeat: no-repeat;--search-input-border-color: var(--mono-tint1);--search-input-border-width: 1px;--search-input-padding: 0.5em;--search-flex-order: 1;--search-result-heading-color: var(--heading-color);--search-result-heading-font-size: var(--base-font-size);--search-result-heading-font-weight: normal;--search-result-heading-margin: 0 0 0.25em;--search-result-item-border-color: var(--mono-tint2);--search-result-item-border-style: solid;--search-result-item-border-width: 0 0 1px 0;--search-result-item-padding: 1em 0;--search-result-keyword-background: var(--mark-background);--search-result-keyword-border-radius: var(--border-radius-s);--search-result-keyword-color: var(--mark-color);--search-result-keyword-margin: 0 0.1em;--search-result-keyword-padding: 0.2em 0}:root{--zoomimage-overlay-background: var(--base-background-color)}:root{--sidebar-background: var(--base-background-color);--sidebar-border-width: 0;--sidebar-padding: 0 25px;--sidebar-transition-duration: var(--duration-fast);--sidebar-width: 17rem;--sidebar-name-font-size: var(--font-size-l);--sidebar-name-margin: 1.5rem 0 0;--sidebar-name-text-align: center;--sidebar-nav-strong-border-color: var(--sidebar-border-color);--sidebar-nav-strong-color: var(--heading-color);--sidebar-nav-strong-font-weight: var(--strong-font-weight);--sidebar-nav-strong-margin: 1.5em 0 0.5em;--sidebar-nav-strong-padding: 0.25em 0;--sidebar-nav-indent: 1em;--sidebar-nav-margin: 1.5rem 0 0;--sidebar-nav-link-border-style: solid;--sidebar-nav-link-border-width: 0;--sidebar-nav-link-color: var(--base-color);--sidebar-nav-link-padding: 0.25em 0;--sidebar-nav-link-text-decoration--active: underline;--sidebar-nav-link-text-decoration--hover: underline;--sidebar-nav-link-before-margin: 0 0.35em 0 0;--sidebar-nav-pagelink-background-repeat: no-repeat;--sidebar-nav-pagelink-transition: var(--sidebar-nav-link-transition);--sidebar-toggle-border-radius: var(--border-radius-s);--sidebar-toggle-border-style: solid;--sidebar-toggle-border-width: 0;--sidebar-toggle-height: 36px;--sidebar-toggle-icon-color: var(--base-color);--sidebar-toggle-icon-height: 10px;--sidebar-toggle-icon-stroke-width: 1px;--sidebar-toggle-icon-width: 16px;--sidebar-toggle-offset-left: 0;--sidebar-toggle-offset-top: calc(35px - (var(--sidebar-toggle-height) / 2));--sidebar-toggle-width: 44px}\n/*# sourceMappingURL=theme-defaults.css.map */"
  },
  {
    "path": "notes/docsify/unpkg/docsify-themeable/dist/css/theme-simple-dark.css",
    "content": "﻿.github-corner{position:absolute;z-index:40;top:0;right:0;border-bottom:0;text-decoration:none}.github-corner svg{height:70px;width:70px;fill:var(--theme-color);color:var(--base-background-color)}.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}.progress{position:fixed;z-index:60;top:0;left:0;right:0;height:3px;width:0;background-color:var(--theme-color);transition:width var(--duration-fast),opacity calc(var(--duration-fast) * 2)}body.ready-transition:after,body.ready-transition>*:not(.progress){opacity:0;transition:opacity var(--spinner-transition-duration)}body.ready-transition:after{content:'';position:absolute;z-index:1000;top:calc(50% - (var(--spinner-size) / 2));left:calc(50% - (var(--spinner-size) / 2));height:var(--spinner-size);width:var(--spinner-size);border:var(--spinner-track-width, 0) solid var(--spinner-track-color);border-left-color:var(--theme-color);border-left-color:var(--theme-color);border-radius:50%;animation:spinner var(--duration-slow) infinite linear}body.ready-transition.ready-spinner:after{opacity:1}body.ready-transition.ready-fix:after{opacity:0}body.ready-transition.ready-fix>*:not(.progress){opacity:1;transition-delay:var(--spinner-transition-duration)}@keyframes spinner{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}*,*:before,*:after{box-sizing:inherit;font-size:inherit;-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:none;-webkit-touch-callout:none}html{box-sizing:border-box;background-color:var(--base-background-color);font-family:var(--base-font-family);font-size:var(--base-font-size);font-weight:var(--base-font-weight);letter-spacing:var(--base-letter-spacing);line-height:var(--base-line-height);color:var(--base-color);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased}::selection{background:var(--selection-color)}a{text-decoration:none;text-decoration-skip:ink;text-decoration-skip-ink:auto}body{margin:0}hr{height:0;margin:2em 0;border:none;border-bottom:var(--hr-border, 0)}img{border:0}main{display:block}main.hidden{display:none}mark{background:var(--mark-background);color:var(--mark-color)}pre{font-family:var(--pre-font-family);font-size:var(--pre-font-size);font-weight:var(--pre-font-weight);line-height:var(--pre-line-height)}small{display:inline-block;font-size:var(--small-font-size)}strong{font-weight:var(--strong-font-weight);color:var(--strong-color, currentColor)}sub,sup{font-size:var(--subsup-font-size);line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}.emoji{height:var(--emoji-size);vertical-align:middle}.task-list-item{list-style:none}.task-list-item input{margin-right:0.5em;margin-left:0;vertical-align:0.075em}.markdown-section code[class*=\"lang-\"],.markdown-section pre[data-lang]{font-family:var(--code-font-family);font-size:var(--code-font-size);font-weight:var(--code-font-weight);letter-spacing:normal;line-height:var(--code-block-line-height);tab-size:var(--code-tab-size);text-align:left;white-space:pre;word-spacing:normal;word-wrap:normal;word-break:normal;-ms-hyphens:none;hyphens:none}.markdown-section pre[data-lang]{position:relative;overflow:hidden;margin:var(--code-block-margin);padding:0;border-radius:var(--code-block-border-radius)}.markdown-section pre[data-lang]::after{content:attr(data-lang);position:absolute;top:0.75em;right:0.75em;opacity:0.6;color:inherit;font-size:var(--font-size-s);line-height:1}.markdown-section pre[data-lang] code{display:block;overflow:auto;padding:var(--code-block-padding)}code[class*=\"lang-\"],pre[data-lang]{color:var(--code-theme-text)}pre[data-lang]::selection,pre[data-lang] ::selection,code[class*=\"lang-\"]::selection,code[class*=\"lang-\"] ::selection{background:var(--code-theme-selection, var(--selection-color))}:not(pre)>code[class*=\"lang-\"],pre[data-lang]{background:var(--code-theme-background)}.namespace{opacity:0.7}.token.comment,.token.prolog,.token.doctype,.token.cdata{color:var(--code-theme-comment)}.token.punctuation{color:var(--code-theme-punctuation)}.token.property,.token.tag,.token.boolean,.token.number,.token.constant,.token.symbol,.token.deleted{color:var(--code-theme-tag)}.token.selector,.token.attr-name,.token.string,.token.char,.token.builtin,.token.inserted{color:var(--code-theme-selector)}.token.operator,.token.entity,.token.url,.language-css .token.string,.style .token.string{color:var(--code-theme-operator)}.token.atrule,.token.attr-value,.token.keyword{color:var(--code-theme-keyword)}.token.function{color:var(--code-theme-function)}.token.regex,.token.important,.token.variable{color:var(--code-theme-variable)}.token.important,.token.bold{font-weight:bold}.token.italic{font-style:italic}.token.entity{cursor:help}.markdown-section{position:relative;max-width:var(--content-max-width);margin:0 auto;padding:2rem 45px}.app-nav:not(:empty) ~ main .markdown-section{padding-top:3.5rem}.markdown-section figure,.markdown-section p,.markdown-section ol,.markdown-section ul{margin:1em 0}.markdown-section ol,.markdown-section ul{padding-left:1.5rem}.markdown-section ol ol,.markdown-section ol ul,.markdown-section ul ol,.markdown-section ul ul{margin-top:0.15rem;margin-bottom:0.15rem}.markdown-section a{border-bottom:var(--link-border-bottom);color:var(--link-color);text-decoration:var(--link-text-decoration);text-decoration-color:var(--link-text-decoration-color)}.markdown-section a:hover{border-bottom:var(--link-border-bottom--hover, var(--link-border-bottom, 0));color:var(--link-color--hover, var(--link-color));text-decoration:var(--link-text-decoration--hover, var(--link-text-decoration));text-decoration-color:var(--link-text-decoration-color--hover, var(--link-text-decoration-color))}.markdown-section a.anchor{border-bottom:0;color:inherit;text-decoration:none}.markdown-section a.anchor:hover{text-decoration:underline}.markdown-section blockquote{overflow:visible;margin:2em 0;padding:1.5em;border-width:var(--blockquote-border-width, 0);border-style:var(--blockquote-border-style);border-color:var(--blockquote-border-color);border-radius:var(--blockquote-border-radius);background:var(--blockquote-background);color:var(--blockquote-color);font-family:var(--blockquote-font-family);font-size:var(--blockquote-font-size);font-style:var(--blockquote-font-style);font-weight:var(--blockquote-font-weight);quotes:\"“\" \"”\" \"‘\" \"’\"}.markdown-section blockquote em{font-family:var(--blockquote-em-font-family);font-size:var(--blockquote-em-font-size);font-style:var(--blockquote-em-font-style);font-weight:var(--blockquote-em-font-weight)}.markdown-section blockquote p:first-child{margin-top:0}.markdown-section blockquote p:first-child:before,.markdown-section blockquote p:first-child:after{color:var(--blockquote-quotes-color);font-family:var(--blockquote-quotes-font-family);font-size:var(--blockquote-quotes-font-size);line-height:0}.markdown-section blockquote p:first-child:before{content:var(--blockquote-quotes-open);margin-right:0.15em;vertical-align:-0.45em}.markdown-section blockquote p:first-child:after{content:var(--blockquote-quotes-close);margin-left:0.15em;vertical-align:-0.55em}.markdown-section blockquote p:last-child{margin-bottom:0}.markdown-section code{font-family:var(--code-font-family);font-size:var(--code-font-size);font-weight:var(--code-font-weight);line-height:inherit}.markdown-section code:not([class*=\"lang-\"]):not([class*=\"language-\"]){margin:var(--code-inline-margin);padding:var(--code-inline-padding);border-radius:var(--code-inline-border-radius);background:var(--code-inline-background);color:var(--code-inline-color, currentColor);white-space:nowrap}.markdown-section h1:first-child,.markdown-section h2:first-child,.markdown-section h3:first-child,.markdown-section h4:first-child,.markdown-section h5:first-child,.markdown-section h6:first-child{margin-top:0}.markdown-section h1+h2,.markdown-section h1+h3,.markdown-section h1+h4,.markdown-section h1+h5,.markdown-section h1+h6,.markdown-section h2+h3,.markdown-section h2+h4,.markdown-section h2+h5,.markdown-section h2+h6,.markdown-section h3+h4,.markdown-section h3+h5,.markdown-section h3+h6,.markdown-section h4+h5,.markdown-section h4+h6,.markdown-section h5+h6{margin-top:1rem}.markdown-section h1{margin:var(--heading-h1-margin, var(--heading-margin));padding:var(--heading-h1-padding, var(--heading-padding));border-width:var(--heading-h1-border-width, 0);border-style:var(--heading-h1-border-style);border-color:var(--heading-h1-border-color);font-family:var(--heading-h1-font-family, var(--heading-font-family));font-size:var(--heading-h1-font-size);font-weight:var(--heading-h1-font-weight, var(--heading-font-weight));line-height:var(--base-line-height);color:var(--heading-h1-color, var(--heading-color))}.markdown-section h2{margin:var(--heading-h2-margin, var(--heading-margin));padding:var(--heading-h2-padding, var(--heading-padding));border-width:var(--heading-h2-border-width, 0);border-style:var(--heading-h2-border-style);border-color:var(--heading-h2-border-color);font-family:var(--heading-h2-font-family, var(--heading-font-family));font-size:var(--heading-h2-font-size);font-weight:var(--heading-h2-font-weight, var(--heading-font-weight));line-height:var(--base-line-height);color:var(--heading-h2-color, var(--heading-color))}.markdown-section h3{margin:var(--heading-h3-margin, var(--heading-margin));padding:var(--heading-h3-padding, var(--heading-padding));border-width:var(--heading-h3-border-width, 0);border-style:var(--heading-h3-border-style);border-color:var(--heading-h3-border-color);font-family:var(--heading-h3-font-family, var(--heading-font-family));font-size:var(--heading-h3-font-size);font-weight:var(--heading-h3-font-weight, var(--heading-font-weight));color:var(--heading-h3-color, var(--heading-color))}.markdown-section h4{margin:var(--heading-h4-margin, var(--heading-margin));padding:var(--heading-h4-padding, var(--heading-padding));border-width:var(--heading-h4-border-width, 0);border-style:var(--heading-h4-border-style);border-color:var(--heading-h4-border-color);font-family:var(--heading-h4-font-family, var(--heading-font-family));font-size:var(--heading-h4-font-size);font-weight:var(--heading-h4-font-weight, var(--heading-font-weight));color:var(--heading-h4-color, var(--heading-color))}.markdown-section h5{margin:var(--heading-h5-margin, var(--heading-margin));padding:var(--heading-h5-padding, var(--heading-padding));border-width:var(--heading-h5-border-width, 0);border-style:var(--heading-h5-border-style);border-color:var(--heading-h5-border-color);font-family:var(--heading-h5-font-family, var(--heading-font-family));font-weight:var(--heading-h5-font-weight, var(--heading-font-weight));color:var(--heading-h5-color, var(--heading-color))}.markdown-section h5,.markdown-section h5+p,.markdown-section h5+p+p{font-size:var(--heading-h5-font-size)}.markdown-section h6{margin:var(--heading-h6-margin, var(--heading-margin));padding:var(--heading-h6-padding, var(--heading-padding));border-width:var(--heading-h6-border-width, 0);border-style:var(--heading-h6-border-style);border-color:var(--heading-h6-border-color);font-family:var(--heading-h6-font-family, var(--heading-font-family));font-weight:var(--heading-h6-font-weight, var(--heading-font-weight))}.markdown-section h6,.markdown-section h6+p,.markdown-section h6+p+p{font-size:var(--heading-h6-font-size);color:var(--heading-h6-color, var(--heading-color))}.markdown-section iframe{margin:1em 0}.markdown-section img{max-width:100%}.markdown-section kbd{display:inline-block;min-width:var(--kbd-min-width);margin:var(--kbd-margin);padding:var(--kbd-padding);border:var(--kbd-border);border-radius:var(--kbd-border-radius);background:var(--kbd-background);font-family:inherit;font-size:var(--kbd-font-size);text-align:center;letter-spacing:0;line-height:1;color:var(--kbd-color)}.markdown-section kbd+kbd{margin-left:-0.15em}.markdown-section table{display:block;overflow:auto;margin:1rem 0;border-spacing:0;border-collapse:collapse}.markdown-section th,.markdown-section td{padding:var(--table-cell-padding)}.markdown-section th:not([align]){text-align:left}.markdown-section thead{border-color:var(--table-head-border-color);border-style:solid;border-width:var(--table-head-border-width, 0);background:var(--table-head-background)}.markdown-section th{font-weight:var(--table-head-font-weight);color:var(--strong-color)}.markdown-section td{border-color:var(--table-cell-border-color);border-style:solid;border-width:var(--table-cell-border-width, 0)}.markdown-section tbody{border-color:var(--table-body-border-color);border-style:solid;border-width:var(--table-body-border-width, 0)}.markdown-section tbody tr:nth-child(odd){background:var(--table-row-odd-background)}.markdown-section tbody tr:nth-child(even){background:var(--table-row-even-background)}.markdown-section>ul .task-list-item{margin-left:-1.25em}.markdown-section>ul .task-list-item .task-list-item{margin-left:0}.markdown-section .table-wrapper table{display:table;width:100%}.markdown-section .table-wrapper td::before{display:none}@media (max-width: 30em){.markdown-section .table-wrapper tbody,.markdown-section .table-wrapper tr,.markdown-section .table-wrapper td{display:block}.markdown-section .table-wrapper th,.markdown-section .table-wrapper td{border:none}.markdown-section .table-wrapper thead{display:none}.markdown-section .table-wrapper tr{border-color:var(--table-cell-border-color);border-style:solid;border-width:var(--table-cell-border-width, 0);padding:var(--table-cell-padding)}.markdown-section .table-wrapper tr:not(:last-child){border-bottom:0}.markdown-section .table-wrapper td{display:-ms-flexbox;display:flex;padding:0.15em 0}.markdown-section .table-wrapper td::before{display:block;min-width:8em;max-width:8em;font-weight:bold;text-align:left}}.markdown-section .tip,.markdown-section .warn{position:relative;margin:2em 0;padding:var(--notice-padding);border-width:var(--notice-border-width, 0);border-style:var(--notice-border-style);border-color:var(--notice-border-color);border-radius:var(--notice-border-radius);background:var(--notice-background);font-family:var(--notice-font-family);font-weight:var(--notice-font-weight);color:var(--notice-color)}.markdown-section .tip:before,.markdown-section .warn:before{display:inline-block;position:var(--notice-before-position, relative);top:var(--notice-before-top);left:var(--notice-before-left);height:var(--notice-before-height);width:var(--notice-before-width);margin:var(--notice-before-margin);padding:var(--notice-before-padding);border-radius:var(--notice-before-border-radius);line-height:var(--notice-before-line-height);font-family:var(--notice-before-font-family);font-size:var(--notice-before-font-size);font-weight:var(--notice-before-font-weight);text-align:center}.markdown-section .tip{border-width:var(--notice-important-border-width, var(--notice-border-width, 0));border-style:var(--notice-important-border-style, var(--notice-border-style));border-color:var(--notice-important-border-color, var(--notice-border-color));background:var(--notice-important-background, var(--notice-background));color:var(--notice-important-color, var(--notice-color))}.markdown-section .tip:before{content:var(--notice-important-before-content, var(--notice-before-content));background:var(--notice-important-before-background, var(--notice-before-background));color:var(--notice-important-before-color, var(--notice-before-color))}.markdown-section .warn{border-width:var(--notice-tip-border-width, var(--notice-border-width, 0));border-style:var(--notice-tip-border-style, var(--notice-border-style));border-color:var(--notice-tip-border-color, var(--notice-border-color));background:var(--notice-tip-background, var(--notice-background));color:var(--notice-tip-color, var(--notice-color))}.markdown-section .warn:before{content:var(--notice-tip-before-content, var(--notice-before-content));background:var(--notice-tip-before-background, var(--notice-before-background));color:var(--notice-tip-before-color, var(--notice-before-color))}.cover{display:none;position:relative;z-index:20;min-height:100vh;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;padding:calc(var(--cover-border-inset, 0px) + var(--cover-border-width, 0px));color:var(--cover-color);text-align:var(--cover-text-align)}@media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none){.cover{height:100vh}}.cover:before,.cover:after{content:'';position:absolute}.cover:before{top:0;bottom:0;left:0;right:0;background-blend-mode:var(--cover-background-blend-mode);background-color:var(--cover-background-color);background-image:var(--cover-background-image);background-position:var(--cover-background-position);background-repeat:var(--cover-background-repeat);background-size:var(--cover-background-size)}.cover:after{top:var(--cover-border-inset, 0);bottom:var(--cover-border-inset, 0);left:var(--cover-border-inset, 0);right:var(--cover-border-inset, 0);border-width:var(--cover-border-width, 0);border-style:solid;border-color:var(--cover-border-color)}.cover a{border-bottom:var(--cover-link-border-bottom);color:var(--cover-link-color);text-decoration:var(--cover-link-text-decoration);text-decoration-color:var(--cover-link-text-decoration-color)}.cover a:hover{border-bottom:var(--cover-link-border-bottom--hover, var(--cover-link-border-bottom));color:var(--cover-link-color--hover, var(--cover-link-color));text-decoration:var(--cover-link-text-decoration--hover, var(--cover-link-text-decoration));text-decoration-color:var(--cover-link-text-decoration-color--hover, var(--cover-link-text-decoration-color))}.cover h1{color:var(--cover-heading-color);position:relative;margin:0;font-size:var(--cover-heading-font-size);font-weight:var(--cover-heading-font-weight);line-height:1.2}.cover h1 a,.cover h1 a:hover{display:block;border-bottom:none;color:inherit;text-decoration:none}.cover h1 small{position:absolute;bottom:0;margin-left:0.5em}.cover h1 span{font-size:calc(var(--cover-heading-font-size-min) * 1px)}@media (min-width: 26em){.cover h1 span{font-size:calc((var(--cover-heading-font-size-min) * 1px) + (var(--cover-heading-font-size-max) - var(--cover-heading-font-size-min)) * ((100vw - 420px) / (1024 - 420)))}}@media (min-width: 64em){.cover h1 span{font-size:calc(var(--cover-heading-font-size-max) * 1px)}}.cover blockquote{margin:0;color:var(--cover-blockquote-color);font-size:var(--cover-blockquote-font-size)}.cover blockquote a{color:inherit}.cover ul{padding:0;list-style-type:none}.cover .cover-main{position:relative;z-index:1;max-width:var(--cover-max-width);margin:var(--cover-margin);padding:0 45px}.cover .cover-main>p:last-child{margin:1.25em -.25em}.cover .cover-main>p:last-child a{display:block;margin:.375em .25em;padding:var(--cover-button-padding);border:var(--cover-button-border);border-radius:var(--cover-button-border-radius);box-shadow:var(--cover-button-box-shadow);background:var(--cover-button-background);text-align:center;text-decoration:var(--cover-button-text-decoration);text-decoration-color:var(--cover-button-text-decoration-color);color:var(--cover-button-color);white-space:nowrap;transition:var(--cover-button-transition)}.cover .cover-main>p:last-child a:hover{border:var(--cover-button-border--hover, var(--cover-button-border));box-shadow:var(--cover-button-box-shadow--hover, var(--cover-button-box-shadow));background:var(--cover-button-background--hover, var(--cover-button-background));text-decoration:var(--cover-button-text-decoration--hover, var(--cover-button-text-decoration));text-decoration-color:var(--cover-button-text-decoration-color--hover, var(--cover-button-text-decoration-color));color:var(--cover-button-color--hover, var(--cover-button-color))}.cover .cover-main>p:last-child a:first-child{border:var(--cover-button-primary-border, var(--cover-button-border));box-shadow:var(--cover-button-primary-box-shadow, var(--cover-button-box-shadow));background:var(--cover-button-primary-background, var(--cover-button-background));text-decoration:var(--cover-button-primary-text-decoration, var(--cover-button-text-decoration));text-decoration-color:var(--cover-button-primary-text-decoration-color, var(--cover-button-text-decoration-color));color:var(--cover-button-primary-color, var(--cover-button-color))}.cover .cover-main>p:last-child a:first-child:hover{border:var(--cover-button-primary-border--hover, var(--cover-button-border--hover, var(--cover-button-primary-border, var(--cover-button-border))));box-shadow:var(--cover-button-primary-box-shadow--hover, var(--cover-button-box-shadow--hover, var(--cover-button-primary-box-shadow, var(--cover-button-box-shadow))));background:var(--cover-button-primary-background--hover, var(--cover-button-background--hover, var(--cover-button-primary-background, var(--cover-button-background))));text-decoration:var(--cover-button-primary-text-decoration--hover, var(--cover-button-text-decoration--hover, var(--cover-button-primary-text-decoration, var(--cover-button-text-decoration))));text-decoration-color:var(--cover-button-primary-text-decoration-color--hover, var(--cover-button-text-decoration-color--hover, var(--cover-button-primary-text-decoration-color, var(--cover-button-text-decoration-color))));color:var(--cover-button-primary-color--hover, var(--cover-button-color--hover, var(--cover-button-primary-color, var(--cover-button-color))))}@media (min-width: 30.01em){.cover .cover-main>p:last-child a{display:inline-block}}.cover .mask{visibility:var(--cover-background-mask-visibility, hidden);position:absolute;top:0;bottom:0;left:0;right:0;background-color:var(--cover-background-mask-color);opacity:var(--cover-background-mask-opacity)}.cover.has-mask .mask{visibility:visible}.cover.show{display:-ms-flexbox;display:flex}.app-nav{position:absolute;z-index:30;top:calc(35px - (0.5em * var(--base-line-height)));left:45px;right:80px;text-align:right}.app-nav.no-badge{right:45px}.app-nav li>img,.app-nav li>a>img{margin-top:-0.25em;vertical-align:middle}.app-nav li>img:first-child,.app-nav li>a>img:first-child{margin-right:0.5em}.app-nav a{display:block;line-height:1;transition:var(--navbar-root-transition)}.app-nav ul,.app-nav li{margin:0;padding:0;list-style:none}.app-nav li{position:relative}.app-nav>ul>li{display:inline-block;margin:var(--navbar-root-margin)}.app-nav>ul>li:first-child{margin-left:0}.app-nav>ul>li:last-child{margin-right:0}.app-nav>ul>li>a,.app-nav>ul>li>span{padding:var(--navbar-root-padding);border-width:var(--navbar-root-border-width, 0);border-style:var(--navbar-root-border-style);border-color:var(--navbar-root-border-color);border-radius:var(--navbar-root-border-radius);background:var(--navbar-root-background);color:var(--navbar-root-color);text-decoration:var(--navbar-root-text-decoration);text-decoration-color:var(--navbar-root-text-decoration-color)}.app-nav>ul>li>a:hover,.app-nav>ul>li>span:hover{background:var(--navbar-root-background--hover, var(--navbar-root-background));border-style:var(--navbar-root-border-style--hover, var(--navbar-root-border-style));border-color:var(--navbar-root-border-color--hover, var(--navbar-root-border-color));color:var(--navbar-root-color--hover, var(--navbar-root-color));text-decoration:var(--navbar-root-text-decoration--hover, var(--navbar-root-text-decoration));text-decoration-color:var(--navbar-root-text-decoration-color--hover, var(--navbar-root-text-decoration-color))}.app-nav>ul>li>a:not(:last-child),.app-nav>ul>li>span:not(:last-child){padding:var(--navbar-menu-root-padding, var(--navbar-root-padding));background:var(--navbar-menu-root-background, var(--navbar-root-background))}.app-nav>ul>li>a:not(:last-child):hover,.app-nav>ul>li>span:not(:last-child):hover{background:var(--navbar-menu-root-background--hover, var(--navbar-menu-root-background, var(--navbar-root-background--hover, var(--navbar-root-background))))}.app-nav>ul>li>a.active{background:var(--navbar-root-background--active, var(--navbar-root-background));border-style:var(--navbar-root-border-style--active, var(--navbar-root-border-style));border-color:var(--navbar-root-border-color--active, var(--navbar-root-border-color));color:var(--navbar-root-color--active, var(--navbar-root-color));text-decoration:var(--navbar-root-text-decoration--active, var(--navbar-root-text-decoration));text-decoration-color:var(--navbar-root-text-decoration-color--active, var(--navbar-root-text-decoration-color))}.app-nav>ul>li>a.active:not(:last-child):hover{background:var(--navbar-menu-root-background--active, var(--navbar-menu-root-background, var(--navbar-root-background--active, var(--navbar-root-background))))}.app-nav>ul>li ul{visibility:hidden;position:absolute;top:100%;right:50%;overflow-y:auto;box-sizing:border-box;max-height:calc(50vh);padding:var(--navbar-menu-padding);border-width:var(--navbar-menu-border-width, 0);border-style:solid;border-color:var(--navbar-menu-border-color);border-radius:var(--navbar-menu-border-radius);background:var(--navbar-menu-background);box-shadow:var(--navbar-menu-box-shadow);text-align:left;white-space:nowrap;opacity:0;transform:translate(50%, -0.35em);transition:var(--navbar-menu-transition)}.app-nav>ul>li ul li{white-space:nowrap}.app-nav>ul>li ul a{margin:var(--navbar-menu-link-margin);padding:var(--navbar-menu-link-padding);border-width:var(--navbar-menu-link-border-width, 0);border-style:var(--navbar-menu-link-border-style);border-color:var(--navbar-menu-link-border-color);border-radius:var(--navbar-menu-link-border-radius);background:var(--navbar-menu-link-background);color:var(--navbar-menu-link-color);text-decoration:var(--navbar-menu-link-text-decoration);text-decoration-color:var(--navbar-menu-link-text-decoration-color)}.app-nav>ul>li ul a:hover{background:var(--navbar-menu-link-background--hover, var(--navbar-menu-link-background));border-style:var(--navbar-menu-link-border-style--hover, var(--navbar-menu-link-border-style));border-color:var(--navbar-menu-link-border-color--hover, var(--navbar-menu-link-border-color));color:var(--navbar-menu-link-color--hover, var(--navbar-menu-link-color));text-decoration:var(--navbar-menu-link-text-decoration--hover, var(--navbar-menu-link-text-decoration));text-decoration-color:var(--navbar-menu-link-text-decoration-color--hover, var(--navbar-menu-link-text-decoration-color))}.app-nav>ul>li ul a.active{background:var(--navbar-menu-link-background--active, var(--navbar-menu-link-background));border-style:var(--navbar-menu-link-border-style--active, var(--navbar-menu-link-border-style));border-color:var(--navbar-menu-link-border-color--active, var(--navbar-menu-link-border-color));color:var(--navbar-menu-link-color--active, var(--navbar-menu-link-color));text-decoration:var(--navbar-menu-link-text-decoration--active, var(--navbar-menu-link-text-decoration));text-decoration-color:var(--navbar-menu-link-text-decoration-color--active, var(--navbar-menu-link-text-decoration-color))}.app-nav>ul>li:hover ul,.app-nav>ul>li:focus ul,.app-nav>ul>li.focus-within ul{visibility:visible;opacity:1;transform:translate(50%, 0)}.sidebar,.sidebar-toggle,main>.content{transition:all var(--sidebar-transition-duration) ease-out}@media (min-width: 48em){nav.app-nav{margin-left:var(--sidebar-width)}}main{position:relative;overflow-x:hidden;min-height:100vh}@media (min-width: 48em){main>.content{margin-left:var(--sidebar-width)}}.sidebar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;position:fixed;z-index:10;top:0;right:100%;overflow-y:auto;height:100vh;width:var(--sidebar-width);padding:var(--sidebar-padding);border-width:var(--sidebar-border-width);border-style:solid;border-color:var(--sidebar-border-color);background:var(--sidebar-background)}.sidebar>h1{margin:0;margin:var(--sidebar-name-margin);padding:var(--sidebar-name-padding);background:var(--sidebar-name-background);color:var(--sidebar-name-color);font-family:var(--sidebar-name-font-family);font-size:var(--sidebar-name-font-size);font-weight:var(--sidebar-name-font-weight);text-align:var(--sidebar-name-text-align)}.sidebar>h1 img{max-width:100%}.sidebar>h1 .app-name-link{color:var(--sidebar-name-color)}@media (min-width: 48em){.sidebar{position:absolute;transform:translateX(var(--sidebar-width))}}@media print{.sidebar{display:none}}.sidebar-nav,.sidebar nav{-ms-flex-order:1;order:1;margin:var(--sidebar-nav-margin);padding:var(--sidebar-nav-padding);background:var(--sidebar-nav-background)}.sidebar-nav ul,.sidebar nav ul{margin:0;padding:0;list-style:none}.sidebar-nav ul ul,.sidebar nav ul ul{margin-left:var(--sidebar-nav-indent)}.sidebar-nav a,.sidebar nav a{display:block;overflow:hidden;margin:var(--sidebar-nav-link-margin);padding:var(--sidebar-nav-link-padding);border-width:var(--sidebar-nav-link-border-width, 0);border-style:var(--sidebar-nav-link-border-style);border-color:var(--sidebar-nav-link-border-color);border-radius:var(--sidebar-nav-link-border-radius);background-color:var(--sidebar-nav-link-background-color);background-image:var(--sidebar-nav-link-background-image);background-position:var(--sidebar-nav-link-background-position);background-repeat:var(--sidebar-nav-link-background-repeat);background-size:var(--sidebar-nav-link-background-size);color:var(--sidebar-nav-link-color);font-weight:var(--sidebar-nav-link-font-weight);white-space:nowrap;text-decoration:var(--sidebar-nav-link-text-decoration);text-decoration-color:var(--sidebar-nav-link-text-decoration-color);text-overflow:ellipsis;transition:var(--sidebar-nav-link-transition)}.sidebar-nav a img,.sidebar nav a img{margin-top:-0.25em;vertical-align:middle}.sidebar-nav a img:first-child,.sidebar nav a img:first-child{margin-right:0.5em}.sidebar-nav a:hover,.sidebar nav a:hover{border-width:var(--sidebar-nav-link-border-width--hover, var(--sidebar-nav-link-border-width, 0));border-style:var(--sidebar-nav-link-border-style--hover, var(--sidebar-nav-link-border-style));border-color:var(--sidebar-nav-link-border-color--hover, var(--sidebar-nav-link-border-color));background-color:var(--sidebar-nav-link-background-color--hover, var(--sidebar-nav-link-background-color));background-image:var(--sidebar-nav-link-background-image--hover, var(--sidebar-nav-link-background-image));background-position:var(--sidebar-nav-link-background-position--hover, var(--sidebar-nav-link-background-position));background-size:var(--sidebar-nav-link-background-size--hover, var(--sidebar-nav-link-background-size));color:var(--sidebar-nav-link-color--hover, var(--sidebar-nav-link-color));font-weight:var(--sidebar-nav-link-font-weight--hover, var(--sidebar-nav-link-font-weight));text-decoration:var(--sidebar-nav-link-text-decoration--hover, var(--sidebar-nav-link-text-decoration));text-decoration-color:var(--sidebar-nav-link-text-decoration-color)}.sidebar-nav ul>li>span,.sidebar-nav ul>li>strong,.sidebar nav ul>li>span,.sidebar nav ul>li>strong{display:block;margin:var(--sidebar-nav-strong-margin);padding:var(--sidebar-nav-strong-padding);border-width:var(--sidebar-nav-strong-border-width, 0);border-style:solid;border-color:var(--sidebar-nav-strong-border-color);color:var(--sidebar-nav-strong-color);font-size:var(--sidebar-nav-strong-font-size);font-weight:var(--sidebar-nav-strong-font-weight);text-transform:var(--sidebar-nav-strong-text-transform)}.sidebar-nav ul>li>span+ul,.sidebar-nav ul>li>strong+ul,.sidebar nav ul>li>span+ul,.sidebar nav ul>li>strong+ul{margin-left:0}.sidebar-nav ul>li:first-child>span,.sidebar-nav ul>li:first-child>strong,.sidebar nav ul>li:first-child>span,.sidebar nav ul>li:first-child>strong{margin-top:0}.sidebar-nav::-webkit-scrollbar,.sidebar nav::-webkit-scrollbar{width:0}@supports (width: env(safe-area-inset)){@media only screen and (orientation: landscape){.sidebar-nav,.sidebar nav{margin-left:calc(env(safe-area-inset-left) / 2)}}}.sidebar-nav li>a:before,.sidebar-nav li>strong:before{display:inline-block}.sidebar-nav li>a{background-repeat:var(--sidebar-nav-pagelink-background-repeat);background-size:var(--sidebar-nav-pagelink-background-size)}.sidebar-nav li>a[href^=\"#/\"]:not([href*=\"?id=\"]){transition:var(--sidebar-nav-pagelink-transition)}.sidebar-nav li>a[href^=\"#/\"]:not([href*=\"?id=\"]),.sidebar-nav li>a[href^=\"#/\"]:not([href*=\"?id=\"]) ~ ul a{padding:var(--sidebar-nav-pagelink-padding, var(--sidebar-nav-link-padding))}.sidebar-nav li>a[href^=\"#/\"]:not([href*=\"?id=\"]):only-child{background-image:var(--sidebar-nav-pagelink-background-image);background-position:var(--sidebar-nav-pagelink-background-position)}.sidebar-nav li>a[href^=\"#/\"]:not([href*=\"?id=\"]):not(:only-child){background-image:var(--sidebar-nav-pagelink-background-image--loaded, var(--sidebar-nav-pagelink-background-image));background-position:var(--sidebar-nav-pagelink-background-position--loaded, var(--sidebar-nav-pagelink-background-image))}.sidebar-nav li.active>a,.sidebar-nav li.collapse>a{border-width:var(--sidebar-nav-link-border-width--active, var(--sidebar-nav-link-border-width));border-style:var(--sidebar-nav-link-border-style--active, var(--sidebar-nav-link-border-style));border-color:var(--sidebar-nav-link-border-color--active, var(--sidebar-nav-link-border-color));background-color:var(--sidebar-nav-link-background-color--active, var(--sidebar-nav-link-background-color));background-image:var(--sidebar-nav-link-background-image--active, var(--sidebar-nav-link-background-image));background-position:var(--sidebar-nav-link-background-position--active, var(--sidebar-nav-link-background-position));background-size:var(--sidebar-nav-link-background-size--active, var(--sidebar-nav-link-background-size));color:var(--sidebar-nav-link-color--active, var(--sidebar-nav-link-color));font-weight:var(--sidebar-nav-link-font-weight--active, var(--sidebar-nav-link-font-weight));text-decoration:var(--sidebar-nav-link-text-decoration--active, var(--sidebar-nav-link-text-decoration));text-decoration-color:var(--sidebar-nav-link-text-decoration-color)}.sidebar-nav li.active>a[href^=\"#/\"]:not([href*=\"?id=\"]):not(:only-child){background-image:var(--sidebar-nav-pagelink-background-image--active, var(--sidebar-nav-pagelink-background-image--loaded, var(--sidebar-nav-pagelink-background-image)));background-position:var(--sidebar-nav-pagelink-background-position--active, var(--sidebar-nav-pagelink-background-position--loaded, var(--sidebar-nav-pagelink-background-image)))}.sidebar-nav li.collapse>a[href^=\"#/\"]:not([href*=\"?id=\"]):not(:only-child){background-image:var(--sidebar-nav-pagelink-background-image--collapse, var(--sidebar-nav-pagelink-background-image--loaded, var(--sidebar-nav-pagelink-background-image)));background-position:var(--sidebar-nav-pagelink-background-position--collapse, var(--sidebar-nav-pagelink-background-position--loaded, var(--sidebar-nav-pagelink-background-image)))}.sidebar-nav li.collapse .app-sub-sidebar{display:none}.sidebar-nav>ul>li>a:before{content:var(--sidebar-nav-link-before-content-l1, var(--sidebar-nav-link-before-content));margin:var(--sidebar-nav-link-before-margin-l1, var(--sidebar-nav-link-before-margin));color:var(--sidebar-nav-link-before-color-l1, var(--sidebar-nav-link-before-color))}.sidebar-nav>ul>li.active>a:before{content:var(--sidebar-nav-link-before-content-l1--active, var(--sidebar-nav-link-before-content--active, var(--sidebar-nav-link-before-content-l1, var(--sidebar-nav-link-before-content))));color:var(--sidebar-nav-link-before-color-l1--active, var(--sidebar-nav-link-before-color--active, var(--sidebar-nav-link-before-color-l1, var(--sidebar-nav-link-before-color))))}.sidebar-nav>ul>li>ul>li>a:before{content:var(--sidebar-nav-link-before-content-l2, var(--sidebar-nav-link-before-content));margin:var(--sidebar-nav-link-before-margin-l2, var(--sidebar-nav-link-before-margin));color:var(--sidebar-nav-link-before-color-l2, var(--sidebar-nav-link-before-color))}.sidebar-nav>ul>li>ul>li.active>a:before{content:var(--sidebar-nav-link-before-content-l2--active, var(--sidebar-nav-link-before-content--active, var(--sidebar-nav-link-before-content-l2, var(--sidebar-nav-link-before-content))));color:var(--sidebar-nav-link-before-color-l2--active, var(--sidebar-nav-link-before-color--active, var(--sidebar-nav-link-before-color-l2, var(--sidebar-nav-link-before-color))))}.sidebar-nav>ul>li>ul>li>ul>li>a:before{content:var(--sidebar-nav-link-before-content-l3, var(--sidebar-nav-link-before-content));margin:var(--sidebar-nav-link-before-margin-l3, var(--sidebar-nav-link-before-margin));color:var(--sidebar-nav-link-before-color-l3, var(--sidebar-nav-link-before-color))}.sidebar-nav>ul>li>ul>li>ul>li.active>a:before{content:var(--sidebar-nav-link-before-content-l3--active, var(--sidebar-nav-link-before-content--active, var(--sidebar-nav-link-before-content-l3, var(--sidebar-nav-link-before-content))));color:var(--sidebar-nav-link-before-color-l3--active, var(--sidebar-nav-link-before-color--active, var(--sidebar-nav-link-before-color-l3, var(--sidebar-nav-link-before-color))))}.sidebar-nav>ul>li>ul>li>ul>li>ul>li>a:before{content:var(--sidebar-nav-link-before-content-l4, var(--sidebar-nav-link-before-content));margin:var(--sidebar-nav-link-before-margin-l4, var(--sidebar-nav-link-before-margin));color:var(--sidebar-nav-link-before-color-l4, var(--sidebar-nav-link-before-color))}.sidebar-nav>ul>li>ul>li>ul>li>ul>li.active>a:before{content:var(--sidebar-nav-link-before-content-l4--active, var(--sidebar-nav-link-before-content--active, var(--sidebar-nav-link-before-content-l4, var(--sidebar-nav-link-before-content))));color:var(--sidebar-nav-link-before-color-l4--active, var(--sidebar-nav-link-before-color--active, var(--sidebar-nav-link-before-color-l4, var(--sidebar-nav-link-before-color))))}.sidebar-nav>:last-child{margin-bottom:2rem}.sidebar-toggle,.sidebar-toggle-button{outline:none}.sidebar-toggle{position:fixed;z-index:11;top:0;bottom:0;left:0;width:40px;margin:0;padding:0;border:0;background:transparent;appearance:none;cursor:pointer}.sidebar-toggle .sidebar-toggle-button{position:absolute;top:var(--sidebar-toggle-offset-top);left:var(--sidebar-toggle-offset-left);height:var(--sidebar-toggle-height);width:var(--sidebar-toggle-width);border-radius:var(--sidebar-toggle-border-radius);border-width:var(--sidebar-toggle-border-width);border-style:var(--sidebar-toggle-border-style);border-color:var(--sidebar-toggle-border-color);background:var(--sidebar-toggle-background, transparent);color:var(--sidebar-toggle-icon-color)}.sidebar-toggle span{position:absolute;top:calc(50% - (var(--sidebar-toggle-icon-stroke-width) / 2));left:calc(50% - (var(--sidebar-toggle-icon-width) / 2));height:var(--sidebar-toggle-icon-stroke-width);width:var(--sidebar-toggle-icon-width);background-color:currentColor}.sidebar-toggle span:nth-child(1){margin-top:calc(0px - (var(--sidebar-toggle-icon-height) / 2))}.sidebar-toggle span:nth-child(3){margin-top:calc((var(--sidebar-toggle-icon-height) / 2))}@media (min-width: 48em){.sidebar-toggle{position:absolute;overflow:visible;left:0;transform:translateX(var(--sidebar-width))}}@media print{.sidebar-toggle{display:none}}@media (max-width: 47.99em){body.close .sidebar,body.close .sidebar-toggle,body.close main>.content{transform:translateX(var(--sidebar-width))}}@media (min-width: 48em){body.close main>.content{transform:translateX(0)}}@media (max-width: 47.99em){body.close nav.app-nav,body.close .github-corner{display:none}}@media (min-width: 48em){body.close .sidebar,body.close .sidebar-toggle{transform:translateX(0)}}@media (min-width: 48em){body.close nav.app-nav{margin-left:0}}@media (max-width: 47.99em){body.close .sidebar-toggle{width:100%}body.close .sidebar-toggle span{margin-top:0}body.close .sidebar-toggle span:nth-child(1){transform:rotate(45deg)}body.close .sidebar-toggle span:nth-child(2){display:none}body.close .sidebar-toggle span:nth-child(3){transform:rotate(-45deg)}}@media (min-width: 48em){body.close main>.content{margin-left:0}}@media (min-width: 48em){body.sticky .sidebar,body.sticky .sidebar-toggle{position:fixed}}body .docsify-copy-code-button,body .docsify-copy-code-button::after{background:var(--copycode-background);color:var(--copycode-color)}body .docsify-pagination-container{border-top:var(--pagination-border-top);color:var(--pagination-color)}body .pagination-item-label{font-size:var(--pagination-label-font-size)}body .pagination-item-label svg{color:var(--pagination-label-color);height:var(--pagination-chevron-height);stroke:var(--pagination-chevron-stroke);stroke-linecap:var(--pagination-chevron-stroke-linecap);stroke-linejoin:var(--pagination-chevron-stroke-linecap);stroke-width:var(--pagination-chevron-stroke-width)}body .pagination-item-title{color:var(--pagination-title-color);font-size:var(--pagination-title-font-size)}body .app-name.hide{display:block}body .sidebar{padding:var(--sidebar-padding)}.sidebar .search{margin:0;padding:0;border:0}.sidebar .search input{padding:0;line-height:1;font-size:inherit}.sidebar .search .clear-button{width:auto}.sidebar .search .clear-button svg{transform:scale(1)}.sidebar .search .matching-post{border:none}.sidebar .search p{font-size:inherit}.sidebar .search{-ms-flex-order:var(--search-flex-order);order:var(--search-flex-order);margin:var(--search-margin);padding:var(--search-padding);background:var(--search-background)}.sidebar .search a{color:inherit}.sidebar .search h2{margin:var(--search-result-heading-margin);font-size:var(--search-result-heading-font-size);font-weight:var(--search-result-heading-font-weight);color:var(--search-result-heading-color)}.sidebar .search .input-wrap{margin:var(--search-input-margin);background-color:var(--search-input-background-color);border-width:var(--search-input-border-width, 0);border-style:solid;border-color:var(--search-input-border-color);border-radius:var(--search-input-border-radius)}.sidebar .search input[type=\"search\"]{min-width:0;padding:var(--search-input-padding);border:none;background-color:transparent;background-image:var(--search-input-background-image);background-position:var(--search-input-background-position);background-repeat:var(--search-input-background-repeat);background-size:var(--search-input-background-size);font-size:var(--search-input-font-size);color:var(--search-input-color);transition:var(--search-input-transition)}.sidebar .search input[type=\"search\"]::-ms-clear{display:none}.sidebar .search input[type=\"search\"]:-ms-input-placeholder{color:var(--search-input-placeholder-color, gray)}.sidebar .search input[type=\"search\"]::placeholder{color:var(--search-input-placeholder-color, gray)}.sidebar .search input[type=\"search\"]::-webkit-input-placeholder{line-height:normal}.sidebar .search input[type=\"search\"]:focus{background-color:var(--search-input-background-color--focus, var(--search-input-background-color));background-image:var(--search-input-background-image--focus, var(--search-input-background-image));background-position:var(--search-input-background-position--focus, var(--search-input-background-position));background-size:var(--search-input-background-size--focus, var(--search-input-background-size))}@supports (width: env(safe-area-inset)){@media only screen and (orientation: landscape){.sidebar .search input[type=\"search\"]{margin-left:calc(env(safe-area-inset-left) / 2)}}}.sidebar .search p{overflow:hidden;text-overflow:ellipsis;-webkit-line-clamp:2}.sidebar .search p:empty{text-align:center}.sidebar .search .clear-button{margin:0 15px 0 0;padding:0;border:none;line-height:1;background:transparent;cursor:pointer}.sidebar .search .clear-button svg circle{fill:var(--search-clear-icon-color1, gray)}.sidebar .search .clear-button svg path{stroke:var(--search-clear-icon-color2, #fff)}.sidebar .search.show ~ *:not(h1){display:none}.sidebar .search .results-panel{display:none;font-size:var(--search-result-item-font-size)}.sidebar .search .results-panel.show{display:block}.sidebar .search .matching-post{margin:var(--search-result-item-margin);padding:var(--search-result-item-padding)}.sidebar .search .matching-post,.sidebar .search .matching-post:last-child{border-width:var(--search-result-item-border-width, 0) !important;border-style:var(--search-result-item-border-style);border-color:var(--search-result-item-border-color)}.sidebar .search .matching-post p{margin:0}.sidebar .search .search-keyword{margin:var(--search-result-keyword-margin);padding:var(--search-result-keyword-padding);border-radius:var(--search-result-keyword-border-radius);background-color:var(--search-result-keyword-background);color:var(--search-result-keyword-color, currentColor);font-style:normal;font-weight:var(--search-result-keyword-font-weight)}.medium-zoom-overlay,.medium-zoom-image--open{z-index:50 !important}.medium-zoom-overlay{background:var(--zoomimage-overlay-background) !important}:root{--mono-hue: 113;--mono-saturation: 0%;--mono-shade3: hsl(var(--mono-hue), var(--mono-saturation), 20%);--mono-shade2: hsl(var(--mono-hue), var(--mono-saturation), 30%);--mono-shade1: hsl(var(--mono-hue), var(--mono-saturation), 40%);--mono-base: hsl(var(--mono-hue), var(--mono-saturation), 50%);--mono-tint1: hsl(var(--mono-hue), var(--mono-saturation), 70%);--mono-tint2: hsl(var(--mono-hue), var(--mono-saturation), 89%);--mono-tint3: hsl(var(--mono-hue), var(--mono-saturation), 97%);--theme-hue: 204;--theme-saturation: 90%;--theme-lightness: 45%;--theme-color: hsl(var(--theme-hue), var(--theme-saturation), var(--theme-lightness));--modular-scale: 1.333;--modular-scale--2: calc(var(--modular-scale-2) / var(--modular-scale));--modular-scale--1: calc(var(--modular-scale-1) / var(--modular-scale));--modular-scale-1: 1rem;--modular-scale-2: calc(var(--modular-scale-1) * var(--modular-scale));--modular-scale-3: calc(var(--modular-scale-2) * var(--modular-scale));--modular-scale-4: calc(var(--modular-scale-3) * var(--modular-scale));--modular-scale-5: calc(var(--modular-scale-4) * var(--modular-scale));--font-size-xxxl: var(--modular-scale-5);--font-size-xxl: var(--modular-scale-4);--font-size-xl: var(--modular-scale-3);--font-size-l: var(--modular-scale-2);--font-size-m: var(--modular-scale-1);--font-size-s: var(--modular-scale--1);--font-size-xs: var(--modular-scale--2);--duration-slow: 1s;--duration-medium: 0.5s;--duration-fast: 0.25s;--spinner-size: 60px;--spinner-track-width: 4px;--spinner-track-color: rgba(0, 0, 0, 0.15);--spinner-transition-duration: var(--duration-medium)}:root{--base-background-color: #fff;--base-color: var(--mono-shade2);--base-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";--base-font-size: 16px;--base-line-height: 1.7;--emoji-size: calc(var(--base-line-height) * 1em);--hr-border: 1px solid var(--mono-tint2);--mark-background: #ffecb3;--pre-font-family: var(--code-font-family);--pre-font-size: var(--code-font-size);--selection-color: #b4d5fe;--small-font-size: var(--font-size-s);--strong-color: var(--heading-color);--strong-font-weight: 600;--subsup-font-size: var(--font-size-s)}:root{--content-max-width: 55em;--blockquote-background: var(--mono-tint3);--blockquote-border-style: solid;--blockquote-border-radius: var(--border-radius-m);--code-font-family: Inconsolata, Consolas, Menlo, Monaco, \"Andale Mono WT\", \"Andale Mono\", \"Lucida Console\", \"DejaVu Sans Mono\", \"Bitstream Vera Sans Mono\", \"Courier New\", Courier, monospace;--code-font-size: calc(var(--font-size-m) * 0.95);--code-tab-size: 4;--code-block-border-radius: var(--border-radius-m);--code-block-line-height: var(--base-line-height);--code-block-margin: 1em 0;--code-block-padding: 1.75em 1.5em 1.5em 1.5em;--code-inline-background: var(--code-theme-background);--code-inline-border-radius: var(--border-radius-s);--code-inline-color: var(--code-theme-text);--code-inline-margin: 0 0.15em;--code-inline-padding: 0.125em 0.4em;--code-theme-background: var(--mono-tint3);--heading-color: var(--mono-shade3);--heading-margin: 2.5rem 0 0;--heading-h1-border-style: solid;--heading-h1-font-size: var(--font-size-xxl);--heading-h2-border-style: solid;--heading-h2-font-size: var(--font-size-xl);--heading-h3-border-style: solid;--heading-h3-font-size: var(--font-size-l);--heading-h4-border-style: solid;--heading-h4-font-size: var(--font-size-m);--heading-h5-border-style: solid;--heading-h5-font-size: var(--font-size-s);--heading-h6-border-style: solid;--heading-h6-color: var(--mono-base);--heading-h6-font-size: var(--font-size-s);--kbd-background: var(--mono-tint3);--kbd-border-radius: var(--border-radius-m);--kbd-margin: 0 0.3em;--kbd-min-width: 2.5em;--kbd-padding: 0.65em 0.5em;--link-text-decoration: underline;--notice-background: var(--mono-tint3);--notice-border-radius: var(--border-radius-m);--notice-border-style: solid;--notice-padding: 1em 1.5em;--table-cell-padding: 0.75em 0.5em;--table-head-border-color: var(--table-cell-border-color);--table-head-font-weight: var(--strong-font-weight);--table-row-odd-background: var(--mono-tint3)}:root{--cover-margin: 0 auto;--cover-max-width: 40em;--cover-text-align: center;--cover-background-color: var(--base-background-color);--cover-background-mask-color: var(--base-background-color);--cover-background-mask-opacity: 0.8;--cover-background-position: center center;--cover-background-repeat: no-repeat;--cover-background-size: cover;--cover-blockquote-font-size: var(--font-size-l);--cover-border-color: var(--theme-color);--cover-button-border: 1px solid var(--theme-color);--cover-button-border-radius: var(--border-radius-m);--cover-button-color: var(--theme-color);--cover-button-padding: 0.5em 2rem;--cover-button-text-decoration: none;--cover-button-transition: all var(--duration-fast) ease-in-out;--cover-button-primary-background: var(--theme-color);--cover-button-primary-border: 1px solid var(--theme-color);--cover-button-primary-color: #fff;--cover-heading-color: var(--theme-color);--cover-heading-font-size: var(--font-size-xxl);--cover-link-text-decoration: underline }:root{--navbar-root-border-style: solid;--navbar-root-margin: 0 0 0 1.5em;--navbar-root-transition: all var(--duration-fast);--navbar-menu-background: var(--base-background-color);--navbar-menu-border-radius: var(--border-radius-m);--navbar-menu-box-shadow: rgba(45,45,45,0.05) 0px 0px 1px, rgba(49,49,49,0.05) 0px 1px 2px, rgba(42,42,42,0.05) 0px 2px 4px, rgba(32,32,32,0.05) 0px 4px 8px, rgba(49,49,49,0.05) 0px 8px 16px, rgba(35,35,35,0.05) 0px 16px 32px;--navbar-menu-padding: 0.5em;--navbar-menu-transition: all var(--duration-fast);--navbar-menu-link-border-style: solid;--navbar-menu-link-margin: 0.75em 0.5em;--navbar-menu-link-padding: 0.2em 0 }:root{--copycode-background: #808080;--copycode-color: #fff}:root{--docsifytabs-border-color: var(--mono-tint2);--docsifytabs-border-radius-px: var(--border-radius-s);--docsifytabs-tab-background: var(--mono-tint3);--docsifytabs-tab-color: var(--mono-tint1)}:root{--pagination-border-top: 1px solid var(--mono-tint2);--pagination-chevron-height: 0.8em;--pagination-chevron-stroke: currentColor;--pagination-chevron-stroke-linecap: round;--pagination-chevron-stroke-width: 1px;--pagination-label-font-size: var(--font-size-s);--pagination-title-font-size: var(--font-size-l)}:root{--search-margin: 1.5rem 0 0;--search-input-background-repeat: no-repeat;--search-input-border-color: var(--mono-tint1);--search-input-border-width: 1px;--search-input-padding: 0.5em;--search-flex-order: 1;--search-result-heading-color: var(--heading-color);--search-result-heading-font-size: var(--base-font-size);--search-result-heading-font-weight: normal;--search-result-heading-margin: 0 0 0.25em;--search-result-item-border-color: var(--mono-tint2);--search-result-item-border-style: solid;--search-result-item-border-width: 0 0 1px 0;--search-result-item-padding: 1em 0;--search-result-keyword-background: var(--mark-background);--search-result-keyword-border-radius: var(--border-radius-s);--search-result-keyword-color: var(--mark-color);--search-result-keyword-margin: 0 0.1em;--search-result-keyword-padding: 0.2em 0}:root{--zoomimage-overlay-background: var(--base-background-color)}:root{--sidebar-background: var(--base-background-color);--sidebar-border-width: 0;--sidebar-padding: 0 25px;--sidebar-transition-duration: var(--duration-fast);--sidebar-width: 17rem;--sidebar-name-font-size: var(--font-size-l);--sidebar-name-margin: 1.5rem 0 0;--sidebar-name-text-align: center;--sidebar-nav-strong-border-color: var(--sidebar-border-color);--sidebar-nav-strong-color: var(--heading-color);--sidebar-nav-strong-font-weight: var(--strong-font-weight);--sidebar-nav-strong-margin: 1.5em 0 0.5em;--sidebar-nav-strong-padding: 0.25em 0;--sidebar-nav-indent: 1em;--sidebar-nav-margin: 1.5rem 0 0;--sidebar-nav-link-border-style: solid;--sidebar-nav-link-border-width: 0;--sidebar-nav-link-color: var(--base-color);--sidebar-nav-link-padding: 0.25em 0;--sidebar-nav-link-text-decoration--active: underline;--sidebar-nav-link-text-decoration--hover: underline;--sidebar-nav-link-before-margin: 0 0.35em 0 0;--sidebar-nav-pagelink-background-repeat: no-repeat;--sidebar-nav-pagelink-transition: var(--sidebar-nav-link-transition);--sidebar-toggle-border-radius: var(--border-radius-s);--sidebar-toggle-border-style: solid;--sidebar-toggle-border-width: 0;--sidebar-toggle-height: 36px;--sidebar-toggle-icon-color: var(--base-color);--sidebar-toggle-icon-height: 10px;--sidebar-toggle-icon-stroke-width: 1px;--sidebar-toggle-icon-width: 16px;--sidebar-toggle-offset-left: 0;--sidebar-toggle-offset-top: calc(35px - (var(--sidebar-toggle-height) / 2));--sidebar-toggle-width: 44px}:root{--code-theme-background: #f3f3f3;--code-theme-comment: #6e8090;--code-theme-function: #dd4a68;--code-theme-keyword: #07a;--code-theme-operator: #a67f59;--code-theme-punctuation: #999;--code-theme-selection: #b3d4fc;--code-theme-selector: #690;--code-theme-tag: #905;--code-theme-text: #333;--code-theme-variable: #e90}:root{--border-radius-s: 2px;--border-radius-m: 4px;--border-radius-l: 8px;--strong-font-weight: 600;--blockquote-border-color: var(--theme-color);--blockquote-border-radius: 0 var(--border-radius-m) var(--border-radius-m) 0;--blockquote-border-width: 0 0 0 4px;--code-inline-background: var(--mono-tint2);--code-theme-background: var(--mono-tint3);--heading-font-weight: var(--strong-font-weight);--heading-h1-font-weight: 400;--heading-h2-font-weight: 400;--heading-h2-border-color: var(--mono-tint2);--heading-h2-border-width: 0 0 1px 0;--heading-h2-margin: 2.5rem 0 1.5rem;--heading-h2-padding: 0 0 1rem 0;--kbd-border: 1px solid var(--mono-tint2);--notice-border-radius: 0 var(--border-radius-m) var(--border-radius-m) 0;--notice-border-width: 0 0 0 4px;--notice-padding: 1em 1.5em 1em 3em;--notice-before-border-radius: 100%;--notice-before-font-weight: bold;--notice-before-height: 1.5em;--notice-before-left: 0.75em;--notice-before-line-height: 1.5;--notice-before-margin: 0 0.25em 0 0;--notice-before-position: absolute;--notice-before-width: var(--notice-before-height);--notice-important-background: hsl(340, 60%, 96%);--notice-important-border-color: hsl(340, 90%, 45%);--notice-important-before-background: var(--notice-important-border-color) url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3E%3Cpath d='M10 14C10 15.1 9.1 16 8 16 6.9 16 6 15.1 6 14 6 12.9 6.9 12 8 12 9.1 12 10 12.9 10 14Z'/%3E%3Cpath d='M10 1.6C10 1.2 9.8 0.9 9.6 0.7 9.2 0.3 8.6 0 8 0 7.4 0 6.8 0.2 6.5 0.6 6.2 0.9 6 1.2 6 1.6 6 1.7 6 1.8 6 1.9L6.8 9.6C6.9 9.9 7 10.1 7.2 10.2 7.4 10.4 7.7 10.5 8 10.5 8.3 10.5 8.6 10.4 8.8 10.3 9 10.1 9.1 9.9 9.2 9.6L10 1.9C10 1.8 10 1.7 10 1.6Z'/%3E%3C/svg%3E\") center / 0.875em no-repeat;--notice-important-before-color: #fff;--notice-important-before-content: \"\";--notice-tip-background: hsl(204, 60%, 96%);--notice-tip-border-color: hsl(204, 90%, 45%);--notice-tip-before-background: var(--notice-tip-border-color) url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3E%3Cpath d='M9.1 0C10.2 0 10.7 0.7 10.7 1.6 10.7 2.6 9.8 3.6 8.6 3.6 7.6 3.6 7 3 7 2 7 1.1 7.7 0 9.1 0Z'/%3E%3Cpath d='M5.8 16C5 16 4.4 15.5 5 13.2L5.9 9.1C6.1 8.5 6.1 8.2 5.9 8.2 5.7 8.2 4.6 8.6 3.9 9.1L3.5 8.4C5.6 6.6 7.9 5.6 8.9 5.6 9.8 5.6 9.9 6.6 9.5 8.2L8.4 12.5C8.2 13.2 8.3 13.5 8.5 13.5 8.7 13.5 9.6 13.2 10.4 12.5L10.9 13.2C8.9 15.2 6.7 16 5.8 16Z'/%3E%3C/svg%3E\") center / 0.875em no-repeat;--notice-tip-before-color: #fff;--notice-tip-before-content: \"\";--table-cell-border-color: var(--mono-tint2);--table-cell-border-width: 1px 0;--cover-background-color: hsl(var(--theme-hue), 25%, 60%);--cover-background-image: radial-gradient(ellipse at center 115%, rgba(255, 255, 255, 0.9), transparent);--cover-blockquote-color: var(--strong-color);--cover-heading-color: #fff;--cover-heading-font-size-max: 56;--cover-heading-font-size-min: 34;--cover-heading-font-weight: 200;--navbar-root-color--active: var(--theme-color);--navbar-menu-border-radius: var(--border-radius-m);--navbar-menu-root-background: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9.6' height='6' viewBox='0 0 9.6 6'%3E%3Cpath d='M1.5 1.5l3.3 3 3.3-3' stroke-width='1.5' stroke='rgb%28179, 179, 179%29' fill='none' stroke-linecap='square' stroke-linejoin='miter' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E\") right no-repeat;--navbar-menu-root-padding: 0 18px 0 0;--search-input-background-color: #fff;--search-input-background-image: url(\"data:image/svg+xml,%3Csvg height='20px' width='20px' viewBox='0 0 24 24' fill='none' stroke='rgba(0, 0, 0, 0.3)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='xMidYMid meet' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='10.5' cy='10.5' r='7.5' vector-effect='non-scaling-stroke'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='15.8' y2='15.8' vector-effect='non-scaling-stroke'%3E%3C/line%3E%3C/svg%3E\");--search-input-background-position: 21px center;--search-input-border-color: var(--sidebar-border-color);--search-input-border-width: 1px 0;--search-input-margin: 0 -25px;--search-input-padding: 0.65em 1em 0.65em 50px;--search-input-placeholder-color: rgba(0, 0, 0, 0.4);--search-clear-icon-color1: rgba(0, 0, 0, 0.3);--search-result-heading-font-weight: var(--strong-font-weight);--search-result-item-border-color: var(--sidebar-border-color);--search-result-keyword-border-radius: var(--border-radius-s);--sidebar-background: var(--mono-tint3);--sidebar-border-color: var(--mono-tint2);--sidebar-border-width: 0 1px 0 0;--sidebar-name-color: var(--theme-color);--sidebar-name-font-weight: 300;--sidebar-nav-strong-border-width: 0 0 1px 0;--sidebar-nav-strong-font-size: smaller;--sidebar-nav-strong-margin: 2em -25px 0.75em 0;--sidebar-nav-strong-padding: 0.25em 0 0.75em 0;--sidebar-nav-strong-text-transform: uppercase;--sidebar-nav-link-border-color: transparent;--sidebar-nav-link-border-color--active: var(--theme-color);--sidebar-nav-link-border-width: 0 4px 0 0;--sidebar-nav-link-color--active: var(--theme-color);--sidebar-nav-link-margin: 0 -25px 0 0;--sidebar-nav-link-text-decoration: none;--sidebar-nav-link-text-decoration--active: none;--sidebar-nav-link-text-decoration--hover: underline;--sidebar-nav-link-before-content-l3: '-';--sidebar-nav-pagelink-background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='7' height='11.2' viewBox='0 0 7 11.2'%3E%3Cpath d='M1.5 1.5l4 4.1 -4 4.1' stroke-width='1.5' stroke='rgb%28179, 179, 179%29' fill='none' stroke-linecap='square' stroke-linejoin='miter' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E\");--sidebar-nav-pagelink-background-image--active: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11.2' height='7' viewBox='0 0 11.2 7'%3E%3Cpath d='M1.5 1.5l4.1 4 4.1-4' stroke-width='1.5' stroke='rgb%2811, 135, 218%29' fill='none' stroke-linecap='square' stroke-linejoin='miter' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E\");--sidebar-nav-pagelink-background-image--collapse: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='7' height='11.2' viewBox='0 0 7 11.2'%3E%3Cpath d='M1.5 1.5l4 4.1 -4 4.1' stroke-width='1.5' stroke='rgb%2811, 135, 218%29' fill='none' stroke-linecap='square' stroke-linejoin='miter' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E\");--sidebar-nav-pagelink-background-image--loaded: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11.2' height='7' viewBox='0 0 11.2 7'%3E%3Cpath d='M1.5 1.5l4.1 4 4.1-4' stroke-width='1.5' stroke='rgb%2811, 135, 218%29' fill='none' stroke-linecap='square' stroke-linejoin='miter' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E\");--sidebar-nav-pagelink-background-position: 3px center;--sidebar-nav-pagelink-background-position--active: left center;--sidebar-nav-pagelink-background-position--collapse: var(--sidebar-nav-pagelink-background-position);--sidebar-nav-pagelink-background-position--loaded: var(--sidebar-nav-pagelink-background-position--active);--sidebar-nav-pagelink-padding: 0.25em 0 0.25em 20px;--sidebar-nav-pagelink-transition: none;--sidebar-toggle-background: var(--sidebar-border-color);--sidebar-toggle-border-radius: 0 var(--border-radius-s) var(--border-radius-s) 0;--sidebar-toggle-width: 32px}:root{--code-theme-background: #222;--code-theme-comment: #516e7a;--code-theme-function: #f07178;--code-theme-keyword: #c2e78c;--code-theme-operator: #ffcb6b;--code-theme-punctuation: #89ddff;--code-theme-selection: rgba(255, 255, 255, 0.2);--code-theme-selector: #ffcb6b;--code-theme-tag: #f07178;--code-theme-text: #f3f3f3;--code-theme-variable: #ffcb6b}:root{--mono-hue: 201;--mono-saturation: 18%;--mono-shade3: hsl(var(--mono-hue), var(--mono-saturation), 13%);--mono-shade2: hsl(var(--mono-hue), var(--mono-saturation), 15%);--mono-shade1: hsl(var(--mono-hue), var(--mono-saturation), 17%);--mono-base: hsl(var(--mono-hue), var(--mono-saturation), 19%);--mono-tint1: hsl(var(--mono-hue), var(--mono-saturation), 25%);--mono-tint2: hsl(var(--mono-hue), var(--mono-saturation), 35%);--mono-tint3: hsl(var(--mono-hue), var(--mono-saturation), 43%);--spinner-track-color: rgba(255, 255, 255, 0.15);--base-background-color: var(--mono-base);--base-color: #d3d3d3;--hr-border: 1px solid var(--mono-tint2);--mark-background: #ffcb6b;--mark-color: var(--base-background-color);--blockquote-background: var(--mono-shade2);--code-inline-background: var(--mono-tint1);--code-theme-background: var(--mono-shade2);--heading-color: #fff;--heading-h2-border-color: var(--mono-tint2);--kbd-background: var(--mono-shade2);--kbd-border: none;--kbd-color: var(--strong-color);--notice-important-background: var(--mono-shade2);--notice-tip-background: var(--mono-shade2);--table-cell-border-color: var(--mono-tint1);--table-row-odd-background: var(--mono-shade2);--cover-background-color: var(--base-background-color);--cover-background-image: radial-gradient(ellipse at center bottom, var(--mono-tint3), transparent);--cover-blockquote-color: var(--mark-background);--cover-button-border: 1px solid var(--mono-tint3);--cover-button-color: #fff;--navbar-menu-background: var(--mono-tint1);--navbar-menu-box-shadow: rgba(0,0,0,0.05) 0px 0px 1px, rgba(0,0,0,0.05) 0px 1px 2px, rgba(0,0,0,0.05) 0px 2px 4px, rgba(0,0,0,0.05) 0px 4px 8px, rgba(0,0,0,0.05) 0px 8px 16px, rgba(0,0,0,0.05) 0px 16px 32px;--copycode-background: var(--mono-tint1);--copycode-color: #fff;--docsifytabs-border-color: var(--mono-tint2);--docsifytabs-tab-background: var(--mono-shade1);--docsifytabs-tab-color: var(--mono-tint2);--pagination-border-top: 1px solid var(--mono-tint2);--pagination-title-color: #fff;--search-input-background-color: var(--mono-shade2);--search-input-background-image: url(\"data:image/svg+xml,%3Csvg height='20px' width='20px' viewBox='0 0 24 24' fill='none' stroke='rgba(255, 255, 255, 0.3)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='xMidYMid meet' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='10.5' cy='10.5' r='7.5' vector-effect='non-scaling-stroke'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='15.8' y2='15.8' vector-effect='non-scaling-stroke'%3E%3C/line%3E%3C/svg%3E\");--search-input-border-color: var(--mono-tint1);--search-input-placeholder-color: rgba(255, 255, 255, 0.4);--search-clear-icon-color1: rgba(255, 255, 255, 0.3);--sidebar-background: var(--mono-shade1);--sidebar-border-color: var(--mono-tint1);--sidebar-nav-pagelink-background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='7' height='11.2' viewBox='0 0 7 11.2'%3E%3Cpath d='M1.5 1.5l4 4.1 -4 4.1' stroke-width='1.5' stroke='rgb%2873, 93, 104%29' fill='none' stroke-linecap='square' stroke-linejoin='miter' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E\")}\n/*# sourceMappingURL=theme-simple-dark.css.map */"
  },
  {
    "path": "notes/docsify/unpkg/docsify-themeable/dist/css/theme-simple.css",
    "content": "﻿.github-corner{position:absolute;z-index:40;top:0;right:0;border-bottom:0;text-decoration:none}.github-corner svg{height:70px;width:70px;fill:var(--theme-color);color:var(--base-background-color)}.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}.progress{position:fixed;z-index:60;top:0;left:0;right:0;height:3px;width:0;background-color:var(--theme-color);transition:width var(--duration-fast),opacity calc(var(--duration-fast) * 2)}body.ready-transition:after,body.ready-transition>*:not(.progress){opacity:0;transition:opacity var(--spinner-transition-duration)}body.ready-transition:after{content:'';position:absolute;z-index:1000;top:calc(50% - (var(--spinner-size) / 2));left:calc(50% - (var(--spinner-size) / 2));height:var(--spinner-size);width:var(--spinner-size);border:var(--spinner-track-width, 0) solid var(--spinner-track-color);border-left-color:var(--theme-color);border-left-color:var(--theme-color);border-radius:50%;animation:spinner var(--duration-slow) infinite linear}body.ready-transition.ready-spinner:after{opacity:1}body.ready-transition.ready-fix:after{opacity:0}body.ready-transition.ready-fix>*:not(.progress){opacity:1;transition-delay:var(--spinner-transition-duration)}@keyframes spinner{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}*,*:before,*:after{box-sizing:inherit;font-size:inherit;-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:none;-webkit-touch-callout:none}html{box-sizing:border-box;background-color:var(--base-background-color);font-family:var(--base-font-family);font-size:var(--base-font-size);font-weight:var(--base-font-weight);letter-spacing:var(--base-letter-spacing);line-height:var(--base-line-height);color:var(--base-color);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased}::selection{background:var(--selection-color)}a{text-decoration:none;text-decoration-skip:ink;text-decoration-skip-ink:auto}body{margin:0}hr{height:0;margin:2em 0;border:none;border-bottom:var(--hr-border, 0)}img{border:0}main{display:block}main.hidden{display:none}mark{background:var(--mark-background);color:var(--mark-color)}pre{font-family:var(--pre-font-family);font-size:var(--pre-font-size);font-weight:var(--pre-font-weight);line-height:var(--pre-line-height)}small{display:inline-block;font-size:var(--small-font-size)}strong{font-weight:var(--strong-font-weight);color:var(--strong-color, currentColor)}sub,sup{font-size:var(--subsup-font-size);line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}.emoji{height:var(--emoji-size);vertical-align:middle}.task-list-item{list-style:none}.task-list-item input{margin-right:0.5em;margin-left:0;vertical-align:0.075em}.markdown-section code[class*=\"lang-\"],.markdown-section pre[data-lang]{font-family:var(--code-font-family);font-size:var(--code-font-size);font-weight:var(--code-font-weight);letter-spacing:normal;line-height:var(--code-block-line-height);tab-size:var(--code-tab-size);text-align:left;white-space:pre;word-spacing:normal;word-wrap:normal;word-break:normal;-ms-hyphens:none;hyphens:none}.markdown-section pre[data-lang]{position:relative;overflow:hidden;margin:var(--code-block-margin);padding:0;border-radius:var(--code-block-border-radius)}.markdown-section pre[data-lang]::after{content:attr(data-lang);position:absolute;top:0.75em;right:0.75em;opacity:0.6;color:inherit;font-size:var(--font-size-s);line-height:1}.markdown-section pre[data-lang] code{display:block;overflow:auto;padding:var(--code-block-padding)}code[class*=\"lang-\"],pre[data-lang]{color:var(--code-theme-text)}pre[data-lang]::selection,pre[data-lang] ::selection,code[class*=\"lang-\"]::selection,code[class*=\"lang-\"] ::selection{background:var(--code-theme-selection, var(--selection-color))}:not(pre)>code[class*=\"lang-\"],pre[data-lang]{background:var(--code-theme-background)}.namespace{opacity:0.7}.token.comment,.token.prolog,.token.doctype,.token.cdata{color:var(--code-theme-comment)}.token.punctuation{color:var(--code-theme-punctuation)}.token.property,.token.tag,.token.boolean,.token.number,.token.constant,.token.symbol,.token.deleted{color:var(--code-theme-tag)}.token.selector,.token.attr-name,.token.string,.token.char,.token.builtin,.token.inserted{color:var(--code-theme-selector)}.token.operator,.token.entity,.token.url,.language-css .token.string,.style .token.string{color:var(--code-theme-operator)}.token.atrule,.token.attr-value,.token.keyword{color:var(--code-theme-keyword)}.token.function{color:var(--code-theme-function)}.token.regex,.token.important,.token.variable{color:var(--code-theme-variable)}.token.important,.token.bold{font-weight:bold}.token.italic{font-style:italic}.token.entity{cursor:help}.markdown-section{position:relative;max-width:var(--content-max-width);margin:0 auto;padding:2rem 45px}.app-nav:not(:empty) ~ main .markdown-section{padding-top:3.5rem}.markdown-section figure,.markdown-section p,.markdown-section ol,.markdown-section ul{margin:1em 0}.markdown-section ol,.markdown-section ul{padding-left:1.5rem}.markdown-section ol ol,.markdown-section ol ul,.markdown-section ul ol,.markdown-section ul ul{margin-top:0.15rem;margin-bottom:0.15rem}.markdown-section a{border-bottom:var(--link-border-bottom);color:var(--link-color);text-decoration:var(--link-text-decoration);text-decoration-color:var(--link-text-decoration-color)}.markdown-section a:hover{border-bottom:var(--link-border-bottom--hover, var(--link-border-bottom, 0));color:var(--link-color--hover, var(--link-color));text-decoration:var(--link-text-decoration--hover, var(--link-text-decoration));text-decoration-color:var(--link-text-decoration-color--hover, var(--link-text-decoration-color))}.markdown-section a.anchor{border-bottom:0;color:inherit;text-decoration:none}.markdown-section a.anchor:hover{text-decoration:underline}.markdown-section blockquote{overflow:visible;margin:2em 0;padding:1.5em;border-width:var(--blockquote-border-width, 0);border-style:var(--blockquote-border-style);border-color:var(--blockquote-border-color);border-radius:var(--blockquote-border-radius);background:var(--blockquote-background);color:var(--blockquote-color);font-family:var(--blockquote-font-family);font-size:var(--blockquote-font-size);font-style:var(--blockquote-font-style);font-weight:var(--blockquote-font-weight);quotes:\"“\" \"”\" \"‘\" \"’\"}.markdown-section blockquote em{font-family:var(--blockquote-em-font-family);font-size:var(--blockquote-em-font-size);font-style:var(--blockquote-em-font-style);font-weight:var(--blockquote-em-font-weight)}.markdown-section blockquote p:first-child{margin-top:0}.markdown-section blockquote p:first-child:before,.markdown-section blockquote p:first-child:after{color:var(--blockquote-quotes-color);font-family:var(--blockquote-quotes-font-family);font-size:var(--blockquote-quotes-font-size);line-height:0}.markdown-section blockquote p:first-child:before{content:var(--blockquote-quotes-open);margin-right:0.15em;vertical-align:-0.45em}.markdown-section blockquote p:first-child:after{content:var(--blockquote-quotes-close);margin-left:0.15em;vertical-align:-0.55em}.markdown-section blockquote p:last-child{margin-bottom:0}.markdown-section code{font-family:var(--code-font-family);font-size:var(--code-font-size);font-weight:var(--code-font-weight);line-height:inherit}.markdown-section code:not([class*=\"lang-\"]):not([class*=\"language-\"]){margin:var(--code-inline-margin);padding:var(--code-inline-padding);border-radius:var(--code-inline-border-radius);background:var(--code-inline-background);color:var(--code-inline-color, currentColor);white-space:nowrap}.markdown-section h1:first-child,.markdown-section h2:first-child,.markdown-section h3:first-child,.markdown-section h4:first-child,.markdown-section h5:first-child,.markdown-section h6:first-child{margin-top:0}.markdown-section h1+h2,.markdown-section h1+h3,.markdown-section h1+h4,.markdown-section h1+h5,.markdown-section h1+h6,.markdown-section h2+h3,.markdown-section h2+h4,.markdown-section h2+h5,.markdown-section h2+h6,.markdown-section h3+h4,.markdown-section h3+h5,.markdown-section h3+h6,.markdown-section h4+h5,.markdown-section h4+h6,.markdown-section h5+h6{margin-top:1rem}.markdown-section h1{margin:var(--heading-h1-margin, var(--heading-margin));padding:var(--heading-h1-padding, var(--heading-padding));border-width:var(--heading-h1-border-width, 0);border-style:var(--heading-h1-border-style);border-color:var(--heading-h1-border-color);font-family:var(--heading-h1-font-family, var(--heading-font-family));font-size:var(--heading-h1-font-size);font-weight:var(--heading-h1-font-weight, var(--heading-font-weight));line-height:var(--base-line-height);color:var(--heading-h1-color, var(--heading-color))}.markdown-section h2{margin:var(--heading-h2-margin, var(--heading-margin));padding:var(--heading-h2-padding, var(--heading-padding));border-width:var(--heading-h2-border-width, 0);border-style:var(--heading-h2-border-style);border-color:var(--heading-h2-border-color);font-family:var(--heading-h2-font-family, var(--heading-font-family));font-size:var(--heading-h2-font-size);font-weight:var(--heading-h2-font-weight, var(--heading-font-weight));line-height:var(--base-line-height);color:var(--heading-h2-color, var(--heading-color))}.markdown-section h3{margin:var(--heading-h3-margin, var(--heading-margin));padding:var(--heading-h3-padding, var(--heading-padding));border-width:var(--heading-h3-border-width, 0);border-style:var(--heading-h3-border-style);border-color:var(--heading-h3-border-color);font-family:var(--heading-h3-font-family, var(--heading-font-family));font-size:var(--heading-h3-font-size);font-weight:var(--heading-h3-font-weight, var(--heading-font-weight));color:var(--heading-h3-color, var(--heading-color))}.markdown-section h4{margin:var(--heading-h4-margin, var(--heading-margin));padding:var(--heading-h4-padding, var(--heading-padding));border-width:var(--heading-h4-border-width, 0);border-style:var(--heading-h4-border-style);border-color:var(--heading-h4-border-color);font-family:var(--heading-h4-font-family, var(--heading-font-family));font-size:var(--heading-h4-font-size);font-weight:var(--heading-h4-font-weight, var(--heading-font-weight));color:var(--heading-h4-color, var(--heading-color))}.markdown-section h5{margin:var(--heading-h5-margin, var(--heading-margin));padding:var(--heading-h5-padding, var(--heading-padding));border-width:var(--heading-h5-border-width, 0);border-style:var(--heading-h5-border-style);border-color:var(--heading-h5-border-color);font-family:var(--heading-h5-font-family, var(--heading-font-family));font-weight:var(--heading-h5-font-weight, var(--heading-font-weight));color:var(--heading-h5-color, var(--heading-color))}.markdown-section h5,.markdown-section h5+p,.markdown-section h5+p+p{font-size:var(--heading-h5-font-size)}.markdown-section h6{margin:var(--heading-h6-margin, var(--heading-margin));padding:var(--heading-h6-padding, var(--heading-padding));border-width:var(--heading-h6-border-width, 0);border-style:var(--heading-h6-border-style);border-color:var(--heading-h6-border-color);font-family:var(--heading-h6-font-family, var(--heading-font-family));font-weight:var(--heading-h6-font-weight, var(--heading-font-weight))}.markdown-section h6,.markdown-section h6+p,.markdown-section h6+p+p{font-size:var(--heading-h6-font-size);color:var(--heading-h6-color, var(--heading-color))}.markdown-section iframe{margin:1em 0}.markdown-section img{max-width:100%}.markdown-section kbd{display:inline-block;min-width:var(--kbd-min-width);margin:var(--kbd-margin);padding:var(--kbd-padding);border:var(--kbd-border);border-radius:var(--kbd-border-radius);background:var(--kbd-background);font-family:inherit;font-size:var(--kbd-font-size);text-align:center;letter-spacing:0;line-height:1;color:var(--kbd-color)}.markdown-section kbd+kbd{margin-left:-0.15em}.markdown-section table{display:block;overflow:auto;margin:1rem 0;border-spacing:0;border-collapse:collapse}.markdown-section th,.markdown-section td{padding:var(--table-cell-padding)}.markdown-section th:not([align]){text-align:left}.markdown-section thead{border-color:var(--table-head-border-color);border-style:solid;border-width:var(--table-head-border-width, 0);background:var(--table-head-background)}.markdown-section th{font-weight:var(--table-head-font-weight);color:var(--strong-color)}.markdown-section td{border-color:var(--table-cell-border-color);border-style:solid;border-width:var(--table-cell-border-width, 0)}.markdown-section tbody{border-color:var(--table-body-border-color);border-style:solid;border-width:var(--table-body-border-width, 0)}.markdown-section tbody tr:nth-child(odd){background:var(--table-row-odd-background)}.markdown-section tbody tr:nth-child(even){background:var(--table-row-even-background)}.markdown-section>ul .task-list-item{margin-left:-1.25em}.markdown-section>ul .task-list-item .task-list-item{margin-left:0}.markdown-section .table-wrapper table{display:table;width:100%}.markdown-section .table-wrapper td::before{display:none}@media (max-width: 30em){.markdown-section .table-wrapper tbody,.markdown-section .table-wrapper tr,.markdown-section .table-wrapper td{display:block}.markdown-section .table-wrapper th,.markdown-section .table-wrapper td{border:none}.markdown-section .table-wrapper thead{display:none}.markdown-section .table-wrapper tr{border-color:var(--table-cell-border-color);border-style:solid;border-width:var(--table-cell-border-width, 0);padding:var(--table-cell-padding)}.markdown-section .table-wrapper tr:not(:last-child){border-bottom:0}.markdown-section .table-wrapper td{display:-ms-flexbox;display:flex;padding:0.15em 0}.markdown-section .table-wrapper td::before{display:block;min-width:8em;max-width:8em;font-weight:bold;text-align:left}}.markdown-section .tip,.markdown-section .warn{position:relative;margin:2em 0;padding:var(--notice-padding);border-width:var(--notice-border-width, 0);border-style:var(--notice-border-style);border-color:var(--notice-border-color);border-radius:var(--notice-border-radius);background:var(--notice-background);font-family:var(--notice-font-family);font-weight:var(--notice-font-weight);color:var(--notice-color)}.markdown-section .tip:before,.markdown-section .warn:before{display:inline-block;position:var(--notice-before-position, relative);top:var(--notice-before-top);left:var(--notice-before-left);height:var(--notice-before-height);width:var(--notice-before-width);margin:var(--notice-before-margin);padding:var(--notice-before-padding);border-radius:var(--notice-before-border-radius);line-height:var(--notice-before-line-height);font-family:var(--notice-before-font-family);font-size:var(--notice-before-font-size);font-weight:var(--notice-before-font-weight);text-align:center}.markdown-section .tip{border-width:var(--notice-important-border-width, var(--notice-border-width, 0));border-style:var(--notice-important-border-style, var(--notice-border-style));border-color:var(--notice-important-border-color, var(--notice-border-color));background:var(--notice-important-background, var(--notice-background));color:var(--notice-important-color, var(--notice-color))}.markdown-section .tip:before{content:var(--notice-important-before-content, var(--notice-before-content));background:var(--notice-important-before-background, var(--notice-before-background));color:var(--notice-important-before-color, var(--notice-before-color))}.markdown-section .warn{border-width:var(--notice-tip-border-width, var(--notice-border-width, 0));border-style:var(--notice-tip-border-style, var(--notice-border-style));border-color:var(--notice-tip-border-color, var(--notice-border-color));background:var(--notice-tip-background, var(--notice-background));color:var(--notice-tip-color, var(--notice-color))}.markdown-section .warn:before{content:var(--notice-tip-before-content, var(--notice-before-content));background:var(--notice-tip-before-background, var(--notice-before-background));color:var(--notice-tip-before-color, var(--notice-before-color))}.cover{display:none;position:relative;z-index:20;min-height:100vh;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;padding:calc(var(--cover-border-inset, 0px) + var(--cover-border-width, 0px));color:var(--cover-color);text-align:var(--cover-text-align)}@media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none){.cover{height:100vh}}.cover:before,.cover:after{content:'';position:absolute}.cover:before{top:0;bottom:0;left:0;right:0;background-blend-mode:var(--cover-background-blend-mode);background-color:var(--cover-background-color);background-image:var(--cover-background-image);background-position:var(--cover-background-position);background-repeat:var(--cover-background-repeat);background-size:var(--cover-background-size)}.cover:after{top:var(--cover-border-inset, 0);bottom:var(--cover-border-inset, 0);left:var(--cover-border-inset, 0);right:var(--cover-border-inset, 0);border-width:var(--cover-border-width, 0);border-style:solid;border-color:var(--cover-border-color)}.cover a{border-bottom:var(--cover-link-border-bottom);color:var(--cover-link-color);text-decoration:var(--cover-link-text-decoration);text-decoration-color:var(--cover-link-text-decoration-color)}.cover a:hover{border-bottom:var(--cover-link-border-bottom--hover, var(--cover-link-border-bottom));color:var(--cover-link-color--hover, var(--cover-link-color));text-decoration:var(--cover-link-text-decoration--hover, var(--cover-link-text-decoration));text-decoration-color:var(--cover-link-text-decoration-color--hover, var(--cover-link-text-decoration-color))}.cover h1{color:var(--cover-heading-color);position:relative;margin:0;font-size:var(--cover-heading-font-size);font-weight:var(--cover-heading-font-weight);line-height:1.2}.cover h1 a,.cover h1 a:hover{display:block;border-bottom:none;color:inherit;text-decoration:none}.cover h1 small{position:absolute;bottom:0;margin-left:0.5em}.cover h1 span{font-size:calc(var(--cover-heading-font-size-min) * 1px)}@media (min-width: 26em){.cover h1 span{font-size:calc((var(--cover-heading-font-size-min) * 1px) + (var(--cover-heading-font-size-max) - var(--cover-heading-font-size-min)) * ((100vw - 420px) / (1024 - 420)))}}@media (min-width: 64em){.cover h1 span{font-size:calc(var(--cover-heading-font-size-max) * 1px)}}.cover blockquote{margin:0;color:var(--cover-blockquote-color);font-size:var(--cover-blockquote-font-size)}.cover blockquote a{color:inherit}.cover ul{padding:0;list-style-type:none}.cover .cover-main{position:relative;z-index:1;max-width:var(--cover-max-width);margin:var(--cover-margin);padding:0 45px}.cover .cover-main>p:last-child{margin:1.25em -.25em}.cover .cover-main>p:last-child a{display:block;margin:.375em .25em;padding:var(--cover-button-padding);border:var(--cover-button-border);border-radius:var(--cover-button-border-radius);box-shadow:var(--cover-button-box-shadow);background:var(--cover-button-background);text-align:center;text-decoration:var(--cover-button-text-decoration);text-decoration-color:var(--cover-button-text-decoration-color);color:var(--cover-button-color);white-space:nowrap;transition:var(--cover-button-transition)}.cover .cover-main>p:last-child a:hover{border:var(--cover-button-border--hover, var(--cover-button-border));box-shadow:var(--cover-button-box-shadow--hover, var(--cover-button-box-shadow));background:var(--cover-button-background--hover, var(--cover-button-background));text-decoration:var(--cover-button-text-decoration--hover, var(--cover-button-text-decoration));text-decoration-color:var(--cover-button-text-decoration-color--hover, var(--cover-button-text-decoration-color));color:var(--cover-button-color--hover, var(--cover-button-color))}.cover .cover-main>p:last-child a:first-child{border:var(--cover-button-primary-border, var(--cover-button-border));box-shadow:var(--cover-button-primary-box-shadow, var(--cover-button-box-shadow));background:var(--cover-button-primary-background, var(--cover-button-background));text-decoration:var(--cover-button-primary-text-decoration, var(--cover-button-text-decoration));text-decoration-color:var(--cover-button-primary-text-decoration-color, var(--cover-button-text-decoration-color));color:var(--cover-button-primary-color, var(--cover-button-color))}.cover .cover-main>p:last-child a:first-child:hover{border:var(--cover-button-primary-border--hover, var(--cover-button-border--hover, var(--cover-button-primary-border, var(--cover-button-border))));box-shadow:var(--cover-button-primary-box-shadow--hover, var(--cover-button-box-shadow--hover, var(--cover-button-primary-box-shadow, var(--cover-button-box-shadow))));background:var(--cover-button-primary-background--hover, var(--cover-button-background--hover, var(--cover-button-primary-background, var(--cover-button-background))));text-decoration:var(--cover-button-primary-text-decoration--hover, var(--cover-button-text-decoration--hover, var(--cover-button-primary-text-decoration, var(--cover-button-text-decoration))));text-decoration-color:var(--cover-button-primary-text-decoration-color--hover, var(--cover-button-text-decoration-color--hover, var(--cover-button-primary-text-decoration-color, var(--cover-button-text-decoration-color))));color:var(--cover-button-primary-color--hover, var(--cover-button-color--hover, var(--cover-button-primary-color, var(--cover-button-color))))}@media (min-width: 30.01em){.cover .cover-main>p:last-child a{display:inline-block}}.cover .mask{visibility:var(--cover-background-mask-visibility, hidden);position:absolute;top:0;bottom:0;left:0;right:0;background-color:var(--cover-background-mask-color);opacity:var(--cover-background-mask-opacity)}.cover.has-mask .mask{visibility:visible}.cover.show{display:-ms-flexbox;display:flex}.app-nav{position:absolute;z-index:30;top:calc(35px - (0.5em * var(--base-line-height)));left:45px;right:80px;text-align:right}.app-nav.no-badge{right:45px}.app-nav li>img,.app-nav li>a>img{margin-top:-0.25em;vertical-align:middle}.app-nav li>img:first-child,.app-nav li>a>img:first-child{margin-right:0.5em}.app-nav a{display:block;line-height:1;transition:var(--navbar-root-transition)}.app-nav ul,.app-nav li{margin:0;padding:0;list-style:none}.app-nav li{position:relative}.app-nav>ul>li{display:inline-block;margin:var(--navbar-root-margin)}.app-nav>ul>li:first-child{margin-left:0}.app-nav>ul>li:last-child{margin-right:0}.app-nav>ul>li>a,.app-nav>ul>li>span{padding:var(--navbar-root-padding);border-width:var(--navbar-root-border-width, 0);border-style:var(--navbar-root-border-style);border-color:var(--navbar-root-border-color);border-radius:var(--navbar-root-border-radius);background:var(--navbar-root-background);color:var(--navbar-root-color);text-decoration:var(--navbar-root-text-decoration);text-decoration-color:var(--navbar-root-text-decoration-color)}.app-nav>ul>li>a:hover,.app-nav>ul>li>span:hover{background:var(--navbar-root-background--hover, var(--navbar-root-background));border-style:var(--navbar-root-border-style--hover, var(--navbar-root-border-style));border-color:var(--navbar-root-border-color--hover, var(--navbar-root-border-color));color:var(--navbar-root-color--hover, var(--navbar-root-color));text-decoration:var(--navbar-root-text-decoration--hover, var(--navbar-root-text-decoration));text-decoration-color:var(--navbar-root-text-decoration-color--hover, var(--navbar-root-text-decoration-color))}.app-nav>ul>li>a:not(:last-child),.app-nav>ul>li>span:not(:last-child){padding:var(--navbar-menu-root-padding, var(--navbar-root-padding));background:var(--navbar-menu-root-background, var(--navbar-root-background))}.app-nav>ul>li>a:not(:last-child):hover,.app-nav>ul>li>span:not(:last-child):hover{background:var(--navbar-menu-root-background--hover, var(--navbar-menu-root-background, var(--navbar-root-background--hover, var(--navbar-root-background))))}.app-nav>ul>li>a.active{background:var(--navbar-root-background--active, var(--navbar-root-background));border-style:var(--navbar-root-border-style--active, var(--navbar-root-border-style));border-color:var(--navbar-root-border-color--active, var(--navbar-root-border-color));color:var(--navbar-root-color--active, var(--navbar-root-color));text-decoration:var(--navbar-root-text-decoration--active, var(--navbar-root-text-decoration));text-decoration-color:var(--navbar-root-text-decoration-color--active, var(--navbar-root-text-decoration-color))}.app-nav>ul>li>a.active:not(:last-child):hover{background:var(--navbar-menu-root-background--active, var(--navbar-menu-root-background, var(--navbar-root-background--active, var(--navbar-root-background))))}.app-nav>ul>li ul{visibility:hidden;position:absolute;top:100%;right:50%;overflow-y:auto;box-sizing:border-box;max-height:calc(50vh);padding:var(--navbar-menu-padding);border-width:var(--navbar-menu-border-width, 0);border-style:solid;border-color:var(--navbar-menu-border-color);border-radius:var(--navbar-menu-border-radius);background:var(--navbar-menu-background);box-shadow:var(--navbar-menu-box-shadow);text-align:left;white-space:nowrap;opacity:0;transform:translate(50%, -0.35em);transition:var(--navbar-menu-transition)}.app-nav>ul>li ul li{white-space:nowrap}.app-nav>ul>li ul a{margin:var(--navbar-menu-link-margin);padding:var(--navbar-menu-link-padding);border-width:var(--navbar-menu-link-border-width, 0);border-style:var(--navbar-menu-link-border-style);border-color:var(--navbar-menu-link-border-color);border-radius:var(--navbar-menu-link-border-radius);background:var(--navbar-menu-link-background);color:var(--navbar-menu-link-color);text-decoration:var(--navbar-menu-link-text-decoration);text-decoration-color:var(--navbar-menu-link-text-decoration-color)}.app-nav>ul>li ul a:hover{background:var(--navbar-menu-link-background--hover, var(--navbar-menu-link-background));border-style:var(--navbar-menu-link-border-style--hover, var(--navbar-menu-link-border-style));border-color:var(--navbar-menu-link-border-color--hover, var(--navbar-menu-link-border-color));color:var(--navbar-menu-link-color--hover, var(--navbar-menu-link-color));text-decoration:var(--navbar-menu-link-text-decoration--hover, var(--navbar-menu-link-text-decoration));text-decoration-color:var(--navbar-menu-link-text-decoration-color--hover, var(--navbar-menu-link-text-decoration-color))}.app-nav>ul>li ul a.active{background:var(--navbar-menu-link-background--active, var(--navbar-menu-link-background));border-style:var(--navbar-menu-link-border-style--active, var(--navbar-menu-link-border-style));border-color:var(--navbar-menu-link-border-color--active, var(--navbar-menu-link-border-color));color:var(--navbar-menu-link-color--active, var(--navbar-menu-link-color));text-decoration:var(--navbar-menu-link-text-decoration--active, var(--navbar-menu-link-text-decoration));text-decoration-color:var(--navbar-menu-link-text-decoration-color--active, var(--navbar-menu-link-text-decoration-color))}.app-nav>ul>li:hover ul,.app-nav>ul>li:focus ul,.app-nav>ul>li.focus-within ul{visibility:visible;opacity:1;transform:translate(50%, 0)}.sidebar,.sidebar-toggle,main>.content{transition:all var(--sidebar-transition-duration) ease-out}@media (min-width: 48em){nav.app-nav{margin-left:var(--sidebar-width)}}main{position:relative;overflow-x:hidden;min-height:100vh}@media (min-width: 48em){main>.content{margin-left:var(--sidebar-width)}}.sidebar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;position:fixed;z-index:10;top:0;right:100%;overflow-y:auto;height:100vh;width:var(--sidebar-width);padding:var(--sidebar-padding);border-width:var(--sidebar-border-width);border-style:solid;border-color:var(--sidebar-border-color);background:var(--sidebar-background)}.sidebar>h1{margin:0;margin:var(--sidebar-name-margin);padding:var(--sidebar-name-padding);background:var(--sidebar-name-background);color:var(--sidebar-name-color);font-family:var(--sidebar-name-font-family);font-size:var(--sidebar-name-font-size);font-weight:var(--sidebar-name-font-weight);text-align:var(--sidebar-name-text-align)}.sidebar>h1 img{max-width:100%}.sidebar>h1 .app-name-link{color:var(--sidebar-name-color)}@media (min-width: 48em){.sidebar{position:absolute;transform:translateX(var(--sidebar-width))}}@media print{.sidebar{display:none}}.sidebar-nav,.sidebar nav{-ms-flex-order:1;order:1;margin:var(--sidebar-nav-margin);padding:var(--sidebar-nav-padding);background:var(--sidebar-nav-background)}.sidebar-nav ul,.sidebar nav ul{margin:0;padding:0;list-style:none}.sidebar-nav ul ul,.sidebar nav ul ul{margin-left:var(--sidebar-nav-indent)}.sidebar-nav a,.sidebar nav a{display:block;overflow:hidden;margin:var(--sidebar-nav-link-margin);padding:var(--sidebar-nav-link-padding);border-width:var(--sidebar-nav-link-border-width, 0);border-style:var(--sidebar-nav-link-border-style);border-color:var(--sidebar-nav-link-border-color);border-radius:var(--sidebar-nav-link-border-radius);background-color:var(--sidebar-nav-link-background-color);background-image:var(--sidebar-nav-link-background-image);background-position:var(--sidebar-nav-link-background-position);background-repeat:var(--sidebar-nav-link-background-repeat);background-size:var(--sidebar-nav-link-background-size);color:var(--sidebar-nav-link-color);font-weight:var(--sidebar-nav-link-font-weight);white-space:nowrap;text-decoration:var(--sidebar-nav-link-text-decoration);text-decoration-color:var(--sidebar-nav-link-text-decoration-color);text-overflow:ellipsis;transition:var(--sidebar-nav-link-transition)}.sidebar-nav a img,.sidebar nav a img{margin-top:-0.25em;vertical-align:middle}.sidebar-nav a img:first-child,.sidebar nav a img:first-child{margin-right:0.5em}.sidebar-nav a:hover,.sidebar nav a:hover{border-width:var(--sidebar-nav-link-border-width--hover, var(--sidebar-nav-link-border-width, 0));border-style:var(--sidebar-nav-link-border-style--hover, var(--sidebar-nav-link-border-style));border-color:var(--sidebar-nav-link-border-color--hover, var(--sidebar-nav-link-border-color));background-color:var(--sidebar-nav-link-background-color--hover, var(--sidebar-nav-link-background-color));background-image:var(--sidebar-nav-link-background-image--hover, var(--sidebar-nav-link-background-image));background-position:var(--sidebar-nav-link-background-position--hover, var(--sidebar-nav-link-background-position));background-size:var(--sidebar-nav-link-background-size--hover, var(--sidebar-nav-link-background-size));color:var(--sidebar-nav-link-color--hover, var(--sidebar-nav-link-color));font-weight:var(--sidebar-nav-link-font-weight--hover, var(--sidebar-nav-link-font-weight));text-decoration:var(--sidebar-nav-link-text-decoration--hover, var(--sidebar-nav-link-text-decoration));text-decoration-color:var(--sidebar-nav-link-text-decoration-color)}.sidebar-nav ul>li>span,.sidebar-nav ul>li>strong,.sidebar nav ul>li>span,.sidebar nav ul>li>strong{display:block;margin:var(--sidebar-nav-strong-margin);padding:var(--sidebar-nav-strong-padding);border-width:var(--sidebar-nav-strong-border-width, 0);border-style:solid;border-color:var(--sidebar-nav-strong-border-color);color:var(--sidebar-nav-strong-color);font-size:var(--sidebar-nav-strong-font-size);font-weight:var(--sidebar-nav-strong-font-weight);text-transform:var(--sidebar-nav-strong-text-transform)}.sidebar-nav ul>li>span+ul,.sidebar-nav ul>li>strong+ul,.sidebar nav ul>li>span+ul,.sidebar nav ul>li>strong+ul{margin-left:0}.sidebar-nav ul>li:first-child>span,.sidebar-nav ul>li:first-child>strong,.sidebar nav ul>li:first-child>span,.sidebar nav ul>li:first-child>strong{margin-top:0}.sidebar-nav::-webkit-scrollbar,.sidebar nav::-webkit-scrollbar{width:0}@supports (width: env(safe-area-inset)){@media only screen and (orientation: landscape){.sidebar-nav,.sidebar nav{margin-left:calc(env(safe-area-inset-left) / 2)}}}.sidebar-nav li>a:before,.sidebar-nav li>strong:before{display:inline-block}.sidebar-nav li>a{background-repeat:var(--sidebar-nav-pagelink-background-repeat);background-size:var(--sidebar-nav-pagelink-background-size)}.sidebar-nav li>a[href^=\"#/\"]:not([href*=\"?id=\"]){transition:var(--sidebar-nav-pagelink-transition)}.sidebar-nav li>a[href^=\"#/\"]:not([href*=\"?id=\"]),.sidebar-nav li>a[href^=\"#/\"]:not([href*=\"?id=\"]) ~ ul a{padding:var(--sidebar-nav-pagelink-padding, var(--sidebar-nav-link-padding))}.sidebar-nav li>a[href^=\"#/\"]:not([href*=\"?id=\"]):only-child{background-image:var(--sidebar-nav-pagelink-background-image);background-position:var(--sidebar-nav-pagelink-background-position)}.sidebar-nav li>a[href^=\"#/\"]:not([href*=\"?id=\"]):not(:only-child){background-image:var(--sidebar-nav-pagelink-background-image--loaded, var(--sidebar-nav-pagelink-background-image));background-position:var(--sidebar-nav-pagelink-background-position--loaded, var(--sidebar-nav-pagelink-background-image))}.sidebar-nav li.active>a,.sidebar-nav li.collapse>a{border-width:var(--sidebar-nav-link-border-width--active, var(--sidebar-nav-link-border-width));border-style:var(--sidebar-nav-link-border-style--active, var(--sidebar-nav-link-border-style));border-color:var(--sidebar-nav-link-border-color--active, var(--sidebar-nav-link-border-color));background-color:var(--sidebar-nav-link-background-color--active, var(--sidebar-nav-link-background-color));background-image:var(--sidebar-nav-link-background-image--active, var(--sidebar-nav-link-background-image));background-position:var(--sidebar-nav-link-background-position--active, var(--sidebar-nav-link-background-position));background-size:var(--sidebar-nav-link-background-size--active, var(--sidebar-nav-link-background-size));color:var(--sidebar-nav-link-color--active, var(--sidebar-nav-link-color));font-weight:var(--sidebar-nav-link-font-weight--active, var(--sidebar-nav-link-font-weight));text-decoration:var(--sidebar-nav-link-text-decoration--active, var(--sidebar-nav-link-text-decoration));text-decoration-color:var(--sidebar-nav-link-text-decoration-color)}.sidebar-nav li.active>a[href^=\"#/\"]:not([href*=\"?id=\"]):not(:only-child){background-image:var(--sidebar-nav-pagelink-background-image--active, var(--sidebar-nav-pagelink-background-image--loaded, var(--sidebar-nav-pagelink-background-image)));background-position:var(--sidebar-nav-pagelink-background-position--active, var(--sidebar-nav-pagelink-background-position--loaded, var(--sidebar-nav-pagelink-background-image)))}.sidebar-nav li.collapse>a[href^=\"#/\"]:not([href*=\"?id=\"]):not(:only-child){background-image:var(--sidebar-nav-pagelink-background-image--collapse, var(--sidebar-nav-pagelink-background-image--loaded, var(--sidebar-nav-pagelink-background-image)));background-position:var(--sidebar-nav-pagelink-background-position--collapse, var(--sidebar-nav-pagelink-background-position--loaded, var(--sidebar-nav-pagelink-background-image)))}.sidebar-nav li.collapse .app-sub-sidebar{display:none}.sidebar-nav>ul>li>a:before{content:var(--sidebar-nav-link-before-content-l1, var(--sidebar-nav-link-before-content));margin:var(--sidebar-nav-link-before-margin-l1, var(--sidebar-nav-link-before-margin));color:var(--sidebar-nav-link-before-color-l1, var(--sidebar-nav-link-before-color))}.sidebar-nav>ul>li.active>a:before{content:var(--sidebar-nav-link-before-content-l1--active, var(--sidebar-nav-link-before-content--active, var(--sidebar-nav-link-before-content-l1, var(--sidebar-nav-link-before-content))));color:var(--sidebar-nav-link-before-color-l1--active, var(--sidebar-nav-link-before-color--active, var(--sidebar-nav-link-before-color-l1, var(--sidebar-nav-link-before-color))))}.sidebar-nav>ul>li>ul>li>a:before{content:var(--sidebar-nav-link-before-content-l2, var(--sidebar-nav-link-before-content));margin:var(--sidebar-nav-link-before-margin-l2, var(--sidebar-nav-link-before-margin));color:var(--sidebar-nav-link-before-color-l2, var(--sidebar-nav-link-before-color))}.sidebar-nav>ul>li>ul>li.active>a:before{content:var(--sidebar-nav-link-before-content-l2--active, var(--sidebar-nav-link-before-content--active, var(--sidebar-nav-link-before-content-l2, var(--sidebar-nav-link-before-content))));color:var(--sidebar-nav-link-before-color-l2--active, var(--sidebar-nav-link-before-color--active, var(--sidebar-nav-link-before-color-l2, var(--sidebar-nav-link-before-color))))}.sidebar-nav>ul>li>ul>li>ul>li>a:before{content:var(--sidebar-nav-link-before-content-l3, var(--sidebar-nav-link-before-content));margin:var(--sidebar-nav-link-before-margin-l3, var(--sidebar-nav-link-before-margin));color:var(--sidebar-nav-link-before-color-l3, var(--sidebar-nav-link-before-color))}.sidebar-nav>ul>li>ul>li>ul>li.active>a:before{content:var(--sidebar-nav-link-before-content-l3--active, var(--sidebar-nav-link-before-content--active, var(--sidebar-nav-link-before-content-l3, var(--sidebar-nav-link-before-content))));color:var(--sidebar-nav-link-before-color-l3--active, var(--sidebar-nav-link-before-color--active, var(--sidebar-nav-link-before-color-l3, var(--sidebar-nav-link-before-color))))}.sidebar-nav>ul>li>ul>li>ul>li>ul>li>a:before{content:var(--sidebar-nav-link-before-content-l4, var(--sidebar-nav-link-before-content));margin:var(--sidebar-nav-link-before-margin-l4, var(--sidebar-nav-link-before-margin));color:var(--sidebar-nav-link-before-color-l4, var(--sidebar-nav-link-before-color))}.sidebar-nav>ul>li>ul>li>ul>li>ul>li.active>a:before{content:var(--sidebar-nav-link-before-content-l4--active, var(--sidebar-nav-link-before-content--active, var(--sidebar-nav-link-before-content-l4, var(--sidebar-nav-link-before-content))));color:var(--sidebar-nav-link-before-color-l4--active, var(--sidebar-nav-link-before-color--active, var(--sidebar-nav-link-before-color-l4, var(--sidebar-nav-link-before-color))))}.sidebar-nav>:last-child{margin-bottom:2rem}.sidebar-toggle,.sidebar-toggle-button{outline:none}.sidebar-toggle{position:fixed;z-index:11;top:0;bottom:0;left:0;width:40px;margin:0;padding:0;border:0;background:transparent;appearance:none;cursor:pointer}.sidebar-toggle .sidebar-toggle-button{position:absolute;top:var(--sidebar-toggle-offset-top);left:var(--sidebar-toggle-offset-left);height:var(--sidebar-toggle-height);width:var(--sidebar-toggle-width);border-radius:var(--sidebar-toggle-border-radius);border-width:var(--sidebar-toggle-border-width);border-style:var(--sidebar-toggle-border-style);border-color:var(--sidebar-toggle-border-color);background:var(--sidebar-toggle-background, transparent);color:var(--sidebar-toggle-icon-color)}.sidebar-toggle span{position:absolute;top:calc(50% - (var(--sidebar-toggle-icon-stroke-width) / 2));left:calc(50% - (var(--sidebar-toggle-icon-width) / 2));height:var(--sidebar-toggle-icon-stroke-width);width:var(--sidebar-toggle-icon-width);background-color:currentColor}.sidebar-toggle span:nth-child(1){margin-top:calc(0px - (var(--sidebar-toggle-icon-height) / 2))}.sidebar-toggle span:nth-child(3){margin-top:calc((var(--sidebar-toggle-icon-height) / 2))}@media (min-width: 48em){.sidebar-toggle{position:absolute;overflow:visible;left:0;transform:translateX(var(--sidebar-width))}}@media print{.sidebar-toggle{display:none}}@media (max-width: 47.99em){body.close .sidebar,body.close .sidebar-toggle,body.close main>.content{transform:translateX(var(--sidebar-width))}}@media (min-width: 48em){body.close main>.content{transform:translateX(0)}}@media (max-width: 47.99em){body.close nav.app-nav,body.close .github-corner{display:none}}@media (min-width: 48em){body.close .sidebar,body.close .sidebar-toggle{transform:translateX(0)}}@media (min-width: 48em){body.close nav.app-nav{margin-left:0}}@media (max-width: 47.99em){body.close .sidebar-toggle{width:100%}body.close .sidebar-toggle span{margin-top:0}body.close .sidebar-toggle span:nth-child(1){transform:rotate(45deg)}body.close .sidebar-toggle span:nth-child(2){display:none}body.close .sidebar-toggle span:nth-child(3){transform:rotate(-45deg)}}@media (min-width: 48em){body.close main>.content{margin-left:0}}@media (min-width: 48em){body.sticky .sidebar,body.sticky .sidebar-toggle{position:fixed}}body .docsify-copy-code-button,body .docsify-copy-code-button::after{background:var(--copycode-background);color:var(--copycode-color)}body .docsify-pagination-container{border-top:var(--pagination-border-top);color:var(--pagination-color)}body .pagination-item-label{font-size:var(--pagination-label-font-size)}body .pagination-item-label svg{color:var(--pagination-label-color);height:var(--pagination-chevron-height);stroke:var(--pagination-chevron-stroke);stroke-linecap:var(--pagination-chevron-stroke-linecap);stroke-linejoin:var(--pagination-chevron-stroke-linecap);stroke-width:var(--pagination-chevron-stroke-width)}body .pagination-item-title{color:var(--pagination-title-color);font-size:var(--pagination-title-font-size)}body .app-name.hide{display:block}body .sidebar{padding:var(--sidebar-padding)}.sidebar .search{margin:0;padding:0;border:0}.sidebar .search input{padding:0;line-height:1;font-size:inherit}.sidebar .search .clear-button{width:auto}.sidebar .search .clear-button svg{transform:scale(1)}.sidebar .search .matching-post{border:none}.sidebar .search p{font-size:inherit}.sidebar .search{-ms-flex-order:var(--search-flex-order);order:var(--search-flex-order);margin:var(--search-margin);padding:var(--search-padding);background:var(--search-background)}.sidebar .search a{color:inherit}.sidebar .search h2{margin:var(--search-result-heading-margin);font-size:var(--search-result-heading-font-size);font-weight:var(--search-result-heading-font-weight);color:var(--search-result-heading-color)}.sidebar .search .input-wrap{margin:var(--search-input-margin);background-color:var(--search-input-background-color);border-width:var(--search-input-border-width, 0);border-style:solid;border-color:var(--search-input-border-color);border-radius:var(--search-input-border-radius)}.sidebar .search input[type=\"search\"]{min-width:0;padding:var(--search-input-padding);border:none;background-color:transparent;background-image:var(--search-input-background-image);background-position:var(--search-input-background-position);background-repeat:var(--search-input-background-repeat);background-size:var(--search-input-background-size);font-size:var(--search-input-font-size);color:var(--search-input-color);transition:var(--search-input-transition)}.sidebar .search input[type=\"search\"]::-ms-clear{display:none}.sidebar .search input[type=\"search\"]:-ms-input-placeholder{color:var(--search-input-placeholder-color, gray)}.sidebar .search input[type=\"search\"]::placeholder{color:var(--search-input-placeholder-color, gray)}.sidebar .search input[type=\"search\"]::-webkit-input-placeholder{line-height:normal}.sidebar .search input[type=\"search\"]:focus{background-color:var(--search-input-background-color--focus, var(--search-input-background-color));background-image:var(--search-input-background-image--focus, var(--search-input-background-image));background-position:var(--search-input-background-position--focus, var(--search-input-background-position));background-size:var(--search-input-background-size--focus, var(--search-input-background-size))}@supports (width: env(safe-area-inset)){@media only screen and (orientation: landscape){.sidebar .search input[type=\"search\"]{margin-left:calc(env(safe-area-inset-left) / 2)}}}.sidebar .search p{overflow:hidden;text-overflow:ellipsis;-webkit-line-clamp:2}.sidebar .search p:empty{text-align:center}.sidebar .search .clear-button{margin:0 15px 0 0;padding:0;border:none;line-height:1;background:transparent;cursor:pointer}.sidebar .search .clear-button svg circle{fill:var(--search-clear-icon-color1, gray)}.sidebar .search .clear-button svg path{stroke:var(--search-clear-icon-color2, #fff)}.sidebar .search.show ~ *:not(h1){display:none}.sidebar .search .results-panel{display:none;font-size:var(--search-result-item-font-size)}.sidebar .search .results-panel.show{display:block}.sidebar .search .matching-post{margin:var(--search-result-item-margin);padding:var(--search-result-item-padding)}.sidebar .search .matching-post,.sidebar .search .matching-post:last-child{border-width:var(--search-result-item-border-width, 0) !important;border-style:var(--search-result-item-border-style);border-color:var(--search-result-item-border-color)}.sidebar .search .matching-post p{margin:0}.sidebar .search .search-keyword{margin:var(--search-result-keyword-margin);padding:var(--search-result-keyword-padding);border-radius:var(--search-result-keyword-border-radius);background-color:var(--search-result-keyword-background);color:var(--search-result-keyword-color, currentColor);font-style:normal;font-weight:var(--search-result-keyword-font-weight)}.medium-zoom-overlay,.medium-zoom-image--open{z-index:50 !important}.medium-zoom-overlay{background:var(--zoomimage-overlay-background) !important}:root{--mono-hue: 113;--mono-saturation: 0%;--mono-shade3: hsl(var(--mono-hue), var(--mono-saturation), 20%);--mono-shade2: hsl(var(--mono-hue), var(--mono-saturation), 30%);--mono-shade1: hsl(var(--mono-hue), var(--mono-saturation), 40%);--mono-base: hsl(var(--mono-hue), var(--mono-saturation), 50%);--mono-tint1: hsl(var(--mono-hue), var(--mono-saturation), 70%);--mono-tint2: hsl(var(--mono-hue), var(--mono-saturation), 89%);--mono-tint3: hsl(var(--mono-hue), var(--mono-saturation), 97%);--theme-hue: 204;--theme-saturation: 90%;--theme-lightness: 45%;--theme-color: hsl(var(--theme-hue), var(--theme-saturation), var(--theme-lightness));--modular-scale: 1.333;--modular-scale--2: calc(var(--modular-scale-2) / var(--modular-scale));--modular-scale--1: calc(var(--modular-scale-1) / var(--modular-scale));--modular-scale-1: 1rem;--modular-scale-2: calc(var(--modular-scale-1) * var(--modular-scale));--modular-scale-3: calc(var(--modular-scale-2) * var(--modular-scale));--modular-scale-4: calc(var(--modular-scale-3) * var(--modular-scale));--modular-scale-5: calc(var(--modular-scale-4) * var(--modular-scale));--font-size-xxxl: var(--modular-scale-5);--font-size-xxl: var(--modular-scale-4);--font-size-xl: var(--modular-scale-3);--font-size-l: var(--modular-scale-2);--font-size-m: var(--modular-scale-1);--font-size-s: var(--modular-scale--1);--font-size-xs: var(--modular-scale--2);--duration-slow: 1s;--duration-medium: 0.5s;--duration-fast: 0.25s;--spinner-size: 60px;--spinner-track-width: 4px;--spinner-track-color: rgba(0, 0, 0, 0.15);--spinner-transition-duration: var(--duration-medium)}:root{--base-background-color: #fff;--base-color: var(--mono-shade2);--base-font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";--base-font-size: 16px;--base-line-height: 1.7;--emoji-size: calc(var(--base-line-height) * 1em);--hr-border: 1px solid var(--mono-tint2);--mark-background: #ffecb3;--pre-font-family: var(--code-font-family);--pre-font-size: var(--code-font-size);--selection-color: #b4d5fe;--small-font-size: var(--font-size-s);--strong-color: var(--heading-color);--strong-font-weight: 600;--subsup-font-size: var(--font-size-s)}:root{--content-max-width: 55em;--blockquote-background: var(--mono-tint3);--blockquote-border-style: solid;--blockquote-border-radius: var(--border-radius-m);--code-font-family: Inconsolata, Consolas, Menlo, Monaco, \"Andale Mono WT\", \"Andale Mono\", \"Lucida Console\", \"DejaVu Sans Mono\", \"Bitstream Vera Sans Mono\", \"Courier New\", Courier, monospace;--code-font-size: calc(var(--font-size-m) * 0.95);--code-tab-size: 4;--code-block-border-radius: var(--border-radius-m);--code-block-line-height: var(--base-line-height);--code-block-margin: 1em 0;--code-block-padding: 1.75em 1.5em 1.5em 1.5em;--code-inline-background: var(--code-theme-background);--code-inline-border-radius: var(--border-radius-s);--code-inline-color: var(--code-theme-text);--code-inline-margin: 0 0.15em;--code-inline-padding: 0.125em 0.4em;--code-theme-background: var(--mono-tint3);--heading-color: var(--mono-shade3);--heading-margin: 2.5rem 0 0;--heading-h1-border-style: solid;--heading-h1-font-size: var(--font-size-xxl);--heading-h2-border-style: solid;--heading-h2-font-size: var(--font-size-xl);--heading-h3-border-style: solid;--heading-h3-font-size: var(--font-size-l);--heading-h4-border-style: solid;--heading-h4-font-size: var(--font-size-m);--heading-h5-border-style: solid;--heading-h5-font-size: var(--font-size-s);--heading-h6-border-style: solid;--heading-h6-color: var(--mono-base);--heading-h6-font-size: var(--font-size-s);--kbd-background: var(--mono-tint3);--kbd-border-radius: var(--border-radius-m);--kbd-margin: 0 0.3em;--kbd-min-width: 2.5em;--kbd-padding: 0.65em 0.5em;--link-text-decoration: underline;--notice-background: var(--mono-tint3);--notice-border-radius: var(--border-radius-m);--notice-border-style: solid;--notice-padding: 1em 1.5em;--table-cell-padding: 0.75em 0.5em;--table-head-border-color: var(--table-cell-border-color);--table-head-font-weight: var(--strong-font-weight);--table-row-odd-background: var(--mono-tint3)}:root{--cover-margin: 0 auto;--cover-max-width: 40em;--cover-text-align: center;--cover-background-color: var(--base-background-color);--cover-background-mask-color: var(--base-background-color);--cover-background-mask-opacity: 0.8;--cover-background-position: center center;--cover-background-repeat: no-repeat;--cover-background-size: cover;--cover-blockquote-font-size: var(--font-size-l);--cover-border-color: var(--theme-color);--cover-button-border: 1px solid var(--theme-color);--cover-button-border-radius: var(--border-radius-m);--cover-button-color: var(--theme-color);--cover-button-padding: 0.5em 2rem;--cover-button-text-decoration: none;--cover-button-transition: all var(--duration-fast) ease-in-out;--cover-button-primary-background: var(--theme-color);--cover-button-primary-border: 1px solid var(--theme-color);--cover-button-primary-color: #fff;--cover-heading-color: var(--theme-color);--cover-heading-font-size: var(--font-size-xxl);--cover-link-text-decoration: underline }:root{--navbar-root-border-style: solid;--navbar-root-margin: 0 0 0 1.5em;--navbar-root-transition: all var(--duration-fast);--navbar-menu-background: var(--base-background-color);--navbar-menu-border-radius: var(--border-radius-m);--navbar-menu-box-shadow: rgba(45,45,45,0.05) 0px 0px 1px, rgba(49,49,49,0.05) 0px 1px 2px, rgba(42,42,42,0.05) 0px 2px 4px, rgba(32,32,32,0.05) 0px 4px 8px, rgba(49,49,49,0.05) 0px 8px 16px, rgba(35,35,35,0.05) 0px 16px 32px;--navbar-menu-padding: 0.5em;--navbar-menu-transition: all var(--duration-fast);--navbar-menu-link-border-style: solid;--navbar-menu-link-margin: 0.75em 0.5em;--navbar-menu-link-padding: 0.2em 0 }:root{--copycode-background: #808080;--copycode-color: #fff}:root{--docsifytabs-border-color: var(--mono-tint2);--docsifytabs-border-radius-px: var(--border-radius-s);--docsifytabs-tab-background: var(--mono-tint3);--docsifytabs-tab-color: var(--mono-tint1)}:root{--pagination-border-top: 1px solid var(--mono-tint2);--pagination-chevron-height: 0.8em;--pagination-chevron-stroke: currentColor;--pagination-chevron-stroke-linecap: round;--pagination-chevron-stroke-width: 1px;--pagination-label-font-size: var(--font-size-s);--pagination-title-font-size: var(--font-size-l)}:root{--search-margin: 1.5rem 0 0;--search-input-background-repeat: no-repeat;--search-input-border-color: var(--mono-tint1);--search-input-border-width: 1px;--search-input-padding: 0.5em;--search-flex-order: 1;--search-result-heading-color: var(--heading-color);--search-result-heading-font-size: var(--base-font-size);--search-result-heading-font-weight: normal;--search-result-heading-margin: 0 0 0.25em;--search-result-item-border-color: var(--mono-tint2);--search-result-item-border-style: solid;--search-result-item-border-width: 0 0 1px 0;--search-result-item-padding: 1em 0;--search-result-keyword-background: var(--mark-background);--search-result-keyword-border-radius: var(--border-radius-s);--search-result-keyword-color: var(--mark-color);--search-result-keyword-margin: 0 0.1em;--search-result-keyword-padding: 0.2em 0}:root{--zoomimage-overlay-background: var(--base-background-color)}:root{--sidebar-background: var(--base-background-color);--sidebar-border-width: 0;--sidebar-padding: 0 25px;--sidebar-transition-duration: var(--duration-fast);--sidebar-width: 17rem;--sidebar-name-font-size: var(--font-size-l);--sidebar-name-margin: 1.5rem 0 0;--sidebar-name-text-align: center;--sidebar-nav-strong-border-color: var(--sidebar-border-color);--sidebar-nav-strong-color: var(--heading-color);--sidebar-nav-strong-font-weight: var(--strong-font-weight);--sidebar-nav-strong-margin: 1.5em 0 0.5em;--sidebar-nav-strong-padding: 0.25em 0;--sidebar-nav-indent: 1em;--sidebar-nav-margin: 1.5rem 0 0;--sidebar-nav-link-border-style: solid;--sidebar-nav-link-border-width: 0;--sidebar-nav-link-color: var(--base-color);--sidebar-nav-link-padding: 0.25em 0;--sidebar-nav-link-text-decoration--active: underline;--sidebar-nav-link-text-decoration--hover: underline;--sidebar-nav-link-before-margin: 0 0.35em 0 0;--sidebar-nav-pagelink-background-repeat: no-repeat;--sidebar-nav-pagelink-transition: var(--sidebar-nav-link-transition);--sidebar-toggle-border-radius: var(--border-radius-s);--sidebar-toggle-border-style: solid;--sidebar-toggle-border-width: 0;--sidebar-toggle-height: 36px;--sidebar-toggle-icon-color: var(--base-color);--sidebar-toggle-icon-height: 10px;--sidebar-toggle-icon-stroke-width: 1px;--sidebar-toggle-icon-width: 16px;--sidebar-toggle-offset-left: 0;--sidebar-toggle-offset-top: calc(35px - (var(--sidebar-toggle-height) / 2));--sidebar-toggle-width: 44px}:root{--code-theme-background: #f3f3f3;--code-theme-comment: #6e8090;--code-theme-function: #dd4a68;--code-theme-keyword: #07a;--code-theme-operator: #a67f59;--code-theme-punctuation: #999;--code-theme-selection: #b3d4fc;--code-theme-selector: #690;--code-theme-tag: #905;--code-theme-text: #333;--code-theme-variable: #e90}:root{--border-radius-s: 2px;--border-radius-m: 4px;--border-radius-l: 8px;--strong-font-weight: 600;--blockquote-border-color: var(--theme-color);--blockquote-border-radius: 0 var(--border-radius-m) var(--border-radius-m) 0;--blockquote-border-width: 0 0 0 4px;--code-inline-background: var(--mono-tint2);--code-theme-background: var(--mono-tint3);--heading-font-weight: var(--strong-font-weight);--heading-h1-font-weight: 400;--heading-h2-font-weight: 400;--heading-h2-border-color: var(--mono-tint2);--heading-h2-border-width: 0 0 1px 0;--heading-h2-margin: 2.5rem 0 1.5rem;--heading-h2-padding: 0 0 1rem 0;--kbd-border: 1px solid var(--mono-tint2);--notice-border-radius: 0 var(--border-radius-m) var(--border-radius-m) 0;--notice-border-width: 0 0 0 4px;--notice-padding: 1em 1.5em 1em 3em;--notice-before-border-radius: 100%;--notice-before-font-weight: bold;--notice-before-height: 1.5em;--notice-before-left: 0.75em;--notice-before-line-height: 1.5;--notice-before-margin: 0 0.25em 0 0;--notice-before-position: absolute;--notice-before-width: var(--notice-before-height);--notice-important-background: hsl(340, 60%, 96%);--notice-important-border-color: hsl(340, 90%, 45%);--notice-important-before-background: var(--notice-important-border-color) url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3E%3Cpath d='M10 14C10 15.1 9.1 16 8 16 6.9 16 6 15.1 6 14 6 12.9 6.9 12 8 12 9.1 12 10 12.9 10 14Z'/%3E%3Cpath d='M10 1.6C10 1.2 9.8 0.9 9.6 0.7 9.2 0.3 8.6 0 8 0 7.4 0 6.8 0.2 6.5 0.6 6.2 0.9 6 1.2 6 1.6 6 1.7 6 1.8 6 1.9L6.8 9.6C6.9 9.9 7 10.1 7.2 10.2 7.4 10.4 7.7 10.5 8 10.5 8.3 10.5 8.6 10.4 8.8 10.3 9 10.1 9.1 9.9 9.2 9.6L10 1.9C10 1.8 10 1.7 10 1.6Z'/%3E%3C/svg%3E\") center / 0.875em no-repeat;--notice-important-before-color: #fff;--notice-important-before-content: \"\";--notice-tip-background: hsl(204, 60%, 96%);--notice-tip-border-color: hsl(204, 90%, 45%);--notice-tip-before-background: var(--notice-tip-border-color) url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3E%3Cpath d='M9.1 0C10.2 0 10.7 0.7 10.7 1.6 10.7 2.6 9.8 3.6 8.6 3.6 7.6 3.6 7 3 7 2 7 1.1 7.7 0 9.1 0Z'/%3E%3Cpath d='M5.8 16C5 16 4.4 15.5 5 13.2L5.9 9.1C6.1 8.5 6.1 8.2 5.9 8.2 5.7 8.2 4.6 8.6 3.9 9.1L3.5 8.4C5.6 6.6 7.9 5.6 8.9 5.6 9.8 5.6 9.9 6.6 9.5 8.2L8.4 12.5C8.2 13.2 8.3 13.5 8.5 13.5 8.7 13.5 9.6 13.2 10.4 12.5L10.9 13.2C8.9 15.2 6.7 16 5.8 16Z'/%3E%3C/svg%3E\") center / 0.875em no-repeat;--notice-tip-before-color: #fff;--notice-tip-before-content: \"\";--table-cell-border-color: var(--mono-tint2);--table-cell-border-width: 1px 0;--cover-background-color: hsl(var(--theme-hue), 25%, 60%);--cover-background-image: radial-gradient(ellipse at center 115%, rgba(255, 255, 255, 0.9), transparent);--cover-blockquote-color: var(--strong-color);--cover-heading-color: #fff;--cover-heading-font-size-max: 56;--cover-heading-font-size-min: 34;--cover-heading-font-weight: 200;--navbar-root-color--active: var(--theme-color);--navbar-menu-border-radius: var(--border-radius-m);--navbar-menu-root-background: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9.6' height='6' viewBox='0 0 9.6 6'%3E%3Cpath d='M1.5 1.5l3.3 3 3.3-3' stroke-width='1.5' stroke='rgb%28179, 179, 179%29' fill='none' stroke-linecap='square' stroke-linejoin='miter' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E\") right no-repeat;--navbar-menu-root-padding: 0 18px 0 0;--search-input-background-color: #fff;--search-input-background-image: url(\"data:image/svg+xml,%3Csvg height='20px' width='20px' viewBox='0 0 24 24' fill='none' stroke='rgba(0, 0, 0, 0.3)' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' preserveAspectRatio='xMidYMid meet' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='10.5' cy='10.5' r='7.5' vector-effect='non-scaling-stroke'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='15.8' y2='15.8' vector-effect='non-scaling-stroke'%3E%3C/line%3E%3C/svg%3E\");--search-input-background-position: 21px center;--search-input-border-color: var(--sidebar-border-color);--search-input-border-width: 1px 0;--search-input-margin: 0 -25px;--search-input-padding: 0.65em 1em 0.65em 50px;--search-input-placeholder-color: rgba(0, 0, 0, 0.4);--search-clear-icon-color1: rgba(0, 0, 0, 0.3);--search-result-heading-font-weight: var(--strong-font-weight);--search-result-item-border-color: var(--sidebar-border-color);--search-result-keyword-border-radius: var(--border-radius-s);--sidebar-background: var(--mono-tint3);--sidebar-border-color: var(--mono-tint2);--sidebar-border-width: 0 1px 0 0;--sidebar-name-color: var(--theme-color);--sidebar-name-font-weight: 300;--sidebar-nav-strong-border-width: 0 0 1px 0;--sidebar-nav-strong-font-size: smaller;--sidebar-nav-strong-margin: 2em -25px 0.75em 0;--sidebar-nav-strong-padding: 0.25em 0 0.75em 0;--sidebar-nav-strong-text-transform: uppercase;--sidebar-nav-link-border-color: transparent;--sidebar-nav-link-border-color--active: var(--theme-color);--sidebar-nav-link-border-width: 0 4px 0 0;--sidebar-nav-link-color--active: var(--theme-color);--sidebar-nav-link-margin: 0 -25px 0 0;--sidebar-nav-link-text-decoration: none;--sidebar-nav-link-text-decoration--active: none;--sidebar-nav-link-text-decoration--hover: underline;--sidebar-nav-link-before-content-l3: '-';--sidebar-nav-pagelink-background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='7' height='11.2' viewBox='0 0 7 11.2'%3E%3Cpath d='M1.5 1.5l4 4.1 -4 4.1' stroke-width='1.5' stroke='rgb%28179, 179, 179%29' fill='none' stroke-linecap='square' stroke-linejoin='miter' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E\");--sidebar-nav-pagelink-background-image--active: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11.2' height='7' viewBox='0 0 11.2 7'%3E%3Cpath d='M1.5 1.5l4.1 4 4.1-4' stroke-width='1.5' stroke='rgb%2811, 135, 218%29' fill='none' stroke-linecap='square' stroke-linejoin='miter' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E\");--sidebar-nav-pagelink-background-image--collapse: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='7' height='11.2' viewBox='0 0 7 11.2'%3E%3Cpath d='M1.5 1.5l4 4.1 -4 4.1' stroke-width='1.5' stroke='rgb%2811, 135, 218%29' fill='none' stroke-linecap='square' stroke-linejoin='miter' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E\");--sidebar-nav-pagelink-background-image--loaded: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11.2' height='7' viewBox='0 0 11.2 7'%3E%3Cpath d='M1.5 1.5l4.1 4 4.1-4' stroke-width='1.5' stroke='rgb%2811, 135, 218%29' fill='none' stroke-linecap='square' stroke-linejoin='miter' vector-effect='non-scaling-stroke'/%3E%3C/svg%3E\");--sidebar-nav-pagelink-background-position: 3px center;--sidebar-nav-pagelink-background-position--active: left center;--sidebar-nav-pagelink-background-position--collapse: var(--sidebar-nav-pagelink-background-position);--sidebar-nav-pagelink-background-position--loaded: var(--sidebar-nav-pagelink-background-position--active);--sidebar-nav-pagelink-padding: 0.25em 0 0.25em 20px;--sidebar-nav-pagelink-transition: none;--sidebar-toggle-background: var(--sidebar-border-color);--sidebar-toggle-border-radius: 0 var(--border-radius-s) var(--border-radius-s) 0;--sidebar-toggle-width: 32px}\n/*# sourceMappingURL=theme-simple.css.map */"
  },
  {
    "path": "notes/docsify/unpkg/gitalk/dist/gitalk.css",
    "content": "@font-face {\n  font-family: octicons-link;\n  src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAZwABAAAAAACFQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABEU0lHAAAGaAAAAAgAAAAIAAAAAUdTVUIAAAZcAAAACgAAAAoAAQAAT1MvMgAAAyQAAABJAAAAYFYEU3RjbWFwAAADcAAAAEUAAACAAJThvmN2dCAAAATkAAAABAAAAAQAAAAAZnBnbQAAA7gAAACyAAABCUM+8IhnYXNwAAAGTAAAABAAAAAQABoAI2dseWYAAAFsAAABPAAAAZwcEq9taGVhZAAAAsgAAAA0AAAANgh4a91oaGVhAAADCAAAABoAAAAkCA8DRGhtdHgAAAL8AAAADAAAAAwGAACfbG9jYQAAAsAAAAAIAAAACABiATBtYXhwAAACqAAAABgAAAAgAA8ASm5hbWUAAAToAAABQgAAAlXu73sOcG9zdAAABiwAAAAeAAAAME3QpOBwcmVwAAAEbAAAAHYAAAB/aFGpk3jaTY6xa8JAGMW/O62BDi0tJLYQincXEypYIiGJjSgHniQ6umTsUEyLm5BV6NDBP8Tpts6F0v+k/0an2i+itHDw3v2+9+DBKTzsJNnWJNTgHEy4BgG3EMI9DCEDOGEXzDADU5hBKMIgNPZqoD3SilVaXZCER3/I7AtxEJLtzzuZfI+VVkprxTlXShWKb3TBecG11rwoNlmmn1P2WYcJczl32etSpKnziC7lQyWe1smVPy/Lt7Kc+0vWY/gAgIIEqAN9we0pwKXreiMasxvabDQMM4riO+qxM2ogwDGOZTXxwxDiycQIcoYFBLj5K3EIaSctAq2kTYiw+ymhce7vwM9jSqO8JyVd5RH9gyTt2+J/yUmYlIR0s04n6+7Vm1ozezUeLEaUjhaDSuXHwVRgvLJn1tQ7xiuVv/ocTRF42mNgZGBgYGbwZOBiAAFGJBIMAAizAFoAAABiAGIAznjaY2BkYGAA4in8zwXi+W2+MjCzMIDApSwvXzC97Z4Ig8N/BxYGZgcgl52BCSQKAA3jCV8CAABfAAAAAAQAAEB42mNgZGBg4f3vACQZQABIMjKgAmYAKEgBXgAAeNpjYGY6wTiBgZWBg2kmUxoDA4MPhGZMYzBi1AHygVLYQUCaawqDA4PChxhmh/8ODDEsvAwHgMKMIDnGL0x7gJQCAwMAJd4MFwAAAHjaY2BgYGaA4DAGRgYQkAHyGMF8NgYrIM3JIAGVYYDT+AEjAwuDFpBmA9KMDEwMCh9i/v8H8sH0/4dQc1iAmAkALaUKLgAAAHjaTY9LDsIgEIbtgqHUPpDi3gPoBVyRTmTddOmqTXThEXqrob2gQ1FjwpDvfwCBdmdXC5AVKFu3e5MfNFJ29KTQT48Ob9/lqYwOGZxeUelN2U2R6+cArgtCJpauW7UQBqnFkUsjAY/kOU1cP+DAgvxwn1chZDwUbd6CFimGXwzwF6tPbFIcjEl+vvmM/byA48e6tWrKArm4ZJlCbdsrxksL1AwWn/yBSJKpYbq8AXaaTb8AAHja28jAwOC00ZrBeQNDQOWO//sdBBgYGRiYWYAEELEwMTE4uzo5Zzo5b2BxdnFOcALxNjA6b2ByTswC8jYwg0VlNuoCTWAMqNzMzsoK1rEhNqByEyerg5PMJlYuVueETKcd/89uBpnpvIEVomeHLoMsAAe1Id4AAAAAAAB42oWQT07CQBTGv0JBhagk7HQzKxca2sJCE1hDt4QF+9JOS0nbaaYDCQfwCJ7Au3AHj+LO13FMmm6cl7785vven0kBjHCBhfpYuNa5Ph1c0e2Xu3jEvWG7UdPDLZ4N92nOm+EBXuAbHmIMSRMs+4aUEd4Nd3CHD8NdvOLTsA2GL8M9PODbcL+hD7C1xoaHeLJSEao0FEW14ckxC+TU8TxvsY6X0eLPmRhry2WVioLpkrbp84LLQPGI7c6sOiUzpWIWS5GzlSgUzzLBSikOPFTOXqly7rqx0Z1Q5BAIoZBSFihQYQOOBEdkCOgXTOHA07HAGjGWiIjaPZNW13/+lm6S9FT7rLHFJ6fQbkATOG1j2OFMucKJJsxIVfQORl+9Jyda6Sl1dUYhSCm1dyClfoeDve4qMYdLEbfqHf3O/AdDumsjAAB42mNgYoAAZQYjBmyAGYQZmdhL8zLdDEydARfoAqIAAAABAAMABwAKABMAB///AA8AAQAAAAAAAAAAAAAAAAABAAAAAA==) format('woff');\n}\n\n.markdown-body {\n  -ms-text-size-adjust: 100%;\n  -webkit-text-size-adjust: 100%;\n  line-height: 1.5;\n  color: #24292e;\n  font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Helvetica, Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\";\n  font-size: 16px;\n  line-height: 1.5;\n  word-wrap: break-word;\n}\n\n.markdown-body .pl-c {\n  color: #6a737d;\n}\n\n.markdown-body .pl-c1,\n.markdown-body .pl-s .pl-v {\n  color: #005cc5;\n}\n\n.markdown-body .pl-e,\n.markdown-body .pl-en {\n  color: #6f42c1;\n}\n\n.markdown-body .pl-smi,\n.markdown-body .pl-s .pl-s1 {\n  color: #24292e;\n}\n\n.markdown-body .pl-ent {\n  color: #22863a;\n}\n\n.markdown-body .pl-k {\n  color: #d73a49;\n}\n\n.markdown-body .pl-s,\n.markdown-body .pl-pds,\n.markdown-body .pl-s .pl-pse .pl-s1,\n.markdown-body .pl-sr,\n.markdown-body .pl-sr .pl-cce,\n.markdown-body .pl-sr .pl-sre,\n.markdown-body .pl-sr .pl-sra {\n  color: #032f62;\n}\n\n.markdown-body .pl-v,\n.markdown-body .pl-smw {\n  color: #e36209;\n}\n\n.markdown-body .pl-bu {\n  color: #b31d28;\n}\n\n.markdown-body .pl-ii {\n  color: #fafbfc;\n  background-color: #b31d28;\n}\n\n.markdown-body .pl-c2 {\n  color: #fafbfc;\n  background-color: #d73a49;\n}\n\n.markdown-body .pl-c2::before {\n  content: \"^M\";\n}\n\n.markdown-body .pl-sr .pl-cce {\n  font-weight: bold;\n  color: #22863a;\n}\n\n.markdown-body .pl-ml {\n  color: #735c0f;\n}\n\n.markdown-body .pl-mh,\n.markdown-body .pl-mh .pl-en,\n.markdown-body .pl-ms {\n  font-weight: bold;\n  color: #005cc5;\n}\n\n.markdown-body .pl-mi {\n  font-style: italic;\n  color: #24292e;\n}\n\n.markdown-body .pl-mb {\n  font-weight: bold;\n  color: #24292e;\n}\n\n.markdown-body .pl-md {\n  color: #b31d28;\n  background-color: #ffeef0;\n}\n\n.markdown-body .pl-mi1 {\n  color: #22863a;\n  background-color: #f0fff4;\n}\n\n.markdown-body .pl-mc {\n  color: #e36209;\n  background-color: #ffebda;\n}\n\n.markdown-body .pl-mi2 {\n  color: #f6f8fa;\n  background-color: #005cc5;\n}\n\n.markdown-body .pl-mdr {\n  font-weight: bold;\n  color: #6f42c1;\n}\n\n.markdown-body .pl-ba {\n  color: #586069;\n}\n\n.markdown-body .pl-sg {\n  color: #959da5;\n}\n\n.markdown-body .pl-corl {\n  text-decoration: underline;\n  color: #032f62;\n}\n\n.markdown-body .octicon {\n  display: inline-block;\n  vertical-align: text-top;\n  fill: currentColor;\n}\n\n.markdown-body a {\n  background-color: transparent;\n  -webkit-text-decoration-skip: objects;\n}\n\n.markdown-body a:active,\n.markdown-body a:hover {\n  outline-width: 0;\n}\n\n.markdown-body strong {\n  font-weight: inherit;\n}\n\n.markdown-body strong {\n  font-weight: bolder;\n}\n\n.markdown-body h1 {\n  font-size: 2em;\n  margin: 0.67em 0;\n}\n\n.markdown-body img {\n  border-style: none;\n}\n\n.markdown-body svg:not(:root) {\n  overflow: hidden;\n}\n\n.markdown-body code,\n.markdown-body kbd,\n.markdown-body pre {\n  font-family: monospace, monospace;\n  font-size: 1em;\n}\n\n.markdown-body hr {\n  -webkit-box-sizing: content-box;\n          box-sizing: content-box;\n  height: 0;\n  overflow: visible;\n}\n\n.markdown-body input {\n  font: inherit;\n  margin: 0;\n}\n\n.markdown-body input {\n  overflow: visible;\n}\n\n.markdown-body [type=\"checkbox\"] {\n  -webkit-box-sizing: border-box;\n          box-sizing: border-box;\n  padding: 0;\n}\n\n.markdown-body * {\n  -webkit-box-sizing: border-box;\n          box-sizing: border-box;\n}\n\n.markdown-body input {\n  font-family: inherit;\n  font-size: inherit;\n  line-height: inherit;\n}\n\n.markdown-body a {\n  color: #0366d6;\n  text-decoration: none;\n}\n\n.markdown-body a:hover {\n  text-decoration: underline;\n}\n\n.markdown-body strong {\n  font-weight: 600;\n}\n\n.markdown-body hr {\n  height: 0;\n  margin: 15px 0;\n  overflow: hidden;\n  background: transparent;\n  border: 0;\n  border-bottom: 1px solid #dfe2e5;\n}\n\n.markdown-body hr::before {\n  display: table;\n  content: \"\";\n}\n\n.markdown-body hr::after {\n  display: table;\n  clear: both;\n  content: \"\";\n}\n\n.markdown-body table {\n  border-spacing: 0;\n  border-collapse: collapse;\n}\n\n.markdown-body td,\n.markdown-body th {\n  padding: 0;\n}\n\n.markdown-body h1,\n.markdown-body h2,\n.markdown-body h3,\n.markdown-body h4,\n.markdown-body h5,\n.markdown-body h6 {\n  margin-top: 0;\n  margin-bottom: 0;\n}\n\n.markdown-body h1 {\n  font-size: 32px;\n  font-weight: 600;\n}\n\n.markdown-body h2 {\n  font-size: 24px;\n  font-weight: 600;\n}\n\n.markdown-body h3 {\n  font-size: 20px;\n  font-weight: 600;\n}\n\n.markdown-body h4 {\n  font-size: 16px;\n  font-weight: 600;\n}\n\n.markdown-body h5 {\n  font-size: 14px;\n  font-weight: 600;\n}\n\n.markdown-body h6 {\n  font-size: 12px;\n  font-weight: 600;\n}\n\n.markdown-body p {\n  margin-top: 0;\n  margin-bottom: 10px;\n}\n\n.markdown-body blockquote {\n  margin: 0;\n}\n\n.markdown-body ul,\n.markdown-body ol {\n  padding-left: 0;\n  margin-top: 0;\n  margin-bottom: 0;\n}\n\n.markdown-body ol ol,\n.markdown-body ul ol {\n  list-style-type: lower-roman;\n}\n\n.markdown-body ul ul ol,\n.markdown-body ul ol ol,\n.markdown-body ol ul ol,\n.markdown-body ol ol ol {\n  list-style-type: lower-alpha;\n}\n\n.markdown-body dd {\n  margin-left: 0;\n}\n\n.markdown-body code {\n  font-family: \"SFMono-Regular\", Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n  font-size: 12px;\n}\n\n.markdown-body pre {\n  margin-top: 0;\n  margin-bottom: 0;\n  font: 12px \"SFMono-Regular\", Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n}\n\n.markdown-body .octicon {\n  vertical-align: text-bottom;\n}\n\n.markdown-body .pl-0 {\n  padding-left: 0 !important;\n}\n\n.markdown-body .pl-1 {\n  padding-left: 4px !important;\n}\n\n.markdown-body .pl-2 {\n  padding-left: 8px !important;\n}\n\n.markdown-body .pl-3 {\n  padding-left: 16px !important;\n}\n\n.markdown-body .pl-4 {\n  padding-left: 24px !important;\n}\n\n.markdown-body .pl-5 {\n  padding-left: 32px !important;\n}\n\n.markdown-body .pl-6 {\n  padding-left: 40px !important;\n}\n\n.markdown-body::before {\n  display: table;\n  content: \"\";\n}\n\n.markdown-body::after {\n  display: table;\n  clear: both;\n  content: \"\";\n}\n\n.markdown-body>*:first-child {\n  margin-top: 0 !important;\n}\n\n.markdown-body>*:last-child {\n  margin-bottom: 0 !important;\n}\n\n.markdown-body a:not([href]) {\n  color: inherit;\n  text-decoration: none;\n}\n\n.markdown-body .anchor {\n  float: left;\n  padding-right: 4px;\n  margin-left: -20px;\n  line-height: 1;\n}\n\n.markdown-body .anchor:focus {\n  outline: none;\n}\n\n.markdown-body p,\n.markdown-body blockquote,\n.markdown-body ul,\n.markdown-body ol,\n.markdown-body dl,\n.markdown-body table,\n.markdown-body pre {\n  margin-top: 0;\n  margin-bottom: 16px;\n}\n\n.markdown-body hr {\n  height: 0.25em;\n  padding: 0;\n  margin: 24px 0;\n  background-color: #e1e4e8;\n  border: 0;\n}\n\n.markdown-body blockquote {\n  padding: 0 1em;\n  color: #6a737d;\n  border-left: 0.25em solid #dfe2e5;\n}\n\n.markdown-body blockquote>:first-child {\n  margin-top: 0;\n}\n\n.markdown-body blockquote>:last-child {\n  margin-bottom: 0;\n}\n\n.markdown-body kbd {\n  display: inline-block;\n  padding: 3px 5px;\n  font-size: 11px;\n  line-height: 10px;\n  color: #444d56;\n  vertical-align: middle;\n  background-color: #fafbfc;\n  border: solid 1px #c6cbd1;\n  border-bottom-color: #959da5;\n  border-radius: 3px;\n  -webkit-box-shadow: inset 0 -1px 0 #959da5;\n          box-shadow: inset 0 -1px 0 #959da5;\n}\n\n.markdown-body h1,\n.markdown-body h2,\n.markdown-body h3,\n.markdown-body h4,\n.markdown-body h5,\n.markdown-body h6 {\n  margin-top: 24px;\n  margin-bottom: 16px;\n  font-weight: 600;\n  line-height: 1.25;\n}\n\n.markdown-body h1 .octicon-link,\n.markdown-body h2 .octicon-link,\n.markdown-body h3 .octicon-link,\n.markdown-body h4 .octicon-link,\n.markdown-body h5 .octicon-link,\n.markdown-body h6 .octicon-link {\n  color: #1b1f23;\n  vertical-align: middle;\n  visibility: hidden;\n}\n\n.markdown-body h1:hover .anchor,\n.markdown-body h2:hover .anchor,\n.markdown-body h3:hover .anchor,\n.markdown-body h4:hover .anchor,\n.markdown-body h5:hover .anchor,\n.markdown-body h6:hover .anchor {\n  text-decoration: none;\n}\n\n.markdown-body h1:hover .anchor .octicon-link,\n.markdown-body h2:hover .anchor .octicon-link,\n.markdown-body h3:hover .anchor .octicon-link,\n.markdown-body h4:hover .anchor .octicon-link,\n.markdown-body h5:hover .anchor .octicon-link,\n.markdown-body h6:hover .anchor .octicon-link {\n  visibility: visible;\n}\n\n.markdown-body h1 {\n  padding-bottom: 0.3em;\n  font-size: 2em;\n  border-bottom: 1px solid #eaecef;\n}\n\n.markdown-body h2 {\n  padding-bottom: 0.3em;\n  font-size: 1.5em;\n  border-bottom: 1px solid #eaecef;\n}\n\n.markdown-body h3 {\n  font-size: 1.25em;\n}\n\n.markdown-body h4 {\n  font-size: 1em;\n}\n\n.markdown-body h5 {\n  font-size: 0.875em;\n}\n\n.markdown-body h6 {\n  font-size: 0.85em;\n  color: #6a737d;\n}\n\n.markdown-body ul,\n.markdown-body ol {\n  padding-left: 2em;\n}\n\n.markdown-body ul ul,\n.markdown-body ul ol,\n.markdown-body ol ol,\n.markdown-body ol ul {\n  margin-top: 0;\n  margin-bottom: 0;\n}\n\n.markdown-body li>p {\n  margin-top: 16px;\n}\n\n.markdown-body li+li {\n  margin-top: 0.25em;\n}\n\n.markdown-body dl {\n  padding: 0;\n}\n\n.markdown-body dl dt {\n  padding: 0;\n  margin-top: 16px;\n  font-size: 1em;\n  font-style: italic;\n  font-weight: 600;\n}\n\n.markdown-body dl dd {\n  padding: 0 16px;\n  margin-bottom: 16px;\n}\n\n.markdown-body table {\n  display: block;\n  width: 100%;\n  overflow: auto;\n}\n\n.markdown-body table th {\n  font-weight: 600;\n}\n\n.markdown-body table th,\n.markdown-body table td {\n  padding: 6px 13px;\n  border: 1px solid #dfe2e5;\n}\n\n.markdown-body table tr {\n  background-color: #fff;\n  border-top: 1px solid #c6cbd1;\n}\n\n.markdown-body table tr:nth-child(2n) {\n  background-color: #f6f8fa;\n}\n\n.markdown-body img {\n  max-width: 100%;\n  -webkit-box-sizing: content-box;\n          box-sizing: content-box;\n  background-color: #fff;\n}\n\n.markdown-body code {\n  padding: 0;\n  padding-top: 0.2em;\n  padding-bottom: 0.2em;\n  margin: 0;\n  font-size: 85%;\n  background-color: rgba(27,31,35,0.05);\n  border-radius: 3px;\n}\n\n.markdown-body code::before,\n.markdown-body code::after {\n  letter-spacing: -0.2em;\n  content: \"\\A0\";\n}\n\n.markdown-body pre {\n  word-wrap: normal;\n}\n\n.markdown-body pre>code {\n  padding: 0;\n  margin: 0;\n  font-size: 100%;\n  word-break: normal;\n  white-space: pre;\n  background: transparent;\n  border: 0;\n}\n\n.markdown-body .highlight {\n  margin-bottom: 16px;\n}\n\n.markdown-body .highlight pre {\n  margin-bottom: 0;\n  word-break: normal;\n}\n\n.markdown-body .highlight pre,\n.markdown-body pre {\n  padding: 16px;\n  overflow: auto;\n  font-size: 85%;\n  line-height: 1.45;\n  background-color: #f6f8fa;\n  border-radius: 3px;\n}\n\n.markdown-body pre code {\n  display: inline;\n  max-width: auto;\n  padding: 0;\n  margin: 0;\n  overflow: visible;\n  line-height: inherit;\n  word-wrap: normal;\n  background-color: transparent;\n  border: 0;\n}\n\n.markdown-body pre code::before,\n.markdown-body pre code::after {\n  content: normal;\n}\n\n.markdown-body .full-commit .btn-outline:not(:disabled):hover {\n  color: #005cc5;\n  border-color: #005cc5;\n}\n\n.markdown-body kbd {\n  display: inline-block;\n  padding: 3px 5px;\n  font: 11px \"SFMono-Regular\", Consolas, \"Liberation Mono\", Menlo, Courier, monospace;\n  line-height: 10px;\n  color: #444d56;\n  vertical-align: middle;\n  background-color: #fafbfc;\n  border: solid 1px #d1d5da;\n  border-bottom-color: #c6cbd1;\n  border-radius: 3px;\n  -webkit-box-shadow: inset 0 -1px 0 #c6cbd1;\n          box-shadow: inset 0 -1px 0 #c6cbd1;\n}\n\n.markdown-body :checked+.radio-label {\n  position: relative;\n  z-index: 1;\n  border-color: #0366d6;\n}\n\n.markdown-body .task-list-item {\n  list-style-type: none;\n}\n\n.markdown-body .task-list-item+.task-list-item {\n  margin-top: 3px;\n}\n\n.markdown-body .task-list-item input {\n  margin: 0 0.2em 0.25em -1.6em;\n  vertical-align: middle;\n}\n\n.markdown-body hr {\n  border-bottom-color: #eee;\n}\n/* variables */\n/* functions & mixins */\n/* variables - calculated */\n/* styles */\n.gt-container {\n  -webkit-box-sizing: border-box;\n          box-sizing: border-box;\n  font-size: 16px;\n/* loader */\n/* error */\n/* initing */\n/* no int */\n/* link */\n/* meta */\n/* popup */\n/* header */\n/* comments */\n/* comment */\n}\n.gt-container * {\n  -webkit-box-sizing: border-box;\n          box-sizing: border-box;\n}\n.gt-container a {\n  color: #6190e8;\n}\n.gt-container a:hover {\n  color: #81a6ed;\n  border-color: #81a6ed;\n}\n.gt-container a.is--active {\n  color: #333;\n  cursor: default !important;\n}\n.gt-container a.is--active:hover {\n  color: #333;\n}\n.gt-container .hide {\n  display: none !important;\n}\n.gt-container .gt-svg {\n  display: inline-block;\n  width: 1em;\n  height: 1em;\n  vertical-align: sub;\n}\n.gt-container .gt-svg svg {\n  width: 100%;\n  height: 100%;\n  fill: #6190e8;\n}\n.gt-container .gt-ico {\n  display: inline-block;\n}\n.gt-container .gt-ico-text {\n  margin-left: 0.3125em;\n}\n.gt-container .gt-ico-github {\n  width: 100%;\n  height: 100%;\n}\n.gt-container .gt-ico-github .gt-svg {\n  width: 100%;\n  height: 100%;\n}\n.gt-container .gt-ico-github svg {\n  fill: inherit;\n}\n.gt-container .gt-spinner {\n  position: relative;\n}\n.gt-container .gt-spinner::before {\n  content: '';\n  -webkit-box-sizing: border-box;\n          box-sizing: border-box;\n  position: absolute;\n  top: 3px;\n  width: 0.75em;\n  height: 0.75em;\n  margin-top: -0.1875em;\n  margin-left: -0.375em;\n  border-radius: 50%;\n  border: 1px solid #fff;\n  border-top-color: #6190e8;\n  -webkit-animation: gt-kf-rotate 0.6s linear infinite;\n          animation: gt-kf-rotate 0.6s linear infinite;\n}\n.gt-container .gt-loader {\n  position: relative;\n  border: 1px solid #999;\n  -webkit-animation: ease gt-kf-rotate 1.5s infinite;\n          animation: ease gt-kf-rotate 1.5s infinite;\n  display: inline-block;\n  font-style: normal;\n  width: 1.75em;\n  height: 1.75em;\n  line-height: 1.75em;\n  border-radius: 50%;\n}\n.gt-container .gt-loader:before {\n  content: '';\n  position: absolute;\n  display: block;\n  top: 0;\n  left: 50%;\n  margin-top: -0.1875em;\n  margin-left: -0.1875em;\n  width: 0.375em;\n  height: 0.375em;\n  background-color: #999;\n  border-radius: 50%;\n}\n.gt-container .gt-avatar {\n  display: inline-block;\n  width: 3.125em;\n  height: 3.125em;\n}\n@media (max-width: 479px) {\n  .gt-container .gt-avatar {\n    width: 2em;\n    height: 2em;\n  }\n}\n.gt-container .gt-avatar img {\n  width: 100%;\n  height: auto;\n  border-radius: 3px;\n}\n.gt-container .gt-avatar-github {\n  width: 3em;\n  height: 3em;\n}\n@media (max-width: 479px) {\n  .gt-container .gt-avatar-github {\n    width: 1.875em;\n    height: 1.875em;\n  }\n}\n.gt-container .gt-btn {\n  padding: 0.75em 1.25em;\n  display: inline-block;\n  line-height: 1;\n  text-decoration: none;\n  white-space: nowrap;\n  cursor: pointer;\n  border: 1px solid #6190e8;\n  border-radius: 5px;\n  background-color: #6190e8;\n  color: #fff;\n  outline: none;\n  font-size: 0.75em;\n}\n.gt-container .gt-btn-text {\n  font-weight: 400;\n}\n.gt-container .gt-btn-loading {\n  position: relative;\n  margin-left: 0.5em;\n  display: inline-block;\n  width: 0.75em;\n  height: 1em;\n  vertical-align: top;\n}\n.gt-container .gt-btn.is--disable {\n  cursor: not-allowed;\n  opacity: 0.5;\n}\n.gt-container .gt-btn-login {\n  margin-right: 0;\n}\n.gt-container .gt-btn-preview {\n  background-color: #fff;\n  color: #6190e8;\n}\n.gt-container .gt-btn-preview:hover {\n  background-color: #f2f2f2;\n  border-color: #81a6ed;\n}\n.gt-container .gt-btn-public:hover {\n  background-color: #81a6ed;\n  border-color: #81a6ed;\n}\n.gt-container .gt-error {\n  text-align: center;\n  margin: 0.625em;\n  color: #ff3860;\n}\n.gt-container .gt-initing {\n  padding: 1.25em 0;\n  text-align: center;\n}\n.gt-container .gt-initing-text {\n  margin: 0.625em auto;\n  font-size: 92%;\n}\n.gt-container .gt-no-init {\n  padding: 1.25em 0;\n  text-align: center;\n}\n.gt-container .gt-link {\n  border-bottom: 1px dotted #6190e8;\n}\n.gt-container .gt-link-counts,\n.gt-container .gt-link-project {\n  text-decoration: none;\n}\n.gt-container .gt-meta {\n  margin: 1.25em 0;\n  padding: 1em 0;\n  position: relative;\n  border-bottom: 1px solid #e9e9e9;\n  font-size: 1em;\n  position: relative;\n  z-index: 10;\n}\n.gt-container .gt-meta:before,\n.gt-container .gt-meta:after {\n  content: \" \";\n  display: table;\n}\n.gt-container .gt-meta:after {\n  clear: both;\n}\n.gt-container .gt-counts {\n  margin: 0 0.625em 0 0;\n}\n.gt-container .gt-user {\n  float: right;\n  margin: 0;\n  font-size: 92%;\n}\n.gt-container .gt-user-pic {\n  width: 16px;\n  height: 16px;\n  vertical-align: top;\n  margin-right: 0.5em;\n}\n.gt-container .gt-user-inner {\n  display: inline-block;\n  cursor: pointer;\n}\n.gt-container .gt-user .gt-ico {\n  margin: 0 0 0 0.3125em;\n}\n.gt-container .gt-user .gt-ico svg {\n  fill: inherit;\n}\n.gt-container .gt-user .is--poping .gt-ico svg {\n  fill: #6190e8;\n}\n.gt-container .gt-version {\n  color: #a1a1a1;\n  margin-left: 0.375em;\n}\n.gt-container .gt-copyright {\n  margin: 0 0.9375em 0.5em;\n  border-top: 1px solid #e9e9e9;\n  padding-top: 0.5em;\n}\n.gt-container .gt-popup {\n  position: absolute;\n  right: 0;\n  top: 2.375em;\n  background: #fff;\n  display: inline-block;\n  border: 1px solid #e9e9e9;\n  padding: 0.625em 0;\n  font-size: 0.875em;\n  letter-spacing: 0.5px;\n}\n.gt-container .gt-popup .gt-action {\n  cursor: pointer;\n  display: block;\n  margin: 0.5em 0;\n  padding: 0 1.125em;\n  position: relative;\n  text-decoration: none;\n}\n.gt-container .gt-popup .gt-action.is--active:before {\n  content: '';\n  width: 0.25em;\n  height: 0.25em;\n  background: #6190e8;\n  position: absolute;\n  left: 0.5em;\n  top: 0.4375em;\n}\n.gt-container .gt-header {\n  position: relative;\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n}\n.gt-container .gt-header-comment {\n  -webkit-box-flex: 1;\n      -ms-flex: 1;\n          flex: 1;\n  margin-left: 1.25em;\n}\n@media (max-width: 479px) {\n  .gt-container .gt-header-comment {\n    margin-left: 0.875em;\n  }\n}\n.gt-container .gt-header-textarea {\n  padding: 0.75em;\n  display: block;\n  -webkit-box-sizing: border-box;\n          box-sizing: border-box;\n  width: 100%;\n  min-height: 5.125em;\n  max-height: 15em;\n  border-radius: 5px;\n  border: 1px solid rgba(0,0,0,0.1);\n  font-size: 0.875em;\n  word-wrap: break-word;\n  resize: vertical;\n  background-color: #f6f6f6;\n  outline: none;\n  -webkit-transition: all 0.25s ease;\n  transition: all 0.25s ease;\n}\n.gt-container .gt-header-textarea:hover {\n  background-color: #fbfbfb;\n}\n.gt-container .gt-header-preview {\n  padding: 0.75em;\n  border-radius: 5px;\n  border: 1px solid rgba(0,0,0,0.1);\n  background-color: #f6f6f6;\n}\n.gt-container .gt-header-controls {\n  position: relative;\n  margin: 0.75em 0 0;\n}\n.gt-container .gt-header-controls:before,\n.gt-container .gt-header-controls:after {\n  content: \" \";\n  display: table;\n}\n.gt-container .gt-header-controls:after {\n  clear: both;\n}\n@media (max-width: 479px) {\n  .gt-container .gt-header-controls {\n    margin: 0;\n  }\n}\n.gt-container .gt-header-controls-tip {\n  font-size: 0.875em;\n  color: #6190e8;\n  text-decoration: none;\n  vertical-align: sub;\n}\n@media (max-width: 479px) {\n  .gt-container .gt-header-controls-tip {\n    display: none;\n  }\n}\n.gt-container .gt-header-controls .gt-btn {\n  float: right;\n  margin-left: 1.25em;\n}\n@media (max-width: 479px) {\n  .gt-container .gt-header-controls .gt-btn {\n    float: none;\n    width: 100%;\n    margin: 0.75em 0 0;\n  }\n}\n.gt-container:after {\n  content: '';\n  position: fixed;\n  bottom: 100%;\n  left: 0;\n  right: 0;\n  top: 0;\n  opacity: 0;\n}\n.gt-container.gt-input-focused {\n  position: relative;\n}\n.gt-container.gt-input-focused:after {\n  content: '';\n  position: fixed;\n  bottom: 0%;\n  left: 0;\n  right: 0;\n  top: 0;\n  background: #000;\n  opacity: 0.6;\n  -webkit-transition: opacity 0.3s, bottom 0s;\n  transition: opacity 0.3s, bottom 0s;\n  z-index: 9999;\n}\n.gt-container.gt-input-focused .gt-header-comment {\n  z-index: 10000;\n}\n.gt-container .gt-comments {\n  padding-top: 1.25em;\n}\n.gt-container .gt-comments-null {\n  text-align: center;\n}\n.gt-container .gt-comments-controls {\n  margin: 1.25em 0;\n  text-align: center;\n}\n.gt-container .gt-comment {\n  position: relative;\n  padding: 0.625em 0;\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n}\n.gt-container .gt-comment-content {\n  -webkit-box-flex: 1;\n      -ms-flex: 1;\n          flex: 1;\n  margin-left: 1.25em;\n  padding: 0.75em 1em;\n  background-color: #f9f9f9;\n  overflow: auto;\n  -webkit-transition: all ease 0.25s;\n  transition: all ease 0.25s;\n}\n.gt-container .gt-comment-content:hover {\n  -webkit-box-shadow: 0 0.625em 3.75em 0 #f4f4f4;\n          box-shadow: 0 0.625em 3.75em 0 #f4f4f4;\n}\n@media (max-width: 479px) {\n  .gt-container .gt-comment-content {\n    margin-left: 0.875em;\n    padding: 0.625em 0.75em;\n  }\n}\n.gt-container .gt-comment-header {\n  margin-bottom: 0.5em;\n  font-size: 0.875em;\n  position: relative;\n}\n.gt-container .gt-comment-username {\n  font-weight: 500;\n  color: #6190e8;\n  text-decoration: none;\n}\n.gt-container .gt-comment-username:hover {\n  text-decoration: underline;\n}\n.gt-container .gt-comment-text {\n  margin-left: 0.5em;\n  color: #a1a1a1;\n}\n.gt-container .gt-comment-date {\n  margin-left: 0.5em;\n  color: #a1a1a1;\n}\n.gt-container .gt-comment-like,\n.gt-container .gt-comment-edit,\n.gt-container .gt-comment-reply {\n  position: absolute;\n  height: 1.375em;\n}\n.gt-container .gt-comment-like:hover,\n.gt-container .gt-comment-edit:hover,\n.gt-container .gt-comment-reply:hover {\n  cursor: pointer;\n}\n.gt-container .gt-comment-like {\n  top: 0;\n  right: 2em;\n}\n.gt-container .gt-comment-edit,\n.gt-container .gt-comment-reply {\n  top: 0;\n  right: 0;\n}\n.gt-container .gt-comment-body {\n  color: #333 !important;\n}\n.gt-container .gt-comment-admin .gt-comment-content {\n  background-color: #f6f9fe;\n}\n@-webkit-keyframes gt-kf-rotate {\n  0% {\n    -webkit-transform: rotate(0);\n            transform: rotate(0);\n  }\n  100% {\n    -webkit-transform: rotate(360deg);\n            transform: rotate(360deg);\n  }\n}\n@keyframes gt-kf-rotate {\n  0% {\n    -webkit-transform: rotate(0);\n            transform: rotate(0);\n  }\n  100% {\n    -webkit-transform: rotate(360deg);\n            transform: rotate(360deg);\n  }\n}\n\n/*# sourceMappingURL=gitalk.css.map*/"
  },
  {
    "path": "notes/docsify/unpkg/gotop/jquery-2.1.0.js",
    "content": "/*!\n * jQuery JavaScript Library v2.1.0\n * http://jquery.com/\n *\n * Includes Sizzle.js\n * http://sizzlejs.com/\n *\n * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors\n * Released under the MIT license\n * http://jquery.org/license\n *\n * Date: 2014-01-23T21:10Z\n */\n\n\n(function( global, factory ) {\n\n\tif ( typeof module === \"object\" && typeof module.exports === \"object\" ) {\n\t\t// For CommonJS and CommonJS-like environments where a proper window is present,\n\t\t// execute the factory and get jQuery\n\t\t// For environments that do not inherently posses a window with a document\n\t\t// (such as Node.js), expose a jQuery-making factory as module.exports\n\t\t// This accentuates the need for the creation of a real window\n\t\t// e.g. var jQuery = require(\"jquery\")(window);\n\t\t// See ticket #14549 for more info\n\t\tmodule.exports = global.document ?\n\t\t\tfactory( global, true ) :\n\t\t\tfunction( w ) {\n\t\t\t\tif ( !w.document ) {\n\t\t\t\t\tthrow new Error( \"jQuery requires a window with a document\" );\n\t\t\t\t}\n\t\t\t\treturn factory( w );\n\t\t\t};\n\t} else {\n\t\tfactory( global );\n\t}\n\n// Pass this if window is not defined yet\n}(typeof window !== \"undefined\" ? window : this, function( window, noGlobal ) {\n\n// Can't do this because several apps including ASP.NET trace\n// the stack via arguments.caller.callee and Firefox dies if\n// you try to trace through \"use strict\" call chains. (#13335)\n// Support: Firefox 18+\n//\n\nvar arr = [];\n\nvar slice = arr.slice;\n\nvar concat = arr.concat;\n\nvar push = arr.push;\n\nvar indexOf = arr.indexOf;\n\nvar class2type = {};\n\nvar toString = class2type.toString;\n\nvar hasOwn = class2type.hasOwnProperty;\n\nvar trim = \"\".trim;\n\nvar support = {};\n\n\n\nvar\n\t// Use the correct document accordingly with window argument (sandbox)\n\tdocument = window.document,\n\n\tversion = \"2.1.0\",\n\n\t// Define a local copy of jQuery\n\tjQuery = function( selector, context ) {\n\t\t// The jQuery object is actually just the init constructor 'enhanced'\n\t\t// Need init if jQuery is called (just allow error to be thrown if not included)\n\t\treturn new jQuery.fn.init( selector, context );\n\t},\n\n\t// Matches dashed string for camelizing\n\trmsPrefix = /^-ms-/,\n\trdashAlpha = /-([\\da-z])/gi,\n\n\t// Used by jQuery.camelCase as callback to replace()\n\tfcamelCase = function( all, letter ) {\n\t\treturn letter.toUpperCase();\n\t};\n\njQuery.fn = jQuery.prototype = {\n\t// The current version of jQuery being used\n\tjquery: version,\n\n\tconstructor: jQuery,\n\n\t// Start with an empty selector\n\tselector: \"\",\n\n\t// The default length of a jQuery object is 0\n\tlength: 0,\n\n\ttoArray: function() {\n\t\treturn slice.call( this );\n\t},\n\n\t// Get the Nth element in the matched element set OR\n\t// Get the whole matched element set as a clean array\n\tget: function( num ) {\n\t\treturn num != null ?\n\n\t\t\t// Return a 'clean' array\n\t\t\t( num < 0 ? this[ num + this.length ] : this[ num ] ) :\n\n\t\t\t// Return just the object\n\t\t\tslice.call( this );\n\t},\n\n\t// Take an array of elements and push it onto the stack\n\t// (returning the new matched element set)\n\tpushStack: function( elems ) {\n\n\t\t// Build a new jQuery matched element set\n\t\tvar ret = jQuery.merge( this.constructor(), elems );\n\n\t\t// Add the old object onto the stack (as a reference)\n\t\tret.prevObject = this;\n\t\tret.context = this.context;\n\n\t\t// Return the newly-formed element set\n\t\treturn ret;\n\t},\n\n\t// Execute a callback for every element in the matched set.\n\t// (You can seed the arguments with an array of args, but this is\n\t// only used internally.)\n\teach: function( callback, args ) {\n\t\treturn jQuery.each( this, callback, args );\n\t},\n\n\tmap: function( callback ) {\n\t\treturn this.pushStack( jQuery.map(this, function( elem, i ) {\n\t\t\treturn callback.call( elem, i, elem );\n\t\t}));\n\t},\n\n\tslice: function() {\n\t\treturn this.pushStack( slice.apply( this, arguments ) );\n\t},\n\n\tfirst: function() {\n\t\treturn this.eq( 0 );\n\t},\n\n\tlast: function() {\n\t\treturn this.eq( -1 );\n\t},\n\n\teq: function( i ) {\n\t\tvar len = this.length,\n\t\t\tj = +i + ( i < 0 ? len : 0 );\n\t\treturn this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );\n\t},\n\n\tend: function() {\n\t\treturn this.prevObject || this.constructor(null);\n\t},\n\n\t// For internal use only.\n\t// Behaves like an Array's method, not like a jQuery method.\n\tpush: push,\n\tsort: arr.sort,\n\tsplice: arr.splice\n};\n\njQuery.extend = jQuery.fn.extend = function() {\n\tvar options, name, src, copy, copyIsArray, clone,\n\t\ttarget = arguments[0] || {},\n\t\ti = 1,\n\t\tlength = arguments.length,\n\t\tdeep = false;\n\n\t// Handle a deep copy situation\n\tif ( typeof target === \"boolean\" ) {\n\t\tdeep = target;\n\n\t\t// skip the boolean and the target\n\t\ttarget = arguments[ i ] || {};\n\t\ti++;\n\t}\n\n\t// Handle case when target is a string or something (possible in deep copy)\n\tif ( typeof target !== \"object\" && !jQuery.isFunction(target) ) {\n\t\ttarget = {};\n\t}\n\n\t// extend jQuery itself if only one argument is passed\n\tif ( i === length ) {\n\t\ttarget = this;\n\t\ti--;\n\t}\n\n\tfor ( ; i < length; i++ ) {\n\t\t// Only deal with non-null/undefined values\n\t\tif ( (options = arguments[ i ]) != null ) {\n\t\t\t// Extend the base object\n\t\t\tfor ( name in options ) {\n\t\t\t\tsrc = target[ name ];\n\t\t\t\tcopy = options[ name ];\n\n\t\t\t\t// Prevent never-ending loop\n\t\t\t\tif ( target === copy ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Recurse if we're merging plain objects or arrays\n\t\t\t\tif ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {\n\t\t\t\t\tif ( copyIsArray ) {\n\t\t\t\t\t\tcopyIsArray = false;\n\t\t\t\t\t\tclone = src && jQuery.isArray(src) ? src : [];\n\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclone = src && jQuery.isPlainObject(src) ? src : {};\n\t\t\t\t\t}\n\n\t\t\t\t\t// Never move original objects, clone them\n\t\t\t\t\ttarget[ name ] = jQuery.extend( deep, clone, copy );\n\n\t\t\t\t// Don't bring in undefined values\n\t\t\t\t} else if ( copy !== undefined ) {\n\t\t\t\t\ttarget[ name ] = copy;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Return the modified object\n\treturn target;\n};\n\njQuery.extend({\n\t// Unique for each copy of jQuery on the page\n\texpando: \"jQuery\" + ( version + Math.random() ).replace( /\\D/g, \"\" ),\n\n\t// Assume jQuery is ready without the ready module\n\tisReady: true,\n\n\terror: function( msg ) {\n\t\tthrow new Error( msg );\n\t},\n\n\tnoop: function() {},\n\n\t// See test/unit/core.js for details concerning isFunction.\n\t// Since version 1.3, DOM methods and functions like alert\n\t// aren't supported. They return false on IE (#2968).\n\tisFunction: function( obj ) {\n\t\treturn jQuery.type(obj) === \"function\";\n\t},\n\n\tisArray: Array.isArray,\n\n\tisWindow: function( obj ) {\n\t\treturn obj != null && obj === obj.window;\n\t},\n\n\tisNumeric: function( obj ) {\n\t\t// parseFloat NaNs numeric-cast false positives (null|true|false|\"\")\n\t\t// ...but misinterprets leading-number strings, particularly hex literals (\"0x...\")\n\t\t// subtraction forces infinities to NaN\n\t\treturn obj - parseFloat( obj ) >= 0;\n\t},\n\n\tisPlainObject: function( obj ) {\n\t\t// Not plain objects:\n\t\t// - Any object or value whose internal [[Class]] property is not \"[object Object]\"\n\t\t// - DOM nodes\n\t\t// - window\n\t\tif ( jQuery.type( obj ) !== \"object\" || obj.nodeType || jQuery.isWindow( obj ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Support: Firefox <20\n\t\t// The try/catch suppresses exceptions thrown when attempting to access\n\t\t// the \"constructor\" property of certain host objects, ie. |window.location|\n\t\t// https://bugzilla.mozilla.org/show_bug.cgi?id=814622\n\t\ttry {\n\t\t\tif ( obj.constructor &&\n\t\t\t\t\t!hasOwn.call( obj.constructor.prototype, \"isPrototypeOf\" ) ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} catch ( e ) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// If the function hasn't returned already, we're confident that\n\t\t// |obj| is a plain object, created by {} or constructed with new Object\n\t\treturn true;\n\t},\n\n\tisEmptyObject: function( obj ) {\n\t\tvar name;\n\t\tfor ( name in obj ) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t},\n\n\ttype: function( obj ) {\n\t\tif ( obj == null ) {\n\t\t\treturn obj + \"\";\n\t\t}\n\t\t// Support: Android < 4.0, iOS < 6 (functionish RegExp)\n\t\treturn typeof obj === \"object\" || typeof obj === \"function\" ?\n\t\t\tclass2type[ toString.call(obj) ] || \"object\" :\n\t\t\ttypeof obj;\n\t},\n\n\t// Evaluates a script in a global context\n\tglobalEval: function( code ) {\n\t\tvar script,\n\t\t\tindirect = eval;\n\n\t\tcode = jQuery.trim( code );\n\n\t\tif ( code ) {\n\t\t\t// If the code includes a valid, prologue position\n\t\t\t// strict mode pragma, execute code by injecting a\n\t\t\t// script tag into the document.\n\t\t\tif ( code.indexOf(\"use strict\") === 1 ) {\n\t\t\t\tscript = document.createElement(\"script\");\n\t\t\t\tscript.text = code;\n\t\t\t\tdocument.head.appendChild( script ).parentNode.removeChild( script );\n\t\t\t} else {\n\t\t\t// Otherwise, avoid the DOM node creation, insertion\n\t\t\t// and removal by using an indirect global eval\n\t\t\t\tindirect( code );\n\t\t\t}\n\t\t}\n\t},\n\n\t// Convert dashed to camelCase; used by the css and data modules\n\t// Microsoft forgot to hump their vendor prefix (#9572)\n\tcamelCase: function( string ) {\n\t\treturn string.replace( rmsPrefix, \"ms-\" ).replace( rdashAlpha, fcamelCase );\n\t},\n\n\tnodeName: function( elem, name ) {\n\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();\n\t},\n\n\t// args is for internal usage only\n\teach: function( obj, callback, args ) {\n\t\tvar value,\n\t\t\ti = 0,\n\t\t\tlength = obj.length,\n\t\t\tisArray = isArraylike( obj );\n\n\t\tif ( args ) {\n\t\t\tif ( isArray ) {\n\t\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\t\tvalue = callback.apply( obj[ i ], args );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( i in obj ) {\n\t\t\t\t\tvalue = callback.apply( obj[ i ], args );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// A special, fast, case for the most common use of each\n\t\t} else {\n\t\t\tif ( isArray ) {\n\t\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\t\tvalue = callback.call( obj[ i ], i, obj[ i ] );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( i in obj ) {\n\t\t\t\t\tvalue = callback.call( obj[ i ], i, obj[ i ] );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn obj;\n\t},\n\n\ttrim: function( text ) {\n\t\treturn text == null ? \"\" : trim.call( text );\n\t},\n\n\t// results is for internal usage only\n\tmakeArray: function( arr, results ) {\n\t\tvar ret = results || [];\n\n\t\tif ( arr != null ) {\n\t\t\tif ( isArraylike( Object(arr) ) ) {\n\t\t\t\tjQuery.merge( ret,\n\t\t\t\t\ttypeof arr === \"string\" ?\n\t\t\t\t\t[ arr ] : arr\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tpush.call( ret, arr );\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\tinArray: function( elem, arr, i ) {\n\t\treturn arr == null ? -1 : indexOf.call( arr, elem, i );\n\t},\n\n\tmerge: function( first, second ) {\n\t\tvar len = +second.length,\n\t\t\tj = 0,\n\t\t\ti = first.length;\n\n\t\tfor ( ; j < len; j++ ) {\n\t\t\tfirst[ i++ ] = second[ j ];\n\t\t}\n\n\t\tfirst.length = i;\n\n\t\treturn first;\n\t},\n\n\tgrep: function( elems, callback, invert ) {\n\t\tvar callbackInverse,\n\t\t\tmatches = [],\n\t\t\ti = 0,\n\t\t\tlength = elems.length,\n\t\t\tcallbackExpect = !invert;\n\n\t\t// Go through the array, only saving the items\n\t\t// that pass the validator function\n\t\tfor ( ; i < length; i++ ) {\n\t\t\tcallbackInverse = !callback( elems[ i ], i );\n\t\t\tif ( callbackInverse !== callbackExpect ) {\n\t\t\t\tmatches.push( elems[ i ] );\n\t\t\t}\n\t\t}\n\n\t\treturn matches;\n\t},\n\n\t// arg is for internal usage only\n\tmap: function( elems, callback, arg ) {\n\t\tvar value,\n\t\t\ti = 0,\n\t\t\tlength = elems.length,\n\t\t\tisArray = isArraylike( elems ),\n\t\t\tret = [];\n\n\t\t// Go through the array, translating each of the items to their new values\n\t\tif ( isArray ) {\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret.push( value );\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Go through every key on the object,\n\t\t} else {\n\t\t\tfor ( i in elems ) {\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret.push( value );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Flatten any nested arrays\n\t\treturn concat.apply( [], ret );\n\t},\n\n\t// A global GUID counter for objects\n\tguid: 1,\n\n\t// Bind a function to a context, optionally partially applying any\n\t// arguments.\n\tproxy: function( fn, context ) {\n\t\tvar tmp, args, proxy;\n\n\t\tif ( typeof context === \"string\" ) {\n\t\t\ttmp = fn[ context ];\n\t\t\tcontext = fn;\n\t\t\tfn = tmp;\n\t\t}\n\n\t\t// Quick check to determine if target is callable, in the spec\n\t\t// this throws a TypeError, but we will just return undefined.\n\t\tif ( !jQuery.isFunction( fn ) ) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// Simulated bind\n\t\targs = slice.call( arguments, 2 );\n\t\tproxy = function() {\n\t\t\treturn fn.apply( context || this, args.concat( slice.call( arguments ) ) );\n\t\t};\n\n\t\t// Set the guid of unique handler to the same of original handler, so it can be removed\n\t\tproxy.guid = fn.guid = fn.guid || jQuery.guid++;\n\n\t\treturn proxy;\n\t},\n\n\tnow: Date.now,\n\n\t// jQuery.support is not used in Core but other projects attach their\n\t// properties to it so it needs to exist.\n\tsupport: support\n});\n\n// Populate the class2type map\njQuery.each(\"Boolean Number String Function Array Date RegExp Object Error\".split(\" \"), function(i, name) {\n\tclass2type[ \"[object \" + name + \"]\" ] = name.toLowerCase();\n});\n\nfunction isArraylike( obj ) {\n\tvar length = obj.length,\n\t\ttype = jQuery.type( obj );\n\n\tif ( type === \"function\" || jQuery.isWindow( obj ) ) {\n\t\treturn false;\n\t}\n\n\tif ( obj.nodeType === 1 && length ) {\n\t\treturn true;\n\t}\n\n\treturn type === \"array\" || length === 0 ||\n\t\ttypeof length === \"number\" && length > 0 && ( length - 1 ) in obj;\n}\nvar Sizzle =\n/*!\n * Sizzle CSS Selector Engine v1.10.16\n * http://sizzlejs.com/\n *\n * Copyright 2013 jQuery Foundation, Inc. and other contributors\n * Released under the MIT license\n * http://jquery.org/license\n *\n * Date: 2014-01-13\n */\n(function( window ) {\n\nvar i,\n\tsupport,\n\tExpr,\n\tgetText,\n\tisXML,\n\tcompile,\n\toutermostContext,\n\tsortInput,\n\thasDuplicate,\n\n\t// Local document vars\n\tsetDocument,\n\tdocument,\n\tdocElem,\n\tdocumentIsHTML,\n\trbuggyQSA,\n\trbuggyMatches,\n\tmatches,\n\tcontains,\n\n\t// Instance-specific data\n\texpando = \"sizzle\" + -(new Date()),\n\tpreferredDoc = window.document,\n\tdirruns = 0,\n\tdone = 0,\n\tclassCache = createCache(),\n\ttokenCache = createCache(),\n\tcompilerCache = createCache(),\n\tsortOrder = function( a, b ) {\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t}\n\t\treturn 0;\n\t},\n\n\t// General-purpose constants\n\tstrundefined = typeof undefined,\n\tMAX_NEGATIVE = 1 << 31,\n\n\t// Instance methods\n\thasOwn = ({}).hasOwnProperty,\n\tarr = [],\n\tpop = arr.pop,\n\tpush_native = arr.push,\n\tpush = arr.push,\n\tslice = arr.slice,\n\t// Use a stripped-down indexOf if we can't use a native one\n\tindexOf = arr.indexOf || function( elem ) {\n\t\tvar i = 0,\n\t\t\tlen = this.length;\n\t\tfor ( ; i < len; i++ ) {\n\t\t\tif ( this[i] === elem ) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t},\n\n\tbooleans = \"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped\",\n\n\t// Regular expressions\n\n\t// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace\n\twhitespace = \"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",\n\t// http://www.w3.org/TR/css3-syntax/#characters\n\tcharacterEncoding = \"(?:\\\\\\\\.|[\\\\w-]|[^\\\\x00-\\\\xa0])+\",\n\n\t// Loosely modeled on CSS identifier characters\n\t// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors\n\t// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier\n\tidentifier = characterEncoding.replace( \"w\", \"w#\" ),\n\n\t// Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors\n\tattributes = \"\\\\[\" + whitespace + \"*(\" + characterEncoding + \")\" + whitespace +\n\t\t\"*(?:([*^$|!~]?=)\" + whitespace + \"*(?:(['\\\"])((?:\\\\\\\\.|[^\\\\\\\\])*?)\\\\3|(\" + identifier + \")|)|)\" + whitespace + \"*\\\\]\",\n\n\t// Prefer arguments quoted,\n\t//   then not containing pseudos/brackets,\n\t//   then attribute selectors/non-parenthetical expressions,\n\t//   then anything else\n\t// These preferences are here to reduce the number of selectors\n\t//   needing tokenize in the PSEUDO preFilter\n\tpseudos = \":(\" + characterEncoding + \")(?:\\\\(((['\\\"])((?:\\\\\\\\.|[^\\\\\\\\])*?)\\\\3|((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\" + attributes.replace( 3, 8 ) + \")*)|.*)\\\\)|)\",\n\n\t// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter\n\trtrim = new RegExp( \"^\" + whitespace + \"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\" + whitespace + \"+$\", \"g\" ),\n\n\trcomma = new RegExp( \"^\" + whitespace + \"*,\" + whitespace + \"*\" ),\n\trcombinators = new RegExp( \"^\" + whitespace + \"*([>+~]|\" + whitespace + \")\" + whitespace + \"*\" ),\n\n\trattributeQuotes = new RegExp( \"=\" + whitespace + \"*([^\\\\]'\\\"]*?)\" + whitespace + \"*\\\\]\", \"g\" ),\n\n\trpseudo = new RegExp( pseudos ),\n\tridentifier = new RegExp( \"^\" + identifier + \"$\" ),\n\n\tmatchExpr = {\n\t\t\"ID\": new RegExp( \"^#(\" + characterEncoding + \")\" ),\n\t\t\"CLASS\": new RegExp( \"^\\\\.(\" + characterEncoding + \")\" ),\n\t\t\"TAG\": new RegExp( \"^(\" + characterEncoding.replace( \"w\", \"w*\" ) + \")\" ),\n\t\t\"ATTR\": new RegExp( \"^\" + attributes ),\n\t\t\"PSEUDO\": new RegExp( \"^\" + pseudos ),\n\t\t\"CHILD\": new RegExp( \"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\" + whitespace +\n\t\t\t\"*(even|odd|(([+-]|)(\\\\d*)n|)\" + whitespace + \"*(?:([+-]|)\" + whitespace +\n\t\t\t\"*(\\\\d+)|))\" + whitespace + \"*\\\\)|)\", \"i\" ),\n\t\t\"bool\": new RegExp( \"^(?:\" + booleans + \")$\", \"i\" ),\n\t\t// For use in libraries implementing .is()\n\t\t// We use this for POS matching in `select`\n\t\t\"needsContext\": new RegExp( \"^\" + whitespace + \"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\" +\n\t\t\twhitespace + \"*((?:-\\\\d)?\\\\d*)\" + whitespace + \"*\\\\)|)(?=[^-]|$)\", \"i\" )\n\t},\n\n\trinputs = /^(?:input|select|textarea|button)$/i,\n\trheader = /^h\\d$/i,\n\n\trnative = /^[^{]+\\{\\s*\\[native \\w/,\n\n\t// Easily-parseable/retrievable ID or TAG or CLASS selectors\n\trquickExpr = /^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,\n\n\trsibling = /[+~]/,\n\trescape = /'|\\\\/g,\n\n\t// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters\n\trunescape = new RegExp( \"\\\\\\\\([\\\\da-f]{1,6}\" + whitespace + \"?|(\" + whitespace + \")|.)\", \"ig\" ),\n\tfunescape = function( _, escaped, escapedWhitespace ) {\n\t\tvar high = \"0x\" + escaped - 0x10000;\n\t\t// NaN means non-codepoint\n\t\t// Support: Firefox\n\t\t// Workaround erroneous numeric interpretation of +\"0x\"\n\t\treturn high !== high || escapedWhitespace ?\n\t\t\tescaped :\n\t\t\thigh < 0 ?\n\t\t\t\t// BMP codepoint\n\t\t\t\tString.fromCharCode( high + 0x10000 ) :\n\t\t\t\t// Supplemental Plane codepoint (surrogate pair)\n\t\t\t\tString.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );\n\t};\n\n// Optimize for push.apply( _, NodeList )\ntry {\n\tpush.apply(\n\t\t(arr = slice.call( preferredDoc.childNodes )),\n\t\tpreferredDoc.childNodes\n\t);\n\t// Support: Android<4.0\n\t// Detect silently failing push.apply\n\tarr[ preferredDoc.childNodes.length ].nodeType;\n} catch ( e ) {\n\tpush = { apply: arr.length ?\n\n\t\t// Leverage slice if possible\n\t\tfunction( target, els ) {\n\t\t\tpush_native.apply( target, slice.call(els) );\n\t\t} :\n\n\t\t// Support: IE<9\n\t\t// Otherwise append directly\n\t\tfunction( target, els ) {\n\t\t\tvar j = target.length,\n\t\t\t\ti = 0;\n\t\t\t// Can't trust NodeList.length\n\t\t\twhile ( (target[j++] = els[i++]) ) {}\n\t\t\ttarget.length = j - 1;\n\t\t}\n\t};\n}\n\nfunction Sizzle( selector, context, results, seed ) {\n\tvar match, elem, m, nodeType,\n\t\t// QSA vars\n\t\ti, groups, old, nid, newContext, newSelector;\n\n\tif ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {\n\t\tsetDocument( context );\n\t}\n\n\tcontext = context || document;\n\tresults = results || [];\n\n\tif ( !selector || typeof selector !== \"string\" ) {\n\t\treturn results;\n\t}\n\n\tif ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {\n\t\treturn [];\n\t}\n\n\tif ( documentIsHTML && !seed ) {\n\n\t\t// Shortcuts\n\t\tif ( (match = rquickExpr.exec( selector )) ) {\n\t\t\t// Speed-up: Sizzle(\"#ID\")\n\t\t\tif ( (m = match[1]) ) {\n\t\t\t\tif ( nodeType === 9 ) {\n\t\t\t\t\telem = context.getElementById( m );\n\t\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t\t// nodes that are no longer in the document (jQuery #6963)\n\t\t\t\t\tif ( elem && elem.parentNode ) {\n\t\t\t\t\t\t// Handle the case where IE, Opera, and Webkit return items\n\t\t\t\t\t\t// by name instead of ID\n\t\t\t\t\t\tif ( elem.id === m ) {\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Context is not a document\n\t\t\t\t\tif ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&\n\t\t\t\t\t\tcontains( context, elem ) && elem.id === m ) {\n\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Speed-up: Sizzle(\"TAG\")\n\t\t\t} else if ( match[2] ) {\n\t\t\t\tpush.apply( results, context.getElementsByTagName( selector ) );\n\t\t\t\treturn results;\n\n\t\t\t// Speed-up: Sizzle(\".CLASS\")\n\t\t\t} else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {\n\t\t\t\tpush.apply( results, context.getElementsByClassName( m ) );\n\t\t\t\treturn results;\n\t\t\t}\n\t\t}\n\n\t\t// QSA path\n\t\tif ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {\n\t\t\tnid = old = expando;\n\t\t\tnewContext = context;\n\t\t\tnewSelector = nodeType === 9 && selector;\n\n\t\t\t// qSA works strangely on Element-rooted queries\n\t\t\t// We can work around this by specifying an extra ID on the root\n\t\t\t// and working up from there (Thanks to Andrew Dupont for the technique)\n\t\t\t// IE 8 doesn't work on object elements\n\t\t\tif ( nodeType === 1 && context.nodeName.toLowerCase() !== \"object\" ) {\n\t\t\t\tgroups = tokenize( selector );\n\n\t\t\t\tif ( (old = context.getAttribute(\"id\")) ) {\n\t\t\t\t\tnid = old.replace( rescape, \"\\\\$&\" );\n\t\t\t\t} else {\n\t\t\t\t\tcontext.setAttribute( \"id\", nid );\n\t\t\t\t}\n\t\t\t\tnid = \"[id='\" + nid + \"'] \";\n\n\t\t\t\ti = groups.length;\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\tgroups[i] = nid + toSelector( groups[i] );\n\t\t\t\t}\n\t\t\t\tnewContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;\n\t\t\t\tnewSelector = groups.join(\",\");\n\t\t\t}\n\n\t\t\tif ( newSelector ) {\n\t\t\t\ttry {\n\t\t\t\t\tpush.apply( results,\n\t\t\t\t\t\tnewContext.querySelectorAll( newSelector )\n\t\t\t\t\t);\n\t\t\t\t\treturn results;\n\t\t\t\t} catch(qsaError) {\n\t\t\t\t} finally {\n\t\t\t\t\tif ( !old ) {\n\t\t\t\t\t\tcontext.removeAttribute(\"id\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// All others\n\treturn select( selector.replace( rtrim, \"$1\" ), context, results, seed );\n}\n\n/**\n * Create key-value caches of limited size\n * @returns {Function(string, Object)} Returns the Object data after storing it on itself with\n *\tproperty name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)\n *\tdeleting the oldest entry\n */\nfunction createCache() {\n\tvar keys = [];\n\n\tfunction cache( key, value ) {\n\t\t// Use (key + \" \") to avoid collision with native prototype properties (see Issue #157)\n\t\tif ( keys.push( key + \" \" ) > Expr.cacheLength ) {\n\t\t\t// Only keep the most recent entries\n\t\t\tdelete cache[ keys.shift() ];\n\t\t}\n\t\treturn (cache[ key + \" \" ] = value);\n\t}\n\treturn cache;\n}\n\n/**\n * Mark a function for special use by Sizzle\n * @param {Function} fn The function to mark\n */\nfunction markFunction( fn ) {\n\tfn[ expando ] = true;\n\treturn fn;\n}\n\n/**\n * Support testing using an element\n * @param {Function} fn Passed the created div and expects a boolean result\n */\nfunction assert( fn ) {\n\tvar div = document.createElement(\"div\");\n\n\ttry {\n\t\treturn !!fn( div );\n\t} catch (e) {\n\t\treturn false;\n\t} finally {\n\t\t// Remove from its parent by default\n\t\tif ( div.parentNode ) {\n\t\t\tdiv.parentNode.removeChild( div );\n\t\t}\n\t\t// release memory in IE\n\t\tdiv = null;\n\t}\n}\n\n/**\n * Adds the same handler for all of the specified attrs\n * @param {String} attrs Pipe-separated list of attributes\n * @param {Function} handler The method that will be applied\n */\nfunction addHandle( attrs, handler ) {\n\tvar arr = attrs.split(\"|\"),\n\t\ti = attrs.length;\n\n\twhile ( i-- ) {\n\t\tExpr.attrHandle[ arr[i] ] = handler;\n\t}\n}\n\n/**\n * Checks document order of two siblings\n * @param {Element} a\n * @param {Element} b\n * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b\n */\nfunction siblingCheck( a, b ) {\n\tvar cur = b && a,\n\t\tdiff = cur && a.nodeType === 1 && b.nodeType === 1 &&\n\t\t\t( ~b.sourceIndex || MAX_NEGATIVE ) -\n\t\t\t( ~a.sourceIndex || MAX_NEGATIVE );\n\n\t// Use IE sourceIndex if available on both nodes\n\tif ( diff ) {\n\t\treturn diff;\n\t}\n\n\t// Check if b follows a\n\tif ( cur ) {\n\t\twhile ( (cur = cur.nextSibling) ) {\n\t\t\tif ( cur === b ) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn a ? 1 : -1;\n}\n\n/**\n * Returns a function to use in pseudos for input types\n * @param {String} type\n */\nfunction createInputPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn name === \"input\" && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for buttons\n * @param {String} type\n */\nfunction createButtonPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn (name === \"input\" || name === \"button\") && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for positionals\n * @param {Function} fn\n */\nfunction createPositionalPseudo( fn ) {\n\treturn markFunction(function( argument ) {\n\t\targument = +argument;\n\t\treturn markFunction(function( seed, matches ) {\n\t\t\tvar j,\n\t\t\t\tmatchIndexes = fn( [], seed.length, argument ),\n\t\t\t\ti = matchIndexes.length;\n\n\t\t\t// Match elements found at the specified indexes\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( seed[ (j = matchIndexes[i]) ] ) {\n\t\t\t\t\tseed[j] = !(matches[j] = seed[j]);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t});\n}\n\n/**\n * Checks a node for validity as a Sizzle context\n * @param {Element|Object=} context\n * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value\n */\nfunction testContext( context ) {\n\treturn context && typeof context.getElementsByTagName !== strundefined && context;\n}\n\n// Expose support vars for convenience\nsupport = Sizzle.support = {};\n\n/**\n * Detects XML nodes\n * @param {Element|Object} elem An element or a document\n * @returns {Boolean} True iff elem is a non-HTML XML node\n */\nisXML = Sizzle.isXML = function( elem ) {\n\t// documentElement is verified for cases where it doesn't yet exist\n\t// (such as loading iframes in IE - #4833)\n\tvar documentElement = elem && (elem.ownerDocument || elem).documentElement;\n\treturn documentElement ? documentElement.nodeName !== \"HTML\" : false;\n};\n\n/**\n * Sets document-related variables once based on the current document\n * @param {Element|Object} [doc] An element or document object to use to set the document\n * @returns {Object} Returns the current document\n */\nsetDocument = Sizzle.setDocument = function( node ) {\n\tvar hasCompare,\n\t\tdoc = node ? node.ownerDocument || node : preferredDoc,\n\t\tparent = doc.defaultView;\n\n\t// If no document and documentElement is available, return\n\tif ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {\n\t\treturn document;\n\t}\n\n\t// Set our document\n\tdocument = doc;\n\tdocElem = doc.documentElement;\n\n\t// Support tests\n\tdocumentIsHTML = !isXML( doc );\n\n\t// Support: IE>8\n\t// If iframe document is assigned to \"document\" variable and if iframe has been reloaded,\n\t// IE will throw \"permission denied\" error when accessing \"document\" variable, see jQuery #13936\n\t// IE6-8 do not support the defaultView property so parent will be undefined\n\tif ( parent && parent !== parent.top ) {\n\t\t// IE11 does not have attachEvent, so all must suffer\n\t\tif ( parent.addEventListener ) {\n\t\t\tparent.addEventListener( \"unload\", function() {\n\t\t\t\tsetDocument();\n\t\t\t}, false );\n\t\t} else if ( parent.attachEvent ) {\n\t\t\tparent.attachEvent( \"onunload\", function() {\n\t\t\t\tsetDocument();\n\t\t\t});\n\t\t}\n\t}\n\n\t/* Attributes\n\t---------------------------------------------------------------------- */\n\n\t// Support: IE<8\n\t// Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)\n\tsupport.attributes = assert(function( div ) {\n\t\tdiv.className = \"i\";\n\t\treturn !div.getAttribute(\"className\");\n\t});\n\n\t/* getElement(s)By*\n\t---------------------------------------------------------------------- */\n\n\t// Check if getElementsByTagName(\"*\") returns only elements\n\tsupport.getElementsByTagName = assert(function( div ) {\n\t\tdiv.appendChild( doc.createComment(\"\") );\n\t\treturn !div.getElementsByTagName(\"*\").length;\n\t});\n\n\t// Check if getElementsByClassName can be trusted\n\tsupport.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) {\n\t\tdiv.innerHTML = \"<div class='a'></div><div class='a i'></div>\";\n\n\t\t// Support: Safari<4\n\t\t// Catch class over-caching\n\t\tdiv.firstChild.className = \"i\";\n\t\t// Support: Opera<10\n\t\t// Catch gEBCN failure to find non-leading classes\n\t\treturn div.getElementsByClassName(\"i\").length === 2;\n\t});\n\n\t// Support: IE<10\n\t// Check if getElementById returns elements by name\n\t// The broken getElementById methods don't pick up programatically-set names,\n\t// so use a roundabout getElementsByName test\n\tsupport.getById = assert(function( div ) {\n\t\tdocElem.appendChild( div ).id = expando;\n\t\treturn !doc.getElementsByName || !doc.getElementsByName( expando ).length;\n\t});\n\n\t// ID find and filter\n\tif ( support.getById ) {\n\t\tExpr.find[\"ID\"] = function( id, context ) {\n\t\t\tif ( typeof context.getElementById !== strundefined && documentIsHTML ) {\n\t\t\t\tvar m = context.getElementById( id );\n\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t// nodes that are no longer in the document #6963\n\t\t\t\treturn m && m.parentNode ? [m] : [];\n\t\t\t}\n\t\t};\n\t\tExpr.filter[\"ID\"] = function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\treturn elem.getAttribute(\"id\") === attrId;\n\t\t\t};\n\t\t};\n\t} else {\n\t\t// Support: IE6/7\n\t\t// getElementById is not reliable as a find shortcut\n\t\tdelete Expr.find[\"ID\"];\n\n\t\tExpr.filter[\"ID\"] =  function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\tvar node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode(\"id\");\n\t\t\t\treturn node && node.value === attrId;\n\t\t\t};\n\t\t};\n\t}\n\n\t// Tag\n\tExpr.find[\"TAG\"] = support.getElementsByTagName ?\n\t\tfunction( tag, context ) {\n\t\t\tif ( typeof context.getElementsByTagName !== strundefined ) {\n\t\t\t\treturn context.getElementsByTagName( tag );\n\t\t\t}\n\t\t} :\n\t\tfunction( tag, context ) {\n\t\t\tvar elem,\n\t\t\t\ttmp = [],\n\t\t\t\ti = 0,\n\t\t\t\tresults = context.getElementsByTagName( tag );\n\n\t\t\t// Filter out possible comments\n\t\t\tif ( tag === \"*\" ) {\n\t\t\t\twhile ( (elem = results[i++]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\ttmp.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn tmp;\n\t\t\t}\n\t\t\treturn results;\n\t\t};\n\n\t// Class\n\tExpr.find[\"CLASS\"] = support.getElementsByClassName && function( className, context ) {\n\t\tif ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {\n\t\t\treturn context.getElementsByClassName( className );\n\t\t}\n\t};\n\n\t/* QSA/matchesSelector\n\t---------------------------------------------------------------------- */\n\n\t// QSA and matchesSelector support\n\n\t// matchesSelector(:active) reports false when true (IE9/Opera 11.5)\n\trbuggyMatches = [];\n\n\t// qSa(:focus) reports false when true (Chrome 21)\n\t// We allow this because of a bug in IE8/9 that throws an error\n\t// whenever `document.activeElement` is accessed on an iframe\n\t// So, we allow :focus to pass through QSA all the time to avoid the IE error\n\t// See http://bugs.jquery.com/ticket/13378\n\trbuggyQSA = [];\n\n\tif ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {\n\t\t// Build QSA regex\n\t\t// Regex strategy adopted from Diego Perini\n\t\tassert(function( div ) {\n\t\t\t// Select is set to empty string on purpose\n\t\t\t// This is to test IE's treatment of not explicitly\n\t\t\t// setting a boolean content attribute,\n\t\t\t// since its presence should be enough\n\t\t\t// http://bugs.jquery.com/ticket/12359\n\t\t\tdiv.innerHTML = \"<select t=''><option selected=''></option></select>\";\n\n\t\t\t// Support: IE8, Opera 10-12\n\t\t\t// Nothing should be selected when empty strings follow ^= or $= or *=\n\t\t\tif ( div.querySelectorAll(\"[t^='']\").length ) {\n\t\t\t\trbuggyQSA.push( \"[*^$]=\" + whitespace + \"*(?:''|\\\"\\\")\" );\n\t\t\t}\n\n\t\t\t// Support: IE8\n\t\t\t// Boolean attributes and \"value\" are not treated correctly\n\t\t\tif ( !div.querySelectorAll(\"[selected]\").length ) {\n\t\t\t\trbuggyQSA.push( \"\\\\[\" + whitespace + \"*(?:value|\" + booleans + \")\" );\n\t\t\t}\n\n\t\t\t// Webkit/Opera - :checked should return selected option elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( !div.querySelectorAll(\":checked\").length ) {\n\t\t\t\trbuggyQSA.push(\":checked\");\n\t\t\t}\n\t\t});\n\n\t\tassert(function( div ) {\n\t\t\t// Support: Windows 8 Native Apps\n\t\t\t// The type and name attributes are restricted during .innerHTML assignment\n\t\t\tvar input = doc.createElement(\"input\");\n\t\t\tinput.setAttribute( \"type\", \"hidden\" );\n\t\t\tdiv.appendChild( input ).setAttribute( \"name\", \"D\" );\n\n\t\t\t// Support: IE8\n\t\t\t// Enforce case-sensitivity of name attribute\n\t\t\tif ( div.querySelectorAll(\"[name=d]\").length ) {\n\t\t\t\trbuggyQSA.push( \"name\" + whitespace + \"*[*^$|!~]?=\" );\n\t\t\t}\n\n\t\t\t// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( !div.querySelectorAll(\":enabled\").length ) {\n\t\t\t\trbuggyQSA.push( \":enabled\", \":disabled\" );\n\t\t\t}\n\n\t\t\t// Opera 10-11 does not throw on post-comma invalid pseudos\n\t\t\tdiv.querySelectorAll(\"*,:x\");\n\t\t\trbuggyQSA.push(\",.*:\");\n\t\t});\n\t}\n\n\tif ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector ||\n\t\tdocElem.mozMatchesSelector ||\n\t\tdocElem.oMatchesSelector ||\n\t\tdocElem.msMatchesSelector) )) ) {\n\n\t\tassert(function( div ) {\n\t\t\t// Check to see if it's possible to do matchesSelector\n\t\t\t// on a disconnected node (IE 9)\n\t\t\tsupport.disconnectedMatch = matches.call( div, \"div\" );\n\n\t\t\t// This should fail with an exception\n\t\t\t// Gecko does not error, returns false instead\n\t\t\tmatches.call( div, \"[s!='']:x\" );\n\t\t\trbuggyMatches.push( \"!=\", pseudos );\n\t\t});\n\t}\n\n\trbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join(\"|\") );\n\trbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join(\"|\") );\n\n\t/* Contains\n\t---------------------------------------------------------------------- */\n\thasCompare = rnative.test( docElem.compareDocumentPosition );\n\n\t// Element contains another\n\t// Purposefully does not implement inclusive descendent\n\t// As in, an element does not contain itself\n\tcontains = hasCompare || rnative.test( docElem.contains ) ?\n\t\tfunction( a, b ) {\n\t\t\tvar adown = a.nodeType === 9 ? a.documentElement : a,\n\t\t\t\tbup = b && b.parentNode;\n\t\t\treturn a === bup || !!( bup && bup.nodeType === 1 && (\n\t\t\t\tadown.contains ?\n\t\t\t\t\tadown.contains( bup ) :\n\t\t\t\t\ta.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16\n\t\t\t));\n\t\t} :\n\t\tfunction( a, b ) {\n\t\t\tif ( b ) {\n\t\t\t\twhile ( (b = b.parentNode) ) {\n\t\t\t\t\tif ( b === a ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\n\t/* Sorting\n\t---------------------------------------------------------------------- */\n\n\t// Document order sorting\n\tsortOrder = hasCompare ?\n\tfunction( a, b ) {\n\n\t\t// Flag for duplicate removal\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\t// Sort on method existence if only one input has compareDocumentPosition\n\t\tvar compare = !a.compareDocumentPosition - !b.compareDocumentPosition;\n\t\tif ( compare ) {\n\t\t\treturn compare;\n\t\t}\n\n\t\t// Calculate position if both inputs belong to the same document\n\t\tcompare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?\n\t\t\ta.compareDocumentPosition( b ) :\n\n\t\t\t// Otherwise we know they are disconnected\n\t\t\t1;\n\n\t\t// Disconnected nodes\n\t\tif ( compare & 1 ||\n\t\t\t(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {\n\n\t\t\t// Choose the first element that is related to our preferred document\n\t\t\tif ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t\tif ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\t// Maintain original order\n\t\t\treturn sortInput ?\n\t\t\t\t( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :\n\t\t\t\t0;\n\t\t}\n\n\t\treturn compare & 4 ? -1 : 1;\n\t} :\n\tfunction( a, b ) {\n\t\t// Exit early if the nodes are identical\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\taup = a.parentNode,\n\t\t\tbup = b.parentNode,\n\t\t\tap = [ a ],\n\t\t\tbp = [ b ];\n\n\t\t// Parentless nodes are either documents or disconnected\n\t\tif ( !aup || !bup ) {\n\t\t\treturn a === doc ? -1 :\n\t\t\t\tb === doc ? 1 :\n\t\t\t\taup ? -1 :\n\t\t\t\tbup ? 1 :\n\t\t\t\tsortInput ?\n\t\t\t\t( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :\n\t\t\t\t0;\n\n\t\t// If the nodes are siblings, we can do a quick check\n\t\t} else if ( aup === bup ) {\n\t\t\treturn siblingCheck( a, b );\n\t\t}\n\n\t\t// Otherwise we need full lists of their ancestors for comparison\n\t\tcur = a;\n\t\twhile ( (cur = cur.parentNode) ) {\n\t\t\tap.unshift( cur );\n\t\t}\n\t\tcur = b;\n\t\twhile ( (cur = cur.parentNode) ) {\n\t\t\tbp.unshift( cur );\n\t\t}\n\n\t\t// Walk down the tree looking for a discrepancy\n\t\twhile ( ap[i] === bp[i] ) {\n\t\t\ti++;\n\t\t}\n\n\t\treturn i ?\n\t\t\t// Do a sibling check if the nodes have a common ancestor\n\t\t\tsiblingCheck( ap[i], bp[i] ) :\n\n\t\t\t// Otherwise nodes in our document sort first\n\t\t\tap[i] === preferredDoc ? -1 :\n\t\t\tbp[i] === preferredDoc ? 1 :\n\t\t\t0;\n\t};\n\n\treturn doc;\n};\n\nSizzle.matches = function( expr, elements ) {\n\treturn Sizzle( expr, null, null, elements );\n};\n\nSizzle.matchesSelector = function( elem, expr ) {\n\t// Set document vars if needed\n\tif ( ( elem.ownerDocument || elem ) !== document ) {\n\t\tsetDocument( elem );\n\t}\n\n\t// Make sure that attribute selectors are quoted\n\texpr = expr.replace( rattributeQuotes, \"='$1']\" );\n\n\tif ( support.matchesSelector && documentIsHTML &&\n\t\t( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&\n\t\t( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {\n\n\t\ttry {\n\t\t\tvar ret = matches.call( elem, expr );\n\n\t\t\t// IE 9's matchesSelector returns false on disconnected nodes\n\t\t\tif ( ret || support.disconnectedMatch ||\n\t\t\t\t\t// As well, disconnected nodes are said to be in a document\n\t\t\t\t\t// fragment in IE 9\n\t\t\t\t\telem.document && elem.document.nodeType !== 11 ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t} catch(e) {}\n\t}\n\n\treturn Sizzle( expr, document, null, [elem] ).length > 0;\n};\n\nSizzle.contains = function( context, elem ) {\n\t// Set document vars if needed\n\tif ( ( context.ownerDocument || context ) !== document ) {\n\t\tsetDocument( context );\n\t}\n\treturn contains( context, elem );\n};\n\nSizzle.attr = function( elem, name ) {\n\t// Set document vars if needed\n\tif ( ( elem.ownerDocument || elem ) !== document ) {\n\t\tsetDocument( elem );\n\t}\n\n\tvar fn = Expr.attrHandle[ name.toLowerCase() ],\n\t\t// Don't get fooled by Object.prototype properties (jQuery #13807)\n\t\tval = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?\n\t\t\tfn( elem, name, !documentIsHTML ) :\n\t\t\tundefined;\n\n\treturn val !== undefined ?\n\t\tval :\n\t\tsupport.attributes || !documentIsHTML ?\n\t\t\telem.getAttribute( name ) :\n\t\t\t(val = elem.getAttributeNode(name)) && val.specified ?\n\t\t\t\tval.value :\n\t\t\t\tnull;\n};\n\nSizzle.error = function( msg ) {\n\tthrow new Error( \"Syntax error, unrecognized expression: \" + msg );\n};\n\n/**\n * Document sorting and removing duplicates\n * @param {ArrayLike} results\n */\nSizzle.uniqueSort = function( results ) {\n\tvar elem,\n\t\tduplicates = [],\n\t\tj = 0,\n\t\ti = 0;\n\n\t// Unless we *know* we can detect duplicates, assume their presence\n\thasDuplicate = !support.detectDuplicates;\n\tsortInput = !support.sortStable && results.slice( 0 );\n\tresults.sort( sortOrder );\n\n\tif ( hasDuplicate ) {\n\t\twhile ( (elem = results[i++]) ) {\n\t\t\tif ( elem === results[ i ] ) {\n\t\t\t\tj = duplicates.push( i );\n\t\t\t}\n\t\t}\n\t\twhile ( j-- ) {\n\t\t\tresults.splice( duplicates[ j ], 1 );\n\t\t}\n\t}\n\n\t// Clear input after sorting to release objects\n\t// See https://github.com/jquery/sizzle/pull/225\n\tsortInput = null;\n\n\treturn results;\n};\n\n/**\n * Utility function for retrieving the text value of an array of DOM nodes\n * @param {Array|Element} elem\n */\ngetText = Sizzle.getText = function( elem ) {\n\tvar node,\n\t\tret = \"\",\n\t\ti = 0,\n\t\tnodeType = elem.nodeType;\n\n\tif ( !nodeType ) {\n\t\t// If no nodeType, this is expected to be an array\n\t\twhile ( (node = elem[i++]) ) {\n\t\t\t// Do not traverse comment nodes\n\t\t\tret += getText( node );\n\t\t}\n\t} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {\n\t\t// Use textContent for elements\n\t\t// innerText usage removed for consistency of new lines (jQuery #11153)\n\t\tif ( typeof elem.textContent === \"string\" ) {\n\t\t\treturn elem.textContent;\n\t\t} else {\n\t\t\t// Traverse its children\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tret += getText( elem );\n\t\t\t}\n\t\t}\n\t} else if ( nodeType === 3 || nodeType === 4 ) {\n\t\treturn elem.nodeValue;\n\t}\n\t// Do not include comment or processing instruction nodes\n\n\treturn ret;\n};\n\nExpr = Sizzle.selectors = {\n\n\t// Can be adjusted by the user\n\tcacheLength: 50,\n\n\tcreatePseudo: markFunction,\n\n\tmatch: matchExpr,\n\n\tattrHandle: {},\n\n\tfind: {},\n\n\trelative: {\n\t\t\">\": { dir: \"parentNode\", first: true },\n\t\t\" \": { dir: \"parentNode\" },\n\t\t\"+\": { dir: \"previousSibling\", first: true },\n\t\t\"~\": { dir: \"previousSibling\" }\n\t},\n\n\tpreFilter: {\n\t\t\"ATTR\": function( match ) {\n\t\t\tmatch[1] = match[1].replace( runescape, funescape );\n\n\t\t\t// Move the given value to match[3] whether quoted or unquoted\n\t\t\tmatch[3] = ( match[4] || match[5] || \"\" ).replace( runescape, funescape );\n\n\t\t\tif ( match[2] === \"~=\" ) {\n\t\t\t\tmatch[3] = \" \" + match[3] + \" \";\n\t\t\t}\n\n\t\t\treturn match.slice( 0, 4 );\n\t\t},\n\n\t\t\"CHILD\": function( match ) {\n\t\t\t/* matches from matchExpr[\"CHILD\"]\n\t\t\t\t1 type (only|nth|...)\n\t\t\t\t2 what (child|of-type)\n\t\t\t\t3 argument (even|odd|\\d*|\\d*n([+-]\\d+)?|...)\n\t\t\t\t4 xn-component of xn+y argument ([+-]?\\d*n|)\n\t\t\t\t5 sign of xn-component\n\t\t\t\t6 x of xn-component\n\t\t\t\t7 sign of y-component\n\t\t\t\t8 y of y-component\n\t\t\t*/\n\t\t\tmatch[1] = match[1].toLowerCase();\n\n\t\t\tif ( match[1].slice( 0, 3 ) === \"nth\" ) {\n\t\t\t\t// nth-* requires argument\n\t\t\t\tif ( !match[3] ) {\n\t\t\t\t\tSizzle.error( match[0] );\n\t\t\t\t}\n\n\t\t\t\t// numeric x and y parameters for Expr.filter.CHILD\n\t\t\t\t// remember that false/true cast respectively to 0/1\n\t\t\t\tmatch[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === \"even\" || match[3] === \"odd\" ) );\n\t\t\t\tmatch[5] = +( ( match[7] + match[8] ) || match[3] === \"odd\" );\n\n\t\t\t// other types prohibit arguments\n\t\t\t} else if ( match[3] ) {\n\t\t\t\tSizzle.error( match[0] );\n\t\t\t}\n\n\t\t\treturn match;\n\t\t},\n\n\t\t\"PSEUDO\": function( match ) {\n\t\t\tvar excess,\n\t\t\t\tunquoted = !match[5] && match[2];\n\n\t\t\tif ( matchExpr[\"CHILD\"].test( match[0] ) ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Accept quoted arguments as-is\n\t\t\tif ( match[3] && match[4] !== undefined ) {\n\t\t\t\tmatch[2] = match[4];\n\n\t\t\t// Strip excess characters from unquoted arguments\n\t\t\t} else if ( unquoted && rpseudo.test( unquoted ) &&\n\t\t\t\t// Get excess from tokenize (recursively)\n\t\t\t\t(excess = tokenize( unquoted, true )) &&\n\t\t\t\t// advance to the next closing parenthesis\n\t\t\t\t(excess = unquoted.indexOf( \")\", unquoted.length - excess ) - unquoted.length) ) {\n\n\t\t\t\t// excess is a negative index\n\t\t\t\tmatch[0] = match[0].slice( 0, excess );\n\t\t\t\tmatch[2] = unquoted.slice( 0, excess );\n\t\t\t}\n\n\t\t\t// Return only captures needed by the pseudo filter method (type and argument)\n\t\t\treturn match.slice( 0, 3 );\n\t\t}\n\t},\n\n\tfilter: {\n\n\t\t\"TAG\": function( nodeNameSelector ) {\n\t\t\tvar nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn nodeNameSelector === \"*\" ?\n\t\t\t\tfunction() { return true; } :\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === nodeName;\n\t\t\t\t};\n\t\t},\n\n\t\t\"CLASS\": function( className ) {\n\t\t\tvar pattern = classCache[ className + \" \" ];\n\n\t\t\treturn pattern ||\n\t\t\t\t(pattern = new RegExp( \"(^|\" + whitespace + \")\" + className + \"(\" + whitespace + \"|$)\" )) &&\n\t\t\t\tclassCache( className, function( elem ) {\n\t\t\t\t\treturn pattern.test( typeof elem.className === \"string\" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute(\"class\") || \"\" );\n\t\t\t\t});\n\t\t},\n\n\t\t\"ATTR\": function( name, operator, check ) {\n\t\t\treturn function( elem ) {\n\t\t\t\tvar result = Sizzle.attr( elem, name );\n\n\t\t\t\tif ( result == null ) {\n\t\t\t\t\treturn operator === \"!=\";\n\t\t\t\t}\n\t\t\t\tif ( !operator ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tresult += \"\";\n\n\t\t\t\treturn operator === \"=\" ? result === check :\n\t\t\t\t\toperator === \"!=\" ? result !== check :\n\t\t\t\t\toperator === \"^=\" ? check && result.indexOf( check ) === 0 :\n\t\t\t\t\toperator === \"*=\" ? check && result.indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"$=\" ? check && result.slice( -check.length ) === check :\n\t\t\t\t\toperator === \"~=\" ? ( \" \" + result + \" \" ).indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"|=\" ? result === check || result.slice( 0, check.length + 1 ) === check + \"-\" :\n\t\t\t\t\tfalse;\n\t\t\t};\n\t\t},\n\n\t\t\"CHILD\": function( type, what, argument, first, last ) {\n\t\t\tvar simple = type.slice( 0, 3 ) !== \"nth\",\n\t\t\t\tforward = type.slice( -4 ) !== \"last\",\n\t\t\t\tofType = what === \"of-type\";\n\n\t\t\treturn first === 1 && last === 0 ?\n\n\t\t\t\t// Shortcut for :nth-*(n)\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn !!elem.parentNode;\n\t\t\t\t} :\n\n\t\t\t\tfunction( elem, context, xml ) {\n\t\t\t\t\tvar cache, outerCache, node, diff, nodeIndex, start,\n\t\t\t\t\t\tdir = simple !== forward ? \"nextSibling\" : \"previousSibling\",\n\t\t\t\t\t\tparent = elem.parentNode,\n\t\t\t\t\t\tname = ofType && elem.nodeName.toLowerCase(),\n\t\t\t\t\t\tuseCache = !xml && !ofType;\n\n\t\t\t\t\tif ( parent ) {\n\n\t\t\t\t\t\t// :(first|last|only)-(child|of-type)\n\t\t\t\t\t\tif ( simple ) {\n\t\t\t\t\t\t\twhile ( dir ) {\n\t\t\t\t\t\t\t\tnode = elem;\n\t\t\t\t\t\t\t\twhile ( (node = node[ dir ]) ) {\n\t\t\t\t\t\t\t\t\tif ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Reverse direction for :only-* (if we haven't yet done so)\n\t\t\t\t\t\t\t\tstart = dir = type === \"only\" && !start && \"nextSibling\";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstart = [ forward ? parent.firstChild : parent.lastChild ];\n\n\t\t\t\t\t\t// non-xml :nth-child(...) stores cache data on `parent`\n\t\t\t\t\t\tif ( forward && useCache ) {\n\t\t\t\t\t\t\t// Seek `elem` from a previously-cached index\n\t\t\t\t\t\t\touterCache = parent[ expando ] || (parent[ expando ] = {});\n\t\t\t\t\t\t\tcache = outerCache[ type ] || [];\n\t\t\t\t\t\t\tnodeIndex = cache[0] === dirruns && cache[1];\n\t\t\t\t\t\t\tdiff = cache[0] === dirruns && cache[2];\n\t\t\t\t\t\t\tnode = nodeIndex && parent.childNodes[ nodeIndex ];\n\n\t\t\t\t\t\t\twhile ( (node = ++nodeIndex && node && node[ dir ] ||\n\n\t\t\t\t\t\t\t\t// Fallback to seeking `elem` from the start\n\t\t\t\t\t\t\t\t(diff = nodeIndex = 0) || start.pop()) ) {\n\n\t\t\t\t\t\t\t\t// When found, cache indexes on `parent` and break\n\t\t\t\t\t\t\t\tif ( node.nodeType === 1 && ++diff && node === elem ) {\n\t\t\t\t\t\t\t\t\touterCache[ type ] = [ dirruns, nodeIndex, diff ];\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Use previously-cached element index if available\n\t\t\t\t\t\t} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {\n\t\t\t\t\t\t\tdiff = cache[1];\n\n\t\t\t\t\t\t// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Use the same loop as above to seek `elem` from the start\n\t\t\t\t\t\t\twhile ( (node = ++nodeIndex && node && node[ dir ] ||\n\t\t\t\t\t\t\t\t(diff = nodeIndex = 0) || start.pop()) ) {\n\n\t\t\t\t\t\t\t\tif ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {\n\t\t\t\t\t\t\t\t\t// Cache the index of each encountered element\n\t\t\t\t\t\t\t\t\tif ( useCache ) {\n\t\t\t\t\t\t\t\t\t\t(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tif ( node === elem ) {\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Incorporate the offset, then check against cycle size\n\t\t\t\t\t\tdiff -= last;\n\t\t\t\t\t\treturn diff === first || ( diff % first === 0 && diff / first >= 0 );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t},\n\n\t\t\"PSEUDO\": function( pseudo, argument ) {\n\t\t\t// pseudo-class names are case-insensitive\n\t\t\t// http://www.w3.org/TR/selectors/#pseudo-classes\n\t\t\t// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters\n\t\t\t// Remember that setFilters inherits from pseudos\n\t\t\tvar args,\n\t\t\t\tfn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||\n\t\t\t\t\tSizzle.error( \"unsupported pseudo: \" + pseudo );\n\n\t\t\t// The user may use createPseudo to indicate that\n\t\t\t// arguments are needed to create the filter function\n\t\t\t// just as Sizzle does\n\t\t\tif ( fn[ expando ] ) {\n\t\t\t\treturn fn( argument );\n\t\t\t}\n\n\t\t\t// But maintain support for old signatures\n\t\t\tif ( fn.length > 1 ) {\n\t\t\t\targs = [ pseudo, pseudo, \"\", argument ];\n\t\t\t\treturn Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?\n\t\t\t\t\tmarkFunction(function( seed, matches ) {\n\t\t\t\t\t\tvar idx,\n\t\t\t\t\t\t\tmatched = fn( seed, argument ),\n\t\t\t\t\t\t\ti = matched.length;\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tidx = indexOf.call( seed, matched[i] );\n\t\t\t\t\t\t\tseed[ idx ] = !( matches[ idx ] = matched[i] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}) :\n\t\t\t\t\tfunction( elem ) {\n\t\t\t\t\t\treturn fn( elem, 0, args );\n\t\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn fn;\n\t\t}\n\t},\n\n\tpseudos: {\n\t\t// Potentially complex pseudos\n\t\t\"not\": markFunction(function( selector ) {\n\t\t\t// Trim the selector passed to compile\n\t\t\t// to avoid treating leading and trailing\n\t\t\t// spaces as combinators\n\t\t\tvar input = [],\n\t\t\t\tresults = [],\n\t\t\t\tmatcher = compile( selector.replace( rtrim, \"$1\" ) );\n\n\t\t\treturn matcher[ expando ] ?\n\t\t\t\tmarkFunction(function( seed, matches, context, xml ) {\n\t\t\t\t\tvar elem,\n\t\t\t\t\t\tunmatched = matcher( seed, null, xml, [] ),\n\t\t\t\t\t\ti = seed.length;\n\n\t\t\t\t\t// Match elements unmatched by `matcher`\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( (elem = unmatched[i]) ) {\n\t\t\t\t\t\t\tseed[i] = !(matches[i] = elem);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}) :\n\t\t\t\tfunction( elem, context, xml ) {\n\t\t\t\t\tinput[0] = elem;\n\t\t\t\t\tmatcher( input, null, xml, results );\n\t\t\t\t\treturn !results.pop();\n\t\t\t\t};\n\t\t}),\n\n\t\t\"has\": markFunction(function( selector ) {\n\t\t\treturn function( elem ) {\n\t\t\t\treturn Sizzle( selector, elem ).length > 0;\n\t\t\t};\n\t\t}),\n\n\t\t\"contains\": markFunction(function( text ) {\n\t\t\treturn function( elem ) {\n\t\t\t\treturn ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;\n\t\t\t};\n\t\t}),\n\n\t\t// \"Whether an element is represented by a :lang() selector\n\t\t// is based solely on the element's language value\n\t\t// being equal to the identifier C,\n\t\t// or beginning with the identifier C immediately followed by \"-\".\n\t\t// The matching of C against the element's language value is performed case-insensitively.\n\t\t// The identifier C does not have to be a valid language name.\"\n\t\t// http://www.w3.org/TR/selectors/#lang-pseudo\n\t\t\"lang\": markFunction( function( lang ) {\n\t\t\t// lang value must be a valid identifier\n\t\t\tif ( !ridentifier.test(lang || \"\") ) {\n\t\t\t\tSizzle.error( \"unsupported lang: \" + lang );\n\t\t\t}\n\t\t\tlang = lang.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn function( elem ) {\n\t\t\t\tvar elemLang;\n\t\t\t\tdo {\n\t\t\t\t\tif ( (elemLang = documentIsHTML ?\n\t\t\t\t\t\telem.lang :\n\t\t\t\t\t\telem.getAttribute(\"xml:lang\") || elem.getAttribute(\"lang\")) ) {\n\n\t\t\t\t\t\telemLang = elemLang.toLowerCase();\n\t\t\t\t\t\treturn elemLang === lang || elemLang.indexOf( lang + \"-\" ) === 0;\n\t\t\t\t\t}\n\t\t\t\t} while ( (elem = elem.parentNode) && elem.nodeType === 1 );\n\t\t\t\treturn false;\n\t\t\t};\n\t\t}),\n\n\t\t// Miscellaneous\n\t\t\"target\": function( elem ) {\n\t\t\tvar hash = window.location && window.location.hash;\n\t\t\treturn hash && hash.slice( 1 ) === elem.id;\n\t\t},\n\n\t\t\"root\": function( elem ) {\n\t\t\treturn elem === docElem;\n\t\t},\n\n\t\t\"focus\": function( elem ) {\n\t\t\treturn elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);\n\t\t},\n\n\t\t// Boolean properties\n\t\t\"enabled\": function( elem ) {\n\t\t\treturn elem.disabled === false;\n\t\t},\n\n\t\t\"disabled\": function( elem ) {\n\t\t\treturn elem.disabled === true;\n\t\t},\n\n\t\t\"checked\": function( elem ) {\n\t\t\t// In CSS3, :checked should return both checked and selected elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\tvar nodeName = elem.nodeName.toLowerCase();\n\t\t\treturn (nodeName === \"input\" && !!elem.checked) || (nodeName === \"option\" && !!elem.selected);\n\t\t},\n\n\t\t\"selected\": function( elem ) {\n\t\t\t// Accessing this property makes selected-by-default\n\t\t\t// options in Safari work properly\n\t\t\tif ( elem.parentNode ) {\n\t\t\t\telem.parentNode.selectedIndex;\n\t\t\t}\n\n\t\t\treturn elem.selected === true;\n\t\t},\n\n\t\t// Contents\n\t\t\"empty\": function( elem ) {\n\t\t\t// http://www.w3.org/TR/selectors/#empty-pseudo\n\t\t\t// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),\n\t\t\t//   but not by others (comment: 8; processing instruction: 7; etc.)\n\t\t\t// nodeType < 6 works because attributes (2) do not appear as children\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tif ( elem.nodeType < 6 ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\n\t\t\"parent\": function( elem ) {\n\t\t\treturn !Expr.pseudos[\"empty\"]( elem );\n\t\t},\n\n\t\t// Element/input types\n\t\t\"header\": function( elem ) {\n\t\t\treturn rheader.test( elem.nodeName );\n\t\t},\n\n\t\t\"input\": function( elem ) {\n\t\t\treturn rinputs.test( elem.nodeName );\n\t\t},\n\n\t\t\"button\": function( elem ) {\n\t\t\tvar name = elem.nodeName.toLowerCase();\n\t\t\treturn name === \"input\" && elem.type === \"button\" || name === \"button\";\n\t\t},\n\n\t\t\"text\": function( elem ) {\n\t\t\tvar attr;\n\t\t\treturn elem.nodeName.toLowerCase() === \"input\" &&\n\t\t\t\telem.type === \"text\" &&\n\n\t\t\t\t// Support: IE<8\n\t\t\t\t// New HTML5 attribute values (e.g., \"search\") appear with elem.type === \"text\"\n\t\t\t\t( (attr = elem.getAttribute(\"type\")) == null || attr.toLowerCase() === \"text\" );\n\t\t},\n\n\t\t// Position-in-collection\n\t\t\"first\": createPositionalPseudo(function() {\n\t\t\treturn [ 0 ];\n\t\t}),\n\n\t\t\"last\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\treturn [ length - 1 ];\n\t\t}),\n\n\t\t\"eq\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\treturn [ argument < 0 ? argument + length : argument ];\n\t\t}),\n\n\t\t\"even\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\tvar i = 0;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"odd\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\tvar i = 1;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"lt\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ? argument + length : argument;\n\t\t\tfor ( ; --i >= 0; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"gt\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ? argument + length : argument;\n\t\t\tfor ( ; ++i < length; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t})\n\t}\n};\n\nExpr.pseudos[\"nth\"] = Expr.pseudos[\"eq\"];\n\n// Add button/input type pseudos\nfor ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {\n\tExpr.pseudos[ i ] = createInputPseudo( i );\n}\nfor ( i in { submit: true, reset: true } ) {\n\tExpr.pseudos[ i ] = createButtonPseudo( i );\n}\n\n// Easy API for creating new setFilters\nfunction setFilters() {}\nsetFilters.prototype = Expr.filters = Expr.pseudos;\nExpr.setFilters = new setFilters();\n\nfunction tokenize( selector, parseOnly ) {\n\tvar matched, match, tokens, type,\n\t\tsoFar, groups, preFilters,\n\t\tcached = tokenCache[ selector + \" \" ];\n\n\tif ( cached ) {\n\t\treturn parseOnly ? 0 : cached.slice( 0 );\n\t}\n\n\tsoFar = selector;\n\tgroups = [];\n\tpreFilters = Expr.preFilter;\n\n\twhile ( soFar ) {\n\n\t\t// Comma and first run\n\t\tif ( !matched || (match = rcomma.exec( soFar )) ) {\n\t\t\tif ( match ) {\n\t\t\t\t// Don't consume trailing commas as valid\n\t\t\t\tsoFar = soFar.slice( match[0].length ) || soFar;\n\t\t\t}\n\t\t\tgroups.push( (tokens = []) );\n\t\t}\n\n\t\tmatched = false;\n\n\t\t// Combinators\n\t\tif ( (match = rcombinators.exec( soFar )) ) {\n\t\t\tmatched = match.shift();\n\t\t\ttokens.push({\n\t\t\t\tvalue: matched,\n\t\t\t\t// Cast descendant combinators to space\n\t\t\t\ttype: match[0].replace( rtrim, \" \" )\n\t\t\t});\n\t\t\tsoFar = soFar.slice( matched.length );\n\t\t}\n\n\t\t// Filters\n\t\tfor ( type in Expr.filter ) {\n\t\t\tif ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||\n\t\t\t\t(match = preFilters[ type ]( match ))) ) {\n\t\t\t\tmatched = match.shift();\n\t\t\t\ttokens.push({\n\t\t\t\t\tvalue: matched,\n\t\t\t\t\ttype: type,\n\t\t\t\t\tmatches: match\n\t\t\t\t});\n\t\t\t\tsoFar = soFar.slice( matched.length );\n\t\t\t}\n\t\t}\n\n\t\tif ( !matched ) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Return the length of the invalid excess\n\t// if we're just parsing\n\t// Otherwise, throw an error or return tokens\n\treturn parseOnly ?\n\t\tsoFar.length :\n\t\tsoFar ?\n\t\t\tSizzle.error( selector ) :\n\t\t\t// Cache the tokens\n\t\t\ttokenCache( selector, groups ).slice( 0 );\n}\n\nfunction toSelector( tokens ) {\n\tvar i = 0,\n\t\tlen = tokens.length,\n\t\tselector = \"\";\n\tfor ( ; i < len; i++ ) {\n\t\tselector += tokens[i].value;\n\t}\n\treturn selector;\n}\n\nfunction addCombinator( matcher, combinator, base ) {\n\tvar dir = combinator.dir,\n\t\tcheckNonElements = base && dir === \"parentNode\",\n\t\tdoneName = done++;\n\n\treturn combinator.first ?\n\t\t// Check against closest ancestor/preceding element\n\t\tfunction( elem, context, xml ) {\n\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\treturn matcher( elem, context, xml );\n\t\t\t\t}\n\t\t\t}\n\t\t} :\n\n\t\t// Check against all ancestor/preceding elements\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar oldCache, outerCache,\n\t\t\t\tnewCache = [ dirruns, doneName ];\n\n\t\t\t// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching\n\t\t\tif ( xml ) {\n\t\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\touterCache = elem[ expando ] || (elem[ expando ] = {});\n\t\t\t\t\t\tif ( (oldCache = outerCache[ dir ]) &&\n\t\t\t\t\t\t\toldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {\n\n\t\t\t\t\t\t\t// Assign to newCache so results back-propagate to previous elements\n\t\t\t\t\t\t\treturn (newCache[ 2 ] = oldCache[ 2 ]);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Reuse newcache so results back-propagate to previous elements\n\t\t\t\t\t\t\touterCache[ dir ] = newCache;\n\n\t\t\t\t\t\t\t// A match means we're done; a fail means we have to keep checking\n\t\t\t\t\t\t\tif ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n}\n\nfunction elementMatcher( matchers ) {\n\treturn matchers.length > 1 ?\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar i = matchers.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( !matchers[i]( elem, context, xml ) ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t} :\n\t\tmatchers[0];\n}\n\nfunction condense( unmatched, map, filter, context, xml ) {\n\tvar elem,\n\t\tnewUnmatched = [],\n\t\ti = 0,\n\t\tlen = unmatched.length,\n\t\tmapped = map != null;\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( (elem = unmatched[i]) ) {\n\t\t\tif ( !filter || filter( elem, context, xml ) ) {\n\t\t\t\tnewUnmatched.push( elem );\n\t\t\t\tif ( mapped ) {\n\t\t\t\t\tmap.push( i );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn newUnmatched;\n}\n\nfunction setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {\n\tif ( postFilter && !postFilter[ expando ] ) {\n\t\tpostFilter = setMatcher( postFilter );\n\t}\n\tif ( postFinder && !postFinder[ expando ] ) {\n\t\tpostFinder = setMatcher( postFinder, postSelector );\n\t}\n\treturn markFunction(function( seed, results, context, xml ) {\n\t\tvar temp, i, elem,\n\t\t\tpreMap = [],\n\t\t\tpostMap = [],\n\t\t\tpreexisting = results.length,\n\n\t\t\t// Get initial elements from seed or context\n\t\t\telems = seed || multipleContexts( selector || \"*\", context.nodeType ? [ context ] : context, [] ),\n\n\t\t\t// Prefilter to get matcher input, preserving a map for seed-results synchronization\n\t\t\tmatcherIn = preFilter && ( seed || !selector ) ?\n\t\t\t\tcondense( elems, preMap, preFilter, context, xml ) :\n\t\t\t\telems,\n\n\t\t\tmatcherOut = matcher ?\n\t\t\t\t// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,\n\t\t\t\tpostFinder || ( seed ? preFilter : preexisting || postFilter ) ?\n\n\t\t\t\t\t// ...intermediate processing is necessary\n\t\t\t\t\t[] :\n\n\t\t\t\t\t// ...otherwise use results directly\n\t\t\t\t\tresults :\n\t\t\t\tmatcherIn;\n\n\t\t// Find primary matches\n\t\tif ( matcher ) {\n\t\t\tmatcher( matcherIn, matcherOut, context, xml );\n\t\t}\n\n\t\t// Apply postFilter\n\t\tif ( postFilter ) {\n\t\t\ttemp = condense( matcherOut, postMap );\n\t\t\tpostFilter( temp, [], context, xml );\n\n\t\t\t// Un-match failing elements by moving them back to matcherIn\n\t\t\ti = temp.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( (elem = temp[i]) ) {\n\t\t\t\t\tmatcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( seed ) {\n\t\t\tif ( postFinder || preFilter ) {\n\t\t\t\tif ( postFinder ) {\n\t\t\t\t\t// Get the final matcherOut by condensing this intermediate into postFinder contexts\n\t\t\t\t\ttemp = [];\n\t\t\t\t\ti = matcherOut.length;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( (elem = matcherOut[i]) ) {\n\t\t\t\t\t\t\t// Restore matcherIn since elem is not yet a final match\n\t\t\t\t\t\t\ttemp.push( (matcherIn[i] = elem) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpostFinder( null, (matcherOut = []), temp, xml );\n\t\t\t\t}\n\n\t\t\t\t// Move matched elements from seed to results to keep them synchronized\n\t\t\t\ti = matcherOut.length;\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\tif ( (elem = matcherOut[i]) &&\n\t\t\t\t\t\t(temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {\n\n\t\t\t\t\t\tseed[temp] = !(results[temp] = elem);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Add elements to results, through postFinder if defined\n\t\t} else {\n\t\t\tmatcherOut = condense(\n\t\t\t\tmatcherOut === results ?\n\t\t\t\t\tmatcherOut.splice( preexisting, matcherOut.length ) :\n\t\t\t\t\tmatcherOut\n\t\t\t);\n\t\t\tif ( postFinder ) {\n\t\t\t\tpostFinder( null, results, matcherOut, xml );\n\t\t\t} else {\n\t\t\t\tpush.apply( results, matcherOut );\n\t\t\t}\n\t\t}\n\t});\n}\n\nfunction matcherFromTokens( tokens ) {\n\tvar checkContext, matcher, j,\n\t\tlen = tokens.length,\n\t\tleadingRelative = Expr.relative[ tokens[0].type ],\n\t\timplicitRelative = leadingRelative || Expr.relative[\" \"],\n\t\ti = leadingRelative ? 1 : 0,\n\n\t\t// The foundational matcher ensures that elements are reachable from top-level context(s)\n\t\tmatchContext = addCombinator( function( elem ) {\n\t\t\treturn elem === checkContext;\n\t\t}, implicitRelative, true ),\n\t\tmatchAnyContext = addCombinator( function( elem ) {\n\t\t\treturn indexOf.call( checkContext, elem ) > -1;\n\t\t}, implicitRelative, true ),\n\t\tmatchers = [ function( elem, context, xml ) {\n\t\t\treturn ( !leadingRelative && ( xml || context !== outermostContext ) ) || (\n\t\t\t\t(checkContext = context).nodeType ?\n\t\t\t\t\tmatchContext( elem, context, xml ) :\n\t\t\t\t\tmatchAnyContext( elem, context, xml ) );\n\t\t} ];\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( (matcher = Expr.relative[ tokens[i].type ]) ) {\n\t\t\tmatchers = [ addCombinator(elementMatcher( matchers ), matcher) ];\n\t\t} else {\n\t\t\tmatcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );\n\n\t\t\t// Return special upon seeing a positional matcher\n\t\t\tif ( matcher[ expando ] ) {\n\t\t\t\t// Find the next relative operator (if any) for proper handling\n\t\t\t\tj = ++i;\n\t\t\t\tfor ( ; j < len; j++ ) {\n\t\t\t\t\tif ( Expr.relative[ tokens[j].type ] ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn setMatcher(\n\t\t\t\t\ti > 1 && elementMatcher( matchers ),\n\t\t\t\t\ti > 1 && toSelector(\n\t\t\t\t\t\t// If the preceding token was a descendant combinator, insert an implicit any-element `*`\n\t\t\t\t\t\ttokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === \" \" ? \"*\" : \"\" })\n\t\t\t\t\t).replace( rtrim, \"$1\" ),\n\t\t\t\t\tmatcher,\n\t\t\t\t\ti < j && matcherFromTokens( tokens.slice( i, j ) ),\n\t\t\t\t\tj < len && matcherFromTokens( (tokens = tokens.slice( j )) ),\n\t\t\t\t\tj < len && toSelector( tokens )\n\t\t\t\t);\n\t\t\t}\n\t\t\tmatchers.push( matcher );\n\t\t}\n\t}\n\n\treturn elementMatcher( matchers );\n}\n\nfunction matcherFromGroupMatchers( elementMatchers, setMatchers ) {\n\tvar bySet = setMatchers.length > 0,\n\t\tbyElement = elementMatchers.length > 0,\n\t\tsuperMatcher = function( seed, context, xml, results, outermost ) {\n\t\t\tvar elem, j, matcher,\n\t\t\t\tmatchedCount = 0,\n\t\t\t\ti = \"0\",\n\t\t\t\tunmatched = seed && [],\n\t\t\t\tsetMatched = [],\n\t\t\t\tcontextBackup = outermostContext,\n\t\t\t\t// We must always have either seed elements or outermost context\n\t\t\t\telems = seed || byElement && Expr.find[\"TAG\"]( \"*\", outermost ),\n\t\t\t\t// Use integer dirruns iff this is the outermost matcher\n\t\t\t\tdirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),\n\t\t\t\tlen = elems.length;\n\n\t\t\tif ( outermost ) {\n\t\t\t\toutermostContext = context !== document && context;\n\t\t\t}\n\n\t\t\t// Add elements passing elementMatchers directly to results\n\t\t\t// Keep `i` a string if there are no elements so `matchedCount` will be \"00\" below\n\t\t\t// Support: IE<9, Safari\n\t\t\t// Tolerate NodeList properties (IE: \"length\"; Safari: <number>) matching elements by id\n\t\t\tfor ( ; i !== len && (elem = elems[i]) != null; i++ ) {\n\t\t\t\tif ( byElement && elem ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (matcher = elementMatchers[j++]) ) {\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( outermost ) {\n\t\t\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Track unmatched elements for set filters\n\t\t\t\tif ( bySet ) {\n\t\t\t\t\t// They will have gone through all possible matchers\n\t\t\t\t\tif ( (elem = !matcher && elem) ) {\n\t\t\t\t\t\tmatchedCount--;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Lengthen the array for every element, matched or not\n\t\t\t\t\tif ( seed ) {\n\t\t\t\t\t\tunmatched.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Apply set filters to unmatched elements\n\t\t\tmatchedCount += i;\n\t\t\tif ( bySet && i !== matchedCount ) {\n\t\t\t\tj = 0;\n\t\t\t\twhile ( (matcher = setMatchers[j++]) ) {\n\t\t\t\t\tmatcher( unmatched, setMatched, context, xml );\n\t\t\t\t}\n\n\t\t\t\tif ( seed ) {\n\t\t\t\t\t// Reintegrate element matches to eliminate the need for sorting\n\t\t\t\t\tif ( matchedCount > 0 ) {\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tif ( !(unmatched[i] || setMatched[i]) ) {\n\t\t\t\t\t\t\t\tsetMatched[i] = pop.call( results );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Discard index placeholder values to get only actual matches\n\t\t\t\t\tsetMatched = condense( setMatched );\n\t\t\t\t}\n\n\t\t\t\t// Add matches to results\n\t\t\t\tpush.apply( results, setMatched );\n\n\t\t\t\t// Seedless set matches succeeding multiple successful matchers stipulate sorting\n\t\t\t\tif ( outermost && !seed && setMatched.length > 0 &&\n\t\t\t\t\t( matchedCount + setMatchers.length ) > 1 ) {\n\n\t\t\t\t\tSizzle.uniqueSort( results );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Override manipulation of globals by nested matchers\n\t\t\tif ( outermost ) {\n\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\toutermostContext = contextBackup;\n\t\t\t}\n\n\t\t\treturn unmatched;\n\t\t};\n\n\treturn bySet ?\n\t\tmarkFunction( superMatcher ) :\n\t\tsuperMatcher;\n}\n\ncompile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {\n\tvar i,\n\t\tsetMatchers = [],\n\t\telementMatchers = [],\n\t\tcached = compilerCache[ selector + \" \" ];\n\n\tif ( !cached ) {\n\t\t// Generate a function of recursive functions that can be used to check each element\n\t\tif ( !group ) {\n\t\t\tgroup = tokenize( selector );\n\t\t}\n\t\ti = group.length;\n\t\twhile ( i-- ) {\n\t\t\tcached = matcherFromTokens( group[i] );\n\t\t\tif ( cached[ expando ] ) {\n\t\t\t\tsetMatchers.push( cached );\n\t\t\t} else {\n\t\t\t\telementMatchers.push( cached );\n\t\t\t}\n\t\t}\n\n\t\t// Cache the compiled function\n\t\tcached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );\n\t}\n\treturn cached;\n};\n\nfunction multipleContexts( selector, contexts, results ) {\n\tvar i = 0,\n\t\tlen = contexts.length;\n\tfor ( ; i < len; i++ ) {\n\t\tSizzle( selector, contexts[i], results );\n\t}\n\treturn results;\n}\n\nfunction select( selector, context, results, seed ) {\n\tvar i, tokens, token, type, find,\n\t\tmatch = tokenize( selector );\n\n\tif ( !seed ) {\n\t\t// Try to minimize operations if there is only one group\n\t\tif ( match.length === 1 ) {\n\n\t\t\t// Take a shortcut and set the context if the root selector is an ID\n\t\t\ttokens = match[0] = match[0].slice( 0 );\n\t\t\tif ( tokens.length > 2 && (token = tokens[0]).type === \"ID\" &&\n\t\t\t\t\tsupport.getById && context.nodeType === 9 && documentIsHTML &&\n\t\t\t\t\tExpr.relative[ tokens[1].type ] ) {\n\n\t\t\t\tcontext = ( Expr.find[\"ID\"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];\n\t\t\t\tif ( !context ) {\n\t\t\t\t\treturn results;\n\t\t\t\t}\n\t\t\t\tselector = selector.slice( tokens.shift().value.length );\n\t\t\t}\n\n\t\t\t// Fetch a seed set for right-to-left matching\n\t\t\ti = matchExpr[\"needsContext\"].test( selector ) ? 0 : tokens.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\ttoken = tokens[i];\n\n\t\t\t\t// Abort if we hit a combinator\n\t\t\t\tif ( Expr.relative[ (type = token.type) ] ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif ( (find = Expr.find[ type ]) ) {\n\t\t\t\t\t// Search, expanding context for leading sibling combinators\n\t\t\t\t\tif ( (seed = find(\n\t\t\t\t\t\ttoken.matches[0].replace( runescape, funescape ),\n\t\t\t\t\t\trsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context\n\t\t\t\t\t)) ) {\n\n\t\t\t\t\t\t// If seed is empty or no tokens remain, we can return early\n\t\t\t\t\t\ttokens.splice( i, 1 );\n\t\t\t\t\t\tselector = seed.length && toSelector( tokens );\n\t\t\t\t\t\tif ( !selector ) {\n\t\t\t\t\t\t\tpush.apply( results, seed );\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Compile and execute a filtering function\n\t// Provide `match` to avoid retokenization if we modified the selector above\n\tcompile( selector, match )(\n\t\tseed,\n\t\tcontext,\n\t\t!documentIsHTML,\n\t\tresults,\n\t\trsibling.test( selector ) && testContext( context.parentNode ) || context\n\t);\n\treturn results;\n}\n\n// One-time assignments\n\n// Sort stability\nsupport.sortStable = expando.split(\"\").sort( sortOrder ).join(\"\") === expando;\n\n// Support: Chrome<14\n// Always assume duplicates if they aren't passed to the comparison function\nsupport.detectDuplicates = !!hasDuplicate;\n\n// Initialize against the default document\nsetDocument();\n\n// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)\n// Detached nodes confoundingly follow *each other*\nsupport.sortDetached = assert(function( div1 ) {\n\t// Should return 1, but returns 4 (following)\n\treturn div1.compareDocumentPosition( document.createElement(\"div\") ) & 1;\n});\n\n// Support: IE<8\n// Prevent attribute/property \"interpolation\"\n// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx\nif ( !assert(function( div ) {\n\tdiv.innerHTML = \"<a href='#'></a>\";\n\treturn div.firstChild.getAttribute(\"href\") === \"#\" ;\n}) ) {\n\taddHandle( \"type|href|height|width\", function( elem, name, isXML ) {\n\t\tif ( !isXML ) {\n\t\t\treturn elem.getAttribute( name, name.toLowerCase() === \"type\" ? 1 : 2 );\n\t\t}\n\t});\n}\n\n// Support: IE<9\n// Use defaultValue in place of getAttribute(\"value\")\nif ( !support.attributes || !assert(function( div ) {\n\tdiv.innerHTML = \"<input/>\";\n\tdiv.firstChild.setAttribute( \"value\", \"\" );\n\treturn div.firstChild.getAttribute( \"value\" ) === \"\";\n}) ) {\n\taddHandle( \"value\", function( elem, name, isXML ) {\n\t\tif ( !isXML && elem.nodeName.toLowerCase() === \"input\" ) {\n\t\t\treturn elem.defaultValue;\n\t\t}\n\t});\n}\n\n// Support: IE<9\n// Use getAttributeNode to fetch booleans when getAttribute lies\nif ( !assert(function( div ) {\n\treturn div.getAttribute(\"disabled\") == null;\n}) ) {\n\taddHandle( booleans, function( elem, name, isXML ) {\n\t\tvar val;\n\t\tif ( !isXML ) {\n\t\t\treturn elem[ name ] === true ? name.toLowerCase() :\n\t\t\t\t\t(val = elem.getAttributeNode( name )) && val.specified ?\n\t\t\t\t\tval.value :\n\t\t\t\tnull;\n\t\t}\n\t});\n}\n\nreturn Sizzle;\n\n})( window );\n\n\n\njQuery.find = Sizzle;\njQuery.expr = Sizzle.selectors;\njQuery.expr[\":\"] = jQuery.expr.pseudos;\njQuery.unique = Sizzle.uniqueSort;\njQuery.text = Sizzle.getText;\njQuery.isXMLDoc = Sizzle.isXML;\njQuery.contains = Sizzle.contains;\n\n\n\nvar rneedsContext = jQuery.expr.match.needsContext;\n\nvar rsingleTag = (/^<(\\w+)\\s*\\/?>(?:<\\/\\1>|)$/);\n\n\n\nvar risSimple = /^.[^:#\\[\\.,]*$/;\n\n// Implement the identical functionality for filter and not\nfunction winnow( elements, qualifier, not ) {\n\tif ( jQuery.isFunction( qualifier ) ) {\n\t\treturn jQuery.grep( elements, function( elem, i ) {\n\t\t\t/* jshint -W018 */\n\t\t\treturn !!qualifier.call( elem, i, elem ) !== not;\n\t\t});\n\n\t}\n\n\tif ( qualifier.nodeType ) {\n\t\treturn jQuery.grep( elements, function( elem ) {\n\t\t\treturn ( elem === qualifier ) !== not;\n\t\t});\n\n\t}\n\n\tif ( typeof qualifier === \"string\" ) {\n\t\tif ( risSimple.test( qualifier ) ) {\n\t\t\treturn jQuery.filter( qualifier, elements, not );\n\t\t}\n\n\t\tqualifier = jQuery.filter( qualifier, elements );\n\t}\n\n\treturn jQuery.grep( elements, function( elem ) {\n\t\treturn ( indexOf.call( qualifier, elem ) >= 0 ) !== not;\n\t});\n}\n\njQuery.filter = function( expr, elems, not ) {\n\tvar elem = elems[ 0 ];\n\n\tif ( not ) {\n\t\texpr = \":not(\" + expr + \")\";\n\t}\n\n\treturn elems.length === 1 && elem.nodeType === 1 ?\n\t\tjQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :\n\t\tjQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {\n\t\t\treturn elem.nodeType === 1;\n\t\t}));\n};\n\njQuery.fn.extend({\n\tfind: function( selector ) {\n\t\tvar i,\n\t\t\tlen = this.length,\n\t\t\tret = [],\n\t\t\tself = this;\n\n\t\tif ( typeof selector !== \"string\" ) {\n\t\t\treturn this.pushStack( jQuery( selector ).filter(function() {\n\t\t\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\t\t\tif ( jQuery.contains( self[ i ], this ) ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}) );\n\t\t}\n\n\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\tjQuery.find( selector, self[ i ], ret );\n\t\t}\n\n\t\t// Needed because $( selector, context ) becomes $( context ).find( selector )\n\t\tret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );\n\t\tret.selector = this.selector ? this.selector + \" \" + selector : selector;\n\t\treturn ret;\n\t},\n\tfilter: function( selector ) {\n\t\treturn this.pushStack( winnow(this, selector || [], false) );\n\t},\n\tnot: function( selector ) {\n\t\treturn this.pushStack( winnow(this, selector || [], true) );\n\t},\n\tis: function( selector ) {\n\t\treturn !!winnow(\n\t\t\tthis,\n\n\t\t\t// If this is a positional/relative selector, check membership in the returned set\n\t\t\t// so $(\"p:first\").is(\"p:last\") won't return true for a doc with two \"p\".\n\t\t\ttypeof selector === \"string\" && rneedsContext.test( selector ) ?\n\t\t\t\tjQuery( selector ) :\n\t\t\t\tselector || [],\n\t\t\tfalse\n\t\t).length;\n\t}\n});\n\n\n// Initialize a jQuery object\n\n\n// A central reference to the root jQuery(document)\nvar rootjQuery,\n\n\t// A simple way to check for HTML strings\n\t// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)\n\t// Strict HTML recognition (#11290: must start with <)\n\trquickExpr = /^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]*))$/,\n\n\tinit = jQuery.fn.init = function( selector, context ) {\n\t\tvar match, elem;\n\n\t\t// HANDLE: $(\"\"), $(null), $(undefined), $(false)\n\t\tif ( !selector ) {\n\t\t\treturn this;\n\t\t}\n\n\t\t// Handle HTML strings\n\t\tif ( typeof selector === \"string\" ) {\n\t\t\tif ( selector[0] === \"<\" && selector[ selector.length - 1 ] === \">\" && selector.length >= 3 ) {\n\t\t\t\t// Assume that strings that start and end with <> are HTML and skip the regex check\n\t\t\t\tmatch = [ null, selector, null ];\n\n\t\t\t} else {\n\t\t\t\tmatch = rquickExpr.exec( selector );\n\t\t\t}\n\n\t\t\t// Match html or make sure no context is specified for #id\n\t\t\tif ( match && (match[1] || !context) ) {\n\n\t\t\t\t// HANDLE: $(html) -> $(array)\n\t\t\t\tif ( match[1] ) {\n\t\t\t\t\tcontext = context instanceof jQuery ? context[0] : context;\n\n\t\t\t\t\t// scripts is true for back-compat\n\t\t\t\t\t// Intentionally let the error be thrown if parseHTML is not present\n\t\t\t\t\tjQuery.merge( this, jQuery.parseHTML(\n\t\t\t\t\t\tmatch[1],\n\t\t\t\t\t\tcontext && context.nodeType ? context.ownerDocument || context : document,\n\t\t\t\t\t\ttrue\n\t\t\t\t\t) );\n\n\t\t\t\t\t// HANDLE: $(html, props)\n\t\t\t\t\tif ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {\n\t\t\t\t\t\tfor ( match in context ) {\n\t\t\t\t\t\t\t// Properties of context are called as methods if possible\n\t\t\t\t\t\t\tif ( jQuery.isFunction( this[ match ] ) ) {\n\t\t\t\t\t\t\t\tthis[ match ]( context[ match ] );\n\n\t\t\t\t\t\t\t// ...and otherwise set as attributes\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.attr( match, context[ match ] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn this;\n\n\t\t\t\t// HANDLE: $(#id)\n\t\t\t\t} else {\n\t\t\t\t\telem = document.getElementById( match[2] );\n\n\t\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t\t// nodes that are no longer in the document #6963\n\t\t\t\t\tif ( elem && elem.parentNode ) {\n\t\t\t\t\t\t// Inject the element directly into the jQuery object\n\t\t\t\t\t\tthis.length = 1;\n\t\t\t\t\t\tthis[0] = elem;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.context = document;\n\t\t\t\t\tthis.selector = selector;\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\n\t\t\t// HANDLE: $(expr, $(...))\n\t\t\t} else if ( !context || context.jquery ) {\n\t\t\t\treturn ( context || rootjQuery ).find( selector );\n\n\t\t\t// HANDLE: $(expr, context)\n\t\t\t// (which is just equivalent to: $(context).find(expr)\n\t\t\t} else {\n\t\t\t\treturn this.constructor( context ).find( selector );\n\t\t\t}\n\n\t\t// HANDLE: $(DOMElement)\n\t\t} else if ( selector.nodeType ) {\n\t\t\tthis.context = this[0] = selector;\n\t\t\tthis.length = 1;\n\t\t\treturn this;\n\n\t\t// HANDLE: $(function)\n\t\t// Shortcut for document ready\n\t\t} else if ( jQuery.isFunction( selector ) ) {\n\t\t\treturn typeof rootjQuery.ready !== \"undefined\" ?\n\t\t\t\trootjQuery.ready( selector ) :\n\t\t\t\t// Execute immediately if ready is not present\n\t\t\t\tselector( jQuery );\n\t\t}\n\n\t\tif ( selector.selector !== undefined ) {\n\t\t\tthis.selector = selector.selector;\n\t\t\tthis.context = selector.context;\n\t\t}\n\n\t\treturn jQuery.makeArray( selector, this );\n\t};\n\n// Give the init function the jQuery prototype for later instantiation\ninit.prototype = jQuery.fn;\n\n// Initialize central reference\nrootjQuery = jQuery( document );\n\n\nvar rparentsprev = /^(?:parents|prev(?:Until|All))/,\n\t// methods guaranteed to produce a unique set when starting from a unique set\n\tguaranteedUnique = {\n\t\tchildren: true,\n\t\tcontents: true,\n\t\tnext: true,\n\t\tprev: true\n\t};\n\njQuery.extend({\n\tdir: function( elem, dir, until ) {\n\t\tvar matched = [],\n\t\t\ttruncate = until !== undefined;\n\n\t\twhile ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) {\n\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\tif ( truncate && jQuery( elem ).is( until ) ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tmatched.push( elem );\n\t\t\t}\n\t\t}\n\t\treturn matched;\n\t},\n\n\tsibling: function( n, elem ) {\n\t\tvar matched = [];\n\n\t\tfor ( ; n; n = n.nextSibling ) {\n\t\t\tif ( n.nodeType === 1 && n !== elem ) {\n\t\t\t\tmatched.push( n );\n\t\t\t}\n\t\t}\n\n\t\treturn matched;\n\t}\n});\n\njQuery.fn.extend({\n\thas: function( target ) {\n\t\tvar targets = jQuery( target, this ),\n\t\t\tl = targets.length;\n\n\t\treturn this.filter(function() {\n\t\t\tvar i = 0;\n\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\tif ( jQuery.contains( this, targets[i] ) ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n\n\tclosest: function( selectors, context ) {\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\tl = this.length,\n\t\t\tmatched = [],\n\t\t\tpos = rneedsContext.test( selectors ) || typeof selectors !== \"string\" ?\n\t\t\t\tjQuery( selectors, context || this.context ) :\n\t\t\t\t0;\n\n\t\tfor ( ; i < l; i++ ) {\n\t\t\tfor ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {\n\t\t\t\t// Always skip document fragments\n\t\t\t\tif ( cur.nodeType < 11 && (pos ?\n\t\t\t\t\tpos.index(cur) > -1 :\n\n\t\t\t\t\t// Don't pass non-elements to Sizzle\n\t\t\t\t\tcur.nodeType === 1 &&\n\t\t\t\t\t\tjQuery.find.matchesSelector(cur, selectors)) ) {\n\n\t\t\t\t\tmatched.push( cur );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );\n\t},\n\n\t// Determine the position of an element within\n\t// the matched set of elements\n\tindex: function( elem ) {\n\n\t\t// No argument, return index in parent\n\t\tif ( !elem ) {\n\t\t\treturn ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;\n\t\t}\n\n\t\t// index in selector\n\t\tif ( typeof elem === \"string\" ) {\n\t\t\treturn indexOf.call( jQuery( elem ), this[ 0 ] );\n\t\t}\n\n\t\t// Locate the position of the desired element\n\t\treturn indexOf.call( this,\n\n\t\t\t// If it receives a jQuery object, the first element is used\n\t\t\telem.jquery ? elem[ 0 ] : elem\n\t\t);\n\t},\n\n\tadd: function( selector, context ) {\n\t\treturn this.pushStack(\n\t\t\tjQuery.unique(\n\t\t\t\tjQuery.merge( this.get(), jQuery( selector, context ) )\n\t\t\t)\n\t\t);\n\t},\n\n\taddBack: function( selector ) {\n\t\treturn this.add( selector == null ?\n\t\t\tthis.prevObject : this.prevObject.filter(selector)\n\t\t);\n\t}\n});\n\nfunction sibling( cur, dir ) {\n\twhile ( (cur = cur[dir]) && cur.nodeType !== 1 ) {}\n\treturn cur;\n}\n\njQuery.each({\n\tparent: function( elem ) {\n\t\tvar parent = elem.parentNode;\n\t\treturn parent && parent.nodeType !== 11 ? parent : null;\n\t},\n\tparents: function( elem ) {\n\t\treturn jQuery.dir( elem, \"parentNode\" );\n\t},\n\tparentsUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"parentNode\", until );\n\t},\n\tnext: function( elem ) {\n\t\treturn sibling( elem, \"nextSibling\" );\n\t},\n\tprev: function( elem ) {\n\t\treturn sibling( elem, \"previousSibling\" );\n\t},\n\tnextAll: function( elem ) {\n\t\treturn jQuery.dir( elem, \"nextSibling\" );\n\t},\n\tprevAll: function( elem ) {\n\t\treturn jQuery.dir( elem, \"previousSibling\" );\n\t},\n\tnextUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"nextSibling\", until );\n\t},\n\tprevUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"previousSibling\", until );\n\t},\n\tsiblings: function( elem ) {\n\t\treturn jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );\n\t},\n\tchildren: function( elem ) {\n\t\treturn jQuery.sibling( elem.firstChild );\n\t},\n\tcontents: function( elem ) {\n\t\treturn elem.contentDocument || jQuery.merge( [], elem.childNodes );\n\t}\n}, function( name, fn ) {\n\tjQuery.fn[ name ] = function( until, selector ) {\n\t\tvar matched = jQuery.map( this, fn, until );\n\n\t\tif ( name.slice( -5 ) !== \"Until\" ) {\n\t\t\tselector = until;\n\t\t}\n\n\t\tif ( selector && typeof selector === \"string\" ) {\n\t\t\tmatched = jQuery.filter( selector, matched );\n\t\t}\n\n\t\tif ( this.length > 1 ) {\n\t\t\t// Remove duplicates\n\t\t\tif ( !guaranteedUnique[ name ] ) {\n\t\t\t\tjQuery.unique( matched );\n\t\t\t}\n\n\t\t\t// Reverse order for parents* and prev-derivatives\n\t\t\tif ( rparentsprev.test( name ) ) {\n\t\t\t\tmatched.reverse();\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack( matched );\n\t};\n});\nvar rnotwhite = (/\\S+/g);\n\n\n\n// String to Object options format cache\nvar optionsCache = {};\n\n// Convert String-formatted options into Object-formatted ones and store in cache\nfunction createOptions( options ) {\n\tvar object = optionsCache[ options ] = {};\n\tjQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {\n\t\tobject[ flag ] = true;\n\t});\n\treturn object;\n}\n\n/*\n * Create a callback list using the following parameters:\n *\n *\toptions: an optional list of space-separated options that will change how\n *\t\t\tthe callback list behaves or a more traditional option object\n *\n * By default a callback list will act like an event callback list and can be\n * \"fired\" multiple times.\n *\n * Possible options:\n *\n *\tonce:\t\t\twill ensure the callback list can only be fired once (like a Deferred)\n *\n *\tmemory:\t\t\twill keep track of previous values and will call any callback added\n *\t\t\t\t\tafter the list has been fired right away with the latest \"memorized\"\n *\t\t\t\t\tvalues (like a Deferred)\n *\n *\tunique:\t\t\twill ensure a callback can only be added once (no duplicate in the list)\n *\n *\tstopOnFalse:\tinterrupt callings when a callback returns false\n *\n */\njQuery.Callbacks = function( options ) {\n\n\t// Convert options from String-formatted to Object-formatted if needed\n\t// (we check in cache first)\n\toptions = typeof options === \"string\" ?\n\t\t( optionsCache[ options ] || createOptions( options ) ) :\n\t\tjQuery.extend( {}, options );\n\n\tvar // Last fire value (for non-forgettable lists)\n\t\tmemory,\n\t\t// Flag to know if list was already fired\n\t\tfired,\n\t\t// Flag to know if list is currently firing\n\t\tfiring,\n\t\t// First callback to fire (used internally by add and fireWith)\n\t\tfiringStart,\n\t\t// End of the loop when firing\n\t\tfiringLength,\n\t\t// Index of currently firing callback (modified by remove if needed)\n\t\tfiringIndex,\n\t\t// Actual callback list\n\t\tlist = [],\n\t\t// Stack of fire calls for repeatable lists\n\t\tstack = !options.once && [],\n\t\t// Fire callbacks\n\t\tfire = function( data ) {\n\t\t\tmemory = options.memory && data;\n\t\t\tfired = true;\n\t\t\tfiringIndex = firingStart || 0;\n\t\t\tfiringStart = 0;\n\t\t\tfiringLength = list.length;\n\t\t\tfiring = true;\n\t\t\tfor ( ; list && firingIndex < firingLength; firingIndex++ ) {\n\t\t\t\tif ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {\n\t\t\t\t\tmemory = false; // To prevent further calls using add\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfiring = false;\n\t\t\tif ( list ) {\n\t\t\t\tif ( stack ) {\n\t\t\t\t\tif ( stack.length ) {\n\t\t\t\t\t\tfire( stack.shift() );\n\t\t\t\t\t}\n\t\t\t\t} else if ( memory ) {\n\t\t\t\t\tlist = [];\n\t\t\t\t} else {\n\t\t\t\t\tself.disable();\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// Actual Callbacks object\n\t\tself = {\n\t\t\t// Add a callback or a collection of callbacks to the list\n\t\t\tadd: function() {\n\t\t\t\tif ( list ) {\n\t\t\t\t\t// First, we save the current length\n\t\t\t\t\tvar start = list.length;\n\t\t\t\t\t(function add( args ) {\n\t\t\t\t\t\tjQuery.each( args, function( _, arg ) {\n\t\t\t\t\t\t\tvar type = jQuery.type( arg );\n\t\t\t\t\t\t\tif ( type === \"function\" ) {\n\t\t\t\t\t\t\t\tif ( !options.unique || !self.has( arg ) ) {\n\t\t\t\t\t\t\t\t\tlist.push( arg );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if ( arg && arg.length && type !== \"string\" ) {\n\t\t\t\t\t\t\t\t// Inspect recursively\n\t\t\t\t\t\t\t\tadd( arg );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t})( arguments );\n\t\t\t\t\t// Do we need to add the callbacks to the\n\t\t\t\t\t// current firing batch?\n\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\tfiringLength = list.length;\n\t\t\t\t\t// With memory, if we're not firing then\n\t\t\t\t\t// we should call right away\n\t\t\t\t\t} else if ( memory ) {\n\t\t\t\t\t\tfiringStart = start;\n\t\t\t\t\t\tfire( memory );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Remove a callback from the list\n\t\t\tremove: function() {\n\t\t\t\tif ( list ) {\n\t\t\t\t\tjQuery.each( arguments, function( _, arg ) {\n\t\t\t\t\t\tvar index;\n\t\t\t\t\t\twhile ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {\n\t\t\t\t\t\t\tlist.splice( index, 1 );\n\t\t\t\t\t\t\t// Handle firing indexes\n\t\t\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\t\t\tif ( index <= firingLength ) {\n\t\t\t\t\t\t\t\t\tfiringLength--;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif ( index <= firingIndex ) {\n\t\t\t\t\t\t\t\t\tfiringIndex--;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Check if a given callback is in the list.\n\t\t\t// If no argument is given, return whether or not list has callbacks attached.\n\t\t\thas: function( fn ) {\n\t\t\t\treturn fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );\n\t\t\t},\n\t\t\t// Remove all callbacks from the list\n\t\t\tempty: function() {\n\t\t\t\tlist = [];\n\t\t\t\tfiringLength = 0;\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Have the list do nothing anymore\n\t\t\tdisable: function() {\n\t\t\t\tlist = stack = memory = undefined;\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Is it disabled?\n\t\t\tdisabled: function() {\n\t\t\t\treturn !list;\n\t\t\t},\n\t\t\t// Lock the list in its current state\n\t\t\tlock: function() {\n\t\t\t\tstack = undefined;\n\t\t\t\tif ( !memory ) {\n\t\t\t\t\tself.disable();\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Is it locked?\n\t\t\tlocked: function() {\n\t\t\t\treturn !stack;\n\t\t\t},\n\t\t\t// Call all callbacks with the given context and arguments\n\t\t\tfireWith: function( context, args ) {\n\t\t\t\tif ( list && ( !fired || stack ) ) {\n\t\t\t\t\targs = args || [];\n\t\t\t\t\targs = [ context, args.slice ? args.slice() : args ];\n\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\tstack.push( args );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfire( args );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Call all the callbacks with the given arguments\n\t\t\tfire: function() {\n\t\t\t\tself.fireWith( this, arguments );\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// To know if the callbacks have already been called at least once\n\t\t\tfired: function() {\n\t\t\t\treturn !!fired;\n\t\t\t}\n\t\t};\n\n\treturn self;\n};\n\n\njQuery.extend({\n\n\tDeferred: function( func ) {\n\t\tvar tuples = [\n\t\t\t\t// action, add listener, listener list, final state\n\t\t\t\t[ \"resolve\", \"done\", jQuery.Callbacks(\"once memory\"), \"resolved\" ],\n\t\t\t\t[ \"reject\", \"fail\", jQuery.Callbacks(\"once memory\"), \"rejected\" ],\n\t\t\t\t[ \"notify\", \"progress\", jQuery.Callbacks(\"memory\") ]\n\t\t\t],\n\t\t\tstate = \"pending\",\n\t\t\tpromise = {\n\t\t\t\tstate: function() {\n\t\t\t\t\treturn state;\n\t\t\t\t},\n\t\t\t\talways: function() {\n\t\t\t\t\tdeferred.done( arguments ).fail( arguments );\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\t\t\t\tthen: function( /* fnDone, fnFail, fnProgress */ ) {\n\t\t\t\t\tvar fns = arguments;\n\t\t\t\t\treturn jQuery.Deferred(function( newDefer ) {\n\t\t\t\t\t\tjQuery.each( tuples, function( i, tuple ) {\n\t\t\t\t\t\t\tvar fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];\n\t\t\t\t\t\t\t// deferred[ done | fail | progress ] for forwarding actions to newDefer\n\t\t\t\t\t\t\tdeferred[ tuple[1] ](function() {\n\t\t\t\t\t\t\t\tvar returned = fn && fn.apply( this, arguments );\n\t\t\t\t\t\t\t\tif ( returned && jQuery.isFunction( returned.promise ) ) {\n\t\t\t\t\t\t\t\t\treturned.promise()\n\t\t\t\t\t\t\t\t\t\t.done( newDefer.resolve )\n\t\t\t\t\t\t\t\t\t\t.fail( newDefer.reject )\n\t\t\t\t\t\t\t\t\t\t.progress( newDefer.notify );\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tnewDefer[ tuple[ 0 ] + \"With\" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfns = null;\n\t\t\t\t\t}).promise();\n\t\t\t\t},\n\t\t\t\t// Get a promise for this deferred\n\t\t\t\t// If obj is provided, the promise aspect is added to the object\n\t\t\t\tpromise: function( obj ) {\n\t\t\t\t\treturn obj != null ? jQuery.extend( obj, promise ) : promise;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdeferred = {};\n\n\t\t// Keep pipe for back-compat\n\t\tpromise.pipe = promise.then;\n\n\t\t// Add list-specific methods\n\t\tjQuery.each( tuples, function( i, tuple ) {\n\t\t\tvar list = tuple[ 2 ],\n\t\t\t\tstateString = tuple[ 3 ];\n\n\t\t\t// promise[ done | fail | progress ] = list.add\n\t\t\tpromise[ tuple[1] ] = list.add;\n\n\t\t\t// Handle state\n\t\t\tif ( stateString ) {\n\t\t\t\tlist.add(function() {\n\t\t\t\t\t// state = [ resolved | rejected ]\n\t\t\t\t\tstate = stateString;\n\n\t\t\t\t// [ reject_list | resolve_list ].disable; progress_list.lock\n\t\t\t\t}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );\n\t\t\t}\n\n\t\t\t// deferred[ resolve | reject | notify ]\n\t\t\tdeferred[ tuple[0] ] = function() {\n\t\t\t\tdeferred[ tuple[0] + \"With\" ]( this === deferred ? promise : this, arguments );\n\t\t\t\treturn this;\n\t\t\t};\n\t\t\tdeferred[ tuple[0] + \"With\" ] = list.fireWith;\n\t\t});\n\n\t\t// Make the deferred a promise\n\t\tpromise.promise( deferred );\n\n\t\t// Call given func if any\n\t\tif ( func ) {\n\t\t\tfunc.call( deferred, deferred );\n\t\t}\n\n\t\t// All done!\n\t\treturn deferred;\n\t},\n\n\t// Deferred helper\n\twhen: function( subordinate /* , ..., subordinateN */ ) {\n\t\tvar i = 0,\n\t\t\tresolveValues = slice.call( arguments ),\n\t\t\tlength = resolveValues.length,\n\n\t\t\t// the count of uncompleted subordinates\n\t\t\tremaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,\n\n\t\t\t// the master Deferred. If resolveValues consist of only a single Deferred, just use that.\n\t\t\tdeferred = remaining === 1 ? subordinate : jQuery.Deferred(),\n\n\t\t\t// Update function for both resolve and progress values\n\t\t\tupdateFunc = function( i, contexts, values ) {\n\t\t\t\treturn function( value ) {\n\t\t\t\t\tcontexts[ i ] = this;\n\t\t\t\t\tvalues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;\n\t\t\t\t\tif ( values === progressValues ) {\n\t\t\t\t\t\tdeferred.notifyWith( contexts, values );\n\t\t\t\t\t} else if ( !( --remaining ) ) {\n\t\t\t\t\t\tdeferred.resolveWith( contexts, values );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\n\t\t\tprogressValues, progressContexts, resolveContexts;\n\n\t\t// add listeners to Deferred subordinates; treat others as resolved\n\t\tif ( length > 1 ) {\n\t\t\tprogressValues = new Array( length );\n\t\t\tprogressContexts = new Array( length );\n\t\t\tresolveContexts = new Array( length );\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tif ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {\n\t\t\t\t\tresolveValues[ i ].promise()\n\t\t\t\t\t\t.done( updateFunc( i, resolveContexts, resolveValues ) )\n\t\t\t\t\t\t.fail( deferred.reject )\n\t\t\t\t\t\t.progress( updateFunc( i, progressContexts, progressValues ) );\n\t\t\t\t} else {\n\t\t\t\t\t--remaining;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// if we're not waiting on anything, resolve the master\n\t\tif ( !remaining ) {\n\t\t\tdeferred.resolveWith( resolveContexts, resolveValues );\n\t\t}\n\n\t\treturn deferred.promise();\n\t}\n});\n\n\n// The deferred used on DOM ready\nvar readyList;\n\njQuery.fn.ready = function( fn ) {\n\t// Add the callback\n\tjQuery.ready.promise().done( fn );\n\n\treturn this;\n};\n\njQuery.extend({\n\t// Is the DOM ready to be used? Set to true once it occurs.\n\tisReady: false,\n\n\t// A counter to track how many items to wait for before\n\t// the ready event fires. See #6781\n\treadyWait: 1,\n\n\t// Hold (or release) the ready event\n\tholdReady: function( hold ) {\n\t\tif ( hold ) {\n\t\t\tjQuery.readyWait++;\n\t\t} else {\n\t\t\tjQuery.ready( true );\n\t\t}\n\t},\n\n\t// Handle when the DOM is ready\n\tready: function( wait ) {\n\n\t\t// Abort if there are pending holds or we're already ready\n\t\tif ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Remember that the DOM is ready\n\t\tjQuery.isReady = true;\n\n\t\t// If a normal DOM Ready event fired, decrement, and wait if need be\n\t\tif ( wait !== true && --jQuery.readyWait > 0 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If there are functions bound, to execute\n\t\treadyList.resolveWith( document, [ jQuery ] );\n\n\t\t// Trigger any bound ready events\n\t\tif ( jQuery.fn.trigger ) {\n\t\t\tjQuery( document ).trigger(\"ready\").off(\"ready\");\n\t\t}\n\t}\n});\n\n/**\n * The ready event handler and self cleanup method\n */\nfunction completed() {\n\tdocument.removeEventListener( \"DOMContentLoaded\", completed, false );\n\twindow.removeEventListener( \"load\", completed, false );\n\tjQuery.ready();\n}\n\njQuery.ready.promise = function( obj ) {\n\tif ( !readyList ) {\n\n\t\treadyList = jQuery.Deferred();\n\n\t\t// Catch cases where $(document).ready() is called after the browser event has already occurred.\n\t\t// we once tried to use readyState \"interactive\" here, but it caused issues like the one\n\t\t// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15\n\t\tif ( document.readyState === \"complete\" ) {\n\t\t\t// Handle it asynchronously to allow scripts the opportunity to delay ready\n\t\t\tsetTimeout( jQuery.ready );\n\n\t\t} else {\n\n\t\t\t// Use the handy event callback\n\t\t\tdocument.addEventListener( \"DOMContentLoaded\", completed, false );\n\n\t\t\t// A fallback to window.onload, that will always work\n\t\t\twindow.addEventListener( \"load\", completed, false );\n\t\t}\n\t}\n\treturn readyList.promise( obj );\n};\n\n// Kick off the DOM ready check even if the user does not\njQuery.ready.promise();\n\n\n\n\n// Multifunctional method to get and set values of a collection\n// The value/s can optionally be executed if it's a function\nvar access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {\n\tvar i = 0,\n\t\tlen = elems.length,\n\t\tbulk = key == null;\n\n\t// Sets many values\n\tif ( jQuery.type( key ) === \"object\" ) {\n\t\tchainable = true;\n\t\tfor ( i in key ) {\n\t\t\tjQuery.access( elems, fn, i, key[i], true, emptyGet, raw );\n\t\t}\n\n\t// Sets one value\n\t} else if ( value !== undefined ) {\n\t\tchainable = true;\n\n\t\tif ( !jQuery.isFunction( value ) ) {\n\t\t\traw = true;\n\t\t}\n\n\t\tif ( bulk ) {\n\t\t\t// Bulk operations run against the entire set\n\t\t\tif ( raw ) {\n\t\t\t\tfn.call( elems, value );\n\t\t\t\tfn = null;\n\n\t\t\t// ...except when executing function values\n\t\t\t} else {\n\t\t\t\tbulk = fn;\n\t\t\t\tfn = function( elem, key, value ) {\n\t\t\t\t\treturn bulk.call( jQuery( elem ), value );\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tif ( fn ) {\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\tfn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn chainable ?\n\t\telems :\n\n\t\t// Gets\n\t\tbulk ?\n\t\t\tfn.call( elems ) :\n\t\t\tlen ? fn( elems[0], key ) : emptyGet;\n};\n\n\n/**\n * Determines whether an object can have data\n */\njQuery.acceptData = function( owner ) {\n\t// Accepts only:\n\t//  - Node\n\t//    - Node.ELEMENT_NODE\n\t//    - Node.DOCUMENT_NODE\n\t//  - Object\n\t//    - Any\n\t/* jshint -W018 */\n\treturn owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );\n};\n\n\nfunction Data() {\n\t// Support: Android < 4,\n\t// Old WebKit does not have Object.preventExtensions/freeze method,\n\t// return new empty object instead with no [[set]] accessor\n\tObject.defineProperty( this.cache = {}, 0, {\n\t\tget: function() {\n\t\t\treturn {};\n\t\t}\n\t});\n\n\tthis.expando = jQuery.expando + Math.random();\n}\n\nData.uid = 1;\nData.accepts = jQuery.acceptData;\n\nData.prototype = {\n\tkey: function( owner ) {\n\t\t// We can accept data for non-element nodes in modern browsers,\n\t\t// but we should not, see #8335.\n\t\t// Always return the key for a frozen object.\n\t\tif ( !Data.accepts( owner ) ) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tvar descriptor = {},\n\t\t\t// Check if the owner object already has a cache key\n\t\t\tunlock = owner[ this.expando ];\n\n\t\t// If not, create one\n\t\tif ( !unlock ) {\n\t\t\tunlock = Data.uid++;\n\n\t\t\t// Secure it in a non-enumerable, non-writable property\n\t\t\ttry {\n\t\t\t\tdescriptor[ this.expando ] = { value: unlock };\n\t\t\t\tObject.defineProperties( owner, descriptor );\n\n\t\t\t// Support: Android < 4\n\t\t\t// Fallback to a less secure definition\n\t\t\t} catch ( e ) {\n\t\t\t\tdescriptor[ this.expando ] = unlock;\n\t\t\t\tjQuery.extend( owner, descriptor );\n\t\t\t}\n\t\t}\n\n\t\t// Ensure the cache object\n\t\tif ( !this.cache[ unlock ] ) {\n\t\t\tthis.cache[ unlock ] = {};\n\t\t}\n\n\t\treturn unlock;\n\t},\n\tset: function( owner, data, value ) {\n\t\tvar prop,\n\t\t\t// There may be an unlock assigned to this node,\n\t\t\t// if there is no entry for this \"owner\", create one inline\n\t\t\t// and set the unlock as though an owner entry had always existed\n\t\t\tunlock = this.key( owner ),\n\t\t\tcache = this.cache[ unlock ];\n\n\t\t// Handle: [ owner, key, value ] args\n\t\tif ( typeof data === \"string\" ) {\n\t\t\tcache[ data ] = value;\n\n\t\t// Handle: [ owner, { properties } ] args\n\t\t} else {\n\t\t\t// Fresh assignments by object are shallow copied\n\t\t\tif ( jQuery.isEmptyObject( cache ) ) {\n\t\t\t\tjQuery.extend( this.cache[ unlock ], data );\n\t\t\t// Otherwise, copy the properties one-by-one to the cache object\n\t\t\t} else {\n\t\t\t\tfor ( prop in data ) {\n\t\t\t\t\tcache[ prop ] = data[ prop ];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn cache;\n\t},\n\tget: function( owner, key ) {\n\t\t// Either a valid cache is found, or will be created.\n\t\t// New caches will be created and the unlock returned,\n\t\t// allowing direct access to the newly created\n\t\t// empty data object. A valid owner object must be provided.\n\t\tvar cache = this.cache[ this.key( owner ) ];\n\n\t\treturn key === undefined ?\n\t\t\tcache : cache[ key ];\n\t},\n\taccess: function( owner, key, value ) {\n\t\tvar stored;\n\t\t// In cases where either:\n\t\t//\n\t\t//   1. No key was specified\n\t\t//   2. A string key was specified, but no value provided\n\t\t//\n\t\t// Take the \"read\" path and allow the get method to determine\n\t\t// which value to return, respectively either:\n\t\t//\n\t\t//   1. The entire cache object\n\t\t//   2. The data stored at the key\n\t\t//\n\t\tif ( key === undefined ||\n\t\t\t\t((key && typeof key === \"string\") && value === undefined) ) {\n\n\t\t\tstored = this.get( owner, key );\n\n\t\t\treturn stored !== undefined ?\n\t\t\t\tstored : this.get( owner, jQuery.camelCase(key) );\n\t\t}\n\n\t\t// [*]When the key is not a string, or both a key and value\n\t\t// are specified, set or extend (existing objects) with either:\n\t\t//\n\t\t//   1. An object of properties\n\t\t//   2. A key and value\n\t\t//\n\t\tthis.set( owner, key, value );\n\n\t\t// Since the \"set\" path can have two possible entry points\n\t\t// return the expected data based on which path was taken[*]\n\t\treturn value !== undefined ? value : key;\n\t},\n\tremove: function( owner, key ) {\n\t\tvar i, name, camel,\n\t\t\tunlock = this.key( owner ),\n\t\t\tcache = this.cache[ unlock ];\n\n\t\tif ( key === undefined ) {\n\t\t\tthis.cache[ unlock ] = {};\n\n\t\t} else {\n\t\t\t// Support array or space separated string of keys\n\t\t\tif ( jQuery.isArray( key ) ) {\n\t\t\t\t// If \"name\" is an array of keys...\n\t\t\t\t// When data is initially created, via (\"key\", \"val\") signature,\n\t\t\t\t// keys will be converted to camelCase.\n\t\t\t\t// Since there is no way to tell _how_ a key was added, remove\n\t\t\t\t// both plain key and camelCase key. #12786\n\t\t\t\t// This will only penalize the array argument path.\n\t\t\t\tname = key.concat( key.map( jQuery.camelCase ) );\n\t\t\t} else {\n\t\t\t\tcamel = jQuery.camelCase( key );\n\t\t\t\t// Try the string as a key before any manipulation\n\t\t\t\tif ( key in cache ) {\n\t\t\t\t\tname = [ key, camel ];\n\t\t\t\t} else {\n\t\t\t\t\t// If a key with the spaces exists, use it.\n\t\t\t\t\t// Otherwise, create an array by matching non-whitespace\n\t\t\t\t\tname = camel;\n\t\t\t\t\tname = name in cache ?\n\t\t\t\t\t\t[ name ] : ( name.match( rnotwhite ) || [] );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ti = name.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tdelete cache[ name[ i ] ];\n\t\t\t}\n\t\t}\n\t},\n\thasData: function( owner ) {\n\t\treturn !jQuery.isEmptyObject(\n\t\t\tthis.cache[ owner[ this.expando ] ] || {}\n\t\t);\n\t},\n\tdiscard: function( owner ) {\n\t\tif ( owner[ this.expando ] ) {\n\t\t\tdelete this.cache[ owner[ this.expando ] ];\n\t\t}\n\t}\n};\nvar data_priv = new Data();\n\nvar data_user = new Data();\n\n\n\n/*\n\tImplementation Summary\n\n\t1. Enforce API surface and semantic compatibility with 1.9.x branch\n\t2. Improve the module's maintainability by reducing the storage\n\t\tpaths to a single mechanism.\n\t3. Use the same single mechanism to support \"private\" and \"user\" data.\n\t4. _Never_ expose \"private\" data to user code (TODO: Drop _data, _removeData)\n\t5. Avoid exposing implementation details on user objects (eg. expando properties)\n\t6. Provide a clear path for implementation upgrade to WeakMap in 2014\n*/\nvar rbrace = /^(?:\\{[\\w\\W]*\\}|\\[[\\w\\W]*\\])$/,\n\trmultiDash = /([A-Z])/g;\n\nfunction dataAttr( elem, key, data ) {\n\tvar name;\n\n\t// If nothing was found internally, try to fetch any\n\t// data from the HTML5 data-* attribute\n\tif ( data === undefined && elem.nodeType === 1 ) {\n\t\tname = \"data-\" + key.replace( rmultiDash, \"-$1\" ).toLowerCase();\n\t\tdata = elem.getAttribute( name );\n\n\t\tif ( typeof data === \"string\" ) {\n\t\t\ttry {\n\t\t\t\tdata = data === \"true\" ? true :\n\t\t\t\t\tdata === \"false\" ? false :\n\t\t\t\t\tdata === \"null\" ? null :\n\t\t\t\t\t// Only convert to a number if it doesn't change the string\n\t\t\t\t\t+data + \"\" === data ? +data :\n\t\t\t\t\trbrace.test( data ) ? jQuery.parseJSON( data ) :\n\t\t\t\t\tdata;\n\t\t\t} catch( e ) {}\n\n\t\t\t// Make sure we set the data so it isn't changed later\n\t\t\tdata_user.set( elem, key, data );\n\t\t} else {\n\t\t\tdata = undefined;\n\t\t}\n\t}\n\treturn data;\n}\n\njQuery.extend({\n\thasData: function( elem ) {\n\t\treturn data_user.hasData( elem ) || data_priv.hasData( elem );\n\t},\n\n\tdata: function( elem, name, data ) {\n\t\treturn data_user.access( elem, name, data );\n\t},\n\n\tremoveData: function( elem, name ) {\n\t\tdata_user.remove( elem, name );\n\t},\n\n\t// TODO: Now that all calls to _data and _removeData have been replaced\n\t// with direct calls to data_priv methods, these can be deprecated.\n\t_data: function( elem, name, data ) {\n\t\treturn data_priv.access( elem, name, data );\n\t},\n\n\t_removeData: function( elem, name ) {\n\t\tdata_priv.remove( elem, name );\n\t}\n});\n\njQuery.fn.extend({\n\tdata: function( key, value ) {\n\t\tvar i, name, data,\n\t\t\telem = this[ 0 ],\n\t\t\tattrs = elem && elem.attributes;\n\n\t\t// Gets all values\n\t\tif ( key === undefined ) {\n\t\t\tif ( this.length ) {\n\t\t\t\tdata = data_user.get( elem );\n\n\t\t\t\tif ( elem.nodeType === 1 && !data_priv.get( elem, \"hasDataAttrs\" ) ) {\n\t\t\t\t\ti = attrs.length;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tname = attrs[ i ].name;\n\n\t\t\t\t\t\tif ( name.indexOf( \"data-\" ) === 0 ) {\n\t\t\t\t\t\t\tname = jQuery.camelCase( name.slice(5) );\n\t\t\t\t\t\t\tdataAttr( elem, name, data[ name ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tdata_priv.set( elem, \"hasDataAttrs\", true );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn data;\n\t\t}\n\n\t\t// Sets multiple values\n\t\tif ( typeof key === \"object\" ) {\n\t\t\treturn this.each(function() {\n\t\t\t\tdata_user.set( this, key );\n\t\t\t});\n\t\t}\n\n\t\treturn access( this, function( value ) {\n\t\t\tvar data,\n\t\t\t\tcamelKey = jQuery.camelCase( key );\n\n\t\t\t// The calling jQuery object (element matches) is not empty\n\t\t\t// (and therefore has an element appears at this[ 0 ]) and the\n\t\t\t// `value` parameter was not undefined. An empty jQuery object\n\t\t\t// will result in `undefined` for elem = this[ 0 ] which will\n\t\t\t// throw an exception if an attempt to read a data cache is made.\n\t\t\tif ( elem && value === undefined ) {\n\t\t\t\t// Attempt to get data from the cache\n\t\t\t\t// with the key as-is\n\t\t\t\tdata = data_user.get( elem, key );\n\t\t\t\tif ( data !== undefined ) {\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// Attempt to get data from the cache\n\t\t\t\t// with the key camelized\n\t\t\t\tdata = data_user.get( elem, camelKey );\n\t\t\t\tif ( data !== undefined ) {\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// Attempt to \"discover\" the data in\n\t\t\t\t// HTML5 custom data-* attrs\n\t\t\t\tdata = dataAttr( elem, camelKey, undefined );\n\t\t\t\tif ( data !== undefined ) {\n\t\t\t\t\treturn data;\n\t\t\t\t}\n\n\t\t\t\t// We tried really hard, but the data doesn't exist.\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Set the data...\n\t\t\tthis.each(function() {\n\t\t\t\t// First, attempt to store a copy or reference of any\n\t\t\t\t// data that might've been store with a camelCased key.\n\t\t\t\tvar data = data_user.get( this, camelKey );\n\n\t\t\t\t// For HTML5 data-* attribute interop, we have to\n\t\t\t\t// store property names with dashes in a camelCase form.\n\t\t\t\t// This might not apply to all properties...*\n\t\t\t\tdata_user.set( this, camelKey, value );\n\n\t\t\t\t// *... In the case of properties that might _actually_\n\t\t\t\t// have dashes, we need to also store a copy of that\n\t\t\t\t// unchanged property.\n\t\t\t\tif ( key.indexOf(\"-\") !== -1 && data !== undefined ) {\n\t\t\t\t\tdata_user.set( this, key, value );\n\t\t\t\t}\n\t\t\t});\n\t\t}, null, value, arguments.length > 1, null, true );\n\t},\n\n\tremoveData: function( key ) {\n\t\treturn this.each(function() {\n\t\t\tdata_user.remove( this, key );\n\t\t});\n\t}\n});\n\n\njQuery.extend({\n\tqueue: function( elem, type, data ) {\n\t\tvar queue;\n\n\t\tif ( elem ) {\n\t\t\ttype = ( type || \"fx\" ) + \"queue\";\n\t\t\tqueue = data_priv.get( elem, type );\n\n\t\t\t// Speed up dequeue by getting out quickly if this is just a lookup\n\t\t\tif ( data ) {\n\t\t\t\tif ( !queue || jQuery.isArray( data ) ) {\n\t\t\t\t\tqueue = data_priv.access( elem, type, jQuery.makeArray(data) );\n\t\t\t\t} else {\n\t\t\t\t\tqueue.push( data );\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn queue || [];\n\t\t}\n\t},\n\n\tdequeue: function( elem, type ) {\n\t\ttype = type || \"fx\";\n\n\t\tvar queue = jQuery.queue( elem, type ),\n\t\t\tstartLength = queue.length,\n\t\t\tfn = queue.shift(),\n\t\t\thooks = jQuery._queueHooks( elem, type ),\n\t\t\tnext = function() {\n\t\t\t\tjQuery.dequeue( elem, type );\n\t\t\t};\n\n\t\t// If the fx queue is dequeued, always remove the progress sentinel\n\t\tif ( fn === \"inprogress\" ) {\n\t\t\tfn = queue.shift();\n\t\t\tstartLength--;\n\t\t}\n\n\t\tif ( fn ) {\n\n\t\t\t// Add a progress sentinel to prevent the fx queue from being\n\t\t\t// automatically dequeued\n\t\t\tif ( type === \"fx\" ) {\n\t\t\t\tqueue.unshift( \"inprogress\" );\n\t\t\t}\n\n\t\t\t// clear up the last queue stop function\n\t\t\tdelete hooks.stop;\n\t\t\tfn.call( elem, next, hooks );\n\t\t}\n\n\t\tif ( !startLength && hooks ) {\n\t\t\thooks.empty.fire();\n\t\t}\n\t},\n\n\t// not intended for public consumption - generates a queueHooks object, or returns the current one\n\t_queueHooks: function( elem, type ) {\n\t\tvar key = type + \"queueHooks\";\n\t\treturn data_priv.get( elem, key ) || data_priv.access( elem, key, {\n\t\t\tempty: jQuery.Callbacks(\"once memory\").add(function() {\n\t\t\t\tdata_priv.remove( elem, [ type + \"queue\", key ] );\n\t\t\t})\n\t\t});\n\t}\n});\n\njQuery.fn.extend({\n\tqueue: function( type, data ) {\n\t\tvar setter = 2;\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tdata = type;\n\t\t\ttype = \"fx\";\n\t\t\tsetter--;\n\t\t}\n\n\t\tif ( arguments.length < setter ) {\n\t\t\treturn jQuery.queue( this[0], type );\n\t\t}\n\n\t\treturn data === undefined ?\n\t\t\tthis :\n\t\t\tthis.each(function() {\n\t\t\t\tvar queue = jQuery.queue( this, type, data );\n\n\t\t\t\t// ensure a hooks for this queue\n\t\t\t\tjQuery._queueHooks( this, type );\n\n\t\t\t\tif ( type === \"fx\" && queue[0] !== \"inprogress\" ) {\n\t\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t\t}\n\t\t\t});\n\t},\n\tdequeue: function( type ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.dequeue( this, type );\n\t\t});\n\t},\n\tclearQueue: function( type ) {\n\t\treturn this.queue( type || \"fx\", [] );\n\t},\n\t// Get a promise resolved when queues of a certain type\n\t// are emptied (fx is the type by default)\n\tpromise: function( type, obj ) {\n\t\tvar tmp,\n\t\t\tcount = 1,\n\t\t\tdefer = jQuery.Deferred(),\n\t\t\telements = this,\n\t\t\ti = this.length,\n\t\t\tresolve = function() {\n\t\t\t\tif ( !( --count ) ) {\n\t\t\t\t\tdefer.resolveWith( elements, [ elements ] );\n\t\t\t\t}\n\t\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tobj = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\ttype = type || \"fx\";\n\n\t\twhile ( i-- ) {\n\t\t\ttmp = data_priv.get( elements[ i ], type + \"queueHooks\" );\n\t\t\tif ( tmp && tmp.empty ) {\n\t\t\t\tcount++;\n\t\t\t\ttmp.empty.add( resolve );\n\t\t\t}\n\t\t}\n\t\tresolve();\n\t\treturn defer.promise( obj );\n\t}\n});\nvar pnum = (/[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/).source;\n\nvar cssExpand = [ \"Top\", \"Right\", \"Bottom\", \"Left\" ];\n\nvar isHidden = function( elem, el ) {\n\t\t// isHidden might be called from jQuery#filter function;\n\t\t// in that case, element will be second argument\n\t\telem = el || elem;\n\t\treturn jQuery.css( elem, \"display\" ) === \"none\" || !jQuery.contains( elem.ownerDocument, elem );\n\t};\n\nvar rcheckableType = (/^(?:checkbox|radio)$/i);\n\n\n\n(function() {\n\tvar fragment = document.createDocumentFragment(),\n\t\tdiv = fragment.appendChild( document.createElement( \"div\" ) );\n\n\t// #11217 - WebKit loses check when the name is after the checked attribute\n\tdiv.innerHTML = \"<input type='radio' checked='checked' name='t'/>\";\n\n\t// Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3\n\t// old WebKit doesn't clone checked state correctly in fragments\n\tsupport.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;\n\n\t// Make sure textarea (and checkbox) defaultValue is properly cloned\n\t// Support: IE9-IE11+\n\tdiv.innerHTML = \"<textarea>x</textarea>\";\n\tsupport.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;\n})();\nvar strundefined = typeof undefined;\n\n\n\nsupport.focusinBubbles = \"onfocusin\" in window;\n\n\nvar\n\trkeyEvent = /^key/,\n\trmouseEvent = /^(?:mouse|contextmenu)|click/,\n\trfocusMorph = /^(?:focusinfocus|focusoutblur)$/,\n\trtypenamespace = /^([^.]*)(?:\\.(.+)|)$/;\n\nfunction returnTrue() {\n\treturn true;\n}\n\nfunction returnFalse() {\n\treturn false;\n}\n\nfunction safeActiveElement() {\n\ttry {\n\t\treturn document.activeElement;\n\t} catch ( err ) { }\n}\n\n/*\n * Helper functions for managing events -- not part of the public interface.\n * Props to Dean Edwards' addEvent library for many of the ideas.\n */\njQuery.event = {\n\n\tglobal: {},\n\n\tadd: function( elem, types, handler, data, selector ) {\n\n\t\tvar handleObjIn, eventHandle, tmp,\n\t\t\tevents, t, handleObj,\n\t\t\tspecial, handlers, type, namespaces, origType,\n\t\t\telemData = data_priv.get( elem );\n\n\t\t// Don't attach events to noData or text/comment nodes (but allow plain objects)\n\t\tif ( !elemData ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Caller can pass in an object of custom data in lieu of the handler\n\t\tif ( handler.handler ) {\n\t\t\thandleObjIn = handler;\n\t\t\thandler = handleObjIn.handler;\n\t\t\tselector = handleObjIn.selector;\n\t\t}\n\n\t\t// Make sure that the handler has a unique ID, used to find/remove it later\n\t\tif ( !handler.guid ) {\n\t\t\thandler.guid = jQuery.guid++;\n\t\t}\n\n\t\t// Init the element's event structure and main handler, if this is the first\n\t\tif ( !(events = elemData.events) ) {\n\t\t\tevents = elemData.events = {};\n\t\t}\n\t\tif ( !(eventHandle = elemData.handle) ) {\n\t\t\teventHandle = elemData.handle = function( e ) {\n\t\t\t\t// Discard the second event of a jQuery.event.trigger() and\n\t\t\t\t// when an event is called after a page has unloaded\n\t\t\t\treturn typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?\n\t\t\t\t\tjQuery.event.dispatch.apply( elem, arguments ) : undefined;\n\t\t\t};\n\t\t}\n\n\t\t// Handle multiple events separated by a space\n\t\ttypes = ( types || \"\" ).match( rnotwhite ) || [ \"\" ];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[t] ) || [];\n\t\t\ttype = origType = tmp[1];\n\t\t\tnamespaces = ( tmp[2] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// There *must* be a type, no attaching namespace-only handlers\n\t\t\tif ( !type ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// If event changes its type, use the special event handlers for the changed type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// If selector defined, determine special event api type, otherwise given type\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\n\t\t\t// Update special based on newly reset type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// handleObj is passed to all event handlers\n\t\t\thandleObj = jQuery.extend({\n\t\t\t\ttype: type,\n\t\t\t\torigType: origType,\n\t\t\t\tdata: data,\n\t\t\t\thandler: handler,\n\t\t\t\tguid: handler.guid,\n\t\t\t\tselector: selector,\n\t\t\t\tneedsContext: selector && jQuery.expr.match.needsContext.test( selector ),\n\t\t\t\tnamespace: namespaces.join(\".\")\n\t\t\t}, handleObjIn );\n\n\t\t\t// Init the event handler queue if we're the first\n\t\t\tif ( !(handlers = events[ type ]) ) {\n\t\t\t\thandlers = events[ type ] = [];\n\t\t\t\thandlers.delegateCount = 0;\n\n\t\t\t\t// Only use addEventListener if the special events handler returns false\n\t\t\t\tif ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {\n\t\t\t\t\tif ( elem.addEventListener ) {\n\t\t\t\t\t\telem.addEventListener( type, eventHandle, false );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( special.add ) {\n\t\t\t\tspecial.add.call( elem, handleObj );\n\n\t\t\t\tif ( !handleObj.handler.guid ) {\n\t\t\t\t\thandleObj.handler.guid = handler.guid;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add to the element's handler list, delegates in front\n\t\t\tif ( selector ) {\n\t\t\t\thandlers.splice( handlers.delegateCount++, 0, handleObj );\n\t\t\t} else {\n\t\t\t\thandlers.push( handleObj );\n\t\t\t}\n\n\t\t\t// Keep track of which events have ever been used, for event optimization\n\t\t\tjQuery.event.global[ type ] = true;\n\t\t}\n\n\t},\n\n\t// Detach an event or set of events from an element\n\tremove: function( elem, types, handler, selector, mappedTypes ) {\n\n\t\tvar j, origCount, tmp,\n\t\t\tevents, t, handleObj,\n\t\t\tspecial, handlers, type, namespaces, origType,\n\t\t\telemData = data_priv.hasData( elem ) && data_priv.get( elem );\n\n\t\tif ( !elemData || !(events = elemData.events) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Once for each type.namespace in types; type may be omitted\n\t\ttypes = ( types || \"\" ).match( rnotwhite ) || [ \"\" ];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[t] ) || [];\n\t\t\ttype = origType = tmp[1];\n\t\t\tnamespaces = ( tmp[2] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// Unbind all events (on this namespace, if provided) for the element\n\t\t\tif ( !type ) {\n\t\t\t\tfor ( type in events ) {\n\t\t\t\t\tjQuery.event.remove( elem, type + types[ t ], handler, selector, true );\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\t\t\thandlers = events[ type ] || [];\n\t\t\ttmp = tmp[2] && new RegExp( \"(^|\\\\.)\" + namespaces.join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\" );\n\n\t\t\t// Remove matching events\n\t\t\torigCount = j = handlers.length;\n\t\t\twhile ( j-- ) {\n\t\t\t\thandleObj = handlers[ j ];\n\n\t\t\t\tif ( ( mappedTypes || origType === handleObj.origType ) &&\n\t\t\t\t\t( !handler || handler.guid === handleObj.guid ) &&\n\t\t\t\t\t( !tmp || tmp.test( handleObj.namespace ) ) &&\n\t\t\t\t\t( !selector || selector === handleObj.selector || selector === \"**\" && handleObj.selector ) ) {\n\t\t\t\t\thandlers.splice( j, 1 );\n\n\t\t\t\t\tif ( handleObj.selector ) {\n\t\t\t\t\t\thandlers.delegateCount--;\n\t\t\t\t\t}\n\t\t\t\t\tif ( special.remove ) {\n\t\t\t\t\t\tspecial.remove.call( elem, handleObj );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Remove generic event handler if we removed something and no more handlers exist\n\t\t\t// (avoids potential for endless recursion during removal of special event handlers)\n\t\t\tif ( origCount && !handlers.length ) {\n\t\t\t\tif ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {\n\t\t\t\t\tjQuery.removeEvent( elem, type, elemData.handle );\n\t\t\t\t}\n\n\t\t\t\tdelete events[ type ];\n\t\t\t}\n\t\t}\n\n\t\t// Remove the expando if it's no longer used\n\t\tif ( jQuery.isEmptyObject( events ) ) {\n\t\t\tdelete elemData.handle;\n\t\t\tdata_priv.remove( elem, \"events\" );\n\t\t}\n\t},\n\n\ttrigger: function( event, data, elem, onlyHandlers ) {\n\n\t\tvar i, cur, tmp, bubbleType, ontype, handle, special,\n\t\t\teventPath = [ elem || document ],\n\t\t\ttype = hasOwn.call( event, \"type\" ) ? event.type : event,\n\t\t\tnamespaces = hasOwn.call( event, \"namespace\" ) ? event.namespace.split(\".\") : [];\n\n\t\tcur = tmp = elem = elem || document;\n\n\t\t// Don't do events on text and comment nodes\n\t\tif ( elem.nodeType === 3 || elem.nodeType === 8 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// focus/blur morphs to focusin/out; ensure we're not firing them right now\n\t\tif ( rfocusMorph.test( type + jQuery.event.triggered ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( type.indexOf(\".\") >= 0 ) {\n\t\t\t// Namespaced trigger; create a regexp to match event type in handle()\n\t\t\tnamespaces = type.split(\".\");\n\t\t\ttype = namespaces.shift();\n\t\t\tnamespaces.sort();\n\t\t}\n\t\tontype = type.indexOf(\":\") < 0 && \"on\" + type;\n\n\t\t// Caller can pass in a jQuery.Event object, Object, or just an event type string\n\t\tevent = event[ jQuery.expando ] ?\n\t\t\tevent :\n\t\t\tnew jQuery.Event( type, typeof event === \"object\" && event );\n\n\t\t// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)\n\t\tevent.isTrigger = onlyHandlers ? 2 : 3;\n\t\tevent.namespace = namespaces.join(\".\");\n\t\tevent.namespace_re = event.namespace ?\n\t\t\tnew RegExp( \"(^|\\\\.)\" + namespaces.join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\" ) :\n\t\t\tnull;\n\n\t\t// Clean up the event in case it is being reused\n\t\tevent.result = undefined;\n\t\tif ( !event.target ) {\n\t\t\tevent.target = elem;\n\t\t}\n\n\t\t// Clone any incoming data and prepend the event, creating the handler arg list\n\t\tdata = data == null ?\n\t\t\t[ event ] :\n\t\t\tjQuery.makeArray( data, [ event ] );\n\n\t\t// Allow special events to draw outside the lines\n\t\tspecial = jQuery.event.special[ type ] || {};\n\t\tif ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine event propagation path in advance, per W3C events spec (#9951)\n\t\t// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)\n\t\tif ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {\n\n\t\t\tbubbleType = special.delegateType || type;\n\t\t\tif ( !rfocusMorph.test( bubbleType + type ) ) {\n\t\t\t\tcur = cur.parentNode;\n\t\t\t}\n\t\t\tfor ( ; cur; cur = cur.parentNode ) {\n\t\t\t\teventPath.push( cur );\n\t\t\t\ttmp = cur;\n\t\t\t}\n\n\t\t\t// Only add window if we got to document (e.g., not plain obj or detached DOM)\n\t\t\tif ( tmp === (elem.ownerDocument || document) ) {\n\t\t\t\teventPath.push( tmp.defaultView || tmp.parentWindow || window );\n\t\t\t}\n\t\t}\n\n\t\t// Fire handlers on the event path\n\t\ti = 0;\n\t\twhile ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {\n\n\t\t\tevent.type = i > 1 ?\n\t\t\t\tbubbleType :\n\t\t\t\tspecial.bindType || type;\n\n\t\t\t// jQuery handler\n\t\t\thandle = ( data_priv.get( cur, \"events\" ) || {} )[ event.type ] && data_priv.get( cur, \"handle\" );\n\t\t\tif ( handle ) {\n\t\t\t\thandle.apply( cur, data );\n\t\t\t}\n\n\t\t\t// Native handler\n\t\t\thandle = ontype && cur[ ontype ];\n\t\t\tif ( handle && handle.apply && jQuery.acceptData( cur ) ) {\n\t\t\t\tevent.result = handle.apply( cur, data );\n\t\t\t\tif ( event.result === false ) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tevent.type = type;\n\n\t\t// If nobody prevented the default action, do it now\n\t\tif ( !onlyHandlers && !event.isDefaultPrevented() ) {\n\n\t\t\tif ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&\n\t\t\t\tjQuery.acceptData( elem ) ) {\n\n\t\t\t\t// Call a native DOM method on the target with the same name name as the event.\n\t\t\t\t// Don't do default actions on window, that's where global variables be (#6170)\n\t\t\t\tif ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {\n\n\t\t\t\t\t// Don't re-trigger an onFOO event when we call its FOO() method\n\t\t\t\t\ttmp = elem[ ontype ];\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = null;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Prevent re-triggering of the same event, since we already bubbled it above\n\t\t\t\t\tjQuery.event.triggered = type;\n\t\t\t\t\telem[ type ]();\n\t\t\t\t\tjQuery.event.triggered = undefined;\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = tmp;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\tdispatch: function( event ) {\n\n\t\t// Make a writable jQuery.Event from the native event object\n\t\tevent = jQuery.event.fix( event );\n\n\t\tvar i, j, ret, matched, handleObj,\n\t\t\thandlerQueue = [],\n\t\t\targs = slice.call( arguments ),\n\t\t\thandlers = ( data_priv.get( this, \"events\" ) || {} )[ event.type ] || [],\n\t\t\tspecial = jQuery.event.special[ event.type ] || {};\n\n\t\t// Use the fix-ed jQuery.Event rather than the (read-only) native event\n\t\targs[0] = event;\n\t\tevent.delegateTarget = this;\n\n\t\t// Call the preDispatch hook for the mapped type, and let it bail if desired\n\t\tif ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine handlers\n\t\thandlerQueue = jQuery.event.handlers.call( this, event, handlers );\n\n\t\t// Run delegates first; they may want to stop propagation beneath us\n\t\ti = 0;\n\t\twhile ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {\n\t\t\tevent.currentTarget = matched.elem;\n\n\t\t\tj = 0;\n\t\t\twhile ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {\n\n\t\t\t\t// Triggered event must either 1) have no namespace, or\n\t\t\t\t// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).\n\t\t\t\tif ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {\n\n\t\t\t\t\tevent.handleObj = handleObj;\n\t\t\t\t\tevent.data = handleObj.data;\n\n\t\t\t\t\tret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )\n\t\t\t\t\t\t\t.apply( matched.elem, args );\n\n\t\t\t\t\tif ( ret !== undefined ) {\n\t\t\t\t\t\tif ( (event.result = ret) === false ) {\n\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Call the postDispatch hook for the mapped type\n\t\tif ( special.postDispatch ) {\n\t\t\tspecial.postDispatch.call( this, event );\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\thandlers: function( event, handlers ) {\n\t\tvar i, matches, sel, handleObj,\n\t\t\thandlerQueue = [],\n\t\t\tdelegateCount = handlers.delegateCount,\n\t\t\tcur = event.target;\n\n\t\t// Find delegate handlers\n\t\t// Black-hole SVG <use> instance trees (#13180)\n\t\t// Avoid non-left-click bubbling in Firefox (#3861)\n\t\tif ( delegateCount && cur.nodeType && (!event.button || event.type !== \"click\") ) {\n\n\t\t\tfor ( ; cur !== this; cur = cur.parentNode || this ) {\n\n\t\t\t\t// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)\n\t\t\t\tif ( cur.disabled !== true || event.type !== \"click\" ) {\n\t\t\t\t\tmatches = [];\n\t\t\t\t\tfor ( i = 0; i < delegateCount; i++ ) {\n\t\t\t\t\t\thandleObj = handlers[ i ];\n\n\t\t\t\t\t\t// Don't conflict with Object.prototype properties (#13203)\n\t\t\t\t\t\tsel = handleObj.selector + \" \";\n\n\t\t\t\t\t\tif ( matches[ sel ] === undefined ) {\n\t\t\t\t\t\t\tmatches[ sel ] = handleObj.needsContext ?\n\t\t\t\t\t\t\t\tjQuery( sel, this ).index( cur ) >= 0 :\n\t\t\t\t\t\t\t\tjQuery.find( sel, this, null, [ cur ] ).length;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( matches[ sel ] ) {\n\t\t\t\t\t\t\tmatches.push( handleObj );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( matches.length ) {\n\t\t\t\t\t\thandlerQueue.push({ elem: cur, handlers: matches });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Add the remaining (directly-bound) handlers\n\t\tif ( delegateCount < handlers.length ) {\n\t\t\thandlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });\n\t\t}\n\n\t\treturn handlerQueue;\n\t},\n\n\t// Includes some event props shared by KeyEvent and MouseEvent\n\tprops: \"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which\".split(\" \"),\n\n\tfixHooks: {},\n\n\tkeyHooks: {\n\t\tprops: \"char charCode key keyCode\".split(\" \"),\n\t\tfilter: function( event, original ) {\n\n\t\t\t// Add which for key events\n\t\t\tif ( event.which == null ) {\n\t\t\t\tevent.which = original.charCode != null ? original.charCode : original.keyCode;\n\t\t\t}\n\n\t\t\treturn event;\n\t\t}\n\t},\n\n\tmouseHooks: {\n\t\tprops: \"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement\".split(\" \"),\n\t\tfilter: function( event, original ) {\n\t\t\tvar eventDoc, doc, body,\n\t\t\t\tbutton = original.button;\n\n\t\t\t// Calculate pageX/Y if missing and clientX/Y available\n\t\t\tif ( event.pageX == null && original.clientX != null ) {\n\t\t\t\teventDoc = event.target.ownerDocument || document;\n\t\t\t\tdoc = eventDoc.documentElement;\n\t\t\t\tbody = eventDoc.body;\n\n\t\t\t\tevent.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );\n\t\t\t\tevent.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );\n\t\t\t}\n\n\t\t\t// Add which for click: 1 === left; 2 === middle; 3 === right\n\t\t\t// Note: button is not normalized, so don't use it\n\t\t\tif ( !event.which && button !== undefined ) {\n\t\t\t\tevent.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );\n\t\t\t}\n\n\t\t\treturn event;\n\t\t}\n\t},\n\n\tfix: function( event ) {\n\t\tif ( event[ jQuery.expando ] ) {\n\t\t\treturn event;\n\t\t}\n\n\t\t// Create a writable copy of the event object and normalize some properties\n\t\tvar i, prop, copy,\n\t\t\ttype = event.type,\n\t\t\toriginalEvent = event,\n\t\t\tfixHook = this.fixHooks[ type ];\n\n\t\tif ( !fixHook ) {\n\t\t\tthis.fixHooks[ type ] = fixHook =\n\t\t\t\trmouseEvent.test( type ) ? this.mouseHooks :\n\t\t\t\trkeyEvent.test( type ) ? this.keyHooks :\n\t\t\t\t{};\n\t\t}\n\t\tcopy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;\n\n\t\tevent = new jQuery.Event( originalEvent );\n\n\t\ti = copy.length;\n\t\twhile ( i-- ) {\n\t\t\tprop = copy[ i ];\n\t\t\tevent[ prop ] = originalEvent[ prop ];\n\t\t}\n\n\t\t// Support: Cordova 2.5 (WebKit) (#13255)\n\t\t// All events should have a target; Cordova deviceready doesn't\n\t\tif ( !event.target ) {\n\t\t\tevent.target = document;\n\t\t}\n\n\t\t// Support: Safari 6.0+, Chrome < 28\n\t\t// Target should not be a text node (#504, #13143)\n\t\tif ( event.target.nodeType === 3 ) {\n\t\t\tevent.target = event.target.parentNode;\n\t\t}\n\n\t\treturn fixHook.filter ? fixHook.filter( event, originalEvent ) : event;\n\t},\n\n\tspecial: {\n\t\tload: {\n\t\t\t// Prevent triggered image.load events from bubbling to window.load\n\t\t\tnoBubble: true\n\t\t},\n\t\tfocus: {\n\t\t\t// Fire native event if possible so blur/focus sequence is correct\n\t\t\ttrigger: function() {\n\t\t\t\tif ( this !== safeActiveElement() && this.focus ) {\n\t\t\t\t\tthis.focus();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdelegateType: \"focusin\"\n\t\t},\n\t\tblur: {\n\t\t\ttrigger: function() {\n\t\t\t\tif ( this === safeActiveElement() && this.blur ) {\n\t\t\t\t\tthis.blur();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdelegateType: \"focusout\"\n\t\t},\n\t\tclick: {\n\t\t\t// For checkbox, fire native event so checked state will be right\n\t\t\ttrigger: function() {\n\t\t\t\tif ( this.type === \"checkbox\" && this.click && jQuery.nodeName( this, \"input\" ) ) {\n\t\t\t\t\tthis.click();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t// For cross-browser consistency, don't fire native .click() on links\n\t\t\t_default: function( event ) {\n\t\t\t\treturn jQuery.nodeName( event.target, \"a\" );\n\t\t\t}\n\t\t},\n\n\t\tbeforeunload: {\n\t\t\tpostDispatch: function( event ) {\n\n\t\t\t\t// Support: Firefox 20+\n\t\t\t\t// Firefox doesn't alert if the returnValue field is not set.\n\t\t\t\tif ( event.result !== undefined ) {\n\t\t\t\t\tevent.originalEvent.returnValue = event.result;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tsimulate: function( type, elem, event, bubble ) {\n\t\t// Piggyback on a donor event to simulate a different one.\n\t\t// Fake originalEvent to avoid donor's stopPropagation, but if the\n\t\t// simulated event prevents default then we do the same on the donor.\n\t\tvar e = jQuery.extend(\n\t\t\tnew jQuery.Event(),\n\t\t\tevent,\n\t\t\t{\n\t\t\t\ttype: type,\n\t\t\t\tisSimulated: true,\n\t\t\t\toriginalEvent: {}\n\t\t\t}\n\t\t);\n\t\tif ( bubble ) {\n\t\t\tjQuery.event.trigger( e, null, elem );\n\t\t} else {\n\t\t\tjQuery.event.dispatch.call( elem, e );\n\t\t}\n\t\tif ( e.isDefaultPrevented() ) {\n\t\t\tevent.preventDefault();\n\t\t}\n\t}\n};\n\njQuery.removeEvent = function( elem, type, handle ) {\n\tif ( elem.removeEventListener ) {\n\t\telem.removeEventListener( type, handle, false );\n\t}\n};\n\njQuery.Event = function( src, props ) {\n\t// Allow instantiation without the 'new' keyword\n\tif ( !(this instanceof jQuery.Event) ) {\n\t\treturn new jQuery.Event( src, props );\n\t}\n\n\t// Event object\n\tif ( src && src.type ) {\n\t\tthis.originalEvent = src;\n\t\tthis.type = src.type;\n\n\t\t// Events bubbling up the document may have been marked as prevented\n\t\t// by a handler lower down the tree; reflect the correct value.\n\t\tthis.isDefaultPrevented = src.defaultPrevented ||\n\t\t\t\t// Support: Android < 4.0\n\t\t\t\tsrc.defaultPrevented === undefined &&\n\t\t\t\tsrc.getPreventDefault && src.getPreventDefault() ?\n\t\t\treturnTrue :\n\t\t\treturnFalse;\n\n\t// Event type\n\t} else {\n\t\tthis.type = src;\n\t}\n\n\t// Put explicitly provided properties onto the event object\n\tif ( props ) {\n\t\tjQuery.extend( this, props );\n\t}\n\n\t// Create a timestamp if incoming event doesn't have one\n\tthis.timeStamp = src && src.timeStamp || jQuery.now();\n\n\t// Mark it as fixed\n\tthis[ jQuery.expando ] = true;\n};\n\n// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding\n// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html\njQuery.Event.prototype = {\n\tisDefaultPrevented: returnFalse,\n\tisPropagationStopped: returnFalse,\n\tisImmediatePropagationStopped: returnFalse,\n\n\tpreventDefault: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isDefaultPrevented = returnTrue;\n\n\t\tif ( e && e.preventDefault ) {\n\t\t\te.preventDefault();\n\t\t}\n\t},\n\tstopPropagation: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isPropagationStopped = returnTrue;\n\n\t\tif ( e && e.stopPropagation ) {\n\t\t\te.stopPropagation();\n\t\t}\n\t},\n\tstopImmediatePropagation: function() {\n\t\tthis.isImmediatePropagationStopped = returnTrue;\n\t\tthis.stopPropagation();\n\t}\n};\n\n// Create mouseenter/leave events using mouseover/out and event-time checks\n// Support: Chrome 15+\njQuery.each({\n\tmouseenter: \"mouseover\",\n\tmouseleave: \"mouseout\"\n}, function( orig, fix ) {\n\tjQuery.event.special[ orig ] = {\n\t\tdelegateType: fix,\n\t\tbindType: fix,\n\n\t\thandle: function( event ) {\n\t\t\tvar ret,\n\t\t\t\ttarget = this,\n\t\t\t\trelated = event.relatedTarget,\n\t\t\t\thandleObj = event.handleObj;\n\n\t\t\t// For mousenter/leave call the handler if related is outside the target.\n\t\t\t// NB: No relatedTarget if the mouse left/entered the browser window\n\t\t\tif ( !related || (related !== target && !jQuery.contains( target, related )) ) {\n\t\t\t\tevent.type = handleObj.origType;\n\t\t\t\tret = handleObj.handler.apply( this, arguments );\n\t\t\t\tevent.type = fix;\n\t\t\t}\n\t\t\treturn ret;\n\t\t}\n\t};\n});\n\n// Create \"bubbling\" focus and blur events\n// Support: Firefox, Chrome, Safari\nif ( !support.focusinBubbles ) {\n\tjQuery.each({ focus: \"focusin\", blur: \"focusout\" }, function( orig, fix ) {\n\n\t\t// Attach a single capturing handler on the document while someone wants focusin/focusout\n\t\tvar handler = function( event ) {\n\t\t\t\tjQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );\n\t\t\t};\n\n\t\tjQuery.event.special[ fix ] = {\n\t\t\tsetup: function() {\n\t\t\t\tvar doc = this.ownerDocument || this,\n\t\t\t\t\tattaches = data_priv.access( doc, fix );\n\n\t\t\t\tif ( !attaches ) {\n\t\t\t\t\tdoc.addEventListener( orig, handler, true );\n\t\t\t\t}\n\t\t\t\tdata_priv.access( doc, fix, ( attaches || 0 ) + 1 );\n\t\t\t},\n\t\t\tteardown: function() {\n\t\t\t\tvar doc = this.ownerDocument || this,\n\t\t\t\t\tattaches = data_priv.access( doc, fix ) - 1;\n\n\t\t\t\tif ( !attaches ) {\n\t\t\t\t\tdoc.removeEventListener( orig, handler, true );\n\t\t\t\t\tdata_priv.remove( doc, fix );\n\n\t\t\t\t} else {\n\t\t\t\t\tdata_priv.access( doc, fix, attaches );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t});\n}\n\njQuery.fn.extend({\n\n\ton: function( types, selector, data, fn, /*INTERNAL*/ one ) {\n\t\tvar origFn, type;\n\n\t\t// Types can be a map of types/handlers\n\t\tif ( typeof types === \"object\" ) {\n\t\t\t// ( types-Object, selector, data )\n\t\t\tif ( typeof selector !== \"string\" ) {\n\t\t\t\t// ( types-Object, data )\n\t\t\t\tdata = data || selector;\n\t\t\t\tselector = undefined;\n\t\t\t}\n\t\t\tfor ( type in types ) {\n\t\t\t\tthis.on( type, selector, data, types[ type ], one );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\n\t\tif ( data == null && fn == null ) {\n\t\t\t// ( types, fn )\n\t\t\tfn = selector;\n\t\t\tdata = selector = undefined;\n\t\t} else if ( fn == null ) {\n\t\t\tif ( typeof selector === \"string\" ) {\n\t\t\t\t// ( types, selector, fn )\n\t\t\t\tfn = data;\n\t\t\t\tdata = undefined;\n\t\t\t} else {\n\t\t\t\t// ( types, data, fn )\n\t\t\t\tfn = data;\n\t\t\t\tdata = selector;\n\t\t\t\tselector = undefined;\n\t\t\t}\n\t\t}\n\t\tif ( fn === false ) {\n\t\t\tfn = returnFalse;\n\t\t} else if ( !fn ) {\n\t\t\treturn this;\n\t\t}\n\n\t\tif ( one === 1 ) {\n\t\t\torigFn = fn;\n\t\t\tfn = function( event ) {\n\t\t\t\t// Can use an empty set, since event contains the info\n\t\t\t\tjQuery().off( event );\n\t\t\t\treturn origFn.apply( this, arguments );\n\t\t\t};\n\t\t\t// Use same guid so caller can remove using origFn\n\t\t\tfn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );\n\t\t}\n\t\treturn this.each( function() {\n\t\t\tjQuery.event.add( this, types, fn, data, selector );\n\t\t});\n\t},\n\tone: function( types, selector, data, fn ) {\n\t\treturn this.on( types, selector, data, fn, 1 );\n\t},\n\toff: function( types, selector, fn ) {\n\t\tvar handleObj, type;\n\t\tif ( types && types.preventDefault && types.handleObj ) {\n\t\t\t// ( event )  dispatched jQuery.Event\n\t\t\thandleObj = types.handleObj;\n\t\t\tjQuery( types.delegateTarget ).off(\n\t\t\t\thandleObj.namespace ? handleObj.origType + \".\" + handleObj.namespace : handleObj.origType,\n\t\t\t\thandleObj.selector,\n\t\t\t\thandleObj.handler\n\t\t\t);\n\t\t\treturn this;\n\t\t}\n\t\tif ( typeof types === \"object\" ) {\n\t\t\t// ( types-object [, selector] )\n\t\t\tfor ( type in types ) {\n\t\t\t\tthis.off( type, selector, types[ type ] );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\t\tif ( selector === false || typeof selector === \"function\" ) {\n\t\t\t// ( types [, fn] )\n\t\t\tfn = selector;\n\t\t\tselector = undefined;\n\t\t}\n\t\tif ( fn === false ) {\n\t\t\tfn = returnFalse;\n\t\t}\n\t\treturn this.each(function() {\n\t\t\tjQuery.event.remove( this, types, fn, selector );\n\t\t});\n\t},\n\n\ttrigger: function( type, data ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.event.trigger( type, data, this );\n\t\t});\n\t},\n\ttriggerHandler: function( type, data ) {\n\t\tvar elem = this[0];\n\t\tif ( elem ) {\n\t\t\treturn jQuery.event.trigger( type, data, elem, true );\n\t\t}\n\t}\n});\n\n\nvar\n\trxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\\w:]+)[^>]*)\\/>/gi,\n\trtagName = /<([\\w:]+)/,\n\trhtml = /<|&#?\\w+;/,\n\trnoInnerhtml = /<(?:script|style|link)/i,\n\t// checked=\"checked\" or checked\n\trchecked = /checked\\s*(?:[^=]|=\\s*.checked.)/i,\n\trscriptType = /^$|\\/(?:java|ecma)script/i,\n\trscriptTypeMasked = /^true\\/(.*)/,\n\trcleanScript = /^\\s*<!(?:\\[CDATA\\[|--)|(?:\\]\\]|--)>\\s*$/g,\n\n\t// We have to close these tags to support XHTML (#13200)\n\twrapMap = {\n\n\t\t// Support: IE 9\n\t\toption: [ 1, \"<select multiple='multiple'>\", \"</select>\" ],\n\n\t\tthead: [ 1, \"<table>\", \"</table>\" ],\n\t\tcol: [ 2, \"<table><colgroup>\", \"</colgroup></table>\" ],\n\t\ttr: [ 2, \"<table><tbody>\", \"</tbody></table>\" ],\n\t\ttd: [ 3, \"<table><tbody><tr>\", \"</tr></tbody></table>\" ],\n\n\t\t_default: [ 0, \"\", \"\" ]\n\t};\n\n// Support: IE 9\nwrapMap.optgroup = wrapMap.option;\n\nwrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;\nwrapMap.th = wrapMap.td;\n\n// Support: 1.x compatibility\n// Manipulating tables requires a tbody\nfunction manipulationTarget( elem, content ) {\n\treturn jQuery.nodeName( elem, \"table\" ) &&\n\t\tjQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, \"tr\" ) ?\n\n\t\telem.getElementsByTagName(\"tbody\")[0] ||\n\t\t\telem.appendChild( elem.ownerDocument.createElement(\"tbody\") ) :\n\t\telem;\n}\n\n// Replace/restore the type attribute of script elements for safe DOM manipulation\nfunction disableScript( elem ) {\n\telem.type = (elem.getAttribute(\"type\") !== null) + \"/\" + elem.type;\n\treturn elem;\n}\nfunction restoreScript( elem ) {\n\tvar match = rscriptTypeMasked.exec( elem.type );\n\n\tif ( match ) {\n\t\telem.type = match[ 1 ];\n\t} else {\n\t\telem.removeAttribute(\"type\");\n\t}\n\n\treturn elem;\n}\n\n// Mark scripts as having already been evaluated\nfunction setGlobalEval( elems, refElements ) {\n\tvar i = 0,\n\t\tl = elems.length;\n\n\tfor ( ; i < l; i++ ) {\n\t\tdata_priv.set(\n\t\t\telems[ i ], \"globalEval\", !refElements || data_priv.get( refElements[ i ], \"globalEval\" )\n\t\t);\n\t}\n}\n\nfunction cloneCopyEvent( src, dest ) {\n\tvar i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;\n\n\tif ( dest.nodeType !== 1 ) {\n\t\treturn;\n\t}\n\n\t// 1. Copy private data: events, handlers, etc.\n\tif ( data_priv.hasData( src ) ) {\n\t\tpdataOld = data_priv.access( src );\n\t\tpdataCur = data_priv.set( dest, pdataOld );\n\t\tevents = pdataOld.events;\n\n\t\tif ( events ) {\n\t\t\tdelete pdataCur.handle;\n\t\t\tpdataCur.events = {};\n\n\t\t\tfor ( type in events ) {\n\t\t\t\tfor ( i = 0, l = events[ type ].length; i < l; i++ ) {\n\t\t\t\t\tjQuery.event.add( dest, type, events[ type ][ i ] );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// 2. Copy user data\n\tif ( data_user.hasData( src ) ) {\n\t\tudataOld = data_user.access( src );\n\t\tudataCur = jQuery.extend( {}, udataOld );\n\n\t\tdata_user.set( dest, udataCur );\n\t}\n}\n\nfunction getAll( context, tag ) {\n\tvar ret = context.getElementsByTagName ? context.getElementsByTagName( tag || \"*\" ) :\n\t\t\tcontext.querySelectorAll ? context.querySelectorAll( tag || \"*\" ) :\n\t\t\t[];\n\n\treturn tag === undefined || tag && jQuery.nodeName( context, tag ) ?\n\t\tjQuery.merge( [ context ], ret ) :\n\t\tret;\n}\n\n// Support: IE >= 9\nfunction fixInput( src, dest ) {\n\tvar nodeName = dest.nodeName.toLowerCase();\n\n\t// Fails to persist the checked state of a cloned checkbox or radio button.\n\tif ( nodeName === \"input\" && rcheckableType.test( src.type ) ) {\n\t\tdest.checked = src.checked;\n\n\t// Fails to return the selected option to the default selected state when cloning options\n\t} else if ( nodeName === \"input\" || nodeName === \"textarea\" ) {\n\t\tdest.defaultValue = src.defaultValue;\n\t}\n}\n\njQuery.extend({\n\tclone: function( elem, dataAndEvents, deepDataAndEvents ) {\n\t\tvar i, l, srcElements, destElements,\n\t\t\tclone = elem.cloneNode( true ),\n\t\t\tinPage = jQuery.contains( elem.ownerDocument, elem );\n\n\t\t// Support: IE >= 9\n\t\t// Fix Cloning issues\n\t\tif ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&\n\t\t\t\t!jQuery.isXMLDoc( elem ) ) {\n\n\t\t\t// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2\n\t\t\tdestElements = getAll( clone );\n\t\t\tsrcElements = getAll( elem );\n\n\t\t\tfor ( i = 0, l = srcElements.length; i < l; i++ ) {\n\t\t\t\tfixInput( srcElements[ i ], destElements[ i ] );\n\t\t\t}\n\t\t}\n\n\t\t// Copy the events from the original to the clone\n\t\tif ( dataAndEvents ) {\n\t\t\tif ( deepDataAndEvents ) {\n\t\t\t\tsrcElements = srcElements || getAll( elem );\n\t\t\t\tdestElements = destElements || getAll( clone );\n\n\t\t\t\tfor ( i = 0, l = srcElements.length; i < l; i++ ) {\n\t\t\t\t\tcloneCopyEvent( srcElements[ i ], destElements[ i ] );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcloneCopyEvent( elem, clone );\n\t\t\t}\n\t\t}\n\n\t\t// Preserve script evaluation history\n\t\tdestElements = getAll( clone, \"script\" );\n\t\tif ( destElements.length > 0 ) {\n\t\t\tsetGlobalEval( destElements, !inPage && getAll( elem, \"script\" ) );\n\t\t}\n\n\t\t// Return the cloned set\n\t\treturn clone;\n\t},\n\n\tbuildFragment: function( elems, context, scripts, selection ) {\n\t\tvar elem, tmp, tag, wrap, contains, j,\n\t\t\tfragment = context.createDocumentFragment(),\n\t\t\tnodes = [],\n\t\t\ti = 0,\n\t\t\tl = elems.length;\n\n\t\tfor ( ; i < l; i++ ) {\n\t\t\telem = elems[ i ];\n\n\t\t\tif ( elem || elem === 0 ) {\n\n\t\t\t\t// Add nodes directly\n\t\t\t\tif ( jQuery.type( elem ) === \"object\" ) {\n\t\t\t\t\t// Support: QtWebKit\n\t\t\t\t\t// jQuery.merge because push.apply(_, arraylike) throws\n\t\t\t\t\tjQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );\n\n\t\t\t\t// Convert non-html into a text node\n\t\t\t\t} else if ( !rhtml.test( elem ) ) {\n\t\t\t\t\tnodes.push( context.createTextNode( elem ) );\n\n\t\t\t\t// Convert html into DOM nodes\n\t\t\t\t} else {\n\t\t\t\t\ttmp = tmp || fragment.appendChild( context.createElement(\"div\") );\n\n\t\t\t\t\t// Deserialize a standard representation\n\t\t\t\t\ttag = ( rtagName.exec( elem ) || [ \"\", \"\" ] )[ 1 ].toLowerCase();\n\t\t\t\t\twrap = wrapMap[ tag ] || wrapMap._default;\n\t\t\t\t\ttmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, \"<$1></$2>\" ) + wrap[ 2 ];\n\n\t\t\t\t\t// Descend through wrappers to the right content\n\t\t\t\t\tj = wrap[ 0 ];\n\t\t\t\t\twhile ( j-- ) {\n\t\t\t\t\t\ttmp = tmp.lastChild;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Support: QtWebKit\n\t\t\t\t\t// jQuery.merge because push.apply(_, arraylike) throws\n\t\t\t\t\tjQuery.merge( nodes, tmp.childNodes );\n\n\t\t\t\t\t// Remember the top-level container\n\t\t\t\t\ttmp = fragment.firstChild;\n\n\t\t\t\t\t// Fixes #12346\n\t\t\t\t\t// Support: Webkit, IE\n\t\t\t\t\ttmp.textContent = \"\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Remove wrapper from fragment\n\t\tfragment.textContent = \"\";\n\n\t\ti = 0;\n\t\twhile ( (elem = nodes[ i++ ]) ) {\n\n\t\t\t// #4087 - If origin and destination elements are the same, and this is\n\t\t\t// that element, do not do anything\n\t\t\tif ( selection && jQuery.inArray( elem, selection ) !== -1 ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcontains = jQuery.contains( elem.ownerDocument, elem );\n\n\t\t\t// Append to fragment\n\t\t\ttmp = getAll( fragment.appendChild( elem ), \"script\" );\n\n\t\t\t// Preserve script evaluation history\n\t\t\tif ( contains ) {\n\t\t\t\tsetGlobalEval( tmp );\n\t\t\t}\n\n\t\t\t// Capture executables\n\t\t\tif ( scripts ) {\n\t\t\t\tj = 0;\n\t\t\t\twhile ( (elem = tmp[ j++ ]) ) {\n\t\t\t\t\tif ( rscriptType.test( elem.type || \"\" ) ) {\n\t\t\t\t\t\tscripts.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn fragment;\n\t},\n\n\tcleanData: function( elems ) {\n\t\tvar data, elem, events, type, key, j,\n\t\t\tspecial = jQuery.event.special,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = elems[ i ]) !== undefined; i++ ) {\n\t\t\tif ( jQuery.acceptData( elem ) ) {\n\t\t\t\tkey = elem[ data_priv.expando ];\n\n\t\t\t\tif ( key && (data = data_priv.cache[ key ]) ) {\n\t\t\t\t\tevents = Object.keys( data.events || {} );\n\t\t\t\t\tif ( events.length ) {\n\t\t\t\t\t\tfor ( j = 0; (type = events[j]) !== undefined; j++ ) {\n\t\t\t\t\t\t\tif ( special[ type ] ) {\n\t\t\t\t\t\t\t\tjQuery.event.remove( elem, type );\n\n\t\t\t\t\t\t\t// This is a shortcut to avoid jQuery.event.remove's overhead\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tjQuery.removeEvent( elem, type, data.handle );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( data_priv.cache[ key ] ) {\n\t\t\t\t\t\t// Discard any remaining `private` data\n\t\t\t\t\t\tdelete data_priv.cache[ key ];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Discard any remaining `user` data\n\t\t\tdelete data_user.cache[ elem[ data_user.expando ] ];\n\t\t}\n\t}\n});\n\njQuery.fn.extend({\n\ttext: function( value ) {\n\t\treturn access( this, function( value ) {\n\t\t\treturn value === undefined ?\n\t\t\t\tjQuery.text( this ) :\n\t\t\t\tthis.empty().each(function() {\n\t\t\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\t\t\tthis.textContent = value;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t}, null, value, arguments.length );\n\t},\n\n\tappend: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\tvar target = manipulationTarget( this, elem );\n\t\t\t\ttarget.appendChild( elem );\n\t\t\t}\n\t\t});\n\t},\n\n\tprepend: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\tvar target = manipulationTarget( this, elem );\n\t\t\t\ttarget.insertBefore( elem, target.firstChild );\n\t\t\t}\n\t\t});\n\t},\n\n\tbefore: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.parentNode ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this );\n\t\t\t}\n\t\t});\n\t},\n\n\tafter: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.parentNode ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this.nextSibling );\n\t\t\t}\n\t\t});\n\t},\n\n\tremove: function( selector, keepData /* Internal Use Only */ ) {\n\t\tvar elem,\n\t\t\telems = selector ? jQuery.filter( selector, this ) : this,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = elems[i]) != null; i++ ) {\n\t\t\tif ( !keepData && elem.nodeType === 1 ) {\n\t\t\t\tjQuery.cleanData( getAll( elem ) );\n\t\t\t}\n\n\t\t\tif ( elem.parentNode ) {\n\t\t\t\tif ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {\n\t\t\t\t\tsetGlobalEval( getAll( elem, \"script\" ) );\n\t\t\t\t}\n\t\t\t\telem.parentNode.removeChild( elem );\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tempty: function() {\n\t\tvar elem,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = this[i]) != null; i++ ) {\n\t\t\tif ( elem.nodeType === 1 ) {\n\n\t\t\t\t// Prevent memory leaks\n\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\n\n\t\t\t\t// Remove any remaining nodes\n\t\t\t\telem.textContent = \"\";\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tclone: function( dataAndEvents, deepDataAndEvents ) {\n\t\tdataAndEvents = dataAndEvents == null ? false : dataAndEvents;\n\t\tdeepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;\n\n\t\treturn this.map(function() {\n\t\t\treturn jQuery.clone( this, dataAndEvents, deepDataAndEvents );\n\t\t});\n\t},\n\n\thtml: function( value ) {\n\t\treturn access( this, function( value ) {\n\t\t\tvar elem = this[ 0 ] || {},\n\t\t\t\ti = 0,\n\t\t\t\tl = this.length;\n\n\t\t\tif ( value === undefined && elem.nodeType === 1 ) {\n\t\t\t\treturn elem.innerHTML;\n\t\t\t}\n\n\t\t\t// See if we can take a shortcut and just use innerHTML\n\t\t\tif ( typeof value === \"string\" && !rnoInnerhtml.test( value ) &&\n\t\t\t\t!wrapMap[ ( rtagName.exec( value ) || [ \"\", \"\" ] )[ 1 ].toLowerCase() ] ) {\n\n\t\t\t\tvalue = value.replace( rxhtmlTag, \"<$1></$2>\" );\n\n\t\t\t\ttry {\n\t\t\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\t\t\telem = this[ i ] || {};\n\n\t\t\t\t\t\t// Remove element nodes and prevent memory leaks\n\t\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\n\t\t\t\t\t\t\telem.innerHTML = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\telem = 0;\n\n\t\t\t\t// If using innerHTML throws an exception, use the fallback method\n\t\t\t\t} catch( e ) {}\n\t\t\t}\n\n\t\t\tif ( elem ) {\n\t\t\t\tthis.empty().append( value );\n\t\t\t}\n\t\t}, null, value, arguments.length );\n\t},\n\n\treplaceWith: function() {\n\t\tvar arg = arguments[ 0 ];\n\n\t\t// Make the changes, replacing each context element with the new content\n\t\tthis.domManip( arguments, function( elem ) {\n\t\t\targ = this.parentNode;\n\n\t\t\tjQuery.cleanData( getAll( this ) );\n\n\t\t\tif ( arg ) {\n\t\t\t\targ.replaceChild( elem, this );\n\t\t\t}\n\t\t});\n\n\t\t// Force removal if there was no new content (e.g., from empty arguments)\n\t\treturn arg && (arg.length || arg.nodeType) ? this : this.remove();\n\t},\n\n\tdetach: function( selector ) {\n\t\treturn this.remove( selector, true );\n\t},\n\n\tdomManip: function( args, callback ) {\n\n\t\t// Flatten any nested arrays\n\t\targs = concat.apply( [], args );\n\n\t\tvar fragment, first, scripts, hasScripts, node, doc,\n\t\t\ti = 0,\n\t\t\tl = this.length,\n\t\t\tset = this,\n\t\t\tiNoClone = l - 1,\n\t\t\tvalue = args[ 0 ],\n\t\t\tisFunction = jQuery.isFunction( value );\n\n\t\t// We can't cloneNode fragments that contain checked, in WebKit\n\t\tif ( isFunction ||\n\t\t\t\t( l > 1 && typeof value === \"string\" &&\n\t\t\t\t\t!support.checkClone && rchecked.test( value ) ) ) {\n\t\t\treturn this.each(function( index ) {\n\t\t\t\tvar self = set.eq( index );\n\t\t\t\tif ( isFunction ) {\n\t\t\t\t\targs[ 0 ] = value.call( this, index, self.html() );\n\t\t\t\t}\n\t\t\t\tself.domManip( args, callback );\n\t\t\t});\n\t\t}\n\n\t\tif ( l ) {\n\t\t\tfragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );\n\t\t\tfirst = fragment.firstChild;\n\n\t\t\tif ( fragment.childNodes.length === 1 ) {\n\t\t\t\tfragment = first;\n\t\t\t}\n\n\t\t\tif ( first ) {\n\t\t\t\tscripts = jQuery.map( getAll( fragment, \"script\" ), disableScript );\n\t\t\t\thasScripts = scripts.length;\n\n\t\t\t\t// Use the original fragment for the last item instead of the first because it can end up\n\t\t\t\t// being emptied incorrectly in certain situations (#8070).\n\t\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\t\tnode = fragment;\n\n\t\t\t\t\tif ( i !== iNoClone ) {\n\t\t\t\t\t\tnode = jQuery.clone( node, true, true );\n\n\t\t\t\t\t\t// Keep references to cloned scripts for later restoration\n\t\t\t\t\t\tif ( hasScripts ) {\n\t\t\t\t\t\t\t// Support: QtWebKit\n\t\t\t\t\t\t\t// jQuery.merge because push.apply(_, arraylike) throws\n\t\t\t\t\t\t\tjQuery.merge( scripts, getAll( node, \"script\" ) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tcallback.call( this[ i ], node, i );\n\t\t\t\t}\n\n\t\t\t\tif ( hasScripts ) {\n\t\t\t\t\tdoc = scripts[ scripts.length - 1 ].ownerDocument;\n\n\t\t\t\t\t// Reenable scripts\n\t\t\t\t\tjQuery.map( scripts, restoreScript );\n\n\t\t\t\t\t// Evaluate executable scripts on first document insertion\n\t\t\t\t\tfor ( i = 0; i < hasScripts; i++ ) {\n\t\t\t\t\t\tnode = scripts[ i ];\n\t\t\t\t\t\tif ( rscriptType.test( node.type || \"\" ) &&\n\t\t\t\t\t\t\t!data_priv.access( node, \"globalEval\" ) && jQuery.contains( doc, node ) ) {\n\n\t\t\t\t\t\t\tif ( node.src ) {\n\t\t\t\t\t\t\t\t// Optional AJAX dependency, but won't run scripts if not present\n\t\t\t\t\t\t\t\tif ( jQuery._evalUrl ) {\n\t\t\t\t\t\t\t\t\tjQuery._evalUrl( node.src );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tjQuery.globalEval( node.textContent.replace( rcleanScript, \"\" ) );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t}\n});\n\njQuery.each({\n\tappendTo: \"append\",\n\tprependTo: \"prepend\",\n\tinsertBefore: \"before\",\n\tinsertAfter: \"after\",\n\treplaceAll: \"replaceWith\"\n}, function( name, original ) {\n\tjQuery.fn[ name ] = function( selector ) {\n\t\tvar elems,\n\t\t\tret = [],\n\t\t\tinsert = jQuery( selector ),\n\t\t\tlast = insert.length - 1,\n\t\t\ti = 0;\n\n\t\tfor ( ; i <= last; i++ ) {\n\t\t\telems = i === last ? this : this.clone( true );\n\t\t\tjQuery( insert[ i ] )[ original ]( elems );\n\n\t\t\t// Support: QtWebKit\n\t\t\t// .get() because push.apply(_, arraylike) throws\n\t\t\tpush.apply( ret, elems.get() );\n\t\t}\n\n\t\treturn this.pushStack( ret );\n\t};\n});\n\n\nvar iframe,\n\telemdisplay = {};\n\n/**\n * Retrieve the actual display of a element\n * @param {String} name nodeName of the element\n * @param {Object} doc Document object\n */\n// Called only from within defaultDisplay\nfunction actualDisplay( name, doc ) {\n\tvar elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),\n\n\t\t// getDefaultComputedStyle might be reliably used only on attached element\n\t\tdisplay = window.getDefaultComputedStyle ?\n\n\t\t\t// Use of this method is a temporary fix (more like optmization) until something better comes along,\n\t\t\t// since it was removed from specification and supported only in FF\n\t\t\twindow.getDefaultComputedStyle( elem[ 0 ] ).display : jQuery.css( elem[ 0 ], \"display\" );\n\n\t// We don't have any data stored on the element,\n\t// so use \"detach\" method as fast way to get rid of the element\n\telem.detach();\n\n\treturn display;\n}\n\n/**\n * Try to determine the default display value of an element\n * @param {String} nodeName\n */\nfunction defaultDisplay( nodeName ) {\n\tvar doc = document,\n\t\tdisplay = elemdisplay[ nodeName ];\n\n\tif ( !display ) {\n\t\tdisplay = actualDisplay( nodeName, doc );\n\n\t\t// If the simple way fails, read from inside an iframe\n\t\tif ( display === \"none\" || !display ) {\n\n\t\t\t// Use the already-created iframe if possible\n\t\t\tiframe = (iframe || jQuery( \"<iframe frameborder='0' width='0' height='0'/>\" )).appendTo( doc.documentElement );\n\n\t\t\t// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse\n\t\t\tdoc = iframe[ 0 ].contentDocument;\n\n\t\t\t// Support: IE\n\t\t\tdoc.write();\n\t\t\tdoc.close();\n\n\t\t\tdisplay = actualDisplay( nodeName, doc );\n\t\t\tiframe.detach();\n\t\t}\n\n\t\t// Store the correct default display\n\t\telemdisplay[ nodeName ] = display;\n\t}\n\n\treturn display;\n}\nvar rmargin = (/^margin/);\n\nvar rnumnonpx = new RegExp( \"^(\" + pnum + \")(?!px)[a-z%]+$\", \"i\" );\n\nvar getStyles = function( elem ) {\n\t\treturn elem.ownerDocument.defaultView.getComputedStyle( elem, null );\n\t};\n\n\n\nfunction curCSS( elem, name, computed ) {\n\tvar width, minWidth, maxWidth, ret,\n\t\tstyle = elem.style;\n\n\tcomputed = computed || getStyles( elem );\n\n\t// Support: IE9\n\t// getPropertyValue is only needed for .css('filter') in IE9, see #12537\n\tif ( computed ) {\n\t\tret = computed.getPropertyValue( name ) || computed[ name ];\n\t}\n\n\tif ( computed ) {\n\n\t\tif ( ret === \"\" && !jQuery.contains( elem.ownerDocument, elem ) ) {\n\t\t\tret = jQuery.style( elem, name );\n\t\t}\n\n\t\t// Support: iOS < 6\n\t\t// A tribute to the \"awesome hack by Dean Edwards\"\n\t\t// iOS < 6 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels\n\t\t// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values\n\t\tif ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {\n\n\t\t\t// Remember the original values\n\t\t\twidth = style.width;\n\t\t\tminWidth = style.minWidth;\n\t\t\tmaxWidth = style.maxWidth;\n\n\t\t\t// Put in the new values to get a computed value out\n\t\t\tstyle.minWidth = style.maxWidth = style.width = ret;\n\t\t\tret = computed.width;\n\n\t\t\t// Revert the changed values\n\t\t\tstyle.width = width;\n\t\t\tstyle.minWidth = minWidth;\n\t\t\tstyle.maxWidth = maxWidth;\n\t\t}\n\t}\n\n\treturn ret !== undefined ?\n\t\t// Support: IE\n\t\t// IE returns zIndex value as an integer.\n\t\tret + \"\" :\n\t\tret;\n}\n\n\nfunction addGetHookIf( conditionFn, hookFn ) {\n\t// Define the hook, we'll check on the first run if it's really needed.\n\treturn {\n\t\tget: function() {\n\t\t\tif ( conditionFn() ) {\n\t\t\t\t// Hook not needed (or it's not possible to use it due to missing dependency),\n\t\t\t\t// remove it.\n\t\t\t\t// Since there are no other hooks for marginRight, remove the whole object.\n\t\t\t\tdelete this.get;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Hook needed; redefine it so that the support test is not executed again.\n\n\t\t\treturn (this.get = hookFn).apply( this, arguments );\n\t\t}\n\t};\n}\n\n\n(function() {\n\tvar pixelPositionVal, boxSizingReliableVal,\n\t\t// Support: Firefox, Android 2.3 (Prefixed box-sizing versions).\n\t\tdivReset = \"padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;\" +\n\t\t\t\"-moz-box-sizing:content-box;box-sizing:content-box\",\n\t\tdocElem = document.documentElement,\n\t\tcontainer = document.createElement( \"div\" ),\n\t\tdiv = document.createElement( \"div\" );\n\n\tdiv.style.backgroundClip = \"content-box\";\n\tdiv.cloneNode( true ).style.backgroundClip = \"\";\n\tsupport.clearCloneStyle = div.style.backgroundClip === \"content-box\";\n\n\tcontainer.style.cssText = \"border:0;width:0;height:0;position:absolute;top:0;left:-9999px;\" +\n\t\t\"margin-top:1px\";\n\tcontainer.appendChild( div );\n\n\t// Executing both pixelPosition & boxSizingReliable tests require only one layout\n\t// so they're executed at the same time to save the second computation.\n\tfunction computePixelPositionAndBoxSizingReliable() {\n\t\t// Support: Firefox, Android 2.3 (Prefixed box-sizing versions).\n\t\tdiv.style.cssText = \"-webkit-box-sizing:border-box;-moz-box-sizing:border-box;\" +\n\t\t\t\"box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;\" +\n\t\t\t\"position:absolute;top:1%\";\n\t\tdocElem.appendChild( container );\n\n\t\tvar divStyle = window.getComputedStyle( div, null );\n\t\tpixelPositionVal = divStyle.top !== \"1%\";\n\t\tboxSizingReliableVal = divStyle.width === \"4px\";\n\n\t\tdocElem.removeChild( container );\n\t}\n\n\t// Use window.getComputedStyle because jsdom on node.js will break without it.\n\tif ( window.getComputedStyle ) {\n\t\tjQuery.extend(support, {\n\t\t\tpixelPosition: function() {\n\t\t\t\t// This test is executed only once but we still do memoizing\n\t\t\t\t// since we can use the boxSizingReliable pre-computing.\n\t\t\t\t// No need to check if the test was already performed, though.\n\t\t\t\tcomputePixelPositionAndBoxSizingReliable();\n\t\t\t\treturn pixelPositionVal;\n\t\t\t},\n\t\t\tboxSizingReliable: function() {\n\t\t\t\tif ( boxSizingReliableVal == null ) {\n\t\t\t\t\tcomputePixelPositionAndBoxSizingReliable();\n\t\t\t\t}\n\t\t\t\treturn boxSizingReliableVal;\n\t\t\t},\n\t\t\treliableMarginRight: function() {\n\t\t\t\t// Support: Android 2.3\n\t\t\t\t// Check if div with explicit width and no margin-right incorrectly\n\t\t\t\t// gets computed margin-right based on width of container. (#3333)\n\t\t\t\t// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right\n\t\t\t\t// This support function is only executed once so no memoizing is needed.\n\t\t\t\tvar ret,\n\t\t\t\t\tmarginDiv = div.appendChild( document.createElement( \"div\" ) );\n\t\t\t\tmarginDiv.style.cssText = div.style.cssText = divReset;\n\t\t\t\tmarginDiv.style.marginRight = marginDiv.style.width = \"0\";\n\t\t\t\tdiv.style.width = \"1px\";\n\t\t\t\tdocElem.appendChild( container );\n\n\t\t\t\tret = !parseFloat( window.getComputedStyle( marginDiv, null ).marginRight );\n\n\t\t\t\tdocElem.removeChild( container );\n\n\t\t\t\t// Clean up the div for other support tests.\n\t\t\t\tdiv.innerHTML = \"\";\n\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t});\n\t}\n})();\n\n\n// A method for quickly swapping in/out CSS properties to get correct calculations.\njQuery.swap = function( elem, options, callback, args ) {\n\tvar ret, name,\n\t\told = {};\n\n\t// Remember the old values, and insert the new ones\n\tfor ( name in options ) {\n\t\told[ name ] = elem.style[ name ];\n\t\telem.style[ name ] = options[ name ];\n\t}\n\n\tret = callback.apply( elem, args || [] );\n\n\t// Revert the old values\n\tfor ( name in options ) {\n\t\telem.style[ name ] = old[ name ];\n\t}\n\n\treturn ret;\n};\n\n\nvar\n\t// swappable if display is none or starts with table except \"table\", \"table-cell\", or \"table-caption\"\n\t// see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display\n\trdisplayswap = /^(none|table(?!-c[ea]).+)/,\n\trnumsplit = new RegExp( \"^(\" + pnum + \")(.*)$\", \"i\" ),\n\trrelNum = new RegExp( \"^([+-])=(\" + pnum + \")\", \"i\" ),\n\n\tcssShow = { position: \"absolute\", visibility: \"hidden\", display: \"block\" },\n\tcssNormalTransform = {\n\t\tletterSpacing: 0,\n\t\tfontWeight: 400\n\t},\n\n\tcssPrefixes = [ \"Webkit\", \"O\", \"Moz\", \"ms\" ];\n\n// return a css property mapped to a potentially vendor prefixed property\nfunction vendorPropName( style, name ) {\n\n\t// shortcut for names that are not vendor prefixed\n\tif ( name in style ) {\n\t\treturn name;\n\t}\n\n\t// check for vendor prefixed names\n\tvar capName = name[0].toUpperCase() + name.slice(1),\n\t\torigName = name,\n\t\ti = cssPrefixes.length;\n\n\twhile ( i-- ) {\n\t\tname = cssPrefixes[ i ] + capName;\n\t\tif ( name in style ) {\n\t\t\treturn name;\n\t\t}\n\t}\n\n\treturn origName;\n}\n\nfunction setPositiveNumber( elem, value, subtract ) {\n\tvar matches = rnumsplit.exec( value );\n\treturn matches ?\n\t\t// Guard against undefined \"subtract\", e.g., when used as in cssHooks\n\t\tMath.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || \"px\" ) :\n\t\tvalue;\n}\n\nfunction augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {\n\tvar i = extra === ( isBorderBox ? \"border\" : \"content\" ) ?\n\t\t// If we already have the right measurement, avoid augmentation\n\t\t4 :\n\t\t// Otherwise initialize for horizontal or vertical properties\n\t\tname === \"width\" ? 1 : 0,\n\n\t\tval = 0;\n\n\tfor ( ; i < 4; i += 2 ) {\n\t\t// both box models exclude margin, so add it if we want it\n\t\tif ( extra === \"margin\" ) {\n\t\t\tval += jQuery.css( elem, extra + cssExpand[ i ], true, styles );\n\t\t}\n\n\t\tif ( isBorderBox ) {\n\t\t\t// border-box includes padding, so remove it if we want content\n\t\t\tif ( extra === \"content\" ) {\n\t\t\t\tval -= jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\n\t\t\t}\n\n\t\t\t// at this point, extra isn't border nor margin, so remove border\n\t\t\tif ( extra !== \"margin\" ) {\n\t\t\t\tval -= jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\t\t\t}\n\t\t} else {\n\t\t\t// at this point, extra isn't content, so add padding\n\t\t\tval += jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\n\n\t\t\t// at this point, extra isn't content nor padding, so add border\n\t\t\tif ( extra !== \"padding\" ) {\n\t\t\t\tval += jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn val;\n}\n\nfunction getWidthOrHeight( elem, name, extra ) {\n\n\t// Start with offset property, which is equivalent to the border-box value\n\tvar valueIsBorderBox = true,\n\t\tval = name === \"width\" ? elem.offsetWidth : elem.offsetHeight,\n\t\tstyles = getStyles( elem ),\n\t\tisBorderBox = jQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\";\n\n\t// some non-html elements return undefined for offsetWidth, so check for null/undefined\n\t// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285\n\t// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668\n\tif ( val <= 0 || val == null ) {\n\t\t// Fall back to computed then uncomputed css if necessary\n\t\tval = curCSS( elem, name, styles );\n\t\tif ( val < 0 || val == null ) {\n\t\t\tval = elem.style[ name ];\n\t\t}\n\n\t\t// Computed unit is not pixels. Stop here and return.\n\t\tif ( rnumnonpx.test(val) ) {\n\t\t\treturn val;\n\t\t}\n\n\t\t// we need the check for style in case a browser which returns unreliable values\n\t\t// for getComputedStyle silently falls back to the reliable elem.style\n\t\tvalueIsBorderBox = isBorderBox &&\n\t\t\t( support.boxSizingReliable() || val === elem.style[ name ] );\n\n\t\t// Normalize \"\", auto, and prepare for extra\n\t\tval = parseFloat( val ) || 0;\n\t}\n\n\t// use the active box-sizing model to add/subtract irrelevant styles\n\treturn ( val +\n\t\taugmentWidthOrHeight(\n\t\t\telem,\n\t\t\tname,\n\t\t\textra || ( isBorderBox ? \"border\" : \"content\" ),\n\t\t\tvalueIsBorderBox,\n\t\t\tstyles\n\t\t)\n\t) + \"px\";\n}\n\nfunction showHide( elements, show ) {\n\tvar display, elem, hidden,\n\t\tvalues = [],\n\t\tindex = 0,\n\t\tlength = elements.length;\n\n\tfor ( ; index < length; index++ ) {\n\t\telem = elements[ index ];\n\t\tif ( !elem.style ) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tvalues[ index ] = data_priv.get( elem, \"olddisplay\" );\n\t\tdisplay = elem.style.display;\n\t\tif ( show ) {\n\t\t\t// Reset the inline display of this element to learn if it is\n\t\t\t// being hidden by cascaded rules or not\n\t\t\tif ( !values[ index ] && display === \"none\" ) {\n\t\t\t\telem.style.display = \"\";\n\t\t\t}\n\n\t\t\t// Set elements which have been overridden with display: none\n\t\t\t// in a stylesheet to whatever the default browser style is\n\t\t\t// for such an element\n\t\t\tif ( elem.style.display === \"\" && isHidden( elem ) ) {\n\t\t\t\tvalues[ index ] = data_priv.access( elem, \"olddisplay\", defaultDisplay(elem.nodeName) );\n\t\t\t}\n\t\t} else {\n\n\t\t\tif ( !values[ index ] ) {\n\t\t\t\thidden = isHidden( elem );\n\n\t\t\t\tif ( display && display !== \"none\" || !hidden ) {\n\t\t\t\t\tdata_priv.set( elem, \"olddisplay\", hidden ? display : jQuery.css(elem, \"display\") );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Set the display of most of the elements in a second loop\n\t// to avoid the constant reflow\n\tfor ( index = 0; index < length; index++ ) {\n\t\telem = elements[ index ];\n\t\tif ( !elem.style ) {\n\t\t\tcontinue;\n\t\t}\n\t\tif ( !show || elem.style.display === \"none\" || elem.style.display === \"\" ) {\n\t\t\telem.style.display = show ? values[ index ] || \"\" : \"none\";\n\t\t}\n\t}\n\n\treturn elements;\n}\n\njQuery.extend({\n\t// Add in style property hooks for overriding the default\n\t// behavior of getting and setting a style property\n\tcssHooks: {\n\t\topacity: {\n\t\t\tget: function( elem, computed ) {\n\t\t\t\tif ( computed ) {\n\t\t\t\t\t// We should always get a number back from opacity\n\t\t\t\t\tvar ret = curCSS( elem, \"opacity\" );\n\t\t\t\t\treturn ret === \"\" ? \"1\" : ret;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t// Don't automatically add \"px\" to these possibly-unitless properties\n\tcssNumber: {\n\t\t\"columnCount\": true,\n\t\t\"fillOpacity\": true,\n\t\t\"fontWeight\": true,\n\t\t\"lineHeight\": true,\n\t\t\"opacity\": true,\n\t\t\"order\": true,\n\t\t\"orphans\": true,\n\t\t\"widows\": true,\n\t\t\"zIndex\": true,\n\t\t\"zoom\": true\n\t},\n\n\t// Add in properties whose names you wish to fix before\n\t// setting or getting the value\n\tcssProps: {\n\t\t// normalize float css property\n\t\t\"float\": \"cssFloat\"\n\t},\n\n\t// Get and set the style property on a DOM Node\n\tstyle: function( elem, name, value, extra ) {\n\t\t// Don't set styles on text and comment nodes\n\t\tif ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Make sure that we're working with the right name\n\t\tvar ret, type, hooks,\n\t\t\torigName = jQuery.camelCase( name ),\n\t\t\tstyle = elem.style;\n\n\t\tname = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );\n\n\t\t// gets hook for the prefixed version\n\t\t// followed by the unprefixed version\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// Check if we're setting a value\n\t\tif ( value !== undefined ) {\n\t\t\ttype = typeof value;\n\n\t\t\t// convert relative number strings (+= or -=) to relative numbers. #7345\n\t\t\tif ( type === \"string\" && (ret = rrelNum.exec( value )) ) {\n\t\t\t\tvalue = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );\n\t\t\t\t// Fixes bug #9237\n\t\t\t\ttype = \"number\";\n\t\t\t}\n\n\t\t\t// Make sure that null and NaN values aren't set. See: #7116\n\t\t\tif ( value == null || value !== value ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If a number was passed in, add 'px' to the (except for certain CSS properties)\n\t\t\tif ( type === \"number\" && !jQuery.cssNumber[ origName ] ) {\n\t\t\t\tvalue += \"px\";\n\t\t\t}\n\n\t\t\t// Fixes #8908, it can be done more correctly by specifying setters in cssHooks,\n\t\t\t// but it would mean to define eight (for every problematic property) identical functions\n\t\t\tif ( !support.clearCloneStyle && value === \"\" && name.indexOf( \"background\" ) === 0 ) {\n\t\t\t\tstyle[ name ] = \"inherit\";\n\t\t\t}\n\n\t\t\t// If a hook was provided, use that value, otherwise just set the specified value\n\t\t\tif ( !hooks || !(\"set\" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {\n\t\t\t\t// Support: Chrome, Safari\n\t\t\t\t// Setting style to blank string required to delete \"style: x !important;\"\n\t\t\t\tstyle[ name ] = \"\";\n\t\t\t\tstyle[ name ] = value;\n\t\t\t}\n\n\t\t} else {\n\t\t\t// If a hook was provided get the non-computed value from there\n\t\t\tif ( hooks && \"get\" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\t// Otherwise just get the value from the style object\n\t\t\treturn style[ name ];\n\t\t}\n\t},\n\n\tcss: function( elem, name, extra, styles ) {\n\t\tvar val, num, hooks,\n\t\t\torigName = jQuery.camelCase( name );\n\n\t\t// Make sure that we're working with the right name\n\t\tname = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );\n\n\t\t// gets hook for the prefixed version\n\t\t// followed by the unprefixed version\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// If a hook was provided get the computed value from there\n\t\tif ( hooks && \"get\" in hooks ) {\n\t\t\tval = hooks.get( elem, true, extra );\n\t\t}\n\n\t\t// Otherwise, if a way to get the computed value exists, use that\n\t\tif ( val === undefined ) {\n\t\t\tval = curCSS( elem, name, styles );\n\t\t}\n\n\t\t//convert \"normal\" to computed value\n\t\tif ( val === \"normal\" && name in cssNormalTransform ) {\n\t\t\tval = cssNormalTransform[ name ];\n\t\t}\n\n\t\t// Return, converting to number if forced or a qualifier was provided and val looks numeric\n\t\tif ( extra === \"\" || extra ) {\n\t\t\tnum = parseFloat( val );\n\t\t\treturn extra === true || jQuery.isNumeric( num ) ? num || 0 : val;\n\t\t}\n\t\treturn val;\n\t}\n});\n\njQuery.each([ \"height\", \"width\" ], function( i, name ) {\n\tjQuery.cssHooks[ name ] = {\n\t\tget: function( elem, computed, extra ) {\n\t\t\tif ( computed ) {\n\t\t\t\t// certain elements can have dimension info if we invisibly show them\n\t\t\t\t// however, it must have a current display style that would benefit from this\n\t\t\t\treturn elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, \"display\" ) ) ?\n\t\t\t\t\tjQuery.swap( elem, cssShow, function() {\n\t\t\t\t\t\treturn getWidthOrHeight( elem, name, extra );\n\t\t\t\t\t}) :\n\t\t\t\t\tgetWidthOrHeight( elem, name, extra );\n\t\t\t}\n\t\t},\n\n\t\tset: function( elem, value, extra ) {\n\t\t\tvar styles = extra && getStyles( elem );\n\t\t\treturn setPositiveNumber( elem, value, extra ?\n\t\t\t\taugmentWidthOrHeight(\n\t\t\t\t\telem,\n\t\t\t\t\tname,\n\t\t\t\t\textra,\n\t\t\t\t\tjQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\",\n\t\t\t\t\tstyles\n\t\t\t\t) : 0\n\t\t\t);\n\t\t}\n\t};\n});\n\n// Support: Android 2.3\njQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,\n\tfunction( elem, computed ) {\n\t\tif ( computed ) {\n\t\t\t// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right\n\t\t\t// Work around by temporarily setting element display to inline-block\n\t\t\treturn jQuery.swap( elem, { \"display\": \"inline-block\" },\n\t\t\t\tcurCSS, [ elem, \"marginRight\" ] );\n\t\t}\n\t}\n);\n\n// These hooks are used by animate to expand properties\njQuery.each({\n\tmargin: \"\",\n\tpadding: \"\",\n\tborder: \"Width\"\n}, function( prefix, suffix ) {\n\tjQuery.cssHooks[ prefix + suffix ] = {\n\t\texpand: function( value ) {\n\t\t\tvar i = 0,\n\t\t\t\texpanded = {},\n\n\t\t\t\t// assumes a single number if not a string\n\t\t\t\tparts = typeof value === \"string\" ? value.split(\" \") : [ value ];\n\n\t\t\tfor ( ; i < 4; i++ ) {\n\t\t\t\texpanded[ prefix + cssExpand[ i ] + suffix ] =\n\t\t\t\t\tparts[ i ] || parts[ i - 2 ] || parts[ 0 ];\n\t\t\t}\n\n\t\t\treturn expanded;\n\t\t}\n\t};\n\n\tif ( !rmargin.test( prefix ) ) {\n\t\tjQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;\n\t}\n});\n\njQuery.fn.extend({\n\tcss: function( name, value ) {\n\t\treturn access( this, function( elem, name, value ) {\n\t\t\tvar styles, len,\n\t\t\t\tmap = {},\n\t\t\t\ti = 0;\n\n\t\t\tif ( jQuery.isArray( name ) ) {\n\t\t\t\tstyles = getStyles( elem );\n\t\t\t\tlen = name.length;\n\n\t\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\t\tmap[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );\n\t\t\t\t}\n\n\t\t\t\treturn map;\n\t\t\t}\n\n\t\t\treturn value !== undefined ?\n\t\t\t\tjQuery.style( elem, name, value ) :\n\t\t\t\tjQuery.css( elem, name );\n\t\t}, name, value, arguments.length > 1 );\n\t},\n\tshow: function() {\n\t\treturn showHide( this, true );\n\t},\n\thide: function() {\n\t\treturn showHide( this );\n\t},\n\ttoggle: function( state ) {\n\t\tif ( typeof state === \"boolean\" ) {\n\t\t\treturn state ? this.show() : this.hide();\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tif ( isHidden( this ) ) {\n\t\t\t\tjQuery( this ).show();\n\t\t\t} else {\n\t\t\t\tjQuery( this ).hide();\n\t\t\t}\n\t\t});\n\t}\n});\n\n\nfunction Tween( elem, options, prop, end, easing ) {\n\treturn new Tween.prototype.init( elem, options, prop, end, easing );\n}\njQuery.Tween = Tween;\n\nTween.prototype = {\n\tconstructor: Tween,\n\tinit: function( elem, options, prop, end, easing, unit ) {\n\t\tthis.elem = elem;\n\t\tthis.prop = prop;\n\t\tthis.easing = easing || \"swing\";\n\t\tthis.options = options;\n\t\tthis.start = this.now = this.cur();\n\t\tthis.end = end;\n\t\tthis.unit = unit || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" );\n\t},\n\tcur: function() {\n\t\tvar hooks = Tween.propHooks[ this.prop ];\n\n\t\treturn hooks && hooks.get ?\n\t\t\thooks.get( this ) :\n\t\t\tTween.propHooks._default.get( this );\n\t},\n\trun: function( percent ) {\n\t\tvar eased,\n\t\t\thooks = Tween.propHooks[ this.prop ];\n\n\t\tif ( this.options.duration ) {\n\t\t\tthis.pos = eased = jQuery.easing[ this.easing ](\n\t\t\t\tpercent, this.options.duration * percent, 0, 1, this.options.duration\n\t\t\t);\n\t\t} else {\n\t\t\tthis.pos = eased = percent;\n\t\t}\n\t\tthis.now = ( this.end - this.start ) * eased + this.start;\n\n\t\tif ( this.options.step ) {\n\t\t\tthis.options.step.call( this.elem, this.now, this );\n\t\t}\n\n\t\tif ( hooks && hooks.set ) {\n\t\t\thooks.set( this );\n\t\t} else {\n\t\t\tTween.propHooks._default.set( this );\n\t\t}\n\t\treturn this;\n\t}\n};\n\nTween.prototype.init.prototype = Tween.prototype;\n\nTween.propHooks = {\n\t_default: {\n\t\tget: function( tween ) {\n\t\t\tvar result;\n\n\t\t\tif ( tween.elem[ tween.prop ] != null &&\n\t\t\t\t(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {\n\t\t\t\treturn tween.elem[ tween.prop ];\n\t\t\t}\n\n\t\t\t// passing an empty string as a 3rd parameter to .css will automatically\n\t\t\t// attempt a parseFloat and fallback to a string if the parse fails\n\t\t\t// so, simple values such as \"10px\" are parsed to Float.\n\t\t\t// complex values such as \"rotate(1rad)\" are returned as is.\n\t\t\tresult = jQuery.css( tween.elem, tween.prop, \"\" );\n\t\t\t// Empty strings, null, undefined and \"auto\" are converted to 0.\n\t\t\treturn !result || result === \"auto\" ? 0 : result;\n\t\t},\n\t\tset: function( tween ) {\n\t\t\t// use step hook for back compat - use cssHook if its there - use .style if its\n\t\t\t// available and use plain properties where available\n\t\t\tif ( jQuery.fx.step[ tween.prop ] ) {\n\t\t\t\tjQuery.fx.step[ tween.prop ]( tween );\n\t\t\t} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {\n\t\t\t\tjQuery.style( tween.elem, tween.prop, tween.now + tween.unit );\n\t\t\t} else {\n\t\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t\t}\n\t\t}\n\t}\n};\n\n// Support: IE9\n// Panic based approach to setting things on disconnected nodes\n\nTween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {\n\tset: function( tween ) {\n\t\tif ( tween.elem.nodeType && tween.elem.parentNode ) {\n\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t}\n\t}\n};\n\njQuery.easing = {\n\tlinear: function( p ) {\n\t\treturn p;\n\t},\n\tswing: function( p ) {\n\t\treturn 0.5 - Math.cos( p * Math.PI ) / 2;\n\t}\n};\n\njQuery.fx = Tween.prototype.init;\n\n// Back Compat <1.8 extension point\njQuery.fx.step = {};\n\n\n\n\nvar\n\tfxNow, timerId,\n\trfxtypes = /^(?:toggle|show|hide)$/,\n\trfxnum = new RegExp( \"^(?:([+-])=|)(\" + pnum + \")([a-z%]*)$\", \"i\" ),\n\trrun = /queueHooks$/,\n\tanimationPrefilters = [ defaultPrefilter ],\n\ttweeners = {\n\t\t\"*\": [ function( prop, value ) {\n\t\t\tvar tween = this.createTween( prop, value ),\n\t\t\t\ttarget = tween.cur(),\n\t\t\t\tparts = rfxnum.exec( value ),\n\t\t\t\tunit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" ),\n\n\t\t\t\t// Starting value computation is required for potential unit mismatches\n\t\t\t\tstart = ( jQuery.cssNumber[ prop ] || unit !== \"px\" && +target ) &&\n\t\t\t\t\trfxnum.exec( jQuery.css( tween.elem, prop ) ),\n\t\t\t\tscale = 1,\n\t\t\t\tmaxIterations = 20;\n\n\t\t\tif ( start && start[ 3 ] !== unit ) {\n\t\t\t\t// Trust units reported by jQuery.css\n\t\t\t\tunit = unit || start[ 3 ];\n\n\t\t\t\t// Make sure we update the tween properties later on\n\t\t\t\tparts = parts || [];\n\n\t\t\t\t// Iteratively approximate from a nonzero starting point\n\t\t\t\tstart = +target || 1;\n\n\t\t\t\tdo {\n\t\t\t\t\t// If previous iteration zeroed out, double until we get *something*\n\t\t\t\t\t// Use a string for doubling factor so we don't accidentally see scale as unchanged below\n\t\t\t\t\tscale = scale || \".5\";\n\n\t\t\t\t\t// Adjust and apply\n\t\t\t\t\tstart = start / scale;\n\t\t\t\t\tjQuery.style( tween.elem, prop, start + unit );\n\n\t\t\t\t// Update scale, tolerating zero or NaN from tween.cur()\n\t\t\t\t// And breaking the loop if scale is unchanged or perfect, or if we've just had enough\n\t\t\t\t} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );\n\t\t\t}\n\n\t\t\t// Update tween properties\n\t\t\tif ( parts ) {\n\t\t\t\tstart = tween.start = +start || +target || 0;\n\t\t\t\ttween.unit = unit;\n\t\t\t\t// If a +=/-= token was provided, we're doing a relative animation\n\t\t\t\ttween.end = parts[ 1 ] ?\n\t\t\t\t\tstart + ( parts[ 1 ] + 1 ) * parts[ 2 ] :\n\t\t\t\t\t+parts[ 2 ];\n\t\t\t}\n\n\t\t\treturn tween;\n\t\t} ]\n\t};\n\n// Animations created synchronously will run synchronously\nfunction createFxNow() {\n\tsetTimeout(function() {\n\t\tfxNow = undefined;\n\t});\n\treturn ( fxNow = jQuery.now() );\n}\n\n// Generate parameters to create a standard animation\nfunction genFx( type, includeWidth ) {\n\tvar which,\n\t\ti = 0,\n\t\tattrs = { height: type };\n\n\t// if we include width, step value is 1 to do all cssExpand values,\n\t// if we don't include width, step value is 2 to skip over Left and Right\n\tincludeWidth = includeWidth ? 1 : 0;\n\tfor ( ; i < 4 ; i += 2 - includeWidth ) {\n\t\twhich = cssExpand[ i ];\n\t\tattrs[ \"margin\" + which ] = attrs[ \"padding\" + which ] = type;\n\t}\n\n\tif ( includeWidth ) {\n\t\tattrs.opacity = attrs.width = type;\n\t}\n\n\treturn attrs;\n}\n\nfunction createTween( value, prop, animation ) {\n\tvar tween,\n\t\tcollection = ( tweeners[ prop ] || [] ).concat( tweeners[ \"*\" ] ),\n\t\tindex = 0,\n\t\tlength = collection.length;\n\tfor ( ; index < length; index++ ) {\n\t\tif ( (tween = collection[ index ].call( animation, prop, value )) ) {\n\n\t\t\t// we're done with this property\n\t\t\treturn tween;\n\t\t}\n\t}\n}\n\nfunction defaultPrefilter( elem, props, opts ) {\n\t/* jshint validthis: true */\n\tvar prop, value, toggle, tween, hooks, oldfire, display,\n\t\tanim = this,\n\t\torig = {},\n\t\tstyle = elem.style,\n\t\thidden = elem.nodeType && isHidden( elem ),\n\t\tdataShow = data_priv.get( elem, \"fxshow\" );\n\n\t// handle queue: false promises\n\tif ( !opts.queue ) {\n\t\thooks = jQuery._queueHooks( elem, \"fx\" );\n\t\tif ( hooks.unqueued == null ) {\n\t\t\thooks.unqueued = 0;\n\t\t\toldfire = hooks.empty.fire;\n\t\t\thooks.empty.fire = function() {\n\t\t\t\tif ( !hooks.unqueued ) {\n\t\t\t\t\toldfire();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t\thooks.unqueued++;\n\n\t\tanim.always(function() {\n\t\t\t// doing this makes sure that the complete handler will be called\n\t\t\t// before this completes\n\t\t\tanim.always(function() {\n\t\t\t\thooks.unqueued--;\n\t\t\t\tif ( !jQuery.queue( elem, \"fx\" ).length ) {\n\t\t\t\t\thooks.empty.fire();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t// height/width overflow pass\n\tif ( elem.nodeType === 1 && ( \"height\" in props || \"width\" in props ) ) {\n\t\t// Make sure that nothing sneaks out\n\t\t// Record all 3 overflow attributes because IE9-10 do not\n\t\t// change the overflow attribute when overflowX and\n\t\t// overflowY are set to the same value\n\t\topts.overflow = [ style.overflow, style.overflowX, style.overflowY ];\n\n\t\t// Set display property to inline-block for height/width\n\t\t// animations on inline elements that are having width/height animated\n\t\tdisplay = jQuery.css( elem, \"display\" );\n\t\t// Get default display if display is currently \"none\"\n\t\tif ( display === \"none\" ) {\n\t\t\tdisplay = defaultDisplay( elem.nodeName );\n\t\t}\n\t\tif ( display === \"inline\" &&\n\t\t\t\tjQuery.css( elem, \"float\" ) === \"none\" ) {\n\n\t\t\tstyle.display = \"inline-block\";\n\t\t}\n\t}\n\n\tif ( opts.overflow ) {\n\t\tstyle.overflow = \"hidden\";\n\t\tanim.always(function() {\n\t\t\tstyle.overflow = opts.overflow[ 0 ];\n\t\t\tstyle.overflowX = opts.overflow[ 1 ];\n\t\t\tstyle.overflowY = opts.overflow[ 2 ];\n\t\t});\n\t}\n\n\t// show/hide pass\n\tfor ( prop in props ) {\n\t\tvalue = props[ prop ];\n\t\tif ( rfxtypes.exec( value ) ) {\n\t\t\tdelete props[ prop ];\n\t\t\ttoggle = toggle || value === \"toggle\";\n\t\t\tif ( value === ( hidden ? \"hide\" : \"show\" ) ) {\n\n\t\t\t\t// If there is dataShow left over from a stopped hide or show and we are going to proceed with show, we should pretend to be hidden\n\t\t\t\tif ( value === \"show\" && dataShow && dataShow[ prop ] !== undefined ) {\n\t\t\t\t\thidden = true;\n\t\t\t\t} else {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\torig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );\n\t\t}\n\t}\n\n\tif ( !jQuery.isEmptyObject( orig ) ) {\n\t\tif ( dataShow ) {\n\t\t\tif ( \"hidden\" in dataShow ) {\n\t\t\t\thidden = dataShow.hidden;\n\t\t\t}\n\t\t} else {\n\t\t\tdataShow = data_priv.access( elem, \"fxshow\", {} );\n\t\t}\n\n\t\t// store state if its toggle - enables .stop().toggle() to \"reverse\"\n\t\tif ( toggle ) {\n\t\t\tdataShow.hidden = !hidden;\n\t\t}\n\t\tif ( hidden ) {\n\t\t\tjQuery( elem ).show();\n\t\t} else {\n\t\t\tanim.done(function() {\n\t\t\t\tjQuery( elem ).hide();\n\t\t\t});\n\t\t}\n\t\tanim.done(function() {\n\t\t\tvar prop;\n\n\t\t\tdata_priv.remove( elem, \"fxshow\" );\n\t\t\tfor ( prop in orig ) {\n\t\t\t\tjQuery.style( elem, prop, orig[ prop ] );\n\t\t\t}\n\t\t});\n\t\tfor ( prop in orig ) {\n\t\t\ttween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );\n\n\t\t\tif ( !( prop in dataShow ) ) {\n\t\t\t\tdataShow[ prop ] = tween.start;\n\t\t\t\tif ( hidden ) {\n\t\t\t\t\ttween.end = tween.start;\n\t\t\t\t\ttween.start = prop === \"width\" || prop === \"height\" ? 1 : 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction propFilter( props, specialEasing ) {\n\tvar index, name, easing, value, hooks;\n\n\t// camelCase, specialEasing and expand cssHook pass\n\tfor ( index in props ) {\n\t\tname = jQuery.camelCase( index );\n\t\teasing = specialEasing[ name ];\n\t\tvalue = props[ index ];\n\t\tif ( jQuery.isArray( value ) ) {\n\t\t\teasing = value[ 1 ];\n\t\t\tvalue = props[ index ] = value[ 0 ];\n\t\t}\n\n\t\tif ( index !== name ) {\n\t\t\tprops[ name ] = value;\n\t\t\tdelete props[ index ];\n\t\t}\n\n\t\thooks = jQuery.cssHooks[ name ];\n\t\tif ( hooks && \"expand\" in hooks ) {\n\t\t\tvalue = hooks.expand( value );\n\t\t\tdelete props[ name ];\n\n\t\t\t// not quite $.extend, this wont overwrite keys already present.\n\t\t\t// also - reusing 'index' from above because we have the correct \"name\"\n\t\t\tfor ( index in value ) {\n\t\t\t\tif ( !( index in props ) ) {\n\t\t\t\t\tprops[ index ] = value[ index ];\n\t\t\t\t\tspecialEasing[ index ] = easing;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tspecialEasing[ name ] = easing;\n\t\t}\n\t}\n}\n\nfunction Animation( elem, properties, options ) {\n\tvar result,\n\t\tstopped,\n\t\tindex = 0,\n\t\tlength = animationPrefilters.length,\n\t\tdeferred = jQuery.Deferred().always( function() {\n\t\t\t// don't match elem in the :animated selector\n\t\t\tdelete tick.elem;\n\t\t}),\n\t\ttick = function() {\n\t\t\tif ( stopped ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tvar currentTime = fxNow || createFxNow(),\n\t\t\t\tremaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),\n\t\t\t\t// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)\n\t\t\t\ttemp = remaining / animation.duration || 0,\n\t\t\t\tpercent = 1 - temp,\n\t\t\t\tindex = 0,\n\t\t\t\tlength = animation.tweens.length;\n\n\t\t\tfor ( ; index < length ; index++ ) {\n\t\t\t\tanimation.tweens[ index ].run( percent );\n\t\t\t}\n\n\t\t\tdeferred.notifyWith( elem, [ animation, percent, remaining ]);\n\n\t\t\tif ( percent < 1 && length ) {\n\t\t\t\treturn remaining;\n\t\t\t} else {\n\t\t\t\tdeferred.resolveWith( elem, [ animation ] );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\t\tanimation = deferred.promise({\n\t\t\telem: elem,\n\t\t\tprops: jQuery.extend( {}, properties ),\n\t\t\topts: jQuery.extend( true, { specialEasing: {} }, options ),\n\t\t\toriginalProperties: properties,\n\t\t\toriginalOptions: options,\n\t\t\tstartTime: fxNow || createFxNow(),\n\t\t\tduration: options.duration,\n\t\t\ttweens: [],\n\t\t\tcreateTween: function( prop, end ) {\n\t\t\t\tvar tween = jQuery.Tween( elem, animation.opts, prop, end,\n\t\t\t\t\t\tanimation.opts.specialEasing[ prop ] || animation.opts.easing );\n\t\t\t\tanimation.tweens.push( tween );\n\t\t\t\treturn tween;\n\t\t\t},\n\t\t\tstop: function( gotoEnd ) {\n\t\t\t\tvar index = 0,\n\t\t\t\t\t// if we are going to the end, we want to run all the tweens\n\t\t\t\t\t// otherwise we skip this part\n\t\t\t\t\tlength = gotoEnd ? animation.tweens.length : 0;\n\t\t\t\tif ( stopped ) {\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t\tstopped = true;\n\t\t\t\tfor ( ; index < length ; index++ ) {\n\t\t\t\t\tanimation.tweens[ index ].run( 1 );\n\t\t\t\t}\n\n\t\t\t\t// resolve when we played the last frame\n\t\t\t\t// otherwise, reject\n\t\t\t\tif ( gotoEnd ) {\n\t\t\t\t\tdeferred.resolveWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t} else {\n\t\t\t\t\tdeferred.rejectWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t}\n\t\t}),\n\t\tprops = animation.props;\n\n\tpropFilter( props, animation.opts.specialEasing );\n\n\tfor ( ; index < length ; index++ ) {\n\t\tresult = animationPrefilters[ index ].call( animation, elem, props, animation.opts );\n\t\tif ( result ) {\n\t\t\treturn result;\n\t\t}\n\t}\n\n\tjQuery.map( props, createTween, animation );\n\n\tif ( jQuery.isFunction( animation.opts.start ) ) {\n\t\tanimation.opts.start.call( elem, animation );\n\t}\n\n\tjQuery.fx.timer(\n\t\tjQuery.extend( tick, {\n\t\t\telem: elem,\n\t\t\tanim: animation,\n\t\t\tqueue: animation.opts.queue\n\t\t})\n\t);\n\n\t// attach callbacks from options\n\treturn animation.progress( animation.opts.progress )\n\t\t.done( animation.opts.done, animation.opts.complete )\n\t\t.fail( animation.opts.fail )\n\t\t.always( animation.opts.always );\n}\n\njQuery.Animation = jQuery.extend( Animation, {\n\n\ttweener: function( props, callback ) {\n\t\tif ( jQuery.isFunction( props ) ) {\n\t\t\tcallback = props;\n\t\t\tprops = [ \"*\" ];\n\t\t} else {\n\t\t\tprops = props.split(\" \");\n\t\t}\n\n\t\tvar prop,\n\t\t\tindex = 0,\n\t\t\tlength = props.length;\n\n\t\tfor ( ; index < length ; index++ ) {\n\t\t\tprop = props[ index ];\n\t\t\ttweeners[ prop ] = tweeners[ prop ] || [];\n\t\t\ttweeners[ prop ].unshift( callback );\n\t\t}\n\t},\n\n\tprefilter: function( callback, prepend ) {\n\t\tif ( prepend ) {\n\t\t\tanimationPrefilters.unshift( callback );\n\t\t} else {\n\t\t\tanimationPrefilters.push( callback );\n\t\t}\n\t}\n});\n\njQuery.speed = function( speed, easing, fn ) {\n\tvar opt = speed && typeof speed === \"object\" ? jQuery.extend( {}, speed ) : {\n\t\tcomplete: fn || !fn && easing ||\n\t\t\tjQuery.isFunction( speed ) && speed,\n\t\tduration: speed,\n\t\teasing: fn && easing || easing && !jQuery.isFunction( easing ) && easing\n\t};\n\n\topt.duration = jQuery.fx.off ? 0 : typeof opt.duration === \"number\" ? opt.duration :\n\t\topt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;\n\n\t// normalize opt.queue - true/undefined/null -> \"fx\"\n\tif ( opt.queue == null || opt.queue === true ) {\n\t\topt.queue = \"fx\";\n\t}\n\n\t// Queueing\n\topt.old = opt.complete;\n\n\topt.complete = function() {\n\t\tif ( jQuery.isFunction( opt.old ) ) {\n\t\t\topt.old.call( this );\n\t\t}\n\n\t\tif ( opt.queue ) {\n\t\t\tjQuery.dequeue( this, opt.queue );\n\t\t}\n\t};\n\n\treturn opt;\n};\n\njQuery.fn.extend({\n\tfadeTo: function( speed, to, easing, callback ) {\n\n\t\t// show any hidden elements after setting opacity to 0\n\t\treturn this.filter( isHidden ).css( \"opacity\", 0 ).show()\n\n\t\t\t// animate to the value specified\n\t\t\t.end().animate({ opacity: to }, speed, easing, callback );\n\t},\n\tanimate: function( prop, speed, easing, callback ) {\n\t\tvar empty = jQuery.isEmptyObject( prop ),\n\t\t\toptall = jQuery.speed( speed, easing, callback ),\n\t\t\tdoAnimation = function() {\n\t\t\t\t// Operate on a copy of prop so per-property easing won't be lost\n\t\t\t\tvar anim = Animation( this, jQuery.extend( {}, prop ), optall );\n\n\t\t\t\t// Empty animations, or finishing resolves immediately\n\t\t\t\tif ( empty || data_priv.get( this, \"finish\" ) ) {\n\t\t\t\t\tanim.stop( true );\n\t\t\t\t}\n\t\t\t};\n\t\t\tdoAnimation.finish = doAnimation;\n\n\t\treturn empty || optall.queue === false ?\n\t\t\tthis.each( doAnimation ) :\n\t\t\tthis.queue( optall.queue, doAnimation );\n\t},\n\tstop: function( type, clearQueue, gotoEnd ) {\n\t\tvar stopQueue = function( hooks ) {\n\t\t\tvar stop = hooks.stop;\n\t\t\tdelete hooks.stop;\n\t\t\tstop( gotoEnd );\n\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tgotoEnd = clearQueue;\n\t\t\tclearQueue = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\tif ( clearQueue && type !== false ) {\n\t\t\tthis.queue( type || \"fx\", [] );\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tvar dequeue = true,\n\t\t\t\tindex = type != null && type + \"queueHooks\",\n\t\t\t\ttimers = jQuery.timers,\n\t\t\t\tdata = data_priv.get( this );\n\n\t\t\tif ( index ) {\n\t\t\t\tif ( data[ index ] && data[ index ].stop ) {\n\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( index in data ) {\n\t\t\t\t\tif ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {\n\t\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor ( index = timers.length; index--; ) {\n\t\t\t\tif ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {\n\t\t\t\t\ttimers[ index ].anim.stop( gotoEnd );\n\t\t\t\t\tdequeue = false;\n\t\t\t\t\ttimers.splice( index, 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// start the next in the queue if the last step wasn't forced\n\t\t\t// timers currently will call their complete callbacks, which will dequeue\n\t\t\t// but only if they were gotoEnd\n\t\t\tif ( dequeue || !gotoEnd ) {\n\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t}\n\t\t});\n\t},\n\tfinish: function( type ) {\n\t\tif ( type !== false ) {\n\t\t\ttype = type || \"fx\";\n\t\t}\n\t\treturn this.each(function() {\n\t\t\tvar index,\n\t\t\t\tdata = data_priv.get( this ),\n\t\t\t\tqueue = data[ type + \"queue\" ],\n\t\t\t\thooks = data[ type + \"queueHooks\" ],\n\t\t\t\ttimers = jQuery.timers,\n\t\t\t\tlength = queue ? queue.length : 0;\n\n\t\t\t// enable finishing flag on private data\n\t\t\tdata.finish = true;\n\n\t\t\t// empty the queue first\n\t\t\tjQuery.queue( this, type, [] );\n\n\t\t\tif ( hooks && hooks.stop ) {\n\t\t\t\thooks.stop.call( this, true );\n\t\t\t}\n\n\t\t\t// look for any active animations, and finish them\n\t\t\tfor ( index = timers.length; index--; ) {\n\t\t\t\tif ( timers[ index ].elem === this && timers[ index ].queue === type ) {\n\t\t\t\t\ttimers[ index ].anim.stop( true );\n\t\t\t\t\ttimers.splice( index, 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// look for any animations in the old queue and finish them\n\t\t\tfor ( index = 0; index < length; index++ ) {\n\t\t\t\tif ( queue[ index ] && queue[ index ].finish ) {\n\t\t\t\t\tqueue[ index ].finish.call( this );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// turn off finishing flag\n\t\t\tdelete data.finish;\n\t\t});\n\t}\n});\n\njQuery.each([ \"toggle\", \"show\", \"hide\" ], function( i, name ) {\n\tvar cssFn = jQuery.fn[ name ];\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn speed == null || typeof speed === \"boolean\" ?\n\t\t\tcssFn.apply( this, arguments ) :\n\t\t\tthis.animate( genFx( name, true ), speed, easing, callback );\n\t};\n});\n\n// Generate shortcuts for custom animations\njQuery.each({\n\tslideDown: genFx(\"show\"),\n\tslideUp: genFx(\"hide\"),\n\tslideToggle: genFx(\"toggle\"),\n\tfadeIn: { opacity: \"show\" },\n\tfadeOut: { opacity: \"hide\" },\n\tfadeToggle: { opacity: \"toggle\" }\n}, function( name, props ) {\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn this.animate( props, speed, easing, callback );\n\t};\n});\n\njQuery.timers = [];\njQuery.fx.tick = function() {\n\tvar timer,\n\t\ti = 0,\n\t\ttimers = jQuery.timers;\n\n\tfxNow = jQuery.now();\n\n\tfor ( ; i < timers.length; i++ ) {\n\t\ttimer = timers[ i ];\n\t\t// Checks the timer has not already been removed\n\t\tif ( !timer() && timers[ i ] === timer ) {\n\t\t\ttimers.splice( i--, 1 );\n\t\t}\n\t}\n\n\tif ( !timers.length ) {\n\t\tjQuery.fx.stop();\n\t}\n\tfxNow = undefined;\n};\n\njQuery.fx.timer = function( timer ) {\n\tjQuery.timers.push( timer );\n\tif ( timer() ) {\n\t\tjQuery.fx.start();\n\t} else {\n\t\tjQuery.timers.pop();\n\t}\n};\n\njQuery.fx.interval = 13;\n\njQuery.fx.start = function() {\n\tif ( !timerId ) {\n\t\ttimerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );\n\t}\n};\n\njQuery.fx.stop = function() {\n\tclearInterval( timerId );\n\ttimerId = null;\n};\n\njQuery.fx.speeds = {\n\tslow: 600,\n\tfast: 200,\n\t// Default speed\n\t_default: 400\n};\n\n\n// Based off of the plugin by Clint Helfers, with permission.\n// http://blindsignals.com/index.php/2009/07/jquery-delay/\njQuery.fn.delay = function( time, type ) {\n\ttime = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;\n\ttype = type || \"fx\";\n\n\treturn this.queue( type, function( next, hooks ) {\n\t\tvar timeout = setTimeout( next, time );\n\t\thooks.stop = function() {\n\t\t\tclearTimeout( timeout );\n\t\t};\n\t});\n};\n\n\n(function() {\n\tvar input = document.createElement( \"input\" ),\n\t\tselect = document.createElement( \"select\" ),\n\t\topt = select.appendChild( document.createElement( \"option\" ) );\n\n\tinput.type = \"checkbox\";\n\n\t// Support: iOS 5.1, Android 4.x, Android 2.3\n\t// Check the default checkbox/radio value (\"\" on old WebKit; \"on\" elsewhere)\n\tsupport.checkOn = input.value !== \"\";\n\n\t// Must access the parent to make an option select properly\n\t// Support: IE9, IE10\n\tsupport.optSelected = opt.selected;\n\n\t// Make sure that the options inside disabled selects aren't marked as disabled\n\t// (WebKit marks them as disabled)\n\tselect.disabled = true;\n\tsupport.optDisabled = !opt.disabled;\n\n\t// Check if an input maintains its value after becoming a radio\n\t// Support: IE9, IE10\n\tinput = document.createElement( \"input\" );\n\tinput.value = \"t\";\n\tinput.type = \"radio\";\n\tsupport.radioValue = input.value === \"t\";\n})();\n\n\nvar nodeHook, boolHook,\n\tattrHandle = jQuery.expr.attrHandle;\n\njQuery.fn.extend({\n\tattr: function( name, value ) {\n\t\treturn access( this, jQuery.attr, name, value, arguments.length > 1 );\n\t},\n\n\tremoveAttr: function( name ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.removeAttr( this, name );\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tattr: function( elem, name, value ) {\n\t\tvar hooks, ret,\n\t\t\tnType = elem.nodeType;\n\n\t\t// don't get/set attributes on text, comment and attribute nodes\n\t\tif ( !elem || nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Fallback to prop when attributes are not supported\n\t\tif ( typeof elem.getAttribute === strundefined ) {\n\t\t\treturn jQuery.prop( elem, name, value );\n\t\t}\n\n\t\t// All attributes are lowercase\n\t\t// Grab necessary hook if one is defined\n\t\tif ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {\n\t\t\tname = name.toLowerCase();\n\t\t\thooks = jQuery.attrHooks[ name ] ||\n\t\t\t\t( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\n\t\t\tif ( value === null ) {\n\t\t\t\tjQuery.removeAttr( elem, name );\n\n\t\t\t} else if ( hooks && \"set\" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {\n\t\t\t\treturn ret;\n\n\t\t\t} else {\n\t\t\t\telem.setAttribute( name, value + \"\" );\n\t\t\t\treturn value;\n\t\t\t}\n\n\t\t} else if ( hooks && \"get\" in hooks && (ret = hooks.get( elem, name )) !== null ) {\n\t\t\treturn ret;\n\n\t\t} else {\n\t\t\tret = jQuery.find.attr( elem, name );\n\n\t\t\t// Non-existent attributes return null, we normalize to undefined\n\t\t\treturn ret == null ?\n\t\t\t\tundefined :\n\t\t\t\tret;\n\t\t}\n\t},\n\n\tremoveAttr: function( elem, value ) {\n\t\tvar name, propName,\n\t\t\ti = 0,\n\t\t\tattrNames = value && value.match( rnotwhite );\n\n\t\tif ( attrNames && elem.nodeType === 1 ) {\n\t\t\twhile ( (name = attrNames[i++]) ) {\n\t\t\t\tpropName = jQuery.propFix[ name ] || name;\n\n\t\t\t\t// Boolean attributes get special treatment (#10870)\n\t\t\t\tif ( jQuery.expr.match.bool.test( name ) ) {\n\t\t\t\t\t// Set corresponding property to false\n\t\t\t\t\telem[ propName ] = false;\n\t\t\t\t}\n\n\t\t\t\telem.removeAttribute( name );\n\t\t\t}\n\t\t}\n\t},\n\n\tattrHooks: {\n\t\ttype: {\n\t\t\tset: function( elem, value ) {\n\t\t\t\tif ( !support.radioValue && value === \"radio\" &&\n\t\t\t\t\tjQuery.nodeName( elem, \"input\" ) ) {\n\t\t\t\t\t// Setting the type on a radio button after the value resets the value in IE6-9\n\t\t\t\t\t// Reset value to default in case type is set after value during creation\n\t\t\t\t\tvar val = elem.value;\n\t\t\t\t\telem.setAttribute( \"type\", value );\n\t\t\t\t\tif ( val ) {\n\t\t\t\t\t\telem.value = val;\n\t\t\t\t\t}\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n});\n\n// Hooks for boolean attributes\nboolHook = {\n\tset: function( elem, value, name ) {\n\t\tif ( value === false ) {\n\t\t\t// Remove boolean attributes when set to false\n\t\t\tjQuery.removeAttr( elem, name );\n\t\t} else {\n\t\t\telem.setAttribute( name, name );\n\t\t}\n\t\treturn name;\n\t}\n};\njQuery.each( jQuery.expr.match.bool.source.match( /\\w+/g ), function( i, name ) {\n\tvar getter = attrHandle[ name ] || jQuery.find.attr;\n\n\tattrHandle[ name ] = function( elem, name, isXML ) {\n\t\tvar ret, handle;\n\t\tif ( !isXML ) {\n\t\t\t// Avoid an infinite loop by temporarily removing this function from the getter\n\t\t\thandle = attrHandle[ name ];\n\t\t\tattrHandle[ name ] = ret;\n\t\t\tret = getter( elem, name, isXML ) != null ?\n\t\t\t\tname.toLowerCase() :\n\t\t\t\tnull;\n\t\t\tattrHandle[ name ] = handle;\n\t\t}\n\t\treturn ret;\n\t};\n});\n\n\n\n\nvar rfocusable = /^(?:input|select|textarea|button)$/i;\n\njQuery.fn.extend({\n\tprop: function( name, value ) {\n\t\treturn access( this, jQuery.prop, name, value, arguments.length > 1 );\n\t},\n\n\tremoveProp: function( name ) {\n\t\treturn this.each(function() {\n\t\t\tdelete this[ jQuery.propFix[ name ] || name ];\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tpropFix: {\n\t\t\"for\": \"htmlFor\",\n\t\t\"class\": \"className\"\n\t},\n\n\tprop: function( elem, name, value ) {\n\t\tvar ret, hooks, notxml,\n\t\t\tnType = elem.nodeType;\n\n\t\t// don't get/set properties on text, comment and attribute nodes\n\t\tif ( !elem || nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\tnotxml = nType !== 1 || !jQuery.isXMLDoc( elem );\n\n\t\tif ( notxml ) {\n\t\t\t// Fix name and attach hooks\n\t\t\tname = jQuery.propFix[ name ] || name;\n\t\t\thooks = jQuery.propHooks[ name ];\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\t\t\treturn hooks && \"set\" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?\n\t\t\t\tret :\n\t\t\t\t( elem[ name ] = value );\n\n\t\t} else {\n\t\t\treturn hooks && \"get\" in hooks && (ret = hooks.get( elem, name )) !== null ?\n\t\t\t\tret :\n\t\t\t\telem[ name ];\n\t\t}\n\t},\n\n\tpropHooks: {\n\t\ttabIndex: {\n\t\t\tget: function( elem ) {\n\t\t\t\treturn elem.hasAttribute( \"tabindex\" ) || rfocusable.test( elem.nodeName ) || elem.href ?\n\t\t\t\t\telem.tabIndex :\n\t\t\t\t\t-1;\n\t\t\t}\n\t\t}\n\t}\n});\n\n// Support: IE9+\n// Selectedness for an option in an optgroup can be inaccurate\nif ( !support.optSelected ) {\n\tjQuery.propHooks.selected = {\n\t\tget: function( elem ) {\n\t\t\tvar parent = elem.parentNode;\n\t\t\tif ( parent && parent.parentNode ) {\n\t\t\t\tparent.parentNode.selectedIndex;\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t};\n}\n\njQuery.each([\n\t\"tabIndex\",\n\t\"readOnly\",\n\t\"maxLength\",\n\t\"cellSpacing\",\n\t\"cellPadding\",\n\t\"rowSpan\",\n\t\"colSpan\",\n\t\"useMap\",\n\t\"frameBorder\",\n\t\"contentEditable\"\n], function() {\n\tjQuery.propFix[ this.toLowerCase() ] = this;\n});\n\n\n\n\nvar rclass = /[\\t\\r\\n\\f]/g;\n\njQuery.fn.extend({\n\taddClass: function( value ) {\n\t\tvar classes, elem, cur, clazz, j, finalValue,\n\t\t\tproceed = typeof value === \"string\" && value,\n\t\t\ti = 0,\n\t\t\tlen = this.length;\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( j ) {\n\t\t\t\tjQuery( this ).addClass( value.call( this, j, this.className ) );\n\t\t\t});\n\t\t}\n\n\t\tif ( proceed ) {\n\t\t\t// The disjunction here is for better compressibility (see removeClass)\n\t\t\tclasses = ( value || \"\" ).match( rnotwhite ) || [];\n\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\telem = this[ i ];\n\t\t\t\tcur = elem.nodeType === 1 && ( elem.className ?\n\t\t\t\t\t( \" \" + elem.className + \" \" ).replace( rclass, \" \" ) :\n\t\t\t\t\t\" \"\n\t\t\t\t);\n\n\t\t\t\tif ( cur ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (clazz = classes[j++]) ) {\n\t\t\t\t\t\tif ( cur.indexOf( \" \" + clazz + \" \" ) < 0 ) {\n\t\t\t\t\t\t\tcur += clazz + \" \";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// only assign if different to avoid unneeded rendering.\n\t\t\t\t\tfinalValue = jQuery.trim( cur );\n\t\t\t\t\tif ( elem.className !== finalValue ) {\n\t\t\t\t\t\telem.className = finalValue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tremoveClass: function( value ) {\n\t\tvar classes, elem, cur, clazz, j, finalValue,\n\t\t\tproceed = arguments.length === 0 || typeof value === \"string\" && value,\n\t\t\ti = 0,\n\t\t\tlen = this.length;\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( j ) {\n\t\t\t\tjQuery( this ).removeClass( value.call( this, j, this.className ) );\n\t\t\t});\n\t\t}\n\t\tif ( proceed ) {\n\t\t\tclasses = ( value || \"\" ).match( rnotwhite ) || [];\n\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\telem = this[ i ];\n\t\t\t\t// This expression is here for better compressibility (see addClass)\n\t\t\t\tcur = elem.nodeType === 1 && ( elem.className ?\n\t\t\t\t\t( \" \" + elem.className + \" \" ).replace( rclass, \" \" ) :\n\t\t\t\t\t\"\"\n\t\t\t\t);\n\n\t\t\t\tif ( cur ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (clazz = classes[j++]) ) {\n\t\t\t\t\t\t// Remove *all* instances\n\t\t\t\t\t\twhile ( cur.indexOf( \" \" + clazz + \" \" ) >= 0 ) {\n\t\t\t\t\t\t\tcur = cur.replace( \" \" + clazz + \" \", \" \" );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// only assign if different to avoid unneeded rendering.\n\t\t\t\t\tfinalValue = value ? jQuery.trim( cur ) : \"\";\n\t\t\t\t\tif ( elem.className !== finalValue ) {\n\t\t\t\t\t\telem.className = finalValue;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\ttoggleClass: function( value, stateVal ) {\n\t\tvar type = typeof value;\n\n\t\tif ( typeof stateVal === \"boolean\" && type === \"string\" ) {\n\t\t\treturn stateVal ? this.addClass( value ) : this.removeClass( value );\n\t\t}\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( i ) {\n\t\t\t\tjQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );\n\t\t\t});\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tif ( type === \"string\" ) {\n\t\t\t\t// toggle individual class names\n\t\t\t\tvar className,\n\t\t\t\t\ti = 0,\n\t\t\t\t\tself = jQuery( this ),\n\t\t\t\t\tclassNames = value.match( rnotwhite ) || [];\n\n\t\t\t\twhile ( (className = classNames[ i++ ]) ) {\n\t\t\t\t\t// check each className given, space separated list\n\t\t\t\t\tif ( self.hasClass( className ) ) {\n\t\t\t\t\t\tself.removeClass( className );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tself.addClass( className );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Toggle whole class name\n\t\t\t} else if ( type === strundefined || type === \"boolean\" ) {\n\t\t\t\tif ( this.className ) {\n\t\t\t\t\t// store className if set\n\t\t\t\t\tdata_priv.set( this, \"__className__\", this.className );\n\t\t\t\t}\n\n\t\t\t\t// If the element has a class name or if we're passed \"false\",\n\t\t\t\t// then remove the whole classname (if there was one, the above saved it).\n\t\t\t\t// Otherwise bring back whatever was previously saved (if anything),\n\t\t\t\t// falling back to the empty string if nothing was stored.\n\t\t\t\tthis.className = this.className || value === false ? \"\" : data_priv.get( this, \"__className__\" ) || \"\";\n\t\t\t}\n\t\t});\n\t},\n\n\thasClass: function( selector ) {\n\t\tvar className = \" \" + selector + \" \",\n\t\t\ti = 0,\n\t\t\tl = this.length;\n\t\tfor ( ; i < l; i++ ) {\n\t\t\tif ( this[i].nodeType === 1 && (\" \" + this[i].className + \" \").replace(rclass, \" \").indexOf( className ) >= 0 ) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n});\n\n\n\n\nvar rreturn = /\\r/g;\n\njQuery.fn.extend({\n\tval: function( value ) {\n\t\tvar hooks, ret, isFunction,\n\t\t\telem = this[0];\n\n\t\tif ( !arguments.length ) {\n\t\t\tif ( elem ) {\n\t\t\t\thooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];\n\n\t\t\t\tif ( hooks && \"get\" in hooks && (ret = hooks.get( elem, \"value\" )) !== undefined ) {\n\t\t\t\t\treturn ret;\n\t\t\t\t}\n\n\t\t\t\tret = elem.value;\n\n\t\t\t\treturn typeof ret === \"string\" ?\n\t\t\t\t\t// handle most common string cases\n\t\t\t\t\tret.replace(rreturn, \"\") :\n\t\t\t\t\t// handle cases where value is null/undef or number\n\t\t\t\t\tret == null ? \"\" : ret;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tisFunction = jQuery.isFunction( value );\n\n\t\treturn this.each(function( i ) {\n\t\t\tvar val;\n\n\t\t\tif ( this.nodeType !== 1 ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( isFunction ) {\n\t\t\t\tval = value.call( this, i, jQuery( this ).val() );\n\t\t\t} else {\n\t\t\t\tval = value;\n\t\t\t}\n\n\t\t\t// Treat null/undefined as \"\"; convert numbers to string\n\t\t\tif ( val == null ) {\n\t\t\t\tval = \"\";\n\n\t\t\t} else if ( typeof val === \"number\" ) {\n\t\t\t\tval += \"\";\n\n\t\t\t} else if ( jQuery.isArray( val ) ) {\n\t\t\t\tval = jQuery.map( val, function( value ) {\n\t\t\t\t\treturn value == null ? \"\" : value + \"\";\n\t\t\t\t});\n\t\t\t}\n\n\t\t\thooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];\n\n\t\t\t// If set returns undefined, fall back to normal setting\n\t\t\tif ( !hooks || !(\"set\" in hooks) || hooks.set( this, val, \"value\" ) === undefined ) {\n\t\t\t\tthis.value = val;\n\t\t\t}\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tvalHooks: {\n\t\tselect: {\n\t\t\tget: function( elem ) {\n\t\t\t\tvar value, option,\n\t\t\t\t\toptions = elem.options,\n\t\t\t\t\tindex = elem.selectedIndex,\n\t\t\t\t\tone = elem.type === \"select-one\" || index < 0,\n\t\t\t\t\tvalues = one ? null : [],\n\t\t\t\t\tmax = one ? index + 1 : options.length,\n\t\t\t\t\ti = index < 0 ?\n\t\t\t\t\t\tmax :\n\t\t\t\t\t\tone ? index : 0;\n\n\t\t\t\t// Loop through all the selected options\n\t\t\t\tfor ( ; i < max; i++ ) {\n\t\t\t\t\toption = options[ i ];\n\n\t\t\t\t\t// IE6-9 doesn't update selected after form reset (#2551)\n\t\t\t\t\tif ( ( option.selected || i === index ) &&\n\t\t\t\t\t\t\t// Don't return options that are disabled or in a disabled optgroup\n\t\t\t\t\t\t\t( support.optDisabled ? !option.disabled : option.getAttribute( \"disabled\" ) === null ) &&\n\t\t\t\t\t\t\t( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, \"optgroup\" ) ) ) {\n\n\t\t\t\t\t\t// Get the specific value for the option\n\t\t\t\t\t\tvalue = jQuery( option ).val();\n\n\t\t\t\t\t\t// We don't need an array for one selects\n\t\t\t\t\t\tif ( one ) {\n\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Multi-Selects return an array\n\t\t\t\t\t\tvalues.push( value );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn values;\n\t\t\t},\n\n\t\t\tset: function( elem, value ) {\n\t\t\t\tvar optionSet, option,\n\t\t\t\t\toptions = elem.options,\n\t\t\t\t\tvalues = jQuery.makeArray( value ),\n\t\t\t\t\ti = options.length;\n\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\toption = options[ i ];\n\t\t\t\t\tif ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {\n\t\t\t\t\t\toptionSet = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// force browsers to behave consistently when non-matching value is set\n\t\t\t\tif ( !optionSet ) {\n\t\t\t\t\telem.selectedIndex = -1;\n\t\t\t\t}\n\t\t\t\treturn values;\n\t\t\t}\n\t\t}\n\t}\n});\n\n// Radios and checkboxes getter/setter\njQuery.each([ \"radio\", \"checkbox\" ], function() {\n\tjQuery.valHooks[ this ] = {\n\t\tset: function( elem, value ) {\n\t\t\tif ( jQuery.isArray( value ) ) {\n\t\t\t\treturn ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );\n\t\t\t}\n\t\t}\n\t};\n\tif ( !support.checkOn ) {\n\t\tjQuery.valHooks[ this ].get = function( elem ) {\n\t\t\t// Support: Webkit\n\t\t\t// \"\" is returned instead of \"on\" if a value isn't specified\n\t\t\treturn elem.getAttribute(\"value\") === null ? \"on\" : elem.value;\n\t\t};\n\t}\n});\n\n\n\n\n// Return jQuery for attributes-only inclusion\n\n\njQuery.each( (\"blur focus focusin focusout load resize scroll unload click dblclick \" +\n\t\"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave \" +\n\t\"change select submit keydown keypress keyup error contextmenu\").split(\" \"), function( i, name ) {\n\n\t// Handle event binding\n\tjQuery.fn[ name ] = function( data, fn ) {\n\t\treturn arguments.length > 0 ?\n\t\t\tthis.on( name, null, data, fn ) :\n\t\t\tthis.trigger( name );\n\t};\n});\n\njQuery.fn.extend({\n\thover: function( fnOver, fnOut ) {\n\t\treturn this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );\n\t},\n\n\tbind: function( types, data, fn ) {\n\t\treturn this.on( types, null, data, fn );\n\t},\n\tunbind: function( types, fn ) {\n\t\treturn this.off( types, null, fn );\n\t},\n\n\tdelegate: function( selector, types, data, fn ) {\n\t\treturn this.on( types, selector, data, fn );\n\t},\n\tundelegate: function( selector, types, fn ) {\n\t\t// ( namespace ) or ( selector, types [, fn] )\n\t\treturn arguments.length === 1 ? this.off( selector, \"**\" ) : this.off( types, selector || \"**\", fn );\n\t}\n});\n\n\nvar nonce = jQuery.now();\n\nvar rquery = (/\\?/);\n\n\n\n// Support: Android 2.3\n// Workaround failure to string-cast null input\njQuery.parseJSON = function( data ) {\n\treturn JSON.parse( data + \"\" );\n};\n\n\n// Cross-browser xml parsing\njQuery.parseXML = function( data ) {\n\tvar xml, tmp;\n\tif ( !data || typeof data !== \"string\" ) {\n\t\treturn null;\n\t}\n\n\t// Support: IE9\n\ttry {\n\t\ttmp = new DOMParser();\n\t\txml = tmp.parseFromString( data, \"text/xml\" );\n\t} catch ( e ) {\n\t\txml = undefined;\n\t}\n\n\tif ( !xml || xml.getElementsByTagName( \"parsererror\" ).length ) {\n\t\tjQuery.error( \"Invalid XML: \" + data );\n\t}\n\treturn xml;\n};\n\n\nvar\n\t// Document location\n\tajaxLocParts,\n\tajaxLocation,\n\n\trhash = /#.*$/,\n\trts = /([?&])_=[^&]*/,\n\trheaders = /^(.*?):[ \\t]*([^\\r\\n]*)$/mg,\n\t// #7653, #8125, #8152: local protocol detection\n\trlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,\n\trnoContent = /^(?:GET|HEAD)$/,\n\trprotocol = /^\\/\\//,\n\trurl = /^([\\w.+-]+:)(?:\\/\\/(?:[^\\/?#]*@|)([^\\/?#:]*)(?::(\\d+)|)|)/,\n\n\t/* Prefilters\n\t * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)\n\t * 2) These are called:\n\t *    - BEFORE asking for a transport\n\t *    - AFTER param serialization (s.data is a string if s.processData is true)\n\t * 3) key is the dataType\n\t * 4) the catchall symbol \"*\" can be used\n\t * 5) execution will start with transport dataType and THEN continue down to \"*\" if needed\n\t */\n\tprefilters = {},\n\n\t/* Transports bindings\n\t * 1) key is the dataType\n\t * 2) the catchall symbol \"*\" can be used\n\t * 3) selection will start with transport dataType and THEN go to \"*\" if needed\n\t */\n\ttransports = {},\n\n\t// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression\n\tallTypes = \"*/\".concat(\"*\");\n\n// #8138, IE may throw an exception when accessing\n// a field from window.location if document.domain has been set\ntry {\n\tajaxLocation = location.href;\n} catch( e ) {\n\t// Use the href attribute of an A element\n\t// since IE will modify it given document.location\n\tajaxLocation = document.createElement( \"a\" );\n\tajaxLocation.href = \"\";\n\tajaxLocation = ajaxLocation.href;\n}\n\n// Segment location into parts\najaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];\n\n// Base \"constructor\" for jQuery.ajaxPrefilter and jQuery.ajaxTransport\nfunction addToPrefiltersOrTransports( structure ) {\n\n\t// dataTypeExpression is optional and defaults to \"*\"\n\treturn function( dataTypeExpression, func ) {\n\n\t\tif ( typeof dataTypeExpression !== \"string\" ) {\n\t\t\tfunc = dataTypeExpression;\n\t\t\tdataTypeExpression = \"*\";\n\t\t}\n\n\t\tvar dataType,\n\t\t\ti = 0,\n\t\t\tdataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];\n\n\t\tif ( jQuery.isFunction( func ) ) {\n\t\t\t// For each dataType in the dataTypeExpression\n\t\t\twhile ( (dataType = dataTypes[i++]) ) {\n\t\t\t\t// Prepend if requested\n\t\t\t\tif ( dataType[0] === \"+\" ) {\n\t\t\t\t\tdataType = dataType.slice( 1 ) || \"*\";\n\t\t\t\t\t(structure[ dataType ] = structure[ dataType ] || []).unshift( func );\n\n\t\t\t\t// Otherwise append\n\t\t\t\t} else {\n\t\t\t\t\t(structure[ dataType ] = structure[ dataType ] || []).push( func );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\n// Base inspection function for prefilters and transports\nfunction inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {\n\n\tvar inspected = {},\n\t\tseekingTransport = ( structure === transports );\n\n\tfunction inspect( dataType ) {\n\t\tvar selected;\n\t\tinspected[ dataType ] = true;\n\t\tjQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {\n\t\t\tvar dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );\n\t\t\tif ( typeof dataTypeOrTransport === \"string\" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {\n\t\t\t\toptions.dataTypes.unshift( dataTypeOrTransport );\n\t\t\t\tinspect( dataTypeOrTransport );\n\t\t\t\treturn false;\n\t\t\t} else if ( seekingTransport ) {\n\t\t\t\treturn !( selected = dataTypeOrTransport );\n\t\t\t}\n\t\t});\n\t\treturn selected;\n\t}\n\n\treturn inspect( options.dataTypes[ 0 ] ) || !inspected[ \"*\" ] && inspect( \"*\" );\n}\n\n// A special extend for ajax options\n// that takes \"flat\" options (not to be deep extended)\n// Fixes #9887\nfunction ajaxExtend( target, src ) {\n\tvar key, deep,\n\t\tflatOptions = jQuery.ajaxSettings.flatOptions || {};\n\n\tfor ( key in src ) {\n\t\tif ( src[ key ] !== undefined ) {\n\t\t\t( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];\n\t\t}\n\t}\n\tif ( deep ) {\n\t\tjQuery.extend( true, target, deep );\n\t}\n\n\treturn target;\n}\n\n/* Handles responses to an ajax request:\n * - finds the right dataType (mediates between content-type and expected dataType)\n * - returns the corresponding response\n */\nfunction ajaxHandleResponses( s, jqXHR, responses ) {\n\n\tvar ct, type, finalDataType, firstDataType,\n\t\tcontents = s.contents,\n\t\tdataTypes = s.dataTypes;\n\n\t// Remove auto dataType and get content-type in the process\n\twhile ( dataTypes[ 0 ] === \"*\" ) {\n\t\tdataTypes.shift();\n\t\tif ( ct === undefined ) {\n\t\t\tct = s.mimeType || jqXHR.getResponseHeader(\"Content-Type\");\n\t\t}\n\t}\n\n\t// Check if we're dealing with a known content-type\n\tif ( ct ) {\n\t\tfor ( type in contents ) {\n\t\t\tif ( contents[ type ] && contents[ type ].test( ct ) ) {\n\t\t\t\tdataTypes.unshift( type );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check to see if we have a response for the expected dataType\n\tif ( dataTypes[ 0 ] in responses ) {\n\t\tfinalDataType = dataTypes[ 0 ];\n\t} else {\n\t\t// Try convertible dataTypes\n\t\tfor ( type in responses ) {\n\t\t\tif ( !dataTypes[ 0 ] || s.converters[ type + \" \" + dataTypes[0] ] ) {\n\t\t\t\tfinalDataType = type;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( !firstDataType ) {\n\t\t\t\tfirstDataType = type;\n\t\t\t}\n\t\t}\n\t\t// Or just use first one\n\t\tfinalDataType = finalDataType || firstDataType;\n\t}\n\n\t// If we found a dataType\n\t// We add the dataType to the list if needed\n\t// and return the corresponding response\n\tif ( finalDataType ) {\n\t\tif ( finalDataType !== dataTypes[ 0 ] ) {\n\t\t\tdataTypes.unshift( finalDataType );\n\t\t}\n\t\treturn responses[ finalDataType ];\n\t}\n}\n\n/* Chain conversions given the request and the original response\n * Also sets the responseXXX fields on the jqXHR instance\n */\nfunction ajaxConvert( s, response, jqXHR, isSuccess ) {\n\tvar conv2, current, conv, tmp, prev,\n\t\tconverters = {},\n\t\t// Work with a copy of dataTypes in case we need to modify it for conversion\n\t\tdataTypes = s.dataTypes.slice();\n\n\t// Create converters map with lowercased keys\n\tif ( dataTypes[ 1 ] ) {\n\t\tfor ( conv in s.converters ) {\n\t\t\tconverters[ conv.toLowerCase() ] = s.converters[ conv ];\n\t\t}\n\t}\n\n\tcurrent = dataTypes.shift();\n\n\t// Convert to each sequential dataType\n\twhile ( current ) {\n\n\t\tif ( s.responseFields[ current ] ) {\n\t\t\tjqXHR[ s.responseFields[ current ] ] = response;\n\t\t}\n\n\t\t// Apply the dataFilter if provided\n\t\tif ( !prev && isSuccess && s.dataFilter ) {\n\t\t\tresponse = s.dataFilter( response, s.dataType );\n\t\t}\n\n\t\tprev = current;\n\t\tcurrent = dataTypes.shift();\n\n\t\tif ( current ) {\n\n\t\t// There's only work to do if current dataType is non-auto\n\t\t\tif ( current === \"*\" ) {\n\n\t\t\t\tcurrent = prev;\n\n\t\t\t// Convert response if prev dataType is non-auto and differs from current\n\t\t\t} else if ( prev !== \"*\" && prev !== current ) {\n\n\t\t\t\t// Seek a direct converter\n\t\t\t\tconv = converters[ prev + \" \" + current ] || converters[ \"* \" + current ];\n\n\t\t\t\t// If none found, seek a pair\n\t\t\t\tif ( !conv ) {\n\t\t\t\t\tfor ( conv2 in converters ) {\n\n\t\t\t\t\t\t// If conv2 outputs current\n\t\t\t\t\t\ttmp = conv2.split( \" \" );\n\t\t\t\t\t\tif ( tmp[ 1 ] === current ) {\n\n\t\t\t\t\t\t\t// If prev can be converted to accepted input\n\t\t\t\t\t\t\tconv = converters[ prev + \" \" + tmp[ 0 ] ] ||\n\t\t\t\t\t\t\t\tconverters[ \"* \" + tmp[ 0 ] ];\n\t\t\t\t\t\t\tif ( conv ) {\n\t\t\t\t\t\t\t\t// Condense equivalence converters\n\t\t\t\t\t\t\t\tif ( conv === true ) {\n\t\t\t\t\t\t\t\t\tconv = converters[ conv2 ];\n\n\t\t\t\t\t\t\t\t// Otherwise, insert the intermediate dataType\n\t\t\t\t\t\t\t\t} else if ( converters[ conv2 ] !== true ) {\n\t\t\t\t\t\t\t\t\tcurrent = tmp[ 0 ];\n\t\t\t\t\t\t\t\t\tdataTypes.unshift( tmp[ 1 ] );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Apply converter (if not an equivalence)\n\t\t\t\tif ( conv !== true ) {\n\n\t\t\t\t\t// Unless errors are allowed to bubble, catch and return them\n\t\t\t\t\tif ( conv && s[ \"throws\" ] ) {\n\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t\t} catch ( e ) {\n\t\t\t\t\t\t\treturn { state: \"parsererror\", error: conv ? e : \"No conversion from \" + prev + \" to \" + current };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { state: \"success\", data: response };\n}\n\njQuery.extend({\n\n\t// Counter for holding the number of active queries\n\tactive: 0,\n\n\t// Last-Modified header cache for next request\n\tlastModified: {},\n\tetag: {},\n\n\tajaxSettings: {\n\t\turl: ajaxLocation,\n\t\ttype: \"GET\",\n\t\tisLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),\n\t\tglobal: true,\n\t\tprocessData: true,\n\t\tasync: true,\n\t\tcontentType: \"application/x-www-form-urlencoded; charset=UTF-8\",\n\t\t/*\n\t\ttimeout: 0,\n\t\tdata: null,\n\t\tdataType: null,\n\t\tusername: null,\n\t\tpassword: null,\n\t\tcache: null,\n\t\tthrows: false,\n\t\ttraditional: false,\n\t\theaders: {},\n\t\t*/\n\n\t\taccepts: {\n\t\t\t\"*\": allTypes,\n\t\t\ttext: \"text/plain\",\n\t\t\thtml: \"text/html\",\n\t\t\txml: \"application/xml, text/xml\",\n\t\t\tjson: \"application/json, text/javascript\"\n\t\t},\n\n\t\tcontents: {\n\t\t\txml: /xml/,\n\t\t\thtml: /html/,\n\t\t\tjson: /json/\n\t\t},\n\n\t\tresponseFields: {\n\t\t\txml: \"responseXML\",\n\t\t\ttext: \"responseText\",\n\t\t\tjson: \"responseJSON\"\n\t\t},\n\n\t\t// Data converters\n\t\t// Keys separate source (or catchall \"*\") and destination types with a single space\n\t\tconverters: {\n\n\t\t\t// Convert anything to text\n\t\t\t\"* text\": String,\n\n\t\t\t// Text to html (true = no transformation)\n\t\t\t\"text html\": true,\n\n\t\t\t// Evaluate text as a json expression\n\t\t\t\"text json\": jQuery.parseJSON,\n\n\t\t\t// Parse text as xml\n\t\t\t\"text xml\": jQuery.parseXML\n\t\t},\n\n\t\t// For options that shouldn't be deep extended:\n\t\t// you can add your own custom options here if\n\t\t// and when you create one that shouldn't be\n\t\t// deep extended (see ajaxExtend)\n\t\tflatOptions: {\n\t\t\turl: true,\n\t\t\tcontext: true\n\t\t}\n\t},\n\n\t// Creates a full fledged settings object into target\n\t// with both ajaxSettings and settings fields.\n\t// If target is omitted, writes into ajaxSettings.\n\tajaxSetup: function( target, settings ) {\n\t\treturn settings ?\n\n\t\t\t// Building a settings object\n\t\t\tajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :\n\n\t\t\t// Extending ajaxSettings\n\t\t\tajaxExtend( jQuery.ajaxSettings, target );\n\t},\n\n\tajaxPrefilter: addToPrefiltersOrTransports( prefilters ),\n\tajaxTransport: addToPrefiltersOrTransports( transports ),\n\n\t// Main method\n\tajax: function( url, options ) {\n\n\t\t// If url is an object, simulate pre-1.5 signature\n\t\tif ( typeof url === \"object\" ) {\n\t\t\toptions = url;\n\t\t\turl = undefined;\n\t\t}\n\n\t\t// Force options to be an object\n\t\toptions = options || {};\n\n\t\tvar transport,\n\t\t\t// URL without anti-cache param\n\t\t\tcacheURL,\n\t\t\t// Response headers\n\t\t\tresponseHeadersString,\n\t\t\tresponseHeaders,\n\t\t\t// timeout handle\n\t\t\ttimeoutTimer,\n\t\t\t// Cross-domain detection vars\n\t\t\tparts,\n\t\t\t// To know if global events are to be dispatched\n\t\t\tfireGlobals,\n\t\t\t// Loop variable\n\t\t\ti,\n\t\t\t// Create the final options object\n\t\t\ts = jQuery.ajaxSetup( {}, options ),\n\t\t\t// Callbacks context\n\t\t\tcallbackContext = s.context || s,\n\t\t\t// Context for global events is callbackContext if it is a DOM node or jQuery collection\n\t\t\tglobalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?\n\t\t\t\tjQuery( callbackContext ) :\n\t\t\t\tjQuery.event,\n\t\t\t// Deferreds\n\t\t\tdeferred = jQuery.Deferred(),\n\t\t\tcompleteDeferred = jQuery.Callbacks(\"once memory\"),\n\t\t\t// Status-dependent callbacks\n\t\t\tstatusCode = s.statusCode || {},\n\t\t\t// Headers (they are sent all at once)\n\t\t\trequestHeaders = {},\n\t\t\trequestHeadersNames = {},\n\t\t\t// The jqXHR state\n\t\t\tstate = 0,\n\t\t\t// Default abort message\n\t\t\tstrAbort = \"canceled\",\n\t\t\t// Fake xhr\n\t\t\tjqXHR = {\n\t\t\t\treadyState: 0,\n\n\t\t\t\t// Builds headers hashtable if needed\n\t\t\t\tgetResponseHeader: function( key ) {\n\t\t\t\t\tvar match;\n\t\t\t\t\tif ( state === 2 ) {\n\t\t\t\t\t\tif ( !responseHeaders ) {\n\t\t\t\t\t\t\tresponseHeaders = {};\n\t\t\t\t\t\t\twhile ( (match = rheaders.exec( responseHeadersString )) ) {\n\t\t\t\t\t\t\t\tresponseHeaders[ match[1].toLowerCase() ] = match[ 2 ];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmatch = responseHeaders[ key.toLowerCase() ];\n\t\t\t\t\t}\n\t\t\t\t\treturn match == null ? null : match;\n\t\t\t\t},\n\n\t\t\t\t// Raw string\n\t\t\t\tgetAllResponseHeaders: function() {\n\t\t\t\t\treturn state === 2 ? responseHeadersString : null;\n\t\t\t\t},\n\n\t\t\t\t// Caches the header\n\t\t\t\tsetRequestHeader: function( name, value ) {\n\t\t\t\t\tvar lname = name.toLowerCase();\n\t\t\t\t\tif ( !state ) {\n\t\t\t\t\t\tname = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;\n\t\t\t\t\t\trequestHeaders[ name ] = value;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Overrides response content-type header\n\t\t\t\toverrideMimeType: function( type ) {\n\t\t\t\t\tif ( !state ) {\n\t\t\t\t\t\ts.mimeType = type;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Status-dependent callbacks\n\t\t\t\tstatusCode: function( map ) {\n\t\t\t\t\tvar code;\n\t\t\t\t\tif ( map ) {\n\t\t\t\t\t\tif ( state < 2 ) {\n\t\t\t\t\t\t\tfor ( code in map ) {\n\t\t\t\t\t\t\t\t// Lazy-add the new callback in a way that preserves old ones\n\t\t\t\t\t\t\t\tstatusCode[ code ] = [ statusCode[ code ], map[ code ] ];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Execute the appropriate callbacks\n\t\t\t\t\t\t\tjqXHR.always( map[ jqXHR.status ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Cancel the request\n\t\t\t\tabort: function( statusText ) {\n\t\t\t\t\tvar finalText = statusText || strAbort;\n\t\t\t\t\tif ( transport ) {\n\t\t\t\t\t\ttransport.abort( finalText );\n\t\t\t\t\t}\n\t\t\t\t\tdone( 0, finalText );\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t};\n\n\t\t// Attach deferreds\n\t\tdeferred.promise( jqXHR ).complete = completeDeferred.add;\n\t\tjqXHR.success = jqXHR.done;\n\t\tjqXHR.error = jqXHR.fail;\n\n\t\t// Remove hash character (#7531: and string promotion)\n\t\t// Add protocol if not provided (prefilters might expect it)\n\t\t// Handle falsy url in the settings object (#10093: consistency with old signature)\n\t\t// We also use the url parameter if available\n\t\ts.url = ( ( url || s.url || ajaxLocation ) + \"\" ).replace( rhash, \"\" )\n\t\t\t.replace( rprotocol, ajaxLocParts[ 1 ] + \"//\" );\n\n\t\t// Alias method option to type as per ticket #12004\n\t\ts.type = options.method || options.type || s.method || s.type;\n\n\t\t// Extract dataTypes list\n\t\ts.dataTypes = jQuery.trim( s.dataType || \"*\" ).toLowerCase().match( rnotwhite ) || [ \"\" ];\n\n\t\t// A cross-domain request is in order when we have a protocol:host:port mismatch\n\t\tif ( s.crossDomain == null ) {\n\t\t\tparts = rurl.exec( s.url.toLowerCase() );\n\t\t\ts.crossDomain = !!( parts &&\n\t\t\t\t( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||\n\t\t\t\t\t( parts[ 3 ] || ( parts[ 1 ] === \"http:\" ? \"80\" : \"443\" ) ) !==\n\t\t\t\t\t\t( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === \"http:\" ? \"80\" : \"443\" ) ) )\n\t\t\t);\n\t\t}\n\n\t\t// Convert data if not already a string\n\t\tif ( s.data && s.processData && typeof s.data !== \"string\" ) {\n\t\t\ts.data = jQuery.param( s.data, s.traditional );\n\t\t}\n\n\t\t// Apply prefilters\n\t\tinspectPrefiltersOrTransports( prefilters, s, options, jqXHR );\n\n\t\t// If request was aborted inside a prefilter, stop there\n\t\tif ( state === 2 ) {\n\t\t\treturn jqXHR;\n\t\t}\n\n\t\t// We can fire global events as of now if asked to\n\t\tfireGlobals = s.global;\n\n\t\t// Watch for a new set of requests\n\t\tif ( fireGlobals && jQuery.active++ === 0 ) {\n\t\t\tjQuery.event.trigger(\"ajaxStart\");\n\t\t}\n\n\t\t// Uppercase the type\n\t\ts.type = s.type.toUpperCase();\n\n\t\t// Determine if request has content\n\t\ts.hasContent = !rnoContent.test( s.type );\n\n\t\t// Save the URL in case we're toying with the If-Modified-Since\n\t\t// and/or If-None-Match header later on\n\t\tcacheURL = s.url;\n\n\t\t// More options handling for requests with no content\n\t\tif ( !s.hasContent ) {\n\n\t\t\t// If data is available, append data to url\n\t\t\tif ( s.data ) {\n\t\t\t\tcacheURL = ( s.url += ( rquery.test( cacheURL ) ? \"&\" : \"?\" ) + s.data );\n\t\t\t\t// #9682: remove data so that it's not used in an eventual retry\n\t\t\t\tdelete s.data;\n\t\t\t}\n\n\t\t\t// Add anti-cache in url if needed\n\t\t\tif ( s.cache === false ) {\n\t\t\t\ts.url = rts.test( cacheURL ) ?\n\n\t\t\t\t\t// If there is already a '_' parameter, set its value\n\t\t\t\t\tcacheURL.replace( rts, \"$1_=\" + nonce++ ) :\n\n\t\t\t\t\t// Otherwise add one to the end\n\t\t\t\t\tcacheURL + ( rquery.test( cacheURL ) ? \"&\" : \"?\" ) + \"_=\" + nonce++;\n\t\t\t}\n\t\t}\n\n\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\tif ( s.ifModified ) {\n\t\t\tif ( jQuery.lastModified[ cacheURL ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-Modified-Since\", jQuery.lastModified[ cacheURL ] );\n\t\t\t}\n\t\t\tif ( jQuery.etag[ cacheURL ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-None-Match\", jQuery.etag[ cacheURL ] );\n\t\t\t}\n\t\t}\n\n\t\t// Set the correct header, if data is being sent\n\t\tif ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {\n\t\t\tjqXHR.setRequestHeader( \"Content-Type\", s.contentType );\n\t\t}\n\n\t\t// Set the Accepts header for the server, depending on the dataType\n\t\tjqXHR.setRequestHeader(\n\t\t\t\"Accept\",\n\t\t\ts.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?\n\t\t\t\ts.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== \"*\" ? \", \" + allTypes + \"; q=0.01\" : \"\" ) :\n\t\t\t\ts.accepts[ \"*\" ]\n\t\t);\n\n\t\t// Check for headers option\n\t\tfor ( i in s.headers ) {\n\t\t\tjqXHR.setRequestHeader( i, s.headers[ i ] );\n\t\t}\n\n\t\t// Allow custom headers/mimetypes and early abort\n\t\tif ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {\n\t\t\t// Abort if not done already and return\n\t\t\treturn jqXHR.abort();\n\t\t}\n\n\t\t// aborting is no longer a cancellation\n\t\tstrAbort = \"abort\";\n\n\t\t// Install callbacks on deferreds\n\t\tfor ( i in { success: 1, error: 1, complete: 1 } ) {\n\t\t\tjqXHR[ i ]( s[ i ] );\n\t\t}\n\n\t\t// Get transport\n\t\ttransport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );\n\n\t\t// If no transport, we auto-abort\n\t\tif ( !transport ) {\n\t\t\tdone( -1, \"No Transport\" );\n\t\t} else {\n\t\t\tjqXHR.readyState = 1;\n\n\t\t\t// Send global event\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxSend\", [ jqXHR, s ] );\n\t\t\t}\n\t\t\t// Timeout\n\t\t\tif ( s.async && s.timeout > 0 ) {\n\t\t\t\ttimeoutTimer = setTimeout(function() {\n\t\t\t\t\tjqXHR.abort(\"timeout\");\n\t\t\t\t}, s.timeout );\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tstate = 1;\n\t\t\t\ttransport.send( requestHeaders, done );\n\t\t\t} catch ( e ) {\n\t\t\t\t// Propagate exception as error if not done\n\t\t\t\tif ( state < 2 ) {\n\t\t\t\t\tdone( -1, e );\n\t\t\t\t// Simply rethrow otherwise\n\t\t\t\t} else {\n\t\t\t\t\tthrow e;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Callback for when everything is done\n\t\tfunction done( status, nativeStatusText, responses, headers ) {\n\t\t\tvar isSuccess, success, error, response, modified,\n\t\t\t\tstatusText = nativeStatusText;\n\n\t\t\t// Called once\n\t\t\tif ( state === 2 ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// State is \"done\" now\n\t\t\tstate = 2;\n\n\t\t\t// Clear timeout if it exists\n\t\t\tif ( timeoutTimer ) {\n\t\t\t\tclearTimeout( timeoutTimer );\n\t\t\t}\n\n\t\t\t// Dereference transport for early garbage collection\n\t\t\t// (no matter how long the jqXHR object will be used)\n\t\t\ttransport = undefined;\n\n\t\t\t// Cache response headers\n\t\t\tresponseHeadersString = headers || \"\";\n\n\t\t\t// Set readyState\n\t\t\tjqXHR.readyState = status > 0 ? 4 : 0;\n\n\t\t\t// Determine if successful\n\t\t\tisSuccess = status >= 200 && status < 300 || status === 304;\n\n\t\t\t// Get response data\n\t\t\tif ( responses ) {\n\t\t\t\tresponse = ajaxHandleResponses( s, jqXHR, responses );\n\t\t\t}\n\n\t\t\t// Convert no matter what (that way responseXXX fields are always set)\n\t\t\tresponse = ajaxConvert( s, response, jqXHR, isSuccess );\n\n\t\t\t// If successful, handle type chaining\n\t\t\tif ( isSuccess ) {\n\n\t\t\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\t\t\tif ( s.ifModified ) {\n\t\t\t\t\tmodified = jqXHR.getResponseHeader(\"Last-Modified\");\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.lastModified[ cacheURL ] = modified;\n\t\t\t\t\t}\n\t\t\t\t\tmodified = jqXHR.getResponseHeader(\"etag\");\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.etag[ cacheURL ] = modified;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// if no content\n\t\t\t\tif ( status === 204 || s.type === \"HEAD\" ) {\n\t\t\t\t\tstatusText = \"nocontent\";\n\n\t\t\t\t// if not modified\n\t\t\t\t} else if ( status === 304 ) {\n\t\t\t\t\tstatusText = \"notmodified\";\n\n\t\t\t\t// If we have data, let's convert it\n\t\t\t\t} else {\n\t\t\t\t\tstatusText = response.state;\n\t\t\t\t\tsuccess = response.data;\n\t\t\t\t\terror = response.error;\n\t\t\t\t\tisSuccess = !error;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// We extract error from statusText\n\t\t\t\t// then normalize statusText and status for non-aborts\n\t\t\t\terror = statusText;\n\t\t\t\tif ( status || !statusText ) {\n\t\t\t\t\tstatusText = \"error\";\n\t\t\t\t\tif ( status < 0 ) {\n\t\t\t\t\t\tstatus = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Set data for the fake xhr object\n\t\t\tjqXHR.status = status;\n\t\t\tjqXHR.statusText = ( nativeStatusText || statusText ) + \"\";\n\n\t\t\t// Success/Error\n\t\t\tif ( isSuccess ) {\n\t\t\t\tdeferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );\n\t\t\t} else {\n\t\t\t\tdeferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );\n\t\t\t}\n\n\t\t\t// Status-dependent callbacks\n\t\t\tjqXHR.statusCode( statusCode );\n\t\t\tstatusCode = undefined;\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( isSuccess ? \"ajaxSuccess\" : \"ajaxError\",\n\t\t\t\t\t[ jqXHR, s, isSuccess ? success : error ] );\n\t\t\t}\n\n\t\t\t// Complete\n\t\t\tcompleteDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxComplete\", [ jqXHR, s ] );\n\t\t\t\t// Handle the global AJAX counter\n\t\t\t\tif ( !( --jQuery.active ) ) {\n\t\t\t\t\tjQuery.event.trigger(\"ajaxStop\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn jqXHR;\n\t},\n\n\tgetJSON: function( url, data, callback ) {\n\t\treturn jQuery.get( url, data, callback, \"json\" );\n\t},\n\n\tgetScript: function( url, callback ) {\n\t\treturn jQuery.get( url, undefined, callback, \"script\" );\n\t}\n});\n\njQuery.each( [ \"get\", \"post\" ], function( i, method ) {\n\tjQuery[ method ] = function( url, data, callback, type ) {\n\t\t// shift arguments if data argument was omitted\n\t\tif ( jQuery.isFunction( data ) ) {\n\t\t\ttype = type || callback;\n\t\t\tcallback = data;\n\t\t\tdata = undefined;\n\t\t}\n\n\t\treturn jQuery.ajax({\n\t\t\turl: url,\n\t\t\ttype: method,\n\t\t\tdataType: type,\n\t\t\tdata: data,\n\t\t\tsuccess: callback\n\t\t});\n\t};\n});\n\n// Attach a bunch of functions for handling common AJAX events\njQuery.each( [ \"ajaxStart\", \"ajaxStop\", \"ajaxComplete\", \"ajaxError\", \"ajaxSuccess\", \"ajaxSend\" ], function( i, type ) {\n\tjQuery.fn[ type ] = function( fn ) {\n\t\treturn this.on( type, fn );\n\t};\n});\n\n\njQuery._evalUrl = function( url ) {\n\treturn jQuery.ajax({\n\t\turl: url,\n\t\ttype: \"GET\",\n\t\tdataType: \"script\",\n\t\tasync: false,\n\t\tglobal: false,\n\t\t\"throws\": true\n\t});\n};\n\n\njQuery.fn.extend({\n\twrapAll: function( html ) {\n\t\tvar wrap;\n\n\t\tif ( jQuery.isFunction( html ) ) {\n\t\t\treturn this.each(function( i ) {\n\t\t\t\tjQuery( this ).wrapAll( html.call(this, i) );\n\t\t\t});\n\t\t}\n\n\t\tif ( this[ 0 ] ) {\n\n\t\t\t// The elements to wrap the target around\n\t\t\twrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );\n\n\t\t\tif ( this[ 0 ].parentNode ) {\n\t\t\t\twrap.insertBefore( this[ 0 ] );\n\t\t\t}\n\n\t\t\twrap.map(function() {\n\t\t\t\tvar elem = this;\n\n\t\t\t\twhile ( elem.firstElementChild ) {\n\t\t\t\t\telem = elem.firstElementChild;\n\t\t\t\t}\n\n\t\t\t\treturn elem;\n\t\t\t}).append( this );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\twrapInner: function( html ) {\n\t\tif ( jQuery.isFunction( html ) ) {\n\t\t\treturn this.each(function( i ) {\n\t\t\t\tjQuery( this ).wrapInner( html.call(this, i) );\n\t\t\t});\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tvar self = jQuery( this ),\n\t\t\t\tcontents = self.contents();\n\n\t\t\tif ( contents.length ) {\n\t\t\t\tcontents.wrapAll( html );\n\n\t\t\t} else {\n\t\t\t\tself.append( html );\n\t\t\t}\n\t\t});\n\t},\n\n\twrap: function( html ) {\n\t\tvar isFunction = jQuery.isFunction( html );\n\n\t\treturn this.each(function( i ) {\n\t\t\tjQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );\n\t\t});\n\t},\n\n\tunwrap: function() {\n\t\treturn this.parent().each(function() {\n\t\t\tif ( !jQuery.nodeName( this, \"body\" ) ) {\n\t\t\t\tjQuery( this ).replaceWith( this.childNodes );\n\t\t\t}\n\t\t}).end();\n\t}\n});\n\n\njQuery.expr.filters.hidden = function( elem ) {\n\t// Support: Opera <= 12.12\n\t// Opera reports offsetWidths and offsetHeights less than zero on some elements\n\treturn elem.offsetWidth <= 0 && elem.offsetHeight <= 0;\n};\njQuery.expr.filters.visible = function( elem ) {\n\treturn !jQuery.expr.filters.hidden( elem );\n};\n\n\n\n\nvar r20 = /%20/g,\n\trbracket = /\\[\\]$/,\n\trCRLF = /\\r?\\n/g,\n\trsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,\n\trsubmittable = /^(?:input|select|textarea|keygen)/i;\n\nfunction buildParams( prefix, obj, traditional, add ) {\n\tvar name;\n\n\tif ( jQuery.isArray( obj ) ) {\n\t\t// Serialize array item.\n\t\tjQuery.each( obj, function( i, v ) {\n\t\t\tif ( traditional || rbracket.test( prefix ) ) {\n\t\t\t\t// Treat each array item as a scalar.\n\t\t\t\tadd( prefix, v );\n\n\t\t\t} else {\n\t\t\t\t// Item is non-scalar (array or object), encode its numeric index.\n\t\t\t\tbuildParams( prefix + \"[\" + ( typeof v === \"object\" ? i : \"\" ) + \"]\", v, traditional, add );\n\t\t\t}\n\t\t});\n\n\t} else if ( !traditional && jQuery.type( obj ) === \"object\" ) {\n\t\t// Serialize object item.\n\t\tfor ( name in obj ) {\n\t\t\tbuildParams( prefix + \"[\" + name + \"]\", obj[ name ], traditional, add );\n\t\t}\n\n\t} else {\n\t\t// Serialize scalar item.\n\t\tadd( prefix, obj );\n\t}\n}\n\n// Serialize an array of form elements or a set of\n// key/values into a query string\njQuery.param = function( a, traditional ) {\n\tvar prefix,\n\t\ts = [],\n\t\tadd = function( key, value ) {\n\t\t\t// If value is a function, invoke it and return its value\n\t\t\tvalue = jQuery.isFunction( value ) ? value() : ( value == null ? \"\" : value );\n\t\t\ts[ s.length ] = encodeURIComponent( key ) + \"=\" + encodeURIComponent( value );\n\t\t};\n\n\t// Set traditional to true for jQuery <= 1.3.2 behavior.\n\tif ( traditional === undefined ) {\n\t\ttraditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;\n\t}\n\n\t// If an array was passed in, assume that it is an array of form elements.\n\tif ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {\n\t\t// Serialize the form elements\n\t\tjQuery.each( a, function() {\n\t\t\tadd( this.name, this.value );\n\t\t});\n\n\t} else {\n\t\t// If traditional, encode the \"old\" way (the way 1.3.2 or older\n\t\t// did it), otherwise encode params recursively.\n\t\tfor ( prefix in a ) {\n\t\t\tbuildParams( prefix, a[ prefix ], traditional, add );\n\t\t}\n\t}\n\n\t// Return the resulting serialization\n\treturn s.join( \"&\" ).replace( r20, \"+\" );\n};\n\njQuery.fn.extend({\n\tserialize: function() {\n\t\treturn jQuery.param( this.serializeArray() );\n\t},\n\tserializeArray: function() {\n\t\treturn this.map(function() {\n\t\t\t// Can add propHook for \"elements\" to filter or add form elements\n\t\t\tvar elements = jQuery.prop( this, \"elements\" );\n\t\t\treturn elements ? jQuery.makeArray( elements ) : this;\n\t\t})\n\t\t.filter(function() {\n\t\t\tvar type = this.type;\n\n\t\t\t// Use .is( \":disabled\" ) so that fieldset[disabled] works\n\t\t\treturn this.name && !jQuery( this ).is( \":disabled\" ) &&\n\t\t\t\trsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&\n\t\t\t\t( this.checked || !rcheckableType.test( type ) );\n\t\t})\n\t\t.map(function( i, elem ) {\n\t\t\tvar val = jQuery( this ).val();\n\n\t\t\treturn val == null ?\n\t\t\t\tnull :\n\t\t\t\tjQuery.isArray( val ) ?\n\t\t\t\t\tjQuery.map( val, function( val ) {\n\t\t\t\t\t\treturn { name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t\t\t\t}) :\n\t\t\t\t\t{ name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t}).get();\n\t}\n});\n\n\njQuery.ajaxSettings.xhr = function() {\n\ttry {\n\t\treturn new XMLHttpRequest();\n\t} catch( e ) {}\n};\n\nvar xhrId = 0,\n\txhrCallbacks = {},\n\txhrSuccessStatus = {\n\t\t// file protocol always yields status code 0, assume 200\n\t\t0: 200,\n\t\t// Support: IE9\n\t\t// #1450: sometimes IE returns 1223 when it should be 204\n\t\t1223: 204\n\t},\n\txhrSupported = jQuery.ajaxSettings.xhr();\n\n// Support: IE9\n// Open requests must be manually aborted on unload (#5280)\nif ( window.ActiveXObject ) {\n\tjQuery( window ).on( \"unload\", function() {\n\t\tfor ( var key in xhrCallbacks ) {\n\t\t\txhrCallbacks[ key ]();\n\t\t}\n\t});\n}\n\nsupport.cors = !!xhrSupported && ( \"withCredentials\" in xhrSupported );\nsupport.ajax = xhrSupported = !!xhrSupported;\n\njQuery.ajaxTransport(function( options ) {\n\tvar callback;\n\n\t// Cross domain only allowed if supported through XMLHttpRequest\n\tif ( support.cors || xhrSupported && !options.crossDomain ) {\n\t\treturn {\n\t\t\tsend: function( headers, complete ) {\n\t\t\t\tvar i,\n\t\t\t\t\txhr = options.xhr(),\n\t\t\t\t\tid = ++xhrId;\n\n\t\t\t\txhr.open( options.type, options.url, options.async, options.username, options.password );\n\n\t\t\t\t// Apply custom fields if provided\n\t\t\t\tif ( options.xhrFields ) {\n\t\t\t\t\tfor ( i in options.xhrFields ) {\n\t\t\t\t\t\txhr[ i ] = options.xhrFields[ i ];\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Override mime type if needed\n\t\t\t\tif ( options.mimeType && xhr.overrideMimeType ) {\n\t\t\t\t\txhr.overrideMimeType( options.mimeType );\n\t\t\t\t}\n\n\t\t\t\t// X-Requested-With header\n\t\t\t\t// For cross-domain requests, seeing as conditions for a preflight are\n\t\t\t\t// akin to a jigsaw puzzle, we simply never set it to be sure.\n\t\t\t\t// (it can always be set on a per-request basis or even using ajaxSetup)\n\t\t\t\t// For same-domain requests, won't change header if already provided.\n\t\t\t\tif ( !options.crossDomain && !headers[\"X-Requested-With\"] ) {\n\t\t\t\t\theaders[\"X-Requested-With\"] = \"XMLHttpRequest\";\n\t\t\t\t}\n\n\t\t\t\t// Set headers\n\t\t\t\tfor ( i in headers ) {\n\t\t\t\t\txhr.setRequestHeader( i, headers[ i ] );\n\t\t\t\t}\n\n\t\t\t\t// Callback\n\t\t\t\tcallback = function( type ) {\n\t\t\t\t\treturn function() {\n\t\t\t\t\t\tif ( callback ) {\n\t\t\t\t\t\t\tdelete xhrCallbacks[ id ];\n\t\t\t\t\t\t\tcallback = xhr.onload = xhr.onerror = null;\n\n\t\t\t\t\t\t\tif ( type === \"abort\" ) {\n\t\t\t\t\t\t\t\txhr.abort();\n\t\t\t\t\t\t\t} else if ( type === \"error\" ) {\n\t\t\t\t\t\t\t\tcomplete(\n\t\t\t\t\t\t\t\t\t// file: protocol always yields status 0; see #8605, #14207\n\t\t\t\t\t\t\t\t\txhr.status,\n\t\t\t\t\t\t\t\t\txhr.statusText\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tcomplete(\n\t\t\t\t\t\t\t\t\txhrSuccessStatus[ xhr.status ] || xhr.status,\n\t\t\t\t\t\t\t\t\txhr.statusText,\n\t\t\t\t\t\t\t\t\t// Support: IE9\n\t\t\t\t\t\t\t\t\t// Accessing binary-data responseText throws an exception\n\t\t\t\t\t\t\t\t\t// (#11426)\n\t\t\t\t\t\t\t\t\ttypeof xhr.responseText === \"string\" ? {\n\t\t\t\t\t\t\t\t\t\ttext: xhr.responseText\n\t\t\t\t\t\t\t\t\t} : undefined,\n\t\t\t\t\t\t\t\t\txhr.getAllResponseHeaders()\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\t\t\t\t};\n\n\t\t\t\t// Listen to events\n\t\t\t\txhr.onload = callback();\n\t\t\t\txhr.onerror = callback(\"error\");\n\n\t\t\t\t// Create the abort callback\n\t\t\t\tcallback = xhrCallbacks[ id ] = callback(\"abort\");\n\n\t\t\t\t// Do send the request\n\t\t\t\t// This may raise an exception which is actually\n\t\t\t\t// handled in jQuery.ajax (so no try/catch here)\n\t\t\t\txhr.send( options.hasContent && options.data || null );\n\t\t\t},\n\n\t\t\tabort: function() {\n\t\t\t\tif ( callback ) {\n\t\t\t\t\tcallback();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n});\n\n\n\n\n// Install script dataType\njQuery.ajaxSetup({\n\taccepts: {\n\t\tscript: \"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"\n\t},\n\tcontents: {\n\t\tscript: /(?:java|ecma)script/\n\t},\n\tconverters: {\n\t\t\"text script\": function( text ) {\n\t\t\tjQuery.globalEval( text );\n\t\t\treturn text;\n\t\t}\n\t}\n});\n\n// Handle cache's special case and crossDomain\njQuery.ajaxPrefilter( \"script\", function( s ) {\n\tif ( s.cache === undefined ) {\n\t\ts.cache = false;\n\t}\n\tif ( s.crossDomain ) {\n\t\ts.type = \"GET\";\n\t}\n});\n\n// Bind script tag hack transport\njQuery.ajaxTransport( \"script\", function( s ) {\n\t// This transport only deals with cross domain requests\n\tif ( s.crossDomain ) {\n\t\tvar script, callback;\n\t\treturn {\n\t\t\tsend: function( _, complete ) {\n\t\t\t\tscript = jQuery(\"<script>\").prop({\n\t\t\t\t\tasync: true,\n\t\t\t\t\tcharset: s.scriptCharset,\n\t\t\t\t\tsrc: s.url\n\t\t\t\t}).on(\n\t\t\t\t\t\"load error\",\n\t\t\t\t\tcallback = function( evt ) {\n\t\t\t\t\t\tscript.remove();\n\t\t\t\t\t\tcallback = null;\n\t\t\t\t\t\tif ( evt ) {\n\t\t\t\t\t\t\tcomplete( evt.type === \"error\" ? 404 : 200, evt.type );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t\tdocument.head.appendChild( script[ 0 ] );\n\t\t\t},\n\t\t\tabort: function() {\n\t\t\t\tif ( callback ) {\n\t\t\t\t\tcallback();\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n});\n\n\n\n\nvar oldCallbacks = [],\n\trjsonp = /(=)\\?(?=&|$)|\\?\\?/;\n\n// Default jsonp settings\njQuery.ajaxSetup({\n\tjsonp: \"callback\",\n\tjsonpCallback: function() {\n\t\tvar callback = oldCallbacks.pop() || ( jQuery.expando + \"_\" + ( nonce++ ) );\n\t\tthis[ callback ] = true;\n\t\treturn callback;\n\t}\n});\n\n// Detect, normalize options and install callbacks for jsonp requests\njQuery.ajaxPrefilter( \"json jsonp\", function( s, originalSettings, jqXHR ) {\n\n\tvar callbackName, overwritten, responseContainer,\n\t\tjsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?\n\t\t\t\"url\" :\n\t\t\ttypeof s.data === \"string\" && !( s.contentType || \"\" ).indexOf(\"application/x-www-form-urlencoded\") && rjsonp.test( s.data ) && \"data\"\n\t\t);\n\n\t// Handle iff the expected data type is \"jsonp\" or we have a parameter to set\n\tif ( jsonProp || s.dataTypes[ 0 ] === \"jsonp\" ) {\n\n\t\t// Get callback name, remembering preexisting value associated with it\n\t\tcallbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?\n\t\t\ts.jsonpCallback() :\n\t\t\ts.jsonpCallback;\n\n\t\t// Insert callback into url or form data\n\t\tif ( jsonProp ) {\n\t\t\ts[ jsonProp ] = s[ jsonProp ].replace( rjsonp, \"$1\" + callbackName );\n\t\t} else if ( s.jsonp !== false ) {\n\t\t\ts.url += ( rquery.test( s.url ) ? \"&\" : \"?\" ) + s.jsonp + \"=\" + callbackName;\n\t\t}\n\n\t\t// Use data converter to retrieve json after script execution\n\t\ts.converters[\"script json\"] = function() {\n\t\t\tif ( !responseContainer ) {\n\t\t\t\tjQuery.error( callbackName + \" was not called\" );\n\t\t\t}\n\t\t\treturn responseContainer[ 0 ];\n\t\t};\n\n\t\t// force json dataType\n\t\ts.dataTypes[ 0 ] = \"json\";\n\n\t\t// Install callback\n\t\toverwritten = window[ callbackName ];\n\t\twindow[ callbackName ] = function() {\n\t\t\tresponseContainer = arguments;\n\t\t};\n\n\t\t// Clean-up function (fires after converters)\n\t\tjqXHR.always(function() {\n\t\t\t// Restore preexisting value\n\t\t\twindow[ callbackName ] = overwritten;\n\n\t\t\t// Save back as free\n\t\t\tif ( s[ callbackName ] ) {\n\t\t\t\t// make sure that re-using the options doesn't screw things around\n\t\t\t\ts.jsonpCallback = originalSettings.jsonpCallback;\n\n\t\t\t\t// save the callback name for future use\n\t\t\t\toldCallbacks.push( callbackName );\n\t\t\t}\n\n\t\t\t// Call if it was a function and we have a response\n\t\t\tif ( responseContainer && jQuery.isFunction( overwritten ) ) {\n\t\t\t\toverwritten( responseContainer[ 0 ] );\n\t\t\t}\n\n\t\t\tresponseContainer = overwritten = undefined;\n\t\t});\n\n\t\t// Delegate to script\n\t\treturn \"script\";\n\t}\n});\n\n\n\n\n// data: string of html\n// context (optional): If specified, the fragment will be created in this context, defaults to document\n// keepScripts (optional): If true, will include scripts passed in the html string\njQuery.parseHTML = function( data, context, keepScripts ) {\n\tif ( !data || typeof data !== \"string\" ) {\n\t\treturn null;\n\t}\n\tif ( typeof context === \"boolean\" ) {\n\t\tkeepScripts = context;\n\t\tcontext = false;\n\t}\n\tcontext = context || document;\n\n\tvar parsed = rsingleTag.exec( data ),\n\t\tscripts = !keepScripts && [];\n\n\t// Single tag\n\tif ( parsed ) {\n\t\treturn [ context.createElement( parsed[1] ) ];\n\t}\n\n\tparsed = jQuery.buildFragment( [ data ], context, scripts );\n\n\tif ( scripts && scripts.length ) {\n\t\tjQuery( scripts ).remove();\n\t}\n\n\treturn jQuery.merge( [], parsed.childNodes );\n};\n\n\n// Keep a copy of the old load method\nvar _load = jQuery.fn.load;\n\n/**\n * Load a url into a page\n */\njQuery.fn.load = function( url, params, callback ) {\n\tif ( typeof url !== \"string\" && _load ) {\n\t\treturn _load.apply( this, arguments );\n\t}\n\n\tvar selector, type, response,\n\t\tself = this,\n\t\toff = url.indexOf(\" \");\n\n\tif ( off >= 0 ) {\n\t\tselector = url.slice( off );\n\t\turl = url.slice( 0, off );\n\t}\n\n\t// If it's a function\n\tif ( jQuery.isFunction( params ) ) {\n\n\t\t// We assume that it's the callback\n\t\tcallback = params;\n\t\tparams = undefined;\n\n\t// Otherwise, build a param string\n\t} else if ( params && typeof params === \"object\" ) {\n\t\ttype = \"POST\";\n\t}\n\n\t// If we have elements to modify, make the request\n\tif ( self.length > 0 ) {\n\t\tjQuery.ajax({\n\t\t\turl: url,\n\n\t\t\t// if \"type\" variable is undefined, then \"GET\" method will be used\n\t\t\ttype: type,\n\t\t\tdataType: \"html\",\n\t\t\tdata: params\n\t\t}).done(function( responseText ) {\n\n\t\t\t// Save response for use in complete callback\n\t\t\tresponse = arguments;\n\n\t\t\tself.html( selector ?\n\n\t\t\t\t// If a selector was specified, locate the right elements in a dummy div\n\t\t\t\t// Exclude scripts to avoid IE 'Permission Denied' errors\n\t\t\t\tjQuery(\"<div>\").append( jQuery.parseHTML( responseText ) ).find( selector ) :\n\n\t\t\t\t// Otherwise use the full result\n\t\t\t\tresponseText );\n\n\t\t}).complete( callback && function( jqXHR, status ) {\n\t\t\tself.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );\n\t\t});\n\t}\n\n\treturn this;\n};\n\n\n\n\njQuery.expr.filters.animated = function( elem ) {\n\treturn jQuery.grep(jQuery.timers, function( fn ) {\n\t\treturn elem === fn.elem;\n\t}).length;\n};\n\n\n\n\nvar docElem = window.document.documentElement;\n\n/**\n * Gets a window from an element\n */\nfunction getWindow( elem ) {\n\treturn jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView;\n}\n\njQuery.offset = {\n\tsetOffset: function( elem, options, i ) {\n\t\tvar curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,\n\t\t\tposition = jQuery.css( elem, \"position\" ),\n\t\t\tcurElem = jQuery( elem ),\n\t\t\tprops = {};\n\n\t\t// Set position first, in-case top/left are set even on static elem\n\t\tif ( position === \"static\" ) {\n\t\t\telem.style.position = \"relative\";\n\t\t}\n\n\t\tcurOffset = curElem.offset();\n\t\tcurCSSTop = jQuery.css( elem, \"top\" );\n\t\tcurCSSLeft = jQuery.css( elem, \"left\" );\n\t\tcalculatePosition = ( position === \"absolute\" || position === \"fixed\" ) &&\n\t\t\t( curCSSTop + curCSSLeft ).indexOf(\"auto\") > -1;\n\n\t\t// Need to be able to calculate position if either top or left is auto and position is either absolute or fixed\n\t\tif ( calculatePosition ) {\n\t\t\tcurPosition = curElem.position();\n\t\t\tcurTop = curPosition.top;\n\t\t\tcurLeft = curPosition.left;\n\n\t\t} else {\n\t\t\tcurTop = parseFloat( curCSSTop ) || 0;\n\t\t\tcurLeft = parseFloat( curCSSLeft ) || 0;\n\t\t}\n\n\t\tif ( jQuery.isFunction( options ) ) {\n\t\t\toptions = options.call( elem, i, curOffset );\n\t\t}\n\n\t\tif ( options.top != null ) {\n\t\t\tprops.top = ( options.top - curOffset.top ) + curTop;\n\t\t}\n\t\tif ( options.left != null ) {\n\t\t\tprops.left = ( options.left - curOffset.left ) + curLeft;\n\t\t}\n\n\t\tif ( \"using\" in options ) {\n\t\t\toptions.using.call( elem, props );\n\n\t\t} else {\n\t\t\tcurElem.css( props );\n\t\t}\n\t}\n};\n\njQuery.fn.extend({\n\toffset: function( options ) {\n\t\tif ( arguments.length ) {\n\t\t\treturn options === undefined ?\n\t\t\t\tthis :\n\t\t\t\tthis.each(function( i ) {\n\t\t\t\t\tjQuery.offset.setOffset( this, options, i );\n\t\t\t\t});\n\t\t}\n\n\t\tvar docElem, win,\n\t\t\telem = this[ 0 ],\n\t\t\tbox = { top: 0, left: 0 },\n\t\t\tdoc = elem && elem.ownerDocument;\n\n\t\tif ( !doc ) {\n\t\t\treturn;\n\t\t}\n\n\t\tdocElem = doc.documentElement;\n\n\t\t// Make sure it's not a disconnected DOM node\n\t\tif ( !jQuery.contains( docElem, elem ) ) {\n\t\t\treturn box;\n\t\t}\n\n\t\t// If we don't have gBCR, just use 0,0 rather than error\n\t\t// BlackBerry 5, iOS 3 (original iPhone)\n\t\tif ( typeof elem.getBoundingClientRect !== strundefined ) {\n\t\t\tbox = elem.getBoundingClientRect();\n\t\t}\n\t\twin = getWindow( doc );\n\t\treturn {\n\t\t\ttop: box.top + win.pageYOffset - docElem.clientTop,\n\t\t\tleft: box.left + win.pageXOffset - docElem.clientLeft\n\t\t};\n\t},\n\n\tposition: function() {\n\t\tif ( !this[ 0 ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar offsetParent, offset,\n\t\t\telem = this[ 0 ],\n\t\t\tparentOffset = { top: 0, left: 0 };\n\n\t\t// Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is its only offset parent\n\t\tif ( jQuery.css( elem, \"position\" ) === \"fixed\" ) {\n\t\t\t// We assume that getBoundingClientRect is available when computed position is fixed\n\t\t\toffset = elem.getBoundingClientRect();\n\n\t\t} else {\n\t\t\t// Get *real* offsetParent\n\t\t\toffsetParent = this.offsetParent();\n\n\t\t\t// Get correct offsets\n\t\t\toffset = this.offset();\n\t\t\tif ( !jQuery.nodeName( offsetParent[ 0 ], \"html\" ) ) {\n\t\t\t\tparentOffset = offsetParent.offset();\n\t\t\t}\n\n\t\t\t// Add offsetParent borders\n\t\t\tparentOffset.top += jQuery.css( offsetParent[ 0 ], \"borderTopWidth\", true );\n\t\t\tparentOffset.left += jQuery.css( offsetParent[ 0 ], \"borderLeftWidth\", true );\n\t\t}\n\n\t\t// Subtract parent offsets and element margins\n\t\treturn {\n\t\t\ttop: offset.top - parentOffset.top - jQuery.css( elem, \"marginTop\", true ),\n\t\t\tleft: offset.left - parentOffset.left - jQuery.css( elem, \"marginLeft\", true )\n\t\t};\n\t},\n\n\toffsetParent: function() {\n\t\treturn this.map(function() {\n\t\t\tvar offsetParent = this.offsetParent || docElem;\n\n\t\t\twhile ( offsetParent && ( !jQuery.nodeName( offsetParent, \"html\" ) && jQuery.css( offsetParent, \"position\" ) === \"static\" ) ) {\n\t\t\t\toffsetParent = offsetParent.offsetParent;\n\t\t\t}\n\n\t\t\treturn offsetParent || docElem;\n\t\t});\n\t}\n});\n\n// Create scrollLeft and scrollTop methods\njQuery.each( { scrollLeft: \"pageXOffset\", scrollTop: \"pageYOffset\" }, function( method, prop ) {\n\tvar top = \"pageYOffset\" === prop;\n\n\tjQuery.fn[ method ] = function( val ) {\n\t\treturn access( this, function( elem, method, val ) {\n\t\t\tvar win = getWindow( elem );\n\n\t\t\tif ( val === undefined ) {\n\t\t\t\treturn win ? win[ prop ] : elem[ method ];\n\t\t\t}\n\n\t\t\tif ( win ) {\n\t\t\t\twin.scrollTo(\n\t\t\t\t\t!top ? val : window.pageXOffset,\n\t\t\t\t\ttop ? val : window.pageYOffset\n\t\t\t\t);\n\n\t\t\t} else {\n\t\t\t\telem[ method ] = val;\n\t\t\t}\n\t\t}, method, val, arguments.length, null );\n\t};\n});\n\n// Add the top/left cssHooks using jQuery.fn.position\n// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084\n// getComputedStyle returns percent when specified for top/left/bottom/right\n// rather than make the css module depend on the offset module, we just check for it here\njQuery.each( [ \"top\", \"left\" ], function( i, prop ) {\n\tjQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,\n\t\tfunction( elem, computed ) {\n\t\t\tif ( computed ) {\n\t\t\t\tcomputed = curCSS( elem, prop );\n\t\t\t\t// if curCSS returns percentage, fallback to offset\n\t\t\t\treturn rnumnonpx.test( computed ) ?\n\t\t\t\t\tjQuery( elem ).position()[ prop ] + \"px\" :\n\t\t\t\t\tcomputed;\n\t\t\t}\n\t\t}\n\t);\n});\n\n\n// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods\njQuery.each( { Height: \"height\", Width: \"width\" }, function( name, type ) {\n\tjQuery.each( { padding: \"inner\" + name, content: type, \"\": \"outer\" + name }, function( defaultExtra, funcName ) {\n\t\t// margin is only for outerHeight, outerWidth\n\t\tjQuery.fn[ funcName ] = function( margin, value ) {\n\t\t\tvar chainable = arguments.length && ( defaultExtra || typeof margin !== \"boolean\" ),\n\t\t\t\textra = defaultExtra || ( margin === true || value === true ? \"margin\" : \"border\" );\n\n\t\t\treturn access( this, function( elem, type, value ) {\n\t\t\t\tvar doc;\n\n\t\t\t\tif ( jQuery.isWindow( elem ) ) {\n\t\t\t\t\t// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there\n\t\t\t\t\t// isn't a whole lot we can do. See pull request at this URL for discussion:\n\t\t\t\t\t// https://github.com/jquery/jquery/pull/764\n\t\t\t\t\treturn elem.document.documentElement[ \"client\" + name ];\n\t\t\t\t}\n\n\t\t\t\t// Get document width or height\n\t\t\t\tif ( elem.nodeType === 9 ) {\n\t\t\t\t\tdoc = elem.documentElement;\n\n\t\t\t\t\t// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],\n\t\t\t\t\t// whichever is greatest\n\t\t\t\t\treturn Math.max(\n\t\t\t\t\t\telem.body[ \"scroll\" + name ], doc[ \"scroll\" + name ],\n\t\t\t\t\t\telem.body[ \"offset\" + name ], doc[ \"offset\" + name ],\n\t\t\t\t\t\tdoc[ \"client\" + name ]\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn value === undefined ?\n\t\t\t\t\t// Get width or height on the element, requesting but not forcing parseFloat\n\t\t\t\t\tjQuery.css( elem, type, extra ) :\n\n\t\t\t\t\t// Set width or height on the element\n\t\t\t\t\tjQuery.style( elem, type, value, extra );\n\t\t\t}, type, chainable ? margin : undefined, chainable, null );\n\t\t};\n\t});\n});\n\n\n// The number of elements contained in the matched element set\njQuery.fn.size = function() {\n\treturn this.length;\n};\n\njQuery.fn.andSelf = jQuery.fn.addBack;\n\n\n\n\n// Register as a named AMD module, since jQuery can be concatenated with other\n// files that may use define, but not via a proper concatenation script that\n// understands anonymous AMD modules. A named AMD is safest and most robust\n// way to register. Lowercase jquery is used because AMD module names are\n// derived from file names, and jQuery is normally delivered in a lowercase\n// file name. Do this after creating the global so that if an AMD module wants\n// to call noConflict to hide this version of jQuery, it will work.\nif ( typeof define === \"function\" && define.amd ) {\n\tdefine( \"jquery\", [], function() {\n\t\treturn jQuery;\n\t});\n}\n\n\n\n\nvar\n\t// Map over jQuery in case of overwrite\n\t_jQuery = window.jQuery,\n\n\t// Map over the $ in case of overwrite\n\t_$ = window.$;\n\njQuery.noConflict = function( deep ) {\n\tif ( window.$ === jQuery ) {\n\t\twindow.$ = _$;\n\t}\n\n\tif ( deep && window.jQuery === jQuery ) {\n\t\twindow.jQuery = _jQuery;\n\t}\n\n\treturn jQuery;\n};\n\n// Expose jQuery and $ identifiers, even in\n// AMD (#7102#comment:10, https://github.com/jquery/jquery/pull/557)\n// and CommonJS for browser emulators (#13566)\nif ( typeof noGlobal === strundefined ) {\n\twindow.jQuery = window.$ = jQuery;\n}\n\n\n\n\nreturn jQuery;\n\n}));\n"
  },
  {
    "path": "notes/git-tutorial.md",
    "content": "# Git Tutorial\n\n这里 Git 学习指引，包含如下几个模块，将用最极简的语言带你进入 Git 版本控制的世界。\n\n| 部分 | 章节                           | 概要                         |\n| :--- | :----------------------------- | :--------------------------- |\n| Ⅰ    | [Git 基础知识](Git.md)         | 发展历史、核心概念、场景应用 |\n| Ⅱ    | [Git 命令速查](Git命令速查.md) | 命令大全，方便随时查询       |\n| Ⅲ    | [Git 工作流](Git工作流.md)     | 协同开发工作流               |\n\n\n\n## 参考资料\n\n- 极客时间苏玲《玩转Git三剑客》\n- [Git 工作流程 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2015/12/git-workflow.html)\n- [git使用 · lzy的Bbbblog](https://o0chivas0o.github.io/2018/12/21/git/)\n\n"
  },
  {
    "path": "notes/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <!-- <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, minimum-scale=1.0, shrink-to-fit=no, viewport-fit=cover\"> -->\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta name=\"description\" content=\"A delightfully simple theme system for docsify.js. Features multiple themes with rich customization options, an improved desktop and mobile experience, and legacy browser support (IE10+).\">\n    <meta name=\"google-site-verification\" content=\"_EWJaM8G6xwMtEqemO4yN8WqSU2CaKVi3jTj0gLg48E\">\n    <title>fullstack tutorial - 全栈开发指南</title>\n\n    <!-- Stylesheets -->\n    <link rel=\"stylesheet\" href=\"docsify/unpkg/docsify-themeable/dist/css/theme-simple.css\" title=\"Simple\">\n    <link rel=\"stylesheet\" href=\"docsify/css/main.css\">\n\n    <!-- Alternate Stylesheets -->\n    <link rel=\"stylesheet alternate\" href=\"docsify/unpkg/docsify-themeable/dist/css/theme-defaults.css\" title=\"Defaults\">\n    <link rel=\"stylesheet alternate\" href=\"docsify/unpkg/docsify-themeable/dist/css/theme-simple-dark.css\" title=\"Simple Dark\">\n\n    <!-- Test Stylesheets -->\n    <!-- <link rel=\"stylesheet\" href=\"//unpkg.com/docsify/themes/vue.css\"> -->\n    <!-- <link rel=\"stylesheet\" href=\"//unpkg.com/docsify/themes/buble.css\"> -->\n    <!-- <link rel=\"stylesheet\" href=\"//unpkg.com/docsify/themes/dark.css\"> -->\n    <!-- <link rel=\"stylesheet\" href=\"//unpkg.com/docsify/themes/pure.css\"> -->\n    <style>\n        .sidebar-nav a,\n        .sidebar nav a {\n            white-space: unset !important;\n        }\n\n        .sidebar-nav ul li ul {\n            font-size: 14px;\n        }\n\n        .sidebar-nav ul li strong {\n            font-size: 17px;\n        }\n\n        h1#fullstack-tutorial span {\n            color: hsl(204, 90%, 45%);\n        }\n\n        article#main a {\n            color: hsl(204, 90%, 45%);\n        }\n        /* 隐藏 Github TOC 目录 */\n        .markdown-section :first-child li{\n            display: none;\n        }\n        /* gittalk padding */\n        .gt-container {\n            /* padding-left: 20px;\n            padding-right: 20px; */\n            padding: 2rem 35px;\n        }\n        /* 隐藏 gitalk 版本号 */\n        .gt-copyright {\n            display: none;\n        }\n        /* 正文CSS padding */\n        article#main {\n            padding: 2rem 35px;\n        }\n        /* 访问量显示 */\n        span#la_19815069 a {\n            text-decoration: none;\n            pointer-events: none;\n        }\n    </style>\n</head>\n\n<body>\n    <div id=\"app\"></div>\n\n    <!-- JavaScript -->\n    <script>\n        window.$docsify = {\n            loadNavbar: true,\n            loadNavbar: 'docsify/navbar.md',\n            // basePath: '/docs/notes',\n            // GENERAL\n            // -----------------------------------------------------------------\n            name: 'fullstack tutorial',\n            repo: 'https://github.com/frank-lam/fullstack-tutorial',\n            coverpage: 'docsify/coverpage.md',\n            homepage: 'introduction.md',\n            loadSidebar: 'docsify/sidebar.md',\n\n            // NAVIGATION\n            // -----------------------------------------------------------------\n            alias: {\n                '.*?/changelog': 'https://unpkg.com/docsify-themeable/CHANGELOG.md',\n            },\n            auto2top: true,\n            maxLevel: 3,\n            subMaxLevel: 2,\n\n            // PLUGINS\n            // -----------------------------------------------------------------\n            executeScript: true,\n            ga: 'UA-100100910-3',\n            search: {\n                depth: 6,\n                noData: 'No results!',\n                placeholder: 'Search ...'\n            }\n        };\n    </script>\n\n    <script src=\"docsify/js/main.js\"></script>\n\n    <script src=\"docsify/unpkg/docsify/lib/plugins/emoji.js\"></script>\n    <script src=\"docsify/unpkg/docsify/docsify.min.js\"></script>\n    <script src=\"docsify/unpkg/docsify-themeable.min.js\"></script>\n    <script src=\"docsify/unpkg/docsify-tabs.min.js\"></script>\n    <script src=\"docsify/unpkg/docsify-copy-code.min.js\"></script>\n\n    <script src=\"docsify/unpkg/docsify-pagination/dist/docsify-pagination.min.js\"></script>\n\n    <!-- <script src=\"https://unpkg/docsify/lib/plugins/external-script.min.js\"></script> -->\n    <script src=\"docsify/unpkg/docsify/lib/plugins/ga.min.js\"></script>\n    <script src=\"docsify/unpkg/docsify/lib/plugins/search.js\"></script>\n    <script src=\"docsify/unpkg/docsify/lib/plugins/zoom-image.min.js\"></script>\n    <script src=\"docsify/unpkg/prismjs/components/prism-bash.min.js\"></script>\n\n    <link rel=\"stylesheet\" href=\"docsify/unpkg/gitalk/dist/gitalk.css\">\n\n    <script src=\"docsify/unpkg/docsify/lib/plugins/gitalk.min.js\"></script>\n    <script src=\"docsify/unpkg/gitalk/dist/gitalk.min.js\"></script>\n    <script>\n        const gitalk = new Gitalk({\n            clientID: '67ba8c5b2ac4f27e47e1',\n            clientSecret: 'bea349f1e21ab64348ddb365901b3f6daf9f0697',\n            repo: 'fullstack-tutorial-gitalk',\n            owner: 'frank-lam',\n            admin: ['frank-lam'],\n            id: location.pathname,\n            // distractionFreeMode: false\n        })\n    </script>\n\n    <script type=\"text/javascript\" src=\"//js.users.51.la/19815069.js\"></script>\n\n    <script src=\"docsify/unpkg/gotop/jquery-2.1.0.js\" charset=\"utf-8\"></script>\n    <script src=\"docsify/unpkg/gotop/jquery.toTop.min.js\" charset=\"utf-8\"></script>\n\n    <script>\n        $('.footer--text').prepend(`<span>${$('.footer--text a').text()}</span>`)\n        $('.footer--text a').remove()\n        $(function () {\n          $('.to-top').toTop();\n        });\n    </script>\n    <a class=\"to-top\" style=\"z-index:2000;font-size: 35px;color:#0e90d2;\">🔝</a>\n</body>\n</html>"
  },
  {
    "path": "notes/introduction.md",
    "content": "![full stack developer tutorial](https://raw.githubusercontent.com/frank-lam/fullstack-tutorial/master/assets/1537592021705.png)\n\n\n<div align=\"center\">  \n    <p>\n        嗨，欢迎来做客，即刻开始 CS 学习之旅.\n    </p>\n    <p>\n        Hey, welcome to visit and start the computer science learning journey.\n    </p>\n</div>\n\n\n|              I              |           II           |           III           |           IV           |            V            |            VI            |        VII        |         VIII         | IX |            X            |            XI            |            XII            |\n| :--------------------------: | :-------------------: | :----------------------: | :---------------------: | :--------------: | :---------------: | :----------------------: | :----------------------: | :----------------------: | :----------------------: | :----------------------: | :----------------------: |\n| 算法<br />[📝](?id=一、数据结构与算法) | Java<br/>[☕️](?id=二、java) | Python<br />[🐍](?id=三、python) | 前端<br />[🔗](?id=四、前端) | 数据库<br/>[💾](?id=五、数据库) | 操作系统<br/>[💻](?id=六、操作系统) | 网络通信<br/>[☁️](?id=七、网络通信) | 分布式<br/>[📃](?id=八、分布式) | 机器学习<br/> [🔍](?id=九、机器学习) |工具<br/>[🔨](?id=十、工具) |Learn<br />[📚](?id=learn-📚) |Talking<br />[💡](?id=talking-💡) |\n\n<div align=\"center\">  \n    <p>\n公告（2018/11/15）：\n        <a href=\"#/技术交流群.md\">QQ技术交流群</a>\n    </p>\n    <p>\n        个人能力有限，欢迎志同道合的朋友们共同维护。<a href=\"#/开源贡献.md\">⊱ 开源小组，英雄招募令</a>\n    </p>\n    <p>\n    招募 PHP，Python，Go，C++，分布式中间件，机器学习等等，板块维护者\n    </p>\n    <div>\n        <a href=\"https://github.com/frank-lam/2019_campus_apply/issues/2\" target=\"_blank\">读者信箱（资料、意见、想法都可以在这里留言分享）</a>\n    </div>\n</div>\n\n\n\n\n\n\n## 前言\n\n- [全栈修炼手册：如何选择自己的技术栈？](如何选择自己的技术栈.md)\n\n  在编程的世界里，该如何选择自己的技术栈呢。学前端？学 APP 开发？对于 Java、C++、C#、Python、PHP 又如何选择呢？人工智能现如今这么火，是不是机器学习、深度学习更高级一些呢？那么程序员又如何修炼内功呢？\n\n- [全栈开发神兵利器](全栈开发神兵利器.md)\n\n  工欲善其事，必先利其器。这里我将推荐开发过程中的提效工具、开发利器、协作工具、文档技术等等。\n\n\n\n\n## 一、数据结构与算法\n\n- [数据结构与算法](数据结构与算法.md)\n\n　　排序算法、动态规划、递归、回溯法、贪心算法等\n\n- [海量数据处理](海量数据处理.md)\n\n  数据处理典型案例，逐渐更新\n\n\n\n## 二、Java\n\n- [Java 基础概念](JavaArchitecture/01-Java基础.md)\n\n　　基本概念、面向对象、关键字、基本数据类型与运算、字符串与数组、异常处理、Object 通用方法\n\n- [Java 集合框架](JavaArchitecture/02-Java集合框架.md)\n\n　　数据结构 & 源码分析：ArrayList、Vector、LinkedList、HashMap、ConcurrentHashMap、HashSet、LinkedHashSet and LinkedHashMap\n\n- [Java 并发编程](JavaArchitecture/03-Java并发编程.md)\n\n　　线程状态、线程机制、线程通信、J.U.C 组件、JMM、线程安全、锁优化\n\n- [Java I/O](JavaArchitecture/04-Java-IO.md)\n\n　　磁盘操作、字节操作、字符操作、对象操作、网络操作、NIO\n\n- [Java 虚拟机](JavaArchitecture/05-Java虚拟机.md)\n\n　　运行时数据区域、垃圾收集、内存分配机制、类加载机制、性能调优监控工具\n\n- [Java 设计模式](JavaArchitecture/06-0设计模式.md)\n\n　　Java 常见的 10 余种设计模式，全 23 种设计模式逐步更新\n\n- [Java Web](JavaArchitecture/07-JavaWeb.md)\n\n　　包含 Servlet & JSP、Spring、SpringMVC、Mybatis、Hibernate、Structs2 核心思想，如 IOC、AOP 等思想。SSM 更详细请转向：[Spring](JavaWeb/Spring.md) | [SpringMVC](https://github.com/frank-lam/SpringMVC_MyBatis_Learning) | [MyBatis](https://github.com/frank-lam/SpringMVC_MyBatis_Learning)\n\n\n\n\n## 三、Python\n\n- Python 语言基础\n- Scrapy 爬虫框架\n- Flask\n- Django\n\n\n\n## 四、前端\n\n- HTML(5)\n\n- CSS(3)\n\n  sass、scss、stylus\n\n- CSS 框架\n\n  BootStarp、LayUI\n\n- JavaScript \n\n  基础语法、进阶、ES6\n\n- JavaScript 框架\n\n  - Vue\n  - React \n  - Angular\n  - jQuery\n\n- Node\n\n  常用 api、对象池、异常处理、进程通信、高并发\n\n- 静态编译\n  - Flow\n  - TypeScript\n\n- 打包工具\n  - webpack\n  - glup\n  - rollup\n\n- 工具\n  - npm\n  - yarn\n\n\n\n## 五、数据库\n\n- [MySQL](MySQL.md)\n\n  存储引擎、事务隔离级别、索引、主从复制\n\n- [Redis](Redis.md)\n\n  Redis 核心知识\n\n- MongoDB\n\n  基于分布式文件存储的数据库\n\n- [SQL](SQL.md)\n\n  常用 SQL 语句\n\n\n\n## 六、操作系统\n\n- [操作系统原理](操作系统.md)\n\n　　进程管理、死锁、内存管理、磁盘设备\n\n- [Linux](Linux.md)\n\n　　基础核心概念、常用命令使用\n\n\n\n## 七、网络通信\n\n- [计算机网络](计算机网络.md)\n\n　　传输层、应用层（HTTP）、网络层、网络安全\n\n- [RESTful API](RESTful%20API.md)\n\n  软件架构风格、格设计原则和约束条件\n\n- [Web网络安全](网络安全.md)\n\n  web前后端漏洞分析与防御，XSS 攻击、CSRF 攻击、DDoS 攻击、SQL 注入\n\n- Socket 网络编程\n\n\n\n## 八、分布式\n\n- [Docker](Docker基础.md)\n\n  容器化引擎服务\n\n- 微服务思想\n\n  服务拆分，持续集成、持续交付、持续部署\n\n- Kubernetes（k8s）\n\n  容器化部署，管理云平台中多个主机上的容器化的应用\n\n- 云计算\n\n  SaaS（软件即服务） 、PaaS（平台即服务） 、IaaS（基础架构即服务）\n\n- Zookeeper\n\n  分布式协调服务\n\n- Dubbo、Thrift（RPC 框架）\n\n  分布式服务治理\n\n- 分布式事务解决方案\n\n- ActiveMQ、Kafka、RabbitMQ\n\n  分布式消息通信\n\n- 熔断，限流，降级机制\n\n- Redis\n\n  分布式缓存\n\n- Mycat\n\n  数据库路由\n\n- Nginx\n\n  反向代理\n\n- Tomcat\n\n  Web Server 服务\n\n- DevOps\n\n  自动化运维\n\n- Go\n\n  并发的、带垃圾回收的、快速编译的语言\n\n【说明】**分布式专题** 笔者也在学习中，这里列举了一些技能列表，笔者将局部更新。敬请期待\n\n\n\n## 九、机器学习\n- 经典机器学习算法\n\n  K 近邻算法、线性回归、梯度下降法、逻辑回归、支持向量机、决策树、集成学习\n\n- 基础框架\n\n  Tensorflow，Scikit-learn\n\n【说明】学习过一段时间算法，但一直未来得及整理。敬请期待\n\n\n\n\n## 十、工具\n\n- [Git](Git.md)\n\n  基本概念，常见命令，速查表\n\n- [Git 工作流](Git工作流.md)\n\n  集中式工作流，功能分支工作流， GitFlow 工作流，Forking 工作流，Pull Requests\n\n- [正则表达式](EfficiencyTools/正则表达式.md)\n\n  常见符号含义，速查表\n\n\n\n\n## Learn 📚\n\n- [LEARN_LIST](LEARNLIST.md)\n\n　　包含阅读清单，学习课程两部分\n\n- [web应用开发标准流程](web应用开发标准流程.md)\n\n\n\n## Talking 💡\n\n　　本仓库致力于成为一个全栈开发爱好者的学习指南，给初学者一个更明确的学习方向，同时也是对自己技能的强化和巩固。在架构师这条路上，希望和大家一起成长，帮助更多的计算机爱好者能够有一个明确的学习路径。持续不间断的维护本仓库，也欢迎有更多的极客们加入。\n\n　　都说好记性不如烂笔头，定期的学习和整理必然对学习巩固有所帮助，这里通过索引的方式对全栈开发技术做一个系统分类，方便随时巩固和学习，当然还有面试。在学习这条路上难免会有很多盲点和学不完的知识。有道无术，术尚可求，掌握好思维能力才能应对千变万化的技术。不要把大脑当成硬盘，也不要做高速运转的 CPU，而修行自己的大脑成为一个搜索引擎，学会分析解决问题。\n\n　　Since 20,May,2018\n\n\n\n### Reference\n\n　　个人的能力有限，在编写的过程中引用了诸多优秀的 GitHub 仓库。本项目的启发来自 [@CyC2018](https://github.com/CyC2018) 的学习笔记，是一个非常优秀的开源项目，在本仓库中部分内容引用文字和图例；引用了 [@计算所的小鼠标](https://github.com/CarpenterLee) 中对于 JCF 的源码分析和理解；引用了  [阿里面试题总结](https://www.nowcoder.com/discuss/5949) 中全部的面试题，并对面经进行了整理勘误，并进行了知识拓展和修改；引用了 [牛客网](https://www.nowcoder.com) 上的面试经验贴。也引用了知乎上的热门回答和优秀博客的回答。在这里特别鸣谢，我将每篇文章中做外链引用说明。\n\n　　文中我也推荐了学习的书籍和学习课程，都将附着上最高清、最形象的配图进行讲解。在文中的配图都来自自己绘制的、博客、Github、PDF书籍等等，这里没法一一感谢，谢谢你们。\n\n　　推荐一些优秀的开源项目，供大家参考，[reference](reference.md)。\n\n\n\n### Contributors\n\n　　该项目还在萌芽起步阶段，在编写的过程中难免遇到错误和不足，你可以在 issue 中提出，或是提交你的 contribution。[【开源贡献】如何给我的仓库贡献？](docs/如何给我的仓库贡献.md)\n\n　　感谢以下伙伴们对于本仓库的贡献，如想加入本开源项目，请联系笔者。\n\n　　<a href=\"https://github.com/RThymer\">\n <img src=\"https://avatars3.githubusercontent.com/u/37107266?s=460&v=4\" width=\"50px\">\n</a> \n<a href=\"https://github.com/liuenci\">\n<img src=\"https://avatars3.githubusercontent.com/u/32006433?s=460&v=4\" width=\"50px\">\n</a>\n<a href=\"https://github.com/mazyi\">\n<img src=\"https://avatars1.githubusercontent.com/u/24809310?s=460&v=4\" width=\"50px\">\n</a> \n<a href=\"https://github.com/si9ma\">\n<img src=\"https://avatars2.githubusercontent.com/u/6824119?s=460&v=4\" width=\"50px\">\n</a> \n<a href=\"https://github.com/FrankLLC\">\n<img src=\"https://avatars0.githubusercontent.com/u/42835340?s=460&v=4\" width=\"50px\">\n</a> \n<a href=\"https://github.com/wyn-lin\">\n<img src=\"https://avatars1.githubusercontent.com/u/32236252?s=400&v=4\" width=\"50px\">\n</a> \n<a href=\"https://github.com/LimitSY\">\n<img src=\"https://avatars2.githubusercontent.com/u/5361364?s=460&v=4\" width=\"50px\">\n</a> \n<a href=\"https://github.com/librarysong\">\n<img src=\"https://avatars0.githubusercontent.com/u/22957350?s=460&v=4\" width=\"50px\">\n</a> \n\n\n\n### License\n\n　　本作品采用 [知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/) 进行许可。使用者可以对本创作进行转载、节选、混编、二次创作，但不得运用于商业目的，且使用时须进行署名，采用本创作的内容必须同样采用本协议进行授权。\n\n　　<a rel=\"license\" href=\"http://creativecommons.org/licenses/by-nc-sa/4.0/\"><img alt=\"知识共享许可协议\" style=\"border-width:0\" src=\"https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png\" /></a>\n\n\n\n## 关于作者 👦\n\n<div align=\"center\">  \n    <p>\n        在颠覆世界的同时，也要好好关照自己。\n    </p>\n<a  target=\"_blank\" href=\"https://zhuanlan.zhihu.com/frankfeekr\" rel=\"nofollow\"><img src=\"https://img.shields.io/badge/知乎专栏-frankfeekr-blue.svg\" alt=\"QQ群\" data-canonical-src=\"\" style=\"max-width:100%;\"></a>\n<a target=\"_blank\" href=\"http://blog.csdn.net/u012104219\" rel=\"nofollow\"><img src=\"https://img.shields.io/badge/CSDN-东风牧野-red.svg\" alt=\"CSDN\" data-canonical-src=\"\" style=\"max-width:100%;\"></a>\n<a target=\"_blank\" href=\"mailto:frank_lin@whu.edu.cn\" rel=\"nofollow\"><img src=\"https://img.shields.io/badge/Email-frank__lin@whu.edu.cn-lightgrey.svg\" alt=\"邮箱\" data-canonical-src=\"\" style=\"max-width:100%;\"></a>\n<a target=\"_blank\" href=\"https://jq.qq.com/?_wv=1027&k=593WvX0\" rel=\"nofollow\" ><img src=\"https://img.shields.io/badge/QQ群-862619503-green.svg\" alt=\"QQ群\" data-canonical-src=\"\" style=\"max-width:100%;\"></a>\n    <br/><br/>\n    <p>\n        from zero to hero.\n    </p>\n</div>\n<div align=\"center\"><img src=\"https://raw.githubusercontent.com/frank-lam/fullstack-tutorial/master/assets/wechat-fullstack.png\" width=\"620\"/></div><br/>\n\n"
  },
  {
    "path": "notes/my_linux_cmd.md",
    "content": "##  一、学习前准备\n\n#### 帮助文档\n\n- [Linux命令大全](http://man.linuxde.net/)（★★★），可以在上面找到你要查找的linux命令\n- [Linux 命令大全](http://www.runoob.com/linux/linux-command-manual.html) | 菜鸟教程\n- [Linux 教程](http://www.runoob.com/linux/linux-tutorial.html) | 菜鸟教程\n\n#### Tab 补全\n- `Tab` 补全是非常有用的一个功能，可以用来自动补全命令或文件名，省时准确。\n- 未输入状态下连按两次 `Tab` 列出所有可用命令\n- 已输入部分命令名或文件名，按 `Tab` 进行自动补全，多用你就肯定会喜欢的了。\n\n\n#### 光标\n- `up` 方向键上（可以调出输入历史执行记录，快速执行命令）\n- `down` 方向键下（配合 up 选择历史执行记录）\n- `Home` 移动光标到本行开头\n- `End` 移动光标到本行结尾\n- `PgUp` 向上翻页\n- `PaDN` 向下翻页\n- `Ctrl + C` 终止当前程序\n- `Ctrl + L` 清屏 = `clear`命令（记住这个快捷键，比`clear`高效很多）\n\n#### *man, info, help\n\n1. `man`命令：Linux下的帮助指令，通过man指令可以查看Linux中的指令帮助、配置文件帮助和编程帮助等信息。 \n2. `info`命令：Linux下info格式的帮助指令。就内容来说，info页面比man page编写得要更好、更容易理解，也更友好，但man page使用起来确实要更容易得多。\n3. `help`命令：用于显示shell内部命令的帮助信息。help命令只能显示shell内部的命令帮助信息。而对于外部命令的帮助信息只能使用man或者info命令查看。\n\n#### 远程连接工具\n- [Xshell 5](http://rj.baidu.com/soft/detail/15201.html)\n- [SecureCRT 6.6](http://download.csdn.net/download/u012104219/10209465)\n- MobaXterm（强烈推荐！）\n\n##  二、玩转Linux命令行之基础篇\n在这里我将介绍10组最常用的基础命令行，也是Linux入门必备的命令行，必须牢记于心，学习的过程中可以在Linux上熟练。这也是作为一个计算机专业人士必须熟练操作的最低要求。\n\n#### 1. cd , pwd\n#### > cd\n`cd`：cd命令用来切换工作目录至dirname。 其中dirName表示法可为绝对路径或相对路径。(cd = Change Directory) \n\n```linux\ncd命令示例：\n\n# 进入用户主目录\n[root@localhost ~]# cd ~ \n# 返回进入此目录之前所在的目录\n[root@localhost ~]# cd -\n# 返回上级目录（若当前目录为“/“，则执行完后还在“/\"；\"..\"为上级目录的意思）；  \n[root@localhost ~]# cd ..\n# 返回上两级目录\n[root@localhost ~]# cd ../..\n# 把上个命令的参数作为cd参数使用。  \n[root@localhost ~]# cd !$\n```\n\n#### > pwd\n\n`pwd`：pwd命令以绝对路径的方式显示用户当前工作目录。(pwd = print working Directory)  \n```\npwd命令示例：\n\n[root@localhost ~]# pwd\n/root\n```\n\n**总结**：cd用来切换目录，pwd用来打印工作目录。这两个是初学Linux的第一个命令行，也是任何操作的基础。  \n\n\n#### 2. mkdir , cp , mv , rm , touch\n#### > mkdir \n`mkdir`：mkdir命令用来创建目录。该命令创建由dirname命名的目录。如果在目录名的前面没有加任何路径名，则在当前目录下创建由dirname指定的目录；如果给出了一个已经存在的路径，将会在该目录下创建一个指定的目录。在创建目录时，应保证新建的目录与它所在目录下的文件没有重名。 (mkdir = make directory)\n```\nmkdir命令示例：\n\n# 在/home/frank目录下创建文件夹\"hellolinux\"\n[root@localhost ~]# mkdir /home/frank/hellolinux\n[root@localhost /home/frank]# mkdir hellolinux\n\n# 在目录/usr/meng下建立子目录test，并且只有文件主有读、写和执行权限，其他人无权访问\n[root@localhost ~]# mkdir -m 700 /usr/meng/test\n```\n#### > cp\n`cp`：cp命令用来将一个或多个源文件或者目录复制到指定的目的文件或目录。所有目标文件指定的目录必须是己经存在的，cp命令不能创建目录。如果没有文件复制的权限，则系统会显示出错信息。(cp = CoPy)\n```\ncp命令示例：\n\n# 将文件file复制到目录/usr/men/tmp下，并改名为file1\n[root@localhost ~]# cp file /usr/men/tmp/file1\n# 复制目录aaa下所有到/bbb目录下，这时如果/bbb目录下有和aaa同名的文件，需要按Y来确认并且会略过aaa目录下的子目录。\n[root@localhost ~]# cp aaa/* /bbb\n```\n#### > mv\n`mv`：mv命令用来对文件或目录重新命名，或者将文件从一个目录移到另一个目录中,将一组文件移至一个目标目录中。(mv = MoVe)\n```\nmv命令示例：\n\n# 重命名，将~目录下的文件名frank.html修改为abby.html\n[root@localhost ~]# mv frank.html abby.html\n# 将目录/usr/men中的所有文件移到当前目录（用.表示）中\n[root@localhost ~]# mv /usr/men/* .\n```\n#### > touch\n`touch`：touch命令有两个功能：一是用于把已存在文件的时间标签更新为系统当前的时间（默认方式），它们的数据将原封不动地保留下来；二是用来创建新的空文件。(touch = touch 已经是全称了)\n```\ntouch命令示例：\n\n# 在当前目录下建立一个空文件ex2，然后，利用ls -l命令可以发现文件ex2的大小为0，表示它是空文件。\n[root@localhost ~]# touch ex2\n```\n\n#### > rm\n\n`rm`：rm命令可以删除一个目录中的一个或多个文件或目录，也可以将某个目录及其下属的所有文件及其子目录均删除掉。对于链接文件，只是删除整个链接文件，而原有文件保持不变。(rm = ReMove)  \n\n**注意**：使用rm命令要格外小心。因为一旦删除了一个文件，就无法再恢复它。所以，在删除文件之前，最好再看一下文件的内容，确定是否真要删除。rm命令可以用-i选项，这个选项在使用文件扩展名字符删除多个文件时特别有用。使用这个选项，系统会要求你逐一确定是否要删除。这时，必须输入y并按Enter键，才能删除文件。如果仅按Enter键或其他字符，文件不会被删除。  \n\n千万不要随便尝试`rm -rf /`这个命令行，否则系统会GG了。[知乎：不小心敲了 rm -rf / 后反应是怎样的？](https://www.zhihu.com/question/29438735)\n```\ntouch命令示例：\n\n# 删除当前目录下除隐含文件外的所有文件和子目录\n[root@localhost ~]# rm -r *\n# 删除当前目录下，后缀为.cmd的所有文件\n[root@localhost ~]# rm *.cmd\n```\n\n#### 3. ls , ll\n#### > ls\n`ls`：ls命令用来显示目标列表，在Linux中是使用率较高的命令。ls命令的输出信息可以进行彩色加亮显示，以分区不同类型的文件。(ls = list)\n\n```\nls命令示例：\n\n# 显示当前目录下非影藏文件与目录\n[root@localhost ~]# ls\nanaconda-ks.cfg  install.log  install.log.syslog  satools\n\n# 显示当前目录下包括影藏文件在内的所有文件列表（这里要注意：.开头的文件就是隐藏文件）\n[root@localhost ~]# ls -a\n.   anaconda-ks.cfg  .bash_logout   .bashrc  install.log         .mysql_history  satools  .tcshrc   .vimrc\n..  .bash_history    .bash_profile  .cshrc   install.log.syslog  .rnd            .ssh     .viminfo\n\n# 输出长格式列表\n[root@localhost ~]# ls -1\nanaconda-ks.cfg\ninstall.log\ninstall.log.syslog\nsatools\n\n# 查看文件大小更适合阅读的方式，可以加上-h(h = human)人类更适合阅读的方式。\n[root@localhost ~]# ls -lh\n-rw-------.  1 root root 1.1K 9月   7 2015 anaconda-ks.cfg\n-rw-r--r--   1 root root 408K 9月   9 2015 banner.zip\n-rw-r--r--   1 root root   15 1月  15 15:54 C\n-rw-r--r--   1 root root 1.7G 10月 26 2015 db_.sql\n-rw-r--r--   1 root root  33M 10月 26 2015 db_.tar.gz\n\n```\n#### > ll\n`ll`：ll并不是linux下一个基本的命令，它实际上是ls -l的一个别名。  我们可以通过修改`~/.bashrc`添加任何其他的命令别名。  \n打开 `~/.bashrc`找到 #alias ll=’ls -l’，去掉前面的#就可以了。（关闭原来的终端才能使命令生效）  \n`ll`安装点击[这里](https://www.cnblogs.com/kongzhongqijing/p/3488884.html)查看详情。用法和`ls`一样，这里就不重复了。\n\n\n#### 4. vim , vi , nano\n这一组的命令，我们介绍了文件编辑工具。这里介绍三种编辑工具，各有所长，根据自己的喜好来选择。我比较倾向于组合使用，发挥在Linux系统上文件编辑的最高效率。\n#### > vim / vi\n它们都是多模式编辑器，不同的是vim 是vi的升级版本，它不仅兼容vi的所有指令，而且还有一些新的特性在里面。所以在这里只要学习vim的基本操作就ok了，本节命令更适合实践演练和操作，故只推荐一个慕课（[点击这里](https://www.imooc.com/learn/111)），和学习的参考文档（[点击这里](http://man.linuxde.net/vi)）。\n\n\n#### > nano\n`nano`：nano是一个字符终端的文本编辑器，有点像DOS下的editor程序。它比vi/vim要简单得多，比较适合Linux初学者使用。某些Linux发行版的默认编辑器就是nano。\n\n如果你没有安装nano编辑器的话，可以使用yum进行安装：`yum -y install nano`。\n\n篇幅有限，对于nano编辑器感兴趣的请[点击这里](http://man.linuxde.net/nano)查看操作说明。\n\n**总结**：想要像Geek一样在Linux上开发的话，必须对编辑工具做到行云流水，一定要花点时间记住所有的快捷键，看起来专业点（可以装逼）。\n\n#### 5. find , locate/slocate\n#### > find\n`find`：find命令用来在指定目录下查找文件。\n```\nfind命令示例：\n\n# 在/home目录下查找以.txt结尾的文件名\n[root@localhost ~]# find /home -name \"*.txt\"\n\n# 基于正则表达式匹配文件路径\n[root@localhost ~]# find . -regex \".*\\(\\.txt\\|\\.pdf\\)$\"\n```\n\n#### > locate/slocate\n`locate/slocate`：locate命令和slocate命令都用来查找文件或目录。\n```\nlocate/slocate命令示例：\n\n# 搜索etc目录下所有以sh开头的文件\n[root@localhost ~]# locate /etc/sh\n\n# 搜索用户主目录下，所有以m开头的文件\n[root@localhost ~]# locate ~/m\n\n# 搜索用户主目录下，所有以m开头的文件，并且忽略大小写\n[root@localhost ~]# locate -i ~/m\n\n```\n#### 6. wget\n`wget`：wget命令用来从指定的URL下载文件。wget非常稳定，它在带宽很窄的情况下和不稳定网络中有很强的适应性，如果是由于网络的原因下载失败，wget会不断的尝试，直到整个文件下载完毕。如果是服务器打断下载过程，它会再次联到服务器上从停止的地方继续下载。这对从那些限定了链接时间的服务器上下载大文件非常有用。\n\n```\nwget命令示例：\n\n# 使用wget下载单个文件\n[root@localhost ~]# wget http://www.linuxde.net/testfile.zip\n\n# 使用wget后台下载\n[root@localhost ~]# wget -b http://www.linuxde.net/testfile.zip\n\n# 当你打算进行定时下载，你应该在预定时间测试下载链接是否有效。我们可以增加--spider参数进行检查。\n[root@localhost ~]# wget --spider URL\n\n# 下载多个文件(filelist.txt保存了url信息，每行一个URL链接)\n[root@localhost ~]# wget -i filelist.txt\n\n\n```\n#### 7. chmod 修改权限\n`chmod`：chmod命令用来变更文件或目录的权限。在UNIX系统家族里，文件或目录权限的控制分别以**读取、写入、执行**3种一般权限来区分，另有3种特殊权限可供运用。用户可以使用chmod指令去变更文件与目录的权限，设置方式采用文字或数字代号皆可。符号连接的权限无法变更，如果用户对符号连接修改权限，其改变会作用在被连接的原始文件。  \n\n- 权限范围的表示法如下：\n- `u`：User，即文件或目录的拥有者；\n- `g`：Group，即文件或目录的所属群组；\n- `o`：Other，除了文件或目录拥有者或所属群组之外，其他用户皆属于这个范围；\n- `a`：All，即全部的用户，包含拥有者，所属群组以及其他用户；\n- `r`：读取权限，数字代号为“4”;\n- `w`：写入权限，数字代号为“2”；\n- `x`：执行或切换权限，数字代号为“1”；\n- `-`：不具任何权限，数字代号为“0”；\n- `s`：特殊功能说明：变更文件或目录的权限。\n\n\n##### linux文件的用户权限的分析图\n![image](https://morvanzhou.github.io/static/results/linux-basic/03-01-02.png)\n- `Type`: 很多种 (最常见的是 - 为文件, d 为文件夹, 其他的还有l, n … 这种东西, 真正自己遇到了, 网上再搜就好, 一次性说太多记不住的).  \n- `User`: 后面跟着的三个空是使用 User 的身份能对这个做什么处理 (r 能读; w 能写; x 能执行; - 不能完成某个操作).  \n- `Group`: 一个 Group 里可能有一个或多个 user, 这些权限的样式和 User 一样.  \n- `Others`: 除了 User 和 Group 以外人的权限  \n\n```\nchmod命令示例：\n\n# 通常的修改形式是\n[root@localhost ~]# chmod [谁][怎么修改] [哪个文件]\n\n# 举个最简单的例子, 现在的 t1.py 是 ----rw-r--, 如果我们想让你(user)有读的能力. 下面这样改就行了.\n[root@localhost ~]# chmod u+r t1.py\n[root@localhost ~]# ls -l\n-r--rw-r-- 1 frank frank 34 Oct 12 09:51 t1.py\n```\n\n这里的 u+r 很形象, User + read, 给 t1.py 这个修改.  \n所以我们的修改形式就能总结出下面这样.\n\n[谁]\n- u: 对于 User 修改\n- g: 对于 Group 修改\n- o: 对于 Others 修改\n- a: (all) 对于所有人修改\n\n[怎么修改]\n- +, -, =: 作用的形式, 加上, 减掉, 等于某些权限\n- r, w, x 或者多个权限一起, 比如 rx\n\n[哪个文件]\n- 施加操作的文件, 可以为多个\n\n\n##### 用数字表示rwx的权限\n其中a,b,c各为一个数字，a表示User，b表示Group，c表示Other的权限。\n\nr=4，w=2，x=1\n- 若要rwx（可读、可写、可执行）属性，则4+2+1=7\n- 若要rw-（可读、可写、不可执行）属性，则4+2=6\n- 若要r-w（可读、不可写、可执行）属性，则4+1=5\n\n```\n示例：\n\nchmod a=rwx file 和 chmod 777 file 效果相同\nchmod ug=rwx,o=x file 和 chmod 771 file 效果相同\n若用chmod 4755 filename可使此程式具有root的权限\n```\n\n#### 8. sudo\n`sudo`：sudo命令用来以其他身份来执行命令，预设的身份为root。在/etc/sudoers中设置了可执行sudo指令的用户。若其未经授权的用户企图使用sudo，则会发出警告的邮件给管理员。用户使用sudo时，必须先输入密码，之后有5分钟的有效期限，超过期限则必须重新输入密码。\n\n\n#### 9. ping , telnet , nc/netcat\n这一组的命令主要用于测试网络连接状态，在很多配置的过程中有很重要的用途。\n#### > ping\n`ping`：ping命令用来测试主机之间网络的连通性。执行ping指令会使用ICMP传输协议，发出要求回应的信息，若远端主机的网络功能没有问题，就会回应该信息，因而得知该主机运作正常。\n```\nping命令示例：\n\n[root@localhost ~]# ping www.linuxde.net\nPING host.1.linuxde.net (100.42.212.8) 56(84) bytes of data.\n64 bytes from 100-42-212-8.static.webnx.com (100.42.212.8): icmp_seq=1 ttl=50 time=177 ms\n64 bytes from 100-42-212-8.static.webnx.com (100.42.212.8): icmp_seq=2 ttl=50 time=178 ms\n64 bytes from 100-42-212-8.static.webnx.com (100.42.212.8): icmp_seq=3 ttl=50 time=174 ms\n64 bytes from 100-42-212-8.static.webnx.com (100.42.212.8): icmp_seq=4 ttl=50 time=177 ms\n...按Ctrl+C结束\n\n```\n\n#### > telnet\n`telnet`：telnet命令用于登录远程主机，对远程主机进行管理。telnet因为采用明文传送报文，安全性不好，很多Linux服务器都不开放telnet服务，而改用更安全的ssh方式了。但仍然有很多别的系统可能采用了telnet方式来提供远程登录，因此弄清楚telnet客户端的使用方式仍是很有必要的。\n\n相比`ping`命令来说，telnet还可以测试某个IP的端口是否能够正常连通\n```\ntelnet命令示例：\n\n# 测试IP:60.191.124.150:8081 是否能够连接的通\n[root@localhost ~]# telnet 60.191.124.150 8081\n\n# 另外我们也可以用wget进行模拟下载来测试是否能够连通\n[root@localhost ~]# wget 60.191.124.150:8081\n```\n\n#### > nc/netcat\n`nc`：nc命令是netcat命令的简称，都是用来设置路由器。安装：`yum install nc`\n```\n\n# nc测试端口连接\n[root@localhost ~]# nc -u -z -w 1 60.191.124.150 8081\n```\n#### 10. ifconfig , netstat\n`ifconfig`：查看网络情况，`netstat`：显示网络状态信息  \n#### >ifconfig\n`ifconfig`：ifconfig命令被用于配置和显示Linux内核中网络接口的网络参数。用ifconfig命令配置的网卡信息，在网卡重启后机器重启后，配置就不存在。要想将上述的配置信息永远的存的电脑里，那就要修改网卡的配置文件了。\n```\nifconfig示例：\n\n[root@localhost ~]# ifconfig\neth0      Link encap:Ethernet  HWaddr 00:16:3E:00:1E:51  \n          inet addr:10.160.7.81  Bcast:10.160.15.255  Mask:255.255.240.0\n          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1\n          RX packets:61430830 errors:0 dropped:0 overruns:0 frame:0\n          TX packets:88534 errors:0 dropped:0 overruns:0 carrier:0\n          collisions:0 txqueuelen:1000\n          RX bytes:3607197869 (3.3 GiB)  TX bytes:6115042 (5.8 MiB)\n\nlo        Link encap:Local Loopback  \n          inet addr:127.0.0.1  Mask:255.0.0.0\n          UP LOOPBACK RUNNING  MTU:16436  Metric:1\n          RX packets:56103 errors:0 dropped:0 overruns:0 frame:0\n          TX packets:56103 errors:0 dropped:0 overruns:0 carrier:0\n          collisions:0 txqueuelen:0\n          RX bytes:5079451 (4.8 MiB)  TX bytes:5079451 (4.8 MiB)\n```\n#### > netstat\n`netstat`：netstat命令用来打印Linux中网络系统的状态信息，可让你得知整个Linux系统的网络情况。[更多使用方法](http://man.linuxde.net/netstat)\n```\nnetstat命令示例：\n\n# 在netstat输出中显示 PID 和进程名称\n[root@localhost ~]# netstat -pt\n\n# 列出所有端口 (包括监听和未监听的)\nnetstat -a     #列出所有端口\nnetstat -at    #列出所有tcp端口\nnetstat -au    #列出所有udp端口        \n\n...\n```\n\n---\n\n\n\n##  三玩转Linux命令行之入门篇\n\n\n\n#### 配置DNS\n```\nvi /etc/resolv.conf\nnameserver 8.8.8.8\n\n#立即生效\nsource /etc/profile\n```\n\n\n\n\n\n\n#### SSH连接\n\nssh -p 22 root@192.168.2.28\n\n免密登录\nsshpass -p [passwd] ssh -p [port] root@192.168.X.X\n\nsshpass安装：sudo apt-get install sshpass\n【随笔】ssh登录时如何直接在参数中加入登录密码 - linxiong - 博客园\nhttps://www.cnblogs.com/linxiong945/p/4226211.html\nSSH 登录一次 Linux, 都需要输入密码, 如果你登录次数很频繁, 而却你的密码又设置得很长, 这就非常麻烦. 还好, 我们可以通过提前设置一个”保密协议”, 来让你的 Linux 识别出哪些电脑能不用密码登录. 这就是 public/private rsa key.\nssh-keygen -t rsa\n\n接着, 我们就要将这个生成好的 “公锁” 给复制去你的 被控制的 Linux. 指令结构还是和上面一样.\n\n$ ssh-copy-id [被控制的用户名]@[它的ip] \n\n\n\n\n#### yum & apt-get 软件包工具  还有命令`rpm`\n\nyum命令是在Fedora和RedHat以及SUSE中基于rpm的软件包管理器，它可以使系统管理人员交互和自动化地更细与管理RPM软件包，能够从指定的服务器自动下载RPM包并且安装，可以自动处理依赖性关系，并且一次安装所有依赖的软体包，无须繁琐地一次次下载、安装。 \n语法 \nyum(选项)(参数) \n\n选项\n-h：显示帮助信息；\n-y：对所有的提问都回答“yes”；\n-c：指定配置文件；\n-q：安静模式；\n-v：详细模式；\n-d：设置调试等级（0-10）；\n-e：设置错误等级（0-10）；\n-R：设置yum处理一个命令的最大等待时间；\n-C：完全从缓存中运行，而不去下载或者更新任何头文件。\n\n参数\ninstall：安装rpm软件包；\nupdate：更新rpm软件包；\ncheck-update：检查是否有可用的更新rpm软件包；\nremove：删除指定的rpm软件包；\nlist：显示软件包的信息；\nsearch：检查软件包的信息；\ninfo：显示指定的rpm软件包的描述信息和概要信息；\nclean：清理yum过期的缓存；\nshell：进入yum的shell提示符；\nresolvedep：显示rpm软件包的依赖关系；\nlocalinstall：安装本地的rpm软件包；\nlocalupdate：显示本地rpm软件包进行更新；\ndeplist：显示rpm软件包的所有依赖关系。\n\n\n\n\n\n#### 后台程序运行 nohup\n\nnohup命令可以将程序以忽略挂起信号的方式运行起来，被运行的程序的输出信息将不会显示到终端。\n\n顺便补充一下Windows上命令后台运行的快捷键，start /b jupyter notebook\n\n#### 文件授权\n\nchmod chgrp chown\n\n#### 重启和关机\n\n\n重启命令：\n1、reboot\n2、shutdown -r now 立即重启\n3、shutdown -r 5  5分钟后自动重启（时间可以自己设置）\n4、shutdown -r 10:30 在时间为10:30时候重启（时间可以自己设置）\n\n关机命令：\n1、init 0\n2、halt   \n3、poweroff  \n4、shutdown -h now 立刻关机\n5、shutdown -h 5  5分钟后自动关机（时间可以自己设置）\n\n\n#### 解压 tar / 压缩\n\nunzip\n\n\n\n#### 查看系统信息\n- `uname -a`\n- `lsb_release -a`\n- `cat /etc/redhat-release`\n- 查看操作系统位数 执行命令`getconf WORD_BIT`\n\n\n#### linux系统dig和nslookup的安装\n`# yum install bind-utils`\n\n\n\n\n#### grep 和 ps 和 kill / killall\n\n#### gcc\n\n#### time date\n\n\n\n\n\n\n#### 磁盘挂载类\nmount\n\n#### whereis whoami\n\n\n\n#### 查看文件大小的命令 du/df\n\n#### 查看服务\nnetstat -nlpt|grep 80 查看该端口号是否被占用\n\n\n\nps可以查看具体的进程信息，一般与管道符连接其他命令使用，如：grep\n\nps常用参数-ef/-aux，一般最常用还是-ef，例：ps -ef|grep mysql 查询mysql进程\n\ntop也可查看进程信息，而且是动态显示\n\nwhoami 查看当前登陆用户\nwho 查看多少用户在使用系统\ndate查看系统时间，可跟时间格式使用\ncal查看日历，可跟年份，查看指定的年份\nchkconfig --list     #查看系统服务启动\nchkconfig iptables on  #开机启动该服务\nchkconfig iptables off  #开机不启动该服务\nservice iptables start #启动该服务\nservice iptables restart #重启启该服务\nps -ef|grep mysql|grep -v grep|awk '{print $2}' ps -ef|grep mysql 是查询mysql服务的进程\n|后的grep -v grep 是匹配不包含grep的行\nawk是取查询结果的第几列，awk '{print $2}'则是取第二列的值\ngrep     无参数则显示匹配的行\n-c         显示匹配的行数\n-v         显示不匹配的行\n\n\n#### 防火墙\n\nservice iptables stop\n永久关闭：\nchkconfig iptables off \n\n\n#### ln\n\n\n\n#### Linux Screen 简单用法 图文教程\n\n[Screen 基本使用](https://www.jiloc.com/42235.html)\n\n\n\n\n#### 命令行清空快捷键\n清屏 \n\n\n#### Linux同步Internet时间\nyum install -y ntpdate \nntpdate time.nuri.net\n.同步时间成功后调整硬件时间\n#hwclock -w\n\n\n#### 重启网卡\nservice network restart\n\n#### 文件传输命令：rz、sz、scp\n- 安装：`sudo yum install lrzsz -y`\n- 使用：`rz`和`sz`很好的可以将本地和远程服务器进行数据传输\n- \n\n#### nc 命令\nnc -l -p 80\n\n\n\n\n#### 系统32位还是64位\n系统版本号和系统位数，使用命令 `cat /etc/issue && arch` 查看，如果是i386或者i686就是32位的，如果是x86_64就是64位的\n\n\n\n初窥Linux 之 我最常用的20条命令 - CSDN博客\n初窥Linux 之 我最常用的20条命令\n\n基础教程：Linux 新手应该知道的 26 个命令_Linux教程_Linux公社-Linux系统门户网站\n基础教程：Linux 新手应该知道的 26 个命令\n\n\nRedHat Enterprise Linux 6.4使用yum安装出现This system is not registered to Red Hat Subscription Management - CSDN博客\nhttp://blog.csdn.net/ytx2014214081/article/details/78468947\n\n\n\n安装 - LAMP一键安装包\nhttp://yumlamp.com/install.html\n\n\n\nyum -y install subversion\n\n\n\nStarting httpd: \nhttpd: Syntax error on\n\nline 221 of /etc/httpd/conf/httpd.conf: Syntax error on \nline 6 of /etc/httpd/conf.d/auth_mysql.conf: Cannot load /etc/httpd/modules/mod_auth_mysql.so into server: libmysqlclient.so.16: cannot open shared object file: No such file or directory\n[FAILED]\n\n\n\n\n#### linux查看当前目录空间\n一、首先使用df -h 命令查看磁盘剩余空间，通过以下图看出/目录下的磁盘空间已经被占满。\n\n- 查看某个文件夹的文件大小：du --max-depth 1 -lh  /var\n- 查看某个文件的大小：du 文件名 -h\n- 查看某个文件夹所在的挂载点：df  /home\n\n\n如何更改linux文件目录拥有者及用户组_百度经验\nhttps://jingyan.baidu.com/article/a501d80c19faa1ec620f5e6b.html"
  },
  {
    "path": "notes/project/README.md",
    "content": "# 面试总结（截止20180921）\n\n## 一、项目技术点深挖\n\n- [x] 单点登录\n\n  共享Redis会话服务器\n\n- [ ] 扫码登录\n\n  token\n\n- [ ] 接口验证，涉及到 RESTful API 和 HTTP、HTTPS 相关\n\n- [ ] 接口验证，这里涉及到网络安全，别拦截了怎么处理，如二次 token 等怎么做，需要深入\n\n当用户登录成功后，返回一个由Token签名生成的秘钥信息(Token可使用base64编码和md5加密，可以放在请求的Header中)，然后对每次后续请求进行Token的封装生成，服务器端在验证是否一致来判断请求是否通过。\n\n（1）常规的方法：用户登陆后生成token，返回客户端，然后服务器使用AOP拦截controller方法，校验token的有效性，每次token是一样的；\n\n（2）用户登陆后生成临时token，存到服务器，并返回客户端，客户端下次请求时把此token传到服务器，验证token是否有效，有效就登陆成功，并生成新的token返回给客户端，让客户端在下一次请求的时候再传回进行判断，如此重复。 这种方法有性能问题，但也有一个漏洞，如果用户在一次请求后，还未进行下一次请求就已被黑客拦截到登录信息并进行假冒登录，他一样可以登录成功并使用户强制下线，但这种方法已大大减少被假冒登录的机会。\n\n（3）两层token：一般第一次用账号密码登录服务器会返回两个token，时效长短不一样，短的时效过了之后，发送时效长的token重新获取一个短时效，如果都过期，那么就需要重新登录了。当然更复杂你还可以做三层token，按照业务分不同token。\n\n- [ ] 数据库同步，线程池工作原理和设计思想，ETL\n- [ ] 分布式高可用，水平拆分，垂直拆分。设计到数据一致性得问题\n- [ ] 数据库分库分表，主从复制，Redis数据库缓存\n- [ ] 消息队列（发布订阅），消息通信（RESTful）。可能会涉及到 Kafka、RabbitMQ\n- [ ] Redis 集群部署，Master-slave 和 哨兵模式\n- [ ] MySQL 数据库定时备份，这个怎么做的\n\nmysqldump\n\n\n\n## 二、后台核心知识点扫盲\n\n- [ ] 线程池工作原理与设计\n- [ ] 类加载机制\n- [ ] 动态代理  和 Cglib\n- [ ] 多并发编程怎么理解的\n- [ ] 锁的类型\n- [ ] 虚拟内存"
  },
  {
    "path": "notes/reference.md",
    "content": "▲ [GO HOME](https://github.com/frank-lam/2019_campus_apply)\n\n\n\n# reference\n\n[目录]\n\n- [开源项目推荐](#开源项目推荐)\n- [学习平台推荐](#学习平台推荐)\n- [好文推荐](#好文推荐)\n\n\n\n\n## 开源项目推荐\n\n这里是一些优秀的开源项目，直接上链接了，供大家参考学习。\n\n- [CyC2018/CS-Notes](https://github.com/CyC2018/CS-Notes)\n- [CarpenterLee/JCFInternals](https://github.com/CarpenterLee/JCFInternals)\n- [Snailclib/JavaGuide](https://github.com/Snailclimb/JavaGuide)\n- [linw7/Skill-Tree](https://github.com/linw7/Skill-Tree)\n- [crossoverJie/JCSprout](https://github.com/crossoverJie/JCSprout)\n- [imhuay/Algorithm_Interview_Notes-Chinese](https://github.com/imhuay/Algorithm_Interview_Notes-Chinese)\n- [xingshaocheng/architect-awesome](https://github.com/xingshaocheng/architect-awesome)\n\n\n\n## 学习平台推荐\n\n这里我将推荐一些好的学习资源与平台\n\n- [千锋李卫民-Java学习指南](http://www.funtl.com/)\t\n  - [Java 单体应用](https://www.bilibili.com/video/av29299488)\n  - [Java 微服务架构](https://www.bilibili.com/video/av29384041)\n  - [Java 微服务实战](https://www.bilibili.com/video/av29882762)\n\n- [【Gitbook】《微服务：从设计到部署》中文版](https://docshome.gitbooks.io/microservices/content/)\n\n\n\n\n\n## 好文推荐\n\n- [Java-面试宝典-知己知彼百战不殆](http://www.funtl.com/2018/09/16/interview/Java-%E9%9D%A2%E8%AF%95%E5%AE%9D%E5%85%B8-%E7%9F%A5%E5%B7%B1%E7%9F%A5%E5%BD%BC%E7%99%BE%E6%88%98%E4%B8%8D%E6%AE%86/)\n"
  },
  {
    "path": "notes/somecoolblog.md",
    "content": "## 优质文章\n\n- [聊聊阿里社招面试，谈谈“野生”Java程序员学习的道路 | 阿里中间件团队博客](http://jm.taobao.org/2018/07/09/%E8%81%8A%E8%81%8A%E9%98%BF%E9%87%8C%E7%A4%BE%E6%8B%9B%E9%9D%A2%E8%AF%95%EF%BC%8C%E8%B0%88%E8%B0%88%E2%80%9C%E9%87%8E%E7%94%9F%E2%80%9DJava%E7%A8%8B%E5%BA%8F%E5%91%98%E5%AD%A6%E4%B9%A0%E7%9A%84%E9%81%93%E8%B7%AF/)\n- \n\n"
  },
  {
    "path": "notes/web应用开发标准流程.md",
    "content": "# web应用开发标准流程\n\n## 一、产品分析\n\n1. 用户需求\n\n2. 竞品分析\n\n3. 市场调研\n\n\n\n## 二、技术选型\n\n1. 前端技术选型\n\n2. 后端技术选型\n\n3. 数据库\n\n4. 业务框架\n\n\n\n## 三、开发实现\n\n1. 前后端开发，测试【坑多】\n\n2. 前后端部署【坑多】\n\n3. 前后端升级【坑多】\n\n4. 业务框架\n\n\n\n## 四、生产上线\n\n1. 部署升级\n\n2. 峰值处理\n\n3. 成本优化\n\n4. 警报处理"
  },
  {
    "path": "notes/全栈开发神兵利器.md",
    "content": "<!-- TOC -->\n\n- [全栈开发神兵利器](#全栈开发神兵利器)\n    - [一、团队协作](#一团队协作)\n        - [团队协作](#团队协作)\n        - [远程](#远程)\n        - [笔记备忘](#笔记备忘)\n    - [二、图形与设计](#二图形与设计)\n        - [思维导图与原型设计](#思维导图与原型设计)\n        - [绘图工具](#绘图工具)\n        - [平面与视频设计](#平面与视频设计)\n    - [三、版本控制](#三版本控制)\n        - [SVN](#svn)\n        - [Git](#git)\n        - [Git 工作流](#git-工作流)\n        - [Git 托管平台](#git-托管平台)\n        - [自主搭建代码托管平台](#自主搭建代码托管平台)\n    - [四、全栈开发](#四全栈开发)\n        - [数据库管理（以Mysql为例）](#数据库管理以mysql为例)\n        - [SSH 连接工具](#ssh-连接工具)\n        - [SHELL](#shell)\n        - [接口调试工具](#接口调试工具)\n        - [轻量级开发工具](#轻量级开发工具)\n        - [容器化技术](#容器化技术)\n    - [五、文档技术](#五文档技术)\n        - [文档平台](#文档平台)\n        - [自动文档生成工具](#自动文档生成工具)\n        - [开源框架](#开源框架)\n\n<!-- /TOC -->\n\n# 全栈开发神兵利器\n\n工欲善其事，必先利其器。这里我将推荐开发过程中的提效工具、开发利器、协作工具、文档工具等等。欢迎在 [issues#21](https://github.com/frank-lam/2019_campus_apply/issues/21) 中补充你用到过的神兵利器，我将把留言中的工具更新到本文。\n\n\n\n## 一、团队协作\n\n### 团队协作\n- [Teambition](https://www.teambition.com)：团队协作工具创导者\n- [有道云协作](http://co.youdao.com/)：企业知识管理与协作平台\n- [tower](https://tower.im/)：深受用户喜爱的团队协作工具\n\n\n\n### 远程\n\n- [TeamViewer](https://www.teamviewer.com/zhCN/)：安全远程访问和支持\n  TeamViewer 基于最广泛的平台和技术，连接全世界的人、地区和事物。\n\n- [向日葵](https://sunlogin.oray.com/zh_CN/)：简单好用的远程控制软件\n\n- [mstsc](https://baike.baidu.com/item/mstsc/1329161?fr=aladdin)：运行 `win+r`，输入 `mstsc`。不要忽略 windows 自带的强大远程桌面连接工具\n\n  注意：真的不要再用 QQ 远程了，真的很卡！\n\n\n\n### 笔记备忘\n\n- [印象笔记](https://www.yinxiang.com/)：工作必备效率应用\n- [有道云笔记](http://note.youdao.com/)：网易出品，获得 5000 万用户青睐的笔记软件。提供了 PC 端、移动端、网页端等多端应用，用户可以随时随地对线上资料进行编辑、分享以及协同。\n- [日事清](https://www.rishiqing.com/)：怕工作进度延误 就用日事清\n- [滴答清单](https://www.dida365.com/)：一个帮你高效完成任务和规划时间的应用\n\n\n\n\n\n## 二、图形与设计\n\n### 思维导图与原型设计\n\n- [XMind](http://www.mindmanager.cc/)：思维导图，框架图等等，非常推荐。收费软件，部分功能可用\n- [MindManager](http://www.mindmanager.cc/)：让思考、计划和沟通变得更容易\n- [百度脑图](http://naotu.baidu.com/)：在线免费脑图，推荐\n- [Mockplus](https://www.mockplus.cn/features?hmsr=bdtg)：更快、更简单的原型设计\n- [Axure RP](https://www.axure.com/)：是一款专业的快速原型设计工具\n\n\n\n### 绘图工具\n\n- [Visio](https://products.office.com/zh-cn/visio/flowchart-software)：微软绘图工具，以直观的方式工作，轻松绘制图表\n- [亿图](http://www.edrawsoft.cn/download-edrawmax.php)：国产综合图形图表设计软件。类似 visio 的绘图工具（[完美破解版](http://www.zdfans.com/html/17131.html)）\n- [ProcessOn](https://www.processon.com/)：支持流程图、思维导图、原型图、UML、网络拓扑图、组织结构图等\n- [draw.io](https://www.draw.io/)：free online diagram software for making flowcharts, process diagrams, org charts, UML, ER and network diagrams\n- [StarUML](http://staruml.io/)：A sophisticated software modeler for agile and concise modeling（总之 UML 绘图神器）\n- logomakr：https://logomakr.com\n\n\n\n### 平面与视频设计\n\n只会写代码，设计都不会？本人从事过多年平面设计和视频相关的工作，这里也给大家推荐一些平时做设计的时的一些软件。\n\n- [Adobe Photoshop](https://www.adobe.com/cn/products/photoshop/free-trial-download.html)：图像编辑和合成，这个就不用我介绍了吧\n- [Adobe Premiere Pro](https://www.adobe.com/cn/products/premiere/free-trial-download.html)：视频制作和编辑（业余爱好者可使用绘声绘影）\n- [After Effects](https://www.adobe.com/cn/products/aftereffects/free-trial-download.html)：电影视觉效果和动态图形\n- [After Illustrator](https://www.adobe.com/cn/products/illustrator/free-trial-download.html)：矢量图形和插图\n- [Corel DRAW](https://www.corel.com/cn/)：和 AI 齐名的矢量图制作工具\n\n\n\n## 三、版本控制\n\n### SVN\n- Subversion (SVN) 是一个开源的版本控制系統, 也就是说 Subversion 管理着随时间改变的数据。 这些数据放置在一个中央资料档案库 (repository)  中。 这个档案库很像一个普通的文件服务器, 不过它会记住每一次文件的变动。 这样你就可以把档案恢复到旧的版本, 或是浏览文件的变动历史\n- 工具下载：[tortoiseSVN](https://tortoisesvn.net/)\n- 学习资源\n    - 文档：[菜鸟教程 SVN教程](http://www.runoob.com/svn/svn-intro.html)\n    - 视频：[版本管理工具介绍—SVN篇](https://www.imooc.com/learn/109)\n\n\n### Git\n- Git 是一个开源的分布式版本控制系统，用于敏捷高效地处理任何或小或大的项目\n- 工具下载：\n    - [SourceTree](https://www.sourcetreeapp.com/)（推荐★★★）\n    - [tortoiseGit](https://tortoisegit.org/)\n    - [GitHub Desktop](https://desktop.github.com/)\n- 学习资源\n    - 文档：[菜鸟教程 Git教程](http://www.runoob.com/git/git-tutorial.html)\n    - 视频：[版本管理工具介绍—Git篇](https://www.imooc.com/learn/208)\n\n\n\n### Git 工作流\n\n- [集中式工作流，功能分支工作流， GitFlow 工作流，Forking 工作流，Pull Requests](Git工作流.md)\n\n\n\n### Git 托管平台\n- [Github](http://www.github.com/)：全球最大的程序员社交网站（同性交友网站）\n- [码云](https://gitee.com/)：国内比较大的 Git 托管平台。码云专为开发者提供稳定、高效、安全的云端软件开发协作平台。无论是个人、团队、或是企业，都能够用码云实现代码托管、项目管理、协作开发\n- [CODING](https://coding.net/)：国内 Git 托管平台，Coding，让开发更简单\n\n\n\n### 自主搭建代码托管平台\n\n- [GitLab](https://www.gitlab.com/)：可以使用 GitLab 官方的服务，也提供了开源社区版供团队搭建使用。（推荐使用 Docker 可实现一键自动化搭建）\n\n- [Gogs](https://gogs.io/)：一款极易搭建的自助 Git 服务，通过 Go 语言写的，适合在 Linux 服务器上搭建\n- [VisualSVN](https://www.visualsvn.com/server/)：VisualSVN Server allows you to easily install and manage a fully-functional Subversion server on the Windows platform. \n- [iF.SVNAdmin](http://svnadmin.insanefactory.com/)：The iF.SVNAdmin application is a web based GUI to your Subversion authorization file. It is based on PHP 5.3 and requires a web server (Apache) to be installed. （通过 PHP 在 Linux 上搭建 SVN 平台，并且有 web 管理页面）\n\n\n\n## 四、全栈开发\n\n### 数据库管理（以Mysql为例）\n- [Navicat Premium](http://www.navicat.com.cn/products/)：可以连接所有数据库，配套Navicat也针对不同的数据库有不同的版本，请点击进入官网自行查看，收费软件，需要百度自行破解。\n- [SQLyog](https://sqlyog.en.softonic.com)：Administrate MySQL Databases With Ease Using a Graphical Interface，免费\n\n\n\n### SSH 连接工具\n\n- [MobaXterm](https://mobaxterm.mobatek.net/)（超级推荐，太极客了！而且是免费的）\n- [Xshell 5](http://rj.baidu.com/soft/detail/15201.html)\n- [SecureCRT 6.6](http://download.csdn.net/download/u012104219/10209465)\n- [GitKraken](https://www.gitkraken.com/git-client)\n\n\n\n  **推荐**：以上三款工具我都使用过，目前已经弃用 Xshell 和 SecureCRT，推荐使用 MobaXterm\n\n\n\n### SHELL\n\n- [Oh My Zsh - a delightful & open source framework for Z-Shell](https://ohmyz.sh/)\n\n\n\n### 接口调试工具\n\n- 抓包工具1 | [Fiddler](https://www.telerik.com/fiddler)：The free web debugging proxy（很优秀的抓包工具，目前似乎只支持 windows 用户）\n- 抓包工具2 | [charles](https://www.charlesproxy.com/)：Charles is an HTTP proxy / HTTP monitor / Reverse Proxy that enables a developer to view all of the HTTP and SSL / HTTPS traffic between their machine and the Internet.（MacOS的必备抓包工具）\n- 接口调试 | [postman](https://www.getpostman.com/)：Developers use Postman to build\n  modern software for the API-first world.\n\n\n\n### 轻量级开发工具\n\n- [Sublime Text](https://www.sublimetext.com/)：A sophisticated text editor for code, markup and prose\n- [VS Code](https://code.visualstudio.com/)：Free. Open source. Runs everywhere.（非常推荐，后起之秀，有丰富的社区插件，超级推荐使用，推荐安装 One Dark Pro Theme）\n- [Atom](https://atom.io/)：A hackable text editor for the 21st Century\n- [brackets](http://brackets.io/)：A modern, open source text editor that understands web design.（前端神奇）\n- 三者比较请移步知乎：[Atom、Sublime Text、VSCode 三者比较，各有哪些优势和弱势？](https://www.zhihu.com/question/41857899?sort=created)\n\n\n\n### 容器化技术\n\n- [Docker](https://www.docker.com/)：秒级启动虚拟机容器技术。真正一次编写，到处运行。（一定要学！）\n\n\n\n## 五、文档技术\n\n在团队协作中必须会涉及到文档交互部分，这里推荐以下几个文档平台和开源项目\n\n### 文档平台\n\n- [看云](https://www.kancloud.cn/)：专注于文档在线创作、协作和托管（极力推荐，每个文档只有50Mb的免费空间，超过需要收费）\n\n\n\n### 自动文档生成工具\n\n- [ApiDoc](http://apidocjs.com/)：Inline Documentation for RESTful web APIs，可以通过命令行将代码中的注释生成在线可调试的文档，开发者的福音啊\n- [Swagger](https://swagger.io/)：The Best APIs are Built with Swagger Tools，在 Java web 项目中用的比较多\n\n\n\n### 开源框架\n\n- [ShowDoc](https://www.showdoc.cc/)：一个非常适合IT团队的在线 API 文档、技术文档工具。使用 PHP 开发的文档框架\n- [MinDoc](https://www.iminho.me/)：MinDoc 是一款针对IT团队开发的简单好用的文档管理系统\n- [vuepress](https://vuepress.vuejs.org/)：vue 官方团队的文档解决方案，适合于静态博客或是文档\n- [docsify](https://docsify.js.org/#/zh-cn/)：类似 gitbook 和 vuepress 的文档解决方案\n- [hexo](https://hexo.io/zh-cn/)：markdown 编写，自动生成静态博客\n"
  },
  {
    "path": "notes/分布式/Zookeeper.md",
    "content": "# Zookeeper\n- [CAP定理](#CAP定理)\n- [Zookeeper概述](#zookeeper概述)\n\t- [Zookeeper特点](#zookeeper特点)\n\t- [Zookeeper使用场景](#zookeeper使用场景)\n\t- [Zookeeper节点状态](#zookeeper节点状态)\n\t- [Zookeeper数据类型](#zookeeper数据类型)\n\t- [Zookeeper数据版本](#zookeeper数据版本)\n\t- [Zookeeper Watcher](#Watcher)\n\t- [Zookeeper Session](#Session)\n\t- [Zookeeper ACL](#ACL)\n\t- [Zookeeper ZAB](#ZAB)\n\t\t- [ZAB 选主流程](#选主流程)\n\t\t- [ZAB 数据同步](#数据同步)\n\t\t- [ZAB 过半同意](#过半同意)\n- [Zookeeper运维](#Zookeeper运维)\n\t- [Zookeeper集群搭建](#Zookeeper集群搭建)\n\t- [Zookeeper集群监控](#Zookeeper集群监控)\n\t- [常用Shell操作](#常用shell操作)\n\t- [开源工具推荐](#开源工具推荐)\n\n## CAP定理\n\n在了解Zookeeper之前，首先需要了解在分布式系统（distributed system）中的基本定理：CAP定理\n定义：**CAP定理指的是在一个分布式系统中，Consistency（一致性）、 Availability（可用性）、Partition tolerance（分区容错性），三者不可兼得**。CAP定理的命名就是这三个指标的首字母。\n\n- **Partition tolerance** 指的是在分布式系统中，由于不同的服务器之间可能无法通讯，所以需要一定的容错机制，默认情况下认为 Partition tolerance总是成立。\n\n- **Consistency** 指的是在分布式系统中，不同的服务器上所存储的数据需要一致，可以理解成当服务器A执行操作数据的指令后，服务器B上也要应用同样的操作以保证其所提供的数据同A中的一致。\n- **Availability** 指的是分布式系统中，每当服务端收到客户端的请求，服务端都必须给出回应。\n\n为什么说这三者不能同时满足呢，其主要原因在于Consistency 和 Availability不可能同时成立。\n假如要保证分布式系统的高数据一致性，则服务端之间一定要在同步后才能开放给客户端进行新的读写操作。即通过加锁同步操作使其可以被看成是一个原子的操作，而在锁定期间，服务端是无法提供服务的，这样服务端是无法做到高可用的，也就违背了Availability。\n假如要保证分布式系统的高可用性，则服务端必须无时无刻给客户端提供服务。而服务端间数据同步的操作由于受到网络等因素的影响，无法实时的进行同步数据，假如服务器A上的数据进行了修改而尚未同步到服务器B上，所以此时服务器B所提供的数据就不是最新的，因而违反了Consistency 。\n\n<div align=\"center\"><img src=\"assets/CAP.jpg\" width=\"\"/></div>\n\n## Zookeeper概述\n\nZooKeeper是一个分布式的、开放源码的分布式协调服务，是Google的Chubby一个开源的实现，是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件，提供的功能包括：配置维护、域名服务、分布式同步、组服务等。由于Hadoop生态系统中很多项目都依赖于zookeeper，如Pig，Hive等， 似乎很像一个动物园管理员，于是取名为Zookeeper。\nZookeeper官网地址为[http://zookeeper.apache.org/](http://zookeeper.apache.org/)。\n\n\n## Zookeeper特点\n\n- <b>顺序一致性</b> 从同一个客户端发起的事务请求，将会严格按照其发起顺序被应用到zookeeper中\n- <b>原子性</b> 所有事物请求的处理结果在整个集群中所有机器上的应用情况是一致的，要么整个集群中所有机器都成功应用了某一事务，要么都没有应用某一事务，不会出现集群中部分机器应用了事务，另一部分没有应用的情况。\n- <b>单一视图</b> 无论客户端连接的是哪个zookeeper服务端，其获取的服务端数据模型都是一致的。\n- <b>可靠性</b> 一旦服务端成功的应用了一个事务，并完成对客户端的响应，那么该事务所引起的服务端状态变更将会一直保留下来，直到有另一个事务又对其进行了改变。\n- <b>实时性</b> 一旦服务端成功的应用了一个事物，那客户端立刻能看到变更后的状态\n\n\n## Zookeeper使用场景\n\n- 名字服务\n- 配置管理\n- 集群管理\n- 集群选举\n- 分布式锁\n- 队列管理\n- 消息订阅\n\n## Zookeeper节点状态\n\n- LOOKING：寻找Leader状态，处于该状态需要进入选举流程\n- LEADING：领导者状态，处于该状态的节点说明是角色已经是Leader\n- FOLLOWING：跟随者状态，表示Leader已经选举出来，当前节点角色是Follower\n- OBSERVER：观察者状态，表明当前节点角色是Observer，Observer节点不参与投票，只负责同步Leader状态\n\n## Zookeeper数据类型\n\n- Zookeeper的数据结构非常类似于文件系统。是由节点组成的树形结构。不同的是文件系统是由文件夹和文件来组成的树，而Zookeeper中是由Znode来组成的树。每一个Znode里都可以存放一段数据，Znode下还可以挂载零个或多个子Znode节点，从而组成一个树形结构。\n- 节点类型\n  - 持久化节点(PERSISTENT)：znode节点的数据不会丢失，除非是客户端主动delete\n  - 持久化顺序节点(PERSISTENT_SEQUENTIAL)：znode节点会根据当前已经存在的znode节点编号自动加 1\n  - 临时节点：临时节点(EPHEMERAL)：当session中断后会被删除\n  - 临时顺序节点(EPHEMERAL_SEQUENTIAL)：znode节点编号会自动加 1，当session中断后会被删除\n\n## Zookeeper数据版本\n\nZookeeper的每个ZNode上都会存储数据，对应到每个ZNode，Zookeeper都会为其维护一个叫做Stat的数据结构，Stat中记录的内容如下：\n\n- cZxid: 节点创建时的zxid\n- ctime: 节点创建时间\n- mZxid: 最后一次更新的zxid\n- mtime: 最后一次更新的时间\n- pZxid: 子节点的最后版本\n- cversion: 子节点数据更新次数\n- dataVersion: 节点数据更新次数\n- aclVersion: acl的变更次数\n- ephemeralOwner: 如果znode是临时节点，则值为所有者的sessionId；如果不是临时节点，则为零\n- dataLength: 节点的数据长度\n- numChildren: 子节点个数\n\n\n## Watcher\n\nWatcher(事件监听器)是 Zookeeper提供的一种 发布/订阅的机制。Zookeeper允许用户在指定节点上注册一些 Watcher，并且在一些特定事件触发的时候，Zookeeper服务端会将事件通知给订阅的客户端。该机制是 Zookeeper实现分布式协调的重要特性。\n\n- watcher特点\n\t- 轻量级：一个callback函数。\n\t- 异步性：不会block正常的读写请求。\n\t- 主动推送：Watch被触发时，由 Zookeeper 服务端主动将更新推送给客户端。\n\t- 一次性：数据变化时，Watch 只会被触发一次。如果客户端想得到后续更新的通知，必须要在 Watch 被触发后重新注册一个 Watch。\n\t- 仅通知：仅通知变更类型，不附带变更后的结果。\n\t- 顺序性：如果多个更新触发了多个 Watch ，那 Watch 被触发的顺序与更新顺序一致\n- watcher使用注意事项。\n\t- 由于watcher是一次性的，所以需要自己去实现永久watch\n\t- 如果被watch的节点频繁更新，会出现“丢数据”的情况\n\t- watcher数量过多会导致性能下降\n\n\n## Session\n\nzookeeper会为每个客户端分配一个session，类似于web服务器一样，用来标识客户端的身份。\n\n- Session作用\n\t-  客户端标识\n\t-  超时检查\n\t-  请求的顺序执行\n\t-  维护临时节点的生命周期\n\t-  watcher通知\n- Session状态\n\t- CONNECTING\n\t- CONNECTED\n\t- RECONNECTING\n\t- RECONNECTED\n\t- CLOSED\n- Session属性\n\t-  sessionID：会话ID，全局唯一\n\t-  TimeOut：会话超时时间\n\t-  TickTime：下次会话超时时间点\n\t-  isClosing：会话是否已经被关闭\n- SessionID构造\n\t- 高8位代表创建Session时所在的zk节点的id\n\t- 中间40位代表zk节点当前角色在创建的时候的时间戳\n\t- 低16位是一个计数器，初始值为0\n\n\n## ACL\n\n在Zookeeper中，node的ACL是没有继承关系的。ACL表现形式:scheme:id:permissions。\n\n- Scheme\n\t- World：它下面只有一个id, 叫anyone。world:anyone代表任何人都有权\n\t- Auth：通过user:password的形式认证，支持Kerberos\n\t- Digest：使用user:password的形式认证\n\t- Ip：通过IP的粒度来控制权限，支持网段\n\t- Super：对应的id拥有超级权限，可以做任何事情\n- Permission\n\t- CREATE(c):  创建权限，可以在在当前node下创建child node\n\t- DELETE(d):  删除权限，可以删除当前的node\n\t- READ(r):  读权限，可以获取当前node的数据，可以list当前node所有的child nodes\n\t- WRITE(w):  写权限，可以向当前node写数据\n\t- ADMIN(a):  管理权限，可以设置当前node的permission\n\n\n## ZAB\n\nZAB 是 ZooKeeper Atomic Broadcast （ZooKeeper 原子广播协议）的缩写，它是特别为 ZooKeeper 设计的崩溃可恢复的原子消息广播算法。ZooKeeper 使用 Leader来接收并处理所有事务请求，并采用 ZAB 协议，将服务器数据的状态变更以事务 Proposal 的形式广播到所有的 Follower 服务器上去。这种主备模型架构保证了同一时刻集群中只有一个服务器广播服务器的状态变更，因此能够很好的保证事物的完整性和顺序性。\nZab协议有两种模式，它们分别是恢复模式(recovery)和广播模式(broadcast)。当服务启动或者在leader崩溃后，Zab就进入了恢复模式，当leader被选举出来，且大多数follower完成了和leader的状态同步以后， 恢复模式就结束了，ZAB开始进入广播模式。\n\n\n### 选主流程\n\n当Leader崩溃或者Leader失去大多数的Follower时，Zookeeper处于恢复模式，在恢复模式下需要重新选举出一个新的Leader，让所有的 Server都恢复到一个正确的状态。Zookeeper的选举算法有两种：一种是基于basic paxos实现的，另外一种是基于fast paxos算法实现的。系统默认的选举算法为fast paxos。\n\n- Basic paxos：当前Server发起选举的线程,向所有Server发起询问,选举线程收到所有回复,计算zxid最大Server,并推荐此为Leader，若此提议获得n/2+1票通过（过半同意）,此为Leader，否则重复上述流程，直到Leader选出。\n\n- Fast paxos:某Server首先向所有Server提议自己要成为Leader，当其它Server收到提议以后，解决epoch和 zxid的冲突，并接受对方的提议，然后向对方发送接受提议完成的消息，重复这个流程，最后一定能选举出Leader。(即提议方解决其他所有epoch和 zxid的冲突,即为Leader)。\n\n\n### 数据同步\n当集群重新选举出Leader后，所有的Follower需要和Leader同步数据，确保集群数据的一致性。\n\n- 数据同步方式\n\t- SNAP-全量同步\n\t\t- 条件：peerLastZxid<minCommittedLog\n\t\t- 说明：证明二者数据差异太大，follower数据过于陈旧，leader发送快照SNAP指令给follower全量同步数据，即leader将所有数据全量同步到follower\n\t- DIFF-增量同步\n\t\t- 条件：minCommittedLog<=peerLastZxid<=maxCommittedLog\n\t\t- 说明：证明二者数据差异不大，follower上有一些leader上已经提交的提议proposal未同步，此时需要增量提交这些提议即可\n\t- TRUNC-仅回滚同步\n\t\t- 条件：peerLastZxid>minCommittedLog\n\t\t- 说明：证明follower上有些提议proposal并未在leader上提交，follower需要回滚到zxid为minCommittedLog对应的事务操作\n\t- TRUNC+DIFF-回滚+增量同步\n\t\t- 条件：minCommittedLog<=peerLastZxid<=maxCommittedLog\n\t\t- 说明：leader a已经将事务truncA提交到本地事务日志中，但没有成功发起proposal协议进行投票就宕机了；然后集群中剔除原leader a重新选举出新leader b，又提交了若干新的提议proposal，然后原leader a重新服务又加入到集群中说明：此时a,b都有一些对方未提交的事务，若b是leader, a需要先回滚truncA然后增量同步新leader b上的数据。\n\n\n### 过半同意\n当数据同步完成后，集群开始从恢复模式进入广播模式，开始接受客户端的事物请求。\n当只有Leader或少数机器批准执行某个任务时，则极端情况下Leader和这些少量机器挂掉，则无法保证新Leader知道之前已经批准该任务，这样就违反了数据可靠性。所以Leader在批准一个任务之前应该保证集群里大部分的机器知道这个提案，这样即使Leader挂掉，选举出来的新Leader也会从其他Follower处获取这个提案。而如果Leader要求所有Follower都同意才执行提案也不行，此时若有一个机器挂掉，Leader就无法继续工作，这样的话整个集群相当于单节点，无法保证可靠性。\n\n## Zookeeper集群搭建\n\n- 1.安装jdk，并且配置jdk的环境变量\n\n- 2.去官网下载zookeeper的安装包，上传到linux集群环境下 <http://zookeeper.apache.org/>\n\n- 3.解压安装包 `tar -zxvf zookeeper-X.X.X.tar.gz`\n\n- 4.进入conf目录，复制zoo-sample.cfg为zoo.cfg,zoo.cfg及为Zookeeper的默认配置文件，我们通过修改zoo.cfg即可配置zookeeper\n\n  -  修改dataDir路径\n    指定zookeeper将数据保存在哪个目录下，如果不修改，默认在/tmp下，由于该目录下数据可能会被linux自动清理，所以一定要修改该路径\n\n  - 修改服务器列表\n    单机模式：在zoo.cfg中只配置一个server.id就是单机模式了\n    伪分布式：在zoo.cfg中配置多个server.id，其中ip都是当前机器，而端口各不相同，这就是伪集群模式\n    完全分布式：多台机器上各自配置server.id\n\n    ```shell\n    server.1=xxx.xxx.xxx.xxx:2888:3888\n    server.2=xxx.xxx.xxx.xxx:2888:3888\n    server.3=xxx.xxx.xxx.xxx:2888:3888\n    ```\n\n  - 在dataDir路径下生成myid文件\n    在dataDir目录下cat一个叫myid的文件，写入你所分配给当前机器的server.id\n\n- 5.Zookeeper操作指令\n\n  和Redis，Mysql等服务类似,访问Zookeeper安装路径下的`bin/zkServer.sh` 即可完成对服务端的启动，停止等操作，具体参数可通过`bin/zkServer.sh --help`查询,以下是常用指令\n  \n  ```shell\n  bin/zkServer.sh start #启动zookeeper服务\n  bin/zkServer.sh stop #停止zookeeper服务\n  bin/zkServer.sh restart #重启zookeeper服务\n  bin/zkServer.sh status #查看服务器状态\n  ```\n  \n\n注意！搭建在多台服务器上的Zookeeper都需要启动，如果不想一台一台的启动，可以通过编写批量启动的shell脚本通过ssh的方式实现对Zookeeper集群的管理。或者安装CDH等Hadoop 发行版实现对Zookeeper集群的管理\n\n## Zookeeper集群监控\n- 基础监控\n\t- CPU\n\t- 内存\n\t- 网络\n\t- IO\n- 进程监控\n\t- CPU\n\t- 内存\n\t- IO\n\t- 连接数\n\t- FD数\n- 端口监控\n- 日志监控\n- JVM监控\n- JMX监控\n- ZXID监控\n- 四字监控\n\t- conf 获取当前zookeeper服务器的配置\n\t- envi 获取当前zookeeper服务器的环境变量\n\t- cons 获取当前zookeeper服务器的活跃连接\n\t- crst 重置当前zookeeper服务器所有连接的统计信息\n\t- srst 重置当前服务器的统计信息\n\t- srvr 输出服务器的详细信息。zk版本、接收/发送包数量、连接数、模式（leader/follower）、节点总数\n\t- stat 输出服务器的详细信息。zk版本、接收/发送包数量、连接数、模式（leader/follower）、节点总数、客户端列表\n\t- mntr 列出集群的健康状态。包括“接受/发送”的包数量、操作延迟、连接数、缓冲队列数、当前服务模式（leader/follower）、节点总数、watch总数、临时节点总数\n\t- ruok 返回“imok”表示正常，否则表示服务异常。\n\t- wchs 列出服务器watches的简洁信息：连接总数、watching节点总数和watches总数\n\t- wchc 通过session分组，列出watch的所有节点，它的输出是一个与 watch 相关的会话的节点列表。如果watches数量很大的话，将会产生很大的开销，会影响性能，小心使用。\n\t- wchp 通过路径分组，列出所有的 watch 的session id信息。它输出一个与 session 相关的路径。如果watches数量很大的话，将会产生很大的开销，会影响性能，小心使用。\n\t- dump 列出未经处理的会话和临时节点（只在leader上有效）\n\n\n## 常用Shell操作\n\n- 连接zookeeper客户端\n  `bin/zkCli.sh [-server ip:port]` \n- 列出节点：\n  `ls path [watch]`\n- 创建节点：\n  `create [-s] [-e] path data acl` \n- 获取节点：\n  `get path [watch]`\n- 更新操作：\n  `set path data [version]`\n- 删除操作：\n  `delete path [version]`\n- 批量执行：\n  \n  ```\n  zkCli.sh -server localhost:2181 <<EOF  \n  ls /\n  get /\n  quit\n  EOF\n  ```\n- 分析snapshot文件：\n\n```\n#!/bin/sh\n\nfunction help(){\n        echo \"-----------------\"\n        echo \"HELP: $0 SnapshotFile\"\n        echo \"-----------------\"\n        exit 1\n}\n\nif [ $# -ne 1 ]\nthen\n        help\nfi\n\nfile=$1\nif [ ! -f $file ]\nthen\n        echo \"ERROR: $file not found\"\n        exit 1\nfi\nzkDir=/usr/local/zookeeper\nJAVA_OPTS=\"$JAVA_OPTS -Djava.ext.dirs=$zkDir:$zkDir/lib\"\njava $JAVA_OPTS org.apache.zookeeper.server.SnapshotFormatter \"$file\"\n```\n- 分析log文件：\n\n```\n#!/bin/sh\n\nfunction help(){\n        echo \"-----------------\"\n        echo \"HELP: $0 LogFile\"\n        echo \"-----------------\"\n        exit 1\n}\n\nif [ $# -ne 1 ]\nthen\n        help\nfi\n\nLogFile=$1\nif [ ! -f $LogFile ]\nthen\n        echo \"ERROR: $LogFile not found\"\n        exit 1\nfi\nzkDir=/usr/local/zookeeper\nJAVA_OPTS=\"$JAVA_OPTS -Djava.ext.dirs=$zkDir:$zkDir/lib\"\njava $JAVA_OPTS org.apache.zookeeper.server.LogFormatter \"$LogFile\"\n```\n\n## 开源工具推荐\n- zk抓包工具：[https://github.com/pyinx/zk-sniffer](https://github.com/pyinx/zk-sniffer)\n- zk压测工具：[https://github.com/phunt/zk-smoketest](https://github.com/phunt/zk-smoketest)\n- zk监控工具：[https://github.com/phunt/zktop](https://github.com/phunt/zktop)\n- zkCli工具：[https://github.com/let-us-go/zkcli](https://github.com/let-us-go/zkcli)\n\n"
  },
  {
    "path": "notes/分布式.md",
    "content": "# 分布式与微服务\n\n\n\n# 前言\n\n这里将持续拓展增加一些分布式相关的核心要点。\n\n- 消息中间件\n  - Kafka\n  - RabbitMQ\n\n- 微服务\n  - Dubbo\n  - Spring Boot\n  - Spring Cloud\n\n- Zookeeper\n- Hadoop\n- Dobbo\n\n\n\n## 1. 分布式锁\n\n> 金山云面经\n\n- 为什么要使用分布式锁？\n\n  - 在单体应用单机环境下，ReentrantLock 或 Synchronized 进行互斥控制。在单机环境中，Java 中提供了很多并发处理相关的 API。但是，随着业务发展的需要，原单体单机部署的系统被演化成分布式集群系统后，由于分布式系统多线程、多进程并且分布在不同机器上，这将使原单机部署情况下的并发控制锁策略失效，单纯的 Java API 并不能提供分布式锁的能力。为了解决这个问题就需要一种跨 JVM 的互斥机制来控制共享资源的访问，这就是分布式锁要解决的问题！\n\n- 分布式锁一般有三种实现方式：\n\n  - 数据库乐观锁\n  - 基于 Redis 的分布式锁\n  - 基于 Zookeeper 的分布式锁\n\n  尽管有这三种方案，但是不同的业务也要根据自己的情况进行选型，他们之间没有最好只有更适合！\n\n\n\n首先，为了确保分布式锁可用，我们至少要确保锁的实现同时满足以下四个条件：\n\n1. **互斥性。**在任意时刻，只有一个客户端能持有锁。\n2. **不会发生死锁。**即使有一个客户端在持有锁的期间崩溃而没有主动解锁，也能保证后续其他客户端能加锁。\n3. **具有容错性。**只要大部分的Redis节点正常运行，客户端就可以加锁和解锁。\n4. **解铃还须系铃人。**加锁和解锁必须是同一个客户端，客户端自己不能把别人加的锁给解了。\n\n\n\n### 数据库实现\n\n基于数据库的实现方式的核心思想是：在数据库中创建一个表，表中包含**方法名**等字段，并在**方法名字段上创建唯一索引**，想要执行某个方法，就使用这个方法名向表中插入数据，成功插入则获取锁，执行完成后删除对应的行数据释放锁。\n\n（1）创建表\n\n要实现分布式锁，最简单的方式可能就是直接创建一张锁表，然后通过操作该表中的数据来实现了。\n\n当我们要锁住某个方法或资源时，我们就在该表中增加一条记录，想要释放锁的时候就删除这条记录。\n\n```mysql\nDROP TABLE IF EXISTS `method_lock`;\nCREATE TABLE `method_lock` (\n  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',\n  `method_name` varchar(64) NOT NULL COMMENT '锁定的方法名',\n  `desc` varchar(255) NOT NULL COMMENT '备注信息',\n  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `uidx_method_name` (`method_name`) USING BTREE\n) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';\n```\n\nUNIQUE 约束唯一标识数据库表中的每条记录,每个表可以有多个 UNIQUE 约束.\n\n\n\n（2）想要执行某个方法，就使用这个方法名向表中插入数据：\n\n```mysql\nINSERT INTO method_lock (method_name, desc) VALUES ('methodName', '测试的methodName');\n```\n\n因为我们对 method_name 做了唯一性约束，这里如果有多个请求同时提交到数据库的话，数据库会保证只有一个操作可以成功，那么我们就可以认为操作成功的那个线程获得了该方法的锁，可以执行方法体内容。\n\n\n\n（3）成功插入则获取锁，执行完成后删除对应的行数据释放锁：\n\n```mysql\ndelete from method_lock where method_name ='methodName';\n```\n\n\n\n**上面这种简单的实现有以下几个问题：**\n\n1. 这把锁强依赖数据库的可用性，数据库是一个单点，一旦数据库挂掉，会导致业务系统不可用。\n\n2. 这把锁没有失效时间，一旦解锁操作失败，就会导致锁记录一直在数据库中，其他线程无法再获得到锁。\n\n3. 这把锁只能是非阻塞的，因为数据的insert操作，一旦插入失败就会直接报错。没有获得锁的线程并不会进入排队队列，要想再次获得锁就要再次触发获得锁操作。\n\n4. 这把锁是非重入的，同一个线程在没有释放锁之前无法再次获得该锁。因为数据中数据已经存在了。\n\n\n\n**以上问题的解决方案：**\n\n1. 数据库是单点？\n\n   搞两个数据库，数据之前双向同步。一旦挂掉快速切换到备库上。\n\n2. 没有失效时间？\n\n   只要做一个定时任务，每隔一定时间把数据库中的超时数据清理一遍。\n\n3. 非阻塞的？\n\n   搞一个 while 循环，直到 insert 成功再返回成功。\n\n4. 非重入的？\n\n   在数据库表中加个字段，记录当前获得锁的机器的主机信息和线程信息，那么下次再获取锁的时候先查询数据库，如果当前机器的主机信息和线程信息在数据库可以查到的话，直接把锁分配给他就可以了。\n\n\n\n### Redis实现\n\n**1、选用Redis实现分布式锁原因：**\n\n1. Redis 有很高的性能； \n2. Redis 命令对此支持较好，实现起来比较方便\n\n**2、使用命令介绍：**\n\n在使用 Redis 实现分布式锁的时候，主要就会使用到这三个命令。\n\n1. SETNX\n\n```redis\nSETNX key val：\n当且仅当key不存在时，set一个key为val的字符串，返回1；若key存在，则什么都不做，返回0。\n```\n\n2. expire\n\n```\nexpire key timeout：\n为key设置一个超时时间，单位为second，超过这个时间锁会自动释放，避免死锁。\n```\n\n3. delete\n\n```\ndelete key：删除key1\n```\n\n**3、实现思想：**\n\n1. 获取锁的时候，使用 setnx 加锁，并使用 expire 命令为锁添加一个超时时间，超过该时间则自动释放锁，锁的 value 值为一个随机生成的 UUID，通过此在释放锁的时候进行判断。\n\n2. 获取锁的时候还设置一个获取的超时时间，若超过这个时间则放弃获取锁。\n\n3. 释放锁的时候，通过UUID判断是不是该锁，若是该锁，则执行delete进行锁释放。\n\n\n\n注解：UUID是128位长的数字，一般用16进制表示，结合机器的网卡、当地时间、一个随机数来生成UUID\n\n\n\n\n\n#### Redis分布式锁遇到的几个问题\n\n众所周知，reids锁是通过 setnx + expire 的方式实现的，setnx 保证只有在 key 不存在时才能set成功，expire 保证锁在非正常释放的情况下不会形成死锁。基本原理就是这个，但实际操作中我们需要注意几个问题：\n\n1. setnx 与 expire 是非原子性的，那么如果 setnx 执行成功、但 expire 未执行，那么锁也就无法过期自动删除了。解决方案：redis 提供命令 `set(key,1,30,nx)` 一步到位设置超时时间。\n\n2. 如果线程 A 先获得了锁，但是执行时间超过了锁的过期时间，锁自动释放了，那么线程 B 获得锁，不仅有可能导致执行结果出现并发不一致问题，如果 A 执行完了，那么还会删除掉 B 的锁。\n\n   解决方案如下：\n\n   1. 使用一个请求标识作为锁的 value 值，在删除前判断一下。\n   2. 使用一个守护线程，不断的更新锁过期时间，保证执行过程中锁不会释放。\n\n3. 判断 value 值与删除锁不是原子的。解决方案：使用 lua 脚本，保证判断与删除原子操作。\n\n \n\n参考资料：\n\n- [Redis分布式锁的正确实现方式（Java版） - 吴大山的博客 | Wudashan Blog](https://wudashan.cn/2017/10/23/Redis-Distributed-Lock-Implement/)\n\n\n\n### Zookeeper实现\n\nZooKeeper 是一个为分布式应用提供一致性服务的开源组件，它内部是一个分层的文件系统目录树结构，规定同一个目录下只能有一个唯一文件名。\n\n基本思路：每个客户端对某个方法加锁时，在 zookeeper 上的与该方法对应的指定节点的目录下，生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单，只需要判断有序节点中序号最小的一个。 当释放锁的时候，只需将这个瞬时节点删除即可。同时，其可以避免服务宕机导致的锁无法释放，而产生的死锁问题。\n\n**Zookeeper能不能解决前面提到的问题。**\n\n- 锁无法释放？使用 Zookeeper 可以有效的解决锁无法释放的问题，因为在创建锁的时候，客户端会在 ZK 中创建一个临时节点，一旦客户端获取到锁之后突然挂掉（Session 连接断开），那么这个临时节点就会自动删除掉。其他客户端就可以再次获得锁。\n- 非阻塞锁？使用 Zookeeper 可以实现阻塞的锁，客户端可以通过在ZK中创建顺序节点，并且在节点上绑定监听器，一旦节点有变化，Zookeeper 会通知客户端，客户端可以检查自己创建的节点是不是当前所有节点中序号最小的，如果是，那么自己就获取到锁，便可以执行业务逻辑了。\n- 不可重入？使用 Zookeeper 也可以有效的解决不可重入的问题，客户端在创建节点的时候，把当前客户端的主机信息和线程信息直接写入到节点中，下次想要获取锁的时候和当前最小的节点中的数据比对一下就可以了。如果和自己的信息一样，那么自己直接获取到锁，如果不一样就再创建一个临时的顺序节点，参与排队。\n- 单点问题？使用 Zookeeper 可以有效的解决单点问题，ZK是集群部署的，只要集群中有半数以上的机器存活，就可以对外提供服务。\n\n\n\n### 三种方案的比较   \n\n- 从实现的复杂性角度（从低到高）\n  Zookeeper >= 缓存 > 数据库\n\n- 从性能角度（从高到低）\n  缓存 > Zookeeper >= 数据库\n\n- 从可靠性角度（从高到低）\n  Zookeeper > 缓存 > 数据库\n\n\n\n\n参考资料\n\n- [基于Redis实现分布式锁 - 从小工到大家 - SegmentFault 思否](https://segmentfault.com/a/1190000015058486?utm_source=tag-newest#articleHeader0)\n- [分布式锁的几种实现方式 - CSDN博客](https://blog.csdn.net/qq_21873747/article/details/79485814)\n\n\n\n## 2. 单点登录\n\n### 1. 单点登录系统机制\n\n#### HTTP 无状态协议\n\nweb 应用采用 browser/server 架构，http 作为通信协议。http 是无状态协议，浏览器的每一次请求，服务器会独立处理，不与之前或之后的请求产生关联，这个过程用下图说明，三次请求/响应对之间没有任何联系\n\n![img](assets/Lusifer2018042722120001.png)\n\n但这也同时意味着，任何用户都能通过浏览器访问服务器资源，如果想保护服务器的某些资源，必须限制浏览器请求；要限制浏览器请求，必须鉴别浏览器请求，响应合法请求，忽略非法请求；要鉴别浏览器请求，必须清楚浏览器请求状态。既然 http 协议无状态，那就让服务器和浏览器共同维护一个状态吧！这就是会话机制\n\n#### 会话机制\n\n浏览器第一次请求服务器，服务器创建一个会话，并将会话的 `id` 作为响应的一部分发送给浏览器，浏览器存储会话 `id`，并在后续第二次和第三次请求中带上会话 `id`，服务器取得请求中的会话 `id` 就知道是不是同一个用户了，这个过程用下图说明，后续请求与第一次请求产生了关联\n\n![img](assets/Lusifer2018042722120002.png)\n\n服务器在内存中保存会话的两种方式\n\n- 请求参数\n- Cookie\n\n将会话 `id` 作为每一个请求的参数，服务器接收请求自然能解析参数获得会话 `id`，并借此判断是否来自同一会话，很明显，这种方式不靠谱。那就浏览器自己来维护这个会话 `id` 吧，每次发送 `http` 请求时浏览器自动发送会话 `id`，`cookie` 机制正好用来做这件事。`cookie` 是浏览器用来存储少量数据的一种机制，数据以 `key/value` 形式存储，浏览器发送 `http` 请求时自动附带 `cookie` 信息\n\n`tomcat` 会话机制当然也实现了 `cookie`，访问 `tomcat` 服务器时，浏览器中可以看到一个名为 `JSESSIONID` 的 `cookie`，这就是 `tomcat` 会话机制维护的会话 `id`，使用了 `cookie` 的请求响应过程如下图\n\n![img](assets/Lusifer2018042722120003.png)\n\n#### 登录状态\n\n有了会话机制，登录状态就好明白了，我们假设浏览器第一次请求服务器需要输入用户名与密码验证身份，服务器拿到用户名密码去数据库比对，正确的话说明当前持有这个会话的用户是合法用户，应该将这个会话标记为“已授权”或者“已登录”等等之类的状态，既然是会话的状态，自然要保存在会话对象中，`tomcat` 在会话对象中设置登录状态如下\n\n```java\nHttpSession session = request.getSession();\nsession.setAttribute(\"isLogin\", true);\n```\n\n用户再次访问时，`tomcat` 在会话对象中查看登录状态\n\n```java\nHttpSession session = request.getSession();\nsession.getAttribute(\"isLogin\");\n```\n\n实现了登录状态的浏览器请求服务器模型如下图描述\n\n![img](assets/Lusifer2018042722120004.png)\n\n每次请求受保护资源时都会检查会话对象中的登录状态，只有 `isLogin=true` 的会话才能访问，登录机制因此而实现\n\n### 2. 多系统登录的复杂性\n\nweb 系统早已从久远的单系统发展成为如今由多系统组成的应用群，面对如此众多的系统，用户难道要一个一个登录、然后一个一个注销吗？就像下图描述的这样\n\n![img](assets/Lusifer2018042722120005.png)\n\n\n\nweb 系统由单系统发展成多系统组成的应用群，复杂性应该由系统内部承担，而不是用户。无论 web 系统内部多么复杂，对用户而言，都是一个统一的整体，也就是说，用户访问 web 系统的整个应用群与访问单个系统一样，登录/注销只要一次就够了\n\n![img](assets/Lusifer2018042722120006.png)\n\n虽然单系统的登录解决方案很完美，但对于多系统应用群已经不再适用了，为什么呢？\n\n单系统登录解决方案的核心是 `cookie`，`cookie` 携带会话 `id` 在浏览器与服务器之间维护会话状态。但 `cookie` 是有限制的，这个限制就是 `cookie` 的域（通常对应网站的域名），浏览器发送 `http` 请求时会自动携带与该域匹配的 `cookie`，而不是所有 `cookie`\n\n![img](assets/Lusifer2018042722120007.png)\n\n既然这样，为什么不将 `web` 应用群中所有子系统的域名统一在一个顶级域名下，例如 `*.baidu.com`，然后将它们的 `cookie` 域设置为 `baidu.com`，这种做法理论上是可以的，甚至早期很多多系统登录就采用这种同域名共享 `cookie` 的方式。\n\n然而，可行并不代表好，共享 cookie 的方式存在众多局限。\n\n1. 应用群域名得统一\n\n2. 应用群各系统使用的技术（至少是 `web` 服务器）要相同，不然 `cookie` 的 `key` 值（`tomcat` 为 `JSESSIONID`）不同，无法维持会话，共享 `cookie` 的方式是无法实现跨语言技术平台登录的，比如`java`、`php`、`python` 系统之间\n\n3. `cookie` 本身不安全\n\n因此，我们需要一种全新的登录方式来实现多系统应用群的登录，这就是 **单点登录**\n\n### 3. 单点登录系统流程\n\n#### 什么是单点登录\n\n什么是单点登录？单点登录全称 Single Sign On（以下简称 SSO），是指在多系统应用群中登录一个系统，便可在其他所有系统中得到授权而无需再次登录，包括 **单点登录** 与 **单点注销** 两部分\n\n#### 单点登录\n\n相比于单系统登录，`sso` 需要一个独立的认证中心，只有认证中心能接受用户的用户名密码等安全信息，其他系统不提供登录入口，只接受认证中心的间接授权。间接授权通过令牌实现，`sso` 认证中心验证用户的用户名密码没问题，创建授权令牌，在接下来的跳转过程中，授权令牌作为参数发送给各个子系统，子系统拿到令牌，即得到了授权，可以借此创建局部会话，局部会话登录方式与单系统的登录方式相同。这个过程，也就是单点登录的原理，用下图说明\n\n![img](assets/Lusifer2018042722120008.png)\n\n下面对上图简要描述\n\n- 用户访问系统 1 的受保护资源，系统 1 发现用户未登录，跳转至 `sso` 认证中心，并将自己的地址作为参数\n- `sso` 认证中心发现用户未登录，将用户引导至登录页面\n- 用户输入用户名密码提交登录申请\n- `sso` 认证中心校验用户信息，创建用户与 `sso` 认证中心之间的会话，称为全局会话，同时创建授权令牌\n- `sso` 认证中心带着令牌跳转会最初的请求地址（系统1）\n- 系统1拿到令牌，去 `sso` 认证中心校验令牌是否有效\n- `sso` 认证中心校验令牌，返回有效，注册系统 1\n- 系统 1 使用该令牌创建与用户的会话，称为局部会话，返回受保护资源\n\n\n\n- 用户访问系统 2 的受保护资源\n- 系统2发现用户未登录，跳转至 `sso` 认证中心，并将自己的地址作为参数\n- `sso` 认证中心发现用户已登录，跳转回系统 2 的地址，并附上令牌\n- 系统 2 拿到令牌，去 `sso` 认证中心校验令牌是否有效\n- `sso` 认证中心校验令牌，返回有效，注册系统 2\n- 系统 2 使用该令牌创建与用户的局部会话，返回受保护资源\n\n用户登录成功之后，会与 `sso` 认证中心及各个子系统建立会话，用户与 `sso` 认证中心建立的会话称为全局会话，用户与各个子系统建立的会话称为局部会话，局部会话建立之后，用户访问子系统受保护资源将不再通过 `sso` 认证中心，全局会话与局部会话有如下约束关系\n\n- 局部会话存在，全局会话一定存在\n- 全局会话存在，局部会话不一定存在\n- 全局会话销毁，局部会话必须销毁\n\n\n\n#### 单点注销\n\n单点登录自然也要单点注销，在一个子系统中注销，所有子系统的会话都将被销毁，用下面的图来说明\n\n![img](assets/Lusifer2018042722120009.png)\n\n`sso` 认证中心一直监听全局会话的状态，一旦全局会话销毁，监听器将通知所有注册系统执行注销操作\n\n- 用户向系统 1 发起注销请求\n- 系统 1 根据用户与系统1建立的会话 `id` 拿到令牌，向 `sso` 认证中心发起注销请求\n- `sso` 认证中心校验令牌有效，销毁全局会话，同时取出所有用此令牌注册的系统地址\n- `sso` 认证中心向所有注册系统发起注销请求\n- 各注册系统接收 `sso` 认证中心的注销请求，销毁局部会话\n- `sso` 认证中心引导用户至登录页面\n\n\n\n参考资料：\n\n- [Java-微服务架构实战](http://www.funtl.com/2018/07/13/contents/Java-%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E5%AE%9E%E6%88%98/)\n\n- [单点登录原理与简单实现 - ywlaker - 博客园](https://www.cnblogs.com/ywlaker/p/6113927.html)\n\n\n\n### 思考\n\nSSO 全局token和局部token过期时间？\n\n\n\n\n\n## 3. 接口验证\n\n### 常用的会话管理\n\n- 基于 server-session 的管理方式\n- cookie-based 的管理方式\n- token-based 的管理方式\n\n参考资料：\n\n- [使用jwt完成sso单点登录 - CSDN博客](https://blog.csdn.net/qq_38401919/article/details/80535258)\n\n### 传统身份验证的方法\n\nHTTP 是一种没有状态的协议，也就是它并不知道是谁是访问应用。这里我们把用户看成是客户端，客户端使用用户名还有密码通过了身份验证，不过下回这个客户端再发送请求时候，还得再验证一下。\n\n解决的方法就是，当用户请求登录的时候，如果没有问题，我们在服务端生成一条记录，这个记录里可以说明一下登录的用户是谁，然后把这条记录的 ID 号发送给客户端，客户端收到以后把这个 ID 号存储在 Cookie 里，下次这个用户再向服务端发送请求的时候，可以带着这个 Cookie ，这样服务端会验证一个这个 Cookie 里的信息，看看能不能在服务端这里找到对应的记录，如果可以，说明用户已经通过了身份验证，就把用户请求的数据返回给客户端。\n\n上面说的就是 Session，我们需要在服务端存储为登录的用户生成的 Session ，这些 Session 可能会存储在内存，磁盘，或者数据库里。我们可能需要在服务端定期的去清理过期的 Session 。\n\n### 基于 Token 的身份验证方法\n\n使用基于 Token 的身份验证方法，在服务端不需要存储用户的登录记录。大概的流程是这样的：\n\n1. 客户端使用用户名跟密码请求登录\n2. 服务端收到请求，去验证用户名与密码\n3. 验证成功后，服务端会签发一个 Token，再把这个 Token 发送给客户端\n4. 客户端收到 Token 以后可以把它存储起来，比如放在 Cookie 里或者 Local Storage 里\n5. 客户端每次向服务端请求资源的时候需要带着服务端签发的 Token\n6. 服务端收到请求，然后去验证客户端请求里面带着的 Token，如果验证成功，就向客户端返回请求的数据\n\n### JWT\n\n实施 Token 验证的方法挺多的，还有一些标准方法，比如 JWT（读作：jot ，表示：JSON Web Tokens） 。\n\nJWT 标准的 Token 有三个部分：\n\n- header（头部）\n- payload（数据）\n- signature（签名）\n\n中间用点分隔开，并且都会使用 Base64 编码，所以真正的 Token 看起来像这样：\n\n```\neyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc\n```\n\n#### Header\n\n每个 JWT token 里面都有一个 header，也就是头部数据。里面包含了使用的算法，这个 JWT 是不是带签名的或者加密的。主要就是说明一下怎么处理这个 JWT token 。\n\n头部里包含的东西可能会根据 JWT 的类型有所变化，比如一个加密的 JWT 里面要包含使用的加密的算法。唯一在头部里面要包含的是 *alg* 这个属性，如果是加密的 JWT，这个属性的值就是使用的签名或者解密用的算法。如果是未加密的 JWT，这个属性的值要设置成 *none*。\n\n示例：\n\n```json\n{\n  \"alg\": \"HS256\"\n}\n```\n\n意思是这个 JWT 用的算法是 HS256。上面的内容得用 base64url 的形式编码一下，所以就变成这样：\n\n```json\neyJhbGciOiJIUzI1NiJ9\n```\n\n#### Payload\n\nPayload 里面是 Token 的具体内容，这些内容里面有一些是标准字段，你也可以添加其它需要的内容。下面是标准字段：\n\n- iss：Issuer，发行者\n- sub：Subject，主题\n- aud：Audience，观众\n- exp：Expiration time，过期时间\n- nbf：Not before\n- iat：Issued at，发行时间\n- jti：JWT ID\n\n比如下面这个 Payload ，用到了 *iss* 发行人，还有 *exp* 过期时间这两个标准字段。另外还有两个自定义的字段，一个是 *name* ，还有一个是 *admin* 。\n\n```json\n{\n \"iss\": \"ninghao.net\",\n \"exp\": \"1438955445\",\n \"name\": \"wanghao\",\n \"admin\": true\n}\n```\n\n使用 base64url 编码以后就变成了这个样子：\n\n```json\neyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ\n```\n\n#### Signature\n\nJWT 的最后一部分是 Signature ，这部分内容有三个部分，先是用 Base64 编码的 header.payload ，再用加密算法加密一下，加密的时候要放进去一个 Secret ，这个相当于是一个密码，这个密码秘密地存储在服务端。\n\n- header\n- payload\n- secret\n\n```json\nconst encodedString = base64UrlEncode(header) + \".\" + base64UrlEncode(payload); \nHMACSHA256(encodedString, 'secret');\n```\n\n处理完成以后看起来像这样：\n\n```json\nSwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc\n```\n\n最后这个在服务端生成并且要发送给客户端的 Token 看起来像这样：\n\n```json\neyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc\n```\n\n客户端收到这个 Token 以后把它存储下来，下回向服务端发送请求的时候就带着这个 Token 。服务端收到这个 Token ，然后进行验证，通过以后就会返回给客户端想要的资源。\n\n\n\n引用来源：\n\n- [基于 Token 的身份验证：JSON Web Token](https://ninghao.net/blog/2834)\n\n\n\n### token过期怎么办？\n\n过期时间为 2t，每次请求验证是否判断 exp < t，是的话 t 往后加一定的时间。\n\n\n\n- [Session与JWT（实现JWT刷新与后端限制授权） - 简书](https://www.jianshu.com/p/78e15a1ac7f2)\n\n\n\n\n## 4. 消息队列\n\n\n\n## 5. 云计算——IaaS、PaaS和SaaS\n\nIaaS 基础设施即服务\n\nPaaS 平台即服务\n\nSaaS 软件即服务\n\n\n\nBaaS 区块链即服务\n\n\n\n云计算、云服务、云存储 —— 全栈\n\n\n\n持续集成与持续交付"
  },
  {
    "path": "notes/分布式锁/分布式锁.md",
    "content": "![1547537715278](assets/1547537715278.png)"
  },
  {
    "path": "notes/分布式锁.md",
    "content": ""
  },
  {
    "path": "notes/大数据.md",
    "content": "学习Scala_进击大数据Spark生态圈-课程章节\nhttps://coding.imooc.com/class/chapter/215.html#Anchor\n\n\n\n\n\n"
  },
  {
    "path": "notes/如何选择自己的技术栈.md",
    "content": "# 如何选择自己的技术栈\n\n在编程的世界里，该如何选择自己的技术栈呢。学前端？学 APP 开发？对于 Java、C++、C#、Python、PHP 又如何选择呢？人工智能现如今这么火，是不是机器学习、深度学习更高级一些呢？那么程序员又如何修炼内功呢？\n\n暂时先更新了一个思维导图，更多内容请持续关注。感兴趣的同学，可以在首页底部找到 QQ 交流群一起交流。\n\n![](../assets/fullstack-tutorial-2019.png)\n\n![](assets/full-stack-dev-mind.svg)\n\n"
  },
  {
    "path": "notes/开源贡献.md",
    "content": "▲ [GO HOME](https://github.com/frank-lam/2019_campus_apply)   |  ▲ [开源贡献文档格式说明](https://github.com/frank-lam/fullstack-tutorial/wiki)\n\n# 开源小组，英雄招募令\n\n诚挚要请您加入我们的「全栈开发-开源小组」\n\n个人能力有限，欢迎志同道合的朋友们共同维护本仓库，一起加入开源世界。\n\n本仓库致力于成为全栈技术开发，不限于语言、算法。\n\n招募 Java，PHP，Python，Go，C++，Node.js，分布式中间件，前端，机器学习，深度学习，计算机视觉等等，板块维护者。当然只要是和技术相关都行啦，欢迎大家的加入。\n\n\n\n如果对开源贡献有兴趣，可以加入下方的 QQ 群和我们交流。\n\n<div align=\"left\"> <img src=\"assets/1541754016518.png\" width=\"400px\"/></div><br/>\n\n想要成为 Collaborators 协作者，请在 QQ 群里私信我\n\n![1543564983070](assets/1543564983070.png)\n\n\n\n\n\n如果只想进行技术交流学习，可直接加入本仓库的技术交流群。\n\n<div align=\"left\"> <img src=\"assets/1541989798280.png\" width=\"400px\"/></div><br/>\n\n"
  },
  {
    "path": "notes/微服务.md",
    "content": "<!-- TOC -->\n\n- [微服务简介](#微服务简介)\n    - [微服务四大问题](#微服务四大问题)\n    - [简介](#简介)\n    - [1. 构建单体应用模型](#1-构建单体应用模型)\n    - [2. 走向单体地狱](#2-走向单体地狱)\n    - [3. 微服务-解决复杂问题](#3-微服务-解决复杂问题)\n    - [4. 微服务的优点](#4-微服务的优点)\n    - [5. 微服务的缺点](#5-微服务的缺点)\n    - [6. 总结](#6-总结)\n\n<!-- /TOC -->\n\n\n# 微服务简介\n\n> 本文参考：[《微服务：从设计到部署》](https://github.com/DocsHome/microservices)\n\n\n## 微服务四大问题\n\n> 高可用、高并发、高性能，最大的问题：网络是不可靠的\n\n1. 客户端如何访问这么多的服务？\n   - API 网关\n\n2. 服务于服务之间如何通信？\n   - 同步通信：\n     - HTTP（Apache Http Client）\n     - RPC（Dubbo 只支持 Java，Apache Thrift，gRPC）\n   - 异步通信：\n     - 消息队列\n     - Kafka\n     - RabbitMQ\n     - RocketMQ\n3. 这么多服务，如何管理？\n   - 服务治理\n     - 服务注册与发现\n       - 基于客户端的服务注册与发现（Apache Zookeeper）\n       - 基于服务端的服务注册与发现（Netflix Eureka）\n4. 服务挂了，怎么办？\n\n   - 重试机制\n   - 服务熔断\n   - 服务降级\n   - 服务限流\n\n\n\n## 简介\n\n如今微服务倍受关注：文章、博客、社交媒体讨论和会议演讲。微服务正在迅速朝着[加德纳技术成熟度曲线（Gartner Hype cycle）](http://www.gartner.com/technology/research/methodologies/hype-cycle.jsp)的高峰前进。与此同时，也有持怀疑态度的软件社区人员认为微服务没什么新鲜可言。反对者声称它的思想只是面向服务架构（SOA）的重塑。然而，无论是炒作还是怀疑，不可否认，[微服务架构模式](http://microservices.io/patterns/microservices.html)具有非常明显的优势 — 特别是在实施敏捷开发和复杂的企业应用交付方面。\n\n本书的七个章节主要介绍如何设计、构建和部署微服务，这是本书的第一章。在此章节中，您将了解到微服务的由来和与传统[单体应用模式](http://microservices.io/patterns/monolithic.html)的对比。这本电子书描述了许多关于微服务架构方面的内容。无论是在项目意义还是实施方面，您都能了解到微服务架构模式的优点与缺点。\n\n我们先来看看为什么要考虑使用微服务。\n\n## 1. 构建单体应用模型\n\n我们假设，您开始开发一个打车应用，打算与 `Uber` 和 `Hailo` 竞争。经过初步交流和需求收集，您开始手动或者使用类似 `Rails`、`Spring Boot`、`Play` 或者 `Maven` 等平台来生成一个新项目。\n\n该新应用是一个模块化的六边形架构，如下图（一个简单的打车应用）所示：\n\n![img](assets/89d9bfed11ff35943269b24b23b866b1.png)\n\n该应用的核心是由模块实现的业务逻辑，它定义了服务、领域对象和事件。围绕核心的是与外部世界接口对接的适配器。适配器示例包括数据库访问组件、生产和消费消息的消息组件和暴露了 API 或实现了一个 UI 的 web 组件。\n\n尽管有一个逻辑模块化架构，但应用程序被作为一个单体进行打包和部署。实际格式取决于应用程序的语言和框架。例如，许多 Java 应用程序被打包成 WAR 文件部署在如 Tomcat 或者 Jetty 之类的应用服务器上。其他 Java 应用程序被打包成自包含 (self-contained) 的可执行 JAR。类似地， Rails 和 Node.js 应用程序被打包为有目录层次的结构\n\n以这种风格编写的应用是很常见的。他们很容易开发，因为我们的 IDE 和其他工具就是专注于构建单体应用。这些应用程序也很容易测试， 您可以通过简单地启动并使用如 Selenium 测试包来测试 UI 以轻松地实现端到端 (end-to-end) 测试。单体应用同样易于部署。您只需拷贝打包好的应用程序到服务器上。您还可以通过运行多个副本和结合负载均衡器来扩展应用。在项目的早期阶段，它可以良好运作。\n\n## 2. 走向单体地狱\n\n不幸的是，这种简单的方法有很大的局限性。成功的应用有一个趋势，随着时间推移而变得越来越臃肿。您的开发团队在每个冲刺阶段都要实现更多的用户需求，这意味着需要添加了许多行代码。几年之后，小而简单的应用将会逐渐成长成一个庞大的单体。为了给出一个极端示例，我最近和一位开发者做了交谈，他正在编写一个工具，该工具用于从他们的数百万行代码 (lines of code， LOC) 应用中分析出数千个 JAR 之间的依赖。我相信这是大量开发者在多年齐心协力下创造出了这样的野兽。\n\n一旦您的应用程序成为了一个庞大、复杂的单体，您的开发组织可能会陷入了一个痛苦的境地，敏捷开发和交付的任何一次尝试都将原地徘徊。一个主要问题是应用程序实在非常复杂。对于任何一个开发人员来说显得过于庞大，这是可以理解的。最终，正确修复 bug 和实现新功能变得非常困难而耗时。此外， 这种趋势就像是往下的螺旋。如果基本代码都令人难以理解，那么改变也不会变得正确，您最终得到的将是一个巨大且不可思议的大泥球。\n\n应用程序的规模也将减缓发展。应用程序越大，启动时间越长。我调查过开发者们的单体应用的大小和性能，一些报告的启动时间为 12 分钟。我也听说过应用程序启动需要 40 分钟以上的怪事。如果开发人员经常要重启应用服务器，那么很大一部分时间都是在等待中度过，他们的生产力将受到限制。\n\n另一个大问题是，复杂的单体应用本身就是持续部署的障碍。如今， SaaS 应用发展到了可以每天多次将变更推送到生产环境中。这对于复杂的单体来说非常困难，因为您需要重新部署整个应用程序才能更新其中任何一部分。 联想到我之前提到的漫长启动时间，这也不会是什么好事。此外，因变更所产生的影响通常不是很明确，您很可能需要做大量的手工测试。因此，持续部署是不可能做到的。\n\n当不同模块存在资源需求冲突时，单体应用可能难以扩展。例如，一个模块可能会执行 CPU 密集型图像处理逻辑，理想情况下是部署在 Amazon EC2 Compute Optimized 实例中。另一个模块可能是一个内存数据库，最适合部署到 EC2 Memory-optimized 实例。然而， 由于这些模块被部署在一起，您必须在硬件选择上做出妥协。\n\n单体应用的另一个问题是可靠性。因为所有模块都运行在同一进程中。任何模块的一个 bug，比如内存泄漏，可能会拖垮整个进程。此外，由于应用程序的所有实例都是相同的，该错误将影响到整个应用的可用性。\n\n最后但同样重要，单体应用使得采用新框架和语言变得非常困难。例如，我们假设您有 200 万行代码使用了 XYZ 框架编写。如果使用较新的 ABC 框架来重写整个应用，这将非常昂贵（在时间和成本方面），即使框架非常好。因此，这对于采用新技术是一个非常大的障碍。在项目开始时， 您无论选择何种新技术都会感到困扰。\n\n总结一下：您有一个成功的关键业务应用程序，它已经发展成为一个只有少数开发人员（如果有的话）能够理解的巨大单体。它使用了过时、非生产性技术编写，这使得招聘优秀开发人员变得非常困难。应用程序变得难以扩展，不可靠。因此敏捷开发和应用交付是不可能的。\n\n\n\n## 3. 微服务-解决复杂问题\n\n许多如 Amazon、 eBay 和 Netflix 这样的组织，已经采用现在所谓的微服务架构模式解决了这个问题，而不是构建一个臃肿的单体应用。它的思路是将应用程序分解成一套较小的互连服务。一个服务通常实现了一组不同的特性或功能，例如订单管理、客户管理等。每一个微服务都是一个迷你应用，它自己的六边形架构包括了业务逻辑以及多个适配器。\n\n一些微服务会暴露一个供其他微服务或应用客户端消费的 API。其他微服务可能实现了一个 web UI。在运行时，每个实例通常是一个云虚拟机 (virtual machine， VM) 或者一个 Docker 容器。\n\n例如，前面描述的系统可能分解成如下图（一个单体应用分解成微服务) 所示：\n\n![img](assets/858f9ae6c861c8c93cd5379be54f9fc1.png)\n\n应用程序的每个功能区域现在都由自己的微服务实现。此外，Web 应用程序被划分为一组更简单的 Web 应用程序。例如，以我们的出租车为例，一个是乘客的应用，一个是司机的应用。这使得它更容易地为特定的用户、司机、设备或者专门的用例部署不同的场景。每个后端服务暴露一个 REST API，大部分服务消费的 API 由其他服务提供。例如， Driver Management 使用了 Notification 服务器来通知一个可用司机一个可选路程。UI 服务调用了其他服务来渲染页面。服务也可以使用异步、基于消息的通信。\n\n一些 REST API 也暴露给移动端应用以供司机和乘客使用。然而，应用不能直接访问后端服务。相反，他们之间的通信是由一个称为 API 网关 (API Gateway) 的中介负责。 API 网关负责负载均衡、缓存、访问控制、 API 计量和监控， 可以通过使用 NGINX 来实现。\n\n开发和交付中的伸缩立方：\n\n![img](assets/0714fcab4f6d5951014e5613657c8289.png)\n\n微服务架构模式相当于此伸缩立方的 Y 轴坐标，此立方是一个来自《架构即未来》 的三维伸缩模型。另外两个坐标轴是由运行多个相同应用程序副本的负载均衡器组成的X 轴坐标和 Z 轴坐标（或数据分区) ，其中请求的属性（例如，一行记录的主键或者客户标识) 用于将请求路由到特定的服务器。\n\n在运行时，X 坐标轴上运行着服务的多个实例，每个服务配合负载均衡器以满足吞吐量和可用性。某些应用程序也有可能使用 Z 坐标轴来进行分区服务。下图展示了如何用 Docker 将 Trip Management 服务部署到 Amazon EC2 上运行。\n\n使用 Docker 部署 Trip Management 服务：\n\n![img](assets/ca6ddafaa8b506ae4d45dde53ee5efb6-5612127.png)\n\n在运行时，Trip Management 服务由多个服务实例组成，每个服务实例是一个 Docker容器。为了实现高可用，容器是在多个云虚拟机上运行的。服务实例的之前是一个类似 NGINX 的负载均衡器，用于跨实例分发请求。负载均衡器也可以处理其他问题，如缓存、访问控制、API 度量和监控。\n\n微服务架构模式明显影响到了应用程序与数据库之间的关系。与其他共享单个数据库模式 (schema) 服务有所不同， 其每一个服务都有自己的数据库模式。一方面，这种做法与企业级数据库数据模型的想法相背，此外，它经常导致部分数据冗余。然而，如果您想从微服务中受益，每一个服务都应该有自己的数据库模式。因为它能实现松耦合。下图展示了数据库架构示例应用程序。\n\n每个服务都拥有各自的数据库。而且，服务可以使用一种最适合其需求、号称多语言持久架构 （polyglot persistence architecture ) 的数据库。例如，DriverManagement，要找到与潜在乘客接近的司机，就必须使用支持高效地理查询的数据库。\n\n打车应用的数据库架构：\n\n![img](assets/384781ccf56d3057df5acd198e8d1f3d.png)\n\n从表面上看，微服务架构模式类似于 SOA。微服务是由一组服务组成。然而，换另一种方式去思考微服务架构模式，它是没有商业化的 SOA，没有集成 Web 服务规范 (WS-) 和企业服务总线 (Enterprise Service Bus， ESB) 。基于微服务的应用支持更简单、轻量级的协议，例如，REST，而不是 WS-。他们也尽量避免使用 ESB，而是实现微服务本身具有类似 ESB 的功能。微服务架构也拒绝了 SOA 的其他部分，例如，数据访问规范模式概念。\n\n\n\n## 4. 微服务的优点\n\n微服务架构模式有许多非常好的地方。\n\n第一，它解决了复杂问题。它把可能会变得庞大的单体应用程序分解成一套服务。虽然功能数量不变，但是应用程序已经被分解成可管理的块或者服务。每个服务都有一个明确定义边界的方式，如远程过程调用（RPC）驱动或消息驱动 API。微服务架构模式强制一定程度的模块化，实际上，使用单体代码来实现是极其困难的。因此，使用微服务架构模式，个体服务能被更快地开发，并更容易理解与维护。\n\n第二，这种架构使得每个服务都可以由一个团队独立专注开发。开发者可以自由选择任何符合服务 API 契约的技术。当然，更多的组织是希望通过技术选型限制来避免完全混乱的状态。然而，这种自由意味着开发人员不再有可能在这种自由的新项目开始时使用过时的技术。当编写一个新服务时，他们可以选择当前的技术。此外，由于服务较小，使用当前技术重写旧服务将变得更加可行。\n\n第三，微服务架构模式可以实现每个微服务独立部署。开发人员根本不需要去协调部署本地变更到服务。这些变更一经测试即可立即部署。比如，UI 团队可以执行 A|B 测试，并快速迭代 UI 变更。微服务架构模式使得持续部署成为可能。\n\n最后，微服务架构模式使得每个服务能够独立扩展。您可以仅部署满足每个服务的容量和可用性约束的实例数目。此外，您可以使用与服务资源要求最匹配的硬件。例如，您可以在 EC2 Compute Optimized 实例上部署一个 CPU 密集型图像处理服务，并且在 EC2 Memory-optimized 实例上部署一个内存数据库服务。\n\n\n\n## 5. 微服务的缺点\n\n就像 Fred Brooks 大约在 30 年前写的《人月神话》中说的，没有银弹。与其他技术一样，微服务架构模式也存在着缺点。其中一个缺点就是名称本身。微服务这个术语的重点过多偏向于服务的规模。事实上，有些开发者主张构建极细粒度的 10 至 100 LOC（代码行） 服务，虽然这对于小型服务可能比较好，但重要的是要记住，小型服务只是一种手段，而不是主要目标。微服务的目标在于充分分解应用程序以方便应用敏捷开发和部署。\n\n微服务另一个主要缺点是由于微服务是一个分布式系统，其使得整体变得复杂。开发者需要选择和实现基于消息或者 RPC 的进程间通信机制。此外，由于目标请求可能很慢或者不可用，他们必须要编写代码来处理局部故障。虽然这些并不是很复杂、高深，但模块间通过语言级方法/过程调用相互调用，这比单体应用要复杂得多。\n\n微服务的另一个挑战是分区数据库架构。更新多个业务实体的业务事务是相当普遍的。这些事务在单体应用中的实现显得微不足道，因为单体只存在一个单独的数据库。在基于微服务的应用程序中，您需要更新不同服务所用的数据库。通常不会选择分布式事务，不仅仅是因为 **CAP 定理**。他们根本不支持如今高度可扩展的 NoSQL 数据库和消息代理。您最后不得不使用基于最终一致性的方法，这对于开发人员来说更具挑战性。\n\n测试微服务应用程序也很复杂。例如，使用现代框架如 Spring Boot，只需要编写一个测试类来启动一个单体 web 应用程序并测试其 REST API。相比之下，一个类似的测试类对于微服务来说需要启动该服务及其所依赖的所有服务，或者至少为这些服务配置存根。再次声明，虽然这不是一件高深的事情，但不要低估了这样做的复杂性。\n\n微服务架构模式的另一个主要挑战是实现了跨越多服务变更。例如，我们假设您正在实现一个变更服务 A、服务 B 和 服务 C 的需求，其中 A 依赖于 B，且 B 依赖于 C。在单体应用程序中，您可以简单地修改相应的模块、整合变更并一次性部署他们。相反，在微服务中您需要仔细规划和协调出现的变更至每个服务。例如，您需要更新服务 C，然后更新服务 B，最后更新服务 A。幸运的是，大多数变更只会影响一个服务，需要协调的多服务变更相对较少。\n\n部署基于微服务的应用程序也是相当复杂的。一个单体应用可以很容易地部署到基于传统负载均衡器的一组相同服务器上。每个应用程序实例都配置有基础设施服务的位置（主机和端口），比如数据库和消息代理。相比之下，微服务应用程序通常由大量的服务组成。例如，据 Adrian Cockcroft 了解到，Hailo 拥有 160 个不同的服务，Netflix 拥有的服务超过 600 个。\n\n每个服务都有多个运行时实例。还有更多的移动部件需要配置、部署、扩展和监控。此外，您还需要实现服务发现机制，使得服务能够发现需要与之通信的任何其他服务的位置（主机和端口）。比较传统麻烦的基于票据（ticket-based）和手动的操作方式无法扩展到如此复杂程度。因此，要成功部署微服务应用程序，需要求开发人员能高度控制部署方式和高度自动化。\n\n一种自动化方式是使用现成的平台即服务（PaaS），如 Cloud Foundry。PaaS 为开发人员提供了一种简单的方式来部署和管理他们的微服务。它让开发人员避开了诸如采购和配置 IT 资源等烦恼。同时，配置 PaaS 的系统人员和网络专业人员可以确保达到最佳实践以落实公司策略。\n\n自动化微服务部署的另一个方式是开发自己的 PaaS。一个普遍的起点是使用集群方案，如 Kubernetes，与 Docker 等容器技术相结合。\n\n\n\n## 6. 总结\n\n构建复杂的微服务应用程序本质上是困难的。单体架构模式只适用于简单、轻量级的应用程序，如果您使用它来构建复杂应用，您最终会陷入痛苦的境地。微服务架构模式是复杂、持续发展应用的一个更好的选择。尽管它存在着缺点和实现挑战。\n\n"
  },
  {
    "path": "notes/技术交流群.md",
    "content": "▲ [GO HOME](https://github.com/frank-lam/2019_campus_apply)\n\n# 技术交流互动\n\n\n\n## 1. 微信交流群\n\n由于微信群二维码只有七天的有效期，大家可加我的微信（**frank_lin5**），拉你进群。\n\n这里没有广告，只有纯粹的技术问题分享，不定期的干货和学习资源分享\n\n<div align=\"left\"> <img src=\"https://github.com/frank-lam/public-static-resources/blob/main/assets/wechat/wx-group-qrcode.png?raw=true\" width=\"400px\"/></div>\n\n\n\n## 2. QQ 交流群\n\n技术交流、租房交流、健身交流、恋爱交流哈哈哈。\n\n为了提高群交流质量，减少广告营销，请大家进群后请修改群名片。格式：“昵称-方向”；例如：“Frank-后台”，“卤蛋-大数据”，“Jack-前端”，“Nancy-运营”等等。\n\n\n\n本群工作日每天早晨 9:10 为您送上新鲜日报！\n\nQQ 技术交流群：862619503\n\n<div align=\"left\"> <img src=\"assets/1541989798280.png\" width=\"400px\"/></div>\n\n\n\n## 3. 微信订阅号\n\n<div align=\"left\"> <img src=\"../assets/wechat/wx-green.png\" width=\"90%\"/></div>"
  },
  {
    "path": "notes/技术点实践.md",
    "content": "# 技术点实践\n\n"
  },
  {
    "path": "notes/持续集成.md",
    "content": "![img](assets/Travis.png)\n\nKrishna Venkata's Blog\nhttps://kvenkata986.github.io/\n\n![20181224210950](assets/20181224210950.png)"
  },
  {
    "path": "notes/操作系统.md",
    "content": "<!-- TOC -->\n\n- [前言](#前言)\n- [一、概述](#一概述)\n    - [1. 操作系统基本特征](#1-操作系统基本特征)\n        - [1. 并发](#1-并发)\n        - [2. 共享](#2-共享)\n        - [3. 虚拟](#3-虚拟)\n        - [4. 异步](#4-异步)\n    - [2. 操作系统基本功能](#2-操作系统基本功能)\n        - [1. 进程管理](#1-进程管理)\n        - [2. 内存管理](#2-内存管理)\n        - [3. 文件管理](#3-文件管理)\n        - [4. 设备管理](#4-设备管理)\n    - [3. 系统调用](#3-系统调用)\n    - [4. 大内核和微内核](#4-大内核和微内核)\n        - [1. 大内核](#1-大内核)\n        - [2. 微内核](#2-微内核)\n    - [5. 中断分类](#5-中断分类)\n        - [1. 外中断](#1-外中断)\n        - [2. 异常](#2-异常)\n        - [3. 陷入](#3-陷入)\n    - [6. 什么是堆和栈？说一下堆栈都存储哪些数据？](#6-什么是堆和栈说一下堆栈都存储哪些数据)\n    - [7. 如何理解分布式锁？](#7-如何理解分布式锁)\n- [二、进程管理](#二进程管理)\n    - [1. 进程与线程](#1-进程与线程)\n        - [1. 进程](#1-进程)\n        - [2. 线程](#2-线程)\n        - [3. 区别](#3-区别)\n    - [2. 进程状态的切换（生命周期）](#2-进程状态的切换生命周期)\n    - [3. 进程调度算法](#3-进程调度算法)\n        - [1. 批处理系统](#1-批处理系统)\n            - [1.1 先来先服务](#11-先来先服务)\n            - [1.2 短作业优先](#12-短作业优先)\n            - [1.3 最短剩余时间优先](#13-最短剩余时间优先)\n        - [2. 交互式系统](#2-交互式系统)\n            - [2.1 时间片轮转](#21-时间片轮转)\n            - [2.2 优先级调度](#22-优先级调度)\n            - [2.3 多级反馈队列](#23-多级反馈队列)\n        - [3. 实时系统](#3-实时系统)\n    - [4. 进程同步](#4-进程同步)\n        - [1. 临界区](#1-临界区)\n        - [2. 同步与互斥](#2-同步与互斥)\n        - [3. 信号量](#3-信号量)\n            - [使用信号量实现生产者-消费者问题](#使用信号量实现生产者-消费者问题)\n        - [4. 管程](#4-管程)\n            - [使用管程实现生产者-消费者问题](#使用管程实现生产者-消费者问题)\n    - [5. 经典同步问题](#5-经典同步问题)\n        - [1. 读者-写者问题](#1-读者-写者问题)\n        - [2. 哲学家进餐问题](#2-哲学家进餐问题)\n    - [6. 进程通信](#6-进程通信)\n        - [* 进程通信方式](#-进程通信方式)\n            - [直接通信](#直接通信)\n            - [间接通信](#间接通信)\n        - [1. 管道](#1-管道)\n        - [2. 命名管道](#2-命名管道)\n        - [3. 消息队列](#3-消息队列)\n        - [4. 信号量](#4-信号量)\n        - [5. 共享内存](#5-共享内存)\n        - [6. 套接字](#6-套接字)\n    - [7. 线程间通信和进程间通信](#7-线程间通信和进程间通信)\n        - [线程间通信](#线程间通信)\n        - [进程间通信](#进程间通信)\n    - [8. 进程操作](#8-进程操作)\n        - [创建一个进程](#创建一个进程)\n        - [父子进程的共享资源](#父子进程的共享资源)\n        - [fork()函数的出错情况](#fork函数的出错情况)\n        - [创建共享空间的子进程](#创建共享空间的子进程)\n        - [在函数内部调用vfork](#在函数内部调用vfork)\n        - [退出进程](#退出进程)\n        - [exit函数与内核函数的关系](#exit函数与内核函数的关系)\n        - [设置进程所有者](#设置进程所有者)\n    - [9. 孤儿进程和僵尸进程](#9-孤儿进程和僵尸进程)\n        - [基本概念](#基本概念)\n        - [问题及危害](#问题及危害)\n        - [测试代码](#测试代码)\n        - [僵尸进程解决办法](#僵尸进程解决办法)\n    - [10. 守护进程](#10-守护进程)\n    - [11. 上下文切换](#11-上下文切换)\n- [三、死锁](#三死锁)\n    - [1. 什么是死锁](#1-什么是死锁)\n    - [2. 死锁的必要条件](#2-死锁的必要条件)\n    - [3. 死锁的处理方法](#3-死锁的处理方法)\n        - [1. 处理死锁的策略](#1-处理死锁的策略)\n        - [2. 死锁检测与死锁恢复](#2-死锁检测与死锁恢复)\n        - [3. 死锁预防](#3-死锁预防)\n        - [4. 死锁避免](#4-死锁避免)\n    - [4. 如何在写程序的时候就避免死锁](#4-如何在写程序的时候就避免死锁)\n- [四、内存管理](#四内存管理)\n    - [1. 虚拟内存](#1-虚拟内存)\n    - [2. 分页系统地址映射](#2-分页系统地址映射)\n    - [3. 页面置换算法](#3-页面置换算法)\n        - [1. 最佳](#1-最佳)\n        - [2. 最近最久未使用](#2-最近最久未使用)\n        - [3. 最近未使用](#3-最近未使用)\n        - [4. 先进先出](#4-先进先出)\n        - [5. 第二次机会算法](#5-第二次机会算法)\n        - [6. 时钟](#6-时钟)\n    - [4. 分段](#4-分段)\n    - [5. 段页式](#5-段页式)\n    - [6. 分页与分段的比较](#6-分页与分段的比较)\n- [五、设备管理](#五设备管理)\n    - [1. 磁盘结构](#1-磁盘结构)\n    - [2. 磁盘调度算法](#2-磁盘调度算法)\n        - [1. 先来先服务](#1-先来先服务)\n        - [2. 最短寻道时间优先](#2-最短寻道时间优先)\n        - [3. 电梯算法](#3-电梯算法)\n- [六、链接](#六链接)\n    - [1. 编译系统](#1-编译系统)\n        - [1. 预处理阶段 (Preprocessing phase)](#1-预处理阶段-preprocessing-phase)\n        - [2. 编译阶段 (Compilation phase)](#2-编译阶段-compilation-phase)\n        - [3. 汇编阶段 (Assembly phase)](#3-汇编阶段-assembly-phase)\n        - [4. 链接阶段 (Linking phase)](#4-链接阶段-linking-phase)\n    - [2. 静态链接](#2-静态链接)\n    - [3. 目标文件](#3-目标文件)\n    - [4. 动态链接](#4-动态链接)\n- [参考资料](#参考资料)\n- [更新说明](#更新说明)\n\n<!-- /TOC -->\n# 前言\n\n在本文将深入展开在面试过程中操作系统部分的知识，用最简短的篇章深入理解。\n\n\n\n参考资料：\n\n- 《Linux+C程序设计大全》第四版，清华大学出版社，配套源码：[linux_c_program_design](https://github.com/frank-lam/linux_c_program_design)\n- 《后台开发：核心技术与应用实践》\n- 《操作系统》清华大学(向勇、陈渝) ，在线课程\n- 部分信息来自：[CyC2018/CS-Notes](https://github.com/CyC2018/CS-Notes/blob/master/notes/%E8%AE%A1%E7%AE%97%E6%9C%BA%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F.md)\n\n\n\n# 一、概述\n\n## 1. 操作系统基本特征\n\n### 1. 并发\n\n并发是指宏观上在一段时间内能同时运行多个程序，而并行则指同一时刻能运行多个指令。\n\n并行需要硬件支持，如多流水线或者多处理器。\n\n操作系统通过引入进程和线程，使得程序能够并发运行。\n\n<div align=\"center\"><img src=\"assets/concurrent_and_parallel.png\" width=\"\"></div>\n\n### 2. 共享\n\n共享是指系统中的资源可以被多个并发进程共同使用。\n\n有两种共享方式：互斥共享和同时共享。\n\n**互斥共享**的资源称为临界资源，例如打印机等，在同一时间只允许一个进程访问，需要用同步机制来实现对临界资源的访问。\n\n### 3. 虚拟\n\n虚拟技术把一个物理实体转换为多个逻辑实体。\n\n利用多道程序设计技术，让每个用户都觉得有一个计算机专门为他服务。\n\n主要有两种虚拟技术：**时分复用技术**和**空分复用技术**。例如多个进程能在同一个处理器上并发执行使用了时分复用技术，让每个进程轮流占有处理器，每次只执行一小个时间片并快速切换。\n\n### 4. 异步\n\n异步指进程不是一次性执行完毕，而是走走停停，以不可知的速度向前推进。\n\n但只要运行环境相同，OS需要保证程序运行的结果也要相同。\n\n\n\n## 2. 操作系统基本功能\n\n### 1. 进程管理\n\n进程控制、进程同步、进程通信、死锁处理、处理机调度等。\n\n### 2. 内存管理\n\n内存分配、地址映射、内存保护与共享、虚拟内存等。\n\n### 3. 文件管理\n\n文件存储空间的管理、目录管理、文件读写管理和保护等。\n\n### 4. 设备管理\n\n完成用户的 I/O 请求，方便用户使用各种设备，并提高设备的利用率。\n\n主要包括缓冲管理、设备分配、设备处理、虛拟设备等。\n\n\n\n## 3. 系统调用\n\n如果一个进程在**用户态**需要使用**内核态**的功能，就进行系统调用从而陷入内核，由操作系统代为完成。\n\n<div align=\"center\"><img src=\"assets/tGPV0.png\" width=\"600\"/></div>\n\nLinux 的系统调用主要有以下这些：\n\n| Task     | Commands                    |\n| -------- | --------------------------- |\n| 进程控制 | fork(); exit(); wait();     |\n| 进程通信 | pipe(); shmget(); mmap();   |\n| 文件操作 | open(); read(); write();    |\n| 设备操作 | ioctl(); read(); write();   |\n| 信息维护 | getpid(); alarm(); sleep(); |\n| 安全     | chmod(); umask(); chown();  |\n\n\n\n## 4. 大内核和微内核\n\n### 1. 大内核\n\n大内核是将操作系统功能作为一个紧密结合的整体放到内核。\n\n由于各模块共享信息，因此有很高的性能。\n\n### 2. 微内核\n\n由于操作系统不断复杂，因此将一部分操作系统功能移出内核，从而降低内核的复杂性。移出的部分根据分层的原则划分成若干服务，相互独立。\n\n在微内核结构下，操作系统被划分成小的、定义良好的模块，只有微内核这一个模块运行在内核态，其余模块运行在用户态。\n\n因为需要频繁地在用户态和核心态之间进行切换，所以会有一定的性能损失。\n\n<div align=\"center\"><img src=\"assets/2_14_microkernelArchitecture.jpg\" width=\"600\"/></div><br/>\n\n## 5. 中断分类\n\n### 1. 外中断\n\n由 CPU 执行指令以外的事件引起，如 I/O 完成中断，表示设备输入/输出处理已经完成，处理器能够发送下一个输入/输出请求。此外还有时钟中断、控制台中断等。\n\n### 2. 异常\n\n由 CPU 执行指令的内部事件引起，如非法操作码、地址越界、算术溢出等。\n\n### 3. 陷入\n\n在用户程序中使用系统调用。\n\n\n\n| 类型     | 源头                     | 响应方式   | 处理机制                             |\n| -------- | ------------------------ | ---------- | ------------------------------------ |\n| 中断     | 外设                     | 异步       | 持续，对用户应用程序是透明的         |\n| 异常     | 应用程序意想不到的行为   | 同步       | 杀死或重新执行意想不到的应用程序指令 |\n| 系统调用 | 应用程序请求操作提供服务 | 异步或同步 | 等待和持续                           |\n\n\n\n## 6. 什么是堆和栈？说一下堆栈都存储哪些数据？\n\n栈区（stack）— 由**编译器**自动分配释放 ，存放函数的参数值，局部变量的值等。其操作方式类似于数据结构中的栈。\n\n堆区（heap） — 一般由**程序员分配释放**， 若程序员不释放，程序结束时可能由OS回收 。\n\n\n\n数据结构中这两个完全就不放一块来讲，数据结构中栈和队列才是好基友，我想新手也很容易区分。 \n\n我想需要区分的情况肯定不是在数据结构话题下，而大多是在 OS 关于不同对象的内存分配这块上。 \n\n简单讲的话，在 C 语言中： \n\n```c\nint a[N];   // go on a stack\nint* a = (int *)malloc(sizeof(int) * N);  // go on a heap\n```\n\n<div align=\"center\"><br/><img src=\"pics/stack_and_heap.jpg\" width=\"550\"/></div>\n\n## 7. 如何理解分布式锁？\n\n- 分布式锁，是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中，常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源，那么访问这些资源的时候，往往需要互斥来防止彼此干扰来保证一致性，在这种情况下，便需要使用到分布式锁。 \n\n\n\n# 二、进程管理\n\n## 1. 进程与线程\n\n<div align=\"center\"><img src=\"pics/process-and-thread.png\" width=\"\"/></div><br/>\n\n### 1. 进程\n\n**进程是资源分配的基本单位**，用来管理资源（例如：内存，文件，网络等资源）\n\n进程控制块 (Process Control Block, PCB) 描述进程的基本信息和运行状态，所谓的创建进程和撤销进程，都是指对 PCB 的操作。**（PCB是描述进程的数据结构）**\n\n下图显示了 4 个程序创建了 4 个进程，这 4 个进程可以并发地执行。\n\n<div align=\"center\"><img src=\"assets/a6ac2b08-3861-4e85-baa8-382287bfee9f.png\" width=\"600\"/></div><br/>\n\n### 2. 线程\n\n线程是独立调度的基本单位。\n\n一个进程中可以有多个线程，它们共享进程资源。\n\nQQ 和浏览器是两个进程，浏览器进程里面有很多线程，例如 HTTP 请求线程、事件响应线程、渲染线程等等，线程的并发执行使得在浏览器中点击一个新链接从而发起 HTTP 请求时，浏览器还可以响应用户的其它事件。\n\n\n\n### 3. 区别\n\n（一）拥有资源\n\n进程是资源分配的基本单位，但是线程不拥有资源，线程可以访问隶属进程的资源。\n\n（二）调度\n\n线程是独立调度的基本单位，在同一进程中，线程的切换不会引起进程切换，从一个进程内的线程切换到另一个进程中的线程时，会引起进程切换。\n\n（三）系统开销\n\n由于创建或撤销进程时，系统都要为之分配或回收资源，如内存空间、I/O 设备等，所付出的开销远大于创建或撤销线程时的开销。类似地，在进行进程切换时，涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置，而线程切换时只需保存和设置少量寄存器内容，开销很小。\n\n（四）通信方面\n\n进程间通信 (IPC) 需要进程同步和互斥手段的辅助，以保证数据的一致性。而线程间可以通过直接读/写同一进程中的数据段（如全局变量）来进行通信。\n\n\n\n## 2. 进程状态的切换（生命周期）\n\n<div align=\"center\"><img src=\"pics/ProcessState.jpg\" width=\"600\"/></div>\n\n- **就绪状态（ready）**：等待被调度\n- **运行状态（running）**\n- **阻塞状态（waiting）**：等待资源\n\n\n\n应该注意以下内容：\n\n- 只有就绪态和运行态可以相互转换，其它的都是单向转换。就绪状态的进程通过调度算法从而获得 CPU 时间，转为运行状态；而运行状态的进程，在分配给它的 CPU 时间片用完之后就会转为就绪状态，等待下一次调度。\n- 阻塞状态是缺少需要的资源从而由运行状态转换而来，但是该资源不包括 CPU 时间，缺少 CPU 时间会从运行态转换为就绪态。\n- 进程只能自己阻塞自己，因为只有进程自身才知道何时需要等待某种事件的发生\n\n\n\n## 3. 进程调度算法\n\n> 网易有道面经\n\n不同环境的调度算法目标不同，因此需要针对不同环境来讨论调度算法。\n\n### 1. 批处理系统\n\n批处理系统没有太多的用户操作，在该系统中，调度算法目标是保证吞吐量和周转时间（从提交到终止的时间）。\n\n#### 1.1 先来先服务\n\n先来先服务 first-come first-serverd（FCFS）\n\n按照请求的顺序进行调度。\n\n有利于长作业，但不利于短作业，因为短作业必须一直等待前面的长作业执行完毕才能执行，而长作业又需要执行很长时间，造成了短作业等待时间过长。\n\n#### 1.2 短作业优先\n\n短作业优先 shortest job first（SJF）\n\n按估计运行时间最短的顺序进行调度。\n\n长作业有可能会饿死，处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来，那么长作业永远得不到调度。\n\n#### 1.3 最短剩余时间优先\n\n最短剩余时间优先 shortest remaining time next（SRTN）\n\n按估计剩余时间最短的顺序进行调度。\n\n\n\n### 2. 交互式系统\n\n交互式系统有大量的用户交互操作，在该系统中调度算法的目标是快速地进行响应。\n\n#### 2.1 时间片轮转\n\n将所有就绪进程按 FCFS （先来先服务） 的原则排成一个队列，每次调度时，把 CPU 时间分配给队首进程，该进程可以执行一个时间片。当时间片用完时，由计时器发出时钟中断，调度程序便停止该进程的执行，并将它送往就绪队列的末尾，同时继续把 CPU 时间分配给队首的进程。\n\n时间片轮转算法的效率和时间片的大小有很大关系。因为进程切换都要保存进程的信息并且载入新进程的信息，如果时间片太小，会导致进程切换得太频繁，在进程切换上就会花过多时间。\n\n<div align=\"center\"><img src=\"assets/8c662999-c16c-481c-9f40-1fdba5bc9167.png\" width=\"\"/></div><br/>\n\n#### 2.2 优先级调度\n\n为每个进程分配一个优先级，按优先级进行调度。\n\n为了防止低优先级的进程永远等不到调度，可以随着时间的推移增加等待进程的优先级。\n\n\n\n#### 2.3 多级反馈队列\n\n如果一个进程需要执行 100 个时间片，如果采用时间片轮转调度算法，那么需要交换 100 次。\n\n多级队列是为这种需要连续执行多个时间片的进程考虑，它设置了多个队列，每个队列时间片大小都不同，例如 1,2,4,8,..。进程在第一个队列没执行完，就会被移到下一个队列。这种方式下，之前的进程只需要交换 7 次。\n\n每个队列优先权也不同，最上面的优先权最高。因此只有上一个队列没有进程在排队，才能调度当前队列上的进程。\n\n可以将这种调度算法看成是**时间片轮转调度算法和优先级调度算法**的结合。\n\n\n\n<div align=\"center\"><img src=\"assets/1-140629155KQ11.jpg\" width=\"\"/></div><br/>\n\n\n\n### 3. 实时系统\n\n实时系统要求一个请求在一个确定时间内得到响应。\n\n分为**硬实时和软实时**，前者必须满足绝对的截止时间，后者可以容忍一定的超时。\n\n\n\n参考资料：\n\n- [操作系统典型调度算法_C语言中文网](http://c.biancheng.net/cpp/html/2595.html)\n\n\n\n## 4. 进程同步\n\n### 1. 临界区\n\n**对临界资源进行访问的那段代码称为临界区**。\n\n为了互斥访问临界资源，每个进程在进入临界区之前，需要先进行检查。\n\n```java\n// entry section\n// critical section;\n// exit section\n```\n\n### 2. 同步与互斥\n\n- 同步：多个进程按一定顺序执行；\n- 互斥：多个进程在同一时刻只有一个进程能进入临界区。\n\n\n\n### 3. 信号量\n\n> P 和 V 是来源于两个荷兰语词汇，P() ---prolaag （荷兰语，尝试减少的意思），V() ---verhoog（荷兰语，增加的意思）  \n\n信号量（Semaphore）是一个整型变量，可以对其执行 down 和 up 操作，也就是常见的 P 和 V 操作。\n\n- **down** : 如果信号量大于 0 ，执行 -1 操作；如果信号量等于 0，进程睡眠，等待信号量大于 0；（阻塞）\n- **up** ：对信号量执行 +1 操作，唤醒睡眠的进程让其完成 down 操作。（唤醒）\n\ndown 和 up 操作需要被设计成原语，不可分割，通常的做法是在执行这些操作的时候屏蔽中断。\n\n如果信号量的取值只能为 0 或者 1，那么就成为了 **互斥量（Mutex）** ，0 表示临界区已经加锁，1 表示临界区解锁。\n\n```C++\ntypedef int semaphore;\nsemaphore mutex = 1;\nvoid P1() {\n    down(&mutex);\n    // 临界区\n    up(&mutex);\n}\n\nvoid P2() {\n    down(&mutex);\n    // 临界区\n    up(&mutex);\n}\n```\n\n#### 使用信号量实现生产者-消费者问题\n\n问题描述：使用一个缓冲区来保存物品，只有缓冲区没有满，生产者才可以放入物品；只有缓冲区不为空，消费者才可以拿走物品。\n\n因为缓冲区属于临界资源，因此需要使用一个互斥量 mutex 来控制对缓冲区的互斥访问。\n\n为了同步生产者和消费者的行为，需要记录缓冲区中物品的数量。数量可以使用信号量来进行统计，这里需要使用两个信号量：empty 记录空缓冲区的数量，full 记录满缓冲区的数量。其中，empty 信号量是在生产者进程中使用，当 empty 不为 0 时，生产者才可以放入物品；full 信号量是在消费者进程中使用，当 full 信号量不为 0 时，消费者才可以取走物品。\n\n注意，不能先对缓冲区进行加锁，再测试信号量。也就是说，不能先执行 down(mutex) 再执行 down(empty)。如果这么做了，那么可能会出现这种情况：生产者对缓冲区加锁后，执行 down(empty) 操作，发现 empty = 0，此时生产者睡眠。消费者不能进入临界区，因为生产者对缓冲区加锁了，也就无法执行 up(empty) 操作，empty 永远都为 0，那么生产者和消费者就会一直等待下去，造成死锁。\n\n```c++\n#define N 100\ntypedef int semaphore;\nsemaphore mutex = 1;\nsemaphore empty = N;\nsemaphore full = 0;\n\nvoid producer() {\n    while(TRUE){\n        int item = produce_item(); // 生产一个产品\n        // down(&empty) 和 down(&mutex) 不能交换位置，否则造成死锁\n        down(&empty); // 记录空缓冲区的数量，这里减少一个产品空间\n        down(&mutex); // 互斥锁\n        insert_item(item);\n        up(&mutex); // 互斥锁\n        up(&full); // 记录满缓冲区的数量，这里增加一个产品\n    }\n}\n\nvoid consumer() {\n    while(TRUE){\n        down(&full); // 记录满缓冲区的数量，减少一个产品\n        down(&mutex); // 互斥锁\n        int item = remove_item();\n        up(&mutex); // 互斥锁\n        up(&empty); // 记录空缓冲区的数量，这里增加一个产品空间\n        consume_item(item);\n    }\n}\n```\n\n### 4. 管程\n\n管程 (英语：Monitors，也称为监视器) 是一种程序结构，结构内的多个子程序（对象或模块）形成的多个工作线程互斥访问共享资源。\n\n使用信号量机制实现的生产者消费者问题需要客户端代码做很多控制，而管程把控制的代码独立出来，不仅不容易出错，也使得客户端代码调用更容易。\n\n管程是为了解决信号量在临界区的 PV 操作上的配对的麻烦，把配对的 PV 操作集中在一起，生成的一种并发编程方法。其中使用了条件变量这种同步机制。 \n\nc 语言不支持管程，下面的示例代码使用了类 Pascal 语言来描述管程。示例代码的管程提供了 insert() 和 remove() 方法，客户端代码通过调用这两个方法来解决生产者-消费者问题。\n\n```pascal\nmonitor ProducerConsumer\n    integer i;\n    condition c;\n\n    procedure insert();\n    begin\n        // ...\n    end;\n\n    procedure remove();\n    begin\n        // ...\n    end;\nend monitor;\n```\n\n管程有一个重要特性：在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程，否者其它进程永远不能使用管程。\n\n管程引入了 **条件变量** 以及相关的操作：**wait()** 和 **signal()** 来实现同步操作。对条件变量执行 wait() 操作会导致调用进程阻塞，把管程让出来给另一个进程持有。signal() 操作用于唤醒被阻塞的进程。\n\n#### 使用管程实现生产者-消费者问题\n\n```pascal\n// 管程\nmonitor ProducerConsumer\n    condition full, empty;\n    integer count := 0;\n    condition c;\n\n    procedure insert(item: integer);\n    begin\n        if count = N then wait(full);\n        insert_item(item);\n        count := count + 1;\n        if count = 1 then signal(empty);\n    end;\n\n    function remove: integer;\n    begin\n        if count = 0 then wait(empty);\n        remove = remove_item;\n        count := count - 1;\n        if count = N -1 then signal(full);\n    end;\nend monitor;\n\n// 生产者客户端\nprocedure producer\nbegin\n    while true do\n    begin\n        item = produce_item;\n        ProducerConsumer.insert(item);\n    end\nend;\n\n// 消费者客户端\nprocedure consumer\nbegin\n    while true do\n    begin\n        item = ProducerConsumer.remove;\n        consume_item(item);\n    end\nend;\n```\n\n\n\n## 5. 经典同步问题\n\n生产者和消费者问题前面已经讨论过了。\n\n### 1. 读者-写者问题\n\n允许多个进程同时对数据进行读操作，但是不允许读和写以及写和写操作同时发生。读者优先策略\n\n\n\nRcount：读操作的进程数量（Rcount=0）\n\nCountMutex：对于Rcount进行加锁（CountMutex=1）\n\nWriteMutex：互斥量对于写操作的加锁（WriteMutex=1）\n\n```java\nRcount = 0;\nsemaphore CountMutex = 1;\nsemaphore WriteMutex = 1;\n\nvoid writer(){\n    while(true){\n        sem_wait(WriteMutex);\n        // TO DO write();\n        sem_post(WriteMutex);\n    }\n}\n\n// 读者优先策略\nvoid reader(){\n    while(true){\n        sem_wait(CountMutex);\n        if(Rcount == 0)\n            sem_wait(WriteMutex);\n        Rcount++;\n        sem_post(CountMutex);\n        \n        // TO DO read();\n        \n        sem_wait(CountMutex);\n        Rcount--;\n        if(Rcount == 0)\n            sem_post(WriteMutex);\n        sem_post(CountMutex);\n\t}\n}\n\n```\n\n\n\n### 2. 哲学家进餐问题\n\n<div align=\"center\"><img src=\"assets/a9077f06-7584-4f2b-8c20-3a8e46928820.jpg\" width=\"300\"/></div><br/>\n\n\n\n五个哲学家围着一张圆桌，每个哲学家面前放着食物。哲学家的生活有两种交替活动：吃饭以及思考。当一个哲学家吃饭时，需要先拿起自己左右两边的两根筷子，并且一次只能拿起一根筷子。\n\n**方案一：**下面是一种错误的解法，考虑到如果所有哲学家同时拿起左手边的筷子，那么就无法拿起右手边的筷子，造成死锁。\n\n```java\n#define N 5\t\t   // 哲学家个数\nvoid philosopher(int i)  // 哲学家编号：0 － 4\n{\n    while(TRUE)\n    {\n        think();\t\t\t// 哲学家在思考\n        take_fork(i);\t\t\t// 去拿左边的叉子\n        take_fork((i + 1) % N);\t// 去拿右边的叉子\n        eat();\t\t\t\t// 吃面条中….\n        put_fork(i);\t\t\t// 放下左边的叉子\n        put_fork((i + 1) % N);\t// 放下右边的叉子\n    }\n}\n\n```\n\n\n\n**方案二：对拿叉子的过程进行了改进，但仍不正确**\n\n```java\n#define N 5\t // 哲学家个数\nwhile(1)  // 去拿两把叉子\n{       \n    take_fork(i);\t\t\t// 去拿左边的叉子\n    if(fork((i+1)%N)) {\t\t// 右边叉子还在吗\n    \ttake_fork((i + 1) % N);// 去拿右边的叉子\n    \tbreak;\t\t\t// 两把叉子均到手\n    }\n    else {\t\t\t\t// 右边叉子已不在\n    \tput_fork(i);\t\t// 放下左边的叉子\n    \twait_some_time();\t// 等待一会儿\n    }\n}\n\n```\n\n\n\n**方案三：等待时间随机变化。可行，但非万全之策**\n\n```java\n#define N 5\t // 哲学家个数\nwhile(1)  // 去拿两把叉子\n{       \n\ttake_fork(i);\t\t\t// 去拿左边的叉子\n\tif(fork((i+1)%N)) {\t\t// 右边叉子还在吗\n\t    take_fork((i + 1) % N);// 去拿右边的叉子\n\t    break;\t\t\t// 两把叉子均到手\n\t}\n\telse {\t\t\t\t// 右边叉子已不在\n\t    put_fork(i);\t\t// 放下左边的叉子\n\t    wait_random_time( );\t// 等待随机长时间\n\t}\n}\n\n```\n\n\n\n**方案四：互斥访问。正确，但每次只允许一人进餐**\n\n```java\nsemaphore mutex\t   // 互斥信号量，初值1\nvoid philosopher(int i)  // 哲学家编号i：0－4\t\n{\n\twhile(TRUE){\n\t    think();\t\t\t// 哲学家在思考\n\t    P(mutex);\t\t\t// 进入临界区\n\t    take_fork(i);\t\t\t// 去拿左边的叉子\n\t    take_fork((i + 1) % N);\t// 去拿右边的叉子\n\t    eat();\t\t\t\t// 吃面条中….\n\t    put_fork(i);\t\t\t// 放下左边的叉子\n\t    put_fork((i + 1) % N);\t// 放下右边的叉子\n\t    V(mutex);\t\t\t// 退出临界区\n\t}\n}\n\n```\n\n\n\n**正确方案如下：**\n\n为了防止死锁的发生，可以设置两个条件（临界资源）：\n\n- 必须同时拿起左右两根筷子；\n- 只有在两个邻居都没有进餐的情况下才允许进餐。\n\n```java\n//1. 必须由一个数据结构，来描述每个哲学家当前的状态\n#define N 5\n#define LEFT i // 左邻居\n#define RIGHT (i + 1) % N    // 右邻居\n#define THINKING 0\n#define HUNGRY   1\n#define EATING   2\ntypedef int semaphore;\nint state[N];                // 跟踪每个哲学家的状态\n\n//2. 该状态是一个临界资源，对它的访问应该互斥地进行\nsemaphore mutex = 1;         // 临界区的互斥\n\n//3. 一个哲学家吃饱后，可能要唤醒邻居，存在着同步关系\nsemaphore s[N];              // 每个哲学家一个信号量\n\nvoid philosopher(int i) {\n    while(TRUE) {\n        think();\n        take_two(i);\n        eat();\n        put_tow(i);\n    }\n}\n\nvoid take_two(int i) {\n    P(&mutex);  // 进入临界区\n    state[i] = HUNGRY; // 我饿了\n    test(i); // 试图拿两把叉子\n    V(&mutex); // 退出临界区\n    P(&s[i]); // 没有叉子便阻塞\n}\n\nvoid put_tow(i) {\n    P(&mutex);\n    state[i] = THINKING;\n    test(LEFT);\n    test(RIGHT);\n    V(&mutex);\n}\n\nvoid test(i) {         // 尝试拿起两把筷子\n    if(state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] !=EATING) {\n        state[i] = EATING;\n        V(&s[i]); // 通知第i个人可以吃饭了\n    }\n}\n```\n\n\n\n\n\n## 6. 进程通信\n\n进程同步与进程通信很容易混淆，它们的区别在于：\n\n- 进程同步：控制多个进程按一定顺序执行\n- 进程通信：进程间传输信息\n\n进程通信是一种手段，而进程同步是一种目的。也可以说，为了能够达到进程同步的目的，需要让进程进行通信，传输一些进程同步所需要的信息。\n\n\n\n\n\n### * 进程通信方式\n\n<div align=\"center\"><img src=\"pics/CommunicationsModels.jpg\" width=\"600\"/></div><br/>\n\n#### 直接通信\n\n发送进程直接把消息发送给接收进程，并将它挂在接收进程的消息缓冲队列上，接收进程从消息缓冲队列中取得消息。\n\nSend 和 Receive 原语的使用格式如下：\n\n```cpp\nSend(Receiver,message);//发送一个消息message给接收进程Receiver\nReceive(Sender,message);//接收Sender进程发送的消息message\n```\n\n\n\n#### 间接通信\n\n间接通信方式是指进程之间的通信需要通过作为**共享数据结构的实体**。该实体用来暂存发送进程发给目标进程的消息。 \n\n发送进程把消息发送到某个中间实体中，接收进程从中间实体中取得消息。这种中间实体一般称为信箱，这种通信方式又称为信箱通信方式。该通信方式广泛应用于计算机网络中，相应的通信系统称为电子邮件系统。\n\n\n\n### 1. 管道\n\n管道是通过调用 pipe 函数创建的，fd[0] 用于读，fd[1] 用于写。\n\n```c++\n#include <unistd.h>\nint pipe(int fd[2]);\n```\n\n它具有以下限制：\n\n- 只支持半双工通信（单向传输）；\n- 只能在父子进程中使用。\n\n<div align=\"center\"><img src=\"assets/53cd9ade-b0a6-4399-b4de-7f1fbd06cdfb.png\" width=\"450\"/></div>\n\n### 2. 命名管道\n\n也称为命名管道，去除了管道只能在父子进程中使用的限制。 \n\n```c++\n#include <sys/stat.h>\nint mkfifo(const char *path, mode_t mode);\nint mkfifoat(int fd, const char *path, mode_t mode);\n```\n\nFIFO 常用于客户-服务器应用程序中，FIFO 用作汇聚点，在客户进程和服务器进程之间传递数据。\n\n<div align=\"center\"><img src=\"assets/2ac50b81-d92a-4401-b9ec-f2113ecc3076.png\" width=\"500\"/></div>\n\n### 3. 消息队列\n\n间接（内核）\n\n相比于 FIFO，消息队列具有以下优点：\n\n- 消息队列可以独立于读写进程存在，从而避免了 FIFO 中同步管道的打开和关闭时可能产生的困难；\n- 避免了 FIFO 的同步阻塞问题，不需要进程自己提供同步方法；\n- 读进程可以根据消息类型有选择地接收消息，而不像 FIFO 那样只能默认地接收。\n\n\n\n### 4. 信号量\n\n它是一个计数器，用于为多个进程提供对共享数据对象的访问。\n\n\n\n### 5. 共享内存\n\n允许多个进程共享一个给定的存储区。因为数据不需要在进程之间复制，所以这是最快的一种 IPC。\n\n需要使用信号量用来同步对共享存储的访问。\n\n多个进程可以将同一个文件映射到它们的地址空间从而实现共享内存。另外 XSI 共享内存不是使用文件，而是使用使用内存的匿名段。\n\n\n\n### 6. 套接字\n\n与其它通信机制不同的是，它可用于不同机器间的进程通信。\n\n\n\n## 7. 线程间通信和进程间通信\n\n### 线程间通信\n\n- **synchronized同步**\n\n  - 这种方式，本质上就是 “共享内存” 式的通信。多个线程需要访问同一个共享变量，谁拿到了锁（获得了访问权限），谁就可以执行。\n\n- **while轮询的方式**\n\n  - 在这种方式下，ThreadA 不断地改变条件，ThreadB 不停地通过 while 语句检测这个条件 `(list.size()==5)` 是否成立 ，从而实现了线程间的通信。但是这种方式会浪费 CPU 资源。\n  - 之所以说它浪费资源，是因为 JVM 调度器将 CPU 交给 ThreadB 执行时，它没做啥 “有用” 的工作，只是在不断地测试某个条件是否成立。\n  - 就类似于现实生活中，某个人一直看着手机屏幕是否有电话来了，而不是：在干别的事情，当有电话来时，响铃通知TA电话来了。\n\n- **wait/notify机制**\n\n  - 当条件未满足时，ThreadA 调用 wait() 放弃 CPU，并进入阻塞状态。（不像 while 轮询那样占用 CPU）\n\n    当条件满足时，ThreadB 调用 notify() 通知线程 A，所谓通知线程 A，就是唤醒线程 A，并让它进入可运行状态。\n\n- **管道通信**\n\n  - java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信\n\n\n\n### 进程间通信\n\n- **管道（Pipe）** ：管道可用于具有亲缘关系进程间的通信，允许一个进程和另一个与它有共同祖先的进程之间进行通信。\n- **命名管道（named pipe）** ：命名管道克服了管道没有名字的限制，因此，除具有管道所具有的功能外，它还允许无亲缘关 系 进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。\n- **信号（Signal）** ：信号是比较复杂的通信方式，用于通知接受进程有某种事件发生，除了用于进程间通信外，进程还可以发送 信号给进程本身；Linux除了支持Unix早期信号语义函数sigal外，还支持语义符合Posix.1标准的信号函数sigaction（实际上，该函数是基于BSD的，BSD为了实现可靠信号机制，又能够统一对外接口，用sigaction函数重新实现了signal函数）。\n- **消息（Message）队列** ：消息队列是消息的链接表，包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息，被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少，管道只能承载无格式字节流以及缓冲区大小受限等缺\n- **共享内存** ：使得多个进程可以访问同一块内存空间，是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制，如信号量结合使用，来达到进程间的同步及互斥。\n- **内存映射（mapped memory）** ：内存映射允许任何多个进程间通信，每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。\n- **信号量（semaphore）** ：主要作为进程间以及同一进程不同线程之间的同步手段。\n- **套接口（Socket）** ：更为一般的进程间通信机制，可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的，但现在一般可以移植到其它类Unix系统上：linux和System V的变种都支持套接字。\n\n\n\n参考资料：\n\n- [Java线程与线程、进程与进程之间通信方式 | 理想村 | 屠杭镝的博客](https://www.tuhd.top/2017/08/04/2017-08-04-threadandprocess/)\n- [JAVA多线程之线程间的通信方式 - hapjin - 博客园](https://www.cnblogs.com/hapjin/p/5492619.html)\n\n\n\n\n\n## 8. 进程操作\n\nLinux进程结构可由三部分组成：\n\n- 代码段（程序）\n- 数据段（数据）\n- 堆栈段（控制块PCB）\n\n进程控制块是进程存在的惟一标识，系统通过PCB的存在而感知进程的存在。系统通过 PCB 对进程进行管理和调度。PCB 包括创建进程、执行进程、退出进程以及改变进程的优先级等。\n\n\n\n一般程序转换为进程分以下几个步骤：\n\n1. 内核将程序读入内存，为程序分配内存空间\n2. 内核为该进程分配进程标识符 PID 和其他所需资源\n3. 内核为进程保存 PID 及相应的状态信息，把进程放到运行队列中等待执行，程序转化为进程后可以被操作系统的调度程序调度执行了\n\n\n\n　　在 UNIX 里，除了进程 0（即 PID=0 的交换进程，Swapper Process）以外的所有进程都是由其他进程使用系统调用 fork 创建的，这里调用 fork 创建新进程的进程即为父进程，而相对应的为其创建出的进程则为子进程，因而除了进程 0 以外的进程都只有一个父进程，但一个进程可以有多个子进程。操作系统内核以进程标识符（Process Identifier，即 PID ）来识别进程。进程 0 是系统引导时创建的一个特殊进程，在其调用 fork 创建出一个子进程（即 PID=1 的进程 1，又称 init）后，进程 0 就转为交换进程（有时也被称为空闲进程），而进程1（init进程）就是系统里其他所有进程的祖先。\n\n\n\n　　进程0：Linux引导中创建的第一个进程，完成加载系统后，演变为进程调度、交换及存储管理进程。\n　　进程1：init 进程，由0进程创建，完成系统的初始化. 是系统中所有其它用户进程的祖先进程。\n\n　　Linux中 1 号进程是由 0 号进程来创建的，因此必须要知道的是如何创建 0 号进程，由于在创建进程时，程序一直运行在内核态，而进程运行在用户态，因此创建 0 号进程涉及到特权级的变化，即从特权级 0 变到特权级 3，Linux 是通过模拟中断返回来实现特权级的变化以及创建 0 号进程，通过将 0 号进程的代码段选择子以及程序计数器EIP直接压入内核态堆栈，然后利用 iret 汇编指令中断返回跳转到 0 号进程运行。\n\n\n\n### 创建一个进程\n\n进程是系统中基本的执行单位。Linux 系统允许任何一个用户进程创建一个子进程，创建成功后，子进程存在于系统之中，并且独立于父进程。该子进程可以接受系统调度，可以得到分配的系统资源。系统也可以检测到子进程的存在，并且赋予它与父进程同样的权利。\n\nLinux系统下使用 fork() 函数创建一个子进程，其函数原型如下：\n\n```c++\n#include <unistd.h>\npid_t fork(void);\n```\n\n在讨论 fork() 函数之前，有必要先明确父进程和子进程两个概念。除了 0 号进程（该进程是系统自举时由系统创建的）以外，Linux 系统中的任何一个进程都是由其他进程创建的。创建新进程的进程，即调用 fork() 函数的进程就是父进程，而新创建的进程就是子进程。\n\nfork() 函数不需要参数，返回值是一个进程标识符 (PID)。对于返回值，有以下 3 种情况：\n\n1. 对于父进程，fork() 函数返回新创建的子进程的 ID。\n\n2. 对于子进程，fork() 函数返回 0。由于系统的 0 号进程是内核进程，所以子进程的进程标识符不会是0，由此可以用来区别父进程和子进程。\n\n3. 如果创建出错，则 fork() 函数返回 -1。\n\nfork() 函数会创建一个新的进程，并从内核中为此进程分配一个新的可用的进程标识符 (PID)，之后，为这个新进程分配进程空间，并将父进程的进程空间中的内容复制到子进程的进程空间中，包括父进程的数据段和堆栈段，并且和父进程共享代码段（写时复制）。这时候，系统中又多了一个进程，这个进程和父进程一模一样，两个进程都要接受系统的调度。\n\n**注意**：由于在复制时复制了父进程的堆栈段，所以两个进程都停留在了 fork() 函数中，等待返回。因此，fork() 函数会返回两次，一次是在父进程中返回，另一次是在子进程中返回，这两次的返回值是不一样的。\n\n<div align=\"center\"><img src=\"assets/1534854268133.png\" width=\"400\"/></div>\n\n下面给出的示例程序用来创建一个子进程，该程序在父进程和子进程中分别输出不同的内容。 \n\n```C\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\nint main(void)\n{\n\tpid_t pid; // 保存进程ID\n\tpid = fork(); // 创建一个新进程\n\tif(pid < 0){ // fork出错\n\t\tprintf(\"fail to fork\\n\");\n\t\texit(1);\n\t}\n    else if(pid == 0){\t// 子进程\n        // 打印子进程的进程ID\n\t\tprintf(\"this is child, pid is : %u\\n\", getpid()); \n\t}\n    else{\n        // 打印父进程和其子进程的进程ID\n\t\tprintf(\"this is parent, pid is : %u, child-pid is : %u\\n\", getpid(), pid);\t\n\t}\n\treturn 0;\n}\n```\n\n程序运行结果如下：\n\n```shell\n$ ./fork \nParent, PID: 2598, Sub-process PID: 2599\nSub-process, PID: 2599, PPID: 2598\n```\n\n由于创建的新进程和父进程在系统看来是地位平等的两个进程，所以运行机会也是一样的，我们不能够对其执行先后顺序进行假设，先执行哪一个进程取决于系统的调度算法。如果想要指定运行的顺序，则需要执行额外的操作。正因为如此，程序在运行时并不能保证输出顺序和上面所描述的一致。\n\ngetpid() 是获得当前进程的pid，而 getppid() 则是获得父进程的 id。\n\n\n\n### 父子进程的共享资源\n\n子进程完全复制了父进程的地址空间的内容，包括堆栈段和数据段的内容。子进程并没有复制代码段，而是和父进程共用代码段。这样做是存在其合理依据的，因为子进程可能执行不同的流程，那么就会改变数据段和堆栈段，因此需要分开存储父子进程各自的数据段和堆栈段。但是代码段是只读的，不存在被修改的问题，因此这一个段可以让父子进程共享，以节省存储空间，如下图所示。\n\n<div align=\"center\"><img src=\"assets/1534855153169.png\" width=\"400\"/></div>\n\n下面给出一个示例来说明这个问题。该程序定义了一个全局变量 global、一个局部变量 stack 和一个指针 heap。该指针用来指向一块动态分配的内存区域。之后，该程序创建一个子进程，在子进程中修改 global、stack 和动态分配的内存中变量的值。然后在父子进程中分别打印出这些变量的值。由于父子进程的运行顺序是不确定的，因此我们先让父进程额外休眠2秒，以保证子进程先运行。\n\n```C++\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n// 全局变量，在数据段中\nint global; \nint main()\n{\n\tpid_t pid;\n\tint stack = 1; // 局部变量，在栈中\n\tint * heap;\n\theap = (int *)malloc(sizeof(int)); // 动态分配的内存，在堆中\n\t*heap = 2;\n\tpid = fork(); // 创建一个子进程\n\tif(pid < 0){ // 创建子进程失败\n\t\tprintf(\"fail to fork\\n\");\n\t\texit(1);\t\n\t}\n    else if(pid == 0){ // 子进程，改变各变量的值\n\t\tglobal++; // 修改栈、堆和数据段\n\t\tstack++;\n\t\t(*heap)++;\n\t\tprintf(\"the child, data : %d, stack : %d, heap : %d\\n\", global, stack, *heap);\n\t\texit(0); // 子进程运行结束\n\t}\n    // 父进程休眠2秒钟，保证子进程先运行\n\tsleep(2); \n    // 输出结果\n\tprintf(\"the parent, data : %d, stack : %d, heap : %d\\n\", global, stack, *heap);\n\treturn 0;\n}\n```\n\n程序运行效果如下：\n\n```\n$ ./fork \nIn sub-process, global: 2, stack: 2, heap: 3\nIn parent-process, global: 1, stack: 1, heap: 2\n```\n\n由于父进程休眠了2秒钟，子进程先于父进程运行，因此会先在子进程中修改数据段和堆栈段中的内容。因此不难看出，子进程对这些数据段和堆栈段中内容的修改并不会影响到父进程的进程环境。\n\n\n\n### fork()函数的出错情况\n\n有两种情况可能会导致fork()函数出错：\n\n1. 系统中已经有太多的进程存在了\n\n2. 调用fork()函数的用户进程太多了\n\n一般情况下，系统都会对一个用户所创建的进程数加以限制。如果操作系统不对其加限制，那么恶意用户可以利用这一缺陷攻击系统。下面是一个利用进程的特性编写的一个病毒程序，该程序是一个死循环，在循环中不断调用fork()函数来创建子进程，直到系统中不能容纳如此多的进程而崩溃为止。下图展示了这种情况：\n\n<div align=\"center\"><img src=\"assets/1534856965816.png\" width=\"600\"/></div>\n\n```C\n#include <unistd.h>\nint main()\n{\n\twhile(1)\n\t\tfork(); /* 不断地创建子进程，使系统中进程溢满 */\n\treturn 0;\n}\n```\n\n\n\n### 创建共享空间的子进程\n\n进程在创建一个新的子进程之后，子进程的地址空间完全和父进程分开。父子进程是两个独立的进程，接受系统调度和分配系统资源的机会均等，因此父进程和子进程更像是一对兄弟。如果父子进程共用父进程的地址空间，则子进程就不是独立于父进程的。\n\nLinux环境下提供了一个与 fork() 函数类似的函数，也可以用来创建一个子进程，只不过新进程与父进程共用父进程的地址空间，其函数原型如下：\n\n```c\n#include <unistd.h>\npid_t vfork(void);\n```\n\nvfork() 和 fork() 函数的区别有以下两点：\n\n1. vfork() 函数产生的子进程和父进程完全共享地址空间，包括代码段、数据段和堆栈段，子进程对这些共享资源所做的修改，可以影响到父进程。由此可知，vfork() 函数与其说是产生了一个进程，还不如说是产生了一个线程。\n\n2. vfork() 函数产生的子进程一定比父进程先运行，也就是说父进程调用了 vfork() 函数后会等待子进程运行后再运行。\n\n下面的示例程序用来验证以上两点。在子进程中，我们先让其休眠 2 秒以释放 CPU 控制权，在前面的 fork() 示例代码中我们已经知道这样会导致其他线程先运行，也就是说如果休眠后父进程先运行的话，则第 1 点则为假；否则为真。第 2 点为真，则会先执行子进程，那么全局变量便会被修改，如果第 1 点为真，那么后执行的父进程也会输出与子进程相同的内容。代码如下：\n\n```c\n//@file vfork.c\n//@brief vfork() usage\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n\nint global = 1;\n\nint main(void)\n{\n    pid_t pid;\n    int   stack = 1;\n    int  *heap;\n\n    heap = (int *)malloc(sizeof(int));\n    *heap = 1;\n\n    pid = vfork();\n    if (pid < 0)\n    {\n        perror(\"fail to vfork\");\n        exit(-1);\n    }\n    else if (pid == 0)\n    {\n        //sub-process, change values\n        sleep(2);//release cpu controlling\n        global = 999;\n        stack  = 888;\n        *heap  = 777;\n        //print all values\n        printf(\"In sub-process, global: %d, stack: %d, heap: %d\\n\",global,stack,*heap);\n        exit(0);\n    }\n    else\n    {\n        //parent-process\n        printf(\"In parent-process, global: %d, stack: %d, heap: %d\\n\",global,stack,*heap);\n    }\n\n    return 0;\n}\n```\n\n程序运行效果如下：\n\n```\n$ ./vfork \nIn sub-process, global: 999, stack: 888, heap: 777\nIn parent-process, global: 999, stack: 888, heap: 777\n```\n\n### 在函数内部调用vfork\n\n在使用 vfork() 函数时应该注意不要在任何函数中调用 vfork() 函数。下面的示例是在一个非 main 函数中调用了 vfork() 函数。该程序定义了一个函数 f1()，该函数内部调用了 vfork() 函数。之后，又定义了一个函数 f2()，这个函数没有实际的意义，只是用来覆盖函数 f1() 调用时的栈帧。main 函数中先调用 f1() 函数，接着调用 f2() 函数。\n\n```c\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n\nint f1(void)\n{\n    vfork();\n    return 0;\n}\n\nint f2(int a, int b)\n{\n    return a+b;\n}\n\nint main(void)\n{\n    int c;\n    \n    f1();\n    c = f2(1,2);\n    printf(\"%d\\n\",c);\n\n    return 0;\n}\n```\n\n程序运行效果如下： \n\n```\n$ ./vfork \n3\nSegmentation fault (core dumped)\n```\n\n通过上面的程序运行结果可以看出，一个进程运行正常，打印出了预期结果，而另一个进程似乎出了问题，发生了段错误。出现这种情况的原因可以用下图来分析一下：\n\n![1534857746158](assets/1534857746158.png)\n\n左边这张图说明调用 vfork() 之后产生了一个子进程，并且和父进程共享堆栈段，两个进程都要从 f1() 函数返回。由于子进程先于父进程运行，所以子进程先从 f1() 函数中返回，并且调用 f2() 函数，其栈帧覆盖了原来 f1() 函数的栈帧。当子进程运行结束，父进程开始运行时，就出现了右图的情景，父进程需要从 f1() 函数返回，但是 f1() 函数的栈帧已经被 f2() 函数的所替代，因此就会出现父进程返回出错，发生段错误的情况。\n\n由此可知，使用 vfork() 函数之后，子进程对父进程的影响是巨大的，其同步措施势在必行。\n\n\n\n### 退出进程\n\n当一个进程需要退出时，需要调用退出函数。Linux 环境下使用 exit() 函数退出进程，其函数原型如下：\n\n```c\n#include <stdlib.h>\nvoid exit(int status);\n```\n\nexit() 函数的参数表示进程的退出状态，这个状态的值是一个整型，保存在全局变量 `$ ?` 中，在 shell 中可以通过 `echo $?` 来检查退出状态值。\n\n注意：这个退出函数会深入内核注销掉进程的内核数据结构，并且释放掉进程的资源。\n\n\n\n### exit函数与内核函数的关系\n\nexit 函数是一个标准的库函数，其内部封装了 Linux 系统调用 exit() 函数。两者的主要区别在于 exit() 函数会在用户空间做一些善后工作，例如清理用户的 I/O 缓冲区，将其内容写入 磁盘文件等，之后才进入内核释放用户进程的地址空间；而 exit() 函数直接进入内核释放用户进程的地址空间，所有用户空间的缓冲区内容都将丢失。\n\n\n\n### 设置进程所有者\n\n每个进程都有两个用户 ID，实际用户 ID 和有效用户 ID。通常这两个 ID 的值是相等的，其取值为进程所有者的用户 ID。但是，在有些场合需要改变进程的有效用户 ID。Linux 环境下使用 setuid() 函数改变一个进程的实际用户ID和有效用户ID，其函数原型如下：\n\n```c\n#include <unistd.h>\nint setuid(uid_t uid);\n```\n\nsetuid() 函数的参数表示改变后的新用户 ID，如果成功修改当前进程的实际用户 ID 和有效用户 ID，函数返回值为 0；如果失败，则返回 -1。只有两种用户可以修改进程的实际用户 ID 和有效用户 ID：\n\n1. 根用户：根用户可以将进程的实际用户 ID 和有效用户 ID 更换。\n\n2. 其他用户：其该用户的用户 ID 等于进程的实际用户 ID 或者保存的用户 ID。\n\n也就是说，用户可以将自己的有效用户 ID 改回去。这种情况多出现于下面的情况：一个进程需要具有某种权限，所以将其有效用户 ID 设置为具有这种权限的用户 ID，当进程不需要这种权限时，进程还原自己之前的有效用户 ID，使自己的权限复原。下面给出一个修改的示例：\n\n```c\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\nint main(void)\n{\n\tFILE *fp;\n\tuid_t uid;\n\tuid_t euid;\n\tuid = getuid();\t\t/* 得到进程的实际用户ID */\n\teuid = geteuid();\t/* 得到进程的有效用户ID */\n\tprintf(\"the uid is : %d\\n\", uid);\n\tprintf(\"the euid is : %d\\n\", euid);\n\tif(setuid(8000) == -1){ /* 改变进程的实际用户ID和有效用户ID */\n\t\tperror(\"fail to set uid\");\n\t\texit(1);\n\t}\n\tprintf(\"after changing\\n\");\n\tuid = getuid();\t\t/* 再次得到进程的实际用户ID */\n\teuid = geteuid();\t/* 再次得到进程的有效用户ID */\n\tprintf(\"the uid is : %d\\n\", uid);\n\tprintf(\"the euid is : %d\\n\", euid);\n\treturn 0;\n}\n```\n\n程序运行效果如下： \n\n```\n$./setuid\nthe uid is : 0\nthe euid is : 0\nafter changing\nthe uid is : 8000\nthe euid is : 8000\n```\n\n\n\n本节参考：\n\n- 《后台开发：核心技术与应用实践》\n- 《Linux+C程序设计大全》十一章：进程控制\n\n- [进程控制(2): 进程操作 - XiaoManon - 博客园](https://www.cnblogs.com/xiaomanon/p/4201006.html)\n\n\n\n## 9. 孤儿进程和僵尸进程\n\n### 基本概念\n\n我们知道在 Unix/Linux 中，正常情况下，子进程是通过父进程创建的，子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程，即父进程永远无法预测子进程 到底什么时候结束。当一个进程完成它的工作终止之后，它的父进程需要调用 wait() 或者 waitpid() 系统调用取得子进程的终止状态。\n\n孤儿进程：一个父进程退出，而它的一个或多个子进程还在运行，那么那些子进程将成为孤儿进程。孤儿进程将被 init 进程（进程号为1）所收养，并由 init 进程对它们完成状态收集工作**。**\n\n僵尸进程：一个进程使用 fork 创建子进程，如果子进程退出，而父进程并没有调用 wait 或 waitpid 获取子进程的状态信息，那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。\n\n### 问题及危害\n\n　　Unix 提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息，就可以得到。这种机制就是：在每个进程退出的时候，内核释放该进程所有的资源，包括打开的文件，占用的内存等。但是仍然为其保留一定的信息（包括进程号 the process ID，退出状态 the termination status of the process，运行时间 the amount of CPU time taken by the process 等)。直到父进程通过 wait / waitpid 来取时才释放。但这样就导致了问题，如果进程不调用 wait / waitpid 的话， 那么保留的那段信息就不会释放，其进程号就会一直被占用，但是系统所能使用的进程号是有限的，如果大量的产生僵死进程，将因为没有可用的进程号而导致系统不能产生新的进程。此即为僵尸进程的危害，应当避免。\n\n　　孤儿进程是没有父进程的进程，孤儿进程这个重任就落到了 init 进程身上，init 进程就好像是一个民政局，专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候，内核就把孤 儿进程的父进程设置为 init，而 init 进程会循环地 wait() 它的已经退出的子进程。这样，当一个孤儿进程凄凉地结束了其生命周期的时候，init 进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。\n\n　　任何一个子进程（init除外）在exit() 之后，并非马上就消失掉，而是留下一个称为僵尸进程 (Zombie) 的数据结构，等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后，父进程没有来得及处理，这时用 ps 命令就能看到子进程的状态是 `Z`。如果父进程能及时处理，可能用 ps 命令就来不及看到子进程的僵尸状态，但这并不等于子进程不经过僵尸状态。如果父进程在子进程结束之前退出，则子进程将由 init 接管。 init 将会以父进程的身份对僵尸状态的子进程进行处理。\n\n　　僵尸进程危害场景：\n\n　　例如有个进程，它定期的产生一个子进程，这个子进程需要做的事情很少，做完它该做的事情之后就退出了，因此这个子进程的生命周期很短，但是，父进程只管生成新的子进程，至于子进程退出之后的事情，则一概不闻不问，这样，系统运行上一段时间之后，系统中就会存在很多的僵死进程，倘若用 ps 命令查看的话，就会看到很多状态为 `Z` 的进程。 严格地来说，僵死进程并不是问题的根源，罪魁祸首是产生出大量僵死进程的那个父进程。因此，当我们寻求如何消灭系统中大量的僵死进程时，答案就是把产生大 量僵死进程的那个元凶枪毙掉（也就是通过 kill 发送 SIGTERM 或者 SIGKILL 信号啦）。枪毙了元凶进程之后，它产生的僵死进程就变成了孤儿进程，这些孤儿进程会被 init 进程接管，init 进程会 wait() 这些孤儿进程，释放它们占用的系统进程表中的资源，这样，这些已经僵死的孤儿进程就能瞑目而去了。\n\n### 测试代码\n\n孤儿进程测试程序如下所示： \n\n```c\n#include <stdio.h>\n#include <stdlib.h>\n#include <errno.h>\n#include <unistd.h>\n\nint main()\n{\n    pid_t pid;\n    //创建一个进程\n    pid = fork();\n    //创建失败\n    if (pid < 0)\n    {\n        perror(\"fork error:\");\n        exit(1);\n    }\n    //子进程\n    if (pid == 0)\n    {\n        printf(\"I am the child process.\\n\");\n        //输出进程ID和父进程ID\n        printf(\"pid: %d\\tppid:%d\\n\",getpid(),getppid());\n        printf(\"I will sleep five seconds.\\n\");\n        //睡眠5s，保证父进程先退出\n        sleep(5);\n        printf(\"pid: %d\\tppid:%d\\n\",getpid(),getppid());\n        printf(\"child process is exited.\\n\");\n    }\n    //父进程\n    else\n    {\n        printf(\"I am father process.\\n\");\n        //父进程睡眠1s，保证子进程输出进程id\n        sleep(1);\n        printf(\"father process is  exited.\");\n    }\n    return 0;\n}\n```\n\n<div align=\"left\"><img src=\"assets/21000845-620318dcd34249d28a73cb3872591461.png\" width=\"\"/></div><br/>\n\n僵尸进程测试程序如下所示： \n\n```c\n#include <stdio.h>\n#include <unistd.h>\n#include <errno.h>\n#include <stdlib.h>\n\nint main()\n{\n    pid_t pid;\n    pid = fork();\n    if (pid < 0)\n    {\n        perror(\"fork error:\");\n        exit(1);\n    }\n    else if (pid == 0)\n    {\n        printf(\"I am child process.I am exiting.\\n\");\n        exit(0);\n    }\n    printf(\"I am father process.I will sleep two seconds\\n\");\n    //等待子进程先退出\n    sleep(2);\n    //输出进程信息\n    system(\"ps -o pid,ppid,state,tty,command\");\n    printf(\"father process is exiting.\\n\");\n    return 0;\n}\n```\n\n测试结果如下所示： \n\n<div align=\"left\"><img src=\"assets/21001428-8f9e134ec7dc44c49521cf3b16ceb418.png\" width=\"\"/></div><br/>\n\n\n\n### 僵尸进程解决办法\n\n- 通过信号机制\n  - 子进程退出时向父进程发送SIGCHILD信号，父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程\n- fork两次\n  - 将子进程成为孤儿进程，从而其的父进程变为 init 进程，通过 init 进程可以处理僵尸进程\n\n\n\n参考资料：\n\n- [孤儿进程与僵尸进程[总结] - Anker's Blog - 博客园](https://www.cnblogs.com/Anker/p/3271773.html)\n\n\n\n## 10. 守护进程\n\nLinux Daemon（守护进程）是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务，不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。\n\n守护进程一般在系统启动时开始运行，除非强行终止，否则直到系统关机都保持运行。守护进程经常以超级用户（root）权限运行，因为它们要使用特殊的端口（1-1024）或访问某些特殊的资源。\n\n一个守护进程的父进程是init进程，因为它真正的父进程在fork出子进程后就先于子进程exit退出了，所以它是一个由init继承的孤儿进程。守护进程是非交互式程序，没有控制终端，所以任何输出，无论是向标准输出设备stdout还是标准出错设备stderr的输出都需要特殊处理。\n\n守护进程的名称通常以d结尾，比如sshd、xinetd、crond等\n\n\n\n编写守护进程的一般步骤步骤：\n\n1. 在父进程中执行 fork 并 exit 推出；\n\n2. 在子进程中调用 setsid 函数创建新的会话；\n\n3. 在子进程中调用 chdir 函数，让根目录 `/` 成为子进程的工作目录；\n\n4. 在子进程中调用umask函数，设置进程的umask 为 0；\n\n5. 在子进程中关闭任何不需要的文件描述符\n\n\n\n参考资料：\n\n- [守护进程概念，以及怎么创建守护进程 - CSDN博客](https://blog.csdn.net/one_piece_hmh/article/details/52770111)\n\n\n\n\n## 11. 上下文切换\n\n上下文切换，有时也称做**进程切换或任务切换**，是指CPU从一个进程或线程切换到另一个进程或线程。 在操作系统中，CPU 切换到另一个进程需要保存当前进程的状态并恢复另一个进程的状态：当前运行任务转为就绪（或者挂起、删除）状态，另一个被选定的就绪任务成为当前任务\n\n<div align=\"center\"><img src=\"pics/ProcessSwitch.jpg\" width=\"500\"/></div><br/>\n\n\n\n\n\n# 三、死锁\n\n> 资源分类：（1）可重用资源；（2）消耗资源\n\n\n\n## 1. 什么是死锁\n\n造成死锁的原因就是多个线程或进程对同一个资源的争抢或相互依赖。一个最简单的解释就是你去面试，面试官问你告诉我什么是死锁，我就录用你，你回答面试官你录用我，我告诉你。  \n\n如果一个进程集合里面的每个进程都在等待只能由这个集合中的其他一个进程（包括他自身）才能引发的事件，这种情况就是死锁。\n\n这个定义可能有点拗口，下面用一个简单例子说明。\n\n资源 A、B，进程 C、D 描述如下：\n\n资源 A 和资源 B，都是不可剥夺资源，现在进程 C 已经申请了资源 A，进程 D 也申请了资源 B，进程 C 接下来的操作需要用到资源 B，而进程 D 恰好也在申请资源A，进程 C、D 都得不到接下来的资源，那么就引发了死锁。\n\n\n\n**然后套用回去定义**：如果一个进程集合里面（进程 C 和进程 D）的每个进程（进程 C 和进程 D）都在等待只能由这个集合中的其他一个进程（对于进程 C，他在等进程 D；对于进程 D，他在等进程 C）才能引发的事件（释放相应资源）。\n\n这里的资源包括了软的资源（代码块）和硬的资源（例如扫描仪）。\n资源一般可以分两种：**可剥夺资源（Preemptable）**和**不可剥夺资源 （Nonpreemptable）**。一般来说对于由可剥夺资源引起的死锁可以由系统的重新分配资源来解决，所以一般来说大家说的死锁都是由于不可剥夺资源所引起的。\n\n\n\n\n\n## 2. 死锁的必要条件\n\n<div align=\"center\"><img src=\"assets/c037c901-7eae-4e31-a1e4-9d41329e5c3e.png\" width=\"\"/></div>\n\n- 互斥：每个资源要么已经分配给了一个进程，要么就是可用的。\n- 占有和等待：已经得到了某个资源的进程可以再请求新的资源。\n- 不可抢占：已经分配给一个进程的资源不能强制性地被抢占，它只能被占有它的进程显式地释放。\n- 循环等待：有两个或者两个以上的进程组成一条环路，该环路中的每个进程都在等待下一个进程所占有的资源。\n\n\n\n## 3. 死锁的处理方法\n\n### 1. 处理死锁的策略\n\n- 鸵鸟策略\n  - 把头埋在沙子里，假装根本没发生问题。\n  - 因为解决死锁问题的代价很高，因此鸵鸟策略这种不采取任务措施的方案会获得更高的性能。当发生死锁时不会对用户造成多大影响，或发生死锁的概率很低，可以采用鸵鸟策略。\n  - 大多数操作系统，包括 Unix，Linux 和 Windows，处理死锁问题的办法仅仅是忽略它。\n\n- 检测死锁并且恢复。\n- 仔细地对资源进行动态分配，以避免死锁。\n- 通过破除死锁四个必要条件之一，来防止死锁产生。\n\n### 2. 死锁检测与死锁恢复\n\n不试图阻止死锁，而是当检测到死锁发生时，采取措施进行恢复。\n\n（一）每种类型一个资源的死锁检测\n\n<div align=\"center\"><img src=\"assets/b1fa0453-a4b0-4eae-a352-48acca8fff74.png\" width=\"500\"/></div>\n\n上图为资源分配图，其中方框表示资源，圆圈表示进程。资源指向进程表示该资源已经分配给该进程，进程指向资源表示进程请求获取该资源。\n\n图 a 可以抽取出环，如图 b，它满足了环路等待条件，因此会发生死锁。\n\n每种类型一个资源的死锁检测算法是通过检测有向图是否存在环来实现，从一个节点出发进行深度优先搜索，对访问过的节点进行标记，如果访问了已经标记的节点，就表示有向图存在环，也就是检测到死锁的发生。\n\n（二）每种类型多个资源的死锁检测\n\n<div align=\"center\"><img src=\"assets/e1eda3d5-5ec8-4708-8e25-1a04c5e11f48.png\" width=\"\"/></div>\n\n上图中，有三个进程四个资源，每个数据代表的含义如下：\n\n- E 向量：资源总量\n- A 向量：资源剩余量\n- C 矩阵：每个进程所拥有的资源数量，每一行都代表一个进程拥有资源的数量\n- R 矩阵：每个进程请求的资源数量\n\n进程 P1 和 P2 所请求的资源都得不到满足，只有进程 P3 可以，让 P3 执行，之后释放 P3 拥有的资源，此时 A = (2 2 2 0)。P2 可以执行，执行后释放 P2 拥有的资源，A = (4 2 2 1) 。P1 也可以执行。所有进程都可以顺利执行，没有死锁。\n\n算法总结如下：\n\n每个进程最开始时都不被标记，执行过程有可能被标记。当算法结束时，任何没有被标记的进程都是死锁进程。\n\n1. 寻找一个没有标记的进程 Pi，它所请求的资源小于等于 A。\n2. 如果找到了这样一个进程，那么将 C 矩阵的第 i 行向量加到 A 中，标记该进程，并转回 1。\n3. 如果没有这样一个进程，算法终止。\n\n（三）死锁恢复\n\n- 利用抢占恢复\n- 利用回滚恢复\n- 通过杀死进程恢复\n\n### 3. 死锁预防\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### 4. 死锁避免\n\n在程序运行时避免发生死锁，在使用前进行判断，只允许不会出现死锁的进程请求资源。\n\n（一）安全状态\n\n<div align=\"center\"><img src=\"assets/ed523051-608f-4c3f-b343-383e2d194470.png\" width=\"\"/></div>\n\n图 a 的第二列 Has 表示已拥有的资源数，第三列 Max 表示总共需要的资源数，Free 表示还有可以使用的资源数。从图 a 开始出发，先让 B 拥有所需的所有资源（图 b），运行结束后释放 B，此时 Free 变为 5（图 c）；接着以同样的方式运行 C 和 A，使得所有进程都能成功运行，因此可以称图 a 所示的状态时安全的。\n\n定义：如果没有死锁发生，并且即使所有进程突然请求对资源的最大需求，也仍然存在某种调度次序能够使得每一个进程运行完毕，则称该状态是安全的。\n\n安全状态的检测与死锁的检测类似，因为安全状态必须要求不能发生死锁。下面的银行家算法与死锁检测算法非常类似，可以结合着做参考对比。\n\n（二）单个资源的银行家算法\n\n一个小城镇的银行家，他向一群客户分别承诺了一定的贷款额度，算法要做的是判断对请求的满足是否会进入不安全状态，如果是，就拒绝请求；否则予以分配。\n\n<div align=\"center\"><img src=\"assets/d160ec2e-cfe2-4640-bda7-62f53e58b8c0.png\" width=\"\"/></div><br/>\n\n上图 c 为不安全状态，因此算法会拒绝之前的请求，从而避免进入图 c 中的状态。\n\n（三）多个资源的银行家算法\n\n<div align=\"center\"><img src=\"assets/62e0dd4f-44c3-43ee-bb6e-fedb9e068519.png\" width=\"\"/></div><br/>\n\n上图中有五个进程，四个资源。左边的图表示已经分配的资源，右边的图表示还需要分配的资源。最右边的 E、P 以及 A 分别表示：总资源、已分配资源以及可用资源，注意这三个为向量，而不是具体数值，例如 A=(1020)，表示 4 个资源分别还剩下 1/0/2/0。\n\n检查一个状态是否安全的算法如下：\n\n- 查找右边的矩阵是否存在一行小于等于向量 A。如果不存在这样的行，那么系统将会发生死锁，状态是不安全的。\n- 假若找到这样一行，将该进程标记为终止，并将其已分配资源加到 A 中。\n- 重复以上两步，直到所有进程都标记为终止，则状态时安全的。\n\n如果一个状态不是安全的，需要拒绝进入这个状态。\n\n## 4. 如何在写程序的时候就避免死锁\n\n> 网易有道面经\n\n所谓的死锁呢，发生的主要原因在于了有多个进程去竞争资源，也就是同时去抢占。\n\n可以自己写一个支持多线程的消息管理类，单开一个线程访问独占资源，其它线程用消息交互实现间接访问。\n这种机制适应性强、效率高，更适合多核环境。\n\n\n\n# 四、内存管理\n\n## 1. 虚拟内存\n\n虚拟内存的目的是为了让物理内存扩充成更大的逻辑内存，从而让程序获得更多的可用内存。\n\n为了更好的管理内存，操作系统将内存抽象成地址空间。每个程序拥有自己的地址空间，这个地址空间被分割成多个块，每一块称为一页。这些页被映射到物理内存，但不需要映射到连续的物理内存，也不需要所有页都必须在物理内存中。当程序引用到一部分不在物理内存中的地址空间时，由硬件执行必要的映射，将缺失的部分装入物理内存并重新执行失败的指令。\n\n从上面的描述中可以看出，虚拟内存允许程序不用将地址空间中的每一页都映射到物理内存，也就是说一个程序不需要全部调入内存就可以运行，这使得有限的内存运行大程序称为可能。例如有一台计算机可以产生 16 位地址，那么一个程序的地址空间范围是 0~64K。该计算机只有 32KB 的物理内存，虚拟内存技术允许该计算机运行一个 64K 大小的程序。\n\n<div align=\"center\"><img src=\"assets/7b281b1e-0595-402b-ae35-8c91084c33c1-1534863247346.png\" width=\"\"/></div>\n\n\n## 2. 分页系统地址映射\n\n- 内存管理单元（MMU）：管理着地址空间和物理内存的转换。\n- 页表（Page table）：页（地址空间）和页框（物理内存空间）的映射表。例如下图中，页表的第 0 个表项为 010，表示第 0 个页映射到第 2 个页框。页表项的最后一位用来标记页是否在内存中。\n\n下图的页表存放着 16 个页，这 16 个页需要用 4 个比特位来进行索引定位。因此对于虚拟地址（0010 000000000100），前 4 位是用来存储页面号，而后 12 位存储在页中的偏移量。\n\n（0010 000000000100）根据前 4 位得到页号为 2，读取表项内容为（110 1），它的前 3 为为页框号，最后 1 位表示该页在内存中。最后映射得到物理内存地址为（110 000000000100）。\n\n<div align=\"center\"><img src=\"assets/cf4386a1-58c9-4eca-a17f-e12b1e9770eb.png\" width=\"500\"/></div>\n\n\n\n\n\n\n## 3. 页面置换算法\n\n在程序运行过程中，如果要访问的页面不在内存中，就发生缺页中断从而将该页调入内存中。此时如果内存已无空闲空间，系统必须从内存中调出一个页面到磁盘对换区中来腾出空间。\n\n页面置换算法和缓存淘汰策略类似，可以将内存看成磁盘的缓存。在缓存系统中，缓存的大小有限，当有新的缓存到达时，需要淘汰一部分已经存在的缓存，这样才有空间存放新的缓存数据。\n\n页面置换算法的主要目标是使页面置换频率最低（也可以说缺页率最低）。\n\n### 1. 最佳\n\n> Optimal\n\n所选择的被换出的页面将是最长时间内不再被访问，通常可以保证获得最低的缺页率。\n\n是一种理论上的算法，因为无法知道一个页面多长时间不再被访问。\n\n举例：一个系统为某进程分配了三个物理块，并有如下页面引用序列：\n\n<div align=\"center\"><img src=\"assets/70120304230421201701.gif\" width=\"\"/></div>\n\n开始运行时，先将 7, 0, 1 三个页面装入内存。当进程要访问页面 2 时，产生缺页中断，会将页面 7 换出，因为页面 7 再次被访问的时间最长。\n\n### 2. 最近最久未使用\n\n> LRU, Least Recently Used\n\n虽然无法知道将来要使用的页面情况，但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出。\n\n为了实现 LRU，需要在内存中维护一个所有页面的链表。当一个页面被访问时，将这个页面移到链表表头。这样就能保证链表表尾的页面时最近最久未访问的。\n\n因为每次访问都需要更新链表，因此这种方式实现的 LRU 代价很高。\n\n<div align=\"center\"><img src=\"assets/68747470733a2f2f6c617465782e636f6465636f67732e636f6d2f6769662e6c617465783f34efbc8c37efbc8c30efbc8c37efbc8c31efbc8c30efbc8c31efbc8c32efbc8c31efbc8c32efbc8c36.gif\" width=\"\"/></div>\n\n<div align=\"center\"><img src=\"assets/eb859228-c0f2-4bce-910d-d9f76929352b.png\" width=\"\"/></div>\n\n\n\n\n### 3. 最近未使用\n\n> NRU, Not Recently Used\n\n每个页面都有两个状态位：R 与 M，当页面被访问时设置页面的 R=1，当页面被修改时设置 M=1。其中 R 位会定时被清零。可以将页面分成以下四类：\n\n- R=0，M=0\n- R=0，M=1\n- R=1，M=0\n- R=1，M=1\n\n当发生缺页中断时，NRU 算法随机地从类编号最小的非空类中挑选一个页面将它换出。\n\nNRU 优先换出已经被修改的脏页面（R=0，M=1），而不是被频繁使用的干净页面（R=1，M=0）。\n\n### 4. 先进先出\n\n> FIFO, First In First Out\n\n选择换出的页面是最先进入的页面。\n\n该算法会将那些经常被访问的页面也被换出，从而使缺页率升高。\n\n### 5. 第二次机会算法\n\nFIFO 算法可能会把经常使用的页面置换出去，为了避免这一问题，对该算法做一个简单的修改：\n\n当页面被访问 (读或写) 时设置该页面的 R 位为 1。需要替换的时候，检查最老页面的 R 位。如果 R 位是 0，那么这个页面既老又没有被使用，可以立刻置换掉；如果是 1，就将 R 位清 0，并把该页面放到链表的尾端，修改它的装入时间使它就像刚装入的一样，然后继续从链表的头部开始搜索。\n\n<div align=\"center\"><img src=\"assets/ecf8ad5d-5403-48b9-b6e7-f2e20ffe8fca.png\" width=\"\"/></div>\n\n\n### 6. 时钟\n\n> Clock\n\n第二次机会算法需要在链表中移动页面，降低了效率。时钟算法使用环形链表将页面链接起来，再使用一个指针指向最老的页面。\n\n<div align=\"center\"><img src=\"assets/5f5ef0b6-98ea-497c-a007-f6c55288eab1.png\" width=\"\"/></div>\n\n\n## 4. 分段\n\n虚拟内存采用的是分页技术，也就是将地址空间划分成固定大小的页，每一页再与内存进行映射。\n\n下图为一个编译器在编译过程中建立的多个表，有 4 个表是动态增长的，如果使用分页系统的一维地址空间，动态增长的特点会导致覆盖问题的出现。\n\n<div align=\"center\"><img src=\"assets/22de0538-7c6e-4365-bd3b-8ce3c5900216.png\" width=\"\"/></div>\n\n分段的做法是把每个表分成段，一个段构成一个独立的地址空间。每个段的长度可以不同，并且可以动态增长。\n\n<div align=\"center\"><img src=\"assets/e0900bb2-220a-43b7-9aa9-1d5cd55ff56e.png\" width=\"\"/></div>\n\n\n\n\n## 5. 段页式\n\n程序的地址空间划分成多个拥有独立地址空间的段，每个段上的地址空间划分成大小相同的页。这样既拥有分段系统的共享和保护，又拥有分页系统的虚拟内存功能。\n\n## 6. 分页与分段的比较\n\n- 对程序员的透明性：分页透明，但是分段需要程序员显示划分每个段。\n- 地址空间的维度：分页是一维地址空间，分段是二维的。\n- 大小是否可以改变：页的大小不可变，段的大小可以动态改变。\n- 出现的原因：分页主要用于实现虚拟内存，从而获得更大的地址空间；分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间并且有助于共享和保护。\n\n\n\n\n\n# 五、设备管理\n\n## 1. 磁盘结构\n\n- 盘面（Platter）：一个磁盘有多个盘面；\n- 磁道（Track）：盘面上的圆形带状区域，一个盘面可以有多个磁道；\n- 扇区（Track Sector）：磁道上的一个弧段，一个磁道可以有多个扇区，它是最小的物理储存单位，目前主要有 512 bytes 与 4 K 两种大小；\n- 磁头（Head）：与盘面非常接近，能够将盘面上的磁场转换为电信号（读），或者将电信号转换为盘面的磁场（写）；\n- 制动手臂（Actuator arm）：用于在磁道之间移动磁头；\n- 主轴（Spindle）：使整个盘面转动。\n\n\n\n<div align=\"center\"><img src=\"assets/moving-head-disk-machanism-1534926483596.jpg\" width=\"600\"/></div>\n\n\n\n<div align=\"center\"><img src=\"assets/ssd-ile-hdd-arasindaki-fark.jpg\" width=\"\"/></div>\n\n\n\n\n## 2. 磁盘调度算法\n\n读写一个磁盘块的时间的影响因素有：\n\n- 旋转时间（主轴旋转磁盘，使得磁头移动到适当的扇区上）\n- 寻道时间（制动手臂移动，使得磁头移动到适当的磁道上）\n- 实际的数据传输时间\n\n其中，寻道时间最长，因此磁盘调度的主要目标是使磁盘的平均寻道时间最短。\n\n### 1. 先来先服务\n\n> FCFS, First Come First Served\n\n- 按照磁盘请求的顺序进行调度\n- 公平对待所有进程\n- 在有很多进程的情况下，接近随机调度的性能\n\n- 优点是公平和简单。缺点也很明显，因为未对寻道做任何优化，使平均寻道时间可能较长。\n\n<div align=\"center\"><img src=\"assets/fcfs-scheduling.png\" width=\"400\"/></div>\n\n### 2. 最短寻道时间优先\n\n> SSTF, Shortest Seek Time First\n\n优先调度与当前磁头所在磁道距离最近的磁道。\n\n虽然平均寻道时间比较低，但是不够公平。如果新到达的磁道请求总是比一个在等待的磁道请求近，那么在等待的磁道请求会一直等待下去，也就是出现饥饿现象。具体来说，两边的磁道请求更容易出现饥饿现象。\n\n<div align=\"center\"><img src=\"assets/4e2485e4-34bd-4967-9f02-0c093b797aaa.png\" width=\"600\"/></div>\n\n\n\n\n### 3. 电梯算法\n\n> SCAN\n\n电梯总是保持一个方向运行，直到该方向没有请求为止，然后改变运行方向。\n\n电梯算法（扫描算法）和电梯的运行过程类似，总是按一个方向来进行磁盘调度，直到该方向上没有未完成的磁盘请求，然后改变方向。\n\n因为考虑了移动方向，因此所有的磁盘请求都会被满足，解决了 SSTF 的饥饿问题。\n\n<div align=\"center\"><img src=\"assets/271ce08f-c124-475f-b490-be44fedc6d2e.png\" width=\"600\"/></div>\n\n\n\n\n\n\n# 六、链接\n\n## 1. 编译系统\n\n以下是一个 hello.c 程序：\n\n```C\n#include <stdio.h>\nint main()\n{\n    printf(\"hello, world\\n\");\n    return 0;\n}\n```\n\n在 Unix 系统上，由编译器把源文件转换为目标文件。\n\n```\ngcc -o hello hello.c\n```\n\n这个过程大致如下：\n\n<div align=\"center\"><img src=\"assets/the-compilation-system.png\" width=\"700\"/></div>\n\n### 1. 预处理阶段 (Preprocessing phase) \n\n预处理（cpp）根据以字符 # 开头的命令，修改原始的 C 程序，生成扩展名为 .i 的文件。\n\n```shell\n$ gcc -E hello.c -o hello.i\n```\n\n### 2. 编译阶段 (Compilation phase) \n\n编译器（cc1）将文本文件 hello.i 翻译成文本文件 hello.s，它包含一个汇编语言程序。\n\n```shell\n$ gcc -S hello.i -o hello.s\n```\n\n### 3. 汇编阶段 (Assembly phase)\n\n编译器（as）将 hello.s 翻译成机器语言指令，把这些指令打包成一种叫做**可重定位目标程序**（relocatable object program）的格式，并将结果保存在目标文件 hello.o 中。\n\n```shell\n$ as hello.s -o hello.o\n```\n\n### 4. 链接阶段 (Linking phase)\n\n`printf` 函数是标准 C 库中的一个函数，在 `printf.o` 这个单独预编译好的目标文件中。连接器（ld）将 printf.o 和 hello.o 合并，结果得到 hello **可执行目标文件**。\n\n```shell\n$ gcc hello.o -o hello\n```\n\n\n\n## 2. 静态链接\n\n静态连接器以一组可重定向目标文件为输入，生成一个完全链接的可执行目标文件作为输出。链接器主要完成以下两个任务：\n\n- 符号解析：每个符号对应于一个函数、一个全局变量或一个静态变量，符号解析的目的是将每个符号引用与一个符号定义关联起来。\n- 重定位：链接器通过把每个符号定义与一个内存位置关联起来，然后修改所有对这些符号的引用，使得它们指向这个内存位置。\n\n<div align=\"center\"><img src=\"assets/static-ld.png\" width=\"400\"/></div>\n\n\n\n\n## 3. 目标文件\n\n- 可执行目标文件：可以直接在内存中执行；\n- 可重定向目标文件：可与其它可重定向目标文件在链接阶段合并，创建一个可执行目标文件；\n- 共享目标文件：这是一种特殊的可重定向目标文件，可以在运行时被动态加载进内存并链接；\n\n\n\n## 4. 动态链接\n\n静态库有以下两个问题：\n\n- 当静态库更新时那么整个程序都要重新进行链接；\n- 对于 printf 这种标准函数库，如果每个程序都要有代码，这会极大浪费资源。\n\n共享库是为了解决静态库的这两个问题而设计的，在 Linux 系统中通常用 .so 后缀来表示，Windows 系统上它们被称为 DLL。它具有以下特点：\n\n- 在给定的文件系统中一个库只有一个文件，所有引用该库的可执行目标文件都共享这个文件，它不会被复制到引用它的可执行文件中；\n- 在内存中，一个共享库的 .text 节（已编译程序的机器代码）的一个副本可以被不同的正在运行的进程共享。\n\n<div align=\"center\"><img src=\"assets/76dc7769-1aac-4888-9bea-064f1caa8e77.jpg\" width=\"400\"/></div>\n\n\n\n\n# 参考资料\n\n- [Operating Systems: Processes](https://www.cs.uic.edu/~jbell/CourseNotes/OperatingSystems/3_Processes.html)\n\n\n\n# 更新说明\n\n2018/8/20-22：基础版v2.5"
  },
  {
    "path": "notes/数据结构.md",
    "content": "# 数据结构部分\n\n# 一、线性表\n\n- 数组\n- 链表\n\n# 二、栈和队列\n\n\n\n# 三、树和二叉树\n\n## 1. 红黑树\n\n红黑树的特性: \n\n（1）每个节点或者是黑色的，或者是红色的\n\n（2）根节点是黑色的\n\n（3）每个叶子节点（NIL，最后的空结点）是黑色。 [注意：这里叶子节点，是指为空(NIL或NULL)的叶子节点！] \n\n（4）如果一个节点是红色的，那么他的孩子结点都是黑色的\n\n（5）从任意一个节点到叶子节点，经过的黑色节点是一样的。[这里也就可以得到插入的节点必然为红色]\n\n红黑树(一)之 原理和算法详细介绍 - 如果天空不死 - 博客园\nhttp://www.cnblogs.com/skywang12345/p/3245399.html\n\n\n\nJCFInternals/5-TreeSet and TreeMap.md at 049c84bb65a3114ba4b8355d83c490fb9b26c6af · CarpenterLee/JCFInternals\nhttps://github.com/CarpenterLee/JCFInternals/blob/049c84bb65a3114ba4b8355d83c490fb9b26c6af/markdown/5-TreeSet%20and%20TreeMap.md\n\n\n\n## 2. 二叉树\n\n### 二分查找法\n\n### 二叉树遍历\n\n\n\n\n\n## 3. 二分搜索树\n\n### 深度优先遍历（前序、中序、后序遍历）\n\n### 广度优先遍历（层序遍历）\n\n\n\n## 4. AVL树\n\n\n\n## 5. B和B+\n\n\n\n# 四、字符串和数组\n\n"
  },
  {
    "path": "notes/数据结构与算法.md",
    "content": "# 前言\n\n本文将系统总结算法面试和经典数据结构相关知识点，在这里分成 【数据结构】 和 【算法】 两部分展开。这里将展示主要的核心知识点，关于代码面试的 Leetcode 习题请转向代码仓库：[Interview-code](https://github.com/frank-lam/interview_code)\n\n\n\n- 阅读书籍\n  - 《算法4》\n  - 《程序员代码面试指南》\n  - 《剑指Offer》\n\n- 学习课程\n  - 刘宇波：玩转数据结构，从入门到进阶\n  - 刘宇波：程序员的内功修炼，学好算法与数据结构\n  - 刘宇波：玩转算法面试 leetcode题库分门别类详细解析\n\n- 在线OJ\n  - [Leetcode中国版](https://leetcode-cn.com/)\n  - [牛客网](https://www.nowcoder.com/)\n\n\n\n# 第一部分：数据结构\n\n## 一、线性表\n\n- 数组\n- 链表\n\n\n\n## 二、栈和队列\n\n\n\n## 三、树和二叉树\n\n### 1. 2-3树\n\n\n\n### 2. 红黑树\n\n红黑树的特性: \n\n（1）每个节点或者是黑色的，或者是红色的\n\n（2）根节点是黑色的\n\n（3）每个叶子节点（NIL，最后的空结点）是黑色。 [注意：这里叶子节点，是指为空(NIL或NULL)的叶子节点！] \n\n（4）如果一个节点是红色的，那么他的孩子结点都是黑色的\n\n（5）从任意一个节点到叶子节点，经过的黑色节点是一样的。[这里也就可以得到插入的节点必然为红色]\n\n\n\n红黑树(一)之 原理和算法详细介绍 - 如果天空不死 - 博客园\nhttp://www.cnblogs.com/skywang12345/p/3245399.html\n\nJCFInternals/5-TreeSet and TreeMap.md at 049c84bb65a3114ba4b8355d83c490fb9b26c6af · CarpenterLee/JCFInternals\nhttps://github.com/CarpenterLee/JCFInternals/blob/049c84bb65a3114ba4b8355d83c490fb9b26c6af/markdown/5-TreeSet%20and%20TreeMap.md\n\n\n\n### 3. 二叉树\n\n#### 二分查找法\n\n在有序表中，通过不断的二分判断mid与目标是否一致，并缩小目标所在区间。\n\n**代码实现**\n\n```java\n// 非递归实现\nprivate static int search(int[] data, int l, int r, int target) {\n    int mid;\n    while(l < r) {\n        mid = (l + r) / 2;\n        if(data[mid] == target) {\n            return mid;\n        } else if(data[mid] < target) {\n            l = mid + 1;\n        } else {\n            r = mid;\n        }\n    }\n    return -1;\n}\n// 递归实现\nprivate static int searchDfs(int[] data, int l, int r, int target) {\n    if(l >= r) {\n        return -1;\n    }\n    int mid = (l + r) / 2;\n    if(target == data[mid]) {\n        return mid;\n    } else if(target > data[mid]) {\n        return searchDfs(data, mid + 1, r, target);\n    } else {\n        return searchDfs(data, l, mid, target);\n    }\n}\n```\n\n\n\n### 4. 二叉树遍历\n\n#### 深度优先遍历（前序、中序、后序遍历）\n\n```C++\n/**\n * 前序遍历 非递归实现\n * Definition for a binary tree node.\n * struct TreeNode {\n *     int val;\n *     TreeNode *left;\n *     TreeNode *right;\n *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}\n * };\n */\nclass Solution {\npublic:\n    vector<int> preorderTraversal(TreeNode* root) {\n        vector<int> ans;\n        TreeNode* node = root;\n        stack<TreeNode*> s;\n        map<TreeNode*, bool> M;\n        if(node != NULL) {\n            s.push(node);\n            while (!s.empty()) {\n                node = s.top();\n                if (!M[node]) {\n                    ans.push_back(node->val);\n                    M[node] = true;\n                }\n                if (node->left != NULL) {\n                    s.push(node->left);\n                    node->left = NULL;\n                } else if (node->right != NULL) {\n                    s.push(node->right);\n                    node->right = NULL;\n                } else {\n                    s.pop();\n                }\n            }\n        }\n        return ans;\n    }\n};\n```\n\n```c++\n/**\n * 中序遍历\n * Definition for a binary tree node.\n * struct TreeNode {\n *     int val;\n *     TreeNode *left;\n *     TreeNode *right;\n *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}\n * };\n */\nclass Solution {\npublic:\n    vector<int> inorderTraversal(TreeNode* root) {\n        vector<int> ans;\n        if(root != NULL) {\n            traversal(root, ans);\n        }\n        return ans;\n    }\n    \n    void traversal(TreeNode* node, vector<int> &ans) {\n        if(node->left != NULL) {\n            traversal(node->left, ans);\n        }\n        ans.push_back(node->val);\n        if(node->right != NULL) {\n            traversal(node->right, ans);\n        }\n    }\n};\n```\n\n\n\n```c++\n/**\n * 后序遍历\n * Definition for a binary tree node.\n * struct TreeNode {\n *     int val;\n *     TreeNode *left;\n *     TreeNode *right;\n *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}\n * };\n */\nclass Solution {\npublic:\n    vector<int> postorderTraversal(TreeNode* root) {\n        vector<int> ans;\n        if(root != NULL) {\n            traversal(root, ans);\n        }\n        return ans;\n    }\n\n    void traversal(TreeNode* node, vector<int> &ans) {\n        if(node->left != NULL) {\n            traversal(node->left, ans);\n        }\n        if(node->right != NULL) {\n            traversal(node->right, ans);\n        }\n        ans.push_back(node->val);\n    }\n};\n```\n\n#### 广度优先遍历（层序遍历）\n\n### 5. AVL树\n\n\n\n### 6. B和B+\n\n\n\n## 四、字符串和数组\n\n\n\n\n\n## 五、图结构\n\n### 邻接矩阵\n\n### 邻接表\n\n\n\n# 第二部分：算法思想\n\n## 一、排序\n\n### 1. 选择排序（Selection Sort）\n\n选择出数组中的最小元素，将它与数组的第一个元素交换位置。再从剩下的元素中选择出最小的元素，将它与数组的第二个元素交换位置。不断进行这样的操作，直到将整个数组排序。 \n\n![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015224719590-1433219824.gif)\n\n**代码实现**\n\n```java\npublic static void sort(int[] arr) {\n    for (int i = 0; i < arr.length; i++) {\n        // 寻找[i, n)区间里的最小值的索引\n        int minIndex = i;\n        for (int j = i + 1; j < arr.length; j++) {\n            if(arr[minIndex] > arr[j]){\n                minIndex = j;\n            }\n        }\n        swap( arr , i , minIndex);\n    }\n}\n\nprivate static void swap(int[] arr, int i, int j) {\n    int t = arr[i];\n    arr[i] = arr[j];\n    arr[j] = t;\n}\n```\n\n**算法分析**\n\n表现最稳定的排序算法之一，因为无论什么数据进去都是O(n2)的时间复杂度，所以用到它的时候，数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。理论上讲，选择排序可能也是平时排序一般人想到的最多的排序方法了吧。\n\n \n\n### 2. 插入排序（Insertion Sort）\n\n插入排序从左到右进行，每次都将当前元素插入到左侧已经排序的数组中，使得插入之后左部数组依然有序。\n\n第 j 元素是通过不断向左比较并交换来实现插入过程：当第 j 元素小于第 j - 1 元素，就将它们的位置交换，然后令 j 指针向左移动一个位置，不断进行以上操作。\n\n![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015225645277-1151100000.gif)\n\n**代码实现**\n\n```java\npublic static void sort(int[] arr) {\n    for (int i = 0; i < arr.length; i++) {\n        // 选择 arr[i...n) 中的最小值\n        int minIndex = i;\n        for (int j = i + 1; j < arr.length; j++) {\n            if (arr[minIndex] > arr[j]) {\n                minIndex = j;\n            }\n        }\n        swap(arr, i, minIndex);\n    }\n}\n\n// 改进版插入排序（减少了数组元素的操作次数）\npublic static void better_sort(int[] arr) {\n    for (int i = 0; i < arr.length; i++) {\n        int e = arr[i];\n        int j = i;\n        for (; j > 0; j--) {\n            if (e < arr[j - 1])\n                arr[j] = arr[j - 1];\n            else\n                break;\n        }\n        arr[j] = e;\n    }\n}\n\nprivate static void swap(int[] arr, int i, int j) {\n    int t = arr[i];\n    arr[i] = arr[j];\n    arr[j] = t;\n}\n```\n\n**算法分析**\n\n插入排序在实现上，通常采用 in-place 排序（即只需用到 O(1) 的额外空间的排序），因而在从后向前扫描过程中，需要反复把已排序元素逐步向后挪位，为最新元素提供插入空间。\n\n \n\n### 3. 冒泡排序（Bubble Sort）\n\n通过从左到右不断交换相邻逆序的相邻元素，在一轮的交换之后，可以让未排序的元素上浮到右侧。\n\n在一轮循环中，如果没有发生交换，就说明数组已经是有序的，此时可以直接退出。\n\n![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015223238449-2146169197.gif)\n\n**代码实现**\n\n```java\nprivate static void sort(int[] arr) {\n    for (int i = arr.length - 1; i > 0; i--) { // 从最后一位开始确定\n        boolean swapped = false;\n        for (int j = 0; j < i; j++) {\n            if(arr[j] > arr[j+1]){\n                swapped = true;\n                swap(arr,j,j+1);\n            }\n        }\n        if(!swapped)\n            return;\n    }\n}\n\nprivate static void swap(int[] arr, int i, int j) {\n    int t = arr[i];\n    arr[i] = arr[j];\n    arr[j] = t;\n}\n```\n\n\n\n### 4. 希尔排序（Shell Sort）\n\n1959年Shell发明，第一个突破O(n2)的排序算法，是简单插入排序的改进版。它与插入排序的不同之处在于，它会优先比较距离较远的元素。希尔排序又叫**缩小增量排序**。\n\n\n\n**算法描述**\n\n先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序，具体算法描述：\n\n- 选择一个增量序列t1，t2，…，tk，其中ti>tj，tk=1；\n- 按增量序列个数k，对序列进行k 趟排序；\n- 每趟排序，根据对应的增量ti，将待排序列分割成若干长度为m 的子序列，分别对各子表进行直接插入排序。仅增量因子为1 时，整个序列作为一个表来处理，表长度即为整个序列的长度。\n\n![](https://images2018.cnblogs.com/blog/849589/201803/849589-20180331170017421-364506073.gif)\n\n**代码实现**\n\n```java\n// 希尔排序\npublic static void sort(int[] arr) {\n    int n = arr.length;\n    for (int h = n / 2; h > 0; h = h / 2) {\n        // 内部是一个插入排序\n        for (int i = 0; i < n; i = i + h) {\n\n            int e = arr[i];\n            int j = i;\n            for (; j > 0; j = j - h) {\n                if (e < arr[j - h])\n                    arr[j] = arr[j - h];\n                else\n                    break;\n            }\n            arr[j] = e;\n        }\n    }\n}\n\n\n// 希尔排序2\npublic static void sort2(int[] arr) {\n    int n = arr.length;\n    // 计算 increment sequence: 1, 4, 13, 40, 121, 364, 1093...\n    int h = 1;\n    while (h < n / 3) h = 3 * h + 1;\n\n    System.out.println(h);\n\n    while (h >= 1) {\n        // h-sort the array\n        for (int i = h; i < n; i++) {\n            \n            // 对 arr[i], arr[i-h], arr[i-2*h], arr[i-3*h]... 使用插入排序\n            int e = arr[i];\n            int j = i;\n            for (; j >= h && e < arr[j - h]; j -= h)\n                arr[j] = arr[j - h];\n            arr[j] = e;\n        }\n\n        h /= 3;\n    }\n}\n```\n\n**算法分析**\n\n对于大规模的数组，插入排序很慢，因为它只能交换相邻的元素，每次只能将逆序数量减少 1。\n\n希尔排序的出现就是为了改进插入排序的这种局限性，它通过交换不相邻的元素，每次可以将逆序数量减少大于 1。\n\n希尔排序使用插入排序对间隔 h 的序列进行排序。通过不断减小 h，最后令 h=1，就可以使得整个数组是有序的。\n\n\n\n### 5. 归并排序（Merge Sort）\n\n归并排序的思想是将数组分成两部分，分别进行排序，然后归并起来。把长度为n的输入序列分成两个长度为n/2的子序列；对这两个子序列分别采用归并排序；将两个排序好的子序列合并成一个最终的排序序列。\n\n![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015230557043-37375010.gif)\n\n**代码实现**\n\n> 1.归并方法\n>\n> 归并方法将数组中两个已经排序的部分归并成一个。\n\n```java\nprivate static void sort(int[] arr) {\n    __MergeSort(arr, 0, arr.length - 1);\n}\n\nprivate static void __MergeSort(int[] arr, int l, int r) {\n    if (l >= r)\n        return;\n    int mid = (l + r) / 2;\n    __MergeSort(arr, l, mid);\n    __MergeSort(arr, mid + 1, r);\n    merge(arr, l, mid, r);\n}\n\n// 将arr[l...mid]和arr[mid+1...r]两部分进行归并\nprivate static void merge(int[] arr, int l, int mid, int r) {\n    int[] aux = Arrays.copyOfRange(arr, l, r + 1);\n\n    // 初始化，i指向左半部分的起始索引位置l；j指向右半部分起始索引位置mid+1\n    int i = l, j = mid + 1;\n    for (int k = l; k <= r; k++) {\n        if (i > mid) {  // 如果左半部分元素已经全部处理完毕\n            arr[k] = aux[j - l];\n            j++;\n        } else if (j > r) {   // 如果右半部分元素已经全部处理完毕\n            arr[k] = aux[i - l];\n            i++;\n        } else if (aux[i - l] < aux[j - l]) {  // 左半部分所指元素 < 右半部分所指元素\n            arr[k] = aux[i - l];\n            i++;\n        } else {  // 左半部分所指元素 >= 右半部分所指元素\n            arr[k] = aux[j - l];\n            j++;\n        }\n    }\n}\n```\n\n> 2.自底向上归并排序\n\n```java\nprivate static void sort(int[] arr) {\n    int N = arr.length;\n    int[] aux = new int[N];\n    for (int sz = 1; sz < N; sz += sz)\n        for (int i = 0; i + sz < N; i += sz + sz)\n            merge(arr, i, i + sz - 1, Math.min(i + sz + sz - 1, N - 1));\n}\n```\n\n\n\n### 6. 快速排序（Quick Sort）\n\n快速排序可以说是20世纪最伟大的算法之一了。相信都有所耳闻，它的速度也正如它的名字那样，是一个非常快的算法了。当然它也后期经过了不断的改进和优化，才被公认为是一个值得信任的非常优秀的算法。\n\n![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015230936371-1413523412.gif)\n\n**代码实现**\n\n#### 1. 普通快速排序\n\n```java\n// 递归使用快速排序,对arr[l...r]的范围进行排序\npublic static void QuickSort(int[] arr,int l,int r){\n    if(l>=r)\n        return;\n    int p = partition(arr,l,r);\n    QuickSort(arr,l,p-1);\n    QuickSort(arr,p+1,r);\n}\n\n// 将数组通过p分割成两部分\n// 对arr[l...r]部分进行partition操作\n// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]\npublic static int partition(int[] arr, int l, int r) {\n    swap(arr, l, (int) (Math.random() % (r - l + 1)) + l);  // 加入这一行变成随机快速排序\n\n    int v = arr[l];\n    int j = l;\n    for(int i = j +1;i<=r;i++){\n        if(arr[i] < v){\n            j++;\n            swap(arr,i,j);\n        }\n    }\n    swap(arr,l,j);\n    return j;\n}\n\npublic static void swap(int[] arr,int i,int j) {\n    int temp = arr[i];\n    arr[i] = arr[j];\n    arr[j] = temp;\n}\n```\n\n快速排序是原地排序，不需要辅助数组，但是递归调用需要辅助栈。\n\n快速排序最好的情况下是每次都正好能将数组对半分，这样递归调用次数才是最少的。这种情况下比较次数为 CN=2CN/2+N，复杂度为 O(NlogN)。\n\n最坏的情况下，第一次从最小的元素切分，第二次从第二小的元素切分，如此这般。因此最坏的情况下需要比较 N2/2。为了防止数组最开始就是有序的，在进行快速排序时需要随机打乱数组。\n\n\n\n#### 2. 双路快速排序\n\n若果数组中含有大量重复的元素，则partition很可能把数组划分成两个及其不平衡的两部分，时间复杂度退化成O(n²)。这时候应该把小于v和大于v放在数组两端。\n\n![](pics/partition2.jpg)\n\n```java\n// 双路快速排序的partition\n// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]\nprivate static int partition(int[] arr, int l, int r) {\n\n    // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot\n    // swap(arr, l, (int) (Math.random() % (r - l + 1)) + l);\n\n    int v = arr[l];\n\n    // arr[l+1...i) <= v; arr(j...r] >= v\n    int i = l + 1, j = r;\n    while (true) {\n        // 注意这里的边界, arr[i].compareTo(v) < 0, 不能是arr[i].compareTo(v) <= 0\n        // 思考一下为什么?\n        while (i <= r && arr[i] < v)\n            i++;\n\n        // 注意这里的边界, arr[j].compareTo(v) > 0, 不能是arr[j].compareTo(v) >= 0\n        // 思考一下为什么?\n        while (j >= l + 1 && arr[j] > v)\n            j--;\n\n        // 对于上面的两个边界的设定, 有的同学在课程的问答区有很好的回答:)\n        // 大家可以参考: http://coding.imooc.com/learn/questiondetail/4920.html\n        if (i > j)\n            break;\n        \n        swap(arr, i, j);\n        i++;\n        j--;\n    }\n\n    swap(arr, l, j);\n\n    return j;\n}\n\n// 递归使用快速排序,对arr[l...r]的范围进行排序\nprivate static void QuickSort2Ways(int[] arr, int l, int r) {\n    // 对于小规模数组, 使用插入排序\n    if (l >= r) return;\n    int p = partition(arr, l, r);\n    QuickSort2Ways(arr, l, p - 1);\n    QuickSort2Ways(arr, p + 1, r);\n}\n```\n\n#### 3. 三路快速排序\n\n数组分成三个部分，大于v 等于v 小于v\n\n在具有大量重复键值对的情况下使用三路快排\n\n![](pics/partition3.jpg)\n\n\n\n```java\n// 递归使用快速排序,对arr[l...r]的范围进行排序\nprivate static void QuickSort3Ways(int[] arr, int l, int r){\n\n    // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot\n    swap( arr, l, (int)(Math.random()*(r-l+1)) + l );\n\n    int v = arr[l];\n\n    int lt = l;     // arr[l+1...lt] < v\n    int gt = r + 1; // arr[gt...r] > v\n    int i = l+1;    // arr[lt+1...i) == v\n    while( i < gt ){\n        if( arr[i] < v){\n            swap( arr, i, lt+1);\n            i ++;\n            lt ++;\n        }\n        else if( arr[i] > v ){\n            swap( arr, i, gt-1);\n            gt --;\n        }\n        else{ // arr[i] == v\n            i ++;\n        }\n    }\n    swap( arr, l, lt );\n\n    QuickSort3Ways(arr, l, lt-1);\n    QuickSort3Ways(arr, gt, r);\n}\n```\n\n\n\n### 7. 堆排序（Heap Sort）\n\n#### 1. 堆\n\n堆的某个节点的值总是大于等于子节点的值，并且堆是一颗完全二叉树。\n\n堆可以用数组来表示，因为堆是完全二叉树，而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2，而它的两个子节点的位置分别为 2k 和 2k+1。这里不使用数组索引为 0 的位置，是为了更清晰地描述节点的位置关系。\n\n![](pics/heap.png)\n\n#### 2. 上浮和下沉\n\n在堆中，当一个节点比父节点大，那么需要交换这个两个节点。交换后还可能比它新的父节点大，因此需要不断地进行比较和交换操作，把这种操作称为**上浮（ShiftUp）**。\n\n![](pics/shiftup_heap.png) \n\n```java\nprivate void shiftUp(int k){\n    while( k > 1 && data[k/2] < data[k])){\n        swap(k, k/2);\n        k /= 2;\n    }\n}\n```\n\n\n\n类似地，当一个节点比子节点来得小，也需要不断地向下进行比较和交换操作，把这种操作称为**下沉（Shift Down）**。一个节点如果有两个子节点，应当与两个子节点中最大那么节点进行交换。\n\n![](pics/shiftdown_heap.png) \n\n```java\nprivate void shiftDown(int k){\n    while( 2*k <= count ){ // 当前结点有左孩子\n        int j = 2*k; // 在此轮循环中,data[k]和data[j]交换位置\n        if( j+1 <= count && data[j+1] > data[j] )\n            j ++;\n        // data[j] 是 data[2*k]和data[2*k+1]中的最大值\n\n        if( data[k] >= data[j] ) \n            break;\n        swap(k, j);\n        k = j;\n    }\n}\n```\n\n#### 3.插入元素\n\n将新元素放到数组末尾，然后上浮到合适的位置。  \n\n```java\n// 向最大堆中插入一个新的元素 item\npublic void insert(Item item){\n    assert count + 1 <= capacity;\n    data[count+1] = item;\n    count ++;\n    shiftUp(count);\n}\n```\n\n#### 4. 删除最大元素\n\n```java\n// 从最大堆中取出堆顶元素, 即堆中所存储的最大数据\npublic Item extractMax(){\n    assert count > 0;\n    Item ret = data[1];\n    \n    swap( 1 , count );\n    count --;\n    shiftDown(1);\n    return ret;\n}\n```\n\n#### 5. 堆排序\n\n由于堆可以很容易得到最大的元素并删除它，不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置，并且不删除它，那么就可以得到一个从尾到头的递减序列，从正向来看就是一个递增序列。因此很容易使用堆来进行排序。并且堆排序是原地排序，不占用额外空间。\n\n```java\n// 不使用一个额外的最大堆, 直接在原数组上进行原地的堆排序\npublic class HeapSort {\n\n    // 对整个arr数组使用HeapSort1排序\n    // HeapSort1, 将所有的元素依次添加到堆中, 在将所有元素从堆中依次取出来, 即完成了排序\n    // 无论是创建堆的过程, 还是从堆中依次取出元素的过程, 时间复杂度均为O(nlogn)\n    // 整个堆排序的整体时间复杂度为O(nlogn)\n    public static void sort1(Comparable[] arr){\n\n        int n = arr.length;\n        MaxHeap<Comparable> maxHeap = new MaxHeap<Comparable>(n);\n        for( int i = 0 ; i < n ; i ++ )\n            maxHeap.insert(arr[i]);\n\n        for( int i = n-1 ; i >= 0 ; i -- )\n            arr[i] = maxHeap.extractMax();\n    }\n\n\n    // 只通过shiftDown操作进行排序\n    public static void sort2(Comparable[] arr){\n        int n = arr.length;\n\n        // 注意，此时我们的堆是从0开始索引的\n        // 从(最后一个元素的索引-1)/2开始\n        // 最后一个元素的索引 = n-1\n        for( int i = (n-1-1)/2 ; i >= 0 ; i -- )\n            shiftDown2(arr, n, i);\n\n        for( int i = n-1; i > 0 ; i-- ){ // 这个的目的是让序列从小到大排序\n            swap( arr, 0, i);\n            shiftDown2(arr, i, 0);\n        }\n    }\n\n    // 交换堆中索引为i和j的两个元素\n    private static void swap(Object[] arr, int i, int j){\n        Object t = arr[i];\n        arr[i] = arr[j];\n        arr[j] = t;\n    }\n\n    // 原始的shiftDown过程\n    private static void shiftDown(Comparable[] arr, int n, int k){\n        while( 2*k+1 < n ){\n            int j = 2*k+1;\n            if( j+1 < n && arr[j+1].compareTo(arr[j]) > 0 )\n                j += 1;\n\n            if( arr[k].compareTo(arr[j]) >= 0 )break;\n\n            swap( arr, k, j);\n            k = j;\n        }\n    }\n\n    // 优化的shiftDown过程, 使用赋值的方式取代不断的swap,\n    // 该优化思想和我们之前对插入排序进行优化的思路是一致的\n    private static void shiftDown2(Comparable[] arr, int n, int k){\n\n        Comparable e = arr[k];\n        while( 2*k+1 < n ){\n            int j = 2*k+1;\n            if( j+1 < n && arr[j+1].compareTo(arr[j]) > 0 )\n                j += 1;\n\n            if( e.compareTo(arr[j]) >= 0 )\n                break;\n\n            arr[k] = arr[j];\n            k = j;\n        }\n\n        arr[k] = e;\n    }\n\n    // 测试 HeapSort\n    public static void main(String[] args) {\n        Integer[] arr = {10, 91, 8, 7, 6, 5, 4, 3, 2, 1};\n        HeapSort.sort2(arr);\n        PrintHelper.printArray(arr);\n    }\n}\n```\n\n#### 6. 堆排序的应用——Top K问题\n\n例如，有1亿个浮点数，如何找出其中最大的10000个？（B326）\n\n\n\n### 8. 计数排序\n\nhttps://www.cnblogs.com/freedom314/p/5847092.html\n\n\n\n### 9. 排序算法总结\n\n|          | 平均时间复杂度 | 原地排序 | 额外空间 | 稳定排序 |\n| :------: | :------------: | :------: | :------: | :------: |\n| 插入排序 |     O(n^2)     |    √     |   O(1)   |    √     |\n| 归并排序 |    O(nlogn)    |    ×     |   O(n)   |    √     |\n| 快速排序 |    O(nlogn)    |    √     | O(logn)  |    ×     |\n|  堆排序  |    O(nlogn)    |    √     |   O(1)   |    ×     |\n\n稳定排序：对于相等的元素，在排序后，原来靠前的元素依然靠前。相等元素的相对位置没有发生变化。\n\n```java\n// 可以通过⾃自定义⽐比较函数，让排序算法不不存在稳定性的问题。\nbool operator<(const Student& otherStudent){\n    return score != otherStudent.score ?\n    score > otherStudent.score :\n    name < otherStudent.name;\n}\n```\n\n\n\n\n\n\n\n![](pics/sort_algorithm_analyze.png) \n\n\n\n\n\n\n\n## 二、递归和回溯法\n\n### 1. 例题\n\n\n\n### 2. 排列问题\n\n\n\n### 3. 组合问题\n\n\n\n### 4. 回溯法的剪枝\n\n\n\n### 5. 二维平面回溯法\n\n\n\n### 6. floodfill算法\n\n\n\n\n\n## 三、动态规划\n\n递归和动态规划都是将原问题拆成多个子问题然后求解，他们之间最本质的区别是，动态规划保存了子问题的解，避免重复计算。 \n\n\n\n动态规划一般可分为4类：\n\n1. 线性动规\n2. 区域动规\n3. 树形动规\n4. 背包动规\n\n\n\n以`198. House Robber`为例，动态规划的**状态定义**和**状态转移方程**如下：\n\n注意其中对状态的**定义**：\n\n- 考虑偷取 [x…n-1] 范围⾥里里的房子 （函数的定义）\n\n根据对状态的定义，决定状态的**转移**：\n\n- f(0) = max{ v(0) + f(2) , v(1) + f(3) , v(2) + f(4) , … ,v(n-3) + f(n-1) , v(n-2),v(n-1) }\n\n (状态转移方程)\n\n![](pics/dynamic_programming.png)\n\n\n\n### 1. 斐波那契数列\n\n#### 1.1 递归方式（自顶向下）\n\n```java\npublic int fib( int n ){\n    if( n == 0 )\n        return 0;\n    if( n == 1 )\n        return 1;\n    return fib(n-1) + fib(n-2);\n}\n\n//控制台输出\nfib(42) = 267914296\ntime : 1949 ms\nrun function fib() 866988873 times.\n```\n\n#### 1.2 记忆化搜索（自底向上）\n\n```java\npublic int fib(int n){\n    int[] memo = new int[n + 1];\n    Arrays.fill(memo, -1);\n    return fib(n, memo);\n}\n\nprivate int fib(int n, int[] memo){\n    if(n == 0)\n        return 0;\n    if(n == 1)\n        return 1;\n    if(memo[n] == -1)\n        memo[n] = fib(n - 1, memo) + fib(n - 2, memo);\n    return memo[n];\n}\n\n//控制台输出\nfib(1000) = 1556111435\ntime : 1 ms\nrun function fib() 1999 times.\n```\n\n#### 1.3 动态规划\n\n```java\npublic int fib(int n){\n    int[] memo = new int[n + 1];\n    Arrays.fill(memo, -1);\n\n    memo[0] = 0;\n    memo[1] = 1;\n    for(int i = 2 ; i <= n ; i ++)\n        memo[i] = memo[i - 1] + memo[i - 2];\n\n    return memo[n];\n}\n```\n\n\n\n\n\n### 2. 背包问题\n\n先得到该问题的局部解然后扩展到全局问题解。\n\n我们可以假设一个B(k,C) 方法，第k件物品，当前背包所剩下的容量C（初始则C=W）情况下，能够偷的最大价值量。\n\nB( i , c ) = max{ F( i - 1 , C ) ,  v(i) + F( i - 1, C - w[i] ) };\n\n#### （1）记忆化搜索\n\n```java\n/**\n * 记忆化搜索\n * 时间复杂度: O(n * C) 其中n为物品个数; C为背包容积\n * 空间复杂度: O(n * C)\n */\npublic class Solution01 {\n    private static int count = 0;\n    private static int[][] memo;\n\n    public int knapsack(int[] w, int[] v, int C) {\n        int n = w.length;\n        memo = new int[n][C + 1];\n        for(int i = 0;i<n;i++)\n            Arrays.fill(memo[i],-1);\n\n        return bestValue(w, v, n - 1, C);\n    }\n\n    // 用 [0...index]的物品,填充容积为c的背包的最大价值\n    private int bestValue(int[] w, int[] v, int i, int C) {\n        count++;\n        if (i < 0 || C <= 0)\n            return 0;\n\n        if (memo[i][C] != -1) // 记忆化搜索\n            return memo[i][C];\n\n        int res = 0;\n        res = bestValue(w, v, i - 1, C);\n        if (C >= w[i])\n            res = max(res, v[i] + bestValue(w, v, i - 1, C - w[i]));\n\n        return memo[i][C] = res;\n    }\n\n    private int max(int a, int b) {\n        return a > b ? a : b;\n    }\n\n    public static void main(String[] args) {\n        int[] w = {5,4,6,3};\n        int[] v = {10,40,30,50};\n        System.out.println(new Solution01().knapsack(w, v, 10));\n        System.out.println(\"count of bestValue() exec：\" + count);\n        PrintHelper.print2DArray(memo);\n    }\n}\n```\n\n\n\n#### （2）动态规划\n\n<div align=\"center\"> <img src=\"pics/knapsack-01.png\" width=\"\"/></div><br/>\n\n\n\n```java\n/**\n * 动态规划\n * 时间复杂度: O(n * C) 其中n为物品个数; C为背包容积\n * 空间复杂度: O(n * C)\n */\npublic class Solution02 {\n    public int knapsack(int[] w, int[] v, int C) {\n        int n = w.length;\n        int[][] memo = new int[n][C + 1];\n\n        if (n == 0 || C == 0)\n            return 0;\n\n        for (int j = 0; j <= C; j++)\n            memo[0][j] = (j >= w[0] ? v[0] : 0);\n\n        for (int i = 1; i < n; i++) {\n            for (int j = 0; j <= C; j++) {\n                memo[i][j] = memo[i - 1][j];\n                if (j >= w[i]) {\n                    memo[i][j] = max(memo[i][j], v[i] + memo[i - 1][j - w[i]]);\n                }\n            }\n        }\n\n        return memo[n - 1][C];\n    }\n\n    private int max(int a, int b) {\n        return a > b ? a : b;\n    }\n\n\n    public static void main(String[] args) {\n        int[] w = {1, 2, 3};\n        int[] v = {6, 10, 12};\n        int C = 5;\n        System.out.println(new Solution02().knapsack(w, v, C));\n    }\n}\n\n```\n\n#### （3）动态规划优化思路1\n\n优化思路：第i行元素只依赖于第i-1行元素，理论上，只需要保持两行元素即可\n\n<div align=\"center\"> <img src=\"pics/knapsack-optimized1.png\" width=\"\"/></div><br/>\n\n```java\n/// 动态规划改进: 滚动数组\n/// 时间复杂度: O(n * C) 其中n为物品个数; C为背包容积\n/// 空间复杂度: O(C), 实际使用了2*C的额外空间\npublic class Solution1 {\n\n    public int knapsack01(int[] w, int[] v, int C){\n\n        if(w == null || v == null || w.length != v.length)\n            throw new IllegalArgumentException(\"Invalid w or v\");\n\n        if(C < 0)\n            throw new IllegalArgumentException(\"C must be greater or equal to zero.\");\n\n        int n = w.length;\n        if(n == 0 || C == 0)\n            return 0;\n\n        int[][] memo = new int[2][C + 1];\n\n        for(int j = 0 ; j <= C ; j ++)\n            memo[0][j] = (j >= w[0] ? v[0] : 0);\n\n        for(int i = 1 ; i < n ; i ++)\n            for(int j = 0 ; j <= C ; j ++){\n                memo[i % 2][j] = memo[(i-1) % 2][j];\n                if(j >= w[i])\n                    memo[i % 2][j] = Math.max(memo[i % 2][j], v[i] + memo[(i-1) % 2][j - w[i]]);\n            }\n\n        return memo[(n-1) % 2][C];\n    }\n}\n```\n\n#### （4）动态规划优化思路2\n\n<div align=\"center\"> <img src=\"pics/knapsack-optimized2.png\" width=\"\"/></div><br/>\n\n```java\n/// 动态规划改进\n/// 时间复杂度: O(n * C) 其中n为物品个数; C为背包容积\n/// 空间复杂度: O(C), 只使用了C的额外空间\npublic class Solution2 {\n\n    public int knapsack01(int[] w, int[] v, int C){\n\n        if(w == null || v == null || w.length != v.length)\n            throw new IllegalArgumentException(\"Invalid w or v\");\n\n        if(C < 0)\n            throw new IllegalArgumentException(\"C must be greater or equal to zero.\");\n\n        int n = w.length;\n        if(n == 0 || C == 0)\n            return 0;\n\n        int[] memo = new int[C+1];\n\n        for(int j = 0 ; j <= C ; j ++)\n            memo[j] = (j >= w[0] ? v[0] : 0);\n\n        for(int i = 1 ; i < n ; i ++)\n            for(int j = C ; j >= w[i] ; j --)\n                memo[j] = Math.max(memo[j], v[i] + memo[j - w[i]]);\n\n        return memo[C];\n    }\n}\n```\n\n#### （5）背包问题更多变种\n\n- 多重背包问题：每个物品不不⽌止1个，有num(i)个\n- 完全背包问题：每个物品可以⽆无限使⽤用\n- 多维费⽤用背包问题：要考虑物品的体积和重量量两个维度？\n- 物品间加⼊入更更多约束：物品间可以互相排斥；也可以互相依赖\n\n\n\n\n\n### 3. 最长上升子序列\n\n**Longest Increasing Subsequence (LIS)**\n\n**【Leetcode 300】最长上升子序列** \n\n给定一个无序的整数数组，找到其中最长上升子序列的长度。\n\n**示例:**\n\n```\n输入: [10,9,2,5,3,7,101,18]\n输出: 4 \n解释: 最长的上升子序列是 [2,3,7,101]，它的长度是 4。\n```\n\n**说明:**\n\n- 可能会有多种最长上升子序列的组合，你只需要输出对应的长度即可。\n- 你算法的时间复杂度应该为 O(*n2*) 。\n\n**进阶:** 你能将算法的时间复杂度降低到 O(*n* log *n*) 吗?\n\n\n\n\n\nLIS( i ) 表示以第 i 个数字为结尾的最长上升子序列的长度\n\nLIS( i ) 表示 [0...i] 的范围内，选择数字nums[i]可以获得的最长上升子序列的长度\n\nLIS ( i )  =   max<sub>j<i</sub>( 1 + LIS( j ) if nums[i] > nums[j] )\n\n```java\npublic class Solution {\n\n    public int lengthOfLIS(int[] nums) {\n        int n = nums.length;\n        if (n==0) {\n            return 0;\n        }\n\n        int res = 1;\n        int[] memo = new int[n];\n\n        Arrays.fill(memo, 1);\n\n        for (int i = 1; i < n; i++) {\n            for (int j = 0; j < i; j++) {\n                if (nums[j] < nums[i])\n                    memo[i] = max(memo[i] , memo[j]+1);\n            }\n        }\n\n        for(int i = 0;i<n;i++){\n            res = max(memo[i],res);\n        }\n        return res;\n    }\n\n    private int max(int a, int b) {\n        return a > b ? a : b;\n    }\n\n    public static void main(String[] args) {\n        int[] arr = {10, 9, 2, 5, 3, 7, 101, 18};\n        System.out.println(new Solution().lengthOfLIS(arr));\n    }\n}\n```\n\n这里思考一个问题：在上面的代码中只求解出了上升子序列的长度，那么如何求出具体的上升子序列呢？\n\n```java\npublic class Solution2 {\n    private static List<Integer> LISindex = new ArrayList<>(); // 记录一下有几个上升子序列\n\n    public List<List<Integer>> lengthOfLIS(int[] nums) {\n        List<List<Integer>> resList = new ArrayList<>();\n        int n = nums.length;\n        if (n == 0) {\n            return null;\n        }\n\n        int res = 1;\n        int[] memo = new int[n];\n\n        Arrays.fill(memo, 1);\n\n        for (int i = 1; i < n; i++) {\n            for (int j = 0; j < i; j++) {\n                if (nums[j] < nums[i])\n                    memo[i] = max(memo[i], memo[j] + 1);\n            }\n        }\n\n        for (int i = 0; i < n; i++) {\n            res = max(memo[i], res);\n        }\n\n        for (int i = 0; i < n; i++) {\n            if (memo[i] == res)\n                LISindex.add(i); // 遍历一下最长子序列最后一位是谁，统计一共有多少个子序列\n        }\n\n        for (int lastIndex : LISindex) {\n            ArrayList<Integer> list = new ArrayList<>();\n            int nowMemoCount = memo[lastIndex];\n\n            for (int i = lastIndex; i >= 0; i--) {\n                if (nowMemoCount - memo[i] == 1 || nowMemoCount - memo[i] == 0) {\n                    list.add(nums[i]);\n                    nowMemoCount--;\n                }\n            }\n            resList.add(reverseList(list));\n        }\n\n        return resList;\n    }\n\n    private int max(int a, int b) {\n        return a > b ? a : b;\n    }\n\n    private List<Integer> reverseList(ArrayList<Integer> list) {\n        List<Integer> newList = new ArrayList<>();\n        for (int i = list.size() - 1; i >= 0; i--) {\n            newList.add(list.get(i));\n        }\n        return newList;\n    }\n\n    public static void main(String[] args) {\n        int[] arr = {10, 9, 2, 5, 3, 7, 101, 18};\n        System.out.println(new Solution2().lengthOfLIS(arr));\n    }\n}\n```\n\n\n\n### 4. 最长公共子序列\n\n**Longest Common Sequence (LCS)**：给出两个字符串S1和S2，求这两个字符串的最长公共子序列的长度\n\n\n\nLCS( m , n ) S1[0…m] 和 S2[0…n] 的最长公共子序列的长度\n\n\n\n**S1[m] == S2[n] :**  \n\nLCS(m,n) = 1 + LCS(m-1,n-1) \n\n**S1[m] != S2[n] :**   \n\nLCS(m,n) = max( LCS(m-1,n) , LCS(m,n-1) )\n\n\n\n<div align=\"center\"> <img src=\"pics/LCS.png\" width=\"\"/></div><br/>\n\n```java\n/**\n * 最长公共子序列\n */\npublic class Solution3 {\n\n    public int LCS(String s1, String s2) {\n        return bestLength(s1, s2, s1.length() - 1, s2.length() - 1);\n    }\n\n    public int bestLength(String s1, String s2, int m, int n) {\n        if (m < 0 || n < 0)\n            return 0;\n        int lcs = 0;\n        if (s1.charAt(m) == s2.charAt(n)) {\n            lcs = 1 + bestLength(s1, s2, m - 1, n - 1);\n        } else {\n            lcs = max(bestLength(s1, s2, m - 1, n), bestLength(s1, s2, m, n - 1));\n        }\n        return lcs;\n    }\n\n    private int max(int a, int b) {\n        return a > b ? a : b;\n    }\n\n    public static void main(String[] args) {\n        System.out.println(new Solution3().LCS(\"ABCDEE\", \"ABDCEE\"));\n    }\n}\n```\n\n\n\n参考资料：\n\n- [动态规划解决01背包问题 - Christal_R - 博客园](https://www.cnblogs.com/Christal-R/p/Dynamic_programming.html)\n- [【经典算法】01背包问题_土豆视频](http://new-play.tudou.com/v/XMTQ3MzI0NzI2OA==.html?spm=a2h0k.8191414.0.0&from=s1.8-1-1.2&f=28521433)\n\n\n\n\n## 四、贪心算法\n\n### 1. assign-cookies\n\n假设你是一位很棒的家长，想要给你的孩子们一些小饼干。但是，每个孩子最多只能给一块饼干。对每个孩子 i ，都有一个胃口值 gi ，这是能让孩子们满足胃口的饼干的最小尺寸；并且每块饼干 j ，都有一个尺寸 sj 。如果 sj >= gi ，我们可以将这个饼干 j 分配给孩子 i ，这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子，并输出这个最大数值。\n\n**注意：**\n\n你可以假设胃口值为正。\n一个小朋友最多只能拥有一块饼干。\n\n**示例 1:**\n\n```\n输入: [1,2,3], [1,1]\n\n输出: 1\n\n解释: \n你有三个孩子和两块小饼干，3个孩子的胃口值分别是：1,2,3。\n虽然你有两块小饼干，由于他们的尺寸都是1，你只能让胃口值是1的孩子满足。\n所以你应该输出1。\n```\n\n**示例 2:**\n\n```\n输入: [1,2], [1,2,3]\n\n输出: 2\n\n解释: \n你有两个孩子和三块小饼干，2个孩子的胃口值分别是1,2。\n你拥有的饼干数量和尺寸都足以让所有孩子满足。\n所以你应该输出2.\n```\n\n\n\n```java\npublic class Solution {\n\n    public int findContentChildren(int[] g, int[] s) {\n        int res = 0;\n        Arrays.sort(g);\n        Arrays.sort(s);\n\n        for (int i = g.length - 1; i >= 0; i--) {\n            for (int j = s.length - 1; j >= 0; j--) {\n                if(s[j] >= g[i]){\n                    res ++;\n                    s[j] = 0;\n                    break;\n                }\n            }\n        }\n\n        return res;\n    }\n\n\n    public static void main(String[] args) {\n        int res =  new Solution().findContentChildren(new int[]{1,2,3}, new int[]{3});\n        System.out.println(res);\n    }\n}\n```\n\n\n\n\n\n\n\n\n\n\n\n\n\n# 第三部分：面试指南\n\n## 1. 判单链表是否对称\n\n> 网易有道面经\n\n**原题**：设单链表中存放着 n 个字符，设计算法并判断字符是否中心对称，如 \"xyzzyx\" 是中心对称的。\n\n**思路1**：可先将字符串中的全部字符进栈，然后再将栈中的字符逐个与链表中的字符进行比较，如全部相等，则为中心对称。\n\n**思路2**：将单链表的前一半元素依次入栈，遍历到单链表的后一半元素的第一个元素时，便从栈中弹出一个元素，对它们俩开始比较。\n\n- 若相等，则将链表中的下一个元素与栈中再次弹出的元素进行比较，直至单链表到末尾，而且如果此时栈也为空栈，则可得出此单链表是中心对称的结论；\n- 若不相等，则单链表不是中心对称。\n\n\n\n## 2. 合并两个有序数组成一个有序数组\n\n> 金山云面经\n\n有两种实现思路：\n\n1. 定义一个新数组，长度为两个数组长度之和，将两个数组都 copy 到新数组，然后排序。\n\n2. 给两个数组分别定义一个下标，最大长度是数组长度减一，按位循环比较两个数组，较小元素的放入新数组，下标加一（注意，较大元素对应的下标不加一），直到某一个下标超过数组长度时退出循环，此时较短数组已经全部放入新数组，较长数组还有部分剩余，最后将剩下的部分元素放入新数组，大功告成。\n\n\n\n\n\n## 3. 求二叉树中值为x的结点的层号\n\n> 金山云面经\n\n思路：利用访问二叉树的路径过程求解（PS：这里不是说遍历过程，是访问过程，无论前中后序遍历，访问过程都是要经过通一结点三次），利用level变量记录访问过程中的层号，递归返回要减一；\n\n```java\n#include <stdio.h>\n#include <stdlib.h>\n\ntypedef char ElementType;\ntypedef struct TNode *Position;\ntypedef Position BinTree;\nstruct TNode{\n    ElementType Data;\n    BinTree Left;\n    BinTree Right;\n};\n\nBinTree CreatBinTree(); /* 实现细节忽略 */\nvoid level_in_x(BinTree BT,char x,int level);\n\nint main()\n{\n    int level = 1;\n    BinTree BT = CreatBinTree();\n    level_in_x(BT,'H',level);\n\n    return 0;\n}\n//静态建树\nBinTree CreatBinTree()\n{\n    BinTree pa = (BinTree)malloc(sizeof(struct TNode));\n    BinTree pb = (BinTree)malloc(sizeof(struct TNode));\n    BinTree pc = (BinTree)malloc(sizeof(struct TNode));\n    BinTree pd = (BinTree)malloc(sizeof(struct TNode));\n    BinTree pe = (BinTree)malloc(sizeof(struct TNode));\n    BinTree pf = (BinTree)malloc(sizeof(struct TNode));\n    BinTree pg = (BinTree)malloc(sizeof(struct TNode));\n    BinTree ph = (BinTree)malloc(sizeof(struct TNode));\n    BinTree pi = (BinTree)malloc(sizeof(struct TNode));\n\n    pa->Data = 'A';\n    pb->Data = 'B';\n    pc->Data = 'C';\n    pd->Data = 'D';\n    pe->Data = 'E';\n    pf->Data = 'F';\n    pg->Data = 'G';\n    ph->Data = 'H';\n    pi->Data = 'I';\n\n    pa->Left = pb; pa->Right = pc;\n    pb->Left = pd; pb->Right = pf;\n    pc->Left = pg; pc->Right = pi;\n    pd->Left = NULL; pd->Right = NULL;\n    pe->Left = NULL; pe->Right = NULL;\n    pf->Left = pe; pf->Right = NULL;\n    pg->Left = NULL; pg->Right = ph;\n    ph->Left = NULL; ph->Right = NULL;\n    pi->Left = NULL; pi->Right = NULL;\n\n    return pa;\n}\n\nvoid level_in_x(BinTree BT,char x,int level)\n{\n\n    if (BT == NULL){\n        return ;\n    }\n    if(BT->Data == x){\n        printf(\"x in %d\",level);\n    }\n    level++;\n    level_in_x(BT->Left,x,level);\n    level_in_x(BT->Right,x,level);\n    level--;\n}\n```\n\n\n\n\n\n\n\n## 阿里面经OneNote\n\n1. 如何判断一个单链表是否有环？  \n2. 快速排序，过程，复杂度？什么情况下适用，什么情况下不适用？ \n3. 什么是二叉平衡树，如何插入节点，删除节点 \n4. 二分搜索的过程 \n5. 归并排序的过程？时间复杂度？空间复杂度？ \n6. 给你一万个数，如何找出里面所有重复的数？用所有你能想到的方法，时间复杂度和空间复杂度分别是多少 \n7. 给你一个数组，如何里面找到和为K的两个数 \n8. 100000个数找出最小或最大的10个？ \n9. 一堆数字里面继续去重，要怎么处理？阅读 RFC2616 文档，即 HTTP/1.1 规范，输入某个网址，利用 Java 的 Socket 发送 HTTP请求，特别要求能够解码 chunked 编码，观察文档中的伪代码实现，自己用Java代码实现，将解析后的整个html文档输出到控制台上，不要求关注太多细节。(就是不允许用httpclient的jar包，自行实现这个jar包类似的功能) \n\n\n\n\n\n\n\n# 第四部分：参考资料\n\n- [数据结构与算法系列 目录 - 如果天空不死 - 博客园](http://www.cnblogs.com/skywang12345/p/3603935.html)\n- [十大经典排序算法](https://www.cnblogs.com/onepixel/articles/7674659.html)\n- [VisuAlgo - visualising data structures and algorithms through animation](https://visualgo.net/en) \n- [Data Structure Visualization](https://www.cs.usfca.edu/~galles/visualization/Algorithms.html)\n- [海量数据处理：十道面试题与十个海量数据处理方法总结 - chenhuan001 - 博客园](https://www.cnblogs.com/chenhuan001/p/5866916.html)\n\n\n\n"
  },
  {
    "path": "notes/智力题.md",
    "content": "1. 给你50个红球和50个黑球，有两个一模一样的桶，往桶里放球，让朋友去随机抽，采用什么策略可以让朋友抽到红球的概率更高？ \n2. 称重的方法（从100个硬币中找出最轻的那个假币） \n3. 两个鸡蛋 \n4. 一筐鸡蛋 取 剩2 1，3 0， 4 1， 5 4， 6 3， \n\n \n\n \n\n1. 在项目中遇到的最难的问题是什么？你是怎么解决的 \n2. 你认为自己有那些方面不足 \n3. 平常如何学习的 \n\n"
  },
  {
    "path": "notes/架构师成长之路.md",
    "content": "# 架构师之路\n\n目前在市面上还没有一本能够将全栈技术做一个完整描述的著作，计划半年时间完成《架构师之路》（拟定）一著。个人能力有限，在这里不可能将所有的技术点面面俱到，保持最简洁的解读。可能不能让你学到所有的技能，但我会推荐一些学习的方法和著作更好的帮助在每个技术栈上快速学习，架构师成长路上的你我。\n\n有道无术，术上可求。希望笔者对于技术上的一些见闻能够帮助更多的计算机爱好者。\n\n\n\n## 第一篇 核心技术基础\n\n- 数据结构与算法\n  - 数据结构\n  - 算法\n\n- 计算机网络\n  - http与https\n  - 传输层、网络层\n  - web网络安全\n\n- Linux与操作系统原理\n  - Linux命令\n  - 进程与线程通信\n\n- 数据库\n  - MySQL\n  - Redis\n  - SQL\n\n\n\n## 第二篇 语言基础\n\n- Java\n\n- C++\n\n- Python\n\n- PHP\n\n- 语言的趋势与发展\n\n\n\n## 第三篇 高效工具\n\n- Git 与 SVN\n  - Sourcetree\n- Maven 与 Composer\n- 高效的开发工具\n  - sublime\n  - Visual Studio Code\n  - IntelliJ IDEA\n  - Pycharm\n  - Navicat Premium\n  - MobaXterm / SecureCRT 8.3\n- 调试工具\n  - fiddler\n  - postman\n- 团队协作工具\n  - Teambition\n- 博客撰写\n  - markdown\n\n\n\n## 第四篇 全栈增长\n\n- 前端技术栈\n- 人人都是产品经理\n- APP是如何开发的\n- UI设计\n\n\n\n## 第五篇 进阶技术\n\n- 设计模式\n\n- 经典机器学习算法\n\n- 微服务从设计到部署\n\n- 分布式系统演进\n\n\n\n## 第六篇 实战应用\n\n- 大并发大流量带来的问题\n- 海量数据处理\n\n\n\n## 第七篇 面试话术"
  },
  {
    "path": "notes/正则表达式.md",
    "content": "# 正则表达式（速查表）\n\n| 符号 | 描述             | 例子   | 能匹配   | 不能匹配 |\n| ---- | ---------------- | ------ | -------- | -------- |\n| ^    | 行首或字符串开始 | ^yo    | yo       | ayo      |\n| $    | 行末或字符串结束 | yo$    | yo       | yop      |\n| \\b   | 单词边界         | \\byo\\b | mu yo mu | muyomu   |\n| \\B   | 非单词边界       | \\Byo\\B | muyomu   | mu yo mu |\n\n| 符号     | 描述                                                         | 例子      | 能匹配 | 不能匹配 |\n| -------- | ------------------------------------------------------------ | --------- | ------ | -------- |\n| (?=xxx)  | 正向肯定预查，从任何匹配xxx的字符串开始处匹配查找字符串      | yo(?=o)   | yoo    | yo       |\n| (?!xxx)  | 正向否定预查，从任何不匹配pattern的字符串开始处匹配查找字符串 | yo(?!o)   | yo     | yoo      |\n| (?<=xxx) | 反向肯定预查，与正向肯定预查类拟，只是方向相反               | (?<=y)o   | yo     | yoo      |\n| (?<!xxx) | 反向否定预查，与正向否定预查类拟，只是方向相反               | (?<!goo)d | mood   | good     |\n\n| 符号  | 描述                        | 例子   | 能匹配        | 不能匹配 |\n| ----- | --------------------------- | ------ | ------------- | -------- |\n| [ ]   | 字符集合                    | [ace]  | a, c, e       | d        |\n| [ - ] | 字符范围                    | [a-c]  | a, b, c       | d        |\n| [^ ]  | 不包含的字符集合            | [^abc] | d, e          | a, b, c  |\n| .     | 匹配除断行外的任何字符      | yo.    | yoo, yop, yoh | yo       |\n| \\s    | 空白字符，等于[\\n\\r\\f\\t ]   | la\\sla | la la         | lala     |\n| \\S    | 非空白字符，等于[^\\n\\r\\f\\t] | la\\sla | lala          | la la    |\n| \\d    | 数字                        | \\d{2}  | 23            | 1a       |\n| \\D    | 非数字                      | \\D{3}  | yoo, abc      | yo1      |\n| \\w    | 单词，等于[a-z-A-Z0-9_]     | \\w{4}  | v123          | v12.3    |\n| \\W    | 非单词，等于[^a-z-A-Z0-9_]  | .$%?   | .$%?          | .ab?     |\n\n| 特殊字符 | 描述       |\n| -------- | ---------- |\n| \\n       | 断行       |\n| \\r       | 回车符     |\n| \\t       | 制表符     |\n| \\v       | 垂直制表符 |\n| \\f       | 换页       |\n| [\\b]     | 退格       |\n\n| 符号    | 描述                      | 例子     | 能匹配     | 不能匹配 |\n| ------- | ------------------------- | -------- | ---------- | -------- |\n| \\|      | 或，其一                  | a\\|b     | a, b       | c        |\n| (xxx)   | 匹配xxx并获取这一匹配     | yo(o\\|p) | yoo 或 yop | yoh      |\n| (?:xxx) | 匹配xxx但不获取这一匹配   | y(?:o)   | yo         | ya       |\n| +       | 重复1次或多次             | yo+      | yo, yooo   | y        |\n| *       | 重复0次或多次             | yo*      | y, yoo     | yop      |\n| ?       | 出现0次或1次              | yo?      | y, yo      | ye       |\n| ??      | ?的懒惰模式，尽可能少匹配 | yoo??    | yo         | yoo      |\n| +?      | +的懒惰模式               | yo+?     | yo         | yoo      |\n| *?      | *的懒惰模式               | yo*?     | y          | yo       |\n| {n}     | 重复n次                   | yo{2}    | yoo        | yooo     |\n| {n,m}   | 重复n到m次                | yo{1, 3} | yo,yooo    | yoooo    |\n| {n,}    | 重复至少n次               | yo{2,}   | yoo, yooo  | yo       |\n\n# Cheat Sheet\n\n[![regexr-table](assets/regexr-table.png)](https://si9ma.github.io/cool-cheatsheet/pdf/regexr.pdf)\n\n# 正则在线验证\n\n- [RegExr: Learn, Build, & Test RegEx](https://regexr.com/)\n- [Online regex tester, debugger with highlighting for PHP, PCRE, Python, Golang and JavaScript](https://regex101.com/)\n"
  },
  {
    "path": "notes/海量数据处理.md",
    "content": "# 前言\n\n这里主要针对案例进行分析讲解，欢迎大家在 issue 或是直接 contribution 更多相关的海量数据相关难题。这里我将持续性的更新一些面试中常见的海量数据案例。\n\n这里我将会持续更新，时间有限不能全面总结，欢迎大家一起完善。\n\n\n\n海量数据问题处理方法\n\n- Hash\n- Bit-Map\n- 布隆过滤器 (Bloom Filter)\n- 堆 (Heap)\n- 双层桶划分\n- 数据库索引\n- 倒排索引 (Inverted Index)\n- B+树\n- Trie树\n- MapReduce \n\n\n\n# 海量数据案例\n\n## 1. 两个大文件中找出共同记录\n\n### 题目描述\n\n给定 a、b 两个文件，各存放 50 亿个 url，每个 url 各占 64 字节，内存限制是 4G，让你找出 a、b 文件共同的 url？\n\n### 解题思路\n\n**方案 1**\n\n首先我们最常想到的方法是读取文件 a，建立哈希表方便后面查找，然后再读取文件 b，遍历文件 b 中每个 url，对于每个遍历，我们都执行查找 hash 表的操作，若 hash 表中搜索到了，则说明两文件共有，存入一个集合。\n\n可以估计每个文件安的大小为 5G×64 =320G，远远大于内存限制的 4G。所以不可能将其完全加载到内存中处理。\n\n针对上述问题，我们分治算法的思想。\n\n1. 遍历文件 a，对每个 url 求取 `hash(url)%1000`，然后根据所取得的值将 url 分别存储到 1000 个小文件（记为 a0,a1,...,a999 每个小文件约 300M），为什么是 1000？主要根据内存大小和要分治的文件大小来计算，我们就大致可以把 320G 大小分为 1000 份，每份大约 300M（当然，到底能不能分布尽量均匀，得看 hash 函数的设计）\n\n2. 遍历文件 b，采取和 a 相同的方式将 url 分别存储到 1000 个小文件（记为 b0,b1,...,b999 ）\n\n   为什么要这样做？文件 a 的 hash 映射和文件 b 的 hash 映射函数要保持一致，这样的话相同的 url 就会保存在对应的小文件中，比如，如果 a 中有一个 url 记录 data1 被 hash 到了 a99 文件中，那么如果 b 中也有相同 url，则一定被 hash 到了 b99 中。\n\n\n　　所以现在问题转换成了：找出 1000 对小文件中每一对相同的 url（**不对应的小文件不可能有相同的 url**）\n\n3. 求每对小文件中相同的 url 时，可以把其中一个小文件的 url 存储到 hash_set 中。然后遍历另一个小文件的每个 url，看其是否在刚才构建的 hash_set 中，如果是，那么就是共同的 url，存到文件里面就可以了。\n\n\n\n**方案2**\n\n如果允许有一定的错误率，可以使用 Bloom filter，4G 内存大概可以表示 340 亿 bit。将其中一个文件中的 url 使用 Bloom filter 映射为这 340 亿 bit，然后挨个读取另外一个文件的 url，检查是否与 Bloom filter，如果是，那么该 ur l应该是共同的 url（注意会有一定的错误率）。\n\n\n\n## 2. 大文件排序和去重\n\n如果有一个500G的超大文件，里面都是数值，如何对这些数值排序？ - CSDN博客\nhttps://blog.csdn.net/u011381576/article/details/79385133\n\n大文件的排序和去重 超级简单的实现 - zero_learner - 博客园\nhttps://www.cnblogs.com/yangxudong/p/3848453.html\n\n\n\n# 参考资料\n\n- [搞定海量数据处理，六道海量数据处理面试题分析与十大方法总结](https://zhuanlan.zhihu.com/p/40430913)\n- [海量数据处理：十道面试题与十个海量数据处理方法总结 - chenhuan001 - 博客园](https://www.cnblogs.com/chenhuan001/p/5866916.html)\n- [大数据和空间限制问题专题（一） - CSDN博客](https://blog.csdn.net/qq_21688757/article/details/53993096)\n\n- [Interview-Book/2018-03-31-数据分析师常见的7道面试题.md](https://github.com/ZuoAndroid/Interview-Book/blob/df3b37cf80de59015c6c3651b1e588d9cbeac889/%E9%9D%A2%E8%AF%95%E9%A2%98/2018-03-31-%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%E5%B8%88%E5%B8%B8%E8%A7%81%E7%9A%847%E9%81%93%E9%9D%A2%E8%AF%95%E9%A2%98.md)\n"
  },
  {
    "path": "notes/网络安全.md",
    "content": "# 网络安全\n\n## 1. 怎么知道连接是恶意的呢？可能是正常连接？\n\n1. 验证码（最简单有效的防护），采用点触验证，滑动验证或第三方验证码服务，普通验证码很容易被破解\n2. 频率，限制同设备，同IP等发送次数，单点时间范围可请求时长\n3. 归属地，检测IP所在地是否与手机号归属地匹配；IP所在地是否是为常在地\n4. 可疑用户，对于可疑用户要求其主动发短信（或其他主动行为）来验证身份\n5. 黑名单，对于黑名单用户，限制其操作，API接口直接返回success，1可以避免浪费资源，2混淆黑户判断\n6. 签名，API接口启用签名策略，签名可以保障请求URL的完整安全，签名匹配再继续下一步操作\n7. token，对于重要的API接口，生成token值，做验证\n8. https，启用https，https 需要秘钥交换，可以在一定程度上鉴别是否伪造IP\n9. 代码混淆，发布前端代码混淆过的包\n10. 风控，大量肉鸡来袭时只能受着，同样攻击者也会暴露意图，分析意图提取算法，分析判断是否为恶意 如果是则断掉；异常账号及时锁定；或从产品角度做出调整，及时止损。\n11. 数据安全，数据安全方面做策略，攻击者得不到有效数据，提高攻击者成本\n12. 恶意IP库，<https://threatbook.cn/>，过滤恶意IP\n\ntips：\n\n- 鉴别IP真伪（自己识别代理IP和机房IP成本略高，可以考虑第三方saas服务。由肉鸡发起的请求没辙，只能想其他方法）\n- 手机号真伪（做空号检测，同样丢给供应商来处理，达不到100%准确率，效率感人，并且不是实时的，可以考虑选择有防攻击的运营商）\n- 安全问题是长期的和攻击者斗智斗勇的问题，没有一劳永逸的解决方案，不断交锋，不断成长\n\n\n\n## 2. 跨站脚本攻击XSS\n\n### XSS攻击是什么\n\n- XSS是跨站脚本攻击(Cross Site Scripting)，为不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆，故将跨站脚本攻击缩写为XSS。恶意攻击者往Web页面里插入恶意Script代码，当用户浏览该页之时，嵌入其中Web里面的Script代码会被执行，从而达到恶意攻击用户的目的。 \n\n\n\n### XSS的危害\n\n其实归根结底，XSS的攻击方式就是想办法“教唆”用户的浏览器去执行一些这个网页中原本不存在的前端代码。\n\n可问题在于尽管一个信息框突然弹出来并不怎么友好，但也不至于会造成什么真实伤害啊。的确如此，但要说明的是，这里拿信息框说事仅仅是为了举个栗子，真正的黑客攻击在XSS中除非恶作剧，不然是不会在恶意植入代码中写上alert（“say something”）的。\n\n在真正的应用中，XSS攻击可以干的事情还有很多，这里举两个例子。\n\n- **窃取网页浏览中的cookie值**\n\n在网页浏览中我们常常涉及到用户登录，登录完毕之后服务端会返回一个cookie值。这个cookie值相当于一个令牌，拿着这张令牌就等同于证明了你是某个用户。\n\n如果你的cookie值被窃取，那么攻击者很可能能够直接利用你的这张令牌不用密码就登录你的账户。如果想要通过script脚本获得当前页面的cookie值，通常会用到document.cookie。\n\n试想下如果像空间说说中能够写入xss攻击语句，那岂不是看了你说说的人的号你都可以登录（不过某些厂商的cookie有其他验证措施如：Http-Only保证同一cookie不能被滥用）\n\n- **劫持流量实现恶意跳转**\n\n这个很简单，就是在网页中想办法插入一句像这样的语句：\n\n```java\n<script>window.location.href=\"http://www.baidu.com\";</script>\n```\n\n那么所访问的网站就会被跳转到百度的首页。\n\n早在2011年新浪就曾爆出过严重的xss漏洞，导致大量用户自动关注某个微博号并自动转发某条微博。具体各位可以自行百度。\n\n\n\n### 攻击分类举例\n\n#### （1）反射型XSS\n\n又称为非持久性跨站点脚本攻击，它是最常见的类型的XSS。漏洞产生的原因是攻击者注入的数据反映在响应中。一个典型的非持久性XSS包含一个带XSS攻击向量的链接(即每次攻击需要用户的点击)。\n\n**简单例子**\n\n正常发送消息：`http://www.test.com/message.php?send=Hello,World！`\n\n接收者将会接收信息并显示Hello,Word\n\n非正常发送消息：`http://www.test.com/message.php?send=<script>alert(‘foolish!’)</script>！`\n\n接收者接收消息显示的时候将会弹出警告窗口\n\n\n\n#### （2）持久型XSS\n\n又称为持久型跨站点脚本，它一般发生在XSS攻击向量(一般指XSS攻击代码)存储在网站数据库，当一个页面被用户打开的时候执行。每当用户打开浏览器,脚本执行。持久的XSS相比非持久性XSS攻击危害性更大,因为每当用户打开页面，查看内容时脚本将自动执行。谷歌的orkut曾经就遭受到XSS。\n\n**简单例子：**\n\n从名字就可了解到存储型XSS攻击就是将攻击代码存入数据库中，然后客户端打开时就执行这些攻击代码。例如留言板\n\n留言板表单中的表单域：`<input type=“text” name=“content” value=“这里是用户填写的数据”>`\n\n**正常操作：**用户是提交相应留言信息；将数据存储到数据库；其他用户访问留言板，应用去数据并显示。\n\n**非正常操作：**攻击者在value填写`<script>alert(‘foolish!’)</script>`【或者html其他标签（破坏样式。。。）、一段攻击型代码】；将数据存储到数据库中；其他用户取出数据显示的时候，将会执行这些攻击性代码\n\n​\t\n\n#### （3）DOM-based XSS\n\n基于DOM的XSS，通过对具体DOM代码进行分析，根据实际情况**构造dom节点**进行XSS跨站脚本攻击。\n\n注：domxss取决于输出位置，并不取决于输出环境，因此domxss既有可能是反射型的，也有可能是存储型的。dom-based与非dom-based，反射和存储是两个不同的分类标准。\n\n\n\n### 防范\n\n记住一句至理名言——“所有用户输入都是不可信的。”（注意: 攻击代码不一定在<script></script>中）\n\n#### 使用XSS Filter\n\n- 输入过滤，对用户提交的数据进行有效性验证，仅接受指定长度范围内并符合我们期望格式的的内容提交，阻止或者忽略除此外的其他任何数据。\n- 输出转义，当需要将一个字符串输出到Web网页时，同时又不确定这个字符串中是否包括XSS特殊字符，为了确保输出内容的完整性和正确性，输出HTML属性时可以使用HTML转义编码（HTMLEncode）进行处理，输出到<script>中，可以进行JS编码。\n\n#### 使用 HttpOnly Cookie\n\n将重要的cookie标记为httponly，这样的话当浏览器向Web服务器发起请求的时就会带上`cookie`字段，但是在`js`脚本中却不能访问这个cookie，这样就避免了XSS攻击利用`JavaScript`的`document.cookie`获取`cookie`。\n\n\n\n### 困难和幸运\n\n真正麻烦的是，在一些场合我们要允许用户输入HTML，又要过滤其中的脚本。这就要求我们对代码小心地进行转义。否则，我们可能既获取不了用户的正确输入，又被XSS攻击。\n幸好，由于XSS臭名昭著历史悠久又极其危险，现代web开发框架如`vue.js`、`react.js`等，在设计的时候就考虑了XSS攻击对html插值进行了更进一步的抽象、过滤和转义，我们只要熟练正确地使用他们，就可以在大部分情况下避免XSS攻击。\n同时，许多基于`MVVM`框架的`SPA`（单页应用）不需要刷新URL来控制view，这样大大防止了XSS隐患。另外，我们还可以用一些防火墙来阻止XSS的运行。\n\n\n\n参考资料：\n\n- [Web安全XSS攻击防范实例视频教程-慕课网](https://www.imooc.com/learn/812)\n- [对于跨站脚本攻击（XSS攻击）的理解和总结](https://zhuanlan.zhihu.com/p/37295186)\n- [【超详细】XSS跨站脚本攻击 - 那一叶随风 - 博客园](https://www.cnblogs.com/phpstudy2015-6/p/6767032.html)\n\n## 3. 跨站请求伪造CSRF\n\n### CSRF是什么？\n\n**跨站请求伪造（英语：Cross-site request forgery）**，维基百科的解释是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法，听起来很厉害的样子。\n\n简单来说，攻击者利用一些技术手段去欺骗用户浏览器去访问一些曾经认证过的网站而执行一些操作。由于认证过，所以浏览器认为是用户的本意。\n\n其实咋们可以简单理解为`虎符调兵`，正常是大将军颁发虎符才能调兵，但是军队只认虎符不认人，假如奸臣偷取虎符假传命令私自调兵造反，那可就大事不好！\n\n例如 `localhost/deleteAriticle.php?id=3&username=xiaoxiao`，攻击者在被攻击的网站页面嵌入这样的代码，当用户xiaoxiao访问该网站的时候，会发起这条请求。服务器会删除id为3的数据。 客户端防范：对于数据库的修改请求，全部使用POST提交，禁止使用GET请求。 服务器端防范：一般的做法是在表单里面添加一段隐藏的唯一的token(请求令牌)。 \n\n\n\n### CSRF原理\n\n那我们具体看看攻击细节\n\n<div align=\"center\"> <img src=\"https://pic4.zhimg.com/v2-3ce6ab7c77ffeac99cb77db42648c390_r.jpg\" width=\"600\"/></div><br/>\n\n\n\n**看图说话，大致过程**\n\n- 用户访问浏览正常网站\n- 正常网站服务器响应并且返回标识该用户身份的cookie\n- 用户未注销正常网站的情况下，访问恶意网站\n- 恶意网站里访问正常网站并且带着标识用户的cookie\n- 正常网站服务器接受来自恶意网站的请求\n\n再次访问正常网站时，浏览器会自动带上标识该用户身份的cookie发送请求，所以正常网站服务器会接受来自恶意网站的请求，从而完成攻击。\n\n当我访问登录一个正常网站，成功访问后服务器会产生一个标识用户身份的cookie给用户的浏览器保存，在标识cookie还存在时访问恶意网站，在该网站里攻击者会让你不知不觉的访问之前的正常网站并且执行一些操作，由于标识用户身份的cookie还存在，所以用户浏览器认为是用户的本意操作而执行该请求，从而攻击成功。\n\n这些欺骗的访问方式有很多，例如“点击小广告、找回密码”等等诱导用户去点击操作。\n\n\n\n### 如何预防CSRF\n\n目前预防方式有二种：\n\n1. 检查Referer字段\n\nHTTP请求head里有个Referer字段，用于表明请求的来源地址。正常情况下，Referer字段和请求的地址是位于同一域名下的，如果是CSRF攻击发起的请求，那么Referer字段和请求的地址就不是同一域名了，那么服务器就能识别出恶意访问。\n\n这个方法缺点是攻击者有可能篡改该Referer字段内容，从而欺骗服务器。\n\n1. 添加校验token\n\n当用户正常访问网站时，服务器会生产一个随机数，并且把该随机数埋入该页面里（一般放在form表单，`<input type=\"hidden\" name=\"_csrf_token\" value=\"xxxx\">`）。正常访问，客户的浏览器是能够得到并且返回该字段，而CSRF一开始是不知道该字段的数值，服务器接受请求发现该字段的异常，从而拒绝该请求。\n\n\n\n### 如何用简洁生动的语言说明 XSS 和 CSRF 的区别？\n\nxss原理上利用的是浏览器**可以拼接成任意的javascript**，然后**黑客拼接好javascript**让浏览器自动地给服务器端发出多个请求（get、post请求）。\ncsrf原理上利用的是网站服务器端所有参数都是**可预先构造**的原理，然后**黑客拼接好具体请求url**，可以引诱你提交他构造好的请求。\n\n \n\n参考资料：\n\n- [「每日一题」CSRF 是什么](https://zhuanlan.zhihu.com/p/22521378)\n\n\n\n## 4. SQL注入攻击\n\n### 什么是SQL注入？\n\n所谓SQL注入，是将客户机提交或Web表单递交的数据，拼接成SQL语句字符串时。如果客户端提交的数据有非法字符或SQL语句关键字时，会造成执行的SQL语句语法错误，或执行结果不正确的情况。通过SQL注入，黑客可以最终达到欺骗服务器，执行恶意的SQL语句，甚至破坏数据库结构的目的。\n\nSQL注入攻击大多是利用设计上的漏洞，在目标服务器上运行Sql语句的攻击方式。开发者在动态生成Sql语句时，没有对用户输入的数据进行验证，是Sql注入攻击得逞的主要原因。\n\n\n\n### 如何防止SQL注入？\n\n在Java中，是使用JDBC和数据库建立连接，并执行SQL语句，和数据库进行数据交互的。\n\nJDBC在执行SQL语句操作时，提供了 **Statement**、**PreparedStatement** 和 **CallableStatement** 三种方式来执行SQL语句。其中 Statement 用于通用查询， PreparedStatement 用于执行参数化查询，而 CallableStatement则是用于存储过程。\n在三个接口中，Statement是PreparedStatement和CallableStatement的父接口。Statement在执行SQL语句时，对于客户端提交的数据只支持拼接SQL语句的方式。\n\n```mysql\nString sql = \"select * from  t_user where userName='\" + name + \n    \"' and  password='\" + password + \"'\"; \n```\n\n所以，使用Statement在执行SQL语句，容易引起SQL注入。PreparedStatement在执行参数化查询时，**支持占位符方式**。\n\n```mysql\nString sql = \"select * from  t_user where userName=? and password=?\";\nPreparedStatement ps = conn.prepareStatement(sql);\nps.setString(1,name);\nps.setString(2,password);\n```\n\n在使用参数化查询的情况下，数据库系统不会将参数的内容，视为SQL指令的一部分来处理。而是在数据库完成SQL指令的编译后，才套用参数运行。因此，就算参数中含有破坏性的指令，也不会被数据库所运行。**所以，使用PreparedStatement的参数化查询可以有效的阻止SQL注入。**\n\n另外，PreparedStatement相比Statement还有以下几个优势\n\n1. 可以预编译SQL语句，多次查询时速度快。\n2. 防止数据库缓冲区溢出\n3. 代码的可读性可维护性好\n\n由于有以上优点，所以，在开发JDBC时，PreparedStatement成为访问数据库的语句对象的首选。\n\n\n\n### 总结\n\n1. 所谓SQL注入，是将客户机提交或Web表单递交的数据，拼接成SQL语句字符串时。如果客户端提交的数据有非法字符或SQL语句关键字时，会造成执行的SQL语句语法错误，或执行结果不正确的情况。通过SQL注入，黑客可以最终达到欺骗服务器，执行恶意的SQL语句，甚至破坏数据库结构的目的。\n2. 在JDBC中使用 PreparedStatement 的参数化查询，数据库系统不会将参数的内容，视为SQL指令的一部分来处理。可以有效防止SQL注入。\n3. 开发JDBC时，尽量采用 PreparedStatement 执行SQL语句，相比 Statement 有以下优势:\n   1. 可以防止SQL注入\n   2. 可以预编译SQL语句，多次查询时速度快\n   3. 防止数据库缓冲区溢出\n   4. 代码的可读性可维护性好\n\n\n\n## 5. 拒绝服务攻击**DDoS** \n\n举个形象的例子：\n\n- 某饭店可以容纳100人同时就餐，某日有个商家恶意竞争，雇佣了200人来这个饭店坐着不吃不喝，导致饭店满满当当无法正常营业。**（DDOS攻击成功）**\n- 老板当即大怒，派人把不吃不喝影响正常营业的人全都轰了出去，且不再让他们进来捣乱，饭店恢复了正常营业。**（添加规则和黑名单进行DDOS防御，防御成功）**\n- 主动攻击的商家心存不满，这次请了五千人逐批次来捣乱，导致该饭店再次无法正常营业。**（增加DDOS流量，改变攻击方式）**\n- 饭店把那些捣乱的人轰出去只后，另一批接踵而来。此时老板将饭店营业规模扩大，该饭店可同时容纳1万人就餐，5000人同时来捣乱饭店营业也不会受到影响。**（增加硬防与其抗衡）**\n\nDDOS是Distributed Denial of Service的缩写，翻译成中文是“分布式拒绝服务“攻击，网络中的DDOS攻击与防御与上面例子所述差不多，DDOS只不过是一个概称，其下有各种攻击方式，比如“CC攻击、SYN攻击、NTP攻击、TCP攻击、DNS攻击等等”，现在DDOS发展变得越来越可怕，NTP攻击渐渐成为主流了，这意味着可以将每秒的攻击流量放大几百倍，比如每秒1G的SYN碎片攻击换成NTP放大攻击，就成为了200G或者更多。\n\n \n\n### SYN Flood\n\n这是一种利用TCP协议缺陷，发送大量伪造的TCP连接请求，从而使得被攻击方资源耗尽（CPU满负荷或内存不足）的攻击方式。建立TCP连接，需要三次握手——客户端发送SYN报文，服务端收到请求并返回报文表示接受，客户端也返回确认，完成连接。\n\nSYN Flood 就是用户向服务器发送报文后突然死机或掉线，那么服务器在发出应答报文后就无法收到客户端的确认报文（第三次握手无法完成），这时服务器端一般会重试并等待一段时间后再丢弃这个未完成的连接。一个用户出现异常导致服务器的一个线程等待一会儿并不是大问题，但恶意攻击者大量模拟这种情况，服务器端为了维护数以万计的半连接而消耗非常多的资源，结果往往是无暇理睬客户的正常请求，甚至崩溃。从正常客户的角度看来，网站失去了响应，无法访问。\n\n<div align=\"center\"> <img src=\"pics/SYN-Flood-Attack.jpg\" width=\"600\"/></div><br/>\n\n\n\n### CC 攻击\n\nCC攻击是目前应用层攻击的主要手段之一，借助代理服务器生成指向目标系统的合法请求，实现伪装和DDoS。我们都有这样的体验，访问一个静态页面，即使人多也不需要太长时间，但如果在高峰期访问论坛、贴吧等，那就很慢了，因为服务器系统需要到数据库中判断访问者否有读帖、发言等权限。访问的人越多，论坛的页面越多，数据库压力就越大，被访问的频率也越高，占用的系统资源也就相当可观。\n\nCC攻击就充分利用了这个特点，模拟多个正常用户不停地访问如论坛这些需要大量数据操作的页面，造成服务器资源的浪费，CPU长时间处于100%，永远都有处理不完的请求，网络拥塞，正常访问被中止。这种攻击技术性含量高，见不到真实源IP，见不到特别大的异常流量，但服务器就是无法进行正常连接。\n\n<div align=\"center\"> <img src=\"https://pic2.zhimg.com/80/v2-a05c091113d9df62a592088af33efd55_r.jpg\" width=\"500\"/></div><br/>\n\n之所以选择代理服务器是因为代理可以有效地隐藏自己的身份，也可以绕开防火墙，因为基本上所有的防火墙都会检测并发的TCP/IP连接数目，超过一定数目一定频率就会被认为是Connection-Flood。当然也可以使用肉鸡来发动CC攻击，攻击者使用CC攻击软件控制大量肉鸡发动攻击，肉鸡可以模拟正常用户访问网站的请求伪造成合法数据包，相比前者来说更难防御。\n\nCC攻击是针对Web服务在第七层协议发起的攻击，在越上层协议上发动DDoS攻击越难以防御，上层协议与业务关联愈加紧密，防御系统面临的情况也会更复杂。比如CC攻击中最重要的方式之一HTTP Flood，不仅会直接导致被攻击的Web前端响应缓慢，对承载的业务造成致命的影响，还可能会引起连锁反应，间接攻击到后端的Java等业务层逻辑以及更后端的数据库服务。\n\n由于CC攻击成本低、威力大，知道创宇安全专家组发现80%的DDoS攻击都是CC攻击。带宽资源严重被消耗，网站瘫痪；CPU、内存利用率飙升，主机瘫痪；瞬间快速打击，无法快速响应。\n\n\n\n### NTP Flood\n\nNTP是标准的基于UDP协议传输的网络时间同步协议，由于UDP协议的无连接性，方便伪造源地址。攻击者使用特殊的数据包，也就是IP地址指向作为反射器的服务器，源IP地址被伪造成攻击目标的IP，反射器接收到数据包时就被骗了，会将响应数据发送给被攻击目标，耗尽目标网络的带宽资源。一般的NTP服务器都有很大的带宽，攻击者可能只需要1Mbps的上传带宽欺骗NTP服务器，就可给目标服务器带来几百上千Mbps的攻击流量。\n\n因此，“问-答”方式的协议都可以被反射型攻击利用，将质询数据包的地址伪造为攻击目标地址，应答的数据包就会都被发送至目标，一旦协议具有递归效果，流量就被显著放大了，堪称一种“借刀杀人”的流量型攻击。\n\n<div align=\"center\"> <img src=\"pics/attack-ntp.png\" width=\"700\"/></div><br/>\n\n\n\n### 预防\n\n没有根治的办法，除非不用TCP/IP链接\n\n- 确保服务器的系统文件是最新版本，并及时更新系统补丁 \n- 关闭不必要的服务 \n- 限制同时打开SYN的半连接数目 \n- 缩短SYN半连接的time out时间 \n- 正确设置防火墙 \n- 禁止对主机的非开放服务的访问 \n- 限制特定IP短地址的访问 \n- 启用防火墙的防DDos的属性 \n- 严格限制对外开放的服务器的向外访问 \n- 运行端口映射程序祸端口扫描程序，要认真检查特权端口和非特权端口。 \n- 认真检查网络设备和主机/服务器系统的日志。只要日志出现漏洞或是时间变更，那这台机器就可能遭到了攻击。 \n- 限制在防火墙外与网络文件共享。这样会给黑客截取系统文件的机会，主机的信息暴露给黑客，无疑是给了对方入侵的机会。 \n\n\n\n### DOS攻击之泪滴攻击\n\n**泪滴攻击**(TearDrop) 指的是向目标机器发送损坏的IP包，诸如重叠的包或过大的包载荷。借由这些手段，该攻击可以通过TCP/IP协议栈中分片重组代码中的bug来瘫痪各种不同的操作系统。\n\n泪滴攻击是拒绝服务攻击的一种。 泪滴是一个特殊构造的应用程序，通过发送伪造的相互重叠的IP分组数据包，使其难以被接收主机重新组合。他们通常会导致目标主机内核失措。 泪滴攻击利用IP分组数据包重叠造成TCP/ IP分片重组代码不能恰当处理IP包。 泪滴攻击不被认为是一个严重的DOS攻击，不会对主机系统造成重大损失。 在大多数情况下，一次简单的重新启动是最好的解决办法，但重新启动操作系统可能导致正在运行的应用程序中未保存的数据丢失。  "
  },
  {
    "path": "notes/计算机网络.md",
    "content": "<!-- TOC -->\n\n- [前言](#前言)\n- [第一部分：传输层](#第一部分传输层)\n    - [1. 说一下OSI七层模型  TCP/IP四层模型  五层协议](#1-说一下osi七层模型--tcpip四层模型--五层协议)\n        - [（1）五层协议](#1五层协议)\n        - [（2）ISO七层模型中表示层和会话层功能是什么？](#2iso七层模型中表示层和会话层功能是什么)\n        - [（3）数据在各层之间的传递过程](#3数据在各层之间的传递过程)\n        - [（4）TCP/IP四层模型](#4tcpip四层模型)\n    - [2. TCP报头格式和UDP报头格式](#2-tcp报头格式和udp报头格式)\n        - [（1）UDP 和 TCP 的特点](#1udp-和-tcp-的特点)\n        - [（2）UDP 首部格式](#2udp-首部格式)\n        - [（3）TCP 首部格式](#3tcp-首部格式)\n    - [3. TCP三次握手？那四次挥手呢？如何保障可靠传输](#3-tcp三次握手那四次挥手呢如何保障可靠传输)\n        - [（1）三次握手](#1三次握手)\n        - [（2）为什么TCP连接需要三次握手，两次不可以吗，为什么](#2为什么tcp连接需要三次握手两次不可以吗为什么)\n        - [（3）四次挥手](#3四次挥手)\n        - [（4）四次挥手的原因](#4四次挥手的原因)\n        - [（5）TIME_WAIT](#5time_wait)\n        - [（6）如何保证可靠传输](#6如何保证可靠传输)\n        - [（7）TCP连接状态？](#7tcp连接状态)\n        - [（8）TCP和HTTP](#8tcp和http)\n    - [4. TCP连接中如果断电怎么办](#4-tcp连接中如果断电怎么办)\n    - [5. TCP和UDP区别？如何改进TCP](#5-tcp和udp区别如何改进tcp)\n    - [6. TCP滑动窗口](#6-tcp滑动窗口)\n    - [7. TCP流量控制](#7-tcp流量控制)\n    - [8. TCP拥塞处理（Congestion Handling）](#8-tcp拥塞处理congestion-handling)\n        - [（1）慢开始与拥塞避免](#1慢开始与拥塞避免)\n        - [（2）快重传与快恢复](#2快重传与快恢复)\n        - [（3）发送窗口的上限值](#3发送窗口的上限值)\n    - [9. 如何区分流量控制和拥塞控制](#9-如何区分流量控制和拥塞控制)\n    - [10. 解释RTO，RTT和超时重传](#10-解释rtortt和超时重传)\n    - [11. 停止等待和超时重传](#11-停止等待和超时重传)\n    - [12. 从输入网址到获得页面的网络请求过程](#12-从输入网址到获得页面的网络请求过程)\n- [第二部分：应用层（HTTP）](#第二部分应用层http)\n    - [1. URL、URI、URN区别](#1-urluriurn区别)\n    - [2. HTTP的请求和响应报文](#2-http的请求和响应报文)\n        - [（1）请求报文](#1请求报文)\n        - [（2）响应报文](#2响应报文)\n    - [3. HTTP状态](#3-http状态)\n        - [（1）1XX 信息](#11xx-信息)\n        - [（2）2XX 成功](#22xx-成功)\n        - [（3）3XX 重定向](#33xx-重定向)\n        - [（4）4XX 客户端错误](#44xx-客户端错误)\n        - [（5）5XX 服务器错误](#55xx-服务器错误)\n    - [4. HTTP方法](#4-http方法)\n        - [（1）GET](#1get)\n        - [（2）HEAD](#2head)\n        - [（3）POST](#3post)\n        - [（4）PUT](#4put)\n        - [（5）PATCH](#5patch)\n        - [（6）DELETE](#6delete)\n        - [（7）OPTIONS](#7options)\n        - [（8）CONNECT](#8connect)\n        - [（9）TRACE](#9trace)\n    - [5. GET和POST的区别？【阿里面经OneNote】](#5-get和post的区别阿里面经onenote)\n    - [6. 如何理解HTTP协议是无状态的](#6-如何理解http协议是无状态的)\n    - [7. 什么是短连接和长连接](#7-什么是短连接和长连接)\n    - [★ 微信二维码登录如何实现](#★-微信二维码登录如何实现)\n    - [8. Cookie](#8-cookie)\n        - [（1）用途](#1用途)\n        - [（2）创建过程](#2创建过程)\n        - [（3）分类](#3分类)\n        - [（4）JavaScript 获取 Cookie](#4javascript-获取-cookie)\n        - [（5）Secure 和 HttpOnly](#5secure-和-httponly)\n        - [（6）作用域](#6作用域)\n    - [9. Session](#9-session)\n    - [10. 浏览器禁用 Cookie](#10-浏览器禁用-cookie)\n    - [11. Cookie 与 Session 选择](#11-cookie-与-session-选择)\n    - [12. HTTPs安全性](#12-https安全性)\n        - [（1）对称密钥加密](#1对称密钥加密)\n        - [（2）非对称密钥加密](#2非对称密钥加密)\n        - [（3）HTTPs 采用的加密方式](#3https-采用的加密方式)\n    - [13. SSL/TLS协议的握手过程](#13-ssltls协议的握手过程)\n        - [SSL (Secure Socket Layer，安全套接字层)](#ssl-secure-socket-layer安全套接字层)\n        - [TLS (Transport Layer Security，传输层安全协议)](#tls-transport-layer-security传输层安全协议)\n        - [（1）client hello](#1client-hello)\n        - [（2）server hello](#2server-hello)\n        - [（3）server certificate](#3server-certificate)\n        - [（4）Server Hello Done](#4server-hello-done)\n        - [（5）Client Key Exchange](#5client-key-exchange)\n        - [（6）Change Cipher Spec(Client)](#6change-cipher-specclient)\n        - [（7）Finished(Client)](#7finishedclient)\n        - [（8）Change Cipher Spec(Server)](#8change-cipher-specserver)\n        - [（9）Finished(Server)](#9finishedserver)\n        - [（10-11）Application Data](#10-11application-data)\n        - [（12）Alert：warning, close notify](#12alertwarning-close-notify)\n        - [（*）demand client certificate](#demand-client-certificate)\n        - [（*）check server certificate](#check-server-certificate)\n    - [14. 数字签名、数字证书、SSL、https是什么关系？](#14-数字签名数字证书sslhttps是什么关系)\n        - [密码](#密码)\n        - [密钥](#密钥)\n        - [对称加密](#对称加密)\n        - [公钥加密（非对称加密）](#公钥加密非对称加密)\n        - [消息摘要](#消息摘要)\n        - [消息认证码](#消息认证码)\n        - [数字签名](#数字签名)\n        - [公钥证书](#公钥证书)\n    - [15. HTTP和HTTPS的区别【阿里面经OneNote】](#15-http和https的区别阿里面经onenote)\n    - [16. HTTP2.0特性](#16-http20特性)\n        - [（1）二进制分帧](#1二进制分帧)\n        - [（2）多路复用](#2多路复用)\n        - [（3）服务器推送](#3服务器推送)\n        - [（4）头部压缩](#4头部压缩)\n- [第三部分：网络层](#第三部分网络层)\n    - [1. mac和ip怎么转换](#1-mac和ip怎么转换)\n    - [2. IP地址子网划分](#2-ip地址子网划分)\n    - [3. 地址解析协议ARP](#3-地址解析协议arp)\n    - [4. 交换机和路由器的区别](#4-交换机和路由器的区别)\n    - [5. 子网掩码的作用](#5-子网掩码的作用)\n- [附录：参考资料](#附录参考资料)\n\n<!-- /TOC -->\n# 前言\n\n本文将总结后台开发中的核心网络知识。主要围绕网络层、传输层、应用层，核心为 TCP 和 HTTP 两部分。\n\n# 第一部分：传输层\n\n## 1. 说一下OSI七层模型  TCP/IP四层模型  五层协议\n\n<div align=\"center\"> <img src=\"assets/1536486064767.png\" width=\"\"/></div>\n\n### （1）五层协议\n\n- **应用层** ：提供用户接口，特指能够发起网络流量的程序，比如客户端程序：QQ，MSN，浏览器等；服务器程序：web服务器，邮件服务器，流媒体服务器等等。数据单位为报文。\n- **运输层** ：提供的是进程间的通用数据传输服务。由于应用层协议很多，定义通用的运输层协议就可以支持不断增多的应用层协议。运输层包括两种协议：\n  - 传输控制协议 TCP，提供面向连接、可靠的数据传输服务，数据单位为报文段；\n  - 用户数据报协议 UDP，提供无连接、尽最大努力的数据传输服务，数据单位为用户数据报。\n  - TCP 主要提供完整性服务，UDP 主要提供及时性服务。\n- **网络层** ：为主机间提供数据传输服务，而运输层协议是为主机中的进程提供服务。网络层把运输层传递下来的报文段或者用户数据报封装成分组。（负责选择最佳路径  规划IP地址）\n  - 路由器查看数据包目标IP地址，根据路由表为数据包选择路径。路由表中的类目可以人工添加（静态路由）也可以动态生成（动态路由）。\n- **数据链路层** ：不同的网络类型，发送数据的机制不同，数据链路层就是将数据包封装成能够在不同的网络传输的帧。能够进行差错检验，但不纠错，监测处错误丢掉该帧。\n  - 帧的开始和结束，透明传输，差错校验\n- **物理层** ：物理层解决如何在连接各种计算机的传输媒体上传输数据比特流，而不是指具体的传输媒体。物理层的主要任务描述为：确定与传输媒体的接口的一些特性，即：\n  - 机械特性：例接口形状，大小，引线数目\n  - 电气特性：例规定电压范围 ( -5V 到 +5V )\n  - 功能特性：例规定 -5V 表示 0，＋5V 表示 1\n  - 过程特性：也称规程特性，规定建立连接时各个相关部件的工作步骤\n\n### （2）ISO七层模型中表示层和会话层功能是什么？\n\n- **表示层** ：数据压缩、加密以及数据描述。这使得应用程序不必担心在各台主机中表示/存储的内部格式（二进制、ASCII，比如乱码）不同的问题。 \n\n- **会话层** ：建立会话，如session认证、断点续传。通信的应用程序之间建立、维护和释放面向用户的连接。通信的应用程序之间建立会话，需要传输层建立1个或多个连接。（...后台运行的木马，netstat -n）\n\n- 说明：五层协议没有表示层和会话层，而是将这些功能留给应用程序开发者处理。\n\n\n\n### （3）数据在各层之间的传递过程\n\n　　在向下的过程中，需要添加下层协议所需要的首部或者尾部，而在向上的过程中不断拆开首部和尾部。\n\n1. 路由器只有下面三层协议，因为路由器位于网络核心中，不需要为进程或者应用程序提供服务，因此也就不需要运输层和应用层。\n2. 交换机只有下面两层协议\n\n<div align=\"center\"> <img src=\"pics/transfer.jpg\" width=\"700\"/> </div><br>\n\n\n\n### （4）TCP/IP四层模型\n\n它只有四层，相当于五层协议中**数据链路层和物理层合并为网络接口层**。\n\n现在的 TCP/IP 体系结构不严格遵循 OSI 分层概念，应用层可能会直接使用 IP 层或者网络接口层。\n\n\n<div align=\"center\"> <img src=\"pics/tcp_ip_4.png\" width=\"400\"/> </div><br>\n\n\n\n\nTCP/IP 协议族是一种沙漏形状，中间小两边大，IP 协议在其中占用举足轻重的地位。 \n\n<div align=\"center\"> <img src=\"pics/tcp_ip_protocol_family.png\" width=\"500\"/> </div><br>\n\n\n\n参考资料：\n\n- [OSI模型、TCP/IP协议栈](https://arch-long.cn/articles/network/OSI%E6%A8%A1%E5%9E%8BTCPIP%E5%8D%8F%E8%AE%AE%E6%A0%88.html)\n\n\n\n## 2. TCP报头格式和UDP报头格式\n\n网络层只把分组发送到目的主机，但是真正通信的并不是主机而是主机中的进程。运输层提供了进程间的逻辑通信，运输层向高层用户屏蔽了下面网络层的核心细节，使应用程序看起来像是在两个运输层实体之间有一条端到端的逻辑通信信道。\n\n### （1）UDP 和 TCP 的特点\n\n- **用户数据报协议 UDP**（User Datagram Protocol）是无连接的，尽最大可能交付，没有拥塞控制，面向报文（对于应用程序传下来的报文不合并也不拆分，只是添加 UDP 首部），支持一对一、一对多、多对一和多对多的交互通信。例如：视频传输、实时通信\n- **传输控制协议 TCP**（Transmission Control Protocol）是面向连接的，提供可靠交付，有流量控制，拥塞控制，提供全双工通信，面向字节流（把应用层传下来的报文看成字节流，把字节流组织成大小不等的数据块），每一条 TCP 连接只能是点对点的（一对一）。\n\n### （2）UDP 首部格式\n\n<div align=\"center\"> <img src=\"pics/udp-head2.png\" width=\"700\"/> </div><br>\n\n首部字段只有 8 个字节，包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和临时添加的。\n\n\n\n### （3）TCP 首部格式\n\n<div align=\"center\"> <img src=\"pics/tcp-head.png\" width=\"700\"/> </div><br>\n\n\n\n- **序号 seq** ：用于对字节流进行编号，例如序号为 301，表示第一个字节的编号为 301，如果携带的数据长度为 100 字节，那么下一个报文段的序号应为 401。[301,400]为序号301的数据长度，下一个则为401\n- **确认号 ack** ：期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段，序号为 501，携带的数据长度为 200 字节，因此 B 期望下一个报文段的序号为 701，B 发送给 A 的确认报文段中确认号就为 701。\n- **数据偏移** ：指的是数据部分距离报文段起始处的偏移量，实际上指的是首部的长度。\n- **确认 ACK** ：当 ACK=1 时确认号字段有效，否则无效。TCP 规定，在连接建立后所有传送的报文段都必须把 ACK 置 1。\n- **同步 SYN** ：在连接建立时用来同步序号。当 SYN=1，ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接，则响应报文中 SYN=1，ACK=1。\n- **终止 FIN** ：用来释放一个连接，当 FIN=1 时，表示此报文段的发送方的数据已发送完毕，并要求释放连接。\n- **窗口** ：窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制，是因为接收方的数据缓存空间是有限的。\n\n\n\n参考资料：\n\n- [计算机网络-运输层-笔记 | SamanthaChen's Blog](https://samanthachen.github.io/2016/08/15/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C3/)\n\n\n\n## 3. TCP三次握手？那四次挥手呢？如何保障可靠传输\n\n### （1）三次握手\n\n<div align=\"center\"> <img src=\"pics/tcp-3.png\" width=\"700\"/> </div><br>\n\n**假设 A 为客户端，B 为服务器端。**\n\n- 首先 B 处于 LISTEN（监听）状态，等待客户的连接请求。\n- A 向 B 发送连接请求报文段，SYN=1，ACK=0，选择一个初始的序号 seq = x。\n- B 收到连接请求报文段，如果同意建立连接，则向 A 发送连接确认报文段，SYN=1，ACK=1，确认号为 x+1，同时也选择一个初始的序号 seq = y。\n- A 收到 B 的连接确认报文段后，还要向 B 发出确认，确认号为 ack = y+1，序号为 seq = x+1。\n- A 的 TCP 通知上层应用进程，连接已经建立。\n- B 收到 A 的确认后，连接建立。\n- B 的 TCP 收到主机 A 的确认后，也通知其上层应用进程：TCP 连接已经建立。\n\n\n\n### （2）为什么TCP连接需要三次握手，两次不可以吗，为什么\n\n**为了防止已失效的连接请求报文段突然又传送到了服务端，占用服务器资源。 （假设主机A为客户端，主机B为服务器端）**\n\n现假定出现一种异常情况，即A发出的第一个连接请求报文段并没有丢失，而是在某些网络节点长时间滞留了，以致延误到连接释放以后的某个时间才到B。本来这是一个已失效的报文段。但是B收到此失效的连接请求报文段后，就误认为是A有发出一次新的连接请求。于是就向A发出确认报文段，同意建立连接。假定不采用三次握手，那么只要B发出确认，新的连接就建立了。\n\n由于现在A并没有发出建立连接的请求，因此不会理睬B的确认，也不会向B发送数据。但B却以为新的运输连接已经建立了，并一直等待A发来数据。B的许多资源就这样白白浪费了。\n\n采用三次握手的办法可以防止上述现象的发生。例如在刚才的情况下，A不会向B的确认发出确认。B由于收不到确认，就知道A并没有要求建立连接。\n\n\n\n### （3）四次挥手\n\n<div align=\"center\"> <img src=\"pics/tcp-4.png\" width=\"750\"/> </div><br>\n\n数据传输结束后，通信的双方都可释放连接。现在 A 的应用进程先向其 TCP 发出连接释放报文段，并停止再发送数据，主动关闭 TCP连接。\n\n- A 把连接释放报文段首部的 FIN = 1，其序号 seq = u，等待 B 的确认。\n- B 发出确认，确认号 ack = u+1，而这个报文段自己的序号 seq = v。（TCP 服务器进程通知高层应用进程）\n- 从 A 到 B 这个方向的连接就释放了，TCP 连接处于半关闭状态。A 不能向 B 发送数据；B 若发送数据，A 仍要接收。\n- 当 B 不再需要连接时，发送连接释放请求报文段，FIN=1。\n- A 收到后发出确认，进入 TIME-WAIT 状态，等待 2 MSL（2*2 = 4 mins）时间后释放连接。\n- B 收到 A 的确认后释放连接。\n\n\n\n### （4）四次挥手的原因\n\n客户端发送了 FIN 连接释放报文之后，服务器收到了这个报文，就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据，传送完毕之后，服务器会发送 FIN 连接释放报文。\n\n\n\n### （5）TIME_WAIT\n\n> MSL是Maximum Segment Lifetime英文的缩写，中文可以译为 “报文最大生存时间”，他是任何报文在网络上存在的最长时间，超过这个时间报文将被丢弃。2MSL = 2*2mins = 4mins\n\n客户端接收到服务器端的 FIN 报文后进入此状态，此时并不是直接进入 CLOSED 状态，还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由：\n\n- 确保最后一个确认报文段能够到达。如果 B 没收到 A 发送来的确认报文段，那么就会重新发送连接释放请求报文段，A 等待一段时间就是为了处理这种情况的发生。\n- 等待一段时间是为了让本连接持续时间内所产生的所有报文段都从网络中消失，使得下一个新的连接不会出现旧的连接请求报文段。\n\n\n\n### （6）如何保证可靠传输\n\n**【详细请查阅】：《计算机网络原理 创新教程》P356——8.4节，可靠传输**\n\n- 应用数据被分割成TCP认为最适合发送的数据块。 \n- **超时重传**：当TCP发出一个段后，它启动一个定时器，等待目的端确认收到这个报文段。如果不能及时收到一个确认，将重发这个报文段。\n- TCP给发送的每一个包进行编号，接收方对数据包进行排序，把有序数据传送给应用层。 \n- **校验和**：TCP将保持它首部和数据的检验和。这是一个端到端的检验和，目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错，TCP将丢弃这个报文段和不确认收到此报文段。\n- TCP的接收端会丢弃重复的数据。\n- **流量控制**：TCP连接的每一方都有固定大小的缓冲空间，TCP的接收端只允许发送端发送接收端缓冲区能接纳的我数据。当接收方来不及处理发送方的数据，能提示发送方降低发送的速率，防止包丢失。TCP使用的流量控制协议是可变大小的滑动窗口协议。\n- **拥塞控制**：当网络拥塞时，减少数据的发送。 \n\n\n\n### （7）TCP连接状态？ \n\n- CLOSED：初始状态。\n- LISTEN：服务器处于监听状态。\n- SYN_SEND：客户端socket执行CONNECT连接，发送SYN包，进入此状态。\n- SYN_RECV：服务端收到SYN包并发送服务端SYN包，进入此状态。\n- ESTABLISH：表示连接建立。客户端发送了最后一个ACK包后进入此状态，服务端接收到ACK包后进入此状态。\n- FIN_WAIT_1：终止连接的一方（通常是客户机）发送了FIN报文后进入。等待对方FIN。\n- CLOSE_WAIT：（假设服务器）接收到客户机FIN包之后等待关闭的阶段。在接收到对方的FIN包之后，自然是需要立即回复ACK包的，表示已经知道断开请求。但是本方是否立即断开连接（发送FIN包）取决于是否还有数据需要发送给客户端，若有，则在发送FIN包之前均为此状态。\n- FIN_WAIT_2：此时是半连接状态，即有一方要求关闭连接，等待另一方关闭。客户端接收到服务器的ACK包，但并没有立即接收到服务端的FIN包，进入FIN_WAIT_2状态。\n- LAST_ACK：服务端发动最后的FIN包，等待最后的客户端ACK响应，进入此状态。\n- TIME_WAIT：客户端收到服务端的FIN包，并立即发出ACK包做最后的确认，在此之后的2MSL时间称为TIME_WAIT状态。\n\n### （8）TCP和HTTP\n\n<div align=\"center\"> <img src=\"pics/tcp-and-http.jpg\" width=\"500\"/></div><br/>\n\n\n\n## 4. TCP连接中如果断电怎么办\n\nTCP新手误区--心跳的意义 - CSDN博客\nhttps://blog.csdn.net/bjrxyz/article/details/71076442\n\n\n\n## 5. TCP和UDP区别？如何改进TCP\n\n- TCP和UDP区别 \n  - UDP 是无连接的，即发送数据之前不需要建立连接。 \n  - UDP 使用尽最大努力交付，即不保证可靠交付，同时也不使用拥塞控制。 \n  - UDP 是面向报文的。UDP 没有拥塞控制，很适合多媒体通信的要求。 \n  - UDP 支持一对一、一对多、多对一和多对多的交互通信。 \n  - UDP 的首部开销小，只有 8 个字节。 \n  - TCP 是面向连接的运输层协议。 \n  - 每一条 TCP 连接只能有两个端点(endpoint)，每一条 TCP 连接只能是点对点的（一对一）。 \n  - TCP 提供可靠交付的服务。 \n  - TCP 提供全双工通信。 \n  - TCP是面向字节流。   \n  - 首部最低20个字节。 \n- TCP加快传输效率的方法 \n  - 采取一块确认的机制 \n\n\n\n## 6. TCP滑动窗口\n\n<div align=\"center\"> <img src=\"pics/sliding_win.png\" width=\"\"/> </div><br>\n\n窗口是缓存的一部分，用来暂时存放字节流。发送方和接收方各有一个窗口，**接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小，发送方根据这个值和其它信息设置自己的窗口大小**。\n\n发送窗口内的字节都允许被发送，接收窗口内的字节都允许被接收。如果发送窗口左部的字节已经发送并且收到了确认，那么就将发送窗口向右滑动一定距离，直到左部第一个字节不是已发送并且已确认的状态；接收窗口的滑动类似，接收窗口左部字节已经发送确认并交付主机，就向右滑动接收窗口。\n\n接收窗口只会对窗口内最后一个按序到达的字节进行确认，例如接收窗口已经收到的字节为 {31, 34, 35}，其中 {31} 按序到达，而 {32, 33} 就不是，因此只对字节 31 进行确认。发送方得到一个字节的确认之后，就知道这个字节之前的所有字节都已经被接收。\n\n\n\n**以下进行滑动窗口模拟**\n\n在 TCP 中，**滑动窗口是为了实现流量控制**。如果对方发送数据过快，接收方就来不及接收，接收方就需要通告对方，减慢数据的发送。 \n\n<div align=\"center\"> <img src=\"pics/sliding_windows.png\" width=\"\"/></div><br/>\n\n\n\n- **发送方接收到了对方发来的报文 ack = 33, win = 10，知道对方收到了 33 号前的数据**，现在期望接收 [33, 43) 号数据。发送方连续发送了 4 个报文段假设为 A, B, C, D, 分别携带 [33, 35), [35, 36), [36, 38), [38, 41) 号数据。\n- 接收方接收到了报文段 A, C，但是没收到 B 和 D，也就是只收到了 [33, 35) 和 [36, 38) 号数据。接收方发送回对报文段 A 的确认：ack = 35, win = 10。\n- 发送方收到了 ack = 35, win = 10，对方期望接收 [35, 45) 号数据。接着发送了一个报文段 E，它携带了 [41, 44) 号数据。\n- 接收方接收到了报文段 B: [35, 36), D:[38, 41)，接收方发送对 D 的确认：ack = 41, win = 10. （这是一个累积确认）\n- 发送方收到了 ack = 41, win = 10，对方期望接收 [41, 51) 号数据。\n- ……\n- 需要注意的是，接收方接收 tcp 报文的顺序是不确定的，并非是一定先收到 35 再收到 36，也可能是先收到 36，37，再收到 35.\n\n \n\n参考资料：\n\n- [20-TCP 协议（滑动窗口——基础） - CSDN博客](https://blog.csdn.net/q1007729991/article/details/70142341)\n- [21-TCP 协议（滑动窗口——抓包分析） - CSDN博客](https://blog.csdn.net/q1007729991/article/details/70143062)\n- [TCP 的那些事儿（下） | | 酷 壳 - CoolShell](https://coolshell.cn/articles/11609.html)\n\n\n\n## 7. TCP流量控制\n\n流量控制是为了控制发送方发送速率，保证接收方来得及接收。\n\n接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小，从而影响发送方的发送速率。将窗口字段设置为 0，则发送方不能发送数据。\n\n\n\n## 8. TCP拥塞处理（Congestion Handling）\n\n拥塞控制的一般原理\n\n- 在某段时间，若对网络中某资源的需求超过了该资源所能提供的可用部分，网络的性能就要变坏——产生拥塞(congestion)。\n- 出现资源拥塞的条件：对资源需求的总和 > 可用资源\n- 若网络中有许多资源同时产生拥塞，网络的性能就要明显变坏，整个网络的吞吐量将随输入负荷的增大而下降。  \n\n\n\n如果网络出现拥塞，分组将会丢失，此时发送方会继续重传，从而导致网络拥塞程度更高。因此当出现拥塞时，应当控制发送方的速率。这一点和流量控制很像，但是出发点不同。流量控制是为了让接收方能来得及接收，而拥塞控制是为了降低整个网络的拥塞程度。\n\n\n\n<div align=\"center\"> <img src=\"pics/congest1.png\" width=\"600\"/> </div><br>\n\n \n\nTCP 主要通过四种算法来进行拥塞控制：慢开始、拥塞避免、快重传、快恢复。\n\n发送方需要维护一个叫做拥塞窗口（cwnd）的状态变量，注意拥塞窗口与发送方窗口的区别：拥塞窗口只是一个状态变量，实际决定发送方能发送多少数据的是发送方窗口。\n\n为了便于讨论，做如下假设：\n\n- 接收方有足够大的接收缓存，因此不会发生流量控制；\n- 虽然 TCP 的窗口基于字节，但是这里设窗口的大小单位为报文段。\n\n\n <div align=\"center\"> <img src=\"pics/congest2-3.png\" width=\"700\"/> </div><br>\n\n\n\n### （1）慢开始与拥塞避免\n\n　　发送的最初执行慢开始，令 cwnd=1，发送方只能发送 1 个报文段；当收到确认后，将 cwnd 加倍，因此之后发送方能够发送的报文段数量为：2、4、8 ...\n\n　　注意到慢开始每个轮次都将 cwnd 加倍，这样会让 cwnd 增长速度非常快，从而使得发送方发送的速度增长速度过快，网络拥塞的可能也就更高。设置一个慢启动阈值 ssthresh，当 cwnd >= ssthresh 时，进入拥塞避免，每个轮次只将 cwnd 加 1。\n\n　　如果出现了超时，则令 ssthresh = cwnd/2，然后重新执行慢开始。\n\n### （2）快重传与快恢复\n\n　　在接收方，要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。例如已经接收到 M1 和 M2，此时收到 M4，应当发送对 M2 的确认。\n\n　　在发送方，如果收到三个重复确认，那么可以知道下一个报文段丢失，此时执行快重传，立即重传下一个报文段。例如收到三个 M2，则 M3 丢失，立即重传 M3。\n\n　　在这种情况下，只是丢失个别报文段，而不是网络拥塞。因此执行快恢复，令 ssthresh = cwnd/2 ，cwnd = ssthresh，注意到此时直接进入拥塞避免。慢开始和快恢复的快慢指的是 cwnd 的设定值，而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1，而快恢复 cwnd 设定为 ssthresh。\n\n\n\n<div align=\"center\"> <img src=\"pics/congest3-2.png\" width=\"600\"/> </div><br>\n\n### （3）发送窗口的上限值\n\n　　发送方的发送窗口的上限值应当取为接收方窗口 rwnd 和拥塞窗口 cwnd 这两个变量中较小的一个，即应按以下公式确定：\n\n- 发送窗口的上限值 =  Min {rwnd, cwnd}\n  - 当 rwnd < cwnd 时，是接收方的接收能力限制发送窗口的最大值。\n  - 当 cwnd < rwnd 时，则是网络的拥塞限制发送窗口的最大值。 \n\n\n\n\n\n## 9. 如何区分流量控制和拥塞控制 \n\n- 拥塞控制所要做的都有一个前提，就是网络能够承受现有的网络负荷。\n- 拥塞控制是一个全局性的过程，涉及到所有的主机、所有的路由器，以及与降低网络传输性能有关的所有因素。\n- 流量控制往往指在给定的发送端和接收端之间的点对点通信量的控制。\n- 流量控制所要做的就是抑制发送端发送数据的速率，以便使接收端来得及接收。 \n\n\n\n- 流量控制属于通信双方协商；拥塞控制涉及通信链路全局。\n\n- 流量控制需要通信双方各维护一个发送窗、一个接收窗，对任意一方，接收窗大小由自身决定，发送窗大小由接收方响应的TCP报文段中窗口值确定；拥塞控制的拥塞窗口大小变化由试探性发送一定数据量数据探查网络状况后而自适应调整。\n\n- 实际最终发送窗口 = min{流控发送窗口，拥塞窗口}。\n\n\n## 10. 解释RTO，RTT和超时重传 \n\n- **超时重传**：发送端发送报文后若长时间未收到确认的报文则需要重发该报文。可能有以下几种情况：\n  - 发送的数据没能到达接收端，所以对方没有响应。\n  - 接收端接收到数据，但是ACK报文在返回过程中丢失。\n  - 接收端拒绝或丢弃数据。\n- **RTO**：从上一次发送数据，因为长期没有收到ACK响应，到下一次重发之间的时间。就是重传间隔。\n  - 通常每次重传RTO是前一次重传间隔的两倍，计量单位通常是RTT。例：1RTT，2RTT，4RTT，8RTT......\n  - 重传次数到达上限之后停止重传。\n- **RTT**：数据从发送到接收到对方响应之间的时间间隔，即数据报在网络中一个往返用时。大小不稳定。\n\n\n\n## 11. 停止等待和超时重传\n\n\n\n\n\n## 12. 从输入网址到获得页面的网络请求过程\n\n- 查询 DNS\n  - 浏览器搜索自身的DNS缓存\n  - 搜索操作系统的DNS缓存，本地host文件查询\n  - 如果 DNS 服务器和我们的主机在同一个子网内，系统会按照下面的 ARP 过程对 DNS 服务器进行 ARP查询\n  - 如果 DNS 服务器和我们的主机在不同的子网，系统会按照下面的 ARP 过程对默认网关进行查询\n- 浏览器获得域名对应的IP地址后，发起HTTP三次握手\n\n- TCP/IP连接建立起来后，浏览器就可以向服务器发送HTTP请求了 \n\n- TLS 握手\n\n  - 客户端发送一个 `ClientHello` 消息到服务器端，消息中同时包含了它的 Transport Layer Security (TLS) 版本，可用的加密算法和压缩算法。\n  - 服务器端向客户端返回一个 `ServerHello` 消息，消息中包含了服务器端的TLS版本，服务器所选择的加密和压缩算法，以及数字证书认证机构（Certificate Authority，缩写 CA）签发的服务器公开证书，证书中包含了公钥。客户端会使用这个公钥加密接下来的握手过程，直到协商生成一个新的对称密钥\n  - 客户端根据自己的信任CA列表，验证服务器端的证书是否可信。如果认为可信，客户端会生成一串伪随机数，使用服务器的公钥加密它。这串随机数会被用于生成新的对称密钥\n  - 服务器端使用自己的私钥解密上面提到的随机数，然后使用这串随机数生成自己的对称主密钥\n  - 客户端发送一个 `Finished` 消息给服务器端，使用对称密钥加密这次通讯的一个散列值\n  - 服务器端生成自己的 hash 值，然后解密客户端发送来的信息，检查这两个值是否对应。如果对应，就向客户端发送一个 `Finished` 消息，也使用协商好的对称密钥加密\n  - 从现在开始，接下来整个 TLS 会话都使用对称秘钥进行加密，传输应用层（HTTP）内容\n\n- HTTP 服务器请求处理\n\n  HTTPD(HTTP Daemon)在服务器端处理请求/响应。最常见的 HTTPD 有 Linux 上常用的 Apache 和 nginx，以及 Windows 上的 IIS。\n\n  - HTTPD 接收请求\n\n  - - 服务器把请求拆分为以下几个参数：\n\n      HTTP 请求方法(`GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `CONNECT`, `OPTIONS`, 或者 `TRACE`)。直接在地址栏中输入 URL 这种情况下，使用的是 GET 方法域名：google.com请求路径/页面：/ (我们没有请求google.com下的指定的页面，因此 / 是默认的路径)\n\n  - 服务器验证其上已经配置了 google.com 的虚拟主机\n\n  - 服务器验证 google.com 接受 GET 方法\n\n  - 服务器验证该用户可以使用 GET 方法(根据 IP 地址，身份信息等)\n\n  - 如果服务器安装了 URL 重写模块（例如 Apache 的 mod_rewrite 和 IIS 的 URL Rewrite），服务器会尝试匹配重写规则，如果匹配上的话，服务器会按照规则重写这个请求\n\n  - 服务器根据请求信息获取相应的响应内容，这种情况下由于访问路径是 \"/\" ,会访问首页文件（你可以重写这个规则，但是这个是最常用的）。\n\n  - 服务器会使用指定的处理程序分析处理这个文件，假如 Google 使用 PHP，服务器会使用 PHP 解析 index 文件，并捕获输出，把 PHP 的输出结果返回给请求者\n\n- 服务器接受到这个请求，根据路径参数，经过后端的一些处理生成HTML页面代码返回给浏览器 \n\n- 浏览器拿到完整的HTML页面代码开始解析和渲染，如果遇到引用的外部[js](http://lib.csdn.net/base/javascript)，CSS,图片等静态资源，它们同样也是一个个的HTTP请求，都需要经过上面的步骤 \n- 浏览器根据拿到的资源对页面进行渲染，最终把一个完整的页面呈现给用户 \n\n\n\n超详细版本请转向阅读：[what-happens-when-zh_CN](https://github.com/skyline75489/what-happens-when-zh_CN)\n\n\n\n# 第二部分：应用层（HTTP）\n\n## 1. URL、URI、URN区别\n\n- URI（Uniform Resource Identifier，统一资源标识符）\n\n  web服务器资源的名字，例如： index.html\n\n- URL（Uniform Resource Locator，统一资源定位符）\n\n- URN（Uniform Resource Name，统一资源名称），例如 urn:isbn:0-486-27557-4。\n\nURI 包含 URL 和 URN，目前 WEB 只有 URL 比较流行，所以见到的基本都是 URL。\n\n<div align=\"center\"> <img src=\"pics/url_uri_urn.jpg\" width=\"400\"/> </div><br>\n\n## 2. HTTP的请求和响应报文\n\n### （1）请求报文\n\n<div align=\"center\"> <img src=\"pics//HTTP_RequestMessageExample.png\" width=\"\"/> </div><br>\n\n\n\n**GET请求**\n<div align=\"center\"> <img src=\"pics/http_request_get.png\" width=\"\"/> </div><br>\n\n\n\n**POST请求**\n\n<div align=\"center\"> <img src=\"pics/http_request_post.png\" width=\"\"/> </div><br>\n\n\n\n\n### （2）响应报文\n\n\n<div align=\"center\"> <img src=\"pics//HTTP_ResponseMessageExample.png\" width=\"\"/> </div><br>\n\n**200响应**\n<div align=\"center\"> <img src=\"pics/http_response_200.png\" width=\"\"/> </div><br>\n\n\n\n**404响应**\n<div align=\"center\"> <img src=\"pics/http_response_400.png\" width=\"\"/> </div><br>\n\n\n\n\n\n参考资料：\n\n- [这一次,让我们再深入一点 - HTTP报文 - 掘金](https://juejin.im/post/5a4f782c5188257326469d7c)\n\n\n\n## 3. HTTP状态\n\n服务器返回的 **响应报文** 中第一行为状态行，包含了状态码以及原因短语，用来告知客户端请求的结果。\n\n| 状态码 | 类别                             | 原因短语                   |\n| ------ | -------------------------------- | -------------------------- |\n| 1XX    | Informational（信息性状态码）    | 接收的请求正在处理         |\n| 2XX    | Success（成功状态码）            | 请求正常处理完毕           |\n| 3XX    | Redirection（重定向状态码）      | 需要进行附加操作以完成请求 |\n| 4XX    | Client Error（客户端错误状态码） | 服务器无法处理请求         |\n| 5XX    | Server Error（服务器错误状态码） | 服务器处理请求出错         |\n\n### （1）1XX 信息\n\n- **100 Continue** ：表明到目前为止都很正常，客户端可以继续发送请求或者忽略这个响应。\n\n### （2）2XX 成功\n\n- **200 OK**\n- **204 No Content** ：请求已经成功处理，但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息，而不需要返回数据时使用。\n- **206 Partial Content** ：表示客户端进行了范围请求。响应报文包含由 Content-Range 指定范围的实体内容。\n\n### （3）3XX 重定向\n\n- **301 Moved Permanently** ：永久性重定向\n- **302 Found** ：临时性重定向\n- **303 See Other** ：和 302 有着相同的功能，但是 303 明确要求客户端应该采用 GET 方法获取资源。\n- 注：虽然 HTTP 协议规定 301、302 状态下重定向时不允许把 POST 方法改成 GET 方法，但是大多数浏览器都会在 301、302 和 303 状态下的重定向把 POST 方法改成 GET 方法。\n- **304 Not Modified** ：如果请求报文首部包含一些条件，例如：If-Match，If-Modified-Since，If-None-Match，If-Range，If-Unmodified-Since，如果不满足条件，则服务器会返回 304 状态码。\n- **307 Temporary Redirect** ：临时重定向，与 302 的含义类似，但是 307 要求浏览器不会把重定向请求的 POST 方法改成 GET 方法。\n\n### （4）4XX 客户端错误\n\n- **400 Bad Request** ：请求报文中存在语法错误。\n- **401 Unauthorized** ：该状态码表示发送的请求需要有认证信息（BASIC 认证、DIGEST 认证）。如果之前已进行过一次请求，则表示用户认证失败。\n- **403 Forbidden** ：请求被拒绝，服务器端没有必要给出拒绝的详细理由。\n- **404 Not Found**\n\n### （5）5XX 服务器错误\n\n- **500 Internal Server Error** ：服务器正在执行请求时发生错误。\n- **503 Service Unavailable** ：服务器暂时处于超负载或正在进行停机维护，现在无法处理请求。\n\n\n\n## 4. HTTP方法\n\n客户端发送的 **请求报文** 第一行为请求行，包含了方法字段。\n\n### （1）GET\n\n> 获取资源\n\n当前网络请求中，绝大部分使用的是 GET 方法。\n\n### （2）HEAD\n\n> 获取报文首部\n\n和 GET 方法一样，但是不返回报文实体主体部分。\n\n主要用于确认 URL 的有效性以及资源更新的日期时间等。\n\n### （3）POST\n\n> 传输实体主体\n\nPOST 主要用来传输数据，而 GET 主要用来获取资源。\n\n更多 POST 与 GET 的比较请见第八章。\n\n### （4）PUT\n\n> 上传文件\n\n由于自身不带验证机制，任何人都可以上传文件，因此存在安全性问题，一般不使用该方法。\n\n```\nPUT /new.html HTTP/1.1\nHost: example.com\nContent-type: text/html\nContent-length: 16\n\n<p>New File</p>\n```\n\n### （5）PATCH\n\n> 对资源进行部分修改\n\nPUT 也可以用于修改资源，但是只能完全替代原始资源，PATCH 允许部分修改。\n\n```\nPATCH /file.txt HTTP/1.1\nHost: www.example.com\nContent-Type: application/example\nIf-Match: \"e0023aa4e\"\nContent-Length: 100\n\n[description of changes]\n```\n\n### （6）DELETE\n\n> 删除文件\n\n与 PUT 功能相反，并且同样不带验证机制。\n\n```\nDELETE /file.html HTTP/1.1\n```\n\n### （7）OPTIONS\n\n> 查询支持的方法\n\n查询指定的 URL 能够支持的方法。\n\n会返回 Allow: GET, POST, HEAD, OPTIONS 这样的内容。\n\n### （8）CONNECT\n\n> 要求在与代理服务器通信时建立隧道\n\n使用 SSL（Secure Sockets Layer，安全套接层）和 TLS（Transport Layer Security，传输层安全）协议把通信内容加密后经网络隧道传输。\n\n```\nCONNECT www.example.com:443 HTTP/1.1\n```\n\n\n<div align=\"center\"> <img src=\"pics/http_connect.jpg\" width=\"\"/> </div><br>\n\n\n\n\n### （9）TRACE\n\n> 追踪路径\n\n服务器会将通信路径返回给客户端。\n\n发送请求时，在 Max-Forwards 首部字段中填入数值，每经过一个服务器就会减 1，当数值为 0 时就停止传输。\n\n通常不会使用 TRACE，并且它容易受到 XST 攻击（Cross-Site Tracing，跨站追踪）。\n\n\n\n## 5. GET和POST的区别？【阿里面经OneNote】\n\n> 就下面的找几个点和面试官侃侃而谈即可，不可能全部都记得，想到什么讲什么吧\n\n- GET 被强制服务器支持 \n- 浏览器对URL的长度有限制，所以GET请求不能代替POST请求发送大量数据\n- GET请求发送数据更小 \n- GET请求是不安全的\n- GET请求是幂等的 \n  - 幂等的意味着对同一URL的多个请求应该返回同样的结果\n- POST请求不能被缓存 \n- POST请求相对GET请求是「安全」的 \n  - 这里安全的含义仅仅是指是非修改信息\n- GET用于信息获取，而且是安全的和幂等的\n  - 所谓安全的意味着该操作用于获取信息而非修改信息。换句话说，GET 请求一般不应产生副作用。就是说，它仅仅是获取资源信息，就像数据库查询一样，不会修改，增加数据，不会影响资源的状态。\n\n- POST是用于修改服务器上的资源的请求\n- 发送包含未知字符的用户输入时，POST 比 GET 更稳定也更可靠 \n\n\n\n**引申：说完原理性的问题，我们从表面上来看看GET和POST的区别：**\n\n- GET是从服务器上获取数据，POST是向服务器传送数据。 GET和 POST只是一种传递数据的方式，GET也可以把数据传到服务器，他们的本质都是发送请求和接收结果。只是组织格式和数据量上面有差别，http协议里面有介绍\n- GET是把参数数据队列加到提交表单的ACTION属性所指的URL中，值和表单内各个字段一一对应，在URL中可以看到。POST是通过HTTP POST机制，将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。 因为GET设计成传输小数据，而且最好是不修改服务器的数据，所以浏览器一般都在地址栏里面可以看到，但POST一般都用来传递大数据，或比较隐私的数据，所以在地址栏看不到，能不能看到不是协议规定，是浏览器规定的。\n- 对于GET方式，服务器端用Request.QueryString获取变量的值，对于POST方式，服务器端用Request.Form获取提交的数据。 没明白，怎么获得变量和你的服务器有关，和GET或POST无关，服务器都对这些请求做了封装  \n- GET传送的数据量较小，不能大于2KB。POST传送的数据量较大，一般被默认为不受限制。但理论上，IIS4中最大量为80KB，IIS5中为100KB。 POST基本没有限制，我想大家都上传过文件，都是用POST方式的。只不过要修改form里面的那个type参数 \n- GET安全性非常低，POST安全性较高。 如果没有加密，他们安全级别都是一样的，随便一个监听器都可以把所有的数据监听到。\n\n\n\n## 6. 如何理解HTTP协议是无状态的\n\n\tHTTP协议是无状态的（stateless），指的是协议对于事务处理没有记忆能力，服务器不知道客户端是什么状态。也就是说，打开一个服务器上的网页和上一次打开这个服务器上的网页之间没有任何联系。HTTP是一个无状态的面向连接的协议，无状态不代表HTTP不能保持TCP连接，更不能代表HTTP使用的是UDP协议（无连接）。 \n\t\n\t缺少状态意味着如果后续处理需要前面的信息，则它必须重传，这样可能导致每次连接传送的数据量增大。另一方面，在服务器不需要先前信息时它的应答就较快。 \n\n\n\n## 7. 什么是短连接和长连接\n\n在HTTP/1.0中默认使用短连接。也就是说，客户端和服务器每进行一次HTTP操作，就建立一次连接，任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源（如JavaScript文件、图像文件、CSS文件等），每遇到这样一个Web资源，浏览器就会重新建立一个HTTP会话。\n\n而从HTTP/1.1起，默认使用长连接，用以保持连接特性。使用长连接的HTTP协议，会在响应头加入这行代码：\n\n```javascript\nConnection:keep-alive\n```\n\n在使用长连接的情况下，当一个网页打开完成后，客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭，客户端再次访问这个服务器时，会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接，它有一个保持时间，可以在不同的服务器软件（如Apache）中设定这个时间。实现长连接需要客户端和服务端都支持长连接。\n\nHTTP协议的长连接和短连接，实质上是TCP协议的长连接和短连接。\n\n\n\n## ★ 微信二维码登录如何实现\n\n\n\n\n\n## 8. Cookie\n\nHTTP 协议是无状态的，主要是为了让 HTTP 协议尽可能简单，使得它能够处理大量事务。HTTP/1.1 引入 Cookie 来保存状态信息。\n\nCookie 是服务器发送到用户浏览器并保存在本地的一小块数据，它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。它用于告知服务端两个请求是否来自同一浏览器，并保持用户的登录状态。\n\n### （1）用途\n\n- 会话状态管理（如用户登录状态、购物车、游戏分数或其它需要记录的信息）\n- 个性化设置（如用户自定义设置、主题等）\n- 浏览器行为跟踪（如跟踪分析用户行为等）\n\nCookie 曾一度用于客户端数据的存储，因为当时并没有其它合适的存储办法而作为唯一的存储手段，但现在随着现代浏览器开始支持各种各样的存储方式，Cookie 渐渐被淘汰。由于服务器指定 Cookie 后，浏览器的每次请求都会携带 Cookie 数据，会带来额外的性能开销（尤其是在移动环境下）。新的浏览器 API 已经允许开发者直接将数据存储到本地，如使用 Web storage API （本地存储和会话存储）或 IndexedDB。\n\n\n\n### （2）创建过程\n\n服务器发送的响应报文包含 Set-Cookie 首部字段，客户端得到响应报文后把 Cookie 内容保存到浏览器中。\n\n```\nHTTP/1.0 200 OK\nContent-type: text/html\nSet-Cookie: yummy_cookie=choco\nSet-Cookie: tasty_cookie=strawberry\n\n[page content]\n```\n\n客户端之后对同一个服务器发送请求时，会从浏览器中读出 Cookie 信息通过 Cookie 请求首部字段发送给服务器。\n\n```\nGET /sample_page.html HTTP/1.1\nHost: www.example.org\nCookie: yummy_cookie=choco; tasty_cookie=strawberry\n```\n\n\n\n### （3）分类\n\n- 会话期 Cookie：浏览器关闭之后它会被自动删除，也就是说它仅在会话期内有效。\n- 持久性 Cookie：指定一个特定的过期时间（Expires）或有效期（max-age）之后就成为了持久性的 Cookie。\n\n```\nSet-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;\n```\n\n\n\n### （4）JavaScript 获取 Cookie\n\n通过 `Document.cookie` 属性可创建新的 Cookie，也可通过该属性访问非 HttpOnly 标记的 Cookie。\n\n```\ndocument.cookie = \"yummy_cookie=choco\";\ndocument.cookie = \"tasty_cookie=strawberry\";\nconsole.log(document.cookie);\n```\n\n\n\n### （5）Secure 和 HttpOnly\n\n标记为 Secure 的 Cookie 只应通过被 HTTPS 协议加密过的请求发送给服务端。但即便设置了 Secure 标记，敏感信息也不应该通过 Cookie 传输，因为 Cookie 有其固有的不安全性，Secure 标记也无法提供确实的安全保障。\n\n标记为 HttpOnly 的 Cookie 不能被 JavaScript 脚本调用。因为跨域脚本 (XSS) 攻击常常使用 JavaScript 的 `Document.cookie`API 窃取用户的 Cookie 信息，因此使用 HttpOnly 标记可以在一定程度上避免 XSS 攻击。\n\n```\nSet-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly\n```\n\n\n\n### （6）作用域\n\nDomain 标识指定了哪些主机可以接受 Cookie。如果不指定，默认为当前文档的主机（不包含子域名）。如果指定了 Domain，则一般包含子域名。例如，如果设置 Domain=mozilla.org，则 Cookie 也包含在子域名中（如 developer.mozilla.org）。\n\nPath 标识指定了主机下的哪些路径可以接受 Cookie（该 URL 路径必须存在于请求 URL 中）。以字符 %x2F (\"/\") 作为路径分隔符，子路径也会被匹配。例如，设置 Path=/docs，则以下地址都会匹配：\n\n- /docs\n- /docs/Web/\n- /docs/Web/HTTP\n\n\n\n## 9. Session\n\n除了可以将用户信息通过 Cookie 存储在用户浏览器中，也可以利用 Session 存储在服务器端，存储在服务器端的信息更加安全。\n\nSession 可以存储在服务器上的文件、数据库或者内存中，现在最常见的是将 Session 存储在内存型数据库中，比如 Redis。\n\n使用 Session 维护用户登录的过程如下：\n\n- 用户进行登录时，用户提交包含用户名和密码的表单，放入 HTTP 请求报文中；\n- 服务器验证该用户名和密码；\n- 如果正确则把用户信息存储到 Redis 中，它在 Redis 中的 ID 称为 Session ID；\n- 服务器返回的响应报文的 Set-Cookie 首部字段包含了这个 Session ID，客户端收到响应报文之后将该 Cookie 值存入浏览器中；\n- 客户端之后对同一个服务器进行请求时会包含该 Cookie 值，服务器收到之后提取出 Session ID，从 Redis 中取出用户信息，继续之后的业务操作。\n\n应该注意 Session ID 的安全性问题，不能让它被恶意攻击者轻易获取，那么就不能产生一个容易被猜到的 Session ID 值。此外，还需要经常重新生成 Session ID。在对安全性要求极高的场景下，例如转账等操作，除了使用 Session 管理用户状态之外，还需要对用户进行重新验证，比如重新输入密码，或者使用短信验证码等方式。\n\n![](pics/session_mechanism.png)\n\n\n\n\n\n\n\n## 10. 浏览器禁用 Cookie\n\n此时无法使用 Cookie 来保存用户信息，只能使用 Session。除此之外，不能再将 Session ID 存放到 Cookie 中，而是使用 URL 重写技术，将 Session ID 作为 URL 的参数进行传递。\n\n\n\n## 11. Cookie 与 Session 选择\n\n- Cookie 只能存储 ASCII 码字符串，而 Session 则可以存取任何类型的数据，因此在考虑数据复杂性时首选 Session；\n- Cookie 存储在浏览器中，容易被恶意查看。如果非要将一些隐私数据存在 Cookie 中，可以将 Cookie 值进行加密，然后在服务器进行解密；\n- 对于大型网站，如果用户所有的信息都存储在 Session 中，那么开销是非常大的，因此不建议将所有的用户信息都存储到 Session 中。\n\n\n\n## 12. HTTPs安全性\n\n**HTTP 有以下安全性问题：**\n\n- 使用明文进行通信，内容可能会被窃听；\n- 不验证通信方的身份，通信方的身份有可能遭遇伪装；\n- 无法证明报文的完整性，报文有可能遭篡改。\n\nHTTPs（Hyper Text Transfer Protocol over Secure Socket Layer），是以安全为目标的HTTP通道，简单讲是HTTP的安全版。\n\nHTTPs 并不是新协议，而是让 HTTP 先和 SSL（Secure Sockets Layer）通信，再由 SSL 和 TCP 通信。也就是说 HTTPs 使用了隧道进行通信。\n\n通过使用 SSL，HTTPs 具有了加密（防窃听）、认证（防伪装）和完整性保护（防篡改）。\n\n<div align=\"center\"> <img src=\"https://github.com/CyC2018/Interview-Notebook/raw/master/pics/ssl-offloading.jpg\" width=\"600\"/></div><br/>\n\n### （1）对称密钥加密\n\n对称密钥加密（Symmetric-Key Encryption），加密和解密使用同一密钥。\n\n- 优点：运算速度快；\n- 缺点：无法安全地将密钥传输给通信方。\n\n\n\n<div align=\"center\"> <img src=\"https://raw.githubusercontent.com/CyC2018/Interview-Notebook/master/pics/7fffa4b8-b36d-471f-ad0c-a88ee763bb76.png\" width=\"500\"/> </div><br>\n\n\n### （2）非对称密钥加密\n\n非对称密钥加密，又称公开密钥加密（Public-Key Encryption），加密和解密使用不同的密钥。\n\n公开密钥所有人都可以获得，通信发送方获得接收方的公开密钥之后，就可以使用公开密钥进行加密，接收方收到通信内容后使用私有密钥解密。\n\n非对称密钥除了用来加密，还可以用来进行签名。因为私有密钥无法被其他人获取，因此通信发送方使用其私有密钥进行签名，通信接收方使用发送方的公开密钥对签名进行解密，就能判断这个签名是否正确。\n\n- 优点：可以更安全地将公开密钥传输给通信发送方；\n- 缺点：运算速度慢。\n\n<div align=\"center\"> <img src=\"https://raw.githubusercontent.com/CyC2018/Interview-Notebook/master/pics/39ccb299-ee99-4dd1-b8b4-2f9ec9495cb4.png\" width=\"500\"/> </div><br>\n\n### （3）HTTPs 采用的加密方式\n\nHTTPs 采用混合的加密机制，使用非对称密钥加密用于传输对称密钥来保证安全性，之后使用对称密钥加密进行通信来保证效率。\n\n<div align=\"center\"> <img src=\"pics/How-HTTPS-Works2.png\" width=\"600\"/> </div><br>\n\n\n\n\n\n## 13. SSL/TLS协议的握手过程\n\n我们知道，HTTP 协议都是明文传输内容，在早期只展示静态内容时没有问题。伴随着互联网的快速发展，人们对于网络传输安全性的要求也越来越高，HTTPS 协议因此出现。如上图所示，在 HTTPS 加密中真正起作用的其实是 SSL/TLS 协议。SSL/TLS 协议作用在 HTTP 协议之下，对于上层应用来说，原来的发送接收数据流程不变，这就很好地兼容了老的 HTTP 协议，这也是软件开发中分层实现的体现。\n\n\n\n### SSL (Secure Socket Layer，安全套接字层)\n\nSSL为Netscape所研发，用以保障在Internet上数据传输之安全，利用数据加密(Encryption)技术，可确保数据在网络上之传输过程中不会被截取，当前为3.0版本。\n\nSSL协议可分为两层： SSL记录协议（SSL Record Protocol）：它建立在可靠的传输协议（如TCP）之上，为高层协议提供数据封装、压缩、加密等基本功能的支持。 SSL握手协议（SSL Handshake Protocol）：它建立在SSL记录协议之上，用于在实际的数据传输开始前，通讯双方进行身份认证、协商加密算法、交换加密密钥等。\n\n\n\n### TLS (Transport Layer Security，传输层安全协议)\n\n用于两个应用程序之间提供保密性和数据完整性。\nTLS 1.0是IETF（Internet Engineering Task Force，Internet工程任务组）制定的一种新的协议，它建立在SSL 3.0协议规范之上，是SSL 3.0的后续版本，可以理解为SSL 3.1，它是写入了 RFC 的。该协议由两层组成： TLS 记录协议（TLS Record）和 TLS 握手协议（TLS Handshake）。较低的层为 TLS 记录协议，位于某个可靠的传输协议（例如 TCP）上面。\n\n\n\nSSL/TLS 握手是为了**安全**地协商出一份**对称加密**的秘钥，这个过程很有意思，下面我们一起来了解一下。\n\n\n\n<div align=\"center\"> <img src=\"pics/https_com.png\" width=\"600\"/> </div><br>\n\n\n\n\n\n### （1）client hello\n\n握手第一步是客户端向服务端发送 Client Hello 消息，这个消息里包含了一个客户端生成的随机数 **Random1**、客户端支持的**加密套件**（Support Ciphers）和 SSL Version 等信息。\n\n\n\n### （2）server hello \n\n第二步是服务端向客户端发送 Server Hello 消息，这个消息会从 Client Hello 传过来的 Support Ciphers 里确定一份加密套件，这个套件决定了后续加密和生成摘要时具体使用哪些算法，另外还会生成一份随机数 **Random2**。注意，至此客户端和服务端都拥有了两个随机数（Random1+ Random2），这两个随机数会在后续生成对称秘钥时用到。\n\n\n\n### （3）server certificate \n\n这一步是服务端将自己的证书下发给客户端，让客户端验证自己的身份，客户端验证通过后取出证书中的公钥。\n\n\n\n### （4）Server Hello Done\n\nServer Hello Done 通知客户端 Server Hello 过程结束。\n\n\n\n### （5）Client Key Exchange\n\n上面客户端根据服务器传来的公钥生成了 **PreMaster Key**，Client Key Exchange 就是将这个 key 传给服务端，服务端再用自己的私钥解出这个 **PreMaster Key** 得到客户端生成的 **Random3**。至此，客户端和服务端都拥有 **Random1** + **Random2** + **Random3**，两边再根据同样的算法就可以生成一份秘钥，握手结束后的应用层数据都是使用这个秘钥进行对称加密。\n\n为什么要使用三个随机数呢？这是因为 SSL/TLS 握手过程的数据都是明文传输的，并且多个随机数种子来生成秘钥不容易被暴力破解出来。\n\n\n\n### （6）Change Cipher Spec(Client)\n\n这一步是客户端通知服务端后面再发送的消息都会使用前面协商出来的秘钥加密了，是一条事件消息。\n\n\n\n### （7）Finished(Client)\n\n客户端发送Finished报文。该报文包含连接至今全部报文的整理校验值。这次握手协议是否能成功，要以服务器是否能够正确解密该报文作为判定标准。\n\n\n\n### （8）Change Cipher Spec(Server)\n\n服务器同样发送Change Cipher Spec报文给客户端\n\n\n\n### （9）Finished(Server)\n\n服务器同样发送Finished报文给客户端\n\n\n\n### （10-11）Application Data\n\n到这里，双方已安全地协商出了同一份秘钥，所有的应用层数据都会用这个秘钥加密后再通过 TCP 进行可靠传输。 \n\n\n\n### （12）Alert：warning, close notify\n\n最后由客户端断开连接。断开连接时，发送close_notify报文。上图做了一些省略，在这步之后再发送一种叫做MAC（Message Authentication Code）的报文摘要。MAC能够查知报文是否遭到篡改，从而保护报文的完整性。\n\n\n\n### （*）demand client certificate\n\nCertificate Request 是服务端要求客户端上报证书，这一步是可选的，对于安全性要求高的场景会用到。\n\n\n\n### （*）check server certificate\n\n客户端收到服务端传来的证书后，先从 CA 验证该证书的合法性，验证通过后取出证书中的服务端公钥，再生成一个随机数 **Random3**，再用服务端公钥非对称加密 **Random3** 生成 **PreMaster Key**。\n\n<div align=\"center\"> <img src=\"pics/SSL_handshake.png\" width=\"\"/></div><br/>\n\n\n\n\n\n参考资料：\n\n- [SSL_handshake_with_two_way_authentication_with_certificates.svg](https://upload.wikimedia.org/wikipedia/commons/a/ae/SSL_handshake_with_two_way_authentication_with_certificates.svg)\n\n- [SSL/TLS 握手过程详解 - 简书](https://www.jianshu.com/p/7158568e4867)\n\n- [图解SSL/TLS协议 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html)\n\n- [【慕课视频】ios开发网络协议https请求视频教程](https://www.imooc.com/learn/969)\n\n- [学习HTTP/2 | levy](http://levy.work/2017-12-28-http2/)\n\n- [详解 https 是如何确保安全的？ - 后端 - 掘金](https://juejin.im/entry/570469035bbb500051d4eceb)\n\n\n## 14. 数字签名、数字证书、SSL、https是什么关系？\n\nHTTPS 是建立在密码学基础之上的一种安全通信协议，严格来说是基于 HTTP 协议和 SSL/TLS 的组合。理解 HTTPS 之前有必要弄清楚一些密码学的相关基础概念，比如：明文、密文、密码、密钥、对称加密、非对称加密、信息摘要、数字签名、数字证书。接下来我会逐个解释这些术语，文章里面提到的『数据』、『消息』都是同一个概念，表示用户之间通信的内容载体，此外文章中提到了以下几个角色：\n\n- Alice：消息发送者\n- Bob：消息接收者\n- Attacker：中间攻击者\n- Trent：第三方认证机构\n\n### 密码\n\n密码学中的“密码”术语与网站登录时用的密码（password）是不一样的概念，password 翻译过来其实是“口令”，它是用于认证用途的一组文本字符串。\n\n而密码学中的密码（cipher）是一套算法(algorithm)，这套算法用于对消息进行加密和解密，从明文到密文的过程称之为加密，密文反过来生成明文称之为解密，加密算法与解密算法合在一起称为密码算法。\n\n### 密钥\n\n密钥（key）是在使用密码算法过程中输入的一段参数。同一个明文在相同的密码算法和不同的密钥计算下会产生不同的密文。很多知名的密码算法都是公开的，密钥才是决定密文是否安全的重要参数，通常密钥越长，破解的难度越大，比如一个8位的密钥最多有256种情况，使用穷举法，能非常轻易的破解。根据密钥的使用方法，密码可分为对称加密和公钥加密。\n\n### 对称加密\n\n对称密钥（Symmetric-key algorithm）又称为共享密钥加密，加密和解密使用相同的密钥。常见的对称加密算法有DES、3DES、AES、RC5、RC6。对称密钥的优点是计算速度快，但是它有缺点，接收者需要发送者告知密钥才能解密，因此密钥如何安全的发送给接收者成为了一个问题。\n\n<div align=\"center\"> <img src=\"https://pic3.zhimg.com/80/v2-95f25c12ae406da22e7c4b4205a491fb_r.jpg\" width=\"\"/></div><br/>\n\n\n\nAlice 给 Bob 发送数据时，把数据用对称加密后发送给 Bob，发送过程中由于对数据进行了加密，因此即使有人窃取了数据也没法破解，因为它不知道密钥是什么。但是同样的问题是 Bob 收到数据后也一筹莫展，因为它也不知道密钥是什么，那么 Alice 是不是可以把数据和密钥一同发给 Bob 呢。当然不行，一旦把密钥和密钥一起发送的话，那就跟发送明文没什么区别了，因为一旦有人把密钥和数据同时获取了，密文就破解了。所以对称加密的密钥配是个问题。如何解决呢，公钥加密是一个办法。\n\n### 公钥加密（非对称加密）\n\n公开密钥加密（public-key cryptography）简称公钥加密，这套密码算法包含配对的密钥对，分为加密密钥和解密密钥。发送者用加密密钥进行加密，接收者用解密密钥进行解密。加密密钥是公开的，任何人都可以获取，因此加密密钥又称为公钥（public key），解密密钥不能公开，只能自己使用，因此它又称为私钥（private key）。常见的公钥加密算法有 RSA。\n\n还是以Alice 给 Bob 发送数据为例，公钥加密算法由接收者 Bob 发起\n\n1. Bob 生成公钥和私钥对，私钥自己保存，不能透露给任何人。\n2. Bob 把公钥发送给 Alice，发送过程中即使被人窃取也没关系\n3. Alice 用公钥把数据进行加密，并发送给 Bob，发送过程中被人窃取了同样没关系，因为没有配对的私钥进行解密是没法破解的\n4. Bob 用配对的私钥解密。\n\n<div align=\"center\"> <img src=\"https://pic4.zhimg.com/80/v2-5bd504b82ccc87ae643a6d6e09579f13_r.jpg\" width=\"\"/></div><br/>\n\n\n\n虽然公钥加密解决了密钥配送的问题，但是你没法确认公钥是不是合法的，Bob 发送的公钥你不能肯定真的是 Bob 发的，因为也有可能在 Bob 把公钥发送给 Alice 的过程中出现中间人攻击，把真实的公钥掉包替换。而对于 Alice 来说完全不知。还有一个缺点是它的运行速度比对称加密慢很多。\n\n\n\n### 消息摘要\n\n消息摘要（message digest）函数是一种用于判断数据完整性的算法，也称为散列函数或哈希函数，函数返回的值叫散列值，散列值又称为消息摘要或者指纹（fingerprint）。这种算法是一个不可逆的算法，因此你没法通过消息摘要反向推倒出消息是什么。所以它也称为**单向散列函数**。下载软件时如何确定是官方提供的完整版呢，如果有中间人在软件里面嵌入了病毒，你也不得而知。所以我们可以使用散列函数对消息进行运算，生成散列值，通常软件提供方会同时提供软件的下载地址和软件的散列值，用户把软件下载后在本地用相同的散列算法计算出散列值，与官方提供的散列值对比，如果相同，说明该软件是完成的，否则就是被人修改过了。常用的散列算法有MD5、SHA。\n\n<div align=\"center\"> <img src=\"https://pic3.zhimg.com/80/v2-1d6cf205ce30b01519d8b86e03968643_r.jpg\" width=\"\"/></div><br/>\n\n\n\n \n\n下载 Eclipse 时，官方网站同时提供了软件地址和消息摘要\n\n<div align=\"center\"> <img src=\"https://pic4.zhimg.com/80/v2-f688bc4f1f5a28882394b2be9bb2ede9_r.jpg\" width=\"\"/></div><br/>\n\n\n\n\n\n散列函数可以保证数据的完整性，识别出数据是否被篡改，但它并不能识别出数据是不是伪装的，因为中间人可以把数据和消息摘要同时替换，数据虽然是完整的，但真实数据被掉包了，接收者收到的并不是发送者发的，而是中间人的。消息认证是解决数据真实性的办法。认证使用的技术有消息认证码和数字签名。\n\n\n\n### 消息认证码\n\n消息认证码（message authentication code）是一种可以确认消息完整性并进行认证（消息认证是指确认消息来自正确的发送者）的技术，简称 MAC。消息认证码可以简单理解为一种与密钥相关的单向散列函数。\n\n<div align=\"center\"> <img src=\"https://pic1.zhimg.com/80/v2-73d52bc2d3828eec4f0dde5ea68eff36_r.jpg\" width=\"\"/></div><br/>\n\nAlice 给 Bob 发送消息前，先把共享密钥（key）发送给 Bob，Alice 把消息计算出 MAC 值，连同消息一起发送给 Bob，Bob 接收到消息和 MAC 值后，与本地计算得到 MAC 值对比，如果两者相同，就说明消息是完整的，而且可以确定是 Alice 发送的，没有中间人伪造。不过，消息认证码同样会遇到对称加密的密钥配送问题，因此解决密钥配送问题还是要采用公钥加密的方式。\n\n此外，消息认证码还有一个无法解决的问题，Bob 虽然可以识别出消息的篡改和伪装，但是 Alice 可以否认说：“我没发消息，应该是 Bob 的密钥被 Attacker 盗取了，这是 Attacker 发的吧”。Alice 这么说你还真没什么可以反驳的，那么如何防止 Alice 不承认呢，数字签名可以实现。\n\n\n\n### 数字签名\n\nAlice 发邮件找 Bob 借1万钱，因为邮件可以被人篡改（改成10万），也可以被伪造（Alice 根本就没发邮件，而是 Attacker 伪造 Alice 在发邮件），Alice 借了钱之后还可以不承认（不是我借的，我没有签名啊）。\n\n**消息认证码**可以解决篡改和伪造的问题，Alice 不承认自己借了钱时，Bob 去找第三方机构做公正，即使这样，公正方也没法判断 Alice 有没有真的借钱，因为他们俩共享了密钥，也就是说两个都可以计算出正确的 MAC 值，Bob 说：“明明你发的消息和 MAC 值和我自己生成的 MAC 值一样，肯定是你发的消息”，Alice 说：“你把密钥透露给了其他人，是他发的邮件，你找他去吧”。Alice 矢口否认。\n\n数字签名（Digital Signature）就可以解决否认的问题，发送消息时，Alice 和 Bob 使用不同的密钥，把公钥加密算法反过来使用，发送者 Alice 使用私钥对消息进行签名，而且只能是拥有私钥的 Alice 可以对消息签名，Bob 用配对的公钥去验证签名，第三方机构也可以用公钥验证签名，如果验证通过，说明消息一定是 Alice 发送的，抵赖也不行，因为你只有 Alice 可以生成签名。这就防止了否认的问题。\n\n<div align=\"center\"> <img src=\"https://pic3.zhimg.com/80/v2-e1819aed10962a9d148a8b4460d606b5_r.jpg\" width=\"\"/></div><br/>\n\n\n\n它的流程是:\n\n第一步：发送者 Alice **把消息哈希函数处理生成消息摘要，摘要信息使用私钥加密之后生成签名**，连同消息一起发送给接收者 Bob。\n\n第二步：数据经过网络传输，Bob收到数据后，把签名和消息分别提取出来。\n\n第三步：对签名进行验证，验证的过程是先把消息提取出来做同样的Hash处理，得到消息摘要，再与 Alice 传过来的签名用公钥解密，如果两者相等，就表示签名验证成功，否则验证失败，表示不是 Alice发的。\n\n<div align=\"center\"> <img src=\"pics/ca-sign.png\" width=\"\"/></div><br/>\n\n\n\n\n\n### 公钥证书\n\n公钥密码在数字签名技术里面扮演举足轻重的角色，但是如何保证公钥是合法的呢，如果是遭到中间人攻击，掉包怎么办？这个时候公钥就应该交给一个第三方权威机构来管理，这个机构就是认证机构（Certification Authority）CA，CA 把用户的姓名、组织、邮箱地址等个人信息收集起来，还有此人的公钥，并由 CA 提供数字签名生成公钥证书（Public-Key Certificate）PKC，简称证书。\n\n<div align=\"center\"> <img src=\"https://pic4.zhimg.com/80/v2-f96d70c0d8848921c2ebb6ef591f3c66_r.jpg\" width=\"\"/></div><br/>\n\n\n\nAlice 向 Bob 发送消息时，是通过 Bob 提供的公钥加密后的数据，而 Alice 获取的公钥并不是由 Bob 直接给的，而是由委托一个受信任的第三方机构给的。\n\n1. Bob 生成密钥对，私钥自己保管，公钥交给认证机构 Trent。\n2. Trent 经过一系列严格的检查确认公钥是 Bob 本人的\n3. Trent 事先也生成自己的一套密钥对，用自己的私钥对 Bob 的公钥进行数字签名并生成数字证书。证书中包含了 Bob 的公钥。公钥在这里是不需要加密的，因为任何人获取 Bob 的公钥都没事，只要确定是 Bob 的公钥就行。\n4. Alice 获取 Trent 提供的证书。\n5. Alice 用 Trent 提供的公钥对证书进行签名验证，签名验证成功就表示证书中的公钥是 Bob 的。\n6. 于是 Alice 就可以用 Bob 提供的公钥对消息加密后发送给 Bob。\n7. Bob 收到密文后，用与之配对的私钥进行解密。\n\n至此，一套比较完善的数据传输方案就完成了。HTTPS（SSL/TLS）就是在这样一套流程基础之上建立起来的。\n\n\n\n\n\n参考资料：\n\n- [数字签名、数字证书、SSL、https是什么关系？ - 知乎](https://www.zhihu.com/question/52493697)\n- [【Python之禅 】HTTPS 为什么更安全，先看这些](https://zhuanlan.zhihu.com/p/25324735)\n\n\n\n\n\n\n## 15. HTTP和HTTPS的区别【阿里面经OneNote】\n\n- http是HTTP协议运行在TCP之上。所有传输的内容都是明文，客户端和服务器端都无法验证对方的身份。 \n- https是HTTP运行在SSL/TLS之上，SSL/TLS运行在TCP之上。所有传输的内容都经过加密，加密采用对称加密，但对称加密的密钥用服务器方的证书进行了非对称加密。此外客户端可以验证服务器端的身份，如果配置了客户端验证，服务器方也可以验证客户端的身份。 \n- https协议需要到ca申请证书，一般免费证书很少，需要交费。 \n- http是超文本传输协议，信息是明文传输，https 则是具有安全性的ssl加密传输协议 \n- http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443。 \n- http的连接很简单,是无状态的 \n- HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议 要比http协议安全 \n\n\n\n## 16. HTTP2.0特性\n\nHTTP/2的通过支持请求与响应的多路复用来减少延迟，通过压缩HTTP首部字段将协议开销降至最低，同时增加对请求优先级和服务器端推送的支持。 \n\n\n\n### （1）二进制分帧\n\n先来理解几个概念：\n\n帧：HTTP/2 数据通信的最小单位消息：指 HTTP/2 中逻辑上的 HTTP 消息。例如请求和响应等，消息由一个或多个帧组成。\n\n流：存在于连接中的一个虚拟通道。流可以承载双向消息，每个流都有一个唯一的整数ID。\n\nHTTP/2 采用二进制格式传输数据，而非 HTTP 1.x 的文本格式，二进制协议解析起来更高效。 HTTP / 1 的请求和响应报文，都是由起始行，首部和实体正文（可选）组成，各部分之间以文本换行符分隔。HTTP/2 将请求和响应数据分割为更小的帧，并且它们采用二进制编码。 \n\n**HTTP/2 中，同域名下所有通信都在单个连接上完成，该连接可以承载任意数量的双向数据流。**每个数据流都以消息的形式发送，而消息又由一个或多个帧组成。多个帧之间可以乱序发送，根据帧首部的流标识可以重新组装。\n\n\n\n### （2）多路复用\n\n多路复用，代替原来的序列和阻塞机制。所有就是请求的都是通过一个 TCP连接并发完成。 HTTP 1.x 中，如果想并发多个请求，必须使用多个 TCP 链接，且浏览器为了控制资源，还会对单个域名有 6-8个的TCP链接请求限制，如下图，红色圈出来的请求就因域名链接数已超过限制，而被挂起等待了一段时间。\n\n<div align=\"center\"> <img src=\"pics/http2-tcp.jpg\" width=\"\"/></div><br/>\n\n在 HTTP/2 中，有了二进制分帧之后，HTTP /2 不再依赖 TCP 链接去实现多流并行了，在 HTTP/2中：\n\n- 同域名下所有通信都在单个连接上完成。\n- 单个连接可以承载任意数量的双向数据流。\n- 数据流以消息的形式发送，而消息又由一个或多个帧组成，多个帧之间可以乱序发送，因为根据帧首部的流标识可以重新组装。\n\n这一特性，使性能有了极大提升：\n\n- **同个域名只需要占用一个 TCP 连接**，消除了因多个 TCP 连接而带来的延时和内存消耗。\n- 单个连接上可以并行交错的请求和响应，之间互不干扰。\n- 在HTTP/2中，每个请求都可以带一个31bit的优先值，0表示最高优先级， 数值越大优先级越低。有了这个优先值，客户端和服务器就可以在处理不同的流时采取不同的策略，以最优的方式发送流、消息和帧。\n\n \n\n### （3）服务器推送\n\n服务端可以在发送页面HTML时主动推送其它资源，而不用等到浏览器解析到相应位置，发起请求再响应。例如服务端可以主动把JS和CSS文件推送给客户端，而不需要客户端解析HTML时再发送这些请求。\n\n服务端可以主动推送，客户端也有权利选择是否接收。如果服务端推送的资源已经被浏览器缓存过，浏览器可以通过发送RST_STREAM帧来拒收。主动推送也遵守同源策略，服务器不会随便推送第三方资源给客户端。\n\n\n\n### （4）头部压缩\n\nHTTP 1.1请求的大小变得越来越大，有时甚至会大于TCP窗口的初始大小，因为它们需要等待带着ACK的响应回来以后才能继续被发送。HTTP/2对消息头采用HPACK（专为http/2头部设计的压缩格式）进行压缩传输，能够节省消息头占用的网络的流量。而HTTP/1.x每次请求，都会携带大量冗余头信息，浪费了很多带宽资源。 \n\n\n\n参考资料：\n\n-  [一文读懂 HTTP/2 特性](https://zhuanlan.zhihu.com/p/26559480)\n-  [【体验http1.1和http2的性能对比动画】HTTP/2: the Future of the Internet | Akamai](HTTP/2: the Future of the Internet | Akamai)\n\n\n\n\n# 第三部分：网络层\n\n## 1. mac和ip怎么转换\n\n**ARP协议：**\n\n将IP地址通过广播 目标MAC地址是FF-FF-FF-FF-FF-FF 解析目标IP地址的MAC地址\n扫描本网段MAC地址。\n\n\n\n**DHCP协议：**\n\nDHCP租约过程就是DHCP客户机动态获取IP地址的过程。\n\nDHCP租约过程分为4步：\n\n1. 客户机请求IP（客户机发DHCPDISCOVER广播包）；\n\n2. 服务器响应（服务器发DHCPOFFER广播包）；\n\n3. 客户机选择IP（客户机发DHCPREQUEST广播包）；\n4. 服务器确定租约（服务器发DHCPACK/DHCPNAK广播包）。\n\n\n\n参考资料：\n\n- [图解DHCP的4步租约过程-大浪淘沙-51CTO博客](http://blog.51cto.com/yuanbin/109574)\n\n\n\n## 2. IP地址子网划分\n\n| 二进制   | 十进制 |\n| -------- | ------ |\n| 1        | 1      |\n| 10       | 2      |\n| 100      | 4      |\n| 1000     | 8      |\n| 10000    | 16     |\n| 100000   | 32     |\n| 1000000  | 64     |\n| 10000000 | 128    |\n|          |        |\n| 10000000 | 128    |\n| 11000000 | 192    |\n| 11100000 | 224    |\n| 11110000 | 240    |\n| 11111000 | 248    |\n| 11111100 | 252    |\n| 11111110 | 254    |\n| 11111111 | 255    |\n\n\n\n<div align=\"center\"><img src=\"assets/子网划分.png\" width=\"650\"/></div>\n\n\n\n```\nIP分类\n公有地址：\nIP分类        缺省掩码\nA 1－127      /8\nB 128－191        /16\nC 192－223      /24\nD 224－239      组播地址\nE 240－247    保留地址\n私有地址：\nA：10.0.0.0 - 10.255.255.255\nB:  172.16.0.0 - 172.31.255.255\nC:  192.168.0.0 - 192.168.255.255\n\n判断合法的主机（IP）地址：\n192.168.10.240/24        合法\n192.168.10.0/24          不合法，主机位全为0，网络地址\n192.168.10.255/24        不合法，主机位全为1，子网广播地址\n255.255.255.255              不合法，网络和主机位全为1，全网广播地址\n127.x.x.x/8                不合法，本地环回地址\n172.16.3.5/24              合法\n192.168.5.240/32        合法\n224.10.10.10.1              不合法，组播地址\n300.2.4.200/24              不合法\n```\n\n\n\n- IP特殊地址\n  - 本地环回地址：127.0.0.0 – 127.255.255.255，测试主机TCP/IP协议栈是否安装正确。\n  - 本地链路地址：169.254.0.0 – 169.254.255.255，自动地址无法获取时系统自动配置占位。\n  - 受限广播地址：255.255.255.255，发往这个地址的数据不能跨越三层设备，但本地网络内所有的主机都可以接收到数据\n\n\n\n- 参考资料：\n  - [4internetLayer](https://canliture.github.io/s/NetworkEngineer/04internetLayer.html)\n  - [IP地址和子网划分 - 混沌的光与影 - 博客园](https://www.cnblogs.com/lifi/p/7353279.html)\n\n  - [ZenCloud2/13-网络管理.md at a78722799508a7ac3fc7d055ff8d2d88edd0b595 · destinyplan/ZenCloud2](ZenCloud2/13-网络管理.md at a78722799508a7ac3fc7d055ff8d2d88edd0b595 · destinyplan/ZenCloud2\n    )\n\n\n\n\n## 3. 地址解析协议ARP\n\n\n\n\n\n## 4. 交换机和路由器的区别\n\n1. 路由器可以给你的局域网自动分配IP，虚拟拨号，就像一个交通警察，指挥着你的电脑该往哪走，你自己不用操心那么多了。交换机只是用来分配网络数据的。\n2. 路由器在网络层，路由器根据IP地址寻址，路由器可以处理TCP/IP协议，交换机不可以。\n3. 交换机在中继层，交换机根据MAC地址寻址。路由器可以把一个IP分配给很多个主机使用，这些主机对外只表现出一个IP。交换机可以把很多主机连起来，这些主机对外各有各的IP。\n4. 路由器提供防火墙的服务，交换机不能提供该功能。集线器、交换机都是做端口扩展的，就是扩大局域网(通常都是以太网)的接入点，也就是能让局域网可以连进来更多的电脑。路由器是用来做网间连接，也就是用来连接不同的网络。\n\n交换机是利用**物理地址或者说MAC地址**来确定转发数据的目的地址。而路由器则是利用不同网络的ID号(即IP地址)来确定数据转发的地址。IP地址是在软件中实现的，描述的是设备所在的网络，有时这些第三层的地址也称为协议地址或者网络地址。MAC地址通常是硬件自带的，由网卡生产商来分配的，而且已经固化到了网卡中去，一般来说是不可更改的。而IP地址则通常由网络管理员或系统自动分配。\n\n \n\n**路由器和交换机的区别一**：交换机是一根网线上网，但是大家上网是分别拨号，各自使用自己的宽带，大家上网没有影响。而路由器比交换机多了一个虚拟拨号功能，通过同一台路由器上网的电脑是共用一个宽带账号，大家上网要相互影响。\n**路由器和交换机的区别二**：交换机工作在中继层，交换机根据MAC地址寻址。路由器工作在网络层，根据IP地址寻址，路由器可以处理TCP/IP协议，而交换机不可以。\n\n**路由器和交换机的区别三**：交换机可以使连接它的多台电脑组成局域网，如果还有代理服务器的话还可以实现同时上网功能而且局域网所有电脑是共享它的带宽速率的，但是交换机没有路由器的自动识别数据包发送和到达地址的功能。路由器可以自动识别数据包发送和到达的地址，路由器相当于马路上的警察，负责交通疏导和指路的。\n\n**路由器和交换机的区别四**：举几个例子,路由器是小邮局，就一个地址(IP)，负责一个地方的收发(个人电脑，某个服务器，所以你家上网要这个东西)，交换机是省里的大邮政中心，负责由一个地址给各个小地方的联系。简单的说路由器专管入网，交换机只管配送，路由路由就是给你找路让你上网的，交换机只负责开门，交换机上面要没有路由你是上不了网的。\n\n**路由器和交换机的区别五**：路由器提供了防火墙的服务。路由器仅仅转发特定地址的数据包，不传送不支持路由协议的数据包传送和未知目标网络数据包的传送，从而可以防止广播风暴。\n\n\n\n\n\n## 5. 子网掩码的作用\n\n内网中192.168.1.199的前三组是网络号，后一组是主机号，子网掩码就是255.255.255.0\n\n**首先要说明的是**：不是某个IP的网络号和主机号决定子网掩码是什么，而是子网掩码决定了某个IP地址的网络号与主机号是什么，IP地址是要搭配子网掩码使用的。例如上面的子网掩码决定了192.168.1.199的前三段192.168.1是网络号，最后一段199是主机号。\n\n我们再来理解子网掩码的作用，先举个例子，市面上的两个厂家都生产电子秤，每个厂家都坚称他们的秤最准，那你是怎么知道他们的秤到底准不准？很简单，你去找一个 1KG 的国际千克原器，各放到他们的秤上测量，如果秤的测量值是1KG，那这把秤就是准的，**子网掩码的作用就相当于这个大家公认的国际千克原器，是我们测量两个IP是否属于同一个网段的一个工具（应该说是让你知道某个IP地址的网络号与主机号分别是什么） 。**\n\n \n\n**如果让你判断一个IP地址：192.168.1.199的网络号和主机号分别是什么？**\n\n请问你怎么判断？你凭什么说192.168.1是网络号？199是主机号？有什么根据吗？\n\n但是如果我给你一个IP地址是以下（带子网掩码）形式的：\n\nIP：192.168.1.199   \n\n子网掩码：255.255.255.0 \n\n那么根据大家公认的规则，你就可以得出这个IP的网络号和主机号了，怎么算呢？\n\n子网掩码的长度和IP地址一样也是一串32位的二进制数字，只不过为人类的可读性和记忆性的方便，通常使用十进制数字来表示，例如把上面的IP地址和子网掩码都转换成相应的二进制就是下面这样的：\n\n                        **十进制**                                                   **二进制**\n\nIP    地址：192.168.1.199       **‐＞**11000000.10101000.00000001.11000111\n\n子网掩码：255.255.255.0       **‐＞**11111111.11111111.11111111.00000000\n\n十进制的显示形式是给人看的，二进制的显示形式是给计算机看的。。。\n\n子网掩码的左边是网络位，用二进制数字“1”表示，1的数目等于网络位的长度；右边是主机位，用二进制数字“0”表示，0的数目等于主机位的长度。 \n\n例如上面的子网掩码255.255.255.0的  “1”的个数是左边24位，则对应IP地址左边的位数也是24位;\n\n                        **十进制**                                                   **二进制**\n\nIP    地址：192.168.1.199       **‐＞11000000.10101000.00000001**.11000111\n\n子网掩码：255.255.255.0       **‐＞11111111.11111111.11111111**.00000000\n\n则这个IP地址的网络号就是11000000.10101000.00000001 ，转换成十进制就是 192.168.1，网掩码255.255.255.0的  “0”的个数是右边8位，则这个IP地址的主机号就是11000111，转换成十进制就是199.\n\n\n\n\n\n# 附录：参考资料\n\n- [OSI 七层参考模型-极客学院（4课时，47分钟）](http://www.jikexueyuan.com/course/1400.html)\n\n- 《计算机网络原理 创新教程》（韩立刚主编）\n- 《自顶向下计算机网络》（第4版）"
  },
  {
    "path": "notes/超简版面试清单.md",
    "content": "# 前言\n\n 面试必备技能清单，这里不会详细论述，更多的是清单列举，罗列一些关键字和链接注释。\n\n\n\n# 数据结构与算法\n\n- 排序算法\n  - 选择排序\n  - 冒泡排序\n  - 插入排序\n  - [快速排序](https://github.com/xingshaocheng/architect-awesome/blob/master/README.md#%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F)\n  - 快速排序（普通，二路，三路）\n  - 归并排序\n  - 希尔排序\n  - [堆排序](https://github.com/xingshaocheng/architect-awesome/blob/master/README.md#%E5%A0%86%E6%8E%92%E5%BA%8F)\n  - 计数排序\n    - https://www.jianshu.com/p/ff1797625d66\n    - \n  - [桶排序](https://github.com/xingshaocheng/architect-awesome/blob/master/README.md#%E6%A1%B6%E6%8E%92%E5%BA%8F)\n  - [基数排序](https://github.com/xingshaocheng/architect-awesome/blob/master/README.md#%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F)\n  - [二分查找](https://github.com/xingshaocheng/architect-awesome/blob/master/README.md#%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE)\n  - [Java 中的排序工具](https://github.com/xingshaocheng/architect-awesome/blob/master/README.md#java-%E4%B8%AD%E7%9A%84%E6%8E%92%E5%BA%8F%E5%B7%A5%E5%85%B7)\n- 数据结构\n  - 红黑树（5点特性）\n\n- 思维拓展\n  - 一堆字符串求出现次数最多的字符串\n\n\n\n# Java\n\n- Java 基础部分\n  - \n- Java 集合框架\n  - ArrayList 与 LinkedList\n  - Hashtable、Hashmap 与  ConcurrentHashmap\n- Java 并发编程\n  - 线程状态\n    - 新建、可运行（运行和就绪）、阻塞、无限期等待、限期等待、死亡\n  - 实现线程三种方式（继承Thread，实现 Runnable 接口，实现 Callable 接口）\n  - 并发两个关键问题\n    - 线程通信\n    - 线程同步\n  - volatile 和 synchronized\n  - lock 和 synchronized\n  - Java内存模型（JMM），解决并发中的可见性，原子性，有序性问题\n- Java IO\n  - 序列化\n  - AIO、BIO、NIO\n- Java 虚拟机\n  - 运行时数据区\n  - \n- Java 设计模式\n  - 单例模式\n  - 工厂模式\n\n\n\n\n\n# Java Web\n\n- Spring\n  - IOC\n  - AOP\n  - DI\n- SpringMVC\n  - 执行流程\n- MyBatis\n\n\n\n# 数据库\n\n- 存储引擎\n  - MyISAM（表级锁，不支持事务，全文索引，只读应用）\n    - .frm / .myd / .myi\n    - 非聚集索引\n  - InnoDB（行级锁，最大支持并发，事务，表更新和查询都相当的频繁）\n    - .frm / .ibd\n    - 聚集索引，以主键为索引来组织数据\n- 索引（衡量标准：IO渐进复杂度）\n  - Hash（冲突，无法做范围查询）\n  - FullText（全文搜索，优化：前缀索引）\n  - R-Tree（空间索引，附近三公里的数据怎么查询）\n  - B+ Tree（B Tree：每个key、data信息存在结点，B+ Tree存在叶子节点）\n- 哪些情况下不会使用索引\n  - 全表扫描更快\n  - 条件中有or\n  - 不是复合索引的第一部分\n  - like以%开始\n\n- 隔离级别\n  - 串行化\n  - 可重复读\n  - 读已提交\n  - 读未提交\n\n- Redis 数据类型\n  - String：字符串\n  - Hash：字典\n  - List：链表\n  - Set：集合\n  - Sorted Set：有序集合\n\n# 操作系统\n\n- 进程和线程的区别\n  - 资源、调度、开销、通信 4个方面来对比\n- 线程间通信\n  - synchronized\n  - while轮询\n  - wait() / notify()\n  - 管道\n\n- 进程通信\n  - 直接通信\n  - 间接通信\n\n\n\n\n\n# 计算机网络\n\n- 应用层\n  - HTTP请求和响应报文\n\n- 传输层\n  - 为什么三次握手，两次不行吗\n  - 四次挥手 TimeWait 作用\n  - TCP可靠传输\n\n\n\n\n\n# 分布式\n\n- 分布式通信协议\n  - http\n  - https\n  - RESTful API\n\n- 分布式锁\n\n\n\n# 项目\n\n- 扫码登录原理\n- Cookie 和 Session 的原理\n- 单点登录\n\n\n\n\n\n\n\n- 参考资料\n  - [面试宝典](http://www.funtl.com/2018/04/07/contents/Java-%E9%9D%A2%E8%AF%95%E5%AE%9D%E5%85%B8/)"
  },
  {
    "path": "notes/软件测试.md",
    "content": "# 软件测试\n\n## 单元测试\n\n- 白盒测试  能看到完整代码的\n- 黑盒测试  没有源码的，功能测试\n- 灰盒测试  介于白盒测试与黑盒测试之间的一种测试，灰盒测试多用于集成测试阶段，不仅关注输出、输入的正确性，同时也关注程序内部的情况。\n\n## 压力测试\n\n- 并发数的问题，能承载多少并发\n\n## 疲劳强度测试\n\n- 长期稳定运行，72小时 7天\n\n## 冒烟测试\n\n- 对主要流程测试，例如 支付环节\n\n## 集成测试\n\n- 完整功能的测试，最终的是测试 整体业务\n\n## 回归测试\n\n- 增加一个功能\n\n## 自动化测试\n\n- 编码、场景设计\n\n## TDD 测试驱动编程\n\nreview\n\n- 代码评审"
  },
  {
    "path": "notes/面试话术.md",
    "content": "# 面试话术\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## HR面试\n\n0. **自我介绍**\n\n   面试官你好，我叫 Frank，应聘的是应用开发（Java方向）\n\n1. **你觉得你的优势是什么**\n\n   用技术解决问题，产品思维，提高效率，解放生产力。举例：投票系统，考勤项目\n\n2. **你有什么优点**\n\n   乐于分享交流，倾听别人的分享，三人行必有我师，持续学习。举例：分享会\n\n3. **你有什么缺点**\n\n   有些事情上太过于执着\n\n4. **为什么想考研**\n\n   本科毕业后技术不够成熟，学习碎片化，故决定考研，固话一些技术层面的东西。举例：github项目\n\n5. **有遇到过什么挫折吗**\n\n   高考失利，努力考研，复盘，技术才是最重要的学历是敲门砖\n\n6. **为什么想加入我们公司**\n\n   有很棒的技术团队，很优秀的产品，想一起创造改变世界一小步\n\n7. **说说你对加班的看法**\n\n   - 事发突然加班，项目上线加班很正常\n   - 拖延症加班，不能容忍\n\n8. **期望薪资**\n\n   25w\n\n9. **对于第一份工作，自己最看重什么**\n\n   学习和成长\n\n10. **如何面对理想与现实的差距（特别指工作中）**\n\n    因为不是一帆风顺，所以要更加努力。牢固自己的基础知识，向着目标去。\n\n11. **你有什么想问的吗**\n    - 如果可以加入贵公司，那么在入职的前会有怎样的一个培训\n    - 如果有 idea 或是想法，如何能够在贵公司实现\n\n\n\n## 结尾总结\n\n到了结尾的话说一下自己都能解决问题，表达自己的能力ok！"
  },
  {
    "path": "notes/项目实践与技术分析.md",
    "content": "# 前言\n\n在这里将罗列一些，项目中常用到的项目技术分析。\n\n切勿夸夸其谈，真诚有理有据。差异化、深入点。\n\n\n\n# 第一部分：项目实践\n\n\n\n\n\n\n\n# 第二部分：技术分析\n\n## 1. ETL数据仓库同步\n\n阿里出品的ETL工具dataX初体验_慕课手记\nhttps://www.imooc.com/article/15640\n\n\n\n## 2. 怎么解决项目中超卖的问题 \n\n\n\n## 3. 扫码登录原理\n\n- [扫码登录实现原理](https://mp.weixin.qq.com/s/Vp9tVJryU04j4hzLWxuVjA)\n\n\n\n\n\n## 4. JWT接口验证\n\n\n\n\n\n\n\n## 5. 消息队列\n\n点对点\n\n发布订阅\n\n\n\n\n\n\n\n# 更新说明\n\n"
  },
  {
    "path": "others/OVERVIEW.md",
    "content": "# 更新说明\n\n| 目录                   | 章节                                                       | 版本 | 说明   | 计划 | 记录              |\n| ---------------------- | ---------------------------------------------------------- | ---- | ------ | ---- | ----------------- |\n| **一、数据结构与算法** |                                                            |      |        |      |                   |\n|                        | [数据结构与算法](notes/数据结构与算法.md)                  | v0.5 |        | 8/4  |                   |\n|                        |                                                            |      |        |      |                   |\n| **二、Java核心知识**   |                                                            |      |        |      |                   |\n|                        | [Java基础](notes/JavaArchitecture/01%20Java%20基础.md)     | v3.0 | 初稿版 |      | 7/31 ; 8/31       |\n|                        | [集合框架](notes/JavaArchitecture/02%20Java%20集合框架.md) | v3.0 | 初稿版 |      | 8/2 ; 9/1         |\n|                        | [并发编程](notes/JavaArchitecture/03%20Java%20并发编程.md) | v3.0 | 初稿版 |      | 8/14 ; 9/2        |\n|                        | [IO流](notes/JavaArchitecture/04%20Java%20IO.md)           | v2.5 | 基础版 |      | 8/6               |\n|                        | [虚拟机](notes/JavaArchitecture/05%20Java%20虚拟机.md)     | v2.4 | 基础版 |      | 8/18              |\n|                        | [设计模式](notes/JavaArchitecture/06%20设计模式.md)        | v0.5 |        |      |                   |\n|                        | [Java Web](notes/JavaArchitecture/07%20Java%20Web.md)      | v1.0 |        |      |                   |\n|                        | [Spring](notes/JavaWeb/Spring.md)                          | v1.0 |        |      |                   |\n|                        | SpringMVC                                                  |      |        |      |                   |\n|                        | Mybatis                                                    |      |        |      |                   |\n|                        | 系统架构                                                   |      |        |      |                   |\n|                        |                                                            |      |        |      |                   |\n| **三、数据库**         |                                                            |      |        |      |                   |\n|                        | [MySQL](notes/MySQL.md)                                    |      |        |      |                   |\n|                        | Redis                                                      |      |        |      |                   |\n|                        |                                                            |      |        |      |                   |\n| **四、操作系统**       |                                                            |      |        |      |                   |\n|                        | [Linux](notes/Linux.md)                                    | v2.5 | 基础版 |      | 8/27              |\n|                        | [操作系统](notes/操作系统.md)                              | v2.5 | 基础版 |      | 7/30，8/22        |\n|                        |                                                            |      |        |      |                   |\n| **五、网络部分**       |                                                            |      |        |      |                   |\n|                        | [计算机网络](notes/计算机网络.md)                          | v2.5 | 基础版 |      | 差网络层部分，8/8 |\n|                        |                                                            |      |        |      |                   |\n| **六、高级部分**       |                                                            |      |        |      |                   |\n|                        | Dobbo                                                      |      |        |      |                   |\n|                        | ZooKeeper                                                  |      |        |      |                   |\n|                        | Nginx                                                      |      |        |      |                   |\n|                        | Docker                                                     |      |        |      |                   |\n\n- 面试技巧\n- 项目技术点分析\n- 简历分析\n\n"
  },
  {
    "path": "others/Overview_ulli.md",
    "content": "### 一、数据结构与算法\n\n- [数据结构与算法 | v0.5](notes/数据结构与算法.md)\n\n\n\n### 二、Java基础\n\n- [Java基础](notes/JavaArchitecture/01%20Java%20基础.md)\n- [集合框架](notes/JavaArchitecture/02%20Java%20集合框架.md)\n- [并发编程](notes/JavaArchitecture/03%20Java%20并发编程.md)\n- [IO流](notes/JavaArchitecture/04%20Java%20IO.md)\n- [虚拟机](notes/JavaArchitecture/05%20Java%20虚拟机.md)\n- [设计模式 | v0.5](notes/JavaArchitecture/06%20设计模式.md)\n- [Java Web | v1.0](notes/JavaArchitecture/07%20Java%20Web.md)\n  - [Spring | v1.0](notes/JavaWeb/Spring.md)\n  - SpringMVC\n  - SpringBoot\n\n\n\n### 三、数据库\n\n- [MySQL](notes/MySQL.md)\n- Redis\n\n\n\n### 四、操作系统\n\n- [Linux](notes/Linux.md)\n- [操作系统 | v1.0](notes/操作系统.md)\n  - 内存管理待完成\n\n\n\n### 五、网络部分\n\n- [计算机网络](notes/计算机网络.md)\n\n\n\n### 六、高级部分\n\n- 中间件\n  - Dobbo\n  - zokkeeper\n- Nginx\n- Docker"
  },
  {
    "path": "others/Skill-Tree.md",
    "content": "# Skill-Tree\n\n## Java语言基础\n\n1. Java核心知识（★★★）\n2. 集合框架（★★★）\n3. 并发编程（★★★）\n4. Java虚拟机（★★）\n5. IO（★★）\n6. Java Web（★★）\n    - Servlet\n    - Spring\n    - SpringMVC\n    - Mybatis\n    - Hibernate\n    - Struct2\n\n\n\n## 通用考察基础\n\n1. 算法与数据结构（★★★★★）\n2. 计算机网络（★★★）\n3. 网络安全（★★）\n4. MySQL（★★★）\n5. Redis（★★）\n6. 操作系统（★★★）\n7. Linux（★★★）\n8. 设计模式（★★★）\n9. Socket（★★）\n10. Web服务（★★）\n    - Nginx\n    - Tomcat\n11. 项目考察（★★★）\n12. 简历编写\n\n\n\n## 加分项\n\n1. Unix\n2. 分布式系统\n3. 负载均衡\n4. 高可用\n5. 系统容灾\n6. 微服务\n   - SpringCloud\n   - Dobbo\n   - ZooKeeper\n7. Docker"
  },
  {
    "path": "others/UPLOG.md",
    "content": "# 更新日志\n\n- 2018/08/31\n  - 更新 “Java - Java 基础知识”  v3.0 初稿版\n- 2018/09/01\n  - 更新 “Java - Java 集合框架”  v3.0 初稿版\n- 2018/09/02\n  - 更新 “Java - Java 并发编程”  v3.0 初稿版\n\n"
  },
  {
    "path": "others/blog/20180606 用别名（alias）创建你自己的命令.md",
    "content": "### 一、alias初体验\n\n现在是时候，感受第一次编程经历了！我们将用 alias 命令创建我们自己的命令。但在 开始之前，我们需要展示一个命令行小技巧。可以把多个命令放在同一行上，命令之间 用”;”分开。它像这样工作\n\n```\ncommand1; command2; command3...\n```\n\n我们会用到下面的例子：  \n\n```\n[me@linuxbox ~]$ cd /usr; ls; cd -\nbin  games    kerberos  lib64    local  share  tmp\n...\n[me@linuxbox ~]$\n```\n\n正如我们看到的，我们在一行上联合了三个命令。**首先**更改目录到/usr，**然后**列出目录 内容，**最后**回到原始目录（用命令”cd -“）,结束在开始的地方。\n\n现在，通过 alias 命令 把这一串命令转变为一个命令。\n\n我们要做的第一件事就是为我们的新命令构想一个名字。\n\n比方说”test”。在使用”test”之前，查明是否”test”命令名已经存在系统中，是个很不错 的主意。\n\n为了查清此事，可以使用 type 命令： \n\n```\n[me@linuxbox ~]$ type test\ntest is a shell builtin\n```\n\n哦！”test”名字已经被使用了。试一下”foo”:  \n\n```\n[me@linuxbox ~]$ type foo\nbash: type: foo: not found\n```\n\n太棒了！”foo”还没被占用。创建命令别名：\n\n```\n[me@linuxbox ~]$ alias foo='cd /usr; ls; cd -'\n```\n\n注意命令结构：\n\n```\nalias name='string'\n```\n\n在命令”alias”之后，输入“name”，紧接着（没有空格）是一个等号，等号之后是 一串用引号引起的字符串，字符串的内容要赋值给 name。我们定义了别名之后， 这个命令别名可以使用在任何地方。试一下：\n\n```\n[me@linuxbox ~]$ foo\nbin   games   kerberos  lib64    local   share  tmp\n...\n[me@linuxbox ~]$\n```\n\n我们也可以使用 type 命令来查看我们的别名：\n\n```\n[me@linuxbox ~]$ type foo\nfoo is aliased to `cd /usr; ls ; cd -'\n```\n\n删除别名，使用 unalias 命令，像这样：\n\n```\n[me@linuxbox ~]$ unalias foo\n[me@linuxbox ~]$ type foo\nbash: type: foo: not found\n```\n\n虽然我们有意避免使用已经存在的命令名来命名我们的别名，但这是常做的事情。通常， 会把一个普遍用到的选项加到一个经常使用的命令后面。例如，之前见到的 ls 命令，会 带有色彩支持：\n\n```\n[me@linuxbox ~]$ type ls\nls is aliased to 'ls --color=tty'\n```\n\n要查看所有定义在系统环境中的别名，使用不带参数的 alias 命令。下面在 Fedora 系统中 默认定义的别名。试着弄明白，它们是做什么的：\n\n```\n[me@linuxbox ~]$ alias\nalias l.='ls -d .* --color=tty'\n...\n```\n\n在命令行中定义别名有点儿小问题。当你的 shell 会话结束时，它们会消失。\n\n\n\n### 二、接如何设置永久有效的alias命令\n\n\n\n#### 1.打开.bashrc文件\n\n当系统重启之后就会失效，所以要实现永久有效，则需要 修改用户目录下的一个文件 .bashrc  目录为 ~/.bashrc  \n\n```\nvim ~/.bashrc\n```\n\n\n\n#### 2.自定义命令行\n\nalias cls=’clear’这行，并且加一个注释` # User specific aliases and functions `方便我们日后的查阅\n修改后如下：\n\n```\n#.bashrc\n# User specific aliases and functions\nalias cls='clear'\n\n\n# Source global definitions\nif [ -f /etc/bashrc ]; then\n        . /etc/bashrc\nfi\n\n# Uncomment the following line if you don't like systemctl's auto-paging feature:\n# export SYSTEMD_PAGER=\n# User specific aliases and functions12345678910111213\n```\n\n\n\n#### 3.保存退出\n\n```\n:wq\n```\n\n\n\n#### 4.使用命令生效更改\n\n```\nsource ~/.bashrc\n```\n\n\n\n#### 5.验证\n\n- 重启后尝试\n- 关闭ssh重新连接\n- 输入alias可以看到所有的别名\n\n"
  },
  {
    "path": "others/blog/20180609 Spring Boot热部署.md",
    "content": ""
  },
  {
    "path": "others/blog/20180620 JWT API安全.md",
    "content": "\n\n\n\n\n\n\n\nSpring Boot用3个class轻松实现JWT, 保护你的RESTful API_搜狐科技_搜狐网\nhttps://www.sohu.com/a/163656038_714863\n\n\n\nspring-boot-jwt-demo/README.md at 8339e62fcde01b9746ed28ca8cff7b6417ecca9e · tiantianmingyuan/spring-boot-jwt-demo\nhttps://github.com/tiantianmingyuan/spring-boot-jwt-demo/blob/8339e62fcde01b9746ed28ca8cff7b6417ecca9e/basic/README.md\n\n\n\n\n\nspringboot-workspace/README.md at 4bb11ef228e28bd275bba03495018967c6521c09 · zhangjinmiao/springboot-workspace\nhttps://github.com/zhangjinmiao/springboot-workspace/blob/4bb11ef228e28bd275bba03495018967c6521c09/jwt/spring-boot-jwt-demo/complete/README.md\n\n\n\n\n\n\n\n重拾后端之Spring Boot（四）：使用JWT和Spring Security保护REST... - 简书\nhttps://www.jianshu.com/p/6307c89fe3fa\n\n\n\n"
  },
  {
    "path": "others/blog/20180621 20小时学会任何技能.md",
    "content": "The first 20 hours -- how to learn anything | Josh Kaufman | TEDxCSU - YouTube\nhttps://www.youtube.com/watch?v=5MgBikgcWnY\n\n\n\n关键20小时快速学习任何技能 - 搜索结果 - 哔哩哔哩弹幕视频网 - ( ゜- ゜)つロ 乾杯~ - bilibili\nhttps://search.bilibili.com/all?keyword=%E5%85%B3%E9%94%AE20%E5%B0%8F%E6%97%B6%E5%BF%AB%E9%80%9F%E5%AD%A6%E4%B9%A0%E4%BB%BB%E4%BD%95%E6%8A%80%E8%83%BD&from_source=banner_search"
  },
  {
    "path": "others/blog/20180701 深入理解快速排序.md",
    "content": "\n\n快速排序可以说是20世纪最伟大的算法之一了。相信都有所耳闻，它的速度也正如它的名字那样，是一个非常快的算法了。当然它也后期经过了不断的改进和优化，才被公认为是一个值得信任的非常优秀的算法。\n\n本文将结合快速排序的三方面进行比较和深入解析。\n\n\n\n## 快速排序\n\n![](pics/quicksort1.jpg)\n\n```java\npublic class QuickSort {\n    // 递归使用快速排序,对arr[l...r]的范围进行排序\n    public static void QuickSort(int[] arr,int l,int r){\n        if(l>=r)\n            return;\n        int p = partition(arr,l,r);\n        QuickSort(arr,l,p-1);\n        QuickSort(arr,p+1,r);\n    }\n\n    // 将数组通过p分割成两部分\n    // 对arr[l...r]部分进行partition操作\n    // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]\n    public static int partition(int[] arr, int l, int r) {\n        swap(arr, l, (int) (Math.random() * (r - l + 1)) + l); // 随机快速排序\n\n        int v = arr[l];\n        int j = l;\n        for(int i = j +1;i<=r;i++){\n            if(arr[i] < v){\n                j++;\n                swap(arr,i,j);\n            }\n        }\n        swap(arr,l,j);\n        return j;\n    }\n\n    public static void swap(int[] arr,int i,int j) {\n        int temp = arr[i];\n        arr[i] = arr[j];\n        arr[j] = temp;\n    }\n\n    // 打印arr数组的所有内容\n    public static void printArray(int[] arr) {\n\n        for (int i = 0; i < arr.length; i++){\n            System.out.print( arr[i] );\n            System.out.print( ' ' );\n        }\n        System.out.println();\n\n        return;\n    }\n\n    public static void main(String[] args){\n        int[] arr = {4,3,12,12};\n        QuickSort(arr,0,arr.length-1);\n        printArray(arr);\n    }\n}\n```\n\n\n\n## 双路快速排序\n\n\n\n若果数组中含有大量重复的元素，则partition很可能把数组划分成两个及其不平衡的两部分，时间复杂度退化成O(n²)。这时候应该把小于v和大于v放在数组两端\n\n![](pics/quicksort2.jpg)\n\n\n\n实际上把等于的部分分散到了数组两端\n\n![](pics/quicksort2-2.jpg)\n\n\n\n\n\n```java\n\npublic class QuickSort2Ways {\n    \n    // 双路快速排序的partition\n    // 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p]\n    private static int partition(int[] arr, int l, int r) {\n\n        // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot\n        swap(arr, l, (int) (Math.random() * (r - l + 1)) + l);\n\n        int v = arr[l];\n\n        // arr[l+1...i) <= v; arr(j...r] >= v\n        int i = l + 1, j = r;\n        while (true) {\n            // 注意这里的边界, arr[i] < 0, 不能是arr[i] <= v\n            // 思考一下为什么?\n            while (i <= r && arr[i] < v)\n                i++;\n\n            // 注意这里的边界, arr[j] > v, 不能是arr[j] >= v\n            // 思考一下为什么?\n            while (j >= l + 1 && arr[j] > v)\n                j--;\n\n            // 对于上面的两个边界的设定, 有的同学在课程的问答区有很好的回答:)\n            // 大家可以参考: http://coding.imooc.com/learn/questiondetail/4920.html\n            // 答案：多了个等号的判断也会造成两棵子树不平衡\n\n            if (i > j)\n                break;\n\n            swap(arr, i, j);\n            i++;\n            j--;\n        }\n\n        swap(arr, l, j);\n        return j;\n    }\n\n    // 递归使用快速排序,对arr[l...r]的范围进行排序\n    private static void QuickSort2Ways(int[] arr, int l, int r) {\n\n        // 对于小规模数组, 使用插入排序\n        // if( r - l <= 15 ){\n        //    InsertionSort.sort(arr, l, r);\n        //    return;\n        // }\n\n        int p = partition(arr, l, r);\n        QuickSort(arr, l, p - 1);\n        QuickSort(arr, p + 1, r);\n    }\n\n\n    private static void swap(int[] arr, int i, int j) {\n        int t = arr[i];\n        arr[i] = arr[j];\n        arr[j] = t;\n    }\n\n    // 打印arr数组的所有内容\n    public static void printArray(int[] arr) {\n\n        for (int i = 0; i < arr.length; i++) {\n            System.out.print(arr[i]);\n            System.out.print(' ');\n        }\n        System.out.println();\n        return;\n    }\n\n    // 测试 QuickSort\n    public static void main(String[] args) {\n\n        //双路快速排序算法也是一个O(nlogn)复杂度的算法\n        // 可以在1秒之内轻松处理100万数量级的数据\n        int[] arr = {4, 3, 12, 12};\n        QuickSort2Ways(arr, 0, arr.length - 1);\n        printArray(arr);\n    }\n}\n```\n\n\n\n\n\n## 三路快速排序\n\n\n\n数组分成三个部分，大于v 等于v 小于v\n\n![](pics/quicksort3.jpg)\n\n![](pics/quicksort3-2.jpg)\n\n```java\n\npublic class QuickSort3Ways {\n\n    // 递归使用快速排序,对arr[l...r]的范围进行排序\n    private static void QuickSort3Ways(int[] arr, int l, int r){\n\n        // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot\n        swap( arr, l, (int)(Math.random()*(r-l+1)) + l );\n\n        int v = arr[l];\n\n        int lt = l;     // arr[l+1...lt] < v\n        int gt = r + 1; // arr[gt...r] > v\n        int i = l+1;    // arr[lt+1...i) == v\n        while( i < gt ){\n            if( arr[i] < v){\n                swap( arr, i, lt+1);\n                i ++;\n                lt ++;\n            }\n            else if( arr[i] > v ){\n                swap( arr, i, gt-1);\n                gt --;\n            }\n            else{ // arr[i] == v\n                i ++;\n            }\n        }\n\n        swap( arr, l, lt );\n\n        QuickSort3Ways(arr, l, lt-1);\n        QuickSort3Ways(arr, gt, r);\n    }\n\n\n    private static void swap(int[] arr, int i, int j) {\n        int t = arr[i];\n        arr[i] = arr[j];\n        arr[j] = t;\n    }\n\n    // 打印arr数组的所有内容\n    public static void printArray(int[] arr) {\n        for (int i = 0; i < arr.length; i++) {\n            System.out.print(arr[i]);\n            System.out.print(' ');\n        }\n        System.out.println();\n        return;\n    }\n\n    // 测试 QuickSort\n    public static void main(String[] args) {\n        // 三路快速排序算法也是一个O(nlogn)复杂度的算法\n        // 可以在1秒之内轻松处理100万数量级的数据\n        int[] arr = {4, 3, 12, 12};\n        QuickSort3Ways(arr, 0, arr.length - 1);\n        printArray(arr);\n    }\n}\n```\n\n"
  },
  {
    "path": "others/book/Java程序员面试笔试宝典.md",
    "content": "## Java程序员面试笔试宝典——学习笔记\n\n\n\n\n\n4.4 基本类型与运算\n\n4.4.1 Java提供了那些基本数据类型\n\n| 目录                                                         | 记录 |\n| ------------------------------------------------------------ | ---- |\n| **上篇面试笔试经验技巧篇**                                   |      |\n| **第1章  面试官箴言**                                        |      |\n| 1.1  有道无术，术可求；有术无道，止于术                      |      |\n| 1.2  求精不求全                                              |      |\n| 1.3  脚踏实地，培养多种技能                                  |      |\n| 1.4  保持空杯心态                                            |      |\n| 1.5  职场是能者的舞台                                        |      |\n| 1.6  学会“纸上谈兵”                                          |      |\n| 1.7  小结                                                    |      |\n|                                                              |      |\n| **第2章  面试心得交流**                                      |      |\n| 2.1  心态决定一切                                            |      |\n| 2.2  假话全不说，真话不全说                                  |      |\n| 2.3  走自己的路，让别人去说吧                                |      |\n| 2.4  夯实基础谋出路                                          |      |\n| 2.5  书中自有编程法                                          |      |\n| 2.6  笔试成绩好，不会被鄙视                                  |      |\n| 2.7  不要一厢情愿做公司的 “备胎”                             |      |\n| 2.8小结                                                      |      |\n|                                                              |      |\n| **第3章企业面试笔试攻略**                                    |      |\n| 3.1  互联网企业                                              |      |\n| 3.2  网络设备提供商                                          |      |\n| 3.3  外企                                                    |      |\n| 3.4  国企                                                    |      |\n| 3.5  研究所                                                  |      |\n| 3.6  创业型企业                                              |      |\n| 3.7  如何抉择                                                |      |\n|                                                              |      |\n| **下篇面试笔试技术攻克篇**                                   |      |\n| **第4章Java基础知识**                                        |      |\n|                                                              |      |\n| **4.1  基本概念**                                            |      |\n| 4.1.1  Java语言有哪些优点                                    |      |\n| 4.1.2  Java与C/C++有什么异同                                 |      |\n| 4.1.3  为什么需要publicstaticvoidmain（String［］args）这个方法 |      |\n| 4.1.4  如何实现在main（）方法执行前输出“HelloWorld”          |      |\n| 4.1.5  Java程序初始化的顺序是怎样的                          |      |\n| 4.1.6  Java中的作用域有哪些                                  |      |\n| 4.1.7  一个Java文件中是否可以定义多个类                      |      |\n| 4.1.8  什么是构造函数                                        |      |\n| 4.1.9  为什么Java中有些接口没有任何方法                      |      |\n| 4.1.10  Java中的clone方法有什么作用                          |      |\n| 4.1.11  什么是反射机制                                       |      |\n| 4.1.12  package有什么作用                                    |      |\n| 4.1.13  如何实现类似于C语言中函数指针的功能                  |      |\n|                                                              |      |\n| **4.2面向对象技术**                                          |      |\n| 4.2.1  面向对象与面向过程有什么区别                          |      |\n| 4.2.2  面向对象有哪些特征                                    |      |\n| 4.2.3  面向对象的开发方式有什么优点                          |      |\n| 4.2.4  什么是继承                                            |      |\n| 4.2.5  组合和继承有什么区别                                  |      |\n| 4.2.6  多态的实现机制是什么                                  |      |\n| 4.2.7  重载和覆盖有什么区别                                  |      |\n| 4.2.8  抽象类（abstractclass）与接口（interface）有什么异同  |      |\n| 4.2.9  内部类有哪些                                          |      |\n| 4.2.10  如何获取父类的类名                                   |      |\n| 4.2.11  this与super有什么区别                                |      |\n|                                                              |      |\n| **4.3关键字**                                                |      |\n| 4.3.1变量命名有哪些规则                                      |      |\n| 4.3.2break、continue以及return有什么区别                     |      |\n| 4.3.3final、finally和finalize有什么区别                      |      |\n| 4.3.4assert有什么作用                                        |      |\n| 4.3.5static关键字有哪些作用                                  |      |\n| 4.3.6使用switch时有哪些注意事项                              |      |\n| 4.3.7volatile有什么作用                                      |      |\n| 4.3.8instanceof有什么作用                                    |      |\n| 4.3.9strictfp有什么作用                                      |      |\n|                                                              |      |\n| **4.4基本类型与运算**                                        |      |\n| 4.4.1Java提供了哪些基本数据类型                              |      |\n| 4.4.2什么是不可变类                                          |      |\n| 4.4.3值传递与引用传递有哪些区别                              |      |\n| 4.4.4不同数据类型的转换有哪些规则                            |      |\n| 4.4.5强制类型转换的注意事项有哪些                            |      |\n| 4.4.6运算符优先级是什么？                                    |      |\n| 4.4.7Math类中round、ceil和floor方法的功能各是什么            |      |\n| 4.4.8++i与i++有什么区别                                      |      |\n| 4.4.9如何实现无符号数的右移操作                              |      |\n| 4.4.10char型变量中是否可以存储一个中文汉字                   |      |\n|                                                              |      |\n| **4.5字符串与数组**                                          |      |\n| 4.5.1字符串创建与存储的机制是什么                            |      |\n| 4.5.2“==”、equals和hashCode有什么区别                        |      |\n| 4.5.3String、StringBuffer、StringBuilder和                   |      |\n| StringTokenizer有什么区别                                    |      |\n| 4.5.4Java中数组是不是对象                                    |      |\n| 4.5.5数组的初始化方式有哪几种                                |      |\n| 4.5.6length属性与length（）方法有什么区别                    |      |\n| **4.6异常处理**                                              |      |\n| 4.6.1finally块中的代码什么时候被执行                         |      |\n| 4.6.2异常处理的原理是什么                                    |      |\n| 4.6.3运行时异常和普通异常有什么区别                          |      |\n| **4.7输入输出流**                                            |      |\n| 4.7.1JavaIO流的实现机制是什么                                |      |\n| 4.7.2管理文件和目录的类是什么                                |      |\n| 4.7.3JavaSocket是什么                                        |      |\n| 4.7.4JavaNIO是什么                                           |      |\n| 4.7.5什么是Java序列化                                        |      |\n| 4.7.6System.out.println（）方法使用需要注意哪些问题          |      |\n| **4.8Java平台与内存管理**                                    |      |\n| 4.8.1为什么说Java是平台独立性语言                            |      |\n| 4.8.2Java平台与其他语言平台有哪些区别                        |      |\n| 4.8.3JVM加载class文件的原理机制是什么                        |      |\n| 4.8.4什么是GC                                                |      |\n| 4.8.5Java是否存在内存泄露问题                                |      |\n| 4.8.6Java中的堆和栈有什么区别                                |      |\n| **4.9容器**                                                  |      |\n| 4.9.1JavaCollections框架是什么                               |      |\n| 4.9.2什么是迭代器                                            |      |\n| 4.9.3ArrayList、Vector和LinkedList有什么区别                 |      |\n| 4.9.4HashMap、HashTable、TreeMap和WeakHashMap有哪些区别      |      |\n| 4.9.5用自定义作为HashMap或HashTable的key需要注意哪些问题     |      |\n| 4.9.6Collection和Collections有什么区别                       |      |\n| **4.10多线程**                                               |      |\n| 4.10.1什么是线程？它与进程有什么区别？为什么要使用多线程     |      |\n| 4.10.2同步和异步有什么区别                                   |      |\n| 4.10.3如何实现Java多线程                                     |      |\n| 4.10.4run（）方法与start（）方法有什么区别                   |      |\n| 4.10.5多线程同步的实现方法有哪些                             |      |\n| 4.10.6sleep（）方法与wait（）方法有什么区别                  |      |\n| 4.10.7终止线程的方法有哪些                                   |      |\n| 4.10.8synchronized与Lock有什么异同                           |      |\n| 4.10.9什么是守护线程                                         |      |\n| 4.10.10join（）方法的作用是什么                              |      |\n| **4.11Java数据库操作**                                       |      |\n| 4.11.1如何通过JDBC访问数据库                                 |      |\n| 4.11.2JDBC处理事务采用什么方法                               |      |\n| 4.11.3Class.forName的作用是什么                              |      |\n| 4.11.4Statement、PreparedStatement和CallableStatement有什么区别 |      |\n| 4.11.5getString（）方法与getObject（）方法有什么区别         |      |\n| 4.11.6使用JDBC时需要注意哪些问题                             |      |\n| 4.11.7什么是JDO                                              |      |\n| 4.11.8JDBC与Hibernate有什么区别                              |      |\n| **第5章JavaWeb**                                             |      |\n| **5.1Servlet与JSP**                                          |      |\n| 5.1.1页面请求的工作流程是怎样的                              |      |\n| 5.1.2HTTP中GET与POST方法有什么区别                           |      |\n| 5.1.3什么是Servlet                                           |      |\n| 5.1.4doPost（）方法与doGet（）方法怎么选择                   |      |\n| 5.1.5什么是Servlet的生命周期                                 |      |\n| 5.1.6JSP有哪些优点                                           |      |\n| 5.1.7JSP与Servlet有何异同                                    |      |\n| 5.1.8如何使用JSP与Servlet实现MVC模型                         |      |\n| 5.1.9Servlet中forward和redirect有什么区别                    |      |\n| 5.1.10JSP的内置对象有哪些                                    |      |\n| 5.1.11request对象主要有哪些方法                              |      |\n| 5.1.12JSP有哪些动作                                          |      |\n| 5.1.13JSP中include指令和include动作有什么区别                |      |\n| 5.1.14会话跟踪技术有哪些                                     |      |\n| 5.1.15Web开发中如何指定字符串的编码                          |      |\n| 5.1.16什么是Ajax                                             |      |\n| 5.1.17cookie和session有什么区别                              |      |\n| 5.2J2EE与EJB                                                 |      |\n| 5.2.1什么是J2EE                                              |      |\n| 5.2.2J2EE中常用的术语有哪些                                  |      |\n| 5.2.3EJB有哪些不同的类别                                     |      |\n| 5.2.4EJB与JavaBean有什么异同                                 |      |\n| 5.2.5EJB有哪些生命周期                                       |      |\n| 5.2.6EJB的角色有哪几种                                       |      |\n| 5.2.7EJB的开发流程是怎样的                                   |      |\n| 5.2.8EJB3.0与EJB2.0有哪些不同之处                            |      |\n| 5.2.9EJB容器有哪些作用                                       |      |\n| 5.2.10EJB规范规定EJB中禁止的操作有哪些                       |      |\n| 5.2.11Web服务器与Web应用服务器有什么区别                     |      |\n| 5.2.12什么是WebService                                       |      |\n| 5.2.13SOAP与REST有什么区别                                   |      |\n| 5.2.14什么是XML                                              |      |\n| 5.2.15数据库连接池的工作机制是怎样的                         |      |\n| 5.2.16J2EE开发有哪些调优的方法                               |      |\n| 5.3框架                                                      |      |\n| 5.3.1什么是Struts框架                                        |      |\n| 5.3.2Struts框架响应客户请求的工作流程是什么                  |      |\n| 5.3.3Struts框架的数据验证可分为几种类型                      |      |\n| 5.3.4FormBean的表单验证流程是什么                            |      |\n| 5.3.5在Struts配置文件中元素包含哪些属性和子元                |      |\n| 5.3.6ActionFormBean的作用有哪些                              |      |\n| 5.3.7ActionForm的执行步骤有哪些                              |      |\n| 5.3.8forward与global-forward有什么区别                       |      |\n| 5.3.9Struts如何实现国际化                                    |      |\n| 5.3.10Struts1与Struts2有哪些区别                             |      |\n| 5.3.11什么是IoC                                              |      |\n| 5.3.12什么是AOP                                              |      |\n| 5.3.13什么是Spring框架                                       |      |\n| 5.3.14什么是Hibernate                                        |      |\n| 5.3.15什么是Hibernate的二级缓存                              |      |\n| 5.3.16Hibernate中session的update（）和saveOrUpdate（）、load（）和get（）有什么区别 |      |\n| 5.3.17Hibernate有哪些主键生成策略                            |      |\n| 5.3.18如何实现分页机制                                       |      |\n| 5.3.19什么是SSH                                              |      |\n| 第6章数据库原理                                              |      |\n| 6.1SQL语言的功能有哪些                                       |      |\n| 6.2内连接与外连接有什么区别                                  |      |\n| 6.3什么是事务                                                |      |\n| 6.4什么是存储过程？它与函数有什么区别与联系                  |      |\n| 6.5各种范式有什么区别                                        |      |\n| 6.6什么是触发器                                              |      |\n| 6.7什么是游标                                                |      |\n| 6.8如果数据库日志满了，会出现什么情况                        |      |\n| 6.9union和unionall有什么区别                                 |      |\n| 6.10什么是视图                                               |      |\n| 第7章设计模式                                                |      |\n| 7.1什么是单例模式                                            |      |\n| 7.2什么是工厂模式                                            |      |\n| 7.3什么是适配器模式                                          |      |\n| 7.4什么是观察者模式                                          |      |\n| 第8章数据结构与算法                                          |      |\n| 8.1链表                                                      |      |\n| 8.1.1如何实现单链表的增删操作                                |      |\n| 8.1.2如何从链表中删除重复数据                                |      |\n| 8.1.3如何找出单链表中的倒数第k个元素                         |      |\n| 8.1.4如何实现链表的反转                                      |      |\n| 8.1.5如何从尾到头输出单链表                                  |      |\n| 8.1.6如何寻找单链表的中间结点                                |      |\n| 8.1.7如何检测一个链表是否有环                                |      |\n| 8.1.8如何在不知道头指针的情况下删除指定结点                  |      |\n| 8.1.9如何判断两个链表是否相交                                |      |\n| 8.2栈与队列                                                  |      |\n| 8.2.1栈与队列有哪些区别                                      |      |\n| 8.2.2如何实现栈                                              |      |\n| 8.2.3如何用O（1）的时间复杂度求栈中最小元素                  |      |\n| 8.2.4如何实现队列                                            |      |\n| 8.2.5如何用两个栈模拟队列操作                                |      |\n| 8.3排序                                                      |      |\n| 8.3.1如何进行选择排序                                        |      |\n| 8.3.2如何进行插入排序                                        |      |\n| 8.3.3如何进行冒泡排序                                        |      |\n| 8.3.4如何进行归并排序                                        |      |\n| 8.3.5如何进行快速排序                                        |      |\n| 8.3.6如何进行希尔排序                                        |      |\n| 8.3.7如何进行堆排序                                          |      |\n| 8.3.8各种排序算法有什么优劣                                  |      |\n| 8.4位运算                                                    |      |\n| 8.4.1如何用移位操作实现乘法运算                              |      |\n| 8.4.2如何判断一个数是否为2的n次方                            |      |\n| 8.4.3如何求二进制数中1的个数                                 |      |\n| 8.5数组                                                      |      |\n| 8.5.1如何寻找数组中的最小值与最大值                          |      |\n| 8.5.2如何找出数组中第二大的数                                |      |\n| 8.5.3如何求最大子数组之和                                    |      |\n| 8.5.4如何找出数组中重复元素最多的数                          |      |\n| 8.5.5如何求数组中两两相加等于20的组合种数                    |      |\n| 8.5.6如何把一个数组循环右移k位                               |      |\n| 8.5.7如何找出数组中第k个最小的数                             |      |\n| 8.5.8如何找出数组中只出现次的数字                            |      |\n| 8.5.9如何找出数组中唯一的重复元素                            |      |\n| 8.5.10如何用递归方法求一个整数数组的最大元素                 |      |\n| 8.5.11如何求数对之差的最大值                                 |      |\n| 8.5.12如何求绝对值最小的数                                   |      |\n| 8.5.13如何求数组中两个元素的最小距离                         |      |\n| 8.5.14如何求指定数字在数组中第一次出现的位置                 |      |\n| 8.5.15如何对数组的两个子有序段进行合并                       |      |\n| 8.5.16如何计算两个有序整型数组的交集                         |      |\n| 8.5.17如何判断一个数组中数值是否连续相邻                     |      |\n| 8.5.18如何求解数组中反序对的个数                             |      |\n| 8.5.19如何求解最小三元组距离                                 |      |\n| 8.6字符串                                                    |      |\n| 8.6.1如何实现字符串的反转                                    |      |\n| 8.6.2如何判断两个字符串是否由相同的字符组成                  |      |\n| 8.6.3如何删除字符串中重复的字符                              |      |\n| 8.6.4如何统计一行字符中有多少个单词                          |      |\n| 8.6.5如何按要求打印数组的排列情况                            |      |\n| 8.6.6如何输出字符串的所有组合                                |      |\n| 8.7二叉树                                                    |      |\n| 8.7.1二叉树基本概念                                          |      |\n| 8.7.2如何实现二叉排序树                                      |      |\n| 8.7.3如何层序遍历二叉树                                      |      |\n| 8.7.4已知先序遍历和中序遍历，如何求后序遍历                  |      |\n| 8.7.5如何求二叉树中结点的最大距离                            |      |\n| 8.8其他                                                      |      |\n| 8.8.1如何消除嵌套的括号                                      |      |\n| 8.8.2如何不使用比较运算就可以求出两个数的最大值与最小值      |      |\n|                                                              |      |\n| **第9章海量数据处理**                                        |      |\n| 9.1问题分析                                                  |      |\n| 9.2基本方法                                                  |      |\n| 9.3经典实例分析                                              |      |\n| 9.3.1topK问题                                                |      |\n| 9.3.2重复问题                                                |      |\n| 9.3.3排序问题                                                |      |\n|                                                              |      |\n|                                                              |      |\n|                                                              |      |\n|                                                              |      |\n|                                                              |      |\n|                                                              |      |"
  },
  {
    "path": "others/devdoc/IntelliJ IDEA/debug技巧.md",
    "content": "Intellij IDEA 2017 debug断点调试技巧与总结详解篇 - CSDN博客\nhttps://blog.csdn.net/qq_27093465/article/details/64124330\n\n\n\n\n\n\n\n"
  },
  {
    "path": "others/devdoc/IntelliJ IDEA/lombok插件使用.md",
    "content": "## lombok插件使用\n\n1.加载pom.xml中的依赖\n\n```xml\n<!-- 日志、@Data注解 -->\n<dependency>\n    <groupId>org.projectlombok</groupId>\n    <artifactId>lombok</artifactId>\n</dependency>\n```\n\n\n\n2.`CTRL+ALT+S`打开设置，`Plugins`中search`lombok`安装\n\n\n\n3.restart\n\n\n\n参考：廖师兄课程`4-2买家类目-dao（下） 02:57`视频"
  },
  {
    "path": "others/devdoc/Java/HTTPS配置.md",
    "content": "网站支持https之二：腾讯云上免费获取SSL证书的步骤 - CSDN博客\nhttps://blog.csdn.net/guoxiaojie_415/article/details/80031909\n\n\n\n```\nserver {\n    listen 443\n    server_name api.chengchijinfu.com\n    charset utf-8;\n    ssl on; \n    ssl_certificate /home/key_dir/1_api.chengchijinfu.com_bundle.crt;  \n    ssl_certificate_key /home/key_dir/2_api.chengchijinfu.com.key; \n}\n```\n\n"
  },
  {
    "path": "others/devdoc/Java/IDEA使用操作.md",
    "content": "idea快速生成实体类Entity（找了半天，自己一步就搞出来了） - CSDN博客\nhttps://blog.csdn.net/hgg923/article/details/53439038\n\n\n\n\n\n"
  },
  {
    "path": "others/devdoc/Java/Java环境搭建.md",
    "content": "by Frank, 2018/5/24\n\n## Java Web 环境搭建\n\n### 1.下载JDK（Java8为例）\n\n- 下载地址：\n\n\t\t[Java SE Development Kit 8 - Downloads](http://www.oracle.com/technetwork/java/javase/downloads/j\tdk8-downloads-2133151.html)\n\n\t\t[jdk-8u171-windows-x64.exe 一键下载地址](http://download.oracle.com/otn-pub/java/jdk/8u171-b11/512cd62ec5174c3487ac17c61aaa89e8/jdk-8u171-windows-x64.exe?AuthParam=1527129569_16da17421367f457979a306ac9a85b08)\n\n- 安装步骤参考如下：\n\n\t\t如何安装Java8？如何安装JDK8与配置环境变量_百度经验\n\thttps://jingyan.baidu.com/article/7c6fb4282f1f6580642c90e1.html\n\n- 配置环境变量\n\n  `JAVA_HOME`：`C:\\Program Files\\Java\\jdk1.8.0_171`\n\n  `Path`：`%JAVA_HOME%\\bin; `\n\n- 验证\n\n  ```shell\n  C:\\Users\\Frank>where java\n  C:\\Program Files\\Java\\jdk1.8.0_171\\bin\\java.exe\n  ```\n\n### 2.Maven安装\n\n- 官网下载：[Maven – Download Apache Maven](https://maven.apache.org/download.cgi)\n\n- 配置环境变量\n\n  `M2_HOME `：`D:\\Program Files\\apache-maven-3.5.3`\n\n  `Path `：`%M2_HOME%\\bin; `\n\n- 验证（win+R 打开运行面板，然后输入CMD，打开命令提示符对话框，然后输入`mvn -version`）\n\n  ```shell\n  C:\\Users\\Frank>mvn -version\n  Apache Maven 3.5.3 (3383c37e1f9e9b3bc3df5050c29c8aff9f295297; 2018-02-25T03:49:05+08:00)\n  Maven home: D:\\Program Files\\apache-maven-3.5.3\\bin\\..\n  Java version: 1.8.0_171, vendor: Oracle Corporation\n  Java home: C:\\Program Files\\Java\\jdk1.8.0_171\\jre\n  Default locale: zh_CN, platform encoding: GBK\n  OS name: \"windows 10\", version: \"10.0\", arch: \"amd64\", family: \"windows\"\n  ```\n\n  出现如上版本号，则说明maven配置成功\n\n- **参考资料**\n\n  - [windows10如何安装Maven_百度经验](https://jingyan.baidu.com/article/046a7b3e80bc06f9c27fa9bb.html)\n  - [如何使用IntelliJ IDEA 配置Maven - CSDN博客](https://blog.csdn.net/westos_linux/article/details/78968012)\n\n  \n\n### 3.IntelliJ IDEA 中配置Tomcat\n\n\n\n\n\n\n\n### 部署线上环境\n\n（1）使用cmd命令行，就进入项目根目录。mvn执行：`mvn clean install package -Dmaven.test.skip=true`，生成`mango.jar`文件\n\n（2）上传`mango.jar`文件至目录`/home/www/api.chengchijinfu.com`\n\n（3）已定义service，直接执行`service mango start`（启动），`service mango stop`（停止）\n\n注意：默认端口号为8080，通过nginx反向代理，api.chengchijinfu.com域名指向8080端口\n\n\n\n\n\n\n\nJenkins \n\nhttps://blog.csdn.net/l1028386804/article/details/78668879\n\n\n\n在PowerShell窗口下执行maven命令行报错：Unknown lifecycle phase \".test.skip=true\". - CSDN博客\nhttps://blog.csdn.net/wushengjun753/article/details/78973618\n\n\n\n不要使用PowerShell命令行模式，使用 cmd 进入命令行执行：\n\n`mvn clean install package -Dmaven.test.skip=true`\n\n\n\n`java -jar -Dserver.port=8090 mango.jar`\n\n\n\n`nohup java -jar mango.jar > /dev/null 2>&1 &`\n\n\n\n```\n!/bin/sh\nnohup java -jar mango.jar > /dev/null 2>&1 &\n```\n\n\nps -ef | grep mango.jar\n\n\n\nkill -9 2576\n\n\n\n\n\n\n\nCentOS7利用systemctl添加自定义系统服务 - CSDN博客\nhttps://blog.csdn.net/gbenson/article/details/51083817\n\n**service**\n\n`/etc/systemd/system`\n\n```\n[Unit]\nDescription=mango\nAfter=syslog.target network.target\n\n[Service]\nType=simple\n\nExecStart=/usr/bin/java -jar /home/www/api.chengchijinfu.com/mango.jar\nExecStop=/bin/kill -15 $MAINPID\n\nUser=root\nGroup=root\n\n[Install]\nWantedBy=multi-user.target\n\n```\n\n\n\n` systemctl start mango`\n\n` systemctl stop mango`\n\n` systemctl enable mango`\n\n` systemctl disable mango`\n\n\n\n\n\n`service mango start`\n\n`service mango stop`\n\n\n\ncentos7通过yum安装JDK1.8 - CSDN博客\nhttps://blog.csdn.net/a360616218/article/details/76736988\n\n\n\ncentos7通过yum安装JDK1.8 - CSDN博客\nhttps://blog.csdn.net/a360616218/article/details/76736988\n\n\n\n\n\n\n\n\n\n#### 5.Linux上利用nginx域名转发 - CSDN博客\n\nhttps://blog.csdn.net/hzw2312/article/details/51789920\n\n\n\nNginx配置同一个域名http与https两种方式都可访问 - 陌上归人的博客 - 博客园\nhttps://www.cnblogs.com/fjping0606/p/6006552.html\n\n\n\n\n\n```\nuser  www www;\n\nworker_processes auto;\n\nerror_log  /home/wwwlogs/nginx_error.log  crit;\n\npid        /usr/local/nginx/logs/nginx.pid;\n\n#Specifies the value for maximum file descriptors that can be opened by this process.\nworker_rlimit_nofile 51200;\n\nevents\n    {\n        use epoll;\n        worker_connections 51200;\n        multi_accept on;\n    }\n\nhttp\n    {\n        include       mime.types;\n        default_type  application/octet-stream;\n\n        server_names_hash_bucket_size 128;\n        client_header_buffer_size 32k;\n        large_client_header_buffers 4 32k;\n        client_max_body_size 50m;\n\n        sendfile   on;\n        tcp_nopush on;\n\n        keepalive_timeout 60;\n\n        tcp_nodelay on;\n\n        fastcgi_connect_timeout 300;\n        fastcgi_send_timeout 300;\n        fastcgi_read_timeout 300;\n        fastcgi_buffer_size 64k;\n        fastcgi_buffers 4 64k;\n        fastcgi_busy_buffers_size 128k;\n        fastcgi_temp_file_write_size 256k;\n\n        gzip on;\n        gzip_min_length  1k;\n        gzip_buffers     4 16k;\n        gzip_http_version 1.1;\n        gzip_comp_level 2;\n        gzip_types     text/plain application/javascript application/x-javascript text/javascript text/css application/xml application/xml+rss;\n        gzip_vary on;\n        gzip_proxied   expired no-cache no-store private auth;\n        gzip_disable   \"MSIE [1-6]\\.\";\n\n        #limit_conn_zone $binary_remote_addr zone=perip:10m;\n        ##If enable limit_conn_zone,add \"limit_conn perip 10;\" to server section.\n\n        server_tokens off;\n        access_log off;\n\nserver {\n    listen 80;\n    listen 443 ssl;\n    server_name api.chengchijinfu.com;\n    ssl_certificate /home/key_dir/1_api.chengchijinfu.com_bundle.crt;\n    ssl_certificate_key /home/key_dir/2_api.chengchijinfu.com.key;\n    ssl_session_cache shared:SSL:1m;\n    ssl_session_timeout 5m;\n    ssl_ciphers HIGH:!aNULL:!MD5;\n    ssl_prefer_server_ciphers on;\n    location / {\n    \tproxy_pass http://127.0.0.1:8080/;\n    }\n }\ninclude vhost/*.conf;\n}\n\n\n```\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "others/devdoc/Java/Spring Boot热部署.md",
    "content": "\n\n\n\nSpring Boot 系列（六）web开发-Spring Boot 热部署 - 神奇Sam - 博客园\nhttps://www.cnblogs.com/magicalSam/p/7196355.html\n\n\n\n基于Maven的SpringBoot项目实现热部署的两种方式 - CSDN博客\nhttps://blog.csdn.net/tengxing007/article/details/72675168\n\n\n\nSpringBoot 热加载以及添加debug调试 - CSDN博客\nhttps://blog.csdn.net/wbwal159/article/details/78678597\n\n\n\n\n\n```\n<plugin>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-maven-plugin</artifactId>\n    <configuration>\n    <fork>true</fork>\n    <jvmArguments>\n    -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005\n    </jvmArguments>\n    </configuration>\n    <dependencies>\n    <dependency>\n    <groupId>org.springframework</groupId>\n    <artifactId>springloaded</artifactId>\n    <version>1.2.6.RELEASE</version>\n    </dependency>\n    </dependencies>\n</plugin>\n```\n\n\n\n\n\n<plugin>\n    <groupId>org.springframework.boot</groupId>\n    <artifactId>spring-boot-maven-plugin</artifactId>\n</plugin>\n\n\n\n    <plugin>\n        <groupId>org.springframework.boot</groupId>\n        <artifactId>spring-boot-maven-plugin</artifactId>\n        <dependencies>\n        <dependency>\n        <groupId>org.springframework</groupId>\n        <artifactId>springloaded</artifactId>\n        <version>1.2.5.RELEASE</version>\n        </dependency>\n        </dependencies>\n    </plugin>\n\n\n"
  },
  {
    "path": "others/devdoc/MySQL/mysql设置远程连接.md",
    "content": "LNMP安装了哪些软件？安装目录在哪？ - LNMP一键安装包\nhttps://lnmp.org/faq/lnmp-software-list.html\n\n**phpmyadmin安装**\n\n下载：`wget https://files.phpmyadmin.net/phpMyAdmin/4.8.0.1/phpMyAdmin-4.8.0.1-all-languages.zip`\n\n解压：`unzip phpMyAdmin-4.8.0.1-all-languages.zip ` \n\n\n\n##### MySQL错误2003：Can’t connect to MySQL server (10060) 无法连接到远程服务器 解决方案\n\nmysql server 端的端口被防火墙挡出，没有开放  \n\n\n\n\n\n**一、修改/etc/mysql/my.conf**\n\n找到bind-address = 127.0.0.1这一行 \n\n改为bind-address = 0.0.0.0即可 \n\n\n\n\n\n1、新建用户远程连接mysql数据库\n\ngrant all on *.* to ''@'%' identified by '123456' with grant option; \n\nflush privileges;\n\n允许任何ip地址(%表示允许任何ip地址)的电脑用admin帐户和密码(123456)来访问这个mysql server。 注意admin账户不一定要存在。 \n\n\n\n\n\n2、支持root用户允许远程连接mysql数据库\n\ngrant all privileges on *.* to 'root'@'%' identified by '123456' with grant option; \n\n\n\nGRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456' WITH GRANT OPTION; \n\n\n\nflush privileges; \n\n\n\nnetstat -anp|grep 3306\n\n\n\n**防火墙**\n\nCentOS7使用firewalld打开关闭防火墙与端口 - 莫小安 - 博客园\nhttps://www.cnblogs.com/moxiaoan/p/5683743.html\n\n\n\n**注意：**你是不是设置了阿里云的安全组规则? 规则里没开3306,即使防火墙开了3306端口,外部还是不能访问的.\n\n参考：[mysql3306远程无法telnet|云服务器 ECS - 开发者论坛](https://bbs.aliyun.com/read/166762.html?ordertype=asc&displayMode=1)\n\n"
  },
  {
    "path": "others/devdoc/redis/redis安装.md",
    "content": "centos安装redis\n\n**yum安装**\n\n`yum install redis`\n\n\n\n**启动redis**\n\n启动Redis服务：使用`service redis start`命令启动redis服务端。 \n\n\n\n**打开redis客户端**\n\n使用命令：`redis-cli` 即可打开客户\n\n\n\n\n\n`service redis start`\n\n\n\n\n\nredis设置密码\n\n如何给redis设置密码 - CSDN博客\nhttps://blog.csdn.net/qq_35357001/article/details/56835919\n\n\n\n"
  },
  {
    "path": "others/devdoc/sublime/sublime右键菜单设置.md",
    "content": "将Sublime Text 添加到鼠标右键菜单的教程方法 - Michael_翔 - 博客园\nhttps://www.cnblogs.com/michael-xiang/p/4831970.html\n\n"
  },
  {
    "path": "others/devdoc/tools/01 Navicat 工具破解说明.md",
    "content": "\n\n下载地址：[Navicat Premium 12安装包（附破解工具）](https://download.csdn.net/download/u012104219/10434335)\n\n\n\n**注意：**\n\tPatchNavicat.exe可能报毒，但是安全的破解文件，介意的小伙伴请慎重下载！\n\n**破解方式：**\n\t打开PatchNavicat.exe\n\t浏览，选定3次，\"C:\\Program Files\\PremiumSoft\\Navicat Premium 12\\navicat.exe\"\n\t即可破解成功！\n\nso easy...\n\n请不要下载官网的安装包，否则无法破解成功。\n\n\n\n\n\n"
  },
  {
    "path": "others/devdoc/tools/02 Idea破解.md",
    "content": "Intellij IDEA 最新旗舰版注册激活破解（2018亲测，可用） - ameijiemu - 博客园\nhttps://www.cnblogs.com/ameijiemu/p/9036528.html\n\n\n\n\n\n"
  },
  {
    "path": "others/emoji.md",
    "content": "## Emoji符号大全\n\n> 如有符号显示不正常，请更换浏览器或操作系统浏览\n>\n\n\n| 表情 | emoji                                                        |\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| 数字 | 0️⃣1️⃣2️⃣3️⃣4️⃣5️⃣6️⃣7️⃣8️⃣9️⃣🔟                                                  |"
  },
  {
    "path": "others/interview/InterviewOneDay/20180807 阿里云面经.md",
    "content": "阿里Java研发工程师实习面经，附面试技巧\nhttps://zhuanlan.zhihu.com/p/35486363\n\n\n\n十分幸运 拿到阿里云的offer，感谢周围无数人对我的支持和鼓励，所以写篇面经希望可以帮助大家。\n\n面试中，运气占很大一部分的，所以你们若是没有通过，一定不要气馁，继续加油。\n\n每个努力的人 都值得钦佩，无论结果如何。\n\n**我说点面试中的小技巧，可以帮助你更好发挥：**\n\n注意，这个方法比较适合我，不一定适合你哇！\n\n> 完全可以将学到的知识讲给自己（或者讲给别人），若是自己（别人）听你的讲述 能够听懂（使用自己的话去讲，而不要有专业术语），那么说明 你已经学好了。其原理就是 费曼学习法。感兴趣的人可以去了解下。\n> 我个人习惯于 将一个知识点 分解为 xxx是什么，xxx有什么用，如何实现这个功能的（核心的工作流程），缺点是什么（以及为什么有这个缺点，缺点如何补救）。\n> 举个例子， CMS垃圾回收器回收时为什么有内存碎片 ， 首先分解为CMS是什么，内存碎片是什么？CMS垃圾回收器有什么用和Serial ParNew Parallel 等比较，优势，CMS是如何工作的，来实现尽可能降低响应时间的，为什么CMS有这个缺点，它是如何取舍的，如何补救这个缺点。\n\n这些子问题都回答好，那么基本上这个问题就学的可以了。\n\n这样可以检测你是否学好，而且，若是你都没法条理清晰地讲给自己，那怎么条理清晰地讲给面试官呢？\n\n**接下来就写下三次面试题目：**\n\n\n\n**阿里一面：**\n\n1.个人介绍\n\n2.项目介绍，项目介绍首先讲最好的项目，因为后面的项目可能都没时间去讲。\n\n3.数据库 联合索引 用法\n\n4.Spring IOC初始化过程\n\n5.ConcurrentHashMap实现原理\n\n6.CAS操作\n\n7.ReentrantLock和Synchronized区别\n\n8.CMS垃圾回收过程\n\n9.Full GC次数太多了，如何优化。\n\n10.直接内存 如何 管理的\n\n11.线程池 参数。\n\n线程创建的过程。有几种方法。\n\n线程资源如何回收的。\n\n一个题目：如何将一个二叉树，转为有序的双向链表。\n\n\n\n**阿里二面**\n\n1.自我介绍\n\n2.项目介绍\n\n3.堆和栈介绍\n\n4.线程安全\n\n5.乐观锁悲观锁\n\n6.TCP三次握手\n\n7.socket通信有关，select epoll\n\n8.项目中的难点有哪些\n\n我答得是：使用一个组合的设计模式 去解决文章多级分类的问题。\n\n\n\n**交叉面**\n\n1.自我介绍\n\n2.项目介绍\n\n3.做这个项目的动机\n\n4.TCP UDP IP ICMP\n\n5.知不知道一个应用层协议，运输层既没有使用TCP，也没有使用UDP\n\n6.二叉树中求最长路径。\n\n递归的方式去实现。\n\n先求左子树深度，后求右子树深度。相加减一，那么就是以当前结点为转折点的解。\n\n然后递归求左子树的解，和右子树的解。返回当前解，左子树解，右子树解中最大的解。时间复杂度为O(n*logn)\n\n其实可以优化到O(n) ，其实不必求两个子树的解，只需要求较深子树的解即可。\n\n7.有什么想问我的吗？\n\n//您觉得成为一个顶尖高手，最重要的是什么呢？\n\n兴趣。做自己感兴趣的事，就不会很疲惫，也会很开心。 //这一点我深有同感。\n\n抓住你的兴趣，做你想做的事，自己驱动自己进步。\n\n//很感谢您的建议。\n\n\n\n**HR面试**\n\n1.自我介绍。\n\n2.项目介绍。项目中难点。得过啥奖没。\n\n3.项目有什么不足？\n\n4.前面的面试 发挥怎么样？\n\n5.前面的面试难度怎样？\n\n愿大家都能拿到自己理想的offer！"
  },
  {
    "path": "others/interview/InterviewOneDay/20180810 招银网络科技.md",
    "content": "招银网络科技面试总结 - CSDN博客\nhttps://blog.csdn.net/liu_005/article/details/62447502"
  },
  {
    "path": "others/interview/InterviewOneDay/20180811 蘑菇街.md",
    "content": "【蘑菇街】Java后台一面_笔经面经_牛客网\nhttps://www.nowcoder.com/discuss/33569\n\n \n\n1分钟前刚结束蘑菇街的一面，面试官人很好。 分享一波面经，希望能攒点人品。 \n\n  1、项目（问了好多东西，为什么不优化这个那个，为什么不考虑这个那个）...爆虐。 \n\n  2、怎么实现搜索功能？ \n\n  3、like的% \n\n  4、mysql如何查看sql性能 \n\n  5、数学题：每年往银行卡里存2W，利润是5%，存20年，收益是多少。 \n\n  6、给你一串字符串，以逗号分隔，比如“hello,wolrd,haha,yoyo,check,now”，说出 几种 获取最后一个now的方法 \n\n  7、一个200*200的数组，每行都按照从大到小排序，但是行与行之前没有任何关系，请问如何获得前200个最大的数字 \n\n  8、对git，svn之外的版本管理器还有了解过吗 \n\n  9、有两家公司，一家有1w个黑名单，一家有800W个黑名单，如何过滤 \n\n  10、Java线程池如何实现（答原理，我答的不好，便有了下面一个题） \n\n  11、线程池里有5个线程，最大数是10，队列长度是1000，进来8个线程如何分配？（好像是这样，我也没太听清） \n\n  12、线程间如何通信 \n\n  13、线程间如何共享数据 \n\n  14、volatile关键字 \n\n  17、最近在干啥 \n\n  18、为什么想来杭州 \n\n  19、对网易、阿里的看法 \n\n  20、对新的技术有什么了解 \n\n  21、问我一个问题（我说点评一下我吧。 答曰，基础还行，项目需要努力） \n\n面试官人真的很好，回答的不好的他也不会像某些面试官一样用鄙夷的语气回答我，而是笑嘻嘻的说回去再自己看看吧（咦？是GG的意思吗？哎，我心情好起伏.....） \n\n  Whatever，继续努力吧，Fight！ "
  },
  {
    "path": "others/interview/InterviewOneDay/20180813 京东.md",
    "content": "京东Java面经1+2+HR笔经面经牛客网\nhttps://www.nowcoder.com/discuss/76916?type=2&order=3&pos=20&page=1\n\n \n\n北京京东等结果真的很熬人等了快一周了，虽然结果还未知，先发一下自己面试经历也算攒个人品吧。。。 \n\n  一面现场 \n\n  面试官没让自我介绍 \n\n  1项目 \n\n  2多线程 \n\n  3Java集合类 \n\n  4一些笔试的错题让我再看为什么做错 \n\n  5HashMap（其实就是按照这个往下说比如怎么扩容，里面的装载因子，长度为什么16，hash函数为什么移位，冲突后怎么解决一类的） \n\n  6项目中Spring AOP用在什么地方，为什么这么用，切点，织入，通知用自己的话描述一下，AOP原理，动态代理2种实现 \n\n  7Spring里面注解用过没有？autowired 和resource区别（一开始我给听成result了。。。。去介绍strusts2了。。） \n\n  8单元测试 \n\n  9Linux命令，怎么日志文件里面找关键字 \n\n  10怎么杀死进程 \n\n  11MQ，zookeeper，dubbo，redis，是否了解分布式，是否了解负载均衡？  \n\n  12能否加班，对团队合作怎么看（我也不知道一面为啥问我这个。。。） \n\n  一面大概聊了快一小时，面试官说一面给过，但是需要加强实践 \n\n  二面现场（全程项目） \n\n  自我介绍 \n\n  1项目 \n\n  2为什么用SSH \n\n  3项目自己做的还是合作 \n\n  4项目中遇到的难点？怎么解决？ \n\n  5怎么学习的项目 \n\n  6看书或者博客里的新技术项目中是否试过？ \n\n  7怎么学习？ \n\n  8单元测试（又来一次。。。早知道简历上不写了。。） \n\n  9Spring（AOP，IOC又来一次。。。） \n\n  10并发发生的场景？有没有想过怎么解决？ \n\n  二面可能也就20分钟吧，我项目没怎么复习 \n\n  HR面电话 \n\n  自我介绍 \n\n  本科社团活动，担任的职务，遇到过哪些问题，怎么解决的，你认为这些问题的本质是什么引起的。。。 \n\n  能实习多久 \n\n  6分钟结束 \n\n  一面二面有一些也没答出来。。。。和第一个面试官聊的还可以，二面就有些严肃了。明天周一了应该结果要出来了。。。羡慕已经拿offer的同学，希望自己也能拿到京东实习offer"
  },
  {
    "path": "others/interview/InterviewOneDay/20180820 Java面试不相信眼泪.md",
    "content": "来源：[Java面试不相信眼泪，金九银十你准备好了吗？](https://www.toutiao.com/i6590978826157687299/?tt_from=weixin&utm_campaign=client_share&group_id=6590978826157687299&from=singlemessage&timestamp=1534586342&app=news_article_social&utm_source=weixin&isappinstalled=0&iid=41314592202&utm_medium=toutiao_ios&wxshare_count=3&pbid=6584669799438960136)\n\n"
  },
  {
    "path": "others/interview/InterviewOneDay/20180823 网易游戏2017暑期实习生面经 Java开发.md",
    "content": "网易游戏2017暑期实习生面经 Java开发_笔经面经_牛客网\nhttps://interview.nowcoder.com/discuss/59422"
  },
  {
    "path": "others/interview/InterviewOneDay/20180823 网易考拉 java 凉面面经.md",
    "content": "(1条未读消息) 网易考拉 java 凉面面经_笔经面经_牛客网\nhttps://www.nowcoder.com/discuss/97738\n\n\n\n\n\n网易杭州研究院面经_笔经面经_牛客网\nhttps://www.nowcoder.com/discuss/97488\n\n\n\n\n\n【面试经验】分享一篇网易杭州研究院的面经_笔经面经_牛客网\nhttps://www.nowcoder.com/discuss/73109"
  },
  {
    "path": "others/interview/InterviewOneDay/20180823 聊聊C10K问题及解决方案.md",
    "content": "聊聊C10K问题及解决方案 - 陶邦仁的个人空间 - 开源中国\nhttps://my.oschina.net/xianggao/blog/664275"
  },
  {
    "path": "others/interview/InterviewOneDay/README.md",
    "content": "## README\n\n这里将每天更新一篇面经，希望对后续的面试有所帮助！也能全面的梳理基础知识\n\n"
  },
  {
    "path": "others/interview/Java面试经验贴.md",
    "content": "\n\n原文地址：https://zhuanlan.zhihu.com/p/25725929\n\n\n专栏好久没更新了，因为最近在找实习呀！\n\n刚从百度回来就开始准备各大内推面试，好在一切顺利，阿里面了五面，还在等消息中.\n\n腾讯也面了三面，但方向不太切合.\n\n也推了美团、网易、华为、唯品会、蘑菇街，不过都还没开始面试，也不打算折腾了......\n\n总结一下复习时所记的笔记吧\n\n具体的题目可以参照下寒假实习的题目，都大同小异\n\n[2016-2017寒假实习面试总结（java后台） - 知乎专栏](https://zhuanlan.zhihu.com/p/24617729)\n\n\n\n\n\n## 目录\n\n[TOC]\n\n\n\n\n\n## Java基础\n\n- Java内存模型\n- 多态（重载重写）\n- object方法\n- 类访问权限\n- sleep、notify、wait 联系、区别\n- String、stringbuffer、stringbuilder 联系、区别、源码\n- Volatile 原理、源码、与syn区别\n- 线程间通信方式\n- 线程的各种状态\n- 等等等等\n\n \n\n\n\n\n\n## 集合框架 \n\n**List**\n\n- ArrayList\n- LinkedList\n- Vector\n\n三者区别，联系，源码\n\n**Set**\n\n- HashSet\n- LinkedHashSet\n- TreeSet\n\n基于什么实现，内部数据结构，适用场景，源码\n\n**Map**\n\n- HashMap\n- weakHashMao\n- LinkedHashMap\n- TreeMap\n\nHashMap与hashtable的区别\n\n内部实现原理、源码、适用场景\n\n \n\n \n\n## 并发包\n\n**ConcurrentHashMap**\n\n- 原理、源码、与hashmap的区别\n\n \n\n**CopyOnWriteArrayList (set)**\n\n- 什么情况加锁、什么情况不加锁、适用场景\n\n \n\n**ArrayblockingQueue (Linked)**\n\n- 两者区别，take、put、offer、poll方法原理、源码\n\n**AtomicInteger (long boolean)**\n\n- 功能\n\n \n\n**CountDownLatch**\n\n- 功能、场景\n\n**CyclicBarrier**\n\n- 功能、场景\n\n**FutureTask (Callable)**\n\n- 源码、场景\n\n \n\n**ReentantLock**\n\n- 与syn的区别、好处、场景\n\n \n\n**Condition**\n\n- 与wait、notify的区别、好处\n\n \n\n**Semaphore**\n\n- 好处、场景\n\n \n\n**ReentrantReadWriteLock**\n\n- 读写分离的好处、适用场景、源码\n\n \n\n**Executors**\n\n- 线程池种类、各个作用、适用场景\n\n \n\n**ThreadPoolExecutor**\n\n- 重载方法的参数、各参数作用、源码\n\n \n\n \n\n## 虚拟机\n\n**JVM五大区**\n\n- 每个区的存储、作用\n\n**JVM内存模型**\n\n- 类加载机制\n- 双亲委派模型\n\n \n\n垃圾收集器\n\n \n\n- 常用gc算法\n- 收集器种类、适用场景\n- fullGC、MinorGC触发条件\n\n \n\nJVM优化\n\n- 可视化工具使用\n- 日志查询\n- 各项参数设置\n- 四种引用\n\n \n\n \n\n## IO流\n\n\n\nBIO\n\n- 字节流：类型、适用场景\n- 字符流：类型、适用场景\n\n \n\nNIO\n\n- 类型、适用场景\n- 三大组件的联系、使用\n- 内存情况\n\n \n\nAIO\n\n \n\n## 大数据\n\n- zookeeper\n- kafka\n- redis集群\n- storm\n- hadoop\n- spark\n- solr cloud\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- InnoDB\n- MyISAM\n- 区别、联系、锁机制、适用场景\n\n \n\n**索引**\n\n- 类型\n- 使用\n- 什么样的字段适合做索引\n\n**SQL优化**\n\n \n\n \n\n## web框架\n\n**Tomcat**\n\n- 结构、流程、源码\n\n**Servlet**\n\n- 生命周期\n- 三种实现方式\n\n \n\n**springMVC**\n\n- 使用\n- 请求流程\n\n \n\n**spring**\n\n- IOC/AOP 原理、源码、联系\n- 两种动态代理实现\n\n \n\n**mybatis**\n\n- 使用\n- \\#、$区别\n- 一级、二级缓存\n\n \n\n**注解技术**\n\nannotation java5\n\n\n\n\n\n \n\n## 设计模式\n\n- **单例模式**\n- **工厂模式**\n- **观察者模式**\n- **适配器模式**\n- **模仿方法模式**\n- **策略模式**\n- **责任链模式**\n- **装饰者模式**\n\n \n\n1. 常用的八种掌握就行，原理，使用\n2. 单例、工厂、观察者重点\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-Tree\n- B+Tree\n\n \n\n两者的联系、区别、适用场景\n\n \n\n \n\n## 算法\n\n- **直接插入排序**\n- **二分插入排序**\n- **希尔插入排序**\n- **冒泡排序**\n- **快排**\n- **选择排序**\n- **堆排序**\n- **归并排序**\n\n \n\n1. 各种排序的思想\n2. 实现复杂度\n3. 稳定性如何\n4. 可以手写\n\n \n\n## 网络\n\n**TCP**\n\n- 三次握手、四次挥手、各种状态、状态改变\n- 和UDP的区别\n\n \n\n**IO模型**\n\n- 同步、异步、阻塞、非阻塞概念\n- 模型种类、各自特点、适用场景\n- 如何使用\n\n \n\n## Linux基础\n\n- 常用命令\n- 管道符\n- 查看日志相关命令\n- CPU使用命令\n"
  },
  {
    "path": "others/interview/TODOLIST.md",
    "content": "## 操作系统\n\n- 进程调度算法\n\n\n\n## 算法\n\n- 写代码，判断链表是否中心对称，要求时间O(N)，空间O(1)，解释思路+口头跑testcase。（8分钟左右）\n\n\n\n## 补充知识点\n\n- 设计一个线程池\n- mybatis二级缓存如何设计\n- CAS如何实现\n- Linux diff命令\n- 函数式编程 Lambda \n- rpc\n- 消息队列\n- 分布式\n\n\n\n## 待整理好文\n\n高效运维：TCP连接的状态详解以及故障排查\nhttps://mp.weixin.qq.com/s/hnstE9WgvShE3qZcWWbUJQ\n\n\n\n## 技术点\n\n- RPC\n\n分布式系统设计与开发\n\n负载均衡技术\n\n系统容灾设计\n\n高可用系统\n\n\n\n\n\n\n\n## README\n\n- 网络 I/O 模型\n\n　　4 种网络 IO 模型、select、poll、epoll"
  },
  {
    "path": "others/interview/interview-daily/20180901 有道简单面经.md",
    "content": "来源：[牛客网](https://www.nowcoder.com/discuss/100101)\n\n\n\n一面（47min） \n\n   \t1. 聊聊你的简历上的项目吧：1234blablabla。（15分钟） \n   \t2. **写代码，判断链表是否中心对称，要求时间O(N)，空间O(1)，解释思路+口头跑testcase。（8分钟左右）** \n\n   \t3. 操作系统学过吗？解释**线程 & 进程**的特点和区别。 \n   \t4. **进程调度算法有哪些**\n\n   \t5. 死锁的必要条件 & **如何在写代码的时候就能完全避免死锁的发生**。 \n   \t6. 计算机网络学过吗？**简单讲一下DNS是什么，DNS解析过程**。 \n   \t7. Java中的各个Map容器，讲讲各自的特殊性质？HashMap / ConcurrentHashMap / LinkedHashMap / TreeMap / HashTable \n   \t8. static关键字的作用 \n   \t9. 数据库方面，**主键和外键的性质，它们的作用**。 \n   \t10. MySQL中索引的数据结构和实现。 \n   \t11. 讲讲你如何理解面向对象？其中主要抠了**Java里多态的体现**。 \n   \t12. 你有什么问题想问我的吗 \n\n\n\n\n二面（35min） \n\n \t1. 讲讲实习的时候做了什么具体东西吧？然后就是交互式探讨（10分钟） \n\n \t2. 写代码，2维字符数组中search给定单词是否存在，DFS轻松破之，口头解释思路（8-10分钟） \n\n \t3. 问：JVM有了解吗？答：有学习过，但是JVM太宽泛了，不知道从哪里说起。问：那**讲讲指令重排序**方面的。 \n\n \t4. （然后应该还问了1-2个问题，但实在是忘记了。。。） \n\n \t5. 你有什么要问我的吗 \n\n \n\n三面（总监）（60min左右） \n\n   \t1. 讲讲你做得最用心，你觉得最值得讲的项目。抠细节，blablabla（15分钟） \n   \t2. **设计新浪微博总体架构，要考虑高并发，海量数据，实时feed流及时性等因素，手画架构图+口头解释+抠细节（安全性 / 并发性 / 可扩展性)。（45分钟）** \n\n \t3. 现在手上有什么offer，投了啥公司，以后想做什么方向。 \n\n \t4. 你有什么要问我的吗 "
  },
  {
    "path": "others/interview/interview-daily/20180902 金山云一二三面.md",
    "content": "## 金山云一二三面\n\n> 来源：[牛客网](https://www.nowcoder.com/discuss/101646)\n\n考察知识点：\n\n- Redis\n  - **数据结构，如何设置过期时间**\n  - **分布式锁**\n- Java容器\n  - **Hashmap、ConcurrentHashmap**\n  - **ArrayList、LinkedList**\n- Spring\n  - **IOC、AOP（AOP是用来干嘛的）**\n  - **SpringMVC 请求过程**\n- Java 虚拟机\n  - 内存分区\n  - 类加载机制\n- Java 并发\n  - **原子更新操作**\n- 分布式专题\n  - **使用过哪些分布式的东西**\n  - Zookeeper\n- 海量数据处理\n  - **大数问题，10G 重复 url 文件，有限内存，输出唯一 url 的排序，写伪代码**\n- 数据结构\n  - **给定二叉树输出给定值的层数**\n  - **合并有序数组**"
  },
  {
    "path": "others/interview/interview-daily/20180913 美团三面.md",
    "content": "## 美团Java web 三面\n\n美团Java web 三面_笔经面经_牛客网\nhttps://www.nowcoder.com/discuss/105268\n\n\n\n**一面**\n\n1. **自我介绍** \n\n2. **谈一个你觉得你学到最多的项目（瞎扯了一大堆）** \n\n3. Spring的bean的作用域？（我靠，就知道singleton，prototype） \n\n  4.Spring的IOC实现原理？没有无参构造函数能实例化吗？有参构造函数注入？（xml配置） \n\n  5.通过反射，谈到了方法区，然后，类加载机制？（hotspot的启动类加载一部分是用c/c++写的，那么请问是怎么写？ W：卧槽，我怎么知道  T：窃笑，估计他是故意的）谁叫我嘴贱说了个启动类加载器一部分是c/c++写的 \n\n  6.synchronized的实现原理？Volatile能保证原子性吗？为什么？（内存屏障？T：什么是内存屏障？ W：emmm。。。操作系统规定的只能按照一定序列的原子操作）  \n\n  7.Volatile多线程++的问题？（读，改，写）  \n\n  8.JVM的调优参数？（-Xmn，-Xms，，，）  \n\n  10.https和http的区别？端口？（443，做梦也没想到会问这个，小哥哥估计是懵了）  \n\n  11.对称加密和非对称加密的区别？  \n\n  12.线程的五态？转化过程？  \n\n  13.TCP三次握手，为什么三次握手？  \n\n  14.JVM内存分区？（主存，工作内存，堆，栈。。。。）  \n\n  15.讲一下GC？（我靠，这么直接？）  \n\n  16.为什么要用老年代和新生代？  \n\n  17.新生代进入老生代的情况？（有三种）  \n\n  18.提前担保机制？  \n\n  19.新生代的分区？  \n\n  20.问你个算法？一堆字符串求出现次数最多的字符串？(最后，回去再想想，W：额，好吧)  \n \n\n**二面**\n\n \t1.变着法的问了一大堆线程池的知识 （记住那些参数稳稳地） \n\n \t2.讲了String的底层实现，自己写一个不可变对象的策略（4个点） \n\n \t3.finally \n\n \t4.数据隔离级别 \n\n \t5.数据库索引 主键和唯一索引有什么区别 \n\n \t6.B+树和B-树的区别 \n\n \t7.聚集索引和非聚集索引的区别？ \n\n \t8.innDB和MyISAM的区别？ \n\n \t9.innoDB的B+树索引叶子节点的Data域存储的是什么？MyISAM的B+树索引叶子节点的Data域存储的是主键还是物理地址？ \n\n \t10.count(*)和count(1)的区别？（讲道理，这个我真不知道）sum(..), avg(..)的用法 \n\n \t11.最左前缀？ \n\n \t12.那些情况下不会使用索引？ \n\n \t13.找数组中没有组成数对的数？一个的情况？二个的情况？（异或） \n\n \t\n \n\n**三面** \n\n1. 进程和线程的区别\n\n3. 什么会有线程？或者说线程存在的目的？ \n\n4. 讲一下innodb的特性？ \n\n5. 复合索引是如何实现的？ \n\n6. 什么事聚集索引？ \n\n7. Tcp和Udp的区别？ \n\n8. cookies和session的区别？优劣？ \n\n9. 单点登录说下？ \n\n10. 你说到了oauth2.0，能具体说下吗？ \n\n11. 100的阶乘后面有几个0？ \n\n12. n条线最多嗯班一个平面分为几个部分？（f(n) = f(n-1)+n） \n\n \t\n\n\n\n \t现在想想好像问的确实有点简单  面试官小哥哥人挺好，和我扯皮扯了20分钟，然后和我说，行了，不扯皮了，我们开始问问题吧 哈哈哈 \n\n \t美团Java web一面 <https://www.nowcoder.com/discuss/94374>  \n\n \t美团Java web二面 <https://www.nowcoder.com/discuss/96239>  \n\n \n\n \t再见，秋招，祝大家好运"
  },
  {
    "path": "others/interview/秋招面经_重口味AC.md",
    "content": " \n\nhttps://www.nowcoder.com/discuss/61958?type=2&order=0&pos=9&page=26\n\n博主渣渣本科，挣扎到十一月秋招终于结束了。面过百度/腾讯/小米/网易/搜狗/知乎/京东/360/瓜子。期间总结了一些面试题目，现在放上来。由于是博主自己的面经记录，所以涵盖不全面的话诸位请谅解。 \n 根据博主的面试经验来看，面试有一定的层次性，如bat级别公司每个点都会深入，而有些公司则只会问到表层，所以将每个领域都分为必须掌握和深入了解这两个部分。 \n\n#   一、计算机网络 \n\n  **基础部分**  \n\n1.    TCP报头格式    \n2.    UDP报头格式    \n3.    TCP/UDP区别（不仅是宏观上的，最好能根据各自的机制讲解清楚）    \n4.    HTTP状态码（最好结合使用场景，比如在缓存命中时使用哪个）    \n5.    HTTP协议（一些报头字段的作用，如cace-control、keep-alive）    \n6.    OSI协议、TCP/IP协议以及每层对应的协议。    \n7.    SESSION机制、cookie机制    \n8.    TCP三次握手、四次挥手（这个问题真的要回答吐了，不过真的是面试官最喜欢问的，建议每天手撸一遍，而且不只是每次请求的过程，各种FIN_WAIT、TIME_WAIT状态也要掌握）。    \n9.    打开网页到页面显示之间的过程（涵盖了各个方面，DNS解析过程，Nginx请求转发、连接建立和保持过程、浏览器内容渲染过程，考虑的越详细越好）。    \n10.    http和https区别，https在请求时额外的过程，https是如何保证数据安全的    \n11.    IP地址子网划分    \n12.    POST和GET区别    \n13.    DNS解析过程   \n\n  **深入部分** \n\n1. TCP如何保证数据的可靠传输的（这个问题可以引申出很多子问题，拥塞控制慢开始、拥塞避免、快重传、滑动窗口协议、停止等待协议、超时重传机制，最好都能掌握） \n2. 地址解析协议ARP \n3. 交换机和路由器的区别 \n\n#   二、数据库 \n\n  **基础部分**  \n\n1.    事务四大特性（ACID）    \n2.    数据库隔离级别，每个级别会引发什么问题，mysql默认是哪个级别    \n3.    MYSQL的两种存储引擎区别（事务、锁级别等等），各自的适用场景    \n4.    数据库的优化（从sql语句优化和索引两个部分回答）    \n5.    索引有B+索引和hash索引，各自的区别    \n6.    B+索引数据结构，和B树的区别    \n7.    索引的分类（主键索引、唯一索引），最左前缀原则，哪些情况索引会失效    \n8.    聚集索引和非聚集索引区别。    \n9.    有哪些锁（乐观锁悲观锁），select时怎么加排它锁    \n10.    关系型数据库和非关系型数据库区别    \n11.    了解nosql    \n12.    数据库三范式，根据某个场景设计数据表（可以通过手绘ER图）    \n13.    数据库的主从复制    \n14.    使用explain优化sql和索引    \n15.    long_query怎么解决    \n16.    内连接、外连接、交叉连接、笛卡儿积等   \n\n  **深入**  \n\n1.    MVCC机制    \n2.    根据具体场景，说明版本控制机制    \n3.    死锁怎么解决    \n4.    varchar和char的使用场景。    \n5.    mysql并发情况下怎么解决（通过事务、隔离级别、锁）   \n\n  **Redis**  \n\n1.    redis数据结构有哪些    \n2.    redis队列应用场景    \n3.    redis和Memcached（支持数据持久化）    \n4.    分布式使用场景（储存session等）    \n5.    发布/订阅使用场景   \n\n#   三、操作系统 \n\n1.    内存的页面置换算法    \n2.    进程调度算法    \n3.    进程间通信方式    \n4.    进程线程区别    \n5.    进程之间的通信    \n6.    父子进程、孤儿进程    \n7.    fork进程时的操作， \n    这个部分我回答的都不好，只能是死记硬背，建议基础好的同学多看看操作系统这部分，能大大加分。   \n\n#   四、算法 \n\n  **基础**  \n\n1.    剑指OFFER的各个题目是最常见的，即使不是原题也是题目的变体，因为面试不像笔试，一般不会出特别困难的题目，所以剑指OFFER上小而精的题目就非常适合。建议手刷一遍。PHP的同学可以参考专栏[剑指OFFER](http://blog.csdn.net/column/details/15795.html)     \n\n2.    二叉树相关（层次遍历、求深度、求两个节点距离、翻转二叉树、前中后序遍历）    \n\n3.    链表相关（插入节点、链表逆置、使用链表进行大数字的加减，双向链表实现队列、寻找链表中的环）    \n\n4.    堆（大量数据中寻找最大N个数字几乎每次都会问，还有堆在插入时进行的调整）    \n\n5.    排序（八大排序，各自的时间复杂度、排序算法的稳定性。快排几乎每次都问）    \n\n6.    二分查找（一般会深入，如寻找数组总和为K的两个数字）    \n\n7.    两个栈实现队列。    \n\n8.    图（深度广度优先遍历、单源最短路径、最小生成树）    \n\n9. ​    动态规划问题。   \n\n   ​    **深入**    \n\n10. ​    红黑树性质   \n\n11.    分治法和动态规划的区别    \n\n12.    计算时间复杂度    \n\n13.    二叉树和哈希表查找的时间复杂度   \n\n  栈和链表是面试算法的时候经常用到的工具，多考虑怎么用数据结构的性质解决，因为面试不像笔试，对基础数据结构关注的比较多一些，一般问题也比较简单。然后取模也是常用的工具（比如有一次问怎么让100个进程按规定的权重被调用，就可以用取模的方式）。 \n 面试官一般会先出简单的问题，然后深入地问下去，最好是根据他的思路走，因为能听懂他的提示也是需要考察的能力。 \n\n#   LINUX \n\n1.    硬链接和软连接区别    \n2.    kill用法，某个进程杀不掉的原因（进入内核态，忽略kill信号）    \n3.    linux用过的命令    \n4.    系统管理命令（如查看内存使用、网络情况）    \n5.    管道的使用 |    \n6.    grep的使用，一定要掌握，每次都会问在文件中查找    \n7.    shell脚本    \n8.    find命令    \n9.    awk使用   \n\n#   项目 \n\n1.    项目中遇到的困难（提前想好，并且把实现或者优化方法说清楚）    \n2.    系统的量级、pv、uv等    \n3.    应对高并发的解决办法（分布式）    \n4.    在项目中主要负责了哪些工作。    \n5.    nginx的负载均衡    \n6.    分布式缓存的一致性，服务器如何扩容（哈希环）   \n\n  总之要把写在简历上的项目部分熟悉一遍，技术栈、项目功能、难点都要考虑好。 "
  },
  {
    "path": "others/interview/面经资源.md",
    "content": "【史上最全面经】后台开发岗-java篇_笔经面经_牛客网\nhttps://www.nowcoder.com/discuss/84004\n\n\n\n\n\n【干货】阿里巴巴2018秋招面经汇总\nhttps://zhuanlan.zhihu.com/p/39869348?from=timeline&isappinstalled=0&wechatShare=1\n\n\n\n【这可能不只是一篇面经】_笔经面经_牛客网\nhttps://www.nowcoder.com/discuss/29890\n\n\n\n阿里二面准备（Java 研发） - 开发者头条（Jack老师hashmap分析）\nhttps://toutiao.io/posts/mowf2q/preview\n\n\n\n\n\n一个学渣的阿里之路 | crossoverJie's Blog\nhttps://crossoverjie.top/2018/06/21/personal/Interview-experience/\n\n\n\n\n\n2019秋招 | 蚂蚁金服面经\nhttps://zhuanlan.zhihu.com/p/40543442\n\n\n\n非CS渣硕深信服秋招内推一面凉凉_笔经面经_牛客网\nhttps://www.nowcoder.com/discuss/84557?type=2&order=3&pos=10&page=1\n\n\n\n\n\n参加完阿里面试：一面+二面+三面+HR四面，后的复盘经验总结！ 优知学院\nhttp://youzhixueyuan.com/ali-interview-one-side-two-sides-three-sides-hr-four-sides-my-experience-of-summary.html\n\n\n\n\n\n如何准备校招技术面试\nhttp://brianway.github.io/2017/09/29/how-to-prepare-a-technical-interview/\n\n\n\n\n\n互联网公司校招Java面试题总结及答案——网易 - CSDN博客\nhttps://blog.csdn.net/d12345678a/article/details/54376929\n\n\n\n\n\n四年努力，梦归阿里，和大家聊聊成长感悟 - 五月的仓颉 - 博客园\nhttps://www.cnblogs.com/xrq730/p/9159586.html"
  },
  {
    "path": "others/pics_tag.md",
    "content": "\n# 文档格式\n\n## 1. 图片格式\n\n\n\n<div align=\"center\"> <img src=\"../pics/hash-to-badlink.png\" width=\"\"/></div><br/>\n\n<div align=\"center\"> <img src=\"pics/concurrent_and_parallel.png\" width=\"\"/></div><br/>\n\n<div align=\"center\"><img src=\"\" width=\"\"/></div><br/>\n\n<div align=\"center\"><img src=\"\" width=\"\"/></div>\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "others/reference/参考资料.md",
    "content": "# 附录：参考资料\n\n[CyC2018/Interview-Notebook: 技术面试需要掌握的基础知识整理，欢迎编辑~](https://github.com/CyC2018/Interview-Notebook) `star: 23489`\n\n[Google Interview University 一套完整的学习手册帮助自己准备 Google 的面试](https://github.com/jwasham/coding-interview-university/blob/master/translations/README-cn.md)\n\n[linw7/Skill-Tree: 🐼 准备秋招，欢迎来树上取果实](https://github.com/linw7/Skill-Tree)\n\n[视频学习资源共享 (更新于：2018年4月30日)](https://github.com/shiyuan17/share_video/tree/1773f9f1e181d40f3e00041805933ca55932c553)\n\n[我珍藏的神兵利器 - 效率工具](https://www.liutf.com/posts/3720794851.html)\n\n[Linux 守护进程的启动方法](https://mp.weixin.qq.com/s/DzajJNhcpB3hqWzzm71Q0w)\n\n[波波老师：关于学习方法和今后提高自学能力的疑问！](http://coding.imooc.com/learn/questiondetail/46130.html)\n\n[最近面了不少java开发，据此来说下我的感受：哪怕事先只准备1小时，成功概率也能大大提升](https://mp.weixin.qq.com/s/TheCxmlDrcz5oFAahz6Rxw)\n\n\n\n#### 学习Github库\n\n[hadyang/interview: Java / Android 笔试、面试 知识整理](https://github.com/hadyang/interview) `star：928`\n\n[francistao/LearningNotes: Enjoy Learning.](https://github.com/francistao/LearningNotes)  `star：9194`\n\n[Snailclimb/Java_Guide: Java面试通关手册（Java学习指南）](https://github.com/Snailclimb/Java_Guide) `star：94`\n\nIntroduction · 笔试面试知识整理\nhttps://hit-alibaba.github.io/interview/index.html\n\n[justjavac/free-programming-books-zh_CN: 免费的计算机编程类中文书籍，欢迎投稿](https://github.com/justjavac/free-programming-books-zh_CN)\n\n"
  }
]