diff --git a/.gitignore b/.gitignore
index 37d6ef0..fa43602 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ _config.yml
.settings
.springBeans
.sts4-cache
+.vscode
### IntelliJ IDEA ###
.idea
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..9a17302
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,11 @@
+{
+ "qiniu.access_key": "5zQ0AuES-7MGr7y89zIyMIK7JsNUlpTLggi5JtGu",
+ "qiniu.bucket": "sihai",
+ "qiniu.domain": "image.ouyangsihai.cn",
+ "qiniu.enable": true,
+ "qiniu.secret_key": "Mq3GKXAmESb4QGcv8mY8-lNWt42G0AFHpTKgl5Yh",
+ "pasteImageToQiniu.access_key": "5zQ0AuES-7MGr7y89zIyMIK7JsNUlpTLggi5JtGu",
+ "pasteImageToQiniu.bucket": "sihai",
+ "pasteImageToQiniu.domain": "image.ouyangsihai.cn",
+ "pasteImageToQiniu.secret_key": "Mq3GKXAmESb4QGcv8mY8-lNWt42G0AFHpTKgl5Yh"
+}
\ No newline at end of file
diff --git a/CNAME b/CNAME
new file mode 100644
index 0000000..6efb3fd
--- /dev/null
+++ b/CNAME
@@ -0,0 +1 @@
+github.ouyangsihai.cn
\ No newline at end of file
diff --git a/README.md b/README.md
index ef8d5cf..1c7180e 100644
--- a/README.md
+++ b/README.md
@@ -1,121 +1,201 @@
-**[JavaInterview](https://github.com/OUYANGSIHAI/JavaInterview)** 是本人在备战春招及这几年学习的知识沉淀,这里面有很多都是自己的原创文章,同时,也有很多是本在备战春招的过程中觉得对面试特别有帮助的文章,**[JavaInterview](https://github.com/OUYANGSIHAI/JavaInterview)** 不一定可以帮助你进入到 BAT 等大厂,但是,如果你认真研究,仔细思考,我相信你也可以跟我一样幸运的进入到腾讯等大厂实实习。
+**[JavaInterview](https://github.com/OUYANGSIHAI/JavaInterview)** 是本人在备战春招及这几年学习的知识沉淀,这里面有很多都是自己的原创文章,同时,也有很多是本在备战春招的过程中觉得对面试特别有帮助的文章,**[JavaInterview](https://github.com/OUYANGSIHAI/JavaInterview)** 不一定可以帮助你进入到 BAT 等大厂,但是,如果你认真研究,仔细思考,我相信你也可以跟我一样幸运的进入到大厂。
-本人经常在 CSDN 写博客,累计**原创博客 400+**,拥有**访问量160W**,目前是 **CSDN 博客专家**,春招目前拿到了腾讯等大厂offer。
+本人经常在 CSDN 写博客,累计**原创博客 400+**,拥有**访问量251W+**,**CSDN 博客专家**,CSDN博客地址:[https://sihai.blog.csdn.net](https://sihai.blog.csdn.net),春招目前拿到了大厂offer。
如果觉得有帮助,给个 **star** 好不好,哈哈(目前还不是很完善,后面会一一补充)。
-**JavaInterview 最新地址**:https://github.com/OUYANGSIHAI/JavaInterview
-
**一起冲!!!**
-
-
-
+👉 如果你不知道该学习什么的话,请看 [Java 学习线路图是怎样的?](https://zhuanlan.zhihu.com/p/392712685) (原创不易,欢迎点赞),这是 2021 最新最完善的 Java 学习路线!
+
+👉 Java学习资源汇总(个人总结)
+
+- **Java基础到Java实战全套学习视频教程,包括多个企业级实战项目**
+
+- **面试算法资料,这是总结的算法资料,学完基本可以应付80%大厂**
+
+- **大厂面试资料,一年时间总结,覆盖Java所有技术点**
+
+- **面试思维导图,手打总结**
+
+👉 **Java各种电子书:各种技术相关的电子书**
-
+👉 **Java面试思维导图(手打)**,我靠这些导图拿到了一线互联网公司的offer,关注公众号,回复:`思维导图`;
-[](https://github.com/OUYANGSIHAI/JavaInterview#%E8%81%94%E7%B3%BB%E6%88%91) [](https://github.com/OUYANGSIHAI/JavaInterview#%E5%85%AC%E4%BC%97%E5%8F%B7) [](https://juejin.im/user/5a672822f265da3e55380f0b) [](https://blog.csdn.net/sihai12345) [](https://space.bilibili.com/441147490)
-
-### 目录(ctrl + f 查找更香)
+**划重点**:获取上面的资源,请关注我的公众号 `程序员的技术圈子`,**微信扫描下面二维码**,回复:`Java资料`,获取思维导图,绿色通道关注福利,等你拿。
-- [项目准备](#----)
-- [面试知识点](#-----)
-- [公司面经](#----)
+
+
+
+
+### 目录(ctrl + f 查找更香:不能点击的,还在写)
+
+- [个人经验](#个人经验)
+- [项目准备](#项目准备)
+- [面试知识点](#面试知识点)
+- [公司面经](#公司面经)
- [Java](#java)
- * [基础](#--)
- * [容器(包括juc)](#-----juc-)
- * [并发](#--)
- * [JVM](#jvm)
- * [Java8](#java8)
-- [计算机网络](#-----)
-- [计算机操作系统](#-------)
+ - [基础](#基础)
+ - [容器(包括juc)](#容器包括juc)
+ - [基础容器](#基础容器)
+ - [阻塞容器](#阻塞容器)
+ - [并发](#并发)
+ - [JVM](#jvm)
+ - [Java8](#java8)
+- [计算机网络](#计算机网络)
+- [计算机操作系统](#计算机操作系统)
- [Linux](#linux)
-- [数据结构与算法](#-------)
- * [数据结构](#----)
- * [算法](#--)
-- [数据库](#---)
- * [MySQL](#mysql)
- + [mysql(优化思路)](#mysql------)
-- [系统设计](#----)
- * [秒杀系统相关](#------)
- * [前后端分离](#-----)
- * [单点登录](#----)
- * [常用框架](#----)
- + [Spring](#spring)
- + [SpringBoot](#springboot)
-- [分布式](#---)
- * [dubbo](#dubbo)
- * [zookeeper](#zookeeper)
- * [RocketMQ](#rocketmq)
- * [RabbitMQ](#rabbitmq)
- * [kafka](#kafka)
- * [消息中间件](#-----)
- * [redis](#redis)
- * [分布式系统](#-----)
-- [线上问题调优(虚拟机,tomcat)](#-----------tomcat-)
-- [面试指南](#----)
-- [工具](#--)
- * [Git](#git)
- * [Docker](#docker)
-- [其他](#--)
- * [权限控制(设计、shiro)](#--------shiro-)
-- [Java学习资源](#--)
-- [Java书籍推荐](#java----)
-- [实战项目推荐](#------)
-- [说明](#--)
- * [JavaInterview介绍](#javainterview--)
- * [关于转载](#----)
- * [如何对该开源文档进行贡献](#------------)
- * [为什么要做这个开源文档?](#------------)
- * [投稿](#--)
- * [联系我](#---)
- * [公众号](#---)
-
+- [数据结构与算法](#数据结构与算法)
+ - [数据结构](#数据结构)
+ - [算法](#算法)
+- [数据库](#数据库)
+ - [MySQL](#mysql)
+ - [MySQL(优化思路)](#mysql优化思路)
+- [系统设计](#系统设计)
+ - [秒杀系统相关](#秒杀系统相关)
+ - [前后端分离](#前后端分离)
+ - [单点登录](#单点登录)
+ - [常用框架](#常用框架)
+ - [Spring](#spring)
+ - [SpringBoot](#springboot)
+- [分布式](#分布式)
+ - [dubbo](#dubbo)
+ - [zookeeper](#zookeeper)
+ - [RocketMQ](#rocketmq)
+ - [RabbitMQ](#rabbitmq)
+ - [kafka](#kafka)
+ - [消息中间件](#消息中间件)
+ - [redis](#redis)
+ - [分布式系统](#分布式系统)
+- [线上问题调优(虚拟机,tomcat)](#线上问题调优虚拟机tomcat)
+- [面试指南](#面试指南)
+- [工具](#工具)
+ - [Git](#git)
+ - [Docker](#docker)
+- [其他](#其他)
+ - [权限控制(设计、shiro)](#权限控制设计shiro)
+- [Java学习资源](#java学习资源)
+- [Java书籍推荐](#java书籍推荐)
+- [实战项目推荐](#实战项目推荐)
+- [程序人生](#程序人生)
+- [说明](#说明)
+ - [JavaInterview介绍](#javainterview介绍)
+ - [关于转载](#关于转载)
+ - [如何对该开源文档进行贡献](#如何对该开源文档进行贡献)
+ - [为什么要做这个开源文档?](#为什么要做这个开源文档)
+ - [投稿](#投稿)
+ - [联系我](#联系我)
+ - [公众号](#公众号)
+
+## 个人经验
+
+- [应届生如何准备校招,用我这一年的校招经历告诉你](https://sihai.blog.csdn.net/article/details/114258312?spm=1001.2014.3001.5502)
+- [【大学到研究生自学Java的学习路线】这是一份最适合普通大众、非科班的路线,帮你快速找到一份满意的工作](https://sihai.blog.csdn.net/article/details/105964718?spm=1001.2014.3001.5502)
+- [两个月的面试真实经历,告诉大家如何能够进入大厂工作?](https://sihai.blog.csdn.net/article/details/105807642)
## 项目准备
-- [如何进行项目的自我介绍呢?](docs/interview/自我介绍和项目介绍.md)
-
-- [项目需要准备必备知识及方法](docs/project/秒杀项目总结.md)
+- [我的个人项目介绍模板](docs/interview/自我介绍和项目介绍.md)
+- [本人面试两个月真实经历:面试了20家大厂之后,发现这样介绍项目经验,显得项目很牛逼!](https://sihai.blog.csdn.net/article/details/105854760)
+- [项目必备知识及解决方案](docs/project/秒杀项目总结.md)
## 面试知识点
-- [各公司面试知识点汇总](docs/interview-experience/面试常见知识.md)
-- [面试常见问题分类汇总](docs/interview-experience/面试常见问题分类汇总.md)
+- [各大公司面试知识点汇总](docs/interview-experience/各大公司面经.md)
+- [Java后端面试常见问题分类汇总(高频考点)](docs/interview-experience/面试常见问题分类汇总.md)
## 公司面经
- [2020年各公司面试经验汇总](docs/interview-experience/各大公司面经.md)
+- [最新!!招银网络科技Java面经,整理附答案](https://mp.weixin.qq.com/s/HAUOH-EYS_3Ho2XxYkTGXA)
+- [拿了 30K 的 offer!](https://mp.weixin.qq.com/s/R4gZ8IuskxgxA1SZwfCOoA)
+- [重磅面经!!四面美团最终拿到了 offer](https://mp.weixin.qq.com/s/P1mDcH5hEXqNp2Jpz5Qjmg)
+- [十面阿里,七面头条](https://mp.weixin.qq.com/s/FErQnLvYnuZxiaDkYWPO5A)
## Java
### 基础
-- [Java基础系列文章](https://mp.weixin.qq.com/mp/homepage?__biz=MzI2OTQ4OTQ1NQ==&hid=1&sn=c455e51f87eaa9c12d6b45e0e4d33960&scene=1&devicetype=iOS13.3.1&version=17000c2b&lang=zh_CN&nettype=WIFI&ascene=7&session_us=gh_2bfc34fbf760&fontScale=100&wx_header=1)
+这几篇文章虽然是基础,但是确实深入理解基础,如果你能很好的理解这些基础,那么对于Java基础面试题也是没有什么问题的,背面试题不如理解原理,很重要。
+
+- [Java基础思维导图](http://mp.weixin.qq.com/s?__biz=MzI2OTQ4OTQ1NQ==&mid=2247483823&idx=1&sn=4588a874055e8ca54f2bbe1ede12cff4&scene=19#wechat_redirect)
+- [Java基础(一) 深入解析基本类型](http://mp.weixin.qq.com/s?__biz=MzI2OTQ4OTQ1NQ==&mid=2247483948&idx=1&sn=cb0ae3d82a1629e3a0538b6f31e2473b&scene=19#wechat_redirect)
+- [Java基础(二) 自增自减与贪心规则](https://mp.weixin.qq.com/s?__biz=MzI2OTQ4OTQ1NQ==&mid=2247483951&idx=1&sn=af5b54ed2e26d975f96643d9dfd66fab&scene=19#wechat_redirect)
+- [Java基础(三) 加强型for循环与Iterator](https://mp.weixin.qq.com/s?__biz=MzI2OTQ4OTQ1NQ==&mid=2247483952&idx=1&sn=43130fdf815970e0e12347d057c6b24f&scene=19#wechat_redirect)
+- [Java基础(四) java运算顺序的深入解析](https://mp.weixin.qq.com/s?__biz=MzI2OTQ4OTQ1NQ==&mid=2247483955&idx=1&sn=abfb3e8ac31cb84bb78216d9c953abc0&scene=19#wechat_redirect)
+- [Java基础(五) String性质深入解析](https://mp.weixin.qq.com/s?__biz=MzI2OTQ4OTQ1NQ==&mid=2247483956&idx=1&sn=1c19164967621fa5449a7830d006c8f9&scene=19#wechat_redirect)
+- [Java基础(六) switch语句的深入解析](https://mp.weixin.qq.com/s?__biz=MzI2OTQ4OTQ1NQ==&mid=2247483999&idx=1&sn=092ad983f87798360ef33b1485f3201b&scene=19#wechat_redirect)
+- [Java基础(七) 深入解析java四种访问权限](https://mp.weixin.qq.com/s?__biz=MzI2OTQ4OTQ1NQ==&mid=2247484000&idx=1&sn=0b188c70ac54c65a0419ab0d5da14af4&scene=19#wechat_redirect)
+- [Java基础(八) 深入解析常量池与装拆箱机制](https://mp.weixin.qq.com/s?__biz=MzI2OTQ4OTQ1NQ==&mid=2247484002&idx=1&sn=a1d9ec01c91537aca444408c989f5a50&scene=19#wechat_redirect)
+- [Java基础(九) 可变参数列表介绍](https://mp.weixin.qq.com/s?__biz=MzI2OTQ4OTQ1NQ==&mid=2247484003&idx=1&sn=84366ed430c332d4b8e2b2d6b54280f4&scene=19#wechat_redirect)
+- [Java基础(十) 深入理解数组类型](https://mp.weixin.qq.com/s?__biz=MzI2OTQ4OTQ1NQ==&mid=2247484004&idx=1&sn=9c58b6948f05bbeffea3552fec9ee9a6&scene=19#wechat_redirect)
+- [Java基础(十一) 枚举类型](https://mp.weixin.qq.com/s?__biz=MzI2OTQ4OTQ1NQ==&mid=2247484005&idx=1&sn=5aaec133dca189fcabc86defcd54c5b8&scene=19#wechat_redirect)
+- [类与接口(二)java的四种内部类详解](https://mp.weixin.qq.com/s?__biz=MzI2OTQ4OTQ1NQ==&mid=2247484075&idx=1&sn=e0fd37cc5c1eb5fb359ed3dc9c15af66&scene=19#wechat_redirect)
+- [类与接口(三)java中的接口与嵌套接口](https://mp.weixin.qq.com/s?__biz=MzI2OTQ4OTQ1NQ==&mid=2247484076&idx=1&sn=1903edbc469b2660e51e1154a8b63a27&scene=19#wechat_redirect)
+- [类与接口(四)方法重载解析](https://mp.weixin.qq.com/s?__biz=MzI2OTQ4OTQ1NQ==&mid=2247484078&idx=1&sn=db5f231dc64974057d4ee29af1649e8b&scene=19#wechat_redirect)
+- [类与接口(五)java多态、方法重写、隐藏](https://mp.weixin.qq.com/s?__biz=MzI2OTQ4OTQ1NQ==&mid=2247484083&idx=1&sn=d5b3d1daca2eb4e8d9583d75e6f6ad6c&scene=19#wechat_redirect)
+
### 容器(包括juc)
+#### 基础容器
+
+- [ArrayList源码分析及真实大厂面试题精讲](https://blog.csdn.net/sihai12345/article/details/138413307?spm=1001.2014.3001.5501)
+- [LinkedList源码分析及真实大厂面试题精讲](https://blog.csdn.net/sihai12345/article/details/138413722?spm=1001.2014.3001.5501)
+- [HashMap源码分析及真实大厂面试题精讲](https://blog.csdn.net/sihai12345/article/details/138416578?spm=1001.2014.3001.5501)
+- TreeMap源码分析及真实大厂面试题精讲
+- TreeSet源码分析及真实大厂面试题精讲
+- LinkedHashMap源码分析及真实大厂面试题精讲
+
+#### 阻塞容器
+
+- [ConcurrentHashMap源码分析及真实大厂面试题精讲](https://blog.csdn.net/sihai12345/article/details/138420403)
+- ArrayBlockingQueue源码分析及真实大厂面试题精讲
+- LinkedBlockingQueue源码分析及真实大厂面试题精讲
+- PriorityBlockingQueue源码分析及真实大厂面试题精讲
### 并发
+- [Synchronized关键字精讲及真实大厂面试题解析](https://blog.csdn.net/sihai12345/article/details/138420474)
+- [Volitale关键字精讲及真实大厂面试题解析](https://blog.csdn.net/sihai12345/article/details/138420521)
+- 关于LRU的实现
+- [ThreadLocal面试中会怎么提问呢?](https://blog.csdn.net/sihai12345/article/details/138420558)
+- [线程池的面试题,这篇文章帮你搞定它!](https://blog.csdn.net/sihai12345/article/details/138420591)
### JVM
- [深入理解Java虚拟机系列](https://mp.weixin.qq.com/s/SZ87s3fmKL3Kc_tAMcOFQw)
- [深入理解Java虚拟机系列--完全解决面试问题](https://blog.ouyangsihai.cn/shen-ru-li-jie-java-xu-ni-ji-xi-lie-jiao-cheng.html)
+- [深入理解Java虚拟机-Java内存区域透彻分析](https://mp.weixin.qq.com/s/WuyxyelaXbU-lg-HVZ95TA)
+- [深入理解Java虚拟机-JVM内存分配与回收策略原理,从此告别JVM内存分配文盲](https://mp.weixin.qq.com/s/IG_zU5xa7y4BB6PVP0Fmow)
+- [深入理解Java虚拟机-常用vm参数分析](https://mp.weixin.qq.com/s/l8fsq07jI0svqBdBGxuOzA)
+- [深入理解Java虚拟机-如何利用JDK自带的命令行工具监控上百万的高并发的虚拟机性能](https://mp.weixin.qq.com/s/wPgA5SDURCAqPsWkZGGX0g)
+- [深入理解Java虚拟机-如何利用VisualVM对高并发项目进行性能分析](https://mp.weixin.qq.com/s/hhA9tI_rYNkJVbF-R45hbA)
+- [深入理解Java虚拟机-你了解GC算法原理吗](https://mp.weixin.qq.com/s/SZ87s3fmKL3Kc_tAMcOFQw)
+- [几个面试官常问的垃圾回收器,下次面试就拿这篇文章怼回去!](https://sihai.blog.csdn.net/article/details/105700527)
+- [面试官100%会严刑拷打的 CMS 垃圾回收器,下次面试就拿这篇文章怼回去!](https://sihai.blog.csdn.net/article/details/105808878)
+- [JVM 面试题 87 题详解](https://sihai.blog.csdn.net/article/details/118737581)
### Java8
-- [Java8快速学习教程](https://blog.ouyangsihai.cn/java8-zui-xin-jiao-cheng-bu-yi-yang-de-java8.html)
-- [Java11的最新特性](https://blog.ouyangsihai.cn/java11-zheng-shi-fa-bu-liao-wo-men-gai-zen-me-ban.html)
+- [Java8 Stream:2万字20个实例,玩转集合的筛选、归约、分组、聚合](https://mp.weixin.qq.com/s/u042M2Sw2glBlevIDVoSXg)
+- [利用Java8新特征,重构传统设计模式,你学会了吗?](https://mp.weixin.qq.com/s/zZ6rWz_t_snYNiNyOtaGiQ)
+- [Java8 之 lambda 表达式、方法引用、函数式接口、默认方式、静态方法](https://mp.weixin.qq.com/s/FdzNWIsEmHVe9Nehxvfa3w)
+- [Java8之Consumer、Supplier、Predicate和Function攻略](https://sihai.blog.csdn.net/article/details/98193777)
+- [Java8 的 Stream 流式操作之王者归来](https://sihai.blog.csdn.net/article/details/100434684)
+- [Java11-17的最新特性](https://mp.weixin.qq.com/s/QPGdNn56mCCDIUS047_1cQ)
## 计算机网络
- [http面试问题全解析](docs/network/http面试问题全解析.md)
+- [计算机网络常见面试题](https://sihai.blog.csdn.net/article/details/118737663)
+- 关于tcp、udp网络模型的问题,这篇文章告诉你
+- http、https还不了解,别慌!
+- 面试官问我计算机网络的问题,我一个问题给他讲半个小时
## 计算机操作系统
@@ -123,28 +203,55 @@
## Linux
-- [java工程师linux命令,这篇文章就够了](https://blog.ouyangsihai.cn/java-gong-cheng-shi-linux-ming-ling-zhe-pian-wen-zhang-jiu-gou-liao.html)
+- [java工程师linux命令,这篇文章就够了](https://mp.weixin.qq.com/s/bj28tvF9TwgwrH65OPjXZg)
+- [linux常见面试题(基础版)](https://sihai.blog.csdn.net/article/details/118737736)
+- [linux高频面试题](docs/operating-system/linux高频面试题.md)
+- 常问的几个Linux面试题,通通解决它
## 数据结构与算法
### 数据结构
+- [跳表这种数据结构,你真的清楚吗,面试官可能会问这些问题!](https://blog.csdn.net/sihai12345/article/details/138419109)
+- 红黑树你了解多少,不会肯定会被面试官怼坏
+- [B树,B+树,你了解多少,面试官问那些问题?](https://segmentfault.com/a/1190000020416577)
+- [这篇文章带你彻底理解红黑树](https://sihai.blog.csdn.net/article/details/118738496)
+- 二叉树、二叉搜索树、二叉平衡树、红黑树、B树、B+树
### 算法
-- [2020年最新算法面试真题汇总](docs/dataStructures-algorithms/算法面试真题汇总.md)
-- [2020年最新算法题型难点总结](docs/dataStructures-algorithms/算法题目难点题目总结.md)
+- [从大学入门到研究生拿大厂offer,必须看的数据结构与算法书籍推荐,不好不推荐!](https://sihai.blog.csdn.net/article/details/106011624?spm=1001.2014.3001.5502)
+- [2021年面试高频算法题题解](docs/dataStructures-algorithms/高频算法题目总结.md)
+- [2021年最新剑指offer难题解析](docs/dataStructures-algorithms/剑指offer难点总结.md)
+- [关于贪心算法的leetcode题目,这篇文章可以帮你解决80%](https://blog.ouyangsihai.cn/jie-shao-yi-xia-guan-yu-leetcode-de-tan-xin-suan-fa-de-jie-ti-fang-fa.html)
+- [dfs题目这样去接题,秒杀leetcode题目](https://sihai.blog.csdn.net/article/details/106895319)
+- [回溯算法不会,这篇文章一定得看](https://sihai.blog.csdn.net/article/details/106993339)
+- 动态规划你了解多少,我来帮你入个们
+- 链表的题目真的不难,看了这篇文章你就知道有多简单了
+- 还在怕二叉树的题目吗?
+= 栈和队列的题目可以这样出题型,你掌握了吗
+- 数组中常用的几种leetcode解题技巧!
## 数据库
### MySQL
-- [MySQL深入理解教程-解决面试中的各种问题](https://blog.ouyangsihai.cn/mysql-shen-ru-li-jie-jiao-cheng-mysql-de-yi-zhu-shi-jie.html)
+- [InnoDB与MyISAM等存储引擎对比](https://sihai.blog.csdn.net/article/details/100832158)
+- [MySQL:从B树到B+树到索引再到存储引擎](https://mp.weixin.qq.com/s/QmG1FyWPp23klTVkTJvcUQ)
+- [MySQL全文索引最强教程](https://blog.ouyangsihai.cn/mysql-quan-wen-suo-yin.html)
+- [MySQL的又一神器-锁,MySQL面试必备](https://sihai.blog.csdn.net/article/details/102680104)
+- [MySQL事务,这篇文章就够了](https://sihai.blog.csdn.net/article/details/102815801)
+- [mysqldump工具命令参数大全](https://blog.ouyangsihai.cn/mysqldump-gong-ju-ming-ling-can-shu-da-quan.html)
+- [看完这篇MySQL备份的文章,再也不用担心删库跑路了](https://blog.ouyangsihai.cn/kan-wan-zhe-pian-mysql-bei-fen-de-wen-zhang-zai-ye-bu-yong-dan-xin-shan-ku-pao-lu-liao.html)
+- 关于MySQL索引,面试中面试官会怎么为难你,一定得注意
+- MySQL中的乐观锁、悲观锁,JDK中的乐观锁、悲观锁?
-#### mysql(优化思路)
+#### MySQL(优化思路)
- [MySQL高频面试题](https://mp.weixin.qq.com/s/KFCkvfF84l6Eu43CH_TmXA)
- [MySQL查询优化过程](https://mp.weixin.qq.com/s/jtuLb8uAIHJNvNpwcIZfpA)
+- [面试官:MySQL 上亿大表,如何深度优化?](https://mp.weixin.qq.com/s/g-_Oz9CLJfBn_asJrzn6Yg)
+- [老司机总结的12条 SQL 优化方案(非常实用)](https://mp.weixin.qq.com/s/7QuASKTpXOm54CgLiHqEJg)
## 系统设计
@@ -175,14 +282,14 @@
#### SpringBoot
-- [springboot史上最全教程](https://blog.csdn.net/sihai12345/category_7779682.html)
+- [springboot史上最全教程,11篇文章全解析](https://blog.csdn.net/sihai12345/category_7779682.html)
- [微服务面试相关资料](docs/microservice/微服务相关资料.md)
## 分布式
### dubbo
-- [dubbo教程](https://blog.ouyangsihai.cn/dubbo-yi-pian-wen-zhang-jiu-gou-liao-dubbo-yu-dao-chu-lian.html)
+- [dubbo入门实战教程,这篇文章真的再好不过了](https://segmentfault.com/a/1190000019896723)
- [dubbo源码分析](http://cmsblogs.com/?p=5324)
- [dubbo面试题](https://mp.weixin.qq.com/s/PdWRHgm83XwPYP08KnkIsw)
- [dubbo面试题2](https://mp.weixin.qq.com/s/Kz0s9K3J9Lpvh37oP_CtCA)
@@ -190,13 +297,7 @@
### zookeeper
- [什么是zookeeper?](https://mp.weixin.qq.com/s/i2_c4A0146B7Ev8QnofbfQ)
-
-- [Zookeeper教程](http://cmsblogs.com/?p=4139)
-
-- [zookeeper源码分析](http://cmsblogs.com/?p=4190)
-
- [zookeeeper面试题](https://segmentfault.com/a/1190000014479433)
-
- [zookeeper面试题2](https://juejin.im/post/5dbac7a0f265da4d2c5e9b3b)
@@ -204,7 +305,6 @@
- [RocketMQ简单教程](https://juejin.im/post/5af02571f265da0b9e64fcfd)
- [RocketMQ教程](https://mp.weixin.qq.com/s/VAZaU1DuKbpnaALjp_-9Qw)
-- [RocketMQ源码分析](http://cmsblogs.com/?p=3236)
- [RocketMQ面试题](https://blog.csdn.net/dingshuo168/article/details/102970988)
### RabbitMQ
@@ -222,8 +322,6 @@
- [kafka面试题](https://blog.csdn.net/qq_28900249/article/details/90346599)
- [kafka面试题2](http://trumandu.github.io/2019/04/13/Kafka%E9%9D%A2%E8%AF%95%E9%A2%98%E4%B8%8E%E7%AD%94%E6%A1%88%E5%85%A8%E5%A5%97%E6%95%B4%E7%90%86/)
-- [分布式架构文章](https://blog.ouyangsihai.cn/fen-bu-shi-jia-gou-xi-lie-wen-zhang.html)
-
### 消息中间件
- [消息中间件面试题总结](docs/project/消息中间件面试题.md)
@@ -233,7 +331,6 @@
- [Redis设计与实现总结文章](https://blog.csdn.net/qq_41594698/category_9067680.html)
- [Redis面试题必备:基础,面试题](https://mp.weixin.qq.com/s/3Fmv7h5p2QDtLxc9n1dp5A)
- [Redis面试相关:其中包含redis知识](https://blog.csdn.net/qq_35190492/article/details/103105780)
-- [Redis源码分析](http://cmsblogs.com/?p=4570)
- [redis其他数据结构](https://blog.csdn.net/c_royi/article/details/82011208)
### 分布式系统
@@ -242,8 +339,8 @@
- [垃圾收集器ZGC](https://juejin.im/post/5dc361d3f265da4d1f51c670)
- [jvm系列文章](https://crowhawk.github.io/tags/#JVM)
- [一次JVM FullGC的背后,竟隐藏着惊心动魄的线上生产事故!](https://mp.weixin.qq.com/s/5SeGxKtwp6KZhUKn8jXi6A)
-- [Java虚拟机调优文章](https://blog.ouyangsihai.cn/categories/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA/)
-- [利用VisualVM对高并发项目进行性能分析](https://blog.ouyangsihai.cn/shen-ru-li-jie-java-xu-ni-ji-ru-he-li-yong-visualvm-dui-gao-bing-fa-xiang-mu-jin-xing-xing-neng-fen-xi.html#toc-heading-8)
+- [深入理解Java虚拟机-如何利用JDK自带的命令行工具监控上百万的高并发的虚拟机性能](https://mp.weixin.qq.com/s/wPgA5SDURCAqPsWkZGGX0g)
+- [深入理解Java虚拟机-如何利用VisualVM对高并发项目进行性能分析](https://mp.weixin.qq.com/s/hhA9tI_rYNkJVbF-R45hbA)
- [JVM性能调优](https://www.iteye.com/blog/uule-2114697)
- [百亿吞吐量服务的JVM性能调优实战](https://mp.weixin.qq.com/s?__biz=MzIwMzY1OTU1NQ==&mid=2247484236&idx=1&sn=b9743b2d7436f84e4617ff34e07abdd8&chksm=96cd4300a1baca1635a137294bc93c518c033ce01f843c9e012a1454b9f3ea3158fa1412e9da&scene=27&ascene=0&devicetype=android-24&version=26060638&nettype=WIFI&abtest_cookie=BAABAAoACwASABMABAAjlx4AUJkeAFmZHgBomR4AAAA%3D&lang=zh_CN&pass_ticket=%2F%2BLqr9N2EZtrEGLFo9vLA6Eqs89DSJ2CBKoAJFZ%2BBngphEP28dwmMQeSZcUB77qZ&wx_header=1)
- [一次线上JVM调优实践,FullGC40次/天到10天一次的优化过程](https://blog.csdn.net/cml_blog/article/details/81057966)
@@ -257,7 +354,7 @@
### Git
-- [实际开发中的git命令大全](https://www.jianshu.com/p/53a00fafbe99)
+- [实际开发中的git命令大全](https://sihai.blog.csdn.net/article/details/106418135)
### Docker
@@ -272,24 +369,28 @@
## Java学习资源
-- [2020年Java学习资源,你想要的都在这里了](https://mp.weixin.qq.com/s/wLjjy7D57s3UOv4sr8Lxkg)
-
-**截图**
-
-
-
-
+- [2021年Java视频学习教程+项目实战](https://github.com/hello-go-maker/cs-learn-source)
+- [2021 Java 1000G 最新学习资源大汇总](https://mp.weixin.qq.com/s/I0jimqziHqRNaIy0kXRCnw)
## Java书籍推荐
+- [从入门到拿大厂offer,必须看的数据结构与算法书籍推荐](https://blog.csdn.net/sihai12345/article/details/106011624)
+- [全网最全电子书下载](https://github.com/hello-go-maker/cs-books)
## 实战项目推荐
>小心翼翼的告诉你,上面的资源当中就有很多**企业级项目**,没有项目一点不用怕,因为你看到了这个。
- [找工作,没有上的了台面的项目怎么办?](https://mp.weixin.qq.com/s/0oK43_z99pVY9dYVXyIeiw)
+- [Java 实战项目推荐](https://github.com/hello-go-maker/cs-learn-source)
+## 程序人生
+
+- [我想是时候跟大学告别了](https://blog.csdn.net/sihai12345/article/details/86934341)
+- [坚持,这两个字非常重要!](https://blog.csdn.net/sihai12345/article/details/89507366)
+- [关于考研,这是我给大家的经验](https://blog.csdn.net/sihai12345/article/details/88548630)
+- [从普通二本到研究生再到自媒体的年轻人,这是我的故事](https://segmentfault.com/a/1190000020317748)
## 说明
@@ -319,12 +420,12 @@
添加我的微信备注 **github**, 即可入群。
-
-
+
### 公众号
-如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
+如果大家想要实时关注我更新的文章以及分享的干货的话,关注我的公众号 **程序员的技术圈子**。
+
+
-
diff --git a/_config.yml b/_config.yml
new file mode 100644
index 0000000..c419263
--- /dev/null
+++ b/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-cayman
\ No newline at end of file
diff --git a/assets/wx.jpg b/assets/wx.jpg
new file mode 100644
index 0000000..3fd7f11
Binary files /dev/null and b/assets/wx.jpg differ
diff --git "a/assets/\347\250\213\345\272\217\345\221\230\346\212\200\346\234\257\345\234\210\345\255\220.jpg" "b/assets/\347\250\213\345\272\217\345\221\230\346\212\200\346\234\257\345\234\210\345\255\220.jpg"
new file mode 100644
index 0000000..8561507
Binary files /dev/null and "b/assets/\347\250\213\345\272\217\345\221\230\346\212\200\346\234\257\345\234\210\345\255\220.jpg" differ
diff --git a/docs/dataStructures-algorithms/Backtracking-NQueens.md b/docs/dataStructures-algorithms/Backtracking-NQueens.md
deleted file mode 100644
index bac262d..0000000
--- a/docs/dataStructures-algorithms/Backtracking-NQueens.md
+++ /dev/null
@@ -1,145 +0,0 @@
-# N皇后
-[51. N皇后](https://leetcode-cn.com/problems/n-queens/)
-### 题目描述
-> n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
->
-
->
-上图为 8 皇后问题的一种解法。
->
-给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
->
-每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
-
-示例:
-
-```
-输入: 4
-输出: [
- [".Q..", // 解法 1
- "...Q",
- "Q...",
- "..Q."],
-
- ["..Q.", // 解法 2
- "Q...",
- "...Q",
- ".Q.."]
-]
-解释: 4 皇后问题存在两个不同的解法。
-```
-
-### 问题分析
-约束条件为每个棋子所在的行、列、对角线都不能有另一个棋子。
-
-使用一维数组表示一种解法,下标(index)表示行,值(value)表示该行的Q(皇后)在哪一列。
-每行只存储一个元素,然后递归到下一行,这样就不用判断行了,只需要判断列和对角线。
-### Solution1
-当result[row] = column时,即row行的棋子在column列。
-
-对于[0, row-1]的任意一行(i 行),若 row 行的棋子和 i 行的棋子在同一列,则有result[i] == column;
-若 row 行的棋子和 i 行的棋子在同一对角线,等腰直角三角形两直角边相等,即 row - i == Math.abs(result[i] - column)
-
-布尔类型变量 isValid 的作用是剪枝,减少不必要的递归。
-```
-public List> solveNQueens(int n) {
- // 下标代表行,值代表列。如result[0] = 3 表示第1行的Q在第3列
- int[] result = new int[n];
- List> resultList = new LinkedList<>();
- dfs(resultList, result, 0, n);
- return resultList;
-}
-
-void dfs(List> resultList, int[] result, int row, int n) {
- // 递归终止条件
- if (row == n) {
- List list = new LinkedList<>();
- for (int x = 0; x < n; ++x) {
- StringBuilder sb = new StringBuilder();
- for (int y = 0; y < n; ++y)
- sb.append(result[x] == y ? "Q" : ".");
- list.add(sb.toString());
- }
- resultList.add(list);
- return;
- }
- for (int column = 0; column < n; ++column) {
- boolean isValid = true;
- result[row] = column;
- /*
- * 逐行往下考察每一行。同列,result[i] == column
- * 同对角线,row - i == Math.abs(result[i] - column)
- */
- for (int i = row - 1; i >= 0; --i) {
- if (result[i] == column || row - i == Math.abs(result[i] - column)) {
- isValid = false;
- break;
- }
- }
- if (isValid) dfs(resultList, result, row + 1, n);
- }
-}
-```
-### Solution2
-使用LinkedList表示一种解法,下标(index)表示行,值(value)表示该行的Q(皇后)在哪一列。
-
-解法二和解法一的不同在于,相同列以及相同对角线的校验。
-将对角线抽象成【一次函数】这个简单的数学模型,根据一次函数的截距是常量这一特性进行校验。
-
-这里,我将右上-左下对角线,简称为“\”对角线;左上-右下对角线简称为“/”对角线。
-
-“/”对角线斜率为1,对应方程为y = x + b,其中b为截距。
-对于线上任意一点,均有y - x = b,即row - i = b;
-定义一个布尔类型数组anti_diag,将b作为下标,当anti_diag[b] = true时,表示相应对角线上已经放置棋子。
-但row - i有可能为负数,负数不能作为数组下标,row - i 的最小值为-n(当row = 0,i = n时),可以加上n作为数组下标,即将row -i + n 作为数组下标。
-row - i + n 的最大值为 2n(当row = n,i = 0时),故anti_diag的容量设置为 2n 即可。
-
-
-
-“\”对角线斜率为-1,对应方程为y = -x + b,其中b为截距。
-对于线上任意一点,均有y + x = b,即row + i = b;
-同理,定义数组main_diag,将b作为下标,当main_diag[row + i] = true时,表示相应对角线上已经放置棋子。
-
-有了两个校验对角线的数组,再来定义一个用于校验列的数组cols,这个太简单啦,不解释。
-
-**解法二时间复杂度为O(n!),在校验相同列和相同对角线时,引入三个布尔类型数组进行判断。相比解法一,少了一层循环,用空间换时间。**
-
-```
-List> resultList = new LinkedList<>();
-
-public List> solveNQueens(int n) {
- boolean[] cols = new boolean[n];
- boolean[] main_diag = new boolean[2 * n];
- boolean[] anti_diag = new boolean[2 * n];
- LinkedList result = new LinkedList<>();
- dfs(result, 0, cols, main_diag, anti_diag, n);
- return resultList;
-}
-
-void dfs(LinkedList result, int row, boolean[] cols, boolean[] main_diag, boolean[] anti_diag, int n) {
- if (row == n) {
- List list = new LinkedList<>();
- for (int x = 0; x < n; ++x) {
- StringBuilder sb = new StringBuilder();
- for (int y = 0; y < n; ++y)
- sb.append(result.get(x) == y ? "Q" : ".");
- list.add(sb.toString());
- }
- resultList.add(list);
- return;
- }
- for (int i = 0; i < n; ++i) {
- if (cols[i] || main_diag[row + i] || anti_diag[row - i + n])
- continue;
- result.add(i);
- cols[i] = true;
- main_diag[row + i] = true;
- anti_diag[row - i + n] = true;
- dfs(result, row + 1, cols, main_diag, anti_diag, n);
- result.removeLast();
- cols[i] = false;
- main_diag[row + i] = false;
- anti_diag[row - i + n] = false;
- }
-}
-```
\ No newline at end of file
diff --git "a/docs/dataStructures-algorithms/\344\270\200\346\226\207\346\220\236\345\256\232\351\223\276\350\241\250\345\237\272\347\241\200\345\222\214\351\223\276\350\241\250\351\235\242\350\257\225\351\242\230.md" "b/docs/dataStructures-algorithms/\344\270\200\346\226\207\346\220\236\345\256\232\351\223\276\350\241\250\345\237\272\347\241\200\345\222\214\351\223\276\350\241\250\351\235\242\350\257\225\351\242\230.md"
new file mode 100644
index 0000000..45fdc1a
--- /dev/null
+++ "b/docs/dataStructures-algorithms/\344\270\200\346\226\207\346\220\236\345\256\232\351\223\276\350\241\250\345\237\272\347\241\200\345\222\214\351\223\276\350\241\250\351\235\242\350\257\225\351\242\230.md"
@@ -0,0 +1,6 @@
+### 链表基础结构
+
+### 链表的常见操作
+
+### 链表常见面试题
+
diff --git "a/docs/dataStructures-algorithms/\345\205\254\345\217\270\347\234\237\351\242\230.md" "b/docs/dataStructures-algorithms/\345\205\254\345\217\270\347\234\237\351\242\230.md"
deleted file mode 100644
index c78ed8f..0000000
--- "a/docs/dataStructures-algorithms/\345\205\254\345\217\270\347\234\237\351\242\230.md"
+++ /dev/null
@@ -1,254 +0,0 @@
-# 网易 2018
-
-下面三道编程题来自网易2018校招编程题,这三道应该来说是非常简单的编程题了,这些题目大家稍微有点编程和数学基础的话应该没什么问题。看答案之前一定要自己先想一下如果是自己做的话会怎么去做,然后再对照这我的答案看看,和你自己想的有什么区别?那一种方法更好?
-
-## 问题
-
-### 一 获得特定数量硬币问题
-
-小易准备去魔法王国采购魔法神器,购买魔法神器需要使用魔法币,但是小易现在一枚魔法币都没有,但是小易有两台魔法机器可以通过投入x(x可以为0)个魔法币产生更多的魔法币。
-
-魔法机器1:如果投入x个魔法币,魔法机器会将其变为2x+1个魔法币
-
-魔法机器2:如果投入x个魔法币,魔法机器会将其变为2x+2个魔法币
-
-小易采购魔法神器总共需要n个魔法币,所以小易只能通过两台魔法机器产生恰好n个魔法币,小易需要你帮他设计一个投入方案使他最后恰好拥有n个魔法币。
-
-**输入描述:** 输入包括一行,包括一个正整数n(1 ≤ n ≤ 10^9),表示小易需要的魔法币数量。
-
-**输出描述:** 输出一个字符串,每个字符表示该次小易选取投入的魔法机器。其中只包含字符'1'和'2'。
-
-**输入例子1:** 10
-
-**输出例子1:** 122
-
-### 二 求“相反数”问题
-
-为了得到一个数的"相反数",我们将这个数的数字顺序颠倒,然后再加上原先的数得到"相反数"。例如,为了得到1325的"相反数",首先我们将该数的数字顺序颠倒,我们得到5231,之后再加上原先的数,我们得到5231+1325=6556.如果颠倒之后的数字有前缀零,前缀零将会被忽略。例如n = 100, 颠倒之后是1.
-
-**输入描述:** 输入包括一个整数n,(1 ≤ n ≤ 10^5)
-
-**输出描述:** 输出一个整数,表示n的相反数
-
-**输入例子1:** 1325
-
-**输出例子1:** 6556
-
-### 三 字符串碎片的平均长度
-
-一个由小写字母组成的字符串可以看成一些同一字母的最大碎片组成的。例如,"aaabbaaac"是由下面碎片组成的:'aaa','bb','c'。牛牛现在给定一个字符串,请你帮助计算这个字符串的所有碎片的平均长度是多少。
-
-**输入描述:** 输入包括一个字符串s,字符串s的长度length(1 ≤ length ≤ 50),s只含小写字母('a'-'z')
-
-**输出描述:** 输出一个整数,表示所有碎片的平均长度,四舍五入保留两位小数。
-
-**如样例所示:** s = "aaabbaaac"
-所有碎片的平均长度 = (3 + 2 + 3 + 1) / 4 = 2.25
-
-**输入例子1:** aaabbaaac
-
-**输出例子1:** 2.25
-
-## 答案
-
-### 一 获得特定数量硬币问题
-
-#### 分析:
-
-作为该试卷的第一题,这道题应该只要思路正确就很简单了。
-
-解题关键:明确魔法机器1只能产生奇数,魔法机器2只能产生偶数即可。我们从后往前一步一步推回去即可。
-
-#### 示例代码
-
-注意:由于用户的输入不确定性,一般是为了程序高可用性使需要将捕获用户输入异常然后友好提示用户输入类型错误并重新输入的。所以下面我给了两个版本,这两个版本都是正确的。这里只是给大家演示如何捕获输入类型异常,后面的题目中我给的代码没有异常处理的部分,参照下面两个示例代码,应该很容易添加。(PS:企业面试中没有明确就不用添加异常处理,当然你有的话也更好)
-
-**不带输入异常处理判断的版本:**
-
-```java
-import java.util.Scanner;
-
-public class Main2 {
- // 解题关键:明确魔法机器1只能产生奇数,魔法机器2只能产生偶数即可。我们从后往前一步一步推回去即可。
-
- public static void main(String[] args) {
- System.out.println("请输入要获得的硬币数量:");
- Scanner scanner = new Scanner(System.in);
- int coincount = scanner.nextInt();
- StringBuilder sb = new StringBuilder();
- while (coincount >= 1) {
- // 偶数的情况
- if (coincount % 2 == 0) {
- coincount = (coincount - 2) / 2;
- sb.append("2");
- // 奇数的情况
- } else {
- coincount = (coincount - 1) / 2;
- sb.append("1");
- }
- }
- // 输出反转后的字符串
- System.out.println(sb.reverse());
-
- }
-}
-```
-
-**带输入异常处理判断的版本(当输入的不是整数的时候会提示重新输入):**
-
-```java
-import java.util.InputMismatchException;
-import java.util.Scanner;
-
-
-public class Main {
- // 解题关键:明确魔法机器1只能产生奇数,魔法机器2只能产生偶数即可。我们从后往前一步一步推回去即可。
-
- public static void main(String[] args) {
- System.out.println("请输入要获得的硬币数量:");
- Scanner scanner = new Scanner(System.in);
- boolean flag = true;
- while (flag) {
- try {
- int coincount = scanner.nextInt();
- StringBuilder sb = new StringBuilder();
- while (coincount >= 1) {
- // 偶数的情况
- if (coincount % 2 == 0) {
- coincount = (coincount - 2) / 2;
- sb.append("2");
- // 奇数的情况
- } else {
- coincount = (coincount - 1) / 2;
- sb.append("1");
- }
- }
- // 输出反转后的字符串
- System.out.println(sb.reverse());
- flag=false;//程序结束
- } catch (InputMismatchException e) {
- System.out.println("输入数据类型不匹配,请您重新输入:");
- scanner.nextLine();
- continue;
- }
- }
-
- }
-}
-
-```
-
-### 二 求“相反数”问题
-
-#### 分析:
-
-解决本道题有几种不同的方法,但是最快速的方法就是利用reverse()方法反转字符串然后再将字符串转换成int类型的整数,这个方法是快速解决本题关键。我们先来回顾一下下面两个知识点:
-
-**1)String转int;**
-
-在 Java 中要将 String 类型转化为 int 类型时,需要使用 Integer 类中的 parseInt() 方法或者 valueOf() 方法进行转换.
-
-```java
- String str = "123";
- int a = Integer.parseInt(str);
-```
-
- 或
-
-```java
- String str = "123";
- int a = Integer.valueOf(str).intValue();
-```
-
-**2)next()和nextLine()的区别**
-
-在Java中输入字符串有两种方法,就是next()和nextLine().两者的区别就是:nextLine()的输入是碰到回车就终止输入,而next()方法是碰到空格,回车,Tab键都会被视为终止符。所以next()不会得到带空格的字符串,而nextLine()可以得到带空格的字符串。
-
-#### 示例代码:
-
-```java
-import java.util.Scanner;
-
-/**
- * 本题关键:①String转int;②next()和nextLine()的区别
- */
-public class Main {
-
- public static void main(String[] args) {
-
- System.out.println("请输入一个整数:");
- Scanner scanner = new Scanner(System.in);
- String s=scanner.next();
- //将字符串转换成数字
- int number1=Integer.parseInt(s);
- //将字符串倒序后转换成数字
- //因为Integer.parseInt()的参数类型必须是字符串所以必须加上toString()
- int number2=Integer.parseInt(new StringBuilder(s).reverse().toString());
- System.out.println(number1+number2);
-
- }
-}
-```
-
-### 三 字符串碎片的平均长度
-
-#### 分析:
-
-这道题的意思也就是要求:(字符串的总长度)/(相同字母团构成的字符串的个数)。
-
-这样就很简单了,就变成了字符串的字符之间的比较。如果需要比较字符串的字符的话,我们可以利用charAt(i)方法:取出特定位置的字符与后一个字符比较,或者利用toCharArray()方法将字符串转换成字符数组采用同样的方法做比较。
-
-#### 示例代码
-
-**利用charAt(i)方法:**
-
-```java
-import java.util.Scanner;
-
-public class Main {
-
- public static void main(String[] args) {
-
- Scanner sc = new Scanner(System.in);
- while (sc.hasNext()) {
- String s = sc.next();
- //个数至少为一个
- float count = 1;
- for (int i = 0; i < s.length() - 1; i++) {
- if (s.charAt(i) != s.charAt(i + 1)) {
- count++;
- }
- }
- System.out.println(s.length() / count);
- }
- }
-
-}
-```
-
-**利用toCharArray()方法:**
-
-```java
-import java.util.Scanner;
-
-public class Main2 {
-
- public static void main(String[] args) {
-
- Scanner sc = new Scanner(System.in);
- while (sc.hasNext()) {
- String s = sc.next();
- //个数至少为一个
- float count = 1;
- char [] stringArr = s.toCharArray();
- for (int i = 0; i < stringArr.length - 1; i++) {
- if (stringArr[i] != stringArr[i + 1]) {
- count++;
- }
- }
- System.out.println(s.length() / count);
- }
- }
-
-}
-```
\ No newline at end of file
diff --git "a/docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md" "b/docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md"
deleted file mode 100644
index 45ea008..0000000
--- "a/docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\345\255\220\347\254\246\344\270\262\347\256\227\346\263\225\351\242\230.md"
+++ /dev/null
@@ -1,467 +0,0 @@
-[toc]
-
-
-## 说明
-
-- 本文作者:wwwxmu
-- 原文地址:https://www.weiweiblog.cn/13string/
-- 作者的博客站点:https://www.weiweiblog.cn/ (推荐哦!)
-
-考虑到篇幅问题,我会分两次更新这个内容。本篇文章只是原文的一部分,我在原文的基础上增加了部分内容以及修改了部分代码和注释。另外,我增加了爱奇艺 2018 秋招 Java:`求给定合法括号序列的深度` 这道题。所有代码均编译成功,并带有注释,欢迎各位享用!
-
-## 1. KMP 算法
-
-谈到字符串问题,不得不提的就是 KMP 算法,它是用来解决字符串查找的问题,可以在一个字符串(S)中查找一个子串(W)出现的位置。KMP 算法把字符匹配的时间复杂度缩小到 O(m+n) ,而空间复杂度也只有O(m)。因为“暴力搜索”的方法会反复回溯主串,导致效率低下,而KMP算法可以利用已经部分匹配这个有效信息,保持主串上的指针不回溯,通过修改子串的指针,让模式串尽量地移动到有效的位置。
-
-具体算法细节请参考:
-
-- **字符串匹配的KMP算法:** http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
-- **从头到尾彻底理解KMP:** https://blog.csdn.net/v_july_v/article/details/7041827
-- **如何更好的理解和掌握 KMP 算法?:** https://www.zhihu.com/question/21923021
-- **KMP 算法详细解析:** https://blog.sengxian.com/algorithms/kmp
-- **图解 KMP 算法:** http://blog.jobbole.com/76611/
-- **汪都能听懂的KMP字符串匹配算法【双语字幕】:** https://www.bilibili.com/video/av3246487/?from=search&seid=17173603269940723925
-- **KMP字符串匹配算法1:** https://www.bilibili.com/video/av11866460?from=search&seid=12730654434238709250
-
-**除此之外,再来了解一下BM算法!**
-
-> BM算法也是一种精确字符串匹配算法,它采用从右向左比较的方法,同时应用到了两种启发式规则,即坏字符规则 和好后缀规则 ,来决定向右跳跃的距离。基本思路就是从右往左进行字符匹配,遇到不匹配的字符后从坏字符表和好后缀表找一个最大的右移值,将模式串右移继续匹配。
-《字符串匹配的KMP算法》:http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
-
-
-## 2. 替换空格
-
-> 剑指offer:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
-
-这里我提供了两种方法:①常规方法;②利用 API 解决。
-
-```java
-//https://www.weiweiblog.cn/replacespace/
-public class Solution {
-
- /**
- * 第一种方法:常规方法。利用String.charAt(i)以及String.valueOf(char).equals(" "
- * )遍历字符串并判断元素是否为空格。是则替换为"%20",否则不替换
- */
- public static String replaceSpace(StringBuffer str) {
-
- int length = str.length();
- // System.out.println("length=" + length);
- StringBuffer result = new StringBuffer();
- for (int i = 0; i < length; i++) {
- char b = str.charAt(i);
- if (String.valueOf(b).equals(" ")) {
- result.append("%20");
- } else {
- result.append(b);
- }
- }
- return result.toString();
-
- }
-
- /**
- * 第二种方法:利用API替换掉所用空格,一行代码解决问题
- */
- public static String replaceSpace2(StringBuffer str) {
-
- return str.toString().replaceAll("\\s", "%20");
- }
-}
-
-```
-
-## 3. 最长公共前缀
-
-> Leetcode: 编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""。
-
-示例 1:
-
-```
-输入: ["flower","flow","flight"]
-输出: "fl"
-```
-
-示例 2:
-
-```
-输入: ["dog","racecar","car"]
-输出: ""
-解释: 输入不存在公共前缀。
-```
-
-
-思路很简单!先利用Arrays.sort(strs)为数组排序,再将数组第一个元素和最后一个元素的字符从前往后对比即可!
-
-```java
-public class Main {
- public static String replaceSpace(String[] strs) {
-
- // 如果检查值不合法及就返回空串
- if (!checkStrs(strs)) {
- return "";
- }
- // 数组长度
- int len = strs.length;
- // 用于保存结果
- StringBuilder res = new StringBuilder();
- // 给字符串数组的元素按照升序排序(包含数字的话,数字会排在前面)
- Arrays.sort(strs);
- int m = strs[0].length();
- int n = strs[len - 1].length();
- int num = Math.min(m, n);
- for (int i = 0; i < num; i++) {
- if (strs[0].charAt(i) == strs[len - 1].charAt(i)) {
- res.append(strs[0].charAt(i));
- } else
- break;
-
- }
- return res.toString();
-
- }
-
- private static boolean chechStrs(String[] strs) {
- boolean flag = false;
- if (strs != null) {
- // 遍历strs检查元素值
- for (int i = 0; i < strs.length; i++) {
- if (strs[i] != null && strs[i].length() != 0) {
- flag = true;
- } else {
- flag = false;
- break;
- }
- }
- }
- return flag;
- }
-
- // 测试
- public static void main(String[] args) {
- String[] strs = { "customer", "car", "cat" };
- // String[] strs = { "customer", "car", null };//空串
- // String[] strs = {};//空串
- // String[] strs = null;//空串
- System.out.println(Main.replaceSpace(strs));// c
- }
-}
-
-```
-
-## 4. 回文串
-
-### 4.1. 最长回文串
-
-> LeetCode: 给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。在构造过程中,请注意区分大小写。比如`"Aa"`不能当做一个回文字符串。注
-意:假设字符串的长度不会超过 1010。
-
-
-
-> 回文串:“回文串”是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。——百度百科 地址:https://baike.baidu.com/item/%E5%9B%9E%E6%96%87%E4%B8%B2/1274921?fr=aladdin
-
-示例 1:
-
-```
-输入:
-"abccccdd"
-
-输出:
-7
-
-解释:
-我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。
-```
-
-我们上面已经知道了什么是回文串?现在我们考虑一下可以构成回文串的两种情况:
-
-- 字符出现次数为双数的组合
-- 字符出现次数为双数的组合+一个只出现一次的字符
-
-统计字符出现的次数即可,双数才能构成回文。因为允许中间一个数单独出现,比如“abcba”,所以如果最后有字母落单,总长度可以加 1。首先将字符串转变为字符数组。然后遍历该数组,判断对应字符是否在hashset中,如果不在就加进去,如果在就让count++,然后移除该字符!这样就能找到出现次数为双数的字符个数。
-
-```java
-//https://leetcode-cn.com/problems/longest-palindrome/description/
-class Solution {
- public int longestPalindrome(String s) {
- if (s.length() == 0)
- return 0;
- // 用于存放字符
- HashSet hashset = new HashSet();
- char[] chars = s.toCharArray();
- int count = 0;
- for (int i = 0; i < chars.length; i++) {
- if (!hashset.contains(chars[i])) {// 如果hashset没有该字符就保存进去
- hashset.add(chars[i]);
- } else {// 如果有,就让count++(说明找到了一个成对的字符),然后把该字符移除
- hashset.remove(chars[i]);
- count++;
- }
- }
- return hashset.isEmpty() ? count * 2 : count * 2 + 1;
- }
-}
-```
-
-
-### 4.2. 验证回文串
-
-> LeetCode: 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。 说明:本题中,我们将空字符串定义为有效的回文串。
-
-示例 1:
-
-```
-输入: "A man, a plan, a canal: Panama"
-输出: true
-```
-
-示例 2:
-
-```
-输入: "race a car"
-输出: false
-```
-
-```java
-//https://leetcode-cn.com/problems/valid-palindrome/description/
-class Solution {
- public boolean isPalindrome(String s) {
- if (s.length() == 0)
- return true;
- int l = 0, r = s.length() - 1;
- while (l < r) {
- // 从头和尾开始向中间遍历
- if (!Character.isLetterOrDigit(s.charAt(l))) {// 字符不是字母和数字的情况
- l++;
- } else if (!Character.isLetterOrDigit(s.charAt(r))) {// 字符不是字母和数字的情况
- r--;
- } else {
- // 判断二者是否相等
- if (Character.toLowerCase(s.charAt(l)) != Character.toLowerCase(s.charAt(r)))
- return false;
- l++;
- r--;
- }
- }
- return true;
- }
-}
-```
-
-
-### 4.3. 最长回文子串
-
-> Leetcode: LeetCode: 最长回文子串 给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为1000。
-
-示例 1:
-
-```
-输入: "babad"
-输出: "bab"
-注意: "aba"也是一个有效答案。
-```
-
-示例 2:
-
-```
-输入: "cbbd"
-输出: "bb"
-```
-
-以某个元素为中心,分别计算偶数长度的回文最大长度和奇数长度的回文最大长度。给大家大致花了个草图,不要嫌弃!
-
-
-
-
-```java
-//https://leetcode-cn.com/problems/longest-palindromic-substring/description/
-class Solution {
- private int index, len;
-
- public String longestPalindrome(String s) {
- if (s.length() < 2)
- return s;
- for (int i = 0; i < s.length() - 1; i++) {
- PalindromeHelper(s, i, i);
- PalindromeHelper(s, i, i + 1);
- }
- return s.substring(index, index + len);
- }
-
- public void PalindromeHelper(String s, int l, int r) {
- while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) {
- l--;
- r++;
- }
- if (len < r - l - 1) {
- index = l + 1;
- len = r - l - 1;
- }
- }
-}
-```
-
-### 4.4. 最长回文子序列
-
-> LeetCode: 最长回文子序列
-给定一个字符串s,找到其中最长的回文子序列。可以假设s的最大长度为1000。
-**最长回文子序列和上一题最长回文子串的区别是,子串是字符串中连续的一个序列,而子序列是字符串中保持相对位置的字符序列,例如,"bbbb"可以是字符串"bbbab"的子序列但不是子串。**
-
-给定一个字符串s,找到其中最长的回文子序列。可以假设s的最大长度为1000。
-
-示例 1:
-
-```
-输入:
-"bbbab"
-输出:
-4
-```
-一个可能的最长回文子序列为 "bbbb"。
-
-示例 2:
-
-```
-输入:
-"cbbd"
-输出:
-2
-```
-
-一个可能的最长回文子序列为 "bb"。
-
-**动态规划:** dp[i][j] = dp[i+1][j-1] + 2 if s.charAt(i) == s.charAt(j) otherwise, dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1])
-
-```java
-class Solution {
- public int longestPalindromeSubseq(String s) {
- int len = s.length();
- int [][] dp = new int[len][len];
- for(int i = len - 1; i>=0; i--){
- dp[i][i] = 1;
- for(int j = i+1; j < len; j++){
- if(s.charAt(i) == s.charAt(j))
- dp[i][j] = dp[i+1][j-1] + 2;
- else
- dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]);
- }
- }
- return dp[0][len-1];
- }
-}
-```
-
-## 5. 括号匹配深度
-
-> 爱奇艺 2018 秋招 Java:
->一个合法的括号匹配序列有以下定义:
->1. 空串""是一个合法的括号匹配序列
->2. 如果"X"和"Y"都是合法的括号匹配序列,"XY"也是一个合法的括号匹配序列
->3. 如果"X"是一个合法的括号匹配序列,那么"(X)"也是一个合法的括号匹配序列
->4. 每个合法的括号序列都可以由以上规则生成。
-
-> 例如: "","()","()()","((()))"都是合法的括号序列
->对于一个合法的括号序列我们又有以下定义它的深度:
->1. 空串""的深度是0
->2. 如果字符串"X"的深度是x,字符串"Y"的深度是y,那么字符串"XY"的深度为max(x,y)
->3. 如果"X"的深度是x,那么字符串"(X)"的深度是x+1
-
-> 例如: "()()()"的深度是1,"((()))"的深度是3。牛牛现在给你一个合法的括号序列,需要你计算出其深度。
-
-```
-输入描述:
-输入包括一个合法的括号序列s,s长度length(2 ≤ length ≤ 50),序列中只包含'('和')'。
-
-输出描述:
-输出一个正整数,即这个序列的深度。
-```
-
-示例:
-
-```
-输入:
-(())
-输出:
-2
-```
-
-思路草图:
-
-
-
-
-代码如下:
-
-```java
-import java.util.Scanner;
-
-/**
- * https://www.nowcoder.com/test/8246651/summary
- *
- * @author Snailclimb
- * @date 2018年9月6日
- * @Description: TODO 求给定合法括号序列的深度
- */
-public class Main {
- public static void main(String[] args) {
- Scanner sc = new Scanner(System.in);
- String s = sc.nextLine();
- int cnt = 0, max = 0, i;
- for (i = 0; i < s.length(); ++i) {
- if (s.charAt(i) == '(')
- cnt++;
- else
- cnt--;
- max = Math.max(max, cnt);
- }
- sc.close();
- System.out.println(max);
- }
-}
-
-```
-
-## 6. 把字符串转换成整数
-
-> 剑指offer: 将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。
-
-```java
-//https://www.weiweiblog.cn/strtoint/
-public class Main {
-
- public static int StrToInt(String str) {
- if (str.length() == 0)
- return 0;
- char[] chars = str.toCharArray();
- // 判断是否存在符号位
- int flag = 0;
- if (chars[0] == '+')
- flag = 1;
- else if (chars[0] == '-')
- flag = 2;
- int start = flag > 0 ? 1 : 0;
- int res = 0;// 保存结果
- for (int i = start; i < chars.length; i++) {
- if (Character.isDigit(chars[i])) {// 调用Character.isDigit(char)方法判断是否是数字,是返回True,否则False
- int temp = chars[i] - '0';
- res = res * 10 + temp;
- } else {
- return 0;
- }
- }
- return flag != 2 ? res : -res;
-
- }
-
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- String s = "-12312312";
- System.out.println("使用库函数转换:" + Integer.valueOf(s));
- int res = Main.StrToInt(s);
- System.out.println("使用自己写的方法转换:" + res);
-
- }
-
-}
-
-```
diff --git "a/docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\351\223\276\350\241\250\347\256\227\346\263\225\351\242\230.md" "b/docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\351\223\276\350\241\250\347\256\227\346\263\225\351\242\230.md"
deleted file mode 100644
index 85e2934..0000000
--- "a/docs/dataStructures-algorithms/\345\207\240\351\201\223\345\270\270\350\247\201\347\232\204\351\223\276\350\241\250\347\256\227\346\263\225\351\242\230.md"
+++ /dev/null
@@ -1,421 +0,0 @@
-
-
-- [1. 两数相加](#1-两数相加)
- - [题目描述](#题目描述)
- - [问题分析](#问题分析)
- - [Solution](#solution)
-- [2. 翻转链表](#2-翻转链表)
- - [题目描述](#题目描述-1)
- - [问题分析](#问题分析-1)
- - [Solution](#solution-1)
-- [3. 链表中倒数第k个节点](#3-链表中倒数第k个节点)
- - [题目描述](#题目描述-2)
- - [问题分析](#问题分析-2)
- - [Solution](#solution-2)
-- [4. 删除链表的倒数第N个节点](#4-删除链表的倒数第n个节点)
- - [问题分析](#问题分析-3)
- - [Solution](#solution-3)
-- [5. 合并两个排序的链表](#5-合并两个排序的链表)
- - [题目描述](#题目描述-3)
- - [问题分析](#问题分析-4)
- - [Solution](#solution-4)
-
-
-
-
-# 1. 两数相加
-
-### 题目描述
-
-> Leetcode:给定两个非空链表来表示两个非负整数。位数按照逆序方式存储,它们的每个节点只存储单个数字。将两数相加返回一个新的链表。
->
->你可以假设除了数字 0 之外,这两个数字都不会以零开头。
-
-示例:
-
-```
-输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
-输出:7 -> 0 -> 8
-原因:342 + 465 = 807
-```
-
-### 问题分析
-
-Leetcode官方详细解答地址:
-
- https://leetcode-cn.com/problems/add-two-numbers/solution/
-
-> 要对头结点进行操作时,考虑创建哑节点dummy,使用dummy->next表示真正的头节点。这样可以避免处理头节点为空的边界问题。
-
-我们使用变量来跟踪进位,并从包含最低有效位的表头开始模拟逐
-位相加的过程。
-
-
-
-### Solution
-
-**我们首先从最低有效位也就是列表 l1和 l2 的表头开始相加。注意需要考虑到进位的情况!**
-
-```java
-/**
- * Definition for singly-linked list.
- * public class ListNode {
- * int val;
- * ListNode next;
- * ListNode(int x) { val = x; }
- * }
- */
- //https://leetcode-cn.com/problems/add-two-numbers/description/
-class Solution {
-public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
- ListNode dummyHead = new ListNode(0);
- ListNode p = l1, q = l2, curr = dummyHead;
- //carry 表示进位数
- int carry = 0;
- while (p != null || q != null) {
- int x = (p != null) ? p.val : 0;
- int y = (q != null) ? q.val : 0;
- int sum = carry + x + y;
- //进位数
- carry = sum / 10;
- //新节点的数值为sum % 10
- curr.next = new ListNode(sum % 10);
- curr = curr.next;
- if (p != null) p = p.next;
- if (q != null) q = q.next;
- }
- if (carry > 0) {
- curr.next = new ListNode(carry);
- }
- return dummyHead.next;
-}
-}
-```
-
-# 2. 翻转链表
-
-
-### 题目描述
-> 剑指 offer:输入一个链表,反转链表后,输出链表的所有元素。
-
-
-
-### 问题分析
-
-这道算法题,说直白点就是:如何让后一个节点指向前一个节点!在下面的代码中定义了一个 next 节点,该节点主要是保存要反转到头的那个节点,防止链表 “断裂”。
-
-### Solution
-
-
-```java
-public class ListNode {
- int val;
- ListNode next = null;
-
- ListNode(int val) {
- this.val = val;
- }
-}
-```
-
-```java
-/**
- *
- * @author Snailclimb
- * @date 2018年9月19日
- * @Description: TODO
- */
-public class Solution {
-
- public ListNode ReverseList(ListNode head) {
-
- ListNode next = null;
- ListNode pre = null;
-
- while (head != null) {
- // 保存要反转到头的那个节点
- next = head.next;
- // 要反转的那个节点指向已经反转的上一个节点(备注:第一次反转的时候会指向null)
- head.next = pre;
- // 上一个已经反转到头部的节点
- pre = head;
- // 一直向链表尾走
- head = next;
- }
- return pre;
- }
-
-}
-```
-
-测试方法:
-
-```java
- public static void main(String[] args) {
-
- ListNode a = new ListNode(1);
- ListNode b = new ListNode(2);
- ListNode c = new ListNode(3);
- ListNode d = new ListNode(4);
- ListNode e = new ListNode(5);
- a.next = b;
- b.next = c;
- c.next = d;
- d.next = e;
- new Solution().ReverseList(a);
- while (e != null) {
- System.out.println(e.val);
- e = e.next;
- }
- }
-```
-
-输出:
-
-```
-5
-4
-3
-2
-1
-```
-
-# 3. 链表中倒数第k个节点
-
-### 题目描述
-
-> 剑指offer: 输入一个链表,输出该链表中倒数第k个结点。
-
-### 问题分析
-
-> **链表中倒数第k个节点也就是正数第(L-K+1)个节点,知道了只一点,这一题基本就没问题!**
-
-首先两个节点/指针,一个节点 node1 先开始跑,指针 node1 跑到 k-1 个节点后,另一个节点 node2 开始跑,当 node1 跑到最后时,node2 所指的节点就是倒数第k个节点也就是正数第(L-K+1)个节点。
-
-
-### Solution
-
-```java
-/*
-public class ListNode {
- int val;
- ListNode next = null;
-
- ListNode(int val) {
- this.val = val;
- }
-}*/
-
-// 时间复杂度O(n),一次遍历即可
-// https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
-public class Solution {
- public ListNode FindKthToTail(ListNode head, int k) {
- // 如果链表为空或者k小于等于0
- if (head == null || k <= 0) {
- return null;
- }
- // 声明两个指向头结点的节点
- ListNode node1 = head, node2 = head;
- // 记录节点的个数
- int count = 0;
- // 记录k值,后面要使用
- int index = k;
- // p指针先跑,并且记录节点数,当node1节点跑了k-1个节点后,node2节点开始跑,
- // 当node1节点跑到最后时,node2节点所指的节点就是倒数第k个节点
- while (node1 != null) {
- node1 = node1.next;
- count++;
- if (k < 1) {
- node2 = node2.next;
- }
- k--;
- }
- // 如果节点个数小于所求的倒数第k个节点,则返回空
- if (count < index)
- return null;
- return node2;
-
- }
-}
-```
-
-
-# 4. 删除链表的倒数第N个节点
-
-
-> Leetcode:给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
-
-**示例:**
-
-```
-给定一个链表: 1->2->3->4->5, 和 n = 2.
-
-当删除了倒数第二个节点后,链表变为 1->2->3->5.
-
-```
-
-**说明:**
-
-给定的 n 保证是有效的。
-
-**进阶:**
-
-你能尝试使用一趟扫描实现吗?
-
-该题在 leetcode 上有详细解答,具体可参考 Leetcode.
-
-### 问题分析
-
-
-我们注意到这个问题可以容易地简化成另一个问题:删除从列表开头数起的第 (L - n + 1)个结点,其中 L是列表的长度。只要我们找到列表的长度 L,这个问题就很容易解决。
-
-
-
-### Solution
-
-**两次遍历法**
-
-首先我们将添加一个 **哑结点** 作为辅助,该结点位于列表头部。哑结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部。在第一次遍历中,我们找出列表的长度 L。然后设置一个指向哑结点的指针,并移动它遍历列表,直至它到达第 (L - n) 个结点那里。**我们把第 (L - n)个结点的 next 指针重新链接至第 (L - n + 2)个结点,完成这个算法。**
-
-```java
-/**
- * Definition for singly-linked list.
- * public class ListNode {
- * int val;
- * ListNode next;
- * ListNode(int x) { val = x; }
- * }
- */
-// https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/description/
-public class Solution {
- public ListNode removeNthFromEnd(ListNode head, int n) {
- // 哑结点,哑结点用来简化某些极端情况,例如列表中只含有一个结点,或需要删除列表的头部
- ListNode dummy = new ListNode(0);
- // 哑结点指向头结点
- dummy.next = head;
- // 保存链表长度
- int length = 0;
- ListNode len = head;
- while (len != null) {
- length++;
- len = len.next;
- }
- length = length - n;
- ListNode target = dummy;
- // 找到 L-n 位置的节点
- while (length > 0) {
- target = target.next;
- length--;
- }
- // 把第 (L - n)个结点的 next 指针重新链接至第 (L - n + 2)个结点
- target.next = target.next.next;
- return dummy.next;
- }
-}
-```
-
-**复杂度分析:**
-
-- **时间复杂度 O(L)** :该算法对列表进行了两次遍历,首先计算了列表的长度 LL 其次找到第 (L - n)(L−n) 个结点。 操作执行了 2L-n2L−n 步,时间复杂度为 O(L)O(L)。
-- **空间复杂度 O(1)** :我们只用了常量级的额外空间。
-
-
-
-**进阶——一次遍历法:**
-
-
-> **链表中倒数第N个节点也就是正数第(L-N+1)个节点。
-
-其实这种方法就和我们上面第四题找“链表中倒数第k个节点”所用的思想是一样的。**基本思路就是:** 定义两个节点 node1、node2;node1 节点先跑,node1节点 跑到第 n+1 个节点的时候,node2 节点开始跑.当node1 节点跑到最后一个节点时,node2 节点所在的位置就是第 (L-n ) 个节点(L代表总链表长度,也就是倒数第 n+1 个节点)
-
-```java
-/**
- * Definition for singly-linked list.
- * public class ListNode {
- * int val;
- * ListNode next;
- * ListNode(int x) { val = x; }
- * }
- */
-public class Solution {
- public ListNode removeNthFromEnd(ListNode head, int n) {
-
- ListNode dummy = new ListNode(0);
- dummy.next = head;
- // 声明两个指向头结点的节点
- ListNode node1 = dummy, node2 = dummy;
-
- // node1 节点先跑,node1节点 跑到第 n 个节点的时候,node2 节点开始跑
- // 当node1 节点跑到最后一个节点时,node2 节点所在的位置就是第 (L-n ) 个节点,也就是倒数第 n+1(L代表总链表长度)
- while (node1 != null) {
- node1 = node1.next;
- if (n < 1 && node1 != null) {
- node2 = node2.next;
- }
- n--;
- }
-
- node2.next = node2.next.next;
-
- return dummy.next;
-
- }
-}
-```
-
-
-
-
-
-# 5. 合并两个排序的链表
-
-### 题目描述
-
-> 剑指offer:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
-
-### 问题分析
-
-我们可以这样分析:
-
-1. 假设我们有两个链表 A,B;
-2. A的头节点A1的值与B的头结点B1的值比较,假设A1小,则A1为头节点;
-3. A2再和B1比较,假设B1小,则,A1指向B1;
-4. A2再和B2比较
-就这样循环往复就行了,应该还算好理解。
-
-考虑通过递归的方式实现!
-
-### Solution
-
-**递归版本:**
-
-```java
-/*
-public class ListNode {
- int val;
- ListNode next = null;
-
- ListNode(int val) {
- this.val = val;
- }
-}*/
-//https://www.nowcoder.com/practice/d8b6b4358f774294a89de2a6ac4d9337?tpId=13&tqId=11169&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
-public class Solution {
-public ListNode Merge(ListNode list1,ListNode list2) {
- if(list1 == null){
- return list2;
- }
- if(list2 == null){
- return list1;
- }
- if(list1.val <= list2.val){
- list1.next = Merge(list1.next, list2);
- return list1;
- }else{
- list2.next = Merge(list1, list2.next);
- return list2;
- }
- }
-}
-```
-
diff --git "a/docs/dataStructures-algorithms/\345\211\221\346\214\207offer-Java\345\256\236\347\216\260\347\211\210\346\234\254.md" "b/docs/dataStructures-algorithms/\345\211\221\346\214\207offer-Java\345\256\236\347\216\260\347\211\210\346\234\254.md"
new file mode 100644
index 0000000..256f99f
--- /dev/null
+++ "b/docs/dataStructures-algorithms/\345\211\221\346\214\207offer-Java\345\256\236\347\216\260\347\211\210\346\234\254.md"
@@ -0,0 +1,3 @@
+https://blog.csdn.net/weixin_43774841/article/details/112912070
+https://zhuanlan.zhihu.com/p/84481303
+https://zhuanlan.zhihu.com/p/84481166
\ No newline at end of file
diff --git "a/docs/dataStructures-algorithms/\345\211\221\346\214\207offer\351\203\250\345\210\206\347\274\226\347\250\213\351\242\230.md" "b/docs/dataStructures-algorithms/\345\211\221\346\214\207offer\351\203\250\345\210\206\347\274\226\347\250\213\351\242\230.md"
deleted file mode 100644
index 51de35e..0000000
--- "a/docs/dataStructures-algorithms/\345\211\221\346\214\207offer\351\203\250\345\210\206\347\274\226\347\250\213\351\242\230.md"
+++ /dev/null
@@ -1,686 +0,0 @@
-### 一 斐波那契数列
-
-#### **题目描述:**
-
-大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。
-n<=39
-
-#### **问题分析:**
-
-可以肯定的是这一题通过递归的方式是肯定能做出来,但是这样会有一个很大的问题,那就是递归大量的重复计算会导致内存溢出。另外可以使用迭代法,用fn1和fn2保存计算过程中的结果,并复用起来。下面我会把两个方法示例代码都给出来并给出两个方法的运行时间对比。
-
-#### **示例代码:**
-
-**采用迭代法:**
-
-```java
- int Fibonacci(int number) {
- if (number <= 0) {
- return 0;
- }
- if (number == 1 || number == 2) {
- return 1;
- }
- int first = 1, second = 1, third = 0;
- for (int i = 3; i <= number; i++) {
- third = first + second;
- first = second;
- second = third;
- }
- return third;
- }
-```
-
-**采用递归:**
-
-```java
- public int Fibonacci(int n) {
-
- if (n <= 0) {
- return 0;
- }
- if (n == 1||n==2) {
- return 1;
- }
-
- return Fibonacci(n - 2) + Fibonacci(n - 1);
-
- }
-```
-
-#### **运行时间对比:**
-
-假设n为40我们分别使用迭代法和递归法计算,计算结果如下:
-
-1. 迭代法
- 
-2. 递归法
- 
-
-### 二 跳台阶问题
-
-#### **题目描述:**
-
-一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
-
-#### **问题分析:**
-
-**正常分析法:**
-a.如果两种跳法,1阶或者2阶,那么假定第一次跳的是一阶,那么剩下的是n-1个台阶,跳法是f(n-1);
-b.假定第一次跳的是2阶,那么剩下的是n-2个台阶,跳法是f(n-2)
-c.由a,b假设可以得出总跳法为: f(n) = f(n-1) + f(n-2)
-d.然后通过实际的情况可以得出:只有一阶的时候 f(1) = 1 ,只有两阶的时候可以有 f(2) = 2
-**找规律分析法:**
-f(1) = 1, f(2) = 2, f(3) = 3, f(4) = 5, 可以总结出f(n) = f(n-1) + f(n-2)的规律。
-但是为什么会出现这样的规律呢?假设现在6个台阶,我们可以从第5跳一步到6,这样的话有多少种方案跳到5就有多少种方案跳到6,另外我们也可以从4跳两步跳到6,跳到4有多少种方案的话,就有多少种方案跳到6,其他的不能从3跳到6什么的啦,所以最后就是f(6) = f(5) + f(4);这样子也很好理解变态跳台阶的问题了。
-
-**所以这道题其实就是斐波那契数列的问题。**
-代码只需要在上一题的代码稍做修改即可。和上一题唯一不同的就是这一题的初始元素变为 1 2 3 5 8.....而上一题为1 1 2 3 5 .......。另外这一题也可以用递归做,但是递归效率太低,所以我这里只给出了迭代方式的代码。
-
-#### **示例代码:**
-
-```java
- int jumpFloor(int number) {
- if (number <= 0) {
- return 0;
- }
- if (number == 1) {
- return 1;
- }
- if (number == 2) {
- return 2;
- }
- int first = 1, second = 2, third = 0;
- for (int i = 3; i <= number; i++) {
- third = first + second;
- first = second;
- second = third;
- }
- return third;
- }
-```
-
-### 三 变态跳台阶问题
-
-#### **题目描述:**
-
-一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
-
-#### **问题分析:**
-
-假设n>=2,第一步有n种跳法:跳1级、跳2级、到跳n级
-跳1级,剩下n-1级,则剩下跳法是f(n-1)
-跳2级,剩下n-2级,则剩下跳法是f(n-2)
-......
-跳n-1级,剩下1级,则剩下跳法是f(1)
-跳n级,剩下0级,则剩下跳法是f(0)
-所以在n>=2的情况下:
-f(n)=f(n-1)+f(n-2)+...+f(1)
-因为f(n-1)=f(n-2)+f(n-3)+...+f(1)
-所以f(n)=2*f(n-1) 又f(1)=1,所以可得**f(n)=2^(number-1)**
-
-#### **示例代码:**
-
-```java
- int JumpFloorII(int number) {
- return 1 << --number;//2^(number-1)用位移操作进行,更快
- }
-```
-
-#### **补充:**
-
-**java中有三种移位运算符:**
-
-1. “<<” : **左移运算符**,等同于乘2的n次方
-2. “>>”: **右移运算符**,等同于除2的n次方
-3. “>>>” **无符号右移运算符**,不管移动前最高位是0还是1,右移后左侧产生的空位部分都以0来填充。与>>类似。
- 例:
- int a = 16;
- int b = a << 2;//左移2,等同于16 * 2的2次方,也就是16 * 4
- int c = a >> 2;//右移2,等同于16 / 2的2次方,也就是16 / 4
-
-### 四 二维数组查找
-
-#### **题目描述:**
-
-在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
-
-#### **问题解析:**
-
-这一道题还是比较简单的,我们需要考虑的是如何做,效率最快。这里有一种很好理解的思路:
-
-> 矩阵是有序的,从左下角来看,向上数字递减,向右数字递增,
-> 因此从左下角开始查找,当要查找数字比左下角数字大时。右移
-> 要查找数字比左下角数字小时,上移。这样找的速度最快。
-
-#### **示例代码:**
-
-```java
- public boolean Find(int target, int [][] array) {
- //基本思路从左下角开始找,这样速度最快
- int row = array.length-1;//行
- int column = 0;//列
- //当行数大于0,当前列数小于总列数时循环条件成立
- while((row >= 0)&& (column< array[0].length)){
- if(array[row][column] > target){
- row--;
- }else if(array[row][column] < target){
- column++;
- }else{
- return true;
- }
- }
- return false;
- }
-```
-
-### 五 替换空格
-
-#### **题目描述:**
-
-请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
-
-#### **问题分析:**
-
-这道题不难,我们可以通过循环判断字符串的字符是否为空格,是的话就利用append()方法添加追加“%20”,否则还是追加原字符。
-
-或者最简单的方法就是利用: replaceAll(String regex,String replacement)方法了,一行代码就可以解决。
-
-#### **示例代码:**
-
-**常规做法:**
-
-```java
- public String replaceSpace(StringBuffer str) {
- StringBuffer out=new StringBuffer();
- for (int i = 0; i < str.toString().length(); i++) {
- char b=str.charAt(i);
- if(String.valueOf(b).equals(" ")){
- out.append("%20");
- }else{
- out.append(b);
- }
- }
- return out.toString();
- }
-```
-
-**一行代码解决:**
-
-```java
- public String replaceSpace(StringBuffer str) {
- //return str.toString().replaceAll(" ", "%20");
- //public String replaceAll(String regex,String replacement)
- //用给定的替换替换与给定的regular expression匹配的此字符串的每个子字符串。
- //\ 转义字符. 如果你要使用 "\" 本身, 则应该使用 "\\". String类型中的空格用“\s”表示,所以我这里猜测"\\s"就是代表空格的意思
- return str.toString().replaceAll("\\s", "%20");
- }
-
-```
-
-### 六 数值的整数次方
-
-#### **题目描述:**
-
-给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
-
-#### **问题解析:**
-
-这道题算是比较麻烦和难一点的一个了。我这里采用的是**二分幂**思想,当然也可以采用**快速幂**。
-更具剑指offer书中细节,该题的解题思路如下:
-1.当底数为0且指数<0时,会出现对0求倒数的情况,需进行错误处理,设置一个全局变量;
-2.判断底数是否等于0,由于base为double型,所以不能直接用==判断
-3.优化求幂函数(二分幂)。
-当n为偶数,a^n =(a^n/2)*(a^n/2);
-当n为奇数,a^n = a^[(n-1)/2] * a^[(n-1)/2] * a。时间复杂度O(logn)
-
-**时间复杂度**:O(logn)
-
-#### **示例代码:**
-
-```java
-public class Solution {
- boolean invalidInput=false;
- public double Power(double base, int exponent) {
- //如果底数等于0并且指数小于0
- //由于base为double型,不能直接用==判断
- if(equal(base,0.0)&&exponent<0){
- invalidInput=true;
- return 0.0;
- }
- int absexponent=exponent;
- //如果指数小于0,将指数转正
- if(exponent<0)
- absexponent=-exponent;
- //getPower方法求出base的exponent次方。
- double res=getPower(base,absexponent);
- //如果指数小于0,所得结果为上面求的结果的倒数
- if(exponent<0)
- res=1.0/res;
- return res;
- }
- //比较两个double型变量是否相等的方法
- boolean equal(double num1,double num2){
- if(num1-num2>-0.000001&&num1-num2<0.000001)
- return true;
- else
- return false;
- }
- //求出b的e次方的方法
- double getPower(double b,int e){
- //如果指数为0,返回1
- if(e==0)
- return 1.0;
- //如果指数为1,返回b
- if(e==1)
- return b;
- //e>>1相等于e/2,这里就是求a^n =(a^n/2)*(a^n/2)
- double result=getPower(b,e>>1);
- result*=result;
- //如果指数n为奇数,则要再乘一次底数base
- if((e&1)==1)
- result*=b;
- return result;
- }
-}
-```
-
-当然这一题也可以采用笨方法:累乘。不过这种方法的时间复杂度为O(n),这样没有前一种方法效率高。
-
-```java
- // 使用累乘
- public double powerAnother(double base, int exponent) {
- double result = 1.0;
- for (int i = 0; i < Math.abs(exponent); i++) {
- result *= base;
- }
- if (exponent >= 0)
- return result;
- else
- return 1 / result;
- }
-```
-
-### 七 调整数组顺序使奇数位于偶数前面
-
-#### **题目描述:**
-
-输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
-
-#### **问题解析:**
-
-这道题有挺多种解法的,给大家介绍一种我觉得挺好理解的方法:
-我们首先统计奇数的个数假设为n,然后新建一个等长数组,然后通过循环判断原数组中的元素为偶数还是奇数。如果是则从数组下标0的元素开始,把该奇数添加到新数组;如果是偶数则从数组下标为n的元素开始把该偶数添加到新数组中。
-
-#### **示例代码:**
-
-时间复杂度为O(n),空间复杂度为O(n)的算法
-
-```java
-public class Solution {
- public void reOrderArray(int [] array) {
- //如果数组长度等于0或者等于1,什么都不做直接返回
- if(array.length==0||array.length==1)
- return;
- //oddCount:保存奇数个数
- //oddBegin:奇数从数组头部开始添加
- int oddCount=0,oddBegin=0;
- //新建一个数组
- int[] newArray=new int[array.length];
- //计算出(数组中的奇数个数)开始添加元素
- for(int i=0;i stack1 = new Stack();
- Stack stack2 = new Stack();
-
- //当执行push操作时,将元素添加到stack1
- public void push(int node) {
- stack1.push(node);
- }
-
- public int pop() {
- //如果两个队列都为空则抛出异常,说明用户没有push进任何元素
- if(stack1.empty()&&stack2.empty()){
- throw new RuntimeException("Queue is empty!");
- }
- //如果stack2不为空直接对stack2执行pop操作,
- if(stack2.empty()){
- while(!stack1.empty()){
- //将stack1的元素按后进先出push进stack2里面
- stack2.push(stack1.pop());
- }
- }
- return stack2.pop();
- }
-}
-```
-
-### 十二 栈的压入,弹出序列
-
-#### **题目描述:**
-
-输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
-
-#### **题目分析:**
-
-这道题想了半天没有思路,参考了Alias的答案,他的思路写的也很详细应该很容易看懂。
-作者:Alias
-https://www.nowcoder.com/questionTerminal/d77d11405cc7470d82554cb392585106
-来源:牛客网
-
-【思路】借用一个辅助的栈,遍历压栈顺序,先讲第一个放入栈中,这里是1,然后判断栈顶元素是不是出栈顺序的第一个元素,这里是4,很显然1≠4,所以我们继续压栈,直到相等以后开始出栈,出栈一个元素,则将出栈顺序向后移动一位,直到不相等,这样循环等压栈顺序遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。
-
-举例:
-
-入栈1,2,3,4,5
-
-出栈4,5,3,2,1
-
-首先1入辅助栈,此时栈顶1≠4,继续入栈2
-
-此时栈顶2≠4,继续入栈3
-
-此时栈顶3≠4,继续入栈4
-
-此时栈顶4=4,出栈4,弹出序列向后一位,此时为5,,辅助栈里面是1,2,3
-
-此时栈顶3≠5,继续入栈5
-
-此时栈顶5=5,出栈5,弹出序列向后一位,此时为3,,辅助栈里面是1,2,3
-
-….
-依次执行,最后辅助栈为空。如果不为空说明弹出序列不是该栈的弹出顺序。
-
-
-
-#### **考察内容:**
-
-栈
-
-#### **示例代码:**
-
-```java
-import java.util.ArrayList;
-import java.util.Stack;
-//这道题没想出来,参考了Alias同学的答案:https://www.nowcoder.com/questionTerminal/d77d11405cc7470d82554cb392585106
-public class Solution {
- public boolean IsPopOrder(int [] pushA,int [] popA) {
- if(pushA.length == 0 || popA.length == 0)
- return false;
- Stack s = new Stack();
- //用于标识弹出序列的位置
- int popIndex = 0;
- for(int i = 0; i< pushA.length;i++){
- s.push(pushA[i]);
- //如果栈不为空,且栈顶元素等于弹出序列
- while(!s.empty() &&s.peek() == popA[popIndex]){
- //出栈
- s.pop();
- //弹出序列向后一位
- popIndex++;
- }
- }
- return s.empty();
- }
-}
-```
\ No newline at end of file
diff --git "a/docs/dataStructures-algorithms/\345\211\221\346\214\207offer\351\232\276\347\202\271\346\200\273\347\273\223.md" "b/docs/dataStructures-algorithms/\345\211\221\346\214\207offer\351\232\276\347\202\271\346\200\273\347\273\223.md"
new file mode 100644
index 0000000..9c32dde
--- /dev/null
+++ "b/docs/dataStructures-algorithms/\345\211\221\346\214\207offer\351\232\276\347\202\271\346\200\273\347\273\223.md"
@@ -0,0 +1,788 @@
+
+
+
+
+
+- [重构二叉树](#重构二叉树httpswwwnowcodercompractice8a19cbe657394eeaac2f6ea9b0f6fcf6tpid13tqid11157rp1rutacoding-interviewsqrutacoding-interviewsquestion-ranking)
+- [旋转数组的最小数字](#旋转数组的最小数字httpswwwnowcodercompractice9f3231a991af4f55b95579b44b7a01batpid13rp1ru2fta2fcoding-interviewsqru2fta2fcoding-interviews2fquestion-ranking)
+- [跳N级台阶](#跳n级台阶httpswwwnowcodercompractice22243d016f6b47f2a6928b4313c85387tpid13rp1ru2fta2fcoding-interviewsqru2fta2fcoding-interviews2fquestion-ranking)
+- [栈的压入、弹出序列](#栈的压入-弹出序列httpswwwnowcodercompracticed77d11405cc7470d82554cb392585106tpid13rp1ru2fta2fcoding-interviewsqru2fta2fcoding-interviews2fquestion-ranking)
+- [调整数组顺序使奇数位于偶数前面](#调整数组顺序使奇数位于偶数前面httpswwwnowcodercompracticebeb5aa231adc45b2a5dcc5b62c93f593tpid13rp1ru2fta2fcoding-interviewsqru2fta2fcoding-interviews2fquestion-ranking)
+- [树的子结构](#树的子结构httpswwwnowcodercompractice6e196c44c7004d15b1610b9afca8bd88tpid13rp1ru2fta2fcoding-interviewsqru2fta2fcoding-interviews2fquestion-ranking)
+- [二叉搜索树的后序遍历序列](#二叉搜索树的后序遍历序列httpswwwnowcodercompracticea861533d45854474ac791d90e447bafdtpid13rp1ru2fta2fcoding-interviewsqru2fta2fcoding-interviews2fquestion-ranking)
+- [二叉树中和为某一值的路径](#二叉树中和为某一值的路径httpswwwnowcodercompracticeb736e784e3e34731af99065031301bcatpid13rp1ru2fta2fcoding-interviewsqru2fta2fcoding-interviews2fquestion-ranking)
+- [二叉搜索树与双向链表](#二叉搜索树与双向链表httpswwwnowcodercompractice947f6eb80d944a84850b0538bf0ec3a5tpid13tqid11179rp1rutacoding-interviewsqrutacoding-interviewsquestion-ranking)
+- [最小的k个数(partation)](#最小的k个数partationhttpswwwnowcodercompractice6a296eb82cf844ca8539b57c23e6e9bftpid13tqid11182rp1rutacoding-interviewsqrutacoding-interviewsquestion-ranking)
+- [连续子数组的最大和(sum < 0置为0)](#连续子数组的最大和sum-0置为0httpswwwnowcodercompractice459bd355da1549fa8a49e350bf3df484tpid13tqid11183rp1rutacoding-interviewsqrutacoding-interviewsquestion-ranking)
+- [整数中1出现的次数(从1到n整数中1出现的次数)](#整数中1出现的次数从1到n整数中1出现的次数httpswwwnowcodercompracticebd7f978302044eee894445e244c7eee6tpid13tqid11184rp1rutacoding-interviewsqrutacoding-interviewsquestion-ranking)
+- [数组中的逆序对](#数组中的逆序对httpswwwnowcodercompractice96bd6684e04a44eb80e6a68efc0ec6c5tpid13tqid11188rp1rutacoding-interviewsqrutacoding-interviewsquestion-ranking)
+- [两个链表的第一个公共结点](#两个链表的第一个公共结点httpswwwnowcodercompractice6ab1d9a29e88450685099d45c9e31e46tpid13tqid11189rp1rutacoding-interviewsqrutacoding-interviewsquestion-ranking)
+- [数字在排序数组中出现的次数](#数字在排序数组中出现的次数httpswwwnowcodercompractice70610bf967994b22bb1c26f9ae901fa2tpid13tqid11190rp1rutacoding-interviewsqrutacoding-interviewsquestion-ranking)
+- [和为S的连续正数序列](#和为s的连续正数序列httpswwwnowcodercompracticec451a3fd84b64cb19485dad758a55ebetpid13tqid11194rp1rutacoding-interviewsqrutacoding-interviewsquestion-ranking)
+- [孩子们的游戏(圆圈中剩下的数)](#孩子们的游戏圆圈中剩下的数httpswwwnowcodercompracticef78a359491e64a50bce2d89cff857eb6tpid13tqid11199rp1rutacoding-interviewsqrutacoding-interviewsquestion-ranking)
+- [不用加减乘除做加法](#不用加减乘除做加法httpswwwnowcodercompractice59ac416b4b944300b617d4f7f111b215tpid13tqid11201rp1rutacoding-interviewsqrutacoding-interviewsquestion-ranking)
+- [数组中重复的数字](#数组中重复的数字httpswwwnowcodercompractice623a5ac0ea5b4e5f95552655361ae0a8tpid13tqid11203rp1rutacoding-interviewsqrutacoding-interviewsquestion-ranking)
+- [正则表达式匹配](#正则表达式匹配)
+- [表示数值的字符串](#表示数值的字符串)
+- [删除链表中重复的结点](#删除链表中重复的结点)
+- [二叉树的下一个结点](#二叉树的下一个结点)
+- [对称的二叉树(序列化)](#对称的二叉树序列化)
+
+
+
+
+### [重构二叉树](https://www.nowcoder.com/practice/8a19cbe657394eeaac2f6ea9b0f6fcf6?tpId=13&&tqId=11157&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+/**
+ * Definition for binary tree
+ * public class TreeNode {
+ * int val;
+ * TreeNode left;
+ * TreeNode right;
+ * TreeNode(int x) { val = x; }
+ * }
+ */
+import java.util.*;
+public class Solution {
+ public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
+ if(pre.length == 0||in.length == 0){
+ return null;
+ }
+
+ // 前序遍历的第一个节点是根结点
+ TreeNode node = new TreeNode(pre[0]);
+ for(int i = 0; i < in.length; i++){
+ // 如果找到中序遍历和根结点一样,
+ // 那么中序遍历的左边是左子树,右边是右子树
+ if(pre[0] == in[i]){
+ node.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i+1),
+ Arrays.copyOfRange(in, 0, i));
+ node.right = reConstructBinaryTree(Arrays.copyOfRange(pre,
+ i+1, pre.length), Arrays.copyOfRange(in, i+1,in.length));
+ }
+ }
+ return node;
+ }
+}
+```
+
+### [旋转数组的最小数字](https://www.nowcoder.com/practice/9f3231a991af4f55b95579b44b7a01ba?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking)
+
+```java
+import java.util.ArrayList;
+
+public class Solution {
+ public int minNumberInRotateArray(int [] array) {
+ int low = 0 ; int high = array.length - 1;
+ while(low < high){
+ int mid = low + (high - low) / 2;
+ // 出现这种情况的array类似[3,4,5,6,0,1,2],
+ // 此时最小数字一定在mid的右边。
+ if(array[mid] > array[high]){
+ low = mid + 1;
+ // 出现这种情况的array类似 [1,0,1,1,1] 或者[1,1,1,0,1],
+ // 此时最小数字不好判断在mid左边还是右边,这时只好一个一个试
+ }else if(array[mid] == array[high]){
+ high = high - 1;
+ // 出现这种情况的array类似[2,2,3,4,5,6,6],
+ // 此时最小数字一定就是array[mid]或者在mid的左边
+ }else{
+ high = mid;
+ }
+ }
+ return array[low];
+ }
+}
+```
+
+### [跳N级台阶](https://www.nowcoder.com/practice/22243d016f6b47f2a6928b4313c85387?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking)
+
+```java
+// 每个台阶都有跳与不跳两种情况(除了最后一个台阶),
+// 最后一个台阶必须跳。所以共用2^(n-1)中情况
+public int JumpFloorII(int target) {
+ return 1 << (target - 1);
+}
+
+public int JumpFloorII(int target) {
+ if(target <= 0){
+ return 0;
+ }
+
+ int arr[] = new int[target + 1];
+ arr[0] = 1;
+ int preSum = arr[0];
+ for(int i = 1 ; i < arr.length ; i++){
+ arr[i] = preSum;
+ preSum += arr[i];
+ }
+
+ return arr[target];
+}
+```
+
+
+### [栈的压入、弹出序列](https://www.nowcoder.com/practice/d77d11405cc7470d82554cb392585106?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking)
+
+```java
+public boolean IsPopOrder(int [] arr1,int [] arr2) {
+ //input check
+ if(arr1 == null || arr2 == null
+ || arr1.length != arr2.length
+ || arr1.length == 0){
+ return false;
+ }
+ Stack stack = new Stack();
+ int length = arr1.length;
+ int i = 0, j = 0;
+ while(i < length && j < length){
+ if(arr1[i] != arr2[j]){
+ // 不相等,压入栈中
+ stack.push(arr1[i++]);
+ }else{
+ // 相等,同时弹出
+ i++;
+ j++;
+ }
+ }
+
+ // 栈中为空时,才满足条件
+ while(j < length){
+ if(arr2[j] != stack.peek()){
+ return false;
+ }else{
+ stack.pop();
+ j++;
+ }
+ }
+
+ return stack.empty() && j == length;
+}
+
+```
+
+### [调整数组顺序使奇数位于偶数前面](https://www.nowcoder.com/practice/beb5aa231adc45b2a5dcc5b62c93f593?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking)
+
+```java
+public void reOrderArray(int [] array) {
+ //相对位置不变,稳定性
+ //插入排序的思想
+ int m = array.length;
+ int k = 0;//记录已经摆好位置的奇数的个数
+ for (int i = 0; i < m; i++) {
+ if (array[i] % 2 == 1) {
+ int j = i;
+ while (j > k) {//j >= k+1
+ int tmp = array[j];
+ array[j] = array[j-1];
+ array[j-1] = tmp;
+ j--;
+ }
+ k++;
+ }
+ }
+}
+
+// 空间换时间
+public class Solution {
+ public void reOrderArray(int [] array) {
+ if (array != null) {
+ int[] even = new int[array.length];
+ int indexOdd = 0;
+ int indexEven = 0;
+ for (int num : array) {
+ if ((num & 1) == 1) {
+ array[indexOdd++] = num;
+ } else {
+ even[indexEven++] = num;
+ }
+ }
+
+ for (int i = 0; i < indexEven; i++) {
+ array[indexOdd + i] = even[i];
+ }
+ }
+ }
+}
+
+```
+
+### [树的子结构](https://www.nowcoder.com/practice/6e196c44c7004d15b1610b9afca8bd88?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking)
+
+```java
+
+// 解法一
+public boolean HasSubtree(TreeNode root1,TreeNode root2) {
+ if(root1==null || root2==null) return false;
+ return doesTree1HasTree2(root1, root2)|| HasSubtree(root1.left, root2)
+ ||HasSubtree(root1.right, root2);
+}
+
+private boolean doesTree1HasTree2(TreeNode root1,TreeNode root2) {
+ if(root2==null) return true;
+ if(root1==null) return false;
+ return root1.val==root2.val && doesTree1HasTree2(root1.left, root2.left)
+ && doesTree1HasTree2(root1.right, root2.right);
+}
+
+// 解法二:先序遍历判断是否是子串
+public boolean HasSubtree(TreeNode root1,TreeNode root2) {
+ if(root2 == null){
+ return false;
+ }
+ String str1 = pre(root1);
+ String str2 = pre(root2);
+ return str1.indexOf(str2) != -1;
+}
+
+public String pre(TreeNode root){
+ if(root == null){
+ return "";
+ }
+ String str = root.val + "";
+ str += pre(root.left);
+ str += pre(root.right);
+ return str;
+}
+```
+
+### [二叉搜索树的后序遍历序列](https://www.nowcoder.com/practice/a861533d45854474ac791d90e447bafd?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking)
+
+BST的后序序列的合法序列是,对于一个序列S,最后一个元素是x (也就是根),如果去掉最后一个元素的序列为T,那么T满足:T可以分成两段,前一段(左子树)小于x,后一段(右子树)大于x,且这两段(子树)都是合法的后序序列。
+```java
+public class Solution {
+ public boolean VerifySquenceOfBST(int [] sequence) {
+ if(sequence.length==0)
+ return false;
+ if(sequence.length==1)
+ return true;
+ return ju(sequence, 0, sequence.length-1);
+
+ }
+
+ public boolean ju(int[] a,int star,int root){
+ if(star>=root)
+ return true;
+ int i = root;
+ //从后面开始找
+ while(i>star&&a[i-1]>a[root])
+ i--;//找到比根小的坐标
+ //从前面开始找 star到i-1应该比根小
+ for(int j = star;ja[root])
+ return false;;
+ return ju(a,star,i-1)&&ju(a, i, root-1);
+ }
+}
+```
+
+### [二叉树中和为某一值的路径](https://www.nowcoder.com/practice/b736e784e3e34731af99065031301bca?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking)
+
+```java
+public class Solution {
+ private ArrayList> listAll =
+ new ArrayList>();
+ private ArrayList list = new ArrayList();
+ public ArrayList> FindPath(TreeNode root,int target) {
+ if(root == null) return listAll;
+ list.add(root.val);
+ target -= root.val;
+ if(target == 0 && root.left == null
+ && root.right == null)
+ listAll.add(new ArrayList(list));
+ FindPath(root.left, target);
+ FindPath(root.right, target);
+ list.remove(list.size()-1);
+ return listAll;
+ }
+}
+```
+
+### [二叉搜索树与双向链表](https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId=13&&tqId=11179&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+// 直接使用中序遍历
+public class Solution {
+ public TreeNode head = null;
+ public TreeNode dummy = null;
+ public TreeNode Convert(TreeNode pRootOfTree) {
+ ConvertSub(pRootOfTree);
+ return dummy;
+ }
+
+ public void ConvertSub(TreeNode root){
+ if(root == null) return;
+ ConvertSub(root.left);
+
+ if(head == null){
+ head = root;
+ dummy = root;
+ } else {
+ head.right = root;
+ root.left = head;
+ head = root;
+ }
+
+ ConvertSub(root.right);
+ }
+}
+```
+
+### [最小的k个数(partation)](https://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf?tpId=13&&tqId=11182&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+// 方法一:最大堆
+public class Solution {
+ public ArrayList GetLeastNumbers_Solution(int[] input, int k) {
+ ArrayList result = new ArrayList();
+ int length = input.length;
+ if(k > length || k == 0){
+ return result;
+ }
+ PriorityQueue maxHeap =
+ new PriorityQueue(k, new Comparator() {
+
+ @Override
+ public int compare(Integer o1, Integer o2) {
+ return o2.compareTo(o1);
+ }
+ });
+ for (int i = 0; i < length; i++) {
+ if (maxHeap.size() != k) {
+ maxHeap.offer(input[i]);
+ } else if (maxHeap.peek() > input[i]) {
+ Integer temp = maxHeap.poll();
+ temp = null;
+ maxHeap.offer(input[i]);
+ }
+ }
+ for (Integer integer : maxHeap) {
+ result.add(integer);
+ }
+ return result;
+ }
+}
+
+// 全排序
+public class Solution {
+ public ArrayList GetLeastNumbers_Solution(int [] input, int k) {
+ ArrayList list = new ArrayList<>();
+ if(k > input.length){
+ return list;
+ }
+ Arrays.sort(input);
+ for(int i = 0; i < k; i++){
+ list.add(input[i]);
+ }
+ return list;
+ }
+}
+```
+
+### [连续子数组的最大和(sum < 0置为0)](https://www.nowcoder.com/practice/459bd355da1549fa8a49e350bf3df484?tpId=13&&tqId=11183&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+
+// dp
+public int FindGreatestSumOfSubArray(int[] array) {
+ if(array.length == 0){
+ return 0;
+ }
+
+ int max = Integer.MIN_VALUE;
+ int[] dp = new int[array.length];
+ for(int i = 0; i < array.length; i++){
+ dp[i] = array[i];
+ }
+ for(int i = 1; i < array.length; i++){
+ dp[i] = Math.max(dp[i-1] + array[i],dp[i]);
+ }
+
+ for(int i = 0; i < dp.length; i++){
+ if(dp[i] > max){
+ max = dp[i];
+ }
+ }
+ return max;
+}
+
+// sum < 0置为0
+public int FindGreatestSumOfSubArray(int[] array) {
+ if(array.length == 0){
+ return 0;
+ }
+
+ int max = Integer.MIN_VALUE;
+ int cur = 0;
+ for(int i = 0; i < array.length; i++){
+ cur += array[i];
+ max = Math.max(max,cur);
+ cur = cur < 0 ? 0 : cur;
+ }
+ return max;
+}
+
+```
+
+### [整数中1出现的次数(从1到n整数中1出现的次数)](https://www.nowcoder.com/practice/bd7f978302044eee894445e244c7eee6?tpId=13&&tqId=11184&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+public int NumberOf1Between1AndN_Solution(int n) {
+ int count=0;
+ StringBuffer s=new StringBuffer();
+ for(int i=1;i= end)
+ return;
+ //计算中间值,注意溢出
+ int mid = start + (end - start)/2;
+
+ //递归分
+ divide(arr,start,mid);
+ divide(arr,mid+1,end);
+
+ //治
+ merge(arr,start,mid,end);
+ }
+
+ private void merge(int[] arr,int start,int mid,int end){
+ int[] temp = new int[end-start+1];
+
+ //存一下变量
+ int i=start,j=mid+1,k=0;
+ //下面就开始两两进行比较,若前面的数大于后面的数,就构成逆序对
+ while(i<=mid && j<=end){
+ //若前面小于后面,直接存进去,并且移动前面数所在的数组的指针即可
+ if(arr[i] <= arr[j]){
+ temp[k++] = arr[i++];
+ }else{
+ temp[k++] = arr[j++];
+ //a[i]>a[j]了,那么这一次,从a[i]开始到a[mid]必定都是大于这个a[j]的,因为此时分治的两边已经是各自有序了
+ cnt = (cnt+mid-i+1)%1000000007;
+ }
+ }
+ //各自还有剩余的没比完,直接赋值即可
+ while(i<=mid)
+ temp[k++] = arr[i++];
+ while(j<=end)
+ temp[k++] = arr[j++];
+ //覆盖原数组
+ for (k = 0; k < temp.length; k++)
+ arr[start + k] = temp[k];
+ }
+}
+```
+
+### [两个链表的第一个公共结点](https://www.nowcoder.com/practice/6ab1d9a29e88450685099d45c9e31e46?tpId=13&&tqId=11189&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
+ if(pHead1 == null || pHead2 == null){
+ return null;
+ }
+
+ ListNode p1 = pHead1;
+ ListNode p2 = pHead2;
+
+ while(p1 != p2){
+ p1 = p1.next;
+ p2 = p2.next;
+ if(p1 != p2){
+ if(p1 == null) p1 = pHead2;
+ if(p2 == null) p2 = pHead1;
+ }
+ }
+
+ return p1;
+
+}
+```
+
+### [数字在排序数组中出现的次数](https://www.nowcoder.com/practice/70610bf967994b22bb1c26f9ae901fa2?tpId=13&&tqId=11190&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+public class Solution {
+ public static int GetNumberOfK(int [] array , int k) {
+ int length = array.length;
+ if(length == 0){
+ return 0;
+ }
+ int lastK = getLastK(array,k);
+ int firstK = getFirstK(array,k);
+ if(firstK != -1 && lastK != -1){
+ return lastK - firstK + 1;
+ }
+ return 0;
+ }
+
+ public static int getLastK(int [] array, int k){
+ int start = 0, end = array.length - 1;
+ int mid = start + (end - start) / 2;
+ while(start <= end){
+ if(array[mid] > k){
+ end = mid - 1;
+ } else if(array[mid] < k){
+ start = mid + 1;
+ } else if(mid + 1 < array.length && array[mid+1] == k){
+ start = mid + 1;
+ }else {
+ return mid;
+ }
+ mid = start + (end - start) / 2;
+ }
+ return -1;
+ }
+
+ public static int getFirstK(int [] array, int k){
+ int start = 0, end = array.length - 1;
+ int mid = start + (end - start) / 2;
+ while(start <= end){
+ if(array[mid] > k){
+ end = mid - 1;
+ } else if(array[mid] < k){
+ start = mid + 1;
+ } else if(mid - 1 >= 0 && array[mid-1] == k){
+ end = mid - 1;
+ }else {
+ return mid;
+ }
+ mid = start + (end - start) / 2;
+ }
+
+ return -1;
+ }
+}
+```
+
+### [和为S的连续正数序列](https://www.nowcoder.com/practice/c451a3fd84b64cb19485dad758a55ebe?tpId=13&&tqId=11194&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+import java.util.ArrayList;
+public class Solution {
+ public ArrayList > FindContinuousSequence(int sum) {
+ ArrayList> list = new ArrayList<>();
+ int left = 1;
+ int right = 2;
+ int res = 0;
+ while(left < right){
+ ArrayList temp = new ArrayList<>();
+ res = 0;
+ int index = left;
+ while(index <= right){
+ //收集临时值
+ temp.add(index);
+ res+=index;
+ index++;
+ }
+
+ if(res < sum){
+ right++;
+ } else if(res > sum){
+ left++;
+ } else {
+ //收集结果
+ list.add(temp);
+ left++;//如果找到一个结果,left右移,继续找其他结果
+ }
+
+ }
+ return list;
+ }
+}
+```
+
+### [孩子们的游戏(圆圈中剩下的数)](https://www.nowcoder.com/practice/f78a359491e64a50bce2d89cff857eb6?tpId=13&&tqId=11199&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+import java.util.*;
+public class Solution {
+ public int LastRemaining_Solution(int n, int m) {
+ LinkedList list = new LinkedList<>();
+ for(int i = 0; i < n; i++){
+ list.add(i);
+ }
+ int bt = 0;
+ while(list.size() > 1){
+ bt = (bt + m - 1) % list.size();
+ list.remove(bt);
+ }
+
+ return list.size() == 1 ? list.get(0) : -1;
+ }
+}
+```
+
+### [不用加减乘除做加法](https://www.nowcoder.com/practice/59ac416b4b944300b617d4f7f111b215?tpId=13&&tqId=11201&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+public class Solution {
+ public int Add(int num1,int num2) {
+ while(num2 != 0){
+ int sum = num1 ^ num2;//不进位求值
+ int carry = (num1 & num2) << 1;//求进位值
+ num1 = sum;
+ num2 = carry;
+ }
+ return num1;
+ }
+}
+```
+
+### [数组中重复的数字](https://www.nowcoder.com/practice/623a5ac0ea5b4e5f95552655361ae0a8?tpId=13&&tqId=11203&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)
+
+```java
+//boolean只占一位,所以还是比较省的
+public boolean duplicate(int numbers[], int length, int[] duplication) {
+ boolean[] k = new boolean[length];
+ for (int i = 0; i < k.length; i++) {
+ if (k[numbers[i]] == true) {
+ duplication[0] = numbers[i];
+ return true;
+ }
+ k[numbers[i]] = true;
+ }
+ return false;
+}
+```
+
+### 正则表达式匹配
+
+```java
+
+```
+
+### 表示数值的字符串
+
+```java
+// 统一回复 .2 在 Java、Python 中都是数字
+public class Solution {
+ public boolean isNumeric(char[] str) {
+ String string = String.valueOf(str);
+ return string.matches("[\\+-]?[0-9]*(\\.[0-9]*)?([eE][\\+-]?[0-9]+)?");
+ }
+}
+```
+
+### 删除链表中重复的结点
+
+```java
+public ListNode deleteDuplication(ListNode pHead){
+ if(pHead == null){
+ return null;
+ }
+ ListNode node = new ListNode(Integer.MIN_VALUE);
+ node.next = pHead;
+ ListNode pre = node, p = pHead;
+ boolean deletedMode = false;
+ while(p != null){
+ if(p.next != null && p.next.val == p.val){
+ p.next = p.next.next;
+ deletedMode = true;
+ }else if(deletedMode){
+ pre.next = p.next;
+ p = pre.next;
+ deletedMode = false;
+ }else{
+ pre = p;
+ p = p.next;
+ }
+ }
+
+ return node.next;
+}
+
+```
+
+### 二叉树的下一个结点
+
+```java
+public TreeLinkNode GetNext(TreeLinkNode pNode){
+ if(pNode == null){
+ return null;
+ }
+ //如果有右子树,后继结点是右子树上最左的结点
+ if(pNode.right != null){
+ TreeLinkNode p = pNode.right;
+ while(p.left != null){
+ p = p.left;
+ }
+ return p;
+ }else{
+ //如果没有右子树,向上查找第一个当前结点是父结点的左孩子的结点
+ TreeLinkNode p = pNode.next;
+ while(p != null && pNode != p.left){
+ pNode = p;
+ p = p.next;
+ }
+
+ if(p != null && pNode == p.left){
+ return p;
+ }
+ return null;
+ }
+}
+
+```
+
+### 对称的二叉树(序列化)
+
+```java
+boolean isSymmetrical(TreeNode pRoot){
+ if(pRoot == null){
+ return true;
+ }
+ StringBuffer str1 = new StringBuffer("");
+ StringBuffer str2 = new StringBuffer("");
+ preOrder(pRoot, str1);
+ preOrder2(pRoot, str2);
+ return str1.toString().equals(str2.toString());
+}
+
+public void preOrder(TreeNode root, StringBuffer str){
+ if(root == null){
+ str.append("#");
+ return;
+ }
+ str.append(String.valueOf(root.val));
+ preOrder(root.left, str);
+ preOrder(root.right, str);
+}
+
+public void preOrder2(TreeNode root, StringBuffer str){
+ if(root == null){
+ str.append("#");
+ return;
+ }
+ str.append(String.valueOf(root.val));
+ preOrder2(root.right, str);
+ preOrder2(root.left, str);
+}
+
+```
\ No newline at end of file
diff --git "a/docs/dataStructures-algorithms/\345\267\246\347\245\236\347\233\264\351\200\232 BAT \347\256\227\346\263\225\345\256\236\347\216\260.md" "b/docs/dataStructures-algorithms/\345\267\246\347\245\236\347\233\264\351\200\232 BAT \347\256\227\346\263\225\345\256\236\347\216\260.md"
new file mode 100644
index 0000000..8c53251
--- /dev/null
+++ "b/docs/dataStructures-algorithms/\345\267\246\347\245\236\347\233\264\351\200\232 BAT \347\256\227\346\263\225\345\256\236\347\216\260.md"
@@ -0,0 +1,4 @@
+https://juejin.cn/post/6844903779289006094
+https://juejin.cn/post/6844903779289022478
+https://juejin.cn/post/6844903779289006093
+https://juejin.cn/post/6844903779289022471
\ No newline at end of file
diff --git "a/docs/dataStructures-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/docs/dataStructures-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md"
deleted file mode 100644
index dfb5bc1..0000000
--- "a/docs/dataStructures-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md"
+++ /dev/null
@@ -1,192 +0,0 @@
-下面只是简单地总结,给了一些参考文章,后面会对这部分内容进行重构。
-
-
-- [Queue](#queue)
- - [什么是队列](#什么是队列)
- - [队列的种类](#队列的种类)
- - [Java 集合框架中的队列 Queue](#java-集合框架中的队列-queue)
- - [推荐文章](#推荐文章)
-- [Set](#set)
- - [什么是 Set](#什么是-set)
- - [补充:有序集合与无序集合说明](#补充:有序集合与无序集合说明)
- - [HashSet 和 TreeSet 底层数据结构](#hashset-和-treeset-底层数据结构)
- - [推荐文章](#推荐文章-1)
-- [List](#list)
- - [什么是List](#什么是list)
- - [List的常见实现类](#list的常见实现类)
- - [ArrayList 和 LinkedList 源码学习](#arraylist-和-linkedlist-源码学习)
- - [推荐阅读](#推荐阅读)
-- [Map](#map)
-- [树](#树)
-
-
-
-
-## Queue
-
-### 什么是队列
-队列是数据结构中比较重要的一种类型,它支持 FIFO,尾部添加、头部删除(先进队列的元素先出队列),跟我们生活中的排队类似。
-
-### 队列的种类
-
-- **单队列**(单队列就是常见的队列, 每次添加元素时,都是添加到队尾,存在“假溢出”的问题也就是明明有位置却不能添加的情况)
-- **循环队列**(避免了“假溢出”的问题)
-
-### Java 集合框架中的队列 Queue
-
-Java 集合中的 Queue 继承自 Collection 接口 ,Deque, LinkedList, PriorityQueue, BlockingQueue 等类都实现了它。
-Queue 用来存放 等待处理元素 的集合,这种场景一般用于缓冲、并发访问。
-除了继承 Collection 接口的一些方法,Queue 还添加了额外的 添加、删除、查询操作。
-
-### 推荐文章
-
-- [Java 集合深入理解(9):Queue 队列](https://blog.csdn.net/u011240877/article/details/52860924)
-
-## Set
-
-### 什么是 Set
-Set 继承于 Collection 接口,是一个不允许出现重复元素,并且无序的集合,主要 HashSet 和 TreeSet 两大实现类。
-
-在判断重复元素的时候,HashSet 集合会调用 hashCode()和 equal()方法来实现;TreeSet 集合会调用compareTo方法来实现。
-
-### 补充:有序集合与无序集合说明
-- 有序集合:集合里的元素可以根据 key 或 index 访问 (List、Map)
-- 无序集合:集合里的元素只能遍历。(Set)
-
-
-### HashSet 和 TreeSet 底层数据结构
-
-**HashSet** 是哈希表结构,主要利用 HashMap 的 key 来存储元素,计算插入元素的 hashCode 来获取元素在集合中的位置;
-
-**TreeSet** 是红黑树结构,每一个元素都是树中的一个节点,插入的元素都会进行排序;
-
-
-### 推荐文章
-
-- [Java集合--Set(基础)](https://www.jianshu.com/p/b48c47a42916)
-
-## List
-
-### 什么是List
-
-在 List 中,用户可以精确控制列表中每个元素的插入位置,另外用户可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素。 与 Set 不同,List 通常允许重复的元素。 另外 List 是有序集合而 Set 是无序集合。
-
-### List的常见实现类
-
-**ArrayList** 是一个数组队列,相当于动态数组。它由数组实现,随机访问效率高,随机插入、随机删除效率低。
-
-**LinkedList** 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率高。
-
-**Vector** 是矢量队列,和ArrayList一样,它也是一个动态数组,由数组实现。但是ArrayList是非线程安全的,而Vector是线程安全的。
-
-**Stack** 是栈,它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。相关阅读:[java数据结构与算法之栈(Stack)设计与实现](https://blog.csdn.net/javazejian/article/details/53362993)
-
-### ArrayList 和 LinkedList 源码学习
-
-- [ArrayList 源码学习](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/collection/ArrayList.md)
-- [LinkedList 源码学习](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/collection/LinkedList.md)
-
-### 推荐阅读
-
-- [java 数据结构与算法之顺序表与链表深入分析](https://blog.csdn.net/javazejian/article/details/52953190)
-
-
-## Map
-
-
-- [集合框架源码学习之 HashMap(JDK1.8)](https://juejin.im/post/5ab0568b5188255580020e56)
-- [ConcurrentHashMap 实现原理及源码分析](https://link.juejin.im/?target=http%3A%2F%2Fwww.cnblogs.com%2Fchengxiao%2Fp%2F6842045.html)
-
-## 树
- * ### 1 二叉树
-
- [二叉树](https://baike.baidu.com/item/%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科)
-
- (1)[完全二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)——若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树。
-
- (2)[满二叉树](https://baike.baidu.com/item/%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91)——除了叶结点外每一个结点都有左右子叶且叶子结点都处在最底层的二叉树。
-
- (3)[平衡二叉树](https://baike.baidu.com/item/%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91/10421057)——平衡二叉树又被称为AVL树(区别于AVL算法),它是一棵二叉排序树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
-
- * ### 2 完全二叉树
-
- [完全二叉树](https://baike.baidu.com/item/%E5%AE%8C%E5%85%A8%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科)
-
- 完全二叉树:叶节点只能出现在最下层和次下层,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。
- * ### 3 满二叉树
-
- [满二叉树](https://baike.baidu.com/item/%E6%BB%A1%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科,国内外的定义不同)
-
- 国内教程定义:一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是满二叉树。也就是说,如果一个二叉树的层数为K,且结点总数是(2^k) -1 ,则它就是满二叉树。
- * ### 堆
-
- [数据结构之堆的定义](https://blog.csdn.net/qq_33186366/article/details/51876191)
-
- 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
- * ### 4 二叉查找树(BST)
-
- [浅谈算法和数据结构: 七 二叉查找树](http://www.cnblogs.com/yangecnu/p/Introduce-Binary-Search-Tree.html)
-
- 二叉查找树的特点:
-
- 1. 若任意节点的左子树不空,则左子树上所有结点的 值均小于它的根结点的值;
- 2. 若任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 3. 任意节点的左、右子树也分别为二叉查找树;
- 4. 没有键值相等的节点(no duplicate nodes)。
-
- * ### 5 平衡二叉树(Self-balancing binary search tree)
-
- [ 平衡二叉树](https://baike.baidu.com/item/%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91)(百度百科,平衡二叉树的常用实现方法有红黑树、AVL、替罪羊树、Treap、伸展树等)
- * ### 6 红黑树
-
- - 红黑树特点:
- 1. 每个节点非红即黑;
- 2. 根节点总是黑色的;
- 3. 每个叶子节点都是黑色的空节点(NIL节点);
- 4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定);
- 5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)。
-
- - 红黑树的应用:
-
- TreeMap、TreeSet以及JDK1.8之后的HashMap底层都用到了红黑树。
-
- - 为什么要用红黑树
-
- 简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。详细了解可以查看 [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐)
-
- - 推荐文章:
- - [漫画:什么是红黑树?](https://juejin.im/post/5a27c6946fb9a04509096248#comment)(也介绍到了二叉查找树,非常推荐)
- - [寻找红黑树的操作手册](http://dandanlove.com/2018/03/18/red-black-tree/)(文章排版以及思路真的不错)
- - [红黑树深入剖析及Java实现](https://zhuanlan.zhihu.com/p/24367771)(美团点评技术团队)
- * ### 7 B-,B+,B*树
-
- [二叉树学习笔记之B树、B+树、B*树 ](https://yq.aliyun.com/articles/38345)
-
- [《B-树,B+树,B*树详解》](https://blog.csdn.net/aqzwss/article/details/53074186)
-
- [《B-树,B+树与B*树的优缺点比较》](https://blog.csdn.net/bigtree_3721/article/details/73632405)
-
- B-树(或B树)是一种平衡的多路查找(又称排序)树,在文件系统中有所应用。主要用作文件的索引。其中的B就表示平衡(Balance)
- 1. B+ 树的叶子节点链表结构相比于 B- 树便于扫库,和范围检索。
- 2. B+树支持range-query(区间查询)非常方便,而B树不支持。这是数据库选用B+树的最主要原因。
- 3. B\*树 是B+树的变体,B\*树分配新结点的概率比B+树要低,空间使用率更高;
- * ### 8 LSM 树
-
- [[HBase] LSM树 VS B+树](https://blog.csdn.net/dbanote/article/details/8897599)
-
- B+树最大的性能问题是会产生大量的随机IO
-
- 为了克服B+树的弱点,HBase引入了LSM树的概念,即Log-Structured Merge-Trees。
-
- [LSM树由来、设计思想以及应用到HBase的索引](http://www.cnblogs.com/yanghuahui/p/3483754.html)
-
-
-## 图
-
-
-
-
-## BFS及DFS
-
-- [《使用BFS及DFS遍历树和图的思路及实现》](https://blog.csdn.net/Gene1994/article/details/85097507)
-
diff --git "a/docs/dataStructures-algorithms/\347\250\213\345\272\217\345\221\230\344\273\243\347\240\201\351\235\242\350\257\225\346\214\207\345\215\227-Java\345\256\236\347\216\260.md" "b/docs/dataStructures-algorithms/\347\250\213\345\272\217\345\221\230\344\273\243\347\240\201\351\235\242\350\257\225\346\214\207\345\215\227-Java\345\256\236\347\216\260.md"
new file mode 100644
index 0000000..6e99702
--- /dev/null
+++ "b/docs/dataStructures-algorithms/\347\250\213\345\272\217\345\221\230\344\273\243\347\240\201\351\235\242\350\257\225\346\214\207\345\215\227-Java\345\256\236\347\216\260.md"
@@ -0,0 +1 @@
+https://github.com/LyricYang/Internet-Recruiting-Algorithm-Problems/blob/master/CodeInterviewGuide/README.md
\ No newline at end of file
diff --git "a/docs/dataStructures-algorithms/\347\256\227\346\263\225\345\255\246\344\271\240\350\265\204\346\272\220\346\216\250\350\215\220.md" "b/docs/dataStructures-algorithms/\347\256\227\346\263\225\345\255\246\344\271\240\350\265\204\346\272\220\346\216\250\350\215\220.md"
deleted file mode 100644
index 4c5df56..0000000
--- "a/docs/dataStructures-algorithms/\347\256\227\346\263\225\345\255\246\344\271\240\350\265\204\346\272\220\346\216\250\350\215\220.md"
+++ /dev/null
@@ -1,52 +0,0 @@
-我比较推荐大家可以刷一下 Leetcode ,我自己平时没事也会刷一下,我觉得刷 Leetcode 不仅是为了能让你更从容地面对面试中的手撕算法问题,更可以提高你的编程思维能力、解决问题的能力以及你对某门编程语言 API 的熟练度。当然牛客网也有一些算法题,我下面也整理了一些。
-
-## LeetCode
-
-- [LeetCode(中国)官网](https://leetcode-cn.com/)
-
-- [如何高效地使用 LeetCode](https://leetcode-cn.com/articles/%E5%A6%82%E4%BD%95%E9%AB%98%E6%95%88%E5%9C%B0%E4%BD%BF%E7%94%A8-leetcode/)
-
-
-## 牛客网
-
-- [牛客网官网](https://www.nowcoder.com)
-- [剑指offer编程题](https://www.nowcoder.com/ta/coding-interviews)
-
-- [2017校招真题](https://www.nowcoder.com/ta/2017test)
-- [华为机试题](https://www.nowcoder.com/ta/huawei)
-
-
-## 公司真题
-
-- [ 网易2018校园招聘编程题真题集合](https://www.nowcoder.com/test/6910869/summary)
-- [ 网易2018校招内推编程题集合](https://www.nowcoder.com/test/6291726/summary)
-- [2017年校招全国统一模拟笔试(第五场)编程题集合](https://www.nowcoder.com/test/5986669/summary)
-- [2017年校招全国统一模拟笔试(第四场)编程题集合](https://www.nowcoder.com/test/5507925/summary)
-- [2017年校招全国统一模拟笔试(第三场)编程题集合](https://www.nowcoder.com/test/5217106/summary)
-- [2017年校招全国统一模拟笔试(第二场)编程题集合](https://www.nowcoder.com/test/4546329/summary)
-- [ 2017年校招全国统一模拟笔试(第一场)编程题集合](https://www.nowcoder.com/test/4236887/summary)
-- [百度2017春招笔试真题编程题集合](https://www.nowcoder.com/test/4998655/summary)
-- [网易2017春招笔试真题编程题集合](https://www.nowcoder.com/test/4575457/summary)
-- [网易2017秋招编程题集合](https://www.nowcoder.com/test/2811407/summary)
-- [网易有道2017内推编程题](https://www.nowcoder.com/test/2385858/summary)
-- [ 滴滴出行2017秋招笔试真题-编程题汇总](https://www.nowcoder.com/test/3701760/summary)
-- [腾讯2017暑期实习生编程题](https://www.nowcoder.com/test/1725829/summary)
-- [今日头条2017客户端工程师实习生笔试题](https://www.nowcoder.com/test/1649301/summary)
-- [今日头条2017后端工程师实习生笔试题](https://www.nowcoder.com/test/1649268/summary)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git "a/docs/dataStructures-algorithms/\351\253\230\351\242\221\347\256\227\346\263\225\351\242\230\347\233\256\346\200\273\347\273\223.md" "b/docs/dataStructures-algorithms/\351\253\230\351\242\221\347\256\227\346\263\225\351\242\230\347\233\256\346\200\273\347\273\223.md"
new file mode 100644
index 0000000..3c616a3
--- /dev/null
+++ "b/docs/dataStructures-algorithms/\351\253\230\351\242\221\347\256\227\346\263\225\351\242\230\347\233\256\346\200\273\347\273\223.md"
@@ -0,0 +1,6885 @@
+Ctrl+Shift+P(MacOS:cmd+shift+p)呼出命令面板,输入Markdown Preview Enhanced: Create Toc会生成一段类似,保存生成目录。
+
+
+
+
+
+
+- [Java API整理](#java-api整理)
+- [Go API整理](#go-api整理)
+- [链表](#链表)
+ - [排序](#排序)
+ - [翻转链表, NC78](#翻转链表-nc78)
+ - [LFU缓存结构设计](#lfu缓存结构设计)
+ - [设计LRU缓存结构, NC93](#设计lru缓存结构-nc93)
+ - [合并有序链表, NC33](#合并有序链表-nc33)
+ - [链表中的节点每K个一组翻转](#链表中的节点每k个一组翻转)
+ - [判断链表中是否有环](#判断链表中是否有环)
+ - [链表中环的入口结点](#链表中环的入口结点)
+ - [删除链表的倒数第n个节点](#删除链表的倒数第n个节点)
+ - [两个链表的第一个公共结点](#两个链表的第一个公共结点)
+ - [两个链表生成相加链表](#两个链表生成相加链表)
+ - [合并k个已排序的链表](#合并k个已排序的链表)
+ - [单链表的排序,NC70](#单链表的排序nc70)
+ - [判断链表是否为回文结构](#判断链表是否为回文结构)
+ - [链表内指定区间反转](#链表内指定区间反转)
+ - [删除有序链表中重复出现的元素](#删除有序链表中重复出现的元素)
+ - [环形链表的约瑟夫问题](#环形链表的约瑟夫问题)
+ - [链表的奇偶重排](#链表的奇偶重排)
+ - [重排链表(1->n->2->n-1)](#重排链表1-n-2-n-1)
+ - [二叉搜索树与双向链表](#二叉搜索树与双向链表)
+- [队列、栈](#队列栈)
+ - [用两个栈实现队列](#用两个栈实现队列)
+ - [有效括号序列](#有效括号序列)
+ - [包含 min 函数的栈](#包含-min-函数的栈)
+ - [表达式求值](#表达式求值)
+ - [最长括号子串](#最长括号子串)
+ - [括号生成](#括号生成)
+- [二叉树](#二叉树)
+ - [实现二叉树先序,中序和后序遍历](#实现二叉树先序中序和后序遍历)
+ - [二叉树的层序遍历](#二叉树的层序遍历)
+ - [二叉树的之字形层序遍历](#二叉树的之字形层序遍历)
+ - [在二叉树中找到两个节点的最近公共祖先](#在二叉树中找到两个节点的最近公共祖先)
+ - [重建二叉树](#重建二叉树)
+ - [输出二叉树的右视图(先重建,再输出右视图)](#输出二叉树的右视图先重建再输出右视图)
+ - [二叉树的最大深度](#二叉树的最大深度)
+ - [判断是不是平衡二叉树](#判断是不是平衡二叉树)
+ - [二叉树根节点到叶子节点的所有路径和](#二叉树根节点到叶子节点的所有路径和)
+ - [二叉树中和为某一值的路径,返回所有路径](#二叉树中和为某一值的路径返回所有路径)
+ - [判断一棵二叉树是否为搜索二叉树和完全二叉树](#判断一棵二叉树是否为搜索二叉树和完全二叉树)
+ - [二叉树的最大路径和](#二叉树的最大路径和)
+ - [判断二叉树是否对称](#判断二叉树是否对称)
+ - [二叉树中是否存在节点和为指定值的路径](#二叉树中是否存在节点和为指定值的路径)
+ - [序列化二叉树](#序列化二叉树)
+ - [二叉搜索树的第k个结点](#二叉搜索树的第k个结点)
+ - [把二叉树打印成多行](#把二叉树打印成多行)
+ - [二叉树的镜像](#二叉树的镜像)
+ - [判断t1树中是否有与t2树拓扑结构完全相同的子树](#判断t1树中是否有与t2树拓扑结构完全相同的子树)
+ - [合并二叉树](#合并二叉树)
+ - [字典树的实现](#字典树的实现)
+ - [找到二叉搜索树中的两个错误节点](#找到二叉搜索树中的两个错误节点)
+- [堆](#堆)
+ - [最小的K个数](#最小的k个数)
+ - [字符串出现次数的TopK问题](#字符串出现次数的topk问题)
+ - [寻找第K大](#寻找第k大)
+- [双指针](#双指针)
+ - [最长无重复子数组的长度](#最长无重复子数组的长度)
+ - [滑动窗口的最大值](#滑动窗口的最大值)
+ - [合并区间(区间重叠)](#合并区间区间重叠)
+ - [反转字符串](#反转字符串)
+ - [数组中相加和为0的三元组](#数组中相加和为0的三元组)
+ - [接雨水问题](#接雨水问题)
+ - [最小覆盖子串(T包含S的最小子串)](#最小覆盖子串t包含s的最小子串)
+ - [两数之和](#两数之和)
+ - [最长重复子串(连续两个相同的字符串)](#最长重复子串连续两个相同的字符串)
+- [动态规划](#动态规划)
+ - [跳台阶](#跳台阶)
+ - [连续子数组的最大和(sum < 0置为0)](#连续子数组的最大和sum--0置为0)
+ - [最长公共子串(返回具体字符串/长度)](#最长公共子串返回具体字符串长度)
+ - [斐波那契数列](#斐波那契数列)
+ - [最长回文子串的长度](#最长回文子串的长度)
+ - [最长递增子序列](#最长递增子序列)
+ - [买卖股票的最佳时机](#买卖股票的最佳时机)
+ - [矩阵的最小路径和](#矩阵的最小路径和)
+ - [编辑距离](#编辑距离)
+ - [不同路径的数目](#不同路径的数目)
+ - [最长公共子序列](#最长公共子序列)
+ - [最长的括号子串](#最长的括号子串)
+ - [高空扔鸡蛋](#高空扔鸡蛋)
+ - [兑换零钱](#兑换零钱)
+ - [最大正方形](#最大正方形)
+ - [通配符匹配](#通配符匹配)
+ - [正则表达式匹配](#正则表达式匹配)
+ - [矩阵最长递增路径](#矩阵最长递增路径)
+ - [最长上升子序列](#最长上升子序列)
+ - [目标和(完全背包)](#目标和完全背包)
+ - [打家劫舍](#打家劫舍)
+ - [带权值的最小路径和](#带权值的最小路径和)
+ - [最长不含重复字符的子字符串](#最长不含重复字符的子字符串)
+ - [把数字翻译成字符串](#把数字翻译成字符串)
+- [二分](#二分)
+ - [求平方根](#求平方根)
+ - [在旋转过的有序数组中寻找目标值](#在旋转过的有序数组中寻找目标值)
+ - [在两个长度相等的排序数组中找到上中位数](#在两个长度相等的排序数组中找到上中位数)
+ - [有序矩阵元素查找](#有序矩阵元素查找)
+ - [二分查找](#二分查找)
+ - [旋转数组的最小数字](#旋转数组的最小数字)
+ - [数字在升序数组中出现的次数](#数字在升序数组中出现的次数)
+ - [峰值](#峰值)
+- [数组](#数组)
+ - [数组中只出现一次的数字](#数组中只出现一次的数字)
+ - [合并两个有序的数组](#合并两个有序的数组)
+ - [子数组最大乘积](#子数组最大乘积)
+ - [数组中最长连续子序列](#数组中最长连续子序列)
+ - [数组中未出现的最小正整数](#数组中未出现的最小正整数)
+ - [顺时针旋转数组](#顺时针旋转数组)
+ - [旋转数组](#旋转数组)
+ - [逆序对](#逆序对)
+ - [调整数组顺序使奇数位于偶数前面](#调整数组顺序使奇数位于偶数前面)
+ - [矩阵乘法](#矩阵乘法)
+- [回溯](#回溯)
+ - [字符串的全排列](#字符串的全排列)
+ - [岛屿的数量](#岛屿的数量)
+ - [没有重复项数字的所有排列(全排列)](#没有重复项数字的所有排列全排列)
+ - [集合的所有子集](#集合的所有子集)
+ - [重复项数字的所有排列](#重复项数字的所有排列)
+ - [N皇后问题](#n皇后问题)
+ - [把数组字符串转换为 ip 地址](#把数组字符串转换为-ip-地址)
+ - [加起来和为目标值的组合](#加起来和为目标值的组合)
+- [其他](#其他)
+ - [螺旋矩阵](#螺旋矩阵)
+ - [顺时针旋转矩阵](#顺时针旋转矩阵)
+ - [进制转换](#进制转换)
+ - [反转数字](#反转数字)
+ - [大数加法](#大数加法)
+ - [把字符串转换成整数(atoi)](#把字符串转换成整数atoi)
+ - [最长公共前缀](#最长公共前缀)
+ - [回文数字](#回文数字)
+ - [字符串变形(反序,大写)](#字符串变形反序大写)
+ - [最大值(数组拼接最大数)](#最大值数组拼接最大数)
+ - [验证ip地址](#验证ip地址)
+ - [二进制中1的个数](#二进制中1的个数)
+ - [第一个只出现一次的字符](#第一个只出现一次的字符)
+- [其他编程题(golang、java)](#其他编程题golangjava)
+ - [单例模式](#单例模式)
+ - [实现线程安全的生产者消费者](#实现线程安全的生产者消费者)
+ - [一个10G的文件,里面全部是自然数,一行一个,乱序排列,对其排序。在32位机器上面完成,内存限制为 2G(bitmap原理知道吗?)](#一个10g的文件里面全部是自然数一行一个乱序排列对其排序在32位机器上面完成内存限制为-2gbitmap原理知道吗)
+ - [实现使用字符串函数名,调用函数](#实现使用字符串函数名调用函数)
+ - [负载均衡算法。(一致性哈希)](#负载均衡算法一致性哈希)
+ - [(Goroutine)有三个函数,分别打印"cat", "fish","dog"要求每一个函数都用一个goroutine,按照顺序打印100次](#goroutine有三个函数分别打印cat-fishdog要求每一个函数都用一个goroutine按照顺序打印100次)
+ - [两个协程交替打印10个字母和数字](#两个协程交替打印10个字母和数字)
+ - [启动 2个groutine 2秒后取消, 第一个协程1秒执行完,第二个协程3秒执行完。](#启动-2个groutine-2秒后取消-第一个协程1秒执行完第二个协程3秒执行完)
+ - [当select监控多个chan同时到达就绪态时,如何先执行某个任务?](#当select监控多个chan同时到达就绪态时如何先执行某个任务)
+
+
+
+## Java API整理
+
+- api
+
+https://blog.csdn.net/qq_34756156/article/details/120713595
+
+## Go API整理
+
+- api
+
+https://www.pseudoyu.com/zh/2021/05/29/algorithm_data_structure_go/
+https://greyireland.gitbook.io/algorithm-pattern/ru-men-pian/golang
+
+- 刷题模板
+
+https://greyireland.gitbook.io/algorithm-pattern/
+
+## 链表
+
+### 排序
+
+```java
+import java.util.*;
+
+
+public class Solution {
+
+ public int[] MySort(int[] arr) {
+// 选择排序
+// return selectSort(arr);
+// 冒泡排序
+// return bubbleSort(arr);
+// 插入排序
+// return insertSort(arr);
+// 希尔排序
+// return shellSort(arr);
+// 归并排序
+// return mergeSort(arr,0,arr.length-1);
+// 快速排序
+// quickSort(arr,0,arr.length-1);
+// return arr;
+// 计数排序
+// return countSort(arr);
+// 基数排序
+// return radixSort(arr);
+// 桶排序
+ return bucketSort(arr);
+ }
+ // 选择排序---选择最小的数与当前数交换
+ public int[] selectSort(int[] arr){
+ if(arr.length<2)return arr;
+ for(int i=0;iarr[j])swap(arr,i,j);
+ }
+ }
+ return arr;
+ }
+
+ // 插入排序---与当前位置之前的所有元素比较,交换元素
+ public int[] insertSort(int[] arr){
+ if(arr.length<2)return arr;
+ for(int i=1;i0;j--){
+ if(arr[j]0;gap=(gap-1)/3){
+ for(int i=gap;i=0;j=j-gap){
+ if(arr[j]= right) return ;
+ int pivot = arr[left];
+ int i = left,j = right;
+ while(i < j){
+ while(arr[j] >= pivot && j>i){
+ j--;
+ }
+ while(arr[i] <= pivot && i0){count++;temp = temp/10;}
+ if(count>max)max = count;
+ }
+
+ for(int m=0;m0){temp=temp/10;}
+ int result = temp%10;
+ for(int k=0;k0){
+ arr[k++] = countArr[i][j];
+ }
+ }
+ }
+ return arr;
+ }
+
+ // 桶排序---给定n个桶,找到最大数与最小数,
+ // 计算出每个桶能装的数的范围,将数分别放入符合条件的桶中,
+ // 对每个桶进行快速排序,最后合并
+ public int[] bucketSort(int[] arr){
+ // 设置桶的个数
+ int bucket = 4;
+ // 找到数组中的最大最小值
+ int min = arr[0],max=arr[0];
+ for(int i=0;imax)max=arr[i];
+ if(arr[i]= min && temp < min+range){
+ for(int k =0;k= min+range && temp < min+2*range){
+ for(int k =0;k= min+2*range && temp < max - range){
+ for(int k =0;k= max - range && temp <= max){
+ for(int k =0;k cache; // 存储缓存的内容
+ Map> freqMap; // 存储每个频次对应的双向链表
+ int size;
+ int capacity;
+ int min; // 存储当前最小频次
+
+ public LFUCache(int capacity) {
+ cache = new HashMap<> (capacity);
+ freqMap = new HashMap<>();
+ this.capacity = capacity;
+ }
+
+ public int get(int key) {
+ Node node = cache.get(key);
+ if (node == null) {
+ return -1;
+ }
+ freqInc(node);
+ return node.value;
+ }
+
+ public void put(int key, int value) {
+ if (capacity == 0) {
+ return;
+ }
+ Node node = cache.get(key);
+ if (node != null) {
+ node.value = value;
+ freqInc(node);
+ } else {
+ if (size == capacity) {
+ Node deadNode = removeNode();
+ cache.remove(deadNode.key);
+ size--;
+ }
+ Node newNode = new Node(key, value);
+ cache.put(key, newNode);
+ addNode(newNode);
+ size++;
+ }
+ }
+
+ void freqInc(Node node) {
+ // 从原freq对应的链表里移除, 并更新min
+ int freq = node.freq;
+ LinkedHashSet set = freqMap.get(freq);
+ set.remove(node);
+ if (freq == min && set.size() == 0) {
+ min = freq + 1;
+ }
+ // 加入新freq对应的链表
+ node.freq++;
+ LinkedHashSet newSet = freqMap.get(freq + 1);
+ if (newSet == null) {
+ newSet = new LinkedHashSet<>();
+ freqMap.put(freq + 1, newSet);
+ }
+ newSet.add(node);
+ }
+
+ void addNode(Node node) {
+ LinkedHashSet set = freqMap.get(1);
+ if (set == null) {
+ set = new LinkedHashSet<>();
+ freqMap.put(1, set);
+ }
+ set.add(node);
+ min = 1;
+ }
+
+ Node removeNode() {
+ LinkedHashSet set = freqMap.get(min);
+ Node deadNode = set.iterator().next();
+ set.remove(deadNode);
+ return deadNode;
+ }
+}
+
+class Node {
+ int key;
+ int value;
+ int freq = 1;
+
+ public Node() {}
+
+ public Node(int key, int value) {
+ this.key = key;
+ this.value = value;
+ }
+}
+```
+
+### 设计LRU缓存结构, NC93
+
+- lru-k算法:https://blog.csdn.net/love254443233/article/details/82598381
+
+```java
+import java.util.*;
+
+public class Solution {
+ /**
+ * lru design
+ * @param operators int整型二维数组 the ops
+ * @param k int整型 the k
+ * @return int整型一维数组
+ */
+ public int[] LRU (int[][] operators, int k) {
+ // write code here
+ ArrayList list = new ArrayList();
+ LRUCache cache = new LRUCache(k);
+ for(int[] op : operators){
+ if(op[0]==1){
+ cache.put(op[1],op[2]);
+ }else{
+ int val = cache.get(op[1]);
+ list.add(val);
+ }
+ }
+ int[] ans = new int[list.size()];
+ for(int i=0;i map;
+ public LinkedList list;
+ public int capacity;
+
+ public LRUCache(int capacity){
+ this.capacity = capacity;
+ map = new HashMap<>();
+ list = new LinkedList<>();
+ }
+
+ public int get(int key){
+ if(!map.containsKey(key)){
+ return -1;
+ }
+ Node temp = map.get(key);
+ put(key,temp.value);
+ return temp.value;
+ }
+
+ public void put(int key, int value){
+ Node node = new Node(key,value);
+ if(map.containsKey(key)){
+ Node temp = map.get(key);
+ list.remove(temp);
+ list.addFirst(node);
+ map.put(key,node);
+ } else {
+ if(map.size() == capacity){
+ Node last = list.removeLast();
+ map.remove(last.key);
+ }
+ list.addFirst(node);
+ map.put(key,node);
+ }
+ }
+
+}
+```
+
+```go
+type LRUCache struct {
+ capacity int
+ m map[int]*Node
+ head, tail *Node
+}
+
+type Node struct {
+ Key int
+ Value int
+ Pre, Next *Node
+}
+
+func (this *LRUCache) Get(key int) int {
+ if v, ok := this.m[key]; ok {
+ this.moveToHead(v)
+ return v.Value
+ }
+ return -1
+}
+
+func (this *LRUCache) moveToHead(node *Node) {
+ this.deleteNode(node)
+ this.addToHead(node)
+}
+
+func (this *LRUCache) deleteNode(node *Node) {
+ node.Pre.Next = node.Next
+ node.Next.Pre = node.Pre
+}
+
+func (this *LRUCache) removeTail() int {
+ node := this.tail.Pre
+ this.deleteNode(node)
+ return node.Key
+}
+
+func (this *LRUCache) addToHead(node *Node) {
+ this.head.Next.Pre = node
+ node.Next = this.head.Next
+ node.Pre = this.head
+ this.head.Next = node
+}
+
+func (this *LRUCache) Put(key int, value int) {
+ if v, ok := this.m[key]; ok {
+ v.Value = value
+ this.moveToHead(v)
+ return
+ }
+
+ if this.capacity == len(this.m) {
+ rmKey := this.removeTail()
+ delete(this.m, rmKey)
+ }
+
+ newNode := &Node{Key: key, Value: value}
+ this.addToHead(newNode)
+ this.m[key] = newNode
+}
+
+func Constructor(capacity int) LRUCache {
+ head, tail := &Node{}, &Node{}
+ head.Next = tail
+ tail.Pre = head
+ return LRUCache{
+ capacity: capacity,
+ m: map[int]*Node{},
+ head: head,
+ tail: tail,
+ }
+}
+```
+
+### 合并有序链表, NC33
+
+```java
+import java.util.*;
+
+/*
+ * public class ListNode {
+ * int val;
+ * ListNode next = null;
+ * }
+ */
+
+public class Solution {
+ /**
+ *
+ * @param l1 ListNode类
+ * @param l2 ListNode类
+ * @return ListNode类
+ */
+ public ListNode mergeTwoLists (ListNode l1, ListNode l2) {
+ ListNode node = new ListNode(0);
+ ListNode res = node;
+ while(l1 != null && l2 != null){
+ if(l1.val > l2.val){
+ node.next = l2;
+ l2 = l2.next;
+ } else {
+ node.next = l1;
+ l1 = l1.next;
+ }
+ node = node.next;
+ }
+
+ if(l1 != null){
+ node.next = l1;
+ }
+
+ if(l2 != null){
+ node.next = l2;
+ }
+
+ return res.next;
+ }
+}
+```
+
+### 链表中的节点每K个一组翻转
+
+```java
+//明显递归解决,翻转第一组之后,以第二组的开头为头节点,继续翻转,转翻到最后,返回。
+public ListNode reverseKGroup(ListNode head, int k) {
+ if(head==null||head.next==null)
+ return head;
+ ListNode h=new ListNode(0);
+ h.next=head;
+ ListNode next=null,tmp=head,cur=head;
+ for(int i=1;i lists) {
+ if(lists == null || lists.size() == 0){
+ return null;
+ }
+
+ return mergeList(lists,0,lists.size()-1);
+ }
+
+ public ListNode mergeList(ArrayList lists, int low, int high){
+ if(low >= high){
+ return lists.get(low);
+ }
+
+ int mid = low + (high - low)/2;
+ ListNode left = mergeList(lists,low,mid);
+ ListNode right = mergeList(lists,mid+1,high);
+ return merge(left,right);
+ }
+
+ public ListNode merge(ListNode left, ListNode right){
+ ListNode h = new ListNode(-1);
+ ListNode tmp = h;
+ while(left != null && right != null){
+ if(left.val < right.val){
+ tmp.next = left;
+ left = left.next;
+ } else {
+ tmp.next = right;
+ right = right.next;
+ }
+ tmp = tmp.next;
+ }
+
+ if(left != null){
+ tmp.next = left;
+ }
+
+ if(right != null){
+ tmp.next = right;
+ }
+
+ return h.next;
+ }
+}
+```
+
+### 单链表的排序,NC70
+
+- 堆排序
+
+```java
+import java.util.*;
+
+public class Solution {
+ /**
+ *
+ * @param head ListNode类 the head node
+ * @return ListNode类
+ */
+ public ListNode sortInList (ListNode head) {
+ // write code here
+ PriorityQueue heap = new PriorityQueue<>((n1, n2) -> n1.val - n2.val);
+ while (head != null) {
+ heap.add(head);
+ head = head.next;
+ }
+ ListNode dummy = new ListNode(-1);
+ ListNode cur = dummy;
+ while (!heap.isEmpty()) {
+ cur.next = heap.poll();
+ cur = cur.next;
+ }
+ cur.next = null;
+ return dummy.next;
+ }
+}
+```
+
+- 归并排序
+
+```java
+import java.util.*;
+public class Solution {
+ //合并两段有序链表
+ ListNode merge(ListNode pHead1, ListNode pHead2) {
+ //一个已经为空了,直接返回另一个
+ if(pHead1 == null)
+ return pHead2;
+ if(pHead2 == null)
+ return pHead1;
+ //加一个表头
+ ListNode head = new ListNode(0);
+ ListNode cur = head;
+ //两个链表都要不为空
+ while(pHead1 != null && pHead2 != null){
+ //取较小值的节点
+ if(pHead1.val <= pHead2.val){
+ cur.next = pHead1;
+ //只移动取值的指针
+ pHead1 = pHead1.next;
+ }else{
+ cur.next = pHead2;
+ //只移动取值的指针
+ pHead2 = pHead2.next;
+ }
+ //指针后移
+ cur = cur.next;
+ }
+ //哪个链表还有剩,直接连在后面
+ if(pHead1 != null)
+ cur.next = pHead1;
+ else
+ cur.next = pHead2;
+ //返回值去掉表头
+ return head.next;
+ }
+
+ public ListNode sortInList (ListNode head) {
+ //链表为空或者只有一个元素,直接就是有序的
+ if(head == null || head.next == null)
+ return head;
+ ListNode left = head;
+ ListNode mid = head.next;
+ ListNode right = head.next.next;
+ //右边的指针到达末尾时,中间的指针指向该段链表的中间
+ while(right != null && right.next != null){
+ left = left.next;
+ mid = mid.next;
+ right = right.next.next;
+ }
+ //左边指针指向左段的左右一个节点,从这里断开
+ left.next = null;
+ //分成两段排序,合并排好序的两段
+ return merge(sortInList(head), sortInList(mid));
+ }
+}
+```
+
+### 判断链表是否为回文结构
+
+```java
+import java.util.*;
+
+public class Solution {
+ /**
+ *
+ * @param head ListNode类 the head
+ * @return bool布尔型
+ */
+ public boolean isPail (ListNode head) {
+ ListNode slow = head;
+ ListNode fast = head;
+ while(fast != null && fast.next != null){
+ fast = fast.next.next;
+ slow = slow.next;
+ }
+
+ Stack stack = new Stack<>();
+ while(slow != null){
+ stack.add(slow.val);
+ slow = slow.next;
+ }
+
+ while(!stack.isEmpty()){
+ if(stack.pop() != head.val){
+ return false;
+ }
+
+ head = head.next;
+ }
+
+ return true;
+ }
+}
+```
+
+### 链表内指定区间反转
+
+```java
+public class Solution {
+ /**
+ *
+ * @param head ListNode类
+ * @param m int整型
+ * @param n int整型
+ * @return ListNode类
+ */
+ public ListNode reverseBetween (ListNode head, int m, int n) {
+ // write code here
+ if(head == null || n == m){
+ return head;
+ }
+
+ ListNode dummy = new ListNode(0);
+ dummy.next = head;
+
+ ListNode cur = dummy;
+ for(int i = 1; i < m; i++){
+ cur = cur.next;
+ }
+
+ // t1代表头,t2代表尾
+ ListNode t1 = cur;
+ ListNode t2 = cur.next;
+ cur = t2;
+
+ ListNode pre = null;
+ // 翻转链表
+ for(int i = 0; i <= n - m; i++){
+ ListNode temp = cur.next;
+ cur.next = pre;
+ pre = cur;
+ cur = temp;
+ }
+
+ t1.next = pre;
+ t2.next = cur;
+
+ return dummy.next;
+ }
+}
+```
+
+### 删除有序链表中重复出现的元素
+
+```java
+public ListNode deleteDuplicates (ListNode head) {
+ ListNode dummy=new ListNode(0);
+ dummy.next=head;
+ ListNode pre=dummy;
+ ListNode p=head;
+ while(p!=null&&p.next!=null){
+ if(p.val==p.next.val){
+ while(p.next!=null&&p.val==p.next.val){
+ p=p.next;
+ }
+ pre.next=p.next;
+ p=p.next;
+ }
+ else{
+ pre=p;
+ p=p.next;
+ }
+ }
+ return dummy.next;
+}
+```
+
+### 环形链表的约瑟夫问题
+
+```java
+public int ysf (int n, int m) {
+ // write code here
+ ListNode head = new ListNode(1) ,p1=head;
+ for(int i=2;i<=n;i++){
+ ListNode temp = new ListNode(i);
+ p1.next=temp;
+ p1=p1.next;
+ }
+ p1.next=head;
+ while(n-->1){
+ int num=m;
+ while(num-->1){
+ p1=p1.next;
+ }
+ p1.next=p1.next.next;
+ }
+ return p1.val;
+}
+```
+
+### 链表的奇偶重排
+
+```java
+import java.util.*;
+
+/*
+ * public class ListNode {
+ * int val;
+ * ListNode next = null;
+ * }
+ */
+
+public class Solution {
+ /**
+ * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
+ *
+ * @param head ListNode类
+ * @return ListNode类
+ */
+ public ListNode oddEvenList (ListNode head) {
+ // write code here
+ if (head == null || head.next == null) return head;
+ /*
+ odd 指向奇数节点的指针
+ oddHead 指向初始奇数节点的头指针
+ even 指向偶数节点的指针
+ evenHead 指向初始偶数节点的头指针
+ */
+ ListNode odd = head, oddHead = head, even = head.next, evenHead = head.next;
+
+ while (even != null && even.next != null) {
+ // 奇数节点指向偶数节点的 next
+ odd.next = even.next;
+ // 奇数节点指针后移
+ odd = odd.next;
+ // 偶数节点指向奇数节点的 next
+ even.next = odd.next;
+ // 偶数节点指针后移
+ even = even.next;
+ }
+ // 将奇数节点的 next 指向 偶数节点的初始头指针
+ odd.next = evenHead;
+ // 返回奇数节点的初始头指针
+ return oddHead;
+ }
+}
+```
+
+### 重排链表(1->n->2->n-1)
+
+```java
+import java.util.*;
+public class Solution {
+ public void reorderList(ListNode head) {
+ if (head == null || head.next == null) return;
+ List list = new ArrayList<>();
+ ListNode cur = head;
+ while (cur != null) {
+ list.add(cur);
+ cur = cur.next;
+ }
+ int l = 0, r = list.size() - 1;
+ while (l < r) {
+ list.get(l).next = list.get(r);
+ l++;
+ list.get(r).next = list.get(l);
+ r--;
+ }
+ list.get(l).next = null;
+ }
+}
+```
+
+### 二叉搜索树与双向链表
+
+```java
+/**
+public class TreeNode {
+ int val = 0;
+ TreeNode left = null;
+ TreeNode right = null;
+
+ public TreeNode(int val) {
+ this.val = val;
+
+ }
+}
+*/
+public class Solution {
+ public TreeNode head = null;
+ public TreeNode dummy = null;
+ public TreeNode Convert(TreeNode pRootOfTree) {
+ ConvertSub(pRootOfTree);
+ return dummy;
+ }
+
+ public void ConvertSub(TreeNode root){
+ if(root == null) return;
+ ConvertSub(root.left);
+
+ if(head == null){
+ head = root;
+ dummy = root;
+ } else {
+ head.right = root;
+ root.left = head;
+ head = root;
+ }
+
+ ConvertSub(root.right);
+ }
+}
+```
+
+## 队列、栈
+
+### 用两个栈实现队列
+
+```java
+import java.util.Stack;
+public class Solution {
+ Stack stack1 = new Stack();
+ Stack stack2 = new Stack();
+
+ public void push(int node) {
+ stack1.add(node);
+ }
+
+ public void pushToPop(){
+ if(stack2.isEmpty()){
+ while(!stack1.isEmpty()){
+ stack2.add(stack1.pop());
+ }
+ }
+ }
+
+ public int pop() {
+ pushToPop();
+ return stack2.pop();
+ }
+}
+```
+
+### 有效括号序列
+
+```java
+import java.util.*;
+public class Solution {
+
+ public boolean isValid (String s) {
+ // write code here
+ Stack stack = new Stack<>();
+ char[] chs = s.toCharArray();
+ for(int i = 0; i < chs.length; i++){
+ if(stack.isEmpty()){
+ stack.push(chs[i]);
+ } else if(chs[i] == '{' || chs[i] == '['
+ || chs[i] == '('){
+ stack.push(chs[i]);
+ } else if((chs[i] == '}' && stack.peek() == '{') ||
+ (chs[i] == ']' && stack.peek() == '[') ||
+ (chs[i] == ')' && stack.peek() == '(')){
+ stack.pop();
+ }
+ }
+
+ return stack.isEmpty() ? true : false;
+ }
+}
+```
+
+### 包含 min 函数的栈
+
+```java
+import java.util.Stack;
+
+public class Solution {
+ Stack minStack = new Stack<>();
+ Stack stack = new Stack<>();
+
+ public void push(int node) {
+ if(minStack.isEmpty()){
+ minStack.push(node);
+ }
+
+ if(node < minStack.peek().intValue()){
+ minStack.push(node);
+ } else {
+ minStack.push(minStack.peek());
+ }
+
+ stack.push(node);
+ }
+
+ public void pop() {
+ if(stack.isEmpty()){
+ return;
+ }
+ stack.pop();
+ minStack.pop();
+ }
+
+ public int top() {
+ return minStack.peek();
+ }
+
+ public int min() {
+ return minStack.peek();
+ }
+}
+```
+
+### 表达式求值
+
+step 1:使用栈辅助处理优先级,默认符号为加号。
+step 2:遍历字符串,遇到数字,则将连续的数字字符部分转化为int型数字。
+step 3:遇到左括号,则将括号后的部分送入递归,处理子问题;
+遇到右括号代表已经到了这个子问题的结尾,结束继续遍历字符串,将子问题的加法部分相加为一个数字,返回。
+step 4:当遇到符号的时候如果是+,得到的数字正常入栈,如果是-,则将其相反数入栈,
+如果是*,则将栈中内容弹出与后一个元素相乘再入栈。
+step 5:最后将栈中剩余的所有元素,进行一次全部相加。
+
+
+```java
+import java.util.*;
+public class Solution {
+ public ArrayList function(String s, int index){
+ Stack stack = new Stack();
+ int num = 0;
+ char op = '+';
+ int i;
+ for(i = index; i < s.length(); i++){
+ //数字转换成int数字
+ //判断是否为数字
+ if(s.charAt(i) >= '0' && s.charAt(i) <= '9'){
+ num = num * 10 + s.charAt(i) - '0';
+ if(i != s.length() - 1)
+ continue;
+ }
+ //碰到'('时,把整个括号内的当成一个数字处理
+ if(s.charAt(i) == '('){
+ //递归处理括号
+ ArrayList res = function(s, i + 1);
+ num = res.get(0);
+ i = res.get(1);
+ if(i != s.length() - 1)
+ continue;
+ }
+ switch(op){
+ //加减号先入栈
+ case '+':
+ stack.push(num);
+ break;
+ case '-':
+ //相反数
+ stack.push(-num);
+ break;
+ //优先计算乘号
+ case '*':
+ int temp = stack.pop();
+ stack.push(temp * num);
+ break;
+ }
+ num = 0;
+ //右括号结束递归
+ if(s.charAt(i) == ')')
+ break;
+ else
+ op = s.charAt(i);
+ }
+ int sum = 0;
+ //栈中元素相加
+ while(!stack.isEmpty())
+ sum += stack.pop();
+ ArrayList temp = new ArrayList();
+ temp.add(sum);
+ temp.add(i);
+ return temp;
+ }
+ public int solve (String s) {
+ ArrayList res = function(s, 0);
+ return res.get(0);
+ }
+}
+```
+
+### 最长括号子串
+
+step 1:可以使用栈来记录左括号下标。
+step 2:遍历字符串,左括号入栈,每次遇到右括号则弹出左括号的下标。
+step 3:然后长度则更新为当前下标与栈顶下标的距离。
+step 4:遇到不符合的括号,可能会使栈为空,因此需要使用start记录上一次结束的位置,
+这样用当前下标减去start即可获取长度,即得到子串。
+step 5:循环中最后维护子串长度最大值。
+
+```java
+import java.util.*;
+public class Solution {
+ public int longestValidParentheses(String s) {
+ if (s == null || s.length() == 0)
+ return 0;
+ int[] dp = new int[s.length()];
+ int ans = 0;
+ for (int i = 1; i < s.length(); i++) {
+ // 如果是'('直接跳过,默认为0
+ if (s.charAt(i) == ')') {
+ if (s.charAt(i - 1) == '(')
+ dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
+ // 说明s.charAt(i - 1)==')'
+ else if (i - dp[i - 1] > 0
+ && s.charAt(i - dp[i - 1] - 1) == '(') {
+ dp[i] = (i - dp[i - 1] > 1
+ ? dp[i - dp[i - 1] - 2] : 0) + dp[i - 1] + 2;
+ // 因为加了一个左括号和一个右括号,所以是加2
+ }
+ }
+ ans = Math.max(ans, dp[i]);
+ }
+ return ans;
+ }
+}
+```
+
+```java
+import java.util.*;
+public class Solution {
+ public int longestValidParentheses (String s) {
+ int res = 0;
+ //记录上一次连续括号结束的位置
+ int start = -1;
+ Stack st = new Stack();
+ for(int i = 0; i < s.length(); i++){
+ //左括号入栈
+ if(s.charAt(i) == '(')
+ st.push(i);
+ //右括号
+ else{
+ //如果右括号时栈为空,不合法,设置为结束位置
+ if(st.isEmpty())
+ start = i;
+ else{
+ //弹出左括号
+ st.pop();
+ //栈中还有左括号,说明右括号不够,减去栈顶位置就是长度
+ if(!st.empty())
+ res = Math.max(res, i - st.peek());
+ //栈中没有括号,说明左右括号行号,减去上一次结束的位置就是长度
+ else
+ res = Math.max(res, i - start);
+ }
+ }
+ }
+ return res;
+ }
+}
+```
+
+### 括号生成
+
+对于括号的题,核心基本都是:
+"一个字符串是合法的括号组合"的*充分必要*条件是:
+
+1. 字符串中开口数等于闭口数 (这是废话)
+2. 字符串的所有prefix都满足: 开口数>=闭口数
+举个栗子,比如 "()(())":
+prefix: "(", "()", "()(", "()((", "()(()", "()(())".
+
+那么对与这道题,为满足1,2, 每一个位置可以有的permutation就是:
+
+1. 如果有多余的开口 -> 可以选开口
+2. 如果有多余未闭合的开口 -> 可以选闭口
+
+剩下的就是正常的递归+回溯了
+时间: O(2^n), 每一位最多2个permutation
+空间: O(n), 栈高是n
+
+```java
+import java.util.*;
+
+public class Solution {
+ ArrayList ans = new ArrayList<>();
+
+ public ArrayList generateParenthesis (int n) {
+ permute(n, n, 0, new StringBuilder());
+ return ans;
+ }
+
+ void permute(int open, int close, int unclosedOpen, StringBuilder sb) {
+ // base case,开口闭口都用完了
+ if (open == 0 && close == 0) {
+ ans.add(sb.toString());
+ return;
+ }
+
+ // always ok to pick an open bracket if there are any open-bracket
+ if (open > 0) {
+ sb.append("(");
+ permute(open-1, close, unclosedOpen+1, sb);
+ sb.deleteCharAt(sb.length()-1);
+ }
+ // can pick close bracket if there is any unclosed open-bracket
+ if (unclosedOpen > 0) {
+ sb.append(")");
+ permute(open, close-1, unclosedOpen-1, sb);
+ sb.deleteCharAt(sb.length()-1);
+ }
+ }
+}
+```
+
+## 二叉树
+
+### 实现二叉树先序,中序和后序遍历
+
+```java
+import java.util.*;
+
+/*
+ * public class TreeNode {
+ * int val = 0;
+ * TreeNode left = null;
+ * TreeNode right = null;
+ * }
+ */
+
+public class Solution {
+ /**
+ *
+ * @param root TreeNode类 the root of binary tree
+ * @return int整型二维数组
+ */
+ public int[][] threeOrders (TreeNode root) {
+ // write code here
+ ArrayList list1 = new ArrayList<>();
+ ArrayList list2 = new ArrayList<>();
+ ArrayList list3 = new ArrayList<>();
+ front(root,list1,list2,list3);
+ int[][] ints = new int[3][list1.size()];
+ for (int i = 0; i < list1.size(); i++) {
+ ints[0][i] = list1.get(i);
+ ints[1][i] = list2.get(i);
+ ints[2][i] = list3.get(i);
+ }
+ return ints;
+ }
+
+ public void front(TreeNode root,ArrayList list1,
+ ArrayList list2,ArrayList list3){
+ if(root == null){
+ return;
+ }
+
+ list1.add(root.val);
+ front(root.left,list1,list2,list3);
+ list2.add(root.val);
+ front(root.right,list1,list2,list3);
+ list3.add(root.val);
+ }
+}
+```
+
+- 非递归遍历
+
+- 前序遍历
+
+用栈来保存信息,但是遍历的时候,是:**先输出根节点信息,然后压入右节点信息,然后再压入左节点信息。**
+
+```java
+public void pre(Node head){
+ if(head == null){
+ return;
+ }
+ Stack stack = new Stack<>();
+ stack.push(head);
+ while(!stack.isEmpty()){
+ head = stack.poll();
+ System.out.println(head.value + " ");
+ if(head.right != null){
+ stack.push(head.right);
+ }
+ if(head.left != null){
+ stack.push(head.left);
+ }
+ }
+ System.out.println();
+}
+```
+
+- 中序遍历
+
+中序遍历的顺序是**左中右**,先一直左节点遍历,并压入栈中,当做节点为空时,输出当前节点,往右节点遍历。
+
+```java
+public void inorder(Node head){
+ if(head == null){
+ return;
+ }
+ Stack stack = new Stack<>();
+ stack.push(head);
+ while(!stack.isEmpty() || head != null){
+ if(head != null){
+ stack.push(head);
+ head = head.left
+ } else {
+ head = stack.poll();
+ System.out.println(head.value + " ");
+ head = head.right;
+ }
+ }
+ System.out.println();
+}
+```
+
+- 后序遍历
+
+用两个栈来实现,压入栈1的时候为**先左后右**,栈1弹出来就是**中右左**,栈2收集起来就是**左右中**。
+
+```java
+// 后序遍历-迭代
+public void postIteOrders(TreeNode root, List postList) {
+ if (root == null) {
+ return;
+ }
+ // 用两个栈来实现
+ // 通过 stack1 和 stack2 来配合可以实现 左 - 右 - 中的顺序
+ Stack stack1 = new Stack<>();
+ Stack stack2 = new Stack<>();
+ stack1.push(root);
+ while (!stack1.isEmpty()) {
+ TreeNode node = stack1.pop();
+ stack2.push(node);
+ // 先入左节点
+ if (node.left != null) {
+ stack1.push(node.left);
+ }
+ // 在入右节点
+ if (node.right != null) {
+ stack1.push(node.right);
+ }
+
+ }
+ // 弹出元素
+ while (!stack2.isEmpty()) {
+ postList.add(stack2.pop().val);
+ }
+}
+```
+
+### 二叉树的层序遍历
+
+```java
+public ArrayList> levelOrder (TreeNode root) {
+ // write code here
+ ArrayList> result = new ArrayList<>();
+ if (root == null) {
+ return result;
+ }
+ // 队列,用于存储元素
+ Queue queue = new LinkedList<>();
+ // 根节点先入队
+ queue.offer(root);
+ // 当队列不为空的时候
+ while(!queue.isEmpty()) {
+ // 队列的大小就是这一层的元素数量
+ int size = queue.size();
+ ArrayList list = new ArrayList<>();
+ // 开始遍历这一层的所有元素
+ for (int i = 0; i < size; i ++) {
+ TreeNode node = queue.poll();
+ // 如果左节点不为空,则入队,作为下一层来遍历
+ if(node.left != null) {
+ queue.offer(node.left);
+ }
+ // 同上
+ if (node.right != null) {
+ queue.offer(node.right);
+ }
+ // 存储一层的节点
+ list.add(node.val);
+ }
+ // 将一层所有的节点汇入到总的结果集中
+ result.add(list);
+ }
+ return result;
+}
+```
+
+### 二叉树的之字形层序遍历
+
+```java
+import java.util.Queue;
+import java.util.LinkedList;
+
+public class Solution {
+
+ public ArrayList> Print(TreeNode root) {
+ ArrayList> res = new ArrayList<>();
+ if (root == null)
+ return res;
+ Queue queue = new LinkedList<>();
+ queue.add(root);
+ boolean leftToRight = true;
+ while (!queue.isEmpty()) {
+ ArrayList level = new ArrayList<>();
+ //统计这一行有多少个节点
+ int count = queue.size();
+ //遍历这一行的所有节点
+ for (int i = 0; i < count; i++) {
+ //poll移除队列头部元素(队列在头部移除,尾部添加)
+ TreeNode node = queue.poll();
+ //判断是从左往右打印还是从右往左打印。
+ if (leftToRight) {
+ level.add(node.val);
+ } else {
+ level.add(0, node.val);
+ }
+ //左右子节点如果不为空会被加入到队列中
+ if (node.left != null)
+ queue.add(node.left);
+ if (node.right != null)
+ queue.add(node.right);
+ }
+ res.add(level);
+ leftToRight = !leftToRight;
+ }
+ return res;
+ }
+}
+```
+
+### 在二叉树中找到两个节点的最近公共祖先
+
+```java
+import java.util.*;
+
+public class Solution {
+ public int lowestCommonAncestor (TreeNode root, int o1, int o2) {
+ // root为空则说明越过了叶子节点
+ if(root == null) return -1;
+ // 如果root为o1或o2中任意一个,则root就是公共祖先
+ if(root.val == o1 || root.val == o2) return root.val;
+
+ //root不为o1或o2
+ int left = lowestCommonAncestor(root.left, o1, o2);
+ int right = lowestCommonAncestor(root.right, o1, o2);
+ //如果left=-1,说明在左子树中一直找到叶子节点,也没找到最近公共祖先
+ //所以最近公共祖先,必在右子树中,right即为最近公共祖先
+ if(left == -1) return right;
+ //同理,最近公共祖先必在左子树中,left即为最近公共祖先
+ else if(right == -1) return left;
+ //若left和right都不为-1,则说明o1,o2节点在root的异侧,则root为最近公共祖先
+ else return root.val;
+ }
+}
+```
+
+### 重建二叉树
+
+```java
+
+import java.util.*;
+public class Solution {
+ public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
+ if(pre.length == 0||in.length == 0){
+ return null;
+ }
+ TreeNode node = new TreeNode(pre[0]);
+ for(int i = 0; i < in.length; i++){
+ if(pre[0] == in[i]){
+ node.left = reConstructBinaryTree(
+ Arrays.copyOfRange(pre, 1, i+1),
+ Arrays.copyOfRange(in, 0, i));
+ node.right = reConstructBinaryTree(
+ Arrays.copyOfRange(pre, i+1, pre.length),
+ Arrays.copyOfRange(in, i+1,in.length));
+ }
+ }
+ return node;
+ }
+}
+```
+
+```java
+public TreeNode reConstructBinaryTree(int [] pre, int [] in) {
+ TreeNode root = rebuild(pre, 0, pre.length - 1,
+ in, 0, in.length - 1);
+ return root;
+}
+
+public TreeNode rebuild(int[] preorder, int preStart,
+ int preEnd, int[] inorder, int inStart, int inEnd) {
+ if (preStart < 0 || inStart < 0 ||
+ preStart > preEnd || inStart > inEnd) {
+ return null;
+ }
+ TreeNode root = new TreeNode(preorder[preStart]);
+
+ int index = 0;
+ for (int i = 0; i < inorder.length; i++) {
+ if (inorder[i] == root.val) {
+ index = i;
+ break;
+ }
+ }
+ int leftLen = index - inStart;
+ int rightLen = inEnd - index;
+ root.left = rebuild(preorder, preStart + 1,
+ leftLen + preStart, inorder, inStart, index - 1);
+ root.right = rebuild(preorder, leftLen + preStart + 1,
+ preEnd, inorder, index + 1, inEnd);
+ return root;
+}
+```
+
+### 输出二叉树的右视图(先重建,再输出右视图)
+
+```java
+public class Solution {
+ /**
+ * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
+ * 求二叉树的右视图
+ * @param xianxu int整型一维数组 先序遍历
+ * @param zhongxu int整型一维数组 中序遍历
+ * @return int整型一维数组
+ */
+ public int[] solve (int[] preorder, int[] inorder) {
+ // write code here
+ if (preorder == null || preorder.length < 1
+ || inorder == null || inorder.length < 1) {
+ return new int[0];
+ }
+ TreeNode root = rebuild(preorder, 0, preorder.length - 1,
+ inorder, 0, inorder.length - 1);
+ LinkedList queue = new LinkedList<>();
+ TreeNode cur = root;
+ queue.offer(cur);
+ List list = new ArrayList<>();
+ while (!queue.isEmpty()) {
+ int size = queue.size();
+ list.add(queue.peekLast().val);
+ for (int i = 0; i < size; i++) {
+ cur = queue.poll();
+ if (cur.left != null) {
+ queue.offer(cur.left);
+ }
+ if (cur.right != null) {
+ queue.offer(cur.right);
+ }
+ }
+ }
+ int[] res = new int[list.size()];
+ for (int i = 0; i < res.length; i++) {
+ res[i] = list.get(i);
+ }
+ return res;
+ }
+
+ public TreeNode rebuild(int[] preorder, int preStart,
+ int preEnd, int[] inorder, int inStart, int inEnd) {
+ if (preStart < 0 || inStart < 0 ||
+ preStart > preEnd || inStart > inEnd) {
+ return null;
+ }
+ TreeNode root = new TreeNode(preorder[preStart]);
+
+ int index = 0;
+ for (int i = 0; i < inorder.length; i++) {
+ if (inorder[i] == root.val) {
+ index = i;
+ break;
+ }
+ }
+ int leftLen = index - inStart;
+ int rightLen = inEnd - index;
+ root.left = rebuild(preorder, preStart + 1,
+ leftLen + preStart, inorder, inStart, index - 1);
+ root.right = rebuild(preorder, leftLen + preStart + 1,
+ preEnd, inorder, index + 1, inEnd);
+ return root;
+ }
+
+ public static class TreeNode {
+ public int val;
+ public TreeNode left;
+ public TreeNode right;
+
+ public TreeNode(int val) {
+ this.val = val;
+ }
+ }
+}
+```
+
+### 二叉树的最大深度
+
+```java
+public int maxDepth(TreeNode root) {
+ return root==null? 0 :
+ Math.max(maxDepth(root.left), maxDepth(root.right))+1;
+}
+```
+
+### 判断是不是平衡二叉树
+
+```java
+import java.util.*;
+public class Solution {
+ public boolean IsBalanced_Solution(TreeNode root) {
+ //可以分别求出左右子树的高度,然后进行对比
+ return TreeDepth(root) >= 0;
+ }
+ //求二叉树深度的方法
+ public int TreeDepth(TreeNode root) {
+ if (root == null) {
+ return 0;
+ }
+ int leftHeight = TreeDepth(root.left);
+ int rightHeight = TreeDepth(root.right);
+ if (leftHeight == -1 || rightHeight == -1
+ || Math.abs(leftHeight - rightHeight) > 1) {
+ return -1;
+ } else {
+ return Math.max(leftHeight, rightHeight) + 1;
+ }
+ }
+}
+```
+
+### 二叉树根节点到叶子节点的所有路径和
+
+```java
+public class Solution {
+ /**
+ *
+ * @param root TreeNode类
+ * @return int整型
+ */
+ public int sumNumbers (TreeNode root) {
+ // 调用dfs
+ return dfs(root,0);
+ }
+ //深度优先搜索
+ public int dfs(TreeNode root,int sum){
+ if(root==null){
+ return 0;
+ }
+ int total = sum*10+root.val;
+ //已达到叶子节点,返回结果
+ if(root.left==null && root.right==null){
+ return total;
+ }else{
+ //递归调用
+ return dfs(root.left,total)+dfs(root.right,total);
+ }
+
+ }
+}
+```
+
+### 二叉树中和为某一值的路径,返回所有路径
+
+```java
+import java.util.ArrayList;
+public class Solution {
+ ArrayList> lists = new ArrayList<>();
+ ArrayList list = new ArrayList<>();
+ public ArrayList> FindPath(TreeNode root,int target) {
+ if(root == null){
+ return lists;
+ }
+ list.add(root.val);
+ target -= root.val;
+ if(target == 0 && root.left == null
+ && root.right == null){
+ lists.add(new ArrayList(list));
+ }
+ FindPath(root.left,target);
+ FindPath(root.right,target);
+ list.remove(list.size() - 1);
+ return lists;
+ }
+}
+```
+
+### 判断一棵二叉树是否为搜索二叉树和完全二叉树
+
+```java
+int num=-1;
+boolean flag = false;
+public boolean[] judgeIt (TreeNode root) {
+ // write code here
+ return new boolean[]{isSearch(root) ,isFull(root)};
+}
+
+public boolean isSearch(TreeNode root){
+ if(root==null){
+ return true;
+ }
+ boolean left = isSearch(root.left);
+ if(num>=root.val){
+ return false;
+ }
+ num=root.val;
+ boolean right = isSearch(root.right);
+ return left && right;
+}
+
+public boolean isFull(TreeNode root){
+ Queue queue = new LinkedList<>();
+ queue.add(root);
+ while(!queue.isEmpty()){
+ TreeNode node = queue.poll();
+ if(node==null){
+ flag=true;
+ }else{
+ if(flag){
+ return false;
+ }else{
+ queue.add(node.left);
+ queue.add(node.right);
+ }
+ }
+ }
+ return true;
+}
+```
+
+### 二叉树的最大路径和
+
+```java
+public class Solution {
+ int max = Integer.MIN_VALUE;
+ /**
+ *
+ * @param root TreeNode类
+ * @return int整型
+ */
+ public int maxPathSum (TreeNode root) {
+ // write code here
+ maxSum(root);
+ return max;
+ }
+
+ public int maxSum(TreeNode root){
+ if(root == null){
+ return 0;
+ }
+
+ //三种情况:1.包含一个子树和顶点,2.仅包含顶点,3.包含左子树和右子树以及顶点。
+ int left = Math.max(maxSum(root.left),0);
+ int right = Math.max(maxSum(root.right),0);
+
+ max = Math.max(max,left+right+root.val);
+
+ //对于每一个子树,返回包含该子树顶点的深度方向的路径和的最大值。
+ return root.val + Math.max(left,right);
+ }
+}
+```
+
+### 判断二叉树是否对称
+
+```java
+public class Solution {
+ /**
+ *
+ * @param root TreeNode类
+ * @return bool布尔型
+ */
+ public boolean isSymmetric (TreeNode root) {
+ // write code here
+ return isSymmetricNode(root,root);
+ }
+
+ public boolean isSymmetricNode(TreeNode node1, TreeNode node2){
+ if(node1 == null && node2 == null){
+ return true;
+ }
+ if(node1 == null || node2 == null){
+ return false;
+ }
+ if(node1.val != node2.val){
+ return false;
+ }
+ return isSymmetricNode(node1.left,node2.right)
+ && isSymmetricNode(node1.right,node2.left);
+ }
+}
+```
+
+### 二叉树中是否存在节点和为指定值的路径
+
+```java
+public class Solution {
+ /**
+ *
+ * @param root TreeNode类
+ * @param sum int整型
+ * @return bool布尔型
+ */
+ public boolean hasPathSum (TreeNode root, int sum) {
+ // write code here
+ if(root == null){
+ return false;
+ }
+
+ if(root.left == null && root.right == null){
+ return sum - root.val == 0;
+ }
+
+ return hasPathSum(root.left,sum - root.val) ||
+ hasPathSum(root.right,sum - root.val);
+ }
+}
+```
+
+### 序列化二叉树
+
+```java
+import java.util.*;
+/*
+public class TreeNode {
+ int val = 0;
+ TreeNode left = null;
+ TreeNode right = null;
+
+ public TreeNode(int val) {
+ this.val = val;
+
+ }
+
+}
+*/
+public class Solution {
+ private int index = -1;
+
+ public String Serialize(TreeNode root) {
+ StringBuilder builder = new StringBuilder();
+ intervalSerialize(builder, root);
+ return builder.toString();
+ }
+
+ private void intervalSerialize(StringBuilder builder, TreeNode root) {
+ if (root == null) {
+ builder.append("#,");
+ return;
+ }
+ builder.append(root.val).append(",");
+ intervalSerialize(builder, root.left);
+ intervalSerialize(builder, root.right);
+ }
+
+ public TreeNode Deserialize(String str) {
+ if (str == null || str.isEmpty()) {
+ return null;
+ }
+ String[] words = str.split(",");
+ return internalDeserialize(words);
+ }
+
+ private TreeNode internalDeserialize(String[] words) {
+ index++;
+ if (index == words.length) {
+ return null;
+ }
+ if ("#".equals(words[index])) {
+ return null;
+ }
+ TreeNode root = new TreeNode(Integer.parseInt(words[index]));
+ root.left = internalDeserialize(words);
+ root.right = internalDeserialize(words);
+ return root;
+ }
+}
+```
+
+### 二叉搜索树的第k个结点
+
+```java
+public class Solution {
+ int index = 0;
+ TreeNode target = null;
+ TreeNode KthNode(TreeNode pRoot, int k){
+ getKthNode(pRoot,k);
+ return target;
+ }
+
+ public void getKthNode(TreeNode pRoot, int k){
+ if(pRoot == null){
+ return;
+ }
+ getKthNode(pRoot.left,k);
+ index++;
+ if(index == k){
+ target = pRoot;
+ return;
+ }
+ getKthNode(pRoot.right,k);
+ }
+}
+```
+
+### 把二叉树打印成多行
+
+```java
+import java.util.*;
+
+public class Solution {
+ ArrayList > Print(TreeNode pRoot) {
+ if(pRoot == null){
+ return new ArrayList>();
+ }
+ ArrayList> list = new ArrayList<>();
+
+ Queue queue = new LinkedList<>();
+ queue.add(pRoot);
+ while(!queue.isEmpty()){
+ ArrayList temp = new ArrayList<>();
+ for(int i = queue.size(); i > 0; i--){
+ TreeNode node = queue.poll();
+ temp.add(node.val);
+ if(node.left != null){
+ queue.add(node.left);
+ }
+ if(node.right != null){
+ queue.add(node.right);
+ }
+ }
+ list.add(temp);
+ }
+
+ return list;
+ }
+
+}
+```
+
+### 二叉树的镜像
+
+```java
+public class Solution {
+ public void Mirror(TreeNode root) {
+ if(root == null){
+ return;
+ }
+ if(root.left == null && root.right == null){
+ return;
+ }
+ Stack stack = new Stack<>();
+ stack.push(root);
+ while(!stack.isEmpty()){
+ TreeNode node = stack.pop();
+
+ if(node.left != null || node.right != null){
+ TreeNode temp = node.left;
+ node.left = node.right;
+ node.right = temp;
+ }
+
+ if(node.left != null){
+ stack.push(node.left);
+ }
+
+ if(node.right != null){
+ stack.push(node.right);
+ }
+ }
+ }
+}
+
+/*
+public class Solution {
+ public void Mirror(TreeNode root) {
+ if(root == null){
+ return;
+ }
+ if(root.left == null && root.right == null){
+ return;
+ }
+
+ TreeNode temp = root.left;
+ root.left = root.right;
+ root.right = temp;
+
+ if(root.left != null){
+ Mirror(root.left);
+ }
+
+ if(root.right != null){
+ Mirror(root.right);
+ }
+ }
+}
+*/
+
+```
+
+### 判断t1树中是否有与t2树拓扑结构完全相同的子树
+
+```java
+/**
+ *
+ * @param root1 TreeNode类
+ * @param root2 TreeNode类
+ * @return bool布尔型
+ */
+public boolean isContains (TreeNode root1, TreeNode root2) {
+ // write code here
+ if(root1 == null || root2 == null){
+ return false;
+ }
+ return recur(root1, root2) || isContains(root1.left,root2)
+ || isContains(root1.right,root2);
+}
+
+public boolean recur(TreeNode root1, TreeNode root2){
+ if(root2 == null){
+ return true;
+ }
+ if(root1 == null || root1.val != root2.val){
+ return false;
+ }
+ return recur(root1.left,root2.left) &&
+ recur(root1.right,root2.right);
+}
+```
+
+### 合并二叉树
+
+```java
+import java.util.*;
+
+public class Solution {
+ /**
+ *
+ * @param t1 TreeNode类
+ * @param t2 TreeNode类
+ * @return TreeNode类
+ */
+ public TreeNode mergeTrees (TreeNode t1, TreeNode t2) {
+ if(t1==null) return t2;
+ if(t2==null) return t1;
+ TreeNode temp=new TreeNode(t1.val+t2.val);
+ temp.left=mergeTrees(t1.left,t2.left);
+ temp.right=mergeTrees(t1.right,t2.right);
+ return temp;
+ }
+}
+```
+
+### 字典树的实现
+
+
+
+```java
+import java.util.*;
+
+public class Solution {
+ /**
+ *
+ * @param operators string字符串二维数组 the ops
+ * @return string字符串一维数组
+ */
+ public String[] trieU (String[][] operators) {
+ //计算结果集长度,并进行初始化
+ int len=0;
+ for(String[] opera:operators){
+ if(opera[0].equals("3")||opera[0].equals("4")){
+ len++;
+ }
+ }
+ String[] res=new String[len];
+ Trie trie=new Trie();
+ int id=0;
+
+ for(String[] opera:operators){
+ if(opera[0].equals("1")){
+ //添加单词
+ trie.insert(opera[1]);
+ }
+ else if(opera[0].equals("2")){
+ //删除单词
+ trie.delete(opera[1]);
+ }
+ else if(opera[0].equals("3")){
+ //查询单词是否存在
+ res[id++]=trie.search(opera[1])?"YES":"NO";
+ }
+ else if(opera[0].equals("4")){
+ //查找以word为前缀的单词数量
+ String preNumber=String.valueOf(trie.prefixNumber(opera[1]));
+ res[id++]=preNumber;
+ }
+ }
+ return res;
+ }
+
+ class Trie{
+ //构建字典树节点
+ class TrieNode{
+ //child数组记录所有子节点
+ TrieNode[] child;
+ //pre_number表示插入单词时,当前节点被访问次数
+ int pre_number;
+ //end表示当前节点是否是某个单词的末尾
+ boolean end;
+ TrieNode(){
+ child=new TrieNode[26];
+ pre_number=0;
+ end=false;
+ }
+ }
+
+ Trie(){}
+
+ //初始化根节点
+ TrieNode root=new TrieNode();
+
+ //添加单词
+ void insert(String word){
+ TrieNode node=root;
+ char[] arr=word.toCharArray();
+ for(char c:arr){
+ //如果子节点不存在,则新建
+ if(node.child[c-'a']==null){
+ node.child[c-'a']=new TrieNode();
+ }
+ //往子节点方向移动
+ node=node.child[c-'a'];
+ node.pre_number++;
+ }
+ node.end=true;
+ }
+
+ void delete(String word){
+ TrieNode node=root;
+ char[] arr=word.toCharArray();
+ for(char c:arr){
+ //往子节点方向移动,将访问次数减一
+ node=node.child[c-'a'];
+ node.pre_number--;
+ }
+ //如果访问次数为0,说明不存在该单词为前缀的单词,以及该单词
+ if(node.pre_number==0){
+ node.end=false;
+ }
+ }
+
+ boolean search(String word){
+ TrieNode node=root;
+ char[] arr=word.toCharArray();
+ for(char c:arr){
+ //如果子节点不存在,说明不存在该单词
+ if(node.child[c-'a']==null){
+ return false;
+ }
+ node=node.child[c-'a'];
+ }
+
+ //如果前面的节点都存在,并且该节点末尾标识为true,则存在该单词
+ return node.end;
+ }
+
+ int prefixNumber(String pre){
+ TrieNode node=root;
+ char[] arr=pre.toCharArray();
+ for(char c:arr){
+ //如果子节点不存在,说明不存在该前缀
+ if(node.child[c-'a']==null){
+ return 0;
+ }
+ node=node.child[c-'a'];
+ }
+
+ //返回以该单词为前缀的数量
+ return node.pre_number;
+ }
+ }
+}
+```
+
+### 找到二叉搜索树中的两个错误节点
+
+
+
+```java
+import java.util.*;
+public class Solution {
+ /**
+ *
+ * @param root TreeNode类 the root
+ * @return int整型一维数组
+ */
+
+ //存储结果集的二维数组
+ int[] result = new int[2];
+ int index = 1;
+ TreeNode preNode;
+ public int[] findError (TreeNode root) {
+ // 特判
+ if(root == null) {
+ return result;
+ }
+ // 递归左子树,寻找该树符合条件的节点
+ findError(root.left);
+ if(preNode == null) {
+ preNode = root;
+ }
+ // 判断是否是出错的节点
+ if(index == 1 && root.val < preNode.val) {
+ result[index] = preNode.val;
+ index--;
+ }
+ if(index == 0 && root.val < preNode.val) {
+ result[index] = root.val;
+ }
+ preNode = root;
+ // 递归右子树,寻找该树符合条件的节点
+ findError(root.right);
+ return result;
+ }
+}
+```
+
+## 堆
+
+### 最小的K个数
+
+```java
+import java.util.*;
+public class Solution {
+ public ArrayList GetLeastNumbers_Solution(int [] input, int k) {
+ ArrayList res = new ArrayList();
+ //排除特殊情况
+ if(k == 0 || input.length == 0)
+ return res;
+ //大根堆
+ PriorityQueue q =
+ new PriorityQueue<>((o1, o2)->o2.compareTo(o1));
+ //构建一个k个大小的堆
+ for(int i = 0; i < k; i++)
+ q.offer(input[i]);
+ for(int i = k; i < input.length; i++){
+ //较小元素入堆
+ if(q.peek() > input[i]){
+ q.poll();
+ q.offer(input[i]);
+ }
+ }
+ //堆中元素取出入数组
+ for(int i = 0; i < k; i++)
+ res.add(q.poll());
+ return res;
+ }
+}
+
+// 自己实现堆排序
+public class Solution {
+ public ArrayList
+ GetLeastNumbers_Solution(int [] input, int k) {
+ ArrayList list = new ArrayList<>();
+ if (input == null || input.length == 0
+ || k > input.length || k == 0)
+ return list;
+ int[] arr = new int[k + 1];//数组下标0的位置作为哨兵,不存储数据
+ //初始化数组
+ for (int i = 1; i < k + 1; i++)
+ arr[i] = input[i - 1];
+ buildMaxHeap(arr, k + 1);//构造大根堆
+ for (int i = k; i < input.length; i++) {
+ if (input[i] < arr[1]) {
+ arr[1] = input[i];
+ adjustDown(arr, 1, k + 1);//将改变了根节点的二叉树继续调整为大根堆
+ }
+ }
+ for (int i = 1; i < arr.length; i++) {
+ list.add(arr[i]);
+ }
+ return list;
+ }
+ /**
+ * @Author: ZwZ
+ * @Description: 构造大根堆
+ * @Param: [arr, length] length:数组长度 作为是否跳出循环的条件
+ * @return: void
+ * @Date: 2020/1/30-22:06
+ */
+ public void buildMaxHeap(int[] arr, int length) {
+ if (arr == null || arr.length == 0 || arr.length == 1)
+ return;
+ for (int i = (length - 1) / 2; i > 0; i--) {
+ adjustDown(arr, i, arr.length);
+ }
+ }
+ /**
+ * @Author: ZwZ
+ * @Description: 堆排序中对一个子二叉树进行堆排序
+ * @Param: [arr, k, length]
+ * @return:
+ * @Date: 2020/1/30-21:55
+ */
+ public void adjustDown(int[] arr, int k, int length) {
+ arr[0] = arr[k];//哨兵
+ for (int i = 2 * k; i <= length; i *= 2) {
+ if (i < length - 1 && arr[i] < arr[i + 1])
+ i++;//取k较大的子结点的下标
+ if (i > length - 1 || arr[0] >= arr[i])
+ break;
+ else {
+ arr[k] = arr[i];
+ k = i; //向下筛选
+ }
+ }
+ arr[k] = arr[0];
+ }
+}
+```
+
+### 字符串出现次数的TopK问题
+
+```java
+public class Solution {
+ public String[][] topKstrings (String[] strings, int k) {
+ String[][] res = new String[k][2];
+ //记录字符出现次数
+ HashMap map = new HashMap<>();
+ for(int i = 0; i < strings.length; i++){
+ if(map.containsKey(strings[i])){
+ map.put(strings[i], map.get(strings[i]) + 1);
+ }else{
+ map.put(strings[i], 1);
+ }
+ }
+ //建立小根堆,自定义比较器(次数值value相同,
+ // 比较key的字典序,不相同直接比较次数值value)
+ PriorityQueue> pq =
+ new PriorityQueue<>((o1,o2) -> o1.getValue().equals(o2.getValue())
+ ? o2.getKey().compareTo(o1.getKey()) : o1.getValue()-o2.getValue());
+ int size = 0;
+ //维护size为k的小根堆
+ for(Map.Entry m : map.entrySet()){
+ if(size < k){
+ pq.offer(m);
+ size++;
+ }
+ //大于堆顶元素插入
+ else if((m.getValue().equals(pq.peek().getValue())
+ ? pq.peek().getKey().compareTo(m.getKey())
+ : m.getValue() - pq.peek().getValue()) > 0){
+ pq.poll();
+ pq.offer(m);
+ }
+ }
+ //取出堆中元素,从后向前放置
+ for(int i = k - 1; i >= 0; i--){
+ Map.Entry entry =(Map.Entry)pq.poll();
+ res[i][0] = entry.getKey();
+ res[i][1] = String.valueOf(entry.getValue());
+ }
+ return res;
+ }
+}
+```
+
+### 寻找第K大
+
+```java
+public int findKth(int[] a, int n, int K){
+ // 暂存K个较大的值,优先队列默认是自然排序(升序),
+ // 队头元素(根)是堆内的最小元素,也就是小根堆
+ PriorityQueue queue = new PriorityQueue<>(K);
+ // 遍历每一个元素,调整小根堆
+ for (int num : a) {
+ // 对于小根堆来说,只要没满就可以加入(不需要比较);
+ // 如果满了,才判断是否需要替换第一个元素
+ if (queue.size() < K) {
+ queue.add(num);
+ } else {
+ // 在小根堆内,存储着K个较大的元素,根是这K个中最小的,
+ // 如果出现比根还要大的元素,说明可以替换根
+ if (num > queue.peek()) {
+ queue.poll(); // 高个中挑矮个,矮个淘汰
+ queue.add(num);
+ }
+ }
+ }
+ return queue.isEmpty() ? 0 : queue.peek();
+}
+```
+
+```java
+import java.util.*;
+
+public class Finder {
+ public int findKth(int[] a, int n, int K) {
+ // write code here
+ return find(a, 0, n-1, K);
+ }
+
+ public int find(int[] a, int low, int high, int K){
+ int pivot = partition(a, low, high);
+
+ if(pivot + 1 < K){
+ return find(a, pivot + 1, high, K);
+ } else if(pivot + 1 > K){
+ return find(a, low, pivot - 1, K);
+ } else {
+ return a[pivot];
+ }
+ }
+
+ int partition(int arr[], int startIndex, int endIndex){
+ int small = startIndex - 1;
+ for (int i = startIndex; i < endIndex; ++i) {
+ if(arr[i] > arr[endIndex]) {
+ swap(arr,++small, i);
+ }
+ }
+ swap(arr,++small,endIndex);
+ return small;
+ }
+
+ public void swap(int[] arr, int i, int j){
+ int temp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = temp;
+ }
+}
+
+```
+
+## 双指针
+
+### 最长无重复子数组的长度
+
+```java
+// 方法1
+public int maxLength (int[] arr) {
+ int left = 0, right = 0;
+ Set set = new HashSet<>();
+ int res = 1;
+ while(right < arr.length){
+ if(!set.contains(arr[right])){
+ set.add(arr[right]);
+ right++;
+ }else{
+ set.remove(arr[left]);
+ left++;
+ }
+ res = Math.max(res, set.size());
+ }
+ return res;
+}
+
+// 方法2
+public int maxLength(int[] arr) {
+ if (arr.length == 0)
+ return 0;
+ HashMap map = new HashMap<>();
+ int max = 0;
+ for (int i = 0, j = 0; i < arr.length; ++i) {
+ if (map.containsKey(arr[i])) {
+ j = Math.max(j, map.get(arr[i]) + 1);
+ }
+ map.put(arr[i], i);
+ max = Math.max(max, i - j + 1);
+ }
+ return max;
+}
+```
+
+### 滑动窗口的最大值
+
+```java
+import java.util.*;
+/**
+用一个双端队列,队列第一个位置保存当前窗口的最大值,当窗口滑动一次
+1.判断当前最大值是否过期
+2.新增加的值从队尾开始比较,把所有比他小的值丢掉
+*/
+public class Solution {
+ public ArrayList maxInWindows(int [] num, int size)
+ {
+ ArrayList res = new ArrayList<>();
+ if(size == 0) return res;
+ int begin;
+ ArrayDeque q = new ArrayDeque<>();
+ for(int i = 0; i < num.length; i++){
+ begin = i - size + 1;
+ if(q.isEmpty())
+ q.add(i);
+ else if(begin > q.peekFirst())
+ q.pollFirst();
+
+ while((!q.isEmpty()) && num[q.peekLast()] <= num[i])
+ q.pollLast();
+ q.add(i);
+ if(begin >= 0)
+ res.add(num[q.peekFirst()]);
+ }
+ return res;
+ }
+}
+```
+
+### 合并区间(区间重叠)
+
+首先将各个区间进行排序,排序规则为首先根据start进行排序,
+如果start相等则根据end从小到大排序new一个新的List result存放结果,
+遍历给定的intervals,比较当前interval的start是否大于result中最后一个元素的end,
+若大于,说明从开了一个区间,若区间有重叠,则更新result中最后一个元素的end。
+
+```java
+import java.util.*;
+/**
+ * Definition for an interval.
+ * public class Interval {
+ * int start;
+ * int end;
+ * Interval() { start = 0; end = 0; }
+ * Interval(int s, int e) { start = s; end = e; }
+ * }
+ */
+public class Solution {
+ public ArrayList merge(ArrayList intervals) {
+ // 首先根据start排序,如果start相等,根据end排序
+ Collections.sort(intervals,
+ (o1, o2) -> (o1.start != o2.start
+ ? o1.start - o2.start : o1.end - o2.end));
+ ArrayList result = new ArrayList<>();
+ if(intervals.size() == 0) {
+ return result;
+ }
+ // 放入第一个区间
+ result.add(intervals.get(0));
+ int count = 0;
+ // 遍历后续区间,查看是否与末尾有重叠
+ for(int i = 1; i < intervals.size(); i++) {
+ Interval o1 = intervals.get(i);
+ Interval origin = result.get(result.size() - 1);
+ // 如果当前Interval的start比List里面最后一个元素的end大,说明从开一个区间
+ if(o1.start > origin.end) {
+ result.add(o1);
+ } else { // 区间有重叠,更新结尾
+ if(o1.end > origin.end) {
+ result.get(result.size() - 1).end = o1.end;
+ }
+ }
+ }
+ return result;
+ }
+}
+```
+
+### 反转字符串
+
+```java
+import java.util.*;
+public class Solution {
+ /**
+ * 反转字符串
+ * @param str string字符串
+ * @return string字符串
+ */
+ public String solve (String str) {
+ // write code here
+ if(str == null){
+ return null;
+ }
+ char[] c = new char[str.length()];
+ int left = 0, right = str.length() - 1;
+
+ while(left <= right){
+ c[left] = str.charAt(right);
+ c[right] = str.charAt(left);
+ left++;
+ right--;
+ }
+ return new String(c);
+ }
+}
+```
+
+### 数组中相加和为0的三元组
+
+```java
+import java.util.*;
+
+public class Solution {
+ public ArrayList> threeSum(int[] num) {
+ ArrayList> list = new ArrayList<>();
+ Arrays.sort(num);
+ int left,right,sum;
+ for(int i = 0; i < num.length - 2; i++){
+ if(i > 0 && num[i] == num[i-1]) continue;
+ left = i + 1;
+ right = num.length - 1;
+ while(left < right){
+ sum = num[i] + num[left] + num[right];
+ if(sum == 0){
+ ArrayList temp = new ArrayList<>();
+ temp.add(num[i]);
+ temp.add(num[left]);
+ temp.add(num[right]);
+ list.add(temp);
+ right--;
+ left++;
+ while(left < right && num[left] == num[left-1]){
+ left++;
+ }
+ while(left < right && num[right] == num[right+1]){
+ right--;
+ }
+ } else if(sum < 0){
+ left++;
+ } else {
+ right--;
+ }
+ }
+ }
+ return list;
+ }
+}
+```
+
+### 接雨水问题
+
+```java
+ public long maxWater(int[] arr) {
+ if (arr.length <= 2)
+ return 0;
+ //找到最高的柱子的下标
+ int max = Integer.MIN_VALUE;
+ int maxIndex = -1;
+ for (int i = 0; i < arr.length; i++) {
+ if (arr[i] > max) {
+ max = arr[i];
+ maxIndex = i;
+ }
+ }
+
+ //统计最高柱子左边能接的雨水数量
+ int left = arr[0];
+ int right = 0;
+ long water = 0;
+ for (int i = 1; i < maxIndex; i++) {
+ right = arr[i];
+ if (right > left) {
+ left = right;
+ } else {
+ water += left - right;
+ }
+ }
+
+ //统计最高柱子右边能接的雨水数量
+ right = arr[arr.length - 1];
+ for (int i = arr.length - 2; i > maxIndex; i--) {
+ left = arr[i];
+ if (arr[i] > right) {
+ right = left;
+ } else {
+ water += right - left;
+ }
+ }
+
+ //返回盛水量
+ return water;
+ }
+```
+
+```java
+public long maxWater (int[] arr) {
+ int l = 0, r = arr.length-1;
+ int maxL = 0, maxR = 0;
+ long res = 0;
+ while(l < r){
+ maxL = Math.max(arr[l],maxL); // 求出左边界的最大值
+ maxR = Math.max(arr[r],maxR); // 求出右边界的最大值
+ if(maxR > maxL){ // 如果
+ res += maxL - arr[l++];
+ }else{
+ res += maxR - arr[r--];
+ }
+ }
+ return res;
+}
+```
+
+### 最小覆盖子串(T包含S的最小子串)
+
+```java
+import java.util.*;
+
+public class Solution {
+ /**
+ *
+ * @param S string字符串
+ * @param T string字符串
+ * @return string字符串
+ */
+ public String minWindow (String s, String t) {
+ HashMap window = new HashMap<>();
+ HashMap need = new HashMap<>();
+ for (int i = 0; i < t.length(); i++) {
+ Integer count = need.get(t.charAt(i));
+ count = count == null ? 1 : ++count;
+ need.put(t.charAt(i),count);
+ }
+ int left =0 , right = 0;
+ int vaild = 0;
+ int len = Integer.MAX_VALUE,start = 0;
+ //最小覆盖字串起始索引
+ while (right < s.length()){
+ char c = s.charAt(right);
+ right++;
+ if (need.containsKey(c)){
+ Integer count = window.get(c);
+ count = count == null ? 1 : ++count;
+ window.put(c,count);
+ if (window.get(c).equals(need.get(c))){
+ vaild++;
+ }
+ }
+
+ //都包含了,right找到了,可以考虑收缩
+ while (vaild == need.size()){
+ if (right -left < len){
+ start = left;
+ len = right - left;
+ }
+ //d是将要移出窗口的字符
+ char d = s.charAt(left);
+ //左移窗口
+ left++;
+ //数据更新
+ if (need.containsKey(d)){
+ if (window.get(d).equals(need.get(d))){
+ vaild--;
+ }
+ window.put(d,window.get(d)-1);
+ }
+ }
+ }
+ return len == Integer.MAX_VALUE
+ ? "" : s.substring(start,start+len);
+ }
+}
+```
+
+### 两数之和
+
+```java
+import java.util.HashMap;
+public class Solution {
+ public int[] twoSum(int[] nums, int target) {
+ HashMap map = new HashMap<>();
+ for (int i = 0; i < nums.length; i++) {
+ if (map.containsKey(nums[i])){
+ return new int[]{map.get(nums[i])+1,i+1};
+ }
+ map.put(target - nums[i],i);
+ }
+ return null;
+ }
+}
+```
+
+### 最长重复子串(连续两个相同的字符串)
+
+```java
+import java.util.*;
+public class Solution {
+ // 使用滑动窗口 滑动窗口中的字符串是重复字符串的一半
+ public int solve (String a) {
+ //枚举长度为i的窗口(按窗口大小倒序枚举),找到第一个满足条件的窗口(窗口为重复子串)
+ char[] cs = a.toCharArray();
+ int len = cs.length;
+ int cnt = 0;
+ // 滑动窗口的大小不可能大于数组长度的一半
+ for (int i=len/2; i>0; i--) { //枚举一半窗口大小
+ //窗口右侧节点范围len-i
+ for (int j=0; j max){
+ max = dp[i];
+ }
+ }
+ return max;
+}
+
+// sum < 0置为0
+public int FindGreatestSumOfSubArray(int[] array) {
+ if(array.length == 0){
+ return 0;
+ }
+
+ int max = Integer.MIN_VALUE;
+ int cur = 0;
+ for(int i = 0; i < array.length; i++){
+ cur += array[i];
+ max = Math.max(max,cur);
+ cur = cur < 0 ? 0 : cur;
+ }
+ return max;
+}
+```
+
+### 最长公共子串(返回具体字符串/长度)
+
+```java
+// 返回字符串
+public class Solution {
+ public String LCS (String str1, String str2) {
+ // write code here
+ int m = str1.length();
+ int n = str2.length();
+ int[][] dp = new int[m][n];
+ int maxLength = 0;
+ int lastIndex = 0;
+ //用来记录str1中最长公共串的最后一个字符的下标
+ for(int i = 0; i < m; i++){
+ for(int j = 0; j < n; j++){
+ if(str1.charAt(i) == str2.charAt(j)){
+ //判断str1中第i个字符是否和str2中第j个字符相等
+ if(i == 0 || j == 0){
+ dp[i][j] = 1;
+ }else{
+ dp[i][j] = dp[i - 1][j - 1] + 1;
+ }
+
+ if(dp[i][j] > maxLength){
+ //判断是否需要更新最长公共子串
+ maxLength = dp[i][j];
+ lastIndex = i;
+ }
+ }
+ }
+ }
+
+ //通过str1来截取长度为maxLength, 最后字符坐标为lastIndex的子串
+ return str1.substring(lastIndex - maxLength + 1,
+ lastIndex + 1);
+ }
+}
+
+// 返回长度
+public class Solution {
+ public int LCS (String s1, String s2) {
+ // write code here
+ int mLength = s1.length();
+ int nLength = s2.length();
+ int[][] dp = new int[mLength + 1][nLength + 1];
+ char[] c1 = s1.toCharArray();
+ char[] c2 = s2.toCharArray();
+ for (int i = 0; i <= mLength; i++) {
+ dp[i][0] = 0;
+ }
+ for (int j = 0; j <= nLength; j++) {
+ dp[0][j] = 0;
+ }
+ for (int i = 1; i <= mLength; i++) {
+ for (int j = 1; j <= nLength; j++) {
+ if (c1[i - 1] != c2[j - 1]) {
+ dp[i][j] =
+ Math.max(dp[i- 1][j], dp[i][j - 1]);
+ } else {
+ dp[i][j] = dp[i - 1][j - 1] + 1;
+ }
+ }
+ }
+ return dp[mLength][nLength];
+ }
+}
+```
+
+### 斐波那契数列
+
+```java
+public class Solution {
+ public int Fibonacci(int n) {
+ if(n == 0){
+ return 0;
+ }
+ if(n == 1 || n == 2){
+ return 1;
+ }
+ return Fibonacci(n-1) + Fibonacci(n-2);
+ }
+}
+```
+
+### 最长回文子串的长度
+
+```java
+public String longestPalindrome1(String s) {
+ if (s == null || s.length() == 0) {
+ return "";
+ }
+ int strLen = s.length();
+ int left = 0;
+ int right = 0;
+ int len = 1;
+ int maxStart = 0;
+ int maxLen = 0;
+
+ for (int i = 0; i < strLen; i++) {
+ left = i - 1;
+ right = i + 1;
+ while (left >= 0 &&
+ s.charAt(left) == s.charAt(i)) {
+ len++;
+ left--;
+ }
+ while (right < strLen &&
+ s.charAt(right) == s.charAt(i)) {
+ len++;
+ right++;
+ }
+ while (left >= 0 && right < strLen
+ && s.charAt(right) == s.charAt(left)) {
+ len = len + 2;
+ left--;
+ right++;
+ }
+ if (len > maxLen) {
+ maxLen = len;
+ maxStart = left;
+ }
+ len = 1;
+ }
+ return s.substring(maxStart + 1,
+ maxStart + maxLen + 1);
+
+}
+```
+
+### 最长递增子序列
+
+```java
+// 求长度
+class Solution {
+ public int lengthOfLIS(int[] nums) {
+ if(nums.length == 0) return 0;
+ int[] dp = new int[nums.length];
+ int res = 0;
+ Arrays.fill(dp, 1);
+ for(int i = 0; i < nums.length; i++) {
+ for(int j = 0; j < i; j++) {
+ if(nums[j] < nums[i])
+ dp[i] = Math.max(dp[i], dp[j] + 1);
+ }
+ res = Math.max(res, dp[i]);
+ }
+ return res;
+ }
+}
+```
+
+```java
+public int[] LIS (int[] arr) {
+ // write code here
+ if(arr == null || arr.length <= 0){
+ return null;
+ }
+
+ int len = arr.length;
+ int[] count = new int[len]; // 存长度
+ int[] end = new int[len]; // 存最长递增子序列
+
+ //init
+ int index = 0; // end 数组下标
+ end[index] = arr[0];
+ count[0] = 1;
+
+ for(int i = 0; i < len; i++){
+ if(end[index] < arr[i]){
+ end[++index] = arr[i];
+ count[i] = index;
+ }
+ else{
+ int left = 0, right = index;
+ while(left <= right){
+ int mid = (left + right) >> 1;
+ if(end[mid] >= arr[i]){
+ right = mid - 1;
+ }
+ else{
+ left = mid + 1;
+ }
+ }
+ end[left] = arr[i];
+ count[i] = left;
+ }
+ }
+
+ //因为返回的数组要求是字典序,所以从后向前遍历
+ int[] res = new int[index + 1];
+ for(int i = len - 1; i >= 0; i--){
+ if(count[i] == index){
+ res[index--] = arr[i];
+ }
+ }
+ return res;
+}
+```
+
+### 买卖股票的最佳时机
+
+base case:
+dp[-1][k][0] = dp[i][0][0] = 0
+dp[-1][k][1] = dp[i][0][1] = -infinity
+
+状态转移⽅程:
+dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
+dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
+
+- k == 1
+
+```java
+public class Solution {
+ /**
+ *
+ * @param prices int整型一维数组
+ * @return int整型
+ */
+ public int maxProfit (int[] prices) {
+ if(prices.length == 0) return 0;
+ // write code here
+ int n = prices.length;
+ int[][] dp = new int[n][2];
+ for(int i = 0; i < n; i++){
+ if(i - 1 == -1){
+ dp[i][0] = 0;
+ dp[i][1] = -prices[i];
+ continue;
+ }
+ dp[i][0] = Math.max(dp[i-1][0],
+ dp[i-1][1] + prices[i]);
+ dp[i][1] = Math.max(dp[i-1][1],-prices[i]);
+ }
+
+ return dp[n-1][0];
+ }
+}
+
+// 空间复杂度优化版本
+int maxProfit(int[] prices) {
+ int n = prices.length;
+ // base case: dp[-1][0] = 0, dp[-1][1] = -infinity
+ int dp_i_0 = 0, dp_i_1 = Integer.MIN_VALUE;
+ for (int i = 0; i < n; i++) {
+ // dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
+ dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
+ // dp[i][1] = max(dp[i-1][1], -prices[i])
+ dp_i_1 = Math.max(dp_i_1, -prices[i]);
+ }
+ return dp_i_0;
+}
+```
+
+- k 为正无穷
+
+```java
+// 原始版本
+int maxProfit_k_inf(int[] prices) {
+ int n = prices.length;
+ int[][] dp = new int[n][2];
+ for (int i = 0; i < n; i++) {
+ if (i - 1 == -1) {
+ // base case
+ dp[i][0] = 0;
+ dp[i][1] = -prices[i];
+ continue;
+ }
+ dp[i][0] = Math.max(dp[i-1][0],
+ dp[i-1][1] + prices[i]);
+ dp[i][1] = Math.max(dp[i-1][1],
+ dp[i-1][0] - prices[i]);
+ }
+ return dp[n - 1][0];
+}
+
+// 空间复杂度优化版本
+int maxProfit_k_inf(int[] prices) {
+ int n = prices.length;
+ int dp_i_0 = 0, dp_i_1 = Integer.MIN_VALUE;
+ for (int i = 0; i < n; i++) {
+ int temp = dp_i_0;
+ dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
+ dp_i_1 = Math.max(dp_i_1, temp - prices[i]);
+ }
+ return dp_i_0;
+}
+```
+
+- k == 2
+
+```java
+// 原始版本
+int maxProfit(int[] prices) {
+ int max_k = 2, n = prices.length;
+ int[][][] dp = new int[n][max_k + 1][2];
+ for (int i = 0; i < n; i++) {
+ for (int k = max_k; k >= 1; k--) {
+ if (i - 1 == -1) {
+ // 处理 base case
+ dp[i][k][0] = 0;
+ dp[i][k][1] = -prices[i];
+ continue;
+ }
+ dp[i][k][0] = Math.max(dp[i-1][k][0],
+ dp[i-1][k][1] + prices[i]);
+ dp[i][k][1] = Math.max(dp[i-1][k][1],
+ dp[i-1][k-1][0] - prices[i]);
+ }
+ }
+ // 穷举了 n × max_k × 2 个状态,正确。
+ return dp[n - 1][max_k][0];
+}
+
+// 空间复杂度优化版本
+int maxProfit_k_2(int[] prices) {
+ // base case
+ int dp_i10 = 0, dp_i11 = Integer.MIN_VALUE;
+ int dp_i20 = 0, dp_i21 = Integer.MIN_VALUE;
+ for (int price : prices) {
+ dp_i20 = Math.max(dp_i20, dp_i21 + price);
+ dp_i21 = Math.max(dp_i21, dp_i10 - price);
+ dp_i10 = Math.max(dp_i10, dp_i11 + price);
+ dp_i11 = Math.max(dp_i11, -price);
+ }
+ return dp_i20;
+}
+```
+
+- k 为正无穷,但含有交易冷冻期
+
+```java
+// 原始版本
+int maxProfit_with_cool(int[] prices) {
+ int n = prices.length;
+ int[][] dp = new int[n][2];
+ for (int i = 0; i < n; i++) {
+ if (i - 1 == -1) {
+ // base case 1
+ dp[i][0] = 0;
+ dp[i][1] = -prices[i];
+ continue;
+ }
+ if (i - 2 == -1) {
+ // base case 2
+ dp[i][0] = Math.max(dp[i-1][0],
+ dp[i-1][1] + prices[i]);
+ // i - 2 小于 0 时根据状态转移方程推出对应 base case
+ dp[i][1] = Math.max(dp[i-1][1], -prices[i]);
+ // dp[i][1]
+ // = max(dp[i-1][1], dp[-1][0] - prices[i])
+ // = max(dp[i-1][1], 0 - prices[i])
+ // = max(dp[i-1][1], -prices[i])
+ continue;
+ }
+ dp[i][0] = Math.max(dp[i-1][0],
+ dp[i-1][1] + prices[i]);
+ dp[i][1] = Math.max(dp[i-1][1],
+ dp[i-2][0] - prices[i]);
+ }
+ return dp[n - 1][0];
+}
+
+// 空间复杂度优化版本
+int maxProfit_with_cool(int[] prices) {
+ int n = prices.length;
+ int dp_i_0 = 0, dp_i_1 = Integer.MIN_VALUE;
+ int dp_pre_0 = 0; // 代表 dp[i-2][0]
+ for (int i = 0; i < n; i++) {
+ int temp = dp_i_0;
+ dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
+ dp_i_1 = Math.max(dp_i_1, dp_pre_0 - prices[i]);
+ dp_pre_0 = temp;
+ }
+ return dp_i_0;
+}
+```
+
+- k 为正无穷且考虑交易手续费
+
+```java
+// 原始版本
+int maxProfit_with_fee(int[] prices, int fee) {
+ int n = prices.length;
+ int[][] dp = new int[n][2];
+ for (int i = 0; i < n; i++) {
+ if (i - 1 == -1) {
+ // base case
+ dp[i][0] = 0;
+ dp[i][1] = -prices[i] - fee;
+ // dp[i][1]
+ // = max(dp[i - 1][1], dp[i - 1][0] - prices[i] - fee)
+ // = max(dp[-1][1], dp[-1][0] - prices[i] - fee)
+ // = max(-inf, 0 - prices[i] - fee)
+ // = -prices[i] - fee
+ continue;
+ }
+ dp[i][0] = Math.max(dp[i - 1][0],
+ dp[i - 1][1] + prices[i]);
+ dp[i][1] = Math.max(dp[i - 1][1],
+ dp[i - 1][0] - prices[i] - fee);
+ }
+ return dp[n - 1][0];
+}
+
+// 空间复杂度优化版本
+int maxProfit_with_fee(int[] prices, int fee) {
+ int n = prices.length;
+ int dp_i_0 = 0, dp_i_1 = Integer.MIN_VALUE;
+ for (int i = 0; i < n; i++) {
+ int temp = dp_i_0;
+ dp_i_0 = Math.max(dp_i_0, dp_i_1 + prices[i]);
+ dp_i_1 = Math.max(dp_i_1, temp - prices[i] - fee);
+ }
+ return dp_i_0;
+}
+```
+
+- 指定 k
+
+```java
+int maxProfit_k_any(int max_k, int[] prices) {
+ int n = prices.length;
+ if (n <= 0) {
+ return 0;
+ }
+ if (max_k > n / 2) {
+ // 复用之前交易次数 k 没有限制的情况
+ return maxProfit_k_inf(prices);
+ }
+
+ // base case:
+ // dp[-1][...][0] = dp[...][0][0] = 0
+ // dp[-1][...][1] = dp[...][0][1] = -infinity
+ int[][][] dp = new int[n][max_k + 1][2];
+ // k = 0 时的 base case
+ for (int i = 0; i < n; i++) {
+ dp[i][0][1] = Integer.MIN_VALUE;
+ dp[i][0][0] = 0;
+ }
+
+ for (int i = 0; i < n; i++)
+ for (int k = max_k; k >= 1; k--) {
+ if (i - 1 == -1) {
+ // 处理 i = -1 时的 base case
+ dp[i][k][0] = 0;
+ dp[i][k][1] = -prices[i];
+ continue;
+ }
+ dp[i][k][0] = Math.max(dp[i-1][k][0],
+ dp[i-1][k][1] + prices[i]);
+ dp[i][k][1] = Math.max(dp[i-1][k][1],
+ dp[i-1][k-1][0] - prices[i]);
+ }
+ return dp[n - 1][max_k][0];
+}
+```
+
+### 矩阵的最小路径和
+
+```java
+import java.util.*;
+public class Solution {
+ /**
+ *
+ * @param matrix int整型二维数组 the matrix
+ * @return int整型
+ */
+ public int minPathSum (int[][] matrix) {
+ // write code here
+ int m = matrix.length, n = matrix[0].length;
+ if (m == 0 || n == 0) return 0;
+
+ for (int i = 1; i < m; i++)
+ matrix[i][0] += matrix[i-1][0];
+ for (int i = 1; i < n; i++)
+ matrix[0][i] += matrix[0][i-1];
+
+ for (int i = 1; i < m; i++) {
+ for (int j = 1; j < n; j++) {
+ matrix[i][j] +=
+ Math.min(matrix[i-1][j], matrix[i][j-1]);
+ }
+ }
+ return matrix[m-1][n-1];
+ }
+}
+```
+
+### 编辑距离
+
+```java
+import java.util.*;
+
+
+public class Solution {
+ /**
+ * min edit cost
+ * @param str1 string字符串 the string
+ * @param str2 string字符串 the string
+ * @param ic int整型 insert cost
+ * @param dc int整型 delete cost
+ * @param rc int整型 replace cost
+ * @return int整型
+ */
+ public int minEditCost (String str1, String str2,
+ int ic, int dc, int rc) {
+ // write code here
+ int len1=str1.length();
+ int len2=str2.length();
+ char[] char1=str1.toCharArray();
+ char[] char2=str2.toCharArray();
+ int[][] dp=new int[len1+1][len2+1];
+ for(int i=1;i<=len1;i++){
+ dp[i][0]=dp[i-1][0]+dc;
+ }
+ for(int i=1;i<=len2;i++){
+ dp[0][i]=dp[0][i-1]+ic;
+ }
+ for(int i=0;i stack = new Stack<>();
+ int last = -1;
+ int maxLen = 0;
+ for(int i = 0; i < s.length(); i++){
+ if(s.charAt(i) == '('){
+ stack.push(i);
+ } else {
+ if(stack.isEmpty()){
+ last = i;
+ } else {
+ stack.pop();
+ if(stack.isEmpty()){
+ maxLen =
+ Math.max(maxLen, i - last);
+ } else {
+ maxLen =
+ Math.max(maxLen, i - stack.peek());
+ }
+ }
+ }
+ }
+
+ return maxLen;
+ }
+}
+
+// 动态规划
+public int longestValidParentheses2(String s) {
+ if (s == null || s.length() == 0)
+ return 0;
+ int[] dp = new int[s.length()];
+ int ans = 0;
+ for (int i = 1; i < s.length(); i++) {
+ // 如果是'('直接跳过,默认为0
+ if (s.charAt(i) == ')') {
+ if (s.charAt(i - 1) == '(')
+ dp[i] =
+ (i >= 2 ? dp[i - 2] : 0) + 2;
+ // 说明s.charAt(i - 1)==')'
+ else if (i - dp[i - 1] > 0 &&
+ s.charAt(i - dp[i - 1] - 1) == '(') {
+ dp[i] =
+ (i - dp[i - 1] > 1 ?
+ dp[i - dp[i - 1] - 2] : 0) + dp[i - 1] + 2;
+ // 因为加了一个左括号和一个右括号,所以是加2
+ }
+ }
+ ans = Math.max(ans, dp[i]);
+ }
+ return ans;
+}
+```
+
+### 高空扔鸡蛋
+
+```java
+// 如果棋子碎了,那么棋子的个数K应该减一,
+// 搜索的楼层区间应该从[1..N]变为[1..i-1]共i-1层楼;
+// 如果棋子没碎,那么棋子的个数K不变,
+// 搜索的楼层区间应该从 [1..N]变为[i+1..N]共N-i层楼。
+import java.util.*;
+public class Solution {
+ /**
+ * 返回最差情况下扔棋子的最小次数
+ * @param k int整型 棋子数
+ * @param n int整型 楼层数
+ * @return int整型
+ */
+ int[][] memo;
+ public int solve (int n, int k) {
+ memo = new int[k + 1][n + 1];
+ for(int[] m : memo) {
+ Arrays.fill(m, -1);
+ }
+ return dp(k, n);
+ }
+
+ // 定义:有K个棋子面对N层楼,最少需要扔 dp(K, N) 次
+ int dp(int k, int n) {
+ // 状态:棋子数k,需要测试的楼层n
+ if(k == 1) {
+ return n;
+ }
+ // 尝试到底层
+ if(n == 0) {
+ return 0;
+ }
+ if(memo[k][n] != -1) {
+ return memo[k][n];
+ }
+ int res = Integer.MAX_VALUE;
+ // 寻找第一层到第n层的最少扔的次数
+ for(int i = 1; i <= n; i++) {
+ res = Math.min(res,
+ // 取决于最差情况(碎了,没碎)
+ Math.max(dp(k-1, i-1), dp(k, n-i)) + 1);
+ }
+ memo[k][n] = res;
+ return res;
+ }
+}
+```
+
+### 兑换零钱
+
+```java
+public class Solution {
+ /**
+ * 最少货币数
+ * @param arr int整型一维数组 the array
+ * @param aim int整型 the target
+ * @return int整型
+ */
+ public int minMoney (int[] arr, int aim) {
+ // write code here
+ //如何使用最少arr元素 构成 aim值
+ //dp[i] 代表给定钱数为i的时候最少货币数
+ // 就是凑成 i 元钱,需要dp[i] 张arr中面值纸币
+ //没办法兑换 arr[i] dp[i] = dp[i]
+ //可以dp[i] = dp[i - arr[i]] + 1
+ //dp[i] = min(dp[i], dp[i-a[j]])
+ if(arr == null || arr.length == 0){
+ return -1;
+ }
+ int[] dp = new int[aim+1];
+ for(int i = 0;i<=aim;i++){
+ dp[i] = aim+1;
+ }
+
+ dp[0] = 0;
+ for(int i = 1;i<=aim;i++){
+ for(int j = 0;j< arr.length;j++){
+ if(arr[j] <= i){
+ //给了一张 3 元钱,小于 需要找零的4 元钱,
+ // 那 就等于 1 + 需要找零剩下的钱dp[i -arr[j]] 4 - 3
+ dp[i] =
+ Math.min(dp[i], dp[i-arr[j]] +1);
+ }
+ }
+ }
+ return (dp[aim] > aim) ?-1 : dp[aim];
+ }
+}
+```
+
+### 最大正方形
+
+1.确定dp[][]数组的含义
+
+此题的dp[i][j],代表以坐标为(i,j)的元素为右下角的正方形的边长。
+
+2.状态转移方程
+
+dp[i][j]的值取决于dp[i-1][j],dp[i-1][j-1],dp[i][j-1]的最小值
+即左方正方形的边长,左上方正方形的边长,上方正方形的边长三者的最小值。
+
+```java
+import java.util.*;
+
+// dp[i][j],代表以坐标为(i,j)的元素为右下角的正方形的边长
+public class Solution {
+ /**
+ * 最大正方形
+ * @param matrix char字符型二维数组
+ * @return int整型
+ */
+ public int solve (char[][] matrix) {
+ // write code here
+ if(matrix.length ==0 || matrix[0].length == 0) return 0;
+ int rows = matrix.length;
+ int cols = matrix[0].length;
+ int[][] dp = new int[rows][cols];
+ int maxSquareLength = 0;
+ for(int i = 0; i < rows; i++){
+ if(matrix[i][0] == '1') dp[i][0] = 1;
+ }
+ for(int i = 0; i < cols; i++){
+ if(matrix[0][i] == '1') dp[0][i] = 1;
+ }
+ for(int i =1; i < rows; i++){
+ for(int j = 1; j < cols; j++){
+ if(matrix[i][j] == '1'){
+ dp[i][j] =
+ Math.min(Math.min(dp[i-1][j-1],
+ dp[i-1][j]),dp[i][j-1])+1;
+ if(dp[i][j] > maxSquareLength)
+ maxSquareLength = dp[i][j];
+ }
+ }
+ }
+ return maxSquareLength*maxSquareLength;
+ }
+}
+```
+
+### 通配符匹配
+
+
+
+```java
+import java.util.*;
+public class Solution {
+ public boolean isMatch(String s, String p) {
+ if (p == null || p.isEmpty())return s == null || s.isEmpty();
+ int slen = s.length(), plen = p.length();
+ boolean[][] dp = new boolean[slen + 1][plen + 1];
+ //初始化dp数组,dp[1][0]~dp[s.length][0]默认值flase不需要显式初始化为false
+ dp[0][0] = true;
+ //dp[0][1]~dp[0][p.length]只有p的j字符以及前面所有字符都为'*'才为true
+ for (int j = 1; j <= plen; j++)dp[0][j] = p.charAt(j - 1) == '*' &&
+ dp[0][j - 1];
+ //填写dp数组剩余部分
+ for (int i = 1; i <= slen; i++) {
+ for (int j = 1; j <= plen; j++) {
+ char si = s.charAt(i - 1), pj = p.charAt(j - 1);
+ if (si == pj || pj == '?') {
+ dp[i][j] = dp[i - 1][j - 1];
+ } else if (pj == '*') {
+ dp[i][j] = dp[i - 1][j] || dp[i][j - 1];
+ }
+ }
+ }
+ return dp[slen][plen];
+ }
+}
+```
+
+### 正则表达式匹配
+
+
+
+```java
+import java.util.*;
+public class Solution {
+ public boolean match (String str, String pattern) {
+ int n=str.length();
+ int m=pattern.length();
+ boolean[][] dp=new boolean[n+1][m+1];
+
+ //初始化
+ dp[0][0]=true;
+ for(int i=1;i<=n;i++){
+ dp[i][0]=false;
+ }
+
+ //分模式串的后一个位置是否为*进行讨论,为*时,
+ // 将*与前一个位置合并起来进行考虑
+ for(int i=0;i<=n;i++){
+ for(int j=1;j<=m;j++){
+ if(pattern.charAt(j-1)!='*'){
+ //当前模式串字符和原串字符匹配
+ if(i>0
+ &&(str.charAt(i-1)==pattern.charAt(j-1)
+ ||(pattern.charAt(j-1)=='.'))){
+ dp[i][j]=dp[i-1][j-1];
+ }
+ }
+ else{
+ if(j>=2){
+ //不管是否匹配,都可以将当前字符绑定上*匹配原串字符0次
+ dp[i][j]=dp[i][j-2];
+ //当前模式串字符和原串字符匹配
+ if(i>0
+ &&(str.charAt(i-1)==pattern.charAt(j-2)
+ ||(pattern.charAt(j-2)=='.'))){
+ dp[i][j]=dp[i-1][j]||dp[i][j-2];
+ }
+ }
+ }
+ }
+ }
+ return dp[n][m];
+ }
+}
+```
+
+### 矩阵最长递增路径
+
+```java
+class Solution {
+ public int longestIncreasingPath(int[][] matrix) {
+ int m = matrix.length;
+ int n = matrix[0].length;
+ int ans = 0;
+ int[][] dp = new int[m][n];//储存当前节点最长路径
+ for (int i = 0; i < m; i++) {
+ for (int j = 0; j < n; j++) {
+ if (dp[i][j] == 0) {
+ ans = Math.max(ans, dfs(matrix, dp, i, j));
+ }
+ }
+ }
+ return ans;
+ }
+ public int dfs(int[][] matrix , int[][] dp, int i , int j){
+ if (dp[i][j] == 0) {
+ int dir1 = 0, dir2 = 0, dir3 = 0, dir4 = 0;
+ if (i > 0 && matrix[i - 1][j] > matrix[i][j]) {
+ dir1 = dfs(matrix, dp, i - 1, j);
+ }
+ if (j > 0 && matrix[i][j - 1] > matrix[i][j]) {
+ dir2 = dfs(matrix, dp, i, j - 1);
+ }
+ if (i < matrix.length - 1
+ && matrix[i + 1][j] > matrix[i][j]) {
+ dir3 = dfs(matrix, dp, i + 1, j);
+ }
+ if (j < matrix[0].length - 1
+ && matrix[i][j + 1] > matrix[i][j]) {
+ dir4 = dfs(matrix, dp, i, j + 1);
+ }
+ //选出四个方向的最长子串,加1后赋值给当前节点
+ dp[i][j] = 1 + Math.max(dir1,
+ Math.max(dir2, Math.max(dir3, dir4)));
+ }
+ return dp[i][j];
+ }
+}
+```
+
+### 最长上升子序列
+
+- 返回长度
+
+```java
+import java.util.*;
+
+
+public class Solution {
+ /**
+ * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
+ *
+ * 给定数组的最长严格上升子序列的长度。
+ * @param arr int整型一维数组 给定的数组
+ * @return int整型
+ */
+ public int LIS (int[] arr) {
+ // write code here
+ int len = arr.length;
+ if (arr == null && len == 0){
+ return 0;
+ }
+ int maxLen = 0;
+ int[] dp = new int[len];
+ Arrays.fill(dp, 1);
+ for (int i = 1; i < len; i++) {
+ for (int j = i-1; j >=0 ; j--) {
+ if (arr[i]> arr[j]){
+ dp[i] = Math.max(dp[i],dp[j]+1);
+ }
+ }
+ maxLen = Math.max(maxLen,dp[i]);
+ }
+ return maxLen;
+ }
+}
+```
+
+- 返回字符串
+
+// 方法一:dp
+状态定义:dp[i]表示以i位置元素结尾的最长上升子序列长度。
+状态初始化:以每个位置结尾的上升子序列长度至少为1。
+状态转移:遍历arr数组,假设当前位置为i,
+比较当前位置i之前的每一个元素与当前位置元素的大小,
+如果小于当前位置元素arr[i],说明可以接在arr前面。
+即 dp[i]=Math.max(dp[i],dp[j]+1)。
+
+// 方法二:dp+数组
+与方法一相同的是,也需要建立一个dp数组找到每一个位置对应的最长上升子序列长度,
+最后再通过逆序遍历arr数组的方式,找到每一个长度对应的那个元素,赋值给结果序列。
+不过确定dp数组的方式有所不同。
+
+为了找到最长的上升子序列,我们可以维护一个单调递增的数组tail记录当前的最长上升子序列,
+如果后面出现的数arr[i]比tail数组末尾元素大,则可以直接加在后面;
+如果不是,则找到tail数组中第一个大于等于arr[i]的元素位置,
+并将该位置的元素替换为arr[i],因为在长度相同的情况下,当前值越小,则后面出现更长子序列的概率越大。
+
+```java
+// 方法一:dp
+public class Solution {
+ public int[] LIS (int[] arr) {
+ int n=arr.length;
+ //dp[i]表示以i位置元素结尾的最长上升子序列长度
+ int[] dp=new int[n+1];
+ //初始化为1
+ Arrays.fill(dp,1);
+ //记录最长子序列的长度
+ int len=0;
+ for(int i=0;i=0;i--){
+ if(dp[i]==len){
+ res[--len]=arr[i];
+ }
+ return res;
+ }
+}
+
+// 方法二:dp+二分
+public class Solution {
+ public int[] LIS (int[] arr) {
+ int n=arr.length;
+ //维护一个单调递增tail数组
+ int[] tail=new int[n];
+ //dp[i]表示以i位置结尾的最长上升子序列长度
+ int[] dp=new int[n];
+ //最长上升子序列长度
+ int len=0;
+ for(int i=0;i=0;i--){
+ if(dp[i]==len){
+ res[--len]=arr[i];
+ }
+ }
+ return res;
+ }
+
+ //二分法找tail数组中第一个大于等于arr[i]的元素位置
+ private int search(int[] nums,int len,int k){
+ int low=0,high=len-1;
+ while(low=k){
+ high=mid;
+ }
+ //否则排除mid以及mid往左的所有元素
+ else{
+ low=mid+1;
+ }
+ }
+ return low;
+ }
+}
+```
+
+### 目标和(完全背包)
+
+```java
+public class Solution {
+ public int findTargetSumWays (int[] nums, int target) {
+ //边界情况判断
+ int n=nums.length;
+ if(n==0) return 0;
+ //记录累加和
+ int sum=0;
+ //遍历nums数组
+ for(int num:nums){
+ sum+=num;
+ }
+ //计算背包容量
+ int V=(sum+target)/2;
+ //如果为奇数,说明nums数组中找不打和为(sum+target)/2的若干数字
+ if((sum+target)%2==1) return 0;
+
+ //dp[j]表示有多少种不同的组合,其累加和为j
+ int[] dp=new int[V+1];
+ //初始化
+ dp[0]=1;
+ for(int i=0;i=nums[i];j--){
+ dp[j]+=dp[j-nums[i]];
+ }
+ }
+ return dp[V];
+ }
+}
+```
+
+### 打家劫舍
+
+```java
+public class Solution {
+ public int rob (int[] nums) {
+ // write code here
+
+ // 一些特殊情况的处理
+ if (1 == nums.length) {
+ return nums[0];
+ }
+ if (2 == nums.length) {
+ return Math.max(nums[0], nums[1]);
+ }
+
+ int l = nums.length;
+ int[] dp = new int[l];
+
+ dp[0] = nums[0];
+ dp[1] = Math.max(nums[0], nums[1]);
+ for (int i = 2; i < l; i++) {
+ dp[i] = Math.max(dp[i - 2]
+ + nums[i], dp[i - 1]);
+ }
+ return dp[l - 1];
+ }
+}
+
+// 圆形情况
+public class Solution {
+ public int rob (int[] nums) {
+ int n=nums.length;
+ //在0到n-2范围内找
+ int rob1=getRob(Arrays.copyOfRange(nums,0,n-1));
+ //在1到n-1范围内找
+ int rob2=getRob(Arrays.copyOfRange(nums,1,n));
+
+ return Math.max(rob1,rob2);
+ }
+
+ private int getRob(int[] nums){
+ int n=nums.length;
+ //边界情况处理
+ if(n==0) return 0;
+ if(n==1) return nums[0];
+ if(n==2) return Math.max(nums[0],nums[1]);
+ //定义dp数组
+ int[] dp=new int[n];
+ //初始化
+ dp[0]=nums[0];
+ dp[1]=Math.max(nums[0],nums[1]);
+ for(int i=2;i
+如果map中没有当前这个元素,那么dp[i]=dp[i-1]+1
+如果map中存在当前的元素,一开始的想法是 dp[i]=i-map.get(array[i]),
+但是这样想有点问题,如果当前的字符串是abba的时候,按照刚才的思路dp[0]=1 dp[1]=2 dp[2]=1 dp[3]=3
+但是dp[3]是错误的,因为中间存在了重复的字符。所以要加一种情况。
+dp[i]=Math.min(dp[i-1]+1,i-map.get(array[i]))
+
+public class Solution {
+ /**
+ * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
+ *
+ *
+ * @param s string字符串
+ * @return int整型
+ */
+ public int lengthOfLongestSubstring (String s) {
+ if(s==null) return 0;
+ char[]array=s.toCharArray();
+ if(array.length==1){
+ return 1;
+ }
+ int[]dp=new int[array.length];
+ int maxLength=1;
+ HashMapmap=new HashMap<>();
+ dp[0]=1;
+ map.put(array[0],0);
+ for(int i=1;i= 10 && num <= 26){
+ if(i == 1){
+ dp[i] += 1;
+ }else{
+ dp[i] += dp[i-2];
+ }
+ }
+ }
+ return dp[nums.length()-1];
+
+ }
+```
+
+## 二分
+
+### 求平方根
+
+```java
+import java.util.*;
+
+
+public class Solution {
+ /**
+ *
+ * @param x int整型
+ * @return int整型
+ */
+ public int sqrt (int x) {
+ // write code here
+ if(x < 2){
+ return x;
+ }
+ int left = 1;
+ int right = x / 2;
+ while(left <= right){
+ int mid = left + (right - left) / 2;
+ if(x / mid == mid){
+ return mid;
+ } else if(x / mid < mid){
+ right = mid - 1;
+ } else if(x / mid > mid){
+ left = mid + 1;
+ }
+ }
+
+ return right;
+ }
+}
+```
+
+### 在旋转过的有序数组中寻找目标值
+
+```java
+public class Solution {
+ public int search (int[] nums, int target) {
+ // write code here
+ if (nums == null || nums.length < 1) {
+ return -1;
+ }
+ if (nums.length == 1) {
+ return nums[0] == target ? 0 : -1;
+ }
+
+ int start = 0, end = nums.length - 1;
+ while (end >= start) {
+ // 找到 左右指针中间位置
+ int mid = (end + start) >> 1;
+ if (nums[mid] == target) {
+ return mid;
+ }
+ // 在左侧升序数组中
+ if (nums[0] <= nums[mid]) {
+ // 在开头和 mid 之间,那么 右指针则为 mid -1
+ if (target >= nums[0]
+ && target < nums[mid]) {
+ end = mid -1;
+ } else {
+ start = mid + 1;
+ }
+ } else {
+ // 如果在 mid 和end 之间,更新 start 为 mid = 1
+ if (target > nums[mid]
+ && target <= nums[end]) {
+ start = mid + 1;
+ } else {
+ end = mid - 1;
+ }
+ }
+ }
+ return -1;
+ }
+}
+```
+
+### 在两个长度相等的排序数组中找到上中位数
+
+```java
+// 普通
+public class Solution {
+ public int findMedianinTwoSortedAray (int[] arr1, int[] arr2) {
+ // write code here
+ int length1 = arr1.length;
+ int length2 = arr2.length;
+ int i=0;
+ int j=0;
+ int[] arr = new int[length1+length2];
+ int index = 0;
+ //合并数组
+ for(;i>1);
+ //arr2中位数
+ mid2 = l2+((r2-l2)>>1);
+ int k = r1-l1+1;
+ if(arr1[mid1] == arr2[mid2]){
+ //若两数组中位数相等,整体中位数也是这个
+ return arr1[mid1];
+ }
+ else if(arr1[mid1] > arr2[mid2]){
+ if(k%2 == 0){//区间元素个数为偶数
+ r1 = mid1; //整体中位数在arr1左区间,包括mid1
+ l2 = mid2+1; //整体中位数在arr2右区间,不包括mid2
+ }
+ else if(k%2 == 1){ //区间元素个数为奇数
+ r1 = mid1; //整体中位数在arr1左区间,包括mid1
+ l2 = mid2; //整体中位数在arr2右区间,包括mid2
+ }
+ }
+ else if (arr1[mid1] < arr2[mid2]){
+ if(k%2 == 0){//区间元素个数为偶数
+ r2 = mid2; //整体中位数在arr2左区间,包括mid2
+ l1 = mid1+1; //整体中位数在arr1右区间,不包括mid1
+ }
+ else if(k%2 == 1){ //区间元素个数为奇数
+ r2 = mid2; //整体中位数在arr2左区间,包括mid2
+ l1 = mid1; //整体中位数在arr1右区间,包括mid1
+ }
+ }
+ }
+ //当区间内只有一个元素时,两个区间中最小值即为整体中位数
+ return Math.min(arr1[l1],arr2[l2]);
+}
+```
+
+### 有序矩阵元素查找
+
+```java
+public class Solution {
+ public int[] findElement(int[][] mat, int n, int m, int x) {
+ // write code here
+ int[] result = new int[2];
+ int row = 0;
+ int col = m - 1;
+ while(row < n && col >= 0) {
+ if(mat[row][col] == x) {
+ result[0] = row;
+ result[1] = col;
+ break;
+ }
+ if(x > mat[row][col]) {
+ row ++;
+ } else {
+ col --;
+ }
+ }
+ return result;
+ }
+}
+```
+
+### 二分查找
+
+```java
+public int binarySearch(int target, int[] nums) {
+ int left = 0;
+ int right = nums.length-1; //取最后一个下标
+ int mid = 0;
+ //左下标大于右下标,直接返回-1
+ if (left > right || nums.length == 0 || nums == null) {
+ return -1;
+ }
+ // 初始化 right 的赋值是 nums.length - 1,
+ // 即最后一个元素的索引,而不是 nums.length
+ while (left <= right) {
+ mid = left + (right - left) / 2; //如果下标之和除以2有小数,则直接去掉
+ if (target == nums[mid]) {
+ return mid; //找到目标值,然后返回
+ } else if (target > nums[mid]) { //目标值大于中间值,向右遍历
+ left = mid + 1; //所以向右遍历的第一个下标是:中间下标+1
+ } else if (target < nums[mid]) { //目标值小于中间值,向左遍历
+ right = mid - 1; //所以向左遍历的最后一个下标是:中间下标-1
+ }
+ }
+ return -1; //找不到对应目标值,直接返回-1
+}
+```
+
+```java
+// 查找最右值
+public class Solution {
+ public int upper_bound_search (int n, int v, int[] a) {
+ // write code here
+ int left = 0, right = n;
+ while(left < right){
+ int mid = left + (right - left) / 2;
+ if(a[mid] == v){
+ right = mid;
+ } else if(a[mid] > v){
+ right = mid;
+ } else {
+ left = mid + 1;
+ }
+ }
+ return left+1;
+ }
+}
+```
+
+```java
+// 查找最左值
+public class Solution {
+ public int left_bound_search (int[] nums, int target) {
+ // write code here
+ if (nums == null || nums.length == 0) {
+ return -1;
+ }
+ int left = 0, right = nums.length - 1;
+
+ while (left < right) {
+ int mid = (left + right) / 2;
+ if (nums[mid] >= target) {
+ right = mid;
+ } else {
+ left = mid + 1;
+ }
+ }
+ return nums[right] == target ? right : -1;
+ }
+}
+```
+
+### 旋转数组的最小数字
+
+1.如果mid>right,说明mid-right之间存在被旋转数组,left = mid+1
+2.如果mid array[right]){
+ left = mid + 1;
+ }
+ else{
+ right = right - 1;
+ }
+ }
+ return array[left];
+ }
+}
+```
+
+### 数字在升序数组中出现的次数
+
+```java
+public class Solution {
+ public int GetNumberOfK(int [] array , int k) {
+ if(array.length == 0 || k < array[0]
+ || k > array[array.length-1]){
+ return 0;
+ }
+ int left = 0;
+ int right = array.length -1;
+ int count = 0;
+ int found = 0;
+ int mid = -1;
+ while(left < right){
+ mid = (left+right)/2;
+ if(array[mid] > k){
+ right = mid-1;
+ }else if(array[mid] < k){
+ left = mid+1;
+ }else{
+ count++;
+ found = mid;
+ break;
+ }
+ }
+
+ int prev = mid-1;
+ int foll = mid+1;
+ while(prev >= left){
+ if(array[prev] == k){
+ count++;
+ prev--;
+ }else{
+ break;
+ }
+ }
+
+ while(foll <= right){
+ if(array[foll] == k){
+ count++;
+ foll++;
+ }else{
+ break;
+ }
+ }
+ return count;
+ }
+}
+```
+
+### 峰值
+
+本题之所以可以使用二分,使复杂度讲到lgn,是因为题目中的nums[i] != nums[i + 1]条件,
+当中间元素mid不是峰时,一定有一边比mid中间值大,
+假设右边的值,即mid+1位置的值大于mid的值,则右边一定存在峰,
+因为右边的值从mid开始要么是 /\ 这个单调性,要么是 / 这种单调性,两种都一定存在峰
+
+```java
+import java.util.*;
+public class Solution {
+ public int findPeakElement (int[] nums) {
+ int left = 0;
+ int right = nums.length - 1;
+ //二分法
+ while(left < right){
+ int mid = (left + right) / 2;
+ //右边是往下,不一定有坡峰
+ if(nums[mid] > nums[mid + 1])
+ right = mid;
+ //右边是往上,一定能找到波峰
+ else
+ left = mid + 1;
+ }
+ //其中一个波峰
+ return right;
+ }
+}
+```
+
+## 数组
+
+### 数组中只出现一次的数字
+
+```java
+public class Solution {
+
+ public void FindNumsAppearOnce(int [] array,
+ int num1[] , int num2[]) {
+ int num = 0;
+ for(int i = 0; i < array.length; i++){
+ num^=array[i];
+ }
+
+ int count = 0;
+ // 标志位,记录num中的第一个1出现的位置
+ for(;count < array.length; count++){
+ if((num&(1< set = new HashSet<>();
+ for(int i = 0; i < array.length; i++){
+ if(!set.add(array[i])){
+ set.remove(array[i]);
+ }
+ }
+
+ Object[] temp = set.toArray();
+ num1[0] = (int)temp[0];
+ num2[0] = (int)temp[1];
+ }*/
+}
+
+```
+
+### 合并两个有序的数组
+
+```java
+public class Solution {
+ public void merge(int A[], int m, int B[], int n) {
+ int i = m-1, j = n-1, k = m+n-1;
+ while(i >= 0 && j >= 0){
+ if(A[i] > B[j]){
+ A[k--] = A[i--];
+ } else {
+ A[k--] = B[j--];
+ }
+ }
+
+ while(j >= 0){
+ A[k--] = B[j--];
+ }
+ }
+}
+```
+
+### 子数组最大乘积
+
+```java
+public class Solution {
+ public double maxProduct(double[] arr) {
+ if(arr.length == 0 || arr == null){
+ return 0.0;
+ }
+ double[] max = new double[arr.length];
+ double[] min = new double[arr.length];
+ max[0] = min[0] = arr[0];
+ for(int i = 1; i < arr.length; i++){
+ max[i] =
+ Math.max(Math.max(max[i-1]*arr[i],
+ min[i-1]*arr[i]),arr[i]);
+ min[i] =
+ Math.min(Math.min(max[i-1]*arr[i],
+ min[i-1]*arr[i]),arr[i]);
+ }
+
+ double ans = max[0];
+ for(int i = 0; i < max.length; i++){
+ if(max[i] > ans){
+ ans = max[i];
+ }
+ }
+ return ans;
+ }
+}
+
+public int maxProduct (int[] nums) {
+ int max = nums[0];
+ int min = nums[0];
+ int result = nums[0];
+ for (int i = 1; i < nums.length; i++) {
+ int t = max;
+ max = Math.max(nums[i],
+ Math.max(max * nums[i], min * nums[i]));
+ min = Math.min(nums[i],
+ Math.min(t * nums[i], min * nums[i]));
+ result = Math.max(result,max);
+ }
+ return result;
+}
+```
+
+### 数组中最长连续子序列
+
+```java
+public int MLS(int[] arr) {
+ if (arr == null || arr.length == 0)
+ return 0;
+ int longest = 1;//记录最长的有序序列
+ int count = 1;//目前有序序列的长度
+ //先对数组进行排序
+ Arrays.sort(arr);
+ for (int i = 1; i < arr.length; i++) {
+ //跳过重复的
+ if (arr[i] == arr[i - 1])
+ continue;
+ //比前一个大1,可以构成连续的序列,count++
+ if ((arr[i] - arr[i - 1]) == 1) {
+ count++;
+ } else {
+ //没有比前一个大1,不可能构成连续的,
+ //count重置为1
+ count = 1;
+ }
+ //记录最长的序列长度
+ longest = Math.max(longest, count);
+ }
+ return longest;
+}
+```
+
+### 数组中未出现的最小正整数
+
+时间复杂度O(n),空间复杂度O(n)的做法:开辟一个新的数组arr,长度为nums.length+1,遍历nums数组,
+如果非负且值小于nums的长度,则把arr[nums[i]]置1。然后遍历辅助数组,找到下标不为1的第一个元素即可。
+
+```java
+public class Solution {
+ public int minNumberDisappeared (int[] nums) {
+ int []arr = new int [nums.length+1];
+ for(int i=0;i0){
+ arr[nums[i]] =1;
+ }
+ }
+ for(int i=1;i= end){
+ return;
+ }
+ int mid = start + (end - start)/2;
+
+ mergeSort(array,start,mid);
+ mergeSort(array,mid + 1,end);
+
+ merge(array,start,mid,end);
+ }
+
+ public void merge(int[] array,int start,int mid,int end){
+ int[] temp = new int[end-start+1];
+ int k = 0;
+ int i = start;
+ int j = mid + 1;
+ while(i <= mid && j <= end){
+ if(array[i] < array[j]){
+ temp[k++] = array[i++];
+ } else {
+ temp[k++] = array[j++];
+ res = (res + mid - i + 1) % 1000000007;
+ }
+ }
+
+ while(i <= mid){
+ temp[k++] = array[i++];
+ }
+
+ while(j <= end){
+ temp[k++] = array[j++];
+ }
+
+ for(k = 0; k < temp.length; k++){
+ array[start + k] = temp[k];
+ }
+ }
+
+
+ /*
+ public int InversePairs(int [] array) {
+ int sum = 0;
+ for(int i = 0; i < array.length - 1; i++){
+ for(int j = i + 1; j < array.length; j++){
+ if(array[i] > array[j]){
+ sum ++;
+ }
+ }
+ }
+ return sum % 1000000007;
+ }
+ */
+}
+```
+
+### 调整数组顺序使奇数位于偶数前面
+
+```java
+// 方法一:先记录下技术的个数,然后遍历数组,用另外一个数组接收奇数和偶数
+import java.util.*;
+public class Solution {
+ public int[] reOrderArray (int[] array) {
+ // write code here
+ int[] arr=new int[array.length];
+ int num=0;
+ for(int a:array){
+ if((a&1)==1) num++;//奇数
+ }
+ int i=0;
+ for(int a:array){
+ if((a&1)==1){ //奇数
+ arr[i++]=a;
+ }else{
+ arr[num++]=a;
+ }
+ }
+ return arr;
+ }
+}
+
+// 方法二:记录已经是奇数的位置下标(视作为有序区域),然后向后遍历,
+// 一经发现是奇数则进行“插入排序”,然后有序区下标加1。
+public class Solution {
+ public int[] reOrderArray (int[] array) {
+ // 首先是对数值长度进行特判
+ if(array==null||array.length==0) return array;
+ //记录已经是奇数的位置
+ int j=0;
+ int temp = 0;
+ for(int i =0;ij){
+ //这区间整体向后移动一位
+ array[k] = array[k-1];
+ k--;
+ }
+ //移位之后将对应的值赋值
+ array[k] = temp;
+ j++;
+ }
+ }
+ //返回结果数数组
+ return array;
+ }
+}
+```
+
+### 矩阵乘法
+
+```java
+import java.util.*;
+public class Solution {
+ public int[][] solve (int[][] a, int[][] b) {
+ // write code here
+ //矩阵相乘条件:a的列和b的行必须相等
+ //记录a的行、a的列(b的行)、b的列
+ int aRow = a.length;
+ int aColumn = a[0].length;
+ int bColumn = b[0].length;
+ //新矩阵行为a的行,列为b的列
+ int[][] res = new int[aRow][bColumn];
+ for(int i=0; i> result = new ArrayList<>();
+ //暂存结果
+ List path = new ArrayList<>();
+
+ public List> permuteUnique(int[] nums) {
+ boolean[] used = new boolean[nums.length];
+ Arrays.fill(used, false);
+ Arrays.sort(nums);
+ backTrack(nums, used);
+ return result;
+ }
+
+ private void backTrack(int[] nums, boolean[] used) {
+ if (path.size() == nums.length) {
+ result.add(new ArrayList<>(path));
+ return;
+ }
+ for (int i = 0; i < nums.length; i++) {
+ // used[i - 1] == true,说明同⼀树⽀nums[i - 1]使⽤过
+ // used[i - 1] == false,说明同⼀树层nums[i - 1]使⽤过
+ // 如果同⼀树层nums[i - 1]使⽤过则直接跳过
+ if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
+ continue;
+ }
+ //如果同⼀树⽀nums[i]没使⽤过开始处理
+ if (used[i] == false) {
+ used[i] = true;//标记同⼀树⽀nums[i]使⽤过,防止同一树支重复使用
+ path.add(nums[i]);
+ backTrack(nums, used);
+ //回溯,说明同⼀树层nums[i]使⽤过,防止下一树层重复
+ path.remove(path.size() - 1);
+ used[i] = false;//回溯
+ }
+ }
+ }
+}
+```
+
+### 岛屿的数量
+
+```java
+class Solution {
+ public int numIslands(char[][] grid) {
+ int count = 0;
+ for(int i = 0; i < grid.length; i++) {
+ for(int j = 0; j < grid[0].length; j++) {
+ if(grid[i][j] == '1'){
+ bfs(grid, i, j);
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
+ public void bfs(char[][] grid, int i, int j){
+ Queue list = new LinkedList<>();
+ list.add(new int[] { i, j });
+ while(!list.isEmpty()){
+ int[] cur = list.remove();
+ i = cur[0]; j = cur[1];
+ if(inArea(i,j,grid)
+ && grid[i][j] == '1') {
+ grid[i][j] = '0';
+ list.add(new int[] { i + 1, j });
+ list.add(new int[] { i - 1, j });
+ list.add(new int[] { i, j + 1 });
+ list.add(new int[] { i, j - 1 });
+ }
+ }
+ }
+
+ public boolean inArea(int i, int j, char[][] grid){
+ return i >=0 && j >= 0
+ && i < grid.length
+ && j < grid[0].length;
+ }
+}
+```
+
+### 没有重复项数字的所有排列(全排列)
+
+```java
+public class Demo1 {
+ ArrayList> res;
+
+ public ArrayList> permute(int[] nums) {
+ res = new ArrayList>();
+ if (nums == null || nums.length < 1)
+ return res;
+ //对数组元素进行从小到大排序
+ Arrays.sort(nums);
+ ArrayList list = new ArrayList();
+
+ solve(list, nums);
+
+ return res;
+ }
+
+ private void solve(ArrayList list, int[] nums) {
+ if (list.size() == nums.length) {
+ res.add(new ArrayList(list));
+ return;
+ }
+ for (int i = 0; i < nums.length; i++) {
+ if (!list.contains(nums[i])) {
+ list.add(nums[i]);
+ solve(list, nums);
+ list.remove(list.size() - 1);
+ }
+ }
+ }
+}
+```
+
+### 集合的所有子集
+
+```java
+import java.util.*;
+public class Solution {
+ public ArrayList> subsets(int[] S) {
+ ArrayList> res=new ArrayList<>();
+ Arrays.sort(S);
+ LinkedList list=new LinkedList<>();
+ dfs(res,list,0,S);
+ return res;
+ }
+ public void dfs(ArrayList> res,
+ LinkedList list, int k, int[] S){
+ res.add(new ArrayList<>(list));
+ for(int i=k;i> res;
+ private boolean[] visited;
+
+ public ArrayList> permute(int[] nums) {
+ res = new ArrayList<>();
+ visited = new boolean[nums.length];
+ List list = new ArrayList<>();
+ backtrace(nums, list);
+ return res;
+ }
+
+ private void backtrace(int[] nums, List list) {
+ if (list.size() == nums.length) {
+ res.add(new ArrayList<>(list));
+ }
+
+ for (int i = 0; i < nums.length; i++) {
+ if (visited[i]) continue;
+ visited[i] = true;
+ list.add(nums[i]);
+ backtrace(nums, list);
+ visited[i] = false;
+ list.remove(list.size() - 1);
+ }
+ }
+}
+```
+
+### N皇后问题
+
+```java
+public class Solution {
+ /**
+ *
+ * @param n int整型 the n
+ * @return int整型
+ */
+ public int Nqueen (int n) {
+ // write code here
+ List res=new ArrayList<>();
+ char[][] chess=new char[n][n];
+ for(int i=0;i res){
+ if(row==chess.length){
+ res.add(1);
+ return;
+ }
+ for(int col=0;col=0&&j=0&&i>=0;i--,j--){
+ if(chess[i][j]=='Q'){
+ return false;
+ }
+ }
+ return true;
+ }
+}
+```
+
+### 把数组字符串转换为 ip 地址
+
+回溯法插入'.',每次可以插入到1个,2个或者3个字符后面,插入3次之后对得到的字符串进行验证
+
+```java
+public class Solution {
+ ArrayList res = new ArrayList<>();
+ public ArrayList restoreIpAddresses (String s) {
+ // write code here
+ if(s.length() == 0)
+ return res;
+ //表示当前字符串s,可以从第0个位置开始插入'.' ,还有3个'.'可以插入
+ backTrack(s, 0, 3);
+ return res;
+ }
+
+ public void backTrack(String s, int start, int cnt){
+ if(cnt == 0){
+ String[] splits = s.split("\\.");
+ //没有插入4个合法的小数点
+ if(splits.length < 4)
+ return;
+ //判断每一位是否合法
+ for(String str:splits){
+ if(str.length() > 1 && str.charAt(0) == '0') return; //最前面的数字不能为0
+ if(Integer.valueOf(str) > 255) return; //每一位都不能大于255
+ }
+ res.add(s);
+ return;
+ }
+
+ if(start >= s.length()) return; //没有插完全部的点 就已经超出字符串的范围了
+ int len = s.length();
+ //每次将一个字符作为一位
+ backTrack(s.substring(0,start+1)+'.'
+ +s.substring(start+1,len), start+2, cnt-1);
+ //每次将两位字符作为一位
+ if(start < len-2)
+ backTrack(s.substring(0,start+2)+'.'
+ +s.substring(start+2,len), start+3, cnt-1);
+ //每次将三位字符作为一位
+ if(start < len-3)
+ backTrack(s.substring(0,start+3)+'.'
+ +s.substring(start+3,len), start+4, cnt-1);
+ }
+}
+```
+
+### 加起来和为目标值的组合
+
+```java
+import java.util.* ;
+public class Solution {
+ public ArrayList> combinationSum2(int[] num, int target) {
+ Arrays.sort(num) ;
+ ArrayList> res = new ArrayList<>() ;
+ help(target , num , 0 , res , new ArrayList()) ;
+ return res ;
+ }
+ public void help(int target , int[] num , int idx ,
+ ArrayList> res , ArrayList tmp) {
+ if(target == 0) {
+ res.add(new ArrayList(tmp)) ;
+ return ;
+ }
+ for(int i = idx ; i < num.length ; i ++) {
+ if(num[i] > target) return ;//剪枝
+ if((i > idx && num[i] == num[i-1])) continue ;//去重
+ tmp.add(num[i]) ;
+ help(target-num[i] , num , i + 1 , res , tmp) ;//递归
+ tmp.remove(tmp.size() - 1) ;//回溯
+ }
+ }
+}
+```
+
+## 其他
+
+### 螺旋矩阵
+
+给定一个m x n大小的矩阵(m行,n列),按螺旋的顺序返回矩阵中的所有元素
+
+```java
+import java.util.*;
+public class Solution {
+ public ArrayList spiralOrder(int[][] matrix) {
+ ArrayList list = new ArrayList<>();
+
+ if(matrix.length == 0) {
+ return list;
+ }
+
+ int left = 0;
+ int right = matrix[0].length - 1;
+ int top = 0;
+ int bottom = matrix.length - 1;
+ int x = 0;
+
+
+ while(true) {
+ for(int i = left; i <= right; i++) { //从左到右
+ list.add(matrix[top][i]) ;
+ }
+
+ if(++top > bottom){
+ break;
+ }
+ for(int i = top; i <= bottom; i++){
+ list.add( matrix[i][right]); //从上到下
+ }
+
+ if(left > --right){
+ break;
+ }
+ for(int i = right; i >= left; i--){
+ list.add(matrix[bottom][i]); //从右到左
+ }
+
+ if(top > --bottom){
+ break;
+ }
+ for(int i = bottom; i >= top; i--){
+ list.add(matrix[i][left]); //从下到上
+ }
+
+ if(++left > right){
+ break;
+ }
+ }
+ return list;
+ }
+}
+```
+
+### 顺时针旋转矩阵
+
+原矩阵元素的列数变成新矩阵元素的行数: 原矩阵元素的行数是第2行,旋转后元素的列数是从右往左倒数第2列。
+因此对于原矩阵mat[i][j],旋转后该值应该在新矩阵ans[j][n-i-1]的位置。
+
+```java
+class Solution {
+public:
+ vector > rotateMatrix(vector > mat, int n) {
+ // write code here
+ vector> ans(n,vector(n));
+ for(int i=0;i=10){
+ sb.append(arr[temp-10]);
+ }
+ //小于10,直接加到sb
+ else{
+ sb.append(temp);
+ }
+ M/=N;
+ }
+ //负数要多加一个负号
+ if(f){
+ sb.append('-');
+ }
+ //反转后转为字符串返回
+ return sb.reverse().toString();
+ }
+}
+```
+
+### 反转数字
+
+```java
+public class Solution {
+ /**
+ *
+ * @param x int整型
+ * @return int整型
+ */
+ public int reverse (int x) {
+ // write code here
+ int res = 0;
+ while(x != 0){
+ // 获取最后一位
+ int tail = x % 10;
+ int newRes = res * 10 + tail;
+ // 如果不等于,说明溢出
+ if((newRes - tail) / 10 != res){
+ return 0;
+ }
+ res = newRes;
+ x /= 10;
+ }
+
+ return res;
+ }
+}
+```
+
+### 大数加法
+
+```java
+public class Solution {
+ public String solve (String s, String t){
+ int i = s.length() - 1, j = t.length() - 1;
+ int temp = 0;
+ StringBuilder out = new Stringbuilder();
+ while (i >= 0 || j >= 0 || temp != 0) {
+ temp += i >= 0 ? s.charAt(i--) - '0' : 0;
+ temp += j >= 0 ? t.charAt(j--) - '0' : 0;
+ out.append(temp % 10);
+ temp = temp / 10;
+ }
+ return out.reverse().toString();
+ }
+}
+
+public class Solution {
+ public String solveByJava(String s, String t){
+ BigInteger num1 = new BigInteger(s);
+ BigInteger num2 = new BigInteger(t);
+ return num1.add(num2).toString();
+ }
+}
+```
+
+### 把字符串转换成整数(atoi)
+
+1、首位空格:通过trim()函数即可处理
+2、正负:通过判断第一位,使用变量储存符号即可
+3、非数字字符:对每一位进行判断,非数字则结束
+4、越界:通过提前预判,判断拼接后是否大于阈值,进行处理
+
+```java
+public class Solution {
+ public int StrToInt (String s) {
+ // write code here
+ char[] array = s.trim().toCharArray();
+ if(array.length==0){
+ return 0;
+ }
+ int sign = 1;
+ int res = 0;
+ int i = 0;
+
+ if(array[i] == '+' || array[i] == '-'){
+ sign = array[i++] == '+' ? 1 : -1;
+ }
+ while(i < array.length){
+ char cur = array[i];
+ if(cur < '0' || cur>'9'){
+ break;
+ }
+ if (res >= Integer.MAX_VALUE / 10) {
+ if(res > Integer.MAX_VALUE / 10){
+ return sign==1
+ ? Integer.MAX_VALUE : Integer.MIN_VALUE;
+ }
+ if(res == Integer.MAX_VALUE / 10){
+ if(sign == 1 && (cur - '0') > 7){
+ return Integer.MAX_VALUE;
+ }else if(sign == -1 && (cur - '0') > 8){
+ return Integer.MIN_VALUE;
+ }
+ }
+ }
+ res = res * 10 + (cur - '0');
+ i++;
+ }
+ return sign * res;
+ }
+}
+```
+
+### 最长公共前缀
+
+```java
+// 方法一:先取第一个字符串当做他们的公共前缀
+// 然后找出他和第2个字符串的公共前缀,然后再用这个找出的公共前缀分别和第3个,第4个……判断
+public String longestCommonPrefix(String[] strs) {
+ //边界条件判断
+ if (strs == null || strs.length == 0)
+ return "";
+ //默认第一个字符串是他们的公共前缀
+ String pre = strs[0];
+ int i = 1;
+ while (i < strs.length) {
+ //不断的截取
+ while (strs[i].indexOf(pre) != 0)
+ pre = pre.substring(0, pre.length() - 1);
+ i++;
+ }
+ return pre;
+}
+
+// 方法二:按照字典序排序之后比较字典序最小的子串和字典序最大的子串的相同部分,
+// 得到的最长公共前缀就是所有字符串的最长公共前缀
+public class Solution {
+ public String longestCommonPrefix (String[] strs) {
+ int len = strs.length;
+ if(len==0) return "";
+ Arrays.sort(strs);
+ //枚举第一个最小的子串和最后一个最大的子串
+ int i = 0;
+ String a = strs[0];
+ String b = strs[len-1];
+ for(i = 0;i < a.length()&&a.charAt(i)==b.charAt(i);i++);
+ return a.substring(0,i);
+ }
+}
+```
+
+### 回文数字
+
+```java
+// 方法一:双指针
+import java.util.*;
+public class Solution {
+ /**
+ *
+ * @param x int整型
+ * @return bool布尔型
+ */
+ public boolean isPalindrome (int x) {
+ if(x<0) return false;
+ // 转换成字符串
+ String xs = String.valueOf(x);
+ // 利用双指针
+ int left = 0;
+ int right = xs.length()-1;
+ // 比较字符串的头部和尾部是否相同
+ while(left < right){
+ // 不相同直接返回
+ if(xs.charAt(left) != xs.charAt(right)) return false;
+ left++;
+ right--;
+ }
+ return true;
+ }
+}
+
+// 方法二:翻转数字
+public class Solution {
+ /**
+ *
+ * @param x int整型
+ * @return bool布尔型
+ */
+ public boolean isPalindrome (int x) {
+ // write code here
+ if(x<0) return false;
+ int reverse = 0;
+ int tmp = x;
+ while(tmp>0){
+ int div = tmp%10;
+ // 判断是否会溢出
+ if(reverse >= Integer.MAX_VALUE/10 && div > 7) return false;
+ // 获得反向数字
+ reverse = reverse*10 + div;
+ tmp = tmp/10;
+ }
+ return x == reverse;
+ }
+}
+```
+
+### 字符串变形(反序,大写)
+
+利用split切割为String数组
+String数组从后往前遍历,拿到具体的String从0到str.length()遍历,并判断大小写然后转换
+需要注意的地方就是s.split(" ",-1),limit需要设置为-1来不舍弃最后的空串
+
+```java
+import java.util.*;
+
+public class Solution {
+ public String trans(String s, int n) {
+ // write code here
+ String[] strArr = s.split(" ",-1); // 注意这里limit为-1,不舍弃最后的空串
+ StringBuilder sb = new StringBuilder();
+ for(int i = strArr.length - 1; i >= 0; i--) {
+ for(int j = 0; j < strArr[i].length(); j++) {
+ if(Character.isUpperCase(strArr[i].charAt(j))) {
+ sb.append(Character.toLowerCase(strArr[i].charAt(j)));
+ } else {
+ sb.append(Character.toUpperCase(strArr[i].charAt(j)));
+ }
+ }
+ if(i != 0) {
+ sb.append(" ");
+ }
+ }
+ return sb.toString();
+ }
+}
+```
+
+### 最大值(数组拼接最大数)
+
+```java
+import java.util.*;
+public class Solution {
+ /**
+ * 最大数
+ * @param nums int整型一维数组
+ * @return string字符串
+ */
+ public String solve (int[] nums) {
+ String[] strArr = new String[nums.length];
+ for (int i = 0 ; i < nums.length ; i++) {
+ strArr[i] = String.valueOf(nums[i]);
+ }
+ Arrays.sort(strArr,
+ (o1, o2) -> Integer.parseInt(o2 + o1) - Integer.parseInt(o1 + o2));
+ StringBuilder maxString = new StringBuilder();
+ if (strArr[0].equals( "0")) {
+ return "0";
+ }
+ for (int i = 0 ; i < strArr.length; i++) {
+ maxString.append(strArr[i]);
+ }
+ return maxString.toString();
+ }
+}
+```
+
+### 验证ip地址
+
+```java
+public String validIPAddress(String IP) {
+ return validIPv4(IP)
+ ? "IPv4" : (validIPv6(IP) ? "IPv6" : "Neither");
+}
+
+private boolean validIPv4(String IP) {
+ String[] strs = IP.split("\\.", -1);
+ if (strs.length != 4) {
+ return false;
+ }
+
+ for (String str : strs) {
+ if (str.length() > 1 && str.startsWith("0")) {
+ return false;
+ }
+ try {
+ int val = Integer.parseInt(str);
+ if (!(val >= 0 && val <= 255)) {
+ return false;
+ }
+ } catch (NumberFormatException numberFormatException) {
+ return false;
+ }
+ }
+ return true;
+}
+
+private boolean validIPv6(String IP) {
+ String[] strs = IP.split(":", -1);
+ if (strs.length != 8) {
+ return false;
+ }
+
+ for (String str : strs) {
+ if (str.length() > 4 || str.length() == 0) {
+ return false;
+ }
+ try {
+ int val = Integer.parseInt(str, 16);
+ } catch (NumberFormatException numberFormatException) {
+ return false;
+ }
+ }
+ return true;
+}
+```
+
+### 二进制中1的个数
+
+将数字与1进行与运算,返回结果为1则表明数字二进制最后一位是1,通过对不断数字右移运算判断有多少个1。
+
+```java
+public class Solution {
+ // replace替换
+ public int NumberOf1(int n) {
+ return Integer.toBinaryString(n).replace("0","").length();
+ }
+
+ // 遍历字符串记录
+ public int NumberOf1(int n) {
+ String str = Integer.toBinaryString(n);
+ int length = str.length();
+ int count = 0;
+ for (int i=0; i>>= 1;
+ }
+ return res;
+ }
+}
+```
+
+### 第一个只出现一次的字符
+
+```java
+import java.util.*;
+public class Solution {
+ public static int FirstNotRepeatingChar(String str) {
+ int[] map = new int[58];
+ char[] chs = str.toCharArray();
+ for(int i = 0; i < chs.length; i++){
+ map[chs[i] - 'A'] ++;
+ }
+ int res = -1;
+ for(int i = 0; i < chs.length; i++){
+ if(map[chs[i] - 'A'] == 1){
+ res = i;
+ break;
+ }
+ }
+ return res;
+ }
+}
+```
+
+## 其他编程题(golang、java)
+
+### 单例模式
+
+双重否定单例模式:即可以保证线程的安全性
+(避免两个线程同时进入到 synchronized (Singleton.class)时,
+线程1先获取到了锁,释放后,线程2执行,如果没有第二次空的判断,会导致多次创建对象),
+也可以实现只有第一次创建new的时候才会执行到同步代码块中的代码,提高了效率。
+
+- java
+
+```java
+// 方法一
+//优点:线程安全,volatile关键词主要是保证多线程之间的可见性,
+// 保证线程在每次取值volatile变量都是最新值
+//volatile关键字主要是禁止命令重排序的,但是volatile不是原子性的
+public class Singleton {
+ private static volatile Singleton instance = null;
+ private Singleton() {}
+ public static Singleton getInstance() {
+ if (null == instance) {
+ synchronized (Singleton.class) {
+ if (null == instance) {
+ instance = new Singleton();
+ }
+ }
+ }
+ return instance;
+ }
+}
+
+// 方法二
+public class Singleton {
+ private Singleton() {}
+ private static Singleton getInstance() {
+ return SingletonHolder.instance;
+ }
+ private static class SingletonHolder {
+ //静态变量值会初始化一次
+ private static Singleton instance = new Singleton();
+ }
+}
+```
+
+- go
+
+```go
+package main
+
+import (
+ "fmt"
+ "sync"
+)
+
+var lock = &sync.Mutex{} // 创建互锁
+type Singleton struct {} // 创建结构体
+
+var singletonInstance *Singleton // 创建指针
+
+func getInstance() *single {
+ if singletonInstance == nil { //!!!注意这里check nil了两次
+ lock.Lock()
+ defer lock.Unlock()
+ if singletonInstance == nil {
+ fmt.Println("创建单例")
+ singletonInstance = &Singleton{}
+ } else {
+ fmt.Println("单例对象已创建")
+ }
+ } else {
+ fmt.Println("单例对象已创建")
+ }
+ return singletonInstance
+}
+```
+
+### 实现线程安全的生产者消费者
+
+- java
+
+https://blog.csdn.net/u010983881/article/details/78554671
+
+- golang
+
+https://blog.csdn.net/weixin_50005436/article/details/123065703
+
+### 一个10G的文件,里面全部是自然数,一行一个,乱序排列,对其排序。在32位机器上面完成,内存限制为 2G(bitmap原理知道吗?)
+
+首先,10G文件是不可能一次性放到内存里的。这类问题一般有两种解决方案:
+
+- 将10G文件分成多个小文件,分别排序,最后合并一个文件;
+- 采用bitmap
+
+如果面试大数据类岗位,可能面试官就想考察你对Mapreduce熟悉程度,要采用第一种merge and sort。
+
+如果是算法类岗位,就要考虑bitmap,但需要注意的是bitmap**不能对重复数据进行排序**。这里我们详细介绍一下:
+
+定量分析一下,32位机器自然数有2^32个,用一个bit来存放一个整数,那么所需的内存是,
+`2^32/(8<<20) = 512MB` ,这些数存放在文件中,一行一个,需要20G容量,
+所以题目问10G文件,只需要256MB内存就可以完成。
+
+bitmap实现具体分为两步:插入一个数,和排序。
+
+```go
+type BitMap struct {
+ vec []byte
+ size int
+}
+
+func New(size int) *BitMap {
+ return &BitMap{
+ size: size,
+ vec: make([]byte, size),
+ }
+}
+
+func (bm *BitMap) Set(num int) (ok bool, err error) {
+ if num/8 >= bm.size {
+ return false, errors.New("the num overflows the size of bitmap")
+ }
+ bm.vec[num/8] |= 1 << (num % 8)
+ return true, nil
+}
+
+func (bm *BitMap) Exist(num int) bool {
+ if num/8 >= bm.size {
+ return false
+ }
+ return bm.vec[num/8]&(1<<(num%8)) > 0
+}
+
+func (bm *BitMap) Sort() (ret []int) {
+ ret = make([]int, 0)
+ for i := 0; i < (8 * bm.size); i++ {
+ if bm.Exist(i) {
+ ret = append(ret, i)
+ }
+ }
+ return
+}
+```
+
+### 实现使用字符串函数名,调用函数
+
+思路:采用反射的Call方法实现。
+
+```go
+package main
+import (
+ "fmt"
+ "reflect"
+)
+
+type Animal struct{
+
+}
+
+func (a *Animal) Eat(){
+ fmt.Println("Eat")
+}
+
+func main(){
+ a := Animal{}
+ reflect.ValueOf(&a).MethodByName("Eat").Call([]reflect.Value{})
+
+}
+```
+
+### 负载均衡算法。(一致性哈希)
+
+```go
+package main
+
+import (
+ "fmt"
+ "sort"
+ "strconv"
+)
+
+type HashFunc func(key []byte) uint32
+
+type ConsistentHash struct {
+ hash HashFunc
+ hashvals []int
+ hashToKey map[int]string
+ virtualNum int
+}
+
+func NewConsistentHash(virtualNum int, fn HashFunc) *ConsistentHash {
+ return &ConsistentHash{
+ hash: fn,
+ virtualNum: virtualNum,
+ hashToKey: make(map[int]string),
+ }
+}
+
+func (ch *ConsistentHash) AddNode(keys ...string) {
+ for _, k := range keys {
+ for i := 0; i < ch.virtualNum; i++ {
+ conv := strconv.Itoa(i)
+ hashval := int(ch.hash([]byte(conv + k)))
+ ch.hashvals = append(ch.hashvals, hashval)
+ ch.hashToKey[hashval] = k
+ }
+ }
+ sort.Ints(ch.hashvals)
+}
+
+func (ch *ConsistentHash) GetNode(key string) string {
+ if len(ch.hashToKey) == 0 {
+ return ""
+ }
+ keyhash := int(ch.hash([]byte(key)))
+ id := sort.Search(len(ch.hashToKey), func(i int) bool {
+ return ch.hashvals[i] >= keyhash
+ })
+ return ch.hashToKey[ch.hashvals[id%len(ch.hashvals)]]
+}
+
+func main() {
+ ch := NewConsistentHash(3, func(key []byte) uint32 {
+ ret, _ := strconv.Atoi(string(key))
+ return uint32(ret)
+ })
+ ch.AddNode("1", "3", "5", "7")
+ testkeys := []string{"12", "4", "7", "8"}
+ for _, k := range testkeys {
+ fmt.Printf("k:%s,node:%s\n", k, ch.GetNode(k))
+ }
+}
+```
+
+### (Goroutine)有三个函数,分别打印"cat", "fish","dog"要求每一个函数都用一个goroutine,按照顺序打印100次
+
+此题目考察channel,用三个无缓冲channel,如果一个channel收到信号则通知下一个。
+
+```go
+package main
+
+import (
+ "fmt"
+ "time"
+)
+
+var dog = make(chan struct{})
+var cat = make(chan struct{})
+var fish = make(chan struct{})
+
+func Dog() {
+ <-fish
+ fmt.Println("dog")
+ dog <- struct{}{}
+}
+
+func Cat() {
+ <-dog
+ fmt.Println("cat")
+ cat <- struct{}{}
+}
+
+func Fish() {
+ <-cat
+ fmt.Println("fish")
+ fish <- struct{}{}
+}
+
+func main() {
+ for i := 0; i < 100; i++ {
+ go Dog()
+ go Cat()
+ go Fish()
+ }
+ fish <- struct{}{}
+
+ time.Sleep(10 * time.Second)
+}
+```
+
+- Java 实现
+
+https://www.cnblogs.com/jyx140521/p/6747750.html
+
+### 两个协程交替打印10个字母和数字
+
+思路:采用channel来协调goroutine之间顺序。
+
+主线程一般要waitGroup等待协程退出,这里简化了一下直接sleep。
+
+```go
+package main
+
+import (
+ "fmt"
+ "time"
+)
+
+var word = make(chan struct{}, 1)
+var num = make(chan struct{}, 1)
+
+func printNums() {
+ for i := 0; i < 10; i++ {
+ <-word
+ fmt.Println(1)
+ num <- struct{}{}
+ }
+}
+func printWords() {
+ for i := 0; i < 10; i++ {
+ <-num
+ fmt.Println("a")
+ word <- struct{}{}
+ }
+}
+
+func main() {
+ num <- struct{}{}
+ go printNums()
+ go printWords()
+ time.Sleep(time.Second * 1)
+}
+```
+
+- Java 实现
+
+```java
+package com.lutongnet.util;
+
+import org.junit.Test;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedTransferQueue;
+import java.util.concurrent.TransferQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.LockSupport;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * @author mifei
+ * @version 1.0.0
+ * @description 多线程测试
+ * @date 2020-11-28 15:18
+ */
+public class CommonThreadTest {
+
+ Thread t1 = null;
+ Thread t2 = null;
+
+ /**
+ * 测试Synchronized的wait和notify写法
+ */
+ @Test
+ public void testSynchronized() {
+ Object o = new Object();
+ char [] letterArray = "ABCDEFGHIJ".toCharArray();
+ char [] numbberArray = "1234567890".toCharArray();
+
+ t1 = new Thread(()->{
+ synchronized (o) {
+ for (char c: letterArray) {
+ System.out.println("字母:" + c);
+ try {
+ o.notify();
+ o.wait();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ o.notify();
+ }
+ });
+
+ t2 = new Thread(()->{
+ synchronized (o) {
+ for (char c: numbberArray) {
+ System.out.println("数字:" + c);
+ try {
+ o.notify();
+ o.wait();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ o.notify();
+ }
+ });
+
+ t1.start();
+ t2.start();
+ }
+
+ /**
+ * 测试ReenTrantLock写法
+ */
+ @Test
+ public void testReenTrantlock() {
+ char [] letterArray = "ABCDEFGHIJ".toCharArray();
+ char [] numberArray = "1234567890".toCharArray();
+
+ Lock lock = new ReentrantLock();
+ Condition letterCondition = lock.newCondition();
+ Condition numberCondition = lock.newCondition();
+
+ new Thread(()->{
+ try {
+ lock.lock();
+ for (char c: letterArray) {
+ System.out.println("字母:" + c);
+ numberCondition.signal();
+ letterCondition.await();
+ }
+ numberCondition.signal();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } finally {
+ lock.unlock();
+ }
+ }, "t1").start();
+
+ new Thread(()->{
+ try {
+ lock.lock();
+ for (char c: numberArray) {
+ System.out.println("数字:" + c);
+ letterCondition.signal();
+ numberCondition.await();
+ }
+ letterCondition.signal();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } finally {
+ lock.unlock();
+ }
+ }, "t2").start();
+ }
+
+ /**
+ * 测试LockSupport写法
+ */
+ @Test
+ public void testLockSupport() {
+ char [] letterArray = "ABCDEFGHIJ".toCharArray();
+ char [] numberArray = "1234567890".toCharArray();
+
+ t1 = new Thread(()->{
+ for (char c: letterArray) {
+ System.out.println("字母:" + c);
+ LockSupport.unpark(t2);
+ LockSupport.park();
+ }
+ });
+
+ t2 = new Thread(()->{
+ for (char c: numberArray) {
+ LockSupport.park();
+ System.out.println("数字:" + c);
+ LockSupport.unpark(t1);
+ }
+ });
+
+ t1.start();
+ t2.start();
+ }
+
+ /**
+ * 测试BlockingQueue写法
+ */
+ @Test
+ public void testBlockingQueue() {
+ char [] letterArray = "ABCDEFGHIJ".toCharArray();
+ char [] numberArray = "1234567890".toCharArray();
+
+ BlockingQueue q1 = new ArrayBlockingQueue(1);
+ BlockingQueue q2 = new ArrayBlockingQueue(1);
+
+ new Thread(()->{
+ for (char c: letterArray) {
+ System.out.println("字母:" + c);
+ try {
+ q1.put("ok");
+ q2.take();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ }
+ }, "t1").start();
+
+ new Thread(()->{
+ for (char c: numberArray) {
+ try {
+ q1.take();
+ System.out.println("数字:" + c);
+ q2.put("ok");
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }, "t2").start();
+ }
+
+ /**
+ * 测试AtomicInteger写法
+ */
+ @Test
+ public void testAtomicInteger() {
+ AtomicInteger threadNo = new AtomicInteger(1);
+
+ char [] letterArray = "ABCDEFGHIJ".toCharArray();
+ char [] numberArray = "1234567890".toCharArray();
+
+ new Thread(()->{
+ for (char c: letterArray) {
+ while (threadNo.get() != 1) {}
+ System.out.println("字母:" + c);
+ threadNo.set(2);
+ }
+ }, "t1").start();
+
+ new Thread(()->{
+ for (char c: numberArray) {
+ while (threadNo.get() != 2) {}
+ System.out.println("数字:" + c);
+ threadNo.set(1);
+ }
+ }, "t2").start();
+ }
+
+ /**
+ * 测试TransferQueue写法
+ */
+ @Test
+ public void testTransferQueue() {
+ char [] letterArray = "ABCDEFGHIJ".toCharArray();
+ char [] numberArray = "1234567890".toCharArray();
+
+ TransferQueue queue = new LinkedTransferQueue<>();
+ new Thread(()->{
+ try {
+ for (char c : letterArray) {
+ System.out.println("数字:" + queue.take());
+ queue.transfer(c);
+ }
+
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }, "t1").start();
+
+ new Thread(()->{
+ try {
+ for (char c : numberArray) {
+ queue.transfer(c);
+ System.out.println("字母:" + queue.take());
+ }
+
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }, "t2").start();
+
+ }
+}
+```
+
+### 启动 2个groutine 2秒后取消, 第一个协程1秒执行完,第二个协程3秒执行完。
+
+思路:采用`ctx, _ := context.WithTimeout(context.Background(), time.Second*2)`实现2s取消。
+协程执行完后通过channel通知,是否超时。
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+)
+
+func f1(in chan struct{}) {
+
+ time.Sleep(1 * time.Second)
+ in <- struct{}{}
+
+}
+
+func f2(in chan struct{}) {
+ time.Sleep(3 * time.Second)
+ in <- struct{}{}
+}
+
+func main() {
+ ch1 := make(chan struct{})
+ ch2 := make(chan struct{})
+ ctx, _ := context.WithTimeout(context.Background(), 2*time.Second)
+
+ go func() {
+ go f1(ch1)
+ select {
+ case <-ctx.Done():
+ fmt.Println("f1 timeout")
+ break
+ case <-ch1:
+ fmt.Println("f1 done")
+ }
+ }()
+
+ go func() {
+ go f2(ch2)
+ select {
+ case <-ctx.Done():
+ fmt.Println("f2 timeout")
+ break
+ case <-ch2:
+ fmt.Println("f2 done")
+ }
+ }()
+ time.Sleep(time.Second * 5)
+}
+```
+
+
+### 当select监控多个chan同时到达就绪态时,如何先执行某个任务?
+
+可以在子case再加一个for select语句。
+
+```go
+func priority_select(ch1, ch2 <-chan string) {
+ for {
+ select {
+ case val := <-ch1:
+ fmt.Println(val)
+ case val2 := <-ch2:
+ priority:
+ for {
+ select {
+ case val1 := <-ch1:
+ fmt.Println(val1)
+
+ default:
+ break priority
+ }
+ }
+ fmt.Println(val2)
+ }
+ }
+
+}
+```
\ No newline at end of file
diff --git "a/docs/dataStructures-algorithms/\351\253\230\351\242\221\347\256\227\346\263\225\351\242\230\347\233\256\346\200\273\347\273\223.pdf" "b/docs/dataStructures-algorithms/\351\253\230\351\242\221\347\256\227\346\263\225\351\242\230\347\233\256\346\200\273\347\273\223.pdf"
new file mode 100644
index 0000000..ddc6e28
Binary files /dev/null and "b/docs/dataStructures-algorithms/\351\253\230\351\242\221\347\256\227\346\263\225\351\242\230\347\233\256\346\200\273\347\273\223.pdf" differ
diff --git "a/docs/database/MySQL\351\235\242\350\257\225\351\242\230\344\270\200.md" "b/docs/database/MySQL\351\235\242\350\257\225\351\242\230\344\270\200.md"
new file mode 100644
index 0000000..aa25c45
--- /dev/null
+++ "b/docs/database/MySQL\351\235\242\350\257\225\351\242\230\344\270\200.md"
@@ -0,0 +1,783 @@
+### 基础问题
+
+> truncate和delete有什么区别?
+
+truncate table:删除内容、不删除定义、释放空间
+delete table:删除内容、不删除定义、不释放空间
+drop table:删除内容、删除定义、释放空间
+
+具体来说,有以下区别:
+- truncate table 只能删除表中全部数据,delete from 可以删除表中的全部数据也可以部分删除。
+- delete from记录是一条条删除的,删除的每一行记录都会进日志,而truncate一次性删除整个页,日志只会记录页的释放。
+- truncate删除后不能回滚,delete则可以。
+- truncate的删除速度比delete快。
+- delete删除后,删除的数据占用的存储空间还存在,可以恢复数据;truncate则空间不存在,同时不能恢复数据。
+
+总的来说,其差别在于,truncate删除是不可恢复的,同时空间也不存在,不支持回滚,而delete删除正好相反。
+
+> select Count(*)、select Count(数字)和select Count(column)的区别?
+
+count(*)和count(1)返回的结果是记录的总行数,包括对NULL的统计;而count(column)是不会记录NULL的统计。
+
+> EXISTS关键词的使用方法
+
+EXISTS表示是否存在,使用EXISTS时,如果内存查询语句查询到符合条件的值,就返回一个true,否则,将返回false。
+
+例如:
+```sql
+SELECT * FROM user
+ where EXISTS
+ (SELECT name FROM employee WHERE id=100)
+```
+
+如果employee表中存在id为100的员工,内层查询就会返回一个true,外层查询接收到true后,开始查询user表中的数据,因为where没有设置其他查询条件,所以,将查询出user的全部数据。
+
+> 内连接(inner join)和外连接的区别?
+
+- 内连接:只会查询出两表连接符合条件的记录。
+
+```sql
+SELECT * FROM user1 u1 INNER JOIN user2 u2 ON u1.id = u2.id;
+```
+
+如上sql所示,只会查询到user1和user2关联符合条件的记录。
+
+外连接分为左外连接、右外连接和全外连接。
+
+- 左外连接
+
+它的原理是:以左表为基准,去右表匹配数据,找不到匹配的用NULL补齐。其显示左表的全部记录和右表符合连接条件的记录。
+
+- 右外连接
+
+它的原理是:以右表为基准,去左表匹配数据,找不到匹配的用NULL补齐。其显示右表的全部记录和左表符合连接条件的记录。这正好与左外连接相反。
+
+- 全外连接
+
+除了显示符合连接条件的记录之外,两个表的其他数据也会显示出来。
+
+> inner join 和 left join性能谁优谁劣?
+
+
+
+如上图,是mysql的执行顺序,我们可以看出,外查询是在内查询的基础上,进而进行查询操作的。因此,我们从理论上可以得出,内连接的执行效率是更好一些的。
+
+但是,外连接也是会进行优化操作的,在编译优化阶段,如果左连接的结果和内连接一样,左连接查询会转换成内连接查询,但这也表明编译优化器也认为内连接的效率是更高的。
+
+虽然从查询的结果来看一般不会有太大的区别,但是,如果左右表之间的数据差别很大,内连接的效率是明显更高的,因为左连接以左表为基准,并且会进行回表操作。
+
+最后,给出一个结论:在外连接和内连接都可以实现需求时,建议使用内连接进行操作。
+
+> 存储过程是什么,优势是什么,为什么不建议使用?
+
+存储过程:为了完成特定功能的SQL语句集,存储在数据库中,一次编译后永久有效,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。存储过程是数据库中的一个重要对象。在数据量特别庞大的情况下利用存储过程能达到倍速的效率提升。
+
+**优势**
+- 存储过程是预编译的,因此执行速度较快;
+- 存储过程在服务端执行,减少客户端的压力;
+- 减少网络流量,客户端只需要传递存储过程名称和参数即可进行调用,减少了传输的数据量;
+- 一次编写,任意次执行,复用的思想简单便捷;
+- 安全性较高,因为在服务端执行,管理员可以对存储过程进行权限限制,能够避免非法的访问,保证数据的安全性。
+
+**缺点**
+
+- 调试麻烦,可移植性查。
+
+这一个缺点也是存储过程在实际开发中用的不多的原因,现在开发的理念是简便。
+
+> 数据库的三级范式
+
+1NF:原子性,字段不可再分;
+2NF:在满足第一范式的基础上,一个表只能说明一个事物,非主键属性必须玩去哪依赖于主键属性;
+3NF:在满足第二范式的基础上,每列都与主键有直接关系,不存在传递依赖,任何非主键属性不依赖于其他非主键属性。
+
+> sql语句应该考虑哪些安全性
+
+- 防止sql注入,对sql语句尽量进行过滤同时使用预编译的sql语句绑定变量
+- 查询错误信息不要返回给用户,将错误记录到日志
+- 最小用户权限设置,最好不要使用root用户连接数据库
+- 定期做数据备份,避免数据丢失
+
+> 什么叫sql注入,如何防止?
+
+SQL注入是一种将SQL代码添加到输入参数中,传递到服务器解析并执行的一种攻击手法。
+
+**SQL注入攻击**是输入参数未经过滤,然后直接拼接到SQL语句当中解析,执行达到预想之外的一种行为,称之为SQL注入攻击。
+
+常见的sql注入,有下列几种:
+- 数字注入
+
+例如,查询的sql语句为:`SELECT * FROM user WHERE id = 1`,正常是没有问题的,如果我们进行sql注入,写成`SELECT * FROM user WHERE id = 1 or 1=1`,那么,这个语句永远都是成立的,这就有了问题,也就是sql注入。
+
+- 字符串注入
+
+字符串注入是因为注释的原因,导致sql错误的被执行,例如字符`#`、`--`。
+
+例如,`SELECT * FROM user WHERE username = 'sihai'#'ADN password = '123456'`,这个sql语句'#'后面都被注释掉了,相当于`SELECT * FROM user WHERE username = 'sihai' `。
+
+这种情况我们在mybatis中也是会存在的,所以在服务端写sql时,需要特别注意此类情况。
+
+该如何防范此类问题呢?
+- 严格检查输入变量的类型和格式,也就是对相关传入的参数进行验证,尽可能降低风险。
+- 过滤和转义特殊字符。
+- 利用mysql的预编译机制,在Java中mybatis也是有预编译的方法的,所以可以采用这种方式避免。
+
+> MySQL中InnoDB和MyISAM的区别?
+
+- 事务处理方面:MyISAM强调的是性能,查询的速度比InnoDB更快,但是,不支持事务,InnoDB是支持事务的。
+- 外键:InnoDB支持外键,MyISAM不支持。
+- 锁:InnoDB支持行锁和表锁,默认会使用行锁,而MyISAM只是支持表锁。由于行锁是能更好的支持并发操作,因此,InnoDB更加适合插入和更新操作较多的情况,而MyISAM适用于频繁查询操作。
+- 全文索引:MyISAM支持全文索引,InnoDB不支持。但是,在5.6版本开始InnoDB也开始支持全文索引。
+- 表主键:MyISAM允许没有主键的表存在,而InnoDB如果不存在主键,会自动生成一个6字节的主键。
+- 查询表的行数差异:InnoDB不保存表的函数信息,因此,select count(*)时会扫描整个表来进行计算;MyISAM内置了计数器,只需要简单的读取保存好的行数即可。
+
+
+### 事务相关
+
+> 数据库事务的四个基本要素?
+
+事务是指一组SQL语句组成的逻辑处理单元。
+
+ACID:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
+
+
+- 原子性(Atomicity)
+
+原子性指的是整个数据库的事务是一个不可分割的工作单位,每一个都应该是一个原子操作。
+
+当我们执行一个事务的时候,如果一系列的操作中,有一个操作失败了,那么,需要将这一个事务中的所有操作恢复到执行事务之前的状态,这就是事务的原子性。
+
+下面举个简单的例子。
+```java
+i++;
+```
+上面这个最简单不过的代码经常也会被问到,这是一个原子操作吗?那肯定不是,如果我们把这个代码放到一个事务中来说,当i+1出现问题的时候,回滚的就是整个代码i++(i = i + 1)了,所以回滚之后,i的值也是不会改变的。
+
+以上就是原子性的概念。
+
+- 一致性(consistency)
+
+一致性是指事务将数据库从一种状态转变为下一种一致性的状态,也就是说在事务执行前后,这两种状态应该是一样的,也就是数据库的完整性约束不会被破坏。
+
+另外,需要注意的是一致性是不关注中间状态的,比如银行转账的过程,你转账给别人,至于中间的状态,你少了500 ,他多了500,这些中间状态不关注,如果分多次转账中间状态也是不可见的,只有最后的成功或者失败的状态是可见的。
+
+如果到分布式的一致性问题,又可以分为强一致性、弱一致性和最终一致性,关于这些概念,可以自己查查,还是很有意思的。
+
+- 隔离性(isolation)
+
+事务我们是可以开启很多的,MySQL数据库中可以同时启动很多的事务,但是,事务和事务之间他们是相互分离的,也就是互不影响的,这就是事务的隔离性。
+
+- 持久性(durability)
+
+事务的持久性是指事务一旦提交,就是永久的了,就是发生问题,数据库也是可以恢复的。因此,持久性保证事务的高可靠性。
+
+> 并发事务会带来什么问题
+
+#### 脏读
+
+**脏读:** 在不同的事务下,当前事务可以读到另外事务未提交的数据。另外我们需要注意的是默认的MySQL隔离级别是`REPEATABLE READ`是不会发生脏读的,脏读发生的条件是需要事务的隔离级别为`READ UNCOMMITTED`,所以如果出现脏读,可能就是这种隔离级别导致的。
+
+下面我们通过一个例子看一下。
+
+
+从上面这个例子可以看出,当我们的事务的隔离级别为`READ UNCOMMITTED`的时候,在会话A还没有提交时,会话B就能够查询到会话A没有提交的数据。
+
+
+#### 不可重复读
+
+**不可重复读:** 是指在一个事务内多次读取同一集合的数据,但是多次读到的数据是不一样的,这就违反了数据库事务的一致性的原则。但是,这跟脏读还是有区别的,脏读的数据是没有提交的,但是不可重复读的数据是已经提交的数据。
+
+我们通过下面的例子来看一下这种问题的发生。
+
+
+
+从上面的例子可以看出,在A的一次会话中,由于会话B插入了数据,导致两次查询的结果不一致,所以就出现了不可重复读的问题。
+
+我们需要注意的是不可重复读读取的数据是已经提交的数据,事务的隔离级别为`READ COMMITTED`,这种问题我们是可以接受的。
+
+如果我们需要避免不可重复读的问题的发生,那么我们可以使用**Next-Key Lock算法**(设置事务的隔离级别为`READ REPEATABLE`)来避免,在MySQL中,不可重复读问题就是Phantom Problem,也就是**幻像问题**。
+
+#### 幻读
+
+幻读本质上也属于不可重复读的情况,会话 A 读取某个范围的数据,会话 B 在这个范围内**插入**新的数据,会话 A 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。
+
+#### 丢失更新
+
+**丢失更新:** 指的是一个事务的更新操作会被另外一个事务的更新操作所覆盖,从而导致数据的不一致。在当前数据库的任何隔离级别下都不会导致丢失更新问题,要出现这个问题,在多用户计算机系统环境下有可能出现这种问题。
+
+如何避免丢失更新的问题呢,我们只需要让事务的操作变成串行化,不要并行执行就可以。
+
+我们一般使用`SELECT ... FOR UPDATE`语句,给操作加上一个排他X锁。
+
+> 数据库事务的隔离级别
+
+数据库提供了四种隔离级别。
+
+- 读未提交数据(READ UNCOMMITTED)
+
+允许事务读取未被其他事务提交的变更,可能有脏读、不可重复读和幻读的问题。
+
+例如,某个时刻会话a修改了一个数据,但是未提交,此时,会话b读取了该数据,此时,a回滚了事务,这就会出现a、b数据不一致,这就是**脏读**。
+
+- 读已提交数据(READ COMMITTED)
+
+允许事务读取已经被其他事务提交的变更,可能不可重复读和幻读的问题。
+
+例如,某个时刻会话a修改了一个数据,提交了,此时的结果是10,此时b对该数据进行了修改为20,并提交了,此时会话a再次读取该数据,发现结果是20了,因此,在同一事务中,出现了两次读取的结果不一致的现象,这就是**不可重复读**。
+
+- 可重复读(REPEATABLE READ,默认隔离级别)
+
+可重复读,从字面上的意思也能明白,就是在同一事务中读取多次,确保每次读取到的数据都是一样的,可以避免脏读和不可重复读,但是可能会出现幻读。
+
+- 可串行化(SERIALIZABLE)
+
+可串行化是指所有的事务都是一个接一个执行,可以避免所有的问题,但是,效果太低。
+
+最后,再用一张图来总结一下。
+
+
+
+### MVCC 实现原理
+
+在理解 MVCC 的实现原理之前,需要先带大家了解一下 **版本链**。
+
+我们都知道,在 InnoDB 每个事务都有一个唯一的事务 ID(transaction id),该 ID 是在启动一个事务时申请的并且严格顺序递增。
+
+另外,数据表中的每行数据都是有多个版本的,每次事务更新都会生成新的版本,并且把本次事务的 transaction id 赋值给这个数据版本的事务 ID(row trx_id)。
+
+除此之外,还有一个 roll_pointer指针,该指针 ROLL_PTR 把一个数据行的所有快照版本记录连接起来。
+
+undo log 的回滚机制也是依靠这个版本链,每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个roll_pointer属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表,所以现在的情况就像下图一样:
+
+
+
+有了上面的知识储备,所谓的 MVCC(Multi-Version Concurrency Control ,多版本并发控制)指的就是在使用**读已提交(READ COMMITTD)**、**可重复读(REPEATABLE READ)** 这两种隔离级别的事务在执行普通的 SELECT 操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。
+
+这两个隔离级别的一个很大不同就是:生成 ReadView 的时机不同,READ COMMITTD 在每一次进行普通 SELECT 操作前都会生成一个 ReadView,而 REPEATABLE READ 只在第一次进行普通 SELECT 操作前生成一个ReadView,数据的可重复读其实就是 ReadView 的重复使用。
+
+#### **ReadView**
+
+MVCC 维护了一个 ReadView 结构,主要包含了当前系统未提交的事务列表 TRX_IDs {TRX_ID_1, TRX_ID_2, ...},还有该列表的最小值 TRX_ID_MIN 和 TRX_ID_MAX。
+
+在进行 SELECT 操作时,根据数据行快照的 TRX_ID 与 TRX_ID_MIN 和 TRX_ID_MAX 之间的关系,从而判断数据行快照是否可以使用:
+
+- TRX_ID < TRX_ID_MIN,表示该数据行快照时在当前所有未提交事务之前进行更改的,因此可以使用。
+- TRX_ID > TRX_ID_MAX,表示该数据行快照是在事务启动之后被更改的,因此不可使用。
+- TRX_ID_MIN <= TRX_ID <= TRX_ID_MAX,需要根据隔离级别再进行判断:
+ - 提交读:如果 TRX_ID 在 TRX_IDs 列表中,表示该数据行快照对应的事务还未提交,则该快照不可使用。否则表示已经提交,可以使用。
+ - 可重复读:都不可以使用。因为如果可以使用的话,那么其它事务也可以读到这个数据行快照并进行修改,那么当前事务再去读这个数据行得到的值就会发生改变,也就是出现了不可重复读问题。
+
+在数据行快照不可使用的情况下,需要沿着 Undo Log 的回滚指针 ROLL_PTR 找到下一个快照,再进行上面的判断。
+### 锁相关
+
+> 数据库中锁机制,说说数据库中锁的类型
+
+对于MySQL来说,锁是一个很重要的特性,数据库的锁是为了支持对共享资源进行并发访问,提供数据的完整性和一致性,这样才能保证在高并发的情况下,访问数据库的时候,数据不会出现问题。
+
+在数据库中,lock和latch都可以称为锁,但是意义却不同。
+
+**Latch**一般称为`闩锁`(轻量级的锁),因为其要求锁定的时间必须非常短。若持续的时间长,则应用的性能会非常差,在InnoDB引擎中,Latch又可以分为`mutex`(互斥量)和`rwlock`(读写锁)。其目的是用来保证并发线程操作临界资源的正确性,并且通常没有死锁检测的机制。
+
+**Lock**的对象是`事务`,用来锁定的是数据库中的对象,如表、页、行。并且一般lock的对象仅在事务commit或rollback后进行释放(不同事务隔离级别释放的时间可能不同)。
+
+InnoDB存储引擎中存在着不同类型的锁,下面一一介绍一下。
+
+**S or X (共享锁、排他锁)**
+
+数据的操作其实只有两种,也就是读和写,而数据库在实现锁时,也会对这两种操作使用不同的锁;InnoDB 实现了标准的**行级锁**,也就是**共享锁(Shared Lock)和互斥锁(Exclusive Lock)**。
+- 共享锁(读锁)(S Lock),允许事务读一行数据。
+- 排他锁(写锁)(X Lock),允许事务删除或更新一行数据。
+
+**IS or IX (共享、排他)意向锁**
+
+为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB存储引擎支持一种额外的锁方式,就称为**意向锁**,意向锁在 InnoDB 中是**表级锁**,意向锁分为:
+
+- 意向共享锁:表达一个事务想要获取一张表中某几行的共享锁。
+- 意向排他锁:表达一个事务想要获取一张表中某几行的排他锁。
+
+在存在行级锁和表级锁的情况下,事务 T 想要对表 A 加 X 锁,就需要先检测是否有其它事务对表 A 或者表 A 中的任意一行加了锁,那么就需要对表 A 的每一行都检测一次,这是非常耗时的。
+
+意向锁在原来的 X/S 锁之上引入了 IX/IS,IX/IS 都是表锁,用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。有以下两个规定:
+
+- 一个事务在获得某个数据行对象的 S 锁之前,必须先获得表的 IS 锁或者更强的锁;
+- 一个事务在获得某个数据行对象的 X 锁之前,必须先获得表的 IX 锁。
+
+通过引入意向锁,事务 T 想要对表 A 加 X 锁,只需要先检测是否有其它事务对表 A 加了 X/IX/S/IS 锁,如果加了就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务 T 加 X 锁失败。
+
+另外,这些锁之间的并不是一定可以共存的,有些锁之间是不兼容的,所谓**兼容性**就是指事务 A 获得一个某行某种锁之后,事务 B 同样的在这个行上尝试获取某种锁,如果能立即获取,则称锁兼容,反之叫冲突。
+
+下面我们再看一下这两种锁的兼容性。
+
+- S or X (共享锁、排他锁)的兼容性
+
+
+
+
+- IS or IX (共享、排他)意向锁的兼容性
+
+
+
+**注意:** 任意 IS/IX 锁之间都是兼容的,因为它们只表示想要对表加锁,而不是真正加锁。
+
+> MySQL中锁的粒度
+
+在数据库中,锁的粒度的不同可以分为表锁、页锁、行锁,这些锁的粒度之间也是会发生升级的,**锁升级**的意思就是讲当前锁的粒度降低,数据库可以把一个表的1000个行锁升级为一个页锁,或者将页锁升级为表锁,下面分别介绍一下这三种锁的粒度(参考自博客:https://blog.csdn.net/baolingye/article/details/102506072)。
+
+##### 表锁
+
+表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。
+
+当然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并大度大打折扣。
+
+使用表级锁定的主要是MyISAM,MEMORY,CSV等一些非事务性存储引擎。
+
+**特点:** 开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
+
+##### 页锁
+
+页级锁定是MySQL中比较独特的一种锁定级别,在其他数据库管理软件中也并不是太常见。页级锁定的特点是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。另外,页级锁定和行级锁定一样,会发生死锁。
+在数据库实现资源锁定的过程中,随着锁定资源颗粒度的减小,锁定相同数据量的数据所需要消耗的内存数量是越来越多的,实现算法也会越来越复杂。不过,随着锁定资源 颗粒度的减小,应用程序的访问请求遇到锁等待的可能性也会随之降低,系统整体并发度也随之提升。
+使用页级锁定的主要是BerkeleyDB存储引擎。
+
+**特点:** 开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
+
+##### 行锁
+
+行级锁定最大的特点就是锁定对象的粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。
+
+虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁。
+
+**特点:** 开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
+
+比较表锁我们可以发现,这两种锁的特点基本都是相反的,而从锁的角度来说,**表级锁**更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;而**行级锁**则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。
+
+##### MySQL 不同引擎支持的锁的粒度
+
+
+
+> 了解一致性非锁定读和一致性锁定读吗?
+
+#### 一致性锁定读(Locking Reads)
+
+在一个事务中查询数据时,普通的SELECT语句不会对查询的数据进行加锁,其他事务仍可以对查询的数据执行更新和删除操作。因此,InnoDB提供了两种类型的锁定读来保证额外的安全性:
+
+ - `SELECT ... LOCK IN SHARE MODE`
+ - `SELECT ... FOR UPDATE`
+
+ `SELECT ... LOCK IN SHARE MODE`: 对读取的行添加S锁,其他事物可以对这些行添加S锁,若添加X锁,则会被阻塞。
+
+ `SELECT ... FOR UPDATE`: 会对查询的行及相关联的索引记录加X锁,其他事务请求的S锁或X锁都会被阻塞。 当事务提交或回滚后,通过这两个语句添加的锁都会被释放。 注意:只有在自动提交被禁用时,SELECT FOR UPDATE才可以锁定行,若开启自动提交,则匹配的行不会被锁定。
+
+ #### 一致性非锁定读
+
+ **一致性非锁定读(consistent nonlocking read)** 是指InnoDB存储引擎通过多版本控制(MVVC)读取当前数据库中行数据的方式。如果读取的行正在执行DELETE或UPDATE操作,这时读取操作不会因此去等待行上锁的释放。相反地,InnoDB会去读取行的一个快照。所以,非锁定读机制大大提高了数据库的并发性。
+
+ 
+
+一致性非锁定读是InnoDB默认的读取方式,即读取不会占用和等待行上的锁。在事务隔离级别`READ COMMITTED`和`REPEATABLE READ`下,InnoDB使用一致性非锁定读。
+
+然而,对于快照数据的定义却不同。在`READ COMMITTED`事务隔离级别下,一致性非锁定读总是**读取被锁定行的最新一份快照数据**。而在`REPEATABLE READ`事务隔离级别下,则**读取事务开始时的行数据版本**。
+
+下面我们通过一个简单的例子来说明一下这两种方式的区别。
+
+首先创建一张表;
+
+
+
+插入一条数据;
+
+```
+insert into lock_test values(1);
+```
+
+查看隔离级别;
+
+```
+select @@tx_isolation;
+```
+
+
+
+下面分为两种事务进行操作。
+
+在`REPEATABLE READ`事务隔离级别下;
+
+
+
+在`REPEATABLE READ`事务隔离级别下,读取事务开始时的行数据,所以当会话B修改了数据之后,通过以前的查询,还是可以查询到数据的。
+
+在`READ COMMITTED`事务隔离级别下;
+
+
+
+在`READ COMMITTED`事务隔离级别下,读取该行版本最新的一个快照数据,所以,由于B会话修改了数据,并且提交了事务,所以,A读取不到数据了。
+
+> InnoDB存储引擎行锁的算法了解吗?
+
+InnoDB存储引擎有3种行锁的算法,其分别是:
+
+- Record Lock:单个行记录上的锁。
+- Gap Lock:间隙锁,锁定一个范围,但不包含记录本身。
+- Next-Key Lock:Gap Lock+Record Lock,锁定一个范围,并且锁定记录本身。
+
+**Record Lock**:总是会去锁住索引记录,如果InnoDB存储引擎表在建立的时候没有设置任何一个索引,那么这时InnoDB存储引擎会使用隐式的主键来进行锁定。
+
+**Next-Key Lock**:结合了Gap Lock和Record Lock的一种锁定算法,在Next-Key Lock算法下,InnoDB对于行的查询都是采用这种锁定算法。举个例子10,20,30,那么该索引可能被Next-Key Locking的区间为:
+
+
+除了Next-Key Locking,还有**Previous-Key Locking**技术,这种技术跟Next-Key Lock正好相反,锁定的区间是区间范围和前一个值。同样上述的值,使用Previous-Key Locking技术,那么可锁定的区间为:
+
+
+不是所有索引都会加上Next-key Lock的,这里有一种**特殊的情况**,在查询的列是唯一索引(包含主键索引)的情况下,`Next-key Lock`会降级为`Record Lock`。
+
+接下来,我们来通过一个例子解释一下。
+```java
+CREATE TABLE test (
+ x INT,
+ y INT,
+ PRIMARY KEY(x), // x是主键索引
+ KEY(y) // y是普通索引
+);
+INSERT INTO test select 3, 2;
+INSERT INTO test select 5, 3;
+INSERT INTO test select 7, 6;
+INSERT INTO test select 10, 8;
+```
+我们现在会话A中执行如下语句;
+```java
+SELECT * FROM test WHERE y = 3 FOR UPDATE
+```
+
+我们分析一下这时候的加锁情况。
+
+- 对于主键x
+
+
+
+
+- 辅助索引y
+
+
+
+
+用户可以通过以下两种方式来显示的关闭Gap Lock:
+
+- 将事务的隔离级别设为 READ COMMITED。
+- 将参数innodb_locks_unsafe_for_binlog设置为1。
+
+**Gap Lock的作用**:是为了阻止多个事务将记录插入到同一个范围内,设计它的目的是用来解决**Phontom Problem(幻读问题)**。在MySQL默认的隔离级别(Repeatable Read)下,InnoDB就是使用它来解决幻读问题。
+
+>**幻读**:是指在同一事务下,连续执行两次同样的SQL语句可能导致不同的结果,第二次的SQL可能会返回之前不存在的行,也就是第一次执行和第二次执行期间有其他事务往里插入了新的行。
+
+> 说说悲观锁和乐观锁
+
+##### 悲观锁
+
+悲观锁是指在数据处理过程中,从一开始就使数据处于锁定状态,知道更改完成才释放。
+
+MySQL中悲观锁使用以下方式:`select...for update`
+
+例如:
+```sql
+select name from item where id = 200 for update;
+insert into orders(id,item_id) values(null,100)
+update item set count = count - 1 where id = 100;
+```
+我们使用`select name from item where id = 200 for update;`对id为200的数据进行了锁定,其他要对该条数据进行修改,必须等到该事务提交之后,否则无法修改。这样就保证了并发的安全性。
+
+需要注意的是:`select...for update`语句必须在事务中使用。
+
+悲观锁虽然能够解决并发安全的问题,但是,这种锁定会导致性能降低,加锁时间过长,并发性不好,影响系统的整体性能。因此,这种方式在实际的开发中用的很少。
+
+##### 乐观锁
+
+乐观锁是相对悲观锁而言的,认为数据一般情况下不会出现冲突,所以在数据进行更新的时候,才会将数据锁定。
+
+**乐观锁的实现方式**
+
+- 使用数据版本(version)机制实现
+
+该方式是在每次更新数据的时候,都要对更新的数据进行version版本+1操作。
+
+其具体原理是:读取数据时,将此版本一同读出,之后,更新数据时,对此版本+1,每次提交数据时,如果提交的数据版本大于或等于数据库表中的版本,则可以更新,说明是最新数据,否则,不予更新,说明数据已经过期。
+
+- 使用时间戳实现
+
+该机制和version是类似的,也是需要再表中增加一个字段,类型使用时间类型即可。
+
+原理:在更新数据时,检查数据库中当前的时间戳和更新前取到的时间戳,如果对比一致,就予以更新,否则不更新。
+
+##### 使用场景分析
+
+悲观锁可以在并发量不大的情况下使用,并发量大的情况下,使用乐观锁,大多数情况下都建议使用乐观锁。
+
+### 索引相关
+
+> 索引有哪些类型?
+
+索引有很多中类型:普通索引、唯一索引、主键索引、组合索引、全文索引,下面我们看看如何创建和删除下面这些类型的索引。
+
+- 唯一索引:是在表上一个或者多个字段组合建立的索引,这些字段组合的值在表中不可重复。
+- 非唯一索引:是在表上一个或者多个字段组合建立的索引,这些字段组合的值在表中可重复。
+- 主键索引:是唯一索引的特定类型。表中创建主键时,会自动创建主键索引且只有一个。
+- 组合索引:基于多个字段而创建的索引。
+
+下面再看看索引的创建和删除的方法。
+
+#### 索引的创建方式
+
+索引的创建是可以在很多种情况下进行的。
+
+- 直接创建索引
+
+```
+CREATE [UNIQUE|FULLLTEXT] INDEX index_name ON table_name(column_name(length))
+```
+`[UNIQUE|FULLLTEXT]`:表示可选择的索引类型,唯一索引还是全文索引,不加话就是普通索引。
+`table_name`:表的名称,表示为哪个表添加索引。
+`column_name(length)`:column_name是表的列名,length表示为这一列的前length行记录添加索引。
+
+- 修改表结构的方式添加索引
+
+```
+ALTER TABLE table_name ADD [UNIQUE|FULLLTEXT] INDEX index_name (column(length))
+```
+
+- 创建表的时候同时创建索引
+
+```
+CREATE TABLE `table` (
+ `id` int(11) NOT NULL AUTO_INCREMENT ,
+ `title` char(255) CHARACTER NOT NULL ,
+ PRIMARY KEY (`id`),
+ [UNIQUE|FULLLTEXT] INDEX index_name (title(length))
+)
+```
+
+#### 主键索引和组合索引创建的方式
+
+前面讲的都是**普通索引、唯一索引和全文索引**创建的方式,但是,**主键索引和组合索引**创建的方式却是有点不一样的,所以单独拿出来讲一下。
+
+**组合索引创建方式**
+
+- 创建表的时候同时创建索引
+
+
+```
+CREATE TABLE `table` (
+ `id` int(11) NOT NULL AUTO_INCREMENT ,
+ `title` char(255) CHARACTER NOT NULL ,
+ PRIMARY KEY (`id`),
+ INDEX index_name(id,title)
+)
+```
+
+- 修改表结构的方式添加索引
+
+```
+ALTER TABLE table_name ADD INDEX name_city_age (name,city,age);
+```
+
+**主键索引创建方式**
+主键索引是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引。
+
+```
+CREATE TABLE `table` (
+ `id` int(11) NOT NULL AUTO_INCREMENT ,
+ `title` char(255) CHARACTER NOT NULL ,
+ PRIMARY KEY (`id`)
+)
+```
+
+#### 删除索引
+
+删除索引可利用`ALTER TABLE`或`DROP INDEX`语句来删除索引。类似于`CREATE INDEX`语句,`DROP INDEX`可以在`ALTER TABLE`内部作为一条语句处理,语法如下。
+
+(1)`DROP INDEX index_name ON talbe_name`
+(2)`ALTER TABLE table_name DROP INDEX index_name`
+(3)`ALTER TABLE table_name DROP PRIMARY KEY`
+
+第3条语句只在删除`PRIMARY KEY`索引时使用,因为一个表只可能有一个`PRIMARY KEY`索引,因此不需要指定索引名。
+
+> 数据库索引的实现原理
+
+在讲解本题之前,建议大家先了解一下B+树的原理,这对后面的讲解的理解有很大的帮助,大家可以阅读一些这篇文章:[面试官问你B树和B+树,就把这篇文章丢给他](https://www.java1000.com/mian-shi-guan-wen-ni-b-shu-he-b-shu-jiu-ba-zhe-pian-wen-zhang-diu-gei-ta.html)
+
+基于MySQL数据库的引擎不同,索引的实现原理也是不相同的,这里主要以最主流的InnoDB和MyISAM搜索引擎举例,来说明索引的实现原理。
+
+##### MyISAM索引实现原理
+
+首先,我们需要明白一点,MyISAM 引擎的整理结构是采用主键索引和辅助索引构成的。MyISAM 引擎使用 B+ 树作为索引结构,叶节点的 data 域存放的是数据记录的地址。如下图所示。
+
+
+
+由上图可知,MyISAM 引擎的叶子节点存放的是**数据记录的地址**。
+
+接下来,再来看一下辅助索引。
+
+
+
+在 MyISAM 中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是**主索引要求 key 是唯一的,而辅助索引的 key 可以重复。**
+
+辅助索引也是一颗B+树,data域保存数据记录的地址。
+
+**基于MyISAM引擎的索引检索算法:** 按照B+树算法搜索索引,假若指定搜索的key存在,则可以直接取出值,然后以data域的值为地址,使用地址获取对应的数据记录。
+
+##### InnoDB索引实现原理
+
+基于MyISAM引擎实现的索引原理与基于InnoDB实现的索引原理总是分不开的,可以说是一对欢喜冤家,而两者之间最大的区别就在于,**InnoDB的数据文件本身就是索引文件**,怎么理解这句话呢?其实是相对于MyISAM引擎实现的索引而言的。从上分析我们可以知道,**MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址**,而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录,而保存这个索引的key就是数据表的主键,因此InnoDB引擎表数据文件本身就是**主索引**。
+
+总结一下,意思就是说InnoDB实现的索引叶子节点保存的是数据记录,而MyISAM引擎实现的索引的叶子节点保存的是数据记录的地址,还需要通过地址去索引对应的数据。
+
+
+
+另外,由于基于InnoDB实现的索引的数据文件本身要按主键聚集,因此,基于InnoDB实现的索引是必须有主键存在的。
+
+**其主键策略**:如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键;如果没有找到上面符合条件的列,则会生成6个字节的bigint unsigned值作为其主键。
+
+**考点:尽量在InnoDB引擎上采用自增字段做表的主键**
+
+InnoDB引擎数据文件本身是一棵B+树,非自增的主键会造成在插入新记录时数据文件为了维持B+树的特性而频繁的分裂调整,十分低效,而使用自增字段作为主键则是一个很好的选择。如果不了解以上B+树的原理,建议阅读上面的B+树的文章。如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页。
+
+因此,采用在我们平时的开发当中,我们通常会采用自增主键,因为,MySQL的常用的版本中,默认的搜索引擎就是InnoDB,所以,采用自增主键,其实是可以保证比较好的效率的。
+
+
+**辅助索引**
+
+在上述MyISAM引擎索引的讲解中提到了辅助索引,MyISAM引擎中的主索引和辅助索引都是指向了同一数据记录的,而在InnoDB引擎中的表现却不一样。
+
+**InnoDB的辅助索引data域存储相应记录主键索引的值而不是地址**。搜索辅助索引需要先根据辅助索引获取到主键值,再根据主键到主索引中获取到对应的数据记录。
+
+
+
+> 谈谈聚簇索引和非聚簇索引
+
+其实,在上面对索引实现原理的分析当中,已经对这两个概念有了很好的讲解了,只是没有明显的指出而已。
+
+InnoDB引擎采用的是**聚簇索引**,而MyISAM引擎采用的是**非聚簇索引**。这两个概念的区别就在于**叶节点是否存放一整行记录**,我们都知道,InnoDB引擎叶子节点存放的是数据记录,而MyISAM引擎的叶子节点存放的是数据记录的地址,所以说,只要理解了基于InnoDB引擎和基于MyISAM引擎实现的索引原理,就理解了以上这两个概念。这么说是不是就很容易理解的,不就是对应了两种不同的引擎吗,是不是很简单。
+
+如果还不是很理解,再放一张图。
+
+
+
+左边的是聚集索引(聚簇索引),右边的是非聚集索引(非聚簇索引),这两个是不是就是**基于InnoDB引擎和基于MyISAM引擎实现的索引原理**。
+
+**聚簇索引的优势**
+
+- 当你需要取出一定范围内的数据时,用聚簇索引也比用非聚簇索引好。
+- 当通过聚簇索引查找目标数据时理论上比非聚簇索引要快,原因在于非聚簇索引叶子节点存放的是数据记录的地址,索引定位到对应主键时还要多一次目标记录寻址,即多一次I/O。
+- 采用覆盖索引扫描的查询可以直接使用页节点中的主键值。
+
+**聚簇索引的不足**
+
+- 插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。
+- 更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新。
+- 二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。二级索引的叶节点存储的是主键值,而不是行指针(非聚簇索引存储的是指针或者说是地址),这是为了减少当出现行移动或数据页分裂时二级索引的维护工作,但会让二级索引占用更多的空间。
+- 采用聚簇索引插入新值比采用非聚簇索引插入新值的速度要慢很多,因为插入要保证主键不能重复,判断主键不能重复,采用的方式在不同的索引下面会有很大的性能差距,聚簇索引遍历所有的叶子节点,非聚簇索引也判断所有的叶子节点,但是聚簇索引的叶子节点除了带有主键还有记录值,记录的大小往往比主键要大的多。这样就会导致聚簇索引在判定新记录携带的主键是否重复时进行昂贵的I/O代价。
+
+最后,说明一下,如果你很好的理解了索引的原理,这上面的就会很好理解,如果理解不到位,就会发现这都是什么东西?因此,看这个的时候,先看一下上面那一题的解答。
+
+> 覆盖索引
+
+覆盖索引(covering index)指一个查询语句的执行只用从索引中就能够取得,不必从数据表中读取。也可以称之为实现了索引覆盖。
+
+举个例子
+
+```sql
+select * from user where name = "sihai";
+```
+以上语句查询是会从数据表中进行查询的,因为,没有对user表中的name字段增加索引的操作。
+
+```sql
+alter table user add index name_index(name);
+```
+我们对user表中的name字段添加了索引。我们再使用sql语句`select * from user where name = "sihai";`进行查询是,就会使用覆盖索引。
+
+因此,我们就可以非常清楚的明白,覆盖索引就是如果发现可以走索引的方式得到数据,就不采用回表查询的操作了,从而提高了查询的效率。
+
+另外,从上面的索引原理的介绍也可以得到另外一个结论:使用覆盖索引InnoDB引擎比MyISAM引擎效果更佳,原因在于InnoDB采用聚集索引,如果二级索引中包含查询所需的数据,就不再需要在聚集索引中查找了。
+
+最后一点,想要使用覆盖索引,就必须要使得查询能够用到索引,因此,也需要注意索引失效的场景。
+
+> 建立索引的原则
+
+- 最左前缀匹配原则,MySQL会遇到范围查询停止匹配,所以会导致组合索引失效。
+- 索引列不进行函数运算。
+- 注意一个表建立索引的数量,不是索引建的越多越好,维护索引也会有很大的开销。
+- 尽量选择区分度高的字段作为索引。
+- where语句中,经常使用的字段应该考虑建立索引。
+- 分组和排序语句中,经常使用的字段应该考虑建立索引。
+- 两个表关联字段考虑建立索引。
+- like模糊查询中,只有右模糊查询才会使用索引。
+- 在varchar 字段上建立索引时,必须指定索引长度。
+- 禁止建立超过3个字段的联合索引。
+- 尽量采用覆盖索引,避免回表查询。
+- 索引优化的目标:至少要达到range级别,要求是ref级别,如果可以是consts最好。
+
+> 索引失效的情况
+
+- 使用组合索引,没有满足最左匹配原则,导致失效。
+- or语句所有字段必须都有索引,否则失效。
+- like以%开头,索引失效。
+- 需要类型转换。
+- where中索引列有运算。
+- where中索引列使用了函数。
+- 如果mysql觉得全表扫描更快时(数据少)。
+
+### 其他问题
+
+> 一张有自增id的表,当数据记录到了20之后,删除了第18,19,20条记录,再把MySQL重启,再插入一条记录,这条记录的id是21还是18呢?
+
+当表的引擎采用MyISAM时,是21,当表的引擎采用InnoDB时是18。
+
+MyISAM引擎会把表自增主键的最大id记录到数据文件中,做了持久化,重启后也不会消失,而InnoDB是将自增主键的最大id记录在内存中,重启后,会丢失。
+
+> 关系型数据库和非关系型数据库的区别
+
+非关系型数据库的优势在于性能和可扩展性,非关系型数据库一般都是基于键值对的,当然也支持文档形式、图片形式等等,文档形式、图片形式等等,使用灵活,应用场景广泛,同时,也是基于内存进行相关的操作,同时,底层也有较好的数据结构的支持,保证了其操作的效率,性能较高;另外,同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。成本低:非关系型数据库部署简单,基本都是开源软件。
+
+
+关系型数据库的优势在于支持更加复杂的sql操作、事务支持和使用表结构更加易于维护。
+
+> binlog、redo log和undo log
+
+redo log(重做日志)是InnoDB存储引擎独有的,它让MySQL拥有了崩溃恢复能力。
+
+比如 MySQL 实例挂了或宕机了,重启时,InnoDB存储引擎会使用redo log恢复数据,保证数据的**持久性与完整性**。
+
+redo log 记录的是数据的物理变化。
+
+
+binlog 记录了数据库表结构和表数据变更,比如update/delete/insert/truncate/create。它不会记录select(因为这没有对表没有进行变更),存储着每条变更的SQL语句(当然从下面的图看来看,不止SQL,还有XID「事务Id」等等)。
+
+主要有两个作用:**复制和恢复数据**
+
+- MySQL在公司使用的时候往往都是一主多从结构的,从服务器需要与主服务器的数据保持一致,这就是通过binlog来实现的
+
+- 数据库的数据被干掉了,我们可以通过binlog来对数据进行恢复。
+
+undo log主要有两个作用:**回滚和多版本控制(MVCC)**
+
+在数据修改的时候,不仅记录了redo log,还记录undo log,如果因为某些原因导致事务失败或回滚了,可以用undo log进行回滚
+
+undo log 主要存储的也是**逻辑日志**,比如我们要insert一条数据了,那undo log会记录的一条对应的delete日志。我们要update一条记录时,它会记录一条对应相反的update记录。
+
+这也应该容易理解,毕竟回滚嘛,跟需要修改的操作相反就好,这样就能达到回滚的目的。因为支持回滚操作,所以我们就能保证**原子性**。
+
+> mysql 优化思路
+
+https://mp.weixin.qq.com/s/jtuLb8uAIHJNvNpwcIZfpA
+
+https://www.cnblogs.com/jay-huaxiao/p/12995510.html
+
+> mysql 语法和复杂语句练习题
+
+- 常用语法
+
+https://www.jb51.net/article/156898.htm
+www.cyc2018.xyz/算法/基础/算法 - 排序.html
+
+- 练习题
+
+https://www.jianshu.com/p/476b52ee4f1b
+www.cyc2018.xyz/算法/基础/算法 - 排序.html
diff --git "a/docs/database/\346\225\260\346\215\256\345\272\223\344\274\230\345\214\226.md" "b/docs/database/\346\225\260\346\215\256\345\272\223\344\274\230\345\214\226.md"
new file mode 100644
index 0000000..32cea00
--- /dev/null
+++ "b/docs/database/\346\225\260\346\215\256\345\272\223\344\274\230\345\214\226.md"
@@ -0,0 +1,12 @@
+## 数据库优化
+
+### 为什么要做优化
+
+
+### 从哪几个方面考虑优化
+
+
+### MySQL语句优化
+
+
+### 其他
\ No newline at end of file
diff --git a/docs/essential-content-for-interview/BATJrealInterviewExperience/2019alipay-pinduoduo-toutiao.md b/docs/essential-content-for-interview/BATJrealInterviewExperience/2019alipay-pinduoduo-toutiao.md
deleted file mode 100644
index 183a185..0000000
--- a/docs/essential-content-for-interview/BATJrealInterviewExperience/2019alipay-pinduoduo-toutiao.md
+++ /dev/null
@@ -1,294 +0,0 @@
-作者: rhwayfun,原文地址:https://mp.weixin.qq.com/s/msYty4vjjC0PvrwasRH5Bw ,JavaGuide 已经获得作者授权并对原文进行了重新排版。
-
-
-- [写在2019年后的蚂蚁、头条、拼多多的面试总结](#写在2019年后的蚂蚁头条拼多多的面试总结)
- - [准备过程](#准备过程)
- - [蚂蚁金服](#蚂蚁金服)
- - [一面](#一面)
- - [二面](#二面)
- - [三面](#三面)
- - [四面](#四面)
- - [五面](#五面)
- - [小结](#小结)
- - [拼多多](#拼多多)
- - [面试前](#面试前)
- - [一面](#一面-1)
- - [二面](#二面-1)
- - [三面](#三面-1)
- - [小结](#小结-1)
- - [字节跳动](#字节跳动)
- - [面试前](#面试前-1)
- - [一面](#一面-2)
- - [二面](#二面-2)
- - [小结](#小结-2)
- - [总结](#总结)
-
-
-
-# 2019年蚂蚁金服、头条、拼多多的面试总结
-
-文章有点长,请耐心看完,绝对有收获!不想听我BB直接进入面试分享:
-
-- 准备过程
-- 蚂蚁金服面试分享
-- 拼多多面试分享
-- 字节跳动面试分享
-- 总结
-
-说起来开始进行面试是年前倒数第二周,上午9点,我还在去公司的公交上,突然收到蚂蚁的面试电话,其实算不上真正的面试。面试官只是和我聊了下他们在做的事情(主要是做双十一这里大促的稳定性保障,偏中间件吧),说的很详细,然后和我沟通了下是否有兴趣,我表示有兴趣,后面就收到正式面试的通知,最后没选择去蚂蚁表示抱歉。
-
-当时我自己也准备出去看看机会,顺便看看自己的实力。当时我其实挺纠结的,一方面现在部门也正需要我,还是可以有一番作为的,另一方面觉得近一年来进步缓慢,没有以前飞速进步的成就感了,而且业务和技术偏于稳定,加上自己也属于那种比较懒散的人,骨子里还是希望能够突破现状,持续在技术上有所精进。
-
-在开始正式的总结之前,还是希望各位同仁能否听我继续发泄一会,抱拳!
-
-我翻开自己2018年初立的flag,觉得甚是惭愧。其中就有一条是保持一周写一篇博客,奈何中间因为各种原因没能坚持下去。细细想来,主要是自己没能真正静下来心认真投入到技术的研究和学习,那么为什么会这样?说白了还是因为没有确定目标或者目标不明确,没有目标或者目标不明确都可能导致行动的失败。
-
-那么问题来了,目标是啥?就我而言,短期目标是深入研究某一项技术,比如最近在研究mysql,那么深入研究一定要动手实践并且有所产出,这就够了么?还需要我们能够举一反三,结合实际开发场景想一想日常开发要注意什么,这中间有没有什么坑?可以看出,要进步真的不是一件简单的事,这种反人类的行为需要我们克服自我的弱点,逐渐形成习惯。真正牛逼的人,从不觉得认真学习是一件多么难的事,因为这已经形成了他的习惯,就喝早上起床刷牙洗脸那么自然简单。
-
-扯了那么多,开始进入正题,先后进行了蚂蚁、拼多多和字节跳动的面试。
-
-## 准备过程
-
-先说说我自己的情况,我2016先在蚂蚁实习了将近三个月,然后去了我现在的老东家,2.5年工作经验,可以说毕业后就一直老老实实在老东家打怪升级,虽说有蚂蚁的实习经历,但是因为时间太短,还是有点虚的。所以面试官看到我简历第一个问题绝对是这样的。
-
-“哇,你在蚂蚁待过,不错啊”,面试官笑嘻嘻地问到。“是的,还好”,我说。“为啥才三个月?”,面试官脸色一沉问到。“哗啦啦解释一通。。。”,我解释道。“哦,原来如此,那我们开始面试吧”,面试官一本正经说到。
-
-尼玛,早知道不写蚂蚁的实习经历了,后面仔细一想,当初写上蚂蚁不就给简历加点料嘛。
-
-言归正传,准备过程其实很早开始了(当然这不是说我工作时老想着跳槽,因为我明白现在的老东家并不是终点,我还需要不断提升),具体可追溯到从蚂蚁离职的时候,当时出来也面了很多公司,没啥大公司,面了大概5家公司,都拿到offer了。
-
-工作之余常常会去额外研究自己感兴趣的技术以及工作用到的技术,力求把原理搞明白,并且会自己实践一把。此外,买了N多书,基本有时间就会去看,补补基础,什么操作系统、数据结构与算法、MySQL、JDK之类的源码,基本都好好温习了(文末会列一下自己看过的书和一些好的资料)。**我深知基础就像“木桶效应”的短板,决定了能装多少水。**
-
-此外,在正式决定看机会之前,我给自己列了一个提纲,主要包括Java要掌握的核心要点,有不懂的就查资料搞懂。我给自己定位还是Java工程师,所以Java体系是一定要做到心中有数的,很多东西没有常年的积累面试的时候很容易露馅,学习要对得起自己,不要骗人。
-
-剩下的就是找平台和内推了,除了蚂蚁,头条和拼多多都是找人内推的,感谢蚂蚁面试官对我的欣赏,以后说不定会去蚂蚁咯😄。
-
-平台:脉脉、GitHub、v2
-
-## 蚂蚁金服
-
-
-
-- 一面
-- 二面
-- 三面
-- 四面
-- 五面
-- 小结
-
-### 一面
-
-一面就做了一道算法题,要求两小时内完成,给了长度为N的有重复元素的数组,要求输出第10大的数。典型的TopK问题,快排算法搞定。
-
-算法题要注意的是合法性校验、边界条件以及异常的处理。另外,如果要写测试用例,一定要保证测试覆盖场景尽可能全。加上平时刷刷算法题,这种考核应该没问题的。
-
-### 二面
-
-- 自我介绍下呗
-- 开源项目贡献过代码么?(Dubbo提过一个打印accesslog的bug算么)
-- 目前在部门做什么,业务简单介绍下,内部有哪些系统,作用和交互过程说下
-- Dubbo踩过哪些坑,分别是怎么解决的?(说了异常处理时业务异常捕获的问题,自定义了一个异常拦截器)
-- 开始进入正题,说下你对线程安全的理解(多线程访问同一个对象,如果不需要考虑额外的同步,调用对象的行为就可以获得正确的结果就是线程安全)
-- 事务有哪些特性?(ACID)
-- 怎么理解原子性?(同一个事务下,多个操作要么成功要么失败,不存在部分成功或者部分失败的情况)
-- 乐观锁和悲观锁的区别?(悲观锁假定会发生冲突,访问的时候都要先获得锁,保证同一个时刻只有线程获得锁,读读也会阻塞;乐观锁假设不会发生冲突,只有在提交操作的时候检查是否有冲突)这两种锁在Java和MySQL分别是怎么实现的?(Java乐观锁通过CAS实现,悲观锁通过synchronize实现。mysql乐观锁通过MVCC,也就是版本实现,悲观锁可以通过select... for update加上排它锁)
-- HashMap为什么不是线程安全的?(多线程操作无并发控制,顺便说了在扩容的时候多线程访问时会造成死锁,会形成一个环,不过扩容时多线程操作形成环的问题再JDK1.8已经解决,但多线程下使用HashMap还会有一些其他问题比如数据丢失,所以多线程下不应该使用HashMap,而应该使用ConcurrentHashMap)怎么让HashMap变得线程安全?(Collections的synchronize方法包装一个线程安全的Map,或者直接用ConcurrentHashMap)两者的区别是什么?(前者直接在put和get方法加了synchronize同步,后者采用了分段锁以及CAS支持更高的并发)
-- jdk1.8对ConcurrentHashMap做了哪些优化?(插入的时候如果数组元素使用了红黑树,取消了分段锁设计,synchronize替代了Lock锁)为什么这样优化?(避免冲突严重时链表多长,提高查询效率,时间复杂度从O(N)提高到O(logN))
-- redis主从机制了解么?怎么实现的?
-- 有过GC调优的经历么?(有点虚,答得不是很好)
-- 有什么想问的么?
-
-### 三面
-
-- 简单自我介绍下
-- 监控系统怎么做的,分为哪些模块,模块之间怎么交互的?用的什么数据库?(MySQL)使用什么存储引擎,为什么使用InnnoDB?(支持事务、聚簇索引、MVCC)
-- 订单表有做拆分么,怎么拆的?(垂直拆分和水平拆分)
-- 水平拆分后查询过程描述下
-- 如果落到某个分片的数据很大怎么办?(按照某种规则,比如哈希取模、range,将单张表拆分为多张表)
-- 哈希取模会有什么问题么?(有的,数据分布不均,扩容缩容相对复杂 )
-- 分库分表后怎么解决读写压力?(一主多从、多主多从)
-- 拆分后主键怎么保证惟一?(UUID、Snowflake算法)
-- Snowflake生成的ID是全局递增唯一么?(不是,只是全局唯一,单机递增)
-- 怎么实现全局递增的唯一ID?(讲了TDDL的一次取一批ID,然后再本地慢慢分配的做法)
-- Mysql的索引结构说下(说了B+树,B+树可以对叶子结点顺序查找,因为叶子结点存放了数据结点且有序)
-- 主键索引和普通索引的区别(主键索引的叶子结点存放了整行记录,普通索引的叶子结点存放了主键ID,查询的时候需要做一次回表查询)一定要回表查询么?(不一定,当查询的字段刚好是索引的字段或者索引的一部分,就可以不用回表,这也是索引覆盖的原理)
-- 你们系统目前的瓶颈在哪里?
-- 你打算怎么优化?简要说下你的优化思路
-- 有什么想问我么?
-
-### 四面
-
-- 介绍下自己
-- 为什么要做逆向?
-- 怎么理解微服务?
-- 服务治理怎么实现的?(说了限流、压测、监控等模块的实现)
-- 这个不是中间件做的事么,为什么你们部门做?(当时没有单独的中间件团队,微服务刚搞不久,需要进行监控和性能优化)
-- 说说Spring的生命周期吧
-- 说说GC的过程(说了young gc和full gc的触发条件和回收过程以及对象创建的过程)
-- CMS GC有什么问题?(并发清除算法,浮动垃圾,短暂停顿)
-- 怎么避免产生浮动垃圾?(记得有个VM参数设置可以让扫描新生代之前进行一次young gc,但是因为gc是虚拟机自动调度的,所以不保证一定执行。但是还有参数可以让虚拟机强制执行一次young gc)
-- 强制young gc会有什么问题?(STW停顿时间变长)
-- 知道G1么?(了解一点 )
-- 回收过程是怎么样的?(young gc、并发阶段、混合阶段、full gc,说了Remember Set)
-- 你提到的Remember Set底层是怎么实现的?
-- 有什么想问的么?
-
-### 五面
-
-五面是HRBP面的,和我提前预约了时间,主要聊了之前在蚂蚁的实习经历、部门在做的事情、职业发展、福利待遇等。阿里面试官确实是具有一票否决权的,很看重你的价值观是否match,一般都比较喜欢皮实的候选人。HR面一定要诚实,不要说谎,只要你说谎HR都会去证实,直接cut了。
-
-- 之前蚂蚁实习三个月怎么不留下来?
-- 实习的时候主管是谁?
-- 实习做了哪些事情?(尼玛这种也问?)
-- 你对技术怎么看?平时使用什么技术栈?(阿里HR真的是既当爹又当妈,😂)
-- 最近有在研究什么东西么
-- 你对SRE怎么看
-- 对待遇有什么预期么
-
-最后HR还对我说目前稳定性保障部挺缺人的,希望我尽快回复。
-
-### 小结
-
-蚂蚁面试比较重视基础,所以Java那些基本功一定要扎实。蚂蚁的工作环境还是挺赞的,因为我面的是稳定性保障部门,还有许多单独的小组,什么三年1班,很有青春的感觉。面试官基本水平都比较高,基本都P7以上,除了基础还问了不少架构设计方面的问题,收获还是挺大的。
-
-## 拼多多
-
-
-
-- 面试前
-- 一面
-- 二面
-- 三面
-- 小结
-
-### 面试前
-
-面完蚂蚁后,早就听闻拼多多这个独角兽,决定也去面一把。首先我在脉脉找了一个拼多多的HR,加了微信聊了下,发了简历便开始我的拼多多面试之旅。这里要非常感谢拼多多HR小姐姐,从面试内推到offer确认一直都在帮我,人真的很nice。
-
-### 一面
-
-- 为啥蚂蚁只待了三个月?没转正?(转正了,解释了一通。。。)
-- Java中的HashMap、TreeMap解释下?(TreeMap红黑树,有序,HashMap无序,数组+链表)
-- TreeMap查询写入的时间复杂度多少?(O(logN))
-- HashMap多线程有什么问题?(线程安全,死锁)怎么解决?( jdk1.8用了synchronize + CAS,扩容的时候通过CAS检查是否有修改,是则重试)重试会有什么问题么?(CAS(Compare And Swap)是比较和交换,不会导致线程阻塞,但是因为重试是通过自旋实现的,所以仍然会占用CPU时间,还有ABA的问题)怎么解决?(超时,限定自旋的次数,ABA可以通过原理变量AtomicStampedReference解决,原理利用版本号进行比较)超过重试次数如果仍然失败怎么办?(synchronize互斥锁)
-- CAS和synchronize有什么区别?都用synchronize不行么?(CAS是乐观锁,不需要阻塞,硬件级别实现的原子性;synchronize会阻塞,JVM级别实现的原子性。使用场景不同,线程冲突严重时CAS会造成CPU压力过大,导致吞吐量下降,synchronize的原理是先自旋然后阻塞,线程冲突严重仍然有较高的吞吐量,因为线程都被阻塞了,不会占用CPU
-)
-- 如果要保证线程安全怎么办?(ConcurrentHashMap)
-- ConcurrentHashMap怎么实现线程安全的?(分段锁)
-- get需要加锁么,为什么?(不用,volatile关键字)
-- volatile的作用是什么?(保证内存可见性)
-- 底层怎么实现的?(说了主内存和工作内存,读写内存屏障,happen-before,并在纸上画了线程交互图)
-- 在多核CPU下,可见性怎么保证?(思考了一会,总线嗅探技术)
-- 聊项目,系统之间是怎么交互的?
-- 系统并发多少,怎么优化?
-- 给我一张纸,画了一个九方格,都填了数字,给一个M*N矩阵,从1开始逆时针打印这M*N个数,要求时间复杂度尽可能低(内心OS:之前貌似碰到过这题,最优解是怎么实现来着)思考中。。。
-- 可以先说下你的思路(想起来了,说了什么时候要变换方向的条件,向右、向下、向左、向上,依此循环)
-- 有什么想问我的?
-
-### 二面
-
-- 自我介绍下
-- 手上还有其他offer么?(拿了蚂蚁的offer)
-- 部门组织结构是怎样的?(这轮不是技术面么,不过还是老老实实说了)
-- 系统有哪些模块,每个模块用了哪些技术,数据怎么流转的?(面试官有点秃顶,一看级别就很高)给了我一张纸,我在上面简单画了下系统之间的流转情况
-- 链路追踪的信息是怎么传递的?(RpcContext的attachment,说了Span的结构:parentSpanId + curSpanId)
-- SpanId怎么保证唯一性?(UUID,说了下内部的定制改动)
-- RpcContext是在什么维度传递的?(线程)
-- Dubbo的远程调用怎么实现的?(讲了读取配置、拼装url、创建Invoker、服务导出、服务注册以及消费者通过动态代理、filter、获取Invoker列表、负载均衡等过程(哗啦啦讲了10多分钟),我可以喝口水么)
-- Spring的单例是怎么实现的?(单例注册表)
-- 为什么要单独实现一个服务治理框架?(说了下内部刚搞微服务不久,主要对服务进行一些监控和性能优化)
-- 谁主导的?内部还在使用么?
-- 逆向有想过怎么做成通用么?
-- 有什么想问的么?
-
-### 三面
-
-二面老大面完后就直接HR面了,主要问了些职业发展、是否有其他offer、以及入职意向等问题,顺便说了下公司的福利待遇等,都比较常规啦。不过要说的是手上有其他offer或者大厂经历会有一定加分。
-
-### 小结
-
-拼多多的面试流程就简单许多,毕竟是一个成立三年多的公司。面试难度中规中矩,只要基础扎实应该不是问题。但不得不说工作强度很大,开始面试前HR就提前和我确认能否接受这样强度的工作,想来的老铁还是要做好准备
-
-## 字节跳动
-
-
-
-- 面试前
-- 一面
-- 二面
-- 小结
-
-### 面试前
-
-头条的面试是三家里最专业的,每次面试前有专门的HR和你约时间,确定OK后再进行面试。每次都是通过视频面试,因为都是之前都是电话面或现场面,所以视频面试还是有点不自然。也有人觉得视频面试体验很赞,当然萝卜青菜各有所爱。最坑的二面的时候对方面试官的网络老是掉线,最后很冤枉的挂了(当然有一些点答得不好也是原因之一)。所以还是有点遗憾的。
-
-### 一面
-
-- 先自我介绍下
-- 聊项目,逆向系统是什么意思
-- 聊项目,逆向系统用了哪些技术
-- 线程池的线程数怎么确定?
-- 如果是IO操作为主怎么确定?
-- 如果计算型操作又怎么确定?
-- Redis熟悉么,了解哪些数据结构?(说了zset) zset底层怎么实现的?(跳表)
-- 跳表的查询过程是怎么样的,查询和插入的时间复杂度?(说了先从第一层查找,不满足就下沉到第二层找,因为每一层都是有序的,写入和插入的时间复杂度都是O(logN))
-- 红黑树了解么,时间复杂度?(说了是N叉平衡树,O(logN))
-- 既然两个数据结构时间复杂度都是O(logN),zset为什么不用红黑树(跳表实现简单,踩坑成本低,红黑树每次插入都要通过旋转以维持平衡,实现复杂)
-- 点了点头,说下Dubbo的原理?(说了服务注册与发布以及消费者调用的过程)踩过什么坑没有?(说了dubbo异常处理的和打印accesslog的问题)
-- CAS了解么?(说了CAS的实现)还了解其他同步机制么?(说了synchronize以及两者的区别,一个乐观锁,一个悲观锁)
-- 那我们做一道题吧,数组A,2*n个元素,n个奇数、n个偶数,设计一个算法,使得数组奇数下标位置放置的都是奇数,偶数下标位置放置的都是偶数
-- 先说下你的思路(从0下标开始遍历,如果是奇数下标判断该元素是否奇数,是则跳过,否则从该位置寻找下一个奇数)
-- 下一个奇数?怎么找?(有点懵逼,思考中。。)
-- 有思路么?(仍然是先遍历一次数组,并对下标进行判断,如果下标属性和该位置元素不匹配从当前下标的下一个遍历数组元素,然后替换)
-- 你这样时间复杂度有点高,如果要求O(N)要怎么做(思考一会,答道“定义两个指针,分别从下标0和1开始遍历,遇见奇数位是是偶数和偶数位是奇数就停下,交换内容”)
-- 时间差不多了,先到这吧。你有什么想问我的?
-
-### 二面
-
-- 面试官和蔼很多,你先介绍下自己吧
-- 你对服务治理怎么理解的?
-- 项目中的限流怎么实现的?(Guava ratelimiter,令牌桶算法)
-- 具体怎么实现的?(要点是固定速率且令牌数有限)
-- 如果突然很多线程同时请求令牌,有什么问题?(导致很多请求积压,线程阻塞)
-- 怎么解决呢?(可以把积压的请求放到消息队列,然后异步处理)
-- 如果不用消息队列怎么解决?(说了RateLimiter预消费的策略)
-- 分布式追踪的上下文是怎么存储和传递的?(ThreadLocal + spanId,当前节点的spanId作为下个节点的父spanId)
-- Dubbo的RpcContext是怎么传递的?(ThreadLocal)主线程的ThreadLocal怎么传递到线程池?(说了先在主线程通过ThreadLocal的get方法拿到上下文信息,在线程池创建新的ThreadLocal并把之前获取的上下文信息设置到ThreadLocal中。这里要注意的线程池创建的ThreadLocal要在finally中手动remove,不然会有内存泄漏的问题)
-- 你说的内存泄漏具体是怎么产生的?(说了ThreadLocal的结构,主要分两种场景:主线程仍然对ThreadLocal有引用和主线程不存在对ThreadLocal的引用。第一种场景因为主线程仍然在运行,所以还是有对ThreadLocal的引用,那么ThreadLocal变量的引用和value是不会被回收的。第二种场景虽然主线程不存在对ThreadLocal的引用,且该引用是弱引用,所以会在gc的时候被回收,但是对用的value不是弱引用,不会被内存回收,仍然会造成内存泄漏)
-- 线程池的线程是不是必须手动remove才可以回收value?(是的,因为线程池的核心线程是一直存在的,如果不清理,那么核心线程的threadLocals变量会一直持有ThreadLocal变量)
-- 那你说的内存泄漏是指主线程还是线程池?(主线程 )
-- 可是主线程不是都退出了,引用的对象不应该会主动回收么?(面试官和内存泄漏杠上了),沉默了一会。。。
-- 那你说下SpringMVC不同用户登录的信息怎么保证线程安全的?(刚才解释的有点懵逼,一下没反应过来,居然回答成锁了。大脑有点晕了,此时已经一个小时过去了,感觉情况不妙。。。)
-- 这个直接用ThreadLocal不就可以么,你见过SpringMVC有锁实现的代码么?(有点晕菜。。。)
-- 我们聊聊mysql吧,说下索引结构(说了B+树)
-- 为什么使用B+树?( 说了查询效率高,O(logN),可以充分利用磁盘预读的特性,多叉树,深度小,叶子结点有序且存储数据)
-- 什么是索引覆盖?(忘记了。。。 )
-- Java为什么要设计双亲委派模型?
-- 什么时候需要自定义类加载器?
-- 我们做一道题吧,手写一个对象池
-- 有什么想问我的么?(感觉我很多点都没答好,是不是挂了(结果真的是) )
-
-### 小结
-
-头条的面试确实很专业,每次面试官会提前给你发一个视频链接,然后准点开始面试,而且考察的点都比较全。
-
-面试官都有一个特点,会抓住一个值得深入的点或者你没说清楚的点深入下去直到你把这个点讲清楚,不然面试官会觉得你并没有真正理解。二面面试官给了我一点建议,研究技术的时候一定要去研究产生的背景,弄明白在什么场景解决什么特定的问题,其实很多技术内部都是相通的。很诚恳,还是很感谢这位面试官大大。
-
-## 总结
-
-从年前开始面试到头条面完大概一个多月的时间,真的有点身心俱疲的感觉。最后拿到了拼多多、蚂蚁的offer,还是蛮幸运的。头条的面试对我帮助很大,再次感谢面试官对我的诚恳建议,以及拼多多的HR对我的啰嗦的问题详细解答。
-
-这里要说的是面试前要做好两件事:简历和自我介绍,简历要好好回顾下自己做的一些项目,然后挑几个亮点项目。自我介绍基本每轮面试都有,所以最好提前自己练习下,想好要讲哪些东西,分别怎么讲。此外,简历提到的技术一定是自己深入研究过的,没有深入研究也最好找点资料预热下,不打无准备的仗。
-
-**这些年看过的书**:
-
-《Effective Java》、《现代操作系统》、《TCP/IP详解:卷一》、《代码整洁之道》、《重构》、《Java程序性能优化》、《Spring实战》、《Zookeeper》、《高性能MySQL》、《亿级网站架构核心技术》、《可伸缩服务架构》、《Java编程思想》
-
-说实话这些书很多只看了一部分,我通常会带着问题看书,不然看着看着就睡着了,简直是催眠良药😅。
-
-
-最后,附一张自己面试前准备的脑图:
-
-链接:https://pan.baidu.com/s/1o2l1tuRakBEP0InKEh4Hzw 密码:300d
-
-全文完。
diff --git "a/docs/essential-content-for-interview/BATJrealInterviewExperience/5\351\235\242\351\230\277\351\207\214,\347\273\210\350\216\267offer.md" "b/docs/essential-content-for-interview/BATJrealInterviewExperience/5\351\235\242\351\230\277\351\207\214,\347\273\210\350\216\267offer.md"
deleted file mode 100644
index 9efac14..0000000
--- "a/docs/essential-content-for-interview/BATJrealInterviewExperience/5\351\235\242\351\230\277\351\207\214,\347\273\210\350\216\267offer.md"
+++ /dev/null
@@ -1,96 +0,0 @@
-> 作者:ppxyn。本文来自读者投稿,同时也欢迎各位投稿,**对于不错的原创文章我根据你的选择给予现金(50-200)、付费专栏或者任选书籍进行奖励!所以,快提 pr 或者邮件的方式(邮件地址在主页)给我投稿吧!** 当然,我觉得奖励是次要的,最重要的是你可以从自己整理知识点的过程中学习到很多知识。
-
-**目录**
-
-
-
-- [前言](#前言)
-- [一面\(技术面\)](#一面技术面)
-- [二面\(技术面\)](#二面技术面)
-- [三面\(技术面\)](#三面技术面)
-- [四面\(半个技术面\)](#四面半个技术面)
-- [五面\(HR面\)](#五面hr面)
-- [总结](#总结)
-
-
-
-### 前言
-
-在接触 Java 之前我接触的比较多的是硬件方面,用的比较多的语言就是C和C++。到了大三我才正式选择 Java 方向,到目前为止使用Java到现在大概有一年多的时间,所以Java算不上很好。刚开始投递的时候,实习刚辞职,也没准备笔试面试,很多东西都忘记了。所以,刚开始我并没有直接就投递阿里,毕竟心里还是有一点点小害怕的。于是,我就先投递了几个不算大的公司来练手,就是想着刷刷经验而已或者说是练练手(ps:还是挺对不起那些公司的)。面了一个月其他公司后,我找了我实验室的学长内推我,后面就有了这5次面试。
-
-下面简单的说一下我的这5次面试:4次技术面+1次HR面,希望我的经历能对你有所帮助。
-
-### 一面(技术面)
-
-1. 自我介绍(主要讲自己会的技术细节,项目经验,经历那些就一语带过,后面面试官会问你的)。
-2. 聊聊项目(就是一个很普通的分布式商城,自己做了一些改进),让我画了整个项目的架构图,然后针对项目抛了一系列的提高性能的问题,还问了我做项目的过程中遇到了那些问题,如何解决的,差不读就这些吧。
-3. 可能是我前面说了我会数据库优化,然后面试官就开始问索引、事务隔离级别、悲观锁和乐观锁、索引、ACID、MVVC这些问题。
-4. 浏览器输入URL发生了什么? TCP和UDP区别? TCP如何保证传输可靠性?
-5. 讲下跳表怎么实现的?哈夫曼编码是怎么回事?非递归且不用额外空间(不用栈),如何遍历二叉树
-6. 后面又问了很多JVM方面的问题,比如Java内存模型、常见的垃圾回收器、双亲委派模型这些
-7. 你有什么问题要问吗?
-
-### 二面(技术面)
-
-1. 自我介绍(主要讲自己会的技术细节,项目经验,经历那些就一语带过,后面面试官会问你的)。
-2. 操作系统的内存管理机制
-3. 进程和线程的区别
-4. 说下你对线程安全的理解
-5. volatile 有什么作用 ,sychronized和lock有什么区别
-6. ReentrantLock实现原理
-7. 用过CountDownLatch么?什么场景下用的?
-8. AQS底层原理。
-9. 造成死锁的原因有哪些,如何预防?
-10. 加锁会带来哪些性能问题。如何解决?
-11. HashMap、ConcurrentHashMap源码。HashMap是线程安全的吗?Hashtable呢?ConcurrentHashMap有了解吗?
-12. 是否可以实习?
-13. 你有什么问题要问吗?
-
-### 三面(技术面)
-
-1. 有没有参加过 ACM 或者他竞赛,有没有拿过什么奖?( 我说我没参加过ACM,本科参加过数学建模竞赛,名次并不好,没拿过什么奖。面试官好像有点失望,然后我又赶紧补充说我和老师一起做过一个项目,目前已经投入使用。面试官还比较感兴趣,后面又和他聊了一下这个项目。)
-2. 研究生期间,做过什么项目,发过论文吗?有什么成果吗?
-3. 你觉得你有什么优点和缺点?你觉得你相比于那些比你更优秀的人欠缺什么?
-4. 有读过什么源码吗?(我说我读过 Java 集合框架和 Netty 的,面试官说 Java 集合前几面一定问的差不多,就不问了,然后就问我 Netty的,我当时很慌啊!)
-5. 介绍一下自己对 Netty 的认识,为什么要用。说说业务中,Netty 的使用场景。什么是TCP 粘包/拆包,解决办法。Netty线程模型。Dubbo 在使用 Netty 作为网络通讯时候是如何避免粘包与半包问题?讲讲Netty的零拷贝?巴拉巴拉问了好多,我记得有好几个我都没回答上来,心里想着凉凉了啊。
-6. 用到了那些开源技术、在开源领域做过贡献吗?
-7. 常见的排序算法及其复杂度,现场写了快排。
-8. 红黑树,B树的一些问题。
-9. 讲讲算法及数据结构在实习项目中的用处。
-10. 自己的未来规划(就简单描述了一下自己未来的设想啊,说的还挺诚恳,面试官好像还挺满意的)
-11. 你有什么问题要问吗?
-
-### 四面(半个技术面)
-
-三面面完当天,晚上9点接到面试电话,感觉像是部门或者项目主管。 这个和之前的面试不大相同,感觉面试官主要考察的是你解决问题的能力、学习能力和团队协作能力。
-
-1. 让我讲一个自己觉得最不错的项目。然后就巴拉巴拉的聊,我记得主要是问了项目是如何进行协作的、遇到问题是如何解决的、与他人发生冲突是如何解决的这些。感觉聊了挺久。
-2. 出现 OOM 后你会怎么排查问题?
-3. 自己平时是如何学习新技术的?除了 Java 还回去了解其他技术吗?
-4. 上一段实习经历的收获。
-5. NginX如何做负载均衡、常见的负载均衡算法有哪些、一致性哈希的一致性是什么意思、一致性哈希是如何做哈希的
-6. 你有什么问题问我吗?
-7. 还有一些其他的,想不起来了,感觉这一面不是偏向技术来问。
-
-## 五面(HR面)
-
-1. 自我介绍(主要讲能突出自己的经历,会的编程技术一语带过)。
-2. 你觉得你有什么优点和缺点?如何克服这些缺点?
-3. 说一件大学里你自己比较有成就感的一件事情,为此付出了那些努力。
-4. 你前面跟其他面试官讲过一些你做的项目吧?可以给我讲讲吗?你要考虑到我不是一个做技术的人,怎么让我也听得懂。项目中有什么问题,你怎么解决的?你最大的收获是什么?
-5. 你目前有面试过其他公司吗?如果让你选,这些公司和阿里,你选哪个?(送分题,回答不好可能送命)
-6. 你期望的工作地点是哪里?
-7. 你有什么问题吗?
-
-### 总结
-
-1. 可以看出面试官问我的很多问题都是比较常见的问题,所以记得一定要提前准备,还要深入准备,不要回答的太皮毛。很多时候一个问题可能会牵扯出很多问题,遇到不会的问题不要慌,冷静分析,如果你真的回答不上来,也不要担心自己是不是就要挂了,很可能这个问题本身就比较难。
-2. 表达能力和沟通能力太重要了,一定要提前练一下,我自身就是一个不太会说话的人,所以,面试前我对于自我介绍、项目介绍和一些常见问题都在脑子里练了好久,确保面试的时候能够很清晰和简洁的说出来。
-3. 等待面试的过程和面试的过程真的好熬人,那段时间我压力也比较大,好在我私下找到学长聊了很多,心情也好了很多。
-4. 面试之后及时总结,面的好的话,不要得意,尽快准备下一场面试吧!
-
-我觉得我还算是比较幸运的,最后也祝大家都能获得心仪的Offer。
-
-
-
-
diff --git "a/docs/essential-content-for-interview/BATJrealInterviewExperience/\350\232\202\350\232\201\351\207\221\346\234\215\345\256\236\344\271\240\347\224\237\351\235\242\347\273\217\346\200\273\347\273\223(\345\267\262\346\213\277\345\217\243\345\244\264offer).md" "b/docs/essential-content-for-interview/BATJrealInterviewExperience/\350\232\202\350\232\201\351\207\221\346\234\215\345\256\236\344\271\240\347\224\237\351\235\242\347\273\217\346\200\273\347\273\223(\345\267\262\346\213\277\345\217\243\345\244\264offer).md"
deleted file mode 100644
index 2e2df23..0000000
--- "a/docs/essential-content-for-interview/BATJrealInterviewExperience/\350\232\202\350\232\201\351\207\221\346\234\215\345\256\236\344\271\240\347\224\237\351\235\242\347\273\217\346\200\273\347\273\223(\345\267\262\346\213\277\345\217\243\345\244\264offer).md"
+++ /dev/null
@@ -1,251 +0,0 @@
-本文来自 Anonymous 的投稿 ,JavaGuide 对原文进行了重新排版和一点完善。
-
-
-
-- [一面 (37 分钟左右)](#一面-37-分钟左右)
-- [二面 (33 分钟左右)](#二面-33-分钟左右)
-- [三面 (46 分钟)](#三面-46-分钟)
-- [HR 面](#hr-面)
-
-
-
-### 一面 (37 分钟左右)
-
-一面是上海的小哥打来的,3.12 号中午确认的内推,下午就打来约时间了,也是唯一一个约时间的面试官。约的晚上八点。紧张的一比,人生第一次面试就献给了阿里。
-
-幸运的是一面的小哥特温柔。好像是个海归?口语中夹杂着英文。废话不多说,上干货:
-
-**面试官:** 先自我介绍下吧!
-
-**我:** 巴拉巴拉...。
-
-> 关于自我介绍:从 HR 面、技术面到高管面/部门主管面,面试官一般会让你先自我介绍一下,所以好好准备自己的自我介绍真的非常重要。网上一般建议的是准备好两份自我介绍:一份对 HR 说的,主要讲能突出自己的经历,会的编程技术一语带过;另一份对技术面试官说的,主要讲自己会的技术细节,项目经验,经历那些就一语带过。
-
-**面试官:** 我看你简历上写你做了个秒杀系统?我们就从这个项目开始吧,先介绍下你的项目。
-
-> 关于项目介绍:如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:
->
-> 1. 对项目整体设计的一个感受(面试官可能会让你画系统的架构图)
-> 2. 在这个项目中你负责了什么、做了什么、担任了什么角色
-> 3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用
-> 4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用 redis 做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。
-
-**我:** 我说了我是如何考虑它的需求(秒杀地址隐藏,记录订单,减库存),一开始简单的用 synchronized 锁住方法,出现了问题,后来乐观锁改进,又有瓶颈,再上缓存,出现了缓存雪崩,于是缓存预热,错开缓存失效时间。最后,发现先记录订单再减库存会减少行级锁等待时间。
-
-> 一面面试官很耐心地听,并给了我一些指导,问了我乐观锁是怎么实现的,我说是基于 sql 语句,在减库存操作的 where 条件里加剩余库存数>0,他说这应该不算是一种乐观锁,应该先查库存,在减库存的时候判断当前库存是否与读到的库存一样(可这样不是多一次查询操作吗?不是很理解,不过我没有反驳,只是说理解您的意思。事实证明千万别怼面试官,即使你觉得他说的不对)
-
-**面试官:** 我缓存雪崩什么情况下会发生?如何避免?
-
-**我:** 当多个商品缓存同时失效时会雪崩,导致大量查询数据库。还有就是秒杀刚开始的时候缓存里没有数据。解决方案:缓存预热,错开缓存失效时间
-
-**面试官:** 问我更新数据库的同时为什么不马上更新缓存,而是删除缓存?
-
-**我:** 因为考虑到更新数据库后更新缓存可能会因为多线程下导致写入脏数据(比如线程 A 先更新数据库成功,接下来要取更新缓存,接着线程 B 更新数据库,但 B 又更新了缓存,接着 B 的时间片用完了,线程 A 更新了缓存)
-
-逼逼了将近 30 分钟,面试官居然用周杰伦的语气对我说:
-
-
-
-我突然受宠若惊,连忙说谢谢,也正是因为第一次面试得到了面试官的肯定,才让我信心大增,二三面稳定发挥。
-
-**面试官又曰:** 我看你还懂数据库是吧,答:略懂略懂。。。那我问个简单的吧!
-
-**我:** 因为这个问题太简单了,所以我忘记它是什么了。
-
-**面试官:** 你还会啥数据库知识?
-
-**我:** 我一听,问的这么随意的吗。。。都让我选题了,我就说我了解索引,慢查询优化,巴拉巴拉
-
-**面试官:** 等等,你说索引是吧,那你能说下索引的存储数据结构吗?
-
-**我:** 我心想这简单啊,我就说 B+树,还说了为什么用 B+树
-
-**面试官:** 你简历上写的这个 J.U.C 包是什么啊?(他居然不知道 JUC)
-
-**我:** 就是 java 多线程的那个包啊。。。
-
-**面试官:** 那你都了解里面的哪些东西呢?
-
-**我:** 哈哈哈!这可是我的强项,从 ConcurrentHashMap,ConcurrentLinkedQueue 说到 CountDownLatch,CyclicBarrier,又说到线程池,分别说了底层实现和项目中的应用。
-
-**面试官:** 我觉得差不多了,那我再问个与技术无关的问题哈,虽然这个问题可能不应该我问,就是你是如何考虑你的项目架构的呢?
-
-**我:** 先用最简单的方式实现它,再去发掘系统的问题和瓶颈,于是查资料改进架构。。。
-
-**面试官:** 好,那我给你介绍下我这边的情况吧
-
-
-
-**总结:** 一面可能是简历面吧,问的比较简单,我在讲项目中说出了我做项目时的学习历程和思考,赢得了面试官的好感,感觉他应该给我的评价很好。
-
-### 二面 (33 分钟左右)
-
-然而开心了没一会,内推人问我面的怎么样啊?看我流程已经到大大 boss 那了。我一听二面不是主管吗???怎么直接跳了一面。于是瞬间慌了,赶紧(下床)学习准备二面。
-
-隔了一天,3.14 的早上 10:56 分,杭州的大大 boss 给我打来了电话,卧槽我当时在上毛概课,万恶的毛概课每节课都点名,我还在最后一排不敢跑出去。于是接起电话来怂怂地说不好意思我在上课,晚上可以面试吗?大大 boss 看来很忙啊,跟我说晚上没时间啊,再说吧!
-
-于是又隔了一天,3.16 中午我收到了北京的电话,当时心里小失望,我的大大 boss 呢???接起电话来,就是一番狂轰乱炸。。。
-
-第一步还是先自我介绍,这个就不多说了,提前准备好要说的重点就没问题!
-
-**面试官:** 我们还是从你的项目开始吧,说说你的秒杀系统。
-
-**我:** 一面时的套路。。。我考虑到秒杀地址在开始前不应暴露给用户。。。
-
-**面试官:** 等下啊,为什么要这样呢?暴露给用户会怎么样?
-
-**我:** 用户提前知道秒杀地址就可以写脚本来抢购了,这样不公平
-
-**面试官:** 那比如说啊,我现在是个黑客,我在秒杀开始时写好了脚本,运行一万个线程获取秒杀地址,这样是不是也不公平呢?
-
-**我:** 我考虑到了这方面,于是我自己写了个 LRU 缓存(划重点,这么多好用的缓存我为啥不用偏要自己写?就是为了让面试官上钩问我是怎么写的,这样我就可以逼逼准备好的内容了!),用这个缓存存储请求的 ip 和用户名,一个 ip 和用户名只能同时透过 3 个请求。
-
-**面试官:** 那我可不可以创建一个 ip 代理池和很多用户来抢购呢?假设我有很多手机号的账户。
-
-**我:** 这就是在为难我胖虎啊,我说这种情况跟真实用户操作太像了。。。我没法区别,不过我觉得可以通过地理位置信息或者机器学习算法来做吧。。。
-
-**面试官:** 好的这个问题就到这吧,你接着说
-
-**我:** 我把生成订单和减库存两条 sql 语句放在一个事务里,都操作成功了则认为秒杀成功。
-
-**面试官:** 等等,你这个订单表和商品库存表是在一个数据库的吧,那如果在不同的数据库中呢?
-
-**我:** 这面试官好变态啊,我只是个本科生?!?!我觉得应该要用分布式锁来实现吧。。。
-
-**面试官:** 有没有更轻量级的做法?
-
-**我:** 不知道了。后来查资料发现可以用消息队列来实现。使用消息队列主要能带来两个好处:(1) 通过异步处理提高系统性能(削峰、减少响应所需时间);(2) 降低系统耦合性。关于消息队列的更多内容可以查看这篇文章:
-
-后来发现消息队列作用好大,于是现在在学手写一个消息队列。
-
-**面试官:** 好的你接着说项目吧。
-
-**我:** 我考虑到了缓存雪崩问题,于是。。。
-
-**面试官:** 等等,你有没有考虑到一种情况,假如说你的缓存刚刚失效,大量流量就来查缓存,你的数据库会不会炸?
-
-**我:** 我不知道数据库会不会炸,反正我快炸了。当时说没考虑这么高的并发量,后来发现也是可以用消息队列来解决,对流量削峰填谷。
-
-**面试官:** 好项目聊(怼)完了,我们来说说别的,操作系统了解吧,你能说说 NIO 吗?
-
-**我:** NIO 是。。。
-
-**面试官:** 那你知道 NIO 的系统调用有哪些吗,具体是怎么实现的?
-
-**我:** 当时复习 NIO 的时候就知道是咋回事,不知道咋实现。最近在补这方面的知识,可见 NIO 还是很重要的!
-
-**面试官:** 说说进程切换时操作系统都会发生什么?
-
-**我:** 不如杀了我,我最讨厌操作系统了。简单说了下,可能不对,需要答案自行百度。
-
-**面试官:** 说说线程池?
-
-**答:** 卧槽这我熟啊,把 Java 并发编程的艺术里讲的都说出来了,说了得有十分钟,自夸一波,毕竟这本书我看了五遍😂
-
-**面试官:** 好问问计网吧如果设计一个聊天系统,应该用 TCP 还是 UDP?为什么
-
-**我:** 当然是 TCP!原因如下:
-
-
-
-**面试官:** 好的,你有什么要问我的吗?
-
-**我:** 我还有下一次面试吗?
-
-**面试官:** 应该。应该有的,一周内吧。还告诉我居然转正前要实习三个月?wtf,一个大三满课的本科生让我如何在八月底前实习三个月?
-
-**我:** 面试官再见
-
-
-
-### 三面 (46 分钟)
-
-3.18 号,三面来了,这次又是那个大大 boss!
-
-第一步还是先自我介绍,这个就不多说了,提前准备好要说的重点就没问题!
-
-**面试官:** 聊聊你的项目?
-
-**我:** 经过二面的教训,我迅速学习了一下分布式的理论知识,并应用到了我的项目(吹牛逼)中。
-
-**面试官:** 看你用到了 Spring 的事务机制,你能说下 Spring 的事务传播吗?
-
-**我:** 完了这个问题好像没准备,虽然之前刷知乎看到过。。。我就只说出来一条,面试官说其实这个有很多机制的,比如事务嵌套,内事务回滚外事务回滚都会有不同情况,你可以回去看看。
-
-**面试官:** 说说你的分布式事务解决方案?
-
-**我:** 我叭叭的照着资料查到的解决方案说了一通,面试官怎么好像没大听懂???
-
-> 阿里巴巴之前开源了一个分布式 Fescar(一种易于使用,高性能,基于 Java 的开源分布式事务解决方案),后来,Ant Financial 加入 Fescar,使其成为一个更加中立和开放的分布式交易社区,Fescar 重命名为 Seata。Github 地址:
-
-**面试官:** 好,我们聊聊其他项目,说说你这个 MapReduce 项目?MapReduce 原理了解过吗?
-
-**我:** 我叭叭地说了一通,面试官好像觉得这个项目太简单了。要不是没项目,我会把我的实验写上吗???
-
-**面试官:** 你这个手写 BP 神经网络是干了啥?
-
-**我:** 这是我选修机器学习课程时的一个作业,我又对它进行了扩展。
-
-**面试官:** 你能说说为什么调整权值时要沿着梯度下降的方向?
-
-**我:** 老大,你太厉害了,怎么什么都懂。我压根没准备这个项目。。。没想到会问,做过去好几个月了,加上当时一紧张就忘了,后来想起来大概是....。
-
-**面试官:** 好我们问问基础知识吧,说说什么叫 xisuo?
-
-**我:**???xisuo,您说什么,不好意思我没听清。(这面试官有点口音。。。)就是 xisuo 啊!xisuo 你不知道吗?。。。尴尬了十几秒后我终于意识到,他在说死锁!!!
-
-**面试官:** 假如 A 账户给 B 账户转钱,会发生 xisuo 吗?能具体说说吗?
-
-**我:** 当时答的不好,后来发现面试官又是想问分布式,具体答案参考这个:
-
-**面试官:** 为什么不考研?
-
-**我:** 不喜欢学术氛围,巴拉巴拉。
-
-**面试官:** 你有什么问题吗?
-
-**我:** 我还有下一面吗。。。面试官说让我等,一周内答复。
-
-------
-
-等了十天,一度以为我凉了,内推人说我流程到 HR 了,让我等着吧可能 HR 太忙了,3.28 号 HR 打来了电话,当时在教室,我直接飞了出去。
-
-### HR 面
-
-**面试官:** 你好啊,先自我介绍下吧
-
-**我:** 巴拉巴拉....HR 面的技术面试和技术面的还是有所区别的!
-
-面试官人特别好,一听就是很会说话的小姐姐!说我这里给你悄悄透露下,你的评级是 A 哦!
-
-
-
-接下来就是几个经典 HR 面挂人的问题,什么难给我来什么,我看别人的 HR 面怎么都是聊聊天。。。
-
-**面试官:** 你为什么选择支付宝呢,你怎么看待支付宝?
-
-**我:** 我从个人情怀,公司理念,环境氛围,市场价值,趋势导向分析了一波(说白了就是疯狂夸支付宝,不过说实话我说的那些一点都没撒谎,阿里确实做到了。比如我举了个雷军和格力打赌 5 年 2000 亿销售额,大部分企业家关注的是利益,而马云更关注的是真的为人类为世界做一些事情,利益不是第一位的。)
-
-**面试官:** 明白了解,那你的优点我们都很明了了,你能说说你的缺点吗?
-
-> 缺点肯定不能是目标岗位需要的关键能力!!!
->
-> 总之,记住一点,面试官问你这个问题的话,你可以说一些不影响你这个职位工作需要的一些缺点。比如你面试后端工程师,面试官问你的缺点是什么的话,你可以这样说:自己比较内向,平时不太爱与人交流,但是考虑到以后可能要和客户沟通,自己正在努力改。
-
-**我:** 据说这是 HR 面最难的一个问题。。。我当时翻了好几天的知乎才找到一个合适的,也符合我的答案:我有时候会表现的不太自信,比如阿里的内推二月份就开始了,其实我当时已经复习了很久了,但是老是觉得自己还不行,不敢投简历,于是又把书看了一遍才投的,当时也是舍友怂恿一波才投的,面了之后发现其实自己也没有很差。(划重点,一定要把自己的缺点圆回来)。
-
-**面试官:** HR 好像不太满意我的答案,继续问我还有缺点吗?
-
-**我:** 我说比较容易紧张吧,举了自己大一面实验室因为紧张没进去的例子,后来不断调整心态,现在已经好很多了。
-
-接下来又是个好难的问题。
-
-**面试官:** BAT 都给你 offer 了,你怎么选?
-
-其实我当时好想说,BT 是什么?不好意思我只知道阿里。
-
-**我 :** 哈哈哈哈开玩笑,就说了阿里的文化,支付宝给我们带来很多便利,想加入支付宝为人类做贡献!
-
-最后 HR 问了我实习时间,现在大几之类的问题,说肯定会给我发 offer 的,让我等着就好了,希望过两天能收到好的结果。
-
-
diff --git a/docs/essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md b/docs/essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md
deleted file mode 100644
index 835b6a5..0000000
--- a/docs/essential-content-for-interview/PreparingForInterview/JavaInterviewLibrary.md
+++ /dev/null
@@ -1,89 +0,0 @@
-昨天我整理了公众号历史所有和面试相关的我觉得还不错的文章:[整理了一些有助于你拿Offer的文章]() 。今天分享一下最近逛Github看到了一些我觉得对于Java面试以及学习有帮助的仓库,这些仓库涉及Java核心知识点整理、Java常见面试题、算法、基础知识点比如网络和操作系统等等。
-
-## 知识点相关
-
-### 1.JavaGuide
-
-- Github地址: [https://github.com/Snailclimb/JavaGuide](https://github.com/Snailclimb/JavaGuide)
-- star: 64.0k
-- 介绍: 【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识。
-
-### 2.CS-Notes
-
-- Github 地址:
-- Star: 68.3k
-- 介绍: 技术面试必备基础知识、Leetcode 题解、后端面试、Java 面试、春招、秋招、操作系统、计算机网络、系统设计。
-
-### 3. advanced-java
-
-- Github地址:[https://github.com/doocs/advanced-java](https://github.com/doocs/advanced-java)
-- star: 23.4k
-- 介绍: 互联网 Java 工程师进阶知识完全扫盲:涵盖高并发、分布式、高可用、微服务等领域知识,后端同学必看,前端同学也可学习。
-
-### 4.JCSprout
-
-- Github地址:[https://github.com/crossoverJie/JCSprout](https://github.com/crossoverJie/JCSprout)
-- star: 21.2k
-- 介绍: Java Core Sprout:处于萌芽阶段的 Java 核心知识库。
-
-### 5.toBeTopJavaer
-
-- Github地址:[https://github.com/hollischuang/toBeTopJavaer](https://github.com/hollischuang/toBeTopJavaer)
-- star: 4.0 k
-- 介绍: Java工程师成神之路。
-
-### 6.architect-awesome
-
-- Github地址:[https://github.com/xingshaocheng/architect-awesome](https://github.com/xingshaocheng/architect-awesome)
-- star: 34.4 k
-- 介绍:后端架构师技术图谱。
-
-### 7.technology-talk
-
-- Github地址: [https://github.com/aalansehaiyang/technology-talk](https://github.com/aalansehaiyang/technology-talk)
-- star: 6.1k
-- 介绍: 汇总java生态圈常用技术框架、开源中间件,系统架构、项目管理、经典架构案例、数据库、常用三方库、线上运维等知识。
-
-### 8.fullstack-tutorial
-
-- Github地址: [https://github.com/frank-lam/fullstack-tutorial](https://github.com/frank-lam/fullstack-tutorial)
-- star: 4.0k
-- 介绍: fullstack tutorial 2019,后台技术栈/架构师之路/全栈开发社区,春招/秋招/校招/面试。
-
-### 9.3y
-
-- Github地址:[https://github.com/ZhongFuCheng3y/3y](https://github.com/ZhongFuCheng3y/3y)
-- star: 1.9 k
-- 介绍: Java 知识整合。
-
-### 10.java-bible
-
-- Github地址:[https://github.com/biezhi/java-bible](https://github.com/biezhi/java-bible)
-- star: 2.3k
-- 介绍: 这里记录了一些技术摘要,部分文章来自网络,本项目的目的力求分享精品技术干货,以Java为主。
-
-### 11.interviews
-
-- Github地址: [https://github.com/kdn251/interviews/blob/master/README-zh-cn.md](https://github.com/kdn251/interviews/blob/master/README-zh-cn.md)
-- star: 35.3k
-- 介绍: 软件工程技术面试个人指南(国外的一个项目,虽然有翻译版,但是不太推荐,因为很多内容并不适用于国内)。
-
-## 算法相关
-
-### 1.LeetCodeAnimation
-
-- Github 地址:
-- Star: 33.4k
-- 介绍: Demonstrate all the questions on LeetCode in the form of animation.(用动画的形式呈现解LeetCode题目的思路)。
-
-### 2.awesome-java-leetcode
-
-- Github地址:[https://github.com/Blankj/awesome-java-leetcode](https://github.com/Blankj/awesome-java-leetcode)
-- star: 6.1k
-- 介绍: LeetCode 上 Facebook 的面试题目。
-
-### 3.leetcode
-
-- Github地址:[https://github.com/azl397985856/leetcode](https://github.com/azl397985856/leetcode)
-- star: 12.0k
-- 介绍: LeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解,记录自己的leetcode解题之路。)
\ No newline at end of file
diff --git a/docs/essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md b/docs/essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md
deleted file mode 100644
index d515693..0000000
--- a/docs/essential-content-for-interview/PreparingForInterview/JavaProgrammerNeedKnow.md
+++ /dev/null
@@ -1,81 +0,0 @@
- 身边的朋友或者公众号的粉丝很多人都向我询问过:“我是双非/三本/专科学校的,我有机会进入大厂吗?”、“非计算机专业的学生能学好吗?”、“如何学习Java?”、“Java学习该学哪些东西?”、“我该如何准备Java面试?”......这些方面的问题。我会根据自己的一点经验对大部分人关心的这些问题进行答疑解惑。现在又刚好赶上考研结束,这篇文章也算是给考研结束准备往Java后端方向发展的朋友们指明一条学习之路。道理懂了如果没有实际行动,那这篇文章对你或许没有任何意义。
-
-### Question1:我是双非/三本/专科学校的,我有机会进入大厂吗?
-
- 我自己也是非985非211学校的,结合自己的经历以及一些朋友的经历,我觉得让我回答这个问题再好不过。
-
- 首先,我觉得学校歧视很正常,真的太正常了,如果要抱怨的话,你只能抱怨自己没有进入名校。但是,千万不要动不动说自己学校差,动不动拿自己学校当做自己进不了大厂的借口,学历只是筛选简历的很多标准中的一个而已,如果你够优秀,简历够丰富,你也一样可以和名校同学一起同台竞争。
-
- 企业HR肯定是更喜欢高学历的人,毕竟985、211优秀人才比例肯定比普通学校高很多,HR团队肯定会优先在这些学校里选。这就好比相亲,你是愿意在很多优秀的人中选一个优秀的,还是愿意在很多普通的人中选一个优秀的呢?
-
- 双非本科甚至是二本、三本甚至是专科的同学也有很多进入大厂的,不过比率相比于名校的低很多而已。从大厂招聘的结果上看,高学历人才的数量占据大头,那些成功进入BAT、美团,京东,网易等大厂的双非本科甚至是二本、三本甚至是专科的同学往往是因为具备丰富的项目经历或者在某个含金量比较高的竞赛比如ACM中取得了不错的成绩。**一部分学历不突出但能力出众的面试者能够进入大厂并不是说明学历不重要,而是学历的软肋能够通过其他的优势来弥补。** 所以,如果你的学校不够好而你自己又想去大厂的话,建议你可以从这几点来做:**①尽量在面试前最好有一个可以拿的出手的项目;②有实习条件的话,尽早出去实习,实习经历也会是你的简历的一个亮点(有能力在大厂实习最佳!);③参加一些含金量比较高的比赛,拿不拿得到名次没关系,重在锻炼。**
-
-
-### Question2:非计算机专业的学生能学好Java后台吗?我能进大厂吗?
-
- 当然可以!现在非科班的程序员很多,很大一部分原因是互联网行业的工资比较高。我们学校外面的培训班里面90%都是非科班,我觉得他们很多人学的都还不错。另外,我的一个朋友本科是机械专业,大一开始自学安卓,技术贼溜,在我看来他比大部分本科是计算机的同学学的还要好。参考Question1的回答,即使你是非科班程序员,如果你想进入大厂的话,你也可以通过自己的其他优势来弥补。
-
- 我觉得我们不应该因为自己的专业给自己划界限或者贴标签,说实话,很多科班的同学可能并不如你,你以为科班的同学就会认真听讲吗?还不是几乎全靠自己课下自学!不过如果你是非科班的话,你想要学好,那么注定就要舍弃自己本专业的一些学习时间,这是无可厚非的。
-
- 建议非科班的同学,首先要打好计算机基础知识基础:①计算机网络、②操作系统、③数据机构与算法,我个人觉得这3个对你最重要。这些东西就像是内功,对你以后的长远发展非常有用。当然,如果你想要进大厂的话,这些知识也是一定会被问到的。另外,“一定学好数据结构与算法!一定学好数据结构与算法!一定学好数据结构与算法!”,重要的东西说3遍。
-
-
-
-### Question3: 我没有实习经历的话找工作是不是特别艰难?
-
- 没有实习经历没关系,只要你有拿得出手的项目或者大赛经历的话,你依然有可能拿到大厂的 offer 。笔主当时找工作的时候就没有实习经历以及大赛获奖经历,单纯就是凭借自己的项目经验撑起了整个面试。
-
- 如果你既没有实习经历,又没有拿得出手的项目或者大赛经历的话,我觉得在简历关,除非你有其他特别的亮点,不然,你应该就会被刷。
-
-### Question4: 我该如何准备面试呢?面试的注意事项有哪些呢?
-
-下面是我总结的一些准备面试的Tips以及面试必备的注意事项:
-
-1. **准备一份自己的自我介绍,面试的时候根据面试对象适当进行修改**(突出重点,突出自己的优势在哪里,切忌流水账);
-2. **注意随身带上自己的成绩单和简历复印件;** (有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。)
-3. **如果需要笔试就提前刷一些笔试题,大部分在线笔试的类型是选择题+编程题,有的还会有简答题。**(平时空闲时间多的可以刷一下笔试题目(牛客网上有很多),但是不要只刷面试题,不动手code,程序员不是为了考试而存在的。)另外,注意抓重点,因为题目太多了,但是有很多题目几乎次次遇到,像这样的题目一定要搞定。
-4. **提前准备技术面试。** 搞清楚自己面试中可能涉及哪些知识点、哪些知识点是重点。面试中哪些问题会被经常问到、自己该如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!)
-5. **面试之前做好定向复习。** 也就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。
-6. **准备好自己的项目介绍。** 如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:①对项目整体设计的一个感受(面试官可能会让你画系统的架构图);②在这个项目中你负责了什么、做了什么、担任了什么角色;③ 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用;④项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用 redis 做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。
-7. **面试之后记得复盘。** 面试遭遇失败是很正常的事情,所以善于总结自己的失败原因才是最重要的。如果失败,不要灰心;如果通过,切勿狂喜。
-
-
-**一些还算不错的 Java面试/学习相关的仓库,相信对大家准备面试一定有帮助:**[盘点一下Github上开源的Java面试/学习相关的仓库,看完弄懂薪资至少增加10k](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484817&idx=1&sn=12f0c254a240c40c2ccab8314653216b&chksm=fd9853f0caefdae6d191e6bf085d44ab9c73f165e3323aa0362d830e420ccbfad93aa5901021&token=766994974&lang=zh_CN#rd)
-
-### Question5: 我该自学还是报培训班呢?
-
- 我本人更加赞同自学(你要知道去了公司可没人手把手教你了,而且几乎所有的公司都对培训班出生的有偏见。为什么有偏见,你学个东西还要去培训班,说明什么,同等水平下,你的自学能力以及自律能力一定是比不上自学的人的)。但是如果,你连每天在寝室坚持学上8个小时以上都坚持不了,或者总是容易半途而废的话,我还是推荐你去培训班。观望身边同学去培训班的,大多是非计算机专业或者是没有自律能力以及自学能力非常差的人。
-
- 另外,如果自律能力不行,你也可以通过结伴学习、参加老师的项目等方式来督促自己学习。
-
- 总结:去不去培训班主要还是看自己,如果自己能坚持自学就自学,坚持不下来就去培训班。
-
-### Question6: 没有项目经历/博客/Github开源项目怎么办?
-
- 从现在开始做!
-
- 网上有很多非常不错的项目视频,你就跟着一步一步做,不光要做,还要改进,改善。另外,如果你的老师有相关 Java 后台项目的话,你也可以主动申请参与进来。
-
- 如果有自己的博客,也算是简历上的一个亮点。建议可以在掘金、Segmentfault、CSDN等技术交流社区写博客,当然,你也可以自己搭建一个博客(采用 Hexo+Githu Pages 搭建非常简单)。写一些什么?学习笔记、实战内容、读书笔记等等都可以。
-
- 多用 Github,用好 Github,上传自己不错的项目,写好 readme 文档,在其他技术社区做好宣传。相信你也会收获一个不错的开源项目!
-
-
-### Question7: 大厂到底青睐什么样的应届生?
-
- 从阿里、腾讯等大厂招聘官网对于Java后端方向/后端方向的应届实习生的要求,我们大概可以总结归纳出下面这 4 点能给简历增加很多分数:
-
-- 参加过竞赛(含金量超高的是ACM);
-- 对数据结构与算法非常熟练;
-- 参与过实际项目(比如学校网站);
-- 参与过某个知名的开源项目或者自己的某个开源项目很不错;
-
- 除了我上面说的这三点,在面试Java工程师的时候,下面几点也提升你的个人竞争力:
-
-- 熟悉Python、Shell、Perl等脚本语言;
-- 熟悉 Java 优化,JVM调优;
-- 熟悉 SOA 模式;
-- 熟悉自己所用框架的底层知识比如Spring;
-- 了解分布式一些常见的理论;
-- 具备高并发开发经验;大数据开发经验等等。
-
diff --git a/docs/essential-content-for-interview/PreparingForInterview/interviewPrepare.md b/docs/essential-content-for-interview/PreparingForInterview/interviewPrepare.md
deleted file mode 100644
index 1ae36a3..0000000
--- a/docs/essential-content-for-interview/PreparingForInterview/interviewPrepare.md
+++ /dev/null
@@ -1,88 +0,0 @@
-不论是校招还是社招都避免不了各种面试、笔试,如何去准备这些东西就显得格外重要。不论是笔试还是面试都是有章可循的,我这个“有章可循”说的意思只是说应对技术面试是可以提前准备。 我其实特别不喜欢那种临近考试就提前背啊记啊各种题的行为,非常反对!我觉得这种方法特别极端,而且在稍有一点经验的面试官面前是根本没有用的。建议大家还是一步一个脚印踏踏实实地走。
-
-
-
-- [1 如何获取大厂面试机会?](#1-如何获取大厂面试机会)
-- [2 面试前的准备](#2--面试前的准备)
- - [2.1 准备自己的自我介绍](#21-准备自己的自我介绍)
- - [2.2 关于着装](#22-关于着装)
- - [2.3 随身带上自己的成绩单和简历](#23-随身带上自己的成绩单和简历)
- - [2.4 如果需要笔试就提前刷一些笔试题](#24-如果需要笔试就提前刷一些笔试题)
- - [2.5 花时间一些逻辑题](#25-花时间一些逻辑题)
- - [2.6 准备好自己的项目介绍](#26-准备好自己的项目介绍)
- - [2.7 提前准备技术面试](#27-提前准备技术面试)
- - [2.7 面试之前做好定向复习](#27-面试之前做好定向复习)
-- [3 面试之后复盘](#3-面试之后复盘)
-
-
-
-## 1 如何获取大厂面试机会?
-
-**在讲如何获取大厂面试机会之前,先来给大家科普/对比一下两个校招非常常见的概念——春招和秋招。**
-
-1. **招聘人数** :秋招多于春招 ;
-2. **招聘时间** : 秋招一般7月左右开始,大概一直持续到10月底。但是大厂(如BAT)都会早开始早结束,所以一定要把握好时间。春招最佳时间为3月,次佳时间为4月,进入5月基本就不会再有春招了(金三银四)。
-3. **应聘难度** :秋招略大于春招;
-4. **招聘公司:** 秋招数量多,而春招数量较少,一般为秋招的补充。
-
-**综上,一般来说,秋招的含金量明显是高于春招的。**
-
-**下面我就说一下我自己知道的一些方法,不过应该也涵盖了大部分获取面试机会的方法。**
-
-1. **关注大厂官网,随时投递简历(走流程的网申);**
-2. **线下参加宣讲会,直接投递简历;**
-3. **找到师兄师姐/认识的人,帮忙内推(能够让你避开网申简历筛选,笔试筛选,还是挺不错的,不过也还是需要你的简历够棒);**
-4. **博客发文被看中/Github优秀开源项目作者,大厂内部人员邀请你面试;**
-5. **求职类网站投递简历(不是太推荐,适合海投);**
-
-
-除了这些方法,我也遇到过这样的经历:有些大公司的一些部门可能暂时没招够人,然后如果你的亲戚或者朋友刚好在这个公司,而你正好又在寻求offer,那么面试机会基本上是有了,而且这种面试的难度好像一般还普遍比其他正规面试低很多。
-
-## 2 面试前的准备
-
-### 2.1 准备自己的自我介绍
-
-从HR面、技术面到高管面/部门主管面,面试官一般会让你先自我介绍一下,所以好好准备自己的自我介绍真的非常重要。网上一般建议的是准备好两份自我介绍:一份对hr说的,主要讲能突出自己的经历,会的编程技术一语带过;另一份对技术面试官说的,主要讲自己会的技术细节,项目经验,经历那些就一语带过。
-
-我这里简单分享一下我自己的自我介绍的一个简单的模板吧:
-
-> 面试官,您好!我叫某某。大学时间我主要利用课外时间学习某某。在校期间参与过一个某某系统的开发,另外,自己学习过程中也写过很多系统比如某某系统。在学习之余,我比较喜欢通过博客整理分享自己所学知识。我现在是某某社区的认证作者,写过某某很不错的文章。另外,我获得过某某奖,我的Github上开源的某个项目已经有多少Star了。
-
-### 2.2 关于着装
-
-穿西装、打领带、小皮鞋?NO!NO!NO!这是互联网公司面试又不是去走红毯,所以你只需要穿的简单大方就好,不需要太正式。
-
-### 2.3 随身带上自己的成绩单和简历
-
-有的公司在面试前都会让你交一份成绩单和简历当做面试中的参考。
-
-### 2.4 如果需要笔试就提前刷一些笔试题
-
-平时空闲时间多的可以刷一下笔试题目(牛客网上有很多)。但是不要只刷面试题,不动手code,程序员不是为了考试而存在的。
-
-### 2.5 花时间一些逻辑题
-
-面试中发现有些公司都有逻辑题测试环节,并且都把逻辑笔试成绩作为很重要的一个参考。
-
-### 2.6 准备好自己的项目介绍
-
-如果有项目的话,技术面试第一步,面试官一般都是让你自己介绍一下你的项目。你可以从下面几个方向来考虑:
-
-1. 对项目整体设计的一个感受(面试官可能会让你画系统的架构图)
-2. 在这个项目中你负责了什么、做了什么、担任了什么角色
-3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用
-4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。
-
-### 2.7 提前准备技术面试
-
-搞清楚自己面试中可能涉及哪些知识点、哪些知识点是重点。面试中哪些问题会被经常问到、自己该如何回答。(强烈不推荐背题,第一:通过背这种方式你能记住多少?能记住多久?第二:背题的方式的学习很难坚持下去!)
-
-### 2.7 面试之前做好定向复习
-
-所谓定向复习就是专门针对你要面试的公司来复习。比如你在面试之前可以在网上找找有没有你要面试的公司的面经。
-
-举个栗子:在我面试 ThoughtWorks 的前几天我就在网上找了一些关于 ThoughtWorks 的技术面的一些文章。然后知道了 ThoughtWorks 的技术面会让我们在之前做的作业的基础上增加一个或两个功能,所以我提前一天就把我之前做的程序重新重构了一下。然后在技术面的时候,简单的改了几行代码之后写个测试就完事了。如果没有提前准备,我觉得 20 分钟我很大几率会完不成这项任务。
-
-## 3 面试之后复盘
-
-如果失败,不要灰心;如果通过,切勿狂喜。面试和工作实际上是两回事,可能很多面试未通过的人,工作能力比你强的多,反之亦然。我个人觉得面试也像是一场全新的征程,失败和胜利都是平常之事。所以,劝各位不要因为面试失败而灰心、丧失斗志。也不要因为面试通过而沾沾自喜,等待你的将是更美好的未来,继续加油!
diff --git "a/docs/essential-content-for-interview/PreparingForInterview/\345\272\224\345\261\212\347\224\237\351\235\242\350\257\225\346\234\200\347\210\261\351\227\256\347\232\204\345\207\240\351\201\223Java\345\237\272\347\241\200\351\227\256\351\242\230.md" "b/docs/essential-content-for-interview/PreparingForInterview/\345\272\224\345\261\212\347\224\237\351\235\242\350\257\225\346\234\200\347\210\261\351\227\256\347\232\204\345\207\240\351\201\223Java\345\237\272\347\241\200\351\227\256\351\242\230.md"
deleted file mode 100644
index 2e93113..0000000
--- "a/docs/essential-content-for-interview/PreparingForInterview/\345\272\224\345\261\212\347\224\237\351\235\242\350\257\225\346\234\200\347\210\261\351\227\256\347\232\204\345\207\240\351\201\223Java\345\237\272\347\241\200\351\227\256\351\242\230.md"
+++ /dev/null
@@ -1,743 +0,0 @@
-
-
-- [一 为什么 Java 中只有值传递?](#一-为什么-java-中只有值传递)
-- [二 ==与 equals(重要)](#二-与-equals重要)
-- [三 hashCode 与 equals(重要)](#三-hashcode-与-equals重要)
- - [3.1 hashCode()介绍](#31-hashcode介绍)
- - [3.2 为什么要有 hashCode](#32-为什么要有-hashcode)
- - [3.3 hashCode()与 equals()的相关规定](#33-hashcode与-equals的相关规定)
- - [3.4 为什么两个对象有相同的 hashcode 值,它们也不一定是相等的?](#34-为什么两个对象有相同的-hashcode-值它们也不一定是相等的)
-- [四 String 和 StringBuffer、StringBuilder 的区别是什么?String 为什么是不可变的?](#四-string-和-stringbufferstringbuilder-的区别是什么string-为什么是不可变的)
- - [String 为什么是不可变的吗?](#string-为什么是不可变的吗)
- - [String 真的是不可变的吗?](#string-真的是不可变的吗)
-- [五 什么是反射机制?反射机制的应用场景有哪些?](#五-什么是反射机制反射机制的应用场景有哪些)
- - [5.1 反射机制介绍](#51-反射机制介绍)
- - [5.2 静态编译和动态编译](#52-静态编译和动态编译)
- - [5.3 反射机制优缺点](#53-反射机制优缺点)
- - [5.4 反射的应用场景](#54-反射的应用场景)
-- [六 什么是 JDK?什么是 JRE?什么是 JVM?三者之间的联系与区别](#六-什么是-jdk什么是-jre什么是-jvm三者之间的联系与区别)
- - [6.1 JVM](#61-jvm)
- - [6.2 JDK 和 JRE](#62-jdk-和-jre)
-- [七 什么是字节码?采用字节码的最大好处是什么?](#七-什么是字节码采用字节码的最大好处是什么)
-- [八 接口和抽象类的区别是什么?](#八-接口和抽象类的区别是什么)
-- [九 重载和重写的区别](#九-重载和重写的区别)
- - [重载](#重载)
- - [重写](#重写)
-- [十. Java 面向对象编程三大特性: 封装 继承 多态](#十-java-面向对象编程三大特性-封装-继承-多态)
- - [封装](#封装)
- - [继承](#继承)
- - [多态](#多态)
-- [十一. 什么是线程和进程?](#十一-什么是线程和进程)
- - [11.1 何为进程?](#111-何为进程)
- - [11.2 何为线程?](#112-何为线程)
-- [十二. 请简要描述线程与进程的关系,区别及优缺点?](#十二-请简要描述线程与进程的关系区别及优缺点)
- - [12.1 图解进程和线程的关系](#121-图解进程和线程的关系)
- - [12.2 程序计数器为什么是私有的?](#122-程序计数器为什么是私有的)
- - [12.3 虚拟机栈和本地方法栈为什么是私有的?](#123-虚拟机栈和本地方法栈为什么是私有的)
- - [12.4 一句话简单了解堆和方法区](#124-一句话简单了解堆和方法区)
-- [十三. 说说并发与并行的区别?](#十三-说说并发与并行的区别)
-- [十四. 什么是上下文切换?](#十四-什么是上下文切换)
-- [十五. 什么是线程死锁?如何避免死锁?](#十五-什么是线程死锁如何避免死锁)
- - [15.1. 认识线程死锁](#151-认识线程死锁)
- - [15.2 如何避免线程死锁?](#152-如何避免线程死锁)
-- [十六. 说说 sleep() 方法和 wait() 方法区别和共同点?](#十六-说说-sleep-方法和-wait-方法区别和共同点)
-- [十七. 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?](#十七-为什么我们调用-start-方法时会执行-run-方法为什么我们不能直接调用-run-方法)
-- [参考](#参考)
-
-
-
-## 一 为什么 Java 中只有值传递?
-
-首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。**按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。** 它用来描述各种程序设计语言(不只是 Java)中方法参数传递方式。
-
-**Java 程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。**
-
-**下面通过 3 个例子来给大家说明**
-
-> **example 1**
-
-```java
-public static void main(String[] args) {
- int num1 = 10;
- int num2 = 20;
-
- swap(num1, num2);
-
- System.out.println("num1 = " + num1);
- System.out.println("num2 = " + num2);
-}
-
-public static void swap(int a, int b) {
- int temp = a;
- a = b;
- b = temp;
-
- System.out.println("a = " + a);
- System.out.println("b = " + b);
-}
-```
-
-**结果:**
-
-```
-a = 20
-b = 10
-num1 = 10
-num2 = 20
-```
-
-**解析:**
-
-
-
-在 swap 方法中,a、b 的值进行交换,并不会影响到 num1、num2。因为,a、b 中的值,只是从 num1、num2 的复制过来的。也就是说,a、b 相当于 num1、num2 的副本,副本的内容无论怎么修改,都不会影响到原件本身。
-
-**通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看 example2.**
-
-> **example 2**
-
-```java
- public static void main(String[] args) {
- int[] arr = { 1, 2, 3, 4, 5 };
- System.out.println(arr[0]);
- change(arr);
- System.out.println(arr[0]);
- }
-
- public static void change(int[] array) {
- // 将数组的第一个元素变为0
- array[0] = 0;
- }
-```
-
-**结果:**
-
-```
-1
-0
-```
-
-**解析:**
-
-
-
-array 被初始化 arr 的拷贝也就是一个对象的引用,也就是说 array 和 arr 指向的是同一个数组对象。 因此,外部对引用对象的改变会反映到所对应的对象上。
-
-**通过 example2 我们已经看到,实现一个改变对象参数状态的方法并不是一件难事。理由很简单,方法得到的是对象引用的拷贝,对象引用及其他的拷贝同时引用同一个对象。**
-
-**很多程序设计语言(特别是,C++和 Pascal)提供了两种参数传递的方式:值调用和引用调用。有些程序员(甚至本书的作者)认为 Java 程序设计语言对对象采用的是引用调用,实际上,这种理解是不对的。由于这种误解具有一定的普遍性,所以下面给出一个反例来详细地阐述一下这个问题。**
-
-> **example 3**
-
-```java
-public class Test {
-
- public static void main(String[] args) {
- // TODO Auto-generated method stub
- Student s1 = new Student("小张");
- Student s2 = new Student("小李");
- Test.swap(s1, s2);
- System.out.println("s1:" + s1.getName());
- System.out.println("s2:" + s2.getName());
- }
-
- public static void swap(Student x, Student y) {
- Student temp = x;
- x = y;
- y = temp;
- System.out.println("x:" + x.getName());
- System.out.println("y:" + y.getName());
- }
-}
-```
-
-**结果:**
-
-```
-x:小李
-y:小张
-s1:小张
-s2:小李
-```
-
-**解析:**
-
-交换之前:
-
-
-
-交换之后:
-
-
-
-通过上面两张图可以很清晰的看出: **方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap 方法的参数 x 和 y 被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝**
-
-> **总结**
-
-Java 程序设计语言对对象采用的不是引用调用,实际上,对象引用是按
-值传递的。
-
-下面再总结一下 Java 中方法参数的使用情况:
-
-- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
-- 一个方法可以改变一个对象参数的状态。
-- 一个方法不能让对象参数引用一个新的对象。
-
-**参考:**
-
-《Java 核心技术卷 Ⅰ》基础知识第十版第四章 4.5 小节
-
-## 二 ==与 equals(重要)
-
-**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)
-
-**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
-
-- 情况 1:类没有覆盖 equals()方法。则通过 equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
-- 情况 2:类覆盖了 equals()方法。一般,我们都覆盖 equals()方法来两个对象的内容相等;若它们的内容相等,则返回 true(即,认为这两个对象相等)。
-
-**举个例子:**
-
-```java
-public class test1 {
- public static void main(String[] args) {
- String a = new String("ab"); // a 为一个引用
- String b = new String("ab"); // b为另一个引用,对象的内容一样
- String aa = "ab"; // 放在常量池中
- String bb = "ab"; // 从常量池中查找
- if (aa == bb) // true
- System.out.println("aa==bb");
- if (a == b) // false,非同一对象
- System.out.println("a==b");
- if (a.equals(b)) // true
- System.out.println("aEQb");
- if (42 == 42.0) { // true
- System.out.println("true");
- }
- }
-}
-```
-
-**说明:**
-
-- String 中的 equals 方法是被重写过的,因为 object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。
-- 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
-
-## 三 hashCode 与 equals(重要)
-
-面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写 hashCode 方法?”
-
-### 3.1 hashCode()介绍
-
-hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。
-
-```java
- /**
- * Returns a hash code value for the object. This method is
- * supported for the benefit of hash tables such as those provided by
- * {@link java.util.HashMap}.
- *
- * As much as is reasonably practical, the hashCode method defined by
- * class {@code Object} does return distinct integers for distinct
- * objects. (This is typically implemented by converting the internal
- * address of the object into an integer, but this implementation
- * technique is not required by the
- * Java™ programming language.)
- *
- * @return a hash code value for this object.
- * @see java.lang.Object#equals(java.lang.Object)
- * @see java.lang.System#identityHashCode
- */
- public native int hashCode();
-```
-
-散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
-
-### 3.2 为什么要有 hashCode
-
-**我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:**
-
-当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的 Java 启蒙书《Head fist java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
-
-### 3.3 hashCode()与 equals()的相关规定
-
-1. 如果两个对象相等,则 hashcode 一定也是相同的
-2. 两个对象相等,对两个对象分别调用 equals 方法都返回 true
-3. 两个对象有相同的 hashcode 值,它们也不一定是相等的
-4. **因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖**
-5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
-
-### 3.4 为什么两个对象有相同的 hashcode 值,它们也不一定是相等的?
-
-在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。
-
-因为 hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode)。
-
-我们刚刚也提到了 HashSet,如果 HashSet 在对比的时候,同样的 hashcode 有多个对象,它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。
-
-## 四 String 和 StringBuffer、StringBuilder 的区别是什么?String 为什么是不可变的?
-
-**可变性**
-
-简单的来说:String 类中使用 final 关键字修饰字符数组来保存字符串,`private final char value[]`,所以 String 对象是不可变的。而 StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串`char[]value` 但是没有用 final 关键字修饰,所以这两种对象都是可变的。
-
-StringBuilder 与 StringBuffer 的构造方法都是调用父类构造方法也就是 AbstractStringBuilder 实现的,大家可以自行查阅源码。
-
-AbstractStringBuilder.java
-
-```java
-abstract class AbstractStringBuilder implements Appendable, CharSequence {
- char[] value;
- int count;
- AbstractStringBuilder() {
- }
- AbstractStringBuilder(int capacity) {
- value = new char[capacity];
- }
-```
-
-**线程安全性**
-
-String 中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
-
-**性能**
-
-每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
-
-**对于三者使用的总结:**
-
-1. 操作少量的数据: 适用 String
-2. 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
-3. 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
-
-#### String 为什么是不可变的吗?
-
-简单来说就是 String 类利用了 final 修饰的 char 类型数组存储字符,源码如下图所以:
-
-```java
- /** The value is used for character storage. */
- private final char value[];
-```
-
-#### String 真的是不可变的吗?
-
-我觉得如果别人问这个问题的话,回答不可变就可以了。
-下面只是给大家看两个有代表性的例子:
-
-**1) String 不可变但不代表引用不可以变**
-
-```java
- String str = "Hello";
- str = str + " World";
- System.out.println("str=" + str);
-```
-
-结果:
-
-```
-str=Hello World
-```
-
-解析:
-
-实际上,原来 String 的内容是不变的,只是 str 由原来指向"Hello"的内存地址转为指向"Hello World"的内存地址而已,也就是说多开辟了一块内存区域给"Hello World"字符串。
-
-**2) 通过反射是可以修改所谓的“不可变”对象**
-
-```java
- // 创建字符串"Hello World", 并赋给引用s
- String s = "Hello World";
-
- System.out.println("s = " + s); // Hello World
-
- // 获取String类中的value字段
- Field valueFieldOfString = String.class.getDeclaredField("value");
-
- // 改变value属性的访问权限
- valueFieldOfString.setAccessible(true);
-
- // 获取s对象上的value属性的值
- char[] value = (char[]) valueFieldOfString.get(s);
-
- // 改变value所引用的数组中的第5个字符
- value[5] = '_';
-
- System.out.println("s = " + s); // Hello_World
-```
-
-结果:
-
-```
-s = Hello World
-s = Hello_World
-```
-
-解析:
-
-用反射可以访问私有成员, 然后反射出 String 对象中的 value 属性, 进而改变通过获得的 value 引用改变数组的结构。但是一般我们不会这么做,这里只是简单提一下有这个东西。
-
-## 五 什么是反射机制?反射机制的应用场景有哪些?
-
-### 5.1 反射机制介绍
-
-JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。
-
-### 5.2 静态编译和动态编译
-
-- **静态编译:**在编译时确定类型,绑定对象
-- **动态编译:**运行时确定类型,绑定对象
-
-### 5.3 反射机制优缺点
-
-- **优点:** 运行期类型的判断,动态加载类,提高代码灵活度。
-- **缺点:** 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 java 代码要慢很多。
-
-### 5.4 反射的应用场景
-
-**反射是框架设计的灵魂。**
-
-在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。
-
-举例:① 我们在使用 JDBC 连接数据库时使用 `Class.forName()`通过反射加载数据库的驱动程序;②Spring 框架也用到很多反射机制,最经典的就是 xml 的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中;
-2)Java 类里面解析 xml 或 properties 里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的 Class 实例; 4)动态配置实例的属性
-
-**推荐阅读:**
-
-- [Reflection:Java 反射机制的应用场景](https://segmentfault.com/a/1190000010162647?utm_source=tuicool&utm_medium=referral "Reflection:Java反射机制的应用场景")
-- [Java 基础之—反射(非常重要)](https://blog.csdn.net/sinat_38259539/article/details/71799078 "Java基础之—反射(非常重要)")
-
-## 六 什么是 JDK?什么是 JRE?什么是 JVM?三者之间的联系与区别
-
-### 6.1 JVM
-
-Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。
-
-**什么是字节码?采用字节码的好处是什么?**
-
-> 在 Java 中,JVM 可以理解的代码就叫做`字节码`(即扩展名为 `.class` 的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
-
-**Java 程序从源代码到运行一般有下面 3 步:**
-
-
-
-我们需要格外注意的是 .class->机器码 这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT 编译器,而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言。
-
-> HotSpot 采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是 JIT 所需要编译的部分。JVM 会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。JDK 9 引入了一种新的编译模式 AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了 JIT 预热等各方面的开销。JDK 支持分层编译和 AOT 协作使用。但是 ,AOT 编译器的编译质量是肯定比不上 JIT 编译器的。
-
-**总结:**
-
-Java 虚拟机(JVM)是运行 Java 字节码的虚拟机。JVM 有针对不同系统的特定实现(Windows,Linux,macOS),目的是使用相同的字节码,它们都会给出相同的结果。字节码和不同系统的 JVM 实现是 Java 语言“一次编译,随处可以运行”的关键所在。
-
-### 6.2 JDK 和 JRE
-
-JDK 是 Java Development Kit,它是功能齐全的 Java SDK。它拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb)。它能够创建和编译程序。
-
-JRE 是 Java 运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件。但是,它不能用于创建新程序。
-
-如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装 JDK 了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何 Java 开发,仍然需要安装 JDK。例如,如果要使用 JSP 部署 Web 应用程序,那么从技术上讲,您只是在应用程序服务器中运行 Java 程序。那你为什么需要 JDK 呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。
-
-## 七 什么是字节码?采用字节码的最大好处是什么?
-
-**先看下 java 中的编译器和解释器:**
-
-Java 中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象的虚拟的机器。这台虚拟的机器在任何平台上都提供给编译程序一个的共同的接口。编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在 Java 中,这种供虚拟机理解的代码叫做`字节码`(即扩展名为`.class`的文件),它不面向任何特定的处理器,只面向虚拟机。每一种平台的解释器是不同的,但是实现的虚拟机是相同的。Java 源程序经过编译器编译后变成字节码,字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。这也就是解释了 Java 的编译与解释并存的特点。
-
-Java 源代码---->编译器---->jvm 可执行的 Java 字节码(即虚拟指令)---->jvm---->jvm 中解释器----->机器可执行的二进制机器码---->程序运行。
-
-**采用字节码的好处:**
-
-Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同的计算机上运行。
-
-## 八 接口和抽象类的区别是什么?
-
-1. 接口的方法默认是 public,所有方法在接口中不能有实现,抽象类可以有非抽象的方法
-2. 接口中的实例变量默认是 final 类型的,而抽象类中则不一定
-3. 一个类可以实现多个接口,但最多只能实现一个抽象类
-4. 一个类实现接口的话要实现接口的所有方法,而抽象类不一定
-5. 接口不能用 new 实例化,但可以声明,但是必须引用一个实现该接口的对象 从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范。
-
-注意:Java8 后接口可以有默认实现( default )。
-
-## 九 重载和重写的区别
-
-### 重载
-
-发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同。
-
-下面是《Java 核心技术》对重载这个概念的介绍:
-
-
-
-### 重写
-
-重写是子类对父类的允许访问的方法的实现过程进行重新编写,发生在子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。另外,如果父类方法访问修饰符为 private 则子类就不能重写该方法。**也就是说方法提供的行为改变,而方法的外貌并没有改变。**
-
-## 十. Java 面向对象编程三大特性: 封装 继承 多态
-
-### 封装
-
-封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果属性不想被外界访问,我们大可不必提供方法给外界访问。但是如果一个类没有提供给外界访问的方法,那么这个类也没有什么意义了。
-
-### 继承
-
-继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。
-
-**关于继承如下 3 点请记住:**
-
-1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,**只是拥有**。
-2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
-3. 子类可以用自己的方式实现父类的方法。(以后介绍)。
-
-### 多态
-
-所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
-
-在 Java 中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
-
-## 十一. 什么是线程和进程?
-
-### 11.1 何为进程?
-
-进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
-
-在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。
-
-如下图所示,在 windows 中通过查看任务管理器的方式,我们就可以清楚看到 window 当前运行的进程(.exe 文件的运行)。
-
-
-
-### 11.2 何为线程?
-
-线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的**堆**和**方法区**资源,但每个线程有自己的**程序计数器**、**虚拟机栈**和**本地方法栈**,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
-
-Java 程序天生就是多线程程序,我们可以通过 JMX 来看一下一个普通的 Java 程序有哪些线程,代码如下。
-
-```java
-public class MultiThread {
- public static void main(String[] args) {
- // 获取 Java 线程管理 MXBean
- ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
- // 不需要获取同步的 monitor 和 synchronizer 信息,仅获取线程和线程堆栈信息
- ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
- // 遍历线程信息,仅打印线程 ID 和线程名称信息
- for (ThreadInfo threadInfo : threadInfos) {
- System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
- }
- }
-}
-```
-
-上述程序输出如下(输出内容可能不同,不用太纠结下面每个线程的作用,只用知道 main 线程执行 main 方法即可):
-
-```
-[5] Attach Listener //添加事件
-[4] Signal Dispatcher // 分发处理给 JVM 信号的线程
-[3] Finalizer //调用对象 finalize 方法的线程
-[2] Reference Handler //清除 reference 线程
-[1] main //main 线程,程序入口
-```
-
-从上面的输出内容可以看出:**一个 Java 程序的运行是 main 线程和多个其他线程同时运行**。
-
-## 十二. 请简要描述线程与进程的关系,区别及优缺点?
-
-**从 JVM 角度说进程和线程之间的关系**
-
-### 12.1 图解进程和线程的关系
-
-下图是 Java 内存区域,通过下图我们从 JVM 的角度来说一下线程和进程之间的关系。如果你对 Java 内存区域 (运行时数据区) 这部分知识不太了解的话可以阅读一下这篇文章:[《可能是把 Java 内存区域讲的最清楚的一篇文章》](https://github.com/Snailclimb/JavaGuide/blob/3965c02cc0f294b0bd3580df4868d5e396959e2e/Java%E7%9B%B8%E5%85%B3/%E5%8F%AF%E8%83%BD%E6%98%AF%E6%8A%8AJava%E5%86%85%E5%AD%98%E5%8C%BA%E5%9F%9F%E8%AE%B2%E7%9A%84%E6%9C%80%E6%B8%85%E6%A5%9A%E7%9A%84%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0.md "《可能是把 Java 内存区域讲的最清楚的一篇文章》")
-
-
-

-
-
-从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的**堆**和**方法区 (JDK1.8 之后的元空间)**资源,但是每个线程有自己的**程序计数器**、**虚拟机栈** 和 **本地方法栈**。
-
-**总结:** 线程 是 进程 划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反
-
-下面是该知识点的扩展内容!
-
-下面来思考这样一个问题:为什么**程序计数器**、**虚拟机栈**和**本地方法栈**是线程私有的呢?为什么堆和方法区是线程共享的呢?
-
-### 12.2 程序计数器为什么是私有的?
-
-程序计数器主要有下面两个作用:
-
-1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
-2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
-
-需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。
-
-所以,程序计数器私有主要是为了**线程切换后能恢复到正确的执行位置**。
-
-### 12.3 虚拟机栈和本地方法栈为什么是私有的?
-
-- **虚拟机栈:** 每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
-- **本地方法栈:** 和虚拟机栈所发挥的作用非常相似,区别是: **虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。** 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
-
-所以,为了**保证线程中的局部变量不被别的线程访问到**,虚拟机栈和本地方法栈是线程私有的。
-
-### 12.4 一句话简单了解堆和方法区
-
-堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
-
-## 十三. 说说并发与并行的区别?
-
-- **并发:** 同一时间段,多个任务都在执行 (单位时间内不一定同时执行);
-- **并行:** 单位时间内,多个任务同时执行。
-
-## 十四. 什么是上下文切换?
-
-多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
-
-概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。**任务从保存到再加载的过程就是一次上下文切换**。
-
-上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
-
-Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
-
-## 十五. 什么是线程死锁?如何避免死锁?
-
-### 15.1. 认识线程死锁
-
-多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
-
-如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
-
-
-
-下面通过一个例子来说明线程死锁,代码模拟了上图的死锁的情况 (代码来源于《并发编程之美》):
-
-```java
-public class DeadLockDemo {
- private static Object resource1 = new Object();//资源 1
- private static Object resource2 = new Object();//资源 2
-
- public static void main(String[] args) {
- new Thread(() -> {
- synchronized (resource1) {
- System.out.println(Thread.currentThread() + "get resource1");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread() + "waiting get resource2");
- synchronized (resource2) {
- System.out.println(Thread.currentThread() + "get resource2");
- }
- }
- }, "线程 1").start();
-
- new Thread(() -> {
- synchronized (resource2) {
- System.out.println(Thread.currentThread() + "get resource2");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread() + "waiting get resource1");
- synchronized (resource1) {
- System.out.println(Thread.currentThread() + "get resource1");
- }
- }
- }, "线程 2").start();
- }
-}
-```
-
-Output
-
-```
-Thread[线程 1,5,main]get resource1
-Thread[线程 2,5,main]get resource2
-Thread[线程 1,5,main]waiting get resource2
-Thread[线程 2,5,main]waiting get resource1
-```
-
-线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过`Thread.sleep(1000);`让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。上面的例子符合产生死锁的四个必要条件。
-
-学过操作系统的朋友都知道产生死锁必须具备以下四个条件:
-
-1. 互斥条件:该资源任意一个时刻只由一个线程占用。
-2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
-3. 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
-4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
-
-### 15.2 如何避免线程死锁?
-
-我们只要破坏产生死锁的四个条件中的其中一个就可以了。
-
-**破坏互斥条件**
-
-这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
-
-**破坏请求与保持条件**
-
-一次性申请所有的资源。
-
-**破坏不剥夺条件**
-
-占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
-
-**破坏循环等待条件**
-
-靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
-
-我们对线程 2 的代码修改成下面这样就不会产生死锁了。
-
-```java
- new Thread(() -> {
- synchronized (resource1) {
- System.out.println(Thread.currentThread() + "get resource1");
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread() + "waiting get resource2");
- synchronized (resource2) {
- System.out.println(Thread.currentThread() + "get resource2");
- }
- }
- }, "线程 2").start();
-```
-
-Output
-
-```
-Thread[线程 1,5,main]get resource1
-Thread[线程 1,5,main]waiting get resource2
-Thread[线程 1,5,main]get resource2
-Thread[线程 2,5,main]get resource1
-Thread[线程 2,5,main]waiting get resource2
-Thread[线程 2,5,main]get resource2
-
-Process finished with exit code 0
-```
-
-我们分析一下上面的代码为什么避免了死锁的发生?
-
-线程 1 首先获得到 resource1 的监视器锁,这时候线程 2 就获取不到了。然后线程 1 再去获取 resource2 的监视器锁,可以获取到。然后线程 1 释放了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就可以执行了。这样就破坏了破坏循环等待条件,因此避免了死锁。
-
-## 十六. 说说 sleep() 方法和 wait() 方法区别和共同点?
-
-- 两者最主要的区别在于:**sleep 方法没有释放锁,而 wait 方法释放了锁** 。
-- 两者都可以暂停线程的执行。
-- Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
-- wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout)超时后线程会自动苏醒。
-
-## 十七. 为什么我们调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?
-
-这是另一个非常经典的 java 多线程面试问题,而且在面试中会经常被问到。很简单,但是很多人都会答不上来!
-
-new 一个 Thread,线程进入了新建状态;调用 start() 方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
-
-**总结: 调用 start 方法方可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行。**
-
-## 参考
-
-- [https://blog.csdn.net/zhzhao999/article/details/53449504](https://blog.csdn.net/zhzhao999/article/details/53449504 "https://blog.csdn.net/zhzhao999/article/details/53449504")
-- [https://www.cnblogs.com/skywang12345/p/3324958.html](https://www.cnblogs.com/skywang12345/p/3324958.html "https://www.cnblogs.com/skywang12345/p/3324958.html")
-- [https://www.cnblogs.com/Eason-S/p/5524837.html](https://www.cnblogs.com/Eason-S/p/5524837.html "https://www.cnblogs.com/Eason-S/p/5524837.html")
diff --git "a/docs/essential-content-for-interview/PreparingForInterview/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md" "b/docs/essential-content-for-interview/PreparingForInterview/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md"
deleted file mode 100644
index 7feead7..0000000
--- "a/docs/essential-content-for-interview/PreparingForInterview/\347\250\213\345\272\217\345\221\230\347\232\204\347\256\200\345\216\206\344\271\213\351\201\223.md"
+++ /dev/null
@@ -1,121 +0,0 @@
-
-
-- [程序员简历就该这样写](#程序员简历就该这样写)
- - [为什么说简历很重要?](#为什么说简历很重要)
- - [先从面试前来说](#先从面试前来说)
- - [再从面试中来说](#再从面试中来说)
- - [下面这几点你必须知道](#下面这几点你必须知道)
- - [必须了解的两大法则](#必须了解的两大法则)
- - [STAR法则(Situation Task Action Result)](#star法则situation-task-action-result)
- - [FAB 法则(Feature Advantage Benefit)](#fab-法则feature-advantage-benefit)
- - [项目经历怎么写?](#项目经历怎么写)
- - [专业技能该怎么写?](#专业技能该怎么写)
- - [排版注意事项](#排版注意事项)
- - [其他的一些小tips](#其他的一些小tips)
- - [推荐的工具/网站](#推荐的工具网站)
-
-
-
-# 程序员简历就该这样写
-
-本篇文章除了教大家用Markdown如何写一份程序员专属的简历,后面还会给大家推荐一些不错的用来写Markdown简历的软件或者网站,以及如何优雅的将Markdown格式转变为PDF格式或者其他格式。
-
-推荐大家使用Markdown语法写简历,然后再将Markdown格式转换为PDF格式后进行简历投递。
-
-如果你对Markdown语法不太了解的话,可以花半个小时简单看一下Markdown语法说明: http://www.markdown.cn 。
-
-## 为什么说简历很重要?
-
-一份好的简历可以在整个申请面试以及面试过程中起到非常好的作用。 在不夸大自己能力的情况下,写出一份好的简历也是一项很棒的能力。为什么说简历很重要呢?
-
-### 先从面试前来说
-
-- 假如你是网申,你的简历必然会经过HR的筛选,一张简历HR可能也就花费10秒钟看一下,然后HR就会决定你这一关是Fail还是Pass。
-- 假如你是内推,如果你的简历没有什么优势的话,就算是内推你的人再用心,也无能为力。
-
-另外,就算你通过了筛选,后面的面试中,面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。
-
-所以,简历就像是我们的一个门面一样,它在很大程度上决定了你能否进入到下一轮的面试中。
-
-### 再从面试中来说
-
-我发现大家比较喜欢看面经 ,这点无可厚非,但是大部分面经都没告诉你很多问题都是在特定条件下才问的。举个简单的例子:一般情况下你的简历上注明你会的东西才会被问到(Java、数据结构、网络、算法这些基础是每个人必问的),比如写了你会 redis,那面试官就很大概率会问你 redis 的一些问题。比如:redis的常见数据类型及应用场景、redis是单线程为什么还这么快、 redis 和 memcached 的区别、redis 内存淘汰机制等等。
-
-所以,首先,你要明确的一点是:**你不会的东西就不要写在简历上**。另外,**你要考虑你该如何才能让你的亮点在简历中凸显出来**,比如:你在某某项目做了什么事情解决了什么问题(只要有项目就一定有要解决的问题)、你的某一个项目里使用了什么技术后整体性能和并发量提升了很多等等。
-
-面试和工作是两回事,聪明的人会把面试官往自己擅长的领域领,其他人则被面试官牵着鼻子走。虽说面试和工作是两回事,但是你要想要获得自己满意的 offer ,你自身的实力必须要强。
-
-## 下面这几点你必须知道
-
-1. 大部分公司的HR都说我们不看重学历(骗你的!),但是如果你的学校不出众的话,很难在一堆简历中脱颖而出,除非你的简历上有特别的亮点,比如:某某大厂的实习经历、获得了某某大赛的奖等等。
-2. **大部分应届生找工作的硬伤是没有工作经验或实习经历,所以如果你是应届生就不要错过秋招和春招。一旦错过,你后面就极大可能会面临社招,这个时候没有工作经验的你可能就会面临各种碰壁,导致找不到一个好的工作**
-3. **写在简历上的东西一定要慎重,这是面试官大量提问的地方;**
-4. **将自己的项目经历完美的展示出来非常重要。**
-
-## 必须了解的两大法则
-
-### STAR法则(Situation Task Action Result)
-
-- **Situation:** 事情是在什么情况下发生;
-- **Task::** 你是如何明确你的任务的;
-- **Action:** 针对这样的情况分析,你采用了什么行动方式;
-- **Result:** 结果怎样,在这样的情况下你学习到了什么。
-
-简而言之,STAR法则,就是一种讲述自己故事的方式,或者说,是一个清晰、条理的作文模板。不管是什么,合理熟练运用此法则,可以轻松的对面试官描述事物的逻辑方式,表现出自己分析阐述问题的清晰性、条理性和逻辑性。
-
-### FAB 法则(Feature Advantage Benefit)
-
-- **Feature:** 是什么;
-- **Advantage:** 比别人好在哪些地方;
-- **Benefit:** 如果雇佣你,招聘方会得到什么好处。
-
-简单来说,这个法则主要是让你的面试官知道你的优势、招了你之后对公司有什么帮助。
-
-## 项目经历怎么写?
-
-简历上有一两个项目经历很正常,但是真正能把项目经历很好的展示给面试官的非常少。对于项目经历大家可以考虑从如下几点来写:
-
-1. 对项目整体设计的一个感受
-2. 在这个项目中你负责了什么、做了什么、担任了什么角色
-3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用
-4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的又或者说你在这个项目用了什么技术实现了什么功能比如:用redis做缓存提高访问速度和并发量、使用消息队列削峰和降流等等。
-
-## 专业技能该怎么写?
-
-先问一下你自己会什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技术,所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你可以花几天时间学习一下,然后在简历上可以写上自己了解这个技能。比如你可以这样写(下面这部分内容摘自我的简历,大家可以根据自己的情况做一些修改和完善):
-
-- 计算机网络、数据结构、算法、操作系统等课内基础知识:掌握
-- Java 基础知识:掌握
-- JVM 虚拟机(Java内存区域、虚拟机垃圾算法、虚拟垃圾收集器、JVM内存管理):掌握
-- 高并发、高可用、高性能系统开发:掌握
-- Struts2、Spring、Hibernate、Ajax、Mybatis、JQuery :掌握
-- SSH 整合、SSM 整合、 SOA 架构:掌握
-- Dubbo: 掌握
-- Zookeeper: 掌握
-- 常见消息队列: 掌握
-- Linux:掌握
-- MySQL常见优化手段:掌握
-- Spring Boot +Spring Cloud +Docker:了解
-- Hadoop 生态相关技术中的 HDFS、Storm、MapReduce、Hive、Hbase :了解
-- Python 基础、一些常见第三方库比如OpenCV、wxpy、wordcloud、matplotlib:熟悉
-
-## 排版注意事项
-
-1. 尽量简洁,不要太花里胡哨;
-2. 一些技术名词不要弄错了大小写比如MySQL不要写成mysql,Java不要写成java。这个在我看来还是比较忌讳的,所以一定要注意这个细节;
-3. 中文和数字英文之间加上空格的话看起来会舒服一点;
-
-## 其他的一些小tips
-
-1. 尽量避免主观表述,少一点语义模糊的形容词,尽量要简洁明了,逻辑结构清晰。
-2. 如果自己有博客或者个人技术栈点的话,写上去会为你加分很多。
-3. 如果自己的Github比较活跃的话,写上去也会为你加分很多。
-4. 注意简历真实性,一定不要写自己不会的东西,或者带有欺骗性的内容
-5. 项目经历建议以时间倒序排序,另外项目经历不在于多,而在于有亮点。
-6. 如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。
-7. 简历最后最好能加上:“感谢您花时间阅读我的简历,期待能有机会和您共事。”这句话,显的你会很有礼貌。
-
-## 推荐的工具/网站
-
-- 冷熊简历(MarkDown在线简历工具,可在线预览、编辑和生成PDF):
-- Typora+[Java程序员简历模板](https://github.com/geekcompany/ResumeSample/blob/master/java.md)
diff --git "a/docs/essential-content-for-interview/PreparingForInterview/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md" "b/docs/essential-content-for-interview/PreparingForInterview/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md"
deleted file mode 100644
index fcbc75d..0000000
--- "a/docs/essential-content-for-interview/PreparingForInterview/\347\276\216\345\233\242\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\346\200\273\347\273\223.md"
+++ /dev/null
@@ -1,925 +0,0 @@
-
-
-- [一 基础篇](#一-基础篇)
- - [1. `System.out.println(3|9)`输出什么?](#1-systemoutprintln39输出什么)
- - [2. 说一下转发(Forward)和重定向(Redirect)的区别](#2-说一下转发forward和重定向redirect的区别)
- - [3. 在浏览器中输入 url 地址到显示主页的过程,整个过程会使用哪些协议](#3-在浏览器中输入-url-地址到显示主页的过程整个过程会使用哪些协议)
- - [4. TCP 三次握手和四次挥手](#4-tcp-三次握手和四次挥手)
- - [为什么要三次握手](#为什么要三次握手)
- - [为什么要传回 SYN](#为什么要传回-syn)
- - [传了 SYN,为啥还要传 ACK](#传了-syn为啥还要传-ack)
- - [为什么要四次挥手](#为什么要四次挥手)
- - [5. IP 地址与 MAC 地址的区别](#5-ip-地址与-mac-地址的区别)
- - [6. HTTP 请求,响应报文格式](#6-http-请求响应报文格式)
- - [7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql 索引主要使用的两种数据结构?什么是覆盖索引?](#7-为什么要使用索引索引这么多优点为什么不对表中的每一个列创建一个索引呢索引是如何提高查询速度的说一下使用索引的注意事项mysql-索引主要使用的两种数据结构什么是覆盖索引)
- - [8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不?](#8-进程与线程的区别是什么进程间的几种通信方式说一下线程间的几种通信方式知道不)
- - [9. 为什么要用单例模式?手写几种线程安全的单例模式?](#9-为什么要用单例模式手写几种线程安全的单例模式)
- - [10. 简单介绍一下 bean;知道 Spring 的 bean 的作用域与生命周期吗?](#10-简单介绍一下-bean知道-spring-的-bean-的作用域与生命周期吗)
- - [11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?](#11-spring-中的事务传播行为了解吗transactiondefinition-接口中哪五个表示隔离级别的常量)
- - [事务传播行为](#事务传播行为)
- - [隔离级别](#隔离级别)
- - [12. SpringMVC 原理了解吗?](#12-springmvc-原理了解吗)
- - [13. Spring AOP IOC 实现原理](#13-spring-aop-ioc-实现原理)
-- [二 进阶篇](#二-进阶篇)
- - [1 消息队列 MQ 的套路](#1-消息队列-mq-的套路)
- - [1.1 介绍一下消息队列 MQ 的应用场景/使用消息队列的好处](#11-介绍一下消息队列-mq-的应用场景使用消息队列的好处)
- - [1)通过异步处理提高系统性能](#1通过异步处理提高系统性能)
- - [2)降低系统耦合性](#2降低系统耦合性)
- - [1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗?](#12-那么使用消息队列会带来什么问题考虑过这些问题吗)
- - [1.3 介绍一下你知道哪几种消息队列,该如何选择呢?](#13-介绍一下你知道哪几种消息队列该如何选择呢)
- - [1.4 关于消息队列其他一些常见的问题展望](#14-关于消息队列其他一些常见的问题展望)
- - [2 谈谈 InnoDB 和 MyIsam 两者的区别](#2-谈谈-innodb-和-myisam-两者的区别)
- - [2.1 两者的对比](#21-两者的对比)
- - [2.2 关于两者的总结](#22-关于两者的总结)
- - [3 聊聊 Java 中的集合吧!](#3-聊聊-java-中的集合吧)
- - [3.1 Arraylist 与 LinkedList 有什么不同?(注意加上从数据结构分析的内容)](#31-arraylist-与-linkedlist-有什么不同注意加上从数据结构分析的内容)
- - [3.2 HashMap 的底层实现](#32-hashmap-的底层实现)
- - [1)JDK1.8 之前](#1jdk18-之前)
- - [2)JDK1.8 之后](#2jdk18-之后)
- - [3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解](#33-既然谈到了红黑树你给我手绘一个出来吧然后简单讲一下自己对于红黑树的理解)
- - [3.4 红黑树这么优秀,为何不直接使用红黑树得了?](#34-红黑树这么优秀为何不直接使用红黑树得了)
- - [3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别](#35-hashmap-和-hashtable-的区别hashset-和-hashmap-区别)
-- [三 终结篇](#三-终结篇)
- - [1. Object 类有哪些方法?](#1-object-类有哪些方法)
- - [1.1 Object 类的常见方法总结](#11-object-类的常见方法总结)
- - [1.2 hashCode 与 equals](#12-hashcode-与-equals)
- - [1.2.1 hashCode()介绍](#121-hashcode介绍)
- - [1.2.2 为什么要有 hashCode](#122-为什么要有-hashcode)
- - [1.2.3 hashCode()与 equals()的相关规定](#123-hashcode与-equals的相关规定)
- - [1.2.4 为什么两个对象有相同的 hashcode 值,它们也不一定是相等的?](#124-为什么两个对象有相同的-hashcode-值它们也不一定是相等的)
- - [1.3 ==与 equals](#13-与-equals)
- - [2 ConcurrentHashMap 相关问题](#2-concurrenthashmap-相关问题)
- - [2.1 ConcurrentHashMap 和 Hashtable 的区别](#21-concurrenthashmap-和-hashtable-的区别)
- - [2.2 ConcurrentHashMap 线程安全的具体实现方式/底层具体实现](#22-concurrenthashmap-线程安全的具体实现方式底层具体实现)
- - [JDK1.7(上面有示意图)](#jdk17上面有示意图)
- - [JDK1.8(上面有示意图)](#jdk18上面有示意图)
- - [3 谈谈 synchronized 和 ReentrantLock 的区别](#3-谈谈-synchronized-和-reentrantlock-的区别)
- - [4 线程池了解吗?](#4-线程池了解吗)
- - [4.1 为什么要用线程池?](#41-为什么要用线程池)
- - [4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么?](#42-java-提供了哪几种线程池他们各自的使用场景是什么)
- - [Java 主要提供了下面 4 种线程池](#java-主要提供了下面-4-种线程池)
- - [各种线程池的适用场景介绍](#各种线程池的适用场景介绍)
- - [4.3 创建的线程池的方式](#43-创建的线程池的方式)
- - [5 Nginx](#5-nginx)
- - [5.1 简单介绍一下 Nginx](#51-简单介绍一下-nginx)
- - [反向代理](#反向代理)
- - [负载均衡](#负载均衡)
- - [动静分离](#动静分离)
- - [5.2 为什么要用 Nginx?](#52-为什么要用-nginx)
- - [5.3 Nginx 的四个主要组成部分了解吗?](#53-nginx-的四个主要组成部分了解吗)
-
-
-
-这些问题是 2018 年去美团面试的同学被问到的一些常见的问题,希望对你有帮助!
-
-# 一 基础篇
-
-## 1. `System.out.println(3|9)`输出什么?
-
-正确答案:11。
-
-**考察知识点:&和&&;|和||**
-
-**&和&&:**
-
-共同点:两者都可做逻辑运算符。它们都表示运算符的两边都是 true 时,结果为 true;
-
-不同点: &也是位运算符。& 表示在运算时两边都会计算,然后再判断;&&表示先运算符号左边的东西,然后判断是否为 true,是 true 就继续运算右边的然后判断并输出,是 false 就停下来直接输出不会再运行后面的东西。
-
-**|和||:**
-
-共同点:两者都可做逻辑运算符。它们都表示运算符的两边任意一边为 true,结果为 true,两边都不是 true,结果就为 false;
-
-不同点:|也是位运算符。| 表示两边都会运算,然后再判断结果;|| 表示先运算符号左边的东西,然后判断是否为 true,是 true 就停下来直接输出不会再运行后面的东西,是 false 就继续运算右边的然后判断并输出。
-
-**回到本题:**
-
-3 | 9=0011(二进制) | 1001(二进制)=1011(二进制)=11(十进制)
-
-## 2. 说一下转发(Forward)和重定向(Redirect)的区别
-
-**转发是服务器行为,重定向是客户端行为。**
-
-**转发(Forword)** 通过 RequestDispatcher 对象的`forward(HttpServletRequest request,HttpServletResponse response)`方法实现的。`RequestDispatcher` 可以通过`HttpServletRequest` 的 `getRequestDispatcher()`方法获得。例如下面的代码就是跳转到 login_success.jsp 页面。
-
-```java
-request.getRequestDispatcher("login_success.jsp").forward(request, response);
-```
-
-**重定向(Redirect)** 是利用服务器返回的状态码来实现的。客户端浏览器请求服务器的时候,服务器会返回一个状态码。服务器通过 HttpServletRequestResponse 的 setStatus(int status)方法设置状态码。如果服务器返回 301 或者 302,则浏览器会到新的网址重新请求该资源。
-
-1. **从地址栏显示来说**:forward 是服务器请求资源,服务器直接访问目标地址的 URL,把那个 URL 的响应内容读取过来,然后把这些内容再发给浏览器。浏览器根本不知道服务器发送的内容从哪里来的,所以它的地址栏还是原来的地址。redirect 是服务端根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址。所以地址栏显示的是新的 URL。
-2. **从数据共享来说**:forward:转发页面和转发到的页面可以共享 request 里面的数据。redirect:不能共享数据。
-3. **从运用地方来说**:forward:一般用于用户登陆的时候,根据角色转发到相应的模块。redirect:一般用于用户注销登陆时返回主页面和跳转到其它的网站等。
-4. **从效率来说**:forward:高。redirect:低。
-
-## 3. 在浏览器中输入 url 地址到显示主页的过程,整个过程会使用哪些协议
-
-图片来源:《图解 HTTP》:
-
-
-
-总体来说分为以下几个过程:
-
-1. DNS 解析
-2. TCP 连接
-3. 发送 HTTP 请求
-4. 服务器处理请求并返回 HTTP 报文
-5. 浏览器解析渲染页面
-6. 连接结束
-
-具体可以参考下面这篇文章:
-
-- [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700 "https://segmentfault.com/a/1190000006879700")
-
-> 修正 [issue-568](https://github.com/Snailclimb/JavaGuide/issues/568 "issue-568"):上图中 IP 数据包在路由器之间使用的协议为 OPSF 协议错误,应该为 OSPF 协议 。
->
-> IP 数据包在路由器之间传播大致分为 IGP 和 BGP 协议,而 IGP 目前主流为 OSPF 协议,思科,华为和 H3C 等主流厂商都有各自实现并使用;BGP 协议为不同 AS(自治系统号)间路由传输,也分为 I-BGP 和 E-BGP,详细资料请查看《TCP/IP 卷一》
-
-## 4. TCP 三次握手和四次挥手
-
-为了准确无误地把数据送达目标处,TCP 协议采用了三次握手策略。
-
-**漫画图解:**
-
-图片来源:《图解 HTTP》
-
-
-**简单示意图:**
-
-
-- 客户端–发送带有 SYN 标志的数据包–一次握手–服务端
-- 服务端–发送带有 SYN/ACK 标志的数据包–二次握手–客户端
-- 客户端–发送带有带有 ACK 标志的数据包–三次握手–服务端
-
-#### 为什么要三次握手
-
-**三次握手的目的是建立可靠的通信信道,说到通讯,简单来说就是数据的发送与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是正常的。**
-
-第一次握手:Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常。
-
-第二次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己接收正常,对方发送正常
-
-第三次握手:Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常
-
-所以三次握手就能确认双发收发功能都正常,缺一不可。
-
-#### 为什么要传回 SYN
-
-接收端传回发送端所发送的 SYN 是为了告诉发送端,我接收到的信息确实就是你所发送的信号了。
-
-> SYN 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务器使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement[汉译:确认字符 ,在数据通信传输中,接收站发给发送站的一种传输控制字符。它表示确认发来的数据已经接受无误。 ])消息响应。这样在客户机和服务器之间才能建立起可靠的 TCP 连接,数据才可以在客户机和服务器之间传递。
-
-#### 传了 SYN,为啥还要传 ACK
-
-双方通信无误必须是两者互相发送信息都无误。传了 SYN,证明发送方(主动关闭方)到接收方(被动关闭方)的通道没有问题,但是接收方到发送方的通道还需要 ACK 信号来进行验证。
-
-断开一个 TCP 连接则需要“四次挥手”:
-
-- 客户端-发送一个 FIN,用来关闭客户端到服务器的数据传送
-- 服务器-收到这个 FIN,它发回一 个 ACK,确认序号为收到的序号加 1 。和 SYN 一样,一个 FIN 将占用一个序号
-- 服务器-关闭与客户端的连接,发送一个 FIN 给客户端
-- 客户端-发回 ACK 报文确认,并将确认序号设置为收到序号加 1
-
-#### 为什么要四次挥手
-
-任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了 TCP 连接。
-
-举个例子:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B 回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。
-
-上面讲的比较概括,推荐一篇讲的比较细致的文章:[https://blog.csdn.net/qzcsu/article/details/72861891](https://blog.csdn.net/qzcsu/article/details/72861891 "https://blog.csdn.net/qzcsu/article/details/72861891")
-
-## 5. IP 地址与 MAC 地址的区别
-
-参考:[https://blog.csdn.net/guoweimelon/article/details/50858597](https://blog.csdn.net/guoweimelon/article/details/50858597 "https://blog.csdn.net/guoweimelon/article/details/50858597")
-
-IP 地址是指互联网协议地址(Internet Protocol Address)IP Address 的缩写。IP 地址是 IP 协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
-
-MAC 地址又称为物理地址、硬件地址,用来定义网络设备的位置。网卡的物理地址通常是由网卡生产厂家写入网卡的,具有全球唯一性。MAC 地址用于在网络中唯一标示一个网卡,一台电脑会有一或多个网卡,每个网卡都需要有一个唯一的 MAC 地址。
-
-## 6. HTTP 请求,响应报文格式
-
-HTTP 请求报文主要由请求行、请求头部、请求正文 3 部分组成
-
-HTTP 响应报文主要由状态行、响应头部、响应正文 3 部分组成
-
-详细内容可以参考:[https://blog.csdn.net/a19881029/article/details/14002273](https://blog.csdn.net/a19881029/article/details/14002273 "https://blog.csdn.net/a19881029/article/details/14002273")
-
-## 7. 为什么要使用索引?索引这么多优点,为什么不对表中的每一个列创建一个索引呢?索引是如何提高查询速度的?说一下使用索引的注意事项?Mysql 索引主要使用的两种数据结构?什么是覆盖索引?
-
-**为什么要使用索引?**
-
-1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
-2. 可以大大加快 数据的检索速度(大大减少的检索的数据量), 这也是创建索引的最主要的原因。
-3. 帮助服务器避免排序和临时表
-4. 将随机 IO 变为顺序 IO
-5. 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
-
-**索引这么多优点,为什么不对表中的每一个列创建一个索引呢?**
-
-1. 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
-2. 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
-3. 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
-
-**索引是如何提高查询速度的?**
-
-将无序的数据变成相对有序的数据(就像查目录一样)
-
-**说一下使用索引的注意事项**
-
-1. 避免 where 子句中对字段施加函数,这会造成无法命中索引。
-2. 在使用 InnoDB 时使用与业务无关的自增主键作为主键,即使用逻辑主键,而不要使用业务主键。
-3. 将打算加索引的列设置为 NOT NULL ,否则将导致引擎放弃使用索引而进行全表扫描
-4. 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗 MySQL 5.7 可以通过查询 sys 库的 schema_unused_indexes 视图来查询哪些索引从未被使用
-5. 在使用 limit offset 查询缓慢时,可以借助索引来提高性能
-
-**Mysql 索引主要使用的哪两种数据结构?**
-
-- 哈希索引:对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择 BTree 索引。
-- BTree 索引:Mysql 的 BTree 索引使用的是 B 树中的 B+Tree。但对于主要的两种存储引擎(MyISAM 和 InnoDB)的实现方式是不同的。
-
-更多关于索引的内容可以查看我的这篇文章:[【思维导图-索引篇】搞定数据库索引就是这么简单](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484486&idx=1&sn=215450f11e042bca8a58eac9f4a97686&chksm=fd985227caefdb3117b8375f150676f5824aa20d1ebfdbcfb93ff06e23e26efbafae6cf6b48e&token=1990180468&lang=zh_CN#rd)
-
-**什么是覆盖索引?**
-
-如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称
-之为“覆盖索引”。我们知道在 InnoDB 存储引擎中,如果不是主键索引,叶子节点存储的是主键+列值。最终还是要“回表”,也就是要通过主键再查找一次,这样就会比较慢。覆盖索引就是把要查询出的列和索引是对应的,不做回表操作!
-
-## 8. 进程与线程的区别是什么?进程间的几种通信方式说一下?线程间的几种通信方式知道不?
-
-**进程与线程的区别是什么?**
-
-线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。另外,也正是因为共享资源,所以线程中执行时一般都要进行同步和互斥。总的来说,进程和线程的主要差别在于它们是不同的操作系统资源管理方式。
-
-**进程间的几种通信方式说一下?**
-
-1. **管道(pipe)**:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有血缘关系的进程间使用。进程的血缘关系通常指父子进程关系。管道分为 pipe(无名管道)和 fifo(命名管道)两种,有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间通信。
-2. **信号量(semophore)**:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
-3. **消息队列(message queue)**:消息队列是由消息组成的链表,存放在内核中 并由消息队列标识符标识。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。消息队列与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。
-4. **信号(signal)**:信号是一种比较复杂的通信方式,用于通知接收进程某一事件已经发生。
-5. **共享内存(shared memory)**:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问,共享内存是最快的 IPC 方式,它是针对其他进程间的通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。
-6. **套接字(socket)**:socket,即套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。也因为这样,套接字明确地将客户端和服务器区分开来。
-
-**线程间的几种通信方式知道不?**
-
-1、锁机制
-
-- 互斥锁:提供了以排它方式阻止数据结构被并发修改的方法。
-- 读写锁:允许多个线程同时读共享数据,而对写操作互斥。
-- 条件变量:可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
-
-2、信号量机制:包括无名线程信号量与有名线程信号量
-
-3、信号机制:类似于进程间的信号处理。
-
-线程间通信的主要目的是用于线程同步,所以线程没有象进程通信中用于数据交换的通信机制。
-
-## 9. 为什么要用单例模式?手写几种线程安全的单例模式?
-
-**简单来说使用单例模式可以带来下面几个好处:**
-
-- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
-- 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间。
-
-**懒汉式(双重检查加锁版本)**
-
-```java
-public class Singleton {
-
- //volatile保证,当uniqueInstance变量被初始化成Singleton实例时,多个线程可以正确处理uniqueInstance变量
- private volatile static Singleton uniqueInstance;
- private Singleton() {
- }
- public static Singleton getInstance() {
- //检查实例,如果不存在,就进入同步代码块
- if (uniqueInstance == null) {
- //只有第一次才彻底执行这里的代码
- synchronized(Singleton.class) {
- //进入同步代码块后,再检查一次,如果仍是null,才创建实例
- if (uniqueInstance == null) {
- uniqueInstance = new Singleton();
- }
- }
- }
- return uniqueInstance;
- }
-}
-```
-
-**静态内部类方式**
-
-静态内部实现的单例是懒加载的且线程安全。
-
-只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance(只有第一次使用这个单例的实例的时候才加载,同时不会有线程安全问题)。
-
-```java
-public class Singleton {
- private static class SingletonHolder {
- private static final Singleton INSTANCE = new Singleton();
- }
- private Singleton (){}
- public static final Singleton getInstance() {
- return SingletonHolder.INSTANCE;
- }
-}
-```
-
-## 10. 简单介绍一下 bean;知道 Spring 的 bean 的作用域与生命周期吗?
-
-在 Spring 中,那些组成应用程序的主体及由 Spring IOC 容器所管理的对象,被称之为 bean。简单地讲,bean 就是由 IOC 容器初始化、装配及管理的对象,除此之外,bean 就与应用程序中的其他对象没有什么区别了。而 bean 的定义以及 bean 相互间的依赖关系将通过配置元数据来描述。
-
-Spring 中的 bean 默认都是单例的,这些单例 Bean 在多线程程序下如何保证线程安全呢? 例如对于 Web 应用来说,Web 容器对于每个用户请求都创建一个单独的 Sevlet 线程来处理请求,引入 Spring 框架之后,每个 Action 都是单例的,那么对于 Spring 托管的单例 Service Bean,如何保证其安全呢? Spring 的单例是基于 BeanFactory 也就是 Spring 容器的,单例 Bean 在此容器内只有一个,Java 的单例是基于 JVM,每个 JVM 内只有一个实例。
-
-
-
-Spring 的 bean 的生命周期以及更多内容可以查看:[一文轻松搞懂 Spring 中 bean 的作用域与生命周期](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484400&idx=2&sn=7201eb365102fce017f89cb3527fb0bc&chksm=fd985591caefdc872a2fac897288119f94c345e4e12150774f960bf5f816b79e4b9b46be3d7f&token=1990180468&lang=zh_CN#rd)
-
-## 11. Spring 中的事务传播行为了解吗?TransactionDefinition 接口中哪五个表示隔离级别的常量?
-
-#### 事务传播行为
-
-事务传播行为(为了解决业务层方法之间互相调用的事务问题):
-当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。在 TransactionDefinition 定义中包括了如下几个表示传播行为的常量:
-
-**支持当前事务的情况:**
-
-- TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
-- TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
-- TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
-
-**不支持当前事务的情况:**
-
-- TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
-- TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
-- TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
-
-**其他情况:**
-
-- TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED。
-
-#### 隔离级别
-
-TransactionDefinition 接口中定义了五个表示隔离级别的常量:
-
-- **TransactionDefinition.ISOLATION_DEFAULT:** 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ 隔离级别 Oracle 默认采用的 READ_COMMITTED 隔离级别.
-- **TransactionDefinition.ISOLATION_READ_UNCOMMITTED:** 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
-- **TransactionDefinition.ISOLATION_READ_COMMITTED:** 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
-- **TransactionDefinition.ISOLATION_REPEATABLE_READ:** 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
-- **TransactionDefinition.ISOLATION_SERIALIZABLE:** 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
-
-## 12. SpringMVC 原理了解吗?
-
-
-
-客户端发送请求-> 前端控制器 DispatcherServlet 接受客户端请求 -> 找到处理器映射 HandlerMapping 解析请求对应的 Handler-> HandlerAdapter 会根据 Handler 来调用真正的处理器处理请求,并处理相应的业务逻辑 -> 处理器返回一个模型视图 ModelAndView -> 视图解析器进行解析 -> 返回一个视图对象->前端控制器 DispatcherServlet 渲染数据(Model)->将得到视图对象返回给用户
-
-关于 SpringMVC 原理更多内容可以查看我的这篇文章:[SpringMVC 工作原理详解](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484496&idx=1&sn=5472ffa687fe4a05f8900d8ee6726de4&chksm=fd985231caefdb27fc75b44ecf76b6f43e4617e0b01b3c040f8b8fab32e51dfa5118eed1d6ad&token=1990180468&lang=zh_CN#rd)
-
-## 13. Spring AOP IOC 实现原理
-
-过了秋招挺长一段时间了,说实话我自己也忘了如何简要概括 Spring AOP IOC 实现原理,就在网上找了一个较为简洁的答案,下面分享给各位。
-
-**IOC:** 控制反转也叫依赖注入。IOC 利用 java 反射机制,AOP 利用代理模式。IOC 概念看似很抽象,但是很容易理解。说简单点就是将对象交给容器管理,你只需要在 spring 配置文件中配置对应的 bean 以及设置相关的属性,让 spring 容器来生成类的实例对象以及管理对象。在 spring 容器启动的时候,spring 会把你在配置文件中配置的 bean 都初始化好,然后在你需要调用的时候,就把它已经初始化好的那些 bean 分配给你需要调用这些 bean 的类。
-
-**AOP:** 面向切面编程。(Aspect-Oriented Programming) 。AOP 可以说是对 OOP 的补充和完善。OOP 引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。实现 AOP 的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码,属于静态代理。
-
-# 二 进阶篇
-
-## 1 消息队列 MQ 的套路
-
-消息队列/消息中间件应该是 Java 程序员必备的一个技能了,如果你之前没接触过消息队列的话,建议先去百度一下某某消息队列入门,然后花 2 个小时就差不多可以学会任何一种消息队列的使用了。如果说仅仅学会使用是万万不够的,在实际生产环境还要考虑消息丢失等等情况。关于消息队列面试相关的问题,推荐大家也可以看一下视频《Java 工程师面试突击第 1 季-中华石杉老师》,如果大家没有资源的话,可以在我的公众号“Java 面试通关手册”后台回复关键字“1”即可!
-
-### 1.1 介绍一下消息队列 MQ 的应用场景/使用消息队列的好处
-
-面试官一般会先问你这个问题,预热一下,看你知道消息队列不,一般在第一面的时候面试官可能只会问消息队列 MQ 的应用场景/使用消息队列的好处、使用消息队列会带来什么问题、消息队列的技术选型这几个问题,不会太深究下去,在后面的第二轮/第三轮技术面试中可能会深入问一下。
-
-**《大型网站技术架构》第四章和第七章均有提到消息队列对应用性能及扩展性的提升。**
-
-#### 1)通过异步处理提高系统性能
-
-
-如上图,**在不使用消息队列服务器的时候,用户的请求数据直接写入数据库,在高并发的情况下数据库压力剧增,使得响应速度变慢。但是在使用消息队列之后,用户的请求数据发送给消息队列之后立即 返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库。由于消息队列服务器处理速度快于数据库(消息队列也比数据库有更好的伸缩性),因此响应速度得到大幅改善。**
-
-通过以上分析我们可以得出**消息队列具有很好的削峰作用的功能**——即**通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。** 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示:
-
-因为**用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败**。因此使用消息队列进行异步处理之后,需要**适当修改业务流程进行配合**,比如**用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功**,以免交易纠纷。这就类似我们平时手机订火车票和电影票。
-
-#### 2)降低系统耦合性
-
-我们知道模块分布式部署以后聚合方式通常有两种:1.**分布式消息队列**和 2.**分布式服务**。
-
-> **先来简单说一下分布式服务:**
-
-目前使用比较多的用来构建**SOA(Service Oriented Architecture 面向服务体系结构)**的**分布式服务框架**是阿里巴巴开源的**Dubbo**。如果想深入了解 Dubbo 的可以看我写的关于 Dubbo 的这一篇文章:**《高性能优秀的服务框架-dubbo 介绍》**:[https://juejin.im/post/5acadeb1f265da2375072f9c](https://juejin.im/post/5acadeb1f265da2375072f9c "https://juejin.im/post/5acadeb1f265da2375072f9c")
-
-> **再来谈我们的分布式消息队列:**
-
-我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。
-
-我们最常见的**事件驱动架构**类似生产者消费者模式,在大型网站中通常用利用消息队列实现事件驱动结构。如下图所示:
-
-
-**消息队列使利用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。** 从上图可以看到**消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合**,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。**对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计**。
-
-消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。
-
-**另外为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。**
-
-**备注:** 不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的,**比如在我们的 ActiveMQ 消息队列中还有点对点工作模式**,具体的会在后面的文章给大家详细介绍,这一篇文章主要还是让大家对消息队列有一个更透彻的了解。
-
-> 这个问题一般会在上一个问题问完之后,紧接着被问到。“使用消息队列会带来什么问题?”这个问题要引起重视,一般我们都会考虑使用消息队列会带来的好处而忽略它带来的问题!
-
-### 1.2 那么使用消息队列会带来什么问题?考虑过这些问题吗?
-
-- **系统可用性降低:** 系统可用性在某种程度上降低,为什么这样说呢?在加入 MQ 之前,你不用考虑消息丢失或者说 MQ 挂掉等等的情况,但是,引入 MQ 之后你就需要去考虑了!
-- **系统复杂性提高:** 加入 MQ 之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!
-- **一致性问题:** 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!
-
-> 了解下面这个问题是为了我们更好的进行技术选型!该部分摘自:《Java 工程师面试突击第 1 季-中华石杉老师》,如果大家没有资源的话,可以在我的公众号“Java 面试通关手册”后台回复关键字“1”即可!
-
-### 1.3 介绍一下你知道哪几种消息队列,该如何选择呢?
-
-| 特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
-| :----------------------- | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: | -----------------------------------------------------------: |
-| 单机吞吐量 | 万级,吞吐量比 RocketMQ 和 Kafka 要低了一个数量级 | 万级,吞吐量比 RocketMQ 和 Kafka 要低了一个数量级 | 10 万级,RocketMQ 也是可以支撑高吞吐的一种 MQ | 10 万级别,这是 kafka 最大的优点,就是吞吐量高。一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |
-| topic 数量对吞吐量的影响 | | | topic 可以达到几百,几千个的级别,吞吐量会有较小幅度的下降这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic | topic 从几十个到几百个的时候,吞吐量会大幅度下降。所以在同等机器下,kafka 尽量保证 topic 数量不要过多。如果要支撑大规模 topic,需要增加更多的机器资源 |
-| 可用性 | 高,基于主从架构实现高可用性 | 高,基于主从架构实现高可用性 | 非常高,分布式架构 | 非常高,kafka 是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
-| 消息可靠性 | 有较低的概率丢失数据 | | 经过参数优化配置,可以做到 0 丢失 | 经过参数优化配置,消息可以做到 0 丢失 |
-| 时效性 | ms 级 | 微秒级,这是 rabbitmq 的一大特点,延迟是最低的 | ms 级 | 延迟在 ms 级以内 |
-| 功能支持 | MQ 领域的功能极其完备 | 基于 erlang 开发,所以并发能力很强,性能极其好,延时很低 | MQ 功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准 |
-| 优劣势总结 | 非常成熟,功能强大,在业内大量的公司以及项目中都有应用。偶尔会有较低概率丢失消息,而且现在社区以及国内应用都越来越少,官方社区现在对 ActiveMQ 5.x 维护越来越少,几个月才发布一个版本而且确实主要是基于解耦和异步来用的,较少在大规模吞吐的场景中使用 | erlang 语言开发,性能极其好,延时很低;吞吐量到万级,MQ 功能比较完备而且开源提供的管理界面非常棒,用起来很好用。社区相对比较活跃,几乎每个月都发布几个版本分在国内一些互联网公司近几年用 rabbitmq 也比较多一些但是问题也是显而易见的,RabbitMQ 确实吞吐量会低一些,这是因为他做的实现机制比较重。而且 erlang 开发,国内有几个公司有实力做 erlang 源码级别的研究和定制?如果说你没这个实力的话,确实偶尔会有一些问题,你很难去看懂源码,你公司对这个东西的掌控很弱,基本职能依赖于开源社区的快速维护和修复 bug。而且 rabbitmq 集群动态扩展会很麻烦,不过这个我觉得还好。其实主要是 erlang 语言本身带来的问题。很难读源码,很难定制和掌控。 | 接口简单易用,而且毕竟在阿里大规模应用过,有阿里品牌保障。日处理消息上百亿之多,可以做到大规模吞吐,性能也非常好,分布式扩展也很方便,社区维护还可以,可靠性和可用性都是 ok 的,还可以支撑大规模的 topic 数量,支持复杂 MQ 业务场景。而且一个很大的优势在于,阿里出品都是 java 系的,我们可以自己阅读源码,定制自己公司的 MQ,可以掌控。社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准 JMS 规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用 RocketMQ 挺好的 | kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。而且 kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。 |
-
-> 这部分内容,我这里不给出答案,大家可以自行根据自己学习的消息队列查阅相关内容,我可能会在后面的文章中介绍到这部分内容。另外,下面这些问题在视频《Java 工程师面试突击第 1 季-中华石杉老师》中都有提到,如果大家没有资源的话,可以在我的公众号“Java 面试通关手册”后台回复关键字“1”即可!
-
-### 1.4 关于消息队列其他一些常见的问题展望
-
-1. 引入消息队列之后如何保证高可用性?
-2. 如何保证消息不被重复消费呢?
-3. 如何保证消息的可靠性传输(如何处理消息丢失的问题)?
-4. 我该怎么保证从消息队列里拿到的数据按顺序执行?
-5. 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?
-6. 如果让你来开发一个消息队列中间件,你会怎么设计架构?
-
-## 2 谈谈 InnoDB 和 MyIsam 两者的区别
-
-### 2.1 两者的对比
-
-1. **count 运算上的区别:** 因为 MyISAM 缓存有表 meta-data(行数等),因此在做 COUNT(\*)时对于一个结构很好的查询是不需要消耗多少资源的。而对于 InnoDB 来说,则没有这种缓存
-2. **是否支持事务和崩溃后的安全恢复:** MyISAM 强调的是性能,每次查询具有原子性,其执行速度比 InnoDB 类型更快,但是不提供事务支持。但是 InnoDB 提供事务支持,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
-3. **是否支持外键:** MyISAM 不支持,而 InnoDB 支持。
-
-### 2.2 关于两者的总结
-
-MyISAM 更适合读密集的表,而 InnoDB 更适合写密集的表。 在数据库做主从分离的情况下,经常选择 MyISAM 作为主库的存储引擎。
-
-一般来说,如果需要事务支持,并且有较高的并发读取频率(MyISAM 的表锁的粒度太大,所以当该表写并发量较高时,要等待的查询就会很多了),InnoDB 是不错的选择。如果你的数据量很大(MyISAM 支持压缩特性可以减少磁盘的空间占用),而且不需要支持事务时,MyISAM 是最好的选择。
-
-## 3 聊聊 Java 中的集合吧!
-
-### 3.1 Arraylist 与 LinkedList 有什么不同?(注意加上从数据结构分析的内容)
-
-- **1. 是否保证线程安全:** ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
-- **2. 底层数据结构:** Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是双向链表数据结构(注意双向链表和双向循环链表的区别:);
-- **3. 插入和删除是否受元素位置的影响:** ① **ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。** 比如:执行`add(E e)`方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(`add(int index, E element)`)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② **LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1) 而数组为近似 O(n) 。**
-- **4. 是否支持快速随机访问:** LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于`get(int index)`方法)。
-- **5. 内存空间占用:** ArrayList 的空 间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。
-
-**补充内容:RandomAccess 接口**
-
-```java
-public interface RandomAccess {
-}
-```
-
-查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。
-
-在 binarySearch() 方法中,它要判断传入的 list 是否 RamdomAccess 的实例,如果是,调用 indexedBinarySearch() 方法,如果不是,那么调用 iteratorBinarySearch() 方法
-
-```java
- public static
- int binarySearch(List extends Comparable super T>> list, T key) {
- if (list instanceof RandomAccess || list.size() Java 中的集合这类问题几乎是面试必问的,问到这类问题的时候,HashMap 又是几乎必问的问题,所以大家一定要引起重视!
-
-### 3.2 HashMap 的底层实现
-
-#### 1)JDK1.8 之前
-
-JDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的时数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。**
-
-**所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。**
-
-**JDK 1.8 HashMap 的 hash 方法源码:**
-
-JDK 1.8 的 hash 方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。
-
-```java
- static final int hash(Object key) {
- int h;
- // key.hashCode():返回散列值也就是hashcode
- // ^ :按位异或
- // >>>:无符号右移,忽略符号位,空位都以0补齐
- return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
- }
-```
-
-对比一下 JDK1.7 的 HashMap 的 hash 方法源码.
-
-```java
-static int hash(int h) {
- // This function ensures that hashCodes that differ only by
- // constant multiples at each bit position have a bounded
- // number of collisions (approximately 8 at default load factor).
-
- h ^= (h >>> 20) ^ (h >>> 12);
- return h ^ (h >>> 7) ^ (h >>> 4);
-}
-```
-
-相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。
-
-所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。
-
-
-
-#### 2)JDK1.8 之后
-
-相比于之前的版本, JDK1.8 之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间。
-
-
-
-TreeMap、TreeSet 以及 JDK1.8 之后的 HashMap 底层都用到了红黑树。红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。
-
-> 问完 HashMap 的底层原理之后,面试官可能就会紧接着问你 HashMap 底层数据结构相关的问题!
-
-### 3.3 既然谈到了红黑树,你给我手绘一个出来吧,然后简单讲一下自己对于红黑树的理解
-
-
-
-**红黑树特点:**
-
-1. 每个节点非红即黑;
-2. 根节点总是黑色的;
-3. 每个叶子节点都是黑色的空节点(NIL 节点);
-4. 如果节点是红色的,则它的子节点必须是黑色的(反之不一定);
-5. 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)
-
-**红黑树的应用:**
-
-TreeMap、TreeSet 以及 JDK1.8 之后的 HashMap 底层都用到了红黑树。
-
-**为什么要用红黑树**
-
-简单来说红黑树就是为了解决二叉查找树的缺陷,因为二叉查找树在某些情况下会退化成一个线性结构。
-
-### 3.4 红黑树这么优秀,为何不直接使用红黑树得了?
-
-说一下自己对于这个问题的看法:我们知道红黑树属于(自)平衡二叉树,但是为了保持“平衡”是需要付出代价的,红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,这费事啊。你说说我们引入红黑树就是为了查找数据快,如果链表长度很短的话,根本不需要引入红黑树的,你引入之后还要付出代价维持它的平衡。但是链表过长就不一样了。至于为什么选 8 这个值呢?通过概率统计所得,这个值是综合查询成本和新增元素成本得出的最好的一个值。
-
-### 3.5 HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别
-
-**HashMap 和 Hashtable 的区别**
-
-1. **线程是否安全:** HashMap 是非线程安全的,Hashtable 是线程安全的;Hashtable 内部的方法基本都经过 `synchronized` 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
-2. **效率:** 因为线程安全的问题,HashMap 要比 Hashtable 效率高一点。另外,Hashtable 基本被淘汰,不要在代码中使用它;
-3. **对 Null key 和 Null value 的支持:** HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。但是在 Hashtable 中 put 进的键值只要有一个 null,直接抛出 NullPointerException。
-4. **初始容量大小和每次扩充容量大小的不同 :** ① 创建时如果不指定容量初始值,Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。② 创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小(HashMap 中的`tableSizeFor()`方法保证,下面给出了源代码)。也就是说 HashMap 总是使用 2 的幂作为哈希表的大小,后面会介绍到为什么是 2 的幂次方。
-5. **底层数据结构:** JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。
-
-**HashSet 和 HashMap 区别**
-
-如果你看过 HashSet 源码的话就应该知道:HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 clone() 方法、writeObject()方法、readObject()方法是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。)
-
-
-
-# 三 终结篇
-
-## 1. Object 类有哪些方法?
-
-这个问题,面试中经常出现。我觉得不论是出于应付面试还是说更好地掌握 Java 这门编程语言,大家都要掌握!
-
-### 1.1 Object 类的常见方法总结
-
-Object 类是一个特殊的类,是所有类的父类。它主要提供了以下 11 个方法:
-
-```java
-
-public final native Class> getClass()//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。
-
-public native int hashCode() //native方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。
-public boolean equals(Object obj)//用于比较2个对象的内存地址是否相等,String类对该方法进行了重写用户比较字符串的值是否相等。
-
-protected native Object clone() throws CloneNotSupportedException//naitive方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象 x,表达式 x.clone() != x 为true,x.clone().getClass() == x.getClass() 为true。Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。
-
-public String toString()//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法。
-
-public final native void notify()//native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
-
-public final native void notifyAll()//native方法,并且不能重写。跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
-
-public final native void wait(long timeout) throws InterruptedException//native方法,并且不能重写。暂停线程的执行。注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间。
-
-public final void wait(long timeout, int nanos) throws InterruptedException//多了nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上nanos毫秒。
-
-public final void wait() throws InterruptedException//跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
-
-protected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作
-
-```
-
-> 问完上面这个问题之后,面试官很可能紧接着就会问你“hashCode 与 equals”相关的问题。
-
-### 1.2 hashCode 与 equals
-
-面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写 equals 时必须重写 hashCode 方法?”
-
-#### 1.2.1 hashCode()介绍
-
-hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在 JDK 的 Object.java 中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。另外需要注意的是: Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法通常用来将对象的 内存地址 转换为整数之后返回。
-
-```java
- public native int hashCode();
-```
-
-散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
-
-#### 1.2.2 为什么要有 hashCode
-
-**我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:**
-
-当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet 会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的 Java 启蒙书《Head fist java》第二版)。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
-
-#### 1.2.3 hashCode()与 equals()的相关规定
-
-1. 如果两个对象相等,则 hashcode 一定也是相同的
-2. 两个对象相等,对两个对象分别调用 equals 方法都返回 true
-3. 两个对象有相同的 hashcode 值,它们也不一定是相等的
-4. **因此,equals 方法被覆盖过,则 hashCode 方法也必须被覆盖**
-5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写 hashCode(),则该 class 的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)
-
-#### 1.2.4 为什么两个对象有相同的 hashcode 值,它们也不一定是相等的?
-
-在这里解释一位小伙伴的问题。以下内容摘自《Head Fisrt Java》。
-
-因为 hashCode() 所使用的杂凑算法也许刚好会让多个对象传回相同的杂凑值。越糟糕的杂凑算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的 hashCode)。
-
-我们刚刚也提到了 HashSet,如果 HashSet 在对比的时候,同样的 hashcode 有多个对象,它会使用 equals() 来判断是否真的相同。也就是说 hashcode 只是用来缩小查找成本。
-
-> ==与 equals 的对比也是比较常问的基础问题之一!
-
-### 1.3 ==与 equals
-
-**==** : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象。(基本数据类型==比较的是值,引用数据类型==比较的是内存地址)
-
-**equals()** : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
-
-- 情况 1:类没有覆盖 equals()方法。则通过 equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
-- 情况 2:类覆盖了 equals()方法。一般,我们都覆盖 equals()方法来两个对象的内容相等;若它们的内容相等,则返回 true(即,认为这两个对象相等)。
-
-**举个例子:**
-
-```java
-public class test1 {
- public static void main(String[] args) {
- String a = new String("ab"); // a 为一个引用
- String b = new String("ab"); // b为另一个引用,对象的内容一样
- String aa = "ab"; // 放在常量池中
- String bb = "ab"; // 从常量池中查找
- if (aa == bb) // true
- System.out.println("aa==bb");
- if (a == b) // false,非同一对象
- System.out.println("a==b");
- if (a.equals(b)) // true
- System.out.println("aEQb");
- if (42 == 42.0) { // true
- System.out.println("true");
- }
- }
-}
-```
-
-**说明:**
-
-- String 中的 equals()方法是被重写过的,因为 Object 的 equals()方法是比较的对象的内存地址,而 String 的 equals()方法比较的是对象的值。
-- 当创建 String 类型的对象时,虚拟机会在常量池中查找有没有已经存在的值和要创建的值相同的对象,如果有就把它赋给当前引用。如果没有就在常量池中重新创建一个 String 对象。
-
-> 在[【备战春招/秋招系列 5】美团面经总结进阶篇 (附详解答案)](https://mp.weixin.qq.com/s?__biz=MzU4NDQ4MzU5OA==&mid=2247484625&idx=1&sn=9c4fa1f7d4291a5fbd7daa44bac2b012&chksm=fd9852b0caefdba6edcf9a827aa4a17ddc97bf6ad2e5ee6f7e1aa1b443b54444d05d2b76732b&token=723699735&lang=zh_CN#rd) 这篇文章中,我们已经提到了一下关于 HashMap 在面试中常见的问题:HashMap 的底层实现、简单讲一下自己对于红黑树的理解、红黑树这么优秀,为何不直接使用红黑树得了、HashMap 和 Hashtable 的区别/HashSet 和 HashMap 区别。HashMap 和 ConcurrentHashMap 这俩兄弟在一般只要面试中问到集合相关的问题就一定会被问到,所以各位务必引起重视!
-
-## 2 ConcurrentHashMap 相关问题
-
-### 2.1 ConcurrentHashMap 和 Hashtable 的区别
-
-ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
-
-- **底层数据结构:** JDK1.7 的 ConcurrentHashMap 底层采用 **分段的数组+链表** 实现,JDK1.8 采用的数据结构跟 HashMap1.8 的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 **数组+链表** 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
-- **实现线程安全的方式(重要):** ① **在 JDK1.7 的时候,ConcurrentHashMap(分段锁)** 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配 16 个 Segment,比 Hashtable 效率提高 16 倍。) **到了 JDK1.8 的时候已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6 以后 对 synchronized 锁做了很多优化)** 整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;② **Hashtable(同一把锁)**:使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
-
-**两者的对比图:**
-
-图片来源:http://www.cnblogs.com/chengxiao/p/6842045.html
-
-Hashtable:
-
-
-JDK1.7 的 ConcurrentHashMap:
-
-JDK1.8 的 ConcurrentHashMap(TreeBin: 红黑二叉树节点
-Node: 链表节点):
-
-
-### 2.2 ConcurrentHashMap 线程安全的具体实现方式/底层具体实现
-
-#### JDK1.7(上面有示意图)
-
-首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
-
-**ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成**。
-
-Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。
-
-```java
-static class Segment extends ReentrantLock implements Serializable {
-}
-```
-
-一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和 HashMap 类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个 HashEntry 数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment 的锁。
-
-#### JDK1.8(上面有示意图)
-
-ConcurrentHashMap 取消了 Segment 分段锁,采用 CAS 和 synchronized 来保证并发安全。数据结构跟 HashMap1.8 的结构类似,数组+链表/红黑二叉树。
-
-synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,效率又提升 N 倍。
-
-## 3 谈谈 synchronized 和 ReentrantLock 的区别
-
-**① 两者都是可重入锁**
-
-两者都是可重入锁。“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增 1,所以要等到锁的计数器下降为 0 时才能释放锁。
-
-**② synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API**
-
-synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。
-
-**③ ReentrantLock 比 synchronized 增加了一些高级功能**
-
-相比 synchronized,ReentrantLock 增加了一些高级功能。主要来说主要有三点:**① 等待可中断;② 可实现公平锁;③ 可实现选择性通知(锁可以绑定多个条件)**
-
-- **ReentrantLock 提供了一种能够中断等待锁的线程的机制**,通过 lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
-- **ReentrantLock 可以指定是公平锁还是非公平锁。而 synchronized 只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。** ReentrantLock 默认情况是非公平的,可以通过 ReentrantLock 类的`ReentrantLock(boolean fair)`构造方法来制定是否是公平的。
-- synchronized 关键字与 wait()和 notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock 类当然也可以实现,但是需要借助于 Condition 接口与 newCondition() 方法。Condition 是 JDK1.5 之后才有的,它具有很好的灵活性,比如可以实现多路通知功能也就是在一个 Lock 对象中可以创建多个 Condition 实例(即对象监视器),**线程对象可以注册在指定的 Condition 中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用 notify/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用 ReentrantLock 类结合 Condition 实例可以实现“选择性通知”** ,这个功能非常重要,而且是 Condition 接口默认提供的。而 synchronized 关键字就相当于整个 Lock 对象中只有一个 Condition 实例,所有的线程都注册在它一个身上。如果执行 notifyAll()方法的话就会通知所有处于等待状态的线程这样会造成很大的效率问题,而 Condition 实例的 signalAll()方法 只会唤醒注册在该 Condition 实例中的所有等待线程。
-
-如果你想使用上述功能,那么选择 ReentrantLock 是一个不错的选择。
-
-**④ 两者的性能已经相差无几**
-
-在 JDK1.6 之前,synchronized 的性能是比 ReentrantLock 差很多。具体表示为:synchronized 关键字吞吐量岁线程数的增加,下降得非常严重。而 ReentrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。JDK1.6 之后,synchronized 和 ReentrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReentrantLock 的文章都是错的!JDK1.6 之后,性能已经不是选择 synchronized 和 ReentrantLock 的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的 synchronized,所以还是提倡在 synchronized 能满足你的需求的情况下,优先考虑使用 synchronized 关键字来进行同步!优化后的 synchronized 和 ReentrantLock 一样,在很多地方都是用到了 CAS 操作。
-
-## 4 线程池了解吗?
-
-### 4.1 为什么要用线程池?
-
-线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。
-
-这里借用《Java 并发编程的艺术》提到的来说一下使用线程池的好处:
-
-- **降低资源消耗。** 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
-- **提高响应速度。** 当任务到达时,任务可以不需要的等到线程创建就能立即执行。
-- **提高线程的可管理性。** 线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
-
-### 4.2 Java 提供了哪几种线程池?他们各自的使用场景是什么?
-
-#### Java 主要提供了下面 4 种线程池
-
-- **FixedThreadPool:** 该方法返回一个固定线程数量的线程池。该线程池中的线程数量始终不变。当有一个新的任务提交时,线程池中若有空闲线程,则立即执行。若没有,则新的任务会被暂存在一个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
-- **SingleThreadExecutor:** 方法返回一个只有一个线程的线程池。若多余一个任务被提交到该线程池,任务会被保存在一个任务队列中,待线程空闲,按先入先出的顺序执行队列中的任务。
-- **CachedThreadPool:** 该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
-- **ScheduledThreadPoolExecutor:** 主要用来在给定的延迟后运行任务,或者定期执行任务。ScheduledThreadPoolExecutor 又分为:ScheduledThreadPoolExecutor(包含多个线程)和 SingleThreadScheduledExecutor (只包含一个线程)两种。
-
-#### 各种线程池的适用场景介绍
-
-- **FixedThreadPool:** 适用于为了满足资源管理需求,而需要限制当前线程数量的应用场景。它适用于负载比较重的服务器;
-- **SingleThreadExecutor:** 适用于需要保证顺序地执行各个任务并且在任意时间点,不会有多个线程是活动的应用场景;
-- **CachedThreadPool:** 适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器;
-- **ScheduledThreadPoolExecutor:** 适用于需要多个后台执行周期任务,同时为了满足资源管理需求而需要限制后台线程的数量的应用场景;
-- **SingleThreadScheduledExecutor:** 适用于需要单个后台线程执行周期任务,同时保证顺序地执行各个任务的应用场景。
-
-### 4.3 创建的线程池的方式
-
-**(1) 使用 Executors 创建**
-
-我们上面刚刚提到了 Java 提供的几种线程池,通过 Executors 工具类我们可以很轻松的创建我们上面说的几种线程池。但是实际上我们一般都不是直接使用 Java 提供好的线程池,另外在《阿里巴巴 Java 开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 构造函数 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
-
-```java
-Executors 返回线程池对象的弊端如下:
-
-FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。
-CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
-
-```
-
-**(2) ThreadPoolExecutor 的构造函数创建**
-
-我们可以自己直接调用 ThreadPoolExecutor 的构造函数来自己创建线程池。在创建的同时,给 BlockQueue 指定容量就可以了。示例如下:
-
-```java
-private static ExecutorService executor = new ThreadPoolExecutor(13, 13,
- 60L, TimeUnit.SECONDS,
- new ArrayBlockingQueue(13));
-```
-
-这种情况下,一旦提交的线程数超过当前可用线程数时,就会抛出 java.util.concurrent.RejectedExecutionException,这是因为当前线程池使用的队列是有边界队列,队列已经满了便无法继续处理新的请求。但是异常(Exception)总比发生错误(Error)要好。
-
-**(3) 使用开源类库**
-
-Hollis 大佬之前在他的文章中也提到了:“除了自己定义 ThreadPoolExecutor 外。还有其他方法。这个时候第一时间就应该想到开源类库,如 apache 和 guava 等。”他推荐使用 guava 提供的 ThreadFactoryBuilder 来创建线程池。下面是参考他的代码示例:
-
-```java
-public class ExecutorsDemo {
-
- private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
- .setNameFormat("demo-pool-%d").build();
-
- private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
- 0L, TimeUnit.MILLISECONDS,
- new LinkedBlockingQueue(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
-
- public static void main(String[] args) {
-
- for (int i = 0; i < Integer.MAX_VALUE; i++) {
- pool.execute(new SubThread());
- }
- }
-}
-```
-
-通过上述方式创建线程时,不仅可以避免 OOM 的问题,还可以自定义线程名称,更加方便的出错的时候溯源。
-
-## 5 Nginx
-
-### 5.1 简单介绍一下 Nginx
-
-Nginx 是一款轻量级的 Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。 Nginx 主要提供反向代理、负载均衡、动静分离(静态资源服务)等服务。下面我简单地介绍一下这些名词。
-
-#### 反向代理
-
-谈到反向代理,就不得不提一下正向代理。无论是正向代理,还是反向代理,说到底,就是代理模式的衍生版本罢了
-
-- **正向代理:**某些情况下,代理我们用户去访问服务器,需要用户手动的设置代理服务器的 ip 和端口号。正向代理比较常见的一个例子就是 VPN 了。
-- **反向代理:** 是用来代理服务器的,代理我们要访问的目标服务器。代理服务器接受请求,然后将请求转发给内部网络的服务器,并将从服务器上得到的结果返回给客户端,此时代理服务器对外就表现为一个服务器。
-
-通过下面两幅图,大家应该更好理解(图源:http://blog.720ui.com/2016/nginx_action_05_proxy/):
-
-
-
-
-
-所以,简单的理解,就是正向代理是为客户端做代理,代替客户端去访问服务器,而反向代理是为服务器做代理,代替服务器接受客户端请求。
-
-#### 负载均衡
-
-在高并发情况下需要使用,其原理就是将并发请求分摊到多个服务器执行,减轻每台服务器的压力,多台服务器(集群)共同完成工作任务,从而提高了数据的吞吐量。
-
-Nginx 支持的 weight 轮询(默认)、ip_hash、fair、url_hash 这四种负载均衡调度算法,感兴趣的可以自行查阅。
-
-负载均衡相比于反向代理更侧重的是将请求分担到多台服务器上去,所以谈论负载均衡只有在提供某服务的服务器大于两台时才有意义。
-
-#### 动静分离
-
-动静分离是让动态网站里的动态网页根据一定规则把不变的资源和经常变的资源区分开来,动静资源做好了拆分以后,我们就可以根据静态资源的特点将其做缓存操作,这就是网站静态化处理的核心思路。
-
-### 5.2 为什么要用 Nginx?
-
-> 这部分内容参考极客时间—[Nginx 核心知识 100 讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE= "Nginx核心知识100讲的内容")。
-
-如果面试官问你这个问题,就一定想看你知道 Nginx 服务器的一些优点吗。
-
-Nginx 有以下 5 个优点:
-
-1. 高并发、高性能(这是其他 web 服务器不具有的)
-2. 可扩展性好(模块化设计,第三方插件生态圈丰富)
-3. 高可靠性(可以在服务器行持续不间断的运行数年)
-4. 热部署(这个功能对于 Nginx 来说特别重要,热部署指可以在不停止 Nginx 服务的情况下升级 Nginx)
-5. BSD 许可证(意味着我们可以将源代码下载下来进行修改然后使用自己的版本)
-
-### 5.3 Nginx 的四个主要组成部分了解吗?
-
-> 这部分内容参考极客时间—[Nginx 核心知识 100 讲的内容](https://time.geekbang.org/course/intro/138?code=AycjiiQk6uQRxnVJzBupFkrGkvZlmYELPRsZbWzaAHE= "Nginx核心知识100讲的内容")。
-
-- Nginx 二进制可执行文件:由各模块源码编译出一个文件
-- nginx.conf 配置文件:控制 Nginx 行为
-- acess.log 访问日志: 记录每一条 HTTP 请求信息
-- error.log 错误日志:定位问题
\ No newline at end of file
diff --git "a/docs/essential-content-for-interview/PreparingForInterview/\351\235\242\350\257\225\345\256\230-\344\275\240\346\234\211\344\273\200\344\271\210\351\227\256\351\242\230\350\246\201\351\227\256\346\210\221.md" "b/docs/essential-content-for-interview/PreparingForInterview/\351\235\242\350\257\225\345\256\230-\344\275\240\346\234\211\344\273\200\344\271\210\351\227\256\351\242\230\350\246\201\351\227\256\346\210\221.md"
deleted file mode 100644
index 7a55d53..0000000
--- "a/docs/essential-content-for-interview/PreparingForInterview/\351\235\242\350\257\225\345\256\230-\344\275\240\346\234\211\344\273\200\344\271\210\351\227\256\351\242\230\350\246\201\351\227\256\346\210\221.md"
+++ /dev/null
@@ -1,64 +0,0 @@
-我还记得当时我去参加面试的时候,几乎每一场面试,特别是HR面和高管面的时候,面试官总是会在结尾问我:“问了你这么多问题了,你有什么问题问我吗?”。这个时候很多人内心就会陷入短暂的纠结中:我该问吗?不问的话面试官会不会对我影响不好?问什么问题?问这个问题会不会让面试官对我的影响不好啊?
-
-
-
-### 这个问题对最终面试结果的影响到底大不大?
-
-就技术面试而言,回答这个问题的时候,只要你不是触碰到你所面试的公司的雷区,那么我觉得这对你能不能拿到最终offer来说影响确实是不大的。我说这些并不代表你就可以直接对面试官说:“我没问题了。”,笔主当时面试的时候确实也说过挺多次“没问题要问了。”,最终也没有导致笔主被pass掉(可能是前面表现比较好,哈哈,自恋一下)。我现在回想起来,觉得自己当时做法其实挺不对的。面试本身就是一个双向选择的过程,你对这个问题的回答也会侧面反映出你对这次面试的上心程度,你的问题是否有价值,也影响了你最终的选择与公司是否选择你。
-
-面试官在技术面试中主要考察的还是你这样个人到底有没有胜任这个工作的能力以及你是否适合公司未来的发展需要,很多公司还需要你认同它的文化,我觉得你只要不是太笨,应该不会栽在这里。除非你和另外一个人在能力上相同,但是只能在你们两个人中选一个,那么这个问题才对你能不能拿到offer至关重要。有准备总比没准备好,给面试官留一个好的影响总归是没错的。
-
-但是,就非技术面试来说,我觉得好好回答这个问题对你最终的结果还是比较重要的。
-
-总的来说不管是技术面试还是非技术面试,如果你想赢得公司的青睐和尊重,我觉得我们都应该重视这个问题。
-
-### 真诚一点,不要问太 Low 的问题
-
-回答这个问题很重要的一点就是你没有必要放低自己的姿态问一些很虚或者故意讨好面试官的问题,也不要把自己从面经上学到的东西照搬下来使用。面试官也不是傻子,特别是那种特别有经验的面试官,你是真心诚意的问问题,还是从别处照搬问题来讨好面试官,人家可能一听就听出来了。总的来说,还是要真诚。除此之外,不要问太 Low 的问题,会显得你整个人格局比较小或者说你根本没有准备(侧面反映你对这家公司不上心,既然你不上心,为什么要要你呢)。举例几个比较 Low 的问题,大家看看自己有没有问过其中的问题:
-
-- 贵公司的主要业务是什么?(面试之前自己不知道提前网上查一下吗?)
-- 贵公司的男女比例如何?(考虑脱单?记住你是来工作的!)
-- 贵公司一年搞几次外出旅游?(你是来工作的,这些娱乐活动先别放在心上!)
-- ......
-
-### 有哪些有价值的问题值得问?
-
-针对这个问题。笔主专门找了几个专门做HR工作的小哥哥小姐姐们询问并且查阅了挺多前辈们的回答,然后结合自己的实际经历,我概括了下面几个比较适合问的问题。
-
-#### 面对HR或者其他Level比较低的面试官时
-
-1. **能不能谈谈你作为一个公司老员工对公司的感受?** (这个问题比较容易回答,不会让面试官陷入无话可说的尴尬境地。另外,从面试官的回答中你可以加深对这个公司的了解,让你更加清楚这个公司到底是不是你想的那样或者说你是否能适应这个公司的文化。除此之外,这样的问题在某种程度上还可以拉进你与面试官的距离。)
-2. **能不能问一下,你当时因为什么原因选择加入这家公司的呢或者说这家公司有哪些地方吸引你?有什么地方你觉得还不太好或者可以继续完善吗?** (类似第一个问题,都是问面试官个人对于公司的看法。)
-3. **我觉得我这次表现的不是太好,你有什么建议或者评价给我吗?**(这个是我常问的。我觉得说自己表现不好只是这个语境需要这样来说,这样可以显的你比较谦虚好学上进。)
-4. **接下来我会有一段空档期,有什么值得注意或者建议学习的吗?** (体现出你对工作比较上心,自助学习意识比较强。)
-5. **这个岗位为什么还在招人?** (岗位真实性和价值咨询)
-6. **大概什么时候能给我回复呢?** (终面的时候,如果面试官没有说的话,可以问一下)
-7. ......
-
-
-
-#### 面对部门领导
-
-1. **部门的主要人员分配以及对应的主要工作能简单介绍一下吗?**
-2. **未来如果我要加入这个团队,你对我的期望是什么?** (部门领导一般情况下是你的直属上级了,你以后和他打交道的机会应该是最多的。你问这个问题,会让他感觉你是一个对他的部门比较上心,比较有团体意识,并且愿意倾听的候选人。)
-3. **公司对新入职的员工的培养机制是什么样的呢?** (正规的公司一般都有培养机制,提前问一下是对你自己的负责也会显的你比较上心)
-4. **以您来看,这个岗位未来在公司内部的发展如何?** (在我看来,问这个问题也是对你自己的负责吧,谁不想发展前景更好的岗位呢?)
-5. **团队现在面临的最大挑战是什么?** (这样的问题不会暴露你对公司的不了解,并且也能让你对未来工作的挑战或困难有一个提前的预期。)
-
-
-
-#### 面对Level比较高的(比如总裁,老板)
-
-1. **贵公司的发展目标和方向是什么?** (看下公司的发展是否满足自己的期望)
-2. **与同行业的竞争者相比,贵公司的核心竞争优势在什么地方?** (充分了解自己的优势和劣势)
-3. **公司现在面临的最大挑战是什么?**
-
-### 来个补充,顺便送个祝福给大家
-
-薪酬待遇和相关福利问题一般在终面的时候(最好不要在前面几面的时候就问到这个问题),面试官会提出来或者在面试完之后以邮件的形式告知你。一般来说,如果面试官很愿意为你回答问题,对你的问题也比较上心的话,那他肯定是觉得你就是他们要招的人。
-
-大家在面试的时候,可以根据自己对于公司或者岗位的了解程度,对上面提到的问题进行适当修饰或者修改。上面提到的一些问题只是给没有经验的朋友一个参考,如果你还有其他比较好的问题的话,那当然也更好啦!
-
-金三银四。过了二月就到了面试高峰期或者说是黄金期。几份惊喜几份愁,愿各位能始终不忘初心!每个人都有每个人的难处。引用一句《阿甘正传》里面的台词:“生活就像一盒巧克力,你永远不知道下一块是什么味道“。
-
-
\ No newline at end of file
diff --git a/docs/essential-content-for-interview/real-interview-experience-analysis/alibaba-1.md b/docs/essential-content-for-interview/real-interview-experience-analysis/alibaba-1.md
deleted file mode 100644
index a1f6101..0000000
--- a/docs/essential-content-for-interview/real-interview-experience-analysis/alibaba-1.md
+++ /dev/null
@@ -1,222 +0,0 @@
-本文的内容都是根据读者投稿的真实面试经历改编而来,首次尝试这种风格的文章,花了几天晚上才总算写完,希望对你有帮助。
-
-本文主要涵盖下面的内容:
-
-1. 分布式商城系统:架构图讲解;
-2. 消息队列相关:削峰和解耦;
-3. Redis 相关:缓存穿透问题的解决;
-4. 一些基础问题:
- - 网络相关:1.浏览器输入 URL 发生了什么? 2.TCP 和 UDP 区别? 3.TCP 如何保证传输可靠性?
- - Java 基础:1. 既然有了字节流,为什么还要有字符流? 2.深拷贝 和 浅拷贝有啥区别呢?
-
-下面是正文!
-
-面试开始,坐在我前面的就是这次我的面试官吗?这发量看着根本不像程序员啊?我心里正嘀咕着,只听见面试官说:“小伙,下午好,我今天就是你的面试官,咱们开始面试吧!”。
-
-### 第一面开始
-
-**面试官:** 我也不用多说了,你先自我介绍一下吧,简历上有的就不要再说了哈。
-
-**我:** 内心 os:"果然如我所料,就知道会让我先自我介绍一下,还好我看了 [JavaGuide](https://github.com/Snailclimb/JavaGuide "JavaGuide") ,学到了一些套路。套路总结起来就是:**最好准备好两份自我介绍,一份对 hr 说的,主要讲能突出自己的经历,会的编程技术一语带过;另一份对技术面试官说的,主要讲自己会的技术细节,项目经验,经历那些就一语带过。** 所以,我按照这个套路准备了一个还算通用的模板,毕竟我懒嘛!不想多准备一个自我介绍,整个通用的多好!
-
-> 面试官,您好!我叫小李子。大学时间我主要利用课外时间学习 Java 相关的知识。在校期间参与过一个某某系统的开发,主要负责数据库设计和后端系统开发.,期间解决了什么问题,巴拉巴拉。另外,我自己在学习过程中也参照网上的教程写过一个电商系统的网站,写这个电商网站主要是为了能让自己接触到分布式系统的开发。在学习之余,我比较喜欢通过博客整理分享自己所学知识。我现在已经是某社区的认证作者,写过一系列关于 线程池使用以及源码分析的文章深受好评。另外,我获得过省级编程比赛二等奖,我将这个获奖项目开源到 Github 还收获了 2k 的 Star 呢?
-
-**面试官:** 你刚刚说参考网上的教程做了一个电商系统?你能画画这个电商系统的架构图吗?
-
-**我:** 内心 os: "这可难不倒我!早知道写在简历上的项目要重视了,提前都把这个系统的架构图画了好多遍了呢!"
-
-
-
-做过分布式电商系统的一定很熟悉上面的架构图(目前比较流行的是微服务架构,但是如果你有分布式开发经验也是非常加分的!)。
-
-**面试官:** 简单介绍一下你做的这个系统吧!
-
-**我:** 我一本正经的对着我刚刚画的商城架构图开始了满嘴造火箭的讲起来:
-
-> 本系统主要分为展示层、服务层和持久层这三层。表现层顾名思义主要就是为了用来展示,比如我们的后台管理系统的页面、商城首页的页面、搜索系统的页面等等,这一层都只是作为展示,并没有提供任何服务。
->
-> 展示层和服务层一般是部署在不同的机器上来提高并发量和扩展性,那么展示层和服务层怎样才能交互呢?在本系统中我们使用 Dubbo 来进行服务治理。Dubbo 是一款高性能、轻量级的开源 Java RPC 框架。Dubbo 在本系统的主要作用就是提供远程 RPC 调用。在本系统中服务层的信息通过 Dubbo 注册给 ZooKeeper,表现层通过 Dubbo 去 ZooKeeper 中获取服务的相关信息。Zookeeper 的作用仅仅是存放提供服务的服务器的地址和一些服务的相关信息,实现 RPC 远程调用功能的还是 Dubbo。如果需要引用到某个服务的时候,我们只需要在配置文件中配置相关信息就可以在代码中直接使用了,就像调用本地方法一样。假如说某个服务的使用量增加时,我们只用为这单个服务增加服务器,而不需要为整个系统添加服务。
->
-> 另外,本系统的数据库使用的是常用的 MySQL,并且用到了数据库中间件 MyCat。另外,本系统还用到 redis 内存数据库来作为缓存来提高系统的反应速度。假如用户第一次访问数据库中的某些数据,这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。
->
-> 系统还用到了 Elasticsearch 来提供搜索功能。使用 Elasticsearch 我们可以非常方便的为我们的商城系统添加必备的搜索功能,并且使用 Elasticsearch 还能提供其它非常实用的功能,并且很容易扩展。
-
-**面试官:** 我看你的系统里面还用到了消息队列,能说说为什么要用它吗?
-
-**我:**
-
-> 使用消息队列主要是为了:
->
-> 1. 减少响应所需时间和削峰。
-> 2. 降低系统耦合性(解耦/提升系统可扩展性)。
-
-**面试官:** 你这说的太简单了!能不能稍微详细一点,最好能画图给我解释一下。
-
-**我:** 内心 os:"都 2019 年了,大部分面试者都能对消息队列的为系统带来的这两个好处倒背如流了,如果你想走的更远就要别别人懂的更深一点!"
-
-> 当我们不使用消息队列的时候,所有的用户的请求会直接落到服务器,然后通过数据库或者缓存响应。假如在高并发的场景下,如果没有缓存或者数据库承受不了这么大的压力的话,就会造成响应速度缓慢,甚至造成数据库宕机。但是,在使用消息队列之后,用户的请求数据发送给了消息队列之后就可以立即返回,再由消息队列的消费者进程从消息队列中获取数据,异步写入数据库,不过要确保消息不被重复消费还要考虑到消息丢失问题。由于消息队列服务器处理速度快于数据库,因此响应速度得到大幅改善。
->
-> 文字 is too 空洞,直接上图吧!下图展示了使用消息前后系统处理用户请求的对比(ps:我自己都被我画的这个图美到了,如果你也觉得这张图好看的话麻烦来个素质三连!)。
->
-> 
->
-> 通过以上分析我们可以得出**消息队列具有很好的削峰作用的功能**——即**通过异步处理,将短时间高并发产生的事务消息存储在消息队列中,从而削平高峰期的并发事务。** 举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示:
->
-> 
->
-> 使用消息队列还可以降低系统耦合性。我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。还是直接上图吧:
->
-> 
->
-> 生产者(客户端)发送消息到消息队列中去,接受者(服务端)处理消息,需要消费的系统直接去消息队列取消息进行消费即可而不需要和其他系统有耦合, 这显然也提高了系统的扩展性。
-
-**面试官:** 你觉得它有什么缺点吗?或者说怎么考虑用不用消息队列?
-
-**我:** 内心 os: "面试官真鸡贼!这不是勾引我上钩么?还好我准备充分。"
-
-> 我觉得可以从下面几个方面来说:
->
-> 1. **系统可用性降低:** 系统可用性在某种程度上降低,为什么这样说呢?在加入 MQ 之前,你不用考虑消息丢失或者说 MQ 挂掉等等的情况,但是,引入 MQ 之后你就需要去考虑了!
-> 2. **系统复杂性提高:** 加入 MQ 之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!
-> 3. **一致性问题:** 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!
-
-**面试官**:做项目的过程中遇到了什么问题吗?解决了吗?如果解决的话是如何解决的呢?
-
-**我** : 内心 os: "做的过程中好像也没有遇到什么问题啊!怎么办?怎么办?突然想到可以说我在使用 Redis 过程中遇到的问题,毕竟我对 Redis 还算熟悉嘛,**把面试官往这个方向吸引**,准没错。"
-
-> 我在使用 Redis 对常用数据进行缓冲的过程中出现了缓存穿透问题。然后,我通过谷歌搜索相关的解决方案来解决的。
-
-**面试官:** 你还知道缓存穿透啊?不错啊!来说说什么是缓存穿透以及你最后的解决办法。
-
-**我:** 我先来谈谈什么是缓存穿透吧!
-
-> 缓存穿透说简单点就是大量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这一层。举个例子:某个黑客故意制造我们缓存中不存在的 key 发起大量请求,导致大量请求落到数据库。
->
-> 总结一下就是:
->
-> 1. 缓存层不命中。
-> 2. 存储层不命中,不将空结果写回缓存。
-> 3. 返回空结果给客户端。
->
-> 一般 MySQL 默认的最大连接数在 150 左右,这个可以通过 `show variables like '%max_connections%';`命令来查看。最大连接数一个还只是一个指标,cpu,内存,磁盘,网络等物理条件都是其运行指标,这些指标都会限制其并发能力!所以,一般 3000 的并发请求就能打死大部分数据库了。
-
-**面试官:** 小伙子不错啊!还准备问你:“为什么 3000 的并发能把支持最大连接数 4000 数据库压死?”想不到你自己就提前回答了!不错!
-
-**我:** 别夸了!别夸了!我再来说说我知道的一些解决办法以及我最后采用的方案吧!您帮忙看看有没有问题。
-
-> 最基本的就是首先做好参数校验,一些不合法的参数请求直接抛出异常信息返回给客户端。比如查询的数据库 id 不能小于 0、传入的邮箱格式不对的时候直接返回错误消息给客户端等等。
->
-> 参数校验通过的情况还是会出现缓存穿透,我们还可以通过以下几个方案来解决这个问题:
->
-> **1)缓存无效 key** : 如果缓存和数据库都查不到某个 key 的数据就写一个到 redis 中去并设置过期时间,具体命令如下:`SET key value EX 10086`。这种方式可以解决请求的 key 变化不频繁的情况,如何黑客恶意攻击,每次构建的不同的请求 key,会导致 redis 中缓存大量无效的 key 。很明显,这种方案并不能从根本上解决此问题。如果非要用这种方式来解决穿透问题的话,尽量将无效的 key 的过期时间设置短一点比如 1 分钟。
->
-> 另外,这里多说一嘴,一般情况下我们是这样设计 key 的: `表名:列名:主键名:主键值`。
->
-> **2)布隆过滤器:** 布隆过滤器是一个非常神奇的数据结构,通过它我们可以非常方便地判断一个给定数据是否存在于海量数据中。我们需要的就是判断 key 是否合法,有没有感觉布隆过滤器就是我们想要找的那个“人”。
-
-**面试官:** 不错不错!你还知道布隆过滤器啊!来给我谈一谈。
-
-**我:** 内心 os:“如果你准备过海量数据处理的面试题,你一定对:“如何确定一个数字是否在于包含大量数字的数字集中(数字集很大,5 亿以上!)?”这个题目很了解了!解决这道题目就要用到布隆过滤器。”
-
-> 布隆过滤器在针对海量数据去重或者验证数据合法性的时候非常有用。**布隆过滤器的本质实际上是 “位(bit)数组”,也就是说每一个存入布隆过滤器的数据都只占一位。相比于我们平时常用的的 List、Map 、Set 等数据结构,它占用空间更少并且效率更高,但是缺点是其返回的结果是概率性的,而不是非常准确的。**
->
-> **当一个元素加入布隆过滤器中的时候,会进行如下操作:**
->
-> 1. 使用布隆过滤器中的哈希函数对元素值进行计算,得到哈希值(有几个哈希函数得到几个哈希值)。
-> 2. 根据得到的哈希值,在位数组中把对应下标的值置为 1。
->
-> **当我们需要判断一个元素是否存在于布隆过滤器的时候,会进行如下操作:**
->
-> 1. 对给定元素再次进行相同的哈希计算;
-> 2. 得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。
->
-> 举个简单的例子:
->
-> 
->
-> 如图所示,当字符串存储要加入到布隆过滤器中时,该字符串首先由多个哈希函数生成不同的哈希值,然后在对应的位数组的下表的元素设置为 1(当位数组初始化时 ,所有位置均为 0)。当第二次存储相同字符串时,因为先前的对应位置已设置为 1,所以很容易知道此值已经存在(去重非常方便)。
->
-> 如果我们需要判断某个字符串是否在布隆过滤器中时,只需要对给定字符串再次进行相同的哈希计算,得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值在布隆过滤器中,如果存在一个值不为 1,说明该元素不在布隆过滤器中。
->
-> **不同的字符串可能哈希出来的位置相同,这种情况我们可以适当增加位数组大小或者调整我们的哈希函数。**
->
-> 综上,我们可以得出:**布隆过滤器说某个元素存在,小概率会误判。布隆过滤器说某个元素不在,那么这个元素一定不在。**
-
-**面试官:** 看来你对布隆过滤器了解的还挺不错的嘛!那你快说说你最后是怎么利用它来解决缓存穿透的。
-
-**我:** 知道了布隆过滤器的原理就之后就很容易做了。我是利用 Redis 布隆过滤器来做的。我把所有可能存在的请求的值都存放在布隆过滤器中,当用户请求过来,我会先判断用户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会走下面的流程。总结一下就是下面这张图(这张图片不是我画的,为了省事直接在网上找的):
-
-
-
-更多关于布隆过滤器的内容可以看我的这篇原创:[《不了解布隆过滤器?一文给你整的明明白白!》](https://github.com/Snailclimb/JavaGuide/blob/master/docs/dataStructures-algorithms/data-structure/bloom-filter.md "《不了解布隆过滤器?一文给你整的明明白白!》") ,强烈推荐,个人感觉网上应该找不到总结的这么明明白白的文章了。
-
-**面试官:** 好了好了。项目就暂时问到这里吧!下面有一些比较基础的问题我简单地问一下你。内心 os: 难不成这家伙满口高并发,连最基础的东西都不会吧!
-
-**我:** 好的好的!没问题!
-
-**面试官:** 浏览器输入 URL 发生了什么?
-
-**我:** 内心 os:“很常问的一个问题,建议拿小本本记好了!另外,百度好像最喜欢问这个问题,去百度面试可要提前备好这道题的功课哦!相似问题:打开一个网页,整个过程会使用哪些协议?”。
-
-> 图解(图片来源:《图解 HTTP》):
->
->
->
-> 总体来说分为以下几个过程:
->
-> 1. DNS 解析
-> 2. TCP 连接
-> 3. 发送 HTTP 请求
-> 4. 服务器处理请求并返回 HTTP 报文
-> 5. 浏览器解析渲染页面
-> 6. 连接结束
->
-> 具体可以参考下面这篇文章:
->
-> - [https://segmentfault.com/a/1190000006879700](https://segmentfault.com/a/1190000006879700 "https://segmentfault.com/a/1190000006879700")
-
-**面试官:** TCP 和 UDP 区别?
-
-**我:**
-
-> 
->
-> UDP 在传送数据之前不需要先建立连接,远地主机在收到 UDP 报文后,不需要给出任何确认。虽然 UDP 不提供可靠交付,但在某些情况下 UDP 确是一种最有效的工作方式(一般用于即时通信),比如: QQ 语音、 QQ 视频 、直播等等
->
-> TCP 提供面向连接的服务。在传送数据之前必须先建立连接,数据传送结束后要释放连接。 TCP 不提供广播或多播服务。由于 TCP 要提供可靠的,面向连接的传输服务(TCP 的可靠体现在 TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源),这一难以避免增加了许多开销,如确认,流量控制,计时器以及连接管理等。这不仅使协议数据单元的首部增大很多,还要占用许多处理机资源。TCP 一般用于文件传输、发送和接收邮件、远程登录等场景。
-
-**面试官:** TCP 如何保证传输可靠性?
-
-**我:**
-
-> 1. 应用数据被分割成 TCP 认为最适合发送的数据块。
-> 2. TCP 给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
-> 3. **校验和:** TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
-> 4. TCP 的接收端会丢弃重复的数据。
-> 5. **流量控制:** TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。 (TCP 利用滑动窗口实现流量控制)
-> 6. **拥塞控制:** 当网络拥塞时,减少数据的发送。
-> 7. **ARQ 协议:** 也是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认。在收到确认后再发下一个分组。
-> 8. **超时重传:** 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
-
-**面试官:** 我再来问你一些 Java 基础的问题吧!小伙子。
-
-**我:** 好的。(内心 os:“你尽管来!”)
-
-**面试官:** 既然有了字节流,为什么还要有字符流?
-
-我:内心 os :“问题本质想问:**不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?**”
-
-> 字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
-
-**面试官**:深拷贝 和 浅拷贝有啥区别呢?
-
-**我:**
-
-> 1. **浅拷贝**:对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。
-> 2. **深拷贝**:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。
->
-> 
-
-**面试官:** 好的!面试结束。小伙子可以的!回家等通知吧!
-
-**我:** 好的好的!辛苦您了!
diff --git "a/docs/essential-content-for-interview/\346\211\213\346\212\212\346\211\213\346\225\231\344\275\240\347\224\250Markdown\345\206\231\344\270\200\344\273\275\351\253\230\350\264\250\351\207\217\347\232\204\347\256\200\345\216\206.md" "b/docs/essential-content-for-interview/\346\211\213\346\212\212\346\211\213\346\225\231\344\275\240\347\224\250Markdown\345\206\231\344\270\200\344\273\275\351\253\230\350\264\250\351\207\217\347\232\204\347\256\200\345\216\206.md"
deleted file mode 100644
index 9cb2811..0000000
--- "a/docs/essential-content-for-interview/\346\211\213\346\212\212\346\211\213\346\225\231\344\275\240\347\224\250Markdown\345\206\231\344\270\200\344\273\275\351\253\230\350\264\250\351\207\217\347\232\204\347\256\200\345\216\206.md"
+++ /dev/null
@@ -1,93 +0,0 @@
-## Markdown 简历模板样式一览
-
-**可以看到我把联系方式放在第一位,因为公司一般会与你联系,所以把联系方式放在第一位也是为了方便联系考虑。**
-
-## 为什么要用 Markdown 写简历?
-
-Markdown 语法简单,易于上手。使用正确的 Markdown 语言写出来的简历不论是在排版还是格式上都比较干净,易于阅读。另外,使用 Markdown 写简历也会给面试官一种你比较专业的感觉。
-
-除了这些,我觉得使用 Markdown 写简历可以很方便将其与PDF、HTML、PNG格式之间转换。后面我会介绍到转换方法,只需要一条命令你就可以实现 Markdown 到 PDF、HTML 与 PNG之间的无缝切换。
-
-> 下面的一些内容我在之前的一篇文章中已经提到过,这里再说一遍,最后会分享如何实现Markdown 到 PDF、HTML、PNG格式之间转换。
-
-## 为什么说简历很重要?
-
-假如你是网申,你的简历必然会经过HR的筛选,一张简历HR可能也就花费10秒钟看一下,然后HR就会决定你这一关是Fail还是Pass。
-
-假如你是内推,如果你的简历没有什么优势的话,就算是内推你的人再用心,也无能为力。
-
-另外,就算你通过了筛选,后面的面试中,面试官也会根据你的简历来判断你究竟是否值得他花费很多时间去面试。
-
-## 写简历的两大法则
-
-目前写简历的方式有两种普遍被认可,一种是 STAR, 一种是 FAB。
-
-**STAR法则(Situation Task Action Result):**
-
-- **Situation:** 事情是在什么情况下发生;
-- **Task::** 你是如何明确你的任务的;
-- **Action:** 针对这样的情况分析,你采用了什么行动方式;
-- **Result:** 结果怎样,在这样的情况下你学习到了什么。
-
-**FAB 法则(Feature Advantage Benefit):**
-
-- **Feature:** 是什么;
-- **Advantage:** 比别人好在哪些地方;
-- **Benefit:** 如果雇佣你,招聘方会得到什么好处。
-
-## 项目经历怎么写?
-简历上有一两个项目经历很正常,但是真正能把项目经历很好的展示给面试官的非常少。对于项目经历大家可以考虑从如下几点来写:
-
-1. 对项目整体设计的一个感受
-2. 在这个项目中你负责了什么、做了什么、担任了什么角色
-3. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用
-4. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的。
-
-## 专业技能该怎么写?
-先问一下你自己会什么,然后看看你意向的公司需要什么。一般HR可能并不太懂技术,所以他在筛选简历的时候可能就盯着你专业技能的关键词来看。对于公司有要求而你不会的技能,你可以花几天时间学习一下,然后在简历上可以写上自己了解这个技能。比如你可以这样写:
-
-- Dubbo:精通
-- Spring:精通
-- Docker:掌握
-- SOA分布式开发 :掌握
-- Spring Cloud:了解
-
-## 简历模板分享
-
-**开源程序员简历模板**: [https://github.com/geekcompany/ResumeSample](https://github.com/geekcompany/ResumeSample)(包括PHP程序员简历模板、iOS程序员简历模板、Android程序员简历模板、Web前端程序员简历模板、Java程序员简历模板、C/C++程序员简历模板、NodeJS程序员简历模板、架构师简历模板以及通用程序员简历模板)
-
-**上述简历模板的改进版本:** [https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/简历模板.md](https://github.com/Snailclimb/Java-Guide/blob/master/面试必备/简历模板.md)
-
-## 其他的一些小tips
-
-1. 尽量避免主观表述,少一点语义模糊的形容词,尽量要简洁明了,逻辑结构清晰。
-2. 注意排版(不需要花花绿绿的),尽量使用Markdown语法。
-3. 如果自己有博客或者个人技术栈点的话,写上去会为你加分很多。
-4. 如果自己的Github比较活跃的话,写上去也会为你加分很多。
-5. 注意简历真实性,一定不要写自己不会的东西,或者带有欺骗性的内容
-6. 项目经历建议以时间倒序排序,另外项目经历不在于多,而在于有亮点。
-7. 如果内容过多的话,不需要非把内容压缩到一页,保持排版干净整洁就可以了。
-8. 简历最后最好能加上:“感谢您花时间阅读我的简历,期待能有机会和您共事。”这句话,显的你会很有礼貌。
-
-
-> 我们刚刚讲了很多关于如何写简历的内容并且分享了一份 Markdown 格式的简历文档。下面我们来看看如何实现 Markdown 到 HTML格式、PNG格式之间转换。
-## Markdown 到 HTML格式、PNG格式之间转换
-
-网上很难找到一个比较方便并且效果好的转换方法,最后我是通过 Visual Studio Code 的 Markdown PDF 插件完美解决了这个问题!
-
-### 安装 Markdown PDF 插件
-
-**① 打开Visual Studio Code ,按快捷键 F1,选择安装扩展选项**
-
-
-
-**② 搜索 “Markdown PDF” 插件并安装 ,然后重启**
-
-
-
-### 使用方法
-
-随便打开一份 Markdown 文件 点击F1,然后输入export即可!
-
-
-
diff --git "a/docs/essential-content-for-interview/\347\256\200\345\216\206\346\250\241\346\235\277.md" "b/docs/essential-content-for-interview/\347\256\200\345\216\206\346\250\241\346\235\277.md"
deleted file mode 100644
index 2a7a043..0000000
--- "a/docs/essential-content-for-interview/\347\256\200\345\216\206\346\250\241\346\235\277.md"
+++ /dev/null
@@ -1,79 +0,0 @@
-# 联系方式
-
-- 手机:
-- Email:
-- 微信:
-
-# 个人信息
-
- - 姓名/性别/出生日期
- - 本科/xxx计算机系xxx专业/英语六级
- - 技术博客:[http://snailclimb.top/](http://snailclimb.top/)
- - 荣誉奖励:获得了什么奖(获奖时间)
- - Github:[https://github.com/Snailclimb ](https://github.com/Snailclimb)
- - Github Resume: [http://resume.github.io/?Snailclimb](http://resume.github.io/?Snailclimb)
- - 期望职位:Java 研发程序员/大数据工程师(Java后台开发为首选)
- - 期望城市:xxx城市
-
-
-# 项目经历
-
-## xxx项目
-
-### 项目描述
-
-介绍该项目是做什么的、使用到了什么技术以及你对项目整体设计的一个感受
-
-### 责任描述
-
-主要可以从下面三点来写:
-
-1. 在这个项目中你负责了什么、做了什么、担任了什么角色
-2. 从这个项目中你学会了那些东西,使用到了那些技术,学会了那些新技术的使用
-3. 另外项目描述中,最好可以体现自己的综合素质,比如你是如何协调项目组成员协同开发的或者在遇到某一个棘手的问题的时候你是如何解决的。
-
-# 开源项目和技术文章
-
-## 开源项目
-
-- [Java-Guide](https://github.com/Snailclimb/Java-Guide) :一份涵盖大部分Java程序员所需要掌握的核心知识。Star:3.9K; Fork:0.9k。
-
-
-## 技术文章推荐
-
-- [可能是把Java内存区域讲的最清楚的一篇文章](https://juejin.im/post/5b7d69e4e51d4538ca5730cb)
-- [搞定JVM垃圾回收就是这么简单](https://juejin.im/post/5b85ea54e51d4538dd08f601)
-- [前端&后端程序员必备的Linux基础知识](https://juejin.im/post/5b3b19856fb9a04fa42f8c71)
-- [可能是把Docker的概念讲的最清楚的一篇文章](https://juejin.im/post/5b260ec26fb9a00e8e4b031a)
-
-
-# 校园经历(可选)
-
-## 2016-2017
-
-担任学校社团-致深社副会长,主要负责团队每周活动的组建以及每周例会的主持。
-
-## 2017-2018
- 担任学校传媒组织:“长江大学在线信息传媒”的副站长以及安卓组成员。主要负责每周例会主持、活动策划以及学校校园通APP的研发工作。
-
-
-# 技能清单
-
-以下均为我熟练使用的技能
-
-- Web开发:PHP/Hack/Node
-- Web框架:ThinkPHP/Yaf/Yii/Lavarel/LazyPHP
-- 前端框架:Bootstrap/AngularJS/EmberJS/HTML5/Cocos2dJS/ionic
-- 前端工具:Bower/Gulp/SaSS/LeSS/PhoneGap
-- 数据库相关:MySQL/PgSQL/PDO/SQLite
-- 版本管理、文档和自动化部署工具:Svn/Git/PHPDoc/Phing/Composer
-- 单元测试:PHPUnit/SimpleTest/Qunit
-- 云和开放平台:SAE/BAE/AWS/微博开放平台/微信应用开发
-
-# 自我评价(可选)
-
-自我发挥。切记不要过度自夸!!!
-
-
-### 感谢您花时间阅读我的简历,期待能有机会和您共事。
-
diff --git "a/docs/essential-content-for-interview/\351\235\242\350\257\225\345\277\205\345\244\207\344\271\213\344\271\220\350\247\202\351\224\201\344\270\216\346\202\262\350\247\202\351\224\201.md" "b/docs/essential-content-for-interview/\351\235\242\350\257\225\345\277\205\345\244\207\344\271\213\344\271\220\350\247\202\351\224\201\344\270\216\346\202\262\350\247\202\351\224\201.md"
deleted file mode 100644
index 00aaecd..0000000
--- "a/docs/essential-content-for-interview/\351\235\242\350\257\225\345\277\205\345\244\207\344\271\213\344\271\220\350\247\202\351\224\201\344\270\216\346\202\262\350\247\202\351\224\201.md"
+++ /dev/null
@@ -1,115 +0,0 @@
-点击关注[公众号](#公众号)及时获取笔主最新更新文章,并可免费领取本文档配套的《Java面试突击》以及Java工程师必备学习资源。
-
-
-
-- [何谓悲观锁与乐观锁](#何谓悲观锁与乐观锁)
- - [悲观锁](#悲观锁)
- - [乐观锁](#乐观锁)
- - [两种锁的使用场景](#两种锁的使用场景)
-- [乐观锁常见的两种实现方式](#乐观锁常见的两种实现方式)
- - [1. 版本号机制](#1-版本号机制)
- - [2. CAS算法](#2-cas算法)
-- [乐观锁的缺点](#乐观锁的缺点)
- - [1 ABA 问题](#1-aba-问题)
- - [2 循环时间长开销大](#2-循环时间长开销大)
- - [3 只能保证一个共享变量的原子操作](#3-只能保证一个共享变量的原子操作)
-- [CAS与synchronized的使用情景](#cas与synchronized的使用情景)
-
-
-
-### 何谓悲观锁与乐观锁
-
-> 乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。
-
-#### 悲观锁
-
-总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(**共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程**)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中`synchronized`和`ReentrantLock`等独占锁就是悲观锁思想的实现。
-
-
-#### 乐观锁
-
-总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。**乐观锁适用于多读的应用类型,这样可以提高吞吐量**,像数据库提供的类似于**write_condition机制**,其实都是提供的乐观锁。在Java中`java.util.concurrent.atomic`包下面的原子变量类就是使用了乐观锁的一种实现方式**CAS**实现的。
-
-#### 两种锁的使用场景
-
-从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像**乐观锁适用于写比较少的情况下(多读场景)**,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以**一般多写的场景下用悲观锁就比较合适。**
-
-
-### 乐观锁常见的两种实现方式
-
-> **乐观锁一般会使用版本号机制或CAS算法实现。**
-
-#### 1. 版本号机制
-
-一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
-
-**举一个简单的例子:**
-假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。
-
-1. 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。
-2. 在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20 )。
-3. 操作员 A 完成了修改工作,将数据版本号加一( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
-4. 操作员 B 完成了操作,也将版本号加一( version=2 )试图向数据库提交数据( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,数据库记录当前版本也为 2 ,不满足 “ 提交版本必须大于记录当前版本才能执行更新 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。
-
-这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。
-
-#### 2. CAS算法
-
-即**compare and swap(比较与交换)**,是一种有名的**无锁算法**。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。**CAS算法**涉及到三个操作数
-
-- 需要读写的内存值 V
-- 进行比较的值 A
-- 拟写入的新值 B
-
-当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个**自旋操作**,即**不断的重试**。
-
-关于自旋锁,大家可以看一下这篇文章,非常不错:[《
-面试必备之深入理解自旋锁》](https://blog.csdn.net/qq_34337272/article/details/81252853)
-
-### 乐观锁的缺点
-
-> ABA 问题是乐观锁一个常见的问题
-
-#### 1 ABA 问题
-
-如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 **"ABA"问题。**
-
-JDK 1.5 以后的 `AtomicStampedReference 类`就提供了此种能力,其中的 `compareAndSet 方法`就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
-
-#### 2 循环时间长开销大
-**自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。** 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
-
-#### 3 只能保证一个共享变量的原子操作
-
-CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了`AtomicReference类`来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用`AtomicReference类`把多个共享变量合并成一个共享变量来操作。
-
-### CAS与synchronized的使用情景
-
-> **简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)**
-
-1. 对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
-2. 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。
-
-补充: Java并发编程这个领域中synchronized关键字一直都是元老级的角色,很久之前很多人都会称它为 **“重量级锁”** 。但是,在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 **偏向锁** 和 **轻量级锁** 以及其它**各种优化**之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 **Lock-Free** 的队列,基本思路是 **自旋后阻塞**,**竞争切换后继续竞争锁**,**稍微牺牲了公平性,但获得了高吞吐量**。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。
-
-## 公众号
-
-如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注我的公众号。
-
-**《Java面试突击》:** 由本文档衍生的专为面试而生的《Java面试突击》V2.0 PDF 版本[公众号](#公众号)后台回复 **"面试突击"** 即可免费领取!
-
-**Java工程师必备学习资源:** 一些Java工程师常用学习资源公众号后台回复关键字 **“1”** 即可免费无套路获取。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git "a/docs/golang/\351\235\242\350\257\225\351\242\230/golang\351\235\242\350\257\225\351\242\230\346\225\264\347\220\206-\345\276\256\344\277\241\345\256\214\346\225\264.md" "b/docs/golang/\351\235\242\350\257\225\351\242\230/golang\351\235\242\350\257\225\351\242\230\346\225\264\347\220\206-\345\276\256\344\277\241\345\256\214\346\225\264.md"
new file mode 100644
index 0000000..e65120c
--- /dev/null
+++ "b/docs/golang/\351\235\242\350\257\225\351\242\230/golang\351\235\242\350\257\225\351\242\230\346\225\264\347\220\206-\345\276\256\344\277\241\345\256\214\346\225\264.md"
@@ -0,0 +1,836 @@
+> ❝
+>
+> 原文: https://zhuanlan.zhihu.com/p/519979757,作者:沪猿小韩。为适于阅读,部分样式及内容有做简单修改。
+>
+> ❞
+
+# 前言
+
+文章部分题目来源于网络,答案系个人结合5月份面试了近30家公司整理所得,最后附录参考原文链接,如有遗漏的原文出处请联系本人。不对之处望批评指正,答案需要加上自己的思考,最好是代码实践下。
+
+参与过面试的企业有:zg人寿,睿科lun,qi猫,yun汉商城,zi节跳动,特斯la,虾P,chuan音,qi安信,ai立信等大大小小企业近30家,BAT简历都过不了。
+
+## 面试建议
+
+### 技术部分
+
+1)算法部分,刷LeetCode就完事了,这是一个长期的过程,短期突击没啥效果,因为题目太多了。
+
+2)语言基础,细分为:
+
+* golang基础及原理,就是本文主要内容了;
+* mysql基础及原理;
+* redis基础及原理;
+* kafka或其他消息中间件(如果没用过,需要了解大概的底层原理及结构);
+* linux常用的命令,比如定时脚本几个参数时间分别代表啥,文件权限需要搞清楚,进程内存占用命令;小公司还要懂一些前端的知识,因为他们希望你什么都会。
+
+3)项目经验,可以搞一个基于gin的后端接口服务的web框架,一般会问你怎么实现的;以及微服务了解一下。
+
+### 非技术部分
+
+1)因为上海5月份居家办公,远程面试,这些题目准备一份,遇到卡壳的题目完全可以参考你准备好的答案,因为视频面试你眼睛是看着面试官还是题目是不太容易区分的(把题目窗口置顶)。
+
+2)HR面也可以完全准备一份可能问到的问题的答案,并不是说你不会回答,而是会让你的表达更顺畅,其次也说明你是有备而来的,我在某拉公司面试就吃了这个亏,技术通过,HR说我的表达能力不行(后续我也会把这个模板分享出来,感谢我媳妇充当面试官,以及指导如何高情商的回答HR的问题)。
+
+3)可以自己录音面试回答,看看自己的语气、音量,顺畅度,如果自己听了都不舒服,面试官可能也不舒服。
+
+## 一、基础部分
+
+#### 1、golang 中 make 和 new 的区别?(基本必问)
+
+共同点:给变量分配内存
+
+不同点:
+
+* 作用变量类型不同,new给string,int和数组分配内存,make给切片,map,channel分配内存;
+
+* 返回类型不一样,new返回指向变量的指针,make返回变量本身;
+
+* new 分配的空间被清零。make 分配空间后,会进行初始化;
+
+* 字节的面试官还说了另外一个区别,就是分配的位置,在堆上还是在栈上?这块我比较模糊,大家可以自己探究下,我搜索出来的答案是golang会弱化分配的位置的概念,因为编译的时候会自动内存逃逸处理,懂的大佬帮忙补充下:make、new内存分配是在堆上还是在栈上?
+
+#### string 底层数据结构
+
+```go
+// from: string.go 在GoLand IDE中双击shift快速找到
+type stringStruct struct {
+ array unsafe.Pointer // 指向一个 [len]byte 的数组
+ length int // 长度
+}
+```
+
+#### []string 和 []byte 的区别
+
+string
+
+1 .是一个指针,指向某个数组的首地址
+[]byte
+
+1 .是一个切片slice。一个封装了数组的结构体
+2 .slice结构
+```go
+type slice struct {
+ array unsafe.Pointer
+ len int
+ cap int
+```
+使用场景
+
+1 .想要在本身原地修改,就只能使用[]byte
+2 .string不能为nil,想要返回nil表达特殊含义,只能使用[]byte
+3 .string可以直接比较,而[]byte不可以,所以[]byte不可以当map的key值。
+4 .因为无法修改string中的某个字符,需要粒度小到操作一个字符时,用[]byte
+5 .[]byte切片这么灵活,想要用切片的特性就用[]byte
+6 .需要大量字符串处理的时候用[]byte,性能好很多
+区别
+
+1 .string的指针指向的内容是不可以改变的,所以每次更改一次字符串,都需要重新分配内存。之前的内存还需要GC收回,这是导致string效率底下的根本原因
+2 .如果我们保存的字符在 ASCII 表的,比如[0-1, a-z,A-Z..]直接可以保存到 byte
+3 .如果我们保存的字符对应码值大于 255,这时我们可以考虑使用 int 类型保存
+
+#### 2、数组和切片的区别 (基本必问)
+
+相同点:
+
+* 只能存储一组相同类型的数据结构
+
+* 都是通过下标来访问,并且有容量长度,长度通过 len 获取,容量通过 cap 获取
+
+区别:
+
+* 数组是定长,访问和复制不能超过数组定义的长度,否则就会下标越界,切片长度和容量可以自动扩容
+
+* 数组是值类型,切片是引用类型,每个切片都引用了一个底层数组,切片本身不能存储任何数据,都是这底层数组存储数据,所以修改切片的时候修改的是底层数组中的数据。切片一旦扩容,指向一个新的底层数组,内存地址也就随之改变
+
+简洁的回答:
+
+1)定义方式不一样 2)初始化方式不一样,数组需要指定大小,大小不改变 3)函数的传递方式不一样,数组传递的是值,切片传的是地址。
+
+数组的定义
+
+```go
+var a1 [3]int
+
+var a2 [...]int{1,2,3}
+```
+
+切片的定义
+
+```go
+var a1 []int
+
+var a2 :=make([]int,3,5)
+```
+
+数组的初始化
+
+```go
+a1 := [...]int{1,2,3}
+
+a2 := [5]int{1,2,3}
+```
+
+切片的初始化
+
+```go
+b:= make([]int,3,5)
+```
+#### 3、for range 的时候它的地址会发生变化么?
+
+答:在 `for a,b := range c` 遍历中, a 和 b 在内存中只会存在一份,即之后每次循环时遍历到的数据都是以值覆盖的方式赋给 a 和 b,a,b 的内存地址始终不变。由于有这个特性,**「for 循环里面如果开协程,不要直接把 a 或者 b 的地址传给协程」**。解决办法:在每次循环时,创建一个临时变量。
+
+#### 4、go defer,多个 defer 的顺序,defer 在什么时机会修改返回值?
+
+作用:defer延迟函数,释放资源,收尾工作;如释放锁,关闭文件,关闭链接;捕获panic;
+
+避坑指南:defer函数紧跟在资源打开后面,否则defer可能得不到执行,导致内存泄露。
+
+多个 defer 调用顺序是 LIFO(后入先出),defer后的操作可以理解为压入栈中
+
+解析:函数的 return 语句并不是原子级的,实际上 return 语句只代理汇编指令 ret。defer 语句是在返回前执行,所以返回过程是:「设置返回值—>执行defer—>ret」。defer可以修改函数最终返回值,修改时机:有名返回值或者函数返回指针 参考:[Go高阶指南07,一文搞懂 defer 实现原理)](https://mp.weixin.qq.com/s?__biz=Mzk0NzI3Mjk1Mg==&mid=2247484683&idx=1&sn=262b205caeef06489e5ac8cf84d44112&chksm=c378289cf40fa18aef1d26e1437232b6b20a2f1f7129951d3b094eefa04007ee3fcd0af4655f&token=1319045915&lang=zh_CN&scene=21#wechat_redirect)
+
+有名返回值
+
+```go
+func b() (i int) {
+ defer func() {
+ i++
+ fmt.Println("defer2:", i)
+ }()
+ defer func() {
+ i++
+ fmt.Println("defer1:", i)
+ }()
+ return i //或者直接写成return
+}
+func main() {
+ fmt.Println("return:", b())
+}
+
+//运行结果:
+//defer1: 1
+//defer2: 2
+//return: 2
+```
+
+函数返回指针
+
+```go
+func c() *int {
+ var i int
+ defer func() {
+ i++
+ fmt.Println("defer2:", i)
+ }()
+ defer func() {
+ i++
+ fmt.Println("defer1:", i)
+ }()
+ return &i
+}
+func main() {
+ fmt.Println("return:", *(c()))
+}
+
+//运行结果:
+//defer1: 1
+//defer2: 2
+//return: 2
+```
+#### 5、uint 类型溢出问题
+
+超过最大存储值如uint8最大是255
+
+var a uint8 =255
+
+var b uint8 =1
+
+a+b = 0总之类型溢出会出现难以意料的事
+
+
+#### 6、能介绍下 rune 类型吗?
+
+相当int32
+
+golang中的字符串底层实现是通过byte数组的,中文字符在unicode下占2个字节,在utf-8编码下占3个字节,而golang默认编码正好是utf-8
+
+byte 等同于int8,常用来处理ascii字符
+
+rune 等同于int32,常用来处理unicode或utf-8字符
+
+
+#### 7、 golang 中解析 tag 是怎么实现的?反射原理是什么?(中高级肯定会问,比较难,需要自己多去总结)
+
+参考如下连接golang中struct关于反射tag (https://blog.csdn.net/paladinosment/article/details/42570937)
+
+```go
+type User struct {
+ name string `json:name-field`
+ age int
+}
+func main() {
+ user := &User{"John Doe The Fourth", 20}
+
+ field, ok := reflect.TypeOf(user).Elem().FieldByName("name")
+ if !ok {
+ panic("Field not found")
+ }
+ fmt.Println(getStructTag(field))
+}
+
+func getStructTag(f reflect.StructField) string {
+ return string(f.Tag)
+}
+```
+
+o 中解析的 tag 是通过反射实现的,反射是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力或动态知道给定数据对象的类型和结构,并有机会修改它。反射将接口变量转换成反射对象 Type 和 Value;反射可以通过反射对象 Value 还原成原先的接口变量;反射可以用来修改一个变量的值,前提是这个值可以被修改;tag是啥:结构体支持标记,name string `json:name-field` 就是 `json:name-field` 这部分
+
+gorm json yaml gRPC protobuf gin.Bind()都是通过反射来实现的
+
+#### 8、调用函数传入结构体时,应该传值还是指针?(Golang 都是传值)
+
+Go 的函数参数传递都是值传递。所谓值传递:指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。参数传递还有引用传递,所谓引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数
+
+因为 Go 里面的 map,slice,chan 是引用类型。变量区分值类型和引用类型。
+所谓值类型:变量和变量的值存在同一个位置。
+所谓引用类型:变量和变量的值是不同的位置,变量的值存储的是对值的引用。
+但并不是 map,slice,chan 的所有的变量在函数内都能被修改,不同数据类型的底层存储结构和实现可能不太一样,情况也就不一样。
+
+#### 9、讲讲 Go 的 slice 底层数据结构和一些特性?
+
+答:Go 的 slice 底层数据结构是由一个 array 指针指向底层数组,len 表示切片长度,cap 表示切片容量。slice 的主要实现是扩容。对于 append 向 slice 添加元素时,假如 slice 容量够用,则追加新元素进去,slice.len++,返回原来的 slice。当原容量不够,则 slice 先扩容,扩容之后 slice 得到新的 slice,将元素追加进新的 slice,slice.len++,返回新的 slice。对于切片的扩容规则:当切片比较小时(容量小于 1024),则采用较大的扩容倍速进行扩容(新的扩容会是原来的 2 倍),避免频繁扩容,从而减少内存分配的次数和数据拷贝的代价。当切片较大的时(原来的 slice 的容量大于或者等于 1024),采用较小的扩容倍速(新的扩容将扩大大于或者等于原来 1.25 倍),主要避免空间浪费,网上其实很多总结的是 1.25 倍,那是在不考虑内存对齐的情况下,实际上还要考虑内存对齐,扩容是大于或者等于 1.25 倍。
+
+(关于刚才问的 slice 为什么传到函数内可能被修改,如果 slice 在函数内没有出现扩容,函数外和函数内 slice 变量指向是同一个数组,则函数内复制的 slice 变量值出现更改,函数外这个 slice 变量值也会被修改。如果 slice 在函数内出现扩容,则函数内变量的值会新生成一个数组(也就是新的 slice,而函数外的 slice 指向的还是原来的 slice,则函数内的修改不会影响函数外的 slice。)
+
+#### 10、讲讲 Go 的 select 底层数据结构和一些特性?(难点,没有项目经常可能说不清,面试一般会问你项目中怎么使用select)
+
+答:go 的 select 为 golang 提供了多路 IO 复用机制,和其他 IO 复用一样,用于检测是否有读写事件是否 ready。linux 的系统 IO 模型有 select,poll,epoll,go 的 select 和 linux 系统 select 非常相似。
+
+select 结构组成主要是由 case 语句和执行的函数组成 select 实现的多路复用是:每个线程或者进程都先到注册和接受的 channel(装置)注册,然后阻塞,然后只有一个线程在运输,当注册的线程和进程准备好数据后,装置会根据注册的信息得到相应的数据。
+
+select 的特性
+
+* select 操作至少要有一个 case 语句,出现读写 nil 的 channel 该分支会忽略,在 nil 的 channel 上操作则会报错。
+* select 仅支持管道,而且是单协程操作。
+* 每个 case 语句仅能处理一个管道,要么读要么写。
+* 多个 case 语句的执行顺序是随机的。
+* 存在 default 语句,select 将不会阻塞,但是存在 default 会影响性能。
+
+- select 和 channel 的使用场景:https://blog.csdn.net/u011240877/article/details/123611525
+
+#### 11、讲讲 Go 的 defer 底层数据结构和一些特性?
+
+答:每个 defer 语句都对应一个_defer 实例,多个实例使用指针连接起来形成一个单连表,保存在 gotoutine 数据结构中,每次插入_defer 实例,均插入到链表的头部,函数结束再一次从头部取出,从而形成后进先出的效果。
+
+defer 的规则总结:
+
+* 延迟函数的参数是 defer 语句出现的时候就已经确定了的。
+* 延迟函数执行按照后进先出的顺序执行,即先出现的 defer 最后执行。
+* 延迟函数可能操作主函数的返回值。
+* 申请资源后立即使用 defer 关闭资源是个好习惯。
+
+#### 12、单引号,双引号,反引号的区别?
+
+单引号,表示byte类型或rune类型,对应 uint8和int32类型,默认是 rune 类型。byte用来强调数据是raw data,而不是数字;而rune用来表示Unicode的code point。
+
+双引号,才是字符串,实际上是字符数组。可以用索引号访问某字节,也可以用len()函数来获取字符串所占的字节长度。
+
+反引号,表示字符串字面量,但不支持任何转义序列。字面量 raw literal string 的意思是,你定义时写的啥样,它就啥样,你有换行,它就换行。你写转义字符,它也就展示转义字符。
+
+# 二、map相关
+
+#### 1、map 使用注意的点,是否并发安全?
+
+map的类型是map[key],key类型的ke必须是可比较的,通常情况,会选择内建的基本类型,比如整数、字符串做key的类型。如果要使用struct作为key,要保证struct对象在逻辑上是不可变的。在Go语言中,map[key]函数返回结果可以是一个值,也可以是两个值。map是无序的,如果我们想要保证遍历map时元素有序,可以使用辅助的数据结构,例如orderedmap。
+
+第一,一定要先初始化,否则panic
+
+第二,map类型是容易发生并发访问问题的。不注意就容易发生程序运行时并发读写导致的panic。Go语言内建的map对象不是线程安全的,并发读写的时候运行时会有检查,遇到并发问题就会导致panic。
+
+#### 2、map 循环是有序的还是无序的?
+
+无序的, map 因扩张⽽重新哈希时,各键值项存储位置都可能会发生改变,顺序自然也没法保证了,所以官方避免大家依赖顺序,直接打乱处理。就是 for range map 在开始处理循环逻辑的时候,就做了随机播种
+
+#### 3、 map 中删除一个 key,它的内存会释放么?(常问)
+
+如果删除的元素是值类型,如int,float,bool,string以及数组和struct,map的内存不会自动释放
+
+如果删除的元素是引用类型,如指针,slice,map,chan等,map的内存会自动释放,但释放的内存是子元素应用类型的内存占用
+
+将map设置为nil后,内存被回收。
+
+这个问题还需要大家去搜索下答案,我记得有不一样的说法,谨慎采用本题答案。
+
+#### 4、怎么处理对 map 进行并发访问?有没有其他方案?区别是什么?
+
+方式一、使用内置sync.Map,详细参考#### golang sync.map 原理和使用 #### (https://yebd1h.smartapps.cn/pages/blog/index?_swebFromHost=baiduboxapp&blogId=114628932&_swebfr=1)
+
+方式二、使用读写锁实现并发安全mapgolang线程安全map (https://yebd1h.smartapps.cn/pages/blog/index?_swebFromHost=baiduboxapp&blogId=123259483&_swebfr=1)
+
+#### 5、 nil map 和空 map 有何不同?
+
+1. 可以对未初始化的map进行取值,但取出来的东西是空:
+
+```go
+var m1 map[string]string
+
+fmt.Println(m1["1"])
+```
+
+1. 不能对未初始化的map进行赋值,这样将会抛出一个异常:
+
+```go
+var m1 map[string]string
+
+m1["1"] = "1"
+
+panic: assignment to entry in nil map
+```
+
+1. 通过fmt打印map时,空map和nil map结果是一样的,都为map[]。所以,这个时候别断定map是空还是nil,而应该通过map == nil来判断。
+ nil map 未初始化,空map是长度为空
+
+#### 6、map 的数据结构是什么?是怎么实现扩容?
+
+答:golang 中 map 是一个 kv 对集合。底层使用 hash table,用链表来解决冲突 ,出现冲突时,不是每一个 key 都申请一个结构通过链表串起来,而是以 bmap 为最小粒度挂载,一个 bmap 可以放 8 个 kv。在哈希函数的选择上,会在程序启动时,检测 cpu 是否支持 aes,如果支持,则使用 aes hash,否则使用 memhash。每个 map 的底层结构是 hmap,是有若干个结构为 bmap 的 bucket 组成的数组。每个 bucket 底层都采用链表结构。
+
+hmap 的结构如下:
+
+```go
+type hmap struct {
+ count int // 元素个数
+ flags uint8
+ B uint8 // 扩容常量相关字段B是buckets数组的长度的对数 2^B
+ noverflow uint16 // 溢出的bucket个数
+ hash0 uint32 // hash seed
+ buckets unsafe.Pointer // buckets 数组指针
+ oldbuckets unsafe.Pointer // 结构扩容的时候用于赋值的buckets数组
+ nevacuate uintptr // 搬迁进度
+ extra *mapextra // 用于扩容的指针
+}
+```
+
+map 的容量大小
+
+底层调用 makemap 函数,计算得到合适的 B,map 容量最多可容纳 6.52^B 个元素,6.5 为装载因子阈值常量。装载因子的计算公式是:装载因子=填入表中的元素个数/散列表的长度,装载因子越大,说明空闲位置越少,冲突越多,散列表的性能会下降。底层调用 makemap 函数,计算得到合适的 B,map 容量最多可容纳 6.52^B 个元素,6.5 为装载因子阈值常量。装载因子的计算公式是:装载因子=填入表中的元素个数/散列表的长度,装载因子越大,说明空闲位置越少,冲突越多,散列表的性能会下降。
+
+触发 map 扩容的条件
+
+* 装载因子超过阈值,源码里定义的阈值是 6.5。
+* overflow 的 bucket 数量过多 map 的 bucket 定位和 key 的定位高八位用于定位 bucket,低八位用于定位 key,快速试错后再进行完整对比。
+
+#### 7、slices能作为map类型的key吗?
+
+当时被问得一脸懵逼,其实是这个问题的变种:golang 哪些类型可以作为map key?
+
+答案是:在golang规范中,可比较的类型都可以作为map key;这个问题又延伸到在:golang规范中,哪些数据类型可以比较?
+
+不能作为map key 的类型包括:
+
+slices maps functions
+
+# 三、context相关
+
+#### 1、context 结构是什么样的?context 使用场景和用途?
+
+(难,也常常问你项目中怎么用,光靠记答案很难让面试官满意,反正有各种结合实际的问题)
+
+参考链接:go context详解 (https://www.cnblogs.com/juanmaofeifei/p/14439957.html)
+
+答:Go 的 Context 的数据结构包含 Deadline,Done,Err,Value,Deadline 方法返回一个 time.Time,表示当前 Context 应该结束的时间,ok 则表示有结束时间,Done 方法当 Context 被取消或者超时时候返回的一个 close 的 channel,告诉给 context 相关的函数要停止当前工作然后返回了,Err 表示 context 被取消的原因,Value 方法表示 context 实现共享数据存储的地方,是协程安全的。context 在业务中是经常被使用的,
+
+其主要的应用 :
+
+1:上下文控制,2:多个 goroutine 之间的数据交互等,3:超时控制:到某个时间点超时,过多久超时。
+
+# 四、channel相关
+
+#### 1、channel 是否线程安全?锁用在什么地方?
+
+
+
+#### 2、go channel 的底层实现原理 (数据结构)
+
+
+
+底层结构需要描述出来,这个简单,buf,发送队列,接收队列,lock。
+
+#### 3、nil、关闭的 channel、有数据的 channel,再进行读、写、关闭会怎么样?(各类变种题型,重要)
+
+
+
+还要去了解一下单向channel,如只读或者只写通道常见的异常问题,这块还需要大家自己总结总结,有懂的大佬也可以评论发送答案。
+
+#### 4、向 channel 发送数据和从 channel 读数据的流程是什么样的?
+
+发送流程:
+
+
+
+接收流程:
+
+这个没啥好说的,底层原理,1、2、3描述出来,保证面试官满意。具体的文字描述下面一题有,channel的概念多且复杂,脑海中有个总分的概念,否则你说的再多,面试官也抓不住你说的重点,等于白说。问题5已经为大家总结好了。
+
+#### 5、讲讲 Go 的 chan 底层数据结构和主要使用场景
+
+答:channel 的数据结构包含 qccount 当前队列中剩余元素个数,dataqsiz 环形队列长度,即可以存放的元素个数,buf 环形队列指针,elemsize 每个元素的大小,closed 标识关闭状态,elemtype 元素类型,sendx 队列下表,指示元素写入时存放到队列中的位置,recv 队列下表,指示元素从队列的该位置读出。recvq 等待读消息的 goroutine 队列,sendq 等待写消息的 goroutine 队列,lock 互斥锁,chan 不允许并发读写。
+
+无缓冲和有缓冲区别:管道没有缓冲区,从管道读数据会阻塞,直到有协程向管道中写入数据。同样,向管道写入数据也会阻塞,直到有协程从管道读取数据。管道有缓冲区但缓冲区没有数据,从管道读取数据也会阻塞,直到协程写入数据,如果管道满了,写数据也会阻塞,直到协程从缓冲区读取数据。
+
+channel 的一些特点 1)、读写值 nil 管道会永久阻塞 2)、关闭的管道读数据仍然可以读数据 3)、往关闭的管道写数据会 panic 4)、关闭为 nil 的管道 panic 5)、关闭已经关闭的管道 panic
+
+向 channel 写数据的流程:如果等待接收队列 recvq 不为空,说明缓冲区中没有数据或者没有缓冲区,此时直接从 recvq 取出 G,并把数据写入,最后把该 G 唤醒,结束发送过程;如果缓冲区中有空余位置,将数据写入缓冲区,结束发送过程;如果缓冲区中没有空余位置,将待发送数据写入 G,将当前 G 加入 sendq,进入睡眠,等待被读 goroutine 唤醒;
+
+向 channel 读数据的流程:如果等待发送队列 sendq 不为空,且没有缓冲区,直接从 sendq 中取出 G,把 G 中数据读出,最后把 G 唤醒,结束读取过程;如果等待发送队列 sendq 不为空,此时说明缓冲区已满,从缓冲区中首部读出数据,把 G 中数据写入缓冲区尾部,把 G 唤醒,结束读取过程;如果缓冲区中有数据,则从缓冲区取出数据,结束读取过程;将当前 goroutine 加入 recvq,进入睡眠,等待被写 goroutine 唤醒;
+
+使用场景:消息传递、消息过滤,信号广播,事件订阅与广播,请求、响应转发,任务分发,结果汇总,并发控制,限流,同步与异步
+
+# 五、GMP相关
+
+#### 1、什么是 GMP?(必问)
+
+答:G 代表着 goroutine,P 代表着上下文处理器,M 代表 thread 线程,在 GPM 模型,有一个全局队列(Global Queue):存放等待运行的 G,还有一个 P 的本地队列:也是存放等待运行的 G,但数量有限,不超过 256 个。GPM 的调度流程从 go func()开始创建一个 goroutine,新建的 goroutine 优先保存在 P 的本地队列中,如果 P 的本地队列已经满了,则会保存到全局队列中。M 会从 P 的队列中取一个可执行状态的 G 来执行,如果 P 的本地队列为空,就会从其他的 MP 组合偷取一个可执行的 G 来执行,当 M 执行某一个 G 时候发生系统调用或者阻塞,M 阻塞,如果这个时候 G 在执行,runtime 会把这个线程 M 从 P 中摘除,然后创建一个新的操作系统线程来服务于这个 P,当 M 系统调用结束时,这个 G 会尝试获取一个空闲的 P 来执行,并放入到这个 P 的本地队列,如果这个线程 M 变成休眠状态,加入到空闲线程中,然后整个 G 就会被放入到全局队列中。
+
+关于 G,P,M 的个数问题,G 的个数理论上是无限制的,但是受内存限制,P 的数量一般建议是逻辑 CPU 数量的 2 倍,M 的数据默认启动的时候是 10000,内核很难支持这么多线程数,所以整个限制客户忽略,M 一般不做设置,设置好 P,M 一般都是要大于 P。
+
+#### 2、进程、线程、协程有什么区别?(必问)
+
+进程:是应用程序的启动实例,每个进程都有独立的内存空间,不同的进程通过进程间的通信方式来通信。
+
+线程:从属于进程,每个进程至少包含一个线程,线程是 CPU 调度的基本单位,多个线程之间可以共享进程的资源并通过共享内存等线程间的通信方式来通信。
+
+协程:为轻量级线程,与线程相比,协程不受操作系统的调度,协程的调度器由用户应用程序提供,协程调度器按照调度策略把协程调度到线程中运行
+
+#### 3、抢占式调度是如何抢占的?
+
+基于协作式抢占
+
+基于信号量抢占
+
+就像操作系统要负责线程的调度一样,Go的runtime要负责goroutine的调度。现代操作系统调度线程都是抢占式的,我们不能依赖用户代码主动让出CPU,或者因为IO、锁等待而让出,这样会造成调度的不公平。基于经典的时间片算法,当线程的时间片用完之后,会被时钟中断给打断,调度器会将当前线程的执行上下文进行保存,然后恢复下一个线程的上下文,分配新的时间片令其开始执行。这种抢占对于线程本身是无感知的,系统底层支持,不需要开发人员特殊处理。
+
+基于时间片的抢占式调度有个明显的优点,能够避免CPU资源持续被少数线程占用,从而使其他线程长时间处于饥饿状态。goroutine的调度器也用到了时间片算法,但是和操作系统的线程调度还是有些区别的,因为整个Go程序都是运行在用户态的,所以不能像操作系统那样利用时钟中断来打断运行中的goroutine。也得益于完全在用户态实现,goroutine的调度切换更加轻量。
+
+上面这两段文字只是对调度的一个概括,具体的协作式调度、信号量调度大家还需要去详细了解,这偏底层了,大厂或者中高级开发会问。(字节就问了)
+
+#### 4、M 和 P 的数量问题?
+
+p默认cpu内核数
+
+M与P的数量没有绝对关系,一个M阻塞,P就会去创建或者切换另一个M,所以,即使P的默认数量是1,也有可能会创建很多个M出来
+
+【Go语言调度模型G、M、P的数量多少合适?】
+
+详细参考这篇文章Go语言调度模型G、M、P的数量多少合适? (https://zoyi14.smartapps.cn/pages/note/index?_swebFromHost=baiduboxapp&origin=share&slug=1a50330adf1b&_swebfr=1)
+
+GMP数量这一块,结论很好记,没用项目经验的话,问了项目中怎么用可能容易卡壳。
+
+# 六、锁相关
+
+#### 1、除了 mutex 以外还有哪些方式安全读写共享变量?
+
+* 将共享变量的读写放到一个 goroutine 中,其它 goroutine 通过 channel 进行读写操作。
+
+* 可以用个数为 1 的信号量(semaphore)实现互斥
+
+* 通过 Mutex 锁实现
+
+#### 2、Go 如何实现原子操作?
+
+答:原子操作就是不可中断的操作,外界是看不到原子操作的中间状态,要么看到原子操作已经完成,要么看到原子操作已经结束。在某个值的原子操作执行的过程中,CPU 绝对不会再去执行其他针对该值的操作,那么其他操作也是原子操作。
+
+Go 语言的标准库代码包 sync/atomic 提供了原子的读取(Load 为前缀的函数)或写入(Store 为前缀的函数)某个值(这里细节还要多去查查资料)。
+
+原子操作与互斥锁的区别
+
+* 互斥锁是一种数据结构,用来让一个线程执行程序的关键部分,完成互斥的多个操作。
+* 原子操作是针对某个值的单个互斥操作。
+* Mutex 是悲观锁还是乐观锁?悲观锁、乐观锁是什么?
+
+悲观锁
+
+悲观锁:当要对数据库中的一条数据进行修改的时候,为了避免同时被其他人修改,最好的办法就是直接对该数据进行加锁以防止并发。这种借助数据库锁机制,在修改数据之前先锁定,再修改的方式被称之为悲观并发控制【Pessimistic Concurrency Control,缩写“PCC”,又名“悲观锁”】。
+
+乐观锁
+
+乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量
+
+#### 4、Mutex 有几种模式?
+
+1. 正常模式
+
+当前的mutex只有一个goruntine来获取,那么没有竞争,直接返回。新的goruntine进来,如果当前mutex已经被获取了,则该goruntine进入一个先入先出的waiter队列,在mutex被释放后,waiter按照先进先出的方式获取锁。该goruntine会处于自旋状态(不挂起,继续占有cpu)。新的goruntine进来,mutex处于空闲状态,将参与竞争。新来的 goroutine 有先天的优势,它们正在 CPU 中运行,可能它们的数量还不少,所以,在高并发情况下,被唤醒的 waiter 可能比较悲剧地获取不到锁,这时,它会被插入到队列的前面。如果 waiter 获取不到锁的时间超过阈值 1 毫秒,那么,这个 Mutex 就进入到了饥饿模式。
+
+1. 饥饿模式
+
+在饥饿模式下,Mutex 的拥有者将直接把锁交给队列最前面的 waiter。新来的 goroutine 不会尝试获取锁,即使看起来锁没有被持有,它也不会去抢,也不会 spin(自旋),它会乖乖地加入到等待队列的尾部。如果拥有 Mutex 的 waiter 发现下面两种情况的其中之一,它就会把这个 Mutex 转换成正常模式:
+
+此 waiter 已经是队列中的最后一个 waiter 了,没有其它的等待锁的 goroutine 了;此 waiter 的等待时间小于 1 毫秒。
+
+#### 5、goroutine 的自旋占用资源如何解决
+
+自旋锁是指当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断地判断是否能够被成功获取,直到获取到锁才会退出循环。
+
+自旋的条件如下:
+
+* 还没自旋超过 4 次,
+* 多核处理器,
+* GOMAXPROCS > 1,
+* p 上本地 goroutine 队列为空。
+
+mutex 会让当前的 goroutine 去空转 CPU,在空转完后再次调用 CAS 方法去尝试性的占有锁资源,直到不满足自旋条件,则最终会加入到等待队列里。
+
+# 七、并发相关
+
+#### 1、怎么控制并发数?
+
+1. 有缓冲通道
+
+根据通道中没有数据时读取操作陷入阻塞和通道已满时继续写入操作陷入阻塞的特性,正好实现控制并发数量。
+
+```go
+func main() {
+ count := 10 // 最大支持并发
+ sum := 100 // 任务总数
+ wg := sync.WaitGroup{} //控制主协程等待所有子协程执行完之后再退出。
+
+ c := make(chan struct{}, count) // 控制任务并发的chan
+ defer close(c)
+
+ for i:=0; i接口,自动垃圾回收和goroutine等让人**拍案叫绝**的设计。
+
+有许多基于Go的优秀项目。Docker,Kubernetes,etcd,deis,flynn,lime,revel等等。Go无疑是**云时代的最好语言**!
+
+题外话到此为止,在面试中,我们需要深入了解Go**语言特性**,并适当辅以**源码阅读**(Go源码非常**人性化,注释非常详细,**基本上只要你学过Go就能看懂)来提升能力。常考的点包括:切片,通道,异常处理,Goroutine,GMP模型,字符串高效拼接,指针,反射,接口,sync,go test和相关工具链。
+
+一切问题的最权威的回答一定来自**官方**,这里力荐golang官方FAQ,虽然是英文的,但是也希望你花3-4天看完。**从使用者的角度去提问题, 从设计者的角度回答问题**。
+
+官方FAQ问题
+ [https://golang.org/doc/faqgolang.org/doc/faq](https://link.zhihu.com/?target=https%3A//golang.org/doc/faq)
+
+**面试题都是来源于网上和自己平时遇到的,但是很少有解答的版本,所以我专门回答了一下,放在专栏。**
+
+【所有试题已注明来源,侵删】
+
+* * *
+
+## **面试题1**
+
+来源:[geektutu](https://link.zhihu.com/?target=https%3A//geektutu.com/post/qa-golang.html)
+
+### 基础语法
+
+### 01 `=` 和 `:=` 的区别?
+
+=是赋值变量,:=是定义变量。
+
+### 02 指针的作用
+
+一个指针可以指向任意变量的地址,它所指向的地址在32位或64位机器上分别**固定**占4或8个字节。指针的作用有:
+
+* 获取变量的值
+
+ ```go
+import fmt
+
+ func main(){
+ a := 1
+ p := &a//取址&
+ fmt.Printf("%d\n", *p);//取值*
+ }
+```
+
+* 改变变量的值
+
+ ```go
+// 交换函数
+ func swap(a, b *int) {
+ *a, *b = *b, *a
+ }
+```
+
+* 用指针替代值传入函数,比如类的接收器就是这样的。
+
+ ```go
+type A struct{}
+
+ func (a *A) fun(){}
+```
+
+### 03 Go 允许多个返回值吗?
+
+可以。通常函数除了一般返回值还会返回一个error。
+
+### 04 Go 有异常类型吗?
+
+有。Go用error类型代替try...catch语句,这样可以节省资源。同时增加代码可读性:
+
+ ```go
+_,err := errorDemo()
+ if err!=nil{
+ fmt.Println(err)
+ return
+ }
+```
+
+也可以用errors.New()来定义自己的异常。errors.Error()会返回异常的字符串表示。只要实现error接口就可以定义自己的异常,
+
+ ```go
+type errorString struct {
+ s string
+ }
+
+ func (e *errorString) Error() string {
+ return e.s
+ }
+
+ // 多一个函数当作构造函数
+ func New(text string) error {
+ return &errorString{text}
+ }
+```
+
+### 05 什么是协程(Goroutine)
+
+协程是**用户态轻量级线程**,它是**线程调度的基本单位**。通常在函数前加上go关键字就能实现并发。一个Goroutine会以一个很小的栈启动2KB或4KB,当遇到栈空间不足时,栈会**自动伸缩**, 因此可以轻易实现成千上万个goroutine同时启动。
+
+### 06 ❤ 如何高效地拼接字符串
+
+拼接字符串的方式有:"+", fmt.Sprintf, strings.Builder, bytes.Buffer, strings.Join
+
+1 "+"
+
+使用`+`操作符进行拼接时,会对字符串进行遍历,计算并开辟一个新的空间来存储原来的两个字符串。
+
+2 fmt.Sprintf
+
+由于采用了接口参数,必须要用反射获取值,因此有性能损耗。
+
+3 strings.Builder:
+
+用WriteString()进行拼接,内部实现是指针+切片,同时String()返回拼接后的字符串,它是直接把[]byte转换为string,从而避免变量拷贝。
+
+`strings.builder`的实现原理很简单,结构如下:
+
+```go
+type Builder struct {
+ addr *Builder // of receiver, to detect copies by value
+ buf []byte // 1
+ }
+```
+
+`addr`字段主要是做`copycheck`,`buf`字段是一个`byte`类型的切片,这个就是用来存放字符串内容的,提供的`writeString()`方法就是像切片`buf`中追加数据:
+
+```go
+func (b *Builder) WriteString(s string) (int, error) {
+ b.copyCheck()
+ b.buf = append(b.buf, s...)
+ return len(s), nil
+ }
+```
+
+提供的`String`方法就是将`[]byte`转换为`string`类型,这里为了避免内存拷贝的问题,使用了强制转换来避免内存拷贝:
+
+```go
+func (b *Builder) String() string {
+ return *(*string)(unsafe.Pointer(&b.buf))
+ }
+```
+
+4 bytes.Buffer
+
+`bytes.Buffer`是一个一个缓冲`byte`类型的缓冲器,这个缓冲器里存放着都是`byte`。使用方式如下:
+
+`bytes.buffer`底层也是一个`[]byte`切片,结构体如下:
+
+```go
+type Buffer struct {
+ buf []byte // contents are the bytes buf[off : len(buf)]
+ off int // read at &buf[off], write at &buf[len(buf)]
+ lastRead readOp // last read operation, so that Unread* can work correctly.
+}
+```
+
+因为`bytes.Buffer`可以持续向`Buffer`尾部写入数据,从`Buffer`头部读取数据,所以`off`字段用来记录读取位置,再利用切片的`cap`特性来知道写入位置,这个不是本次的重点,重点看一下`WriteString`方法是如何拼接字符串的:
+
+```go
+func (b *Buffer) WriteString(s string) (n int, err error) {
+ b.lastRead = opInvalid
+ m, ok := b.tryGrowByReslice(len(s))
+ if !ok {
+ m = b.grow(len(s))
+ }
+ return copy(b.buf[m:], s), nil
+}
+```
+
+切片在创建时并不会申请内存块,只有在往里写数据时才会申请,首次申请的大小即为写入数据的大小。如果写入的数据小于64字节,则按64字节申请。采用动态扩展`slice`的机制,字符串追加采用`copy`的方式将追加的部分拷贝到尾部,`copy`是内置的拷贝函数,可以减少内存分配。
+
+但是在将`[]byte`转换为`string`类型依旧使用了标准类型,所以会发生内存分配:
+
+```go
+func (b *Buffer) String() string {
+ if b == nil {
+ // Special case, useful in debugging.
+ return ""
+ }
+ return string(b.buf[b.off:])
+}
+```
+
+5 strings.join
+
+`strings.join`也是基于`strings.builder`来实现的,并且可以自定义分隔符,代码如下:
+
+```go
+func Join(elems []string, sep string) string {
+ switch len(elems) {
+ case 0:
+ return ""
+ case 1:
+ return elems[0]
+ }
+ n := len(sep) * (len(elems) - 1)
+ for i := 0; i < len(elems); i++ {
+ n += len(elems[i])
+ }
+
+ var b Builder
+ b.Grow(n)
+ b.WriteString(elems[0])
+ for _, s := range elems[1:] {
+ b.WriteString(sep)
+ b.WriteString(s)
+ }
+ return b.String()
+}
+```
+
+唯一不同在于在`join`方法内调用了`b.Grow(n)`方法,这个是进行初步的容量分配,而前面计算的n的长度就是我们要拼接的slice的长度,因为我们传入切片长度固定,所以提前进行容量分配可以减少内存分配,很高效。
+
+```go
+func main(){
+ a := []string{"a", "b", "c"}
+ //方式1: ret := a[0] + a[1] + a[2]
+ //方式2: ret := fmt.Sprintf("%s%s%s", a[0],a[1],a[2])
+ //方式3: var sb strings.Builder
+ sb.WriteString(a[0])
+ sb.WriteString(a[1])
+ sb.WriteString(a[2])
+ ret := sb.String()
+ //方式4: buf := new(bytes.Buffer)
+ buf.Write(a[0])
+ buf.Write(a[1])
+ buf.Write(a[2])
+ ret := buf.String()
+ //方式5: ret := strings.Join(a,"")
+}
+```
+
+总结:
+
+strings.Join ≈ strings.Builder > bytes.Buffer > "+" > fmt.Sprintf
+
+> 参考资料:[字符串拼接性能及原理 | Go 语言高性能编程 | 极客兔兔](https://link.zhihu.com/?target=https%3A//geektutu.com/post/hpg-string-concat.html)
+
+### 07 什么是 rune 类型
+
+ASCII 码只需要 7 bit 就可以完整地表示,但只能表示英文字母在内的128个字符,为了表示世界上大部分的文字系统,发明了 Unicode, 它是ASCII的超集,包含世界上书写系统中存在的所有字符,并为每个代码分配一个标准编号(称为Unicode CodePoint),在 Go 语言中称之为 rune,是 int32 类型的别名。
+
+Go 语言中,字符串的底层表示是 byte (8 bit) 序列,而非 rune (32 bit) 序列。
+
+```go
+sample := "我爱GO"
+runeSamp := []rune(sample)
+runeSamp[0] = '你'
+fmt.Println(string(runeSamp)) // "你爱GO" fmt.Println(len(runeSamp)) // 4
+```
+
+### 08 如何判断 map 中是否包含某个 key ?
+
+```go
+var sample map[int]int
+if _, ok := sample[10];ok{
+
+}else{
+
+}
+```
+
+### 09 Go 支持默认参数或可选参数吗?
+
+不支持。但是可以利用结构体参数,或者...传入参数切片。
+
+### 10 defer 的执行顺序
+
+defer执行顺序和调用顺序相反,类似于栈后进先出(LIFO)。
+
+defer在return之后执行,但在函数退出之前,defer可以修改返回值。下面是一个例子:
+
+```go
+func test() int {
+ i := 0
+ defer func() {
+ fmt.Println("defer1")
+ }()
+ defer func() {
+ i += 1
+ fmt.Println("defer2")
+ }()
+ return i
+}
+
+func main() {
+ fmt.Println("return", test())
+}
+// defer2 // defer1 // return 0
+```
+
+上面这个例子中,test返回值并没有修改,这是由于Go的返回机制决定的,执行Return语句后,Go会创建一个临时变量保存返回值。如果是有名返回(也就是指明返回值functest()(i int))
+
+```go
+func test() (i int) {
+ i = 0
+ defer func() {
+ i += 1
+ fmt.Println("defer2")
+ }()
+ return i
+}
+
+func main() {
+ fmt.Println("return", test())
+}
+// defer2 // return 1
+```
+
+这个例子中,返回值被修改了。对于有名返回值的函数,执行 return 语句时,并不会再创建临时变量保存,因此,defer 语句修改了 i,即对返回值产生了影响。
+
+### 11 如何交换 2 个变量的值?
+
+对于变量而言`a,b = b,a`; 对于指针而言`*a,*b = *b, *a`
+
+### 12 Go 语言 tag 的用处?
+
+tag可以为结构体成员提供属性。常见的:
+
+1. json序列化或反序列化时字段的名称
+2. db: sqlx模块中对应的数据库字段名
+3. form: gin框架中对应的前端的数据字段名
+4. binding: 搭配 form 使用, 默认如果没查找到结构体中的某个字段则不报错值为空, binding为 required 代表没找到返回错误给前端
+
+### 13 如何获取一个结构体的所有tag?
+
+利用反射:
+
+```go
+import reflect
+type Author struct {
+ Name int `json:Name`
+ Publications []string `json:Publication,omitempty`
+}
+
+func main() {
+ t := reflect.TypeOf(Author{})
+ for i := 0; i < t.NumField(); i++ {
+ name := t.Field(i).Name
+ s, _ := t.FieldByName(name)
+ fmt.Println(name, s.Tag)
+ }
+}
+```
+
+上述例子中,`reflect.TypeOf`方法获取对象的类型,之后`NumField()`获取结构体成员的数量。 通过`Field(i)`获取第i个成员的名字。 再通过其`Tag` 方法获得标签。
+
+### 14 如何判断 2 个字符串切片(slice) 是相等的?
+
+reflect.DeepEqual(), 但反射非常影响性能。
+
+### 15 结构体打印时,`%v` 和 `%+v` 的区别
+
+`%v`输出结构体各成员的值;
+
+`%+v`输出结构体各成员的**名称**和**值**;
+
+`%#v`输出结构体名称和结构体各成员的名称和值
+
+### 16 Go 语言中如何表示枚举值(enums)?
+
+在常量中用iota可以表示枚举。iota从0开始。
+
+```go
+const (
+ B = 1 << (10 * iota)
+ KiB
+ MiB
+ GiB
+ TiB
+ PiB
+ EiB
+)
+```
+
+### 17 空 struct{} 的用途
+
+* 用map模拟一个set,那么就要把值置为struct{},struct{}本身不占任何空间,可以避免任何多余的内存分配。
+
+```go
+type Set map[string]struct{}
+
+func main() {
+ set := make(Set)
+
+ for _, item := range []string{"A", "A", "B", "C"} {
+ set[item] = struct{}{}
+ }
+ fmt.Println(len(set)) // 3
+ if _, ok := set["A"]; ok {
+ fmt.Println("A exists") // A exists
+ }
+}
+```
+
+* 有时候给通道发送一个空结构体,channel<-struct{}{},也是节省了空间。
+
+```go
+func main() {
+ ch := make(chan struct{}, 1)
+ go func() {
+ <-ch
+ // do something
+ }()
+ ch <- struct{}{}
+ // ...
+}
+```
+
+* 仅有方法的结构体
+
+```go
+type Lamp struct{}
+```
+
+### **18 go里面的int和int32是同一个概念吗?**
+
+不是一个概念!千万不能混淆。go语言中的int的大小是和操作系统位数相关的,如果是32位操作系统,int类型的大小就是4字节。如果是64位操作系统,int类型的大小就是8个字节。除此之外uint也与操作系统有关。
+
+int8占1个字节,int16占2个字节,int32占4个字节,int64占8个字节。
+
+### 实现原理
+
+### 01 init() 函数是什么时候执行的?
+
+**简答**: 在main函数之前执行。
+
+**详细**:init()函数是go初始化的一部分,由runtime初始化每个导入的包,初始化不是按照从上到下的导入顺序,而是按照解析的依赖关系,没有依赖的包最先初始化。
+
+每个包首先初始化包作用域的常量和变量(常量优先于变量),然后执行包的`init()`函数。同一个包,甚至是同一个源文件可以有多个`init()`函数。`init()`函数没有入参和返回值,不能被其他函数调用,同一个包内多个`init()`函数的执行顺序不作保证。
+
+执行顺序:import –> const –> var –>`init()`–>`main()`
+
+一个文件可以有多个`init()`函数!
+
+### 02 ❤如何知道一个对象是分配在栈上还是堆上?
+
+Go和C++不同,Go局部变量会进行**逃逸分析**。如果**变量离开作用域后没有被引用**,则**优先**分配到栈上,否则分配到堆上。那么如何判断是否发生了逃逸呢?
+
+`go build -gcflags '-m -m -l' xxx.go`.
+
+关于逃逸的可能情况:变量大小不确定,变量类型不确定,变量分配的内存超过用户栈最大值,暴露给了外部指针。
+
+### 03 2 个 interface 可以比较吗 ?
+
+Go 语言中,interface 的内部实现包含了 2 个字段,类型 `T` 和 值 `V`,interface 可以使用 `==` 或 `!=` 比较。2 个 interface 相等有以下 2 种情况
+
+1. 两个 interface 均等于 nil(此时 V 和 T 都处于 unset 状态)
+2. 类型 T 相同,且对应的值 V 相等。
+
+看下面的例子:
+
+```go
+type Stu struct {
+ Name string
+}
+
+type StuInt interface{}
+
+func main() {
+ var stu1, stu2 StuInt = &Stu{"Tom"}, &Stu{"Tom"}
+ var stu3, stu4 StuInt = Stu{"Tom"}, Stu{"Tom"}
+ fmt.Println(stu1 == stu2) // false
+ fmt.Println(stu3 == stu4) // true }
+```
+
+`stu1` 和 `stu2` 对应的类型是 `*Stu`,值是 Stu 结构体的地址,两个地址不同,因此结果为 false。
+`stu3` 和 `stu4` 对应的类型是 `Stu`,值是 Stu 结构体,且各字段相等,因此结果为 true。
+
+### 04 2 个 nil 可能不相等吗?
+
+可能不等。interface在运行时绑定值,只有值为nil接口值才为nil,但是与指针的nil不相等。举个例子:
+
+```go
+var p *int = nil
+var i interface{} = nil
+if(p == i){
+ fmt.Println("Equal")
+}
+```
+
+两者并不相同。总结:**两个nil只有在类型相同时才相等**。
+
+### 05 ❤简述 Go 语言GC(垃圾回收)的工作原理
+
+垃圾回收机制是Go一大特(nan)色(dian)。Go1.3采用**标记清除法**, Go1.5采用**三色标记法**,Go1.8采用**三色标记法+混合写屏障**。
+
+**_标记清除法_**
+
+分为两个阶段:标记和清除
+
+标记阶段:从根对象出发寻找并标记所有存活的对象。
+
+清除阶段:遍历堆中的对象,回收未标记的对象,并加入空闲链表。
+
+缺点是需要暂停程序STW。
+
+**_三色标记法_**:
+
+将对象标记为白色,灰色或黑色。
+
+白色:不确定对象(默认色);黑色:存活对象。灰色:存活对象,子对象待处理。
+
+标记开始时,先将所有对象加入白色集合(需要STW)。首先将根对象标记为灰色,然后将一个对象从灰色集合取出,遍历其子对象,放入灰色集合。同时将取出的对象放入黑色集合,直到灰色集合为空。最后的白色集合对象就是需要清理的对象。
+
+这种方法有一个缺陷,如果对象的引用被用户修改了,那么之前的标记就无效了。因此Go采用了**写屏障技术**,当对象新增或者更新会将其着色为灰色。
+
+一次完整的GC分为四个阶段:
+
+1. 准备标记(需要STW),开启写屏障。
+2. 开始标记
+3. 标记结束(STW),关闭写屏障
+4. 清理(并发)
+
+基于插入写屏障和删除写屏障在结束时需要STW来重新扫描栈,带来性能瓶颈。**混合写屏障**分为以下四步:
+
+1. GC开始时,将栈上的全部对象标记为黑色(不需要二次扫描,无需STW);
+2. GC期间,任何栈上创建的新对象均为黑色
+3. 被删除引用的对象标记为灰色
+4. 被添加引用的对象标记为灰色
+
+总而言之就是确保黑色对象不能引用白色对象,这个改进直接使得GC时间从 2s降低到2us。
+
+### 06 函数返回局部变量的指针是否安全?
+
+这一点和C++不同,在Go里面返回局部变量的指针是安全的。因为Go会进行**逃逸分析**,如果发现局部变量的作用域超过该函数则会**把指针分配到堆区**,避免内存泄漏。
+
+### 07 非接口的任意类型 T() 都能够调用 `*T` 的方法吗?反过来呢?
+
+一个T类型的值可以调用*T类型声明的方法,当且仅当T是**可寻址的**。
+
+反之:*T 可以调用T()的方法,因为指针可以解引用。
+
+### 08 go slice是怎么扩容的?
+
+如果当前容量小于1024,则判断所需容量是否大于原来容量2倍,如果大于,当前容量加上所需容量;否则当前容量乘2。
+
+如果当前容量大于1024,则每次按照1.25倍速度递增容量,也就是每次加上cap/4。
+
+### [并发编程](https://link.zhihu.com/?target=https%3A//geektutu.com/post/qa-golang-3.html)
+
+### 01 ❤无缓冲的 channel 和有缓冲的 channel 的区别?
+
+(这个问题笔者也纠结了很久,直到看到一篇文章,阻塞与否是分别针对发送接收方而言的,才茅塞顿开)
+
+对于无缓冲区channel:
+
+发送的数据如果没有被接收方接收,那么**发送方阻塞;**如果一直接收不到发送方的数据,**接收方阻塞**;
+
+有缓冲的channel:
+
+发送方在缓冲区满的时候阻塞,接收方不阻塞;接收方在缓冲区为空的时候阻塞,发送方不阻塞。
+
+可以类比生产者与消费者问题。
+
+
+### 02 为什么有协程泄露(Goroutine Leak)?
+
+协程泄漏是指协程创建之后没有得到释放。主要原因有:
+
+1. 缺少接收器,导致发送阻塞
+2. 缺少发送器,导致接收阻塞
+3. 死锁。多个协程由于竞争资源导致死锁。
+4. WaitGroup Add()和Done()不相等,前者更大。
+
+### 03 Go 可以限制运行时操作系统线程的数量吗? 常见的goroutine操作函数有哪些?
+
+可以,使用runtime.GOMAXPROCS(num int)可以设置线程数目。该值默认为CPU逻辑核数,如果设的太大,会引起频繁的线程切换,降低性能。
+
+runtime.Gosched(),用于让出CPU时间片,让出当前goroutine的执行权限,调度器安排其它等待的任务运行,并在下次某个时候从该位置恢复执行。
+runtime.Goexit(),调用此函数会立即使当前的goroutine的运行终止(终止协程),而其它的goroutine并不会受此影响。runtime.Goexit在终止当前goroutine前会先执行此goroutine的还未执行的defer语句。请注意千万别在主函数调用runtime.Goexit,因为会引发panic。
+
+### 04 如何控制协程数目。
+
+> The GOMAXPROCS variable limits the number of operating system threads that can execute user-level Go code simultaneously. There is no limit to the number of threads that can be blocked in system calls on behalf of Go code; those do not count against the GOMAXPROCS limit.
+
+可以使用环境变量 `GOMAXPROCS` 或 `runtime.GOMAXPROCS(num int)` 设置,例如:
+
+```go
+runtime.GOMAXPROCS(1) // 限制同时执行Go代码的操作系统线程数为 1
+```
+
+从官方文档的解释可以看到,`GOMAXPROCS` 限制的是同时执行用户态 Go 代码的操作系统线程的数量,但是对于被系统调用阻塞的线程数量是没有限制的。`GOMAXPROCS` 的默认值等于 CPU 的逻辑核数,同一时间,一个核只能绑定一个线程,然后运行被调度的协程。因此对于 CPU 密集型的任务,若该值过大,例如设置为 CPU 逻辑核数的 2 倍,会增加线程切换的开销,降低性能。对于 I/O 密集型应用,适当地调大该值,可以提高 I/O 吞吐率。
+
+另外对于协程,可以用带缓冲区的channel来控制,下面的例子是协程数为1024的例子
+
+```go
+var wg sync.WaitGroup
+ch := make(chan struct{}, 1024)
+for i:=0; i<20000; i++{
+ wg.Add(1)
+ ch<-struct{}{}
+ go func(){
+ defer wg.Done()
+ <-ch
+ }
+}
+wg.Wait()
+```
+
+此外还可以用**协程池**:其原理无外乎是将上述代码中通道和协程函数解耦,并封装成单独的结构体。常见第三方协程池库,比如[tunny](https://link.zhihu.com/?target=http%3A//github.com/Jeffail/tunny)等。
+
+面试题评价:★★★☆☆。偏容易和基础。❤表示需要重点关注。
+
+* * *
+
+## **面试题2**
+
+来源:Durant Thorvalds
+
+### ❤new和make的区别?
+
+* new只用于分配内存,返回一个指向地址的**指针**。它为每个新类型分配一片内存,初始化为0且返回类型*T的内存地址,它相当于&T{}
+* make只可用于**slice,map,channel**的初始化,返回的是**引用**。
+
+```go
+a := new(int)
+*a = 46
+fmt.Println(*a)
+```
+
+### 请你讲一下Go面向对象是如何实现的?
+
+Go实现面向对象的两个关键是struct和interface。
+
+封装:对于同一个包,对象对包内的文件可见;对不同的包,需要将对象以大写开头才是可见的。
+
+继承:继承是编译时特征,在struct内加入所需要继承的类即可:
+
+```go
+type A struct{}
+type B struct{
+A
+}
+```
+
+多态:多态是运行时特征,Go多态通过interface来实现。类型和接口是松耦合的,某个类型的实例可以赋给它所实现的任意接口类型的变量。
+
+Go支持多重继承,就是在类型中嵌入所有必要的父类型。
+
+### 二维切片如何初始化
+
+一种方式是对每一个维度都初始化。
+
+另一种方式是用一个单独的一维切片初始化。
+
+```go
+// Allocate the top-level slice.
+picture := make([][]uint8, YSize) // One row per unit of y.
+// Loop over the rows, allocating the slice for each row.
+for i := range picture {
+ picture[i] = make([]uint8, XSize)
+}
+// Allocate the top-level slice, the same as before.
+picture := make([][]uint8, YSize) // One row per unit of y.
+// Allocate one large slice to hold all the pixels.
+pixels := make([]uint8, XSize*YSize) // Has type []uint8 even though picture is [][]uint8.
+// Loop over the rows, slicingog each row from the front of the remaining pixels slice.
+for i := range picture {
+ picture[i], pixels = pixels[:XSize], pixels[XSize:]
+```
+
+### uint型变量值分别为 1,2,它们相减的结果是多少?
+
+ ```go
+var a uint = 1
+ var b uint = 2
+ fmt.Println(a - b)
+```
+
+答案,结果会溢出,如果是32位系统,结果是2^32-1,如果是64位系统,结果2^64-1.
+
+### 讲一下go有没有函数在main之前执行?怎么用?
+
+go的init函数在main函数之前执行,它有如下特点:
+
+* 初始化不能采用初始化表达式初始化的变量;
+* 程序运行前执行注册
+* 实现sync.Once功能
+* 不能被其它函数调用
+* init函数没有入口参数和返回值:
+
+```go
+func init(){
+ register...
+}
+```
+
+* 每个包可以有多个init函数,**每个源文件也可以有多个init函数**。
+* 同一个包的init执行顺序,golang没有明确定义,编程时要注意程序不要依赖这个执行顺序。
+* 不同包的init函数按照包导入的依赖关系决定执行顺序。
+
+### 下面这句代码是什么作用,为什么要定义一个空值?
+
+```go
+var _ Codec = (*GobCodec)(nil)
+type GobCodec struct{
+ conn io.ReadWriteCloser
+ buf *bufio.Writer
+ dec *gob.Decoder
+ enc *gob.Encoder
+}
+
+type Codec interface {
+ io.Closer
+ ReadHeader(*Header) error
+ ReadBody(interface{}) error
+ Write(*Header, interface{}) error
+}
+```
+
+答:将nil转换为*GobCodec类型,然后再转换为Codec接口,如果转换失败,说明*GobCodec没有实现Codec接口的所有方法。
+
+### ❤golang的内存管理的原理清楚吗?简述go内存管理机制。
+
+golang内存管理基本是参考tcmalloc来进行的。go内存管理本质上是一个内存池,只不过内部做了很多优化:自动伸缩内存池大小,合理的切割内存块。
+
+> 一些基本概念:
+> 页Page:一块8K大小的内存空间。Go向操作系统申请和释放内存都是以页为单位的。
+> span : 内存块,一个或多个连续的 page 组成一个 span 。如果把 page 比喻成工人, span 可看成是小队,工人被分成若干个队伍,不同的队伍干不同的活。
+> sizeclass : 空间规格,每个 span 都带有一个 sizeclass ,标记着该 span 中的 page 应该如何使用。使用上面的比喻,就是 sizeclass 标志着 span 是一个什么样的队伍。
+> object : 对象,用来存储一个变量数据内存空间,一个 span 在初始化时,会被切割成一堆等大的 object 。假设 object 的大小是 16B , span 大小是 8K ,那么就会把 span 中的 page 就会被初始化 8K / 16B = 512 个 object 。所谓内存分配,就是分配一个 object 出去。
+
+1. **mheap**
+
+一开始go从操作系统索取一大块内存作为内存池,并放在一个叫mheap的内存池进行管理,mheap将一整块内存切割为不同的区域,并将一部分内存切割为合适的大小。
+
+
+
+mheap.spans :用来存储 page 和 span 信息,比如一个 span 的起始地址是多少,有几个 page,已使用了多大等等。
+
+mheap.bitmap 存储着各个 span 中对象的标记信息,比如对象是否可回收等等。
+
+mheap.arena_start : 将要分配给应用程序使用的空间。
+
+1. **mcentral**
+
+用途相同的span会以链表的形式组织在一起存放在mcentral中。这里用途用**sizeclass**来表示,就是该span存储哪种大小的对象。
+
+找到合适的 span 后,会从中取一个 object 返回给上层使用。
+
+1. **mcache**
+
+为了提高内存并发申请效率,加入缓存层mcache。每一个mcache和处理器P对应。Go申请内存首先从P的mcache中分配,如果没有可用的span再从mcentral中获取。
+
+> 参考资料:[Go 语言内存管理(二):Go 内存管理](https://link.zhihu.com/?target=https%3A//cloud.tencent.com/developer/article/1422392)
+
+### ❤mutex有几种模式?
+
+mutex有两种模式:**normal** 和 **starvation**
+
+正常模式
+
+所有goroutine按照FIFO的顺序进行锁获取,被唤醒的goroutine和新请求锁的goroutine同时进行锁获取,通常**新请求锁的goroutine更容易获取锁**(持续占有cpu),被唤醒的goroutine则不容易获取到锁。公平性:否。
+
+饥饿模式
+
+所有尝试获取锁的goroutine进行等待排队,**新请求锁的goroutine不会进行锁获取**(禁用自旋),而是加入队列尾部等待获取锁。公平性:是。
+
+> 参考链接:[Go Mutex 饥饿模式](https://link.zhihu.com/?target=https%3A//blog.csdn.net/qq_37102984/article/details/115322706),[GO 互斥锁(Mutex)原理](https://link.zhihu.com/?target=https%3A//blog.csdn.net/baolingye/article/details/111357407%23%3A~%3Atext%3D%25E6%25AF%258F%25E4%25B8%25AAMutex%25E9%2583%25BD%2Ctarving%25E3%2580%2582)
+
+* * *
+
+## **面试题3**
+
+来源**:**[如果你是一个Golang面试官,你会问哪些问题?](https://www.zhihu.com/question/67846139/answer/1983588716)
+
+### ❤go如何进行调度的。GMP中状态流转。
+
+Go里面GMP分别代表:G:goroutine,M:线程(真正在CPU上跑的),P:调度器。
+
+GMP模型
+
+调度器是M和G之间桥梁。
+
+go进行调度过程:
+
+* 某个线程尝试创建一个新的G,那么这个G就会被安排到这个线程的G本地队列LRQ中,如果LRQ满了,就会分配到全局队列GRQ中;
+* 尝试获取当前线程的M,如果无法获取,就会从空闲的M列表中找一个,如果空闲列表也没有,那么就创建一个M,然后绑定G与P运行。
+* 进入调度循环:
+
+* 找到一个合适的G
+* 执行G,完成以后退出
+
+### Go什么时候发生阻塞?阻塞时,调度器会怎么做。
+
+* 用于**原子、互斥量或通道**操作导致goroutine阻塞,调度器将把当前阻塞的goroutine从本地运行队列**LRQ换出**,并重新调度其它goroutine;
+* 由于**网络请求**和**IO**导致的阻塞,Go提供了网络轮询器(Netpoller)来处理,后台用epoll等技术实现IO多路复用。
+
+其它回答:
+
+* **channel阻塞**:当goroutine读写channel发生阻塞时,会调用gopark函数,该G脱离当前的M和P,调度器将新的G放入当前M。
+* **系统调用**:当某个G由于系统调用陷入内核态,该P就会脱离当前M,此时P会更新自己的状态为Psyscall,M与G相互绑定,进行系统调用。结束以后,若该P状态还是Psyscall,则直接关联该M和G,否则使用闲置的处理器处理该G。
+* **系统监控**:当某个G在P上运行的时间超过10ms时候,或者P处于Psyscall状态过长等情况就会调用retake函数,触发新的调度。
+* **主动让出**:由于是协作式调度,该G会主动让出当前的P(通过GoSched),更新状态为Grunnable,该P会调度队列中的G运行。
+
+> 更多关于netpoller的内容可以参看:[https://strikefreedom.top/go-netpoll-io-multiplexing-reactor](https://link.zhihu.com/?target=https%3A//strikefreedom.top/go-netpoll-io-multiplexing-reactor)
+
+### ❤Go中GMP有哪些状态?
+
+G的状态:
+
+**_Gidle**:刚刚被分配并且还没有被初始化,值为0,为创建goroutine后的默认值
+
+**_Grunnable**: 没有执行代码,没有栈的所有权,存储在运行队列中,可能在某个P的本地队列或全局队列中(如上图)。
+
+**_Grunning**: 正在执行代码的goroutine,拥有栈的所有权(如上图)。
+
+**_Gsyscall**:正在执行系统调用,拥有栈的所有权,与P脱离,但是与某个M绑定,会在调用结束后被分配到运行队列(如上图)。
+
+**_Gwaiting**:被阻塞的goroutine,阻塞在某个channel的发送或者接收队列(如上图)。
+
+**_Gdead**: 当前goroutine未被使用,没有执行代码,可能有分配的栈,分布在空闲列表gFree,可能是一个刚刚初始化的goroutine,也可能是执行了goexit退出的goroutine(如上图)。
+
+**_Gcopystac**:栈正在被拷贝,没有执行代码,不在运行队列上,执行权在
+
+**_Gscan** : GC 正在扫描栈空间,没有执行代码,可以与其他状态同时存在。
+
+P的状态:
+
+**_Pidle** :处理器没有运行用户代码或者调度器,被空闲队列或者改变其状态的结构持有,运行队列为空
+
+**_Prunning** :被线程 M 持有,并且正在执行用户代码或者调度器(如上图)
+
+**_Psyscall**:没有执行用户代码,当前线程陷入系统调用(如上图)
+
+**_Pgcstop** :被线程 M 持有,当前处理器由于垃圾回收被停止
+
+**_Pdead** :当前处理器已经不被使用
+
+M的状态:
+
+**自旋线程**:处于运行状态但是没有可执行goroutine的线程,数量最多为GOMAXPROC,若是数量大于GOMAXPROC就会进入休眠。
+
+**非自旋线程**:处于运行状态有可执行goroutine的线程。
+
+下面一张图很好的展示了Goroutine状态流转:
+
+Goroutine状态流转
+### GMP能不能去掉P层?会怎么样?
+
+去掉p会导致,当G进行**系统调用时候,会一直阻塞**,其它G无法获得M。
+
+### 如果有一个G一直占用资源怎么办。
+
+如果有个goroutine一直占用资源,那么GMP模型会**从正常模式转变为饥饿模式**(类似于mutex),允许其它goroutine抢占(禁用自旋锁)。
+
+### 若干线程一个线程发生OOM(Out of memory)会怎么办。
+
+对于线程而言:发生内存溢出的线程会被kill,其它线程不受影响。
+
+### goroutine什么情况会发生内存泄漏?如何避免。
+
+在Go中内存泄露分为暂时性内存泄露和永久性内存泄露。
+
+**暂时性内存泄露**
+
+* 获取长字符串中的一段导致长字符串未释放
+* 获取长slice中的一段导致长slice未释放
+* 在长slice新建slice导致泄漏
+
+string相比切片少了一个容量的cap字段,可以把string当成一个只读的切片类型。获取长string或者切片中的一段内容,由于新生成的对象和老的string或者切片共用一个内存空间,会导致老的string和切片资源暂时得不到释放,造成短暂的内存泄漏
+
+**永久性内存泄露**
+
+* goroutine永久阻塞而导致泄漏
+* time.Ticker未关闭导致泄漏
+* 不正确使用Finalizer导致泄漏
+
+### 怎么调试go?
+
+在vscode设置mode为debug。需要go-dlv插件。
+
+### Go GC有几个阶段
+
+目前的go GC采用**三色标记法**和**混合写屏障**技术。
+
+Go GC有**四**个阶段:
+
+* STW,开启混合写屏障,扫描栈对象;
+* 将所有对象加入白色集合,从根对象开始,将其放入灰色集合。每次从灰色集合取出一个对象标记为黑色,然后遍历其子对象,标记为灰色,放入灰色集合;
+* 如此循环直到灰色集合为空。剩余的白色对象就是需要清理的对象。
+* STW,关闭混合写屏障;
+* 在后台进行GC(并发)。
+
+### go竞态条件了解吗?
+
+所谓竞态竞争,就是当**两个或以上的goroutine访问相同资源时候,对资源进行读/写。**
+
+比如`var a int = 0`,有两个协程分别对a+=1,我们发现最后a不一定为2.这就是竞态竞争。
+
+通常我们可以用`go run -race xx.go`来进行检测。
+
+解决方法是,对临界区资源上锁,或者使用原子操作(atomics),原子操作的开销小于上锁。
+
+### 如果若干个goroutine,有一个panic会怎么做?
+
+有一个panic,那么剩余goroutine也会退出。
+
+> 参考理解:[goroutine配上panic会怎样](https://link.zhihu.com/?target=https%3A//blog.csdn.net/huorongbj/article/details/123013273)。
+
+### defer可以捕获goroutine的子goroutine吗?
+
+不可以。它们处于不同的调度器P中。对于子goroutine,正确的做法是:
+
+1. 必须通过 defer 关键字来调用 recover()。
+2. 当通过 goroutine 调用某个方法,一定要确保内部有 recover() 机制。
+
+### ❤gRPC是什么?
+
+基于go的**远程过程调用**。RPC 框架的目标就是让远程服务调用更加简单、透明,RPC 框架负责屏蔽底层的传输方式(TCP 或者 UDP)、序列化方式(XML/Json/ 二进制)和通信细节。服务调用者可以像调用本地接口一样调用远程的服务提供者,而不需要关心底层通信细节和调用过程。
+
+gRPC框架图
+## 面试题4
+
+需要面试者有一定的大型项目经验经验,了解使用**微服务,etcd,gin,gorm,gRPC**等典型框架等模型或框架。
+
+### 微服务了解吗?
+
+微服务是一种开发软件的架构和组织方法,其中软件由通过明确定义的 API 进行通信的小型独立服务组成。微服务架构使应用程序更易于扩展和更快地开发,从而加速创新并缩短新功能的上市时间。
+
+微服务示意图
+
+微服务有着自主,专用,灵活性等优点。
+
+> 参考资料:[什么是微服务?| AWS](https://link.zhihu.com/?target=https%3A//aws.amazon.com/cn/microservices/)
+
+### 服务发现是怎么做的?
+
+主要有两种服务发现机制:**客户端发现**和**服务端发现**。
+
+**客户端发现模式**:当我们使用客户端发现的时候,客户端负责决定可用服务实例的网络地址并且在集群中对请求负载均衡, 客户端访问**服务登记表**,也就是一个可用服务的数据库,然后客户端使用一种**负载均衡算法**选择一个可用的服务实例然后发起请求。该模式如下图所示:
+
+客户端发现模式
+
+**服务端发现模式**:客户端通过**负载均衡器**向某个服务提出请求,负载均衡器查询服务注册表,并将请求转发到可用的服务实例。如同客户端发现,服务实例在服务注册表中注册或注销。
+
+服务端发现模式
+
+参考资料:[「Chris Richardson 微服务系列」服务发现的可行方案以及实践案例](https://link.zhihu.com/?target=http%3A//blog.daocloud.io/3289.html)
+
+### ETCD用过吗?
+
+**etcd**是一个**高度一致**的**分布式键值存储**,它提供了一种可靠的方式来存储需要由分布式系统或机器集群访问的数据。它可以优雅地处理网络分区期间的领导者**选举**,即使在领导者节点中也可以容忍机器故障。
+
+etcd 是用**Go语言**编写的,它具有出色的跨平台支持,小的二进制文件和强大的社区。etcd机器之间的通信通过**Raft共识算法**处理。
+
+关于文档可以参考:[v3.5 docs](https://link.zhihu.com/?target=https%3A//etcd.io/docs/v3.5/)
+
+### GIN怎么做参数校验?
+
+go采用validator作参数校验。
+
+它具有以下独特功能:
+
+* 使用验证tag或自定义validator进行跨字段Field和跨结构体验证。
+* 允许切片、数组和哈希表,多维字段的任何或所有级别进行校验。
+* 能够对哈希表key和value进行验证
+* 通过在验证之前确定它的基础类型来处理类型接口。
+* 别名验证标签,允许将多个验证映射到单个标签,以便更轻松地定义结构体上的验证
+* gin web 框架的默认验证器;
+
+参考资料:[validator package - pkg.go.dev](https://link.zhihu.com/?target=https%3A//pkg.go.dev/github.com/go-playground/validator%23section-readme)
+
+### 中间件用过吗?
+
+Middleware是Web的重要组成部分,中间件(通常)是一小段代码,它们接受一个请求,对其进行处理,每个中间件只处理一件事情,完成后将其传递给另一个中间件或最终处理程序,这样就做到了程序的解耦。
+
+### Go解析Tag是怎么实现的?
+
+Go解析tag采用的是**反射**。
+
+具体来说使用reflect.ValueOf方法获取其反射值,然后获取其Type属性,之后再通过Field(i)获取第i+1个field,再.Tag获得Tag。
+
+反射实现的原理在: `src/reflect/type.go`中
+
+### 你项目有优雅的启停吗?
+
+所谓「优雅」启停就是在启动退出服务时要满足以下几个条件:
+
+* **不可以关闭现有连接**(进程)
+* 新的进程启动并「**接管**」旧进程
+* 连接要**随时响应用户请求**,不可以出现拒绝请求的情况
+* 停止的时候,必须**处理完既有连接**,并且**停止接收新的连接**。
+
+为此我们必须引用**信号**来完成这些目的:
+
+启动:
+
+* 监听SIGHUP(在用户终端连接(正常或非正常)结束时发出);
+* 收到信号后将服务监听的文件描述符传递给新的子进程,此时新老进程同时接收请求;
+
+退出:
+
+* 监听SIGINT和SIGSTP和SIGQUIT等。
+* 父进程停止接收新请求,等待旧请求完成(或超时);
+* 父进程退出。
+
+实现:go1.8采用Http.Server内置的Shutdown方法支持优雅关机。 然后[fvbock/endless](https://link.zhihu.com/?target=http%3A//github.com/fvbock/endless)可以实现优雅重启。
+
+> 参考资料:[gin框架实践连载八 | 如何优雅重启和停止 - 掘金](https://link.zhihu.com/?target=https%3A//juejin.cn/post/6867074626427502600%23heading-3),[优雅地关闭或重启 go web 项目](https://link.zhihu.com/?target=http%3A//www.phpxs.com/post/7186/)
+
+### 持久化怎么做的?
+
+所谓持久化就是将要保存的字符串写到硬盘等设备。
+
+* 最简单的方式就是采用ioutil的WriteFile()方法将字符串写到磁盘上,这种方法面临**格式化**方面的问题。
+* 更好的做法是将数据按照**固定协议**进行组织再进行读写,比如JSON,XML,Gob,csv等。
+* 如果要考虑**高并发**和**高可用**,必须把数据放入到数据库中,比如MySQL,PostgreDB,MongoDB等。
+
+参考链接:[Golang 持久化](https://link.zhihu.com/?target=https%3A//www.jianshu.com/p/015aca3e11ae)
+
+* * *
+
+## **面试题5**
+
+作者:Dylan2333 链接:
+
+ [测开转Go开发-面经&总结_笔经面经_牛客网www.nowcoder.com/discuss/826193?type=post&order=recall&pos=&page=1&ncTraceId=&channel=-1&source_id=search_post_nctrack&gio_id=9C5DC1FFB3FC3BE29281D7CCFC420365-1645173894793](https://link.zhihu.com/?target=https%3A//www.nowcoder.com/discuss/826193%3Ftype%3Dpost%26order%3Drecall%26pos%3D%26page%3D1%26ncTraceId%3D%26channel%3D-1%26source_id%3Dsearch_post_nctrack%26gio_id%3D9C5DC1FFB3FC3BE29281D7CCFC420365-1645173894793)
+
+该试题需要面试者有非常丰富的项目阅历和底层原理经验,熟练使用**微服务,etcd,gin,gorm,gRPC**等典型框架等模型或框架。
+
+### channel 死锁的场景
+
+* 当一个`channel`中没有数据,而直接读取时,会发生死锁:
+
+```go
+q := make(chan int,2)
+<-q
+```
+
+解决方案是采用select语句,再default放默认处理方式:
+
+```go
+q := make(chan int,2)
+select{
+ case val:=<-q:
+ default:
+ ...
+
+}
+```
+
+* 当channel数据满了,再尝试写数据会造成死锁:
+
+```go
+q := make(chan int,2)
+q<-1
+q<-2
+q<-3
+```
+
+解决方法,采用select
+
+```go
+func main() {
+ q := make(chan int, 2)
+ q <- 1
+ q <- 2
+ select {
+ case q <- 3:
+ fmt.Println("ok")
+ default:
+ fmt.Println("wrong")
+ }
+
+}
+```
+
+* 向一个关闭的channel写数据。
+
+注意:一个已经关闭的channel,只能读数据,不能写数据。
+
+参考资料:[Golang关于channel死锁情况的汇总以及解决方案](https://link.zhihu.com/?target=https%3A//blog.csdn.net/qq_35976351/article/details/81984117)
+
+### 读写channel应该先关哪个?
+
+应该写channel先关。因为对于已经关闭的channel只能读,不能写。
+
+### 对已经关闭的chan进行读写会怎么样?
+
+* 读已经关闭的chan能一直读到东西,但是读到的内容根据通道内关闭前是否有元素而不同。
+
+* 如果chan关闭前,buffer内有元素还未读,会正确读到chan内的值,且返回的第二个bool值(是否读成功)为true。
+* 如果chan关闭前,buffer内有元素已经被读完,chan内无值,接下来所有接收的值都会非阻塞直接成功,返回 channel 元素的零值,但是第二个bool值一直为false。
+
+写已经关闭的chan会panic。
+
+### 说说 atomic底层怎么实现的.
+
+atomic源码位于`sync\atomic`。通过阅读源码可知,atomic采用**CAS**(CompareAndSwap)的方式实现的。所谓CAS就是使用了CPU中的原子性操作。在操作共享变量的时候,CAS不需要对其进行加锁,而是通过类似于乐观锁的方式进行检测,总是假设被操作的值未曾改变(即与旧值相等),并一旦确认这个假设的真实性就立即进行值替换。本质上是**不断占用CPU资源来避免加锁的开销**。
+
+> 参考资料:[Go语言的原子操作atomic - 编程猎人](https://link.zhihu.com/?target=https%3A//www.programminghunter.com/article/37392193442/)
+
+### channel底层实现?是否线程安全。
+
+channel底层实现在`src/runtime/chan.go`中
+
+channel内部是一个循环链表。内部包含buf, sendx, recvx, lock ,recvq, sendq几个部分;
+
+buf是有缓冲的channel所特有的结构,用来存储缓存数据。是个循环链表;
+
+* sendx和recvx用于记录buf这个循环链表中的发送或者接收的index;
+* lock是个互斥锁;
+* recvq和sendq分别是接收(<-channel)或者发送(channel <- xxx)的goroutine抽象出来的结构体(sudog)的队列。是个双向链表。
+
+channel是**线程安全**的。
+
+> 参考资料:[Kitou:Golang 深度剖析 -- channel的底层实现](https://zhuanlan.zhihu.com/p/264305133)
+
+### map的底层实现。
+
+源码位于`src\runtime\map.go` 中。
+
+go的map和C++map不一样,底层实现是哈希表,包括两个部分:**hmap**和**bucket**。
+
+hmap结构体如图:
+
+```go
+type hmap struct {
+ count int //map元素的个数,调用len()直接返回此值
+
+ // map标记:
+ // 1\. key和value是否包指针
+ // 2\. 是否正在扩容
+ // 3\. 是否是同样大小的扩容
+ // 4\. 是否正在 `range`方式访问当前的buckets
+ // 5\. 是否有 `range`方式访问旧的bucket
+ flags uint8
+
+ B uint8 // buckets 的对数 log_2, buckets 数组的长度就是 2^B
+ noverflow uint16 // overflow 的 bucket 近似数
+ hash0 uint32 // hash种子 计算 key 的哈希的时候会传入哈希函数
+ buckets unsafe.Pointer // 指向 buckets 数组,大小为 2^B 如果元素个数为0,就为 nil
+
+ // 扩容的时候,buckets 长度会是 oldbuckets 的两倍
+ oldbuckets unsafe.Pointer // bucket slice指针,仅当在扩容的时候不为nil
+
+ nevacuate uintptr // 扩容时已经移到新的map中的bucket数量
+ extra *mapextra // optional fields
+}
+```
+
+里面最重要的是buckets(桶)。buckets是一个指针,最终它指向的是一个结构体:
+
+```go
+// A bucket for a Go map.
+type bmap struct {
+ tophash [bucketCnt]uint8
+}
+```
+
+每个bucket固定包含8个key和value(可以查看源码bucketCnt=8).实现上面是一个固定的大小连续内存块,分成四部分:每个条目的状态,8个key值,8个value值,指向下个bucket的指针。
+
+创建哈希表使用的是`makemap`函数.map 的一个关键点在于,**哈希函数**的选择。在程序启动时,会检测 cpu 是否支持 aes,如果支持,则使用 aes hash,否则使用 memhash。这是在函数 alginit() 中完成,位于路径:`src/runtime/alg.go` 下。
+
+map查找就是将key哈希后得到64位(64位机)用最后B个比特位计算在哪个桶。在 bucket 中,从前往后找到第一个空位。这样,在查找某个 key 时,先找到对应的桶,再去遍历 bucket 中的 key。
+
+关于map的查找和扩容可以参考[map的用法到map底层实现分析](https://link.zhihu.com/?target=https%3A//blog.csdn.net/chenxun_2010/article/details/103768011%3Futm_medium%3Ddistribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-0.pc_relevant_aa%26spm%3D1001.2101.3001.4242.1%26utm_relevant_index%3D3)。
+
+### select的实现原理?
+
+select源码位于`src\runtime\select.go`,最重要的`scase` 数据结构为:
+
+```go
+type scase struct {
+ c *hchan // chan
+ elem unsafe.Pointer // data element
+}
+```
+
+scase.c为当前case语句所操作的channel指针,这也说明了一个case语句只能操作一个channel。
+
+scase.elem表示缓冲区地址:
+
+* caseRecv : scase.elem表示读出channel的数据存放地址;
+* caseSend : scase.elem表示将要写入channel的数据存放地址;
+
+select的主要实现位于:`selectgo`函数:其主要功能如下:
+
+```go
+//1\. 锁定scase语句中所有的channel
+ //2\. 按照随机顺序检测scase中的channel是否ready
+ // 2.1 如果case可读,则读取channel中数据,解锁所有的channel,然后返回(case index, true)
+ // 2.2 如果case可写,则将数据写入channel,解锁所有的channel,然后返回(case index, false)
+ // 2.3 所有case都未ready,则解锁所有的channel,然后返回(default index, false)
+ //3\. 所有case都未ready,且没有default语句
+ // 3.1 将当前协程加入到所有channel的等待队列
+ // 3.2 当将协程转入阻塞,等待被唤醒
+ //4\. 唤醒后返回channel对应的case index
+ // 4.1 如果是读操作,解锁所有的channel,然后返回(case index, true)
+ // 4.2 如果是写操作,解锁所有的channel,然后返回(case index, false)
+```
+
+参考资料:[Go select的使用和实现原理](https://link.zhihu.com/?target=https%3A//www.cnblogs.com/wuyepeng/p/13910678.html%23%3A~%3Atext%3D%25E4%25B8%2580%25E3%2580%2581select%25E7%25AE%2580%25E4%25BB%258B.%25201.Go%25E7%259A%2584select%25E8%25AF%25AD%25E5%258F%25A5%25E6%2598%25AF%25E4%25B8%2580%25E7%25A7%258D%25E4%25BB%2585%25E8%2583%25BD%25E7%2594%25A8%25E4%25BA%258Echannl%25E5%258F%2591%25E9%2580%2581%25E5%2592%258C%25E6%258E%25A5%25E6%2594%25B6%25E6%25B6%2588%25E6%2581%25AF%25E7%259A%2584%25E4%25B8%2593%25E7%2594%25A8%25E8%25AF%25AD%25E5%258F%25A5%25EF%25BC%258C%25E6%25AD%25A4%25E8%25AF%25AD%25E5%258F%25A5%25E8%25BF%2590%25E8%25A1%258C%25E6%259C%259F%25E9%2597%25B4%25E6%2598%25AF%25E9%2598%25BB%25E5%25A1%259E%25E7%259A%2584%25EF%25BC%259B%25E5%25BD%2593select%25E4%25B8%25AD%25E6%25B2%25A1%25E6%259C%2589case%25E8%25AF%25AD%25E5%258F%25A5%25E7%259A%2584%25E6%2597%25B6%25E5%2580%2599%25EF%25BC%258C%25E4%25BC%259A%25E9%2598%25BB%25E5%25A1%259E%25E5%25BD%2593%25E5%2589%258Dgroutine%25E3%2580%2582.%25202.select%25E6%2598%25AFGolang%25E5%259C%25A8%25E8%25AF%25AD%25E8%25A8%2580%25E5%25B1%2582%25E9%259D%25A2%25E6%258F%2590%25E4%25BE%259B%25E7%259A%2584I%252FO%25E5%25A4%259A%25E8%25B7%25AF%25E5%25A4%258D%25E7%2594%25A8%25E7%259A%2584%25E6%259C%25BA%25E5%2588%25B6%25EF%25BC%258C%25E5%2585%25B6%25E4%25B8%2593%25E9%2597%25A8%25E7%2594%25A8%25E6%259D%25A5%25E6%25A3%2580%25E6%25B5%258B%25E5%25A4%259A%25E4%25B8%25AAchannel%25E6%2598%25AF%25E5%2590%25A6%25E5%2587%2586%25E5%25A4%2587%25E5%25AE%258C%25E6%25AF%2595%25EF%25BC%259A%25E5%258F%25AF%25E8%25AF%25BB%25E6%2588%2596%25E5%258F%25AF%25E5%2586%2599%25E3%2580%2582.%2C3.select%25E8%25AF%25AD%25E5%258F%25A5%25E4%25B8%25AD%25E9%2599%25A4default%25E5%25A4%2596%25EF%25BC%258C%25E6%25AF%258F%25E4%25B8%25AAcase%25E6%2593%258D%25E4%25BD%259C%25E4%25B8%2580%25E4%25B8%25AAchannel%25EF%25BC%258C%25E8%25A6%2581%25E4%25B9%2588%25E8%25AF%25BB%25E8%25A6%2581%25E4%25B9%2588%25E5%2586%2599.%25204.select%25E8%25AF%25AD%25E5%258F%25A5%25E4%25B8%25AD%25E9%2599%25A4default%25E5%25A4%2596%25EF%25BC%258C%25E5%2590%2584case%25E6%2589%25A7%25E8%25A1%258C%25E9%25A1%25BA%25E5%25BA%258F%25E6%2598%25AF%25E9%259A%258F%25E6%259C%25BA%25E7%259A%2584.%25205.select%25E8%25AF%25AD%25E5%258F%25A5%25E4%25B8%25AD%25E5%25A6%2582%25E6%259E%259C%25E6%25B2%25A1%25E6%259C%2589default%25E8%25AF%25AD%25E5%258F%25A5%25EF%25BC%258C%25E5%2588%2599%25E4%25BC%259A%25E9%2598%25BB%25E5%25A1%259E%25E7%25AD%2589%25E5%25BE%2585%25E4%25BB%25BB%25E4%25B8%2580case.%25206.select%25E8%25AF%25AD%25E5%258F%25A5%25E4%25B8%25AD%25E8%25AF%25BB%25E6%2593%258D%25E4%25BD%259C%25E8%25A6%2581%25E5%2588%25A4%25E6%2596%25AD%25E6%2598%25AF%25E5%2590%25A6%25E6%2588%2590%25E5%258A%259F%25E8%25AF%25BB%25E5%258F%2596%25EF%25BC%258C%25E5%2585%25B3%25E9%2597%25AD%25E7%259A%2584channel%25E4%25B9%259F%25E5%258F%25AF%25E4%25BB%25A5%25E8%25AF%25BB%25E5%258F%2596).
+
+### go的interface怎么实现的?
+
+go interface源码在`runtime\iface.go`中。
+
+go的接口由两种类型实现`iface`和`eface`。iface是包含方法的接口,而eface不包含方法。
+
+* `iface`
+
+对应的数据结构是(位于`src\runtime\runtime2.go`):
+
+```go
+type iface struct {
+ tab *itab
+ data unsafe.Pointer
+}
+```
+
+可以简单理解为,tab表示接口的具体结构类型,而data是接口的值。
+
+itab:
+
+```go
+type itab struct {
+ inter *interfacetype //此属性用于定位到具体interface
+ _type *_type //此属性用于定位到具体interface
+ hash uint32 // copy of _type.hash. Used for type switches.
+ _ [4]byte
+ fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
+}
+```
+
+属性`interfacetype`类似于`_type`,其作用就是interface的公共描述,类似的还有`maptype`、`arraytype`、`chantype`…其都是各个结构的公共描述,可以理解为一种外在的表现信息。interfaetype和type唯一确定了接口类型,而hash用于查询和类型判断。fun表示方法集。
+
+* `eface`
+
+与iface基本一致,但是用`_type`直接表示类型,这样的话就无法使用方法。
+
+```go
+type eface struct {
+ _type *_type
+ data unsafe.Pointer
+}
+```
+
+这里篇幅有限,深入讨论可以看:[深入研究 Go interface 底层实现](https://link.zhihu.com/?target=https%3A//halfrost.com/go_interface/%23toc-1)
+
+### go的reflect 底层实现
+
+go reflect源码位于`src\reflect\`下面,作为一个库独立存在。反射是基于**接口**实现的。
+
+Go反射有三大法则:
+
+* 反射从**接口**映射到**反射对象;**
+
+法则1
+
+* 反射从**反射对象**映射到**接口值**;
+
+法则2
+
+* 只有**值可以修改**(settable),才可以**修改**反射对象。
+
+Go反射基于上述三点实现。我们先从最核心的两个源文件入手`type.go`和`value.go`.
+
+type用于获取当前值的类型。value用于获取当前的值。
+
+> 参考资料:[The Laws of Reflection](https://link.zhihu.com/?target=https%3A//go.dev/blog/laws-of-reflection), [图解go反射实现原理](https://link.zhihu.com/?target=https%3A//i6448038.github.io/2020/02/15/golang-reflection/)
+
+### go GC的原理知道吗?
+
+如果需要从源码角度解释GC,推荐阅读(非常详细,图文并茂):
+
+[https://draveness.me/golang/docs/part3-runtime/ch07-memory/golang-garbage-collector/](https://link.zhihu.com/?target=https%3A//draveness.me/golang/docs/part3-runtime/ch07-memory/golang-garbage-collector/)
+
+### go里用过哪些设计模式 。[Go 语言垃圾收集器的实现原理](https://link.zhihu.com/?target=https%3A//draveness.me/golang/docs/part3-runtime/ch07-memory/golang-garbage-collector/)go里用过哪些设计模式 。
+
+工厂模式:比如New()方法返回一个对象实例。
+
+单例模式:比如日志系统,只需要一个实例就可以完成日志记录了。
+
+更多的可以模式可以参考:[Go语言设计模式汇总](https://link.zhihu.com/?target=https%3A//www.cnblogs.com/Survivalist/p/11207789.html)
+
+### go的调试/分析工具用过哪些。
+
+go的自带工具链相当丰富,
+
+* go cover : 测试代码覆盖率;
+* godoc: 用于生成go文档;
+* pprof:用于性能调优,针对cpu,内存和并发;
+* race:用于竞争检测;
+
+### 进程被kill,如何保证所有goroutine顺利退出
+
+goroutine监听SIGKILL信号,一旦接收到SIGKILL,则立刻退出。可采用select方法。
+
+### 说说context包的作用?你用过哪些,原理知道吗?
+
+`context`可以用来在`goroutine`之间传递上下文信息,相同的`context`可以传递给运行在不同`goroutine`中的函数,上下文对于多个`goroutine`同时使用是安全的,`context`包定义了上下文类型,可以使用`background`、`TODO`创建一个上下文,在函数调用链之间传播`context`,也可以使用`WithDeadline`、`WithTimeout`、`WithCancel` 或 `WithValue` 创建的修改副本替换它,听起来有点绕,其实总结起就是一句话:`context`的作用就是在不同的`goroutine`之间同步请求特定的数据、取消信号以及处理请求的截止日期。
+
+关于context原理,可以参看:[小白也能看懂的context包详解:从入门到精通](https://link.zhihu.com/?target=https%3A//cloud.tencent.com/developer/article/1900658)
+
+### grpc为啥好,基本原理是什么,和http比呢
+
+官方介绍:gRPC 是一个现代开源的**高性能远程过程调用** (RPC) 框架,可以在**任何环境**中运行。它可以通过对负载平衡、跟踪、健康检查和身份验证的可插拔支持有效地连接数据中心内和跨数据中心的服务。它也适用于分布式计算的最后一英里,将设备、移动应用程序和浏览器连接到后端服务。
+
+区别:
+- rpc是远程过程调用,就是本地去调用一个远程的函数,而http是通过 url和符合restful风格的数据包去发送和获取数据;
+- rpc的一般使用的编解码协议更加高效,比如grpc使用protobuf编解码。而http的一般使用json进行编解码,数据相比rpc更加直观,但是数据包也更大,效率低下;
+- rpc一般用在服务内部的相互调用,而http则用于和用户交互;
+相似点:
+都有类似的机制,例如grpc的metadata机制和http的头机制作用相似,而且web框架,和rpc框架中都有拦截器的概念。grpc使用的是http2.0协议。
+官网:[gRPC](https://link.zhihu.com/?target=https%3A//grpc.io/)
+
+### etcd怎么搭建的,具体怎么用的
+
+### 熔断怎么做的
+
+### 服务降级怎么搞
+
+### 1亿条数据动态增长,取top10,怎么实现
+
+### 进程挂了怎么办
+
+### nginx配置过吗,有哪些注意的点
+
+### 设计一个阻塞队列
+
+### mq消费阻塞怎么办
+
+### 性能没达到预期,有什么解决方案
+
+* * *
+
+## 编程系列
+
+### 一个10G的文件,里面全部是自然数,一行一个,乱序排列,对其排序。在32位机器上面完成,内存限制为 2G(bitmap原理知道吗?)
+
+首先,10G文件是不可能一次性放到内存里的。这类问题一般有两种解决方案:
+
+* 将10G文件分成多个小文件,分别排序,最后合并一个文件;
+* 采用bitmap
+
+如果面试大数据类岗位,可能面试官就想考察你对Mapreduce熟悉程度,要采用第一种merge and sort。
+
+如果是算法类岗位,就要考虑bitmap,但需要注意的是bitmap**不能对重复数据进行排序**。这里我们详细介绍一下:
+
+定量分析一下,32位机器自然数有2^32个,用一个bit来存放一个整数,那么所需的内存是,`2^32/(8<<20) = 512MB` ,这些数存放在文件中,一行一个,需要20G容量,所以题目问10G文件,只需要256MB内存就可以完成。
+
+bitmap实现具体分为两步:插入一个数,和排序。
+
+```go
+type BitMap struct {
+ vec []byte
+ size int
+}
+
+func New(size int) *BitMap {
+ return &BitMap{
+ size: size,
+ vec: make([]byte, size),
+ }
+}
+
+func (bm *BitMap) Set(num int) (ok bool, err error) {
+ if num/8 >= bm.size {
+ return false, errors.New("the num overflows the size of bitmap")
+ }
+ bm.vec[num/8] |= 1 << (num % 8)
+ return true, nil
+}
+
+func (bm *BitMap) Exist(num int) bool {
+ if num/8 >= bm.size {
+ return false
+ }
+ return bm.vec[num/8]&(1<<(num%8)) > 0
+}
+
+func (bm *BitMap) Sort() (ret []int) {
+ ret = make([]int, 0)
+ for i := 0; i < (8 * bm.size); i++ {
+ if bm.Exist(i) {
+ ret = append(ret, i)
+ }
+ }
+ return
+}
+```
+
+### 实现使用字符串函数名,调用函数。
+
+思路:采用反射的Call方法实现。
+
+```go
+package main
+import (
+ "fmt"
+ "reflect"
+)
+
+type Animal struct{
+
+}
+
+func (a *Animal) Eat(){
+ fmt.Println("Eat")
+}
+
+func main(){
+ a := Animal{}
+ reflect.ValueOf(&a).MethodByName("Eat").Call([]reflect.Value{})
+
+}
+```
+
+### 负载均衡算法。(一致性哈希)
+
+```go
+package main
+
+import (
+ "fmt"
+ "sort"
+ "strconv"
+)
+
+type HashFunc func(key []byte) uint32
+
+type ConsistentHash struct {
+ hash HashFunc
+ hashvals []int
+ hashToKey map[int]string
+ virtualNum int
+}
+
+func NewConsistentHash(virtualNum int, fn HashFunc) *ConsistentHash {
+ return &ConsistentHash{
+ hash: fn,
+ virtualNum: virtualNum,
+ hashToKey: make(map[int]string),
+ }
+}
+
+func (ch *ConsistentHash) AddNode(keys ...string) {
+ for _, k := range keys {
+ for i := 0; i < ch.virtualNum; i++ {
+ conv := strconv.Itoa(i)
+ hashval := int(ch.hash([]byte(conv + k)))
+ ch.hashvals = append(ch.hashvals, hashval)
+ ch.hashToKey[hashval] = k
+ }
+ }
+ sort.Ints(ch.hashvals)
+}
+
+func (ch *ConsistentHash) GetNode(key string) string {
+ if len(ch.hashToKey) == 0 {
+ return ""
+ }
+ keyhash := int(ch.hash([]byte(key)))
+ id := sort.Search(len(ch.hashToKey), func(i int) bool {
+ return ch.hashvals[i] >= keyhash
+ })
+ return ch.hashToKey[ch.hashvals[id%len(ch.hashvals)]]
+}
+
+func main() {
+ ch := NewConsistentHash(3, func(key []byte) uint32 {
+ ret, _ := strconv.Atoi(string(key))
+ return uint32(ret)
+ })
+ ch.AddNode("1", "3", "5", "7")
+ testkeys := []string{"12", "4", "7", "8"}
+ for _, k := range testkeys {
+ fmt.Printf("k:%s,node:%s\n", k, ch.GetNode(k))
+ }
+}
+```
+
+### (Goroutine)有三个函数,分别打印"cat", "fish","dog"要求每一个函数都用一个goroutine,按照顺序打印100次。
+
+此题目考察channel,用三个无缓冲channel,如果一个channel收到信号则通知下一个。
+
+```go
+package main
+
+import (
+ "fmt"
+ "time"
+)
+
+var dog = make(chan struct{})
+var cat = make(chan struct{})
+var fish = make(chan struct{})
+
+func Dog() {
+ <-fish
+ fmt.Println("dog")
+ dog <- struct{}{}
+}
+
+func Cat() {
+ <-dog
+ fmt.Println("cat")
+ cat <- struct{}{}
+}
+
+func Fish() {
+ <-cat
+ fmt.Println("fish")
+ fish <- struct{}{}
+}
+
+func main() {
+ for i := 0; i < 100; i++ {
+ go Dog()
+ go Cat()
+ go Fish()
+ }
+ fish <- struct{}{}
+
+ time.Sleep(10 * time.Second)
+}
+```
+
+### 两个协程交替打印10个字母和数字
+
+思路:采用channel来协调goroutine之间顺序。
+
+主线程一般要waitGroup等待协程退出,这里简化了一下直接sleep。
+
+```go
+package main
+
+import (
+ "fmt"
+ "time"
+)
+
+var word = make(chan struct{}, 1)
+var num = make(chan struct{}, 1)
+
+func printNums() {
+ for i := 0; i < 10; i++ {
+ <-word
+ fmt.Println(1)
+ num <- struct{}{}
+ }
+}
+func printWords() {
+ for i := 0; i < 10; i++ {
+ <-num
+ fmt.Println("a")
+ word <- struct{}{}
+ }
+}
+
+func main() {
+ num <- struct{}{}
+ go printNums()
+ go printWords()
+ time.Sleep(time.Second * 1)
+}
+```
+
+代码:
+
+ [@中二的灰太狼](https://www.zhihu.com/people/2d6eba22144ae58788cef3ed60485e9d)
+
+### 启动 2个groutine 2秒后取消, 第一个协程1秒执行完,第二个协程3秒执行完。
+
+思路:采用`ctx, _ := context.WithTimeout(context.Background(), time.Second*2)`实现2s取消。协程执行完后通过channel通知,是否超时。
+
+```go
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+)
+
+func f1(in chan struct{}) {
+
+ time.Sleep(1 * time.Second)
+ in <- struct{}{}
+
+}
+
+func f2(in chan struct{}) {
+ time.Sleep(3 * time.Second)
+ in <- struct{}{}
+}
+
+func main() {
+ ch1 := make(chan struct{})
+ ch2 := make(chan struct{})
+ ctx, _ := context.WithTimeout(context.Background(), 2*time.Second)
+
+ go func() {
+ go f1(ch1)
+ select {
+ case <-ctx.Done():
+ fmt.Println("f1 timeout")
+ break
+ case <-ch1:
+ fmt.Println("f1 done")
+ }
+ }()
+
+ go func() {
+ go f2(ch2)
+ select {
+ case <-ctx.Done():
+ fmt.Println("f2 timeout")
+ break
+ case <-ch2:
+ fmt.Println("f2 done")
+ }
+ }()
+ time.Sleep(time.Second * 5)
+}
+```
+
+代码:
+
+ [@中二的灰太狼](https://www.zhihu.com/people/2d6eba22144ae58788cef3ed60485e9d)
+
+### 当select监控多个chan同时到达就绪态时,如何先执行某个任务?
+
+可以在子case再加一个for select语句。
+
+```go
+func priority_select(ch1, ch2 <-chan string) {
+ for {
+ select {
+ case val := <-ch1:
+ fmt.Println(val)
+ case val2 := <-ch2:
+ priority:
+ for {
+ select {
+ case val1 := <-ch1:
+ fmt.Println(val1)
+
+ default:
+ break priority
+ }
+ }
+ fmt.Println(val2)
+ }
+ }
+
+}
+```
\ No newline at end of file
diff --git "a/docs/golang/\351\235\242\350\257\225\351\242\230/test.md" "b/docs/golang/\351\235\242\350\257\225\351\242\230/test.md"
new file mode 100644
index 0000000..34eb5a3
--- /dev/null
+++ "b/docs/golang/\351\235\242\350\257\225\351\242\230/test.md"
@@ -0,0 +1,4279 @@
+
+### 文章目录
+
+* [SpringBoot](https://www.pudn.com/news/62d0d14055398e076b87721e.html#SpringBoot_0)
+* * [1、SpringBoot入门](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1SpringBoot_7)
+ * * [1.1、Spring程序与SpringBoot程序对比](https://www.pudn.com/news/62d0d14055398e076b87721e.html#11SpringSpringBoot_9)
+ * [1.2、parent](https://www.pudn.com/news/62d0d14055398e076b87721e.html#12parent_13)
+ * [1.3、引导类](https://www.pudn.com/news/62d0d14055398e076b87721e.html#13_36)
+ * [1.4、内置Tomcat](https://www.pudn.com/news/62d0d14055398e076b87721e.html#14Tomcat_59)
+ * [2、Rest风格](https://www.pudn.com/news/62d0d14055398e076b87721e.html#2Rest_75)
+ * * [2.1、什么是Rest](https://www.pudn.com/news/62d0d14055398e076b87721e.html#21Rest_77)
+ * [2.2、Rest入门案例](https://www.pudn.com/news/62d0d14055398e076b87721e.html#22Rest_114)
+ * [2.3、Restful快速开发](https://www.pudn.com/news/62d0d14055398e076b87721e.html#23Restful_153)
+ * [3、配置文档](https://www.pudn.com/news/62d0d14055398e076b87721e.html#3_163)
+ * [3.1、基础配置](https://www.pudn.com/news/62d0d14055398e076b87721e.html#31_165)
+ * [3.2、配置文件类型](https://www.pudn.com/news/62d0d14055398e076b87721e.html#32_191)
+ * [3.3、配置文件加载优先级](https://www.pudn.com/news/62d0d14055398e076b87721e.html#33_221)
+ * [3.4、yaml数据格式](https://www.pudn.com/news/62d0d14055398e076b87721e.html#34yaml_230)
+ * [数据类型](https://www.pudn.com/news/62d0d14055398e076b87721e.html#_262)
+ * [3.5、读取yaml单一属性数据](https://www.pudn.com/news/62d0d14055398e076b87721e.html#35yaml_321)
+ * [3.6、yaml文件中的变量应用](https://www.pudn.com/news/62d0d14055398e076b87721e.html#36yaml_353)
+ * [3.7、读取yaml全部属性数据](https://www.pudn.com/news/62d0d14055398e076b87721e.html#37yaml_367)
+ * [3.8、读取yaml应用类型属性数据](https://www.pudn.com/news/62d0d14055398e076b87721e.html#38yaml_376)
+ * [4、SpringBoot整合JUnit](https://www.pudn.com/news/62d0d14055398e076b87721e.html#4SpringBootJUnit_430)
+ * * [4.1、整合JUnit](https://www.pudn.com/news/62d0d14055398e076b87721e.html#41JUnit_432)
+ * [4.2、整合JUnit——classes属性](https://www.pudn.com/news/62d0d14055398e076b87721e.html#42JUnitclasses_473)
+ * [5、SpringBoot整合MyBatis、MyBatisPlus](https://www.pudn.com/news/62d0d14055398e076b87721e.html#5SpringBootMyBatisMyBatisPlus_499)
+ * * [5.1、整合MyBatis](https://www.pudn.com/news/62d0d14055398e076b87721e.html#51MyBatis_501)
+ * [5.2、常见问题处理](https://www.pudn.com/news/62d0d14055398e076b87721e.html#52_597)
+ * [5.3、整合MyBatisPlus](https://www.pudn.com/news/62d0d14055398e076b87721e.html#53MyBatisPlus_614)
+ * [6、SpringBoot整合Druid](https://www.pudn.com/news/62d0d14055398e076b87721e.html#6SpringBootDruid_674)
+ * [7、SSMP](https://www.pudn.com/news/62d0d14055398e076b87721e.html#7SSMP_715)
+ * * [7.1、数据配置](https://www.pudn.com/news/62d0d14055398e076b87721e.html#71_717)
+ * [7.2、分页](https://www.pudn.com/news/62d0d14055398e076b87721e.html#72_934)
+ * [7.3、数据层标准开发](https://www.pudn.com/news/62d0d14055398e076b87721e.html#73_994)
+ * [7.4、业务层标准开发(基础CRUD)](https://www.pudn.com/news/62d0d14055398e076b87721e.html#74CRUD_1031)
+ * [7.5、业务层快速开发(基于MyBatisPlus构建)](https://www.pudn.com/news/62d0d14055398e076b87721e.html#75MyBatisPlus_1158)
+ * [7.7、表现层标准开发](https://www.pudn.com/news/62d0d14055398e076b87721e.html#77_1285)
+ * [7.8、表现层数据一致性处理(R对象)](https://www.pudn.com/news/62d0d14055398e076b87721e.html#78R_1357)
+ * [8、Springboot工程打包与运行](https://www.pudn.com/news/62d0d14055398e076b87721e.html#8Springboot_1448)
+ * * [8.1、程序为什么要打包](https://www.pudn.com/news/62d0d14055398e076b87721e.html#81_1450)
+ * [8.2、SpringBoot项目快速启动(Windows版)](https://www.pudn.com/news/62d0d14055398e076b87721e.html#82SpringBootWindows_1456)
+ * [8.3、打包插件](https://www.pudn.com/news/62d0d14055398e076b87721e.html#83_1511)
+ * [8.4、Boot工程快速启动](https://www.pudn.com/news/62d0d14055398e076b87721e.html#84Boot_1580)
+ * [8.6、临时属性](https://www.pudn.com/news/62d0d14055398e076b87721e.html#86_1635)
+ * * [8.6.1、临时属性](https://www.pudn.com/news/62d0d14055398e076b87721e.html#861_1637)
+ * [8.6.2、开发环境](https://www.pudn.com/news/62d0d14055398e076b87721e.html#862_1657)
+ * [8.7、配置环境](https://www.pudn.com/news/62d0d14055398e076b87721e.html#87_1688)
+ * * [8.7.1、配置文件分类](https://www.pudn.com/news/62d0d14055398e076b87721e.html#871_1691)
+ * [8.7.2、自定义配置文件](https://www.pudn.com/news/62d0d14055398e076b87721e.html#872_1711)
+ * [8.7.3、多环境开发(yaml)](https://www.pudn.com/news/62d0d14055398e076b87721e.html#873yaml_1745)
+ * [8.7.4、多环境开发文件(yaml)](https://www.pudn.com/news/62d0d14055398e076b87721e.html#874yaml_1783)
+ * [8.7.5、多环境分组管理](https://www.pudn.com/news/62d0d14055398e076b87721e.html#875_1821)
+ * [8.7.6、多环境开发控制](https://www.pudn.com/news/62d0d14055398e076b87721e.html#876_1871)
+ * [9、日志](https://www.pudn.com/news/62d0d14055398e076b87721e.html#9_1923)
+ * * [9.1、日志基础操作](https://www.pudn.com/news/62d0d14055398e076b87721e.html#91_1925)
+ * [9.2、快速创建日志对象](https://www.pudn.com/news/62d0d14055398e076b87721e.html#92_2006)
+ * [9.3、日志输出格式控制](https://www.pudn.com/news/62d0d14055398e076b87721e.html#93_2034)
+ * [9.4、文件记录日志](https://www.pudn.com/news/62d0d14055398e076b87721e.html#94_2061)
+ * [10、热部署](https://www.pudn.com/news/62d0d14055398e076b87721e.html#10_2084)
+ * [11、属性绑定](https://www.pudn.com/news/62d0d14055398e076b87721e.html#11_2113)
+ * [12、常用计量单位应用](https://www.pudn.com/news/62d0d14055398e076b87721e.html#12_2158)
+ * [13、测试类](https://www.pudn.com/news/62d0d14055398e076b87721e.html#13_2230)
+ * * [13.1、加载专用属性](https://www.pudn.com/news/62d0d14055398e076b87721e.html#131_2232)
+ * [13.2、加载专用类](https://www.pudn.com/news/62d0d14055398e076b87721e.html#132_2262)
+ * [13.3、业务层测试事务回滚](https://www.pudn.com/news/62d0d14055398e076b87721e.html#133_2381)
+ * [13.4、测试用例数据设定](https://www.pudn.com/news/62d0d14055398e076b87721e.html#134_2404)
+ * [14、数据层解决方案](https://www.pudn.com/news/62d0d14055398e076b87721e.html#14_2431)
+ * * [14.1、SQL](https://www.pudn.com/news/62d0d14055398e076b87721e.html#141SQL_2433)
+ * [14.2、MongoDB](https://www.pudn.com/news/62d0d14055398e076b87721e.html#142MongoDB_2641)
+ * * [14.2.1、MongoDB的使用](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1421MongoDB_2647)
+ * [14.2.2、MongoDB可视化客户端](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1422MongoDB_2671)
+ * [14.2.3、Springboot集成MongoDB](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1423SpringbootMongoDB_2689)
+ * [14.3、ElasticSearch(ES)](https://www.pudn.com/news/62d0d14055398e076b87721e.html#143ElasticSearchES_2730)
+ * * [14.3.1、ES下载](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1431ES_2738)
+ * [14.3.2、ES索引、分词器](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1432ES_2750)
+ * [14.3.3、文档操作(增删改查)](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1433_2792)
+ * [14.3.4、Springboot集成ES](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1434SpringbootES_2850)
+ * [14.3.5、索引](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1435_2939)
+ * [15、缓存](https://www.pudn.com/news/62d0d14055398e076b87721e.html#15_3057)
+ * * [15.1、缓存简介](https://www.pudn.com/news/62d0d14055398e076b87721e.html#151_3059)
+ * [15.2、缓存使用](https://www.pudn.com/news/62d0d14055398e076b87721e.html#152_3073)
+ * [15.3、其他缓存](https://www.pudn.com/news/62d0d14055398e076b87721e.html#153_3115)
+ * [15.4、缓存使用案例——手机验证码](https://www.pudn.com/news/62d0d14055398e076b87721e.html#154_3132)
+ * * [15.4.1、Cache](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1541Cache_3146)
+ * [15.4.2、Ehcache](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1542Ehcache_3217)
+ * [15.4.3、Redis](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1543Redis_3276)
+ * [15.4.4、memcached](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1544memcached_3316)
+ * [15.4.5、jetcache](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1545jetcache_3445)
+ * [15.4.6、j2cache](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1546j2cache_3652)
+ * [16、定时](https://www.pudn.com/news/62d0d14055398e076b87721e.html#16_3730)
+ * * [16.1、SpringBoot整合Quartz](https://www.pudn.com/news/62d0d14055398e076b87721e.html#161SpringBootQuartz_3745)
+ * [16.2、Spring Task](https://www.pudn.com/news/62d0d14055398e076b87721e.html#162Spring_Task_3795)
+ * [16.3、SpringBoot整合JavaMail](https://www.pudn.com/news/62d0d14055398e076b87721e.html#163SpringBootJavaMail_3841)
+ * [17、消息](https://www.pudn.com/news/62d0d14055398e076b87721e.html#17_3925)
+ * * [17.1、JMS](https://www.pudn.com/news/62d0d14055398e076b87721e.html#171JMS_3935)
+ * [17.2、AMQP](https://www.pudn.com/news/62d0d14055398e076b87721e.html#172AMQP_3954)
+ * [17.3、MQTT](https://www.pudn.com/news/62d0d14055398e076b87721e.html#173MQTT_3970)
+ * [17.4、Kafka](https://www.pudn.com/news/62d0d14055398e076b87721e.html#174Kafka_3976)
+ * [17.5、消息案例](https://www.pudn.com/news/62d0d14055398e076b87721e.html#175_3982)
+ * [17.6、ActiveMQ](https://www.pudn.com/news/62d0d14055398e076b87721e.html#176ActiveMQ_4000)
+ * * [17.6.1、SpringBoot整合ActiveMQ](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1761SpringBootActiveMQ_4018)
+ * [17.7、RabbitMQ](https://www.pudn.com/news/62d0d14055398e076b87721e.html#177RabbitMQ_4104)
+ * * [17.7.1、SpringBoot整合RabbitMQ](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1771SpringBootRabbitMQ_4148)
+ * [17.8、RocketMQ](https://www.pudn.com/news/62d0d14055398e076b87721e.html#178RocketMQ_4323)
+ * * [17.8.1、SpringBoot整合RocketMQ](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1781SpringBootRocketMQ_4358)
+ * [17.9、Kafka](https://www.pudn.com/news/62d0d14055398e076b87721e.html#179Kafka_4434)
+ * * [17.9.1、SpringBoot整合Kafka](https://www.pudn.com/news/62d0d14055398e076b87721e.html#1791SpringBootKafka_4476)
+ * [18、监控](https://www.pudn.com/news/62d0d14055398e076b87721e.html#18_4525)
+ * * [18.1、简介](https://www.pudn.com/news/62d0d14055398e076b87721e.html#181_4527)
+ * [18.2、可视化监控平台](https://www.pudn.com/news/62d0d14055398e076b87721e.html#182_4545)
+ * [18.3、监控原理](https://www.pudn.com/news/62d0d14055398e076b87721e.html#183_4658)
+ * [18.4、自定义监控指标](https://www.pudn.com/news/62d0d14055398e076b87721e.html#184_4703)
+ * [18.3、监控原理](https://www.pudn.com/news/62d0d14055398e076b87721e.html#183_4879)
+ * [18.4、自定义监控指标](https://www.pudn.com/news/62d0d14055398e076b87721e.html#184_4928)
+
+## 1、SpringBoot入门
+
+### 1.1、Spring程序与SpringBoot程序对比
+
+
+
+### 1.2、parent
+
+
+
+* starter
+
+ SpringBoot中常见项目名称,定义了当前项目使用的所有依赖坐标,以达到减少依赖配置的目的
+
+* parent
+
+ 所有SpringBoot项目要继承的项目,定义了若干个坐标版本号(依赖管理,而非依赖),以达到减少依赖冲突的目的
+
+ spring-boot-starter-parent各版本间存在着诸多坐标版本不同
+
+* 实际开发
+
+ 使用任意坐标时,仅书写GAV(groupId, artifactId, version)中的G和A,V由SpringBoot提供,除非SpringBoot未提供对应版本V
+
+ 如发生坐标错误,再指定Version(要小心版本冲突)
+
+### 1.3、引导类
+
+* 启动方式
+
+ @SpringBootApplication
+ public class Springboot0101QuickstartApplication {
+
+ public static void main(String[] args) {
+ ConfigurableApplicationContext ctx = SpringApplication.run(Springboot0101QuickstartApplication.class, args);
+ //获取bean对象
+ BookController bean = ctx.getBean(BookController.class);
+ System.out.println("bean======>" + bean);
+ }
+ }
+
+ SpringBoot的引导类是Boot工程的执行入口,运行main方法就可以启动项目
+
+ SpringBoot工程运行后初始化Spring容器,扫描引导类所在包加载bean
+
+### 1.4、内置Tomcat
+
+
+
+* Jetty比Tomcat更轻量级,可扩展性更强(相较于Tomcat),谷歌应用引擎(GAE)已经全面切换为Jetty
+
+* 内置服务器
+
+ tomcat(默认) apache出品,粉丝多,应用面广,负载了若干较重的组件
+
+ jetty 更轻量级,负载性能远不及tomcat
+
+ undertow undertow,负载性能勉强跑赢tomcat
+
+## 2、Rest风格
+
+### 2.1、什么是Rest
+
+1. 什么是 rest :
+
+ REST(Representational State Transfer)表现形式状态转换
+
+ 传统风格资源描述形式
+ http://localhost/user/getById?id=1 (得到id为1的用户)
+ http://localhost/user/saveUser (保存用户)
+
+ REST风格描述形式
+ http://localhost/user/1 (得到id为1的用户)
+ http://localhost/user (保存用户)
+
+2. 优点:
+
+ 隐藏资源的访问行为, 无法通过地址得知对资源是何种操作
+ 书写简化
+
+3. 按照REST风格访问资源时使用行为动作区分对资源进行了何种操作
+
+ GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源
+
+ http://localhost/users 查询全部用户信息 GET (查询)
+ http://localhost/users/1 查询指定用户信息 GET (查询)
+ http://localhost/users 添加用户信息 POST (新增/保存)
+ http://localhost/users 修改用户信息 PUT (修改/更新)
+ http://localhost/users/1 删除用户信息 DELETE (删除)
+
+注意:
+
+上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范
+描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如: users、
+
+1. 根据REST风格对资源进行访问称为**RESTful**
+
+### 2.2、Rest入门案例
+
+步骤:
+
+①设定http请求动作(动词)
+
+使用 @RequestMapping 注解的 method 属性声明请求的方式
+
+使用 @RequestBody 注解 获取请求体内容。直接使用得到是 key=value&key=value…结构的数据。get 请求方式不适用。
+
+使用@ResponseBody 注解实现将 controller 方法返回对象转换为 json 响应给客户端。
+
+@RequestMapping(value=“/users”,method=RequestMethod.POST)
+
+
+
+②:设定请求参数(路径变量)
+
+使用`@PathVariable` 用于绑定 url 中的占位符。例如:请求 url 中 /delete/`{id}`,这个`{id}`就是 url 占位符。
+
+
+@RequestMapping
+
+![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HhzZsWGO-1657811363433)(SpringBoot.assets/image-20220312164532116.png)]](https://img-blog.csdnimg.cn/7fb5465b71a346d4b2e8920493c5e36c.png)
+
+@PathVariable
+
+![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yaepxdng-1657811363434)(SpringBoot.assets/image-20220312164552778.png)]](https://img-blog.csdnimg.cn/a9c22e3367a7480ea219baaa21de868e.png)
+
+@RequestBody @RequestParam @PathVariable
+
+
+### 2.3、Restful快速开发
+
+使用 `@RestController` 注解开发 RESTful 风格
+
+
+使用 @GetMapping @PostMapping @PutMapping @DeleteMapping 简化 `@RequestMapping` 注解开发
+
+
+
+### 3、配置文档
+
+### 3.1、基础配置
+
+1. 修改配置
+ 修改服务器端口
+ server.port=80
+ 关闭运行日志图标(banner)
+ spring.main.banner-mode=off
+ 设置日志相关
+ logging.level.root=debug
+
+# 服务器端口配置
+server.port=80
+
+# 修改banner
+# spring.main.banner-mode=off
+# spring.banner.image.location=logo.png
+
+# 日志
+logging.level.root=info
+
+1. SpringBoot内置属性查询
+ https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties
+ 官方文档中参考文档第一项:Application Propertie
+
+### 3.2、配置文件类型
+
+* 配置文件格式
+ 
+
+* SpringBoot提供了多种属性配置方式
+
+ application.properties
+
+ server.port=80
+
+ application.yml
+
+ server:
+ port: 81
+
+ application.yaml
+
+ server:
+ port: 82
+
+
+
+### 3.3、配置文件加载优先级
+
+* SpringBoot配置文件加载顺序
+ application.properties > application.yml > application.yaml
+* 常用配置文件种类
+ application.yml
+
+### 3.4、yaml数据格式
+
+yaml
+
+YAML(YAML Ain’t Markup Language),一种数据序列化格式
+
+优点:
+ 容易阅读
+ 容易与脚本语言交互
+ 以数据为核心,重数据轻格式
+
+YAML文件扩展名
+ .yml(主流)
+ .yaml
+
+yaml语法规则
+基本语法
+
+key: value -> value 前面一定要有空格
+大小写敏感
+属性层级关系使用多行描述,每行结尾使用冒号结束
+使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)
+属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)
+# 表示注释
+核心规则:数据前面要加空格与冒号隔开
server:
+ servlet:
+ context-path: /hello
+ port: 82
+### 数据类型
+
+* 字面值表示方式
+ 
+
+# 字面值表示方式
+
+boolean: TRUE #TRUE,true,True,FALSE,false , False 均可
+float: 3.14 #6.8523015e+5 # 支持科学计数法
+int: 123 #0b1010_0111_0100_1010_1110 # 支持二进制、八进制、十六进制
+# null: ~ # 使用 ~ 表示 null
+string: HelloWorld # 字符串可以直接书写
+string2: "Hello World" # 可以使用双引号包裹特殊字符
+date: 2018-02-17 # 日期必须使用 yyyy-MM-dd 格式
+datetime: 2018-02-17T15:02:31+08:00 # 时间和日期之间使用 T 连接,最后使用 + 代表时区
+
+* 数组表示方式:在属性名书写位置的下方使用减号作为数据开始符号,每行书写一个数据,减号与数据间空格分隔
+
+
+
+subject:
+ - Java
+ - 前端
+ - 大数据
+
+enterprise:
+ name: zhangsan
+ age: 16
+
+subject2:
+ - Java
+ - 前端
+ - 大数据
+likes: [王者荣耀,刺激战场] # 数组书写缩略格式
+
+users: # 对象数组格式
+ - name: Tom
+ age: 4
+
+ - name: Jerry
+ age: 5
+users2: # 对象数组格式二
+ -
+ name: Tom
+ age: 4
+ -
+ name: Jerry
+ age: 5
+
+# 对象数组缩略格式
+users3: [ { name:Tom , age:4 } , { name:Jerry , age:5 } ]
+### 3.5、读取yaml单一属性数据
+
+* 使用@Value读取单个数据,属性名引用方式:${一级属性名.二级属性名……}
+ 
+
+ @Value("${country}")
+ private String country1;
+
+ @Value("${user.age}")
+ private String age1;
+
+ @Value("${likes[1]}")
+ private String likes1;
+
+ @Value("${users[1].name}")
+ private String name1;
+
+ @GetMapping
+ public String getById() {
+ System.out.println("springboot is running2...");
+ System.out.println("country1=>" + country1);
+ System.out.println("age1=>" + age1);
+ System.out.println("likes1=>" + likes1);
+ System.out.println("name1=>" + name1);
+ return "springboot is running2...";
+ }
+### 3.6、yaml文件中的变量应用
+
+* 在配置文件中可以使用属性名引用方式引用属性
+ 
+ 
+
+* 属性值中如果出现转移字符,需要使用双引号包裹
+
+ lesson: "Spring\tboot\nlesson"
+
+### 3.7、读取yaml全部属性数据
+
+* 封装全部数据到Environment对象
+* 注意 要导这个 包
+* **import org.springframework.core.env.Environment**
+ 
+ 
+
+### 3.8、读取yaml应用类型属性数据
+
+* 自定义对象封装指定数据
+
+
+
+* 自定义对象封装指定数据的作用
+ 
+
+# 创建类,用于封装下面的数据
+# 由spring帮我们去加载数据到对象中,一定要告诉spring加载这组信息
+# 使用时候从spring中直接获取信息使用
+
+datasource:
+ driver: com.mysql.jdbc.Driver
+ url: jdbc:mysql://localhost/springboot_db
+ username: root
+ password: root666123
//1.定义数据模型封装yaml文件中对应的数据
+//2.定义为spring管控的bean
+@Component
+//3.指定加载的数据
+@ConfigurationProperties(prefix = "datasource")
+public class MyDataSource {
+
+ private String driver;
+ private String url;
+ private String username;
+ private String password;
+
+ //省略get/set/tostring 方法
+}
+
+使用自动装配封装指定数据
+
+ @Autowired
+ private MyDataSource myDataSource;
+
+输出查看
+
+System.out.println(myDataSource);
+## 4、SpringBoot整合JUnit
+
+### 4.1、整合JUnit
+
+* 添加Junit的起步依赖 Spring Initializr 创建时自带
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+* SpringBoot整合JUnit
+
+ @SpringBootTest
+ class Springboot07JunitApplicationTests {
+ @Autowired
+ private BookService bookService;
+ @Test
+ public void testSave(){
+ bookService.save();
+ }
+ }
+* @SpringBootTest
+ 名称:@SpringBootTest
+ 类型:测试类注解
+ 位置:测试类定义上方
+ 作用:设置JUnit加载的SpringBoot启动类
+ 范例:
+
+ @SpringBootTest
+ class Springboot05JUnitApplicationTests {}
+
+### 4.2、整合JUnit——classes属性
+
+
+
+@SpringBootTest(classes = Springboot04JunitApplication.class)
+//@ContextConfiguration(classes = Springboot04JunitApplication.class)
+class Springboot04JunitApplicationTests {
+ //1.注入你要测试的对象
+ @Autowired
+ private BookDao bookDao;
+
+ @Test
+ void contextLoads() {
+ //2.执行要测试的对象对应的方法
+ bookDao.save();
+ System.out.println("two...");
+ }
+}
+
+注意:
+
+* 如果测试类在SpringBoot启动类的包或子包中,可以省略启动类的设置,也就是省略classes的设定
+
+## 5、SpringBoot整合MyBatis、MyBatisPlus
+
+### 5.1、整合MyBatis
+
+①:创建新模块,选择Spring初始化,并配置模块相关基础信息
+
+②:选择当前模块需要使用的技术集(MyBatis、MySQL)
+
+③:设置数据源参数
+
+#DB Configuration:
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/springboot_db
+ username: root
+ password: 123456
+
+④:创建user表
+在 springboot_db 数据库中创建 user 表
+
+-- ----------------------------
+-- Table structure for `user`
+-- ----------------------------
+DROP TABLE IF EXISTS `user`;
+CREATE TABLE `user` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `username` varchar(50) DEFAULT NULL,
+ `password` varchar(50) DEFAULT NULL,
+ `name` varchar(50) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Records of user
+-- ----------------------------
+INSERT INTO `user` VALUES ('1', 'zhangsan', '123', '张三');
+INSERT INTO `user` VALUES ('2', 'lisi', '123', '李四');
+
+⑤:创建实体Bean
+
+public class User {
+ // 主键
+ private Long id;
+ // 用户名
+ private String username;
+ // 密码
+ private String password;
+ // 姓名
+ private String name;
+
+ //此处省略getter,setter,toString方法 .. ..
+
+}
+
+⑥: 定义数据层接口与映射配置
+
+@Mapper
+public interface UserDao {
+
+ @Select("select * from user")
+ public List getAll();
+}
+
+⑦:测试类中注入dao接口,测试功能
+
+@SpringBootTest
+class Springboot05MybatisApplicationTests {
+
+ @Autowired
+ private UserDao userDao;
+
+ @Test
+ void contextLoads() {
+ List userList = userDao.getAll();
+ System.out.println(userList);
+ }
+
+}
+
+⑧:运行如下
+
+[User{id=1, username='zhangsan', password='123', name='张三'}, User{id=2, username='lisi', password='123', name='李四'}]
+### 5.2、常见问题处理
+
+SpringBoot版本低于2.4.3(不含),Mysql驱动版本大于8.0时,需要在url连接串中配置时区
+
+jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC
+
+或在MySQL数据库端配置时区解决此问题
+
+1.MySQL 8.X驱动强制要求设置时区
+
+修改url,添加serverTimezone设定
+修改MySQL数据库配置(略)
+
+2.驱动类过时,提醒更换为com.mysql.cj.jdbc.Driver
+
+### 5.3、整合MyBatisPlus
+
+①:手动添加SpringBoot整合MyBatis-Plus的坐标,可以通过mvnrepository获取
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ 3.4.3
+
+
+注意事项: 由于SpringBoot中未收录MyBatis-Plus的坐标版本,需要指定对应的Version
+
+②:定义数据层接口与映射配置,继承BaseMapper
+
+@Mapper
+public interface UserDao extends BaseMapper {
+
+}
+
+③:其他同SpringBoot整合MyBatis
+(略)
+
+④:测试类中注入dao接口,测试功能
+
+@SpringBootTest
+class Springboot06MybatisPlusApplicationTests {
+
+ @Autowired
+ private UserDao userDao;
+
+ @Test
+ void contextLoads() {
+ List users = userDao.selectList(null);
+ System.out.println(users);
+ }
+
+}
+
+⑤: 运行如下:
+
+[User{id=1, username='zhangsan', password='123', name='张三'}, User{id=2, username='lisi', password='123', name='李四'}]
+
+注意: 如果你的数据库表有前缀要在 application.yml 添加如下配制
+
+#设置Mp相关的配置
+mybatis-plus:
+ global-config:
+ db-config:
+ table-prefix: tbl_
+### 6、SpringBoot整合Druid
+
+①: 导入Druid对应的starter
+
+
+ com.alibaba
+ druid-spring-boot-starter
+ 1.2.6
+
#DB Configuration:
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC
+ username: root
+ password: Lemon
+ type: com.alibaba.druid.pool.DruidDataSource
+
+②: 指定数据源类型 (这种方式只需导入一个 Druid 的坐标)
+
+spring:
+ datasource:
+ druid:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/springboot_db?serverTimezone=UTC
+ username: root
+ password: 123456
+
+或者 变更Druid的配置方式(推荐) 这种方式需要导入 Druid对应的starter
+
+## 7、SSMP
+
+### 7.1、数据配置
+
+1\. 案例实现方案分析
+ 实体类开发————使用Lombok快速制作实体类
+ Dao开发————整合MyBatisPlus,制作数据层测试类
+ Service开发————基于MyBatisPlus进行增量开发,制作业务层测试类
+ Controller开发————基于Restful开发,使用PostMan测试接口功能
+ Controller开发————前后端开发协议制作
+ 页面开发————基于VUE+ElementUI制作,前后端联调,页面数据处理,页面消息处理
+ 列表、新增、修改、删除、分页、查询
+ 项目异常处理
+ 按条件查询————页面功能调整、Controller修正功能、Service修正功能
+2\. SSMP案例制作流程解析
+ 先开发基础CRUD功能,做一层测一层
+ 调通页面,确认异步提交成功后,制作所有功能
+ 添加分页功能与查询功能
DROP TABLE IF EXISTS `tbl_book`;
+CREATE TABLE `tbl_book` (
+ `id` int(11) NOT NULL AUTO_INCREMENT,
+ `type` varchar(20) DEFAULT NULL,
+ `name` varchar(50) DEFAULT NULL,
+ `description` varchar(255) DEFAULT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Records of tbl_book
+-- ----------------------------
+INSERT INTO `tbl_book` VALUES ('1', '计算机理论', 'Spring实战第5版', 'Spring入门经典教程,深入理解Spring原理技术内幕');
+INSERT INTO `tbl_book` VALUES ('2', '计算机理论', 'Spring 5核心原理与30个类手写实战', '十年沉淀之作,写Spring精华思想');
+INSERT INTO `tbl_book` VALUES ('3', '计算机理论', 'Spring 5设计模式', '深入Spring源码剖析Spring源码中蕴含的10大设计模式');
+INSERT INTO `tbl_book` VALUES ('4', '计算机理论', 'Spring MVC+ MyBatis开发从入门到项目实战', '全方位解析面向Web应用的轻量级框架,带你成为Spring MVC开发高手');
+INSERT INTO `tbl_book` VALUES ('5', '计算机理论', '轻量级Java Web企业应用实战', '源码级剖析Spring框架,适合已掌握Java基础的读者');
+INSERT INTO `tbl_book` VALUES ('6', '计算机理论', 'Java核心技术卷|基础知识(原书第11版)', 'Core Java第11版,Jolt大奖获奖作品,针对Java SE9、10、 11全面更新');
+INSERT INTO `tbl_book` VALUES ('7', '计算机理论', '深入理解Java虚拟机', '5个维度全面剖析JVM,面试知识点全覆盖');
+INSERT INTO `tbl_book` VALUES ('8', '计算机理论', 'Java编程思想(第4版)', 'Java学习必读经典殿堂级著作!赢得了全球程序员的广泛赞誉');
+INSERT INTO `tbl_book` VALUES ('9', '计算机理论', '零基础学Java (全彩版)', '零基础自学编程的入门]图书,由浅入深,详解Java语言的编程思想和核心技术');
+INSERT INTO `tbl_book` VALUES ('10', '市场营销', '直播就该这么做:主播高效沟通实战指南', '李子柒、李佳琦、薇娅成长为网红的秘密都在书中');
+INSERT INTO `tbl_book` VALUES ('11', '市场营销', '直播销讲实战一本通', '和秋叶一起学系列网络营销书籍');
+INSERT INTO `tbl_book` VALUES ('12', '市场营销', '直播带货:淘宝、天猫直播从新手到高手', '一本教你如何玩转直播的书, 10堂课轻松实现带货月入3W+');
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ mysql
+ mysql-connector-java
+ runtime
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ 3.4.3
+
+
+
+ com.alibaba
+ druid-spring-boot-starter
+ 1.2.6
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ 3.4.3
+
+
+
+ com.alibaba
+ druid-spring-boot-starter
+ 1.2.6
+
+
+
server:
+ port: 80
+# druid 数据源配制
+spring:
+ datasource:
+ druid:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC
+ username: root
+ password: Lemon
+
+# mybatis-plus
+mybatis-plus:
+ global-config:
+ db-config:
+ table-prefix: tbl_
+ id-type: auto # 主键策略
+
+Book.class
+
+@Data
+public class Book {
+ private Integer id;
+ private String type;
+ private String name;
+ private String description;
+}
+
+BookDao.class
+
+@Mapper
+public interface BookDao extends BaseMapper {
+
+ /**
+ * 查询一个
+ * 这是 Mybatis 开发
+ * @param id
+ * @return
+ */
+ @Select("select * from tbl_book where id = #{id}")
+ Book getById(Integer id);
+}
+
+测试类
+
+@SpringBootTest
+public class BookDaoTestCase {
+
+ @Autowired
+ private BookDao bookDao;
+
+ @Test
+ void testGetById() {
+ System.out.println(bookDao.getById(1));
+ System.out.println(bookDao.selectById(1));
+ }
+
+ @Test
+ void testSave() {
+ Book book = new Book();
+ book.setType("测试数据123");
+ book.setName("测试数据123");
+ book.setDescription("测试数据123");
+ bookDao.insert(book);
+ }
+
+ @Test
+ void testUpdate() {
+ Book book = new Book();
+ book.setId(13);
+ book.setType("测试数据asfd");
+ book.setName("测试数据123");
+ book.setDescription("测试数据123");
+ bookDao.updateById(book);
+ }
+
+ @Test
+ void testDelete() {
+ bookDao.deleteById(13);
+ }
+
+ @Test
+ void testGetAll() {
+ System.out.println(bookDao.selectList(null));
+ }
+
+ @Test
+ void testGetPage() {
+ }
+
+ @Test
+ void testGetBy() {
+ }
+}
+
+开启MybatisPlus运行日志
+
+# mybatis-plus
+mybatis-plus:
+ global-config:
+ db-config:
+ table-prefix: tbl_
+ id-type: auto # 主键策略
+ configuration:
+ # 开启MyBatisPlus的日志
+ log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+### 7.2、分页
+
+分页操作需要设定分页对象IPage
+
+@Test
+void testGetPage() {
+ IPage page = new Page(1, 5);
+ bookDao.selectPage(page, null);
+}
+
+* IPage对象中封装了分页操作中的所有数据
+
+ 数据
+
+ 当前页码值
+
+ 每页数据总量
+
+ 最大页码值
+
+ 数据总量
+
+* 分页操作是在MyBatisPlus的常规操作基础上增强得到,内部是动态的拼写SQL语句,因此需要增强对应的功能
+
+使用MyBatisPlus拦截器实现
+
+@Configuration
+public class MybatisPlusConfig {
+
+ @Bean
+ public MybatisPlusInterceptor mybatisPlusInterceptor() {
+ //1\. 定义 Mp 拦截器
+ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+ //2\. 添加具体的拦截器 分页拦截器
+ interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
+ return interceptor;
+ }
+}
+
+测试
+
+@Test
+void testGetPage() {
+ IPage page = new Page(1, 5);
+ bookDao.selectPage(page, null);
+ System.out.println(page.getCurrent());
+ System.out.println(page.getSize());
+ System.out.println(page.getPages());
+ System.out.println(page.getTotal());
+ System.out.println(page.getRecords());
+}
+### 7.3、数据层标准开发
+
+* 使用QueryWrapper对象封装查询条件,推荐使用LambdaQueryWrapper对象,所有查询操作封装成方法调用
+
+@Test
+void testGetBy2() {
+ LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
+ lambdaQueryWrapper.like(Book::getName, "Spring");
+ bookDao.selectList(lambdaQueryWrapper);
+}
@Test
+void testGetBy() {
+ QueryWrapper queryWrapper = new QueryWrapper<>();
+ queryWrapper.like("name", "Spring");
+ bookDao.selectList(queryWrapper);
+}
+
+* 支持动态拼写查询条件
+
+@Test
+void testGetBy2() {
+ String name = "1";
+ LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>();
+ //if (name != null) lambdaQueryWrapper.like(Book::getName,name);
+ lambdaQueryWrapper.like(Strings.isNotEmpty(name), Book::getName, name);
+ bookDao.selectList(lambdaQueryWrapper);
+}
+### 7.4、业务层标准开发(基础CRUD)
+
+* Service层接口定义与数据层接口定义具有较大区别,不要混用
+ selectByUserNameAndPassword(String username,String password); 数据层接口
+ login(String username,String password); Service层接口
+
+* 接口定义
+
+ public interface BookService {
+
+ Boolean save(Book book);
+
+ Boolean update(Book book);
+
+ Boolean delete(Integer id);
+
+ Book getById(Integer id);
+
+ List getAll();
+
+ IPage getPage(int currentPage,int pageSize);
+ }
+* 实现类定义
+
+@Service
+public class BookServiceImpl implements BookService {
+
+ @Autowired
+ private BookDao bookDao;
+
+ @Override
+ public Boolean save(Book book) {
+ return bookDao.insert(book) > 0;
+ }
+
+ @Override
+ public Boolean update(Book book) {
+ return bookDao.updateById(book) > 0;
+ }
+
+ @Override
+ public Boolean delete(Integer id) {
+ return bookDao.deleteById(id) > 0;
+ }
+
+ @Override
+ public Book getById(Integer id) {
+ return bookDao.selectById(id);
+ }
+
+ @Override
+ public List getAll() {
+ return bookDao.selectList(null);
+ }
+
+ @Override
+ public IPage getPage(int currentPage, int pageSize) {
+ IPage page = new Page(currentPage, pageSize);
+ bookDao.selectPage(page, null);
+ return page;
+ }
+}
+
+* 测试类定义
+
+@SpringBootTest
+public class BookServiceTestCase {
+
+ @Autowired
+ private BookService bookService;
+
+ @Test
+ void testGetById() {
+ System.out.println(bookService.getById(4));
+ }
+
+ @Test
+ void testSave() {
+ Book book = new Book();
+ book.setType("测试数据123");
+ book.setName("测试数据123");
+ book.setDescription("测试数据123");
+ bookService.save(book);
+ }
+
+ @Test
+ void testUpdate() {
+ Book book = new Book();
+ book.setId(14);
+ book.setType("测试数据asfd");
+ book.setName("测试数据123");
+ book.setDescription("测试数据123");
+ bookService.update(book);
+ }
+
+ @Test
+ void testDelete() {
+ bookService.delete(14);
+ }
+
+ @Test
+ void testGetAll() {
+ System.out.println(bookService.getAll());
+ }
+
+ @Test
+ void testGetPage() {
+ IPage page = bookService.getPage(2, 5);
+ System.out.println(page.getCurrent());
+ System.out.println(page.getSize());
+ System.out.println(page.getPages());
+ System.out.println(page.getTotal());
+ System.out.println(page.getRecords());
+ }
+}
+### 7.5、业务层快速开发(基于MyBatisPlus构建)
+
+* 快速开发方案
+
+ 使用MyBatisPlus提供有业务层通用接口(ISerivce)与业务层通用实现类(ServiceImpl)
+
+ 在通用类基础上做功能重载或功能追加
+
+ 注意重载时不要覆盖原始操作,避免原始提供的功能丢失
+
+* 接口定义
+
+public interface IBookService extends IService {
+}
+
+* 接口追加功能
+
+public interface IBookService extends IService {
+
+ // 追加的操作与原始操作通过名称区分,功能类似
+ Boolean delete(Integer id);
+
+ Boolean insert(Book book);
+
+ Boolean modify(Book book);
+
+ Book get(Integer id);
+}
+
+
+
+* 实现类定义
+
+@Service
+public class BookServiceImpl extends ServiceImpl implements IBookService {
+}
+
+* 实现类追加功能
+
+@Service
+public class BookServiceImpl extends ServiceImpl implements IBookService {
+
+ @Autowired
+ private BookDao bookDao;
+
+ public Boolean insert(Book book) {
+ return bookDao.insert(book) > 0;
+ }
+
+ public Boolean modify(Book book) {
+ return bookDao.updateById(book) > 0;
+ }
+
+ public Boolean delete(Integer id) {
+ return bookDao.deleteById(id) > 0;
+ }
+
+ public Book get(Integer id) {
+ return bookDao.selectById(id);
+ }
+}
+
+* 测试类定义
+
+@SpringBootTest
+public class BookServiceTest {
+
+ @Autowired
+ private IBookService bookService;
+
+ @Test
+ void testGetById() {
+ System.out.println(bookService.getById(4));
+ }
+
+ @Test
+ void testSave() {
+ Book book = new Book();
+ book.setType("测试数据123");
+ book.setName("测试数据123");
+ book.setDescription("测试数据123");
+ bookService.save(book);
+ }
+
+ @Test
+ void testUpdate() {
+ Book book = new Book();
+ book.setId(14);
+ book.setType("===========");
+ book.setName("测试数据123");
+ book.setDescription("测试数据123");
+ bookService.updateById(book);
+ }
+
+ @Test
+ void testDelete() {
+ bookService.removeById(14);
+ }
+
+ @Test
+ void testGetAll() {
+ System.out.println(bookService.list());
+ }
+
+ @Test
+ void testGetPage() {
+ IPage page = new Page<>(2, 5);
+ bookService.page(page);
+ System.out.println(page.getCurrent());
+ System.out.println(page.getSize());
+ System.out.println(page.getPages());
+ System.out.println(page.getTotal());
+ System.out.println(page.getRecords());
+ }
+}
+### 7.7、表现层标准开发
+
+* 基于Restful进行表现层接口开发
+* 使用Postman测试表现层接口功能
+
+@RestController
+@RequestMapping("/books")
+public class BookController {
+
+ @Autowired
+ private IBookService bookService;
+
+ @GetMapping
+ public List getAll() {
+ return bookService.list();
+ }
+
+ @PostMapping
+ public Boolean save(@RequestBody Book book) {
+ return bookService.save(book);
+ }
+
+ @PutMapping
+ public Boolean update(@RequestBody Book book) {
+ return bookService.modify(book);
+ }
+
+ @DeleteMapping("{id}")
+ public Boolean delete(@PathVariable Integer id) {
+ return bookService.delete(id);
+ }
+
+ @GetMapping("{id}")
+ public Book getById(@PathVariable Integer id) {
+ return bookService.getById(id);
+ }
+
+ @GetMapping("{currentPage}/{pageSize}")
+ public IPage getPage(@PathVariable Integer currentPage, @PathVariable int pageSize) {
+ return bookService.getPage(currentPage, pageSize);
+ }
+
+}
+
+添加 分页的业务层方法
+
+IBookService
+
+ IPage getPage(int currentPage,int pageSize);
+
+BookServiceImpl
+
+@Override
+public IPage getPage(int currentPage, int pageSize) {
+
+ IPage page = new Page(currentPage, pageSize);
+ bookDao.selectPage(page, null);
+
+ return page;
+}
+
+
+
+
+
+### 7.8、表现层数据一致性处理(R对象)
+
+统一格式
+
+
+
+
+* 设计表现层返回结果的模型类,用于后端与前端进行数据格式统一,也称为**前后端数据协议**
+
+@Data
+public class R {
+ private Boolean flag;
+ private Object data;
+
+ public R() {
+ }
+
+ /**
+ * 不返回数据的构造方法
+ *
+ * @param flag
+ */
+ public R(Boolean flag) {
+ this.flag = flag;
+ }
+
+ /**
+ * 返回数据的构造方法
+ *
+ * @param flag
+ * @param data
+ */
+ public R(Boolean flag, Object data) {
+ this.flag = flag;
+ this.data = data;
+ }
+}
+
+* 表现层接口统一返回值类型结果
+
+@RestController
+@RequestMapping("/books")
+public class BookController {
+
+ @Autowired
+ private IBookService bookService;
+
+ @GetMapping
+ public R getAll() {
+ return new R(true, bookService.list());
+ }
+
+ @PostMapping
+ public R save(@RequestBody Book book) {
+ return new R(bookService.save(book));
+
+ }
+
+ @PutMapping
+ public R update(@RequestBody Book book) {
+ return new R(bookService.modify(book));
+ }
+
+ @DeleteMapping("{id}")
+ public R delete(@PathVariable Integer id) {
+ return new R(bookService.delete(id));
+ }
+
+ @GetMapping("{id}")
+ public R getById(@PathVariable Integer id) {
+ return new R(true, bookService.getById(id));
+ }
+
+ @GetMapping("{currentPage}/{pageSize}")
+ public R getPage(@PathVariable Integer currentPage, @PathVariable int pageSize) {
+ return new R(true, bookService.getPage(currentPage, pageSize));
+ }
+
+}
+
+
+
+**前端部分省略**
+
+## 8、Springboot工程打包与运行
+
+### 8.1、程序为什么要打包
+
+将程序部署在独立的服务器上
+
+
+### 8.2、SpringBoot项目快速启动(Windows版)
+
+步骤
+
+①:对SpringBoot项目打包(执行[Maven](https://so.csdn.net/so/search?q=Maven&spm=1001.2101.3001.7020)构建指令package)
+执行 package 打包命令之前 先执行 **mvn clean** 删除 target 目录及内容
+
+mvn package
+
+![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Eg4DFXqd-1657811363451)(SpringBoot.assets/image-20220317212408717.png)]](https://img-blog.csdnimg.cn/a19f1b3a110349cd9e16dbbd229ded5e.png)
+
+打包完成 生成对应的 jar 文件
+
+
+可能出现的问题: IDEA下 执行 Maven 命令控制台中文乱码
+Ctr+Alt+S 打开设置,在Build,Execution ,Deployment找到Build Tools下Maven项下的Runner ,在VM Options 添加
+-Dfile.encoding=GB2312 ,点击OK。
+![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z5rHQGqK-1657811363452)(SpringBoot.assets/image-20220317212514627.png)]](https://img-blog.csdnimg.cn/74c0cddee08a4766bf1390fb3f7fac3f.png)
+
+②:运行项目(执行启动指令) java -jar <打包文件名>
+
+java –jar springboot.jar
+
+注意事项:
+jar支持命令行启动需要依赖maven插件支持,请确认打包时是否具有SpringBoot对应的maven插件
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
+地址栏输入 cmd 回车
+
+
+执行 java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar
+
+
+打包优化:跳过 test 生命周期
+
+
+
+### 8.3、打包插件
+
+如果没有配制spring boot 打包插件可能遇到下面的问题:
+
+
+使用SpringBoot提供的maven插件可以将工程打包成可执行jar包
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+可执行jar包目录
+
+
+ar包描述文件(MANIFEST.MF)
+
+普通工程
+
+Manifest-Version: 1.0
+Implementation-Title: springboot_08_ssmp
+Implementation-Version: 0.0.1-SNAPSHOT
+Build-Jdk-Spec: 1.8
+Created-By: Maven Jar Plugin 3.2.0
+
+基于spring-boot-maven-plugin打包的工程
+
+Manifest-Version: 1.0
+Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
+Implementation-Title: springboot_08_ssmp
+Implementation-Version: 0.0.1-SNAPSHOT
+Spring-Boot-Layers-Index: BOOT-INF/layers.idx
+Start-Class: com.example.SSMPApplication 启动类
+Spring-Boot-Classes: BOOT-INF/classes/
+Spring-Boot-Lib: BOOT-INF/lib/
+Build-Jdk-Spec: 1.8
+Spring-Boot-Version: 2.5.6
+Created-By: Maven Jar Plugin 3.2.0
+Main-Class: org.springframework.boot.loader.JarLauncher jar启动器
+
+命令行启动常见问题及解决方案
+
+* Windonws端口被占用
+
+# 查询端口
+netstat -ano
+# 查询指定端口
+netstat -ano |findstr "端口号"
+# 根据进程PID查询进程名称
+tasklist |findstr "进程PID号"
+# 根据PID杀死任务
+taskkill /F /PID "进程PID号"
+# 根据进程名称杀死任务
+taskkill -f -t -im "进程名称"
+### 8.4、Boot工程快速启动
+
+
+
+* 基于Linux(CenterOS7)
+
+* 安装JDK,且版本不低于打包时使用的JDK版本
+
+ * 可以使用 yum 安装
+* 安装 MySQL
+
+ * 可以参考: https://blog.csdn.net/qq_42324086/article/details/120579197
+* 安装包保存在/usr/local/自定义目录中或$HOME下
+
+* 其他操作参照Windows版进行
+
+**启动成功无法访问**
+
+添加 80 端口
+
+* 添加 端口
+
+firewall-cmd --zone=public --permanent --add-port=80/tcp
+
+重启
+
+systemctl restart firewalld
+
+后台启动命令
+
+nohup java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar > server.log 2>&1 &
+
+停止服务
+
+* ps -ef | grep “java -jar”
+* kill -9 PID
+* cat server.log (查看日志)
+
+[root@cjbCentos01 app]# ps -ef | grep "java -jar"
+UID PID PPID C STIME TTY TIME CMD
+root 6848 6021 7 14:45 pts/2 00:00:19 java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar
+root 6919 6021 0 14:49 pts/2 00:00:00 grep --color=auto java -jar
+[root@cjbCentos01 app]# kill -9 6848
+[root@cjbCentos01 app]# ps -ef | grep "java -jar"
+root 7016 6021 0 14:52 pts/2 00:00:00 grep --color=auto java -jar
+[1]+ 已杀死 nohup java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar > server.log 2>&1
+[root@cjbCentos01 app]#
+### 8.6、临时属性
+
+#### 8.6.1、临时属性
+
+
+
+* 带属性数启动SpringBoot
+
+java -jar springboot_08_ssmp-0.0.1-SNAPSHOT.jar --server.port=8080
+
+* 携带多个属性启动SpringBoot,属性间使用空格分隔
+
+属性加载优先顺序
+
+1. 参看 https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config
+ 
+
+#### 8.6.2、开发环境
+
+
+
+* 带属性启动SpringBoot程序,为程序添加运行属性
+ 
+ 
+
+在启动类中 main 可以通过 System.out.println(Arrays.toString(args)); 查看配制的属性
+
+通过编程形式带参数启动SpringBoot程序,为程序添加运行参数
+
+public static void main(String[] args) {
+ String[] arg = new String[1];
+ arg[0] = "--server.port=8080";
+ SpringApplication.run(SSMPApplication.class, arg);
+}
+
+不携带参数启动SpringBoot程序
+
+public static void main(String[] args) {
+ //可以在启动boot程序时断开读取外部临时配置对应的入口,也就是去掉读取外部参数的形参
+ SpringApplication.run(SSMPApplication.class);
+}
+### 8.7、配置环境
+
+
+
+#### 8.7.1、配置文件分类
+
+* SpringBoot中4级配置文件
+
+ * 1级:file :config/application.yml 【最高】
+
+ * 2级:file :application.yml
+
+ * 3级:classpath:config/application.yml
+
+ * 4级:classpath:application.yml 【最低】
+
+* 作用:
+
+ * 1级与2级留做系统打包后设置通用属性,1级常用于运维经理进行线上整体项目部署方案调控
+
+ * 3级与4级用于系统开发阶段设置通用属性,3级常用于项目经理进行整体项目属性调控
+
+#### 8.7.2、自定义配置文件
+
+
+
+通过启动参数加载配置文件(无需书写配置文件扩展名) --spring.config.name=eban
+
+
+properties与yml文件格式均支持
+
+* 通过启动参数加载指定文件路径下的配置文件 --spring.config.location=classpath:/ebank.yml
+ 
+
+properties与yml文件格式均支持
+
+* 通过启动参数加载指定文件路径下的配置文件时可以加载多个配置,后面的会覆盖前面的
+
+--spring.config.location=classpath:/ebank.yml,classpath:/ebank-server.yml
+
+
+
+注意事项:
+多配置文件常用于将配置进行分类,进行独立管理,或将可选配置单独制作便于上线更新维护
+
+自定义配置文件——重要说明
+
+* 单服务器项目:使用自定义配置文件需求较低
+
+* 多服务器项目:使用自定义配置文件需求较高,将所有配置放置在一个目录中,统一管理
+
+* 基于SpringCloud技术,所有的服务器将不再设置配置文件,而是通过配置中心进行设定,动态加载配置信息
+
+#### 8.7.3、多环境开发(yaml)
+
+
+
+
+
+#应用环境
+#公共配制
+spring:
+ profiles:
+ active: dev
+
+#设置环境
+#开发环境
+---
+spring:
+ config:
+ activate:
+ on-profile: dev
+server:
+ port: 81
+
+#生产环境
+---
+spring:
+ profiles: pro
+server:
+ port: 80
+
+#测试环境
+---
+spring:
+ profiles: test
+server:
+ port: 82
+#### 8.7.4、多环境开发文件(yaml)
+
+
+
+多环境开发(YAML版)多配置文件格式
+主启动配置文件application.yml
+
+#应用环境
+#公共配制
+spring:
+ profiles:
+ active: test
+
+环境分类配置文件application-pro.yml
+
+server:
+ port: 81
+
+环境分类配置文件application-dev.yml
+
+server:
+ port: 82
+
+环境分类配置文件application-test.yml
+
+server:
+ port: 83
+#### 8.7.5、多环境分组管理
+
+* 根据功能对配置文件中的信息进行拆分,并制作成独立的配置文件,命名规则如下
+
+ * application-devDB.yml
+
+ * application-devRedis.yml
+
+ * application-devMVC.yml
+
+* 使用include属性在激活指定环境的情况下,同时对多个环境进行加载使其生效,多个环境间使用逗号分隔
+
+ spring:
+ profiles:
+ active: dev
+ include: devDB,devMVC
+
+ 注意事项:
+ **当主环境dev与其他环境有相同属性时,主环境属性生效;其他环境中有相同属性时,最后加载的环境属性生效**
+
+ The following profiles are active: devDB,devMVC,dev
+* 从Spring2.4版开始使用group属性替代include属性,降低了配置书写量
+
+* 使用**group**属性定义多种主环境与子环境的包含关系
+
+spring:
+ profiles:
+ active: dev
+ group:
+ "dev": devDB,devMVC
+ "pro": proDB,proMVC
+ "test": testDB,testRedis,testMVC
+
+注意事项:
+**使用group属性,会覆盖 主环境dev (active) 的内容,最后加载的环境属性生效**
+
+The following profiles are active: dev,devDB,devMVC
+#### 8.7.6、多环境开发控制
+
+
+Maven与SpringBoot多环境兼容
+①:Maven中设置多环境属性
+
+
+
+
+ dev_env
+
+ dev
+
+
+ true
+
+
+
+ pro_env
+
+ pro
+
+
+
+
+ test_env
+
+ test
+
+
+
+
+②:SpringBoot中引用Maven属性
+
+spring:
+ profiles:
+ active: @profile.active@
+ group:
+ "dev": devDB,devMVC
+ "pro": proDB,proMVC
+
+
+
+③:执行Maven打包指令,并在生成的boot打包文件.jar文件中查看对应信息
+问题:**修改pom.xml 文件后,启动没有生效 手动 compile 即可**
+
+
+或者 设置 IDEA进行自动编译
+
+
+## 9、日志
+
+### 9.1、日志基础操作
+
+日志(log)作用
+
+1. 编程期调试代码
+2. 运营期记录信息
+ * 记录日常运营重要信息(峰值流量、平均响应时长……)
+ * 记录应用报错信息(错误堆栈)
+ * 记录运维过程数据(扩容、宕机、报警……)
+
+代码中使用日志工具记录日志
+
+* 先引入 Lombok 工具类
+
+
+
+ org.projectlombok
+ lombok
+
+
+ ①:添加日志记录操作
+
+ @RestController
+ @RequestMapping("/books")
+ public class BookController {
+ private static final Logger log = LoggerFactory.getLogger(BookController.class);
+
+ @GetMapping
+ public String getById() {
+ System.out.println("springboot is running...");
+ log.debug("debug ...");
+ log.info("info ...");
+ log.warn("warn ...");
+ log.error("error ...");
+ return "springboot is running...";
+ }
+ }
+* 日志级别
+
+TRACE:运行堆栈信息,使用率低
+DEBUG:程序员调试代码使用
+INFO:记录运维过程数据
+WARN:记录运维过程报警数据
+ERROR:记录错误堆栈信息
+FATAL:灾难信息,合并计入ERRO
+
+②:设置日志输出级别
+
+# 开启 debug 模式,输出调试信息,常用于检查系统运行状况
+debug: true
+# 设置日志级别, root 表示根节点,即整体应用日志级别
+logging:
+ level:
+ root: debug
+
+③:设置日志组,控制指定包对应的日志输出级别,也可以直接控制指定包对应的日志输出级别
+
+logging:
+ # 设置分组
+ group:
+ # 自定义组名,设置当前组中所包含的包
+ ebank: com.example.controller,com.example.service,com.example.dao
+ iservice: com.alibaba
+ level:
+ root: info
+ # 设置某个包的日志级别
+# com.example.controller: debug
+ # 为对应组设置日志级别
+ ebank: warn
+### 9.2、快速创建日志对象
+
+* 使用lombok提供的注解@Slf4j简化开发,减少日志对象的声明操作
+
+ @Slf4j
+ //Rest模式
+ @RestController
+ @RequestMapping("/books")
+ public class BookController {
+
+ @GetMapping
+ public String getById(){
+ System.out.println("springboot is running...2");
+
+ log.debug("debug...");
+ log.info("info...");
+ log.warn("warn...");
+ log.error("error...");
+
+ return "springboot is running...2";
+ }
+
+ }
+
+### 9.3、日志输出格式控制
+
+
+
+* PID:进程ID,用于表明当前操作所处的进程,当多服务同时记录日志时,该值可用于协助程序员调试程序
+* 所属类/接口名:当前显示信息为SpringBoot重写后的信息,名称过长时,简化包名书写为首字母,甚至直接删除
+* 设置日志输出格式
+
+logging:
+ pattern:
+ console: "%d - %m%n"
+
+%d:日期
+%m:消息
+%n:换行
+
+
+logging:
+pattern:
+# console: "%d - %m%n"
+console: "%d %clr(%5p) --- [%16t] %clr(%-40.40c){cyan} : %m %n"
+### 9.4、文件记录日志
+
+* 设置日志文件
+
+logging:
+ file:
+ name: server.log
+
+* 日志文件详细配置
+
+logging:
+ file:
+ name: server.log
+ logback:
+ rollingpolicy:
+ max-file-size: 4KB
+ file-name-pattern: server.%d{yyyy-MM-dd}.%i.log
+
+
+
+## 10、热部署
+
+
+ org.springframework.boot
+ spring-boot-devtools
+ true
+
spring:
+ # 热部署范围配置
+ devtools:
+ restart:
+ # 设置不参与热部署的文件和文件夹(即修改后不重启)
+ exclude: static/**,public/**,config/application.yml
+ #是否可用
+ enabled: false
+
+如果配置文件比较多的时候找热部署对应配置比较麻烦,可以在`springboot`启动类的main方法中设置,此处设置的优先级将比配置文件高,一定会生效。
+
+System.setProperty("spring.devtools.restart.enabled", "false");
+## 11、属性绑定
+
+1. 先在要配置的类上面加@Component注解将该类交由spring容器管理;
+2. `@ConfigurationProperties(prefix="xxx")`,xxx跟application.yml配置文件中的属性对应;
+3. 如果多个配置类想统一管理也可以通过`@EnableConfigurationProperties({xxx.class, yyy.class})`的方式完成配置,不过该注解会与@Component配置发生冲突,二选一即可;
+4. 第三方类对象想通过配置进行属性注入,可以通过创建一个方法,在方法体上加@Bean和`@ConfigurationProperties(prefix="xxx")`注解,然后方法返回这个第三方对象的方式。
+5. 使用`@ConfigurationProperties(prefix="xxx")`注解后idea工具会报一个警告Spring Boot Configuration Annotation Processor not configured
+
+@ConfigurationProperties(prefix="xxx")
+@Data
+public class ServerConfig {
+ private String inAddress;
+ private int port;
+ private long timeout;
+}
+
+`@ConfigurationProperties`绑定属性支持属性名宽松绑定,又叫松散绑定。
+
+比如要将`ServerConfig.class`作为配置类,并通过配置文件`application.yml`绑定属性
+
+ServerConfig.class
serverConfig:
+ # ipAddress: 192.168.0.1 # 驼峰模式
+ # ipaddress: 192.168.0.1
+ # IPADDRESS: 192.168.0.1
+ ip-address: 192.168.0.1 # 主流配置方式,烤肉串模式
+ # ip_address: 192.168.0.1 # 下划线模式
+ # IP_ADDRESS: 192.168.0.1 # 常量模式
+ # ip_Add_rEss: 192.168.0.1
+ # ipaddress: 192.168.0.1
+ port: 8888
+ timeout: -1
+
+以ipAddress属性为例,上面的多种配置方式皆可生效,这就是松散绑定。而@Value不支持松散绑定,必须一一对应。
+
+`@ConfigurationProperties(prefix="serverconfig")`中的prefix的值为serverconfig或者server-config,如果是serverConfig就会报错,这与松散绑定的前缀命名规范有关:仅能使用纯小写字母、数字、中划线作为合法的字符
+
+## 12、常用计量单位应用
+
+//@Component
+@ConfigurationProperties(prefix = "server-config")
+@Data
+public class ServerConfig {
+ private String ipAddress;
+ private int port;
+ @DurationUnit(ChronoUnit.MINUTES)
+ private Duration timeout;
+
+ @DataSizeUnit(DataUnit.MEGABYTES)
+ private DataSize dataSize;
+}
+
+引入`Bean`属性校验框架的步骤:
+
+1. 在`pom.xml`中添加`JSR303`规范和`hibernate`校验框架的依赖:
+
+
+
+ javax.validation
+ validation-api
+
+
+
+ org.hibernate.validator
+ hibernate-validator
+
+
+1. 在要校验的类上加`@Validated`注解
+
+2. 设置具体的校验规则,如:`@Max(value=8888, message="最大值不能超过8888")`
+
+@ConfigurationProperties(prefix = "server-config")
+@Data
+// 2.开启对当前bean的属性注入校验
+@Validated
+public class ServerConfig {
+ private String ipAddress;
+ // 设置具体的规则
+ @Max(value = 8888, message = "最大值不能超过8888")
+ @Min(value = 1000, message = "最小值不能低于1000")
+ private int port;
+ @DurationUnit(ChronoUnit.MINUTES)
+ private Duration timeout;
+
+ @DataSizeUnit(DataUnit.MEGABYTES)
+ private DataSize dataSize;
+}
+
+进制转换中的一些问题:
+
+如`application.yml`文件中对数据库有如下配置:
+
+datasource:
+ driverClassName: com.mysql.cj.jdbc.Driver123
+ # 不加引号读取的时候默认解析为了8进制数,转成十进制就是87
+ # 所以想让这里正确识别,需要加上引号
+ # password: 0127
+ password: "0127"
+## 13、测试类
+
+### 13.1、加载专用属性
+
+@SpringBootTest注解中可以设置properties和args属性,这里的args属性的作用跟idea工具中自带的程序参数类似,只不过这里的配置是源码级别的,会随着源码的移动而跟随,而idea中的程序参数的配置会丢失。并且这里的args属性的配置的作用范围比较小,仅在当前测试类生效。
+
+application.yml
+
+test:
+ prop: testValue
// properties属性可以为当前测试用例添加临时的属性配置
+//@SpringBootTest(properties = {"test.prop=testValue1"})
+// args属性可以为当前测试用例添加临时的命令行参数
+//@SpringBootTest(args = {"--test.prop=testValue2"})
+// 优先级排序: args > properties > 配置文件
+@SpringBootTest(args = {"--test.prop=testValue2"}, properties = {"test.prop=testValue1"})
+class PropertiesAndArgsTest {
+ @Value("${test.prop}")
+ private String prop;
+ @Test
+ public void testProperties() {
+ System.out.println("prop = " + prop);
+ }
+}
+### 13.2、加载专用类
+
+某些测试类中需要用到第三方的类,而其他测试类则不需要用到,这里可以在类上加载`@Import({xxx.class, yyy.class})`
+
+@RestController
+@RequestMapping("/books")
+public class BookController {
+ /*@GetMapping("/{id}")
+ public String getById(@PathVariable int id) {
+ System.out.println("id = " + id);
+ return "getById...";
+ }*/
+
+ @GetMapping("/{id}")
+ public Book getById(@PathVariable int id) {
+ System.out.println("id = " + id);
+ Book book = new Book();
+ book.setId(5);
+ book.setName("springboot");
+ book.setType("springboot");
+ book.setDescription("springboot");
+ return book;
+ }
+}
+
+相应测试类
+
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+// 开启虚拟mvc调用
+@AutoConfigureMockMvc
+public class WebTest {
+ @Test
+ public void testRandomPort() {
+ }
+
+ @Test
+ public void testWeb(@Autowired MockMvc mvc) throws Exception {
+ // 创建虚拟请求,当前访问 /books
+ MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/5");
+ mvc.perform(builder);
+ }
+
+ @Test
+ public void testStatus(@Autowired MockMvc mvc) throws Exception {
+ MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books1/6");
+ ResultActions action = mvc.perform(builder);
+ // 设定预期值,与真实值进行比较,成功测试通过,失败测试不通过
+ // 定义本次调用的预期值
+ StatusResultMatchers srm = MockMvcResultMatchers.status();
+ // 预计本次调用成功的状态码:200
+ ResultMatcher ok = srm.isOk();
+ // 添加预计值到本次调用过程中进行匹配
+ action.andExpect(ok);
+ }
+
+ @Test
+ public void testBody(@Autowired MockMvc mvc) throws Exception {
+ MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/6");
+ ResultActions action = mvc.perform(builder);
+ // 设定预期值,与真实值进行比较,成功测试通过,失败测试不通过
+ // 定义本次调用的预期值
+ ContentResultMatchers crm = MockMvcResultMatchers.content();
+ // 预计本次调用成功的状态码:200
+ ResultMatcher rm = crm.string("getById...");
+ // 添加预计值到本次调用过程中进行匹配
+ action.andExpect(rm);
+ }
+
+ @Test
+ public void testJson(@Autowired MockMvc mvc) throws Exception {
+ MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/7");
+ ResultActions action = mvc.perform(builder);
+ // 设定预期值,与真实值进行比较,成功测试通过,失败测试不通过
+ // 定义本次调用的预期值
+ ContentResultMatchers jsonMatcher = MockMvcResultMatchers.content();
+ ResultMatcher rm = jsonMatcher.json("{\"id\":5,\"name\":\"springboot\",\"type\":\"springboot\",\"description\":\"springboot1\"}");
+ action.andExpect(rm);
+ }
+
+ @Test
+ public void testContentType(@Autowired MockMvc mvc) throws Exception {
+ MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/7");
+ ResultActions action = mvc.perform(builder);
+ // 设定预期值,与真实值进行比较,成功测试通过,失败测试不通过
+ // 定义本次调用的预期值
+ HeaderResultMatchers hrm = MockMvcResultMatchers.header();
+ ResultMatcher rm = hrm.string("Content-Type", "application/json");
+ action.andExpect(rm);
+ }
+
+ @Test
+ // 完整测试
+ public void testGetById(@Autowired MockMvc mvc) throws Exception {
+ MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/books/8");
+ ResultActions action = mvc.perform(builder);
+
+ // 1、比较状态码
+ StatusResultMatchers statusResultMatchers = MockMvcResultMatchers.status();
+ ResultMatcher statusResultMatcher = statusResultMatchers.isOk();
+ action.andExpect(statusResultMatcher);
+
+ // 2、比较返回值类型
+ HeaderResultMatchers headerResultMatchers = MockMvcResultMatchers.header();
+ ResultMatcher headerResultMatcher = headerResultMatchers.string("Content-Type", "application/json");
+ action.andExpect(headerResultMatcher);
+
+ /// 3、比较json返回值
+ ContentResultMatchers contentResultMatchers = MockMvcResultMatchers.content();
+ ResultMatcher jsonResultMatcher = contentResultMatchers.json("{\"id\":5,\"name\":\"springboot\",\"type\":\"springboot\",\"description\":\"springboot\"}");
+ action.andExpect(jsonResultMatcher);
+ }
+}
+### 13.3、业务层测试事务回滚
+
+* 为测试用例添加事务,SpringBoot会对测试用例对应的事务提交操作进行回滚
+
+ @SpringBootTest
+ @Transactional public class DaoTest {
+ @Autowired
+ private BookService bookService; }
+* l如果想在测试用例中提交事务,可以通过@Rollback注解设置
+
+ @SpringBootTest
+ @Transactional
+ @Rollback(false)
+ public class DaoTest {
+ }
+
+### 13.4、测试用例数据设定
+
+* 测试用例数据通常采用随机值进行测试,使用SpringBoot提供的随机数为其赋值
+
+testcast:
+ book:
+ id: ${random.int} # 随机整数
+ id2: ${random.int(10)} # 10以内随机数
+ type: ${random.int(10,20)} # 10到20随机数
+ uuid: ${random.uuid} # 随机uuid
+ name: ${random.value} # 随机字符串,MD5字符串,32位
+ publishTime: ${random.long} # 随机整数(long范围)
u${random.int}表示随机整数
+
+u${random.int(10)}表示10以内的随机数
+
+u${random.int(10,20)}表示10到20的随机数
+
+u其中()可以是任意字符,例如[],!!均可
+## 14、数据层解决方案
+
+### 14.1、SQL
+
+现有数据层解决方案技术选型
+
+Druid + MyBatis-Plus + MySQL
+
+* 数据源:DruidDataSource
+* 持久化技术:MyBatis-Plus / MyBatis
+* 数据库:MySQL
+
+格式一:
+
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
+ username: root
+ password: root
+
+格式二:
+
+spring:
+ datasource:
+ druid:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
+ username: root
+ password: root
+
+* SpringBoot提供了3种内嵌的数据源对象供开发者选择
+ * HikariCP
+ * Tomcat提供DataSource
+ * Commons DBCP
+
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
+ username: root
+ password: root
spring:
+ datasource:
+ druid:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
+ username: root
+ password: root
+
+* SpringBoot提供了3种内嵌的数据源对象供开发者选择
+
+ * HikariCP:默认内置数据源对象
+
+ * Tomcat提供DataSource:HikariCP不可用的情况下,且在web环境中,将使用tomcat服务器配置的数据源对象
+
+ * Commons DBCP:Hikari不可用,tomcat数据源也不可用,将使用dbcp数据源
+
+* 通用配置无法设置具体的数据源配置信息,仅提供基本的连接相关配置,如需配置,在下一级配置中设置具体设定
+
+spring:
+ datasource:
+ driver-class-name: com.mysql.cj.jdbc.Driver
+ url: jdbc:mysql://localhost:3306/ssm_db
+ username: root
+ password: root
+ hikari:
+ maximum-pool-size: 50
+
+* 内置持久化解决方案——JdbcTemplate
+
+@SpringBootTest
+class Springboot15SqlApplicationTests {
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
+ @Test
+ void testJdbc(){
+ String sql = "select * from tbl_book where id = 1";
+ List query = jdbcTemplate.query(sql, new RowMapper() {
+ @Override
+ public Book mapRow(ResultSet rs, int rowNum) throws SQLException {
+ Book temp = new Book();
+ temp.setId(rs.getInt("id"));
+ temp.setName(rs.getString("name"));
+ temp.setType(rs.getString("type"));
+ temp.setDescription(rs.getString("description"));
+ return temp;
+ }
+ });
+ System.out.println(query);
+ }
+}
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
spring:
+ jdbc:
+ template:
+ query-timeout: -1 # 查询超时时间
+ max-rows: 500 # 最大行数
+ fetch-size: -1 # 缓存行数
+
+* SpringBoot提供了3种内嵌数据库供开发者选择,提高开发测试效率
+
+ * H2
+ * HSQL
+ * Derby
+* 导入H2相关坐标
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ com.h2database
+ h2
+ runtime
+
+* 设置当前项目为web工程,并配置H2管理控制台参数
+
+server:
+ port: 80
+spring:
+ h2:
+ console:
+ path: /h2
+ enabled: true
+
+访问用户名sa,默认密码123456
+
+操作数据库(创建表)
+
+create table tbl_book (id int,name varchar,type varchar,description varchar)
#设置访问数据源
+server:
+ port: 80
+spring:
+ datasource:
+ driver-class-name: org.h2.Driver
+ url: jdbc:h2:~/test
+ username: sa
+ password: 123456
+h2:
+ console:
+ path: /h2
+ enabled: true
+
+H2数据库控制台仅用于开发阶段,线上项目请务必关闭控制台功能
+
+server:
+ port: 80
+spring:
+ h2:
+ console:
+ path: /h2
+ enabled: false
+
+SpringBoot可以根据url地址自动识别数据库种类,在保障驱动类存在的情况下,可以省略配置
+
+server:
+ port: 80
+spring:
+ datasource:
+# driver-class-name: org.h2.Driver
+ url: jdbc:h2:~/test
+ username: sa
+ password: 123456
+ h2:
+ console:
+ path: /h2
+ enabled: true
+
+
+
+### 14.2、MongoDB
+
+MongoDB是一个开源、高性能、无模式的文档型数据库。NoSQL数据库产品中的一种,是最像关系型数据库的非关系型数据库
+
+
+#### 14.2.1、MongoDB的使用
+
+* Windows版Mongo下载
+
+https://www.mongodb.com/try/download
+
+* Windows版Mongo安装
+
+解压缩后设置数据目录
+
+* Windows版Mongo启动
+
+服务端启动
+
+在bin目录下
+
+`mongod --dbpath=..\data\db`
+
+客户端启动
+
+`mongo --host=127.0.0.1 --port=27017`
+
+#### 14.2.2、MongoDB可视化客户端
+
+
+
+* 新增
+
+ `db.集合名称.insert/save/insertOne(文档)`
+
+* 修改
+
+ `db.集合名称.remove(条件)`
+
+* 删除
+
+ `db.集合名称.update(条件,{操作种类:{文档}})`
+ 
+ 
+
+#### 14.2.3、Springboot集成MongoDB
+
+导入MongoDB驱动
+
+
+ org.springframework.boot
+ spring-boot-starter-data-mongodb
+
+
+配置客户端
+
+spring:
+ data:
+ mongodb:
+ uri: mongodb://localhost/itheima
+
+客户端读写MongoDB
+
+@Test
+void testSave(@Autowired MongoTemplate mongoTemplate){
+ Book book = new Book();
+ book.setId(1);
+ book.setType("springboot");
+ book.setName("springboot");
+ book.setDescription("springboot");
+ mongoTemplate.save(book);
+}
+@Test
+void testFind(@Autowired MongoTemplate mongoTemplate){
+ List all = mongoTemplate.findAll(Book.class);
+ System.out.println(all);
+}
+### 14.3、ElasticSearch(ES)
+
+Elasticsearch是一个分布式全文搜索引擎
+
+
+
+
+
+#### 14.3.1、ES下载
+
+* Windows版ES下载
+
+[https://](https://www.elastic.co/cn/downloads/elasticsearch)[www.elastic.co/cn/downloads/elasticsearch](https://www.elastic.co/cn/downloads/elasticsearch)
+
+* Windows版ES安装与启动
+
+运行:`elasticsearch.bat`
+
+#### 14.3.2、ES索引、分词器
+
+* 创建/查询/删除索引
+
+put:`http://localhost:9200/books`
+
+get:`http://localhost:9200/books`
+
+delete:`http://localhost:9200/books`
+
+* IK分词器
+
+ 下载:https://github.com/medcl/elasticsearch-analysis-ik/releases
+
+* 创建索引并指定规则
+
+{
+ "mappings":{
+ "properties":{
+ "id":{
+ "type":"keyword"
+ },
+ "name":{
+ "type":"text", "analyzer":"ik_max_word", "copy_to":"all"
+ },
+ "type":{
+ "type":"keyword"
+ },
+ "description":{
+ "type":"text", "analyzer":"ik_max_word", "copy_to":"all"
+ },
+ "all":{
+ "type":"text", "analyzer":"ik_max_word"
+ }
+ }
+ }
+}
+#### 14.3.3、文档操作(增删改查)
+
+* 创建文档
+
+ post:`http://localhost:9200/books/_doc`(使用系统生成的id)
+
+ post:`http://localhost:9200/books/_create/1`(使用指定id)
+
+ post:`http://localhost:9200/books/_doc/1`(使用指定id,不存在创建,存在更新,版本递增)
+
+ {
+ "name":"springboot",
+ "type":"springboot",
+ "description":"springboot"
+ }
+* 查询文档
+
+ get:`http://localhost:9200/books/_doc/1`
+
+ get:`http://localhost:9200/books/_search`
+
+* 条件查询
+
+ get:`http://localhost:9200/books/_search?q=name:springboot`
+
+* 删除文档
+
+ delete:`http://localhost:9200/books/_doc/1`
+
+* 修改文档(全量修改)
+
+ put:`http://localhost:9200/books/_doc/1`
+
+ {
+ "name":"springboot",
+ "type":"springboot",
+ "description":"springboot"
+ }
+* 修改文档(部分修改)
+
+ post:`http://localhost:9200/books/_update/1`
+
+ {
+ "doc":{
+ "name":"springboot"
+ }
+ }
+
+#### 14.3.4、Springboot集成ES
+
+* 导入坐标
+
+
+ org.springframework.boot
+ spring-boot-starter-data-elasticsearch
+
+* 配置
+
+ spring:
+ elasticsearch:
+ rest:
+ uris: http://localhost:9200
+* 客户端
+
+ @SpringBootTest
+ class Springboot18EsApplicationTests {
+ @Autowired
+ private ElasticsearchRestTemplate template;
+ }
+* SpringBoot平台并没有跟随ES的更新速度进行同步更新,ES提供了High Level Client操作ES
+
+ 导入坐标
+
+
+ org.elasticsearch.client
+ elasticsearch-rest-high-level-client
+
+
+ 无需配置
+
+* 客户端
+
+ @Test
+ void test() throws IOException {
+ HttpHost host = HttpHost.create("http://localhost:9200");
+ RestClientBuilder builder = RestClient.builder(host);
+ RestHighLevelClient client = new RestHighLevelClient(builder);
+ //客户端操作
+ CreateIndexRequest request = new CreateIndexRequest("books");
+ //获取操作索引的客户端对象,调用创建索引操作
+ client.indices().create(request, RequestOptions.DEFAULT);
+ //关闭客户端
+ client.close();
+ }
+* 客户端改进
+
+ @SpringBootTest
+ class Springboot18EsApplicationTests {
+ @Autowired
+ private BookDao bookDao;
+ @Autowired
+ RestHighLevelClient client;
+ @BeforeEach
+ void setUp() {
+ this.client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://localhost:9200")));
+ }
+ @AfterEach
+ void tearDown() throws IOException {
+ this.client.close();
+ }
+ @Test
+ void test() throws IOException {
+ //客户端操作
+ CreateIndexRequest request = new CreateIndexRequest("books");
+ //获取操作索引的客户端对象,调用创建索引操作
+ client.indices().create(request, RequestOptions.DEFAULT);
+ }
+ }
+
+#### 14.3.5、索引
+
+* 创建索引
+
+ //创建索引
+ @Test
+ void testCreateIndexByIK() throws IOException {
+ HttpHost host = HttpHost.create("http://localhost:9200");
+ RestClientBuilder builder = RestClient.builder(host);
+ RestHighLevelClient client = new RestHighLevelClient(builder);
+ //客户端操作
+ CreateIndexRequest request = new CreateIndexRequest("books");
+ //设置要执行操作
+ String json = "";
+ //设置请求参数,参数类型json数据
+ request.source(json,XContentType.JSON);
+ //获取操作索引的客户端对象,调用创建索引操作
+ client.indices().create(request, RequestOptions.DEFAULT);
+ //关闭客户端
+ client.close();
+ }
String json = "{\n" +
+ " \"mappings\":{\n" +
+ " \"properties\":{\n" +
+ " \"id\":{\n" +
+ " \"type\":\"keyword\"\n" +
+ " },\n" +
+ " \"name\":{\n" +
+ " \"type\":\"text\",\n" +
+ " \"analyzer\":\"ik_max_word\",\n" +
+ " \"copy_to\":\"all\"\n" +
+ " },\n" +
+ " \"type\":{\n" +
+ " \"type\":\"keyword\"\n" +
+ " },\n" +
+ " \"description\":{\n" +
+ " \"type\":\"text\",\n" +
+ " \"analyzer\":\"ik_max_word\",\n" +
+ " \"copy_to\":\"all\"\n" +
+ " },\n" +
+ " \"all\":{\n" +
+ " \"type\":\"text\",\n" +
+ " \"analyzer\":\"ik_max_word\"\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ "}";
+* 添加文档
+
+ //添加文档
+ @Test
+ void testCreateDoc() throws IOException {
+ Book book = bookDao.selectById(1);
+ IndexRequest request = new IndexRequest("books").id(book.getId().toString());
+ String json = JSON.toJSONString(book);
+ request.source(json,XContentType.JSON);
+ client.index(request, RequestOptions.DEFAULT);
+ }
+* 批量添加文档
+
+ //批量添加文档
+ @Test
+ void testCreateDocAll() throws IOException {
+ List bookList = bookDao.selectList(null);
+ BulkRequest bulk = new BulkRequest();
+ for (Book book : bookList) {
+ IndexRequest request = new IndexRequest("books").id(book.getId().toString());
+ String json = JSON.toJSONString(book);
+ request.source(json,XContentType.JSON);
+ bulk.add(request);
+ }
+ client.bulk(bulk,RequestOptions.DEFAULT);
+ }
+* 按id查询文档
+
+ @Test
+ void testGet() throws IOException {
+ GetRequest request = new GetRequest("books","1");
+ GetResponse response = client.get(request, RequestOptions.DEFAULT);
+ String json = response.getSourceAsString();
+ System.out.println(json);
+ }
+* 按条件查询文档
+
+@Test
+void testSearch() throws IOException {
+ SearchRequest request = new SearchRequest("books");
+ SearchSourceBuilder builder = new SearchSourceBuilder();
+ builder.query(QueryBuilders.termQuery("all",“java"));
+ request.source(builder);
+ SearchResponse response = client.search(request, RequestOptions.DEFAULT);
+ SearchHits hits = response.getHits();
+ for (SearchHit hit : hits) {
+ String source = hit.getSourceAsString();
+ Book book = JSON.parseObject(source, Book.class);
+ System.out.println(book);
+ }
+}
+## 15、缓存
+
+### 15.1、缓存简介
+
+缓存是一种介于数据永久存储介质与数据应用之间的数据临时存储介质
+
+
+* 缓存是一种介于数据永久存储介质与数据应用之间的数据临时存储介质
+
+* 使用缓存可以有效的减少低速数据读取过程的次数(例如磁盘IO),提高系统性能
+
+* 缓存不仅可以用于提高永久性存储介质的数据读取效率,还可以提供临时的数据存储空间
+ 
+
+* SpringBoot提供了缓存技术,方便缓存使用
+
+### 15.2、缓存使用
+
+* 启用缓存
+
+* 设置进入缓存的数据
+
+* 设置读取缓存的数据
+
+* 导入缓存技术对应的starter
+
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
+* 启用缓存
+
+ @SpringBootApplication
+ @EnableCaching
+ public class Springboot19CacheApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(Springboot19CacheApplication.class, args);
+ }
+ }
+* 设置当前操作的结果数据进入缓存
+
+@Cacheable(value="cacheSpace",key="#id")
+public Book getById(Integer id) {
+ return bookDao.selectById(id);
+}
+### 15.3、其他缓存
+
+* SpringBoot提供的缓存技术除了提供默认的缓存方案,还可以对其他缓存技术进行整合,统一接口,方便缓存技术的开发与管理
+ * Generic
+ * JCache
+ * **Ehcache**
+ * Hazelcast
+ * Infinispan
+ * Couchbase
+ * **Redis**
+ * Caffeine
+ * Simple(默认)
+ * **memcached**
+ * jetcache(阿里)
+
+### 15.4、缓存使用案例——手机验证码
+
+* 需求
+
+ * 输入手机号获取验证码,组织文档以短信形式发送给用户(页面模拟)
+
+ * 输入手机号和验证码验证结果
+
+* 需求分析
+
+ * 提供controller,传入手机号,业务层通过手机号计算出独有的6位验证码数据,存入缓存后返回此数据
+
+ * 提供controller,传入手机号与验证码,业务层通过手机号从缓存中读取验证码与输入验证码进行比对,返回比对结果
+
+#### 15.4.1、Cache
+
+* 开启缓存
+
+ @SpringBootApplication
+ @EnableCaching
+ public class Springboot19CacheApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(Springboot19CacheApplication.class, args);
+ }
+ }
+* 业务层接口
+
+ public interface SMSCodeService {
+ /**
+ * 传入手机号获取验证码,存入缓存
+ * @param tele
+ * @return
+ */
+ String sendCodeToSMS(String tele);
+
+ /**
+ * 传入手机号与验证码,校验匹配是否成功
+ * @param smsCode
+ * @return
+ */
+ boolean checkCode(SMSCode smsCode);
+ }
+* 业务层设置获取验证码操作,并存储缓存,手机号为key,验证码为value
+
+ @Autowired
+ private CodeUtils codeUtils;
+ @CachePut(value = "smsCode",key="#tele")
+ public String sendCodeToSMS(String tele) {
+ String code = codeUtils.generator(tele);
+ return code;
+ }
+* 业务层设置校验验证码操作,校验码通过缓存读取,返回校验结果
+
+ @Autowired
+ private CodeUtils codeUtils;
+ public boolean checkCode(SMSCode smsCode) {
+ //取出内存中的验证码与传递过来的验证码比对,如果相同,返回true
+ String code = smsCode.getCode();
+ String cacheCode = codeUtils.get(smsCode.getTele());
+ return code.equals(cacheCode);
+ }
@Component
+ public class CodeUtils {
+ @Cacheable(value = "smsCode",key="#tele")
+ public String get(String tele){
+ return null;
+ }
+ }
+
+#### 15.4.2、Ehcache
+
+* 加入Ehcache坐标(缓存供应商实现)
+
+
+ net.sf.ehcache
+ ehcache
+
+* 缓存设定为使用Ehcache
+
+ spring:
+ cache:
+ type: ehcache
+ ehcache:
+ config: ehcache.xml
+* 提供ehcache配置文件ehcache.xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#### 15.4.3、Redis
+
+* 加入Redis坐标(缓存供应商实现)
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
+
+* 配置Redis服务器,缓存设定为使用Redis
+
+ spring:
+ redis:
+ host: localhost
+ port: 6379
+ cache:
+ type: redis
+* 设置Redis相关配置
+
+ spring:
+ redis:
+ host: localhost
+ port: 6379
+ cache:
+ type: redis
+ redis:
+ use-key-prefix: true # 是否使用前缀名(系统定义前缀名)
+ key-prefix: sms_ # 追加自定义前缀名
+ time-to-live: 10s # 有效时长
+ cache-null-values: false # 是否允许存储空值
+
+#### 15.4.4、memcached
+
+* 下载memcached
+
+* 地址:https://www.runoob.com/memcached/window-install-memcached.html
+
+* 安装memcached
+
+ * 使用管理员身份运行cmd指令
+
+ * 安装
+
+ `memcached.exe -d install`
+
+* 运行
+
+ * 启动服务
+
+ `memcached.exe -d start`
+
+ * 定制服务
+
+ `memcached.exe -d stop`
+
+* memcached客户端选择
+
+ * Memcached Client for Java:最早期客户端,稳定可靠,用户群广
+ * SpyMemcached:效率更高
+ * Xmemcached:并发处理更好
+* SpringBoot未提供对memcached的整合,需要使用硬编码方式实现客户端初始化管理
+
+* 加入Xmemcached坐标(缓存供应商实现)
+
+
+ com.googlecode.xmemcached
+ xmemcached
+ 2.4.7
+
+* 配置memcached服务器必要属性
+
+ memcached:
+ # memcached服务器地址
+ servers: localhost:11211
+ # 连接池的数量
+ poolSize: 10
+ # 设置默认操作超时
+ opTimeout: 3000
+* 创建读取属性配置信息类,加载配置
+
+ @Component
+ @ConfigurationProperties(prefix = "memcached")
+ @Data
+ public class XMemcachedProperties {
+ private String servers;
+ private Integer poolSize;
+ private Long opTimeout;
+ }
+* 创建客户端配置类
+
+ @Configuration
+ public class XMemcachedConfig {
+ @Autowired
+ private XMemcachedProperties xMemcachedProperties;
+ @Bean
+ public MemcachedClient getMemcachedClinet() throws IOException {
+ MemcachedClientBuilder builder = new XMemcachedClientBuilder(xMemcachedProperties.getServers());
+ MemcachedClient memcachedClient = builder.build();
+ return memcachedClient;
+ }
+ }
+* 配置memcached属性
+
+ @Service
+ public class SMSCodeServiceMemcacheImpl implements SMSCodeService {
+ @Autowired
+ private CodeUtils codeUtils;
+ @Autowired
+ private MemcachedClient memcachedClient;
+ @Override
+ public String sendCodeToSMS(String tele) {
+ String code = this.codeUtils.generator(tele);
+ //将数据加入memcache
+ try {
+ memcachedClient.set(tele,0,code); // key,timeout,value
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return code;
+ }
+ }
@Service
+ public class SMSCodeServiceMemcacheImpl implements SMSCodeService {
+ @Autowired
+ private CodeUtils codeUtils;
+ @Autowired
+ private MemcachedClient memcachedClient;
+ @Override
+ public boolean checkCode(CodeMsg codeMsg) {
+ String value = null;
+ try {
+ value = memcachedClient.get(codeMsg.getTele()).toString();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return codeMsg.getCode().equals(value);
+ }
+ }
+
+#### 15.4.5、jetcache
+
+* jetCache对SpringCache进行了封装,在原有功能基础上实现了多级缓存、缓存统计、自动刷新、异步调用、数据报表等功能
+
+* jetCache设定了本地缓存与远程缓存的多级缓存解决方案
+
+ * 本地缓存(local)
+
+ * LinkedHashMap
+ * Caffeine
+ * 远程缓存(remote)
+
+ * Redis
+ * Tair
+* 加入jetcache坐标
+
+
+ com.alicp.jetcache
+ jetcache-starter-redis
+ 2.6.2
+
+* 配置**远程**缓存必要属性
+
+ jetcache:
+ remote:
+ default:
+ type: redis
+ host: localhost
+ port: 6379
+ poolConfig:
+ maxTotal: 50
jetcache:
+ remote:
+ default:
+ type: redis
+ host: localhost
+ port: 6379
+ poolConfig:
+ maxTotal: 50
+ sms:
+ type: redis
+ host: localhost
+ port: 6379
+ poolConfig:
+ maxTotal: 50
+* 配置**本地**缓存必要属性
+
+ jetcache:
+ local:
+ default:
+ type: linkedhashmap
+ keyConvertor: fastjson
+* 配置范例
+
+ jetcache:
+ statIntervalMinutes: 15
+ areaInCacheName: false
+ local:
+ default:
+ type: linkedhashmap
+ keyConvertor: fastjson
+ limit: 100
+ remote:
+ default:
+ host: localhost
+ port: 6379
+ type: redis
+ keyConvertor: fastjson
+ valueEncoder: java
+ valueDecoder: java
+ poolConfig:
+ minIdle: 5
+ maxIdle: 20
+ maxTotal: 50
+* 配置属性说明
+ 
+
+* 开启jetcache注解支持
+
+ @SpringBootApplication
+ @EnableCreateCacheAnnotation
+ public class Springboot19CacheApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(Springboot19CacheApplication.class, args);
+ }
+ }
+* 声明缓存对象
+
+ @Service
+ public class SMSCodeServiceImpl implements SMSCodeService {
+ @Autowired
+ private CodeUtils codeUtils;
+ @CreateCache(name = "smsCache", expire = 3600)
+ private Cache jetSMSCache;
+ }
+* 操作缓存
+
+ @Service
+ public class SMSCodeServiceImpl implements SMSCodeService {
+ @Override
+ public String sendCodeToSMS(String tele) {
+ String code = this.codeUtils.generator(tele);
+ jetSMSCache.put(tele,code);
+ return code;
+ }
+ @Override
+ public boolean checkCode(CodeMsg codeMsg) {
+ String value = jetSMSCache.get(codeMsg.getTele());
+ return codeMsg.getCode().equals(value);
+ }
+ }
+* 启用方法注解
+
+ @SpringBootApplication
+ @EnableCreateCacheAnnotation
+ @EnableMethodCache(basePackages = "com.itheima")
+ public class Springboot20JetCacheApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(Springboot20JetCacheApplication.class, args);
+ }
+ }
+* 使用方法注解操作缓存
+
+ @Service
+ public class BookServiceImpl implements BookService {
+ @Autowired
+ private BookDao bookDao;
+ @Cached(name = "smsCache_", key = "#id", expire = 3600)
+ @CacheRefresh(refresh = 10,timeUnit = TimeUnit.SECONDS)
+ public Book getById(Integer id) {
+ return bookDao.selectById(id);
+ }
+ }
@Service
+ public class BookServiceImpl implements BookService {
+
+ @CacheUpdate(name = "smsCache_", key = "#book.id", value = "#book")
+ public boolean update(Book book) {
+ return bookDao.updateById(book) > 0;
+ }
+
+ @CacheInvalidate(name = "smsCache_", key = "#id")
+ public boolean delete(Integer id) {
+ return bookDao.deleteById(id) > 0;
+ }
+ }
+* 缓存对象必须保障可序列化
+
+ @Data
+ public class Book implements Serializable {
+ }
jetcache:
+ remote:
+ default:
+ type: redis
+ keyConvertor: fastjson
+ valueEncoder: java
+ valueDecoder: java
+* 查看缓存统计报告
+
+ jetcache:
+ statIntervalMinutes: 15
+
+#### 15.4.6、j2cache
+
+* j2cache是一个缓存整合框架,可以提供缓存的整合方案,使各种缓存搭配使用,自身不提供缓存功能
+
+* 基于 ehcache + redis 进行整合
+
+* 加入j2cache坐标,加入整合缓存的坐标
+
+
+ net.oschina.j2cache
+ j2cache-spring-boot2-starter
+ 2.8.0-release
+
+
+ net.oschina.j2cache
+ j2cache-core
+ 2.8.4-release
+
+
+ net.sf.ehcache
+ ehcache
+
+* 配置使用j2cache(application.yml)
+
+ j2cache:
+ config-location: j2cache.properties
+* 配置一级缓存与二级缓存以及一级缓存数据到二级缓存的发送方式(j2cache.properties)
+
+ # 配置1级缓存
+ j2cache.L1.provider_class = ehcache
+ ehcache.configXml = ehcache.xml
+
+ # 配置1级缓存数据到2级缓存的广播方式:可以使用redis提供的消息订阅模式,也可以使用jgroups多播实现
+ j2cache.broadcast = net.oschina.j2cache.cache.support.redis.SpringRedisPubSubPolicy
+
+ # 配置2级缓存
+ j2cache.L2.provider_class = net.oschina.j2cache.cache.support.redis.SpringRedisProvider
+ j2cache.L2.config_section = redis
+ redis.hosts = localhost:6379
+* 设置使用缓存
+
+ @Service
+ public class SMSCodeServiceImpl implements SMSCodeService {
+ @Autowired
+ private CodeUtils codeUtils;
+ @Autowired
+ private CacheChannel cacheChannel;
+ }
@Service
+ public class SMSCodeServiceImpl implements SMSCodeService {
+ @Override
+ public String sendCodeToSMS(String tele) {
+ String code = codeUtils.generator(tele);
+ cacheChannel.set("sms",tele,code);
+ return code;
+ }
+ @Override
+ public boolean checkCode(SMSCode smsCode) {
+ String code = cacheChannel.get("sms",smsCode.getTele()).asString();
+ return smsCode.getCode().equals(code);
+ }
+ }
+
+## 16、定时
+
+任务
+
+* 定时任务是企业级应用中的常见操作
+
+ * 年度报表
+ * 缓存统计报告
+ * … …
+* 市面上流行的定时任务技术
+
+ * Quartz
+ * Spring Task
+
+### 16.1、SpringBoot整合Quartz
+
+* 相关概念
+
+ * 工作(Job):用于定义具体执行的工作
+ * 工作明细(JobDetail):用于描述定时工作相关的信息
+ * 触发器(Trigger):用于描述触发工作的规则,通常使用cron表达式定义调度规则
+ * 调度器(Scheduler):描述了工作明细与触发器的对应关系
+* 导入SpringBoot整合quartz的坐标
+
+
+ org.springframework.boot
+ spring-boot-starter-quartz
+
+* 定义具体要执行的任务,继承QuartzJobBean
+
+ public class QuartzTaskBean extends QuartzJobBean {
+ @Override
+ protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
+ System.out.println(“quartz job run... ");
+ }
+ }
+* 定义工作明细与触发器,并绑定对应关系
+
+ @Configuration
+ public class QuartzConfig {
+ @Bean
+ public JobDetail printJobDetail(){
+ return JobBuilder.newJob(QuartzTaskBean.class).storeDurably().build();
+ }
+ @Bean
+ public Trigger printJobTrigger() {
+ CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule("0/3 * * * * ?");
+ return TriggerBuilder.newTrigger().forJob(printJobDetail())
+ .withSchedule(cronScheduleBuilder).build();
+ }
+ }
+
+### 16.2、Spring Task
+
+* 开启定时任务功能
+
+ @SpringBootApplication
+ @EnableScheduling
+ public class Springboot22TaskApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(Springboot22TaskApplication.class, args);
+ }
+ }
+* 设置定时执行的任务,并设定执行周期
+
+ @Component
+ public class ScheduledBean {
+ @Scheduled(cron = "0/5 * * * * ?")
+ public void printLog(){
+ System.out.println(Thread.currentThread().getName()+":run...");
+ }
+ }
+* 定时任务相关配置
+
+ spring:
+ task:
+ scheduling:
+ # 任务调度线程池大小 默认 1
+ pool:
+ size: 1
+ # 调度线程名称前缀 默认 scheduling-
+ thread-name-prefix: ssm_
+ shutdown:
+ # 线程池关闭时等待所有任务完成
+ await-termination: false
+ # 调度线程关闭前最大等待时间,确保最后一定关闭
+ await-termination-period: 10s
+
+### 16.3、SpringBoot整合JavaMail
+
+* SMTP(Simple Mail Transfer Protocol):简单邮件传输协议,用于**发送**电子邮件的传输协议
+
+* POP3(Post Office Protocol - Version 3):用于**接收**电子邮件的标准协议
+
+* IMAP(Internet Mail Access Protocol):互联网消息协议,是POP3的替代协议
+
+* 导入SpringBoot整合JavaMail的坐标
+
+
+ org.springframework.boot
+ spring-boot-starter-mail
+
+* 配置JavaMail
+
+ spring:
+ mail:
+ host: smtp.qq.com
+ username: *********@qq.com
+ password: *********
+
+
+
+* 开启定时任务功能
+
+ @Service
+ public class SendMailServiceImpl implements SendMailService {
+ private String from = “********@qq.com"; // 发送人
+ private String to = "********@126.com"; // 接收人
+ private String subject = "测试邮件"; // 邮件主题
+ private String text = "测试邮件正文"; // 邮件内容
+ }
+* 开启定时任务功能
+
+ @Service
+ public class SendMailServiceImpl implements SendMailService {
+ @Autowired
+ private JavaMailSender javaMailSender;
+ @Override
+ public void sendMail() {
+ SimpleMailMessage mailMessage = new SimpleMailMessage();
+ mailMessage.setFrom(from);
+ mailMessage.setTo(to);
+ mailMessage.setSubject(subject);
+ mailMessage.setText(text);
+ javaMailSender.send(mailMessage);
+ }
+ }
+* 附件与HTML文本支持
+
+ private String text = "传智教育";
+ @Override
+ public void sendMail() {
+ try {
+ MimeMessage mimeMessage = javaMailSender.createMimeMessage();
+ MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage,true);
+ mimeMessageHelper.setFrom(from);
+ mimeMessageHelper.setTo(to);
+ mimeMessageHelper.setSubject(subject);
+ mimeMessageHelper.setText(text,true);
+ File file = new File("logo.png");
+ mimeMessageHelper.addAttachment("美图.png",file);
+ javaMailSender.send(mimeMessage);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+## 17、消息
+
+
+
+* 企业级应用中广泛使用的三种异步消息传递技术
+ * JMS
+ * AMQP
+ * MQTT
+
+### 17.1、JMS
+
+* JMS(Java Message Service):一个规范,等同于JDBC规范,提供了与消息服务相关的API接口
+
+* JMS消息模型
+
+ * peer-2-peer:点对点模型,消息发送到一个队列中,队列保存消息。队列的消息只能被一个消费者消费,或超时
+ * **pub**lish-**sub**scribe:发布订阅模型,消息可以被多个消费者消费,生产者和消费者完全独立,不需要感知对方的存在
+* JMS消息种类
+
+ * TextMessage
+ * MapMessage
+ * **BytesMessage**
+ * StreamMessage
+ * ObjectMessage
+ * Message (只有消息头和属性)
+* JMS实现:ActiveMQ、Redis、HornetMQ、RabbitMQ、RocketMQ(没有完全遵守JMS规范
+
+### 17.2、AMQP
+
+* AMQP(advanced message queuing protocol):一种协议(高级消息队列协议,也是消息代理规范),规范了网络交换的数据格式,兼容JMS
+
+* 优点:具有跨平台性,服务器供应商,生产者,消费者可以使用不同的语言来实现
+
+* AMQP消息模型
+
+ * direct exchange
+ * fanout exchange
+ * topic exchange
+ * headers exchange
+ * system exchange
+* AMQP消息种类:byte[]
+
+* AMQP实现:RabbitMQ、StormMQ、RocketMQ
+
+### 17.3、MQTT
+
+* MQTT(Message Queueing Telemetry Transport)消息队列遥测传输,专为小设备设计,是物联网(IOT)生态系统中主要成分之一
+
+### 17.4、Kafka
+
+* Kafka,一种高吞吐量的分布式发布订阅消息系统,提供实时消息功能。
+
+### 17.5、消息案例
+
+* 购物订单业务
+ * 登录状态检测
+ * 生成主单
+ * 生成子单
+ * 库存检测与变更
+ * 积分变更
+ * 支付
+ * 短信通知(异步)
+ * 购物车维护
+ * 运单信息初始化
+ * 商品库存维护
+ * 会员维护
+ * …
+
+### 17.6、ActiveMQ
+
+* 下载地址:[https://activemq.apache.org/components/classic/download](https://activemq.apache.org/components/classic/download/)[/](https://activemq.apache.org/components/classic/download/)
+
+* 安装:解压缩
+
+* 启动服务
+
+ `activemq.bat`
+
+* 访问服务器
+
+ `http://127.0.0.1:8161/`
+
+ * 服务端口:61616,管理后台端口:8161
+ * 用户名&密码:**admin**
+
+#### 17.6.1、SpringBoot整合ActiveMQ
+
+* 导入SpringBoot整合ActiveMQ坐标
+
+
+ org.springframework.boot
+ spring-boot-starter-activemq
+
+* 配置ActiveMQ(采用默认配置)
+
+ spring:
+ activemq:
+ broker-url: tcp://localhost:61616
+ jms:
+ pub-sub-domain: true
+ template:
+ default-destination: itheima
+* 生产与消费消息(使用默认消息存储队列)
+
+ @Service
+ public class MessageServiceActivemqImpl implements MessageService {
+ @Autowired
+ private JmsMessagingTemplate jmsMessagingTemplate;
+ public void sendMessage(String id) {
+ System.out.println("使用Active将待发送短信的订单纳入处理队列,id:"+id);
+ jmsMessagingTemplate.convertAndSend(id);
+ }
+ public String doMessage() {
+ return jmsMessagingTemplate.receiveAndConvert(String.class);
+ }
+ }
+* 生产与消费消息(指定消息存储队列)
+
+ @Service
+ public class MessageServiceActivemqImpl implements MessageService {
+ @Autowired
+ private JmsMessagingTemplate jmsMessagingTemplate;
+
+ public void sendMessage(String id) {
+ System.out.println("使用Active将待发送短信的订单纳入处理队列,id:"+id);
+ jmsMessagingTemplate.convertAndSend("order.sm.queue.id",id);
+ }
+ public String doMessage() {
+ return jmsMessagingTemplate.receiveAndConvert("order.sm.queue.id",String.class);
+ }
+ }
+* 使用消息监听器对消息队列监听
+
+ @Component
+ public class MessageListener {
+ @JmsListener(destination = "order.sm.queue.id")
+ public void receive(String id){
+ System.out.println("已完成短信发送业务,id:"+id);
+ }
+ }
+* 流程性业务消息消费完转入下一个消息队列
+
+ @Component
+ public class MessageListener {
+ @JmsListener(destination = "order.sm.queue.id")
+ @SendTo("order.other.queue.id")
+ public String receive(String id){
+ System.out.println("已完成短信发送业务,id:"+id);
+ return "new:"+id;
+ }
+ }
+
+### 17.7、RabbitMQ
+
+* RabbitMQ基于Erlang语言编写,需要安装Erlang
+
+* Erlang
+
+ * 下载地址:[https](https://www.erlang.org/downloads)[😕/www.erlang.org/downloads](https://www.erlang.org/downloads)
+ * 安装:一键傻瓜式安装,安装完毕需要重启,需要依赖Windows组件
+ * 环境变量配置
+ * ERLANG_HOME
+ * PATH
+* 下载地址:[https://](https://rabbitmq.com/install-windows.html)[rabbitmq.com/install-windows.html](https://rabbitmq.com/install-windows.html)
+
+* 安装:一键傻瓜式安装
+
+* 启动服务
+
+ `rabbitmq-service.bat start`
+
+* 关闭服务
+
+ `rabbitmq-service.bat stop`
+
+* 查看服务状态
+
+ `rabbitmqctl status`
+
+* 服务管理可视化(插件形式)
+
+* 查看已安装的插件列表
+
+* 开启服务管理插件
+
+ `rabbitmq-plugins.bat enable rabbitmq_management`
+
+* 访问服务器
+
+ `http://localhost:15672`
+
+ * 服务端口:5672,管理后台端口:15672
+ * 用户名&密码:**guest**
+
+#### 17.7.1、SpringBoot整合RabbitMQ
+
+* 导入SpringBoot整合RabbitMQ坐标
+
+
+ org.springframework.boot
+ spring-boot-starter-amqp
+
+* 配置RabbitMQ (采用默认配置)
+
+ spring:
+ rabbitmq:
+ host: localhost
+ port: 5672
+* 定义消息队列(direct)
+
+ @Configuration
+ public class RabbitDirectConfig {
+ @Bean
+ public Queue queue(){
+ return new Queue("simple_queue");
+ }
+ }
@Configuration
+ public class RabbitDirectConfig {
+ @Bean
+ public Queue queue(){
+ // durable:是否持久化,默认false
+ // exclusive:是否当前连接专用,默认false,连接关闭后队列即被删除
+ // autoDelete:是否自动删除,当生产者或消费者不再使用此队列,自动删除
+ return new Queue("simple_queue",true,false,false);
+ }
+ }
@Configuration
+ public class RabbitDirectConfig {
+ @Bean
+ public Queue directQueue(){
+ return new Queue("direct_queue");
+ }
+ @Bean
+ public Queue directQueue2(){
+ return new Queue("direct_queue2");
+ }
+ @Bean
+ public DirectExchange directExchange(){
+ return new DirectExchange("directExchange");
+ }
+ @Bean
+ public Binding bindingDirect(){
+ return BindingBuilder.bind(directQueue()).to(directExchange()).with("direct");
+ }
+ @Bean
+ public Binding bindingDirect2(){
+ return BindingBuilder.bind(directQueue2()).to(directExchange()).with("direct2");
+ }
+ }
+* 生产与消费消息(direct)
+
+ @Service
+ public class MessageServiceRabbitmqDirectImpl implements MessageService {
+ @Autowired
+ private AmqpTemplate amqpTemplate;
+ @Override
+ public void sendMessage(String id) {
+ System.out.println("使用Rabbitmq将待发送短信的订单纳入处理队列,id:"+id);
+ amqpTemplate.convertAndSend("directExchange","direct",id);
+ }
+ }
+* 使用消息监听器对消息队列监听(direct)
+
+ @Component
+ public class RabbitMessageListener {
+ @RabbitListener(queues = "direct_queue")
+ public void receive(String id){
+ System.out.println("已完成短信发送业务,id:"+id);
+ }
+ }
+* 使用多消息监听器对消息队列监听进行消息轮循处理(direct)
+
+ @Component
+ public class RabbitMessageListener2 {
+ @RabbitListener(queues = "direct_queue")
+ public void receive(String id){
+ System.out.println("已完成短信发送业务(two),id:"+id);
+ }
+ }
+* 定义消息队列(topic)
+
+ @Configuration
+ public class RabbitTopicConfig {
+ @Bean
+ public Queue topicQueue(){ return new Queue("topic_queue"); }
+ @Bean
+ public Queue topicQueue2(){ return new Queue("topic_queue2"); }
+ @Bean
+ public TopicExchange topicExchange(){
+ return new TopicExchange("topicExchange");
+ }
+ @Bean
+ public Binding bindingTopic(){
+ return BindingBuilder.bind(topicQueue()).to(topicExchange()).with("topic.*.*");
+ }
+ @Bean
+ public Binding bindingTopic2(){
+ return BindingBuilder.bind(topicQueue2()).to(topicExchange()).with("topic.#");
+ }
+ }
+* 绑定键匹配规则
+
+ * `*`(星号): 用来表示一个单词 ,且该单词是必须出现的
+ * `#`(井号): 用来表示任意数量
+
+
+
+* 生产与消费消息(topic)
+
+ @Service
+ public class MessageServiceRabbitmqTopicmpl implements MessageService {
+ @Autowired
+ private AmqpTemplate amqpTemplate;
+ @Override
+ public void sendMessage(String id) {
+ System.out.println("使用Rabbitmq将待发送短信的订单纳入处理队列,id:"+id);
+ amqpTemplate.convertAndSend("topicExchange","topic.order.id",id);
+ }
+ }
+* 使用消息监听器对消息队列监听(topic)
+
+ @Component
+ public class RabbitTopicMessageListener {
+ @RabbitListener(queues = "topic_queue")
+ public void receive(String id){
+ System.out.println("已完成短信发送业务,id:"+id);
+ }
+ @RabbitListener(queues = "topic_queue2")
+ public void receive2(String id){
+ System.out.println("已完成短信发送业务(two),id:"+id);
+ }
+ }
+
+### 17.8、RocketMQ
+
+* 下载地址:[https://rocketmq.apache.org](https://rocketmq.apache.org/)[/](https://rocketmq.apache.org/)
+
+* 安装:解压缩
+
+ * 默认服务端口:9876
+* 环境变量配置
+
+* ROCKETMQ_HOME
+
+* PATH
+
+* NAMESRV_ADDR (建议): 127.0.0.1:9876
+
+* 命名服务器与broker
+ 
+
+* 启动命名服务器
+
+ `mqnamesrv`
+
+* 启动broker
+
+ `mqbroker`
+
+* 服务器功能测试:生产者
+
+ `tools org.apache.rocketmq.example.quickstart.Producer`
+
+* 服务器功能测试:消费者
+
+ `tools org.apache.rocketmq.example.quickstart.Consumer`
+
+#### 17.8.1、SpringBoot整合RocketMQ
+
+* 导入SpringBoot整合RocketMQ坐标
+
+
+ org.apache.rocketmq
+ rocketmq-spring-boot-starter
+ 2.2.1
+
+
+* 配置RocketMQ (采用默认配置)
+
+ rocketmq:
+ name-server: localhost:9876
+ producer:
+ group: group_rocketmq
+* 生产消息
+
+ @Service
+ public class MessageServiceRocketmqImpl implements MessageService {
+ @Autowired
+ private RocketMQTemplate rocketMQTemplate;
+ @Override
+ public void sendMessage(String id) {
+ rocketMQTemplate.convertAndSend("order_sm_id",id);
+ System.out.println("使用Rabbitmq将待发送短信的订单纳入处理队列,id:"+id);
+ }
+ }
+* 生产异步消息
+
+ @Service
+ public class MessageServiceRocketmqImpl implements MessageService {
+ @Autowired
+ private RocketMQTemplate rocketMQTemplate;
+ @Override
+ public void sendMessage(String id) {
+ SendCallback callback = new SendCallback() {
+ @Override
+ public void onSuccess(SendResult sendResult) {
+ System.out.println("消息发送成功");
+ }
+ @Override
+ public void onException(Throwable throwable) {
+ System.out.println("消息发送失败!!!!!!!!!!!");
+ }
+ };
+ System.out.println("使用Rabbitmq将待发送短信的订单纳入处理队列,id:"+id);
+ rocketMQTemplate.asyncSend("order_sm_id",id,callback);
+ }
+ }
+* 使用消息监听器对消息队列监听
+
+ @Component
+ @RocketMQMessageListener(topic="order_sm_id",consumerGroup = "group_rocketmq")
+ public class RocketmqMessageListener implements RocketMQListener {
+ @Override
+ public void onMessage(String id) {
+ System.out.println("已完成短信发送业务,id:"+id);
+ }
+ }
+
+### 17.9、Kafka
+
+* 下载地址:[https://](https://kafka.apache.org/downloads)[kafka.apache.org/downloads](https://kafka.apache.org/downloads)
+
+* windows 系统下3.0.0版本存在BUG,建议使用2.X版本
+
+* 安装:解压缩
+
+* 启动zookeeper
+
+ `zookeeper-server-start.bat ..\..\config\zookeeper.properties`
+
+ * 默认端口:2181
+* 启动kafka
+
+ `kafka-server-start.bat ..\..\config\server.properties`
+
+ * 默认端口:9092
+* 创建topic
+
+ `kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic itheima`
+
+* 查看topic
+
+ `kafka-topics.bat --zookeeper 127.0.0.1:2181 --list`
+
+* 删除topic
+
+ `kafka-topics.bat --delete --zookeeper localhost:2181 --topic itheima`
+
+* 生产者功能测试
+
+ `kafka-console-producer.bat --broker-list localhost:9092 --topic itheima`
+
+* 消费者功能测试
+
+ `kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic itheima --from-beginning`
+
+#### 17.9.1、SpringBoot整合Kafka
+
+* 导入SpringBoot整合Kafka坐标
+
+
+ org.springframework.kafka
+ spring-kafka
+
+* 配置Kafka (采用默认配置)
+
+ spring:
+ kafka:
+ bootstrap-servers: localhost:9092
+ consumer:
+ group-id: order
+* 生产消息
+
+ @Service
+ public class MessageServiceKafkaImpl implements MessageService {
+ @Autowired
+ private KafkaTemplate kafkaTemplate;
+ @Override
+ public void sendMessage(String id) {
+ System.out.println("使用Kafka将待发送短信的订单纳入处理队列,id:"+id);
+ kafkaTemplate.send("kafka_topic",id); }
+ }
+* 使用消息监听器对消息队列监听
+
+ @Component
+ public class KafkaMessageListener{
+ @KafkaListener(topics = {"kafka_topic"})
+ public void onMessage(ConsumerRecord, ?> record) {
+ System.out.println("已完成短信发送业务,id:"+record.value());
+ }
+ }
+
+## 18、监控
+
+### 18.1、简介
+
+监控的意义:
+
+* 监控服务状态是否宕机
+* 监控服务运行指标(内存、虚拟机、线程、请求等)
+* 监控日志
+* 管理服务(服务下线)
+
+监控的实施方式:
+
+* 显示监控信息的服务器:用于获取服务信息,并显示对应的信息
+* 运行的服务:启动时主动上报,告知监控服务器自己需要受到监控
+ 
+
+### 18.2、可视化监控平台
+
+* Spring Boot Admin,开源社区项目,用于管理和监控SpringBoot应用程序。 客户端注册到服务端后,通过HTTP请求方式,服务端定期从客户端获取对应的信息,并通过UI界面展示对应信息。
+
+* Admin服务端
+
+
+ 2.5.4
+
+
+
+ de.codecentric
+ spring-boot-admin-starter-server
+
+
+
+
+
+ de.codecentric
+ spring-boot-admin-dependencies
+ ${spring-boot-admin.version}
+ pom
+ import
+
+
+
+* Admin客户端
+
+
+ 2.5.4
+
+
+
+ de.codecentric
+ spring-boot-admin-starter-client
+
+
+
+
+
+ de.codecentric
+ spring-boot-admin-dependencies
+ ${spring-boot-admin.version}
+ pom
+ import
+
+
+
+* Admin服务端
+
+
+ de.codecentric
+ spring-boot-admin-starter-server
+ 2.5.4
+
+* Admin客户端
+
+
+ de.codecentric
+ spring-boot-admin-starter-client
+ 2.5.4
+
+* Admin服务端
+
+ server:
+ port: 8080
+* 设置启用Spring-Admin
+
+ @SpringBootApplication
+ @EnableAdminServer
+ public class Springboot25ActuatorServerApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(Springboot25ActuatorServerApplication.class, args);
+ }
+ }
+* Admin客户端
+
+ spring:
+ boot:
+ admin:
+ client:
+ url: http://localhost:8080
+ management:
+ endpoint:
+ health:
+ show-details: always
+ endpoints:
+ web:
+ exposure:
+ include: "*"
+
+
+
+### 18.3、监控原理
+
+* Actuator提供了SpringBoot生产就绪功能,通过端点的配置与访问,获取端点信息
+
+* 端点描述了一组监控信息,SpringBoot提供了多个内置端点,也可以根据需要自定义端点信息
+
+* 访问当前应用所有端点信息:**/actuator**
+
+* 访问端点详细信息:/actuator**/****端点名称**
+ 
+
+
+
+* Web程序专用端点
+ 
+
+* 启用指定端点
+
+ management:
+ endpoint:
+ health: # 端点名称
+ enabled: true show-details: always beans: # 端点名称 enabled: true
+* 启用所有端点
+
+ management:
+ endpoints:
+ enabled-by-default: true
+* 暴露端点功能
+
+ * 端点中包含的信息存在敏感信息,需要对外暴露端点功能时手动设定指定端点信息
+ 
+ 
+ 
+
+### 18.4、自定义监控指标
+
+* 为info端点添加自定义指标
+
+ info:
+ appName: @project.artifactId@
+ version: @project.version@
+ author: itheima
@Component
+ public class AppInfoContributor implements InfoContributor {
+ @Override
+ public void contribute(Info.Builder builder) {
+ Map infoMap = new HashMap<>();
+ infoMap.put("buildTime","2006");
+ builder.withDetail("runTime",System.currentTimeMillis())
+ .withDetail("company","传智教育");
+ builder.withDetails(infoMap);
+ }
+ }
+* 为Health端点添加自定义指标
+
+ @Component
+ public class AppHealthContributor extends AbstractHealthIndicator {
+ @Override
+ protected void doHealthCheck(Health.Builder builder) throws Exception {
+ boolean condition = true;
+ if(condition){
+ Map infoMap = new HashMap<>();
+ infoMap.put("buildTime","2006");
+ builder.withDetail("runTime",System.currentTimeMillis())
+ .withDetail("company","传智教育");
+ builder.withDetails(infoMap);
+ builder.status(Status.UP);
+ }else{
+ builder.status(Status.DOWN);
+ }
+ }
+ }
+* 为Metrics端点添加自定义指标
+
+ @Service
+ public class BookServiceImpl extends ServiceImpl implements IBookService {
+ private Counter counter;
+ public BookServiceImpl(MeterRegistry meterRegistry){
+ counter = meterRegistry.counter("用户付费操作次数:");
+ }
+ @Override
+ public boolean delete(Integer id) {
+ counter.increment();
+ return bookDao.deleteById(id) > 0;
+ }
+ }
+* 自定义端点
+
+ @Component
+ @Endpoint(id="pay")
+ public class PayEndPoint {
+ @ReadOperation
+ public Object getPay(){
+ //调用业务操作,获取支付相关信息结果,最终return出去
+ Map payMap = new HashMap();
+ payMap.put("level 1",103);
+ payMap.put("level 2",315);
+ payMap.put("level 3",666);
+ return payMap;
+ }
+ }
+
+pom
+import
+
+ - Admin客户端
+
+```xml
+
+ 2.5.4
+
+
+
+ de.codecentric
+ spring-boot-admin-starter-client
+
+
+
+
+
+ de.codecentric
+ spring-boot-admin-dependencies
+ ${spring-boot-admin.version}
+ pom
+ import
+
+
+
+
+* Admin服务端
+
+
+ de.codecentric
+ spring-boot-admin-starter-server
+ 2.5.4
+
+* Admin客户端
+
+
+ de.codecentric
+ spring-boot-admin-starter-client
+ 2.5.4
+
+* Admin服务端
+
+ server:
+ port: 8080
+* 设置启用Spring-Admin
+
+ @SpringBootApplication
+ @EnableAdminServer
+ public class Springboot25ActuatorServerApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(Springboot25ActuatorServerApplication.class, args);
+ }
+ }
+* Admin客户端
+
+ spring:
+ boot:
+ admin:
+ client:
+ url: http://localhost:8080
+ management:
+ endpoint:
+ health:
+ show-details: always
+ endpoints:
+ web:
+ exposure:
+ include: "*"
+
+ [外链图片转存中…(img-cXWfQSEx-1657811363485)]
+
+### 18.3、监控原理
+
+* Actuator提供了SpringBoot生产就绪功能,通过端点的配置与访问,获取端点信息
+
+* 端点描述了一组监控信息,SpringBoot提供了多个内置端点,也可以根据需要自定义端点信息
+
+* 访问当前应用所有端点信息:**/actuator**
+
+* 访问端点详细信息:/actuator**/****端点名称**
+
+ [外链图片转存中…(img-KSdMaD18-1657811363486)]
+
+ [外链图片转存中…(img-UlUbALwe-1657811363487)]
+
+* Web程序专用端点
+
+ [外链图片转存中…(img-maGlhCAb-1657811363487)]
+
+* 启用指定端点
+
+ management:
+ endpoint:
+ health: # 端点名称
+ enabled: true show-details: always beans: # 端点名称 enabled: true
+* 启用所有端点
+
+ management:
+ endpoints:
+ enabled-by-default: true
+* 暴露端点功能
+
+ * 端点中包含的信息存在敏感信息,需要对外暴露端点功能时手动设定指定端点信息
+
+ [外链图片转存中…(img-6UeTKYgJ-1657811363488)]
+
+[外链图片转存中…(img-bLPYJP4S-1657811363489)]
+
+[外链图片转存中…(img-RwCI70cm-1657811363490)]
+
+### 18.4、自定义监控指标
+
+* 为info端点添加自定义指标
+
+ info:
+ appName: @project.artifactId@
+ version: @project.version@
+ author: itheima
@Component
+ public class AppInfoContributor implements InfoContributor {
+ @Override
+ public void contribute(Info.Builder builder) {
+ Map infoMap = new HashMap<>();
+ infoMap.put("buildTime","2006");
+ builder.withDetail("runTime",System.currentTimeMillis())
+ .withDetail("company","传智教育");
+ builder.withDetails(infoMap);
+ }
+ }
+* 为Health端点添加自定义指标
+
+ @Component
+ public class AppHealthContributor extends AbstractHealthIndicator {
+ @Override
+ protected void doHealthCheck(Health.Builder builder) throws Exception {
+ boolean condition = true;
+ if(condition){
+ Map infoMap = new HashMap<>();
+ infoMap.put("buildTime","2006");
+ builder.withDetail("runTime",System.currentTimeMillis())
+ .withDetail("company","传智教育");
+ builder.withDetails(infoMap);
+ builder.status(Status.UP);
+ }else{
+ builder.status(Status.DOWN);
+ }
+ }
+ }
+* 为Metrics端点添加自定义指标
+
+ @Service
+ public class BookServiceImpl extends ServiceImpl implements IBookService {
+ private Counter counter;
+ public BookServiceImpl(MeterRegistry meterRegistry){
+ counter = meterRegistry.counter("用户付费操作次数:");
+ }
+ @Override
+ public boolean delete(Integer id) {
+ counter.increment();
+ return bookDao.deleteById(id) > 0;
+ }
+ }
+* 自定义端点
+
+ @Component
+ @Endpoint(id="pay")
+ public class PayEndPoint {
+ @ReadOperation
+ public Object getPay(){
+ //调用业务操作,获取支付相关信息结果,最终return出去
+ Map payMap = new HashMap();
+ payMap.put("level 1",103);
+ payMap.put("level 2",315);
+ payMap.put("level 3",666);
+ return payMap;
+ }
+ }
\ No newline at end of file
diff --git "a/docs/interview-experience/\345\220\204\345\244\247\345\205\254\345\217\270\351\235\242\347\273\217.md" "b/docs/interview-experience/\345\220\204\345\244\247\345\205\254\345\217\270\351\235\242\347\273\217.md"
index 03ebf3e..1c0fbd0 100644
--- "a/docs/interview-experience/\345\220\204\345\244\247\345\205\254\345\217\270\351\235\242\347\273\217.md"
+++ "b/docs/interview-experience/\345\220\204\345\244\247\345\205\254\345\217\270\351\235\242\347\273\217.md"
@@ -1,3 +1,49 @@
+# 京东
+
+### 京东一面
+
+1、Java中的乐观锁悲观锁
+
+https://segmentfault.com/a/1190000016611415
+
+2、单点登录
+3、集合的问题
+4、反转链表
+5、二分查找
+6、堆排序
+7、JUC
+8、Java中如何实现线程安全
+9、ArrayList和linkedList区别
+10、数组和链表区别
+11、volitale
+
+### 秋招一面
+
+1、数据库死锁及解决方案
+
+https://blog.csdn.net/cbjcry/article/details/84920174
+
+
+2、数据库分库分表后的分页查询及相关操作怎么解决
+
+https://crossoverjie.top/2019/07/24/framework-design/sharding-db-03/
+https://juejin.im/entry/6844903478171533320
+https://tech.meituan.com/2016/11/18/dianping-order-db-sharding.html
+
+
+### 秋招二面
+
+1、UML
+https://www.jianshu.com/p/28200121a33d
+2、超时确认,快速重传原理,快速重传重传几次,3次
+https://blog.csdn.net/u010710458/article/details/79968648
+https://www.cnblogs.com/postw/p/9678454.html
+3、singleThreadThreadPool相对于ThreadPoolExecutor的优势
+4、可重复读机制
+https://www.pianshen.com/article/11361872925/
+
+
+
# 阿里
- [阿里社招四面(分布式)](https://www.nowcoder.com/discuss/349542)
@@ -19,6 +65,18 @@
8、maven:如何查找重复的jar包问题
9、单点登录如何做的,如果保证安全性
+**缺点**:linux实战的不懂
+
+### 钉钉二面
+
+1、项目有什么难点:应该不管是什么项目都是数据库优化跟JVM问题排查
+2、操作系统内存交换方式
+3、tcp7层模型
+4、平时学习方法
+5、自己博客写的好的进行介绍
+
+**缺点**:重点不突出、操作系统不清楚。
+
# 腾讯
@@ -77,6 +135,36 @@
# 头条
+### 一面
+
+1、多线程调度原理
+https://zhuanlan.zhihu.com/p/58846439
+2、select、poll、epoll
+3、多线程原理与操作系统
+https://www.zhihu.com/question/25527028
+4、redis的单线程模型:单核会有多线程切换吗
+https://blog.csdn.net/qq_27185561/article/details/82900426
+5、算法、算法、算法、算法。
+6、线程池用到的数据结构
+7、最长上升子序列的个数
+8、为什么myisam快:非聚集索引,B+树,为什么用B+树和B树的区别。为什么B+树IO次数少
+9、MyISAM与InnoDB 的区别
+https://www.cnblogs.com/fwqblogs/p/6645274.html
+10、事务隔离级别:可重复读会发生幻读吗?会
+
+### 二面
+
+1、hashmap解决冲突方法
+2、hashmap扩容机制
+3、jvm的jstack作用
+4、最长不重复子串
+5、https
+
+
+
+
+
+
# 拼多多
@@ -133,6 +221,14 @@ https://www.cnblogs.com/JackPn/p/9386182.html
# 快手
+1、项目
+2、两数之和等于target
+3、http介绍
+4、输入网址的过程
+5、dubbo原理
+6、http和dubbo协议的区别
+
+**缺点**:写算法还不熟练
#### Tip:本来有很多我准备的资料的,但是都是外链,或者不合适的分享方式,所以大家去公众号回复【资料】好了。
diff --git "a/docs/interview-experience/\351\235\242\350\257\225\345\270\270\350\247\201\347\237\245\350\257\206.md" "b/docs/interview-experience/\351\235\242\350\257\225\345\270\270\350\247\201\347\237\245\350\257\206.md"
deleted file mode 100644
index d5c41a0..0000000
--- "a/docs/interview-experience/\351\235\242\350\257\225\345\270\270\350\247\201\347\237\245\350\257\206.md"
+++ /dev/null
@@ -1,90 +0,0 @@
-# 阿里
-
-linux进程结构
-linux文件系统
-linux统计文件数量
-spingioc,springaop中的cglib动态
-shell脚本
-webLogic知道不? 与tomcat区别?
-webservice
-HashMap链表转红黑树是怎么实现的
-进程通信
-散列表(哈希表)
-Java中pv操作实现时用的类
-说一下TCP/IP的状态转移
-说一下NIO
-线程通信
-写过异步的代码吗
-拜占庭算法的理解?
-红黑树么,在插入上有什么优化?
-resetful风格
-分布式rpc调度过程中要注意的问题
-hashmap并发读写死循环问题
-快排时间复杂度?最好什么情况,最坏什么情况?有什么改进方案?
-HashMap get和put源码,为什么红黑而非平衡树?
-分布式系统CAP理论,重点解释分区容错性的意义
-对虚拟内存的理解
-三个线程如何实现交替打印ABC
-线程池中LinkedBlockingQueue满了的话,线程会怎么样
-HashMap和ConcurrentHashMap哪个效率更高?为什么?
-mybatis如何进行类型转换
-myql间歇锁的实现原理
-future的底层实现异步原理
-rpc原理
-多个服务端上下线怎么感知
-降级处理hystrix了解过么
-redis的热点key问题
-一致性哈希
-ClassLoader原理和应用
-注解的原理
-
-
-
-# 腾讯
-
-
-
-# 百度
-
-
-
-# 滴滴
-
-
-
-# 头条
-
-
-
-# 拼多多
-
-
-
-# 美团
-
-
-
-# 小米
-
-
-
-# 网易
-
-
-
-# 华为
-
-
-#### Tip:本来有很多我准备的资料的,但是都是外链,或者不合适的分享方式,所以大家去公众号回复【资料】好了。
-
-
-
-现在免费送给大家,在我的公众号 **好好学java** 回复 **资料** 即可获取。
-
-有收获?希望老铁们来个三连击,给更多的人看到这篇文章
-
-1、老铁们,关注我的原创微信公众号「**好好学java**」,专注于Java、数据结构和算法、微服务、中间件等技术分享,保证你看完有所收获。
-
-
-
-2、给俺一个 **star** 呗,可以让更多的人看到这篇文章,顺便激励下我继续写作,嘻嘻。
\ No newline at end of file
diff --git "a/docs/interview-experience/\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\345\210\206\347\261\273\346\261\207\346\200\273.md" "b/docs/interview-experience/\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\345\210\206\347\261\273\346\261\207\346\200\273.md"
index 7857062..5c926ef 100644
--- "a/docs/interview-experience/\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\345\210\206\347\261\273\346\261\207\346\200\273.md"
+++ "b/docs/interview-experience/\351\235\242\350\257\225\345\270\270\350\247\201\351\227\256\351\242\230\345\210\206\347\261\273\346\261\207\346\200\273.md"
@@ -1,96 +1,398 @@
-# Java基础
-#### 反射在jvm层面的实现
+
+
+
+
+- [一 Java基础](#一-java基础)
+ - [一致性hash算法](#一致性hash算法)
+ - [sleep和wait](#sleep和wait)
+ - [强软弱虚引用](#强软弱虚引用)
+ - [Arrays.sort原理](#arrayssort原理)
+ - [创建对象的方式](#创建对象的方式)
+ - [若hashcode方法永远返回1会产生什么结果](#若hashcode方法永远返回1会产生什么结果)
+ - [解决hash冲突的三种方法](#解决hash冲突的三种方法)
+ - [为什么要重写hashCode()方法和equals()方法以及如何进行重写](#为什么要重写hashcode方法和equals方法以及如何进行重写)
+ - [动态代理](#动态代理)
+ - [sleep和wait的区别](#sleep和wait的区别)
+ - [java 地址和值传递的例子](#java-地址和值传递的例子)
+ - [Java序列化](#java序列化)
+ - [java NIO,java 多线程、线程池,java 网络编程解决并发量](#java-niojava-多线程-线程池java-网络编程解决并发量)
+ - [JDBC 连接的过程 ,手写 jdbc 连接过程](#jdbc-连接的过程-手写-jdbc-连接过程)
+ - [说出三个遇到过的程序报异常的情况](#说出三个遇到过的程序报异常的情况)
+ - [socket 是靠什么协议支持的](#socket-是靠什么协议支持的)
+ - [java io 用到什么设计模式](#java-io-用到什么设计模式)
+ - [serviable 的序列化,其中 uuid 的作用](#serviable-的序列化其中-uuid-的作用)
+ - [什么情景下会用到反射](#什么情景下会用到反射)
+ - [浅克隆与深克隆有什么区别,如何实现深克隆](#浅克隆与深克隆有什么区别如何实现深克隆)
+ - [反射能够使用私有的方法属性吗和底层原理?](#反射能够使用私有的方法属性吗和底层原理)
+ - [处理器指令优化有些什么考虑?](#处理器指令优化有些什么考虑)
+ - [object 对象的常用方法](#object-对象的常用方法)
+ - [Stack 和 ArrayList 的区别](#stack-和-arraylist-的区别)
+ - [statement 和 prestatement 的区别](#statement-和-prestatement-的区别)
+ - [手写模拟实现一个阻塞队列](#手写模拟实现一个阻塞队列)
+ - [util 包下有哪几种接口](#util-包下有哪几种接口)
+ - [很常见的 Nullpointerexception ,你是怎么排查的,怎么解决的;](#很常见的-nullpointerexception-你是怎么排查的怎么解决的)
+ - [静态内部类和非静态内部类的区别是什么?](#静态内部类和非静态内部类的区别是什么)
+ - [怎么创建静态内部类和非静态内部类?](#怎么创建静态内部类和非静态内部类)
+ - [Xml 解析方式,原理优缺点](#xml-解析方式原理优缺点)
+ - [静态变量和全局变量的区别](#静态变量和全局变量的区别)
+- [二 Java集合](#二-java集合)
+ - [hashmap的jdk1.7和jdk1.8区别](#hashmap的jdk17和jdk18区别)
+ - [concurrenthashmap的jdk1.7和jdk1.8区别](#concurrenthashmap的jdk17和jdk18区别)
+ - [HashMap 实现原理,扩容因子过大过小的缺点,扩容过程 采用什么方法能保证每个 bucket 中的数据更均匀 解决冲突的方式,还有没有其他方式(全域哈希)](#hashmap-实现原理扩容因子过大过小的缺点扩容过程-采用什么方法能保证每个-bucket-中的数据更均匀-解决冲突的方式还有没有其他方式全域哈希)
+ - [Collection 集合类中只能在 Iterator 中删除元素的原因](#collection-集合类中只能在-iterator-中删除元素的原因)
+ - [ArrayList、LinkedList、Vector](#arraylist-linkedlist-vector)
+ - [还了解除 util 其他包下的 List 吗?](#还了解除-util-其他包下的-list-吗)
+ - [CopyOnWriteArrayList](#copyonwritearraylist)
+ - [ConcurrentHashMap 和 LinkedHashMap 差异和适用情形](#concurrenthashmap-和-linkedhashmap-差异和适用情形)
+ - [ConcurrentHashMap分段锁是如何实现,ConcurrentHashmap jdk1.8 访问的时候是怎么加锁的,插入的时候是怎么加锁的 访问不加 锁插入的时候对头结点加锁](#concurrenthashmap分段锁是如何实现concurrenthashmap-jdk18-访问的时候是怎么加锁的插入的时候是怎么加锁的-访问不加-锁插入的时候对头结点加锁)
+ - [ArrayDeque 的使用场景](#arraydeque-的使用场景)
+ - [ArrayBlockingQueue 源码](#arrayblockingqueue-源码)
+ - [hashmap 和 treemap 的区别](#hashmap-和-treemap-的区别)
+ - [hashmap](#hashmap)
+ - [treemap](#treemap)
+ - [rehash 过程](#rehash-过程)
+ - [HashMap 的负载因子,为什么容量为2^n](#hashmap-的负载因子为什么容量为2n)
+ - [list,map,set 之间的区别](#listmapset-之间的区别)
+ - [什么时候会用到 HashMap](#什么时候会用到-hashmap)
+ - [常见的线程安全的集合类](#常见的线程安全的集合类)
+- [三 JVM](#三-jvm)
+ - [反射在jvm层面的实现](#反射在jvm层面的实现)
+ - [jvm的方法区存什么?](#jvm的方法区存什么)
+ - [JDK1.8 JVM方法区变成了什么,为什么这样做](#jdk18-jvm方法区变成了什么为什么这样做)
+ - [oom出现的原因](#oom出现的原因)
+ - [Class.forName和ClassLoader的区别](#classforname和classloader的区别)
+ - [java对象信息分配](#java对象信息分配)
+ - [java虚拟机ZGC详解](#java虚拟机zgc详解)
+ - [java虚拟机CMS详解](#java虚拟机cms详解)
+ - [java虚拟机G1详解](#java虚拟机g1详解)
+ - [JVM tomcat 容器启动,jvm 加载情况描述](#jvm-tomcat-容器启动jvm-加载情况描述)
+- [四 多线程并发](#四-多线程并发)
+ - [volitale使用场景](#volitale使用场景)
+ - [可重入锁,实现原理](#可重入锁实现原理)
+ - [Java 无锁原理](#java-无锁原理)
+ - [讲讲多线程,多线程的同步方法](#讲讲多线程多线程的同步方法)
+ - [synchronized原理](#synchronized原理)
+ - [reetrantlock](#reetrantlock)
+ - [java 线程安全都体现在哪些方面](#java-线程安全都体现在哪些方面)
+ - [如果维护线程安全 如果想实现一个线程安全的队列,可以怎么实现?](#如果维护线程安全-如果想实现一个线程安全的队列可以怎么实现)
+ - [Java多线程通信方式](#java多线程通信方式)
+ - [CountDownLatch、CyclicBarrier、Semaphore 用法总结](#countdownlatch-cyclicbarrier-semaphore-用法总结)
+ - [juc下的内容](#juc下的内容)
+ - [AOS等并发相关面试题](#aos等并发相关面试题)
+ - [threadlocal](#threadlocal)
+ - [java 线程池达到提交上限的具体情况 ,线程池用法,Java 多线程,线程池有哪几类,每一类的差别](#java-线程池达到提交上限的具体情况-线程池用法java-多线程线程池有哪几类每一类的差别)
+ - [要你设计的话,如何实现一个线程池](#要你设计的话如何实现一个线程池)
+ - [线程池的类型,固定大小的线程池内部是如何实现的,等待队列是用了哪一个队列实现 线程池种类和工作流程](#线程池的类型固定大小的线程池内部是如何实现的等待队列是用了哪一个队列实现-线程池种类和工作流程)
+ - [线程池使用了什么设计模式](#线程池使用了什么设计模式)
+ - [线程池使用时一般要考虑哪些问题](#线程池使用时一般要考虑哪些问题)
+ - [线程池的配置](#线程池的配置)
+ - [Excutor 以及 Connector 的配置](#excutor-以及-connector-的配置)
+- [五 Java框架(ssm)](#五-java框架ssm)
+- [hibernate](#hibernate)
+ - [Hibernate 的生成策略](#hibernate-的生成策略)
+ - [Hibernate 与 Mybatis 区别](#hibernate-与-mybatis-区别)
+ - [Mybatis原理](#mybatis原理)
+ - [mybatis执行select的过程](#mybatis执行select的过程)
+ - [mybatis有哪些executors](#mybatis有哪些executors)
+ - [mybatis插件原理](#mybatis插件原理)
+ - [mybatis二级缓存](#mybatis二级缓存)
+- [spring&springmvc](#springspringmvc)
+ - [spring中的设计模式](#spring中的设计模式)
+ - [spring中bean的作用域](#spring中bean的作用域)
+ - [BeanFactory和FactoryBean区别](#beanfactory和factorybean区别)
+ - [aspect的种类](#aspect的种类)
+ - [spring aop的实际应用](#spring-aop的实际应用)
+ - [spring实现多线程安全](#spring实现多线程安全)
+ - [spring的bean的高并发安全问题](#spring的bean的高并发安全问题)
+ - [ioc aop总结(概述性)](#ioc-aop总结概述性)
+ - [Spring 的加载流程,Spring 的源码中 Bean 的构造的流程](#spring-的加载流程spring-的源码中-bean-的构造的流程)
+ - [Spring 事务源码,IOC 源码,AOP 源码](#spring-事务源码ioc-源码aop-源码)
+ - [spring 的作用及理解 事务怎么配置](#spring-的作用及理解-事务怎么配置)
+ - [spring事务失效情况](#spring事务失效情况)
+ - [Spring 的 annotation 如何实现](#spring-的-annotation-如何实现)
+ - [SpringMVC 工作原理](#springmvc-工作原理)
+ - [了解 SpringMVC 与 Struct2 区别](#了解-springmvc-与-struct2-区别)
+ - [springMVC 和 spring 是什么关系](#springmvc-和-spring-是什么关系)
+ - [项目中 Spring 的 IOC 和 AOP 具体怎么使用的](#项目中-spring-的-ioc-和-aop-具体怎么使用的)
+ - [spring mvc 底层实现原理](#spring-mvc-底层实现原理)
+ - [动态代理的原理](#动态代理的原理)
+ - [如果使用 spring mvc,那 post 请求跟 put 请求有什么区别啊; 然后开始问 springmvc:描述从 tomcat 开始到 springmvc 返回到前端显示的整个流程,接着问 springmvc 中的 handlerMapping 的内部实现,然后又问 spring 中从载入 xml 文件到 getbean 整个流程,描述一遍](#如果使用-spring-mvc那-post-请求跟-put-请求有什么区别啊-然后开始问-springmvc描述从-tomcat-开始到-springmvc-返回到前端显示的整个流程接着问-springmvc-中的-handlermapping-的内部实现然后又问-spring-中从载入-xml-文件到-getbean-整个流程描述一遍)
+- [六 微服务(springboot等)](#六-微服务springboot等)
+ - [springboot](#springboot)
+ - [springcloud](#springcloud)
+- [七 数据结构](#七-数据结构)
+ - [二叉树相关](#二叉树相关)
+ - [红黑树](#红黑树)
+- [八 数据库](#八-数据库)
+- [MySQL](#mysql)
+ - [数据库死锁问题](#数据库死锁问题)
+ - [hash索引和B+树索引的区别](#hash索引和b树索引的区别)
+ - [可重复的原理MVCC](#可重复的原理mvcc)
+ - [count(1)、count(*)、count(列名)](#count1-count-count列名)
+ - [mysql的undo、redo、binlog的区别](#mysql的undo-redo-binlog的区别)
+ - [explain解释](#explain解释)
+ - [mysql分页查询优化](#mysql分页查询优化)
+ - [sql注入](#sql注入)
+ - [为什么用B+树](#为什么用b树)
+ - [sql执行流程](#sql执行流程)
+ - [聚集索引与非聚集索引](#聚集索引与非聚集索引)
+ - [覆盖索引](#覆盖索引)
+ - [sql总结](#sql总结)
+ - [有人建议给每张表都建一个自增主键,这样做有什么优点跟缺点](#有人建议给每张表都建一个自增主键这样做有什么优点跟缺点)
+ - [对 MySQL 的了解,和 oracle 的区别](#对-mysql-的了解和-oracle-的区别)
+ - [500万数字排序,内存只能容纳5万个,如何排序,如何优化?](#500万数字排序内存只能容纳5万个如何排序如何优化)
+ - [平时怎么写数据库的模糊查询(由字典树扯到模糊查询,前缀查询,例如“abc%”,还是索引策略的问题)](#平时怎么写数据库的模糊查询由字典树扯到模糊查询前缀查询例如abc还是索引策略的问题)
+ - [数据库里有 10000000 条用户信息,需要给每位用户发送信息(必须发送成功),要求节省内存](#数据库里有-10000000-条用户信息需要给每位用户发送信息必须发送成功要求节省内存)
+ - [项目中如何实现事务](#项目中如何实现事务)
+ - [数据库设计一般设计成第几范式](#数据库设计一般设计成第几范式)
+ - [mysql 用的什么版本 5.7 跟 5.6 有啥区别](#mysql-用的什么版本-57-跟-56-有啥区别)
+ - [提升 MySQL 安全性](#提升-mysql-安全性)
+ - [问了一个这样的表(三个字段:姓名,id,分数)要求查出平均分大于 80 的 id 然后分数降序排序,然后经过提示用聚合函数 avg。](#问了一个这样的表三个字段姓名id分数要求查出平均分大于-80-的-id-然后分数降序排序然后经过提示用聚合函数-avg)
+ - [为什么 mysql 事务能保证失败回滚](#为什么-mysql-事务能保证失败回滚)
+ - [主键索引底层的实现原理](#主键索引底层的实现原理)
+ - [经典的01索引问题?](#经典的01索引问题)
+ - [如何在长文本中快捷的筛选出你的名字?](#如何在长文本中快捷的筛选出你的名字)
+ - [多列索引及最左前缀原则和其他使用场景](#多列索引及最左前缀原则和其他使用场景)
+ - [事务隔离级别](#事务隔离级别)
+ - [索引的最左前缀原则](#索引的最左前缀原则)
+ - [数据库悲观锁怎么实现的](#数据库悲观锁怎么实现的)
+ - [建表的原则](#建表的原则)
+ - [索引的内涵和用法](#索引的内涵和用法)
+ - [给了两条 SQL 语句,让根据这两条语句建索引(个人想法:主要考虑复合索引只能匹配前缀列的特点)](#给了两条-sql-语句让根据这两条语句建索引个人想法主要考虑复合索引只能匹配前缀列的特点)
+ - [那么我们来聊一下数据库。A 和 B 两个表做等值连接(Inner join) 怎么优化](#那么我们来聊一下数据库a-和-b-两个表做等值连接inner-join-怎么优化)
+ - [数据库连接池的理解和优化](#数据库连接池的理解和优化)
+ - [Sql语句分组排序](#sql语句分组排序)
+ - [SQL语句的5个连接概念](#sql语句的5个连接概念)
+ - [数据库优化和架构(主要是主从分离和分库分表相关)](#数据库优化和架构主要是主从分离和分库分表相关)
+ - [分库分表](#分库分表)
+ - [跨库join实现](#跨库join实现)
+ - [探讨主从分离和分库分表相关](#探讨主从分离和分库分表相关)
+ - [数据库中间件](#数据库中间件)
+ - [读写分离在中间件的实现](#读写分离在中间件的实现)
+ - [限流 and 熔断](#限流-and-熔断)
+ - [行锁适用场景](#行锁适用场景)
+- [Redis](#redis)
+ - [redis为什么快?](#redis为什么快)
+ - [Redis 数据结构原理](#redis-数据结构原理)
+ - [Redis 持久化机制](#redis-持久化机制)
+ - [Redis 的一致性哈希算法](#redis-的一致性哈希算法)
+ - [redis了解多少](#redis了解多少)
+ - [redis五种数据类型,当散列类型的 value 值非常大的时候怎么进行压缩](#redis五种数据类型当散列类型的-value-值非常大的时候怎么进行压缩)
+ - [用redis怎么实现摇一摇与附近的人功能](#用redis怎么实现摇一摇与附近的人功能)
+ - [redis 主从复制过程](#redis-主从复制过程)
+ - [Redis 如何解决 key 冲突](#redis-如何解决-key-冲突)
+ - [redis 是怎么存储数据的](#redis-是怎么存储数据的)
+ - [redis 使用场景](#redis-使用场景)
+- [九 计算机网络](#九-计算机网络)
+ - [cookie 禁用怎么办](#cookie-禁用怎么办)
+ - [Netty new 实例化过程](#netty-new-实例化过程)
+ - [socket 实现过程,具体用的方法;怎么实现异步 socket.](#socket-实现过程具体用的方法怎么实现异步-socket)
+ - [浏览器的缓存机制](#浏览器的缓存机制)
+ - [http相关问题](#http相关问题)
+ - [TCP三次握手第三次握手时ACK丢失怎么办](#tcp三次握手第三次握手时ack丢失怎么办)
+ - [dns属于udp还是tcp,原因](#dns属于udp还是tcp原因)
+ - [http的幂等性](#http的幂等性)
+ - [建立连接的过程客户端跟服务端会交换什么信息(参考 TCP 报文结构)](#建立连接的过程客户端跟服务端会交换什么信息参考-tcp-报文结构)
+ - [丢包如何解决重传的消耗](#丢包如何解决重传的消耗)
+ - [traceroute 实现原理](#traceroute-实现原理)
+ - [IO多路复用](#io多路复用)
+ - [select 和 poll 区别?](#select-和-poll-区别)
+ - [在不使用 WebSocket 情况下怎么实现服务器推送的一种方法](#在不使用-websocket-情况下怎么实现服务器推送的一种方法)
+ - [可以使用客户端定时刷新请求或者和 TCP 保持心跳连接实现。](#可以使用客户端定时刷新请求或者和-tcp-保持心跳连接实现)
+ - [查看磁盘读写吞吐量?](#查看磁盘读写吞吐量)
+ - [PING 位于哪一层](#ping-位于哪一层)
+ - [网络重定向,说下流程](#网络重定向说下流程)
+ - [controller 怎么处理的请求:路由](#controller-怎么处理的请求路由)
+ - [IP 地址分为几类,每类都代表什么,私网是哪些](#ip-地址分为几类每类都代表什么私网是哪些)
+- [十 操作系统](#十-操作系统)
+ - [Java I/O 底层细节,注意是底层细节,而不是怎么用](#java-io-底层细节注意是底层细节而不是怎么用)
+ - [Java IO 模型(BIO,NIO 等) ,Tomcat 用的哪一种模型](#java-io-模型bionio-等-tomcat-用的哪一种模型)
+ - [当获取第一个获取锁之后,条件不满足需要释放锁应当怎么做?](#当获取第一个获取锁之后条件不满足需要释放锁应当怎么做)
+ - [手写一个线程安全的生产者与消费者。](#手写一个线程安全的生产者与消费者)
+ - [进程和线程调度方法](#进程和线程调度方法)
+- [linux](#linux)
+ - [linux查找命令](#linux查找命令)
+ - [项目部署常见linux命令](#项目部署常见linux命令)
+ - [进程文件里有哪些信息](#进程文件里有哪些信息)
+ - [sed 和 awk 的区别](#sed-和-awk-的区别)
+ - [linux查看进程并杀死的命令](#linux查看进程并杀死的命令)
+ - [有一个文件被锁住,如何查看锁住它的线程?](#有一个文件被锁住如何查看锁住它的线程)
+ - [如何查看一个文件第100行到150行的内容](#如何查看一个文件第100行到150行的内容)
+ - [如何查看进程消耗的资源](#如何查看进程消耗的资源)
+ - [如何查看每个进程下的线程?](#如何查看每个进程下的线程)
+ - [linux 如何查找文件](#linux-如何查找文件)
+ - [select epoll等问题](#select-epoll等问题)
+- [十一 框架其他](#十一-框架其他)
+ - [Servlet 的 Filter 用的什么设计模式](#servlet-的-filter-用的什么设计模式)
+ - [zookeeper 的常用功能,自己用它来做什么](#zookeeper-的常用功能自己用它来做什么)
+ - [redis 的操作是不是原子操作](#redis-的操作是不是原子操作)
+ - [秒杀业务场景设计](#秒杀业务场景设计)
+ - [如何设计淘宝秒杀系统(重点关注架构,比如数据一致性,数据库集群一致性哈希,缓存, 分库分表等等)](#如何设计淘宝秒杀系统重点关注架构比如数据一致性数据库集群一致性哈希缓存-分库分表等等)
+ - [对后台的优化有了解吗?比如负载均衡](#对后台的优化有了解吗比如负载均衡)
+ - [对 Restful 了解 Restful 的认识,优点,以及和 soap 的区别](#对-restful-了解-restful-的认识优点以及和-soap-的区别)
+ - [lrucache 的基本原理](#lrucache-的基本原理)
+- [十二 设计模式](#十二-设计模式)
+ - [Java常见设计模式](#java常见设计模式)
+- [十三 分布式](#十三-分布式)
+ - [dubbo中的dubbo协议和http协议有什么区别?](#dubbo中的dubbo协议和http协议有什么区别)
+ - [负载均衡](#负载均衡)
+ - [分布式锁的实现方式及优缺点](#分布式锁的实现方式及优缺点)
+ - [CAP](#cap)
+ - [如何实现分布式缓存](#如何实现分布式缓存)
+- [十四 其他](#十四-其他)
+ - [Java 8 函数式编程 回调函数](#java-8-函数式编程-回调函数)
+ - [函数式编程,面向对象之间区别](#函数式编程面向对象之间区别)
+ - [Java 8 中 stream 迭代的优势和区别?](#java-8-中-stream-迭代的优势和区别)
+ - [同步等于可见性吗?](#同步等于可见性吗)
+ - [git底层数据结构](#git底层数据结构)
+ - [安全加密](#安全加密)
+ - [web安全问题](#web安全问题)
+
+
+
+
+### 一 Java基础
+
+#### 一致性hash算法
+
+https://blog.csdn.net/qq_40551994/article/details/100991581
+
+#### sleep和wait
+
+https://blog.csdn.net/qq_20009015/article/details/89980966
+https://blog.csdn.net/lengyue309/article/details/79639245
+
+#### 强软弱虚引用
+
+https://blog.csdn.net/junjunba2689/article/details/80601729
+
+#### Arrays.sort原理
+
+https://www.cnblogs.com/baichunyu/p/11935995.html
+
+#### 创建对象的方式
+
+https://blog.csdn.net/w410589502/article/details/56489294
-https://www.jianshu.com/p/b6cb4c694951
+#### 若hashcode方法永远返回1会产生什么结果
-#### mysql语句分别会加什么锁
+https://blog.csdn.net/cnq2328/article/details/50436175
-https://blog.csdn.net/iceman1952/article/details/85504278
+#### 解决hash冲突的三种方法
-#### 若hashcode方法永远返回1会产生什么结果
+https://blog.csdn.net/qq_32595453/article/details/80660676
-https://blog.csdn.net/cnq2328/article/details/50436175
+#### 为什么要重写hashCode()方法和equals()方法以及如何进行重写
-#### jvm的方法区存什么?
+https://blog.csdn.net/xlgen157387/article/details/63683882
-https://www.jianshu.com/p/10584345b10a
+#### 动态代理
-#### Class.forName和ClassLoader的区别
+https://segmentfault.com/a/1190000011291179
-https://blog.csdn.net/qq_27093465/article/details/52262340
+#### sleep和wait的区别
-#### java对象信息分配
+https://blog.csdn.net/u012050154/article/details/50903326
-https://blog.csdn.net/u014520047/article/details/81940447
+#### java 地址和值传递的例子
-#### java虚拟机ZGC详解
+https://www.cnblogs.com/zhangyu317/p/11226105.html
-https://vimsky.com/article/4162.html
+#### Java序列化
-#### java虚拟机CMS详解
+https://juejin.im/post/5ce3cdc8e51d45777b1a3cdf
-https://juejin.im/post/5c7262a15188252f30484351
+#### java NIO,java 多线程、线程池,java 网络编程解决并发量
-#### java虚拟机G1详解
+Java Nio使用:https://blog.csdn.net/forezp/article/details/88414741
+Java Nio原理:https://www.cnblogs.com/crazymakercircle/p/10225159.html
+线程池:http://cmsblogs.com/?p=2448
+为什么nio快:https://blog.csdn.net/yaogao000/article/details/47972143
-https://zhuanlan.zhihu.com/p/59861022
+#### JDBC 连接的过程 ,手写 jdbc 连接过程
-#### 解决hash冲突的三种方法
+https://blog.csdn.net/qq_44971038/article/details/103204217
-https://blog.csdn.net/qq_32595453/article/details/80660676
+#### 说出三个遇到过的程序报异常的情况
-#### 为什么要重写hashCode()方法和equals()方法以及如何进行重写
+https://www.cnblogs.com/winnie-man/p/10471338.html
-https://blog.csdn.net/xlgen157387/article/details/63683882
+#### socket 是靠什么协议支持的
-#### 动态代理
+TCP/IP,协议。socket用于 通信,在实际应用中有im等,因此需要可靠的网络协议,UDP则是不可靠的协议,且服务端与客户端不链接,UDP用于广播,视频流等
-- https://segmentfault.com/a/1190000011291179
+#### java io 用到什么设计模式
-#### 红黑树
+装饰模式和适配器模式
-- https://zhuanlan.zhihu.com/p/31805309
+#### serviable 的序列化,其中 uuid 的作用
-#### hashmap的jdk1.7和jdk1.8区别
+相当于快递的打包和拆包,里面的东西要保持一致,不能人为的去改变他,不然就交易不成功。序列化与反序列化也是一样,而版本号的存在就是要是里面内容要是不一致,不然就报错。像一个防伪码一样。
-- https://juejin.im/post/5aa5d8d26fb9a028d2079264
+#### 什么情景下会用到反射
-- https://blog.csdn.net/qq_36520235/article/details/82417949
+注解、Spring 配置文件、动态代理、jdbc
-#### concurrenthashmap的jdk1.7和jdk1.8区别
+#### 浅克隆与深克隆有什么区别,如何实现深克隆
-- [面试题](https://juejin.im/post/5df8d7346fb9a015ff64eaf9)
+浅拷贝:仅仅克隆基本类型变量,而不克隆引用类型的变量
+深克隆:既克隆基本类型变量,也克隆引用类型变量
+1.浅克隆:只复制基本类型的数据,引用类型的数据只复制了引用的地址,引用的对象并没有复制,在新的对象中修改引用类型的数据会影响原对象中的引用。直接使用clone方法,再嵌套的还是浅克隆,因为有些引用类型不能直接克隆。
+2.深克隆:是在引用类型的类中也实现了clone,是clone的嵌套,并且在clone方法中又对没有clone方法的引用类型又做差异化复制,克隆后的对象与原对象之间完全不会影响,但是内容完全相同。
-#### Java I/O 底层细节,注意是底层细节,而不是怎么用
+#### 反射能够使用私有的方法属性吗和底层原理?
-可以从Java IO底层、JavaIO模型(阻塞、异步等)
+https://blog.51cto.com/4247649/2109128
-https://www.cnblogs.com/crazymakercircle/p/10225159.html
+#### 处理器指令优化有些什么考虑?
-#### 如何实现分布式缓存
+禁止重排序
-redis如何实现分布式缓存
-https://stor.51cto.com/art/201912/607229.htm
+#### object 对象的常用方法
-#### 浏览器的缓存机制
+#### Stack 和 ArrayList 的区别
-说明计算机网络的知识还没有记住
+#### statement 和 prestatement 的区别
-https://www.cnblogs.com/yangyangxxb/p/10218871.html
+1、Statement用于执行静态SQL语句,在执行时,必须指定一个事先准备好的SQL语句。
+2、PrepareStatement是预编译的SQL语句对象,sql语句被预编译并保存在对象中。被封装的sql语句代表某一类操作,语句中可以包含动态参数“?”,在执行时可以为“?”动态设置参数值。
+3、使用PrepareStatement对象执行sql时,sql被数据库进行解析和编译,然后被放到命令缓冲区,每当执行同一个PrepareStatement对象时,它就会被解析一次,但不会被再次编译。在缓冲区可以发现预编译的命令,并且可以重用。
+4、PrepareStatement可以减少编译次数提高数据库性能。
-#### JVM tomcat 容器启动,jvm 加载情况描述
+#### 手写模拟实现一个阻塞队列
-- tomcat请求流程:http://objcoding.com/2017/06/12/Tomcat-structure-and-processing-request-process/
+https://www.cnblogs.com/keeya/p/9713686.html
-其实就是jvm的类加载情况,非常相似
-- https://blog.csdn.net/lduzhenlin/article/details/83013143
-- https://blog.csdn.net/xlgen157387/article/details/53521928
+#### util 包下有哪几种接口
-#### 当获取第一个获取锁之后,条件不满足需要释放锁应当怎么做?
+#### 很常见的 Nullpointerexception ,你是怎么排查的,怎么解决的;
-https://www.jianshu.com/p/eb112b25b848
+#### 静态内部类和非静态内部类的区别是什么?
+
+#### 怎么创建静态内部类和非静态内部类?
+
+https://blog.csdn.net/qq_38366777/article/details/78088386
+
+#### Xml 解析方式,原理优缺点
+
+https://segmentfault.com/a/1190000013504078?utm_source=tag-newest
+
+#### 静态变量和全局变量的区别
+
+
+### 二 Java集合
+
+#### hashmap的jdk1.7和jdk1.8区别
+
+https://juejin.im/post/5aa5d8d26fb9a028d2079264
+
+https://blog.csdn.net/qq_36520235/article/details/82417949
+
+#### concurrenthashmap的jdk1.7和jdk1.8区别
+
+https://juejin.im/post/5df8d7346fb9a015ff64eaf9
#### HashMap 实现原理,扩容因子过大过小的缺点,扩容过程 采用什么方法能保证每个 bucket 中的数据更均匀 解决冲突的方式,还有没有其他方式(全域哈希)
@@ -109,49 +411,35 @@ https://www.cnblogs.com/peizhe123/p/5790252.html
更加详细的解释
https://blog.csdn.net/yanshuanche3765/article/details/78917507
-#### java 地址和值传递的例子
-
-https://www.cnblogs.com/zhangyu317/p/11226105.html
-
-#### java NIO,java 多线程、线程池,java 网络编程解决并发量
+#### ArrayList、LinkedList、Vector
-- Java Nio使用:https://blog.csdn.net/forezp/article/details/88414741
-- Java Nio原理:https://www.cnblogs.com/crazymakercircle/p/10225159.html
-- 线程池:http://cmsblogs.com/?p=2448
-- 为什么nio快:https://blog.csdn.net/yaogao000/article/details/47972143
+https://blog.csdn.net/zhangqiluGrubby/article/details/72870493
-#### 手写一个线程安全的生产者与消费者。
+##### 还了解除 util 其他包下的 List 吗?
-- https://www.cnblogs.com/jun-ma/p/11843394.html
-- https://blog.csdn.net/Virgil_K2017/article/details/89283946
+##### CopyOnWriteArrayList
+(1)CopyOnWriteArrayList使用ReentrantLock重入锁加锁,保证线程安全;
+(2)CopyOnWriteArrayList的写操作都要先拷贝一份新数组,在新数组中做修改,修改完了再用新数组替换老数组,所以空间复杂度是O(n),性能比较低下;
+(3)CopyOnWriteArrayList的读操作支持随机访问,时间复杂度为O(1);
+(4)CopyOnWriteArrayList采用读写分离的思想,读操作不加锁,写操作加锁,且写操作占用较大内存空间,所以适用于读多写少的场合;
+(5)CopyOnWriteArrayList只保证最终一致性,不保证实时一致性;
#### ConcurrentHashMap 和 LinkedHashMap 差异和适用情形
哈希表的原理:https://blog.csdn.net/yyyljw/article/details/80903391
可以以下方面进行回答
-
(1)使用的数据结构?
-
(2)添加元素、删除元素的基本逻辑?
-
(3)是否是fail-fast的?
-
(4)是否需要扩容?扩容规则?
-
(5)是否有序?是按插入顺序还是自然顺序还是访问顺序?
-
(6)是否线程安全?
-
(7)使用的锁?
-
(8)优点?缺点?
-
(9)适用的场景?
-
(10)时间复杂度?
-
(11)空间复杂度?
#### ConcurrentHashMap分段锁是如何实现,ConcurrentHashmap jdk1.8 访问的时候是怎么加锁的,插入的时候是怎么加锁的 访问不加 锁插入的时候对头结点加锁
@@ -164,18 +452,6 @@ jdk1.8;https://blog.csdn.net/weixin_42130471/article/details/89813248
2、线程不是安全的
3、可以用来实现栈
-#### JDBC 连接的过程 ,手写 jdbc 连接过程
-
-https://blog.csdn.net/qq_44971038/article/details/103204217
-
-#### 可重入锁,实现原理
-
-ReetrantLock:https://www.jianshu.com/p/f8f6ac49830e
-
-#### Java IO 模型(BIO,NIO 等) ,Tomcat 用的哪一种模型
-
-tomcat支持:https://blog.csdn.net/fd2025/article/details/80007435
-
#### ArrayBlockingQueue 源码
http://cmsblogs.com/?p=4755
@@ -185,16 +461,6 @@ http://cmsblogs.com/?p=4755
(3)入队和出队各定义了四组方法为满足不同的用途;
(4)利用重入锁和两个条件保证并发安全:lock、notEmpty、notFull
-#### 多进程和多线程的区别
-
-#### 说出三个遇到过的程序报异常的情况
-
-https://www.cnblogs.com/winnie-man/p/10471338.html
-
-#### Java 无锁原理
-
-https://blog.csdn.net/qq_39291929/article/details/81501829
-
#### hashmap 和 treemap 的区别
http://cmsblogs.com/?p=4743
@@ -223,151 +489,111 @@ http://cmsblogs.com/?p=4743
https://www.jianshu.com/p/dde9b12343c1
-#### 网络编程的 accept 和 connect
-
#### HashMap 的负载因子,为什么容量为2^n
HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现就在把数据存到哪个链表中的算法; 这个算法实际就是取模,hash%length,计算机中直接求余效率不如位移运算,源码中做了优化hash&(length-1), hash%length==hash&(length-1)的前提是length是2的n次方; 为什么这样能均匀分布减少碰撞呢?2的n次方实际就是1后面n个0,2的n次方-1 实际就是n个1; 例如长度为9时候,3&(9-1)=0 2&(9-1)=0 ,都在0上,碰撞了; 例如长度为8时候,3&(8-1)=3 2&(8-1)=2 ,不同位置上,不碰撞; 其实就是按位“与”的时候,每一位都能 &1 ,也就是和1111……1111111进行与运算
-#### try catch finally 可不可以没有 catch(try return,finally return)
-
-#### mapreduce 流程,如何保证 reduce 接受的数据没有丢失,数据如何去重,mapreduce 原理,partion 发生在什么阶段
-
-#### 直接写一个 java 程序,统计 IP 地址的次数
-
-#### 讲讲多线程,多线程的同步方法
-
-1、synchronized
-2、reetrantlock
-
#### list,map,set 之间的区别
https://blog.csdn.net/u012102104/article/details/79235938
-#### socket 是靠什么协议支持的
-
-TCP/IP,协议。socket用于 通信,在实际应用中有im等,因此需要可靠的网络协议,UDP则是不可靠的协议,且服务端与客户端不链接,UDP用于广播,视频流等
-
-#### java io 用到什么设计模式
-
-装饰模式和适配器模式
-
-#### serviable 的序列化,其中 uuid 的作用
-
-相当于快递的打包和拆包,里面的东西要保持一致,不能人为的去改变他,不然就交易不成功。序列化与反序列化也是一样,而版本号的存在就是要是里面内容要是不一致,不然就报错。像一个防伪码一样。
-
#### 什么时候会用到 HashMap
-#### 什么情景下会用到反射
+#### 常见的线程安全的集合类
-注解、Spring 配置文件、动态代理、jdbc
-#### 浅克隆与深克隆有什么区别,如何实现深克隆
+### 三 JVM
-浅拷贝:仅仅克隆基本类型变量,而不克隆引用类型的变量
-深克隆:既克隆基本类型变量,也克隆引用类型变量
+#### 反射在jvm层面的实现
-1.浅克隆:只复制基本类型的数据,引用类型的数据只复制了引用的地址,引用的对象并没有复制,在新的对象中修改引用类型的数据会影响原对象中的引用。直接使用clone方法,再嵌套的还是浅克隆,因为有些引用类型不能直接克隆。
-2.深克隆:是在引用类型的类中也实现了clone,是clone的嵌套,并且在clone方法中又对没有clone方法的引用类型又做差异化复制,克隆后的对象与原对象之间完全不会影响,但是内容完全相同。
+https://www.jianshu.com/p/b6cb4c694951
+#### jvm的方法区存什么?
-##### 常见的线程安全的集合类
+https://www.jianshu.com/p/10584345b10a
-##### Java 8 函数式编程 回调函数
+#### JDK1.8 JVM方法区变成了什么,为什么这样做
-#### 函数式编程,面向对象之间区别
+https://blog.csdn.net/u011665991/article/details/107141348/
-#### Java 8 中 stream 迭代的优势和区别?
+#### oom出现的原因
-#### 同步等于可见性吗?
+https://blog.csdn.net/iteye_9584/article/details/82583093
-保证了可见性不等于正确同步,因为还有原子性没考虑。
+#### Class.forName和ClassLoader的区别
-#### 还了解除 util 其他包下的 List 吗?
+https://blog.csdn.net/qq_27093465/article/details/52262340
-##### CopyOnWriteArrayList
+#### java对象信息分配
-(1)CopyOnWriteArrayList使用ReentrantLock重入锁加锁,保证线程安全;
-(2)CopyOnWriteArrayList的写操作都要先拷贝一份新数组,在新数组中做修改,修改完了再用新数组替换老数组,所以空间复杂度是O(n),性能比较低下;
-(3)CopyOnWriteArrayList的读操作支持随机访问,时间复杂度为O(1);
-(4)CopyOnWriteArrayList采用读写分离的思想,读操作不加锁,写操作加锁,且写操作占用较大内存空间,所以适用于读多写少的场合;
-(5)CopyOnWriteArrayList只保证最终一致性,不保证实时一致性;
+https://blog.csdn.net/u014520047/article/details/81940447
-#### 反射能够使用私有的方法属性吗和底层原理?
+#### java虚拟机ZGC详解
-https://blog.51cto.com/4247649/2109128
+https://vimsky.com/article/4162.html
-#### 处理器指令优化有些什么考虑?
+#### java虚拟机CMS详解
-禁止重排序
+https://juejin.im/post/5c7262a15188252f30484351
-#### object 对象的常用方法
+#### java虚拟机G1详解
-#### Stack 和 ArrayList 的区别
+https://zhuanlan.zhihu.com/p/59861022
-#### statement 和 prestatement 的区别
+#### JVM tomcat 容器启动,jvm 加载情况描述
-1、Statement用于执行静态SQL语句,在执行时,必须指定一个事先准备好的SQL语句。
-2、PrepareStatement是预编译的SQL语句对象,sql语句被预编译并保存在对象中。被封装的sql语句代表某一类操作,语句中可以包含动态参数“?”,在执行时可以为“?”动态设置参数值。
-3、使用PrepareStatement对象执行sql时,sql被数据库进行解析和编译,然后被放到命令缓冲区,每当执行同一个PrepareStatement对象时,它就会被解析一次,但不会被再次编译。在缓冲区可以发现预编译的命令,并且可以重用。
-4、PrepareStatement可以减少编译次数提高数据库性能。
+tomcat请求流程:http://objcoding.com/2017/06/12/Tomcat-structure-and-processing-request-process/
-#### 手写模拟实现一个阻塞队列
+其实就是jvm的类加载情况,非常相似
+https://blog.csdn.net/lduzhenlin/article/details/83013143
+https://blog.csdn.net/xlgen157387/article/details/53521928
-https://www.cnblogs.com/keeya/p/9713686.html
+### 四 多线程并发
-#### 怎么使用父类的方法
+#### volitale使用场景
-#### util 包下有哪几种接口
+https://blog.csdn.net/vking_wang/article/details/9982709
-#### cookie 禁用怎么办
+#### 可重入锁,实现原理
-https://segmentfault.com/q/1010000007715137
+ReetrantLock:https://www.jianshu.com/p/f8f6ac49830e
-- Netty new 实例化过程
+#### Java 无锁原理
-#### socket 实现过程,具体用的方法;怎么实现异步 socket.
+https://blog.csdn.net/qq_39291929/article/details/81501829
-https://blog.csdn.net/charjay_lin/article/details/81810922
+#### 讲讲多线程,多线程的同步方法
+
+#### synchronized原理
-- 很常见的 Nullpointerexception ,你是怎么排查的,怎么解决的;
+https://www.jianshu.com/p/d53bf830fa09
-#### Binder 的原理
+#### reetrantlock
#### java 线程安全都体现在哪些方面
#### 如果维护线程安全 如果想实现一个线程安全的队列,可以怎么实现?
-JUC 包里的 ArrayBlockingQueue 还有 LinkedBlockingQueue 啥的又结合源码说了一 通。
-
-#### 静态内部类和非静态内部类的区别是什么?
-
-#### 怎么创建静态内部类和非静态内部类?
+JUC 包里的 ArrayBlockingQueue 还有 LinkedBlockingQueue 啥的又结合源码说了一通。
-https://blog.csdn.net/qq_38366777/article/details/78088386
-
-#### 断点续传的原理
-
-#### Xml 解析方式,原理优缺点
+#### Java多线程通信方式
-####
-
-https://segmentfault.com/a/1190000013504078?utm_source=tag-newest
-
-#### 静态变量和全局变量的区别
+https://blog.csdn.net/u011514810/article/details/77131296
+https://blog.csdn.net/xiaokang123456kao/article/details/77331878
+#### CountDownLatch、CyclicBarrier、Semaphore 用法总结
-### Java多线程
+https://segmentfault.com/a/1190000012234469
-#### CountDownLatch、CyclicBarrier、Semaphore 用法总结
+#### juc下的内容
-- https://segmentfault.com/a/1190000012234469
+https://blog.csdn.net/sixabs/article/details/98471709
#### AOS等并发相关面试题
-- https://cloud.tencent.com/developer/article/1471770
-- https://zhuanlan.zhihu.com/p/96544118
-- https://zhuanlan.zhihu.com/p/48295486
+https://cloud.tencent.com/developer/article/1471770
+https://zhuanlan.zhihu.com/p/96544118
+https://zhuanlan.zhihu.com/p/48295486
#### threadlocal
https://juejin.im/post/5ac2eb52518825555e5e06ee
@@ -399,263 +625,216 @@ https://juejin.im/post/5d1882b1f265da1ba84aa676#heading-14
https://www.cnblogs.com/kismetv/p/7806063.html
-### spring&springmvc
-
-面试题:
-https://mp.weixin.qq.com/s/2Y5X11TycreHgO0R3agK2A
-https://mp.weixin.qq.com/s/IdjCxumDleLqdU8MgQnrLQ
-
-#### ioc aop总结(概述性)
-
-https://juejin.im/post/5b040cf66fb9a07ab7748c8b
-https://juejin.im/post/5b06bf2df265da0de2574ee1
-
-#### Spring 的加载流程,Spring 的源码中 Bean 的构造的流程
-
-spring ioc系列文章:http://cmsblogs.com/?p=2806
-- 加载流程(概述):https://www.jianshu.com/p/5fd1922ccab1
-- 循环依赖问题:https://blog.csdn.net/u010853261/article/details/77940767
-
-
-#### Spring 事务源码,IOC 源码,AOP 源码
-
-https://juejin.im/post/5c525968e51d453f5e6b744b
-
-ioc、aop系列源码:
-https://segmentfault.com/a/1190000015319623
-http://www.tianxiaobo.com/2018/05/30/Spring-IOC-%E5%AE%B9%E5%99%A8%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0%E5%AF%BC%E8%AF%BB/
+### 五 Java框架(ssm)
-#### spring 的作用及理解 事务怎么配置
-
-https://www.jianshu.com/p/e7d59ebf41a3
-
-#### Spring 的 annotation 如何实现
-https://segmentfault.com/a/1190000013258647
-
-#### SpringMVC 工作原理
-
-https://blog.csdn.net/cswhale/article/details/16941281
-
-#### 了解 SpringMVC 与 Struct2 区别
-
-https://blog.csdn.net/chenleixing/article/details/44570681
-
-#### springMVC 和 spring 是什么关系
-
-
-#### 项目中 Spring 的 IOC 和 AOP 具体怎么使用的
-
-- https://www.cnblogs.com/xdp-gacl/p/4249939.html
-- https://juejin.im/post/5b06bf2df265da0de2574ee1
-
-#### spring mvc 底层实现原理
-
-https://blog.csdn.net/weixin_42323802/article/details/84038765
-
-#### 动态代理的原理
-
-https://juejin.im/post/5a3284a75188252970793195
-
-#### 如果使用 spring mvc,那 post 请求跟 put 请求有什么区别啊; 然后开始问 springmvc:描述从 tomcat 开始到 springmvc 返回到前端显示的整个流程,接着问 springmvc 中的 handlerMapping 的内部实现,然后又问 spring 中从载入 xml 文件到 getbean 整个流程,描述一遍
-
-### springboot & springcloud
-
-#### springboot
-
-- [springboot面试题](https://mp.weixin.qq.com/s/id0Ga1OC4D3Hu6lkzc9hRg)
-- [springboot面试题2](https://mp.weixin.qq.com/s/XGIErbCx2i6Y8vBgw1gq_Q)
-
-#### springcloud
+### hibernate
-- [springcloudm面试题](https://mp.weixin.qq.com/s/CYfLA9s9zhwcIwJjMFXhQQ)
+#### Hibernate 的生成策略
+主要说了 native 、uuid
+https://blog.csdn.net/itmyhome1990/article/details/54863822
-### servlet
+#### Hibernate 与 Mybatis 区别
-#### Servlet 知道是做什么的吗?和 JSP 有什么联系?
+https://blog.csdn.net/wangpeng047/article/details/17038659
-jsp就是在html里面写java代码,servlet就是在java里面写html代码…其实jsp经过容器解释之后就是servlet.只是我们自己写代码的时候尽量能让它们各司其职,jsp更注重前端显示,servlet更注重模型和业务逻辑。不要写出万能的jsp或servlet来即可。
+#### Mybatis原理
-作者:知乎用户
-链接:https://www.zhihu.com/question/37962386/answer/74906895
+https://www.javazhiyin.com/34438.html
+#### mybatis执行select的过程
-#### JSP 的运行原理?
+https://www.jianshu.com/p/ae2bda8f9d84
+https://blog.csdn.net/qwesxd/article/details/90049863
-jsp/servlet原理:https://www.jianshu.com/p/93736c3b448b
+#### mybatis有哪些executors
-#### JSP 属于 Java 中 的吗?
+https://blog.csdn.net/weixin_42495773/article/details/106799280
+https://blog.csdn.net/weixin_34025051/article/details/92405286
-#### Servlet 是线程安全
+#### mybatis插件原理
-https://blog.csdn.net/qq_24145735/article/details/52433096
-https://www.cnblogs.com/chanshuyi/p/5052426.html
+https://www.cnblogs.com/qdhxhz/p/11390778.html
-#### servlet 是单例
-#### servlet 和 filter 的区别。
+#### mybatis二级缓存
-https://blog.csdn.net/weixin_42669555/article/details/81049423
+https://blog.csdn.net/csdnliuxin123524/article/details/78874261
-#### servlet jsp tomcat常见面试题
-https://juejin.im/post/5a75ab4b6fb9a063592ba9db
-https://blog.csdn.net/shxz130/article/details/39735373
+### spring&springmvc
+面试题:
+https://mp.weixin.qq.com/s/2Y5X11TycreHgO0R3agK2A
+https://mp.weixin.qq.com/s/IdjCxumDleLqdU8MgQnrLQ
+#### spring中的设计模式
-### hibernate
+https://juejin.im/post/5ce69379e51d455d877e0ca0
-#### Hibernate 的生成策略
+#### spring中bean的作用域
-主要说了 native 、uuid
+https://blog.csdn.net/weidaoyouwen/article/details/80503575
-https://blog.csdn.net/itmyhome1990/article/details/54863822
+#### BeanFactory和FactoryBean区别
-#### Hibernate 与 Mybatis 区别
+https://blog.csdn.net/weixin_38361347/article/details/92852611
-- https://blog.csdn.net/wangpeng047/article/details/17038659
+#### aspect的种类
-#### Mybatis原理
+https://blog.csdn.net/StubbornAccepted/article/details/70767014
-- https://www.javazhiyin.com/34438.html
+#### spring aop的实际应用
-### Redis
+https://blog.csdn.net/zzh_spring/article/details/107207025
-- Redis 数据结构
+#### spring实现多线程安全
-- Redis 持久化机制
+https://www.cnblogs.com/tiancai/p/9627109.html
-- Redis 的一致性哈希算法
+#### spring的bean的高并发安全问题
-- redis了解多少
+https://blog.csdn.net/songzehao/article/details/103365494/
-- redis五种数据类型,当散列类型的 value 值非常大的时候怎么进行压缩
+#### ioc aop总结(概述性)
-#### 用redis怎么实现摇一摇与附近的人功能
+https://juejin.im/post/5b040cf66fb9a07ab7748c8b
+https://juejin.im/post/5b06bf2df265da0de2574ee1
-https://blog.csdn.net/smartwu_sir/article/details/80254733
+#### Spring 的加载流程,Spring 的源码中 Bean 的构造的流程
-- redis 主从复制过程
+spring ioc系列文章:http://cmsblogs.com/?p=2806
+加载流程(概述):https://www.jianshu.com/p/5fd1922ccab1
+循环依赖问题:https://blog.csdn.net/u010853261/article/details/77940767
+https://blog.csdn.net/a15119273009/article/details/108007864
-- Redis 如何解决 key 冲突
-- redis 是怎么存储数据的
+#### Spring 事务源码,IOC 源码,AOP 源码
-- redis 使用场景
+https://juejin.im/post/5c525968e51d453f5e6b744b
-### 框架其他
+ioc、aop系列源码:
+https://segmentfault.com/a/1190000015319623
+http://www.tianxiaobo.com/2018/05/30/Spring-IOC-%E5%AE%B9%E5%99%A8%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0%E5%AF%BC%E8%AF%BB/
-#### Servlet 的 Filter 用的什么设计模式
+#### spring 的作用及理解 事务怎么配置
-https://www.jianshu.com/p/e4197a54828d
+https://www.jianshu.com/p/e7d59ebf41a3
-- zookeeper 的常用功能,自己用它来做什么
+#### spring事务失效情况
+https://blog.csdn.net/luo4105/article/details/79733338
-#### ibatis 是怎么实现映射的,它的映射原理是什么
+#### Spring 的 annotation 如何实现
+https://segmentfault.com/a/1190000013258647
-mybatis面试题:https://zhuanlan.zhihu.com/p/44464109
+#### SpringMVC 工作原理
-#### redis 的操作是不是原子操作
+https://blog.csdn.net/cswhale/article/details/16941281
-https://juejin.im/entry/58f9e22044d9040069d40dca
+#### 了解 SpringMVC 与 Struct2 区别
-#### 秒杀业务场景设计
+https://blog.csdn.net/chenleixing/article/details/44570681
-- WebSocket 长连接问题
+#### springMVC 和 spring 是什么关系
-- 如何设计淘宝秒杀系统(重点关注架构,比如数据一致性,数据库集群一致性哈希,缓存, 分库分表等等)
-- List 接口去实例化一个它的实现类(ArrayList)以及直接用 ArrayList 去 new 一个该类的对 象,这两种方式有什么区别
+#### 项目中 Spring 的 IOC 和 AOP 具体怎么使用的
-#### Tomcat 关注哪些参数 (tomcat调优)
+https://www.cnblogs.com/xdp-gacl/p/4249939.html
+https://juejin.im/post/5b06bf2df265da0de2574ee1
-https://juejin.im/post/5ac034f351882548fe4a4383
+#### spring mvc 底层实现原理
-https://testerhome.com/topics/16082
+https://blog.csdn.net/weixin_42323802/article/details/84038765
+#### 动态代理的原理
-- 对后台的优化有了解吗?比如负载均衡
+https://juejin.im/post/5a3284a75188252970793195
-我给面试官说了 Ngix+Tomcat 负载均 衡,异步处理(消息缓冲服务器),缓存(Redis, Memcache), NoSQL,数据库优化,存储索引优化
+#### 如果使用 spring mvc,那 post 请求跟 put 请求有什么区别啊; 然后开始问 springmvc:描述从 tomcat 开始到 springmvc 返回到前端显示的整个流程,接着问 springmvc 中的 handlerMapping 的内部实现,然后又问 spring 中从载入 xml 文件到 getbean 整个流程,描述一遍
-#### 对 Restful 了解 Restful 的认识,优点,以及和 soap 的区别
+### 六 微服务(springboot等)
-https://www.ruanyifeng.com/blog/2011/09/restful.html
+#### springboot
-- lrucache 的基本原理
+[springboot面试题](https://mp.weixin.qq.com/s/id0Ga1OC4D3Hu6lkzc9hRg)
+[springboot面试题2](https://mp.weixin.qq.com/s/XGIErbCx2i6Y8vBgw1gq_Q)
+#### springcloud
-### 设计模式
+[springcloud面试题](https://mp.weixin.qq.com/s/CYfLA9s9zhwcIwJjMFXhQQ)
-#### Java常见设计模式
-- https://www.jianshu.com/p/61b67ca754a3
+### 七 数据结构
-- 单例模式(双检锁模式)、简单工厂、观察者模式、适配器模式、职责链模式等等
+#### 二叉树相关
-- 享元模式模式 选两个画下 UML 图
+https://www.jianshu.com/p/655d83f9ba7b
+https://www.jianshu.com/p/ff4b93b088eb
-- 手写单例
+#### 红黑树
-写的是静态内部类的单例,然后他问我这个地方为什么用 private,这儿为啥用 static, 这就考察你的基本功啦
+https://www.jianshu.com/p/e136ec79235c
+https://zhuanlan.zhihu.com/p/31805309
-- 静态类与单例模式的区别
-- 单例模式 double check 单例模式都有什么,都是否线程安全,怎么改进(从 synchronized 到 双重检验锁 到 枚举 Enum)
-- 基本的设计模式及其核心思想
+### 八 数据库
-- 来,我们写一个单例模式的实现
+### MySQL
-这里有一个深坑,详情请见《 JVM 》 第 370 页
+#### 数据库死锁问题
+https://blog.csdn.net/cbjcry/article/details/84920174
-- 基本的设计原则
+#### hash索引和B+树索引的区别
-如果有人问你接口里的属性为什么都是 final static 的,记得和他聊一聊设计原则。
+https://www.cnblogs.com/heiming/p/5865101.html
+#### 可重复的原理MVCC
-### 数据库
+https://www.cnblogs.com/wade-luffy/p/8686883.html
+https://blog.csdn.net/weixin_42041027/article/details/100587435
+https://www.jianshu.com/p/8845ddca3b23
#### count(1)、count(*)、count(列名)
-- https://blog.csdn.net/iFuMI/article/details/77920767
+https://blog.csdn.net/iFuMI/article/details/77920767
#### mysql的undo、redo、binlog的区别
-- https://mp.weixin.qq.com/s/0z6GmUp0Lb1hDUo0EyYiUg
+https://mp.weixin.qq.com/s/0z6GmUp0Lb1hDUo0EyYiUg
#### explain解释
-- https://segmentfault.com/a/1190000010293791
+https://segmentfault.com/a/1190000010293791
#### mysql分页查询优化
-- https://blog.csdn.net/hanchao5272/article/details/102790490
+https://blog.csdn.net/hanchao5272/article/details/102790490
#### sql注入
-- https://blog.csdn.net/github_36032947/article/details/78442189
+https://blog.csdn.net/github_36032947/article/details/78442189
#### 为什么用B+树
-- https://blog.csdn.net/xlgen157387/article/details/79450295
+https://blog.csdn.net/xlgen157387/article/details/79450295
#### sql执行流程
-- https://juejin.im/post/5b7036de6fb9a009c40997eb
+https://juejin.im/post/5b7036de6fb9a009c40997eb
#### 聚集索引与非聚集索引
-- https://juejin.im/post/5cdd701ee51d453a36384939
+https://juejin.im/post/5cdd701ee51d453a36384939
#### 覆盖索引
-- https://www.jianshu.com/p/77eaad62f974
+https://www.jianshu.com/p/77eaad62f974
#### sql总结
@@ -669,62 +848,55 @@ https://blog.csdn.net/yixuandong9010/article/details/72286029
https://juejin.im/post/5cbdbb455188250ab224802d
-
#### 500万数字排序,内存只能容纳5万个,如何排序,如何优化?
参考文章:https://juejin.im/entry/5a27cb796fb9a045104a5e8c
-- 平时怎么写数据库的模糊查询(由字典树扯到模糊查询,前缀查询,例如“abc%”,还是索引策略的问题)
+#### 平时怎么写数据库的模糊查询(由字典树扯到模糊查询,前缀查询,例如“abc%”,还是索引策略的问题)
-- 数据库里有 10000000 条用户信息,需要给每位用户发送信息(必须发送成功),要求节省内存
+#### 数据库里有 10000000 条用户信息,需要给每位用户发送信息(必须发送成功),要求节省内存
-- 项目中如何实现事务
+#### 项目中如何实现事务
#### 数据库设计一般设计成第几范式
https://blog.csdn.net/hsd2012/article/details/51018631
-- mysql 用的什么版本 5.7 跟 5.6 有啥区别
+#### mysql 用的什么版本 5.7 跟 5.6 有啥区别
#### 提升 MySQL 安全性
https://blog.csdn.net/listen_for/article/details/53907270
-- 问了一个这样的表(三个字段:姓名,id,分数)要求查出平均分大于 80 的 id 然后分数降序排序,然后经过提示用聚合函数 avg。
+#### 问了一个这样的表(三个字段:姓名,id,分数)要求查出平均分大于 80 的 id 然后分数降序排序,然后经过提示用聚合函数 avg。
select id from table group by id having avg(score) > 80 order by avg(score) desc。
-- 为什么 mysql 事务能保证失败回滚
-
-
-#### 一道算法题,在一个整形数组中,找出第三大的数,注意时间效率 (使用堆)
-
+#### 为什么 mysql 事务能保证失败回滚
-
-- 主键索引底层的实现原理
+#### 主键索引底层的实现原理
B+树
-- 经典的01索引问题?
+#### 经典的01索引问题?
-- 如何在长文本中快捷的筛选出你的名字?
+#### 如何在长文本中快捷的筛选出你的名字?
全文索引
-- 多列索引及最左前缀原则和其他使用场景
+#### 多列索引及最左前缀原则和其他使用场景
-- 事务隔离级别
+#### 事务隔离级别
-- 索引的最左前缀原则
+#### 索引的最左前缀原则
-- 数据库悲观锁怎么实现的
+#### 数据库悲观锁怎么实现的
https://www.jianshu.com/p/f5ff017db62a
-- 建表的原则
-
-- 索引的内涵和用法
+#### 建表的原则
+#### 索引的内涵和用法
-- 给了两条 SQL 语句,让根据这两条语句建索引(个人想法:主要考虑复合索引只能匹配前缀列的特点)
+#### 给了两条 SQL 语句,让根据这两条语句建索引(个人想法:主要考虑复合索引只能匹配前缀列的特点)
#### 那么我们来聊一下数据库。A 和 B 两个表做等值连接(Inner join) 怎么优化
@@ -735,29 +907,80 @@ https://blog.csdn.net/hguisu/article/details/5731880
#### 数据库连接池的理解和优化
-- Sql 语句 分组排序
+#### Sql语句分组排序
-- SQL 语句的 5 个连接概念
+#### SQL语句的5个连接概念
-- 数据库优化和架构(主要是主从分离和分库分表相关)
+#### 数据库优化和架构(主要是主从分离和分库分表相关)
-分库分表
+#### 分库分表
-- 跨库join实现
+#### 跨库join实现
-- 探讨主从分离和分库分表相关
+#### 探讨主从分离和分库分表相关
-- 数据库中间件
+#### 数据库中间件
-- 读写分离在中间件的实现
+#### 读写分离在中间件的实现
-- 限流 and 熔断
+#### 限流 and 熔断
#### 行锁适用场景
https://cloud.tencent.com/developer/article/1104098
-### 计算机网络
+
+### Redis
+
+#### redis为什么快?
+
+https://zhuanlan.zhihu.com/p/57089960
+
+#### Redis 数据结构原理
+
+https://blog.csdn.net/jackFXX/article/details/82318080
+
+#### Redis 持久化机制
+
+#### Redis 的一致性哈希算法
+
+#### redis了解多少
+
+#### redis五种数据类型,当散列类型的 value 值非常大的时候怎么进行压缩
+
+#### 用redis怎么实现摇一摇与附近的人功能
+
+https://blog.csdn.net/smartwu_sir/article/details/80254733
+
+#### redis 主从复制过程
+
+#### Redis 如何解决 key 冲突
+
+#### redis 是怎么存储数据的
+
+#### redis 使用场景
+
+### 九 计算机网络
+
+#### cookie 禁用怎么办
+
+https://segmentfault.com/q/1010000007715137
+
+#### Netty new 实例化过程
+
+#### socket 实现过程,具体用的方法;怎么实现异步 socket.
+
+https://blog.csdn.net/charjay_lin/article/details/81810922
+
+#### 浏览器的缓存机制
+
+说明计算机网络的知识还没有记住
+
+https://www.cnblogs.com/yangyangxxb/p/10218871.html
+
+#### http相关问题
+
+https://mp.weixin.qq.com/s/xSGD3rWdboeeQvaS8jEchw
#### TCP三次握手第三次握手时ACK丢失怎么办
@@ -765,13 +988,13 @@ https://www.cnblogs.com/wuyepeng/p/9801470.html
#### dns属于udp还是tcp,原因
-- https://www.zhihu.com/question/310145373
+https://www.zhihu.com/question/310145373
#### http的幂等性
-- https://www.cnblogs.com/weidagang2046/archive/2011/06/04/idempotence.html
+https://www.cnblogs.com/weidagang2046/archive/2011/06/04/idempotence.html
-- 建立连接的过程客户端跟服务端会交换什么信息(参考 TCP 报文结构)
+#### 建立连接的过程客户端跟服务端会交换什么信息(参考 TCP 报文结构)
#### 丢包如何解决重传的消耗
@@ -783,7 +1006,7 @@ https://zhuanlan.zhihu.com/p/36811672
#### IO多路复用
-- https://sanyuesha.com/python-server-tutorial/book/ch05.html
+https://sanyuesha.com/python-server-tutorial/book/ch05.html
#### select 和 poll 区别?
@@ -793,7 +1016,7 @@ https://zhuanlan.zhihu.com/p/36811672
服务器推送:https://juejin.im/post/5c20e5766fb9a049b13e387b
-可以使用客户端定时刷新请求或者和 TCP 保持心跳连接实现。
+#### 可以使用客户端定时刷新请求或者和 TCP 保持心跳连接实现。
#### 查看磁盘读写吞吐量?
@@ -807,27 +1030,51 @@ https://www.cnblogs.com/ggjucheng/archive/2013/01/13/2858810.html
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Redirections
-- controller 怎么处理的请求:路由
+#### controller 怎么处理的请求:路由
#### IP 地址分为几类,每类都代表什么,私网是哪些
https://zhuanlan.zhihu.com/p/54593244
+
+### 十 操作系统
+
+#### Java I/O 底层细节,注意是底层细节,而不是怎么用
+
+可以从Java IO底层、JavaIO模型(阻塞、异步等)
+https://www.cnblogs.com/crazymakercircle/p/10225159.html
+
+#### Java IO 模型(BIO,NIO 等) ,Tomcat 用的哪一种模型
+
+tomcat支持:https://blog.csdn.net/fd2025/article/details/80007435
+
+#### 当获取第一个获取锁之后,条件不满足需要释放锁应当怎么做?
+
+https://www.jianshu.com/p/eb112b25b848
+
+#### 手写一个线程安全的生产者与消费者。
+
+https://blog.csdn.net/u010983881/article/details/78554671
+
+#### 进程和线程调度方法
+
+https://www.jianshu.com/p/91c8600cb2ae
+
### linux
#### linux查找命令
-- https://blog.51cto.com/whylinux/2043871
+https://blog.51cto.com/whylinux/2043871
#### 项目部署常见linux命令
-- https://blog.csdn.net/u010938610/article/details/79625988
+https://blog.csdn.net/u010938610/article/details/79625988
-- 进程文件里有哪些信息
+#### 进程文件里有哪些信息
#### sed 和 awk 的区别
-- awk用法:https://www.cnblogs.com/isykw/p/6258781.html
+awk用法:https://www.cnblogs.com/isykw/p/6258781.html
其实sed和awk都是每次读入一行来处理的,区别是:sed 适合简单的文本替换和搜索;而awk除了自动给你分列之外,里面丰富的函数大大增强了awk的功能。数据统计,正则表达式搜索,逻辑处理,前后置脚本等。因此基本上sed能做的,awk可以全部完成并且做的更好。
@@ -838,8 +1085,6 @@ https://zhuanlan.zhihu.com/p/54593244
https://blog.csdn.net/qingmu0803/article/details/38271077
-
-
#### 有一个文件被锁住,如何查看锁住它的线程?
#### 如何查看一个文件第100行到150行的内容
@@ -852,7 +1097,7 @@ https://www.cnblogs.com/freeweb/p/5407105.html
https://blog.csdn.net/inuyashaw/article/details/55095545
-- linux 如何查找文件
+#### linux 如何查找文件
linux命令:https://juejin.im/post/5d3857eaf265da1bd04f2437
@@ -860,91 +1105,93 @@ linux命令:https://juejin.im/post/5d3857eaf265da1bd04f2437
https://juejin.im/post/5b624f4d518825068302aee9#heading-13
-### 安全加密
-
-- http://www.ruanyifeng.com/blog/2011/08/what_is_a_digital_signature.html
-- https://yq.aliyun.com/articles/54155
+### 十一 框架其他
-#### web安全问题
+#### Servlet 的 Filter 用的什么设计模式
-https://juejin.im/post/5da44c5de51d45783a772a22
+https://www.jianshu.com/p/e4197a54828d
-### 分布式
+#### zookeeper 的常用功能,自己用它来做什么
-#### dubbo中的dubbo协议和http协议有什么区别?
+#### redis 的操作是不是原子操作
-- https://blog.csdn.net/wjw_77/article/details/99696757
+https://juejin.im/entry/58f9e22044d9040069d40dca
-### 项目及规划
+#### 秒杀业务场景设计
-1. 对你来说影响最大的一个项目(该面试中有关项目问题都针对该项目展开)?
+#### 如何设计淘宝秒杀系统(重点关注架构,比如数据一致性,数据库集群一致性哈希,缓存, 分库分表等等)
-2. 项目哪一部分最难攻克?如何攻克?
-个人建议:大家一定要选自己印象最深的项目回答,首先按模块,然后组成 人员,最后你在项目中的角色和发挥 的作用。全程组织好语言,最好不要有停顿,面试官可以 看出你对项目的熟悉程度
+#### 对后台的优化有了解吗?比如负载均衡
-3. 你觉得你在项目运行过程中作为组长是否最大限度发挥了组员的优势?具体事例?
+我给面试官说了 Ngix+Tomcat 负载均 衡,异步处理(消息缓冲服务器),缓存(Redis, Memcache), NoSQL,数据库优化,存储索引优化
-4. 职业规划,今天想发展的工作方向
-5. 项目里我遇到过的最大的困难是什么
+#### 对 Restful 了解 Restful 的认识,优点,以及和 soap 的区别
-6. 实验室的新来的研一,你会给他们什么学习上的建议,例如对于内核源码的枯 燥如何克服
+https://www.ruanyifeng.com/blog/2011/09/restful.html
-7. 如何协调团队中多人的工作
+#### lrucache 的基本原理
-8. 当团队中有某人的任务没有完成的很好,如何处理
-9. 平时看些什么书,技术 综合
+### 十二 设计模式
-10. 项目解决的什么问题 用到了哪些技术
+#### Java常见设计模式
-11. 怎么预防 bug 日志 jvm 异常信息 如何找问题的根源(统计表格)
+- https://www.jianshu.com/p/61b67ca754a3
+- 单例模式(双检锁模式)、简单工厂、观察者模式、适配器模式、职责链模式等等
+- 享元模式模式 选两个画下 UML 图
+- 手写单例
+写的是静态内部类的单例,然后他问我这个地方为什么用 private,这儿为啥用 static, 这就考察你的基本功啦
+- 静态类与单例模式的区别
+- 单例模式 double check 单例模式都有什么,都是否线程安全,怎么改进(从 synchronized 到 双重检验锁 到 枚举 Enum)
+- 基本的设计模式及其核心思想
+- 来,我们写一个单例模式的实现
-12. 你是怎么学习的,说完会让举个例子
+### 十三 分布式
-13. 实习投了哪几个公司?为什么,原因
+#### dubbo中的dubbo协议和http协议有什么区别?
-14. 最得意的项目是什么?为什么?(回答因为项目对实际作用大,并得到认可)
+https://blog.csdn.net/wjw_77/article/details/99696757
-15. 最得意的项目内容,讲了会
+#### 负载均衡
-16. 你简历上写的是最想去的部门不是我们部门,来我们部门的话对你有影响麽?
+https://juejin.im/post/5b39eea0e51d4558c1010e36
-17. 你除了在学校还有哪些方式去获取知识和技术?
+#### 分布式锁的实现方式及优缺点
-18. 你了解阿里文化和阿里开源吗?
+https://zhuanlan.zhihu.com/p/62158041
-19. 遇到困难解决问题的思路?
+#### CAP
-20. 我觉得最成功的一件事了
+https://www.jianshu.com/p/8025e3346734
-我说能说几件吗,说了我大学明白明白了 自己想干什么,选择了自己喜欢的事,大学里学会了和自己相处,自己一个人的 时候也不会感觉无聊,精神世界比较丰富,坚持锻炼,健身,有个很不错的身体, 然后顿了顿笑着说,说,有一个对我很好的女朋友算吗?
+#### 如何实现分布式缓存
-21. 压力大的时候怎么调整?多个任务冲突了你怎么协调的?
+redis如何实现分布式缓存
+https://stor.51cto.com/art/201912/607229.htm
-22. 家里有几个孩子,父母对你来北京有什么看法?
+### 十四 其他
-23. 职业生涯规划
+##### Java 8 函数式编程 回调函数
-24. 你在什么情况下可能会离职
+#### 函数式编程,面向对象之间区别
-25. 对你影响最大的人
+#### Java 8 中 stream 迭代的优势和区别?
-26. 1. 优点 3 个,以及缺点 2. 说说你应聘这个岗位的优势 3. 说说家庭 4. 为什么 想来网易,用过网易的哪些产品,对比下有什么好的地方 5. 投递了哪些公司,对第一份工 作怎么看待
+#### 同步等于可见性吗?
-27. 为什么要选择互联网(楼主偏底层的)
+保证了可见性不等于正确同步,因为还有原子性没考虑。
-28. 为什么来网易(看你如何夸)
+#### git底层数据结构
-29. 在校期间怎样学习
+https://blog.csdn.net/leo187/article/details/106233706
-30. 经常逛的技术性网站有哪些?
+#### 安全加密
-31. 举出你在开发过程中遇到的原先不知道的 bug, 通过各种方式定位 bug 并最终 成功解决的例子
+http://www.ruanyifeng.com/blog/2011/08/what_is_a_digital_signature.html
+https://yq.aliyun.com/articles/54155
-32. 举出一个例子说明你的自学能力 7 次面试记录,除了京东基本上也都走到了很后面的阶段。硬要说经验可能有三点:
+#### web安全问题
-- 不会就不会。我比较爽快,如果遇到的不会的甚至是不确定的,都直接说:“对不起, 我答不上来”之类的。
-- 一技之长。中间件和架构相关的实习经历,让我基本上和面试官都可以聊的很多, 也可以看到,我整个过程没有多少算法题。是因为面试官和你聊完项目就知道你能 做事了。其实,面试官很不愿意出算法题的(BAT 那个档次除外),你能和他扯技 术他当然高兴了。关键很多人只会算法(逃)。
-- 基础非常重要。面试官只要问 Java 相关的基础,我都有自信让一般的面试官感觉 惊讶,甚至学到新知识
\ No newline at end of file
+https://juejin.im/post/5da44c5de51d45783a772a22
diff --git "a/docs/interview/\345\267\262\346\212\225\345\205\254\345\217\270\346\203\205\345\206\265.md" "b/docs/interview/\345\267\262\346\212\225\345\205\254\345\217\270\346\203\205\345\206\265.md"
index c1bb8fe..3ca1b9c 100644
--- "a/docs/interview/\345\267\262\346\212\225\345\205\254\345\217\270\346\203\205\345\206\265.md"
+++ "b/docs/interview/\345\267\262\346\212\225\345\205\254\345\217\270\346\203\205\345\206\265.md"
@@ -5,7 +5,7 @@
|蘑菇街|http://job.mogujie.com/#/candidate/perfectInfo | | offer |
|虎牙| | | 不匹配 |
|远景|https://campus.envisioncn.com/ | | 笔试 |
-|阿里钉钉| https://campus.alibaba.com/myJobApply.htm?saveResume=yes&t=1584782560963 |https://www.nowcoder.com/discuss/368915?type=0&order=0&pos=25&page=3| 一面 |
+|阿里钉钉| https://campus.alibaba.com/myJobApply.htm?saveResume=yes&t=1584782560963 |https://www.nowcoder.com/discuss/368915?type=0&order=0&pos=25&page=3| 二面 |
|阿里新零售| |https://www.nowcoder.com/discuss/374171?type=0&order=0&pos=35&page=1 https://www.nowcoder.com/discuss/372118?type=0&order=0&pos=80&page=2| |
|深信服| |https://www.nowcoder.com/discuss/369399?type=0&order=0&pos=40&page=6| |
|CVTE| |https://www.nowcoder.com/discuss/368463?type=0&order=0&pos=87&page=3| 已投 |
@@ -15,13 +15,13 @@
|拼多多| https://pinduoduo.zhiye.com/Portal/Apply/Index | https://www.nowcoder.com/discuss/393350?type=post&order=time&pos=&page=8 | 已投 |
|腾讯| https://join.qq.com/center.php |https://www.nowcoder.com/discuss/377813?type=post&order=time&pos=&page=1| offer |
|猿辅导| https://app.mokahr.com/m/candidate/applications/deliver-query/fenbi |https://www.nowcoder.com/discuss/375610?type=0&order=0&pos=95&page=2| 已投 |
-|斗鱼| https://app.mokahr.com/m/candidate/applications/deliver-query/douyu |https://www.nowcoder.com/discuss/375180?type=0&order=0&pos=158&page=1| 笔试 |
+|斗鱼| https://app.mokahr.com/m/candidate/applications/deliver-query/douyu |https://www.nowcoder.com/discuss/375180?type=0&order=0&pos=158&page=1| 一面挂 |
|淘宝技术部| |https://www.nowcoder.com/discuss/374655?type=0&order=0&pos=165&page=6| |
|字节跳动| https://job.bytedance.com/user https://job.bytedance.com/referral/pc/position/application?lightning=1&token=MzsxNTg0MTU2NDIxMDIzOzY2ODgyMjg1NzI1Mjk3MjI4ODM7MA/profile/ |https://www.nowcoder.com/discuss/381888?type=post&order=time&pos=&page=2| 已投 |
|陌陌| | 来自内推军 |已投|
|网易| http://gzgame.campus.163.com/applyPosition.do?&lan=zh |https://www.nowcoder.com/discuss/373132?type=post&order=create&pos=&page=1 一姐| 已投|
|百度| https://talent.baidu.com/external/baidu/index.html#/individualCenter |https://www.nowcoder.com/discuss/376515?type=post&order=time&pos=&page=1| 已投 |
-|京东| http://campus.jd.com/web/resume/resume_index?fxType=0 |https://www.nowcoder.com/discuss/372978?type=post&order=time&pos=&page=4| 已投 |
+|京东| http://campus.jd.com/web/resume/resume_index?fxType=0 |https://www.nowcoder.com/discuss/372978?type=post&order=time&pos=&page=4| 二面挂 |
|爱奇艺| | | |
|科大讯飞| | | |
|度小满| | https://www.nowcoder.com/discuss/387950?type=post&order=time&pos=&page=13 | 已投 |
\ No newline at end of file
diff --git "a/docs/interview/\350\207\252\346\210\221\344\273\213\347\273\215\345\222\214\351\241\271\347\233\256\344\273\213\347\273\215.md" "b/docs/interview/\350\207\252\346\210\221\344\273\213\347\273\215\345\222\214\351\241\271\347\233\256\344\273\213\347\273\215.md"
index 374650b..3825b52 100644
--- "a/docs/interview/\350\207\252\346\210\221\344\273\213\347\273\215\345\222\214\351\241\271\347\233\256\344\273\213\347\273\215.md"
+++ "b/docs/interview/\350\207\252\346\210\221\344\273\213\347\273\215\345\222\214\351\241\271\347\233\256\344\273\213\347\273\215.md"
@@ -35,20 +35,8 @@
4、对数据库性能进行调优
5、开发抢购活动功能模块(业务需求)
-### 北京项目介绍
-这个项目的背景是,现在深空探索的研究越来越热,这个项目就是研究及开发脉冲星导航的相关问题,而脉冲星导航的能够解决深空探索航天器的位置问题,这个就是北京项目的研究目标。
-
-脉冲星是一颗稳定的中子星,能够发出稳定的脉冲波,通过接收脉冲星发出来的脉冲波,可以确定航天器在太空中的位置。
-
-而我在北京的工作是:
-1、开发一个软件
-2、实现相关算法对航天器上的部件精度进行控制
-3、航天器相关的数据进行数据显示
-4、近地卫星轨道5星编队仿真实现
-
-
-2019年5月–2019年9月,在####实习,参加了脉冲星导航的项目开发工作。项目的背景是,现在深空探索的研究越来越热,这个项目就是研究及开发脉冲星导航的相关问题,而脉冲星导航的能够解决深空探索航天器的位置问题,这个就是项目的研究目标。
+2019年5月–2019年9月,在中国空间技术研究院钱学森空间技术实验室实习,参加了脉冲星导航的项目开发工作。项目的背景是,现在深空探索的研究越来越热,这个项目就是研究及开发脉冲星导航的相关问题,而脉冲星导航的能够解决深空探索航天器的位置问题,这个就是项目的研究目标。
负责工作:
1、开发一个脉冲星导航仿真软件
2、实现相关算法对航天器上的部件精度进行控制
@@ -69,4 +57,6 @@

-2、给俺一个 **star** 呗,可以让更多的人看到这篇文章,顺便激励下我继续写作,嘻嘻。
\ No newline at end of file
+2、给俺一个 **star** 呗,可以让更多的人看到这篇文章,顺便激励下我继续写作,嘻嘻。
+
+
diff --git a/docs/java/BIO-NIO-AIO.md b/docs/java/BIO-NIO-AIO.md
deleted file mode 100644
index 36aac43..0000000
--- a/docs/java/BIO-NIO-AIO.md
+++ /dev/null
@@ -1,346 +0,0 @@
-熟练掌握 BIO,NIO,AIO 的基本概念以及一些常见问题是你准备面试的过程中不可或缺的一部分,另外这些知识点也是你学习 Netty 的基础。
-
-
-
-- [BIO,NIO,AIO 总结](#bionioaio-总结)
- - [1. BIO \(Blocking I/O\)](#1-bio-blocking-io)
- - [1.1 传统 BIO](#11-传统-bio)
- - [1.2 伪异步 IO](#12-伪异步-io)
- - [1.3 代码示例](#13-代码示例)
- - [1.4 总结](#14-总结)
- - [2. NIO \(New I/O\)](#2-nio-new-io)
- - [2.1 NIO 简介](#21-nio-简介)
- - [2.2 NIO的特性/NIO与IO区别](#22-nio的特性nio与io区别)
- - [1)Non-blocking IO(非阻塞IO)](#1non-blocking-io(非阻塞io))
- - [2)Buffer\(缓冲区\)](#2buffer缓冲区)
- - [3)Channel \(通道\)](#3channel-通道)
- - [4)Selectors\(选择器\)](#4selectors选择器)
- - [2.3 NIO 读数据和写数据方式](#23-nio-读数据和写数据方式)
- - [2.4 NIO核心组件简单介绍](#24-nio核心组件简单介绍)
- - [2.5 代码示例](#25-代码示例)
- - [3. AIO \(Asynchronous I/O\)](#3-aio-asynchronous-io)
- - [参考](#参考)
-
-
-
-
-# BIO,NIO,AIO 总结
-
- Java 中的 BIO、NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装。程序员在使用这些 API 的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码。只需要使用Java的API就可以了。
-
-在讲 BIO,NIO,AIO 之前先来回顾一下这样几个概念:同步与异步,阻塞与非阻塞。
-
-**同步与异步**
-
-- **同步:** 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回。
-- **异步:** 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但是被调用者并没有返回结果,此时我们可以处理其他的请求,被调用者通常依靠事件,回调等机制来通知调用者其返回结果。
-
-同步和异步的区别最大在于异步的话调用者不需要等待处理结果,被调用者会通过回调等机制来通知调用者其返回结果。
-
-**阻塞和非阻塞**
-
-- **阻塞:** 阻塞就是发起一个请求,调用者一直等待请求结果返回,也就是当前线程会被挂起,无法从事其他任务,只有当条件就绪才能继续。
-- **非阻塞:** 非阻塞就是发起一个请求,调用者不用一直等着结果返回,可以先去干其他事情。
-
-举个生活中简单的例子,你妈妈让你烧水,小时候你比较笨啊,在那里傻等着水开(**同步阻塞**)。等你稍微再长大一点,你知道每次烧水的空隙可以去干点其他事,然后只需要时不时来看看水开了没有(**同步非阻塞**)。后来,你们家用上了水开了会发出声音的壶,这样你就只需要听到响声后就知道水开了,在这期间你可以随便干自己的事情,你需要去倒水了(**异步非阻塞**)。
-
-
-## 1. BIO (Blocking I/O)
-
-同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。
-
-### 1.1 传统 BIO
-
-BIO通信(一请求一应答)模型图如下(图源网络,原出处不明):
-
-
-
-采用 **BIO 通信模型** 的服务端,通常由一个独立的 Acceptor 线程负责监听客户端的连接。我们一般通过在`while(true)` 循环中服务端会调用 `accept()` 方法等待接收客户端的连接的方式监听请求,请求一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成, 不过可以通过多线程来支持多个客户端的连接,如上图所示。
-
-如果要让 **BIO 通信模型** 能够同时处理多个客户端请求,就必须使用多线程(主要原因是`socket.accept()`、`socket.read()`、`socket.write()` 涉及的三个主要函数都是同步阻塞的),也就是说它在接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,处理完成之后,通过输出流返回应答给客户端,线程销毁。这就是典型的 **一请求一应答通信模型** 。我们可以设想一下如果这个连接不做任何事情的话就会造成不必要的线程开销,不过可以通过 **线程池机制** 改善,线程池还可以让线程的创建和回收成本相对较低。使用`FixedThreadPool` 可以有效的控制了线程的最大数量,保证了系统有限的资源的控制,实现了N(客户端请求数量):M(处理客户端请求的线程数量)的伪异步I/O模型(N 可以远远大于 M),下面一节"伪异步 BIO"中会详细介绍到。
-
-**我们再设想一下当客户端并发访问量增加后这种模型会出现什么问题?**
-
-在 Java 虚拟机中,线程是宝贵的资源,线程的创建和销毁成本很高,除此之外,线程的切换成本也是很高的。尤其在 Linux 这样的操作系统中,线程本质上就是一个进程,创建和销毁线程都是重量级的系统函数。如果并发访问量增加会导致线程数急剧膨胀可能会导致线程堆栈溢出、创建新线程失败等问题,最终导致进程宕机或者僵死,不能对外提供服务。
-
-### 1.2 伪异步 IO
-
-为了解决同步阻塞I/O面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化一一一后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数M:线程池最大线程数N的比例关系,其中M可以远远大于N.通过线程池可以灵活地调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。
-
-伪异步IO模型图(图源网络,原出处不明):
-
-
-
-采用线程池和任务队列可以实现一种叫做伪异步的 I/O 通信框架,它的模型图如上图所示。当有新的客户端接入时,将客户端的 Socket 封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK 的线程池维护一个消息队列和 N 个活跃线程,对消息队列中的任务进行处理。由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
-
-伪异步I/O通信框架采用了线程池实现,因此避免了为每个请求都创建一个独立线程造成的线程资源耗尽问题。不过因为它的底层仍然是同步阻塞的BIO模型,因此无法从根本上解决问题。
-
-### 1.3 代码示例
-
-下面代码中演示了BIO通信(一请求一应答)模型。我们会在客户端创建多个线程依次连接服务端并向其发送"当前时间+:hello world",服务端会为每个客户端线程创建一个线程来处理。代码示例出自闪电侠的博客,原地址如下:
-
-[https://www.jianshu.com/p/a4e03835921a](https://www.jianshu.com/p/a4e03835921a)
-
-**客户端**
-
-```java
-/**
- *
- * @author 闪电侠
- * @date 2018年10月14日
- * @Description:客户端
- */
-public class IOClient {
-
- public static void main(String[] args) {
- // TODO 创建多个线程,模拟多个客户端连接服务端
- new Thread(() -> {
- try {
- Socket socket = new Socket("127.0.0.1", 3333);
- while (true) {
- try {
- socket.getOutputStream().write((new Date() + ": hello world").getBytes());
- Thread.sleep(2000);
- } catch (Exception e) {
- }
- }
- } catch (IOException e) {
- }
- }).start();
-
- }
-
-}
-
-```
-
-**服务端**
-
-```java
-/**
- * @author 闪电侠
- * @date 2018年10月14日
- * @Description: 服务端
- */
-public class IOServer {
-
- public static void main(String[] args) throws IOException {
- // TODO 服务端处理客户端连接请求
- ServerSocket serverSocket = new ServerSocket(3333);
-
- // 接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理
- new Thread(() -> {
- while (true) {
- try {
- // 阻塞方法获取新的连接
- Socket socket = serverSocket.accept();
-
- // 每一个新的连接都创建一个线程,负责读取数据
- new Thread(() -> {
- try {
- int len;
- byte[] data = new byte[1024];
- InputStream inputStream = socket.getInputStream();
- // 按字节流方式读取数据
- while ((len = inputStream.read(data)) != -1) {
- System.out.println(new String(data, 0, len));
- }
- } catch (IOException e) {
- }
- }).start();
-
- } catch (IOException e) {
- }
-
- }
- }).start();
-
- }
-
-}
-```
-
-### 1.4 总结
-
-在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
-
-
-
-## 2. NIO (New I/O)
-
-### 2.1 NIO 简介
-
- NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。
-
-NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 `Socket` 和 `ServerSocket` 相对应的 `SocketChannel` 和 `ServerSocketChannel` 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。
-
-### 2.2 NIO的特性/NIO与IO区别
-
-如果是在面试中回答这个问题,我觉得首先肯定要从 NIO 流是非阻塞 IO 而 IO 流是阻塞 IO 说起。然后,可以从 NIO 的3个核心组件/特性为 NIO 带来的一些改进来分析。如果,你把这些都回答上了我觉得你对于 NIO 就有了更为深入一点的认识,面试官问到你这个问题,你也能很轻松的回答上来了。
-
-#### 1)Non-blocking IO(非阻塞IO)
-
-**IO流是阻塞的,NIO流是不阻塞的。**
-
-Java NIO使我们可以进行非阻塞IO操作。比如说,单线程中从通道读取数据到buffer,同时可以继续做别的事情,当数据读取到buffer中后,线程再继续处理数据。写数据也是一样的。另外,非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。
-
-Java IO的各种流是阻塞的。这意味着,当一个线程调用 `read()` 或 `write()` 时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了
-
-#### 2)Buffer(缓冲区)
-
-**IO 面向流(Stream oriented),而 NIO 面向缓冲区(Buffer oriented)。**
-
-Buffer是一个对象,它包含一些要写入或者要读出的数据。在NIO类库中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中·可以将数据直接写入或者将数据直接读到 Stream 对象中。虽然 Stream 中也有 Buffer 开头的扩展类,但只是流的包装类,还是从流读到缓冲区,而 NIO 却是直接读到 Buffer 中进行操作。
-
-在NIO厍中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的; 在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。
-
-最常用的缓冲区是 ByteBuffer,一个 ByteBuffer 提供了一组功能用于操作 byte 数组。除了ByteBuffer,还有其他的一些缓冲区,事实上,每一种Java基本类型(除了Boolean类型)都对应有一种缓冲区。
-
-#### 3)Channel (通道)
-
-NIO 通过Channel(通道) 进行读写。
-
-通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。因为 Buffer,通道可以异步地读写。
-
-#### 4)Selector (选择器)
-
-NIO有选择器,而IO没有。
-
-选择器用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此,为了提高系统效率选择器是有用的。
-
-
-
-### 2.3 NIO 读数据和写数据方式
-通常来说NIO中的所有IO都是从 Channel(通道) 开始的。
-
-- 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。
-- 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。
-
-数据读取和写入操作图示:
-
-
-
-
-### 2.4 NIO核心组件简单介绍
-
-NIO 包含下面几个核心的组件:
-
-- Channel(通道)
-- Buffer(缓冲区)
-- Selector(选择器)
-
-整个NIO体系包含的类远远不止这三个,只能说这三个是NIO体系的“核心API”。我们上面已经对这三个概念进行了基本的阐述,这里就不多做解释了。
-
-### 2.5 代码示例
-
-代码示例出自闪电侠的博客,原地址如下:
-
-[https://www.jianshu.com/p/a4e03835921a](https://www.jianshu.com/p/a4e03835921a)
-
-客户端 IOClient.java 的代码不变,我们对服务端使用 NIO 进行改造。以下代码较多而且逻辑比较复杂,大家看看就好。
-
-```java
-/**
- *
- * @author 闪电侠
- * @date 2019年2月21日
- * @Description: NIO 改造后的服务端
- */
-public class NIOServer {
- public static void main(String[] args) throws IOException {
- // 1. serverSelector负责轮询是否有新的连接,服务端监测到新的连接之后,不再创建一个新的线程,
- // 而是直接将新连接绑定到clientSelector上,这样就不用 IO 模型中 1w 个 while 循环在死等
- Selector serverSelector = Selector.open();
- // 2. clientSelector负责轮询连接是否有数据可读
- Selector clientSelector = Selector.open();
-
- new Thread(() -> {
- try {
- // 对应IO编程中服务端启动
- ServerSocketChannel listenerChannel = ServerSocketChannel.open();
- listenerChannel.socket().bind(new InetSocketAddress(3333));
- listenerChannel.configureBlocking(false);
- listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);
-
- while (true) {
- // 监测是否有新的连接,这里的1指的是阻塞的时间为 1ms
- if (serverSelector.select(1) > 0) {
- Set set = serverSelector.selectedKeys();
- Iterator keyIterator = set.iterator();
-
- while (keyIterator.hasNext()) {
- SelectionKey key = keyIterator.next();
-
- if (key.isAcceptable()) {
- try {
- // (1) 每来一个新连接,不需要创建一个线程,而是直接注册到clientSelector
- SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
- clientChannel.configureBlocking(false);
- clientChannel.register(clientSelector, SelectionKey.OP_READ);
- } finally {
- keyIterator.remove();
- }
- }
-
- }
- }
- }
- } catch (IOException ignored) {
- }
- }).start();
- new Thread(() -> {
- try {
- while (true) {
- // (2) 批量轮询是否有哪些连接有数据可读,这里的1指的是阻塞的时间为 1ms
- if (clientSelector.select(1) > 0) {
- Set set = clientSelector.selectedKeys();
- Iterator keyIterator = set.iterator();
-
- while (keyIterator.hasNext()) {
- SelectionKey key = keyIterator.next();
-
- if (key.isReadable()) {
- try {
- SocketChannel clientChannel = (SocketChannel) key.channel();
- ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
- // (3) 面向 Buffer
- clientChannel.read(byteBuffer);
- byteBuffer.flip();
- System.out.println(
- Charset.defaultCharset().newDecoder().decode(byteBuffer).toString());
- } finally {
- keyIterator.remove();
- key.interestOps(SelectionKey.OP_READ);
- }
- }
-
- }
- }
- }
- } catch (IOException ignored) {
- }
- }).start();
-
- }
-}
-```
-
-为什么大家都不愿意用 JDK 原生 NIO 进行开发呢?从上面的代码中大家都可以看出来,是真的难用!除了编程复杂、编程模型难之外,它还有以下让人诟病的问题:
-
-- JDK 的 NIO 底层由 epoll 实现,该实现饱受诟病的空轮询 bug 会导致 cpu 飙升 100%
-- 项目庞大之后,自行实现的 NIO 很容易出现各类 bug,维护成本较高,上面这一坨代码我都不能保证没有 bug
-
-Netty 的出现很大程度上改善了 JDK 原生 NIO 所存在的一些让人难以忍受的问题。
-
-### 3. AIO (Asynchronous I/O)
-
-AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
-
-AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。(除了 AIO 其他的 IO 类型都是同步的,这一点可以从底层IO线程模型解释,推荐一篇文章:[《漫话:如何给女朋友解释什么是Linux的五种IO模型?》](https://mp.weixin.qq.com/s?__biz=Mzg3MjA4MTExMw==&mid=2247484746&idx=1&sn=c0a7f9129d780786cabfcac0a8aa6bb7&source=41#wechat_redirect) )
-
-查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
-
-## 参考
-
-- 《Netty 权威指南》第二版
-- https://zhuanlan.zhihu.com/p/23488863 (美团技术团队)
diff --git a/docs/java/Basis/Arrays,CollectionsCommonMethods.md b/docs/java/Basis/Arrays,CollectionsCommonMethods.md
deleted file mode 100644
index 0710de4..0000000
--- a/docs/java/Basis/Arrays,CollectionsCommonMethods.md
+++ /dev/null
@@ -1,383 +0,0 @@
-
-
-- [Collections 工具类和 Arrays 工具类常见方法](#collections-工具类和-arrays-工具类常见方法)
- - [Collections](#collections)
- - [排序操作](#排序操作)
- - [查找,替换操作](#查找替换操作)
- - [同步控制](#同步控制)
- - [Arrays类的常见操作](#arrays类的常见操作)
- - [排序 : `sort()`](#排序--sort)
- - [查找 : `binarySearch()`](#查找--binarysearch)
- - [比较: `equals()`](#比较-equals)
- - [填充 : `fill()`](#填充--fill)
- - [转列表 `asList()`](#转列表-aslist)
- - [转字符串 `toString()`](#转字符串-tostring)
- - [复制 `copyOf()`](#复制-copyof)
-
-
-# Collections 工具类和 Arrays 工具类常见方法
-
-## Collections
-
-Collections 工具类常用方法:
-
-1. 排序
-2. 查找,替换操作
-3. 同步控制(不推荐,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合)
-
-### 排序操作
-
-```java
-void reverse(List list)//反转
-void shuffle(List list)//随机排序
-void sort(List list)//按自然排序的升序排序
-void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑
-void swap(List list, int i , int j)//交换两个索引位置的元素
-void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面。
-```
-
-**示例代码:**
-
-```java
- ArrayList arrayList = new ArrayList();
- arrayList.add(-1);
- arrayList.add(3);
- arrayList.add(3);
- arrayList.add(-5);
- arrayList.add(7);
- arrayList.add(4);
- arrayList.add(-9);
- arrayList.add(-7);
- System.out.println("原始数组:");
- System.out.println(arrayList);
- // void reverse(List list):反转
- Collections.reverse(arrayList);
- System.out.println("Collections.reverse(arrayList):");
- System.out.println(arrayList);
-
-
- Collections.rotate(arrayList, 4);
- System.out.println("Collections.rotate(arrayList, 4):");
- System.out.println(arrayList);
-
- // void sort(List list),按自然排序的升序排序
- Collections.sort(arrayList);
- System.out.println("Collections.sort(arrayList):");
- System.out.println(arrayList);
-
- // void shuffle(List list),随机排序
- Collections.shuffle(arrayList);
- System.out.println("Collections.shuffle(arrayList):");
- System.out.println(arrayList);
-
- // void swap(List list, int i , int j),交换两个索引位置的元素
- Collections.swap(arrayList, 2, 5);
- System.out.println("Collections.swap(arrayList, 2, 5):");
- System.out.println(arrayList);
-
- // 定制排序的用法
- Collections.sort(arrayList, new Comparator() {
-
- @Override
- public int compare(Integer o1, Integer o2) {
- return o2.compareTo(o1);
- }
- });
- System.out.println("定制排序后:");
- System.out.println(arrayList);
-```
-
-### 查找,替换操作
-
-```java
-int binarySearch(List list, Object key)//对List进行二分查找,返回索引,注意List必须是有序的
-int max(Collection coll)//根据元素的自然顺序,返回最大的元素。 类比int min(Collection coll)
-int max(Collection coll, Comparator c)//根据定制排序,返回最大元素,排序规则由Comparatator类控制。类比int min(Collection coll, Comparator c)
-void fill(List list, Object obj)//用指定的元素代替指定list中的所有元素。
-int frequency(Collection c, Object o)//统计元素出现次数
-int indexOfSubList(List list, List target)//统计target在list中第一次出现的索引,找不到则返回-1,类比int lastIndexOfSubList(List source, list target).
-boolean replaceAll(List list, Object oldVal, Object newVal), 用新元素替换旧元素
-```
-
-**示例代码:**
-
-```java
- ArrayList arrayList = new ArrayList();
- arrayList.add(-1);
- arrayList.add(3);
- arrayList.add(3);
- arrayList.add(-5);
- arrayList.add(7);
- arrayList.add(4);
- arrayList.add(-9);
- arrayList.add(-7);
- ArrayList arrayList2 = new ArrayList();
- arrayList2.add(-3);
- arrayList2.add(-5);
- arrayList2.add(7);
- System.out.println("原始数组:");
- System.out.println(arrayList);
-
- System.out.println("Collections.max(arrayList):");
- System.out.println(Collections.max(arrayList));
-
- System.out.println("Collections.min(arrayList):");
- System.out.println(Collections.min(arrayList));
-
- System.out.println("Collections.replaceAll(arrayList, 3, -3):");
- Collections.replaceAll(arrayList, 3, -3);
- System.out.println(arrayList);
-
- System.out.println("Collections.frequency(arrayList, -3):");
- System.out.println(Collections.frequency(arrayList, -3));
-
- System.out.println("Collections.indexOfSubList(arrayList, arrayList2):");
- System.out.println(Collections.indexOfSubList(arrayList, arrayList2));
-
- System.out.println("Collections.binarySearch(arrayList, 7):");
- // 对List进行二分查找,返回索引,List必须是有序的
- Collections.sort(arrayList);
- System.out.println(Collections.binarySearch(arrayList, 7));
-```
-
-### 同步控制
-
-Collections提供了多个`synchronizedXxx()`方法·,该方法可以将指定集合包装成线程同步的集合,从而解决多线程并发访问集合时的线程安全问题。
-
-我们知道 HashSet,TreeSet,ArrayList,LinkedList,HashMap,TreeMap 都是线程不安全的。Collections提供了多个静态方法可以把他们包装成线程同步的集合。
-
-**最好不要用下面这些方法,效率非常低,需要线程安全的集合类型时请考虑使用 JUC 包下的并发集合。**
-
-方法如下:
-
-```java
-synchronizedCollection(Collection c) //返回指定 collection 支持的同步(线程安全的)collection。
-synchronizedList(List list)//返回指定列表支持的同步(线程安全的)List。
-synchronizedMap(Map m) //返回由指定映射支持的同步(线程安全的)Map。
-synchronizedSet(Set s) //返回指定 set 支持的同步(线程安全的)set。
-```
-
-### Collections还可以设置不可变集合,提供了如下三类方法:
-
-```java
-emptyXxx(): 返回一个空的、不可变的集合对象,此处的集合既可以是List,也可以是Set,还可以是Map。
-singletonXxx(): 返回一个只包含指定对象(只有一个或一个元素)的不可变的集合对象,此处的集合可以是:List,Set,Map。
-unmodifiableXxx(): 返回指定集合对象的不可变视图,此处的集合可以是:List,Set,Map。
-上面三类方法的参数是原有的集合对象,返回值是该集合的”只读“版本。
-```
-
-**示例代码:**
-
-```java
- ArrayList arrayList = new ArrayList();
- arrayList.add(-1);
- arrayList.add(3);
- arrayList.add(3);
- arrayList.add(-5);
- arrayList.add(7);
- arrayList.add(4);
- arrayList.add(-9);
- arrayList.add(-7);
- HashSet integers1 = new HashSet<>();
- integers1.add(1);
- integers1.add(3);
- integers1.add(2);
- Map scores = new HashMap();
- scores.put("语文" , 80);
- scores.put("Java" , 82);
-
- //Collections.emptyXXX();创建一个空的、不可改变的XXX对象
- List